From 0a6d775fa7935039de8c81ce669f084c003996d0 Mon Sep 17 00:00:00 2001 From: Anna Prosvetova Date: Sun, 21 Aug 2022 14:51:04 +0200 Subject: [PATCH 001/824] Github: Update CODEOWNERS (#1631) --- .github/CODEOWNERS | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 71acb5f14b8..ea165de2f72 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -17,7 +17,6 @@ /applications/gui/ @skotopes @DrZlo13 @hedger /applications/ibutton/ @skotopes @DrZlo13 @hedger @gsurkov /applications/infrared/ @skotopes @DrZlo13 @hedger @gsurkov -/applications/infrared_monitor/ @skotopes @DrZlo13 @hedger @gsurkov /applications/input/ @skotopes @DrZlo13 @hedger /applications/lfrfid/ @skotopes @DrZlo13 @hedger /applications/lfrfid_debug/ @skotopes @DrZlo13 @hedger @@ -46,11 +45,11 @@ /debug/ @skotopes @DrZlo13 @hedger # Docker -/docker/ @skotopes @DrZlo13 @hedger @aprosvetova -/docker-compose.yml @skotopes @DrZlo13 @hedger @aprosvetova +/docker/ @skotopes @DrZlo13 @hedger @drunkbatya +/docker-compose.yml @skotopes @DrZlo13 @hedger @drunkbatya # Documentation -/documentation/ @skotopes @DrZlo13 @hedger @aprosvetova +/documentation/ @skotopes @DrZlo13 @hedger @drunkbatya # Firmware targets /firmware/ @skotopes @DrZlo13 @hedger @@ -84,8 +83,5 @@ /lib/u8g2/ @skotopes @DrZlo13 @hedger /lib/update_util/ @skotopes @DrZlo13 @hedger -# Make tools -/make/ @skotopes @DrZlo13 @hedger @aprosvetova - # Helper scripts /scripts/ @skotopes @DrZlo13 @hedger From cfc0383b96c0f386a20bd142fb72b650b36e9fea Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Mon, 22 Aug 2022 19:19:03 +0300 Subject: [PATCH 002/824] Archive: dont start browser worker on favourites tab (#1628) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- .../archive/helpers/archive_browser.c | 28 +++++++++++++------ .../archive/helpers/archive_browser.h | 1 - .../archive/views/archive_browser_view.c | 9 ++---- .../archive/views/archive_browser_view.h | 1 + 4 files changed, 23 insertions(+), 16 deletions(-) diff --git a/applications/archive/helpers/archive_browser.c b/applications/archive/helpers/archive_browser.c index 54759dadc95..2dfb9484b81 100644 --- a/applications/archive/helpers/archive_browser.c +++ b/applications/archive/helpers/archive_browser.c @@ -77,14 +77,24 @@ static void archive_long_load_cb(void* context) { }); } -void archive_file_browser_set_callbacks(ArchiveBrowserView* browser) { +static void archive_file_browser_set_path( + ArchiveBrowserView* browser, + string_t path, + const char* filter_ext, + bool skip_assets) { furi_assert(browser); - - file_browser_worker_set_callback_context(browser->worker, browser); - file_browser_worker_set_folder_callback(browser->worker, archive_folder_open_cb); - file_browser_worker_set_list_callback(browser->worker, archive_list_load_cb); - file_browser_worker_set_item_callback(browser->worker, archive_list_item_cb); - file_browser_worker_set_long_load_callback(browser->worker, archive_long_load_cb); + if(!browser->worker_running) { + browser->worker = file_browser_worker_alloc(path, filter_ext, skip_assets); + file_browser_worker_set_callback_context(browser->worker, browser); + file_browser_worker_set_folder_callback(browser->worker, archive_folder_open_cb); + file_browser_worker_set_list_callback(browser->worker, archive_list_load_cb); + file_browser_worker_set_item_callback(browser->worker, archive_list_item_cb); + file_browser_worker_set_long_load_callback(browser->worker, archive_long_load_cb); + browser->worker_running = true; + } else { + furi_assert(browser->worker); + file_browser_worker_set_config(browser->worker, path, filter_ext, skip_assets); + } } bool archive_is_item_in_array(ArchiveBrowserViewModel* model, uint32_t idx) { @@ -438,8 +448,8 @@ void archive_switch_tab(ArchiveBrowserView* browser, InputKey key) { tab = archive_get_tab(browser); if(archive_is_dir_exists(browser->path)) { bool skip_assets = (strcmp(archive_get_tab_ext(tab), "*") == 0) ? false : true; - file_browser_worker_set_config( - browser->worker, browser->path, archive_get_tab_ext(tab), skip_assets); + archive_file_browser_set_path( + browser, browser->path, archive_get_tab_ext(tab), skip_assets); tab_empty = false; // Empty check will be performed later } else { tab_empty = true; diff --git a/applications/archive/helpers/archive_browser.h b/applications/archive/helpers/archive_browser.h index d6c79817a27..ad64a984532 100644 --- a/applications/archive/helpers/archive_browser.h +++ b/applications/archive/helpers/archive_browser.h @@ -87,4 +87,3 @@ void archive_switch_tab(ArchiveBrowserView* browser, InputKey key); void archive_enter_dir(ArchiveBrowserView* browser, string_t name); void archive_leave_dir(ArchiveBrowserView* browser); void archive_refresh_dir(ArchiveBrowserView* browser); -void archive_file_browser_set_callbacks(ArchiveBrowserView* browser); diff --git a/applications/archive/views/archive_browser_view.c b/applications/archive/views/archive_browser_view.c index 810d5c8f787..174071ad49a 100644 --- a/applications/archive/views/archive_browser_view.c +++ b/applications/archive/views/archive_browser_view.c @@ -370,18 +370,15 @@ ArchiveBrowserView* browser_alloc() { return true; }); - browser->worker = file_browser_worker_alloc(browser->path, "*", false); - archive_file_browser_set_callbacks(browser); - - file_browser_worker_set_callback_context(browser->worker, browser); - return browser; } void browser_free(ArchiveBrowserView* browser) { furi_assert(browser); - file_browser_worker_free(browser->worker); + if(browser->worker_running) { + file_browser_worker_free(browser->worker); + } with_view_model( browser->view, (ArchiveBrowserViewModel * model) { diff --git a/applications/archive/views/archive_browser_view.h b/applications/archive/views/archive_browser_view.h index 2de04a166c0..5c649c389de 100644 --- a/applications/archive/views/archive_browser_view.h +++ b/applications/archive/views/archive_browser_view.h @@ -74,6 +74,7 @@ typedef enum { struct ArchiveBrowserView { View* view; BrowserWorker* worker; + bool worker_running; ArchiveBrowserViewCallback callback; void* context; string_t path; From 4e1470cef2bf9ffca3b17ae38cbbce0e7a1f9eb2 Mon Sep 17 00:00:00 2001 From: Lesha Lomalkin Date: Mon, 22 Aug 2022 19:24:08 +0300 Subject: [PATCH 003/824] slideshow.py: add return code on error (#1636) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * slideshow.py: add return code on error * Scripts: remove dead code Co-authored-by: Lesha Lomalkin Co-authored-by: あく --- scripts/slideshow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/slideshow.py b/scripts/slideshow.py index 7626a6aca05..8a9541a7c17 100644 --- a/scripts/slideshow.py +++ b/scripts/slideshow.py @@ -36,7 +36,7 @@ def pack(self): file_idx += 1 except Exception as e: self.logger.error(e) - break + return 3 widths = set(img.width for img in images) heights = set(img.height for img in images) From 84e2e321b432c6073d7aa82cf80105960bd58938 Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Mon, 22 Aug 2022 19:36:45 +0300 Subject: [PATCH 004/824] RPC: more asserts and checks (#1606) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- applications/rpc/rpc.c | 280 ++----------------------- applications/rpc/rpc_app.c | 1 + applications/rpc/rpc_cli.c | 16 +- applications/rpc/rpc_debug.c | 260 +++++++++++++++++++++++ applications/rpc/rpc_gpio.c | 5 - applications/rpc/rpc_i.h | 4 +- applications/rpc/rpc_storage.c | 8 + applications/rpc/rpc_system.c | 4 + applications/unit_tests/rpc/rpc_test.c | 2 +- 9 files changed, 299 insertions(+), 281 deletions(-) create mode 100644 applications/rpc/rpc_debug.c diff --git a/applications/rpc/rpc.c b/applications/rpc/rpc.c index f241a67869d..d767a928d63 100644 --- a/applications/rpc/rpc.c +++ b/applications/rpc/rpc.c @@ -80,13 +80,11 @@ struct Rpc { FuriMutex* busy_mutex; }; -static bool content_callback(pb_istream_t* stream, const pb_field_t* field, void** arg); - static void rpc_close_session_process(const PB_Main* request, void* context) { furi_assert(request); + furi_assert(context); RpcSession* session = (RpcSession*)context; - furi_assert(session); rpc_send_and_release_empty(session, request->command_id, PB_CommandStatus_OK); furi_mutex_acquire(session->callbacks_mutex, FuriWaitForever); @@ -98,264 +96,6 @@ static void rpc_close_session_process(const PB_Main* request, void* context) { furi_mutex_release(session->callbacks_mutex); } -static size_t rpc_sprintf_msg_file( - string_t str, - const char* prefix, - const PB_Storage_File* msg_file, - size_t msg_files_size) { - size_t cnt = 0; - - for(size_t i = 0; i < msg_files_size; ++i, ++msg_file) { - string_cat_printf( - str, - "%s[%c] size: %5ld", - prefix, - msg_file->type == PB_Storage_File_FileType_DIR ? 'd' : 'f', - msg_file->size); - - if(msg_file->name) { - string_cat_printf(str, " \'%s\'", msg_file->name); - } - - if(msg_file->data && msg_file->data->size) { - string_cat_printf( - str, - " (%d):\'%.*s%s\'", - msg_file->data->size, - MIN(msg_file->data->size, 30), - msg_file->data->bytes, - msg_file->data->size > 30 ? "..." : ""); - } - - string_cat_printf(str, "\r\n"); - } - - return cnt; -} - -void rpc_print_data(const char* prefix, uint8_t* buffer, size_t size) { - string_t str; - string_init(str); - string_reserve(str, 100 + size * 5); - - string_cat_printf(str, "\r\n%s DEC(%d): {", prefix, size); - for(size_t i = 0; i < size; ++i) { - string_cat_printf(str, "%d, ", buffer[i]); - } - string_cat_printf(str, "}\r\n"); - - printf("%s", string_get_cstr(str)); - string_reset(str); - string_reserve(str, 100 + size * 3); - - string_cat_printf(str, "%s HEX(%d): {", prefix, size); - for(size_t i = 0; i < size; ++i) { - string_cat_printf(str, "%02X", buffer[i]); - } - string_cat_printf(str, "}\r\n\r\n"); - - printf("%s", string_get_cstr(str)); - string_clear(str); -} - -void rpc_print_message(const PB_Main* message) { - string_t str; - string_init(str); - - string_cat_printf( - str, - "PB_Main: {\r\n\tresult: %d cmd_id: %ld (%s)\r\n", - message->command_status, - message->command_id, - message->has_next ? "has_next" : "last"); - switch(message->which_content) { - default: - /* not implemented yet */ - string_cat_printf(str, "\tNOT_IMPLEMENTED (%d) {\r\n", message->which_content); - break; - case PB_Main_stop_session_tag: - string_cat_printf(str, "\tstop_session {\r\n"); - break; - case PB_Main_app_start_request_tag: { - string_cat_printf(str, "\tapp_start {\r\n"); - const char* name = message->content.app_start_request.name; - const char* args = message->content.app_start_request.args; - if(name) { - string_cat_printf(str, "\t\tname: %s\r\n", name); - } - if(args) { - string_cat_printf(str, "\t\targs: %s\r\n", args); - } - break; - } - case PB_Main_app_lock_status_request_tag: { - string_cat_printf(str, "\tapp_lock_status_request {\r\n"); - break; - } - case PB_Main_app_lock_status_response_tag: { - string_cat_printf(str, "\tapp_lock_status_response {\r\n"); - bool lock_status = message->content.app_lock_status_response.locked; - string_cat_printf(str, "\t\tlocked: %s\r\n", lock_status ? "true" : "false"); - break; - } - case PB_Main_storage_md5sum_request_tag: { - string_cat_printf(str, "\tmd5sum_request {\r\n"); - const char* path = message->content.storage_md5sum_request.path; - if(path) { - string_cat_printf(str, "\t\tpath: %s\r\n", path); - } - break; - } - case PB_Main_storage_md5sum_response_tag: { - string_cat_printf(str, "\tmd5sum_response {\r\n"); - const char* path = message->content.storage_md5sum_response.md5sum; - if(path) { - string_cat_printf(str, "\t\tmd5sum: %s\r\n", path); - } - break; - } - case PB_Main_system_ping_request_tag: - string_cat_printf(str, "\tping_request {\r\n"); - break; - case PB_Main_system_ping_response_tag: - string_cat_printf(str, "\tping_response {\r\n"); - break; - case PB_Main_system_device_info_request_tag: - string_cat_printf(str, "\tdevice_info_request {\r\n"); - break; - case PB_Main_system_device_info_response_tag: - string_cat_printf(str, "\tdevice_info_response {\r\n"); - string_cat_printf( - str, - "\t\t%s: %s\r\n", - message->content.system_device_info_response.key, - message->content.system_device_info_response.value); - break; - case PB_Main_storage_mkdir_request_tag: - string_cat_printf(str, "\tmkdir {\r\n"); - break; - case PB_Main_storage_delete_request_tag: { - string_cat_printf(str, "\tdelete {\r\n"); - const char* path = message->content.storage_delete_request.path; - if(path) { - string_cat_printf(str, "\t\tpath: %s\r\n", path); - } - break; - } - case PB_Main_empty_tag: - string_cat_printf(str, "\tempty {\r\n"); - break; - case PB_Main_storage_info_request_tag: { - string_cat_printf(str, "\tinfo_request {\r\n"); - const char* path = message->content.storage_info_request.path; - if(path) { - string_cat_printf(str, "\t\tpath: %s\r\n", path); - } - break; - } - case PB_Main_storage_info_response_tag: { - string_cat_printf(str, "\tinfo_response {\r\n"); - string_cat_printf( - str, "\t\ttotal_space: %lu\r\n", message->content.storage_info_response.total_space); - string_cat_printf( - str, "\t\tfree_space: %lu\r\n", message->content.storage_info_response.free_space); - break; - } - case PB_Main_storage_stat_request_tag: { - string_cat_printf(str, "\tstat_request {\r\n"); - const char* path = message->content.storage_stat_request.path; - if(path) { - string_cat_printf(str, "\t\tpath: %s\r\n", path); - } - break; - } - case PB_Main_storage_stat_response_tag: { - string_cat_printf(str, "\tstat_response {\r\n"); - if(message->content.storage_stat_response.has_file) { - const PB_Storage_File* msg_file = &message->content.storage_stat_response.file; - rpc_sprintf_msg_file(str, "\t\t\t", msg_file, 1); - } - break; - } - case PB_Main_storage_list_request_tag: { - string_cat_printf(str, "\tlist_request {\r\n"); - const char* path = message->content.storage_list_request.path; - if(path) { - string_cat_printf(str, "\t\tpath: %s\r\n", path); - } - break; - } - case PB_Main_storage_read_request_tag: { - string_cat_printf(str, "\tread_request {\r\n"); - const char* path = message->content.storage_read_request.path; - if(path) { - string_cat_printf(str, "\t\tpath: %s\r\n", path); - } - break; - } - case PB_Main_storage_write_request_tag: { - string_cat_printf(str, "\twrite_request {\r\n"); - const char* path = message->content.storage_write_request.path; - if(path) { - string_cat_printf(str, "\t\tpath: %s\r\n", path); - } - if(message->content.storage_write_request.has_file) { - const PB_Storage_File* msg_file = &message->content.storage_write_request.file; - rpc_sprintf_msg_file(str, "\t\t\t", msg_file, 1); - } - break; - } - case PB_Main_storage_read_response_tag: - string_cat_printf(str, "\tread_response {\r\n"); - if(message->content.storage_read_response.has_file) { - const PB_Storage_File* msg_file = &message->content.storage_read_response.file; - rpc_sprintf_msg_file(str, "\t\t\t", msg_file, 1); - } - break; - case PB_Main_storage_list_response_tag: { - const PB_Storage_File* msg_file = message->content.storage_list_response.file; - size_t msg_file_count = message->content.storage_list_response.file_count; - string_cat_printf(str, "\tlist_response {\r\n"); - rpc_sprintf_msg_file(str, "\t\t", msg_file, msg_file_count); - break; - } - case PB_Main_storage_rename_request_tag: { - string_cat_printf(str, "\trename_request {\r\n"); - string_cat_printf( - str, "\t\told_path: %s\r\n", message->content.storage_rename_request.old_path); - string_cat_printf( - str, "\t\tnew_path: %s\r\n", message->content.storage_rename_request.new_path); - break; - } - case PB_Main_gui_start_screen_stream_request_tag: - string_cat_printf(str, "\tstart_screen_stream {\r\n"); - break; - case PB_Main_gui_stop_screen_stream_request_tag: - string_cat_printf(str, "\tstop_screen_stream {\r\n"); - break; - case PB_Main_gui_screen_frame_tag: - string_cat_printf(str, "\tscreen_frame {\r\n"); - break; - case PB_Main_gui_send_input_event_request_tag: - string_cat_printf(str, "\tsend_input_event {\r\n"); - string_cat_printf( - str, "\t\tkey: %d\r\n", message->content.gui_send_input_event_request.key); - string_cat_printf( - str, "\t\type: %d\r\n", message->content.gui_send_input_event_request.type); - break; - case PB_Main_gui_start_virtual_display_request_tag: - string_cat_printf(str, "\tstart_virtual_display {\r\n"); - break; - case PB_Main_gui_stop_virtual_display_request_tag: - string_cat_printf(str, "\tstop_virtual_display {\r\n"); - break; - } - string_cat_printf(str, "\t}\r\n}\r\n"); - printf("%s", string_get_cstr(str)); - - string_clear(str); -} - void rpc_session_set_context(RpcSession* session, void* context) { furi_assert(session); @@ -409,6 +149,9 @@ void rpc_session_set_terminated_callback( size_t rpc_session_feed(RpcSession* session, uint8_t* encoded_bytes, size_t size, TickType_t timeout) { furi_assert(session); + furi_assert(encoded_bytes); + furi_assert(size > 0); + size_t bytes_sent = xStreamBufferSend(session->stream, encoded_bytes, size, timeout); furi_thread_flags_set(furi_thread_get_id(session->thread), RpcEvtNewData); @@ -422,6 +165,8 @@ size_t rpc_session_get_available_size(RpcSession* session) { } bool rpc_pb_stream_read(pb_istream_t* istream, pb_byte_t* buf, size_t count) { + furi_assert(istream); + furi_assert(buf); RpcSession* session = istream->state; furi_assert(session); furi_assert(istream->bytes_left); @@ -462,16 +207,17 @@ bool rpc_pb_stream_read(pb_istream_t* istream, pb_byte_t* buf, size_t count) { } #if SRV_RPC_DEBUG - rpc_print_data("INPUT", buf, bytes_received); + rpc_debug_print_data("INPUT", buf, bytes_received); #endif return (count == bytes_received); } -static bool content_callback(pb_istream_t* stream, const pb_field_t* field, void** arg) { +static bool rpc_pb_content_callback(pb_istream_t* stream, const pb_field_t* field, void** arg) { furi_assert(stream); RpcSession* session = stream->state; furi_assert(session); + furi_assert(field); RpcHandler* handler = RpcHandlerDict_get(session->handlers, field->tag); @@ -502,7 +248,7 @@ static int32_t rpc_session_worker(void* context) { if(pb_decode_ex(&istream, &PB_Main_msg, session->decoded_message, PB_DECODE_DELIMITED)) { #if SRV_RPC_DEBUG FURI_LOG_I(TAG, "INPUT:"); - rpc_print_message(session->decoded_message); + rpc_debug_print_message(session->decoded_message); #endif RpcHandler* handler = RpcHandlerDict_get(session->handlers, session->decoded_message->which_content); @@ -610,7 +356,7 @@ RpcSession* rpc_session_open(Rpc* rpc) { RpcHandlerDict_init(session->handlers); session->decoded_message = malloc(sizeof(PB_Main)); - session->decoded_message->cb_content.funcs.decode = content_callback; + session->decoded_message->cb_content.funcs.decode = rpc_pb_content_callback; session->decoded_message->cb_content.arg = session; session->system_contexts = malloc(COUNT_OF(rpc_systems) * sizeof(void*)); @@ -678,7 +424,7 @@ void rpc_send(RpcSession* session, PB_Main* message) { #if SRV_RPC_DEBUG FURI_LOG_I(TAG, "OUTPUT:"); - rpc_print_message(message); + rpc_debug_print_message(message); #endif bool result = pb_encode_ex(&ostream, &PB_Main_msg, message, PB_ENCODE_DELIMITED); @@ -690,7 +436,7 @@ void rpc_send(RpcSession* session, PB_Main* message) { pb_encode_ex(&ostream, &PB_Main_msg, message, PB_ENCODE_DELIMITED); #if SRV_RPC_DEBUG - rpc_print_data("OUTPUT", buffer, ostream.bytes_written); + rpc_debug_print_data("OUTPUT", buffer, ostream.bytes_written); #endif furi_mutex_acquire(session->callbacks_mutex, FuriWaitForever); diff --git a/applications/rpc/rpc_app.c b/applications/rpc/rpc_app.c index 555cec8cf03..b8b34170e59 100644 --- a/applications/rpc/rpc_app.c +++ b/applications/rpc/rpc_app.c @@ -299,6 +299,7 @@ void* rpc_system_app_alloc(RpcSession* session) { void rpc_system_app_free(void* context) { RpcAppSystem* rpc_app = context; + furi_assert(rpc_app); RpcSession* session = rpc_app->session; furi_assert(session); diff --git a/applications/rpc/rpc_cli.c b/applications/rpc/rpc_cli.c index efc672193d6..8cb0f76acdf 100644 --- a/applications/rpc/rpc_cli.c +++ b/applications/rpc/rpc_cli.c @@ -14,23 +14,23 @@ typedef struct { #define CLI_READ_BUFFER_SIZE 64 -static void rpc_send_bytes_callback(void* context, uint8_t* bytes, size_t bytes_len) { +static void rpc_cli_send_bytes_callback(void* context, uint8_t* bytes, size_t bytes_len) { furi_assert(context); furi_assert(bytes); - furi_assert(bytes_len); + furi_assert(bytes_len > 0); CliRpc* cli_rpc = context; cli_write(cli_rpc->cli, bytes, bytes_len); } -static void rpc_session_close_callback(void* context) { +static void rpc_cli_session_close_callback(void* context) { furi_assert(context); CliRpc* cli_rpc = context; cli_rpc->session_close_request = true; } -static void rpc_session_terminated_callback(void* context) { +static void rpc_cli_session_terminated_callback(void* context) { furi_check(context); CliRpc* cli_rpc = context; @@ -39,6 +39,8 @@ static void rpc_session_terminated_callback(void* context) { void rpc_cli_command_start_session(Cli* cli, string_t args, void* context) { UNUSED(args); + furi_assert(cli); + furi_assert(context); Rpc* rpc = context; uint32_t mem_before = memmgr_get_free_heap(); @@ -55,9 +57,9 @@ void rpc_cli_command_start_session(Cli* cli, string_t args, void* context) { CliRpc cli_rpc = {.cli = cli, .session_close_request = false}; cli_rpc.terminate_semaphore = furi_semaphore_alloc(1, 0); rpc_session_set_context(rpc_session, &cli_rpc); - rpc_session_set_send_bytes_callback(rpc_session, rpc_send_bytes_callback); - rpc_session_set_close_callback(rpc_session, rpc_session_close_callback); - rpc_session_set_terminated_callback(rpc_session, rpc_session_terminated_callback); + rpc_session_set_send_bytes_callback(rpc_session, rpc_cli_send_bytes_callback); + rpc_session_set_close_callback(rpc_session, rpc_cli_session_close_callback); + rpc_session_set_terminated_callback(rpc_session, rpc_cli_session_terminated_callback); uint8_t* buffer = malloc(CLI_READ_BUFFER_SIZE); size_t size_received = 0; diff --git a/applications/rpc/rpc_debug.c b/applications/rpc/rpc_debug.c new file mode 100644 index 00000000000..9c04bd89a55 --- /dev/null +++ b/applications/rpc/rpc_debug.c @@ -0,0 +1,260 @@ +#include "rpc_i.h" +#include + +static size_t rpc_debug_print_file_msg( + string_t str, + const char* prefix, + const PB_Storage_File* msg_file, + size_t msg_files_size) { + size_t cnt = 0; + + for(size_t i = 0; i < msg_files_size; ++i, ++msg_file) { + string_cat_printf( + str, + "%s[%c] size: %5ld", + prefix, + msg_file->type == PB_Storage_File_FileType_DIR ? 'd' : 'f', + msg_file->size); + + if(msg_file->name) { + string_cat_printf(str, " \'%s\'", msg_file->name); + } + + if(msg_file->data && msg_file->data->size) { + string_cat_printf( + str, + " (%d):\'%.*s%s\'", + msg_file->data->size, + MIN(msg_file->data->size, 30), + msg_file->data->bytes, + msg_file->data->size > 30 ? "..." : ""); + } + + string_cat_printf(str, "\r\n"); + } + + return cnt; +} + +void rpc_debug_print_data(const char* prefix, uint8_t* buffer, size_t size) { + string_t str; + string_init(str); + string_reserve(str, 100 + size * 5); + + string_cat_printf(str, "\r\n%s DEC(%d): {", prefix, size); + for(size_t i = 0; i < size; ++i) { + string_cat_printf(str, "%d, ", buffer[i]); + } + string_cat_printf(str, "}\r\n"); + + printf("%s", string_get_cstr(str)); + string_reset(str); + string_reserve(str, 100 + size * 3); + + string_cat_printf(str, "%s HEX(%d): {", prefix, size); + for(size_t i = 0; i < size; ++i) { + string_cat_printf(str, "%02X", buffer[i]); + } + string_cat_printf(str, "}\r\n\r\n"); + + printf("%s", string_get_cstr(str)); + string_clear(str); +} + +void rpc_debug_print_message(const PB_Main* message) { + string_t str; + string_init(str); + + string_cat_printf( + str, + "PB_Main: {\r\n\tresult: %d cmd_id: %ld (%s)\r\n", + message->command_status, + message->command_id, + message->has_next ? "has_next" : "last"); + switch(message->which_content) { + default: + /* not implemented yet */ + string_cat_printf(str, "\tNOT_IMPLEMENTED (%d) {\r\n", message->which_content); + break; + case PB_Main_stop_session_tag: + string_cat_printf(str, "\tstop_session {\r\n"); + break; + case PB_Main_app_start_request_tag: { + string_cat_printf(str, "\tapp_start {\r\n"); + const char* name = message->content.app_start_request.name; + const char* args = message->content.app_start_request.args; + if(name) { + string_cat_printf(str, "\t\tname: %s\r\n", name); + } + if(args) { + string_cat_printf(str, "\t\targs: %s\r\n", args); + } + break; + } + case PB_Main_app_lock_status_request_tag: { + string_cat_printf(str, "\tapp_lock_status_request {\r\n"); + break; + } + case PB_Main_app_lock_status_response_tag: { + string_cat_printf(str, "\tapp_lock_status_response {\r\n"); + bool lock_status = message->content.app_lock_status_response.locked; + string_cat_printf(str, "\t\tlocked: %s\r\n", lock_status ? "true" : "false"); + break; + } + case PB_Main_storage_md5sum_request_tag: { + string_cat_printf(str, "\tmd5sum_request {\r\n"); + const char* path = message->content.storage_md5sum_request.path; + if(path) { + string_cat_printf(str, "\t\tpath: %s\r\n", path); + } + break; + } + case PB_Main_storage_md5sum_response_tag: { + string_cat_printf(str, "\tmd5sum_response {\r\n"); + const char* path = message->content.storage_md5sum_response.md5sum; + if(path) { + string_cat_printf(str, "\t\tmd5sum: %s\r\n", path); + } + break; + } + case PB_Main_system_ping_request_tag: + string_cat_printf(str, "\tping_request {\r\n"); + break; + case PB_Main_system_ping_response_tag: + string_cat_printf(str, "\tping_response {\r\n"); + break; + case PB_Main_system_device_info_request_tag: + string_cat_printf(str, "\tdevice_info_request {\r\n"); + break; + case PB_Main_system_device_info_response_tag: + string_cat_printf(str, "\tdevice_info_response {\r\n"); + string_cat_printf( + str, + "\t\t%s: %s\r\n", + message->content.system_device_info_response.key, + message->content.system_device_info_response.value); + break; + case PB_Main_storage_mkdir_request_tag: + string_cat_printf(str, "\tmkdir {\r\n"); + break; + case PB_Main_storage_delete_request_tag: { + string_cat_printf(str, "\tdelete {\r\n"); + const char* path = message->content.storage_delete_request.path; + if(path) { + string_cat_printf(str, "\t\tpath: %s\r\n", path); + } + break; + } + case PB_Main_empty_tag: + string_cat_printf(str, "\tempty {\r\n"); + break; + case PB_Main_storage_info_request_tag: { + string_cat_printf(str, "\tinfo_request {\r\n"); + const char* path = message->content.storage_info_request.path; + if(path) { + string_cat_printf(str, "\t\tpath: %s\r\n", path); + } + break; + } + case PB_Main_storage_info_response_tag: { + string_cat_printf(str, "\tinfo_response {\r\n"); + string_cat_printf( + str, "\t\ttotal_space: %lu\r\n", message->content.storage_info_response.total_space); + string_cat_printf( + str, "\t\tfree_space: %lu\r\n", message->content.storage_info_response.free_space); + break; + } + case PB_Main_storage_stat_request_tag: { + string_cat_printf(str, "\tstat_request {\r\n"); + const char* path = message->content.storage_stat_request.path; + if(path) { + string_cat_printf(str, "\t\tpath: %s\r\n", path); + } + break; + } + case PB_Main_storage_stat_response_tag: { + string_cat_printf(str, "\tstat_response {\r\n"); + if(message->content.storage_stat_response.has_file) { + const PB_Storage_File* msg_file = &message->content.storage_stat_response.file; + rpc_debug_print_file_msg(str, "\t\t\t", msg_file, 1); + } + break; + } + case PB_Main_storage_list_request_tag: { + string_cat_printf(str, "\tlist_request {\r\n"); + const char* path = message->content.storage_list_request.path; + if(path) { + string_cat_printf(str, "\t\tpath: %s\r\n", path); + } + break; + } + case PB_Main_storage_read_request_tag: { + string_cat_printf(str, "\tread_request {\r\n"); + const char* path = message->content.storage_read_request.path; + if(path) { + string_cat_printf(str, "\t\tpath: %s\r\n", path); + } + break; + } + case PB_Main_storage_write_request_tag: { + string_cat_printf(str, "\twrite_request {\r\n"); + const char* path = message->content.storage_write_request.path; + if(path) { + string_cat_printf(str, "\t\tpath: %s\r\n", path); + } + if(message->content.storage_write_request.has_file) { + const PB_Storage_File* msg_file = &message->content.storage_write_request.file; + rpc_debug_print_file_msg(str, "\t\t\t", msg_file, 1); + } + break; + } + case PB_Main_storage_read_response_tag: + string_cat_printf(str, "\tread_response {\r\n"); + if(message->content.storage_read_response.has_file) { + const PB_Storage_File* msg_file = &message->content.storage_read_response.file; + rpc_debug_print_file_msg(str, "\t\t\t", msg_file, 1); + } + break; + case PB_Main_storage_list_response_tag: { + const PB_Storage_File* msg_file = message->content.storage_list_response.file; + size_t msg_file_count = message->content.storage_list_response.file_count; + string_cat_printf(str, "\tlist_response {\r\n"); + rpc_debug_print_file_msg(str, "\t\t", msg_file, msg_file_count); + break; + } + case PB_Main_storage_rename_request_tag: { + string_cat_printf(str, "\trename_request {\r\n"); + string_cat_printf( + str, "\t\told_path: %s\r\n", message->content.storage_rename_request.old_path); + string_cat_printf( + str, "\t\tnew_path: %s\r\n", message->content.storage_rename_request.new_path); + break; + } + case PB_Main_gui_start_screen_stream_request_tag: + string_cat_printf(str, "\tstart_screen_stream {\r\n"); + break; + case PB_Main_gui_stop_screen_stream_request_tag: + string_cat_printf(str, "\tstop_screen_stream {\r\n"); + break; + case PB_Main_gui_screen_frame_tag: + string_cat_printf(str, "\tscreen_frame {\r\n"); + break; + case PB_Main_gui_send_input_event_request_tag: + string_cat_printf(str, "\tsend_input_event {\r\n"); + string_cat_printf( + str, "\t\tkey: %d\r\n", message->content.gui_send_input_event_request.key); + string_cat_printf( + str, "\t\type: %d\r\n", message->content.gui_send_input_event_request.type); + break; + case PB_Main_gui_start_virtual_display_request_tag: + string_cat_printf(str, "\tstart_virtual_display {\r\n"); + break; + case PB_Main_gui_stop_virtual_display_request_tag: + string_cat_printf(str, "\tstop_virtual_display {\r\n"); + break; + } + string_cat_printf(str, "\t}\r\n}\r\n"); + printf("%s", string_get_cstr(str)); + + string_clear(str); +} diff --git a/applications/rpc/rpc_gpio.c b/applications/rpc/rpc_gpio.c index 614e775a1c5..09e7385052e 100644 --- a/applications/rpc/rpc_gpio.c +++ b/applications/rpc/rpc_gpio.c @@ -57,7 +57,6 @@ static void rpc_system_gpio_set_pin_mode(const PB_Main* request, void* context) furi_assert(request->which_content == PB_Main_gpio_set_pin_mode_tag); RpcSession* session = context; - furi_assert(session); PB_Gpio_SetPinMode cmd = request->content.gpio_set_pin_mode; const GpioPin* pin = rpc_pin_to_hal_pin(cmd.pin); @@ -77,7 +76,6 @@ static void rpc_system_gpio_write_pin(const PB_Main* request, void* context) { furi_assert(request->which_content == PB_Main_gpio_write_pin_tag); RpcSession* session = context; - furi_assert(session); PB_Gpio_WritePin cmd = request->content.gpio_write_pin; const GpioPin* pin = rpc_pin_to_hal_pin(cmd.pin); @@ -105,7 +103,6 @@ static void rpc_system_gpio_read_pin(const PB_Main* request, void* context) { furi_assert(request->which_content == PB_Main_gpio_read_pin_tag); RpcSession* session = context; - furi_assert(session); PB_Gpio_ReadPin cmd = request->content.gpio_read_pin; const GpioPin* pin = rpc_pin_to_hal_pin(cmd.pin); @@ -133,7 +130,6 @@ void rpc_system_gpio_get_pin_mode(const PB_Main* request, void* context) { furi_assert(request->which_content == PB_Main_gpio_get_pin_mode_tag); RpcSession* session = context; - furi_assert(session); PB_Gpio_GetPinMode cmd = request->content.gpio_get_pin_mode; const GpioPin* pin = rpc_pin_to_hal_pin(cmd.pin); @@ -170,7 +166,6 @@ void rpc_system_gpio_set_input_pull(const PB_Main* request, void* context) { furi_assert(request->which_content == PB_Main_gpio_set_input_pull_tag); RpcSession* session = context; - furi_assert(session); PB_Gpio_SetInputPull cmd = request->content.gpio_set_input_pull; const GpioPin* pin = rpc_pin_to_hal_pin(cmd.pin); diff --git a/applications/rpc/rpc_i.h b/applications/rpc/rpc_i.h index e512ad39768..9ffd054a0c6 100644 --- a/applications/rpc/rpc_i.h +++ b/applications/rpc/rpc_i.h @@ -35,7 +35,9 @@ void rpc_system_gui_free(void* ctx); void* rpc_system_gpio_alloc(RpcSession* session); void rpc_system_gpio_free(void* ctx); -void rpc_print_message(const PB_Main* message); +void rpc_debug_print_message(const PB_Main* message); +void rpc_debug_print_data(const char* prefix, uint8_t* buffer, size_t size); + void rpc_cli_command_start_session(Cli* cli, string_t args, void* context); PB_CommandStatus rpc_system_storage_get_error(FS_Error fs_error); diff --git a/applications/rpc/rpc_storage.c b/applications/rpc/rpc_storage.c index ad6191b2fd7..1b545b41417 100644 --- a/applications/rpc/rpc_storage.c +++ b/applications/rpc/rpc_storage.c @@ -37,6 +37,7 @@ static void rpc_system_storage_reset_state( RpcSession* session, bool send_error) { furi_assert(rpc_storage); + furi_assert(session); if(rpc_storage->state != RpcStorageStateIdle) { if(send_error) { @@ -177,6 +178,8 @@ static void rpc_system_storage_stat_process(const PB_Main* request, void* contex } static void rpc_system_storage_list_root(const PB_Main* request, void* context) { + furi_assert(request); + furi_assert(context); RpcStorageSystem* rpc_storage = context; RpcSession* session = rpc_storage->session; furi_assert(session); @@ -411,6 +414,8 @@ static void rpc_system_storage_write_process(const PB_Main* request, void* conte } static bool rpc_system_storage_is_dir_is_empty(Storage* fs_api, const char* path) { + furi_assert(fs_api); + furi_assert(path); FileInfo fileinfo; bool is_dir_is_empty = true; FS_Error error = storage_common_stat(fs_api, path, &fileinfo); @@ -605,6 +610,7 @@ static void rpc_system_storage_rename_process(const PB_Main* request, void* cont static void rpc_system_storage_backup_create_process(const PB_Main* request, void* context) { furi_assert(request); furi_assert(request->which_content == PB_Main_storage_backup_create_request_tag); + furi_assert(context); FURI_LOG_D(TAG, "BackupCreate"); @@ -626,6 +632,7 @@ static void rpc_system_storage_backup_create_process(const PB_Main* request, voi static void rpc_system_storage_backup_restore_process(const PB_Main* request, void* context) { furi_assert(request); furi_assert(request->which_content == PB_Main_storage_backup_restore_request_tag); + furi_assert(context); FURI_LOG_D(TAG, "BackupRestore"); @@ -695,6 +702,7 @@ void* rpc_system_storage_alloc(RpcSession* session) { } void rpc_system_storage_free(void* context) { + furi_assert(context); RpcStorageSystem* rpc_storage = context; RpcSession* session = rpc_storage->session; furi_assert(session); diff --git a/applications/rpc/rpc_system.c b/applications/rpc/rpc_system.c index 0538aa64dcd..1681bb17e8f 100644 --- a/applications/rpc/rpc_system.c +++ b/applications/rpc/rpc_system.c @@ -77,6 +77,7 @@ static void rpc_system_system_device_info_callback( furi_assert(key); furi_assert(value); RpcSystemContext* ctx = context; + furi_assert(ctx); furi_assert(key); furi_assert(value); @@ -233,6 +234,7 @@ static void rpc_system_system_power_info_callback( furi_assert(key); furi_assert(value); RpcSystemContext* ctx = context; + furi_assert(ctx); furi_assert(key); furi_assert(value); @@ -297,6 +299,8 @@ static void rpc_system_system_update_request_process(const PB_Main* request, voi #endif void* rpc_system_system_alloc(RpcSession* session) { + furi_assert(session); + RpcHandler rpc_handler = { .message_handler = NULL, .decode_submessage = NULL, diff --git a/applications/unit_tests/rpc/rpc_test.c b/applications/unit_tests/rpc/rpc_test.c index d31311af6c4..6ee2aed652e 100644 --- a/applications/unit_tests/rpc/rpc_test.c +++ b/applications/unit_tests/rpc/rpc_test.c @@ -215,7 +215,7 @@ static void test_rpc_print_message_list(MsgList_t msg_list) { MsgList_reverse(msg_list); for M_EACH(msg, msg_list, MsgList_t) { - rpc_print_message(msg); + rpc_debug_print_message(msg); } MsgList_reverse(msg_list); #else From df4755bc065bc7e4a313d5273b39106081c08fe7 Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Mon, 22 Aug 2022 19:41:41 +0300 Subject: [PATCH 005/824] [FL-2727] BLE Remote UI fixes #1609 Co-authored-by: Aleksandr Kutuzov --- applications/bt/bt_hid_app/bt_hid.c | 5 ++-- .../bt/bt_hid_app/views/bt_hid_keynote.c | 7 +++-- .../bt/bt_hid_app/views/bt_hid_media.c | 14 +++++++++ .../bt/bt_hid_app/views/bt_hid_mouse.c | 27 ++++++++++++------ .../icons/BLE/BLE_HID/Left_mouse_icon_9x9.png | Bin 0 -> 3622 bytes .../BLE/BLE_HID/Ok_btn_pressed_13x13.png | Bin 0 -> 3625 bytes .../BLE/BLE_HID/Right_mouse_icon_9x9.png | Bin 0 -> 3622 bytes 7 files changed, 40 insertions(+), 13 deletions(-) mode change 100755 => 100644 applications/bt/bt_hid_app/bt_hid.c create mode 100644 assets/icons/BLE/BLE_HID/Left_mouse_icon_9x9.png create mode 100644 assets/icons/BLE/BLE_HID/Ok_btn_pressed_13x13.png create mode 100644 assets/icons/BLE/BLE_HID/Right_mouse_icon_9x9.png diff --git a/applications/bt/bt_hid_app/bt_hid.c b/applications/bt/bt_hid_app/bt_hid.c old mode 100755 new mode 100644 index 3189042c0e9..0827bd0ad0f --- a/applications/bt/bt_hid_app/bt_hid.c +++ b/applications/bt/bt_hid_app/bt_hid.c @@ -89,8 +89,7 @@ BtHid* bt_hid_app_alloc() { app->submenu, "Keynote", BtHidSubmenuIndexKeynote, bt_hid_submenu_callback, app); submenu_add_item( app->submenu, "Keyboard", BtHidSubmenuIndexKeyboard, bt_hid_submenu_callback, app); - submenu_add_item( - app->submenu, "Media Player", BtHidSubmenuIndexMedia, bt_hid_submenu_callback, app); + submenu_add_item(app->submenu, "Media", BtHidSubmenuIndexMedia, bt_hid_submenu_callback, app); submenu_add_item(app->submenu, "Mouse", BtHidSubmenuIndexMouse, bt_hid_submenu_callback, app); view_set_previous_callback(submenu_get_view(app->submenu), bt_hid_exit); view_dispatcher_add_view( @@ -134,7 +133,7 @@ BtHid* bt_hid_app_alloc() { app->view_dispatcher, BtHidViewMouse, bt_hid_mouse_get_view(app->bt_hid_mouse)); // TODO switch to menu after Media is done - app->view_id = BtHidViewKeynote; + app->view_id = BtHidViewSubmenu; view_dispatcher_switch_to_view(app->view_dispatcher, app->view_id); return app; diff --git a/applications/bt/bt_hid_app/views/bt_hid_keynote.c b/applications/bt/bt_hid_app/views/bt_hid_keynote.c index 60a1ebc0877..ea4ee16fa4b 100755 --- a/applications/bt/bt_hid_app/views/bt_hid_keynote.c +++ b/applications/bt/bt_hid_app/views/bt_hid_keynote.c @@ -43,7 +43,10 @@ static void bt_hid_keynote_draw_callback(Canvas* canvas, void* context) { } canvas_set_font(canvas, FontPrimary); elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Keynote"); + + canvas_draw_icon(canvas, 68, 2, &I_Pin_back_arrow_10x8); canvas_set_font(canvas, FontSecondary); + elements_multiline_text_aligned(canvas, 127, 3, AlignRight, AlignTop, "Hold to exit"); // Up canvas_draw_icon(canvas, 21, 24, &I_Button_18x18); @@ -97,8 +100,8 @@ static void bt_hid_keynote_draw_callback(Canvas* canvas, void* context) { elements_slightly_rounded_box(canvas, 66, 47, 60, 13); canvas_set_color(canvas, ColorWhite); } - canvas_draw_icon(canvas, 110, 49, &I_Ok_btn_9x9); - elements_multiline_text_aligned(canvas, 76, 56, AlignLeft, AlignBottom, "Back"); + canvas_draw_icon(canvas, 74, 49, &I_Pin_back_arrow_10x8); + elements_multiline_text_aligned(canvas, 91, 57, AlignLeft, AlignBottom, "Back"); } static void bt_hid_keynote_process(BtHidKeynote* bt_hid_keynote, InputEvent* event) { diff --git a/applications/bt/bt_hid_app/views/bt_hid_media.c b/applications/bt/bt_hid_app/views/bt_hid_media.c index b384f47cf18..258ea0a404b 100755 --- a/applications/bt/bt_hid_app/views/bt_hid_media.c +++ b/applications/bt/bt_hid_app/views/bt_hid_media.c @@ -49,7 +49,9 @@ static void bt_hid_media_draw_callback(Canvas* canvas, void* context) { // Up if(model->up_pressed) { + canvas_set_bitmap_mode(canvas, 1); canvas_draw_icon(canvas, 93, 9, &I_Pressed_Button_13x13); + canvas_set_bitmap_mode(canvas, 0); canvas_set_color(canvas, ColorWhite); } canvas_draw_icon(canvas, 96, 12, &I_Volup_8x6); @@ -57,7 +59,9 @@ static void bt_hid_media_draw_callback(Canvas* canvas, void* context) { // Down if(model->down_pressed) { + canvas_set_bitmap_mode(canvas, 1); canvas_draw_icon(canvas, 93, 41, &I_Pressed_Button_13x13); + canvas_set_bitmap_mode(canvas, 0); canvas_set_color(canvas, ColorWhite); } canvas_draw_icon(canvas, 96, 45, &I_Voldwn_6x6); @@ -65,7 +69,9 @@ static void bt_hid_media_draw_callback(Canvas* canvas, void* context) { // Left if(model->left_pressed) { + canvas_set_bitmap_mode(canvas, 1); canvas_draw_icon(canvas, 77, 25, &I_Pressed_Button_13x13); + canvas_set_bitmap_mode(canvas, 0); canvas_set_color(canvas, ColorWhite); } bt_hid_media_draw_arrow(canvas, 82, 31, CanvasDirectionRightToLeft); @@ -74,7 +80,9 @@ static void bt_hid_media_draw_callback(Canvas* canvas, void* context) { // Right if(model->right_pressed) { + canvas_set_bitmap_mode(canvas, 1); canvas_draw_icon(canvas, 109, 25, &I_Pressed_Button_13x13); + canvas_set_bitmap_mode(canvas, 0); canvas_set_color(canvas, ColorWhite); } bt_hid_media_draw_arrow(canvas, 112, 31, CanvasDirectionLeftToRight); @@ -89,6 +97,12 @@ static void bt_hid_media_draw_callback(Canvas* canvas, void* context) { bt_hid_media_draw_arrow(canvas, 96, 31, CanvasDirectionLeftToRight); canvas_draw_line(canvas, 100, 29, 100, 33); canvas_draw_line(canvas, 102, 29, 102, 33); + canvas_set_color(canvas, ColorBlack); + + // Exit + canvas_draw_icon(canvas, 0, 54, &I_Pin_back_arrow_10x8); + canvas_set_font(canvas, FontSecondary); + elements_multiline_text_aligned(canvas, 13, 62, AlignLeft, AlignBottom, "Hold to exit"); } static void bt_hid_media_process_press(BtHidMedia* bt_hid_media, InputEvent* event) { diff --git a/applications/bt/bt_hid_app/views/bt_hid_mouse.c b/applications/bt/bt_hid_app/views/bt_hid_mouse.c index fb1537a2c9d..f9d84f9fbd4 100644 --- a/applications/bt/bt_hid_app/views/bt_hid_mouse.c +++ b/applications/bt/bt_hid_app/views/bt_hid_mouse.c @@ -36,7 +36,11 @@ static void bt_hid_mouse_draw_callback(Canvas* canvas, void* context) { canvas_set_font(canvas, FontSecondary); if(model->left_mouse_held == true) { - elements_multiline_text_aligned(canvas, 0, 60, AlignLeft, AlignBottom, "Selecting..."); + elements_multiline_text_aligned(canvas, 0, 62, AlignLeft, AlignBottom, "Selecting..."); + } else { + canvas_draw_icon(canvas, 0, 54, &I_Pin_back_arrow_10x8); + canvas_set_font(canvas, FontSecondary); + elements_multiline_text_aligned(canvas, 13, 62, AlignLeft, AlignBottom, "Hold to exit"); } // Keypad circles @@ -44,7 +48,9 @@ static void bt_hid_mouse_draw_callback(Canvas* canvas, void* context) { // Up if(model->up_pressed) { + canvas_set_bitmap_mode(canvas, 1); canvas_draw_icon(canvas, 81, 9, &I_Pressed_Button_13x13); + canvas_set_bitmap_mode(canvas, 0); canvas_set_color(canvas, ColorWhite); } canvas_draw_icon(canvas, 84, 10, &I_Pin_arrow_up7x9); @@ -52,7 +58,9 @@ static void bt_hid_mouse_draw_callback(Canvas* canvas, void* context) { // Down if(model->down_pressed) { + canvas_set_bitmap_mode(canvas, 1); canvas_draw_icon(canvas, 81, 41, &I_Pressed_Button_13x13); + canvas_set_bitmap_mode(canvas, 0); canvas_set_color(canvas, ColorWhite); } canvas_draw_icon(canvas, 84, 43, &I_Pin_arrow_down_7x9); @@ -60,7 +68,9 @@ static void bt_hid_mouse_draw_callback(Canvas* canvas, void* context) { // Left if(model->left_pressed) { + canvas_set_bitmap_mode(canvas, 1); canvas_draw_icon(canvas, 65, 25, &I_Pressed_Button_13x13); + canvas_set_bitmap_mode(canvas, 0); canvas_set_color(canvas, ColorWhite); } canvas_draw_icon(canvas, 67, 28, &I_Pin_arrow_left_9x7); @@ -68,7 +78,9 @@ static void bt_hid_mouse_draw_callback(Canvas* canvas, void* context) { // Right if(model->right_pressed) { + canvas_set_bitmap_mode(canvas, 1); canvas_draw_icon(canvas, 97, 25, &I_Pressed_Button_13x13); + canvas_set_bitmap_mode(canvas, 0); canvas_set_color(canvas, ColorWhite); } canvas_draw_icon(canvas, 99, 28, &I_Pin_arrow_right_9x7); @@ -76,18 +88,17 @@ static void bt_hid_mouse_draw_callback(Canvas* canvas, void* context) { // Ok if(model->left_mouse_pressed) { - canvas_draw_icon(canvas, 81, 25, &I_Pressed_Button_13x13); - canvas_set_color(canvas, ColorWhite); + canvas_draw_icon(canvas, 81, 25, &I_Ok_btn_pressed_13x13); + } else { + canvas_draw_icon(canvas, 83, 27, &I_Left_mouse_icon_9x9); } - canvas_draw_icon(canvas, 83, 27, &I_Ok_btn_9x9); - canvas_set_color(canvas, ColorBlack); // Back if(model->right_mouse_pressed) { - canvas_draw_icon(canvas, 108, 48, &I_Pressed_Button_13x13); - canvas_set_color(canvas, ColorWhite); + canvas_draw_icon(canvas, 108, 48, &I_Ok_btn_pressed_13x13); + } else { + canvas_draw_icon(canvas, 110, 50, &I_Right_mouse_icon_9x9); } - canvas_draw_icon(canvas, 110, 50, &I_Ok_btn_9x9); } static void bt_hid_mouse_process(BtHidMouse* bt_hid_mouse, InputEvent* event) { diff --git a/assets/icons/BLE/BLE_HID/Left_mouse_icon_9x9.png b/assets/icons/BLE/BLE_HID/Left_mouse_icon_9x9.png new file mode 100644 index 0000000000000000000000000000000000000000..c533d85729f9b778cba33227141c90dea298f5e3 GIT binary patch literal 3622 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 literal 0 HcmV?d00001 diff --git a/assets/icons/BLE/BLE_HID/Ok_btn_pressed_13x13.png b/assets/icons/BLE/BLE_HID/Ok_btn_pressed_13x13.png new file mode 100644 index 0000000000000000000000000000000000000000..6b46ba3a8243811986234686c4289f23d5de535c GIT binary patch literal 3625 zcmaJ@XH-+!7QPHfZz4rNoDdKYAt9lekWiA)n?R5t77_>{Vn_(lP!vI=DFY(X1wo}3 z6%<84X#!FOq&E=|(E;92gpu~b%sB7;nD_3w_nve1+TXXoz0W>totP7L<|2Y}f&c)B zSX$s5_r|@CpPTbH)s?gZ06|kK7JI@Hiv=;5bT8@!G5`dOWI9psPV>^}^@&xCb#&+* zYr3NpKgbbtGgLA`MO{%q+$vfzXIRRie!rk8K_zR)VcF)&~UC~C9|TNuZ~|h*+SbvH&nO~b9n!U@Rp|LsTqiIn4mHP z5a+M(RP^6g;sQ28P^e?zI=)u`S3sW-KTv0zQKxk%YFF$FChas==yk3-R>E;>{!mH4 zI4BO22N;`ig=VIzI04x_fP1?KX&N}83An3X{nQ79W^SYfa{+F56s5Sb69CWwax@O` zHULVxPu?&E2wH%omvs{Y7}5l^EM2@TfXB~)x-M~{a)4hL&~k{5I12Ct1MaO#N&&$2 zG(gg9*#-66u`=;Fbxx(y%28Fy2-7e(eoa3<7Z=E3wJuAUW0HErpNQ$kkcPlCS$LR^ z*oT!40LV^|;$*wB9nd9O*43pKS1Ec<^UG`AT`-9>y))Zg%rFLkDOO0&js~o>j1#f+Z;+4CbVD~!F`nC9H78XlgVnHjQb!nhIJT(0a;8qU?Z zY+v|21huuk_Tkk>`~ZN<4pV<@BEMRHP@|6b zQ2oBKdZ8_Mz3Uj|rUr~SM$j|#5Yzo=$u*2xWancAb$94{V+EZ$2k*#4hA5=L`GqK& zA@-ffpH;6`6DGi8(#n5;s5lbMMY=&yisP3_i`Y=Cx8RYusSJ7>E$INZPSCZ0Io`m7 zoGlcV(afI^QK!vbCK$8=@M~LOLRj({8$;1!-=?JUOl*km%9=1Y9Cq+${I_WC?e5%$i5{ z6E=@Tm}#AW9uFG>A|5ueAlMM>hAav|hm>{pj|k`sa9?+5Pz5IzSU**Hx&Qa3gCsaC zieRCkG$0Xw04g3Fjcw9bmWaW^RjY3OWclPFzE`5xtk>63X)yr%yQ_ z;*JLBSZl;g=1k*^_Kf_D;osVrg_|f_kO;WvPTV z!6d6Bl_Ys}D88^LuV|u3$a%%N9UotK*6B)_nX|UjbfLie5|fB2Q`Zx!dQcDg&3-Wxi={T7o>rcwHPf0OsPL*Ns#x28v0Y4e zw5`fJnrC2RVAIms(RsgfAWb&|4I6~dWz1y^W=uYJKNWCFqq3m#1=+HE=2V{RVr7kQ z#3_VpF2VWKnF_Pg%+ezR)uq+>`}3>p677n!1}Ke>f2(|3S@>M`@$3-qXjvt#@(Phc zlA%0*Q`WecSetm|<&|Hy(R?CN!=l9srxZf`pE4zpCy^8BU3V9auDn@Io`+Hh-QwLt z+S8Q>+K)C-Go3Q}%qcRID*y16=$kRt*V-W|hL8;T=JD3r87tPB-sIhw;I`@udxoZ2rYiz}SaG32e61tb96Rzhv^y{9tK5w^gq-ULrn8aRH+V$KG+U)`ILyvG# zxMRXh!rXq^+z7g?_&UxAIZFOkKD=NOn_XohWfFg_^xABFsiJr5ueVAS*XL5Z61u3O z5hp@E54__eej?s%3=vk1h>CEDG>T(H6XbeeDZ1>QF|7Y2?mI3SH<3Ys*&`llTIs4A z7D3LVM)Y6myfkWtc)51;6EX>w7pxBvyIBXNR(4GIkuFtkUnCwd5bTK%xyvW2>B z(CuFnYIFmY-)QG*%vN1jExc7@BVse2fy|OlzXYPe(a2g@`0a#SewZRf+r&!B7s@BE zOYJ4(i1M8`zBivk4=3@x^{Kd3vd>jhuo9E^8GlM`P@S)wLU!?b-5Jw{NG{Gg*16D8 z(KdQZ|L)Sg-35sTiK*L_xslc`nhJzZwI$~f1XyNYV-sV#htsJa+->=Y%#yiFj z9Q$f6+VbllzAlt^81+k z=>5vzIghT%^J4U+m*T9cUen#1a|SgAU8k2{u$Ie5XAii%a7llJJV*P&`hwa??6YsF zzFVDMR(0B^YB8wxS+LjoynL2^*Z68};BV5q1N~VD^my$`5Pkj4`r4%QcnDKZZ5p#QfD<9kK*{zZ#vvYr^y-Y?L8nV&J?JzD zanA=5Kx1&w0Dv+IU=Tfg$Se?vOriRs!AsSz!62$98tkHLt7Xf;lD(-GK}@n!kR9G5 z$j1ZW2{tkWp#qQ`0vee`1O?D8`1&IQ(BMCKk(~LS843pd;llDkgZ~souss37(wStC zJ_M%ep{1n-(nmnZoDNfCx0YnBA2GQEf>W8DP?f-YB(f;=KXE~Dp zqxT<){qcbeGSrdmPru0Y;Ow23(q1SA63ZkLS#&0zPQUP@kSDz9EV{opodJStLtr2^ zTcQWmch7S44~VTT($d$TMfCL`TjJ1Q4he)x^+f98FuE`;eI1yVnJx@wiZj7sk7ICf z3|1em4MV{7e_(NRkBc<2FY5=^^FLS){(oTi8iK~)M8=Vs)JtSfGbWt|`Xg&3^&hlg z5ilLB-f;wnPv@Vt{E7Aa2Q7bLP5vhq$`J$I+uQ%z>mMdg1MN-!ZeGsf@AfDAa(bT0 zY3`!iXF2z3K;VQ8-jp-$h5$Pu0Q7Lr69@cE tNU_5F5@tDqw>z-XxMyOWnyrgG{91sV9boy3dsxXH+S1exSB7!F_HPPcG0^}3 literal 0 HcmV?d00001 diff --git a/assets/icons/BLE/BLE_HID/Right_mouse_icon_9x9.png b/assets/icons/BLE/BLE_HID/Right_mouse_icon_9x9.png new file mode 100644 index 0000000000000000000000000000000000000000..446d7176c8dc3ce478572f4a044a7e32b326bc72 GIT binary patch literal 3622 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 Date: Mon, 22 Aug 2022 19:53:51 +0300 Subject: [PATCH 006/824] [FL-2737] Dolphin level thresholds update #1610 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- applications/dolphin/helpers/dolphin_state.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/applications/dolphin/helpers/dolphin_state.c b/applications/dolphin/helpers/dolphin_state.c index 76f38a5fd45..95e2f42f49d 100644 --- a/applications/dolphin/helpers/dolphin_state.c +++ b/applications/dolphin/helpers/dolphin_state.c @@ -14,8 +14,8 @@ #define DOLPHIN_STATE_PATH INT_PATH(DOLPHIN_STATE_FILE_NAME) #define DOLPHIN_STATE_HEADER_MAGIC 0xD0 #define DOLPHIN_STATE_HEADER_VERSION 0x01 -#define LEVEL2_THRESHOLD 735 -#define LEVEL3_THRESHOLD 2940 +#define LEVEL2_THRESHOLD 300 +#define LEVEL3_THRESHOLD 1800 #define BUTTHURT_MAX 14 #define BUTTHURT_MIN 0 From 9829145d8cfd41053aa66c4f7cb19238464c0449 Mon Sep 17 00:00:00 2001 From: hedger Date: Mon, 22 Aug 2022 20:06:17 +0300 Subject: [PATCH 007/824] fbt: fixed include paths; added PVS-Studio configuration (#1615) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fbt: fixed include paths for generated version header * lib: STM32CubeWB: refactored & cleaned up WPAN include paths * hal: linter fixes for new headers * fbt: added version_json target * Added .pvsconfig; split common_defines.h into 2 files * Added PVS-Studio basic configuration files; updated .gitignore Co-authored-by: あく --- .gitignore | 4 + .pvsconfig | 22 +++ .pvsoptions | 1 + applications/bt/bt_cli.c | 2 +- firmware.scons | 2 + firmware/SConscript | 5 + firmware/targets/f7/Inc/stm32.h | 32 +--- firmware/targets/f7/ble_glue/app_conf.h | 5 +- firmware/targets/f7/ble_glue/app_debug.c | 11 +- .../targets/f7/ble_glue/battery_service.c | 2 +- firmware/targets/f7/ble_glue/ble_app.c | 6 +- firmware/targets/f7/ble_glue/ble_const.h | 115 ++++++++++++++ firmware/targets/f7/ble_glue/ble_glue.c | 10 +- firmware/targets/f7/ble_glue/compiler.h | 150 ++++++++++++++++++ .../targets/f7/ble_glue/dev_info_service.c | 2 +- firmware/targets/f7/ble_glue/gap.c | 2 +- firmware/targets/f7/ble_glue/hid_service.c | 2 +- firmware/targets/f7/ble_glue/hw_ipcc.c | 2 +- firmware/targets/f7/ble_glue/osal.h | 63 ++++++++ firmware/targets/f7/ble_glue/serial_service.c | 2 +- firmware/targets/f7/furi_hal/furi_hal_bt.c | 5 +- .../targets/f7/furi_hal/furi_hal_crypto.c | 2 +- firmware/targets/f7/furi_hal/furi_hal_flash.c | 4 +- firmware/targets/f7/furi_hal/furi_hal_info.c | 2 +- .../targets/f7/furi_hal/furi_hal_version.c | 2 +- furi/core/common_defines.h | 87 +--------- furi/core/core_defines.h | 97 +++++++++++ furi/core/record.h | 3 +- lib/STM32CubeWB.scons | 19 +-- lib/toolbox/SConscript | 3 +- 30 files changed, 507 insertions(+), 157 deletions(-) create mode 100644 .pvsconfig create mode 100644 .pvsoptions create mode 100644 firmware/targets/f7/ble_glue/ble_const.h create mode 100644 firmware/targets/f7/ble_glue/compiler.h create mode 100644 firmware/targets/f7/ble_glue/osal.h create mode 100644 furi/core/core_defines.h diff --git a/.gitignore b/.gitignore index 21f391743ec..38a31bf01bd 100644 --- a/.gitignore +++ b/.gitignore @@ -50,3 +50,7 @@ build/ # openocd output file openocd.log + +# PVS Studio temporary files +.PVS-Studio/ +PVS-Studio.log diff --git a/.pvsconfig b/.pvsconfig new file mode 100644 index 00000000000..d17eaa5a031 --- /dev/null +++ b/.pvsconfig @@ -0,0 +1,22 @@ +# MLib macros we can't do much about. +//-V:M_EACH:1048,1044 +//-V:ARRAY_DEF:760,747,568,776,729,712,654 +//-V:LIST_DEF:760,747,568,712,729,654,776 +//-V:BPTREE_DEF2:779,1086,557,773,512 +//-V:DICT_DEF2:779,524,776,760,1044,1001,729,590,568,747,685 +//-V:ALGO_DEF:1048,747,1044 + +# Non-severe malloc/null pointer deref warnings +//-V::522:2,3 + +# Warning about headers with copyleft license +//-V::1042 + +# Potentially null argument warnings +//-V:memset:575 +//-V:memcpy:575 +//-V:strcpy:575 +//-V:strchr:575 + +# For loop warning on M_FOREACH +//-V:for:1044 diff --git a/.pvsoptions b/.pvsoptions new file mode 100644 index 00000000000..6715f871896 --- /dev/null +++ b/.pvsoptions @@ -0,0 +1 @@ +--rules-config .pvsconfig -e lib/fatfs -e lib/fnv1a-hash -e lib/FreeRTOS-Kernel -e lib/heatshrink -e lib/libusb_stm32 -e lib/littlefs -e lib/mbedtls -e lib/micro-ecc -e lib/microtar -e lib/mlib -e lib/qrcode -e lib/ST25RFAL002 -e lib/STM32CubeWB -e lib/u8g2 -e toolchain/ diff --git a/applications/bt/bt_cli.c b/applications/bt/bt_cli.c index 3aa1bc75236..79500fac48d 100644 --- a/applications/bt/bt_cli.c +++ b/applications/bt/bt_cli.c @@ -3,7 +3,7 @@ #include #include -#include "ble.h" +#include #include "bt_settings.h" #include "bt_service/bt.h" diff --git a/firmware.scons b/firmware.scons index 863b35fca9f..745543070c2 100644 --- a/firmware.scons +++ b/firmware.scons @@ -244,6 +244,8 @@ if should_gen_cdb_and_link_dir(fwenv, BUILD_TARGETS): # without filtering, both updater & firmware commands would be generated fwenv.Replace(COMPILATIONDB_PATH_FILTER=fwenv.subst("*${FW_FLAVOR}*")) AlwaysBuild(fwcdb) + Precious(fwcdb) + NoClean(fwcdb) Alias(fwenv["FIRMWARE_BUILD_CFG"] + "_cdb", fwcdb) fw_artifacts.append(fwcdb) diff --git a/firmware/SConscript b/firmware/SConscript index 8dade34e11b..3530ed82783 100644 --- a/firmware/SConscript +++ b/firmware/SConscript @@ -3,6 +3,11 @@ Import("env") env.Append(LINT_SOURCES=["firmware"]) libenv = env.Clone(FW_LIB_NAME="flipper${TARGET_HW}") +libenv.Append( + CPPPATH=[ + "#/lib/STM32CubeWB/Middlewares/ST/STM32_WPAN/interface/patterns/ble_thread/tl", + ] +) libenv.ApplyLibFlags() diff --git a/firmware/targets/f7/Inc/stm32.h b/firmware/targets/f7/Inc/stm32.h index 83dda96e27d..8e5cc379b5d 100644 --- a/firmware/targets/f7/Inc/stm32.h +++ b/firmware/targets/f7/Inc/stm32.h @@ -16,36 +16,6 @@ /* bit value */ #define _BV(bit) (0x01 << (bit)) -#if defined(STM32F0) -#include "STM32F0xx/Include/stm32f0xx.h" -#elif defined(STM32F1) -#include "STM32F1xx/Include/stm32f1xx.h" -#elif defined(STM32F2) -#include "STM32F2xx/Include/stm32f2xx.h" -#elif defined(STM32F3) -#include "STM32F3xx/Include/stm32f3xx.h" -#elif defined(STM32F4) -#include "STM32F4xx/Include/stm32f4xx.h" -#elif defined(STM32F7) -#include "STM32F7xx/Include/stm32f7xx.h" -#elif defined(STM32H7) -#include "STM32H7xx/Include/stm32h7xx.h" -#elif defined(STM32L0) -#include "STM32L0xx/Include/stm32l0xx.h" -#elif defined(STM32L1) -#include "STM32L1xx/Include/stm32l1xx.h" -#elif defined(STM32L4) -#include "STM32L4xx/Include/stm32l4xx.h" -#elif defined(STM32L5) -#include "STM32L5xx/Include/stm32l5xx.h" -#elif defined(STM32G0) -#include "STM32G0xx/Include/stm32g0xx.h" -#elif defined(STM32G4) -#include "STM32G4xx/Include/stm32g4xx.h" -#elif defined(STM32WB) -#include "STM32WBxx/Include/stm32wbxx.h" -#else -#error "STM32 family not defined" -#endif +#include "stm32wbxx.h" #endif // _STM32_H_ diff --git a/firmware/targets/f7/ble_glue/app_conf.h b/firmware/targets/f7/ble_glue/app_conf.h index 1d7474da5cf..aaa755a3622 100644 --- a/firmware/targets/f7/ble_glue/app_conf.h +++ b/firmware/targets/f7/ble_glue/app_conf.h @@ -1,9 +1,10 @@ #pragma once -#include "hw.h" #include "hw_conf.h" #include "hw_if.h" -#include "ble_bufsize.h" + +#include +#include #define CFG_TX_POWER (0x19) /* +0dBm */ diff --git a/firmware/targets/f7/ble_glue/app_debug.c b/firmware/targets/f7/ble_glue/app_debug.c index e480ea364a7..d84588540a0 100644 --- a/firmware/targets/f7/ble_glue/app_debug.c +++ b/firmware/targets/f7/ble_glue/app_debug.c @@ -1,10 +1,11 @@ -#include "utilities_common.h" - #include "app_common.h" #include "app_debug.h" -#include "shci.h" -#include "tl.h" -#include "dbg_trace.h" +#include +#include +#include +#include +#include + #include typedef PACKED_STRUCT { diff --git a/firmware/targets/f7/ble_glue/battery_service.c b/firmware/targets/f7/ble_glue/battery_service.c index a95f9187284..8c371efadba 100644 --- a/firmware/targets/f7/ble_glue/battery_service.c +++ b/firmware/targets/f7/ble_glue/battery_service.c @@ -1,6 +1,6 @@ #include "battery_service.h" #include "app_common.h" -#include "ble.h" +#include #include #include diff --git a/firmware/targets/f7/ble_glue/ble_app.c b/firmware/targets/f7/ble_glue/ble_app.c index 4d3c96e13c2..3cf02009fc8 100644 --- a/firmware/targets/f7/ble_glue/ble_app.c +++ b/firmware/targets/f7/ble_glue/ble_app.c @@ -1,8 +1,8 @@ #include "ble_app.h" -#include "hci_tl.h" -#include "ble.h" -#include "shci.h" +#include +#include +#include #include "gap.h" #include diff --git a/firmware/targets/f7/ble_glue/ble_const.h b/firmware/targets/f7/ble_glue/ble_const.h new file mode 100644 index 00000000000..0e4c8b398d9 --- /dev/null +++ b/firmware/targets/f7/ble_glue/ble_const.h @@ -0,0 +1,115 @@ +/***************************************************************************** + * @file ble_const.h + * @author MDG + * @brief This file contains the definitions which are compiler dependent. + ***************************************************************************** + * @attention + * + * Copyright (c) 2018-2022 STMicroelectronics. + * All rights reserved. + * + * This software is licensed under terms that can be found in the LICENSE file + * in the root directory of this software component. + * If no LICENSE file comes with this software, it is provided AS-IS. + * + ***************************************************************************** + */ + +#ifndef BLE_CONST_H__ +#define BLE_CONST_H__ + +#include +#include +#include +#include +#include "osal.h" + +/* Default BLE variant */ +#ifndef BASIC_FEATURES +#define BASIC_FEATURES 0 +#endif +#ifndef SLAVE_ONLY +#define SLAVE_ONLY 0 +#endif +#ifndef LL_ONLY +#define LL_ONLY 0 +#endif +#ifndef BEACON_ONLY +#define BEACON_ONLY 0 +#endif + +/* Size of command/events buffers: + * + * To change the size of commands and events parameters used in the + * auto-generated files, you need to update 2 defines: + * + * - BLE_CMD_MAX_PARAM_LEN + * - BLE_EVT_MAX_PARAM_LEN + * + * These 2 defines are set below with default values and can be changed. + * + * To compute the value to support a characteristic of 512 bytes for a specific + * command or an event, you need to look in "ble_types.h". + * + * Here are 2 examples, one with a command and one with an event: + * + * - aci_gatt_update_char_value_ext_cp0 + * ---------------------------------- + * + * we have in the structure: + * + * uint8_t Value[(BLE_CMD_MAX_PARAM_LEN- 12)/sizeof(uint8_t)]; + * + * so to support a 512 byte value, we need to have + * + * BLE_CMD_MAX_PARAM_LEN at least equal to: 512 + 12 = 524 + * + * - aci_gatt_read_handle_value_rp0 + * ------------------------------ + * + * we have in the structure: + * + * uint8_t Value[((BLE_EVT_MAX_PARAM_LEN - 3) - 5)/sizeof(uint8_t)]; + * + * so to support a 512 byte value, we need to have + * + * BLE_EVT_MAX_PARAM_LEN at least equal to: 512 + 3 + 5 = 520 + * + * If you need several events or commands with 512-size values, you need to + * take the maximum values for BLE_EVT_MAX_PARAM_LEN and BLE_CMD_MAX_PARAM_LEN. + * + */ + +/* Maximum parameter size of BLE commands. + * Change this value if needed. */ +#define BLE_CMD_MAX_PARAM_LEN HCI_COMMAND_MAX_PARAM_LEN + +/* Maximum parameter size of BLE responses/events. + * Change this value if needed. */ +#define BLE_EVT_MAX_PARAM_LEN HCI_EVENT_MAX_PARAM_LEN + +/* Callback function to send command and receive response */ +struct hci_request { + uint16_t ogf; + uint16_t ocf; + int event; + void* cparam; + int clen; + void* rparam; + int rlen; +}; +extern int hci_send_req(struct hci_request* req, uint8_t async); + +#ifndef FALSE +#define FALSE 0 +#endif + +#ifndef MIN +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#endif + +#ifndef MAX +#define MAX(a, b) (((a) > (b)) ? (a) : (b)) +#endif + +#endif /* BLE_CONST_H__ */ diff --git a/firmware/targets/f7/ble_glue/ble_glue.c b/firmware/targets/f7/ble_glue/ble_glue.c index 585a8982027..87af5f2a892 100644 --- a/firmware/targets/f7/ble_glue/ble_glue.c +++ b/firmware/targets/f7/ble_glue/ble_glue.c @@ -1,14 +1,14 @@ #include "ble_glue.h" #include "app_common.h" #include "ble_app.h" -#include "ble.h" -#include "tl.h" -#include "shci.h" -#include "shci_tl.h" +#include + +#include +#include +#include #include "app_debug.h" #include -#include #define TAG "Core2" diff --git a/firmware/targets/f7/ble_glue/compiler.h b/firmware/targets/f7/ble_glue/compiler.h new file mode 100644 index 00000000000..1c39628197d --- /dev/null +++ b/firmware/targets/f7/ble_glue/compiler.h @@ -0,0 +1,150 @@ +/***************************************************************************** + * @file compiler.h + * @author MDG + * @brief This file contains the definitions which are compiler dependent. + ***************************************************************************** + * @attention + * + * Copyright (c) 2018-2022 STMicroelectronics. + * All rights reserved. + * + * This software is licensed under terms that can be found in the LICENSE file + * in the root directory of this software component. + * If no LICENSE file comes with this software, it is provided AS-IS. + * + ***************************************************************************** + */ + +#ifndef COMPILER_H__ +#define COMPILER_H__ + +/** + * @brief This is the section dedicated to IAR toolchain + */ +#if defined(__ICCARM__) || defined(__IAR_SYSTEMS_ASM__) + +#ifndef __WEAK +#define __WEAK __weak +#endif + +#define QUOTE_(a) #a + +/** + * @brief PACKED + * Use the PACKED macro for variables that needs to be packed. + * Usage: PACKED(struct) myStruct_s + * PACKED(union) myStruct_s + */ +#define PACKED(decl) __packed decl + +/** + * @brief SECTION + * Use the SECTION macro to assign data or code in a specific section. + * Usage: SECTION(".my_section") + */ +#define SECTION(name) _Pragma(QUOTE_(location = name)) + +/** + * @brief ALIGN_DEF + * Use the ALIGN_DEF macro to specify the alignment of a variable. + * Usage: ALIGN_DEF(4) + */ +#define ALIGN_DEF(v) _Pragma(QUOTE_(data_alignment = v)) + +/** + * @brief NO_INIT + * Use the NO_INIT macro to declare a not initialized variable. + * Usage: NO_INIT(int my_no_init_var) + * Usage: NO_INIT(uint16_t my_no_init_array[10]) + */ +#define NO_INIT(var) __no_init var + +/** + * @brief This is the section dedicated to GNU toolchain + */ +#else +#ifdef __GNUC__ + +#ifndef __WEAK +#define __WEAK __attribute__((weak)) +#endif + +/** + * @brief PACKED + * Use the PACKED macro for variables that needs to be packed. + * Usage: PACKED(struct) myStruct_s + * PACKED(union) myStruct_s + */ +#define PACKED(decl) decl __attribute__((packed)) + +/** + * @brief SECTION + * Use the SECTION macro to assign data or code in a specific section. + * Usage: SECTION(".my_section") + */ +#define SECTION(name) __attribute__((section(name))) + +/** + * @brief ALIGN_DEF + * Use the ALIGN_DEF macro to specify the alignment of a variable. + * Usage: ALIGN_DEF(4) + */ +#define ALIGN_DEF(N) __attribute__((aligned(N))) + +/** + * @brief NO_INIT + * Use the NO_INIT macro to declare a not initialized variable. + * Usage: NO_INIT(int my_no_init_var) + * Usage: NO_INIT(uint16_t my_no_init_array[10]) + */ +#define NO_INIT(var) var __attribute__((section(".noinit"))) + +/** + * @brief This is the section dedicated to Keil toolchain + */ +#else +#ifdef __CC_ARM + +#ifndef __WEAK +#define __WEAK __weak +#endif + +/** + * @brief PACKED + * Use the PACKED macro for variables that needs to be packed. + * Usage: PACKED(struct) myStruct_s + * PACKED(union) myStruct_s + */ +#define PACKED(decl) decl __attribute__((packed)) + +/** + * @brief SECTION + * Use the SECTION macro to assign data or code in a specific section. + * Usage: SECTION(".my_section") + */ +#define SECTION(name) __attribute__((section(name))) + +/** + * @brief ALIGN_DEF + * Use the ALIGN_DEF macro to specify the alignment of a variable. + * Usage: ALIGN_DEF(4) + */ +#define ALIGN_DEF(N) __attribute__((aligned(N))) + +/** + * @brief NO_INIT + * Use the NO_INIT macro to declare a not initialized variable. + * Usage: NO_INIT(int my_no_init_var) + * Usage: NO_INIT(uint16_t my_no_init_array[10]) + */ +#define NO_INIT(var) var __attribute__((section("NoInit"))) + +#else + +#error Neither ICCARM, CC ARM nor GNUC C detected. Define your macros. + +#endif +#endif +#endif + +#endif /* COMPILER_H__ */ diff --git a/firmware/targets/f7/ble_glue/dev_info_service.c b/firmware/targets/f7/ble_glue/dev_info_service.c index d6d1e479ef8..ecfa08b1755 100755 --- a/firmware/targets/f7/ble_glue/dev_info_service.c +++ b/firmware/targets/f7/ble_glue/dev_info_service.c @@ -1,6 +1,6 @@ #include "dev_info_service.h" #include "app_common.h" -#include "ble.h" +#include #include #include diff --git a/firmware/targets/f7/ble_glue/gap.c b/firmware/targets/f7/ble_glue/gap.c index 7154b9b1194..62db30feea3 100644 --- a/firmware/targets/f7/ble_glue/gap.c +++ b/firmware/targets/f7/ble_glue/gap.c @@ -1,6 +1,6 @@ #include "gap.h" -#include "ble.h" +#include #include #include diff --git a/firmware/targets/f7/ble_glue/hid_service.c b/firmware/targets/f7/ble_glue/hid_service.c index 0efe1747b63..d0ca9685ab6 100644 --- a/firmware/targets/f7/ble_glue/hid_service.c +++ b/firmware/targets/f7/ble_glue/hid_service.c @@ -1,6 +1,6 @@ #include "hid_service.h" #include "app_common.h" -#include "ble.h" +#include #include diff --git a/firmware/targets/f7/ble_glue/hw_ipcc.c b/firmware/targets/f7/ble_glue/hw_ipcc.c index ccdb0736e8b..64dd9ef9b7b 100644 --- a/firmware/targets/f7/ble_glue/hw_ipcc.c +++ b/firmware/targets/f7/ble_glue/hw_ipcc.c @@ -19,7 +19,7 @@ /* Includes ------------------------------------------------------------------*/ #include "app_common.h" -#include "mbox_def.h" +#include /* Global variables ---------------------------------------------------------*/ /* Private defines -----------------------------------------------------------*/ diff --git a/firmware/targets/f7/ble_glue/osal.h b/firmware/targets/f7/ble_glue/osal.h new file mode 100644 index 00000000000..e5e0c4f6896 --- /dev/null +++ b/firmware/targets/f7/ble_glue/osal.h @@ -0,0 +1,63 @@ +/***************************************************************************** + * @file osal.h + * @author MDG + * @brief This header file defines the OS abstraction layer used by + * the BLE stack. OSAL defines the set of functions which needs to be + * ported to target operating system and target platform. + * Actually, only memset, memcpy and memcmp wrappers are defined. + ***************************************************************************** + * @attention + * + * Copyright (c) 2018-2022 STMicroelectronics. + * All rights reserved. + * + * This software is licensed under terms that can be found in the LICENSE file + * in the root directory of this software component. + * If no LICENSE file comes with this software, it is provided AS-IS. + * + ***************************************************************************** + */ + +#ifndef OSAL_H__ +#define OSAL_H__ + +/** + * This function copies size number of bytes from a + * memory location pointed by src to a destination + * memory location pointed by dest + * + * @param[in] dest Destination address + * @param[in] src Source address + * @param[in] size size in the bytes + * + * @return Address of the destination + */ + +extern void* Osal_MemCpy(void* dest, const void* src, unsigned int size); + +/** + * This function sets first number of bytes, specified + * by size, to the destination memory pointed by ptr + * to the specified value + * + * @param[in] ptr Destination address + * @param[in] value Value to be set + * @param[in] size Size in the bytes + * + * @return Address of the destination + */ + +extern void* Osal_MemSet(void* ptr, int value, unsigned int size); + +/** + * This function compares n bytes of two regions of memory + * + * @param[in] s1 First buffer to compare. + * @param[in] s2 Second buffer to compare. + * @param[in] size Number of bytes to compare. + * + * @return 0 if the two buffers are equal, 1 otherwise + */ +extern int Osal_MemCmp(const void* s1, const void* s2, unsigned int size); + +#endif /* OSAL_H__ */ diff --git a/firmware/targets/f7/ble_glue/serial_service.c b/firmware/targets/f7/ble_glue/serial_service.c index 91e12dd688e..eb58ae0ec35 100644 --- a/firmware/targets/f7/ble_glue/serial_service.c +++ b/firmware/targets/f7/ble_glue/serial_service.c @@ -1,6 +1,6 @@ #include "serial_service.h" #include "app_common.h" -#include "ble.h" +#include #include diff --git a/firmware/targets/f7/furi_hal/furi_hal_bt.c b/firmware/targets/f7/furi_hal/furi_hal_bt.c index 47ed5992e31..1a2b436d28a 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_bt.c +++ b/firmware/targets/f7/furi_hal/furi_hal_bt.c @@ -1,7 +1,8 @@ #include -#include + +#include +#include #include -#include #include #include diff --git a/firmware/targets/f7/furi_hal/furi_hal_crypto.c b/firmware/targets/f7/furi_hal/furi_hal_crypto.c index b3c68e2d01c..dbd8c58c2a0 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_crypto.c +++ b/firmware/targets/f7/furi_hal/furi_hal_crypto.c @@ -4,7 +4,7 @@ #include #include #include -#include +#include #define TAG "FuriHalCrypto" diff --git a/firmware/targets/f7/furi_hal/furi_hal_flash.c b/firmware/targets/f7/furi_hal/furi_hal_flash.c index ac141db0459..9e05dc1238a 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_flash.c +++ b/firmware/targets/f7/furi_hal/furi_hal_flash.c @@ -1,8 +1,8 @@ #include #include #include -#include -#include +#include +#include #include diff --git a/firmware/targets/f7/furi_hal/furi_hal_info.c b/firmware/targets/f7/furi_hal/furi_hal_info.c index 1f75ea331ea..25ea42bb6d8 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_info.c +++ b/firmware/targets/f7/furi_hal/furi_hal_info.c @@ -4,7 +4,7 @@ #include #include -#include +#include #include #include diff --git a/firmware/targets/f7/furi_hal/furi_hal_version.c b/firmware/targets/f7/furi_hal/furi_hal_version.c index 0d87c807313..697d659311b 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_version.c +++ b/firmware/targets/f7/furi_hal/furi_hal_version.c @@ -6,7 +6,7 @@ #include #include -#include "ble.h" +#include #define TAG "FuriHalVersion" diff --git a/furi/core/common_defines.h b/furi/core/common_defines.h index c211ad7ee74..31be7fff074 100644 --- a/furi/core/common_defines.h +++ b/furi/core/common_defines.h @@ -1,5 +1,6 @@ #pragma once +#include "core_defines.h" #include #include #include @@ -10,92 +11,6 @@ extern "C" { #include -#ifndef MAX -#define MAX(a, b) \ - ({ \ - __typeof__(a) _a = (a); \ - __typeof__(b) _b = (b); \ - _a > _b ? _a : _b; \ - }) -#endif - -#ifndef MIN -#define MIN(a, b) \ - ({ \ - __typeof__(a) _a = (a); \ - __typeof__(b) _b = (b); \ - _a < _b ? _a : _b; \ - }) -#endif - -#ifndef ROUND_UP_TO -#define ROUND_UP_TO(a, b) \ - ({ \ - __typeof__(a) _a = (a); \ - __typeof__(b) _b = (b); \ - _a / _b + !!(_a % _b); \ - }) -#endif - -#ifndef CLAMP -#define CLAMP(x, upper, lower) (MIN(upper, MAX(x, lower))) -#endif - -#ifndef COUNT_OF -#define COUNT_OF(x) (sizeof(x) / sizeof(x[0])) -#endif - -#ifndef FURI_SWAP -#define FURI_SWAP(x, y) \ - do { \ - typeof(x) SWAP = x; \ - x = y; \ - y = SWAP; \ - } while(0) -#endif - -#ifndef PLACE_IN_SECTION -#define PLACE_IN_SECTION(x) __attribute__((section(x))) -#endif - -#ifndef ALIGN -#define ALIGN(n) __attribute__((aligned(n))) -#endif - -#ifndef __weak -#define __weak __attribute__((weak)) -#endif - -#ifndef UNUSED -#define UNUSED(X) (void)(X) -#endif - -#ifndef STRINGIFY -#define STRINGIFY(x) #x -#endif - -#ifndef TOSTRING -#define TOSTRING(x) STRINGIFY(x) -#endif - -#ifndef REVERSE_BYTES_U32 -#define REVERSE_BYTES_U32(x) \ - ((((x)&0x000000FF) << 24) | (((x)&0x0000FF00) << 8) | (((x)&0x00FF0000) >> 8) | \ - (((x)&0xFF000000) >> 24)) -#endif - -#ifndef FURI_BIT -#define FURI_BIT(x, n) (((x) >> (n)) & 1) -#endif - -#ifndef FURI_BIT_SET -#define FURI_BIT_SET(x, n) ((x) |= (1 << (n))) -#endif - -#ifndef FURI_BIT_CLEAR -#define FURI_BIT_CLEAR(x, n) ((x) &= ~(1 << (n))) -#endif - #ifndef FURI_IS_IRQ_MASKED #define FURI_IS_IRQ_MASKED() (__get_PRIMASK() != 0U) #endif diff --git a/furi/core/core_defines.h b/furi/core/core_defines.h new file mode 100644 index 00000000000..9ed05b2107c --- /dev/null +++ b/furi/core/core_defines.h @@ -0,0 +1,97 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#define FURI_RETURNS_NONNULL __attribute__((returns_nonnull)) + +#ifndef MAX +#define MAX(a, b) \ + ({ \ + __typeof__(a) _a = (a); \ + __typeof__(b) _b = (b); \ + _a > _b ? _a : _b; \ + }) +#endif + +#ifndef MIN +#define MIN(a, b) \ + ({ \ + __typeof__(a) _a = (a); \ + __typeof__(b) _b = (b); \ + _a < _b ? _a : _b; \ + }) +#endif + +#ifndef ROUND_UP_TO +#define ROUND_UP_TO(a, b) \ + ({ \ + __typeof__(a) _a = (a); \ + __typeof__(b) _b = (b); \ + _a / _b + !!(_a % _b); \ + }) +#endif + +#ifndef CLAMP +#define CLAMP(x, upper, lower) (MIN(upper, MAX(x, lower))) +#endif + +#ifndef COUNT_OF +#define COUNT_OF(x) (sizeof(x) / sizeof(x[0])) +#endif + +#ifndef FURI_SWAP +#define FURI_SWAP(x, y) \ + do { \ + typeof(x) SWAP = x; \ + x = y; \ + y = SWAP; \ + } while(0) +#endif + +#ifndef PLACE_IN_SECTION +#define PLACE_IN_SECTION(x) __attribute__((section(x))) +#endif + +#ifndef ALIGN +#define ALIGN(n) __attribute__((aligned(n))) +#endif + +#ifndef __weak +#define __weak __attribute__((weak)) +#endif + +#ifndef UNUSED +#define UNUSED(X) (void)(X) +#endif + +#ifndef STRINGIFY +#define STRINGIFY(x) #x +#endif + +#ifndef TOSTRING +#define TOSTRING(x) STRINGIFY(x) +#endif + +#ifndef REVERSE_BYTES_U32 +#define REVERSE_BYTES_U32(x) \ + ((((x)&0x000000FF) << 24) | (((x)&0x0000FF00) << 8) | (((x)&0x00FF0000) >> 8) | \ + (((x)&0xFF000000) >> 24)) +#endif + +#ifndef FURI_BIT +#define FURI_BIT(x, n) (((x) >> (n)) & 1) +#endif + +#ifndef FURI_BIT_SET +#define FURI_BIT_SET(x, n) ((x) |= (1 << (n))) +#endif + +#ifndef FURI_BIT_CLEAR +#define FURI_BIT_CLEAR(x, n) ((x) &= ~(1 << (n))) +#endif + +#ifdef __cplusplus +} +#endif diff --git a/furi/core/record.h b/furi/core/record.h index cb4bd199f88..4819123e277 100644 --- a/furi/core/record.h +++ b/furi/core/record.h @@ -6,6 +6,7 @@ #pragma once #include +#include "core_defines.h" #ifdef __cplusplus extern "C" { @@ -51,7 +52,7 @@ bool furi_record_destroy(const char* name); * @note Thread safe. Open and close must be executed from the same * thread. Suspends caller thread till record is available */ -void* furi_record_open(const char* name); +FURI_RETURNS_NONNULL void* furi_record_open(const char* name); /** Close record * diff --git a/lib/STM32CubeWB.scons b/lib/STM32CubeWB.scons index 02618ae73a3..80d06b5fcc9 100644 --- a/lib/STM32CubeWB.scons +++ b/lib/STM32CubeWB.scons @@ -2,19 +2,10 @@ Import("env") env.Append( CPPPATH=[ - "#/lib/STM32CubeWB/Drivers/CMSIS/Device/ST", "#/lib/STM32CubeWB/Drivers/CMSIS/Device/ST/STM32WBxx/Include", "#/lib/STM32CubeWB/Drivers/CMSIS/Include", "#/lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc", - "#/lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/Legacy", "#/lib/STM32CubeWB/Middlewares/ST/STM32_WPAN", - "#/lib/STM32CubeWB/Middlewares/ST/STM32_WPAN/ble", - "#/lib/STM32CubeWB/Middlewares/ST/STM32_WPAN/ble/core", - "#/lib/STM32CubeWB/Middlewares/ST/STM32_WPAN/ble/core/template", - "#/lib/STM32CubeWB/Middlewares/ST/STM32_WPAN/interface/patterns/ble_thread", - "#/lib/STM32CubeWB/Middlewares/ST/STM32_WPAN/interface/patterns/ble_thread/shci", - "#/lib/STM32CubeWB/Middlewares/ST/STM32_WPAN/interface/patterns/ble_thread/tl", - "#/lib/STM32CubeWB/Middlewares/ST/STM32_WPAN/utilities", ], CPPDEFINES=[ "STM32WB", @@ -33,6 +24,16 @@ if env["RAM_EXEC"]: libenv = env.Clone(FW_LIB_NAME="stm32cubewb") +libenv.Append( + CPPPATH=[ + "#/lib/STM32CubeWB/Middlewares/ST/STM32_WPAN/ble", + "#/lib/STM32CubeWB/Middlewares/ST/STM32_WPAN/ble/core", + "#/lib/STM32CubeWB/Middlewares/ST/STM32_WPAN/interface/patterns/ble_thread", + "#/lib/STM32CubeWB/Middlewares/ST/STM32_WPAN/interface/patterns/ble_thread/shci", + "#/lib/STM32CubeWB/Middlewares/ST/STM32_WPAN/interface/patterns/ble_thread/tl", + "#/lib/STM32CubeWB/Middlewares/ST/STM32_WPAN/utilities", + ] +) libenv.ApplyLibFlags() sources = libenv.GlobRecursive( diff --git a/lib/toolbox/SConscript b/lib/toolbox/SConscript index 26c233d98b5..e4f2cbd0200 100644 --- a/lib/toolbox/SConscript +++ b/lib/toolbox/SConscript @@ -29,6 +29,7 @@ build_version = libenv.VersionBuilder( fw_version_json = libenv.InstallAs( "${BUILD_DIR}/${FIRMWARE_BUILD_CFG}.json", "version.json" ) +Alias("version_json", fw_version_json) env.Append(FW_VERSION_JSON=fw_version_json) # Default(fw_version_json) @@ -40,7 +41,7 @@ if not version_depends: sources = libenv.GlobRecursive("*.c") -libenv.Append(CPPPATH=["."]) +libenv.Append(CPPPATH=[libenv.Dir(".")]) lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources) libenv.Install("${LIB_DIST_DIR}", lib) From 8992f8ac3633702947177021cd845f8947ef91c2 Mon Sep 17 00:00:00 2001 From: gornekich Date: Mon, 22 Aug 2022 20:14:31 +0300 Subject: [PATCH 008/824] Fix mifare ultralight/ntag unlock #1624 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- lib/nfc/nfc_worker.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/nfc/nfc_worker.c b/lib/nfc/nfc_worker.c index 45bbc5f41a4..f07ea616ced 100644 --- a/lib/nfc/nfc_worker.c +++ b/lib/nfc/nfc_worker.c @@ -545,6 +545,16 @@ void nfc_worker_mf_ultralight_read_auth(NfcWorker* nfc_worker) { } data->auth_success = mf_ultralight_authenticate(&tx_rx, key, &pack); + + if(!data->auth_success) { + // Reset card + furi_hal_nfc_sleep(); + if(!furi_hal_nfc_activate_nfca(300, NULL)) { + nfc_worker->callback(NfcWorkerEventFail, nfc_worker->context); + break; + } + } + mf_ul_read_card(&tx_rx, &reader, data); if(data->auth_success) { MfUltralightConfigPages* config_pages = mf_ultralight_get_config_pages(data); From 9317ded1a96b6ed3e78e094bb8cbd4f72e5eadbb Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Mon, 22 Aug 2022 20:54:01 +0300 Subject: [PATCH 009/824] [FL-2749] New power off screen #1637 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- applications/power/power_service/power.c | 14 +++--- applications/power/power_service/power_api.c | 4 +- applications/power/power_service/power_i.h | 5 +- .../power_service/views/power_unplug_usb.c | 43 ++++++++++++++++++ .../power_service/views/power_unplug_usb.h | 11 +++++ .../icons/Power/Unplug_bg_bottom_128x10.png | Bin 0 -> 5355 bytes assets/icons/Power/Unplug_bg_top_128x14.png | Bin 0 -> 5945 bytes 7 files changed, 67 insertions(+), 10 deletions(-) create mode 100644 applications/power/power_service/views/power_unplug_usb.c create mode 100644 applications/power/power_service/views/power_unplug_usb.h create mode 100644 assets/icons/Power/Unplug_bg_bottom_128x10.png create mode 100644 assets/icons/Power/Unplug_bg_top_128x14.png diff --git a/applications/power/power_service/power.c b/applications/power/power_service/power.c index 9036ae1ce39..3a2c6cf3b1f 100644 --- a/applications/power/power_service/power.c +++ b/applications/power/power_service/power.c @@ -55,13 +55,14 @@ Power* power_alloc() { // Gui power->view_dispatcher = view_dispatcher_alloc(); - power->popup = popup_alloc(); - popup_set_header( - power->popup, "Disconnect USB for safe\nshutdown", 64, 26, AlignCenter, AlignTop); - view_dispatcher_add_view(power->view_dispatcher, PowerViewPopup, popup_get_view(power->popup)); power->power_off = power_off_alloc(); view_dispatcher_add_view( power->view_dispatcher, PowerViewOff, power_off_get_view(power->power_off)); + power->power_unplug_usb = power_unplug_usb_alloc(); + view_dispatcher_add_view( + power->view_dispatcher, + PowerViewUnplugUsb, + power_unplug_usb_get_view(power->power_unplug_usb)); view_dispatcher_attach_to_gui( power->view_dispatcher, power->gui, ViewDispatcherTypeFullscreen); @@ -78,8 +79,9 @@ void power_free(Power* power) { // Gui view_dispatcher_remove_view(power->view_dispatcher, PowerViewOff); power_off_free(power->power_off); - view_dispatcher_remove_view(power->view_dispatcher, PowerViewPopup); - popup_free(power->popup); + view_dispatcher_remove_view(power->view_dispatcher, PowerViewUnplugUsb); + power_unplug_usb_free(power->power_unplug_usb); + view_port_free(power->battery_view_port); // State diff --git a/applications/power/power_service/power_api.c b/applications/power/power_service/power_api.c index d26fb3b4f09..8185b7cd528 100644 --- a/applications/power/power_service/power_api.c +++ b/applications/power/power_service/power_api.c @@ -8,8 +8,8 @@ void power_off(Power* power) { furi_hal_power_off(); // Notify user if USB is plugged view_dispatcher_send_to_front(power->view_dispatcher); - view_dispatcher_switch_to_view(power->view_dispatcher, PowerViewPopup); - furi_delay_ms(10); + view_dispatcher_switch_to_view(power->view_dispatcher, PowerViewUnplugUsb); + furi_delay_ms(100); furi_halt("Disconnect USB for safe shutdown"); } diff --git a/applications/power/power_service/power_i.h b/applications/power/power_service/power_i.h index c7181d0a1ad..66ced885b16 100755 --- a/applications/power/power_service/power_i.h +++ b/applications/power/power_service/power_i.h @@ -8,6 +8,7 @@ #include #include "views/power_off.h" +#include "views/power_unplug_usb.h" #include @@ -21,8 +22,8 @@ typedef enum { struct Power { ViewDispatcher* view_dispatcher; - Popup* popup; PowerOff* power_off; + PowerUnplugUsb* power_unplug_usb; ViewPort* battery_view_port; Gui* gui; @@ -42,6 +43,6 @@ struct Power { }; typedef enum { - PowerViewPopup, PowerViewOff, + PowerViewUnplugUsb, } PowerView; diff --git a/applications/power/power_service/views/power_unplug_usb.c b/applications/power/power_service/views/power_unplug_usb.c new file mode 100644 index 00000000000..6d14be22c70 --- /dev/null +++ b/applications/power/power_service/views/power_unplug_usb.c @@ -0,0 +1,43 @@ +#include "power_unplug_usb.h" +#include +#include + +struct PowerUnplugUsb { + View* view; +}; + +static void power_unplug_usb_draw_callback(Canvas* canvas, void* _model) { + UNUSED(_model); + + canvas_set_color(canvas, ColorBlack); + canvas_draw_icon(canvas, 0, 0, &I_Unplug_bg_top_128x14); + canvas_draw_box(canvas, 0, 14, 128, (64 - 10 - 14)); + canvas_draw_icon(canvas, 0, (64 - 10), &I_Unplug_bg_bottom_128x10); + + canvas_set_color(canvas, ColorWhite); + canvas_set_font(canvas, FontPrimary); + elements_multiline_text_aligned( + canvas, 64, 32, AlignCenter, AlignCenter, "It's now safe to unplug\nUSB cable"); +} + +PowerUnplugUsb* power_unplug_usb_alloc() { + PowerUnplugUsb* power_unplug_usb = malloc(sizeof(PowerUnplugUsb)); + + power_unplug_usb->view = view_alloc(); + view_set_context(power_unplug_usb->view, power_unplug_usb); + view_set_draw_callback(power_unplug_usb->view, power_unplug_usb_draw_callback); + view_set_input_callback(power_unplug_usb->view, NULL); + + return power_unplug_usb; +} + +void power_unplug_usb_free(PowerUnplugUsb* power_unplug_usb) { + furi_assert(power_unplug_usb); + view_free(power_unplug_usb->view); + free(power_unplug_usb); +} + +View* power_unplug_usb_get_view(PowerUnplugUsb* power_unplug_usb) { + furi_assert(power_unplug_usb); + return power_unplug_usb->view; +} diff --git a/applications/power/power_service/views/power_unplug_usb.h b/applications/power/power_service/views/power_unplug_usb.h new file mode 100644 index 00000000000..e85c6d03b9a --- /dev/null +++ b/applications/power/power_service/views/power_unplug_usb.h @@ -0,0 +1,11 @@ +#pragma once + +typedef struct PowerUnplugUsb PowerUnplugUsb; + +#include + +PowerUnplugUsb* power_unplug_usb_alloc(); + +void power_unplug_usb_free(PowerUnplugUsb* power_unplug_usb); + +View* power_unplug_usb_get_view(PowerUnplugUsb* power_unplug_usb); diff --git a/assets/icons/Power/Unplug_bg_bottom_128x10.png b/assets/icons/Power/Unplug_bg_bottom_128x10.png new file mode 100644 index 0000000000000000000000000000000000000000..35d73ba76ea10724f02119ac7c7413c6ec7d6d3f GIT binary patch literal 5355 zcmeHKc~lcu7mtVn0nv(}RVp#iD%HuJkPL)HK#3X%i!3VWWHJdMD@ib66O0>+h>BVf z5L8qU3M!y1qV_|<6^pyLp;8wV#08humTwYV@U-WA$8)~^XU>`A&0T))-ru|LzPxmv z|I88C@mLH7GlJvi6NLVzq7T-5IQpGr8ybhfn06`%;K>r=54XZr4WH9YZMGWU71>&*+nu8nyi+sUR&&B)^ZK2foU zl6Q8~thtiLn$A_nJ;I`} z6rSL5JlW&$!{6Ezg=5$@C-4ZdEA7Y6jVY};u;)@t*jLW70nUr7fkoaT5v}ilQ&ys$cxLF?%V?j5acfjUzR8I#-?9BFqD2uUK~HMH}aa z&99gC(eoqi`-*Q54yBg#TV2Zy~7HOOFCHJ$J4j0n>oZPsMw@% zZ}zUcu)oxaQKQgHI>=yZy{XBpQA2X?=7gSb}8IQc5cey&_v)JU#y$&qVI^e)$J70JDHvIKu$J~8(09GFep1%gW z(w$nK72ZiM+&z7JetFw7*A8upxiUNZfs0u<@K_iVFVNB{!jyV4_}I=K99i+SuXbW=5f5eKQunH-Q_2*WBWCE+8n5( zEHjNMDDk)R@wKnP`j(7}SrPi(9KxUaA4YI8GIw;$Q})99x;pNbmn|#9nJ-CYf-L8H zzx4juSb*=w>}hF8%x=)8@`%*4UE|DXHyOd{wVO{?WJFe)Lzv{WNmHn&H|G>PCAC~( znO3RZi{Suldn5dY&qIP75VHRW07`3ZLM50QwIuHerAsPs$lS$=jP{+m_aY1x!5EJn@ zqlres#)orxIB$gt!O;jb0txWdi5FAw?pU0gN+1M-d}h3dK;PK-XpKe*5{X)^mY}5) z6eTw_9 zlPS5}kMMH!dlpbWh&o6~BojzPnT$BtL#^>$jDoxm=pQ}QA?SHd3_{e3I2DZeE=J@U z$H5Q+_@lovP9-&_BY=sB6p^8(YIIieXH(APaCsj+3>1jOGNsWACHpf=jac}Jtj}UI zXpHF$4g_`oi2IrKhun?Es1=tB`Y7NyLwFn?Hr_BlC{Vy+0cadTLOzv2MQ8xTr|7BUfu6IgA*fP;i16#X$rV7G1~?0tku$(3nCxz#<9A z0H4I5@kw-s3xx~~f(THF(W-=`gQGG)2~a2si%O@!LIwaagj9egAn^efnMDC$ghh3M zUcy?7ipo-a(*#!a^OcEWS@gWAl=aXT8BA^R}Ofn1$Tn4J? zBf454)Mz0U;w3_PM7cuC)5sNWigBTwKaWKljTlw|B@7@jfxi<*e4jAU&@(<{>_+?> zCvHZAK}`nr8<3&R3+;u(kInEsXK1|t=I8xd{F_tYa9=0+B7VQp^_8wKV&IF6zgE{* zy1s~kFEajGUH><_u%E6|h#dVFq(v`F?Ryu+q8F{<{Mj>oj8`tqTknPxbY!9Qi%?@Q zmNtfG2xd$EI8x<`S?WZWr_iqttv6Yg$&cd4(I zGg~J*H#IwP$FfzI1I&{q>hC?}>v?~e;f5-yLH|iSALd>;oc*e{=<= G^#294V-(;3 literal 0 HcmV?d00001 diff --git a/assets/icons/Power/Unplug_bg_top_128x14.png b/assets/icons/Power/Unplug_bg_top_128x14.png new file mode 100644 index 0000000000000000000000000000000000000000..bafa2c4947082eff8f59d48d119e9ca01ece9729 GIT binary patch literal 5945 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 Date: Mon, 22 Aug 2022 22:01:42 +0400 Subject: [PATCH 010/824] [FL-2757] SubGhz: add protocol Magellen (#1633) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * SubGhz: add decoder Paradox Wireless * SubGhz: fix syntax * SubGhz: rename paradox_wireless -> magellen, add encoder Magellen, parse event * SuBghz: add unit_test Magellen Co-authored-by: あく --- applications/unit_tests/subghz/subghz_test.c | 17 +- assets/unit_tests/subghz/magellen.sub | 7 + assets/unit_tests/subghz/magellen_raw.sub | 8 + assets/unit_tests/subghz/test_random_raw.sub | 11 + lib/subghz/protocols/magellen.c | 444 +++++++++++++++++++ lib/subghz/protocols/magellen.h | 107 +++++ lib/subghz/protocols/registry.c | 2 +- lib/subghz/protocols/registry.h | 1 + 8 files changed, 595 insertions(+), 2 deletions(-) create mode 100644 assets/unit_tests/subghz/magellen.sub create mode 100644 assets/unit_tests/subghz/magellen_raw.sub create mode 100644 lib/subghz/protocols/magellen.c create mode 100644 lib/subghz/protocols/magellen.h diff --git a/applications/unit_tests/subghz/subghz_test.c b/applications/unit_tests/subghz/subghz_test.c index f91d27234a9..04f442f6c41 100644 --- a/applications/unit_tests/subghz/subghz_test.c +++ b/applications/unit_tests/subghz/subghz_test.c @@ -13,7 +13,7 @@ #define CAME_ATOMO_DIR_NAME EXT_PATH("subghz/assets/came_atomo") #define NICE_FLOR_S_DIR_NAME EXT_PATH("subghz/assets/nice_flor_s") #define TEST_RANDOM_DIR_NAME EXT_PATH("unit_tests/subghz/test_random_raw.sub") -#define TEST_RANDOM_COUNT_PARSE 188 +#define TEST_RANDOM_COUNT_PARSE 196 #define TEST_TIMEOUT 10000 static SubGhzEnvironment* environment_handler; @@ -412,6 +412,13 @@ MU_TEST(subghz_decoder_honeywell_wdb_test) { "Test decoder " SUBGHZ_PROTOCOL_HONEYWELL_WDB_NAME " error\r\n"); } +MU_TEST(subghz_decoder_magellen_test) { + mu_assert( + subghz_decoder_test( + EXT_PATH("unit_tests/subghz/magellen_raw.sub"), SUBGHZ_PROTOCOL_MAGELLEN_NAME), + "Test decoder " SUBGHZ_PROTOCOL_MAGELLEN_NAME " error\r\n"); +} + //test encoders MU_TEST(subghz_encoder_princeton_test) { mu_assert( @@ -515,6 +522,12 @@ MU_TEST(subghz_encoder_honeywell_wdb_test) { "Test encoder " SUBGHZ_PROTOCOL_HONEYWELL_WDB_NAME " error\r\n"); } +MU_TEST(subghz_encoder_magellen_test) { + mu_assert( + subghz_encoder_test(EXT_PATH("unit_tests/subghz/magellen.sub")), + "Test encoder " SUBGHZ_PROTOCOL_MAGELLEN_NAME " error\r\n"); +} + MU_TEST(subghz_random_test) { mu_assert(subghz_decode_random_test(TEST_RANDOM_DIR_NAME), "Random test error\r\n"); } @@ -552,6 +565,7 @@ MU_TEST_SUITE(subghz) { MU_RUN_TEST(subghz_decoder_doitrand_test); MU_RUN_TEST(subghz_decoder_phoenix_v2_test); MU_RUN_TEST(subghz_decoder_honeywell_wdb_test); + MU_RUN_TEST(subghz_decoder_magellen_test); MU_RUN_TEST(subghz_encoder_princeton_test); MU_RUN_TEST(subghz_encoder_came_test); @@ -570,6 +584,7 @@ MU_TEST_SUITE(subghz) { MU_RUN_TEST(subghz_encoder_doitrand_test); MU_RUN_TEST(subghz_encoder_phoenix_v2_test); MU_RUN_TEST(subghz_encoder_honeywell_wdb_test); + MU_RUN_TEST(subghz_encoder_magellen_test); MU_RUN_TEST(subghz_random_test); subghz_test_deinit(); diff --git a/assets/unit_tests/subghz/magellen.sub b/assets/unit_tests/subghz/magellen.sub new file mode 100644 index 00000000000..3317fd4bce9 --- /dev/null +++ b/assets/unit_tests/subghz/magellen.sub @@ -0,0 +1,7 @@ +Filetype: Flipper SubGhz Key File +Version: 1 +Frequency: 433920000 +Preset: FuriHalSubGhzPresetOok650Async +Protocol: Magellen +Bit: 32 +Key: 00 00 00 00 37 AE 48 28 diff --git a/assets/unit_tests/subghz/magellen_raw.sub b/assets/unit_tests/subghz/magellen_raw.sub new file mode 100644 index 00000000000..0d70576bab5 --- /dev/null +++ b/assets/unit_tests/subghz/magellen_raw.sub @@ -0,0 +1,8 @@ +Filetype: Flipper SubGhz RAW File +Version: 1 +Frequency: 433920000 +Preset: FuriHalSubGhzPresetOok650Async +Protocol: RAW +RAW_Data: 29262 361 -68 2635 -66 24113 -66 1131 -100 4157 -66 26253 -130 621 -18438 99 -298 231 -66 197 -496 753 -230 7503 -16526 65 -396 65 -296 99 -196 293 -64 429 -132 397 -66 329 -66 37701 -66 13475 -100 54967 -64 18209 -18340 97 -462 197 -98 587 -232 97 -100 259 -98 197 -262 297 -64 557 -100 599 -100 333 -234 42493 -13212 6449 -206 173 -214 217 -176 195 -218 181 -218 181 -182 217 -182 217 -176 187 -214 215 -180 217 -182 217 -182 217 -178 185 -424 1177 -388 387 -240 381 -214 181 -398 211 -380 419 -176 217 -394 203 -394 205 -380 189 -402 421 -168 219 -398 393 -190 191 -398 205 -406 185 -402 381 -212 215 -362 241 -378 421 -176 377 -218 197 -378 427 -210 393 -172 429 -172 397 -212 217 -362 389 -228 197 -372 417 -204 395 -210 181 -398 391 -192 201 -216888 761 -200 299 -166 695 -132 15435 -66 5611 -66 21049 -66 4947 -66 2355 -66 1921 -100 2223 -100 2107 -100 397 -98 3643 -66 5301 -98 14205 -66 37371 -246 175 -216 179 -216 177 -224 149 -246 159 -228 181 -212 201 -204 159 -244 151 -254 169 -214 181 -210 197 -182 181 -454 1141 -444 357 -228 361 -246 177 -396 209 -412 367 -188 187 -434 201 -394 185 -406 193 -402 377 -238 181 -386 381 -234 153 -424 205 -412 157 -412 383 -240 181 -398 203 -392 385 -236 371 -212 179 -400 383 -240 359 -210 375 -220 381 -246 175 -394 383 -240 181 -398 363 -222 379 -246 175 -394 383 -204 217 -182856 99 -66 99 -300 133 -402 65 -198 99 -328 65 -100 491 -164 593 -100 3547 -64 361 -66 789 -68 2521 -66 22883 -66 2659 -98 3309 -130 3789 -100 9689 -17178 99 -1388 65 -266 197 -100 131 -134 99 -232 627 -130 233 -66 1949 -100 14567 -198 165 -256 181 -208 159 -214 183 -220 163 -244 149 -246 159 -236 181 -254 141 -226 151 -246 157 -228 181 -212 201 -400 1163 -428 379 -230 355 -244 177 -396 207 -412 367 -222 157 -418 189 -410 207 -412 171 -430 357 -226 165 -404 413 -204 181 -428 173 -428 169 -426 353 -236 173 -414 173 -408 381 -244 337 -222 201 -408 397 -208 393 -204 395 -208 359 -246 177 -394 387 -200 205 -380 415 -202 395 -208 181 -432 357 -226 169 -195084 65 -300 763 -66 297 -364 593 -68 2883 -66 1357 -68 363 -98 3841 -66 3119 -66 5153 -66 4023 -268 143 -246 133 -290 141 -250 139 -254 141 -226 181 -248 137 -254 143 -252 139 -252 143 -230 181 -250 139 -254 145 -436 1135 -448 349 -240 347 -254 157 -434 167 -426 377 -226 157 -434 167 -426 155 -440 163 -434 375 -206 215 -380 381 -234 153 +RAW_Data: -424 205 -412 159 -412 381 -240 181 -398 203 -392 387 -236 369 -212 179 -400 383 -240 359 -244 339 -222 381 -246 175 -394 383 -240 181 -398 363 -222 381 -244 175 -392 383 -240 181 -184002 99 -360 63 -330 65 -132 129 -232 97 -198 295 -328 6031 -66 831 -132 3417 -66 2187 -64 2183 -100 6535 -66 1127 -66 2569 -66 2031 -66 2271 -66 2183 -66 3815 -66 3803 -66 493 -66 1909 -66 1627 -98 4805 -17512 67 -2164 131 -498 265 -430 163 -98 97 -64 99 -230 99 -100 229 -230 165 -196 63 -132 99 -66 927 -66 14955 -66 19621 -68 2627 -66 14305 -68 23247 -66 2891 -66 3941 -66 3021 -212 173 -242 181 -218 181 -214 181 -208 157 -250 141 -248 181 -218 179 -214 179 -210 159 -250 179 -214 181 -218 181 -404 1153 -404 389 -244 375 -192 181 -436 161 -414 383 -240 181 -398 205 -392 201 -394 205 -394 365 -246 177 -396 383 -204 217 -398 171 -426 167 -428 353 -242 173 -420 173 -408 373 -220 403 -208 175 -422 381 -194 399 -228 357 -246 355 -210 215 -400 387 -208 181 -398 391 -226 353 -246 177 -398 383 -204 217 -185098 163 -166 525 -98 293 -100 63 -66 229 -66 1183 -66 1507 -66 3089 -98 30187 -66 2847 -19112 133 -364 131 -394 97 -166 295 -66 229 -164 227 -66 263 -130 623 -98 2071 -66 493 -66 787 -98 691 -64 10249 -132 3879 -66 1949 -66 3453 -198 23157 -66 2845 -100 1193 -66 1587 -100 3797 -98 3187 -100 3319 -66 22119 -98 5513 -226 155 -244 153 -256 131 -248 151 -246 159 -262 121 -274 133 -272 127 -244 153 -254 167 -248 145 -244 133 -252 177 -398 1169 -418 381 -238 359 -242 141 -430 169 -426 357 -274 139 -422 171 -442 173 -428 167 -426 353 -236 171 -416 379 -226 149 -436 161 -438 173 -406 381 -234 153 -424 205 -380 389 -244 359 -206 215 -384 381 -246 335 -224 383 -246 355 -244 179 -404 385 -206 181 -432 359 -226 355 -246 175 -398 383 -240 181 -179760 97 -168 727 -66 97 -332 1389 -66 2793 -66 4955 -100 12453 -100 2425 -66 21965 -66 3809 -68 1683 -66 3095 -66 2153 -64 999 -208 173 -220 181 -214 191 -196 181 -212 183 -220 191 -212 181 -214 191 -198 181 -212 181 -222 191 -212 181 -214 191 -416 1167 -424 369 -220 373 -210 209 -390 207 -376 403 -190 187 -418 189 -408 209 -412 173 -428 357 -226 169 -404 399 -208 179 -412 209 -396 169 -428 355 -230 201 -378 205 -406 381 -244 339 -222 193 -400 413 -204 393 -208 347 -220 401 -210 175 -422 383 -202 217 -398 365 -222 377 -246 175 -390 385 -204 217 -179890 165 -1552 131 -164 65 +RAW_Data: -1448 361 -17056 131 -134 233 -1462 131 -166 953 -100 261 -164 5077 -272 137 -268 143 -252 141 -248 143 -246 159 -252 141 -244 143 -290 107 -276 145 -244 131 -250 179 -248 143 -252 141 -414 1165 -424 373 -236 359 -242 145 -434 169 -428 355 -230 169 -442 173 -434 157 -406 193 -402 379 -238 181 -422 335 -252 157 -434 167 -428 185 -406 381 -208 211 -390 207 -410 381 -200 373 -236 171 -414 383 -202 393 -210 379 -220 373 -208 211 -390 383 -204 217 -398 365 -220 379 -244 175 -394 381 -240 181 -161030 97 -166 167 -930 593 -2670 1091 -132 229 -98 461 -164 1649 -66 6311 -100 44723 -16832 67 -2656 131 -132 99 -132 263 -100 399 -68 893 -18950 99 -164 165 -198 525 -998 335 -66 565 -66 1057 -17880 97 -360 195 -262 131 -332 625 -98 197 -230 455 -98 9343 -16498 67 -368 131 -598 65 -1066 333 -300 789 -130 757 -66 87207 -16554 97 -3520 97 -786 591 -64 461 -98 21495 -66 24811 -18448 131 -296 491 -134 163 -760 1091 -230 893 -66 927 -68 4581 -68 32965 -64 45217 -17292 131 -1684 231 -132 327 -64 163 -330 263 -230 25751 diff --git a/assets/unit_tests/subghz/test_random_raw.sub b/assets/unit_tests/subghz/test_random_raw.sub index 0a7d529ce64..06dfb9b4b6a 100644 --- a/assets/unit_tests/subghz/test_random_raw.sub +++ b/assets/unit_tests/subghz/test_random_raw.sub @@ -128,3 +128,14 @@ RAW_Data: 161 -302 147 -320 331 -162 171 -314 161 -312 163 -300 175 -296 195 -31 RAW_Data: 163 -300 175 -298 169 -318 177 -290 191 -298 165 -314 161 -310 189 -284 185 -292 189 -298 193 -284 189 -310 163 -312 497 -460 145 -320 325 -170 169 -290 333 -162 171 -314 307 -158 317 -168 169 -290 333 -162 171 -314 281 -182 159 -296 189 -300 165 -312 309 -182 313 -168 143 -318 331 -162 301 -148 319 -196 139 -314 165 -336 137 -338 135 -338 161 -312 159 -294 327 -160 173 -314 163 -312 159 -312 163 -300 175 -302 195 -312 163 -312 159 -312 163 -300 175 -298 169 -318 175 -292 189 -300 165 -312 161 -312 187 -284 185 -292 189 -300 193 -284 187 -312 161 -314 471 -486 167 -314 313 -152 159 -300 335 -162 169 -308 321 -156 329 -170 127 -318 321 -176 155 -300 333 -164 169 -302 165 -314 161 -310 329 -168 299 -166 151 -328 311 -154 331 -168 299 -178 143 -318 153 -322 167 -332 149 -320 151 -318 165 -304 321 -156 181 -290 189 -300 173 -294 177 -294 189 -302 173 -294 177 -294 189 -302 173 -294 177 -318 193 -278 173 -294 179 -292 191 -302 173 -294 177 -294 189 -302 173 -320 177 -294 195 -278 175 -318 491 -460 163 -314 333 -158 159 -294 327 -160 171 -316 311 -180 307 -170 141 -318 331 -162 143 -316 309 -182 157 -294 189 -302 165 -312 309 -182 313 -168 141 -318 331 -164 299 -148 321 -194 141 -314 165 -334 137 -338 135 -338 161 -312 159 -294 329 -158 173 -314 163 -310 161 -306 173 -314 165 -304 193 -284 187 -286 189 -276 197 -294 193 -310 163 -312 161 -310 163 -300 175 -296 193 -310 165 -284 187 -310 163 -300 175 -300 169 -318 177 -316 483 -466 187 -292 341 -134 171 -308 319 -150 179 -300 333 -164 309 -162 171 -316 313 -152 153 -316 327 -160 171 -316 163 -310 161 -312 307 -166 337 -136 171 -322 305 -164 299 -174 301 -194 141 -318 165 -336 137 -338 161 -310 163 -300 149 -320 331 -162 171 -314 161 -312 163 -300 173 -296 195 -310 163 -312 159 -312 163 -312 159 -296 189 -302 165 -312 189 -284 189 -284 185 -294 189 -296 165 -314 161 -310 189 -284 187 -294 189 -298 193 -284 485 -494 167 -298 321 -150 177 -312 313 -168 171 -290 333 -162 299 -176 151 -316 325 -162 171 -316 289 -176 151 -314 189 -296 165 -314 309 -182 305 -170 141 -318 333 -162 299 -150 319 -194 141 -314 165 -334 137 -340 133 -338 161 -302 149 -320 329 -164 171 -286 187 -312 163 -300 173 -298 193 -310 163 -312 159 -312 163 -312 159 -296 189 -302 191 -286 187 -284 189 -312 159 -292 191 -296 165 -314 161 -310 189 -284 187 -292 RAW_Data: 189 -300 193 -284 485 -494 167 -298 321 -150 177 -300 327 -150 167 -328 305 -160 319 -168 171 -290 333 -162 171 -314 281 -184 159 -296 189 -300 165 -314 307 -184 311 -170 141 -318 333 -162 299 -150 319 -194 139 -314 167 -334 135 -340 133 -338 163 -312 159 -294 327 -158 173 -314 163 -312 159 -312 165 -298 177 -302 193 -314 163 -310 161 -312 163 -300 173 -300 167 -318 177 -292 189 -300 165 -312 161 -312 187 -284 185 -292 189 -300 191 -286 187 -284 189 -300 487 -484 149 -320 333 -138 171 -322 321 -154 151 -316 327 -160 311 -164 173 -312 307 -160 161 -298 355 -132 173 -314 163 -310 161 -312 335 -170 311 -164 143 -322 307 -164 323 -150 301 -196 141 -318 165 -334 137 -338 161 -312 161 -302 147 -320 331 -162 173 -312 161 -312 163 -300 173 -298 195 -310 163 -312 159 -312 163 -312 159 -296 189 -300 193 -284 189 -284 189 -284 185 -294 189 -296 165 -314 161 -312 189 -284 185 -294 189 -298 193 -286 485 -468 193 -296 323 -150 147 -340 313 -168 171 -292 331 -164 299 -174 151 -316 325 -160 173 -316 289 -176 149 -316 189 -294 165 -314 309 -182 307 -170 141 -318 331 -164 299 -148 321 -194 139 -314 167 -306 165 -338 133 -338 163 -312 159 -294 327 -160 173 -314 163 -312 161 -310 165 -300 175 -300 169 -318 177 -290 189 -298 165 -314 187 -284 189 -312 159 -294 189 -298 165 -314 161 -310 161 -306 173 -316 193 -280 191 -312 159 -312 163 -300 515 -460 161 -312 305 -190 135 -328 329 -134 201 -286 309 -182 311 -168 143 -318 331 -162 143 -316 309 -182 157 -294 189 -300 165 -312 309 -184 311 -168 143 -318 331 -162 301 -148 319 -196 139 -314 165 -334 137 -340 159 -312 163 -300 149 -318 331 -162 173 -312 161 -312 163 -300 175 -296 195 -310 163 -312 159 -312 163 -300 173 -296 193 -312 163 -312 159 -312 163 -312 161 -298 189 -300 165 -312 161 -312 189 -312 159 -292 191 -300 191 -286 485 -468 193 -298 323 -148 147 -342 323 -160 173 -314 289 -176 297 -196 139 -314 331 -162 143 -314 311 -180 157 -292 191 -300 165 -312 311 -182 309 -170 141 -318 333 -162 299 -150 319 -194 141 -314 165 -334 137 -338 161 -312 161 -312 159 -294 329 -158 173 -314 163 -310 161 -312 165 -298 175 -304 193 -312 163 -310 161 -310 163 -302 173 -300 169 -318 175 -292 189 -300 165 -312 161 -310 189 -278 173 -316 195 -280 191 -312 159 -312 163 -300 515 -460 163 -310 305 -190 137 -328 329 -134 199 -288 309 -182 311 -168 143 -318 RAW_Data: 331 -162 143 -316 309 -182 157 -292 191 -300 165 -312 309 -182 313 -168 141 -318 331 -164 299 -174 295 -194 141 -314 165 -334 137 -340 133 -338 163 -312 159 -292 327 -160 171 -316 163 -310 161 -312 165 -298 175 -304 193 -312 163 -312 159 -312 163 -300 175 -300 169 -316 177 -292 189 -298 167 -312 161 -310 189 -278 173 -316 193 -282 191 -312 159 -312 163 -300 515 -460 163 -310 305 -190 137 -326 331 -132 201 -286 309 -184 309 -170 141 -318 333 -162 143 -314 311 -180 159 -294 189 -300 165 -312 309 -184 311 -168 143 -318 331 -162 301 -148 319 -196 139 -314 165 -336 135 -340 159 -312 163 -300 149 -320 329 -164 171 -314 159 -312 163 -300 175 -296 195 -310 163 -312 159 -312 163 -300 175 -294 195 -310 163 -312 161 -310 163 -300 175 -298 169 -318 153 -314 191 -296 193 -286 187 -284 189 -312 161 -294 509 -468 159 -298 335 -162 169 -304 323 -150 175 -300 335 -162 311 -160 173 -316 313 -152 151 -316 327 -160 171 -316 163 -312 159 -312 307 -168 335 -138 169 -322 305 -164 299 -176 299 -194 141 -316 167 -334 137 -340 159 -312 161 -302 147 -320 331 -162 173 -312 161 -312 163 -300 175 -296 193 -310 165 -310 161 -312 161 -302 173 -296 195 -310 163 -312 161 -310 163 -300 175 -298 169 -318 177 -290 189 -296 193 -286 187 -310 163 -300 175 -296 501 -484 161 -288 347 -154 151 -316 325 -160 173 -314 313 -152 301 -196 141 -314 331 -162 143 -316 309 -182 157 -294 189 -300 165 -314 309 -182 311 -168 143 -318 331 -162 301 -148 319 -196 139 -314 165 -336 137 -338 133 -338 163 -312 159 -294 327 -158 173 -316 161 -312 159 -312 163 -298 177 -302 193 -314 163 -312 159 -312 163 -300 175 -298 169 -318 177 -290 191 -298 165 -314 161 -310 189 -284 193 -292 179 -308 167 -314 161 -312 189 -302 481 -460 173 -320 303 -168 169 -294 331 -164 171 -314 307 -160 319 -168 169 -292 331 -162 143 -342 283 -182 159 -294 189 -302 165 -312 309 -184 311 -168 143 -318 331 -162 299 -150 319 -194 141 -314 165 -336 135 -340 133 -338 163 -312 159 -294 327 -158 173 -316 161 -312 159 -312 165 -298 177 -302 193 -312 163 -312 161 -310 163 -314 161 -296 189 -302 165 -312 187 -284 189 -284 185 -294 189 -296 165 -314 187 -284 189 -302 173 -294 195 -310 491 -458 163 -318 319 -156 153 -314 327 -160 171 -316 311 -152 327 -170 141 -316 331 -164 143 -314 309 -182 157 -294 189 -300 165 -314 309 -182 311 -168 143 -316 333 -162 299 -150 +RAW_Data: 15041 -66 15883 -66 12643 -66 12681 -66 3413 -68 2713 -68 33389 -66 1445 -66 1279 -68 1027 -66 6911 -98 25229 -66 3967 -100 3019 -100 6131 -66 955 -66 3605 -66 12411 -98 1419 -66 3593 -68 2753 -66 2457 -66 6007 -66 627 -100 1597 -66 3071 -98 22749 -66 333 -66 12829 -66 4313 -132 855 -66 44097 -64 20391 -98 29999 -66 3539 -98 557 -66 1489 -100 4081 -100 3857 -64 2895 -132 2261 -166 3089 -66 2429 -68 34467 -66 3585 -66 3087 -66 3329 -132 5287 -66 1063 -98 15259 -100 2535 -66 995 -66 13057 -100 24233 -68 531 -100 26415 -66 1761 -100 2717 -66 4071 -100 12191 -66 23367 -68 2323 -66 19809 -248 245 -1388 255 -242 275 -1358 273 -1370 277 -246 277 -1368 275 -246 275 -1362 275 -244 275 -1364 275 -244 275 -1362 275 -244 275 -1328 273 -278 273 -1358 275 -246 275 -238 263 -1384 275 -246 273 -1358 275 -244 273 -1358 275 -246 275 -1360 275 -1344 277 -246 275 -1358 275 -244 275 -234 263 -1382 277 -1344 277 -246 279 -1362 275 -246 271 -234 261 -1380 275 -246 273 -1360 275 -246 275 -1366 277 -1340 277 -248 279 -238 263 -1382 275 -1344 277 -246 279 -1364 277 -244 275 -234 263 -1382 277 -244 273 -1358 275 -1344 277 -248 279 -1368 275 -244 273 -1360 239 -280 271 -1358 275 -244 275 -1358 275 -174 269 -10298 289 -2660 267 -238 299 -1356 275 -244 275 -1356 275 -1344 277 -248 277 -1360 275 -246 275 -1328 309 -244 273 -1358 277 -244 275 -1356 275 -246 273 -1326 309 -244 275 -1356 275 -246 273 -234 263 -1380 277 -246 273 -1326 309 -244 273 -1356 277 -246 277 -1358 275 -1338 279 -248 279 -1364 275 -246 273 -234 261 -1380 277 -1344 279 -250 277 -1330 309 -244 273 -232 261 -1384 275 -246 273 -1356 275 -248 275 -1360 275 -1340 279 -248 277 -236 263 -1380 277 -1342 279 -248 279 -1366 275 -246 273 -234 263 -1380 275 -246 275 -1358 275 -1340 279 -248 281 -1336 309 -244 273 -1358 275 -246 273 -1360 275 -244 273 -1358 275 -176 267 -10306 257 -2646 299 -234 301 -1354 277 -246 275 -1356 277 -1340 279 -250 279 -1332 309 -244 275 -1358 275 -248 273 -1326 309 -246 273 -1326 309 -244 275 -1356 277 -248 275 -1328 309 -246 273 -234 261 -1382 277 -246 277 -1326 309 -244 275 -1358 277 -246 277 -1356 277 -1346 277 -250 277 -1358 277 -246 275 -234 263 -1382 279 -1346 279 -248 281 -1330 307 -246 273 -236 261 -1380 277 -246 277 -1360 277 -246 277 -1360 275 -1344 279 -248 279 -236 263 -1384 277 -1340 279 -250 281 -1338 307 -246 271 -234 261 -1384 277 -246 275 -1356 277 -1340 279 -250 283 -1336 309 -246 273 -1356 277 -246 273 -1360 277 -246 +RAW_Data: 275 -1328 309 -174 269 -10296 289 -2648 267 -238 299 -1356 277 -246 275 -1324 307 -1342 279 -250 277 -1330 309 -244 275 -1362 277 -244 275 -1356 275 -248 273 -1328 309 -244 273 -1328 309 -244 275 -1360 277 -246 275 -234 259 -1384 277 -246 275 -1360 275 -246 273 -1358 277 -248 277 -1362 275 -1344 277 -248 277 -1328 307 -246 273 -236 261 -1384 277 -1348 279 -248 279 -1360 277 -246 273 -234 263 -1388 275 -246 275 -1360 277 -248 279 -1368 277 -1344 279 -248 279 -240 265 -1386 275 -1342 279 -286 247 -1372 275 -248 275 -238 265 -1386 277 -248 275 -1360 275 -1344 277 -286 247 -1374 275 -246 275 -1362 277 -246 275 -1360 277 -248 275 -1326 307 -174 269 -10290 287 -2654 269 -236 301 -1352 275 -248 273 -1326 311 -1340 277 -248 277 -1328 309 -244 273 -1358 275 -244 275 -1326 309 -244 273 -1356 277 -244 273 -1356 275 -246 275 -1358 275 -244 275 -234 261 -1382 277 -246 273 -1358 275 -246 273 -1360 277 -246 273 -1324 309 -1340 277 -248 277 -1328 307 -246 271 -234 259 -1382 277 -1346 279 -248 277 -1330 309 -244 271 -232 259 -1382 277 -244 275 -1356 277 -248 273 -1354 277 -1342 277 -248 275 -236 261 -1380 277 -1344 277 -248 279 -1330 307 -246 273 -234 261 -1378 277 -246 273 -1356 277 -1342 277 -248 277 -1330 309 -244 273 -1322 307 -246 273 -1326 309 -244 273 -1322 309 -176 267 -10298 257 -2682 265 -236 299 -1324 309 -248 273 -1324 311 -1342 277 -246 279 -1360 277 -244 275 -1362 275 -244 275 -1358 275 -244 275 -1360 275 -246 273 -1360 275 -244 277 -1360 275 -246 273 -234 263 -1384 275 -246 273 -1358 275 -246 275 -1360 277 -246 277 -1356 277 -1342 279 -248 277 -1364 275 -244 275 -234 261 -1384 275 -1344 277 -250 279 -1366 275 -246 273 -236 263 -1384 277 -246 275 -1358 277 -246 277 -1362 277 -1342 279 -248 279 -236 265 -1382 277 -1346 277 -248 281 -1366 275 -246 275 -234 265 -1384 275 -246 273 -1358 277 -1344 279 -248 279 -1364 275 -244 275 -1324 309 -246 273 -1324 307 -246 273 -1326 309 -174 267 -118796 133 -100 131 -892 329 -166 199 -132 131 -166 99 -100 265 -264 4663 -134 4889 -100 365 -98 5921 -100 5903 -68 4877 -98 2953 -98 1645 -64 1687 -66 981 -98 10769 -66 18319 -66 4831 -66 13301 -66 893 -132 5967 -100 15949 -66 3749 -66 497 -100 625 -66 1147 -66 469 -66 1261 -66 3651 -100 265 -100 26741 -68 6873 -66 4485 -100 2667 -68 3159 -68 2857 -132 2655 -66 12903 -66 1277 -66 1711 -66 787 -100 1327 -198 727 -64 1677 -100 1187 -66 1019 -66 891 -66 4303 -100 11297 -66 3923 -254 253 -1380 247 -292 253 -1344 +RAW_Data: 277 -1346 277 -250 279 -1364 275 -244 275 -1362 275 -244 275 -1356 275 -246 273 -1358 241 -278 273 -1356 275 -246 273 -1360 275 -246 273 -234 263 -1382 275 -244 273 -1358 275 -246 273 -1360 275 -246 273 -1358 275 -1340 277 -248 277 -1362 275 -246 273 -234 261 -1380 277 -1344 277 -248 279 -1362 275 -244 273 -236 261 -1380 275 -244 275 -1360 275 -246 275 -1358 275 -1346 277 -246 275 -236 263 -1384 275 -1342 277 -248 277 -1364 277 -244 273 -234 261 -1378 277 -246 273 -1356 277 -1340 277 -248 281 -1334 307 -246 271 -1356 275 -246 273 -1358 275 -244 273 -1326 309 -174 267 -10296 257 -2650 297 -232 263 -1384 277 -244 273 -1358 275 -1340 279 -248 279 -1328 309 -244 275 -1328 307 -244 273 -1356 275 -244 275 -1358 275 -246 273 -1324 309 -244 275 -1328 307 -244 273 -234 261 -1382 275 -246 273 -1326 309 -244 273 -1358 275 -246 273 -1358 275 -1338 279 -248 279 -1330 309 -244 273 -232 261 -1380 277 -1344 279 -248 279 -1330 309 -244 271 -234 261 -1382 275 -246 273 -1358 277 -244 275 -1330 309 -1338 277 -246 277 -236 263 -1380 277 -1342 277 -248 279 -1364 275 -246 273 -232 261 -1380 275 -248 275 -1328 307 -1338 277 -248 279 -1334 309 -244 271 -1358 275 -244 275 -1324 307 -246 271 -1328 309 -174 265 -10270 291 -2640 297 -232 297 -1350 277 -248 275 -1326 309 -1340 277 -248 277 -1328 309 -244 273 -1358 275 -246 273 -1326 309 -244 273 -1354 275 -246 273 -1330 307 -244 273 -1358 275 -246 273 -234 263 -1380 275 -246 273 -1358 275 -246 273 -1360 275 -244 273 -1358 275 -1340 277 -248 279 -1364 275 -244 273 -232 261 -1380 277 -1342 279 -250 279 -1332 307 -244 271 -234 261 -1378 277 -246 273 -1358 275 -248 275 -1360 275 -1340 277 -248 275 -236 263 -1382 277 -1344 277 -246 277 -1364 275 -246 273 -234 259 -1380 275 -246 273 -1362 275 -1342 275 -248 277 -1334 309 -244 271 -1356 275 -244 275 -1326 307 -244 273 -1356 275 -176 267 -10290 289 -2644 267 -238 301 -1320 309 -246 273 -1324 309 -1340 277 -248 277 -1328 307 -246 273 -1326 307 -246 273 -1324 309 -246 273 -1322 309 -246 273 -1322 307 -246 275 -1326 309 -246 273 -234 259 -1382 275 -246 275 -1322 309 -246 273 -1326 309 -246 273 -1326 309 -1340 277 -248 275 -1326 309 -246 273 -232 261 -1380 279 -1346 277 -250 277 -1328 309 -244 271 -232 261 -1380 277 -246 273 -1358 275 -248 273 -1328 307 -1340 277 -248 277 -236 261 -1380 277 -1344 277 -248 279 -1328 309 -244 275 -232 261 -1378 277 -248 273 -1326 309 -1344 277 -248 277 -1358 277 -246 273 -1328 307 -244 271 -1324 309 -244 +RAW_Data: 273 -1324 309 -174 267 -10270 289 -2638 297 -234 297 -1352 275 -248 275 -1328 307 -1340 277 -248 275 -1330 309 -244 273 -1358 275 -244 275 -1326 309 -244 271 -1356 275 -244 275 -1326 307 -246 273 -1326 309 -244 273 -234 261 -1378 275 -248 275 -1326 309 -244 271 -1356 277 -248 273 -1328 309 -1338 277 -248 277 -1328 309 -244 271 -232 261 -1380 277 -1348 279 -248 277 -1328 307 -246 271 -234 259 -1384 275 -244 275 -1356 277 -246 275 -1326 309 -1344 275 -248 275 -236 261 -1378 277 -1342 277 -250 279 -1334 309 -244 271 -232 261 -1380 277 -246 273 -1326 307 -1344 277 -248 277 -1328 309 -246 273 -1326 309 -244 271 -1324 309 -244 273 -1324 307 -176 267 -10288 287 -2618 299 -236 299 -1354 277 -244 273 -1326 307 -1340 279 -248 275 -1328 309 -244 275 -1326 309 -246 273 -1324 307 -246 273 -1322 309 -244 273 -1322 309 -244 275 -1328 309 -246 273 -232 261 -1380 277 -246 275 -1324 309 -244 273 -1356 277 -246 275 -1324 309 -1340 279 -246 277 -1328 309 -244 273 -232 261 -1382 277 -1344 279 -250 277 -1324 309 -246 273 -234 261 -1380 277 -246 273 -1358 277 -246 273 -1328 309 -1340 277 -248 275 -236 261 -1380 275 -1344 279 -248 279 -1360 277 -244 273 -234 261 -1380 277 -246 275 -1354 277 -1344 277 -248 277 -1328 311 -246 273 -1324 307 -244 273 -1324 309 -244 273 -1320 309 -176 269 -118210 761 -168 267 -66 563 -132 99 -132 3543 -66 5345 -100 4355 -66 4617 -68 20503 -166 2379 -132 293 -98 4117 -66 1151 -98 3353 -66 3485 -66 2491 -66 6133 -66 233 -68 16307 -68 16959 -98 357 -66 5419 -134 799 -100 327 -100 791 -66 2481 -66 963 -100 3481 -98 1679 -134 2473 -100 227 -68 3087 -66 11527 -130 4305 -98 435 -66 563 -100 2887 -100 267 -66 1787 -66 9655 -66 4793 -100 2119 -66 359 -98 1313 -132 3393 -234 995 -66 2681 -98 99 -130 1379 -100 3757 -100 21695 -132 5135 -100 693 -98 4631 -100 2325 -68 4937 -66 10409 -98 897 -100 1287 -66 2565 -66 3753 -66 4055 -66 2023 -68 1961 -68 629 -66 431 -66 5039 -66 2155 -100 2673 -66 1163 -98 6539 -100 825 -66 1197 -100 3053 -66 13973 -68 15515 -100 1861 -66 1027 -66 797 -98 959 -98 787 -132 787 -64 3811 -132 1747 -66 6683 -66 1033 -68 24927 -66 1259 -100 1125 -68 663 -66 1687 -66 4357 -132 4567 -66 3969 -98 3317 -132 433 -134 6043 -66 3249 -100 431 -98 2367 -100 11265 -66 5085 -68 2355 -64 1815 -66 1395 -274 241 -1366 275 -244 275 -1362 275 -1338 277 -284 243 -1368 239 -278 275 -1362 275 -244 275 -1360 241 -278 273 -1356 275 -246 275 -1360 239 -280 275 -1360 +RAW_Data: 275 -244 275 -234 263 -1386 239 -280 273 -1356 275 -244 273 -1360 275 -244 277 -1364 275 -1336 277 -248 277 -1366 275 -244 273 -234 263 -1386 275 -1340 277 -248 279 -1364 275 -244 275 -234 263 -1384 273 -244 275 -1358 275 -244 275 -1364 275 -1342 275 -248 277 -236 265 -1384 275 -1340 277 -282 243 -1366 275 -246 273 -236 263 -1382 277 -244 275 -1358 275 -1342 277 -248 277 -1364 275 -246 275 -1360 239 -280 273 -1358 241 -278 275 -1356 275 -210 233 -10302 257 -2652 297 -232 297 -1354 277 -244 275 -1358 275 -1340 279 -248 279 -1360 275 -246 275 -1360 275 -246 273 -1360 275 -244 275 -1328 309 -242 273 -1324 309 -244 275 -1360 275 -246 273 -234 261 -1384 275 -246 273 -1358 275 -244 275 -1358 277 -248 273 -1358 275 -1340 279 -248 277 -1334 307 -242 273 -232 261 -1380 277 -1348 277 -250 277 -1364 275 -244 275 -234 261 -1380 277 -244 275 -1358 277 -246 277 -1360 277 -1342 275 -248 275 -236 263 -1380 277 -1344 277 -248 279 -1368 275 -244 275 -232 261 -1382 277 -244 275 -1356 275 -1344 277 -248 279 -1362 275 -246 275 -1360 275 -246 273 -1356 275 -246 273 -1356 275 -176 267 -10302 257 -2648 299 -234 297 -1352 277 -246 275 -1326 309 -1340 279 -248 277 -1330 309 -244 275 -1328 309 -244 273 -1324 309 -244 275 -1324 309 -246 273 -1324 307 -246 275 -1328 309 -244 273 -234 261 -1378 277 -248 275 -1328 309 -244 273 -1356 277 -248 275 -1326 309 -1344 277 -248 275 -1326 309 -246 273 -234 259 -1380 277 -1348 281 -248 279 -1328 307 -246 273 -234 259 -1382 277 -246 275 -1360 275 -248 275 -1324 309 -1340 279 -248 277 -238 261 -1382 277 -1344 277 -248 279 -1330 311 -244 273 -234 259 -1378 277 -248 275 -1326 309 -1340 279 -248 279 -1336 307 -246 271 -1324 309 -244 275 -1324 307 -246 273 -1326 309 -174 269 -10296 257 -2648 299 -234 297 -1352 277 -248 273 -1326 309 -1342 277 -248 277 -1328 309 -246 275 -1328 309 -244 273 -1326 309 -244 273 -1322 309 -244 273 -1328 307 -244 275 -1328 309 -246 273 -234 261 -1382 277 -246 275 -1326 309 -244 273 -1352 277 -248 275 -1330 309 -1340 277 -248 277 -1328 309 -244 275 -232 261 -1384 277 -1342 279 -250 279 -1328 309 -244 273 -234 263 -1380 277 -246 273 -1360 277 -246 275 -1326 309 -1340 277 -250 277 -236 263 -1382 277 -1342 277 -248 279 -1362 277 -246 273 -234 263 -1382 277 -244 275 -1356 277 -1340 279 -248 279 -1362 275 -246 275 -1328 307 -246 273 -1356 275 -246 273 -1356 275 -174 269 -10292 287 -2650 269 -236 301 -1354 275 -248 273 -1358 275 -1340 279 -248 277 -1332 307 -246 275 -1328 +RAW_Data: 309 -244 273 -1324 309 -244 273 -1356 275 -246 273 -1358 275 -244 277 -1330 309 -244 273 -234 261 -1382 277 -244 275 -1358 275 -246 273 -1356 277 -248 275 -1360 275 -1340 277 -248 277 -1360 275 -246 273 -236 261 -1382 279 -1344 279 -248 279 -1360 277 -244 273 -234 261 -1380 277 -246 275 -1360 277 -246 273 -1360 275 -1342 279 -248 275 -236 263 -1382 275 -1344 279 -248 279 -1362 277 -246 273 -234 263 -1380 277 -246 275 -1356 275 -1342 277 -248 281 -1336 307 -246 271 -1354 277 -246 275 -1328 307 -244 273 -1352 277 -176 269 -10300 257 -2650 299 -232 297 -1354 277 -246 275 -1356 277 -1342 277 -248 279 -1328 309 -244 275 -1360 275 -246 273 -1328 307 -246 273 -1356 277 -246 277 -1326 309 -244 277 -1360 277 -246 273 -234 263 -1384 277 -246 275 -1324 309 -246 275 -1358 277 -246 277 -1360 277 -1344 277 -248 277 -1326 309 -246 273 -236 261 -1382 277 -1348 279 -250 281 -1330 307 -246 273 -234 263 -1386 277 -244 275 -1356 277 -248 277 -1362 277 -1342 277 -250 277 -238 263 -1384 277 -1342 277 -250 281 -1332 309 -246 273 -234 263 -1380 277 -246 275 -1360 277 -1342 279 -248 281 -1334 307 -246 273 -1356 275 -248 275 -1328 309 -244 275 -1324 309 -176 269 -115034 163 -362 67 -894 529 -166 14663 -98 4135 -66 3681 -100 299 -68 9829 -66 3517 -64 21569 -66 3251 -66 2209 -64 23701 -66 3359 -68 1057 -66 723 -66 299 -134 765 -66 589 -98 1687 -134 2153 -66 3081 -68 10447 -66 11643 -66 2451 -66 2277 -66 2897 -66 755 -100 5539 -64 5117 -132 4867 -134 3931 -64 625 -66 1317 -98 11597 -66 2255 -66 1165 -66 1123 -66 6371 -100 699 -68 1811 -66 621 -68 2191 -64 1291 -134 3003 -66 2423 -64 1463 -66 663 -100 1127 -100 6169 -100 489 -100 6087 -100 2027 -66 1195 -66 13195 -66 557 -66 40423 -98 1919 -100 1061 -132 201 -66 2553 -132 12549 -66 1789 -100 921 -134 1067 -66 729 -66 10029 -66 3909 -100 265 -100 16017 -134 21177 -68 2461 -66 2215 -68 1197 -66 5911 -66 2645 -66 3419 -132 16275 -64 5091 -68 2123 -66 2677 -64 10305 -66 12381 -100 427 -166 25331 -66 2457 -66 11859 -248 279 -1368 275 -246 275 -1360 275 -1340 277 -246 279 -1364 239 -278 275 -1358 275 -244 275 -1362 239 -278 273 -1358 239 -280 271 -1360 241 -278 273 -1360 275 -244 275 -234 261 -1384 239 -280 273 -1356 275 -244 273 -1360 275 -244 275 -1358 275 -1344 277 -248 275 -1358 275 -244 273 -236 261 -1384 275 -1342 279 -246 279 -1360 275 -244 275 -234 263 -1384 239 -278 273 -1358 275 -244 275 -1362 275 -1342 275 -248 275 -238 263 -1382 275 -1344 275 -248 +RAW_Data: 277 -1364 275 -244 273 -234 263 -1380 275 -246 273 -1358 275 -1342 277 -246 279 -1366 275 -244 273 -1362 239 -278 239 -1386 275 -246 273 -1360 241 -208 269 -10290 257 -2686 265 -232 265 -1384 275 -246 275 -1358 275 -1344 277 -248 275 -1358 275 -246 275 -1360 277 -244 273 -1326 309 -244 271 -1354 275 -244 275 -1358 275 -246 273 -1358 275 -246 273 -234 263 -1378 275 -246 275 -1360 275 -244 273 -1356 275 -246 275 -1360 275 -1342 277 -246 277 -1360 275 -246 273 -232 261 -1382 277 -1342 279 -248 279 -1360 275 -244 275 -232 261 -1380 277 -244 275 -1356 277 -246 277 -1360 275 -1342 277 -246 275 -236 263 -1384 275 -1342 277 -248 277 -1362 275 -246 273 -234 261 -1378 277 -246 275 -1328 307 -1340 277 -246 279 -1366 275 -244 273 -1326 307 -244 273 -1324 309 -244 273 -1356 275 -174 267 -10304 255 -2648 297 -230 263 -1382 277 -244 275 -1330 307 -1338 277 -248 277 -1330 309 -244 273 -1356 275 -246 273 -1362 275 -244 273 -1356 275 -244 273 -1326 307 -244 273 -1360 273 -246 273 -236 261 -1380 275 -244 275 -1328 307 -244 273 -1358 275 -244 275 -1360 275 -1342 277 -246 277 -1364 275 -244 271 -232 261 -1384 277 -1340 279 -248 279 -1360 275 -246 273 -234 261 -1380 275 -244 275 -1360 277 -244 275 -1356 275 -1342 279 -246 277 -236 263 -1382 275 -1340 277 -248 279 -1366 275 -246 271 -234 261 -1382 277 -244 275 -1354 275 -1342 277 -248 277 -1364 273 -246 273 -1362 275 -244 271 -1360 275 -244 273 -1358 275 -174 267 -10272 289 -2646 265 -262 261 -1382 277 -244 275 -1356 275 -1342 277 -248 277 -1364 275 -244 275 -1360 275 -244 273 -1358 275 -244 273 -1358 275 -244 273 -1326 307 -244 275 -1358 275 -246 273 -234 261 -1382 275 -246 273 -1358 275 -244 273 -1358 275 -246 275 -1360 275 -1338 277 -248 277 -1362 277 -244 271 -234 261 -1380 277 -1344 279 -248 277 -1332 273 -278 271 -234 261 -1382 275 -244 275 -1356 277 -246 275 -1360 277 -1340 277 -246 277 -234 263 -1384 275 -1342 277 -248 277 -1366 275 -244 273 -234 261 -1380 275 -246 273 -1360 275 -1340 277 -246 279 -1334 307 -244 273 -1356 275 -246 273 -1360 275 -244 271 -1354 277 -174 269 -10300 257 -2648 297 -230 263 -1384 277 -244 273 -1356 277 -1342 277 -248 277 -1362 275 -244 275 -1330 307 -244 273 -1324 309 -244 273 -1324 307 -246 273 -1326 307 -244 273 -1358 275 -246 273 -234 261 -1380 277 -246 273 -1358 275 -244 275 -1354 277 -248 275 -1360 275 -1338 279 -246 277 -1360 275 -244 273 -234 261 -1378 279 -1344 279 -248 279 -1330 309 -244 271 -232 261 -1380 277 -246 273 -1360 +RAW_Data: 277 -244 275 -1360 275 -1340 277 -246 277 -236 261 -1380 275 -1346 277 -248 277 -1362 275 -246 273 -234 263 -1380 275 -244 275 -1358 275 -1340 277 -248 279 -1334 309 -244 273 -1324 307 -246 273 -1356 275 -244 273 -1356 275 -174 269 -10302 257 -2644 297 -232 263 -1384 277 -246 275 -1354 275 -1344 277 -248 275 -1360 275 -246 275 -1358 275 -246 273 -1326 307 -246 273 -1324 307 -244 273 -1328 307 -244 273 -1358 275 -244 273 -236 261 -1380 275 -246 273 -1358 275 -244 273 -1358 275 -246 273 -1360 275 -1344 275 -248 275 -1360 275 -244 273 -234 261 -1378 277 -1344 279 -248 277 -1362 275 -246 273 -234 261 -1378 275 -244 275 -1360 275 -246 275 -1358 275 -1344 277 -246 277 -234 263 -1380 275 -1338 279 -246 281 -1368 275 -244 271 -234 261 -1386 275 -244 271 -1358 275 -1342 277 -246 279 -1362 275 -244 275 -1326 273 -278 273 -1358 239 -278 273 -1358 275 -174 267 -127478 195 -964 2317 -66 763 -98 1455 -100 16109 -66 5683 -98 11469 -66 34413 -66 5443 -66 11613 -66 2737 -66 12191 -66 2951 -68 1851 -68 1895 -68 2643 +RAW_Data: 29262 361 -68 2635 -66 24113 -66 1131 -100 4157 -66 26253 -130 621 -18438 99 -298 231 -66 197 -496 753 -230 7503 -16526 65 -396 65 -296 99 -196 293 -64 429 -132 397 -66 329 -66 37701 -66 13475 -100 54967 -64 18209 -18340 97 -462 197 -98 587 -232 97 -100 259 -98 197 -262 297 -64 557 -100 599 -100 333 -234 42493 -13212 6449 -206 173 -214 217 -176 195 -218 181 -218 181 -182 217 -182 217 -176 187 -214 215 -180 217 -182 217 -182 217 -178 185 -424 1177 -388 387 -240 381 -214 181 -398 211 -380 419 -176 217 -394 203 -394 205 -380 189 -402 421 -168 219 -398 393 -190 191 -398 205 -406 185 -402 381 -212 215 -362 241 -378 421 -176 377 -218 197 -378 427 -210 393 -172 429 -172 397 -212 217 -362 389 -228 197 -372 417 -204 395 -210 181 -398 391 -192 201 -216888 761 -200 299 -166 695 -132 15435 -66 5611 -66 21049 -66 4947 -66 2355 -66 1921 -100 2223 -100 2107 -100 397 -98 3643 -66 5301 -98 14205 -66 37371 -246 175 -216 179 -216 177 -224 149 -246 159 -228 181 -212 201 -204 159 -244 151 -254 169 -214 181 -210 197 -182 181 -454 1141 -444 357 -228 361 -246 177 -396 209 -412 367 -188 187 -434 201 -394 185 -406 193 -402 377 -238 181 -386 381 -234 153 -424 205 -412 157 -412 383 -240 181 -398 203 -392 385 -236 371 -212 179 -400 383 -240 359 -210 375 -220 381 -246 175 -394 383 -240 181 -398 363 -222 379 -246 175 -394 383 -204 217 -182856 99 -66 99 -300 133 -402 65 -198 99 -328 65 -100 491 -164 593 -100 3547 -64 361 -66 789 -68 2521 -66 22883 -66 2659 -98 3309 -130 3789 -100 9689 -17178 99 -1388 65 -266 197 -100 131 -134 99 -232 627 -130 233 -66 1949 -100 14567 -198 165 -256 181 -208 159 -214 183 -220 163 -244 149 -246 159 -236 181 -254 141 -226 151 -246 157 -228 181 -212 201 -400 1163 -428 379 -230 355 -244 177 -396 207 -412 367 -222 157 -418 189 -410 207 -412 171 -430 357 -226 165 -404 413 -204 181 -428 173 -428 169 -426 353 -236 173 -414 173 -408 381 -244 337 -222 201 -408 397 -208 393 -204 395 -208 359 -246 177 -394 387 -200 205 -380 415 -202 395 -208 181 -432 357 -226 169 -195084 65 -300 763 -66 297 -364 593 -68 2883 -66 1357 -68 363 -98 3841 -66 3119 -66 5153 -66 4023 -268 143 -246 133 -290 141 -250 139 -254 141 -226 181 -248 137 -254 143 -252 139 -252 143 -230 181 -250 139 -254 145 -436 1135 -448 349 -240 347 -254 157 -434 167 -426 377 -226 157 -434 167 -426 155 -440 163 -434 375 -206 215 -380 381 -234 153 +RAW_Data: -424 205 -412 159 -412 381 -240 181 -398 203 -392 387 -236 369 -212 179 -400 383 -240 359 -244 339 -222 381 -246 175 -394 383 -240 181 -398 363 -222 381 -244 175 -392 383 -240 181 -184002 99 -360 63 -330 65 -132 129 -232 97 -198 295 -328 6031 -66 831 -132 3417 -66 2187 -64 2183 -100 6535 -66 1127 -66 2569 -66 2031 -66 2271 -66 2183 -66 3815 -66 3803 -66 493 -66 1909 -66 1627 -98 4805 -17512 67 -2164 131 -498 265 -430 163 -98 97 -64 99 -230 99 -100 229 -230 165 -196 63 -132 99 -66 927 -66 14955 -66 19621 -68 2627 -66 14305 -68 23247 -66 2891 -66 3941 -66 3021 -212 173 -242 181 -218 181 -214 181 -208 157 -250 141 -248 181 -218 179 -214 179 -210 159 -250 179 -214 181 -218 181 -404 1153 -404 389 -244 375 -192 181 -436 161 -414 383 -240 181 -398 205 -392 201 -394 205 -394 365 -246 177 -396 383 -204 217 -398 171 -426 167 -428 353 -242 173 -420 173 -408 373 -220 403 -208 175 -422 381 -194 399 -228 357 -246 355 -210 215 -400 387 -208 181 -398 391 -226 353 -246 177 -398 383 -204 217 -185098 163 -166 525 -98 293 -100 63 -66 229 -66 1183 -66 1507 -66 3089 -98 30187 -66 2847 -19112 133 -364 131 -394 97 -166 295 -66 229 -164 227 -66 263 -130 623 -98 2071 -66 493 -66 787 -98 691 -64 10249 -132 3879 -66 1949 -66 3453 -198 23157 -66 2845 -100 1193 -66 1587 -100 3797 -98 3187 -100 3319 -66 22119 -98 5513 -226 155 -244 153 -256 131 -248 151 -246 159 -262 121 -274 133 -272 127 -244 153 -254 167 -248 145 -244 133 -252 177 -398 1169 -418 381 -238 359 -242 141 -430 169 -426 357 -274 139 -422 171 -442 173 -428 167 -426 353 -236 171 -416 379 -226 149 -436 161 -438 173 -406 381 -234 153 -424 205 -380 389 -244 359 -206 215 -384 381 -246 335 -224 383 -246 355 -244 179 -404 385 -206 181 -432 359 -226 355 -246 175 -398 383 -240 181 -179760 97 -168 727 -66 97 -332 1389 -66 2793 -66 4955 -100 12453 -100 2425 -66 21965 -66 3809 -68 1683 -66 3095 -66 2153 -64 999 -208 173 -220 181 -214 191 -196 181 -212 183 -220 191 -212 181 -214 191 -198 181 -212 181 -222 191 -212 181 -214 191 -416 1167 -424 369 -220 373 -210 209 -390 207 -376 403 -190 187 -418 189 -408 209 -412 173 -428 357 -226 169 -404 399 -208 179 -412 209 -396 169 -428 355 -230 201 -378 205 -406 381 -244 339 -222 193 -400 413 -204 393 -208 347 -220 401 -210 175 -422 383 -202 217 -398 365 -222 377 -246 175 -390 385 -204 217 -179890 165 -1552 131 -164 65 +RAW_Data: -1448 361 -17056 131 -134 233 -1462 131 -166 953 -100 261 -164 5077 -272 137 -268 143 -252 141 -248 143 -246 159 -252 141 -244 143 -290 107 -276 145 -244 131 -250 179 -248 143 -252 141 -414 1165 -424 373 -236 359 -242 145 -434 169 -428 355 -230 169 -442 173 -434 157 -406 193 -402 379 -238 181 -422 335 -252 157 -434 167 -428 185 -406 381 -208 211 -390 207 -410 381 -200 373 -236 171 -414 383 -202 393 -210 379 -220 373 -208 211 -390 383 -204 217 -398 365 -220 379 -244 175 -394 381 -240 181 -161030 97 -166 167 -930 593 -2670 1091 -132 229 -98 461 -164 1649 -66 6311 -100 44723 -16832 67 -2656 131 -132 99 -132 263 -100 399 -68 893 -18950 99 -164 165 -198 525 -998 335 -66 565 -66 1057 -17880 97 -360 195 -262 131 -332 625 -98 197 -230 455 -98 9343 -16498 67 -368 131 -598 65 -1066 333 -300 789 -130 757 -66 87207 -16554 97 -3520 97 -786 591 -64 461 -98 21495 -66 24811 -18448 131 -296 491 -134 163 -760 1091 -230 893 -66 927 -68 4581 -68 32965 -64 45217 -17292 131 -1684 231 -132 327 -64 163 -330 263 -230 25751 diff --git a/lib/subghz/protocols/magellen.c b/lib/subghz/protocols/magellen.c new file mode 100644 index 00000000000..859f8035195 --- /dev/null +++ b/lib/subghz/protocols/magellen.c @@ -0,0 +1,444 @@ +#include "magellen.h" + +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#define TAG "SubGhzProtocolMagellen" + +static const SubGhzBlockConst subghz_protocol_magellen_const = { + .te_short = 200, + .te_long = 400, + .te_delta = 100, + .min_count_bit_for_found = 32, +}; + +struct SubGhzProtocolDecoderMagellen { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; + uint16_t header_count; +}; + +struct SubGhzProtocolEncoderMagellen { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + MagellenDecoderStepReset = 0, + MagellenDecoderStepCheckPreambula, + MagellenDecoderStepFoundPreambula, + MagellenDecoderStepSaveDuration, + MagellenDecoderStepCheckDuration, +} MagellenDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_magellen_decoder = { + .alloc = subghz_protocol_decoder_magellen_alloc, + .free = subghz_protocol_decoder_magellen_free, + + .feed = subghz_protocol_decoder_magellen_feed, + .reset = subghz_protocol_decoder_magellen_reset, + + .get_hash_data = subghz_protocol_decoder_magellen_get_hash_data, + .serialize = subghz_protocol_decoder_magellen_serialize, + .deserialize = subghz_protocol_decoder_magellen_deserialize, + .get_string = subghz_protocol_decoder_magellen_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_magellen_encoder = { + .alloc = subghz_protocol_encoder_magellen_alloc, + .free = subghz_protocol_encoder_magellen_free, + + .deserialize = subghz_protocol_encoder_magellen_deserialize, + .stop = subghz_protocol_encoder_magellen_stop, + .yield = subghz_protocol_encoder_magellen_yield, +}; + +const SubGhzProtocol subghz_protocol_magellen = { + .name = SUBGHZ_PROTOCOL_MAGELLEN_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_magellen_decoder, + .encoder = &subghz_protocol_magellen_encoder, +}; + +void* subghz_protocol_encoder_magellen_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderMagellen* instance = malloc(sizeof(SubGhzProtocolEncoderMagellen)); + + instance->base.protocol = &subghz_protocol_magellen; + 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_running = false; + return instance; +} + +void subghz_protocol_encoder_magellen_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderMagellen* instance = context; + free(instance->encoder.upload); + free(instance); +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderMagellen instance + * @return true On success + */ +static bool subghz_protocol_encoder_magellen_get_upload(SubGhzProtocolEncoderMagellen* instance) { + furi_assert(instance); + + size_t index = 0; + + //Send header + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_magellen_const.te_short * 4); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_magellen_const.te_short); + for(uint8_t i = 0; i < 12; i++) { + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_magellen_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_magellen_const.te_short); + } + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_magellen_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_magellen_const.te_long); + + //Send start bit + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_magellen_const.te_long * 3); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_magellen_const.te_long); + + //Send key data + for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) { + if(bit_read(instance->generic.data, i - 1)) { + //send bit 1 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_magellen_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_magellen_const.te_long); + } else { + //send bit 0 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_magellen_const.te_long); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_magellen_const.te_short); + } + } + + //Send stop bit + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_magellen_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_magellen_const.te_long * 100); + + instance->encoder.size_upload = index; + return true; +} + +bool subghz_protocol_encoder_magellen_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderMagellen* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_magellen_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + subghz_protocol_encoder_magellen_get_upload(instance); + instance->encoder.is_running = true; + + res = true; + } while(false); + + return res; +} + +void subghz_protocol_encoder_magellen_stop(void* context) { + SubGhzProtocolEncoderMagellen* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_magellen_yield(void* context) { + SubGhzProtocolEncoderMagellen* instance = context; + + if(instance->encoder.repeat == 0 || !instance->encoder.is_running) { + instance->encoder.is_running = 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_magellen_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderMagellen* instance = malloc(sizeof(SubGhzProtocolDecoderMagellen)); + instance->base.protocol = &subghz_protocol_magellen; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_magellen_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderMagellen* instance = context; + free(instance); +} + +void subghz_protocol_decoder_magellen_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderMagellen* instance = context; + instance->decoder.parser_step = MagellenDecoderStepReset; +} + +uint8_t subghz_protocol_magellen_crc8(uint8_t* data, size_t len) { + uint8_t crc = 0x00; + size_t i, j; + for(i = 0; i < len; i++) { + crc ^= data[i]; + for(j = 0; j < 8; j++) { + if((crc & 0x80) != 0) + crc = (uint8_t)((crc << 1) ^ 0x31); + else + crc <<= 1; + } + } + return crc; +} + +static bool subghz_protocol_magellen_check_crc(SubGhzProtocolDecoderMagellen* instance) { + uint8_t data[3] = { + instance->decoder.decode_data >> 24, + instance->decoder.decode_data >> 16, + instance->decoder.decode_data >> 8}; + return (instance->decoder.decode_data & 0xFF) == + subghz_protocol_magellen_crc8(data, sizeof(data)); +} + +void subghz_protocol_decoder_magellen_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderMagellen* instance = context; + + switch(instance->decoder.parser_step) { + case MagellenDecoderStepReset: + if((level) && (DURATION_DIFF(duration, subghz_protocol_magellen_const.te_short) < + subghz_protocol_magellen_const.te_delta)) { + instance->decoder.parser_step = MagellenDecoderStepCheckPreambula; + instance->decoder.te_last = duration; + instance->header_count = 0; + } + break; + + case MagellenDecoderStepCheckPreambula: + if(level) { + instance->decoder.te_last = duration; + } else { + if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_magellen_const.te_short) < + subghz_protocol_magellen_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_magellen_const.te_short) < + subghz_protocol_magellen_const.te_delta)) { + // Found header + instance->header_count++; + } else if( + (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_magellen_const.te_short) < + subghz_protocol_magellen_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_magellen_const.te_long) < + subghz_protocol_magellen_const.te_delta * 2) && + (instance->header_count > 10)) { + instance->decoder.parser_step = MagellenDecoderStepFoundPreambula; + } else { + instance->decoder.parser_step = MagellenDecoderStepReset; + } + } + break; + + case MagellenDecoderStepFoundPreambula: + if(level) { + instance->decoder.te_last = duration; + } else { + if((DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_magellen_const.te_short * 6) < + subghz_protocol_magellen_const.te_delta * 3) && + (DURATION_DIFF(duration, subghz_protocol_magellen_const.te_long) < + subghz_protocol_magellen_const.te_delta * 2)) { + instance->decoder.parser_step = MagellenDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } else { + instance->decoder.parser_step = MagellenDecoderStepReset; + } + } + break; + + case MagellenDecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = MagellenDecoderStepCheckDuration; + } else { + instance->decoder.parser_step = MagellenDecoderStepReset; + } + break; + + case MagellenDecoderStepCheckDuration: + if(!level) { + if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_magellen_const.te_short) < + subghz_protocol_magellen_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_magellen_const.te_long) < + subghz_protocol_magellen_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = MagellenDecoderStepSaveDuration; + } else if( + (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_magellen_const.te_long) < + subghz_protocol_magellen_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_magellen_const.te_short) < + subghz_protocol_magellen_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = MagellenDecoderStepSaveDuration; + } else if(duration >= (subghz_protocol_magellen_const.te_long * 3)) { + //Found stop bit + if((instance->decoder.decode_count_bit == + subghz_protocol_magellen_const.min_count_bit_for_found) && + subghz_protocol_magellen_check_crc(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.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->decoder.parser_step = MagellenDecoderStepReset; + } else { + instance->decoder.parser_step = MagellenDecoderStepReset; + } + } else { + instance->decoder.parser_step = MagellenDecoderStepReset; + } + break; + } +} + +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + */ +static void subghz_protocol_magellen_check_remote_controller(SubGhzBlockGeneric* instance) { + /* +* package 32b data 24b CRC8 +* 0x037AE4828 => 001101111010111001001000 00101000 +* +* 0x037AE48 (flipped in reverse bit sequence) => 0x1275EC +* +* 0x1275EC => 0x12-event codes, 0x75EC-serial (dec 117236) +* +* event codes +* bit_0: 1-alarm, 0-close +* bit_1: 1-Tamper On (alarm), 0-Tamper Off (ok) +* bit_2: ? +* bit_3: 1-power on +* bit_4: model type - door alarm +* bit_5: model type - motion sensor +* bit_6: ? +* bit_7: ? +* +*/ + uint64_t data_rev = subghz_protocol_blocks_reverse_key(instance->data >> 8, 24); + instance->serial = data_rev & 0xFFFF; + instance->btn = (data_rev >> 16) & 0xFF; +} + +static void subghz_protocol_magellen_get_event_serialize(uint8_t event, string_t output) { + string_cat_printf( + output, + "%s%s%s%s%s%s%s%s", + (event & 0x1 ? " Alarm" : "Ok"), + ((event >> 1) & 0x1 ? ", Tamper On (Alarm)" : ""), + ((event >> 2) & 0x1 ? ", ?" : ""), + ((event >> 3) & 0x1 ? ", Power On" : ""), + ((event >> 4) & 0x1 ? ", MT:Door_Alarm" : ""), + ((event >> 5) & 0x1 ? ", MT:Motion_Sensor" : ""), + ((event >> 6) & 0x1 ? ", ?" : ""), + ((event >> 7) & 0x1 ? ", ?" : "")); +} + +uint8_t subghz_protocol_decoder_magellen_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderMagellen* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_magellen_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzPresetDefinition* preset) { + furi_assert(context); + SubGhzProtocolDecoderMagellen* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +bool subghz_protocol_decoder_magellen_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderMagellen* instance = context; + bool ret = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_magellen_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + ret = true; + } while(false); + return ret; +} + +void subghz_protocol_decoder_magellen_get_string(void* context, string_t output) { + furi_assert(context); + SubGhzProtocolDecoderMagellen* instance = context; + subghz_protocol_magellen_check_remote_controller(&instance->generic); + string_cat_printf( + output, + "%s %dbit\r\n" + "Key:0x%08lX\r\n" + "Sn:%03d%03d, Event:0x%02X\r\n" + "Stat:", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data & 0xFFFFFFFF), + (instance->generic.serial >> 8) & 0xFF, + instance->generic.serial & 0xFF, + instance->generic.btn); + + subghz_protocol_magellen_get_event_serialize(instance->generic.btn, output); +} diff --git a/lib/subghz/protocols/magellen.h b/lib/subghz/protocols/magellen.h new file mode 100644 index 00000000000..224f79011be --- /dev/null +++ b/lib/subghz/protocols/magellen.h @@ -0,0 +1,107 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_MAGELLEN_NAME "Magellen" + +typedef struct SubGhzProtocolDecoderMagellen SubGhzProtocolDecoderMagellen; +typedef struct SubGhzProtocolEncoderMagellen SubGhzProtocolEncoderMagellen; + +extern const SubGhzProtocolDecoder subghz_protocol_magellen_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_magellen_encoder; +extern const SubGhzProtocol subghz_protocol_magellen; + +/** + * Allocate SubGhzProtocolEncoderMagellen. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderMagellen* pointer to a SubGhzProtocolEncoderMagellen instance + */ +void* subghz_protocol_encoder_magellen_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderMagellen. + * @param context Pointer to a SubGhzProtocolEncoderMagellen instance + */ +void subghz_protocol_encoder_magellen_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderMagellen instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_encoder_magellen_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderMagellen instance + */ +void subghz_protocol_encoder_magellen_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderMagellen instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_magellen_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderMagellen. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderMagellen* pointer to a SubGhzProtocolDecoderMagellen instance + */ +void* subghz_protocol_decoder_magellen_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderMagellen. + * @param context Pointer to a SubGhzProtocolDecoderMagellen instance + */ +void subghz_protocol_decoder_magellen_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderMagellen. + * @param context Pointer to a SubGhzProtocolDecoderMagellen instance + */ +void subghz_protocol_decoder_magellen_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderMagellen instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_magellen_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderMagellen instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_magellen_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderMagellen. + * @param context Pointer to a SubGhzProtocolDecoderMagellen instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzPresetDefinition + * @return true On success + */ +bool subghz_protocol_decoder_magellen_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzPresetDefinition* preset); + +/** + * Deserialize data SubGhzProtocolDecoderMagellen. + * @param context Pointer to a SubGhzProtocolDecoderMagellen instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_decoder_magellen_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderMagellen instance + * @param output Resulting text + */ +void subghz_protocol_decoder_magellen_get_string(void* context, string_t output); diff --git a/lib/subghz/protocols/registry.c b/lib/subghz/protocols/registry.c index 19b03cebcfd..b72278788cb 100644 --- a/lib/subghz/protocols/registry.c +++ b/lib/subghz/protocols/registry.c @@ -11,7 +11,7 @@ const SubGhzProtocol* subghz_protocol_registry[] = { &subghz_protocol_secplus_v1, &subghz_protocol_megacode, &subghz_protocol_holtek, &subghz_protocol_chamb_code, &subghz_protocol_power_smart, &subghz_protocol_marantec, &subghz_protocol_bett, &subghz_protocol_doitrand, &subghz_protocol_phoenix_v2, - &subghz_protocol_honeywell_wdb, + &subghz_protocol_honeywell_wdb, &subghz_protocol_magellen, }; diff --git a/lib/subghz/protocols/registry.h b/lib/subghz/protocols/registry.h index d7569513298..36a560765ac 100644 --- a/lib/subghz/protocols/registry.h +++ b/lib/subghz/protocols/registry.h @@ -33,6 +33,7 @@ #include "doitrand.h" #include "phoenix_v2.h" #include "honeywell_wdb.h" +#include "magellen.h" /** * Registration by name SubGhzProtocol. From a7a9c38036633ee25de70903c6ceacabf4f3307c Mon Sep 17 00:00:00 2001 From: Max Andreev Date: Tue, 23 Aug 2022 14:29:26 +0300 Subject: [PATCH 011/824] Amap and PVS Studio reports in CI/CD (#1526) --- .github/CODEOWNERS | 4 - .github/actions/docker/action.yml | 11 -- .github/workflows/amap_analyse.yml | 120 ++++++++++++++++++++++ .github/workflows/build.yml | 4 + .github/workflows/check_submodules.yml | 1 + .github/workflows/lint_c.yml | 1 + .github/workflows/lint_python.yml | 1 + .github/workflows/pvs_studio.yml | 107 +++++++++++++++++++ .pvsoptions | 2 +- ReadMe.md | 24 ----- applications/subghz/subghz_setting.c | 18 ++-- assets/ReadMe.md | 6 -- docker-compose.yml | 12 --- docker/Dockerfile | 41 -------- docker/entrypoint.sh | 9 -- scripts/amap_mariadb_insert.py | 136 +++++++++++++++++++++++++ scripts/toolchain/fbtenv.cmd | 2 +- scripts/toolchain/fbtenv.sh | 8 +- 18 files changed, 384 insertions(+), 123 deletions(-) delete mode 100644 .github/actions/docker/action.yml create mode 100644 .github/workflows/amap_analyse.yml create mode 100644 .github/workflows/pvs_studio.yml delete mode 100644 docker-compose.yml delete mode 100644 docker/Dockerfile delete mode 100755 docker/entrypoint.sh create mode 100755 scripts/amap_mariadb_insert.py diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index ea165de2f72..6dac0496ab5 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -44,10 +44,6 @@ # Debug tools and plugins /debug/ @skotopes @DrZlo13 @hedger -# Docker -/docker/ @skotopes @DrZlo13 @hedger @drunkbatya -/docker-compose.yml @skotopes @DrZlo13 @hedger @drunkbatya - # Documentation /documentation/ @skotopes @DrZlo13 @hedger @drunkbatya diff --git a/.github/actions/docker/action.yml b/.github/actions/docker/action.yml deleted file mode 100644 index 3608f96e67b..00000000000 --- a/.github/actions/docker/action.yml +++ /dev/null @@ -1,11 +0,0 @@ -name: 'Run in docker' -inputs: - run: # id of input - description: 'A command to run' - required: true - default: '' -runs: - using: 'docker' - image: '../../../docker/Dockerfile' - args: - - ${{ inputs.run }} diff --git a/.github/workflows/amap_analyse.yml b/.github/workflows/amap_analyse.yml new file mode 100644 index 00000000000..3bfcba2cf55 --- /dev/null +++ b/.github/workflows/amap_analyse.yml @@ -0,0 +1,120 @@ +name: 'Analyze .map file with Amap' + +on: + push: + branches: + - dev + - "release*" + tags: + - '*' + pull_request: + +env: + TARGETS: f7 + +jobs: + amap_analyse: + runs-on: [self-hosted,FlipperZeroMacShell] + steps: + - name: 'Wait Build workflow' + uses: fountainhead/action-wait-for-check@v1.0.0 + id: wait-for-build + with: + token: ${{ secrets.GITHUB_TOKEN }} + checkName: 'main' + ref: ${{ github.event.pull_request.head.sha || github.sha }} + intervalSeconds: 20 + + - name: 'Check Build workflow status' + if: steps.wait-for-build.outputs.conclusion == 'failure' + run: | + exit 1 + + - name: 'Decontaminate previous build leftovers' + run: | + if [ -d .git ]; then + git submodule status \ + || git checkout `git rev-list --max-parents=0 HEAD | tail -n 1` + fi + + - name: 'Checkout code' + uses: actions/checkout@v2 + with: + fetch-depth: 0 + ref: ${{ github.event.pull_request.head.sha }} + + - name: 'Generate prefixes by commit' + id: names + run: | + REF="${{github.ref}}" + COMMIT_HASH="$(git rev-parse HEAD)" + SHA="$(git rev-parse --short HEAD)" + COMMIT_MSG="${{github.event.head_commit.message}}" + if [[ ${{ github.event_name }} == 'pull_request' ]]; then + REF="${{github.head_ref}}" + COMMIT_HASH="$(git log -1 --pretty=oneline | awk '{print $1}')" + SHA="$(cut -c -8 <<< "$COMMIT_HASH")" + COMMIT_MSG="$(git log -1 --pretty=format:"%s")" + PULL_ID="${{github.event.pull_request.number}}" + PULL_NAME="${{github.event.pull_request.title}}" + fi + BRANCH_NAME=${REF#refs/*/} + SUFFIX=${BRANCH_NAME//\//_}-$(date +'%d%m%Y')-${SHA} + if [[ "${{ github.ref }}" == "refs/tags/"* ]]; then + SUFFIX=${BRANCH_NAME//\//_} + fi + echo "::set-output name=commit-hash::${COMMIT_HASH}" + echo "::set-output name=commit-msg::${COMMIT_MSG}" + echo "::set-output name=pull-id::${PULL_ID}" + echo "::set-output name=pull-name::${PULL_NAME}" + echo "::set-output name=branch-name::${BRANCH_NAME}" + echo "::set-output name=suffix::${SUFFIX}" + + - name: 'Make artifacts directory' + run: | + rm -rf artifacts + mkdir artifacts + + - name: 'Download build artifacts' + if: ${{ !github.event.pull_request.head.repo.fork }} + run: | + echo "${{ secrets.RSYNC_DEPLOY_KEY }}" > deploy_key; + chmod 600 ./deploy_key; + rsync -avzP \ + -e 'ssh -p ${{ secrets.RSYNC_DEPLOY_PORT }} -i ./deploy_key' \ + ${{ secrets.RSYNC_DEPLOY_USER }}@${{ secrets.RSYNC_DEPLOY_HOST }}:"${{ secrets.RSYNC_DEPLOY_BASE_PATH }}${{steps.names.outputs.branch-name}}/" artifacts/; + rm ./deploy_key; + + - name: 'Make .map file analyse' + run: | + cd artifacts/ + /Applications/amap/Contents/MacOS/amap -f flipper-z-f7-firmware-${{steps.names.outputs.suffix}}.elf.map + + - name: 'Upload report to DB' + run: | + FBT_TOOLCHAIN_PATH=/opt source scripts/toolchain/fbtenv.sh + get_size() + { + SECTION="$1"; + arm-none-eabi-size \ + -A artifacts/flipper-z-f7-firmware-${{steps.names.outputs.suffix}}.elf \ + | grep "^$SECTION" | awk '{print $2}' + } + export COMMIT_HASH="${{steps.names.outputs.commit-hash}}" + export COMMIT_MSG="${{steps.names.outputs.commit-msg}}" + export BRANCH_NAME="${{steps.names.outputs.branch-name}}" + export PULL_ID="${{steps.names.outputs.pull-id}}" + export PULL_NAME="${{steps.names.outputs.pull-name}}" + export BSS_SIZE="$(get_size ".bss")" + export TEXT_SIZE="$(get_size ".text")" + export RODATA_SIZE="$(get_size ".rodata")" + export DATA_SIZE="$(get_size ".data")" + export FREE_FLASH_SIZE="$(get_size ".free_flash")" + python3 -m pip install mariadb + python3 scripts/amap_mariadb_insert.py \ + ${{ secrets.AMAP_MARIADB_USER }} \ + ${{ secrets.AMAP_MARIADB_PASSWORD }} \ + ${{ secrets.AMAP_MARIADB_HOST }} \ + ${{ secrets.AMAP_MARIADB_PORT }} \ + ${{ secrets.AMAP_MARIADB_DATABASE }} \ + artifacts/flipper-z-f7-firmware-${{steps.names.outputs.suffix}}.elf.map.all diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a1ae875a8ba..b677a293f6f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -108,6 +108,10 @@ jobs: FBT_TOOLCHAIN_PATH=/opt ./fbt copro_dist tar czpf artifacts/flipper-z-any-core2_firmware-${{steps.names.outputs.suffix}}.tgz -C assets core2_firmware + - name: 'Copy .map file' + run: | + cp build/f7-firmware-D/firmware.elf.map artifacts/flipper-z-f7-firmware-${{steps.names.outputs.suffix}}.elf.map + - name: 'Upload artifacts to update server' if: ${{ !github.event.pull_request.head.repo.fork }} run: | diff --git a/.github/workflows/check_submodules.yml b/.github/workflows/check_submodules.yml index f9699be87ae..e021c969a8e 100644 --- a/.github/workflows/check_submodules.yml +++ b/.github/workflows/check_submodules.yml @@ -25,6 +25,7 @@ jobs: uses: actions/checkout@v2 with: fetch-depth: 0 + ref: ${{ github.event.pull_request.head.sha }} - name: 'Check protobuf branch' run: | diff --git a/.github/workflows/lint_c.yml b/.github/workflows/lint_c.yml index aaff396ec6f..64d14b713b4 100644 --- a/.github/workflows/lint_c.yml +++ b/.github/workflows/lint_c.yml @@ -28,6 +28,7 @@ jobs: uses: actions/checkout@v2 with: fetch-depth: 0 + ref: ${{ github.event.pull_request.head.sha }} - name: 'Check code formatting' id: syntax_check diff --git a/.github/workflows/lint_python.yml b/.github/workflows/lint_python.yml index c059f4348ac..5051c569140 100644 --- a/.github/workflows/lint_python.yml +++ b/.github/workflows/lint_python.yml @@ -25,6 +25,7 @@ jobs: uses: actions/checkout@v2 with: fetch-depth: 0 + ref: ${{ github.event.pull_request.head.sha }} - name: 'Check code formatting' run: SET_GH_OUTPUT=1 FBT_TOOLCHAIN_PATH=/opt ./fbt lint_py diff --git a/.github/workflows/pvs_studio.yml b/.github/workflows/pvs_studio.yml new file mode 100644 index 00000000000..c238b1c6f9a --- /dev/null +++ b/.github/workflows/pvs_studio.yml @@ -0,0 +1,107 @@ +name: 'Static C/C++ analysis with PVS-Studio' + +on: + push: + branches: + - dev + - "release*" + tags: + - '*' + pull_request: + +env: + TARGETS: f7 + DEFAULT_TARGET: f7 + +jobs: + analyse_c_cpp: + runs-on: [self-hosted, FlipperZeroShell] + steps: + - name: 'Decontaminate previous build leftovers' + run: | + if [ -d .git ] + then + git submodule status \ + || git checkout `git rev-list --max-parents=0 HEAD | tail -n 1` + fi + + - name: 'Checkout code' + uses: actions/checkout@v2 + with: + fetch-depth: 0 + ref: ${{ github.event.pull_request.head.sha }} + + - name: 'Generate suffix and folder name' + id: names + run: | + REF=${{ github.ref }} + if [[ ${{ github.event_name }} == 'pull_request' ]]; then + REF=${{ github.head_ref }} + fi + BRANCH_OR_TAG=${REF#refs/*/} + SHA=$(git rev-parse --short HEAD) + + if [[ "${{ github.ref }}" == "refs/tags/"* ]]; then + SUFFIX=${BRANCH_OR_TAG//\//_} + else + SUFFIX=${BRANCH_OR_TAG//\//_}-$(date +'%d%m%Y')-${SHA} + fi + + echo "WORKFLOW_BRANCH_OR_TAG=${BRANCH_OR_TAG}" >> $GITHUB_ENV + echo "DIST_SUFFIX=${SUFFIX}" >> $GITHUB_ENV + echo "::set-output name=artifacts-path::${BRANCH_OR_TAG}" + echo "::set-output name=suffix::${SUFFIX}" + echo "::set-output name=short-hash::${SHA}" + echo "::set-output name=default-target::${DEFAULT_TARGET}" + + - name: 'Make reports directory' + run: | + rm -rf reports/ + mkdir reports + + - name: 'Generate compile_comands.json' + run: | + FBT_TOOLCHAIN_PATH=/opt ./fbt COMPACT=1 version_json proto_ver icons firmware_cdb dolphin_internal dolphin_blocking + + - name: 'Static code analysis' + run: | + FBT_TOOLCHAIN_PATH=/opt source scripts/toolchain/fbtenv.sh + pvs-studio-analyzer credentials ${{ secrets.PVS_STUDIO_CREDENTIALS }} + pvs-studio-analyzer analyze \ + @.pvsoptions \ + -j$(grep -c processor /proc/cpuinfo) \ + -f build/f7-firmware-DC/compile_commands.json \ + -o PVS-Studio.log + + - name: 'Convert PVS-Studio output to html page' + run: plog-converter -a GA:1,2,3 -t fullhtml PVS-Studio.log -o reports/${{steps.names.outputs.default-target}}-${{steps.names.outputs.suffix}} + + - name: 'Upload artifacts to update server' + if: ${{ !github.event.pull_request.head.repo.fork }} + run: | + echo "${{ secrets.RSYNC_DEPLOY_KEY }}" > deploy_key; + chmod 600 ./deploy_key; + rsync -avrzP --mkpath \ + -e 'ssh -p ${{ secrets.RSYNC_DEPLOY_PORT }} -i ./deploy_key' \ + reports/ ${{ secrets.RSYNC_DEPLOY_USER }}@${{ secrets.RSYNC_DEPLOY_HOST }}:/home/data/firmware-pvs-studio-report/"${{steps.names.outputs.artifacts-path}}/"; + rm ./deploy_key; + + - name: 'Find Previous Comment' + if: ${{ !github.event.pull_request.head.repo.fork && github.event.pull_request }} + uses: peter-evans/find-comment@v1 + id: fc + with: + issue-number: ${{ github.event.pull_request.number }} + comment-author: 'github-actions[bot]' + body-includes: 'PVS-Studio report for commit' + + - name: 'Create or update comment' + if: ${{ !github.event.pull_request.head.repo.fork && github.event.pull_request}} + uses: peter-evans/create-or-update-comment@v1 + with: + comment-id: ${{ steps.fc.outputs.comment-id }} + issue-number: ${{ github.event.pull_request.number }} + body: | + **PVS-Studio report for commit `${{steps.names.outputs.short-hash}}`:** + - [Report](https://update.flipperzero.one/builds/firmware-pvs-studio-report/${{steps.names.outputs.artifacts-path}}/${{steps.names.outputs.default-target}}-${{steps.names.outputs.suffix}}/index.html) + edit-mode: replace diff --git a/.pvsoptions b/.pvsoptions index 6715f871896..4c80ab66788 100644 --- a/.pvsoptions +++ b/.pvsoptions @@ -1 +1 @@ ---rules-config .pvsconfig -e lib/fatfs -e lib/fnv1a-hash -e lib/FreeRTOS-Kernel -e lib/heatshrink -e lib/libusb_stm32 -e lib/littlefs -e lib/mbedtls -e lib/micro-ecc -e lib/microtar -e lib/mlib -e lib/qrcode -e lib/ST25RFAL002 -e lib/STM32CubeWB -e lib/u8g2 -e toolchain/ +--rules-config .pvsconfig -e lib/fatfs -e lib/fnv1a-hash -e lib/FreeRTOS-Kernel -e lib/heatshrink -e lib/libusb_stm32 -e lib/littlefs -e lib/mbedtls -e lib/micro-ecc -e lib/microtar -e lib/mlib -e lib/qrcode -e lib/ST25RFAL002 -e lib/STM32CubeWB -e lib/u8g2 -e */arm-none-eabi/* diff --git a/ReadMe.md b/ReadMe.md index 36a887dc42e..7a9777121af 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -61,29 +61,6 @@ One liner: `./fbt firmware_flash` 3. Run `dfu-util -D full.dfu -a 0` -# Build with Docker - -## Prerequisites - -1. Install [Docker Engine and Docker Compose](https://www.docker.com/get-started) -2. Prepare the container: - - ```sh - docker-compose up -d - ``` - -## Compile everything - -```sh -docker-compose exec dev ./fbt -``` - -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 Check out `documentation/fbt.md` for details on building and flashing firmware. @@ -157,7 +134,6 @@ Connect your device via ST-Link and run: - `assets` - Assets used by applications and services - `furi` - Furi Core: os level primitives and helpers - `debug` - Debug tool: GDB-plugins, SVD-file and etc -- `docker` - Docker image sources (used for firmware build automation) - `documentation` - Documentation generation system configs and input files - `firmware` - Firmware source code - `lib` - Our and 3rd party libraries, drivers and etc... diff --git a/applications/subghz/subghz_setting.c b/applications/subghz/subghz_setting.c index 0a662f589bf..b7c143cd5a2 100644 --- a/applications/subghz/subghz_setting.c +++ b/applications/subghz/subghz_setting.c @@ -446,15 +446,15 @@ const char* subghz_setting_get_preset_name(SubGhzSetting* instance, size_t idx) int subghz_setting_get_inx_preset_by_name(SubGhzSetting* instance, const char* preset_name) { furi_assert(instance); size_t idx = 0; - for - M_EACH(item, instance->preset->data, SubGhzSettingCustomPresetItemArray_t) { - if(strcmp(string_get_cstr(item->custom_preset_name), preset_name) == 0) { - return idx; - } - idx++; - } - furi_crash("SubGhz: No name preset."); - return -1; + for + M_EACH(item, instance->preset->data, SubGhzSettingCustomPresetItemArray_t) { + if(strcmp(string_get_cstr(item->custom_preset_name), preset_name) == 0) { + return idx; + } + idx++; + } + furi_crash("SubGhz: No name preset."); + return -1; } bool subghz_setting_load_custom_preset( diff --git a/assets/ReadMe.md b/assets/ReadMe.md index 2cd99d56b64..2d493b4fec4 100644 --- a/assets/ReadMe.md +++ b/assets/ReadMe.md @@ -9,12 +9,6 @@ ./fbt icons proto dolphin_internal dolphin_blocking dolphin_ext resources ``` -# Compiling with Docker-Compose - -```bash -docker-compose exec dev ./fbt icons proto dolphin_internal dolphin_blocking dolphin_ext resources -``` - # Asset naming rules ## Images and Animations diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 39aca411a19..00000000000 --- a/docker-compose.yml +++ /dev/null @@ -1,12 +0,0 @@ -version: '3' -services: - dev: - image: flipperdevices/flipperzero-toolchain - network_mode: host - privileged: true - tty: true - stdin_open: true - volumes: - - .:/project - - /dev/bus/usb:/dev/bus/usb - working_dir: '/project' diff --git a/docker/Dockerfile b/docker/Dockerfile deleted file mode 100644 index 64db408f3af..00000000000 --- a/docker/Dockerfile +++ /dev/null @@ -1,41 +0,0 @@ -FROM ubuntu:hirsute - -RUN apt-get update \ - && DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y \ - ca-certificates \ - build-essential \ - python3 \ - git \ - clang-format-12 \ - dfu-util \ - openocd \ - libncurses5 \ - python-setuptools \ - libpython2.7-dev \ - libxml2-dev \ - libxslt1-dev \ - zlib1g-dev \ - wget \ - python3-protobuf \ - protobuf-compiler \ - python3-pip \ - libpython3-dev \ - ccache \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* - -RUN wget --progress=dot:giga "https://developer.arm.com/-/media/Files/downloads/gnu-rm/10.3-2021.07/gcc-arm-none-eabi-10.3-2021.07-$(uname -m)-linux.tar.bz2" && \ - tar xjf gcc-arm-none-eabi-10.3-2021.07-$(uname -m)-linux.tar.bz2 && \ - rm gcc-arm-none-eabi-10.3-2021.07-$(uname -m)-linux.tar.bz2 && \ - cd gcc-arm-none-eabi-10.3-2021.07/bin/ && \ - rm -rf ../share && \ - for file in * ; do ln -s "${PWD}/${file}" "/usr/bin/${file}" ; done && \ - cd / && arm-none-eabi-gcc -v && arm-none-eabi-gdb -v - -RUN pip3 install heatshrink2==0.11.0 Pillow==9.1.1 - -RUN ln -s `which clang-format-12` /usr/local/bin/clang-format - -COPY entrypoint.sh / - -ENTRYPOINT ["/entrypoint.sh"] diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh deleted file mode 100755 index 4d553e0b427..00000000000 --- a/docker/entrypoint.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash - -if [ -z "$1" ]; then - bash -else - echo "Running $1" - set -ex - bash -c "$1" -fi diff --git a/scripts/amap_mariadb_insert.py b/scripts/amap_mariadb_insert.py new file mode 100755 index 00000000000..6ff1b3bf038 --- /dev/null +++ b/scripts/amap_mariadb_insert.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python3 + +from datetime import datetime +import argparse +import mariadb +import sys +import os + + +def parseArgs(): + parser = argparse.ArgumentParser() + parser.add_argument("db_user", help="MariaDB user") + parser.add_argument("db_pass", help="MariaDB password") + parser.add_argument("db_host", help="MariaDB hostname") + parser.add_argument("db_port", type=int, help="MariaDB port") + parser.add_argument("db_name", help="MariaDB database") + parser.add_argument("report_file", help="Report file(.map.all)") + args = parser.parse_args() + return args + + +def mariadbConnect(args): + try: + conn = mariadb.connect( + user=args.db_user, + password=args.db_pass, + host=args.db_host, + port=args.db_port, + database=args.db_name, + ) + except mariadb.Error as e: + print(f"Error connecting to MariaDB: {e}") + sys.exit(1) + return conn + + +def parseEnv(): + outArr = [] + outArr.append(datetime.now().strftime("%Y-%m-%d %H:%M:%S")) + outArr.append(os.getenv("COMMIT_HASH", default=None)) + outArr.append(os.getenv("COMMIT_MSG", default=None)) + outArr.append(os.getenv("BRANCH_NAME", default=None)) + outArr.append(os.getenv("BSS_SIZE", default=None)) + outArr.append(os.getenv("TEXT_SIZE", default=None)) + outArr.append(os.getenv("RODATA_SIZE", default=None)) + outArr.append(os.getenv("DATA_SIZE", default=None)) + outArr.append(os.getenv("FREE_FLASH_SIZE", default=None)) + outArr.append(os.getenv("PULL_ID", default=None)) + outArr.append(os.getenv("PULL_NAME", default=None)) + return outArr + + +def createTables(cur, conn): + headerTable = "CREATE TABLE IF NOT EXISTS `header` ( \ + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, \ + `datetime` datetime NOT NULL, \ + `commit` varchar(40) NOT NULL, \ + `commit_msg` text NOT NULL, \ + `branch_name` text NOT NULL, \ + `bss_size` int(10) unsigned NOT NULL, \ + `text_size` int(10) unsigned NOT NULL, \ + `rodata_size` int(10) unsigned NOT NULL, \ + `data_size` int(10) unsigned NOT NULL, \ + `free_flash_size` int(10) unsigned NOT NULL, \ + `pullrequest_id` int(10) unsigned DEFAULT NULL, \ + `pullrequest_name` text DEFAULT NULL, \ + PRIMARY KEY (`id`), \ + KEY `header_id_index` (`id`) )" + dataTable = "CREATE TABLE IF NOT EXISTS `data` ( \ + `header_id` int(10) unsigned NOT NULL, \ + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, \ + `section` text NOT NULL, \ + `address` text NOT NULL, \ + `size` int(10) unsigned NOT NULL, \ + `name` text NOT NULL, \ + `lib` text NOT NULL, \ + `obj_name` text NOT NULL, \ + PRIMARY KEY (`id`), \ + KEY `data_id_index` (`id`), \ + KEY `data_header_id_index` (`header_id`), \ + CONSTRAINT `data_header_id_foreign` FOREIGN KEY (`header_id`) REFERENCES `header` (`id`) )" + cur.execute(headerTable) + cur.execute(dataTable) + conn.commit() + + +def insertHeader(data, cur, conn): + query = "INSERT INTO `header` ( \ + datetime, commit, commit_msg, branch_name, bss_size, text_size, \ + rodata_size, data_size, free_flash_size, pullrequest_id, pullrequest_name) \ + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" + cur.execute(query, data) + conn.commit() + return cur.lastrowid + + +def parseFile(fileObj, headerID): + arr = [] + fileLines = fileObj.readlines() + for line in fileLines: + lineArr = [] + tempLineArr = line.split("\t") + lineArr.append(headerID) + lineArr.append(tempLineArr[0]) # section + lineArr.append(int(tempLineArr[2], 16)) # address hex + lineArr.append(int(tempLineArr[3])) # size + lineArr.append(tempLineArr[4]) # name + lineArr.append(tempLineArr[5]) # lib + lineArr.append(tempLineArr[6]) # obj_name + arr.append(tuple(lineArr)) + return arr + + +def insertData(data, cur, conn): + query = "INSERT INTO `data` ( \ + header_id, section, address, size, \ + name, lib, obj_name) \ + VALUES (?, ?, ?, ?, ? ,?, ?)" + cur.executemany(query, data) + conn.commit() + + +def main(): + args = parseArgs() + dbConn = mariadbConnect(args) + reportFile = open(args.report_file) + dbCurs = dbConn.cursor() + createTables(dbCurs, dbConn) + headerID = insertHeader(parseEnv(), dbCurs, dbConn) + insertData(parseFile(reportFile, headerID), dbCurs, dbConn) + reportFile.close() + dbCurs.close() + + +if __name__ == "__main__": + main() diff --git a/scripts/toolchain/fbtenv.cmd b/scripts/toolchain/fbtenv.cmd index aac2a33091a..f955a4db3fe 100644 --- a/scripts/toolchain/fbtenv.cmd +++ b/scripts/toolchain/fbtenv.cmd @@ -13,7 +13,7 @@ if not [%FBT_NOENV%] == [] ( exit /b 0 ) -set "FLIPPER_TOOLCHAIN_VERSION=8" +set "FLIPPER_TOOLCHAIN_VERSION=9" set "FBT_TOOLCHAIN_ROOT=%FBT_ROOT%\toolchain\i686-windows" diff --git a/scripts/toolchain/fbtenv.sh b/scripts/toolchain/fbtenv.sh index 654b1fe0d7f..d9d3cbe89b3 100755 --- a/scripts/toolchain/fbtenv.sh +++ b/scripts/toolchain/fbtenv.sh @@ -5,7 +5,7 @@ # public variables DEFAULT_SCRIPT_PATH="$(pwd -P)"; SCRIPT_PATH="${SCRIPT_PATH:-$DEFAULT_SCRIPT_PATH}"; -FBT_TOOLCHAIN_VERSION="${FBT_TOOLCHAIN_VERSION:-"8"}"; +FBT_TOOLCHAIN_VERSION="${FBT_TOOLCHAIN_VERSION:-"9"}"; FBT_TOOLCHAIN_PATH="${FBT_TOOLCHAIN_PATH:-$SCRIPT_PATH}"; fbtenv_check_sourced() @@ -13,12 +13,9 @@ fbtenv_check_sourced() case "${ZSH_EVAL_CONTEXT:-""}" in *:file:*) return 0;; esac - case ${0##*/} in dash|-dash|bash|-bash|ksh|-ksh|sh|-sh) + case ${0##*/} in dash|-dash|bash|-bash|ksh|-ksh|sh|-sh|*.sh|fbt) return 0;; esac - if [ "$(basename $0)" = "fbt" ]; then - return 0; - fi echo "Running this script manually is wrong, please source it"; echo "Example:"; printf "\tsource scripts/toolchain/fbtenv.sh\n"; @@ -202,6 +199,7 @@ fbtenv_main() fbtenv_check_script_path || return 1; fbtenv_get_kernel_type || return 1; fbtenv_check_download_toolchain || return 1; + PS1="[FBT]$PS1"; PATH="$TOOLCHAIN_ARCH_DIR/python/bin:$PATH"; PATH="$TOOLCHAIN_ARCH_DIR/bin:$PATH"; PATH="$TOOLCHAIN_ARCH_DIR/protobuf/bin:$PATH"; From ddd5d5a535ea29854b9ae1bbf4b80adb7c826746 Mon Sep 17 00:00:00 2001 From: Max Andreev Date: Tue, 23 Aug 2022 14:48:39 +0300 Subject: [PATCH 012/824] fix Amap reports outside pull-request (#1642) --- .github/workflows/amap_analyse.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/amap_analyse.yml b/.github/workflows/amap_analyse.yml index 3bfcba2cf55..dd903e2e839 100644 --- a/.github/workflows/amap_analyse.yml +++ b/.github/workflows/amap_analyse.yml @@ -103,13 +103,15 @@ jobs: export COMMIT_HASH="${{steps.names.outputs.commit-hash}}" export COMMIT_MSG="${{steps.names.outputs.commit-msg}}" export BRANCH_NAME="${{steps.names.outputs.branch-name}}" - export PULL_ID="${{steps.names.outputs.pull-id}}" - export PULL_NAME="${{steps.names.outputs.pull-name}}" export BSS_SIZE="$(get_size ".bss")" export TEXT_SIZE="$(get_size ".text")" export RODATA_SIZE="$(get_size ".rodata")" export DATA_SIZE="$(get_size ".data")" export FREE_FLASH_SIZE="$(get_size ".free_flash")" + if [[ ${{ github.event_name }} == 'pull_request' ]]; then + export PULL_ID="${{steps.names.outputs.pull-id}}" + export PULL_NAME="${{steps.names.outputs.pull-name}}" + fi python3 -m pip install mariadb python3 scripts/amap_mariadb_insert.py \ ${{ secrets.AMAP_MARIADB_USER }} \ From f92127c0a7053e0a91dc6fb605bfd8ebefac937b Mon Sep 17 00:00:00 2001 From: Eric Betts Date: Tue, 23 Aug 2022 06:19:17 -0700 Subject: [PATCH 013/824] Picopass load/info/delete (#1562) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * increase stack size * rfalPicoPassPollerWriteBlock * UI for loading picopass * Move picopass parsing and add delete, delete success Co-authored-by: あく --- applications/picopass/application.fam | 2 +- applications/picopass/picopass.c | 23 ++ applications/picopass/picopass_device.c | 227 +++++++++++++++++- applications/picopass/picopass_device.h | 21 ++ applications/picopass/picopass_i.h | 5 + applications/picopass/picopass_worker.c | 99 +------- applications/picopass/picopass_worker_i.h | 9 + .../picopass/scenes/picopass_scene_config.h | 4 + .../picopass/scenes/picopass_scene_delete.c | 58 +++++ .../scenes/picopass_scene_delete_success.c | 40 +++ .../scenes/picopass_scene_device_info.c | 82 +++++++ .../scenes/picopass_scene_file_select.c | 25 ++ .../scenes/picopass_scene_saved_menu.c | 24 ++ .../picopass/scenes/picopass_scene_start.c | 5 + lib/ST25RFAL002/include/rfal_picopass.h | 2 + lib/ST25RFAL002/source/rfal_picopass.c | 26 ++ 16 files changed, 559 insertions(+), 93 deletions(-) create mode 100644 applications/picopass/scenes/picopass_scene_delete.c create mode 100755 applications/picopass/scenes/picopass_scene_delete_success.c create mode 100644 applications/picopass/scenes/picopass_scene_device_info.c create mode 100644 applications/picopass/scenes/picopass_scene_file_select.c diff --git a/applications/picopass/application.fam b/applications/picopass/application.fam index 223094250db..ffc4b5182b8 100644 --- a/applications/picopass/application.fam +++ b/applications/picopass/application.fam @@ -5,7 +5,7 @@ App( entry_point="picopass_app", cdefines=["APP_PICOPASS"], requires=["storage", "gui"], - stack_size=1 * 1024, + stack_size=4 * 1024, icon="A_Plugins_14", order=30, ) diff --git a/applications/picopass/picopass.c b/applications/picopass/picopass.c index 8c0db4e2a7f..e9f48b6765c 100644 --- a/applications/picopass/picopass.c +++ b/applications/picopass/picopass.c @@ -56,6 +56,11 @@ Picopass* picopass_alloc() { view_dispatcher_add_view( picopass->view_dispatcher, PicopassViewPopup, popup_get_view(picopass->popup)); + // Loading + picopass->loading = loading_alloc(); + view_dispatcher_add_view( + picopass->view_dispatcher, PicopassViewLoading, loading_get_view(picopass->loading)); + // Text Input picopass->text_input = text_input_alloc(); view_dispatcher_add_view( @@ -86,6 +91,10 @@ void picopass_free(Picopass* picopass) { view_dispatcher_remove_view(picopass->view_dispatcher, PicopassViewPopup); popup_free(picopass->popup); + // Loading + view_dispatcher_remove_view(picopass->view_dispatcher, PicopassViewLoading); + loading_free(picopass->loading); + // TextInput view_dispatcher_remove_view(picopass->view_dispatcher, PicopassViewTextInput); text_input_free(picopass->text_input); @@ -148,6 +157,20 @@ void picopass_blink_stop(Picopass* picopass) { notification_message(picopass->notifications, &picopass_sequence_blink_stop); } +void picopass_show_loading_popup(void* context, bool show) { + Picopass* picopass = context; + TaskHandle_t timer_task = xTaskGetHandle(configTIMER_SERVICE_TASK_NAME); + + if(show) { + // Raise timer priority so that animations can play + vTaskPrioritySet(timer_task, configMAX_PRIORITIES - 1); + view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewLoading); + } else { + // Restore default timer priority + vTaskPrioritySet(timer_task, configTIMER_TASK_PRIORITY); + } +} + int32_t picopass_app(void* p) { UNUSED(p); Picopass* picopass = picopass_alloc(); diff --git a/applications/picopass/picopass_device.c b/applications/picopass/picopass_device.c index 9b422edd30f..4cd6faaab7c 100644 --- a/applications/picopass/picopass_device.c +++ b/applications/picopass/picopass_device.c @@ -8,6 +8,9 @@ static const char* picopass_file_header = "Flipper Picopass device"; static const uint32_t picopass_file_version = 1; +const uint8_t picopass_iclass_decryptionkey[] = + {0xb4, 0x21, 0x2c, 0xca, 0xb7, 0xed, 0x21, 0x0f, 0x7b, 0x93, 0xd4, 0x59, 0x39, 0xc7, 0xdd, 0x36}; + PicopassDevice* picopass_device_alloc() { PicopassDevice* picopass_dev = malloc(sizeof(PicopassDevice)); picopass_dev->dev_data.pacs.legacy = false; @@ -15,6 +18,7 @@ PicopassDevice* picopass_device_alloc() { picopass_dev->dev_data.pacs.pin_length = 0; picopass_dev->storage = furi_record_open(RECORD_STORAGE); picopass_dev->dialogs = furi_record_open(RECORD_DIALOGS); + string_init(picopass_dev->load_path); return picopass_dev; } @@ -111,7 +115,7 @@ static bool picopass_device_save_file( } while(0); if(!saved) { - dialog_message_show_storage_error(dev->dialogs, "Can not save\nkey file"); + dialog_message_show_storage_error(dev->dialogs, "Can not save\nfile"); } string_clear(temp_str); flipper_format_free(file); @@ -128,11 +132,83 @@ bool picopass_device_save(PicopassDevice* dev, const char* dev_name) { return false; } +static bool picopass_device_load_data(PicopassDevice* dev, string_t path, bool show_dialog) { + bool parsed = false; + FlipperFormat* file = flipper_format_file_alloc(dev->storage); + PicopassBlock* AA1 = dev->dev_data.AA1; + PicopassPacs* pacs = &dev->dev_data.pacs; + string_t temp_str; + string_init(temp_str); + bool deprecated_version = false; + + if(dev->loading_cb) { + dev->loading_cb(dev->loading_cb_ctx, true); + } + + do { + if(!flipper_format_file_open_existing(file, string_get_cstr(path))) break; + + // Read and verify file header + uint32_t version = 0; + if(!flipper_format_read_header(file, temp_str, &version)) break; + if(string_cmp_str(temp_str, picopass_file_header) || (version != picopass_file_version)) { + deprecated_version = true; + break; + } + + // Parse header blocks + bool block_read = true; + for(size_t i = 0; i < 6; i++) { + string_printf(temp_str, "Block %d", i); + if(!flipper_format_read_hex( + file, string_get_cstr(temp_str), AA1[i].data, PICOPASS_BLOCK_LEN)) { + block_read = false; + break; + } + } + + size_t app_limit = AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[0]; + for(size_t i = 6; i < app_limit; i++) { + string_printf(temp_str, "Block %d", i); + if(!flipper_format_read_hex( + file, string_get_cstr(temp_str), AA1[i].data, PICOPASS_BLOCK_LEN)) { + block_read = false; + break; + } + } + if(!block_read) break; + + if(picopass_device_parse_credential(AA1, pacs) != ERR_NONE) break; + if(picopass_device_parse_wiegand(pacs->credential, &pacs->record) != ERR_NONE) break; + + parsed = true; + } while(false); + + if(dev->loading_cb) { + dev->loading_cb(dev->loading_cb_ctx, false); + } + + if((!parsed) && (show_dialog)) { + if(deprecated_version) { + dialog_message_show_storage_error(dev->dialogs, "File format deprecated"); + } else { + dialog_message_show_storage_error(dev->dialogs, "Can not parse\nfile"); + } + } + + string_clear(temp_str); + flipper_format_free(file); + + return parsed; +} + void picopass_device_clear(PicopassDevice* dev) { furi_assert(dev); picopass_device_data_clear(&dev->dev_data); memset(&dev->dev_data, 0, sizeof(dev->dev_data)); + dev->format = PicopassDeviceSaveFormatHF; + string_reset(dev->load_path); } void picopass_device_free(PicopassDevice* picopass_dev) { @@ -144,6 +220,36 @@ void picopass_device_free(PicopassDevice* picopass_dev) { free(picopass_dev); } +bool picopass_file_select(PicopassDevice* dev) { + furi_assert(dev); + + // Input events and views are managed by file_browser + string_t picopass_app_folder; + string_init_set_str(picopass_app_folder, PICOPASS_APP_FOLDER); + bool res = dialog_file_browser_show( + dev->dialogs, + dev->load_path, + picopass_app_folder, + PICOPASS_APP_EXTENSION, + true, + &I_Nfc_10px, + true); + string_clear(picopass_app_folder); + if(res) { + string_t filename; + string_init(filename); + path_extract_filename(dev->load_path, filename, true); + strncpy(dev->dev_name, string_get_cstr(filename), PICOPASS_DEV_NAME_MAX_LEN); + res = picopass_device_load_data(dev, dev->load_path, true); + if(res) { + picopass_device_set_name(dev, dev->dev_name); + } + string_clear(filename); + } + + return res; +} + void picopass_device_data_clear(PicopassDeviceData* dev_data) { for(size_t i = 0; i < PICOPASS_MAX_APP_LIMIT; i++) { memset(dev_data->AA1[i].data, 0, sizeof(dev_data->AA1[i].data)); @@ -152,3 +258,122 @@ void picopass_device_data_clear(PicopassDeviceData* dev_data) { dev_data->pacs.se_enabled = false; dev_data->pacs.pin_length = 0; } + +bool picopass_device_delete(PicopassDevice* dev, bool use_load_path) { + furi_assert(dev); + + bool deleted = false; + string_t file_path; + string_init(file_path); + + do { + // Delete original file + 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", PICOPASS_APP_FOLDER, dev->dev_name, PICOPASS_APP_EXTENSION); + } + if(!storage_simply_remove(dev->storage, string_get_cstr(file_path))) break; + deleted = true; + } while(0); + + if(!deleted) { + dialog_message_show_storage_error(dev->dialogs, "Can not remove file"); + } + + string_clear(file_path); + return deleted; +} + +void picopass_device_set_loading_callback( + PicopassDevice* dev, + PicopassLoadingCallback callback, + void* context) { + furi_assert(dev); + + dev->loading_cb = callback; + dev->loading_cb_ctx = context; +} + +ReturnCode picopass_device_decrypt(uint8_t* enc_data, uint8_t* dec_data) { + uint8_t key[32] = {0}; + memcpy(key, picopass_iclass_decryptionkey, sizeof(picopass_iclass_decryptionkey)); + mbedtls_des3_context ctx; + mbedtls_des3_init(&ctx); + mbedtls_des3_set2key_dec(&ctx, key); + mbedtls_des3_crypt_ecb(&ctx, enc_data, dec_data); + mbedtls_des3_free(&ctx); + return ERR_NONE; +} + +ReturnCode picopass_device_parse_credential(PicopassBlock* AA1, PicopassPacs* pacs) { + ReturnCode err; + + // Thank you proxmark! + pacs->legacy = (memcmp(AA1[5].data, "\xff\xff\xff\xff\xff\xff\xff\xff", 8) == 0); + pacs->se_enabled = (memcmp(AA1[5].data, "\xff\xff\xff\x00\x06\xff\xff\xff", 8) == 0); + + pacs->biometrics = AA1[6].data[4]; + pacs->pin_length = AA1[6].data[6] & 0x0F; + pacs->encryption = AA1[6].data[7]; + + if(pacs->encryption == PicopassDeviceEncryption3DES) { + FURI_LOG_D(TAG, "3DES Encrypted"); + err = picopass_device_decrypt(AA1[7].data, pacs->credential); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "decrypt error %d", err); + return err; + } + + err = picopass_device_decrypt(AA1[8].data, pacs->pin0); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "decrypt error %d", err); + return err; + } + + err = picopass_device_decrypt(AA1[9].data, pacs->pin1); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "decrypt error %d", err); + return err; + } + } else if(pacs->encryption == PicopassDeviceEncryptionNone) { + FURI_LOG_D(TAG, "No Encryption"); + memcpy(pacs->credential, AA1[7].data, PICOPASS_BLOCK_LEN); + memcpy(pacs->pin0, AA1[8].data, PICOPASS_BLOCK_LEN); + memcpy(pacs->pin1, AA1[9].data, PICOPASS_BLOCK_LEN); + } else if(pacs->encryption == PicopassDeviceEncryptionDES) { + FURI_LOG_D(TAG, "DES Encrypted"); + } else { + FURI_LOG_D(TAG, "Unknown encryption"); + } + + return ERR_NONE; +} + +ReturnCode picopass_device_parse_wiegand(uint8_t* data, PicopassWiegandRecord* record) { + uint32_t* halves = (uint32_t*)data; + if(halves[0] == 0) { + uint8_t leading0s = __builtin_clz(REVERSE_BYTES_U32(halves[1])); + record->bitLength = 31 - leading0s; + } else { + uint8_t leading0s = __builtin_clz(REVERSE_BYTES_U32(halves[0])); + record->bitLength = 63 - leading0s; + } + FURI_LOG_D(TAG, "bitLength: %d", record->bitLength); + + if(record->bitLength == 26) { + uint8_t* v4 = data + 4; + uint32_t bot = v4[3] | (v4[2] << 8) | (v4[1] << 16) | (v4[0] << 24); + + record->CardNumber = (bot >> 1) & 0xFFFF; + record->FacilityCode = (bot >> 17) & 0xFF; + FURI_LOG_D(TAG, "FC:%u CN: %u\n", record->FacilityCode, record->CardNumber); + record->valid = true; + } else { + record->CardNumber = 0; + record->FacilityCode = 0; + record->valid = false; + } + return ERR_NONE; +} diff --git a/applications/picopass/picopass_device.h b/applications/picopass/picopass_device.h index 89e031ca7d6..0415b8794c3 100644 --- a/applications/picopass/picopass_device.h +++ b/applications/picopass/picopass_device.h @@ -7,6 +7,10 @@ #include +#include +#include +#include + #define PICOPASS_DEV_NAME_MAX_LEN 22 #define PICOPASS_READER_DATA_MAX_SIZE 64 #define PICOPASS_BLOCK_LEN 8 @@ -20,6 +24,8 @@ #define PICOPASS_APP_EXTENSION ".picopass" #define PICOPASS_APP_SHADOW_EXTENSION ".pas" +typedef void (*PicopassLoadingCallback)(void* context, bool state); + typedef enum { PicopassDeviceEncryptionUnknown = 0, PicopassDeviceEncryptionNone = 0x14, @@ -67,6 +73,9 @@ typedef struct { char dev_name[PICOPASS_DEV_NAME_MAX_LEN + 1]; string_t load_path; PicopassDeviceSaveFormat format; + PicopassLoadingCallback loading_cb; + void* loading_cb_ctx; + } PicopassDevice; PicopassDevice* picopass_device_alloc(); @@ -77,6 +86,18 @@ void picopass_device_set_name(PicopassDevice* dev, const char* name); bool picopass_device_save(PicopassDevice* dev, const char* dev_name); +bool picopass_file_select(PicopassDevice* dev); + void picopass_device_data_clear(PicopassDeviceData* dev_data); void picopass_device_clear(PicopassDevice* dev); + +bool picopass_device_delete(PicopassDevice* dev, bool use_load_path); + +void picopass_device_set_loading_callback( + PicopassDevice* dev, + PicopassLoadingCallback callback, + void* context); + +ReturnCode picopass_device_parse_credential(PicopassBlock* AA1, PicopassPacs* pacs); +ReturnCode picopass_device_parse_wiegand(uint8_t* data, PicopassWiegandRecord* record); diff --git a/applications/picopass/picopass_i.h b/applications/picopass/picopass_i.h index dbf4f8be582..d295f53ac12 100644 --- a/applications/picopass/picopass_i.h +++ b/applications/picopass/picopass_i.h @@ -14,6 +14,7 @@ #include #include +#include #include #include @@ -55,6 +56,7 @@ struct Picopass { // Common Views Submenu* submenu; Popup* popup; + Loading* loading; TextInput* text_input; Widget* widget; }; @@ -62,6 +64,7 @@ struct Picopass { typedef enum { PicopassViewMenu, PicopassViewPopup, + PicopassViewLoading, PicopassViewTextInput, PicopassViewWidget, } PicopassView; @@ -75,3 +78,5 @@ void picopass_text_store_clear(Picopass* picopass); void picopass_blink_start(Picopass* picopass); void picopass_blink_stop(Picopass* picopass); + +void picopass_show_loading_popup(void* context, bool show); diff --git a/applications/picopass/picopass_worker.c b/applications/picopass/picopass_worker.c index 3079a98c4d9..88df8d45b86 100644 --- a/applications/picopass/picopass_worker.c +++ b/applications/picopass/picopass_worker.c @@ -1,23 +1,9 @@ #include "picopass_worker_i.h" -#include - -#include -#include -#include -#include -#include - -#include -#include -#include - -#include #define TAG "PicopassWorker" const uint8_t picopass_iclass_key[] = {0xaf, 0xa7, 0x85, 0xa7, 0xda, 0xb3, 0x33, 0x78}; -const uint8_t picopass_iclass_decryptionkey[] = - {0xb4, 0x21, 0x2c, 0xca, 0xb7, 0xed, 0x21, 0x0f, 0x7b, 0x93, 0xd4, 0x59, 0x39, 0xc7, 0xdd, 0x36}; +const uint8_t picopass_factory_key[] = {0x76, 0x65, 0x54, 0x43, 0x32, 0x21, 0x10, 0x00}; static void picopass_worker_enable_field() { st25r3916TxRxOn(); @@ -31,44 +17,6 @@ static ReturnCode picopass_worker_disable_field(ReturnCode rc) { return rc; } -static ReturnCode picopass_worker_decrypt(uint8_t* enc_data, uint8_t* dec_data) { - uint8_t key[32] = {0}; - memcpy(key, picopass_iclass_decryptionkey, sizeof(picopass_iclass_decryptionkey)); - mbedtls_des3_context ctx; - mbedtls_des3_init(&ctx); - mbedtls_des3_set2key_dec(&ctx, key); - mbedtls_des3_crypt_ecb(&ctx, enc_data, dec_data); - mbedtls_des3_free(&ctx); - return ERR_NONE; -} - -static ReturnCode picopass_worker_parse_wiegand(uint8_t* data, PicopassWiegandRecord* record) { - uint32_t* halves = (uint32_t*)data; - if(halves[0] == 0) { - uint8_t leading0s = __builtin_clz(REVERSE_BYTES_U32(halves[1])); - record->bitLength = 31 - leading0s; - } else { - uint8_t leading0s = __builtin_clz(REVERSE_BYTES_U32(halves[0])); - record->bitLength = 63 - leading0s; - } - FURI_LOG_D(TAG, "bitLength: %d", record->bitLength); - - if(record->bitLength == 26) { - uint8_t* v4 = data + 4; - uint32_t bot = v4[3] | (v4[2] << 8) | (v4[1] << 16) | (v4[0] << 24); - - record->CardNumber = (bot >> 1) & 0xFFFF; - record->FacilityCode = (bot >> 17) & 0xFF; - FURI_LOG_D(TAG, "FC:%u CN: %u\n", record->FacilityCode, record->CardNumber); - record->valid = true; - } else { - record->CardNumber = 0; - record->FacilityCode = 0; - record->valid = false; - } - return ERR_NONE; -} - /***************************** Picopass Worker API *******************************/ PicopassWorker* picopass_worker_alloc() { @@ -272,46 +220,15 @@ void picopass_worker_detect(PicopassWorker* picopass_worker) { FURI_LOG_E(TAG, "picopass_read_card error %d", err); } - // Thank you proxmark! - pacs->legacy = (memcmp(AA1[5].data, "\xff\xff\xff\xff\xff\xff\xff\xff", 8) == 0); - pacs->se_enabled = (memcmp(AA1[5].data, "\xff\xff\xff\x00\x06\xff\xff\xff", 8) == 0); - - pacs->biometrics = AA1[6].data[4]; - pacs->pin_length = AA1[6].data[6] & 0x0F; - pacs->encryption = AA1[6].data[7]; - - if(pacs->encryption == PicopassDeviceEncryption3DES) { - FURI_LOG_D(TAG, "3DES Encrypted"); - err = picopass_worker_decrypt(AA1[7].data, pacs->credential); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "decrypt error %d", err); - break; - } - - err = picopass_worker_decrypt(AA1[8].data, pacs->pin0); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "decrypt error %d", err); - break; - } - - err = picopass_worker_decrypt(AA1[9].data, pacs->pin1); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "decrypt error %d", err); - break; - } - } else if(pacs->encryption == PicopassDeviceEncryptionNone) { - FURI_LOG_D(TAG, "No Encryption"); - memcpy(pacs->credential, AA1[7].data, PICOPASS_BLOCK_LEN); - memcpy(pacs->pin0, AA1[8].data, PICOPASS_BLOCK_LEN); - memcpy(pacs->pin1, AA1[9].data, PICOPASS_BLOCK_LEN); - } else if(pacs->encryption == PicopassDeviceEncryptionDES) { - FURI_LOG_D(TAG, "DES Encrypted"); - } else { - FURI_LOG_D(TAG, "Unknown encryption"); - break; + err = picopass_device_parse_credential(AA1, pacs); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "picopass_device_parse_credential error %d", err); } - picopass_worker_parse_wiegand(pacs->credential, &pacs->record); + err = picopass_device_parse_wiegand(pacs->credential, &pacs->record); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "picopass_device_parse_wiegand error %d", err); + } // Notify caller and exit if(picopass_worker->callback) { diff --git a/applications/picopass/picopass_worker_i.h b/applications/picopass/picopass_worker_i.h index 2610d5e7f06..789951900b7 100644 --- a/applications/picopass/picopass_worker_i.h +++ b/applications/picopass/picopass_worker_i.h @@ -6,6 +6,15 @@ #include #include +#include + +#include +#include +#include +#include + +#include + struct PicopassWorker { FuriThread* thread; Storage* storage; diff --git a/applications/picopass/scenes/picopass_scene_config.h b/applications/picopass/scenes/picopass_scene_config.h index 0a3e73f2979..87745378b67 100755 --- a/applications/picopass/scenes/picopass_scene_config.h +++ b/applications/picopass/scenes/picopass_scene_config.h @@ -5,3 +5,7 @@ ADD_SCENE(picopass, card_menu, CardMenu) ADD_SCENE(picopass, save_name, SaveName) ADD_SCENE(picopass, save_success, SaveSuccess) ADD_SCENE(picopass, saved_menu, SavedMenu) +ADD_SCENE(picopass, file_select, FileSelect) +ADD_SCENE(picopass, device_info, DeviceInfo) +ADD_SCENE(picopass, delete, Delete) +ADD_SCENE(picopass, delete_success, DeleteSuccess) diff --git a/applications/picopass/scenes/picopass_scene_delete.c b/applications/picopass/scenes/picopass_scene_delete.c new file mode 100644 index 00000000000..fb23cb5d4de --- /dev/null +++ b/applications/picopass/scenes/picopass_scene_delete.c @@ -0,0 +1,58 @@ +#include "../picopass_i.h" + +void picopass_scene_delete_widget_callback(GuiButtonType result, InputType type, void* context) { + Picopass* picopass = context; + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(picopass->view_dispatcher, result); + } +} + +void picopass_scene_delete_on_enter(void* context) { + Picopass* picopass = context; + + // Setup Custom Widget view + char temp_str[64]; + snprintf(temp_str, sizeof(temp_str), "\e#Delete %s?\e#", picopass->dev->dev_name); + widget_add_text_box_element( + picopass->widget, 0, 0, 128, 23, AlignCenter, AlignCenter, temp_str, false); + widget_add_button_element( + picopass->widget, + GuiButtonTypeLeft, + "Back", + picopass_scene_delete_widget_callback, + picopass); + widget_add_button_element( + picopass->widget, + GuiButtonTypeRight, + "Delete", + picopass_scene_delete_widget_callback, + picopass); + + view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewWidget); +} + +bool picopass_scene_delete_on_event(void* context, SceneManagerEvent event) { + Picopass* picopass = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == GuiButtonTypeLeft) { + return scene_manager_previous_scene(picopass->scene_manager); + } else if(event.event == GuiButtonTypeRight) { + if(picopass_device_delete(picopass->dev, true)) { + scene_manager_next_scene(picopass->scene_manager, PicopassSceneDeleteSuccess); + } else { + scene_manager_search_and_switch_to_previous_scene( + picopass->scene_manager, PicopassSceneStart); + } + consumed = true; + } + } + return consumed; +} + +void picopass_scene_delete_on_exit(void* context) { + Picopass* picopass = context; + + widget_reset(picopass->widget); +} diff --git a/applications/picopass/scenes/picopass_scene_delete_success.c b/applications/picopass/scenes/picopass_scene_delete_success.c new file mode 100755 index 00000000000..f2a36a7fb1e --- /dev/null +++ b/applications/picopass/scenes/picopass_scene_delete_success.c @@ -0,0 +1,40 @@ +#include "../picopass_i.h" + +void picopass_scene_delete_success_popup_callback(void* context) { + Picopass* picopass = context; + view_dispatcher_send_custom_event(picopass->view_dispatcher, PicopassCustomEventViewExit); +} + +void picopass_scene_delete_success_on_enter(void* context) { + Picopass* picopass = context; + + // Setup view + Popup* popup = picopass->popup; + popup_set_icon(popup, 0, 2, &I_DolphinMafia_115x62); + popup_set_header(popup, "Deleted", 83, 19, AlignLeft, AlignBottom); + popup_set_timeout(popup, 1500); + popup_set_context(popup, picopass); + popup_set_callback(popup, picopass_scene_delete_success_popup_callback); + popup_enable_timeout(popup); + view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewPopup); +} + +bool picopass_scene_delete_success_on_event(void* context, SceneManagerEvent event) { + Picopass* picopass = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == PicopassCustomEventViewExit) { + consumed = scene_manager_search_and_switch_to_previous_scene( + picopass->scene_manager, PicopassSceneStart); + } + } + return consumed; +} + +void picopass_scene_delete_success_on_exit(void* context) { + Picopass* picopass = context; + + // Clear view + popup_reset(picopass->popup); +} diff --git a/applications/picopass/scenes/picopass_scene_device_info.c b/applications/picopass/scenes/picopass_scene_device_info.c new file mode 100644 index 00000000000..38891b67334 --- /dev/null +++ b/applications/picopass/scenes/picopass_scene_device_info.c @@ -0,0 +1,82 @@ +#include "../picopass_i.h" +#include + +void picopass_scene_device_info_widget_callback( + GuiButtonType result, + InputType type, + void* context) { + Picopass* picopass = context; + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(picopass->view_dispatcher, result); + } +} + +void picopass_scene_device_info_on_enter(void* context) { + Picopass* picopass = context; + + string_t credential_str; + string_t wiegand_str; + string_init(credential_str); + string_init(wiegand_str); + + DOLPHIN_DEED(DolphinDeedNfcReadSuccess); + + // Setup view + PicopassPacs* pacs = &picopass->dev->dev_data.pacs; + Widget* widget = picopass->widget; + + size_t bytesLength = 1 + pacs->record.bitLength / 8; + string_set_str(credential_str, ""); + for(uint8_t i = PICOPASS_BLOCK_LEN - bytesLength; i < PICOPASS_BLOCK_LEN; i++) { + string_cat_printf(credential_str, " %02X", pacs->credential[i]); + } + + if(pacs->record.valid) { + string_cat_printf( + wiegand_str, "FC: %u CN: %u", pacs->record.FacilityCode, pacs->record.CardNumber); + } else { + string_cat_printf(wiegand_str, "%d bits", pacs->record.bitLength); + } + + widget_add_string_element( + widget, 64, 12, AlignCenter, AlignCenter, FontPrimary, string_get_cstr(wiegand_str)); + widget_add_string_element( + widget, 64, 32, AlignCenter, AlignCenter, FontSecondary, string_get_cstr(credential_str)); + + string_clear(credential_str); + string_clear(wiegand_str); + + widget_add_button_element( + picopass->widget, + GuiButtonTypeLeft, + "Back", + picopass_scene_device_info_widget_callback, + picopass); + + view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewWidget); +} + +bool picopass_scene_device_info_on_event(void* context, SceneManagerEvent event) { + Picopass* picopass = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == GuiButtonTypeLeft) { + consumed = scene_manager_previous_scene(picopass->scene_manager); + } else if(event.event == PicopassCustomEventViewExit) { + view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewWidget); + consumed = true; + } + } else if(event.type == SceneManagerEventTypeBack) { + view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewWidget); + consumed = true; + } + return consumed; +} + +void picopass_scene_device_info_on_exit(void* context) { + Picopass* picopass = context; + + // Clear views + widget_reset(picopass->widget); +} diff --git a/applications/picopass/scenes/picopass_scene_file_select.c b/applications/picopass/scenes/picopass_scene_file_select.c new file mode 100644 index 00000000000..b3d4c3d73ec --- /dev/null +++ b/applications/picopass/scenes/picopass_scene_file_select.c @@ -0,0 +1,25 @@ +#include "../picopass_i.h" +#include "picopass/picopass_device.h" + +void picopass_scene_file_select_on_enter(void* context) { + Picopass* picopass = context; + // Process file_select return + picopass_device_set_loading_callback(picopass->dev, picopass_show_loading_popup, picopass); + if(picopass_file_select(picopass->dev)) { + scene_manager_next_scene(picopass->scene_manager, PicopassSceneSavedMenu); + } else { + scene_manager_search_and_switch_to_previous_scene( + picopass->scene_manager, PicopassSceneStart); + } + picopass_device_set_loading_callback(picopass->dev, NULL, picopass); +} + +bool picopass_scene_file_select_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + UNUSED(event); + return false; +} + +void picopass_scene_file_select_on_exit(void* context) { + UNUSED(context); +} diff --git a/applications/picopass/scenes/picopass_scene_saved_menu.c b/applications/picopass/scenes/picopass_scene_saved_menu.c index 232cf26a976..8f0ce40bae4 100644 --- a/applications/picopass/scenes/picopass_scene_saved_menu.c +++ b/applications/picopass/scenes/picopass_scene_saved_menu.c @@ -1,5 +1,11 @@ #include "../picopass_i.h" +enum SubmenuIndex { + SubmenuIndexDelete, + SubmenuIndexInfo, + SubmenuIndexWrite, +}; + void picopass_scene_saved_menu_submenu_callback(void* context, uint32_t index) { Picopass* picopass = context; @@ -8,6 +14,16 @@ void picopass_scene_saved_menu_submenu_callback(void* context, uint32_t index) { void picopass_scene_saved_menu_on_enter(void* context) { Picopass* picopass = context; + Submenu* submenu = picopass->submenu; + + submenu_add_item( + submenu, + "Delete", + SubmenuIndexDelete, + picopass_scene_saved_menu_submenu_callback, + picopass); + submenu_add_item( + submenu, "Info", SubmenuIndexInfo, picopass_scene_saved_menu_submenu_callback, picopass); submenu_set_selected_item( picopass->submenu, @@ -23,6 +39,14 @@ bool picopass_scene_saved_menu_on_event(void* context, SceneManagerEvent event) if(event.type == SceneManagerEventTypeCustom) { scene_manager_set_scene_state( picopass->scene_manager, PicopassSceneSavedMenu, event.event); + + if(event.event == SubmenuIndexDelete) { + scene_manager_next_scene(picopass->scene_manager, PicopassSceneDelete); + consumed = true; + } else if(event.event == SubmenuIndexInfo) { + scene_manager_next_scene(picopass->scene_manager, PicopassSceneDeviceInfo); + consumed = true; + } } return consumed; diff --git a/applications/picopass/scenes/picopass_scene_start.c b/applications/picopass/scenes/picopass_scene_start.c index 7f42fb13309..76c18a22a5d 100644 --- a/applications/picopass/scenes/picopass_scene_start.c +++ b/applications/picopass/scenes/picopass_scene_start.c @@ -17,6 +17,8 @@ void picopass_scene_start_on_enter(void* context) { Submenu* submenu = picopass->submenu; submenu_add_item( submenu, "Read Card", SubmenuIndexRead, picopass_scene_start_submenu_callback, picopass); + submenu_add_item( + submenu, "Saved", SubmenuIndexSaved, picopass_scene_start_submenu_callback, picopass); submenu_set_selected_item( submenu, scene_manager_get_scene_state(picopass->scene_manager, PicopassSceneStart)); @@ -32,6 +34,9 @@ bool picopass_scene_start_on_event(void* context, SceneManagerEvent event) { if(event.event == SubmenuIndexRead) { scene_manager_next_scene(picopass->scene_manager, PicopassSceneReadCard); consumed = true; + } else if(event.event == SubmenuIndexSaved) { + scene_manager_next_scene(picopass->scene_manager, PicopassSceneFileSelect); + consumed = true; } scene_manager_set_scene_state(picopass->scene_manager, PicopassSceneStart, event.event); } diff --git a/lib/ST25RFAL002/include/rfal_picopass.h b/lib/ST25RFAL002/include/rfal_picopass.h index 5b8150251ce..2baf96f3754 100644 --- a/lib/ST25RFAL002/include/rfal_picopass.h +++ b/lib/ST25RFAL002/include/rfal_picopass.h @@ -26,6 +26,7 @@ enum { RFAL_PICOPASS_CMD_READCHECK = 0x88, RFAL_PICOPASS_CMD_CHECK = 0x05, RFAL_PICOPASS_CMD_READ = 0x0C, + RFAL_PICOPASS_CMD_WRITE = 0x0C, }; typedef struct { @@ -58,5 +59,6 @@ ReturnCode rfalPicoPassPollerSelect(uint8_t* csn, rfalPicoPassSelectRes* selRes) ReturnCode rfalPicoPassPollerReadCheck(rfalPicoPassReadCheckRes* rcRes); ReturnCode rfalPicoPassPollerCheck(uint8_t* mac, rfalPicoPassCheckRes* chkRes); ReturnCode rfalPicoPassPollerReadBlock(uint8_t blockNum, rfalPicoPassReadBlockRes* readRes); +ReturnCode rfalPicoPassPollerWriteBlock(uint8_t blockNum, uint8_t data[8], uint8_t mac[4]); #endif /* RFAL_PICOPASS_H */ diff --git a/lib/ST25RFAL002/source/rfal_picopass.c b/lib/ST25RFAL002/source/rfal_picopass.c index 55dbe6497b1..d4422e41230 100644 --- a/lib/ST25RFAL002/source/rfal_picopass.c +++ b/lib/ST25RFAL002/source/rfal_picopass.c @@ -158,3 +158,29 @@ ReturnCode rfalPicoPassPollerReadBlock(uint8_t blockNum, rfalPicoPassReadBlockRe fwt); return ret; } + +ReturnCode rfalPicoPassPollerWriteBlock(uint8_t blockNum, uint8_t data[8], uint8_t mac[4]) { + ReturnCode ret; + + uint8_t txBuf[14] = {RFAL_PICOPASS_CMD_WRITE, blockNum, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + memcpy(txBuf + 2, data, RFAL_PICOPASS_MAX_BLOCK_LEN); + memcpy(txBuf + 10, mac, 4); + + uint16_t recvLen = 0; + uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS; + uint32_t fwt = rfalConvMsTo1fc(20); + rfalPicoPassReadBlockRes readRes; + + ret = rfalTransceiveBlockingTxRx( + txBuf, + sizeof(txBuf), + (uint8_t*)&readRes, + sizeof(rfalPicoPassReadBlockRes), + &recvLen, + flags, + fwt); + + // TODO: compare response + + return ret; +} From 9bfb641d3eddd30a8f8c5c216cb6e7c404aefe0a Mon Sep 17 00:00:00 2001 From: SG Date: Wed, 24 Aug 2022 01:57:39 +1000 Subject: [PATCH 014/824] [FL-2529][FL-1628] New LF-RFID subsystem (#1601) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Makefile: unit tests pack * RFID: pulse joiner and its unit test * Move pulse protocol helpers to appropriate place * Drop pulse_joiner tests * Generic protocol, protocols dictionary, unit test * Protocol dict unit test * iButton: protocols dictionary * Lib: varint * Lib: profiler * Unit test: varint * rfid: worker mockup * LFRFID: em4100 unit test * Storage: file_exist function * rfid: fsk osc * rfid: generic fsk demodulator * rfid: protocol em4100 * rfid: protocol h10301 * rfid: protocol io prox xsf * Unit test: rfid protocols * rfid: new hal * rfid: raw worker * Unit test: fix error output * rfid: worker * rfid: plain c cli * fw: migrate to scons * lfrfid: full io prox support * unit test: io prox protocol * SubGHZ: move bit defines to source * FSK oscillator: level duration compability * libs: bit manipulation library * lfrfid: ioprox protocol, use bit library and new level duration method of FSK ocillator * bit lib: unit tests * Bit lib: parity tests, remove every nth bit, copy bits * Lfrfid: awid protocol * bit lib: uint16 and uint32 getters, unit tests * lfrfid: FDX-B read, draft version * Minunit: better memeq assert * bit lib: reverse, print, print regions * Protocol dict: get protocol features, get protocol validate count * lfrfid worker: improved read * lfrfid raw worker: psk support * Cli: rfid plain C cli * protocol AWID: render * protocol em4100: render * protocol h10301: render * protocol indala26: support every indala 26 scramble * Protocol IO Prox: render * Protocol FDX-B: advanced read * lfrfid: remove unused test function * lfrfid: fix os primitives * bit lib: crc16 and unit tests * FDX-B: save data * lfrfid worker: increase stream size. Alloc raw worker only when needed. * lfrfid: indala26 emulation * lfrfid: prepare to write * lfrfid: fdx-b emulation * lfrfid: awid, ioprox write * lfrfid: write t55xx w\o validation * lfrfid: better t55xx block0 handling * lfrfid: use new t5577 functions in worker * lfrfid: improve protocol description * lfrfid: write and verify * lfrfid: delete cpp cli * lfrfid: improve worker usage * lfrfid-app: step to new worker * lfrfid: old indala (I40134) load fallback * lfrfid: indala26, recover wrong synced data * lfrfid: remove old worker * lfrfid app: dummy read screen * lfrfid app: less dummy read screen * lfrfid: generic 96-bit HID protocol (covers up to HID 37-bit) * rename * lfrfid: improve indala26 read * lfrfid: generic 192-bit HID protocol (covers all HID extended) * lfrfid: TODO about HID render * lfrfid: new protocol FDX-A * lfrfid-app: correct worker stop on exit * misc fixes * lfrfid: FDX-A and HID distinguishability has been fixed. * lfrfid: decode HID size header and render it (#1612) * lfrfid: rename HID96 and HID192 to HIDProx and HIDExt * lfrfid: extra actions scene * lfrfid: decode generic HID Proximity size lazily (#1618) * lib: stream of data buffers concept * lfrfid: raw file helper * lfrfid: changed raw worker api * lfrfid: packed varint pair * lfrfid: read stream speedup * lfrfid app: show read mode * Documentation * lfrfid app: raw read gui * lfrfid app: storage check for raw read * memleak fix * review fixes * lfrfid app: read blink color * lfrfid app: reset key name after read * review fixes * lfrfid app: fix copypasted text * review fixes * lfrfid: disable debug gpio * lfrfid: card detection events * lfrfid: change validation color from magenta to green * Update core_defines. * lfrfid: prefix fdx-b id by zeroes * lfrfid: parse up to 43-bit HID Proximity keys (#1640) * Fbt: downgrade toolchain and fix PS1 * lfrfid: fix unit tests * lfrfid app: remove printf * lfrfid: indala26, use bit 55 as data * lfrfid: indala26, better brief format * lfrfid: indala26, loading fallback * lfrfid: read timing tuning Co-authored-by: James Ide Co-authored-by: あく --- applications/archive/helpers/archive_apps.c | 18 +- .../archive/helpers/archive_favorites.c | 33 +- .../desktop/animations/animation_manager.c | 3 +- .../lfrfid/helpers/decoder_analyzer.cpp | 50 -- .../lfrfid/helpers/decoder_analyzer.h | 21 - .../lfrfid/helpers/decoder_emmarin.cpp | 72 -- applications/lfrfid/helpers/decoder_emmarin.h | 21 - .../lfrfid/helpers/decoder_gpio_out.cpp | 15 - .../lfrfid/helpers/decoder_gpio_out.h | 14 - applications/lfrfid/helpers/decoder_hid26.cpp | 98 --- applications/lfrfid/helpers/decoder_hid26.h | 24 - .../lfrfid/helpers/decoder_indala.cpp | 76 --- applications/lfrfid/helpers/decoder_indala.h | 25 - .../lfrfid/helpers/decoder_ioprox.cpp | 107 --- applications/lfrfid/helpers/decoder_ioprox.h | 26 - applications/lfrfid/helpers/emmarin.h | 15 - .../lfrfid/helpers/encoder_emmarin.cpp | 24 - applications/lfrfid/helpers/encoder_emmarin.h | 22 - applications/lfrfid/helpers/encoder_generic.h | 27 - .../lfrfid/helpers/encoder_hid_h10301.cpp | 46 -- .../lfrfid/helpers/encoder_hid_h10301.h | 26 - .../lfrfid/helpers/encoder_indala_40134.cpp | 36 - .../lfrfid/helpers/encoder_indala_40134.h | 23 - .../lfrfid/helpers/encoder_ioprox.cpp | 32 - applications/lfrfid/helpers/encoder_ioprox.h | 25 - applications/lfrfid/helpers/key_info.cpp | 76 --- applications/lfrfid/helpers/key_info.h | 17 - applications/lfrfid/helpers/osc_fsk.cpp | 20 - applications/lfrfid/helpers/osc_fsk.h | 30 - .../helpers/protocols/protocol_emmarin.cpp | 150 ----- .../helpers/protocols/protocol_emmarin.h | 22 - .../helpers/protocols/protocol_generic.h | 60 -- .../helpers/protocols/protocol_hid_h10301.cpp | 238 ------- .../helpers/protocols/protocol_hid_h10301.h | 22 - .../protocols/protocol_indala_40134.cpp | 237 ------- .../helpers/protocols/protocol_indala_40134.h | 22 - .../helpers/protocols/protocol_ioprox.cpp | 193 ------ .../helpers/protocols/protocol_ioprox.h | 26 - applications/lfrfid/helpers/pulse_joiner.cpp | 95 --- applications/lfrfid/helpers/pulse_joiner.h | 36 - applications/lfrfid/helpers/rfid_key.cpp | 65 -- applications/lfrfid/helpers/rfid_key.h | 27 - applications/lfrfid/helpers/rfid_reader.cpp | 175 ----- applications/lfrfid/helpers/rfid_reader.h | 59 -- .../lfrfid/helpers/rfid_timer_emulator.cpp | 56 -- .../lfrfid/helpers/rfid_timer_emulator.h | 31 - applications/lfrfid/helpers/rfid_worker.cpp | 136 ---- applications/lfrfid/helpers/rfid_worker.h | 48 -- applications/lfrfid/helpers/rfid_writer.cpp | 183 ----- applications/lfrfid/helpers/rfid_writer.h | 21 - .../lfrfid/helpers/state_sequencer.cpp | 50 -- applications/lfrfid/helpers/state_sequencer.h | 25 - applications/lfrfid/lfrfid_app.cpp | 108 ++- applications/lfrfid/lfrfid_app.h | 51 +- applications/lfrfid/lfrfid_cli.c | 575 ++++++++++++++++ applications/lfrfid/lfrfid_cli.cpp | 177 ----- .../scene/lfrfid_app_scene_delete_confirm.cpp | 47 +- .../scene/lfrfid_app_scene_delete_confirm.h | 1 - .../lfrfid/scene/lfrfid_app_scene_emulate.cpp | 23 +- .../lfrfid/scene/lfrfid_app_scene_emulate.h | 3 - .../scene/lfrfid_app_scene_extra_actions.cpp | 63 ++ .../scene/lfrfid_app_scene_extra_actions.h | 13 + .../scene/lfrfid_app_scene_raw_info.cpp | 77 +++ .../lfrfid/scene/lfrfid_app_scene_raw_info.h | 12 + .../scene/lfrfid_app_scene_raw_name.cpp | 46 ++ .../lfrfid/scene/lfrfid_app_scene_raw_name.h | 12 + .../scene/lfrfid_app_scene_raw_read.cpp | 107 +++ .../lfrfid/scene/lfrfid_app_scene_raw_read.h | 15 + .../scene/lfrfid_app_scene_raw_success.cpp | 45 ++ .../scene/lfrfid_app_scene_raw_success.h | 13 + .../lfrfid/scene/lfrfid_app_scene_read.cpp | 98 ++- .../scene/lfrfid_app_scene_read_menu.cpp | 6 +- .../scene/lfrfid_app_scene_read_success.cpp | 125 +--- .../scene/lfrfid_app_scene_read_success.h | 3 +- .../lfrfid/scene/lfrfid_app_scene_rpc.cpp | 10 +- .../scene/lfrfid_app_scene_save_data.cpp | 18 +- .../lfrfid/scene/lfrfid_app_scene_save_data.h | 21 - .../scene/lfrfid_app_scene_save_name.cpp | 18 +- .../scene/lfrfid_app_scene_save_type.cpp | 16 +- .../lfrfid/scene/lfrfid_app_scene_save_type.h | 4 +- .../scene/lfrfid_app_scene_saved_info.cpp | 87 +-- .../scene/lfrfid_app_scene_saved_info.h | 5 +- .../scene/lfrfid_app_scene_saved_key_menu.cpp | 6 +- .../lfrfid/scene/lfrfid_app_scene_start.cpp | 15 +- .../lfrfid/scene/lfrfid_app_scene_write.cpp | 95 +-- .../lfrfid/scene/lfrfid_app_scene_write.h | 4 - applications/storage/storage.h | 9 + applications/storage/storage_external_api.c | 12 + applications/unit_tests/lfrfid/bit_lib_test.c | 473 +++++++++++++ .../unit_tests/lfrfid/lfrfid_protocols.c | 464 +++++++++++++ applications/unit_tests/minunit.h | 174 +++-- .../protocol_dict/protocol_dict_test.c | 222 +++++++ applications/unit_tests/test_index.c | 6 + applications/unit_tests/varint/varint_test.c | 88 +++ applications/updater/util/update_task.c | 3 +- firmware.scons | 1 + firmware/targets/f7/furi_hal/furi_hal_rfid.c | 186 ++++++ .../targets/furi_hal_include/furi_hal_rfid.h | 18 + furi/core/core_defines.h | 2 + lib/SConscript | 1 + lib/flipper_format/flipper_format.h | 4 +- lib/lfrfid/SConscript | 19 + lib/lfrfid/lfrfid_dict_file.c | 182 +++++ lib/lfrfid/lfrfid_dict_file.h | 31 + lib/lfrfid/lfrfid_raw_file.c | 145 ++++ lib/lfrfid/lfrfid_raw_file.h | 95 +++ lib/lfrfid/lfrfid_raw_worker.c | 357 ++++++++++ lib/lfrfid/lfrfid_raw_worker.h | 68 ++ lib/lfrfid/lfrfid_worker.c | 169 +++++ lib/lfrfid/lfrfid_worker.h | 152 +++++ lib/lfrfid/lfrfid_worker_i.h | 64 ++ lib/lfrfid/lfrfid_worker_modes.c | 624 ++++++++++++++++++ lib/lfrfid/protocols/lfrfid_protocols.c | 22 + lib/lfrfid/protocols/lfrfid_protocols.h | 35 + lib/lfrfid/protocols/protocol_awid.c | 239 +++++++ lib/lfrfid/protocols/protocol_awid.h | 4 + lib/lfrfid/protocols/protocol_em4100.c | 292 ++++++++ lib/lfrfid/protocols/protocol_em4100.h | 4 + lib/lfrfid/protocols/protocol_fdx_a.c | 239 +++++++ lib/lfrfid/protocols/protocol_fdx_a.h | 4 + lib/lfrfid/protocols/protocol_fdx_b.c | 374 +++++++++++ lib/lfrfid/protocols/protocol_fdx_b.h | 4 + lib/lfrfid/protocols/protocol_h10301.c | 386 +++++++++++ lib/lfrfid/protocols/protocol_h10301.h | 4 + .../protocols/protocol_hid_ex_generic.c | 219 ++++++ .../protocols/protocol_hid_ex_generic.h | 4 + lib/lfrfid/protocols/protocol_hid_generic.c | 280 ++++++++ lib/lfrfid/protocols/protocol_hid_generic.h | 4 + lib/lfrfid/protocols/protocol_indala26.c | 353 ++++++++++ lib/lfrfid/protocols/protocol_indala26.h | 4 + lib/lfrfid/protocols/protocol_io_prox_xsf.c | 297 +++++++++ lib/lfrfid/protocols/protocol_io_prox_xsf.h | 4 + lib/lfrfid/tools/bit_lib.c | 291 ++++++++ lib/lfrfid/tools/bit_lib.h | 220 ++++++ lib/lfrfid/tools/fsk_demod.c | 93 +++ lib/lfrfid/tools/fsk_demod.h | 44 ++ lib/lfrfid/tools/fsk_ocs.c | 62 ++ lib/lfrfid/tools/fsk_osc.h | 60 ++ lib/lfrfid/tools/t5577.c | 94 +++ lib/lfrfid/tools/t5577.h | 56 ++ lib/lfrfid/tools/varint_pair.c | 77 +++ lib/lfrfid/tools/varint_pair.h | 79 +++ lib/one_wire/ibutton/encoder/encoder_cyfral.c | 126 ---- lib/one_wire/ibutton/encoder/encoder_cyfral.h | 54 -- .../ibutton/encoder/encoder_metakom.c | 93 --- .../ibutton/encoder/encoder_metakom.h | 54 -- lib/one_wire/ibutton/ibutton_key.h | 1 + lib/one_wire/ibutton/ibutton_worker.c | 24 +- lib/one_wire/ibutton/ibutton_worker_i.h | 27 +- lib/one_wire/ibutton/ibutton_worker_modes.c | 116 ++-- .../ibutton/protocols/ibutton_protocols.c | 8 + .../ibutton/protocols/ibutton_protocols.h | 11 + .../ibutton/protocols/protocol_cyfral.c | 344 ++++++++++ .../ibutton/protocols/protocol_cyfral.h | 4 + .../protocol_metakom.c | 201 ++++-- .../ibutton/protocols/protocol_metakom.h | 4 + .../ibutton/pulse_protocols/protocol_cyfral.c | 256 ------- .../ibutton/pulse_protocols/protocol_cyfral.h | 38 -- .../pulse_protocols/protocol_metakom.h | 38 -- lib/one_wire/pulse_protocols/pulse_decoder.c | 66 -- lib/one_wire/pulse_protocols/pulse_decoder.h | 70 -- lib/one_wire/pulse_protocols/pulse_protocol.c | 67 -- lib/one_wire/pulse_protocols/pulse_protocol.h | 122 ---- lib/subghz/protocols/keeloq_common.c | 4 + lib/subghz/protocols/keeloq_common.h | 3 - lib/toolbox/buffer_stream.c | 145 ++++ lib/toolbox/buffer_stream.h | 94 +++ lib/toolbox/profiler.c | 87 +++ lib/toolbox/profiler.h | 23 + lib/toolbox/protocols/protocol.h | 46 ++ lib/toolbox/protocols/protocol_dict.c | 226 +++++++ lib/toolbox/protocols/protocol_dict.h | 73 ++ lib/toolbox/pulse_joiner.c | 117 ++++ lib/toolbox/pulse_joiner.h | 46 ++ .../pulse_protocols/pulse_glue.c | 0 .../pulse_protocols/pulse_glue.h | 0 lib/toolbox/varint.c | 76 +++ lib/toolbox/varint.h | 35 + scripts/toolchain/fbtenv.sh | 4 +- 179 files changed, 10234 insertions(+), 4804 deletions(-) delete mode 100644 applications/lfrfid/helpers/decoder_analyzer.cpp delete mode 100644 applications/lfrfid/helpers/decoder_analyzer.h delete mode 100644 applications/lfrfid/helpers/decoder_emmarin.cpp delete mode 100644 applications/lfrfid/helpers/decoder_emmarin.h delete mode 100644 applications/lfrfid/helpers/decoder_gpio_out.cpp delete mode 100644 applications/lfrfid/helpers/decoder_gpio_out.h delete mode 100644 applications/lfrfid/helpers/decoder_hid26.cpp delete mode 100644 applications/lfrfid/helpers/decoder_hid26.h delete mode 100644 applications/lfrfid/helpers/decoder_indala.cpp delete mode 100644 applications/lfrfid/helpers/decoder_indala.h delete mode 100644 applications/lfrfid/helpers/decoder_ioprox.cpp delete mode 100644 applications/lfrfid/helpers/decoder_ioprox.h delete mode 100644 applications/lfrfid/helpers/emmarin.h delete mode 100644 applications/lfrfid/helpers/encoder_emmarin.cpp delete mode 100644 applications/lfrfid/helpers/encoder_emmarin.h delete mode 100644 applications/lfrfid/helpers/encoder_generic.h delete mode 100644 applications/lfrfid/helpers/encoder_hid_h10301.cpp delete mode 100644 applications/lfrfid/helpers/encoder_hid_h10301.h delete mode 100644 applications/lfrfid/helpers/encoder_indala_40134.cpp delete mode 100644 applications/lfrfid/helpers/encoder_indala_40134.h delete mode 100644 applications/lfrfid/helpers/encoder_ioprox.cpp delete mode 100644 applications/lfrfid/helpers/encoder_ioprox.h delete mode 100644 applications/lfrfid/helpers/key_info.cpp delete mode 100644 applications/lfrfid/helpers/key_info.h delete mode 100644 applications/lfrfid/helpers/osc_fsk.cpp delete mode 100644 applications/lfrfid/helpers/osc_fsk.h delete mode 100644 applications/lfrfid/helpers/protocols/protocol_emmarin.cpp delete mode 100644 applications/lfrfid/helpers/protocols/protocol_emmarin.h delete mode 100644 applications/lfrfid/helpers/protocols/protocol_generic.h delete mode 100644 applications/lfrfid/helpers/protocols/protocol_hid_h10301.cpp delete mode 100644 applications/lfrfid/helpers/protocols/protocol_hid_h10301.h delete mode 100644 applications/lfrfid/helpers/protocols/protocol_indala_40134.cpp delete mode 100644 applications/lfrfid/helpers/protocols/protocol_indala_40134.h delete mode 100644 applications/lfrfid/helpers/protocols/protocol_ioprox.cpp delete mode 100644 applications/lfrfid/helpers/protocols/protocol_ioprox.h delete mode 100644 applications/lfrfid/helpers/pulse_joiner.cpp delete mode 100644 applications/lfrfid/helpers/pulse_joiner.h delete mode 100644 applications/lfrfid/helpers/rfid_key.cpp delete mode 100644 applications/lfrfid/helpers/rfid_key.h delete mode 100644 applications/lfrfid/helpers/rfid_reader.cpp delete mode 100644 applications/lfrfid/helpers/rfid_reader.h delete mode 100644 applications/lfrfid/helpers/rfid_timer_emulator.cpp delete mode 100644 applications/lfrfid/helpers/rfid_timer_emulator.h delete mode 100644 applications/lfrfid/helpers/rfid_worker.cpp delete mode 100644 applications/lfrfid/helpers/rfid_worker.h delete mode 100644 applications/lfrfid/helpers/rfid_writer.cpp delete mode 100644 applications/lfrfid/helpers/rfid_writer.h delete mode 100644 applications/lfrfid/helpers/state_sequencer.cpp delete mode 100644 applications/lfrfid/helpers/state_sequencer.h create mode 100644 applications/lfrfid/lfrfid_cli.c delete mode 100644 applications/lfrfid/lfrfid_cli.cpp create mode 100644 applications/lfrfid/scene/lfrfid_app_scene_extra_actions.cpp create mode 100644 applications/lfrfid/scene/lfrfid_app_scene_extra_actions.h create mode 100644 applications/lfrfid/scene/lfrfid_app_scene_raw_info.cpp create mode 100644 applications/lfrfid/scene/lfrfid_app_scene_raw_info.h create mode 100644 applications/lfrfid/scene/lfrfid_app_scene_raw_name.cpp create mode 100644 applications/lfrfid/scene/lfrfid_app_scene_raw_name.h create mode 100644 applications/lfrfid/scene/lfrfid_app_scene_raw_read.cpp create mode 100644 applications/lfrfid/scene/lfrfid_app_scene_raw_read.h create mode 100644 applications/lfrfid/scene/lfrfid_app_scene_raw_success.cpp create mode 100644 applications/lfrfid/scene/lfrfid_app_scene_raw_success.h create mode 100644 applications/unit_tests/lfrfid/bit_lib_test.c create mode 100644 applications/unit_tests/lfrfid/lfrfid_protocols.c create mode 100644 applications/unit_tests/protocol_dict/protocol_dict_test.c create mode 100644 applications/unit_tests/varint/varint_test.c create mode 100644 lib/lfrfid/SConscript create mode 100644 lib/lfrfid/lfrfid_dict_file.c create mode 100644 lib/lfrfid/lfrfid_dict_file.h create mode 100644 lib/lfrfid/lfrfid_raw_file.c create mode 100644 lib/lfrfid/lfrfid_raw_file.h create mode 100644 lib/lfrfid/lfrfid_raw_worker.c create mode 100644 lib/lfrfid/lfrfid_raw_worker.h create mode 100644 lib/lfrfid/lfrfid_worker.c create mode 100644 lib/lfrfid/lfrfid_worker.h create mode 100644 lib/lfrfid/lfrfid_worker_i.h create mode 100644 lib/lfrfid/lfrfid_worker_modes.c create mode 100644 lib/lfrfid/protocols/lfrfid_protocols.c create mode 100644 lib/lfrfid/protocols/lfrfid_protocols.h create mode 100644 lib/lfrfid/protocols/protocol_awid.c create mode 100644 lib/lfrfid/protocols/protocol_awid.h create mode 100644 lib/lfrfid/protocols/protocol_em4100.c create mode 100644 lib/lfrfid/protocols/protocol_em4100.h create mode 100644 lib/lfrfid/protocols/protocol_fdx_a.c create mode 100644 lib/lfrfid/protocols/protocol_fdx_a.h create mode 100644 lib/lfrfid/protocols/protocol_fdx_b.c create mode 100644 lib/lfrfid/protocols/protocol_fdx_b.h create mode 100644 lib/lfrfid/protocols/protocol_h10301.c create mode 100644 lib/lfrfid/protocols/protocol_h10301.h create mode 100644 lib/lfrfid/protocols/protocol_hid_ex_generic.c create mode 100644 lib/lfrfid/protocols/protocol_hid_ex_generic.h create mode 100644 lib/lfrfid/protocols/protocol_hid_generic.c create mode 100644 lib/lfrfid/protocols/protocol_hid_generic.h create mode 100644 lib/lfrfid/protocols/protocol_indala26.c create mode 100644 lib/lfrfid/protocols/protocol_indala26.h create mode 100644 lib/lfrfid/protocols/protocol_io_prox_xsf.c create mode 100644 lib/lfrfid/protocols/protocol_io_prox_xsf.h create mode 100644 lib/lfrfid/tools/bit_lib.c create mode 100644 lib/lfrfid/tools/bit_lib.h create mode 100644 lib/lfrfid/tools/fsk_demod.c create mode 100644 lib/lfrfid/tools/fsk_demod.h create mode 100644 lib/lfrfid/tools/fsk_ocs.c create mode 100644 lib/lfrfid/tools/fsk_osc.h create mode 100644 lib/lfrfid/tools/t5577.c create mode 100644 lib/lfrfid/tools/t5577.h create mode 100644 lib/lfrfid/tools/varint_pair.c create mode 100644 lib/lfrfid/tools/varint_pair.h delete mode 100644 lib/one_wire/ibutton/encoder/encoder_cyfral.c delete mode 100644 lib/one_wire/ibutton/encoder/encoder_cyfral.h delete mode 100644 lib/one_wire/ibutton/encoder/encoder_metakom.c delete mode 100644 lib/one_wire/ibutton/encoder/encoder_metakom.h create mode 100644 lib/one_wire/ibutton/protocols/ibutton_protocols.c create mode 100644 lib/one_wire/ibutton/protocols/ibutton_protocols.h create mode 100644 lib/one_wire/ibutton/protocols/protocol_cyfral.c create mode 100644 lib/one_wire/ibutton/protocols/protocol_cyfral.h rename lib/one_wire/ibutton/{pulse_protocols => protocols}/protocol_metakom.c (51%) create mode 100644 lib/one_wire/ibutton/protocols/protocol_metakom.h delete mode 100644 lib/one_wire/ibutton/pulse_protocols/protocol_cyfral.c delete mode 100644 lib/one_wire/ibutton/pulse_protocols/protocol_cyfral.h delete mode 100644 lib/one_wire/ibutton/pulse_protocols/protocol_metakom.h delete mode 100644 lib/one_wire/pulse_protocols/pulse_decoder.c delete mode 100644 lib/one_wire/pulse_protocols/pulse_decoder.h delete mode 100644 lib/one_wire/pulse_protocols/pulse_protocol.c delete mode 100644 lib/one_wire/pulse_protocols/pulse_protocol.h create mode 100644 lib/toolbox/buffer_stream.c create mode 100644 lib/toolbox/buffer_stream.h create mode 100644 lib/toolbox/profiler.c create mode 100644 lib/toolbox/profiler.h create mode 100644 lib/toolbox/protocols/protocol.h create mode 100644 lib/toolbox/protocols/protocol_dict.c create mode 100644 lib/toolbox/protocols/protocol_dict.h create mode 100644 lib/toolbox/pulse_joiner.c create mode 100644 lib/toolbox/pulse_joiner.h rename lib/{one_wire => toolbox}/pulse_protocols/pulse_glue.c (100%) rename lib/{one_wire => toolbox}/pulse_protocols/pulse_glue.h (100%) create mode 100644 lib/toolbox/varint.c create mode 100644 lib/toolbox/varint.h diff --git a/applications/archive/helpers/archive_apps.c b/applications/archive/helpers/archive_apps.c index 9a3f825f1c4..72084f1134e 100644 --- a/applications/archive/helpers/archive_apps.c +++ b/applications/archive/helpers/archive_apps.c @@ -29,23 +29,13 @@ bool archive_app_is_available(void* context, const char* path) { if(app == ArchiveAppTypeU2f) { bool file_exists = false; - Storage* fs_api = furi_record_open(RECORD_STORAGE); - File* file = storage_file_alloc(fs_api); - - file_exists = - storage_file_open(file, ANY_PATH("u2f/key.u2f"), FSAM_READ, FSOM_OPEN_EXISTING); - if(file_exists) { - storage_file_close(file); - file_exists = - storage_file_open(file, ANY_PATH("u2f/cnt.u2f"), FSAM_READ, FSOM_OPEN_EXISTING); - if(file_exists) { - storage_file_close(file); - } + Storage* storage = furi_record_open(RECORD_STORAGE); + + if(storage_file_exists(storage, ANY_PATH("u2f/key.u2f"))) { + file_exists = storage_file_exists(storage, ANY_PATH("u2f/cnt.u2f")); } - storage_file_free(file); furi_record_close(RECORD_STORAGE); - return file_exists; } else { return false; diff --git a/applications/archive/helpers/archive_favorites.c b/applications/archive/helpers/archive_favorites.c index 35199242e95..4d5b555f5a3 100644 --- a/applications/archive/helpers/archive_favorites.c +++ b/applications/archive/helpers/archive_favorites.c @@ -82,9 +82,8 @@ uint16_t archive_favorites_count(void* context) { static bool archive_favourites_rescan() { string_t buffer; string_init(buffer); - Storage* fs_api = furi_record_open(RECORD_STORAGE); - File* file = storage_file_alloc(fs_api); - File* fav_item_file = storage_file_alloc(fs_api); + Storage* storage = furi_record_open(RECORD_STORAGE); + File* file = storage_file_alloc(storage); bool result = storage_file_open(file, ARCHIVE_FAV_PATH, FSAM_READ, FSOM_OPEN_EXISTING); if(result) { @@ -101,13 +100,8 @@ static bool archive_favourites_rescan() { archive_file_append(ARCHIVE_FAV_TEMP_PATH, "%s\n", string_get_cstr(buffer)); } } else { - bool file_exists = storage_file_open( - fav_item_file, string_get_cstr(buffer), FSAM_READ, FSOM_OPEN_EXISTING); - if(file_exists) { - storage_file_close(fav_item_file); + if(storage_file_exists(storage, string_get_cstr(buffer))) { archive_file_append(ARCHIVE_FAV_TEMP_PATH, "%s\n", string_get_cstr(buffer)); - } else { - storage_file_close(fav_item_file); } } } @@ -116,12 +110,11 @@ static bool archive_favourites_rescan() { string_clear(buffer); storage_file_close(file); - storage_common_remove(fs_api, ARCHIVE_FAV_PATH); - storage_common_rename(fs_api, ARCHIVE_FAV_TEMP_PATH, ARCHIVE_FAV_PATH); - storage_common_remove(fs_api, ARCHIVE_FAV_TEMP_PATH); + storage_common_remove(storage, ARCHIVE_FAV_PATH); + storage_common_rename(storage, ARCHIVE_FAV_TEMP_PATH, ARCHIVE_FAV_PATH); + storage_common_remove(storage, ARCHIVE_FAV_TEMP_PATH); storage_file_free(file); - storage_file_free(fav_item_file); furi_record_close(RECORD_STORAGE); return result; @@ -131,9 +124,8 @@ bool archive_favorites_read(void* context) { furi_assert(context); ArchiveBrowserView* browser = context; - Storage* fs_api = furi_record_open(RECORD_STORAGE); - File* file = storage_file_alloc(fs_api); - File* fav_item_file = storage_file_alloc(fs_api); + Storage* storage = furi_record_open(RECORD_STORAGE); + File* file = storage_file_alloc(storage); string_t buffer; FileInfo file_info; @@ -163,16 +155,12 @@ bool archive_favorites_read(void* context) { need_refresh = true; } } else { - bool file_exists = storage_file_open( - fav_item_file, string_get_cstr(buffer), FSAM_READ, FSOM_OPEN_EXISTING); - if(file_exists) { - storage_common_stat(fs_api, string_get_cstr(buffer), &file_info); - storage_file_close(fav_item_file); + if(storage_file_exists(storage, string_get_cstr(buffer))) { + storage_common_stat(storage, string_get_cstr(buffer), &file_info); archive_add_file_item( browser, (file_info.flags & FSF_DIRECTORY), string_get_cstr(buffer)); file_count++; } else { - storage_file_close(fav_item_file); need_refresh = true; } } @@ -183,7 +171,6 @@ bool archive_favorites_read(void* context) { storage_file_close(file); string_clear(buffer); storage_file_free(file); - storage_file_free(fav_item_file); furi_record_close(RECORD_STORAGE); archive_set_item_count(browser, file_count); diff --git a/applications/desktop/animations/animation_manager.c b/applications/desktop/animations/animation_manager.c index d755be9c048..1e2a521e121 100644 --- a/applications/desktop/animations/animation_manager.c +++ b/applications/desktop/animations/animation_manager.c @@ -220,8 +220,7 @@ static bool animation_manager_check_blocking(AnimationManager* animation_manager furi_assert(blocking_animation); animation_manager->sd_shown_sd_ok = true; } else if(!animation_manager->sd_shown_no_db) { - bool db_exists = storage_common_stat(storage, EXT_PATH("Manifest"), NULL) == FSE_OK; - if(!db_exists) { + if(!storage_file_exists(storage, EXT_PATH("Manifest"))) { blocking_animation = animation_storage_find_animation(NO_DB_ANIMATION_NAME); furi_assert(blocking_animation); animation_manager->sd_shown_no_db = true; diff --git a/applications/lfrfid/helpers/decoder_analyzer.cpp b/applications/lfrfid/helpers/decoder_analyzer.cpp deleted file mode 100644 index 8d344b8809a..00000000000 --- a/applications/lfrfid/helpers/decoder_analyzer.cpp +++ /dev/null @@ -1,50 +0,0 @@ -#include "decoder_analyzer.h" -#include -#include - -// FIXME: unused args? -bool DecoderAnalyzer::read(uint8_t* /* _data */, uint8_t /* _data_size */) { - bool result = false; - - if(ready) { - result = true; - - for(size_t i = 0; i < data_size; i++) { - printf("%lu ", data[i]); - if((i + 1) % 8 == 0) printf("\r\n"); - } - printf("\r\n--------\r\n"); - - ready = false; - } - - return result; -} - -void DecoderAnalyzer::process_front(bool polarity, uint32_t time) { - UNUSED(polarity); - if(ready) return; - - data[data_index] = time; - - if(data_index < data_size) { - data_index++; - } else { - data_index = 0; - ready = true; - } -} - -DecoderAnalyzer::DecoderAnalyzer() { - data = reinterpret_cast(calloc(data_size, sizeof(uint32_t))); - furi_check(data); - data_index = 0; - ready = false; -} - -DecoderAnalyzer::~DecoderAnalyzer() { - free(data); -} - -void DecoderAnalyzer::reset_state() { -} diff --git a/applications/lfrfid/helpers/decoder_analyzer.h b/applications/lfrfid/helpers/decoder_analyzer.h deleted file mode 100644 index eecea6edd92..00000000000 --- a/applications/lfrfid/helpers/decoder_analyzer.h +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once -#include -#include - -class DecoderAnalyzer { -public: - bool read(uint8_t* data, uint8_t data_size); - void process_front(bool polarity, uint32_t time); - - DecoderAnalyzer(); - ~DecoderAnalyzer(); - -private: - void reset_state(); - - std::atomic ready; - - static const uint32_t data_size = 2048; - uint32_t data_index = 0; - uint32_t* data; -}; diff --git a/applications/lfrfid/helpers/decoder_emmarin.cpp b/applications/lfrfid/helpers/decoder_emmarin.cpp deleted file mode 100644 index daa8e238c23..00000000000 --- a/applications/lfrfid/helpers/decoder_emmarin.cpp +++ /dev/null @@ -1,72 +0,0 @@ -#include "emmarin.h" -#include "decoder_emmarin.h" -#include -#include - -constexpr uint32_t clocks_in_us = 64; -constexpr uint32_t short_time = 255 * clocks_in_us; -constexpr uint32_t long_time = 510 * clocks_in_us; -constexpr uint32_t jitter_time = 100 * clocks_in_us; - -constexpr uint32_t short_time_low = short_time - jitter_time; -constexpr uint32_t short_time_high = short_time + jitter_time; -constexpr uint32_t long_time_low = long_time - jitter_time; -constexpr uint32_t long_time_high = long_time + jitter_time; - -void DecoderEMMarin::reset_state() { - ready = false; - read_data = 0; - manchester_advance( - manchester_saved_state, ManchesterEventReset, &manchester_saved_state, nullptr); -} - -bool DecoderEMMarin::read(uint8_t* data, uint8_t data_size) { - bool result = false; - - if(ready) { - result = true; - em_marin.decode( - reinterpret_cast(&read_data), sizeof(uint64_t), data, data_size); - ready = false; - } - - return result; -} - -void DecoderEMMarin::process_front(bool polarity, uint32_t time) { - if(ready) return; - if(time < short_time_low) return; - - ManchesterEvent event = ManchesterEventReset; - - if(time > short_time_low && time < short_time_high) { - if(polarity) { - event = ManchesterEventShortHigh; - } else { - event = ManchesterEventShortLow; - } - } else if(time > long_time_low && time < long_time_high) { - if(polarity) { - event = ManchesterEventLongHigh; - } else { - event = ManchesterEventLongLow; - } - } - - if(event != ManchesterEventReset) { - bool data; - bool data_ok = - manchester_advance(manchester_saved_state, event, &manchester_saved_state, &data); - - if(data_ok) { - read_data = (read_data << 1) | data; - - ready = em_marin.can_be_decoded( - reinterpret_cast(&read_data), sizeof(uint64_t)); - } - } -} - -DecoderEMMarin::DecoderEMMarin() { - reset_state(); -} diff --git a/applications/lfrfid/helpers/decoder_emmarin.h b/applications/lfrfid/helpers/decoder_emmarin.h deleted file mode 100644 index a3b48d71ce1..00000000000 --- a/applications/lfrfid/helpers/decoder_emmarin.h +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once -#include -#include -#include -#include "protocols/protocol_emmarin.h" -class DecoderEMMarin { -public: - bool read(uint8_t* data, uint8_t data_size); - void process_front(bool polarity, uint32_t time); - - DecoderEMMarin(); - -private: - void reset_state(); - - uint64_t read_data = 0; - std::atomic ready; - - ManchesterState manchester_saved_state; - ProtocolEMMarin em_marin; -}; diff --git a/applications/lfrfid/helpers/decoder_gpio_out.cpp b/applications/lfrfid/helpers/decoder_gpio_out.cpp deleted file mode 100644 index dfb43426725..00000000000 --- a/applications/lfrfid/helpers/decoder_gpio_out.cpp +++ /dev/null @@ -1,15 +0,0 @@ -#include "decoder_gpio_out.h" -#include -#include - -void DecoderGpioOut::process_front(bool polarity, uint32_t /* time */) { - furi_hal_gpio_write(&gpio_ext_pa7, polarity); -} - -DecoderGpioOut::DecoderGpioOut() { - furi_hal_gpio_init_simple(&gpio_ext_pa7, GpioModeOutputPushPull); -} - -DecoderGpioOut::~DecoderGpioOut() { - furi_hal_gpio_init_simple(&gpio_ext_pa7, GpioModeAnalog); -} diff --git a/applications/lfrfid/helpers/decoder_gpio_out.h b/applications/lfrfid/helpers/decoder_gpio_out.h deleted file mode 100644 index 087720dfde7..00000000000 --- a/applications/lfrfid/helpers/decoder_gpio_out.h +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once -#include -#include - -class DecoderGpioOut { -public: - void process_front(bool polarity, uint32_t time); - - DecoderGpioOut(); - ~DecoderGpioOut(); - -private: - void reset_state(); -}; diff --git a/applications/lfrfid/helpers/decoder_hid26.cpp b/applications/lfrfid/helpers/decoder_hid26.cpp deleted file mode 100644 index e530b508927..00000000000 --- a/applications/lfrfid/helpers/decoder_hid26.cpp +++ /dev/null @@ -1,98 +0,0 @@ -#include "decoder_hid26.h" -#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 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; - -bool DecoderHID26::read(uint8_t* data, uint8_t data_size) { - bool result = false; - furi_assert(data_size >= 3); - - if(ready) { - result = true; - hid.decode( - reinterpret_cast(&stored_data), sizeof(uint32_t) * 3, data, data_size); - ready = false; - } - - return result; -} - -void DecoderHID26::process_front(bool polarity, uint32_t time) { - if(ready) return; - - if(polarity == true) { - last_pulse_time = time; - } else { - last_pulse_time += time; - - if(last_pulse_time > min_time && last_pulse_time < max_time) { - bool pulse; - - if(last_pulse_time < mid_time) { - // 6 pulses - pulse = false; - } else { - // 5 pulses - pulse = true; - } - - if(last_pulse == pulse) { - pulse_count++; - - if(pulse) { - if(pulse_count > 4) { - pulse_count = 0; - store_data(1); - } - } else { - if(pulse_count > 5) { - pulse_count = 0; - store_data(0); - } - } - } else { - if(last_pulse) { - if(pulse_count > 2) { - store_data(1); - } - } else { - if(pulse_count > 3) { - store_data(0); - } - } - - pulse_count = 0; - last_pulse = pulse; - } - } - } -} - -DecoderHID26::DecoderHID26() { - reset_state(); -} - -void DecoderHID26::store_data(bool data) { - stored_data[0] = (stored_data[0] << 1) | ((stored_data[1] >> 31) & 1); - stored_data[1] = (stored_data[1] << 1) | ((stored_data[2] >> 31) & 1); - stored_data[2] = (stored_data[2] << 1) | data; - - if(hid.can_be_decoded(reinterpret_cast(&stored_data), sizeof(uint32_t) * 3)) { - ready = true; - } -} - -void DecoderHID26::reset_state() { - last_pulse = false; - pulse_count = 0; - ready = false; - last_pulse_time = 0; -} diff --git a/applications/lfrfid/helpers/decoder_hid26.h b/applications/lfrfid/helpers/decoder_hid26.h deleted file mode 100644 index c73ff88c400..00000000000 --- a/applications/lfrfid/helpers/decoder_hid26.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once -#include -#include -#include "protocols/protocol_hid_h10301.h" - -class DecoderHID26 { -public: - bool read(uint8_t* data, uint8_t data_size); - void process_front(bool polarity, uint32_t time); - DecoderHID26(); - -private: - uint32_t last_pulse_time = 0; - bool last_pulse; - uint8_t pulse_count; - - uint32_t stored_data[3] = {0, 0, 0}; - void store_data(bool data); - - std::atomic ready; - - void reset_state(); - ProtocolHID10301 hid; -}; diff --git a/applications/lfrfid/helpers/decoder_indala.cpp b/applications/lfrfid/helpers/decoder_indala.cpp deleted file mode 100644 index 100dde73bdf..00000000000 --- a/applications/lfrfid/helpers/decoder_indala.cpp +++ /dev/null @@ -1,76 +0,0 @@ -#include "decoder_indala.h" -#include - -constexpr uint32_t clocks_in_us = 64; -constexpr uint32_t us_per_bit = 255; - -bool DecoderIndala::read(uint8_t* data, uint8_t data_size) { - bool result = false; - - if(ready) { - result = true; - if(cursed_data_valid) { - indala.decode( - reinterpret_cast(&cursed_raw_data), - sizeof(uint64_t), - data, - data_size); - } else { - indala.decode( - reinterpret_cast(&raw_data), sizeof(uint64_t), data, data_size); - } - reset_state(); - } - - return result; -} - -void DecoderIndala::process_front(bool polarity, uint32_t time) { - if(ready) return; - - process_internal(polarity, time, &raw_data); - if(ready) return; - - if(polarity) { - time = time + 110; - } else { - time = time - 110; - } - - process_internal(!polarity, time, &cursed_raw_data); - if(ready) { - cursed_data_valid = true; - } -} - -void DecoderIndala::process_internal(bool polarity, uint32_t time, uint64_t* data) { - time /= clocks_in_us; - time += (us_per_bit / 2); - - uint32_t bit_count = (time / us_per_bit); - - if(bit_count < 64) { - for(uint32_t i = 0; i < bit_count; i++) { - *data = (*data << 1) | polarity; - - if((*data >> 32) == 0xa0000000ULL) { - if(indala.can_be_decoded( - reinterpret_cast(data), sizeof(uint64_t))) { - ready = true; - break; - } - } - } - } -} - -DecoderIndala::DecoderIndala() { - reset_state(); -} - -void DecoderIndala::reset_state() { - raw_data = 0; - cursed_raw_data = 0; - ready = false; - cursed_data_valid = false; -} diff --git a/applications/lfrfid/helpers/decoder_indala.h b/applications/lfrfid/helpers/decoder_indala.h deleted file mode 100644 index 5fbde7b6bdf..00000000000 --- a/applications/lfrfid/helpers/decoder_indala.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once -#include -#include -#include -#include "protocols/protocol_indala_40134.h" - -class DecoderIndala { -public: - bool read(uint8_t* data, uint8_t data_size); - void process_front(bool polarity, uint32_t time); - - void process_internal(bool polarity, uint32_t time, uint64_t* data); - - DecoderIndala(); - -private: - void reset_state(); - - uint64_t raw_data; - uint64_t cursed_raw_data; - - std::atomic ready; - std::atomic cursed_data_valid; - ProtocolIndala40134 indala; -}; diff --git a/applications/lfrfid/helpers/decoder_ioprox.cpp b/applications/lfrfid/helpers/decoder_ioprox.cpp deleted file mode 100644 index 7b44d3ceae4..00000000000 --- a/applications/lfrfid/helpers/decoder_ioprox.cpp +++ /dev/null @@ -1,107 +0,0 @@ -#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 deleted file mode 100644 index aff4a47786a..00000000000 --- a/applications/lfrfid/helpers/decoder_ioprox.h +++ /dev/null @@ -1,26 +0,0 @@ -#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/emmarin.h b/applications/lfrfid/helpers/emmarin.h deleted file mode 100644 index ff8273bf767..00000000000 --- a/applications/lfrfid/helpers/emmarin.h +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once -#include - -#define EM_HEADER_POS 55 -#define EM_HEADER_MASK (0x1FFLLU << EM_HEADER_POS) - -#define EM_FIRST_ROW_POS 50 -#define EM_ROW_COUNT 10 - -#define EM_COLUMN_POS 4 -#define EM_STOP_POS 0 -#define EM_STOP_MASK (0x1LLU << EM_STOP_POS) - -#define EM_HEADER_AND_STOP_MASK (EM_HEADER_MASK | EM_STOP_MASK) -#define EM_HEADER_AND_STOP_DATA (EM_HEADER_MASK) diff --git a/applications/lfrfid/helpers/encoder_emmarin.cpp b/applications/lfrfid/helpers/encoder_emmarin.cpp deleted file mode 100644 index c329ab40046..00000000000 --- a/applications/lfrfid/helpers/encoder_emmarin.cpp +++ /dev/null @@ -1,24 +0,0 @@ -#include "encoder_emmarin.h" -#include "protocols/protocol_emmarin.h" -#include - -void EncoderEM::init(const uint8_t* data, const uint8_t data_size) { - ProtocolEMMarin em_marin; - em_marin.encode(data, data_size, reinterpret_cast(&card_data), sizeof(uint64_t)); - - card_data_index = 0; -} - -// data transmitted as manchester encoding -// 0 - high2low -// 1 - low2high -void EncoderEM::get_next(bool* polarity, uint16_t* period, uint16_t* pulse) { - *period = clocks_per_bit; - *pulse = clocks_per_bit / 2; - *polarity = (card_data >> (63 - card_data_index)) & 1; - - card_data_index++; - if(card_data_index >= 64) { - card_data_index = 0; - } -} diff --git a/applications/lfrfid/helpers/encoder_emmarin.h b/applications/lfrfid/helpers/encoder_emmarin.h deleted file mode 100644 index 560daec1b5a..00000000000 --- a/applications/lfrfid/helpers/encoder_emmarin.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once -#include "encoder_generic.h" - -class EncoderEM : public EncoderGeneric { -public: - /** - * @brief init data to emulate - * - * @param data 1 byte FC, next 4 byte SN - * @param data_size must be 5 - */ - void init(const uint8_t* data, const uint8_t data_size) final; - - void get_next(bool* polarity, uint16_t* period, uint16_t* pulse) final; - -private: - // clock pulses per bit - static const uint8_t clocks_per_bit = 64; - - uint64_t card_data; - uint8_t card_data_index; -}; diff --git a/applications/lfrfid/helpers/encoder_generic.h b/applications/lfrfid/helpers/encoder_generic.h deleted file mode 100644 index dc6ae850396..00000000000 --- a/applications/lfrfid/helpers/encoder_generic.h +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once -#include -#include - -class EncoderGeneric { -public: - /** - * @brief init encoder - * - * @param data data array - * @param data_size data array size - */ - virtual void init(const uint8_t* data, const uint8_t data_size) = 0; - - /** - * @brief Get the next timer pulse - * - * @param polarity pulse polarity true = high2low, false = low2high - * @param period overall period time in timer clicks - * @param pulse pulse time in timer clicks - */ - virtual void get_next(bool* polarity, uint16_t* period, uint16_t* pulse) = 0; - - virtual ~EncoderGeneric(){}; - -private: -}; diff --git a/applications/lfrfid/helpers/encoder_hid_h10301.cpp b/applications/lfrfid/helpers/encoder_hid_h10301.cpp deleted file mode 100644 index 09f637fee10..00000000000 --- a/applications/lfrfid/helpers/encoder_hid_h10301.cpp +++ /dev/null @@ -1,46 +0,0 @@ -#include "encoder_hid_h10301.h" -#include "protocols/protocol_hid_h10301.h" -#include - -void EncoderHID_H10301::init(const uint8_t* data, const uint8_t data_size) { - ProtocolHID10301 hid; - hid.encode(data, data_size, reinterpret_cast(&card_data), sizeof(card_data) * 3); - - card_data_index = 0; -} - -void EncoderHID_H10301::write_bit(bool bit, uint8_t position) { - write_raw_bit(bit, position + 0); - write_raw_bit(!bit, position + 1); -} - -void EncoderHID_H10301::write_raw_bit(bool bit, uint8_t position) { - if(bit) { - card_data[position / 32] |= 1UL << (31 - (position % 32)); - } else { - card_data[position / 32] &= ~(1UL << (31 - (position % 32))); - } -} - -void EncoderHID_H10301::get_next(bool* polarity, uint16_t* period, uint16_t* pulse) { - uint8_t bit = (card_data[card_data_index / 32] >> (31 - (card_data_index % 32))) & 1; - - bool advance = fsk->next(bit, period); - if(advance) { - card_data_index++; - if(card_data_index >= (32 * card_data_max)) { - card_data_index = 0; - } - } - - *polarity = true; - *pulse = *period / 2; -} - -EncoderHID_H10301::EncoderHID_H10301() { - fsk = new OscFSK(8, 10, 50); -} - -EncoderHID_H10301::~EncoderHID_H10301() { - delete fsk; -} diff --git a/applications/lfrfid/helpers/encoder_hid_h10301.h b/applications/lfrfid/helpers/encoder_hid_h10301.h deleted file mode 100644 index 8cc5aa5cbfa..00000000000 --- a/applications/lfrfid/helpers/encoder_hid_h10301.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once -#include "encoder_generic.h" -#include "osc_fsk.h" - -class EncoderHID_H10301 : public EncoderGeneric { -public: - /** - * @brief init data to emulate - * - * @param data 1 byte FC, next 2 byte SN - * @param data_size must be 3 - */ - void init(const uint8_t* data, const uint8_t data_size) final; - void get_next(bool* polarity, uint16_t* period, uint16_t* pulse) final; - EncoderHID_H10301(); - ~EncoderHID_H10301(); - -private: - static const uint8_t card_data_max = 3; - uint32_t card_data[card_data_max]; - uint8_t card_data_index; - void write_bit(bool bit, uint8_t position); - void write_raw_bit(bool bit, uint8_t position); - - OscFSK* fsk; -}; diff --git a/applications/lfrfid/helpers/encoder_indala_40134.cpp b/applications/lfrfid/helpers/encoder_indala_40134.cpp deleted file mode 100644 index 764237d1fee..00000000000 --- a/applications/lfrfid/helpers/encoder_indala_40134.cpp +++ /dev/null @@ -1,36 +0,0 @@ -#include "encoder_indala_40134.h" -#include "protocols/protocol_indala_40134.h" -#include - -void EncoderIndala_40134::init(const uint8_t* data, const uint8_t data_size) { - ProtocolIndala40134 indala; - indala.encode(data, data_size, reinterpret_cast(&card_data), sizeof(card_data)); - - last_bit = card_data & 1; - card_data_index = 0; - current_polarity = true; -} - -void EncoderIndala_40134::get_next(bool* polarity, uint16_t* period, uint16_t* pulse) { - *period = 2; - *pulse = 1; - *polarity = current_polarity; - - bit_clock_index++; - if(bit_clock_index >= clock_per_bit) { - bit_clock_index = 0; - - bool current_bit = (card_data >> (63 - card_data_index)) & 1; - - if(current_bit != last_bit) { - current_polarity = !current_polarity; - } - - last_bit = current_bit; - - card_data_index++; - if(card_data_index >= 64) { - card_data_index = 0; - } - } -} diff --git a/applications/lfrfid/helpers/encoder_indala_40134.h b/applications/lfrfid/helpers/encoder_indala_40134.h deleted file mode 100644 index eda29457f37..00000000000 --- a/applications/lfrfid/helpers/encoder_indala_40134.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once -#include "encoder_generic.h" - -class EncoderIndala_40134 : public EncoderGeneric { -public: - /** - * @brief init data to emulate - * - * @param data indala raw data - * @param data_size must be 5 - */ - void init(const uint8_t* data, const uint8_t data_size) final; - - void get_next(bool* polarity, uint16_t* period, uint16_t* pulse) final; - -private: - uint64_t card_data; - uint8_t card_data_index; - uint8_t bit_clock_index; - bool last_bit; - bool current_polarity; - static const uint8_t clock_per_bit = 16; -}; diff --git a/applications/lfrfid/helpers/encoder_ioprox.cpp b/applications/lfrfid/helpers/encoder_ioprox.cpp deleted file mode 100644 index 4177991863c..00000000000 --- a/applications/lfrfid/helpers/encoder_ioprox.cpp +++ /dev/null @@ -1,32 +0,0 @@ -#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 deleted file mode 100644 index 568b406711a..00000000000 --- a/applications/lfrfid/helpers/encoder_ioprox.h +++ /dev/null @@ -1,25 +0,0 @@ -#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 deleted file mode 100644 index 4803cd6dc25..00000000000 --- a/applications/lfrfid/helpers/key_info.cpp +++ /dev/null @@ -1,76 +0,0 @@ -#include "key_info.h" -#include - -const char* lfrfid_key_get_type_string(LfrfidKeyType type) { - switch(type) { - case LfrfidKeyType::KeyEM4100: - return "EM4100"; - break; - case LfrfidKeyType::KeyH10301: - return "H10301"; - break; - case LfrfidKeyType::KeyI40134: - return "I40134"; - break; - case LfrfidKeyType::KeyIoProxXSF: - return "IoProxXSF"; - break; - } - - return "Unknown"; -} - -const char* lfrfid_key_get_manufacturer_string(LfrfidKeyType type) { - switch(type) { - case LfrfidKeyType::KeyEM4100: - return "EM-Marin"; - break; - case LfrfidKeyType::KeyH10301: - return "HID"; - break; - case LfrfidKeyType::KeyI40134: - return "Indala"; - break; - case LfrfidKeyType::KeyIoProxXSF: - return "Kantech"; - } - - return "Unknown"; -} - -bool lfrfid_key_get_string_type(const char* string, LfrfidKeyType* type) { - bool result = true; - - if(strcmp("EM4100", string) == 0) { - *type = LfrfidKeyType::KeyEM4100; - } else if(strcmp("H10301", string) == 0) { - *type = LfrfidKeyType::KeyH10301; - } else if(strcmp("I40134", string) == 0) { - *type = LfrfidKeyType::KeyI40134; - } else if(strcmp("IoProxXSF", string) == 0) { - *type = LfrfidKeyType::KeyIoProxXSF; - } else { - result = false; - } - - return result; -} - -uint8_t lfrfid_key_get_type_data_count(LfrfidKeyType type) { - switch(type) { - case LfrfidKeyType::KeyEM4100: - return 5; - break; - case LfrfidKeyType::KeyH10301: - return 3; - break; - 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 deleted file mode 100644 index 75a0a94063f..00000000000 --- a/applications/lfrfid/helpers/key_info.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once -#include - -static const uint8_t LFRFID_KEY_SIZE = 8; -static const uint8_t LFRFID_KEY_NAME_SIZE = 22; - -enum class LfrfidKeyType : uint8_t { - KeyEM4100, - KeyH10301, - KeyI40134, - KeyIoProxXSF, -}; - -const char* lfrfid_key_get_type_string(LfrfidKeyType type); -const char* lfrfid_key_get_manufacturer_string(LfrfidKeyType type); -bool lfrfid_key_get_string_type(const char* string, LfrfidKeyType* type); -uint8_t lfrfid_key_get_type_data_count(LfrfidKeyType type); diff --git a/applications/lfrfid/helpers/osc_fsk.cpp b/applications/lfrfid/helpers/osc_fsk.cpp deleted file mode 100644 index 5f3b5367b7e..00000000000 --- a/applications/lfrfid/helpers/osc_fsk.cpp +++ /dev/null @@ -1,20 +0,0 @@ -#include "osc_fsk.h" - -OscFSK::OscFSK(uint16_t _freq_low, uint16_t _freq_hi, uint16_t _osc_phase_max) - : freq{_freq_low, _freq_hi} - , osc_phase_max(_osc_phase_max) { - osc_phase_current = 0; -} - -bool OscFSK::next(bool bit, uint16_t* period) { - bool advance = false; - *period = freq[bit]; - osc_phase_current += *period; - - if(osc_phase_current > osc_phase_max) { - advance = true; - osc_phase_current -= osc_phase_max; - } - - return advance; -} diff --git a/applications/lfrfid/helpers/osc_fsk.h b/applications/lfrfid/helpers/osc_fsk.h deleted file mode 100644 index eaaaa10ad0e..00000000000 --- a/applications/lfrfid/helpers/osc_fsk.h +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once -#include - -/** - * This code tries to fit the periods into a given number of cycles (phases) by taking cycles from the next cycle of periods. - */ -class OscFSK { -public: - /** - * Get next period - * @param bit bit value - * @param period return period - * @return bool whether to advance to the next bit - */ - bool next(bool bit, uint16_t* period); - - /** - * FSK ocillator constructor - * - * @param freq_low bit 0 freq - * @param freq_hi bit 1 freq - * @param osc_phase_max max oscillator phase - */ - OscFSK(uint16_t freq_low, uint16_t freq_hi, uint16_t osc_phase_max); - -private: - const uint16_t freq[2]; - const uint16_t osc_phase_max; - int32_t osc_phase_current; -}; diff --git a/applications/lfrfid/helpers/protocols/protocol_emmarin.cpp b/applications/lfrfid/helpers/protocols/protocol_emmarin.cpp deleted file mode 100644 index 4ac180e300b..00000000000 --- a/applications/lfrfid/helpers/protocols/protocol_emmarin.cpp +++ /dev/null @@ -1,150 +0,0 @@ -#include "protocol_emmarin.h" -#include - -#define EM_HEADER_POS 55 -#define EM_HEADER_MASK (0x1FFLLU << EM_HEADER_POS) - -#define EM_FIRST_ROW_POS 50 - -#define EM_ROW_COUNT 10 -#define EM_COLUMN_COUNT 4 -#define EM_BITS_PER_ROW_COUNT (EM_COLUMN_COUNT + 1) - -#define EM_COLUMN_POS 4 -#define EM_STOP_POS 0 -#define EM_STOP_MASK (0x1LLU << EM_STOP_POS) - -#define EM_HEADER_AND_STOP_MASK (EM_HEADER_MASK | EM_STOP_MASK) -#define EM_HEADER_AND_STOP_DATA (EM_HEADER_MASK) - -typedef uint64_t EMMarinCardData; - -void write_nibble(bool low_nibble, uint8_t data, EMMarinCardData* card_data) { - uint8_t parity_sum = 0; - uint8_t start = 0; - if(!low_nibble) start = 4; - - for(int8_t i = (start + 3); i >= start; i--) { - parity_sum += (data >> i) & 1; - *card_data = (*card_data << 1) | ((data >> i) & 1); - } - - *card_data = (*card_data << 1) | ((parity_sum % 2) & 1); -} - -uint8_t ProtocolEMMarin::get_encoded_data_size() { - return sizeof(EMMarinCardData); -} - -uint8_t ProtocolEMMarin::get_decoded_data_size() { - return 5; -} - -void ProtocolEMMarin::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()); - - EMMarinCardData card_data; - - // header - card_data = 0b111111111; - - // data - for(uint8_t i = 0; i < get_decoded_data_size(); i++) { - write_nibble(false, decoded_data[i], &card_data); - write_nibble(true, decoded_data[i], &card_data); - } - - // column parity and stop bit - uint8_t parity_sum; - - for(uint8_t c = 0; c < EM_COLUMN_COUNT; c++) { - parity_sum = 0; - for(uint8_t i = 1; i <= EM_ROW_COUNT; i++) { - uint8_t parity_bit = (card_data >> (i * EM_BITS_PER_ROW_COUNT - 1)) & 1; - parity_sum += parity_bit; - } - card_data = (card_data << 1) | ((parity_sum % 2) & 1); - } - - // stop bit - card_data = (card_data << 1) | 0; - - memcpy(encoded_data, &card_data, get_encoded_data_size()); -} - -void ProtocolEMMarin::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()); - - uint8_t decoded_data_index = 0; - EMMarinCardData card_data = *(reinterpret_cast(encoded_data)); - - // clean result - memset(decoded_data, 0, decoded_data_size); - - // header - for(uint8_t i = 0; i < 9; i++) { - card_data = card_data << 1; - } - - // nibbles - uint8_t value = 0; - for(uint8_t r = 0; r < EM_ROW_COUNT; r++) { - uint8_t nibble = 0; - for(uint8_t i = 0; i < 5; i++) { - if(i < 4) nibble = (nibble << 1) | (card_data & (1LLU << 63) ? 1 : 0); - card_data = card_data << 1; - } - value = (value << 4) | nibble; - if(r % 2) { - decoded_data[decoded_data_index] |= value; - decoded_data_index++; - value = 0; - } - } -} - -bool ProtocolEMMarin::can_be_decoded(const uint8_t* encoded_data, const uint8_t encoded_data_size) { - furi_check(encoded_data_size >= get_encoded_data_size()); - const EMMarinCardData* card_data = reinterpret_cast(encoded_data); - - // check header and stop bit - if((*card_data & EM_HEADER_AND_STOP_MASK) != EM_HEADER_AND_STOP_DATA) return false; - - // check row parity - for(uint8_t i = 0; i < EM_ROW_COUNT; i++) { - uint8_t parity_sum = 0; - - for(uint8_t j = 0; j < EM_BITS_PER_ROW_COUNT; j++) { - parity_sum += (*card_data >> (EM_FIRST_ROW_POS - i * EM_BITS_PER_ROW_COUNT + j)) & 1; - } - - if((parity_sum % 2)) { - return false; - } - } - - // check columns parity - for(uint8_t i = 0; i < EM_COLUMN_COUNT; i++) { - uint8_t parity_sum = 0; - - for(uint8_t j = 0; j < EM_ROW_COUNT + 1; j++) { - parity_sum += (*card_data >> (EM_COLUMN_POS - i + j * EM_BITS_PER_ROW_COUNT)) & 1; - } - - if((parity_sum % 2)) { - return false; - } - } - - return true; -} diff --git a/applications/lfrfid/helpers/protocols/protocol_emmarin.h b/applications/lfrfid/helpers/protocols/protocol_emmarin.h deleted file mode 100644 index 7a866f9097e..00000000000 --- a/applications/lfrfid/helpers/protocols/protocol_emmarin.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once -#include "protocol_generic.h" - -class ProtocolEMMarin : 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; -}; diff --git a/applications/lfrfid/helpers/protocols/protocol_generic.h b/applications/lfrfid/helpers/protocols/protocol_generic.h deleted file mode 100644 index d593f708995..00000000000 --- a/applications/lfrfid/helpers/protocols/protocol_generic.h +++ /dev/null @@ -1,60 +0,0 @@ -#pragma once -#include "stdint.h" -#include "stdbool.h" - -class ProtocolGeneric { -public: - /** - * @brief Get the encoded data size - * - * @return uint8_t size of encoded data in bytes - */ - virtual uint8_t get_encoded_data_size() = 0; - - /** - * @brief Get the decoded data size - * - * @return uint8_t size of decoded data in bytes - */ - virtual uint8_t get_decoded_data_size() = 0; - - /** - * @brief encode decoded data - * - * @param decoded_data - * @param decoded_data_size - * @param encoded_data - * @param encoded_data_size - */ - virtual void encode( - const uint8_t* decoded_data, - const uint8_t decoded_data_size, - uint8_t* encoded_data, - const uint8_t encoded_data_size) = 0; - - /** - * @brief decode encoded data - * - * @param encoded_data - * @param encoded_data_size - * @param decoded_data - * @param decoded_data_size - */ - virtual void decode( - const uint8_t* encoded_data, - const uint8_t encoded_data_size, - uint8_t* decoded_data, - const uint8_t decoded_data_size) = 0; - - /** - * @brief fast check that data can be correctly decoded - * - * @param encoded_data - * @param encoded_data_size - * @return true - can be correctly decoded - * @return false - cannot be correctly decoded - */ - virtual bool can_be_decoded(const uint8_t* encoded_data, const uint8_t encoded_data_size) = 0; - - virtual ~ProtocolGeneric(){}; -}; diff --git a/applications/lfrfid/helpers/protocols/protocol_hid_h10301.cpp b/applications/lfrfid/helpers/protocols/protocol_hid_h10301.cpp deleted file mode 100644 index b718388388d..00000000000 --- a/applications/lfrfid/helpers/protocols/protocol_hid_h10301.cpp +++ /dev/null @@ -1,238 +0,0 @@ -#include "protocol_hid_h10301.h" -#include - -typedef uint32_t HID10301CardData; -constexpr uint8_t HID10301Count = 3; -constexpr uint8_t HID10301BitSize = sizeof(HID10301CardData) * 8; - -static void write_raw_bit(bool bit, uint8_t position, HID10301CardData* card_data) { - if(bit) { - card_data[position / HID10301BitSize] |= - 1UL << (HID10301BitSize - (position % HID10301BitSize) - 1); - } else { - card_data[position / (sizeof(HID10301CardData) * 8)] &= - ~(1UL << (HID10301BitSize - (position % HID10301BitSize) - 1)); - } -} - -static void write_bit(bool bit, uint8_t position, HID10301CardData* card_data) { - write_raw_bit(bit, position + 0, card_data); - write_raw_bit(!bit, position + 1, card_data); -} - -uint8_t ProtocolHID10301::get_encoded_data_size() { - return sizeof(HID10301CardData) * HID10301Count; -} - -uint8_t ProtocolHID10301::get_decoded_data_size() { - return 3; -} - -void ProtocolHID10301::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()); - - HID10301CardData card_data[HID10301Count] = {0, 0, 0}; - - uint32_t fc_cn = (decoded_data[0] << 16) | (decoded_data[1] << 8) | decoded_data[2]; - - // even parity sum calculation (high 12 bits of data) - uint8_t even_parity_sum = 0; - for(int8_t i = 12; i < 24; i++) { - if(((fc_cn >> i) & 1) == 1) { - even_parity_sum++; - } - } - - // odd parity sum calculation (low 12 bits of data) - uint8_t odd_parity_sum = 1; - for(int8_t i = 0; i < 12; i++) { - if(((fc_cn >> i) & 1) == 1) { - odd_parity_sum++; - } - } - - // 0x1D preamble - write_raw_bit(0, 0, card_data); - write_raw_bit(0, 1, card_data); - write_raw_bit(0, 2, card_data); - write_raw_bit(1, 3, card_data); - write_raw_bit(1, 4, card_data); - write_raw_bit(1, 5, card_data); - write_raw_bit(0, 6, card_data); - write_raw_bit(1, 7, card_data); - - // company / OEM code 1 - write_bit(0, 8, card_data); - write_bit(0, 10, card_data); - write_bit(0, 12, card_data); - write_bit(0, 14, card_data); - write_bit(0, 16, card_data); - write_bit(0, 18, card_data); - write_bit(1, 20, card_data); - - // card format / length 1 - write_bit(0, 22, card_data); - write_bit(0, 24, card_data); - write_bit(0, 26, card_data); - write_bit(0, 28, card_data); - write_bit(0, 30, card_data); - write_bit(0, 32, card_data); - write_bit(0, 34, card_data); - write_bit(0, 36, card_data); - write_bit(0, 38, card_data); - write_bit(0, 40, card_data); - write_bit(1, 42, card_data); - - // even parity bit - write_bit((even_parity_sum % 2), 44, card_data); - - // data - for(uint8_t i = 0; i < 24; i++) { - write_bit((fc_cn >> (23 - i)) & 1, 46 + (i * 2), card_data); - } - - // odd parity bit - write_bit((odd_parity_sum % 2), 94, card_data); - - memcpy(encoded_data, &card_data, get_encoded_data_size()); -} - -void ProtocolHID10301::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()); - - const HID10301CardData* card_data = reinterpret_cast(encoded_data); - - // data decoding - uint32_t result = 0; - - // decode from word 1 - // coded with 01 = 0, 10 = 1 transitions - for(int8_t i = 9; i >= 0; i--) { - switch((*(card_data + 1) >> (2 * i)) & 0b11) { - case 0b01: - result = (result << 1) | 0; - break; - case 0b10: - result = (result << 1) | 1; - break; - default: - break; - } - } - - // decode from word 2 - // coded with 01 = 0, 10 = 1 transitions - for(int8_t i = 15; i >= 0; i--) { - switch((*(card_data + 2) >> (2 * i)) & 0b11) { - case 0b01: - result = (result << 1) | 0; - break; - case 0b10: - result = (result << 1) | 1; - break; - default: - break; - } - } - - uint8_t data[3] = {(uint8_t)(result >> 17), (uint8_t)(result >> 9), (uint8_t)(result >> 1)}; - - memcpy(decoded_data, &data, get_decoded_data_size()); -} - -bool ProtocolHID10301::can_be_decoded(const uint8_t* encoded_data, const uint8_t encoded_data_size) { - furi_check(encoded_data_size >= get_encoded_data_size()); - - const HID10301CardData* card_data = reinterpret_cast(encoded_data); - - // packet preamble - // raw data - if(*(encoded_data + 3) != 0x1D) { - return false; - } - - // encoded company/oem - // coded with 01 = 0, 10 = 1 transitions - // stored in word 0 - if((*card_data >> 10 & 0x3FFF) != 0x1556) { - return false; - } - - // encoded format/length - // coded with 01 = 0, 10 = 1 transitions - // stored in word 0 and word 1 - if((((*card_data & 0x3FF) << 12) | ((*(card_data + 1) >> 20) & 0xFFF)) != 0x155556) { - return false; - } - - // data decoding - uint32_t result = 0; - - // decode from word 1 - // coded with 01 = 0, 10 = 1 transitions - for(int8_t i = 9; i >= 0; i--) { - switch((*(card_data + 1) >> (2 * i)) & 0b11) { - case 0b01: - result = (result << 1) | 0; - break; - case 0b10: - result = (result << 1) | 1; - break; - default: - return false; - break; - } - } - - // decode from word 2 - // coded with 01 = 0, 10 = 1 transitions - for(int8_t i = 15; i >= 0; i--) { - switch((*(card_data + 2) >> (2 * i)) & 0b11) { - case 0b01: - result = (result << 1) | 0; - break; - case 0b10: - result = (result << 1) | 1; - break; - default: - return false; - break; - } - } - - // trailing parity (odd) test - uint8_t parity_sum = 0; - for(int8_t i = 0; i < 13; i++) { - if(((result >> i) & 1) == 1) { - parity_sum++; - } - } - - if((parity_sum % 2) != 1) { - return false; - } - - // leading parity (even) test - parity_sum = 0; - for(int8_t i = 13; i < 26; i++) { - if(((result >> i) & 1) == 1) { - parity_sum++; - } - } - - if((parity_sum % 2) == 1) { - return false; - } - - return true; -} diff --git a/applications/lfrfid/helpers/protocols/protocol_hid_h10301.h b/applications/lfrfid/helpers/protocols/protocol_hid_h10301.h deleted file mode 100644 index fbd6e0b2bf9..00000000000 --- a/applications/lfrfid/helpers/protocols/protocol_hid_h10301.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once -#include "protocol_generic.h" - -class ProtocolHID10301 : 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; -}; diff --git a/applications/lfrfid/helpers/protocols/protocol_indala_40134.cpp b/applications/lfrfid/helpers/protocols/protocol_indala_40134.cpp deleted file mode 100644 index 482339def33..00000000000 --- a/applications/lfrfid/helpers/protocols/protocol_indala_40134.cpp +++ /dev/null @@ -1,237 +0,0 @@ -#include "protocol_indala_40134.h" -#include - -typedef uint64_t Indala40134CardData; - -static void set_bit(bool bit, uint8_t position, Indala40134CardData* card_data) { - position = (sizeof(Indala40134CardData) * 8) - 1 - position; - if(bit) { - *card_data |= 1ull << position; - } else { - *card_data &= ~(1ull << position); - } -} - -static bool get_bit(uint8_t position, const Indala40134CardData* card_data) { - position = (sizeof(Indala40134CardData) * 8) - 1 - position; - return (*card_data >> position) & 1; -} - -uint8_t ProtocolIndala40134::get_encoded_data_size() { - return sizeof(Indala40134CardData); -} - -uint8_t ProtocolIndala40134::get_decoded_data_size() { - return 3; -} - -void ProtocolIndala40134::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()); - - uint32_t fc_and_card = (decoded_data[0] << 16) | (decoded_data[1] << 8) | decoded_data[2]; - Indala40134CardData card_data = 0; - - // preamble - set_bit(1, 0, &card_data); - set_bit(1, 2, &card_data); - set_bit(1, 32, &card_data); - - // factory code - set_bit(((fc_and_card >> 23) & 1), 57, &card_data); - set_bit(((fc_and_card >> 22) & 1), 49, &card_data); - set_bit(((fc_and_card >> 21) & 1), 44, &card_data); - set_bit(((fc_and_card >> 20) & 1), 47, &card_data); - set_bit(((fc_and_card >> 19) & 1), 48, &card_data); - set_bit(((fc_and_card >> 18) & 1), 53, &card_data); - set_bit(((fc_and_card >> 17) & 1), 39, &card_data); - set_bit(((fc_and_card >> 16) & 1), 58, &card_data); - - // card number - set_bit(((fc_and_card >> 15) & 1), 42, &card_data); - set_bit(((fc_and_card >> 14) & 1), 45, &card_data); - set_bit(((fc_and_card >> 13) & 1), 43, &card_data); - set_bit(((fc_and_card >> 12) & 1), 40, &card_data); - set_bit(((fc_and_card >> 11) & 1), 52, &card_data); - set_bit(((fc_and_card >> 10) & 1), 36, &card_data); - set_bit(((fc_and_card >> 9) & 1), 35, &card_data); - set_bit(((fc_and_card >> 8) & 1), 51, &card_data); - set_bit(((fc_and_card >> 7) & 1), 46, &card_data); - set_bit(((fc_and_card >> 6) & 1), 33, &card_data); - set_bit(((fc_and_card >> 5) & 1), 37, &card_data); - set_bit(((fc_and_card >> 4) & 1), 54, &card_data); - set_bit(((fc_and_card >> 3) & 1), 56, &card_data); - set_bit(((fc_and_card >> 2) & 1), 59, &card_data); - set_bit(((fc_and_card >> 1) & 1), 50, &card_data); - set_bit(((fc_and_card >> 0) & 1), 41, &card_data); - - // checksum - uint8_t checksum = 0; - checksum += ((fc_and_card >> 14) & 1); - checksum += ((fc_and_card >> 12) & 1); - checksum += ((fc_and_card >> 9) & 1); - checksum += ((fc_and_card >> 8) & 1); - checksum += ((fc_and_card >> 6) & 1); - checksum += ((fc_and_card >> 5) & 1); - checksum += ((fc_and_card >> 2) & 1); - checksum += ((fc_and_card >> 0) & 1); - - // wiegand parity bits - // even parity sum calculation (high 12 bits of data) - uint8_t even_parity_sum = 0; - for(int8_t i = 12; i < 24; i++) { - if(((fc_and_card >> i) & 1) == 1) { - even_parity_sum++; - } - } - - // odd parity sum calculation (low 12 bits of data) - uint8_t odd_parity_sum = 1; - for(int8_t i = 0; i < 12; i++) { - if(((fc_and_card >> i) & 1) == 1) { - odd_parity_sum++; - } - } - - // even parity bit - set_bit((even_parity_sum % 2), 34, &card_data); - - // odd parity bit - set_bit((odd_parity_sum % 2), 38, &card_data); - - // checksum - if((checksum & 1) == 1) { - set_bit(0, 62, &card_data); - set_bit(1, 63, &card_data); - } else { - set_bit(1, 62, &card_data); - set_bit(0, 63, &card_data); - } - - memcpy(encoded_data, &card_data, get_encoded_data_size()); -} - -// factory code -static uint8_t get_fc(const Indala40134CardData* card_data) { - uint8_t fc = 0; - - fc = fc << 1 | get_bit(57, card_data); - fc = fc << 1 | get_bit(49, card_data); - fc = fc << 1 | get_bit(44, card_data); - fc = fc << 1 | get_bit(47, card_data); - fc = fc << 1 | get_bit(48, card_data); - fc = fc << 1 | get_bit(53, card_data); - fc = fc << 1 | get_bit(39, card_data); - fc = fc << 1 | get_bit(58, card_data); - - return fc; -} - -// card number -static uint16_t get_cn(const Indala40134CardData* card_data) { - uint16_t cn = 0; - - cn = cn << 1 | get_bit(42, card_data); - cn = cn << 1 | get_bit(45, card_data); - cn = cn << 1 | get_bit(43, card_data); - cn = cn << 1 | get_bit(40, card_data); - cn = cn << 1 | get_bit(52, card_data); - cn = cn << 1 | get_bit(36, card_data); - cn = cn << 1 | get_bit(35, card_data); - cn = cn << 1 | get_bit(51, card_data); - cn = cn << 1 | get_bit(46, card_data); - cn = cn << 1 | get_bit(33, card_data); - cn = cn << 1 | get_bit(37, card_data); - cn = cn << 1 | get_bit(54, card_data); - cn = cn << 1 | get_bit(56, card_data); - cn = cn << 1 | get_bit(59, card_data); - cn = cn << 1 | get_bit(50, card_data); - cn = cn << 1 | get_bit(41, card_data); - - return cn; -} - -void ProtocolIndala40134::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()); - - const Indala40134CardData* card_data = - reinterpret_cast(encoded_data); - - uint8_t fc = get_fc(card_data); - uint16_t card = get_cn(card_data); - - decoded_data[0] = fc; - decoded_data[1] = card >> 8; - decoded_data[2] = card; -} - -bool ProtocolIndala40134::can_be_decoded( - const uint8_t* encoded_data, - const uint8_t encoded_data_size) { - furi_check(encoded_data_size >= get_encoded_data_size()); - bool can_be_decoded = false; - - const Indala40134CardData* card_data = - reinterpret_cast(encoded_data); - - do { - // preambula - if((*card_data >> 32) != 0xa0000000UL) break; - - // data - const uint32_t fc_and_card = get_fc(card_data) << 16 | get_cn(card_data); - - // checksum - const uint8_t checksum = get_bit(62, card_data) << 1 | get_bit(63, card_data); - uint8_t checksum_sum = 0; - checksum_sum += ((fc_and_card >> 14) & 1); - checksum_sum += ((fc_and_card >> 12) & 1); - checksum_sum += ((fc_and_card >> 9) & 1); - checksum_sum += ((fc_and_card >> 8) & 1); - checksum_sum += ((fc_and_card >> 6) & 1); - checksum_sum += ((fc_and_card >> 5) & 1); - checksum_sum += ((fc_and_card >> 2) & 1); - checksum_sum += ((fc_and_card >> 0) & 1); - checksum_sum = checksum_sum & 0b1; - - if(checksum_sum == 1 && checksum == 0b01) { - } else if(checksum_sum == 0 && checksum == 0b10) { - } else { - break; - } - - // wiegand parity bits - // even parity sum calculation (high 12 bits of data) - const bool even_parity = get_bit(34, card_data); - uint8_t even_parity_sum = 0; - for(int8_t i = 12; i < 24; i++) { - if(((fc_and_card >> i) & 1) == 1) { - even_parity_sum++; - } - } - if(even_parity_sum % 2 != even_parity) break; - - // odd parity sum calculation (low 12 bits of data) - const bool odd_parity = get_bit(38, card_data); - uint8_t odd_parity_sum = 1; - for(int8_t i = 0; i < 12; i++) { - if(((fc_and_card >> i) & 1) == 1) { - odd_parity_sum++; - } - } - if(odd_parity_sum % 2 != odd_parity) break; - - can_be_decoded = true; - } while(false); - - return can_be_decoded; -} diff --git a/applications/lfrfid/helpers/protocols/protocol_indala_40134.h b/applications/lfrfid/helpers/protocols/protocol_indala_40134.h deleted file mode 100644 index d378bb2ceef..00000000000 --- a/applications/lfrfid/helpers/protocols/protocol_indala_40134.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once -#include "protocol_generic.h" - -class ProtocolIndala40134 : 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; -}; diff --git a/applications/lfrfid/helpers/protocols/protocol_ioprox.cpp b/applications/lfrfid/helpers/protocols/protocol_ioprox.cpp deleted file mode 100644 index b3b6a0e598e..00000000000 --- a/applications/lfrfid/helpers/protocols/protocol_ioprox.cpp +++ /dev/null @@ -1,193 +0,0 @@ -#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 deleted file mode 100644 index 2fff53b171d..00000000000 --- a/applications/lfrfid/helpers/protocols/protocol_ioprox.h +++ /dev/null @@ -1,26 +0,0 @@ -#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/pulse_joiner.cpp b/applications/lfrfid/helpers/pulse_joiner.cpp deleted file mode 100644 index c72019b1438..00000000000 --- a/applications/lfrfid/helpers/pulse_joiner.cpp +++ /dev/null @@ -1,95 +0,0 @@ -#include "pulse_joiner.h" -#include - -bool PulseJoiner::push_pulse(bool polarity, uint16_t period, uint16_t pulse) { - bool result = false; - furi_check((pulse_index + 1) < pulse_max); - - if(polarity == false && pulse_index == 0) { - // first negative pulse is ommited - - } else { - pulses[pulse_index].polarity = polarity; - pulses[pulse_index].time = pulse; - pulse_index++; - } - - if(period > pulse) { - pulses[pulse_index].polarity = !polarity; - pulses[pulse_index].time = period - pulse; - pulse_index++; - } - - if(pulse_index >= 4) { - // we know that first pulse is always high - // so we wait 2 edges, hi2low and next low2hi - - uint8_t edges_count = 0; - bool last_polarity = pulses[0].polarity; - - for(uint8_t i = 1; i < pulse_index; i++) { - if(pulses[i].polarity != last_polarity) { - edges_count++; - last_polarity = pulses[i].polarity; - } - } - - if(edges_count >= 2) { - result = true; - } - } - - return result; -} - -void PulseJoiner::pop_pulse(uint16_t* period, uint16_t* pulse) { - furi_check(pulse_index <= (pulse_max + 1)); - - uint16_t tmp_period = 0; - uint16_t tmp_pulse = 0; - uint8_t edges_count = 0; - bool last_polarity = pulses[0].polarity; - uint8_t next_fist_pulse = 0; - - for(uint8_t i = 0; i < pulse_max; i++) { - // count edges - if(pulses[i].polarity != last_polarity) { - edges_count++; - last_polarity = pulses[i].polarity; - } - - // wait for 2 edges - if(edges_count == 2) { - next_fist_pulse = i; - break; - } - - // sum pulse time - if(pulses[i].polarity) { - tmp_period += pulses[i].time; - tmp_pulse += pulses[i].time; - } else { - tmp_period += pulses[i].time; - } - pulse_index--; - } - - *period = tmp_period; - *pulse = tmp_pulse; - - // remove counted periods and shift data - for(uint8_t i = 0; i < pulse_max; i++) { - if((next_fist_pulse + i) < pulse_max) { - pulses[i].polarity = pulses[next_fist_pulse + i].polarity; - pulses[i].time = pulses[next_fist_pulse + i].time; - } else { - break; - } - } -} - -PulseJoiner::PulseJoiner() { - for(uint8_t i = 0; i < pulse_max; i++) { - pulses[i] = {false, 0}; - } -} diff --git a/applications/lfrfid/helpers/pulse_joiner.h b/applications/lfrfid/helpers/pulse_joiner.h deleted file mode 100644 index 1639d837167..00000000000 --- a/applications/lfrfid/helpers/pulse_joiner.h +++ /dev/null @@ -1,36 +0,0 @@ -#pragma once -#include "stdint.h" - -class PulseJoiner { -public: - /** - * @brief Push timer pulse. First negative pulse is ommited. - * - * @param polarity pulse polarity: true = high2low, false = low2high - * @param period overall period time in timer clicks - * @param pulse pulse time in timer clicks - * - * @return true - next pulse can and must be popped immediatly - */ - bool push_pulse(bool polarity, uint16_t period, uint16_t pulse); - - /** - * @brief Get the next timer pulse. Call only if push_pulse returns true. - * - * @param period overall period time in timer clicks - * @param pulse pulse time in timer clicks - */ - void pop_pulse(uint16_t* period, uint16_t* pulse); - - PulseJoiner(); - -private: - struct Pulse { - bool polarity; - uint16_t time; - }; - - uint8_t pulse_index = 0; - static const uint8_t pulse_max = 6; - Pulse pulses[pulse_max]; -}; diff --git a/applications/lfrfid/helpers/rfid_key.cpp b/applications/lfrfid/helpers/rfid_key.cpp deleted file mode 100644 index 2d99d40f26e..00000000000 --- a/applications/lfrfid/helpers/rfid_key.cpp +++ /dev/null @@ -1,65 +0,0 @@ -#include "rfid_key.h" -#include -#include - -RfidKey::RfidKey() { - clear(); -} - -RfidKey::~RfidKey() { -} - -void RfidKey::set_type(LfrfidKeyType _type) { - type = _type; -} - -void RfidKey::set_data(const uint8_t* _data, const uint8_t _data_size) { - furi_assert(_data_size <= data.size()); - for(uint8_t i = 0; i < _data_size; i++) { - data[i] = _data[i]; - } -} - -void RfidKey::set_name(const char* _name) { - strlcpy(name, _name, get_name_length()); -} - -LfrfidKeyType RfidKey::get_type() { - return type; -} - -const uint8_t* RfidKey::get_data() { - return &data[0]; -} - -const char* RfidKey::get_type_text() { - return lfrfid_key_get_type_string(type); -} - -uint8_t RfidKey::get_type_data_count() const { - return lfrfid_key_get_type_data_count(type); -} - -char* RfidKey::get_name() { - return name; -} - -uint8_t RfidKey::get_name_length() { - return LFRFID_KEY_NAME_SIZE; -} - -void RfidKey::clear() { - set_name(""); - set_type(LfrfidKeyType::KeyEM4100); - data.fill(0); -} - -RfidKey& RfidKey::operator=(const RfidKey& rhs) { - if(this == &rhs) return *this; - - set_type(rhs.type); - set_name(rhs.name); - set_data(&rhs.data[0], get_type_data_count()); - - return *this; -} diff --git a/applications/lfrfid/helpers/rfid_key.h b/applications/lfrfid/helpers/rfid_key.h deleted file mode 100644 index 29b87cf9c0b..00000000000 --- a/applications/lfrfid/helpers/rfid_key.h +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once -#include "key_info.h" -#include - -class RfidKey { -public: - RfidKey(); - ~RfidKey(); - - void set_type(LfrfidKeyType type); - void set_data(const uint8_t* data, const uint8_t data_size); - void set_name(const char* name); - - LfrfidKeyType get_type(); - const uint8_t* get_data(); - const char* get_type_text(); - uint8_t get_type_data_count() const; - char* get_name(); - uint8_t get_name_length(); - void clear(); - RfidKey& operator=(const RfidKey& rhs); - -private: - std::array data; - LfrfidKeyType type; - char name[LFRFID_KEY_NAME_SIZE + 1]; -}; diff --git a/applications/lfrfid/helpers/rfid_reader.cpp b/applications/lfrfid/helpers/rfid_reader.cpp deleted file mode 100644 index 029b1cb4bd3..00000000000 --- a/applications/lfrfid/helpers/rfid_reader.cpp +++ /dev/null @@ -1,175 +0,0 @@ -#include "rfid_reader.h" -#include -#include -#include - -/** - * @brief private violation assistant for RfidReader - */ -struct RfidReaderAccessor { - static void decode(RfidReader& rfid_reader, bool polarity) { - rfid_reader.decode(polarity); - } -}; - -void RfidReader::decode(bool polarity) { - uint32_t current_dwt_value = DWT->CYCCNT; - uint32_t period = current_dwt_value - last_dwt_value; - last_dwt_value = current_dwt_value; - -#ifdef RFID_GPIO_DEBUG - decoder_gpio_out.process_front(polarity, period); -#endif - - switch(type) { - 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; - } - - detect_ticks++; -} - -bool RfidReader::switch_timer_elapsed() { - const uint32_t seconds_to_switch = furi_kernel_get_tick_frequency() * 2.0f; - return (furi_get_tick() - switch_os_tick_last) > seconds_to_switch; -} - -void RfidReader::switch_timer_reset() { - switch_os_tick_last = furi_get_tick(); -} - -void RfidReader::switch_mode() { - switch(type) { - case Type::Normal: - type = Type::Indala; - furi_hal_rfid_change_read_config(62500.0f, 0.25f); - break; - case Type::Indala: - type = Type::Normal; - furi_hal_rfid_change_read_config(125000.0f, 0.5f); - break; - } - - switch_timer_reset(); -} - -static void comparator_trigger_callback(bool level, void* comp_ctx) { - RfidReader* _this = static_cast(comp_ctx); - - RfidReaderAccessor::decode(*_this, !level); -} - -RfidReader::RfidReader() { -} - -void RfidReader::start() { - type = Type::Normal; - - furi_hal_rfid_pins_read(); - furi_hal_rfid_tim_read(125000, 0.5); - furi_hal_rfid_tim_read_start(); - start_comparator(); - - switch_timer_reset(); - last_read_count = 0; -} - -void RfidReader::start_forced(RfidReader::Type _type) { - start(); - if(_type == Type::Indala) { - switch_mode(); - } -} - -void RfidReader::stop() { - furi_hal_rfid_pins_reset(); - furi_hal_rfid_tim_read_stop(); - furi_hal_rfid_tim_reset(); - stop_comparator(); -} - -bool RfidReader::read(LfrfidKeyType* _type, uint8_t* data, uint8_t data_size, bool switch_enable) { - bool result = false; - bool something_read = false; - - // reading - if(decoder_em.read(data, data_size)) { - *_type = LfrfidKeyType::KeyEM4100; - something_read = true; - } - - if(decoder_hid26.read(data, data_size)) { - *_type = LfrfidKeyType::KeyH10301; - 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; - } - - // validation - if(something_read) { - switch_timer_reset(); - - if(last_read_type == *_type && memcmp(last_read_data, data, data_size) == 0) { - last_read_count = last_read_count + 1; - - if(last_read_count > 2) { - result = true; - } - } else { - last_read_type = *_type; - memcpy(last_read_data, data, data_size); - last_read_count = 0; - } - } - - // mode switching - if(switch_enable && switch_timer_elapsed()) { - switch_mode(); - last_read_count = 0; - } - - return result; -} - -bool RfidReader::detect() { - bool detected = false; - if(detect_ticks > 10) { - detected = true; - } - detect_ticks = 0; - - return detected; -} - -bool RfidReader::any_read() { - return last_read_count > 0; -} - -void RfidReader::start_comparator(void) { - furi_hal_rfid_comp_set_callback(comparator_trigger_callback, this); - last_dwt_value = DWT->CYCCNT; - - furi_hal_rfid_comp_start(); -} - -void RfidReader::stop_comparator(void) { - furi_hal_rfid_comp_stop(); - furi_hal_rfid_comp_set_callback(NULL, NULL); -} diff --git a/applications/lfrfid/helpers/rfid_reader.h b/applications/lfrfid/helpers/rfid_reader.h deleted file mode 100644 index 903bbecf93e..00000000000 --- a/applications/lfrfid/helpers/rfid_reader.h +++ /dev/null @@ -1,59 +0,0 @@ -#pragma once -//#include "decoder_analyzer.h" -#include "decoder_gpio_out.h" -#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 - -class RfidReader { -public: - enum class Type : uint8_t { - Normal, - Indala, - }; - - RfidReader(); - void start(); - void start_forced(RfidReader::Type type); - void stop(); - bool read(LfrfidKeyType* type, uint8_t* data, uint8_t data_size, bool switch_enable = true); - - bool detect(); - bool any_read(); - -private: - friend struct RfidReaderAccessor; - - //DecoderAnalyzer decoder_analyzer; -#ifdef RFID_GPIO_DEBUG - DecoderGpioOut decoder_gpio_out; -#endif - DecoderEMMarin decoder_em; - DecoderHID26 decoder_hid26; - DecoderIndala decoder_indala; - DecoderIoProx decoder_ioprox; - - uint32_t last_dwt_value; - - void start_comparator(void); - void stop_comparator(void); - - void decode(bool polarity); - - uint32_t detect_ticks; - - uint32_t switch_os_tick_last; - bool switch_timer_elapsed(); - void switch_timer_reset(); - void switch_mode(); - - LfrfidKeyType last_read_type; - uint8_t last_read_data[LFRFID_KEY_SIZE]; - uint8_t last_read_count; - - Type type = Type::Normal; -}; diff --git a/applications/lfrfid/helpers/rfid_timer_emulator.cpp b/applications/lfrfid/helpers/rfid_timer_emulator.cpp deleted file mode 100644 index f5337c31dd9..00000000000 --- a/applications/lfrfid/helpers/rfid_timer_emulator.cpp +++ /dev/null @@ -1,56 +0,0 @@ -#include "rfid_timer_emulator.h" - -RfidTimerEmulator::RfidTimerEmulator() { -} - -RfidTimerEmulator::~RfidTimerEmulator() { - std::map::iterator it; - - for(it = encoders.begin(); it != encoders.end(); ++it) { - delete it->second; - } - - encoders.clear(); -} - -void RfidTimerEmulator::start(LfrfidKeyType type, const uint8_t* data, uint8_t data_size) { - if(encoders.count(type)) { - current_encoder = encoders.find(type)->second; - - if(data_size >= lfrfid_key_get_type_data_count(type)) { - current_encoder->init(data, data_size); - - furi_hal_rfid_tim_emulate(125000); - furi_hal_rfid_pins_emulate(); - - furi_hal_rfid_tim_emulate_start(RfidTimerEmulator::timer_update_callback, this); - } - } else { - // not found - } -} - -void RfidTimerEmulator::stop() { - furi_hal_rfid_tim_emulate_stop(); - furi_hal_rfid_tim_reset(); - furi_hal_rfid_pins_reset(); -} - -void RfidTimerEmulator::timer_update_callback(void* ctx) { - RfidTimerEmulator* _this = static_cast(ctx); - - bool result; - bool polarity; - uint16_t period; - uint16_t pulse; - - do { - _this->current_encoder->get_next(&polarity, &period, &pulse); - result = _this->pulse_joiner.push_pulse(polarity, period, pulse); - } while(result == false); - - _this->pulse_joiner.pop_pulse(&period, &pulse); - - furi_hal_rfid_set_emulate_period(period - 1); - furi_hal_rfid_set_emulate_pulse(pulse); -} diff --git a/applications/lfrfid/helpers/rfid_timer_emulator.h b/applications/lfrfid/helpers/rfid_timer_emulator.h deleted file mode 100644 index 2129a1c7f86..00000000000 --- a/applications/lfrfid/helpers/rfid_timer_emulator.h +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once -#include -#include "key_info.h" -#include "encoder_generic.h" -#include "encoder_emmarin.h" -#include "encoder_hid_h10301.h" -#include "encoder_indala_40134.h" -#include "encoder_ioprox.h" -#include "pulse_joiner.h" -#include - -class RfidTimerEmulator { -public: - RfidTimerEmulator(); - ~RfidTimerEmulator(); - void start(LfrfidKeyType type, const uint8_t* data, uint8_t data_size); - void stop(); - -private: - EncoderGeneric* current_encoder = nullptr; - - std::map encoders = { - {LfrfidKeyType::KeyEM4100, new EncoderEM()}, - {LfrfidKeyType::KeyH10301, new EncoderHID_H10301()}, - {LfrfidKeyType::KeyI40134, new EncoderIndala_40134()}, - {LfrfidKeyType::KeyIoProxXSF, new EncoderIoProx()}, - }; - - PulseJoiner pulse_joiner; - static void timer_update_callback(void* ctx); -}; diff --git a/applications/lfrfid/helpers/rfid_worker.cpp b/applications/lfrfid/helpers/rfid_worker.cpp deleted file mode 100644 index af15a340fba..00000000000 --- a/applications/lfrfid/helpers/rfid_worker.cpp +++ /dev/null @@ -1,136 +0,0 @@ -#include "rfid_worker.h" - -RfidWorker::RfidWorker() { -} - -RfidWorker::~RfidWorker() { -} - -void RfidWorker::start_read() { - reader.start(); -} - -bool RfidWorker::read() { - static const uint8_t data_size = LFRFID_KEY_SIZE; - uint8_t data[data_size] = {0}; - LfrfidKeyType type; - - bool result = reader.read(&type, data, data_size); - - if(result) { - key.set_type(type); - key.set_data(data, data_size); - }; - - return result; -} - -bool RfidWorker::detect() { - return reader.detect(); -} - -bool RfidWorker::any_read() { - return reader.any_read(); -} - -void RfidWorker::stop_read() { - reader.stop(); -} - -void RfidWorker::start_write() { - write_result = WriteResult::Nothing; - write_sequence = new TickSequencer(); - validate_counts = 0; - - write_sequence->do_every_tick(1, std::bind(&RfidWorker::sq_write, this)); - write_sequence->do_after_tick(2, std::bind(&RfidWorker::sq_write_start_validate, this)); - write_sequence->do_every_tick(30, std::bind(&RfidWorker::sq_write_validate, this)); - write_sequence->do_every_tick(1, std::bind(&RfidWorker::sq_write_stop_validate, this)); -} - -RfidWorker::WriteResult RfidWorker::write() { - write_sequence->tick(); - return write_result; -} - -void RfidWorker::stop_write() { - delete write_sequence; - reader.stop(); -} - -void RfidWorker::start_emulate() { - emulator.start(key.get_type(), key.get_data(), key.get_type_data_count()); -} - -void RfidWorker::stop_emulate() { - emulator.stop(); -} - -void RfidWorker::sq_write() { - for(size_t i = 0; i < 5; i++) { - switch(key.get_type()) { - case LfrfidKeyType::KeyEM4100: - writer.start(); - writer.write_em(key.get_data()); - writer.stop(); - break; - case LfrfidKeyType::KeyH10301: - writer.start(); - writer.write_hid(key.get_data()); - writer.stop(); - break; - case LfrfidKeyType::KeyI40134: - writer.start(); - writer.write_indala(key.get_data()); - writer.stop(); - break; - case LfrfidKeyType::KeyIoProxXSF: - writer.start(); - writer.write_ioprox(key.get_data()); - writer.stop(); - break; - } - } -} - -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: - reader.start_forced(RfidReader::Type::Indala); - break; - } -} - -void RfidWorker::sq_write_validate() { - static const uint8_t data_size = LFRFID_KEY_SIZE; - uint8_t data[data_size] = {0}; - LfrfidKeyType type; - - bool result = reader.read(&type, data, data_size); - - if(result && (write_result != WriteResult::Ok)) { - if(validate_counts > (5 * 60)) { - write_result = WriteResult::NotWritable; - } - - if(type == key.get_type()) { - if(memcmp(data, key.get_data(), key.get_type_data_count()) == 0) { - write_result = WriteResult::Ok; - validate_counts = 0; - } else { - validate_counts++; - } - } else { - validate_counts++; - } - }; -} - -void RfidWorker::sq_write_stop_validate() { - reader.stop(); -} diff --git a/applications/lfrfid/helpers/rfid_worker.h b/applications/lfrfid/helpers/rfid_worker.h deleted file mode 100644 index 2c49ad14e06..00000000000 --- a/applications/lfrfid/helpers/rfid_worker.h +++ /dev/null @@ -1,48 +0,0 @@ -#pragma once -#include "key_info.h" -#include "rfid_reader.h" -#include "rfid_writer.h" -#include "rfid_timer_emulator.h" -#include "rfid_key.h" -#include "state_sequencer.h" - -class RfidWorker { -public: - RfidWorker(); - ~RfidWorker(); - - void start_read(); - bool read(); - bool detect(); - bool any_read(); - void stop_read(); - - enum class WriteResult : uint8_t { - Ok, - NotWritable, - Nothing, - }; - - void start_write(); - WriteResult write(); - void stop_write(); - - void start_emulate(); - void stop_emulate(); - - RfidKey key; - -private: - RfidWriter writer; - RfidReader reader; - RfidTimerEmulator emulator; - - WriteResult write_result; - TickSequencer* write_sequence; - - void sq_write(); - void sq_write_start_validate(); - void sq_write_validate(); - uint16_t validate_counts; - void sq_write_stop_validate(); -}; diff --git a/applications/lfrfid/helpers/rfid_writer.cpp b/applications/lfrfid/helpers/rfid_writer.cpp deleted file mode 100644 index 31838fde0b9..00000000000 --- a/applications/lfrfid/helpers/rfid_writer.cpp +++ /dev/null @@ -1,183 +0,0 @@ -#include "rfid_writer.h" -#include "protocols/protocol_ioprox.h" -#include -#include "protocols/protocol_emmarin.h" -#include "protocols/protocol_hid_h10301.h" -#include "protocols/protocol_indala_40134.h" - -/** - * @brief all timings are specified in field clocks (field clock = 125 kHz, 8 us) - * - */ -class T55xxTiming { -public: - constexpr static const uint16_t wait_time = 400; - constexpr static const uint8_t start_gap = 30; - constexpr static const uint8_t write_gap = 18; - constexpr static const uint8_t data_0 = 24; - constexpr static const uint8_t data_1 = 56; - constexpr static const uint16_t program = 700; -}; - -class T55xxCmd { -public: - constexpr static const uint8_t opcode_page_0 = 0b10; - constexpr static const uint8_t opcode_page_1 = 0b11; - constexpr static const uint8_t opcode_reset = 0b00; -}; - -RfidWriter::RfidWriter() { -} - -RfidWriter::~RfidWriter() { -} - -void RfidWriter::start() { - furi_hal_rfid_tim_read(125000, 0.5); - furi_hal_rfid_pins_read(); - furi_hal_rfid_tim_read_start(); - - // do not ground the antenna - furi_hal_rfid_pin_pull_release(); -} - -void RfidWriter::stop() { - furi_hal_rfid_tim_read_stop(); - furi_hal_rfid_tim_reset(); - furi_hal_rfid_pins_reset(); -} - -void RfidWriter::write_gap(uint32_t gap_time) { - furi_hal_rfid_tim_read_stop(); - furi_delay_us(gap_time * 8); - furi_hal_rfid_tim_read_start(); -} - -void RfidWriter::write_bit(bool value) { - if(value) { - furi_delay_us(T55xxTiming::data_1 * 8); - } else { - furi_delay_us(T55xxTiming::data_0 * 8); - } - write_gap(T55xxTiming::write_gap); -} - -void RfidWriter::write_byte(uint8_t value) { - for(uint8_t i = 0; i < 8; i++) { - write_bit((value >> i) & 1); - } -} - -void RfidWriter::write_block(uint8_t page, uint8_t block, bool lock_bit, uint32_t data) { - furi_delay_us(T55xxTiming::wait_time * 8); - - // start gap - write_gap(T55xxTiming::start_gap); - - // opcode - switch(page) { - case 0: - write_bit(1); - write_bit(0); - break; - case 1: - write_bit(1); - write_bit(1); - break; - default: - furi_check(false); - break; - } - - // lock bit - write_bit(lock_bit); - - // data - for(uint8_t i = 0; i < 32; i++) { - write_bit((data >> (31 - i)) & 1); - } - - // block address - write_bit((block >> 2) & 1); - write_bit((block >> 1) & 1); - write_bit((block >> 0) & 1); - - furi_delay_us(T55xxTiming::program * 8); - - furi_delay_us(T55xxTiming::wait_time * 8); - write_reset(); -} - -void RfidWriter::write_reset() { - write_gap(T55xxTiming::start_gap); - write_bit(1); - write_bit(0); -} - -void RfidWriter::write_em(const uint8_t em_data[5]) { - ProtocolEMMarin em_card; - uint64_t em_encoded_data; - em_card.encode(em_data, 5, reinterpret_cast(&em_encoded_data), sizeof(uint64_t)); - const uint32_t em_config_block_data = 0b00000000000101001000000001000000; - - FURI_CRITICAL_ENTER(); - write_block(0, 0, false, em_config_block_data); - write_block(0, 1, false, em_encoded_data); - write_block(0, 2, false, em_encoded_data >> 32); - write_reset(); - FURI_CRITICAL_EXIT(); -} - -void RfidWriter::write_hid(const uint8_t hid_data[3]) { - ProtocolHID10301 hid_card; - uint32_t card_data[3]; - hid_card.encode(hid_data, 3, reinterpret_cast(&card_data), sizeof(card_data) * 3); - - const uint32_t hid_config_block_data = 0b00000000000100000111000001100000; - - FURI_CRITICAL_ENTER(); - write_block(0, 0, false, hid_config_block_data); - write_block(0, 1, false, card_data[0]); - write_block(0, 2, false, card_data[1]); - write_block(0, 3, false, card_data[2]); - write_reset(); - 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]; - indala_card.encode( - indala_data, 3, reinterpret_cast(&card_data), sizeof(card_data) * 2); - - const uint32_t indala_config_block_data = 0b00000000000010000001000001000000; - - FURI_CRITICAL_ENTER(); - write_block(0, 0, false, indala_config_block_data); - write_block(0, 1, false, card_data[0]); - write_block(0, 2, false, card_data[1]); - write_reset(); - FURI_CRITICAL_EXIT(); -} diff --git a/applications/lfrfid/helpers/rfid_writer.h b/applications/lfrfid/helpers/rfid_writer.h deleted file mode 100644 index 98d2bf9555b..00000000000 --- a/applications/lfrfid/helpers/rfid_writer.h +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once -#include "stdint.h" - -class RfidWriter { -public: - RfidWriter(); - ~RfidWriter(); - void start(); - 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: - void write_gap(uint32_t gap_time); - void write_bit(bool value); - void write_byte(uint8_t value); - void write_block(uint8_t page, uint8_t block, bool lock_bit, uint32_t data); - void write_reset(); -}; diff --git a/applications/lfrfid/helpers/state_sequencer.cpp b/applications/lfrfid/helpers/state_sequencer.cpp deleted file mode 100644 index e6718df5cd9..00000000000 --- a/applications/lfrfid/helpers/state_sequencer.cpp +++ /dev/null @@ -1,50 +0,0 @@ -#include "state_sequencer.h" -#include "stdio.h" - -TickSequencer::TickSequencer() { -} - -TickSequencer::~TickSequencer() { -} - -void TickSequencer::tick() { - if(tick_count == list_it->first) { - tick_count = 0; - - list_it++; - if(list_it == list.end()) { - list_it = list.begin(); - } - } - - list_it->second(); - tick_count++; -} - -void TickSequencer::reset() { - list_it = list.begin(); - tick_count = 0; -} - -void TickSequencer::clear() { - list.clear(); - reset(); -} - -void TickSequencer::do_every_tick(uint32_t tick_count, std::function fn) { - list.push_back(std::make_pair(tick_count, fn)); - reset(); -} - -void TickSequencer::do_after_tick(uint32_t tick_count, std::function fn) { - if(tick_count > 1) { - list.push_back( - std::make_pair(tick_count - 1, std::bind(&TickSequencer::do_nothing, this))); - } - list.push_back(std::make_pair(1, fn)); - - reset(); -} - -void TickSequencer::do_nothing() { -} diff --git a/applications/lfrfid/helpers/state_sequencer.h b/applications/lfrfid/helpers/state_sequencer.h deleted file mode 100644 index 12512ab5172..00000000000 --- a/applications/lfrfid/helpers/state_sequencer.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once -#include "stdint.h" -#include -#include - -class TickSequencer { -public: - TickSequencer(); - ~TickSequencer(); - - void tick(); - void reset(); - void clear(); - - void do_every_tick(uint32_t tick_count, std::function fn); - void do_after_tick(uint32_t tick_count, std::function fn); - -private: - std::list > > list; - std::list > >::iterator list_it; - - uint32_t tick_count; - - void do_nothing(); -}; diff --git a/applications/lfrfid/lfrfid_app.cpp b/applications/lfrfid/lfrfid_app.cpp index f1a575de540..9373ca8c711 100644 --- a/applications/lfrfid/lfrfid_app.cpp +++ b/applications/lfrfid/lfrfid_app.cpp @@ -21,6 +21,11 @@ #include "scene/lfrfid_app_scene_delete_confirm.h" #include "scene/lfrfid_app_scene_delete_success.h" #include "scene/lfrfid_app_scene_rpc.h" +#include "scene/lfrfid_app_scene_extra_actions.h" +#include "scene/lfrfid_app_scene_raw_info.h" +#include "scene/lfrfid_app_scene_raw_name.h" +#include "scene/lfrfid_app_scene_raw_read.h" +#include "scene/lfrfid_app_scene_raw_success.h" #include #include @@ -28,24 +33,44 @@ #include const char* LfRfidApp::app_folder = ANY_PATH("lfrfid"); +const char* LfRfidApp::app_sd_folder = EXT_PATH("lfrfid"); const char* LfRfidApp::app_extension = ".rfid"; const char* LfRfidApp::app_filetype = "Flipper RFID key"; LfRfidApp::LfRfidApp() : scene_controller{this} - , notification{"notification"} - , storage{"storage"} - , dialogs{"dialogs"} + , notification{RECORD_NOTIFICATION} + , storage{RECORD_STORAGE} + , dialogs{RECORD_DIALOGS} , text_store(40) { + string_init(file_name); + string_init(raw_file_name); string_init_set_str(file_path, app_folder); + + dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax); + + size_t size = protocol_dict_get_max_data_size(dict); + new_key_data = (uint8_t*)malloc(size); + old_key_data = (uint8_t*)malloc(size); + + lfworker = lfrfid_worker_alloc(dict); } LfRfidApp::~LfRfidApp() { + string_clear(raw_file_name); + string_clear(file_name); string_clear(file_path); + protocol_dict_free(dict); + + lfrfid_worker_free(lfworker); + if(rpc_ctx) { rpc_system_app_set_callback(rpc_ctx, NULL, NULL); rpc_system_app_send_exited(rpc_ctx); } + + free(new_key_data); + free(old_key_data); } static void rpc_command_callback(RpcAppSystemEvent rpc_event, void* context) { @@ -88,7 +113,7 @@ void LfRfidApp::run(void* _args) { scene_controller.process(100, SceneType::Rpc); } else { string_set_str(file_path, args); - load_key_data(file_path, &worker.key, true); + load_key_data(file_path, true); view_controller.attach_to_gui(ViewDispatcherTypeFullscreen); scene_controller.add_scene(SceneType::Emulate, new LfRfidAppSceneEmulate()); scene_controller.process(100, SceneType::Emulate); @@ -114,11 +139,16 @@ void LfRfidApp::run(void* _args) { scene_controller.add_scene(SceneType::SavedInfo, new LfRfidAppSceneSavedInfo()); scene_controller.add_scene(SceneType::DeleteConfirm, new LfRfidAppSceneDeleteConfirm()); scene_controller.add_scene(SceneType::DeleteSuccess, new LfRfidAppSceneDeleteSuccess()); + scene_controller.add_scene(SceneType::ExtraActions, new LfRfidAppSceneExtraActions()); + scene_controller.add_scene(SceneType::RawInfo, new LfRfidAppSceneRawInfo()); + scene_controller.add_scene(SceneType::RawName, new LfRfidAppSceneRawName()); + scene_controller.add_scene(SceneType::RawRead, new LfRfidAppSceneRawRead()); + scene_controller.add_scene(SceneType::RawSuccess, new LfRfidAppSceneRawSuccess()); scene_controller.process(100); } } -bool LfRfidApp::save_key(RfidKey* key) { +bool LfRfidApp::save_key() { bool result = false; make_app_folder(); @@ -128,9 +158,9 @@ bool LfRfidApp::save_key(RfidKey* key) { string_left(file_path, filename_start); } - string_cat_printf(file_path, "/%s%s", key->get_name(), app_extension); + string_cat_printf(file_path, "/%s%s", string_get_cstr(file_name), app_extension); - result = save_key_data(file_path, key); + result = save_key_data(file_path); return result; } @@ -143,56 +173,27 @@ bool LfRfidApp::load_key_from_file_select(bool need_restore) { dialogs, file_path, file_path, app_extension, true, &I_125_10px, true); if(result) { - result = load_key_data(file_path, &worker.key, true); + result = load_key_data(file_path, true); } return result; } -bool LfRfidApp::delete_key(RfidKey* key) { - UNUSED(key); +bool LfRfidApp::delete_key() { return storage_simply_remove(storage, string_get_cstr(file_path)); } -bool LfRfidApp::load_key_data(string_t path, RfidKey* key, bool show_dialog) { - FlipperFormat* file = flipper_format_file_alloc(storage); +bool LfRfidApp::load_key_data(string_t path, bool show_dialog) { bool result = false; - string_t str_result; - string_init(str_result); do { - if(!flipper_format_file_open_existing(file, string_get_cstr(path))) break; - - // header - uint32_t version; - if(!flipper_format_read_header(file, str_result, &version)) break; - if(string_cmp_str(str_result, app_filetype) != 0) break; - if(version != 1) break; - - // key type - LfrfidKeyType type; - RfidKey loaded_key; - - if(!flipper_format_read_string(file, "Key type", str_result)) break; - if(!lfrfid_key_get_string_type(string_get_cstr(str_result), &type)) break; - loaded_key.set_type(type); + protocol_id = lfrfid_dict_file_load(dict, string_get_cstr(path)); + if(protocol_id == PROTOCOL_NO) break; - // key data - uint8_t key_data[loaded_key.get_type_data_count()] = {}; - if(!flipper_format_read_hex(file, "Data", key_data, loaded_key.get_type_data_count())) - break; - loaded_key.set_data(key_data, loaded_key.get_type_data_count()); - - path_extract_filename(path, str_result, true); - loaded_key.set_name(string_get_cstr(str_result)); - - *key = loaded_key; + path_extract_filename(path, file_name, true); result = true; } while(0); - flipper_format_free(file); - string_clear(str_result); - if((!result) && (show_dialog)) { dialog_message_show_storage_error(dialogs, "Cannot load\nkey file"); } @@ -200,27 +201,8 @@ bool LfRfidApp::load_key_data(string_t path, RfidKey* key, bool show_dialog) { return result; } -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, 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; - if(!flipper_format_write_string_cstr( - file, "Key type", lfrfid_key_get_type_string(key->get_type()))) - break; - if(!flipper_format_write_comment_cstr( - file, "Data size for EM4100 is 5, for H10301 is 3, for I40134 is 3")) - break; - if(!flipper_format_write_hex(file, "Data", key->get_data(), key->get_type_data_count())) - break; - result = true; - } while(0); - - flipper_format_free(file); +bool LfRfidApp::save_key_data(string_t path) { + bool result = lfrfid_dict_file_save(dict, protocol_id, string_get_cstr(path)); if(!result) { dialog_message_show_storage_error(dialogs, "Cannot save\nkey file"); diff --git a/applications/lfrfid/lfrfid_app.h b/applications/lfrfid/lfrfid_app.h index db022c9aaef..153218dbdf5 100644 --- a/applications/lfrfid/lfrfid_app.h +++ b/applications/lfrfid/lfrfid_app.h @@ -20,9 +20,15 @@ #include #include -#include "helpers/rfid_worker.h" #include "rpc/rpc_app.h" +#include +#include +#include +#include + +#define LFRFID_KEY_NAME_SIZE 22 + class LfRfidApp { public: enum class EventType : uint8_t { @@ -32,7 +38,19 @@ class LfRfidApp { Stay, Retry, Exit, - EmulateStart, + ReadEventSenseStart, + ReadEventSenseEnd, + ReadEventSenseCardStart, + ReadEventSenseCardEnd, + ReadEventStartASK, + ReadEventStartPSK, + ReadEventDone, + ReadEventOverrun, + ReadEventError, + WriteEventOK, + WriteEventProtocolCannotBeWritten, + WriteEventFobCannotBeWritten, + WriteEventTooLongToWrite, RpcLoadFile, RpcSessionClose, }; @@ -57,12 +75,17 @@ class LfRfidApp { DeleteConfirm, DeleteSuccess, Rpc, + ExtraActions, + RawInfo, + RawName, + RawRead, + RawSuccess, }; class Event { public: union { - int32_t menu_index; + int32_t signed_int; } payload; EventType type; @@ -79,8 +102,6 @@ class LfRfidApp { RecordController storage; RecordController dialogs; - RfidWorker worker; - TextStore text_store; string_t file_path; @@ -90,15 +111,27 @@ class LfRfidApp { void run(void* args); static const char* app_folder; + static const char* app_sd_folder; static const char* app_extension; static const char* app_filetype; - bool save_key(RfidKey* key); + bool save_key(); bool load_key_from_file_select(bool need_restore); - bool delete_key(RfidKey* key); + bool delete_key(); - bool load_key_data(string_t path, RfidKey* key, bool show_dialog); - bool save_key_data(string_t path, RfidKey* key); + bool load_key_data(string_t path, bool show_dialog); + bool save_key_data(string_t path); void make_app_folder(); + + ProtocolDict* dict; + LFRFIDWorker* lfworker; + string_t file_name; + ProtocolId protocol_id; + LFRFIDWorkerReadType read_type; + + uint8_t* old_key_data; + uint8_t* new_key_data; + + string_t raw_file_name; }; diff --git a/applications/lfrfid/lfrfid_cli.c b/applications/lfrfid/lfrfid_cli.c new file mode 100644 index 00000000000..9a6930a671a --- /dev/null +++ b/applications/lfrfid/lfrfid_cli.c @@ -0,0 +1,575 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +static void lfrfid_cli(Cli* cli, string_t args, void* context); + +// app cli function +void lfrfid_on_system_start() { + Cli* cli = furi_record_open(RECORD_CLI); + cli_add_command(cli, "rfid", CliCommandFlagDefault, lfrfid_cli, NULL); + furi_record_close(RECORD_CLI); +} + +static void lfrfid_cli_print_usage() { + printf("Usage:\r\n"); + printf("rfid read \r\n"); + printf("rfid \r\n"); + printf("rfid raw_read \r\n"); + printf("rfid raw_emulate \r\n"); +}; + +typedef struct { + ProtocolId protocol; + FuriEventFlag* event; +} LFRFIDCliReadContext; + +static void lfrfid_cli_read_callback(LFRFIDWorkerReadResult result, ProtocolId proto, void* ctx) { + furi_assert(ctx); + LFRFIDCliReadContext* context = ctx; + if(result == LFRFIDWorkerReadDone) { + context->protocol = proto; + FURI_SW_MEMBARRIER(); + } + furi_event_flag_set(context->event, 1 << result); +} + +static void lfrfid_cli_read(Cli* cli, string_t args) { + string_t type_string; + string_init(type_string); + LFRFIDWorkerReadType type = LFRFIDWorkerReadTypeAuto; + + if(args_read_string_and_trim(args, type_string)) { + if(string_cmp_str(type_string, "normal") == 0 || string_cmp_str(type_string, "ask") == 0) { + // ask + type = LFRFIDWorkerReadTypeASKOnly; + } else if( + string_cmp_str(type_string, "indala") == 0 || + string_cmp_str(type_string, "psk") == 0) { + // psk + type = LFRFIDWorkerReadTypePSKOnly; + } else { + lfrfid_cli_print_usage(); + string_clear(type_string); + return; + } + } + string_clear(type_string); + + ProtocolDict* dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax); + LFRFIDWorker* worker = lfrfid_worker_alloc(dict); + LFRFIDCliReadContext context; + context.protocol = PROTOCOL_NO; + context.event = furi_event_flag_alloc(); + + lfrfid_worker_start_thread(worker); + + printf("Reading RFID...\r\nPress Ctrl+C to abort\r\n"); + + const uint32_t available_flags = (1 << LFRFIDWorkerReadDone); + + lfrfid_worker_read_start(worker, type, lfrfid_cli_read_callback, &context); + + while(true) { + uint32_t flags = + furi_event_flag_wait(context.event, available_flags, FuriFlagWaitAny, 100); + + if(flags != FuriFlagErrorTimeout) { + if(FURI_BIT(flags, LFRFIDWorkerReadDone)) { + break; + } + } + + if(cli_cmd_interrupt_received(cli)) break; + } + + lfrfid_worker_stop(worker); + lfrfid_worker_stop_thread(worker); + lfrfid_worker_free(worker); + + if(context.protocol != PROTOCOL_NO) { + printf("%s ", protocol_dict_get_name(dict, context.protocol)); + + size_t size = protocol_dict_get_data_size(dict, context.protocol); + uint8_t* data = malloc(size); + protocol_dict_get_data(dict, context.protocol, data, size); + for(size_t i = 0; i < size; i++) { + printf("%02X", data[i]); + } + printf("\r\n"); + free(data); + + string_t info; + string_init(info); + protocol_dict_render_data(dict, info, context.protocol); + if(string_size(info) > 0) { + printf("%s\r\n", string_get_cstr(info)); + } + string_clear(info); + } + + printf("Reading stopped\r\n"); + protocol_dict_free(dict); + + furi_event_flag_free(context.event); +} + +static bool lfrfid_cli_parse_args(string_t args, ProtocolDict* dict, ProtocolId* protocol) { + bool result = false; + string_t protocol_name, data_text; + string_init(protocol_name); + string_init(data_text); + size_t data_size = protocol_dict_get_max_data_size(dict); + uint8_t* data = malloc(data_size); + + do { + // load args + if(!args_read_string_and_trim(args, protocol_name) || + !args_read_string_and_trim(args, data_text)) { + lfrfid_cli_print_usage(); + break; + } + + // check protocol arg + *protocol = protocol_dict_get_protocol_by_name(dict, string_get_cstr(protocol_name)); + if(*protocol == PROTOCOL_NO) { + printf( + "Unknown protocol: %s\r\n" + "Available protocols:\r\n", + string_get_cstr(protocol_name)); + + for(ProtocolId i = 0; i < LFRFIDProtocolMax; i++) { + printf( + "\t%s, %d bytes long\r\n", + protocol_dict_get_name(dict, i), + protocol_dict_get_data_size(dict, i)); + } + break; + } + + data_size = protocol_dict_get_data_size(dict, *protocol); + + // check data arg + if(!args_read_hex_bytes(data_text, data, data_size)) { + printf( + "%s data needs to be %d bytes long\r\n", + protocol_dict_get_name(dict, *protocol), + data_size); + break; + } + + // load data to protocol + protocol_dict_set_data(dict, *protocol, data, data_size); + + result = true; + } while(false); + + free(data); + string_clear(protocol_name); + string_clear(data_text); + return result; +} + +static void lfrfid_cli_write_callback(LFRFIDWorkerWriteResult result, void* ctx) { + furi_assert(ctx); + FuriEventFlag* events = ctx; + furi_event_flag_set(events, 1 << result); +} + +static void lfrfid_cli_write(Cli* cli, string_t args) { + ProtocolDict* dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax); + ProtocolId protocol; + + if(!lfrfid_cli_parse_args(args, dict, &protocol)) { + protocol_dict_free(dict); + return; + } + + LFRFIDWorker* worker = lfrfid_worker_alloc(dict); + FuriEventFlag* event = furi_event_flag_alloc(); + + lfrfid_worker_start_thread(worker); + lfrfid_worker_write_start(worker, protocol, lfrfid_cli_write_callback, event); + + printf("Writing RFID...\r\nPress Ctrl+C to abort\r\n"); + const uint32_t available_flags = (1 << LFRFIDWorkerWriteOK) | + (1 << LFRFIDWorkerWriteProtocolCannotBeWritten) | + (1 << LFRFIDWorkerWriteFobCannotBeWritten); + + while(!cli_cmd_interrupt_received(cli)) { + uint32_t flags = furi_event_flag_wait(event, available_flags, FuriFlagWaitAny, 100); + if(flags != FuriFlagErrorTimeout) { + if(FURI_BIT(flags, LFRFIDWorkerWriteOK)) { + printf("Written!\r\n"); + break; + } + + if(FURI_BIT(flags, LFRFIDWorkerWriteProtocolCannotBeWritten)) { + printf("This protocol cannot be written.\r\n"); + break; + } + + if(FURI_BIT(flags, LFRFIDWorkerWriteFobCannotBeWritten)) { + printf("Seems this fob cannot be written.\r\n"); + } + } + } + printf("Writing stopped\r\n"); + + lfrfid_worker_stop(worker); + lfrfid_worker_stop_thread(worker); + lfrfid_worker_free(worker); + protocol_dict_free(dict); + furi_event_flag_free(event); +} + +static void lfrfid_cli_emulate(Cli* cli, string_t args) { + ProtocolDict* dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax); + ProtocolId protocol; + + if(!lfrfid_cli_parse_args(args, dict, &protocol)) { + protocol_dict_free(dict); + return; + } + + LFRFIDWorker* worker = lfrfid_worker_alloc(dict); + + lfrfid_worker_start_thread(worker); + lfrfid_worker_emulate_start(worker, protocol); + + printf("Emulating RFID...\r\nPress Ctrl+C to abort\r\n"); + while(!cli_cmd_interrupt_received(cli)) { + furi_delay_ms(100); + } + printf("Emulation stopped\r\n"); + + lfrfid_worker_stop(worker); + lfrfid_worker_stop_thread(worker); + lfrfid_worker_free(worker); + protocol_dict_free(dict); +} + +static void lfrfid_cli_raw_analyze(Cli* cli, string_t args) { + UNUSED(cli); + string_t filepath, info_string; + string_init(filepath); + string_init(info_string); + Storage* storage = furi_record_open(RECORD_STORAGE); + LFRFIDRawFile* file = lfrfid_raw_file_alloc(storage); + + do { + float frequency = 0; + float duty_cycle = 0; + + if(!args_read_probably_quoted_string_and_trim(args, filepath)) { + lfrfid_cli_print_usage(); + break; + } + + if(!lfrfid_raw_file_open_read(file, string_get_cstr(filepath))) { + printf("Failed to open file\r\n"); + break; + } + + if(!lfrfid_raw_file_read_header(file, &frequency, &duty_cycle)) { + printf("Invalid header\r\n"); + break; + } + + bool file_end = false; + uint32_t total_warns = 0; + uint32_t total_duration = 0; + uint32_t total_pulse = 0; + ProtocolId total_protocol = PROTOCOL_NO; + + ProtocolDict* dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax); + protocol_dict_decoders_start(dict); + + while(!file_end) { + uint32_t pulse = 0; + uint32_t duration = 0; + if(lfrfid_raw_file_read_pair(file, &duration, &pulse, &file_end)) { + bool warn = false; + + if(pulse > duration || pulse <= 0 || duration <= 0) { + total_warns += 1; + warn = true; + } + + string_printf(info_string, "[%ld %ld]", pulse, duration); + printf("%-16s", string_get_cstr(info_string)); + string_printf(info_string, "[%ld %ld]", pulse, duration - pulse); + printf("%-16s", string_get_cstr(info_string)); + + if(warn) { + printf(" <<----"); + } + + if(total_protocol == PROTOCOL_NO) { + total_protocol = protocol_dict_decoders_feed(dict, true, pulse); + if(total_protocol == PROTOCOL_NO) { + total_protocol = + protocol_dict_decoders_feed(dict, false, duration - pulse); + } + + if(total_protocol != PROTOCOL_NO) { + printf(" ", protocol_dict_get_name(dict, total_protocol)); + } + } + + printf("\r\n"); + + total_pulse += pulse; + total_duration += duration; + + if(total_protocol != PROTOCOL_NO) { + break; + } + } else { + printf("Failed to read pair\r\n"); + break; + } + } + + printf(" Frequency: %f\r\n", (double)frequency); + printf(" Duty Cycle: %f\r\n", (double)duty_cycle); + printf(" Warns: %ld\r\n", total_warns); + printf(" Pulse sum: %ld\r\n", total_pulse); + printf("Duration sum: %ld\r\n", total_duration); + printf(" Average: %f\r\n", (double)((float)total_pulse / (float)total_duration)); + printf(" Protocol: "); + + if(total_protocol != PROTOCOL_NO) { + size_t data_size = protocol_dict_get_data_size(dict, total_protocol); + uint8_t* data = malloc(data_size); + protocol_dict_get_data(dict, total_protocol, data, data_size); + + printf("%s [", protocol_dict_get_name(dict, total_protocol)); + for(size_t i = 0; i < data_size; i++) { + printf("%02X", data[i]); + if(i < data_size - 1) { + printf(" "); + } + } + printf("]\r\n"); + + protocol_dict_render_data(dict, info_string, total_protocol); + printf("%s\r\n", string_get_cstr(info_string)); + + free(data); + } else { + printf("not found\r\n"); + } + + protocol_dict_free(dict); + } while(false); + + string_clear(filepath); + string_clear(info_string); + lfrfid_raw_file_free(file); + furi_record_close(RECORD_STORAGE); +} + +static void lfrfid_cli_raw_read_callback(LFRFIDWorkerReadRawResult result, void* context) { + furi_assert(context); + FuriEventFlag* event = context; + furi_event_flag_set(event, 1 << result); +} + +static void lfrfid_cli_raw_read(Cli* cli, string_t args) { + UNUSED(cli); + + string_t filepath, type_string; + string_init(filepath); + string_init(type_string); + LFRFIDWorkerReadType type = LFRFIDWorkerReadTypeAuto; + + do { + if(args_read_string_and_trim(args, type_string)) { + if(string_cmp_str(type_string, "normal") == 0 || + string_cmp_str(type_string, "ask") == 0) { + // ask + type = LFRFIDWorkerReadTypeASKOnly; + } else if( + string_cmp_str(type_string, "indala") == 0 || + string_cmp_str(type_string, "psk") == 0) { + // psk + type = LFRFIDWorkerReadTypePSKOnly; + } else { + lfrfid_cli_print_usage(); + break; + } + } + + if(!args_read_probably_quoted_string_and_trim(args, filepath)) { + lfrfid_cli_print_usage(); + break; + } + + ProtocolDict* dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax); + LFRFIDWorker* worker = lfrfid_worker_alloc(dict); + FuriEventFlag* event = furi_event_flag_alloc(); + + lfrfid_worker_start_thread(worker); + + bool overrun = false; + + const uint32_t available_flags = (1 << LFRFIDWorkerReadRawFileError) | + (1 << LFRFIDWorkerReadRawOverrun); + + lfrfid_worker_read_raw_start( + worker, string_get_cstr(filepath), type, lfrfid_cli_raw_read_callback, event); + while(true) { + uint32_t flags = furi_event_flag_wait(event, available_flags, FuriFlagWaitAny, 100); + + if(flags != FuriFlagErrorTimeout) { + if(FURI_BIT(flags, LFRFIDWorkerReadRawFileError)) { + printf("File is not RFID raw file\r\n"); + break; + } + + if(FURI_BIT(flags, LFRFIDWorkerReadRawOverrun)) { + if(!overrun) { + printf("Overrun\r\n"); + overrun = true; + } + } + } + + if(cli_cmd_interrupt_received(cli)) break; + } + + if(overrun) { + printf("An overrun occurred during read\r\n"); + } + + lfrfid_worker_stop(worker); + + lfrfid_worker_stop_thread(worker); + lfrfid_worker_free(worker); + protocol_dict_free(dict); + + furi_event_flag_free(event); + + } while(false); + + string_clear(filepath); + string_clear(type_string); +} + +static void lfrfid_cli_raw_emulate_callback(LFRFIDWorkerEmulateRawResult result, void* context) { + furi_assert(context); + FuriEventFlag* event = context; + furi_event_flag_set(event, 1 << result); +} + +static void lfrfid_cli_raw_emulate(Cli* cli, string_t args) { + UNUSED(cli); + + string_t filepath; + string_init(filepath); + Storage* storage = furi_record_open(RECORD_STORAGE); + + do { + if(!args_read_probably_quoted_string_and_trim(args, filepath)) { + lfrfid_cli_print_usage(); + break; + } + + if(!storage_file_exists(storage, string_get_cstr(filepath))) { + printf("File not found: \"%s\"\r\n", string_get_cstr(filepath)); + break; + } + + ProtocolDict* dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax); + LFRFIDWorker* worker = lfrfid_worker_alloc(dict); + FuriEventFlag* event = furi_event_flag_alloc(); + + lfrfid_worker_start_thread(worker); + + bool overrun = false; + + const uint32_t available_flags = (1 << LFRFIDWorkerEmulateRawFileError) | + (1 << LFRFIDWorkerEmulateRawOverrun); + + lfrfid_worker_emulate_raw_start( + worker, string_get_cstr(filepath), lfrfid_cli_raw_emulate_callback, event); + while(true) { + uint32_t flags = furi_event_flag_wait(event, available_flags, FuriFlagWaitAny, 100); + + if(flags != FuriFlagErrorTimeout) { + if(FURI_BIT(flags, LFRFIDWorkerEmulateRawFileError)) { + printf("File is not RFID raw file\r\n"); + break; + } + + if(FURI_BIT(flags, LFRFIDWorkerEmulateRawOverrun)) { + if(!overrun) { + printf("Overrun\r\n"); + overrun = true; + } + } + } + + if(cli_cmd_interrupt_received(cli)) break; + } + + if(overrun) { + printf("An overrun occurred during emulation\r\n"); + } + + lfrfid_worker_stop(worker); + + lfrfid_worker_stop_thread(worker); + lfrfid_worker_free(worker); + protocol_dict_free(dict); + + furi_event_flag_free(event); + + } while(false); + + furi_record_close(RECORD_STORAGE); + string_clear(filepath); +} + +static void lfrfid_cli(Cli* cli, string_t args, void* context) { + UNUSED(context); + string_t cmd; + string_init(cmd); + + if(!args_read_string_and_trim(args, cmd)) { + string_clear(cmd); + lfrfid_cli_print_usage(); + return; + } + + if(string_cmp_str(cmd, "read") == 0) { + lfrfid_cli_read(cli, args); + } else if(string_cmp_str(cmd, "write") == 0) { + lfrfid_cli_write(cli, args); + } else if(string_cmp_str(cmd, "emulate") == 0) { + lfrfid_cli_emulate(cli, args); + } else if(string_cmp_str(cmd, "raw_read") == 0) { + lfrfid_cli_raw_read(cli, args); + } else if(string_cmp_str(cmd, "raw_emulate") == 0) { + lfrfid_cli_raw_emulate(cli, args); + } else if(string_cmp_str(cmd, "raw_analyze") == 0) { + lfrfid_cli_raw_analyze(cli, args); + } else { + lfrfid_cli_print_usage(); + } + + string_clear(cmd); +} \ No newline at end of file diff --git a/applications/lfrfid/lfrfid_cli.cpp b/applications/lfrfid/lfrfid_cli.cpp deleted file mode 100644 index 732197e9534..00000000000 --- a/applications/lfrfid/lfrfid_cli.cpp +++ /dev/null @@ -1,177 +0,0 @@ -#include -#include -#include -#include -#include - -#include "helpers/rfid_reader.h" -#include "helpers/rfid_timer_emulator.h" - -static void lfrfid_cli(Cli* cli, string_t args, void* context); - -// app cli function -extern "C" void lfrfid_on_system_start() { -#ifdef SRV_CLI - Cli* cli = static_cast(furi_record_open("cli")); - cli_add_command(cli, "rfid", CliCommandFlagDefault, lfrfid_cli, NULL); - furi_record_close("cli"); -#else - UNUSED(lfrfid_cli); -#endif -} - -void lfrfid_cli_print_usage() { - printf("Usage:\r\n"); - printf("rfid read \r\n"); - printf("rfid \r\n"); - printf("\t choose from:\r\n"); - 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"); -}; - -static bool lfrfid_cli_get_key_type(string_t data, LfrfidKeyType* type) { - bool result = false; - - if(string_cmp_str(data, "EM4100") == 0 || string_cmp_str(data, "EM-Marin") == 0) { - result = true; - *type = LfrfidKeyType::KeyEM4100; - } else if(string_cmp_str(data, "H10301") == 0 || string_cmp_str(data, "HID26") == 0) { - result = true; - *type = LfrfidKeyType::KeyH10301; - } 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; -} - -static void lfrfid_cli_read(Cli* cli, string_t args) { - RfidReader reader; - string_t type_string; - string_init(type_string); - bool simple_mode = true; - LfrfidKeyType type; - RfidReader::Type reader_type = RfidReader::Type::Normal; - static const uint8_t data_size = LFRFID_KEY_SIZE; - uint8_t data[data_size] = {0}; - - if(args_read_string_and_trim(args, type_string)) { - simple_mode = false; - - if(string_cmp_str(type_string, "normal") == 0) { - reader_type = RfidReader::Type::Normal; - } else if(string_cmp_str(type_string, "indala") == 0) { - reader_type = RfidReader::Type::Indala; - } else { - lfrfid_cli_print_usage(); - string_clear(type_string); - return; - } - } - - if(simple_mode) { - reader.start(); - } else { - reader.start_forced(reader_type); - } - - printf("Reading RFID...\r\nPress Ctrl+C to abort\r\n"); - while(!cli_cmd_interrupt_received(cli)) { - if(reader.read(&type, data, data_size, simple_mode)) { - printf("%s", lfrfid_key_get_type_string(type)); - printf(" "); - - for(uint8_t i = 0; i < lfrfid_key_get_type_data_count(type); i++) { - printf("%02X", data[i]); - } - printf("\r\n"); - break; - } - furi_delay_ms(100); - } - - printf("Reading stopped\r\n"); - reader.stop(); - - string_clear(type_string); -} - -static void lfrfid_cli_write(Cli* cli, string_t args) { - UNUSED(cli); - UNUSED(args); - // TODO implement rfid write - printf("Not Implemented :(\r\n"); -} - -static void lfrfid_cli_emulate(Cli* cli, string_t args) { - string_t data; - string_init(data); - RfidTimerEmulator emulator; - - static const uint8_t data_size = LFRFID_KEY_SIZE; - uint8_t key_data[data_size] = {0}; - uint8_t key_data_size = 0; - LfrfidKeyType type; - - if(!args_read_string_and_trim(args, data)) { - lfrfid_cli_print_usage(); - string_clear(data); - return; - } - - if(!lfrfid_cli_get_key_type(data, &type)) { - lfrfid_cli_print_usage(); - string_clear(data); - return; - } - - key_data_size = lfrfid_key_get_type_data_count(type); - - if(!args_read_hex_bytes(args, key_data, key_data_size)) { - lfrfid_cli_print_usage(); - string_clear(data); - return; - } - - emulator.start(type, key_data, key_data_size); - - printf("Emulating RFID...\r\nPress Ctrl+C to abort\r\n"); - while(!cli_cmd_interrupt_received(cli)) { - furi_delay_ms(100); - } - printf("Emulation stopped\r\n"); - emulator.stop(); - - string_clear(data); -} - -static void lfrfid_cli(Cli* cli, string_t args, void* context) { - UNUSED(context); - string_t cmd; - string_init(cmd); - - if(!args_read_string_and_trim(args, cmd)) { - string_clear(cmd); - lfrfid_cli_print_usage(); - return; - } - - if(string_cmp_str(cmd, "read") == 0) { - lfrfid_cli_read(cli, args); - } else if(string_cmp_str(cmd, "write") == 0) { - lfrfid_cli_write(cli, args); - } else if(string_cmp_str(cmd, "emulate") == 0) { - lfrfid_cli_emulate(cli, args); - } else { - lfrfid_cli_print_usage(); - } - - string_clear(cmd); -} diff --git a/applications/lfrfid/scene/lfrfid_app_scene_delete_confirm.cpp b/applications/lfrfid/scene/lfrfid_app_scene_delete_confirm.cpp index 236ca8c29fa..58ff4dcdfc3 100644 --- a/applications/lfrfid/scene/lfrfid_app_scene_delete_confirm.cpp +++ b/applications/lfrfid/scene/lfrfid_app_scene_delete_confirm.cpp @@ -5,7 +5,6 @@ void LfRfidAppSceneDeleteConfirm::on_enter(LfRfidApp* app, bool /* need_restore */) { string_init(string_data); - string_init(string_decrypted); string_init(string_header); auto container = app->view_controller.get(); @@ -21,49 +20,26 @@ void LfRfidAppSceneDeleteConfirm::on_enter(LfRfidApp* app, bool /* need_restore auto line_1 = container->add(); auto line_2 = container->add(); auto line_3 = container->add(); - auto line_4 = container->add(); - RfidKey& key = app->worker.key; - const uint8_t* data = key.get_data(); - - for(uint8_t i = 0; i < key.get_type_data_count(); i++) { + size_t size = protocol_dict_get_data_size(app->dict, app->protocol_id); + uint8_t* data = (uint8_t*)malloc(size); + protocol_dict_get_data(app->dict, app->protocol_id, data, size); + for(uint8_t i = 0; i < MIN(size, (size_t)8); i++) { if(i != 0) { string_cat_printf(string_data, " "); } + string_cat_printf(string_data, "%02X", data[i]); } + free(data); - string_printf(string_header, "Delete %s?", key.get_name()); + string_printf(string_header, "Delete %s?", string_get_cstr(app->file_name)); line_1->set_text( - string_get_cstr(string_header), 64, 19, 128 - 2, AlignCenter, AlignBottom, FontPrimary); + string_get_cstr(string_header), 64, 0, 128 - 2, AlignCenter, AlignTop, FontPrimary); line_2->set_text( - string_get_cstr(string_data), 64, 29, 0, AlignCenter, AlignBottom, FontSecondary); - - switch(key.get_type()) { - case LfrfidKeyType::KeyEM4100: - string_printf( - string_decrypted, "%03u,%05u", data[2], (uint16_t)((data[3] << 8) | (data[4]))); - - break; - case LfrfidKeyType::KeyH10301: - case LfrfidKeyType::KeyI40134: - string_printf( - string_decrypted, "FC: %u ID: %u", data[0], (uint16_t)((data[1] << 8) | (data[2]))); - break; - 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; - } + string_get_cstr(string_data), 64, 19, 0, AlignCenter, AlignTop, FontSecondary); line_3->set_text( - string_get_cstr(string_decrypted), 64, 39, 0, AlignCenter, AlignBottom, FontSecondary); - - line_4->set_text( - lfrfid_key_get_type_string(key.get_type()), + protocol_dict_get_name(app->dict, app->protocol_id), 64, 49, 0, @@ -78,7 +54,7 @@ bool LfRfidAppSceneDeleteConfirm::on_event(LfRfidApp* app, LfRfidApp::Event* eve bool consumed = false; if(event->type == LfRfidApp::EventType::Next) { - app->delete_key(&app->worker.key); + app->delete_key(); app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::DeleteSuccess); consumed = true; } else if(event->type == LfRfidApp::EventType::Stay) { @@ -94,7 +70,6 @@ bool LfRfidAppSceneDeleteConfirm::on_event(LfRfidApp* app, LfRfidApp::Event* eve void LfRfidAppSceneDeleteConfirm::on_exit(LfRfidApp* app) { app->view_controller.get()->clean(); string_clear(string_data); - string_clear(string_decrypted); string_clear(string_header); } diff --git a/applications/lfrfid/scene/lfrfid_app_scene_delete_confirm.h b/applications/lfrfid/scene/lfrfid_app_scene_delete_confirm.h index e30764f0232..f9daed54328 100644 --- a/applications/lfrfid/scene/lfrfid_app_scene_delete_confirm.h +++ b/applications/lfrfid/scene/lfrfid_app_scene_delete_confirm.h @@ -13,5 +13,4 @@ class LfRfidAppSceneDeleteConfirm : public GenericScene { string_t string_header; string_t string_data; - string_t string_decrypted; }; diff --git a/applications/lfrfid/scene/lfrfid_app_scene_emulate.cpp b/applications/lfrfid/scene/lfrfid_app_scene_emulate.cpp index cad4f17c7e2..02cb011d13c 100644 --- a/applications/lfrfid/scene/lfrfid_app_scene_emulate.cpp +++ b/applications/lfrfid/scene/lfrfid_app_scene_emulate.cpp @@ -3,28 +3,21 @@ #include void LfRfidAppSceneEmulate::on_enter(LfRfidApp* app, bool /* need_restore */) { - string_init(data_string); - DOLPHIN_DEED(DolphinDeedRfidEmulate); - const uint8_t* data = app->worker.key.get_data(); - - for(uint8_t i = 0; i < app->worker.key.get_type_data_count(); i++) { - string_cat_printf(data_string, "%02X", data[i]); - } - auto popup = app->view_controller.get(); popup->set_header("Emulating", 89, 30, AlignCenter, AlignTop); - if(strlen(app->worker.key.get_name())) { - popup->set_text(app->worker.key.get_name(), 89, 43, AlignCenter, AlignTop); + if(string_size(app->file_name)) { + popup->set_text(string_get_cstr(app->file_name), 89, 43, AlignCenter, AlignTop); } else { - popup->set_text(string_get_cstr(data_string), 89, 43, AlignCenter, AlignTop); + popup->set_text( + protocol_dict_get_name(app->dict, app->protocol_id), 89, 43, AlignCenter, AlignTop); } popup->set_icon(0, 3, &I_RFIDDolphinSend_97x61); app->view_controller.switch_to(); - app->worker.start_emulate(); - + lfrfid_worker_start_thread(app->lfworker); + lfrfid_worker_emulate_start(app->lfworker, (LFRFIDProtocol)app->protocol_id); notification_message(app->notification, &sequence_blink_start_magenta); } @@ -37,7 +30,7 @@ bool LfRfidAppSceneEmulate::on_event(LfRfidApp* app, LfRfidApp::Event* event) { void LfRfidAppSceneEmulate::on_exit(LfRfidApp* app) { app->view_controller.get()->clean(); - app->worker.stop_emulate(); - string_clear(data_string); + lfrfid_worker_stop(app->lfworker); + lfrfid_worker_stop_thread(app->lfworker); notification_message(app->notification, &sequence_blink_stop); } diff --git a/applications/lfrfid/scene/lfrfid_app_scene_emulate.h b/applications/lfrfid/scene/lfrfid_app_scene_emulate.h index 937e49af9b1..13d2b857dc9 100644 --- a/applications/lfrfid/scene/lfrfid_app_scene_emulate.h +++ b/applications/lfrfid/scene/lfrfid_app_scene_emulate.h @@ -6,7 +6,4 @@ class LfRfidAppSceneEmulate : public GenericScene { void on_enter(LfRfidApp* app, bool need_restore) final; bool on_event(LfRfidApp* app, LfRfidApp::Event* event) final; void on_exit(LfRfidApp* app) final; - -private: - string_t data_string; }; diff --git a/applications/lfrfid/scene/lfrfid_app_scene_extra_actions.cpp b/applications/lfrfid/scene/lfrfid_app_scene_extra_actions.cpp new file mode 100644 index 00000000000..ea4f03dbb49 --- /dev/null +++ b/applications/lfrfid/scene/lfrfid_app_scene_extra_actions.cpp @@ -0,0 +1,63 @@ +#include "lfrfid_app_scene_extra_actions.h" + +typedef enum { + SubmenuASK, + SubmenuPSK, + SubmenuRAW, +} SubmenuIndex; + +void LfRfidAppSceneExtraActions::on_enter(LfRfidApp* app, bool need_restore) { + auto submenu = app->view_controller.get(); + + submenu->add_item("Read ASK (Animal, Ordinary Card)", SubmenuASK, submenu_callback, app); + submenu->add_item("Read PSK (Indala)", SubmenuPSK, submenu_callback, app); + + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + submenu->add_item("Read RAW RFID data", SubmenuRAW, submenu_callback, app); + } + + if(need_restore) { + submenu->set_selected_item(submenu_item_selected); + } + + app->view_controller.switch_to(); +} + +bool LfRfidAppSceneExtraActions::on_event(LfRfidApp* app, LfRfidApp::Event* event) { + bool consumed = false; + + if(event->type == LfRfidApp::EventType::MenuSelected) { + submenu_item_selected = event->payload.signed_int; + switch(event->payload.signed_int) { + case SubmenuASK: + app->read_type = LFRFIDWorkerReadTypeASKOnly; + app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::Read); + break; + case SubmenuPSK: + app->read_type = LFRFIDWorkerReadTypePSKOnly; + app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::Read); + break; + case SubmenuRAW: + app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::RawName); + break; + } + + consumed = true; + } + + return consumed; +} + +void LfRfidAppSceneExtraActions::on_exit(LfRfidApp* app) { + app->view_controller.get()->clean(); +} + +void LfRfidAppSceneExtraActions::submenu_callback(void* context, uint32_t index) { + LfRfidApp* app = static_cast(context); + LfRfidApp::Event event; + + event.type = LfRfidApp::EventType::MenuSelected; + event.payload.signed_int = index; + + app->view_controller.send_event(&event); +} diff --git a/applications/lfrfid/scene/lfrfid_app_scene_extra_actions.h b/applications/lfrfid/scene/lfrfid_app_scene_extra_actions.h new file mode 100644 index 00000000000..dcd746146cf --- /dev/null +++ b/applications/lfrfid/scene/lfrfid_app_scene_extra_actions.h @@ -0,0 +1,13 @@ +#pragma once +#include "../lfrfid_app.h" + +class LfRfidAppSceneExtraActions : public GenericScene { +public: + void on_enter(LfRfidApp* app, bool need_restore) final; + bool on_event(LfRfidApp* app, LfRfidApp::Event* event) final; + void on_exit(LfRfidApp* app) final; + +private: + static void submenu_callback(void* context, uint32_t index); + uint32_t submenu_item_selected = 0; +}; diff --git a/applications/lfrfid/scene/lfrfid_app_scene_raw_info.cpp b/applications/lfrfid/scene/lfrfid_app_scene_raw_info.cpp new file mode 100644 index 00000000000..ce3634b2acd --- /dev/null +++ b/applications/lfrfid/scene/lfrfid_app_scene_raw_info.cpp @@ -0,0 +1,77 @@ +#include "lfrfid_app_scene_raw_info.h" +#include "../view/elements/button_element.h" +#include "../view/elements/icon_element.h" +#include "../view/elements/string_element.h" + +static void ok_callback(void* context) { + LfRfidApp* app = static_cast(context); + LfRfidApp::Event event; + event.type = LfRfidApp::EventType::Next; + app->view_controller.send_event(&event); +} + +static void back_callback(void* context) { + LfRfidApp* app = static_cast(context); + LfRfidApp::Event event; + event.type = LfRfidApp::EventType::Back; + app->view_controller.send_event(&event); +} + +void LfRfidAppSceneRawInfo::on_enter(LfRfidApp* app, bool /* need_restore */) { + string_init(string_info); + + auto container = app->view_controller.get(); + + bool sd_exist = storage_sd_status(app->storage) == FSE_OK; + if(!sd_exist) { + auto icon = container->add(); + icon->set_icon(0, 0, &I_SDQuestion_35x43); + auto line = container->add(); + line->set_text( + "No SD card found.\nThis function will not\nwork without\nSD card.", + 81, + 4, + 0, + AlignCenter, + AlignTop, + FontSecondary); + + auto button = container->add(); + button->set_type(ButtonElement::Type::Left, "Back"); + button->set_callback(app, back_callback); + } else { + string_printf( + string_info, + "RAW RFID data reader\r\n" + "1) Put the Flipper on your card\r\n" + "2) Press OK\r\n" + "3) Wait until data is read"); + + auto line = container->add(); + line->set_text(string_get_cstr(string_info), 0, 1, 0, AlignLeft, AlignTop, FontSecondary); + + auto button = container->add(); + button->set_type(ButtonElement::Type::Center, "OK"); + button->set_callback(app, ok_callback); + } + + app->view_controller.switch_to(); +} + +bool LfRfidAppSceneRawInfo::on_event(LfRfidApp* app, LfRfidApp::Event* event) { + bool consumed = false; + if(event->type == LfRfidApp::EventType::Next) { + app->scene_controller.switch_to_scene({LfRfidApp::SceneType::RawRead}); + consumed = true; + } else if(event->type == LfRfidApp::EventType::Back) { + app->scene_controller.search_and_switch_to_previous_scene( + {LfRfidApp::SceneType::ExtraActions}); + consumed = true; + } + return consumed; +} + +void LfRfidAppSceneRawInfo::on_exit(LfRfidApp* app) { + app->view_controller.get()->clean(); + string_clear(string_info); +} diff --git a/applications/lfrfid/scene/lfrfid_app_scene_raw_info.h b/applications/lfrfid/scene/lfrfid_app_scene_raw_info.h new file mode 100644 index 00000000000..eecca14368c --- /dev/null +++ b/applications/lfrfid/scene/lfrfid_app_scene_raw_info.h @@ -0,0 +1,12 @@ +#pragma once +#include "../lfrfid_app.h" + +class LfRfidAppSceneRawInfo : public GenericScene { +public: + void on_enter(LfRfidApp* app, bool need_restore) final; + bool on_event(LfRfidApp* app, LfRfidApp::Event* event) final; + void on_exit(LfRfidApp* app) final; + +private: + string_t string_info; +}; diff --git a/applications/lfrfid/scene/lfrfid_app_scene_raw_name.cpp b/applications/lfrfid/scene/lfrfid_app_scene_raw_name.cpp new file mode 100644 index 00000000000..0ad346198ea --- /dev/null +++ b/applications/lfrfid/scene/lfrfid_app_scene_raw_name.cpp @@ -0,0 +1,46 @@ + +#include "lfrfid_app_scene_raw_name.h" +#include "m-string.h" +#include +#include + +void LfRfidAppSceneRawName::on_enter(LfRfidApp* app, bool /* need_restore */) { + const char* key_name = string_get_cstr(app->raw_file_name); + + bool key_name_empty = (string_size(app->raw_file_name) == 0); + if(key_name_empty) { + app->text_store.set("RfidRecord"); + } else { + app->text_store.set("%s", key_name); + } + + auto text_input = app->view_controller.get(); + text_input->set_header_text("Name the raw file"); + + text_input->set_result_callback( + save_callback, app, app->text_store.text, LFRFID_KEY_NAME_SIZE, key_name_empty); + + app->view_controller.switch_to(); +} + +bool LfRfidAppSceneRawName::on_event(LfRfidApp* app, LfRfidApp::Event* event) { + bool consumed = false; + + if(event->type == LfRfidApp::EventType::Next) { + string_set_str(app->raw_file_name, app->text_store.text); + app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::RawInfo); + } + + return consumed; +} + +void LfRfidAppSceneRawName::on_exit(LfRfidApp* app) { + app->view_controller.get()->clean(); +} + +void LfRfidAppSceneRawName::save_callback(void* context) { + LfRfidApp* app = static_cast(context); + LfRfidApp::Event event; + event.type = LfRfidApp::EventType::Next; + app->view_controller.send_event(&event); +} diff --git a/applications/lfrfid/scene/lfrfid_app_scene_raw_name.h b/applications/lfrfid/scene/lfrfid_app_scene_raw_name.h new file mode 100644 index 00000000000..225d135e5e0 --- /dev/null +++ b/applications/lfrfid/scene/lfrfid_app_scene_raw_name.h @@ -0,0 +1,12 @@ +#pragma once +#include "../lfrfid_app.h" + +class LfRfidAppSceneRawName : public GenericScene { +public: + void on_enter(LfRfidApp* app, bool need_restore) final; + bool on_event(LfRfidApp* app, LfRfidApp::Event* event) final; + void on_exit(LfRfidApp* app) final; + +private: + static void save_callback(void* context); +}; diff --git a/applications/lfrfid/scene/lfrfid_app_scene_raw_read.cpp b/applications/lfrfid/scene/lfrfid_app_scene_raw_read.cpp new file mode 100644 index 00000000000..0d04e6bc754 --- /dev/null +++ b/applications/lfrfid/scene/lfrfid_app_scene_raw_read.cpp @@ -0,0 +1,107 @@ +#include "lfrfid_app_scene_raw_read.h" +#include + +#define RAW_READ_TIME 5000 + +static void lfrfid_read_callback(LFRFIDWorkerReadRawResult result, void* ctx) { + LfRfidApp* app = static_cast(ctx); + LfRfidApp::Event event; + + switch(result) { + case LFRFIDWorkerReadRawFileError: + event.type = LfRfidApp::EventType::ReadEventError; + break; + case LFRFIDWorkerReadRawOverrun: + event.type = LfRfidApp::EventType::ReadEventOverrun; + break; + } + + app->view_controller.send_event(&event); +} + +static void timer_callback(void* ctx) { + LfRfidApp* app = static_cast(ctx); + LfRfidApp::Event event; + event.type = LfRfidApp::EventType::ReadEventDone; + app->view_controller.send_event(&event); +} + +void LfRfidAppSceneRawRead::on_enter(LfRfidApp* app, bool /* need_restore */) { + string_init(string_file_name); + auto popup = app->view_controller.get(); + popup->set_icon(0, 3, &I_RFIDDolphinReceive_97x61); + app->view_controller.switch_to(); + lfrfid_worker_start_thread(app->lfworker); + app->make_app_folder(); + + timer = furi_timer_alloc(timer_callback, FuriTimerTypeOnce, app); + furi_timer_start(timer, RAW_READ_TIME); + string_printf( + string_file_name, "%s/%s.ask.raw", app->app_sd_folder, string_get_cstr(app->raw_file_name)); + popup->set_header("Reading\nRAW RFID\nASK", 89, 30, AlignCenter, AlignTop); + lfrfid_worker_read_raw_start( + app->lfworker, + string_get_cstr(string_file_name), + LFRFIDWorkerReadTypeASKOnly, + lfrfid_read_callback, + app); + + notification_message(app->notification, &sequence_blink_start_cyan); + + is_psk = false; + error = false; +} + +bool LfRfidAppSceneRawRead::on_event(LfRfidApp* app, LfRfidApp::Event* event) { + UNUSED(app); + bool consumed = true; + auto popup = app->view_controller.get(); + + switch(event->type) { + case LfRfidApp::EventType::ReadEventError: + error = true; + popup->set_header("Reading\nRAW RFID\nFile error", 89, 30, AlignCenter, AlignTop); + notification_message(app->notification, &sequence_blink_start_red); + furi_timer_stop(timer); + break; + case LfRfidApp::EventType::ReadEventDone: + if(!error) { + if(is_psk) { + notification_message(app->notification, &sequence_success); + app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::RawSuccess); + } else { + popup->set_header("Reading\nRAW RFID\nPSK", 89, 30, AlignCenter, AlignTop); + notification_message(app->notification, &sequence_blink_start_yellow); + lfrfid_worker_stop(app->lfworker); + string_printf( + string_file_name, + "%s/%s.psk.raw", + app->app_sd_folder, + string_get_cstr(app->raw_file_name)); + lfrfid_worker_read_raw_start( + app->lfworker, + string_get_cstr(string_file_name), + LFRFIDWorkerReadTypePSKOnly, + lfrfid_read_callback, + app); + furi_timer_start(timer, RAW_READ_TIME); + is_psk = true; + } + } + break; + default: + consumed = false; + break; + } + + return consumed; +} + +void LfRfidAppSceneRawRead::on_exit(LfRfidApp* app) { + notification_message(app->notification, &sequence_blink_stop); + app->view_controller.get()->clean(); + lfrfid_worker_stop(app->lfworker); + lfrfid_worker_stop_thread(app->lfworker); + furi_timer_free(timer); + string_clear(string_file_name); +} diff --git a/applications/lfrfid/scene/lfrfid_app_scene_raw_read.h b/applications/lfrfid/scene/lfrfid_app_scene_raw_read.h new file mode 100644 index 00000000000..09ef746390a --- /dev/null +++ b/applications/lfrfid/scene/lfrfid_app_scene_raw_read.h @@ -0,0 +1,15 @@ +#pragma once +#include "../lfrfid_app.h" + +class LfRfidAppSceneRawRead : public GenericScene { +public: + void on_enter(LfRfidApp* app, bool need_restore) final; + bool on_event(LfRfidApp* app, LfRfidApp::Event* event) final; + void on_exit(LfRfidApp* app) final; + +private: + string_t string_file_name; + FuriTimer* timer; + bool is_psk; + bool error; +}; diff --git a/applications/lfrfid/scene/lfrfid_app_scene_raw_success.cpp b/applications/lfrfid/scene/lfrfid_app_scene_raw_success.cpp new file mode 100644 index 00000000000..227ab580a8e --- /dev/null +++ b/applications/lfrfid/scene/lfrfid_app_scene_raw_success.cpp @@ -0,0 +1,45 @@ +#include "lfrfid_app_scene_raw_success.h" +#include "../view/elements/button_element.h" +#include "../view/elements/icon_element.h" +#include "../view/elements/string_element.h" + +void LfRfidAppSceneRawSuccess::on_enter(LfRfidApp* app, bool /* need_restore */) { + string_init(string_info); + + string_printf(string_info, "RAW RFID read success!\r\n"); + string_cat_printf(string_info, "Now you can analyze files\r\n"); + string_cat_printf(string_info, "Or send them to developers"); + + auto container = app->view_controller.get(); + + auto line = container->add(); + line->set_text(string_get_cstr(string_info), 0, 1, 0, AlignLeft, AlignTop, FontSecondary); + + auto button = container->add(); + button->set_type(ButtonElement::Type::Center, "OK"); + button->set_callback(app, LfRfidAppSceneRawSuccess::ok_callback); + + app->view_controller.switch_to(); +} + +bool LfRfidAppSceneRawSuccess::on_event(LfRfidApp* app, LfRfidApp::Event* event) { + bool consumed = false; + if(event->type == LfRfidApp::EventType::Next) { + app->scene_controller.search_and_switch_to_previous_scene( + {LfRfidApp::SceneType::ExtraActions}); + consumed = true; + } + return consumed; +} + +void LfRfidAppSceneRawSuccess::on_exit(LfRfidApp* app) { + app->view_controller.get()->clean(); + string_clear(string_info); +} + +void LfRfidAppSceneRawSuccess::ok_callback(void* context) { + LfRfidApp* app = static_cast(context); + LfRfidApp::Event event; + event.type = LfRfidApp::EventType::Next; + app->view_controller.send_event(&event); +} diff --git a/applications/lfrfid/scene/lfrfid_app_scene_raw_success.h b/applications/lfrfid/scene/lfrfid_app_scene_raw_success.h new file mode 100644 index 00000000000..0a0b0116b54 --- /dev/null +++ b/applications/lfrfid/scene/lfrfid_app_scene_raw_success.h @@ -0,0 +1,13 @@ +#pragma once +#include "../lfrfid_app.h" + +class LfRfidAppSceneRawSuccess : public GenericScene { +public: + void on_enter(LfRfidApp* app, bool need_restore) final; + bool on_event(LfRfidApp* app, LfRfidApp::Event* event) final; + void on_exit(LfRfidApp* app) final; + +private: + string_t string_info; + static void ok_callback(void* context); +}; diff --git a/applications/lfrfid/scene/lfrfid_app_scene_read.cpp b/applications/lfrfid/scene/lfrfid_app_scene_read.cpp index 67279a163e1..120eb1a0726 100644 --- a/applications/lfrfid/scene/lfrfid_app_scene_read.cpp +++ b/applications/lfrfid/scene/lfrfid_app_scene_read.cpp @@ -1,40 +1,100 @@ #include "lfrfid_app_scene_read.h" #include +static void lfrfid_read_callback(LFRFIDWorkerReadResult result, ProtocolId protocol, void* ctx) { + LfRfidApp* app = static_cast(ctx); + LfRfidApp::Event event; + + switch(result) { + case LFRFIDWorkerReadSenseStart: + event.type = LfRfidApp::EventType::ReadEventSenseStart; + break; + case LFRFIDWorkerReadSenseEnd: + event.type = LfRfidApp::EventType::ReadEventSenseEnd; + break; + case LFRFIDWorkerReadSenseCardStart: + event.type = LfRfidApp::EventType::ReadEventSenseCardStart; + break; + case LFRFIDWorkerReadSenseCardEnd: + event.type = LfRfidApp::EventType::ReadEventSenseCardEnd; + break; + case LFRFIDWorkerReadDone: + event.type = LfRfidApp::EventType::ReadEventDone; + break; + case LFRFIDWorkerReadStartASK: + event.type = LfRfidApp::EventType::ReadEventStartASK; + break; + case LFRFIDWorkerReadStartPSK: + event.type = LfRfidApp::EventType::ReadEventStartPSK; + break; + } + + event.payload.signed_int = protocol; + + app->view_controller.send_event(&event); +} + void LfRfidAppSceneRead::on_enter(LfRfidApp* app, bool /* need_restore */) { auto popup = app->view_controller.get(); DOLPHIN_DEED(DolphinDeedRfidRead); - popup->set_header("Reading\nLF RFID", 89, 34, AlignCenter, AlignTop); + if(app->read_type == LFRFIDWorkerReadTypePSKOnly) { + popup->set_header("Reading\nLF RFID\nPSK", 89, 30, AlignCenter, AlignTop); + } else { + popup->set_header("Reading\nLF RFID\nASK", 89, 30, AlignCenter, AlignTop); + } + popup->set_icon(0, 3, &I_RFIDDolphinReceive_97x61); app->view_controller.switch_to(); - app->worker.start_read(); + lfrfid_worker_start_thread(app->lfworker); + lfrfid_worker_read_start(app->lfworker, app->read_type, lfrfid_read_callback, app); + + notification_message(app->notification, &sequence_blink_start_cyan); } bool LfRfidAppSceneRead::on_event(LfRfidApp* app, LfRfidApp::Event* event) { - bool consumed = false; - - if(event->type == LfRfidApp::EventType::Tick) { - if(app->worker.read()) { - DOLPHIN_DEED(DolphinDeedRfidReadSuccess); - notification_message(app->notification, &sequence_success); - app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::ReadSuccess); - } else { - if(app->worker.any_read()) { - notification_message(app->notification, &sequence_blink_yellow_10); - } else if(app->worker.detect()) { - notification_message(app->notification, &sequence_blink_yellow_10); - } else { - notification_message(app->notification, &sequence_blink_cyan_10); - } - } + bool consumed = true; + auto popup = app->view_controller.get(); + + switch(event->type) { + case LfRfidApp::EventType::ReadEventSenseStart: + notification_message(app->notification, &sequence_blink_stop); + notification_message(app->notification, &sequence_blink_start_yellow); + break; + case LfRfidApp::EventType::ReadEventSenseCardStart: + notification_message(app->notification, &sequence_blink_stop); + notification_message(app->notification, &sequence_blink_start_green); + break; + case LfRfidApp::EventType::ReadEventSenseEnd: + case LfRfidApp::EventType::ReadEventSenseCardEnd: + notification_message(app->notification, &sequence_blink_stop); + notification_message(app->notification, &sequence_blink_start_cyan); + break; + case LfRfidApp::EventType::ReadEventDone: + app->protocol_id = event->payload.signed_int; + DOLPHIN_DEED(DolphinDeedRfidReadSuccess); + notification_message(app->notification, &sequence_success); + string_reset(app->file_name); + app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::ReadSuccess); + break; + case LfRfidApp::EventType::ReadEventStartPSK: + popup->set_header("Reading\nLF RFID\nPSK", 89, 30, AlignCenter, AlignTop); + break; + case LfRfidApp::EventType::ReadEventStartASK: + popup->set_header("Reading\nLF RFID\nASK", 89, 30, AlignCenter, AlignTop); + break; + default: + consumed = false; + break; } return consumed; } void LfRfidAppSceneRead::on_exit(LfRfidApp* app) { + notification_message(app->notification, &sequence_blink_stop); app->view_controller.get()->clean(); - app->worker.stop_read(); + lfrfid_worker_stop(app->lfworker); + lfrfid_worker_stop_thread(app->lfworker); } diff --git a/applications/lfrfid/scene/lfrfid_app_scene_read_menu.cpp b/applications/lfrfid/scene/lfrfid_app_scene_read_menu.cpp index 76c91230654..aa3b3f1fbac 100644 --- a/applications/lfrfid/scene/lfrfid_app_scene_read_menu.cpp +++ b/applications/lfrfid/scene/lfrfid_app_scene_read_menu.cpp @@ -24,8 +24,8 @@ bool LfRfidAppSceneReadKeyMenu::on_event(LfRfidApp* app, LfRfidApp::Event* event bool consumed = false; if(event->type == LfRfidApp::EventType::MenuSelected) { - submenu_item_selected = event->payload.menu_index; - switch(event->payload.menu_index) { + submenu_item_selected = event->payload.signed_int; + switch(event->payload.signed_int) { case SubmenuWrite: app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::Write); break; @@ -54,7 +54,7 @@ void LfRfidAppSceneReadKeyMenu::submenu_callback(void* context, uint32_t index) LfRfidApp::Event event; event.type = LfRfidApp::EventType::MenuSelected; - event.payload.menu_index = index; + event.payload.signed_int = index; app->view_controller.send_event(&event); } diff --git a/applications/lfrfid/scene/lfrfid_app_scene_read_success.cpp b/applications/lfrfid/scene/lfrfid_app_scene_read_success.cpp index 010cac2cfa5..277b43a3e5f 100644 --- a/applications/lfrfid/scene/lfrfid_app_scene_read_success.cpp +++ b/applications/lfrfid/scene/lfrfid_app_scene_read_success.cpp @@ -4,10 +4,37 @@ #include "../view/elements/string_element.h" 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]); + string_init(string_info); + string_init(string_header); + + string_init_printf( + string_header, + "%s[%s]", + protocol_dict_get_name(app->dict, app->protocol_id), + protocol_dict_get_manufacturer(app->dict, app->protocol_id)); + + size_t size = protocol_dict_get_data_size(app->dict, app->protocol_id); + uint8_t* data = (uint8_t*)malloc(size); + protocol_dict_get_data(app->dict, app->protocol_id, data, size); + for(uint8_t i = 0; i < size; i++) { + if(i != 0) { + string_cat_printf(string_info, " "); + } + + if(i >= 9) { + string_cat_printf(string_info, "..."); + break; + } else { + string_cat_printf(string_info, "%02X", data[i]); + } + } + free(data); + + string_t render_data; + string_init(render_data); + protocol_dict_render_brief_data(app->dict, render_data, app->protocol_id); + string_cat_printf(string_info, "\r\n%s", string_get_cstr(render_data)); + string_clear(render_data); auto container = app->view_controller.get(); @@ -19,90 +46,11 @@ void LfRfidAppSceneReadSuccess::on_enter(LfRfidApp* app, bool /* need_restore */ button->set_type(ButtonElement::Type::Right, "More"); button->set_callback(app, LfRfidAppSceneReadSuccess::more_callback); - auto icon = container->add(); - icon->set_icon(3, 12, &I_RFIDBigChip_37x36); - auto header = container->add(); - header->set_text(app->worker.key.get_type_text(), 89, 3, 0, AlignCenter); - - auto line_1_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_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(); - - switch(app->worker.key.get_type()) { - case LfrfidKeyType::KeyEM4100: - line_1_text->set_text("HEX:", 65, 23, 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); + header->set_text(string_get_cstr(string_header), 0, 2, 0, AlignLeft, AlignTop, FontPrimary); - 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], "Manchester"); - string_printf(string[2], "%03u,%05u", data[2], (uint16_t)((data[3] << 8) | (data[4]))); - - 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_3_value->set_text( - string_get_cstr(string[2]), 68, 47, 0, AlignLeft, AlignBottom, FontSecondary); - break; - case LfrfidKeyType::KeyH10301: - case LfrfidKeyType::KeyI40134: - 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_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[1] << 8) | (data[2]))); - - 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_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; - } + auto text = container->add(); + text->set_text(string_get_cstr(string_info), 0, 16, 0, AlignLeft, AlignTop, FontSecondary); app->view_controller.switch_to(); @@ -129,9 +77,8 @@ bool LfRfidAppSceneReadSuccess::on_event(LfRfidApp* app, LfRfidApp::Event* event void LfRfidAppSceneReadSuccess::on_exit(LfRfidApp* app) { notification_message_block(app->notification, &sequence_reset_green); app->view_controller.get()->clean(); - string_clear(string[0]); - string_clear(string[1]); - string_clear(string[2]); + string_clear(string_info); + string_clear(string_header); } void LfRfidAppSceneReadSuccess::back_callback(void* context) { diff --git a/applications/lfrfid/scene/lfrfid_app_scene_read_success.h b/applications/lfrfid/scene/lfrfid_app_scene_read_success.h index ac0e3c1b5c1..6d90f631095 100644 --- a/applications/lfrfid/scene/lfrfid_app_scene_read_success.h +++ b/applications/lfrfid/scene/lfrfid_app_scene_read_success.h @@ -11,5 +11,6 @@ class LfRfidAppSceneReadSuccess : public GenericScene { static void back_callback(void* context); static void more_callback(void* context); - string_t string[3]; + string_t string_header; + string_t string_info; }; diff --git a/applications/lfrfid/scene/lfrfid_app_scene_rpc.cpp b/applications/lfrfid/scene/lfrfid_app_scene_rpc.cpp index 54a57c9a2ec..c2e5ec2a61d 100644 --- a/applications/lfrfid/scene/lfrfid_app_scene_rpc.cpp +++ b/applications/lfrfid/scene/lfrfid_app_scene_rpc.cpp @@ -37,12 +37,13 @@ bool LfRfidAppSceneRpc::on_event(LfRfidApp* app, LfRfidApp::Event* event) { bool result = false; if(arg && !emulating) { string_set_str(app->file_path, arg); - if(app->load_key_data(app->file_path, &(app->worker.key), false)) { - app->worker.start_emulate(); + if(app->load_key_data(app->file_path, false)) { + lfrfid_worker_start_thread(app->lfworker); + lfrfid_worker_emulate_start(app->lfworker, (LFRFIDProtocol)app->protocol_id); emulating = true; auto popup = app->view_controller.get(); - app->text_store.set("emulating\n%s", app->worker.key.get_name()); + app->text_store.set("emulating\n%s", string_get_cstr(app->file_name)); popup->set_text(app->text_store.text, 89, 44, AlignCenter, AlignTop); notification_message(app->notification, &sequence_blink_start_magenta); @@ -57,7 +58,8 @@ bool LfRfidAppSceneRpc::on_event(LfRfidApp* app, LfRfidApp::Event* event) { void LfRfidAppSceneRpc::on_exit(LfRfidApp* app) { if(emulating) { - app->worker.stop_emulate(); + lfrfid_worker_stop(app->lfworker); + lfrfid_worker_stop_thread(app->lfworker); notification_message(app->notification, &sequence_blink_stop); } app->view_controller.get()->clean(); diff --git a/applications/lfrfid/scene/lfrfid_app_scene_save_data.cpp b/applications/lfrfid/scene/lfrfid_app_scene_save_data.cpp index 3a13e6838eb..c506cd72993 100644 --- a/applications/lfrfid/scene/lfrfid_app_scene_save_data.cpp +++ b/applications/lfrfid/scene/lfrfid_app_scene_save_data.cpp @@ -3,31 +3,29 @@ void LfRfidAppSceneSaveData::on_enter(LfRfidApp* app, bool need_restore) { auto byte_input = app->view_controller.get(); - RfidKey& key = app->worker.key; - - if(need_restore) printf("restored\r\n"); + size_t size = protocol_dict_get_data_size(app->dict, app->protocol_id); if(need_restore) { - key.set_data(old_key_data, key.get_type_data_count()); + protocol_dict_set_data(app->dict, app->protocol_id, app->old_key_data, size); } else { - memcpy(old_key_data, key.get_data(), key.get_type_data_count()); + protocol_dict_get_data(app->dict, app->protocol_id, app->old_key_data, size); } - memcpy(new_key_data, key.get_data(), key.get_type_data_count()); + protocol_dict_get_data(app->dict, app->protocol_id, app->new_key_data, size); + byte_input->set_header_text("Enter the data in hex"); - byte_input->set_result_callback( - save_callback, NULL, app, new_key_data, app->worker.key.get_type_data_count()); + byte_input->set_result_callback(save_callback, NULL, app, app->new_key_data, size); app->view_controller.switch_to(); } bool LfRfidAppSceneSaveData::on_event(LfRfidApp* app, LfRfidApp::Event* event) { bool consumed = false; - RfidKey& key = app->worker.key; if(event->type == LfRfidApp::EventType::Next) { - key.set_data(new_key_data, key.get_type_data_count()); + size_t size = protocol_dict_get_data_size(app->dict, app->protocol_id); + protocol_dict_set_data(app->dict, app->protocol_id, app->new_key_data, size); DOLPHIN_DEED(DolphinDeedRfidAdd); app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::SaveName); } diff --git a/applications/lfrfid/scene/lfrfid_app_scene_save_data.h b/applications/lfrfid/scene/lfrfid_app_scene_save_data.h index 6458ae649e4..d03cae12510 100644 --- a/applications/lfrfid/scene/lfrfid_app_scene_save_data.h +++ b/applications/lfrfid/scene/lfrfid_app_scene_save_data.h @@ -9,25 +9,4 @@ class LfRfidAppSceneSaveData : public GenericScene { private: static void save_callback(void* context); - uint8_t old_key_data[LFRFID_KEY_SIZE] = { - 0xAA, - 0xAA, - 0xAA, - 0xAA, - 0xAA, - 0xAA, - 0xAA, - 0xAA, - }; - - uint8_t new_key_data[LFRFID_KEY_SIZE] = { - 0xBB, - 0xBB, - 0xBB, - 0xBB, - 0xBB, - 0xBB, - 0xBB, - 0xBB, - }; }; diff --git a/applications/lfrfid/scene/lfrfid_app_scene_save_name.cpp b/applications/lfrfid/scene/lfrfid_app_scene_save_name.cpp index d7ba2c9edba..ed58b6453e9 100644 --- a/applications/lfrfid/scene/lfrfid_app_scene_save_name.cpp +++ b/applications/lfrfid/scene/lfrfid_app_scene_save_name.cpp @@ -4,9 +4,9 @@ #include void LfRfidAppSceneSaveName::on_enter(LfRfidApp* app, bool /* need_restore */) { - const char* key_name = app->worker.key.get_name(); + const char* key_name = string_get_cstr(app->file_name); - bool key_name_empty = !strcmp(key_name, ""); + bool key_name_empty = (string_size(app->file_name) == 0); 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); @@ -18,11 +18,7 @@ void LfRfidAppSceneSaveName::on_enter(LfRfidApp* app, bool /* need_restore */) { text_input->set_header_text("Name the card"); text_input->set_result_callback( - save_callback, - app, - app->text_store.text, - app->worker.key.get_name_length(), - key_name_empty); + save_callback, app, app->text_store.text, LFRFID_KEY_NAME_SIZE, key_name_empty); string_t folder_path; string_init(folder_path); @@ -42,13 +38,13 @@ bool LfRfidAppSceneSaveName::on_event(LfRfidApp* app, LfRfidApp::Event* event) { bool consumed = false; if(event->type == LfRfidApp::EventType::Next) { - if(strlen(app->worker.key.get_name())) { - app->delete_key(&app->worker.key); + if(string_size(app->file_name) > 0) { + app->delete_key(); } - app->worker.key.set_name(app->text_store.text); + string_set_str(app->file_name, app->text_store.text); - if(app->save_key(&app->worker.key)) { + if(app->save_key()) { app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::SaveSuccess); } else { app->scene_controller.search_and_switch_to_previous_scene( diff --git a/applications/lfrfid/scene/lfrfid_app_scene_save_type.cpp b/applications/lfrfid/scene/lfrfid_app_scene_save_type.cpp index 334bb1a03d9..b017e7b057f 100644 --- a/applications/lfrfid/scene/lfrfid_app_scene_save_type.cpp +++ b/applications/lfrfid/scene/lfrfid_app_scene_save_type.cpp @@ -3,12 +3,12 @@ void LfRfidAppSceneSaveType::on_enter(LfRfidApp* app, bool need_restore) { auto submenu = app->view_controller.get(); - for(uint8_t i = 0; i <= keys_count; i++) { + for(uint8_t i = 0; i < keys_count; i++) { string_init_printf( submenu_name[i], "%s %s", - lfrfid_key_get_manufacturer_string(static_cast(i)), - lfrfid_key_get_type_string(static_cast(i))); + protocol_dict_get_manufacturer(app->dict, i), + protocol_dict_get_name(app->dict, i)); submenu->add_item(string_get_cstr(submenu_name[i]), i, submenu_callback, app); } @@ -19,15 +19,15 @@ void LfRfidAppSceneSaveType::on_enter(LfRfidApp* app, bool need_restore) { app->view_controller.switch_to(); // clear key name - app->worker.key.set_name(""); + string_reset(app->file_name); } bool LfRfidAppSceneSaveType::on_event(LfRfidApp* app, LfRfidApp::Event* event) { bool consumed = false; if(event->type == LfRfidApp::EventType::MenuSelected) { - submenu_item_selected = event->payload.menu_index; - app->worker.key.set_type(static_cast(event->payload.menu_index)); + submenu_item_selected = event->payload.signed_int; + app->protocol_id = event->payload.signed_int; app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::SaveData); consumed = true; } @@ -37,7 +37,7 @@ bool LfRfidAppSceneSaveType::on_event(LfRfidApp* app, LfRfidApp::Event* event) { void LfRfidAppSceneSaveType::on_exit(LfRfidApp* app) { app->view_controller.get()->clean(); - for(uint8_t i = 0; i <= keys_count; i++) { + for(uint8_t i = 0; i < keys_count; i++) { string_clear(submenu_name[i]); } } @@ -47,7 +47,7 @@ void LfRfidAppSceneSaveType::submenu_callback(void* context, uint32_t index) { LfRfidApp::Event event; event.type = LfRfidApp::EventType::MenuSelected; - event.payload.menu_index = index; + event.payload.signed_int = index; app->view_controller.send_event(&event); } diff --git a/applications/lfrfid/scene/lfrfid_app_scene_save_type.h b/applications/lfrfid/scene/lfrfid_app_scene_save_type.h index 847c0dabbf3..e4c1be3e64c 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 @@ class LfRfidAppSceneSaveType : public GenericScene { 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::KeyIoProxXSF); - string_t submenu_name[keys_count + 1]; + static const uint8_t keys_count = static_cast(LFRFIDProtocol::LFRFIDProtocolMax); + string_t submenu_name[keys_count]; }; diff --git a/applications/lfrfid/scene/lfrfid_app_scene_saved_info.cpp b/applications/lfrfid/scene/lfrfid_app_scene_saved_info.cpp index dd4a3d4ebff..614dd505c2e 100644 --- a/applications/lfrfid/scene/lfrfid_app_scene_saved_info.cpp +++ b/applications/lfrfid/scene/lfrfid_app_scene_saved_info.cpp @@ -4,65 +4,36 @@ #include "../view/elements/string_element.h" void LfRfidAppSceneSavedInfo::on_enter(LfRfidApp* app, bool /* need_restore */) { - string_init(string_data); - string_init(string_decrypted); - - auto container = app->view_controller.get(); - - auto button = container->add(); - button->set_type(ButtonElement::Type::Left, "Back"); - button->set_callback(app, LfRfidAppSceneSavedInfo::back_callback); - - auto line_1 = container->add(); - auto line_2 = container->add(); - auto line_3 = container->add(); - auto line_4 = container->add(); - - RfidKey& key = app->worker.key; - const uint8_t* data = key.get_data(); - - for(uint8_t i = 0; i < key.get_type_data_count(); i++) { + string_init(string_info); + + string_printf( + string_info, + "%s [%s]\r\n", + string_get_cstr(app->file_name), + protocol_dict_get_name(app->dict, app->protocol_id)); + + size_t size = protocol_dict_get_data_size(app->dict, app->protocol_id); + uint8_t* data = (uint8_t*)malloc(size); + protocol_dict_get_data(app->dict, app->protocol_id, data, size); + for(uint8_t i = 0; i < size; i++) { if(i != 0) { - string_cat_printf(string_data, " "); + string_cat_printf(string_info, " "); } - string_cat_printf(string_data, "%02X", data[i]); - } - line_1->set_text(key.get_name(), 64, 17, 128 - 2, AlignCenter, AlignBottom, FontSecondary); - line_2->set_text( - string_get_cstr(string_data), 64, 29, 0, AlignCenter, AlignBottom, FontPrimary); + string_cat_printf(string_info, "%02X", data[i]); + } + free(data); - switch(key.get_type()) { - case LfrfidKeyType::KeyEM4100: - string_printf( - string_decrypted, "%03u,%05u", data[2], (uint16_t)((data[3] << 8) | (data[4]))); + string_t render_data; + string_init(render_data); + protocol_dict_render_data(app->dict, render_data, app->protocol_id); + string_cat_printf(string_info, "\r\n%s", string_get_cstr(render_data)); + string_clear(render_data); - break; - case LfrfidKeyType::KeyH10301: - case LfrfidKeyType::KeyI40134: - string_printf( - string_decrypted, "FC: %u ID: %u", data[0], (uint16_t)((data[1] << 8) | (data[2]))); - break; - 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); + auto container = app->view_controller.get(); - line_4->set_text( - lfrfid_key_get_type_string(key.get_type()), - 64, - 49, - 0, - AlignCenter, - AlignBottom, - FontSecondary); + auto line_1 = container->add(); + line_1->set_text(string_get_cstr(string_info), 0, 1, 0, AlignLeft, AlignTop, FontSecondary); app->view_controller.switch_to(); } @@ -73,13 +44,5 @@ bool LfRfidAppSceneSavedInfo::on_event(LfRfidApp* /* app */, LfRfidApp::Event* / void LfRfidAppSceneSavedInfo::on_exit(LfRfidApp* app) { app->view_controller.get()->clean(); - string_clear(string_data); - string_clear(string_decrypted); -} - -void LfRfidAppSceneSavedInfo::back_callback(void* context) { - LfRfidApp* app = static_cast(context); - LfRfidApp::Event event; - event.type = LfRfidApp::EventType::Back; - app->view_controller.send_event(&event); + string_clear(string_info); } diff --git a/applications/lfrfid/scene/lfrfid_app_scene_saved_info.h b/applications/lfrfid/scene/lfrfid_app_scene_saved_info.h index 5aa33e8ad7b..b0b588bcb1a 100644 --- a/applications/lfrfid/scene/lfrfid_app_scene_saved_info.h +++ b/applications/lfrfid/scene/lfrfid_app_scene_saved_info.h @@ -8,8 +8,5 @@ class LfRfidAppSceneSavedInfo : public GenericScene { void on_exit(LfRfidApp* app) final; private: - static void back_callback(void* context); - - string_t string_data; - string_t string_decrypted; + string_t string_info; }; diff --git a/applications/lfrfid/scene/lfrfid_app_scene_saved_key_menu.cpp b/applications/lfrfid/scene/lfrfid_app_scene_saved_key_menu.cpp index e6677fe8daf..e7a38d8ad1b 100644 --- a/applications/lfrfid/scene/lfrfid_app_scene_saved_key_menu.cpp +++ b/applications/lfrfid/scene/lfrfid_app_scene_saved_key_menu.cpp @@ -28,8 +28,8 @@ bool LfRfidAppSceneSavedKeyMenu::on_event(LfRfidApp* app, LfRfidApp::Event* even bool consumed = false; if(event->type == LfRfidApp::EventType::MenuSelected) { - submenu_item_selected = event->payload.menu_index; - switch(event->payload.menu_index) { + submenu_item_selected = event->payload.signed_int; + switch(event->payload.signed_int) { case SubmenuEmulate: app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::Emulate); break; @@ -61,7 +61,7 @@ void LfRfidAppSceneSavedKeyMenu::submenu_callback(void* context, uint32_t index) LfRfidApp::Event event; event.type = LfRfidApp::EventType::MenuSelected; - event.payload.menu_index = index; + event.payload.signed_int = index; app->view_controller.send_event(&event); } diff --git a/applications/lfrfid/scene/lfrfid_app_scene_start.cpp b/applications/lfrfid/scene/lfrfid_app_scene_start.cpp index f5afad5c988..5005c9afb57 100644 --- a/applications/lfrfid/scene/lfrfid_app_scene_start.cpp +++ b/applications/lfrfid/scene/lfrfid_app_scene_start.cpp @@ -4,6 +4,7 @@ typedef enum { SubmenuRead, SubmenuSaved, SubmenuAddManually, + SubmenuExtraActions, } SubmenuIndex; void LfRfidAppSceneStart::on_enter(LfRfidApp* app, bool need_restore) { @@ -12,6 +13,7 @@ void LfRfidAppSceneStart::on_enter(LfRfidApp* app, bool need_restore) { submenu->add_item("Read", SubmenuRead, submenu_callback, app); submenu->add_item("Saved", SubmenuSaved, submenu_callback, app); submenu->add_item("Add Manually", SubmenuAddManually, submenu_callback, app); + submenu->add_item("Extra Actions", SubmenuExtraActions, submenu_callback, app); if(need_restore) { submenu->set_selected_item(submenu_item_selected); @@ -20,15 +22,17 @@ void LfRfidAppSceneStart::on_enter(LfRfidApp* app, bool need_restore) { app->view_controller.switch_to(); // clear key - app->worker.key.clear(); + string_reset(app->file_name); + app->protocol_id = PROTOCOL_NO; + app->read_type = LFRFIDWorkerReadTypeAuto; } bool LfRfidAppSceneStart::on_event(LfRfidApp* app, LfRfidApp::Event* event) { bool consumed = false; if(event->type == LfRfidApp::EventType::MenuSelected) { - submenu_item_selected = event->payload.menu_index; - switch(event->payload.menu_index) { + submenu_item_selected = event->payload.signed_int; + switch(event->payload.signed_int) { case SubmenuRead: app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::Read); break; @@ -38,6 +42,9 @@ bool LfRfidAppSceneStart::on_event(LfRfidApp* app, LfRfidApp::Event* event) { case SubmenuAddManually: app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::SaveType); break; + case SubmenuExtraActions: + app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::ExtraActions); + break; } consumed = true; } @@ -54,7 +61,7 @@ void LfRfidAppSceneStart::submenu_callback(void* context, uint32_t index) { LfRfidApp::Event event; event.type = LfRfidApp::EventType::MenuSelected; - event.payload.menu_index = index; + event.payload.signed_int = index; app->view_controller.send_event(&event); } diff --git a/applications/lfrfid/scene/lfrfid_app_scene_write.cpp b/applications/lfrfid/scene/lfrfid_app_scene_write.cpp index 274ba31582f..8e04d8e8de9 100644 --- a/applications/lfrfid/scene/lfrfid_app_scene_write.cpp +++ b/applications/lfrfid/scene/lfrfid_app_scene_write.cpp @@ -1,66 +1,79 @@ #include "lfrfid_app_scene_write.h" -void LfRfidAppSceneWrite::on_enter(LfRfidApp* app, bool /* need_restore */) { - card_not_supported = false; - string_init(data_string); - - const uint8_t* data = app->worker.key.get_data(); +static void lfrfid_write_callback(LFRFIDWorkerWriteResult result, void* ctx) { + LfRfidApp* app = static_cast(ctx); + LfRfidApp::Event event; - for(uint8_t i = 0; i < app->worker.key.get_type_data_count(); i++) { - string_cat_printf(data_string, "%02X", data[i]); + switch(result) { + case LFRFIDWorkerWriteOK: + event.type = LfRfidApp::EventType::WriteEventOK; + break; + case LFRFIDWorkerWriteProtocolCannotBeWritten: + event.type = LfRfidApp::EventType::WriteEventProtocolCannotBeWritten; + break; + case LFRFIDWorkerWriteFobCannotBeWritten: + event.type = LfRfidApp::EventType::WriteEventFobCannotBeWritten; + break; + case LFRFIDWorkerWriteTooLongToWrite: + event.type = LfRfidApp::EventType::WriteEventTooLongToWrite; + break; } + app->view_controller.send_event(&event); +} + +void LfRfidAppSceneWrite::on_enter(LfRfidApp* app, bool /* need_restore */) { auto popup = app->view_controller.get(); popup->set_header("Writing", 89, 30, AlignCenter, AlignTop); - if(strlen(app->worker.key.get_name())) { - popup->set_text(app->worker.key.get_name(), 89, 43, AlignCenter, AlignTop); + if(string_size(app->file_name)) { + popup->set_text(string_get_cstr(app->file_name), 89, 43, AlignCenter, AlignTop); } else { - popup->set_text(string_get_cstr(data_string), 89, 43, AlignCenter, AlignTop); + popup->set_text( + protocol_dict_get_name(app->dict, app->protocol_id), 89, 43, AlignCenter, AlignTop); } popup->set_icon(0, 3, &I_RFIDDolphinSend_97x61); app->view_controller.switch_to(); - app->worker.start_write(); + lfrfid_worker_start_thread(app->lfworker); + lfrfid_worker_write_start( + app->lfworker, (LFRFIDProtocol)app->protocol_id, lfrfid_write_callback, app); + notification_message(app->notification, &sequence_blink_start_magenta); } bool LfRfidAppSceneWrite::on_event(LfRfidApp* app, LfRfidApp::Event* event) { - bool consumed = false; - - if(event->type == LfRfidApp::EventType::Tick) { - RfidWorker::WriteResult result = app->worker.write(); + bool consumed = true; + auto popup = app->view_controller.get(); - switch(result) { - case RfidWorker::WriteResult::Nothing: - notification_message(app->notification, &sequence_blink_magenta_10); - break; - case RfidWorker::WriteResult::Ok: - notification_message(app->notification, &sequence_success); - app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::WriteSuccess); - break; - case RfidWorker::WriteResult::NotWritable: - if(!card_not_supported) { - auto popup = app->view_controller.get(); - popup->set_icon(72, 17, &I_DolphinCommon_56x48); - popup->set_header("Still trying to write...", 64, 3, AlignCenter, AlignTop); - popup->set_text( - "Make sure this\ncard is writable\nand not\nprotected.", - 3, - 17, - AlignLeft, - AlignTop); - card_not_supported = true; - } - notification_message(app->notification, &sequence_blink_yellow_10); - break; - } + switch(event->type) { + case LfRfidApp::EventType::WriteEventOK: + notification_message(app->notification, &sequence_success); + app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::WriteSuccess); + break; + case LfRfidApp::EventType::WriteEventProtocolCannotBeWritten: + popup->set_icon(72, 17, &I_DolphinCommon_56x48); + popup->set_header("Error", 64, 3, AlignCenter, AlignTop); + popup->set_text("This protocol\ncannot be written", 3, 17, AlignLeft, AlignTop); + notification_message(app->notification, &sequence_blink_start_red); + break; + case LfRfidApp::EventType::WriteEventFobCannotBeWritten: + case LfRfidApp::EventType::WriteEventTooLongToWrite: + popup->set_icon(72, 17, &I_DolphinCommon_56x48); + popup->set_header("Still trying to write...", 64, 3, AlignCenter, AlignTop); + popup->set_text( + "Make sure this\ncard is writable\nand not\nprotected.", 3, 17, AlignLeft, AlignTop); + notification_message(app->notification, &sequence_blink_start_yellow); + break; + default: + consumed = false; } return consumed; } void LfRfidAppSceneWrite::on_exit(LfRfidApp* app) { + notification_message(app->notification, &sequence_blink_stop); app->view_controller.get()->clean(); - app->worker.stop_write(); - string_clear(data_string); + lfrfid_worker_stop(app->lfworker); + lfrfid_worker_stop_thread(app->lfworker); } diff --git a/applications/lfrfid/scene/lfrfid_app_scene_write.h b/applications/lfrfid/scene/lfrfid_app_scene_write.h index 3abadebab5a..7564eff9dd9 100644 --- a/applications/lfrfid/scene/lfrfid_app_scene_write.h +++ b/applications/lfrfid/scene/lfrfid_app_scene_write.h @@ -6,8 +6,4 @@ class LfRfidAppSceneWrite : public GenericScene { void on_enter(LfRfidApp* app, bool need_restore) final; bool on_event(LfRfidApp* app, LfRfidApp::Event* event) final; void on_exit(LfRfidApp* app) final; - -private: - string_t data_string; - bool card_not_supported; }; diff --git a/applications/storage/storage.h b/applications/storage/storage.h index 55a951d128c..1a7c934950a 100644 --- a/applications/storage/storage.h +++ b/applications/storage/storage.h @@ -136,6 +136,15 @@ bool storage_file_sync(File* file); */ bool storage_file_eof(File* file); +/** + * @brief Check that file exists + * + * @param storage + * @param path + * @return true if file exists + */ +bool storage_file_exists(Storage* storage, const char* path); + /******************* Dir Functions *******************/ /** Opens a directory to get objects from it diff --git a/applications/storage/storage_external_api.c b/applications/storage/storage_external_api.c index b32080dfcc3..80cafb2828c 100644 --- a/applications/storage/storage_external_api.c +++ b/applications/storage/storage_external_api.c @@ -240,6 +240,18 @@ bool storage_file_eof(File* file) { return S_RETURN_BOOL; } +bool storage_file_exists(Storage* storage, const char* path) { + bool exist = false; + FileInfo fileinfo; + FS_Error error = storage_common_stat(storage, path, &fileinfo); + + if(error == FSE_OK && !(fileinfo.flags & FSF_DIRECTORY)) { + exist = true; + } + + return exist; +} + /****************** DIR ******************/ static bool storage_dir_open_internal(File* file, const char* path) { diff --git a/applications/unit_tests/lfrfid/bit_lib_test.c b/applications/unit_tests/lfrfid/bit_lib_test.c new file mode 100644 index 00000000000..7266157033d --- /dev/null +++ b/applications/unit_tests/lfrfid/bit_lib_test.c @@ -0,0 +1,473 @@ +#include +#include "../minunit.h" +#include + +MU_TEST(test_bit_lib_increment_index) { + uint32_t index = 0; + + // test increment + for(uint32_t i = 0; i < 31; ++i) { + bit_lib_increment_index(index, 32); + mu_assert_int_eq(i + 1, index); + } + + // test wrap around + for(uint32_t i = 0; i < 512; ++i) { + bit_lib_increment_index(index, 32); + mu_assert_int_less_than(32, index); + } +} + +MU_TEST(test_bit_lib_is_set) { + uint32_t value = 0x0000FFFF; + + for(uint32_t i = 0; i < 16; ++i) { + mu_check(bit_lib_bit_is_set(value, i)); + mu_check(!bit_lib_bit_is_not_set(value, i)); + } + + for(uint32_t i = 16; i < 32; ++i) { + mu_check(!bit_lib_bit_is_set(value, i)); + mu_check(bit_lib_bit_is_not_set(value, i)); + } +} + +MU_TEST(test_bit_lib_push) { +#define TEST_BIT_LIB_PUSH_DATA_SIZE 4 + uint8_t data[TEST_BIT_LIB_PUSH_DATA_SIZE] = {0}; + uint8_t expected_data_1[TEST_BIT_LIB_PUSH_DATA_SIZE] = {0x00, 0x00, 0x0F, 0xFF}; + uint8_t expected_data_2[TEST_BIT_LIB_PUSH_DATA_SIZE] = {0x00, 0xFF, 0xF0, 0x00}; + uint8_t expected_data_3[TEST_BIT_LIB_PUSH_DATA_SIZE] = {0xFF, 0x00, 0x00, 0xFF}; + uint8_t expected_data_4[TEST_BIT_LIB_PUSH_DATA_SIZE] = {0xFF, 0xFF, 0xFF, 0xFF}; + uint8_t expected_data_5[TEST_BIT_LIB_PUSH_DATA_SIZE] = {0x00, 0x00, 0x00, 0x00}; + uint8_t expected_data_6[TEST_BIT_LIB_PUSH_DATA_SIZE] = {0xCC, 0xCC, 0xCC, 0xCC}; + + for(uint32_t i = 0; i < 12; ++i) { + bit_lib_push_bit(data, TEST_BIT_LIB_PUSH_DATA_SIZE, true); + } + mu_assert_mem_eq(expected_data_1, data, TEST_BIT_LIB_PUSH_DATA_SIZE); + + for(uint32_t i = 0; i < 12; ++i) { + bit_lib_push_bit(data, TEST_BIT_LIB_PUSH_DATA_SIZE, false); + } + mu_assert_mem_eq(expected_data_2, data, TEST_BIT_LIB_PUSH_DATA_SIZE); + + for(uint32_t i = 0; i < 4; ++i) { + bit_lib_push_bit(data, TEST_BIT_LIB_PUSH_DATA_SIZE, false); + } + for(uint32_t i = 0; i < 8; ++i) { + bit_lib_push_bit(data, TEST_BIT_LIB_PUSH_DATA_SIZE, true); + } + mu_assert_mem_eq(expected_data_3, data, TEST_BIT_LIB_PUSH_DATA_SIZE); + + for(uint32_t i = 0; i < TEST_BIT_LIB_PUSH_DATA_SIZE * 8; ++i) { + bit_lib_push_bit(data, TEST_BIT_LIB_PUSH_DATA_SIZE, true); + } + mu_assert_mem_eq(expected_data_4, data, TEST_BIT_LIB_PUSH_DATA_SIZE); + + for(uint32_t i = 0; i < TEST_BIT_LIB_PUSH_DATA_SIZE * 8; ++i) { + bit_lib_push_bit(data, TEST_BIT_LIB_PUSH_DATA_SIZE, false); + } + mu_assert_mem_eq(expected_data_5, data, TEST_BIT_LIB_PUSH_DATA_SIZE); + + for(uint32_t i = 0; i < TEST_BIT_LIB_PUSH_DATA_SIZE * 2; ++i) { + bit_lib_push_bit(data, TEST_BIT_LIB_PUSH_DATA_SIZE, true); + bit_lib_push_bit(data, TEST_BIT_LIB_PUSH_DATA_SIZE, true); + bit_lib_push_bit(data, TEST_BIT_LIB_PUSH_DATA_SIZE, false); + bit_lib_push_bit(data, TEST_BIT_LIB_PUSH_DATA_SIZE, false); + } + mu_assert_mem_eq(expected_data_6, data, TEST_BIT_LIB_PUSH_DATA_SIZE); +} + +MU_TEST(test_bit_lib_set_bit) { + uint8_t value[2] = {0x00, 0xFF}; + bit_lib_set_bit(value, 15, false); + mu_assert_mem_eq(value, ((uint8_t[]){0x00, 0xFE}), 2); + bit_lib_set_bit(value, 14, false); + mu_assert_mem_eq(value, ((uint8_t[]){0x00, 0xFC}), 2); + bit_lib_set_bit(value, 13, false); + mu_assert_mem_eq(value, ((uint8_t[]){0x00, 0xF8}), 2); + bit_lib_set_bit(value, 12, false); + mu_assert_mem_eq(value, ((uint8_t[]){0x00, 0xF0}), 2); + bit_lib_set_bit(value, 11, false); + mu_assert_mem_eq(value, ((uint8_t[]){0x00, 0xE0}), 2); + bit_lib_set_bit(value, 10, false); + mu_assert_mem_eq(value, ((uint8_t[]){0x00, 0xC0}), 2); + bit_lib_set_bit(value, 9, false); + mu_assert_mem_eq(value, ((uint8_t[]){0x00, 0x80}), 2); + bit_lib_set_bit(value, 8, false); + mu_assert_mem_eq(value, ((uint8_t[]){0x00, 0x00}), 2); + + bit_lib_set_bit(value, 7, true); + mu_assert_mem_eq(value, ((uint8_t[]){0x01, 0x00}), 2); + bit_lib_set_bit(value, 6, true); + mu_assert_mem_eq(value, ((uint8_t[]){0x03, 0x00}), 2); + bit_lib_set_bit(value, 5, true); + mu_assert_mem_eq(value, ((uint8_t[]){0x07, 0x00}), 2); + bit_lib_set_bit(value, 4, true); + mu_assert_mem_eq(value, ((uint8_t[]){0x0F, 0x00}), 2); + bit_lib_set_bit(value, 3, true); + mu_assert_mem_eq(value, ((uint8_t[]){0x1F, 0x00}), 2); + bit_lib_set_bit(value, 2, true); + mu_assert_mem_eq(value, ((uint8_t[]){0x3F, 0x00}), 2); + bit_lib_set_bit(value, 1, true); + mu_assert_mem_eq(value, ((uint8_t[]){0x7F, 0x00}), 2); + bit_lib_set_bit(value, 0, true); + mu_assert_mem_eq(value, ((uint8_t[]){0xFF, 0x00}), 2); +} + +MU_TEST(test_bit_lib_set_bits) { + uint8_t value[2] = {0b00000000, 0b11111111}; + // set 4 bits to 0b0100 from 12 index + bit_lib_set_bits(value, 12, 0b0100, 4); + // [0100] + mu_assert_mem_eq(value, ((uint8_t[]){0b00000000, 0b11110100}), 2); + + // set 2 bits to 0b11 from 11 index + bit_lib_set_bits(value, 11, 0b11, 2); + // [11] + mu_assert_mem_eq(value, ((uint8_t[]){0b00000000, 0b11111100}), 2); + + // set 3 bits to 0b111 from 0 index + bit_lib_set_bits(value, 0, 0b111, 3); + // [111] + mu_assert_mem_eq(value, ((uint8_t[]){0b11100000, 0b11111100}), 2); + + // set 8 bits to 0b11111000 from 3 index + bit_lib_set_bits(value, 3, 0b11111000, 8); + // [11111 000] + mu_assert_mem_eq(value, ((uint8_t[]){0b11111111, 0b00011100}), 2); +} + +MU_TEST(test_bit_lib_get_bit) { + uint8_t value[2] = {0b00000000, 0b11111111}; + for(uint32_t i = 0; i < 8; ++i) { + mu_check(bit_lib_get_bit(value, i) == false); + } + for(uint32_t i = 8; i < 16; ++i) { + mu_check(bit_lib_get_bit(value, i) == true); + } +} + +MU_TEST(test_bit_lib_get_bits) { + uint8_t value[2] = {0b00000000, 0b11111111}; + mu_assert_int_eq(0b00000000, bit_lib_get_bits(value, 0, 8)); + mu_assert_int_eq(0b00000001, bit_lib_get_bits(value, 1, 8)); + mu_assert_int_eq(0b00000011, bit_lib_get_bits(value, 2, 8)); + mu_assert_int_eq(0b00000111, bit_lib_get_bits(value, 3, 8)); + mu_assert_int_eq(0b00001111, bit_lib_get_bits(value, 4, 8)); + mu_assert_int_eq(0b00011111, bit_lib_get_bits(value, 5, 8)); + mu_assert_int_eq(0b00111111, bit_lib_get_bits(value, 6, 8)); + mu_assert_int_eq(0b01111111, bit_lib_get_bits(value, 7, 8)); + mu_assert_int_eq(0b11111111, bit_lib_get_bits(value, 8, 8)); +} + +MU_TEST(test_bit_lib_get_bits_16) { + uint8_t value[2] = {0b00001001, 0b10110001}; + mu_assert_int_eq(0b0, bit_lib_get_bits_16(value, 0, 1)); + mu_assert_int_eq(0b00, bit_lib_get_bits_16(value, 0, 2)); + mu_assert_int_eq(0b000, bit_lib_get_bits_16(value, 0, 3)); + mu_assert_int_eq(0b0000, bit_lib_get_bits_16(value, 0, 4)); + mu_assert_int_eq(0b00001, bit_lib_get_bits_16(value, 0, 5)); + mu_assert_int_eq(0b000010, bit_lib_get_bits_16(value, 0, 6)); + mu_assert_int_eq(0b0000100, bit_lib_get_bits_16(value, 0, 7)); + mu_assert_int_eq(0b00001001, bit_lib_get_bits_16(value, 0, 8)); + mu_assert_int_eq(0b000010011, bit_lib_get_bits_16(value, 0, 9)); + mu_assert_int_eq(0b0000100110, bit_lib_get_bits_16(value, 0, 10)); + mu_assert_int_eq(0b00001001101, bit_lib_get_bits_16(value, 0, 11)); + mu_assert_int_eq(0b000010011011, bit_lib_get_bits_16(value, 0, 12)); + mu_assert_int_eq(0b0000100110110, bit_lib_get_bits_16(value, 0, 13)); + mu_assert_int_eq(0b00001001101100, bit_lib_get_bits_16(value, 0, 14)); + mu_assert_int_eq(0b000010011011000, bit_lib_get_bits_16(value, 0, 15)); + mu_assert_int_eq(0b0000100110110001, bit_lib_get_bits_16(value, 0, 16)); +} + +MU_TEST(test_bit_lib_get_bits_32) { + uint8_t value[4] = {0b00001001, 0b10110001, 0b10001100, 0b01100010}; + mu_assert_int_eq(0b0, bit_lib_get_bits_32(value, 0, 1)); + mu_assert_int_eq(0b00, bit_lib_get_bits_32(value, 0, 2)); + mu_assert_int_eq(0b000, bit_lib_get_bits_32(value, 0, 3)); + mu_assert_int_eq(0b0000, bit_lib_get_bits_32(value, 0, 4)); + mu_assert_int_eq(0b00001, bit_lib_get_bits_32(value, 0, 5)); + mu_assert_int_eq(0b000010, bit_lib_get_bits_32(value, 0, 6)); + mu_assert_int_eq(0b0000100, bit_lib_get_bits_32(value, 0, 7)); + mu_assert_int_eq(0b00001001, bit_lib_get_bits_32(value, 0, 8)); + mu_assert_int_eq(0b000010011, bit_lib_get_bits_32(value, 0, 9)); + mu_assert_int_eq(0b0000100110, bit_lib_get_bits_32(value, 0, 10)); + mu_assert_int_eq(0b00001001101, bit_lib_get_bits_32(value, 0, 11)); + mu_assert_int_eq(0b000010011011, bit_lib_get_bits_32(value, 0, 12)); + mu_assert_int_eq(0b0000100110110, bit_lib_get_bits_32(value, 0, 13)); + mu_assert_int_eq(0b00001001101100, bit_lib_get_bits_32(value, 0, 14)); + mu_assert_int_eq(0b000010011011000, bit_lib_get_bits_32(value, 0, 15)); + mu_assert_int_eq(0b0000100110110001, bit_lib_get_bits_32(value, 0, 16)); + mu_assert_int_eq(0b00001001101100011, bit_lib_get_bits_32(value, 0, 17)); + mu_assert_int_eq(0b000010011011000110, bit_lib_get_bits_32(value, 0, 18)); + mu_assert_int_eq(0b0000100110110001100, bit_lib_get_bits_32(value, 0, 19)); + mu_assert_int_eq(0b00001001101100011000, bit_lib_get_bits_32(value, 0, 20)); + mu_assert_int_eq(0b000010011011000110001, bit_lib_get_bits_32(value, 0, 21)); + mu_assert_int_eq(0b0000100110110001100011, bit_lib_get_bits_32(value, 0, 22)); + mu_assert_int_eq(0b00001001101100011000110, bit_lib_get_bits_32(value, 0, 23)); + mu_assert_int_eq(0b000010011011000110001100, bit_lib_get_bits_32(value, 0, 24)); + mu_assert_int_eq(0b0000100110110001100011000, bit_lib_get_bits_32(value, 0, 25)); + mu_assert_int_eq(0b00001001101100011000110001, bit_lib_get_bits_32(value, 0, 26)); + mu_assert_int_eq(0b000010011011000110001100011, bit_lib_get_bits_32(value, 0, 27)); + mu_assert_int_eq(0b0000100110110001100011000110, bit_lib_get_bits_32(value, 0, 28)); + mu_assert_int_eq(0b00001001101100011000110001100, bit_lib_get_bits_32(value, 0, 29)); + mu_assert_int_eq(0b000010011011000110001100011000, bit_lib_get_bits_32(value, 0, 30)); + mu_assert_int_eq(0b0000100110110001100011000110001, bit_lib_get_bits_32(value, 0, 31)); + mu_assert_int_eq(0b00001001101100011000110001100010, bit_lib_get_bits_32(value, 0, 32)); +} + +MU_TEST(test_bit_lib_test_parity_u32) { + // test even parity + mu_assert_int_eq(bit_lib_test_parity_32(0b00000000, BitLibParityEven), 0); + mu_assert_int_eq(bit_lib_test_parity_32(0b00000001, BitLibParityEven), 1); + mu_assert_int_eq(bit_lib_test_parity_32(0b00000010, BitLibParityEven), 1); + mu_assert_int_eq(bit_lib_test_parity_32(0b00000011, BitLibParityEven), 0); + mu_assert_int_eq(bit_lib_test_parity_32(0b00000100, BitLibParityEven), 1); + mu_assert_int_eq(bit_lib_test_parity_32(0b00000101, BitLibParityEven), 0); + mu_assert_int_eq(bit_lib_test_parity_32(0b00000110, BitLibParityEven), 0); + mu_assert_int_eq(bit_lib_test_parity_32(0b00000111, BitLibParityEven), 1); + mu_assert_int_eq(bit_lib_test_parity_32(0b00001000, BitLibParityEven), 1); + mu_assert_int_eq(bit_lib_test_parity_32(0b00001001, BitLibParityEven), 0); + mu_assert_int_eq(bit_lib_test_parity_32(0b00001010, BitLibParityEven), 0); + mu_assert_int_eq(bit_lib_test_parity_32(0b00001011, BitLibParityEven), 1); + mu_assert_int_eq(bit_lib_test_parity_32(0b00001100, BitLibParityEven), 0); + mu_assert_int_eq(bit_lib_test_parity_32(0b00001101, BitLibParityEven), 1); + mu_assert_int_eq(bit_lib_test_parity_32(0b00001110, BitLibParityEven), 1); + mu_assert_int_eq(bit_lib_test_parity_32(0b00001111, BitLibParityEven), 0); + mu_assert_int_eq(bit_lib_test_parity_32(0b00010000, BitLibParityEven), 1); + + // test odd parity + mu_assert_int_eq(bit_lib_test_parity_32(0b00000000, BitLibParityOdd), 1); + mu_assert_int_eq(bit_lib_test_parity_32(0b00000001, BitLibParityOdd), 0); + mu_assert_int_eq(bit_lib_test_parity_32(0b00000010, BitLibParityOdd), 0); + mu_assert_int_eq(bit_lib_test_parity_32(0b00000011, BitLibParityOdd), 1); + mu_assert_int_eq(bit_lib_test_parity_32(0b00000100, BitLibParityOdd), 0); + mu_assert_int_eq(bit_lib_test_parity_32(0b00000101, BitLibParityOdd), 1); + mu_assert_int_eq(bit_lib_test_parity_32(0b00000110, BitLibParityOdd), 1); + mu_assert_int_eq(bit_lib_test_parity_32(0b00000111, BitLibParityOdd), 0); + mu_assert_int_eq(bit_lib_test_parity_32(0b00001000, BitLibParityOdd), 0); + mu_assert_int_eq(bit_lib_test_parity_32(0b00001001, BitLibParityOdd), 1); + mu_assert_int_eq(bit_lib_test_parity_32(0b00001010, BitLibParityOdd), 1); + mu_assert_int_eq(bit_lib_test_parity_32(0b00001011, BitLibParityOdd), 0); + mu_assert_int_eq(bit_lib_test_parity_32(0b00001100, BitLibParityOdd), 1); + mu_assert_int_eq(bit_lib_test_parity_32(0b00001101, BitLibParityOdd), 0); + mu_assert_int_eq(bit_lib_test_parity_32(0b00001110, BitLibParityOdd), 0); + mu_assert_int_eq(bit_lib_test_parity_32(0b00001111, BitLibParityOdd), 1); + mu_assert_int_eq(bit_lib_test_parity_32(0b00010000, BitLibParityOdd), 0); +} + +MU_TEST(test_bit_lib_test_parity) { + // next data contains valid parity for 1-3 nibble and invalid for 4 nibble + uint8_t data_always_0_parity[2] = {0b11101110, 0b11101111}; + uint8_t data_always_1_parity[2] = {0b00010001, 0b00010000}; + uint8_t data_always_odd_parity[2] = {0b00000011, 0b11110111}; + uint8_t data_always_even_parity[2] = {0b00010111, 0b10110011}; + + // test alawys 0 parity + mu_check(bit_lib_test_parity(data_always_0_parity, 0, 12, BitLibParityAlways0, 4)); + mu_check(bit_lib_test_parity(data_always_0_parity, 4, 8, BitLibParityAlways0, 4)); + mu_check(bit_lib_test_parity(data_always_0_parity, 8, 4, BitLibParityAlways0, 4)); + mu_check(bit_lib_test_parity(data_always_1_parity, 12, 4, BitLibParityAlways0, 4)); + + mu_check(!bit_lib_test_parity(data_always_0_parity, 0, 16, BitLibParityAlways0, 4)); + mu_check(!bit_lib_test_parity(data_always_0_parity, 4, 12, BitLibParityAlways0, 4)); + mu_check(!bit_lib_test_parity(data_always_0_parity, 8, 8, BitLibParityAlways0, 4)); + mu_check(!bit_lib_test_parity(data_always_0_parity, 12, 4, BitLibParityAlways0, 4)); + + // test alawys 1 parity + mu_check(bit_lib_test_parity(data_always_1_parity, 0, 12, BitLibParityAlways1, 4)); + mu_check(bit_lib_test_parity(data_always_1_parity, 4, 8, BitLibParityAlways1, 4)); + mu_check(bit_lib_test_parity(data_always_1_parity, 8, 4, BitLibParityAlways1, 4)); + mu_check(bit_lib_test_parity(data_always_0_parity, 12, 4, BitLibParityAlways1, 4)); + + mu_check(!bit_lib_test_parity(data_always_1_parity, 0, 16, BitLibParityAlways1, 4)); + mu_check(!bit_lib_test_parity(data_always_1_parity, 4, 12, BitLibParityAlways1, 4)); + mu_check(!bit_lib_test_parity(data_always_1_parity, 8, 8, BitLibParityAlways1, 4)); + mu_check(!bit_lib_test_parity(data_always_1_parity, 12, 4, BitLibParityAlways1, 4)); + + // test odd parity + mu_check(bit_lib_test_parity(data_always_odd_parity, 0, 12, BitLibParityOdd, 4)); + mu_check(bit_lib_test_parity(data_always_odd_parity, 4, 8, BitLibParityOdd, 4)); + mu_check(bit_lib_test_parity(data_always_odd_parity, 8, 4, BitLibParityOdd, 4)); + mu_check(bit_lib_test_parity(data_always_even_parity, 12, 4, BitLibParityOdd, 4)); + + mu_check(!bit_lib_test_parity(data_always_odd_parity, 0, 16, BitLibParityOdd, 4)); + mu_check(!bit_lib_test_parity(data_always_odd_parity, 4, 12, BitLibParityOdd, 4)); + mu_check(!bit_lib_test_parity(data_always_odd_parity, 8, 8, BitLibParityOdd, 4)); + mu_check(!bit_lib_test_parity(data_always_odd_parity, 12, 4, BitLibParityOdd, 4)); + + // test even parity + mu_check(bit_lib_test_parity(data_always_even_parity, 0, 12, BitLibParityEven, 4)); + mu_check(bit_lib_test_parity(data_always_even_parity, 4, 8, BitLibParityEven, 4)); + mu_check(bit_lib_test_parity(data_always_even_parity, 8, 4, BitLibParityEven, 4)); + mu_check(bit_lib_test_parity(data_always_odd_parity, 12, 4, BitLibParityEven, 4)); + + mu_check(!bit_lib_test_parity(data_always_even_parity, 0, 16, BitLibParityEven, 4)); + mu_check(!bit_lib_test_parity(data_always_even_parity, 4, 12, BitLibParityEven, 4)); + mu_check(!bit_lib_test_parity(data_always_even_parity, 8, 8, BitLibParityEven, 4)); + mu_check(!bit_lib_test_parity(data_always_even_parity, 12, 4, BitLibParityEven, 4)); +} + +MU_TEST(test_bit_lib_remove_bit_every_nth) { + // TODO: more tests + uint8_t data_i[1] = {0b00001111}; + uint8_t data_o[1] = {0b00011111}; + size_t length; + + length = bit_lib_remove_bit_every_nth(data_i, 0, 8, 3); + mu_assert_int_eq(6, length); + mu_assert_mem_eq(data_o, data_i, 1); +} + +MU_TEST(test_bit_lib_reverse_bits) { + uint8_t data_1_i[2] = {0b11001010, 0b00011111}; + uint8_t data_1_o[2] = {0b11111000, 0b01010011}; + + // reverse bits [0..15] + bit_lib_reverse_bits(data_1_i, 0, 16); + mu_assert_mem_eq(data_1_o, data_1_i, 2); + + uint8_t data_2_i[2] = {0b11001010, 0b00011111}; + uint8_t data_2_o[2] = {0b11001000, 0b01011111}; + + // reverse bits [4..11] + bit_lib_reverse_bits(data_2_i, 4, 8); + mu_assert_mem_eq(data_2_o, data_2_i, 2); +} + +MU_TEST(test_bit_lib_copy_bits) { + uint8_t data_1_i[2] = {0b11001010, 0b00011111}; + uint8_t data_1_o[2] = {0}; + + // data_1_o[0..15] = data_1_i[0..15] + bit_lib_copy_bits(data_1_o, 0, 16, data_1_i, 0); + mu_assert_mem_eq(data_1_i, data_1_o, 2); + + memset(data_1_o, 0, 2); + // data_1_o[4..11] = data_1_i[0..7] + bit_lib_copy_bits(data_1_o, 4, 8, data_1_i, 0); + mu_assert_mem_eq(((uint8_t[]){0b00001100, 0b10100000}), data_1_o, 2); +} + +MU_TEST(test_bit_lib_get_bit_count) { + mu_assert_int_eq(0, bit_lib_get_bit_count(0)); + mu_assert_int_eq(1, bit_lib_get_bit_count(0b1)); + mu_assert_int_eq(1, bit_lib_get_bit_count(0b10)); + mu_assert_int_eq(2, bit_lib_get_bit_count(0b11)); + mu_assert_int_eq(4, bit_lib_get_bit_count(0b11000011)); + mu_assert_int_eq(6, bit_lib_get_bit_count(0b11000011000011)); + mu_assert_int_eq(8, bit_lib_get_bit_count(0b11111111)); + mu_assert_int_eq(16, bit_lib_get_bit_count(0b11111110000000000000000111111111)); + mu_assert_int_eq(32, bit_lib_get_bit_count(0b11111111111111111111111111111111)); +} + +MU_TEST(test_bit_lib_reverse_16_fast) { + mu_assert_int_eq(0b0000000000000000, bit_lib_reverse_16_fast(0b0000000000000000)); + mu_assert_int_eq(0b1000000000000000, bit_lib_reverse_16_fast(0b0000000000000001)); + mu_assert_int_eq(0b1100000000000000, bit_lib_reverse_16_fast(0b0000000000000011)); + mu_assert_int_eq(0b0000100000001001, bit_lib_reverse_16_fast(0b1001000000010000)); +} + +MU_TEST(test_bit_lib_crc16) { + uint8_t data[9] = {'1', '2', '3', '4', '5', '6', '7', '8', '9'}; + uint8_t data_size = 9; + + // Algorithm + // Check Poly Init RefIn RefOut XorOut + // CRC-16/CCITT-FALSE + // 0x29B1 0x1021 0xFFFF false false 0x0000 + mu_assert_int_eq(0x29B1, bit_lib_crc16(data, data_size, 0x1021, 0xFFFF, false, false, 0x0000)); + // CRC-16/ARC + // 0xBB3D 0x8005 0x0000 true true 0x0000 + mu_assert_int_eq(0xBB3D, bit_lib_crc16(data, data_size, 0x8005, 0x0000, true, true, 0x0000)); + // CRC-16/AUG-CCITT + // 0xE5CC 0x1021 0x1D0F false false 0x0000 + mu_assert_int_eq(0xE5CC, bit_lib_crc16(data, data_size, 0x1021, 0x1D0F, false, false, 0x0000)); + // CRC-16/BUYPASS + // 0xFEE8 0x8005 0x0000 false false 0x0000 + mu_assert_int_eq(0xFEE8, bit_lib_crc16(data, data_size, 0x8005, 0x0000, false, false, 0x0000)); + // CRC-16/CDMA2000 + // 0x4C06 0xC867 0xFFFF false false 0x0000 + mu_assert_int_eq(0x4C06, bit_lib_crc16(data, data_size, 0xC867, 0xFFFF, false, false, 0x0000)); + // CRC-16/DDS-110 + // 0x9ECF 0x8005 0x800D false false 0x0000 + mu_assert_int_eq(0x9ECF, bit_lib_crc16(data, data_size, 0x8005, 0x800D, false, false, 0x0000)); + // CRC-16/DECT-R + // 0x007E 0x0589 0x0000 false false 0x0001 + mu_assert_int_eq(0x007E, bit_lib_crc16(data, data_size, 0x0589, 0x0000, false, false, 0x0001)); + // CRC-16/DECT-X + // 0x007F 0x0589 0x0000 false false 0x0000 + mu_assert_int_eq(0x007F, bit_lib_crc16(data, data_size, 0x0589, 0x0000, false, false, 0x0000)); + // CRC-16/DNP + // 0xEA82 0x3D65 0x0000 true true 0xFFFF + mu_assert_int_eq(0xEA82, bit_lib_crc16(data, data_size, 0x3D65, 0x0000, true, true, 0xFFFF)); + // CRC-16/EN-13757 + // 0xC2B7 0x3D65 0x0000 false false 0xFFFF + mu_assert_int_eq(0xC2B7, bit_lib_crc16(data, data_size, 0x3D65, 0x0000, false, false, 0xFFFF)); + // CRC-16/GENIBUS + // 0xD64E 0x1021 0xFFFF false false 0xFFFF + mu_assert_int_eq(0xD64E, bit_lib_crc16(data, data_size, 0x1021, 0xFFFF, false, false, 0xFFFF)); + // CRC-16/MAXIM + // 0x44C2 0x8005 0x0000 true true 0xFFFF + mu_assert_int_eq(0x44C2, bit_lib_crc16(data, data_size, 0x8005, 0x0000, true, true, 0xFFFF)); + // CRC-16/MCRF4XX + // 0x6F91 0x1021 0xFFFF true true 0x0000 + mu_assert_int_eq(0x6F91, bit_lib_crc16(data, data_size, 0x1021, 0xFFFF, true, true, 0x0000)); + // CRC-16/RIELLO + // 0x63D0 0x1021 0xB2AA true true 0x0000 + mu_assert_int_eq(0x63D0, bit_lib_crc16(data, data_size, 0x1021, 0xB2AA, true, true, 0x0000)); + // CRC-16/T10-DIF + // 0xD0DB 0x8BB7 0x0000 false false 0x0000 + mu_assert_int_eq(0xD0DB, bit_lib_crc16(data, data_size, 0x8BB7, 0x0000, false, false, 0x0000)); + // CRC-16/TELEDISK + // 0x0FB3 0xA097 0x0000 false false 0x0000 + mu_assert_int_eq(0x0FB3, bit_lib_crc16(data, data_size, 0xA097, 0x0000, false, false, 0x0000)); + // CRC-16/TMS37157 + // 0x26B1 0x1021 0x89EC true true 0x0000 + mu_assert_int_eq(0x26B1, bit_lib_crc16(data, data_size, 0x1021, 0x89EC, true, true, 0x0000)); + // CRC-16/USB + // 0xB4C8 0x8005 0xFFFF true true 0xFFFF + mu_assert_int_eq(0xB4C8, bit_lib_crc16(data, data_size, 0x8005, 0xFFFF, true, true, 0xFFFF)); + // CRC-A + // 0xBF05 0x1021 0xC6C6 true true 0x0000 + mu_assert_int_eq(0xBF05, bit_lib_crc16(data, data_size, 0x1021, 0xC6C6, true, true, 0x0000)); + // CRC-16/KERMIT + // 0x2189 0x1021 0x0000 true true 0x0000 + mu_assert_int_eq(0x2189, bit_lib_crc16(data, data_size, 0x1021, 0x0000, true, true, 0x0000)); + // CRC-16/MODBUS + // 0x4B37 0x8005 0xFFFF true true 0x0000 + mu_assert_int_eq(0x4B37, bit_lib_crc16(data, data_size, 0x8005, 0xFFFF, true, true, 0x0000)); + // CRC-16/X-25 + // 0x906E 0x1021 0xFFFF true true 0xFFFF + mu_assert_int_eq(0x906E, bit_lib_crc16(data, data_size, 0x1021, 0xFFFF, true, true, 0xFFFF)); + // CRC-16/XMODEM + // 0x31C3 0x1021 0x0000 false false 0x0000 + mu_assert_int_eq(0x31C3, bit_lib_crc16(data, data_size, 0x1021, 0x0000, false, false, 0x0000)); +} + +MU_TEST_SUITE(test_bit_lib) { + MU_RUN_TEST(test_bit_lib_increment_index); + MU_RUN_TEST(test_bit_lib_is_set); + MU_RUN_TEST(test_bit_lib_push); + MU_RUN_TEST(test_bit_lib_set_bit); + MU_RUN_TEST(test_bit_lib_set_bits); + MU_RUN_TEST(test_bit_lib_get_bit); + MU_RUN_TEST(test_bit_lib_get_bits); + MU_RUN_TEST(test_bit_lib_get_bits_16); + MU_RUN_TEST(test_bit_lib_get_bits_32); + MU_RUN_TEST(test_bit_lib_test_parity_u32); + MU_RUN_TEST(test_bit_lib_test_parity); + MU_RUN_TEST(test_bit_lib_remove_bit_every_nth); + MU_RUN_TEST(test_bit_lib_copy_bits); + MU_RUN_TEST(test_bit_lib_reverse_bits); + MU_RUN_TEST(test_bit_lib_get_bit_count); + MU_RUN_TEST(test_bit_lib_reverse_16_fast); + MU_RUN_TEST(test_bit_lib_crc16); +} + +int run_minunit_test_bit_lib() { + MU_RUN_SUITE(test_bit_lib); + return MU_EXIT_CODE; +} \ No newline at end of file diff --git a/applications/unit_tests/lfrfid/lfrfid_protocols.c b/applications/unit_tests/lfrfid/lfrfid_protocols.c new file mode 100644 index 00000000000..4401cbb4d3c --- /dev/null +++ b/applications/unit_tests/lfrfid/lfrfid_protocols.c @@ -0,0 +1,464 @@ +#include +#include "../minunit.h" +#include +#include +#include + +#define LF_RFID_READ_TIMING_MULTIPLIER 8 + +#define EM_TEST_DATA \ + { 0x58, 0x00, 0x85, 0x64, 0x02 } +#define EM_TEST_DATA_SIZE 5 +#define EM_TEST_EMULATION_TIMINGS_COUNT (64 * 2) + +const int8_t em_test_timings[EM_TEST_EMULATION_TIMINGS_COUNT] = { + 32, -32, 32, -32, 32, -32, 32, -32, 32, -32, 32, -32, 32, -32, 32, -32, 32, -32, -32, + 32, 32, -32, -32, 32, 32, -32, -32, 32, 32, -32, -32, 32, -32, 32, -32, 32, 32, -32, + -32, 32, -32, 32, -32, 32, -32, 32, -32, 32, -32, 32, -32, 32, -32, 32, -32, 32, -32, + 32, 32, -32, -32, 32, -32, 32, -32, 32, 32, -32, -32, 32, 32, -32, -32, 32, 32, -32, + -32, 32, -32, 32, 32, -32, 32, -32, -32, 32, -32, 32, -32, 32, 32, -32, -32, 32, -32, + 32, 32, -32, -32, 32, -32, 32, -32, 32, -32, 32, -32, 32, -32, 32, -32, 32, 32, -32, + -32, 32, 32, -32, -32, 32, -32, 32, -32, 32, -32, 32, -32, 32, +}; + +#define HID10301_TEST_DATA \ + { 0x8D, 0x48, 0xA8 } +#define HID10301_TEST_DATA_SIZE 3 +#define HID10301_TEST_EMULATION_TIMINGS_COUNT (541 * 2) + +const int8_t hid10301_test_timings[HID10301_TEST_EMULATION_TIMINGS_COUNT] = { + 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, + 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, + 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, + 4, -4, 4, -4, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, + 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 5, -5, 5, -5, + 5, -5, 5, -5, 5, -5, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 5, -5, 5, -5, 5, -5, 5, -5, + 5, -5, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, + 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 4, -4, 4, -4, + 4, -4, 4, -4, 4, -4, 4, -4, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, + 5, -5, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, + 4, -4, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 5, -5, + 5, -5, 5, -5, 5, -5, 5, -5, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 5, -5, 5, -5, 5, -5, + 5, -5, 5, -5, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, + 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 4, -4, + 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 4, -4, 4, -4, 4, -4, + 4, -4, 4, -4, 4, -4, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, + 4, -4, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, + 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 5, -5, 5, -5, + 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, + 4, -4, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 5, -5, + 5, -5, 5, -5, 5, -5, 5, -5, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, + 4, -4, 4, -4, 4, -4, 4, -4, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 4, -4, 4, -4, 4, -4, 4, -4, + 4, -4, 4, -4, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, + 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 4, -4, 4, -4, 4, -4, + 4, -4, 4, -4, 4, -4, 4, -4, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 4, -4, 4, -4, 4, -4, 4, -4, + 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, + 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, + 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, + 5, -5, 5, -5, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, + 4, -4, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 5, -5, + 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 4, -4, 4, -4, 4, -4, 4, -4, + 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 5, -5, 5, -5, 5, -5, 5, -5, + 5, -5, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 4, -4, + 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, + 5, -5, 5, -5, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, + 4, -4, 4, -4, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 4, -4, + 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 5, -5, 5, -5, + 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, + 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, + 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 4, -4, 4, -4, + 4, -4, 4, -4, 4, -4, 4, -4, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, + 5, -5, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, +}; + +#define IOPROX_XSF_TEST_DATA \ + { 0x65, 0x01, 0x05, 0x39 } +#define IOPROX_XSF_TEST_DATA_SIZE 4 +#define IOPROX_XSF_TEST_EMULATION_TIMINGS_COUNT (468 * 2) + +const int8_t ioprox_xsf_test_timings[IOPROX_XSF_TEST_EMULATION_TIMINGS_COUNT] = { + 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, + 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, + 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, + 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, + 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, + 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, + 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, + 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, + 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, + 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, + 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, + 4, -4, 4, -4, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, + 5, -5, 5, -5, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, + 4, -4, 4, -4, 4, -4, 4, -4, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 4, -4, 4, -4, + 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, + 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, + 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, + 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, + 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, + 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 5, -5, 5, -5, 5, -5, 5, -5, + 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 4, -4, 4, -4, 4, -4, 4, -4, + 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, + 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, + 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 5, -5, 5, -5, 5, -5, + 5, -5, 5, -5, 5, -5, 5, -5, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 5, -5, 5, -5, + 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 4, -4, 4, -4, + 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, + 4, -4, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, + 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, + 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 5, -5, 5, -5, 5, -5, + 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 4, -4, 4, -4, 4, -4, + 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, + 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, + 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4, + 4, -4, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, + 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, +}; + +#define INDALA26_EMULATION_TIMINGS_COUNT (1024 * 2) +#define INDALA26_TEST_DATA \ + { 0x3B, 0x73, 0x64, 0xA8 } +#define INDALA26_TEST_DATA_SIZE 4 + +const int8_t indala26_test_timings[INDALA26_EMULATION_TIMINGS_COUNT] = { + 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, + 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, + 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, -1, 1, -1, 1, -1, 1, -1, 1, + -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, + 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, + 1, -1, 1, -1, 1, -1, 1, -1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, + -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, + -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, + -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, + -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, + -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, + -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, + -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, + -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, + -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, + -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, + -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, + -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, + -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, + -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, + -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, + -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, + -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, + -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, + -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, + -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, + -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, + -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, + -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, + -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, + -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, + -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, + -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, + -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, + -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, + -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, + -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, + -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, + -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, + -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, + -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, + -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, + -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, + -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, + 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, + 1, -1, 1, -1, 1, -1, 1, -1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, + -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, + -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, + 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, + 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, + 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, + 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, + -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, + -1, 1, -1, 1, -1, 1, -1, 1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, + 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, + 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, + -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, + -1, 1, -1, 1, -1, 1, -1, 1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, + 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, + 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, + 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, + 1, -1, 1, -1, 1, -1, 1, -1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, + -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, + -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, + 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, + 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, + 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, -1, 1, -1, 1, -1, 1, -1, 1, + -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, + 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, + 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, + 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, -1, 1, -1, 1, -1, 1, -1, 1, + -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, + -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, + -1, 1, -1, 1, -1, 1, -1, 1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, + 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, -1, 1, -1, 1, -1, 1, -1, 1, + -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, + -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, + -1, 1, -1, 1, -1, 1, -1, 1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, + 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, -1, 1, -1, 1, -1, 1, -1, 1, + -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, + 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, + 1, -1, 1, -1, 1, -1, 1, -1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, + -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, + -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, + -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, + -1, 1, -1, 1, -1, 1, -1, 1, +}; + +MU_TEST(test_lfrfid_protocol_em_read_simple) { + ProtocolDict* dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax); + mu_assert_int_eq(EM_TEST_DATA_SIZE, protocol_dict_get_data_size(dict, LFRFIDProtocolEM4100)); + mu_assert_string_eq("EM4100", protocol_dict_get_name(dict, LFRFIDProtocolEM4100)); + mu_assert_string_eq("EM-Micro", protocol_dict_get_manufacturer(dict, LFRFIDProtocolEM4100)); + + const uint8_t data[EM_TEST_DATA_SIZE] = EM_TEST_DATA; + + protocol_dict_decoders_start(dict); + + ProtocolId protocol = PROTOCOL_NO; + PulseGlue* pulse_glue = pulse_glue_alloc(); + + for(size_t i = 0; i < EM_TEST_EMULATION_TIMINGS_COUNT * 10; i++) { + bool pulse_pop = pulse_glue_push( + pulse_glue, + em_test_timings[i % EM_TEST_EMULATION_TIMINGS_COUNT] >= 0, + abs(em_test_timings[i % EM_TEST_EMULATION_TIMINGS_COUNT]) * + LF_RFID_READ_TIMING_MULTIPLIER); + + if(pulse_pop) { + uint32_t length, period; + pulse_glue_pop(pulse_glue, &length, &period); + + protocol = protocol_dict_decoders_feed(dict, true, period); + if(protocol != PROTOCOL_NO) break; + + protocol = protocol_dict_decoders_feed(dict, false, length - period); + if(protocol != PROTOCOL_NO) break; + } + } + + pulse_glue_free(pulse_glue); + + mu_assert_int_eq(LFRFIDProtocolEM4100, protocol); + uint8_t received_data[EM_TEST_DATA_SIZE] = {0}; + protocol_dict_get_data(dict, protocol, received_data, EM_TEST_DATA_SIZE); + + mu_assert_mem_eq(data, received_data, EM_TEST_DATA_SIZE); + + protocol_dict_free(dict); +} + +MU_TEST(test_lfrfid_protocol_em_emulate_simple) { + ProtocolDict* dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax); + mu_assert_int_eq(EM_TEST_DATA_SIZE, protocol_dict_get_data_size(dict, LFRFIDProtocolEM4100)); + mu_assert_string_eq("EM4100", protocol_dict_get_name(dict, LFRFIDProtocolEM4100)); + mu_assert_string_eq("EM-Micro", protocol_dict_get_manufacturer(dict, LFRFIDProtocolEM4100)); + + const uint8_t data[EM_TEST_DATA_SIZE] = EM_TEST_DATA; + + protocol_dict_set_data(dict, LFRFIDProtocolEM4100, data, EM_TEST_DATA_SIZE); + mu_check(protocol_dict_encoder_start(dict, LFRFIDProtocolEM4100)); + + for(size_t i = 0; i < EM_TEST_EMULATION_TIMINGS_COUNT; i++) { + LevelDuration level_duration = protocol_dict_encoder_yield(dict, LFRFIDProtocolEM4100); + + if(level_duration_get_level(level_duration)) { + mu_assert_int_eq(em_test_timings[i], level_duration_get_duration(level_duration)); + } else { + mu_assert_int_eq(em_test_timings[i], -level_duration_get_duration(level_duration)); + } + } + + protocol_dict_free(dict); +} + +MU_TEST(test_lfrfid_protocol_h10301_read_simple) { + ProtocolDict* dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax); + mu_assert_int_eq( + HID10301_TEST_DATA_SIZE, protocol_dict_get_data_size(dict, LFRFIDProtocolH10301)); + mu_assert_string_eq("H10301", protocol_dict_get_name(dict, LFRFIDProtocolH10301)); + mu_assert_string_eq("HID", protocol_dict_get_manufacturer(dict, LFRFIDProtocolH10301)); + + const uint8_t data[HID10301_TEST_DATA_SIZE] = HID10301_TEST_DATA; + + protocol_dict_decoders_start(dict); + + ProtocolId protocol = PROTOCOL_NO; + PulseGlue* pulse_glue = pulse_glue_alloc(); + + for(size_t i = 0; i < HID10301_TEST_EMULATION_TIMINGS_COUNT * 10; i++) { + bool pulse_pop = pulse_glue_push( + pulse_glue, + hid10301_test_timings[i % HID10301_TEST_EMULATION_TIMINGS_COUNT] >= 0, + abs(hid10301_test_timings[i % HID10301_TEST_EMULATION_TIMINGS_COUNT]) * + LF_RFID_READ_TIMING_MULTIPLIER); + + if(pulse_pop) { + uint32_t length, period; + pulse_glue_pop(pulse_glue, &length, &period); + + protocol = protocol_dict_decoders_feed(dict, true, period); + if(protocol != PROTOCOL_NO) break; + + protocol = protocol_dict_decoders_feed(dict, false, length - period); + if(protocol != PROTOCOL_NO) break; + } + } + + pulse_glue_free(pulse_glue); + + mu_assert_int_eq(LFRFIDProtocolH10301, protocol); + uint8_t received_data[HID10301_TEST_DATA_SIZE] = {0}; + protocol_dict_get_data(dict, protocol, received_data, HID10301_TEST_DATA_SIZE); + + mu_assert_mem_eq(data, received_data, HID10301_TEST_DATA_SIZE); + + protocol_dict_free(dict); +} + +MU_TEST(test_lfrfid_protocol_h10301_emulate_simple) { + ProtocolDict* dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax); + mu_assert_int_eq( + HID10301_TEST_DATA_SIZE, protocol_dict_get_data_size(dict, LFRFIDProtocolH10301)); + mu_assert_string_eq("H10301", protocol_dict_get_name(dict, LFRFIDProtocolH10301)); + mu_assert_string_eq("HID", protocol_dict_get_manufacturer(dict, LFRFIDProtocolH10301)); + + const uint8_t data[HID10301_TEST_DATA_SIZE] = HID10301_TEST_DATA; + + protocol_dict_set_data(dict, LFRFIDProtocolH10301, data, HID10301_TEST_DATA_SIZE); + mu_check(protocol_dict_encoder_start(dict, LFRFIDProtocolH10301)); + + for(size_t i = 0; i < HID10301_TEST_EMULATION_TIMINGS_COUNT; i++) { + LevelDuration level_duration = protocol_dict_encoder_yield(dict, LFRFIDProtocolH10301); + + if(level_duration_get_level(level_duration)) { + mu_assert_int_eq( + hid10301_test_timings[i], level_duration_get_duration(level_duration)); + } else { + mu_assert_int_eq( + hid10301_test_timings[i], -level_duration_get_duration(level_duration)); + } + } + + protocol_dict_free(dict); +} + +MU_TEST(test_lfrfid_protocol_ioprox_xsf_read_simple) { + ProtocolDict* dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax); + mu_assert_int_eq( + IOPROX_XSF_TEST_DATA_SIZE, protocol_dict_get_data_size(dict, LFRFIDProtocolIOProxXSF)); + mu_assert_string_eq("IoProxXSF", protocol_dict_get_name(dict, LFRFIDProtocolIOProxXSF)); + mu_assert_string_eq("Kantech", protocol_dict_get_manufacturer(dict, LFRFIDProtocolIOProxXSF)); + + const uint8_t data[IOPROX_XSF_TEST_DATA_SIZE] = IOPROX_XSF_TEST_DATA; + + protocol_dict_decoders_start(dict); + + ProtocolId protocol = PROTOCOL_NO; + PulseGlue* pulse_glue = pulse_glue_alloc(); + + for(size_t i = 0; i < IOPROX_XSF_TEST_EMULATION_TIMINGS_COUNT * 10; i++) { + bool pulse_pop = pulse_glue_push( + pulse_glue, + ioprox_xsf_test_timings[i % IOPROX_XSF_TEST_EMULATION_TIMINGS_COUNT] >= 0, + abs(ioprox_xsf_test_timings[i % IOPROX_XSF_TEST_EMULATION_TIMINGS_COUNT]) * + LF_RFID_READ_TIMING_MULTIPLIER); + + if(pulse_pop) { + uint32_t length, period; + pulse_glue_pop(pulse_glue, &length, &period); + + protocol = protocol_dict_decoders_feed(dict, true, period); + if(protocol != PROTOCOL_NO) break; + + protocol = protocol_dict_decoders_feed(dict, false, length - period); + if(protocol != PROTOCOL_NO) break; + } + } + + pulse_glue_free(pulse_glue); + + mu_assert_int_eq(LFRFIDProtocolIOProxXSF, protocol); + uint8_t received_data[IOPROX_XSF_TEST_DATA_SIZE] = {0}; + protocol_dict_get_data(dict, protocol, received_data, IOPROX_XSF_TEST_DATA_SIZE); + + mu_assert_mem_eq(data, received_data, IOPROX_XSF_TEST_DATA_SIZE); + + protocol_dict_free(dict); +} + +MU_TEST(test_lfrfid_protocol_ioprox_xsf_emulate_simple) { + ProtocolDict* dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax); + mu_assert_int_eq( + IOPROX_XSF_TEST_DATA_SIZE, protocol_dict_get_data_size(dict, LFRFIDProtocolIOProxXSF)); + mu_assert_string_eq("IoProxXSF", protocol_dict_get_name(dict, LFRFIDProtocolIOProxXSF)); + mu_assert_string_eq("Kantech", protocol_dict_get_manufacturer(dict, LFRFIDProtocolIOProxXSF)); + + const uint8_t data[IOPROX_XSF_TEST_DATA_SIZE] = IOPROX_XSF_TEST_DATA; + + protocol_dict_set_data(dict, LFRFIDProtocolIOProxXSF, data, IOPROX_XSF_TEST_DATA_SIZE); + mu_check(protocol_dict_encoder_start(dict, LFRFIDProtocolIOProxXSF)); + + for(size_t i = 0; i < IOPROX_XSF_TEST_EMULATION_TIMINGS_COUNT; i++) { + LevelDuration level_duration = protocol_dict_encoder_yield(dict, LFRFIDProtocolIOProxXSF); + + if(level_duration_get_level(level_duration)) { + mu_assert_int_eq( + ioprox_xsf_test_timings[i], level_duration_get_duration(level_duration)); + } else { + mu_assert_int_eq( + ioprox_xsf_test_timings[i], -level_duration_get_duration(level_duration)); + } + } + + protocol_dict_free(dict); +} + +MU_TEST(test_lfrfid_protocol_inadala26_emulate_simple) { + ProtocolDict* dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax); + mu_assert_int_eq( + INDALA26_TEST_DATA_SIZE, protocol_dict_get_data_size(dict, LFRFIDProtocolIndala26)); + mu_assert_string_eq("Indala26", protocol_dict_get_name(dict, LFRFIDProtocolIndala26)); + mu_assert_string_eq("Motorola", protocol_dict_get_manufacturer(dict, LFRFIDProtocolIndala26)); + + const uint8_t data[INDALA26_TEST_DATA_SIZE] = INDALA26_TEST_DATA; + + protocol_dict_set_data(dict, LFRFIDProtocolIndala26, data, INDALA26_TEST_DATA_SIZE); + mu_check(protocol_dict_encoder_start(dict, LFRFIDProtocolIndala26)); + + for(size_t i = 0; i < INDALA26_EMULATION_TIMINGS_COUNT; i++) { + LevelDuration level_duration = protocol_dict_encoder_yield(dict, LFRFIDProtocolIndala26); + + if(level_duration_get_level(level_duration)) { + mu_assert_int_eq( + indala26_test_timings[i], level_duration_get_duration(level_duration)); + } else { + mu_assert_int_eq( + indala26_test_timings[i], -level_duration_get_duration(level_duration)); + } + } + + protocol_dict_free(dict); +} + +MU_TEST_SUITE(test_lfrfid_protocols_suite) { + MU_RUN_TEST(test_lfrfid_protocol_em_read_simple); + MU_RUN_TEST(test_lfrfid_protocol_em_emulate_simple); + + MU_RUN_TEST(test_lfrfid_protocol_h10301_read_simple); + MU_RUN_TEST(test_lfrfid_protocol_h10301_emulate_simple); + + MU_RUN_TEST(test_lfrfid_protocol_ioprox_xsf_read_simple); + MU_RUN_TEST(test_lfrfid_protocol_ioprox_xsf_emulate_simple); + + MU_RUN_TEST(test_lfrfid_protocol_inadala26_emulate_simple); +} + +int run_minunit_test_lfrfid_protocols() { + MU_RUN_SUITE(test_lfrfid_protocols_suite); + return MU_EXIT_CODE; +} \ No newline at end of file diff --git a/applications/unit_tests/minunit.h b/applications/unit_tests/minunit.h index d1efd13e61f..17eb7b3f137 100644 --- a/applications/unit_tests/minunit.h +++ b/applications/unit_tests/minunit.h @@ -151,46 +151,46 @@ void minunit_print_fail(const char* error); #define MU_EXIT_CODE minunit_fail /* Assertions */ -#define mu_check(test) \ - MU__SAFE_BLOCK( \ - minunit_assert++; if(!(test)) { \ - snprintf( \ - minunit_last_message, \ - MINUNIT_MESSAGE_LEN, \ - "%s failed:\n\t%s:%d: %s", \ - __func__, \ - __FILE__, \ - __LINE__, \ - #test); \ - minunit_status = 1; \ - return; \ +#define mu_check(test) \ + MU__SAFE_BLOCK( \ + minunit_assert++; if(!(test)) { \ + snprintf( \ + minunit_last_message, \ + MINUNIT_MESSAGE_LEN, \ + "%s failed:\r\n\t%s:%d: %s", \ + __func__, \ + __FILE__, \ + __LINE__, \ + #test); \ + minunit_status = 1; \ + return; \ } else { minunit_print_progress(); }) -#define mu_fail(message) \ - MU__SAFE_BLOCK(minunit_assert++; snprintf( \ - minunit_last_message, \ - MINUNIT_MESSAGE_LEN, \ - "%s failed:\n\t%s:%d: %s", \ - __func__, \ - __FILE__, \ - __LINE__, \ - message); \ - minunit_status = 1; \ +#define mu_fail(message) \ + MU__SAFE_BLOCK(minunit_assert++; snprintf( \ + minunit_last_message, \ + MINUNIT_MESSAGE_LEN, \ + "%s failed:\r\n\t%s:%d: %s", \ + __func__, \ + __FILE__, \ + __LINE__, \ + message); \ + minunit_status = 1; \ return;) -#define mu_assert(test, message) \ - MU__SAFE_BLOCK( \ - minunit_assert++; if(!(test)) { \ - snprintf( \ - minunit_last_message, \ - MINUNIT_MESSAGE_LEN, \ - "%s failed:\n\t%s:%d: %s", \ - __func__, \ - __FILE__, \ - __LINE__, \ - message); \ - minunit_status = 1; \ - return; \ +#define mu_assert(test, message) \ + MU__SAFE_BLOCK( \ + minunit_assert++; if(!(test)) { \ + snprintf( \ + minunit_last_message, \ + MINUNIT_MESSAGE_LEN, \ + "%s failed:\r\n\t%s:%d: %s", \ + __func__, \ + __FILE__, \ + __LINE__, \ + message); \ + minunit_status = 1; \ + return; \ } else { minunit_print_progress(); }) #define mu_assert_int_eq(expected, result) \ @@ -201,7 +201,7 @@ void minunit_print_fail(const char* error); snprintf( \ minunit_last_message, \ MINUNIT_MESSAGE_LEN, \ - "%s failed:\n\t%s:%d: %d expected but was %d", \ + "%s failed:\r\n\t%s:%d: %d expected but was %d", \ __func__, \ __FILE__, \ __LINE__, \ @@ -219,7 +219,7 @@ void minunit_print_fail(const char* error); snprintf( \ minunit_last_message, \ MINUNIT_MESSAGE_LEN, \ - "%s failed:\n\t%s:%d: expected different results but both were %d", \ + "%s failed:\r\n\t%s:%d: expected different results but both were %d", \ __func__, \ __FILE__, \ __LINE__, \ @@ -236,7 +236,7 @@ void minunit_print_fail(const char* error); snprintf( \ minunit_last_message, \ MINUNIT_MESSAGE_LEN, \ - "%s failed:\n\t%s:%d: %d <= %d", \ + "%s failed:\r\n\t%s:%d: %d <= %d", \ __func__, \ __FILE__, \ __LINE__, \ @@ -254,7 +254,7 @@ void minunit_print_fail(const char* error); snprintf( \ minunit_last_message, \ MINUNIT_MESSAGE_LEN, \ - "%s failed:\n\t%s:%d: %d >= %d", \ + "%s failed:\r\n\t%s:%d: %d >= %d", \ __func__, \ __FILE__, \ __LINE__, \ @@ -274,7 +274,7 @@ void minunit_print_fail(const char* error); snprintf( \ minunit_last_message, \ MINUNIT_MESSAGE_LEN, \ - "%s failed:\n\t%s:%d: %d was not between (inclusive) %d and %d", \ + "%s failed:\r\n\t%s:%d: %d was not between (inclusive) %d and %d", \ __func__, \ __FILE__, \ __LINE__, \ @@ -302,7 +302,7 @@ void minunit_print_fail(const char* error); snprintf( \ minunit_last_message, \ MINUNIT_MESSAGE_LEN, \ - "%s failed:\n\t%s:%d: expected to be one of %s but was %d", \ + "%s failed:\r\n\t%s:%d: expected to be one of %s but was %d", \ __func__, \ __FILE__, \ __LINE__, \ @@ -321,7 +321,7 @@ void minunit_print_fail(const char* error); snprintf( \ minunit_last_message, \ MINUNIT_MESSAGE_LEN, \ - "%s failed:\n\t%s:%d: %.*g expected but was %.*g", \ + "%s failed:\r\n\t%s:%d: %.*g expected but was %.*g", \ __func__, \ __FILE__, \ __LINE__, \ @@ -341,7 +341,7 @@ void minunit_print_fail(const char* error); snprintf( \ minunit_last_message, \ MINUNIT_MESSAGE_LEN, \ - "%s failed:\n\t%s:%d: %f <= %f", \ + "%s failed:\r\n\t%s:%d: %f <= %f", \ __func__, \ __FILE__, \ __LINE__, \ @@ -359,7 +359,7 @@ void minunit_print_fail(const char* error); snprintf( \ minunit_last_message, \ MINUNIT_MESSAGE_LEN, \ - "%s failed:\n\t%s:%d: %f >= %f", \ + "%s failed:\r\n\t%s:%d: %f >= %f", \ __func__, \ __FILE__, \ __LINE__, \ @@ -379,7 +379,7 @@ void minunit_print_fail(const char* error); snprintf( \ minunit_last_message, \ MINUNIT_MESSAGE_LEN, \ - "%s failed:\n\t%s:%d: %f was not between (inclusive) %f and %f", \ + "%s failed:\r\n\t%s:%d: %f was not between (inclusive) %f and %f", \ __func__, \ __FILE__, \ __LINE__, \ @@ -400,7 +400,7 @@ void minunit_print_fail(const char* error); snprintf( \ minunit_last_message, \ MINUNIT_MESSAGE_LEN, \ - "%s failed:\n\t%s:%d: '%s' expected but was '%s'", \ + "%s failed:\r\n\t%s:%d: '%s' expected but was '%s'", \ __func__, \ __FILE__, \ __LINE__, \ @@ -410,13 +410,41 @@ void minunit_print_fail(const char* error); return; \ } else { minunit_print_progress(); }) +#define mu_assert_mem_eq(expected, result, size) \ + MU__SAFE_BLOCK( \ + const void* minunit_tmp_e = expected; const void* minunit_tmp_r = result; \ + minunit_assert++; \ + if(memcmp(minunit_tmp_e, minunit_tmp_r, size)) { \ + snprintf( \ + minunit_last_message, \ + MINUNIT_MESSAGE_LEN, \ + "%s failed:\r\n\t%s:%d: mem not equal\r\n\tEXP RES", \ + __func__, \ + __FILE__, \ + __LINE__); \ + for(size_t __index = 0; __index < size; __index++) { \ + if(strlen(minunit_last_message) > MINUNIT_MESSAGE_LEN - 20) break; \ + uint8_t __e = ((uint8_t*)minunit_tmp_e)[__index]; \ + uint8_t __r = ((uint8_t*)minunit_tmp_r)[__index]; \ + snprintf( \ + minunit_last_message + strlen(minunit_last_message), \ + MINUNIT_MESSAGE_LEN - strlen(minunit_last_message), \ + "\r\n\t%02X %s %02X", \ + __e, \ + ((__e == __r) ? ".." : "!="), \ + __r); \ + } \ + minunit_status = 1; \ + return; \ + } else { minunit_print_progress(); }) + #define mu_assert_null(result) \ MU__SAFE_BLOCK( \ minunit_assert++; if(result == NULL) { minunit_print_progress(); } else { \ snprintf( \ minunit_last_message, \ MINUNIT_MESSAGE_LEN, \ - "%s failed:\n\t%s:%d: Expected result was not NULL", \ + "%s failed:\r\n\t%s:%d: Expected result was not NULL", \ __func__, \ __FILE__, \ __LINE__); \ @@ -430,7 +458,7 @@ void minunit_print_fail(const char* error); snprintf( \ minunit_last_message, \ MINUNIT_MESSAGE_LEN, \ - "%s failed:\n\t%s:%d: Expected result was not NULL", \ + "%s failed:\r\n\t%s:%d: Expected result was not NULL", \ __func__, \ __FILE__, \ __LINE__); \ @@ -438,32 +466,32 @@ void minunit_print_fail(const char* error); return; \ }) -#define mu_assert_pointers_eq(pointer1, pointer2) \ - MU__SAFE_BLOCK( \ - minunit_assert++; if(pointer1 == pointer2) { minunit_print_progress(); } else { \ - snprintf( \ - minunit_last_message, \ - MINUNIT_MESSAGE_LEN, \ - "%s failed:\n\t%s:%d: Expected the pointers to point to the same memory location", \ - __func__, \ - __FILE__, \ - __LINE__); \ - minunit_status = 1; \ - return; \ +#define mu_assert_pointers_eq(pointer1, pointer2) \ + MU__SAFE_BLOCK( \ + minunit_assert++; if(pointer1 == pointer2) { minunit_print_progress(); } else { \ + snprintf( \ + minunit_last_message, \ + MINUNIT_MESSAGE_LEN, \ + "%s failed:\r\n\t%s:%d: Expected the pointers to point to the same memory location", \ + __func__, \ + __FILE__, \ + __LINE__); \ + minunit_status = 1; \ + return; \ }) -#define mu_assert_pointers_not_eq(pointer1, pointer2) \ - MU__SAFE_BLOCK( \ - minunit_assert++; if(pointer1 != pointer2) { minunit_print_progress(); } else { \ - snprintf( \ - minunit_last_message, \ - MINUNIT_MESSAGE_LEN, \ - "%s failed:\n\t%s:%d: Expected the pointers to point to the same memory location", \ - __func__, \ - __FILE__, \ - __LINE__); \ - minunit_status = 1; \ - return; \ +#define mu_assert_pointers_not_eq(pointer1, pointer2) \ + MU__SAFE_BLOCK( \ + minunit_assert++; if(pointer1 != pointer2) { minunit_print_progress(); } else { \ + snprintf( \ + minunit_last_message, \ + MINUNIT_MESSAGE_LEN, \ + "%s failed:\r\n\t%s:%d: Expected the pointers to point to the same memory location", \ + __func__, \ + __FILE__, \ + __LINE__); \ + minunit_status = 1; \ + return; \ }) /* diff --git a/applications/unit_tests/protocol_dict/protocol_dict_test.c b/applications/unit_tests/protocol_dict/protocol_dict_test.c new file mode 100644 index 00000000000..73e77ec900c --- /dev/null +++ b/applications/unit_tests/protocol_dict/protocol_dict_test.c @@ -0,0 +1,222 @@ +#include +#include "../minunit.h" +#include + +typedef enum { + TestDictProtocol0, + TestDictProtocol1, + + TestDictProtocolMax, +} TestDictProtocols; + +/*********************** PROTOCOL 0 START ***********************/ + +typedef struct { + uint32_t data; + size_t encoder_counter; +} Protocol0Data; + +static const uint32_t protocol_0_decoder_result = 0xDEADBEEF; + +static void* protocol_0_alloc() { + void* data = malloc(sizeof(Protocol0Data)); + return data; +} + +static void protocol_0_free(Protocol0Data* data) { + free(data); +} + +static uint8_t* protocol_0_get_data(Protocol0Data* data) { + return (uint8_t*)&data->data; +} + +static void protocol_0_decoder_start(Protocol0Data* data) { + data->data = 0; +} + +static bool protocol_0_decoder_feed(Protocol0Data* data, bool level, uint32_t duration) { + if(level && duration == 666) { + data->data = protocol_0_decoder_result; + return true; + } else { + return false; + } +} + +static bool protocol_0_encoder_start(Protocol0Data* data) { + data->encoder_counter = 0; + return true; +} + +static LevelDuration protocol_0_encoder_yield(Protocol0Data* data) { + data->encoder_counter++; + return level_duration_make(data->encoder_counter % 2, data->data); +} + +/*********************** PROTOCOL 1 START ***********************/ + +typedef struct { + uint64_t data; + size_t encoder_counter; +} Protocol1Data; + +static const uint64_t protocol_1_decoder_result = 0x1234567890ABCDEF; + +static void* protocol_1_alloc() { + void* data = malloc(sizeof(Protocol1Data)); + return data; +} + +static void protocol_1_free(Protocol1Data* data) { + free(data); +} + +static uint8_t* protocol_1_get_data(Protocol1Data* data) { + return (uint8_t*)&data->data; +} + +static void protocol_1_decoder_start(Protocol1Data* data) { + data->data = 0; +} + +static bool protocol_1_decoder_feed(Protocol1Data* data, bool level, uint32_t duration) { + if(level && duration == 543) { + data->data = 0x1234567890ABCDEF; + return true; + } else { + return false; + } +} + +static bool protocol_1_encoder_start(Protocol1Data* data) { + data->encoder_counter = 0; + return true; +} + +static LevelDuration protocol_1_encoder_yield(Protocol1Data* data) { + data->encoder_counter++; + return level_duration_make(!(data->encoder_counter % 2), 100); +} + +/*********************** PROTOCOLS DESCRIPTION ***********************/ +static const ProtocolBase protocol_0 = { + .name = "Protocol 0", + .manufacturer = "Manufacturer 0", + .data_size = 4, + .alloc = (ProtocolAlloc)protocol_0_alloc, + .free = (ProtocolFree)protocol_0_free, + .get_data = (ProtocolGetData)protocol_0_get_data, + .decoder = + { + .start = (ProtocolDecoderStart)protocol_0_decoder_start, + .feed = (ProtocolDecoderFeed)protocol_0_decoder_feed, + }, + .encoder = + { + .start = (ProtocolEncoderStart)protocol_0_encoder_start, + .yield = (ProtocolEncoderYield)protocol_0_encoder_yield, + }, +}; + +static const ProtocolBase protocol_1 = { + .name = "Protocol 1", + .manufacturer = "Manufacturer 1", + .data_size = 8, + .alloc = (ProtocolAlloc)protocol_1_alloc, + .free = (ProtocolFree)protocol_1_free, + .get_data = (ProtocolGetData)protocol_1_get_data, + .decoder = + { + .start = (ProtocolDecoderStart)protocol_1_decoder_start, + .feed = (ProtocolDecoderFeed)protocol_1_decoder_feed, + }, + .encoder = + { + .start = (ProtocolEncoderStart)protocol_1_encoder_start, + .yield = (ProtocolEncoderYield)protocol_1_encoder_yield, + }, +}; + +static const ProtocolBase* test_protocols_base[] = { + [TestDictProtocol0] = &protocol_0, + [TestDictProtocol1] = &protocol_1, +}; + +MU_TEST(test_protocol_dict) { + ProtocolDict* dict = protocol_dict_alloc(test_protocols_base, TestDictProtocolMax); + size_t max_data_size = protocol_dict_get_max_data_size(dict); + mu_assert_int_eq(8, max_data_size); + uint8_t* data = malloc(max_data_size); + + protocol_dict_decoders_start(dict); + ProtocolId protocol_id = PROTOCOL_NO; + + for(size_t i = 0; i < 100; i++) { + protocol_id = protocol_dict_decoders_feed(dict, i % 2, 100); + mu_assert_int_eq(PROTOCOL_NO, protocol_id); + } + + // trigger protocol 1 + protocol_id = protocol_dict_decoders_feed(dict, true, 543); + mu_assert_int_eq(TestDictProtocol1, protocol_id); + + mu_assert_string_eq("Protocol 1", protocol_dict_get_name(dict, protocol_id)); + mu_assert_string_eq("Manufacturer 1", protocol_dict_get_manufacturer(dict, protocol_id)); + + size_t data_size = protocol_dict_get_data_size(dict, protocol_id); + mu_assert_int_eq(8, data_size); + + protocol_dict_get_data(dict, protocol_id, data, data_size); + mu_assert_mem_eq(&protocol_1_decoder_result, data, data_size); + + // trigger protocol 0 + protocol_id = protocol_dict_decoders_feed(dict, true, 666); + mu_assert_int_eq(TestDictProtocol0, protocol_id); + + mu_assert_string_eq("Protocol 0", protocol_dict_get_name(dict, protocol_id)); + mu_assert_string_eq("Manufacturer 0", protocol_dict_get_manufacturer(dict, protocol_id)); + + data_size = protocol_dict_get_data_size(dict, protocol_id); + mu_assert_int_eq(4, data_size); + + protocol_dict_get_data(dict, protocol_id, data, data_size); + mu_assert_mem_eq(&protocol_0_decoder_result, data, data_size); + + protocol_dict_decoders_start(dict); + + protocol_id = TestDictProtocol0; + + const uint8_t protocol_0_test_data[4] = {100, 0, 0, 0}; + protocol_dict_set_data(dict, protocol_id, protocol_0_test_data, 4); + + mu_check(protocol_dict_encoder_start(dict, protocol_id)); + + LevelDuration level; + level = protocol_dict_encoder_yield(dict, protocol_id); + mu_assert_int_eq(true, level_duration_get_level(level)); + mu_assert_int_eq(100, level_duration_get_duration(level)); + level = protocol_dict_encoder_yield(dict, protocol_id); + mu_assert_int_eq(false, level_duration_get_level(level)); + mu_assert_int_eq(100, level_duration_get_duration(level)); + level = protocol_dict_encoder_yield(dict, protocol_id); + mu_assert_int_eq(true, level_duration_get_level(level)); + mu_assert_int_eq(100, level_duration_get_duration(level)); + + mu_check(protocol_dict_encoder_start(dict, protocol_id)); + level = protocol_dict_encoder_yield(dict, protocol_id); + mu_assert_int_eq(true, level_duration_get_level(level)); + mu_assert_int_eq(100, level_duration_get_duration(level)); + + protocol_dict_free(dict); + free(data); +} + +MU_TEST_SUITE(test_protocol_dict_suite) { + MU_RUN_TEST(test_protocol_dict); +} + +int run_minunit_test_protocol_dict() { + MU_RUN_SUITE(test_protocol_dict_suite); + return MU_EXIT_CODE; +} \ No newline at end of file diff --git a/applications/unit_tests/test_index.c b/applications/unit_tests/test_index.c index e528224651d..81d891b2ba7 100644 --- a/applications/unit_tests/test_index.c +++ b/applications/unit_tests/test_index.c @@ -19,7 +19,10 @@ int run_minunit_test_stream(); int run_minunit_test_storage(); int run_minunit_test_subghz(); int run_minunit_test_dirwalk(); +int run_minunit_test_protocol_dict(); +int run_minunit_test_lfrfid_protocols(); int run_minunit_test_nfc(); +int run_minunit_test_bit_lib(); typedef int (*UnitTestEntry)(); @@ -39,6 +42,9 @@ const UnitTest unit_tests[] = { {.name = "subghz", .entry = run_minunit_test_subghz}, {.name = "infrared", .entry = run_minunit_test_infrared}, {.name = "nfc", .entry = run_minunit_test_nfc}, + {.name = "protocol_dict", .entry = run_minunit_test_protocol_dict}, + {.name = "lfrfid", .entry = run_minunit_test_lfrfid_protocols}, + {.name = "bit_lib", .entry = run_minunit_test_bit_lib}, }; void minunit_print_progress() { diff --git a/applications/unit_tests/varint/varint_test.c b/applications/unit_tests/varint/varint_test.c new file mode 100644 index 00000000000..8faab136883 --- /dev/null +++ b/applications/unit_tests/varint/varint_test.c @@ -0,0 +1,88 @@ +#include +#include +#include "../minunit.h" +#include +#include + +MU_TEST(test_varint_basic_u) { + mu_assert_int_eq(1, varint_uint32_length(0)); + mu_assert_int_eq(5, varint_uint32_length(UINT32_MAX)); + + uint8_t data[8] = {}; + uint32_t out_value; + + mu_assert_int_eq(1, varint_uint32_pack(0, data)); + mu_assert_int_eq(1, varint_uint32_unpack(&out_value, data, 8)); + mu_assert_int_eq(0, out_value); + + mu_assert_int_eq(5, varint_uint32_pack(UINT32_MAX, data)); + mu_assert_int_eq(5, varint_uint32_unpack(&out_value, data, 8)); + mu_assert_int_eq(UINT32_MAX, out_value); +} + +MU_TEST(test_varint_basic_i) { + mu_assert_int_eq(5, varint_int32_length(INT32_MIN / 2)); + mu_assert_int_eq(1, varint_int32_length(0)); + mu_assert_int_eq(5, varint_int32_length(INT32_MAX / 2)); + + mu_assert_int_eq(2, varint_int32_length(127)); + mu_assert_int_eq(2, varint_int32_length(-127)); + + uint8_t data[8] = {}; + int32_t out_value; + mu_assert_int_eq(1, varint_int32_pack(0, data)); + mu_assert_int_eq(1, varint_int32_unpack(&out_value, data, 8)); + mu_assert_int_eq(0, out_value); + + mu_assert_int_eq(2, varint_int32_pack(127, data)); + mu_assert_int_eq(2, varint_int32_unpack(&out_value, data, 8)); + mu_assert_int_eq(127, out_value); + + mu_assert_int_eq(2, varint_int32_pack(-127, data)); + mu_assert_int_eq(2, varint_int32_unpack(&out_value, data, 8)); + mu_assert_int_eq(-127, out_value); + + mu_assert_int_eq(5, varint_int32_pack(INT32_MAX, data)); + mu_assert_int_eq(5, varint_int32_unpack(&out_value, data, 8)); + mu_assert_int_eq(INT32_MAX, out_value); + + mu_assert_int_eq(5, varint_int32_pack(INT32_MIN / 2 + 1, data)); + mu_assert_int_eq(5, varint_int32_unpack(&out_value, data, 8)); + mu_assert_int_eq(INT32_MIN / 2 + 1, out_value); +} + +MU_TEST(test_varint_rand_u) { + uint8_t data[8] = {}; + uint32_t out_value; + + for(size_t i = 0; i < 200000; i++) { + uint32_t rand_value = rand(); + mu_assert_int_eq( + varint_uint32_pack(rand_value, data), varint_uint32_unpack(&out_value, data, 8)); + mu_assert_int_eq(rand_value, out_value); + } +} + +MU_TEST(test_varint_rand_i) { + uint8_t data[8] = {}; + int32_t out_value; + + for(size_t i = 0; i < 200000; i++) { + int32_t rand_value = rand() + (INT32_MIN / 2 + 1); + mu_assert_int_eq( + varint_int32_pack(rand_value, data), varint_int32_unpack(&out_value, data, 8)); + mu_assert_int_eq(rand_value, out_value); + } +} + +MU_TEST_SUITE(test_varint_suite) { + MU_RUN_TEST(test_varint_basic_u); + MU_RUN_TEST(test_varint_basic_i); + MU_RUN_TEST(test_varint_rand_u); + MU_RUN_TEST(test_varint_rand_i); +} + +int run_minunit_test_varint() { + MU_RUN_SUITE(test_varint_suite); + return MU_EXIT_CODE; +} \ No newline at end of file diff --git a/applications/updater/util/update_task.c b/applications/updater/util/update_task.c index 6864076d688..b047731977e 100644 --- a/applications/updater/util/update_task.c +++ b/applications/updater/util/update_task.c @@ -170,8 +170,7 @@ static bool update_task_check_file_exists(UpdateTask* update_task, string_t file string_t tmp_path; string_init_set(tmp_path, update_task->update_path); path_append(tmp_path, string_get_cstr(filename)); - bool exists = - (storage_common_stat(update_task->storage, string_get_cstr(tmp_path), NULL) == FSE_OK); + bool exists = storage_file_exists(update_task->storage, string_get_cstr(tmp_path)); string_clear(tmp_path); return exists; } diff --git a/firmware.scons b/firmware.scons index 745543070c2..f47e9ff265d 100644 --- a/firmware.scons +++ b/firmware.scons @@ -200,6 +200,7 @@ fwelf = fwenv["FW_ELF"] = fwenv.Program( "misc", "mbedtls", "loclass", + "lfrfid", # 2nd round "flipperformat", "toolbox", diff --git a/firmware/targets/f7/furi_hal/furi_hal_rfid.c b/firmware/targets/f7/furi_hal/furi_hal_rfid.c index 507c53bfe16..0ade85e0afb 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_rfid.c +++ b/firmware/targets/f7/furi_hal/furi_hal_rfid.c @@ -6,6 +6,7 @@ #include #include +#include #define FURI_HAL_RFID_READ_TIMER TIM1 #define FURI_HAL_RFID_READ_TIMER_CHANNEL LL_TIM_CHANNEL_CH1N @@ -16,8 +17,14 @@ #define FURI_HAL_RFID_EMULATE_TIMER_IRQ FuriHalInterruptIdTIM2 #define FURI_HAL_RFID_EMULATE_TIMER_CHANNEL LL_TIM_CHANNEL_CH3 +#define RFID_CAPTURE_TIM TIM2 +#define RFID_CAPTURE_IND_CH LL_TIM_CHANNEL_CH3 +#define RFID_CAPTURE_DIR_CH LL_TIM_CHANNEL_CH4 + typedef struct { FuriHalRfidEmulateCallback callback; + FuriHalRfidDMACallback dma_callback; + FuriHalRfidReadCaptureCallback read_capture_callback; void* context; } FuriHalRfid; @@ -212,6 +219,185 @@ void furi_hal_rfid_tim_emulate_stop() { furi_hal_interrupt_set_isr(FURI_HAL_RFID_EMULATE_TIMER_IRQ, NULL, NULL); } +static void furi_hal_capture_dma_isr(void* context) { + UNUSED(context); + + // Channel 3, positive level + if(LL_TIM_IsActiveFlag_CC3(RFID_CAPTURE_TIM)) { + LL_TIM_ClearFlag_CC3(RFID_CAPTURE_TIM); + furi_hal_rfid->read_capture_callback( + true, LL_TIM_IC_GetCaptureCH3(RFID_CAPTURE_TIM), furi_hal_rfid->context); + } + + // Channel 4, overall level + if(LL_TIM_IsActiveFlag_CC4(RFID_CAPTURE_TIM)) { + LL_TIM_ClearFlag_CC4(RFID_CAPTURE_TIM); + LL_TIM_SetCounter(RFID_CAPTURE_TIM, 0); + furi_hal_rfid->read_capture_callback( + false, LL_TIM_IC_GetCaptureCH4(RFID_CAPTURE_TIM), furi_hal_rfid->context); + } +} + +void furi_hal_rfid_tim_read_capture_start(FuriHalRfidReadCaptureCallback callback, void* context) { + FURI_CRITICAL_ENTER(); + LL_TIM_DeInit(RFID_CAPTURE_TIM); + FURI_CRITICAL_EXIT(); + + furi_assert(furi_hal_rfid); + + furi_hal_rfid->read_capture_callback = callback; + furi_hal_rfid->context = context; + + // Timer: base + LL_TIM_InitTypeDef TIM_InitStruct = {0}; + TIM_InitStruct.Prescaler = 64 - 1; + TIM_InitStruct.CounterMode = LL_TIM_COUNTERMODE_UP; + TIM_InitStruct.Autoreload = UINT32_MAX; + TIM_InitStruct.ClockDivision = LL_TIM_CLOCKDIVISION_DIV1; + LL_TIM_Init(RFID_CAPTURE_TIM, &TIM_InitStruct); + + // Timer: advanced + LL_TIM_SetClockSource(RFID_CAPTURE_TIM, LL_TIM_CLOCKSOURCE_INTERNAL); + LL_TIM_DisableARRPreload(RFID_CAPTURE_TIM); + LL_TIM_SetTriggerInput(RFID_CAPTURE_TIM, LL_TIM_TS_TI2FP2); + LL_TIM_SetSlaveMode(RFID_CAPTURE_TIM, LL_TIM_SLAVEMODE_DISABLED); + LL_TIM_SetTriggerOutput(RFID_CAPTURE_TIM, LL_TIM_TRGO_RESET); + LL_TIM_EnableMasterSlaveMode(RFID_CAPTURE_TIM); + LL_TIM_DisableDMAReq_TRIG(RFID_CAPTURE_TIM); + LL_TIM_DisableIT_TRIG(RFID_CAPTURE_TIM); + LL_TIM_SetRemap(RFID_CAPTURE_TIM, LL_TIM_TIM2_TI4_RMP_COMP1); + + // Timer: channel 3 indirect + LL_TIM_IC_SetActiveInput(RFID_CAPTURE_TIM, RFID_CAPTURE_IND_CH, LL_TIM_ACTIVEINPUT_INDIRECTTI); + LL_TIM_IC_SetPrescaler(RFID_CAPTURE_TIM, RFID_CAPTURE_IND_CH, LL_TIM_ICPSC_DIV1); + LL_TIM_IC_SetPolarity(RFID_CAPTURE_TIM, RFID_CAPTURE_IND_CH, LL_TIM_IC_POLARITY_FALLING); + LL_TIM_IC_SetFilter(RFID_CAPTURE_TIM, RFID_CAPTURE_IND_CH, LL_TIM_IC_FILTER_FDIV1); + + // Timer: channel 4 direct + LL_TIM_IC_SetActiveInput(RFID_CAPTURE_TIM, RFID_CAPTURE_DIR_CH, LL_TIM_ACTIVEINPUT_DIRECTTI); + LL_TIM_IC_SetPrescaler(RFID_CAPTURE_TIM, RFID_CAPTURE_DIR_CH, LL_TIM_ICPSC_DIV1); + LL_TIM_IC_SetPolarity(RFID_CAPTURE_TIM, RFID_CAPTURE_DIR_CH, LL_TIM_IC_POLARITY_RISING); + LL_TIM_IC_SetFilter(RFID_CAPTURE_TIM, RFID_CAPTURE_DIR_CH, LL_TIM_IC_FILTER_FDIV1); + + furi_hal_interrupt_set_isr(FURI_HAL_RFID_EMULATE_TIMER_IRQ, furi_hal_capture_dma_isr, NULL); + + LL_TIM_EnableIT_CC3(RFID_CAPTURE_TIM); + LL_TIM_EnableIT_CC4(RFID_CAPTURE_TIM); + LL_TIM_CC_EnableChannel(RFID_CAPTURE_TIM, RFID_CAPTURE_IND_CH); + LL_TIM_CC_EnableChannel(RFID_CAPTURE_TIM, RFID_CAPTURE_DIR_CH); + LL_TIM_SetCounter(RFID_CAPTURE_TIM, 0); + LL_TIM_EnableCounter(RFID_CAPTURE_TIM); + + furi_hal_rfid_comp_start(); +} + +void furi_hal_rfid_tim_read_capture_stop() { + furi_hal_rfid_comp_stop(); + + furi_hal_interrupt_set_isr(FURI_HAL_RFID_EMULATE_TIMER_IRQ, NULL, NULL); + + FURI_CRITICAL_ENTER(); + LL_TIM_DeInit(RFID_CAPTURE_TIM); + FURI_CRITICAL_EXIT(); +} + +static void furi_hal_rfid_dma_isr() { + if(LL_DMA_IsActiveFlag_HT1(DMA1)) { + LL_DMA_ClearFlag_HT1(DMA1); + furi_hal_rfid->dma_callback(true, furi_hal_rfid->context); + } + + if(LL_DMA_IsActiveFlag_TC1(DMA1)) { + LL_DMA_ClearFlag_TC1(DMA1); + furi_hal_rfid->dma_callback(false, furi_hal_rfid->context); + } +} + +void furi_hal_rfid_tim_emulate_dma_start( + uint32_t* duration, + uint32_t* pulse, + size_t length, + FuriHalRfidDMACallback callback, + void* context) { + furi_assert(furi_hal_rfid); + + // setup interrupts + furi_hal_rfid->dma_callback = callback; + furi_hal_rfid->context = context; + + // setup pins + furi_hal_rfid_pins_emulate(); + + // configure timer + furi_hal_rfid_tim_emulate(125000); + LL_TIM_OC_SetPolarity( + FURI_HAL_RFID_EMULATE_TIMER, FURI_HAL_RFID_EMULATE_TIMER_CHANNEL, LL_TIM_OCPOLARITY_HIGH); + LL_TIM_EnableDMAReq_UPDATE(FURI_HAL_RFID_EMULATE_TIMER); + + // configure DMA "mem -> ARR" channel + LL_DMA_InitTypeDef dma_config = {0}; + dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (FURI_HAL_RFID_EMULATE_TIMER->ARR); + dma_config.MemoryOrM2MDstAddress = (uint32_t)duration; + 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_WORD; + dma_config.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_WORD; + dma_config.NbData = length; + dma_config.PeriphRequest = LL_DMAMUX_REQ_TIM2_UP; + dma_config.Priority = LL_DMA_MODE_NORMAL; + LL_DMA_Init(DMA1, LL_DMA_CHANNEL_1, &dma_config); + LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_1); + + // configure DMA "mem -> CCR3" channel +#if FURI_HAL_RFID_EMULATE_TIMER_CHANNEL == LL_TIM_CHANNEL_CH3 + dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (FURI_HAL_RFID_EMULATE_TIMER->CCR3); +#else +#error Update this code. Would you kindly? +#endif + dma_config.MemoryOrM2MDstAddress = (uint32_t)pulse; + 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_WORD; + dma_config.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_WORD; + dma_config.NbData = length; + dma_config.PeriphRequest = LL_DMAMUX_REQ_TIM2_UP; + dma_config.Priority = LL_DMA_MODE_NORMAL; + LL_DMA_Init(DMA1, LL_DMA_CHANNEL_2, &dma_config); + LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_2); + + // attach interrupt to one of DMA channels + furi_hal_interrupt_set_isr(FuriHalInterruptIdDma1Ch1, furi_hal_rfid_dma_isr, NULL); + LL_DMA_EnableIT_TC(DMA1, LL_DMA_CHANNEL_1); + LL_DMA_EnableIT_HT(DMA1, LL_DMA_CHANNEL_1); + + // start + LL_TIM_EnableAllOutputs(FURI_HAL_RFID_EMULATE_TIMER); + + LL_TIM_SetCounter(FURI_HAL_RFID_EMULATE_TIMER, 0); + LL_TIM_EnableCounter(FURI_HAL_RFID_EMULATE_TIMER); +} + +void furi_hal_rfid_tim_emulate_dma_stop() { + LL_TIM_DisableCounter(FURI_HAL_RFID_EMULATE_TIMER); + LL_TIM_DisableAllOutputs(FURI_HAL_RFID_EMULATE_TIMER); + + furi_hal_interrupt_set_isr(FuriHalInterruptIdDma1Ch1, NULL, NULL); + LL_DMA_DisableIT_TC(DMA1, LL_DMA_CHANNEL_1); + LL_DMA_DisableIT_HT(DMA1, LL_DMA_CHANNEL_1); + + FURI_CRITICAL_ENTER(); + + LL_DMA_DeInit(DMA1, LL_DMA_CHANNEL_1); + LL_DMA_DeInit(DMA1, LL_DMA_CHANNEL_2); + LL_TIM_DeInit(FURI_HAL_RFID_EMULATE_TIMER); + + FURI_CRITICAL_EXIT(); +} + void furi_hal_rfid_tim_reset() { FURI_CRITICAL_ENTER(); diff --git a/firmware/targets/furi_hal_include/furi_hal_rfid.h b/firmware/targets/furi_hal_include/furi_hal_rfid.h index d26ba53fee1..36563c1d16a 100644 --- a/firmware/targets/furi_hal_include/furi_hal_rfid.h +++ b/firmware/targets/furi_hal_include/furi_hal_rfid.h @@ -7,6 +7,7 @@ #include #include +#include #ifdef __cplusplus extern "C" { @@ -63,6 +64,23 @@ typedef void (*FuriHalRfidEmulateCallback)(void* context); */ void furi_hal_rfid_tim_emulate_start(FuriHalRfidEmulateCallback callback, void* context); +typedef void (*FuriHalRfidReadCaptureCallback)(bool level, uint32_t duration, void* context); + +void furi_hal_rfid_tim_read_capture_start(FuriHalRfidReadCaptureCallback callback, void* context); + +void furi_hal_rfid_tim_read_capture_stop(); + +typedef void (*FuriHalRfidDMACallback)(bool half, void* context); + +void furi_hal_rfid_tim_emulate_dma_start( + uint32_t* duration, + uint32_t* pulse, + size_t length, + FuriHalRfidDMACallback callback, + void* context); + +void furi_hal_rfid_tim_emulate_dma_stop(); + /** Stop emulation timer */ void furi_hal_rfid_tim_emulate_stop(); diff --git a/furi/core/core_defines.h b/furi/core/core_defines.h index 9ed05b2107c..801810f6a14 100644 --- a/furi/core/core_defines.h +++ b/furi/core/core_defines.h @@ -92,6 +92,8 @@ extern "C" { #define FURI_BIT_CLEAR(x, n) ((x) &= ~(1 << (n))) #endif +#define FURI_SW_MEMBARRIER() asm volatile("" : : : "memory") + #ifdef __cplusplus } #endif diff --git a/lib/SConscript b/lib/SConscript index 5e5bb2eaa7f..4498c4c9c6f 100644 --- a/lib/SConscript +++ b/lib/SConscript @@ -77,6 +77,7 @@ libs = env.BuildModules( "appframe", "misc", "loclass", + "lfrfid", ], ) diff --git a/lib/flipper_format/flipper_format.h b/lib/flipper_format/flipper_format.h index 6163dee0e6d..9e82bb31100 100644 --- a/lib/flipper_format/flipper_format.h +++ b/lib/flipper_format/flipper_format.h @@ -41,7 +41,7 @@ * Writing: * * ~~~~~~~~~~~~~~~~~~~~~ - * FlipperFormat format = flipper_format_file_alloc(storage); + * FlipperFormat* format = flipper_format_file_alloc(storage); * * do { * const uint32_t version = 1; @@ -66,7 +66,7 @@ * Reading: * * ~~~~~~~~~~~~~~~~~~~~~ - * FlipperFormat file = flipper_format_file_alloc(storage); + * FlipperFormat* file = flipper_format_file_alloc(storage); * * do { * uint32_t version = 1; diff --git a/lib/lfrfid/SConscript b/lib/lfrfid/SConscript new file mode 100644 index 00000000000..8d354288321 --- /dev/null +++ b/lib/lfrfid/SConscript @@ -0,0 +1,19 @@ +Import("env") + +env.Append( + LINT_SOURCES=[ + "#/lib/lfrfid", + ], + CPPPATH=[ + "#/lib/lfrfid", + ], +) + +libenv = env.Clone(FW_LIB_NAME="lfrfid") +libenv.ApplyLibFlags() + +sources = libenv.GlobRecursive("*.c*") + +lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources) +libenv.Install("${LIB_DIST_DIR}", lib) +Return("lib") diff --git a/lib/lfrfid/lfrfid_dict_file.c b/lib/lfrfid/lfrfid_dict_file.c new file mode 100644 index 00000000000..bb6af39a498 --- /dev/null +++ b/lib/lfrfid/lfrfid_dict_file.c @@ -0,0 +1,182 @@ +#include "lfrfid_dict_file.h" +#include +#include +#include + +#define LFRFID_DICT_FILETYPE "Flipper RFID key" + +bool lfrfid_dict_file_save(ProtocolDict* dict, ProtocolId protocol, const char* filename) { + furi_check(protocol != PROTOCOL_NO); + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* file = flipper_format_file_alloc(storage); + size_t data_size = protocol_dict_get_data_size(dict, protocol); + uint8_t* data = malloc(data_size); + bool result = false; + + do { + if(!flipper_format_file_open_always(file, filename)) break; + if(!flipper_format_write_header_cstr(file, LFRFID_DICT_FILETYPE, 1)) break; + + // TODO: write comment about protocol types into file + + if(!flipper_format_write_string_cstr( + file, "Key type", protocol_dict_get_name(dict, protocol))) + break; + + // TODO: write comment about protocol sizes into file + + protocol_dict_get_data(dict, protocol, data, data_size); + + if(!flipper_format_write_hex(file, "Data", data, data_size)) break; + result = true; + } while(false); + + flipper_format_free(file); + furi_record_close(RECORD_STORAGE); + free(data); + + return result; +} + +static void lfrfid_dict_protocol_indala_data( + uint8_t* data, + size_t data_size, + uint8_t* protocol_data, + size_t protocol_data_size) { + UNUSED(data_size); + memset(protocol_data, 0, protocol_data_size); + + // fc + bit_lib_set_bit(protocol_data, 24, bit_lib_get_bit(data, 0)); + bit_lib_set_bit(protocol_data, 16, bit_lib_get_bit(data, 1)); + bit_lib_set_bit(protocol_data, 11, bit_lib_get_bit(data, 2)); + bit_lib_set_bit(protocol_data, 14, bit_lib_get_bit(data, 3)); + bit_lib_set_bit(protocol_data, 15, bit_lib_get_bit(data, 4)); + bit_lib_set_bit(protocol_data, 20, bit_lib_get_bit(data, 5)); + bit_lib_set_bit(protocol_data, 6, bit_lib_get_bit(data, 6)); + bit_lib_set_bit(protocol_data, 25, bit_lib_get_bit(data, 7)); + + // cn + bit_lib_set_bit(protocol_data, 9, bit_lib_get_bit(data, 8 + 0)); + bit_lib_set_bit(protocol_data, 12, bit_lib_get_bit(data, 8 + 1)); + bit_lib_set_bit(protocol_data, 10, bit_lib_get_bit(data, 8 + 2)); + bit_lib_set_bit(protocol_data, 7, bit_lib_get_bit(data, 8 + 3)); + bit_lib_set_bit(protocol_data, 19, bit_lib_get_bit(data, 8 + 4)); + bit_lib_set_bit(protocol_data, 3, bit_lib_get_bit(data, 8 + 5)); + bit_lib_set_bit(protocol_data, 2, bit_lib_get_bit(data, 8 + 6)); + bit_lib_set_bit(protocol_data, 18, bit_lib_get_bit(data, 8 + 7)); + bit_lib_set_bit(protocol_data, 13, bit_lib_get_bit(data, 8 + 8)); + bit_lib_set_bit(protocol_data, 0, bit_lib_get_bit(data, 8 + 9)); + bit_lib_set_bit(protocol_data, 4, bit_lib_get_bit(data, 8 + 10)); + bit_lib_set_bit(protocol_data, 21, bit_lib_get_bit(data, 8 + 11)); + bit_lib_set_bit(protocol_data, 23, bit_lib_get_bit(data, 8 + 12)); + bit_lib_set_bit(protocol_data, 26, bit_lib_get_bit(data, 8 + 13)); + bit_lib_set_bit(protocol_data, 17, bit_lib_get_bit(data, 8 + 14)); + bit_lib_set_bit(protocol_data, 8, bit_lib_get_bit(data, 8 + 15)); + + const uint32_t fc_and_card = data[0] << 16 | data[1] << 8 | data[2]; + + // indala checksum + uint8_t checksum_sum = 0; + checksum_sum += ((fc_and_card >> 14) & 1); + checksum_sum += ((fc_and_card >> 12) & 1); + checksum_sum += ((fc_and_card >> 9) & 1); + checksum_sum += ((fc_and_card >> 8) & 1); + checksum_sum += ((fc_and_card >> 6) & 1); + checksum_sum += ((fc_and_card >> 5) & 1); + checksum_sum += ((fc_and_card >> 2) & 1); + checksum_sum += ((fc_and_card >> 0) & 1); + checksum_sum = checksum_sum & 0b1; + + if(checksum_sum) { + bit_lib_set_bit(protocol_data, 27, 0); + bit_lib_set_bit(protocol_data, 28, 1); + } else { + bit_lib_set_bit(protocol_data, 27, 1); + bit_lib_set_bit(protocol_data, 28, 0); + } + + // wiegand parity + uint8_t even_parity_sum = 0; + for(int8_t i = 12; i < 24; i++) { + if(((fc_and_card >> i) & 1) == 1) { + even_parity_sum++; + } + } + bit_lib_set_bit(protocol_data, 1, even_parity_sum % 2); + + uint8_t odd_parity_sum = 1; + for(int8_t i = 0; i < 12; i++) { + if(((fc_and_card >> i) & 1) == 1) { + odd_parity_sum++; + } + } + bit_lib_set_bit(protocol_data, 5, odd_parity_sum % 2); +} + +static ProtocolId lfrfid_dict_protocol_fallback( + ProtocolDict* dict, + const char* protocol_name, + FlipperFormat* file) { + ProtocolId result = PROTOCOL_NO; + if(strcmp(protocol_name, "I40134") == 0) { + ProtocolId protocol = LFRFIDProtocolIndala26; + + size_t data_size = 3; + size_t protocol_data_size = protocol_dict_get_data_size(dict, protocol); + uint8_t* data = malloc(data_size); + uint8_t* protocol_data = malloc(protocol_data_size); + if(flipper_format_read_hex(file, "Data", data, data_size)) { + lfrfid_dict_protocol_indala_data(data, data_size, protocol_data, protocol_data_size); + protocol_dict_set_data(dict, protocol, protocol_data, protocol_data_size); + result = protocol; + } + free(protocol_data); + free(data); + } + + return result; +} + +ProtocolId lfrfid_dict_file_load(ProtocolDict* dict, const char* filename) { + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* file = flipper_format_file_alloc(storage); + ProtocolId result = PROTOCOL_NO; + uint8_t* data = malloc(protocol_dict_get_max_data_size(dict)); + string_t str_result; + string_init(str_result); + + do { + if(!flipper_format_file_open_existing(file, filename)) break; + + // header + uint32_t version; + if(!flipper_format_read_header(file, str_result, &version)) break; + if(string_cmp_str(str_result, LFRFID_DICT_FILETYPE) != 0) break; + if(version != 1) break; + + // type + if(!flipper_format_read_string(file, "Key type", str_result)) break; + ProtocolId protocol; + protocol = protocol_dict_get_protocol_by_name(dict, string_get_cstr(str_result)); + + if(protocol == PROTOCOL_NO) { + protocol = lfrfid_dict_protocol_fallback(dict, string_get_cstr(str_result), file); + if(protocol == PROTOCOL_NO) break; + } else { + // data + size_t data_size = protocol_dict_get_data_size(dict, protocol); + if(!flipper_format_read_hex(file, "Data", data, data_size)) break; + protocol_dict_set_data(dict, protocol, data, data_size); + } + + result = protocol; + } while(false); + + free(data); + string_clear(str_result); + flipper_format_free(file); + furi_record_close(RECORD_STORAGE); + + return result; +} \ No newline at end of file diff --git a/lib/lfrfid/lfrfid_dict_file.h b/lib/lfrfid/lfrfid_dict_file.h new file mode 100644 index 00000000000..077bb0ba138 --- /dev/null +++ b/lib/lfrfid/lfrfid_dict_file.h @@ -0,0 +1,31 @@ +#pragma once +#include +#include "protocols/lfrfid_protocols.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Save protocol from dictionary to file + * + * @param dict + * @param protocol + * @param filename + * @return true + * @return false + */ +bool lfrfid_dict_file_save(ProtocolDict* dict, ProtocolId protocol, const char* filename); + +/** + * @brief Load protocol from file to dictionary + * + * @param dict + * @param filename + * @return ProtocolId + */ +ProtocolId lfrfid_dict_file_load(ProtocolDict* dict, const char* filename); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/lfrfid/lfrfid_raw_file.c b/lib/lfrfid/lfrfid_raw_file.c new file mode 100644 index 00000000000..27c6f24754e --- /dev/null +++ b/lib/lfrfid/lfrfid_raw_file.c @@ -0,0 +1,145 @@ +#include "lfrfid_raw_file.h" +#include "tools/varint_pair.h" +#include +#include + +#define LFRFID_RAW_FILE_MAGIC 0x4C464952 +#define LFRFID_RAW_FILE_VERSION 1 + +#define TAG "RFID RAW File" + +typedef struct { + uint32_t magic; + uint32_t version; + float frequency; + float duty_cycle; + uint32_t max_buffer_size; +} LFRFIDRawFileHeader; + +struct LFRFIDRawFile { + Stream* stream; + uint32_t max_buffer_size; + + uint8_t* buffer; + uint32_t buffer_size; + size_t buffer_counter; +}; + +LFRFIDRawFile* lfrfid_raw_file_alloc(Storage* storage) { + LFRFIDRawFile* file = malloc(sizeof(LFRFIDRawFile)); + file->stream = file_stream_alloc(storage); + file->buffer = NULL; + return file; +} + +void lfrfid_raw_file_free(LFRFIDRawFile* file) { + if(file->buffer) free(file->buffer); + stream_free(file->stream); + free(file); +} + +bool lfrfid_raw_file_open_write(LFRFIDRawFile* file, const char* file_path) { + return file_stream_open(file->stream, file_path, FSAM_READ_WRITE, FSOM_CREATE_ALWAYS); +} + +bool lfrfid_raw_file_open_read(LFRFIDRawFile* file, const char* file_path) { + return file_stream_open(file->stream, file_path, FSAM_READ, FSOM_OPEN_EXISTING); +} + +bool lfrfid_raw_file_write_header( + LFRFIDRawFile* file, + float frequency, + float duty_cycle, + uint32_t max_buffer_size) { + LFRFIDRawFileHeader header = { + .magic = LFRFID_RAW_FILE_MAGIC, + .version = LFRFID_RAW_FILE_VERSION, + .frequency = frequency, + .duty_cycle = duty_cycle, + .max_buffer_size = max_buffer_size}; + + size_t size = stream_write(file->stream, (uint8_t*)&header, sizeof(LFRFIDRawFileHeader)); + return (size == sizeof(LFRFIDRawFileHeader)); +} + +bool lfrfid_raw_file_write_buffer(LFRFIDRawFile* file, uint8_t* buffer_data, size_t buffer_size) { + size_t size; + size = stream_write(file->stream, (uint8_t*)&buffer_size, sizeof(size_t)); + if(size != sizeof(size_t)) return false; + + size = stream_write(file->stream, buffer_data, buffer_size); + if(size != buffer_size) return false; + + return true; +} + +bool lfrfid_raw_file_read_header(LFRFIDRawFile* file, float* frequency, float* duty_cycle) { + LFRFIDRawFileHeader header; + size_t size = stream_read(file->stream, (uint8_t*)&header, sizeof(LFRFIDRawFileHeader)); + if(size == sizeof(LFRFIDRawFileHeader)) { + if(header.magic == LFRFID_RAW_FILE_MAGIC && header.version == LFRFID_RAW_FILE_VERSION) { + *frequency = header.frequency; + *duty_cycle = header.duty_cycle; + file->max_buffer_size = header.max_buffer_size; + file->buffer = malloc(file->max_buffer_size); + file->buffer_size = 0; + file->buffer_counter = 0; + return true; + } else { + return false; + } + } else { + return false; + } +} + +bool lfrfid_raw_file_read_pair( + LFRFIDRawFile* file, + uint32_t* duration, + uint32_t* pulse, + bool* pass_end) { + size_t length = 0; + if(file->buffer_counter >= file->buffer_size) { + if(stream_eof(file->stream)) { + // rewind stream and pass header + stream_seek(file->stream, sizeof(LFRFIDRawFileHeader), StreamOffsetFromStart); + if(pass_end) *pass_end = true; + } + + length = stream_read(file->stream, (uint8_t*)&file->buffer_size, sizeof(size_t)); + if(length != sizeof(size_t)) { + FURI_LOG_E(TAG, "read pair: failed to read size"); + return false; + } + + if(file->buffer_size > file->max_buffer_size) { + FURI_LOG_E(TAG, "read pair: buffer size is too big"); + return false; + } + + length = stream_read(file->stream, file->buffer, file->buffer_size); + if(length != file->buffer_size) { + FURI_LOG_E(TAG, "read pair: failed to read data"); + return false; + } + + file->buffer_counter = 0; + } + + size_t size = 0; + bool result = varint_pair_unpack( + &file->buffer[file->buffer_counter], + (size_t)(file->buffer_size - file->buffer_counter), + pulse, + duration, + &size); + + if(result) { + file->buffer_counter += size; + } else { + FURI_LOG_E(TAG, "read pair: buffer is too small"); + return false; + } + + return true; +} \ No newline at end of file diff --git a/lib/lfrfid/lfrfid_raw_file.h b/lib/lfrfid/lfrfid_raw_file.h new file mode 100644 index 00000000000..3f2f14e09f4 --- /dev/null +++ b/lib/lfrfid/lfrfid_raw_file.h @@ -0,0 +1,95 @@ +#pragma once +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct LFRFIDRawFile LFRFIDRawFile; + +/** + * @brief Allocate a new LFRFIDRawFile instance + * + * @param storage + * @return LFRFIDRawFile* + */ +LFRFIDRawFile* lfrfid_raw_file_alloc(Storage* storage); + +/** + * @brief Free a LFRFIDRawFile instance + * + * @param file + */ +void lfrfid_raw_file_free(LFRFIDRawFile* file); + +/** + * @brief Open RAW file for writing + * + * @param file + * @param file_path + * @return bool + */ +bool lfrfid_raw_file_open_write(LFRFIDRawFile* file, const char* file_path); + +/** + * @brief Open RAW file for reading + * @param file + * @param file_path + * @return bool + */ +bool lfrfid_raw_file_open_read(LFRFIDRawFile* file, const char* file_path); + +/** + * @brief Write RAW file header + * + * @param file + * @param frequency + * @param duty_cycle + * @param max_buffer_size + * @return bool + */ +bool lfrfid_raw_file_write_header( + LFRFIDRawFile* file, + float frequency, + float duty_cycle, + uint32_t max_buffer_size); + +/** + * @brief Write data to RAW file + * + * @param file + * @param buffer_data + * @param buffer_size + * @return bool + */ +bool lfrfid_raw_file_write_buffer(LFRFIDRawFile* file, uint8_t* buffer_data, size_t buffer_size); + +/** + * @brief Read RAW file header + * + * @param file + * @param frequency + * @param duty_cycle + * @return bool + */ +bool lfrfid_raw_file_read_header(LFRFIDRawFile* file, float* frequency, float* duty_cycle); + +/** + * @brief Read varint-encoded pair from RAW file + * + * @param file + * @param duration + * @param pulse + * @param pass_end file was wrapped around, can be NULL + * @return bool + */ +bool lfrfid_raw_file_read_pair( + LFRFIDRawFile* file, + uint32_t* duration, + uint32_t* pulse, + bool* pass_end); + +#ifdef __cplusplus +} +#endif diff --git a/lib/lfrfid/lfrfid_raw_worker.c b/lib/lfrfid/lfrfid_raw_worker.c new file mode 100644 index 00000000000..4050a8ca8a5 --- /dev/null +++ b/lib/lfrfid/lfrfid_raw_worker.c @@ -0,0 +1,357 @@ +#include +#include +#include +#include +#include +#include "lfrfid_raw_worker.h" +#include "lfrfid_raw_file.h" +#include "tools/varint_pair.h" + +#define EMULATE_BUFFER_SIZE 1024 +#define RFID_DATA_BUFFER_SIZE 2048 +#define READ_DATA_BUFFER_COUNT 4 + +#define TAG_EMULATE "RAW EMULATE" + +// emulate mode +typedef struct { + size_t overrun_count; + StreamBufferHandle_t stream; +} RfidEmulateCtx; + +typedef struct { + uint32_t emulate_buffer_arr[EMULATE_BUFFER_SIZE]; + uint32_t emulate_buffer_ccr[EMULATE_BUFFER_SIZE]; + RfidEmulateCtx ctx; +} LFRFIDRawWorkerEmulateData; + +typedef enum { + HalfTransfer, + TransferComplete, +} LFRFIDRawEmulateDMAEvent; + +// read mode +#define READ_TEMP_DATA_SIZE 10 + +typedef struct { + BufferStream* stream; + VarintPair* pair; +} LFRFIDRawWorkerReadData; + +// main worker +struct LFRFIDRawWorker { + string_t file_path; + FuriThread* thread; + FuriEventFlag* events; + + LFRFIDWorkerEmulateRawCallback emulate_callback; + LFRFIDWorkerReadRawCallback read_callback; + void* context; + + float frequency; + float duty_cycle; +}; + +typedef enum { + LFRFIDRawWorkerEventStop, +} LFRFIDRawWorkerEvent; + +static int32_t lfrfid_raw_read_worker_thread(void* thread_context); +static int32_t lfrfid_raw_emulate_worker_thread(void* thread_context); + +LFRFIDRawWorker* lfrfid_raw_worker_alloc() { + LFRFIDRawWorker* worker = malloc(sizeof(LFRFIDRawWorker)); + + worker->thread = furi_thread_alloc(); + furi_thread_set_name(worker->thread, "lfrfid_raw_worker"); + furi_thread_set_context(worker->thread, worker); + furi_thread_set_stack_size(worker->thread, 2048); + + worker->events = furi_event_flag_alloc(NULL); + + string_init(worker->file_path); + return worker; +} + +void lfrfid_raw_worker_free(LFRFIDRawWorker* worker) { + furi_thread_free(worker->thread); + furi_event_flag_free(worker->events); + string_clear(worker->file_path); + free(worker); +} + +void lfrfid_raw_worker_start_read( + LFRFIDRawWorker* worker, + const char* file_path, + float freq, + float duty_cycle, + LFRFIDWorkerReadRawCallback callback, + void* context) { + furi_check(furi_thread_get_state(worker->thread) == FuriThreadStateStopped); + + string_set(worker->file_path, file_path); + + worker->frequency = freq; + worker->duty_cycle = duty_cycle; + worker->read_callback = callback; + worker->context = context; + + furi_thread_set_callback(worker->thread, lfrfid_raw_read_worker_thread); + + furi_thread_start(worker->thread); +} + +void lfrfid_raw_worker_start_emulate( + LFRFIDRawWorker* worker, + const char* file_path, + LFRFIDWorkerEmulateRawCallback callback, + void* context) { + furi_check(furi_thread_get_state(worker->thread) == FuriThreadStateStopped); + string_set(worker->file_path, file_path); + worker->emulate_callback = callback; + worker->context = context; + furi_thread_set_callback(worker->thread, lfrfid_raw_emulate_worker_thread); + furi_thread_start(worker->thread); +} + +void lfrfid_raw_worker_stop(LFRFIDRawWorker* worker) { + worker->emulate_callback = NULL; + worker->context = NULL; + worker->read_callback = NULL; + worker->context = NULL; + furi_event_flag_set(worker->events, 1 << LFRFIDRawWorkerEventStop); + furi_thread_join(worker->thread); +} + +static void lfrfid_raw_worker_capture(bool level, uint32_t duration, void* context) { + LFRFIDRawWorkerReadData* ctx = context; + + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + + bool need_to_send = varint_pair_pack(ctx->pair, level, duration); + + if(need_to_send) { + buffer_stream_send_from_isr( + ctx->stream, + varint_pair_get_data(ctx->pair), + varint_pair_get_size(ctx->pair), + &xHigherPriorityTaskWoken); + varint_pair_reset(ctx->pair); + } + + portYIELD_FROM_ISR(xHigherPriorityTaskWoken); +} + +static int32_t lfrfid_raw_read_worker_thread(void* thread_context) { + LFRFIDRawWorker* worker = (LFRFIDRawWorker*)thread_context; + + Storage* storage = furi_record_open(RECORD_STORAGE); + LFRFIDRawFile* file = lfrfid_raw_file_alloc(storage); + const char* filename = string_get_cstr(worker->file_path); + bool file_valid = lfrfid_raw_file_open_write(file, filename); + + LFRFIDRawWorkerReadData* data = malloc(sizeof(LFRFIDRawWorkerReadData)); + + data->stream = buffer_stream_alloc(RFID_DATA_BUFFER_SIZE, READ_DATA_BUFFER_COUNT); + data->pair = varint_pair_alloc(); + + if(file_valid) { + // write header + file_valid = lfrfid_raw_file_write_header( + file, worker->frequency, worker->duty_cycle, RFID_DATA_BUFFER_SIZE); + } + + if(file_valid) { + // setup carrier + furi_hal_rfid_pins_read(); + furi_hal_rfid_tim_read(worker->frequency, worker->duty_cycle); + furi_hal_rfid_tim_read_start(); + + // stabilize detector + furi_delay_ms(1500); + + // start capture + furi_hal_rfid_tim_read_capture_start(lfrfid_raw_worker_capture, data); + + while(1) { + Buffer* buffer = buffer_stream_receive(data->stream, 100); + + if(buffer != NULL) { + file_valid = lfrfid_raw_file_write_buffer( + file, buffer_get_data(buffer), buffer_get_size(buffer)); + buffer_reset(buffer); + } + + if(!file_valid) { + if(worker->read_callback != NULL) { + // message file_error to worker + worker->read_callback(LFRFIDWorkerReadRawFileError, worker->context); + } + break; + } + + if(buffer_stream_get_overrun_count(data->stream) > 0 && + worker->read_callback != NULL) { + // message overrun to worker + worker->read_callback(LFRFIDWorkerReadRawOverrun, worker->context); + } + + uint32_t flags = furi_event_flag_get(worker->events); + if(FURI_BIT(flags, LFRFIDRawWorkerEventStop)) { + break; + } + } + + furi_hal_rfid_tim_read_capture_stop(); + furi_hal_rfid_tim_read_stop(); + } else { + if(worker->read_callback != NULL) { + // message file_error to worker + worker->read_callback(LFRFIDWorkerReadRawFileError, worker->context); + } + } + + if(!file_valid) { + const uint32_t available_flags = (1 << LFRFIDRawWorkerEventStop); + while(true) { + uint32_t flags = furi_event_flag_wait( + worker->events, available_flags, FuriFlagWaitAny, FuriWaitForever); + + if(FURI_BIT(flags, LFRFIDRawWorkerEventStop)) { + break; + } + } + } + + varint_pair_free(data->pair); + buffer_stream_free(data->stream); + lfrfid_raw_file_free(file); + furi_record_close(RECORD_STORAGE); + free(data); + + return 0; +} + +static void rfid_emulate_dma_isr(bool half, void* context) { + RfidEmulateCtx* ctx = context; + + uint32_t flag = half ? HalfTransfer : TransferComplete; + size_t len = xStreamBufferSendFromISR(ctx->stream, &flag, sizeof(uint32_t), pdFALSE); + if(len != sizeof(uint32_t)) { + ctx->overrun_count++; + } +} + +static int32_t lfrfid_raw_emulate_worker_thread(void* thread_context) { + LFRFIDRawWorker* worker = thread_context; + + bool file_valid = true; + + LFRFIDRawWorkerEmulateData* data = malloc(sizeof(LFRFIDRawWorkerEmulateData)); + + Storage* storage = furi_record_open(RECORD_STORAGE); + data->ctx.overrun_count = 0; + data->ctx.stream = xStreamBufferCreate(sizeof(uint32_t), sizeof(uint32_t)); + + LFRFIDRawFile* file = lfrfid_raw_file_alloc(storage); + + do { + file_valid = lfrfid_raw_file_open_read(file, string_get_cstr(worker->file_path)); + if(!file_valid) break; + file_valid = lfrfid_raw_file_read_header(file, &worker->frequency, &worker->duty_cycle); + if(!file_valid) break; + + for(size_t i = 0; i < EMULATE_BUFFER_SIZE; i++) { + file_valid = lfrfid_raw_file_read_pair( + file, &data->emulate_buffer_arr[i], &data->emulate_buffer_ccr[i], NULL); + if(!file_valid) break; + data->emulate_buffer_arr[i] /= 8; + data->emulate_buffer_arr[i] -= 1; + data->emulate_buffer_ccr[i] /= 8; + } + } while(false); + + furi_hal_rfid_tim_emulate_dma_start( + data->emulate_buffer_arr, + data->emulate_buffer_ccr, + EMULATE_BUFFER_SIZE, + rfid_emulate_dma_isr, + &data->ctx); + + if(!file_valid && worker->emulate_callback != NULL) { + // message file_error to worker + worker->emulate_callback(LFRFIDWorkerEmulateRawFileError, worker->context); + } + + if(file_valid) { + uint32_t flag = 0; + + while(true) { + size_t size = xStreamBufferReceive(data->ctx.stream, &flag, sizeof(uint32_t), 100); + + if(size == sizeof(uint32_t)) { + size_t start = 0; + if(flag == TransferComplete) { + start = (EMULATE_BUFFER_SIZE / 2); + } + + for(size_t i = 0; i < (EMULATE_BUFFER_SIZE / 2); i++) { + file_valid = lfrfid_raw_file_read_pair( + file, + &data->emulate_buffer_arr[start + i], + &data->emulate_buffer_ccr[start + i], + NULL); + if(!file_valid) break; + data->emulate_buffer_arr[i] /= 8; + data->emulate_buffer_arr[i] -= 1; + data->emulate_buffer_ccr[i] /= 8; + } + } else if(size != 0) { + data->ctx.overrun_count++; + } + + if(!file_valid) { + if(worker->emulate_callback != NULL) { + // message file_error to worker + worker->emulate_callback(LFRFIDWorkerEmulateRawFileError, worker->context); + } + break; + } + + if(data->ctx.overrun_count > 0 && worker->emulate_callback != NULL) { + // message overrun to worker + worker->emulate_callback(LFRFIDWorkerEmulateRawOverrun, worker->context); + } + + uint32_t flags = furi_event_flag_get(worker->events); + if(FURI_BIT(flags, LFRFIDRawWorkerEventStop)) { + break; + }; + } + } + + furi_hal_rfid_tim_emulate_dma_stop(); + + if(!file_valid) { + const uint32_t available_flags = (1 << LFRFIDRawWorkerEventStop); + while(true) { + uint32_t flags = furi_event_flag_wait( + worker->events, available_flags, FuriFlagWaitAny, FuriWaitForever); + + if(FURI_BIT(flags, LFRFIDRawWorkerEventStop)) { + break; + }; + } + } + + if(data->ctx.overrun_count) { + FURI_LOG_E(TAG_EMULATE, "overruns: %lu", data->ctx.overrun_count); + } + + vStreamBufferDelete(data->ctx.stream); + lfrfid_raw_file_free(file); + furi_record_close(RECORD_STORAGE); + free(data); + + return 0; +} \ No newline at end of file diff --git a/lib/lfrfid/lfrfid_raw_worker.h b/lib/lfrfid/lfrfid_raw_worker.h new file mode 100644 index 00000000000..1195dd58790 --- /dev/null +++ b/lib/lfrfid/lfrfid_raw_worker.h @@ -0,0 +1,68 @@ +#pragma once +#include +#include "lfrfid_worker.h" +#include +#include "protocols/lfrfid_protocols.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct LFRFIDRawWorker LFRFIDRawWorker; + +/** + * @brief Allocate a new LFRFIDRawWorker instance + * + * @return LFRFIDRawWorker* + */ +LFRFIDRawWorker* lfrfid_raw_worker_alloc(); + +/** + * @brief Free a LFRFIDRawWorker instance + * + * @param worker LFRFIDRawWorker instance + */ +void lfrfid_raw_worker_free(LFRFIDRawWorker* worker); + +/** + * @brief Start reading + * + * @param worker LFRFIDRawWorker instance + * @param file_path path where file will be saved + * @param frequency HW frequency + * @param duty_cycle HW duty cycle + * @param callback callback for read event + * @param context context for callback + */ +void lfrfid_raw_worker_start_read( + LFRFIDRawWorker* worker, + const char* file_path, + float frequency, + float duty_cycle, + LFRFIDWorkerReadRawCallback callback, + void* context); + +/** + * @brief Start emulate + * + * @param worker LFRFIDRawWorker instance + * @param file_path path to file that will be emulated + * @param callback callback for emulate event + * @param context context for callback + */ +void lfrfid_raw_worker_start_emulate( + LFRFIDRawWorker* worker, + const char* file_path, + LFRFIDWorkerEmulateRawCallback callback, + void* context); + +/** + * @brief Stop worker + * + * @param worker + */ +void lfrfid_raw_worker_stop(LFRFIDRawWorker* worker); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/lfrfid/lfrfid_worker.c b/lib/lfrfid/lfrfid_worker.c new file mode 100644 index 00000000000..8b4f8b6a985 --- /dev/null +++ b/lib/lfrfid/lfrfid_worker.c @@ -0,0 +1,169 @@ +#include +#include +#include +#include "lfrfid_worker_i.h" + +typedef enum { + LFRFIDEventStopThread = (1 << 0), + LFRFIDEventStopMode = (1 << 1), + LFRFIDEventRead = (1 << 2), + LFRFIDEventWrite = (1 << 3), + LFRFIDEventEmulate = (1 << 4), + LFRFIDEventReadRaw = (1 << 5), + LFRFIDEventEmulateRaw = (1 << 6), + LFRFIDEventAll = + (LFRFIDEventStopThread | LFRFIDEventStopMode | LFRFIDEventRead | LFRFIDEventWrite | + LFRFIDEventEmulate | LFRFIDEventReadRaw | LFRFIDEventEmulateRaw), +} LFRFIDEventType; + +static int32_t lfrfid_worker_thread(void* thread_context); + +LFRFIDWorker* lfrfid_worker_alloc(ProtocolDict* dict) { + furi_assert(dict); + + LFRFIDWorker* worker = malloc(sizeof(LFRFIDWorker)); + worker->mode_index = LFRFIDWorkerIdle; + worker->read_cb = NULL; + worker->write_cb = NULL; + worker->cb_ctx = NULL; + worker->raw_filename = NULL; + worker->mode_storage = NULL; + + worker->thread = furi_thread_alloc(); + furi_thread_set_name(worker->thread, "lfrfid_worker"); + furi_thread_set_callback(worker->thread, lfrfid_worker_thread); + furi_thread_set_context(worker->thread, worker); + furi_thread_set_stack_size(worker->thread, 2048); + + worker->protocols = dict; + + return worker; +} + +void lfrfid_worker_free(LFRFIDWorker* worker) { + if(worker->raw_filename) { + free(worker->raw_filename); + } + + furi_thread_free(worker->thread); + free(worker); +} + +void lfrfid_worker_read_start( + LFRFIDWorker* worker, + LFRFIDWorkerReadType type, + LFRFIDWorkerReadCallback callback, + void* context) { + furi_assert(worker->mode_index == LFRFIDWorkerIdle); + worker->read_type = type; + worker->read_cb = callback; + worker->cb_ctx = context; + furi_thread_flags_set(furi_thread_get_id(worker->thread), LFRFIDEventRead); +} + +void lfrfid_worker_write_start( + LFRFIDWorker* worker, + LFRFIDProtocol protocol, + LFRFIDWorkerWriteCallback callback, + void* context) { + furi_assert(worker->mode_index == LFRFIDWorkerIdle); + worker->protocol = protocol; + worker->write_cb = callback; + worker->cb_ctx = context; + furi_thread_flags_set(furi_thread_get_id(worker->thread), LFRFIDEventWrite); +} + +void lfrfid_worker_emulate_start(LFRFIDWorker* worker, LFRFIDProtocol protocol) { + furi_assert(worker->mode_index == LFRFIDWorkerIdle); + worker->protocol = protocol; + furi_thread_flags_set(furi_thread_get_id(worker->thread), LFRFIDEventEmulate); +} + +void lfrfid_worker_set_filename(LFRFIDWorker* worker, const char* filename) { + if(worker->raw_filename) { + free(worker->raw_filename); + } + + worker->raw_filename = strdup(filename); +} + +void lfrfid_worker_read_raw_start( + LFRFIDWorker* worker, + const char* filename, + LFRFIDWorkerReadType type, + LFRFIDWorkerReadRawCallback callback, + void* context) { + furi_assert(worker->mode_index == LFRFIDWorkerIdle); + worker->read_type = type; + worker->read_raw_cb = callback; + worker->cb_ctx = context; + lfrfid_worker_set_filename(worker, filename); + furi_thread_flags_set(furi_thread_get_id(worker->thread), LFRFIDEventReadRaw); +} + +void lfrfid_worker_emulate_raw_start( + LFRFIDWorker* worker, + const char* filename, + LFRFIDWorkerEmulateRawCallback callback, + void* context) { + furi_assert(worker->mode_index == LFRFIDWorkerIdle); + lfrfid_worker_set_filename(worker, filename); + worker->emulate_raw_cb = callback; + worker->cb_ctx = context; + furi_thread_flags_set(furi_thread_get_id(worker->thread), LFRFIDEventEmulateRaw); +} + +void lfrfid_worker_stop(LFRFIDWorker* worker) { + furi_thread_flags_set(furi_thread_get_id(worker->thread), LFRFIDEventStopMode); +} + +void lfrfid_worker_start_thread(LFRFIDWorker* worker) { + furi_thread_start(worker->thread); +} + +void lfrfid_worker_stop_thread(LFRFIDWorker* worker) { + furi_assert(worker->mode_index == LFRFIDWorkerIdle); + furi_thread_flags_set(furi_thread_get_id(worker->thread), LFRFIDEventStopThread); + furi_thread_join(worker->thread); +} + +bool lfrfid_worker_check_for_stop(LFRFIDWorker* worker) { + UNUSED(worker); + uint32_t flags = furi_thread_flags_get(); + return (flags & LFRFIDEventStopMode); +} + +size_t lfrfid_worker_dict_get_data_size(LFRFIDWorker* worker, LFRFIDProtocol protocol) { + furi_assert(worker->mode_index == LFRFIDWorkerIdle); + return protocol_dict_get_data_size(worker->protocols, protocol); +} + +static int32_t lfrfid_worker_thread(void* thread_context) { + LFRFIDWorker* worker = thread_context; + bool running = true; + + while(running) { + uint32_t flags = furi_thread_flags_wait(LFRFIDEventAll, FuriFlagWaitAny, FuriWaitForever); + if(flags != FuriFlagErrorTimeout) { + // stop thread + if(flags & LFRFIDEventStopThread) break; + + // switch mode + if(flags & LFRFIDEventRead) worker->mode_index = LFRFIDWorkerRead; + if(flags & LFRFIDEventWrite) worker->mode_index = LFRFIDWorkerWrite; + if(flags & LFRFIDEventEmulate) worker->mode_index = LFRFIDWorkerEmulate; + if(flags & LFRFIDEventReadRaw) worker->mode_index = LFRFIDWorkerReadRaw; + if(flags & LFRFIDEventEmulateRaw) worker->mode_index = LFRFIDWorkerEmulateRaw; + + // do mode, if it exists + if(lfrfid_worker_modes[worker->mode_index].process) { + lfrfid_worker_modes[worker->mode_index].process(worker); + } + + // reset mode + worker->mode_index = LFRFIDWorkerIdle; + } + } + + return 0; +} \ No newline at end of file diff --git a/lib/lfrfid/lfrfid_worker.h b/lib/lfrfid/lfrfid_worker.h new file mode 100644 index 00000000000..def9f89a474 --- /dev/null +++ b/lib/lfrfid/lfrfid_worker.h @@ -0,0 +1,152 @@ +/** + * @file lfrfid_worker.h + * + * LFRFID worker + */ + +#pragma once +#include +#include "protocols/lfrfid_protocols.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + LFRFIDWorkerWriteOK, + LFRFIDWorkerWriteProtocolCannotBeWritten, + LFRFIDWorkerWriteFobCannotBeWritten, + LFRFIDWorkerWriteTooLongToWrite, +} LFRFIDWorkerWriteResult; + +typedef enum { + LFRFIDWorkerReadTypeAuto, + LFRFIDWorkerReadTypeASKOnly, + LFRFIDWorkerReadTypePSKOnly, +} LFRFIDWorkerReadType; + +typedef enum { + LFRFIDWorkerReadSenseStart, // TODO: not implemented + LFRFIDWorkerReadSenseEnd, // TODO: not implemented + LFRFIDWorkerReadSenseCardStart, + LFRFIDWorkerReadSenseCardEnd, + LFRFIDWorkerReadStartASK, + LFRFIDWorkerReadStartPSK, + LFRFIDWorkerReadDone, +} LFRFIDWorkerReadResult; + +typedef enum { + LFRFIDWorkerReadRawFileError, + LFRFIDWorkerReadRawOverrun, +} LFRFIDWorkerReadRawResult; + +typedef enum { + LFRFIDWorkerEmulateRawFileError, + LFRFIDWorkerEmulateRawOverrun, +} LFRFIDWorkerEmulateRawResult; + +typedef void ( + *LFRFIDWorkerReadCallback)(LFRFIDWorkerReadResult result, ProtocolId protocol, void* context); +typedef void (*LFRFIDWorkerWriteCallback)(LFRFIDWorkerWriteResult result, void* context); + +typedef void (*LFRFIDWorkerReadRawCallback)(LFRFIDWorkerReadRawResult result, void* context); +typedef void (*LFRFIDWorkerEmulateRawCallback)(LFRFIDWorkerEmulateRawResult result, void* context); + +typedef struct LFRFIDWorker LFRFIDWorker; + +/** + * Allocate LF-RFID worker + * @return LFRFIDWorker* + */ +LFRFIDWorker* lfrfid_worker_alloc(ProtocolDict* dict); + +/** + * Free LF-RFID worker + * @param worker + */ +void lfrfid_worker_free(LFRFIDWorker* worker); + +/** + * Start LF-RFID worker thread + * @param worker + */ +void lfrfid_worker_start_thread(LFRFIDWorker* worker); + +/** + * Stop LF-RFID worker thread + * @param worker + */ +void lfrfid_worker_stop_thread(LFRFIDWorker* worker); + +/** + * @brief Start read mode + * + * @param worker + * @param type + * @param callback + * @param context + */ +void lfrfid_worker_read_start( + LFRFIDWorker* worker, + LFRFIDWorkerReadType type, + LFRFIDWorkerReadCallback callback, + void* context); + +/** + * @brief Start write mode + * + * @param worker + * @param protocol + * @param callback + * @param context + */ +void lfrfid_worker_write_start( + LFRFIDWorker* worker, + LFRFIDProtocol protocol, + LFRFIDWorkerWriteCallback callback, + void* context); + +/** + * Start emulate mode + * @param worker + */ +void lfrfid_worker_emulate_start(LFRFIDWorker* worker, LFRFIDProtocol protocol); + +/** + * @brief Start raw read mode + * + * @param worker + * @param filename + * @param type + * @param callback + * @param context + */ +void lfrfid_worker_read_raw_start( + LFRFIDWorker* worker, + const char* filename, + LFRFIDWorkerReadType type, + LFRFIDWorkerReadRawCallback callback, + void* context); + +/** + * Emulate raw read mode + * @param worker + * @param filename + * @param callback + * @param context + */ +void lfrfid_worker_emulate_raw_start( + LFRFIDWorker* worker, + const char* filename, + LFRFIDWorkerEmulateRawCallback callback, + void* context); + +/** + * Stop all modes + * @param worker + */ +void lfrfid_worker_stop(LFRFIDWorker* worker); + +#ifdef __cplusplus +} +#endif diff --git a/lib/lfrfid/lfrfid_worker_i.h b/lib/lfrfid/lfrfid_worker_i.h new file mode 100644 index 00000000000..33c0bff0851 --- /dev/null +++ b/lib/lfrfid/lfrfid_worker_i.h @@ -0,0 +1,64 @@ +/** + * @file lfrfid_worker_i.h + * + * lfrfid worker, internal definitions + */ + +#pragma once +#include +#include "lfrfid_worker.h" +#include "lfrfid_raw_worker.h" +#include "protocols/lfrfid_protocols.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + void (*const process)(LFRFIDWorker* worker); +} LFRFIDWorkerModeType; + +typedef enum { + LFRFIDWorkerIdle, + LFRFIDWorkerRead, + LFRFIDWorkerWrite, + LFRFIDWorkerEmulate, + LFRFIDWorkerReadRaw, + LFRFIDWorkerEmulateRaw, +} LFRFIDWorkerMode; + +struct LFRFIDWorker { + char* raw_filename; + + LFRFIDWorkerMode mode_index; + void* mode_storage; + + FuriEventFlag* events; + FuriThread* thread; + + LFRFIDWorkerReadType read_type; + + LFRFIDWorkerReadCallback read_cb; + LFRFIDWorkerWriteCallback write_cb; + LFRFIDWorkerReadRawCallback read_raw_cb; + LFRFIDWorkerEmulateRawCallback emulate_raw_cb; + + void* cb_ctx; + + ProtocolDict* protocols; + LFRFIDProtocol protocol; +}; + +extern const LFRFIDWorkerModeType lfrfid_worker_modes[]; + +/** + * @brief Check for stop flag + * + * @param worker + * @return bool + */ +bool lfrfid_worker_check_for_stop(LFRFIDWorker* worker); + +#ifdef __cplusplus +} +#endif diff --git a/lib/lfrfid/lfrfid_worker_modes.c b/lib/lfrfid/lfrfid_worker_modes.c new file mode 100644 index 00000000000..f41a7194a12 --- /dev/null +++ b/lib/lfrfid/lfrfid_worker_modes.c @@ -0,0 +1,624 @@ +#include +#include +#include "lfrfid_worker_i.h" +#include "tools/t5577.h" +#include +#include +#include +#include "tools/varint_pair.h" +#include "tools/bit_lib.h" + +#define TAG "LFRFIDWorker" + +/** + * if READ_DEBUG_GPIO is defined: + * gpio_ext_pa7 will repeat signal coming from the comparator + * gpio_ext_pa6 will show load on the decoder + */ +// #define LFRFID_WORKER_READ_DEBUG_GPIO 1 + +#ifdef LFRFID_WORKER_READ_DEBUG_GPIO +#define LFRFID_WORKER_READ_DEBUG_GPIO_VALUE &gpio_ext_pa7 +#define LFRFID_WORKER_READ_DEBUG_GPIO_LOAD &gpio_ext_pa6 +#endif + +#define LFRFID_WORKER_READ_AVERAGE_COUNT 64 +#define LFRFID_WORKER_READ_MIN_TIME_US 16 + +#define LFRFID_WORKER_READ_DROP_TIME_MS 50 +#define LFRFID_WORKER_READ_STABILIZE_TIME_MS 450 +#define LFRFID_WORKER_READ_SWITCH_TIME_MS 1500 + +#define LFRFID_WORKER_WRITE_VERIFY_TIME_MS 1500 +#define LFRFID_WORKER_WRITE_DROP_TIME_MS 50 +#define LFRFID_WORKER_WRITE_TOO_LONG_TIME_MS 10000 + +#define LFRFID_WORKER_WRITE_MAX_UNSUCCESSFUL_READS 5 + +#define LFRFID_WORKER_READ_BUFFER_SIZE 512 +#define LFRFID_WORKER_READ_BUFFER_COUNT 8 + +#define LFRFID_WORKER_EMULATE_BUFFER_SIZE 1024 + +#define LFRFID_WORKER_DELAY_QUANT 50 + +void lfrfid_worker_delay(LFRFIDWorker* worker, uint32_t milliseconds) { + for(uint32_t i = 0; i < (milliseconds / LFRFID_WORKER_DELAY_QUANT); i++) { + if(lfrfid_worker_check_for_stop(worker)) break; + furi_delay_ms(LFRFID_WORKER_DELAY_QUANT); + } +} + +/**************************************************************************************************/ +/********************************************** READ **********************************************/ +/**************************************************************************************************/ + +typedef struct { + BufferStream* stream; + VarintPair* pair; + bool ignore_next_pulse; +} LFRFIDWorkerReadContext; + +static void lfrfid_worker_read_capture(bool level, uint32_t duration, void* context) { + LFRFIDWorkerReadContext* ctx = context; + + // ignore pulse if last pulse was noise + if(ctx->ignore_next_pulse) { + ctx->ignore_next_pulse = false; + return; + } + + // ignore noise spikes + if(duration <= LFRFID_WORKER_READ_MIN_TIME_US) { + if(level) { + ctx->ignore_next_pulse = true; + } + varint_pair_reset(ctx->pair); + return; + } + +#ifdef LFRFID_WORKER_READ_DEBUG_GPIO + furi_hal_gpio_write(LFRFID_WORKER_READ_DEBUG_GPIO_VALUE, level); +#endif + + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + bool need_to_send = varint_pair_pack(ctx->pair, level, duration); + if(need_to_send) { + buffer_stream_send_from_isr( + ctx->stream, + varint_pair_get_data(ctx->pair), + varint_pair_get_size(ctx->pair), + &xHigherPriorityTaskWoken); + varint_pair_reset(ctx->pair); + } + portYIELD_FROM_ISR(xHigherPriorityTaskWoken); +} + +typedef enum { + LFRFIDWorkerReadOK, + LFRFIDWorkerReadExit, + LFRFIDWorkerReadTimeout, +} LFRFIDWorkerReadState; + +static LFRFIDWorkerReadState lfrfid_worker_read_internal( + LFRFIDWorker* worker, + LFRFIDFeature feature, + uint32_t timeout, + ProtocolId* result_protocol) { + LFRFIDWorkerReadState state = LFRFIDWorkerReadTimeout; + furi_hal_rfid_pins_read(); + + if(feature & LFRFIDFeatureASK) { + furi_hal_rfid_tim_read(125000, 0.5); + FURI_LOG_D(TAG, "Start ASK"); + if(worker->read_cb) { + worker->read_cb(LFRFIDWorkerReadStartASK, PROTOCOL_NO, worker->cb_ctx); + } + } else { + furi_hal_rfid_tim_read(62500, 0.25); + FURI_LOG_D(TAG, "Start PSK"); + if(worker->read_cb) { + worker->read_cb(LFRFIDWorkerReadStartPSK, PROTOCOL_NO, worker->cb_ctx); + } + } + + furi_hal_rfid_tim_read_start(); + + // stabilize detector + lfrfid_worker_delay(worker, LFRFID_WORKER_READ_STABILIZE_TIME_MS); + + protocol_dict_decoders_start(worker->protocols); + +#ifdef LFRFID_WORKER_READ_DEBUG_GPIO + furi_hal_gpio_init_simple(LFRFID_WORKER_READ_DEBUG_GPIO_VALUE, GpioModeOutputPushPull); + furi_hal_gpio_init_simple(LFRFID_WORKER_READ_DEBUG_GPIO_LOAD, GpioModeOutputPushPull); +#endif + + LFRFIDWorkerReadContext ctx; + ctx.pair = varint_pair_alloc(); + ctx.stream = + buffer_stream_alloc(LFRFID_WORKER_READ_BUFFER_SIZE, LFRFID_WORKER_READ_BUFFER_COUNT); + + furi_hal_rfid_tim_read_capture_start(lfrfid_worker_read_capture, &ctx); + + *result_protocol = PROTOCOL_NO; + ProtocolId last_protocol = PROTOCOL_NO; + size_t last_size = protocol_dict_get_max_data_size(worker->protocols); + uint8_t* last_data = malloc(last_size); + uint8_t* protocol_data = malloc(last_size); + size_t last_read_count = 0; + + uint32_t switch_os_tick_last = furi_get_tick(); + + uint32_t average_duration = 0; + uint32_t average_pulse = 0; + size_t average_index = 0; + bool card_detected = false; + + FURI_LOG_D(TAG, "Read started"); + while(true) { + if(lfrfid_worker_check_for_stop(worker)) { + state = LFRFIDWorkerReadExit; + break; + } + + Buffer* buffer = buffer_stream_receive(ctx.stream, 100); + +#ifdef LFRFID_WORKER_READ_DEBUG_GPIO + furi_hal_gpio_write(LFRFID_WORKER_READ_DEBUG_GPIO_LOAD, true); +#endif + + if(buffer_stream_get_overrun_count(ctx.stream) > 0) { + FURI_LOG_E(TAG, "Read overrun, recovering"); + buffer_stream_reset(ctx.stream); + continue; + } + + if(buffer == NULL) { + continue; + } + + size_t size = buffer_get_size(buffer); + uint8_t* data = buffer_get_data(buffer); + size_t index = 0; + + while(index < size) { + uint32_t duration; + uint32_t pulse; + size_t tmp_size; + + if(!varint_pair_unpack(&data[index], size - index, &pulse, &duration, &tmp_size)) { + FURI_LOG_E(TAG, "can't unpack varint pair"); + break; + } else { + index += tmp_size; + + average_duration += duration; + average_pulse += pulse; + average_index++; + if(average_index >= LFRFID_WORKER_READ_AVERAGE_COUNT) { + float average = (float)average_pulse / (float)average_duration; + average_pulse = 0; + average_duration = 0; + average_index = 0; + + if(worker->read_cb) { + if(average > 0.2 && average < 0.8) { + if(!card_detected) { + card_detected = true; + worker->read_cb( + LFRFIDWorkerReadSenseStart, PROTOCOL_NO, worker->cb_ctx); + } + } else { + if(card_detected) { + card_detected = false; + worker->read_cb( + LFRFIDWorkerReadSenseEnd, PROTOCOL_NO, worker->cb_ctx); + } + } + } + } + + ProtocolId protocol = PROTOCOL_NO; + + protocol = protocol_dict_decoders_feed_by_feature( + worker->protocols, feature, true, pulse); + if(protocol == PROTOCOL_NO) { + protocol = protocol_dict_decoders_feed_by_feature( + worker->protocols, feature, false, duration - pulse); + } + + if(protocol != PROTOCOL_NO) { + // reset switch timer + switch_os_tick_last = furi_get_tick(); + + size_t protocol_data_size = + protocol_dict_get_data_size(worker->protocols, protocol); + protocol_dict_get_data( + worker->protocols, protocol, protocol_data, protocol_data_size); + + // validate protocol + if(protocol == last_protocol && + memcmp(last_data, protocol_data, protocol_data_size) == 0) { + last_read_count = last_read_count + 1; + + size_t validation_count = + protocol_dict_get_validate_count(worker->protocols, protocol); + + if(last_read_count >= validation_count) { + state = LFRFIDWorkerReadOK; + *result_protocol = protocol; + break; + } + } else { + if(last_protocol == PROTOCOL_NO && worker->read_cb) { + worker->read_cb( + LFRFIDWorkerReadSenseCardStart, protocol, worker->cb_ctx); + } + + last_protocol = protocol; + memcpy(last_data, protocol_data, protocol_data_size); + last_read_count = 0; + } + + string_t string_info; + string_init(string_info); + for(uint8_t i = 0; i < protocol_data_size; i++) { + if(i != 0) { + string_cat_printf(string_info, " "); + } + + string_cat_printf(string_info, "%02X", protocol_data[i]); + } + + FURI_LOG_D( + TAG, + "%s, %d, [%s]", + protocol_dict_get_name(worker->protocols, protocol), + last_read_count, + string_get_cstr(string_info)); + string_clear(string_info); + + protocol_dict_decoders_start(worker->protocols); + } + } + } + + buffer_reset(buffer); + +#ifdef LFRFID_WORKER_READ_DEBUG_GPIO + furi_hal_gpio_write(LFRFID_WORKER_READ_DEBUG_GPIO_LOAD, false); +#endif + + if(*result_protocol != PROTOCOL_NO) { + break; + } + + if((furi_get_tick() - switch_os_tick_last) > timeout) { + state = LFRFIDWorkerReadTimeout; + break; + } + } + + FURI_LOG_D(TAG, "Read stopped"); + + if(last_protocol != PROTOCOL_NO && worker->read_cb) { + worker->read_cb(LFRFIDWorkerReadSenseCardEnd, last_protocol, worker->cb_ctx); + } + + if(card_detected && worker->read_cb) { + worker->read_cb(LFRFIDWorkerReadSenseEnd, last_protocol, worker->cb_ctx); + } + + furi_hal_rfid_tim_read_capture_stop(); + furi_hal_rfid_tim_read_stop(); + furi_hal_rfid_pins_reset(); + + varint_pair_free(ctx.pair); + buffer_stream_free(ctx.stream); + + free(protocol_data); + free(last_data); + +#ifdef LFRFID_WORKER_READ_DEBUG_GPIO + furi_hal_gpio_init_simple(LFRFID_WORKER_READ_DEBUG_GPIO_VALUE, GpioModeAnalog); + furi_hal_gpio_init_simple(LFRFID_WORKER_READ_DEBUG_GPIO_LOAD, GpioModeAnalog); +#endif + + return state; +} + +static void lfrfid_worker_mode_read_process(LFRFIDWorker* worker) { + LFRFIDFeature feature = LFRFIDFeatureASK; + ProtocolId read_result = PROTOCOL_NO; + LFRFIDWorkerReadState state; + + if(worker->read_type == LFRFIDWorkerReadTypePSKOnly) { + feature = LFRFIDFeaturePSK; + } else { + feature = LFRFIDFeatureASK; + } + + if(worker->read_type == LFRFIDWorkerReadTypeAuto) { + while(1) { + // read for a while + state = lfrfid_worker_read_internal( + worker, feature, LFRFID_WORKER_READ_SWITCH_TIME_MS, &read_result); + + if(state == LFRFIDWorkerReadOK || state == LFRFIDWorkerReadExit) { + break; + } + + // switch to next feature + if(feature == LFRFIDFeatureASK) { + feature = LFRFIDFeaturePSK; + } else { + feature = LFRFIDFeatureASK; + } + + lfrfid_worker_delay(worker, LFRFID_WORKER_READ_DROP_TIME_MS); + } + } else { + while(1) { + if(worker->read_type == LFRFIDWorkerReadTypeASKOnly) { + state = lfrfid_worker_read_internal(worker, feature, UINT32_MAX, &read_result); + } else { + state = lfrfid_worker_read_internal( + worker, feature, LFRFID_WORKER_READ_SWITCH_TIME_MS, &read_result); + } + + if(state == LFRFIDWorkerReadOK || state == LFRFIDWorkerReadExit) { + break; + } + + lfrfid_worker_delay(worker, LFRFID_WORKER_READ_DROP_TIME_MS); + } + } + + if(state == LFRFIDWorkerReadOK && worker->read_cb) { + worker->read_cb(LFRFIDWorkerReadDone, read_result, worker->cb_ctx); + } +} + +/**************************************************************************************************/ +/******************************************** EMULATE *********************************************/ +/**************************************************************************************************/ + +typedef struct { + uint32_t duration[LFRFID_WORKER_EMULATE_BUFFER_SIZE]; + uint32_t pulse[LFRFID_WORKER_EMULATE_BUFFER_SIZE]; +} LFRFIDWorkerEmulateBuffer; + +typedef enum { + HalfTransfer, + TransferComplete, +} LFRFIDWorkerEmulateDMAEvent; + +static void lfrfid_worker_emulate_dma_isr(bool half, void* context) { + StreamBufferHandle_t stream = context; + uint32_t flag = half ? HalfTransfer : TransferComplete; + xStreamBufferSendFromISR(stream, &flag, sizeof(uint32_t), pdFALSE); +} + +static void lfrfid_worker_mode_emulate_process(LFRFIDWorker* worker) { + LFRFIDWorkerEmulateBuffer* buffer = malloc(sizeof(LFRFIDWorkerEmulateBuffer)); + StreamBufferHandle_t stream = xStreamBufferCreate(sizeof(uint32_t), sizeof(uint32_t)); + LFRFIDProtocol protocol = worker->protocol; + PulseGlue* pulse_glue = pulse_glue_alloc(); + + protocol_dict_encoder_start(worker->protocols, protocol); + + for(size_t i = 0; i < LFRFID_WORKER_EMULATE_BUFFER_SIZE; i++) { + bool pulse_pop = false; + while(!pulse_pop) { + LevelDuration level_duration = + protocol_dict_encoder_yield(worker->protocols, protocol); + pulse_pop = pulse_glue_push( + pulse_glue, + level_duration_get_level(level_duration), + level_duration_get_duration(level_duration)); + } + uint32_t duration, pulse; + pulse_glue_pop(pulse_glue, &duration, &pulse); + buffer->duration[i] = duration - 1; + buffer->pulse[i] = pulse; + } + +#ifdef LFRFID_WORKER_READ_DEBUG_GPIO + furi_hal_gpio_init_simple(LFRFID_WORKER_READ_DEBUG_GPIO_LOAD, GpioModeOutputPushPull); +#endif + + furi_hal_rfid_tim_emulate_dma_start( + buffer->duration, + buffer->pulse, + LFRFID_WORKER_EMULATE_BUFFER_SIZE, + lfrfid_worker_emulate_dma_isr, + stream); + + while(true) { + uint32_t flag = 0; + size_t size = xStreamBufferReceive(stream, &flag, sizeof(uint32_t), 100); + +#ifdef LFRFID_WORKER_READ_DEBUG_GPIO + furi_hal_gpio_write(LFRFID_WORKER_READ_DEBUG_GPIO_LOAD, true); +#endif + + if(size == sizeof(uint32_t)) { + size_t start = 0; + + if(flag == HalfTransfer) { + start = 0; + } else if(flag == TransferComplete) { + start = (LFRFID_WORKER_EMULATE_BUFFER_SIZE / 2); + } + + for(size_t i = 0; i < (LFRFID_WORKER_EMULATE_BUFFER_SIZE / 2); i++) { + bool pulse_pop = false; + while(!pulse_pop) { + LevelDuration level_duration = + protocol_dict_encoder_yield(worker->protocols, protocol); + pulse_pop = pulse_glue_push( + pulse_glue, + level_duration_get_level(level_duration), + level_duration_get_duration(level_duration)); + } + uint32_t duration, pulse; + pulse_glue_pop(pulse_glue, &duration, &pulse); + buffer->duration[start + i] = duration - 1; + buffer->pulse[start + i] = pulse; + } + } + + if(lfrfid_worker_check_for_stop(worker)) { + break; + } + +#ifdef LFRFID_WORKER_READ_DEBUG_GPIO + furi_hal_gpio_write(LFRFID_WORKER_READ_DEBUG_GPIO_LOAD, false); +#endif + } + + furi_hal_rfid_tim_emulate_dma_stop(); + +#ifdef LFRFID_WORKER_READ_DEBUG_GPIO + furi_hal_gpio_init_simple(LFRFID_WORKER_READ_DEBUG_GPIO_LOAD, GpioModeAnalog); +#endif + + free(buffer); + vStreamBufferDelete(stream); + pulse_glue_free(pulse_glue); +} + +/**************************************************************************************************/ +/********************************************* WRITE **********************************************/ +/**************************************************************************************************/ + +static void lfrfid_worker_mode_write_process(LFRFIDWorker* worker) { + LFRFIDProtocol protocol = worker->protocol; + LFRFIDWriteRequest* request = malloc(sizeof(LFRFIDWriteRequest)); + request->write_type = LFRFIDWriteTypeT5577; + + bool can_be_written = protocol_dict_get_write_data(worker->protocols, protocol, request); + + uint32_t write_start_time = furi_get_tick(); + bool too_long = false; + size_t unsuccessful_reads = 0; + + size_t data_size = protocol_dict_get_data_size(worker->protocols, protocol); + uint8_t* verify_data = malloc(data_size); + uint8_t* read_data = malloc(data_size); + protocol_dict_get_data(worker->protocols, protocol, verify_data, data_size); + + if(can_be_written) { + while(!lfrfid_worker_check_for_stop(worker)) { + FURI_LOG_D(TAG, "Data write"); + t5577_write(&request->t5577); + + ProtocolId read_result = PROTOCOL_NO; + LFRFIDWorkerReadState state = lfrfid_worker_read_internal( + worker, + protocol_dict_get_features(worker->protocols, protocol), + LFRFID_WORKER_WRITE_VERIFY_TIME_MS, + &read_result); + + if(state == LFRFIDWorkerReadOK) { + protocol_dict_get_data(worker->protocols, protocol, read_data, data_size); + + if(memcmp(read_data, verify_data, data_size) == 0) { + if(worker->write_cb) { + worker->write_cb(LFRFIDWorkerWriteOK, worker->cb_ctx); + } + break; + } else { + unsuccessful_reads++; + + if(unsuccessful_reads == LFRFID_WORKER_WRITE_MAX_UNSUCCESSFUL_READS) { + if(worker->write_cb) { + worker->write_cb(LFRFIDWorkerWriteFobCannotBeWritten, worker->cb_ctx); + } + } + } + } else if(state == LFRFIDWorkerReadExit) { + break; + } + + if(!too_long && + (furi_get_tick() - write_start_time) > LFRFID_WORKER_WRITE_TOO_LONG_TIME_MS) { + too_long = true; + if(worker->write_cb) { + worker->write_cb(LFRFIDWorkerWriteTooLongToWrite, worker->cb_ctx); + } + } + + lfrfid_worker_delay(worker, LFRFID_WORKER_WRITE_DROP_TIME_MS); + } + } else { + if(worker->write_cb) { + worker->write_cb(LFRFIDWorkerWriteProtocolCannotBeWritten, worker->cb_ctx); + } + } + + free(request); + free(verify_data); + free(read_data); +} + +/**************************************************************************************************/ +/******************************************* READ RAW *********************************************/ +/**************************************************************************************************/ + +static void lfrfid_worker_mode_read_raw_process(LFRFIDWorker* worker) { + LFRFIDRawWorker* raw_worker = lfrfid_raw_worker_alloc(); + + switch(worker->read_type) { + case LFRFIDWorkerReadTypePSKOnly: + lfrfid_raw_worker_start_read( + raw_worker, worker->raw_filename, 62500, 0.25, worker->read_raw_cb, worker->cb_ctx); + break; + case LFRFIDWorkerReadTypeASKOnly: + lfrfid_raw_worker_start_read( + raw_worker, worker->raw_filename, 125000, 0.5, worker->read_raw_cb, worker->cb_ctx); + break; + default: + furi_crash("RAW can be only PSK or ASK"); + break; + } + + while(!lfrfid_worker_check_for_stop(worker)) { + furi_delay_ms(100); + } + + lfrfid_raw_worker_stop(raw_worker); + lfrfid_raw_worker_free(raw_worker); +} + +/**************************************************************************************************/ +/***************************************** EMULATE RAW ********************************************/ +/**************************************************************************************************/ + +static void lfrfid_worker_mode_emulate_raw_process(LFRFIDWorker* worker) { + LFRFIDRawWorker* raw_worker = lfrfid_raw_worker_alloc(); + + lfrfid_raw_worker_start_emulate( + raw_worker, worker->raw_filename, worker->emulate_raw_cb, worker->cb_ctx); + + while(!lfrfid_worker_check_for_stop(worker)) { + furi_delay_ms(100); + } + + lfrfid_raw_worker_stop(raw_worker); + lfrfid_raw_worker_free(raw_worker); +} + +/**************************************************************************************************/ +/******************************************** MODES ***********************************************/ +/**************************************************************************************************/ + +const LFRFIDWorkerModeType lfrfid_worker_modes[] = { + [LFRFIDWorkerIdle] = {.process = NULL}, + [LFRFIDWorkerRead] = {.process = lfrfid_worker_mode_read_process}, + [LFRFIDWorkerWrite] = {.process = lfrfid_worker_mode_write_process}, + [LFRFIDWorkerEmulate] = {.process = lfrfid_worker_mode_emulate_process}, + [LFRFIDWorkerReadRaw] = {.process = lfrfid_worker_mode_read_raw_process}, + [LFRFIDWorkerEmulateRaw] = {.process = lfrfid_worker_mode_emulate_raw_process}, +}; \ No newline at end of file diff --git a/lib/lfrfid/protocols/lfrfid_protocols.c b/lib/lfrfid/protocols/lfrfid_protocols.c new file mode 100644 index 00000000000..5df01b19e54 --- /dev/null +++ b/lib/lfrfid/protocols/lfrfid_protocols.c @@ -0,0 +1,22 @@ +#include "lfrfid_protocols.h" +#include "protocol_em4100.h" +#include "protocol_h10301.h" +#include "protocol_indala26.h" +#include "protocol_io_prox_xsf.h" +#include "protocol_awid.h" +#include "protocol_fdx_a.h" +#include "protocol_fdx_b.h" +#include "protocol_hid_generic.h" +#include "protocol_hid_ex_generic.h" + +const ProtocolBase* lfrfid_protocols[] = { + [LFRFIDProtocolEM4100] = &protocol_em4100, + [LFRFIDProtocolH10301] = &protocol_h10301, + [LFRFIDProtocolIndala26] = &protocol_indala26, + [LFRFIDProtocolIOProxXSF] = &protocol_io_prox_xsf, + [LFRFIDProtocolAwid] = &protocol_awid, + [LFRFIDProtocolFDXA] = &protocol_fdx_a, + [LFRFIDProtocolFDXB] = &protocol_fdx_b, + [LFRFIDProtocolHidGeneric] = &protocol_hid_generic, + [LFRFIDProtocolHidExGeneric] = &protocol_hid_ex_generic, +}; \ No newline at end of file diff --git a/lib/lfrfid/protocols/lfrfid_protocols.h b/lib/lfrfid/protocols/lfrfid_protocols.h new file mode 100644 index 00000000000..4b8f6573d1b --- /dev/null +++ b/lib/lfrfid/protocols/lfrfid_protocols.h @@ -0,0 +1,35 @@ +#pragma once +#include +#include "../tools/t5577.h" + +typedef enum { + LFRFIDFeatureASK = 1 << 0, /** ASK Demodulation */ + LFRFIDFeaturePSK = 1 << 1, /** PSK Demodulation */ +} LFRFIDFeature; + +typedef enum { + LFRFIDProtocolEM4100, + LFRFIDProtocolH10301, + LFRFIDProtocolIndala26, + LFRFIDProtocolIOProxXSF, + LFRFIDProtocolAwid, + LFRFIDProtocolFDXA, + LFRFIDProtocolFDXB, + LFRFIDProtocolHidGeneric, + LFRFIDProtocolHidExGeneric, + + LFRFIDProtocolMax, +} LFRFIDProtocol; + +extern const ProtocolBase* lfrfid_protocols[]; + +typedef enum { + LFRFIDWriteTypeT5577, +} LFRFIDWriteType; + +typedef struct { + LFRFIDWriteType write_type; + union { + LFRFIDT5577 t5577; + }; +} LFRFIDWriteRequest; \ No newline at end of file diff --git a/lib/lfrfid/protocols/protocol_awid.c b/lib/lfrfid/protocols/protocol_awid.c new file mode 100644 index 00000000000..243b5edeb4d --- /dev/null +++ b/lib/lfrfid/protocols/protocol_awid.c @@ -0,0 +1,239 @@ +#include +#include +#include +#include +#include +#include "lfrfid_protocols.h" + +#define JITTER_TIME (20) +#define MIN_TIME (64 - JITTER_TIME) +#define MAX_TIME (80 + JITTER_TIME) + +#define AWID_DECODED_DATA_SIZE (9) + +#define AWID_ENCODED_BIT_SIZE (96) +#define AWID_ENCODED_DATA_SIZE (((AWID_ENCODED_BIT_SIZE) / 8) + 1) +#define AWID_ENCODED_DATA_LAST (AWID_ENCODED_DATA_SIZE - 1) + +typedef struct { + FSKDemod* fsk_demod; +} ProtocolAwidDecoder; + +typedef struct { + FSKOsc* fsk_osc; + uint8_t encoded_index; +} ProtocolAwidEncoder; + +typedef struct { + ProtocolAwidDecoder decoder; + ProtocolAwidEncoder encoder; + uint8_t encoded_data[AWID_ENCODED_DATA_SIZE]; + uint8_t data[AWID_DECODED_DATA_SIZE]; +} ProtocolAwid; + +ProtocolAwid* protocol_awid_alloc(void) { + ProtocolAwid* protocol = malloc(sizeof(ProtocolAwid)); + protocol->decoder.fsk_demod = fsk_demod_alloc(MIN_TIME, 6, MAX_TIME, 5); + protocol->encoder.fsk_osc = fsk_osc_alloc(8, 10, 50); + + return protocol; +}; + +void protocol_awid_free(ProtocolAwid* protocol) { + fsk_demod_free(protocol->decoder.fsk_demod); + fsk_osc_free(protocol->encoder.fsk_osc); + free(protocol); +}; + +uint8_t* protocol_awid_get_data(ProtocolAwid* protocol) { + return protocol->data; +}; + +void protocol_awid_decoder_start(ProtocolAwid* protocol) { + memset(protocol->encoded_data, 0, AWID_ENCODED_DATA_SIZE); +}; + +static bool protocol_awid_can_be_decoded(const uint8_t* data) { + bool result = false; + + // Index map + // 0 10 20 30 40 50 60 + // | | | | | | | + // 01234567 890 1 234 5 678 9 012 3 456 7 890 1 234 5 678 9 012 3 456 7 890 1 234 5 678 9 012 3 - to 96 + // ----------------------------------------------------------------------------- + // 00000001 000 1 110 1 101 1 011 1 101 1 010 0 000 1 000 1 010 0 001 0 110 1 100 0 000 1 000 1 + // preamble bbb o bbb o bbw o fff o fff o ffc o ccc o ccc o ccc o ccc o ccc o wxx o xxx o xxx o - to 96 + // |---26 bit---| |-----117----||-------------142-------------| + // b = format bit len, o = odd parity of last 3 bits + // f = facility code, c = card number + // w = wiegand parity + // (26 bit format shown) + + do { + // check preamble and spacing + if(data[0] != 0b00000001 || data[AWID_ENCODED_DATA_LAST] != 0b00000001) break; + + // check odd parity for every 4 bits starting from the second byte + bool parity_error = bit_lib_test_parity(data, 8, 88, BitLibParityOdd, 4); + if(parity_error) break; + + result = true; + } while(false); + + return result; +} + +static void protocol_awid_decode(uint8_t* encoded_data, uint8_t* decoded_data) { + bit_lib_remove_bit_every_nth(encoded_data, 8, 88, 4); + bit_lib_copy_bits(decoded_data, 0, 66, encoded_data, 8); +} + +bool protocol_awid_decoder_feed(ProtocolAwid* protocol, bool level, uint32_t duration) { + bool value; + uint32_t count; + bool result = false; + + fsk_demod_feed(protocol->decoder.fsk_demod, level, duration, &value, &count); + if(count > 0) { + for(size_t i = 0; i < count; i++) { + bit_lib_push_bit(protocol->encoded_data, AWID_ENCODED_DATA_SIZE, value); + if(protocol_awid_can_be_decoded(protocol->encoded_data)) { + protocol_awid_decode(protocol->encoded_data, protocol->data); + + result = true; + break; + } + } + } + + return result; +}; + +static void protocol_awid_encode(const uint8_t* decoded_data, uint8_t* encoded_data) { + memset(encoded_data, 0, AWID_ENCODED_DATA_SIZE); + + // preamble + bit_lib_set_bits(encoded_data, 0, 0b00000001, 8); + + for(size_t i = 0; i < 88 / 4; i++) { + uint8_t value = bit_lib_get_bits(decoded_data, i * 3, 3) << 1; + value |= bit_lib_test_parity_32(value, BitLibParityOdd); + bit_lib_set_bits(encoded_data, 8 + i * 4, value, 4); + } +}; + +bool protocol_awid_encoder_start(ProtocolAwid* protocol) { + protocol_awid_encode(protocol->data, (uint8_t*)protocol->encoded_data); + protocol->encoder.encoded_index = 0; + fsk_osc_reset(protocol->encoder.fsk_osc); + return true; +}; + +LevelDuration protocol_awid_encoder_yield(ProtocolAwid* protocol) { + bool level; + uint32_t duration; + + bool bit = bit_lib_get_bit(protocol->encoded_data, protocol->encoder.encoded_index); + bool advance = fsk_osc_next_half(protocol->encoder.fsk_osc, bit, &level, &duration); + + if(advance) { + bit_lib_increment_index(protocol->encoder.encoded_index, AWID_ENCODED_BIT_SIZE); + } + return level_duration_make(level, duration); +}; + +void protocol_awid_render_data(ProtocolAwid* protocol, string_t result) { + // Index map + // 0 10 20 30 40 50 60 + // | | | | | | | + // 01234567 8 90123456 7890123456789012 3 456789012345678901234567890123456 + // ------------------------------------------------------------------------ + // 00011010 1 01110101 0000000010001110 1 000000000000000000000000000000000 + // bbbbbbbb w ffffffff cccccccccccccccc w xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + // |26 bit| |-117--| |-----142------| + // b = format bit len, o = odd parity of last 3 bits + // f = facility code, c = card number + // w = wiegand parity + // (26 bit format shown) + + uint8_t* decoded_data = protocol->data; + uint8_t format_length = decoded_data[0]; + + string_cat_printf(result, "Format: %d\r\n", format_length); + if(format_length == 26) { + uint8_t facility; + bit_lib_copy_bits(&facility, 0, 8, decoded_data, 9); + + uint16_t card_id; + bit_lib_copy_bits((uint8_t*)&card_id, 8, 8, decoded_data, 17); + bit_lib_copy_bits((uint8_t*)&card_id, 0, 8, decoded_data, 25); + string_cat_printf(result, "Facility: %d\r\n", facility); + string_cat_printf(result, "Card: %d", card_id); + } else { + // print 66 bits as hex + string_cat_printf(result, "Data: "); + for(size_t i = 0; i < AWID_DECODED_DATA_SIZE; i++) { + string_cat_printf(result, "%02X", decoded_data[i]); + } + } +}; + +void protocol_awid_render_brief_data(ProtocolAwid* protocol, string_t result) { + uint8_t* decoded_data = protocol->data; + uint8_t format_length = decoded_data[0]; + + string_cat_printf(result, "Format: %d\r\n", format_length); + if(format_length == 26) { + uint8_t facility; + bit_lib_copy_bits(&facility, 0, 8, decoded_data, 9); + + uint16_t card_id; + bit_lib_copy_bits((uint8_t*)&card_id, 8, 8, decoded_data, 17); + bit_lib_copy_bits((uint8_t*)&card_id, 0, 8, decoded_data, 25); + string_cat_printf(result, "ID: %03u,%05u", facility, card_id); + } else { + string_cat_printf(result, "Data: unknown"); + } +}; + +bool protocol_awid_write_data(ProtocolAwid* protocol, void* data) { + LFRFIDWriteRequest* request = (LFRFIDWriteRequest*)data; + bool result = false; + + protocol_awid_encode(protocol->data, (uint8_t*)protocol->encoded_data); + + if(request->write_type == LFRFIDWriteTypeT5577) { + request->t5577.block[0] = LFRFID_T5577_MODULATION_FSK2a | LFRFID_T5577_BITRATE_RF_50 | + (3 << LFRFID_T5577_MAXBLOCK_SHIFT); + request->t5577.block[1] = bit_lib_get_bits_32(protocol->encoded_data, 0, 32); + request->t5577.block[2] = bit_lib_get_bits_32(protocol->encoded_data, 32, 32); + request->t5577.block[3] = bit_lib_get_bits_32(protocol->encoded_data, 64, 32); + request->t5577.blocks_to_write = 4; + result = true; + } + return result; +}; + +const ProtocolBase protocol_awid = { + .name = "AWID", + .manufacturer = "AWIG", + .data_size = AWID_DECODED_DATA_SIZE, + .features = LFRFIDFeatureASK, + .validate_count = 3, + .alloc = (ProtocolAlloc)protocol_awid_alloc, + .free = (ProtocolFree)protocol_awid_free, + .get_data = (ProtocolGetData)protocol_awid_get_data, + .decoder = + { + .start = (ProtocolDecoderStart)protocol_awid_decoder_start, + .feed = (ProtocolDecoderFeed)protocol_awid_decoder_feed, + }, + .encoder = + { + .start = (ProtocolEncoderStart)protocol_awid_encoder_start, + .yield = (ProtocolEncoderYield)protocol_awid_encoder_yield, + }, + .render_data = (ProtocolRenderData)protocol_awid_render_data, + .render_brief_data = (ProtocolRenderData)protocol_awid_render_brief_data, + .write_data = (ProtocolWriteData)protocol_awid_write_data, +}; \ No newline at end of file diff --git a/lib/lfrfid/protocols/protocol_awid.h b/lib/lfrfid/protocols/protocol_awid.h new file mode 100644 index 00000000000..51a4ea52f59 --- /dev/null +++ b/lib/lfrfid/protocols/protocol_awid.h @@ -0,0 +1,4 @@ +#pragma once +#include + +extern const ProtocolBase protocol_awid; \ No newline at end of file diff --git a/lib/lfrfid/protocols/protocol_em4100.c b/lib/lfrfid/protocols/protocol_em4100.c new file mode 100644 index 00000000000..92721fcdc1e --- /dev/null +++ b/lib/lfrfid/protocols/protocol_em4100.c @@ -0,0 +1,292 @@ +#include +#include +#include +#include "lfrfid_protocols.h" + +typedef uint64_t EM4100DecodedData; + +#define EM_HEADER_POS (55) +#define EM_HEADER_MASK (0x1FFLLU << EM_HEADER_POS) + +#define EM_FIRST_ROW_POS (50) + +#define EM_ROW_COUNT (10) +#define EM_COLUMN_COUNT (4) +#define EM_BITS_PER_ROW_COUNT (EM_COLUMN_COUNT + 1) + +#define EM_COLUMN_POS (4) +#define EM_STOP_POS (0) +#define EM_STOP_MASK (0x1LLU << EM_STOP_POS) + +#define EM_HEADER_AND_STOP_MASK (EM_HEADER_MASK | EM_STOP_MASK) +#define EM_HEADER_AND_STOP_DATA (EM_HEADER_MASK) + +#define EM4100_DECODED_DATA_SIZE (5) +#define EM4100_ENCODED_DATA_SIZE (sizeof(EM4100DecodedData)) + +#define EM4100_CLOCK_PER_BIT (64) + +#define EM_READ_SHORT_TIME (256) +#define EM_READ_LONG_TIME (512) +#define EM_READ_JITTER_TIME (100) + +#define EM_READ_SHORT_TIME_LOW (EM_READ_SHORT_TIME - EM_READ_JITTER_TIME) +#define EM_READ_SHORT_TIME_HIGH (EM_READ_SHORT_TIME + EM_READ_JITTER_TIME) +#define EM_READ_LONG_TIME_LOW (EM_READ_LONG_TIME - EM_READ_JITTER_TIME) +#define EM_READ_LONG_TIME_HIGH (EM_READ_LONG_TIME + EM_READ_JITTER_TIME) + +typedef struct { + uint8_t data[EM4100_DECODED_DATA_SIZE]; + + EM4100DecodedData encoded_data; + uint8_t encoded_data_index; + bool encoded_polarity; + + ManchesterState decoder_manchester_state; +} ProtocolEM4100; + +ProtocolEM4100* protocol_em4100_alloc(void) { + ProtocolEM4100* proto = malloc(sizeof(ProtocolEM4100)); + return (void*)proto; +}; + +void protocol_em4100_free(ProtocolEM4100* proto) { + free(proto); +}; + +uint8_t* protocol_em4100_get_data(ProtocolEM4100* proto) { + return proto->data; +}; + +static void em4100_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 >= EM4100_DECODED_DATA_SIZE); + furi_check(encoded_data_size >= EM4100_ENCODED_DATA_SIZE); + + uint8_t decoded_data_index = 0; + EM4100DecodedData card_data = *((EM4100DecodedData*)(encoded_data)); + + // clean result + memset(decoded_data, 0, decoded_data_size); + + // header + for(uint8_t i = 0; i < 9; i++) { + card_data = card_data << 1; + } + + // nibbles + uint8_t value = 0; + for(uint8_t r = 0; r < EM_ROW_COUNT; r++) { + uint8_t nibble = 0; + for(uint8_t i = 0; i < 5; i++) { + if(i < 4) nibble = (nibble << 1) | (card_data & (1LLU << 63) ? 1 : 0); + card_data = card_data << 1; + } + value = (value << 4) | nibble; + if(r % 2) { + decoded_data[decoded_data_index] |= value; + decoded_data_index++; + value = 0; + } + } +} + +static bool em4100_can_be_decoded(const uint8_t* encoded_data, const uint8_t encoded_data_size) { + furi_check(encoded_data_size >= EM4100_ENCODED_DATA_SIZE); + const EM4100DecodedData* card_data = (EM4100DecodedData*)encoded_data; + + // check header and stop bit + if((*card_data & EM_HEADER_AND_STOP_MASK) != EM_HEADER_AND_STOP_DATA) return false; + + // check row parity + for(uint8_t i = 0; i < EM_ROW_COUNT; i++) { + uint8_t parity_sum = 0; + + for(uint8_t j = 0; j < EM_BITS_PER_ROW_COUNT; j++) { + parity_sum += (*card_data >> (EM_FIRST_ROW_POS - i * EM_BITS_PER_ROW_COUNT + j)) & 1; + } + + if((parity_sum % 2)) { + return false; + } + } + + // check columns parity + for(uint8_t i = 0; i < EM_COLUMN_COUNT; i++) { + uint8_t parity_sum = 0; + + for(uint8_t j = 0; j < EM_ROW_COUNT + 1; j++) { + parity_sum += (*card_data >> (EM_COLUMN_POS - i + j * EM_BITS_PER_ROW_COUNT)) & 1; + } + + if((parity_sum % 2)) { + return false; + } + } + + return true; +} + +void protocol_em4100_decoder_start(ProtocolEM4100* proto) { + memset(proto->data, 0, EM4100_DECODED_DATA_SIZE); + proto->encoded_data = 0; + manchester_advance( + proto->decoder_manchester_state, + ManchesterEventReset, + &proto->decoder_manchester_state, + NULL); +}; + +bool protocol_em4100_decoder_feed(ProtocolEM4100* proto, bool level, uint32_t duration) { + bool result = false; + + ManchesterEvent event = ManchesterEventReset; + + if(duration > EM_READ_SHORT_TIME_LOW && duration < EM_READ_SHORT_TIME_HIGH) { + if(!level) { + event = ManchesterEventShortHigh; + } else { + event = ManchesterEventShortLow; + } + } else if(duration > EM_READ_LONG_TIME_LOW && duration < EM_READ_LONG_TIME_HIGH) { + if(!level) { + event = ManchesterEventLongHigh; + } else { + event = ManchesterEventLongLow; + } + } + + if(event != ManchesterEventReset) { + bool data; + bool data_ok = manchester_advance( + proto->decoder_manchester_state, event, &proto->decoder_manchester_state, &data); + + if(data_ok) { + proto->encoded_data = (proto->encoded_data << 1) | data; + + if(em4100_can_be_decoded((uint8_t*)&proto->encoded_data, sizeof(EM4100DecodedData))) { + em4100_decode( + (uint8_t*)&proto->encoded_data, + sizeof(EM4100DecodedData), + proto->data, + EM4100_DECODED_DATA_SIZE); + result = true; + } + } + } + + return result; +}; + +static void em4100_write_nibble(bool low_nibble, uint8_t data, EM4100DecodedData* encoded_data) { + uint8_t parity_sum = 0; + uint8_t start = 0; + if(!low_nibble) start = 4; + + for(int8_t i = (start + 3); i >= start; i--) { + parity_sum += (data >> i) & 1; + *encoded_data = (*encoded_data << 1) | ((data >> i) & 1); + } + + *encoded_data = (*encoded_data << 1) | ((parity_sum % 2) & 1); +} + +bool protocol_em4100_encoder_start(ProtocolEM4100* proto) { + // header + proto->encoded_data = 0b111111111; + + // data + for(uint8_t i = 0; i < EM4100_DECODED_DATA_SIZE; i++) { + em4100_write_nibble(false, proto->data[i], &proto->encoded_data); + em4100_write_nibble(true, proto->data[i], &proto->encoded_data); + } + + // column parity and stop bit + uint8_t parity_sum; + + for(uint8_t c = 0; c < EM_COLUMN_COUNT; c++) { + parity_sum = 0; + for(uint8_t i = 1; i <= EM_ROW_COUNT; i++) { + uint8_t parity_bit = (proto->encoded_data >> (i * EM_BITS_PER_ROW_COUNT - 1)) & 1; + parity_sum += parity_bit; + } + proto->encoded_data = (proto->encoded_data << 1) | ((parity_sum % 2) & 1); + } + + // stop bit + proto->encoded_data = (proto->encoded_data << 1) | 0; + + proto->encoded_data_index = 0; + proto->encoded_polarity = true; + + return true; +}; + +LevelDuration protocol_em4100_encoder_yield(ProtocolEM4100* proto) { + bool level = (proto->encoded_data >> (63 - proto->encoded_data_index)) & 1; + uint32_t duration = EM4100_CLOCK_PER_BIT / 2; + + if(proto->encoded_polarity) { + proto->encoded_polarity = false; + } else { + level = !level; + + proto->encoded_polarity = true; + proto->encoded_data_index++; + if(proto->encoded_data_index >= 64) { + proto->encoded_data_index = 0; + } + } + + return level_duration_make(level, duration); +}; + +bool protocol_em4100_write_data(ProtocolEM4100* protocol, void* data) { + LFRFIDWriteRequest* request = (LFRFIDWriteRequest*)data; + bool result = false; + + protocol_em4100_encoder_start(protocol); + + if(request->write_type == LFRFIDWriteTypeT5577) { + request->t5577.block[0] = + (LFRFID_T5577_MODULATION_MANCHESTER | LFRFID_T5577_BITRATE_RF_64 | + (2 << LFRFID_T5577_MAXBLOCK_SHIFT)); + request->t5577.block[1] = protocol->encoded_data; + request->t5577.block[2] = protocol->encoded_data >> 32; + request->t5577.blocks_to_write = 3; + result = true; + } + return result; +}; + +void protocol_em4100_render_data(ProtocolEM4100* protocol, string_t result) { + uint8_t* data = protocol->data; + string_printf(result, "ID: %03u,%05u", data[2], (uint16_t)((data[3] << 8) | (data[4]))); +}; + +const ProtocolBase protocol_em4100 = { + .name = "EM4100", + .manufacturer = "EM-Micro", + .data_size = EM4100_DECODED_DATA_SIZE, + .features = LFRFIDFeatureASK | LFRFIDFeaturePSK, + .validate_count = 3, + .alloc = (ProtocolAlloc)protocol_em4100_alloc, + .free = (ProtocolFree)protocol_em4100_free, + .get_data = (ProtocolGetData)protocol_em4100_get_data, + .decoder = + { + .start = (ProtocolDecoderStart)protocol_em4100_decoder_start, + .feed = (ProtocolDecoderFeed)protocol_em4100_decoder_feed, + }, + .encoder = + { + .start = (ProtocolEncoderStart)protocol_em4100_encoder_start, + .yield = (ProtocolEncoderYield)protocol_em4100_encoder_yield, + }, + .render_data = (ProtocolRenderData)protocol_em4100_render_data, + .render_brief_data = (ProtocolRenderData)protocol_em4100_render_data, + .write_data = (ProtocolWriteData)protocol_em4100_write_data, +}; \ No newline at end of file diff --git a/lib/lfrfid/protocols/protocol_em4100.h b/lib/lfrfid/protocols/protocol_em4100.h new file mode 100644 index 00000000000..6e1e25b9371 --- /dev/null +++ b/lib/lfrfid/protocols/protocol_em4100.h @@ -0,0 +1,4 @@ +#pragma once +#include + +extern const ProtocolBase protocol_em4100; \ No newline at end of file diff --git a/lib/lfrfid/protocols/protocol_fdx_a.c b/lib/lfrfid/protocols/protocol_fdx_a.c new file mode 100644 index 00000000000..23f9e2857f8 --- /dev/null +++ b/lib/lfrfid/protocols/protocol_fdx_a.c @@ -0,0 +1,239 @@ +#include +#include +#include +#include +#include "lfrfid_protocols.h" +#include + +#define JITTER_TIME (20) +#define MIN_TIME (64 - JITTER_TIME) +#define MAX_TIME (80 + JITTER_TIME) + +#define FDXA_DATA_SIZE 10 +#define FDXA_PREAMBLE_SIZE 2 + +#define FDXA_ENCODED_DATA_SIZE (FDXA_PREAMBLE_SIZE + FDXA_DATA_SIZE + FDXA_PREAMBLE_SIZE) +#define FDXA_ENCODED_BIT_SIZE ((FDXA_PREAMBLE_SIZE + FDXA_DATA_SIZE) * 8) +#define FDXA_DECODED_DATA_SIZE (5) +#define FDXA_DECODED_BIT_SIZE ((FDXA_ENCODED_BIT_SIZE - FDXA_PREAMBLE_SIZE * 8) / 2) + +#define FDXA_PREAMBLE_0 0x55 +#define FDXA_PREAMBLE_1 0x1D + +typedef struct { + FSKDemod* fsk_demod; +} ProtocolFDXADecoder; + +typedef struct { + FSKOsc* fsk_osc; + uint8_t encoded_index; + uint32_t pulse; +} ProtocolFDXAEncoder; + +typedef struct { + ProtocolFDXADecoder decoder; + ProtocolFDXAEncoder encoder; + uint8_t encoded_data[FDXA_ENCODED_DATA_SIZE]; + uint8_t data[FDXA_DECODED_DATA_SIZE]; + size_t protocol_size; +} ProtocolFDXA; + +ProtocolFDXA* protocol_fdx_a_alloc(void) { + ProtocolFDXA* protocol = malloc(sizeof(ProtocolFDXA)); + protocol->decoder.fsk_demod = fsk_demod_alloc(MIN_TIME, 6, MAX_TIME, 5); + protocol->encoder.fsk_osc = fsk_osc_alloc(8, 10, 50); + + return protocol; +}; + +void protocol_fdx_a_free(ProtocolFDXA* protocol) { + fsk_demod_free(protocol->decoder.fsk_demod); + fsk_osc_free(protocol->encoder.fsk_osc); + free(protocol); +}; + +uint8_t* protocol_fdx_a_get_data(ProtocolFDXA* protocol) { + return protocol->data; +}; + +void protocol_fdx_a_decoder_start(ProtocolFDXA* protocol) { + memset(protocol->encoded_data, 0, FDXA_ENCODED_DATA_SIZE); +}; + +static bool protocol_fdx_a_decode(const uint8_t* from, uint8_t* to) { + size_t bit_index = 0; + for(size_t i = FDXA_PREAMBLE_SIZE; i < (FDXA_PREAMBLE_SIZE + FDXA_DATA_SIZE); i++) { + for(size_t n = 0; n < 4; n++) { + uint8_t bit_pair = (from[i] >> (6 - (n * 2))) & 0b11; + if(bit_pair == 0b01) { + bit_lib_set_bit(to, bit_index, 0); + } else if(bit_pair == 0b10) { + bit_lib_set_bit(to, bit_index, 1); + } else { + return false; + } + bit_index++; + } + } + + return true; +} + +static bool protocol_fdx_a_can_be_decoded(const uint8_t* data) { + // check preamble + if(data[0] != FDXA_PREAMBLE_0 || data[1] != FDXA_PREAMBLE_1 || data[12] != FDXA_PREAMBLE_0 || + data[13] != FDXA_PREAMBLE_1) { + return false; + } + + // check for manchester encoding + uint8_t decoded_data[FDXA_DECODED_DATA_SIZE]; + if(!protocol_fdx_a_decode(data, decoded_data)) return false; + + uint8_t parity_sum = 0; + for(size_t i = 0; i < FDXA_DECODED_DATA_SIZE; i++) { + parity_sum += bit_lib_test_parity_32(decoded_data[i], BitLibParityOdd); + decoded_data[i] &= 0x7F; + } + + return (parity_sum == 0); +} + +bool protocol_fdx_a_decoder_feed(ProtocolFDXA* protocol, bool level, uint32_t duration) { + bool value; + uint32_t count; + bool result = false; + + fsk_demod_feed(protocol->decoder.fsk_demod, level, duration, &value, &count); + if(count > 0) { + for(size_t i = 0; i < count; i++) { + bit_lib_push_bit(protocol->encoded_data, FDXA_ENCODED_DATA_SIZE, value); + if(protocol_fdx_a_can_be_decoded(protocol->encoded_data)) { + protocol_fdx_a_decode(protocol->encoded_data, protocol->data); + result = true; + } + } + } + + return result; +}; + +static void protocol_fdx_a_encode(ProtocolFDXA* protocol) { + protocol->encoded_data[0] = FDXA_PREAMBLE_0; + protocol->encoded_data[1] = FDXA_PREAMBLE_1; + + size_t bit_index = 0; + for(size_t i = 0; i < FDXA_DECODED_BIT_SIZE; i++) { + bool bit = bit_lib_get_bit(protocol->data, i); + if(bit) { + bit_lib_set_bit(protocol->encoded_data, 16 + bit_index, 1); + bit_lib_set_bit(protocol->encoded_data, 16 + bit_index + 1, 0); + } else { + bit_lib_set_bit(protocol->encoded_data, 16 + bit_index, 0); + bit_lib_set_bit(protocol->encoded_data, 16 + bit_index + 1, 1); + } + bit_index += 2; + } +} + +bool protocol_fdx_a_encoder_start(ProtocolFDXA* protocol) { + protocol->encoder.encoded_index = 0; + protocol->encoder.pulse = 0; + protocol_fdx_a_encode(protocol); + + return true; +}; + +LevelDuration protocol_fdx_a_encoder_yield(ProtocolFDXA* protocol) { + bool level = 0; + uint32_t duration = 0; + + // if pulse is zero, we need to output high, otherwise we need to output low + if(protocol->encoder.pulse == 0) { + // get bit + uint8_t bit = bit_lib_get_bit(protocol->encoded_data, protocol->encoder.encoded_index); + + // get pulse from oscillator + bool advance = fsk_osc_next(protocol->encoder.fsk_osc, bit, &duration); + + if(advance) { + bit_lib_increment_index(protocol->encoder.encoded_index, FDXA_ENCODED_BIT_SIZE); + } + + // duration diveded by 2 because we need to output high and low + duration = duration / 2; + protocol->encoder.pulse = duration; + level = true; + } else { + // output low half and reset pulse + duration = protocol->encoder.pulse; + protocol->encoder.pulse = 0; + level = false; + } + + return level_duration_make(level, duration); +}; + +bool protocol_fdx_a_write_data(ProtocolFDXA* protocol, void* data) { + LFRFIDWriteRequest* request = (LFRFIDWriteRequest*)data; + bool result = false; + + protocol_fdx_a_encoder_start(protocol); + + if(request->write_type == LFRFIDWriteTypeT5577) { + request->t5577.block[0] = LFRFID_T5577_MODULATION_FSK2a | LFRFID_T5577_BITRATE_RF_50 | + (3 << LFRFID_T5577_MAXBLOCK_SHIFT); + request->t5577.block[1] = bit_lib_get_bits_32(protocol->encoded_data, 0, 32); + request->t5577.block[2] = bit_lib_get_bits_32(protocol->encoded_data, 32, 32); + request->t5577.block[3] = bit_lib_get_bits_32(protocol->encoded_data, 64, 32); + request->t5577.blocks_to_write = 4; + result = true; + } + return result; +}; + +void protocol_fdx_a_render_data(ProtocolFDXA* protocol, string_t result) { + uint8_t data[FDXA_DECODED_DATA_SIZE]; + memcpy(data, protocol->data, FDXA_DECODED_DATA_SIZE); + + uint8_t parity_sum = 0; + for(size_t i = 0; i < FDXA_DECODED_DATA_SIZE; i++) { + parity_sum += bit_lib_test_parity_32(data[i], BitLibParityOdd); + data[i] &= 0x7F; + } + + string_printf( + result, + "ID: %02X%02X%02X%02X%02X\r\n" + "Parity: %s", + data[0], + data[1], + data[2], + data[3], + data[4], + parity_sum == 0 ? "+" : "-"); +}; + +const ProtocolBase protocol_fdx_a = { + .name = "FDX-A", + .manufacturer = "FECAVA", + .data_size = FDXA_DECODED_DATA_SIZE, + .features = LFRFIDFeatureASK, + .validate_count = 3, + .alloc = (ProtocolAlloc)protocol_fdx_a_alloc, + .free = (ProtocolFree)protocol_fdx_a_free, + .get_data = (ProtocolGetData)protocol_fdx_a_get_data, + .decoder = + { + .start = (ProtocolDecoderStart)protocol_fdx_a_decoder_start, + .feed = (ProtocolDecoderFeed)protocol_fdx_a_decoder_feed, + }, + .encoder = + { + .start = (ProtocolEncoderStart)protocol_fdx_a_encoder_start, + .yield = (ProtocolEncoderYield)protocol_fdx_a_encoder_yield, + }, + .render_data = (ProtocolRenderData)protocol_fdx_a_render_data, + .render_brief_data = (ProtocolRenderData)protocol_fdx_a_render_data, + .write_data = (ProtocolWriteData)protocol_fdx_a_write_data, +}; \ No newline at end of file diff --git a/lib/lfrfid/protocols/protocol_fdx_a.h b/lib/lfrfid/protocols/protocol_fdx_a.h new file mode 100644 index 00000000000..355544881c0 --- /dev/null +++ b/lib/lfrfid/protocols/protocol_fdx_a.h @@ -0,0 +1,4 @@ +#pragma once +#include + +extern const ProtocolBase protocol_fdx_a; \ No newline at end of file diff --git a/lib/lfrfid/protocols/protocol_fdx_b.c b/lib/lfrfid/protocols/protocol_fdx_b.c new file mode 100644 index 00000000000..f68a884e853 --- /dev/null +++ b/lib/lfrfid/protocols/protocol_fdx_b.c @@ -0,0 +1,374 @@ +#include +#include "toolbox/level_duration.h" +#include "protocol_fdx_b.h" +#include +#include +#include "lfrfid_protocols.h" + +#define FDX_B_ENCODED_BIT_SIZE (128) +#define FDX_B_ENCODED_BYTE_SIZE (((FDX_B_ENCODED_BIT_SIZE) / 8)) +#define FDX_B_PREAMBLE_BIT_SIZE (11) +#define FDX_B_PREAMBLE_BYTE_SIZE (2) +#define FDX_B_ENCODED_BYTE_FULL_SIZE (FDX_B_ENCODED_BYTE_SIZE + FDX_B_PREAMBLE_BYTE_SIZE) + +#define FDXB_DECODED_DATA_SIZE (11) + +#define FDX_B_SHORT_TIME (128) +#define FDX_B_LONG_TIME (256) +#define FDX_B_JITTER_TIME (60) + +#define FDX_B_SHORT_TIME_LOW (FDX_B_SHORT_TIME - FDX_B_JITTER_TIME) +#define FDX_B_SHORT_TIME_HIGH (FDX_B_SHORT_TIME + FDX_B_JITTER_TIME) +#define FDX_B_LONG_TIME_LOW (FDX_B_LONG_TIME - FDX_B_JITTER_TIME) +#define FDX_B_LONG_TIME_HIGH (FDX_B_LONG_TIME + FDX_B_JITTER_TIME) + +typedef struct { + bool last_short; + bool last_level; + size_t encoded_index; + uint8_t encoded_data[FDX_B_ENCODED_BYTE_FULL_SIZE]; + uint8_t data[FDXB_DECODED_DATA_SIZE]; +} ProtocolFDXB; + +ProtocolFDXB* protocol_fdx_b_alloc(void) { + ProtocolFDXB* protocol = malloc(sizeof(ProtocolFDXB)); + return protocol; +}; + +void protocol_fdx_b_free(ProtocolFDXB* protocol) { + free(protocol); +}; + +uint8_t* protocol_fdx_b_get_data(ProtocolFDXB* proto) { + return proto->data; +}; + +void protocol_fdx_b_decoder_start(ProtocolFDXB* protocol) { + memset(protocol->encoded_data, 0, FDX_B_ENCODED_BYTE_FULL_SIZE); + protocol->last_short = false; +}; + +static bool protocol_fdx_b_can_be_decoded(ProtocolFDXB* protocol) { + bool result = false; + + /* + msb lsb + 0 10000000000 Header pattern. 11 bits. + 11 1nnnnnnnn + 20 1nnnnnnnn 38 bit (12 digit) National code. + 29 1nnnnnnnn eg. 000000001008 (decimal). + 38 1nnnnnnnn + 47 1nnnnnncc 10 bit (3 digit) Country code. + 56 1cccccccc eg. 999 (decimal). + 65 1s------- 1 bit data block status flag. + 74 1-------a 1 bit animal application indicator. + 83 1xxxxxxxx 16 bit checksum. + 92 1xxxxxxxx + 101 1eeeeeeee 24 bits of extra data if present. + 110 1eeeeeeee eg. $123456. + 119 1eeeeeeee + */ + + do { + // check 11 bits preamble + if(bit_lib_get_bits_16(protocol->encoded_data, 0, 11) != 0b10000000000) break; + // check next 11 bits preamble + if(bit_lib_get_bits_16(protocol->encoded_data, 128, 11) != 0b10000000000) break; + // check control bits + if(!bit_lib_test_parity(protocol->encoded_data, 3, 13 * 9, BitLibParityAlways1, 9)) break; + + // compute checksum + uint8_t crc_data[8]; + for(size_t i = 0; i < 8; i++) { + bit_lib_copy_bits(crc_data, i * 8, 8, protocol->encoded_data, 12 + 9 * i); + } + uint16_t crc_res = bit_lib_crc16(crc_data, 8, 0x1021, 0x0000, false, false, 0x0000); + + // read checksum + uint16_t crc_ex = 0; + bit_lib_copy_bits((uint8_t*)&crc_ex, 8, 8, protocol->encoded_data, 84); + bit_lib_copy_bits((uint8_t*)&crc_ex, 0, 8, protocol->encoded_data, 93); + + // compare checksum + if(crc_res != crc_ex) break; + + result = true; + } while(false); + + return result; +} + +void protocol_fdx_b_decode(ProtocolFDXB* protocol) { + // remove parity + bit_lib_remove_bit_every_nth(protocol->encoded_data, 3, 13 * 9, 9); + + // remove header pattern + for(size_t i = 0; i < 11; i++) + bit_lib_push_bit(protocol->encoded_data, FDX_B_ENCODED_BYTE_FULL_SIZE, 0); + + // 0 nnnnnnnn + // 8 nnnnnnnn 38 bit (12 digit) National code. + // 16 nnnnnnnn eg. 000000001008 (decimal). + // 24 nnnnnnnn + // 32 nnnnnncc 10 bit (3 digit) Country code. + // 40 cccccccc eg. 999 (decimal). + // 48 s------- 1 bit data block status flag. + // 56 -------a 1 bit animal application indicator. + // 64 xxxxxxxx 16 bit checksum. + // 72 xxxxxxxx + // 80 eeeeeeee 24 bits of extra data if present. + // 88 eeeeeeee eg. $123456. + // 92 eeeeeeee + + // copy data without checksum + bit_lib_copy_bits(protocol->data, 0, 64, protocol->encoded_data, 0); + bit_lib_copy_bits(protocol->data, 64, 24, protocol->encoded_data, 80); + + // const BitLibRegion regions_encoded[] = { + // {'n', 0, 38}, + // {'c', 38, 10}, + // {'b', 48, 16}, + // {'x', 64, 16}, + // {'e', 80, 24}, + // }; + + // bit_lib_print_regions(regions_encoded, 5, protocol->encoded_data, FDX_B_ENCODED_BIT_SIZE); + + // const BitLibRegion regions_decoded[] = { + // {'n', 0, 38}, + // {'c', 38, 10}, + // {'b', 48, 16}, + // {'e', 64, 24}, + // }; + + // bit_lib_print_regions(regions_decoded, 4, protocol->data, FDXB_DECODED_DATA_SIZE * 8); +} + +bool protocol_fdx_b_decoder_feed(ProtocolFDXB* protocol, bool level, uint32_t duration) { + bool result = false; + UNUSED(level); + + bool pushed = false; + + // Bi-Phase Manchester decoding + if(duration >= FDX_B_SHORT_TIME_LOW && duration <= FDX_B_SHORT_TIME_HIGH) { + if(protocol->last_short == false) { + protocol->last_short = true; + } else { + pushed = true; + bit_lib_push_bit(protocol->encoded_data, FDX_B_ENCODED_BYTE_FULL_SIZE, false); + protocol->last_short = false; + } + } else if(duration >= FDX_B_LONG_TIME_LOW && duration <= FDX_B_LONG_TIME_HIGH) { + if(protocol->last_short == false) { + pushed = true; + bit_lib_push_bit(protocol->encoded_data, FDX_B_ENCODED_BYTE_FULL_SIZE, true); + } else { + // reset + protocol->last_short = false; + } + } else { + // reset + protocol->last_short = false; + } + + if(pushed && protocol_fdx_b_can_be_decoded(protocol)) { + protocol_fdx_b_decode(protocol); + result = true; + } + + return result; +}; + +bool protocol_fdx_b_encoder_start(ProtocolFDXB* protocol) { + memset(protocol->encoded_data, 0, FDX_B_ENCODED_BYTE_FULL_SIZE); + bit_lib_set_bit(protocol->encoded_data, 0, 1); + for(size_t i = 0; i < 13; i++) { + bit_lib_set_bit(protocol->encoded_data, 11 + 9 * i, 1); + if(i == 8 || i == 9) continue; + + if(i < 8) { + bit_lib_copy_bits(protocol->encoded_data, 12 + 9 * i, 8, protocol->data, i * 8); + } else { + bit_lib_copy_bits(protocol->encoded_data, 12 + 9 * i, 8, protocol->data, (i - 2) * 8); + } + } + + uint16_t crc_res = bit_lib_crc16(protocol->data, 8, 0x1021, 0x0000, false, false, 0x0000); + bit_lib_copy_bits(protocol->encoded_data, 84, 8, (uint8_t*)&crc_res, 8); + bit_lib_copy_bits(protocol->encoded_data, 93, 8, (uint8_t*)&crc_res, 0); + + protocol->encoded_index = 0; + protocol->last_short = false; + protocol->last_level = false; + return true; +}; + +LevelDuration protocol_fdx_b_encoder_yield(ProtocolFDXB* protocol) { + uint32_t duration; + protocol->last_level = !protocol->last_level; + + bool bit = bit_lib_get_bit(protocol->encoded_data, protocol->encoded_index); + + // Bi-Phase Manchester encoder + if(bit) { + // one long pulse for 1 + duration = FDX_B_LONG_TIME / 8; + bit_lib_increment_index(protocol->encoded_index, FDX_B_ENCODED_BIT_SIZE); + } else { + // two short pulses for 0 + duration = FDX_B_SHORT_TIME / 8; + if(protocol->last_short) { + bit_lib_increment_index(protocol->encoded_index, FDX_B_ENCODED_BIT_SIZE); + protocol->last_short = false; + } else { + protocol->last_short = true; + } + } + + return level_duration_make(protocol->last_level, duration); +}; + +// 0 nnnnnnnn +// 8 nnnnnnnn 38 bit (12 digit) National code. +// 16 nnnnnnnn eg. 000000001008 (decimal). +// 24 nnnnnnnn +// 32 nnnnnnnn 10 bit (3 digit) Country code. +// 40 cccccccc eg. 999 (decimal). +// 48 s------- 1 bit data block status flag. +// 56 -------a 1 bit animal application indicator. +// 64 eeeeeeee 24 bits of extra data if present. +// 72 eeeeeeee eg. $123456. +// 80 eeeeeeee + +static uint64_t protocol_fdx_b_get_national_code(const uint8_t* data) { + uint64_t national_code = bit_lib_get_bits_32(data, 0, 32); + national_code = national_code << 32; + national_code |= bit_lib_get_bits_32(data, 32, 6) << (32 - 6); + bit_lib_reverse_bits((uint8_t*)&national_code, 0, 64); + return national_code; +} + +static uint16_t protocol_fdx_b_get_country_code(const uint8_t* data) { + uint16_t country_code = bit_lib_get_bits_16(data, 38, 10) << 6; + bit_lib_reverse_bits((uint8_t*)&country_code, 0, 16); + return country_code; +} + +static bool protocol_fdx_b_get_temp(const uint8_t* data, float* temp) { + uint32_t extended = bit_lib_get_bits_32(data, 64, 24) << 8; + bit_lib_reverse_bits((uint8_t*)&extended, 0, 32); + + uint8_t ex_parity = (extended & 0x100) >> 8; + uint8_t ex_temperature = extended & 0xff; + uint8_t ex_calc_parity = bit_lib_test_parity_32(ex_temperature, BitLibParityOdd); + bool ex_temperature_present = (ex_calc_parity == ex_parity) && !(extended & 0xe00); + + if(ex_temperature_present) { + float temperature_f = 74 + ex_temperature * 0.2; + *temp = temperature_f; + return true; + } else { + return false; + } +} + +void protocol_fdx_b_render_data(ProtocolFDXB* protocol, string_t result) { + // 38 bits of national code + uint64_t national_code = protocol_fdx_b_get_national_code(protocol->data); + + // 10 bit of country code + uint16_t country_code = protocol_fdx_b_get_country_code(protocol->data); + + bool block_status = bit_lib_get_bit(protocol->data, 48); + bool rudi_bit = bit_lib_get_bit(protocol->data, 49); + uint8_t reserved = bit_lib_get_bits(protocol->data, 50, 5); + uint8_t user_info = bit_lib_get_bits(protocol->data, 55, 5); + uint8_t replacement_number = bit_lib_get_bits(protocol->data, 60, 3); + bool animal_flag = bit_lib_get_bit(protocol->data, 63); + + string_printf(result, "ID: %03u-%012llu\r\n", country_code, national_code); + string_cat_printf(result, "Animal: %s, ", animal_flag ? "Yes" : "No"); + + float temperature; + if(protocol_fdx_b_get_temp(protocol->data, &temperature)) { + float temperature_c = (temperature - 32) / 1.8; + string_cat_printf( + result, "T: %.2fF, %.2fC\r\n", (double)temperature, (double)temperature_c); + } else { + string_cat_printf(result, "T: ---\r\n"); + } + + string_cat_printf( + result, + "Bits: %X-%X-%X-%X-%X", + block_status, + rudi_bit, + reserved, + user_info, + replacement_number); +}; + +void protocol_fdx_b_render_brief_data(ProtocolFDXB* protocol, string_t result) { + // 38 bits of national code + uint64_t national_code = protocol_fdx_b_get_national_code(protocol->data); + + // 10 bit of country code + uint16_t country_code = protocol_fdx_b_get_country_code(protocol->data); + + bool animal_flag = bit_lib_get_bit(protocol->data, 63); + + string_printf(result, "ID: %03u-%012llu\r\n", country_code, national_code); + string_cat_printf(result, "Animal: %s, ", animal_flag ? "Yes" : "No"); + + float temperature; + if(protocol_fdx_b_get_temp(protocol->data, &temperature)) { + float temperature_c = (temperature - 32) / 1.8; + string_cat_printf(result, "T: %.2fC", (double)temperature_c); + } else { + string_cat_printf(result, "T: ---"); + } +}; + +bool protocol_fdx_b_write_data(ProtocolFDXB* protocol, void* data) { + LFRFIDWriteRequest* request = (LFRFIDWriteRequest*)data; + bool result = false; + + protocol_fdx_b_encoder_start(protocol); + + if(request->write_type == LFRFIDWriteTypeT5577) { + request->t5577.block[0] = LFRFID_T5577_MODULATION_DIPHASE | LFRFID_T5577_BITRATE_RF_32 | + (4 << LFRFID_T5577_MAXBLOCK_SHIFT); + request->t5577.block[1] = bit_lib_get_bits_32(protocol->encoded_data, 0, 32); + request->t5577.block[2] = bit_lib_get_bits_32(protocol->encoded_data, 32, 32); + request->t5577.block[3] = bit_lib_get_bits_32(protocol->encoded_data, 64, 32); + request->t5577.block[4] = bit_lib_get_bits_32(protocol->encoded_data, 96, 32); + request->t5577.blocks_to_write = 5; + result = true; + } + return result; +}; + +const ProtocolBase protocol_fdx_b = { + .name = "FDX-B", + .manufacturer = "ISO", + .data_size = FDXB_DECODED_DATA_SIZE, + .features = LFRFIDFeatureASK, + .validate_count = 3, + .alloc = (ProtocolAlloc)protocol_fdx_b_alloc, + .free = (ProtocolFree)protocol_fdx_b_free, + .get_data = (ProtocolGetData)protocol_fdx_b_get_data, + .decoder = + { + .start = (ProtocolDecoderStart)protocol_fdx_b_decoder_start, + .feed = (ProtocolDecoderFeed)protocol_fdx_b_decoder_feed, + }, + .encoder = + { + .start = (ProtocolEncoderStart)protocol_fdx_b_encoder_start, + .yield = (ProtocolEncoderYield)protocol_fdx_b_encoder_yield, + }, + .render_data = (ProtocolRenderData)protocol_fdx_b_render_data, + .render_brief_data = (ProtocolRenderData)protocol_fdx_b_render_brief_data, + .write_data = (ProtocolWriteData)protocol_fdx_b_write_data, +}; \ No newline at end of file diff --git a/lib/lfrfid/protocols/protocol_fdx_b.h b/lib/lfrfid/protocols/protocol_fdx_b.h new file mode 100644 index 00000000000..549c862e380 --- /dev/null +++ b/lib/lfrfid/protocols/protocol_fdx_b.h @@ -0,0 +1,4 @@ +#pragma once +#include + +extern const ProtocolBase protocol_fdx_b; \ No newline at end of file diff --git a/lib/lfrfid/protocols/protocol_h10301.c b/lib/lfrfid/protocols/protocol_h10301.c new file mode 100644 index 00000000000..f30f75facfa --- /dev/null +++ b/lib/lfrfid/protocols/protocol_h10301.c @@ -0,0 +1,386 @@ +#include +#include +#include +#include +#include "lfrfid_protocols.h" + +#define JITTER_TIME (20) +#define MIN_TIME (64 - JITTER_TIME) +#define MAX_TIME (80 + JITTER_TIME) + +#define H10301_DECODED_DATA_SIZE (3) +#define H10301_ENCODED_DATA_SIZE_U32 (3) +#define H10301_ENCODED_DATA_SIZE (sizeof(uint32_t) * H10301_ENCODED_DATA_SIZE_U32) + +#define H10301_BIT_SIZE (sizeof(uint32_t) * 8) +#define H10301_BIT_MAX_SIZE (H10301_BIT_SIZE * H10301_DECODED_DATA_SIZE) + +typedef struct { + FSKDemod* fsk_demod; +} ProtocolH10301Decoder; + +typedef struct { + FSKOsc* fsk_osc; + uint8_t encoded_index; + uint32_t pulse; +} ProtocolH10301Encoder; + +typedef struct { + ProtocolH10301Decoder decoder; + ProtocolH10301Encoder encoder; + uint32_t encoded_data[H10301_ENCODED_DATA_SIZE_U32]; + uint8_t data[H10301_DECODED_DATA_SIZE]; +} ProtocolH10301; + +ProtocolH10301* protocol_h10301_alloc(void) { + ProtocolH10301* protocol = malloc(sizeof(ProtocolH10301)); + protocol->decoder.fsk_demod = fsk_demod_alloc(MIN_TIME, 6, MAX_TIME, 5); + protocol->encoder.fsk_osc = fsk_osc_alloc(8, 10, 50); + + return protocol; +}; + +void protocol_h10301_free(ProtocolH10301* protocol) { + fsk_demod_free(protocol->decoder.fsk_demod); + fsk_osc_free(protocol->encoder.fsk_osc); + free(protocol); +}; + +uint8_t* protocol_h10301_get_data(ProtocolH10301* protocol) { + return protocol->data; +}; + +void protocol_h10301_decoder_start(ProtocolH10301* protocol) { + memset(protocol->encoded_data, 0, sizeof(uint32_t) * 3); +}; + +static void protocol_h10301_decoder_store_data(ProtocolH10301* protocol, bool data) { + protocol->encoded_data[0] = (protocol->encoded_data[0] << 1) | + ((protocol->encoded_data[1] >> 31) & 1); + protocol->encoded_data[1] = (protocol->encoded_data[1] << 1) | + ((protocol->encoded_data[2] >> 31) & 1); + protocol->encoded_data[2] = (protocol->encoded_data[2] << 1) | data; +} + +static bool protocol_h10301_can_be_decoded(const uint32_t* card_data) { + const uint8_t* encoded_data = (const uint8_t*)card_data; + + // packet preamble + // raw data + if(*(encoded_data + 3) != 0x1D) { + return false; + } + + // encoded company/oem + // coded with 01 = 0, 10 = 1 transitions + // stored in word 0 + if((*card_data >> 10 & 0x3FFF) != 0x1556) { + return false; + } + + // encoded format/length + // coded with 01 = 0, 10 = 1 transitions + // stored in word 0 and word 1 + if((((*card_data & 0x3FF) << 12) | ((*(card_data + 1) >> 20) & 0xFFF)) != 0x155556) { + return false; + } + + // data decoding + uint32_t result = 0; + + // decode from word 1 + // coded with 01 = 0, 10 = 1 transitions + for(int8_t i = 9; i >= 0; i--) { + switch((*(card_data + 1) >> (2 * i)) & 0b11) { + case 0b01: + result = (result << 1) | 0; + break; + case 0b10: + result = (result << 1) | 1; + break; + default: + return false; + break; + } + } + + // decode from word 2 + // coded with 01 = 0, 10 = 1 transitions + for(int8_t i = 15; i >= 0; i--) { + switch((*(card_data + 2) >> (2 * i)) & 0b11) { + case 0b01: + result = (result << 1) | 0; + break; + case 0b10: + result = (result << 1) | 1; + break; + default: + return false; + break; + } + } + + // trailing parity (odd) test + uint8_t parity_sum = 0; + for(int8_t i = 0; i < 13; i++) { + if(((result >> i) & 1) == 1) { + parity_sum++; + } + } + + if((parity_sum % 2) != 1) { + return false; + } + + // leading parity (even) test + parity_sum = 0; + for(int8_t i = 13; i < 26; i++) { + if(((result >> i) & 1) == 1) { + parity_sum++; + } + } + + if((parity_sum % 2) == 1) { + return false; + } + + return true; +} + +static void protocol_h10301_decode(const uint32_t* card_data, uint8_t* decoded_data) { + // data decoding + uint32_t result = 0; + + // decode from word 1 + // coded with 01 = 0, 10 = 1 transitions + for(int8_t i = 9; i >= 0; i--) { + switch((*(card_data + 1) >> (2 * i)) & 0b11) { + case 0b01: + result = (result << 1) | 0; + break; + case 0b10: + result = (result << 1) | 1; + break; + default: + break; + } + } + + // decode from word 2 + // coded with 01 = 0, 10 = 1 transitions + for(int8_t i = 15; i >= 0; i--) { + switch((*(card_data + 2) >> (2 * i)) & 0b11) { + case 0b01: + result = (result << 1) | 0; + break; + case 0b10: + result = (result << 1) | 1; + break; + default: + break; + } + } + + uint8_t data[H10301_DECODED_DATA_SIZE] = { + (uint8_t)(result >> 17), (uint8_t)(result >> 9), (uint8_t)(result >> 1)}; + + memcpy(decoded_data, &data, H10301_DECODED_DATA_SIZE); +} + +bool protocol_h10301_decoder_feed(ProtocolH10301* protocol, bool level, uint32_t duration) { + bool value; + uint32_t count; + bool result = false; + + fsk_demod_feed(protocol->decoder.fsk_demod, level, duration, &value, &count); + if(count > 0) { + for(size_t i = 0; i < count; i++) { + protocol_h10301_decoder_store_data(protocol, value); + if(protocol_h10301_can_be_decoded(protocol->encoded_data)) { + protocol_h10301_decode(protocol->encoded_data, protocol->data); + result = true; + break; + } + } + } + + return result; +}; + +static void protocol_h10301_write_raw_bit(bool bit, uint8_t position, uint32_t* card_data) { + if(bit) { + card_data[position / H10301_BIT_SIZE] |= + 1UL << (H10301_BIT_SIZE - (position % H10301_BIT_SIZE) - 1); + } else { + card_data[position / H10301_BIT_SIZE] &= + ~(1UL << (H10301_BIT_SIZE - (position % H10301_BIT_SIZE) - 1)); + } +} + +static void protocol_h10301_write_bit(bool bit, uint8_t position, uint32_t* card_data) { + protocol_h10301_write_raw_bit(bit, position + 0, card_data); + protocol_h10301_write_raw_bit(!bit, position + 1, card_data); +} + +void protocol_h10301_encode(const uint8_t* decoded_data, uint8_t* encoded_data) { + uint32_t card_data[H10301_DECODED_DATA_SIZE] = {0, 0, 0}; + + uint32_t fc_cn = (decoded_data[0] << 16) | (decoded_data[1] << 8) | decoded_data[2]; + + // even parity sum calculation (high 12 bits of data) + uint8_t even_parity_sum = 0; + for(int8_t i = 12; i < 24; i++) { + if(((fc_cn >> i) & 1) == 1) { + even_parity_sum++; + } + } + + // odd parity sum calculation (low 12 bits of data) + uint8_t odd_parity_sum = 1; + for(int8_t i = 0; i < 12; i++) { + if(((fc_cn >> i) & 1) == 1) { + odd_parity_sum++; + } + } + + // 0x1D preamble + protocol_h10301_write_raw_bit(0, 0, card_data); + protocol_h10301_write_raw_bit(0, 1, card_data); + protocol_h10301_write_raw_bit(0, 2, card_data); + protocol_h10301_write_raw_bit(1, 3, card_data); + protocol_h10301_write_raw_bit(1, 4, card_data); + protocol_h10301_write_raw_bit(1, 5, card_data); + protocol_h10301_write_raw_bit(0, 6, card_data); + protocol_h10301_write_raw_bit(1, 7, card_data); + + // company / OEM code 1 + protocol_h10301_write_bit(0, 8, card_data); + protocol_h10301_write_bit(0, 10, card_data); + protocol_h10301_write_bit(0, 12, card_data); + protocol_h10301_write_bit(0, 14, card_data); + protocol_h10301_write_bit(0, 16, card_data); + protocol_h10301_write_bit(0, 18, card_data); + protocol_h10301_write_bit(1, 20, card_data); + + // card format / length 1 + protocol_h10301_write_bit(0, 22, card_data); + protocol_h10301_write_bit(0, 24, card_data); + protocol_h10301_write_bit(0, 26, card_data); + protocol_h10301_write_bit(0, 28, card_data); + protocol_h10301_write_bit(0, 30, card_data); + protocol_h10301_write_bit(0, 32, card_data); + protocol_h10301_write_bit(0, 34, card_data); + protocol_h10301_write_bit(0, 36, card_data); + protocol_h10301_write_bit(0, 38, card_data); + protocol_h10301_write_bit(0, 40, card_data); + protocol_h10301_write_bit(1, 42, card_data); + + // even parity bit + protocol_h10301_write_bit((even_parity_sum % 2), 44, card_data); + + // data + for(uint8_t i = 0; i < 24; i++) { + protocol_h10301_write_bit((fc_cn >> (23 - i)) & 1, 46 + (i * 2), card_data); + } + + // odd parity bit + protocol_h10301_write_bit((odd_parity_sum % 2), 94, card_data); + + memcpy(encoded_data, &card_data, H10301_ENCODED_DATA_SIZE); +} + +bool protocol_h10301_encoder_start(ProtocolH10301* protocol) { + protocol_h10301_encode(protocol->data, (uint8_t*)protocol->encoded_data); + protocol->encoder.encoded_index = 0; + protocol->encoder.pulse = 0; + + return true; +}; + +LevelDuration protocol_h10301_encoder_yield(ProtocolH10301* protocol) { + bool level = 0; + uint32_t duration = 0; + + // if pulse is zero, we need to output high, otherwise we need to output low + if(protocol->encoder.pulse == 0) { + // get bit + uint8_t bit = + (protocol->encoded_data[protocol->encoder.encoded_index / H10301_BIT_SIZE] >> + ((H10301_BIT_SIZE - 1) - (protocol->encoder.encoded_index % H10301_BIT_SIZE))) & + 1; + + // get pulse from oscillator + bool advance = fsk_osc_next(protocol->encoder.fsk_osc, bit, &duration); + + if(advance) { + protocol->encoder.encoded_index++; + if(protocol->encoder.encoded_index >= (H10301_BIT_MAX_SIZE)) { + protocol->encoder.encoded_index = 0; + } + } + + // duration diveded by 2 because we need to output high and low + duration = duration / 2; + protocol->encoder.pulse = duration; + level = true; + } else { + // output low half and reset pulse + duration = protocol->encoder.pulse; + protocol->encoder.pulse = 0; + level = false; + } + + return level_duration_make(level, duration); +}; + +bool protocol_h10301_write_data(ProtocolH10301* protocol, void* data) { + LFRFIDWriteRequest* request = (LFRFIDWriteRequest*)data; + bool result = false; + + protocol_h10301_encoder_start(protocol); + + if(request->write_type == LFRFIDWriteTypeT5577) { + request->t5577.block[0] = LFRFID_T5577_MODULATION_FSK2a | LFRFID_T5577_BITRATE_RF_50 | + (3 << LFRFID_T5577_MAXBLOCK_SHIFT); + request->t5577.block[1] = protocol->encoded_data[0]; + request->t5577.block[2] = protocol->encoded_data[1]; + request->t5577.block[3] = protocol->encoded_data[2]; + request->t5577.blocks_to_write = 4; + result = true; + } + return result; +}; + +void protocol_h10301_render_data(ProtocolH10301* protocol, string_t result) { + uint8_t* data = protocol->data; + string_printf( + result, + "FC: %u\r\n" + "Card: %u", + data[0], + (uint16_t)((data[1] << 8) | (data[2]))); +}; + +const ProtocolBase protocol_h10301 = { + .name = "H10301", + .manufacturer = "HID", + .data_size = H10301_DECODED_DATA_SIZE, + .features = LFRFIDFeatureASK, + .validate_count = 3, + .alloc = (ProtocolAlloc)protocol_h10301_alloc, + .free = (ProtocolFree)protocol_h10301_free, + .get_data = (ProtocolGetData)protocol_h10301_get_data, + .decoder = + { + .start = (ProtocolDecoderStart)protocol_h10301_decoder_start, + .feed = (ProtocolDecoderFeed)protocol_h10301_decoder_feed, + }, + .encoder = + { + .start = (ProtocolEncoderStart)protocol_h10301_encoder_start, + .yield = (ProtocolEncoderYield)protocol_h10301_encoder_yield, + }, + .render_data = (ProtocolRenderData)protocol_h10301_render_data, + .render_brief_data = (ProtocolRenderData)protocol_h10301_render_data, + .write_data = (ProtocolWriteData)protocol_h10301_write_data, +}; \ No newline at end of file diff --git a/lib/lfrfid/protocols/protocol_h10301.h b/lib/lfrfid/protocols/protocol_h10301.h new file mode 100644 index 00000000000..b7ee5ad57d9 --- /dev/null +++ b/lib/lfrfid/protocols/protocol_h10301.h @@ -0,0 +1,4 @@ +#pragma once +#include + +extern const ProtocolBase protocol_h10301; \ No newline at end of file diff --git a/lib/lfrfid/protocols/protocol_hid_ex_generic.c b/lib/lfrfid/protocols/protocol_hid_ex_generic.c new file mode 100644 index 00000000000..e0a85266184 --- /dev/null +++ b/lib/lfrfid/protocols/protocol_hid_ex_generic.c @@ -0,0 +1,219 @@ +#include +#include +#include +#include +#include "lfrfid_protocols.h" +#include + +#define JITTER_TIME (20) +#define MIN_TIME (64 - JITTER_TIME) +#define MAX_TIME (80 + JITTER_TIME) + +#define HID_DATA_SIZE 23 +#define HID_PREAMBLE_SIZE 1 + +#define HID_ENCODED_DATA_SIZE (HID_PREAMBLE_SIZE + HID_DATA_SIZE + HID_PREAMBLE_SIZE) +#define HID_ENCODED_BIT_SIZE ((HID_PREAMBLE_SIZE + HID_DATA_SIZE) * 8) +#define HID_DECODED_DATA_SIZE (12) +#define HID_DECODED_BIT_SIZE ((HID_ENCODED_BIT_SIZE - HID_PREAMBLE_SIZE * 8) / 2) + +#define HID_PREAMBLE 0x1D + +typedef struct { + FSKDemod* fsk_demod; +} ProtocolHIDExDecoder; + +typedef struct { + FSKOsc* fsk_osc; + uint8_t encoded_index; + uint32_t pulse; +} ProtocolHIDExEncoder; + +typedef struct { + ProtocolHIDExDecoder decoder; + ProtocolHIDExEncoder encoder; + uint8_t encoded_data[HID_ENCODED_DATA_SIZE]; + uint8_t data[HID_DECODED_DATA_SIZE]; + size_t protocol_size; +} ProtocolHIDEx; + +ProtocolHIDEx* protocol_hid_ex_generic_alloc(void) { + ProtocolHIDEx* protocol = malloc(sizeof(ProtocolHIDEx)); + protocol->decoder.fsk_demod = fsk_demod_alloc(MIN_TIME, 6, MAX_TIME, 5); + protocol->encoder.fsk_osc = fsk_osc_alloc(8, 10, 50); + + return protocol; +}; + +void protocol_hid_ex_generic_free(ProtocolHIDEx* protocol) { + fsk_demod_free(protocol->decoder.fsk_demod); + fsk_osc_free(protocol->encoder.fsk_osc); + free(protocol); +}; + +uint8_t* protocol_hid_ex_generic_get_data(ProtocolHIDEx* protocol) { + return protocol->data; +}; + +void protocol_hid_ex_generic_decoder_start(ProtocolHIDEx* protocol) { + memset(protocol->encoded_data, 0, HID_ENCODED_DATA_SIZE); +}; + +static bool protocol_hid_ex_generic_can_be_decoded(const uint8_t* data) { + // check preamble + if(data[0] != HID_PREAMBLE || data[HID_PREAMBLE_SIZE + HID_DATA_SIZE] != HID_PREAMBLE) { + return false; + } + + // check for manchester encoding + for(size_t i = HID_PREAMBLE_SIZE; i < (HID_PREAMBLE_SIZE + HID_DATA_SIZE); i++) { + for(size_t n = 0; n < 4; n++) { + uint8_t bit_pair = (data[i] >> (n * 2)) & 0b11; + if(bit_pair == 0b11 || bit_pair == 0b00) { + return false; + } + } + } + + return true; +} + +static void protocol_hid_ex_generic_decode(const uint8_t* from, uint8_t* to) { + size_t bit_index = 0; + for(size_t i = HID_PREAMBLE_SIZE; i < (HID_PREAMBLE_SIZE + HID_DATA_SIZE); i++) { + for(size_t n = 0; n < 4; n++) { + uint8_t bit_pair = (from[i] >> (6 - (n * 2))) & 0b11; + if(bit_pair == 0b01) { + bit_lib_set_bit(to, bit_index, 0); + } else if(bit_pair == 0b10) { + bit_lib_set_bit(to, bit_index, 1); + } + bit_index++; + } + } +} + +bool protocol_hid_ex_generic_decoder_feed(ProtocolHIDEx* protocol, bool level, uint32_t duration) { + bool value; + uint32_t count; + bool result = false; + + fsk_demod_feed(protocol->decoder.fsk_demod, level, duration, &value, &count); + if(count > 0) { + for(size_t i = 0; i < count; i++) { + bit_lib_push_bit(protocol->encoded_data, HID_ENCODED_DATA_SIZE, value); + if(protocol_hid_ex_generic_can_be_decoded(protocol->encoded_data)) { + protocol_hid_ex_generic_decode(protocol->encoded_data, protocol->data); + result = true; + } + } + } + + return result; +}; + +static void protocol_hid_ex_generic_encode(ProtocolHIDEx* protocol) { + protocol->encoded_data[0] = HID_PREAMBLE; + + size_t bit_index = 0; + for(size_t i = 0; i < HID_DECODED_BIT_SIZE; i++) { + bool bit = bit_lib_get_bit(protocol->data, i); + if(bit) { + bit_lib_set_bit(protocol->encoded_data, 8 + bit_index, 1); + bit_lib_set_bit(protocol->encoded_data, 8 + bit_index + 1, 0); + } else { + bit_lib_set_bit(protocol->encoded_data, 8 + bit_index, 0); + bit_lib_set_bit(protocol->encoded_data, 8 + bit_index + 1, 1); + } + bit_index += 2; + } +} + +bool protocol_hid_ex_generic_encoder_start(ProtocolHIDEx* protocol) { + protocol->encoder.encoded_index = 0; + protocol->encoder.pulse = 0; + protocol_hid_ex_generic_encode(protocol); + + return true; +}; + +LevelDuration protocol_hid_ex_generic_encoder_yield(ProtocolHIDEx* protocol) { + bool level = 0; + uint32_t duration = 0; + + // if pulse is zero, we need to output high, otherwise we need to output low + if(protocol->encoder.pulse == 0) { + // get bit + uint8_t bit = bit_lib_get_bit(protocol->encoded_data, protocol->encoder.encoded_index); + + // get pulse from oscillator + bool advance = fsk_osc_next(protocol->encoder.fsk_osc, bit, &duration); + + if(advance) { + bit_lib_increment_index(protocol->encoder.encoded_index, HID_ENCODED_BIT_SIZE); + } + + // duration diveded by 2 because we need to output high and low + duration = duration / 2; + protocol->encoder.pulse = duration; + level = true; + } else { + // output low half and reset pulse + duration = protocol->encoder.pulse; + protocol->encoder.pulse = 0; + level = false; + } + + return level_duration_make(level, duration); +}; + +bool protocol_hid_ex_generic_write_data(ProtocolHIDEx* protocol, void* data) { + LFRFIDWriteRequest* request = (LFRFIDWriteRequest*)data; + bool result = false; + + protocol_hid_ex_generic_encoder_start(protocol); + + if(request->write_type == LFRFIDWriteTypeT5577) { + request->t5577.block[0] = LFRFID_T5577_MODULATION_FSK2a | LFRFID_T5577_BITRATE_RF_50 | + (6 << LFRFID_T5577_MAXBLOCK_SHIFT); + request->t5577.block[1] = bit_lib_get_bits_32(protocol->encoded_data, 0, 32); + request->t5577.block[2] = bit_lib_get_bits_32(protocol->encoded_data, 32, 32); + request->t5577.block[3] = bit_lib_get_bits_32(protocol->encoded_data, 64, 32); + request->t5577.block[4] = bit_lib_get_bits_32(protocol->encoded_data, 96, 32); + request->t5577.block[5] = bit_lib_get_bits_32(protocol->encoded_data, 128, 32); + request->t5577.block[6] = bit_lib_get_bits_32(protocol->encoded_data, 160, 32); + request->t5577.blocks_to_write = 7; + result = true; + } + return result; +}; + +void protocol_hid_ex_generic_render_data(ProtocolHIDEx* protocol, string_t result) { + // TODO: parser and render functions + UNUSED(protocol); + string_printf(result, "Generic HID Extended\r\nData: Unknown"); +}; + +const ProtocolBase protocol_hid_ex_generic = { + .name = "HIDExt", + .manufacturer = "Generic", + .data_size = HID_DECODED_DATA_SIZE, + .features = LFRFIDFeatureASK, + .validate_count = 3, + .alloc = (ProtocolAlloc)protocol_hid_ex_generic_alloc, + .free = (ProtocolFree)protocol_hid_ex_generic_free, + .get_data = (ProtocolGetData)protocol_hid_ex_generic_get_data, + .decoder = + { + .start = (ProtocolDecoderStart)protocol_hid_ex_generic_decoder_start, + .feed = (ProtocolDecoderFeed)protocol_hid_ex_generic_decoder_feed, + }, + .encoder = + { + .start = (ProtocolEncoderStart)protocol_hid_ex_generic_encoder_start, + .yield = (ProtocolEncoderYield)protocol_hid_ex_generic_encoder_yield, + }, + .render_data = (ProtocolRenderData)protocol_hid_ex_generic_render_data, + .render_brief_data = (ProtocolRenderData)protocol_hid_ex_generic_render_data, + .write_data = (ProtocolWriteData)protocol_hid_ex_generic_write_data, +}; \ No newline at end of file diff --git a/lib/lfrfid/protocols/protocol_hid_ex_generic.h b/lib/lfrfid/protocols/protocol_hid_ex_generic.h new file mode 100644 index 00000000000..9c4ddffff3e --- /dev/null +++ b/lib/lfrfid/protocols/protocol_hid_ex_generic.h @@ -0,0 +1,4 @@ +#pragma once +#include + +extern const ProtocolBase protocol_hid_ex_generic; \ No newline at end of file diff --git a/lib/lfrfid/protocols/protocol_hid_generic.c b/lib/lfrfid/protocols/protocol_hid_generic.c new file mode 100644 index 00000000000..2516d681058 --- /dev/null +++ b/lib/lfrfid/protocols/protocol_hid_generic.c @@ -0,0 +1,280 @@ +#include +#include +#include +#include +#include "lfrfid_protocols.h" +#include + +#define JITTER_TIME (20) +#define MIN_TIME (64 - JITTER_TIME) +#define MAX_TIME (80 + JITTER_TIME) + +#define HID_DATA_SIZE 11 +#define HID_PREAMBLE_SIZE 1 +#define HID_PROTOCOL_SIZE_UNKNOWN 0 + +#define HID_ENCODED_DATA_SIZE (HID_PREAMBLE_SIZE + HID_DATA_SIZE + HID_PREAMBLE_SIZE) +#define HID_ENCODED_BIT_SIZE ((HID_PREAMBLE_SIZE + HID_DATA_SIZE) * 8) +#define HID_DECODED_DATA_SIZE (6) +#define HID_DECODED_BIT_SIZE ((HID_ENCODED_BIT_SIZE - HID_PREAMBLE_SIZE * 8) / 2) + +#define HID_PREAMBLE 0x1D + +typedef struct { + FSKDemod* fsk_demod; +} ProtocolHIDDecoder; + +typedef struct { + FSKOsc* fsk_osc; + uint8_t encoded_index; + uint32_t pulse; +} ProtocolHIDEncoder; + +typedef struct { + ProtocolHIDDecoder decoder; + ProtocolHIDEncoder encoder; + uint8_t encoded_data[HID_ENCODED_DATA_SIZE]; + uint8_t data[HID_DECODED_DATA_SIZE]; +} ProtocolHID; + +ProtocolHID* protocol_hid_generic_alloc(void) { + ProtocolHID* protocol = malloc(sizeof(ProtocolHID)); + protocol->decoder.fsk_demod = fsk_demod_alloc(MIN_TIME, 6, MAX_TIME, 5); + protocol->encoder.fsk_osc = fsk_osc_alloc(8, 10, 50); + + return protocol; +}; + +void protocol_hid_generic_free(ProtocolHID* protocol) { + fsk_demod_free(protocol->decoder.fsk_demod); + fsk_osc_free(protocol->encoder.fsk_osc); + free(protocol); +}; + +uint8_t* protocol_hid_generic_get_data(ProtocolHID* protocol) { + return protocol->data; +}; + +void protocol_hid_generic_decoder_start(ProtocolHID* protocol) { + memset(protocol->encoded_data, 0, HID_ENCODED_DATA_SIZE); +}; + +static bool protocol_hid_generic_can_be_decoded(const uint8_t* data) { + // check preamble + if(data[0] != HID_PREAMBLE || data[HID_PREAMBLE_SIZE + HID_DATA_SIZE] != HID_PREAMBLE) { + return false; + } + + // check for manchester encoding + for(size_t i = HID_PREAMBLE_SIZE; i < (HID_PREAMBLE_SIZE + HID_DATA_SIZE); i++) { + for(size_t n = 0; n < 4; n++) { + uint8_t bit_pair = (data[i] >> (n * 2)) & 0b11; + if(bit_pair == 0b11 || bit_pair == 0b00) { + return false; + } + } + } + + return true; +} + +static void protocol_hid_generic_decode(const uint8_t* from, uint8_t* to) { + size_t bit_index = 0; + for(size_t i = HID_PREAMBLE_SIZE; i < (HID_PREAMBLE_SIZE + HID_DATA_SIZE); i++) { + for(size_t n = 0; n < 4; n++) { + uint8_t bit_pair = (from[i] >> (6 - (n * 2))) & 0b11; + if(bit_pair == 0b01) { + bit_lib_set_bit(to, bit_index, 0); + } else if(bit_pair == 0b10) { + bit_lib_set_bit(to, bit_index, 1); + } + bit_index++; + } + } +} + +/** + * Decodes size from the HID Proximity header: + * - If any of the first six bits is 1, the key is composed of the bits + * following the first 1 + * - Otherwise, if the first six bits are 0: + * - If the seventh bit is 0, the key is composed of the remaining 37 bits. + * - If the seventh bit is 1, the size header continues until the next 1 bit, + * and the key is composed of however many bits remain. + * + * HID Proximity keys are 26 bits at minimum. If the header implies a key size + * under 26 bits, this function returns HID_PROTOCOL_SIZE_UNKNOWN. + */ +static uint8_t protocol_hid_generic_decode_protocol_size(ProtocolHID* protocol) { + for(size_t bit_index = 0; bit_index < 6; bit_index++) { + if(bit_lib_get_bit(protocol->data, bit_index)) { + return HID_DECODED_BIT_SIZE - bit_index - 1; + } + } + + if(!bit_lib_get_bit(protocol->data, 6)) { + return 37; + } + + size_t bit_index = 7; + uint8_t size = 36; + while(!bit_lib_get_bit(protocol->data, bit_index) && size >= 26) { + size--; + bit_index++; + } + return size < 26 ? HID_PROTOCOL_SIZE_UNKNOWN : size; +} + +bool protocol_hid_generic_decoder_feed(ProtocolHID* protocol, bool level, uint32_t duration) { + bool value; + uint32_t count; + bool result = false; + + fsk_demod_feed(protocol->decoder.fsk_demod, level, duration, &value, &count); + if(count > 0) { + for(size_t i = 0; i < count; i++) { + bit_lib_push_bit(protocol->encoded_data, HID_ENCODED_DATA_SIZE, value); + if(protocol_hid_generic_can_be_decoded(protocol->encoded_data)) { + protocol_hid_generic_decode(protocol->encoded_data, protocol->data); + result = true; + } + } + } + + return result; +}; + +static void protocol_hid_generic_encode(ProtocolHID* protocol) { + protocol->encoded_data[0] = HID_PREAMBLE; + + size_t bit_index = 0; + for(size_t i = 0; i < HID_DECODED_BIT_SIZE; i++) { + bool bit = bit_lib_get_bit(protocol->data, i); + if(bit) { + bit_lib_set_bit(protocol->encoded_data, 8 + bit_index, 1); + bit_lib_set_bit(protocol->encoded_data, 8 + bit_index + 1, 0); + } else { + bit_lib_set_bit(protocol->encoded_data, 8 + bit_index, 0); + bit_lib_set_bit(protocol->encoded_data, 8 + bit_index + 1, 1); + } + bit_index += 2; + } +} + +bool protocol_hid_generic_encoder_start(ProtocolHID* protocol) { + protocol->encoder.encoded_index = 0; + protocol->encoder.pulse = 0; + protocol_hid_generic_encode(protocol); + + return true; +}; + +LevelDuration protocol_hid_generic_encoder_yield(ProtocolHID* protocol) { + bool level = 0; + uint32_t duration = 0; + + // if pulse is zero, we need to output high, otherwise we need to output low + if(protocol->encoder.pulse == 0) { + // get bit + uint8_t bit = bit_lib_get_bit(protocol->encoded_data, protocol->encoder.encoded_index); + + // get pulse from oscillator + bool advance = fsk_osc_next(protocol->encoder.fsk_osc, bit, &duration); + + if(advance) { + bit_lib_increment_index(protocol->encoder.encoded_index, HID_ENCODED_BIT_SIZE); + } + + // duration diveded by 2 because we need to output high and low + duration = duration / 2; + protocol->encoder.pulse = duration; + level = true; + } else { + // output low half and reset pulse + duration = protocol->encoder.pulse; + protocol->encoder.pulse = 0; + level = false; + } + + return level_duration_make(level, duration); +}; + +bool protocol_hid_generic_write_data(ProtocolHID* protocol, void* data) { + LFRFIDWriteRequest* request = (LFRFIDWriteRequest*)data; + bool result = false; + + protocol_hid_generic_encoder_start(protocol); + + if(request->write_type == LFRFIDWriteTypeT5577) { + request->t5577.block[0] = LFRFID_T5577_MODULATION_FSK2a | LFRFID_T5577_BITRATE_RF_50 | + (3 << LFRFID_T5577_MAXBLOCK_SHIFT); + request->t5577.block[1] = bit_lib_get_bits_32(protocol->encoded_data, 0, 32); + request->t5577.block[2] = bit_lib_get_bits_32(protocol->encoded_data, 32, 32); + request->t5577.block[3] = bit_lib_get_bits_32(protocol->encoded_data, 64, 32); + request->t5577.blocks_to_write = 4; + result = true; + } + return result; +}; + +static void protocol_hid_generic_string_cat_protocol_bits(ProtocolHID* protocol, uint8_t protocol_size, string_t result) { + // round up to the nearest nibble + const uint8_t hex_character_count = (protocol_size + 3) / 4; + const uint8_t protocol_bit_index = HID_DECODED_BIT_SIZE - protocol_size; + + for(size_t i = 0; i < hex_character_count; i++) { + uint8_t nibble = + i == 0 ? bit_lib_get_bits( + protocol->data, protocol_bit_index, protocol_size % 4 == 0 ? 4 : protocol_size % 4) : + bit_lib_get_bits(protocol->data, protocol_bit_index + i * 4, 4); + string_cat_printf(result, "%X", nibble & 0xF); + } +} + +void protocol_hid_generic_render_data(ProtocolHID* protocol, string_t result) { + const uint8_t protocol_size = protocol_hid_generic_decode_protocol_size(protocol); + + if(protocol_size == HID_PROTOCOL_SIZE_UNKNOWN) { + string_printf( + result, + "Generic HID Proximity\r\n" + "Data: %02X%02X%02X%02X%02X%X", + protocol->data[0], + protocol->data[1], + protocol->data[2], + protocol->data[3], + protocol->data[4], + protocol->data[5] >> 4); + } else { + string_printf( + result, + "%hhu-bit HID Proximity\r\n" + "Data: ", + protocol_size); + protocol_hid_generic_string_cat_protocol_bits(protocol, protocol_size, result); + } +}; + +const ProtocolBase protocol_hid_generic = { + .name = "HIDProx", + .manufacturer = "Generic", + .data_size = HID_DECODED_DATA_SIZE, + .features = LFRFIDFeatureASK, + .validate_count = 6, + .alloc = (ProtocolAlloc)protocol_hid_generic_alloc, + .free = (ProtocolFree)protocol_hid_generic_free, + .get_data = (ProtocolGetData)protocol_hid_generic_get_data, + .decoder = + { + .start = (ProtocolDecoderStart)protocol_hid_generic_decoder_start, + .feed = (ProtocolDecoderFeed)protocol_hid_generic_decoder_feed, + }, + .encoder = + { + .start = (ProtocolEncoderStart)protocol_hid_generic_encoder_start, + .yield = (ProtocolEncoderYield)protocol_hid_generic_encoder_yield, + }, + .render_data = (ProtocolRenderData)protocol_hid_generic_render_data, + .render_brief_data = (ProtocolRenderData)protocol_hid_generic_render_data, + .write_data = (ProtocolWriteData)protocol_hid_generic_write_data, +}; diff --git a/lib/lfrfid/protocols/protocol_hid_generic.h b/lib/lfrfid/protocols/protocol_hid_generic.h new file mode 100644 index 00000000000..22e78a4d34f --- /dev/null +++ b/lib/lfrfid/protocols/protocol_hid_generic.h @@ -0,0 +1,4 @@ +#pragma once +#include + +extern const ProtocolBase protocol_hid_generic; \ No newline at end of file diff --git a/lib/lfrfid/protocols/protocol_indala26.c b/lib/lfrfid/protocols/protocol_indala26.c new file mode 100644 index 00000000000..136ececf8df --- /dev/null +++ b/lib/lfrfid/protocols/protocol_indala26.c @@ -0,0 +1,353 @@ +#include +#include +#include +#include "lfrfid_protocols.h" + +#define INDALA26_PREAMBLE_BIT_SIZE (33) +#define INDALA26_PREAMBLE_DATA_SIZE (5) + +#define INDALA26_ENCODED_BIT_SIZE (64) +#define INDALA26_ENCODED_DATA_SIZE \ + (((INDALA26_ENCODED_BIT_SIZE) / 8) + INDALA26_PREAMBLE_DATA_SIZE) +#define INDALA26_ENCODED_DATA_LAST ((INDALA26_ENCODED_BIT_SIZE) / 8) + +#define INDALA26_DECODED_BIT_SIZE (28) +#define INDALA26_DECODED_DATA_SIZE (4) + +#define INDALA26_US_PER_BIT (255) +#define INDALA26_ENCODER_PULSES_PER_BIT (16) + +typedef struct { + uint8_t data_index; + uint8_t bit_clock_index; + bool last_bit; + bool current_polarity; + bool pulse_phase; +} ProtocolIndalaEncoder; + +typedef struct { + uint8_t encoded_data[INDALA26_ENCODED_DATA_SIZE]; + uint8_t negative_encoded_data[INDALA26_ENCODED_DATA_SIZE]; + uint8_t corrupted_encoded_data[INDALA26_ENCODED_DATA_SIZE]; + uint8_t corrupted_negative_encoded_data[INDALA26_ENCODED_DATA_SIZE]; + + uint8_t data[INDALA26_DECODED_DATA_SIZE]; + ProtocolIndalaEncoder encoder; +} ProtocolIndala; + +ProtocolIndala* protocol_indala26_alloc(void) { + ProtocolIndala* protocol = malloc(sizeof(ProtocolIndala)); + return protocol; +}; + +void protocol_indala26_free(ProtocolIndala* protocol) { + free(protocol); +}; + +uint8_t* protocol_indala26_get_data(ProtocolIndala* protocol) { + return protocol->data; +}; + +void protocol_indala26_decoder_start(ProtocolIndala* protocol) { + memset(protocol->encoded_data, 0, INDALA26_ENCODED_DATA_SIZE); + memset(protocol->negative_encoded_data, 0, INDALA26_ENCODED_DATA_SIZE); + memset(protocol->corrupted_encoded_data, 0, INDALA26_ENCODED_DATA_SIZE); + memset(protocol->corrupted_negative_encoded_data, 0, INDALA26_ENCODED_DATA_SIZE); +}; + +static bool protocol_indala26_check_preamble(uint8_t* data, size_t bit_index) { + // Preamble 10100000 00000000 00000000 00000000 1 + if(*(uint32_t*)&data[bit_index / 8] != 0b00000000000000000000000010100000) return false; + if(bit_lib_get_bit(data, bit_index + 32) != 1) return false; + return true; +} + +static bool protocol_indala26_can_be_decoded(uint8_t* data) { + if(!protocol_indala26_check_preamble(data, 0)) return false; + if(!protocol_indala26_check_preamble(data, 64)) return false; + if(bit_lib_get_bit(data, 61) != 0) return false; + if(bit_lib_get_bit(data, 60) != 0) return false; + return true; +} + +static bool protocol_indala26_decoder_feed_internal(bool polarity, uint32_t time, uint8_t* data) { + time += (INDALA26_US_PER_BIT / 2); + + size_t bit_count = (time / INDALA26_US_PER_BIT); + bool result = false; + + if(bit_count < INDALA26_ENCODED_BIT_SIZE) { + for(size_t i = 0; i < bit_count; i++) { + bit_lib_push_bit(data, INDALA26_ENCODED_DATA_SIZE, polarity); + if(protocol_indala26_can_be_decoded(data)) { + result = true; + break; + } + } + } + + return result; +} + +static void protocol_indala26_decoder_save(uint8_t* data_to, const uint8_t* data_from) { + bit_lib_copy_bits(data_to, 0, 22, data_from, 33); + bit_lib_copy_bits(data_to, 22, 5, data_from, 55); + bit_lib_copy_bits(data_to, 27, 2, data_from, 62); +} + +bool protocol_indala26_decoder_feed(ProtocolIndala* protocol, bool level, uint32_t duration) { + bool result = false; + + if(duration > (INDALA26_US_PER_BIT / 2)) { + if(protocol_indala26_decoder_feed_internal(level, duration, protocol->encoded_data)) { + protocol_indala26_decoder_save(protocol->data, protocol->encoded_data); + FURI_LOG_D("Indala26", "Positive"); + result = true; + return result; + } + + if(protocol_indala26_decoder_feed_internal( + !level, duration, protocol->negative_encoded_data)) { + protocol_indala26_decoder_save(protocol->data, protocol->negative_encoded_data); + FURI_LOG_D("Indala26", "Negative"); + result = true; + return result; + } + } + + if(duration > (INDALA26_US_PER_BIT / 4)) { + // Try to decode wrong phase synced data + if(level) { + duration += 120; + } else { + if(duration > 120) { + duration -= 120; + } + } + + if(protocol_indala26_decoder_feed_internal( + level, duration, protocol->corrupted_encoded_data)) { + protocol_indala26_decoder_save(protocol->data, protocol->corrupted_encoded_data); + FURI_LOG_D("Indala26", "Positive Corrupted"); + + result = true; + return result; + } + + if(protocol_indala26_decoder_feed_internal( + !level, duration, protocol->corrupted_negative_encoded_data)) { + protocol_indala26_decoder_save( + protocol->data, protocol->corrupted_negative_encoded_data); + FURI_LOG_D("Indala26", "Negative Corrupted"); + + result = true; + return result; + } + } + + return result; +}; + +bool protocol_indala26_encoder_start(ProtocolIndala* protocol) { + memset(protocol->encoded_data, 0, INDALA26_ENCODED_DATA_SIZE); + *(uint32_t*)&protocol->encoded_data[0] = 0b00000000000000000000000010100000; + bit_lib_set_bit(protocol->encoded_data, 32, 1); + bit_lib_copy_bits(protocol->encoded_data, 33, 22, protocol->data, 0); + bit_lib_copy_bits(protocol->encoded_data, 55, 5, protocol->data, 22); + bit_lib_copy_bits(protocol->encoded_data, 62, 2, protocol->data, 27); + + protocol->encoder.last_bit = + bit_lib_get_bit(protocol->encoded_data, INDALA26_ENCODED_BIT_SIZE - 1); + protocol->encoder.data_index = 0; + protocol->encoder.current_polarity = true; + protocol->encoder.pulse_phase = true; + protocol->encoder.bit_clock_index = 0; + + return true; +}; + +LevelDuration protocol_indala26_encoder_yield(ProtocolIndala* protocol) { + LevelDuration level_duration; + ProtocolIndalaEncoder* encoder = &protocol->encoder; + + if(encoder->pulse_phase) { + level_duration = level_duration_make(encoder->current_polarity, 1); + encoder->pulse_phase = false; + } else { + level_duration = level_duration_make(!encoder->current_polarity, 1); + encoder->pulse_phase = true; + + encoder->bit_clock_index++; + if(encoder->bit_clock_index >= INDALA26_ENCODER_PULSES_PER_BIT) { + encoder->bit_clock_index = 0; + + bool current_bit = bit_lib_get_bit(protocol->encoded_data, encoder->data_index); + + if(current_bit != encoder->last_bit) { + encoder->current_polarity = !encoder->current_polarity; + } + + encoder->last_bit = current_bit; + + bit_lib_increment_index(encoder->data_index, INDALA26_ENCODED_BIT_SIZE); + } + } + + return level_duration; +}; + +// factory code +static uint8_t get_fc(const uint8_t* data) { + uint8_t fc = 0; + + fc = fc << 1 | bit_lib_get_bit(data, 24); + fc = fc << 1 | bit_lib_get_bit(data, 16); + fc = fc << 1 | bit_lib_get_bit(data, 11); + fc = fc << 1 | bit_lib_get_bit(data, 14); + fc = fc << 1 | bit_lib_get_bit(data, 15); + fc = fc << 1 | bit_lib_get_bit(data, 20); + fc = fc << 1 | bit_lib_get_bit(data, 6); + fc = fc << 1 | bit_lib_get_bit(data, 25); + + return fc; +} + +// card number +static uint16_t get_cn(const uint8_t* data) { + uint16_t cn = 0; + + cn = cn << 1 | bit_lib_get_bit(data, 9); + cn = cn << 1 | bit_lib_get_bit(data, 12); + cn = cn << 1 | bit_lib_get_bit(data, 10); + cn = cn << 1 | bit_lib_get_bit(data, 7); + cn = cn << 1 | bit_lib_get_bit(data, 19); + cn = cn << 1 | bit_lib_get_bit(data, 3); + cn = cn << 1 | bit_lib_get_bit(data, 2); + cn = cn << 1 | bit_lib_get_bit(data, 18); + cn = cn << 1 | bit_lib_get_bit(data, 13); + cn = cn << 1 | bit_lib_get_bit(data, 0); + cn = cn << 1 | bit_lib_get_bit(data, 4); + cn = cn << 1 | bit_lib_get_bit(data, 21); + cn = cn << 1 | bit_lib_get_bit(data, 23); + cn = cn << 1 | bit_lib_get_bit(data, 26); + cn = cn << 1 | bit_lib_get_bit(data, 17); + cn = cn << 1 | bit_lib_get_bit(data, 8); + + return cn; +} + +void protocol_indala26_render_data_internal(ProtocolIndala* protocol, string_t result, bool brief) { + bool wiegand_correct = true; + bool checksum_correct = true; + + const uint8_t fc = get_fc(protocol->data); + const uint16_t card = get_cn(protocol->data); + const uint32_t fc_and_card = fc << 16 | card; + const uint8_t checksum = bit_lib_get_bit(protocol->data, 27) << 1 | + bit_lib_get_bit(protocol->data, 28); + const bool even_parity = bit_lib_get_bit(protocol->data, 1); + const bool odd_parity = bit_lib_get_bit(protocol->data, 5); + + // indala checksum + uint8_t checksum_sum = 0; + checksum_sum += ((fc_and_card >> 14) & 1); + checksum_sum += ((fc_and_card >> 12) & 1); + checksum_sum += ((fc_and_card >> 9) & 1); + checksum_sum += ((fc_and_card >> 8) & 1); + checksum_sum += ((fc_and_card >> 6) & 1); + checksum_sum += ((fc_and_card >> 5) & 1); + checksum_sum += ((fc_and_card >> 2) & 1); + checksum_sum += ((fc_and_card >> 0) & 1); + checksum_sum = checksum_sum & 0b1; + + if(checksum_sum == 1 && checksum == 0b01) { + } else if(checksum_sum == 0 && checksum == 0b10) { + } else { + checksum_correct = false; + } + + // wiegand parity + uint8_t even_parity_sum = 0; + for(int8_t i = 12; i < 24; i++) { + if(((fc_and_card >> i) & 1) == 1) { + even_parity_sum++; + } + } + if(even_parity_sum % 2 != even_parity) wiegand_correct = false; + + uint8_t odd_parity_sum = 1; + for(int8_t i = 0; i < 12; i++) { + if(((fc_and_card >> i) & 1) == 1) { + odd_parity_sum++; + } + } + if(odd_parity_sum % 2 != odd_parity) wiegand_correct = false; + + if(brief) { + string_printf( + result, + "FC: %u\r\nCard: %u, Parity:%s%s", + fc, + card, + (checksum_correct ? "+" : "-"), + (wiegand_correct ? "+" : "-")); + } else { + string_printf( + result, + "FC: %u\r\n" + "Card: %u\r\n" + "Checksum: %s\r\n" + "W26 Parity: %s", + fc, + card, + (checksum_correct ? "+" : "-"), + (wiegand_correct ? "+" : "-")); + } +} +void protocol_indala26_render_data(ProtocolIndala* protocol, string_t result) { + protocol_indala26_render_data_internal(protocol, result, false); +} +void protocol_indala26_render_brief_data(ProtocolIndala* protocol, string_t result) { + protocol_indala26_render_data_internal(protocol, result, true); +} + +bool protocol_indala26_write_data(ProtocolIndala* protocol, void* data) { + LFRFIDWriteRequest* request = (LFRFIDWriteRequest*)data; + bool result = false; + + protocol_indala26_encoder_start(protocol); + + if(request->write_type == LFRFIDWriteTypeT5577) { + request->t5577.block[0] = LFRFID_T5577_BITRATE_RF_32 | LFRFID_T5577_MODULATION_PSK1 | + (2 << LFRFID_T5577_MAXBLOCK_SHIFT); + request->t5577.block[1] = bit_lib_get_bits_32(protocol->encoded_data, 0, 32); + request->t5577.block[2] = bit_lib_get_bits_32(protocol->encoded_data, 32, 32); + request->t5577.blocks_to_write = 3; + result = true; + } + return result; +}; + +const ProtocolBase protocol_indala26 = { + .name = "Indala26", + .manufacturer = "Motorola", + .data_size = INDALA26_DECODED_DATA_SIZE, + .features = LFRFIDFeaturePSK, + .validate_count = 6, + .alloc = (ProtocolAlloc)protocol_indala26_alloc, + .free = (ProtocolFree)protocol_indala26_free, + .get_data = (ProtocolGetData)protocol_indala26_get_data, + .decoder = + { + .start = (ProtocolDecoderStart)protocol_indala26_decoder_start, + .feed = (ProtocolDecoderFeed)protocol_indala26_decoder_feed, + }, + .encoder = + { + .start = (ProtocolEncoderStart)protocol_indala26_encoder_start, + .yield = (ProtocolEncoderYield)protocol_indala26_encoder_yield, + }, + .render_data = (ProtocolRenderData)protocol_indala26_render_data, + .render_brief_data = (ProtocolRenderData)protocol_indala26_render_brief_data, + .write_data = (ProtocolWriteData)protocol_indala26_write_data, +}; \ No newline at end of file diff --git a/lib/lfrfid/protocols/protocol_indala26.h b/lib/lfrfid/protocols/protocol_indala26.h new file mode 100644 index 00000000000..c0c61784b7f --- /dev/null +++ b/lib/lfrfid/protocols/protocol_indala26.h @@ -0,0 +1,4 @@ +#pragma once +#include + +extern const ProtocolBase protocol_indala26; diff --git a/lib/lfrfid/protocols/protocol_io_prox_xsf.c b/lib/lfrfid/protocols/protocol_io_prox_xsf.c new file mode 100644 index 00000000000..66b1610bf29 --- /dev/null +++ b/lib/lfrfid/protocols/protocol_io_prox_xsf.c @@ -0,0 +1,297 @@ +#include +#include +#include +#include +#include +#include "lfrfid_protocols.h" + +#define JITTER_TIME (20) +#define MIN_TIME (64 - JITTER_TIME) +#define MAX_TIME (80 + JITTER_TIME) + +#define IOPROXXSF_DECODED_DATA_SIZE (4) +#define IOPROXXSF_ENCODED_DATA_SIZE (8) + +#define IOPROXXSF_BIT_SIZE (8) +#define IOPROXXSF_BIT_MAX_SIZE (IOPROXXSF_BIT_SIZE * IOPROXXSF_ENCODED_DATA_SIZE) + +typedef struct { + FSKDemod* fsk_demod; +} ProtocolIOProxXSFDecoder; + +typedef struct { + FSKOsc* fsk_osc; + uint8_t encoded_index; +} ProtocolIOProxXSFEncoder; + +typedef struct { + ProtocolIOProxXSFEncoder encoder; + ProtocolIOProxXSFDecoder decoder; + uint8_t encoded_data[IOPROXXSF_ENCODED_DATA_SIZE]; + uint8_t data[IOPROXXSF_DECODED_DATA_SIZE]; +} ProtocolIOProxXSF; + +ProtocolIOProxXSF* protocol_io_prox_xsf_alloc(void) { + ProtocolIOProxXSF* protocol = malloc(sizeof(ProtocolIOProxXSF)); + protocol->decoder.fsk_demod = fsk_demod_alloc(MIN_TIME, 8, MAX_TIME, 6); + protocol->encoder.fsk_osc = fsk_osc_alloc(8, 10, 64); + return protocol; +}; + +void protocol_io_prox_xsf_free(ProtocolIOProxXSF* protocol) { + fsk_demod_free(protocol->decoder.fsk_demod); + fsk_osc_free(protocol->encoder.fsk_osc); + free(protocol); +}; + +uint8_t* protocol_io_prox_xsf_get_data(ProtocolIOProxXSF* protocol) { + return protocol->data; +}; + +void protocol_io_prox_xsf_decoder_start(ProtocolIOProxXSF* protocol) { + memset(protocol->encoded_data, 0, IOPROXXSF_ENCODED_DATA_SIZE); +}; + +static uint8_t protocol_io_prox_xsf_compute_checksum(const uint8_t* data) { + // 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; + + for(size_t i = 1; i <= 5; i++) { + checksum += bit_lib_get_bits(data, 9 * i, 8); + } + + return 0xFF - checksum; +} + +static bool protocol_io_prox_xsf_can_be_decoded(const uint8_t* encoded_data) { + // 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(bit_lib_bit_is_not_set(encoded_data[2], 6)) { + return false; + } + if(bit_lib_bit_is_not_set(encoded_data[3], 5)) { + return false; + } + if(bit_lib_bit_is_not_set(encoded_data[4], 4)) { + return false; + } + if(bit_lib_bit_is_not_set(encoded_data[5], 3)) { + return false; + } + if(bit_lib_bit_is_not_set(encoded_data[6], 2)) { + return false; + } + if(bit_lib_bit_is_not_set(encoded_data[7], 1)) { + return false; + } + if(bit_lib_bit_is_not_set(encoded_data[7], 0)) { + return false; + } + + // ... and validate our checksums. + uint8_t checksum = protocol_io_prox_xsf_compute_checksum(encoded_data); + uint8_t checkval = bit_lib_get_bits(encoded_data, 54, 8); + + if(checksum != checkval) { + return false; + } + + return true; +} + +void protocol_io_prox_xsf_decode(const uint8_t* encoded_data, uint8_t* decoded_data) { + // 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] = bit_lib_get_bits(encoded_data, 18, 8); + + // Version code. + decoded_data[1] = bit_lib_get_bits(encoded_data, 27, 8); + + // Code bytes. + decoded_data[2] = bit_lib_get_bits(encoded_data, 36, 8); + decoded_data[3] = bit_lib_get_bits(encoded_data, 45, 8); +} + +bool protocol_io_prox_xsf_decoder_feed(ProtocolIOProxXSF* protocol, bool level, uint32_t duration) { + bool result = false; + + uint32_t count; + bool value; + + fsk_demod_feed(protocol->decoder.fsk_demod, level, duration, &value, &count); + for(size_t i = 0; i < count; i++) { + bit_lib_push_bit(protocol->encoded_data, IOPROXXSF_ENCODED_DATA_SIZE, value); + if(protocol_io_prox_xsf_can_be_decoded(protocol->encoded_data)) { + protocol_io_prox_xsf_decode(protocol->encoded_data, protocol->data); + result = true; + break; + } + } + + return result; +}; + +static void protocol_io_prox_xsf_encode(const uint8_t* decoded_data, uint8_t* encoded_data) { + // 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. + bit_lib_set_bits(encoded_data, 0, 0b00000000, 8); + bit_lib_set_bit(encoded_data, 8, 0); + + bit_lib_set_bits(encoded_data, 9, 0b11110000, 8); + bit_lib_set_bit(encoded_data, 17, 1); + + // Facility code. + bit_lib_set_bits(encoded_data, 18, decoded_data[0], 8); + bit_lib_set_bit(encoded_data, 26, 1); + + // Version + bit_lib_set_bits(encoded_data, 27, decoded_data[1], 8); + bit_lib_set_bit(encoded_data, 35, 1); + + // Code one + bit_lib_set_bits(encoded_data, 36, decoded_data[2], 8); + bit_lib_set_bit(encoded_data, 44, 1); + + // Code two + bit_lib_set_bits(encoded_data, 45, decoded_data[3], 8); + bit_lib_set_bit(encoded_data, 53, 1); + + // Checksum + bit_lib_set_bits(encoded_data, 54, protocol_io_prox_xsf_compute_checksum(encoded_data), 8); + bit_lib_set_bit(encoded_data, 62, 1); + bit_lib_set_bit(encoded_data, 63, 1); +} + +bool protocol_io_prox_xsf_encoder_start(ProtocolIOProxXSF* protocol) { + protocol_io_prox_xsf_encode(protocol->data, protocol->encoded_data); + protocol->encoder.encoded_index = 0; + fsk_osc_reset(protocol->encoder.fsk_osc); + return true; +}; + +LevelDuration protocol_io_prox_xsf_encoder_yield(ProtocolIOProxXSF* protocol) { + bool level; + uint32_t duration; + + bool bit = bit_lib_get_bit(protocol->encoded_data, protocol->encoder.encoded_index); + bool advance = fsk_osc_next_half(protocol->encoder.fsk_osc, bit, &level, &duration); + + if(advance) { + bit_lib_increment_index(protocol->encoder.encoded_index, IOPROXXSF_BIT_MAX_SIZE); + } + return level_duration_make(level, duration); +}; + +void protocol_io_prox_xsf_render_data(ProtocolIOProxXSF* protocol, string_t result) { + uint8_t* data = protocol->data; + string_printf( + result, + "FC: %u\r\n" + "VС: %u\r\n" + "Card: %u", + data[0], + data[1], + (uint16_t)((data[2] << 8) | (data[3]))); +} + +void protocol_io_prox_xsf_render_brief_data(ProtocolIOProxXSF* protocol, string_t result) { + uint8_t* data = protocol->data; + string_printf( + result, + "FC: %u, VС: %u\r\n" + "Card: %u", + data[0], + data[1], + (uint16_t)((data[2] << 8) | (data[3]))); +} + +bool protocol_io_prox_xsf_write_data(ProtocolIOProxXSF* protocol, void* data) { + LFRFIDWriteRequest* request = (LFRFIDWriteRequest*)data; + bool result = false; + + protocol_io_prox_xsf_encode(protocol->data, protocol->encoded_data); + + if(request->write_type == LFRFIDWriteTypeT5577) { + request->t5577.block[0] = LFRFID_T5577_MODULATION_FSK2a | LFRFID_T5577_BITRATE_RF_64 | + (2 << LFRFID_T5577_MAXBLOCK_SHIFT); + request->t5577.block[1] = bit_lib_get_bits_32(protocol->encoded_data, 0, 32); + request->t5577.block[2] = bit_lib_get_bits_32(protocol->encoded_data, 32, 32); + request->t5577.blocks_to_write = 3; + result = true; + } + return result; +}; + +const ProtocolBase protocol_io_prox_xsf = { + .name = "IoProxXSF", + .manufacturer = "Kantech", + .data_size = IOPROXXSF_DECODED_DATA_SIZE, + .features = LFRFIDFeatureASK, + .validate_count = 3, + .alloc = (ProtocolAlloc)protocol_io_prox_xsf_alloc, + .free = (ProtocolFree)protocol_io_prox_xsf_free, + .get_data = (ProtocolGetData)protocol_io_prox_xsf_get_data, + .decoder = + { + .start = (ProtocolDecoderStart)protocol_io_prox_xsf_decoder_start, + .feed = (ProtocolDecoderFeed)protocol_io_prox_xsf_decoder_feed, + }, + .encoder = + { + .start = (ProtocolEncoderStart)protocol_io_prox_xsf_encoder_start, + .yield = (ProtocolEncoderYield)protocol_io_prox_xsf_encoder_yield, + }, + .render_data = (ProtocolRenderData)protocol_io_prox_xsf_render_data, + .render_brief_data = (ProtocolRenderData)protocol_io_prox_xsf_render_brief_data, + .write_data = (ProtocolWriteData)protocol_io_prox_xsf_write_data, +}; \ No newline at end of file diff --git a/lib/lfrfid/protocols/protocol_io_prox_xsf.h b/lib/lfrfid/protocols/protocol_io_prox_xsf.h new file mode 100644 index 00000000000..af94fb4636c --- /dev/null +++ b/lib/lfrfid/protocols/protocol_io_prox_xsf.h @@ -0,0 +1,4 @@ +#pragma once +#include + +extern const ProtocolBase protocol_io_prox_xsf; diff --git a/lib/lfrfid/tools/bit_lib.c b/lib/lfrfid/tools/bit_lib.c new file mode 100644 index 00000000000..b57bda8a8ae --- /dev/null +++ b/lib/lfrfid/tools/bit_lib.c @@ -0,0 +1,291 @@ +#include "bit_lib.h" +#include +#include + +void bit_lib_push_bit(uint8_t* data, size_t data_size, bool bit) { + size_t last_index = data_size - 1; + + for(size_t i = 0; i < last_index; ++i) { + data[i] = (data[i] << 1) | ((data[i + 1] >> 7) & 1); + } + data[last_index] = (data[last_index] << 1) | bit; +} + +void bit_lib_set_bit(uint8_t* data, size_t position, bool bit) { + if(bit) { + data[position / 8] |= 1UL << (7 - (position % 8)); + } else { + data[position / 8] &= ~(1UL << (7 - (position % 8))); + } +} + +void bit_lib_set_bits(uint8_t* data, size_t position, uint8_t byte, uint8_t length) { + furi_check(length <= 8); + furi_check(length > 0); + + for(uint8_t i = 0; i < length; ++i) { + uint8_t shift = (length - 1) - i; + bit_lib_set_bit(data, position + i, (byte >> shift) & 1); + } +} + +bool bit_lib_get_bit(const uint8_t* data, size_t position) { + return (data[position / 8] >> (7 - (position % 8))) & 1; +} + +uint8_t bit_lib_get_bits(const uint8_t* data, size_t position, uint8_t length) { + uint8_t shift = position % 8; + if(shift == 0) { + return data[position / 8] >> (8 - length); + } else { + // TODO fix read out of bounds + uint8_t value = (data[position / 8] << (shift)); + value |= data[position / 8 + 1] >> (8 - shift); + value = value >> (8 - length); + return value; + } +} + +uint16_t bit_lib_get_bits_16(const uint8_t* data, size_t position, uint8_t length) { + uint16_t value = 0; + if(length <= 8) { + value = bit_lib_get_bits(data, position, length); + } else { + value = bit_lib_get_bits(data, position, 8) << (length - 8); + value |= bit_lib_get_bits(data, position + 8, length - 8); + } + return value; +} + +uint32_t bit_lib_get_bits_32(const uint8_t* data, size_t position, uint8_t length) { + uint32_t value = 0; + if(length <= 8) { + value = bit_lib_get_bits(data, position, length); + } else if(length <= 16) { + value = bit_lib_get_bits(data, position, 8) << (length - 8); + value |= bit_lib_get_bits(data, position + 8, length - 8); + } else if(length <= 24) { + value = bit_lib_get_bits(data, position, 8) << (length - 8); + value |= bit_lib_get_bits(data, position + 8, 8) << (length - 16); + value |= bit_lib_get_bits(data, position + 16, length - 16); + } else { + value = bit_lib_get_bits(data, position, 8) << (length - 8); + value |= bit_lib_get_bits(data, position + 8, 8) << (length - 16); + value |= bit_lib_get_bits(data, position + 16, 8) << (length - 24); + value |= bit_lib_get_bits(data, position + 24, length - 24); + } + + return value; +} + +bool bit_lib_test_parity_32(uint32_t bits, BitLibParity parity) { +#if !defined __GNUC__ +#error Please, implement parity test for non-GCC compilers +#else + switch(parity) { + case BitLibParityEven: + return __builtin_parity(bits); + case BitLibParityOdd: + return !__builtin_parity(bits); + default: + furi_crash("Unknown parity"); + } +#endif +} + +bool bit_lib_test_parity( + const uint8_t* bits, + size_t position, + uint8_t length, + BitLibParity parity, + uint8_t parity_length) { + uint32_t parity_block; + bool result = true; + const size_t parity_blocks_count = length / parity_length; + + for(size_t i = 0; i < parity_blocks_count; ++i) { + switch(parity) { + case BitLibParityEven: + case BitLibParityOdd: + parity_block = bit_lib_get_bits_32(bits, position + i * parity_length, parity_length); + if(!bit_lib_test_parity_32(parity_block, parity)) { + result = false; + } + break; + case BitLibParityAlways0: + if(bit_lib_get_bit(bits, position + i * parity_length + parity_length - 1)) { + result = false; + } + break; + case BitLibParityAlways1: + if(!bit_lib_get_bit(bits, position + i * parity_length + parity_length - 1)) { + result = false; + } + break; + } + + if(!result) break; + } + return result; +} + +size_t bit_lib_remove_bit_every_nth(uint8_t* data, size_t position, uint8_t length, uint8_t n) { + size_t counter = 0; + size_t result_counter = 0; + uint8_t bit_buffer = 0; + uint8_t bit_counter = 0; + + while(counter < length) { + if((counter + 1) % n != 0) { + bit_buffer = (bit_buffer << 1) | bit_lib_get_bit(data, position + counter); + bit_counter++; + } + + if(bit_counter == 8) { + bit_lib_set_bits(data, position + result_counter, bit_buffer, 8); + bit_counter = 0; + bit_buffer = 0; + result_counter += 8; + } + counter++; + } + + if(bit_counter != 0) { + bit_lib_set_bits(data, position + result_counter, bit_buffer, bit_counter); + result_counter += bit_counter; + } + return result_counter; +} + +void bit_lib_copy_bits( + uint8_t* data, + size_t position, + size_t length, + const uint8_t* source, + size_t source_position) { + for(size_t i = 0; i < length; ++i) { + bit_lib_set_bit(data, position + i, bit_lib_get_bit(source, source_position + i)); + } +} + +void bit_lib_reverse_bits(uint8_t* data, size_t position, uint8_t length) { + size_t i = 0; + size_t j = length - 1; + + while(i < j) { + bool tmp = bit_lib_get_bit(data, position + i); + bit_lib_set_bit(data, position + i, bit_lib_get_bit(data, position + j)); + bit_lib_set_bit(data, position + j, tmp); + i++; + j--; + } +} + +uint8_t bit_lib_get_bit_count(uint32_t data) { +#if defined __GNUC__ + return __builtin_popcountl(data); +#else +#error Please, implement popcount for non-GCC compilers +#endif +} + +void bit_lib_print_bits(const uint8_t* data, size_t length) { + for(size_t i = 0; i < length; ++i) { + printf("%u", bit_lib_get_bit(data, i)); + } +} + +void bit_lib_print_regions( + const BitLibRegion* regions, + size_t region_count, + const uint8_t* data, + size_t length) { + // print data + bit_lib_print_bits(data, length); + printf("\r\n"); + + // print regions + for(size_t c = 0; c < length; ++c) { + bool print = false; + + for(size_t i = 0; i < region_count; i++) { + if(regions[i].start <= c && c < regions[i].start + regions[i].length) { + print = true; + printf("%c", regions[i].mark); + break; + } + } + + if(!print) { + printf(" "); + } + } + printf("\r\n"); + + // print regions data + for(size_t c = 0; c < length; ++c) { + bool print = false; + + for(size_t i = 0; i < region_count; i++) { + if(regions[i].start <= c && c < regions[i].start + regions[i].length) { + print = true; + printf("%u", bit_lib_get_bit(data, c)); + break; + } + } + + if(!print) { + printf(" "); + } + } + printf("\r\n"); +} + +uint16_t bit_lib_reverse_16_fast(uint16_t data) { + uint16_t result = 0; + result |= (data & 0x8000) >> 15; + result |= (data & 0x4000) >> 13; + result |= (data & 0x2000) >> 11; + result |= (data & 0x1000) >> 9; + result |= (data & 0x0800) >> 7; + result |= (data & 0x0400) >> 5; + result |= (data & 0x0200) >> 3; + result |= (data & 0x0100) >> 1; + result |= (data & 0x0080) << 1; + result |= (data & 0x0040) << 3; + result |= (data & 0x0020) << 5; + result |= (data & 0x0010) << 7; + result |= (data & 0x0008) << 9; + result |= (data & 0x0004) << 11; + result |= (data & 0x0002) << 13; + result |= (data & 0x0001) << 15; + return result; +} + +uint16_t bit_lib_crc16( + uint8_t const* data, + size_t data_size, + uint16_t polynom, + uint16_t init, + bool ref_in, + bool ref_out, + uint16_t xor_out) { + uint16_t crc = init; + + for(size_t i = 0; i < data_size; ++i) { + uint8_t byte = data[i]; + if(ref_in) byte = bit_lib_reverse_16_fast(byte) >> 8; + + for(size_t j = 0; j < 8; ++j) { + bool c15 = (crc >> 15 & 1); + bool bit = (byte >> (7 - j) & 1); + crc <<= 1; + if(c15 ^ bit) crc ^= polynom; + } + } + + if(ref_out) crc = bit_lib_reverse_16_fast(crc); + crc ^= xor_out; + + return crc; +} \ No newline at end of file diff --git a/lib/lfrfid/tools/bit_lib.h b/lib/lfrfid/tools/bit_lib.h new file mode 100644 index 00000000000..24aae0e9cc8 --- /dev/null +++ b/lib/lfrfid/tools/bit_lib.h @@ -0,0 +1,220 @@ +#pragma once +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + BitLibParityEven, + BitLibParityOdd, + BitLibParityAlways0, + BitLibParityAlways1, +} BitLibParity; + +/** @brief Increment and wrap around a value. + * @param index value to increment + * @param length wrap-around range + */ +#define bit_lib_increment_index(index, length) (index = (((index) + 1) % (length))) + +/** @brief Test if a bit is set. + * @param data value to test + * @param index bit index to test + */ +#define bit_lib_bit_is_set(data, index) ((data & (1 << (index))) != 0) + +/** @brief Test if a bit is not set. + * @param data value to test + * @param index bit index to test + */ +#define bit_lib_bit_is_not_set(data, index) ((data & (1 << (index))) == 0) + +/** @brief Push a bit into a byte array. + * @param data array to push bit into + * @param data_size array size + * @param bit bit to push + */ +void bit_lib_push_bit(uint8_t* data, size_t data_size, bool bit); + +/** @brief Set a bit in a byte array. + * @param data array to set bit in + * @param position The position of the bit to set. + * @param bit bit value to set + */ +void bit_lib_set_bit(uint8_t* data, size_t position, bool bit); + +/** @brief Set the bit at the given position to the given value. + * @param data The data to set the bit in. + * @param position The position of the bit to set. + * @param byte The data to set the bit to. + * @param length The length of the data. + */ +void bit_lib_set_bits(uint8_t* data, size_t position, uint8_t byte, uint8_t length); + +/** @brief Get the bit of a byte. + * @param data The byte to get the bits from. + * @param position The position of the bit. + * @return The bit. + */ +bool bit_lib_get_bit(const uint8_t* data, size_t position); + +/** + * @brief Get the bits of a data, as uint8_t. + * @param data The data to get the bits from. + * @param position The position of the first bit. + * @param length The length of the bits. + * @return The bits. + */ +uint8_t bit_lib_get_bits(const uint8_t* data, size_t position, uint8_t length); + +/** + * @brief Get the bits of a data, as uint16_t. + * @param data The data to get the bits from. + * @param position The position of the first bit. + * @param length The length of the bits. + * @return The bits. + */ +uint16_t bit_lib_get_bits_16(const uint8_t* data, size_t position, uint8_t length); + +/** + * @brief Get the bits of a data, as uint32_t. + * @param data The data to get the bits from. + * @param position The position of the first bit. + * @param length The length of the bits. + * @return The bits. + */ +uint32_t bit_lib_get_bits_32(const uint8_t* data, size_t position, uint8_t length); + +/** + * @brief Test parity of given bits + * @param bits Bits to test parity of + * @param parity Parity to test against + * @return true if parity is correct, false otherwise + */ +bool bit_lib_test_parity_32(uint32_t bits, BitLibParity parity); + +/** + * @brief Test parity of bit array, check parity for every parity_length block from start + * + * @param data Bit array + * @param position Start position + * @param length Bit count + * @param parity Parity to test against + * @param parity_length Parity block length + * @return true + * @return false + */ +bool bit_lib_test_parity( + const uint8_t* data, + size_t position, + uint8_t length, + BitLibParity parity, + uint8_t parity_length); + +/** + * @brief Remove bit every n in array and shift array left. Useful to remove parity. + * + * @param data Bit array + * @param position Start position + * @param length Bit count + * @param n every n bit will be removed + * @return size_t + */ +size_t bit_lib_remove_bit_every_nth(uint8_t* data, size_t position, uint8_t length, uint8_t n); + +/** + * @brief Copy bits from source to destination. + * + * @param data destination array + * @param position position in destination array + * @param length length of bits to copy + * @param source source array + * @param source_position position in source array + */ +void bit_lib_copy_bits( + uint8_t* data, + size_t position, + size_t length, + const uint8_t* source, + size_t source_position); + +/** + * @brief Reverse bits in bit array + * + * @param data Bit array + * @param position start position + * @param length length of bits to reverse + */ +void bit_lib_reverse_bits(uint8_t* data, size_t position, uint8_t length); + +/** + * @brief Count 1 bits in data + * + * @param data + * @return uint8_t set bit count + */ +uint8_t bit_lib_get_bit_count(uint32_t data); + +/** + * @brief Print data as bit array + * + * @param data + * @param length + */ +void bit_lib_print_bits(const uint8_t* data, size_t length); + +typedef struct { + const char mark; + const size_t start; + const size_t length; +} BitLibRegion; + +/** + * @brief Print data as bit array and mark regions. Regions needs to be sorted by start position. + * + * @param regions + * @param region_count + * @param data + * @param length + */ +void bit_lib_print_regions( + const BitLibRegion* regions, + size_t region_count, + const uint8_t* data, + size_t length); + +/** + * @brief Reverse bits in uint16_t, faster than generic bit_lib_reverse_bits. + * + * @param data + * @return uint16_t + */ +uint16_t bit_lib_reverse_16_fast(uint16_t data); + +/** + * @brief Slow, but generic CRC16 implementation + * + * @param data + * @param data_size + * @param polynom CRC polynom + * @param init init value + * @param ref_in true if the right bit is older + * @param ref_out true to reverse output + * @param xor_out xor output with this value + * @return uint16_t + */ +uint16_t bit_lib_crc16( + uint8_t const* data, + size_t data_size, + uint16_t polynom, + uint16_t init, + bool ref_in, + bool ref_out, + uint16_t xor_out); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/lfrfid/tools/fsk_demod.c b/lib/lfrfid/tools/fsk_demod.c new file mode 100644 index 00000000000..a5155d88e51 --- /dev/null +++ b/lib/lfrfid/tools/fsk_demod.c @@ -0,0 +1,93 @@ +#include +#include "fsk_demod.h" + +struct FSKDemod { + uint32_t low_time; + uint32_t low_pulses; + uint32_t hi_time; + uint32_t hi_pulses; + + bool invert; + uint32_t mid_time; + uint32_t time; + uint32_t count; + bool last_pulse; +}; + +FSKDemod* + fsk_demod_alloc(uint32_t low_time, uint32_t low_pulses, uint32_t hi_time, uint32_t hi_pulses) { + FSKDemod* demod = malloc(sizeof(FSKDemod)); + demod->invert = false; + + if(low_time > hi_time) { + uint32_t tmp; + tmp = hi_time; + hi_time = low_time; + low_time = tmp; + + tmp = hi_pulses; + hi_pulses = low_pulses; + low_pulses = tmp; + + demod->invert = true; + } + + demod->low_time = low_time; + demod->low_pulses = low_pulses; + demod->hi_time = hi_time; + demod->hi_pulses = hi_pulses; + + demod->mid_time = (hi_time - low_time) / 2 + low_time; + demod->time = 0; + demod->count = 0; + demod->last_pulse = false; + + return demod; +} + +void fsk_demod_free(FSKDemod* demod) { + free(demod); +} + +void fsk_demod_feed(FSKDemod* demod, bool polarity, uint32_t time, bool* value, uint32_t* count) { + *count = 0; + + if(polarity) { + // accumulate time + demod->time = time; + } else { + demod->time += time; + + // check for valid pulse + if(demod->time >= demod->low_time && demod->time < demod->hi_time) { + bool pulse; + + if(demod->time < demod->mid_time) { + pulse = false; + } else { + pulse = true; + } + + demod->count++; + + // check for edge transition + if(demod->last_pulse != pulse) { + uint32_t data_count = demod->count + 1; + + if(demod->last_pulse) { + data_count /= demod->hi_pulses; + *value = !demod->invert; + } else { + data_count /= demod->low_pulses; + *value = demod->invert; + } + + *count = data_count; + demod->count = 0; + demod->last_pulse = pulse; + } + } else { + demod->count = 0; + } + } +} diff --git a/lib/lfrfid/tools/fsk_demod.h b/lib/lfrfid/tools/fsk_demod.h new file mode 100644 index 00000000000..d816b0dac1e --- /dev/null +++ b/lib/lfrfid/tools/fsk_demod.h @@ -0,0 +1,44 @@ +#pragma once +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct FSKDemod FSKDemod; + +/** + * @brief Allocate a new FSKDemod instance + * FSKDemod is a demodulator that can decode FSK encoded data + * + * @param low_time time between rising edges for the 0 bit + * @param low_pulses rising edges count for the 0 bit + * @param hi_time time between rising edges for the 1 bit + * @param hi_pulses rising edges count for the 1 bit + * @return FSKDemod* + */ +FSKDemod* + fsk_demod_alloc(uint32_t low_time, uint32_t low_pulses, uint32_t hi_time, uint32_t hi_pulses); + +/** + * @brief Free a FSKDemod instance + * + * @param fsk_demod + */ +void fsk_demod_free(FSKDemod* fsk_demod); + +/** + * @brief Feed sample to demodulator + * + * @param demod FSKDemod instance + * @param polarity sample polarity + * @param time sample time + * @param value demodulated bit value + * @param count demodulated bit count + */ +void fsk_demod_feed(FSKDemod* demod, bool polarity, uint32_t time, bool* value, uint32_t* count); + +#ifdef __cplusplus +} +#endif diff --git a/lib/lfrfid/tools/fsk_ocs.c b/lib/lfrfid/tools/fsk_ocs.c new file mode 100644 index 00000000000..1fd46cf1793 --- /dev/null +++ b/lib/lfrfid/tools/fsk_ocs.c @@ -0,0 +1,62 @@ +#include "fsk_osc.h" +#include + +struct FSKOsc { + uint16_t freq[2]; + uint16_t osc_phase_max; + int32_t osc_phase_current; + + uint32_t pulse; +}; + +FSKOsc* fsk_osc_alloc(uint32_t freq_low, uint32_t freq_hi, uint32_t osc_phase_max) { + FSKOsc* osc = malloc(sizeof(FSKOsc)); + osc->freq[0] = freq_low; + osc->freq[1] = freq_hi; + osc->osc_phase_max = osc_phase_max; + osc->osc_phase_current = 0; + osc->pulse = 0; + return osc; +} + +void fsk_osc_free(FSKOsc* osc) { + free(osc); +} + +void fsk_osc_reset(FSKOsc* osc) { + osc->osc_phase_current = 0; + osc->pulse = 0; +} + +bool fsk_osc_next(FSKOsc* osc, bool bit, uint32_t* period) { + bool advance = false; + *period = osc->freq[bit]; + osc->osc_phase_current += *period; + + if(osc->osc_phase_current > osc->osc_phase_max) { + advance = true; + osc->osc_phase_current -= osc->osc_phase_max; + } + + return advance; +} + +bool fsk_osc_next_half(FSKOsc* osc, bool bit, bool* level, uint32_t* duration) { + bool advance = false; + + // if pulse is zero, we need to output high, otherwise we need to output low + if(osc->pulse == 0) { + uint32_t length; + advance = fsk_osc_next(osc, bit, &length); + *duration = length / 2; + osc->pulse = *duration; + *level = true; + } else { + // output low half and reset pulse + *duration = osc->pulse; + osc->pulse = 0; + *level = false; + } + + return advance; +} \ No newline at end of file diff --git a/lib/lfrfid/tools/fsk_osc.h b/lib/lfrfid/tools/fsk_osc.h new file mode 100644 index 00000000000..ed7d436c855 --- /dev/null +++ b/lib/lfrfid/tools/fsk_osc.h @@ -0,0 +1,60 @@ +#pragma once +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct FSKOsc FSKOsc; + +/** + * @brief Allocate a new FSKOsc instance + * FSKOsc is a oscillator that can provide FSK encoding + * + * @param freq_low + * @param freq_hi + * @param osc_phase_max + * @return FSKOsc* + */ +FSKOsc* fsk_osc_alloc(uint32_t freq_low, uint32_t freq_hi, uint32_t osc_phase_max); + +/** + * @brief Free a FSKOsc instance + * + * @param osc + */ +void fsk_osc_free(FSKOsc* osc); + +/** + * @brief Reset ocillator + * + * @param osc + */ +void fsk_osc_reset(FSKOsc* osc); + +/** + * @brief Get next duration sample from oscillator + * + * @param osc + * @param bit + * @param period + * @return bool + */ +bool fsk_osc_next(FSKOsc* osc, bool bit, uint32_t* period); + +/** + * @brief Get next half of sample from oscillator + * Useful when encoding high and low levels separately. + * + * @param osc + * @param bit + * @param level + * @param duration + * @return bool + */ +bool fsk_osc_next_half(FSKOsc* osc, bool bit, bool* level, uint32_t* duration); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/lfrfid/tools/t5577.c b/lib/lfrfid/tools/t5577.c new file mode 100644 index 00000000000..3444afea3e4 --- /dev/null +++ b/lib/lfrfid/tools/t5577.c @@ -0,0 +1,94 @@ +#include "t5577.h" +#include +#include + +#define T5577_TIMING_WAIT_TIME 400 +#define T5577_TIMING_START_GAP 30 +#define T5577_TIMING_WRITE_GAP 18 +#define T5577_TIMING_DATA_0 24 +#define T5577_TIMING_DATA_1 56 +#define T5577_TIMING_PROGRAM 700 + +#define T5577_OPCODE_PAGE_0 0b10 +#define T5577_OPCODE_PAGE_1 0b11 +#define T5577_OPCODE_RESET 0b00 + +static void t5577_start() { + furi_hal_rfid_tim_read(125000, 0.5); + furi_hal_rfid_pins_read(); + furi_hal_rfid_tim_read_start(); + + // do not ground the antenna + furi_hal_rfid_pin_pull_release(); +} + +static void t5577_stop() { + furi_hal_rfid_tim_read_stop(); + furi_hal_rfid_tim_reset(); + furi_hal_rfid_pins_reset(); +} + +static void t5577_write_gap(uint32_t gap_time) { + furi_hal_rfid_tim_read_stop(); + furi_delay_us(gap_time * 8); + furi_hal_rfid_tim_read_start(); +} + +static void t5577_write_bit(bool value) { + if(value) { + furi_delay_us(T5577_TIMING_DATA_1 * 8); + } else { + furi_delay_us(T5577_TIMING_DATA_0 * 8); + } + t5577_write_gap(T5577_TIMING_WRITE_GAP); +} + +static void t5577_write_opcode(uint8_t value) { + t5577_write_bit((value >> 1) & 1); + t5577_write_bit((value >> 0) & 1); +} + +static void t5577_write_reset() { + t5577_write_gap(T5577_TIMING_START_GAP); + t5577_write_bit(1); + t5577_write_bit(0); +} + +static void t5577_write_block(uint8_t block, bool lock_bit, uint32_t data) { + furi_delay_us(T5577_TIMING_WAIT_TIME * 8); + + // start gap + t5577_write_gap(T5577_TIMING_START_GAP); + + // opcode for page 0 + t5577_write_opcode(T5577_OPCODE_PAGE_0); + + // lock bit + t5577_write_bit(lock_bit); + + // data + for(uint8_t i = 0; i < 32; i++) { + t5577_write_bit((data >> (31 - i)) & 1); + } + + // block address + t5577_write_bit((block >> 2) & 1); + t5577_write_bit((block >> 1) & 1); + t5577_write_bit((block >> 0) & 1); + + furi_delay_us(T5577_TIMING_PROGRAM * 8); + + furi_delay_us(T5577_TIMING_WAIT_TIME * 8); + t5577_write_reset(); +} + +void t5577_write(LFRFIDT5577* data) { + t5577_start(); + FURI_CRITICAL_ENTER(); + for(size_t i = 0; i < data->blocks_to_write; i++) { + t5577_write_block(i, false, data->block[i]); + } + t5577_write_reset(); + FURI_CRITICAL_EXIT(); + t5577_stop(); +} \ No newline at end of file diff --git a/lib/lfrfid/tools/t5577.h b/lib/lfrfid/tools/t5577.h new file mode 100644 index 00000000000..6d53b5dc74b --- /dev/null +++ b/lib/lfrfid/tools/t5577.h @@ -0,0 +1,56 @@ +#pragma once +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define LFRFID_T5577_BLOCK_COUNT 8 + +// T5577 block 0 definitions, thanks proxmark3! +#define LFRFID_T5577_POR_DELAY 0x00000001 +#define LFRFID_T5577_ST_TERMINATOR 0x00000008 +#define LFRFID_T5577_PWD 0x00000010 +#define LFRFID_T5577_MAXBLOCK_SHIFT 5 +#define LFRFID_T5577_AOR 0x00000200 +#define LFRFID_T5577_PSKCF_RF_2 0 +#define LFRFID_T5577_PSKCF_RF_4 0x00000400 +#define LFRFID_T5577_PSKCF_RF_8 0x00000800 +#define LFRFID_T5577_MODULATION_DIRECT 0 +#define LFRFID_T5577_MODULATION_PSK1 0x00001000 +#define LFRFID_T5577_MODULATION_PSK2 0x00002000 +#define LFRFID_T5577_MODULATION_PSK3 0x00003000 +#define LFRFID_T5577_MODULATION_FSK1 0x00004000 +#define LFRFID_T5577_MODULATION_FSK2 0x00005000 +#define LFRFID_T5577_MODULATION_FSK1a 0x00006000 +#define LFRFID_T5577_MODULATION_FSK2a 0x00007000 +#define LFRFID_T5577_MODULATION_MANCHESTER 0x00008000 +#define LFRFID_T5577_MODULATION_BIPHASE 0x00010000 +#define LFRFID_T5577_MODULATION_DIPHASE 0x00018000 +#define LFRFID_T5577_X_MODE 0x00020000 +#define LFRFID_T5577_BITRATE_RF_8 0 +#define LFRFID_T5577_BITRATE_RF_16 0x00040000 +#define LFRFID_T5577_BITRATE_RF_32 0x00080000 +#define LFRFID_T5577_BITRATE_RF_40 0x000C0000 +#define LFRFID_T5577_BITRATE_RF_50 0x00100000 +#define LFRFID_T5577_BITRATE_RF_64 0x00140000 +#define LFRFID_T5577_BITRATE_RF_100 0x00180000 +#define LFRFID_T5577_BITRATE_RF_128 0x001C0000 +#define LFRFID_T5577_TESTMODE_DISABLED 0x60000000 + +typedef struct { + uint32_t block[LFRFID_T5577_BLOCK_COUNT]; + uint32_t blocks_to_write; +} LFRFIDT5577; + +/** + * @brief Write T5577 tag data to tag + * + * @param data + */ +void t5577_write(LFRFIDT5577* data); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/lfrfid/tools/varint_pair.c b/lib/lfrfid/tools/varint_pair.c new file mode 100644 index 00000000000..c59ba55b40d --- /dev/null +++ b/lib/lfrfid/tools/varint_pair.c @@ -0,0 +1,77 @@ +#include "varint_pair.h" +#include + +#define VARINT_PAIR_SIZE 10 + +struct VarintPair { + size_t data_length; + uint8_t data[VARINT_PAIR_SIZE]; +}; + +VarintPair* varint_pair_alloc() { + VarintPair* pair = malloc(sizeof(VarintPair)); + pair->data_length = 0; + return pair; +} + +void varint_pair_free(VarintPair* pair) { + free(pair); +} + +bool varint_pair_pack(VarintPair* pair, bool first, uint32_t value) { + bool result = false; + + if(first) { + if(pair->data_length == 0) { + pair->data_length = varint_uint32_pack(value, pair->data); + } else { + pair->data_length = 0; + } + } else { + if(pair->data_length > 0) { + pair->data_length += varint_uint32_pack(value, pair->data + pair->data_length); + result = true; + } else { + pair->data_length = 0; + } + } + + return result; +} + +bool varint_pair_unpack( + uint8_t* data, + size_t data_length, + uint32_t* value_1, + uint32_t* value_2, + size_t* length) { + size_t size = 0; + uint32_t tmp_value_1; + uint32_t tmp_value_2; + + size += varint_uint32_unpack(&tmp_value_1, &data[size], data_length); + + if(size >= data_length) { + return false; + } + + size += varint_uint32_unpack(&tmp_value_2, &data[size], (size_t)(data_length - size)); + + *value_1 = tmp_value_1; + *value_2 = tmp_value_2; + *length = size; + + return true; +} + +uint8_t* varint_pair_get_data(VarintPair* pair) { + return pair->data; +} + +size_t varint_pair_get_size(VarintPair* pair) { + return pair->data_length; +} + +void varint_pair_reset(VarintPair* pair) { + pair->data_length = 0; +} diff --git a/lib/lfrfid/tools/varint_pair.h b/lib/lfrfid/tools/varint_pair.h new file mode 100644 index 00000000000..3c8386423e4 --- /dev/null +++ b/lib/lfrfid/tools/varint_pair.h @@ -0,0 +1,79 @@ +#pragma once +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct VarintPair VarintPair; + +/** + * @brief Allocate a new VarintPair instance + * + * VarintPair is a buffer that holds pair of varint values + * @return VarintPair* + */ +VarintPair* varint_pair_alloc(); + +/** + * @brief Free a VarintPair instance + * + * @param pair + */ +void varint_pair_free(VarintPair* pair); + +/** + * @brief Write varint pair to buffer + * + * @param pair + * @param first + * @param value + * @return bool pair complete and needs to be written + */ +bool varint_pair_pack(VarintPair* pair, bool first, uint32_t value); + +/** + * @brief Get pointer to varint pair buffer + * + * @param pair + * @return uint8_t* + */ +uint8_t* varint_pair_get_data(VarintPair* pair); + +/** + * @brief Get size of varint pair buffer + * + * @param pair + * @return size_t + */ +size_t varint_pair_get_size(VarintPair* pair); + +/** + * @brief Reset varint pair buffer + * + * @param pair + */ +void varint_pair_reset(VarintPair* pair); + +/** + * @brief Unpack varint pair to uint32_t pair from buffer + * + * @param data + * @param data_length + * @param value_1 + * @param value_2 + * @param length + * @return bool + */ +bool varint_pair_unpack( + uint8_t* data, + size_t data_length, + uint32_t* value_1, + uint32_t* value_2, + size_t* length); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/one_wire/ibutton/encoder/encoder_cyfral.c b/lib/one_wire/ibutton/encoder/encoder_cyfral.c deleted file mode 100644 index 0b506b2b04b..00000000000 --- a/lib/one_wire/ibutton/encoder/encoder_cyfral.c +++ /dev/null @@ -1,126 +0,0 @@ -#include "encoder_cyfral.h" -#include - -#define CYFRAL_DATA_SIZE sizeof(uint16_t) -#define CYFRAL_PERIOD (125 * furi_hal_cortex_instructions_per_microsecond()) -#define CYFRAL_0_LOW (CYFRAL_PERIOD * 0.66f) -#define CYFRAL_0_HI (CYFRAL_PERIOD * 0.33f) -#define CYFRAL_1_LOW (CYFRAL_PERIOD * 0.33f) -#define CYFRAL_1_HI (CYFRAL_PERIOD * 0.66f) - -#define CYFRAL_SET_DATA(level, len) \ - *polarity = level; \ - *length = len; - -struct EncoderCyfral { - uint32_t data; - uint32_t index; -}; - -EncoderCyfral* encoder_cyfral_alloc() { - EncoderCyfral* cyfral = malloc(sizeof(EncoderCyfral)); - encoder_cyfral_reset(cyfral); - return cyfral; -} - -void encoder_cyfral_free(EncoderCyfral* cyfral) { - free(cyfral); -} - -void encoder_cyfral_reset(EncoderCyfral* cyfral) { - cyfral->data = 0; - cyfral->index = 0; -} - -uint32_t cyfral_encoder_encode(const uint16_t data) { - uint32_t value = 0; - for(int8_t i = 0; i <= 7; i++) { - switch((data >> (i * 2)) & 0b00000011) { - case 0b11: - value = value << 4; - value += 0b00000111; - break; - case 0b10: - value = value << 4; - value += 0b00001011; - break; - case 0b01: - value = value << 4; - value += 0b00001101; - break; - case 0b00: - value = value << 4; - value += 0b00001110; - break; - default: - break; - } - } - - return value; -} - -void encoder_cyfral_set_data(EncoderCyfral* cyfral, const uint8_t* data, size_t data_size) { - furi_assert(cyfral); - furi_check(data_size >= CYFRAL_DATA_SIZE); - uint16_t intermediate; - memcpy(&intermediate, data, CYFRAL_DATA_SIZE); - cyfral->data = cyfral_encoder_encode(intermediate); -} - -void encoder_cyfral_get_pulse(EncoderCyfral* cyfral, bool* polarity, uint32_t* length) { - if(cyfral->index < 8) { - // start word (0b0001) - switch(cyfral->index) { - case 0: - CYFRAL_SET_DATA(false, CYFRAL_0_LOW); - break; - case 1: - CYFRAL_SET_DATA(true, CYFRAL_0_HI); - break; - case 2: - CYFRAL_SET_DATA(false, CYFRAL_0_LOW); - break; - case 3: - CYFRAL_SET_DATA(true, CYFRAL_0_HI); - break; - case 4: - CYFRAL_SET_DATA(false, CYFRAL_0_LOW); - break; - case 5: - CYFRAL_SET_DATA(true, CYFRAL_0_HI); - break; - case 6: - CYFRAL_SET_DATA(false, CYFRAL_1_LOW); - break; - case 7: - CYFRAL_SET_DATA(true, CYFRAL_1_HI); - break; - } - } else { - // data - uint8_t data_start_index = cyfral->index - 8; - bool clock_polarity = (data_start_index) % 2; - uint8_t bit_index = (data_start_index) / 2; - bool bit_value = ((cyfral->data >> bit_index) & 1); - - if(!clock_polarity) { - if(bit_value) { - CYFRAL_SET_DATA(false, CYFRAL_1_LOW); - } else { - CYFRAL_SET_DATA(false, CYFRAL_0_LOW); - } - } else { - if(bit_value) { - CYFRAL_SET_DATA(true, CYFRAL_1_HI); - } else { - CYFRAL_SET_DATA(true, CYFRAL_0_HI); - } - } - } - - cyfral->index++; - if(cyfral->index >= (9 * 4 * 2)) { - cyfral->index = 0; - } -} diff --git a/lib/one_wire/ibutton/encoder/encoder_cyfral.h b/lib/one_wire/ibutton/encoder/encoder_cyfral.h deleted file mode 100644 index 4fc7be5ed8d..00000000000 --- a/lib/one_wire/ibutton/encoder/encoder_cyfral.h +++ /dev/null @@ -1,54 +0,0 @@ -/** - * @file encoder_cyfral.h - * - * Cyfral pulse format encoder - */ - -#pragma once -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct EncoderCyfral EncoderCyfral; - -/** - * Allocate Cyfral encoder - * @return EncoderCyfral* - */ -EncoderCyfral* encoder_cyfral_alloc(); - -/** - * Deallocate Cyfral encoder - * @param cyfral - */ -void encoder_cyfral_free(EncoderCyfral* cyfral); - -/** - * Reset Cyfral encoder - * @param cyfral - */ -void encoder_cyfral_reset(EncoderCyfral* cyfral); - -/** - * Set data to be encoded to Cyfral pulse format, 2 bytes - * @param cyfral - * @param data - * @param data_size - */ -void encoder_cyfral_set_data(EncoderCyfral* cyfral, const uint8_t* data, size_t data_size); - -/** - * Pop pulse from Cyfral encoder - * @param cyfral - * @param polarity - * @param length - */ -void encoder_cyfral_get_pulse(EncoderCyfral* cyfral, bool* polarity, uint32_t* length); - -#ifdef __cplusplus -} -#endif diff --git a/lib/one_wire/ibutton/encoder/encoder_metakom.c b/lib/one_wire/ibutton/encoder/encoder_metakom.c deleted file mode 100644 index 5b978ebe285..00000000000 --- a/lib/one_wire/ibutton/encoder/encoder_metakom.c +++ /dev/null @@ -1,93 +0,0 @@ -#include "encoder_metakom.h" -#include - -#define METAKOM_DATA_SIZE sizeof(uint32_t) -#define METAKOM_PERIOD (125 * furi_hal_cortex_instructions_per_microsecond()) -#define METAKOM_0_LOW (METAKOM_PERIOD * 0.33f) -#define METAKOM_0_HI (METAKOM_PERIOD * 0.66f) -#define METAKOM_1_LOW (METAKOM_PERIOD * 0.66f) -#define METAKOM_1_HI (METAKOM_PERIOD * 0.33f) - -#define METAKOM_SET_DATA(level, len) \ - *polarity = !level; \ - *length = len; - -struct EncoderMetakom { - uint32_t data; - uint32_t index; -}; - -EncoderMetakom* encoder_metakom_alloc() { - EncoderMetakom* metakom = malloc(sizeof(EncoderMetakom)); - encoder_metakom_reset(metakom); - return metakom; -} - -void encoder_metakom_free(EncoderMetakom* metakom) { - free(metakom); -} - -void encoder_metakom_reset(EncoderMetakom* metakom) { - metakom->data = 0; - metakom->index = 0; -} - -void encoder_metakom_set_data(EncoderMetakom* metakom, const uint8_t* data, size_t data_size) { - furi_assert(metakom); - furi_check(data_size >= METAKOM_DATA_SIZE); - memcpy(&metakom->data, data, METAKOM_DATA_SIZE); -} - -void encoder_metakom_get_pulse(EncoderMetakom* metakom, bool* polarity, uint32_t* length) { - if(metakom->index == 0) { - // sync bit - METAKOM_SET_DATA(true, METAKOM_PERIOD); - } else if(metakom->index >= 1 && metakom->index <= 6) { - // start word (0b010) - switch(metakom->index) { - case 1: - METAKOM_SET_DATA(false, METAKOM_0_LOW); - break; - case 2: - METAKOM_SET_DATA(true, METAKOM_0_HI); - break; - case 3: - METAKOM_SET_DATA(false, METAKOM_1_LOW); - break; - case 4: - METAKOM_SET_DATA(true, METAKOM_1_HI); - break; - case 5: - METAKOM_SET_DATA(false, METAKOM_0_LOW); - break; - case 6: - METAKOM_SET_DATA(true, METAKOM_0_HI); - break; - } - } else { - // data - uint8_t data_start_index = metakom->index - 7; - bool clock_polarity = (data_start_index) % 2; - uint8_t bit_index = (data_start_index) / 2; - bool bit_value = (metakom->data >> (32 - 1 - bit_index)) & 1; - - if(!clock_polarity) { - if(bit_value) { - METAKOM_SET_DATA(false, METAKOM_1_LOW); - } else { - METAKOM_SET_DATA(false, METAKOM_0_LOW); - } - } else { - if(bit_value) { - METAKOM_SET_DATA(true, METAKOM_1_HI); - } else { - METAKOM_SET_DATA(true, METAKOM_0_HI); - } - } - } - - metakom->index++; - if(metakom->index >= (1 + 3 * 2 + 32 * 2)) { - metakom->index = 0; - } -} diff --git a/lib/one_wire/ibutton/encoder/encoder_metakom.h b/lib/one_wire/ibutton/encoder/encoder_metakom.h deleted file mode 100644 index 50ff11a243c..00000000000 --- a/lib/one_wire/ibutton/encoder/encoder_metakom.h +++ /dev/null @@ -1,54 +0,0 @@ -/** - * @file encoder_metakom.h - * - * Metakom pulse format encoder - */ - -#pragma once -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct EncoderMetakom EncoderMetakom; - -/** - * Allocate Metakom encoder - * @return EncoderMetakom* - */ -EncoderMetakom* encoder_metakom_alloc(); - -/** - * Deallocate Metakom encoder - * @param metakom - */ -void encoder_metakom_free(EncoderMetakom* metakom); - -/** - * Reset Metakom encoder - * @param metakom - */ -void encoder_metakom_reset(EncoderMetakom* metakom); - -/** - * Set data to be encoded to Metakom pulse format, 4 bytes - * @param metakom - * @param data - * @param data_size - */ -void encoder_metakom_set_data(EncoderMetakom* metakom, const uint8_t* data, size_t data_size); - -/** - * Pop pulse from Metakom encoder - * @param cyfral - * @param polarity - * @param length - */ -void encoder_metakom_get_pulse(EncoderMetakom* metakom, bool* polarity, uint32_t* length); - -#ifdef __cplusplus -} -#endif diff --git a/lib/one_wire/ibutton/ibutton_key.h b/lib/one_wire/ibutton/ibutton_key.h index f66537d7ea9..d682555a86d 100644 --- a/lib/one_wire/ibutton/ibutton_key.h +++ b/lib/one_wire/ibutton/ibutton_key.h @@ -5,6 +5,7 @@ */ #pragma once +#include #ifdef __cplusplus extern "C" { diff --git a/lib/one_wire/ibutton/ibutton_worker.c b/lib/one_wire/ibutton/ibutton_worker.c index 755ed32f3e9..26982bcb678 100644 --- a/lib/one_wire/ibutton/ibutton_worker.c +++ b/lib/one_wire/ibutton/ibutton_worker.c @@ -29,34 +29,21 @@ iButtonWorker* ibutton_worker_alloc() { worker->slave = onewire_slave_alloc(); worker->writer = ibutton_writer_alloc(worker->host); worker->device = onewire_device_alloc(0, 0, 0, 0, 0, 0, 0, 0); - worker->pulse_decoder = pulse_decoder_alloc(); - worker->protocol_cyfral = protocol_cyfral_alloc(); - worker->protocol_metakom = protocol_metakom_alloc(); worker->messages = furi_message_queue_alloc(1, sizeof(iButtonMessage)); + worker->mode_index = iButtonWorkerIdle; - worker->last_dwt_value = 0; worker->read_cb = NULL; worker->write_cb = NULL; worker->emulate_cb = NULL; worker->cb_ctx = NULL; - worker->encoder_cyfral = encoder_cyfral_alloc(); - worker->encoder_metakom = encoder_metakom_alloc(); - worker->thread = furi_thread_alloc(); furi_thread_set_name(worker->thread, "ibutton_worker"); furi_thread_set_callback(worker->thread, ibutton_worker_thread); furi_thread_set_context(worker->thread, worker); furi_thread_set_stack_size(worker->thread, 2048); - pulse_decoder_add_protocol( - worker->pulse_decoder, - protocol_cyfral_get_protocol(worker->protocol_cyfral), - PulseProtocolCyfral); - pulse_decoder_add_protocol( - worker->pulse_decoder, - protocol_metakom_get_protocol(worker->protocol_metakom), - PulseProtocolMetakom); + worker->protocols = protocol_dict_alloc(ibutton_protocols, iButtonProtocolMax); return worker; } @@ -113,10 +100,6 @@ void ibutton_worker_stop(iButtonWorker* worker) { } void ibutton_worker_free(iButtonWorker* worker) { - pulse_decoder_free(worker->pulse_decoder); - protocol_metakom_free(worker->protocol_metakom); - protocol_cyfral_free(worker->protocol_cyfral); - ibutton_writer_free(worker->writer); onewire_slave_free(worker->slave); @@ -124,8 +107,7 @@ void ibutton_worker_free(iButtonWorker* worker) { onewire_host_free(worker->host); onewire_device_free(worker->device); - encoder_cyfral_free(worker->encoder_cyfral); - encoder_metakom_free(worker->encoder_metakom); + protocol_dict_free(worker->protocols); furi_message_queue_free(worker->messages); diff --git a/lib/one_wire/ibutton/ibutton_worker_i.h b/lib/one_wire/ibutton/ibutton_worker_i.h index 28588443943..2396fbd61c4 100644 --- a/lib/one_wire/ibutton/ibutton_worker_i.h +++ b/lib/one_wire/ibutton/ibutton_worker_i.h @@ -10,21 +10,13 @@ #include "../one_wire_host.h" #include "../one_wire_slave.h" #include "../one_wire_device.h" -#include "../pulse_protocols/pulse_decoder.h" -#include "pulse_protocols/protocol_cyfral.h" -#include "pulse_protocols/protocol_metakom.h" -#include "encoder/encoder_cyfral.h" -#include "encoder/encoder_metakom.h" +#include +#include "protocols/ibutton_protocols.h" #ifdef __cplusplus extern "C" { #endif -typedef enum { - PulseProtocolCyfral, - PulseProtocolMetakom, -} PulseProtocols; - typedef struct { const uint32_t quant; void (*const start)(iButtonWorker* worker); @@ -39,11 +31,6 @@ typedef enum { iButtonWorkerEmulate = 3, } iButtonWorkerMode; -typedef enum { - iButtonEmulateModeCyfral, - iButtonEmulateModeMetakom, -} iButtonEmulateMode; - struct iButtonWorker { iButtonKey* key_p; uint8_t* key_data; @@ -55,19 +42,13 @@ struct iButtonWorker { FuriMessageQueue* messages; FuriThread* thread; - PulseDecoder* pulse_decoder; - ProtocolCyfral* protocol_cyfral; - ProtocolMetakom* protocol_metakom; - uint32_t last_dwt_value; - iButtonWorkerReadCallback read_cb; iButtonWorkerWriteCallback write_cb; iButtonWorkerEmulateCallback emulate_cb; void* cb_ctx; - EncoderCyfral* encoder_cyfral; - EncoderMetakom* encoder_metakom; - iButtonEmulateMode emulate_mode; + ProtocolDict* protocols; + iButtonProtocol protocol_to_encode; }; extern const iButtonWorkerModeType ibutton_worker_modes[]; diff --git a/lib/one_wire/ibutton/ibutton_worker_modes.c b/lib/one_wire/ibutton/ibutton_worker_modes.c index 78e05d0ee31..d585e27f4dd 100644 --- a/lib/one_wire/ibutton/ibutton_worker_modes.c +++ b/lib/one_wire/ibutton/ibutton_worker_modes.c @@ -2,6 +2,7 @@ #include #include "ibutton_worker_i.h" #include "ibutton_key_command.h" +#include void ibutton_worker_mode_idle_start(iButtonWorker* worker); void ibutton_worker_mode_idle_tick(iButtonWorker* worker); @@ -62,59 +63,86 @@ void ibutton_worker_mode_idle_stop(iButtonWorker* worker) { /*********************** READ ***********************/ +typedef struct { + uint32_t last_dwt_value; + StreamBufferHandle_t stream; +} iButtonReadContext; + void ibutton_worker_comparator_callback(bool level, void* context) { - iButtonWorker* worker = context; + iButtonReadContext* read_context = context; uint32_t current_dwt_value = DWT->CYCCNT; - pulse_decoder_process_pulse( - worker->pulse_decoder, level, current_dwt_value - worker->last_dwt_value); + LevelDuration data = + level_duration_make(level, current_dwt_value - read_context->last_dwt_value); + xStreamBufferSend(read_context->stream, &data, sizeof(LevelDuration), 0); - worker->last_dwt_value = current_dwt_value; + read_context->last_dwt_value = current_dwt_value; } bool ibutton_worker_read_comparator(iButtonWorker* worker) { bool result = false; - pulse_decoder_reset(worker->pulse_decoder); + protocol_dict_decoders_start(worker->protocols); furi_hal_rfid_pins_reset(); // pulldown pull pin, we sense the signal through the analog part of the RFID schematic furi_hal_rfid_pin_pull_pulldown(); - furi_hal_rfid_comp_set_callback(ibutton_worker_comparator_callback, worker); - worker->last_dwt_value = DWT->CYCCNT; + + iButtonReadContext read_context = { + .last_dwt_value = DWT->CYCCNT, + .stream = xStreamBufferCreate(sizeof(LevelDuration) * 512, 1), + }; + + furi_hal_rfid_comp_set_callback(ibutton_worker_comparator_callback, &read_context); furi_hal_rfid_comp_start(); - // TODO: rework with thread events, "pulse_decoder_get_decoded_index_with_timeout" - furi_delay_ms(100); - int32_t decoded_index = pulse_decoder_get_decoded_index(worker->pulse_decoder); - if(decoded_index >= 0) { - pulse_decoder_get_data( - worker->pulse_decoder, decoded_index, worker->key_data, ibutton_key_get_max_size()); - } + uint32_t tick_start = furi_get_tick(); + while(true) { + LevelDuration level; + size_t ret = xStreamBufferReceive(read_context.stream, &level, sizeof(LevelDuration), 100); - switch(decoded_index) { - case PulseProtocolCyfral: - furi_check(worker->key_p != NULL); - ibutton_key_set_type(worker->key_p, iButtonKeyCyfral); - ibutton_key_set_data(worker->key_p, worker->key_data, ibutton_key_get_max_size()); - result = true; - break; - case PulseProtocolMetakom: - furi_check(worker->key_p != NULL); - ibutton_key_set_type(worker->key_p, iButtonKeyMetakom); - ibutton_key_set_data(worker->key_p, worker->key_data, ibutton_key_get_max_size()); - result = true; - break; - break; - default: - break; + if((furi_get_tick() - tick_start) > 100) { + break; + } + + if(ret > 0) { + ProtocolId decoded_index = protocol_dict_decoders_feed( + worker->protocols, + level_duration_get_level(level), + level_duration_get_duration(level)); + + if(decoded_index == PROTOCOL_NO) continue; + + protocol_dict_get_data( + worker->protocols, decoded_index, worker->key_data, ibutton_key_get_max_size()); + + switch(decoded_index) { + case iButtonProtocolCyfral: + furi_check(worker->key_p != NULL); + ibutton_key_set_type(worker->key_p, iButtonKeyCyfral); + ibutton_key_set_data(worker->key_p, worker->key_data, ibutton_key_get_max_size()); + result = true; + break; + case iButtonProtocolMetakom: + furi_check(worker->key_p != NULL); + ibutton_key_set_type(worker->key_p, iButtonKeyMetakom); + ibutton_key_set_data(worker->key_p, worker->key_data, ibutton_key_get_max_size()); + result = true; + break; + break; + default: + break; + } + } } furi_hal_rfid_comp_stop(); furi_hal_rfid_comp_set_callback(NULL, NULL); furi_hal_rfid_pins_reset(); + vStreamBufferDelete(read_context.stream); + return result; } @@ -207,21 +235,12 @@ void ibutton_worker_emulate_timer_cb(void* context) { furi_assert(context); iButtonWorker* worker = context; - bool polarity; - uint32_t length; + LevelDuration level = + protocol_dict_encoder_yield(worker->protocols, worker->protocol_to_encode); - switch(worker->emulate_mode) { - case iButtonEmulateModeCyfral: - encoder_cyfral_get_pulse(worker->encoder_cyfral, &polarity, &length); - break; - case iButtonEmulateModeMetakom: - encoder_metakom_get_pulse(worker->encoder_metakom, &polarity, &length); - break; - } - - furi_hal_ibutton_emulate_set_next(length); + furi_hal_ibutton_emulate_set_next(level_duration_get_duration(level)); - if(polarity) { + if(level_duration_get_level(level)) { furi_hal_ibutton_pin_high(); } else { furi_hal_ibutton_pin_low(); @@ -238,17 +257,16 @@ void ibutton_worker_emulate_timer_start(iButtonWorker* worker) { return; break; case iButtonKeyCyfral: - worker->emulate_mode = iButtonEmulateModeCyfral; - encoder_cyfral_reset(worker->encoder_cyfral); - encoder_cyfral_set_data(worker->encoder_cyfral, key_id, key_size); + worker->protocol_to_encode = iButtonProtocolCyfral; break; case iButtonKeyMetakom: - worker->emulate_mode = iButtonEmulateModeMetakom; - encoder_metakom_reset(worker->encoder_metakom); - encoder_metakom_set_data(worker->encoder_metakom, key_id, key_size); + worker->protocol_to_encode = iButtonProtocolMetakom; break; } + protocol_dict_set_data(worker->protocols, worker->protocol_to_encode, key_id, key_size); + protocol_dict_encoder_start(worker->protocols, worker->protocol_to_encode); + furi_hal_ibutton_start_drive(); furi_hal_ibutton_emulate_start(0, ibutton_worker_emulate_timer_cb, worker); } diff --git a/lib/one_wire/ibutton/protocols/ibutton_protocols.c b/lib/one_wire/ibutton/protocols/ibutton_protocols.c new file mode 100644 index 00000000000..b07d68b33cc --- /dev/null +++ b/lib/one_wire/ibutton/protocols/ibutton_protocols.c @@ -0,0 +1,8 @@ +#include "ibutton_protocols.h" +#include "protocol_cyfral.h" +#include "protocol_metakom.h" + +const ProtocolBase* ibutton_protocols[] = { + [iButtonProtocolCyfral] = &protocol_cyfral, + [iButtonProtocolMetakom] = &protocol_metakom, +}; \ No newline at end of file diff --git a/lib/one_wire/ibutton/protocols/ibutton_protocols.h b/lib/one_wire/ibutton/protocols/ibutton_protocols.h new file mode 100644 index 00000000000..cdaa3ab6fa4 --- /dev/null +++ b/lib/one_wire/ibutton/protocols/ibutton_protocols.h @@ -0,0 +1,11 @@ +#pragma once +#include "toolbox/protocols/protocol.h" + +typedef enum { + iButtonProtocolCyfral, + iButtonProtocolMetakom, + + iButtonProtocolMax, +} iButtonProtocol; + +extern const ProtocolBase* ibutton_protocols[]; \ No newline at end of file diff --git a/lib/one_wire/ibutton/protocols/protocol_cyfral.c b/lib/one_wire/ibutton/protocols/protocol_cyfral.c new file mode 100644 index 00000000000..51c42824fca --- /dev/null +++ b/lib/one_wire/ibutton/protocols/protocol_cyfral.c @@ -0,0 +1,344 @@ +#include +#include +#include "protocol_cyfral.h" + +#define CYFRAL_DATA_SIZE sizeof(uint16_t) +#define CYFRAL_PERIOD (125 * furi_hal_cortex_instructions_per_microsecond()) +#define CYFRAL_0_LOW (CYFRAL_PERIOD * 0.66f) +#define CYFRAL_0_HI (CYFRAL_PERIOD * 0.33f) +#define CYFRAL_1_LOW (CYFRAL_PERIOD * 0.33f) +#define CYFRAL_1_HI (CYFRAL_PERIOD * 0.66f) + +#define CYFRAL_MAX_PERIOD_US 230 + +typedef enum { + CYFRAL_BIT_WAIT_FRONT_HIGH, + CYFRAL_BIT_WAIT_FRONT_LOW, +} CyfralBitState; + +typedef enum { + CYFRAL_WAIT_START_NIBBLE, + CYFRAL_READ_NIBBLE, + CYFRAL_READ_STOP_NIBBLE, +} CyfralState; + +typedef struct { + CyfralState state; + CyfralBitState bit_state; + + // high + low period time + uint32_t period_time; + // temporary nibble storage + uint8_t nibble; + // data valid flag + // MUST be checked only in READ_STOP_NIBBLE state + bool data_valid; + // nibble index, we expect 8 nibbles + uint8_t index; + // bit index in nibble, 4 bit per nibble + uint8_t bit_index; + // max period, 230us x clock per us + uint32_t max_period; +} ProtocolCyfralDecoder; + +typedef struct { + uint32_t data; + uint32_t index; +} ProtocolCyfralEncoder; + +typedef struct { + uint16_t data; + + ProtocolCyfralDecoder decoder; + ProtocolCyfralEncoder encoder; +} ProtocolCyfral; + +static void* protocol_cyfral_alloc(void) { + ProtocolCyfral* proto = malloc(sizeof(ProtocolCyfral)); + return (void*)proto; +} + +static void protocol_cyfral_free(ProtocolCyfral* proto) { + free(proto); +} + +static uint8_t* protocol_cyfral_get_data(ProtocolCyfral* proto) { + return (uint8_t*)&proto->data; +} + +static void protocol_cyfral_decoder_start(ProtocolCyfral* proto) { + ProtocolCyfralDecoder* cyfral = &proto->decoder; + + cyfral->state = CYFRAL_WAIT_START_NIBBLE; + cyfral->bit_state = CYFRAL_BIT_WAIT_FRONT_LOW; + cyfral->period_time = 0; + cyfral->bit_index = 0; + cyfral->index = 0; + cyfral->nibble = 0; + cyfral->data_valid = true; + cyfral->max_period = CYFRAL_MAX_PERIOD_US * furi_hal_cortex_instructions_per_microsecond(); + + proto->data = 0; +} + +static bool protocol_cyfral_decoder_process_bit( + ProtocolCyfralDecoder* cyfral, + bool polarity, + uint32_t length, + bool* bit_ready, + bool* bit_value) { + bool result = true; + *bit_ready = false; + + // bit start from low + switch(cyfral->bit_state) { + case CYFRAL_BIT_WAIT_FRONT_LOW: + if(polarity == true) { + cyfral->period_time += length; + + *bit_ready = true; + if(cyfral->period_time <= cyfral->max_period) { + if((cyfral->period_time / 2) > length) { + *bit_value = false; + } else { + *bit_value = true; + } + } else { + result = false; + } + + cyfral->bit_state = CYFRAL_BIT_WAIT_FRONT_HIGH; + } else { + result = false; + } + break; + case CYFRAL_BIT_WAIT_FRONT_HIGH: + if(polarity == false) { + cyfral->period_time = length; + cyfral->bit_state = CYFRAL_BIT_WAIT_FRONT_LOW; + } else { + result = false; + } + break; + } + + return result; +} + +static bool protocol_cyfral_decoder_feed(ProtocolCyfral* proto, bool level, uint32_t duration) { + ProtocolCyfralDecoder* cyfral = &proto->decoder; + + bool bit_ready; + bool bit_value; + bool decoded = false; + + switch(cyfral->state) { + case CYFRAL_WAIT_START_NIBBLE: + // wait for start word + if(protocol_cyfral_decoder_process_bit(cyfral, level, duration, &bit_ready, &bit_value)) { + if(bit_ready) { + cyfral->nibble = ((cyfral->nibble << 1) | bit_value) & 0x0F; + if(cyfral->nibble == 0b0001) { + cyfral->nibble = 0; + cyfral->state = CYFRAL_READ_NIBBLE; + } + } + } else { + protocol_cyfral_decoder_start(proto); + } + + break; + case CYFRAL_READ_NIBBLE: + // read nibbles + if(protocol_cyfral_decoder_process_bit(cyfral, level, duration, &bit_ready, &bit_value)) { + if(bit_ready) { + cyfral->nibble = (cyfral->nibble << 1) | bit_value; + + cyfral->bit_index++; + + //convert every nibble to 2-bit index + if(cyfral->bit_index == 4) { + switch(cyfral->nibble) { + case 0b1110: + proto->data = (proto->data << 2) | 0b11; + break; + case 0b1101: + proto->data = (proto->data << 2) | 0b10; + break; + case 0b1011: + proto->data = (proto->data << 2) | 0b01; + break; + case 0b0111: + proto->data = (proto->data << 2) | 0b00; + break; + default: + cyfral->data_valid = false; + break; + } + + cyfral->nibble = 0; + cyfral->bit_index = 0; + cyfral->index++; + } + + // succefully read 8 nibbles + if(cyfral->index == 8) { + cyfral->state = CYFRAL_READ_STOP_NIBBLE; + } + } + } else { + protocol_cyfral_decoder_start(proto); + } + break; + case CYFRAL_READ_STOP_NIBBLE: + // read stop nibble + if(protocol_cyfral_decoder_process_bit(cyfral, level, duration, &bit_ready, &bit_value)) { + if(bit_ready) { + cyfral->nibble = ((cyfral->nibble << 1) | bit_value) & 0x0F; + cyfral->bit_index++; + + switch(cyfral->bit_index) { + case 0: + case 1: + case 2: + case 3: + break; + case 4: + if(cyfral->nibble == 0b0001) { + // validate data + if(cyfral->data_valid) { + decoded = true; + } else { + protocol_cyfral_decoder_start(proto); + } + } else { + protocol_cyfral_decoder_start(proto); + } + break; + default: + protocol_cyfral_decoder_start(proto); + break; + } + } + } else { + protocol_cyfral_decoder_start(proto); + } + break; + } + + return decoded; +} + +static uint32_t protocol_cyfral_encoder_encode(const uint16_t data) { + uint32_t value = 0; + for(int8_t i = 0; i <= 7; i++) { + switch((data >> (i * 2)) & 0b00000011) { + case 0b11: + value = value << 4; + value += 0b00000111; + break; + case 0b10: + value = value << 4; + value += 0b00001011; + break; + case 0b01: + value = value << 4; + value += 0b00001101; + break; + case 0b00: + value = value << 4; + value += 0b00001110; + break; + default: + break; + } + } + + return value; +} + +static bool protocol_cyfral_encoder_start(ProtocolCyfral* proto) { + proto->encoder.index = 0; + proto->encoder.data = protocol_cyfral_encoder_encode(proto->data); + return true; +} + +static LevelDuration protocol_cyfral_encoder_yield(ProtocolCyfral* proto) { + LevelDuration result; + + if(proto->encoder.index < 8) { + // start word (0b0001) + switch(proto->encoder.index) { + case 0: + result = level_duration_make(false, CYFRAL_0_LOW); + break; + case 1: + result = level_duration_make(true, CYFRAL_0_HI); + break; + case 2: + result = level_duration_make(false, CYFRAL_0_LOW); + break; + case 3: + result = level_duration_make(true, CYFRAL_0_HI); + break; + case 4: + result = level_duration_make(false, CYFRAL_0_LOW); + break; + case 5: + result = level_duration_make(true, CYFRAL_0_HI); + break; + case 6: + result = level_duration_make(false, CYFRAL_1_LOW); + break; + case 7: + result = level_duration_make(true, CYFRAL_1_HI); + break; + } + } else { + // data + uint8_t data_start_index = proto->encoder.index - 8; + bool clock_polarity = (data_start_index) % 2; + uint8_t bit_index = (data_start_index) / 2; + bool bit_value = ((proto->encoder.data >> bit_index) & 1); + + if(!clock_polarity) { + if(bit_value) { + result = level_duration_make(false, CYFRAL_1_LOW); + } else { + result = level_duration_make(false, CYFRAL_0_LOW); + } + } else { + if(bit_value) { + result = level_duration_make(true, CYFRAL_1_HI); + } else { + result = level_duration_make(true, CYFRAL_0_HI); + } + } + } + + proto->encoder.index++; + if(proto->encoder.index >= (9 * 4 * 2)) { + proto->encoder.index = 0; + } + + return result; +} + +const ProtocolBase protocol_cyfral = { + .name = "Cyfral", + .manufacturer = "Cyfral", + .data_size = CYFRAL_DATA_SIZE, + .alloc = (ProtocolAlloc)protocol_cyfral_alloc, + .free = (ProtocolFree)protocol_cyfral_free, + .get_data = (ProtocolGetData)protocol_cyfral_get_data, + .decoder = + { + .start = (ProtocolDecoderStart)protocol_cyfral_decoder_start, + .feed = (ProtocolDecoderFeed)protocol_cyfral_decoder_feed, + }, + .encoder = + { + .start = (ProtocolEncoderStart)protocol_cyfral_encoder_start, + .yield = (ProtocolEncoderYield)protocol_cyfral_encoder_yield, + }, +}; \ No newline at end of file diff --git a/lib/one_wire/ibutton/protocols/protocol_cyfral.h b/lib/one_wire/ibutton/protocols/protocol_cyfral.h new file mode 100644 index 00000000000..97a98e485e4 --- /dev/null +++ b/lib/one_wire/ibutton/protocols/protocol_cyfral.h @@ -0,0 +1,4 @@ +#pragma once +#include "toolbox/protocols/protocol.h" + +extern const ProtocolBase protocol_cyfral; \ No newline at end of file diff --git a/lib/one_wire/ibutton/pulse_protocols/protocol_metakom.c b/lib/one_wire/ibutton/protocols/protocol_metakom.c similarity index 51% rename from lib/one_wire/ibutton/pulse_protocols/protocol_metakom.c rename to lib/one_wire/ibutton/protocols/protocol_metakom.c index 9df9e20bed1..00f16e45570 100644 --- a/lib/one_wire/ibutton/pulse_protocols/protocol_metakom.c +++ b/lib/one_wire/ibutton/protocols/protocol_metakom.c @@ -1,9 +1,14 @@ +#include +#include #include "protocol_metakom.h" -#include -#include -#include -#define METAKOM_DATA_SIZE 4 +#define METAKOM_DATA_SIZE sizeof(uint32_t) +#define METAKOM_PERIOD (125 * furi_hal_cortex_instructions_per_microsecond()) +#define METAKOM_0_LOW (METAKOM_PERIOD * 0.33f) +#define METAKOM_0_HI (METAKOM_PERIOD * 0.66f) +#define METAKOM_1_LOW (METAKOM_PERIOD * 0.66f) +#define METAKOM_1_HI (METAKOM_PERIOD * 0.33f) + #define METAKOM_PERIOD_SAMPLE_COUNT 10 typedef enum { @@ -19,79 +24,49 @@ typedef enum { METAKOM_BIT_WAIT_FRONT_LOW, } MetakomBitState; -struct ProtocolMetakom { - PulseProtocol* protocol; - +typedef struct { // high + low period time uint32_t period_time; uint32_t low_time_storage; uint8_t period_sample_index; uint32_t period_sample_data[METAKOM_PERIOD_SAMPLE_COUNT]; - // ready flag - // TODO: atomic access - bool ready; - uint8_t tmp_data; uint8_t tmp_counter; - uint32_t key_data; uint8_t key_data_index; MetakomBitState bit_state; MetakomState state; -}; - -static void metakom_pulse(void* context, bool polarity, uint32_t length); -static void metakom_reset(void* context); -static void metakom_get_data(void* context, uint8_t* data, size_t length); -static bool metakom_decoded(void* context); +} ProtocolMetakomDecoder; -ProtocolMetakom* protocol_metakom_alloc() { - ProtocolMetakom* metakom = malloc(sizeof(ProtocolMetakom)); - metakom_reset(metakom); +typedef struct { + uint32_t index; +} ProtocolMetakomEncoder; - metakom->protocol = pulse_protocol_alloc(); +typedef struct { + uint32_t data; - pulse_protocol_set_context(metakom->protocol, metakom); - pulse_protocol_set_pulse_cb(metakom->protocol, metakom_pulse); - pulse_protocol_set_reset_cb(metakom->protocol, metakom_reset); - pulse_protocol_set_get_data_cb(metakom->protocol, metakom_get_data); - pulse_protocol_set_decoded_cb(metakom->protocol, metakom_decoded); + ProtocolMetakomDecoder decoder; + ProtocolMetakomEncoder encoder; +} ProtocolMetakom; - return metakom; +static ProtocolMetakom* protocol_metakom_alloc(void) { + ProtocolMetakom* proto = malloc(sizeof(ProtocolMetakom)); + return (void*)proto; } -void protocol_metakom_free(ProtocolMetakom* metakom) { - furi_assert(metakom); - pulse_protocol_free(metakom->protocol); - free(metakom); +static void protocol_metakom_free(ProtocolMetakom* proto) { + free(proto); } -PulseProtocol* protocol_metakom_get_protocol(ProtocolMetakom* metakom) { - furi_assert(metakom); - return metakom->protocol; +static uint8_t* protocol_metakom_get_data(ProtocolMetakom* proto) { + return (uint8_t*)&proto->data; } -static void metakom_get_data(void* context, uint8_t* data, size_t length) { - furi_assert(context); - furi_check(length >= METAKOM_DATA_SIZE); - ProtocolMetakom* metakom = context; - memcpy(data, &metakom->key_data, METAKOM_DATA_SIZE); -} - -static bool metakom_decoded(void* context) { - furi_assert(context); - ProtocolMetakom* metakom = context; - bool decoded = metakom->ready; - return decoded; -} +static void protocol_metakom_decoder_start(ProtocolMetakom* proto) { + ProtocolMetakomDecoder* metakom = &proto->decoder; -static void metakom_reset(void* context) { - furi_assert(context); - ProtocolMetakom* metakom = context; - - metakom->ready = false; metakom->period_sample_index = 0; metakom->period_time = 0; metakom->tmp_counter = 0; @@ -101,9 +76,10 @@ static void metakom_reset(void* context) { }; metakom->state = METAKOM_WAIT_PERIOD_SYNC; metakom->bit_state = METAKOM_BIT_WAIT_FRONT_LOW; - metakom->key_data = 0; metakom->key_data_index = 0; metakom->low_time_storage = 0; + + proto->data = 0; } static bool metakom_parity_check(uint8_t data) { @@ -122,7 +98,7 @@ static bool metakom_parity_check(uint8_t data) { } static bool metakom_process_bit( - ProtocolMetakom* metakom, + ProtocolMetakomDecoder* metakom, bool polarity, uint32_t time, uint32_t* high_time, @@ -149,18 +125,17 @@ static bool metakom_process_bit( return result; } -static void metakom_pulse(void* context, bool polarity, uint32_t time) { - furi_assert(context); - ProtocolMetakom* metakom = context; +static bool protocol_metakom_decoder_feed(ProtocolMetakom* proto, bool level, uint32_t duration) { + ProtocolMetakomDecoder* metakom = &proto->decoder; - if(metakom->ready) return; + bool ready = false; uint32_t high_time = 0; uint32_t low_time = 0; switch(metakom->state) { case METAKOM_WAIT_PERIOD_SYNC: - if(metakom_process_bit(metakom, polarity, time, &high_time, &low_time)) { + if(metakom_process_bit(metakom, level, duration, &high_time, &low_time)) { metakom->period_sample_data[metakom->period_sample_index] = high_time + low_time; metakom->period_sample_index++; @@ -176,7 +151,7 @@ static void metakom_pulse(void* context, bool polarity, uint32_t time) { break; case METAKOM_WAIT_START_BIT: - if(metakom_process_bit(metakom, polarity, time, &high_time, &low_time)) { + if(metakom_process_bit(metakom, level, duration, &high_time, &low_time)) { metakom->tmp_counter++; if(high_time > metakom->period_time) { metakom->tmp_counter = 0; @@ -184,13 +159,13 @@ static void metakom_pulse(void* context, bool polarity, uint32_t time) { } if(metakom->tmp_counter > 40) { - metakom_reset(metakom); + protocol_metakom_decoder_start(proto); } } break; case METAKOM_WAIT_START_WORD: - if(metakom_process_bit(metakom, polarity, time, &high_time, &low_time)) { + if(metakom_process_bit(metakom, level, duration, &high_time, &low_time)) { if(low_time < (metakom->period_time / 2)) { metakom->tmp_data = (metakom->tmp_data << 1) | 0b0; } else { @@ -204,13 +179,13 @@ static void metakom_pulse(void* context, bool polarity, uint32_t time) { metakom->tmp_data = 0; metakom->state = METAKOM_READ_WORD; } else { - metakom_reset(metakom); + protocol_metakom_decoder_start(proto); } } } break; case METAKOM_READ_WORD: - if(metakom_process_bit(metakom, polarity, time, &high_time, &low_time)) { + if(metakom_process_bit(metakom, level, duration, &high_time, &low_time)) { if(low_time < (metakom->period_time / 2)) { metakom->tmp_data = (metakom->tmp_data << 1) | 0b0; } else { @@ -220,7 +195,7 @@ static void metakom_pulse(void* context, bool polarity, uint32_t time) { if(metakom->tmp_counter == 8) { if(metakom_parity_check(metakom->tmp_data)) { - metakom->key_data = (metakom->key_data << 8) | metakom->tmp_data; + proto->data = (proto->data << 8) | metakom->tmp_data; metakom->key_data_index++; metakom->tmp_data = 0; metakom->tmp_counter = 0; @@ -230,17 +205,17 @@ static void metakom_pulse(void* context, bool polarity, uint32_t time) { if(high_time > metakom->period_time) { metakom->state = METAKOM_READ_STOP_WORD; } else { - metakom_reset(metakom); + protocol_metakom_decoder_start(proto); } } } else { - metakom_reset(metakom); + protocol_metakom_decoder_start(proto); } } } break; case METAKOM_READ_STOP_WORD: - if(metakom_process_bit(metakom, polarity, time, &high_time, &low_time)) { + if(metakom_process_bit(metakom, level, duration, &high_time, &low_time)) { if(low_time < (metakom->period_time / 2)) { metakom->tmp_data = (metakom->tmp_data << 1) | 0b0; } else { @@ -250,12 +225,96 @@ static void metakom_pulse(void* context, bool polarity, uint32_t time) { if(metakom->tmp_counter == 3) { if(metakom->tmp_data == 0b010) { - metakom->ready = true; + ready = true; } else { - metakom_reset(metakom); + protocol_metakom_decoder_start(proto); } } } break; } + + return ready; +} + +static bool protocol_metakom_encoder_start(ProtocolMetakom* proto) { + proto->encoder.index = 0; + return true; +} + +static LevelDuration protocol_metakom_encoder_yield(ProtocolMetakom* proto) { + LevelDuration result; + + if(proto->encoder.index == 0) { + // sync bit + result = level_duration_make(false, METAKOM_PERIOD); + } else if(proto->encoder.index >= 1 && proto->encoder.index <= 6) { + // start word (0b010) + switch(proto->encoder.index) { + case 1: + result = level_duration_make(true, METAKOM_0_LOW); + break; + case 2: + result = level_duration_make(false, METAKOM_0_HI); + break; + case 3: + result = level_duration_make(true, METAKOM_1_LOW); + break; + case 4: + result = level_duration_make(false, METAKOM_1_HI); + break; + case 5: + result = level_duration_make(true, METAKOM_0_LOW); + break; + case 6: + result = level_duration_make(false, METAKOM_0_HI); + break; + } + } else { + // data + uint8_t data_start_index = proto->encoder.index - 7; + bool clock_polarity = (data_start_index) % 2; + uint8_t bit_index = (data_start_index) / 2; + bool bit_value = (proto->data >> (32 - 1 - bit_index)) & 1; + + if(!clock_polarity) { + if(bit_value) { + result = level_duration_make(true, METAKOM_1_LOW); + } else { + result = level_duration_make(true, METAKOM_0_LOW); + } + } else { + if(bit_value) { + result = level_duration_make(false, METAKOM_1_HI); + } else { + result = level_duration_make(false, METAKOM_0_HI); + } + } + } + + proto->encoder.index++; + if(proto->encoder.index >= (1 + 3 * 2 + 32 * 2)) { + proto->encoder.index = 0; + } + + return result; } + +const ProtocolBase protocol_metakom = { + .name = "Metakom", + .manufacturer = "Metakom", + .data_size = METAKOM_DATA_SIZE, + .alloc = (ProtocolAlloc)protocol_metakom_alloc, + .free = (ProtocolFree)protocol_metakom_free, + .get_data = (ProtocolGetData)protocol_metakom_get_data, + .decoder = + { + .start = (ProtocolDecoderStart)protocol_metakom_decoder_start, + .feed = (ProtocolDecoderFeed)protocol_metakom_decoder_feed, + }, + .encoder = + { + .start = (ProtocolEncoderStart)protocol_metakom_encoder_start, + .yield = (ProtocolEncoderYield)protocol_metakom_encoder_yield, + }, +}; \ No newline at end of file diff --git a/lib/one_wire/ibutton/protocols/protocol_metakom.h b/lib/one_wire/ibutton/protocols/protocol_metakom.h new file mode 100644 index 00000000000..5e44a2a8ce5 --- /dev/null +++ b/lib/one_wire/ibutton/protocols/protocol_metakom.h @@ -0,0 +1,4 @@ +#pragma once +#include "toolbox/protocols/protocol.h" + +extern const ProtocolBase protocol_metakom; \ No newline at end of file diff --git a/lib/one_wire/ibutton/pulse_protocols/protocol_cyfral.c b/lib/one_wire/ibutton/pulse_protocols/protocol_cyfral.c deleted file mode 100644 index 7c2897907be..00000000000 --- a/lib/one_wire/ibutton/pulse_protocols/protocol_cyfral.c +++ /dev/null @@ -1,256 +0,0 @@ -#include "protocol_cyfral.h" -#include -#include -#include -#include - -#define CYFRAL_DATA_SIZE 2 -#define CYFRAL_MAX_PERIOD_US 230 - -typedef enum { - CYFRAL_BIT_WAIT_FRONT_HIGH, - CYFRAL_BIT_WAIT_FRONT_LOW, -} CyfralBitState; - -typedef enum { - CYFRAL_WAIT_START_NIBBLE, - CYFRAL_READ_NIBBLE, - CYFRAL_READ_STOP_NIBBLE, -} CyfralState; - -struct ProtocolCyfral { - PulseProtocol* protocol; - - CyfralState state; - CyfralBitState bit_state; - - // ready flag, key is read and valid - // TODO: atomic access - bool ready; - // key data storage - uint16_t key_data; - // high + low period time - uint32_t period_time; - // temporary nibble storage - uint8_t nibble; - // data valid flag - // MUST be checked only in READ_STOP_NIBBLE state - bool data_valid; - // nibble index, we expect 8 nibbles - uint8_t index; - // bit index in nibble, 4 bit per nibble - uint8_t bit_index; - // max period, 230us x clock per us - uint32_t max_period; -}; - -static void cyfral_pulse(void* context, bool polarity, uint32_t length); -static void cyfral_reset(void* context); -static void cyfral_get_data(void* context, uint8_t* data, size_t length); -static bool cyfral_decoded(void* context); - -ProtocolCyfral* protocol_cyfral_alloc() { - ProtocolCyfral* cyfral = malloc(sizeof(ProtocolCyfral)); - cyfral_reset(cyfral); - - cyfral->protocol = pulse_protocol_alloc(); - - pulse_protocol_set_context(cyfral->protocol, cyfral); - pulse_protocol_set_pulse_cb(cyfral->protocol, cyfral_pulse); - pulse_protocol_set_reset_cb(cyfral->protocol, cyfral_reset); - pulse_protocol_set_get_data_cb(cyfral->protocol, cyfral_get_data); - pulse_protocol_set_decoded_cb(cyfral->protocol, cyfral_decoded); - - return cyfral; -} - -void protocol_cyfral_free(ProtocolCyfral* cyfral) { - furi_assert(cyfral); - pulse_protocol_free(cyfral->protocol); - free(cyfral); -} - -PulseProtocol* protocol_cyfral_get_protocol(ProtocolCyfral* cyfral) { - furi_assert(cyfral); - return cyfral->protocol; -} - -static void cyfral_get_data(void* context, uint8_t* data, size_t length) { - furi_assert(context); - furi_check(length >= CYFRAL_DATA_SIZE); - ProtocolCyfral* cyfral = context; - memcpy(data, &cyfral->key_data, CYFRAL_DATA_SIZE); -} - -static bool cyfral_decoded(void* context) { - furi_assert(context); - ProtocolCyfral* cyfral = context; - bool decoded = cyfral->ready; - return decoded; -} - -static void cyfral_reset(void* context) { - furi_assert(context); - ProtocolCyfral* cyfral = context; - cyfral->state = CYFRAL_WAIT_START_NIBBLE; - cyfral->bit_state = CYFRAL_BIT_WAIT_FRONT_LOW; - - cyfral->period_time = 0; - cyfral->bit_index = 0; - cyfral->ready = false; - cyfral->index = 0; - - cyfral->key_data = 0; - cyfral->nibble = 0; - cyfral->data_valid = true; - - cyfral->max_period = CYFRAL_MAX_PERIOD_US * furi_hal_cortex_instructions_per_microsecond(); -} - -static bool cyfral_process_bit( - ProtocolCyfral* cyfral, - bool polarity, - uint32_t length, - bool* bit_ready, - bool* bit_value) { - bool result = true; - *bit_ready = false; - - // bit start from low - switch(cyfral->bit_state) { - case CYFRAL_BIT_WAIT_FRONT_LOW: - if(polarity == true) { - cyfral->period_time += length; - - *bit_ready = true; - if(cyfral->period_time <= cyfral->max_period) { - if((cyfral->period_time / 2) > length) { - *bit_value = false; - } else { - *bit_value = true; - } - } else { - result = false; - } - - cyfral->bit_state = CYFRAL_BIT_WAIT_FRONT_HIGH; - } else { - result = false; - } - break; - case CYFRAL_BIT_WAIT_FRONT_HIGH: - if(polarity == false) { - cyfral->period_time = length; - cyfral->bit_state = CYFRAL_BIT_WAIT_FRONT_LOW; - } else { - result = false; - } - break; - } - - return result; -} - -static void cyfral_pulse(void* context, bool polarity, uint32_t length) { - furi_assert(context); - ProtocolCyfral* cyfral = context; - - bool bit_ready; - bool bit_value; - - if(cyfral->ready) return; - - switch(cyfral->state) { - case CYFRAL_WAIT_START_NIBBLE: - // wait for start word - if(cyfral_process_bit(cyfral, polarity, length, &bit_ready, &bit_value)) { - if(bit_ready) { - cyfral->nibble = ((cyfral->nibble << 1) | bit_value) & 0x0F; - if(cyfral->nibble == 0b0001) { - cyfral->nibble = 0; - cyfral->state = CYFRAL_READ_NIBBLE; - } - } - } else { - cyfral_reset(cyfral); - } - - break; - case CYFRAL_READ_NIBBLE: - // read nibbles - if(cyfral_process_bit(cyfral, polarity, length, &bit_ready, &bit_value)) { - if(bit_ready) { - cyfral->nibble = (cyfral->nibble << 1) | bit_value; - - cyfral->bit_index++; - - //convert every nibble to 2-bit index - if(cyfral->bit_index == 4) { - switch(cyfral->nibble) { - case 0b1110: - cyfral->key_data = (cyfral->key_data << 2) | 0b11; - break; - case 0b1101: - cyfral->key_data = (cyfral->key_data << 2) | 0b10; - break; - case 0b1011: - cyfral->key_data = (cyfral->key_data << 2) | 0b01; - break; - case 0b0111: - cyfral->key_data = (cyfral->key_data << 2) | 0b00; - break; - default: - cyfral->data_valid = false; - break; - } - - cyfral->nibble = 0; - cyfral->bit_index = 0; - cyfral->index++; - } - - // succefully read 8 nibbles - if(cyfral->index == 8) { - cyfral->state = CYFRAL_READ_STOP_NIBBLE; - } - } - } else { - cyfral_reset(cyfral); - } - break; - case CYFRAL_READ_STOP_NIBBLE: - // read stop nibble - if(cyfral_process_bit(cyfral, polarity, length, &bit_ready, &bit_value)) { - if(bit_ready) { - cyfral->nibble = ((cyfral->nibble << 1) | bit_value) & 0x0F; - cyfral->bit_index++; - - switch(cyfral->bit_index) { - case 0: - case 1: - case 2: - case 3: - break; - case 4: - if(cyfral->nibble == 0b0001) { - // validate data - if(cyfral->data_valid) { - cyfral->ready = true; - } else { - cyfral_reset(cyfral); - } - } else { - cyfral_reset(cyfral); - } - break; - default: - cyfral_reset(cyfral); - break; - } - } - } else { - cyfral_reset(cyfral); - } - break; - } -} diff --git a/lib/one_wire/ibutton/pulse_protocols/protocol_cyfral.h b/lib/one_wire/ibutton/pulse_protocols/protocol_cyfral.h deleted file mode 100644 index 10305da12f5..00000000000 --- a/lib/one_wire/ibutton/pulse_protocols/protocol_cyfral.h +++ /dev/null @@ -1,38 +0,0 @@ -/** - * @file protocol_cyfral.h - * - * Cyfral pulse format decoder - */ - -#pragma once -#include -#include "../../pulse_protocols/pulse_protocol.h" - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct ProtocolCyfral ProtocolCyfral; - -/** - * Allocate decoder - * @return ProtocolCyfral* - */ -ProtocolCyfral* protocol_cyfral_alloc(); - -/** - * Deallocate decoder - * @param cyfral - */ -void protocol_cyfral_free(ProtocolCyfral* cyfral); - -/** - * Get protocol interface - * @param cyfral - * @return PulseProtocol* - */ -PulseProtocol* protocol_cyfral_get_protocol(ProtocolCyfral* cyfral); - -#ifdef __cplusplus -} -#endif diff --git a/lib/one_wire/ibutton/pulse_protocols/protocol_metakom.h b/lib/one_wire/ibutton/pulse_protocols/protocol_metakom.h deleted file mode 100644 index fdc45769538..00000000000 --- a/lib/one_wire/ibutton/pulse_protocols/protocol_metakom.h +++ /dev/null @@ -1,38 +0,0 @@ -/** - * @file protocol_metakom.h - * - * Metakom pulse format decoder - */ - -#pragma once -#include -#include "../../pulse_protocols/pulse_protocol.h" - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct ProtocolMetakom ProtocolMetakom; - -/** - * Allocate decoder - * @return ProtocolMetakom* - */ -ProtocolMetakom* protocol_metakom_alloc(); - -/** - * Free decoder - * @param metakom - */ -void protocol_metakom_free(ProtocolMetakom* metakom); - -/** - * Get protocol interface - * @param metakom - * @return PulseProtocol* - */ -PulseProtocol* protocol_metakom_get_protocol(ProtocolMetakom* metakom); - -#ifdef __cplusplus -} -#endif diff --git a/lib/one_wire/pulse_protocols/pulse_decoder.c b/lib/one_wire/pulse_protocols/pulse_decoder.c deleted file mode 100644 index c7d3b09ecb8..00000000000 --- a/lib/one_wire/pulse_protocols/pulse_decoder.c +++ /dev/null @@ -1,66 +0,0 @@ -#include -#include "pulse_decoder.h" -#include -#include - -#define MAX_PROTOCOL 5 - -struct PulseDecoder { - PulseProtocol* protocols[MAX_PROTOCOL]; -}; - -PulseDecoder* pulse_decoder_alloc() { - PulseDecoder* decoder = malloc(sizeof(PulseDecoder)); - memset(decoder, 0, sizeof(PulseDecoder)); - return decoder; -} - -void pulse_decoder_free(PulseDecoder* reader) { - furi_assert(reader); - free(reader); -} - -void pulse_decoder_add_protocol(PulseDecoder* reader, PulseProtocol* protocol, int32_t index) { - furi_check(index < MAX_PROTOCOL); - furi_check(reader->protocols[index] == NULL); - reader->protocols[index] = protocol; -} - -void pulse_decoder_process_pulse(PulseDecoder* reader, bool polarity, uint32_t length) { - furi_assert(reader); - for(size_t index = 0; index < MAX_PROTOCOL; index++) { - if(reader->protocols[index] != NULL) { - pulse_protocol_process_pulse(reader->protocols[index], polarity, length); - } - } -} - -int32_t pulse_decoder_get_decoded_index(PulseDecoder* reader) { - furi_assert(reader); - int32_t decoded = -1; - for(size_t index = 0; index < MAX_PROTOCOL; index++) { - if(reader->protocols[index] != NULL) { - if(pulse_protocol_decoded(reader->protocols[index])) { - decoded = index; - break; - } - } - } - - return decoded; -} - -void pulse_decoder_reset(PulseDecoder* reader) { - furi_assert(reader); - for(size_t index = 0; index < MAX_PROTOCOL; index++) { - if(reader->protocols[index] != NULL) { - pulse_protocol_reset(reader->protocols[index]); - } - } -} - -void pulse_decoder_get_data(PulseDecoder* reader, int32_t index, uint8_t* data, size_t length) { - furi_assert(reader); - furi_check(reader->protocols[index] != NULL); - pulse_protocol_get_data(reader->protocols[index], data, length); -} diff --git a/lib/one_wire/pulse_protocols/pulse_decoder.h b/lib/one_wire/pulse_protocols/pulse_decoder.h deleted file mode 100644 index dbaef52b5e4..00000000000 --- a/lib/one_wire/pulse_protocols/pulse_decoder.h +++ /dev/null @@ -1,70 +0,0 @@ -/** - * @file pulse_decoder.h - * - * Generic pulse protocol decoder library - */ - -#pragma once -#include -#include -#include "pulse_protocol.h" - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct PulseDecoder PulseDecoder; - -/** - * Allocate decoder - * @return PulseDecoder* - */ -PulseDecoder* pulse_decoder_alloc(); - -/** - * Deallocate decoder - * @param decoder - */ -void pulse_decoder_free(PulseDecoder* decoder); - -/** - * Add protocol to decoder - * @param decoder - * @param protocol protocol implementation - * @param index protocol index, should not be repeated - */ -void pulse_decoder_add_protocol(PulseDecoder* decoder, PulseProtocol* protocol, int32_t index); - -/** - * Push and process pulse with decoder - * @param decoder - * @param polarity - * @param length - */ -void pulse_decoder_process_pulse(PulseDecoder* decoder, bool polarity, uint32_t length); - -/** - * Get indec of decoded protocol - * @param decoder - * @return int32_t, -1 if nothing decoded, or index of decoded protocol - */ -int32_t pulse_decoder_get_decoded_index(PulseDecoder* decoder); - -/** - * Reset all protocols in decoder - * @param decoder - */ -void pulse_decoder_reset(PulseDecoder* decoder); - -/** - * Get decoded data from protocol - * @param decoder - * @param index - * @param data - * @param length - */ -void pulse_decoder_get_data(PulseDecoder* decoder, int32_t index, uint8_t* data, size_t length); - -#ifdef __cplusplus -} -#endif diff --git a/lib/one_wire/pulse_protocols/pulse_protocol.c b/lib/one_wire/pulse_protocols/pulse_protocol.c deleted file mode 100644 index 76feba11358..00000000000 --- a/lib/one_wire/pulse_protocols/pulse_protocol.c +++ /dev/null @@ -1,67 +0,0 @@ -#include "pulse_protocol.h" -#include -#include - -struct PulseProtocol { - void* context; - PulseProtocolPulseCallback pulse_cb; - PulseProtocolResetCallback reset_cb; - PulseProtocolGetDataCallback get_data_cb; - PulseProtocolDecodedCallback decoded_cb; -}; - -PulseProtocol* pulse_protocol_alloc() { - PulseProtocol* protocol = malloc(sizeof(PulseProtocol)); - memset(protocol, 0, sizeof(PulseProtocol)); - return protocol; -} - -void pulse_protocol_set_context(PulseProtocol* protocol, void* context) { - protocol->context = context; -} - -void pulse_protocol_set_pulse_cb(PulseProtocol* protocol, PulseProtocolPulseCallback callback) { - protocol->pulse_cb = callback; -} - -void pulse_protocol_set_reset_cb(PulseProtocol* protocol, PulseProtocolResetCallback callback) { - protocol->reset_cb = callback; -} - -void pulse_protocol_set_get_data_cb(PulseProtocol* protocol, PulseProtocolGetDataCallback callback) { - protocol->get_data_cb = callback; -} - -void pulse_protocol_set_decoded_cb(PulseProtocol* protocol, PulseProtocolDecodedCallback callback) { - protocol->decoded_cb = callback; -} - -void pulse_protocol_free(PulseProtocol* protocol) { - free(protocol); -} - -void pulse_protocol_process_pulse(PulseProtocol* protocol, bool polarity, uint32_t length) { - if(protocol->pulse_cb != NULL) { - protocol->pulse_cb(protocol->context, polarity, length); - } -} - -void pulse_protocol_reset(PulseProtocol* protocol) { - if(protocol->reset_cb != NULL) { - protocol->reset_cb(protocol->context); - } -} - -bool pulse_protocol_decoded(PulseProtocol* protocol) { - bool result = false; - if(protocol->decoded_cb != NULL) { - result = protocol->decoded_cb(protocol->context); - } - return result; -} - -void pulse_protocol_get_data(PulseProtocol* protocol, uint8_t* data, size_t length) { - if(protocol->get_data_cb != NULL) { - protocol->get_data_cb(protocol->context, data, length); - } -} diff --git a/lib/one_wire/pulse_protocols/pulse_protocol.h b/lib/one_wire/pulse_protocols/pulse_protocol.h deleted file mode 100644 index bfce0e76d8b..00000000000 --- a/lib/one_wire/pulse_protocols/pulse_protocol.h +++ /dev/null @@ -1,122 +0,0 @@ -/** - * @file pulse_protocol.h - * - * Generic pulse protocol decoder library, protocol interface - */ - -#pragma once -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * Anonymous PulseProtocol struct - */ -typedef struct PulseProtocol PulseProtocol; - -/** - * Process pulse callback - */ -typedef void (*PulseProtocolPulseCallback)(void* context, bool polarity, uint32_t length); - -/** - * Reset protocol callback - */ -typedef void (*PulseProtocolResetCallback)(void* context); - -/** - * Get decoded data callback - */ -typedef void (*PulseProtocolGetDataCallback)(void* context, uint8_t* data, size_t length); - -/** - * Is protocol decoded callback - */ -typedef bool (*PulseProtocolDecodedCallback)(void* context); - -/** - * Allocate protocol - * @return PulseProtocol* - */ -PulseProtocol* pulse_protocol_alloc(); - -/** - * Deallocate protocol - * @param protocol - */ -void pulse_protocol_free(PulseProtocol* protocol); - -/** - * Set context for callbacks - * @param protocol - * @param context - */ -void pulse_protocol_set_context(PulseProtocol* protocol, void* context); - -/** - * Set "Process pulse" callback. Called from the decoder when a new pulse is received. - * @param protocol - * @param callback - */ -void pulse_protocol_set_pulse_cb(PulseProtocol* protocol, PulseProtocolPulseCallback callback); - -/** - * Set "Reset protocol" callback. Called from the decoder when the decoder is reset. - * @param protocol - * @param callback - */ -void pulse_protocol_set_reset_cb(PulseProtocol* protocol, PulseProtocolResetCallback callback); - -/** - * Set "Get decoded data" callback. Called from the decoder when the decoder wants to get decoded data. - * @param protocol - * @param callback - */ -void pulse_protocol_set_get_data_cb(PulseProtocol* protocol, PulseProtocolGetDataCallback callback); - -/** - * Set "Is protocol decoded" callback. Called from the decoder when the decoder wants to know if a protocol has been decoded. - * @param protocol - * @param callback - */ -void pulse_protocol_set_decoded_cb(PulseProtocol* protocol, PulseProtocolDecodedCallback callback); - -/** - * Part of decoder interface. - * @param protocol - * @param polarity - * @param length - */ -void pulse_protocol_process_pulse(PulseProtocol* protocol, bool polarity, uint32_t length); - -/** - * Part of decoder interface. - * @param protocol - * @return true - * @return false - */ -bool pulse_protocol_decoded(PulseProtocol* protocol); - -/** - * Part of decoder interface. - * @param protocol - * @return true - * @return false - */ -void pulse_protocol_get_data(PulseProtocol* protocol, uint8_t* data, size_t length); - -/** - * Part of decoder interface. - * @param protocol - * @return true - * @return false - */ -void pulse_protocol_reset(PulseProtocol* protocol); - -#ifdef __cplusplus -} -#endif diff --git a/lib/subghz/protocols/keeloq_common.c b/lib/subghz/protocols/keeloq_common.c index 7e864a325bd..0f8c763db24 100644 --- a/lib/subghz/protocols/keeloq_common.c +++ b/lib/subghz/protocols/keeloq_common.c @@ -5,6 +5,10 @@ #include #include +#define bit(x, n) (((x) >> (n)) & 1) +#define g5(x, a, b, c, d, e) \ + (bit(x, a) + bit(x, b) * 2 + bit(x, c) * 4 + bit(x, d) * 8 + bit(x, e) * 16) + /** Simple Learning Encrypt * @param data - 0xBSSSCCCC, B(4bit) key, S(10bit) serial&0x3FF, C(16bit) counter * @param key - manufacture (64bit) diff --git a/lib/subghz/protocols/keeloq_common.h b/lib/subghz/protocols/keeloq_common.h index 50d447ed725..aa07a7f5858 100644 --- a/lib/subghz/protocols/keeloq_common.h +++ b/lib/subghz/protocols/keeloq_common.h @@ -11,9 +11,6 @@ * */ #define KEELOQ_NLF 0x3A5C742E -#define bit(x, n) (((x) >> (n)) & 1) -#define g5(x, a, b, c, d, e) \ - (bit(x, a) + bit(x, b) * 2 + bit(x, c) * 4 + bit(x, d) * 8 + bit(x, e) * 16) /* * KeeLoq learning types diff --git a/lib/toolbox/buffer_stream.c b/lib/toolbox/buffer_stream.c new file mode 100644 index 00000000000..66d2109630b --- /dev/null +++ b/lib/toolbox/buffer_stream.c @@ -0,0 +1,145 @@ +#include "buffer_stream.h" +#include + +struct Buffer { + volatile bool occupied; + volatile size_t size; + uint8_t* data; + size_t max_data_size; +}; + +struct BufferStream { + size_t stream_overrun_count; + StreamBufferHandle_t stream; + + size_t index; + Buffer* buffers; + size_t max_buffers_count; +}; + +bool buffer_write(Buffer* buffer, const uint8_t* data, size_t size) { + if(buffer->occupied) { + return false; + } + if((buffer->size + size) > buffer->max_data_size) { + return false; + } + memcpy(buffer->data + buffer->size, data, size); + buffer->size += size; + return true; +} + +uint8_t* buffer_get_data(Buffer* buffer) { + return buffer->data; +} + +size_t buffer_get_size(Buffer* buffer) { + return buffer->size; +} + +void buffer_reset(Buffer* buffer) { + buffer->occupied = false; + buffer->size = 0; +} + +BufferStream* buffer_stream_alloc(size_t buffer_size, size_t buffers_count) { + furi_assert(buffer_size > 0); + furi_assert(buffers_count > 0); + BufferStream* buffer_stream = malloc(sizeof(BufferStream)); + buffer_stream->max_buffers_count = buffers_count; + buffer_stream->buffers = malloc(sizeof(Buffer) * buffer_stream->max_buffers_count); + for(size_t i = 0; i < buffer_stream->max_buffers_count; i++) { + buffer_stream->buffers[i].occupied = false; + buffer_stream->buffers[i].size = 0; + buffer_stream->buffers[i].data = malloc(buffer_size); + buffer_stream->buffers[i].max_data_size = buffer_size; + } + buffer_stream->stream = xStreamBufferCreate( + sizeof(BufferStream*) * buffer_stream->max_buffers_count, sizeof(BufferStream*)); + buffer_stream->stream_overrun_count = 0; + buffer_stream->index = 0; + + return buffer_stream; +} + +void buffer_stream_free(BufferStream* buffer_stream) { + for(size_t i = 0; i < buffer_stream->max_buffers_count; i++) { + free(buffer_stream->buffers[i].data); + } + vStreamBufferDelete(buffer_stream->stream); + free(buffer_stream->buffers); + free(buffer_stream); +} + +static inline int8_t buffer_stream_get_free_buffer(BufferStream* buffer_stream) { + int8_t id = -1; + for(size_t i = 0; i < buffer_stream->max_buffers_count; i++) { + if(buffer_stream->buffers[i].occupied == false) { + id = i; + break; + } + } + + return id; +} + +bool buffer_stream_send_from_isr( + BufferStream* buffer_stream, + const uint8_t* data, + size_t size, + BaseType_t* const task_woken) { + Buffer* buffer = &buffer_stream->buffers[buffer_stream->index]; + bool result = true; + + // write to buffer + if(!buffer_write(buffer, data, size)) { + // if buffer is full - send it + buffer->occupied = true; + // we always have space for buffer in stream + xStreamBufferSendFromISR(buffer_stream->stream, &buffer, sizeof(Buffer*), task_woken); + + // get new buffer from the pool + int8_t index = buffer_stream_get_free_buffer(buffer_stream); + + // check that we have valid buffer + if(index == -1) { + // no free buffer + buffer_stream->stream_overrun_count++; + result = false; + } else { + // write to new buffer + buffer_stream->index = index; + buffer = &buffer_stream->buffers[buffer_stream->index]; + buffer_write(buffer, data, size); + } + } + + return result; +} + +Buffer* buffer_stream_receive(BufferStream* buffer_stream, TickType_t timeout) { + Buffer* buffer; + size_t size = xStreamBufferReceive(buffer_stream->stream, &buffer, sizeof(Buffer*), timeout); + + if(size == sizeof(Buffer*)) { + return buffer; + } else { + return NULL; + } +} + +size_t buffer_stream_get_overrun_count(BufferStream* buffer_stream) { + return buffer_stream->stream_overrun_count; +} + +void buffer_stream_reset(BufferStream* buffer_stream) { + FURI_CRITICAL_ENTER(); + BaseType_t xReturn = xStreamBufferReset(buffer_stream->stream); + furi_assert(xReturn == pdPASS); + UNUSED(xReturn); + buffer_stream->stream_overrun_count = 0; + for(size_t i = 0; i < buffer_stream->max_buffers_count; i++) { + buffer_reset(&buffer_stream->buffers[i]); + } + FURI_CRITICAL_EXIT(); +} \ No newline at end of file diff --git a/lib/toolbox/buffer_stream.h b/lib/toolbox/buffer_stream.h new file mode 100644 index 00000000000..d4c3cddf7cc --- /dev/null +++ b/lib/toolbox/buffer_stream.h @@ -0,0 +1,94 @@ +/** + * @file buffer_stream.h + * + * This file implements the concept of a buffer stream. + * Data is written to the buffer until the buffer is full. + * Then the buffer pointer is written to the stream, and the new write buffer is taken from the buffer pool. + * After the buffer has been read by the receiving thread, it is sent to the free buffer pool. + * + * This will speed up sending large chunks of data between threads, compared to using a stream directly. + */ +#pragma once +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct Buffer Buffer; + +/** + * @brief Get buffer data pointer + * @param buffer + * @return uint8_t* + */ +uint8_t* buffer_get_data(Buffer* buffer); + +/** + * @brief Get buffer size + * @param buffer + * @return size_t + */ +size_t buffer_get_size(Buffer* buffer); + +/** + * @brief Reset buffer and send to free buffer pool + * @param buffer + */ +void buffer_reset(Buffer* buffer); + +typedef struct BufferStream BufferStream; + +/** + * @brief Allocate a new BufferStream instance + * @param buffer_size + * @param buffers_count + * @return BufferStream* + */ +BufferStream* buffer_stream_alloc(size_t buffer_size, size_t buffers_count); + +/** + * @brief Free a BufferStream instance + * @param buffer_stream + */ +void buffer_stream_free(BufferStream* buffer_stream); + +/** + * @brief Write data to buffer stream, from ISR context + * Data will be written to the buffer until the buffer is full, and only then will the buffer be sent. + * @param buffer_stream + * @param data + * @param size + * @param task_woken + * @return bool + */ +bool buffer_stream_send_from_isr( + BufferStream* buffer_stream, + const uint8_t* data, + size_t size, + BaseType_t* const task_woken); + +/** + * @brief Receive buffer from stream + * @param buffer_stream + * @param timeout + * @return Buffer* + */ +Buffer* buffer_stream_receive(BufferStream* buffer_stream, TickType_t timeout); + +/** + * @brief Get stream overrun count + * @param buffer_stream + * @return size_t + */ +size_t buffer_stream_get_overrun_count(BufferStream* buffer_stream); + +/** + * @brief Reset stream and buffer pool + * @param buffer_stream + */ +void buffer_stream_reset(BufferStream* buffer_stream); + +#ifdef __cplusplus +} +#endif diff --git a/lib/toolbox/profiler.c b/lib/toolbox/profiler.c new file mode 100644 index 00000000000..96f38dce2c5 --- /dev/null +++ b/lib/toolbox/profiler.c @@ -0,0 +1,87 @@ +#include "profiler.h" +#include +#include +#include +#include + +typedef struct { + uint32_t start; + uint32_t length; + uint32_t count; +} ProfilerRecord; + +DICT_DEF2(ProfilerRecordDict, const char*, M_CSTR_OPLIST, ProfilerRecord, M_POD_OPLIST) +#define M_OPL_ProfilerRecord_t() DICT_OPLIST(ProfilerRecord, M_CSTR_OPLIST, M_POD_OPLIST) + +struct Profiler { + ProfilerRecordDict_t records; +}; + +Profiler* profiler_alloc() { + Profiler* profiler = malloc(sizeof(Profiler)); + ProfilerRecordDict_init(profiler->records); + return profiler; +} + +void profiler_free(Profiler* profiler) { + ProfilerRecordDict_clear(profiler->records); + free(profiler); +} + +void profiler_prealloc(Profiler* profiler, const char* key) { + ProfilerRecord record = { + .start = 0, + .length = 0, + .count = 0, + }; + + ProfilerRecordDict_set_at(profiler->records, key, record); +} + +void profiler_start(Profiler* profiler, const char* key) { + ProfilerRecord* record = ProfilerRecordDict_get(profiler->records, key); + if(record == NULL) { + profiler_prealloc(profiler, key); + record = ProfilerRecordDict_get(profiler->records, key); + } + + furi_check(record->start == 0); + record->start = DWT->CYCCNT; +} + +void profiler_stop(Profiler* profiler, const char* key) { + ProfilerRecord* record = ProfilerRecordDict_get(profiler->records, key); + furi_check(record != NULL); + + record->length += DWT->CYCCNT - record->start; + record->start = 0; + record->count++; +} + +void profiler_dump(Profiler* profiler) { + printf("Profiler:\r\n"); + + ProfilerRecordDict_it_t it; + for(ProfilerRecordDict_it(it, profiler->records); !ProfilerRecordDict_end_p(it); + ProfilerRecordDict_next(it)) { + const ProfilerRecordDict_itref_t* itref = ProfilerRecordDict_cref(it); + + uint32_t count = itref->value.count; + + uint32_t clocks = itref->value.length; + double us = (double)clocks / (double)64.0; + double ms = (double)clocks / (double)64000.0; + double s = (double)clocks / (double)64000000.0; + + printf("\t%s[%lu]: %f s, %f ms, %f us, %lu clk\r\n", itref->key, count, s, ms, us, clocks); + + if(count > 1) { + us /= (double)count; + ms /= (double)count; + s /= (double)count; + clocks /= count; + + printf("\t%s[1]: %f s, %f ms, %f us, %lu clk\r\n", itref->key, s, ms, us, clocks); + } + } +} diff --git a/lib/toolbox/profiler.h b/lib/toolbox/profiler.h new file mode 100644 index 00000000000..840146332c5 --- /dev/null +++ b/lib/toolbox/profiler.h @@ -0,0 +1,23 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct Profiler Profiler; + +Profiler* profiler_alloc(); + +void profiler_free(Profiler* profiler); + +void profiler_prealloc(Profiler* profiler, const char* key); + +void profiler_start(Profiler* profiler, const char* key); + +void profiler_stop(Profiler* profiler, const char* key); + +void profiler_dump(Profiler* profiler); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/toolbox/protocols/protocol.h b/lib/toolbox/protocols/protocol.h new file mode 100644 index 00000000000..56bb1b74ca2 --- /dev/null +++ b/lib/toolbox/protocols/protocol.h @@ -0,0 +1,46 @@ +#pragma once +#include +#include +#include +#include +#include + +typedef void* (*ProtocolAlloc)(void); +typedef void (*ProtocolFree)(void* protocol); +typedef uint8_t* (*ProtocolGetData)(void* protocol); + +typedef void (*ProtocolDecoderStart)(void* protocol); +typedef bool (*ProtocolDecoderFeed)(void* protocol, bool level, uint32_t duration); + +typedef bool (*ProtocolEncoderStart)(void* protocol); +typedef LevelDuration (*ProtocolEncoderYield)(void* protocol); + +typedef void (*ProtocolRenderData)(void* protocol, string_t result); +typedef bool (*ProtocolWriteData)(void* protocol, void* data); + +typedef struct { + ProtocolDecoderStart start; + ProtocolDecoderFeed feed; +} ProtocolDecoder; + +typedef struct { + ProtocolEncoderStart start; + ProtocolEncoderYield yield; +} ProtocolEncoder; + +typedef struct { + const size_t data_size; + const char* name; + const char* manufacturer; + const uint32_t features; + const uint8_t validate_count; + + ProtocolAlloc alloc; + ProtocolFree free; + ProtocolGetData get_data; + ProtocolDecoder decoder; + ProtocolEncoder encoder; + ProtocolRenderData render_data; + ProtocolRenderData render_brief_data; + ProtocolWriteData write_data; +} ProtocolBase; \ No newline at end of file diff --git a/lib/toolbox/protocols/protocol_dict.c b/lib/toolbox/protocols/protocol_dict.c new file mode 100644 index 00000000000..2a022cc406b --- /dev/null +++ b/lib/toolbox/protocols/protocol_dict.c @@ -0,0 +1,226 @@ +#include +#include "protocol_dict.h" + +struct ProtocolDict { + const ProtocolBase** base; + size_t count; + void** data; +}; + +ProtocolDict* protocol_dict_alloc(const ProtocolBase** protocols, size_t count) { + ProtocolDict* dict = malloc(sizeof(ProtocolDict)); + dict->base = protocols; + dict->count = count; + dict->data = malloc(sizeof(void*) * dict->count); + + for(size_t i = 0; i < dict->count; i++) { + dict->data[i] = dict->base[i]->alloc(); + } + + return dict; +} + +void protocol_dict_free(ProtocolDict* dict) { + for(size_t i = 0; i < dict->count; i++) { + dict->base[i]->free(dict->data[i]); + } + + free(dict->data); + free(dict); +} + +void protocol_dict_set_data( + ProtocolDict* dict, + size_t protocol_index, + const uint8_t* data, + size_t data_size) { + furi_assert(protocol_index < dict->count); + furi_assert(dict->base[protocol_index]->get_data != NULL); + uint8_t* protocol_data = dict->base[protocol_index]->get_data(dict->data[protocol_index]); + size_t protocol_data_size = dict->base[protocol_index]->data_size; + furi_check(data_size >= protocol_data_size); + memcpy(protocol_data, data, protocol_data_size); +} + +void protocol_dict_get_data( + ProtocolDict* dict, + size_t protocol_index, + uint8_t* data, + size_t data_size) { + furi_assert(protocol_index < dict->count); + furi_assert(dict->base[protocol_index]->get_data != NULL); + uint8_t* protocol_data = dict->base[protocol_index]->get_data(dict->data[protocol_index]); + size_t protocol_data_size = dict->base[protocol_index]->data_size; + furi_check(data_size >= protocol_data_size); + memcpy(data, protocol_data, protocol_data_size); +} + +size_t protocol_dict_get_data_size(ProtocolDict* dict, size_t protocol_index) { + furi_assert(protocol_index < dict->count); + return dict->base[protocol_index]->data_size; +} + +size_t protocol_dict_get_max_data_size(ProtocolDict* dict) { + size_t max_data_size = 0; + for(size_t i = 0; i < dict->count; i++) { + size_t data_size = dict->base[i]->data_size; + if(data_size > max_data_size) { + max_data_size = data_size; + } + } + + return max_data_size; +} + +const char* protocol_dict_get_name(ProtocolDict* dict, size_t protocol_index) { + furi_assert(protocol_index < dict->count); + return dict->base[protocol_index]->name; +} + +const char* protocol_dict_get_manufacturer(ProtocolDict* dict, size_t protocol_index) { + furi_assert(protocol_index < dict->count); + return dict->base[protocol_index]->manufacturer; +} + +void protocol_dict_decoders_start(ProtocolDict* dict) { + for(size_t i = 0; i < dict->count; i++) { + ProtocolDecoderStart fn = dict->base[i]->decoder.start; + + if(fn) { + fn(dict->data[i]); + } + } +} + +uint32_t protocol_dict_get_features(ProtocolDict* dict, size_t protocol_index) { + furi_assert(protocol_index < dict->count); + return dict->base[protocol_index]->features; +} + +ProtocolId protocol_dict_decoders_feed(ProtocolDict* dict, bool level, uint32_t duration) { + bool done = false; + ProtocolId ready_protocol_id = PROTOCOL_NO; + + for(size_t i = 0; i < dict->count; i++) { + ProtocolDecoderFeed fn = dict->base[i]->decoder.feed; + + if(fn) { + if(fn(dict->data[i], level, duration)) { + if(!done) { + ready_protocol_id = i; + done = true; + } + } + } + } + + return ready_protocol_id; +} + +ProtocolId protocol_dict_decoders_feed_by_feature( + ProtocolDict* dict, + uint32_t feature, + bool level, + uint32_t duration) { + bool done = false; + ProtocolId ready_protocol_id = PROTOCOL_NO; + + for(size_t i = 0; i < dict->count; i++) { + uint32_t features = dict->base[i]->features; + if(features & feature) { + ProtocolDecoderFeed fn = dict->base[i]->decoder.feed; + + if(fn) { + if(fn(dict->data[i], level, duration)) { + if(!done) { + ready_protocol_id = i; + done = true; + } + } + } + } + } + + return ready_protocol_id; +} + +ProtocolId protocol_dict_decoders_feed_by_id( + ProtocolDict* dict, + size_t protocol_index, + bool level, + uint32_t duration) { + furi_assert(protocol_index < dict->count); + + ProtocolId ready_protocol_id = PROTOCOL_NO; + ProtocolDecoderFeed fn = dict->base[protocol_index]->decoder.feed; + + if(fn) { + if(fn(dict->data[protocol_index], level, duration)) { + ready_protocol_id = protocol_index; + } + } + + return ready_protocol_id; +} + +bool protocol_dict_encoder_start(ProtocolDict* dict, size_t protocol_index) { + furi_assert(protocol_index < dict->count); + ProtocolEncoderStart fn = dict->base[protocol_index]->encoder.start; + + if(fn) { + return fn(dict->data[protocol_index]); + } else { + return false; + } +} + +LevelDuration protocol_dict_encoder_yield(ProtocolDict* dict, size_t protocol_index) { + furi_assert(protocol_index < dict->count); + ProtocolEncoderYield fn = dict->base[protocol_index]->encoder.yield; + + if(fn) { + return fn(dict->data[protocol_index]); + } else { + return level_duration_reset(); + } +} + +void protocol_dict_render_data(ProtocolDict* dict, string_t result, size_t protocol_index) { + furi_assert(protocol_index < dict->count); + ProtocolRenderData fn = dict->base[protocol_index]->render_data; + + if(fn) { + return fn(dict->data[protocol_index], result); + } +} + +void protocol_dict_render_brief_data(ProtocolDict* dict, string_t result, size_t protocol_index) { + furi_assert(protocol_index < dict->count); + ProtocolRenderData fn = dict->base[protocol_index]->render_brief_data; + + if(fn) { + return fn(dict->data[protocol_index], result); + } +} + +uint32_t protocol_dict_get_validate_count(ProtocolDict* dict, size_t protocol_index) { + furi_assert(protocol_index < dict->count); + return dict->base[protocol_index]->validate_count; +} + +ProtocolId protocol_dict_get_protocol_by_name(ProtocolDict* dict, const char* name) { + for(size_t i = 0; i < dict->count; i++) { + if(strcmp(name, protocol_dict_get_name(dict, i)) == 0) { + return i; + } + } + return PROTOCOL_NO; +} + +bool protocol_dict_get_write_data(ProtocolDict* dict, size_t protocol_index, void* data) { + furi_assert(protocol_index < dict->count); + ProtocolWriteData fn = dict->base[protocol_index]->write_data; + + furi_assert(fn); + return fn(dict->data[protocol_index], data); +} \ No newline at end of file diff --git a/lib/toolbox/protocols/protocol_dict.h b/lib/toolbox/protocols/protocol_dict.h new file mode 100644 index 00000000000..3037ddd5e4c --- /dev/null +++ b/lib/toolbox/protocols/protocol_dict.h @@ -0,0 +1,73 @@ +#pragma once +#include "protocol.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct ProtocolDict ProtocolDict; + +typedef int32_t ProtocolId; + +#define PROTOCOL_NO (-1) +#define PROTOCOL_ALL_FEATURES (0xFFFFFFFF) + +ProtocolDict* protocol_dict_alloc(const ProtocolBase** protocols, size_t protocol_count); + +void protocol_dict_free(ProtocolDict* dict); + +void protocol_dict_set_data( + ProtocolDict* dict, + size_t protocol_index, + const uint8_t* data, + size_t data_size); + +void protocol_dict_get_data( + ProtocolDict* dict, + size_t protocol_index, + uint8_t* data, + size_t data_size); + +size_t protocol_dict_get_data_size(ProtocolDict* dict, size_t protocol_index); + +size_t protocol_dict_get_max_data_size(ProtocolDict* dict); + +const char* protocol_dict_get_name(ProtocolDict* dict, size_t protocol_index); + +const char* protocol_dict_get_manufacturer(ProtocolDict* dict, size_t protocol_index); + +void protocol_dict_decoders_start(ProtocolDict* dict); + +uint32_t protocol_dict_get_features(ProtocolDict* dict, size_t protocol_index); + +ProtocolId protocol_dict_decoders_feed(ProtocolDict* dict, bool level, uint32_t duration); + +ProtocolId protocol_dict_decoders_feed_by_feature( + ProtocolDict* dict, + uint32_t feature, + bool level, + uint32_t duration); + +ProtocolId protocol_dict_decoders_feed_by_id( + ProtocolDict* dict, + size_t protocol_index, + bool level, + uint32_t duration); + +bool protocol_dict_encoder_start(ProtocolDict* dict, size_t protocol_index); + +LevelDuration protocol_dict_encoder_yield(ProtocolDict* dict, size_t protocol_index); + +void protocol_dict_render_data(ProtocolDict* dict, string_t result, size_t protocol_index); + +void protocol_dict_render_brief_data(ProtocolDict* dict, string_t result, size_t protocol_index); + +uint32_t protocol_dict_get_validate_count(ProtocolDict* dict, size_t protocol_index); + +ProtocolId protocol_dict_get_protocol_by_name(ProtocolDict* dict, const char* name); + +bool protocol_dict_get_write_data(ProtocolDict* dict, size_t protocol_index, void* data); + +#ifdef __cplusplus +} +#endif diff --git a/lib/toolbox/pulse_joiner.c b/lib/toolbox/pulse_joiner.c new file mode 100644 index 00000000000..b6206486c95 --- /dev/null +++ b/lib/toolbox/pulse_joiner.c @@ -0,0 +1,117 @@ +#include "pulse_joiner.h" +#include + +#define PULSE_MAX_COUNT 6 + +typedef struct { + bool polarity; + uint16_t time; +} Pulse; + +struct PulseJoiner { + size_t pulse_index; + Pulse pulses[PULSE_MAX_COUNT]; +}; + +PulseJoiner* pulse_joiner_alloc() { + PulseJoiner* pulse_joiner = malloc(sizeof(PulseJoiner)); + + pulse_joiner->pulse_index = 0; + for(uint8_t i = 0; i < PULSE_MAX_COUNT; i++) { + pulse_joiner->pulses[i].polarity = false; + pulse_joiner->pulses[i].time = 0; + } + + return pulse_joiner; +} + +void pulse_joiner_free(PulseJoiner* pulse_joiner) { + free(pulse_joiner); +} + +bool pulse_joiner_push_pulse(PulseJoiner* pulse_joiner, bool polarity, size_t period, size_t pulse) { + bool result = false; + furi_check((pulse_joiner->pulse_index + 1) < PULSE_MAX_COUNT); + + if(polarity == false && pulse_joiner->pulse_index == 0) { + // first negative pulse is omitted + + } else { + pulse_joiner->pulses[pulse_joiner->pulse_index].polarity = polarity; + pulse_joiner->pulses[pulse_joiner->pulse_index].time = pulse; + pulse_joiner->pulse_index++; + } + + if(period > pulse) { + pulse_joiner->pulses[pulse_joiner->pulse_index].polarity = !polarity; + pulse_joiner->pulses[pulse_joiner->pulse_index].time = period - pulse; + pulse_joiner->pulse_index++; + } + + if(pulse_joiner->pulse_index >= 4) { + // we know that first pulse is always high + // so we wait 2 edges, hi2low and next low2hi + + uint8_t edges_count = 0; + bool last_polarity = pulse_joiner->pulses[0].polarity; + + for(uint8_t i = 1; i < pulse_joiner->pulse_index; i++) { + if(pulse_joiner->pulses[i].polarity != last_polarity) { + edges_count++; + last_polarity = pulse_joiner->pulses[i].polarity; + } + } + + if(edges_count >= 2) { + result = true; + } + } + + return result; +} + +void pulse_joiner_pop_pulse(PulseJoiner* pulse_joiner, size_t* period, size_t* pulse) { + furi_check(pulse_joiner->pulse_index <= (PULSE_MAX_COUNT + 1)); + + uint16_t tmp_period = 0; + uint16_t tmp_pulse = 0; + uint8_t edges_count = 0; + bool last_polarity = pulse_joiner->pulses[0].polarity; + uint8_t next_fist_pulse = 0; + + for(uint8_t i = 0; i < PULSE_MAX_COUNT; i++) { + // count edges + if(pulse_joiner->pulses[i].polarity != last_polarity) { + edges_count++; + last_polarity = pulse_joiner->pulses[i].polarity; + } + + // wait for 2 edges + if(edges_count == 2) { + next_fist_pulse = i; + break; + } + + // sum pulse time + if(pulse_joiner->pulses[i].polarity) { + tmp_period += pulse_joiner->pulses[i].time; + tmp_pulse += pulse_joiner->pulses[i].time; + } else { + tmp_period += pulse_joiner->pulses[i].time; + } + pulse_joiner->pulse_index--; + } + + *period = tmp_period; + *pulse = tmp_pulse; + + // remove counted periods and shift data + for(uint8_t i = 0; i < PULSE_MAX_COUNT; i++) { + if((next_fist_pulse + i) < PULSE_MAX_COUNT) { + pulse_joiner->pulses[i].polarity = pulse_joiner->pulses[next_fist_pulse + i].polarity; + pulse_joiner->pulses[i].time = pulse_joiner->pulses[next_fist_pulse + i].time; + } else { + break; + } + } +} \ No newline at end of file diff --git a/lib/toolbox/pulse_joiner.h b/lib/toolbox/pulse_joiner.h new file mode 100644 index 00000000000..25f702e7297 --- /dev/null +++ b/lib/toolbox/pulse_joiner.h @@ -0,0 +1,46 @@ +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct PulseJoiner PulseJoiner; + +/** + * @brief Alloc PulseJoiner + * + * @return PulseJoiner* + */ +PulseJoiner* pulse_joiner_alloc(); + +/** + * @brief Free PulseJoiner + * + * @param pulse_joiner + */ +void pulse_joiner_free(PulseJoiner* pulse_joiner); + +/** + * @brief Push timer pulse. First negative pulse is ommited. + * + * @param polarity pulse polarity: true = high2low, false = low2high + * @param period overall period time in timer clicks + * @param pulse pulse time in timer clicks + * + * @return true - next pulse can and must be popped immediatly + */ +bool pulse_joiner_push_pulse(PulseJoiner* pulse_joiner, bool polarity, size_t period, size_t pulse); + +/** + * @brief Get the next timer pulse. Call only if push_pulse returns true. + * + * @param period overall period time in timer clicks + * @param pulse pulse time in timer clicks + */ +void pulse_joiner_pop_pulse(PulseJoiner* pulse_joiner, size_t* period, size_t* pulse); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/one_wire/pulse_protocols/pulse_glue.c b/lib/toolbox/pulse_protocols/pulse_glue.c similarity index 100% rename from lib/one_wire/pulse_protocols/pulse_glue.c rename to lib/toolbox/pulse_protocols/pulse_glue.c diff --git a/lib/one_wire/pulse_protocols/pulse_glue.h b/lib/toolbox/pulse_protocols/pulse_glue.h similarity index 100% rename from lib/one_wire/pulse_protocols/pulse_glue.h rename to lib/toolbox/pulse_protocols/pulse_glue.h diff --git a/lib/toolbox/varint.c b/lib/toolbox/varint.c new file mode 100644 index 00000000000..ee2f5c3af9d --- /dev/null +++ b/lib/toolbox/varint.c @@ -0,0 +1,76 @@ +#include "varint.h" + +size_t varint_uint32_pack(uint32_t value, uint8_t* output) { + uint8_t* start = output; + while(value >= 0x80) { + *output++ = (value | 0x80); + value >>= 7; + } + *output++ = value; + return output - start; +} + +size_t varint_uint32_unpack(uint32_t* value, const uint8_t* input, size_t input_size) { + size_t i; + uint32_t parsed = 0; + + for(i = 0; i < input_size; i++) { + parsed |= (input[i] & 0x7F) << (7 * i); + + if(!(input[i] & 0x80)) { + break; + } + } + + *value = parsed; + + return i + 1; +} + +size_t varint_uint32_length(uint32_t value) { + size_t size = 0; + while(value >= 0x80) { + value >>= 7; + size++; + } + size++; + + return size; +} + +size_t varint_int32_pack(int32_t value, uint8_t* output) { + uint32_t v; + + if(value >= 0) { + v = value * 2; + } else { + v = (value * -2) - 1; + } + + return varint_uint32_pack(v, output); +} + +size_t varint_int32_unpack(int32_t* value, const uint8_t* input, size_t input_size) { + uint32_t v; + size_t size = varint_uint32_unpack(&v, input, input_size); + + if(v & 1) { + *value = (int32_t)(v + 1) / (-2); + } else { + *value = v / 2; + } + + return size; +} + +size_t varint_int32_length(int32_t value) { + uint32_t v; + + if(value >= 0) { + v = value * 2; + } else { + v = (value * -2) - 1; + } + + return varint_uint32_length(v); +} \ No newline at end of file diff --git a/lib/toolbox/varint.h b/lib/toolbox/varint.h new file mode 100644 index 00000000000..bf4681d4dcb --- /dev/null +++ b/lib/toolbox/varint.h @@ -0,0 +1,35 @@ +#pragma once +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Pack uint32 to varint + * @param value value from UINT32_MIN to UINT32_MAX + * @param output output array, need to be at least 5 bytes long + * @return size_t + */ +size_t varint_uint32_pack(uint32_t value, uint8_t* output); + +size_t varint_uint32_unpack(uint32_t* value, const uint8_t* input, size_t input_size); + +size_t varint_uint32_length(uint32_t value); + +/** + * Pack int32 to varint + * @param value value from (INT32_MIN / 2 + 1) to INT32_MAX + * @param output output array, need to be at least 5 bytes long + * @return size_t + */ +size_t varint_int32_pack(int32_t value, uint8_t* output); + +size_t varint_int32_unpack(int32_t* value, const uint8_t* input, size_t input_size); + +size_t varint_int32_length(int32_t value); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/scripts/toolchain/fbtenv.sh b/scripts/toolchain/fbtenv.sh index d9d3cbe89b3..b06c582aeb0 100755 --- a/scripts/toolchain/fbtenv.sh +++ b/scripts/toolchain/fbtenv.sh @@ -5,7 +5,7 @@ # public variables DEFAULT_SCRIPT_PATH="$(pwd -P)"; SCRIPT_PATH="${SCRIPT_PATH:-$DEFAULT_SCRIPT_PATH}"; -FBT_TOOLCHAIN_VERSION="${FBT_TOOLCHAIN_VERSION:-"9"}"; +FBT_TOOLCHAIN_VERSION="${FBT_TOOLCHAIN_VERSION:-"8"}"; FBT_TOOLCHAIN_PATH="${FBT_TOOLCHAIN_PATH:-$SCRIPT_PATH}"; fbtenv_check_sourced() @@ -199,7 +199,7 @@ fbtenv_main() fbtenv_check_script_path || return 1; fbtenv_get_kernel_type || return 1; fbtenv_check_download_toolchain || return 1; - PS1="[FBT]$PS1"; + PS1="[FBT]${PS1-}"; PATH="$TOOLCHAIN_ARCH_DIR/python/bin:$PATH"; PATH="$TOOLCHAIN_ARCH_DIR/bin:$PATH"; PATH="$TOOLCHAIN_ARCH_DIR/protobuf/bin:$PATH"; From b0daa601ad5b87427a45f9089c8b403a01f72c2a Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Tue, 23 Aug 2022 19:18:28 +0300 Subject: [PATCH 015/824] [FL-2727, FL-2749] New icon in BLE remote app #1644 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- .../power_service/views/power_unplug_usb.c | 2 +- .../icons/BLE/BLE_HID/Ble_connected_15x15.png | Bin 177 -> 3634 bytes .../BLE/BLE_HID/Ble_disconnected_15x15.png | Bin 178 -> 3632 bytes 3 files changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/power/power_service/views/power_unplug_usb.c b/applications/power/power_service/views/power_unplug_usb.c index 6d14be22c70..5632cd8b05b 100644 --- a/applications/power/power_service/views/power_unplug_usb.c +++ b/applications/power/power_service/views/power_unplug_usb.c @@ -17,7 +17,7 @@ static void power_unplug_usb_draw_callback(Canvas* canvas, void* _model) { canvas_set_color(canvas, ColorWhite); canvas_set_font(canvas, FontPrimary); elements_multiline_text_aligned( - canvas, 64, 32, AlignCenter, AlignCenter, "It's now safe to unplug\nUSB cable"); + canvas, 64, 32, AlignCenter, AlignCenter, "It's now safe to unplug\nthe USB cable"); } PowerUnplugUsb* power_unplug_usb_alloc() { diff --git a/assets/icons/BLE/BLE_HID/Ble_connected_15x15.png b/assets/icons/BLE/BLE_HID/Ble_connected_15x15.png index 58776828ed48a1c3a497755800660b79ea3b8137..64dab9b5307410bb10fd7a6634cc35ed88af4671 100644 GIT binary patch literal 3634 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 P00000NkvXXu0mjf##lkG From 7c54fcf60ae9e555482113a3dcece885e1fa1590 Mon Sep 17 00:00:00 2001 From: Max Andreev Date: Tue, 23 Aug 2022 20:57:59 +0300 Subject: [PATCH 016/824] fix fbtenv.sh under zsh (#1645) * fix fbtenv under ZSH, some improovements, add amap workflow timeout * fix copy .map file in build.yml --- .github/workflows/amap_analyse.yml | 1 + .github/workflows/build.yml | 2 +- scripts/toolchain/fbtenv.sh | 58 +++++++++++++++++++++++++----- 3 files changed, 51 insertions(+), 10 deletions(-) diff --git a/.github/workflows/amap_analyse.yml b/.github/workflows/amap_analyse.yml index dd903e2e839..3443771dd43 100644 --- a/.github/workflows/amap_analyse.yml +++ b/.github/workflows/amap_analyse.yml @@ -15,6 +15,7 @@ env: jobs: amap_analyse: runs-on: [self-hosted,FlipperZeroMacShell] + timeout-minutes: 15 steps: - name: 'Wait Build workflow' uses: fountainhead/action-wait-for-check@v1.0.0 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b677a293f6f..7eeb5c22b46 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -110,7 +110,7 @@ jobs: - name: 'Copy .map file' run: | - cp build/f7-firmware-D/firmware.elf.map artifacts/flipper-z-f7-firmware-${{steps.names.outputs.suffix}}.elf.map + cp build/f7-firmware-*/firmware.elf.map artifacts/flipper-z-f7-firmware-${{steps.names.outputs.suffix}}.elf.map - name: 'Upload artifacts to update server' if: ${{ !github.event.pull_request.head.repo.fork }} diff --git a/scripts/toolchain/fbtenv.sh b/scripts/toolchain/fbtenv.sh index b06c582aeb0..6f3e8c661a1 100755 --- a/scripts/toolchain/fbtenv.sh +++ b/scripts/toolchain/fbtenv.sh @@ -8,20 +8,61 @@ SCRIPT_PATH="${SCRIPT_PATH:-$DEFAULT_SCRIPT_PATH}"; FBT_TOOLCHAIN_VERSION="${FBT_TOOLCHAIN_VERSION:-"8"}"; FBT_TOOLCHAIN_PATH="${FBT_TOOLCHAIN_PATH:-$SCRIPT_PATH}"; +fbtenv_show_usage() +{ + echo "Running this script manually is wrong, please source it"; + echo "Example:"; + printf "\tsource scripts/toolchain/fbtenv.sh\n"; +} + +fbtenv_curl() +{ + curl --progress-bar -SLo "$1" "$2"; +} + +fbtenv_wget() +{ + wget --show-progress --progress=bar:force -qO "$1" "$2"; +} + fbtenv_check_sourced() { case "${ZSH_EVAL_CONTEXT:-""}" in *:file:*) return 0;; esac + if [ ${0##*/} = "fbtenv.sh" ]; then # exluding script itself + fbtenv_show_usage; + return 1; + fi case ${0##*/} in dash|-dash|bash|-bash|ksh|-ksh|sh|-sh|*.sh|fbt) return 0;; esac - echo "Running this script manually is wrong, please source it"; - echo "Example:"; - printf "\tsource scripts/toolchain/fbtenv.sh\n"; + fbtenv_show_usage; return 1; } +fbtenv_chck_many_source() +{ + if ! echo "${PS1:-""}" | grep -q "[fbt]"; then + if ! echo "${PROMPT:-""}" | grep -q "[fbt]"; then + return 0; + fi + fi + echo "Warning! It script seen to be sourced more then once!"; + echo "It may signalise what you are making some mistakes, please open a new shell!"; + return 1; +} + +fbtenv_set_shell_prompt() +{ + if [ -n "${PS1:-""}" ]; then + PS1="[fbt]$PS1"; + elif [ -n "${PROMPT:-""}" ]; then + PROMPT="[fbt]$PROMPT"; + fi + return 0; # all other shells +} + fbtenv_check_script_path() { if [ ! -x "$SCRIPT_PATH/fbt" ]; then @@ -97,7 +138,7 @@ fbtenv_download_toolchain_tar() { echo "Downloading toolchain:"; mkdir -p "$FBT_TOOLCHAIN_PATH/toolchain" || return 1; - "$DOWNLOADER" $DOWNLOADER_ARGS "$FBT_TOOLCHAIN_PATH/toolchain/$TOOLCHAIN_TAR" "$TOOLCHAIN_URL" || return 1; + "$FBT_DOWNLOADER" "$FBT_TOOLCHAIN_PATH/toolchain/$TOOLCHAIN_TAR" "$TOOLCHAIN_URL" || return 1; echo "done"; return 0; } @@ -156,13 +197,11 @@ fbtenv_curl_wget_check() return 1; fi echo "yes" - DOWNLOADER="wget"; - DOWNLOADER_ARGS="--show-progress --progress=bar:force -qO"; + FBT_DOWNLOADER="fbtenv_wget"; return 0; fi echo "yes" - DOWNLOADER="curl"; - DOWNLOADER_ARGS="--progress-bar -SLo"; + FBT_DOWNLOADER="fbtenv_curl"; return 0; } @@ -196,10 +235,11 @@ fbtenv_download_toolchain() fbtenv_main() { fbtenv_check_sourced || return 1; + fbtenv_chck_many_source; # many source it's just a warning + fbtenv_set_shell_prompt; fbtenv_check_script_path || return 1; fbtenv_get_kernel_type || return 1; fbtenv_check_download_toolchain || return 1; - PS1="[FBT]${PS1-}"; PATH="$TOOLCHAIN_ARCH_DIR/python/bin:$PATH"; PATH="$TOOLCHAIN_ARCH_DIR/bin:$PATH"; PATH="$TOOLCHAIN_ARCH_DIR/protobuf/bin:$PATH"; From 7e20df7e93379190298b3e7573e009c2ac94cafe Mon Sep 17 00:00:00 2001 From: SG Date: Thu, 25 Aug 2022 00:41:59 +1000 Subject: [PATCH 017/824] LFRFID RC fixes (#1652) * lfrid: fix write validation * lfrid app: restore key data after write --- applications/lfrfid/scene/lfrfid_app_scene_write.cpp | 9 +++++++++ lib/lfrfid/lfrfid_worker_modes.c | 12 ++++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/applications/lfrfid/scene/lfrfid_app_scene_write.cpp b/applications/lfrfid/scene/lfrfid_app_scene_write.cpp index 8e04d8e8de9..39e0630e7d2 100644 --- a/applications/lfrfid/scene/lfrfid_app_scene_write.cpp +++ b/applications/lfrfid/scene/lfrfid_app_scene_write.cpp @@ -35,6 +35,11 @@ void LfRfidAppSceneWrite::on_enter(LfRfidApp* app, bool /* need_restore */) { popup->set_icon(0, 3, &I_RFIDDolphinSend_97x61); app->view_controller.switch_to(); + + size_t size = protocol_dict_get_data_size(app->dict, app->protocol_id); + app->old_key_data = (uint8_t*)malloc(size); + protocol_dict_get_data(app->dict, app->protocol_id, app->old_key_data, size); + lfrfid_worker_start_thread(app->lfworker); lfrfid_worker_write_start( app->lfworker, (LFRFIDProtocol)app->protocol_id, lfrfid_write_callback, app); @@ -76,4 +81,8 @@ void LfRfidAppSceneWrite::on_exit(LfRfidApp* app) { app->view_controller.get()->clean(); lfrfid_worker_stop(app->lfworker); lfrfid_worker_stop_thread(app->lfworker); + + size_t size = protocol_dict_get_data_size(app->dict, app->protocol_id); + protocol_dict_set_data(app->dict, app->protocol_id, app->old_key_data, size); + free(app->old_key_data); } diff --git a/lib/lfrfid/lfrfid_worker_modes.c b/lib/lfrfid/lfrfid_worker_modes.c index f41a7194a12..33683589c52 100644 --- a/lib/lfrfid/lfrfid_worker_modes.c +++ b/lib/lfrfid/lfrfid_worker_modes.c @@ -522,9 +522,17 @@ static void lfrfid_worker_mode_write_process(LFRFIDWorker* worker) { &read_result); if(state == LFRFIDWorkerReadOK) { - protocol_dict_get_data(worker->protocols, protocol, read_data, data_size); + bool read_success = false; - if(memcmp(read_data, verify_data, data_size) == 0) { + if(read_result == protocol) { + protocol_dict_get_data(worker->protocols, protocol, read_data, data_size); + + if(memcmp(read_data, verify_data, data_size) == 0) { + read_success = true; + } + } + + if(read_success) { if(worker->write_cb) { worker->write_cb(LFRFIDWorkerWriteOK, worker->cb_ctx); } From ce7b943793926ba7cffa914942f91c8791396595 Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Wed, 24 Aug 2022 19:14:27 +0400 Subject: [PATCH 018/824] [FL-2764] SubGhz: fix CAME, Chamberlain potocol (#1650) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * SubGhz: fix guard time CAME potocol * SubGhz: fix file upload Chamberlain * Github: fix spelling Co-authored-by: あく --- .github/workflows/amap_analyse.yml | 2 +- .github/workflows/build.yml | 2 +- lib/subghz/protocols/came.c | 11 +++++++---- lib/subghz/protocols/chamberlain_code.c | 4 ++-- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/.github/workflows/amap_analyse.yml b/.github/workflows/amap_analyse.yml index 3443771dd43..a87857e74b3 100644 --- a/.github/workflows/amap_analyse.yml +++ b/.github/workflows/amap_analyse.yml @@ -86,7 +86,7 @@ jobs: ${{ secrets.RSYNC_DEPLOY_USER }}@${{ secrets.RSYNC_DEPLOY_HOST }}:"${{ secrets.RSYNC_DEPLOY_BASE_PATH }}${{steps.names.outputs.branch-name}}/" artifacts/; rm ./deploy_key; - - name: 'Make .map file analyse' + - name: 'Make .map file analyze' run: | cd artifacts/ /Applications/amap/Contents/MacOS/amap -f flipper-z-f7-firmware-${{steps.names.outputs.suffix}}.elf.map diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7eeb5c22b46..3fe733caa7b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -93,7 +93,7 @@ jobs: rm -rf artifacts/${BUNDLE_NAME} done - - name: "Check for uncommited changes" + - name: "Check for uncommitted changes" run: | git diff --exit-code diff --git a/lib/subghz/protocols/came.c b/lib/subghz/protocols/came.c index 37048017e3f..726461d4d1e 100644 --- a/lib/subghz/protocols/came.c +++ b/lib/subghz/protocols/came.c @@ -112,8 +112,11 @@ static bool subghz_protocol_encoder_came_get_upload(SubGhzProtocolEncoderCame* i instance->encoder.size_upload = size_upload; } //Send header - instance->encoder.upload[index++] = - level_duration_make(false, (uint32_t)subghz_protocol_came_const.te_short * 36); + instance->encoder.upload[index++] = level_duration_make( + false, + ((instance->generic.data_count_bit == subghz_protocol_came_const.min_count_bit_for_found) ? + (uint32_t)subghz_protocol_came_const.te_short * 39 : + (uint32_t)subghz_protocol_came_const.te_short * 76)); //Send start bit instance->encoder.upload[index++] = level_duration_make(true, (uint32_t)subghz_protocol_came_const.te_short); @@ -213,8 +216,8 @@ void subghz_protocol_decoder_came_feed(void* context, bool level, uint32_t durat SubGhzProtocolDecoderCame* instance = context; switch(instance->decoder.parser_step) { case CameDecoderStepReset: - if((!level) && (DURATION_DIFF(duration, subghz_protocol_came_const.te_short * 51) < - subghz_protocol_came_const.te_delta * 51)) { //Need protocol 36 te_short + if((!level) && (DURATION_DIFF(duration, subghz_protocol_came_const.te_short * 56) < + subghz_protocol_came_const.te_delta * 47)) { //Found header CAME instance->decoder.parser_step = CameDecoderStepFoundStartBit; } diff --git a/lib/subghz/protocols/chamberlain_code.c b/lib/subghz/protocols/chamberlain_code.c index 6c99d84519e..51f2bcd323a 100644 --- a/lib/subghz/protocols/chamberlain_code.c +++ b/lib/subghz/protocols/chamberlain_code.c @@ -215,7 +215,7 @@ bool subghz_protocol_encoder_chamb_code_deserialize(void* context, FlipperFormat FURI_LOG_E(TAG, "Deserialize error"); break; } - if(instance->generic.data_count_bit < + if(instance->generic.data_count_bit > subghz_protocol_chamb_code_const.min_count_bit_for_found) { FURI_LOG_E(TAG, "Wrong number of bits in key"); break; @@ -441,7 +441,7 @@ bool subghz_protocol_decoder_chamb_code_deserialize(void* context, FlipperFormat if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { break; } - if(instance->generic.data_count_bit < + if(instance->generic.data_count_bit > subghz_protocol_chamb_code_const.min_count_bit_for_found) { FURI_LOG_E(TAG, "Wrong number of bits in key"); break; From ab4bb55d0fc7cbc1b117104c0190cac2d21ab8b3 Mon Sep 17 00:00:00 2001 From: Yukai Li Date: Thu, 25 Aug 2022 10:07:54 -0600 Subject: [PATCH 019/824] nfc: Change furi_assert to furi_crash for default switch cases (#1662) * nfc: Change furi_assert to furi_crash for default switch cases * Nfc: change MiFare Ultralight crash message Co-authored-by: Aleksandr Kutuzov --- applications/nfc/helpers/nfc_generators.c | 2 +- lib/nfc/protocols/mifare_ultralight.c | 11 ++++------- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/applications/nfc/helpers/nfc_generators.c b/applications/nfc/helpers/nfc_generators.c index b94adbd7b03..11083b9f0ae 100644 --- a/applications/nfc/helpers/nfc_generators.c +++ b/applications/nfc/helpers/nfc_generators.c @@ -254,7 +254,7 @@ static void session_register_page = 234; break; default: - furi_assert(false); + furi_crash("Unknown MFUL"); break; } diff --git a/lib/nfc/protocols/mifare_ultralight.c b/lib/nfc/protocols/mifare_ultralight.c index f637d378a70..b3d80deb3c4 100644 --- a/lib/nfc/protocols/mifare_ultralight.c +++ b/lib/nfc/protocols/mifare_ultralight.c @@ -940,7 +940,7 @@ static bool mf_ul_check_lock(MfUltralightEmulator* emulator, int16_t write_page) if(write_page >= 512) return true; break; default: - furi_assert(false); + furi_crash("Unknown MFUL"); return true; } @@ -967,8 +967,7 @@ static bool mf_ul_check_lock(MfUltralightEmulator* emulator, int16_t write_page) else if(write_page == 41) shift = 12; else { - furi_assert(false); - shift = 0; + furi_crash("Unknown MFUL"); } break; @@ -999,8 +998,7 @@ static bool mf_ul_check_lock(MfUltralightEmulator* emulator, int16_t write_page) shift = (write_page - 16) / 32; break; default: - furi_assert(false); - shift = 0; + furi_crash("Unknown MFUL"); break; } @@ -1177,8 +1175,7 @@ static void mf_ul_emulate_write( block_lock_count = 8; break; default: - furi_assert(false); - block_lock_count = 0; + furi_crash("Unknown MFUL"); break; } From 99a7d06f71f40b3dea27622f27b71010624291c4 Mon Sep 17 00:00:00 2001 From: SG Date: Sat, 27 Aug 2022 14:25:47 +1000 Subject: [PATCH 020/824] Speedup SD card & enlarge your RAM. (#1649) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * FuriHal: sram2 memory manager * FuriHal: sram2 memory allocator * FuriHal: allow NULL buffers for txrx in spi hal * SD card: sector cache * FuriHal: fix init in memory hal * RPC: STARTUP instead SERVICE * Memory: pool "free" command * Thread: service can be statically allocated in a memory pool Co-authored-by: あく --- applications/cli/cli_commands.c | 3 + applications/meta/application.fam | 2 +- applications/rpc/application.fam | 12 +- applications/rpc/rpc.c | 4 +- firmware/targets/f7/Inc/FreeRTOSConfig.h | 2 +- firmware/targets/f7/fatfs/sector_cache.c | 59 +++++++++ firmware/targets/f7/fatfs/sector_cache.h | 36 ++++++ firmware/targets/f7/fatfs/stm32_adafruit_sd.c | 35 +++--- firmware/targets/f7/furi_hal/furi_hal.c | 2 + .../targets/f7/furi_hal/furi_hal_memory.c | 119 ++++++++++++++++++ firmware/targets/f7/furi_hal/furi_hal_spi.c | 18 ++- firmware/targets/f7/stm32wb55xx_flash.ld | 12 +- firmware/targets/f7/stm32wb55xx_ram_fw.ld | 12 +- .../furi_hal_include/furi_hal_memory.h | 44 +++++++ furi/core/memmgr.c | 16 +++ furi/core/memmgr.h | 22 ++++ furi/core/thread.c | 38 ++++-- furi/core/thread.h | 7 ++ furi/flipper.c | 20 +++ 19 files changed, 410 insertions(+), 53 deletions(-) create mode 100644 firmware/targets/f7/fatfs/sector_cache.c create mode 100644 firmware/targets/f7/fatfs/sector_cache.h create mode 100644 firmware/targets/f7/furi_hal/furi_hal_memory.c create mode 100644 firmware/targets/furi_hal_include/furi_hal_memory.h diff --git a/applications/cli/cli_commands.c b/applications/cli/cli_commands.c index 177a274a106..a6dd672fc14 100644 --- a/applications/cli/cli_commands.c +++ b/applications/cli/cli_commands.c @@ -281,6 +281,9 @@ void cli_command_free(Cli* cli, string_t args, void* context) { printf("Total heap size: %d\r\n", memmgr_get_total_heap()); printf("Minimum heap size: %d\r\n", memmgr_get_minimum_free_heap()); printf("Maximum heap block: %d\r\n", memmgr_heap_get_max_free_block()); + + printf("Pool free: %d\r\n", memmgr_pool_get_free()); + printf("Maximum pool block: %d\r\n", memmgr_pool_get_max_block()); } void cli_command_free_blocks(Cli* cli, string_t args, void* context) { diff --git a/applications/meta/application.fam b/applications/meta/application.fam index a447b94ae4d..8b873b5fba6 100644 --- a/applications/meta/application.fam +++ b/applications/meta/application.fam @@ -3,7 +3,7 @@ App( name="Basic services", apptype=FlipperAppType.METAPACKAGE, provides=[ - "rpc", + "rpc_start", "bt", "desktop", "loader", diff --git a/applications/rpc/application.fam b/applications/rpc/application.fam index 683396e32cb..3a139cb3b09 100644 --- a/applications/rpc/application.fam +++ b/applications/rpc/application.fam @@ -1,12 +1,8 @@ App( - appid="rpc", - name="RpcSrv", - apptype=FlipperAppType.SERVICE, - entry_point="rpc_srv", + appid="rpc_start", + apptype=FlipperAppType.STARTUP, + entry_point="rpc_on_system_start", cdefines=["SRV_RPC"], - requires=[ - "cli", - ], - stack_size=4 * 1024, + requires=["cli"], order=10, ) diff --git a/applications/rpc/rpc.c b/applications/rpc/rpc.c index d767a928d63..abba6ea42a9 100644 --- a/applications/rpc/rpc.c +++ b/applications/rpc/rpc.c @@ -395,7 +395,7 @@ void rpc_session_close(RpcSession* session) { furi_thread_flags_set(furi_thread_get_id(session->thread), RpcEvtDisconnect); } -int32_t rpc_srv(void* p) { +void rpc_on_system_start(void* p) { UNUSED(p); Rpc* rpc = malloc(sizeof(Rpc)); @@ -406,8 +406,6 @@ int32_t rpc_srv(void* p) { cli, "start_rpc_session", CliCommandFlagParallelSafe, rpc_cli_command_start_session, rpc); furi_record_create(RECORD_RPC, rpc); - - return 0; } void rpc_add_handler(RpcSession* session, pb_size_t message_tag, RpcHandler* handler) { diff --git a/firmware/targets/f7/Inc/FreeRTOSConfig.h b/firmware/targets/f7/Inc/FreeRTOSConfig.h index f54d774caaa..ab2dc14ef43 100644 --- a/firmware/targets/f7/Inc/FreeRTOSConfig.h +++ b/firmware/targets/f7/Inc/FreeRTOSConfig.h @@ -14,7 +14,7 @@ extern uint32_t SystemCoreClock; #define configENABLE_MPU 0 #define configUSE_PREEMPTION 1 -#define configSUPPORT_STATIC_ALLOCATION 0 +#define configSUPPORT_STATIC_ALLOCATION 1 #define configSUPPORT_DYNAMIC_ALLOCATION 1 #define configUSE_IDLE_HOOK 0 #define configUSE_TICK_HOOK 0 diff --git a/firmware/targets/f7/fatfs/sector_cache.c b/firmware/targets/f7/fatfs/sector_cache.c new file mode 100644 index 00000000000..5a4f1b9789b --- /dev/null +++ b/firmware/targets/f7/fatfs/sector_cache.c @@ -0,0 +1,59 @@ +#include "sector_cache.h" + +#include +#include +#include +#include +#include + +#define SECTOR_SIZE 512 +#define N_SECTORS 8 + +typedef struct { + uint32_t itr; + uint32_t sectors[N_SECTORS]; + uint8_t sector_data[N_SECTORS][SECTOR_SIZE]; +} SectorCache; + +static SectorCache* cache = NULL; + +void sector_cache_init() { + if(cache == NULL) { + cache = furi_hal_memory_alloc(sizeof(SectorCache)); + } + + if(cache != NULL) { + FURI_LOG_I("SectorCache", "Initializing sector cache"); + memset(cache, 0, sizeof(SectorCache)); + } else { + FURI_LOG_E("SectorCache", "Cannot enable sector cache"); + } +} + +uint8_t* sector_cache_get(uint32_t n_sector) { + if(cache != NULL && n_sector != 0) { + for(int sector_i = 0; sector_i < N_SECTORS; ++sector_i) { + if(cache->sectors[sector_i] == n_sector) { + return cache->sector_data[sector_i]; + } + } + } + return NULL; +} + +void sector_cache_put(uint32_t n_sector, uint8_t* data) { + if(cache == NULL) return; + cache->sectors[cache->itr % N_SECTORS] = n_sector; + memcpy(cache->sector_data[cache->itr % N_SECTORS], data, SECTOR_SIZE); + cache->itr++; +} + +void sector_cache_invalidate_range(uint32_t start_sector, uint32_t end_sector) { + if(cache == NULL) return; + for(int sector_i = 0; sector_i < N_SECTORS; ++sector_i) { + if((cache->sectors[sector_i] >= start_sector) && + (cache->sectors[sector_i] <= end_sector)) { + cache->sectors[sector_i] = 0; + } + } +} \ No newline at end of file diff --git a/firmware/targets/f7/fatfs/sector_cache.h b/firmware/targets/f7/fatfs/sector_cache.h new file mode 100644 index 00000000000..5fe4a2ed86b --- /dev/null +++ b/firmware/targets/f7/fatfs/sector_cache.h @@ -0,0 +1,36 @@ +#pragma once +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Init sector cache system + */ +void sector_cache_init(); + +/** + * @brief Get sector data from cache + * @param n_sector Sector number + * @return Pointer to sector data or NULL if not found + */ +uint8_t* sector_cache_get(uint32_t n_sector); + +/** + * @brief Put sector data to cache + * @param n_sector Sector number + * @param data Pointer to sector data + */ +void sector_cache_put(uint32_t n_sector, uint8_t* data); + +/** + * @brief Invalidate sector cache for given range + * @param start_sector Start sector number + * @param end_sector End sector number + */ +void sector_cache_invalidate_range(uint32_t start_sector, uint32_t end_sector); + +#ifdef __cplusplus +} +#endif diff --git a/firmware/targets/f7/fatfs/stm32_adafruit_sd.c b/firmware/targets/f7/fatfs/stm32_adafruit_sd.c index 6db430a5359..07cae31fe8e 100644 --- a/firmware/targets/f7/fatfs/stm32_adafruit_sd.c +++ b/firmware/targets/f7/fatfs/stm32_adafruit_sd.c @@ -92,6 +92,7 @@ #include "string.h" #include "stdio.h" #include +#include "sector_cache.h" /** @addtogroup BSP * @{ @@ -377,6 +378,8 @@ uint8_t BSP_SD_Init(bool reset_card) { furi_hal_sd_spi_handle = NULL; furi_hal_spi_release(&furi_hal_spi_bus_handle_sd_slow); + sector_cache_init(); + /* SD initialized and set to SPI mode properly */ return res; } @@ -427,9 +430,15 @@ uint8_t uint32_t offset = 0; uint32_t addr; uint8_t retr = BSP_SD_ERROR; - uint8_t* ptr = NULL; SD_CmdAnswer_typedef response; uint16_t BlockSize = 512; + uint8_t* cached_data; + + bool single_sector_read = (NumOfBlocks == 1); + if(single_sector_read && (cached_data = sector_cache_get(ReadAddr))) { + memcpy(pData, cached_data, BlockSize); + return BSP_SD_OK; + } /* Send CMD16 (SD_CMD_SET_BLOCKLEN) to set the size of the block and Check if the SD acknowledged the set block length command: R1 response (0x00: no errors) */ @@ -440,12 +449,6 @@ uint8_t goto error; } - ptr = malloc(sizeof(uint8_t) * BlockSize); - if(ptr == NULL) { - goto error; - } - memset(ptr, SD_DUMMY_BYTE, sizeof(uint8_t) * BlockSize); - /* Initialize the address */ addr = (ReadAddr * ((flag_SDHC == 1) ? 1 : BlockSize)); @@ -461,7 +464,7 @@ uint8_t /* Now look for the data token to signify the start of the data */ if(SD_WaitData(SD_TOKEN_START_DATA_SINGLE_BLOCK_READ) == BSP_SD_OK) { /* Read the SD block data : read NumByteToRead data */ - SD_IO_WriteReadData(ptr, (uint8_t*)pData + offset, BlockSize); + SD_IO_WriteReadData(NULL, (uint8_t*)pData + offset, BlockSize); /* Set next read address*/ offset += BlockSize; @@ -479,13 +482,16 @@ uint8_t SD_IO_WriteByte(SD_DUMMY_BYTE); } + if(single_sector_read) { + sector_cache_put(ReadAddr, (uint8_t*)pData); + } + retr = BSP_SD_OK; error: /* Send dummy byte: 8 Clock pulses of delay */ SD_IO_CSState(1); SD_IO_WriteByte(SD_DUMMY_BYTE); - if(ptr != NULL) free(ptr); /* Return the reponse */ return retr; @@ -509,9 +515,9 @@ uint8_t BSP_SD_WriteBlocks( uint32_t offset = 0; uint32_t addr; uint8_t retr = BSP_SD_ERROR; - uint8_t* ptr = NULL; SD_CmdAnswer_typedef response; uint16_t BlockSize = 512; + sector_cache_invalidate_range(WriteAddr, WriteAddr + NumOfBlocks); /* Send CMD16 (SD_CMD_SET_BLOCKLEN) to set the size of the block and Check if the SD acknowledged the set block length command: R1 response (0x00: no errors) */ @@ -522,11 +528,6 @@ uint8_t BSP_SD_WriteBlocks( goto error; } - ptr = malloc(sizeof(uint8_t) * BlockSize); - if(ptr == NULL) { - goto error; - } - /* Initialize the address */ addr = (WriteAddr * ((flag_SDHC == 1) ? 1 : BlockSize)); @@ -547,7 +548,7 @@ uint8_t BSP_SD_WriteBlocks( SD_IO_WriteByte(SD_TOKEN_START_DATA_SINGLE_BLOCK_WRITE); /* Write the block data to SD */ - SD_IO_WriteReadData((uint8_t*)pData + offset, ptr, BlockSize); + SD_IO_WriteReadData((uint8_t*)pData + offset, NULL, BlockSize); /* Set next write address */ offset += BlockSize; @@ -569,7 +570,7 @@ uint8_t BSP_SD_WriteBlocks( retr = BSP_SD_OK; error: - if(ptr != NULL) free(ptr); + /* Send dummy byte: 8 Clock pulses of delay */ SD_IO_CSState(1); SD_IO_WriteByte(SD_DUMMY_BYTE); diff --git a/firmware/targets/f7/furi_hal/furi_hal.c b/firmware/targets/f7/furi_hal/furi_hal.c index d0856127ac3..141efdb6036 100644 --- a/firmware/targets/f7/furi_hal/furi_hal.c +++ b/firmware/targets/f7/furi_hal/furi_hal.c @@ -1,5 +1,6 @@ #include #include +#include #include @@ -78,6 +79,7 @@ void furi_hal_init() { furi_hal_rfid_init(); #endif furi_hal_bt_init(); + furi_hal_memory_init(); furi_hal_compress_icon_init(); // FatFS driver initialization diff --git a/firmware/targets/f7/furi_hal/furi_hal_memory.c b/firmware/targets/f7/furi_hal/furi_hal_memory.c new file mode 100644 index 00000000000..43dc56f11b8 --- /dev/null +++ b/firmware/targets/f7/furi_hal/furi_hal_memory.c @@ -0,0 +1,119 @@ +#include +#include +#include + +#define TAG "FuriHalMemory" + +typedef enum { + SRAM_A, + SRAM_B, + SRAM_MAX, +} SRAM; + +typedef struct { + void* start; + uint32_t size; +} FuriHalMemoryRegion; + +typedef struct { + FuriHalMemoryRegion region[SRAM_MAX]; +} FuriHalMemory; + +static FuriHalMemory* furi_hal_memory = NULL; + +extern const void __sram2a_start__; +extern const void __sram2a_free__; +extern const void __sram2b_start__; + +void furi_hal_memory_init() { + if(furi_hal_rtc_get_boot_mode() != FuriHalRtcBootModeNormal) { + return; + } + + if(!ble_glue_wait_for_c2_start(FURI_HAL_BT_C2_START_TIMEOUT)) { + FURI_LOG_E(TAG, "C2 start timeout"); + return; + } + + FuriHalMemory* memory = malloc(sizeof(FuriHalMemory)); + + const BleGlueC2Info* c2_ver = ble_glue_get_c2_info(); + + if(c2_ver->mode == BleGlueC2ModeStack) { + uint32_t sram2a_busy_size = (uint32_t)&__sram2a_free__ - (uint32_t)&__sram2a_start__; + uint32_t sram2a_unprotected_size = (32 - c2_ver->MemorySizeSram2A) * 1024; + uint32_t sram2b_unprotected_size = (32 - c2_ver->MemorySizeSram2B) * 1024; + + memory->region[SRAM_A].start = (uint8_t*)&__sram2a_free__; + memory->region[SRAM_B].start = (uint8_t*)&__sram2b_start__; + + if(sram2a_unprotected_size > sram2a_busy_size) { + memory->region[SRAM_A].size = sram2a_unprotected_size - sram2a_busy_size; + } else { + memory->region[SRAM_A].size = 0; + } + memory->region[SRAM_B].size = sram2b_unprotected_size; + + FURI_LOG_I( + TAG, "SRAM2A: 0x%p, %d", memory->region[SRAM_A].start, memory->region[SRAM_A].size); + FURI_LOG_I( + TAG, "SRAM2B: 0x%p, %d", memory->region[SRAM_B].start, memory->region[SRAM_B].size); + + if((memory->region[SRAM_A].size > 0) || (memory->region[SRAM_B].size > 0)) { + if((memory->region[SRAM_A].size > 0)) { + FURI_LOG_I(TAG, "SRAM2A clear"); + memset(memory->region[SRAM_A].start, 0, memory->region[SRAM_A].size); + } + if((memory->region[SRAM_B].size > 0)) { + FURI_LOG_I(TAG, "SRAM2B clear"); + memset(memory->region[SRAM_B].start, 0, memory->region[SRAM_B].size); + } + furi_hal_memory = memory; + FURI_LOG_I(TAG, "Enabled"); + } else { + free(memory); + FURI_LOG_E(TAG, "No SRAM2 available"); + } + } else { + free(memory); + FURI_LOG_E(TAG, "No Core2 available"); + } +} + +void* furi_hal_memory_alloc(size_t size) { + if(furi_hal_memory == NULL) { + return NULL; + } + + for(int i = 0; i < SRAM_MAX; i++) { + if(furi_hal_memory->region[i].size >= size) { + void* ptr = furi_hal_memory->region[i].start; + furi_hal_memory->region[i].start += size; + furi_hal_memory->region[i].size -= size; + return ptr; + } + } + return NULL; +} + +size_t furi_hal_memory_get_free() { + if(furi_hal_memory == NULL) return 0; + + size_t free = 0; + for(int i = 0; i < SRAM_MAX; i++) { + free += furi_hal_memory->region[i].size; + } + return free; +} + +size_t furi_hal_memory_max_pool_block() { + if(furi_hal_memory == NULL) return 0; + + size_t max = 0; + for(int i = 0; i < SRAM_MAX; i++) { + if(furi_hal_memory->region[i].size > max) { + max = furi_hal_memory->region[i].size; + } + } + return max; +} \ No newline at end of file diff --git a/firmware/targets/f7/furi_hal/furi_hal_spi.c b/firmware/targets/f7/furi_hal/furi_hal_spi.c index f8c5a2c7820..2d54278d64b 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_spi.c +++ b/firmware/targets/f7/furi_hal/furi_hal_spi.c @@ -139,8 +139,6 @@ bool furi_hal_spi_bus_trx( uint32_t timeout) { furi_assert(handle); furi_assert(handle->bus->current_handle == handle); - furi_assert(tx_buffer); - furi_assert(rx_buffer); furi_assert(size > 0); bool ret = true; @@ -149,15 +147,23 @@ bool furi_hal_spi_bus_trx( while(size > 0) { if(tx_size > 0 && LL_SPI_IsActiveFlag_TXE(handle->bus->spi) && tx_allowed) { - LL_SPI_TransmitData8(handle->bus->spi, *tx_buffer); - tx_buffer++; + if(tx_buffer) { + LL_SPI_TransmitData8(handle->bus->spi, *tx_buffer); + tx_buffer++; + } else { + LL_SPI_TransmitData8(handle->bus->spi, 0xFF); + } tx_size--; tx_allowed = false; } if(LL_SPI_IsActiveFlag_RXNE(handle->bus->spi)) { - *rx_buffer = LL_SPI_ReceiveData8(handle->bus->spi); - rx_buffer++; + if(rx_buffer) { + *rx_buffer = LL_SPI_ReceiveData8(handle->bus->spi); + rx_buffer++; + } else { + LL_SPI_ReceiveData8(handle->bus->spi); + } size--; tx_allowed = true; } diff --git a/firmware/targets/f7/stm32wb55xx_flash.ld b/firmware/targets/f7/stm32wb55xx_flash.ld index 20314ba3c8c..4124c096d90 100644 --- a/firmware/targets/f7/stm32wb55xx_flash.ld +++ b/firmware/targets/f7/stm32wb55xx_flash.ld @@ -57,7 +57,8 @@ MEMORY { FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K RAM1 (xrw) : ORIGIN = 0x20000008, LENGTH = 0x2FFF8 -RAM_SHARED (xrw) : ORIGIN = 0x20030000, LENGTH = 10K +RAM2A (xrw) : ORIGIN = 0x20030000, LENGTH = 10K +RAM2B (xrw) : ORIGIN = 0x20038000, LENGTH = 10K } /* Define output sections */ @@ -186,9 +187,12 @@ SECTIONS } .ARM.attributes 0 : { *(.ARM.attributes) } - MAPPING_TABLE (NOLOAD) : { *(MAPPING_TABLE) } >RAM_SHARED - MB_MEM1 (NOLOAD) : { *(MB_MEM1) } >RAM_SHARED - MB_MEM2 (NOLOAD) : { _sMB_MEM2 = . ; *(MB_MEM2) ; _eMB_MEM2 = . ; } >RAM_SHARED + ._sram2a_start : { . = ALIGN(4); __sram2a_start__ = .; } >RAM2A + MAPPING_TABLE (NOLOAD) : { *(MAPPING_TABLE) } >RAM2A + MB_MEM1 (NOLOAD) : { *(MB_MEM1) } >RAM2A + MB_MEM2 (NOLOAD) : { _sMB_MEM2 = . ; *(MB_MEM2) ; _eMB_MEM2 = . ; } >RAM2A + ._sram2a_free : { . = ALIGN(4); __sram2a_free__ = .; } >RAM2A + ._sram2b_start : { . = ALIGN(4); __sram2b_start__ = .; } >RAM2B } diff --git a/firmware/targets/f7/stm32wb55xx_ram_fw.ld b/firmware/targets/f7/stm32wb55xx_ram_fw.ld index 8c4d41c5b2a..1b7fe3600a9 100644 --- a/firmware/targets/f7/stm32wb55xx_ram_fw.ld +++ b/firmware/targets/f7/stm32wb55xx_ram_fw.ld @@ -57,7 +57,8 @@ MEMORY { FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K RAM1 (xrw) : ORIGIN = 0x20000000, LENGTH = 0x30000 -RAM_SHARED (xrw) : ORIGIN = 0x20030000, LENGTH = 10K +RAM2A (xrw) : ORIGIN = 0x20030000, LENGTH = 10K +RAM2B (xrw) : ORIGIN = 0x20038000, LENGTH = 10K } /* Define output sections */ @@ -184,9 +185,12 @@ SECTIONS } .ARM.attributes 0 : { *(.ARM.attributes) } - MAPPING_TABLE (NOLOAD) : { *(MAPPING_TABLE) } >RAM_SHARED - MB_MEM1 (NOLOAD) : { *(MB_MEM1) } >RAM_SHARED - MB_MEM2 (NOLOAD) : { _sMB_MEM2 = . ; *(MB_MEM2) ; _eMB_MEM2 = . ; } >RAM_SHARED + ._sram2a_start : { . = ALIGN(4); __sram2a_start__ = .; } >RAM2A + MAPPING_TABLE (NOLOAD) : { *(MAPPING_TABLE) } >RAM2A + MB_MEM1 (NOLOAD) : { *(MB_MEM1) } >RAM2A + MB_MEM2 (NOLOAD) : { _sMB_MEM2 = . ; *(MB_MEM2) ; _eMB_MEM2 = . ; } >RAM2A + ._sram2a_free : { . = ALIGN(4); __sram2a_free__ = .; } >RAM2A + ._sram2b_start : { . = ALIGN(4); __sram2b_start__ = .; } >RAM2B } diff --git a/firmware/targets/furi_hal_include/furi_hal_memory.h b/firmware/targets/furi_hal_include/furi_hal_memory.h new file mode 100644 index 00000000000..e9efa08c158 --- /dev/null +++ b/firmware/targets/furi_hal_include/furi_hal_memory.h @@ -0,0 +1,44 @@ +/** + * @file furi_hal_memory.h + * Memory HAL API + */ + +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Init memory pool manager + */ +void furi_hal_memory_init(); + +/** + * @brief Allocate memory from separate memory pool. That memory can't be freed. + * + * @param size + * @return void* + */ +void* furi_hal_memory_alloc(size_t size); + +/** + * @brief Get free memory pool size + * + * @return size_t + */ +size_t furi_hal_memory_get_free(); + +/** + * @brief Get max free block size from memory pool + * + * @return size_t + */ +size_t furi_hal_memory_max_pool_block(); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/furi/core/memmgr.c b/furi/core/memmgr.c index 80f87b930e7..dba1a707409 100644 --- a/furi/core/memmgr.c +++ b/furi/core/memmgr.c @@ -1,6 +1,7 @@ #include "memmgr.h" #include "common_defines.h" #include +#include extern void* pvPortMalloc(size_t xSize); extern void vPortFree(void* pv); @@ -77,3 +78,18 @@ void* __wrap__realloc_r(struct _reent* r, void* ptr, size_t size) { UNUSED(r); return realloc(ptr, size); } + +void* memmgr_alloc_from_pool(size_t size) { + void* p = furi_hal_memory_alloc(size); + if(p == NULL) p = malloc(size); + + return p; +} + +size_t memmgr_pool_get_free(void) { + return furi_hal_memory_get_free(); +} + +size_t memmgr_pool_get_max_block(void) { + return furi_hal_memory_max_pool_block(); +} \ No newline at end of file diff --git a/furi/core/memmgr.h b/furi/core/memmgr.h index d7285fb238a..fdecfd72da4 100644 --- a/furi/core/memmgr.h +++ b/furi/core/memmgr.h @@ -35,6 +35,28 @@ size_t memmgr_get_total_heap(void); */ size_t memmgr_get_minimum_free_heap(void); +/** + * @brief Allocate memory from separate memory pool. That memory can't be freed. + * + * @param size + * @return void* + */ +void* memmgr_alloc_from_pool(size_t size); + +/** + * @brief Get free memory pool size + * + * @return size_t + */ +size_t memmgr_pool_get_free(void); + +/** + * @brief Get max free block size from memory pool + * + * @return size_t + */ +size_t memmgr_pool_get_max_block(void); + #ifdef __cplusplus } #endif diff --git a/furi/core/thread.c b/furi/core/thread.c index 044f83711b6..a68472b56a6 100644 --- a/furi/core/thread.c +++ b/furi/core/thread.c @@ -7,7 +7,9 @@ #include "mutex.h" #include +#include "log.h" #include +#include #include #define THREAD_NOTIFY_INDEX 1 // Index 0 is used for stream buffers @@ -20,6 +22,7 @@ struct FuriThreadStdout { }; struct FuriThread { + bool is_service; FuriThreadState state; int32_t ret; @@ -84,6 +87,11 @@ static void furi_thread_body(void* context) { furi_assert(thread->state == FuriThreadStateRunning); furi_thread_set_state(thread, FuriThreadStateStopped); + if(thread->is_service) { + FURI_LOG_E( + "Service", "%s thread exited. Thread memory cannot be reclaimed.", thread->name); + } + // clear thread local storage __furi_thread_stdout_flush(thread); furi_assert(pvTaskGetThreadLocalStoragePointer(NULL, 0) != NULL); @@ -96,7 +104,7 @@ static void furi_thread_body(void* context) { FuriThread* furi_thread_alloc() { FuriThread* thread = malloc(sizeof(FuriThread)); string_init(thread->output.buffer); - + thread->is_service = false; return thread; } @@ -117,6 +125,10 @@ void furi_thread_set_name(FuriThread* thread, const char* name) { thread->name = name ? strdup(name) : NULL; } +void furi_thread_mark_as_service(FuriThread* thread) { + thread->is_service = true; +} + void furi_thread_set_stack_size(FuriThread* thread, size_t stack_size) { furi_assert(thread); furi_assert(thread->state == FuriThreadStateStopped); @@ -168,15 +180,23 @@ void furi_thread_start(FuriThread* thread) { furi_thread_set_state(thread, FuriThreadStateStarting); - BaseType_t ret = xTaskCreate( - furi_thread_body, - thread->name, - thread->stack_size / 4, - thread, - thread->priority ? thread->priority : FuriThreadPriorityNormal, - &thread->task_handle); + uint32_t stack = thread->stack_size / 4; + UBaseType_t priority = thread->priority ? thread->priority : FuriThreadPriorityNormal; + if(thread->is_service) { + thread->task_handle = xTaskCreateStatic( + furi_thread_body, + thread->name, + stack, + thread, + priority, + memmgr_alloc_from_pool(sizeof(StackType_t) * stack), + memmgr_alloc_from_pool(sizeof(StaticTask_t))); + } else { + BaseType_t ret = xTaskCreate( + furi_thread_body, thread->name, stack, thread, priority, &thread->task_handle); + furi_check(ret == pdPASS); + } - furi_check(ret == pdPASS); furi_check(thread->task_handle); } diff --git a/furi/core/thread.h b/furi/core/thread.h index 7f746f03faa..f15b9ff661f 100644 --- a/furi/core/thread.h +++ b/furi/core/thread.h @@ -73,6 +73,13 @@ void furi_thread_free(FuriThread* thread); */ void furi_thread_set_name(FuriThread* thread, const char* name); +/** Mark thread as service + * The service cannot be stopped or removed, and cannot exit from the thread body + * + * @param thread + */ +void furi_thread_mark_as_service(FuriThread* thread); + /** Set FuriThread stack size * * @param thread FuriThread instance diff --git a/furi/flipper.c b/furi/flipper.c index c7d7c5a6c2e..2acfea0151d 100755 --- a/furi/flipper.c +++ b/furi/flipper.c @@ -2,6 +2,7 @@ #include #include #include +#include #define TAG "Flipper" @@ -38,9 +39,28 @@ void flipper_init() { furi_thread_set_name(thread, FLIPPER_SERVICES[i].name); furi_thread_set_stack_size(thread, FLIPPER_SERVICES[i].stack_size); furi_thread_set_callback(thread, FLIPPER_SERVICES[i].app); + furi_thread_mark_as_service(thread); furi_thread_start(thread); } FURI_LOG_I(TAG, "services startup complete"); } + +void vApplicationGetIdleTaskMemory( + StaticTask_t** tcb_ptr, + StackType_t** stack_ptr, + uint32_t* stack_size) { + *tcb_ptr = memmgr_alloc_from_pool(sizeof(StaticTask_t)); + *stack_ptr = memmgr_alloc_from_pool(sizeof(StackType_t) * configMINIMAL_STACK_SIZE); + *stack_size = configMINIMAL_STACK_SIZE; +} + +void vApplicationGetTimerTaskMemory( + StaticTask_t** tcb_ptr, + StackType_t** stack_ptr, + uint32_t* stack_size) { + *tcb_ptr = memmgr_alloc_from_pool(sizeof(StaticTask_t)); + *stack_ptr = memmgr_alloc_from_pool(sizeof(StackType_t) * configTIMER_TASK_STACK_DEPTH); + *stack_size = configTIMER_TASK_STACK_DEPTH; +} \ No newline at end of file From 1a4a6d4625aa3205c845e1e38c4f4b14419662b4 Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Sat, 27 Aug 2022 12:06:25 +0400 Subject: [PATCH 021/824] [FL-2769] SubGhz: out debug data to external pin #1665 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- .../targets/f7/furi_hal/furi_hal_subghz.c | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/firmware/targets/f7/furi_hal/furi_hal_subghz.c b/firmware/targets/f7/furi_hal/furi_hal_subghz.c index ade46238982..7afb88bc332 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_subghz.c +++ b/firmware/targets/f7/furi_hal/furi_hal_subghz.c @@ -17,6 +17,28 @@ #define TAG "FuriHalSubGhz" + +/* + * Uncomment define to enable duplication of + * IO GO0 CC1101 to an external comb. + * Debug pin can be assigned + * gpio_ext_pc0 + * gpio_ext_pc1 + * gpio_ext_pc3 + * gpio_ext_pb2 + * gpio_ext_pb3 + * gpio_ext_pa4 + * gpio_ext_pa6 + * gpio_ext_pa7 + * Attention this setting switches pin to output. + * Make sure it is not connected directly to power or ground + */ + +//#define SUBGHZ_DEBUG_CC1101_PIN gpio_ext_pa7 +#ifdef SUBGHZ_DEBUG_CC1101_PIN +uint32_t subghz_debug_gpio_buff[2]; +#endif + typedef struct { volatile SubGhzState state; volatile SubGhzRegulation regulation; @@ -361,6 +383,9 @@ static void furi_hal_subghz_capture_ISR() { LL_TIM_ClearFlag_CC1(TIM2); furi_hal_subghz_capture_delta_duration = LL_TIM_IC_GetCaptureCH1(TIM2); if(furi_hal_subghz_capture_callback) { +#ifdef SUBGHZ_DEBUG_CC1101_PIN + furi_hal_gpio_write(&SUBGHZ_DEBUG_CC1101_PIN, false); +#endif furi_hal_subghz_capture_callback( true, furi_hal_subghz_capture_delta_duration, @@ -371,6 +396,9 @@ static void furi_hal_subghz_capture_ISR() { if(LL_TIM_IsActiveFlag_CC2(TIM2)) { LL_TIM_ClearFlag_CC2(TIM2); if(furi_hal_subghz_capture_callback) { +#ifdef SUBGHZ_DEBUG_CC1101_PIN + furi_hal_gpio_write(&SUBGHZ_DEBUG_CC1101_PIN, true); +#endif furi_hal_subghz_capture_callback( false, LL_TIM_IC_GetCaptureCH2(TIM2) - furi_hal_subghz_capture_delta_duration, @@ -432,6 +460,11 @@ void furi_hal_subghz_start_async_rx(FuriHalSubGhzCaptureCallback callback, void* LL_TIM_SetCounter(TIM2, 0); LL_TIM_EnableCounter(TIM2); +#ifdef SUBGHZ_DEBUG_CC1101_PIN + furi_hal_gpio_init( + &SUBGHZ_DEBUG_CC1101_PIN, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); +#endif + // Switch to RX furi_hal_subghz_rx(); } @@ -445,6 +478,11 @@ void furi_hal_subghz_stop_async_rx() { FURI_CRITICAL_ENTER(); LL_TIM_DeInit(TIM2); + +#ifdef SUBGHZ_DEBUG_CC1101_PIN + furi_hal_gpio_init(&SUBGHZ_DEBUG_CC1101_PIN, GpioModeAnalog, GpioPullNo, GpioSpeedLow); +#endif + FURI_CRITICAL_EXIT(); furi_hal_interrupt_set_isr(FuriHalInterruptIdTIM2, NULL, NULL); @@ -630,6 +668,32 @@ bool furi_hal_subghz_start_async_tx(FuriHalSubGhzAsyncTxCallback callback, void* LL_TIM_SetCounter(TIM2, 0); LL_TIM_EnableCounter(TIM2); + +#ifdef SUBGHZ_DEBUG_CC1101_PIN + furi_hal_gpio_init( + &SUBGHZ_DEBUG_CC1101_PIN, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); + + const GpioPin* gpio = &SUBGHZ_DEBUG_CC1101_PIN; + subghz_debug_gpio_buff[0] = gpio->pin; + subghz_debug_gpio_buff[1] = (uint32_t)gpio->pin << GPIO_NUMBER; + + dma_config.MemoryOrM2MDstAddress = (uint32_t)subghz_debug_gpio_buff; + dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (gpio->port->BSRR); + 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_WORD; + dma_config.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_WORD; + 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_2, &dma_config); + LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_2, 2); + LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_2); + +#endif + return true; } @@ -661,6 +725,12 @@ void furi_hal_subghz_stop_async_tx() { // Deinitialize GPIO furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + +#ifdef SUBGHZ_DEBUG_CC1101_PIN + LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_2); + furi_hal_gpio_init(&SUBGHZ_DEBUG_CC1101_PIN, GpioModeAnalog, GpioPullNo, GpioSpeedLow); +#endif + FURI_CRITICAL_EXIT(); free(furi_hal_subghz_async_tx.buffer); From 689da15346e22e8b0857923155aadaa698d37a7d Mon Sep 17 00:00:00 2001 From: Max Andreev Date: Sat, 27 Aug 2022 15:38:13 +0300 Subject: [PATCH 022/824] workflows and fbtenv improovements (#1661) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add --restore option, improove clearing * fix trap * fix unset * fix fbtenv clearing * disabling pvs studio and amap analyses in forks, fbtenv.sh fixes * fbtenv fix Co-authored-by: あく --- .github/workflows/amap_analyse.yml | 1 + .github/workflows/pvs_studio.yml | 1 + scripts/toolchain/fbtenv.sh | 50 ++++++++++++++++++++++++------ 3 files changed, 43 insertions(+), 9 deletions(-) diff --git a/.github/workflows/amap_analyse.yml b/.github/workflows/amap_analyse.yml index a87857e74b3..11dac543ae2 100644 --- a/.github/workflows/amap_analyse.yml +++ b/.github/workflows/amap_analyse.yml @@ -14,6 +14,7 @@ env: jobs: amap_analyse: + if: ${{ !github.event.pull_request.head.repo.fork }} runs-on: [self-hosted,FlipperZeroMacShell] timeout-minutes: 15 steps: diff --git a/.github/workflows/pvs_studio.yml b/.github/workflows/pvs_studio.yml index c238b1c6f9a..5733fd27494 100644 --- a/.github/workflows/pvs_studio.yml +++ b/.github/workflows/pvs_studio.yml @@ -15,6 +15,7 @@ env: jobs: analyse_c_cpp: + if: ${{ !github.event.pull_request.head.repo.fork }} runs-on: [self-hosted, FlipperZeroShell] steps: - name: 'Decontaminate previous build leftovers' diff --git a/scripts/toolchain/fbtenv.sh b/scripts/toolchain/fbtenv.sh index 6f3e8c661a1..cbbb7b94db5 100755 --- a/scripts/toolchain/fbtenv.sh +++ b/scripts/toolchain/fbtenv.sh @@ -13,6 +13,9 @@ fbtenv_show_usage() echo "Running this script manually is wrong, please source it"; echo "Example:"; printf "\tsource scripts/toolchain/fbtenv.sh\n"; + echo "To restore your enviroment source fbtenv.sh with '--restore'." + echo "Example:"; + printf "\tsource scripts/toolchain/fbtenv.sh --restore\n"; } fbtenv_curl() @@ -25,9 +28,27 @@ fbtenv_wget() wget --show-progress --progress=bar:force -qO "$1" "$2"; } +fbtenv_restore_env() +{ + TOOLCHAIN_ARCH_DIR_SED="$(echo "$TOOLCHAIN_ARCH_DIR" | sed 's/\//\\\//g')" + PATH="$(echo "$PATH" | /usr/bin/sed "s/$TOOLCHAIN_ARCH_DIR_SED\/python\/bin://g")"; + PATH="$(echo "$PATH" | /usr/bin/sed "s/$TOOLCHAIN_ARCH_DIR_SED\/bin://g")"; + PATH="$(echo "$PATH" | /usr/bin/sed "s/$TOOLCHAIN_ARCH_DIR_SED\/protobuf\/bin://g")"; + PATH="$(echo "$PATH" | /usr/bin/sed "s/$TOOLCHAIN_ARCH_DIR_SED\/openocd\/bin://g")"; + if [ -n "${PS1:-""}" ]; then + PS1="$(echo "$PS1" | sed 's/\[fbt\]//g')"; + elif [ -n "${PROMPT:-""}" ]; then + PROMPT="$(echo "$PROMPT" | sed 's/\[fbt\]//g')"; + fi + unset SCRIPT_PATH; + unset FBT_TOOLCHAIN_VERSION; + unset FBT_TOOLCHAIN_PATH; +} + fbtenv_check_sourced() { case "${ZSH_EVAL_CONTEXT:-""}" in *:file:*) + setopt +o nomatch; # disabling 'no match found' warning in zsh return 0;; esac if [ ${0##*/} = "fbtenv.sh" ]; then # exluding script itself @@ -138,15 +159,17 @@ fbtenv_download_toolchain_tar() { echo "Downloading toolchain:"; mkdir -p "$FBT_TOOLCHAIN_PATH/toolchain" || return 1; - "$FBT_DOWNLOADER" "$FBT_TOOLCHAIN_PATH/toolchain/$TOOLCHAIN_TAR" "$TOOLCHAIN_URL" || return 1; + "$FBT_DOWNLOADER" "$FBT_TOOLCHAIN_PATH/toolchain/$TOOLCHAIN_TAR.part" "$TOOLCHAIN_URL" || return 1; + # restoring oroginal filename if file downloaded successfully + mv "$FBT_TOOLCHAIN_PATH/toolchain/$TOOLCHAIN_TAR.part" "$FBT_TOOLCHAIN_PATH/toolchain/$TOOLCHAIN_TAR" echo "done"; return 0; } fbtenv_remove_old_tooclhain() { - printf "Removing old toolchain (if exist).."; - rm -rf "${TOOLCHAIN_ARCH_DIR}"; + printf "Removing old toolchain.."; + rm -rf "${TOOLCHAIN_ARCH_DIR:?}"; echo "done"; } @@ -175,8 +198,12 @@ fbtenv_unpack_toolchain() fbtenv_clearing() { printf "Clearing.."; - rm -rf "${FBT_TOOLCHAIN_PATH:?}/toolchain/$TOOLCHAIN_TAR"; + if [ -n "${FBT_TOOLCHAIN_PATH:-""}" ]; then + rm -rf "${FBT_TOOLCHAIN_PATH:?}/toolchain/"*.tar.gz; + rm -rf "${FBT_TOOLCHAIN_PATH:?}/toolchain/"*.part; + fi echo "done"; + trap - 2; return 0; } @@ -222,12 +249,13 @@ fbtenv_download_toolchain() fbtenv_check_tar || return 1; TOOLCHAIN_TAR="$(basename "$TOOLCHAIN_URL")"; TOOLCHAIN_DIR="$(echo "$TOOLCHAIN_TAR" | sed "s/-$FBT_TOOLCHAIN_VERSION.tar.gz//g")"; + trap fbtenv_clearing 2; # trap will be restored in fbtenv_clearing if ! fbtenv_check_downloaded_toolchain; then fbtenv_curl_wget_check || return 1; - fbtenv_download_toolchain_tar; + fbtenv_download_toolchain_tar || return 1; fi fbtenv_remove_old_tooclhain; - fbtenv_unpack_toolchain || { fbtenv_clearing && return 1; }; + fbtenv_unpack_toolchain || return 1; fbtenv_clearing; return 0; } @@ -235,15 +263,19 @@ fbtenv_download_toolchain() fbtenv_main() { fbtenv_check_sourced || return 1; + fbtenv_get_kernel_type || return 1; + if [ "$1" = "--restore" ]; then + fbtenv_restore_env; + return 0; + fi fbtenv_chck_many_source; # many source it's just a warning - fbtenv_set_shell_prompt; fbtenv_check_script_path || return 1; - fbtenv_get_kernel_type || return 1; fbtenv_check_download_toolchain || return 1; + fbtenv_set_shell_prompt; PATH="$TOOLCHAIN_ARCH_DIR/python/bin:$PATH"; PATH="$TOOLCHAIN_ARCH_DIR/bin:$PATH"; PATH="$TOOLCHAIN_ARCH_DIR/protobuf/bin:$PATH"; PATH="$TOOLCHAIN_ARCH_DIR/openocd/bin:$PATH"; } -fbtenv_main; +fbtenv_main "${1:-""}"; From ffa3ff5e7c0751d8f792f590513d3604a1b96b0a Mon Sep 17 00:00:00 2001 From: Himura Kazuto Date: Sun, 28 Aug 2022 19:39:08 +0400 Subject: [PATCH 023/824] Remove obsolete info from docs (#1672) --- ReadMe.md | 14 +------------- documentation/fbt.md | 1 - scripts/ReadMe.md | 1 - 3 files changed, 1 insertion(+), 15 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index 7a9777121af..2e3b87a675c 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -74,19 +74,7 @@ brew bundle --verbose ## Linux Prerequisites -### gcc-arm-none-eabi - -```sh -toolchain="gcc-arm-none-eabi-10.3-2021.10" -toolchain_package="$toolchain-$(uname -m)-linux" - -wget -P /opt "https://developer.arm.com/-/media/Files/downloads/gnu-rm/10.3-2021.10/$toolchain_package.tar.bz2" - -tar xjf /opt/$toolchain_package.tar.bz2 -C /opt -rm /opt/$toolchain_package.tar.bz2 - -for file in /opt/$toolchain/bin/* ; do ln -s "${file}" "/usr/bin/$(basename ${file})" ; done -``` +The FBT tool handles everything, only `git` is required. ### Optional dependencies diff --git a/documentation/fbt.md b/documentation/fbt.md index 53fc4b5e39c..48726273e70 100644 --- a/documentation/fbt.md +++ b/documentation/fbt.md @@ -6,7 +6,6 @@ It is invoked by `./fbt` in the firmware project root directory. Internally, it ## Requirements Please install Python packages required by assets build scripts: `pip3 install -r scripts/requirements.txt` -Make sure that `gcc-arm-none-eabi` toolchain & OpenOCD executables are in system's PATH. ## NB diff --git a/scripts/ReadMe.md b/scripts/ReadMe.md index d06303957fe..d37e67c90c0 100644 --- a/scripts/ReadMe.md +++ b/scripts/ReadMe.md @@ -26,7 +26,6 @@ Also display type, region and etc... ## Core1 and Core2 firmware flashing -Main flashing sequence can be found in root `Makefile`. Core2 goes first, then Core1. Never flash FUS or you will loose your job, girlfriend and keys in secure enclave. From 1350dcaf63fb9883642b585cbbf272f013a9528f Mon Sep 17 00:00:00 2001 From: Sebastian Mauer Date: Mon, 29 Aug 2022 16:04:17 +0100 Subject: [PATCH 024/824] Add support for PAC/Stanley tags (#1648) * Add support for PAC/Stanley tags * Address review comments --- lib/flipper_format/flipper_format_stream.c | 2 +- lib/lfrfid/protocols/lfrfid_protocols.c | 2 + lib/lfrfid/protocols/lfrfid_protocols.h | 1 + lib/lfrfid/protocols/protocol_pac_stanley.c | 227 ++++++++++++++++++++ lib/lfrfid/protocols/protocol_pac_stanley.h | 4 + lib/lfrfid/tools/bit_lib.c | 7 + lib/lfrfid/tools/bit_lib.h | 8 + lib/nfc/nfc_device.c | 2 +- lib/toolbox/hex.c | 20 +- lib/toolbox/hex.h | 20 +- 10 files changed, 287 insertions(+), 6 deletions(-) create mode 100644 lib/lfrfid/protocols/protocol_pac_stanley.c create mode 100644 lib/lfrfid/protocols/protocol_pac_stanley.h diff --git a/lib/flipper_format/flipper_format_stream.c b/lib/flipper_format/flipper_format_stream.c index 81189b69b87..e4b7b30030b 100644 --- a/lib/flipper_format/flipper_format_stream.c +++ b/lib/flipper_format/flipper_format_stream.c @@ -356,7 +356,7 @@ bool flipper_format_stream_read_value_line( uint8_t* data = _data; if(string_size(value) >= 2) { // sscanf "%02X" does not work here - if(hex_chars_to_uint8( + if(hex_char_to_uint8( string_get_char(value, 0), string_get_char(value, 1), &data[i])) { diff --git a/lib/lfrfid/protocols/lfrfid_protocols.c b/lib/lfrfid/protocols/lfrfid_protocols.c index 5df01b19e54..8014c917961 100644 --- a/lib/lfrfid/protocols/lfrfid_protocols.c +++ b/lib/lfrfid/protocols/lfrfid_protocols.c @@ -8,6 +8,7 @@ #include "protocol_fdx_b.h" #include "protocol_hid_generic.h" #include "protocol_hid_ex_generic.h" +#include "protocol_pac_stanley.h" const ProtocolBase* lfrfid_protocols[] = { [LFRFIDProtocolEM4100] = &protocol_em4100, @@ -19,4 +20,5 @@ const ProtocolBase* lfrfid_protocols[] = { [LFRFIDProtocolFDXB] = &protocol_fdx_b, [LFRFIDProtocolHidGeneric] = &protocol_hid_generic, [LFRFIDProtocolHidExGeneric] = &protocol_hid_ex_generic, + [LFRFIDProtocolPACStanley] = &protocol_pac_stanley, }; \ No newline at end of file diff --git a/lib/lfrfid/protocols/lfrfid_protocols.h b/lib/lfrfid/protocols/lfrfid_protocols.h index 4b8f6573d1b..2a21641277d 100644 --- a/lib/lfrfid/protocols/lfrfid_protocols.h +++ b/lib/lfrfid/protocols/lfrfid_protocols.h @@ -17,6 +17,7 @@ typedef enum { LFRFIDProtocolFDXB, LFRFIDProtocolHidGeneric, LFRFIDProtocolHidExGeneric, + LFRFIDProtocolPACStanley, LFRFIDProtocolMax, } LFRFIDProtocol; diff --git a/lib/lfrfid/protocols/protocol_pac_stanley.c b/lib/lfrfid/protocols/protocol_pac_stanley.c new file mode 100644 index 00000000000..7ab16a103e8 --- /dev/null +++ b/lib/lfrfid/protocols/protocol_pac_stanley.c @@ -0,0 +1,227 @@ +#include +#include +#include +#include +#include +#include "lfrfid_protocols.h" + +#define PAC_STANLEY_ENCODED_BIT_SIZE (128) +#define PAC_STANLEY_ENCODED_BYTE_SIZE (((PAC_STANLEY_ENCODED_BIT_SIZE) / 8)) +#define PAC_STANLEY_PREAMBLE_BIT_SIZE (8) +#define PAC_STANLEY_PREAMBLE_BYTE_SIZE (1) +#define PAC_STANLEY_ENCODED_BYTE_FULL_SIZE \ + (PAC_STANLEY_ENCODED_BYTE_SIZE + PAC_STANLEY_PREAMBLE_BYTE_SIZE) +#define PAC_STANLEY_BYTE_LENGTH (10) // start bit, 7 data bits, parity bit, stop bit +#define PAC_STANLEY_DATA_START_INDEX 8 + (3 * PAC_STANLEY_BYTE_LENGTH) + 1 + +#define PAC_STANLEY_DECODED_DATA_SIZE (4) +#define PAC_STANLEY_ENCODED_DATA_SIZE (sizeof(ProtocolPACStanley)) + +#define PAC_STANLEY_CLOCKS_IN_US (32) +#define PAC_STANLEY_CYCLE_LENGTH (256) +#define PAC_STANLEY_MIN_TIME (60) +#define PAC_STANLEY_MAX_TIME (4000) + +typedef struct { + bool inverted; + bool got_preamble; + size_t encoded_index; + uint8_t encoded_data[PAC_STANLEY_ENCODED_BYTE_FULL_SIZE]; + uint8_t data[PAC_STANLEY_DECODED_DATA_SIZE]; +} ProtocolPACStanley; + +ProtocolPACStanley* protocol_pac_stanley_alloc(void) { + ProtocolPACStanley* protocol = malloc(sizeof(ProtocolPACStanley)); + return (void*)protocol; +} + +void protocol_pac_stanley_free(ProtocolPACStanley* protocol) { + free(protocol); +} + +uint8_t* protocol_pac_stanley_get_data(ProtocolPACStanley* protocol) { + return protocol->data; +} + +static void protocol_pac_stanley_decode(ProtocolPACStanley* protocol) { + uint8_t asciiCardId[8]; + for(size_t idx = 0; idx < 8; idx++) { + uint8_t byte = bit_lib_reverse_8_fast(bit_lib_get_bits( + protocol->encoded_data, + PAC_STANLEY_DATA_START_INDEX + (PAC_STANLEY_BYTE_LENGTH * idx), + 8)); + asciiCardId[idx] = byte & 0x7F; // discard the parity bit + } + + hex_chars_to_uint8((char*)asciiCardId, protocol->data); +} + +static bool protocol_pac_stanley_can_be_decoded(ProtocolPACStanley* protocol) { + // Check preamble + if(bit_lib_get_bits(protocol->encoded_data, 0, 8) != 0b11111111) return false; + if(bit_lib_get_bit(protocol->encoded_data, 8) != 0) return false; + if(bit_lib_get_bit(protocol->encoded_data, 9) != 0) return false; + if(bit_lib_get_bit(protocol->encoded_data, 10) != 1) return false; + if(bit_lib_get_bits(protocol->encoded_data, 11, 8) != 0b00000010) return false; + + // Check next preamble + if(bit_lib_get_bits(protocol->encoded_data, 128, 8) != 0b11111111) return false; + + // Checksum + uint8_t checksum = 0; + uint8_t stripped_byte; + for(size_t idx = 0; idx < 9; idx++) { + uint8_t byte = bit_lib_reverse_8_fast(bit_lib_get_bits( + protocol->encoded_data, + PAC_STANLEY_DATA_START_INDEX + (PAC_STANLEY_BYTE_LENGTH * idx), + 8)); + stripped_byte = byte & 0x7F; // discard the parity bit + if(bit_lib_test_parity_32(stripped_byte, BitLibParityOdd) != (byte & 0x80) >> 7) { + return false; + } + if(idx < 8) checksum ^= stripped_byte; + } + if(stripped_byte != checksum) return false; + return true; +} + +void protocol_pac_stanley_decoder_start(ProtocolPACStanley* protocol) { + memset(protocol->data, 0, PAC_STANLEY_DECODED_DATA_SIZE); + protocol->inverted = false; + protocol->got_preamble = false; +} + +bool protocol_pac_stanley_decoder_feed(ProtocolPACStanley* protocol, bool level, uint32_t duration) { + bool pushed = false; + + if(duration > PAC_STANLEY_MAX_TIME) return false; + + uint8_t pulses = (uint8_t)round((float)duration / PAC_STANLEY_CYCLE_LENGTH); + + // Handle last stopbit & preamble (1 sb, 8 bit preamble) + if(pulses >= 9 && !protocol->got_preamble) { + pulses = 8; + protocol->got_preamble = true; + protocol->inverted = !level; + } else if(pulses >= 9 && protocol->got_preamble) { + protocol->got_preamble = false; + } else if(pulses == 0 && duration > PAC_STANLEY_MIN_TIME) { + pulses = 1; + } + + if(pulses) { + for(uint8_t i = 0; i < pulses; i++) { + bit_lib_push_bit( + protocol->encoded_data, + PAC_STANLEY_ENCODED_BYTE_FULL_SIZE, + level ^ protocol->inverted); + } + pushed = true; + } + + if(pushed && protocol_pac_stanley_can_be_decoded(protocol)) { + protocol_pac_stanley_decode(protocol); + return true; + } + + return false; +} + +bool protocol_pac_stanley_encoder_start(ProtocolPACStanley* protocol) { + memset(protocol->encoded_data, 0, PAC_STANLEY_ENCODED_BYTE_SIZE); + + uint8_t idbytes[10]; + idbytes[0] = '2'; + idbytes[1] = '0'; + + uint8_to_hex_chars(protocol->data, &idbytes[2], 8); + + // insert start and stop bits + for(size_t i = 0; i < 16; i++) protocol->encoded_data[i] = 0x40 >> (i + 3) % 5 * 2; + + protocol->encoded_data[0] = 0xFF; // mark + stop + protocol->encoded_data[1] = 0x20; // start + reflect8(STX) + + uint8_t checksum = 0; + for(size_t i = 2; i < 13; i++) { + uint8_t shift = 7 - (i + 3) % 4 * 2; + uint8_t index = i + (i - 1) / 4; + + uint16_t pattern; + if(i < 12) { + pattern = bit_lib_reverse_8_fast(idbytes[i - 2]); + pattern |= bit_lib_test_parity_32(pattern, BitLibParityOdd); + if(i > 3) checksum ^= idbytes[i - 2]; + } else { + pattern = (bit_lib_reverse_8_fast(checksum) & 0xFE) | + (bit_lib_test_parity_32(checksum, BitLibParityOdd)); + } + pattern <<= shift; + + protocol->encoded_data[index] |= pattern >> 8 & 0xFF; + protocol->encoded_data[index + 1] |= pattern & 0xFF; + } + + protocol->encoded_index = 0; + return true; +} + +LevelDuration protocol_pac_stanley_encoder_yield(ProtocolPACStanley* protocol) { + uint16_t length = PAC_STANLEY_CLOCKS_IN_US; + bool bit = bit_lib_get_bit(protocol->encoded_data, protocol->encoded_index); + bit_lib_increment_index(protocol->encoded_index, PAC_STANLEY_ENCODED_BIT_SIZE); + while(bit_lib_get_bit(protocol->encoded_data, protocol->encoded_index) == bit) { + length += PAC_STANLEY_CLOCKS_IN_US; + bit_lib_increment_index(protocol->encoded_index, PAC_STANLEY_ENCODED_BIT_SIZE); + } + + return level_duration_make(bit, length); +} + +bool protocol_pac_stanley_write_data(ProtocolPACStanley* protocol, void* data) { + LFRFIDWriteRequest* request = (LFRFIDWriteRequest*)data; + bool result = false; + + protocol_pac_stanley_encoder_start(protocol); + + if(request->write_type == LFRFIDWriteTypeT5577) { + request->t5577.block[0] = LFRFID_T5577_MODULATION_DIRECT | LFRFID_T5577_BITRATE_RF_32 | + (4 << LFRFID_T5577_MAXBLOCK_SHIFT); + request->t5577.block[1] = bit_lib_get_bits_32(protocol->encoded_data, 0, 32); + request->t5577.block[2] = bit_lib_get_bits_32(protocol->encoded_data, 32, 32); + request->t5577.block[3] = bit_lib_get_bits_32(protocol->encoded_data, 64, 32); + request->t5577.block[4] = bit_lib_get_bits_32(protocol->encoded_data, 96, 32); + request->t5577.blocks_to_write = 5; + result = true; + } + return result; +} + +void protocol_pac_stanley_render_data(ProtocolPACStanley* protocol, string_t result) { + uint8_t* data = protocol->data; + string_printf(result, "CIN: %02X%02X%02X%02X", data[0], data[1], data[2], data[3]); +} + +const ProtocolBase protocol_pac_stanley = { + .name = "PAC/Stanley", + .manufacturer = "N/A", + .data_size = PAC_STANLEY_DECODED_DATA_SIZE, + .features = LFRFIDFeatureASK, + .validate_count = 3, + .alloc = (ProtocolAlloc)protocol_pac_stanley_alloc, + .free = (ProtocolFree)protocol_pac_stanley_free, + .get_data = (ProtocolGetData)protocol_pac_stanley_get_data, + .decoder = + { + .start = (ProtocolDecoderStart)protocol_pac_stanley_decoder_start, + .feed = (ProtocolDecoderFeed)protocol_pac_stanley_decoder_feed, + }, + .encoder = + { + .start = (ProtocolEncoderStart)protocol_pac_stanley_encoder_start, + .yield = (ProtocolEncoderYield)protocol_pac_stanley_encoder_yield, + }, + .render_data = (ProtocolRenderData)protocol_pac_stanley_render_data, + .render_brief_data = (ProtocolRenderData)protocol_pac_stanley_render_data, + .write_data = (ProtocolWriteData)protocol_pac_stanley_write_data, +}; \ No newline at end of file diff --git a/lib/lfrfid/protocols/protocol_pac_stanley.h b/lib/lfrfid/protocols/protocol_pac_stanley.h new file mode 100644 index 00000000000..3ca329cf797 --- /dev/null +++ b/lib/lfrfid/protocols/protocol_pac_stanley.h @@ -0,0 +1,4 @@ +#pragma once +#include + +extern const ProtocolBase protocol_pac_stanley; \ No newline at end of file diff --git a/lib/lfrfid/tools/bit_lib.c b/lib/lfrfid/tools/bit_lib.c index b57bda8a8ae..2df12707af2 100644 --- a/lib/lfrfid/tools/bit_lib.c +++ b/lib/lfrfid/tools/bit_lib.c @@ -262,6 +262,13 @@ uint16_t bit_lib_reverse_16_fast(uint16_t data) { return result; } +uint8_t bit_lib_reverse_8_fast(uint8_t byte) { + byte = (byte & 0xF0) >> 4 | (byte & 0x0F) << 4; + byte = (byte & 0xCC) >> 2 | (byte & 0x33) << 2; + byte = (byte & 0xAA) >> 1 | (byte & 0x55) << 1; + return byte; +} + uint16_t bit_lib_crc16( uint8_t const* data, size_t data_size, diff --git a/lib/lfrfid/tools/bit_lib.h b/lib/lfrfid/tools/bit_lib.h index 24aae0e9cc8..482ae36b220 100644 --- a/lib/lfrfid/tools/bit_lib.h +++ b/lib/lfrfid/tools/bit_lib.h @@ -194,6 +194,14 @@ void bit_lib_print_regions( */ uint16_t bit_lib_reverse_16_fast(uint16_t data); +/** + * @brief Reverse bits in uint8_t, faster than generic bit_lib_reverse_bits. + * + * @param byte Byte + * @return uint8_t the reversed byte + */ +uint8_t bit_lib_reverse_8_fast(uint8_t byte); + /** * @brief Slow, but generic CRC16 implementation * diff --git a/lib/nfc/nfc_device.c b/lib/nfc/nfc_device.c index 0bfdb3dacae..c46919219a6 100644 --- a/lib/nfc/nfc_device.c +++ b/lib/nfc/nfc_device.c @@ -782,7 +782,7 @@ static void nfc_device_load_mifare_classic_block( char hi = string_get_char(block_str, 3 * i); char low = string_get_char(block_str, 3 * i + 1); uint8_t byte = 0; - if(hex_chars_to_uint8(hi, low, &byte)) { + if(hex_char_to_uint8(hi, low, &byte)) { block_tmp.value[i] = byte; } else { FURI_BIT_SET(block_unknown_bytes_mask, i); diff --git a/lib/toolbox/hex.c b/lib/toolbox/hex.c index 41bb24bba74..7b2719b795e 100644 --- a/lib/toolbox/hex.c +++ b/lib/toolbox/hex.c @@ -15,7 +15,7 @@ bool hex_char_to_hex_nibble(char c, uint8_t* nibble) { } } -bool hex_chars_to_uint8(char hi, char low, uint8_t* value) { +bool hex_char_to_uint8(char hi, char low, uint8_t* value) { uint8_t hi_nibble_value, low_nibble_value; if(hex_char_to_hex_nibble(hi, &hi_nibble_value) && @@ -27,13 +27,29 @@ bool hex_chars_to_uint8(char hi, char low, uint8_t* value) { } } +bool hex_chars_to_uint8(const char* value_str, uint8_t* value) { + bool parse_success = false; + while(*value_str && value_str[1]) { + parse_success = hex_char_to_uint8(*value_str, value_str[1], value++); + if(!parse_success) break; + value_str += 2; + } + return parse_success; +} + 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]); + parse_success = hex_char_to_uint8(value_str[i * 2], value_str[i * 2 + 1], &_value[7 - i]); if(!parse_success) break; } return parse_success; } + +void uint8_to_hex_chars(const uint8_t* src, uint8_t* target, int length) { + const char chars[] = "0123456789ABCDEF"; + while(--length >= 0) + target[length] = chars[(src[length >> 1] >> ((1 - (length & 1)) << 2)) & 0xF]; +} diff --git a/lib/toolbox/hex.h b/lib/toolbox/hex.h index c6683a52cdc..740f23b776c 100644 --- a/lib/toolbox/hex.h +++ b/lib/toolbox/hex.h @@ -14,14 +14,22 @@ extern "C" { */ bool hex_char_to_hex_nibble(char c, uint8_t* nibble); -/** Convert ASCII hex values to byte +/** Convert ASCII hex value 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); +bool hex_char_to_uint8(char hi, char low, uint8_t* value); + +/** Convert ASCII hex values to uint8_t + * @param value_str ASCII data + * @param value output value + * + * @return bool conversion status + */ +bool hex_chars_to_uint8(const char* value_str, uint8_t* value); /** Convert ASCII hex values to uint64_t * @param value_str ASCII 64 bi data @@ -31,6 +39,14 @@ bool hex_chars_to_uint8(char hi, char low, uint8_t* value); */ bool hex_chars_to_uint64(const char* value_str, uint64_t* value); +/** Convert uint8_t to ASCII hex values + * @param src source data + * @param target output value + * @param length data length + * + */ +void uint8_to_hex_chars(const uint8_t* src, uint8_t* target, int length); + #ifdef __cplusplus } #endif From 39f936ce1237a9c65fe0a657f22ec92d4bd67094 Mon Sep 17 00:00:00 2001 From: Sebastian Mauer Date: Mon, 29 Aug 2022 16:08:10 +0100 Subject: [PATCH 025/824] Add support for Paradox tags (#1655) * Add support for Paradox tags --- lib/lfrfid/protocols/lfrfid_protocols.c | 2 + lib/lfrfid/protocols/lfrfid_protocols.h | 1 + lib/lfrfid/protocols/protocol_paradox.c | 201 ++++++++++++++++++++++++ lib/lfrfid/protocols/protocol_paradox.h | 4 + 4 files changed, 208 insertions(+) create mode 100644 lib/lfrfid/protocols/protocol_paradox.c create mode 100644 lib/lfrfid/protocols/protocol_paradox.h diff --git a/lib/lfrfid/protocols/lfrfid_protocols.c b/lib/lfrfid/protocols/lfrfid_protocols.c index 8014c917961..c8d8d58c348 100644 --- a/lib/lfrfid/protocols/lfrfid_protocols.c +++ b/lib/lfrfid/protocols/lfrfid_protocols.c @@ -8,6 +8,7 @@ #include "protocol_fdx_b.h" #include "protocol_hid_generic.h" #include "protocol_hid_ex_generic.h" +#include "protocol_paradox.h" #include "protocol_pac_stanley.h" const ProtocolBase* lfrfid_protocols[] = { @@ -20,5 +21,6 @@ const ProtocolBase* lfrfid_protocols[] = { [LFRFIDProtocolFDXB] = &protocol_fdx_b, [LFRFIDProtocolHidGeneric] = &protocol_hid_generic, [LFRFIDProtocolHidExGeneric] = &protocol_hid_ex_generic, + [LFRFIDProtocolParadox] = &protocol_paradox, [LFRFIDProtocolPACStanley] = &protocol_pac_stanley, }; \ No newline at end of file diff --git a/lib/lfrfid/protocols/lfrfid_protocols.h b/lib/lfrfid/protocols/lfrfid_protocols.h index 2a21641277d..4f529e65f10 100644 --- a/lib/lfrfid/protocols/lfrfid_protocols.h +++ b/lib/lfrfid/protocols/lfrfid_protocols.h @@ -17,6 +17,7 @@ typedef enum { LFRFIDProtocolFDXB, LFRFIDProtocolHidGeneric, LFRFIDProtocolHidExGeneric, + LFRFIDProtocolParadox, LFRFIDProtocolPACStanley, LFRFIDProtocolMax, diff --git a/lib/lfrfid/protocols/protocol_paradox.c b/lib/lfrfid/protocols/protocol_paradox.c new file mode 100644 index 00000000000..66399a6057a --- /dev/null +++ b/lib/lfrfid/protocols/protocol_paradox.c @@ -0,0 +1,201 @@ +#include +#include +#include +#include +#include +#include "lfrfid_protocols.h" + +#define JITTER_TIME (20) +#define MIN_TIME (64 - JITTER_TIME) +#define MAX_TIME (80 + JITTER_TIME) + +#define PARADOX_DECODED_DATA_SIZE (6) + +#define PARADOX_PREAMBLE_LENGTH (8) +#define PARADOX_ENCODED_BIT_SIZE (96) +#define PARADOX_ENCODED_DATA_SIZE (((PARADOX_ENCODED_BIT_SIZE) / 8) + 1) +#define PARADOX_ENCODED_DATA_LAST (PARADOX_ENCODED_DATA_SIZE - 1) + +typedef struct { + FSKDemod* fsk_demod; +} ProtocolParadoxDecoder; + +typedef struct { + FSKOsc* fsk_osc; + uint8_t encoded_index; +} ProtocolParadoxEncoder; + +typedef struct { + ProtocolParadoxDecoder decoder; + ProtocolParadoxEncoder encoder; + uint8_t encoded_data[PARADOX_ENCODED_DATA_SIZE]; + uint8_t data[PARADOX_DECODED_DATA_SIZE]; +} ProtocolParadox; + +ProtocolParadox* protocol_paradox_alloc(void) { + ProtocolParadox* protocol = malloc(sizeof(ProtocolParadox)); + protocol->decoder.fsk_demod = fsk_demod_alloc(MIN_TIME, 6, MAX_TIME, 5); + protocol->encoder.fsk_osc = fsk_osc_alloc(8, 10, 50); + + return protocol; +}; + +void protocol_paradox_free(ProtocolParadox* protocol) { + fsk_demod_free(protocol->decoder.fsk_demod); + fsk_osc_free(protocol->encoder.fsk_osc); + free(protocol); +}; + +uint8_t* protocol_paradox_get_data(ProtocolParadox* protocol) { + return protocol->data; +}; + +void protocol_paradox_decoder_start(ProtocolParadox* protocol) { + memset(protocol->encoded_data, 0, PARADOX_ENCODED_DATA_SIZE); +}; + +static bool protocol_paradox_can_be_decoded(ProtocolParadox* protocol) { + // check preamble + if(protocol->encoded_data[0] != 0b00001111 || + protocol->encoded_data[PARADOX_ENCODED_DATA_LAST] != 0b00001111) + return false; + + for(uint32_t i = PARADOX_PREAMBLE_LENGTH; i < 96; i += 2) { + if(bit_lib_get_bit(protocol->encoded_data, i) == + bit_lib_get_bit(protocol->encoded_data, i + 1)) { + return false; + } + } + + return true; +} + +static void protocol_paradox_decode(uint8_t* encoded_data, uint8_t* decoded_data) { + for(uint32_t i = PARADOX_PREAMBLE_LENGTH; i < 96; i += 2) { + if(bit_lib_get_bits(encoded_data, i, 2) == 0b01) { + bit_lib_push_bit(decoded_data, PARADOX_DECODED_DATA_SIZE, 0); + } else if(bit_lib_get_bits(encoded_data, i, 2) == 0b10) { + bit_lib_push_bit(decoded_data, PARADOX_DECODED_DATA_SIZE, 1); + } + } + bit_lib_push_bit(decoded_data, PARADOX_DECODED_DATA_SIZE, 0); + bit_lib_push_bit(decoded_data, PARADOX_DECODED_DATA_SIZE, 0); + bit_lib_push_bit(decoded_data, PARADOX_DECODED_DATA_SIZE, 0); + bit_lib_push_bit(decoded_data, PARADOX_DECODED_DATA_SIZE, 0); +} + +bool protocol_paradox_decoder_feed(ProtocolParadox* protocol, bool level, uint32_t duration) { + bool value; + uint32_t count; + + fsk_demod_feed(protocol->decoder.fsk_demod, level, duration, &value, &count); + if(count > 0) { + for(size_t i = 0; i < count; i++) { + bit_lib_push_bit(protocol->encoded_data, PARADOX_ENCODED_DATA_SIZE, value); + if(protocol_paradox_can_be_decoded(protocol)) { + protocol_paradox_decode(protocol->encoded_data, protocol->data); + + return true; + } + } + } + + return false; +}; + +static void protocol_paradox_encode(const uint8_t* decoded_data, uint8_t* encoded_data) { + // preamble + bit_lib_set_bits(encoded_data, 0, 0b00001111, 8); + + for(size_t i = 0; i < 44; i++) { + if(bit_lib_get_bit(decoded_data, i)) { + bit_lib_set_bits(encoded_data, PARADOX_PREAMBLE_LENGTH + i * 2, 0b10, 2); + } else { + bit_lib_set_bits(encoded_data, PARADOX_PREAMBLE_LENGTH + i * 2, 0b01, 2); + } + } +}; + +bool protocol_paradox_encoder_start(ProtocolParadox* protocol) { + protocol_paradox_encode(protocol->data, (uint8_t*)protocol->encoded_data); + protocol->encoder.encoded_index = 0; + fsk_osc_reset(protocol->encoder.fsk_osc); + return true; +}; + +LevelDuration protocol_paradox_encoder_yield(ProtocolParadox* protocol) { + bool level; + uint32_t duration; + + bool bit = bit_lib_get_bit(protocol->encoded_data, protocol->encoder.encoded_index); + bool advance = fsk_osc_next_half(protocol->encoder.fsk_osc, bit, &level, &duration); + + if(advance) { + bit_lib_increment_index(protocol->encoder.encoded_index, PARADOX_ENCODED_BIT_SIZE); + } + return level_duration_make(level, duration); +}; + +void protocol_paradox_render_data(ProtocolParadox* protocol, string_t result) { + uint8_t* decoded_data = protocol->data; + uint8_t fc = bit_lib_get_bits(decoded_data, 10, 8); + uint16_t card_id = bit_lib_get_bits_16(decoded_data, 18, 16); + + string_cat_printf(result, "Facility: %u\r\n", fc); + string_cat_printf(result, "Card: %lu\r\n", card_id); + string_cat_printf(result, "Data: "); + for(size_t i = 0; i < PARADOX_DECODED_DATA_SIZE; i++) { + string_cat_printf(result, "%02X", decoded_data[i]); + } +}; + +void protocol_paradox_render_brief_data(ProtocolParadox* protocol, string_t result) { + uint8_t* decoded_data = protocol->data; + + uint8_t fc = bit_lib_get_bits(decoded_data, 10, 8); + uint16_t card_id = bit_lib_get_bits_16(decoded_data, 18, 16); + + string_cat_printf(result, "ID: %03u,%05u", fc, card_id); +}; + +bool protocol_paradox_write_data(ProtocolParadox* protocol, void* data) { + LFRFIDWriteRequest* request = (LFRFIDWriteRequest*)data; + bool result = false; + + protocol_paradox_encode(protocol->data, (uint8_t*)protocol->encoded_data); + + if(request->write_type == LFRFIDWriteTypeT5577) { + request->t5577.block[0] = LFRFID_T5577_MODULATION_FSK2a | LFRFID_T5577_BITRATE_RF_50 | + (3 << LFRFID_T5577_MAXBLOCK_SHIFT); + request->t5577.block[1] = bit_lib_get_bits_32(protocol->encoded_data, 0, 32); + request->t5577.block[2] = bit_lib_get_bits_32(protocol->encoded_data, 32, 32); + request->t5577.block[3] = bit_lib_get_bits_32(protocol->encoded_data, 64, 32); + request->t5577.blocks_to_write = 4; + result = true; + } + return result; +}; + +const ProtocolBase protocol_paradox = { + .name = "Paradox", + .manufacturer = "Paradox", + .data_size = PARADOX_DECODED_DATA_SIZE, + .features = LFRFIDFeatureASK, + .validate_count = 3, + .alloc = (ProtocolAlloc)protocol_paradox_alloc, + .free = (ProtocolFree)protocol_paradox_free, + .get_data = (ProtocolGetData)protocol_paradox_get_data, + .decoder = + { + .start = (ProtocolDecoderStart)protocol_paradox_decoder_start, + .feed = (ProtocolDecoderFeed)protocol_paradox_decoder_feed, + }, + .encoder = + { + .start = (ProtocolEncoderStart)protocol_paradox_encoder_start, + .yield = (ProtocolEncoderYield)protocol_paradox_encoder_yield, + }, + .render_data = (ProtocolRenderData)protocol_paradox_render_data, + .render_brief_data = (ProtocolRenderData)protocol_paradox_render_brief_data, + .write_data = (ProtocolWriteData)protocol_paradox_write_data, +}; \ No newline at end of file diff --git a/lib/lfrfid/protocols/protocol_paradox.h b/lib/lfrfid/protocols/protocol_paradox.h new file mode 100644 index 00000000000..a80696c35ab --- /dev/null +++ b/lib/lfrfid/protocols/protocol_paradox.h @@ -0,0 +1,4 @@ +#pragma once +#include + +extern const ProtocolBase protocol_paradox; \ No newline at end of file From f09c5889ddd85474b7b089321ce88756da07c6ae Mon Sep 17 00:00:00 2001 From: Sebastian Mauer Date: Mon, 29 Aug 2022 16:15:27 +0100 Subject: [PATCH 026/824] Add support for Jablotron tags (#1657) * Add support for Jablotron tags --- lib/lfrfid/protocols/lfrfid_protocols.c | 2 + lib/lfrfid/protocols/lfrfid_protocols.h | 2 +- lib/lfrfid/protocols/protocol_jablotron.c | 212 ++++++++++++++++++++++ lib/lfrfid/protocols/protocol_jablotron.h | 4 + 4 files changed, 219 insertions(+), 1 deletion(-) create mode 100644 lib/lfrfid/protocols/protocol_jablotron.c create mode 100644 lib/lfrfid/protocols/protocol_jablotron.h diff --git a/lib/lfrfid/protocols/lfrfid_protocols.c b/lib/lfrfid/protocols/lfrfid_protocols.c index c8d8d58c348..6d42cf31e64 100644 --- a/lib/lfrfid/protocols/lfrfid_protocols.c +++ b/lib/lfrfid/protocols/lfrfid_protocols.c @@ -8,6 +8,7 @@ #include "protocol_fdx_b.h" #include "protocol_hid_generic.h" #include "protocol_hid_ex_generic.h" +#include "protocol_jablotron.h" #include "protocol_paradox.h" #include "protocol_pac_stanley.h" @@ -21,6 +22,7 @@ const ProtocolBase* lfrfid_protocols[] = { [LFRFIDProtocolFDXB] = &protocol_fdx_b, [LFRFIDProtocolHidGeneric] = &protocol_hid_generic, [LFRFIDProtocolHidExGeneric] = &protocol_hid_ex_generic, + [LFRFIDProtocolJablotron] = &protocol_jablotron, [LFRFIDProtocolParadox] = &protocol_paradox, [LFRFIDProtocolPACStanley] = &protocol_pac_stanley, }; \ No newline at end of file diff --git a/lib/lfrfid/protocols/lfrfid_protocols.h b/lib/lfrfid/protocols/lfrfid_protocols.h index 4f529e65f10..42113af1ecc 100644 --- a/lib/lfrfid/protocols/lfrfid_protocols.h +++ b/lib/lfrfid/protocols/lfrfid_protocols.h @@ -17,9 +17,9 @@ typedef enum { LFRFIDProtocolFDXB, LFRFIDProtocolHidGeneric, LFRFIDProtocolHidExGeneric, + LFRFIDProtocolJablotron, LFRFIDProtocolParadox, LFRFIDProtocolPACStanley, - LFRFIDProtocolMax, } LFRFIDProtocol; diff --git a/lib/lfrfid/protocols/protocol_jablotron.c b/lib/lfrfid/protocols/protocol_jablotron.c new file mode 100644 index 00000000000..64b55166c54 --- /dev/null +++ b/lib/lfrfid/protocols/protocol_jablotron.c @@ -0,0 +1,212 @@ +#include +#include "toolbox/level_duration.h" +#include "protocol_jablotron.h" +#include +#include +#include "lfrfid_protocols.h" + +#define JABLOTRON_ENCODED_BIT_SIZE (64) +#define JABLOTRON_ENCODED_BYTE_SIZE (((JABLOTRON_ENCODED_BIT_SIZE) / 8)) +#define JABLOTRON_PREAMBLE_BIT_SIZE (16) +#define JABLOTRON_PREAMBLE_BYTE_SIZE (2) +#define JABLOTRON_ENCODED_BYTE_FULL_SIZE \ + (JABLOTRON_ENCODED_BYTE_SIZE + JABLOTRON_PREAMBLE_BYTE_SIZE) + +#define JABLOTRON_DECODED_DATA_SIZE (5) + +#define JABLOTRON_SHORT_TIME (256) +#define JABLOTRON_LONG_TIME (512) +#define JABLOTRON_JITTER_TIME (120) + +#define JABLOTRON_SHORT_TIME_LOW (JABLOTRON_SHORT_TIME - JABLOTRON_JITTER_TIME) +#define JABLOTRON_SHORT_TIME_HIGH (JABLOTRON_SHORT_TIME + JABLOTRON_JITTER_TIME) +#define JABLOTRON_LONG_TIME_LOW (JABLOTRON_LONG_TIME - JABLOTRON_JITTER_TIME) +#define JABLOTRON_LONG_TIME_HIGH (JABLOTRON_LONG_TIME + JABLOTRON_JITTER_TIME) + +typedef struct { + bool last_short; + bool last_level; + size_t encoded_index; + uint8_t encoded_data[JABLOTRON_ENCODED_BYTE_FULL_SIZE]; + uint8_t data[JABLOTRON_DECODED_DATA_SIZE]; +} ProtocolJablotron; + +ProtocolJablotron* protocol_jablotron_alloc(void) { + ProtocolJablotron* protocol = malloc(sizeof(ProtocolJablotron)); + return protocol; +}; + +void protocol_jablotron_free(ProtocolJablotron* protocol) { + free(protocol); +}; + +uint8_t* protocol_jablotron_get_data(ProtocolJablotron* proto) { + return proto->data; +}; + +void protocol_jablotron_decoder_start(ProtocolJablotron* protocol) { + memset(protocol->encoded_data, 0, JABLOTRON_ENCODED_BYTE_FULL_SIZE); + protocol->last_short = false; +}; + +uint8_t protocol_jablotron_checksum(uint8_t* bits) { + uint8_t chksum = 0; + for(uint8_t i = 16; i < 56; i += 8) { + chksum += bit_lib_get_bits(bits, i, 8); + } + chksum ^= 0x3A; + return chksum; +} + +uint64_t protocol_jablotron_card_id(uint8_t* bytes) { + uint64_t id = 0; + for(int i = 0; i < 5; i++) { + id *= 100; + id += ((bytes[i] & 0xF0) >> 4) * 10 + (bytes[i] & 0x0F); + } + return id; +} + +static bool protocol_jablotron_can_be_decoded(ProtocolJablotron* protocol) { + // check 11 bits preamble + if(bit_lib_get_bits_16(protocol->encoded_data, 0, 16) != 0b1111111111111111) return false; + // check next 11 bits preamble + if(bit_lib_get_bits_16(protocol->encoded_data, 64, 16) != 0b1111111111111111) return false; + + uint8_t checksum = bit_lib_get_bits(protocol->encoded_data, 56, 8); + if(checksum != protocol_jablotron_checksum(protocol->encoded_data)) return false; + + return true; +} + +void protocol_jablotron_decode(ProtocolJablotron* protocol) { + bit_lib_copy_bits(protocol->data, 0, 40, protocol->encoded_data, 16); +} + +bool protocol_jablotron_decoder_feed(ProtocolJablotron* protocol, bool level, uint32_t duration) { + UNUSED(level); + bool pushed = false; + + // Bi-Phase Manchester decoding + if(duration >= JABLOTRON_SHORT_TIME_LOW && duration <= JABLOTRON_SHORT_TIME_HIGH) { + if(protocol->last_short == false) { + protocol->last_short = true; + } else { + pushed = true; + bit_lib_push_bit(protocol->encoded_data, JABLOTRON_ENCODED_BYTE_FULL_SIZE, false); + protocol->last_short = false; + } + } else if(duration >= JABLOTRON_LONG_TIME_LOW && duration <= JABLOTRON_LONG_TIME_HIGH) { + if(protocol->last_short == false) { + pushed = true; + bit_lib_push_bit(protocol->encoded_data, JABLOTRON_ENCODED_BYTE_FULL_SIZE, true); + } else { + // reset + protocol->last_short = false; + } + } else { + // reset + protocol->last_short = false; + } + + if(pushed && protocol_jablotron_can_be_decoded(protocol)) { + protocol_jablotron_decode(protocol); + return true; + } + + return false; +}; + +bool protocol_jablotron_encoder_start(ProtocolJablotron* protocol) { + // preamble + bit_lib_set_bits(protocol->encoded_data, 0, 0b11111111, 8); + bit_lib_set_bits(protocol->encoded_data, 8, 0b11111111, 8); + + // Full code + bit_lib_copy_bits(protocol->encoded_data, 16, 40, protocol->data, 0); + + // Checksum + bit_lib_set_bits( + protocol->encoded_data, 56, protocol_jablotron_checksum(protocol->encoded_data), 8); + + protocol->encoded_index = 0; + protocol->last_short = false; + protocol->last_level = false; + return true; +}; + +LevelDuration protocol_jablotron_encoder_yield(ProtocolJablotron* protocol) { + uint32_t duration; + protocol->last_level = !protocol->last_level; + + bool bit = bit_lib_get_bit(protocol->encoded_data, protocol->encoded_index); + + // Bi-Phase Manchester encoder + if(bit) { + // one long pulse for 1 + duration = JABLOTRON_LONG_TIME / 8; + bit_lib_increment_index(protocol->encoded_index, JABLOTRON_ENCODED_BIT_SIZE); + } else { + // two short pulses for 0 + duration = JABLOTRON_SHORT_TIME / 8; + if(protocol->last_short) { + bit_lib_increment_index(protocol->encoded_index, JABLOTRON_ENCODED_BIT_SIZE); + protocol->last_short = false; + } else { + protocol->last_short = true; + } + } + + return level_duration_make(protocol->last_level, duration); +}; + +void protocol_jablotron_render_data(ProtocolJablotron* protocol, string_t result) { + uint64_t id = protocol_jablotron_card_id(protocol->data); + string_printf(result, "ID: %llx\r\n", id); +}; + +void protocol_jablotron_render_brief_data(ProtocolJablotron* protocol, string_t result) { + uint64_t id = protocol_jablotron_card_id(protocol->data); + string_printf(result, "ID: %llx\r\n", id); +}; + +bool protocol_jablotron_write_data(ProtocolJablotron* protocol, void* data) { + LFRFIDWriteRequest* request = (LFRFIDWriteRequest*)data; + bool result = false; + + protocol_jablotron_encoder_start(protocol); + + if(request->write_type == LFRFIDWriteTypeT5577) { + request->t5577.block[0] = LFRFID_T5577_MODULATION_DIPHASE | LFRFID_T5577_BITRATE_RF_64 | + (2 << LFRFID_T5577_MAXBLOCK_SHIFT); + request->t5577.block[1] = bit_lib_get_bits_32(protocol->encoded_data, 0, 32); + request->t5577.block[2] = bit_lib_get_bits_32(protocol->encoded_data, 32, 32); + request->t5577.blocks_to_write = 3; + result = true; + } + return result; +}; + +const ProtocolBase protocol_jablotron = { + .name = "Jablotron", + .manufacturer = "Jablotron", + .data_size = JABLOTRON_DECODED_DATA_SIZE, + .features = LFRFIDFeatureASK, + .validate_count = 3, + .alloc = (ProtocolAlloc)protocol_jablotron_alloc, + .free = (ProtocolFree)protocol_jablotron_free, + .get_data = (ProtocolGetData)protocol_jablotron_get_data, + .decoder = + { + .start = (ProtocolDecoderStart)protocol_jablotron_decoder_start, + .feed = (ProtocolDecoderFeed)protocol_jablotron_decoder_feed, + }, + .encoder = + { + .start = (ProtocolEncoderStart)protocol_jablotron_encoder_start, + .yield = (ProtocolEncoderYield)protocol_jablotron_encoder_yield, + }, + .render_data = (ProtocolRenderData)protocol_jablotron_render_data, + .render_brief_data = (ProtocolRenderData)protocol_jablotron_render_brief_data, + .write_data = (ProtocolWriteData)protocol_jablotron_write_data, +}; \ No newline at end of file diff --git a/lib/lfrfid/protocols/protocol_jablotron.h b/lib/lfrfid/protocols/protocol_jablotron.h new file mode 100644 index 00000000000..4de57de42a5 --- /dev/null +++ b/lib/lfrfid/protocols/protocol_jablotron.h @@ -0,0 +1,4 @@ +#pragma once +#include + +extern const ProtocolBase protocol_jablotron; \ No newline at end of file From 274f17ed5a48f0deaf016bd44908fe70beb29ffb Mon Sep 17 00:00:00 2001 From: Sebastian Mauer Date: Mon, 29 Aug 2022 16:19:56 +0100 Subject: [PATCH 027/824] Add support for Viking tags (#1668) * Add support for Viking tags Fix blocks_to_write for T5577 --- lib/lfrfid/protocols/lfrfid_protocols.c | 2 + lib/lfrfid/protocols/lfrfid_protocols.h | 1 + lib/lfrfid/protocols/protocol_viking.c | 201 ++++++++++++++++++++++++ lib/lfrfid/protocols/protocol_viking.h | 4 + 4 files changed, 208 insertions(+) create mode 100644 lib/lfrfid/protocols/protocol_viking.c create mode 100644 lib/lfrfid/protocols/protocol_viking.h diff --git a/lib/lfrfid/protocols/lfrfid_protocols.c b/lib/lfrfid/protocols/lfrfid_protocols.c index 6d42cf31e64..30ba24e6e5b 100644 --- a/lib/lfrfid/protocols/lfrfid_protocols.c +++ b/lib/lfrfid/protocols/lfrfid_protocols.c @@ -8,6 +8,7 @@ #include "protocol_fdx_b.h" #include "protocol_hid_generic.h" #include "protocol_hid_ex_generic.h" +#include "protocol_viking.h" #include "protocol_jablotron.h" #include "protocol_paradox.h" #include "protocol_pac_stanley.h" @@ -22,6 +23,7 @@ const ProtocolBase* lfrfid_protocols[] = { [LFRFIDProtocolFDXB] = &protocol_fdx_b, [LFRFIDProtocolHidGeneric] = &protocol_hid_generic, [LFRFIDProtocolHidExGeneric] = &protocol_hid_ex_generic, + [LFRFIDProtocolViking] = &protocol_viking, [LFRFIDProtocolJablotron] = &protocol_jablotron, [LFRFIDProtocolParadox] = &protocol_paradox, [LFRFIDProtocolPACStanley] = &protocol_pac_stanley, diff --git a/lib/lfrfid/protocols/lfrfid_protocols.h b/lib/lfrfid/protocols/lfrfid_protocols.h index 42113af1ecc..710b6e57ba6 100644 --- a/lib/lfrfid/protocols/lfrfid_protocols.h +++ b/lib/lfrfid/protocols/lfrfid_protocols.h @@ -17,6 +17,7 @@ typedef enum { LFRFIDProtocolFDXB, LFRFIDProtocolHidGeneric, LFRFIDProtocolHidExGeneric, + LFRFIDProtocolViking, LFRFIDProtocolJablotron, LFRFIDProtocolParadox, LFRFIDProtocolPACStanley, diff --git a/lib/lfrfid/protocols/protocol_viking.c b/lib/lfrfid/protocols/protocol_viking.c new file mode 100644 index 00000000000..7fd72bd93bb --- /dev/null +++ b/lib/lfrfid/protocols/protocol_viking.c @@ -0,0 +1,201 @@ +#include +#include +#include +#include +#include "lfrfid_protocols.h" + +#define VIKING_CLOCK_PER_BIT (32) + +#define VIKING_ENCODED_BIT_SIZE (64) +#define VIKING_ENCODED_BYTE_SIZE (((VIKING_ENCODED_BIT_SIZE) / 8)) +#define VIKING_PREAMBLE_BIT_SIZE (24) +#define VIKING_PREAMBLE_BYTE_SIZE (3) +#define VIKING_ENCODED_BYTE_FULL_SIZE (VIKING_ENCODED_BYTE_SIZE + VIKING_PREAMBLE_BYTE_SIZE) +#define VIKING_DECODED_DATA_SIZE 4 + +#define VIKING_READ_SHORT_TIME (128) +#define VIKING_READ_LONG_TIME (256) +#define VIKING_READ_JITTER_TIME (60) + +#define VIKING_READ_SHORT_TIME_LOW (VIKING_READ_SHORT_TIME - VIKING_READ_JITTER_TIME) +#define VIKING_READ_SHORT_TIME_HIGH (VIKING_READ_SHORT_TIME + VIKING_READ_JITTER_TIME) +#define VIKING_READ_LONG_TIME_LOW (VIKING_READ_LONG_TIME - VIKING_READ_JITTER_TIME) +#define VIKING_READ_LONG_TIME_HIGH (VIKING_READ_LONG_TIME + VIKING_READ_JITTER_TIME) + +typedef struct { + uint8_t data[VIKING_DECODED_DATA_SIZE]; + uint8_t encoded_data[VIKING_ENCODED_BYTE_FULL_SIZE]; + + uint8_t encoded_data_index; + bool encoded_polarity; + + ManchesterState decoder_manchester_state; +} ProtocolViking; + +ProtocolViking* protocol_viking_alloc(void) { + ProtocolViking* proto = malloc(sizeof(ProtocolViking)); + return (void*)proto; +}; + +void protocol_viking_free(ProtocolViking* protocol) { + free(protocol); +}; + +uint8_t* protocol_viking_get_data(ProtocolViking* protocol) { + return protocol->data; +}; + +static void protocol_viking_decode(ProtocolViking* protocol) { + // Copy Card ID + bit_lib_copy_bits(protocol->data, 0, 32, protocol->encoded_data, 24); +} + +static bool protocol_viking_can_be_decoded(ProtocolViking* protocol) { + // check 24 bits preamble + if(bit_lib_get_bits_16(protocol->encoded_data, 0, 16) != 0b1111001000000000) return false; + if(bit_lib_get_bits(protocol->encoded_data, 16, 8) != 0b00000000) return false; + + // check next 24 bits preamble + if(bit_lib_get_bits_16(protocol->encoded_data, 64, 16) != 0b1111001000000000) return false; + if(bit_lib_get_bits(protocol->encoded_data, 80, 8) != 0b00000000) return false; + + // Checksum + uint32_t checksum = bit_lib_get_bits(protocol->encoded_data, 0, 8) ^ + bit_lib_get_bits(protocol->encoded_data, 8, 8) ^ + bit_lib_get_bits(protocol->encoded_data, 16, 8) ^ + bit_lib_get_bits(protocol->encoded_data, 24, 8) ^ + bit_lib_get_bits(protocol->encoded_data, 32, 8) ^ + bit_lib_get_bits(protocol->encoded_data, 40, 8) ^ + bit_lib_get_bits(protocol->encoded_data, 48, 8) ^ + bit_lib_get_bits(protocol->encoded_data, 56, 8) ^ 0xA8; + if(checksum != 0) return false; + + return true; +} + +void protocol_viking_decoder_start(ProtocolViking* protocol) { + memset(protocol->encoded_data, 0, VIKING_ENCODED_BYTE_FULL_SIZE); + manchester_advance( + protocol->decoder_manchester_state, + ManchesterEventReset, + &protocol->decoder_manchester_state, + NULL); +}; + +bool protocol_viking_decoder_feed(ProtocolViking* protocol, bool level, uint32_t duration) { + bool result = false; + + ManchesterEvent event = ManchesterEventReset; + + if(duration > VIKING_READ_SHORT_TIME_LOW && duration < VIKING_READ_SHORT_TIME_HIGH) { + if(!level) { + event = ManchesterEventShortHigh; + } else { + event = ManchesterEventShortLow; + } + } else if(duration > VIKING_READ_LONG_TIME_LOW && duration < VIKING_READ_LONG_TIME_HIGH) { + if(!level) { + event = ManchesterEventLongHigh; + } else { + event = ManchesterEventLongLow; + } + } + + if(event != ManchesterEventReset) { + bool data; + bool data_ok = manchester_advance( + protocol->decoder_manchester_state, event, &protocol->decoder_manchester_state, &data); + + if(data_ok) { + bit_lib_push_bit(protocol->encoded_data, VIKING_ENCODED_BYTE_FULL_SIZE, data); + + if(protocol_viking_can_be_decoded(protocol)) { + protocol_viking_decode(protocol); + result = true; + } + } + } + + return result; +}; + +bool protocol_viking_encoder_start(ProtocolViking* protocol) { + // Preamble + bit_lib_set_bits(protocol->encoded_data, 0, 0b11110010, 8); + bit_lib_set_bits(protocol->encoded_data, 8, 0b00000000, 8); + bit_lib_set_bits(protocol->encoded_data, 16, 0b00000000, 8); + + // Card Id + bit_lib_copy_bits(protocol->encoded_data, 24, 32, protocol->data, 0); + + // Checksum + uint32_t id = bit_lib_get_bits_32(protocol->data, 0, 32); + uint8_t checksum = ((id >> 24) & 0xFF) ^ ((id >> 16) & 0xFF) ^ ((id >> 8) & 0xFF) ^ + (id & 0xFF) ^ 0xF2 ^ 0xA8; + bit_lib_set_bits(protocol->encoded_data, 56, checksum, 8); + + return true; +}; + +LevelDuration protocol_viking_encoder_yield(ProtocolViking* protocol) { + bool level = bit_lib_get_bit(protocol->encoded_data, protocol->encoded_data_index); + uint32_t duration = VIKING_CLOCK_PER_BIT / 2; + + if(protocol->encoded_polarity) { + protocol->encoded_polarity = false; + } else { + level = !level; + + protocol->encoded_polarity = true; + bit_lib_increment_index(protocol->encoded_data_index, VIKING_ENCODED_BIT_SIZE); + } + + return level_duration_make(level, duration); +}; + +bool protocol_viking_write_data(ProtocolViking* protocol, void* data) { + LFRFIDWriteRequest* request = (LFRFIDWriteRequest*)data; + bool result = false; + + protocol_viking_encoder_start(protocol); + + if(request->write_type == LFRFIDWriteTypeT5577) { + request->t5577.block[0] = + (LFRFID_T5577_MODULATION_MANCHESTER | LFRFID_T5577_BITRATE_RF_32 | + (2 << LFRFID_T5577_MAXBLOCK_SHIFT)); + request->t5577.block[1] = bit_lib_get_bits_32(protocol->encoded_data, 0, 32); + request->t5577.block[2] = bit_lib_get_bits_32(protocol->encoded_data, 32, 32); + request->t5577.blocks_to_write = 3; + result = true; + } + return result; +}; + +void protocol_viking_render_data(ProtocolViking* protocol, string_t result) { + uint32_t id = bit_lib_get_bits_32(protocol->data, 0, 32); + string_printf(result, "ID: %08lx\r\n", id); +}; + +const ProtocolBase protocol_viking = { + .name = "Viking", + .manufacturer = "Viking", + .data_size = VIKING_DECODED_DATA_SIZE, + .features = LFRFIDFeatureASK, + .validate_count = 3, + .alloc = (ProtocolAlloc)protocol_viking_alloc, + .free = (ProtocolFree)protocol_viking_free, + .get_data = (ProtocolGetData)protocol_viking_get_data, + .decoder = + { + .start = (ProtocolDecoderStart)protocol_viking_decoder_start, + .feed = (ProtocolDecoderFeed)protocol_viking_decoder_feed, + }, + .encoder = + { + .start = (ProtocolEncoderStart)protocol_viking_encoder_start, + .yield = (ProtocolEncoderYield)protocol_viking_encoder_yield, + }, + .render_data = (ProtocolRenderData)protocol_viking_render_data, + .render_brief_data = (ProtocolRenderData)protocol_viking_render_data, + .write_data = (ProtocolWriteData)protocol_viking_write_data, +}; \ No newline at end of file diff --git a/lib/lfrfid/protocols/protocol_viking.h b/lib/lfrfid/protocols/protocol_viking.h new file mode 100644 index 00000000000..3286e03a7da --- /dev/null +++ b/lib/lfrfid/protocols/protocol_viking.h @@ -0,0 +1,4 @@ +#pragma once +#include + +extern const ProtocolBase protocol_viking; From 611b7e15ed46ac08489edd6c477c5285dae7d1c7 Mon Sep 17 00:00:00 2001 From: Walter Doekes Date: Mon, 29 Aug 2022 18:20:57 +0200 Subject: [PATCH 028/824] Remove execute permissions from *.c and *.h files (#1651) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add permission fix (no execute bit for source files) to fbt lint|format * Remove execute bit from 59 source files using fbt format * Also list which permissions are unwanted in lint.py * Also remove exec permissions from lib/../rfal_nfc.c Co-authored-by: あく --- applications/bad_usb/views/bad_usb_view.h | 0 .../bt/bt_debug_app/views/bt_carrier_test.c | 0 applications/bt/bt_debug_app/views/bt_test.c | 0 applications/bt/bt_debug_app/views/bt_test.h | 0 .../bt/bt_hid_app/views/bt_hid_keynote.c | 0 .../bt/bt_hid_app/views/bt_hid_media.c | 0 .../bt/bt_settings_app/bt_settings_app.c | 0 .../bt/bt_settings_app/bt_settings_app.h | 0 .../scenes/bt_settings_scene_config.h | 0 .../bt_settings_scene_forget_dev_confirm.c | 0 .../bt_settings_scene_forget_dev_success.c | 0 .../scenes/bt_settings_scene_start.c | 0 applications/cli/cli_i.h | 0 applications/gpio/views/gpio_test.c | 0 applications/gpio/views/gpio_test.h | 0 applications/gpio/views/gpio_usb_uart.h | 0 applications/gui/canvas.h | 0 applications/gui/modules/dialog_ex.c | 0 applications/gui/modules/menu.h | 0 applications/gui/modules/text_box.c | 0 applications/gui/modules/text_box.h | 0 applications/gui/modules/variable_item_list.h | 0 applications/gui/modules/widget.h | 0 .../widget_elements/widget_element_frame.c | 0 .../widget_elements/widget_element_i.h | 0 .../widget_elements/widget_element_string.c | 0 applications/gui/scene_manager.c | 0 applications/gui/scene_manager.h | 0 applications/gui/scene_manager_i.h | 0 applications/gui/view_dispatcher.h | 0 applications/nfc/helpers/nfc_emv_parser.c | 0 applications/nfc/helpers/nfc_emv_parser.h | 0 applications/nfc/nfc_cli.c | 0 applications/nfc/scenes/nfc_scene.c | 0 applications/nfc/scenes/nfc_scene_config.h | 0 applications/nfc/scenes/nfc_scene_delete.c | 0 .../nfc/scenes/nfc_scene_delete_success.c | 0 .../nfc/scenes/nfc_scene_emulate_uid.c | 0 .../nfc/scenes/nfc_scene_file_select.c | 0 .../scenes/nfc_scene_mf_ultralight_emulate.c | 0 .../nfc/scenes/nfc_scene_read_card_success.c | 0 applications/nfc/scenes/nfc_scene_set_atqa.c | 0 applications/nfc/scenes/nfc_scene_set_sak.c | 0 applications/nfc/scenes/nfc_scene_set_uid.c | 0 applications/picopass/picopass_worker.h | 0 applications/picopass/scenes/picopass_scene.c | 0 .../picopass/scenes/picopass_scene_config.h | 0 .../scenes/picopass_scene_delete_success.c | 0 .../power/battery_test_app/battery_test_app.c | 0 applications/power/power_service/power_i.h | 0 .../power_settings_scene_battery_info.c | 0 .../scenes/power_settings_scene_config.h | 0 .../scenes/power_settings_scene_reboot.c | 0 .../scenes/power_settings_scene_start.c | 0 .../storage_settings_scene_formatting.c | 0 applications/system/system_settings.h | 0 lib/ST25RFAL002/source/rfal_nfc.c | 0 lib/nfc/nfc_worker.h | 0 lib/nfc/protocols/emv.h | 0 lib/nfc/protocols/nfca.c | 0 scripts/lint.py | 59 +++++++++++++------ 61 files changed, 41 insertions(+), 18 deletions(-) mode change 100755 => 100644 applications/bad_usb/views/bad_usb_view.h mode change 100755 => 100644 applications/bt/bt_debug_app/views/bt_carrier_test.c mode change 100755 => 100644 applications/bt/bt_debug_app/views/bt_test.c mode change 100755 => 100644 applications/bt/bt_debug_app/views/bt_test.h mode change 100755 => 100644 applications/bt/bt_hid_app/views/bt_hid_keynote.c mode change 100755 => 100644 applications/bt/bt_hid_app/views/bt_hid_media.c mode change 100755 => 100644 applications/bt/bt_settings_app/bt_settings_app.c mode change 100755 => 100644 applications/bt/bt_settings_app/bt_settings_app.h mode change 100755 => 100644 applications/bt/bt_settings_app/scenes/bt_settings_scene_config.h mode change 100755 => 100644 applications/bt/bt_settings_app/scenes/bt_settings_scene_forget_dev_confirm.c mode change 100755 => 100644 applications/bt/bt_settings_app/scenes/bt_settings_scene_forget_dev_success.c mode change 100755 => 100644 applications/bt/bt_settings_app/scenes/bt_settings_scene_start.c mode change 100755 => 100644 applications/cli/cli_i.h mode change 100755 => 100644 applications/gpio/views/gpio_test.c mode change 100755 => 100644 applications/gpio/views/gpio_test.h mode change 100755 => 100644 applications/gpio/views/gpio_usb_uart.h mode change 100755 => 100644 applications/gui/canvas.h mode change 100755 => 100644 applications/gui/modules/dialog_ex.c mode change 100755 => 100644 applications/gui/modules/menu.h mode change 100755 => 100644 applications/gui/modules/text_box.c mode change 100755 => 100644 applications/gui/modules/text_box.h mode change 100755 => 100644 applications/gui/modules/variable_item_list.h mode change 100755 => 100644 applications/gui/modules/widget.h mode change 100755 => 100644 applications/gui/modules/widget_elements/widget_element_frame.c mode change 100755 => 100644 applications/gui/modules/widget_elements/widget_element_i.h mode change 100755 => 100644 applications/gui/modules/widget_elements/widget_element_string.c mode change 100755 => 100644 applications/gui/scene_manager.c mode change 100755 => 100644 applications/gui/scene_manager.h mode change 100755 => 100644 applications/gui/scene_manager_i.h mode change 100755 => 100644 applications/gui/view_dispatcher.h mode change 100755 => 100644 applications/nfc/helpers/nfc_emv_parser.c mode change 100755 => 100644 applications/nfc/helpers/nfc_emv_parser.h mode change 100755 => 100644 applications/nfc/nfc_cli.c mode change 100755 => 100644 applications/nfc/scenes/nfc_scene.c mode change 100755 => 100644 applications/nfc/scenes/nfc_scene_config.h mode change 100755 => 100644 applications/nfc/scenes/nfc_scene_delete.c mode change 100755 => 100644 applications/nfc/scenes/nfc_scene_delete_success.c mode change 100755 => 100644 applications/nfc/scenes/nfc_scene_emulate_uid.c mode change 100755 => 100644 applications/nfc/scenes/nfc_scene_file_select.c mode change 100755 => 100644 applications/nfc/scenes/nfc_scene_mf_ultralight_emulate.c mode change 100755 => 100644 applications/nfc/scenes/nfc_scene_read_card_success.c mode change 100755 => 100644 applications/nfc/scenes/nfc_scene_set_atqa.c mode change 100755 => 100644 applications/nfc/scenes/nfc_scene_set_sak.c mode change 100755 => 100644 applications/nfc/scenes/nfc_scene_set_uid.c mode change 100755 => 100644 applications/picopass/picopass_worker.h mode change 100755 => 100644 applications/picopass/scenes/picopass_scene.c mode change 100755 => 100644 applications/picopass/scenes/picopass_scene_config.h mode change 100755 => 100644 applications/picopass/scenes/picopass_scene_delete_success.c mode change 100755 => 100644 applications/power/battery_test_app/battery_test_app.c mode change 100755 => 100644 applications/power/power_service/power_i.h mode change 100755 => 100644 applications/power/power_settings_app/scenes/power_settings_scene_battery_info.c mode change 100755 => 100644 applications/power/power_settings_app/scenes/power_settings_scene_config.h mode change 100755 => 100644 applications/power/power_settings_app/scenes/power_settings_scene_reboot.c mode change 100755 => 100644 applications/power/power_settings_app/scenes/power_settings_scene_start.c mode change 100755 => 100644 applications/storage_settings/scenes/storage_settings_scene_formatting.c mode change 100755 => 100644 applications/system/system_settings.h mode change 100755 => 100644 lib/ST25RFAL002/source/rfal_nfc.c mode change 100755 => 100644 lib/nfc/nfc_worker.h mode change 100755 => 100644 lib/nfc/protocols/emv.h mode change 100755 => 100644 lib/nfc/protocols/nfca.c diff --git a/applications/bad_usb/views/bad_usb_view.h b/applications/bad_usb/views/bad_usb_view.h old mode 100755 new mode 100644 diff --git a/applications/bt/bt_debug_app/views/bt_carrier_test.c b/applications/bt/bt_debug_app/views/bt_carrier_test.c old mode 100755 new mode 100644 diff --git a/applications/bt/bt_debug_app/views/bt_test.c b/applications/bt/bt_debug_app/views/bt_test.c old mode 100755 new mode 100644 diff --git a/applications/bt/bt_debug_app/views/bt_test.h b/applications/bt/bt_debug_app/views/bt_test.h old mode 100755 new mode 100644 diff --git a/applications/bt/bt_hid_app/views/bt_hid_keynote.c b/applications/bt/bt_hid_app/views/bt_hid_keynote.c old mode 100755 new mode 100644 diff --git a/applications/bt/bt_hid_app/views/bt_hid_media.c b/applications/bt/bt_hid_app/views/bt_hid_media.c old mode 100755 new mode 100644 diff --git a/applications/bt/bt_settings_app/bt_settings_app.c b/applications/bt/bt_settings_app/bt_settings_app.c old mode 100755 new mode 100644 diff --git a/applications/bt/bt_settings_app/bt_settings_app.h b/applications/bt/bt_settings_app/bt_settings_app.h old mode 100755 new mode 100644 diff --git a/applications/bt/bt_settings_app/scenes/bt_settings_scene_config.h b/applications/bt/bt_settings_app/scenes/bt_settings_scene_config.h old mode 100755 new mode 100644 diff --git a/applications/bt/bt_settings_app/scenes/bt_settings_scene_forget_dev_confirm.c b/applications/bt/bt_settings_app/scenes/bt_settings_scene_forget_dev_confirm.c old mode 100755 new mode 100644 diff --git a/applications/bt/bt_settings_app/scenes/bt_settings_scene_forget_dev_success.c b/applications/bt/bt_settings_app/scenes/bt_settings_scene_forget_dev_success.c old mode 100755 new mode 100644 diff --git a/applications/bt/bt_settings_app/scenes/bt_settings_scene_start.c b/applications/bt/bt_settings_app/scenes/bt_settings_scene_start.c old mode 100755 new mode 100644 diff --git a/applications/cli/cli_i.h b/applications/cli/cli_i.h old mode 100755 new mode 100644 diff --git a/applications/gpio/views/gpio_test.c b/applications/gpio/views/gpio_test.c old mode 100755 new mode 100644 diff --git a/applications/gpio/views/gpio_test.h b/applications/gpio/views/gpio_test.h old mode 100755 new mode 100644 diff --git a/applications/gpio/views/gpio_usb_uart.h b/applications/gpio/views/gpio_usb_uart.h old mode 100755 new mode 100644 diff --git a/applications/gui/canvas.h b/applications/gui/canvas.h old mode 100755 new mode 100644 diff --git a/applications/gui/modules/dialog_ex.c b/applications/gui/modules/dialog_ex.c old mode 100755 new mode 100644 diff --git a/applications/gui/modules/menu.h b/applications/gui/modules/menu.h old mode 100755 new mode 100644 diff --git a/applications/gui/modules/text_box.c b/applications/gui/modules/text_box.c old mode 100755 new mode 100644 diff --git a/applications/gui/modules/text_box.h b/applications/gui/modules/text_box.h old mode 100755 new mode 100644 diff --git a/applications/gui/modules/variable_item_list.h b/applications/gui/modules/variable_item_list.h old mode 100755 new mode 100644 diff --git a/applications/gui/modules/widget.h b/applications/gui/modules/widget.h old mode 100755 new mode 100644 diff --git a/applications/gui/modules/widget_elements/widget_element_frame.c b/applications/gui/modules/widget_elements/widget_element_frame.c old mode 100755 new mode 100644 diff --git a/applications/gui/modules/widget_elements/widget_element_i.h b/applications/gui/modules/widget_elements/widget_element_i.h old mode 100755 new mode 100644 diff --git a/applications/gui/modules/widget_elements/widget_element_string.c b/applications/gui/modules/widget_elements/widget_element_string.c old mode 100755 new mode 100644 diff --git a/applications/gui/scene_manager.c b/applications/gui/scene_manager.c old mode 100755 new mode 100644 diff --git a/applications/gui/scene_manager.h b/applications/gui/scene_manager.h old mode 100755 new mode 100644 diff --git a/applications/gui/scene_manager_i.h b/applications/gui/scene_manager_i.h old mode 100755 new mode 100644 diff --git a/applications/gui/view_dispatcher.h b/applications/gui/view_dispatcher.h old mode 100755 new mode 100644 diff --git a/applications/nfc/helpers/nfc_emv_parser.c b/applications/nfc/helpers/nfc_emv_parser.c old mode 100755 new mode 100644 diff --git a/applications/nfc/helpers/nfc_emv_parser.h b/applications/nfc/helpers/nfc_emv_parser.h old mode 100755 new mode 100644 diff --git a/applications/nfc/nfc_cli.c b/applications/nfc/nfc_cli.c old mode 100755 new mode 100644 diff --git a/applications/nfc/scenes/nfc_scene.c b/applications/nfc/scenes/nfc_scene.c old mode 100755 new mode 100644 diff --git a/applications/nfc/scenes/nfc_scene_config.h b/applications/nfc/scenes/nfc_scene_config.h old mode 100755 new mode 100644 diff --git a/applications/nfc/scenes/nfc_scene_delete.c b/applications/nfc/scenes/nfc_scene_delete.c old mode 100755 new mode 100644 diff --git a/applications/nfc/scenes/nfc_scene_delete_success.c b/applications/nfc/scenes/nfc_scene_delete_success.c old mode 100755 new mode 100644 diff --git a/applications/nfc/scenes/nfc_scene_emulate_uid.c b/applications/nfc/scenes/nfc_scene_emulate_uid.c old mode 100755 new mode 100644 diff --git a/applications/nfc/scenes/nfc_scene_file_select.c b/applications/nfc/scenes/nfc_scene_file_select.c old mode 100755 new mode 100644 diff --git a/applications/nfc/scenes/nfc_scene_mf_ultralight_emulate.c b/applications/nfc/scenes/nfc_scene_mf_ultralight_emulate.c old mode 100755 new mode 100644 diff --git a/applications/nfc/scenes/nfc_scene_read_card_success.c b/applications/nfc/scenes/nfc_scene_read_card_success.c old mode 100755 new mode 100644 diff --git a/applications/nfc/scenes/nfc_scene_set_atqa.c b/applications/nfc/scenes/nfc_scene_set_atqa.c old mode 100755 new mode 100644 diff --git a/applications/nfc/scenes/nfc_scene_set_sak.c b/applications/nfc/scenes/nfc_scene_set_sak.c old mode 100755 new mode 100644 diff --git a/applications/nfc/scenes/nfc_scene_set_uid.c b/applications/nfc/scenes/nfc_scene_set_uid.c old mode 100755 new mode 100644 diff --git a/applications/picopass/picopass_worker.h b/applications/picopass/picopass_worker.h old mode 100755 new mode 100644 diff --git a/applications/picopass/scenes/picopass_scene.c b/applications/picopass/scenes/picopass_scene.c old mode 100755 new mode 100644 diff --git a/applications/picopass/scenes/picopass_scene_config.h b/applications/picopass/scenes/picopass_scene_config.h old mode 100755 new mode 100644 diff --git a/applications/picopass/scenes/picopass_scene_delete_success.c b/applications/picopass/scenes/picopass_scene_delete_success.c old mode 100755 new mode 100644 diff --git a/applications/power/battery_test_app/battery_test_app.c b/applications/power/battery_test_app/battery_test_app.c old mode 100755 new mode 100644 diff --git a/applications/power/power_service/power_i.h b/applications/power/power_service/power_i.h old mode 100755 new mode 100644 diff --git a/applications/power/power_settings_app/scenes/power_settings_scene_battery_info.c b/applications/power/power_settings_app/scenes/power_settings_scene_battery_info.c old mode 100755 new mode 100644 diff --git a/applications/power/power_settings_app/scenes/power_settings_scene_config.h b/applications/power/power_settings_app/scenes/power_settings_scene_config.h old mode 100755 new mode 100644 diff --git a/applications/power/power_settings_app/scenes/power_settings_scene_reboot.c b/applications/power/power_settings_app/scenes/power_settings_scene_reboot.c old mode 100755 new mode 100644 diff --git a/applications/power/power_settings_app/scenes/power_settings_scene_start.c b/applications/power/power_settings_app/scenes/power_settings_scene_start.c old mode 100755 new mode 100644 diff --git a/applications/storage_settings/scenes/storage_settings_scene_formatting.c b/applications/storage_settings/scenes/storage_settings_scene_formatting.c old mode 100755 new mode 100644 diff --git a/applications/system/system_settings.h b/applications/system/system_settings.h old mode 100755 new mode 100644 diff --git a/lib/ST25RFAL002/source/rfal_nfc.c b/lib/ST25RFAL002/source/rfal_nfc.c old mode 100755 new mode 100644 diff --git a/lib/nfc/nfc_worker.h b/lib/nfc/nfc_worker.h old mode 100755 new mode 100644 diff --git a/lib/nfc/protocols/emv.h b/lib/nfc/protocols/emv.h old mode 100755 new mode 100644 diff --git a/lib/nfc/protocols/nfca.c b/lib/nfc/protocols/nfca.c old mode 100755 new mode 100644 diff --git a/scripts/lint.py b/scripts/lint.py index b3c3e7da406..30a5699a762 100755 --- a/scripts/lint.py +++ b/scripts/lint.py @@ -88,7 +88,7 @@ def _format_sources(self, sources: list, dry_run: bool = False): def _fix_filename(self, filename: str): return filename.replace("-", "_") - def _replace_occurance(self, sources: list, old: str, new: str): + def _replace_occurrence(self, sources: list, old: str, new: str): old = old.encode() new = new.encode() for source in sources: @@ -102,7 +102,7 @@ def _apply_file_naming_convention(self, sources: list, dry_run: bool = False): pattern = re.compile(SOURCE_CODE_FILE_PATTERN) good = [] bad = [] - # Check sources for invalid filesname + # Check sources for invalid filenames for source in sources: basename = os.path.basename(source) if not pattern.match(basename): @@ -113,40 +113,63 @@ def _apply_file_naming_convention(self, sources: list, dry_run: bool = False): bad.append((source, basename, new_basename)) else: good.append(source) - # Notify about errors or replace all occurances + # Notify about errors or replace all occurrences if dry_run: if len(bad) > 0: self.logger.error(f"Found {len(bad)} incorrectly named files") self.logger.info(bad) return False else: - # Replace occurances in text files + # Replace occurrences in text files for source, old, new in bad: - self._replace_occurance(sources, old, new) + self._replace_occurrence(sources, old, new) # Rename files for source, old, new in bad: shutil.move(source, source.replace(old, new)) return True - def check(self): + def _apply_file_permissions(self, sources: list, dry_run: bool = False): + execute_permissions = 0o111 + pattern = re.compile(SOURCE_CODE_FILE_PATTERN) + good = [] + bad = [] + # Check sources for unexpected execute permissions + for source in sources: + st = os.stat(source) + perms_too_many = st.st_mode & execute_permissions + if perms_too_many: + good_perms = st.st_mode & ~perms_too_many + bad.append((source, oct(perms_too_many), good_perms)) + else: + good.append(source) + # Notify or fix + if dry_run: + if len(bad) > 0: + self.logger.error(f"Found {len(bad)} incorrect permissions") + self.logger.info([record[0:2] for record in bad]) + return False + else: + for source, perms_too_many, new_perms in bad: + os.chmod(source, new_perms) + return True + + def _perform(self, dry_run: bool): result = 0 sources = self._find_sources(self.args.input) - if not self._format_sources(sources, dry_run=True): - result |= 0b01 - if not self._apply_file_naming_convention(sources, dry_run=True): - result |= 0b10 + if not self._format_sources(sources, dry_run=dry_run): + result |= 0b001 + if not self._apply_file_naming_convention(sources, dry_run=dry_run): + result |= 0b010 + if not self._apply_file_permissions(sources, dry_run=dry_run): + result |= 0b100 self._check_folders(self.args.input) return result + def check(self): + return self._perform(dry_run=True) + def format(self): - result = 0 - sources = self._find_sources(self.args.input) - if not self._format_sources(sources): - result |= 0b01 - if not self._apply_file_naming_convention(sources): - result |= 0b10 - self._check_folders(self.args.input) - return result + return self._perform(dry_run=False) if __name__ == "__main__": From d76ba206522346763c4e7cfdb37607c9faf1e969 Mon Sep 17 00:00:00 2001 From: Sebastian Mauer Date: Mon, 29 Aug 2022 17:31:28 +0100 Subject: [PATCH 029/824] Add support for Pyramid tags (#1676) * Add support for Pyramid tags * Also add additional checks for AWID decoder to avoid missdetection * lfrfid worker: reset GPIO_LOAD pin * lfrfid: protocol viking, format * lfrfid: protocol pyramid, format * lfrfid: protocol paradox, format * lfrfid: protocol jablotron, format * lfrfid: protocol em4100, format * lfrfid: increase reading time by 0.5s since protocol viking takes longer to read Co-authored-by: SG --- lib/lfrfid/lfrfid_worker_modes.c | 46 ++-- lib/lfrfid/protocols/lfrfid_protocols.c | 2 + lib/lfrfid/protocols/lfrfid_protocols.h | 1 + lib/lfrfid/protocols/protocol_awid.c | 9 +- lib/lfrfid/protocols/protocol_em4100.c | 2 +- lib/lfrfid/protocols/protocol_jablotron.c | 9 +- lib/lfrfid/protocols/protocol_paradox.c | 2 +- lib/lfrfid/protocols/protocol_pyramid.c | 277 ++++++++++++++++++++++ lib/lfrfid/protocols/protocol_pyramid.h | 4 + lib/lfrfid/protocols/protocol_viking.c | 2 +- lib/lfrfid/tools/bit_lib.c | 69 ++++++ lib/lfrfid/tools/bit_lib.h | 44 ++++ 12 files changed, 438 insertions(+), 29 deletions(-) create mode 100644 lib/lfrfid/protocols/protocol_pyramid.c create mode 100644 lib/lfrfid/protocols/protocol_pyramid.h diff --git a/lib/lfrfid/lfrfid_worker_modes.c b/lib/lfrfid/lfrfid_worker_modes.c index 33683589c52..16936cca14c 100644 --- a/lib/lfrfid/lfrfid_worker_modes.c +++ b/lib/lfrfid/lfrfid_worker_modes.c @@ -27,16 +27,16 @@ #define LFRFID_WORKER_READ_DROP_TIME_MS 50 #define LFRFID_WORKER_READ_STABILIZE_TIME_MS 450 -#define LFRFID_WORKER_READ_SWITCH_TIME_MS 1500 +#define LFRFID_WORKER_READ_SWITCH_TIME_MS 2000 -#define LFRFID_WORKER_WRITE_VERIFY_TIME_MS 1500 +#define LFRFID_WORKER_WRITE_VERIFY_TIME_MS 2000 #define LFRFID_WORKER_WRITE_DROP_TIME_MS 50 #define LFRFID_WORKER_WRITE_TOO_LONG_TIME_MS 10000 #define LFRFID_WORKER_WRITE_MAX_UNSUCCESSFUL_READS 5 #define LFRFID_WORKER_READ_BUFFER_SIZE 512 -#define LFRFID_WORKER_READ_BUFFER_COUNT 8 +#define LFRFID_WORKER_READ_BUFFER_COUNT 16 #define LFRFID_WORKER_EMULATE_BUFFER_SIZE 1024 @@ -132,6 +132,8 @@ static LFRFIDWorkerReadState lfrfid_worker_read_internal( #ifdef LFRFID_WORKER_READ_DEBUG_GPIO furi_hal_gpio_init_simple(LFRFID_WORKER_READ_DEBUG_GPIO_VALUE, GpioModeOutputPushPull); furi_hal_gpio_init_simple(LFRFID_WORKER_READ_DEBUG_GPIO_LOAD, GpioModeOutputPushPull); + furi_hal_gpio_write(LFRFID_WORKER_READ_DEBUG_GPIO_VALUE, false); + furi_hal_gpio_write(LFRFID_WORKER_READ_DEBUG_GPIO_LOAD, false); #endif LFRFIDWorkerReadContext ctx; @@ -171,10 +173,16 @@ static LFRFIDWorkerReadState lfrfid_worker_read_internal( if(buffer_stream_get_overrun_count(ctx.stream) > 0) { FURI_LOG_E(TAG, "Read overrun, recovering"); buffer_stream_reset(ctx.stream); +#ifdef LFRFID_WORKER_READ_DEBUG_GPIO + furi_hal_gpio_write(LFRFID_WORKER_READ_DEBUG_GPIO_LOAD, false); +#endif continue; } if(buffer == NULL) { +#ifdef LFRFID_WORKER_READ_DEBUG_GPIO + furi_hal_gpio_write(LFRFID_WORKER_READ_DEBUG_GPIO_LOAD, false); +#endif continue; } @@ -261,24 +269,26 @@ static LFRFIDWorkerReadState lfrfid_worker_read_internal( last_read_count = 0; } - string_t string_info; - string_init(string_info); - for(uint8_t i = 0; i < protocol_data_size; i++) { - if(i != 0) { - string_cat_printf(string_info, " "); + if(furi_log_get_level() >= FuriLogLevelDebug) { + string_t string_info; + string_init(string_info); + for(uint8_t i = 0; i < protocol_data_size; i++) { + if(i != 0) { + string_cat_printf(string_info, " "); + } + + string_cat_printf(string_info, "%02X", protocol_data[i]); } - string_cat_printf(string_info, "%02X", protocol_data[i]); + FURI_LOG_D( + TAG, + "%s, %d, [%s]", + protocol_dict_get_name(worker->protocols, protocol), + last_read_count, + string_get_cstr(string_info)); + string_clear(string_info); } - FURI_LOG_D( - TAG, - "%s, %d, [%s]", - protocol_dict_get_name(worker->protocols, protocol), - last_read_count, - string_get_cstr(string_info)); - string_clear(string_info); - protocol_dict_decoders_start(worker->protocols); } } @@ -321,6 +331,8 @@ static LFRFIDWorkerReadState lfrfid_worker_read_internal( free(last_data); #ifdef LFRFID_WORKER_READ_DEBUG_GPIO + furi_hal_gpio_write(LFRFID_WORKER_READ_DEBUG_GPIO_VALUE, false); + furi_hal_gpio_write(LFRFID_WORKER_READ_DEBUG_GPIO_LOAD, false); furi_hal_gpio_init_simple(LFRFID_WORKER_READ_DEBUG_GPIO_VALUE, GpioModeAnalog); furi_hal_gpio_init_simple(LFRFID_WORKER_READ_DEBUG_GPIO_LOAD, GpioModeAnalog); #endif diff --git a/lib/lfrfid/protocols/lfrfid_protocols.c b/lib/lfrfid/protocols/lfrfid_protocols.c index 30ba24e6e5b..fc658610626 100644 --- a/lib/lfrfid/protocols/lfrfid_protocols.c +++ b/lib/lfrfid/protocols/lfrfid_protocols.c @@ -8,6 +8,7 @@ #include "protocol_fdx_b.h" #include "protocol_hid_generic.h" #include "protocol_hid_ex_generic.h" +#include "protocol_pyramid.h" #include "protocol_viking.h" #include "protocol_jablotron.h" #include "protocol_paradox.h" @@ -23,6 +24,7 @@ const ProtocolBase* lfrfid_protocols[] = { [LFRFIDProtocolFDXB] = &protocol_fdx_b, [LFRFIDProtocolHidGeneric] = &protocol_hid_generic, [LFRFIDProtocolHidExGeneric] = &protocol_hid_ex_generic, + [LFRFIDProtocolPyramid] = &protocol_pyramid, [LFRFIDProtocolViking] = &protocol_viking, [LFRFIDProtocolJablotron] = &protocol_jablotron, [LFRFIDProtocolParadox] = &protocol_paradox, diff --git a/lib/lfrfid/protocols/lfrfid_protocols.h b/lib/lfrfid/protocols/lfrfid_protocols.h index 710b6e57ba6..210ddd15a61 100644 --- a/lib/lfrfid/protocols/lfrfid_protocols.h +++ b/lib/lfrfid/protocols/lfrfid_protocols.h @@ -17,6 +17,7 @@ typedef enum { LFRFIDProtocolFDXB, LFRFIDProtocolHidGeneric, LFRFIDProtocolHidExGeneric, + LFRFIDProtocolPyramid, LFRFIDProtocolViking, LFRFIDProtocolJablotron, LFRFIDProtocolParadox, diff --git a/lib/lfrfid/protocols/protocol_awid.c b/lib/lfrfid/protocols/protocol_awid.c index 243b5edeb4d..97c07d7b21d 100644 --- a/lib/lfrfid/protocols/protocol_awid.c +++ b/lib/lfrfid/protocols/protocol_awid.c @@ -53,7 +53,7 @@ void protocol_awid_decoder_start(ProtocolAwid* protocol) { memset(protocol->encoded_data, 0, AWID_ENCODED_DATA_SIZE); }; -static bool protocol_awid_can_be_decoded(const uint8_t* data) { +static bool protocol_awid_can_be_decoded(uint8_t* data) { bool result = false; // Index map @@ -77,6 +77,12 @@ static bool protocol_awid_can_be_decoded(const uint8_t* data) { bool parity_error = bit_lib_test_parity(data, 8, 88, BitLibParityOdd, 4); if(parity_error) break; + bit_lib_remove_bit_every_nth(data, 8, 88, 4); + + // Avoid detection for invalid formats + uint8_t len = bit_lib_get_bits(data, 8, 8); + if(len != 26 && len != 50 && len != 37 && len != 34) break; + result = true; } while(false); @@ -84,7 +90,6 @@ static bool protocol_awid_can_be_decoded(const uint8_t* data) { } static void protocol_awid_decode(uint8_t* encoded_data, uint8_t* decoded_data) { - bit_lib_remove_bit_every_nth(encoded_data, 8, 88, 4); bit_lib_copy_bits(decoded_data, 0, 66, encoded_data, 8); } diff --git a/lib/lfrfid/protocols/protocol_em4100.c b/lib/lfrfid/protocols/protocol_em4100.c index 92721fcdc1e..17f57421a0e 100644 --- a/lib/lfrfid/protocols/protocol_em4100.c +++ b/lib/lfrfid/protocols/protocol_em4100.c @@ -264,7 +264,7 @@ bool protocol_em4100_write_data(ProtocolEM4100* protocol, void* data) { void protocol_em4100_render_data(ProtocolEM4100* protocol, string_t result) { uint8_t* data = protocol->data; - string_printf(result, "ID: %03u,%05u", data[2], (uint16_t)((data[3] << 8) | (data[4]))); + string_printf(result, "FC: %03u, Card: %05u", data[2], (uint16_t)((data[3] << 8) | (data[4]))); }; const ProtocolBase protocol_em4100 = { diff --git a/lib/lfrfid/protocols/protocol_jablotron.c b/lib/lfrfid/protocols/protocol_jablotron.c index 64b55166c54..e00b1e59c96 100644 --- a/lib/lfrfid/protocols/protocol_jablotron.c +++ b/lib/lfrfid/protocols/protocol_jablotron.c @@ -162,12 +162,7 @@ LevelDuration protocol_jablotron_encoder_yield(ProtocolJablotron* protocol) { void protocol_jablotron_render_data(ProtocolJablotron* protocol, string_t result) { uint64_t id = protocol_jablotron_card_id(protocol->data); - string_printf(result, "ID: %llx\r\n", id); -}; - -void protocol_jablotron_render_brief_data(ProtocolJablotron* protocol, string_t result) { - uint64_t id = protocol_jablotron_card_id(protocol->data); - string_printf(result, "ID: %llx\r\n", id); + string_printf(result, "ID: %llX\r\n", id); }; bool protocol_jablotron_write_data(ProtocolJablotron* protocol, void* data) { @@ -207,6 +202,6 @@ const ProtocolBase protocol_jablotron = { .yield = (ProtocolEncoderYield)protocol_jablotron_encoder_yield, }, .render_data = (ProtocolRenderData)protocol_jablotron_render_data, - .render_brief_data = (ProtocolRenderData)protocol_jablotron_render_brief_data, + .render_brief_data = (ProtocolRenderData)protocol_jablotron_render_data, .write_data = (ProtocolWriteData)protocol_jablotron_write_data, }; \ No newline at end of file diff --git a/lib/lfrfid/protocols/protocol_paradox.c b/lib/lfrfid/protocols/protocol_paradox.c index 66399a6057a..be627600bcf 100644 --- a/lib/lfrfid/protocols/protocol_paradox.c +++ b/lib/lfrfid/protocols/protocol_paradox.c @@ -155,7 +155,7 @@ void protocol_paradox_render_brief_data(ProtocolParadox* protocol, string_t resu uint8_t fc = bit_lib_get_bits(decoded_data, 10, 8); uint16_t card_id = bit_lib_get_bits_16(decoded_data, 18, 16); - string_cat_printf(result, "ID: %03u,%05u", fc, card_id); + string_cat_printf(result, "FC: %03u, Card: %05u", fc, card_id); }; bool protocol_paradox_write_data(ProtocolParadox* protocol, void* data) { diff --git a/lib/lfrfid/protocols/protocol_pyramid.c b/lib/lfrfid/protocols/protocol_pyramid.c new file mode 100644 index 00000000000..a0404b48568 --- /dev/null +++ b/lib/lfrfid/protocols/protocol_pyramid.c @@ -0,0 +1,277 @@ +#include +#include +#include +#include +#include "lfrfid_protocols.h" +#include + +#define JITTER_TIME (20) +#define MIN_TIME (64 - JITTER_TIME) +#define MAX_TIME (80 + JITTER_TIME) + +#define PYRAMID_DATA_SIZE 13 +#define PYRAMID_PREAMBLE_SIZE 3 + +#define PYRAMID_ENCODED_DATA_SIZE \ + (PYRAMID_PREAMBLE_SIZE + PYRAMID_DATA_SIZE + PYRAMID_PREAMBLE_SIZE) +#define PYRAMID_ENCODED_BIT_SIZE ((PYRAMID_PREAMBLE_SIZE + PYRAMID_DATA_SIZE) * 8) +#define PYRAMID_DECODED_DATA_SIZE (4) +#define PYRAMID_DECODED_BIT_SIZE ((PYRAMID_ENCODED_BIT_SIZE - PYRAMID_PREAMBLE_SIZE * 8) / 2) + +typedef struct { + FSKDemod* fsk_demod; +} ProtocolPyramidDecoder; + +typedef struct { + FSKOsc* fsk_osc; + uint8_t encoded_index; + uint32_t pulse; +} ProtocolPyramidEncoder; + +typedef struct { + ProtocolPyramidDecoder decoder; + ProtocolPyramidEncoder encoder; + uint8_t encoded_data[PYRAMID_ENCODED_DATA_SIZE]; + uint8_t data[PYRAMID_DECODED_DATA_SIZE]; +} ProtocolPyramid; + +ProtocolPyramid* protocol_pyramid_alloc(void) { + ProtocolPyramid* protocol = malloc(sizeof(ProtocolPyramid)); + protocol->decoder.fsk_demod = fsk_demod_alloc(MIN_TIME, 6, MAX_TIME, 5); + protocol->encoder.fsk_osc = fsk_osc_alloc(8, 10, 50); + + return protocol; +}; + +void protocol_pyramid_free(ProtocolPyramid* protocol) { + fsk_demod_free(protocol->decoder.fsk_demod); + fsk_osc_free(protocol->encoder.fsk_osc); + free(protocol); +}; + +uint8_t* protocol_pyramid_get_data(ProtocolPyramid* protocol) { + return protocol->data; +}; + +void protocol_pyramid_decoder_start(ProtocolPyramid* protocol) { + memset(protocol->encoded_data, 0, PYRAMID_ENCODED_DATA_SIZE); +}; + +static bool protocol_pyramid_can_be_decoded(uint8_t* data) { + // check preamble + if(bit_lib_get_bits_16(data, 0, 16) != 0b0000000000000001 || + bit_lib_get_bits(data, 16, 8) != 0b00000001) { + return false; + } + + if(bit_lib_get_bits_16(data, 128, 16) != 0b0000000000000001 || + bit_lib_get_bits(data, 136, 8) != 0b00000001) { + return false; + } + + uint8_t checksum = bit_lib_get_bits(data, 120, 8); + uint8_t checksum_data[13] = {0x00}; + for(uint8_t i = 0; i < 13; i++) { + checksum_data[i] = bit_lib_get_bits(data, 16 + (i * 8), 8); + } + + uint8_t calc_checksum = bit_lib_crc8(checksum_data, 13, 0x31, 0x00, true, true, 0x00); + if(checksum != calc_checksum) return false; + + // Remove parity + bit_lib_remove_bit_every_nth(data, 8, 15 * 8, 8); + + // Determine Startbit and format + int j; + for(j = 0; j < 105; ++j) { + if(bit_lib_get_bit(data, j)) break; + } + uint8_t fmt_len = 105 - j; + + // Only suppport 26bit format for now + if(fmt_len != 26) return false; + + return true; +} + +static void protocol_pyramid_decode(ProtocolPyramid* protocol) { + // Format + bit_lib_set_bits(protocol->data, 0, 26, 8); + + // Facility Code + bit_lib_copy_bits(protocol->data, 8, 8, protocol->encoded_data, 73 + 8); + + // Card Number + bit_lib_copy_bits(protocol->data, 16, 16, protocol->encoded_data, 81 + 8); +} + +bool protocol_pyramid_decoder_feed(ProtocolPyramid* protocol, bool level, uint32_t duration) { + bool value; + uint32_t count; + bool result = false; + + fsk_demod_feed(protocol->decoder.fsk_demod, level, duration, &value, &count); + if(count > 0) { + for(size_t i = 0; i < count; i++) { + bit_lib_push_bit(protocol->encoded_data, PYRAMID_ENCODED_DATA_SIZE, value); + if(protocol_pyramid_can_be_decoded(protocol->encoded_data)) { + protocol_pyramid_decode(protocol); + result = true; + } + } + } + + return result; +}; + +bool protocol_pyramid_get_parity(const uint8_t* bits, uint8_t type, int length) { + int x; + for(x = 0; length > 0; --length) x += bit_lib_get_bit(bits, length - 1); + x %= 2; + return x ^ type; +} + +void protocol_pyramid_add_wiegand_parity( + uint8_t* target, + uint8_t target_position, + uint8_t* source, + uint8_t length) { + bit_lib_set_bit( + target, target_position, protocol_pyramid_get_parity(source, 0 /* even */, length / 2)); + bit_lib_copy_bits(target, target_position + 1, length, source, 0); + bit_lib_set_bit( + target, + target_position + length + 1, + protocol_pyramid_get_parity(source + length / 2, 1 /* odd */, length / 2)); +} + +static void protocol_pyramid_encode(ProtocolPyramid* protocol) { + memset(protocol->encoded_data, 0, sizeof(protocol->encoded_data)); + + uint8_t pre[16]; + memset(pre, 0, sizeof(pre)); + + // Format start bit + bit_lib_set_bit(pre, 79, 1); + + uint8_t wiegand[3]; + memset(wiegand, 0, sizeof(wiegand)); + + // FC + bit_lib_copy_bits(wiegand, 0, 8, protocol->data, 8); + + // CardNum + bit_lib_copy_bits(wiegand, 8, 16, protocol->data, 16); + + // Wiegand parity + protocol_pyramid_add_wiegand_parity(pre, 80, wiegand, 24); + + bit_lib_add_parity(pre, 8, protocol->encoded_data, 8, 102, 8, 1); + + // Add checksum + uint8_t checksum_buffer[13]; + for(uint8_t i = 0; i < 13; i++) + checksum_buffer[i] = bit_lib_get_bits(protocol->encoded_data, 16 + (i * 8), 8); + + uint8_t crc = bit_lib_crc8(checksum_buffer, 13, 0x31, 0x00, true, true, 0x00); + bit_lib_set_bits(protocol->encoded_data, 120, crc, 8); +} + +bool protocol_pyramid_encoder_start(ProtocolPyramid* protocol) { + protocol->encoder.encoded_index = 0; + protocol->encoder.pulse = 0; + protocol_pyramid_encode(protocol); + + return true; +}; + +LevelDuration protocol_pyramid_encoder_yield(ProtocolPyramid* protocol) { + bool level = 0; + uint32_t duration = 0; + + // if pulse is zero, we need to output high, otherwise we need to output low + if(protocol->encoder.pulse == 0) { + // get bit + uint8_t bit = bit_lib_get_bit(protocol->encoded_data, protocol->encoder.encoded_index); + + // get pulse from oscillator + bool advance = fsk_osc_next(protocol->encoder.fsk_osc, bit, &duration); + + if(advance) { + bit_lib_increment_index(protocol->encoder.encoded_index, PYRAMID_ENCODED_BIT_SIZE); + } + + // duration diveded by 2 because we need to output high and low + duration = duration / 2; + protocol->encoder.pulse = duration; + level = true; + } else { + // output low half and reset pulse + duration = protocol->encoder.pulse; + protocol->encoder.pulse = 0; + level = false; + } + + return level_duration_make(level, duration); +}; + +bool protocol_pyramid_write_data(ProtocolPyramid* protocol, void* data) { + LFRFIDWriteRequest* request = (LFRFIDWriteRequest*)data; + bool result = false; + + protocol_pyramid_encoder_start(protocol); + + if(request->write_type == LFRFIDWriteTypeT5577) { + request->t5577.block[0] = LFRFID_T5577_MODULATION_FSK2a | LFRFID_T5577_BITRATE_RF_50 | + (4 << LFRFID_T5577_MAXBLOCK_SHIFT); + request->t5577.block[1] = bit_lib_get_bits_32(protocol->encoded_data, 0, 32); + request->t5577.block[2] = bit_lib_get_bits_32(protocol->encoded_data, 32, 32); + request->t5577.block[3] = bit_lib_get_bits_32(protocol->encoded_data, 64, 32); + request->t5577.block[4] = bit_lib_get_bits_32(protocol->encoded_data, 96, 32); + request->t5577.blocks_to_write = 5; + result = true; + } + return result; +}; + +void protocol_pyramid_render_data(ProtocolPyramid* protocol, string_t result) { + uint8_t* decoded_data = protocol->data; + uint8_t format_length = decoded_data[0]; + + string_cat_printf(result, "Format: 26\r\n", format_length); + if(format_length == 26) { + uint8_t facility; + bit_lib_copy_bits(&facility, 0, 8, decoded_data, 8); + + uint16_t card_id; + bit_lib_copy_bits((uint8_t*)&card_id, 8, 8, decoded_data, 16); + bit_lib_copy_bits((uint8_t*)&card_id, 0, 8, decoded_data, 24); + string_cat_printf(result, "FC: %03u, Card: %05u", facility, card_id); + } else { + string_cat_printf(result, "Data: unknown"); + } +}; + +const ProtocolBase protocol_pyramid = { + .name = "Pyramid", + .manufacturer = "Farpointe", + .data_size = PYRAMID_DECODED_DATA_SIZE, + .features = LFRFIDFeatureASK, + .validate_count = 3, + .alloc = (ProtocolAlloc)protocol_pyramid_alloc, + .free = (ProtocolFree)protocol_pyramid_free, + .get_data = (ProtocolGetData)protocol_pyramid_get_data, + .decoder = + { + .start = (ProtocolDecoderStart)protocol_pyramid_decoder_start, + .feed = (ProtocolDecoderFeed)protocol_pyramid_decoder_feed, + }, + .encoder = + { + .start = (ProtocolEncoderStart)protocol_pyramid_encoder_start, + .yield = (ProtocolEncoderYield)protocol_pyramid_encoder_yield, + }, + .render_data = (ProtocolRenderData)protocol_pyramid_render_data, + .render_brief_data = (ProtocolRenderData)protocol_pyramid_render_data, + .write_data = (ProtocolWriteData)protocol_pyramid_write_data, +}; diff --git a/lib/lfrfid/protocols/protocol_pyramid.h b/lib/lfrfid/protocols/protocol_pyramid.h new file mode 100644 index 00000000000..940ecaec203 --- /dev/null +++ b/lib/lfrfid/protocols/protocol_pyramid.h @@ -0,0 +1,4 @@ +#pragma once +#include + +extern const ProtocolBase protocol_pyramid; diff --git a/lib/lfrfid/protocols/protocol_viking.c b/lib/lfrfid/protocols/protocol_viking.c index 7fd72bd93bb..6119b734831 100644 --- a/lib/lfrfid/protocols/protocol_viking.c +++ b/lib/lfrfid/protocols/protocol_viking.c @@ -173,7 +173,7 @@ bool protocol_viking_write_data(ProtocolViking* protocol, void* data) { void protocol_viking_render_data(ProtocolViking* protocol, string_t result) { uint32_t id = bit_lib_get_bits_32(protocol->data, 0, 32); - string_printf(result, "ID: %08lx\r\n", id); + string_printf(result, "ID: %08lX\r\n", id); }; const ProtocolBase protocol_viking = { diff --git a/lib/lfrfid/tools/bit_lib.c b/lib/lfrfid/tools/bit_lib.c index 2df12707af2..c84f4b7ed2d 100644 --- a/lib/lfrfid/tools/bit_lib.c +++ b/lib/lfrfid/tools/bit_lib.c @@ -129,6 +129,45 @@ bool bit_lib_test_parity( return result; } +size_t bit_lib_add_parity( + const uint8_t* data, + size_t position, + uint8_t* dest, + size_t dest_position, + uint8_t source_length, + uint8_t parity_length, + BitLibParity parity) { + uint32_t parity_word = 0; + size_t j = 0, bit_count = 0; + for(int word = 0; word < source_length; word += parity_length - 1) { + for(int bit = 0; bit < parity_length - 1; bit++) { + parity_word = (parity_word << 1) | bit_lib_get_bit(data, position + word + bit); + bit_lib_set_bit( + dest, dest_position + j++, bit_lib_get_bit(data, position + word + bit)); + } + // if parity fails then return 0 + switch(parity) { + case BitLibParityAlways0: + bit_lib_set_bit(dest, dest_position + j++, 0); + break; // marker bit which should be a 0 + case BitLibParityAlways1: + bit_lib_set_bit(dest, dest_position + j++, 1); + break; // marker bit which should be a 1 + default: + bit_lib_set_bit( + dest, + dest_position + j++, + (bit_lib_test_parity_32(parity_word, BitLibParityOdd) ^ parity) ^ 1); + break; + } + bit_count += parity_length; + parity_word = 0; + } + // if we got here then all the parities passed + // return bit count + return bit_count; +} + size_t bit_lib_remove_bit_every_nth(uint8_t* data, size_t position, uint8_t length, uint8_t n) { size_t counter = 0; size_t result_counter = 0; @@ -269,6 +308,36 @@ uint8_t bit_lib_reverse_8_fast(uint8_t byte) { return byte; } +uint16_t bit_lib_crc8( + uint8_t const* data, + size_t data_size, + uint8_t polynom, + uint8_t init, + bool ref_in, + bool ref_out, + uint8_t xor_out) { + uint8_t crc = init; + + for(size_t i = 0; i < data_size; ++i) { + uint8_t byte = data[i]; + if(ref_in) bit_lib_reverse_bits(&byte, 0, 8); + crc ^= byte; + + for(size_t j = 8; j > 0; --j) { + if(crc & TOPBIT(8)) { + crc = (crc << 1) ^ polynom; + } else { + crc = (crc << 1); + } + } + } + + if(ref_out) bit_lib_reverse_bits(&crc, 0, 8); + crc ^= xor_out; + + return crc; +} + uint16_t bit_lib_crc16( uint8_t const* data, size_t data_size, diff --git a/lib/lfrfid/tools/bit_lib.h b/lib/lfrfid/tools/bit_lib.h index 482ae36b220..1b048db3523 100644 --- a/lib/lfrfid/tools/bit_lib.h +++ b/lib/lfrfid/tools/bit_lib.h @@ -7,6 +7,8 @@ extern "C" { #endif +#define TOPBIT(X) (1 << (X - 1)) + typedef enum { BitLibParityEven, BitLibParityOdd, @@ -114,6 +116,27 @@ bool bit_lib_test_parity( BitLibParity parity, uint8_t parity_length); +/** + * @brief Add parity to bit array + * + * @param data Source bit array + * @param position Start position + * @param dest Destination bit array + * @param dest_position Destination position + * @param source_length Source bit count + * @param parity_length Parity block length + * @param parity Parity to test against + * @return size_t + */ +size_t bit_lib_add_parity( + const uint8_t* data, + size_t position, + uint8_t* dest, + size_t dest_position, + uint8_t source_length, + uint8_t parity_length, + BitLibParity parity); + /** * @brief Remove bit every n in array and shift array left. Useful to remove parity. * @@ -202,6 +225,27 @@ uint16_t bit_lib_reverse_16_fast(uint16_t data); */ uint8_t bit_lib_reverse_8_fast(uint8_t byte); +/** + * @brief Slow, but generic CRC8 implementation + * + * @param data + * @param data_size + * @param polynom CRC polynom + * @param init init value + * @param ref_in true if the right bit is older + * @param ref_out true to reverse output + * @param xor_out xor output with this value + * @return uint8_t + */ +uint16_t bit_lib_crc8( + uint8_t const* data, + size_t data_size, + uint8_t polynom, + uint8_t init, + bool ref_in, + bool ref_out, + uint8_t xor_out); + /** * @brief Slow, but generic CRC16 implementation * From 5e2a90c6f1a56874e2319c5ed087d760a55fc549 Mon Sep 17 00:00:00 2001 From: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> Date: Tue, 30 Aug 2022 12:20:35 +0300 Subject: [PATCH 030/824] [FL-2773] Fix crash after cancelling Learn New Remote #1675 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- applications/infrared/scenes/infrared_scene_learn.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/infrared/scenes/infrared_scene_learn.c b/applications/infrared/scenes/infrared_scene_learn.c index 0edb74ca2b4..37f9b3e0548 100644 --- a/applications/infrared/scenes/infrared_scene_learn.c +++ b/applications/infrared/scenes/infrared_scene_learn.c @@ -25,7 +25,6 @@ bool infrared_scene_learn_on_event(void* context, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { if(event.event == InfraredCustomEventTypeSignalReceived) { - infrared_worker_rx_set_received_signal_callback(infrared->worker, NULL, NULL); infrared_play_notification_message(infrared, InfraredNotificationMessageSuccess); scene_manager_next_scene(infrared->scene_manager, InfraredSceneLearnSuccess); consumed = true; @@ -38,6 +37,7 @@ bool infrared_scene_learn_on_event(void* context, SceneManagerEvent event) { void infrared_scene_learn_on_exit(void* context) { Infrared* infrared = context; Popup* popup = infrared->popup; + infrared_worker_rx_set_received_signal_callback(infrared->worker, NULL, NULL); infrared_worker_rx_stop(infrared->worker); infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkStop); popup_set_icon(popup, 0, 0, NULL); From 4fcb90928c96dfa8c324fe7392b517954d7688cc Mon Sep 17 00:00:00 2001 From: Max Lapan Date: Tue, 30 Aug 2022 13:33:05 +0200 Subject: [PATCH 031/824] ST25TB type is not handled (#1679) We search for ST25TB type cards, but not handling them being found. As a result, such cards are detected as NFC-A with 8-byte UID, which lead to read error on emulation attempt. Co-authored-by: gornekich --- firmware/targets/f7/furi_hal/furi_hal_nfc.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc.c b/firmware/targets/f7/furi_hal/furi_hal_nfc.c index 2d6db8fbf12..bd7fbc3fcc9 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_nfc.c +++ b/firmware/targets/f7/furi_hal/furi_hal_nfc.c @@ -124,7 +124,9 @@ bool furi_hal_nfc_detect(FuriHalNfcDevData* nfc_data, uint32_t timeout) { } nfc_data->cuid = (cuid_start[0] << 24) | (cuid_start[1] << 16) | (cuid_start[2] << 8) | (cuid_start[3]); - } else if(dev_list[0].type == RFAL_NFC_LISTEN_TYPE_NFCB) { + } else if(dev_list[0].type == RFAL_NFC_LISTEN_TYPE_NFCB || + dev_list[0].type == RFAL_NFC_LISTEN_TYPE_ST25TB) + { nfc_data->type = FuriHalNfcTypeB; } else if(dev_list[0].type == RFAL_NFC_LISTEN_TYPE_NFCF) { nfc_data->type = FuriHalNfcTypeF; From e4c6158d65b93166eb4dc54857c30d3dff575001 Mon Sep 17 00:00:00 2001 From: Foul Date: Tue, 30 Aug 2022 14:15:14 +0200 Subject: [PATCH 032/824] Update comment in SConstruct (#1684) * Update SConstruct: fix Typo * Grammar in SConstruct Co-authored-by: Aleksandr Kutuzov --- SConstruct | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SConstruct b/SConstruct index 52fe75a6a9c..4462f2f3005 100644 --- a/SConstruct +++ b/SConstruct @@ -1,5 +1,5 @@ # -# Main Fipper Build System entry point +# Main Flipper Build System entry point # # This file is evaluated by scons (the build system) every time fbt is invoked. # Scons constructs all referenced environments & their targets' dependency @@ -15,7 +15,7 @@ DefaultEnvironment(tools=[]) # Progress(["OwO\r", "owo\r", "uwu\r", "owo\r"], interval=15) -# This environment is created only for loading options & validating file/dir existance +# This environment is created only for loading options & validating file/dir existence fbt_variables = SConscript("site_scons/commandline.scons") cmd_environment = Environment(tools=[], variables=fbt_variables) Help(fbt_variables.GenerateHelpText(cmd_environment)) From 8e9043003f6d7462e1790d00bacc6f997c6f3dbc Mon Sep 17 00:00:00 2001 From: Eric Betts Date: Tue, 30 Aug 2022 06:59:34 -0700 Subject: [PATCH 033/824] Picopass write (#1658) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [picopass] Prevent false success with non-standard key * UI for writing * worker function for write * Update write command value * Show card read failure message Co-authored-by: あく --- applications/picopass/picopass_worker.c | 114 +++++++++++++++++- applications/picopass/picopass_worker.h | 1 + applications/picopass/picopass_worker_i.h | 1 + .../picopass/scenes/picopass_scene_config.h | 2 + .../scenes/picopass_scene_read_card.c | 2 - .../scenes/picopass_scene_read_card_success.c | 77 +++++++----- .../scenes/picopass_scene_saved_menu.c | 5 + .../scenes/picopass_scene_write_card.c | 53 ++++++++ .../picopass_scene_write_card_success.c | 57 +++++++++ lib/ST25RFAL002/include/rfal_picopass.h | 2 +- lib/ST25RFAL002/source/rfal_picopass.c | 16 ++- 11 files changed, 286 insertions(+), 44 deletions(-) create mode 100644 applications/picopass/scenes/picopass_scene_write_card.c create mode 100644 applications/picopass/scenes/picopass_scene_write_card_success.c diff --git a/applications/picopass/picopass_worker.c b/applications/picopass/picopass_worker.c index 88df8d45b86..3ca56d0065f 100644 --- a/applications/picopass/picopass_worker.c +++ b/applications/picopass/picopass_worker.c @@ -190,12 +190,87 @@ ReturnCode picopass_read_card(PicopassBlock* AA1) { return ERR_NONE; } +ReturnCode picopass_write_card(PicopassBlock* AA1) { + rfalPicoPassIdentifyRes idRes; + rfalPicoPassSelectRes selRes; + rfalPicoPassReadCheckRes rcRes; + rfalPicoPassCheckRes chkRes; + + ReturnCode err; + + uint8_t div_key[8] = {0}; + uint8_t mac[4] = {0}; + uint8_t ccnr[12] = {0}; + + err = rfalPicoPassPollerIdentify(&idRes); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "rfalPicoPassPollerIdentify error %d", err); + return err; + } + + err = rfalPicoPassPollerSelect(idRes.CSN, &selRes); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "rfalPicoPassPollerSelect error %d", err); + return err; + } + + err = rfalPicoPassPollerReadCheck(&rcRes); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "rfalPicoPassPollerReadCheck error %d", err); + return err; + } + memcpy(ccnr, rcRes.CCNR, sizeof(rcRes.CCNR)); // last 4 bytes left 0 + + loclass_diversifyKey(selRes.CSN, picopass_iclass_key, div_key); + loclass_opt_doReaderMAC(ccnr, div_key, mac); + + err = rfalPicoPassPollerCheck(mac, &chkRes); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "rfalPicoPassPollerCheck error %d", err); + return err; + } + + for(size_t i = 6; i < 10; i++) { + FURI_LOG_D(TAG, "rfalPicoPassPollerWriteBlock %d", i); + uint8_t data[9] = {0}; + data[0] = i; + memcpy(data + 1, AA1[i].data, RFAL_PICOPASS_MAX_BLOCK_LEN); + loclass_doMAC_N(data, sizeof(data), div_key, mac); + FURI_LOG_D( + TAG, + "loclass_doMAC_N %d %02x%02x%02x%02x%02x%02x%02x%02x %02x%02x%02x%02x", + i, + data[1], + data[2], + data[3], + data[4], + data[5], + data[6], + data[7], + data[8], + mac[0], + mac[1], + mac[2], + mac[3]); + + err = rfalPicoPassPollerWriteBlock(i, AA1[i].data, mac); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "rfalPicoPassPollerWriteBlock error %d", err); + return err; + } + } + + return ERR_NONE; +} + int32_t picopass_worker_task(void* context) { PicopassWorker* picopass_worker = context; picopass_worker_enable_field(); if(picopass_worker->state == PicopassWorkerStateDetect) { picopass_worker_detect(picopass_worker); + } else if(picopass_worker->state == PicopassWorkerStateWrite) { + picopass_worker_write(picopass_worker); } picopass_worker_disable_field(ERR_NONE); @@ -212,27 +287,60 @@ void picopass_worker_detect(PicopassWorker* picopass_worker) { PicopassPacs* pacs = &dev_data->pacs; ReturnCode err; + PicopassWorkerEvent nextState = PicopassWorkerEventSuccess; + while(picopass_worker->state == PicopassWorkerStateDetect) { if(picopass_detect_card(1000) == ERR_NONE) { // Process first found device err = picopass_read_card(AA1); if(err != ERR_NONE) { FURI_LOG_E(TAG, "picopass_read_card error %d", err); + nextState = PicopassWorkerEventFail; } - err = picopass_device_parse_credential(AA1, pacs); + if(nextState == PicopassWorkerEventSuccess) { + err = picopass_device_parse_credential(AA1, pacs); + } if(err != ERR_NONE) { FURI_LOG_E(TAG, "picopass_device_parse_credential error %d", err); + nextState = PicopassWorkerEventFail; } - err = picopass_device_parse_wiegand(pacs->credential, &pacs->record); + if(nextState == PicopassWorkerEventSuccess) { + err = picopass_device_parse_wiegand(pacs->credential, &pacs->record); + } if(err != ERR_NONE) { FURI_LOG_E(TAG, "picopass_device_parse_wiegand error %d", err); + nextState = PicopassWorkerEventFail; + } + + // Notify caller and exit + if(picopass_worker->callback) { + picopass_worker->callback(nextState, picopass_worker->context); + } + break; + } + furi_delay_ms(100); + } +} + +void picopass_worker_write(PicopassWorker* picopass_worker) { + PicopassDeviceData* dev_data = picopass_worker->dev_data; + PicopassBlock* AA1 = dev_data->AA1; + ReturnCode err; + PicopassWorkerEvent nextState = PicopassWorkerEventSuccess; + + while(picopass_worker->state == PicopassWorkerStateWrite) { + if(picopass_detect_card(1000) == ERR_NONE) { + err = picopass_write_card(AA1); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "picopass_write_card error %d", err); + nextState = PicopassWorkerEventFail; } // Notify caller and exit if(picopass_worker->callback) { - picopass_worker->callback(PicopassWorkerEventSuccess, picopass_worker->context); + picopass_worker->callback(nextState, picopass_worker->context); } break; } diff --git a/applications/picopass/picopass_worker.h b/applications/picopass/picopass_worker.h index 9035f1c8951..6ad70905835 100644 --- a/applications/picopass/picopass_worker.h +++ b/applications/picopass/picopass_worker.h @@ -11,6 +11,7 @@ typedef enum { PicopassWorkerStateReady, // Main worker states PicopassWorkerStateDetect, + PicopassWorkerStateWrite, // Transition PicopassWorkerStateStop, } PicopassWorkerState; diff --git a/applications/picopass/picopass_worker_i.h b/applications/picopass/picopass_worker_i.h index 789951900b7..9b215fb7437 100644 --- a/applications/picopass/picopass_worker_i.h +++ b/applications/picopass/picopass_worker_i.h @@ -31,3 +31,4 @@ void picopass_worker_change_state(PicopassWorker* picopass_worker, PicopassWorke int32_t picopass_worker_task(void* context); void picopass_worker_detect(PicopassWorker* picopass_worker); +void picopass_worker_write(PicopassWorker* picopass_worker); diff --git a/applications/picopass/scenes/picopass_scene_config.h b/applications/picopass/scenes/picopass_scene_config.h index 87745378b67..27d6bbcd777 100644 --- a/applications/picopass/scenes/picopass_scene_config.h +++ b/applications/picopass/scenes/picopass_scene_config.h @@ -9,3 +9,5 @@ ADD_SCENE(picopass, file_select, FileSelect) ADD_SCENE(picopass, device_info, DeviceInfo) ADD_SCENE(picopass, delete, Delete) ADD_SCENE(picopass, delete_success, DeleteSuccess) +ADD_SCENE(picopass, write_card, WriteCard) +ADD_SCENE(picopass, write_card_success, WriteCardSuccess) diff --git a/applications/picopass/scenes/picopass_scene_read_card.c b/applications/picopass/scenes/picopass_scene_read_card.c index 0867898a53d..8188207a2a1 100644 --- a/applications/picopass/scenes/picopass_scene_read_card.c +++ b/applications/picopass/scenes/picopass_scene_read_card.c @@ -37,8 +37,6 @@ bool picopass_scene_read_card_on_event(void* context, SceneManagerEvent event) { scene_manager_next_scene(picopass->scene_manager, PicopassSceneReadCardSuccess); consumed = true; } - } else if(event.type == SceneManagerEventTypeTick) { - consumed = true; } return consumed; } diff --git a/applications/picopass/scenes/picopass_scene_read_card_success.c b/applications/picopass/scenes/picopass_scene_read_card_success.c index 3866d201c47..c281b32ca27 100644 --- a/applications/picopass/scenes/picopass_scene_read_card_success.c +++ b/applications/picopass/scenes/picopass_scene_read_card_success.c @@ -29,38 +29,57 @@ void picopass_scene_read_card_success_on_enter(void* context) { PicopassPacs* pacs = &picopass->dev->dev_data.pacs; Widget* widget = picopass->widget; - size_t bytesLength = 1 + pacs->record.bitLength / 8; - string_set_str(credential_str, ""); - for(uint8_t i = PICOPASS_BLOCK_LEN - bytesLength; i < PICOPASS_BLOCK_LEN; i++) { - string_cat_printf(credential_str, " %02X", pacs->credential[i]); - } - - if(pacs->record.valid) { - string_cat_printf( - wiegand_str, "FC: %u CN: %u", pacs->record.FacilityCode, pacs->record.CardNumber); + if(pacs->record.bitLength == 0) { + string_cat_printf(wiegand_str, "Read Failed"); + + widget_add_button_element( + widget, + GuiButtonTypeLeft, + "Retry", + picopass_scene_read_card_success_widget_callback, + picopass); + + widget_add_string_element( + widget, 64, 12, AlignCenter, AlignCenter, FontPrimary, string_get_cstr(wiegand_str)); } else { - string_cat_printf(wiegand_str, "%d bits", pacs->record.bitLength); - } + size_t bytesLength = 1 + pacs->record.bitLength / 8; + string_set_str(credential_str, ""); + for(uint8_t i = PICOPASS_BLOCK_LEN - bytesLength; i < PICOPASS_BLOCK_LEN; i++) { + string_cat_printf(credential_str, " %02X", pacs->credential[i]); + } - widget_add_button_element( - widget, - GuiButtonTypeLeft, - "Retry", - picopass_scene_read_card_success_widget_callback, - picopass); - - widget_add_button_element( - widget, - GuiButtonTypeRight, - "More", - picopass_scene_read_card_success_widget_callback, - picopass); - - widget_add_string_element( - widget, 64, 12, AlignCenter, AlignCenter, FontPrimary, string_get_cstr(wiegand_str)); - widget_add_string_element( - widget, 64, 32, AlignCenter, AlignCenter, FontSecondary, string_get_cstr(credential_str)); + if(pacs->record.valid) { + string_cat_printf( + wiegand_str, "FC: %u CN: %u", pacs->record.FacilityCode, pacs->record.CardNumber); + } else { + string_cat_printf(wiegand_str, "%d bits", pacs->record.bitLength); + } + widget_add_button_element( + widget, + GuiButtonTypeLeft, + "Retry", + picopass_scene_read_card_success_widget_callback, + picopass); + + widget_add_button_element( + widget, + GuiButtonTypeRight, + "More", + picopass_scene_read_card_success_widget_callback, + picopass); + + widget_add_string_element( + widget, 64, 12, AlignCenter, AlignCenter, FontPrimary, string_get_cstr(wiegand_str)); + widget_add_string_element( + widget, + 64, + 32, + AlignCenter, + AlignCenter, + FontSecondary, + string_get_cstr(credential_str)); + } string_clear(credential_str); string_clear(wiegand_str); diff --git a/applications/picopass/scenes/picopass_scene_saved_menu.c b/applications/picopass/scenes/picopass_scene_saved_menu.c index 8f0ce40bae4..90a27ee8167 100644 --- a/applications/picopass/scenes/picopass_scene_saved_menu.c +++ b/applications/picopass/scenes/picopass_scene_saved_menu.c @@ -24,6 +24,8 @@ void picopass_scene_saved_menu_on_enter(void* context) { picopass); submenu_add_item( submenu, "Info", SubmenuIndexInfo, picopass_scene_saved_menu_submenu_callback, picopass); + submenu_add_item( + submenu, "Write", SubmenuIndexWrite, picopass_scene_saved_menu_submenu_callback, picopass); submenu_set_selected_item( picopass->submenu, @@ -46,6 +48,9 @@ bool picopass_scene_saved_menu_on_event(void* context, SceneManagerEvent event) } else if(event.event == SubmenuIndexInfo) { scene_manager_next_scene(picopass->scene_manager, PicopassSceneDeviceInfo); consumed = true; + } else if(event.event == SubmenuIndexWrite) { + scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteCard); + consumed = true; } } diff --git a/applications/picopass/scenes/picopass_scene_write_card.c b/applications/picopass/scenes/picopass_scene_write_card.c new file mode 100644 index 00000000000..a905dca9556 --- /dev/null +++ b/applications/picopass/scenes/picopass_scene_write_card.c @@ -0,0 +1,53 @@ +#include "../picopass_i.h" +#include + +void picopass_write_card_worker_callback(PicopassWorkerEvent event, void* context) { + UNUSED(event); + Picopass* picopass = context; + view_dispatcher_send_custom_event(picopass->view_dispatcher, PicopassCustomEventWorkerExit); +} + +void picopass_scene_write_card_on_enter(void* context) { + Picopass* picopass = context; + DOLPHIN_DEED(DolphinDeedNfcSave); + + // Setup view + Popup* popup = picopass->popup; + popup_set_header(popup, "Writing\npicopass\ncard", 68, 30, AlignLeft, AlignTop); + popup_set_icon(popup, 0, 3, &I_RFIDDolphinSend_97x61); + + // Start worker + view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewPopup); + picopass_worker_start( + picopass->worker, + PicopassWorkerStateWrite, + &picopass->dev->dev_data, + picopass_write_card_worker_callback, + picopass); + + picopass_blink_start(picopass); +} + +bool picopass_scene_write_card_on_event(void* context, SceneManagerEvent event) { + Picopass* picopass = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == PicopassCustomEventWorkerExit) { + scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteCardSuccess); + consumed = true; + } + } + return consumed; +} + +void picopass_scene_write_card_on_exit(void* context) { + Picopass* picopass = context; + + // Stop worker + picopass_worker_stop(picopass->worker); + // Clear view + popup_reset(picopass->popup); + + picopass_blink_stop(picopass); +} diff --git a/applications/picopass/scenes/picopass_scene_write_card_success.c b/applications/picopass/scenes/picopass_scene_write_card_success.c new file mode 100644 index 00000000000..108e7d1cefd --- /dev/null +++ b/applications/picopass/scenes/picopass_scene_write_card_success.c @@ -0,0 +1,57 @@ +#include "../picopass_i.h" +#include + +void picopass_scene_write_card_success_widget_callback( + GuiButtonType result, + InputType type, + void* context) { + furi_assert(context); + Picopass* picopass = context; + + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(picopass->view_dispatcher, result); + } +} + +void picopass_scene_write_card_success_on_enter(void* context) { + Picopass* picopass = context; + Widget* widget = picopass->widget; + + DOLPHIN_DEED(DolphinDeedNfcReadSuccess); + + // Send notification + notification_message(picopass->notifications, &sequence_success); + + widget_add_button_element( + widget, + GuiButtonTypeLeft, + "Retry", + picopass_scene_write_card_success_widget_callback, + picopass); + + view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewWidget); +} + +bool picopass_scene_write_card_success_on_event(void* context, SceneManagerEvent event) { + Picopass* picopass = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == GuiButtonTypeLeft) { + consumed = scene_manager_previous_scene(picopass->scene_manager); + } else if(event.event == GuiButtonTypeRight) { + // Clear device name + picopass_device_set_name(picopass->dev, ""); + scene_manager_next_scene(picopass->scene_manager, PicopassSceneCardMenu); + consumed = true; + } + } + return consumed; +} + +void picopass_scene_write_card_success_on_exit(void* context) { + Picopass* picopass = context; + + // Clear view + widget_reset(picopass->widget); +} diff --git a/lib/ST25RFAL002/include/rfal_picopass.h b/lib/ST25RFAL002/include/rfal_picopass.h index 2baf96f3754..e7fb272ceef 100644 --- a/lib/ST25RFAL002/include/rfal_picopass.h +++ b/lib/ST25RFAL002/include/rfal_picopass.h @@ -26,7 +26,7 @@ enum { RFAL_PICOPASS_CMD_READCHECK = 0x88, RFAL_PICOPASS_CMD_CHECK = 0x05, RFAL_PICOPASS_CMD_READ = 0x0C, - RFAL_PICOPASS_CMD_WRITE = 0x0C, + RFAL_PICOPASS_CMD_WRITE = 0x87, }; typedef struct { diff --git a/lib/ST25RFAL002/source/rfal_picopass.c b/lib/ST25RFAL002/source/rfal_picopass.c index d4422e41230..f33fcdf8c11 100644 --- a/lib/ST25RFAL002/source/rfal_picopass.c +++ b/lib/ST25RFAL002/source/rfal_picopass.c @@ -2,6 +2,8 @@ #include "rfal_picopass.h" #include "utils.h" +#define TAG "RFAL_PICOPASS" + typedef struct { uint8_t CMD; uint8_t CSN[RFAL_PICOPASS_UID_LEN]; @@ -169,18 +171,14 @@ ReturnCode rfalPicoPassPollerWriteBlock(uint8_t blockNum, uint8_t data[8], uint8 uint16_t recvLen = 0; uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS; uint32_t fwt = rfalConvMsTo1fc(20); - rfalPicoPassReadBlockRes readRes; + rfalPicoPassReadBlockRes block; ret = rfalTransceiveBlockingTxRx( - txBuf, - sizeof(txBuf), - (uint8_t*)&readRes, - sizeof(rfalPicoPassReadBlockRes), - &recvLen, - flags, - fwt); + txBuf, sizeof(txBuf), (uint8_t*)&block, sizeof(block), &recvLen, flags, fwt); - // TODO: compare response + if(ret == ERR_NONE) { + // TODO: compare response + } return ret; } From e7a5d19f9c919c1bc5c49f53f93b6ac8bec03b2b Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Tue, 30 Aug 2022 19:22:24 +0400 Subject: [PATCH 034/824] [FL-2778] SubGhz: fix CLI "subghz tx" (#1681) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * SubGhz: fix CLI "subghz tx" * Fix qoutes in amap workflow * Github: fix step naming * fix quotes in PR name again Co-authored-by: あく Co-authored-by: DrunkBatya --- .github/workflows/amap_analyse.yml | 16 +++++++++++++--- applications/subghz/subghz_cli.c | 20 ++++++++++++-------- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/.github/workflows/amap_analyse.yml b/.github/workflows/amap_analyse.yml index 11dac543ae2..5efdb09e52d 100644 --- a/.github/workflows/amap_analyse.yml +++ b/.github/workflows/amap_analyse.yml @@ -45,6 +45,19 @@ jobs: fetch-depth: 0 ref: ${{ github.event.pull_request.head.sha }} + - name: 'Escape pull request title' + if: github.event_name == 'pull_request' + run: | + import json + import os + import shlex + with open('${{ github.event_path }}') as fh: + event = json.load(fh) + escaped = shlex.quote(event['pull_request']['title']) + with open(os.environ['GITHUB_ENV'], 'a') as fh: + print(f'PULL_NAME={escaped}', file=fh) + shell: python3 {0} + - name: 'Generate prefixes by commit' id: names run: | @@ -58,7 +71,6 @@ jobs: SHA="$(cut -c -8 <<< "$COMMIT_HASH")" COMMIT_MSG="$(git log -1 --pretty=format:"%s")" PULL_ID="${{github.event.pull_request.number}}" - PULL_NAME="${{github.event.pull_request.title}}" fi BRANCH_NAME=${REF#refs/*/} SUFFIX=${BRANCH_NAME//\//_}-$(date +'%d%m%Y')-${SHA} @@ -78,7 +90,6 @@ jobs: mkdir artifacts - name: 'Download build artifacts' - if: ${{ !github.event.pull_request.head.repo.fork }} run: | echo "${{ secrets.RSYNC_DEPLOY_KEY }}" > deploy_key; chmod 600 ./deploy_key; @@ -112,7 +123,6 @@ jobs: export FREE_FLASH_SIZE="$(get_size ".free_flash")" if [[ ${{ github.event_name }} == 'pull_request' ]]; then export PULL_ID="${{steps.names.outputs.pull-id}}" - export PULL_NAME="${{steps.names.outputs.pull-name}}" fi python3 -m pip install mariadb python3 scripts/amap_mariadb_insert.py \ diff --git a/applications/subghz/subghz_cli.c b/applications/subghz/subghz_cli.c index 09dae0481fa..cda50c79cab 100644 --- a/applications/subghz/subghz_cli.c +++ b/applications/subghz/subghz_cli.c @@ -114,19 +114,21 @@ void subghz_cli_command_tx(Cli* cli, string_t args, void* context) { uint32_t frequency = 433920000; uint32_t key = 0x0074BADE; uint32_t repeat = 10; + uint32_t te = 403; if(string_size(args)) { - int ret = sscanf(string_get_cstr(args), "%lx %lu %lu", &key, &frequency, &repeat); - if(ret != 3) { + int ret = sscanf(string_get_cstr(args), "%lx %lu %lu %lu", &key, &frequency, &te, &repeat); + if(ret != 4) { printf( - "sscanf returned %d, key: %lx, frequency: %lu, repeat: %lu\r\n", + "sscanf returned %d, key: %lx, frequency: %lu, te:%lu, repeat: %lu\r\n", ret, key, frequency, + te, repeat); cli_print_usage( "subghz tx", - "<3 Byte Key: in hex> ", + "<3 Byte Key: in hex> ", string_get_cstr(args)); return; } @@ -139,9 +141,10 @@ void subghz_cli_command_tx(Cli* cli, string_t args, void* context) { } printf( - "Transmitting at %lu, key %lx, repeat %lu. Press CTRL+C to stop\r\n", + "Transmitting at %lu, key %lx, te %lu, repeat %lu. Press CTRL+C to stop\r\n", frequency, key, + te, repeat); string_t flipper_format_string; @@ -149,12 +152,13 @@ void subghz_cli_command_tx(Cli* cli, string_t args, void* context) { flipper_format_string, "Protocol: Princeton\n" "Bit: 24\n" - "Key: 00 00 00 00 00 %X %X %X\n" - "TE: 403\n" + "Key: 00 00 00 00 00 %02X %02X %02X\n" + "TE: %d\n" "Repeat: %d\n", (uint8_t)((key >> 16) & 0xFF), (uint8_t)((key >> 8) & 0xFF), (uint8_t)(key & 0xFF), + te, repeat); FlipperFormat* flipper_format = flipper_format_string_alloc(); Stream* stream = flipper_format_get_raw_stream(flipper_format); @@ -425,7 +429,7 @@ static void subghz_cli_command_print_usage() { printf("\tchat \t - Chat with other Flippers\r\n"); printf( - "\ttx <3 byte Key: in hex> \t - Transmitting key\r\n"); + "\ttx <3 byte Key: in hex> \t - Transmitting key\r\n"); printf("\trx \t - Reception key\r\n"); printf("\tdecode_raw \t - Testing\r\n"); From 311b60f8150708dcb1c63b5f4f8c248fc88dc8d4 Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Wed, 31 Aug 2022 18:21:36 +0400 Subject: [PATCH 035/824] [FL-2771] SubGhz: add protocol Prastel #1674 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- lib/subghz/protocols/came.c | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/lib/subghz/protocols/came.c b/lib/subghz/protocols/came.c index 726461d4d1e..14c66b7fa99 100644 --- a/lib/subghz/protocols/came.c +++ b/lib/subghz/protocols/came.c @@ -13,6 +13,9 @@ */ #define TAG "SubGhzProtocolCAME" +#define CAME_24_COUNT_BIT 24 +#define PRASTEL_COUNT_BIT 25 +#define PRASTEL_NAME "Prastel" static const SubGhzBlockConst subghz_protocol_came_const = { .te_short = 320, @@ -114,9 +117,9 @@ static bool subghz_protocol_encoder_came_get_upload(SubGhzProtocolEncoderCame* i //Send header instance->encoder.upload[index++] = level_duration_make( false, - ((instance->generic.data_count_bit == subghz_protocol_came_const.min_count_bit_for_found) ? - (uint32_t)subghz_protocol_came_const.te_short * 39 : - (uint32_t)subghz_protocol_came_const.te_short * 76)); + ((instance->generic.data_count_bit == CAME_24_COUNT_BIT) ? + (uint32_t)subghz_protocol_came_const.te_short * 76 : + (uint32_t)subghz_protocol_came_const.te_short * 39)); //Send start bit instance->encoder.upload[index++] = level_duration_make(true, (uint32_t)subghz_protocol_came_const.te_short); @@ -150,8 +153,8 @@ bool subghz_protocol_encoder_came_deserialize(void* context, FlipperFormat* flip } if((instance->generic.data_count_bit != subghz_protocol_came_const.min_count_bit_for_found) && - (instance->generic.data_count_bit != - 2 * subghz_protocol_came_const.min_count_bit_for_found)) { + (instance->generic.data_count_bit != CAME_24_COUNT_BIT) && + (instance->generic.data_count_bit != PRASTEL_COUNT_BIT)) { FURI_LOG_E(TAG, "Wrong number of bits in key"); break; } @@ -309,8 +312,8 @@ bool subghz_protocol_decoder_came_deserialize(void* context, FlipperFormat* flip } if((instance->generic.data_count_bit != subghz_protocol_came_const.min_count_bit_for_found) && - (instance->generic.data_count_bit != - 2 * subghz_protocol_came_const.min_count_bit_for_found)) { + (instance->generic.data_count_bit != CAME_24_COUNT_BIT) && + (instance->generic.data_count_bit != PRASTEL_COUNT_BIT)) { FURI_LOG_E(TAG, "Wrong number of bits in key"); break; } @@ -335,7 +338,8 @@ void subghz_protocol_decoder_came_get_string(void* context, string_t output) { "%s %dbit\r\n" "Key:0x%08lX\r\n" "Yek:0x%08lX\r\n", - instance->generic.protocol_name, + (instance->generic.data_count_bit == PRASTEL_COUNT_BIT ? PRASTEL_NAME : + instance->generic.protocol_name), instance->generic.data_count_bit, code_found_lo, code_found_reverse_lo); From 0ee4573a650f1017d9710b5eaf027581f1fb5005 Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Wed, 31 Aug 2022 18:27:34 +0400 Subject: [PATCH 036/824] SubGhz: add protocol Intertechno_V3 (#1622) * SubGhz: add decode Intertechno_V3 * SubGhz: add encoder Intertechno V3 * SubGhz: add uni_test Intertechno V3 * SubGhz: fix syntax * SubGhz: add Intertechno V3 dimming mode * SubGhz: fix parsing event Magellen protocol * SubGhz: fix syntax * SubGhz: fix encoder dimm mode Co-authored-by: Aleksandr Kutuzov --- applications/unit_tests/subghz/subghz_test.c | 20 +- assets/unit_tests/subghz/intertechno_v3.sub | 7 + .../unit_tests/subghz/intertechno_v3_raw.sub | 13 + lib/subghz/protocols/intertechno_v3.c | 472 ++++++++++++++++++ lib/subghz/protocols/intertechno_v3.h | 111 ++++ lib/subghz/protocols/magellen.c | 9 +- lib/subghz/protocols/registry.c | 2 +- lib/subghz/protocols/registry.h | 1 + 8 files changed, 628 insertions(+), 7 deletions(-) create mode 100644 assets/unit_tests/subghz/intertechno_v3.sub create mode 100644 assets/unit_tests/subghz/intertechno_v3_raw.sub create mode 100644 lib/subghz/protocols/intertechno_v3.c create mode 100644 lib/subghz/protocols/intertechno_v3.h diff --git a/applications/unit_tests/subghz/subghz_test.c b/applications/unit_tests/subghz/subghz_test.c index 04f442f6c41..6345b758fd9 100644 --- a/applications/unit_tests/subghz/subghz_test.c +++ b/applications/unit_tests/subghz/subghz_test.c @@ -13,7 +13,7 @@ #define CAME_ATOMO_DIR_NAME EXT_PATH("subghz/assets/came_atomo") #define NICE_FLOR_S_DIR_NAME EXT_PATH("subghz/assets/nice_flor_s") #define TEST_RANDOM_DIR_NAME EXT_PATH("unit_tests/subghz/test_random_raw.sub") -#define TEST_RANDOM_COUNT_PARSE 196 +#define TEST_RANDOM_COUNT_PARSE 208 #define TEST_TIMEOUT 10000 static SubGhzEnvironment* environment_handler; @@ -127,7 +127,7 @@ static bool subghz_decode_random_test(const char* path) { } subghz_file_encoder_worker_free(file_worker_encoder_handler); } - FURI_LOG_T(TAG, "\r\n Decoder count parse \033[0;33m%d\033[0m ", subghz_test_decoder_count); + FURI_LOG_D(TAG, "\r\n Decoder count parse \033[0;33m%d\033[0m ", subghz_test_decoder_count); if(furi_get_tick() - test_start > TEST_TIMEOUT * 10) { printf("\033[0;31mRandom test ERROR TimeOut\033[0m\r\n"); return false; @@ -419,6 +419,14 @@ MU_TEST(subghz_decoder_magellen_test) { "Test decoder " SUBGHZ_PROTOCOL_MAGELLEN_NAME " error\r\n"); } +MU_TEST(subghz_decoder_intertechno_v3_test) { + mu_assert( + subghz_decoder_test( + EXT_PATH("unit_tests/subghz/intertechno_v3_raw.sub"), + SUBGHZ_PROTOCOL_INTERTECHNO_V3_NAME), + "Test decoder " SUBGHZ_PROTOCOL_INTERTECHNO_V3_NAME " error\r\n"); +} + //test encoders MU_TEST(subghz_encoder_princeton_test) { mu_assert( @@ -528,6 +536,12 @@ MU_TEST(subghz_encoder_magellen_test) { "Test encoder " SUBGHZ_PROTOCOL_MAGELLEN_NAME " error\r\n"); } +MU_TEST(subghz_encoder_intertechno_v3_test) { + mu_assert( + subghz_encoder_test(EXT_PATH("unit_tests/subghz/intertechno_v3.sub")), + "Test encoder " SUBGHZ_PROTOCOL_INTERTECHNO_V3_NAME " error\r\n"); +} + MU_TEST(subghz_random_test) { mu_assert(subghz_decode_random_test(TEST_RANDOM_DIR_NAME), "Random test error\r\n"); } @@ -566,6 +580,7 @@ MU_TEST_SUITE(subghz) { MU_RUN_TEST(subghz_decoder_phoenix_v2_test); MU_RUN_TEST(subghz_decoder_honeywell_wdb_test); MU_RUN_TEST(subghz_decoder_magellen_test); + MU_RUN_TEST(subghz_decoder_intertechno_v3_test); MU_RUN_TEST(subghz_encoder_princeton_test); MU_RUN_TEST(subghz_encoder_came_test); @@ -585,6 +600,7 @@ MU_TEST_SUITE(subghz) { MU_RUN_TEST(subghz_encoder_phoenix_v2_test); MU_RUN_TEST(subghz_encoder_honeywell_wdb_test); MU_RUN_TEST(subghz_encoder_magellen_test); + MU_RUN_TEST(subghz_encoder_intertechno_v3_test); MU_RUN_TEST(subghz_random_test); subghz_test_deinit(); diff --git a/assets/unit_tests/subghz/intertechno_v3.sub b/assets/unit_tests/subghz/intertechno_v3.sub new file mode 100644 index 00000000000..af68b745555 --- /dev/null +++ b/assets/unit_tests/subghz/intertechno_v3.sub @@ -0,0 +1,7 @@ +Filetype: Flipper SubGhz Key File +Version: 1 +Frequency: 433920000 +Preset: FuriHalSubGhzPresetOok650Async +Protocol: Intertechno_V3 +Bit: 32 +Key: 00 00 00 00 3F 86 C5 9F diff --git a/assets/unit_tests/subghz/intertechno_v3_raw.sub b/assets/unit_tests/subghz/intertechno_v3_raw.sub new file mode 100644 index 00000000000..2cfc79511cd --- /dev/null +++ b/assets/unit_tests/subghz/intertechno_v3_raw.sub @@ -0,0 +1,13 @@ +Filetype: Flipper SubGhz RAW File +Version: 1 +Frequency: 433920000 +Preset: FuriHalSubGhzPresetOok650Async +Protocol: RAW +RAW_Data: 15041 -66 15883 -66 12643 -66 12681 -66 3413 -68 2713 -68 33389 -66 1445 -66 1279 -68 1027 -66 6911 -98 25229 -66 3967 -100 3019 -100 6131 -66 955 -66 3605 -66 12411 -98 1419 -66 3593 -68 2753 -66 2457 -66 6007 -66 627 -100 1597 -66 3071 -98 22749 -66 333 -66 12829 -66 4313 -132 855 -66 44097 -64 20391 -98 29999 -66 3539 -98 557 -66 1489 -100 4081 -100 3857 -64 2895 -132 2261 -166 3089 -66 2429 -68 34467 -66 3585 -66 3087 -66 3329 -132 5287 -66 1063 -98 15259 -100 2535 -66 995 -66 13057 -100 24233 -68 531 -100 26415 -66 1761 -100 2717 -66 4071 -100 12191 -66 23367 -68 2323 -66 19809 -248 245 -1388 255 -242 275 -1358 273 -1370 277 -246 277 -1368 275 -246 275 -1362 275 -244 275 -1364 275 -244 275 -1362 275 -244 275 -1328 273 -278 273 -1358 275 -246 275 -238 263 -1384 275 -246 273 -1358 275 -244 273 -1358 275 -246 275 -1360 275 -1344 277 -246 275 -1358 275 -244 275 -234 263 -1382 277 -1344 277 -246 279 -1362 275 -246 271 -234 261 -1380 275 -246 273 -1360 275 -246 275 -1366 277 -1340 277 -248 279 -238 263 -1382 275 -1344 277 -246 279 -1364 277 -244 275 -234 263 -1382 277 -244 273 -1358 275 -1344 277 -248 279 -1368 275 -244 273 -1360 239 -280 271 -1358 275 -244 275 -1358 275 -174 269 -10298 289 -2660 267 -238 299 -1356 275 -244 275 -1356 275 -1344 277 -248 277 -1360 275 -246 275 -1328 309 -244 273 -1358 277 -244 275 -1356 275 -246 273 -1326 309 -244 275 -1356 275 -246 273 -234 263 -1380 277 -246 273 -1326 309 -244 273 -1356 277 -246 277 -1358 275 -1338 279 -248 279 -1364 275 -246 273 -234 261 -1380 277 -1344 279 -250 277 -1330 309 -244 273 -232 261 -1384 275 -246 273 -1356 275 -248 275 -1360 275 -1340 279 -248 277 -236 263 -1380 277 -1342 279 -248 279 -1366 275 -246 273 -234 263 -1380 275 -246 275 -1358 275 -1340 279 -248 281 -1336 309 -244 273 -1358 275 -246 273 -1360 275 -244 273 -1358 275 -176 267 -10306 257 -2646 299 -234 301 -1354 277 -246 275 -1356 277 -1340 279 -250 279 -1332 309 -244 275 -1358 275 -248 273 -1326 309 -246 273 -1326 309 -244 275 -1356 277 -248 275 -1328 309 -246 273 -234 261 -1382 277 -246 277 -1326 309 -244 275 -1358 277 -246 277 -1356 277 -1346 277 -250 277 -1358 277 -246 275 -234 263 -1382 279 -1346 279 -248 281 -1330 307 -246 273 -236 261 -1380 277 -246 277 -1360 277 -246 277 -1360 275 -1344 279 -248 279 -236 263 -1384 277 -1340 279 -250 281 -1338 307 -246 271 -234 261 -1384 277 -246 275 -1356 277 -1340 279 -250 283 -1336 309 -246 273 -1356 277 -246 273 -1360 277 -246 +RAW_Data: 275 -1328 309 -174 269 -10296 289 -2648 267 -238 299 -1356 277 -246 275 -1324 307 -1342 279 -250 277 -1330 309 -244 275 -1362 277 -244 275 -1356 275 -248 273 -1328 309 -244 273 -1328 309 -244 275 -1360 277 -246 275 -234 259 -1384 277 -246 275 -1360 275 -246 273 -1358 277 -248 277 -1362 275 -1344 277 -248 277 -1328 307 -246 273 -236 261 -1384 277 -1348 279 -248 279 -1360 277 -246 273 -234 263 -1388 275 -246 275 -1360 277 -248 279 -1368 277 -1344 279 -248 279 -240 265 -1386 275 -1342 279 -286 247 -1372 275 -248 275 -238 265 -1386 277 -248 275 -1360 275 -1344 277 -286 247 -1374 275 -246 275 -1362 277 -246 275 -1360 277 -248 275 -1326 307 -174 269 -10290 287 -2654 269 -236 301 -1352 275 -248 273 -1326 311 -1340 277 -248 277 -1328 309 -244 273 -1358 275 -244 275 -1326 309 -244 273 -1356 277 -244 273 -1356 275 -246 275 -1358 275 -244 275 -234 261 -1382 277 -246 273 -1358 275 -246 273 -1360 277 -246 273 -1324 309 -1340 277 -248 277 -1328 307 -246 271 -234 259 -1382 277 -1346 279 -248 277 -1330 309 -244 271 -232 259 -1382 277 -244 275 -1356 277 -248 273 -1354 277 -1342 277 -248 275 -236 261 -1380 277 -1344 277 -248 279 -1330 307 -246 273 -234 261 -1378 277 -246 273 -1356 277 -1342 277 -248 277 -1330 309 -244 273 -1322 307 -246 273 -1326 309 -244 273 -1322 309 -176 267 -10298 257 -2682 265 -236 299 -1324 309 -248 273 -1324 311 -1342 277 -246 279 -1360 277 -244 275 -1362 275 -244 275 -1358 275 -244 275 -1360 275 -246 273 -1360 275 -244 277 -1360 275 -246 273 -234 263 -1384 275 -246 273 -1358 275 -246 275 -1360 277 -246 277 -1356 277 -1342 279 -248 277 -1364 275 -244 275 -234 261 -1384 275 -1344 277 -250 279 -1366 275 -246 273 -236 263 -1384 277 -246 275 -1358 277 -246 277 -1362 277 -1342 279 -248 279 -236 265 -1382 277 -1346 277 -248 281 -1366 275 -246 275 -234 265 -1384 275 -246 273 -1358 277 -1344 279 -248 279 -1364 275 -244 275 -1324 309 -246 273 -1324 307 -246 273 -1326 309 -174 267 -118796 133 -100 131 -892 329 -166 199 -132 131 -166 99 -100 265 -264 4663 -134 4889 -100 365 -98 5921 -100 5903 -68 4877 -98 2953 -98 1645 -64 1687 -66 981 -98 10769 -66 18319 -66 4831 -66 13301 -66 893 -132 5967 -100 15949 -66 3749 -66 497 -100 625 -66 1147 -66 469 -66 1261 -66 3651 -100 265 -100 26741 -68 6873 -66 4485 -100 2667 -68 3159 -68 2857 -132 2655 -66 12903 -66 1277 -66 1711 -66 787 -100 1327 -198 727 -64 1677 -100 1187 -66 1019 -66 891 -66 4303 -100 11297 -66 3923 -254 253 -1380 247 -292 253 -1344 +RAW_Data: 277 -1346 277 -250 279 -1364 275 -244 275 -1362 275 -244 275 -1356 275 -246 273 -1358 241 -278 273 -1356 275 -246 273 -1360 275 -246 273 -234 263 -1382 275 -244 273 -1358 275 -246 273 -1360 275 -246 273 -1358 275 -1340 277 -248 277 -1362 275 -246 273 -234 261 -1380 277 -1344 277 -248 279 -1362 275 -244 273 -236 261 -1380 275 -244 275 -1360 275 -246 275 -1358 275 -1346 277 -246 275 -236 263 -1384 275 -1342 277 -248 277 -1364 277 -244 273 -234 261 -1378 277 -246 273 -1356 277 -1340 277 -248 281 -1334 307 -246 271 -1356 275 -246 273 -1358 275 -244 273 -1326 309 -174 267 -10296 257 -2650 297 -232 263 -1384 277 -244 273 -1358 275 -1340 279 -248 279 -1328 309 -244 275 -1328 307 -244 273 -1356 275 -244 275 -1358 275 -246 273 -1324 309 -244 275 -1328 307 -244 273 -234 261 -1382 275 -246 273 -1326 309 -244 273 -1358 275 -246 273 -1358 275 -1338 279 -248 279 -1330 309 -244 273 -232 261 -1380 277 -1344 279 -248 279 -1330 309 -244 271 -234 261 -1382 275 -246 273 -1358 277 -244 275 -1330 309 -1338 277 -246 277 -236 263 -1380 277 -1342 277 -248 279 -1364 275 -246 273 -232 261 -1380 275 -248 275 -1328 307 -1338 277 -248 279 -1334 309 -244 271 -1358 275 -244 275 -1324 307 -246 271 -1328 309 -174 265 -10270 291 -2640 297 -232 297 -1350 277 -248 275 -1326 309 -1340 277 -248 277 -1328 309 -244 273 -1358 275 -246 273 -1326 309 -244 273 -1354 275 -246 273 -1330 307 -244 273 -1358 275 -246 273 -234 263 -1380 275 -246 273 -1358 275 -246 273 -1360 275 -244 273 -1358 275 -1340 277 -248 279 -1364 275 -244 273 -232 261 -1380 277 -1342 279 -250 279 -1332 307 -244 271 -234 261 -1378 277 -246 273 -1358 275 -248 275 -1360 275 -1340 277 -248 275 -236 263 -1382 277 -1344 277 -246 277 -1364 275 -246 273 -234 259 -1380 275 -246 273 -1362 275 -1342 275 -248 277 -1334 309 -244 271 -1356 275 -244 275 -1326 307 -244 273 -1356 275 -176 267 -10290 289 -2644 267 -238 301 -1320 309 -246 273 -1324 309 -1340 277 -248 277 -1328 307 -246 273 -1326 307 -246 273 -1324 309 -246 273 -1322 309 -246 273 -1322 307 -246 275 -1326 309 -246 273 -234 259 -1382 275 -246 275 -1322 309 -246 273 -1326 309 -246 273 -1326 309 -1340 277 -248 275 -1326 309 -246 273 -232 261 -1380 279 -1346 277 -250 277 -1328 309 -244 271 -232 261 -1380 277 -246 273 -1358 275 -248 273 -1328 307 -1340 277 -248 277 -236 261 -1380 277 -1344 277 -248 279 -1328 309 -244 275 -232 261 -1378 277 -248 273 -1326 309 -1344 277 -248 277 -1358 277 -246 273 -1328 307 -244 271 -1324 309 -244 +RAW_Data: 273 -1324 309 -174 267 -10270 289 -2638 297 -234 297 -1352 275 -248 275 -1328 307 -1340 277 -248 275 -1330 309 -244 273 -1358 275 -244 275 -1326 309 -244 271 -1356 275 -244 275 -1326 307 -246 273 -1326 309 -244 273 -234 261 -1378 275 -248 275 -1326 309 -244 271 -1356 277 -248 273 -1328 309 -1338 277 -248 277 -1328 309 -244 271 -232 261 -1380 277 -1348 279 -248 277 -1328 307 -246 271 -234 259 -1384 275 -244 275 -1356 277 -246 275 -1326 309 -1344 275 -248 275 -236 261 -1378 277 -1342 277 -250 279 -1334 309 -244 271 -232 261 -1380 277 -246 273 -1326 307 -1344 277 -248 277 -1328 309 -246 273 -1326 309 -244 271 -1324 309 -244 273 -1324 307 -176 267 -10288 287 -2618 299 -236 299 -1354 277 -244 273 -1326 307 -1340 279 -248 275 -1328 309 -244 275 -1326 309 -246 273 -1324 307 -246 273 -1322 309 -244 273 -1322 309 -244 275 -1328 309 -246 273 -232 261 -1380 277 -246 275 -1324 309 -244 273 -1356 277 -246 275 -1324 309 -1340 279 -246 277 -1328 309 -244 273 -232 261 -1382 277 -1344 279 -250 277 -1324 309 -246 273 -234 261 -1380 277 -246 273 -1358 277 -246 273 -1328 309 -1340 277 -248 275 -236 261 -1380 275 -1344 279 -248 279 -1360 277 -244 273 -234 261 -1380 277 -246 275 -1354 277 -1344 277 -248 277 -1328 311 -246 273 -1324 307 -244 273 -1324 309 -244 273 -1320 309 -176 269 -118210 761 -168 267 -66 563 -132 99 -132 3543 -66 5345 -100 4355 -66 4617 -68 20503 -166 2379 -132 293 -98 4117 -66 1151 -98 3353 -66 3485 -66 2491 -66 6133 -66 233 -68 16307 -68 16959 -98 357 -66 5419 -134 799 -100 327 -100 791 -66 2481 -66 963 -100 3481 -98 1679 -134 2473 -100 227 -68 3087 -66 11527 -130 4305 -98 435 -66 563 -100 2887 -100 267 -66 1787 -66 9655 -66 4793 -100 2119 -66 359 -98 1313 -132 3393 -234 995 -66 2681 -98 99 -130 1379 -100 3757 -100 21695 -132 5135 -100 693 -98 4631 -100 2325 -68 4937 -66 10409 -98 897 -100 1287 -66 2565 -66 3753 -66 4055 -66 2023 -68 1961 -68 629 -66 431 -66 5039 -66 2155 -100 2673 -66 1163 -98 6539 -100 825 -66 1197 -100 3053 -66 13973 -68 15515 -100 1861 -66 1027 -66 797 -98 959 -98 787 -132 787 -64 3811 -132 1747 -66 6683 -66 1033 -68 24927 -66 1259 -100 1125 -68 663 -66 1687 -66 4357 -132 4567 -66 3969 -98 3317 -132 433 -134 6043 -66 3249 -100 431 -98 2367 -100 11265 -66 5085 -68 2355 -64 1815 -66 1395 -274 241 -1366 275 -244 275 -1362 275 -1338 277 -284 243 -1368 239 -278 275 -1362 275 -244 275 -1360 241 -278 273 -1356 275 -246 275 -1360 239 -280 275 -1360 +RAW_Data: 275 -244 275 -234 263 -1386 239 -280 273 -1356 275 -244 273 -1360 275 -244 277 -1364 275 -1336 277 -248 277 -1366 275 -244 273 -234 263 -1386 275 -1340 277 -248 279 -1364 275 -244 275 -234 263 -1384 273 -244 275 -1358 275 -244 275 -1364 275 -1342 275 -248 277 -236 265 -1384 275 -1340 277 -282 243 -1366 275 -246 273 -236 263 -1382 277 -244 275 -1358 275 -1342 277 -248 277 -1364 275 -246 275 -1360 239 -280 273 -1358 241 -278 275 -1356 275 -210 233 -10302 257 -2652 297 -232 297 -1354 277 -244 275 -1358 275 -1340 279 -248 279 -1360 275 -246 275 -1360 275 -246 273 -1360 275 -244 275 -1328 309 -242 273 -1324 309 -244 275 -1360 275 -246 273 -234 261 -1384 275 -246 273 -1358 275 -244 275 -1358 277 -248 273 -1358 275 -1340 279 -248 277 -1334 307 -242 273 -232 261 -1380 277 -1348 277 -250 277 -1364 275 -244 275 -234 261 -1380 277 -244 275 -1358 277 -246 277 -1360 277 -1342 275 -248 275 -236 263 -1380 277 -1344 277 -248 279 -1368 275 -244 275 -232 261 -1382 277 -244 275 -1356 275 -1344 277 -248 279 -1362 275 -246 275 -1360 275 -246 273 -1356 275 -246 273 -1356 275 -176 267 -10302 257 -2648 299 -234 297 -1352 277 -246 275 -1326 309 -1340 279 -248 277 -1330 309 -244 275 -1328 309 -244 273 -1324 309 -244 275 -1324 309 -246 273 -1324 307 -246 275 -1328 309 -244 273 -234 261 -1378 277 -248 275 -1328 309 -244 273 -1356 277 -248 275 -1326 309 -1344 277 -248 275 -1326 309 -246 273 -234 259 -1380 277 -1348 281 -248 279 -1328 307 -246 273 -234 259 -1382 277 -246 275 -1360 275 -248 275 -1324 309 -1340 279 -248 277 -238 261 -1382 277 -1344 277 -248 279 -1330 311 -244 273 -234 259 -1378 277 -248 275 -1326 309 -1340 279 -248 279 -1336 307 -246 271 -1324 309 -244 275 -1324 307 -246 273 -1326 309 -174 269 -10296 257 -2648 299 -234 297 -1352 277 -248 273 -1326 309 -1342 277 -248 277 -1328 309 -246 275 -1328 309 -244 273 -1326 309 -244 273 -1322 309 -244 273 -1328 307 -244 275 -1328 309 -246 273 -234 261 -1382 277 -246 275 -1326 309 -244 273 -1352 277 -248 275 -1330 309 -1340 277 -248 277 -1328 309 -244 275 -232 261 -1384 277 -1342 279 -250 279 -1328 309 -244 273 -234 263 -1380 277 -246 273 -1360 277 -246 275 -1326 309 -1340 277 -250 277 -236 263 -1382 277 -1342 277 -248 279 -1362 277 -246 273 -234 263 -1382 277 -244 275 -1356 277 -1340 279 -248 279 -1362 275 -246 275 -1328 307 -246 273 -1356 275 -246 273 -1356 275 -174 269 -10292 287 -2650 269 -236 301 -1354 275 -248 273 -1358 275 -1340 279 -248 277 -1332 307 -246 275 -1328 +RAW_Data: 309 -244 273 -1324 309 -244 273 -1356 275 -246 273 -1358 275 -244 277 -1330 309 -244 273 -234 261 -1382 277 -244 275 -1358 275 -246 273 -1356 277 -248 275 -1360 275 -1340 277 -248 277 -1360 275 -246 273 -236 261 -1382 279 -1344 279 -248 279 -1360 277 -244 273 -234 261 -1380 277 -246 275 -1360 277 -246 273 -1360 275 -1342 279 -248 275 -236 263 -1382 275 -1344 279 -248 279 -1362 277 -246 273 -234 263 -1380 277 -246 275 -1356 275 -1342 277 -248 281 -1336 307 -246 271 -1354 277 -246 275 -1328 307 -244 273 -1352 277 -176 269 -10300 257 -2650 299 -232 297 -1354 277 -246 275 -1356 277 -1342 277 -248 279 -1328 309 -244 275 -1360 275 -246 273 -1328 307 -246 273 -1356 277 -246 277 -1326 309 -244 277 -1360 277 -246 273 -234 263 -1384 277 -246 275 -1324 309 -246 275 -1358 277 -246 277 -1360 277 -1344 277 -248 277 -1326 309 -246 273 -236 261 -1382 277 -1348 279 -250 281 -1330 307 -246 273 -234 263 -1386 277 -244 275 -1356 277 -248 277 -1362 277 -1342 277 -250 277 -238 263 -1384 277 -1342 277 -250 281 -1332 309 -246 273 -234 263 -1380 277 -246 275 -1360 277 -1342 279 -248 281 -1334 307 -246 273 -1356 275 -248 275 -1328 309 -244 275 -1324 309 -176 269 -115034 163 -362 67 -894 529 -166 14663 -98 4135 -66 3681 -100 299 -68 9829 -66 3517 -64 21569 -66 3251 -66 2209 -64 23701 -66 3359 -68 1057 -66 723 -66 299 -134 765 -66 589 -98 1687 -134 2153 -66 3081 -68 10447 -66 11643 -66 2451 -66 2277 -66 2897 -66 755 -100 5539 -64 5117 -132 4867 -134 3931 -64 625 -66 1317 -98 11597 -66 2255 -66 1165 -66 1123 -66 6371 -100 699 -68 1811 -66 621 -68 2191 -64 1291 -134 3003 -66 2423 -64 1463 -66 663 -100 1127 -100 6169 -100 489 -100 6087 -100 2027 -66 1195 -66 13195 -66 557 -66 40423 -98 1919 -100 1061 -132 201 -66 2553 -132 12549 -66 1789 -100 921 -134 1067 -66 729 -66 10029 -66 3909 -100 265 -100 16017 -134 21177 -68 2461 -66 2215 -68 1197 -66 5911 -66 2645 -66 3419 -132 16275 -64 5091 -68 2123 -66 2677 -64 10305 -66 12381 -100 427 -166 25331 -66 2457 -66 11859 -248 279 -1368 275 -246 275 -1360 275 -1340 277 -246 279 -1364 239 -278 275 -1358 275 -244 275 -1362 239 -278 273 -1358 239 -280 271 -1360 241 -278 273 -1360 275 -244 275 -234 261 -1384 239 -280 273 -1356 275 -244 273 -1360 275 -244 275 -1358 275 -1344 277 -248 275 -1358 275 -244 273 -236 261 -1384 275 -1342 279 -246 279 -1360 275 -244 275 -234 263 -1384 239 -278 273 -1358 275 -244 275 -1362 275 -1342 275 -248 275 -238 263 -1382 275 -1344 275 -248 +RAW_Data: 277 -1364 275 -244 273 -234 263 -1380 275 -246 273 -1358 275 -1342 277 -246 279 -1366 275 -244 273 -1362 239 -278 239 -1386 275 -246 273 -1360 241 -208 269 -10290 257 -2686 265 -232 265 -1384 275 -246 275 -1358 275 -1344 277 -248 275 -1358 275 -246 275 -1360 277 -244 273 -1326 309 -244 271 -1354 275 -244 275 -1358 275 -246 273 -1358 275 -246 273 -234 263 -1378 275 -246 275 -1360 275 -244 273 -1356 275 -246 275 -1360 275 -1342 277 -246 277 -1360 275 -246 273 -232 261 -1382 277 -1342 279 -248 279 -1360 275 -244 275 -232 261 -1380 277 -244 275 -1356 277 -246 277 -1360 275 -1342 277 -246 275 -236 263 -1384 275 -1342 277 -248 277 -1362 275 -246 273 -234 261 -1378 277 -246 275 -1328 307 -1340 277 -246 279 -1366 275 -244 273 -1326 307 -244 273 -1324 309 -244 273 -1356 275 -174 267 -10304 255 -2648 297 -230 263 -1382 277 -244 275 -1330 307 -1338 277 -248 277 -1330 309 -244 273 -1356 275 -246 273 -1362 275 -244 273 -1356 275 -244 273 -1326 307 -244 273 -1360 273 -246 273 -236 261 -1380 275 -244 275 -1328 307 -244 273 -1358 275 -244 275 -1360 275 -1342 277 -246 277 -1364 275 -244 271 -232 261 -1384 277 -1340 279 -248 279 -1360 275 -246 273 -234 261 -1380 275 -244 275 -1360 277 -244 275 -1356 275 -1342 279 -246 277 -236 263 -1382 275 -1340 277 -248 279 -1366 275 -246 271 -234 261 -1382 277 -244 275 -1354 275 -1342 277 -248 277 -1364 273 -246 273 -1362 275 -244 271 -1360 275 -244 273 -1358 275 -174 267 -10272 289 -2646 265 -262 261 -1382 277 -244 275 -1356 275 -1342 277 -248 277 -1364 275 -244 275 -1360 275 -244 273 -1358 275 -244 273 -1358 275 -244 273 -1326 307 -244 275 -1358 275 -246 273 -234 261 -1382 275 -246 273 -1358 275 -244 273 -1358 275 -246 275 -1360 275 -1338 277 -248 277 -1362 277 -244 271 -234 261 -1380 277 -1344 279 -248 277 -1332 273 -278 271 -234 261 -1382 275 -244 275 -1356 277 -246 275 -1360 277 -1340 277 -246 277 -234 263 -1384 275 -1342 277 -248 277 -1366 275 -244 273 -234 261 -1380 275 -246 273 -1360 275 -1340 277 -246 279 -1334 307 -244 273 -1356 275 -246 273 -1360 275 -244 271 -1354 277 -174 269 -10300 257 -2648 297 -230 263 -1384 277 -244 273 -1356 277 -1342 277 -248 277 -1362 275 -244 275 -1330 307 -244 273 -1324 309 -244 273 -1324 307 -246 273 -1326 307 -244 273 -1358 275 -246 273 -234 261 -1380 277 -246 273 -1358 275 -244 275 -1354 277 -248 275 -1360 275 -1338 279 -246 277 -1360 275 -244 273 -234 261 -1378 279 -1344 279 -248 279 -1330 309 -244 271 -232 261 -1380 277 -246 273 -1360 +RAW_Data: 277 -244 275 -1360 275 -1340 277 -246 277 -236 261 -1380 275 -1346 277 -248 277 -1362 275 -246 273 -234 263 -1380 275 -244 275 -1358 275 -1340 277 -248 279 -1334 309 -244 273 -1324 307 -246 273 -1356 275 -244 273 -1356 275 -174 269 -10302 257 -2644 297 -232 263 -1384 277 -246 275 -1354 275 -1344 277 -248 275 -1360 275 -246 275 -1358 275 -246 273 -1326 307 -246 273 -1324 307 -244 273 -1328 307 -244 273 -1358 275 -244 273 -236 261 -1380 275 -246 273 -1358 275 -244 273 -1358 275 -246 273 -1360 275 -1344 275 -248 275 -1360 275 -244 273 -234 261 -1378 277 -1344 279 -248 277 -1362 275 -246 273 -234 261 -1378 275 -244 275 -1360 275 -246 275 -1358 275 -1344 277 -246 277 -234 263 -1380 275 -1338 279 -246 281 -1368 275 -244 271 -234 261 -1386 275 -244 271 -1358 275 -1342 277 -246 279 -1362 275 -244 275 -1326 273 -278 273 -1358 239 -278 273 -1358 275 -174 267 -127478 195 -964 2317 -66 763 -98 1455 -100 16109 -66 5683 -98 11469 -66 34413 -66 5443 -66 11613 -66 2737 -66 12191 -66 2951 -68 1851 -68 1895 -68 2643 diff --git a/lib/subghz/protocols/intertechno_v3.c b/lib/subghz/protocols/intertechno_v3.c new file mode 100644 index 00000000000..e70bb8c8b7e --- /dev/null +++ b/lib/subghz/protocols/intertechno_v3.c @@ -0,0 +1,472 @@ +#include "intertechno_v3.h" + +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#define TAG "SubGhzProtocolIntertechnoV3" + +#define CH_PATTERN "%c%c%c%c" +#define CNT_TO_CH(ch) \ + (ch & 0x8 ? '1' : '0'), (ch & 0x4 ? '1' : '0'), (ch & 0x2 ? '1' : '0'), (ch & 0x1 ? '1' : '0') + +#define INTERTECHNO_V3_DIMMING_COUNT_BIT 36 + +static const SubGhzBlockConst subghz_protocol_intertechno_v3_const = { + .te_short = 275, + .te_long = 1375, + .te_delta = 150, + .min_count_bit_for_found = 32, +}; + +struct SubGhzProtocolDecoderIntertechno_V3 { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; +}; + +struct SubGhzProtocolEncoderIntertechno_V3 { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + IntertechnoV3DecoderStepReset = 0, + IntertechnoV3DecoderStepStartSync, + IntertechnoV3DecoderStepFoundSync, + IntertechnoV3DecoderStepStartDuration, + IntertechnoV3DecoderStepSaveDuration, + IntertechnoV3DecoderStepCheckDuration, + IntertechnoV3DecoderStepEndDuration, +} IntertechnoV3DecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_intertechno_v3_decoder = { + .alloc = subghz_protocol_decoder_intertechno_v3_alloc, + .free = subghz_protocol_decoder_intertechno_v3_free, + + .feed = subghz_protocol_decoder_intertechno_v3_feed, + .reset = subghz_protocol_decoder_intertechno_v3_reset, + + .get_hash_data = subghz_protocol_decoder_intertechno_v3_get_hash_data, + .serialize = subghz_protocol_decoder_intertechno_v3_serialize, + .deserialize = subghz_protocol_decoder_intertechno_v3_deserialize, + .get_string = subghz_protocol_decoder_intertechno_v3_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_intertechno_v3_encoder = { + .alloc = subghz_protocol_encoder_intertechno_v3_alloc, + .free = subghz_protocol_encoder_intertechno_v3_free, + + .deserialize = subghz_protocol_encoder_intertechno_v3_deserialize, + .stop = subghz_protocol_encoder_intertechno_v3_stop, + .yield = subghz_protocol_encoder_intertechno_v3_yield, +}; + +const SubGhzProtocol subghz_protocol_intertechno_v3 = { + .name = SUBGHZ_PROTOCOL_INTERTECHNO_V3_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | + SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_intertechno_v3_decoder, + .encoder = &subghz_protocol_intertechno_v3_encoder, +}; + +void* subghz_protocol_encoder_intertechno_v3_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderIntertechno_V3* instance = + malloc(sizeof(SubGhzProtocolEncoderIntertechno_V3)); + + instance->base.protocol = &subghz_protocol_intertechno_v3; + 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_running = false; + return instance; +} + +void subghz_protocol_encoder_intertechno_v3_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderIntertechno_V3* instance = context; + free(instance->encoder.upload); + free(instance); +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderIntertechno_V3 instance + * @return true On success + */ +static bool subghz_protocol_encoder_intertechno_v3_get_upload( + SubGhzProtocolEncoderIntertechno_V3* instance) { + furi_assert(instance); + size_t index = 0; + + //Send header + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_intertechno_v3_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_intertechno_v3_const.te_short * 38); + //Send sync + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_intertechno_v3_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_intertechno_v3_const.te_short * 10); + //Send key data + for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) { + if((instance->generic.data_count_bit == INTERTECHNO_V3_DIMMING_COUNT_BIT) && (i == 9)) { + //send bit dimm + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_intertechno_v3_const.te_short); + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_intertechno_v3_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_intertechno_v3_const.te_short); + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_intertechno_v3_const.te_short); + } else if(bit_read(instance->generic.data, i - 1)) { + //send bit 1 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_intertechno_v3_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_intertechno_v3_const.te_long); + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_intertechno_v3_const.te_short); + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_intertechno_v3_const.te_short); + } else { + //send bit 0 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_intertechno_v3_const.te_short); + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_intertechno_v3_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_intertechno_v3_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_intertechno_v3_const.te_long); + } + } + instance->encoder.size_upload = index; + return true; +} + +bool subghz_protocol_encoder_intertechno_v3_deserialize( + void* context, + FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderIntertechno_V3* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + if((instance->generic.data_count_bit != + subghz_protocol_intertechno_v3_const.min_count_bit_for_found) && + (instance->generic.data_count_bit != INTERTECHNO_V3_DIMMING_COUNT_BIT)) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + subghz_protocol_encoder_intertechno_v3_get_upload(instance); + instance->encoder.is_running = true; + + res = true; + } while(false); + + return res; +} + +void subghz_protocol_encoder_intertechno_v3_stop(void* context) { + SubGhzProtocolEncoderIntertechno_V3* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_intertechno_v3_yield(void* context) { + SubGhzProtocolEncoderIntertechno_V3* instance = context; + + if(instance->encoder.repeat == 0 || !instance->encoder.is_running) { + instance->encoder.is_running = 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_intertechno_v3_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderIntertechno_V3* instance = + malloc(sizeof(SubGhzProtocolDecoderIntertechno_V3)); + instance->base.protocol = &subghz_protocol_intertechno_v3; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_intertechno_v3_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderIntertechno_V3* instance = context; + free(instance); +} + +void subghz_protocol_decoder_intertechno_v3_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderIntertechno_V3* instance = context; + instance->decoder.parser_step = IntertechnoV3DecoderStepReset; +} + +void subghz_protocol_decoder_intertechno_v3_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderIntertechno_V3* instance = context; + switch(instance->decoder.parser_step) { + case IntertechnoV3DecoderStepReset: + if((!level) && + (DURATION_DIFF(duration, subghz_protocol_intertechno_v3_const.te_short * 37) < + subghz_protocol_intertechno_v3_const.te_delta * 15)) { + instance->decoder.parser_step = IntertechnoV3DecoderStepStartSync; + } + break; + case IntertechnoV3DecoderStepStartSync: + if(level && (DURATION_DIFF(duration, subghz_protocol_intertechno_v3_const.te_short) < + subghz_protocol_intertechno_v3_const.te_delta)) { + instance->decoder.parser_step = IntertechnoV3DecoderStepFoundSync; + } else { + instance->decoder.parser_step = IntertechnoV3DecoderStepReset; + } + break; + + case IntertechnoV3DecoderStepFoundSync: + if(!level && (DURATION_DIFF(duration, subghz_protocol_intertechno_v3_const.te_short * 10) < + subghz_protocol_intertechno_v3_const.te_delta * 3)) { + instance->decoder.parser_step = IntertechnoV3DecoderStepStartDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } else { + instance->decoder.parser_step = IntertechnoV3DecoderStepReset; + } + break; + + case IntertechnoV3DecoderStepStartDuration: + if(level && (DURATION_DIFF(duration, subghz_protocol_intertechno_v3_const.te_short) < + subghz_protocol_intertechno_v3_const.te_delta)) { + instance->decoder.parser_step = IntertechnoV3DecoderStepSaveDuration; + } else { + instance->decoder.parser_step = IntertechnoV3DecoderStepReset; + } + break; + + case IntertechnoV3DecoderStepSaveDuration: + if(!level) { //save interval + if(duration >= (subghz_protocol_intertechno_v3_const.te_short * 11)) { + instance->decoder.parser_step = IntertechnoV3DecoderStepStartSync; + if((instance->decoder.decode_count_bit == + subghz_protocol_intertechno_v3_const.min_count_bit_for_found) || + (instance->decoder.decode_count_bit == INTERTECHNO_V3_DIMMING_COUNT_BIT)) { + 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); + } + break; + } + instance->decoder.te_last = duration; + instance->decoder.parser_step = IntertechnoV3DecoderStepCheckDuration; + } else { + instance->decoder.parser_step = IntertechnoV3DecoderStepReset; + } + break; + case IntertechnoV3DecoderStepCheckDuration: + if(level) { + //Add 0 bit + if((DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_intertechno_v3_const.te_short) < + subghz_protocol_intertechno_v3_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_intertechno_v3_const.te_short) < + subghz_protocol_intertechno_v3_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = IntertechnoV3DecoderStepEndDuration; + } else if( + //Add 1 bit + (DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_intertechno_v3_const.te_long) < + subghz_protocol_intertechno_v3_const.te_delta * 2) && + (DURATION_DIFF(duration, subghz_protocol_intertechno_v3_const.te_short) < + subghz_protocol_intertechno_v3_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = IntertechnoV3DecoderStepEndDuration; + + } else if( + //Add dimm_state + (DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_intertechno_v3_const.te_short) < + subghz_protocol_intertechno_v3_const.te_delta * 2) && + (DURATION_DIFF(duration, subghz_protocol_intertechno_v3_const.te_short) < + subghz_protocol_intertechno_v3_const.te_delta) && + (instance->decoder.decode_count_bit == 27)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = IntertechnoV3DecoderStepEndDuration; + + } else + instance->decoder.parser_step = IntertechnoV3DecoderStepReset; + } else { + instance->decoder.parser_step = IntertechnoV3DecoderStepReset; + } + break; + + case IntertechnoV3DecoderStepEndDuration: + if(!level && ((DURATION_DIFF(duration, subghz_protocol_intertechno_v3_const.te_short) < + subghz_protocol_intertechno_v3_const.te_delta) || + (DURATION_DIFF(duration, subghz_protocol_intertechno_v3_const.te_long) < + subghz_protocol_intertechno_v3_const.te_delta * 2))) { + instance->decoder.parser_step = IntertechnoV3DecoderStepStartDuration; + } else { + instance->decoder.parser_step = IntertechnoV3DecoderStepReset; + } + break; + } +} + +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + */ +static void subghz_protocol_intertechno_v3_check_remote_controller(SubGhzBlockGeneric* instance) { + /* + * A frame is either 32 or 36 bits: + * + * _ + * start bit: | |__________ (T,10T) + * _ _ + * '0': | |_| |_____ (T,T,T,5T) + * _ _ + * '1': | |_____| |_ (T,5T,T,T) + * _ _ + * dimm: | |_| |_ (T,T,T,T) + * + * _ + * stop bit: | |____...____ (T,38T) + * + * if frame 32 bits + * SSSSSSSSSSSSSSSSSSSSSSSSSS all_ch on/off ~ch + * Key:0x3F86C59F => 00111111100001101100010110 0 1 1111 + * + * if frame 36 bits + * SSSSSSSSSSSSSSSSSSSSSSSSSS all_ch dimm ~ch dimm_level + * Key:0x42D2E8856 => 01000010110100101110100010 0 X 0101 0110 + * + */ + + if(instance->data_count_bit == subghz_protocol_intertechno_v3_const.min_count_bit_for_found) { + instance->serial = (instance->data >> 6) & 0x3FFFFFF; + if((instance->data >> 5) & 0x1) { + instance->cnt = 1 << 5; + } else { + instance->cnt = (~instance->data & 0xF); + } + instance->btn = (instance->data >> 4) & 0x1; + } else if(instance->data_count_bit == INTERTECHNO_V3_DIMMING_COUNT_BIT) { + instance->serial = (instance->data >> 10) & 0x3FFFFFF; + if((instance->data >> 9) & 0x1) { + instance->cnt = 1 << 5; + } else { + instance->cnt = (~(instance->data >> 4) & 0xF); + } + instance->btn = (instance->data) & 0xF; + } else { + instance->serial = 0; + instance->cnt = 0; + instance->btn = 0; + } +} + +uint8_t subghz_protocol_decoder_intertechno_v3_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderIntertechno_V3* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_intertechno_v3_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzPresetDefinition* preset) { + furi_assert(context); + SubGhzProtocolDecoderIntertechno_V3* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +bool subghz_protocol_decoder_intertechno_v3_deserialize( + void* context, + FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderIntertechno_V3* instance = context; + bool ret = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if((instance->generic.data_count_bit != + subghz_protocol_intertechno_v3_const.min_count_bit_for_found) && + (instance->generic.data_count_bit != INTERTECHNO_V3_DIMMING_COUNT_BIT)) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + ret = true; + } while(false); + return ret; +} + +void subghz_protocol_decoder_intertechno_v3_get_string(void* context, string_t output) { + furi_assert(context); + SubGhzProtocolDecoderIntertechno_V3* instance = context; + + subghz_protocol_intertechno_v3_check_remote_controller(&instance->generic); + + string_cat_printf( + output, + "%.11s %db\r\n" + "Key:0x%08llX\r\n" + "Sn:%07lX\r\n", + instance->generic.protocol_name, + instance->generic.data_count_bit, + instance->generic.data, + instance->generic.serial); + + if(instance->generic.data_count_bit == + subghz_protocol_intertechno_v3_const.min_count_bit_for_found) { + if(instance->generic.cnt >> 5) { + string_cat_printf( + output, "Ch: All Btn:%s\r\n", (instance->generic.btn ? "On" : "Off")); + } else { + string_cat_printf( + output, + "Ch:" CH_PATTERN " Btn:%s\r\n", + CNT_TO_CH(instance->generic.cnt), + (instance->generic.btn ? "On" : "Off")); + } + } else if(instance->generic.data_count_bit == INTERTECHNO_V3_DIMMING_COUNT_BIT) { + string_cat_printf( + output, + "Ch:" CH_PATTERN " Dimm:%d%%\r\n", + CNT_TO_CH(instance->generic.cnt), + (int)(6.67 * (float)instance->generic.btn)); + } +} diff --git a/lib/subghz/protocols/intertechno_v3.h b/lib/subghz/protocols/intertechno_v3.h new file mode 100644 index 00000000000..65d6f61d10b --- /dev/null +++ b/lib/subghz/protocols/intertechno_v3.h @@ -0,0 +1,111 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_INTERTECHNO_V3_NAME "Intertechno_V3" + +typedef struct SubGhzProtocolDecoderIntertechno_V3 SubGhzProtocolDecoderIntertechno_V3; +typedef struct SubGhzProtocolEncoderIntertechno_V3 SubGhzProtocolEncoderIntertechno_V3; + +extern const SubGhzProtocolDecoder subghz_protocol_intertechno_v3_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_intertechno_v3_encoder; +extern const SubGhzProtocol subghz_protocol_intertechno_v3; + +/** + * Allocate SubGhzProtocolEncoderIntertechno_V3. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderIntertechno_V3* pointer to a SubGhzProtocolEncoderIntertechno_V3 instance + */ +void* subghz_protocol_encoder_intertechno_v3_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderIntertechno_V3. + * @param context Pointer to a SubGhzProtocolEncoderIntertechno_V3 instance + */ +void subghz_protocol_encoder_intertechno_v3_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderIntertechno_V3 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_encoder_intertechno_v3_deserialize( + void* context, + FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderIntertechno_V3 instance + */ +void subghz_protocol_encoder_intertechno_v3_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderIntertechno_V3 instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_intertechno_v3_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderIntertechno_V3. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderIntertechno_V3* pointer to a SubGhzProtocolDecoderIntertechno_V3 instance + */ +void* subghz_protocol_decoder_intertechno_v3_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderIntertechno_V3. + * @param context Pointer to a SubGhzProtocolDecoderIntertechno_V3 instance + */ +void subghz_protocol_decoder_intertechno_v3_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderIntertechno_V3. + * @param context Pointer to a SubGhzProtocolDecoderIntertechno_V3 instance + */ +void subghz_protocol_decoder_intertechno_v3_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderIntertechno_V3 instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_intertechno_v3_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderIntertechno_V3 instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_intertechno_v3_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderIntertechno_V3. + * @param context Pointer to a SubGhzProtocolDecoderIntertechno_V3 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzPresetDefinition + * @return true On success + */ +bool subghz_protocol_decoder_intertechno_v3_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzPresetDefinition* preset); + +/** + * Deserialize data SubGhzProtocolDecoderIntertechno_V3. + * @param context Pointer to a SubGhzProtocolDecoderIntertechno_V3 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_decoder_intertechno_v3_deserialize( + void* context, + FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderIntertechno_V3 instance + * @param output Resulting text + */ +void subghz_protocol_decoder_intertechno_v3_get_string(void* context, string_t output); diff --git a/lib/subghz/protocols/magellen.c b/lib/subghz/protocols/magellen.c index 859f8035195..bb0600a7453 100644 --- a/lib/subghz/protocols/magellen.c +++ b/lib/subghz/protocols/magellen.c @@ -360,11 +360,11 @@ static void subghz_protocol_magellen_check_remote_controller(SubGhzBlockGeneric* * 0x1275EC => 0x12-event codes, 0x75EC-serial (dec 117236) * * event codes -* bit_0: 1-alarm, 0-close +* bit_0: 1-Open/Motion, 0-close/ok * bit_1: 1-Tamper On (alarm), 0-Tamper Off (ok) * bit_2: ? * bit_3: 1-power on -* bit_4: model type - door alarm +* bit_4: model type - wireless reed * bit_5: model type - motion sensor * bit_6: ? * bit_7: ? @@ -379,11 +379,12 @@ static void subghz_protocol_magellen_get_event_serialize(uint8_t event, string_t string_cat_printf( output, "%s%s%s%s%s%s%s%s", - (event & 0x1 ? " Alarm" : "Ok"), + ((event >> 4) & 0x1 ? (event & 0x1 ? " Open" : " Close") : + (event & 0x1 ? " Motion" : " Ok")), ((event >> 1) & 0x1 ? ", Tamper On (Alarm)" : ""), ((event >> 2) & 0x1 ? ", ?" : ""), ((event >> 3) & 0x1 ? ", Power On" : ""), - ((event >> 4) & 0x1 ? ", MT:Door_Alarm" : ""), + ((event >> 4) & 0x1 ? ", MT:Wireless_Reed" : ""), ((event >> 5) & 0x1 ? ", MT:Motion_Sensor" : ""), ((event >> 6) & 0x1 ? ", ?" : ""), ((event >> 7) & 0x1 ? ", ?" : "")); diff --git a/lib/subghz/protocols/registry.c b/lib/subghz/protocols/registry.c index b72278788cb..6c113cbf868 100644 --- a/lib/subghz/protocols/registry.c +++ b/lib/subghz/protocols/registry.c @@ -11,7 +11,7 @@ const SubGhzProtocol* subghz_protocol_registry[] = { &subghz_protocol_secplus_v1, &subghz_protocol_megacode, &subghz_protocol_holtek, &subghz_protocol_chamb_code, &subghz_protocol_power_smart, &subghz_protocol_marantec, &subghz_protocol_bett, &subghz_protocol_doitrand, &subghz_protocol_phoenix_v2, - &subghz_protocol_honeywell_wdb, &subghz_protocol_magellen, + &subghz_protocol_honeywell_wdb, &subghz_protocol_magellen, &subghz_protocol_intertechno_v3, }; diff --git a/lib/subghz/protocols/registry.h b/lib/subghz/protocols/registry.h index 36a560765ac..342bf6d2bb5 100644 --- a/lib/subghz/protocols/registry.h +++ b/lib/subghz/protocols/registry.h @@ -34,6 +34,7 @@ #include "phoenix_v2.h" #include "honeywell_wdb.h" #include "magellen.h" +#include "intertechno_v3.h" /** * Registration by name SubGhzProtocol. From 10b0a611cff79cd50fd8bce53fce5adc767a5cf4 Mon Sep 17 00:00:00 2001 From: Sebastian Mauer Date: Fri, 2 Sep 2022 12:15:34 +0100 Subject: [PATCH 037/824] Add support for Gallagher tags (#1680) * Add Gallagher protocol --- lib/lfrfid/protocols/lfrfid_protocols.c | 2 + lib/lfrfid/protocols/lfrfid_protocols.h | 1 + lib/lfrfid/protocols/protocol_gallagher.c | 300 ++++++++++++++++++++++ lib/lfrfid/protocols/protocol_gallagher.h | 4 + 4 files changed, 307 insertions(+) create mode 100644 lib/lfrfid/protocols/protocol_gallagher.c create mode 100644 lib/lfrfid/protocols/protocol_gallagher.h diff --git a/lib/lfrfid/protocols/lfrfid_protocols.c b/lib/lfrfid/protocols/lfrfid_protocols.c index fc658610626..3d142969bcf 100644 --- a/lib/lfrfid/protocols/lfrfid_protocols.c +++ b/lib/lfrfid/protocols/lfrfid_protocols.c @@ -13,6 +13,7 @@ #include "protocol_jablotron.h" #include "protocol_paradox.h" #include "protocol_pac_stanley.h" +#include "protocol_gallagher.h" const ProtocolBase* lfrfid_protocols[] = { [LFRFIDProtocolEM4100] = &protocol_em4100, @@ -29,4 +30,5 @@ const ProtocolBase* lfrfid_protocols[] = { [LFRFIDProtocolJablotron] = &protocol_jablotron, [LFRFIDProtocolParadox] = &protocol_paradox, [LFRFIDProtocolPACStanley] = &protocol_pac_stanley, + [LFRFIDProtocolGallagher] = &protocol_gallagher, }; \ No newline at end of file diff --git a/lib/lfrfid/protocols/lfrfid_protocols.h b/lib/lfrfid/protocols/lfrfid_protocols.h index 210ddd15a61..20b784dcca2 100644 --- a/lib/lfrfid/protocols/lfrfid_protocols.h +++ b/lib/lfrfid/protocols/lfrfid_protocols.h @@ -22,6 +22,7 @@ typedef enum { LFRFIDProtocolJablotron, LFRFIDProtocolParadox, LFRFIDProtocolPACStanley, + LFRFIDProtocolGallagher, LFRFIDProtocolMax, } LFRFIDProtocol; diff --git a/lib/lfrfid/protocols/protocol_gallagher.c b/lib/lfrfid/protocols/protocol_gallagher.c new file mode 100644 index 00000000000..c205ab8a502 --- /dev/null +++ b/lib/lfrfid/protocols/protocol_gallagher.c @@ -0,0 +1,300 @@ +#include +#include +#include +#include +#include "lfrfid_protocols.h" + +#define GALLAGHER_CLOCK_PER_BIT (32) + +#define GALLAGHER_ENCODED_BIT_SIZE (96) +#define GALLAGHER_ENCODED_BYTE_SIZE ((GALLAGHER_ENCODED_BIT_SIZE) / 8) +#define GALLAGHER_PREAMBLE_BIT_SIZE (16) +#define GALLAGHER_PREAMBLE_BYTE_SIZE ((GALLAGHER_PREAMBLE_BIT_SIZE) / 8) +#define GALLAGHER_ENCODED_BYTE_FULL_SIZE \ + (GALLAGHER_ENCODED_BYTE_SIZE + GALLAGHER_PREAMBLE_BYTE_SIZE) +#define GALLAGHER_DECODED_DATA_SIZE 8 + +#define GALLAGHER_READ_SHORT_TIME (128) +#define GALLAGHER_READ_LONG_TIME (256) +#define GALLAGHER_READ_JITTER_TIME (60) + +#define GALLAGHER_READ_SHORT_TIME_LOW (GALLAGHER_READ_SHORT_TIME - GALLAGHER_READ_JITTER_TIME) +#define GALLAGHER_READ_SHORT_TIME_HIGH (GALLAGHER_READ_SHORT_TIME + GALLAGHER_READ_JITTER_TIME) +#define GALLAGHER_READ_LONG_TIME_LOW (GALLAGHER_READ_LONG_TIME - GALLAGHER_READ_JITTER_TIME) +#define GALLAGHER_READ_LONG_TIME_HIGH (GALLAGHER_READ_LONG_TIME + GALLAGHER_READ_JITTER_TIME) + +typedef struct { + uint8_t data[GALLAGHER_DECODED_DATA_SIZE]; + uint8_t encoded_data[GALLAGHER_ENCODED_BYTE_FULL_SIZE]; + + uint8_t encoded_data_index; + bool encoded_polarity; + + ManchesterState decoder_manchester_state; +} ProtocolGallagher; + +ProtocolGallagher* protocol_gallagher_alloc(void) { + ProtocolGallagher* proto = malloc(sizeof(ProtocolGallagher)); + return (void*)proto; +}; + +void protocol_gallagher_free(ProtocolGallagher* protocol) { + free(protocol); +}; + +uint8_t* protocol_gallagher_get_data(ProtocolGallagher* protocol) { + return protocol->data; +}; + +static void protocol_gallagher_scramble(uint8_t* data, size_t length) { + const uint8_t lut[] = { + 0xa3, 0xb0, 0x80, 0xc6, 0xb2, 0xf4, 0x5c, 0x6c, 0x81, 0xf1, 0xbb, 0xeb, 0x55, 0x67, 0x3c, + 0x05, 0x1a, 0x0e, 0x61, 0xf6, 0x22, 0xce, 0xaa, 0x8f, 0xbd, 0x3b, 0x1f, 0x5e, 0x44, 0x04, + 0x51, 0x2e, 0x4d, 0x9a, 0x84, 0xea, 0xf8, 0x66, 0x74, 0x29, 0x7f, 0x70, 0xd8, 0x31, 0x7a, + 0x6d, 0xa4, 0x00, 0x82, 0xb9, 0x5f, 0xb4, 0x16, 0xab, 0xff, 0xc2, 0x39, 0xdc, 0x19, 0x65, + 0x57, 0x7c, 0x20, 0xfa, 0x5a, 0x49, 0x13, 0xd0, 0xfb, 0xa8, 0x91, 0x73, 0xb1, 0x33, 0x18, + 0xbe, 0x21, 0x72, 0x48, 0xb6, 0xdb, 0xa0, 0x5d, 0xcc, 0xe6, 0x17, 0x27, 0xe5, 0xd4, 0x53, + 0x42, 0xf3, 0xdd, 0x7b, 0x24, 0xac, 0x2b, 0x58, 0x1e, 0xa7, 0xe7, 0x86, 0x40, 0xd3, 0x98, + 0x97, 0x71, 0xcb, 0x3a, 0x0f, 0x01, 0x9b, 0x6e, 0x1b, 0xfc, 0x34, 0xa6, 0xda, 0x07, 0x0c, + 0xae, 0x37, 0xca, 0x54, 0xfd, 0x26, 0xfe, 0x0a, 0x45, 0xa2, 0x2a, 0xc4, 0x12, 0x0d, 0xf5, + 0x4f, 0x69, 0xe0, 0x8a, 0x77, 0x60, 0x3f, 0x99, 0x95, 0xd2, 0x38, 0x36, 0x62, 0xb7, 0x32, + 0x7e, 0x79, 0xc0, 0x46, 0x93, 0x2f, 0xa5, 0xba, 0x5b, 0xaf, 0x52, 0x1d, 0xc3, 0x75, 0xcf, + 0xd6, 0x4c, 0x83, 0xe8, 0x3d, 0x30, 0x4e, 0xbc, 0x08, 0x2d, 0x09, 0x06, 0xd9, 0x25, 0x9e, + 0x89, 0xf2, 0x96, 0x88, 0xc1, 0x8c, 0x94, 0x0b, 0x28, 0xf0, 0x47, 0x63, 0xd5, 0xb3, 0x68, + 0x56, 0x9c, 0xf9, 0x6f, 0x41, 0x50, 0x85, 0x8b, 0x9d, 0x59, 0xbf, 0x9f, 0xe2, 0x8e, 0x6a, + 0x11, 0x23, 0xa1, 0xcd, 0xb5, 0x7d, 0xc7, 0xa9, 0xc8, 0xef, 0xdf, 0x02, 0xb8, 0x03, 0x6b, + 0x35, 0x3e, 0x2c, 0x76, 0xc9, 0xde, 0x1c, 0x4b, 0xd1, 0xed, 0x14, 0xc5, 0xad, 0xe9, 0x64, + 0x4a, 0xec, 0x8d, 0xf7, 0x10, 0x43, 0x78, 0x15, 0x87, 0xe4, 0xd7, 0x92, 0xe1, 0xee, 0xe3, + 0x90}; + for(size_t i = 0; i < length; i++) { + data[i] = lut[data[i]]; + } +} + +static void protocol_gallagher_descramble(uint8_t* data, size_t length) { + const uint8_t lut[] = { + 0x2f, 0x6e, 0xdd, 0xdf, 0x1d, 0x0f, 0xb0, 0x76, 0xad, 0xaf, 0x7f, 0xbb, 0x77, 0x85, 0x11, + 0x6d, 0xf4, 0xd2, 0x84, 0x42, 0xeb, 0xf7, 0x34, 0x55, 0x4a, 0x3a, 0x10, 0x71, 0xe7, 0xa1, + 0x62, 0x1a, 0x3e, 0x4c, 0x14, 0xd3, 0x5e, 0xb2, 0x7d, 0x56, 0xbc, 0x27, 0x82, 0x60, 0xe3, + 0xae, 0x1f, 0x9b, 0xaa, 0x2b, 0x95, 0x49, 0x73, 0xe1, 0x92, 0x79, 0x91, 0x38, 0x6c, 0x19, + 0x0e, 0xa9, 0xe2, 0x8d, 0x66, 0xc7, 0x5a, 0xf5, 0x1c, 0x80, 0x99, 0xbe, 0x4e, 0x41, 0xf0, + 0xe8, 0xa6, 0x20, 0xab, 0x87, 0xc8, 0x1e, 0xa0, 0x59, 0x7b, 0x0c, 0xc3, 0x3c, 0x61, 0xcc, + 0x40, 0x9e, 0x06, 0x52, 0x1b, 0x32, 0x8c, 0x12, 0x93, 0xbf, 0xef, 0x3b, 0x25, 0x0d, 0xc2, + 0x88, 0xd1, 0xe0, 0x07, 0x2d, 0x70, 0xc6, 0x29, 0x6a, 0x4d, 0x47, 0x26, 0xa3, 0xe4, 0x8b, + 0xf6, 0x97, 0x2c, 0x5d, 0x3d, 0xd7, 0x96, 0x28, 0x02, 0x08, 0x30, 0xa7, 0x22, 0xc9, 0x65, + 0xf8, 0xb7, 0xb4, 0x8a, 0xca, 0xb9, 0xf2, 0xd0, 0x17, 0xff, 0x46, 0xfb, 0x9a, 0xba, 0x8f, + 0xb6, 0x69, 0x68, 0x8e, 0x21, 0x6f, 0xc4, 0xcb, 0xb3, 0xce, 0x51, 0xd4, 0x81, 0x00, 0x2e, + 0x9c, 0x74, 0x63, 0x45, 0xd9, 0x16, 0x35, 0x5f, 0xed, 0x78, 0x9f, 0x01, 0x48, 0x04, 0xc1, + 0x33, 0xd6, 0x4f, 0x94, 0xde, 0x31, 0x9d, 0x0a, 0xac, 0x18, 0x4b, 0xcd, 0x98, 0xb8, 0x37, + 0xa2, 0x83, 0xec, 0x03, 0xd8, 0xda, 0xe5, 0x7a, 0x6b, 0x53, 0xd5, 0x15, 0xa4, 0x43, 0xe9, + 0x90, 0x67, 0x58, 0xc0, 0xa5, 0xfa, 0x2a, 0xb1, 0x75, 0x50, 0x39, 0x5c, 0xe6, 0xdc, 0x89, + 0xfc, 0xcf, 0xfe, 0xf9, 0x57, 0x54, 0x64, 0xa8, 0xee, 0x23, 0x0b, 0xf1, 0xea, 0xfd, 0xdb, + 0xbd, 0x09, 0xb5, 0x5b, 0x05, 0x86, 0x13, 0xf3, 0x24, 0xc5, 0x3f, 0x44, 0x72, 0x7c, 0x7e, + 0x36}; + + for(size_t i = 0; i < length; i++) { + data[i] = lut[data[i]]; + } +} + +static void protocol_gallagher_decode(ProtocolGallagher* protocol) { + bit_lib_remove_bit_every_nth(protocol->encoded_data, 16, 9 * 8, 9); + protocol_gallagher_descramble(protocol->encoded_data + 2, 8); + + // Region code + bit_lib_set_bits(protocol->data, 0, (protocol->encoded_data[5] & 0x1E) >> 1, 4); + + // Issue Level + bit_lib_set_bits(protocol->data, 4, (protocol->encoded_data[9] & 0x0F), 4); + + // Facility Code + uint32_t fc = (protocol->encoded_data[7] & 0x0F) << 12 | protocol->encoded_data[3] << 4 | + ((protocol->encoded_data[9] >> 4) & 0x0F); + protocol->data[3] = (uint8_t)fc; + protocol->data[2] = (uint8_t)(fc >>= 8); + protocol->data[1] = (uint8_t)(fc >>= 8); + + // Card Number + uint32_t card = protocol->encoded_data[2] << 16 | (protocol->encoded_data[6] & 0x1F) << 11 | + protocol->encoded_data[4] << 3 | (protocol->encoded_data[5] & 0xE0) >> 5; + protocol->data[7] = (uint8_t)card; + protocol->data[6] = (uint8_t)(card >>= 8); + protocol->data[5] = (uint8_t)(card >>= 8); + protocol->data[4] = (uint8_t)(card >>= 8); +} + +static bool protocol_gallagher_can_be_decoded(ProtocolGallagher* protocol) { + // check 16 bits preamble + if(bit_lib_get_bits_16(protocol->encoded_data, 0, 16) != 0b0111111111101010) return false; + + // check next 16 bits preamble + if(bit_lib_get_bits_16(protocol->encoded_data, 96, 16) != 0b0111111111101010) return false; + + uint8_t checksum_arr[8] = {0}; + for(int i = 0, pos = 0; i < 8; i++) { + // Following the preamble, every 9th bit is a checksum-bit for the preceding byte + pos = 16 + (9 * i); + checksum_arr[i] = bit_lib_get_bits(protocol->encoded_data, pos, 8); + } + uint8_t crc = bit_lib_get_bits(protocol->encoded_data, 16 + (9 * 8), 8); + uint8_t calc_crc = bit_lib_crc8(checksum_arr, 8, 0x7, 0x2c, false, false, 0x00); + + // crc + if(crc != calc_crc) return false; + + return true; +} + +void protocol_gallagher_decoder_start(ProtocolGallagher* protocol) { + memset(protocol->encoded_data, 0, GALLAGHER_ENCODED_BYTE_FULL_SIZE); + manchester_advance( + protocol->decoder_manchester_state, + ManchesterEventReset, + &protocol->decoder_manchester_state, + NULL); +}; + +bool protocol_gallagher_decoder_feed(ProtocolGallagher* protocol, bool level, uint32_t duration) { + bool result = false; + + ManchesterEvent event = ManchesterEventReset; + + if(duration > GALLAGHER_READ_SHORT_TIME_LOW && duration < GALLAGHER_READ_SHORT_TIME_HIGH) { + if(!level) { + event = ManchesterEventShortHigh; + } else { + event = ManchesterEventShortLow; + } + } else if(duration > GALLAGHER_READ_LONG_TIME_LOW && duration < GALLAGHER_READ_LONG_TIME_HIGH) { + if(!level) { + event = ManchesterEventLongHigh; + } else { + event = ManchesterEventLongLow; + } + } + + if(event != ManchesterEventReset) { + bool data; + bool data_ok = manchester_advance( + protocol->decoder_manchester_state, event, &protocol->decoder_manchester_state, &data); + + if(data_ok) { + bit_lib_push_bit(protocol->encoded_data, GALLAGHER_ENCODED_BYTE_FULL_SIZE, data); + + if(protocol_gallagher_can_be_decoded(protocol)) { + protocol_gallagher_decode(protocol); + result = true; + } + } + } + + return result; +}; + +bool protocol_gallagher_encoder_start(ProtocolGallagher* protocol) { + // Preamble + bit_lib_set_bits(protocol->encoded_data, 0, 0b01111111, 8); + bit_lib_set_bits(protocol->encoded_data, 8, 0b11101010, 8); + + uint8_t rc = bit_lib_get_bits(protocol->data, 0, 4); + uint8_t il = bit_lib_get_bits(protocol->data, 4, 4); + uint32_t fc = bit_lib_get_bits_32(protocol->data, 8, 24); + uint32_t cn = bit_lib_get_bits_32(protocol->data, 32, 32); + + uint8_t payload[8] = {0}; + payload[0] = (cn & 0xffffff) >> 16; + payload[1] = (fc & 0xfff) >> 4; + payload[2] = (cn & 0x7ff) >> 3; + payload[3] = (cn & 0x7) << 5 | (rc & 0xf) << 1; + payload[4] = (cn & 0xffff) >> 11; + payload[5] = (fc & 0xffff) >> 12; + payload[6] = 0; + payload[7] = (fc & 0xf) << 4 | (il & 0xf); + + // Gallagher scramble + protocol_gallagher_scramble(payload, 8); + + for(int i = 0; i < 8; i++) { + // data byte + bit_lib_set_bits(protocol->encoded_data, 16 + (i * 9), payload[i], 8); + + // every byte is followed by a bit which is the inverse of the last bit + bit_lib_set_bit(protocol->encoded_data, 16 + (i * 9) + 8, !(payload[i] & 0x1)); + } + + // checksum + uint8_t crc = bit_lib_crc8(payload, 8, 0x7, 0x2c, false, false, 0x00); + bit_lib_set_bits(protocol->encoded_data, 16 + (9 * 8), crc, 8); + + return true; +}; + +LevelDuration protocol_gallagher_encoder_yield(ProtocolGallagher* protocol) { + bool level = bit_lib_get_bit(protocol->encoded_data, protocol->encoded_data_index); + uint32_t duration = GALLAGHER_CLOCK_PER_BIT / 2; + + if(protocol->encoded_polarity) { + protocol->encoded_polarity = false; + } else { + level = !level; + + protocol->encoded_polarity = true; + bit_lib_increment_index(protocol->encoded_data_index, GALLAGHER_ENCODED_BIT_SIZE); + } + + return level_duration_make(level, duration); +}; + +bool protocol_gallagher_write_data(ProtocolGallagher* protocol, void* data) { + LFRFIDWriteRequest* request = (LFRFIDWriteRequest*)data; + bool result = false; + + protocol_gallagher_encoder_start(protocol); + + if(request->write_type == LFRFIDWriteTypeT5577) { + request->t5577.block[0] = + (LFRFID_T5577_MODULATION_MANCHESTER | LFRFID_T5577_BITRATE_RF_32 | + (3 << LFRFID_T5577_MAXBLOCK_SHIFT)); + request->t5577.block[1] = bit_lib_get_bits_32(protocol->encoded_data, 0, 32); + request->t5577.block[2] = bit_lib_get_bits_32(protocol->encoded_data, 32, 32); + request->t5577.block[3] = bit_lib_get_bits_32(protocol->encoded_data, 64, 32); + request->t5577.blocks_to_write = 4; + result = true; + } + return result; +}; + +void protocol_gallagher_render_data(ProtocolGallagher* protocol, string_t result) { + UNUSED(protocol); + uint8_t rc = bit_lib_get_bits(protocol->data, 0, 4); + uint8_t il = bit_lib_get_bits(protocol->data, 4, 4); + uint32_t fc = bit_lib_get_bits_32(protocol->data, 8, 24); + uint32_t card_id = bit_lib_get_bits_32(protocol->data, 32, 32); + + string_cat_printf(result, "Region: %u, Issue Level: %u\r\n", rc, il); + string_cat_printf(result, "FC: %u, C: %lu\r\n", fc, card_id); +}; + +const ProtocolBase protocol_gallagher = { + .name = "Gallagher", + .manufacturer = "Gallagher", + .data_size = GALLAGHER_DECODED_DATA_SIZE, + .features = LFRFIDFeatureASK, + .validate_count = 3, + .alloc = (ProtocolAlloc)protocol_gallagher_alloc, + .free = (ProtocolFree)protocol_gallagher_free, + .get_data = (ProtocolGetData)protocol_gallagher_get_data, + .decoder = + { + .start = (ProtocolDecoderStart)protocol_gallagher_decoder_start, + .feed = (ProtocolDecoderFeed)protocol_gallagher_decoder_feed, + }, + .encoder = + { + .start = (ProtocolEncoderStart)protocol_gallagher_encoder_start, + .yield = (ProtocolEncoderYield)protocol_gallagher_encoder_yield, + }, + .render_data = (ProtocolRenderData)protocol_gallagher_render_data, + .render_brief_data = (ProtocolRenderData)protocol_gallagher_render_data, + .write_data = (ProtocolWriteData)protocol_gallagher_write_data, +}; \ No newline at end of file diff --git a/lib/lfrfid/protocols/protocol_gallagher.h b/lib/lfrfid/protocols/protocol_gallagher.h new file mode 100644 index 00000000000..2d922f605e4 --- /dev/null +++ b/lib/lfrfid/protocols/protocol_gallagher.h @@ -0,0 +1,4 @@ +#pragma once +#include + +extern const ProtocolBase protocol_gallagher; \ No newline at end of file From 1d787e6da819e3edd63a613203c7a7999459374f Mon Sep 17 00:00:00 2001 From: Sebastian Mauer Date: Fri, 2 Sep 2022 12:36:13 +0100 Subject: [PATCH 038/824] Add support for Keri tags (#1689) Co-authored-by: SG --- lib/lfrfid/protocols/lfrfid_protocols.c | 2 + lib/lfrfid/protocols/lfrfid_protocols.h | 1 + lib/lfrfid/protocols/protocol_keri.c | 264 ++++++++++++++++++++++++ lib/lfrfid/protocols/protocol_keri.h | 4 + 4 files changed, 271 insertions(+) create mode 100644 lib/lfrfid/protocols/protocol_keri.c create mode 100644 lib/lfrfid/protocols/protocol_keri.h diff --git a/lib/lfrfid/protocols/lfrfid_protocols.c b/lib/lfrfid/protocols/lfrfid_protocols.c index 3d142969bcf..bd29bd8e09e 100644 --- a/lib/lfrfid/protocols/lfrfid_protocols.c +++ b/lib/lfrfid/protocols/lfrfid_protocols.c @@ -13,6 +13,7 @@ #include "protocol_jablotron.h" #include "protocol_paradox.h" #include "protocol_pac_stanley.h" +#include "protocol_keri.h" #include "protocol_gallagher.h" const ProtocolBase* lfrfid_protocols[] = { @@ -30,5 +31,6 @@ const ProtocolBase* lfrfid_protocols[] = { [LFRFIDProtocolJablotron] = &protocol_jablotron, [LFRFIDProtocolParadox] = &protocol_paradox, [LFRFIDProtocolPACStanley] = &protocol_pac_stanley, + [LFRFIDProtocolKeri] = &protocol_keri, [LFRFIDProtocolGallagher] = &protocol_gallagher, }; \ No newline at end of file diff --git a/lib/lfrfid/protocols/lfrfid_protocols.h b/lib/lfrfid/protocols/lfrfid_protocols.h index 20b784dcca2..26065c9aa69 100644 --- a/lib/lfrfid/protocols/lfrfid_protocols.h +++ b/lib/lfrfid/protocols/lfrfid_protocols.h @@ -22,6 +22,7 @@ typedef enum { LFRFIDProtocolJablotron, LFRFIDProtocolParadox, LFRFIDProtocolPACStanley, + LFRFIDProtocolKeri, LFRFIDProtocolGallagher, LFRFIDProtocolMax, } LFRFIDProtocol; diff --git a/lib/lfrfid/protocols/protocol_keri.c b/lib/lfrfid/protocols/protocol_keri.c new file mode 100644 index 00000000000..7e66255463b --- /dev/null +++ b/lib/lfrfid/protocols/protocol_keri.c @@ -0,0 +1,264 @@ +#include +#include +#include +#include "lfrfid_protocols.h" + +#define KERI_PREAMBLE_BIT_SIZE (33) +#define KERI_PREAMBLE_DATA_SIZE (5) + +#define KERI_ENCODED_BIT_SIZE (64) +#define KERI_ENCODED_DATA_SIZE (((KERI_ENCODED_BIT_SIZE) / 8) + KERI_PREAMBLE_DATA_SIZE) +#define KERI_ENCODED_DATA_LAST ((KERI_ENCODED_BIT_SIZE) / 8) + +#define KERI_DECODED_BIT_SIZE (28) +#define KERI_DECODED_DATA_SIZE (4) + +#define KERI_US_PER_BIT (255) +#define KERI_ENCODER_PULSES_PER_BIT (16) + +typedef struct { + uint8_t data_index; + uint8_t bit_clock_index; + bool last_bit; + bool current_polarity; + bool pulse_phase; +} ProtocolKeriEncoder; + +typedef struct { + uint8_t encoded_data[KERI_ENCODED_DATA_SIZE]; + uint8_t negative_encoded_data[KERI_ENCODED_DATA_SIZE]; + uint8_t corrupted_encoded_data[KERI_ENCODED_DATA_SIZE]; + uint8_t corrupted_negative_encoded_data[KERI_ENCODED_DATA_SIZE]; + + uint8_t data[KERI_DECODED_DATA_SIZE]; + ProtocolKeriEncoder encoder; +} ProtocolKeri; + +ProtocolKeri* protocol_keri_alloc(void) { + ProtocolKeri* protocol = malloc(sizeof(ProtocolKeri)); + return protocol; +}; + +void protocol_keri_free(ProtocolKeri* protocol) { + free(protocol); +}; + +uint8_t* protocol_keri_get_data(ProtocolKeri* protocol) { + return protocol->data; +}; + +void protocol_keri_decoder_start(ProtocolKeri* protocol) { + memset(protocol->encoded_data, 0, KERI_ENCODED_DATA_SIZE); + memset(protocol->negative_encoded_data, 0, KERI_ENCODED_DATA_SIZE); + memset(protocol->corrupted_encoded_data, 0, KERI_ENCODED_DATA_SIZE); + memset(protocol->corrupted_negative_encoded_data, 0, KERI_ENCODED_DATA_SIZE); +}; + +static bool protocol_keri_check_preamble(uint8_t* data, size_t bit_index) { + // Preamble 11100000 00000000 00000000 00000000 1 + if(*(uint32_t*)&data[bit_index / 8] != 0b00000000000000000000000011100000) return false; + if(bit_lib_get_bit(data, bit_index + 32) != 1) return false; + return true; +} + +static bool protocol_keri_can_be_decoded(uint8_t* data) { + if(!protocol_keri_check_preamble(data, 0)) return false; + if(!protocol_keri_check_preamble(data, 64)) return false; + ///if(bit_lib_get_bit(data, 61) != 0) return false; + //if(bit_lib_get_bit(data, 60) != 0) return false; + return true; +} + +static bool protocol_keri_decoder_feed_internal(bool polarity, uint32_t time, uint8_t* data) { + time += (KERI_US_PER_BIT / 2); + + size_t bit_count = (time / KERI_US_PER_BIT); + bool result = false; + + if(bit_count < KERI_ENCODED_BIT_SIZE) { + for(size_t i = 0; i < bit_count; i++) { + bit_lib_push_bit(data, KERI_ENCODED_DATA_SIZE, polarity); + if(protocol_keri_can_be_decoded(data)) { + result = true; + break; + } + } + } + + return result; +} + +static void protocol_keri_descramble(uint32_t* fc, uint32_t* cn, uint32_t* internal_id) { + const uint8_t card_to_id[] = {255, 255, 255, 255, 13, 12, 20, 5, 16, 6, 21, + 17, 8, 255, 0, 7, 10, 15, 255, 11, 4, 1, + 255, 18, 255, 19, 2, 14, 3, 9, 255, 255}; + + const uint8_t card_to_fc[] = {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 0, 255, 255, 255, 255, 2, 255, 255, 255, + 3, 255, 4, 255, 255, 255, 255, 255, 1, 255}; + + *fc = 0; + *cn = 0; + for(uint8_t card_idx = 0; card_idx < 32; card_idx++) { + bool bit = (*internal_id >> card_idx) & 1; + // Card ID + if(card_to_id[card_idx] < 32) { + *cn = *cn | (bit << card_to_id[card_idx]); + } + // Card FC + if(card_to_fc[card_idx] < 32) { + *fc = *fc | (bit << card_to_fc[card_idx]); + } + } +} + +static void protocol_keri_decoder_save(uint8_t* data_to, const uint8_t* data_from) { + uint32_t id = bit_lib_get_bits_32(data_from, 32, 32); + data_to[3] = (uint8_t)id; + data_to[2] = (uint8_t)(id >>= 8); + data_to[1] = (uint8_t)(id >>= 8); + data_to[0] = (uint8_t)(id >>= 8); +} + +bool protocol_keri_decoder_feed(ProtocolKeri* protocol, bool level, uint32_t duration) { + bool result = false; + + if(duration > (KERI_US_PER_BIT / 2)) { + if(protocol_keri_decoder_feed_internal(level, duration, protocol->encoded_data)) { + protocol_keri_decoder_save(protocol->data, protocol->encoded_data); + result = true; + return result; + } + + if(protocol_keri_decoder_feed_internal(!level, duration, protocol->negative_encoded_data)) { + protocol_keri_decoder_save(protocol->data, protocol->negative_encoded_data); + result = true; + return result; + } + } + + if(duration > (KERI_US_PER_BIT / 4)) { + // Try to decode wrong phase synced data + if(level) { + duration += 120; + } else { + if(duration > 120) { + duration -= 120; + } + } + + if(protocol_keri_decoder_feed_internal(level, duration, protocol->corrupted_encoded_data)) { + protocol_keri_decoder_save(protocol->data, protocol->corrupted_encoded_data); + + result = true; + return result; + } + + if(protocol_keri_decoder_feed_internal( + !level, duration, protocol->corrupted_negative_encoded_data)) { + protocol_keri_decoder_save(protocol->data, protocol->corrupted_negative_encoded_data); + + result = true; + return result; + } + } + + return result; +}; + +bool protocol_keri_encoder_start(ProtocolKeri* protocol) { + memset(protocol->encoded_data, 0, KERI_ENCODED_DATA_SIZE); + *(uint32_t*)&protocol->encoded_data[0] = 0b00000000000000000000000011100000; + bit_lib_copy_bits(protocol->encoded_data, 32, 32, protocol->data, 0); + + protocol->encoder.last_bit = + bit_lib_get_bit(protocol->encoded_data, KERI_ENCODED_BIT_SIZE - 1); + protocol->encoder.data_index = 0; + protocol->encoder.current_polarity = true; + protocol->encoder.pulse_phase = true; + protocol->encoder.bit_clock_index = 0; + + return true; +}; + +LevelDuration protocol_keri_encoder_yield(ProtocolKeri* protocol) { + LevelDuration level_duration; + ProtocolKeriEncoder* encoder = &protocol->encoder; + + if(encoder->pulse_phase) { + level_duration = level_duration_make(encoder->current_polarity, 1); + encoder->pulse_phase = false; + } else { + level_duration = level_duration_make(!encoder->current_polarity, 1); + encoder->pulse_phase = true; + + encoder->bit_clock_index++; + if(encoder->bit_clock_index >= KERI_ENCODER_PULSES_PER_BIT) { + encoder->bit_clock_index = 0; + + bool current_bit = bit_lib_get_bit(protocol->encoded_data, encoder->data_index); + + if(current_bit != encoder->last_bit) { + encoder->current_polarity = !encoder->current_polarity; + } + + encoder->last_bit = current_bit; + + bit_lib_increment_index(encoder->data_index, KERI_ENCODED_BIT_SIZE); + } + } + + return level_duration; +}; + +void protocol_keri_render_data(ProtocolKeri* protocol, string_t result) { + uint32_t data = bit_lib_get_bits_32(protocol->data, 0, 32); + uint32_t internal_id = data & 0x7FFFFFFF; + uint32_t fc = 0; + uint32_t cn = 0; + protocol_keri_descramble(&fc, &cn, &data); + string_printf(result, "Internal ID: %u\r\nFC: %u, Card: %u\r\n", internal_id, fc, cn); +} + +bool protocol_keri_write_data(ProtocolKeri* protocol, void* data) { + LFRFIDWriteRequest* request = (LFRFIDWriteRequest*)data; + bool result = false; + + protocol_keri_encoder_start(protocol); + + if(request->write_type == LFRFIDWriteTypeT5577) { + request->t5577.block[0] = LFRFID_T5577_TESTMODE_DISABLED | LFRFID_T5577_X_MODE | + LFRFID_T5577_MODULATION_PSK1 | LFRFID_T5577_PSKCF_RF_2 | + (2 << LFRFID_T5577_MAXBLOCK_SHIFT); + request->t5577.block[0] |= 0xF << 18; + request->t5577.block[1] = bit_lib_get_bits_32(protocol->encoded_data, 0, 32); + request->t5577.block[2] = bit_lib_get_bits_32(protocol->encoded_data, 32, 32); + request->t5577.blocks_to_write = 3; + result = true; + } + return result; +}; + +const ProtocolBase protocol_keri = { + .name = "Keri", + .manufacturer = "Keri", + .data_size = KERI_DECODED_DATA_SIZE, + .features = LFRFIDFeaturePSK, + .validate_count = 6, + .alloc = (ProtocolAlloc)protocol_keri_alloc, + .free = (ProtocolFree)protocol_keri_free, + .get_data = (ProtocolGetData)protocol_keri_get_data, + .decoder = + { + .start = (ProtocolDecoderStart)protocol_keri_decoder_start, + .feed = (ProtocolDecoderFeed)protocol_keri_decoder_feed, + }, + .encoder = + { + .start = (ProtocolEncoderStart)protocol_keri_encoder_start, + .yield = (ProtocolEncoderYield)protocol_keri_encoder_yield, + }, + .render_data = (ProtocolRenderData)protocol_keri_render_data, + .render_brief_data = (ProtocolRenderData)protocol_keri_render_data, + .write_data = (ProtocolWriteData)protocol_keri_write_data, +}; \ No newline at end of file diff --git a/lib/lfrfid/protocols/protocol_keri.h b/lib/lfrfid/protocols/protocol_keri.h new file mode 100644 index 00000000000..4cf5f370c9c --- /dev/null +++ b/lib/lfrfid/protocols/protocol_keri.h @@ -0,0 +1,4 @@ +#pragma once +#include + +extern const ProtocolBase protocol_keri; From a3932cfa6d7c5309d5b814d879802714536da270 Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Sat, 3 Sep 2022 10:19:01 +0400 Subject: [PATCH 039/824] [FL-2787] SubGhz: add protocol Clemsa, fix decoder BETT (#1696) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * SubGhz: add protocol Clemsa * SubGhz: fix decoder BETT protocol Co-authored-by: あく --- applications/unit_tests/subghz/subghz_test.c | 17 +- assets/unit_tests/subghz/clemsa.sub | 7 + assets/unit_tests/subghz/clemsa_raw.sub | 14 + assets/unit_tests/subghz/test_random_raw.sub | 6 + lib/subghz/protocols/bett.c | 24 +- lib/subghz/protocols/clemsa.c | 365 +++++++++++++++++++ lib/subghz/protocols/clemsa.h | 107 ++++++ lib/subghz/protocols/registry.c | 1 + lib/subghz/protocols/registry.h | 1 + 9 files changed, 522 insertions(+), 20 deletions(-) create mode 100644 assets/unit_tests/subghz/clemsa.sub create mode 100644 assets/unit_tests/subghz/clemsa_raw.sub create mode 100644 lib/subghz/protocols/clemsa.c create mode 100644 lib/subghz/protocols/clemsa.h diff --git a/applications/unit_tests/subghz/subghz_test.c b/applications/unit_tests/subghz/subghz_test.c index 6345b758fd9..df269ed046f 100644 --- a/applications/unit_tests/subghz/subghz_test.c +++ b/applications/unit_tests/subghz/subghz_test.c @@ -13,7 +13,7 @@ #define CAME_ATOMO_DIR_NAME EXT_PATH("subghz/assets/came_atomo") #define NICE_FLOR_S_DIR_NAME EXT_PATH("subghz/assets/nice_flor_s") #define TEST_RANDOM_DIR_NAME EXT_PATH("unit_tests/subghz/test_random_raw.sub") -#define TEST_RANDOM_COUNT_PARSE 208 +#define TEST_RANDOM_COUNT_PARSE 232 #define TEST_TIMEOUT 10000 static SubGhzEnvironment* environment_handler; @@ -427,6 +427,13 @@ MU_TEST(subghz_decoder_intertechno_v3_test) { "Test decoder " SUBGHZ_PROTOCOL_INTERTECHNO_V3_NAME " error\r\n"); } +MU_TEST(subghz_decoder_clemsa_test) { + mu_assert( + subghz_decoder_test( + EXT_PATH("unit_tests/subghz/clemsa_raw.sub"), SUBGHZ_PROTOCOL_CLEMSA_NAME), + "Test decoder " SUBGHZ_PROTOCOL_CLEMSA_NAME " error\r\n"); +} + //test encoders MU_TEST(subghz_encoder_princeton_test) { mu_assert( @@ -542,6 +549,12 @@ MU_TEST(subghz_encoder_intertechno_v3_test) { "Test encoder " SUBGHZ_PROTOCOL_INTERTECHNO_V3_NAME " error\r\n"); } +MU_TEST(subghz_encoder_clemsa_test) { + mu_assert( + subghz_encoder_test(EXT_PATH("unit_tests/subghz/clemsa.sub")), + "Test encoder " SUBGHZ_PROTOCOL_CLEMSA_NAME " error\r\n"); +} + MU_TEST(subghz_random_test) { mu_assert(subghz_decode_random_test(TEST_RANDOM_DIR_NAME), "Random test error\r\n"); } @@ -581,6 +594,7 @@ MU_TEST_SUITE(subghz) { MU_RUN_TEST(subghz_decoder_honeywell_wdb_test); MU_RUN_TEST(subghz_decoder_magellen_test); MU_RUN_TEST(subghz_decoder_intertechno_v3_test); + MU_RUN_TEST(subghz_decoder_clemsa_test); MU_RUN_TEST(subghz_encoder_princeton_test); MU_RUN_TEST(subghz_encoder_came_test); @@ -601,6 +615,7 @@ MU_TEST_SUITE(subghz) { MU_RUN_TEST(subghz_encoder_honeywell_wdb_test); MU_RUN_TEST(subghz_encoder_magellen_test); MU_RUN_TEST(subghz_encoder_intertechno_v3_test); + MU_RUN_TEST(subghz_encoder_clemsa_test); MU_RUN_TEST(subghz_random_test); subghz_test_deinit(); diff --git a/assets/unit_tests/subghz/clemsa.sub b/assets/unit_tests/subghz/clemsa.sub new file mode 100644 index 00000000000..b07d031f090 --- /dev/null +++ b/assets/unit_tests/subghz/clemsa.sub @@ -0,0 +1,7 @@ +Filetype: Flipper SubGhz Key File +Version: 1 +Frequency: 433920000 +Preset: FuriHalSubGhzPresetOok650Async +Protocol: Clemsa +Bit: 18 +Key: 00 00 00 00 00 02 FC AA diff --git a/assets/unit_tests/subghz/clemsa_raw.sub b/assets/unit_tests/subghz/clemsa_raw.sub new file mode 100644 index 00000000000..5f86de98c61 --- /dev/null +++ b/assets/unit_tests/subghz/clemsa_raw.sub @@ -0,0 +1,14 @@ +Filetype: Flipper SubGhz RAW File +Version: 1 +Frequency: 433920000 +Preset: FuriHalSubGhzPresetOok650Async +Protocol: RAW +RAW_Data: -334 10811 -4320 65 -100 65 -698 10157 -7550 65 -200 165 -166 133 -66 531 -66 331 -102 197 -132 133 -298 99 -132 263 -200 261 -988 99 -262 131 -296 97 -132 229 -100 459 -100 131 -132 393 -100 1119 -100 8325 -6376 133 -366 131 -562 65 -1034 131 -198 563 -168 365 -66 229 -332 297 -100 231 -166 429 -132 295 -166 97 -100 195 -724 97 -132 97 -1088 163 -200 1651 -100 2885 -8520 365 -166 97 -1558 163 -198 163 -132 465 -134 131 -66 267 -198 65 -232 299 -66 165 -166 65 -498 165 -100 233 -200 133 -166 131 -68 821 -100 263 -66 7633 -7610 1259 -200 99 -98 165 -1196 99 -132 263 -266 99 -200 463 -66 627 -66 1981 -98 7801 -4004 97 -628 65 -264 133 -1088 163 -134 131 -928 297 -166 133 -134 131 -266 297 -596 229 -164 427 -564 197 -166 265 -198 65 -100 559 -6708 131 -132 131 -430 527 -200 367 -66 263 -198 233 -98 299 -68 365 -296 465 -132 855 -66 857 -98 4741 -8312 99 -364 163 -200 133 -1428 529 -132 65 -166 595 -1392 97 -100 97 -132 99 -264 199 -828 99 -398 297 -66 233 -98 861 -100 663 -100 2357 -100 5075 -4640 131 -3312 231 -100 363 -132 99 -296 99 -132 165 -132 363 -98 165 -130 65 -98 165 -132 163 -130 63 -164 297 -198 9769 -3852 133 -98 67 -1226 329 -526 99 -164 295 -496 1713 -196 1681 -130 131 -132 5497 -7230 65 -1150 133 -330 259 -66 329 -100 97 -988 165 -134 197 -166 67 -100 361 -68 461 -100 231 -132 165 -66 365 -264 231 -100 99 -98 265 -696 99 -166 199 -100 101 -962 7101 -6484 363 -760 363 -132 265 -134 431 -264 329 -66 427 -330 263 -164 593 -130 231 -130 627 -66 399 -432 329 -526 131 -100 591 -166 9305 -4044 65 -3532 361 -98 163 -66 461 -264 197 -98 391 -66 329 -132 165 -136 463 -66 529 -166 131 -100 199 -264 133 -66 361 -98 689 -66 229 -198 627 -66 297 -100 261 -66 1685 -134 7883 -3932 229 -728 133 -98 133 -862 65 -132 99 -498 297 -166 133 -332 197 -132 693 -198 97 -656 1087 -64 6209 -6164 131 -266 99 -496 165 -432 67 -100 97 -330 821 -98 361 -100 493 -164 133 -66 197 -200 431 -66 65 -66 399 -66 331 -200 199 -402 131 -664 10955 -5314 65 -262 97 -198 97 -724 99 -196 65 -592 327 -66 625 -262 131 -66 197 -64 427 -132 65 -628 265 -332 329 -368 789 -66 8809 -6238 129 -328 295 -232 363 -98 431 -100 199 -98 261 -530 561 -592 263 -132 1645 -5358 65 -764 65 -330 165 -1158 197 -432 265 -98 397 -166 463 -498 561 -398 199 -66 199 -66 3479 +RAW_Data: -12124 165 -626 65 -890 229 -362 1329 -66 2187 -98 2081 -66 725 -134 3309 -9856 165 -166 263 -198 65 -960 653 -66 261 -66 821 -66 12463 -4032 97 -166 97 -924 65 -464 97 -68 163 -198 165 -100 263 -232 97 -366 633 -12244 65 -332 231 -200 197 -134 197 -234 2457 -134 399 -132 923 -198 197 -64 331 -132 295 -66 11377 -3896 163 -98 65 -194 131 -132 231 -366 131 -132 65 -1050 197 -200 299 -66 3007 -100 11685 -6172 133 -1154 491 -100 293 -200 65 -98 429 -266 463 -64 797 -98 265 -266 397 -132 1227 -66 8485 -4224 97 -166 65 -100 199 -2706 65 -66 263 -98 299 -298 231 -100 499 -66 97 -134 295 -66 431 -198 565 -66 2093 -100 533 -4056 65 -1482 229 -1160 165 -168 299 -166 459 -66 165 -134 99 -100 497 -166 397 -200 431 -200 65 -66 661 -164 529 -66 4671 -8442 131 -100 65 -66 165 -530 131 -132 597 -66 963 -488 275 -2806 2641 -438 2639 -420 2691 -412 2677 -404 2669 -416 2693 -416 361 -2730 367 -2724 2665 -422 355 -2726 2687 -394 399 -2698 2697 -390 375 -2732 2701 -388 355 -21244 2697 -416 377 -2722 2675 -416 2689 -388 2707 -396 2679 -414 2689 -392 2717 -388 375 -2706 383 -2732 2673 -396 365 -2734 2695 -386 377 -2728 2689 -384 389 -2730 2671 -386 379 -21252 2693 -416 347 -2748 2687 -396 2707 -384 2701 -388 2679 -412 2701 -418 2669 -404 363 -2726 385 -2698 2699 -416 355 -2706 2689 -416 365 -2736 2673 -386 415 -2692 2709 -378 361 -21270 2685 -452 311 -2774 2637 -442 2665 -418 2661 -448 2653 -408 2703 -384 2687 -410 365 -2738 355 -2742 2689 -382 371 -2738 2677 -384 415 -2700 2673 -410 363 -2730 2701 -386 357 -21270 2705 -398 361 -2740 2689 -386 2679 -414 2687 -410 2673 -418 2697 -386 2681 -412 383 -2702 395 -2706 2703 -380 385 -2696 2709 -418 355 -2702 2699 -418 361 -2700 2717 -386 375 -21242 2697 -418 355 -2748 2695 -382 2683 -410 2703 -380 2707 -384 2705 -404 2675 -416 383 -2700 359 -2728 2695 -382 385 -2728 2687 -416 357 -2708 2705 -386 389 -2700 2717 -388 373 -21234 2721 -418 353 -2724 2667 -416 2705 -380 2693 -386 2711 -384 2691 -422 2699 -398 365 -2726 385 -2700 2703 -378 361 -2728 2697 -420 357 -2704 2711 -388 377 -2734 2667 -406 363 -21260 2699 -418 361 -2702 2711 -380 2695 -416 2689 -402 2687 -384 2695 -416 2677 -408 361 -2732 385 -2704 2707 -414 325 -2730 2695 -418 361 -2728 2669 -414 385 -2702 2701 -414 355 -21246 2705 -378 379 -2728 2693 -384 2715 -380 2717 -386 2695 -390 2709 -388 2683 -420 355 -2730 385 -2700 2685 -416 347 -2748 2683 -396 365 -2740 2667 -416 385 -2696 2693 -414 349 -21260 2689 -452 319 -2762 2631 -476 2627 -430 2689 +RAW_Data: -416 2667 -412 2671 -416 2689 -424 347 -2732 353 -2738 2669 -410 363 -2728 2689 -410 349 -2742 2677 -386 415 -2696 2705 -380 361 -21254 2717 -386 413 -2700 2693 -380 2695 -416 2691 -398 2679 -416 2699 -384 2709 -382 367 -2726 361 -2730 2707 -382 377 -2728 2675 -386 411 -2684 2713 -414 357 -2704 2707 -388 357 -21276 2699 -382 385 -2724 2689 -416 2661 -416 2685 -384 2723 -388 2703 -400 2677 -416 385 -2696 357 -2724 2713 -384 375 -2726 2673 -420 355 -2728 2685 -396 397 -2688 2697 -408 363 -21252 2667 -484 311 -2766 2635 -476 2637 -420 2665 -448 2651 -408 2701 -384 2697 -406 355 -2758 327 -2736 2685 -420 361 -2728 2683 -384 385 -2726 2685 -414 357 -2708 2711 -388 375 -21246 2689 -448 323 -2750 2695 -378 2715 -386 2687 -392 2711 -384 2683 -416 2705 -412 327 -2732 387 -2728 2679 -416 357 -2738 2659 -418 363 -2732 2677 -386 413 -2700 2695 -380 391 -21258 2679 -406 383 -2706 2695 -384 2703 -418 2679 -404 2705 -380 2687 -418 2669 -410 359 -2726 387 -2696 2707 -416 357 -2710 2693 -418 361 -2730 2691 -380 385 -2730 2677 -414 357 -21254 2711 -382 385 -2700 2713 -414 2667 -384 2705 -418 2675 -406 2699 -382 2689 -418 365 -2726 377 -2708 2701 -384 389 -2698 2709 -398 361 -2738 2669 -416 385 -2692 2687 -418 357 -77456 131 -398 197 -132 295 -330 97 -132 229 -164 459 -164 295 -264 393 -264 719 -64 427 -98 855 -134 395 -98 297 -164 263 -262 65 -100 63 -132 197 -328 1185 -66 9359 -6420 261 -664 131 -100 299 -134 301 -232 363 -232 299 -200 165 -166 427 -230 299 -164 361 -394 1025 -100 225 -820 165 -1248 491 -100 293 -66 261 -264 131 -98 589 -164 655 -132 427 -132 295 -164 129 -132 163 -328 263 -196 627 -566 129 -100 131 -98 2377 -130 1255 -3878 297 -232 195 -132 65 -98 165 -596 397 -266 99 -198 363 -98 923 -100 431 -66 1383 -3724 297 -166 165 -66 99 -398 265 -266 463 -232 133 -232 65 -230 65 -266 959 -200 99 -298 231 -68 65 -100 97 -398 363 -132 199 -134 133 -134 133 -266 593 -66 363 -66 827 -2374 65 -1724 399 -166 265 -100 331 -198 165 -398 233 -98 233 -66 165 -266 97 -66 231 -132 165 -298 395 -234 99 -132 65 -100 99 -132 131 -66 297 -264 197 -194 229 -530 2189 -166 9577 -3702 199 -98 465 -398 97 -134 395 -132 429 -100 529 -68 263 -132 265 -368 263 -860 97 -100 163 -196 427 -98 163 -166 327 -98 493 -166 327 -98 233 -5094 99 -198 97 -100 65 -1250 131 -560 855 -66 855 -262 859 -164 10219 -7528 761 -66 1121 -100 429 -298 331 -232 263 -166 261 -166 265 -100 1427 -98 9787 -6682 131 -564 429 +RAW_Data: -66 529 -66 2519 -66 265 -68 10101 -1794 65 -1890 393 -562 97 -132 197 -98 493 -330 97 -164 97 -230 327 -326 99 -100 97 -164 65 -132 293 -98 297 -166 161 -130 297 -230 1391 -68 11185 -3800 229 -230 297 -66 65 -198 65 -466 99 -464 99 -430 67 -698 295 -132 165 -164 1095 -66 299 -66 1321 -264 12675 -66 99 -166 229 -134 65 -330 165 -164 65 -890 131 -830 67 -66 1157 -100 167 -168 265 -66 827 -66 2047 -100 261 -594 2279 -134 10701 -3890 163 -1384 67 -98 99 -1322 99 -98 65 -398 823 -66 65 -68 927 -100 495 -132 593 -100 165 -198 1387 -1022 131 -728 99 -662 97 -462 495 -200 829 -330 563 -100 297 -330 65 -598 165 -592 295 -166 131 -764 165 -164 565 -66 131 -166 165 -66 9675 -5052 165 -2878 199 -66 265 -432 265 -66 267 -898 163 -132 231 -198 229 -164 97 -100 4445 -66 7853 -636 199 -662 265 -298 233 -1428 331 -134 1791 -66 1649 -66 297 -100 361 -198 559 -98 363 -200 1315 -66 265 -98 1049 -132 1647 -66 265 -822 295 -526 131 -1712 199 -166 231 -200 165 -66 265 -166 97 -132 163 -164 395 -630 495 -168 297 -298 229 -266 629 -200 133 -132 133 -166 65 -132 99 -100 131 -66 67 -98 133 -496 1391 -98 1751 -164 359 -132 97 -164 263 -64 691 -66 199 -66 293 -98 589 -198 11299 -3968 65 -68 65 -2702 65 -1186 927 -166 65 -66 429 -134 197 -134 529 -200 67 -66 231 -100 2151 -4014 97 -1486 99 -464 65 -330 129 -330 331 -134 599 -66 497 -200 165 -66 661 -166 6881 -8830 295 -100 197 -232 725 -134 299 -166 229 -166 525 -198 295 -66 459 -66 329 -230 595 -98 299 -132 329 -66 99 -98 163 -134 229 -100 8345 -6726 131 -132 295 -66 1579 -66 329 -98 501 -132 231 -66 491 -298 331 -266 363 -132 1193 -168 8847 -4194 199 -828 65 -100 195 -262 197 -298 65 -898 65 -132 629 -66 229 -100 291 -100 623 -66 295 -66 461 -132 529 -632 597 -132 65 -100 97 -134 297 -100 297 -166 397 -168 527 -134 9603 -3850 99 -200 67 -896 959 -198 165 -100 229 -266 531 -64 165 -132 163 -296 3715 -11994 165 -1492 429 -68 263 -100 265 -330 199 -64 495 -132 363 -66 63 -166 297 -398 65 -100 231 -332 199 -100 7683 -4916 65 -1294 297 -1022 1325 -166 393 -132 165 -498 1255 -134 197 -198 427 -164 329 -132 631 -594 199 -196 99 -100 265 -134 1457 -100 3649 -8592 67 -268 131 -332 99 -100 65 -760 101 -198 297 -168 199 -132 369 -100 97 -132 99 -232 397 -198 99 -134 97 -100 231 -332 131 -796 329 -266 263 +RAW_Data: -100 10841 -4030 163 -164 197 -398 195 -592 65 -132 63 -430 295 -298 263 -200 3517 -132 3763 -12296 99 -330 361 -98 99 -200 65 -430 165 -166 2327 -100 4051 -66 9653 -3478 197 -66 163 -198 167 -66 65 -598 165 -298 131 -666 199 -198 299 -298 165 -200 565 -66 797 -98 1125 -98 825 -100 4113 -6956 65 -5536 165 -266 99 -232 461 -198 65 -200 1989 -66 295 -66 723 -66 65 -98 329 -98 955 -66 559 -232 331 -66 10851 -1048 65 -3748 65 -498 99 -1392 99 -794 529 -98 331 -98 397 -164 363 -394 331 -266 299 -230 165 -66 3001 -568 197 -2872 2579 -468 2637 -472 2599 -488 2647 -426 2653 -448 2665 -392 379 -2734 381 -2700 2691 -388 377 -2732 2669 -420 355 -2726 2687 -394 403 -2706 2687 -388 377 -21248 2717 -388 377 -2738 2659 -408 2703 -382 2689 -416 2679 -408 2701 -382 2687 -418 365 -2736 365 -2722 2689 -384 391 -2696 2707 -386 379 -2734 2665 -410 361 -2726 2703 -418 357 -21246 2679 -466 297 -2768 2657 -448 2627 -434 2669 -450 2653 -416 2673 -408 2697 -386 383 -2728 369 -2706 2701 -382 387 -2726 2687 -418 357 -2708 2693 -418 361 -2702 2709 -396 401 -21232 2689 -406 361 -2736 2695 -386 2695 -406 2695 -382 2687 -418 2675 -410 2693 -414 375 -2692 389 -2706 2701 -404 363 -2724 2695 -388 389 -2702 2719 -358 405 -2704 2701 -402 363 -21262 2677 -414 367 -2738 2677 -386 2693 -420 2701 -400 2677 -386 2695 -420 2669 -430 369 -2718 353 -2730 2673 -412 361 -2734 2691 -420 357 -2698 2701 -394 401 -2702 2687 -424 347 -21244 2701 -418 365 -2726 2703 -382 2697 -420 2675 -400 2685 -384 2721 -398 2667 -418 355 -2744 343 -2722 2703 -420 353 -2724 2689 -396 363 -2736 2687 -390 377 -2730 2697 -386 357 -21274 2683 -414 375 -2726 2667 -420 2703 -398 2677 -388 2695 -420 2699 -398 2671 -384 415 -2698 357 -2738 2695 -382 383 -2724 2685 -416 357 -2706 2707 -384 391 -2726 2671 -384 415 -21238 2651 -476 293 -2776 2653 -462 2641 -446 2661 -422 2663 -418 2689 -412 2683 -414 357 -2706 385 -2698 2715 -378 379 -2710 2719 -388 377 -2708 2695 -406 361 -2724 2689 -416 361 -21244 2703 -386 413 -2698 2697 -414 2689 -384 2685 -386 2719 -378 2701 -416 2689 -386 377 -2728 387 -2700 2673 -410 361 -2730 2695 -420 357 -2732 2679 -386 377 -2734 2699 -378 361 -21262 2697 -392 405 -2702 2687 -406 2703 -382 2703 -418 2671 -406 2677 -416 2695 -386 387 -2700 381 -2704 2695 -418 357 -2712 2721 -388 375 -2702 2693 -408 363 -2730 2693 -420 355 -21248 2653 -494 289 -91206 131 -132 97 -232 559 -132 591 -98 691 -66 131 -130 297 -66 231 -66 331 -66 433 -100 499 -132 231 -166 197 -134 593 -100 11707 -4456 133 -200 131 +RAW_Data: -66 133 -66 97 -166 561 -100 895 -132 1323 -66 10873 -3752 99 -722 229 -394 97 -66 99 -98 99 -328 297 -328 265 -298 3089 -132 10573 -1460 133 -432 99 -232 99 -132 333 -232 731 -164 65 -166 165 -132 131 -330 65 -98 131 -596 65 -198 133 -98 397 -568 65 -132 1157 -166 195 -130 131 -64 99 -66 63 -198 265 -98 297 -66 63 -166 295 -100 1747 -232 6099 -11348 199 -528 297 -266 97 -598 99 -198 231 -64 4433 -334 65 -298 65 -3284 67 -530 97 -432 133 -2356 493 -68 231 -168 297 -266 427 -100 559 -98 229 -460 197 -66 261 -132 65 -98 565 -132 231 -66 497 -100 3491 -12356 65 -660 197 -198 165 -132 331 -134 65 -98 2651 -134 4531 -10850 65 -1322 263 -68 431 -232 165 -134 165 -202 231 -300 5625 -66 6951 -8162 65 -398 99 -596 65 -132 461 -598 429 -132 97 -132 463 -232 229 -98 329 -100 397 -100 363 -100 231 -200 163 -200 961 -66 693 -100 397 -134 10601 -3872 263 -100 165 -100 131 -198 99 -696 233 -1524 331 -132 131 -164 229 -132 493 -98 631 -134 231 -100 595 -66 295 -66 5965 -8248 99 -296 99 -98 397 -66 65 -924 229 -398 299 -98 1425 -130 565 -198 827 -262 429 -598 725 -704 729 -12290 131 -98 99 -98 65 -100 163 -164 65 -494 231 -100 97 -100 863 -66 1751 -3948 165 -100 195 -66 165 -296 65 -2042 99 -200 495 -132 557 -100 827 -98 167 -66 433 -100 661 -164 689 -98 10803 -3906 231 -296 295 -232 99 -234 131 -332 395 -266 1283 -164 755 -466 397 -164 335 -66 1355 -14376 557 -66 331 -68 431 -134 599 -364 229 -100 763 -98 265 -132 525 -166 99 -396 495 -98 3867 -134 595 -168 865 -166 503 -200 467 -134 8145 -458 235 -794 599 -458 265 -436 231 -426 333 -368 299 -730 689 -360 327 -370 363 -326 367 -668 733 -328 363 -302 397 -328 371 -296 393 -666 725 -622 795 -634 433 -264 1023 -228 5041 -762 585 -466 233 -826 235 -470 631 -368 299 -402 299 -726 361 -328 331 -370 363 -332 701 -298 401 -692 369 -302 759 -268 461 -236 435 -622 423 -260 465 -266 719 -13608 65 -624 197 -558 921 -164 1315 -134 465 -134 263 -100 295 -132 293 -66 329 -98 197 -132 9977 -5036 197 -798 333 -828 295 -100 197 -100 165 -66 665 -100 763 -300 297 -166 165 -98 823 -8348 229 -100 427 -196 263 -624 197 -134 797 -100 263 -68 529 -132 233 -134 165 -264 131 -132 559 -66 263 -228 927 -132 731 -102 1061 -66 863 -8206 131 -332 299 -166 461 -100 99 -66 429 -66 3271 -98 465 -100 401 -232 331 -66 397 -430 10341 +RAW_Data: -5434 65 -298 133 -132 131 -68 231 -200 661 -132 9517 -424 97 -1456 99 -1694 393 -100 131 -560 131 -196 197 -298 65 -428 229 -196 297 -266 131 -166 2435 -66 10161 -11230 65 -1320 131 -298 265 -532 231 -200 1291 -68 631 -66 12645 -4048 133 -66 67 -132 167 -266 163 -66 397 -132 197 -132 299 -98 197 -198 2903 -66 2361 -66 9627 -3588 197 -332 165 -68 331 -68 197 -132 99 -100 663 -66 363 -230 231 -166 131 -100 201 -298 163 -132 133 -202 363 -300 397 -102 263 -100 165 -66 1221 -66 1479 -132 165 -98 229 -12976 263 -66 363 -134 231 -66 629 -132 327 -100 97 -130 99 -164 227 -64 297 -132 397 -164 425 -198 97 -198 99 -66 365 -164 199 -102 97 -66 1817 -13524 231 -134 16907 -4086 233 -630 65 -396 201 -66 165 -198 67 -198 99 -664 2117 -166 12473 -446 2649 -440 2661 -420 2651 -422 2681 -418 2703 -400 365 -2724 387 -2696 2695 -414 357 -2704 2707 -386 389 -2700 2687 -392 405 -2706 2695 -402 363 -21268 2707 -388 377 -2706 2691 -404 2699 -382 2717 -382 2707 -378 2693 -416 2687 -396 363 -2736 355 -2748 2659 -416 365 -2708 2715 -388 377 -2708 2697 -404 363 -2730 2673 -420 355 -21268 2655 -460 319 -2766 2663 -448 2631 -436 2665 -418 2683 -410 2681 -416 2701 -386 383 -2700 375 -2744 2669 -416 353 -2730 2685 -416 357 -2708 2721 -380 369 -2724 2697 -382 385 -21260 2701 -418 353 -2720 2673 -418 2675 -408 2693 -384 2715 -386 2717 -386 2691 -404 363 -2732 387 -2702 2669 -412 359 -2736 2699 -380 381 -2728 2675 -416 381 -2720 2675 -414 347 -21280 2685 -390 377 -2724 2689 -416 2673 -408 2705 -382 2695 -410 2689 -414 2661 -418 385 -2704 369 -2704 2693 -416 375 -2726 2661 -420 355 -2728 2711 -388 375 -2702 2691 -410 363 -21252 2659 -488 287 -2794 2651 -448 2629 -436 2671 -416 2695 -416 2663 -406 2699 -384 383 -2730 367 -2702 2695 -418 385 -2702 2685 -412 349 -2744 2693 -366 389 -2714 2693 -394 381 -21266 2685 -418 363 -2730 2683 -382 2693 -418 2675 -410 2699 -384 2719 -382 2707 -380 359 -2734 387 -2704 2709 -380 361 -2732 2699 -418 357 -2728 2667 -416 383 -2696 2709 -380 391 -21228 2685 -458 307 -2800 2647 -412 2659 -432 2667 -416 2695 -416 2675 -406 2675 -416 383 -2700 361 -2730 2687 -414 375 -2696 2701 -420 353 -2720 2711 -382 367 -2728 2675 -416 385 -21222 2735 -386 355 -2744 2687 -396 2679 -418 2701 -386 2705 -382 2681 -410 2697 -384 385 -2736 365 -2704 2715 -384 377 -2696 2697 -416 349 -2722 2707 -386 379 -2732 2671 -410 361 -21258 2681 -464 297 -2796 2629 -456 2655 -420 2661 -448 2663 -404 2695 -382 2715 -380 371 -2740 355 -2744 2679 -384 391 -2728 2675 -388 379 +RAW_Data: -2728 2695 -414 357 -2704 2705 -418 357 -21262 2673 -416 383 -2696 2709 -380 2703 -384 2699 -418 2671 -408 2695 -382 2713 -386 379 -2730 357 -2732 2695 -384 383 -2730 2679 -416 357 -2708 2701 -410 349 -2736 2697 -382 385 -21252 2669 -478 289 -2790 2647 -426 2651 -444 2653 -430 2659 -418 2695 -414 2681 -402 349 -2738 383 -2722 2677 -414 347 -2744 2691 -382 369 -2730 2691 -384 383 -2734 2679 -414 347 -21264 2705 -386 379 -2736 2667 -410 2695 -382 2715 -380 2709 -420 2665 -392 2713 -382 383 -2730 365 -2728 2665 -418 383 -2696 2693 -418 357 -2710 2711 -380 375 -2718 2701 -416 357 -21238 2677 -484 311 -2766 2635 -444 2657 -420 2663 -422 2695 -416 2667 -428 2675 -396 363 -73890 133 -98 131 -132 129 -658 99 -66 853 -100 63 -100 361 -98 1589 -66 1231 -132 65 -100 297 -198 65 -132 265 -66 9857 -4672 165 -1030 97 -1394 65 -200 2687 -68 6873 -8336 99 -1156 97 -66 163 -232 163 -262 197 -132 295 -132 263 -166 953 -100 263 -130 393 -164 295 -64 329 -66 393 -164 823 -130 165 -66 6133 -8436 165 -164 265 -266 65 -362 197 -696 3181 -132 363 -98 65 -166 131 -66 399 -132 663 -396 329 -66 7335 -7578 497 -230 627 -264 99 -366 99 -132 131 -134 265 -498 163 -100 1323 -66 265 -66 1129 -100 399 -132 365 -100 795 -68 397 -98 597 -364 297 -132 361 -132 265 -132 8591 -4740 65 -100 131 -166 199 -1088 97 -296 99 -528 131 -98 661 -66 401 -198 1157 -166 361 -164 495 -100 165 -66 297 -100 1423 -66 3067 -5658 67 -6406 197 -1092 65 -530 659 -68 265 -100 991 -68 231 -230 297 -66 327 -66 131 -132 659 -134 131 -100 1183 -132 263 -98 621 -66 2075 -6976 65 -5138 67 -132 129 -664 67 -132 165 -100 331 -466 231 -68 467 -98 563 -66 231 -100 531 -66 465 -66 1023 -166 297 -134 3409 -12290 67 -164 99 -532 133 -166 263 -66 231 -66 721 -64 131 -68 959 -134 495 -100 299 -98 497 -98 365 -100 397 -232 297 -98 531 -66 3029 -12216 265 -132 99 -364 199 -234 131 -66 431 -166 333 -166 397 -132 327 -100 395 -66 197 -132 395 -66 527 -98 295 -100 97 -98 789 -132 363 -132 297 -200 2815 -4914 65 -6620 65 -462 65 -134 297 -66 497 -264 231 -198 2773 -134 365 -100 831 -166 131 -100 297 -132 861 -132 299 -100 561 -66 1381 -6946 65 -5516 231 -266 97 -1362 1093 -68 1621 -134 165 -332 297 -98 361 -228 97 -132 797 -98 3487 -13224 229 -164 65 -132 913 -66 1123 -98 527 -134 929 -98 723 -100 12259 -270 165 -132 67 -132 165 -1326 99 -98 65 -1194 431 -66 695 -66 733 -134 197 +RAW_Data: -134 10801 -166 67 -6130 133 -198 231 -334 365 -98 229 -132 165 -68 231 -166 14501 -524 65 -328 131 -498 129 -1288 65 -494 163 -64 165 -66 527 -132 131 -132 1019 -198 129 -166 393 -198 65 -164 6411 -66 3255 -10642 65 -1320 165 -164 493 -492 559 -264 2555 -66 695 -66 1657 -164 855 -66 4001 -10526 97 -596 133 -298 67 -264 65 -300 65 -100 263 -166 231 -134 99 -100 2703 -68 13643 -4922 297 -100 65 -232 133 -198 331 -300 231 -66 331 -100 12047 -3872 97 -196 65 -494 329 -66 65 -890 97 -98 229 -164 195 -596 797 -66 861 -132 65 -66 231 -100 565 -66 65 -66 1297 -132 265 -66 363 -134 265 -364 297 -164 299 -134 297 -134 495 -98 11309 -3790 131 -1380 65 -758 65 -164 129 -460 65 -360 199 -100 563 -68 497 -198 363 -266 263 -100 165 -66 697 -66 1933 -13594 65 -762 1223 -132 1119 -196 361 -134 131 -100 793 -166 695 -68 231 -68 463 -66 11727 -4204 363 -264 131 -132 133 -1124 97 -100 163 -100 327 -100 331 -198 397 -66 397 -100 395 -100 163 -66 197 -564 1059 -7962 65 -100 65 -198 129 -362 99 -394 197 -296 495 -100 1357 -68 459 -66 593 -66 265 -68 301 -132 465 -66 231 -200 397 -66 397 -232 199 -298 12077 -4350 231 -796 363 -198 133 -264 65 -1132 597 -332 3295 -100 755 -98 231 -164 97 -264 459 -166 759 -164 3265 -12138 99 -232 99 -1228 1025 -100 393 -66 531 -132 693 -132 1063 -66 427 -64 297 -294 229 -98 9723 -5404 67 -466 99 -796 267 -98 201 -100 167 -264 461 -98 1415 -66 861 -66 267 -66 331 -134 1663 -66 2089 -7012 65 -100 101 -4804 431 -728 99 -100 65 -100 995 -134 165 -66 929 -100 65 -66 927 -100 1093 -168 99 -100 497 -66 665 -200 6517 -8312 165 -66 129 -66 559 -166 99 -430 65 -398 67 -66 593 -198 459 -132 261 -132 263 -130 723 -66 459 -100 325 -166 67 -198 559 -66 493 -66 11475 -3896 99 -266 99 -66 197 -1092 129 -198 361 -166 163 -98 263 -196 759 -100 265 -100 365 -630 4635 -12748 65 -1712 461 -100 497 -66 395 -98 265 -98 229 -164 529 -132 297 -66 565 -132 987 -132 8665 -2820 2265 -450 313 -2774 2643 -442 325 -2772 2665 -416 359 -2734 2667 -386 379 -21274 2657 -474 293 -2810 2619 -466 2613 -476 2629 -452 2663 -388 2683 -418 2705 -400 365 -2722 387 -2700 2697 -380 361 -2732 2691 -418 361 -2732 2667 -416 383 -2698 2697 -416 357 -21238 2715 -384 383 -2732 2685 -416 2667 -416 2695 -398 2671 -418 2687 -390 2713 -382 383 -2730 365 -2728 2661 -416 379 -2716 2685 -384 379 -2720 2703 -378 401 -2718 2671 diff --git a/assets/unit_tests/subghz/test_random_raw.sub b/assets/unit_tests/subghz/test_random_raw.sub index 06dfb9b4b6a..928838d3c8f 100644 --- a/assets/unit_tests/subghz/test_random_raw.sub +++ b/assets/unit_tests/subghz/test_random_raw.sub @@ -139,3 +139,9 @@ RAW_Data: 277 -244 275 -1360 275 -1340 277 -246 277 -236 261 -1380 275 -1346 277 RAW_Data: 29262 361 -68 2635 -66 24113 -66 1131 -100 4157 -66 26253 -130 621 -18438 99 -298 231 -66 197 -496 753 -230 7503 -16526 65 -396 65 -296 99 -196 293 -64 429 -132 397 -66 329 -66 37701 -66 13475 -100 54967 -64 18209 -18340 97 -462 197 -98 587 -232 97 -100 259 -98 197 -262 297 -64 557 -100 599 -100 333 -234 42493 -13212 6449 -206 173 -214 217 -176 195 -218 181 -218 181 -182 217 -182 217 -176 187 -214 215 -180 217 -182 217 -182 217 -178 185 -424 1177 -388 387 -240 381 -214 181 -398 211 -380 419 -176 217 -394 203 -394 205 -380 189 -402 421 -168 219 -398 393 -190 191 -398 205 -406 185 -402 381 -212 215 -362 241 -378 421 -176 377 -218 197 -378 427 -210 393 -172 429 -172 397 -212 217 -362 389 -228 197 -372 417 -204 395 -210 181 -398 391 -192 201 -216888 761 -200 299 -166 695 -132 15435 -66 5611 -66 21049 -66 4947 -66 2355 -66 1921 -100 2223 -100 2107 -100 397 -98 3643 -66 5301 -98 14205 -66 37371 -246 175 -216 179 -216 177 -224 149 -246 159 -228 181 -212 201 -204 159 -244 151 -254 169 -214 181 -210 197 -182 181 -454 1141 -444 357 -228 361 -246 177 -396 209 -412 367 -188 187 -434 201 -394 185 -406 193 -402 377 -238 181 -386 381 -234 153 -424 205 -412 157 -412 383 -240 181 -398 203 -392 385 -236 371 -212 179 -400 383 -240 359 -210 375 -220 381 -246 175 -394 383 -240 181 -398 363 -222 379 -246 175 -394 383 -204 217 -182856 99 -66 99 -300 133 -402 65 -198 99 -328 65 -100 491 -164 593 -100 3547 -64 361 -66 789 -68 2521 -66 22883 -66 2659 -98 3309 -130 3789 -100 9689 -17178 99 -1388 65 -266 197 -100 131 -134 99 -232 627 -130 233 -66 1949 -100 14567 -198 165 -256 181 -208 159 -214 183 -220 163 -244 149 -246 159 -236 181 -254 141 -226 151 -246 157 -228 181 -212 201 -400 1163 -428 379 -230 355 -244 177 -396 207 -412 367 -222 157 -418 189 -410 207 -412 171 -430 357 -226 165 -404 413 -204 181 -428 173 -428 169 -426 353 -236 173 -414 173 -408 381 -244 337 -222 201 -408 397 -208 393 -204 395 -208 359 -246 177 -394 387 -200 205 -380 415 -202 395 -208 181 -432 357 -226 169 -195084 65 -300 763 -66 297 -364 593 -68 2883 -66 1357 -68 363 -98 3841 -66 3119 -66 5153 -66 4023 -268 143 -246 133 -290 141 -250 139 -254 141 -226 181 -248 137 -254 143 -252 139 -252 143 -230 181 -250 139 -254 145 -436 1135 -448 349 -240 347 -254 157 -434 167 -426 377 -226 157 -434 167 -426 155 -440 163 -434 375 -206 215 -380 381 -234 153 RAW_Data: -424 205 -412 159 -412 381 -240 181 -398 203 -392 387 -236 369 -212 179 -400 383 -240 359 -244 339 -222 381 -246 175 -394 383 -240 181 -398 363 -222 381 -244 175 -392 383 -240 181 -184002 99 -360 63 -330 65 -132 129 -232 97 -198 295 -328 6031 -66 831 -132 3417 -66 2187 -64 2183 -100 6535 -66 1127 -66 2569 -66 2031 -66 2271 -66 2183 -66 3815 -66 3803 -66 493 -66 1909 -66 1627 -98 4805 -17512 67 -2164 131 -498 265 -430 163 -98 97 -64 99 -230 99 -100 229 -230 165 -196 63 -132 99 -66 927 -66 14955 -66 19621 -68 2627 -66 14305 -68 23247 -66 2891 -66 3941 -66 3021 -212 173 -242 181 -218 181 -214 181 -208 157 -250 141 -248 181 -218 179 -214 179 -210 159 -250 179 -214 181 -218 181 -404 1153 -404 389 -244 375 -192 181 -436 161 -414 383 -240 181 -398 205 -392 201 -394 205 -394 365 -246 177 -396 383 -204 217 -398 171 -426 167 -428 353 -242 173 -420 173 -408 373 -220 403 -208 175 -422 381 -194 399 -228 357 -246 355 -210 215 -400 387 -208 181 -398 391 -226 353 -246 177 -398 383 -204 217 -185098 163 -166 525 -98 293 -100 63 -66 229 -66 1183 -66 1507 -66 3089 -98 30187 -66 2847 -19112 133 -364 131 -394 97 -166 295 -66 229 -164 227 -66 263 -130 623 -98 2071 -66 493 -66 787 -98 691 -64 10249 -132 3879 -66 1949 -66 3453 -198 23157 -66 2845 -100 1193 -66 1587 -100 3797 -98 3187 -100 3319 -66 22119 -98 5513 -226 155 -244 153 -256 131 -248 151 -246 159 -262 121 -274 133 -272 127 -244 153 -254 167 -248 145 -244 133 -252 177 -398 1169 -418 381 -238 359 -242 141 -430 169 -426 357 -274 139 -422 171 -442 173 -428 167 -426 353 -236 171 -416 379 -226 149 -436 161 -438 173 -406 381 -234 153 -424 205 -380 389 -244 359 -206 215 -384 381 -246 335 -224 383 -246 355 -244 179 -404 385 -206 181 -432 359 -226 355 -246 175 -398 383 -240 181 -179760 97 -168 727 -66 97 -332 1389 -66 2793 -66 4955 -100 12453 -100 2425 -66 21965 -66 3809 -68 1683 -66 3095 -66 2153 -64 999 -208 173 -220 181 -214 191 -196 181 -212 183 -220 191 -212 181 -214 191 -198 181 -212 181 -222 191 -212 181 -214 191 -416 1167 -424 369 -220 373 -210 209 -390 207 -376 403 -190 187 -418 189 -408 209 -412 173 -428 357 -226 169 -404 399 -208 179 -412 209 -396 169 -428 355 -230 201 -378 205 -406 381 -244 339 -222 193 -400 413 -204 393 -208 347 -220 401 -210 175 -422 383 -202 217 -398 365 -222 377 -246 175 -390 385 -204 217 -179890 165 -1552 131 -164 65 RAW_Data: -1448 361 -17056 131 -134 233 -1462 131 -166 953 -100 261 -164 5077 -272 137 -268 143 -252 141 -248 143 -246 159 -252 141 -244 143 -290 107 -276 145 -244 131 -250 179 -248 143 -252 141 -414 1165 -424 373 -236 359 -242 145 -434 169 -428 355 -230 169 -442 173 -434 157 -406 193 -402 379 -238 181 -422 335 -252 157 -434 167 -428 185 -406 381 -208 211 -390 207 -410 381 -200 373 -236 171 -414 383 -202 393 -210 379 -220 373 -208 211 -390 383 -204 217 -398 365 -220 379 -244 175 -394 381 -240 181 -161030 97 -166 167 -930 593 -2670 1091 -132 229 -98 461 -164 1649 -66 6311 -100 44723 -16832 67 -2656 131 -132 99 -132 263 -100 399 -68 893 -18950 99 -164 165 -198 525 -998 335 -66 565 -66 1057 -17880 97 -360 195 -262 131 -332 625 -98 197 -230 455 -98 9343 -16498 67 -368 131 -598 65 -1066 333 -300 789 -130 757 -66 87207 -16554 97 -3520 97 -786 591 -64 461 -98 21495 -66 24811 -18448 131 -296 491 -134 163 -760 1091 -230 893 -66 927 -68 4581 -68 32965 -64 45217 -17292 131 -1684 231 -132 327 -64 163 -330 263 -230 25751 +RAW_Data: -66 529 -66 2519 -66 265 -68 10101 -1794 65 -1890 393 -562 97 -132 197 -98 493 -330 97 -164 97 -230 327 -326 99 -100 97 -164 65 -132 293 -98 297 -166 161 -130 297 -230 1391 -68 11185 -3800 229 -230 297 -66 65 -198 65 -466 99 -464 99 -430 67 -698 295 -132 165 -164 1095 -66 299 -66 1321 -264 12675 -66 99 -166 229 -134 65 -330 165 -164 65 -890 131 -830 67 -66 1157 -100 167 -168 265 -66 827 -66 2047 -100 261 -594 2279 -134 10701 -3890 163 -1384 67 -98 99 -1322 99 -98 65 -398 823 -66 65 -68 927 -100 495 -132 593 -100 165 -198 1387 -1022 131 -728 99 -662 97 -462 495 -200 829 -330 563 -100 297 -330 65 -598 165 -592 295 -166 131 -764 165 -164 565 -66 131 -166 165 -66 9675 -5052 165 -2878 199 -66 265 -432 265 -66 267 -898 163 -132 231 -198 229 -164 97 -100 4445 -66 7853 -636 199 -662 265 -298 233 -1428 331 -134 1791 -66 1649 -66 297 -100 361 -198 559 -98 363 -200 1315 -66 265 -98 1049 -132 1647 -66 265 -822 295 -526 131 -1712 199 -166 231 -200 165 -66 265 -166 97 -132 163 -164 395 -630 495 -168 297 -298 229 -266 629 -200 133 -132 133 -166 65 -132 99 -100 131 -66 67 -98 133 -496 1391 -98 1751 -164 359 -132 97 -164 263 -64 691 -66 199 -66 293 -98 589 -198 11299 -3968 65 -68 65 -2702 65 -1186 927 -166 65 -66 429 -134 197 -134 529 -200 67 -66 231 -100 2151 -4014 97 -1486 99 -464 65 -330 129 -330 331 -134 599 -66 497 -200 165 -66 661 -166 6881 -8830 295 -100 197 -232 725 -134 299 -166 229 -166 525 -198 295 -66 459 -66 329 -230 595 -98 299 -132 329 -66 99 -98 163 -134 229 -100 8345 -6726 131 -132 295 -66 1579 -66 329 -98 501 -132 231 -66 491 -298 331 -266 363 -132 1193 -168 8847 -4194 199 -828 65 -100 195 -262 197 -298 65 -898 65 -132 629 -66 229 -100 291 -100 623 -66 295 -66 461 -132 529 -632 597 -132 65 -100 97 -134 297 -100 297 -166 397 -168 527 -134 9603 -3850 99 -200 67 -896 959 -198 165 -100 229 -266 531 -64 165 -132 163 -296 3715 -11994 165 -1492 429 -68 263 -100 265 -330 199 -64 495 -132 363 -66 63 -166 297 -398 65 -100 231 -332 199 -100 7683 -4916 65 -1294 297 -1022 1325 -166 393 -132 165 -498 1255 -134 197 -198 427 -164 329 -132 631 -594 199 -196 99 -100 265 -134 1457 -100 3649 -8592 67 -268 131 -332 99 -100 65 -760 101 -198 297 -168 199 -132 369 -100 97 -132 99 -232 397 -198 99 -134 97 -100 231 -332 131 -796 329 -266 263 +RAW_Data: -100 10841 -4030 163 -164 197 -398 195 -592 65 -132 63 -430 295 -298 263 -200 3517 -132 3763 -12296 99 -330 361 -98 99 -200 65 -430 165 -166 2327 -100 4051 -66 9653 -3478 197 -66 163 -198 167 -66 65 -598 165 -298 131 -666 199 -198 299 -298 165 -200 565 -66 797 -98 1125 -98 825 -100 4113 -6956 65 -5536 165 -266 99 -232 461 -198 65 -200 1989 -66 295 -66 723 -66 65 -98 329 -98 955 -66 559 -232 331 -66 10851 -1048 65 -3748 65 -498 99 -1392 99 -794 529 -98 331 -98 397 -164 363 -394 331 -266 299 -230 165 -66 3001 -568 197 -2872 2579 -468 2637 -472 2599 -488 2647 -426 2653 -448 2665 -392 379 -2734 381 -2700 2691 -388 377 -2732 2669 -420 355 -2726 2687 -394 403 -2706 2687 -388 377 -21248 2717 -388 377 -2738 2659 -408 2703 -382 2689 -416 2679 -408 2701 -382 2687 -418 365 -2736 365 -2722 2689 -384 391 -2696 2707 -386 379 -2734 2665 -410 361 -2726 2703 -418 357 -21246 2679 -466 297 -2768 2657 -448 2627 -434 2669 -450 2653 -416 2673 -408 2697 -386 383 -2728 369 -2706 2701 -382 387 -2726 2687 -418 357 -2708 2693 -418 361 -2702 2709 -396 401 -21232 2689 -406 361 -2736 2695 -386 2695 -406 2695 -382 2687 -418 2675 -410 2693 -414 375 -2692 389 -2706 2701 -404 363 -2724 2695 -388 389 -2702 2719 -358 405 -2704 2701 -402 363 -21262 2677 -414 367 -2738 2677 -386 2693 -420 2701 -400 2677 -386 2695 -420 2669 -430 369 -2718 353 -2730 2673 -412 361 -2734 2691 -420 357 -2698 2701 -394 401 -2702 2687 -424 347 -21244 2701 -418 365 -2726 2703 -382 2697 -420 2675 -400 2685 -384 2721 -398 2667 -418 355 -2744 343 -2722 2703 -420 353 -2724 2689 -396 363 -2736 2687 -390 377 -2730 2697 -386 357 -21274 2683 -414 375 -2726 2667 -420 2703 -398 2677 -388 2695 -420 2699 -398 2671 -384 415 -2698 357 -2738 2695 -382 383 -2724 2685 -416 357 -2706 2707 -384 391 -2726 2671 -384 415 -21238 2651 -476 293 -2776 2653 -462 2641 -446 2661 -422 2663 -418 2689 -412 2683 -414 357 -2706 385 -2698 2715 -378 379 -2710 2719 -388 377 -2708 2695 -406 361 -2724 2689 -416 361 -21244 2703 -386 413 -2698 2697 -414 2689 -384 2685 -386 2719 -378 2701 -416 2689 -386 377 -2728 387 -2700 2673 -410 361 -2730 2695 -420 357 -2732 2679 -386 377 -2734 2699 -378 361 -21262 2697 -392 405 -2702 2687 -406 2703 -382 2703 -418 2671 -406 2677 -416 2695 -386 387 -2700 381 -2704 2695 -418 357 -2712 2721 -388 375 -2702 2693 -408 363 -2730 2693 -420 355 -21248 2653 -494 289 -91206 131 -132 97 -232 559 -132 591 -98 691 -66 131 -130 297 -66 231 -66 331 -66 433 -100 499 -132 231 -166 197 -134 593 -100 11707 -4456 133 -200 131 +RAW_Data: -66 133 -66 97 -166 561 -100 895 -132 1323 -66 10873 -3752 99 -722 229 -394 97 -66 99 -98 99 -328 297 -328 265 -298 3089 -132 10573 -1460 133 -432 99 -232 99 -132 333 -232 731 -164 65 -166 165 -132 131 -330 65 -98 131 -596 65 -198 133 -98 397 -568 65 -132 1157 -166 195 -130 131 -64 99 -66 63 -198 265 -98 297 -66 63 -166 295 -100 1747 -232 6099 -11348 199 -528 297 -266 97 -598 99 -198 231 -64 4433 -334 65 -298 65 -3284 67 -530 97 -432 133 -2356 493 -68 231 -168 297 -266 427 -100 559 -98 229 -460 197 -66 261 -132 65 -98 565 -132 231 -66 497 -100 3491 -12356 65 -660 197 -198 165 -132 331 -134 65 -98 2651 -134 4531 -10850 65 -1322 263 -68 431 -232 165 -134 165 -202 231 -300 5625 -66 6951 -8162 65 -398 99 -596 65 -132 461 -598 429 -132 97 -132 463 -232 229 -98 329 -100 397 -100 363 -100 231 -200 163 -200 961 -66 693 -100 397 -134 10601 -3872 263 -100 165 -100 131 -198 99 -696 233 -1524 331 -132 131 -164 229 -132 493 -98 631 -134 231 -100 595 -66 295 -66 5965 -8248 99 -296 99 -98 397 -66 65 -924 229 -398 299 -98 1425 -130 565 -198 827 -262 429 -598 725 -704 729 -12290 131 -98 99 -98 65 -100 163 -164 65 -494 231 -100 97 -100 863 -66 1751 -3948 165 -100 195 -66 165 -296 65 -2042 99 -200 495 -132 557 -100 827 -98 167 -66 433 -100 661 -164 689 -98 10803 -3906 231 -296 295 -232 99 -234 131 -332 395 -266 1283 -164 755 -466 397 -164 335 -66 1355 -14376 557 -66 331 -68 431 -134 599 -364 229 -100 763 -98 265 -132 525 -166 99 -396 495 -98 3867 -134 595 -168 865 -166 503 -200 467 -134 8145 -458 235 -794 599 -458 265 -436 231 -426 333 -368 299 -730 689 -360 327 -370 363 -326 367 -668 733 -328 363 -302 397 -328 371 -296 393 -666 725 -622 795 -634 433 -264 1023 -228 5041 -762 585 -466 233 -826 235 -470 631 -368 299 -402 299 -726 361 -328 331 -370 363 -332 701 -298 401 -692 369 -302 759 -268 461 -236 435 -622 423 -260 465 -266 719 -13608 65 -624 197 -558 921 -164 1315 -134 465 -134 263 -100 295 -132 293 -66 329 -98 197 -132 9977 -5036 197 -798 333 -828 295 -100 197 -100 165 -66 665 -100 763 -300 297 -166 165 -98 823 -8348 229 -100 427 -196 263 -624 197 -134 797 -100 263 -68 529 -132 233 -134 165 -264 131 -132 559 -66 263 -228 927 -132 731 -102 1061 -66 863 -8206 131 -332 299 -166 461 -100 99 -66 429 -66 3271 -98 465 -100 401 -232 331 -66 397 -430 10341 +RAW_Data: -5434 65 -298 133 -132 131 -68 231 -200 661 -132 9517 -424 97 -1456 99 -1694 393 -100 131 -560 131 -196 197 -298 65 -428 229 -196 297 -266 131 -166 2435 -66 10161 -11230 65 -1320 131 -298 265 -532 231 -200 1291 -68 631 -66 12645 -4048 133 -66 67 -132 167 -266 163 -66 397 -132 197 -132 299 -98 197 -198 2903 -66 2361 -66 9627 -3588 197 -332 165 -68 331 -68 197 -132 99 -100 663 -66 363 -230 231 -166 131 -100 201 -298 163 -132 133 -202 363 -300 397 -102 263 -100 165 -66 1221 -66 1479 -132 165 -98 229 -12976 263 -66 363 -134 231 -66 629 -132 327 -100 97 -130 99 -164 227 -64 297 -132 397 -164 425 -198 97 -198 99 -66 365 -164 199 -102 97 -66 1817 -13524 231 -134 16907 -4086 233 -630 65 -396 201 -66 165 -198 67 -198 99 -664 2117 -166 12473 -446 2649 -440 2661 -420 2651 -422 2681 -418 2703 -400 365 -2724 387 -2696 2695 -414 357 -2704 2707 -386 389 -2700 2687 -392 405 -2706 2695 -402 363 -21268 2707 -388 377 -2706 2691 -404 2699 -382 2717 -382 2707 -378 2693 -416 2687 -396 363 -2736 355 -2748 2659 -416 365 -2708 2715 -388 377 -2708 2697 -404 363 -2730 2673 -420 355 -21268 2655 -460 319 -2766 2663 -448 2631 -436 2665 -418 2683 -410 2681 -416 2701 -386 383 -2700 375 -2744 2669 -416 353 -2730 2685 -416 357 -2708 2721 -380 369 -2724 2697 -382 385 -21260 2701 -418 353 -2720 2673 -418 2675 -408 2693 -384 2715 -386 2717 -386 2691 -404 363 -2732 387 -2702 2669 -412 359 -2736 2699 -380 381 -2728 2675 -416 381 -2720 2675 -414 347 -21280 2685 -390 377 -2724 2689 -416 2673 -408 2705 -382 2695 -410 2689 -414 2661 -418 385 -2704 369 -2704 2693 -416 375 -2726 2661 -420 355 -2728 2711 -388 375 -2702 2691 -410 363 -21252 2659 -488 287 -2794 2651 -448 2629 -436 2671 -416 2695 -416 2663 -406 2699 -384 383 -2730 367 -2702 2695 -418 385 -2702 2685 -412 349 -2744 2693 -366 389 -2714 2693 -394 381 -21266 2685 -418 363 -2730 2683 -382 2693 -418 2675 -410 2699 -384 2719 -382 2707 -380 359 -2734 387 -2704 2709 -380 361 -2732 2699 -418 357 -2728 2667 -416 383 -2696 2709 -380 391 -21228 2685 -458 307 -2800 2647 -412 2659 -432 2667 -416 2695 -416 2675 -406 2675 -416 383 -2700 361 -2730 2687 -414 375 -2696 2701 -420 353 -2720 2711 -382 367 -2728 2675 -416 385 -21222 2735 -386 355 -2744 2687 -396 2679 -418 2701 -386 2705 -382 2681 -410 2697 -384 385 -2736 365 -2704 2715 -384 377 -2696 2697 -416 349 -2722 2707 -386 379 -2732 2671 -410 361 -21258 2681 -464 297 -2796 2629 -456 2655 -420 2661 -448 2663 -404 2695 -382 2715 -380 371 -2740 355 -2744 2679 -384 391 -2728 2675 -388 379 +RAW_Data: -2728 2695 -414 357 -2704 2705 -418 357 -21262 2673 -416 383 -2696 2709 -380 2703 -384 2699 -418 2671 -408 2695 -382 2713 -386 379 -2730 357 -2732 2695 -384 383 -2730 2679 -416 357 -2708 2701 -410 349 -2736 2697 -382 385 -21252 2669 -478 289 -2790 2647 -426 2651 -444 2653 -430 2659 -418 2695 -414 2681 -402 349 -2738 383 -2722 2677 -414 347 -2744 2691 -382 369 -2730 2691 -384 383 -2734 2679 -414 347 -21264 2705 -386 379 -2736 2667 -410 2695 -382 2715 -380 2709 -420 2665 -392 2713 -382 383 -2730 365 -2728 2665 -418 383 -2696 2693 -418 357 -2710 2711 -380 375 -2718 2701 -416 357 -21238 2677 -484 311 -2766 2635 -444 2657 -420 2663 -422 2695 -416 2667 -428 2675 -396 363 -73890 133 -98 131 -132 129 -658 99 -66 853 -100 63 -100 361 -98 1589 -66 1231 -132 65 -100 297 -198 65 -132 265 -66 9857 -4672 165 -1030 97 -1394 65 -200 2687 -68 6873 -8336 99 -1156 97 -66 163 -232 163 -262 197 -132 295 -132 263 -166 953 -100 263 -130 393 -164 295 -64 329 -66 393 -164 823 -130 165 -66 6133 -8436 165 -164 265 -266 65 -362 197 -696 3181 -132 363 -98 65 -166 131 -66 399 -132 663 -396 329 -66 7335 -7578 497 -230 627 -264 99 -366 99 -132 131 -134 265 -498 163 -100 1323 -66 265 -66 1129 -100 399 -132 365 -100 795 -68 397 -98 597 -364 297 -132 361 -132 265 -132 8591 -4740 65 -100 131 -166 199 -1088 97 -296 99 -528 131 -98 661 -66 401 -198 1157 -166 361 -164 495 -100 165 -66 297 -100 1423 -66 3067 -5658 67 -6406 197 -1092 65 -530 659 -68 265 -100 991 -68 231 -230 297 -66 327 -66 131 -132 659 -134 131 -100 1183 -132 263 -98 621 -66 2075 -6976 65 -5138 67 -132 129 -664 67 -132 165 -100 331 -466 231 -68 467 -98 563 -66 231 -100 531 -66 465 -66 1023 -166 297 -134 3409 -12290 67 -164 99 -532 133 -166 263 -66 231 -66 721 -64 131 -68 959 -134 495 -100 299 -98 497 -98 365 -100 397 -232 297 -98 531 -66 3029 -12216 265 -132 99 -364 199 -234 131 -66 431 -166 333 -166 397 -132 327 -100 395 -66 197 -132 395 -66 527 -98 295 -100 97 -98 789 -132 363 -132 297 -200 2815 -4914 65 -6620 65 -462 65 -134 297 -66 497 -264 231 -198 2773 -134 365 -100 831 -166 131 -100 297 -132 861 -132 299 -100 561 -66 1381 -6946 65 -5516 231 -266 97 -1362 1093 -68 1621 -134 165 -332 297 -98 361 -228 97 -132 797 -98 3487 -13224 229 -164 65 -132 913 -66 1123 -98 527 -134 929 -98 723 -100 12259 -270 165 -132 67 -132 165 -1326 99 -98 65 -1194 431 -66 695 -66 733 -134 197 +RAW_Data: -134 10801 -166 67 -6130 133 -198 231 -334 365 -98 229 -132 165 -68 231 -166 14501 -524 65 -328 131 -498 129 -1288 65 -494 163 -64 165 -66 527 -132 131 -132 1019 -198 129 -166 393 -198 65 -164 6411 -66 3255 -10642 65 -1320 165 -164 493 -492 559 -264 2555 -66 695 -66 1657 -164 855 -66 4001 -10526 97 -596 133 -298 67 -264 65 -300 65 -100 263 -166 231 -134 99 -100 2703 -68 13643 -4922 297 -100 65 -232 133 -198 331 -300 231 -66 331 -100 12047 -3872 97 -196 65 -494 329 -66 65 -890 97 -98 229 -164 195 -596 797 -66 861 -132 65 -66 231 -100 565 -66 65 -66 1297 -132 265 -66 363 -134 265 -364 297 -164 299 -134 297 -134 495 -98 11309 -3790 131 -1380 65 -758 65 -164 129 -460 65 -360 199 -100 563 -68 497 -198 363 -266 263 -100 165 -66 697 -66 1933 -13594 65 -762 1223 -132 1119 -196 361 -134 131 -100 793 -166 695 -68 231 -68 463 -66 11727 -4204 363 -264 131 -132 133 -1124 97 -100 163 -100 327 -100 331 -198 397 -66 397 -100 395 -100 163 -66 197 -564 1059 -7962 65 -100 65 -198 129 -362 99 -394 197 -296 495 -100 1357 -68 459 -66 593 -66 265 -68 301 -132 465 -66 231 -200 397 -66 397 -232 199 -298 12077 -4350 231 -796 363 -198 133 -264 65 -1132 597 -332 3295 -100 755 -98 231 -164 97 -264 459 -166 759 -164 3265 -12138 99 -232 99 -1228 1025 -100 393 -66 531 -132 693 -132 1063 -66 427 -64 297 -294 229 -98 9723 -5404 67 -466 99 -796 267 -98 201 -100 167 -264 461 -98 1415 -66 861 -66 267 -66 331 -134 1663 -66 2089 -7012 65 -100 101 -4804 431 -728 99 -100 65 -100 995 -134 165 -66 929 -100 65 -66 927 -100 1093 -168 99 -100 497 -66 665 -200 6517 -8312 165 -66 129 -66 559 -166 99 -430 65 -398 67 -66 593 -198 459 -132 261 -132 263 -130 723 -66 459 -100 325 -166 67 -198 559 -66 493 -66 11475 -3896 99 -266 99 -66 197 -1092 129 -198 361 -166 163 -98 263 -196 759 -100 265 -100 365 -630 4635 -12748 65 -1712 461 -100 497 -66 395 -98 265 -98 229 -164 529 -132 297 -66 565 -132 987 -132 8665 -2820 2265 -450 313 -2774 2643 -442 325 -2772 2665 -416 359 -2734 2667 -386 379 -21274 2657 -474 293 -2810 2619 -466 2613 -476 2629 -452 2663 -388 2683 -418 2705 -400 365 -2722 387 -2700 2697 -380 361 -2732 2691 -418 361 -2732 2667 -416 383 -2698 2697 -416 357 -21238 2715 -384 383 -2732 2685 -416 2667 -416 2695 -398 2671 -418 2687 -390 2713 -382 383 -2730 365 -2728 2661 -416 379 -2716 2685 -384 379 -2720 2703 -378 401 -2718 2671 diff --git a/lib/subghz/protocols/bett.c b/lib/subghz/protocols/bett.c index aca8b8c4f7e..08080dc6c9f 100644 --- a/lib/subghz/protocols/bett.c +++ b/lib/subghz/protocols/bett.c @@ -92,7 +92,7 @@ void* subghz_protocol_encoder_bett_alloc(SubGhzEnvironment* environment) { instance->generic.protocol_name = instance->base.protocol->name; instance->encoder.repeat = 10; - instance->encoder.size_upload = 52; //max 24bit*2 + 2 (start, stop) + instance->encoder.size_upload = 52; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; return instance; @@ -233,7 +233,8 @@ void subghz_protocol_decoder_bett_feed(void* context, bool level, uint32_t durat case BETTDecoderStepReset: if((!level) && (DURATION_DIFF(duration, subghz_protocol_bett_const.te_short * 44) < (subghz_protocol_bett_const.te_delta * 15))) { - //Found Preambula + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; instance->decoder.parser_step = BETTDecoderStepCheckDuration; } break; @@ -288,20 +289,6 @@ void subghz_protocol_decoder_bett_feed(void* context, bool level, uint32_t durat } } -/** - * Analysis of received data - * @param instance Pointer to a SubGhzBlockGeneric* instance - */ -static void subghz_protocol_bett_check_remote_controller(SubGhzBlockGeneric* instance) { - uint32_t code_found_reverse = - subghz_protocol_blocks_reverse_key(instance->data, instance->data_count_bit); - - instance->serial = (code_found_reverse & 0xFF) << 12 | - ((code_found_reverse >> 8) & 0xFF) << 4 | - ((code_found_reverse >> 20) & 0x0F); - instance->btn = ((code_found_reverse >> 16) & 0x0F); -} - uint8_t subghz_protocol_decoder_bett_get_hash_data(void* context) { furi_assert(context); SubGhzProtocolDecoderBETT* instance = context; @@ -339,8 +326,7 @@ bool subghz_protocol_decoder_bett_deserialize(void* context, FlipperFormat* flip void subghz_protocol_decoder_bett_get_string(void* context, string_t output) { furi_assert(context); SubGhzProtocolDecoderBETT* instance = context; - subghz_protocol_bett_check_remote_controller(&instance->generic); - uint32_t data = (uint32_t)(instance->generic.data & 0xFFFFFF); + uint32_t data = (uint32_t)(instance->generic.data & 0x3FFFF); string_cat_printf( output, "%s %dbit\r\n" @@ -350,7 +336,7 @@ void subghz_protocol_decoder_bett_get_string(void* context, string_t output) { " -: " DIP_PATTERN "\r\n", instance->generic.protocol_name, instance->generic.data_count_bit, - (uint32_t)(instance->generic.data & 0xFFFFFF), + data, SHOW_DIP_P(data, DIP_P), SHOW_DIP_P(data, DIP_O), SHOW_DIP_P(data, DIP_N)); diff --git a/lib/subghz/protocols/clemsa.c b/lib/subghz/protocols/clemsa.c new file mode 100644 index 00000000000..357a0b06ded --- /dev/null +++ b/lib/subghz/protocols/clemsa.c @@ -0,0 +1,365 @@ +#include "clemsa.h" + +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +// protocol BERNER / ELKA / TEDSEN / TELETASTER +#define TAG "SubGhzProtocolClemsa" + +#define DIP_P 0b11 //(+) +#define DIP_O 0b10 //(0) +#define DIP_N 0b00 //(-) + +#define DIP_PATTERN "%c%c%c%c%c%c%c%c" +#define SHOW_DIP_P(dip, check_dip) \ + ((((dip >> 0xE) & 0x3) == check_dip) ? '*' : '_'), \ + ((((dip >> 0xC) & 0x3) == check_dip) ? '*' : '_'), \ + ((((dip >> 0xA) & 0x3) == check_dip) ? '*' : '_'), \ + ((((dip >> 0x8) & 0x3) == check_dip) ? '*' : '_'), \ + ((((dip >> 0x6) & 0x3) == check_dip) ? '*' : '_'), \ + ((((dip >> 0x4) & 0x3) == check_dip) ? '*' : '_'), \ + ((((dip >> 0x2) & 0x3) == check_dip) ? '*' : '_'), \ + ((((dip >> 0x0) & 0x3) == check_dip) ? '*' : '_') + +static const SubGhzBlockConst subghz_protocol_clemsa_const = { + .te_short = 385, + .te_long = 2695, + .te_delta = 150, + .min_count_bit_for_found = 18, +}; + +struct SubGhzProtocolDecoderClemsa { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; +}; + +struct SubGhzProtocolEncoderClemsa { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + ClemsaDecoderStepReset = 0, + ClemsaDecoderStepSaveDuration, + ClemsaDecoderStepCheckDuration, +} ClemsaDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_clemsa_decoder = { + .alloc = subghz_protocol_decoder_clemsa_alloc, + .free = subghz_protocol_decoder_clemsa_free, + + .feed = subghz_protocol_decoder_clemsa_feed, + .reset = subghz_protocol_decoder_clemsa_reset, + + .get_hash_data = subghz_protocol_decoder_clemsa_get_hash_data, + .serialize = subghz_protocol_decoder_clemsa_serialize, + .deserialize = subghz_protocol_decoder_clemsa_deserialize, + .get_string = subghz_protocol_decoder_clemsa_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_clemsa_encoder = { + .alloc = subghz_protocol_encoder_clemsa_alloc, + .free = subghz_protocol_encoder_clemsa_free, + + .deserialize = subghz_protocol_encoder_clemsa_deserialize, + .stop = subghz_protocol_encoder_clemsa_stop, + .yield = subghz_protocol_encoder_clemsa_yield, +}; + +const SubGhzProtocol subghz_protocol_clemsa = { + .name = SUBGHZ_PROTOCOL_CLEMSA_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_clemsa_decoder, + .encoder = &subghz_protocol_clemsa_encoder, +}; + +void* subghz_protocol_encoder_clemsa_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderClemsa* instance = malloc(sizeof(SubGhzProtocolEncoderClemsa)); + + instance->base.protocol = &subghz_protocol_clemsa; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->encoder.repeat = 10; + instance->encoder.size_upload = 52; + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_running = false; + return instance; +} + +void subghz_protocol_encoder_clemsa_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderClemsa* instance = context; + free(instance->encoder.upload); + free(instance); +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderClemsa instance + * @return true On success + */ +static bool subghz_protocol_encoder_clemsa_get_upload(SubGhzProtocolEncoderClemsa* 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, "Size upload exceeds allocated encoder buffer."); + return false; + } else { + instance->encoder.size_upload = size_upload; + } + + for(uint8_t i = instance->generic.data_count_bit; i > 1; i--) { + if(bit_read(instance->generic.data, i - 1)) { + //send bit 1 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_clemsa_const.te_long); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_clemsa_const.te_short); + } else { + //send bit 0 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_clemsa_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_clemsa_const.te_long); + } + } + if(bit_read(instance->generic.data, 0)) { + //send bit 1 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_clemsa_const.te_long); + instance->encoder.upload[index++] = level_duration_make( + false, + (uint32_t)subghz_protocol_clemsa_const.te_short + + subghz_protocol_clemsa_const.te_long * 7); + } else { + //send bit 0 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_clemsa_const.te_short); + instance->encoder.upload[index++] = level_duration_make( + false, + (uint32_t)subghz_protocol_clemsa_const.te_long + + subghz_protocol_clemsa_const.te_long * 7); + } + return true; +} + +bool subghz_protocol_encoder_clemsa_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderClemsa* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_clemsa_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + subghz_protocol_encoder_clemsa_get_upload(instance); + instance->encoder.is_running = true; + + res = true; + } while(false); + + return res; +} + +void subghz_protocol_encoder_clemsa_stop(void* context) { + SubGhzProtocolEncoderClemsa* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_clemsa_yield(void* context) { + SubGhzProtocolEncoderClemsa* instance = context; + + if(instance->encoder.repeat == 0 || !instance->encoder.is_running) { + instance->encoder.is_running = 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_clemsa_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderClemsa* instance = malloc(sizeof(SubGhzProtocolDecoderClemsa)); + instance->base.protocol = &subghz_protocol_clemsa; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_clemsa_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderClemsa* instance = context; + free(instance); +} + +void subghz_protocol_decoder_clemsa_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderClemsa* instance = context; + instance->decoder.parser_step = ClemsaDecoderStepReset; +} + +void subghz_protocol_decoder_clemsa_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderClemsa* instance = context; + + switch(instance->decoder.parser_step) { + case ClemsaDecoderStepReset: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_clemsa_const.te_short * 51) < + subghz_protocol_clemsa_const.te_delta * 25)) { + instance->decoder.parser_step = ClemsaDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } + break; + + case ClemsaDecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = ClemsaDecoderStepCheckDuration; + } else { + instance->decoder.parser_step = ClemsaDecoderStepReset; + } + break; + + case ClemsaDecoderStepCheckDuration: + if(!level) { + if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_clemsa_const.te_short) < + subghz_protocol_clemsa_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_clemsa_const.te_long) < + subghz_protocol_clemsa_const.te_delta * 3)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = ClemsaDecoderStepSaveDuration; + } else if( + (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_clemsa_const.te_long) < + subghz_protocol_clemsa_const.te_delta * 3) && + (DURATION_DIFF(duration, subghz_protocol_clemsa_const.te_short) < + subghz_protocol_clemsa_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = ClemsaDecoderStepSaveDuration; + } else if( + DURATION_DIFF(duration, subghz_protocol_clemsa_const.te_short * 51) < + subghz_protocol_clemsa_const.te_delta * 25) { + if((DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_clemsa_const.te_short) < + subghz_protocol_clemsa_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + } else if((DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_clemsa_const.te_long) < + subghz_protocol_clemsa_const.te_delta * 3)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + } else { + instance->decoder.parser_step = ClemsaDecoderStepReset; + } + + if(instance->decoder.decode_count_bit == + subghz_protocol_clemsa_const.min_count_bit_for_found) { + 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 = ClemsaDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + + } else { + instance->decoder.parser_step = ClemsaDecoderStepReset; + } + } else { + instance->decoder.parser_step = ClemsaDecoderStepReset; + } + break; + } +} + +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + */ +static void subghz_protocol_clemsa_check_remote_controller(SubGhzBlockGeneric* instance) { + instance->serial = (instance->data >> 2) & 0xFFFF; + instance->btn = (instance->data & 0x03); +} + +uint8_t subghz_protocol_decoder_clemsa_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderClemsa* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_clemsa_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzPresetDefinition* preset) { + furi_assert(context); + SubGhzProtocolDecoderClemsa* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +bool subghz_protocol_decoder_clemsa_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderClemsa* instance = context; + bool ret = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_clemsa_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + ret = true; + } while(false); + return ret; +} + +void subghz_protocol_decoder_clemsa_get_string(void* context, string_t output) { + furi_assert(context); + SubGhzProtocolDecoderClemsa* instance = context; + subghz_protocol_clemsa_check_remote_controller(&instance->generic); + //uint32_t data = (uint32_t)(instance->generic.data & 0xFFFFFF); + string_cat_printf( + output, + "%s %dbit\r\n" + "Key:%05lX Btn %X\r\n" + " +: " DIP_PATTERN "\r\n" + " o: " DIP_PATTERN "\r\n" + " -: " DIP_PATTERN "\r\n", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data & 0x3FFFF), + instance->generic.btn, + SHOW_DIP_P(instance->generic.serial, DIP_P), + SHOW_DIP_P(instance->generic.serial, DIP_O), + SHOW_DIP_P(instance->generic.serial, DIP_N)); +} diff --git a/lib/subghz/protocols/clemsa.h b/lib/subghz/protocols/clemsa.h new file mode 100644 index 00000000000..a83983122c1 --- /dev/null +++ b/lib/subghz/protocols/clemsa.h @@ -0,0 +1,107 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_CLEMSA_NAME "Clemsa" + +typedef struct SubGhzProtocolDecoderClemsa SubGhzProtocolDecoderClemsa; +typedef struct SubGhzProtocolEncoderClemsa SubGhzProtocolEncoderClemsa; + +extern const SubGhzProtocolDecoder subghz_protocol_clemsa_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_clemsa_encoder; +extern const SubGhzProtocol subghz_protocol_clemsa; + +/** + * Allocate SubGhzProtocolEncoderClemsa. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderClemsa* pointer to a SubGhzProtocolEncoderClemsa instance + */ +void* subghz_protocol_encoder_clemsa_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderClemsa. + * @param context Pointer to a SubGhzProtocolEncoderClemsa instance + */ +void subghz_protocol_encoder_clemsa_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderClemsa instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_encoder_clemsa_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderClemsa instance + */ +void subghz_protocol_encoder_clemsa_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderClemsa instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_clemsa_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderClemsa. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderClemsa* pointer to a SubGhzProtocolDecoderClemsa instance + */ +void* subghz_protocol_decoder_clemsa_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderClemsa. + * @param context Pointer to a SubGhzProtocolDecoderClemsa instance + */ +void subghz_protocol_decoder_clemsa_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderClemsa. + * @param context Pointer to a SubGhzProtocolDecoderClemsa instance + */ +void subghz_protocol_decoder_clemsa_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderClemsa instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_clemsa_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderClemsa instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_clemsa_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderClemsa. + * @param context Pointer to a SubGhzProtocolDecoderClemsa instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzPresetDefinition + * @return true On success + */ +bool subghz_protocol_decoder_clemsa_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzPresetDefinition* preset); + +/** + * Deserialize data SubGhzProtocolDecoderClemsa. + * @param context Pointer to a SubGhzProtocolDecoderClemsa instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_decoder_clemsa_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderClemsa instance + * @param output Resulting text + */ +void subghz_protocol_decoder_clemsa_get_string(void* context, string_t output); diff --git a/lib/subghz/protocols/registry.c b/lib/subghz/protocols/registry.c index 6c113cbf868..8bb45e9aded 100644 --- a/lib/subghz/protocols/registry.c +++ b/lib/subghz/protocols/registry.c @@ -12,6 +12,7 @@ const SubGhzProtocol* subghz_protocol_registry[] = { &subghz_protocol_chamb_code, &subghz_protocol_power_smart, &subghz_protocol_marantec, &subghz_protocol_bett, &subghz_protocol_doitrand, &subghz_protocol_phoenix_v2, &subghz_protocol_honeywell_wdb, &subghz_protocol_magellen, &subghz_protocol_intertechno_v3, + &subghz_protocol_clemsa }; diff --git a/lib/subghz/protocols/registry.h b/lib/subghz/protocols/registry.h index 342bf6d2bb5..ad1151024b8 100644 --- a/lib/subghz/protocols/registry.h +++ b/lib/subghz/protocols/registry.h @@ -35,6 +35,7 @@ #include "honeywell_wdb.h" #include "magellen.h" #include "intertechno_v3.h" +#include "clemsa.h" /** * Registration by name SubGhzProtocol. From 53aa5c71a0e67d913af2611416f63848958d7729 Mon Sep 17 00:00:00 2001 From: Max Andreev Date: Sat, 3 Sep 2022 10:09:03 +0300 Subject: [PATCH 040/824] Amap workflow, "toolchain improvements" (#1685) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix quotes in amap * try to fix quotes * try to read "commit_message" * Add new actions anv parser * fix amap_anayse * fix script ssl error * test build with new get commit details method * fix build.yml * add fbt envs to get_env.py * fix envs * using new commit info "way" * try to fix report link in PR page * fix "pvs_studio.yml" again * fix vars * fix "build.yml" Co-authored-by: あく --- .github/workflows/amap_analyse.yml | 57 +++------------ .github/workflows/build.yml | 98 +++++++++++--------------- .github/workflows/check_submodules.yml | 6 +- .github/workflows/lint_c.yml | 6 +- .github/workflows/lint_python.yml | 6 +- .github/workflows/pvs_studio.yml | 41 +++++------ scripts/get_env.py | 87 +++++++++++++++++++++++ scripts/toolchain/fbtenv.cmd | 2 +- scripts/toolchain/fbtenv.sh | 2 +- 9 files changed, 162 insertions(+), 143 deletions(-) create mode 100644 scripts/get_env.py diff --git a/.github/workflows/amap_analyse.yml b/.github/workflows/amap_analyse.yml index 5efdb09e52d..36352978444 100644 --- a/.github/workflows/amap_analyse.yml +++ b/.github/workflows/amap_analyse.yml @@ -35,8 +35,7 @@ jobs: - name: 'Decontaminate previous build leftovers' run: | if [ -d .git ]; then - git submodule status \ - || git checkout `git rev-list --max-parents=0 HEAD | tail -n 1` + git submodule status || git checkout "$(git rev-list --max-parents=0 HEAD | tail -n 1)" fi - name: 'Checkout code' @@ -45,44 +44,14 @@ jobs: fetch-depth: 0 ref: ${{ github.event.pull_request.head.sha }} - - name: 'Escape pull request title' - if: github.event_name == 'pull_request' + - name: 'Get commit details' run: | - import json - import os - import shlex - with open('${{ github.event_path }}') as fh: - event = json.load(fh) - escaped = shlex.quote(event['pull_request']['title']) - with open(os.environ['GITHUB_ENV'], 'a') as fh: - print(f'PULL_NAME={escaped}', file=fh) - shell: python3 {0} - - - name: 'Generate prefixes by commit' - id: names - run: | - REF="${{github.ref}}" - COMMIT_HASH="$(git rev-parse HEAD)" - SHA="$(git rev-parse --short HEAD)" - COMMIT_MSG="${{github.event.head_commit.message}}" + FBT_TOOLCHAIN_PATH=/opt source scripts/toolchain/fbtenv.sh if [[ ${{ github.event_name }} == 'pull_request' ]]; then - REF="${{github.head_ref}}" - COMMIT_HASH="$(git log -1 --pretty=oneline | awk '{print $1}')" - SHA="$(cut -c -8 <<< "$COMMIT_HASH")" - COMMIT_MSG="$(git log -1 --pretty=format:"%s")" - PULL_ID="${{github.event.pull_request.number}}" + python3 scripts/get_env.py "--event_file=${{ github.event_path }}" "--is_pull" + else + python3 scripts/get_env.py "--event_file=${{ github.event_path }}" fi - BRANCH_NAME=${REF#refs/*/} - SUFFIX=${BRANCH_NAME//\//_}-$(date +'%d%m%Y')-${SHA} - if [[ "${{ github.ref }}" == "refs/tags/"* ]]; then - SUFFIX=${BRANCH_NAME//\//_} - fi - echo "::set-output name=commit-hash::${COMMIT_HASH}" - echo "::set-output name=commit-msg::${COMMIT_MSG}" - echo "::set-output name=pull-id::${PULL_ID}" - echo "::set-output name=pull-name::${PULL_NAME}" - echo "::set-output name=branch-name::${BRANCH_NAME}" - echo "::set-output name=suffix::${SUFFIX}" - name: 'Make artifacts directory' run: | @@ -95,13 +64,13 @@ jobs: chmod 600 ./deploy_key; rsync -avzP \ -e 'ssh -p ${{ secrets.RSYNC_DEPLOY_PORT }} -i ./deploy_key' \ - ${{ secrets.RSYNC_DEPLOY_USER }}@${{ secrets.RSYNC_DEPLOY_HOST }}:"${{ secrets.RSYNC_DEPLOY_BASE_PATH }}${{steps.names.outputs.branch-name}}/" artifacts/; + ${{ secrets.RSYNC_DEPLOY_USER }}@${{ secrets.RSYNC_DEPLOY_HOST }}:"${{ secrets.RSYNC_DEPLOY_BASE_PATH }}${BRANCH_NAME}/" artifacts/; rm ./deploy_key; - name: 'Make .map file analyze' run: | cd artifacts/ - /Applications/amap/Contents/MacOS/amap -f flipper-z-f7-firmware-${{steps.names.outputs.suffix}}.elf.map + /Applications/amap/Contents/MacOS/amap -f "flipper-z-f7-firmware-${SUFFIX}.elf.map" - name: 'Upload report to DB' run: | @@ -110,20 +79,14 @@ jobs: { SECTION="$1"; arm-none-eabi-size \ - -A artifacts/flipper-z-f7-firmware-${{steps.names.outputs.suffix}}.elf \ + -A artifacts/flipper-z-f7-firmware-$SUFFIX.elf \ | grep "^$SECTION" | awk '{print $2}' } - export COMMIT_HASH="${{steps.names.outputs.commit-hash}}" - export COMMIT_MSG="${{steps.names.outputs.commit-msg}}" - export BRANCH_NAME="${{steps.names.outputs.branch-name}}" export BSS_SIZE="$(get_size ".bss")" export TEXT_SIZE="$(get_size ".text")" export RODATA_SIZE="$(get_size ".rodata")" export DATA_SIZE="$(get_size ".data")" export FREE_FLASH_SIZE="$(get_size ".free_flash")" - if [[ ${{ github.event_name }} == 'pull_request' ]]; then - export PULL_ID="${{steps.names.outputs.pull-id}}" - fi python3 -m pip install mariadb python3 scripts/amap_mariadb_insert.py \ ${{ secrets.AMAP_MARIADB_USER }} \ @@ -131,4 +94,4 @@ jobs: ${{ secrets.AMAP_MARIADB_HOST }} \ ${{ secrets.AMAP_MARIADB_PORT }} \ ${{ secrets.AMAP_MARIADB_DATABASE }} \ - artifacts/flipper-z-f7-firmware-${{steps.names.outputs.suffix}}.elf.map.all + artifacts/flipper-z-f7-firmware-$SUFFIX.elf.map.all diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3fe733caa7b..530bbc9ee83 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,10 +19,8 @@ jobs: steps: - name: 'Decontaminate previous build leftovers' run: | - if [ -d .git ] - then - git submodule status \ - || git checkout `git rev-list --max-parents=0 HEAD | tail -n 1` + if [ -d .git ]; then + git submodule status || git checkout "$(git rev-list --max-parents=0 HEAD | tail -n 1)" fi - name: 'Checkout code' @@ -33,51 +31,44 @@ jobs: - name: 'Make artifacts directory' run: | - test -d artifacts && rm -rf artifacts || true + rm -rf artifacts mkdir artifacts - - name: 'Generate suffix and folder name' - id: names + - name: 'Get commit details' run: | - REF=${{ github.ref }} + FBT_TOOLCHAIN_PATH=/opt source scripts/toolchain/fbtenv.sh if [[ ${{ github.event_name }} == 'pull_request' ]]; then - REF=${{ github.head_ref }} - fi - BRANCH_OR_TAG=${REF#refs/*/} - SHA=$(git rev-parse --short HEAD) - - if [[ "${{ github.ref }}" == "refs/tags/"* ]]; then - SUFFIX=${BRANCH_OR_TAG//\//_} + python3 scripts/get_env.py "--event_file=${{ github.event_path }}" "--is_pull" else - SUFFIX=${BRANCH_OR_TAG//\//_}-$(date +'%d%m%Y')-${SHA} + python3 scripts/get_env.py "--event_file=${{ github.event_path }}" fi - echo "WORKFLOW_BRANCH_OR_TAG=${BRANCH_OR_TAG}" >> $GITHUB_ENV - echo "DIST_SUFFIX=${SUFFIX}" >> $GITHUB_ENV - echo "::set-output name=artifacts-path::${BRANCH_OR_TAG}" + - name: 'Generate suffixes for comment' + id: names + run: | + echo "::set-output name=branch_name::${BRANCH_NAME}" + echo "::set-output name=commit_sha::${COMMIT_SHA}" + echo "::set-output name=default_target::${DEFAULT_TARGET}" echo "::set-output name=suffix::${SUFFIX}" - echo "::set-output name=short-hash::${SHA}" - echo "::set-output name=default-target::${DEFAULT_TARGET}" - name: 'Bundle scripts' if: ${{ !github.event.pull_request.head.repo.fork }} run: | - tar czpf artifacts/flipper-z-any-scripts-${{steps.names.outputs.suffix}}.tgz scripts + tar czpf artifacts/flipper-z-any-scripts-${SUFFIX}.tgz scripts - name: 'Build the firmware' run: | set -e - for TARGET in ${TARGETS} - do - FBT_TOOLCHAIN_PATH=/opt ./fbt TARGET_HW=`echo ${TARGET} | sed 's/f//'` updater_package ${{ startsWith(github.ref, 'refs/tags') && 'DEBUG=0 COMPACT=1' || '' }} + for TARGET in ${TARGETS}; do + FBT_TOOLCHAIN_PATH=/opt ./fbt TARGET_HW="$(echo "${TARGET}" | sed 's/f//')" \ + updater_package ${{ startsWith(github.ref, 'refs/tags') && 'DEBUG=0 COMPACT=1' || '' }} done - name: 'Move upload files' if: ${{ !github.event.pull_request.head.repo.fork }} run: | set -e - for TARGET in ${TARGETS} - do + for TARGET in ${TARGETS}; do mv dist/${TARGET}-*/* artifacts/ done @@ -85,12 +76,11 @@ jobs: if: ${{ !github.event.pull_request.head.repo.fork }} run: | set -e - for UPDATEBUNDLE in artifacts/*/ - do - BUNDLE_NAME=`echo $UPDATEBUNDLE | cut -d'/' -f2` - echo Packaging ${BUNDLE_NAME} - tar czpf artifacts/flipper-z-${BUNDLE_NAME}.tgz -C artifacts ${BUNDLE_NAME} - rm -rf artifacts/${BUNDLE_NAME} + for UPDATEBUNDLE in artifacts/*/; do + BUNDLE_NAME="$(echo "$UPDATEBUNDLE" | cut -d'/' -f2)" + echo Packaging "${BUNDLE_NAME}" + tar czpf "artifacts/flipper-z-${BUNDLE_NAME}.tgz" -C artifacts "${BUNDLE_NAME}" + rm -rf "artifacts/${BUNDLE_NAME}" done - name: "Check for uncommitted changes" @@ -100,17 +90,17 @@ jobs: - name: 'Bundle resources' if: ${{ !github.event.pull_request.head.repo.fork }} run: | - tar czpf artifacts/flipper-z-any-resources-${{steps.names.outputs.suffix}}.tgz -C assets resources + tar czpf "artifacts/flipper-z-any-resources-${SUFFIX}.tgz" -C assets resources - name: 'Bundle core2 firmware' if: ${{ !github.event.pull_request.head.repo.fork }} run: | FBT_TOOLCHAIN_PATH=/opt ./fbt copro_dist - tar czpf artifacts/flipper-z-any-core2_firmware-${{steps.names.outputs.suffix}}.tgz -C assets core2_firmware + tar czpf "artifacts/flipper-z-any-core2_firmware-${SUFFIX}.tgz" -C assets core2_firmware - name: 'Copy .map file' run: | - cp build/f7-firmware-*/firmware.elf.map artifacts/flipper-z-f7-firmware-${{steps.names.outputs.suffix}}.elf.map + cp build/f7-firmware-*/firmware.elf.map "artifacts/flipper-z-f7-firmware-${SUFFIX}.elf.map" - name: 'Upload artifacts to update server' if: ${{ !github.event.pull_request.head.repo.fork }} @@ -119,7 +109,7 @@ jobs: chmod 600 ./deploy_key; rsync -avzP --delete --mkpath \ -e 'ssh -p ${{ secrets.RSYNC_DEPLOY_PORT }} -i ./deploy_key' \ - artifacts/ ${{ secrets.RSYNC_DEPLOY_USER }}@${{ secrets.RSYNC_DEPLOY_HOST }}:"${{ secrets.RSYNC_DEPLOY_BASE_PATH }}${{steps.names.outputs.artifacts-path}}/"; + artifacts/ ${{ secrets.RSYNC_DEPLOY_USER }}@${{ secrets.RSYNC_DEPLOY_HOST }}:"${{ secrets.RSYNC_DEPLOY_BASE_PATH }}${BRANCH_NAME}/"; rm ./deploy_key; - name: 'Trigger update server reindex' @@ -142,10 +132,10 @@ jobs: comment-id: ${{ steps.fc.outputs.comment-id }} issue-number: ${{ github.event.pull_request.number }} body: | - **Compiled firmware for commit `${{steps.names.outputs.short-hash}}`:** - - [📦 Update package](https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.artifacts-path}}/flipper-z-${{steps.names.outputs.default-target}}-update-${{steps.names.outputs.suffix}}.tgz) - - [📥 DFU file](https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.artifacts-path}}/flipper-z-${{steps.names.outputs.default-target}}-full-${{steps.names.outputs.suffix}}.dfu) - - [☁️ Web updater](https://my.flipp.dev/?url=https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.artifacts-path}}/flipper-z-${{steps.names.outputs.default-target}}-update-${{steps.names.outputs.suffix}}.tgz&channel=${{steps.names.outputs.artifacts-path}}&version=${{steps.names.outputs.short-hash}}) + **Compiled firmware for commit `${{steps.names.outputs.commit_sha}}`:** + - [📦 Update package](https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.branch_name}}/flipper-z-${{steps.names.outputs.default_target}}-update-${{steps.names.outputs.suffix}}.tgz) + - [📥 DFU file](https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.branch_name}}/flipper-z-${{steps.names.outputs.default_target}}-full-${{steps.names.outputs.suffix}}.dfu) + - [☁️ Web updater](https://my.flipp.dev/?url=https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.branch_name}}/flipper-z-${{steps.names.outputs.default_target}}-update-${{steps.names.outputs.suffix}}.tgz&channel=${{steps.names.outputs.branch_name}}&version=${{steps.names.outputs.commit_sha}}) edit-mode: replace compact: @@ -157,7 +147,7 @@ jobs: if [ -d .git ] then git submodule status \ - || git checkout `git rev-list --max-parents=0 HEAD | tail -n 1` + || git checkout "$(git rev-list --max-parents=0 HEAD | tail -n 1)" fi - name: 'Checkout code' @@ -167,29 +157,21 @@ jobs: submodules: true ref: ${{ github.event.pull_request.head.sha }} - - name: 'Generate suffix and folder name' - id: names + - name: 'Get commit details' run: | - REF=${{ github.ref }} + FBT_TOOLCHAIN_PATH=/opt source scripts/toolchain/fbtenv.sh if [[ ${{ github.event_name }} == 'pull_request' ]]; then - REF=${{ github.head_ref }} - fi - BRANCH_OR_TAG=${REF#refs/*/} - SHA=$(git rev-parse --short HEAD) - - if [[ "${{ github.ref }}" == "refs/tags/"* ]]; then - SUFFIX=${BRANCH_OR_TAG//\//_} + python3 scripts/get_env.py "--event_file=${{ github.event_path }}" "--is_pull" else - SUFFIX=${BRANCH_OR_TAG//\//_}-$(date +'%d%m%Y')-${SHA} + python3 scripts/get_env.py "--event_file=${{ github.event_path }}" fi - - echo "WORKFLOW_BRANCH_OR_TAG=${BRANCH_OR_TAG}" >> $GITHUB_ENV + echo "WORKFLOW_BRANCH_OR_TAG=${BRANCH_NAME}" >> $GITHUB_ENV echo "DIST_SUFFIX=${SUFFIX}" >> $GITHUB_ENV - name: 'Build the firmware' run: | set -e - for TARGET in ${TARGETS} - do - FBT_TOOLCHAIN_PATH=/opt ./fbt TARGET_HW=`echo ${TARGET} | sed 's/f//'` updater_package DEBUG=0 COMPACT=1 + for TARGET in ${TARGETS}; do + FBT_TOOLCHAIN_PATH=/opt ./fbt TARGET_HW="$(echo "${TARGET}" | sed 's/f//')" \ + updater_package DEBUG=0 COMPACT=1 done diff --git a/.github/workflows/check_submodules.yml b/.github/workflows/check_submodules.yml index e021c969a8e..e4178c3c702 100644 --- a/.github/workflows/check_submodules.yml +++ b/.github/workflows/check_submodules.yml @@ -15,10 +15,8 @@ jobs: steps: - name: 'Decontaminate previous build leftovers' run: | - if [ -d .git ] - then - git submodule status \ - || git checkout `git rev-list --max-parents=0 HEAD | tail -n 1` + if [ -d .git ]; then + git submodule status || git checkout "$(git rev-list --max-parents=0 HEAD | tail -n 1)" fi - name: 'Checkout code' diff --git a/.github/workflows/lint_c.yml b/.github/workflows/lint_c.yml index 64d14b713b4..becafcab088 100644 --- a/.github/workflows/lint_c.yml +++ b/.github/workflows/lint_c.yml @@ -18,10 +18,8 @@ jobs: steps: - name: 'Decontaminate previous build leftovers' run: | - if [ -d .git ] - then - git submodule status \ - || git checkout `git rev-list --max-parents=0 HEAD | tail -n 1` + if [ -d .git ]; then + git submodule status || git checkout "$(git rev-list --max-parents=0 HEAD | tail -n 1)" fi - name: 'Checkout code' diff --git a/.github/workflows/lint_python.yml b/.github/workflows/lint_python.yml index 5051c569140..d5ff834ea5b 100644 --- a/.github/workflows/lint_python.yml +++ b/.github/workflows/lint_python.yml @@ -15,10 +15,8 @@ jobs: steps: - name: 'Decontaminate previous build leftovers' run: | - if [ -d .git ] - then - git submodule status \ - || git checkout `git rev-list --max-parents=0 HEAD | tail -n 1` + if [ -d .git ]; then + git submodule status || git checkout "$(git rev-list --max-parents=0 HEAD | tail -n 1)" fi - name: 'Checkout code' diff --git a/.github/workflows/pvs_studio.yml b/.github/workflows/pvs_studio.yml index 5733fd27494..8ace58b6160 100644 --- a/.github/workflows/pvs_studio.yml +++ b/.github/workflows/pvs_studio.yml @@ -20,10 +20,8 @@ jobs: steps: - name: 'Decontaminate previous build leftovers' run: | - if [ -d .git ] - then - git submodule status \ - || git checkout `git rev-list --max-parents=0 HEAD | tail -n 1` + if [ -d .git ]; then + git submodule status || git checkout "$(git rev-list --max-parents=0 HEAD | tail -n 1)" fi - name: 'Checkout code' @@ -32,28 +30,23 @@ jobs: fetch-depth: 0 ref: ${{ github.event.pull_request.head.sha }} - - name: 'Generate suffix and folder name' - id: names + - name: 'Get commit details' run: | - REF=${{ github.ref }} + FBT_TOOLCHAIN_PATH=/opt source scripts/toolchain/fbtenv.sh if [[ ${{ github.event_name }} == 'pull_request' ]]; then - REF=${{ github.head_ref }} - fi - BRANCH_OR_TAG=${REF#refs/*/} - SHA=$(git rev-parse --short HEAD) - - if [[ "${{ github.ref }}" == "refs/tags/"* ]]; then - SUFFIX=${BRANCH_OR_TAG//\//_} + python3 scripts/get_env.py "--event_file=${{ github.event_path }}" "--is_pull" else - SUFFIX=${BRANCH_OR_TAG//\//_}-$(date +'%d%m%Y')-${SHA} + python3 scripts/get_env.py "--event_file=${{ github.event_path }}" fi - echo "WORKFLOW_BRANCH_OR_TAG=${BRANCH_OR_TAG}" >> $GITHUB_ENV - echo "DIST_SUFFIX=${SUFFIX}" >> $GITHUB_ENV - echo "::set-output name=artifacts-path::${BRANCH_OR_TAG}" + - name: 'Generate suffixes for comment' + if: ${{ !github.event.pull_request.head.repo.fork && github.event.pull_request }} + id: names + run: | + echo "::set-output name=branch_name::${BRANCH_NAME}" + echo "::set-output name=commit_sha::${COMMIT_SHA}" + echo "::set-output name=default_target::${DEFAULT_TARGET}" echo "::set-output name=suffix::${SUFFIX}" - echo "::set-output name=short-hash::${SHA}" - echo "::set-output name=default-target::${DEFAULT_TARGET}" - name: 'Make reports directory' run: | @@ -75,7 +68,7 @@ jobs: -o PVS-Studio.log - name: 'Convert PVS-Studio output to html page' - run: plog-converter -a GA:1,2,3 -t fullhtml PVS-Studio.log -o reports/${{steps.names.outputs.default-target}}-${{steps.names.outputs.suffix}} + run: plog-converter -a GA:1,2,3 -t fullhtml PVS-Studio.log -o reports/${DEFAULT_TARGET}-${SUFFIX} - name: 'Upload artifacts to update server' if: ${{ !github.event.pull_request.head.repo.fork }} @@ -84,7 +77,7 @@ jobs: chmod 600 ./deploy_key; rsync -avrzP --mkpath \ -e 'ssh -p ${{ secrets.RSYNC_DEPLOY_PORT }} -i ./deploy_key' \ - reports/ ${{ secrets.RSYNC_DEPLOY_USER }}@${{ secrets.RSYNC_DEPLOY_HOST }}:/home/data/firmware-pvs-studio-report/"${{steps.names.outputs.artifacts-path}}/"; + reports/ ${{ secrets.RSYNC_DEPLOY_USER }}@${{ secrets.RSYNC_DEPLOY_HOST }}:/home/data/firmware-pvs-studio-report/"${BRANCH_NAME}/"; rm ./deploy_key; - name: 'Find Previous Comment' @@ -103,6 +96,6 @@ jobs: comment-id: ${{ steps.fc.outputs.comment-id }} issue-number: ${{ github.event.pull_request.number }} body: | - **PVS-Studio report for commit `${{steps.names.outputs.short-hash}}`:** - - [Report](https://update.flipperzero.one/builds/firmware-pvs-studio-report/${{steps.names.outputs.artifacts-path}}/${{steps.names.outputs.default-target}}-${{steps.names.outputs.suffix}}/index.html) + **PVS-Studio report for commit `${{steps.names.outputs.commit_sha}}`:** + - [Report](https://update.flipperzero.one/builds/firmware-pvs-studio-report/${{steps.names.outputs.branch_name}}/${{steps.names.outputs.default_target}}-${{steps.names.outputs.suffix}}/index.html) edit-mode: replace diff --git a/scripts/get_env.py b/scripts/get_env.py new file mode 100644 index 00000000000..e503803e142 --- /dev/null +++ b/scripts/get_env.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python3 + +import ssl +import json +import os +import shlex +import re +import argparse +import datetime +import urllib.request + +# event_file = open('${{ github.event_path }}') + + +def parse_args(): + parser = argparse.ArgumentParser() + parser.add_argument("--event_file", help="Current GitHub event file", required=True) + parser.add_argument( + "--is_pull", help="Is it Pull Request", default=False, action="store_true" + ) + args = parser.parse_args() + return args + + +def get_commit_json(event): + context = ssl._create_unverified_context() + with urllib.request.urlopen( + event["pull_request"]["_links"]["commits"]["href"], context=context + ) as commit_file: + commit_json = json.loads(commit_file.read().decode("utf-8")) + return commit_json + + +def get_details(event, args): + data = {} + current_time = datetime.datetime.utcnow().date() + if args.is_pull: + commit_json = get_commit_json(event) + data["commit_comment"] = shlex.quote(commit_json[-1]["commit"]["message"]) + data["commit_hash"] = commit_json[-1]["sha"] + ref = event["pull_request"]["head"]["ref"] + data["pull_id"] = event["pull_request"]["number"] + data["pull_name"] = shlex.quote(event["pull_request"]["title"]) + else: + data["commit_comment"] = shlex.quote(event["commits"][-1]["message"]) + data["commit_hash"] = event["commits"][-1]["id"] + ref = event["ref"] + data["commit_sha"] = data["commit_hash"][:8] + data["branch_name"] = re.sub("refs/\w+/", "", ref) + data["suffix"] = ( + data["branch_name"].replace("/", "_") + + "-" + + current_time.strftime("%d%m%Y") + + "-" + + data["commit_sha"] + ) + if ref.startswith("refs/tags/"): + data["suffix"] = data["branch_name"].replace("/", "_") + return data + + +def add_envs(data, env_file, args): + print(f'COMMIT_MSG={data["commit_comment"]}', file=env_file) + print(f'COMMIT_HASH={data["commit_hash"]}', file=env_file) + print(f'COMMIT_SHA={data["commit_sha"]}', file=env_file) + print(f'SUFFIX={data["suffix"]}', file=env_file) + print(f'BRANCH_NAME={data["branch_name"]}', file=env_file) + print(f'DIST_SUFFIX={data["suffix"]}', file=env_file) + print(f'WORKFLOW_BRANCH_OR_TAG={data["branch_name"]}', file=env_file) + if args.is_pull: + print(f'PULL_ID={data["pull_id"]}', file=env_file) + print(f'PULL_NAME={data["pull_name"]}', file=env_file) + + +def main(): + args = parse_args() + event_file = open(args.event_file) + event = json.load(event_file) + env_file = open(os.environ["GITHUB_ENV"], "a") + data = get_details(event, args) + add_envs(data, env_file, args) + event_file.close() + env_file.close() + + +if __name__ == "__main__": + main() diff --git a/scripts/toolchain/fbtenv.cmd b/scripts/toolchain/fbtenv.cmd index f955a4db3fe..cf5d2441af8 100644 --- a/scripts/toolchain/fbtenv.cmd +++ b/scripts/toolchain/fbtenv.cmd @@ -13,7 +13,7 @@ if not [%FBT_NOENV%] == [] ( exit /b 0 ) -set "FLIPPER_TOOLCHAIN_VERSION=9" +set "FLIPPER_TOOLCHAIN_VERSION=12" set "FBT_TOOLCHAIN_ROOT=%FBT_ROOT%\toolchain\i686-windows" diff --git a/scripts/toolchain/fbtenv.sh b/scripts/toolchain/fbtenv.sh index cbbb7b94db5..1c56c03cc48 100755 --- a/scripts/toolchain/fbtenv.sh +++ b/scripts/toolchain/fbtenv.sh @@ -5,7 +5,7 @@ # public variables DEFAULT_SCRIPT_PATH="$(pwd -P)"; SCRIPT_PATH="${SCRIPT_PATH:-$DEFAULT_SCRIPT_PATH}"; -FBT_TOOLCHAIN_VERSION="${FBT_TOOLCHAIN_VERSION:-"8"}"; +FBT_TOOLCHAIN_VERSION="${FBT_TOOLCHAIN_VERSION:-"12"}"; FBT_TOOLCHAIN_PATH="${FBT_TOOLCHAIN_PATH:-$SCRIPT_PATH}"; fbtenv_show_usage() From bd54c2b34284a8242656570cc3e4b4a6a6ed7a0a Mon Sep 17 00:00:00 2001 From: Max Andreev Date: Sat, 3 Sep 2022 13:18:24 +0300 Subject: [PATCH 041/824] Fix CI/CD (#1698) * Test newline commit * Test newline commit * Fix some variables and test newline "quotted" commit --- scripts/get_env.py | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/scripts/get_env.py b/scripts/get_env.py index e503803e142..843a1e4067c 100644 --- a/scripts/get_env.py +++ b/scripts/get_env.py @@ -5,11 +5,15 @@ import os import shlex import re +import string +import random import argparse import datetime import urllib.request -# event_file = open('${{ github.event_path }}') + +def id_gen(size=5, chars=string.ascii_uppercase + string.digits): + return "".join(random.choice(chars) for _ in range(size)) def parse_args(): @@ -59,17 +63,24 @@ def get_details(event, args): return data +def add_env(name, value, file): + delimeter = id_gen() + print(f"{name}<<{delimeter}", file=file) + print(f"{value}", file=file) + print(f"{delimeter}", file=file) + + def add_envs(data, env_file, args): - print(f'COMMIT_MSG={data["commit_comment"]}', file=env_file) - print(f'COMMIT_HASH={data["commit_hash"]}', file=env_file) - print(f'COMMIT_SHA={data["commit_sha"]}', file=env_file) - print(f'SUFFIX={data["suffix"]}', file=env_file) - print(f'BRANCH_NAME={data["branch_name"]}', file=env_file) - print(f'DIST_SUFFIX={data["suffix"]}', file=env_file) - print(f'WORKFLOW_BRANCH_OR_TAG={data["branch_name"]}', file=env_file) + add_env("COMMIT_MSG", data["commit_comment"], env_file) + add_env("COMMIT_HASH", data["commit_hash"], env_file) + add_env("COMMIT_SHA", data["commit_sha"], env_file) + add_env("SUFFIX", data["suffix"], env_file) + add_env("BRANCH_NAME", data["branch_name"], env_file) + add_env("DIST_SUFFIX", data["suffix"], env_file) + add_env("WORKFLOW_BRANCH_OR_TAG", data["branch_name"], env_file) if args.is_pull: - print(f'PULL_ID={data["pull_id"]}', file=env_file) - print(f'PULL_NAME={data["pull_name"]}', file=env_file) + add_env("PULL_ID", data["pull_id"], env_file) + add_env("PULL_NAME", data["pull_name"], env_file) def main(): From ed2c607dd3d3f0dc2af24c6c04b626b73f1a5fb3 Mon Sep 17 00:00:00 2001 From: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> Date: Sat, 3 Sep 2022 13:46:07 +0300 Subject: [PATCH 042/824] [FL-2776] IR CLI Decode Command (#1692) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add decode option to Infrared app * Implement saving results to file * Refactor code * Correct formatting * Refactor code * Better command line arguments handling * Accept generic IR files * Remove unneeded define Co-authored-by: あく --- applications/infrared/infrared_cli.c | 178 +++++++++++++++++++++++++-- 1 file changed, 168 insertions(+), 10 deletions(-) diff --git a/applications/infrared/infrared_cli.c b/applications/infrared/infrared_cli.c index aae02e8fd14..693e191ee6a 100644 --- a/applications/infrared/infrared_cli.c +++ b/applications/infrared/infrared_cli.c @@ -3,6 +3,8 @@ #include #include #include +#include +#include #include "infrared_signal.h" @@ -10,6 +12,7 @@ static void infrared_cli_start_ir_rx(Cli* cli, string_t args); static void infrared_cli_start_ir_tx(Cli* cli, string_t args); +static void infrared_cli_process_decode(Cli* cli, string_t args); static const struct { const char* cmd; @@ -17,6 +20,7 @@ static const struct { } infrared_cli_commands[] = { {.cmd = "rx", .process_function = infrared_cli_start_ir_rx}, {.cmd = "tx", .process_function = infrared_cli_start_ir_tx}, + {.cmd = "decode", .process_function = infrared_cli_process_decode}, }; static void signal_received_callback(void* context, InfraredWorkerSignal* received_signal) { @@ -86,6 +90,7 @@ static void infrared_cli_print_usage(void) { "\tFrequency (%d - %d), Duty cycle (0 - 100), max 512 samples\r\n", INFRARED_MIN_FREQUENCY, INFRARED_MAX_FREQUENCY); + printf("\tir decode []\r\n"); } static bool infrared_cli_parse_message(const char* str, InfraredSignal* signal) { @@ -162,6 +167,160 @@ static void infrared_cli_start_ir_tx(Cli* cli, string_t args) { infrared_signal_free(signal); } +static bool + infrared_cli_save_signal(InfraredSignal* signal, FlipperFormat* file, const char* name) { + bool ret = infrared_signal_save(signal, file, name); + if(!ret) { + printf("Failed to save signal: \"%s\"\r\n", name); + } + return ret; +} + +static bool infrared_cli_decode_raw_signal( + InfraredRawSignal* raw_signal, + InfraredDecoderHandler* decoder, + FlipperFormat* output_file, + const char* signal_name) { + InfraredSignal* signal = infrared_signal_alloc(); + bool ret = false, level = true, is_decoded = false; + + size_t i; + for(i = 0; i < raw_signal->timings_size; ++i) { + // TODO: Any infrared_check_decoder_ready() magic? + const InfraredMessage* message = infrared_decode(decoder, level, raw_signal->timings[i]); + + if(message) { + is_decoded = true; + printf( + "Protocol: %s address: 0x%lX command: 0x%lX %s\r\n", + infrared_get_protocol_name(message->protocol), + message->address, + message->command, + (message->repeat ? "R" : "")); + if(output_file && !message->repeat) { + infrared_signal_set_message(signal, message); + if(!infrared_cli_save_signal(signal, output_file, signal_name)) break; + } + } + + level = !level; + } + + if(i == raw_signal->timings_size) { + if(!is_decoded && output_file) { + infrared_signal_set_raw_signal( + signal, + raw_signal->timings, + raw_signal->timings_size, + raw_signal->frequency, + raw_signal->duty_cycle); + ret = infrared_cli_save_signal(signal, output_file, signal_name); + } else { + ret = true; + } + } + + infrared_reset_decoder(decoder); + infrared_signal_free(signal); + return ret; +} + +static bool infrared_cli_decode_file(FlipperFormat* input_file, FlipperFormat* output_file) { + bool ret = false; + + InfraredSignal* signal = infrared_signal_alloc(); + InfraredDecoderHandler* decoder = infrared_alloc_decoder(); + + string_t tmp; + string_init(tmp); + + while(infrared_signal_read(signal, input_file, tmp)) { + ret = false; + if(!infrared_signal_is_valid(signal)) { + printf("Invalid signal\r\n"); + break; + } + if(!infrared_signal_is_raw(signal)) { + if(output_file && + !infrared_cli_save_signal(signal, output_file, string_get_cstr(tmp))) { + break; + } else { + printf("Skipping decoded signal\r\n"); + continue; + } + } + InfraredRawSignal* raw_signal = infrared_signal_get_raw_signal(signal); + printf("Raw signal: %s, %u samples\r\n", string_get_cstr(tmp), raw_signal->timings_size); + if(!infrared_cli_decode_raw_signal(raw_signal, decoder, output_file, string_get_cstr(tmp))) + break; + ret = true; + } + + infrared_free_decoder(decoder); + infrared_signal_free(signal); + string_clear(tmp); + + return ret; +} + +static void infrared_cli_process_decode(Cli* cli, string_t args) { + UNUSED(cli); + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* input_file = flipper_format_buffered_file_alloc(storage); + FlipperFormat* output_file = NULL; + + uint32_t version; + string_t tmp, header, input_path, output_path; + string_init(tmp); + string_init(header); + string_init(input_path); + string_init(output_path); + + do { + if(!args_read_probably_quoted_string_and_trim(args, input_path)) { + printf("Wrong arguments.\r\n"); + infrared_cli_print_usage(); + break; + } + args_read_probably_quoted_string_and_trim(args, output_path); + if(!flipper_format_buffered_file_open_existing(input_file, string_get_cstr(input_path))) { + printf("Failed to open file for reading: \"%s\"\r\n", string_get_cstr(input_path)); + break; + } + if(!flipper_format_read_header(input_file, header, &version) || + (!string_start_with_str_p(header, "IR")) || version != 1) { + printf("Invalid or corrupted input file: \"%s\"\r\n", string_get_cstr(input_path)); + break; + } + if(!string_empty_p(output_path)) { + printf("Writing output to file: \"%s\"\r\n", string_get_cstr(output_path)); + output_file = flipper_format_file_alloc(storage); + } + if(output_file && + !flipper_format_file_open_always(output_file, string_get_cstr(output_path))) { + printf("Failed to open file for writing: \"%s\"\r\n", string_get_cstr(output_path)); + break; + } + if(output_file && !flipper_format_write_header(output_file, header, version)) { + printf("Failed to write to the output file: \"%s\"\r\n", string_get_cstr(output_path)); + break; + } + if(!infrared_cli_decode_file(input_file, output_file)) { + break; + } + printf("File successfully decoded.\r\n"); + } while(false); + + string_clear(tmp); + string_clear(header); + string_clear(input_path); + string_clear(output_path); + + flipper_format_free(input_file); + if(output_file) flipper_format_free(output_file); + furi_record_close(RECORD_STORAGE); +} + static void infrared_cli_start_ir(Cli* cli, string_t args, void* context) { UNUSED(context); if(furi_hal_infrared_is_busy()) { @@ -169,18 +328,15 @@ static void infrared_cli_start_ir(Cli* cli, string_t args, void* context) { return; } + string_t command; + string_init(command); + args_read_string_and_trim(args, command); + size_t i = 0; for(; i < COUNT_OF(infrared_cli_commands); ++i) { - size_t size = strlen(infrared_cli_commands[i].cmd); - bool cmd_found = !strncmp(string_get_cstr(args), infrared_cli_commands[i].cmd, size); - if(cmd_found) { - if(string_size(args) == size) { - break; - } - if(string_get_cstr(args)[size] == ' ') { - string_right(args, size + 1); - break; - } + size_t cmd_len = strlen(infrared_cli_commands[i].cmd); + if(!strncmp(string_get_cstr(command), infrared_cli_commands[i].cmd, cmd_len)) { + break; } } @@ -189,6 +345,8 @@ static void infrared_cli_start_ir(Cli* cli, string_t args, void* context) { } else { infrared_cli_print_usage(); } + + string_clear(command); } void infrared_on_system_start() { #ifdef SRV_CLI From 1853359d78f11428d48290cef89edb7c4558f372 Mon Sep 17 00:00:00 2001 From: gornekich Date: Sat, 3 Sep 2022 15:25:36 +0300 Subject: [PATCH 043/824] [FL-2759], [FL-2766] NFC collect params for mfkey32 attack (#1643) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * nfc: start nfc over rpc * nfc: add detect reader state * nfc: add reader analyzer * nfc: rework reader analyzer * reader_analyzer: print collected nonces to debug * reader analyzer: add save on SD card * reader_analyzer: separate mfkey related part to different file * mfkey32: add logic for collecting parameters * nfc: rework pcap with reader analyzer * nfc: add logger for reader * nfc: clean up * nfc: add detect reader view * nfc: add detect reader and mfkey nonces scenes * nfc: add mfkey comlplete scene * nfc: add new assets * nfc: fix gui * nfc: fix iso14443-4 UID emulation * nfc: add no sd card notification * nfc: fix grammar Co-authored-by: あく --- applications/nfc/nfc.c | 9 + applications/nfc/nfc_i.h | 3 + applications/nfc/scenes/nfc_scene_config.h | 2 + .../nfc/scenes/nfc_scene_detect_reader.c | 104 +------ .../nfc/scenes/nfc_scene_mfkey_complete.c | 49 ++++ .../nfc/scenes/nfc_scene_mfkey_nonces_info.c | 55 ++++ applications/nfc/scenes/nfc_scene_start.c | 8 +- applications/nfc/views/detect_reader.c | 115 ++++++++ applications/nfc/views/detect_reader.h | 23 ++ .../{Reader_detect.png => ArrowC_1_36x36.png} | Bin 3771 -> 3692 bytes assets/icons/NFC/Tap_reader_36x38.png | Bin 0 -> 3748 bytes firmware/targets/f7/furi_hal/furi_hal_nfc.c | 21 +- .../targets/furi_hal_include/furi_hal_nfc.h | 2 + lib/nfc/helpers/mfkey32.c | 230 +++++++++++++++ lib/nfc/helpers/mfkey32.h | 27 ++ lib/nfc/helpers/nfc_debug_log.c | 72 +++++ lib/nfc/helpers/nfc_debug_log.h | 17 ++ lib/nfc/helpers/nfc_debug_pcap.c | 204 ++++++-------- lib/nfc/helpers/nfc_debug_pcap.h | 26 +- lib/nfc/helpers/reader_analyzer.c | 261 ++++++++++++++++++ lib/nfc/helpers/reader_analyzer.h | 41 +++ lib/nfc/nfc_worker.c | 126 ++++++++- lib/nfc/nfc_worker.h | 5 + lib/nfc/nfc_worker_i.h | 7 +- 24 files changed, 1155 insertions(+), 252 deletions(-) create mode 100644 applications/nfc/scenes/nfc_scene_mfkey_complete.c create mode 100644 applications/nfc/scenes/nfc_scene_mfkey_nonces_info.c create mode 100644 applications/nfc/views/detect_reader.c create mode 100644 applications/nfc/views/detect_reader.h rename assets/icons/NFC/{Reader_detect.png => ArrowC_1_36x36.png} (84%) create mode 100644 assets/icons/NFC/Tap_reader_36x38.png create mode 100644 lib/nfc/helpers/mfkey32.c create mode 100644 lib/nfc/helpers/mfkey32.h create mode 100644 lib/nfc/helpers/nfc_debug_log.c create mode 100644 lib/nfc/helpers/nfc_debug_log.h create mode 100644 lib/nfc/helpers/reader_analyzer.c create mode 100644 lib/nfc/helpers/reader_analyzer.h diff --git a/applications/nfc/nfc.c b/applications/nfc/nfc.c index 3422e91af85..57e25f81efe 100644 --- a/applications/nfc/nfc.c +++ b/applications/nfc/nfc.c @@ -94,6 +94,11 @@ Nfc* nfc_alloc() { view_dispatcher_add_view( nfc->view_dispatcher, NfcViewDictAttack, dict_attack_get_view(nfc->dict_attack)); + // Detect Reader + nfc->detect_reader = detect_reader_alloc(); + view_dispatcher_add_view( + nfc->view_dispatcher, NfcViewDetectReader, detect_reader_get_view(nfc->detect_reader)); + // Generator nfc->generator = NULL; @@ -158,6 +163,10 @@ void nfc_free(Nfc* nfc) { view_dispatcher_remove_view(nfc->view_dispatcher, NfcViewDictAttack); dict_attack_free(nfc->dict_attack); + // Detect Reader + view_dispatcher_remove_view(nfc->view_dispatcher, NfcViewDetectReader); + detect_reader_free(nfc->detect_reader); + // Worker nfc_worker_stop(nfc->worker); nfc_worker_free(nfc->worker); diff --git a/applications/nfc/nfc_i.h b/applications/nfc/nfc_i.h index bcfe4a21921..c9ba7fff718 100644 --- a/applications/nfc/nfc_i.h +++ b/applications/nfc/nfc_i.h @@ -28,6 +28,7 @@ #include #include "views/dict_attack.h" +#include "views/detect_reader.h" #include #include @@ -71,6 +72,7 @@ struct Nfc { TextBox* text_box; Widget* widget; DictAttack* dict_attack; + DetectReader* detect_reader; const NfcGenerator* generator; }; @@ -85,6 +87,7 @@ typedef enum { NfcViewTextBox, NfcViewWidget, NfcViewDictAttack, + NfcViewDetectReader, } NfcView; Nfc* nfc_alloc(); diff --git a/applications/nfc/scenes/nfc_scene_config.h b/applications/nfc/scenes/nfc_scene_config.h index ff34a11d84c..540fe10987d 100644 --- a/applications/nfc/scenes/nfc_scene_config.h +++ b/applications/nfc/scenes/nfc_scene_config.h @@ -48,4 +48,6 @@ ADD_SCENE(nfc, rpc, Rpc) ADD_SCENE(nfc, exit_confirm, ExitConfirm) ADD_SCENE(nfc, retry_confirm, RetryConfirm) ADD_SCENE(nfc, detect_reader, DetectReader) +ADD_SCENE(nfc, mfkey_nonces_info, MfkeyNoncesInfo) +ADD_SCENE(nfc, mfkey_complete, MfkeyComplete) ADD_SCENE(nfc, nfc_data_info, NfcDataInfo) diff --git a/applications/nfc/scenes/nfc_scene_detect_reader.c b/applications/nfc/scenes/nfc_scene_detect_reader.c index f734f04cbd4..8945febafa3 100644 --- a/applications/nfc/scenes/nfc_scene_detect_reader.c +++ b/applications/nfc/scenes/nfc_scene_detect_reader.c @@ -1,126 +1,48 @@ #include "../nfc_i.h" #include -#define NFC_SCENE_DETECT_READER_LOG_SIZE_MAX (200) - -enum { - NfcSceneDetectReaderStateWidget, - NfcSceneDetectReaderStateTextBox, -}; - bool nfc_detect_reader_worker_callback(NfcWorkerEvent event, void* context) { UNUSED(event); furi_assert(context); Nfc* nfc = context; - view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventWorkerExit); + view_dispatcher_send_custom_event(nfc->view_dispatcher, event); return true; } -void nfc_scene_detect_reader_widget_callback(GuiButtonType result, InputType type, void* context) { - furi_assert(context); - Nfc* nfc = context; - if(type == InputTypeShort) { - view_dispatcher_send_custom_event(nfc->view_dispatcher, result); - } -} - -void nfc_detect_reader_textbox_callback(void* context) { +void nfc_scene_detect_reader_callback(void* context) { furi_assert(context); Nfc* nfc = context; view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit); } -// Add widget with device name or inform that data received -static void nfc_scene_detect_reader_widget_config(Nfc* nfc, bool data_received) { - Widget* widget = nfc->widget; - widget_reset(widget); - - widget_add_icon_element(widget, 0, 14, &I_Reader_detect); - widget_add_string_element( - widget, 64, 3, AlignCenter, AlignTop, FontSecondary, "Hold Near Reader"); - widget_add_string_element(widget, 55, 22, AlignLeft, AlignTop, FontPrimary, "Emulating..."); - - if(data_received) { - widget_add_button_element( - widget, GuiButtonTypeCenter, "Log", nfc_scene_detect_reader_widget_callback, nfc); - } -} - void nfc_scene_detect_reader_on_enter(void* context) { Nfc* nfc = context; DOLPHIN_DEED(DolphinDeedNfcEmulate); - FuriHalNfcDevData nfc_params = { - .uid = {0x36, 0x9C, 0xe7, 0xb1, 0x0A, 0xC1, 0x34}, - .uid_len = 7, - .atqa = {0x44, 0x00}, - .sak = 0x08, - .type = FuriHalNfcTypeA, - }; - nfc->dev->dev_data.nfc_data = nfc_params; - - // Setup Widget - nfc_scene_detect_reader_widget_config(nfc, false); - // Setup TextBox - TextBox* text_box = nfc->text_box; - text_box_set_font(text_box, TextBoxFontHex); - text_box_set_focus(text_box, TextBoxFocusEnd); - string_reset(nfc->text_box_store); - // Set Widget state and view - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneDetectReader, NfcSceneDetectReaderStateWidget); - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); - // Start worker - memset(&nfc->dev->dev_data.reader_data, 0, sizeof(NfcReaderRequestData)); + detect_reader_set_callback(nfc->detect_reader, nfc_scene_detect_reader_callback, nfc); nfc_worker_start( nfc->worker, - NfcWorkerStateUidEmulate, + NfcWorkerStateAnalyzeReader, &nfc->dev->dev_data, nfc_detect_reader_worker_callback, nfc); + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewDetectReader); + nfc_blink_start(nfc); } bool nfc_scene_detect_reader_on_event(void* context, SceneManagerEvent event) { Nfc* nfc = context; - NfcReaderRequestData* reader_data = &nfc->dev->dev_data.reader_data; - uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneDetectReader); bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { - if(event.event == NfcCustomEventWorkerExit) { - // Add data button to widget if data is received for the first time - if(!string_size(nfc->text_box_store)) { - nfc_scene_detect_reader_widget_config(nfc, true); - } - // Update TextBox data - if(string_size(nfc->text_box_store) < NFC_SCENE_DETECT_READER_LOG_SIZE_MAX) { - string_cat_printf(nfc->text_box_store, "R:"); - for(uint16_t i = 0; i < reader_data->size; i++) { - string_cat_printf(nfc->text_box_store, " %02X", reader_data->data[i]); - } - string_push_back(nfc->text_box_store, '\n'); - text_box_set_text(nfc->text_box, string_get_cstr(nfc->text_box_store)); - } - memset(reader_data, 0, sizeof(NfcReaderRequestData)); - consumed = true; - } else if(event.event == GuiButtonTypeCenter && state == NfcSceneDetectReaderStateWidget) { - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewTextBox); - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneDetectReader, NfcSceneDetectReaderStateTextBox); + if(event.event == NfcCustomEventViewExit) { + nfc_worker_stop(nfc->worker); + scene_manager_next_scene(nfc->scene_manager, NfcSceneMfkeyNoncesInfo); consumed = true; - } else if(event.event == NfcCustomEventViewExit && state == NfcSceneDetectReaderStateTextBox) { - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneDetectReader, NfcSceneDetectReaderStateWidget); - consumed = true; - } - } else if(event.type == SceneManagerEventTypeBack) { - if(state == NfcSceneDetectReaderStateTextBox) { - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneDetectReader, NfcSceneDetectReaderStateWidget); + } else if(event.event == NfcWorkerEventDetectReaderMfkeyCollected) { + detect_reader_inc_nonce_cnt(nfc->detect_reader); consumed = true; } } @@ -135,9 +57,7 @@ void nfc_scene_detect_reader_on_exit(void* context) { nfc_worker_stop(nfc->worker); // Clear view - widget_reset(nfc->widget); - text_box_reset(nfc->text_box); - string_reset(nfc->text_box_store); + detect_reader_reset(nfc->detect_reader); nfc_blink_stop(nfc); } diff --git a/applications/nfc/scenes/nfc_scene_mfkey_complete.c b/applications/nfc/scenes/nfc_scene_mfkey_complete.c new file mode 100644 index 00000000000..3c4f9dba19e --- /dev/null +++ b/applications/nfc/scenes/nfc_scene_mfkey_complete.c @@ -0,0 +1,49 @@ +#include "../nfc_i.h" + +void nfc_scene_mfkey_complete_callback(GuiButtonType result, InputType type, void* context) { + Nfc* nfc = context; + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(nfc->view_dispatcher, result); + } +} + +void nfc_scene_mfkey_complete_on_enter(void* context) { + Nfc* nfc = context; + + widget_add_string_element(nfc->widget, 64, 0, AlignCenter, AlignTop, FontPrimary, "Complete!"); + widget_add_string_multiline_element( + nfc->widget, + 64, + 32, + AlignCenter, + AlignCenter, + FontSecondary, + "Now use mfkey32v2\nto extract keys"); + widget_add_button_element( + nfc->widget, GuiButtonTypeCenter, "OK", nfc_scene_mfkey_complete_callback, nfc); + + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); +} + +bool nfc_scene_mfkey_complete_on_event(void* context, SceneManagerEvent event) { + Nfc* nfc = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == GuiButtonTypeCenter) { + consumed = scene_manager_search_and_switch_to_previous_scene( + nfc->scene_manager, NfcSceneStart); + } + } else if(event.event == SceneManagerEventTypeBack) { + consumed = + scene_manager_search_and_switch_to_previous_scene(nfc->scene_manager, NfcSceneStart); + } + + return consumed; +} + +void nfc_scene_mfkey_complete_on_exit(void* context) { + Nfc* nfc = context; + + widget_reset(nfc->widget); +} \ No newline at end of file diff --git a/applications/nfc/scenes/nfc_scene_mfkey_nonces_info.c b/applications/nfc/scenes/nfc_scene_mfkey_nonces_info.c new file mode 100644 index 00000000000..b45b690d38a --- /dev/null +++ b/applications/nfc/scenes/nfc_scene_mfkey_nonces_info.c @@ -0,0 +1,55 @@ +#include "../nfc_i.h" +#include + +void nfc_scene_mfkey_nonces_info_callback(GuiButtonType result, InputType type, void* context) { + Nfc* nfc = context; + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(nfc->view_dispatcher, result); + } +} + +void nfc_scene_mfkey_nonces_info_on_enter(void* context) { + Nfc* nfc = context; + + string_t temp_str; + string_init(temp_str); + + uint16_t nonces_saved = mfkey32_get_auth_sectors(temp_str); + widget_add_text_scroll_element(nfc->widget, 0, 22, 128, 42, string_get_cstr(temp_str)); + string_printf(temp_str, "Nonces saved %d", nonces_saved); + widget_add_string_element( + nfc->widget, 0, 0, AlignLeft, AlignTop, FontPrimary, string_get_cstr(temp_str)); + widget_add_string_element( + nfc->widget, 0, 12, AlignLeft, AlignTop, FontSecondary, "Authenticated sectors:"); + + widget_add_button_element( + nfc->widget, GuiButtonTypeRight, "Next", nfc_scene_mfkey_nonces_info_callback, nfc); + + string_clear(temp_str); + + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); +} + +bool nfc_scene_mfkey_nonces_info_on_event(void* context, SceneManagerEvent event) { + Nfc* nfc = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == GuiButtonTypeRight) { + scene_manager_next_scene(nfc->scene_manager, NfcSceneMfkeyComplete); + 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_mfkey_nonces_info_on_exit(void* context) { + Nfc* nfc = context; + + // Clear view + widget_reset(nfc->widget); +} diff --git a/applications/nfc/scenes/nfc_scene_start.c b/applications/nfc/scenes/nfc_scene_start.c index 01ffb46b82a..1a9051dfd6d 100644 --- a/applications/nfc/scenes/nfc_scene_start.c +++ b/applications/nfc/scenes/nfc_scene_start.c @@ -49,7 +49,12 @@ bool nfc_scene_start_on_event(void* context, SceneManagerEvent event) { scene_manager_next_scene(nfc->scene_manager, NfcSceneRead); consumed = true; } else if(event.event == SubmenuIndexDetectReader) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneDetectReader); + bool sd_exist = storage_sd_status(nfc->dev->storage) == FSE_OK; + if(sd_exist) { + scene_manager_next_scene(nfc->scene_manager, NfcSceneDetectReader); + } else { + scene_manager_next_scene(nfc->scene_manager, NfcSceneDictNotFound); + } consumed = true; } else if(event.event == SubmenuIndexSaved) { scene_manager_next_scene(nfc->scene_manager, NfcSceneFileSelect); @@ -61,7 +66,6 @@ bool nfc_scene_start_on_event(void* context, SceneManagerEvent event) { scene_manager_next_scene(nfc->scene_manager, NfcSceneSetType); consumed = true; } else if(event.event == SubmenuIndexDebug) { - scene_manager_set_scene_state(nfc->scene_manager, NfcSceneStart, SubmenuIndexDebug); scene_manager_next_scene(nfc->scene_manager, NfcSceneDebug); consumed = true; } diff --git a/applications/nfc/views/detect_reader.c b/applications/nfc/views/detect_reader.c new file mode 100644 index 00000000000..177c13f75da --- /dev/null +++ b/applications/nfc/views/detect_reader.c @@ -0,0 +1,115 @@ +#include "detect_reader.h" + +#include + +struct DetectReader { + View* view; + DetectReaderDoneCallback callback; + void* context; +}; + +typedef struct { + uint16_t nonces; +} DetectReaderViewModel; + +static void detect_reader_draw_callback(Canvas* canvas, void* model) { + DetectReaderViewModel* m = model; + char text[32] = {}; + + snprintf(text, sizeof(text), "Tap the reader several times"); + canvas_draw_str_aligned(canvas, 64, 0, AlignCenter, AlignTop, "Tap the reader several times"); + + if(m->nonces == 0) { + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 52, 22, AlignLeft, AlignTop, "Emulating..."); + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned(canvas, 52, 35, AlignLeft, AlignTop, "MIFARE Classic"); + canvas_draw_icon(canvas, 0, 13, &I_Tap_reader_36x38); + } else { + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 54, 22, AlignLeft, AlignTop, "Collecting..."); + canvas_set_font(canvas, FontSecondary); + snprintf(text, sizeof(text), "Nonces: %d", m->nonces); + canvas_draw_str_aligned(canvas, 54, 35, AlignLeft, AlignTop, text); + elements_button_right(canvas, "Next"); + canvas_draw_icon(canvas, 6, 15, &I_ArrowC_1_36x36); + } +} + +static bool detect_reader_input_callback(InputEvent* event, void* context) { + DetectReader* detect_reader = context; + furi_assert(detect_reader->callback); + bool consumed = false; + + uint8_t nonces = 0; + with_view_model( + detect_reader->view, (DetectReaderViewModel * model) { + nonces = model->nonces; + return false; + }); + + if(event->type == InputTypeShort) { + if(event->key == InputKeyRight) { + if(nonces > 0) { + detect_reader->callback(detect_reader->context); + consumed = true; + } + } + } + + return consumed; +} + +DetectReader* detect_reader_alloc() { + DetectReader* detect_reader = malloc(sizeof(DetectReader)); + detect_reader->view = view_alloc(); + view_allocate_model(detect_reader->view, ViewModelTypeLocking, sizeof(DetectReaderViewModel)); + view_set_draw_callback(detect_reader->view, detect_reader_draw_callback); + view_set_input_callback(detect_reader->view, detect_reader_input_callback); + view_set_context(detect_reader->view, detect_reader); + + return detect_reader; +} + +void detect_reader_free(DetectReader* detect_reader) { + furi_assert(detect_reader); + + view_free(detect_reader->view); + free(detect_reader); +} + +void detect_reader_reset(DetectReader* detect_reader) { + furi_assert(detect_reader); + + with_view_model( + detect_reader->view, (DetectReaderViewModel * model) { + model->nonces = 0; + return false; + }); +} + +View* detect_reader_get_view(DetectReader* detect_reader) { + furi_assert(detect_reader); + + return detect_reader->view; +} + +void detect_reader_set_callback( + DetectReader* detect_reader, + DetectReaderDoneCallback callback, + void* context) { + furi_assert(detect_reader); + furi_assert(callback); + + detect_reader->callback = callback; + detect_reader->context = context; +} + +void detect_reader_inc_nonce_cnt(DetectReader* detect_reader) { + furi_assert(detect_reader); + with_view_model( + detect_reader->view, (DetectReaderViewModel * model) { + model->nonces++; + return false; + }); +} diff --git a/applications/nfc/views/detect_reader.h b/applications/nfc/views/detect_reader.h new file mode 100644 index 00000000000..12cd03db496 --- /dev/null +++ b/applications/nfc/views/detect_reader.h @@ -0,0 +1,23 @@ +#pragma once +#include +#include +#include + +typedef struct DetectReader DetectReader; + +typedef void (*DetectReaderDoneCallback)(void* context); + +DetectReader* detect_reader_alloc(); + +void detect_reader_free(DetectReader* detect_reader); + +void detect_reader_reset(DetectReader* detect_reader); + +View* detect_reader_get_view(DetectReader* detect_reader); + +void detect_reader_set_callback( + DetectReader* detect_reader, + DetectReaderDoneCallback callback, + void* context); + +void detect_reader_inc_nonce_cnt(DetectReader* detect_reader); diff --git a/assets/icons/NFC/Reader_detect.png b/assets/icons/NFC/ArrowC_1_36x36.png similarity index 84% rename from assets/icons/NFC/Reader_detect.png rename to assets/icons/NFC/ArrowC_1_36x36.png index 56d3663eaa2666f68493fa3088f9beab1bcf0717..3a0c6dd0cb2b7cb6eebb1507fa68a8dbda11b146 100644 GIT binary patch delta 357 zcmdlj`$k5wGr-TCmrII^fq{Y7)59eQNUMM_2Q!eob}wVqM#UN)1rtjb6K7LP7eiwM zV?#q%7e^Oo3nv#NCuegbV?#?L^T~U8WMGP1aVq-BV*pd+gj10xFWe+&oQhg`o$4cP$(}aJgOf0y3X6|{1mKqhGt5Z`l45wTW-f~7ZOyE#*_A#dudpaER<@1+W{|V@1 zeR(js>PvlR+=6%QlYK7jS+djmuBy#xwb=&lY)j*IozgaqS@B1FHWLp+VKqbVv)Xmn PKyLAL^>bP0l+XkKN=9^t delta 436 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, &tx_rx->rx_bits); data_received = true; + if(tx_rx->sniff_rx) { + tx_rx->sniff_rx(tx_rx->rx_data, tx_rx->rx_bits, false, tx_rx->sniff_context); + } break; } continue; @@ -497,14 +508,14 @@ static bool furi_hal_nfc_transparent_tx_rx(FuriHalNfcTxRxContext* tx_rx, uint16_ furi_hal_spi_bus_handle_init(&furi_hal_spi_bus_handle_nfc); st25r3916ExecuteCommand(ST25R3916_CMD_UNMASK_RECEIVE_DATA); - if(tx_rx->sniff_tx) { - tx_rx->sniff_tx(tx_rx->tx_data, tx_rx->tx_bits, false, tx_rx->sniff_context); - } - // Manually wait for interrupt furi_hal_gpio_init(&gpio_nfc_irq_rfid_pull, GpioModeInput, GpioPullDown, GpioSpeedVeryHigh); st25r3916ClearAndEnableInterrupts(ST25R3916_IRQ_MASK_RXE); + if(tx_rx->sniff_tx) { + tx_rx->sniff_tx(tx_rx->tx_data, tx_rx->tx_bits, false, tx_rx->sniff_context); + } + uint32_t irq = 0; uint8_t rxe = 0; uint32_t start = DWT->CYCCNT; diff --git a/firmware/targets/furi_hal_include/furi_hal_nfc.h b/firmware/targets/furi_hal_include/furi_hal_nfc.h index 71186076f63..0bcb45068d6 100644 --- a/firmware/targets/furi_hal_include/furi_hal_nfc.h +++ b/firmware/targets/furi_hal_include/furi_hal_nfc.h @@ -120,6 +120,8 @@ void furi_hal_nfc_field_off(); */ void furi_hal_nfc_start_sleep(); +void furi_hal_nfc_stop_cmd(); + /** NFC stop sleep */ void furi_hal_nfc_exit_sleep(); diff --git a/lib/nfc/helpers/mfkey32.c b/lib/nfc/helpers/mfkey32.c new file mode 100644 index 00000000000..64b06e259c8 --- /dev/null +++ b/lib/nfc/helpers/mfkey32.c @@ -0,0 +1,230 @@ +#include "mfkey32.h" + +#include +#include +#include +#include +#include + +#include +#include + +#define TAG "Mfkey32" + +#define MFKEY32_LOGS_PATH EXT_PATH("nfc/.mfkey32.log") + +typedef enum { + Mfkey32StateIdle, + Mfkey32StateAuthReceived, + Mfkey32StateAuthNtSent, + Mfkey32StateAuthArNrReceived, +} Mfkey32State; + +typedef struct { + uint32_t cuid; + uint8_t sector; + MfClassicKey key; + uint32_t nt0; + uint32_t nr0; + uint32_t ar0; + uint32_t nt1; + uint32_t nr1; + uint32_t ar1; +} Mfkey32Params; + +ARRAY_DEF(Mfkey32Params, Mfkey32Params, M_POD_OPLIST); + +typedef struct { + uint8_t sector; + MfClassicKey key; + uint32_t nt; + uint32_t nr; + uint32_t ar; +} Mfkey32Nonce; + +struct Mfkey32 { + Mfkey32State state; + Stream* file_stream; + Mfkey32Params_t params_arr; + Mfkey32Nonce nonce; + uint32_t cuid; + Mfkey32ParseDataCallback callback; + void* context; +}; + +Mfkey32* mfkey32_alloc(uint32_t cuid) { + Mfkey32* instance = malloc(sizeof(Mfkey32)); + instance->cuid = cuid; + instance->state = Mfkey32StateIdle; + Storage* storage = furi_record_open(RECORD_STORAGE); + instance->file_stream = buffered_file_stream_alloc(storage); + if(!buffered_file_stream_open( + instance->file_stream, MFKEY32_LOGS_PATH, FSAM_WRITE, FSOM_OPEN_APPEND)) { + buffered_file_stream_close(instance->file_stream); + stream_free(instance->file_stream); + free(instance); + instance = NULL; + } else { + Mfkey32Params_init(instance->params_arr); + } + + furi_record_close(RECORD_STORAGE); + + return instance; +} + +void mfkey32_free(Mfkey32* instance) { + furi_assert(instance != NULL); + + Mfkey32Params_clear(instance->params_arr); + buffered_file_stream_close(instance->file_stream); + stream_free(instance->file_stream); + free(instance); +} + +void mfkey32_set_callback(Mfkey32* instance, Mfkey32ParseDataCallback callback, void* context) { + furi_assert(instance); + furi_assert(callback); + + instance->callback = callback; + instance->context = context; +} + +static bool mfkey32_write_params(Mfkey32* instance, Mfkey32Params* params) { + string_t str; + string_init_printf( + str, + "Sector %d key %c cuid %08x nt0 %08x nr0 %08x ar0 %08x nt1 %08x nr1 %08x ar1 %08x\n", + params->sector, + params->key == MfClassicKeyA ? 'A' : 'B', + params->cuid, + params->nt0, + params->nr0, + params->ar0, + params->nt1, + params->nr1, + params->ar1); + bool write_success = stream_write_string(instance->file_stream, str); + string_clear(str); + return write_success; +} + +static void mfkey32_add_params(Mfkey32* instance) { + Mfkey32Nonce* nonce = &instance->nonce; + bool nonce_added = false; + // Search if we partially collected params + if(Mfkey32Params_size(instance->params_arr)) { + Mfkey32Params_it_t it; + for(Mfkey32Params_it(it, instance->params_arr); !Mfkey32Params_end_p(it); + Mfkey32Params_next(it)) { + Mfkey32Params* params = Mfkey32Params_ref(it); + if((params->sector == nonce->sector) && (params->key == nonce->key)) { + params->nt1 = nonce->nt; + params->nr1 = nonce->nr; + params->ar1 = nonce->ar; + nonce_added = true; + FURI_LOG_I( + TAG, + "Params for sector %d key %c collected", + params->sector, + params->key == MfClassicKeyA ? 'A' : 'B'); + // Write on sd card + if(mfkey32_write_params(instance, params)) { + Mfkey32Params_remove(instance->params_arr, it); + if(instance->callback) { + instance->callback(Mfkey32EventParamCollected, instance->context); + } + } + } + } + } + if(!nonce_added) { + Mfkey32Params params = { + .sector = nonce->sector, + .key = nonce->key, + .cuid = instance->cuid, + .nt0 = nonce->nt, + .nr0 = nonce->nr, + .ar0 = nonce->ar, + }; + Mfkey32Params_push_back(instance->params_arr, params); + } +} + +void mfkey32_process_data( + Mfkey32* instance, + uint8_t* data, + uint16_t len, + bool reader_to_tag, + bool crc_dropped) { + furi_assert(instance); + furi_assert(data); + + Mfkey32Nonce* nonce = &instance->nonce; + uint16_t data_len = len; + if((data_len > 3) && !crc_dropped) { + data_len -= 2; + } + + bool data_processed = false; + if(instance->state == Mfkey32StateIdle) { + if(reader_to_tag) { + if((data[0] == 0x60) || (data[0] == 0x61)) { + nonce->key = data[0] == 0x60 ? MfClassicKeyA : MfClassicKeyB; + nonce->sector = mf_classic_get_sector_by_block(data[1]); + instance->state = Mfkey32StateAuthReceived; + data_processed = true; + } + } + } else if(instance->state == Mfkey32StateAuthReceived) { + if(!reader_to_tag) { + if(len == 4) { + nonce->nt = nfc_util_bytes2num(data, 4); + instance->state = Mfkey32StateAuthNtSent; + data_processed = true; + } + } + } else if(instance->state == Mfkey32StateAuthNtSent) { + if(reader_to_tag) { + if(len == 8) { + nonce->nr = nfc_util_bytes2num(data, 4); + nonce->ar = nfc_util_bytes2num(&data[4], 4); + mfkey32_add_params(instance); + instance->state = Mfkey32StateIdle; + } + } + } + if(!data_processed) { + instance->state = Mfkey32StateIdle; + } +} + +uint16_t mfkey32_get_auth_sectors(string_t data_str) { + furi_assert(data_str); + + uint16_t nonces_num = 0; + Storage* storage = furi_record_open(RECORD_STORAGE); + Stream* file_stream = buffered_file_stream_alloc(storage); + string_t temp_str; + string_init(temp_str); + + do { + if(!buffered_file_stream_open( + file_stream, MFKEY32_LOGS_PATH, FSAM_READ, FSOM_OPEN_EXISTING)) + break; + while(true) { + if(!stream_read_line(file_stream, temp_str)) break; + size_t uid_pos = string_search_str(temp_str, "cuid"); + string_left(temp_str, uid_pos); + string_push_back(temp_str, '\n'); + string_cat(data_str, temp_str); + nonces_num++; + } + } while(false); + + buffered_file_stream_close(file_stream); + stream_free(file_stream); + string_clear(temp_str); + + return nonces_num; +} diff --git a/lib/nfc/helpers/mfkey32.h b/lib/nfc/helpers/mfkey32.h new file mode 100644 index 00000000000..c4f13cc2c49 --- /dev/null +++ b/lib/nfc/helpers/mfkey32.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include + +typedef struct Mfkey32 Mfkey32; + +typedef enum { + Mfkey32EventParamCollected, +} Mfkey32Event; + +typedef void (*Mfkey32ParseDataCallback)(Mfkey32Event event, void* context); + +Mfkey32* mfkey32_alloc(uint32_t cuid); + +void mfkey32_free(Mfkey32* instance); + +void mfkey32_process_data( + Mfkey32* instance, + uint8_t* data, + uint16_t len, + bool reader_to_tag, + bool crc_dropped); + +void mfkey32_set_callback(Mfkey32* instance, Mfkey32ParseDataCallback callback, void* context); + +uint16_t mfkey32_get_auth_sectors(string_t string); diff --git a/lib/nfc/helpers/nfc_debug_log.c b/lib/nfc/helpers/nfc_debug_log.c new file mode 100644 index 00000000000..1cbc5224f67 --- /dev/null +++ b/lib/nfc/helpers/nfc_debug_log.c @@ -0,0 +1,72 @@ +#include "nfc_debug_log.h" + +#include +#include +#include + +#define TAG "NfcDebugLog" + +#define NFC_DEBUG_PCAP_FILENAME EXT_PATH("nfc/debug.txt") + +struct NfcDebugLog { + Stream* file_stream; + string_t data_str; +}; + +NfcDebugLog* nfc_debug_log_alloc() { + NfcDebugLog* instance = malloc(sizeof(NfcDebugLog)); + + Storage* storage = furi_record_open(RECORD_STORAGE); + instance->file_stream = buffered_file_stream_alloc(storage); + + if(!buffered_file_stream_open( + instance->file_stream, NFC_DEBUG_PCAP_FILENAME, FSAM_WRITE, FSOM_OPEN_APPEND)) { + buffered_file_stream_close(instance->file_stream); + stream_free(instance->file_stream); + instance->file_stream = NULL; + } + + if(!instance->file_stream) { + free(instance); + instance = NULL; + } else { + string_init(instance->data_str); + } + furi_record_close(RECORD_STORAGE); + + return instance; +} + +void nfc_debug_log_free(NfcDebugLog* instance) { + furi_assert(instance); + furi_assert(instance->file_stream); + furi_assert(instance->data_str); + + buffered_file_stream_close(instance->file_stream); + stream_free(instance->file_stream); + string_clear(instance->data_str); + + free(instance); +} + +void nfc_debug_log_process_data( + NfcDebugLog* instance, + uint8_t* data, + uint16_t len, + bool reader_to_tag, + bool crc_dropped) { + furi_assert(instance); + furi_assert(instance->file_stream); + furi_assert(instance->data_str); + furi_assert(data); + UNUSED(crc_dropped); + + string_printf(instance->data_str, "%lu %c:", furi_get_tick(), reader_to_tag ? 'R' : 'T'); + uint16_t data_len = len; + for(size_t i = 0; i < data_len; i++) { + string_cat_printf(instance->data_str, " %02x", data[i]); + } + string_push_back(instance->data_str, '\n'); + + stream_write_string(instance->file_stream, instance->data_str); +} diff --git a/lib/nfc/helpers/nfc_debug_log.h b/lib/nfc/helpers/nfc_debug_log.h new file mode 100644 index 00000000000..261b8008b8e --- /dev/null +++ b/lib/nfc/helpers/nfc_debug_log.h @@ -0,0 +1,17 @@ +#pragma once + +#include +#include + +typedef struct NfcDebugLog NfcDebugLog; + +NfcDebugLog* nfc_debug_log_alloc(); + +void nfc_debug_log_free(NfcDebugLog* instance); + +void nfc_debug_log_process_data( + NfcDebugLog* instance, + uint8_t* data, + uint16_t len, + bool reader_to_tag, + bool crc_dropped); diff --git a/lib/nfc/helpers/nfc_debug_pcap.c b/lib/nfc/helpers/nfc_debug_pcap.c index 48d72bfbfaf..6c7654ad57a 100644 --- a/lib/nfc/helpers/nfc_debug_pcap.c +++ b/lib/nfc/helpers/nfc_debug_pcap.c @@ -1,7 +1,9 @@ #include "nfc_debug_pcap.h" +#include +#include +#include #include -#include #define TAG "NfcDebugPcap" @@ -16,48 +18,94 @@ #define DATA_PCD_TO_PICC_CRC_DROPPED 0xFA #define NFC_DEBUG_PCAP_FILENAME EXT_PATH("nfc/debug.pcap") -#define NFC_DEBUG_PCAP_BUFFER_SIZE 64 - -struct NfcDebugPcapWorker { - bool alive; - Storage* storage; - File* file; - StreamBufferHandle_t stream; - FuriThread* thread; + +struct NfcDebugPcap { + Stream* file_stream; }; -static File* nfc_debug_pcap_open(Storage* storage) { - File* file = storage_file_alloc(storage); - if(!storage_file_open(file, NFC_DEBUG_PCAP_FILENAME, FSAM_WRITE, FSOM_OPEN_APPEND)) { - storage_file_free(file); - return NULL; - } - if(!storage_file_tell(file)) { - struct { - uint32_t magic; - uint16_t major, minor; - uint32_t reserved[2]; - uint32_t snaplen; - uint32_t link_type; - } __attribute__((__packed__)) pcap_hdr = { - .magic = PCAP_MAGIC, - .major = PCAP_MAJOR, - .minor = PCAP_MINOR, - .snaplen = FURI_HAL_NFC_DATA_BUFF_SIZE, - .link_type = DLT_ISO_14443, - }; - if(storage_file_write(file, &pcap_hdr, sizeof(pcap_hdr)) != sizeof(pcap_hdr)) { - FURI_LOG_E(TAG, "Failed to write pcap header"); +static Stream* nfc_debug_pcap_open(Storage* storage) { + Stream* stream = NULL; + stream = buffered_file_stream_alloc(storage); + if(!buffered_file_stream_open(stream, NFC_DEBUG_PCAP_FILENAME, FSAM_WRITE, FSOM_OPEN_APPEND)) { + buffered_file_stream_close(stream); + stream_free(stream); + stream = NULL; + } else { + if(!stream_tell(stream)) { + struct { + uint32_t magic; + uint16_t major, minor; + uint32_t reserved[2]; + uint32_t snaplen; + uint32_t link_type; + } __attribute__((__packed__)) pcap_hdr = { + .magic = PCAP_MAGIC, + .major = PCAP_MAJOR, + .minor = PCAP_MINOR, + .snaplen = FURI_HAL_NFC_DATA_BUFF_SIZE, + .link_type = DLT_ISO_14443, + }; + if(stream_write(stream, (uint8_t*)&pcap_hdr, sizeof(pcap_hdr)) != sizeof(pcap_hdr)) { + FURI_LOG_E(TAG, "Failed to write pcap header"); + buffered_file_stream_close(stream); + stream_free(stream); + stream = NULL; + } } } - return file; + return stream; +} + +NfcDebugPcap* nfc_debug_pcap_alloc() { + NfcDebugPcap* instance = malloc(sizeof(NfcDebugPcap)); + + Storage* storage = furi_record_open(RECORD_STORAGE); + instance->file_stream = nfc_debug_pcap_open(storage); + if(!instance->file_stream) { + free(instance); + instance = NULL; + } + furi_record_close(RECORD_STORAGE); + + return instance; +} + +void nfc_debug_pcap_free(NfcDebugPcap* instance) { + furi_assert(instance); + furi_assert(instance->file_stream); + + buffered_file_stream_close(instance->file_stream); + stream_free(instance->file_stream); + + free(instance); } -static void - nfc_debug_pcap_write(NfcDebugPcapWorker* instance, uint8_t event, uint8_t* data, uint16_t len) { +void nfc_debug_pcap_process_data( + NfcDebugPcap* instance, + uint8_t* data, + uint16_t len, + bool reader_to_tag, + bool crc_dropped) { + furi_assert(instance); + furi_assert(data); FuriHalRtcDateTime datetime; furi_hal_rtc_get_datetime(&datetime); + uint8_t event = 0; + if(reader_to_tag) { + if(crc_dropped) { + event = DATA_PCD_TO_PICC_CRC_DROPPED; + } else { + event = DATA_PCD_TO_PICC; + } + } else { + if(crc_dropped) { + event = DATA_PICC_TO_PCD_CRC_DROPPED; + } else { + event = DATA_PICC_TO_PCD; + } + } + struct { // https://wiki.wireshark.org/Development/LibpcapFileFormat#record-packet-header uint32_t ts_sec; @@ -77,90 +125,6 @@ static void .event = event, .len = len << 8 | len >> 8, }; - xStreamBufferSend(instance->stream, &pkt_hdr, sizeof(pkt_hdr), FuriWaitForever); - xStreamBufferSend(instance->stream, data, len, FuriWaitForever); -} - -static void - nfc_debug_pcap_write_tx(uint8_t* data, uint16_t bits, bool crc_dropped, void* context) { - NfcDebugPcapWorker* instance = context; - uint8_t event = crc_dropped ? DATA_PCD_TO_PICC_CRC_DROPPED : DATA_PCD_TO_PICC; - nfc_debug_pcap_write(instance, event, data, bits / 8); -} - -static void - nfc_debug_pcap_write_rx(uint8_t* data, uint16_t bits, bool crc_dropped, void* context) { - NfcDebugPcapWorker* instance = context; - uint8_t event = crc_dropped ? DATA_PICC_TO_PCD_CRC_DROPPED : DATA_PICC_TO_PCD; - nfc_debug_pcap_write(instance, event, data, bits / 8); -} - -int32_t nfc_debug_pcap_thread(void* context) { - NfcDebugPcapWorker* instance = context; - uint8_t buffer[NFC_DEBUG_PCAP_BUFFER_SIZE]; - - while(instance->alive) { - size_t ret = - xStreamBufferReceive(instance->stream, buffer, NFC_DEBUG_PCAP_BUFFER_SIZE, 50); - if(storage_file_write(instance->file, buffer, ret) != ret) { - FURI_LOG_E(TAG, "Failed to write pcap data"); - } - } - - return 0; -} - -NfcDebugPcapWorker* nfc_debug_pcap_alloc(Storage* storage) { - NfcDebugPcapWorker* instance = malloc(sizeof(NfcDebugPcapWorker)); - - instance->alive = true; - - instance->storage = storage; - - instance->file = nfc_debug_pcap_open(storage); - - instance->stream = xStreamBufferCreate(4096, 1); - - instance->thread = furi_thread_alloc(); - furi_thread_set_name(instance->thread, "PcapWorker"); - furi_thread_set_stack_size(instance->thread, 1024); - furi_thread_set_callback(instance->thread, nfc_debug_pcap_thread); - furi_thread_set_context(instance->thread, instance); - furi_thread_start(instance->thread); - - return instance; -} - -void nfc_debug_pcap_free(NfcDebugPcapWorker* instance) { - furi_assert(instance); - - instance->alive = false; - - furi_thread_join(instance->thread); - furi_thread_free(instance->thread); - - vStreamBufferDelete(instance->stream); - - if(instance->file) storage_file_free(instance->file); - - instance->storage = NULL; - - free(instance); -} - -void nfc_debug_pcap_prepare_tx_rx( - NfcDebugPcapWorker* instance, - FuriHalNfcTxRxContext* tx_rx, - bool is_picc) { - if(!instance || !instance->file) return; - - if(is_picc) { - tx_rx->sniff_tx = nfc_debug_pcap_write_rx; - tx_rx->sniff_rx = nfc_debug_pcap_write_tx; - } else { - tx_rx->sniff_tx = nfc_debug_pcap_write_tx; - tx_rx->sniff_rx = nfc_debug_pcap_write_rx; - } - - tx_rx->sniff_context = instance; + stream_write(instance->file_stream, (uint8_t*)&pkt_hdr, sizeof(pkt_hdr)); + stream_write(instance->file_stream, data, len); } diff --git a/lib/nfc/helpers/nfc_debug_pcap.h b/lib/nfc/helpers/nfc_debug_pcap.h index 6d2a449ae27..eeddc56112c 100644 --- a/lib/nfc/helpers/nfc_debug_pcap.h +++ b/lib/nfc/helpers/nfc_debug_pcap.h @@ -1,21 +1,17 @@ #pragma once -#include -#include +#include +#include -typedef struct NfcDebugPcapWorker NfcDebugPcapWorker; +typedef struct NfcDebugPcap NfcDebugPcap; -NfcDebugPcapWorker* nfc_debug_pcap_alloc(Storage* storage); +NfcDebugPcap* nfc_debug_pcap_alloc(); -void nfc_debug_pcap_free(NfcDebugPcapWorker* instance); +void nfc_debug_pcap_free(NfcDebugPcap* instance); -/** Prepare tx/rx context for debug pcap logging, if enabled. - * - * @param instance NfcDebugPcapWorker* instance, can be NULL - * @param tx_rx TX/RX context to log - * @param is_picc if true, record Flipper as PICC, else PCD. - */ -void nfc_debug_pcap_prepare_tx_rx( - NfcDebugPcapWorker* instance, - FuriHalNfcTxRxContext* tx_rx, - bool is_picc); +void nfc_debug_pcap_process_data( + NfcDebugPcap* instance, + uint8_t* data, + uint16_t len, + bool reader_to_tag, + bool crc_dropped); diff --git a/lib/nfc/helpers/reader_analyzer.c b/lib/nfc/helpers/reader_analyzer.c new file mode 100644 index 00000000000..680b8cef9cd --- /dev/null +++ b/lib/nfc/helpers/reader_analyzer.c @@ -0,0 +1,261 @@ +#include "reader_analyzer.h" +#include +#include +#include +#include + +#include "mfkey32.h" +#include "nfc_debug_pcap.h" +#include "nfc_debug_log.h" + +#define TAG "ReaderAnalyzer" + +#define READER_ANALYZER_MAX_BUFF_SIZE (1024) + +typedef struct { + bool reader_to_tag; + bool crc_dropped; + uint16_t len; +} ReaderAnalyzerHeader; + +typedef enum { + ReaderAnalyzerNfcDataMfClassic, +} ReaderAnalyzerNfcData; + +struct ReaderAnalyzer { + FuriHalNfcDevData nfc_data; + + bool alive; + StreamBufferHandle_t stream; + FuriThread* thread; + + ReaderAnalyzerParseDataCallback callback; + void* context; + + ReaderAnalyzerMode mode; + Mfkey32* mfkey32; + NfcDebugLog* debug_log; + NfcDebugPcap* pcap; +}; + +const FuriHalNfcDevData reader_analyzer_nfc_data[] = { + [ReaderAnalyzerNfcDataMfClassic] = + {.sak = 0x08, + .atqa = {0x44, 0x00}, + .interface = FuriHalNfcInterfaceRf, + .type = FuriHalNfcTypeA, + .uid_len = 7, + .uid = {0x04, 0x77, 0x70, 0x2A, 0x23, 0x4F, 0x80}, + .cuid = 0x2A234F80}, +}; + +void reader_analyzer_parse(ReaderAnalyzer* instance, uint8_t* buffer, size_t size) { + if(size < sizeof(ReaderAnalyzerHeader)) return; + + size_t bytes_i = 0; + while(bytes_i < size) { + ReaderAnalyzerHeader* header = (ReaderAnalyzerHeader*)&buffer[bytes_i]; + uint16_t len = header->len; + if(bytes_i + len > size) break; + bytes_i += sizeof(ReaderAnalyzerHeader); + if(instance->mfkey32) { + mfkey32_process_data( + instance->mfkey32, + &buffer[bytes_i], + len, + header->reader_to_tag, + header->crc_dropped); + } + if(instance->pcap) { + nfc_debug_pcap_process_data( + instance->pcap, &buffer[bytes_i], len, header->reader_to_tag, header->crc_dropped); + } + if(instance->debug_log) { + nfc_debug_log_process_data( + instance->debug_log, + &buffer[bytes_i], + len, + header->reader_to_tag, + header->crc_dropped); + } + bytes_i += len; + } +} + +int32_t reader_analyzer_thread(void* context) { + ReaderAnalyzer* reader_analyzer = context; + uint8_t buffer[READER_ANALYZER_MAX_BUFF_SIZE] = {}; + + while(reader_analyzer->alive || !xStreamBufferIsEmpty(reader_analyzer->stream)) { + size_t ret = xStreamBufferReceive( + reader_analyzer->stream, buffer, READER_ANALYZER_MAX_BUFF_SIZE, 50); + if(ret) { + reader_analyzer_parse(reader_analyzer, buffer, ret); + } + } + + return 0; +} + +ReaderAnalyzer* reader_analyzer_alloc() { + ReaderAnalyzer* instance = malloc(sizeof(ReaderAnalyzer)); + + instance->nfc_data = reader_analyzer_nfc_data[ReaderAnalyzerNfcDataMfClassic]; + instance->alive = false; + instance->stream = + xStreamBufferCreate(READER_ANALYZER_MAX_BUFF_SIZE, sizeof(ReaderAnalyzerHeader)); + + instance->thread = furi_thread_alloc(); + furi_thread_set_name(instance->thread, "ReaderAnalyzerWorker"); + furi_thread_set_stack_size(instance->thread, 2048); + furi_thread_set_callback(instance->thread, reader_analyzer_thread); + furi_thread_set_context(instance->thread, instance); + furi_thread_set_priority(instance->thread, FuriThreadPriorityLow); + + return instance; +} + +static void reader_analyzer_mfkey_callback(Mfkey32Event event, void* context) { + furi_assert(context); + ReaderAnalyzer* instance = context; + + if(event == Mfkey32EventParamCollected) { + if(instance->callback) { + instance->callback(ReaderAnalyzerEventMfkeyCollected, instance->context); + } + } +} + +void reader_analyzer_start(ReaderAnalyzer* instance, ReaderAnalyzerMode mode) { + furi_assert(instance); + + xStreamBufferReset(instance->stream); + if(mode & ReaderAnalyzerModeDebugLog) { + instance->debug_log = nfc_debug_log_alloc(); + } + if(mode & ReaderAnalyzerModeMfkey) { + instance->mfkey32 = mfkey32_alloc(instance->nfc_data.cuid); + if(instance->mfkey32) { + mfkey32_set_callback(instance->mfkey32, reader_analyzer_mfkey_callback, instance); + } + } + if(mode & ReaderAnalyzerModeDebugPcap) { + instance->pcap = nfc_debug_pcap_alloc(); + } + + instance->alive = true; + furi_thread_start(instance->thread); +} + +void reader_analyzer_stop(ReaderAnalyzer* instance) { + furi_assert(instance); + + instance->alive = false; + furi_thread_join(instance->thread); + + if(instance->debug_log) { + nfc_debug_log_free(instance->debug_log); + instance->debug_log = NULL; + } + if(instance->mfkey32) { + mfkey32_free(instance->mfkey32); + instance->mfkey32 = NULL; + } + if(instance->pcap) { + nfc_debug_pcap_free(instance->pcap); + } +} + +void reader_analyzer_free(ReaderAnalyzer* instance) { + furi_assert(instance); + + reader_analyzer_stop(instance); + furi_thread_free(instance->thread); + vStreamBufferDelete(instance->stream); + free(instance); +} + +void reader_analyzer_set_callback( + ReaderAnalyzer* instance, + ReaderAnalyzerParseDataCallback callback, + void* context) { + furi_assert(instance); + furi_assert(callback); + + instance->callback = callback; + instance->context = context; +} + +NfcProtocol + reader_analyzer_guess_protocol(ReaderAnalyzer* instance, uint8_t* buff_rx, uint16_t len) { + furi_assert(instance); + furi_assert(buff_rx); + UNUSED(len); + NfcProtocol protocol = NfcDeviceProtocolUnknown; + + if((buff_rx[0] == 0x60) || (buff_rx[0] == 0x61)) { + protocol = NfcDeviceProtocolMifareClassic; + } + + return protocol; +} + +FuriHalNfcDevData* reader_analyzer_get_nfc_data(ReaderAnalyzer* instance) { + furi_assert(instance); + + return &instance->nfc_data; +} + +static void reader_analyzer_write( + ReaderAnalyzer* instance, + uint8_t* data, + uint16_t len, + bool reader_to_tag, + bool crc_dropped) { + ReaderAnalyzerHeader header = { + .reader_to_tag = reader_to_tag, .crc_dropped = crc_dropped, .len = len}; + size_t data_sent = 0; + data_sent = xStreamBufferSend( + instance->stream, &header, sizeof(ReaderAnalyzerHeader), FuriWaitForever); + if(data_sent != sizeof(ReaderAnalyzerHeader)) { + FURI_LOG_W(TAG, "Sent %d out of %d bytes", data_sent, sizeof(ReaderAnalyzerHeader)); + } + data_sent = xStreamBufferSend(instance->stream, data, len, FuriWaitForever); + if(data_sent != len) { + FURI_LOG_W(TAG, "Sent %d out of %d bytes", data_sent, len); + } +} + +static void + reader_analyzer_write_rx(uint8_t* data, uint16_t bits, bool crc_dropped, void* context) { + UNUSED(crc_dropped); + ReaderAnalyzer* reader_analyzer = context; + uint16_t bytes = bits < 8 ? 1 : bits / 8; + reader_analyzer_write(reader_analyzer, data, bytes, false, crc_dropped); +} + +static void + reader_analyzer_write_tx(uint8_t* data, uint16_t bits, bool crc_dropped, void* context) { + UNUSED(crc_dropped); + ReaderAnalyzer* reader_analyzer = context; + uint16_t bytes = bits < 8 ? 1 : bits / 8; + reader_analyzer_write(reader_analyzer, data, bytes, true, crc_dropped); +} + +void reader_analyzer_prepare_tx_rx( + ReaderAnalyzer* instance, + FuriHalNfcTxRxContext* tx_rx, + bool is_picc) { + furi_assert(instance); + furi_assert(tx_rx); + + if(is_picc) { + tx_rx->sniff_tx = reader_analyzer_write_rx; + tx_rx->sniff_rx = reader_analyzer_write_tx; + } else { + tx_rx->sniff_rx = reader_analyzer_write_rx; + tx_rx->sniff_tx = reader_analyzer_write_tx; + } + + tx_rx->sniff_context = instance; +} diff --git a/lib/nfc/helpers/reader_analyzer.h b/lib/nfc/helpers/reader_analyzer.h new file mode 100644 index 00000000000..cc501f5a63b --- /dev/null +++ b/lib/nfc/helpers/reader_analyzer.h @@ -0,0 +1,41 @@ +#pragma once + +#include +#include + +typedef enum { + ReaderAnalyzerModeDebugLog = 0x01, + ReaderAnalyzerModeMfkey = 0x02, + ReaderAnalyzerModeDebugPcap = 0x04, +} ReaderAnalyzerMode; + +typedef enum { + ReaderAnalyzerEventMfkeyCollected, +} ReaderAnalyzerEvent; + +typedef struct ReaderAnalyzer ReaderAnalyzer; + +typedef void (*ReaderAnalyzerParseDataCallback)(ReaderAnalyzerEvent event, void* context); + +ReaderAnalyzer* reader_analyzer_alloc(); + +void reader_analyzer_free(ReaderAnalyzer* instance); + +void reader_analyzer_set_callback( + ReaderAnalyzer* instance, + ReaderAnalyzerParseDataCallback callback, + void* context); + +void reader_analyzer_start(ReaderAnalyzer* instance, ReaderAnalyzerMode mode); + +void reader_analyzer_stop(ReaderAnalyzer* instance); + +NfcProtocol + reader_analyzer_guess_protocol(ReaderAnalyzer* instance, uint8_t* buff_rx, uint16_t len); + +FuriHalNfcDevData* reader_analyzer_get_nfc_data(ReaderAnalyzer* instance); + +void reader_analyzer_prepare_tx_rx( + ReaderAnalyzer* instance, + FuriHalNfcTxRxContext* tx_rx, + bool is_picc); diff --git a/lib/nfc/nfc_worker.c b/lib/nfc/nfc_worker.c index f07ea616ced..6355f8d1ea2 100644 --- a/lib/nfc/nfc_worker.c +++ b/lib/nfc/nfc_worker.c @@ -28,9 +28,7 @@ NfcWorker* nfc_worker_alloc() { } nfc_worker_change_state(nfc_worker, NfcWorkerStateReady); - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - nfc_worker->debug_pcap_worker = nfc_debug_pcap_alloc(nfc_worker->storage); - } + nfc_worker->reader_analyzer = reader_analyzer_alloc(nfc_worker->storage); return nfc_worker; } @@ -42,7 +40,7 @@ void nfc_worker_free(NfcWorker* nfc_worker) { furi_record_close(RECORD_STORAGE); - if(nfc_worker->debug_pcap_worker) nfc_debug_pcap_free(nfc_worker->debug_pcap_worker); + reader_analyzer_free(nfc_worker->reader_analyzer); free(nfc_worker); } @@ -105,6 +103,8 @@ int32_t nfc_worker_task(void* context) { nfc_worker_mf_ultralight_read_auth(nfc_worker); } else if(nfc_worker->state == NfcWorkerStateMfClassicDictAttack) { nfc_worker_mf_classic_dict_attack(nfc_worker); + } else if(nfc_worker->state == NfcWorkerStateAnalyzeReader) { + nfc_worker_analyze_reader(nfc_worker); } furi_hal_nfc_sleep(); nfc_worker_change_state(nfc_worker, NfcWorkerStateReady); @@ -117,7 +117,11 @@ static bool nfc_worker_read_mf_ultralight(NfcWorker* nfc_worker, FuriHalNfcTxRxC MfUltralightReader reader = {}; MfUltralightData data = {}; - nfc_debug_pcap_prepare_tx_rx(nfc_worker->debug_pcap_worker, tx_rx, false); + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + reader_analyzer_prepare_tx_rx(nfc_worker->reader_analyzer, tx_rx, false); + reader_analyzer_start(nfc_worker->reader_analyzer, ReaderAnalyzerModeDebugLog); + } + do { // Read card if(!furi_hal_nfc_detect(&nfc_worker->dev_data->nfc_data, 200)) break; @@ -127,6 +131,10 @@ static bool nfc_worker_read_mf_ultralight(NfcWorker* nfc_worker, FuriHalNfcTxRxC read_success = true; } while(false); + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + reader_analyzer_stop(nfc_worker->reader_analyzer); + } + return read_success; } @@ -134,7 +142,11 @@ static bool nfc_worker_read_mf_classic(NfcWorker* nfc_worker, FuriHalNfcTxRxCont furi_assert(nfc_worker->callback); bool read_success = false; - nfc_debug_pcap_prepare_tx_rx(nfc_worker->debug_pcap_worker, tx_rx, false); + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + reader_analyzer_prepare_tx_rx(nfc_worker->reader_analyzer, tx_rx, false); + reader_analyzer_start(nfc_worker->reader_analyzer, ReaderAnalyzerModeDebugLog); + } + do { // Try to read supported card FURI_LOG_I(TAG, "Try read supported card ..."); @@ -162,6 +174,9 @@ static bool nfc_worker_read_mf_classic(NfcWorker* nfc_worker, FuriHalNfcTxRxCont } } while(false); + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + reader_analyzer_stop(nfc_worker->reader_analyzer); + } return read_success; } @@ -169,13 +184,21 @@ static bool nfc_worker_read_mf_desfire(NfcWorker* nfc_worker, FuriHalNfcTxRxCont bool read_success = false; MifareDesfireData* data = &nfc_worker->dev_data->mf_df_data; - nfc_debug_pcap_prepare_tx_rx(nfc_worker->debug_pcap_worker, tx_rx, false); + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + reader_analyzer_prepare_tx_rx(nfc_worker->reader_analyzer, tx_rx, false); + reader_analyzer_start(nfc_worker->reader_analyzer, ReaderAnalyzerModeDebugLog); + } + do { if(!furi_hal_nfc_detect(&nfc_worker->dev_data->nfc_data, 300)) break; if(!mf_df_read_card(tx_rx, data)) break; read_success = true; } while(false); + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + reader_analyzer_stop(nfc_worker->reader_analyzer); + } + return read_success; } @@ -184,7 +207,11 @@ static bool nfc_worker_read_bank_card(NfcWorker* nfc_worker, FuriHalNfcTxRxConte EmvApplication emv_app = {}; EmvData* result = &nfc_worker->dev_data->emv_data; - nfc_debug_pcap_prepare_tx_rx(nfc_worker->debug_pcap_worker, tx_rx, false); + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + reader_analyzer_prepare_tx_rx(nfc_worker->reader_analyzer, tx_rx, false); + reader_analyzer_start(nfc_worker->reader_analyzer, ReaderAnalyzerModeDebugLog); + } + do { // Read card if(!furi_hal_nfc_detect(&nfc_worker->dev_data->nfc_data, 300)) break; @@ -211,6 +238,10 @@ static bool nfc_worker_read_bank_card(NfcWorker* nfc_worker, FuriHalNfcTxRxConte read_success = true; } while(false); + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + reader_analyzer_stop(nfc_worker->reader_analyzer); + } + return read_success; } @@ -320,18 +351,14 @@ void nfc_worker_read(NfcWorker* nfc_worker) { void nfc_worker_emulate_uid(NfcWorker* nfc_worker) { FuriHalNfcTxRxContext tx_rx = {}; - nfc_debug_pcap_prepare_tx_rx(nfc_worker->debug_pcap_worker, &tx_rx, true); FuriHalNfcDevData* data = &nfc_worker->dev_data->nfc_data; NfcReaderRequestData* reader_data = &nfc_worker->dev_data->reader_data; // TODO add support for RATS - // Now remove bit 6 in SAK to support ISO-14443A-3 emulation // Need to save ATS to support ISO-14443A-4 emulation - uint8_t sak = data->sak; - FURI_BIT_CLEAR(sak, 5); while(nfc_worker->state == NfcWorkerStateUidEmulate) { - if(furi_hal_nfc_listen(data->uid, data->uid_len, data->atqa, sak, true, 100)) { + if(furi_hal_nfc_listen(data->uid, data->uid_len, data->atqa, data->sak, false, 100)) { if(furi_hal_nfc_tx_rx(&tx_rx, 100)) { reader_data->size = tx_rx.rx_bits / 8; if(reader_data->size > 0) { @@ -349,7 +376,6 @@ void nfc_worker_emulate_uid(NfcWorker* nfc_worker) { void nfc_worker_emulate_apdu(NfcWorker* nfc_worker) { FuriHalNfcTxRxContext tx_rx = {}; - nfc_debug_pcap_prepare_tx_rx(nfc_worker->debug_pcap_worker, &tx_rx, true); FuriHalNfcDevData params = { .uid = {0xCF, 0x72, 0xd4, 0x40}, .uid_len = 4, @@ -358,6 +384,11 @@ void nfc_worker_emulate_apdu(NfcWorker* nfc_worker) { .type = FuriHalNfcTypeA, }; + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + reader_analyzer_prepare_tx_rx(nfc_worker->reader_analyzer, &tx_rx, true); + reader_analyzer_start(nfc_worker->reader_analyzer, ReaderAnalyzerModeDebugLog); + } + while(nfc_worker->state == NfcWorkerStateEmulateApdu) { if(furi_hal_nfc_listen(params.uid, params.uid_len, params.atqa, params.sak, false, 300)) { FURI_LOG_D(TAG, "POS terminal detected"); @@ -370,6 +401,10 @@ void nfc_worker_emulate_apdu(NfcWorker* nfc_worker) { furi_hal_nfc_sleep(); furi_delay_ms(20); } + + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + reader_analyzer_stop(nfc_worker->reader_analyzer); + } } void nfc_worker_emulate_mf_ultralight(NfcWorker* nfc_worker) { @@ -484,7 +519,6 @@ void nfc_worker_mf_classic_dict_attack(NfcWorker* nfc_worker) { void nfc_worker_emulate_mf_classic(NfcWorker* nfc_worker) { FuriHalNfcTxRxContext tx_rx = {}; - nfc_debug_pcap_prepare_tx_rx(nfc_worker->debug_pcap_worker, &tx_rx, true); FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data; MfClassicEmulator emulator = { .cuid = nfc_util_bytes2num(&nfc_data->uid[nfc_data->uid_len - 4], 4), @@ -525,6 +559,11 @@ void nfc_worker_mf_ultralight_read_auth(NfcWorker* nfc_worker) { MfUltralightReader reader = {}; mf_ul_reset(data); + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + reader_analyzer_prepare_tx_rx(nfc_worker->reader_analyzer, &tx_rx, true); + reader_analyzer_start(nfc_worker->reader_analyzer, ReaderAnalyzerModeDebugLog); + } + uint32_t key = 0; uint16_t pack = 0; while(nfc_worker->state == NfcWorkerStateReadMfUltralightReadAuth) { @@ -577,4 +616,61 @@ void nfc_worker_mf_ultralight_read_auth(NfcWorker* nfc_worker) { furi_delay_ms(10); } } + + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + reader_analyzer_stop(nfc_worker->reader_analyzer); + } +} + +static void nfc_worker_reader_analyzer_callback(ReaderAnalyzerEvent event, void* context) { + furi_assert(context); + NfcWorker* nfc_worker = context; + + if(event == ReaderAnalyzerEventMfkeyCollected) { + if(nfc_worker->callback) { + nfc_worker->callback(NfcWorkerEventDetectReaderMfkeyCollected, nfc_worker->context); + } + } +} + +void nfc_worker_analyze_reader(NfcWorker* nfc_worker) { + FuriHalNfcTxRxContext tx_rx = {}; + + ReaderAnalyzer* reader_analyzer = nfc_worker->reader_analyzer; + FuriHalNfcDevData* nfc_data = reader_analyzer_get_nfc_data(reader_analyzer); + 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; + reader_analyzer_prepare_tx_rx(reader_analyzer, &tx_rx, true); + reader_analyzer_start(nfc_worker->reader_analyzer, ReaderAnalyzerModeMfkey); + reader_analyzer_set_callback(reader_analyzer, nfc_worker_reader_analyzer_callback, nfc_worker); + + rfal_platform_spi_acquire(); + + FURI_LOG_D(TAG, "Start reader analyzer"); + while(nfc_worker->state == NfcWorkerStateAnalyzeReader) { + furi_hal_nfc_stop_cmd(); + furi_delay_ms(5); + furi_hal_nfc_listen_start(nfc_data); + if(furi_hal_nfc_listen_rx(&tx_rx, 300)) { + NfcProtocol protocol = + reader_analyzer_guess_protocol(reader_analyzer, tx_rx.rx_data, tx_rx.rx_bits / 8); + if(protocol == NfcDeviceProtocolMifareClassic) { + mf_classic_emulator(&emulator, &tx_rx); + } + } else { + FURI_LOG_D(TAG, "No data from reader"); + continue; + } + } + + rfal_platform_spi_release(); + + reader_analyzer_stop(nfc_worker->reader_analyzer); + + nfca_signal_free(nfca_signal); } diff --git a/lib/nfc/nfc_worker.h b/lib/nfc/nfc_worker.h index 22cbc3dcc99..b7bf4da9ad9 100644 --- a/lib/nfc/nfc_worker.h +++ b/lib/nfc/nfc_worker.h @@ -16,6 +16,7 @@ typedef enum { NfcWorkerStateMfClassicEmulate, NfcWorkerStateReadMfUltralightReadAuth, NfcWorkerStateMfClassicDictAttack, + NfcWorkerStateAnalyzeReader, // Debug NfcWorkerStateEmulateApdu, NfcWorkerStateField, @@ -54,8 +55,12 @@ typedef enum { NfcWorkerEventFoundKeyA, NfcWorkerEventFoundKeyB, + // Detect Reader events + NfcWorkerEventDetectReaderMfkeyCollected, + // Mifare Ultralight events NfcWorkerEventMfUltralightPassKey, + } NfcWorkerEvent; typedef bool (*NfcWorkerCallback)(NfcWorkerEvent event, void* context); diff --git a/lib/nfc/nfc_worker_i.h b/lib/nfc/nfc_worker_i.h index 1e98879a7da..526182f9a99 100644 --- a/lib/nfc/nfc_worker_i.h +++ b/lib/nfc/nfc_worker_i.h @@ -12,8 +12,7 @@ #include #include #include - -#include "helpers/nfc_debug_pcap.h" +#include struct NfcWorker { FuriThread* thread; @@ -27,7 +26,7 @@ struct NfcWorker { NfcWorkerState state; - NfcDebugPcapWorker* debug_pcap_worker; + ReaderAnalyzer* reader_analyzer; }; void nfc_worker_change_state(NfcWorker* nfc_worker, NfcWorkerState state); @@ -49,3 +48,5 @@ void nfc_worker_mf_ultralight_read_auth(NfcWorker* nfc_worker); void nfc_worker_mf_ul_auth_attack(NfcWorker* nfc_worker); void nfc_worker_emulate_apdu(NfcWorker* nfc_worker); + +void nfc_worker_analyze_reader(NfcWorker* nfc_worker); From 97b27261d5a570972a76399297a8c3551c3ea045 Mon Sep 17 00:00:00 2001 From: Dig Date: Sat, 3 Sep 2022 13:53:43 +0100 Subject: [PATCH 044/824] fbt: fbtenv_chck_many_source, fix typos + grep logic (#1699) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- scripts/toolchain/fbtenv.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/toolchain/fbtenv.sh b/scripts/toolchain/fbtenv.sh index 1c56c03cc48..da03df220b2 100755 --- a/scripts/toolchain/fbtenv.sh +++ b/scripts/toolchain/fbtenv.sh @@ -64,13 +64,13 @@ fbtenv_check_sourced() fbtenv_chck_many_source() { - if ! echo "${PS1:-""}" | grep -q "[fbt]"; then - if ! echo "${PROMPT:-""}" | grep -q "[fbt]"; then + if ! echo "${PS1:-""}" | grep -qF "[fbt]"; then + if ! echo "${PROMPT:-""}" | grep -qF "[fbt]"; then return 0; fi fi - echo "Warning! It script seen to be sourced more then once!"; - echo "It may signalise what you are making some mistakes, please open a new shell!"; + echo "Warning! FBT environment script sourced more than once!"; + echo "This may signal that you are making mistakes, please open a new shell!"; return 1; } From 8d8481b17fdcd0b2f4303b2e88742a94e980c989 Mon Sep 17 00:00:00 2001 From: VVX7 <46228229+VVX7@users.noreply.github.com> Date: Mon, 5 Sep 2022 07:42:59 -0400 Subject: [PATCH 045/824] fix buffer overflow in mifar ul load (#1697) Co-authored-by: gornekich --- lib/nfc/nfc_device.c | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/nfc/nfc_device.c b/lib/nfc/nfc_device.c index c46919219a6..ea06ae70f89 100644 --- a/lib/nfc/nfc_device.c +++ b/lib/nfc/nfc_device.c @@ -195,6 +195,7 @@ bool nfc_device_load_mifare_ul_data(FlipperFormat* file, NfcDevice* dev) { } data->data_size = pages_total * 4; data->data_read = pages_read * 4; + if(data->data_size > MF_UL_MAX_DUMP_SIZE || data->data_read > MF_UL_MAX_DUMP_SIZE) break; bool pages_parsed = true; for(uint16_t i = 0; i < pages_total; i++) { string_printf(temp_str, "Page %d", i); From b7a6d18186cdfa419f168c00fe345448cfb3aaca Mon Sep 17 00:00:00 2001 From: Max Andreev Date: Mon, 5 Sep 2022 16:40:54 +0300 Subject: [PATCH 046/824] Fix CI/CD in tags #1703 --- .github/workflows/amap_analyse.yml | 8 +++++--- .github/workflows/build.yml | 18 ++++++++++-------- .github/workflows/pvs_studio.yml | 8 +++++--- scripts/get_env.py | 13 ++++++++++--- 4 files changed, 30 insertions(+), 17 deletions(-) diff --git a/.github/workflows/amap_analyse.yml b/.github/workflows/amap_analyse.yml index 36352978444..6be99c9d1e3 100644 --- a/.github/workflows/amap_analyse.yml +++ b/.github/workflows/amap_analyse.yml @@ -46,12 +46,14 @@ jobs: - name: 'Get commit details' run: | - FBT_TOOLCHAIN_PATH=/opt source scripts/toolchain/fbtenv.sh if [[ ${{ github.event_name }} == 'pull_request' ]]; then - python3 scripts/get_env.py "--event_file=${{ github.event_path }}" "--is_pull" + TYPE="pull" + elif [[ "${{ github.ref }}" == "refs/tags/"* ]]; then + TYPE="tag" else - python3 scripts/get_env.py "--event_file=${{ github.event_path }}" + TYPE="other" fi + python3 scripts/get_env.py "--event_file=${{ github.event_path }}" "--type=$TYPE" - name: 'Make artifacts directory' run: | diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 530bbc9ee83..15b3966a774 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -36,12 +36,14 @@ jobs: - name: 'Get commit details' run: | - FBT_TOOLCHAIN_PATH=/opt source scripts/toolchain/fbtenv.sh if [[ ${{ github.event_name }} == 'pull_request' ]]; then - python3 scripts/get_env.py "--event_file=${{ github.event_path }}" "--is_pull" + TYPE="pull" + elif [[ "${{ github.ref }}" == "refs/tags/"* ]]; then + TYPE="tag" else - python3 scripts/get_env.py "--event_file=${{ github.event_path }}" + TYPE="other" fi + python3 scripts/get_env.py "--event_file=${{ github.event_path }}" "--type=$TYPE" - name: 'Generate suffixes for comment' id: names @@ -159,14 +161,14 @@ jobs: - name: 'Get commit details' run: | - FBT_TOOLCHAIN_PATH=/opt source scripts/toolchain/fbtenv.sh if [[ ${{ github.event_name }} == 'pull_request' ]]; then - python3 scripts/get_env.py "--event_file=${{ github.event_path }}" "--is_pull" + TYPE="pull" + elif [[ "${{ github.ref }}" == "refs/tags/"* ]]; then + TYPE="tag" else - python3 scripts/get_env.py "--event_file=${{ github.event_path }}" + TYPE="other" fi - echo "WORKFLOW_BRANCH_OR_TAG=${BRANCH_NAME}" >> $GITHUB_ENV - echo "DIST_SUFFIX=${SUFFIX}" >> $GITHUB_ENV + python3 scripts/get_env.py "--event_file=${{ github.event_path }}" "--type=$TYPE" - name: 'Build the firmware' run: | diff --git a/.github/workflows/pvs_studio.yml b/.github/workflows/pvs_studio.yml index 8ace58b6160..7bd71de4992 100644 --- a/.github/workflows/pvs_studio.yml +++ b/.github/workflows/pvs_studio.yml @@ -32,12 +32,14 @@ jobs: - name: 'Get commit details' run: | - FBT_TOOLCHAIN_PATH=/opt source scripts/toolchain/fbtenv.sh if [[ ${{ github.event_name }} == 'pull_request' ]]; then - python3 scripts/get_env.py "--event_file=${{ github.event_path }}" "--is_pull" + TYPE="pull" + elif [[ "${{ github.ref }}" == "refs/tags/"* ]]; then + TYPE="tag" else - python3 scripts/get_env.py "--event_file=${{ github.event_path }}" + TYPE="other" fi + python3 scripts/get_env.py "--event_file=${{ github.event_path }}" "--type=$TYPE" - name: 'Generate suffixes for comment' if: ${{ !github.event.pull_request.head.repo.fork && github.event.pull_request }} diff --git a/scripts/get_env.py b/scripts/get_env.py index 843a1e4067c..d0527309496 100644 --- a/scripts/get_env.py +++ b/scripts/get_env.py @@ -20,7 +20,10 @@ def parse_args(): parser = argparse.ArgumentParser() parser.add_argument("--event_file", help="Current GitHub event file", required=True) parser.add_argument( - "--is_pull", help="Is it Pull Request", default=False, action="store_true" + "--type", + help="Event file type", + required=True, + choices=["pull", "tag", "other"], ) args = parser.parse_args() return args @@ -38,13 +41,17 @@ def get_commit_json(event): def get_details(event, args): data = {} current_time = datetime.datetime.utcnow().date() - if args.is_pull: + if args.type == "pull": commit_json = get_commit_json(event) data["commit_comment"] = shlex.quote(commit_json[-1]["commit"]["message"]) data["commit_hash"] = commit_json[-1]["sha"] ref = event["pull_request"]["head"]["ref"] data["pull_id"] = event["pull_request"]["number"] data["pull_name"] = shlex.quote(event["pull_request"]["title"]) + elif args.type == "tag": + data["commit_comment"] = shlex.quote(event["head_commit"]["message"]) + data["commit_hash"] = event["head_commit"]["id"] + ref = event["ref"] else: data["commit_comment"] = shlex.quote(event["commits"][-1]["message"]) data["commit_hash"] = event["commits"][-1]["id"] @@ -78,7 +85,7 @@ def add_envs(data, env_file, args): add_env("BRANCH_NAME", data["branch_name"], env_file) add_env("DIST_SUFFIX", data["suffix"], env_file) add_env("WORKFLOW_BRANCH_OR_TAG", data["branch_name"], env_file) - if args.is_pull: + if args.type == "pull": add_env("PULL_ID", data["pull_id"], env_file) add_env("PULL_NAME", data["pull_name"], env_file) From 3a767c9c02372b9ec77bcbbd3e252b479fa382d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Tue, 6 Sep 2022 02:40:20 +0900 Subject: [PATCH 047/824] [FL-2794] Lib: update LFS to v2.5.0, lower update free page limit (#1706) * Lib: update lfs to v2.5.0 * Storage: set minimum free pages for update on internal storage to 3 * Updater: lower min int free space limit * lfs: disabled debug and trace logs by default Co-authored-by: hedger --- applications/storage/storages/storage_int.c | 2 +- lib/lfs_config.h | 97 ++++++++++++--------- lib/littlefs | 2 +- lib/update_util/update_operation.c | 2 +- 4 files changed, 59 insertions(+), 44 deletions(-) diff --git a/applications/storage/storages/storage_int.c b/applications/storage/storages/storage_int.c index 0765a92dc5c..cae61f16e47 100644 --- a/applications/storage/storages/storage_int.c +++ b/applications/storage/storages/storage_int.c @@ -9,7 +9,7 @@ /* When less than LFS_RESERVED_PAGES_COUNT are left free, creation & * modification of non-dot files is restricted */ -#define LFS_RESERVED_PAGES_COUNT 5 +#define LFS_RESERVED_PAGES_COUNT 3 typedef struct { const size_t start_address; diff --git a/lib/lfs_config.h b/lib/lfs_config.h index 59b3c486e33..ff8bc4b235f 100644 --- a/lib/lfs_config.h +++ b/lib/lfs_config.h @@ -5,21 +5,26 @@ #ifdef FURI_NDEBUG #define LFS_NO_ASSERT #define LFS_ASSERT(x) -#else +#else #define LFS_ASSERT furi_assert #endif #define LFS_TAG "Lfs" +#ifdef FURI_LFS_DEBUG #define LFS_TRACE(...) FURI_LOG_T(LFS_TAG, __VA_ARGS__); #define LFS_DEBUG(...) FURI_LOG_D(LFS_TAG, __VA_ARGS__); +#else +#define LFS_TRACE(...) + +#define LFS_DEBUG(...) +#endif // FURI_LFS_DEBUG #define LFS_WARN(...) FURI_LOG_W(LFS_TAG, __VA_ARGS__); #define LFS_ERROR(...) FURI_LOG_E(LFS_TAG, __VA_ARGS__); - // Because crc #undef LFS_CONFIG @@ -35,16 +40,13 @@ #ifndef LFS_NO_ASSERT #include #endif -#if !defined(LFS_NO_DEBUG) || \ - !defined(LFS_NO_WARN) || \ - !defined(LFS_NO_ERROR) || \ - defined(LFS_YES_TRACE) +#if !defined(LFS_NO_DEBUG) || !defined(LFS_NO_WARN) || !defined(LFS_NO_ERROR) || \ + defined(LFS_YES_TRACE) #include #endif #ifdef __cplusplus -extern "C" -{ +extern "C" { #endif // Builtin functions, these may be replaced by more efficient @@ -66,21 +68,29 @@ static inline uint32_t lfs_aligndown(uint32_t a, uint32_t alignment) { } static inline uint32_t lfs_alignup(uint32_t a, uint32_t alignment) { - return lfs_aligndown(a + alignment-1, alignment); + return lfs_aligndown(a + alignment - 1, alignment); } // Find the smallest power of 2 greater than or equal to a static inline uint32_t lfs_npw2(uint32_t a) { #if !defined(LFS_NO_INTRINSICS) && (defined(__GNUC__) || defined(__CC_ARM)) - return 32 - __builtin_clz(a-1); + return 32 - __builtin_clz(a - 1); #else uint32_t r = 0; uint32_t s; a -= 1; - s = (a > 0xffff) << 4; a >>= s; r |= s; - s = (a > 0xff ) << 3; a >>= s; r |= s; - s = (a > 0xf ) << 2; a >>= s; r |= s; - s = (a > 0x3 ) << 1; a >>= s; r |= s; + s = (a > 0xffff) << 4; + a >>= s; + r |= s; + s = (a > 0xff) << 3; + a >>= s; + r |= s; + s = (a > 0xf) << 2; + a >>= s; + r |= s; + s = (a > 0x3) << 1; + a >>= s; + r |= s; return (r | (a >> 1)) + 1; #endif } @@ -114,20 +124,23 @@ static inline int lfs_scmp(uint32_t a, uint32_t b) { // Convert between 32-bit little-endian and native order static inline uint32_t lfs_fromle32(uint32_t a) { -#if !defined(LFS_NO_INTRINSICS) && ( \ - (defined( BYTE_ORDER ) && defined( ORDER_LITTLE_ENDIAN ) && BYTE_ORDER == ORDER_LITTLE_ENDIAN ) || \ - (defined(__BYTE_ORDER ) && defined(__ORDER_LITTLE_ENDIAN ) && __BYTE_ORDER == __ORDER_LITTLE_ENDIAN ) || \ - (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)) +#if !defined(LFS_NO_INTRINSICS) && \ + ((defined(BYTE_ORDER) && defined(ORDER_LITTLE_ENDIAN) && \ + BYTE_ORDER == ORDER_LITTLE_ENDIAN) || \ + (defined(__BYTE_ORDER) && defined(__ORDER_LITTLE_ENDIAN) && \ + __BYTE_ORDER == __ORDER_LITTLE_ENDIAN) || \ + (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && \ + __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)) return a; -#elif !defined(LFS_NO_INTRINSICS) && ( \ - (defined( BYTE_ORDER ) && defined( ORDER_BIG_ENDIAN ) && BYTE_ORDER == ORDER_BIG_ENDIAN ) || \ - (defined(__BYTE_ORDER ) && defined(__ORDER_BIG_ENDIAN ) && __BYTE_ORDER == __ORDER_BIG_ENDIAN ) || \ - (defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)) +#elif !defined(LFS_NO_INTRINSICS) && \ + ((defined(BYTE_ORDER) && defined(ORDER_BIG_ENDIAN) && BYTE_ORDER == ORDER_BIG_ENDIAN) || \ + (defined(__BYTE_ORDER) && defined(__ORDER_BIG_ENDIAN) && \ + __BYTE_ORDER == __ORDER_BIG_ENDIAN) || \ + (defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && \ + __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)) return __builtin_bswap32(a); #else - return (((uint8_t*)&a)[0] << 0) | - (((uint8_t*)&a)[1] << 8) | - (((uint8_t*)&a)[2] << 16) | + return (((uint8_t*)&a)[0] << 0) | (((uint8_t*)&a)[1] << 8) | (((uint8_t*)&a)[2] << 16) | (((uint8_t*)&a)[3] << 24); #endif } @@ -138,21 +151,24 @@ static inline uint32_t lfs_tole32(uint32_t a) { // Convert between 32-bit big-endian and native order static inline uint32_t lfs_frombe32(uint32_t a) { -#if !defined(LFS_NO_INTRINSICS) && ( \ - (defined( BYTE_ORDER ) && defined( ORDER_LITTLE_ENDIAN ) && BYTE_ORDER == ORDER_LITTLE_ENDIAN ) || \ - (defined(__BYTE_ORDER ) && defined(__ORDER_LITTLE_ENDIAN ) && __BYTE_ORDER == __ORDER_LITTLE_ENDIAN ) || \ - (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)) +#if !defined(LFS_NO_INTRINSICS) && \ + ((defined(BYTE_ORDER) && defined(ORDER_LITTLE_ENDIAN) && \ + BYTE_ORDER == ORDER_LITTLE_ENDIAN) || \ + (defined(__BYTE_ORDER) && defined(__ORDER_LITTLE_ENDIAN) && \ + __BYTE_ORDER == __ORDER_LITTLE_ENDIAN) || \ + (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && \ + __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)) return __builtin_bswap32(a); -#elif !defined(LFS_NO_INTRINSICS) && ( \ - (defined( BYTE_ORDER ) && defined( ORDER_BIG_ENDIAN ) && BYTE_ORDER == ORDER_BIG_ENDIAN ) || \ - (defined(__BYTE_ORDER ) && defined(__ORDER_BIG_ENDIAN ) && __BYTE_ORDER == __ORDER_BIG_ENDIAN ) || \ - (defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)) +#elif !defined(LFS_NO_INTRINSICS) && \ + ((defined(BYTE_ORDER) && defined(ORDER_BIG_ENDIAN) && BYTE_ORDER == ORDER_BIG_ENDIAN) || \ + (defined(__BYTE_ORDER) && defined(__ORDER_BIG_ENDIAN) && \ + __BYTE_ORDER == __ORDER_BIG_ENDIAN) || \ + (defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && \ + __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)) return a; #else - return (((uint8_t*)&a)[0] << 24) | - (((uint8_t*)&a)[1] << 16) | - (((uint8_t*)&a)[2] << 8) | - (((uint8_t*)&a)[3] << 0); + return (((uint8_t*)&a)[0] << 24) | (((uint8_t*)&a)[1] << 16) | (((uint8_t*)&a)[2] << 8) | + (((uint8_t*)&a)[3] << 0); #endif } @@ -161,11 +177,11 @@ static inline uint32_t lfs_tobe32(uint32_t a) { } // Calculate CRC-32 with polynomial = 0x04c11db7 -uint32_t lfs_crc(uint32_t crc, const void *buffer, size_t size); +uint32_t lfs_crc(uint32_t crc, const void* buffer, size_t size); // Allocate memory, only used if buffers are not provided to littlefs // Note, memory must be 64-bit aligned -static inline void *lfs_malloc(size_t size) { +static inline void* lfs_malloc(size_t size) { #ifndef LFS_NO_MALLOC return malloc(size); #else @@ -175,7 +191,7 @@ static inline void *lfs_malloc(size_t size) { } // Deallocate memory, only used if buffers are not provided to littlefs -static inline void lfs_free(void *p) { +static inline void lfs_free(void* p) { #ifndef LFS_NO_MALLOC free(p); #else @@ -183,7 +199,6 @@ static inline void lfs_free(void *p) { #endif } - #ifdef __cplusplus } /* extern "C" */ #endif diff --git a/lib/littlefs b/lib/littlefs index 1863dc7883d..40dba4a556e 160000 --- a/lib/littlefs +++ b/lib/littlefs @@ -1 +1 @@ -Subproject commit 1863dc7883d82bd6ca79faa164b65341064d1c16 +Subproject commit 40dba4a556e0d81dfbe64301a6aa4e18ceca896c diff --git a/lib/update_util/update_operation.c b/lib/update_util/update_operation.c index 138828ff0bf..56f412a95f6 100644 --- a/lib/update_util/update_operation.c +++ b/lib/update_util/update_operation.c @@ -12,7 +12,7 @@ #define UPDATE_ROOT_DIR EXT_PATH("update") /* Need at least 4 free LFS pages before update */ -#define UPDATE_MIN_INT_FREE_SPACE 4 * 4 * 1024 +#define UPDATE_MIN_INT_FREE_SPACE 2 * 4 * 1024 static const char* update_prepare_result_descr[] = { [UpdatePrepareResultOK] = "OK", From d1c79a83dea7a7be7f36eb14c186926dda498a6d Mon Sep 17 00:00:00 2001 From: Max Andreev Date: Tue, 6 Sep 2022 14:19:34 +0300 Subject: [PATCH 048/824] New toolchain 15 (#1709) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- scripts/toolchain/fbtenv.cmd | 2 +- scripts/toolchain/fbtenv.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/toolchain/fbtenv.cmd b/scripts/toolchain/fbtenv.cmd index cf5d2441af8..1403837d940 100644 --- a/scripts/toolchain/fbtenv.cmd +++ b/scripts/toolchain/fbtenv.cmd @@ -13,7 +13,7 @@ if not [%FBT_NOENV%] == [] ( exit /b 0 ) -set "FLIPPER_TOOLCHAIN_VERSION=12" +set "FLIPPER_TOOLCHAIN_VERSION=15" set "FBT_TOOLCHAIN_ROOT=%FBT_ROOT%\toolchain\i686-windows" diff --git a/scripts/toolchain/fbtenv.sh b/scripts/toolchain/fbtenv.sh index da03df220b2..f68e4c0bad8 100755 --- a/scripts/toolchain/fbtenv.sh +++ b/scripts/toolchain/fbtenv.sh @@ -5,7 +5,7 @@ # public variables DEFAULT_SCRIPT_PATH="$(pwd -P)"; SCRIPT_PATH="${SCRIPT_PATH:-$DEFAULT_SCRIPT_PATH}"; -FBT_TOOLCHAIN_VERSION="${FBT_TOOLCHAIN_VERSION:-"12"}"; +FBT_TOOLCHAIN_VERSION="${FBT_TOOLCHAIN_VERSION:-"15"}"; FBT_TOOLCHAIN_PATH="${FBT_TOOLCHAIN_PATH:-$SCRIPT_PATH}"; fbtenv_show_usage() From ff33bc6aea43009ec2fd17a90a851160330b33f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Thu, 8 Sep 2022 15:47:23 +0900 Subject: [PATCH 049/824] Furi: wait for timer wind down in destructor (#1716) --- applications/gui/modules/text_input.c | 2 -- furi/core/timer.c | 29 ++++++++++++++------------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/applications/gui/modules/text_input.c b/applications/gui/modules/text_input.c index 26f74e7aca8..b8098a3b9f2 100644 --- a/applications/gui/modules/text_input.c +++ b/applications/gui/modules/text_input.c @@ -463,8 +463,6 @@ void text_input_free(TextInput* text_input) { // Send stop command furi_timer_stop(text_input->timer); - // Wait till timer stop - while(furi_timer_is_running(text_input->timer)) furi_delay_tick(1); // Release allocated memory furi_timer_free(text_input->timer); diff --git a/furi/core/timer.c b/furi/core/timer.c index 807f477e4ec..462a2e89ea0 100644 --- a/furi/core/timer.c +++ b/furi/core/timer.c @@ -1,5 +1,7 @@ #include "timer.h" #include "check.h" +#include "memmgr.h" +#include "kernel.h" #include "core/common_defines.h" #include @@ -39,7 +41,7 @@ FuriTimer* furi_timer_alloc(FuriTimerCallback func, FuriTimerType type, void* co /* Dynamic memory allocation is available: if memory for callback and */ /* its context is not provided, allocate it from dynamic memory pool */ if(callb == NULL) { - callb = (TimerCallback_t*)pvPortMalloc(sizeof(TimerCallback_t)); + callb = (TimerCallback_t*)malloc(sizeof(TimerCallback_t)); if(callb != NULL) { /* Callback memory was allocated from dynamic pool, set flag */ @@ -65,7 +67,7 @@ FuriTimer* furi_timer_alloc(FuriTimerCallback func, FuriTimerType type, void* co if((hTimer == NULL) && (callb != NULL) && (callb_dyn == 1U)) { /* Failed to create a timer, release allocated resources */ callb = (TimerCallback_t*)((uint32_t)callb & ~1U); - vPortFree(callb); + free(callb); } } @@ -82,14 +84,16 @@ void furi_timer_free(FuriTimer* instance) { callb = (TimerCallback_t*)pvTimerGetTimerID(hTimer); - if(xTimerDelete(hTimer, portMAX_DELAY) == pdPASS) { - if((uint32_t)callb & 1U) { - /* Callback memory was allocated from dynamic pool, clear flag */ - callb = (TimerCallback_t*)((uint32_t)callb & ~1U); + furi_check(xTimerDelete(hTimer, portMAX_DELAY) == pdPASS); - /* Return allocated memory to dynamic pool */ - vPortFree(callb); - } + while (furi_timer_is_running(instance)) furi_delay_tick(2); + + if((uint32_t)callb & 1U) { + /* Callback memory was allocated from dynamic pool, clear flag */ + callb = (TimerCallback_t*)((uint32_t)callb & ~1U); + + /* Return allocated memory to dynamic pool */ + free(callb); } } @@ -120,11 +124,8 @@ FuriStatus furi_timer_stop(FuriTimer* instance) { if(xTimerIsTimerActive(hTimer) == pdFALSE) { stat = FuriStatusErrorResource; } else { - if(xTimerStop(hTimer, portMAX_DELAY) == pdPASS) { - stat = FuriStatusOk; - } else { - stat = FuriStatusError; - } + furi_check(xTimerStop(hTimer, portMAX_DELAY) == pdPASS); + stat = FuriStatusOk; } /* Return execution status */ From b62096fb91f5f5a7be76cd60ff2ba7c550bad55a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Thu, 8 Sep 2022 16:18:18 +0900 Subject: [PATCH 050/824] [FL-2645, FL-2810] SubGhz: handle missing key in cryptostore. Lib: lower default display contrast. (#1717) * SubGhz: skip file loading on key load fail * Lib: update default display contrast * Format sources Co-authored-by: hedger --- lib/subghz/subghz_keystore.c | 99 +++++++++++++++++++----------------- lib/u8g2/u8g2_glue.c | 4 +- 2 files changed, 55 insertions(+), 48 deletions(-) diff --git a/lib/subghz/subghz_keystore.c b/lib/subghz/subghz_keystore.c index 0abd2d5e667..4d2eb0e50c6 100644 --- a/lib/subghz/subghz_keystore.c +++ b/lib/subghz/subghz_keystore.c @@ -114,62 +114,69 @@ static bool subghz_keystore_read_file(SubGhzKeystore* instance, Stream* stream, char* encrypted_line = malloc(SUBGHZ_KEYSTORE_FILE_ENCRYPTED_LINE_SIZE); size_t encrypted_line_cursor = 0; - if(iv) furi_hal_crypto_store_load_key(SUBGHZ_KEYSTORE_FILE_ENCRYPTION_KEY_SLOT, iv); - - size_t ret = 0; do { - ret = stream_read(stream, buffer, FILE_BUFFER_SIZE); - for(uint16_t i = 0; i < ret; i++) { - if(buffer[i] == '\n' && encrypted_line_cursor > 0) { - // Process line - if(iv) { - // Data alignment check, 32 instead of 16 because of hex encoding - size_t len = strlen(encrypted_line); - if(len % 32 == 0) { - // Inplace hex to bin conversion - for(size_t i = 0; i < len; i += 2) { - uint8_t hi_nibble = 0; - uint8_t lo_nibble = 0; - hex_char_to_hex_nibble(encrypted_line[i], &hi_nibble); - hex_char_to_hex_nibble(encrypted_line[i + 1], &lo_nibble); - encrypted_line[i / 2] = (hi_nibble << 4) | lo_nibble; - } - len /= 2; + if(iv) { + if(!furi_hal_crypto_store_load_key(SUBGHZ_KEYSTORE_FILE_ENCRYPTION_KEY_SLOT, iv)) { + FURI_LOG_E(TAG, "Unable to load decryption key"); + break; + } + } - if(furi_hal_crypto_decrypt( - (uint8_t*)encrypted_line, (uint8_t*)decrypted_line, len)) { - subghz_keystore_process_line(instance, decrypted_line); + size_t ret = 0; + do { + ret = stream_read(stream, buffer, FILE_BUFFER_SIZE); + for(uint16_t i = 0; i < ret; i++) { + if(buffer[i] == '\n' && encrypted_line_cursor > 0) { + // Process line + if(iv) { + // Data alignment check, 32 instead of 16 because of hex encoding + size_t len = strlen(encrypted_line); + if(len % 32 == 0) { + // Inplace hex to bin conversion + for(size_t i = 0; i < len; i += 2) { + uint8_t hi_nibble = 0; + uint8_t lo_nibble = 0; + hex_char_to_hex_nibble(encrypted_line[i], &hi_nibble); + hex_char_to_hex_nibble(encrypted_line[i + 1], &lo_nibble); + encrypted_line[i / 2] = (hi_nibble << 4) | lo_nibble; + } + len /= 2; + + if(furi_hal_crypto_decrypt( + (uint8_t*)encrypted_line, (uint8_t*)decrypted_line, len)) { + subghz_keystore_process_line(instance, decrypted_line); + } else { + FURI_LOG_E(TAG, "Decryption failed"); + result = false; + break; + } } else { - FURI_LOG_E(TAG, "Decryption failed"); - result = false; - break; + FURI_LOG_E(TAG, "Invalid encrypted data: %s", encrypted_line); } } else { - FURI_LOG_E(TAG, "Invalid encrypted data: %s", encrypted_line); + subghz_keystore_process_line(instance, encrypted_line); } + // reset line buffer + memset(decrypted_line, 0, SUBGHZ_KEYSTORE_FILE_DECRYPTED_LINE_SIZE); + memset(encrypted_line, 0, SUBGHZ_KEYSTORE_FILE_ENCRYPTED_LINE_SIZE); + encrypted_line_cursor = 0; + } else if(buffer[i] == '\r' || buffer[i] == '\n') { + // do not add line endings to the buffer } else { - subghz_keystore_process_line(instance, encrypted_line); - } - // reset line buffer - memset(decrypted_line, 0, SUBGHZ_KEYSTORE_FILE_DECRYPTED_LINE_SIZE); - memset(encrypted_line, 0, SUBGHZ_KEYSTORE_FILE_ENCRYPTED_LINE_SIZE); - encrypted_line_cursor = 0; - } else if(buffer[i] == '\r' || buffer[i] == '\n') { - // do not add line endings to the buffer - } else { - if(encrypted_line_cursor < SUBGHZ_KEYSTORE_FILE_ENCRYPTED_LINE_SIZE) { - encrypted_line[encrypted_line_cursor] = buffer[i]; - encrypted_line_cursor++; - } else { - FURI_LOG_E(TAG, "Malformed file"); - result = false; - break; + if(encrypted_line_cursor < SUBGHZ_KEYSTORE_FILE_ENCRYPTED_LINE_SIZE) { + encrypted_line[encrypted_line_cursor] = buffer[i]; + encrypted_line_cursor++; + } else { + FURI_LOG_E(TAG, "Malformed file"); + result = false; + break; + } } } - } - } while(ret > 0 && result); + } while(ret > 0 && result); - if(iv) furi_hal_crypto_store_unload_key(SUBGHZ_KEYSTORE_FILE_ENCRYPTION_KEY_SLOT); + if(iv) furi_hal_crypto_store_unload_key(SUBGHZ_KEYSTORE_FILE_ENCRYPTION_KEY_SLOT); + } while(false); free(encrypted_line); free(decrypted_line); diff --git a/lib/u8g2/u8g2_glue.c b/lib/u8g2/u8g2_glue.c index 77f37f14ccd..17a702b50fc 100644 --- a/lib/u8g2/u8g2_glue.c +++ b/lib/u8g2/u8g2_glue.c @@ -225,7 +225,7 @@ uint8_t u8x8_d_st756x_flipper(u8x8_t* u8x8, uint8_t msg, uint8_t arg_int, void* * RR = 10 / ((1 - (63 - 32) / 162) * 2.1) ~= 5.88 is 6 (0b110) * Bias = 1/9 (false) */ - u8x8_d_st756x_init(u8x8, 32, 0b110, false); + u8x8_d_st756x_init(u8x8, 31, 0b110, false); } else { /* ERC v1(ST7565) and v2(ST7567) * EV = 33 @@ -233,7 +233,7 @@ uint8_t u8x8_d_st756x_flipper(u8x8_t* u8x8, uint8_t msg, uint8_t arg_int, void* * RR = 9.3 / ((1 - (63 - 32) / 162) * 2.1) ~= 5.47 is 5.5 (0b101) * Bias = 1/9 (false) */ - u8x8_d_st756x_init(u8x8, 33, 0b101, false); + u8x8_d_st756x_init(u8x8, 32, 0b101, false); } break; case U8X8_MSG_DISPLAY_SET_FLIP_MODE: From e9ab581771eb2c70d37fc26037eda440234cc63e Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Thu, 8 Sep 2022 11:27:03 +0400 Subject: [PATCH 051/824] SubGhz: fix decoder kelooq (#1719) * SubGhz: fix decoder kelooq Co-authored-by: hedger --- lib/subghz/protocols/keeloq.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/subghz/protocols/keeloq.c b/lib/subghz/protocols/keeloq.c index 88738f3fb4b..0321a876773 100644 --- a/lib/subghz/protocols/keeloq.c +++ b/lib/subghz/protocols/keeloq.c @@ -407,7 +407,7 @@ void subghz_protocol_decoder_keeloq_feed(void* context, bool level, uint32_t dur (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_keeloq_const.te_short) < subghz_protocol_keeloq_const.te_delta) && (DURATION_DIFF(duration, subghz_protocol_keeloq_const.te_long) < - subghz_protocol_keeloq_const.te_delta)) { + subghz_protocol_keeloq_const.te_delta * 2)) { if(instance->decoder.decode_count_bit < subghz_protocol_keeloq_const.min_count_bit_for_found) { subghz_protocol_blocks_add_bit(&instance->decoder, 1); @@ -415,7 +415,7 @@ void subghz_protocol_decoder_keeloq_feed(void* context, bool level, uint32_t dur instance->decoder.parser_step = KeeloqDecoderStepSaveDuration; } else if( (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_keeloq_const.te_long) < - subghz_protocol_keeloq_const.te_delta) && + subghz_protocol_keeloq_const.te_delta * 2) && (DURATION_DIFF(duration, subghz_protocol_keeloq_const.te_short) < subghz_protocol_keeloq_const.te_delta)) { if(instance->decoder.decode_count_bit < From 0f6f9ad52e6912b8b15cd3dcff7065ceb0fb3116 Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Thu, 8 Sep 2022 19:40:33 +0300 Subject: [PATCH 052/824] [FL-2753] RFID app port to plain C (#1710) * LF RFID: port to plain C * LFRFID debug port to C, new reading screen * LFRFID debug: fix pvs-studio warnings * RFID read view: remove unused input callback * RFID read view: animation update --- applications/gui/modules/validators.c | 4 +- applications/lfrfid/lfrfid.c | 316 ++++++++++++++++++ applications/lfrfid/lfrfid_app.cpp | 218 ------------ applications/lfrfid/lfrfid_app.h | 137 -------- applications/lfrfid/lfrfid_app_launcher.cpp | 10 - applications/lfrfid/lfrfid_cli.c | 2 +- applications/lfrfid/lfrfid_i.h | 145 ++++++++ .../scene/lfrfid_app_scene_delete_confirm.cpp | 88 ----- .../scene/lfrfid_app_scene_delete_confirm.h | 16 - .../scene/lfrfid_app_scene_delete_success.cpp | 38 --- .../scene/lfrfid_app_scene_delete_success.h | 12 - .../lfrfid/scene/lfrfid_app_scene_emulate.cpp | 36 -- .../lfrfid/scene/lfrfid_app_scene_emulate.h | 9 - .../scene/lfrfid_app_scene_exit_confirm.cpp | 59 ---- .../scene/lfrfid_app_scene_exit_confirm.h | 13 - .../scene/lfrfid_app_scene_extra_actions.cpp | 63 ---- .../scene/lfrfid_app_scene_extra_actions.h | 13 - .../scene/lfrfid_app_scene_raw_info.cpp | 77 ----- .../lfrfid/scene/lfrfid_app_scene_raw_info.h | 12 - .../scene/lfrfid_app_scene_raw_name.cpp | 46 --- .../lfrfid/scene/lfrfid_app_scene_raw_name.h | 12 - .../scene/lfrfid_app_scene_raw_read.cpp | 107 ------ .../lfrfid/scene/lfrfid_app_scene_raw_read.h | 15 - .../scene/lfrfid_app_scene_raw_success.cpp | 45 --- .../scene/lfrfid_app_scene_raw_success.h | 13 - .../lfrfid/scene/lfrfid_app_scene_read.cpp | 100 ------ .../lfrfid/scene/lfrfid_app_scene_read.h | 9 - .../scene/lfrfid_app_scene_read_menu.cpp | 60 ---- .../lfrfid/scene/lfrfid_app_scene_read_menu.h | 13 - .../scene/lfrfid_app_scene_read_success.cpp | 96 ------ .../scene/lfrfid_app_scene_read_success.h | 16 - .../scene/lfrfid_app_scene_retry_confirm.cpp | 59 ---- .../scene/lfrfid_app_scene_retry_confirm.h | 13 - .../lfrfid/scene/lfrfid_app_scene_rpc.cpp | 66 ---- .../lfrfid/scene/lfrfid_app_scene_rpc.h | 12 - .../scene/lfrfid_app_scene_save_data.cpp | 45 --- .../lfrfid/scene/lfrfid_app_scene_save_data.h | 12 - .../scene/lfrfid_app_scene_save_name.cpp | 72 ---- .../lfrfid/scene/lfrfid_app_scene_save_name.h | 12 - .../scene/lfrfid_app_scene_save_success.cpp | 50 --- .../scene/lfrfid_app_scene_save_success.h | 12 - .../scene/lfrfid_app_scene_save_type.cpp | 53 --- .../lfrfid/scene/lfrfid_app_scene_save_type.h | 15 - .../scene/lfrfid_app_scene_saved_info.cpp | 48 --- .../scene/lfrfid_app_scene_saved_info.h | 12 - .../scene/lfrfid_app_scene_saved_key_menu.cpp | 67 ---- .../scene/lfrfid_app_scene_saved_key_menu.h | 13 - .../scene/lfrfid_app_scene_select_key.cpp | 16 - .../scene/lfrfid_app_scene_select_key.h | 9 - .../lfrfid/scene/lfrfid_app_scene_start.cpp | 67 ---- .../lfrfid/scene/lfrfid_app_scene_start.h | 13 - .../lfrfid/scene/lfrfid_app_scene_write.cpp | 88 ----- .../lfrfid/scene/lfrfid_app_scene_write.h | 9 - .../scene/lfrfid_app_scene_write_success.cpp | 38 --- .../scene/lfrfid_app_scene_write_success.h | 12 - applications/lfrfid/scenes/lfrfid_scene.c | 30 ++ applications/lfrfid/scenes/lfrfid_scene.h | 29 ++ .../lfrfid/scenes/lfrfid_scene_config.h | 24 ++ .../scenes/lfrfid_scene_delete_confirm.c | 68 ++++ .../scenes/lfrfid_scene_delete_success.c | 35 ++ .../lfrfid/scenes/lfrfid_scene_emulate.c | 44 +++ .../lfrfid/scenes/lfrfid_scene_exit_confirm.c | 39 +++ .../scenes/lfrfid_scene_extra_actions.c | 79 +++++ .../lfrfid/scenes/lfrfid_scene_raw_info.c | 64 ++++ .../lfrfid/scenes/lfrfid_scene_raw_name.c | 58 ++++ .../lfrfid/scenes/lfrfid_scene_raw_read.c | 126 +++++++ .../lfrfid/scenes/lfrfid_scene_raw_success.c | 39 +++ .../lfrfid/scenes/lfrfid_scene_read.c | 109 ++++++ .../scenes/lfrfid_scene_read_key_menu.c | 58 ++++ .../lfrfid/scenes/lfrfid_scene_read_success.c | 79 +++++ .../scenes/lfrfid_scene_retry_confirm.c | 39 +++ applications/lfrfid/scenes/lfrfid_scene_rpc.c | 67 ++++ .../lfrfid/scenes/lfrfid_scene_save_data.c | 51 +++ .../lfrfid/scenes/lfrfid_scene_save_name.c | 76 +++++ .../lfrfid/scenes/lfrfid_scene_save_success.c | 43 +++ .../lfrfid/scenes/lfrfid_scene_save_type.c | 86 +++++ .../lfrfid/scenes/lfrfid_scene_saved_info.c | 51 +++ .../scenes/lfrfid_scene_saved_key_menu.c | 69 ++++ .../lfrfid/scenes/lfrfid_scene_select_key.c | 22 ++ .../lfrfid/scenes/lfrfid_scene_start.c | 72 ++++ .../lfrfid/scenes/lfrfid_scene_write.c | 96 ++++++ .../scenes/lfrfid_scene_write_success.c | 38 +++ applications/lfrfid/view/container_vm.cpp | 115 ------- applications/lfrfid/view/container_vm.h | 17 - .../lfrfid/view/elements/button_element.cpp | 65 ---- .../lfrfid/view/elements/button_element.h | 28 -- .../lfrfid/view/elements/generic_element.cpp | 15 - .../lfrfid/view/elements/generic_element.h | 21 -- .../lfrfid/view/elements/icon_element.cpp | 25 -- .../lfrfid/view/elements/icon_element.h | 17 - .../lfrfid/view/elements/string_element.cpp | 47 --- .../lfrfid/view/elements/string_element.h | 28 -- applications/lfrfid/views/lfrfid_view_read.c | 117 +++++++ applications/lfrfid/views/lfrfid_view_read.h | 19 ++ applications/lfrfid_debug/lfrfid_debug.c | 81 +++++ .../lfrfid_debug/lfrfid_debug_app.cpp | 17 - applications/lfrfid_debug/lfrfid_debug_app.h | 40 --- .../lfrfid_debug_app_launcher.cpp | 11 - applications/lfrfid_debug/lfrfid_debug_i.h | 31 ++ .../scene/lfrfid_debug_app_scene_start.cpp | 47 --- .../scene/lfrfid_debug_app_scene_start.h | 13 - .../scene/lfrfid_debug_app_scene_tune.h | 9 - .../scenes/lfrfid_debug_app_scene_start.c | 44 +++ .../lfrfid_debug_app_scene_tune.c} | 28 +- .../lfrfid_debug/scenes/lfrfid_debug_scene.c | 30 ++ .../lfrfid_debug/scenes/lfrfid_debug_scene.h | 29 ++ .../scenes/lfrfid_debug_scene_config.h | 2 + .../view_modules/lfrfid_view_tune_vm.cpp | 214 ------------ .../view_modules/lfrfid_view_tune_vm.h | 26 -- .../views/lfrfid_debug_view_tune.c | 229 +++++++++++++ .../views/lfrfid_debug_view_tune.h | 18 + .../Common/Round_loader_8x8/frame_01.png | Bin 0 -> 7324 bytes .../Common/Round_loader_8x8/frame_02.png | Bin 0 -> 3606 bytes .../Common/Round_loader_8x8/frame_03.png | Bin 0 -> 3603 bytes .../Common/Round_loader_8x8/frame_04.png | Bin 0 -> 3605 bytes .../Common/Round_loader_8x8/frame_05.png | Bin 0 -> 3598 bytes .../icons/Common/Round_loader_8x8/frame_rate | 1 + 117 files changed, 2674 insertions(+), 2914 deletions(-) create mode 100644 applications/lfrfid/lfrfid.c delete mode 100644 applications/lfrfid/lfrfid_app.cpp delete mode 100644 applications/lfrfid/lfrfid_app.h delete mode 100644 applications/lfrfid/lfrfid_app_launcher.cpp create mode 100644 applications/lfrfid/lfrfid_i.h delete mode 100644 applications/lfrfid/scene/lfrfid_app_scene_delete_confirm.cpp delete mode 100644 applications/lfrfid/scene/lfrfid_app_scene_delete_confirm.h delete mode 100644 applications/lfrfid/scene/lfrfid_app_scene_delete_success.cpp delete mode 100644 applications/lfrfid/scene/lfrfid_app_scene_delete_success.h delete mode 100644 applications/lfrfid/scene/lfrfid_app_scene_emulate.cpp delete mode 100644 applications/lfrfid/scene/lfrfid_app_scene_emulate.h delete mode 100644 applications/lfrfid/scene/lfrfid_app_scene_exit_confirm.cpp delete mode 100644 applications/lfrfid/scene/lfrfid_app_scene_exit_confirm.h delete mode 100644 applications/lfrfid/scene/lfrfid_app_scene_extra_actions.cpp delete mode 100644 applications/lfrfid/scene/lfrfid_app_scene_extra_actions.h delete mode 100644 applications/lfrfid/scene/lfrfid_app_scene_raw_info.cpp delete mode 100644 applications/lfrfid/scene/lfrfid_app_scene_raw_info.h delete mode 100644 applications/lfrfid/scene/lfrfid_app_scene_raw_name.cpp delete mode 100644 applications/lfrfid/scene/lfrfid_app_scene_raw_name.h delete mode 100644 applications/lfrfid/scene/lfrfid_app_scene_raw_read.cpp delete mode 100644 applications/lfrfid/scene/lfrfid_app_scene_raw_read.h delete mode 100644 applications/lfrfid/scene/lfrfid_app_scene_raw_success.cpp delete mode 100644 applications/lfrfid/scene/lfrfid_app_scene_raw_success.h delete mode 100644 applications/lfrfid/scene/lfrfid_app_scene_read.cpp delete mode 100644 applications/lfrfid/scene/lfrfid_app_scene_read.h delete mode 100644 applications/lfrfid/scene/lfrfid_app_scene_read_menu.cpp delete mode 100644 applications/lfrfid/scene/lfrfid_app_scene_read_menu.h delete mode 100644 applications/lfrfid/scene/lfrfid_app_scene_read_success.cpp delete mode 100644 applications/lfrfid/scene/lfrfid_app_scene_read_success.h delete mode 100644 applications/lfrfid/scene/lfrfid_app_scene_retry_confirm.cpp delete mode 100644 applications/lfrfid/scene/lfrfid_app_scene_retry_confirm.h delete mode 100644 applications/lfrfid/scene/lfrfid_app_scene_rpc.cpp delete mode 100644 applications/lfrfid/scene/lfrfid_app_scene_rpc.h delete mode 100644 applications/lfrfid/scene/lfrfid_app_scene_save_data.cpp delete mode 100644 applications/lfrfid/scene/lfrfid_app_scene_save_data.h delete mode 100644 applications/lfrfid/scene/lfrfid_app_scene_save_name.cpp delete mode 100644 applications/lfrfid/scene/lfrfid_app_scene_save_name.h delete mode 100644 applications/lfrfid/scene/lfrfid_app_scene_save_success.cpp delete mode 100644 applications/lfrfid/scene/lfrfid_app_scene_save_success.h delete mode 100644 applications/lfrfid/scene/lfrfid_app_scene_save_type.cpp delete mode 100644 applications/lfrfid/scene/lfrfid_app_scene_save_type.h delete mode 100644 applications/lfrfid/scene/lfrfid_app_scene_saved_info.cpp delete mode 100644 applications/lfrfid/scene/lfrfid_app_scene_saved_info.h delete mode 100644 applications/lfrfid/scene/lfrfid_app_scene_saved_key_menu.cpp delete mode 100644 applications/lfrfid/scene/lfrfid_app_scene_saved_key_menu.h delete mode 100644 applications/lfrfid/scene/lfrfid_app_scene_select_key.cpp delete mode 100644 applications/lfrfid/scene/lfrfid_app_scene_select_key.h delete mode 100644 applications/lfrfid/scene/lfrfid_app_scene_start.cpp delete mode 100644 applications/lfrfid/scene/lfrfid_app_scene_start.h delete mode 100644 applications/lfrfid/scene/lfrfid_app_scene_write.cpp delete mode 100644 applications/lfrfid/scene/lfrfid_app_scene_write.h delete mode 100644 applications/lfrfid/scene/lfrfid_app_scene_write_success.cpp delete mode 100644 applications/lfrfid/scene/lfrfid_app_scene_write_success.h create mode 100644 applications/lfrfid/scenes/lfrfid_scene.c create mode 100644 applications/lfrfid/scenes/lfrfid_scene.h create mode 100644 applications/lfrfid/scenes/lfrfid_scene_config.h create mode 100644 applications/lfrfid/scenes/lfrfid_scene_delete_confirm.c create mode 100644 applications/lfrfid/scenes/lfrfid_scene_delete_success.c create mode 100644 applications/lfrfid/scenes/lfrfid_scene_emulate.c create mode 100644 applications/lfrfid/scenes/lfrfid_scene_exit_confirm.c create mode 100644 applications/lfrfid/scenes/lfrfid_scene_extra_actions.c create mode 100644 applications/lfrfid/scenes/lfrfid_scene_raw_info.c create mode 100644 applications/lfrfid/scenes/lfrfid_scene_raw_name.c create mode 100644 applications/lfrfid/scenes/lfrfid_scene_raw_read.c create mode 100644 applications/lfrfid/scenes/lfrfid_scene_raw_success.c create mode 100644 applications/lfrfid/scenes/lfrfid_scene_read.c create mode 100644 applications/lfrfid/scenes/lfrfid_scene_read_key_menu.c create mode 100644 applications/lfrfid/scenes/lfrfid_scene_read_success.c create mode 100644 applications/lfrfid/scenes/lfrfid_scene_retry_confirm.c create mode 100644 applications/lfrfid/scenes/lfrfid_scene_rpc.c create mode 100644 applications/lfrfid/scenes/lfrfid_scene_save_data.c create mode 100644 applications/lfrfid/scenes/lfrfid_scene_save_name.c create mode 100644 applications/lfrfid/scenes/lfrfid_scene_save_success.c create mode 100644 applications/lfrfid/scenes/lfrfid_scene_save_type.c create mode 100644 applications/lfrfid/scenes/lfrfid_scene_saved_info.c create mode 100644 applications/lfrfid/scenes/lfrfid_scene_saved_key_menu.c create mode 100644 applications/lfrfid/scenes/lfrfid_scene_select_key.c create mode 100644 applications/lfrfid/scenes/lfrfid_scene_start.c create mode 100644 applications/lfrfid/scenes/lfrfid_scene_write.c create mode 100644 applications/lfrfid/scenes/lfrfid_scene_write_success.c delete mode 100644 applications/lfrfid/view/container_vm.cpp delete mode 100644 applications/lfrfid/view/container_vm.h delete mode 100644 applications/lfrfid/view/elements/button_element.cpp delete mode 100644 applications/lfrfid/view/elements/button_element.h delete mode 100644 applications/lfrfid/view/elements/generic_element.cpp delete mode 100644 applications/lfrfid/view/elements/generic_element.h delete mode 100644 applications/lfrfid/view/elements/icon_element.cpp delete mode 100644 applications/lfrfid/view/elements/icon_element.h delete mode 100644 applications/lfrfid/view/elements/string_element.cpp delete mode 100644 applications/lfrfid/view/elements/string_element.h create mode 100644 applications/lfrfid/views/lfrfid_view_read.c create mode 100644 applications/lfrfid/views/lfrfid_view_read.h create mode 100644 applications/lfrfid_debug/lfrfid_debug.c delete mode 100644 applications/lfrfid_debug/lfrfid_debug_app.cpp delete mode 100644 applications/lfrfid_debug/lfrfid_debug_app.h delete mode 100644 applications/lfrfid_debug/lfrfid_debug_app_launcher.cpp create mode 100644 applications/lfrfid_debug/lfrfid_debug_i.h delete mode 100644 applications/lfrfid_debug/scene/lfrfid_debug_app_scene_start.cpp delete mode 100644 applications/lfrfid_debug/scene/lfrfid_debug_app_scene_start.h delete mode 100644 applications/lfrfid_debug/scene/lfrfid_debug_app_scene_tune.h create mode 100644 applications/lfrfid_debug/scenes/lfrfid_debug_app_scene_start.c rename applications/lfrfid_debug/{scene/lfrfid_debug_app_scene_tune.cpp => scenes/lfrfid_debug_app_scene_tune.c} (53%) create mode 100644 applications/lfrfid_debug/scenes/lfrfid_debug_scene.c create mode 100644 applications/lfrfid_debug/scenes/lfrfid_debug_scene.h create mode 100644 applications/lfrfid_debug/scenes/lfrfid_debug_scene_config.h delete mode 100644 applications/lfrfid_debug/view_modules/lfrfid_view_tune_vm.cpp delete mode 100644 applications/lfrfid_debug/view_modules/lfrfid_view_tune_vm.h create mode 100644 applications/lfrfid_debug/views/lfrfid_debug_view_tune.c create mode 100644 applications/lfrfid_debug/views/lfrfid_debug_view_tune.h create mode 100644 assets/icons/Common/Round_loader_8x8/frame_01.png create mode 100644 assets/icons/Common/Round_loader_8x8/frame_02.png create mode 100644 assets/icons/Common/Round_loader_8x8/frame_03.png create mode 100644 assets/icons/Common/Round_loader_8x8/frame_04.png create mode 100644 assets/icons/Common/Round_loader_8x8/frame_05.png create mode 100644 assets/icons/Common/Round_loader_8x8/frame_rate diff --git a/applications/gui/modules/validators.c b/applications/gui/modules/validators.c index ac293f8cb1b..52f9946ca2c 100644 --- a/applications/gui/modules/validators.c +++ b/applications/gui/modules/validators.c @@ -42,7 +42,9 @@ ValidatorIsFile* validator_is_file_alloc_init( instance->app_path_folder = strdup(app_path_folder); instance->app_extension = app_extension; - instance->current_name = strdup(current_name); + if(current_name != NULL) { + instance->current_name = strdup(current_name); + } return instance; } diff --git a/applications/lfrfid/lfrfid.c b/applications/lfrfid/lfrfid.c new file mode 100644 index 00000000000..87332b1ac4d --- /dev/null +++ b/applications/lfrfid/lfrfid.c @@ -0,0 +1,316 @@ +#include "lfrfid_i.h" + +static bool lfrfid_debug_custom_event_callback(void* context, uint32_t event) { + furi_assert(context); + LfRfid* app = context; + return scene_manager_handle_custom_event(app->scene_manager, event); +} + +static bool lfrfid_debug_back_event_callback(void* context) { + furi_assert(context); + LfRfid* app = context; + return scene_manager_handle_back_event(app->scene_manager); +} + +static void rpc_command_callback(RpcAppSystemEvent rpc_event, void* context) { + furi_assert(context); + LfRfid* app = (LfRfid*)context; + + if(rpc_event == RpcAppEventSessionClose) { + view_dispatcher_send_custom_event(app->view_dispatcher, LfRfidEventRpcSessionClose); + // Detach RPC + rpc_system_app_set_callback(app->rpc_ctx, NULL, NULL); + app->rpc_ctx = NULL; + } else if(rpc_event == RpcAppEventAppExit) { + view_dispatcher_send_custom_event(app->view_dispatcher, LfRfidEventExit); + } else if(rpc_event == RpcAppEventLoadFile) { + view_dispatcher_send_custom_event(app->view_dispatcher, LfRfidEventRpcLoadFile); + } else { + rpc_system_app_confirm(app->rpc_ctx, rpc_event, false); + } +} + +static LfRfid* lfrfid_alloc() { + LfRfid* lfrfid = malloc(sizeof(LfRfid)); + + lfrfid->storage = furi_record_open(RECORD_STORAGE); + lfrfid->dialogs = furi_record_open(RECORD_DIALOGS); + + string_init(lfrfid->file_name); + string_init(lfrfid->raw_file_name); + string_init_set_str(lfrfid->file_path, LFRFID_APP_FOLDER); + + lfrfid->dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax); + + size_t size = protocol_dict_get_max_data_size(lfrfid->dict); + lfrfid->new_key_data = (uint8_t*)malloc(size); + lfrfid->old_key_data = (uint8_t*)malloc(size); + + lfrfid->lfworker = lfrfid_worker_alloc(lfrfid->dict); + + lfrfid->view_dispatcher = view_dispatcher_alloc(); + lfrfid->scene_manager = scene_manager_alloc(&lfrfid_scene_handlers, lfrfid); + view_dispatcher_enable_queue(lfrfid->view_dispatcher); + view_dispatcher_set_event_callback_context(lfrfid->view_dispatcher, lfrfid); + view_dispatcher_set_custom_event_callback( + lfrfid->view_dispatcher, lfrfid_debug_custom_event_callback); + view_dispatcher_set_navigation_event_callback( + lfrfid->view_dispatcher, lfrfid_debug_back_event_callback); + + // Open GUI record + lfrfid->gui = furi_record_open(RECORD_GUI); + + // Open Notification record + lfrfid->notifications = furi_record_open(RECORD_NOTIFICATION); + + // Submenu + lfrfid->submenu = submenu_alloc(); + view_dispatcher_add_view( + lfrfid->view_dispatcher, LfRfidViewSubmenu, submenu_get_view(lfrfid->submenu)); + + // Dialog + lfrfid->dialog_ex = dialog_ex_alloc(); + view_dispatcher_add_view( + lfrfid->view_dispatcher, LfRfidViewDialogEx, dialog_ex_get_view(lfrfid->dialog_ex)); + + // Popup + lfrfid->popup = popup_alloc(); + view_dispatcher_add_view( + lfrfid->view_dispatcher, LfRfidViewPopup, popup_get_view(lfrfid->popup)); + + // Widget + lfrfid->widget = widget_alloc(); + view_dispatcher_add_view( + lfrfid->view_dispatcher, LfRfidViewWidget, widget_get_view(lfrfid->widget)); + + // Text Input + lfrfid->text_input = text_input_alloc(); + view_dispatcher_add_view( + lfrfid->view_dispatcher, LfRfidViewTextInput, text_input_get_view(lfrfid->text_input)); + + // Byte Input + lfrfid->byte_input = byte_input_alloc(); + view_dispatcher_add_view( + lfrfid->view_dispatcher, LfRfidViewByteInput, byte_input_get_view(lfrfid->byte_input)); + + // Read custom view + lfrfid->read_view = lfrfid_view_read_alloc(); + view_dispatcher_add_view( + lfrfid->view_dispatcher, LfRfidViewRead, lfrfid_view_read_get_view(lfrfid->read_view)); + + return lfrfid; +} + +static void lfrfid_free(LfRfid* lfrfid) { + furi_assert(lfrfid); + + string_clear(lfrfid->raw_file_name); + string_clear(lfrfid->file_name); + string_clear(lfrfid->file_path); + protocol_dict_free(lfrfid->dict); + + lfrfid_worker_free(lfrfid->lfworker); + + if(lfrfid->rpc_ctx) { + rpc_system_app_set_callback(lfrfid->rpc_ctx, NULL, NULL); + rpc_system_app_send_exited(lfrfid->rpc_ctx); + } + + free(lfrfid->new_key_data); + free(lfrfid->old_key_data); + + // Submenu + view_dispatcher_remove_view(lfrfid->view_dispatcher, LfRfidViewSubmenu); + submenu_free(lfrfid->submenu); + + // DialogEx + view_dispatcher_remove_view(lfrfid->view_dispatcher, LfRfidViewDialogEx); + dialog_ex_free(lfrfid->dialog_ex); + + // Popup + view_dispatcher_remove_view(lfrfid->view_dispatcher, LfRfidViewPopup); + popup_free(lfrfid->popup); + + // Widget + view_dispatcher_remove_view(lfrfid->view_dispatcher, LfRfidViewWidget); + widget_free(lfrfid->widget); + + // TextInput + view_dispatcher_remove_view(lfrfid->view_dispatcher, LfRfidViewTextInput); + text_input_free(lfrfid->text_input); + + // ByteInput + view_dispatcher_remove_view(lfrfid->view_dispatcher, LfRfidViewByteInput); + byte_input_free(lfrfid->byte_input); + + // Read custom view + view_dispatcher_remove_view(lfrfid->view_dispatcher, LfRfidViewRead); + lfrfid_view_read_free(lfrfid->read_view); + + // View Dispatcher + view_dispatcher_free(lfrfid->view_dispatcher); + + // Scene Manager + scene_manager_free(lfrfid->scene_manager); + + // GUI + furi_record_close(RECORD_GUI); + lfrfid->gui = NULL; + + // Notifications + furi_record_close(RECORD_NOTIFICATION); + lfrfid->notifications = NULL; + + furi_record_close(RECORD_STORAGE); + furi_record_close(RECORD_DIALOGS); + + free(lfrfid); +} + +int32_t lfrfid_app(void* p) { + LfRfid* app = lfrfid_alloc(); + char* args = p; + + lfrfid_make_app_folder(app); + + if(args && strlen(args)) { + uint32_t rpc_ctx_ptr = 0; + if(sscanf(args, "RPC %lX", &rpc_ctx_ptr) == 1) { + app->rpc_ctx = (RpcAppSystem*)rpc_ctx_ptr; + rpc_system_app_set_callback(app->rpc_ctx, rpc_command_callback, app); + rpc_system_app_send_started(app->rpc_ctx); + view_dispatcher_attach_to_gui( + app->view_dispatcher, app->gui, ViewDispatcherTypeDesktop); + scene_manager_next_scene(app->scene_manager, LfRfidSceneRpc); + } else { + string_set_str(app->file_path, args); + lfrfid_load_key_data(app, app->file_path, true); + view_dispatcher_attach_to_gui( + app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + scene_manager_next_scene(app->scene_manager, LfRfidSceneEmulate); + } + + } else { + view_dispatcher_attach_to_gui( + app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + scene_manager_next_scene(app->scene_manager, LfRfidSceneStart); + } + + view_dispatcher_run(app->view_dispatcher); + + lfrfid_free(app); + + return 0; +} + +bool lfrfid_save_key(LfRfid* app) { + furi_assert(app); + + bool result = false; + + lfrfid_make_app_folder(app); + + if(string_end_with_str_p(app->file_path, LFRFID_APP_EXTENSION)) { + size_t filename_start = string_search_rchar(app->file_path, '/'); + string_left(app->file_path, filename_start); + } + + string_cat_printf( + app->file_path, "/%s%s", string_get_cstr(app->file_name), LFRFID_APP_EXTENSION); + + result = lfrfid_save_key_data(app, app->file_path); + return result; +} + +bool lfrfid_load_key_from_file_select(LfRfid* app) { + furi_assert(app); + + bool result = dialog_file_browser_show( + app->dialogs, + app->file_path, + app->file_path, + LFRFID_APP_EXTENSION, + true, + &I_125_10px, + true); + + if(result) { + result = lfrfid_load_key_data(app, app->file_path, true); + } + + return result; +} + +bool lfrfid_delete_key(LfRfid* app) { + furi_assert(app); + + return storage_simply_remove(app->storage, string_get_cstr(app->file_path)); +} + +bool lfrfid_load_key_data(LfRfid* app, string_t path, bool show_dialog) { + bool result = false; + + do { + app->protocol_id = lfrfid_dict_file_load(app->dict, string_get_cstr(path)); + if(app->protocol_id == PROTOCOL_NO) break; + + path_extract_filename(path, app->file_name, true); + result = true; + } while(0); + + if((!result) && (show_dialog)) { + dialog_message_show_storage_error(app->dialogs, "Cannot load\nkey file"); + } + + return result; +} + +bool lfrfid_save_key_data(LfRfid* app, string_t path) { + bool result = lfrfid_dict_file_save(app->dict, app->protocol_id, string_get_cstr(path)); + + if(!result) { + dialog_message_show_storage_error(app->dialogs, "Cannot save\nkey file"); + } + + return result; +} + +void lfrfid_make_app_folder(LfRfid* app) { + furi_assert(app); + + if(!storage_simply_mkdir(app->storage, LFRFID_APP_FOLDER)) { + dialog_message_show_storage_error(app->dialogs, "Cannot create\napp folder"); + } +} + +void lfrfid_text_store_set(LfRfid* app, const char* text, ...) { + furi_assert(app); + va_list args; + va_start(args, text); + + vsnprintf(app->text_store, LFRFID_TEXT_STORE_SIZE, text, args); + + va_end(args); +} + +void lfrfid_text_store_clear(LfRfid* app) { + furi_assert(app); + memset(app->text_store, 0, sizeof(app->text_store)); +} + +void lfrfid_popup_timeout_callback(void* context) { + LfRfid* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, LfRfidEventPopupClosed); +} + +void lfrfid_widget_callback(GuiButtonType result, InputType type, void* context) { + LfRfid* app = context; + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(app->view_dispatcher, result); + } +} + +void lfrfid_text_input_callback(void* context) { + LfRfid* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, LfRfidEventNext); +} \ No newline at end of file diff --git a/applications/lfrfid/lfrfid_app.cpp b/applications/lfrfid/lfrfid_app.cpp deleted file mode 100644 index 9373ca8c711..00000000000 --- a/applications/lfrfid/lfrfid_app.cpp +++ /dev/null @@ -1,218 +0,0 @@ -#include "lfrfid_app.h" -#include "assets_icons.h" -#include -#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" -#include "scene/lfrfid_app_scene_retry_confirm.h" -#include "scene/lfrfid_app_scene_exit_confirm.h" -#include "scene/lfrfid_app_scene_read_menu.h" -#include "scene/lfrfid_app_scene_write.h" -#include "scene/lfrfid_app_scene_write_success.h" -#include "scene/lfrfid_app_scene_emulate.h" -#include "scene/lfrfid_app_scene_save_name.h" -#include "scene/lfrfid_app_scene_save_success.h" -#include "scene/lfrfid_app_scene_select_key.h" -#include "scene/lfrfid_app_scene_saved_key_menu.h" -#include "scene/lfrfid_app_scene_save_data.h" -#include "scene/lfrfid_app_scene_save_type.h" -#include "scene/lfrfid_app_scene_saved_info.h" -#include "scene/lfrfid_app_scene_delete_confirm.h" -#include "scene/lfrfid_app_scene_delete_success.h" -#include "scene/lfrfid_app_scene_rpc.h" -#include "scene/lfrfid_app_scene_extra_actions.h" -#include "scene/lfrfid_app_scene_raw_info.h" -#include "scene/lfrfid_app_scene_raw_name.h" -#include "scene/lfrfid_app_scene_raw_read.h" -#include "scene/lfrfid_app_scene_raw_success.h" - -#include -#include - -#include - -const char* LfRfidApp::app_folder = ANY_PATH("lfrfid"); -const char* LfRfidApp::app_sd_folder = EXT_PATH("lfrfid"); -const char* LfRfidApp::app_extension = ".rfid"; -const char* LfRfidApp::app_filetype = "Flipper RFID key"; - -LfRfidApp::LfRfidApp() - : scene_controller{this} - , notification{RECORD_NOTIFICATION} - , storage{RECORD_STORAGE} - , dialogs{RECORD_DIALOGS} - , text_store(40) { - string_init(file_name); - string_init(raw_file_name); - string_init_set_str(file_path, app_folder); - - dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax); - - size_t size = protocol_dict_get_max_data_size(dict); - new_key_data = (uint8_t*)malloc(size); - old_key_data = (uint8_t*)malloc(size); - - lfworker = lfrfid_worker_alloc(dict); -} - -LfRfidApp::~LfRfidApp() { - string_clear(raw_file_name); - string_clear(file_name); - string_clear(file_path); - protocol_dict_free(dict); - - lfrfid_worker_free(lfworker); - - if(rpc_ctx) { - rpc_system_app_set_callback(rpc_ctx, NULL, NULL); - rpc_system_app_send_exited(rpc_ctx); - } - - free(new_key_data); - free(old_key_data); -} - -static void rpc_command_callback(RpcAppSystemEvent rpc_event, void* context) { - furi_assert(context); - LfRfidApp* app = static_cast(context); - - if(rpc_event == RpcAppEventSessionClose) { - LfRfidApp::Event event; - event.type = LfRfidApp::EventType::RpcSessionClose; - app->view_controller.send_event(&event); - // Detach RPC - rpc_system_app_set_callback(app->rpc_ctx, NULL, NULL); - app->rpc_ctx = NULL; - } else if(rpc_event == RpcAppEventAppExit) { - LfRfidApp::Event event; - event.type = LfRfidApp::EventType::Exit; - app->view_controller.send_event(&event); - } else if(rpc_event == RpcAppEventLoadFile) { - LfRfidApp::Event event; - event.type = LfRfidApp::EventType::RpcLoadFile; - app->view_controller.send_event(&event); - } else { - rpc_system_app_confirm(app->rpc_ctx, rpc_event, false); - } -} - -void LfRfidApp::run(void* _args) { - const char* args = reinterpret_cast(_args); - - make_app_folder(); - - if(args && strlen(args)) { - uint32_t rpc_ctx_ptr = 0; - if(sscanf(args, "RPC %lX", &rpc_ctx_ptr) == 1) { - rpc_ctx = (RpcAppSystem*)rpc_ctx_ptr; - rpc_system_app_set_callback(rpc_ctx, rpc_command_callback, this); - rpc_system_app_send_started(rpc_ctx); - view_controller.attach_to_gui(ViewDispatcherTypeDesktop); - scene_controller.add_scene(SceneType::Rpc, new LfRfidAppSceneRpc()); - scene_controller.process(100, SceneType::Rpc); - } else { - string_set_str(file_path, args); - load_key_data(file_path, true); - view_controller.attach_to_gui(ViewDispatcherTypeFullscreen); - scene_controller.add_scene(SceneType::Emulate, new LfRfidAppSceneEmulate()); - scene_controller.process(100, SceneType::Emulate); - } - - } else { - view_controller.attach_to_gui(ViewDispatcherTypeFullscreen); - scene_controller.add_scene(SceneType::Start, new LfRfidAppSceneStart()); - scene_controller.add_scene(SceneType::Read, new LfRfidAppSceneRead()); - scene_controller.add_scene(SceneType::RetryConfirm, new LfRfidAppSceneRetryConfirm()); - scene_controller.add_scene(SceneType::ExitConfirm, new LfRfidAppSceneExitConfirm()); - scene_controller.add_scene(SceneType::ReadSuccess, new LfRfidAppSceneReadSuccess()); - scene_controller.add_scene(SceneType::ReadKeyMenu, new LfRfidAppSceneReadKeyMenu()); - scene_controller.add_scene(SceneType::Write, new LfRfidAppSceneWrite()); - scene_controller.add_scene(SceneType::WriteSuccess, new LfRfidAppSceneWriteSuccess()); - scene_controller.add_scene(SceneType::Emulate, new LfRfidAppSceneEmulate()); - scene_controller.add_scene(SceneType::SaveName, new LfRfidAppSceneSaveName()); - scene_controller.add_scene(SceneType::SaveSuccess, new LfRfidAppSceneSaveSuccess()); - scene_controller.add_scene(SceneType::SelectKey, new LfRfidAppSceneSelectKey()); - scene_controller.add_scene(SceneType::SavedKeyMenu, new LfRfidAppSceneSavedKeyMenu()); - scene_controller.add_scene(SceneType::SaveData, new LfRfidAppSceneSaveData()); - scene_controller.add_scene(SceneType::SaveType, new LfRfidAppSceneSaveType()); - scene_controller.add_scene(SceneType::SavedInfo, new LfRfidAppSceneSavedInfo()); - scene_controller.add_scene(SceneType::DeleteConfirm, new LfRfidAppSceneDeleteConfirm()); - scene_controller.add_scene(SceneType::DeleteSuccess, new LfRfidAppSceneDeleteSuccess()); - scene_controller.add_scene(SceneType::ExtraActions, new LfRfidAppSceneExtraActions()); - scene_controller.add_scene(SceneType::RawInfo, new LfRfidAppSceneRawInfo()); - scene_controller.add_scene(SceneType::RawName, new LfRfidAppSceneRawName()); - scene_controller.add_scene(SceneType::RawRead, new LfRfidAppSceneRawRead()); - scene_controller.add_scene(SceneType::RawSuccess, new LfRfidAppSceneRawSuccess()); - scene_controller.process(100); - } -} - -bool LfRfidApp::save_key() { - bool result = false; - - make_app_folder(); - - 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", string_get_cstr(file_name), app_extension); - - result = save_key_data(file_path); - return result; -} - -bool LfRfidApp::load_key_from_file_select(bool need_restore) { - 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) { - result = load_key_data(file_path, true); - } - - return result; -} - -bool LfRfidApp::delete_key() { - return storage_simply_remove(storage, string_get_cstr(file_path)); -} - -bool LfRfidApp::load_key_data(string_t path, bool show_dialog) { - bool result = false; - - do { - protocol_id = lfrfid_dict_file_load(dict, string_get_cstr(path)); - if(protocol_id == PROTOCOL_NO) break; - - path_extract_filename(path, file_name, true); - result = true; - } while(0); - - if((!result) && (show_dialog)) { - dialog_message_show_storage_error(dialogs, "Cannot load\nkey file"); - } - - return result; -} - -bool LfRfidApp::save_key_data(string_t path) { - bool result = lfrfid_dict_file_save(dict, protocol_id, string_get_cstr(path)); - - if(!result) { - dialog_message_show_storage_error(dialogs, "Cannot save\nkey file"); - } - - return result; -} - -void LfRfidApp::make_app_folder() { - if(!storage_simply_mkdir(storage, app_folder)) { - dialog_message_show_storage_error(dialogs, "Cannot create\napp folder"); - } -} diff --git a/applications/lfrfid/lfrfid_app.h b/applications/lfrfid/lfrfid_app.h deleted file mode 100644 index 153218dbdf5..00000000000 --- a/applications/lfrfid/lfrfid_app.h +++ /dev/null @@ -1,137 +0,0 @@ -#pragma once -#include "m-string.h" -#include -#include - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include "view/container_vm.h" - -#include -#include -#include - -#include "rpc/rpc_app.h" - -#include -#include -#include -#include - -#define LFRFID_KEY_NAME_SIZE 22 - -class LfRfidApp { -public: - enum class EventType : uint8_t { - GENERIC_EVENT_ENUM_VALUES, - Next, - MenuSelected, - Stay, - Retry, - Exit, - ReadEventSenseStart, - ReadEventSenseEnd, - ReadEventSenseCardStart, - ReadEventSenseCardEnd, - ReadEventStartASK, - ReadEventStartPSK, - ReadEventDone, - ReadEventOverrun, - ReadEventError, - WriteEventOK, - WriteEventProtocolCannotBeWritten, - WriteEventFobCannotBeWritten, - WriteEventTooLongToWrite, - RpcLoadFile, - RpcSessionClose, - }; - - enum class SceneType : uint8_t { - GENERIC_SCENE_ENUM_VALUES, - Read, - ReadSuccess, - RetryConfirm, - ExitConfirm, - ReadKeyMenu, - Write, - WriteSuccess, - Emulate, - SaveName, - SaveSuccess, - SelectKey, - SavedKeyMenu, - SaveData, - SaveType, - SavedInfo, - DeleteConfirm, - DeleteSuccess, - Rpc, - ExtraActions, - RawInfo, - RawName, - RawRead, - RawSuccess, - }; - - class Event { - public: - union { - int32_t signed_int; - } payload; - - EventType type; - }; - - SceneController, LfRfidApp> scene_controller; - ViewController - view_controller; - - ~LfRfidApp(); - LfRfidApp(); - - RecordController notification; - RecordController storage; - RecordController dialogs; - - TextStore text_store; - - string_t file_path; - - RpcAppSystem* rpc_ctx; - - void run(void* args); - - static const char* app_folder; - static const char* app_sd_folder; - static const char* app_extension; - static const char* app_filetype; - - bool save_key(); - bool load_key_from_file_select(bool need_restore); - bool delete_key(); - - bool load_key_data(string_t path, bool show_dialog); - bool save_key_data(string_t path); - - void make_app_folder(); - - ProtocolDict* dict; - LFRFIDWorker* lfworker; - string_t file_name; - ProtocolId protocol_id; - LFRFIDWorkerReadType read_type; - - uint8_t* old_key_data; - uint8_t* new_key_data; - - string_t raw_file_name; -}; diff --git a/applications/lfrfid/lfrfid_app_launcher.cpp b/applications/lfrfid/lfrfid_app_launcher.cpp deleted file mode 100644 index 2855850f569..00000000000 --- a/applications/lfrfid/lfrfid_app_launcher.cpp +++ /dev/null @@ -1,10 +0,0 @@ -#include "lfrfid_app.h" - -// app enter function -extern "C" int32_t lfrfid_app(void* args) { - LfRfidApp* app = new LfRfidApp(); - app->run(args); - delete app; - - return 0; -} diff --git a/applications/lfrfid/lfrfid_cli.c b/applications/lfrfid/lfrfid_cli.c index 9a6930a671a..281a0e9638d 100644 --- a/applications/lfrfid/lfrfid_cli.c +++ b/applications/lfrfid/lfrfid_cli.c @@ -114,7 +114,7 @@ static void lfrfid_cli_read(Cli* cli, string_t args) { string_t info; string_init(info); protocol_dict_render_data(dict, info, context.protocol); - if(string_size(info) > 0) { + if(!string_empty_p(info)) { printf("%s\r\n", string_get_cstr(info)); } string_clear(info); diff --git a/applications/lfrfid/lfrfid_i.h b/applications/lfrfid/lfrfid_i.h new file mode 100644 index 00000000000..77e872527e1 --- /dev/null +++ b/applications/lfrfid/lfrfid_i.h @@ -0,0 +1,145 @@ +#pragma once + +#include "m-string.h" + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include + +#define LFRFID_KEY_NAME_SIZE 22 +#define LFRFID_TEXT_STORE_SIZE 40 + +#define LFRFID_APP_FOLDER ANY_PATH("lfrfid") +#define LFRFID_SD_FOLDER EXT_PATH("lfrfid") +#define LFRFID_APP_EXTENSION ".rfid" +#define LFRFID_APP_SHADOW_EXTENSION ".shd" + +#define LFRFID_APP_RAW_ASK_EXTENSION ".ask.raw" +#define LFRFID_APP_RAW_PSK_EXTENSION ".psk.raw" + +enum LfRfidCustomEvent { + LfRfidEventNext = 100, + LfRfidEventExit, + LfRfidEventPopupClosed, + LfRfidEventReadSenseStart, + LfRfidEventReadSenseEnd, + LfRfidEventReadSenseCardStart, + LfRfidEventReadSenseCardEnd, + LfRfidEventReadStartASK, + LfRfidEventReadStartPSK, + LfRfidEventReadDone, + LfRfidEventReadOverrun, + LfRfidEventReadError, + LfRfidEventWriteOK, + LfRfidEventWriteProtocolCannotBeWritten, + LfRfidEventWriteFobCannotBeWritten, + LfRfidEventWriteTooLongToWrite, + LfRfidEventRpcLoadFile, + LfRfidEventRpcSessionClose, +}; + +typedef enum { + LfRfidRpcStateIdle, + LfRfidRpcStateEmulating, +} LfRfidRpcState; + +typedef struct LfRfid LfRfid; + +struct LfRfid { + LFRFIDWorker* lfworker; + ViewDispatcher* view_dispatcher; + Gui* gui; + NotificationApp* notifications; + SceneManager* scene_manager; + Storage* storage; + DialogsApp* dialogs; + Widget* widget; + + char text_store[LFRFID_TEXT_STORE_SIZE + 1]; + string_t file_path; + string_t file_name; + string_t raw_file_name; + + ProtocolDict* dict; + ProtocolId protocol_id; + ProtocolId protocol_id_next; + LFRFIDWorkerReadType read_type; + + uint8_t* old_key_data; + uint8_t* new_key_data; + + RpcAppSystem* rpc_ctx; + LfRfidRpcState rpc_state; + + // Common Views + Submenu* submenu; + DialogEx* dialog_ex; + Popup* popup; + TextInput* text_input; + ByteInput* byte_input; + + // Custom views + LfRfidReadView* read_view; +}; + +typedef enum { + LfRfidViewSubmenu, + LfRfidViewDialogEx, + LfRfidViewPopup, + LfRfidViewWidget, + LfRfidViewTextInput, + LfRfidViewByteInput, + LfRfidViewRead, +} LfRfidView; + +bool lfrfid_save_key(LfRfid* app); + +bool lfrfid_load_key_from_file_select(LfRfid* app); + +bool lfrfid_delete_key(LfRfid* app); + +bool lfrfid_load_key_data(LfRfid* app, string_t path, bool show_dialog); + +bool lfrfid_save_key_data(LfRfid* app, string_t path); + +void lfrfid_make_app_folder(LfRfid* app); + +void lfrfid_text_store_set(LfRfid* app, const char* text, ...); + +void lfrfid_text_store_clear(LfRfid* app); + +void lfrfid_popup_timeout_callback(void* context); + +void lfrfid_widget_callback(GuiButtonType result, InputType type, void* context); + +void lfrfid_text_input_callback(void* context); diff --git a/applications/lfrfid/scene/lfrfid_app_scene_delete_confirm.cpp b/applications/lfrfid/scene/lfrfid_app_scene_delete_confirm.cpp deleted file mode 100644 index 58ff4dcdfc3..00000000000 --- a/applications/lfrfid/scene/lfrfid_app_scene_delete_confirm.cpp +++ /dev/null @@ -1,88 +0,0 @@ -#include "lfrfid_app_scene_delete_confirm.h" -#include "../view/elements/button_element.h" -#include "../view/elements/icon_element.h" -#include "../view/elements/string_element.h" - -void LfRfidAppSceneDeleteConfirm::on_enter(LfRfidApp* app, bool /* need_restore */) { - string_init(string_data); - string_init(string_header); - - auto container = app->view_controller.get(); - - auto button = container->add(); - button->set_type(ButtonElement::Type::Left, "Back"); - button->set_callback(app, LfRfidAppSceneDeleteConfirm::back_callback); - - button = container->add(); - button->set_type(ButtonElement::Type::Right, "Delete"); - button->set_callback(app, LfRfidAppSceneDeleteConfirm::delete_callback); - - auto line_1 = container->add(); - auto line_2 = container->add(); - auto line_3 = container->add(); - - size_t size = protocol_dict_get_data_size(app->dict, app->protocol_id); - uint8_t* data = (uint8_t*)malloc(size); - protocol_dict_get_data(app->dict, app->protocol_id, data, size); - for(uint8_t i = 0; i < MIN(size, (size_t)8); i++) { - if(i != 0) { - string_cat_printf(string_data, " "); - } - - string_cat_printf(string_data, "%02X", data[i]); - } - free(data); - - string_printf(string_header, "Delete %s?", string_get_cstr(app->file_name)); - line_1->set_text( - string_get_cstr(string_header), 64, 0, 128 - 2, AlignCenter, AlignTop, FontPrimary); - line_2->set_text( - string_get_cstr(string_data), 64, 19, 0, AlignCenter, AlignTop, FontSecondary); - line_3->set_text( - protocol_dict_get_name(app->dict, app->protocol_id), - 64, - 49, - 0, - AlignCenter, - AlignBottom, - FontSecondary); - - app->view_controller.switch_to(); -} - -bool LfRfidAppSceneDeleteConfirm::on_event(LfRfidApp* app, LfRfidApp::Event* event) { - bool consumed = false; - - if(event->type == LfRfidApp::EventType::Next) { - app->delete_key(); - app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::DeleteSuccess); - consumed = true; - } else if(event->type == LfRfidApp::EventType::Stay) { - app->scene_controller.switch_to_previous_scene(); - consumed = true; - } else if(event->type == LfRfidApp::EventType::Back) { - consumed = true; - } - - return consumed; -} - -void LfRfidAppSceneDeleteConfirm::on_exit(LfRfidApp* app) { - app->view_controller.get()->clean(); - string_clear(string_data); - string_clear(string_header); -} - -void LfRfidAppSceneDeleteConfirm::back_callback(void* context) { - LfRfidApp* app = static_cast(context); - LfRfidApp::Event event; - event.type = LfRfidApp::EventType::Stay; - app->view_controller.send_event(&event); -} - -void LfRfidAppSceneDeleteConfirm::delete_callback(void* context) { - LfRfidApp* app = static_cast(context); - LfRfidApp::Event event; - event.type = LfRfidApp::EventType::Next; - app->view_controller.send_event(&event); -} diff --git a/applications/lfrfid/scene/lfrfid_app_scene_delete_confirm.h b/applications/lfrfid/scene/lfrfid_app_scene_delete_confirm.h deleted file mode 100644 index f9daed54328..00000000000 --- a/applications/lfrfid/scene/lfrfid_app_scene_delete_confirm.h +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once -#include "../lfrfid_app.h" - -class LfRfidAppSceneDeleteConfirm : public GenericScene { -public: - void on_enter(LfRfidApp* app, bool need_restore) final; - bool on_event(LfRfidApp* app, LfRfidApp::Event* event) final; - void on_exit(LfRfidApp* app) final; - -private: - static void back_callback(void* context); - static void delete_callback(void* context); - - string_t string_header; - string_t string_data; -}; diff --git a/applications/lfrfid/scene/lfrfid_app_scene_delete_success.cpp b/applications/lfrfid/scene/lfrfid_app_scene_delete_success.cpp deleted file mode 100644 index 9820338d126..00000000000 --- a/applications/lfrfid/scene/lfrfid_app_scene_delete_success.cpp +++ /dev/null @@ -1,38 +0,0 @@ -#include "lfrfid_app_scene_delete_success.h" - -void LfRfidAppSceneDeleteSuccess::on_enter(LfRfidApp* app, bool /* need_restore */) { - auto popup = app->view_controller.get(); - - popup->set_icon(0, 2, &I_DolphinMafia_115x62); - popup->set_header("Deleted", 83, 19, AlignLeft, AlignBottom); - popup->set_context(app); - popup->set_callback(LfRfidAppSceneDeleteSuccess::timeout_callback); - popup->set_timeout(1500); - popup->enable_timeout(); - - app->view_controller.switch_to(); -} - -bool LfRfidAppSceneDeleteSuccess::on_event(LfRfidApp* app, LfRfidApp::Event* event) { - bool consumed = false; - - if(event->type == LfRfidApp::EventType::Back) { - app->scene_controller.search_and_switch_to_previous_scene( - {LfRfidApp::SceneType::SelectKey}); - - consumed = true; - } - - return consumed; -} - -void LfRfidAppSceneDeleteSuccess::on_exit(LfRfidApp* app) { - app->view_controller.get()->clean(); -} - -void LfRfidAppSceneDeleteSuccess::timeout_callback(void* context) { - LfRfidApp* app = static_cast(context); - LfRfidApp::Event event; - event.type = LfRfidApp::EventType::Back; - app->view_controller.send_event(&event); -} diff --git a/applications/lfrfid/scene/lfrfid_app_scene_delete_success.h b/applications/lfrfid/scene/lfrfid_app_scene_delete_success.h deleted file mode 100644 index 009b9f25dba..00000000000 --- a/applications/lfrfid/scene/lfrfid_app_scene_delete_success.h +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once -#include "../lfrfid_app.h" - -class LfRfidAppSceneDeleteSuccess : public GenericScene { -public: - void on_enter(LfRfidApp* app, bool need_restore) final; - bool on_event(LfRfidApp* app, LfRfidApp::Event* event) final; - void on_exit(LfRfidApp* app) final; - -private: - static void timeout_callback(void* context); -}; diff --git a/applications/lfrfid/scene/lfrfid_app_scene_emulate.cpp b/applications/lfrfid/scene/lfrfid_app_scene_emulate.cpp deleted file mode 100644 index 02cb011d13c..00000000000 --- a/applications/lfrfid/scene/lfrfid_app_scene_emulate.cpp +++ /dev/null @@ -1,36 +0,0 @@ -#include "lfrfid_app_scene_emulate.h" -#include -#include - -void LfRfidAppSceneEmulate::on_enter(LfRfidApp* app, bool /* need_restore */) { - DOLPHIN_DEED(DolphinDeedRfidEmulate); - auto popup = app->view_controller.get(); - - popup->set_header("Emulating", 89, 30, AlignCenter, AlignTop); - if(string_size(app->file_name)) { - popup->set_text(string_get_cstr(app->file_name), 89, 43, AlignCenter, AlignTop); - } else { - popup->set_text( - protocol_dict_get_name(app->dict, app->protocol_id), 89, 43, AlignCenter, AlignTop); - } - popup->set_icon(0, 3, &I_RFIDDolphinSend_97x61); - - app->view_controller.switch_to(); - lfrfid_worker_start_thread(app->lfworker); - lfrfid_worker_emulate_start(app->lfworker, (LFRFIDProtocol)app->protocol_id); - notification_message(app->notification, &sequence_blink_start_magenta); -} - -bool LfRfidAppSceneEmulate::on_event(LfRfidApp* app, LfRfidApp::Event* event) { - UNUSED(app); - UNUSED(event); - bool consumed = false; - return consumed; -} - -void LfRfidAppSceneEmulate::on_exit(LfRfidApp* app) { - app->view_controller.get()->clean(); - lfrfid_worker_stop(app->lfworker); - lfrfid_worker_stop_thread(app->lfworker); - notification_message(app->notification, &sequence_blink_stop); -} diff --git a/applications/lfrfid/scene/lfrfid_app_scene_emulate.h b/applications/lfrfid/scene/lfrfid_app_scene_emulate.h deleted file mode 100644 index 13d2b857dc9..00000000000 --- a/applications/lfrfid/scene/lfrfid_app_scene_emulate.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once -#include "../lfrfid_app.h" - -class LfRfidAppSceneEmulate : public GenericScene { -public: - void on_enter(LfRfidApp* app, bool need_restore) final; - bool on_event(LfRfidApp* app, LfRfidApp::Event* event) final; - void on_exit(LfRfidApp* app) final; -}; diff --git a/applications/lfrfid/scene/lfrfid_app_scene_exit_confirm.cpp b/applications/lfrfid/scene/lfrfid_app_scene_exit_confirm.cpp deleted file mode 100644 index be070b40e00..00000000000 --- a/applications/lfrfid/scene/lfrfid_app_scene_exit_confirm.cpp +++ /dev/null @@ -1,59 +0,0 @@ -#include "lfrfid_app_scene_exit_confirm.h" -#include "../view/elements/button_element.h" -#include "../view/elements/icon_element.h" -#include "../view/elements/string_element.h" - -void LfRfidAppSceneExitConfirm::on_enter(LfRfidApp* app, bool /* need_restore */) { - auto container = app->view_controller.get(); - - auto button = container->add(); - button->set_type(ButtonElement::Type::Left, "Exit"); - button->set_callback(app, LfRfidAppSceneExitConfirm::exit_callback); - - button = container->add(); - button->set_type(ButtonElement::Type::Right, "Stay"); - button->set_callback(app, LfRfidAppSceneExitConfirm::stay_callback); - - auto line_1 = container->add(); - auto line_2 = container->add(); - - line_1->set_text("Exit to RFID Menu?", 64, 19, 128 - 2, AlignCenter, AlignBottom, FontPrimary); - line_2->set_text( - "All unsaved data will be lost!", 64, 31, 0, AlignCenter, AlignBottom, FontSecondary); - - app->view_controller.switch_to(); -} - -bool LfRfidAppSceneExitConfirm::on_event(LfRfidApp* app, LfRfidApp::Event* event) { - bool consumed = false; - - if(event->type == LfRfidApp::EventType::Next) { - app->scene_controller.search_and_switch_to_previous_scene({LfRfidApp::SceneType::Start}); - consumed = true; - } else if(event->type == LfRfidApp::EventType::Stay) { - app->scene_controller.switch_to_previous_scene(); - consumed = true; - } else if(event->type == LfRfidApp::EventType::Back) { - consumed = true; - } - - return consumed; -} - -void LfRfidAppSceneExitConfirm::on_exit(LfRfidApp* app) { - app->view_controller.get()->clean(); -} - -void LfRfidAppSceneExitConfirm::exit_callback(void* context) { - LfRfidApp* app = static_cast(context); - LfRfidApp::Event event; - event.type = LfRfidApp::EventType::Next; - app->view_controller.send_event(&event); -} - -void LfRfidAppSceneExitConfirm::stay_callback(void* context) { - LfRfidApp* app = static_cast(context); - LfRfidApp::Event event; - event.type = LfRfidApp::EventType::Stay; - app->view_controller.send_event(&event); -} diff --git a/applications/lfrfid/scene/lfrfid_app_scene_exit_confirm.h b/applications/lfrfid/scene/lfrfid_app_scene_exit_confirm.h deleted file mode 100644 index a0a1023258b..00000000000 --- a/applications/lfrfid/scene/lfrfid_app_scene_exit_confirm.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once -#include "../lfrfid_app.h" - -class LfRfidAppSceneExitConfirm : public GenericScene { -public: - void on_enter(LfRfidApp* app, bool need_restore) final; - bool on_event(LfRfidApp* app, LfRfidApp::Event* event) final; - void on_exit(LfRfidApp* app) final; - -private: - static void exit_callback(void* context); - static void stay_callback(void* context); -}; diff --git a/applications/lfrfid/scene/lfrfid_app_scene_extra_actions.cpp b/applications/lfrfid/scene/lfrfid_app_scene_extra_actions.cpp deleted file mode 100644 index ea4f03dbb49..00000000000 --- a/applications/lfrfid/scene/lfrfid_app_scene_extra_actions.cpp +++ /dev/null @@ -1,63 +0,0 @@ -#include "lfrfid_app_scene_extra_actions.h" - -typedef enum { - SubmenuASK, - SubmenuPSK, - SubmenuRAW, -} SubmenuIndex; - -void LfRfidAppSceneExtraActions::on_enter(LfRfidApp* app, bool need_restore) { - auto submenu = app->view_controller.get(); - - submenu->add_item("Read ASK (Animal, Ordinary Card)", SubmenuASK, submenu_callback, app); - submenu->add_item("Read PSK (Indala)", SubmenuPSK, submenu_callback, app); - - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - submenu->add_item("Read RAW RFID data", SubmenuRAW, submenu_callback, app); - } - - if(need_restore) { - submenu->set_selected_item(submenu_item_selected); - } - - app->view_controller.switch_to(); -} - -bool LfRfidAppSceneExtraActions::on_event(LfRfidApp* app, LfRfidApp::Event* event) { - bool consumed = false; - - if(event->type == LfRfidApp::EventType::MenuSelected) { - submenu_item_selected = event->payload.signed_int; - switch(event->payload.signed_int) { - case SubmenuASK: - app->read_type = LFRFIDWorkerReadTypeASKOnly; - app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::Read); - break; - case SubmenuPSK: - app->read_type = LFRFIDWorkerReadTypePSKOnly; - app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::Read); - break; - case SubmenuRAW: - app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::RawName); - break; - } - - consumed = true; - } - - return consumed; -} - -void LfRfidAppSceneExtraActions::on_exit(LfRfidApp* app) { - app->view_controller.get()->clean(); -} - -void LfRfidAppSceneExtraActions::submenu_callback(void* context, uint32_t index) { - LfRfidApp* app = static_cast(context); - LfRfidApp::Event event; - - event.type = LfRfidApp::EventType::MenuSelected; - event.payload.signed_int = index; - - app->view_controller.send_event(&event); -} diff --git a/applications/lfrfid/scene/lfrfid_app_scene_extra_actions.h b/applications/lfrfid/scene/lfrfid_app_scene_extra_actions.h deleted file mode 100644 index dcd746146cf..00000000000 --- a/applications/lfrfid/scene/lfrfid_app_scene_extra_actions.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once -#include "../lfrfid_app.h" - -class LfRfidAppSceneExtraActions : public GenericScene { -public: - void on_enter(LfRfidApp* app, bool need_restore) final; - bool on_event(LfRfidApp* app, LfRfidApp::Event* event) final; - void on_exit(LfRfidApp* app) final; - -private: - static void submenu_callback(void* context, uint32_t index); - uint32_t submenu_item_selected = 0; -}; diff --git a/applications/lfrfid/scene/lfrfid_app_scene_raw_info.cpp b/applications/lfrfid/scene/lfrfid_app_scene_raw_info.cpp deleted file mode 100644 index ce3634b2acd..00000000000 --- a/applications/lfrfid/scene/lfrfid_app_scene_raw_info.cpp +++ /dev/null @@ -1,77 +0,0 @@ -#include "lfrfid_app_scene_raw_info.h" -#include "../view/elements/button_element.h" -#include "../view/elements/icon_element.h" -#include "../view/elements/string_element.h" - -static void ok_callback(void* context) { - LfRfidApp* app = static_cast(context); - LfRfidApp::Event event; - event.type = LfRfidApp::EventType::Next; - app->view_controller.send_event(&event); -} - -static void back_callback(void* context) { - LfRfidApp* app = static_cast(context); - LfRfidApp::Event event; - event.type = LfRfidApp::EventType::Back; - app->view_controller.send_event(&event); -} - -void LfRfidAppSceneRawInfo::on_enter(LfRfidApp* app, bool /* need_restore */) { - string_init(string_info); - - auto container = app->view_controller.get(); - - bool sd_exist = storage_sd_status(app->storage) == FSE_OK; - if(!sd_exist) { - auto icon = container->add(); - icon->set_icon(0, 0, &I_SDQuestion_35x43); - auto line = container->add(); - line->set_text( - "No SD card found.\nThis function will not\nwork without\nSD card.", - 81, - 4, - 0, - AlignCenter, - AlignTop, - FontSecondary); - - auto button = container->add(); - button->set_type(ButtonElement::Type::Left, "Back"); - button->set_callback(app, back_callback); - } else { - string_printf( - string_info, - "RAW RFID data reader\r\n" - "1) Put the Flipper on your card\r\n" - "2) Press OK\r\n" - "3) Wait until data is read"); - - auto line = container->add(); - line->set_text(string_get_cstr(string_info), 0, 1, 0, AlignLeft, AlignTop, FontSecondary); - - auto button = container->add(); - button->set_type(ButtonElement::Type::Center, "OK"); - button->set_callback(app, ok_callback); - } - - app->view_controller.switch_to(); -} - -bool LfRfidAppSceneRawInfo::on_event(LfRfidApp* app, LfRfidApp::Event* event) { - bool consumed = false; - if(event->type == LfRfidApp::EventType::Next) { - app->scene_controller.switch_to_scene({LfRfidApp::SceneType::RawRead}); - consumed = true; - } else if(event->type == LfRfidApp::EventType::Back) { - app->scene_controller.search_and_switch_to_previous_scene( - {LfRfidApp::SceneType::ExtraActions}); - consumed = true; - } - return consumed; -} - -void LfRfidAppSceneRawInfo::on_exit(LfRfidApp* app) { - app->view_controller.get()->clean(); - string_clear(string_info); -} diff --git a/applications/lfrfid/scene/lfrfid_app_scene_raw_info.h b/applications/lfrfid/scene/lfrfid_app_scene_raw_info.h deleted file mode 100644 index eecca14368c..00000000000 --- a/applications/lfrfid/scene/lfrfid_app_scene_raw_info.h +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once -#include "../lfrfid_app.h" - -class LfRfidAppSceneRawInfo : public GenericScene { -public: - void on_enter(LfRfidApp* app, bool need_restore) final; - bool on_event(LfRfidApp* app, LfRfidApp::Event* event) final; - void on_exit(LfRfidApp* app) final; - -private: - string_t string_info; -}; diff --git a/applications/lfrfid/scene/lfrfid_app_scene_raw_name.cpp b/applications/lfrfid/scene/lfrfid_app_scene_raw_name.cpp deleted file mode 100644 index 0ad346198ea..00000000000 --- a/applications/lfrfid/scene/lfrfid_app_scene_raw_name.cpp +++ /dev/null @@ -1,46 +0,0 @@ - -#include "lfrfid_app_scene_raw_name.h" -#include "m-string.h" -#include -#include - -void LfRfidAppSceneRawName::on_enter(LfRfidApp* app, bool /* need_restore */) { - const char* key_name = string_get_cstr(app->raw_file_name); - - bool key_name_empty = (string_size(app->raw_file_name) == 0); - if(key_name_empty) { - app->text_store.set("RfidRecord"); - } else { - app->text_store.set("%s", key_name); - } - - auto text_input = app->view_controller.get(); - text_input->set_header_text("Name the raw file"); - - text_input->set_result_callback( - save_callback, app, app->text_store.text, LFRFID_KEY_NAME_SIZE, key_name_empty); - - app->view_controller.switch_to(); -} - -bool LfRfidAppSceneRawName::on_event(LfRfidApp* app, LfRfidApp::Event* event) { - bool consumed = false; - - if(event->type == LfRfidApp::EventType::Next) { - string_set_str(app->raw_file_name, app->text_store.text); - app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::RawInfo); - } - - return consumed; -} - -void LfRfidAppSceneRawName::on_exit(LfRfidApp* app) { - app->view_controller.get()->clean(); -} - -void LfRfidAppSceneRawName::save_callback(void* context) { - LfRfidApp* app = static_cast(context); - LfRfidApp::Event event; - event.type = LfRfidApp::EventType::Next; - app->view_controller.send_event(&event); -} diff --git a/applications/lfrfid/scene/lfrfid_app_scene_raw_name.h b/applications/lfrfid/scene/lfrfid_app_scene_raw_name.h deleted file mode 100644 index 225d135e5e0..00000000000 --- a/applications/lfrfid/scene/lfrfid_app_scene_raw_name.h +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once -#include "../lfrfid_app.h" - -class LfRfidAppSceneRawName : public GenericScene { -public: - void on_enter(LfRfidApp* app, bool need_restore) final; - bool on_event(LfRfidApp* app, LfRfidApp::Event* event) final; - void on_exit(LfRfidApp* app) final; - -private: - static void save_callback(void* context); -}; diff --git a/applications/lfrfid/scene/lfrfid_app_scene_raw_read.cpp b/applications/lfrfid/scene/lfrfid_app_scene_raw_read.cpp deleted file mode 100644 index 0d04e6bc754..00000000000 --- a/applications/lfrfid/scene/lfrfid_app_scene_raw_read.cpp +++ /dev/null @@ -1,107 +0,0 @@ -#include "lfrfid_app_scene_raw_read.h" -#include - -#define RAW_READ_TIME 5000 - -static void lfrfid_read_callback(LFRFIDWorkerReadRawResult result, void* ctx) { - LfRfidApp* app = static_cast(ctx); - LfRfidApp::Event event; - - switch(result) { - case LFRFIDWorkerReadRawFileError: - event.type = LfRfidApp::EventType::ReadEventError; - break; - case LFRFIDWorkerReadRawOverrun: - event.type = LfRfidApp::EventType::ReadEventOverrun; - break; - } - - app->view_controller.send_event(&event); -} - -static void timer_callback(void* ctx) { - LfRfidApp* app = static_cast(ctx); - LfRfidApp::Event event; - event.type = LfRfidApp::EventType::ReadEventDone; - app->view_controller.send_event(&event); -} - -void LfRfidAppSceneRawRead::on_enter(LfRfidApp* app, bool /* need_restore */) { - string_init(string_file_name); - auto popup = app->view_controller.get(); - popup->set_icon(0, 3, &I_RFIDDolphinReceive_97x61); - app->view_controller.switch_to(); - lfrfid_worker_start_thread(app->lfworker); - app->make_app_folder(); - - timer = furi_timer_alloc(timer_callback, FuriTimerTypeOnce, app); - furi_timer_start(timer, RAW_READ_TIME); - string_printf( - string_file_name, "%s/%s.ask.raw", app->app_sd_folder, string_get_cstr(app->raw_file_name)); - popup->set_header("Reading\nRAW RFID\nASK", 89, 30, AlignCenter, AlignTop); - lfrfid_worker_read_raw_start( - app->lfworker, - string_get_cstr(string_file_name), - LFRFIDWorkerReadTypeASKOnly, - lfrfid_read_callback, - app); - - notification_message(app->notification, &sequence_blink_start_cyan); - - is_psk = false; - error = false; -} - -bool LfRfidAppSceneRawRead::on_event(LfRfidApp* app, LfRfidApp::Event* event) { - UNUSED(app); - bool consumed = true; - auto popup = app->view_controller.get(); - - switch(event->type) { - case LfRfidApp::EventType::ReadEventError: - error = true; - popup->set_header("Reading\nRAW RFID\nFile error", 89, 30, AlignCenter, AlignTop); - notification_message(app->notification, &sequence_blink_start_red); - furi_timer_stop(timer); - break; - case LfRfidApp::EventType::ReadEventDone: - if(!error) { - if(is_psk) { - notification_message(app->notification, &sequence_success); - app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::RawSuccess); - } else { - popup->set_header("Reading\nRAW RFID\nPSK", 89, 30, AlignCenter, AlignTop); - notification_message(app->notification, &sequence_blink_start_yellow); - lfrfid_worker_stop(app->lfworker); - string_printf( - string_file_name, - "%s/%s.psk.raw", - app->app_sd_folder, - string_get_cstr(app->raw_file_name)); - lfrfid_worker_read_raw_start( - app->lfworker, - string_get_cstr(string_file_name), - LFRFIDWorkerReadTypePSKOnly, - lfrfid_read_callback, - app); - furi_timer_start(timer, RAW_READ_TIME); - is_psk = true; - } - } - break; - default: - consumed = false; - break; - } - - return consumed; -} - -void LfRfidAppSceneRawRead::on_exit(LfRfidApp* app) { - notification_message(app->notification, &sequence_blink_stop); - app->view_controller.get()->clean(); - lfrfid_worker_stop(app->lfworker); - lfrfid_worker_stop_thread(app->lfworker); - furi_timer_free(timer); - string_clear(string_file_name); -} diff --git a/applications/lfrfid/scene/lfrfid_app_scene_raw_read.h b/applications/lfrfid/scene/lfrfid_app_scene_raw_read.h deleted file mode 100644 index 09ef746390a..00000000000 --- a/applications/lfrfid/scene/lfrfid_app_scene_raw_read.h +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once -#include "../lfrfid_app.h" - -class LfRfidAppSceneRawRead : public GenericScene { -public: - void on_enter(LfRfidApp* app, bool need_restore) final; - bool on_event(LfRfidApp* app, LfRfidApp::Event* event) final; - void on_exit(LfRfidApp* app) final; - -private: - string_t string_file_name; - FuriTimer* timer; - bool is_psk; - bool error; -}; diff --git a/applications/lfrfid/scene/lfrfid_app_scene_raw_success.cpp b/applications/lfrfid/scene/lfrfid_app_scene_raw_success.cpp deleted file mode 100644 index 227ab580a8e..00000000000 --- a/applications/lfrfid/scene/lfrfid_app_scene_raw_success.cpp +++ /dev/null @@ -1,45 +0,0 @@ -#include "lfrfid_app_scene_raw_success.h" -#include "../view/elements/button_element.h" -#include "../view/elements/icon_element.h" -#include "../view/elements/string_element.h" - -void LfRfidAppSceneRawSuccess::on_enter(LfRfidApp* app, bool /* need_restore */) { - string_init(string_info); - - string_printf(string_info, "RAW RFID read success!\r\n"); - string_cat_printf(string_info, "Now you can analyze files\r\n"); - string_cat_printf(string_info, "Or send them to developers"); - - auto container = app->view_controller.get(); - - auto line = container->add(); - line->set_text(string_get_cstr(string_info), 0, 1, 0, AlignLeft, AlignTop, FontSecondary); - - auto button = container->add(); - button->set_type(ButtonElement::Type::Center, "OK"); - button->set_callback(app, LfRfidAppSceneRawSuccess::ok_callback); - - app->view_controller.switch_to(); -} - -bool LfRfidAppSceneRawSuccess::on_event(LfRfidApp* app, LfRfidApp::Event* event) { - bool consumed = false; - if(event->type == LfRfidApp::EventType::Next) { - app->scene_controller.search_and_switch_to_previous_scene( - {LfRfidApp::SceneType::ExtraActions}); - consumed = true; - } - return consumed; -} - -void LfRfidAppSceneRawSuccess::on_exit(LfRfidApp* app) { - app->view_controller.get()->clean(); - string_clear(string_info); -} - -void LfRfidAppSceneRawSuccess::ok_callback(void* context) { - LfRfidApp* app = static_cast(context); - LfRfidApp::Event event; - event.type = LfRfidApp::EventType::Next; - app->view_controller.send_event(&event); -} diff --git a/applications/lfrfid/scene/lfrfid_app_scene_raw_success.h b/applications/lfrfid/scene/lfrfid_app_scene_raw_success.h deleted file mode 100644 index 0a0b0116b54..00000000000 --- a/applications/lfrfid/scene/lfrfid_app_scene_raw_success.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once -#include "../lfrfid_app.h" - -class LfRfidAppSceneRawSuccess : public GenericScene { -public: - void on_enter(LfRfidApp* app, bool need_restore) final; - bool on_event(LfRfidApp* app, LfRfidApp::Event* event) final; - void on_exit(LfRfidApp* app) final; - -private: - string_t string_info; - static void ok_callback(void* context); -}; diff --git a/applications/lfrfid/scene/lfrfid_app_scene_read.cpp b/applications/lfrfid/scene/lfrfid_app_scene_read.cpp deleted file mode 100644 index 120eb1a0726..00000000000 --- a/applications/lfrfid/scene/lfrfid_app_scene_read.cpp +++ /dev/null @@ -1,100 +0,0 @@ -#include "lfrfid_app_scene_read.h" -#include - -static void lfrfid_read_callback(LFRFIDWorkerReadResult result, ProtocolId protocol, void* ctx) { - LfRfidApp* app = static_cast(ctx); - LfRfidApp::Event event; - - switch(result) { - case LFRFIDWorkerReadSenseStart: - event.type = LfRfidApp::EventType::ReadEventSenseStart; - break; - case LFRFIDWorkerReadSenseEnd: - event.type = LfRfidApp::EventType::ReadEventSenseEnd; - break; - case LFRFIDWorkerReadSenseCardStart: - event.type = LfRfidApp::EventType::ReadEventSenseCardStart; - break; - case LFRFIDWorkerReadSenseCardEnd: - event.type = LfRfidApp::EventType::ReadEventSenseCardEnd; - break; - case LFRFIDWorkerReadDone: - event.type = LfRfidApp::EventType::ReadEventDone; - break; - case LFRFIDWorkerReadStartASK: - event.type = LfRfidApp::EventType::ReadEventStartASK; - break; - case LFRFIDWorkerReadStartPSK: - event.type = LfRfidApp::EventType::ReadEventStartPSK; - break; - } - - event.payload.signed_int = protocol; - - app->view_controller.send_event(&event); -} - -void LfRfidAppSceneRead::on_enter(LfRfidApp* app, bool /* need_restore */) { - auto popup = app->view_controller.get(); - - DOLPHIN_DEED(DolphinDeedRfidRead); - if(app->read_type == LFRFIDWorkerReadTypePSKOnly) { - popup->set_header("Reading\nLF RFID\nPSK", 89, 30, AlignCenter, AlignTop); - } else { - popup->set_header("Reading\nLF RFID\nASK", 89, 30, AlignCenter, AlignTop); - } - - popup->set_icon(0, 3, &I_RFIDDolphinReceive_97x61); - - app->view_controller.switch_to(); - lfrfid_worker_start_thread(app->lfworker); - lfrfid_worker_read_start(app->lfworker, app->read_type, lfrfid_read_callback, app); - - notification_message(app->notification, &sequence_blink_start_cyan); -} - -bool LfRfidAppSceneRead::on_event(LfRfidApp* app, LfRfidApp::Event* event) { - bool consumed = true; - auto popup = app->view_controller.get(); - - switch(event->type) { - case LfRfidApp::EventType::ReadEventSenseStart: - notification_message(app->notification, &sequence_blink_stop); - notification_message(app->notification, &sequence_blink_start_yellow); - break; - case LfRfidApp::EventType::ReadEventSenseCardStart: - notification_message(app->notification, &sequence_blink_stop); - notification_message(app->notification, &sequence_blink_start_green); - break; - case LfRfidApp::EventType::ReadEventSenseEnd: - case LfRfidApp::EventType::ReadEventSenseCardEnd: - notification_message(app->notification, &sequence_blink_stop); - notification_message(app->notification, &sequence_blink_start_cyan); - break; - case LfRfidApp::EventType::ReadEventDone: - app->protocol_id = event->payload.signed_int; - DOLPHIN_DEED(DolphinDeedRfidReadSuccess); - notification_message(app->notification, &sequence_success); - string_reset(app->file_name); - app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::ReadSuccess); - break; - case LfRfidApp::EventType::ReadEventStartPSK: - popup->set_header("Reading\nLF RFID\nPSK", 89, 30, AlignCenter, AlignTop); - break; - case LfRfidApp::EventType::ReadEventStartASK: - popup->set_header("Reading\nLF RFID\nASK", 89, 30, AlignCenter, AlignTop); - break; - default: - consumed = false; - break; - } - - return consumed; -} - -void LfRfidAppSceneRead::on_exit(LfRfidApp* app) { - notification_message(app->notification, &sequence_blink_stop); - app->view_controller.get()->clean(); - lfrfid_worker_stop(app->lfworker); - lfrfid_worker_stop_thread(app->lfworker); -} diff --git a/applications/lfrfid/scene/lfrfid_app_scene_read.h b/applications/lfrfid/scene/lfrfid_app_scene_read.h deleted file mode 100644 index b5035b72a02..00000000000 --- a/applications/lfrfid/scene/lfrfid_app_scene_read.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once -#include "../lfrfid_app.h" - -class LfRfidAppSceneRead : public GenericScene { -public: - void on_enter(LfRfidApp* app, bool need_restore) final; - bool on_event(LfRfidApp* app, LfRfidApp::Event* event) final; - void on_exit(LfRfidApp* app) final; -}; diff --git a/applications/lfrfid/scene/lfrfid_app_scene_read_menu.cpp b/applications/lfrfid/scene/lfrfid_app_scene_read_menu.cpp deleted file mode 100644 index aa3b3f1fbac..00000000000 --- a/applications/lfrfid/scene/lfrfid_app_scene_read_menu.cpp +++ /dev/null @@ -1,60 +0,0 @@ -#include "lfrfid_app_scene_read_menu.h" - -typedef enum { - SubmenuSave, - SubmenuEmulate, - SubmenuWrite, -} SubmenuIndex; - -void LfRfidAppSceneReadKeyMenu::on_enter(LfRfidApp* app, bool need_restore) { - auto submenu = app->view_controller.get(); - - submenu->add_item("Save", SubmenuSave, submenu_callback, app); - submenu->add_item("Emulate", SubmenuEmulate, submenu_callback, app); - submenu->add_item("Write", SubmenuWrite, submenu_callback, app); - - if(need_restore) { - submenu->set_selected_item(submenu_item_selected); - } - - app->view_controller.switch_to(); -} - -bool LfRfidAppSceneReadKeyMenu::on_event(LfRfidApp* app, LfRfidApp::Event* event) { - bool consumed = false; - - if(event->type == LfRfidApp::EventType::MenuSelected) { - submenu_item_selected = event->payload.signed_int; - switch(event->payload.signed_int) { - case SubmenuWrite: - app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::Write); - break; - case SubmenuSave: - app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::SaveName); - break; - case SubmenuEmulate: - app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::Emulate); - break; - } - consumed = true; - } else if(event->type == LfRfidApp::EventType::Back) { - app->scene_controller.switch_to_previous_scene(); - consumed = true; - } - - return consumed; -} - -void LfRfidAppSceneReadKeyMenu::on_exit(LfRfidApp* app) { - app->view_controller.get()->clean(); -} - -void LfRfidAppSceneReadKeyMenu::submenu_callback(void* context, uint32_t index) { - LfRfidApp* app = static_cast(context); - LfRfidApp::Event event; - - event.type = LfRfidApp::EventType::MenuSelected; - event.payload.signed_int = index; - - app->view_controller.send_event(&event); -} diff --git a/applications/lfrfid/scene/lfrfid_app_scene_read_menu.h b/applications/lfrfid/scene/lfrfid_app_scene_read_menu.h deleted file mode 100644 index 2b50b96f9a9..00000000000 --- a/applications/lfrfid/scene/lfrfid_app_scene_read_menu.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once -#include "../lfrfid_app.h" - -class LfRfidAppSceneReadKeyMenu : public GenericScene { -public: - void on_enter(LfRfidApp* app, bool need_restore) final; - bool on_event(LfRfidApp* app, LfRfidApp::Event* event) final; - void on_exit(LfRfidApp* app) final; - -private: - static void submenu_callback(void* context, uint32_t index); - uint32_t submenu_item_selected = 0; -}; diff --git a/applications/lfrfid/scene/lfrfid_app_scene_read_success.cpp b/applications/lfrfid/scene/lfrfid_app_scene_read_success.cpp deleted file mode 100644 index 277b43a3e5f..00000000000 --- a/applications/lfrfid/scene/lfrfid_app_scene_read_success.cpp +++ /dev/null @@ -1,96 +0,0 @@ -#include "lfrfid_app_scene_read_success.h" -#include "../view/elements/button_element.h" -#include "../view/elements/icon_element.h" -#include "../view/elements/string_element.h" - -void LfRfidAppSceneReadSuccess::on_enter(LfRfidApp* app, bool /* need_restore */) { - string_init(string_info); - string_init(string_header); - - string_init_printf( - string_header, - "%s[%s]", - protocol_dict_get_name(app->dict, app->protocol_id), - protocol_dict_get_manufacturer(app->dict, app->protocol_id)); - - size_t size = protocol_dict_get_data_size(app->dict, app->protocol_id); - uint8_t* data = (uint8_t*)malloc(size); - protocol_dict_get_data(app->dict, app->protocol_id, data, size); - for(uint8_t i = 0; i < size; i++) { - if(i != 0) { - string_cat_printf(string_info, " "); - } - - if(i >= 9) { - string_cat_printf(string_info, "..."); - break; - } else { - string_cat_printf(string_info, "%02X", data[i]); - } - } - free(data); - - string_t render_data; - string_init(render_data); - protocol_dict_render_brief_data(app->dict, render_data, app->protocol_id); - string_cat_printf(string_info, "\r\n%s", string_get_cstr(render_data)); - string_clear(render_data); - - auto container = app->view_controller.get(); - - auto button = container->add(); - button->set_type(ButtonElement::Type::Left, "Retry"); - button->set_callback(app, LfRfidAppSceneReadSuccess::back_callback); - - button = container->add(); - button->set_type(ButtonElement::Type::Right, "More"); - button->set_callback(app, LfRfidAppSceneReadSuccess::more_callback); - - auto header = container->add(); - header->set_text(string_get_cstr(string_header), 0, 2, 0, AlignLeft, AlignTop, FontPrimary); - - auto text = container->add(); - text->set_text(string_get_cstr(string_info), 0, 16, 0, AlignLeft, AlignTop, FontSecondary); - - app->view_controller.switch_to(); - - notification_message_block(app->notification, &sequence_set_green_255); -} - -bool LfRfidAppSceneReadSuccess::on_event(LfRfidApp* app, LfRfidApp::Event* event) { - bool consumed = false; - - if(event->type == LfRfidApp::EventType::Next) { - app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::ReadKeyMenu); - consumed = true; - } else if(event->type == LfRfidApp::EventType::Retry) { - app->scene_controller.switch_to_next_scene({LfRfidApp::SceneType::RetryConfirm}); - consumed = true; - } else if(event->type == LfRfidApp::EventType::Back) { - app->scene_controller.switch_to_next_scene({LfRfidApp::SceneType::ExitConfirm}); - consumed = true; - } - - return consumed; -} - -void LfRfidAppSceneReadSuccess::on_exit(LfRfidApp* app) { - notification_message_block(app->notification, &sequence_reset_green); - app->view_controller.get()->clean(); - string_clear(string_info); - string_clear(string_header); -} - -void LfRfidAppSceneReadSuccess::back_callback(void* context) { - LfRfidApp* app = static_cast(context); - LfRfidApp::Event event; - event.type = LfRfidApp::EventType::Retry; - app->view_controller.send_event(&event); -} - -void LfRfidAppSceneReadSuccess::more_callback(void* context) { - LfRfidApp* app = static_cast(context); - LfRfidApp::Event event; - event.type = LfRfidApp::EventType::Next; - app->view_controller.send_event(&event); -} diff --git a/applications/lfrfid/scene/lfrfid_app_scene_read_success.h b/applications/lfrfid/scene/lfrfid_app_scene_read_success.h deleted file mode 100644 index 6d90f631095..00000000000 --- a/applications/lfrfid/scene/lfrfid_app_scene_read_success.h +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once -#include "../lfrfid_app.h" - -class LfRfidAppSceneReadSuccess : public GenericScene { -public: - void on_enter(LfRfidApp* app, bool need_restore) final; - bool on_event(LfRfidApp* app, LfRfidApp::Event* event) final; - void on_exit(LfRfidApp* app) final; - -private: - static void back_callback(void* context); - static void more_callback(void* context); - - string_t string_header; - string_t string_info; -}; diff --git a/applications/lfrfid/scene/lfrfid_app_scene_retry_confirm.cpp b/applications/lfrfid/scene/lfrfid_app_scene_retry_confirm.cpp deleted file mode 100644 index c181223238f..00000000000 --- a/applications/lfrfid/scene/lfrfid_app_scene_retry_confirm.cpp +++ /dev/null @@ -1,59 +0,0 @@ -#include "lfrfid_app_scene_retry_confirm.h" -#include "../view/elements/button_element.h" -#include "../view/elements/icon_element.h" -#include "../view/elements/string_element.h" - -void LfRfidAppSceneRetryConfirm::on_enter(LfRfidApp* app, bool /* need_restore */) { - auto container = app->view_controller.get(); - - auto button = container->add(); - button->set_type(ButtonElement::Type::Left, "Exit"); - button->set_callback(app, LfRfidAppSceneRetryConfirm::exit_callback); - - button = container->add(); - button->set_type(ButtonElement::Type::Right, "Stay"); - button->set_callback(app, LfRfidAppSceneRetryConfirm::stay_callback); - - auto line_1 = container->add(); - auto line_2 = container->add(); - - line_1->set_text("Return to Reading?", 64, 19, 128 - 2, AlignCenter, AlignBottom, FontPrimary); - line_2->set_text( - "All unsaved data will be lost!", 64, 29, 0, AlignCenter, AlignBottom, FontSecondary); - - app->view_controller.switch_to(); -} - -bool LfRfidAppSceneRetryConfirm::on_event(LfRfidApp* app, LfRfidApp::Event* event) { - bool consumed = false; - - if(event->type == LfRfidApp::EventType::Next) { - app->scene_controller.search_and_switch_to_previous_scene({LfRfidApp::SceneType::Read}); - consumed = true; - } else if(event->type == LfRfidApp::EventType::Stay) { - app->scene_controller.switch_to_previous_scene(); - consumed = true; - } else if(event->type == LfRfidApp::EventType::Back) { - consumed = true; - } - - return consumed; -} - -void LfRfidAppSceneRetryConfirm::on_exit(LfRfidApp* app) { - app->view_controller.get()->clean(); -} - -void LfRfidAppSceneRetryConfirm::exit_callback(void* context) { - LfRfidApp* app = static_cast(context); - LfRfidApp::Event event; - event.type = LfRfidApp::EventType::Next; - app->view_controller.send_event(&event); -} - -void LfRfidAppSceneRetryConfirm::stay_callback(void* context) { - LfRfidApp* app = static_cast(context); - LfRfidApp::Event event; - event.type = LfRfidApp::EventType::Stay; - app->view_controller.send_event(&event); -} diff --git a/applications/lfrfid/scene/lfrfid_app_scene_retry_confirm.h b/applications/lfrfid/scene/lfrfid_app_scene_retry_confirm.h deleted file mode 100644 index 01b7329c90a..00000000000 --- a/applications/lfrfid/scene/lfrfid_app_scene_retry_confirm.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once -#include "../lfrfid_app.h" - -class LfRfidAppSceneRetryConfirm : public GenericScene { -public: - void on_enter(LfRfidApp* app, bool need_restore) final; - bool on_event(LfRfidApp* app, LfRfidApp::Event* event) final; - void on_exit(LfRfidApp* app) final; - -private: - static void exit_callback(void* context); - static void stay_callback(void* context); -}; diff --git a/applications/lfrfid/scene/lfrfid_app_scene_rpc.cpp b/applications/lfrfid/scene/lfrfid_app_scene_rpc.cpp deleted file mode 100644 index c2e5ec2a61d..00000000000 --- a/applications/lfrfid/scene/lfrfid_app_scene_rpc.cpp +++ /dev/null @@ -1,66 +0,0 @@ -#include "lfrfid_app_scene_rpc.h" -#include -#include -#include - -void LfRfidAppSceneRpc::on_enter(LfRfidApp* app, bool /* need_restore */) { - auto popup = app->view_controller.get(); - - popup->set_header("LF RFID", 89, 42, AlignCenter, AlignBottom); - popup->set_text("RPC mode", 89, 44, AlignCenter, AlignTop); - popup->set_icon(0, 12, &I_RFIDDolphinSend_97x61); - - app->view_controller.switch_to(); - - notification_message(app->notification, &sequence_display_backlight_on); -} - -bool LfRfidAppSceneRpc::on_event(LfRfidApp* app, LfRfidApp::Event* event) { - UNUSED(app); - UNUSED(event); - bool consumed = false; - - if(event->type == LfRfidApp::EventType::Exit) { - consumed = true; - LfRfidApp::Event view_event; - view_event.type = LfRfidApp::EventType::Back; - app->view_controller.send_event(&view_event); - rpc_system_app_confirm(app->rpc_ctx, RpcAppEventAppExit, true); - } else if(event->type == LfRfidApp::EventType::RpcSessionClose) { - consumed = true; - LfRfidApp::Event view_event; - view_event.type = LfRfidApp::EventType::Back; - app->view_controller.send_event(&view_event); - } else if(event->type == LfRfidApp::EventType::RpcLoadFile) { - const char* arg = rpc_system_app_get_data(app->rpc_ctx); - consumed = true; - bool result = false; - if(arg && !emulating) { - string_set_str(app->file_path, arg); - if(app->load_key_data(app->file_path, false)) { - lfrfid_worker_start_thread(app->lfworker); - lfrfid_worker_emulate_start(app->lfworker, (LFRFIDProtocol)app->protocol_id); - emulating = true; - - auto popup = app->view_controller.get(); - app->text_store.set("emulating\n%s", string_get_cstr(app->file_name)); - popup->set_text(app->text_store.text, 89, 44, AlignCenter, AlignTop); - - notification_message(app->notification, &sequence_blink_start_magenta); - result = true; - } - } - rpc_system_app_confirm(app->rpc_ctx, RpcAppEventLoadFile, result); - } - - return consumed; -} - -void LfRfidAppSceneRpc::on_exit(LfRfidApp* app) { - if(emulating) { - lfrfid_worker_stop(app->lfworker); - lfrfid_worker_stop_thread(app->lfworker); - notification_message(app->notification, &sequence_blink_stop); - } - app->view_controller.get()->clean(); -} diff --git a/applications/lfrfid/scene/lfrfid_app_scene_rpc.h b/applications/lfrfid/scene/lfrfid_app_scene_rpc.h deleted file mode 100644 index f630dfd35e1..00000000000 --- a/applications/lfrfid/scene/lfrfid_app_scene_rpc.h +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once -#include "../lfrfid_app.h" - -class LfRfidAppSceneRpc : public GenericScene { -public: - void on_enter(LfRfidApp* app, bool need_restore) final; - bool on_event(LfRfidApp* app, LfRfidApp::Event* event) final; - void on_exit(LfRfidApp* app) final; - -private: - bool emulating = false; -}; diff --git a/applications/lfrfid/scene/lfrfid_app_scene_save_data.cpp b/applications/lfrfid/scene/lfrfid_app_scene_save_data.cpp deleted file mode 100644 index c506cd72993..00000000000 --- a/applications/lfrfid/scene/lfrfid_app_scene_save_data.cpp +++ /dev/null @@ -1,45 +0,0 @@ -#include "lfrfid_app_scene_save_data.h" -#include - -void LfRfidAppSceneSaveData::on_enter(LfRfidApp* app, bool need_restore) { - auto byte_input = app->view_controller.get(); - size_t size = protocol_dict_get_data_size(app->dict, app->protocol_id); - - if(need_restore) { - protocol_dict_set_data(app->dict, app->protocol_id, app->old_key_data, size); - } else { - protocol_dict_get_data(app->dict, app->protocol_id, app->old_key_data, size); - } - - protocol_dict_get_data(app->dict, app->protocol_id, app->new_key_data, size); - - byte_input->set_header_text("Enter the data in hex"); - - byte_input->set_result_callback(save_callback, NULL, app, app->new_key_data, size); - - app->view_controller.switch_to(); -} - -bool LfRfidAppSceneSaveData::on_event(LfRfidApp* app, LfRfidApp::Event* event) { - bool consumed = false; - - if(event->type == LfRfidApp::EventType::Next) { - size_t size = protocol_dict_get_data_size(app->dict, app->protocol_id); - protocol_dict_set_data(app->dict, app->protocol_id, app->new_key_data, size); - DOLPHIN_DEED(DolphinDeedRfidAdd); - app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::SaveName); - } - - return consumed; -} - -void LfRfidAppSceneSaveData::on_exit(LfRfidApp* app) { - app->view_controller.get()->clean(); -} - -void LfRfidAppSceneSaveData::save_callback(void* context) { - LfRfidApp* app = static_cast(context); - LfRfidApp::Event event; - event.type = LfRfidApp::EventType::Next; - app->view_controller.send_event(&event); -} diff --git a/applications/lfrfid/scene/lfrfid_app_scene_save_data.h b/applications/lfrfid/scene/lfrfid_app_scene_save_data.h deleted file mode 100644 index d03cae12510..00000000000 --- a/applications/lfrfid/scene/lfrfid_app_scene_save_data.h +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once -#include "../lfrfid_app.h" - -class LfRfidAppSceneSaveData : public GenericScene { -public: - void on_enter(LfRfidApp* app, bool need_restore) final; - bool on_event(LfRfidApp* app, LfRfidApp::Event* event) final; - void on_exit(LfRfidApp* app) final; - -private: - static void save_callback(void* context); -}; diff --git a/applications/lfrfid/scene/lfrfid_app_scene_save_name.cpp b/applications/lfrfid/scene/lfrfid_app_scene_save_name.cpp deleted file mode 100644 index ed58b6453e9..00000000000 --- a/applications/lfrfid/scene/lfrfid_app_scene_save_name.cpp +++ /dev/null @@ -1,72 +0,0 @@ -#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 = string_get_cstr(app->file_name); - - bool key_name_empty = (string_size(app->file_name) == 0); - 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); - } - - auto text_input = app->view_controller.get(); - text_input->set_header_text("Name the card"); - - text_input->set_result_callback( - save_callback, app, app->text_store.text, LFRFID_KEY_NAME_SIZE, 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(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(); -} - -bool LfRfidAppSceneSaveName::on_event(LfRfidApp* app, LfRfidApp::Event* event) { - bool consumed = false; - - if(event->type == LfRfidApp::EventType::Next) { - if(string_size(app->file_name) > 0) { - app->delete_key(); - } - - string_set_str(app->file_name, app->text_store.text); - - if(app->save_key()) { - app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::SaveSuccess); - } else { - app->scene_controller.search_and_switch_to_previous_scene( - {LfRfidApp::SceneType::ReadKeyMenu}); - } - } - - return consumed; -} - -void LfRfidAppSceneSaveName::on_exit(LfRfidApp* app) { - void* validator_context = - app->view_controller.get()->get_validator_callback_context(); - app->view_controller.get()->set_validator(NULL, NULL); - validator_is_file_free((ValidatorIsFile*)validator_context); - - app->view_controller.get()->clean(); -} - -void LfRfidAppSceneSaveName::save_callback(void* context) { - LfRfidApp* app = static_cast(context); - LfRfidApp::Event event; - event.type = LfRfidApp::EventType::Next; - app->view_controller.send_event(&event); -} diff --git a/applications/lfrfid/scene/lfrfid_app_scene_save_name.h b/applications/lfrfid/scene/lfrfid_app_scene_save_name.h deleted file mode 100644 index ced42cc0e45..00000000000 --- a/applications/lfrfid/scene/lfrfid_app_scene_save_name.h +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once -#include "../lfrfid_app.h" - -class LfRfidAppSceneSaveName : public GenericScene { -public: - void on_enter(LfRfidApp* app, bool need_restore) final; - bool on_event(LfRfidApp* app, LfRfidApp::Event* event) final; - void on_exit(LfRfidApp* app) final; - -private: - static void save_callback(void* context); -}; diff --git a/applications/lfrfid/scene/lfrfid_app_scene_save_success.cpp b/applications/lfrfid/scene/lfrfid_app_scene_save_success.cpp deleted file mode 100644 index 64efafa7314..00000000000 --- a/applications/lfrfid/scene/lfrfid_app_scene_save_success.cpp +++ /dev/null @@ -1,50 +0,0 @@ -#include "lfrfid_app_scene_save_success.h" -#include -#include -#include - -void LfRfidAppSceneSaveSuccess::on_enter(LfRfidApp* app, bool /* need_restore */) { - auto popup = app->view_controller.get(); - - DOLPHIN_DEED(DolphinDeedRfidSave); - popup->set_icon(32, 5, &I_DolphinNice_96x59); - popup->set_header("Saved!", 5, 7, AlignLeft, AlignTop); - popup->set_context(app); - popup->set_callback(LfRfidAppSceneSaveSuccess::timeout_callback); - popup->set_timeout(1500); - popup->enable_timeout(); - - app->view_controller.switch_to(); -} - -bool LfRfidAppSceneSaveSuccess::on_event(LfRfidApp* app, LfRfidApp::Event* event) { - bool consumed = false; - - if(event->type == LfRfidApp::EventType::Back) { - bool result = app->scene_controller.has_previous_scene( - {LfRfidApp::SceneType::ReadKeyMenu, LfRfidApp::SceneType::SelectKey}); - - if(result) { - app->scene_controller.search_and_switch_to_previous_scene( - {LfRfidApp::SceneType::ReadKeyMenu, LfRfidApp::SceneType::SelectKey}); - } else { - app->scene_controller.search_and_switch_to_another_scene( - {LfRfidApp::SceneType::SaveType}, LfRfidApp::SceneType::SelectKey); - } - - consumed = true; - } - - return consumed; -} - -void LfRfidAppSceneSaveSuccess::on_exit(LfRfidApp* app) { - app->view_controller.get()->clean(); -} - -void LfRfidAppSceneSaveSuccess::timeout_callback(void* context) { - LfRfidApp* app = static_cast(context); - LfRfidApp::Event event; - event.type = LfRfidApp::EventType::Back; - app->view_controller.send_event(&event); -} diff --git a/applications/lfrfid/scene/lfrfid_app_scene_save_success.h b/applications/lfrfid/scene/lfrfid_app_scene_save_success.h deleted file mode 100644 index 62273a76be3..00000000000 --- a/applications/lfrfid/scene/lfrfid_app_scene_save_success.h +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once -#include "../lfrfid_app.h" - -class LfRfidAppSceneSaveSuccess : public GenericScene { -public: - void on_enter(LfRfidApp* app, bool need_restore) final; - bool on_event(LfRfidApp* app, LfRfidApp::Event* event) final; - void on_exit(LfRfidApp* app) final; - -private: - static void timeout_callback(void* context); -}; diff --git a/applications/lfrfid/scene/lfrfid_app_scene_save_type.cpp b/applications/lfrfid/scene/lfrfid_app_scene_save_type.cpp deleted file mode 100644 index b017e7b057f..00000000000 --- a/applications/lfrfid/scene/lfrfid_app_scene_save_type.cpp +++ /dev/null @@ -1,53 +0,0 @@ -#include "lfrfid_app_scene_save_type.h" - -void LfRfidAppSceneSaveType::on_enter(LfRfidApp* app, bool need_restore) { - auto submenu = app->view_controller.get(); - - for(uint8_t i = 0; i < keys_count; i++) { - string_init_printf( - submenu_name[i], - "%s %s", - protocol_dict_get_manufacturer(app->dict, i), - protocol_dict_get_name(app->dict, i)); - submenu->add_item(string_get_cstr(submenu_name[i]), i, submenu_callback, app); - } - - if(need_restore) { - submenu->set_selected_item(submenu_item_selected); - } - - app->view_controller.switch_to(); - - // clear key name - string_reset(app->file_name); -} - -bool LfRfidAppSceneSaveType::on_event(LfRfidApp* app, LfRfidApp::Event* event) { - bool consumed = false; - - if(event->type == LfRfidApp::EventType::MenuSelected) { - submenu_item_selected = event->payload.signed_int; - app->protocol_id = event->payload.signed_int; - app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::SaveData); - consumed = true; - } - - return consumed; -} - -void LfRfidAppSceneSaveType::on_exit(LfRfidApp* app) { - app->view_controller.get()->clean(); - for(uint8_t i = 0; i < keys_count; i++) { - string_clear(submenu_name[i]); - } -} - -void LfRfidAppSceneSaveType::submenu_callback(void* context, uint32_t index) { - LfRfidApp* app = static_cast(context); - LfRfidApp::Event event; - - event.type = LfRfidApp::EventType::MenuSelected; - event.payload.signed_int = index; - - app->view_controller.send_event(&event); -} diff --git a/applications/lfrfid/scene/lfrfid_app_scene_save_type.h b/applications/lfrfid/scene/lfrfid_app_scene_save_type.h deleted file mode 100644 index e4c1be3e64c..00000000000 --- a/applications/lfrfid/scene/lfrfid_app_scene_save_type.h +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once -#include "../lfrfid_app.h" - -class LfRfidAppSceneSaveType : public GenericScene { -public: - void on_enter(LfRfidApp* app, bool need_restore) final; - bool on_event(LfRfidApp* app, LfRfidApp::Event* event) final; - void on_exit(LfRfidApp* app) final; - -private: - static void submenu_callback(void* context, uint32_t index); - uint32_t submenu_item_selected = 0; - static const uint8_t keys_count = static_cast(LFRFIDProtocol::LFRFIDProtocolMax); - string_t submenu_name[keys_count]; -}; diff --git a/applications/lfrfid/scene/lfrfid_app_scene_saved_info.cpp b/applications/lfrfid/scene/lfrfid_app_scene_saved_info.cpp deleted file mode 100644 index 614dd505c2e..00000000000 --- a/applications/lfrfid/scene/lfrfid_app_scene_saved_info.cpp +++ /dev/null @@ -1,48 +0,0 @@ -#include "lfrfid_app_scene_saved_info.h" -#include "../view/elements/button_element.h" -#include "../view/elements/icon_element.h" -#include "../view/elements/string_element.h" - -void LfRfidAppSceneSavedInfo::on_enter(LfRfidApp* app, bool /* need_restore */) { - string_init(string_info); - - string_printf( - string_info, - "%s [%s]\r\n", - string_get_cstr(app->file_name), - protocol_dict_get_name(app->dict, app->protocol_id)); - - size_t size = protocol_dict_get_data_size(app->dict, app->protocol_id); - uint8_t* data = (uint8_t*)malloc(size); - protocol_dict_get_data(app->dict, app->protocol_id, data, size); - for(uint8_t i = 0; i < size; i++) { - if(i != 0) { - string_cat_printf(string_info, " "); - } - - string_cat_printf(string_info, "%02X", data[i]); - } - free(data); - - string_t render_data; - string_init(render_data); - protocol_dict_render_data(app->dict, render_data, app->protocol_id); - string_cat_printf(string_info, "\r\n%s", string_get_cstr(render_data)); - string_clear(render_data); - - auto container = app->view_controller.get(); - - auto line_1 = container->add(); - line_1->set_text(string_get_cstr(string_info), 0, 1, 0, AlignLeft, AlignTop, FontSecondary); - - app->view_controller.switch_to(); -} - -bool LfRfidAppSceneSavedInfo::on_event(LfRfidApp* /* app */, LfRfidApp::Event* /* event */) { - return false; -} - -void LfRfidAppSceneSavedInfo::on_exit(LfRfidApp* app) { - app->view_controller.get()->clean(); - string_clear(string_info); -} diff --git a/applications/lfrfid/scene/lfrfid_app_scene_saved_info.h b/applications/lfrfid/scene/lfrfid_app_scene_saved_info.h deleted file mode 100644 index b0b588bcb1a..00000000000 --- a/applications/lfrfid/scene/lfrfid_app_scene_saved_info.h +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once -#include "../lfrfid_app.h" - -class LfRfidAppSceneSavedInfo : public GenericScene { -public: - void on_enter(LfRfidApp* app, bool need_restore) final; - bool on_event(LfRfidApp* app, LfRfidApp::Event* event) final; - void on_exit(LfRfidApp* app) final; - -private: - string_t string_info; -}; diff --git a/applications/lfrfid/scene/lfrfid_app_scene_saved_key_menu.cpp b/applications/lfrfid/scene/lfrfid_app_scene_saved_key_menu.cpp deleted file mode 100644 index e7a38d8ad1b..00000000000 --- a/applications/lfrfid/scene/lfrfid_app_scene_saved_key_menu.cpp +++ /dev/null @@ -1,67 +0,0 @@ -#include "lfrfid_app_scene_saved_key_menu.h" - -typedef enum { - SubmenuEmulate, - SubmenuWrite, - SubmenuEdit, - SubmenuDelete, - SubmenuInfo, -} SubmenuIndex; - -void LfRfidAppSceneSavedKeyMenu::on_enter(LfRfidApp* app, bool need_restore) { - auto submenu = app->view_controller.get(); - - submenu->add_item("Emulate", SubmenuEmulate, submenu_callback, app); - submenu->add_item("Write", SubmenuWrite, submenu_callback, app); - submenu->add_item("Edit", SubmenuEdit, submenu_callback, app); - submenu->add_item("Delete", SubmenuDelete, submenu_callback, app); - submenu->add_item("Info", SubmenuInfo, submenu_callback, app); - - if(need_restore) { - submenu->set_selected_item(submenu_item_selected); - } - - app->view_controller.switch_to(); -} - -bool LfRfidAppSceneSavedKeyMenu::on_event(LfRfidApp* app, LfRfidApp::Event* event) { - bool consumed = false; - - if(event->type == LfRfidApp::EventType::MenuSelected) { - submenu_item_selected = event->payload.signed_int; - switch(event->payload.signed_int) { - case SubmenuEmulate: - app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::Emulate); - break; - case SubmenuWrite: - app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::Write); - break; - case SubmenuEdit: - app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::SaveData); - break; - case SubmenuDelete: - app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::DeleteConfirm); - break; - case SubmenuInfo: - app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::SavedInfo); - break; - } - consumed = true; - } - - return consumed; -} - -void LfRfidAppSceneSavedKeyMenu::on_exit(LfRfidApp* app) { - app->view_controller.get()->clean(); -} - -void LfRfidAppSceneSavedKeyMenu::submenu_callback(void* context, uint32_t index) { - LfRfidApp* app = static_cast(context); - LfRfidApp::Event event; - - event.type = LfRfidApp::EventType::MenuSelected; - event.payload.signed_int = index; - - app->view_controller.send_event(&event); -} diff --git a/applications/lfrfid/scene/lfrfid_app_scene_saved_key_menu.h b/applications/lfrfid/scene/lfrfid_app_scene_saved_key_menu.h deleted file mode 100644 index 69a6e5e58b4..00000000000 --- a/applications/lfrfid/scene/lfrfid_app_scene_saved_key_menu.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once -#include "../lfrfid_app.h" - -class LfRfidAppSceneSavedKeyMenu : public GenericScene { -public: - void on_enter(LfRfidApp* app, bool need_restore) final; - bool on_event(LfRfidApp* app, LfRfidApp::Event* event) final; - void on_exit(LfRfidApp* app) final; - -private: - static void submenu_callback(void* context, uint32_t index); - uint32_t submenu_item_selected = 0; -}; diff --git a/applications/lfrfid/scene/lfrfid_app_scene_select_key.cpp b/applications/lfrfid/scene/lfrfid_app_scene_select_key.cpp deleted file mode 100644 index 6d5df73cb8c..00000000000 --- a/applications/lfrfid/scene/lfrfid_app_scene_select_key.cpp +++ /dev/null @@ -1,16 +0,0 @@ -#include "lfrfid_app_scene_select_key.h" - -void LfRfidAppSceneSelectKey::on_enter(LfRfidApp* app, bool need_restore) { - if(app->load_key_from_file_select(need_restore)) { - app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::SavedKeyMenu); - } else { - app->scene_controller.switch_to_previous_scene(); - } -} - -bool LfRfidAppSceneSelectKey::on_event(LfRfidApp* /* app */, LfRfidApp::Event* /* event */) { - return false; -} - -void LfRfidAppSceneSelectKey::on_exit(LfRfidApp* /* app */) { -} diff --git a/applications/lfrfid/scene/lfrfid_app_scene_select_key.h b/applications/lfrfid/scene/lfrfid_app_scene_select_key.h deleted file mode 100644 index be565a91c56..00000000000 --- a/applications/lfrfid/scene/lfrfid_app_scene_select_key.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once -#include "../lfrfid_app.h" - -class LfRfidAppSceneSelectKey : public GenericScene { -public: - void on_enter(LfRfidApp* app, bool need_restore) final; - bool on_event(LfRfidApp* app, LfRfidApp::Event* event) final; - void on_exit(LfRfidApp* app) final; -}; diff --git a/applications/lfrfid/scene/lfrfid_app_scene_start.cpp b/applications/lfrfid/scene/lfrfid_app_scene_start.cpp deleted file mode 100644 index 5005c9afb57..00000000000 --- a/applications/lfrfid/scene/lfrfid_app_scene_start.cpp +++ /dev/null @@ -1,67 +0,0 @@ -#include "lfrfid_app_scene_start.h" - -typedef enum { - SubmenuRead, - SubmenuSaved, - SubmenuAddManually, - SubmenuExtraActions, -} SubmenuIndex; - -void LfRfidAppSceneStart::on_enter(LfRfidApp* app, bool need_restore) { - auto submenu = app->view_controller.get(); - - submenu->add_item("Read", SubmenuRead, submenu_callback, app); - submenu->add_item("Saved", SubmenuSaved, submenu_callback, app); - submenu->add_item("Add Manually", SubmenuAddManually, submenu_callback, app); - submenu->add_item("Extra Actions", SubmenuExtraActions, submenu_callback, app); - - if(need_restore) { - submenu->set_selected_item(submenu_item_selected); - } - - app->view_controller.switch_to(); - - // clear key - string_reset(app->file_name); - app->protocol_id = PROTOCOL_NO; - app->read_type = LFRFIDWorkerReadTypeAuto; -} - -bool LfRfidAppSceneStart::on_event(LfRfidApp* app, LfRfidApp::Event* event) { - bool consumed = false; - - if(event->type == LfRfidApp::EventType::MenuSelected) { - submenu_item_selected = event->payload.signed_int; - switch(event->payload.signed_int) { - case SubmenuRead: - app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::Read); - break; - case SubmenuSaved: - app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::SelectKey); - break; - case SubmenuAddManually: - app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::SaveType); - break; - case SubmenuExtraActions: - app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::ExtraActions); - break; - } - consumed = true; - } - - return consumed; -} - -void LfRfidAppSceneStart::on_exit(LfRfidApp* app) { - app->view_controller.get()->clean(); -} - -void LfRfidAppSceneStart::submenu_callback(void* context, uint32_t index) { - LfRfidApp* app = static_cast(context); - LfRfidApp::Event event; - - event.type = LfRfidApp::EventType::MenuSelected; - event.payload.signed_int = index; - - app->view_controller.send_event(&event); -} diff --git a/applications/lfrfid/scene/lfrfid_app_scene_start.h b/applications/lfrfid/scene/lfrfid_app_scene_start.h deleted file mode 100644 index 255590d6acc..00000000000 --- a/applications/lfrfid/scene/lfrfid_app_scene_start.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once -#include "../lfrfid_app.h" - -class LfRfidAppSceneStart : public GenericScene { -public: - void on_enter(LfRfidApp* app, bool need_restore) final; - bool on_event(LfRfidApp* app, LfRfidApp::Event* event) final; - void on_exit(LfRfidApp* app) final; - -private: - static void submenu_callback(void* context, uint32_t index); - uint32_t submenu_item_selected = 0; -}; diff --git a/applications/lfrfid/scene/lfrfid_app_scene_write.cpp b/applications/lfrfid/scene/lfrfid_app_scene_write.cpp deleted file mode 100644 index 39e0630e7d2..00000000000 --- a/applications/lfrfid/scene/lfrfid_app_scene_write.cpp +++ /dev/null @@ -1,88 +0,0 @@ -#include "lfrfid_app_scene_write.h" - -static void lfrfid_write_callback(LFRFIDWorkerWriteResult result, void* ctx) { - LfRfidApp* app = static_cast(ctx); - LfRfidApp::Event event; - - switch(result) { - case LFRFIDWorkerWriteOK: - event.type = LfRfidApp::EventType::WriteEventOK; - break; - case LFRFIDWorkerWriteProtocolCannotBeWritten: - event.type = LfRfidApp::EventType::WriteEventProtocolCannotBeWritten; - break; - case LFRFIDWorkerWriteFobCannotBeWritten: - event.type = LfRfidApp::EventType::WriteEventFobCannotBeWritten; - break; - case LFRFIDWorkerWriteTooLongToWrite: - event.type = LfRfidApp::EventType::WriteEventTooLongToWrite; - break; - } - - app->view_controller.send_event(&event); -} - -void LfRfidAppSceneWrite::on_enter(LfRfidApp* app, bool /* need_restore */) { - auto popup = app->view_controller.get(); - - popup->set_header("Writing", 89, 30, AlignCenter, AlignTop); - if(string_size(app->file_name)) { - popup->set_text(string_get_cstr(app->file_name), 89, 43, AlignCenter, AlignTop); - } else { - popup->set_text( - protocol_dict_get_name(app->dict, app->protocol_id), 89, 43, AlignCenter, AlignTop); - } - popup->set_icon(0, 3, &I_RFIDDolphinSend_97x61); - - app->view_controller.switch_to(); - - size_t size = protocol_dict_get_data_size(app->dict, app->protocol_id); - app->old_key_data = (uint8_t*)malloc(size); - protocol_dict_get_data(app->dict, app->protocol_id, app->old_key_data, size); - - lfrfid_worker_start_thread(app->lfworker); - lfrfid_worker_write_start( - app->lfworker, (LFRFIDProtocol)app->protocol_id, lfrfid_write_callback, app); - notification_message(app->notification, &sequence_blink_start_magenta); -} - -bool LfRfidAppSceneWrite::on_event(LfRfidApp* app, LfRfidApp::Event* event) { - bool consumed = true; - auto popup = app->view_controller.get(); - - switch(event->type) { - case LfRfidApp::EventType::WriteEventOK: - notification_message(app->notification, &sequence_success); - app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::WriteSuccess); - break; - case LfRfidApp::EventType::WriteEventProtocolCannotBeWritten: - popup->set_icon(72, 17, &I_DolphinCommon_56x48); - popup->set_header("Error", 64, 3, AlignCenter, AlignTop); - popup->set_text("This protocol\ncannot be written", 3, 17, AlignLeft, AlignTop); - notification_message(app->notification, &sequence_blink_start_red); - break; - case LfRfidApp::EventType::WriteEventFobCannotBeWritten: - case LfRfidApp::EventType::WriteEventTooLongToWrite: - popup->set_icon(72, 17, &I_DolphinCommon_56x48); - popup->set_header("Still trying to write...", 64, 3, AlignCenter, AlignTop); - popup->set_text( - "Make sure this\ncard is writable\nand not\nprotected.", 3, 17, AlignLeft, AlignTop); - notification_message(app->notification, &sequence_blink_start_yellow); - break; - default: - consumed = false; - } - - return consumed; -} - -void LfRfidAppSceneWrite::on_exit(LfRfidApp* app) { - notification_message(app->notification, &sequence_blink_stop); - app->view_controller.get()->clean(); - lfrfid_worker_stop(app->lfworker); - lfrfid_worker_stop_thread(app->lfworker); - - size_t size = protocol_dict_get_data_size(app->dict, app->protocol_id); - protocol_dict_set_data(app->dict, app->protocol_id, app->old_key_data, size); - free(app->old_key_data); -} diff --git a/applications/lfrfid/scene/lfrfid_app_scene_write.h b/applications/lfrfid/scene/lfrfid_app_scene_write.h deleted file mode 100644 index 7564eff9dd9..00000000000 --- a/applications/lfrfid/scene/lfrfid_app_scene_write.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once -#include "../lfrfid_app.h" - -class LfRfidAppSceneWrite : public GenericScene { -public: - void on_enter(LfRfidApp* app, bool need_restore) final; - bool on_event(LfRfidApp* app, LfRfidApp::Event* event) final; - void on_exit(LfRfidApp* app) final; -}; diff --git a/applications/lfrfid/scene/lfrfid_app_scene_write_success.cpp b/applications/lfrfid/scene/lfrfid_app_scene_write_success.cpp deleted file mode 100644 index 3cf00183d71..00000000000 --- a/applications/lfrfid/scene/lfrfid_app_scene_write_success.cpp +++ /dev/null @@ -1,38 +0,0 @@ -#include "lfrfid_app_scene_write_success.h" - -void LfRfidAppSceneWriteSuccess::on_enter(LfRfidApp* app, bool /* need_restore */) { - auto popup = app->view_controller.get(); - popup->set_header("Successfully\nwritten!", 94, 3, AlignCenter, AlignTop); - popup->set_icon(0, 6, &I_RFIDDolphinSuccess_108x57); - popup->set_context(app); - popup->set_callback(LfRfidAppSceneWriteSuccess::timeout_callback); - popup->set_timeout(1500); - popup->enable_timeout(); - - app->view_controller.switch_to(); - notification_message_block(app->notification, &sequence_set_green_255); -} - -bool LfRfidAppSceneWriteSuccess::on_event(LfRfidApp* app, LfRfidApp::Event* event) { - bool consumed = false; - - if(event->type == LfRfidApp::EventType::Back) { - app->scene_controller.search_and_switch_to_previous_scene( - {LfRfidApp::SceneType::ReadKeyMenu, LfRfidApp::SceneType::SelectKey}); - consumed = true; - } - - return consumed; -} - -void LfRfidAppSceneWriteSuccess::on_exit(LfRfidApp* app) { - notification_message_block(app->notification, &sequence_reset_green); - app->view_controller.get()->clean(); -} - -void LfRfidAppSceneWriteSuccess::timeout_callback(void* context) { - LfRfidApp* app = static_cast(context); - LfRfidApp::Event event; - event.type = LfRfidApp::EventType::Back; - app->view_controller.send_event(&event); -} diff --git a/applications/lfrfid/scene/lfrfid_app_scene_write_success.h b/applications/lfrfid/scene/lfrfid_app_scene_write_success.h deleted file mode 100644 index 4ac9f0892f8..00000000000 --- a/applications/lfrfid/scene/lfrfid_app_scene_write_success.h +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once -#include "../lfrfid_app.h" - -class LfRfidAppSceneWriteSuccess : public GenericScene { -public: - void on_enter(LfRfidApp* app, bool need_restore) final; - bool on_event(LfRfidApp* app, LfRfidApp::Event* event) final; - void on_exit(LfRfidApp* app) final; - -private: - static void timeout_callback(void* context); -}; diff --git a/applications/lfrfid/scenes/lfrfid_scene.c b/applications/lfrfid/scenes/lfrfid_scene.c new file mode 100644 index 00000000000..0de5ec36b07 --- /dev/null +++ b/applications/lfrfid/scenes/lfrfid_scene.c @@ -0,0 +1,30 @@ +#include "lfrfid_scene.h" + +// Generate scene on_enter handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, +void (*const lfrfid_on_enter_handlers[])(void*) = { +#include "lfrfid_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 lfrfid_on_event_handlers[])(void* context, SceneManagerEvent event) = { +#include "lfrfid_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 lfrfid_on_exit_handlers[])(void* context) = { +#include "lfrfid_scene_config.h" +}; +#undef ADD_SCENE + +// Initialize scene handlers configuration structure +const SceneManagerHandlers lfrfid_scene_handlers = { + .on_enter_handlers = lfrfid_on_enter_handlers, + .on_event_handlers = lfrfid_on_event_handlers, + .on_exit_handlers = lfrfid_on_exit_handlers, + .scene_num = LfRfidSceneNum, +}; diff --git a/applications/lfrfid/scenes/lfrfid_scene.h b/applications/lfrfid/scenes/lfrfid_scene.h new file mode 100644 index 00000000000..8ce7da09c3f --- /dev/null +++ b/applications/lfrfid/scenes/lfrfid_scene.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +// Generate scene id and total number +#define ADD_SCENE(prefix, name, id) LfRfidScene##id, +typedef enum { +#include "lfrfid_scene_config.h" + LfRfidSceneNum, +} LfRfidScene; +#undef ADD_SCENE + +extern const SceneManagerHandlers lfrfid_scene_handlers; + +// Generate scene on_enter handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); +#include "lfrfid_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 "lfrfid_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 "lfrfid_scene_config.h" +#undef ADD_SCENE diff --git a/applications/lfrfid/scenes/lfrfid_scene_config.h b/applications/lfrfid/scenes/lfrfid_scene_config.h new file mode 100644 index 00000000000..b77ade82f69 --- /dev/null +++ b/applications/lfrfid/scenes/lfrfid_scene_config.h @@ -0,0 +1,24 @@ +ADD_SCENE(lfrfid, start, Start) +ADD_SCENE(lfrfid, read, Read) +ADD_SCENE(lfrfid, read_success, ReadSuccess) +ADD_SCENE(lfrfid, retry_confirm, RetryConfirm) +ADD_SCENE(lfrfid, exit_confirm, ExitConfirm) +ADD_SCENE(lfrfid, delete_confirm, DeleteConfirm) +ADD_SCENE(lfrfid, read_key_menu, ReadKeyMenu) +ADD_SCENE(lfrfid, write, Write) +ADD_SCENE(lfrfid, write_success, WriteSuccess) +ADD_SCENE(lfrfid, emulate, Emulate) +ADD_SCENE(lfrfid, save_name, SaveName) +ADD_SCENE(lfrfid, save_success, SaveSuccess) +ADD_SCENE(lfrfid, select_key, SelectKey) +ADD_SCENE(lfrfid, saved_key_menu, SavedKeyMenu) +ADD_SCENE(lfrfid, save_data, SaveData) +ADD_SCENE(lfrfid, save_type, SaveType) +ADD_SCENE(lfrfid, saved_info, SavedInfo) +ADD_SCENE(lfrfid, delete_success, DeleteSuccess) +ADD_SCENE(lfrfid, extra_actions, ExtraActions) +ADD_SCENE(lfrfid, raw_info, RawInfo) +ADD_SCENE(lfrfid, raw_name, RawName) +ADD_SCENE(lfrfid, raw_read, RawRead) +ADD_SCENE(lfrfid, raw_success, RawSuccess) +ADD_SCENE(lfrfid, rpc, Rpc) diff --git a/applications/lfrfid/scenes/lfrfid_scene_delete_confirm.c b/applications/lfrfid/scenes/lfrfid_scene_delete_confirm.c new file mode 100644 index 00000000000..dc1c3df268f --- /dev/null +++ b/applications/lfrfid/scenes/lfrfid_scene_delete_confirm.c @@ -0,0 +1,68 @@ +#include "../lfrfid_i.h" + +void lfrfid_scene_delete_confirm_on_enter(void* context) { + LfRfid* app = context; + Widget* widget = app->widget; + + string_t tmp_string; + string_init(tmp_string); + + widget_add_button_element(widget, GuiButtonTypeLeft, "Back", lfrfid_widget_callback, app); + widget_add_button_element(widget, GuiButtonTypeRight, "Delete", lfrfid_widget_callback, app); + + string_printf(tmp_string, "Delete %s?", string_get_cstr(app->file_name)); + widget_add_string_element( + widget, 64, 0, AlignCenter, AlignTop, FontPrimary, string_get_cstr(tmp_string)); + + string_reset(tmp_string); + size_t size = protocol_dict_get_data_size(app->dict, app->protocol_id); + uint8_t* data = (uint8_t*)malloc(size); + protocol_dict_get_data(app->dict, app->protocol_id, data, size); + for(uint8_t i = 0; i < MIN(size, (size_t)8); i++) { + if(i != 0) { + string_cat_printf(tmp_string, " "); + } + + string_cat_printf(tmp_string, "%02X", data[i]); + } + free(data); + + widget_add_string_element( + widget, 64, 19, AlignCenter, AlignTop, FontSecondary, string_get_cstr(tmp_string)); + widget_add_string_element( + widget, + 64, + 49, + AlignCenter, + AlignBottom, + FontSecondary, + protocol_dict_get_name(app->dict, app->protocol_id)); + + view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewWidget); + string_clear(tmp_string); +} + +bool lfrfid_scene_delete_confirm_on_event(void* context, SceneManagerEvent event) { + LfRfid* app = context; + SceneManager* scene_manager = app->scene_manager; + bool consumed = false; + + if(event.type == SceneManagerEventTypeBack) { + consumed = true; // Ignore Back button presses + } else if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + if(event.event == GuiButtonTypeLeft) { + scene_manager_previous_scene(scene_manager); + } else if(event.event == GuiButtonTypeRight) { + lfrfid_delete_key(app); + scene_manager_next_scene(scene_manager, LfRfidSceneDeleteSuccess); + } + } + + return consumed; +} + +void lfrfid_scene_delete_confirm_on_exit(void* context) { + LfRfid* app = context; + widget_reset(app->widget); +} diff --git a/applications/lfrfid/scenes/lfrfid_scene_delete_success.c b/applications/lfrfid/scenes/lfrfid_scene_delete_success.c new file mode 100644 index 00000000000..f940b9bd436 --- /dev/null +++ b/applications/lfrfid/scenes/lfrfid_scene_delete_success.c @@ -0,0 +1,35 @@ +#include "../lfrfid_i.h" + +void lfrfid_scene_delete_success_on_enter(void* context) { + LfRfid* app = context; + Popup* popup = app->popup; + + popup_set_icon(popup, 0, 2, &I_DolphinMafia_115x62); + popup_set_header(popup, "Deleted", 83, 19, AlignLeft, AlignBottom); + popup_set_context(popup, app); + popup_set_callback(popup, lfrfid_popup_timeout_callback); + popup_set_timeout(popup, 1500); + popup_enable_timeout(popup); + + view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewPopup); +} + +bool lfrfid_scene_delete_success_on_event(void* context, SceneManagerEvent event) { + LfRfid* app = context; + bool consumed = false; + + if((event.type == SceneManagerEventTypeBack) || + ((event.type == SceneManagerEventTypeCustom) && (event.event == LfRfidEventPopupClosed))) { + scene_manager_search_and_switch_to_previous_scene( + app->scene_manager, LfRfidSceneSelectKey); + consumed = true; + } + + return consumed; +} + +void lfrfid_scene_delete_success_on_exit(void* context) { + LfRfid* app = context; + + popup_reset(app->popup); +} diff --git a/applications/lfrfid/scenes/lfrfid_scene_emulate.c b/applications/lfrfid/scenes/lfrfid_scene_emulate.c new file mode 100644 index 00000000000..70cc2418c58 --- /dev/null +++ b/applications/lfrfid/scenes/lfrfid_scene_emulate.c @@ -0,0 +1,44 @@ +#include "../lfrfid_i.h" +#include + +void lfrfid_scene_emulate_on_enter(void* context) { + LfRfid* app = context; + Popup* popup = app->popup; + + DOLPHIN_DEED(DolphinDeedRfidEmulate); + + popup_set_header(popup, "Emulating", 89, 30, AlignCenter, AlignTop); + if(!string_empty_p(app->file_name)) { + popup_set_text(popup, string_get_cstr(app->file_name), 89, 43, AlignCenter, AlignTop); + } else { + popup_set_text( + popup, + protocol_dict_get_name(app->dict, app->protocol_id), + 89, + 43, + AlignCenter, + AlignTop); + } + popup_set_icon(popup, 0, 3, &I_RFIDDolphinSend_97x61); + + lfrfid_worker_start_thread(app->lfworker); + lfrfid_worker_emulate_start(app->lfworker, (LFRFIDProtocol)app->protocol_id); + notification_message(app->notifications, &sequence_blink_start_magenta); + + view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewPopup); +} + +bool lfrfid_scene_emulate_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + UNUSED(event); + bool consumed = false; + return consumed; +} + +void lfrfid_scene_emulate_on_exit(void* context) { + LfRfid* app = context; + notification_message(app->notifications, &sequence_blink_stop); + popup_reset(app->popup); + lfrfid_worker_stop(app->lfworker); + lfrfid_worker_stop_thread(app->lfworker); +} diff --git a/applications/lfrfid/scenes/lfrfid_scene_exit_confirm.c b/applications/lfrfid/scenes/lfrfid_scene_exit_confirm.c new file mode 100644 index 00000000000..e8ab481c6fa --- /dev/null +++ b/applications/lfrfid/scenes/lfrfid_scene_exit_confirm.c @@ -0,0 +1,39 @@ +#include "../lfrfid_i.h" + +void lfrfid_scene_exit_confirm_on_enter(void* context) { + LfRfid* app = context; + Widget* widget = app->widget; + + widget_add_button_element(widget, GuiButtonTypeLeft, "Exit", lfrfid_widget_callback, app); + widget_add_button_element(widget, GuiButtonTypeRight, "Stay", lfrfid_widget_callback, app); + widget_add_string_element( + widget, 64, 19, AlignCenter, AlignBottom, FontPrimary, "Exit to RFID Menu?"); + widget_add_string_element( + widget, 64, 31, AlignCenter, AlignBottom, FontSecondary, "All unsaved data will be lost!"); + + view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewWidget); +} + +bool lfrfid_scene_exit_confirm_on_event(void* context, SceneManagerEvent event) { + LfRfid* app = context; + SceneManager* scene_manager = app->scene_manager; + bool consumed = false; + + if(event.type == SceneManagerEventTypeBack) { + consumed = true; // Ignore Back button presses + } else if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + if(event.event == GuiButtonTypeLeft) { + scene_manager_search_and_switch_to_previous_scene(scene_manager, LfRfidSceneStart); + } else if(event.event == GuiButtonTypeRight) { + scene_manager_previous_scene(scene_manager); + } + } + + return consumed; +} + +void lfrfid_scene_exit_confirm_on_exit(void* context) { + LfRfid* app = context; + widget_reset(app->widget); +} diff --git a/applications/lfrfid/scenes/lfrfid_scene_extra_actions.c b/applications/lfrfid/scenes/lfrfid_scene_extra_actions.c new file mode 100644 index 00000000000..43e3de99e72 --- /dev/null +++ b/applications/lfrfid/scenes/lfrfid_scene_extra_actions.c @@ -0,0 +1,79 @@ +#include "../lfrfid_i.h" + +typedef enum { + SubmenuIndexASK, + SubmenuIndexPSK, + SubmenuIndexRAW, +} SubmenuIndex; + +static void lfrfid_scene_extra_actions_submenu_callback(void* context, uint32_t index) { + LfRfid* app = context; + + view_dispatcher_send_custom_event(app->view_dispatcher, index); +} + +void lfrfid_scene_extra_actions_on_enter(void* context) { + LfRfid* app = context; + Submenu* submenu = app->submenu; + + submenu_add_item( + submenu, + "Read ASK (Animal, Ordinary Card)", + SubmenuIndexASK, + lfrfid_scene_extra_actions_submenu_callback, + app); + submenu_add_item( + submenu, + "Read PSK (Indala)", + SubmenuIndexPSK, + lfrfid_scene_extra_actions_submenu_callback, + app); + + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + submenu_add_item( + submenu, + "Read RAW RFID data", + SubmenuIndexRAW, + lfrfid_scene_extra_actions_submenu_callback, + app); + } + + submenu_set_selected_item( + submenu, scene_manager_get_scene_state(app->scene_manager, LfRfidSceneExtraActions)); + + // clear key + string_reset(app->file_name); + app->protocol_id = PROTOCOL_NO; + app->read_type = LFRFIDWorkerReadTypeAuto; + + view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewSubmenu); +} + +bool lfrfid_scene_extra_actions_on_event(void* context, SceneManagerEvent event) { + LfRfid* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubmenuIndexASK) { + app->read_type = LFRFIDWorkerReadTypeASKOnly; + scene_manager_next_scene(app->scene_manager, LfRfidSceneRead); + consumed = true; + } else if(event.event == SubmenuIndexPSK) { + app->read_type = LFRFIDWorkerReadTypePSKOnly; + scene_manager_next_scene(app->scene_manager, LfRfidSceneRead); + consumed = true; + } else if(event.event == SubmenuIndexRAW) { + scene_manager_next_scene(app->scene_manager, LfRfidSceneRawName); + consumed = true; + } + scene_manager_set_scene_state(app->scene_manager, LfRfidSceneExtraActions, event.event); + } + + return consumed; +} + +void lfrfid_scene_extra_actions_on_exit(void* context) { + LfRfid* app = context; + + submenu_reset(app->submenu); +} diff --git a/applications/lfrfid/scenes/lfrfid_scene_raw_info.c b/applications/lfrfid/scenes/lfrfid_scene_raw_info.c new file mode 100644 index 00000000000..f60dd6243db --- /dev/null +++ b/applications/lfrfid/scenes/lfrfid_scene_raw_info.c @@ -0,0 +1,64 @@ +#include "../lfrfid_i.h" + +void lfrfid_scene_raw_info_on_enter(void* context) { + LfRfid* app = context; + Widget* widget = app->widget; + + // string_t tmp_string; + // string_init(tmp_string); + + bool sd_exist = storage_sd_status(app->storage) == FSE_OK; + if(!sd_exist) { + widget_add_icon_element(widget, 0, 0, &I_SDQuestion_35x43); + widget_add_string_multiline_element( + widget, + 81, + 4, + AlignCenter, + AlignTop, + FontSecondary, + "No SD card found.\nThis function will not\nwork without\nSD card."); + + widget_add_button_element(widget, GuiButtonTypeLeft, "Back", lfrfid_widget_callback, app); + } else { + widget_add_string_multiline_element( + widget, + 0, + 1, + AlignLeft, + AlignTop, + FontSecondary, + "RAW RFID data reader\n1) Put the Flipper on your card\n2) Press OK\n3) Wait until data is read"); + + widget_add_button_element(widget, GuiButtonTypeCenter, "OK", lfrfid_widget_callback, app); + } + + view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewWidget); + //string_clear(tmp_string); +} + +bool lfrfid_scene_raw_info_on_event(void* context, SceneManagerEvent event) { + LfRfid* app = context; + SceneManager* scene_manager = app->scene_manager; + bool consumed = false; + + if(event.type == SceneManagerEventTypeBack) { + consumed = true; + scene_manager_search_and_switch_to_previous_scene(scene_manager, LfRfidSceneExtraActions); + } else if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + if(event.event == GuiButtonTypeCenter) { + scene_manager_next_scene(scene_manager, LfRfidSceneRawRead); + } else if(event.event == GuiButtonTypeLeft) { + scene_manager_search_and_switch_to_previous_scene( + scene_manager, LfRfidSceneExtraActions); + } + } + + return consumed; +} + +void lfrfid_scene_raw_info_on_exit(void* context) { + LfRfid* app = context; + widget_reset(app->widget); +} diff --git a/applications/lfrfid/scenes/lfrfid_scene_raw_name.c b/applications/lfrfid/scenes/lfrfid_scene_raw_name.c new file mode 100644 index 00000000000..512f9ee4c93 --- /dev/null +++ b/applications/lfrfid/scenes/lfrfid_scene_raw_name.c @@ -0,0 +1,58 @@ +#include "../lfrfid_i.h" + +void lfrfid_scene_raw_name_on_enter(void* context) { + LfRfid* app = context; + TextInput* text_input = app->text_input; + + const char* key_name = string_get_cstr(app->raw_file_name); + + bool key_name_is_empty = string_empty_p(app->file_name); + if(key_name_is_empty) { + lfrfid_text_store_set(app, "RfidRecord"); + } else { + lfrfid_text_store_set(app, "%s", key_name); + } + + text_input_set_header_text(text_input, "Name the raw file"); + + text_input_set_result_callback( + text_input, + lfrfid_text_input_callback, + app, + app->text_store, + LFRFID_KEY_NAME_SIZE, + key_name_is_empty); + + ValidatorIsFile* validator_is_file = + validator_is_file_alloc_init(LFRFID_SD_FOLDER, LFRFID_APP_RAW_ASK_EXTENSION, NULL); + text_input_set_validator(text_input, validator_is_file_callback, validator_is_file); + + view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewTextInput); +} + +bool lfrfid_scene_raw_name_on_event(void* context, SceneManagerEvent event) { + LfRfid* app = context; + SceneManager* scene_manager = app->scene_manager; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == LfRfidEventNext) { + consumed = true; + string_set_str(app->raw_file_name, app->text_store); + scene_manager_next_scene(scene_manager, LfRfidSceneRawInfo); + } + } + + return consumed; +} + +void lfrfid_scene_raw_name_on_exit(void* context) { + LfRfid* app = context; + TextInput* text_input = app->text_input; + + void* validator_context = text_input_get_validator_callback_context(text_input); + text_input_set_validator(text_input, NULL, NULL); + validator_is_file_free((ValidatorIsFile*)validator_context); + + text_input_reset(text_input); +} diff --git a/applications/lfrfid/scenes/lfrfid_scene_raw_read.c b/applications/lfrfid/scenes/lfrfid_scene_raw_read.c new file mode 100644 index 00000000000..d0c03ffa947 --- /dev/null +++ b/applications/lfrfid/scenes/lfrfid_scene_raw_read.c @@ -0,0 +1,126 @@ +#include "../lfrfid_i.h" + +#define RAW_READ_TIME 5000 + +typedef struct { + string_t string_file_name; + FuriTimer* timer; + bool is_psk; + bool error; +} LfRfidReadRawState; + +static void lfrfid_read_callback(LFRFIDWorkerReadRawResult result, void* context) { + LfRfid* app = context; + + if(result == LFRFIDWorkerReadRawFileError) { + view_dispatcher_send_custom_event(app->view_dispatcher, LfRfidEventReadError); + } else if(result == LFRFIDWorkerReadRawOverrun) { + view_dispatcher_send_custom_event(app->view_dispatcher, LfRfidEventReadOverrun); + } +} + +static void timer_callback(void* context) { + LfRfid* app = context; + + view_dispatcher_send_custom_event(app->view_dispatcher, LfRfidEventReadDone); +} + +void lfrfid_scene_raw_read_on_enter(void* context) { + LfRfid* app = context; + Popup* popup = app->popup; + + LfRfidReadRawState* state = malloc(sizeof(LfRfidReadRawState)); + scene_manager_set_scene_state(app->scene_manager, LfRfidSceneRawRead, (uint32_t)state); + string_init(state->string_file_name); + + popup_set_icon(popup, 0, 3, &I_RFIDDolphinReceive_97x61); + view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewPopup); + lfrfid_worker_start_thread(app->lfworker); + lfrfid_make_app_folder(app); + + state->timer = furi_timer_alloc(timer_callback, FuriTimerTypeOnce, app); + furi_timer_start(state->timer, RAW_READ_TIME); + string_printf( + state->string_file_name, + "%s/%s%s", + LFRFID_SD_FOLDER, + string_get_cstr(app->raw_file_name), + LFRFID_APP_RAW_ASK_EXTENSION); + popup_set_header(popup, "Reading\nRAW RFID\nASK", 89, 30, AlignCenter, AlignTop); + lfrfid_worker_read_raw_start( + app->lfworker, + string_get_cstr(state->string_file_name), + LFRFIDWorkerReadTypeASKOnly, + lfrfid_read_callback, + app); + + notification_message(app->notifications, &sequence_blink_start_cyan); + + state->is_psk = false; + state->error = false; +} + +bool lfrfid_scene_raw_read_on_event(void* context, SceneManagerEvent event) { + LfRfid* app = context; + Popup* popup = app->popup; + LfRfidReadRawState* state = + (LfRfidReadRawState*)scene_manager_get_scene_state(app->scene_manager, LfRfidSceneRawRead); + bool consumed = false; + + furi_assert(state); + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == LfRfidEventReadError) { + consumed = true; + state->error = true; + popup_set_header( + popup, "Reading\nRAW RFID\nFile error", 89, 30, AlignCenter, AlignTop); + notification_message(app->notifications, &sequence_blink_start_red); + furi_timer_stop(state->timer); + } else if(event.event == LfRfidEventReadDone) { + consumed = true; + if(!state->error) { + if(state->is_psk) { + notification_message(app->notifications, &sequence_success); + scene_manager_next_scene(app->scene_manager, LfRfidSceneRawSuccess); + } else { + popup_set_header( + popup, "Reading\nRAW RFID\nPSK", 89, 30, AlignCenter, AlignTop); + notification_message(app->notifications, &sequence_blink_start_yellow); + lfrfid_worker_stop(app->lfworker); + string_printf( + state->string_file_name, + "%s/%s%s", + LFRFID_SD_FOLDER, + string_get_cstr(app->raw_file_name), + LFRFID_APP_RAW_PSK_EXTENSION); + lfrfid_worker_read_raw_start( + app->lfworker, + string_get_cstr(state->string_file_name), + LFRFIDWorkerReadTypePSKOnly, + lfrfid_read_callback, + app); + furi_timer_start(state->timer, RAW_READ_TIME); + state->is_psk = true; + } + } + } + } + + return consumed; +} + +void lfrfid_scene_raw_read_on_exit(void* context) { + LfRfid* app = context; + LfRfidReadRawState* state = + (LfRfidReadRawState*)scene_manager_get_scene_state(app->scene_manager, LfRfidSceneRawRead); + + notification_message(app->notifications, &sequence_blink_stop); + popup_reset(app->popup); + lfrfid_worker_stop(app->lfworker); + lfrfid_worker_stop_thread(app->lfworker); + furi_timer_free(state->timer); + + string_clear(state->string_file_name); + free(state); +} diff --git a/applications/lfrfid/scenes/lfrfid_scene_raw_success.c b/applications/lfrfid/scenes/lfrfid_scene_raw_success.c new file mode 100644 index 00000000000..09a005298f6 --- /dev/null +++ b/applications/lfrfid/scenes/lfrfid_scene_raw_success.c @@ -0,0 +1,39 @@ +#include "../lfrfid_i.h" + +void lfrfid_scene_raw_success_on_enter(void* context) { + LfRfid* app = context; + Widget* widget = app->widget; + + widget_add_button_element(widget, GuiButtonTypeCenter, "OK", lfrfid_widget_callback, app); + + widget_add_string_multiline_element( + widget, + 0, + 1, + AlignLeft, + AlignTop, + FontSecondary, + "RAW RFID read success!\nNow you can analyze files\nOr send them to developers"); + + view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewWidget); +} + +bool lfrfid_scene_raw_success_on_event(void* context, SceneManagerEvent event) { + LfRfid* app = context; + SceneManager* scene_manager = app->scene_manager; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + if(event.event == GuiButtonTypeCenter) { + scene_manager_search_and_switch_to_previous_scene( + scene_manager, LfRfidSceneExtraActions); + } + } + return consumed; +} + +void lfrfid_scene_raw_success_on_exit(void* context) { + LfRfid* app = context; + widget_reset(app->widget); +} diff --git a/applications/lfrfid/scenes/lfrfid_scene_read.c b/applications/lfrfid/scenes/lfrfid_scene_read.c new file mode 100644 index 00000000000..661680381ea --- /dev/null +++ b/applications/lfrfid/scenes/lfrfid_scene_read.c @@ -0,0 +1,109 @@ +#include "../lfrfid_i.h" +#include + +static const NotificationSequence sequence_blink_set_yellow = { + &message_blink_set_color_yellow, + NULL, +}; + +static const NotificationSequence sequence_blink_set_green = { + &message_blink_set_color_green, + NULL, +}; + +static const NotificationSequence sequence_blink_set_cyan = { + &message_blink_set_color_cyan, + NULL, +}; + +static void + lfrfid_read_callback(LFRFIDWorkerReadResult result, ProtocolId protocol, void* context) { + LfRfid* app = context; + uint32_t event = 0; + + if(result == LFRFIDWorkerReadSenseStart) { + event = LfRfidEventReadSenseStart; + } else if(result == LFRFIDWorkerReadSenseEnd) { + event = LfRfidEventReadSenseEnd; + } else if(result == LFRFIDWorkerReadSenseCardStart) { + event = LfRfidEventReadSenseCardStart; + } else if(result == LFRFIDWorkerReadSenseCardEnd) { + event = LfRfidEventReadSenseCardEnd; + } else if(result == LFRFIDWorkerReadDone) { + event = LfRfidEventReadDone; + app->protocol_id_next = protocol; + } else if(result == LFRFIDWorkerReadStartASK) { + event = LfRfidEventReadStartASK; + } else if(result == LFRFIDWorkerReadStartPSK) { + event = LfRfidEventReadStartPSK; + } else { + return; + } + + view_dispatcher_send_custom_event(app->view_dispatcher, event); +} + +void lfrfid_scene_read_on_enter(void* context) { + LfRfid* app = context; + + DOLPHIN_DEED(DolphinDeedRfidRead); + if(app->read_type == LFRFIDWorkerReadTypePSKOnly) { + lfrfid_view_read_set_read_mode(app->read_view, LfRfidReadPskOnly); + } else if(app->read_type == LFRFIDWorkerReadTypeASKOnly) { + lfrfid_view_read_set_read_mode(app->read_view, LfRfidReadAskOnly); + } + + lfrfid_worker_start_thread(app->lfworker); + lfrfid_worker_read_start(app->lfworker, app->read_type, lfrfid_read_callback, app); + + notification_message(app->notifications, &sequence_blink_start_cyan); + + view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewRead); +} + +bool lfrfid_scene_read_on_event(void* context, SceneManagerEvent event) { + LfRfid* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == LfRfidEventReadSenseStart) { + notification_message(app->notifications, &sequence_blink_set_yellow); + consumed = true; + } else if(event.event == LfRfidEventReadSenseCardStart) { + notification_message(app->notifications, &sequence_blink_set_green); + consumed = true; + } else if( + (event.event == LfRfidEventReadSenseEnd) || + (event.event == LfRfidEventReadSenseCardEnd)) { + notification_message(app->notifications, &sequence_blink_set_cyan); + consumed = true; + } else if(event.event == LfRfidEventReadDone) { + app->protocol_id = app->protocol_id_next; + DOLPHIN_DEED(DolphinDeedRfidReadSuccess); + notification_message(app->notifications, &sequence_success); + string_reset(app->file_name); + scene_manager_next_scene(app->scene_manager, LfRfidSceneReadSuccess); + consumed = true; + } else if(event.event == LfRfidEventReadStartPSK) { + if(app->read_type == LFRFIDWorkerReadTypeAuto) { + lfrfid_view_read_set_read_mode(app->read_view, LfRfidReadPsk); + } + consumed = true; + } else if(event.event == LfRfidEventReadStartASK) { + if(app->read_type == LFRFIDWorkerReadTypeAuto) { + lfrfid_view_read_set_read_mode(app->read_view, LfRfidReadAsk); + } + consumed = true; + } + } + + return consumed; +} + +void lfrfid_scene_read_on_exit(void* context) { + LfRfid* app = context; + notification_message(app->notifications, &sequence_blink_stop); + popup_reset(app->popup); + lfrfid_worker_stop(app->lfworker); + lfrfid_worker_stop_thread(app->lfworker); +} diff --git a/applications/lfrfid/scenes/lfrfid_scene_read_key_menu.c b/applications/lfrfid/scenes/lfrfid_scene_read_key_menu.c new file mode 100644 index 00000000000..221cc00844d --- /dev/null +++ b/applications/lfrfid/scenes/lfrfid_scene_read_key_menu.c @@ -0,0 +1,58 @@ +#include "../lfrfid_i.h" + +typedef enum { + SubmenuIndexSave, + SubmenuIndexEmulate, + SubmenuIndexWrite, +} SubmenuIndex; + +void lfrfid_scene_read_key_menu_submenu_callback(void* context, uint32_t index) { + LfRfid* app = context; + + view_dispatcher_send_custom_event(app->view_dispatcher, index); +} + +void lfrfid_scene_read_key_menu_on_enter(void* context) { + LfRfid* app = context; + Submenu* submenu = app->submenu; + + submenu_add_item( + submenu, "Save", SubmenuIndexSave, lfrfid_scene_read_key_menu_submenu_callback, app); + submenu_add_item( + submenu, "Emulate", SubmenuIndexEmulate, lfrfid_scene_read_key_menu_submenu_callback, app); + submenu_add_item( + submenu, "Write", SubmenuIndexWrite, lfrfid_scene_read_key_menu_submenu_callback, app); + + submenu_set_selected_item( + submenu, scene_manager_get_scene_state(app->scene_manager, LfRfidSceneReadKeyMenu)); + + view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewSubmenu); +} + +bool lfrfid_scene_read_key_menu_on_event(void* context, SceneManagerEvent event) { + LfRfid* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubmenuIndexWrite) { + scene_manager_next_scene(app->scene_manager, LfRfidSceneWrite); + consumed = true; + } else if(event.event == SubmenuIndexSave) { + string_reset(app->file_name); + scene_manager_next_scene(app->scene_manager, LfRfidSceneSaveName); + consumed = true; + } else if(event.event == SubmenuIndexEmulate) { + scene_manager_next_scene(app->scene_manager, LfRfidSceneEmulate); + consumed = true; + } + scene_manager_set_scene_state(app->scene_manager, LfRfidSceneReadKeyMenu, event.event); + } + + return consumed; +} + +void lfrfid_scene_read_key_menu_on_exit(void* context) { + LfRfid* app = context; + + submenu_reset(app->submenu); +} diff --git a/applications/lfrfid/scenes/lfrfid_scene_read_success.c b/applications/lfrfid/scenes/lfrfid_scene_read_success.c new file mode 100644 index 00000000000..6761dcfe623 --- /dev/null +++ b/applications/lfrfid/scenes/lfrfid_scene_read_success.c @@ -0,0 +1,79 @@ +#include "../lfrfid_i.h" + +void lfrfid_scene_read_success_on_enter(void* context) { + LfRfid* app = context; + Widget* widget = app->widget; + + string_t tmp_string; + string_init(tmp_string); + + widget_add_button_element(widget, GuiButtonTypeLeft, "Retry", lfrfid_widget_callback, app); + widget_add_button_element(widget, GuiButtonTypeRight, "More", lfrfid_widget_callback, app); + + string_printf( + tmp_string, + "%s[%s]", + protocol_dict_get_name(app->dict, app->protocol_id), + protocol_dict_get_manufacturer(app->dict, app->protocol_id)); + + widget_add_string_element( + widget, 0, 2, AlignLeft, AlignTop, FontPrimary, string_get_cstr(tmp_string)); + + string_reset(tmp_string); + size_t size = protocol_dict_get_data_size(app->dict, app->protocol_id); + uint8_t* data = (uint8_t*)malloc(size); + protocol_dict_get_data(app->dict, app->protocol_id, data, size); + for(uint8_t i = 0; i < size; i++) { + if(i != 0) { + string_cat_printf(tmp_string, " "); + } + + if(i >= 9) { + string_cat_printf(tmp_string, "..."); + break; + } else { + string_cat_printf(tmp_string, "%02X", data[i]); + } + } + free(data); + + string_t render_data; + string_init(render_data); + protocol_dict_render_brief_data(app->dict, render_data, app->protocol_id); + string_cat_printf(tmp_string, "\r\n%s", string_get_cstr(render_data)); + string_clear(render_data); + + widget_add_string_element( + widget, 0, 16, AlignLeft, AlignTop, FontSecondary, string_get_cstr(tmp_string)); + + notification_message_block(app->notifications, &sequence_set_green_255); + + view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewWidget); + string_clear(tmp_string); +} + +bool lfrfid_scene_read_success_on_event(void* context, SceneManagerEvent event) { + LfRfid* app = context; + SceneManager* scene_manager = app->scene_manager; + bool consumed = false; + + if(event.type == SceneManagerEventTypeBack) { + scene_manager_next_scene(scene_manager, LfRfidSceneExitConfirm); + consumed = true; + } else if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + if(event.event == GuiButtonTypeLeft) { + scene_manager_next_scene(scene_manager, LfRfidSceneRetryConfirm); + } else if(event.event == GuiButtonTypeRight) { + scene_manager_next_scene(scene_manager, LfRfidSceneReadKeyMenu); + } + } + + return consumed; +} + +void lfrfid_scene_read_success_on_exit(void* context) { + LfRfid* app = context; + notification_message_block(app->notifications, &sequence_reset_green); + widget_reset(app->widget); +} diff --git a/applications/lfrfid/scenes/lfrfid_scene_retry_confirm.c b/applications/lfrfid/scenes/lfrfid_scene_retry_confirm.c new file mode 100644 index 00000000000..f639f0ae1aa --- /dev/null +++ b/applications/lfrfid/scenes/lfrfid_scene_retry_confirm.c @@ -0,0 +1,39 @@ +#include "../lfrfid_i.h" + +void lfrfid_scene_retry_confirm_on_enter(void* context) { + LfRfid* app = context; + Widget* widget = app->widget; + + widget_add_button_element(widget, GuiButtonTypeLeft, "Exit", lfrfid_widget_callback, app); + widget_add_button_element(widget, GuiButtonTypeRight, "Stay", lfrfid_widget_callback, app); + widget_add_string_element( + widget, 64, 19, AlignCenter, AlignBottom, FontPrimary, "Return to reading?"); + widget_add_string_element( + widget, 64, 29, AlignCenter, AlignBottom, FontSecondary, "All unsaved data will be lost!"); + + view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewWidget); +} + +bool lfrfid_scene_retry_confirm_on_event(void* context, SceneManagerEvent event) { + LfRfid* app = context; + SceneManager* scene_manager = app->scene_manager; + bool consumed = false; + + if(event.type == SceneManagerEventTypeBack) { + consumed = true; // Ignore Back button presses + } else if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + if(event.event == GuiButtonTypeLeft) { + scene_manager_search_and_switch_to_previous_scene(scene_manager, LfRfidSceneRead); + } else if(event.event == GuiButtonTypeRight) { + scene_manager_previous_scene(scene_manager); + } + } + + return consumed; +} + +void lfrfid_scene_retry_confirm_on_exit(void* context) { + LfRfid* app = context; + widget_reset(app->widget); +} diff --git a/applications/lfrfid/scenes/lfrfid_scene_rpc.c b/applications/lfrfid/scenes/lfrfid_scene_rpc.c new file mode 100644 index 00000000000..a69d6453a3c --- /dev/null +++ b/applications/lfrfid/scenes/lfrfid_scene_rpc.c @@ -0,0 +1,67 @@ +#include "../lfrfid_i.h" + +void lfrfid_scene_rpc_on_enter(void* context) { + LfRfid* app = context; + Popup* popup = app->popup; + + popup_set_header(popup, "LF RFID", 89, 42, AlignCenter, AlignBottom); + popup_set_text(popup, "RPC mode", 89, 44, AlignCenter, AlignTop); + popup_set_icon(popup, 0, 12, &I_RFIDDolphinSend_97x61); + + view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewPopup); + + notification_message(app->notifications, &sequence_display_backlight_on); + + app->rpc_state = LfRfidRpcStateIdle; +} + +bool lfrfid_scene_rpc_on_event(void* context, SceneManagerEvent event) { + LfRfid* app = context; + Popup* popup = app->popup; + UNUSED(event); + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + if(event.event == LfRfidEventExit) { + rpc_system_app_confirm(app->rpc_ctx, RpcAppEventAppExit, true); + scene_manager_stop(app->scene_manager); + view_dispatcher_stop(app->view_dispatcher); + } else if(event.event == LfRfidEventRpcSessionClose) { + scene_manager_stop(app->scene_manager); + view_dispatcher_stop(app->view_dispatcher); + } else if(event.event == LfRfidEventRpcLoadFile) { + const char* arg = rpc_system_app_get_data(app->rpc_ctx); + bool result = false; + if(arg && (app->rpc_state == LfRfidRpcStateIdle)) { + string_set_str(app->file_path, arg); + if(lfrfid_load_key_data(app, app->file_path, false)) { + lfrfid_worker_start_thread(app->lfworker); + lfrfid_worker_emulate_start(app->lfworker, (LFRFIDProtocol)app->protocol_id); + app->rpc_state = LfRfidRpcStateEmulating; + + lfrfid_text_store_set(app, "emulating\n%s", string_get_cstr(app->file_name)); + popup_set_text(popup, app->text_store, 89, 44, AlignCenter, AlignTop); + + notification_message(app->notifications, &sequence_blink_start_magenta); + result = true; + } + } + rpc_system_app_confirm(app->rpc_ctx, RpcAppEventLoadFile, result); + } + } + return consumed; +} + +void lfrfid_scene_rpc_on_exit(void* context) { + LfRfid* app = context; + Popup* popup = app->popup; + + if(app->rpc_state == LfRfidRpcStateEmulating) { + lfrfid_worker_stop(app->lfworker); + lfrfid_worker_stop_thread(app->lfworker); + notification_message(app->notifications, &sequence_blink_stop); + } + + popup_reset(popup); +} diff --git a/applications/lfrfid/scenes/lfrfid_scene_save_data.c b/applications/lfrfid/scenes/lfrfid_scene_save_data.c new file mode 100644 index 00000000000..2ca1bb433cb --- /dev/null +++ b/applications/lfrfid/scenes/lfrfid_scene_save_data.c @@ -0,0 +1,51 @@ +#include "../lfrfid_i.h" +#include + +void lfrfid_scene_save_data_on_enter(void* context) { + LfRfid* app = context; + ByteInput* byte_input = app->byte_input; + + size_t size = protocol_dict_get_data_size(app->dict, app->protocol_id); + + bool need_restore = scene_manager_get_scene_state(app->scene_manager, LfRfidSceneSaveData); + + if(need_restore) { + protocol_dict_set_data(app->dict, app->protocol_id, app->old_key_data, size); + } else { + protocol_dict_get_data(app->dict, app->protocol_id, app->old_key_data, size); + } + + protocol_dict_get_data(app->dict, app->protocol_id, app->new_key_data, size); + + byte_input_set_header_text(byte_input, "Enter the data in hex"); + + byte_input_set_result_callback( + byte_input, lfrfid_text_input_callback, NULL, app, app->new_key_data, size); + + view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewByteInput); +} + +bool lfrfid_scene_save_data_on_event(void* context, SceneManagerEvent event) { + LfRfid* app = context; + SceneManager* scene_manager = app->scene_manager; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == LfRfidEventNext) { + consumed = true; + size_t size = protocol_dict_get_data_size(app->dict, app->protocol_id); + protocol_dict_set_data(app->dict, app->protocol_id, app->new_key_data, size); + DOLPHIN_DEED(DolphinDeedRfidAdd); + scene_manager_next_scene(scene_manager, LfRfidSceneSaveName); + scene_manager_set_scene_state(scene_manager, LfRfidSceneSaveData, 1); + } + } else if(event.type == SceneManagerEventTypeBack) { + scene_manager_set_scene_state(scene_manager, LfRfidSceneSaveData, 0); + } + + return consumed; +} + +void lfrfid_scene_save_data_on_exit(void* context) { + UNUSED(context); +} diff --git a/applications/lfrfid/scenes/lfrfid_scene_save_name.c b/applications/lfrfid/scenes/lfrfid_scene_save_name.c new file mode 100644 index 00000000000..febf30a418c --- /dev/null +++ b/applications/lfrfid/scenes/lfrfid_scene_save_name.c @@ -0,0 +1,76 @@ +#include "m-string.h" +#include +#include "../lfrfid_i.h" + +void lfrfid_scene_save_name_on_enter(void* context) { + LfRfid* app = context; + TextInput* text_input = app->text_input; + string_t folder_path; + string_init(folder_path); + + bool key_name_is_empty = string_empty_p(app->file_name); + if(key_name_is_empty) { + string_set_str(app->file_path, LFRFID_APP_FOLDER); + set_random_name(app->text_store, LFRFID_TEXT_STORE_SIZE); + string_set_str(folder_path, LFRFID_APP_FOLDER); + } else { + lfrfid_text_store_set(app, "%s", string_get_cstr(app->file_name)); + path_extract_dirname(string_get_cstr(app->file_path), folder_path); + } + + text_input_set_header_text(text_input, "Name the card"); + text_input_set_result_callback( + text_input, + lfrfid_text_input_callback, + app, + app->text_store, + LFRFID_KEY_NAME_SIZE, + key_name_is_empty); + + FURI_LOG_I("", "%s %s", string_get_cstr(folder_path), app->text_store); + + ValidatorIsFile* validator_is_file = validator_is_file_alloc_init( + string_get_cstr(folder_path), LFRFID_APP_EXTENSION, string_get_cstr(app->file_name)); + text_input_set_validator(text_input, validator_is_file_callback, validator_is_file); + + string_clear(folder_path); + + view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewTextInput); +} + +bool lfrfid_scene_save_name_on_event(void* context, SceneManagerEvent event) { + LfRfid* app = context; + SceneManager* scene_manager = app->scene_manager; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == LfRfidEventNext) { + consumed = true; + if(!string_empty_p(app->file_name)) { + lfrfid_delete_key(app); + } + + string_set_str(app->file_name, app->text_store); + + if(lfrfid_save_key(app)) { + scene_manager_next_scene(scene_manager, LfRfidSceneSaveSuccess); + } else { + scene_manager_search_and_switch_to_previous_scene( + scene_manager, LfRfidSceneReadKeyMenu); + } + } + } + + return consumed; +} + +void lfrfid_scene_save_name_on_exit(void* context) { + LfRfid* app = context; + TextInput* text_input = app->text_input; + + void* validator_context = text_input_get_validator_callback_context(text_input); + text_input_set_validator(text_input, NULL, NULL); + validator_is_file_free((ValidatorIsFile*)validator_context); + + text_input_reset(text_input); +} diff --git a/applications/lfrfid/scenes/lfrfid_scene_save_success.c b/applications/lfrfid/scenes/lfrfid_scene_save_success.c new file mode 100644 index 00000000000..830ef3368c2 --- /dev/null +++ b/applications/lfrfid/scenes/lfrfid_scene_save_success.c @@ -0,0 +1,43 @@ +#include "../lfrfid_i.h" +#include + +void lfrfid_scene_save_success_on_enter(void* context) { + LfRfid* app = context; + Popup* popup = app->popup; + + DOLPHIN_DEED(DolphinDeedRfidSave); + popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); + popup_set_header(popup, "Saved!", 5, 7, AlignLeft, AlignTop); + popup_set_context(popup, app); + popup_set_callback(popup, lfrfid_popup_timeout_callback); + popup_set_timeout(popup, 1500); + popup_enable_timeout(popup); + + view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewPopup); +} + +bool lfrfid_scene_save_success_on_event(void* context, SceneManagerEvent event) { + LfRfid* app = context; + bool consumed = false; + + const uint32_t prev_scenes[] = {LfRfidSceneReadKeyMenu, LfRfidSceneSelectKey}; + + if((event.type == SceneManagerEventTypeBack) || + ((event.type == SceneManagerEventTypeCustom) && (event.event == LfRfidEventPopupClosed))) { + bool result = scene_manager_search_and_switch_to_previous_scene_one_of( + app->scene_manager, prev_scenes, COUNT_OF(prev_scenes)); + if(!result) { + scene_manager_search_and_switch_to_another_scene( + app->scene_manager, LfRfidSceneSelectKey); + } + consumed = true; + } + + return consumed; +} + +void lfrfid_scene_save_success_on_exit(void* context) { + LfRfid* app = context; + + popup_reset(app->popup); +} diff --git a/applications/lfrfid/scenes/lfrfid_scene_save_type.c b/applications/lfrfid/scenes/lfrfid_scene_save_type.c new file mode 100644 index 00000000000..4c111600725 --- /dev/null +++ b/applications/lfrfid/scenes/lfrfid_scene_save_type.c @@ -0,0 +1,86 @@ +#include "../lfrfid_i.h" + +typedef struct { + string_t menu_item_name[LFRFIDProtocolMax]; + uint32_t line_sel; +} SaveTypeCtx; + +static void lfrfid_scene_save_type_submenu_callback(void* context, uint32_t index) { + LfRfid* app = context; + + view_dispatcher_send_custom_event(app->view_dispatcher, index); +} + +void lfrfid_scene_save_type_on_enter(void* context) { + LfRfid* app = context; + Submenu* submenu = app->submenu; + + SaveTypeCtx* state = malloc(sizeof(SaveTypeCtx)); + for(uint8_t i = 0; i < LFRFIDProtocolMax; i++) { + if(strcmp( + protocol_dict_get_manufacturer(app->dict, i), + protocol_dict_get_name(app->dict, i)) != 0) { + string_init_printf( + state->menu_item_name[i], + "%s %s", + protocol_dict_get_manufacturer(app->dict, i), + protocol_dict_get_name(app->dict, i)); + } else { + string_init_printf( + state->menu_item_name[i], "%s", protocol_dict_get_name(app->dict, i)); + } + submenu_add_item( + submenu, + string_get_cstr(state->menu_item_name[i]), + i, + lfrfid_scene_save_type_submenu_callback, + app); + } + + submenu_set_selected_item( + submenu, scene_manager_get_scene_state(app->scene_manager, LfRfidSceneSaveType)); + + scene_manager_set_scene_state(app->scene_manager, LfRfidSceneSaveType, (uint32_t)state); + + // clear key name + string_reset(app->file_name); + + view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewSubmenu); +} + +bool lfrfid_scene_save_type_on_event(void* context, SceneManagerEvent event) { + LfRfid* app = context; + bool consumed = false; + + SaveTypeCtx* state = + (SaveTypeCtx*)scene_manager_get_scene_state(app->scene_manager, LfRfidSceneSaveType); + furi_check(state); + + if(event.type == SceneManagerEventTypeCustom) { + app->protocol_id = event.event; + state->line_sel = event.event; + scene_manager_next_scene(app->scene_manager, LfRfidSceneSaveData); + consumed = true; + } + + return consumed; +} + +void lfrfid_scene_save_type_on_exit(void* context) { + LfRfid* app = context; + SaveTypeCtx* state = + (SaveTypeCtx*)scene_manager_get_scene_state(app->scene_manager, LfRfidSceneSaveType); + furi_check(state); + + submenu_reset(app->submenu); + + for(uint8_t i = 0; i < LFRFIDProtocolMax; i++) { + string_clear(state->menu_item_name[i]); + } + + uint32_t line_sel = state->line_sel; + + free(state); + + scene_manager_set_scene_state(app->scene_manager, LfRfidSceneSaveType, line_sel); +} diff --git a/applications/lfrfid/scenes/lfrfid_scene_saved_info.c b/applications/lfrfid/scenes/lfrfid_scene_saved_info.c new file mode 100644 index 00000000000..1496c6b4b2b --- /dev/null +++ b/applications/lfrfid/scenes/lfrfid_scene_saved_info.c @@ -0,0 +1,51 @@ +#include "../lfrfid_i.h" + +void lfrfid_scene_saved_info_on_enter(void* context) { + LfRfid* app = context; + Widget* widget = app->widget; + + string_t tmp_string; + string_init(tmp_string); + + string_printf( + tmp_string, + "%s [%s]\r\n", + string_get_cstr(app->file_name), + protocol_dict_get_name(app->dict, app->protocol_id)); + + size_t size = protocol_dict_get_data_size(app->dict, app->protocol_id); + uint8_t* data = (uint8_t*)malloc(size); + protocol_dict_get_data(app->dict, app->protocol_id, data, size); + for(uint8_t i = 0; i < size; i++) { + if(i != 0) { + string_cat_printf(tmp_string, " "); + } + + string_cat_printf(tmp_string, "%02X", data[i]); + } + free(data); + + string_t render_data; + string_init(render_data); + protocol_dict_render_data(app->dict, render_data, app->protocol_id); + string_cat_printf(tmp_string, "\r\n%s", string_get_cstr(render_data)); + string_clear(render_data); + + widget_add_string_multiline_element( + widget, 0, 1, AlignLeft, AlignTop, FontSecondary, string_get_cstr(tmp_string)); + + view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewWidget); + string_clear(tmp_string); +} + +bool lfrfid_scene_saved_info_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + UNUSED(event); + bool consumed = false; + return consumed; +} + +void lfrfid_scene_saved_info_on_exit(void* context) { + LfRfid* app = context; + widget_reset(app->widget); +} diff --git a/applications/lfrfid/scenes/lfrfid_scene_saved_key_menu.c b/applications/lfrfid/scenes/lfrfid_scene_saved_key_menu.c new file mode 100644 index 00000000000..040b31f10b8 --- /dev/null +++ b/applications/lfrfid/scenes/lfrfid_scene_saved_key_menu.c @@ -0,0 +1,69 @@ +#include "../lfrfid_i.h" + +typedef enum { + SubmenuIndexEmulate, + SubmenuIndexWrite, + SubmenuIndexEdit, + SubmenuIndexDelete, + SubmenuIndexInfo, +} SubmenuIndex; + +static void lfrfid_scene_saved_key_menu_submenu_callback(void* context, uint32_t index) { + LfRfid* app = context; + + view_dispatcher_send_custom_event(app->view_dispatcher, index); +} + +void lfrfid_scene_saved_key_menu_on_enter(void* context) { + LfRfid* app = context; + Submenu* submenu = app->submenu; + + submenu_add_item( + submenu, "Emulate", SubmenuIndexEmulate, lfrfid_scene_saved_key_menu_submenu_callback, app); + submenu_add_item( + submenu, "Write", SubmenuIndexWrite, lfrfid_scene_saved_key_menu_submenu_callback, app); + submenu_add_item( + submenu, "Edit", SubmenuIndexEdit, lfrfid_scene_saved_key_menu_submenu_callback, app); + submenu_add_item( + submenu, "Delete", SubmenuIndexDelete, lfrfid_scene_saved_key_menu_submenu_callback, app); + submenu_add_item( + submenu, "Info", SubmenuIndexInfo, lfrfid_scene_saved_key_menu_submenu_callback, app); + + submenu_set_selected_item( + submenu, scene_manager_get_scene_state(app->scene_manager, LfRfidSceneSavedKeyMenu)); + + view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewSubmenu); +} + +bool lfrfid_scene_saved_key_menu_on_event(void* context, SceneManagerEvent event) { + LfRfid* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubmenuIndexEmulate) { + scene_manager_next_scene(app->scene_manager, LfRfidSceneEmulate); + consumed = true; + } else if(event.event == SubmenuIndexWrite) { + scene_manager_next_scene(app->scene_manager, LfRfidSceneWrite); + consumed = true; + } else if(event.event == SubmenuIndexEdit) { + scene_manager_next_scene(app->scene_manager, LfRfidSceneSaveData); + consumed = true; + } else if(event.event == SubmenuIndexDelete) { + scene_manager_next_scene(app->scene_manager, LfRfidSceneDeleteConfirm); + consumed = true; + } else if(event.event == SubmenuIndexInfo) { + scene_manager_next_scene(app->scene_manager, LfRfidSceneSavedInfo); + consumed = true; + } + scene_manager_set_scene_state(app->scene_manager, LfRfidSceneSavedKeyMenu, event.event); + } + + return consumed; +} + +void lfrfid_scene_saved_key_menu_on_exit(void* context) { + LfRfid* app = context; + + submenu_reset(app->submenu); +} diff --git a/applications/lfrfid/scenes/lfrfid_scene_select_key.c b/applications/lfrfid/scenes/lfrfid_scene_select_key.c new file mode 100644 index 00000000000..2a9cc1c634a --- /dev/null +++ b/applications/lfrfid/scenes/lfrfid_scene_select_key.c @@ -0,0 +1,22 @@ +#include "../lfrfid_i.h" + +void lfrfid_scene_select_key_on_enter(void* context) { + LfRfid* app = context; + + if(lfrfid_load_key_from_file_select(app)) { + scene_manager_next_scene(app->scene_manager, LfRfidSceneSavedKeyMenu); + } else { + scene_manager_previous_scene(app->scene_manager); + } +} + +bool lfrfid_scene_select_key_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + UNUSED(event); + bool consumed = false; + return consumed; +} + +void lfrfid_scene_select_key_on_exit(void* context) { + UNUSED(context); +} diff --git a/applications/lfrfid/scenes/lfrfid_scene_start.c b/applications/lfrfid/scenes/lfrfid_scene_start.c new file mode 100644 index 00000000000..9074e859b45 --- /dev/null +++ b/applications/lfrfid/scenes/lfrfid_scene_start.c @@ -0,0 +1,72 @@ +#include "../lfrfid_i.h" + +typedef enum { + SubmenuIndexRead, + SubmenuIndexSaved, + SubmenuIndexAddManually, + SubmenuIndexExtraActions, +} SubmenuIndex; + +static void lfrfid_scene_start_submenu_callback(void* context, uint32_t index) { + LfRfid* app = context; + + view_dispatcher_send_custom_event(app->view_dispatcher, index); +} + +void lfrfid_scene_start_on_enter(void* context) { + LfRfid* app = context; + Submenu* submenu = app->submenu; + + submenu_add_item(submenu, "Read", SubmenuIndexRead, lfrfid_scene_start_submenu_callback, app); + submenu_add_item( + submenu, "Saved", SubmenuIndexSaved, lfrfid_scene_start_submenu_callback, app); + submenu_add_item( + submenu, "Add Manually", SubmenuIndexAddManually, lfrfid_scene_start_submenu_callback, app); + submenu_add_item( + submenu, + "Extra Actions", + SubmenuIndexExtraActions, + lfrfid_scene_start_submenu_callback, + app); + + submenu_set_selected_item( + submenu, scene_manager_get_scene_state(app->scene_manager, LfRfidSceneStart)); + + // clear key + string_reset(app->file_name); + app->protocol_id = PROTOCOL_NO; + app->read_type = LFRFIDWorkerReadTypeAuto; + + view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewSubmenu); +} + +bool lfrfid_scene_start_on_event(void* context, SceneManagerEvent event) { + LfRfid* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubmenuIndexRead) { + scene_manager_next_scene(app->scene_manager, LfRfidSceneRead); + consumed = true; + } else if(event.event == SubmenuIndexSaved) { + string_set_str(app->file_path, LFRFID_APP_FOLDER); + scene_manager_next_scene(app->scene_manager, LfRfidSceneSelectKey); + consumed = true; + } else if(event.event == SubmenuIndexAddManually) { + scene_manager_next_scene(app->scene_manager, LfRfidSceneSaveType); + consumed = true; + } else if(event.event == SubmenuIndexExtraActions) { + scene_manager_next_scene(app->scene_manager, LfRfidSceneExtraActions); + consumed = true; + } + scene_manager_set_scene_state(app->scene_manager, LfRfidSceneStart, event.event); + } + + return consumed; +} + +void lfrfid_scene_start_on_exit(void* context) { + LfRfid* app = context; + + submenu_reset(app->submenu); +} diff --git a/applications/lfrfid/scenes/lfrfid_scene_write.c b/applications/lfrfid/scenes/lfrfid_scene_write.c new file mode 100644 index 00000000000..4b03bac15a1 --- /dev/null +++ b/applications/lfrfid/scenes/lfrfid_scene_write.c @@ -0,0 +1,96 @@ +#include "../lfrfid_i.h" + +static void lfrfid_write_callback(LFRFIDWorkerWriteResult result, void* context) { + LfRfid* app = context; + uint32_t event = 0; + + if(result == LFRFIDWorkerWriteOK) { + event = LfRfidEventWriteOK; + } else if(result == LFRFIDWorkerWriteProtocolCannotBeWritten) { + event = LfRfidEventWriteProtocolCannotBeWritten; + } else if(result == LFRFIDWorkerWriteFobCannotBeWritten) { + event = LfRfidEventWriteFobCannotBeWritten; + } else if(result == LFRFIDWorkerWriteTooLongToWrite) { + event = LfRfidEventWriteTooLongToWrite; + } + + view_dispatcher_send_custom_event(app->view_dispatcher, event); +} + +void lfrfid_scene_write_on_enter(void* context) { + LfRfid* app = context; + Popup* popup = app->popup; + + popup_set_header(popup, "Writing", 89, 30, AlignCenter, AlignTop); + if(!string_empty_p(app->file_name)) { + popup_set_text(popup, string_get_cstr(app->file_name), 89, 43, AlignCenter, AlignTop); + } else { + popup_set_text( + popup, + protocol_dict_get_name(app->dict, app->protocol_id), + 89, + 43, + AlignCenter, + AlignTop); + } + popup_set_icon(popup, 0, 3, &I_RFIDDolphinSend_97x61); + + view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewPopup); + + size_t size = protocol_dict_get_data_size(app->dict, app->protocol_id); + app->old_key_data = (uint8_t*)malloc(size); + protocol_dict_get_data(app->dict, app->protocol_id, app->old_key_data, size); + + lfrfid_worker_start_thread(app->lfworker); + lfrfid_worker_write_start( + app->lfworker, (LFRFIDProtocol)app->protocol_id, lfrfid_write_callback, app); + notification_message(app->notifications, &sequence_blink_start_magenta); +} + +bool lfrfid_scene_write_on_event(void* context, SceneManagerEvent event) { + LfRfid* app = context; + Popup* popup = app->popup; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == LfRfidEventWriteOK) { + notification_message(app->notifications, &sequence_success); + scene_manager_next_scene(app->scene_manager, LfRfidSceneWriteSuccess); + consumed = true; + } else if(event.event == LfRfidEventWriteProtocolCannotBeWritten) { + popup_set_icon(popup, 72, 17, &I_DolphinCommon_56x48); + popup_set_header(popup, "Error", 64, 3, AlignCenter, AlignTop); + popup_set_text(popup, "This protocol\ncannot be written", 3, 17, AlignLeft, AlignTop); + notification_message(app->notifications, &sequence_blink_start_red); + consumed = true; + } else if( + (event.event == LfRfidEventWriteFobCannotBeWritten) || + (event.event == LfRfidEventWriteTooLongToWrite)) { + popup_set_icon(popup, 72, 17, &I_DolphinCommon_56x48); + popup_set_header(popup, "Still trying to write...", 64, 3, AlignCenter, AlignTop); + popup_set_text( + popup, + "Make sure this\ncard is writable\nand not\nprotected.", + 3, + 17, + AlignLeft, + AlignTop); + notification_message(app->notifications, &sequence_blink_start_yellow); + consumed = true; + } + } + + return consumed; +} + +void lfrfid_scene_write_on_exit(void* context) { + LfRfid* app = context; + notification_message(app->notifications, &sequence_blink_stop); + popup_reset(app->popup); + lfrfid_worker_stop(app->lfworker); + lfrfid_worker_stop_thread(app->lfworker); + + size_t size = protocol_dict_get_data_size(app->dict, app->protocol_id); + protocol_dict_set_data(app->dict, app->protocol_id, app->old_key_data, size); + free(app->old_key_data); +} diff --git a/applications/lfrfid/scenes/lfrfid_scene_write_success.c b/applications/lfrfid/scenes/lfrfid_scene_write_success.c new file mode 100644 index 00000000000..52e30d6b666 --- /dev/null +++ b/applications/lfrfid/scenes/lfrfid_scene_write_success.c @@ -0,0 +1,38 @@ +#include "../lfrfid_i.h" + +void lfrfid_scene_write_success_on_enter(void* context) { + LfRfid* app = context; + Popup* popup = app->popup; + + popup_set_header(popup, "Successfully\nwritten!", 94, 3, AlignCenter, AlignTop); + popup_set_icon(popup, 0, 6, &I_RFIDDolphinSuccess_108x57); + popup_set_context(popup, app); + popup_set_callback(popup, lfrfid_popup_timeout_callback); + popup_set_timeout(popup, 1500); + popup_enable_timeout(popup); + + view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewPopup); + notification_message_block(app->notifications, &sequence_set_green_255); +} + +bool lfrfid_scene_write_success_on_event(void* context, SceneManagerEvent event) { + LfRfid* app = context; + bool consumed = false; + + const uint32_t prev_scenes[] = {LfRfidSceneReadKeyMenu, LfRfidSceneSelectKey}; + + if((event.type == SceneManagerEventTypeBack) || + ((event.type == SceneManagerEventTypeCustom) && (event.event == LfRfidEventPopupClosed))) { + scene_manager_search_and_switch_to_previous_scene_one_of( + app->scene_manager, prev_scenes, COUNT_OF(prev_scenes)); + consumed = true; + } + + return consumed; +} + +void lfrfid_scene_write_success_on_exit(void* context) { + LfRfid* app = context; + notification_message_block(app->notifications, &sequence_reset_green); + popup_reset(app->popup); +} diff --git a/applications/lfrfid/view/container_vm.cpp b/applications/lfrfid/view/container_vm.cpp deleted file mode 100644 index 3c01ba30423..00000000000 --- a/applications/lfrfid/view/container_vm.cpp +++ /dev/null @@ -1,115 +0,0 @@ -#include "container_vm.h" -#include "elements/generic_element.h" -#include "elements/string_element.h" -#include "elements/icon_element.h" -#include "elements/button_element.h" -#include - -class ContainerVMData { -public: - ContainerVMData(){}; - - ~ContainerVMData() { - for(auto& it : elements) delete it; - }; - - std::list elements; - - template T add(const T element, View* view) { - elements.push_back(element); - element->set_parent_view(view); - return element; - } - - void clean() { - for(auto& it : elements) delete it; - elements.clear(); - } -}; - -struct ContainerVMModel { - ContainerVMData* data; -}; - -ContainerVM::ContainerVM() { - view = view_alloc(); - view_set_context(view, this); - view_allocate_model(view, ViewModelTypeLocking, sizeof(ContainerVMModel)); - - with_view_model_cpp(view, ContainerVMModel, model, { - model->data = new ContainerVMData(); - return true; - }); - - view_set_draw_callback(view, view_draw_callback); - view_set_input_callback(view, view_input_callback); -} - -ContainerVM::~ContainerVM() { - with_view_model_cpp(view, ContainerVMModel, model, { - delete model->data; - model->data = NULL; - return false; - }); - - view_free(view); -} - -View* ContainerVM::get_view() { - return view; -} - -void ContainerVM::clean() { - with_view_model_cpp(view, ContainerVMModel, model, { - model->data->clean(); - return true; - }); -} - -template T* ContainerVM::add() { - T* element = new T(); - - with_view_model_cpp(view, ContainerVMModel, model, { - model->data->add(element, view); - return true; - }); - - return element; -} - -void ContainerVM::view_draw_callback(Canvas* canvas, void* model) { - ContainerVMData* data = static_cast(model)->data; - - canvas_clear(canvas); - canvas_set_color(canvas, ColorBlack); - canvas_set_font(canvas, FontPrimary); - - for(const auto& element : data->elements) { - element->draw(canvas); - } -} - -bool ContainerVM::view_input_callback(InputEvent* event, void* context) { - bool consumed = false; - View* view = static_cast(context)->view; - - with_view_model_cpp(view, ContainerVMModel, model, { - for(const auto& element : model->data->elements) { - if(element->input(event)) { - consumed = true; - } - - if(consumed) { - break; - } - } - - return consumed; - }); - - return consumed; -} - -template StringElement* ContainerVM::add(); -template IconElement* ContainerVM::add(); -template ButtonElement* ContainerVM::add(); diff --git a/applications/lfrfid/view/container_vm.h b/applications/lfrfid/view/container_vm.h deleted file mode 100644 index 011baa2e98c..00000000000 --- a/applications/lfrfid/view/container_vm.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once -#include - -class ContainerVM : public GenericViewModule { -public: - ContainerVM(); - ~ContainerVM() final; - View* get_view() final; - void clean() final; - - template T* add(); - -private: - View* view; - static void view_draw_callback(Canvas* canvas, void* model); - static bool view_input_callback(InputEvent* event, void* context); -}; diff --git a/applications/lfrfid/view/elements/button_element.cpp b/applications/lfrfid/view/elements/button_element.cpp deleted file mode 100644 index 58e1ac3e1c9..00000000000 --- a/applications/lfrfid/view/elements/button_element.cpp +++ /dev/null @@ -1,65 +0,0 @@ -#include "button_element.h" -#include - -ButtonElement::ButtonElement() { -} - -ButtonElement::~ButtonElement() { -} - -void ButtonElement::draw(Canvas* canvas) { - if(text != nullptr) { - canvas_set_font(canvas, FontSecondary); - switch(type) { - case Type::Left: - elements_button_left(canvas, text); - break; - case Type::Center: - elements_button_center(canvas, text); - break; - case Type::Right: - elements_button_right(canvas, text); - break; - } - } -} - -bool ButtonElement::input(InputEvent* event) { - bool consumed = false; - if(event->type == InputTypeShort && callback != nullptr) { - switch(type) { - case Type::Left: - if(event->key == InputKeyLeft) { - callback(context); - consumed = true; - } - break; - case Type::Center: - if(event->key == InputKeyOk) { - callback(context); - consumed = true; - } - break; - case Type::Right: - if(event->key == InputKeyRight) { - callback(context); - consumed = true; - } - break; - } - } - - return consumed; -} - -void ButtonElement::set_type(Type _type, const char* _text) { - lock_model(); - type = _type; - text = _text; - unlock_model(true); -} - -void ButtonElement::set_callback(void* _context, ButtonElementCallback _callback) { - context = _context; - callback = _callback; -} diff --git a/applications/lfrfid/view/elements/button_element.h b/applications/lfrfid/view/elements/button_element.h deleted file mode 100644 index eb964427738..00000000000 --- a/applications/lfrfid/view/elements/button_element.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once -#include "generic_element.h" - -typedef void (*ButtonElementCallback)(void* context); - -class ButtonElement : public GenericElement { -public: - ButtonElement(); - ~ButtonElement() final; - void draw(Canvas* canvas) final; - bool input(InputEvent* event) final; - - enum class Type : uint8_t { - Left, - Center, - Right, - }; - - void set_type(Type type, const char* text); - void set_callback(void* context, ButtonElementCallback callback); - -private: - Type type = Type::Left; - const char* text = nullptr; - - void* context = nullptr; - ButtonElementCallback callback = nullptr; -}; diff --git a/applications/lfrfid/view/elements/generic_element.cpp b/applications/lfrfid/view/elements/generic_element.cpp deleted file mode 100644 index e0f08d15c0f..00000000000 --- a/applications/lfrfid/view/elements/generic_element.cpp +++ /dev/null @@ -1,15 +0,0 @@ -#include "generic_element.h" - -void GenericElement::lock_model() { - furi_assert(view != nullptr); - view_get_model(view); -} - -void GenericElement::unlock_model(bool need_redraw) { - furi_assert(view != nullptr); - view_commit_model(view, need_redraw); -} - -void GenericElement::set_parent_view(View* _view) { - view = _view; -} diff --git a/applications/lfrfid/view/elements/generic_element.h b/applications/lfrfid/view/elements/generic_element.h deleted file mode 100644 index f5a58b2d923..00000000000 --- a/applications/lfrfid/view/elements/generic_element.h +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once -#include -#include - -class GenericElement { -public: - GenericElement(){}; - virtual ~GenericElement(){}; - virtual void draw(Canvas* canvas) = 0; - virtual bool input(InputEvent* event) = 0; - - // TODO that must be accessible only to ContainerVMData - void set_parent_view(View* view); - - // TODO that must be accessible only to inheritors - void lock_model(); - void unlock_model(bool need_redraw); - -private: - View* view = nullptr; -}; diff --git a/applications/lfrfid/view/elements/icon_element.cpp b/applications/lfrfid/view/elements/icon_element.cpp deleted file mode 100644 index 0b6fba7dad4..00000000000 --- a/applications/lfrfid/view/elements/icon_element.cpp +++ /dev/null @@ -1,25 +0,0 @@ -#include "icon_element.h" - -IconElement::IconElement() { -} - -IconElement::~IconElement() { -} - -void IconElement::draw(Canvas* canvas) { - if(icon != NULL) { - canvas_draw_icon(canvas, x, y, icon); - } -} - -bool IconElement::input(InputEvent* /* event */) { - return false; -} - -void IconElement::set_icon(uint8_t _x, uint8_t _y, const Icon* _icon) { - lock_model(); - icon = _icon; - x = _x; - y = _y; - unlock_model(true); -} diff --git a/applications/lfrfid/view/elements/icon_element.h b/applications/lfrfid/view/elements/icon_element.h deleted file mode 100644 index a08202741ea..00000000000 --- a/applications/lfrfid/view/elements/icon_element.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once -#include "generic_element.h" - -class IconElement : public GenericElement { -public: - IconElement(); - ~IconElement() final; - void draw(Canvas* canvas) final; - bool input(InputEvent* event) final; - - void set_icon(uint8_t x = 0, uint8_t y = 0, const Icon* icon = NULL); - -private: - const Icon* icon = NULL; - uint8_t x = 0; - uint8_t y = 0; -}; diff --git a/applications/lfrfid/view/elements/string_element.cpp b/applications/lfrfid/view/elements/string_element.cpp deleted file mode 100644 index 44c11e01a4c..00000000000 --- a/applications/lfrfid/view/elements/string_element.cpp +++ /dev/null @@ -1,47 +0,0 @@ -#include "string_element.h" -#include - -StringElement::StringElement() { -} - -StringElement::~StringElement() { -} - -void StringElement::draw(Canvas* canvas) { - if(text) { - string_t line; - string_init(line); - string_set_str(line, text); - - canvas_set_font(canvas, font); - if(fit_width != 0) { - elements_string_fit_width(canvas, line, fit_width); - } - elements_multiline_text_aligned(canvas, x, y, horizontal, vertical, string_get_cstr(line)); - - string_clear(line); - } -} - -bool StringElement::input(InputEvent* /* event */) { - return false; -} - -void StringElement::set_text( - const char* _text, - uint8_t _x, - uint8_t _y, - uint8_t _fit_w, - Align _horizontal, - Align _vertical, - Font _font) { - lock_model(); - text = _text; - x = _x; - y = _y; - fit_width = _fit_w; - horizontal = _horizontal; - vertical = _vertical; - font = _font; - unlock_model(true); -} diff --git a/applications/lfrfid/view/elements/string_element.h b/applications/lfrfid/view/elements/string_element.h deleted file mode 100644 index 173fdd60197..00000000000 --- a/applications/lfrfid/view/elements/string_element.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once -#include "generic_element.h" - -class StringElement : public GenericElement { -public: - StringElement(); - ~StringElement() final; - void draw(Canvas* canvas) final; - bool input(InputEvent* event) final; - - void set_text( - const char* text = NULL, - uint8_t x = 0, - uint8_t y = 0, - uint8_t fit_width = 0, - Align horizontal = AlignLeft, - Align vertical = AlignTop, - Font font = FontPrimary); - -private: - const char* text = NULL; - uint8_t x = 0; - uint8_t y = 0; - uint8_t fit_width = 0; - Align horizontal = AlignLeft; - Align vertical = AlignTop; - Font font = FontPrimary; -}; diff --git a/applications/lfrfid/views/lfrfid_view_read.c b/applications/lfrfid/views/lfrfid_view_read.c new file mode 100644 index 00000000000..2b63175da2f --- /dev/null +++ b/applications/lfrfid/views/lfrfid_view_read.c @@ -0,0 +1,117 @@ +#include "lfrfid_view_read.h" +#include + +#define TEMP_STR_LEN 128 + +struct LfRfidReadView { + View* view; +}; + +typedef struct { + IconAnimation* icon; + LfRfidReadViewMode read_mode; +} LfRfidReadViewModel; + +static void lfrfid_view_read_draw_callback(Canvas* canvas, void* _model) { + LfRfidReadViewModel* model = _model; + canvas_set_color(canvas, ColorBlack); + + canvas_draw_icon(canvas, 0, 8, &I_NFC_manual); + + canvas_set_font(canvas, FontPrimary); + + if(model->read_mode == LfRfidReadAsk) { + canvas_draw_str(canvas, 70, 16, "Reading 1/2"); + + canvas_draw_str(canvas, 77, 29, "ASK"); + canvas_draw_icon(canvas, 70, 22, &I_ButtonRight_4x7); + canvas_draw_icon_animation(canvas, 102, 21, model->icon); + + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 77, 43, "PSK"); + } else if(model->read_mode == LfRfidReadPsk) { + canvas_draw_str(canvas, 70, 16, "Reading 2/2"); + + canvas_draw_str(canvas, 77, 43, "PSK"); + canvas_draw_icon(canvas, 70, 36, &I_ButtonRight_4x7); + canvas_draw_icon_animation(canvas, 102, 35, model->icon); + + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 77, 29, "ASK"); + } else { + canvas_draw_str(canvas, 72, 16, "Reading"); + + if(model->read_mode == LfRfidReadAskOnly) { + canvas_draw_str(canvas, 77, 35, "ASK"); + } else { + canvas_draw_str(canvas, 77, 35, "PSK"); + } + canvas_draw_icon_animation(canvas, 102, 27, model->icon); + } + + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 61, 56, "Don't move card"); +} + +void lfrfid_view_read_enter(void* context) { + LfRfidReadView* read_view = context; + with_view_model( + read_view->view, (LfRfidReadViewModel * model) { + icon_animation_start(model->icon); + return true; + }); +} + +void lfrfid_view_read_exit(void* context) { + LfRfidReadView* read_view = context; + with_view_model( + read_view->view, (LfRfidReadViewModel * model) { + icon_animation_stop(model->icon); + return false; + }); +} + +LfRfidReadView* lfrfid_view_read_alloc() { + LfRfidReadView* read_view = malloc(sizeof(LfRfidReadView)); + read_view->view = view_alloc(); + view_set_context(read_view->view, read_view); + view_allocate_model(read_view->view, ViewModelTypeLocking, sizeof(LfRfidReadViewModel)); + + with_view_model( + read_view->view, (LfRfidReadViewModel * model) { + model->icon = icon_animation_alloc(&A_Round_loader_8x8); + view_tie_icon_animation(read_view->view, model->icon); + return false; + }); + + view_set_draw_callback(read_view->view, lfrfid_view_read_draw_callback); + view_set_enter_callback(read_view->view, lfrfid_view_read_enter); + view_set_exit_callback(read_view->view, lfrfid_view_read_exit); + + return read_view; +} + +void lfrfid_view_read_free(LfRfidReadView* read_view) { + with_view_model( + read_view->view, (LfRfidReadViewModel * model) { + icon_animation_free(model->icon); + return false; + }); + + view_free(read_view->view); + free(read_view); +} + +View* lfrfid_view_read_get_view(LfRfidReadView* read_view) { + return read_view->view; +} + +void lfrfid_view_read_set_read_mode(LfRfidReadView* read_view, LfRfidReadViewMode mode) { + with_view_model( + read_view->view, (LfRfidReadViewModel * model) { + icon_animation_stop(model->icon); + icon_animation_start(model->icon); + model->read_mode = mode; + return true; + }); +} diff --git a/applications/lfrfid/views/lfrfid_view_read.h b/applications/lfrfid/views/lfrfid_view_read.h new file mode 100644 index 00000000000..55bb1f230fa --- /dev/null +++ b/applications/lfrfid/views/lfrfid_view_read.h @@ -0,0 +1,19 @@ +#pragma once +#include + +typedef enum { + LfRfidReadAsk, + LfRfidReadPsk, + LfRfidReadAskOnly, + LfRfidReadPskOnly +} LfRfidReadViewMode; + +typedef struct LfRfidReadView LfRfidReadView; + +LfRfidReadView* lfrfid_view_read_alloc(); + +void lfrfid_view_read_free(LfRfidReadView* read_view); + +View* lfrfid_view_read_get_view(LfRfidReadView* read_view); + +void lfrfid_view_read_set_read_mode(LfRfidReadView* read_view, LfRfidReadViewMode mode); diff --git a/applications/lfrfid_debug/lfrfid_debug.c b/applications/lfrfid_debug/lfrfid_debug.c new file mode 100644 index 00000000000..63d66b68b9c --- /dev/null +++ b/applications/lfrfid_debug/lfrfid_debug.c @@ -0,0 +1,81 @@ +#include "lfrfid_debug_i.h" + +static bool lfrfid_debug_custom_event_callback(void* context, uint32_t event) { + furi_assert(context); + LfRfidDebug* app = context; + return scene_manager_handle_custom_event(app->scene_manager, event); +} + +static bool lfrfid_debug_back_event_callback(void* context) { + furi_assert(context); + LfRfidDebug* app = context; + return scene_manager_handle_back_event(app->scene_manager); +} + +static LfRfidDebug* lfrfid_debug_alloc() { + LfRfidDebug* app = malloc(sizeof(LfRfidDebug)); + + app->view_dispatcher = view_dispatcher_alloc(); + app->scene_manager = scene_manager_alloc(&lfrfid_debug_scene_handlers, app); + view_dispatcher_enable_queue(app->view_dispatcher); + view_dispatcher_set_event_callback_context(app->view_dispatcher, app); + view_dispatcher_set_custom_event_callback( + app->view_dispatcher, lfrfid_debug_custom_event_callback); + view_dispatcher_set_navigation_event_callback( + app->view_dispatcher, lfrfid_debug_back_event_callback); + + // Open GUI record + app->gui = furi_record_open(RECORD_GUI); + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + + // Submenu + app->submenu = submenu_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, LfRfidDebugViewSubmenu, submenu_get_view(app->submenu)); + + // Tune view + app->tune_view = lfrfid_debug_view_tune_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + LfRfidDebugViewTune, + lfrfid_debug_view_tune_get_view(app->tune_view)); + + return app; +} + +static void lfrfid_debug_free(LfRfidDebug* app) { + furi_assert(app); + + // Submenu + view_dispatcher_remove_view(app->view_dispatcher, LfRfidDebugViewSubmenu); + submenu_free(app->submenu); + + // Tune view + view_dispatcher_remove_view(app->view_dispatcher, LfRfidDebugViewTune); + lfrfid_debug_view_tune_free(app->tune_view); + + // View Dispatcher + view_dispatcher_free(app->view_dispatcher); + + // Scene Manager + scene_manager_free(app->scene_manager); + + // GUI + furi_record_close(RECORD_GUI); + app->gui = NULL; + + free(app); +} + +int32_t lfrfid_debug_app(void* p) { + UNUSED(p); + LfRfidDebug* app = lfrfid_debug_alloc(); + + scene_manager_next_scene(app->scene_manager, LfRfidDebugSceneStart); + + view_dispatcher_run(app->view_dispatcher); + + lfrfid_debug_free(app); + + return 0; +} \ No newline at end of file diff --git a/applications/lfrfid_debug/lfrfid_debug_app.cpp b/applications/lfrfid_debug/lfrfid_debug_app.cpp deleted file mode 100644 index ef970e3617a..00000000000 --- a/applications/lfrfid_debug/lfrfid_debug_app.cpp +++ /dev/null @@ -1,17 +0,0 @@ -#include "lfrfid_debug_app.h" -#include "scene/lfrfid_debug_app_scene_start.h" -#include "scene/lfrfid_debug_app_scene_tune.h" - -LfRfidDebugApp::LfRfidDebugApp() - : scene_controller{this} { -} - -LfRfidDebugApp::~LfRfidDebugApp() { -} - -void LfRfidDebugApp::run() { - view_controller.attach_to_gui(ViewDispatcherTypeFullscreen); - scene_controller.add_scene(SceneType::Start, new LfRfidDebugAppSceneStart()); - scene_controller.add_scene(SceneType::TuneScene, new LfRfidDebugAppSceneTune()); - scene_controller.process(100); -} diff --git a/applications/lfrfid_debug/lfrfid_debug_app.h b/applications/lfrfid_debug/lfrfid_debug_app.h deleted file mode 100644 index fee183aec40..00000000000 --- a/applications/lfrfid_debug/lfrfid_debug_app.h +++ /dev/null @@ -1,40 +0,0 @@ -#pragma once -#include -#include - -#include -#include -#include - -#include -#include "view_modules/lfrfid_view_tune_vm.h" - -class LfRfidDebugApp { -public: - enum class EventType : uint8_t { - GENERIC_EVENT_ENUM_VALUES, - MenuSelected, - }; - - enum class SceneType : uint8_t { - GENERIC_SCENE_ENUM_VALUES, - TuneScene, - }; - - class Event { - public: - union { - int32_t menu_index; - } payload; - - EventType type; - }; - - SceneController, LfRfidDebugApp> scene_controller; - ViewController view_controller; - - ~LfRfidDebugApp(); - LfRfidDebugApp(); - - void run(); -}; diff --git a/applications/lfrfid_debug/lfrfid_debug_app_launcher.cpp b/applications/lfrfid_debug/lfrfid_debug_app_launcher.cpp deleted file mode 100644 index 4551a17cb4f..00000000000 --- a/applications/lfrfid_debug/lfrfid_debug_app_launcher.cpp +++ /dev/null @@ -1,11 +0,0 @@ -#include "lfrfid_debug_app.h" - -// app enter function -extern "C" int32_t lfrfid_debug_app(void* p) { - UNUSED(p); - LfRfidDebugApp* app = new LfRfidDebugApp(); - app->run(); - delete app; - - return 0; -} diff --git a/applications/lfrfid_debug/lfrfid_debug_i.h b/applications/lfrfid_debug/lfrfid_debug_i.h new file mode 100644 index 00000000000..368f1f15862 --- /dev/null +++ b/applications/lfrfid_debug/lfrfid_debug_i.h @@ -0,0 +1,31 @@ +#pragma once +#include +#include + +#include +#include +#include +#include + +#include + +#include + +#include + +typedef struct LfRfidDebug LfRfidDebug; + +struct LfRfidDebug { + Gui* gui; + ViewDispatcher* view_dispatcher; + SceneManager* scene_manager; + + // Common Views + Submenu* submenu; + LfRfidTuneView* tune_view; +}; + +typedef enum { + LfRfidDebugViewSubmenu, + LfRfidDebugViewTune, +} LfRfidDebugView; diff --git a/applications/lfrfid_debug/scene/lfrfid_debug_app_scene_start.cpp b/applications/lfrfid_debug/scene/lfrfid_debug_app_scene_start.cpp deleted file mode 100644 index 873e152a102..00000000000 --- a/applications/lfrfid_debug/scene/lfrfid_debug_app_scene_start.cpp +++ /dev/null @@ -1,47 +0,0 @@ -#include "lfrfid_debug_app_scene_start.h" - -typedef enum { - SubmenuTune, -} SubmenuIndex; - -void LfRfidDebugAppSceneStart::on_enter(LfRfidDebugApp* app, bool need_restore) { - auto submenu = app->view_controller.get(); - auto callback = cbc::obtain_connector(this, &LfRfidDebugAppSceneStart::submenu_callback); - - submenu->add_item("Tune", SubmenuTune, callback, app); - - if(need_restore) { - submenu->set_selected_item(submenu_item_selected); - } - app->view_controller.switch_to(); -} - -bool LfRfidDebugAppSceneStart::on_event(LfRfidDebugApp* app, LfRfidDebugApp::Event* event) { - bool consumed = false; - - if(event->type == LfRfidDebugApp::EventType::MenuSelected) { - submenu_item_selected = event->payload.menu_index; - switch(event->payload.menu_index) { - case SubmenuTune: - app->scene_controller.switch_to_next_scene(LfRfidDebugApp::SceneType::TuneScene); - break; - } - consumed = true; - } - - return consumed; -} - -void LfRfidDebugAppSceneStart::on_exit(LfRfidDebugApp* app) { - app->view_controller.get()->clean(); -} - -void LfRfidDebugAppSceneStart::submenu_callback(void* context, uint32_t index) { - LfRfidDebugApp* app = static_cast(context); - LfRfidDebugApp::Event event; - - event.type = LfRfidDebugApp::EventType::MenuSelected; - event.payload.menu_index = index; - - app->view_controller.send_event(&event); -} diff --git a/applications/lfrfid_debug/scene/lfrfid_debug_app_scene_start.h b/applications/lfrfid_debug/scene/lfrfid_debug_app_scene_start.h deleted file mode 100644 index 7fc0b07d97d..00000000000 --- a/applications/lfrfid_debug/scene/lfrfid_debug_app_scene_start.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once -#include "../lfrfid_debug_app.h" - -class LfRfidDebugAppSceneStart : public GenericScene { -public: - void on_enter(LfRfidDebugApp* app, bool need_restore) final; - bool on_event(LfRfidDebugApp* app, LfRfidDebugApp::Event* event) final; - void on_exit(LfRfidDebugApp* app) final; - -private: - void submenu_callback(void* context, uint32_t index); - uint32_t submenu_item_selected = 0; -}; diff --git a/applications/lfrfid_debug/scene/lfrfid_debug_app_scene_tune.h b/applications/lfrfid_debug/scene/lfrfid_debug_app_scene_tune.h deleted file mode 100644 index 53399efc9b7..00000000000 --- a/applications/lfrfid_debug/scene/lfrfid_debug_app_scene_tune.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once -#include "../lfrfid_debug_app.h" - -class LfRfidDebugAppSceneTune : public GenericScene { -public: - void on_enter(LfRfidDebugApp* app, bool need_restore) final; - bool on_event(LfRfidDebugApp* app, LfRfidDebugApp::Event* event) final; - void on_exit(LfRfidDebugApp* app) final; -}; diff --git a/applications/lfrfid_debug/scenes/lfrfid_debug_app_scene_start.c b/applications/lfrfid_debug/scenes/lfrfid_debug_app_scene_start.c new file mode 100644 index 00000000000..2fc6706e38c --- /dev/null +++ b/applications/lfrfid_debug/scenes/lfrfid_debug_app_scene_start.c @@ -0,0 +1,44 @@ +#include "../lfrfid_debug_i.h" + +typedef enum { + SubmenuIndexTune, +} SubmenuIndex; + +static void lfrfid_debug_scene_start_submenu_callback(void* context, uint32_t index) { + LfRfidDebug* app = context; + + view_dispatcher_send_custom_event(app->view_dispatcher, index); +} + +void lfrfid_debug_scene_start_on_enter(void* context) { + LfRfidDebug* app = context; + Submenu* submenu = app->submenu; + + submenu_add_item( + submenu, "Tune", SubmenuIndexTune, lfrfid_debug_scene_start_submenu_callback, app); + + submenu_set_selected_item( + submenu, scene_manager_get_scene_state(app->scene_manager, LfRfidDebugSceneStart)); + + view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidDebugViewSubmenu); +} + +bool lfrfid_debug_scene_start_on_event(void* context, SceneManagerEvent event) { + LfRfidDebug* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubmenuIndexTune) { + scene_manager_next_scene(app->scene_manager, LfRfidDebugSceneTune); + consumed = true; + } + } + + return consumed; +} + +void lfrfid_debug_scene_start_on_exit(void* context) { + LfRfidDebug* app = context; + + submenu_reset(app->submenu); +} diff --git a/applications/lfrfid_debug/scene/lfrfid_debug_app_scene_tune.cpp b/applications/lfrfid_debug/scenes/lfrfid_debug_app_scene_tune.c similarity index 53% rename from applications/lfrfid_debug/scene/lfrfid_debug_app_scene_tune.cpp rename to applications/lfrfid_debug/scenes/lfrfid_debug_app_scene_tune.c index 4b6276497f5..c7f3bf24fdc 100644 --- a/applications/lfrfid_debug/scene/lfrfid_debug_app_scene_tune.cpp +++ b/applications/lfrfid_debug/scenes/lfrfid_debug_app_scene_tune.c @@ -1,4 +1,4 @@ -#include "lfrfid_debug_app_scene_tune.h" +#include "../lfrfid_debug_i.h" #include static void comparator_trigger_callback(bool level, void* comp_ctx) { @@ -6,32 +6,38 @@ static void comparator_trigger_callback(bool level, void* comp_ctx) { furi_hal_gpio_write(&gpio_ext_pa7, !level); } -void LfRfidDebugAppSceneTune::on_enter(LfRfidDebugApp* app, bool /* need_restore */) { - app->view_controller.switch_to(); +void lfrfid_debug_scene_tune_on_enter(void* context) { + LfRfidDebug* app = context; + furi_hal_gpio_init_simple(&gpio_ext_pa7, GpioModeOutputPushPull); - furi_hal_rfid_comp_set_callback(comparator_trigger_callback, this); + furi_hal_rfid_comp_set_callback(comparator_trigger_callback, app); furi_hal_rfid_comp_start(); furi_hal_rfid_pins_read(); furi_hal_rfid_tim_read(125000, 0.5); furi_hal_rfid_tim_read_start(); + + view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidDebugViewTune); } -bool LfRfidDebugAppSceneTune::on_event(LfRfidDebugApp* app, LfRfidDebugApp::Event* /* event */) { - bool consumed = false; +bool lfrfid_debug_scene_tune_on_event(void* context, SceneManagerEvent event) { + UNUSED(event); - LfRfidViewTuneVM* tune = app->view_controller; + LfRfidDebug* app = context; + bool consumed = false; - if(tune->is_dirty()) { - furi_hal_rfid_set_read_period(tune->get_ARR()); - furi_hal_rfid_set_read_pulse(tune->get_CCR()); + if(lfrfid_debug_view_tune_is_dirty(app->tune_view)) { + furi_hal_rfid_set_read_period(lfrfid_debug_view_tune_get_arr(app->tune_view)); + furi_hal_rfid_set_read_pulse(lfrfid_debug_view_tune_get_ccr(app->tune_view)); } return consumed; } -void LfRfidDebugAppSceneTune::on_exit(LfRfidDebugApp* /* app */) { +void lfrfid_debug_scene_tune_on_exit(void* context) { + UNUSED(context); + furi_hal_rfid_comp_stop(); furi_hal_rfid_comp_set_callback(NULL, NULL); diff --git a/applications/lfrfid_debug/scenes/lfrfid_debug_scene.c b/applications/lfrfid_debug/scenes/lfrfid_debug_scene.c new file mode 100644 index 00000000000..e6288e923b2 --- /dev/null +++ b/applications/lfrfid_debug/scenes/lfrfid_debug_scene.c @@ -0,0 +1,30 @@ +#include "lfrfid_debug_scene.h" + +// Generate scene on_enter handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, +void (*const lfrfid_debug_on_enter_handlers[])(void*) = { +#include "lfrfid_debug_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 lfrfid_debug_on_event_handlers[])(void* context, SceneManagerEvent event) = { +#include "lfrfid_debug_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 lfrfid_debug_on_exit_handlers[])(void* context) = { +#include "lfrfid_debug_scene_config.h" +}; +#undef ADD_SCENE + +// Initialize scene handlers configuration structure +const SceneManagerHandlers lfrfid_debug_scene_handlers = { + .on_enter_handlers = lfrfid_debug_on_enter_handlers, + .on_event_handlers = lfrfid_debug_on_event_handlers, + .on_exit_handlers = lfrfid_debug_on_exit_handlers, + .scene_num = LfRfidDebugSceneNum, +}; diff --git a/applications/lfrfid_debug/scenes/lfrfid_debug_scene.h b/applications/lfrfid_debug/scenes/lfrfid_debug_scene.h new file mode 100644 index 00000000000..8fc74f72407 --- /dev/null +++ b/applications/lfrfid_debug/scenes/lfrfid_debug_scene.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +// Generate scene id and total number +#define ADD_SCENE(prefix, name, id) LfRfidDebugScene##id, +typedef enum { +#include "lfrfid_debug_scene_config.h" + LfRfidDebugSceneNum, +} LfRfidDebugScene; +#undef ADD_SCENE + +extern const SceneManagerHandlers lfrfid_debug_scene_handlers; + +// Generate scene on_enter handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); +#include "lfrfid_debug_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 "lfrfid_debug_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 "lfrfid_debug_scene_config.h" +#undef ADD_SCENE diff --git a/applications/lfrfid_debug/scenes/lfrfid_debug_scene_config.h b/applications/lfrfid_debug/scenes/lfrfid_debug_scene_config.h new file mode 100644 index 00000000000..f3cca47b88d --- /dev/null +++ b/applications/lfrfid_debug/scenes/lfrfid_debug_scene_config.h @@ -0,0 +1,2 @@ +ADD_SCENE(lfrfid_debug, start, Start) +ADD_SCENE(lfrfid_debug, tune, Tune) diff --git a/applications/lfrfid_debug/view_modules/lfrfid_view_tune_vm.cpp b/applications/lfrfid_debug/view_modules/lfrfid_view_tune_vm.cpp deleted file mode 100644 index 5c244b92ca3..00000000000 --- a/applications/lfrfid_debug/view_modules/lfrfid_view_tune_vm.cpp +++ /dev/null @@ -1,214 +0,0 @@ -#include "lfrfid_view_tune_vm.h" -#include -#include - -struct LfRfidViewTuneVMModel { - bool dirty; - bool fine; - uint32_t ARR; - uint32_t CCR; - int pos; -}; - -void LfRfidViewTuneVM::view_draw_callback(Canvas* canvas, void* _model) { - LfRfidViewTuneVMModel* model = reinterpret_cast(_model); - canvas_clear(canvas); - canvas_set_color(canvas, ColorBlack); - - if(model->fine) { - canvas_draw_box( - canvas, - 128 - canvas_string_width(canvas, "Fine") - 4, - 0, - canvas_string_width(canvas, "Fine") + 4, - canvas_current_font_height(canvas) + 1); - canvas_set_color(canvas, ColorWhite); - } - canvas_draw_str_aligned(canvas, 128 - 2, 2, AlignRight, AlignTop, "Fine"); - canvas_set_color(canvas, ColorBlack); - - constexpr uint8_t buffer_size = 128; - char buffer[buffer_size + 1]; - double freq = ((float)SystemCoreClock / ((float)model->ARR + 1)); - double duty = ((float)model->CCR + 1) / ((float)model->ARR + 1) * 100.0f; - snprintf( - buffer, - buffer_size, - "%sARR: %lu\n" - "freq = %.4f\n" - "%sCCR: %lu\n" - "duty = %.4f", - model->pos == 0 ? ">" : "", - model->ARR, - freq, - model->pos == 1 ? ">" : "", - model->CCR, - duty); - elements_multiline_text_aligned(canvas, 2, 2, AlignLeft, AlignTop, buffer); -} - -bool LfRfidViewTuneVM::view_input_callback(InputEvent* event, void* context) { - LfRfidViewTuneVM* _this = reinterpret_cast(context); - bool consumed = false; - - // Process key presses only - if(event->type == InputTypeShort || event->type == InputTypeRepeat) { - consumed = true; - - switch(event->key) { - case InputKeyLeft: - _this->button_left(); - break; - case InputKeyRight: - _this->button_right(); - break; - case InputKeyUp: - _this->button_up(); - break; - case InputKeyDown: - _this->button_down(); - break; - case InputKeyOk: - _this->button_ok(); - break; - default: - consumed = false; - break; - } - } - - return consumed; -} - -void LfRfidViewTuneVM::button_up() { - with_view_model_cpp(view, LfRfidViewTuneVMModel, model, { - if(model->pos > 0) model->pos--; - return true; - }); -} - -void LfRfidViewTuneVM::button_down() { - with_view_model_cpp(view, LfRfidViewTuneVMModel, model, { - if(model->pos < 1) model->pos++; - return true; - }); -} - -void LfRfidViewTuneVM::button_left() { - with_view_model_cpp(view, LfRfidViewTuneVMModel, model, { - if(model->pos == 0) { - if(model->fine) { - model->ARR -= 1; - } else { - model->ARR -= 10; - } - } else if(model->pos == 1) { - if(model->fine) { - model->CCR -= 1; - } else { - model->CCR -= 10; - } - } - - model->dirty = true; - return true; - }); -} - -void LfRfidViewTuneVM::button_right() { - with_view_model_cpp(view, LfRfidViewTuneVMModel, model, { - if(model->pos == 0) { - if(model->fine) { - model->ARR += 1; - } else { - model->ARR += 10; - } - } else if(model->pos == 1) { - if(model->fine) { - model->CCR += 1; - } else { - model->CCR += 10; - } - } - - model->dirty = true; - return true; - }); -} - -void LfRfidViewTuneVM::button_ok() { - with_view_model_cpp(view, LfRfidViewTuneVMModel, model, { - model->fine = !model->fine; - return true; - }); -} - -LfRfidViewTuneVM::LfRfidViewTuneVM() { - view = view_alloc(); - view_set_context(view, this); - view_allocate_model(view, ViewModelTypeLocking, sizeof(LfRfidViewTuneVMModel)); - - with_view_model_cpp(view, LfRfidViewTuneVMModel, model, { - model->dirty = true; - model->fine = false; - model->ARR = 511; - model->CCR = 255; - model->pos = 0; - return true; - }); - - view_set_draw_callback( - view, cbc::obtain_connector(this, &LfRfidViewTuneVM::view_draw_callback)); - view_set_input_callback( - view, cbc::obtain_connector(this, &LfRfidViewTuneVM::view_input_callback)); -} - -LfRfidViewTuneVM::~LfRfidViewTuneVM() { - view_free(view); -} - -View* LfRfidViewTuneVM::get_view() { - return view; -} - -void LfRfidViewTuneVM::clean() { - with_view_model_cpp(view, LfRfidViewTuneVMModel, model, { - model->dirty = true; - model->fine = false; - model->ARR = 511; - model->CCR = 255; - model->pos = 0; - return true; - }); -} - -bool LfRfidViewTuneVM::is_dirty() { - bool result; - with_view_model_cpp(view, LfRfidViewTuneVMModel, model, { - result = model->dirty; - model->dirty = false; - return false; - }); - - return result; -} - -uint32_t LfRfidViewTuneVM::get_ARR() { - uint32_t result; - with_view_model_cpp(view, LfRfidViewTuneVMModel, model, { - result = model->ARR; - return false; - }); - - return result; -} - -uint32_t LfRfidViewTuneVM::get_CCR() { - uint32_t result; - with_view_model_cpp(view, LfRfidViewTuneVMModel, model, { - result = model->CCR; - return false; - }); - - return result; -} diff --git a/applications/lfrfid_debug/view_modules/lfrfid_view_tune_vm.h b/applications/lfrfid_debug/view_modules/lfrfid_view_tune_vm.h deleted file mode 100644 index 7fb18565483..00000000000 --- a/applications/lfrfid_debug/view_modules/lfrfid_view_tune_vm.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once -#include -#include - -class LfRfidViewTuneVM : public GenericViewModule { -public: - LfRfidViewTuneVM(); - ~LfRfidViewTuneVM() final; - View* get_view() final; - void clean() final; - - bool is_dirty(); - uint32_t get_ARR(); - uint32_t get_CCR(); - -private: - View* view; - void view_draw_callback(Canvas* canvas, void* _model); - bool view_input_callback(InputEvent* event, void* context); - - void button_up(); - void button_down(); - void button_left(); - void button_right(); - void button_ok(); -}; diff --git a/applications/lfrfid_debug/views/lfrfid_debug_view_tune.c b/applications/lfrfid_debug/views/lfrfid_debug_view_tune.c new file mode 100644 index 00000000000..38fe3603658 --- /dev/null +++ b/applications/lfrfid_debug/views/lfrfid_debug_view_tune.c @@ -0,0 +1,229 @@ +#include "lfrfid_debug_view_tune.h" +#include + +#define TEMP_STR_LEN 128 + +struct LfRfidTuneView { + View* view; +}; + +typedef struct { + bool dirty; + bool fine; + uint32_t ARR; + uint32_t CCR; + int pos; +} LfRfidTuneViewModel; + +static void lfrfid_debug_view_tune_draw_callback(Canvas* canvas, void* _model) { + LfRfidTuneViewModel* model = _model; + canvas_set_color(canvas, ColorBlack); + + if(model->fine) { + canvas_draw_box( + canvas, + 128 - canvas_string_width(canvas, "Fine") - 4, + 0, + canvas_string_width(canvas, "Fine") + 4, + canvas_current_font_height(canvas) + 1); + canvas_set_color(canvas, ColorWhite); + } + canvas_draw_str_aligned(canvas, 128 - 2, 2, AlignRight, AlignTop, "Fine"); + canvas_set_color(canvas, ColorBlack); + + char buffer[TEMP_STR_LEN + 1]; + double freq = ((float)SystemCoreClock / ((float)model->ARR + 1)); + double duty = ((float)model->CCR + 1) / ((float)model->ARR + 1) * 100.0f; + snprintf( + buffer, + TEMP_STR_LEN, + "%sARR: %lu\n" + "freq = %.4f\n" + "%sCCR: %lu\n" + "duty = %.4f", + model->pos == 0 ? ">" : "", + model->ARR, + freq, + model->pos == 1 ? ">" : "", + model->CCR, + duty); + elements_multiline_text_aligned(canvas, 2, 2, AlignLeft, AlignTop, buffer); +} + +static void lfrfid_debug_view_tune_button_up(LfRfidTuneView* tune_view) { + with_view_model( + tune_view->view, (LfRfidTuneViewModel * model) { + if(model->pos > 0) model->pos--; + return true; + }); +} + +static void lfrfid_debug_view_tune_button_down(LfRfidTuneView* tune_view) { + with_view_model( + tune_view->view, (LfRfidTuneViewModel * model) { + if(model->pos < 1) model->pos++; + return true; + }); +} + +static void lfrfid_debug_view_tune_button_left(LfRfidTuneView* tune_view) { + with_view_model( + tune_view->view, (LfRfidTuneViewModel * model) { + if(model->pos == 0) { + if(model->fine) { + model->ARR -= 1; + } else { + model->ARR -= 10; + } + } else if(model->pos == 1) { + if(model->fine) { + model->CCR -= 1; + } else { + model->CCR -= 10; + } + } + + model->dirty = true; + return true; + }); +} + +static void lfrfid_debug_view_tune_button_right(LfRfidTuneView* tune_view) { + with_view_model( + tune_view->view, (LfRfidTuneViewModel * model) { + if(model->pos == 0) { + if(model->fine) { + model->ARR += 1; + } else { + model->ARR += 10; + } + } else if(model->pos == 1) { + if(model->fine) { + model->CCR += 1; + } else { + model->CCR += 10; + } + } + + model->dirty = true; + return true; + }); +} + +static void lfrfid_debug_view_tune_button_ok(LfRfidTuneView* tune_view) { + with_view_model( + tune_view->view, (LfRfidTuneViewModel * model) { + model->fine = !model->fine; + return true; + }); +} + +static bool lfrfid_debug_view_tune_input_callback(InputEvent* event, void* context) { + LfRfidTuneView* tune_view = context; + bool consumed = false; + + // Process key presses only + if(event->type == InputTypeShort || event->type == InputTypeRepeat) { + consumed = true; + + switch(event->key) { + case InputKeyLeft: + lfrfid_debug_view_tune_button_left(tune_view); + break; + case InputKeyRight: + lfrfid_debug_view_tune_button_right(tune_view); + break; + case InputKeyUp: + lfrfid_debug_view_tune_button_up(tune_view); + break; + case InputKeyDown: + lfrfid_debug_view_tune_button_down(tune_view); + break; + case InputKeyOk: + lfrfid_debug_view_tune_button_ok(tune_view); + break; + default: + consumed = false; + break; + } + } + + return consumed; +} + +LfRfidTuneView* lfrfid_debug_view_tune_alloc() { + LfRfidTuneView* tune_view = malloc(sizeof(LfRfidTuneView)); + tune_view->view = view_alloc(); + view_set_context(tune_view->view, tune_view); + view_allocate_model(tune_view->view, ViewModelTypeLocking, sizeof(LfRfidTuneViewModel)); + + with_view_model( + tune_view->view, (LfRfidTuneViewModel * model) { + model->dirty = true; + model->fine = false; + model->ARR = 511; + model->CCR = 255; + model->pos = 0; + return true; + }); + + view_set_draw_callback(tune_view->view, lfrfid_debug_view_tune_draw_callback); + view_set_input_callback(tune_view->view, lfrfid_debug_view_tune_input_callback); + + return tune_view; +} + +void lfrfid_debug_view_tune_free(LfRfidTuneView* tune_view) { + view_free(tune_view->view); + free(tune_view); +} + +View* lfrfid_debug_view_tune_get_view(LfRfidTuneView* tune_view) { + return tune_view->view; +} + +void lfrfid_debug_view_tune_clean(LfRfidTuneView* tune_view) { + with_view_model( + tune_view->view, (LfRfidTuneViewModel * model) { + model->dirty = true; + model->fine = false; + model->ARR = 511; + model->CCR = 255; + model->pos = 0; + return true; + }); +} + +bool lfrfid_debug_view_tune_is_dirty(LfRfidTuneView* tune_view) { + bool result = false; + with_view_model( + tune_view->view, (LfRfidTuneViewModel * model) { + result = model->dirty; + model->dirty = false; + return false; + }); + + return result; +} + +uint32_t lfrfid_debug_view_tune_get_arr(LfRfidTuneView* tune_view) { + uint32_t result = false; + with_view_model( + tune_view->view, (LfRfidTuneViewModel * model) { + result = model->ARR; + return false; + }); + + return result; +} + +uint32_t lfrfid_debug_view_tune_get_ccr(LfRfidTuneView* tune_view) { + uint32_t result = false; + with_view_model( + tune_view->view, (LfRfidTuneViewModel * model) { + result = model->CCR; + return false; + }); + + return result; +} diff --git a/applications/lfrfid_debug/views/lfrfid_debug_view_tune.h b/applications/lfrfid_debug/views/lfrfid_debug_view_tune.h new file mode 100644 index 00000000000..fd6d0b1fe9a --- /dev/null +++ b/applications/lfrfid_debug/views/lfrfid_debug_view_tune.h @@ -0,0 +1,18 @@ +#pragma once +#include + +typedef struct LfRfidTuneView LfRfidTuneView; + +LfRfidTuneView* lfrfid_debug_view_tune_alloc(); + +void lfrfid_debug_view_tune_free(LfRfidTuneView* tune_view); + +View* lfrfid_debug_view_tune_get_view(LfRfidTuneView* tune_view); + +void lfrfid_debug_view_tune_clean(LfRfidTuneView* tune_view); + +bool lfrfid_debug_view_tune_is_dirty(LfRfidTuneView* tune_view); + +uint32_t lfrfid_debug_view_tune_get_arr(LfRfidTuneView* tune_view); + +uint32_t lfrfid_debug_view_tune_get_ccr(LfRfidTuneView* tune_view); diff --git a/assets/icons/Common/Round_loader_8x8/frame_01.png b/assets/icons/Common/Round_loader_8x8/frame_01.png new file mode 100644 index 0000000000000000000000000000000000000000..a5dc239d85e8a02c44786475069a9b683bda5aba GIT binary patch literal 7324 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 TxH*)9T8IUCC+Rm6uZaH!P{_=Z literal 0 HcmV?d00001 diff --git a/assets/icons/Common/Round_loader_8x8/frame_02.png b/assets/icons/Common/Round_loader_8x8/frame_02.png new file mode 100644 index 0000000000000000000000000000000000000000..162d8a8f42a9492ace7717662ec13d525ee90f95 GIT binary patch literal 3606 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 W0R@kPHAx&eU}I@-QEKLS@qYkZZBH2h literal 0 HcmV?d00001 diff --git a/assets/icons/Common/Round_loader_8x8/frame_03.png b/assets/icons/Common/Round_loader_8x8/frame_03.png new file mode 100644 index 0000000000000000000000000000000000000000..5483e47345687cbb9bdca56f6c9a946ebed1b542 GIT binary patch literal 3603 zcmaJ@c{r4N*nVw=vSbZmj5w8L7)wmXHkMJg#x^Q3#$Yf@V=yC0q?BygvL=+&P)eC( zs|eYmNS5p*G?tLHZ=7>F-}lEk-+Nu}`&-`UzMuQ~J@ z0>Fw4-X>w!eBqk_=CZJeaYJ+rzq6S5zHFpZePlO3GACvVQK+49SLo#jz=Vb6Cii19~5aE`4v@Zuj~SVl7L~b$kZN=L=@1Z?${&-H0=i- zJ3RH`2T}kafF5QF1|A3ivp=j1d4S&HQsu0B z4dgM5!Rp*~r5q8i&!Lu_5!F0Ea6CFu$k+pj$S_rH1weY7076c*jSFPWm8a2FJ6!Be zj%Fe`fV!5{w)7KST4K!9_tGJY^TVSGqpt(XOfiy&g}wR4!P)6qilWFVF6;Ka03f~A z`D$CwC~<0Xd}PvlDuDH)A%EL{o&+{jSQy%_y1~f}2-|kB`rIcbdbr4q9FK!){fkcn zea?XI(3+c8KVRM{{`aA4v2?vnW4YWr_Du$|2l)5wnN#w-Q>`%;YxqlRgftu*zQTUH z-5)v+oXa%MD5)!m+B%0J2A87Zr&_p8HHoWJirbCR7q{XMcaCb8olzCPzJR{Qv4jqeQ1x~Hnfob{)^FKHGt0YlFaOv0z0YNLT(Xh`Tm{K<#OTYVL6jEv?)rft^6>I49^}Wzk22#bxSpF)ZGL zoUsw2v%F_73%?GCOiZ*yTcZ*8m1Y&oMJjn+_@XXrzWxK8eBZ8IDO6;wp5_TFOGH~R zl)o#;DdZ}h{XcNhCCX8RvZx z!2qQvRU`@}+TGk}eluPn25X2JwdTt|h$>7GIj(~qEh!S)b`tHq+)L77TzSrUkrLbZbK_nCMf{HiZxnp(*>7$s+({yx3S|LgAQsp+U6 zA$tX`2t+j^Jr7CwOP!Z0+Fv0B$*{i1^C3N>$x-7IR3^jcme4Jd6Z*)?kudS3cOt2< zROi(2)TsxKPJvEF-Sh{093MIfJv{4t;hu%lQ~R-EzK;?QyNhL=UKZFp4b1JeY{G+)N)Uh@4}bqnfh}BfwUJ_$qZPPgX`(_6K!*GyH&^p-=NIzgrfZ9_+MSCj2N^ zmi)%*VRgzM)zIeEO_U1I)WgoVCSt$(-@-FH=P!@k-`C<>nN;sv&}mih2UGd`Ri7~( znxvsOgLJ@?q5TCn;BSEO#6$ZF>kAvkYYw-i_-A@oc9wsjwzf8@4lsHd&9m|P-P!)R zn3~U3{hM*ocg?dKj>qc8Hm3EZg|+ac*`%#9ZaEt|)18-{KQXL&&djLJ5PDn!>}6cC z_GZDdTAtr8$ttN2&LRwnjfy24?mE2eK0*-H3XJ3?QSSg_^s;CRI`!BKrl`qEP_e=d3M4DP$Tb$H@)987rGs}!IGl8E&3d}^Id9Cc zWk3v59xs(4xpqP9@Db<(^=~K39jNE1tExSWan}u>>iC6(;u|lH_4_@i1p6@j0#E_i z*(_!R^8xb`?dnhSg>(_B=s~`@Yvc93_3&$Bft*tTnOfQ7nT}~xZ0W>6{tL;;>Gqb~ z5Cc~o*TgWr=vyM`PmCs_K&JMP{dN;YL>pPr#miaIcIGwtarr09DwxL&ZcgSnr>^J? z9=m{8_q}3g5%gAz&km(RPm;R%LUGB)*{tUaqvP|r&uaQ9*(uotC;@z0xymPvibEB4 z-YP1|0^{^a!aJGdtM69F71sin`o#mHtvd_KQKj~R64sFWDzwx4Pt%ylYUHVpT0`F3 z-_{iOL`sBJCqA}kK7QKEq`58(gKN!SN+`xUNQS6ux}hq)X!!ZA3oX7aPtvyRN6{#! zML+t$VmvCU>+W5IN~DW+YH><)3$gUasTWvrOsba?%P+nAwDZLylBwU!oHgS|B?SyTs;zy(M?mSu z$su9niL3}xU)Ee^$TRY(BZ99@CSLt9bT91unElr~?QaI&v<+oktXSrqLDE&aV6{OE zwE{vO{7yCN>y3gO*|V~iH5I?YS(;UsRXzAD)JBTs%O7a#fcJlDcmxgyfuP+bec4S zTWy(hF({?}u$ej5$@*|{-LI}Y@YByv>Tb8`)vUL(G8IJS!2?XC%H^NID|3<|{NJqy z+(Hv*k2Y)iw!ZCN7y5Rwe7g)+M;QL;QRmU|##M+V$agh&JLT7ht0P^Ot(M)27K(8R zIr8!{0@BrsUhT;vMNf)EJKlvm|Dw+XKAalb8r?Y2f!^$$-?J9mQCmtcUZSub7C(YD zDmKbjwbS7X_clw~`!BGVtjeLrYWa*qo5tJrOP%Z3#-jIjtAxh9wcyF*`Bp zGy<881^@#i8X1H2$5BC^I3GL_4raZq1%vQjaIlMxjfM>wf%C;9Ln%0iP+LcAs6STU z3v6TvGN8fO1Oyxv1ELWEh=DK~9Q-FQj6L7khJZnThEVS!{;K@`x zi3r+Z#CVc|sBkb_>|a$7$p6q11OGlL_L)Iw7&1gtU1O)J-+?wZ|9>cf@K1Cg6@~j> zz5hxa=tw8yAShfQDTsn)_s&~!Clwinpx`i662*~33iy4H_P!)4DbSZh20^vdwLyn$ zFjzcsXXo&r5E~nq6)}*CA!2b>W^gc@L>-U!f|)_JOwG+Kw4oXZGfho%GgH%}y83!Y z5vF?jCR!%u+P|@8By11?N2LD7da<#32y-(NsF^Ml3jKHNjv)wSc4jji1s{y_vY?O% zpg*gI;s09;^uM+Ih4uOuEq~Xc^`BS>TMT5UxBsWtzqi;1+L``iUiRW2`{Rgg&r{gu zc6ZGcWPcdD?5!Qm+2gNYzwUm%J;m^US0qvsJRL7hDPdnXol@&dfp0- a0Js2dTR^rk|jF{jU{C58}IM!{e6FYea~?`&wbz5bDrmQ-Pe6S$MIZ6S(^*+OY;K&AYg$s zMYBiVotu}7{aos_egOddCIk}`l!b{2h(aZM5Pb0fK<~@34a6j`O6U)*;)NWHd*>gg z`-THx1txEkuv5P94FG#tSj4a)Du&-dOnhH9(!M^Tn;)4IGn#j}!qezl%$ZpGb2{JU z28y1A1hMAkHa=}sO=VAQwlg|}!uE+MB~9jg1G?xaJ$SENf(^31xjKwf968Jl5}s^% z?a9*s0P|c7dMNFY!6wH;GQbH?y44PeG>-g=tVAg}0-z*d7%Vciha(XUG^yD&i2+Uf zfk(Da-T8qO0Ptl58-s!S0>JDK3w<7-w>Wt~66no8P|OR&aR3>I%@1)Jz64zD!_g|7 zb+3UurXg63yRMWY%;`DIoHMMN2MCDABnlb20$~}(Dy;xWhZ8V4DB8vavgFExcGV6S zyHKJkkQ_i=OKMyCsV)t%iq!Ygfs6CQqw=G#eIFZRC65Su@Qs7B)3X#rkyBij?Rx=0 zdad)-wvIvK)a3Zcq{oym>qkTWw$D5ntS`SXv|V+blN%7W?qKz~Oic7}Q5rcO`Pcds zp9T6H0I#7nXN`WoyfggogVtghI-7gA ze!JZtG!IfF%W#qT{DpxQ*3Gt5XWwjZv4j;*WHWYCb-vB7SWFbF~5m zwrXU&Kqr~XH>Zw@OkaX6$fa%ZB?k%s%xIfB>*u?lwfNSBe&^tf6~uG}0EVGUs@J9W zBmu&nnlQ#H03_G0B^WAm0Bz}oV*pU~b^p10DUz0N*+w(X3qShEX!qV%V$3?h>{qf? zb&{OeU8aV657p@j8}AwqJHUT?f0~h$T^+oa``IHd>AZcJBf@2`)O7@=hDFYW&rl?H zx$e(>y}v4&kK3q;R}?D`kJ;xI-wST%mCT6e24j($H^K@*(=lSF!3|B+Le64i?<8lW z!lEiwOgHYF;d)|!69{%~^IDa;dP?|{(_9}p{S9B-las#oHqWnSEgl!vsV?Yo{WJF5^($tsS25B`jS{#CjFv-!7ETs(EI?eQE*Hz< zJ;WIsCOXS|{<83E--yISbBra%c2h#9ry%RhuJOc6P$g*TYszHvMuKcZ3oV>(a7=8g`+(UKXno;q&Y2|kI$=a-C0MCX@ z0OX>jt%=`#52ik6`k{!NAB%w@v2O!_bTYLDEO;F>H8JW zF)g~Jz6XTT-1wY`Uhjk;s`U~p|8^&vnw59lDdQ^6neW10rHmM9SdzsC%@w(mF zKDpSM&sF`KaZz{7vKvmuYR5LF^`!;3@T6I#tuk*q=sPeRmK{DZEqcz)sLT+19DPwT zj#+!N;8`ut@0Dbg)CXh{hr~w35{`5oS+*thsn2fC^3P_@?wZqG!LBH-5LY^6TMvlH z_8g}j52n)mTLXM4RWpSnns18hi{DH;ddhg-_RCq)lDvB=u|*_3AYIaySahw3bStPO zbwc`kW!JM0%LQ34vj$2&6F&~NA{s&-Y*?vf$uv8^L9mQ|%&%fbFSL&36lE2?UP_#T zoDVqfbbh1ypv}{4rQ9AUv8liQ>~M99oq&87cVpYvAGtt&NmYrsQ@8imlF z(2UUCo4Fgu*ITv{e%6jK79|#}d0aVOaZGSjhf9Y)d`?yG$}U1m$WKwDMmVU9a(cviFta5 z#m#0_gjL+H2&Z59X||9qLK8j2H+OZszPBE6bjoOL?p-<1=(*iQADzm6qlOLCV`f+*1Nxd7BIS#2S zI)leUOxC@kZBF>V)!?&1D>IU$ZoE)fvT`u(`NHh@eBraIZc27ab^%&|kXEMr30i)* z+{Qy$Iay$wF-d$UlYHgf>bSz1-%`K0ZK8v{y6gjCyq^Zw`X~$cb|2*bW}3cg-K!FEB*QC zM8)K&qqR$S@G^tg*Z1XQ`MbFpnT5Cs85iQoFSOf1 z-plcAWo_@;xU{fZb7xzhGy4~BYZt5p-Zao8$(P+s#{sjsJYF!17%V5F@0@h_~D z*{WyCbVr|Rvyf#{*Y}9Q1Zg*!%G$}$!3y(0n}*zki+3*WUNK|Mc+<$f0}pF!pYRdU z+HnVwaLPnh7`ZQNE;H~M<;+pR*G3bs{u;U)d~po*^>+K4fj4bK8JEhJd1sIeDRta(lOuTYly7|y>@E5R{0FXu2~VQoAjd9292C^yI20P!(n zfU#=1nc?Gn+PT`(q$AfrlJ?!Q+-0?S>C04t^L!QM2YA)3U$OjD38EBX9!l97K|VZ7 zp24rS%sJ|n(tcRY9Pea(xU}wF*X{S|=O;DiTa0Sf+gX`%lG5OT3dPFhp8+d#l7al+ zEeD)~66g;%Yx=gn?Oqr9cByRpF}{vC{L{70wd0MG5KEBnO73>buMby7y3AY6yA@6- z#3kg&$;k*vS1-D^Cyx|8DH82?7wYhfG2{1OYG`Y8<5UM`vv+>aT5LycDWiCa%6d@z z5ZA~r-HXPA*+{{Ho|lj z_p$#frD$dtnl0Ib7Ko+d0V8*^8y;jq#Cqb@YNpl_eZUCaPg~ zFluxnh0O*4Jp(!gi}S(LKyG+X0to?Ty{rX;2<`~5qm~uaieiHIA|QjPc-tUrJ6w zQ^i4{P|z`5H7#vzycj=yV(Eif=KySmgM(Or`SD%(6JPVx*Bw6SHFQ)R{uYgNcTP zTkroV>}SWI;2~(dAK9OZW1pRe!cHm*+=Pn9(#TXhGTHZcBT-&t8rjc_OaZ|()HFed zt*|%(X(w{zZ;F)_+=Aps!;)}#3sVG`EuuysxWjc!%uJ17rn)c~%v4?7%+%QUn6|FY zF%x4QT_X)6GtJ*zQ!>tTHTolW#}GscJF_XCN(jKapP-V7pucwwC;YjV zV}I29hwJ|5T6F%%g|Lf(?40fYI_uvdwt;r0|C*P*_}Bh;65I1swz6ksV+;nfGzK%0l$4S!Th@fKG_)y_ zts*p*D3T>R35_LW?Hlj!?frd!e0|SxJkNdK*K?ldb=}u}KF9G~x3jSn5|kAL06@qJ zX^!TM{M$D_59hhqVeZF0O-A0_Q9CM6)B^^6}*U(SQ|+l91e!iFB;L+$D(2iW%NJChULYytKzPUP_TM{|M3lg1R zz4qp70Dw6j20e`S$asV6AsOHXC|w!{#2SZxMO50UI0K*rUJBmnSdgqneYVj*DWhm{c@&{LGyFAenM?knO4Vz_{`gO&%lO=$q40ik$uG&^gD zirgrX6-X|ij+NY+dZJTXq9XadZ1BR|(1_y5YyZb)Sm{HeUIJs_%+w4eapWWqs%Z;u*kaJ4#eaLDwLx00$fA(FwCPVpsg1dIjDtp|m)*OvC`lUTg9*PcI z=DgkN3z-AXrJJOc)a6BNp2L!YO3|^CEM7A$(#oXLR%7JF&DcX7BRY@Is7cm1rmdW0OnQOI-BP^pRxSw!oG8HM+;**g8-ATMfIDq zyAl9VZyhLO1ppFjSK~}nxq#Nx{80d?`nvZ_VUjfJEyrl)IhPJUGTpiRl?1a+IP;Z! zRh=|9c89si?gMp(qGmhB!uJW@-J4=6<4_0d;eGaqM>c0q`mku(D=mGY$sw^bm!>Jw zJKXnXzusGQRe;yDiC-M62#ea|5!(Z93VCntG6atE5cC{&QW@ir%*gQg?m8lIBt*CAWB zA0GZZx1i(1*FYL@;YKVjyhBUa>*iSmzrcxwwq?p8VypFZ57^^) z%rU0Qcg6jR*~(}C5*Bw$L>>@szBOhesHhwdwc2InhwMYD=iBESb&3))Q7Y5%#O5z! z{7=G}pwxu&c#(M9TkFkl#>ypN4N)T~f!qV={3NmCx_IMp-g|eTW@$OaCtHiMKk^qx z!S0;0BIX3yLF}&;iR%QWE4OAyAz1n9d0p?*3EqSiAIKD~QeYrrzeVc%)K0&zJEtb6 zB7Ow#7P=}F(TMaoDB~w{UZ!AgxeO!?b)WA;YFd+n<|n9Jn)hvy+hj+~;pM}jk_qp` zl3~eC$zjQp#SV@Ej>cV#;$03897P_Sb-Hl>nB!Bs(ISD5QV+U{6NyjgVAwz z?I+#xYTPz2ui=EAT#Y=Ia}}BAfGdA16Qd~g7K)$8m~~T~c&63-Zd-etezH1)3gTOr z3xb?S*&_mqy_jAJURe|8TDgSZJ|3Dg_?&sXO|ea=?YugVy0QA>Zr>8GlCY8^y&=6d zy&kjFS*h9SUUncQ%Op!JYY9e#tpo~G>Q$bsjI8VnZ2xBT0XNN<5?Fe-Bs?|HVYx*7 zk+&rMjor=eP&lHg!><=F3ef7IdsOXrxGF-Ta_p0nD`#@`HX;SNF_Ar}gVhy@7 z{j#w&pR4*dVj}NZWHua+){AaT=}ie`@ugU&tT1mo896bWmYhB@t-8-lt4$NTo&D|P zoHKT3z%p3R3rjLe>VqUp&c*BJU>(&|>a?P%9;B3<$b1RsUb1kD;1sMge7vm=( zXM@hVoL#TpZ~HV;CA(Xb*wj~ldZ?Op-W_HH$FH?b@ff`|;=U7d=UUDE8t@>zMk%a2 zEG=y3M)vyAHP&X_&)Q+eg4DbXpF7tpu5qsFOR|@q^7!#6@TT$J+o{VJ!}oFrWVex| z*=_+L7X@k}xlVdPZWX89@2!^JA^DC!OY)T5nr4d%YJc~h*sJaQgQ8*N`>Uv_mgoFY zLzezg6$%8IH0jj~5{C{$i#5KTEVHMbqphfQGsj%ky{lvA7TBhIhO8_Lc^7g_vgNlo|tN5 zWd|F&=(@y*>POxdOMPNI9sx45gY2~(FCbaVi!WTsh_tn+$&JZ9`MA8|xZ$mdET`mU z{ehzw5NkeHZI1=M)fTWtt1=Q~ZoN=iw01J@{=#hkeD1TlK~iQ?W*%CIkW!}lNwfT5 zxviI~YNF5>V}kfjF7evCl`*B&fW+Frrihz z?YQ8}=wFCMM|9r1hfuxjtdm@n)XXB4{y6mlCy7n=bY%OccAa**cvw2wjY(k^mi~Nn ztYTus*~YCabcsRi?fr7R{N3y{ayoyS#9VQyeR#au*R<>X)j6ROx%rrJIXB|*FSNTM zzDu#KWv%a8d33S5v!`30Gy4|q>g6qmWL%@|HE_zrxsBqqx9E-8kp$oUDWJ`PH$@iK7Lc1=(3P#_*46 z{frgVMw*}NN!My`M0>WeH0?X8+-;?K@yleK>s%G(2YAJ!Pr3X=3A_|;8AjP0Mm{`E zp2n}RW}OX7X+Nx|k9M#>TwL?5>k9bv^OJ__9Y!_#?TlPGNo8PPg>vQ6&!FX5>0rU{ zsD9UwIQqkln%>QCJJ&?MT`b#rjISdO{dBK$Z-3(=!WI^|mc5nq>%+C-PRka{E~R5i zF>zT63UWfS)eD|&iNgg?3dGyrg*p9VOb0xe9NZjPKhcia=$YHK8r@!7$|zc-vL6&Z zgf%KPDpa*G;Pdx4O4|A^uq)V=gN@Y+X$LnC%CPTM2AS+iE*TW5z z_i+9yWoQ-{nmyTz7L29h0aH)12OeZa#Cqe=c&sPmTr=Jf0C)}(956JDH426!6E(2g z7!5j+!eIk|p)s9;#rffBAP>AZfdmJ$U)F*_1W!2FS=Uo`wa{iTAk+gGBpQ~kP6|-^tzeE1z)=Yl z8i7m#Z7X6u$bmFCm{aV3c0r{4D@zLar&FAsLFiZtL`y?+dsn}K*4F<<}y+9Y90l z|6A|>DIDOypx_~Bd;mF+isPJ}m(q4B3JgKTV`*fn1DWjqyODN2WEwfZhfD!MwKa4= z2d%L<0%<#P=x>U(HOz_>K*N%7cq?-_m?NS=Ab7&`5fF_!`NYm zaFv}1h_#0_hsBAxB%zfAB08S0j{5bP;rA;I|l|> MS=gADnz>*8AMz7TL;wH) literal 0 HcmV?d00001 diff --git a/assets/icons/Common/Round_loader_8x8/frame_rate b/assets/icons/Common/Round_loader_8x8/frame_rate new file mode 100644 index 00000000000..d8263ee9860 --- /dev/null +++ b/assets/icons/Common/Round_loader_8x8/frame_rate @@ -0,0 +1 @@ +2 \ No newline at end of file From b9a766d909059fe6e6b007fe1f37290bea6724ca Mon Sep 17 00:00:00 2001 From: SG Date: Thu, 15 Sep 2022 02:11:38 +1000 Subject: [PATCH 053/824] [FL-2627] Flipper applications: SDK, build and debug system (#1387) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Added support for running applications from SD card (FAPs - Flipper Application Packages) * Added plugin_dist target for fbt to build FAPs * All apps of type FlipperAppType.EXTERNAL and FlipperAppType.PLUGIN are built as FAPs by default * Updated VSCode configuration for new fbt features - re-deploy stock configuration to use them * Added debugging support for FAPs with fbt debug & VSCode * Added public firmware API with automated versioning Co-authored-by: hedger Co-authored-by: SG Co-authored-by: あく --- .clang-format | 151 +- .gitmodules | 3 + .vscode/example/launch.json | 13 +- .vscode/example/settings.json | 3 + .vscode/example/tasks.json | 30 + ReadMe.md | 4 + SConstruct | 28 +- applications/ReadMe.md | 90 +- applications/bt/application.fam | 66 - .../{ => debug}/accessor/accessor.cpp | 0 .../{ => debug}/accessor/accessor_app.cpp | 0 .../{ => debug}/accessor/accessor_app.h | 0 .../{ => debug}/accessor/accessor_event.h | 0 .../accessor/accessor_view_manager.cpp | 0 .../accessor/accessor_view_manager.h | 0 .../{ => debug}/accessor/application.fam | 1 + .../{ => debug}/accessor/helpers/wiegand.cpp | 0 .../{ => debug}/accessor/helpers/wiegand.h | 0 .../accessor/scene/accessor_scene_generic.h | 0 .../accessor/scene/accessor_scene_start.cpp | 0 .../accessor/scene/accessor_scene_start.h | 0 applications/debug/application.fam | 16 + .../debug/battery_test_app/application.fam | 14 + .../battery_test_app/battery_test_app.c | 10 +- .../battery_test_app/battery_test_app.h | 5 +- applications/debug/blink_test/application.fam | 11 + .../blink_test}/blink_test.c | 0 .../debug/bt_debug_app/application.fam | 18 + .../{bt => debug}/bt_debug_app/bt_debug_app.c | 0 .../{bt => debug}/bt_debug_app/bt_debug_app.h | 2 +- .../bt_debug_app/views/bt_carrier_test.c | 0 .../bt_debug_app/views/bt_carrier_test.h | 0 .../bt_debug_app/views/bt_packet_test.c | 0 .../bt_debug_app/views/bt_packet_test.h | 0 .../bt_debug_app/views/bt_test.c | 0 .../bt_debug_app/views/bt_test.h | 0 .../bt_debug_app/views/bt_test_types.h | 0 .../debug/display_test/application.fam | 11 + .../display_test/display_test.c | 0 .../display_test/display_test.h | 0 .../display_test/view_display_test.c | 0 .../display_test/view_display_test.h | 0 .../debug/file_browser_test/application.fam | 11 + .../file_browser_test/file_browser_app.c | 0 .../file_browser_test/file_browser_app_i.h | 0 .../scenes/file_browser_scene.c | 0 .../scenes/file_browser_scene.h | 0 .../scenes/file_browser_scene_browser.c | 0 .../scenes/file_browser_scene_config.h | 0 .../scenes/file_browser_scene_result.c | 0 .../scenes/file_browser_scene_start.c | 0 .../debug/keypad_test/application.fam | 11 + .../keypad_test}/keypad_test.c | 0 .../{ => debug}/lfrfid_debug/application.fam | 5 +- .../{ => debug}/lfrfid_debug/lfrfid_debug.c | 0 .../{ => debug}/lfrfid_debug/lfrfid_debug_i.h | 5 +- .../scenes/lfrfid_debug_app_scene_start.c | 0 .../scenes/lfrfid_debug_app_scene_tune.c | 0 .../lfrfid_debug/scenes/lfrfid_debug_scene.c | 0 .../lfrfid_debug/scenes/lfrfid_debug_scene.h | 0 .../scenes/lfrfid_debug_scene_config.h | 0 .../views/lfrfid_debug_view_tune.c | 0 .../views/lfrfid_debug_view_tune.h | 0 .../debug/text_box_test/application.fam | 11 + .../text_box_test}/text_box_test.c | 0 applications/debug/uart_echo/application.fam | 11 + .../uart_echo}/uart_echo.c | 0 .../{ => debug}/unit_tests/application.fam | 2 +- .../flipper_format_string_test.c | 0 .../flipper_format/flipper_format_test.c | 0 .../unit_tests/furi/furi_memmgr_test.c | 0 .../unit_tests/furi/furi_pubsub_test.c | 0 .../unit_tests/furi/furi_record_test.c | 0 .../{ => debug}/unit_tests/furi/furi_test.c | 0 .../unit_tests/furi/furi_valuemutex_test.c | 0 .../unit_tests/infrared/infrared_test.c | 0 .../unit_tests/lfrfid/bit_lib_test.c | 0 .../unit_tests/lfrfid/lfrfid_protocols.c | 0 applications/{ => debug}/unit_tests/minunit.h | 0 .../{ => debug}/unit_tests/minunit_vars.h | 0 .../{ => debug}/unit_tests/minunit_vars_ex.h | 0 .../{ => debug}/unit_tests/nfc/nfc_test.c | 2 +- .../protocol_dict/protocol_dict_test.c | 0 .../{ => debug}/unit_tests/rpc/rpc_test.c | 0 .../unit_tests/storage/dirwalk_test.c | 0 .../unit_tests/storage/storage_test.c | 0 .../unit_tests/stream/stream_test.c | 0 .../unit_tests/subghz/subghz_test.c | 0 .../{ => debug}/unit_tests/test_index.c | 0 .../unit_tests/varint/varint_test.c | 0 applications/debug/usb_mouse/application.fam | 11 + .../usb_mouse}/usb_mouse.c | 0 applications/debug/usb_test/application.fam | 11 + .../usb_test}/usb_test.c | 0 applications/debug/vibro_test/application.fam | 11 + .../vibro_test}/vibro_test.c | 0 applications/debug_tools/application.fam | 115 - applications/extapps.scons | 58 - applications/gui/application.fam | 13 - applications/gui/modules/dialog.c | 131 - applications/gui/modules/dialog.h | 95 - applications/main/application.fam | 17 + .../{ => main}/archive/application.fam | 0 applications/{ => main}/archive/archive.c | 0 applications/{ => main}/archive/archive.h | 0 applications/{ => main}/archive/archive_i.h | 0 .../{ => main}/archive/helpers/archive_apps.c | 0 .../{ => main}/archive/helpers/archive_apps.h | 0 .../archive/helpers/archive_browser.c | 2 +- .../archive/helpers/archive_browser.h | 0 .../archive/helpers/archive_favorites.c | 0 .../archive/helpers/archive_favorites.h | 0 .../archive/helpers/archive_files.c | 0 .../archive/helpers/archive_files.h | 0 .../{ => main}/archive/scenes/archive_scene.c | 0 .../{ => main}/archive/scenes/archive_scene.h | 0 .../archive/scenes/archive_scene_browser.c | 0 .../archive/scenes/archive_scene_config.h | 0 .../archive/scenes/archive_scene_delete.c | 0 .../archive/scenes/archive_scene_rename.c | 0 .../archive/views/archive_browser_view.c | 4 +- .../archive/views/archive_browser_view.h | 0 .../{ => main}/bad_usb/application.fam | 0 applications/{ => main}/bad_usb/bad_usb_app.c | 0 applications/{ => main}/bad_usb/bad_usb_app.h | 0 .../{ => main}/bad_usb/bad_usb_app_i.h | 0 .../{ => main}/bad_usb/bad_usb_script.c | 0 .../{ => main}/bad_usb/bad_usb_script.h | 0 .../{ => main}/bad_usb/scenes/bad_usb_scene.c | 0 .../{ => main}/bad_usb/scenes/bad_usb_scene.h | 0 .../bad_usb/scenes/bad_usb_scene_config.h | 0 .../bad_usb/scenes/bad_usb_scene_error.c | 0 .../scenes/bad_usb_scene_file_select.c | 11 +- .../bad_usb/scenes/bad_usb_scene_work.c | 0 .../{ => main}/bad_usb/views/bad_usb_view.c | 0 .../{ => main}/bad_usb/views/bad_usb_view.h | 0 applications/main/fap_loader/application.fam | 13 + .../main/fap_loader/elf_cpp/compilesort.hpp | 111 + .../main/fap_loader/elf_cpp/elf_hashtable.cpp | 48 + .../main/fap_loader/elf_cpp/elf_hashtable.h | 14 + .../elf_cpp/elf_hashtable_checks.hpp | 18 + .../fap_loader/elf_cpp/elf_hashtable_entry.h | 41 + applications/main/fap_loader/fap_loader_app.c | 176 + applications/{ => main}/gpio/application.fam | 0 applications/{ => main}/gpio/gpio_app.c | 0 applications/{ => main}/gpio/gpio_app.h | 0 applications/{ => main}/gpio/gpio_app_i.h | 0 .../{ => main}/gpio/gpio_custom_event.h | 0 applications/{ => main}/gpio/gpio_item.c | 0 applications/{ => main}/gpio/gpio_item.h | 0 .../{ => main}/gpio/scenes/gpio_scene.c | 0 .../{ => main}/gpio/scenes/gpio_scene.h | 0 .../gpio/scenes/gpio_scene_config.h | 0 .../{ => main}/gpio/scenes/gpio_scene_start.c | 0 .../{ => main}/gpio/scenes/gpio_scene_test.c | 0 .../gpio/scenes/gpio_scene_usb_uart.c | 0 .../scenes/gpio_scene_usb_uart_close_rpc.c | 0 .../gpio/scenes/gpio_scene_usb_uart_config.c | 0 .../{ => main}/gpio/usb_uart_bridge.c | 2 +- .../{ => main}/gpio/usb_uart_bridge.h | 0 .../{ => main}/gpio/views/gpio_test.c | 0 .../{ => main}/gpio/views/gpio_test.h | 0 .../{ => main}/gpio/views/gpio_usb_uart.c | 0 .../{ => main}/gpio/views/gpio_usb_uart.h | 0 .../{ => main}/ibutton/application.fam | 0 applications/{ => main}/ibutton/ibutton.c | 11 +- applications/{ => main}/ibutton/ibutton.h | 0 applications/{ => main}/ibutton/ibutton_cli.c | 0 .../{ => main}/ibutton/ibutton_custom_event.h | 0 applications/{ => main}/ibutton/ibutton_i.h | 0 .../{ => main}/ibutton/scenes/ibutton_scene.c | 0 .../{ => main}/ibutton/scenes/ibutton_scene.h | 0 .../ibutton/scenes/ibutton_scene_add_type.c | 0 .../ibutton/scenes/ibutton_scene_add_value.c | 0 .../ibutton/scenes/ibutton_scene_config.h | 0 .../scenes/ibutton_scene_delete_confirm.c | 0 .../scenes/ibutton_scene_delete_success.c | 0 .../ibutton/scenes/ibutton_scene_emulate.c | 0 .../scenes/ibutton_scene_exit_confirm.c | 0 .../ibutton/scenes/ibutton_scene_info.c | 0 .../ibutton/scenes/ibutton_scene_read.c | 0 .../scenes/ibutton_scene_read_crc_error.c | 0 .../scenes/ibutton_scene_read_key_menu.c | 0 .../scenes/ibutton_scene_read_not_key_error.c | 0 .../scenes/ibutton_scene_read_success.c | 0 .../scenes/ibutton_scene_retry_confirm.c | 0 .../ibutton/scenes/ibutton_scene_rpc.c | 0 .../ibutton/scenes/ibutton_scene_save_name.c | 0 .../scenes/ibutton_scene_save_success.c | 0 .../scenes/ibutton_scene_saved_key_menu.c | 0 .../ibutton/scenes/ibutton_scene_select_key.c | 0 .../ibutton/scenes/ibutton_scene_start.c | 0 .../ibutton/scenes/ibutton_scene_write.c | 0 .../scenes/ibutton_scene_write_success.c | 0 .../{ => main}/infrared/application.fam | 0 applications/{ => main}/infrared/infrared.c | 0 applications/{ => main}/infrared/infrared.h | 0 .../infrared/infrared_brute_force.c | 0 .../infrared/infrared_brute_force.h | 0 .../{ => main}/infrared/infrared_cli.c | 0 .../infrared/infrared_custom_event.h | 0 applications/{ => main}/infrared/infrared_i.h | 0 .../{ => main}/infrared/infrared_remote.c | 0 .../{ => main}/infrared/infrared_remote.h | 0 .../infrared/infrared_remote_button.c | 0 .../infrared/infrared_remote_button.h | 0 .../{ => main}/infrared/infrared_signal.c | 0 .../{ => main}/infrared/infrared_signal.h | 0 .../common/infrared_scene_universal_common.c | 0 .../common/infrared_scene_universal_common.h | 0 .../infrared/scenes/infrared_scene.c | 0 .../infrared/scenes/infrared_scene.h | 0 .../infrared/scenes/infrared_scene_ask_back.c | 0 .../scenes/infrared_scene_ask_retry.c | 0 .../infrared/scenes/infrared_scene_config.h | 0 .../infrared/scenes/infrared_scene_debug.c | 0 .../infrared/scenes/infrared_scene_edit.c | 0 .../infrared_scene_edit_button_select.c | 0 .../scenes/infrared_scene_edit_delete.c | 0 .../scenes/infrared_scene_edit_delete_done.c | 0 .../scenes/infrared_scene_edit_rename.c | 0 .../scenes/infrared_scene_edit_rename_done.c | 0 .../scenes/infrared_scene_error_databases.c | 0 .../infrared/scenes/infrared_scene_learn.c | 0 .../scenes/infrared_scene_learn_done.c | 0 .../scenes/infrared_scene_learn_enter_name.c | 0 .../scenes/infrared_scene_learn_success.c | 0 .../infrared/scenes/infrared_scene_remote.c | 0 .../scenes/infrared_scene_remote_list.c | 11 +- .../infrared/scenes/infrared_scene_rpc.c | 0 .../infrared/scenes/infrared_scene_start.c | 0 .../scenes/infrared_scene_universal.c | 0 .../scenes/infrared_scene_universal_tv.c | 0 .../infrared/views/infrared_debug_view.c | 0 .../infrared/views/infrared_debug_view.h | 0 .../infrared/views/infrared_progress_view.c | 0 .../infrared/views/infrared_progress_view.h | 0 .../{ => main}/lfrfid/application.fam | 1 - applications/{ => main}/lfrfid/lfrfid.c | 14 +- applications/{ => main}/lfrfid/lfrfid_cli.c | 0 applications/{ => main}/lfrfid/lfrfid_i.h | 0 .../{ => main}/lfrfid/scenes/lfrfid_scene.c | 0 .../{ => main}/lfrfid/scenes/lfrfid_scene.h | 0 .../lfrfid/scenes/lfrfid_scene_config.h | 0 .../scenes/lfrfid_scene_delete_confirm.c | 0 .../scenes/lfrfid_scene_delete_success.c | 0 .../lfrfid/scenes/lfrfid_scene_emulate.c | 0 .../lfrfid/scenes/lfrfid_scene_exit_confirm.c | 0 .../scenes/lfrfid_scene_extra_actions.c | 0 .../lfrfid/scenes/lfrfid_scene_raw_info.c | 0 .../lfrfid/scenes/lfrfid_scene_raw_name.c | 0 .../lfrfid/scenes/lfrfid_scene_raw_read.c | 0 .../lfrfid/scenes/lfrfid_scene_raw_success.c | 0 .../lfrfid/scenes/lfrfid_scene_read.c | 0 .../scenes/lfrfid_scene_read_key_menu.c | 0 .../lfrfid/scenes/lfrfid_scene_read_success.c | 0 .../scenes/lfrfid_scene_retry_confirm.c | 0 .../lfrfid/scenes/lfrfid_scene_rpc.c | 0 .../lfrfid/scenes/lfrfid_scene_save_data.c | 0 .../lfrfid/scenes/lfrfid_scene_save_name.c | 0 .../lfrfid/scenes/lfrfid_scene_save_success.c | 0 .../lfrfid/scenes/lfrfid_scene_save_type.c | 0 .../lfrfid/scenes/lfrfid_scene_saved_info.c | 0 .../scenes/lfrfid_scene_saved_key_menu.c | 0 .../lfrfid/scenes/lfrfid_scene_select_key.c | 0 .../lfrfid/scenes/lfrfid_scene_start.c | 0 .../lfrfid/scenes/lfrfid_scene_write.c | 0 .../scenes/lfrfid_scene_write_success.c | 0 .../lfrfid/views/lfrfid_view_read.c | 0 .../lfrfid/views/lfrfid_view_read.h | 0 applications/{ => main}/nfc/application.fam | 0 .../{ => main}/nfc/helpers/nfc_custom_event.h | 0 .../{ => main}/nfc/helpers/nfc_emv_parser.c | 0 .../{ => main}/nfc/helpers/nfc_emv_parser.h | 0 .../{ => main}/nfc/helpers/nfc_generators.c | 0 .../{ => main}/nfc/helpers/nfc_generators.h | 0 applications/{ => main}/nfc/nfc.c | 0 applications/{ => main}/nfc/nfc.h | 0 applications/{ => main}/nfc/nfc_cli.c | 0 applications/{ => main}/nfc/nfc_i.h | 0 .../{ => main}/nfc/scenes/nfc_scene.c | 0 .../{ => main}/nfc/scenes/nfc_scene.h | 0 .../{ => main}/nfc/scenes/nfc_scene_config.h | 0 .../{ => main}/nfc/scenes/nfc_scene_debug.c | 0 .../{ => main}/nfc/scenes/nfc_scene_delete.c | 0 .../nfc/scenes/nfc_scene_delete_success.c | 0 .../nfc/scenes/nfc_scene_detect_reader.c | 0 .../nfc/scenes/nfc_scene_device_info.c | 0 .../nfc/scenes/nfc_scene_dict_not_found.c | 0 .../scenes/nfc_scene_emulate_apdu_sequence.c | 0 .../nfc/scenes/nfc_scene_emulate_uid.c | 0 .../nfc/scenes/nfc_scene_emv_menu.c | 0 .../nfc/scenes/nfc_scene_emv_read_success.c | 0 .../nfc/scenes/nfc_scene_exit_confirm.c | 0 .../nfc/scenes/nfc_scene_extra_actions.c | 0 .../{ => main}/nfc/scenes/nfc_scene_field.c | 0 .../nfc/scenes/nfc_scene_file_select.c | 0 .../nfc/scenes/nfc_scene_generate_info.c | 0 .../scenes/nfc_scene_mf_classic_dict_attack.c | 0 .../nfc/scenes/nfc_scene_mf_classic_emulate.c | 0 .../nfc/scenes/nfc_scene_mf_classic_keys.c | 0 .../scenes/nfc_scene_mf_classic_keys_add.c | 0 .../nfc/scenes/nfc_scene_mf_classic_menu.c | 0 .../nfc_scene_mf_classic_read_success.c | 0 .../nfc/scenes/nfc_scene_mf_desfire_app.c | 0 .../nfc/scenes/nfc_scene_mf_desfire_data.c | 0 .../nfc/scenes/nfc_scene_mf_desfire_menu.c | 0 .../nfc_scene_mf_desfire_read_success.c | 0 .../nfc/scenes/nfc_scene_mf_ultralight_data.c | 0 .../scenes/nfc_scene_mf_ultralight_emulate.c | 0 .../nfc_scene_mf_ultralight_key_input.c | 0 .../nfc/scenes/nfc_scene_mf_ultralight_menu.c | 0 .../nfc_scene_mf_ultralight_read_auth.c | 0 ...nfc_scene_mf_ultralight_read_auth_result.c | 0 .../nfc_scene_mf_ultralight_read_success.c | 0 .../nfc_scene_mf_ultralight_unlock_menu.c | 0 .../nfc_scene_mf_ultralight_unlock_warn.c | 0 .../nfc/scenes/nfc_scene_mfkey_complete.c | 0 .../nfc/scenes/nfc_scene_mfkey_nonces_info.c | 0 .../nfc/scenes/nfc_scene_nfc_data_info.c | 0 .../nfc/scenes/nfc_scene_nfca_menu.c | 0 .../nfc/scenes/nfc_scene_nfca_read_success.c | 0 .../{ => main}/nfc/scenes/nfc_scene_read.c | 0 .../nfc/scenes/nfc_scene_read_card_success.c | 0 .../nfc/scenes/nfc_scene_restore_original.c | 0 .../nfc_scene_restore_original_confirm.c | 0 .../nfc/scenes/nfc_scene_retry_confirm.c | 0 .../{ => main}/nfc/scenes/nfc_scene_rpc.c | 0 .../nfc/scenes/nfc_scene_save_name.c | 0 .../nfc/scenes/nfc_scene_save_success.c | 0 .../nfc/scenes/nfc_scene_saved_menu.c | 0 .../nfc/scenes/nfc_scene_set_atqa.c | 0 .../{ => main}/nfc/scenes/nfc_scene_set_sak.c | 0 .../nfc/scenes/nfc_scene_set_type.c | 0 .../{ => main}/nfc/scenes/nfc_scene_set_uid.c | 0 .../{ => main}/nfc/scenes/nfc_scene_start.c | 0 .../{ => main}/nfc/views/detect_reader.c | 0 .../{ => main}/nfc/views/detect_reader.h | 0 .../{ => main}/nfc/views/dict_attack.c | 0 .../{ => main}/nfc/views/dict_attack.h | 0 .../{ => main}/subghz/application.fam | 0 .../{ => main}/subghz/helpers/subghz_chat.c | 0 .../{ => main}/subghz/helpers/subghz_chat.h | 0 .../subghz/helpers/subghz_custom_event.h | 0 .../subghz_frequency_analyzer_worker.c | 0 .../subghz_frequency_analyzer_worker.h | 0 .../subghz/helpers/subghz_testing.c | 0 .../subghz/helpers/subghz_testing.h | 0 .../{ => main}/subghz/helpers/subghz_types.h | 0 .../{ => main}/subghz/scenes/subghz_scene.c | 0 .../{ => main}/subghz/scenes/subghz_scene.h | 0 .../subghz/scenes/subghz_scene_config.h | 0 .../subghz/scenes/subghz_scene_delete.c | 0 .../subghz/scenes/subghz_scene_delete_raw.c | 0 .../scenes/subghz_scene_delete_success.c | 0 .../scenes/subghz_scene_frequency_analyzer.c | 0 .../subghz/scenes/subghz_scene_more_raw.c | 0 .../subghz/scenes/subghz_scene_need_saving.c | 0 .../subghz/scenes/subghz_scene_read_raw.c | 0 .../subghz/scenes/subghz_scene_receiver.c | 0 .../scenes/subghz_scene_receiver_config.c | 0 .../scenes/subghz_scene_receiver_info.c | 0 .../subghz/scenes/subghz_scene_rpc.c | 0 .../subghz/scenes/subghz_scene_save_name.c | 0 .../subghz/scenes/subghz_scene_save_success.c | 0 .../subghz/scenes/subghz_scene_saved.c | 0 .../subghz/scenes/subghz_scene_saved_menu.c | 0 .../subghz/scenes/subghz_scene_set_type.c | 0 .../subghz/scenes/subghz_scene_show_error.c | 0 .../scenes/subghz_scene_show_error_sub.c | 0 .../subghz/scenes/subghz_scene_show_only_rx.c | 0 .../subghz/scenes/subghz_scene_start.c | 0 .../subghz/scenes/subghz_scene_test.c | 0 .../subghz/scenes/subghz_scene_test_carrier.c | 0 .../subghz/scenes/subghz_scene_test_packet.c | 0 .../subghz/scenes/subghz_scene_test_static.c | 0 .../subghz/scenes/subghz_scene_transmitter.c | 0 applications/{ => main}/subghz/subghz.c | 0 applications/{ => main}/subghz/subghz.h | 0 applications/{ => main}/subghz/subghz_cli.c | 2 + applications/{ => main}/subghz/subghz_cli.h | 0 .../{ => main}/subghz/subghz_history.c | 0 .../{ => main}/subghz/subghz_history.h | 0 applications/{ => main}/subghz/subghz_i.c | 13 +- applications/{ => main}/subghz/subghz_i.h | 0 .../{ => main}/subghz/subghz_setting.c | 0 .../{ => main}/subghz/subghz_setting.h | 0 .../{ => main}/subghz/views/receiver.c | 0 .../{ => main}/subghz/views/receiver.h | 0 .../subghz/views/subghz_frequency_analyzer.c | 0 .../subghz/views/subghz_frequency_analyzer.h | 0 .../{ => main}/subghz/views/subghz_read_raw.c | 0 .../{ => main}/subghz/views/subghz_read_raw.h | 0 .../subghz/views/subghz_test_carrier.c | 0 .../subghz/views/subghz_test_carrier.h | 0 .../subghz/views/subghz_test_packet.c | 0 .../subghz/views/subghz_test_packet.h | 0 .../subghz/views/subghz_test_static.c | 0 .../subghz/views/subghz_test_static.h | 0 .../{ => main}/subghz/views/transmitter.c | 0 .../{ => main}/subghz/views/transmitter.h | 0 applications/{ => main}/u2f/application.fam | 0 .../{ => main}/u2f/scenes/u2f_scene.c | 0 .../{ => main}/u2f/scenes/u2f_scene.h | 0 .../{ => main}/u2f/scenes/u2f_scene_config.h | 0 .../{ => main}/u2f/scenes/u2f_scene_error.c | 0 .../{ => main}/u2f/scenes/u2f_scene_main.c | 0 applications/{ => main}/u2f/u2f.c | 0 applications/{ => main}/u2f/u2f.h | 0 applications/{ => main}/u2f/u2f_app.c | 0 applications/{ => main}/u2f/u2f_app.h | 0 applications/{ => main}/u2f/u2f_app_i.h | 0 applications/{ => main}/u2f/u2f_data.c | 0 applications/{ => main}/u2f/u2f_data.h | 0 applications/{ => main}/u2f/u2f_hid.c | 0 applications/{ => main}/u2f/u2f_hid.h | 0 applications/{ => main}/u2f/views/u2f_view.c | 0 applications/{ => main}/u2f/views/u2f_view.h | 0 applications/meta/application.fam | 41 - applications/picopass/application.fam | 11 - applications/plugins/application.fam | 10 + .../plugins/bt_hid_app/application.fam | 15 + .../{bt => plugins}/bt_hid_app/bt_hid.c | 2 +- .../{bt => plugins}/bt_hid_app/bt_hid.h | 2 +- .../plugins/bt_hid_app/bt_remote_10px.png | Bin 0 -> 151 bytes .../bt_hid_app/views/bt_hid_keyboard.c | 0 .../bt_hid_app/views/bt_hid_keyboard.h | 0 .../bt_hid_app/views/bt_hid_keynote.c | 0 .../bt_hid_app/views/bt_hid_keynote.h | 0 .../bt_hid_app/views/bt_hid_media.c | 0 .../bt_hid_app/views/bt_hid_media.h | 0 .../bt_hid_app/views/bt_hid_mouse.c | 0 .../bt_hid_app/views/bt_hid_mouse.h | 0 .../music_player/application.fam | 2 + .../{ => plugins}/music_player/music_player.c | 14 +- .../music_player/music_player_cli.c | 0 .../music_player/music_player_worker.c | 0 .../music_player/music_player_worker.h | 0 applications/plugins/picopass/application.fam | 17 + .../picopass}/loclass/optimized_cipher.c | 121 +- .../picopass}/loclass/optimized_cipher.h | 22 +- .../picopass}/loclass/optimized_cipherutils.c | 29 +- .../picopass}/loclass/optimized_cipherutils.h | 22 +- .../picopass}/loclass/optimized_elite.c | 26 +- .../picopass}/loclass/optimized_elite.h | 4 +- .../picopass}/loclass/optimized_ikeys.c | 51 +- .../picopass}/loclass/optimized_ikeys.h | 2 +- .../{ => plugins}/picopass/picopass.c | 0 .../{ => plugins}/picopass/picopass.h | 0 .../{ => plugins}/picopass/picopass_device.c | 13 +- .../{ => plugins}/picopass/picopass_device.h | 9 +- .../{ => plugins}/picopass/picopass_i.h | 4 +- .../{ => plugins}/picopass/picopass_worker.c | 10 +- .../{ => plugins}/picopass/picopass_worker.h | 0 .../picopass/picopass_worker_i.h | 0 .../plugins/picopass}/rfal_picopass.c | 121 +- applications/plugins/picopass/rfal_picopass.h | 48 + .../picopass/scenes/picopass_scene.c | 0 .../picopass/scenes/picopass_scene.h | 0 .../scenes/picopass_scene_card_menu.c | 0 .../picopass/scenes/picopass_scene_config.h | 0 .../picopass/scenes/picopass_scene_delete.c | 0 .../scenes/picopass_scene_delete_success.c | 0 .../scenes/picopass_scene_device_info.c | 0 .../scenes/picopass_scene_file_select.c | 2 +- .../scenes/picopass_scene_read_card.c | 0 .../scenes/picopass_scene_read_card_success.c | 0 .../scenes/picopass_scene_save_name.c | 0 .../scenes/picopass_scene_save_success.c | 0 .../scenes/picopass_scene_saved_menu.c | 0 .../picopass/scenes/picopass_scene_start.c | 0 .../scenes/picopass_scene_write_card.c | 0 .../picopass_scene_write_card_success.c | 0 .../{ => plugins}/snake_game/application.fam | 3 +- .../plugins/snake_game/snake_10px.png | Bin 0 -> 158 bytes .../{ => plugins}/snake_game/snake_game.c | 0 applications/power/application.fam | 53 - applications/power/power_cli.h | 3 - applications/services/application.fam | 13 + applications/{ => services}/applications.h | 0 applications/services/bt/application.fam | 25 + applications/{ => services}/bt/bt_cli.c | 2 +- .../{ => services}/bt/bt_service/bt.c | 0 .../{ => services}/bt/bt_service/bt.h | 0 .../{ => services}/bt/bt_service/bt_api.c | 0 .../{ => services}/bt/bt_service/bt_i.h | 6 +- .../bt/bt_service/bt_keys_filename.h | 0 .../bt/bt_service/bt_keys_storage.c | 0 .../bt/bt_service/bt_keys_storage.h | 0 applications/{ => services}/bt/bt_settings.c | 0 applications/{ => services}/bt/bt_settings.h | 0 .../{ => services}/bt/bt_settings_filename.h | 0 .../{ => services}/cli/application.fam | 1 + applications/{ => services}/cli/cli.c | 0 applications/{ => services}/cli/cli.h | 0 .../{ => services}/cli/cli_command_gpio.c | 0 .../{ => services}/cli/cli_command_gpio.h | 0 .../{ => services}/cli/cli_commands.c | 0 .../{ => services}/cli/cli_commands.h | 0 applications/{ => services}/cli/cli_i.h | 12 +- applications/{ => services}/cli/cli_vcp.c | 2 +- applications/{ => services}/cli/cli_vcp.h | 4 +- .../{ => services}/crypto/application.fam | 0 .../{ => services}/crypto/crypto_cli.c | 0 .../desktop/animations/animation_manager.c | 0 .../desktop/animations/animation_manager.h | 0 .../desktop/animations/animation_storage.c | 0 .../desktop/animations/animation_storage.h | 0 .../desktop/animations/animation_storage_i.h | 0 .../animations/views/bubble_animation_view.c | 0 .../animations/views/bubble_animation_view.h | 0 .../views/one_shot_animation_view.c | 0 .../views/one_shot_animation_view.h | 0 .../{ => services}/desktop/application.fam | 13 - applications/{ => services}/desktop/desktop.c | 0 applications/{ => services}/desktop/desktop.h | 0 .../{ => services}/desktop/desktop_i.h | 2 +- .../desktop}/desktop_settings.h | 0 .../desktop}/desktop_settings_filename.h | 0 .../{ => services}/desktop/helpers/pin_lock.c | 1 + .../{ => services}/desktop/helpers/pin_lock.h | 2 +- .../desktop/helpers/slideshow.c | 0 .../desktop/helpers/slideshow.h | 0 .../desktop/helpers/slideshow_filename.h | 0 .../desktop/scenes/desktop_scene.c | 0 .../desktop/scenes/desktop_scene.h | 0 .../desktop/scenes/desktop_scene_config.h | 0 .../desktop/scenes/desktop_scene_debug.c | 0 .../desktop/scenes/desktop_scene_fault.c | 0 .../scenes/desktop_scene_hw_mismatch.c | 0 .../desktop/scenes/desktop_scene_i.h | 0 .../desktop/scenes/desktop_scene_lock_menu.c | 2 +- .../desktop/scenes/desktop_scene_locked.c | 0 .../desktop/scenes/desktop_scene_main.c | 0 .../desktop/scenes/desktop_scene_pin_input.c | 0 .../scenes/desktop_scene_pin_timeout.c | 0 .../desktop/scenes/desktop_scene_slideshow.c | 0 .../desktop/views/desktop_events.h | 0 .../desktop/views/desktop_view_debug.c | 0 .../desktop/views/desktop_view_debug.h | 0 .../desktop/views/desktop_view_lock_menu.c | 0 .../desktop/views/desktop_view_lock_menu.h | 0 .../desktop/views/desktop_view_locked.c | 2 +- .../desktop/views/desktop_view_locked.h | 2 +- .../desktop/views/desktop_view_main.c | 0 .../desktop/views/desktop_view_main.h | 0 .../desktop/views/desktop_view_pin_input.c | 2 +- .../desktop/views/desktop_view_pin_input.h | 2 +- .../views/desktop_view_pin_setup_done.c | 0 .../views/desktop_view_pin_setup_done.h | 0 .../desktop/views/desktop_view_pin_timeout.c | 0 .../desktop/views/desktop_view_pin_timeout.h | 0 .../desktop/views/desktop_view_slideshow.c | 0 .../desktop/views/desktop_view_slideshow.h | 0 .../{ => services}/dialogs/application.fam | 1 + applications/{ => services}/dialogs/dialogs.c | 12 + applications/{ => services}/dialogs/dialogs.h | 41 +- .../{ => services}/dialogs/dialogs_api.c | 16 +- .../{ => services}/dialogs/dialogs_api_lock.h | 0 .../{ => services}/dialogs/dialogs_i.h | 1 + .../{ => services}/dialogs/dialogs_message.h | 2 + .../dialogs/dialogs_module_file_browser.c | 1 + .../dialogs/dialogs_module_file_browser.h | 0 .../dialogs/dialogs_module_message.c | 0 .../dialogs/dialogs_module_message.h | 0 .../{ => services}/dialogs/view_holder.c | 0 .../{ => services}/dialogs/view_holder.h | 0 applications/services/dolphin/application.fam | 10 + applications/{ => services}/dolphin/dolphin.c | 0 applications/{ => services}/dolphin/dolphin.h | 0 .../{ => services}/dolphin/dolphin_i.h | 0 .../dolphin/helpers/dolphin_deed.c | 0 .../dolphin/helpers/dolphin_deed.h | 0 .../dolphin/helpers/dolphin_state.c | 0 .../dolphin/helpers/dolphin_state.h | 0 .../dolphin/helpers/dolphin_state_filename.h | 0 applications/services/gui/application.fam | 36 + applications/{ => services}/gui/canvas.c | 0 applications/{ => services}/gui/canvas.h | 0 applications/{ => services}/gui/canvas_i.h | 0 applications/{ => services}/gui/elements.c | 0 applications/{ => services}/gui/elements.h | 0 applications/{ => services}/gui/gui.c | 0 applications/{ => services}/gui/gui.h | 0 applications/{ => services}/gui/gui_i.h | 0 applications/{ => services}/gui/icon.c | 0 applications/{ => services}/gui/icon.h | 0 .../{ => services}/gui/icon_animation.c | 0 .../{ => services}/gui/icon_animation.h | 0 .../{ => services}/gui/icon_animation_i.h | 0 applications/{ => services}/gui/icon_i.h | 0 .../{ => services}/gui/modules/button_menu.c | 0 .../{ => services}/gui/modules/button_menu.h | 0 .../{ => services}/gui/modules/button_panel.c | 0 .../{ => services}/gui/modules/button_panel.h | 0 .../{ => services}/gui/modules/byte_input.c | 0 .../{ => services}/gui/modules/byte_input.h | 0 .../{ => services}/gui/modules/dialog_ex.c | 0 .../{ => services}/gui/modules/dialog_ex.h | 0 .../{ => services}/gui/modules/empty_screen.c | 0 .../{ => services}/gui/modules/empty_screen.h | 0 .../{ => services}/gui/modules/file_browser.c | 87 +- .../{ => services}/gui/modules/file_browser.h | 8 + .../gui/modules/file_browser_worker.c | 0 .../gui/modules/file_browser_worker.h | 0 .../{ => services}/gui/modules/loading.c | 0 .../{ => services}/gui/modules/loading.h | 0 .../{ => services}/gui/modules/menu.c | 0 .../{ => services}/gui/modules/menu.h | 0 .../{ => services}/gui/modules/popup.c | 0 .../{ => services}/gui/modules/popup.h | 0 .../{ => services}/gui/modules/submenu.c | 0 .../{ => services}/gui/modules/submenu.h | 0 .../{ => services}/gui/modules/text_box.c | 0 .../{ => services}/gui/modules/text_box.h | 0 .../{ => services}/gui/modules/text_input.c | 0 .../{ => services}/gui/modules/text_input.h | 0 .../{ => services}/gui/modules/validators.c | 2 +- .../{ => services}/gui/modules/validators.h | 0 .../gui/modules/variable_item_list.c | 0 .../gui/modules/variable_item_list.h | 0 .../{ => services}/gui/modules/widget.c | 1 + .../{ => services}/gui/modules/widget.h | 4 +- .../modules/widget_elements/widget_element.h | 22 + .../widget_elements/widget_element_button.c | 0 .../widget_elements/widget_element_frame.c | 0 .../widget_elements/widget_element_i.h | 15 +- .../widget_elements/widget_element_icon.c | 0 .../widget_elements/widget_element_string.c | 0 .../widget_element_string_multiline.c | 0 .../widget_elements/widget_element_text_box.c | 0 .../widget_element_text_scroll.c | 0 .../{ => services}/gui/scene_manager.c | 0 .../{ => services}/gui/scene_manager.h | 0 .../{ => services}/gui/scene_manager_i.h | 0 applications/{ => services}/gui/view.c | 0 applications/{ => services}/gui/view.h | 0 .../{ => services}/gui/view_dispatcher.c | 0 .../{ => services}/gui/view_dispatcher.h | 0 .../{ => services}/gui/view_dispatcher_i.h | 0 applications/{ => services}/gui/view_i.h | 0 applications/{ => services}/gui/view_port.c | 0 applications/{ => services}/gui/view_port.h | 0 applications/{ => services}/gui/view_port_i.h | 0 applications/{ => services}/gui/view_stack.c | 0 applications/{ => services}/gui/view_stack.h | 0 .../{ => services}/input/application.fam | 1 + applications/{ => services}/input/input.c | 0 applications/{ => services}/input/input.h | 8 + applications/{ => services}/input/input_cli.c | 0 applications/{ => services}/input/input_i.h | 0 .../{ => services}/loader/application.fam | 1 + applications/{ => services}/loader/loader.c | 2 +- applications/{ => services}/loader/loader.h | 8 + applications/{ => services}/loader/loader_i.h | 0 .../notification/application.fam | 11 +- .../notification/notification.h | 0 .../notification/notification_app.c | 0 .../notification/notification_app.h | 0 .../notification/notification_app_api.c | 0 .../notification/notification_messages.c | 0 .../notification/notification_messages.h | 0 .../notification_messages_notes.c | 0 .../notification_messages_notes.h | 0 .../notification_settings_filename.h | 0 applications/services/power/application.fam | 26 + applications/{ => services}/power/power_cli.c | 0 applications/services/power/power_cli.h | 11 + .../power/power_service/power.c | 0 .../power/power_service/power.h | 8 + .../power/power_service/power_api.c | 0 .../power/power_service/power_i.h | 0 .../power/power_service/views/power_off.c | 0 .../power/power_service/views/power_off.h | 0 .../power_service/views/power_unplug_usb.c | 0 .../power_service/views/power_unplug_usb.h | 0 .../{ => services}/rpc/application.fam | 1 + applications/{ => services}/rpc/rpc.c | 0 applications/{ => services}/rpc/rpc.h | 8 + applications/{ => services}/rpc/rpc_app.c | 0 applications/{ => services}/rpc/rpc_app.h | 0 applications/{ => services}/rpc/rpc_cli.c | 0 applications/{ => services}/rpc/rpc_debug.c | 0 applications/{ => services}/rpc/rpc_gpio.c | 0 applications/{ => services}/rpc/rpc_gui.c | 0 applications/{ => services}/rpc/rpc_i.h | 0 applications/{ => services}/rpc/rpc_storage.c | 0 applications/{ => services}/rpc/rpc_system.c | 0 .../{ => services}/storage/application.fam | 1 + .../{ => services}/storage/filesystem_api.c | 0 .../storage/filesystem_api_defines.h | 0 .../storage/filesystem_api_internal.h | 0 applications/{ => services}/storage/storage.c | 0 applications/{ => services}/storage/storage.h | 0 .../{ => services}/storage/storage_cli.c | 0 .../storage/storage_external_api.c | 0 .../{ => services}/storage/storage_glue.c | 0 .../{ => services}/storage/storage_glue.h | 0 .../{ => services}/storage/storage_i.h | 0 .../storage/storage_internal_api.c | 0 .../{ => services}/storage/storage_message.h | 0 .../storage/storage_processing.c | 0 .../storage/storage_processing.h | 0 .../{ => services}/storage/storage_sd_api.c | 0 .../{ => services}/storage/storage_sd_api.h | 0 .../{ => services}/storage/storage_test_app.c | 0 .../storage/storages/sd_notify.c | 0 .../storage/storages/sd_notify.h | 0 .../storage/storages/storage_ext.c | 0 .../storage/storages/storage_ext.h | 0 .../storage/storages/storage_int.c | 0 .../storage/storages/storage_int.h | 0 applications/{ => settings}/about/about.c | 0 .../{ => settings}/about/application.fam | 0 applications/settings/application.fam | 10 + .../settings/bt_settings_app/application.fam | 12 + .../bt_settings_app/bt_settings_app.c | 0 .../bt_settings_app/bt_settings_app.h | 2 +- .../scenes/bt_settings_scene.c | 0 .../scenes/bt_settings_scene.h | 0 .../scenes/bt_settings_scene_config.h | 0 .../bt_settings_scene_forget_dev_confirm.c | 0 .../bt_settings_scene_forget_dev_success.c | 0 .../scenes/bt_settings_scene_start.c | 0 .../settings/desktop_settings/application.fam | 12 + .../desktop_settings/desktop_settings_app.c | 2 +- .../desktop_settings/desktop_settings_app.h | 4 +- .../scenes/desktop_settings_scene.c | 0 .../scenes/desktop_settings_scene.h | 0 .../scenes/desktop_settings_scene_config.h | 0 .../scenes/desktop_settings_scene_favorite.c | 0 .../scenes/desktop_settings_scene_i.h | 0 .../scenes/desktop_settings_scene_pin_auth.c | 6 +- .../desktop_settings_scene_pin_disable.c | 3 +- .../scenes/desktop_settings_scene_pin_error.c | 6 +- .../scenes/desktop_settings_scene_pin_menu.c | 0 .../scenes/desktop_settings_scene_pin_setup.c | 6 +- .../desktop_settings_scene_pin_setup_done.c | 4 +- .../desktop_settings_scene_pin_setup_howto.c | 0 .../desktop_settings_scene_pin_setup_howto2.c | 0 .../scenes/desktop_settings_scene_start.c | 0 .../desktop_settings_view_pin_setup_howto.c | 0 .../desktop_settings_view_pin_setup_howto.h | 0 .../desktop_settings_view_pin_setup_howto2.c | 0 .../desktop_settings_view_pin_setup_howto2.h | 0 .../dolphin_passport}/application.fam | 10 - .../dolphin_passport}/passport.c | 0 .../notification_settings/application.fam | 9 + .../notification_settings_app.c | 2 +- .../power_settings_app/application.fam | 13 + .../power_settings_app/power_settings_app.c | 0 .../power_settings_app/power_settings_app.h | 0 .../scenes/power_settings_scene.c | 0 .../scenes/power_settings_scene.h | 0 .../power_settings_scene_battery_info.c | 0 .../scenes/power_settings_scene_config.h | 0 .../scenes/power_settings_scene_power_off.c | 0 .../scenes/power_settings_scene_reboot.c | 0 .../scenes/power_settings_scene_start.c | 0 .../power_settings_app/views/battery_info.c | 0 .../power_settings_app/views/battery_info.h | 0 .../storage_settings/application.fam | 0 .../scenes/storage_settings_scene.c | 0 .../scenes/storage_settings_scene.h | 0 .../scenes/storage_settings_scene_benchmark.c | 0 .../scenes/storage_settings_scene_config.h | 0 .../storage_settings_scene_factory_reset.c | 0 .../storage_settings_scene_format_confirm.c | 0 .../storage_settings_scene_formatting.c | 0 .../storage_settings_scene_internal_info.c | 0 .../scenes/storage_settings_scene_sd_info.c | 0 .../scenes/storage_settings_scene_start.c | 0 .../storage_settings_scene_unmount_confirm.c | 0 .../scenes/storage_settings_scene_unmounted.c | 0 .../storage_settings/storage_settings.c | 0 .../storage_settings/storage_settings.h | 0 applications/settings/system/application.fam | 9 + .../{ => settings}/system/system_settings.c | 0 .../{ => settings}/system/system_settings.h | 0 applications/system/application.fam | 15 +- .../storage_move_to_sd/application.fam | 0 .../scenes/storage_move_to_sd_scene.c | 0 .../scenes/storage_move_to_sd_scene.h | 0 .../scenes/storage_move_to_sd_scene_config.h | 0 .../scenes/storage_move_to_sd_scene_confirm.c | 0 .../storage_move_to_sd_scene_progress.c | 0 .../storage_move_to_sd/storage_move_to_sd.c | 0 .../storage_move_to_sd/storage_move_to_sd.h | 0 .../{ => system}/updater/application.fam | 0 .../{ => system}/updater/cli/updater_cli.c | 0 .../updater/scenes/updater_scene.c | 0 .../updater/scenes/updater_scene.h | 0 .../updater/scenes/updater_scene_config.h | 0 .../updater/scenes/updater_scene_error.c | 2 +- .../updater/scenes/updater_scene_loadcfg.c | 2 +- .../updater/scenes/updater_scene_main.c | 4 +- applications/{ => system}/updater/updater.c | 0 applications/{ => system}/updater/updater_i.h | 0 .../{ => system}/updater/util/update_task.c | 0 .../{ => system}/updater/util/update_task.h | 0 .../{ => system}/updater/util/update_task_i.h | 0 .../updater/util/update_task_worker_backup.c | 0 .../updater/util/update_task_worker_flasher.c | 0 .../{ => system}/updater/views/updater_main.c | 0 .../{ => system}/updater/views/updater_main.h | 0 applications_user/.gitignore | 1 + applications_user/README.md | 1 + assets/.gitignore | 3 +- assets/SConscript | 11 +- assets/resources/Manifest | 241 -- debug/flipperapps.py | 157 + documentation/AppManifests.md | 78 +- documentation/AppsOnSDCard.md | 72 + documentation/fbt.md | 7 +- fbt_options.py | 15 +- firmware.scons | 85 +- firmware/SConscript | 12 +- firmware/targets/f7/api_symbols.csv | 2870 +++++++++++++++++ firmware/targets/f7/application-ext.ld | 21 +- firmware/targets/f7/furi_hal/furi_hal_clock.h | 8 + firmware/targets/f7/furi_hal/furi_hal_flash.h | 8 + firmware/targets/f7/furi_hal/furi_hal_nfc.c | 49 +- .../targets/f7/furi_hal/furi_hal_subghz.c | 3 +- .../targets/f7/furi_hal/furi_hal_usb_cdc.c | 2 +- ...uri_hal_usb_cdc_i.h => furi_hal_usb_cdc.h} | 8 + .../f7/platform_specific/intrinsic_export.h | 11 + firmware/targets/f7/stm32wb55xx_flash.ld | 10 +- firmware/targets/f7/stm32wb55xx_ram_fw.ld | 4 +- firmware/targets/furi_hal_include/furi_hal.h | 3 +- .../furi_hal_include/furi_hal_bt_hid.h | 8 + .../furi_hal_include/furi_hal_bt_serial.h | 8 + .../furi_hal_include/furi_hal_compress.h | 8 + .../furi_hal_include/furi_hal_crypto.h | 8 + .../targets/furi_hal_include/furi_hal_nfc.h | 157 +- .../furi_hal_include/furi_hal_region.h | 8 + .../targets/furi_hal_include/furi_hal_usb.h | 8 + .../furi_hal_include/furi_hal_usb_hid.h | 8 + .../furi_hal_include/furi_hal_usb_hid_u2f.h | 8 + furi/core/memmgr.c | 16 + furi/core/memmgr.h | 15 + lib/SConscript | 14 +- lib/ST25RFAL002/include/rfal_picopass.h | 64 - lib/STM32CubeWB.scons | 5 + lib/app-scened-template/generic_scene.hpp | 3 +- lib/app-scened-template/record_controller.hpp | 3 +- lib/app-scened-template/scene_controller.hpp | 3 +- lib/app-scened-template/typeindex_no_rtti.hpp | 21 +- lib/app-scened-template/view_controller.hpp | 12 +- lib/cxxheaderparser | 1 + lib/digital_signal/digital_signal.h | 8 + lib/flipper_application/SConscript | 20 + .../application_manifest.h | 45 + lib/flipper_application/elf/elf.h | 1148 +++++++ .../elf/elf_api_interface.h | 12 + .../flipper_applicaiton_i.c | 477 +++ lib/flipper_application/flipper_application.c | 131 + lib/flipper_application/flipper_application.h | 129 + .../flipper_application_i.h | 99 + lib/flipper_format/SConscript | 4 + lib/loclass.scons | 17 - lib/misc.scons | 3 + lib/nfc/nfc_device.c | 15 +- lib/print/SConscript | 6 + lib/print/wrappers.c | 5 +- lib/print/wrappers.h | 25 + lib/subghz/SConscript | 7 + lib/subghz/environment.h | 8 + lib/subghz/protocols/base.h | 8 + lib/subghz/receiver.h | 8 + lib/subghz/subghz_keystore.h | 8 + lib/subghz/subghz_tx_rx_worker.h | 8 + lib/subghz/subghz_worker.h | 8 + lib/subghz/transmitter.h | 8 + lib/toolbox/SConscript | 17 + lib/toolbox/hmac_sha256.h | 11 + lib/toolbox/saved_struct.h | 8 + lib/toolbox/sha256.h | 10 + lib/update_util/lfs_backup.c | 2 +- scripts/assets.py | 6 +- scripts/runfap.py | 99 + site_scons/cc.scons | 1 + site_scons/commandline.scons | 41 +- site_scons/extapps.scons | 94 + site_scons/fbt/appmanifest.py | 55 +- site_scons/fbt/elfmanifest.py | 85 + site_scons/fbt/sdk.py | 514 +++ site_scons/site_init.py | 1 + site_scons/site_tools/blackmagic.py | 2 +- site_scons/site_tools/fbt_apps.py | 26 +- site_scons/site_tools/fbt_assets.py | 4 +- site_scons/site_tools/fbt_dist.py | 7 +- site_scons/site_tools/fbt_extapps.py | 144 +- site_scons/site_tools/fbt_sdk.py | 208 ++ site_scons/site_tools/fwbin.py | 2 + site_scons/site_tools/sconsmodular.py | 5 + 895 files changed, 8860 insertions(+), 1463 deletions(-) delete mode 100644 applications/bt/application.fam rename applications/{ => debug}/accessor/accessor.cpp (100%) rename applications/{ => debug}/accessor/accessor_app.cpp (100%) rename applications/{ => debug}/accessor/accessor_app.h (100%) rename applications/{ => debug}/accessor/accessor_event.h (100%) rename applications/{ => debug}/accessor/accessor_view_manager.cpp (100%) rename applications/{ => debug}/accessor/accessor_view_manager.h (100%) rename applications/{ => debug}/accessor/application.fam (88%) rename applications/{ => debug}/accessor/helpers/wiegand.cpp (100%) rename applications/{ => debug}/accessor/helpers/wiegand.h (100%) rename applications/{ => debug}/accessor/scene/accessor_scene_generic.h (100%) rename applications/{ => debug}/accessor/scene/accessor_scene_start.cpp (100%) rename applications/{ => debug}/accessor/scene/accessor_scene_start.h (100%) create mode 100644 applications/debug/application.fam create mode 100644 applications/debug/battery_test_app/application.fam rename applications/{power => debug}/battery_test_app/battery_test_app.c (92%) rename applications/{power => debug}/battery_test_app/battery_test_app.h (82%) create mode 100644 applications/debug/blink_test/application.fam rename applications/{debug_tools => debug/blink_test}/blink_test.c (100%) create mode 100644 applications/debug/bt_debug_app/application.fam rename applications/{bt => debug}/bt_debug_app/bt_debug_app.c (100%) rename applications/{bt => debug}/bt_debug_app/bt_debug_app.h (95%) rename applications/{bt => debug}/bt_debug_app/views/bt_carrier_test.c (100%) rename applications/{bt => debug}/bt_debug_app/views/bt_carrier_test.h (100%) rename applications/{bt => debug}/bt_debug_app/views/bt_packet_test.c (100%) rename applications/{bt => debug}/bt_debug_app/views/bt_packet_test.h (100%) rename applications/{bt => debug}/bt_debug_app/views/bt_test.c (100%) rename applications/{bt => debug}/bt_debug_app/views/bt_test.h (100%) rename applications/{bt => debug}/bt_debug_app/views/bt_test_types.h (100%) create mode 100644 applications/debug/display_test/application.fam rename applications/{debug_tools => debug}/display_test/display_test.c (100%) rename applications/{debug_tools => debug}/display_test/display_test.h (100%) rename applications/{debug_tools => debug}/display_test/view_display_test.c (100%) rename applications/{debug_tools => debug}/display_test/view_display_test.h (100%) create mode 100644 applications/debug/file_browser_test/application.fam rename applications/{debug_tools => debug}/file_browser_test/file_browser_app.c (100%) rename applications/{debug_tools => debug}/file_browser_test/file_browser_app_i.h (100%) rename applications/{debug_tools => debug}/file_browser_test/scenes/file_browser_scene.c (100%) rename applications/{debug_tools => debug}/file_browser_test/scenes/file_browser_scene.h (100%) rename applications/{debug_tools => debug}/file_browser_test/scenes/file_browser_scene_browser.c (100%) rename applications/{debug_tools => debug}/file_browser_test/scenes/file_browser_scene_config.h (100%) rename applications/{debug_tools => debug}/file_browser_test/scenes/file_browser_scene_result.c (100%) rename applications/{debug_tools => debug}/file_browser_test/scenes/file_browser_scene_start.c (100%) create mode 100644 applications/debug/keypad_test/application.fam rename applications/{debug_tools => debug/keypad_test}/keypad_test.c (100%) rename applications/{ => debug}/lfrfid_debug/application.fam (74%) rename applications/{ => debug}/lfrfid_debug/lfrfid_debug.c (100%) rename applications/{ => debug}/lfrfid_debug/lfrfid_debug_i.h (82%) rename applications/{ => debug}/lfrfid_debug/scenes/lfrfid_debug_app_scene_start.c (100%) rename applications/{ => debug}/lfrfid_debug/scenes/lfrfid_debug_app_scene_tune.c (100%) rename applications/{ => debug}/lfrfid_debug/scenes/lfrfid_debug_scene.c (100%) rename applications/{ => debug}/lfrfid_debug/scenes/lfrfid_debug_scene.h (100%) rename applications/{ => debug}/lfrfid_debug/scenes/lfrfid_debug_scene_config.h (100%) rename applications/{ => debug}/lfrfid_debug/views/lfrfid_debug_view_tune.c (100%) rename applications/{ => debug}/lfrfid_debug/views/lfrfid_debug_view_tune.h (100%) create mode 100644 applications/debug/text_box_test/application.fam rename applications/{debug_tools => debug/text_box_test}/text_box_test.c (100%) create mode 100644 applications/debug/uart_echo/application.fam rename applications/{debug_tools => debug/uart_echo}/uart_echo.c (100%) rename applications/{ => debug}/unit_tests/application.fam (90%) rename applications/{ => debug}/unit_tests/flipper_format/flipper_format_string_test.c (100%) rename applications/{ => debug}/unit_tests/flipper_format/flipper_format_test.c (100%) rename applications/{ => debug}/unit_tests/furi/furi_memmgr_test.c (100%) rename applications/{ => debug}/unit_tests/furi/furi_pubsub_test.c (100%) rename applications/{ => debug}/unit_tests/furi/furi_record_test.c (100%) rename applications/{ => debug}/unit_tests/furi/furi_test.c (100%) rename applications/{ => debug}/unit_tests/furi/furi_valuemutex_test.c (100%) rename applications/{ => debug}/unit_tests/infrared/infrared_test.c (100%) rename applications/{ => debug}/unit_tests/lfrfid/bit_lib_test.c (100%) rename applications/{ => debug}/unit_tests/lfrfid/lfrfid_protocols.c (100%) rename applications/{ => debug}/unit_tests/minunit.h (100%) rename applications/{ => debug}/unit_tests/minunit_vars.h (100%) rename applications/{ => debug}/unit_tests/minunit_vars_ex.h (100%) rename applications/{ => debug}/unit_tests/nfc/nfc_test.c (99%) rename applications/{ => debug}/unit_tests/protocol_dict/protocol_dict_test.c (100%) rename applications/{ => debug}/unit_tests/rpc/rpc_test.c (100%) rename applications/{ => debug}/unit_tests/storage/dirwalk_test.c (100%) rename applications/{ => debug}/unit_tests/storage/storage_test.c (100%) rename applications/{ => debug}/unit_tests/stream/stream_test.c (100%) rename applications/{ => debug}/unit_tests/subghz/subghz_test.c (100%) rename applications/{ => debug}/unit_tests/test_index.c (100%) rename applications/{ => debug}/unit_tests/varint/varint_test.c (100%) create mode 100644 applications/debug/usb_mouse/application.fam rename applications/{debug_tools => debug/usb_mouse}/usb_mouse.c (100%) create mode 100644 applications/debug/usb_test/application.fam rename applications/{debug_tools => debug/usb_test}/usb_test.c (100%) create mode 100644 applications/debug/vibro_test/application.fam rename applications/{debug_tools => debug/vibro_test}/vibro_test.c (100%) delete mode 100644 applications/debug_tools/application.fam delete mode 100644 applications/extapps.scons delete mode 100644 applications/gui/application.fam delete mode 100644 applications/gui/modules/dialog.c delete mode 100644 applications/gui/modules/dialog.h create mode 100644 applications/main/application.fam rename applications/{ => main}/archive/application.fam (100%) rename applications/{ => main}/archive/archive.c (100%) rename applications/{ => main}/archive/archive.h (100%) rename applications/{ => main}/archive/archive_i.h (100%) rename applications/{ => main}/archive/helpers/archive_apps.c (100%) rename applications/{ => main}/archive/helpers/archive_apps.h (100%) rename applications/{ => main}/archive/helpers/archive_browser.c (99%) rename applications/{ => main}/archive/helpers/archive_browser.h (100%) rename applications/{ => main}/archive/helpers/archive_favorites.c (100%) rename applications/{ => main}/archive/helpers/archive_favorites.h (100%) rename applications/{ => main}/archive/helpers/archive_files.c (100%) rename applications/{ => main}/archive/helpers/archive_files.h (100%) rename applications/{ => main}/archive/scenes/archive_scene.c (100%) rename applications/{ => main}/archive/scenes/archive_scene.h (100%) rename applications/{ => main}/archive/scenes/archive_scene_browser.c (100%) rename applications/{ => main}/archive/scenes/archive_scene_config.h (100%) rename applications/{ => main}/archive/scenes/archive_scene_delete.c (100%) rename applications/{ => main}/archive/scenes/archive_scene_rename.c (100%) rename applications/{ => main}/archive/views/archive_browser_view.c (99%) rename applications/{ => main}/archive/views/archive_browser_view.h (100%) rename applications/{ => main}/bad_usb/application.fam (100%) rename applications/{ => main}/bad_usb/bad_usb_app.c (100%) rename applications/{ => main}/bad_usb/bad_usb_app.h (100%) rename applications/{ => main}/bad_usb/bad_usb_app_i.h (100%) rename applications/{ => main}/bad_usb/bad_usb_script.c (100%) rename applications/{ => main}/bad_usb/bad_usb_script.h (100%) rename applications/{ => main}/bad_usb/scenes/bad_usb_scene.c (100%) rename applications/{ => main}/bad_usb/scenes/bad_usb_scene.h (100%) rename applications/{ => main}/bad_usb/scenes/bad_usb_scene_config.h (100%) rename applications/{ => main}/bad_usb/scenes/bad_usb_scene_error.c (100%) rename applications/{ => main}/bad_usb/scenes/bad_usb_scene_file_select.c (80%) rename applications/{ => main}/bad_usb/scenes/bad_usb_scene_work.c (100%) rename applications/{ => main}/bad_usb/views/bad_usb_view.c (100%) rename applications/{ => main}/bad_usb/views/bad_usb_view.h (100%) create mode 100644 applications/main/fap_loader/application.fam create mode 100644 applications/main/fap_loader/elf_cpp/compilesort.hpp create mode 100644 applications/main/fap_loader/elf_cpp/elf_hashtable.cpp create mode 100644 applications/main/fap_loader/elf_cpp/elf_hashtable.h create mode 100644 applications/main/fap_loader/elf_cpp/elf_hashtable_checks.hpp create mode 100644 applications/main/fap_loader/elf_cpp/elf_hashtable_entry.h create mode 100644 applications/main/fap_loader/fap_loader_app.c rename applications/{ => main}/gpio/application.fam (100%) rename applications/{ => main}/gpio/gpio_app.c (100%) rename applications/{ => main}/gpio/gpio_app.h (100%) rename applications/{ => main}/gpio/gpio_app_i.h (100%) rename applications/{ => main}/gpio/gpio_custom_event.h (100%) rename applications/{ => main}/gpio/gpio_item.c (100%) rename applications/{ => main}/gpio/gpio_item.h (100%) rename applications/{ => main}/gpio/scenes/gpio_scene.c (100%) rename applications/{ => main}/gpio/scenes/gpio_scene.h (100%) rename applications/{ => main}/gpio/scenes/gpio_scene_config.h (100%) rename applications/{ => main}/gpio/scenes/gpio_scene_start.c (100%) rename applications/{ => main}/gpio/scenes/gpio_scene_test.c (100%) rename applications/{ => main}/gpio/scenes/gpio_scene_usb_uart.c (100%) rename applications/{ => main}/gpio/scenes/gpio_scene_usb_uart_close_rpc.c (100%) rename applications/{ => main}/gpio/scenes/gpio_scene_usb_uart_config.c (100%) rename applications/{ => main}/gpio/usb_uart_bridge.c (99%) rename applications/{ => main}/gpio/usb_uart_bridge.h (100%) rename applications/{ => main}/gpio/views/gpio_test.c (100%) rename applications/{ => main}/gpio/views/gpio_test.h (100%) rename applications/{ => main}/gpio/views/gpio_usb_uart.c (100%) rename applications/{ => main}/gpio/views/gpio_usb_uart.h (100%) rename applications/{ => main}/ibutton/application.fam (100%) rename applications/{ => main}/ibutton/ibutton.c (98%) rename applications/{ => main}/ibutton/ibutton.h (100%) rename applications/{ => main}/ibutton/ibutton_cli.c (100%) rename applications/{ => main}/ibutton/ibutton_custom_event.h (100%) rename applications/{ => main}/ibutton/ibutton_i.h (100%) rename applications/{ => main}/ibutton/scenes/ibutton_scene.c (100%) rename applications/{ => main}/ibutton/scenes/ibutton_scene.h (100%) rename applications/{ => main}/ibutton/scenes/ibutton_scene_add_type.c (100%) rename applications/{ => main}/ibutton/scenes/ibutton_scene_add_value.c (100%) rename applications/{ => main}/ibutton/scenes/ibutton_scene_config.h (100%) rename applications/{ => main}/ibutton/scenes/ibutton_scene_delete_confirm.c (100%) rename applications/{ => main}/ibutton/scenes/ibutton_scene_delete_success.c (100%) rename applications/{ => main}/ibutton/scenes/ibutton_scene_emulate.c (100%) rename applications/{ => main}/ibutton/scenes/ibutton_scene_exit_confirm.c (100%) rename applications/{ => main}/ibutton/scenes/ibutton_scene_info.c (100%) rename applications/{ => main}/ibutton/scenes/ibutton_scene_read.c (100%) rename applications/{ => main}/ibutton/scenes/ibutton_scene_read_crc_error.c (100%) rename applications/{ => main}/ibutton/scenes/ibutton_scene_read_key_menu.c (100%) rename applications/{ => main}/ibutton/scenes/ibutton_scene_read_not_key_error.c (100%) rename applications/{ => main}/ibutton/scenes/ibutton_scene_read_success.c (100%) rename applications/{ => main}/ibutton/scenes/ibutton_scene_retry_confirm.c (100%) rename applications/{ => main}/ibutton/scenes/ibutton_scene_rpc.c (100%) rename applications/{ => main}/ibutton/scenes/ibutton_scene_save_name.c (100%) rename applications/{ => main}/ibutton/scenes/ibutton_scene_save_success.c (100%) rename applications/{ => main}/ibutton/scenes/ibutton_scene_saved_key_menu.c (100%) rename applications/{ => main}/ibutton/scenes/ibutton_scene_select_key.c (100%) rename applications/{ => main}/ibutton/scenes/ibutton_scene_start.c (100%) rename applications/{ => main}/ibutton/scenes/ibutton_scene_write.c (100%) rename applications/{ => main}/ibutton/scenes/ibutton_scene_write_success.c (100%) rename applications/{ => main}/infrared/application.fam (100%) rename applications/{ => main}/infrared/infrared.c (100%) rename applications/{ => main}/infrared/infrared.h (100%) rename applications/{ => main}/infrared/infrared_brute_force.c (100%) rename applications/{ => main}/infrared/infrared_brute_force.h (100%) rename applications/{ => main}/infrared/infrared_cli.c (100%) rename applications/{ => main}/infrared/infrared_custom_event.h (100%) rename applications/{ => main}/infrared/infrared_i.h (100%) rename applications/{ => main}/infrared/infrared_remote.c (100%) rename applications/{ => main}/infrared/infrared_remote.h (100%) rename applications/{ => main}/infrared/infrared_remote_button.c (100%) rename applications/{ => main}/infrared/infrared_remote_button.h (100%) rename applications/{ => main}/infrared/infrared_signal.c (100%) rename applications/{ => main}/infrared/infrared_signal.h (100%) rename applications/{ => main}/infrared/scenes/common/infrared_scene_universal_common.c (100%) rename applications/{ => main}/infrared/scenes/common/infrared_scene_universal_common.h (100%) rename applications/{ => main}/infrared/scenes/infrared_scene.c (100%) rename applications/{ => main}/infrared/scenes/infrared_scene.h (100%) rename applications/{ => main}/infrared/scenes/infrared_scene_ask_back.c (100%) rename applications/{ => main}/infrared/scenes/infrared_scene_ask_retry.c (100%) rename applications/{ => main}/infrared/scenes/infrared_scene_config.h (100%) rename applications/{ => main}/infrared/scenes/infrared_scene_debug.c (100%) rename applications/{ => main}/infrared/scenes/infrared_scene_edit.c (100%) rename applications/{ => main}/infrared/scenes/infrared_scene_edit_button_select.c (100%) rename applications/{ => main}/infrared/scenes/infrared_scene_edit_delete.c (100%) rename applications/{ => main}/infrared/scenes/infrared_scene_edit_delete_done.c (100%) rename applications/{ => main}/infrared/scenes/infrared_scene_edit_rename.c (100%) rename applications/{ => main}/infrared/scenes/infrared_scene_edit_rename_done.c (100%) rename applications/{ => main}/infrared/scenes/infrared_scene_error_databases.c (100%) rename applications/{ => main}/infrared/scenes/infrared_scene_learn.c (100%) rename applications/{ => main}/infrared/scenes/infrared_scene_learn_done.c (100%) rename applications/{ => main}/infrared/scenes/infrared_scene_learn_enter_name.c (100%) rename applications/{ => main}/infrared/scenes/infrared_scene_learn_success.c (100%) rename applications/{ => main}/infrared/scenes/infrared_scene_remote.c (100%) rename applications/{ => main}/infrared/scenes/infrared_scene_remote_list.c (82%) rename applications/{ => main}/infrared/scenes/infrared_scene_rpc.c (100%) rename applications/{ => main}/infrared/scenes/infrared_scene_start.c (100%) rename applications/{ => main}/infrared/scenes/infrared_scene_universal.c (100%) rename applications/{ => main}/infrared/scenes/infrared_scene_universal_tv.c (100%) rename applications/{ => main}/infrared/views/infrared_debug_view.c (100%) rename applications/{ => main}/infrared/views/infrared_debug_view.h (100%) rename applications/{ => main}/infrared/views/infrared_progress_view.c (100%) rename applications/{ => main}/infrared/views/infrared_progress_view.h (100%) rename applications/{ => main}/lfrfid/application.fam (95%) rename applications/{ => main}/lfrfid/lfrfid.c (96%) rename applications/{ => main}/lfrfid/lfrfid_cli.c (100%) rename applications/{ => main}/lfrfid/lfrfid_i.h (100%) rename applications/{ => main}/lfrfid/scenes/lfrfid_scene.c (100%) rename applications/{ => main}/lfrfid/scenes/lfrfid_scene.h (100%) rename applications/{ => main}/lfrfid/scenes/lfrfid_scene_config.h (100%) rename applications/{ => main}/lfrfid/scenes/lfrfid_scene_delete_confirm.c (100%) rename applications/{ => main}/lfrfid/scenes/lfrfid_scene_delete_success.c (100%) rename applications/{ => main}/lfrfid/scenes/lfrfid_scene_emulate.c (100%) rename applications/{ => main}/lfrfid/scenes/lfrfid_scene_exit_confirm.c (100%) rename applications/{ => main}/lfrfid/scenes/lfrfid_scene_extra_actions.c (100%) rename applications/{ => main}/lfrfid/scenes/lfrfid_scene_raw_info.c (100%) rename applications/{ => main}/lfrfid/scenes/lfrfid_scene_raw_name.c (100%) rename applications/{ => main}/lfrfid/scenes/lfrfid_scene_raw_read.c (100%) rename applications/{ => main}/lfrfid/scenes/lfrfid_scene_raw_success.c (100%) rename applications/{ => main}/lfrfid/scenes/lfrfid_scene_read.c (100%) rename applications/{ => main}/lfrfid/scenes/lfrfid_scene_read_key_menu.c (100%) rename applications/{ => main}/lfrfid/scenes/lfrfid_scene_read_success.c (100%) rename applications/{ => main}/lfrfid/scenes/lfrfid_scene_retry_confirm.c (100%) rename applications/{ => main}/lfrfid/scenes/lfrfid_scene_rpc.c (100%) rename applications/{ => main}/lfrfid/scenes/lfrfid_scene_save_data.c (100%) rename applications/{ => main}/lfrfid/scenes/lfrfid_scene_save_name.c (100%) rename applications/{ => main}/lfrfid/scenes/lfrfid_scene_save_success.c (100%) rename applications/{ => main}/lfrfid/scenes/lfrfid_scene_save_type.c (100%) rename applications/{ => main}/lfrfid/scenes/lfrfid_scene_saved_info.c (100%) rename applications/{ => main}/lfrfid/scenes/lfrfid_scene_saved_key_menu.c (100%) rename applications/{ => main}/lfrfid/scenes/lfrfid_scene_select_key.c (100%) rename applications/{ => main}/lfrfid/scenes/lfrfid_scene_start.c (100%) rename applications/{ => main}/lfrfid/scenes/lfrfid_scene_write.c (100%) rename applications/{ => main}/lfrfid/scenes/lfrfid_scene_write_success.c (100%) rename applications/{ => main}/lfrfid/views/lfrfid_view_read.c (100%) rename applications/{ => main}/lfrfid/views/lfrfid_view_read.h (100%) rename applications/{ => main}/nfc/application.fam (100%) rename applications/{ => main}/nfc/helpers/nfc_custom_event.h (100%) rename applications/{ => main}/nfc/helpers/nfc_emv_parser.c (100%) rename applications/{ => main}/nfc/helpers/nfc_emv_parser.h (100%) rename applications/{ => main}/nfc/helpers/nfc_generators.c (100%) rename applications/{ => main}/nfc/helpers/nfc_generators.h (100%) rename applications/{ => main}/nfc/nfc.c (100%) rename applications/{ => main}/nfc/nfc.h (100%) rename applications/{ => main}/nfc/nfc_cli.c (100%) rename applications/{ => main}/nfc/nfc_i.h (100%) rename applications/{ => main}/nfc/scenes/nfc_scene.c (100%) rename applications/{ => main}/nfc/scenes/nfc_scene.h (100%) rename applications/{ => main}/nfc/scenes/nfc_scene_config.h (100%) rename applications/{ => main}/nfc/scenes/nfc_scene_debug.c (100%) rename applications/{ => main}/nfc/scenes/nfc_scene_delete.c (100%) rename applications/{ => main}/nfc/scenes/nfc_scene_delete_success.c (100%) rename applications/{ => main}/nfc/scenes/nfc_scene_detect_reader.c (100%) rename applications/{ => main}/nfc/scenes/nfc_scene_device_info.c (100%) rename applications/{ => main}/nfc/scenes/nfc_scene_dict_not_found.c (100%) rename applications/{ => main}/nfc/scenes/nfc_scene_emulate_apdu_sequence.c (100%) rename applications/{ => main}/nfc/scenes/nfc_scene_emulate_uid.c (100%) rename applications/{ => main}/nfc/scenes/nfc_scene_emv_menu.c (100%) rename applications/{ => main}/nfc/scenes/nfc_scene_emv_read_success.c (100%) rename applications/{ => main}/nfc/scenes/nfc_scene_exit_confirm.c (100%) rename applications/{ => main}/nfc/scenes/nfc_scene_extra_actions.c (100%) rename applications/{ => main}/nfc/scenes/nfc_scene_field.c (100%) rename applications/{ => main}/nfc/scenes/nfc_scene_file_select.c (100%) rename applications/{ => main}/nfc/scenes/nfc_scene_generate_info.c (100%) rename applications/{ => main}/nfc/scenes/nfc_scene_mf_classic_dict_attack.c (100%) rename applications/{ => main}/nfc/scenes/nfc_scene_mf_classic_emulate.c (100%) rename applications/{ => main}/nfc/scenes/nfc_scene_mf_classic_keys.c (100%) rename applications/{ => main}/nfc/scenes/nfc_scene_mf_classic_keys_add.c (100%) rename applications/{ => main}/nfc/scenes/nfc_scene_mf_classic_menu.c (100%) rename applications/{ => main}/nfc/scenes/nfc_scene_mf_classic_read_success.c (100%) rename applications/{ => main}/nfc/scenes/nfc_scene_mf_desfire_app.c (100%) rename applications/{ => main}/nfc/scenes/nfc_scene_mf_desfire_data.c (100%) rename applications/{ => main}/nfc/scenes/nfc_scene_mf_desfire_menu.c (100%) rename applications/{ => main}/nfc/scenes/nfc_scene_mf_desfire_read_success.c (100%) rename applications/{ => main}/nfc/scenes/nfc_scene_mf_ultralight_data.c (100%) rename applications/{ => main}/nfc/scenes/nfc_scene_mf_ultralight_emulate.c (100%) rename applications/{ => main}/nfc/scenes/nfc_scene_mf_ultralight_key_input.c (100%) rename applications/{ => main}/nfc/scenes/nfc_scene_mf_ultralight_menu.c (100%) rename applications/{ => main}/nfc/scenes/nfc_scene_mf_ultralight_read_auth.c (100%) rename applications/{ => main}/nfc/scenes/nfc_scene_mf_ultralight_read_auth_result.c (100%) rename applications/{ => main}/nfc/scenes/nfc_scene_mf_ultralight_read_success.c (100%) rename applications/{ => main}/nfc/scenes/nfc_scene_mf_ultralight_unlock_menu.c (100%) rename applications/{ => main}/nfc/scenes/nfc_scene_mf_ultralight_unlock_warn.c (100%) rename applications/{ => main}/nfc/scenes/nfc_scene_mfkey_complete.c (100%) rename applications/{ => main}/nfc/scenes/nfc_scene_mfkey_nonces_info.c (100%) rename applications/{ => main}/nfc/scenes/nfc_scene_nfc_data_info.c (100%) rename applications/{ => main}/nfc/scenes/nfc_scene_nfca_menu.c (100%) rename applications/{ => main}/nfc/scenes/nfc_scene_nfca_read_success.c (100%) rename applications/{ => main}/nfc/scenes/nfc_scene_read.c (100%) rename applications/{ => main}/nfc/scenes/nfc_scene_read_card_success.c (100%) rename applications/{ => main}/nfc/scenes/nfc_scene_restore_original.c (100%) rename applications/{ => main}/nfc/scenes/nfc_scene_restore_original_confirm.c (100%) rename applications/{ => main}/nfc/scenes/nfc_scene_retry_confirm.c (100%) rename applications/{ => main}/nfc/scenes/nfc_scene_rpc.c (100%) rename applications/{ => main}/nfc/scenes/nfc_scene_save_name.c (100%) rename applications/{ => main}/nfc/scenes/nfc_scene_save_success.c (100%) rename applications/{ => main}/nfc/scenes/nfc_scene_saved_menu.c (100%) rename applications/{ => main}/nfc/scenes/nfc_scene_set_atqa.c (100%) rename applications/{ => main}/nfc/scenes/nfc_scene_set_sak.c (100%) rename applications/{ => main}/nfc/scenes/nfc_scene_set_type.c (100%) rename applications/{ => main}/nfc/scenes/nfc_scene_set_uid.c (100%) rename applications/{ => main}/nfc/scenes/nfc_scene_start.c (100%) rename applications/{ => main}/nfc/views/detect_reader.c (100%) rename applications/{ => main}/nfc/views/detect_reader.h (100%) rename applications/{ => main}/nfc/views/dict_attack.c (100%) rename applications/{ => main}/nfc/views/dict_attack.h (100%) rename applications/{ => main}/subghz/application.fam (100%) rename applications/{ => main}/subghz/helpers/subghz_chat.c (100%) rename applications/{ => main}/subghz/helpers/subghz_chat.h (100%) rename applications/{ => main}/subghz/helpers/subghz_custom_event.h (100%) rename applications/{ => main}/subghz/helpers/subghz_frequency_analyzer_worker.c (100%) rename applications/{ => main}/subghz/helpers/subghz_frequency_analyzer_worker.h (100%) rename applications/{ => main}/subghz/helpers/subghz_testing.c (100%) rename applications/{ => main}/subghz/helpers/subghz_testing.h (100%) rename applications/{ => main}/subghz/helpers/subghz_types.h (100%) rename applications/{ => main}/subghz/scenes/subghz_scene.c (100%) rename applications/{ => main}/subghz/scenes/subghz_scene.h (100%) rename applications/{ => main}/subghz/scenes/subghz_scene_config.h (100%) rename applications/{ => main}/subghz/scenes/subghz_scene_delete.c (100%) rename applications/{ => main}/subghz/scenes/subghz_scene_delete_raw.c (100%) rename applications/{ => main}/subghz/scenes/subghz_scene_delete_success.c (100%) rename applications/{ => main}/subghz/scenes/subghz_scene_frequency_analyzer.c (100%) rename applications/{ => main}/subghz/scenes/subghz_scene_more_raw.c (100%) rename applications/{ => main}/subghz/scenes/subghz_scene_need_saving.c (100%) rename applications/{ => main}/subghz/scenes/subghz_scene_read_raw.c (100%) rename applications/{ => main}/subghz/scenes/subghz_scene_receiver.c (100%) rename applications/{ => main}/subghz/scenes/subghz_scene_receiver_config.c (100%) rename applications/{ => main}/subghz/scenes/subghz_scene_receiver_info.c (100%) rename applications/{ => main}/subghz/scenes/subghz_scene_rpc.c (100%) rename applications/{ => main}/subghz/scenes/subghz_scene_save_name.c (100%) rename applications/{ => main}/subghz/scenes/subghz_scene_save_success.c (100%) rename applications/{ => main}/subghz/scenes/subghz_scene_saved.c (100%) rename applications/{ => main}/subghz/scenes/subghz_scene_saved_menu.c (100%) rename applications/{ => main}/subghz/scenes/subghz_scene_set_type.c (100%) rename applications/{ => main}/subghz/scenes/subghz_scene_show_error.c (100%) rename applications/{ => main}/subghz/scenes/subghz_scene_show_error_sub.c (100%) rename applications/{ => main}/subghz/scenes/subghz_scene_show_only_rx.c (100%) rename applications/{ => main}/subghz/scenes/subghz_scene_start.c (100%) rename applications/{ => main}/subghz/scenes/subghz_scene_test.c (100%) rename applications/{ => main}/subghz/scenes/subghz_scene_test_carrier.c (100%) rename applications/{ => main}/subghz/scenes/subghz_scene_test_packet.c (100%) rename applications/{ => main}/subghz/scenes/subghz_scene_test_static.c (100%) rename applications/{ => main}/subghz/scenes/subghz_scene_transmitter.c (100%) rename applications/{ => main}/subghz/subghz.c (100%) rename applications/{ => main}/subghz/subghz.h (100%) rename applications/{ => main}/subghz/subghz_cli.c (99%) rename applications/{ => main}/subghz/subghz_cli.h (100%) rename applications/{ => main}/subghz/subghz_history.c (100%) rename applications/{ => main}/subghz/subghz_history.h (100%) rename applications/{ => main}/subghz/subghz_i.c (98%) rename applications/{ => main}/subghz/subghz_i.h (100%) rename applications/{ => main}/subghz/subghz_setting.c (100%) rename applications/{ => main}/subghz/subghz_setting.h (100%) rename applications/{ => main}/subghz/views/receiver.c (100%) rename applications/{ => main}/subghz/views/receiver.h (100%) rename applications/{ => main}/subghz/views/subghz_frequency_analyzer.c (100%) rename applications/{ => main}/subghz/views/subghz_frequency_analyzer.h (100%) rename applications/{ => main}/subghz/views/subghz_read_raw.c (100%) rename applications/{ => main}/subghz/views/subghz_read_raw.h (100%) rename applications/{ => main}/subghz/views/subghz_test_carrier.c (100%) rename applications/{ => main}/subghz/views/subghz_test_carrier.h (100%) rename applications/{ => main}/subghz/views/subghz_test_packet.c (100%) rename applications/{ => main}/subghz/views/subghz_test_packet.h (100%) rename applications/{ => main}/subghz/views/subghz_test_static.c (100%) rename applications/{ => main}/subghz/views/subghz_test_static.h (100%) rename applications/{ => main}/subghz/views/transmitter.c (100%) rename applications/{ => main}/subghz/views/transmitter.h (100%) rename applications/{ => main}/u2f/application.fam (100%) rename applications/{ => main}/u2f/scenes/u2f_scene.c (100%) rename applications/{ => main}/u2f/scenes/u2f_scene.h (100%) rename applications/{ => main}/u2f/scenes/u2f_scene_config.h (100%) rename applications/{ => main}/u2f/scenes/u2f_scene_error.c (100%) rename applications/{ => main}/u2f/scenes/u2f_scene_main.c (100%) rename applications/{ => main}/u2f/u2f.c (100%) rename applications/{ => main}/u2f/u2f.h (100%) rename applications/{ => main}/u2f/u2f_app.c (100%) rename applications/{ => main}/u2f/u2f_app.h (100%) rename applications/{ => main}/u2f/u2f_app_i.h (100%) rename applications/{ => main}/u2f/u2f_data.c (100%) rename applications/{ => main}/u2f/u2f_data.h (100%) rename applications/{ => main}/u2f/u2f_hid.c (100%) rename applications/{ => main}/u2f/u2f_hid.h (100%) rename applications/{ => main}/u2f/views/u2f_view.c (100%) rename applications/{ => main}/u2f/views/u2f_view.h (100%) delete mode 100644 applications/meta/application.fam delete mode 100644 applications/picopass/application.fam create mode 100644 applications/plugins/application.fam create mode 100644 applications/plugins/bt_hid_app/application.fam rename applications/{bt => plugins}/bt_hid_app/bt_hid.c (99%) rename applications/{bt => plugins}/bt_hid_app/bt_hid.h (94%) create mode 100644 applications/plugins/bt_hid_app/bt_remote_10px.png rename applications/{bt => plugins}/bt_hid_app/views/bt_hid_keyboard.c (100%) rename applications/{bt => plugins}/bt_hid_app/views/bt_hid_keyboard.h (100%) rename applications/{bt => plugins}/bt_hid_app/views/bt_hid_keynote.c (100%) rename applications/{bt => plugins}/bt_hid_app/views/bt_hid_keynote.h (100%) rename applications/{bt => plugins}/bt_hid_app/views/bt_hid_media.c (100%) rename applications/{bt => plugins}/bt_hid_app/views/bt_hid_media.h (100%) rename applications/{bt => plugins}/bt_hid_app/views/bt_hid_mouse.c (100%) rename applications/{bt => plugins}/bt_hid_app/views/bt_hid_mouse.h (100%) rename applications/{ => plugins}/music_player/application.fam (84%) rename applications/{ => plugins}/music_player/music_player.c (96%) rename applications/{ => plugins}/music_player/music_player_cli.c (100%) rename applications/{ => plugins}/music_player/music_player_worker.c (100%) rename applications/{ => plugins}/music_player/music_player_worker.h (100%) create mode 100644 applications/plugins/picopass/application.fam rename {lib => applications/plugins/picopass}/loclass/optimized_cipher.c (73%) rename {lib => applications/plugins/picopass}/loclass/optimized_cipher.h (84%) rename {lib => applications/plugins/picopass}/loclass/optimized_cipherutils.c (84%) rename {lib => applications/plugins/picopass}/loclass/optimized_cipherutils.h (78%) rename {lib => applications/plugins/picopass}/loclass/optimized_elite.c (92%) rename {lib => applications/plugins/picopass}/loclass/optimized_elite.h (95%) rename {lib => applications/plugins/picopass}/loclass/optimized_ikeys.c (90%) rename {lib => applications/plugins/picopass}/loclass/optimized_ikeys.h (96%) rename applications/{ => plugins}/picopass/picopass.c (100%) rename applications/{ => plugins}/picopass/picopass.h (100%) rename applications/{ => plugins}/picopass/picopass_device.c (98%) rename applications/{ => plugins}/picopass/picopass_device.h (96%) rename applications/{ => plugins}/picopass/picopass_i.h (96%) rename applications/{ => plugins}/picopass/picopass_worker.c (98%) rename applications/{ => plugins}/picopass/picopass_worker.h (100%) rename applications/{ => plugins}/picopass/picopass_worker_i.h (100%) rename {lib/ST25RFAL002/source => applications/plugins/picopass}/rfal_picopass.c (50%) create mode 100644 applications/plugins/picopass/rfal_picopass.h rename applications/{ => plugins}/picopass/scenes/picopass_scene.c (100%) rename applications/{ => plugins}/picopass/scenes/picopass_scene.h (100%) rename applications/{ => plugins}/picopass/scenes/picopass_scene_card_menu.c (100%) rename applications/{ => plugins}/picopass/scenes/picopass_scene_config.h (100%) rename applications/{ => plugins}/picopass/scenes/picopass_scene_delete.c (100%) rename applications/{ => plugins}/picopass/scenes/picopass_scene_delete_success.c (100%) rename applications/{ => plugins}/picopass/scenes/picopass_scene_device_info.c (100%) rename applications/{ => plugins}/picopass/scenes/picopass_scene_file_select.c (95%) rename applications/{ => plugins}/picopass/scenes/picopass_scene_read_card.c (100%) rename applications/{ => plugins}/picopass/scenes/picopass_scene_read_card_success.c (100%) rename applications/{ => plugins}/picopass/scenes/picopass_scene_save_name.c (100%) rename applications/{ => plugins}/picopass/scenes/picopass_scene_save_success.c (100%) rename applications/{ => plugins}/picopass/scenes/picopass_scene_saved_menu.c (100%) rename applications/{ => plugins}/picopass/scenes/picopass_scene_start.c (100%) rename applications/{ => plugins}/picopass/scenes/picopass_scene_write_card.c (100%) rename applications/{ => plugins}/picopass/scenes/picopass_scene_write_card_success.c (100%) rename applications/{ => plugins}/snake_game/application.fam (79%) create mode 100644 applications/plugins/snake_game/snake_10px.png rename applications/{ => plugins}/snake_game/snake_game.c (100%) delete mode 100644 applications/power/application.fam delete mode 100644 applications/power/power_cli.h create mode 100644 applications/services/application.fam rename applications/{ => services}/applications.h (100%) create mode 100644 applications/services/bt/application.fam rename applications/{ => services}/bt/bt_cli.c (99%) rename applications/{ => services}/bt/bt_service/bt.c (100%) rename applications/{ => services}/bt/bt_service/bt.h (100%) rename applications/{ => services}/bt/bt_service/bt_api.c (100%) rename applications/{ => services}/bt/bt_service/bt_i.h (92%) rename applications/{ => services}/bt/bt_service/bt_keys_filename.h (100%) rename applications/{ => services}/bt/bt_service/bt_keys_storage.c (100%) rename applications/{ => services}/bt/bt_service/bt_keys_storage.h (100%) rename applications/{ => services}/bt/bt_settings.c (100%) rename applications/{ => services}/bt/bt_settings.h (100%) rename applications/{ => services}/bt/bt_settings_filename.h (100%) rename applications/{ => services}/cli/application.fam (81%) rename applications/{ => services}/cli/cli.c (100%) rename applications/{ => services}/cli/cli.h (100%) rename applications/{ => services}/cli/cli_command_gpio.c (100%) rename applications/{ => services}/cli/cli_command_gpio.h (100%) rename applications/{ => services}/cli/cli_commands.c (100%) rename applications/{ => services}/cli/cli_commands.h (100%) rename applications/{ => services}/cli/cli_i.h (92%) rename applications/{ => services}/cli/cli_vcp.c (99%) rename applications/{ => services}/cli/cli_vcp.h (80%) rename applications/{ => services}/crypto/application.fam (100%) rename applications/{ => services}/crypto/crypto_cli.c (100%) rename applications/{ => services}/desktop/animations/animation_manager.c (100%) rename applications/{ => services}/desktop/animations/animation_manager.h (100%) rename applications/{ => services}/desktop/animations/animation_storage.c (100%) rename applications/{ => services}/desktop/animations/animation_storage.h (100%) rename applications/{ => services}/desktop/animations/animation_storage_i.h (100%) rename applications/{ => services}/desktop/animations/views/bubble_animation_view.c (100%) rename applications/{ => services}/desktop/animations/views/bubble_animation_view.h (100%) rename applications/{ => services}/desktop/animations/views/one_shot_animation_view.c (100%) rename applications/{ => services}/desktop/animations/views/one_shot_animation_view.h (100%) rename applications/{ => services}/desktop/application.fam (59%) rename applications/{ => services}/desktop/desktop.c (100%) rename applications/{ => services}/desktop/desktop.h (100%) rename applications/{ => services}/desktop/desktop_i.h (97%) rename applications/{desktop/desktop_settings => services/desktop}/desktop_settings.h (100%) rename applications/{desktop/desktop_settings => services/desktop}/desktop_settings_filename.h (100%) rename applications/{ => services}/desktop/helpers/pin_lock.c (99%) rename applications/{ => services}/desktop/helpers/pin_lock.h (91%) rename applications/{ => services}/desktop/helpers/slideshow.c (100%) rename applications/{ => services}/desktop/helpers/slideshow.h (100%) rename applications/{ => services}/desktop/helpers/slideshow_filename.h (100%) rename applications/{ => services}/desktop/scenes/desktop_scene.c (100%) rename applications/{ => services}/desktop/scenes/desktop_scene.h (100%) rename applications/{ => services}/desktop/scenes/desktop_scene_config.h (100%) rename applications/{ => services}/desktop/scenes/desktop_scene_debug.c (100%) rename applications/{ => services}/desktop/scenes/desktop_scene_fault.c (100%) rename applications/{ => services}/desktop/scenes/desktop_scene_hw_mismatch.c (100%) rename applications/{ => services}/desktop/scenes/desktop_scene_i.h (100%) rename applications/{ => services}/desktop/scenes/desktop_scene_lock_menu.c (98%) rename applications/{ => services}/desktop/scenes/desktop_scene_locked.c (100%) rename applications/{ => services}/desktop/scenes/desktop_scene_main.c (100%) rename applications/{ => services}/desktop/scenes/desktop_scene_pin_input.c (100%) rename applications/{ => services}/desktop/scenes/desktop_scene_pin_timeout.c (100%) rename applications/{ => services}/desktop/scenes/desktop_scene_slideshow.c (100%) rename applications/{ => services}/desktop/views/desktop_events.h (100%) rename applications/{ => services}/desktop/views/desktop_view_debug.c (100%) rename applications/{ => services}/desktop/views/desktop_view_debug.h (100%) rename applications/{ => services}/desktop/views/desktop_view_lock_menu.c (100%) rename applications/{ => services}/desktop/views/desktop_view_lock_menu.h (100%) rename applications/{ => services}/desktop/views/desktop_view_locked.c (99%) rename applications/{ => services}/desktop/views/desktop_view_locked.h (94%) rename applications/{ => services}/desktop/views/desktop_view_main.c (100%) rename applications/{ => services}/desktop/views/desktop_view_main.h (100%) rename applications/{ => services}/desktop/views/desktop_view_pin_input.c (99%) rename applications/{ => services}/desktop/views/desktop_view_pin_input.h (96%) rename applications/{ => services}/desktop/views/desktop_view_pin_setup_done.c (100%) rename applications/{ => services}/desktop/views/desktop_view_pin_setup_done.h (100%) rename applications/{ => services}/desktop/views/desktop_view_pin_timeout.c (100%) rename applications/{ => services}/desktop/views/desktop_view_pin_timeout.h (100%) rename applications/{ => services}/desktop/views/desktop_view_slideshow.c (100%) rename applications/{ => services}/desktop/views/desktop_view_slideshow.h (100%) rename applications/{ => services}/dialogs/application.fam (87%) rename applications/{ => services}/dialogs/dialogs.c (78%) rename applications/{ => services}/dialogs/dialogs.h (80%) rename applications/{ => services}/dialogs/dialogs_api.c (82%) rename applications/{ => services}/dialogs/dialogs_api_lock.h (100%) rename applications/{ => services}/dialogs/dialogs_i.h (85%) rename applications/{ => services}/dialogs/dialogs_message.h (92%) rename applications/{ => services}/dialogs/dialogs_module_file_browser.c (95%) rename applications/{ => services}/dialogs/dialogs_module_file_browser.h (100%) rename applications/{ => services}/dialogs/dialogs_module_message.c (100%) rename applications/{ => services}/dialogs/dialogs_module_message.h (100%) rename applications/{ => services}/dialogs/view_holder.c (100%) rename applications/{ => services}/dialogs/view_holder.h (100%) create mode 100644 applications/services/dolphin/application.fam rename applications/{ => services}/dolphin/dolphin.c (100%) rename applications/{ => services}/dolphin/dolphin.h (100%) rename applications/{ => services}/dolphin/dolphin_i.h (100%) rename applications/{ => services}/dolphin/helpers/dolphin_deed.c (100%) rename applications/{ => services}/dolphin/helpers/dolphin_deed.h (100%) rename applications/{ => services}/dolphin/helpers/dolphin_state.c (100%) rename applications/{ => services}/dolphin/helpers/dolphin_state.h (100%) rename applications/{ => services}/dolphin/helpers/dolphin_state_filename.h (100%) create mode 100644 applications/services/gui/application.fam rename applications/{ => services}/gui/canvas.c (100%) rename applications/{ => services}/gui/canvas.h (100%) rename applications/{ => services}/gui/canvas_i.h (100%) rename applications/{ => services}/gui/elements.c (100%) rename applications/{ => services}/gui/elements.h (100%) rename applications/{ => services}/gui/gui.c (100%) rename applications/{ => services}/gui/gui.h (100%) rename applications/{ => services}/gui/gui_i.h (100%) rename applications/{ => services}/gui/icon.c (100%) rename applications/{ => services}/gui/icon.h (100%) rename applications/{ => services}/gui/icon_animation.c (100%) rename applications/{ => services}/gui/icon_animation.h (100%) rename applications/{ => services}/gui/icon_animation_i.h (100%) rename applications/{ => services}/gui/icon_i.h (100%) rename applications/{ => services}/gui/modules/button_menu.c (100%) rename applications/{ => services}/gui/modules/button_menu.h (100%) rename applications/{ => services}/gui/modules/button_panel.c (100%) rename applications/{ => services}/gui/modules/button_panel.h (100%) rename applications/{ => services}/gui/modules/byte_input.c (100%) rename applications/{ => services}/gui/modules/byte_input.h (100%) rename applications/{ => services}/gui/modules/dialog_ex.c (100%) rename applications/{ => services}/gui/modules/dialog_ex.h (100%) rename applications/{ => services}/gui/modules/empty_screen.c (100%) rename applications/{ => services}/gui/modules/empty_screen.h (100%) rename applications/{ => services}/gui/modules/file_browser.c (85%) rename applications/{ => services}/gui/modules/file_browser.h (76%) rename applications/{ => services}/gui/modules/file_browser_worker.c (100%) rename applications/{ => services}/gui/modules/file_browser_worker.h (100%) rename applications/{ => services}/gui/modules/loading.c (100%) rename applications/{ => services}/gui/modules/loading.h (100%) rename applications/{ => services}/gui/modules/menu.c (100%) rename applications/{ => services}/gui/modules/menu.h (100%) rename applications/{ => services}/gui/modules/popup.c (100%) rename applications/{ => services}/gui/modules/popup.h (100%) rename applications/{ => services}/gui/modules/submenu.c (100%) rename applications/{ => services}/gui/modules/submenu.h (100%) rename applications/{ => services}/gui/modules/text_box.c (100%) rename applications/{ => services}/gui/modules/text_box.h (100%) rename applications/{ => services}/gui/modules/text_input.c (100%) rename applications/{ => services}/gui/modules/text_input.h (100%) rename applications/{ => services}/gui/modules/validators.c (97%) rename applications/{ => services}/gui/modules/validators.h (100%) rename applications/{ => services}/gui/modules/variable_item_list.c (100%) rename applications/{ => services}/gui/modules/variable_item_list.h (100%) rename applications/{ => services}/gui/modules/widget.c (99%) rename applications/{ => services}/gui/modules/widget.h (98%) create mode 100644 applications/services/gui/modules/widget_elements/widget_element.h rename applications/{ => services}/gui/modules/widget_elements/widget_element_button.c (100%) rename applications/{ => services}/gui/modules/widget_elements/widget_element_frame.c (100%) rename applications/{ => services}/gui/modules/widget_elements/widget_element_i.h (90%) rename applications/{ => services}/gui/modules/widget_elements/widget_element_icon.c (100%) rename applications/{ => services}/gui/modules/widget_elements/widget_element_string.c (100%) rename applications/{ => services}/gui/modules/widget_elements/widget_element_string_multiline.c (100%) rename applications/{ => services}/gui/modules/widget_elements/widget_element_text_box.c (100%) rename applications/{ => services}/gui/modules/widget_elements/widget_element_text_scroll.c (100%) rename applications/{ => services}/gui/scene_manager.c (100%) rename applications/{ => services}/gui/scene_manager.h (100%) rename applications/{ => services}/gui/scene_manager_i.h (100%) rename applications/{ => services}/gui/view.c (100%) rename applications/{ => services}/gui/view.h (100%) rename applications/{ => services}/gui/view_dispatcher.c (100%) rename applications/{ => services}/gui/view_dispatcher.h (100%) rename applications/{ => services}/gui/view_dispatcher_i.h (100%) rename applications/{ => services}/gui/view_i.h (100%) rename applications/{ => services}/gui/view_port.c (100%) rename applications/{ => services}/gui/view_port.h (100%) rename applications/{ => services}/gui/view_port_i.h (100%) rename applications/{ => services}/gui/view_stack.c (100%) rename applications/{ => services}/gui/view_stack.h (100%) rename applications/{ => services}/input/application.fam (86%) rename applications/{ => services}/input/input.c (100%) rename applications/{ => services}/input/input.h (94%) rename applications/{ => services}/input/input_cli.c (100%) rename applications/{ => services}/input/input_i.h (100%) rename applications/{ => services}/loader/application.fam (87%) rename applications/{ => services}/loader/loader.c (99%) rename applications/{ => services}/loader/loader.h (93%) rename applications/{ => services}/loader/loader_i.h (100%) rename applications/{ => services}/notification/application.fam (54%) rename applications/{ => services}/notification/notification.h (100%) rename applications/{ => services}/notification/notification_app.c (100%) rename applications/{ => services}/notification/notification_app.h (100%) rename applications/{ => services}/notification/notification_app_api.c (100%) rename applications/{ => services}/notification/notification_messages.c (100%) rename applications/{ => services}/notification/notification_messages.h (100%) rename applications/{ => services}/notification/notification_messages_notes.c (100%) rename applications/{ => services}/notification/notification_messages_notes.h (100%) rename applications/{ => services}/notification/notification_settings_filename.h (100%) create mode 100644 applications/services/power/application.fam rename applications/{ => services}/power/power_cli.c (100%) create mode 100644 applications/services/power/power_cli.h rename applications/{ => services}/power/power_service/power.c (100%) rename applications/{ => services}/power/power_service/power.h (96%) rename applications/{ => services}/power/power_service/power_api.c (100%) rename applications/{ => services}/power/power_service/power_i.h (100%) rename applications/{ => services}/power/power_service/views/power_off.c (100%) rename applications/{ => services}/power/power_service/views/power_off.h (100%) rename applications/{ => services}/power/power_service/views/power_unplug_usb.c (100%) rename applications/{ => services}/power/power_service/views/power_unplug_usb.h (100%) rename applications/{ => services}/rpc/application.fam (84%) rename applications/{ => services}/rpc/rpc.c (100%) rename applications/{ => services}/rpc/rpc.h (98%) rename applications/{ => services}/rpc/rpc_app.c (100%) rename applications/{ => services}/rpc/rpc_app.h (100%) rename applications/{ => services}/rpc/rpc_cli.c (100%) rename applications/{ => services}/rpc/rpc_debug.c (100%) rename applications/{ => services}/rpc/rpc_gpio.c (100%) rename applications/{ => services}/rpc/rpc_gui.c (100%) rename applications/{ => services}/rpc/rpc_i.h (100%) rename applications/{ => services}/rpc/rpc_storage.c (100%) rename applications/{ => services}/rpc/rpc_system.c (100%) rename applications/{ => services}/storage/application.fam (92%) rename applications/{ => services}/storage/filesystem_api.c (100%) rename applications/{ => services}/storage/filesystem_api_defines.h (100%) rename applications/{ => services}/storage/filesystem_api_internal.h (100%) rename applications/{ => services}/storage/storage.c (100%) rename applications/{ => services}/storage/storage.h (100%) rename applications/{ => services}/storage/storage_cli.c (100%) rename applications/{ => services}/storage/storage_external_api.c (100%) rename applications/{ => services}/storage/storage_glue.c (100%) rename applications/{ => services}/storage/storage_glue.h (100%) rename applications/{ => services}/storage/storage_i.h (100%) rename applications/{ => services}/storage/storage_internal_api.c (100%) rename applications/{ => services}/storage/storage_message.h (100%) rename applications/{ => services}/storage/storage_processing.c (100%) rename applications/{ => services}/storage/storage_processing.h (100%) rename applications/{ => services}/storage/storage_sd_api.c (100%) rename applications/{ => services}/storage/storage_sd_api.h (100%) rename applications/{ => services}/storage/storage_test_app.c (100%) rename applications/{ => services}/storage/storages/sd_notify.c (100%) rename applications/{ => services}/storage/storages/sd_notify.h (100%) rename applications/{ => services}/storage/storages/storage_ext.c (100%) rename applications/{ => services}/storage/storages/storage_ext.h (100%) rename applications/{ => services}/storage/storages/storage_int.c (100%) rename applications/{ => services}/storage/storages/storage_int.h (100%) rename applications/{ => settings}/about/about.c (100%) rename applications/{ => settings}/about/application.fam (100%) create mode 100644 applications/settings/application.fam create mode 100644 applications/settings/bt_settings_app/application.fam rename applications/{bt => settings}/bt_settings_app/bt_settings_app.c (100%) rename applications/{bt => settings}/bt_settings_app/bt_settings_app.h (96%) rename applications/{bt => settings}/bt_settings_app/scenes/bt_settings_scene.c (100%) rename applications/{bt => settings}/bt_settings_app/scenes/bt_settings_scene.h (100%) rename applications/{bt => settings}/bt_settings_app/scenes/bt_settings_scene_config.h (100%) rename applications/{bt => settings}/bt_settings_app/scenes/bt_settings_scene_forget_dev_confirm.c (100%) rename applications/{bt => settings}/bt_settings_app/scenes/bt_settings_scene_forget_dev_success.c (100%) rename applications/{bt => settings}/bt_settings_app/scenes/bt_settings_scene_start.c (100%) create mode 100644 applications/settings/desktop_settings/application.fam rename applications/{desktop => settings}/desktop_settings/desktop_settings_app.c (98%) rename applications/{desktop => settings}/desktop_settings/desktop_settings_app.h (92%) rename applications/{desktop => settings}/desktop_settings/scenes/desktop_settings_scene.c (100%) rename applications/{desktop => settings}/desktop_settings/scenes/desktop_settings_scene.h (100%) rename applications/{desktop => settings}/desktop_settings/scenes/desktop_settings_scene_config.h (100%) rename applications/{desktop => settings}/desktop_settings/scenes/desktop_settings_scene_favorite.c (100%) rename applications/{desktop => settings}/desktop_settings/scenes/desktop_settings_scene_i.h (100%) rename applications/{desktop => settings}/desktop_settings/scenes/desktop_settings_scene_pin_auth.c (96%) rename applications/{desktop => settings}/desktop_settings/scenes/desktop_settings_scene_pin_disable.c (95%) rename applications/{desktop => settings}/desktop_settings/scenes/desktop_settings_scene_pin_error.c (95%) rename applications/{desktop => settings}/desktop_settings/scenes/desktop_settings_scene_pin_menu.c (100%) rename applications/{desktop => settings}/desktop_settings/scenes/desktop_settings_scene_pin_setup.c (96%) rename applications/{desktop => settings}/desktop_settings/scenes/desktop_settings_scene_pin_setup_done.c (96%) rename applications/{desktop => settings}/desktop_settings/scenes/desktop_settings_scene_pin_setup_howto.c (100%) rename applications/{desktop => settings}/desktop_settings/scenes/desktop_settings_scene_pin_setup_howto2.c (100%) rename applications/{desktop => settings}/desktop_settings/scenes/desktop_settings_scene_start.c (100%) rename applications/{desktop => settings}/desktop_settings/views/desktop_settings_view_pin_setup_howto.c (100%) rename applications/{desktop => settings}/desktop_settings/views/desktop_settings_view_pin_setup_howto.h (100%) rename applications/{desktop => settings}/desktop_settings/views/desktop_settings_view_pin_setup_howto2.c (100%) rename applications/{desktop => settings}/desktop_settings/views/desktop_settings_view_pin_setup_howto2.h (100%) rename applications/{dolphin => settings/dolphin_passport}/application.fam (56%) rename applications/{dolphin/passport => settings/dolphin_passport}/passport.c (100%) create mode 100644 applications/settings/notification_settings/application.fam rename applications/{notification => settings/notification_settings}/notification_settings_app.c (99%) create mode 100644 applications/settings/power_settings_app/application.fam rename applications/{power => settings}/power_settings_app/power_settings_app.c (100%) rename applications/{power => settings}/power_settings_app/power_settings_app.h (100%) rename applications/{power => settings}/power_settings_app/scenes/power_settings_scene.c (100%) rename applications/{power => settings}/power_settings_app/scenes/power_settings_scene.h (100%) rename applications/{power => settings}/power_settings_app/scenes/power_settings_scene_battery_info.c (100%) rename applications/{power => settings}/power_settings_app/scenes/power_settings_scene_config.h (100%) rename applications/{power => settings}/power_settings_app/scenes/power_settings_scene_power_off.c (100%) rename applications/{power => settings}/power_settings_app/scenes/power_settings_scene_reboot.c (100%) rename applications/{power => settings}/power_settings_app/scenes/power_settings_scene_start.c (100%) rename applications/{power => settings}/power_settings_app/views/battery_info.c (100%) rename applications/{power => settings}/power_settings_app/views/battery_info.h (100%) rename applications/{ => settings}/storage_settings/application.fam (100%) rename applications/{ => settings}/storage_settings/scenes/storage_settings_scene.c (100%) rename applications/{ => settings}/storage_settings/scenes/storage_settings_scene.h (100%) rename applications/{ => settings}/storage_settings/scenes/storage_settings_scene_benchmark.c (100%) rename applications/{ => settings}/storage_settings/scenes/storage_settings_scene_config.h (100%) rename applications/{ => settings}/storage_settings/scenes/storage_settings_scene_factory_reset.c (100%) rename applications/{ => settings}/storage_settings/scenes/storage_settings_scene_format_confirm.c (100%) rename applications/{ => settings}/storage_settings/scenes/storage_settings_scene_formatting.c (100%) rename applications/{ => settings}/storage_settings/scenes/storage_settings_scene_internal_info.c (100%) rename applications/{ => settings}/storage_settings/scenes/storage_settings_scene_sd_info.c (100%) rename applications/{ => settings}/storage_settings/scenes/storage_settings_scene_start.c (100%) rename applications/{ => settings}/storage_settings/scenes/storage_settings_scene_unmount_confirm.c (100%) rename applications/{ => settings}/storage_settings/scenes/storage_settings_scene_unmounted.c (100%) rename applications/{ => settings}/storage_settings/storage_settings.c (100%) rename applications/{ => settings}/storage_settings/storage_settings.h (100%) create mode 100644 applications/settings/system/application.fam rename applications/{ => settings}/system/system_settings.c (100%) rename applications/{ => settings}/system/system_settings.h (100%) rename applications/{ => system}/storage_move_to_sd/application.fam (100%) rename applications/{ => system}/storage_move_to_sd/scenes/storage_move_to_sd_scene.c (100%) rename applications/{ => system}/storage_move_to_sd/scenes/storage_move_to_sd_scene.h (100%) rename applications/{ => system}/storage_move_to_sd/scenes/storage_move_to_sd_scene_config.h (100%) rename applications/{ => system}/storage_move_to_sd/scenes/storage_move_to_sd_scene_confirm.c (100%) rename applications/{ => system}/storage_move_to_sd/scenes/storage_move_to_sd_scene_progress.c (100%) rename applications/{ => system}/storage_move_to_sd/storage_move_to_sd.c (100%) rename applications/{ => system}/storage_move_to_sd/storage_move_to_sd.h (100%) rename applications/{ => system}/updater/application.fam (100%) rename applications/{ => system}/updater/cli/updater_cli.c (100%) rename applications/{ => system}/updater/scenes/updater_scene.c (100%) rename applications/{ => system}/updater/scenes/updater_scene.h (100%) rename applications/{ => system}/updater/scenes/updater_scene_config.h (100%) rename applications/{ => system}/updater/scenes/updater_scene_error.c (98%) rename applications/{ => system}/updater/scenes/updater_scene_loadcfg.c (99%) rename applications/{ => system}/updater/scenes/updater_scene_main.c (98%) rename applications/{ => system}/updater/updater.c (100%) rename applications/{ => system}/updater/updater_i.h (100%) rename applications/{ => system}/updater/util/update_task.c (100%) rename applications/{ => system}/updater/util/update_task.h (100%) rename applications/{ => system}/updater/util/update_task_i.h (100%) rename applications/{ => system}/updater/util/update_task_worker_backup.c (100%) rename applications/{ => system}/updater/util/update_task_worker_flasher.c (100%) rename applications/{ => system}/updater/views/updater_main.c (100%) rename applications/{ => system}/updater/views/updater_main.h (100%) create mode 100644 applications_user/.gitignore create mode 100644 applications_user/README.md delete mode 100644 assets/resources/Manifest create mode 100644 debug/flipperapps.py create mode 100644 documentation/AppsOnSDCard.md create mode 100644 firmware/targets/f7/api_symbols.csv rename firmware/targets/f7/furi_hal/{furi_hal_usb_cdc_i.h => furi_hal_usb_cdc.h} (91%) create mode 100644 firmware/targets/f7/platform_specific/intrinsic_export.h delete mode 100644 lib/ST25RFAL002/include/rfal_picopass.h create mode 160000 lib/cxxheaderparser create mode 100644 lib/flipper_application/SConscript create mode 100644 lib/flipper_application/application_manifest.h create mode 100644 lib/flipper_application/elf/elf.h create mode 100644 lib/flipper_application/elf/elf_api_interface.h create mode 100644 lib/flipper_application/flipper_applicaiton_i.c create mode 100644 lib/flipper_application/flipper_application.c create mode 100644 lib/flipper_application/flipper_application.h create mode 100644 lib/flipper_application/flipper_application_i.h delete mode 100644 lib/loclass.scons create mode 100644 lib/print/wrappers.h create mode 100644 scripts/runfap.py create mode 100644 site_scons/extapps.scons create mode 100644 site_scons/fbt/elfmanifest.py create mode 100644 site_scons/fbt/sdk.py create mode 100644 site_scons/site_tools/fbt_sdk.py diff --git a/.clang-format b/.clang-format index 3337a7409cf..4b76f7fa43b 100644 --- a/.clang-format +++ b/.clang-format @@ -1,86 +1,191 @@ +--- +Language: Cpp AccessModifierOffset: -4 AlignAfterOpenBracket: AlwaysBreak -AlignConsecutiveAssignments: false -AlignConsecutiveDeclarations: false +AlignArrayOfStructures: None +AlignConsecutiveMacros: None +AlignConsecutiveAssignments: None +AlignConsecutiveBitFields: None +AlignConsecutiveDeclarations: None AlignEscapedNewlines: Left -AlignOperands: true +AlignOperands: Align AlignTrailingComments: false +AllowAllArgumentsOnNextLine: true AllowAllParametersOfDeclarationOnNextLine: false +AllowShortEnumsOnASingleLine: true AllowShortBlocksOnASingleLine: Never AllowShortCaseLabelsOnASingleLine: false AllowShortFunctionsOnASingleLine: None -AllowShortIfStatementsOnASingleLine: true +AllowShortLambdasOnASingleLine: All +AllowShortIfStatementsOnASingleLine: WithoutElse AllowShortLoopsOnASingleLine: true AlwaysBreakAfterDefinitionReturnType: None AlwaysBreakAfterReturnType: None AlwaysBreakBeforeMultilineStrings: false -AlwaysBreakTemplateDeclarations: false +AlwaysBreakTemplateDeclarations: Yes +AttributeMacros: + - __capability BinPackArguments: false BinPackParameters: false +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: Never + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + BeforeLambdaBody: false + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true BreakBeforeBinaryOperators: None +BreakBeforeConceptDeclarations: true BreakBeforeBraces: Attach +BreakBeforeInheritanceComma: false +BreakInheritanceList: BeforeColon BreakBeforeTernaryOperators: false +BreakConstructorInitializersBeforeComma: false BreakConstructorInitializers: BeforeComma +BreakAfterJavaFieldAnnotations: false BreakStringLiterals: false -ColumnLimit: 99 +ColumnLimit: 99 +CommentPragmas: '^ IWYU pragma:' +QualifierAlignment: Leave CompactNamespaces: false -ConstructorInitializerAllOnOneLineOrOnePerLine: false ConstructorInitializerIndentWidth: 4 ContinuationIndentWidth: 4 Cpp11BracedListStyle: true +DeriveLineEnding: true DerivePointerAlignment: false -DisableFormat: false +DisableFormat: false +EmptyLineAfterAccessModifier: Never +EmptyLineBeforeAccessModifier: LogicalBlock ExperimentalAutoDetectBinPacking: false +PackConstructorInitializers: BinPack +BasedOnStyle: '' +ConstructorInitializerAllOnOneLineOrOnePerLine: false +AllowAllConstructorInitializersOnNextLine: true FixNamespaceComments: false - -IncludeBlocks: Preserve +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IfMacros: + - KJ_IF_MAYBE +IncludeBlocks: Preserve IncludeCategories: - - Regex: '.*' - Priority: 1 + - Regex: '.*' + Priority: 1 + SortPriority: 0 + CaseSensitive: false + - Regex: '^(<|"(gtest|gmock|isl|json)/)' + Priority: 3 + SortPriority: 0 + CaseSensitive: false + - Regex: '.*' + Priority: 1 + SortPriority: 0 + CaseSensitive: false IncludeIsMainRegex: '(Test)?$' +IncludeIsMainSourceRegex: '' +IndentAccessModifiers: false IndentCaseLabels: false +IndentCaseBlocks: false +IndentGotoLabels: true IndentPPDirectives: None -IndentWidth: 4 +IndentExternBlock: AfterExternBlock +IndentRequires: false +IndentWidth: 4 IndentWrappedFunctionNames: true +InsertTrailingCommas: None JavaScriptQuotes: Leave JavaScriptWrapImports: true KeepEmptyLinesAtTheStartOfBlocks: false +LambdaBodyIndentation: Signature MacroBlockBegin: '' -MacroBlockEnd: '' +MacroBlockEnd: '' MaxEmptyLinesToKeep: 1 NamespaceIndentation: None ObjCBinPackProtocolList: Auto ObjCBlockIndentWidth: 4 +ObjCBreakBeforeNestedBlockParam: true ObjCSpaceAfterProperty: true ObjCSpaceBeforeProtocolList: true - -# Taken from git's rules PenaltyBreakAssignment: 10 PenaltyBreakBeforeFirstCallParameter: 30 PenaltyBreakComment: 10 PenaltyBreakFirstLessLess: 0 +PenaltyBreakOpenParenthesis: 0 PenaltyBreakString: 10 +PenaltyBreakTemplateDeclaration: 10 PenaltyExcessCharacter: 100 PenaltyReturnTypeOnItsOwnLine: 60 - +PenaltyIndentedWhitespace: 0 PointerAlignment: Left -ReflowComments: false -SortIncludes: false +PPIndentWidth: -1 +ReferenceAlignment: Pointer +ReflowComments: false +RemoveBracesLLVM: false +SeparateDefinitionBlocks: Leave +ShortNamespaceLines: 1 +SortIncludes: Never +SortJavaStaticImport: Before SortUsingDeclarations: false SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false SpaceAfterTemplateKeyword: true SpaceBeforeAssignmentOperators: true +SpaceBeforeCaseColon: false +SpaceBeforeCpp11BracedList: false SpaceBeforeCtorInitializerColon: true SpaceBeforeInheritanceColon: true SpaceBeforeParens: Never +SpaceBeforeParensOptions: + AfterControlStatements: false + AfterForeachMacros: false + AfterFunctionDefinitionName: false + AfterFunctionDeclarationName: false + AfterIfMacros: false + AfterOverloadedOperator: false + BeforeNonEmptyParentheses: false +SpaceAroundPointerQualifiers: Default SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyBlock: false SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 1 -SpacesInAngles: false +SpacesInAngles: Never +SpacesInConditionalStatement: false SpacesInContainerLiterals: false SpacesInCStyleCastParentheses: false +SpacesInLineCommentPrefix: + Minimum: 1 + Maximum: -1 SpacesInParentheses: false SpacesInSquareBrackets: false -Standard: Cpp03 -TabWidth: 4 -UseTab: Never +SpaceBeforeSquareBrackets: false +BitFieldColonSpacing: Both +Standard: c++03 +StatementAttributeLikeMacros: + - Q_EMIT +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 4 +UseCRLF: false +UseTab: Never +WhitespaceSensitiveMacros: + - STRINGIZE + - PP_STRINGIZE + - BOOST_PP_STRINGIZE + - NS_SWIFT_NAME + - CF_SWIFT_NAME +... + diff --git a/.gitmodules b/.gitmodules index b580a8c71a8..ba764498120 100644 --- a/.gitmodules +++ b/.gitmodules @@ -28,3 +28,6 @@ [submodule "lib/mbedtls"] path = lib/mbedtls url = https://github.com/Mbed-TLS/mbedtls.git +[submodule "lib/cxxheaderparser"] + path = lib/cxxheaderparser + url = https://github.com/robotpy/cxxheaderparser.git diff --git a/.vscode/example/launch.json b/.vscode/example/launch.json index bdad042859c..f9470a74091 100644 --- a/.vscode/example/launch.json +++ b/.vscode/example/launch.json @@ -31,7 +31,10 @@ ], "postAttachCommands": [ // "attach 1", - "compare-sections", + // "compare-sections", + "source debug/flipperapps.py", + // "source debug/FreeRTOS/FreeRTOS.py", + // "svd_load debug/STM32WB55_CM4.svd" ] // "showDevDebugOutput": "raw", }, @@ -50,7 +53,8 @@ "attach 1", "set confirm off", "set mem inaccessible-by-default off", - "compare-sections", + "source debug/flipperapps.py", + // "compare-sections", ] // "showDevDebugOutput": "raw", }, @@ -65,6 +69,9 @@ "device": "STM32WB55RG", "svdFile": "./debug/STM32WB55_CM4.svd", "rtos": "FreeRTOS", + "postAttachCommands": [ + "source debug/flipperapps.py", + ] // "showDevDebugOutput": "raw", }, { @@ -73,7 +80,7 @@ "request": "launch", "program": "./lib/scons/scripts/scons.py", "args": [ - "sdk" + "plugin_dist" ] }, { diff --git a/.vscode/example/settings.json b/.vscode/example/settings.json index 925c2e0763b..0a05da67cc8 100644 --- a/.vscode/example/settings.json +++ b/.vscode/example/settings.json @@ -12,6 +12,9 @@ "cortex-debug.openocdPath.windows": "${workspaceFolder}/toolchain/i686-windows/openocd/bin/openocd.exe", "cortex-debug.openocdPath.linux": "${workspaceFolder}/toolchain/x86_64-linux/openocd/bin/openocd", "cortex-debug.openocdPath.osx": "${workspaceFolder}/toolchain/x86_64-darwin/openocd/bin/openocd", + "cortex-debug.gdbPath.windows": "${workspaceFolder}/toolchain/i686-windows/bin/arm-none-eabi-gdb-py.bat", + "cortex-debug.gdbPath.linux": "${workspaceFolder}/toolchain/x86_64-linux/bin/arm-none-eabi-gdb-py", + "cortex-debug.gdbPath.osx": "${workspaceFolder}/toolchain/x86_64-darwin/bin/arm-none-eabi-gdb", "editor.formatOnSave": true, "files.associations": { "*.scons": "python", diff --git a/.vscode/example/tasks.json b/.vscode/example/tasks.json index 32efa006827..3946d05ccbe 100644 --- a/.vscode/example/tasks.json +++ b/.vscode/example/tasks.json @@ -93,11 +93,41 @@ "type": "shell", "command": "./fbt FIRMWARE_APP_SET=unit_tests FORCE=1 flash_usb" }, + { + "label": "[Debug] Flash (USB, with resources)", + "group": "build", + "type": "shell", + "command": "./fbt FORCE=1 flash_usb_full" + }, { "label": "[Release] Flash (USB, with resources)", "group": "build", "type": "shell", "command": "./fbt COMPACT=1 DEBUG=0 FORCE=1 flash_usb_full" }, + { + "label": "[Debug] Build FAPs", + "group": "build", + "type": "shell", + "command": "./fbt plugin_dist" + }, + { + "label": "[Release] Build FAPs", + "group": "build", + "type": "shell", + "command": "./fbt COMPACT=1 DEBUG=0 plugin_dist" + }, + { + "label": "[Debug] Launch App on Flipper", + "group": "build", + "type": "shell", + "command": "./fbt launch_app APPSRC=${relativeFileDirname}" + }, + { + "label": "[Release] Launch App on Flipper", + "group": "build", + "type": "shell", + "command": "./fbt COMPACT=1 DEBUG=0 launch_app APPSRC=${relativeFileDirname}" + } ] } \ No newline at end of file diff --git a/ReadMe.md b/ReadMe.md index 2e3b87a675c..e848e18a4fe 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -14,6 +14,10 @@ You should clone with $ git clone --recursive https://github.com/flipperdevices/flipperzero-firmware.git ``` +# Read the Docs + +Check out details on [how to build firmware](documentation/fbt.md), [write applications](documentation/AppsOnSDCard.md), [un-brick your device](documentation/KeyCombo.md) and more in `documentation` folder. + # Update firmware [Get Latest Firmware from Update Server](https://update.flipperzero.one/) diff --git a/SConstruct b/SConstruct index 4462f2f3005..f39bba686fc 100644 --- a/SConstruct +++ b/SConstruct @@ -9,9 +9,10 @@ import os import subprocess +DefaultEnvironment(tools=[]) + EnsurePythonVersion(3, 8) -DefaultEnvironment(tools=[]) # Progress(["OwO\r", "owo\r", "uwu\r", "owo\r"], interval=15) @@ -58,6 +59,8 @@ distenv = coreenv.Clone( "-ex", "source debug/FreeRTOS/FreeRTOS.py", "-ex", + "source debug/flipperapps.py", + "-ex", "source debug/PyCortexMDebug/PyCortexMDebug.py", "-ex", "svd_load ${SVD_FILE}", @@ -160,6 +163,28 @@ if GetOption("fullenv") or any( basic_dist = distenv.DistCommand("fw_dist", distenv["DIST_DEPENDS"]) distenv.Default(basic_dist) +dist_dir = distenv.GetProjetDirName() +plugin_dist = [ + distenv.Install( + f"#/dist/{dist_dir}/apps/debug_elf", + firmware_env["FW_EXTAPPS"]["debug"].values(), + ), + *( + distenv.Install(f"#/dist/{dist_dir}/apps/{dist_entry[0]}", dist_entry[1]) + for dist_entry in firmware_env["FW_EXTAPPS"]["dist"].values() + ), +] +Depends(plugin_dist, firmware_env["FW_EXTAPPS"]["validators"].values()) +Alias("plugin_dist", plugin_dist) +# distenv.Default(plugin_dist) + +plugin_resources_dist = list( + distenv.Install(f"#/assets/resources/apps/{dist_entry[0]}", dist_entry[1]) + for dist_entry in firmware_env["FW_EXTAPPS"]["dist"].values() +) +distenv.Depends(firmware_env["FW_RESOURCES"], plugin_resources_dist) + + # Target for bundling core2 package for qFlipper copro_dist = distenv.CoproBuilder( distenv.Dir("assets/core2_firmware"), @@ -240,7 +265,6 @@ firmware_env.Append( "site_scons", "scripts", # Extra files - "applications/extapps.scons", "SConstruct", "firmware.scons", "fbt_options.py", diff --git a/applications/ReadMe.md b/applications/ReadMe.md index cb2f5628d28..aee612578ee 100644 --- a/applications/ReadMe.md +++ b/applications/ReadMe.md @@ -1,38 +1,90 @@ # Structure -- `about` - Small About application that shows flipper info -- `accessor` - Wiegand server +- `application.h` - Firmware application list header + + +## debug + +Applications for factory testing the Flipper. + +- `accessor` - Wiegand server +- `battery_test_app` - Battery debug app +- `blink_test` - LED blinker +- `bt_debug_app` - BT test app. Requires full BT stack installed +- `display_test` - Various display tests & tweaks +- `file_browser_test` - Test UI for file picker +- `keypad_test` - Keypad test +- `lfrfid_debug` - LF RFID debug tool +- `text_box_test` - UI tests +- `uart_echo` - UART mode test +- `unit_tests` - Unit tests +- `usb_mouse` - USB HID test +- `usb_test` - Other USB tests +- `vibro_test` - Vibro test + + +## main + +Applications for main Flipper menu. + - `archive` - Archive and file manager - `bad_usb` - Bad USB application +- `fap_loader` - External applications loader +- `gpio` - GPIO application: includes USART bridge and GPIO control +- `ibutton` - iButton application, onewire keys and more +- `infrared` - Infrared application, controls your IR devices +- `lfrfid` - LF RFID application +- `nfc` - NFC application, HF rfid, EMV and etc +- `subghz` - SubGhz application, 433 fobs and etc +- `u2f` - U2F Application + + +## plugins + +Extra apps for Plugins & App Loader menus. + +- `bt_hid_app` - BT Remote controller +- `music_player` - Music player app (demo) +- `picopass` - Picopass tool +- `snake_game` - Snake game application + + +## services + +Background services providing system APIs to applications. + - `bt` - BLE service and application - `cli` - Console service and API - `crypto` - Crypto cli tools -- `debug_tools` - Different tools that we use for debug - `desktop` - Desktop service - `dialogs` - Dialogs service: GUI Dialogs for your app - `dolphin` - Dolphin service and supplementary apps -- `gpio` - GPIO application: includes USART bridge and GPIO control - `gui` - GUI service and API -- `ibutton` - iButton application, onewire keys and more - `input` - Input service -- `infrared` - Infrared application, controls your IR devices -- `lfrfid` - LF RFID application -- `lfrfid_debug` - LF RFID debug tool - `loader` - Application loader service -- `music_player` - Music player app (demo) -- `nfc` - NFC application, HF rfid, EMV and etc - `notification` - Notification service - `power` - Power service -- `power_observer` - Power debug tool - `rpc` - RPC service and API -- `scened_app_example` - C++ application example -- `snake_game` - Snake game application - `storage` - Storage service, internal + sdcard + + +## settings + +Small applications providing configuration for basic firmware and its services. + +- `about` - Small About application that shows flipper info +- `bt_settings_app` - Bluetooth options +- `desktop_settings` - Desktop configuration +- `dolphin_passport` - Dolphin passport app +- `notification_settings` - LCD brightness, sound volume, etc configuration +- `power_settings_app` - Basic power options - `storage_settings` - Storage settings app -- `subghz` - SubGhz application, 433 fobs and etc -- `system` - System settings, tools and API -- `tests` - Unit tests and etc -- `u2f` - U2F Application -- `updater` - Update service & application +- `system` - System settings -- `application.h` - Firmware application list header + +## system + +Utility apps not visible in other menus. + +- `storage_move_to_sd` - Data migration tool for internal storage +- `updater` - Update service & application diff --git a/applications/bt/application.fam b/applications/bt/application.fam deleted file mode 100644 index 248386ff305..00000000000 --- a/applications/bt/application.fam +++ /dev/null @@ -1,66 +0,0 @@ -App( - appid="bt", - name="BtSrv", - apptype=FlipperAppType.SERVICE, - entry_point="bt_srv", - cdefines=["SRV_BT"], - requires=[ - "cli", - "dialogs", - ], - provides=[ - "bt_start", - "bt_settings", - "bt_debug", - ], - stack_size=1 * 1024, - order=20, -) - -App( - appid="bt_start", - apptype=FlipperAppType.STARTUP, - entry_point="bt_on_system_start", - order=70, -) - -App( - appid="bt_settings", - name="Bluetooth", - apptype=FlipperAppType.SETTINGS, - entry_point="bt_settings_app", - stack_size=1 * 1024, - requires=[ - "bt", - "gui", - ], - order=10, -) - -App( - appid="bt_debug", - name="Bluetooth Debug", - apptype=FlipperAppType.DEBUG, - entry_point="bt_debug_app", - stack_size=1 * 1024, - requires=[ - "bt", - "gui", - "dialogs", - ], - order=110, -) - -App( - appid="bt_hid", - name="Bluetooth Remote", - apptype=FlipperAppType.PLUGIN, - entry_point="bt_hid_app", - stack_size=1 * 1024, - cdefines=["APP_BLE_HID"], - requires=[ - "bt", - "gui", - ], - order=10, -) diff --git a/applications/accessor/accessor.cpp b/applications/debug/accessor/accessor.cpp similarity index 100% rename from applications/accessor/accessor.cpp rename to applications/debug/accessor/accessor.cpp diff --git a/applications/accessor/accessor_app.cpp b/applications/debug/accessor/accessor_app.cpp similarity index 100% rename from applications/accessor/accessor_app.cpp rename to applications/debug/accessor/accessor_app.cpp diff --git a/applications/accessor/accessor_app.h b/applications/debug/accessor/accessor_app.h similarity index 100% rename from applications/accessor/accessor_app.h rename to applications/debug/accessor/accessor_app.h diff --git a/applications/accessor/accessor_event.h b/applications/debug/accessor/accessor_event.h similarity index 100% rename from applications/accessor/accessor_event.h rename to applications/debug/accessor/accessor_event.h diff --git a/applications/accessor/accessor_view_manager.cpp b/applications/debug/accessor/accessor_view_manager.cpp similarity index 100% rename from applications/accessor/accessor_view_manager.cpp rename to applications/debug/accessor/accessor_view_manager.cpp diff --git a/applications/accessor/accessor_view_manager.h b/applications/debug/accessor/accessor_view_manager.h similarity index 100% rename from applications/accessor/accessor_view_manager.h rename to applications/debug/accessor/accessor_view_manager.h diff --git a/applications/accessor/application.fam b/applications/debug/accessor/application.fam similarity index 88% rename from applications/accessor/application.fam rename to applications/debug/accessor/application.fam index 8a94049e14a..93fc9bc3f86 100644 --- a/applications/accessor/application.fam +++ b/applications/debug/accessor/application.fam @@ -7,4 +7,5 @@ App( requires=["gui"], stack_size=4 * 1024, order=40, + fap_category="Debug", ) diff --git a/applications/accessor/helpers/wiegand.cpp b/applications/debug/accessor/helpers/wiegand.cpp similarity index 100% rename from applications/accessor/helpers/wiegand.cpp rename to applications/debug/accessor/helpers/wiegand.cpp diff --git a/applications/accessor/helpers/wiegand.h b/applications/debug/accessor/helpers/wiegand.h similarity index 100% rename from applications/accessor/helpers/wiegand.h rename to applications/debug/accessor/helpers/wiegand.h diff --git a/applications/accessor/scene/accessor_scene_generic.h b/applications/debug/accessor/scene/accessor_scene_generic.h similarity index 100% rename from applications/accessor/scene/accessor_scene_generic.h rename to applications/debug/accessor/scene/accessor_scene_generic.h diff --git a/applications/accessor/scene/accessor_scene_start.cpp b/applications/debug/accessor/scene/accessor_scene_start.cpp similarity index 100% rename from applications/accessor/scene/accessor_scene_start.cpp rename to applications/debug/accessor/scene/accessor_scene_start.cpp diff --git a/applications/accessor/scene/accessor_scene_start.h b/applications/debug/accessor/scene/accessor_scene_start.h similarity index 100% rename from applications/accessor/scene/accessor_scene_start.h rename to applications/debug/accessor/scene/accessor_scene_start.h diff --git a/applications/debug/application.fam b/applications/debug/application.fam new file mode 100644 index 00000000000..a33b3693dfe --- /dev/null +++ b/applications/debug/application.fam @@ -0,0 +1,16 @@ +App( + appid="debug_apps", + name="Basic debug apps bundle", + apptype=FlipperAppType.METAPACKAGE, + provides=[ + "blink_test", + "vibro_test", + "keypad_test", + "usb_test", + "usb_mouse", + "uart_echo", + "display_test", + "text_box_test", + "file_browser_test", + ], +) diff --git a/applications/debug/battery_test_app/application.fam b/applications/debug/battery_test_app/application.fam new file mode 100644 index 00000000000..b388445cc39 --- /dev/null +++ b/applications/debug/battery_test_app/application.fam @@ -0,0 +1,14 @@ +App( + appid="battery_test", + name="Battery Test", + apptype=FlipperAppType.DEBUG, + entry_point="battery_test_app", + cdefines=["APP_BATTERY_TEST"], + requires=[ + "gui", + "power", + ], + stack_size=1 * 1024, + order=130, + fap_category="Debug", +) diff --git a/applications/power/battery_test_app/battery_test_app.c b/applications/debug/battery_test_app/battery_test_app.c similarity index 92% rename from applications/power/battery_test_app/battery_test_app.c rename to applications/debug/battery_test_app/battery_test_app.c index ab6889dc8df..eabf3c04b44 100644 --- a/applications/power/battery_test_app/battery_test_app.c +++ b/applications/debug/battery_test_app/battery_test_app.c @@ -27,7 +27,7 @@ static void battery_test_battery_info_update_model(void* context) { .charge = app->info.charge, .health = app->info.health, }; - battery_info_set_data(app->batery_info, &battery_info_data); + battery_info_set_data(app->battery_info, &battery_info_data); notification_message(app->notifications, &sequence_display_backlight_on); } @@ -48,13 +48,13 @@ BatteryTestApp* battery_test_alloc() { view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); // Views - app->batery_info = battery_info_alloc(); + app->battery_info = battery_info_alloc(); view_set_previous_callback( - battery_info_get_view(app->batery_info), battery_test_exit_confirm_view); + battery_info_get_view(app->battery_info), battery_test_exit_confirm_view); view_dispatcher_add_view( app->view_dispatcher, BatteryTestAppViewBatteryInfo, - battery_info_get_view(app->batery_info)); + battery_info_get_view(app->battery_info)); app->dialog = dialog_ex_alloc(); dialog_ex_set_header(app->dialog, "Close Battery Test?", 64, 12, AlignCenter, AlignTop); @@ -76,7 +76,7 @@ void battery_test_free(BatteryTestApp* app) { // Views view_dispatcher_remove_view(app->view_dispatcher, BatteryTestAppViewBatteryInfo); - battery_info_free(app->batery_info); + battery_info_free(app->battery_info); view_dispatcher_remove_view(app->view_dispatcher, BatteryTestAppViewExitDialog); dialog_ex_free(app->dialog); // View dispatcher diff --git a/applications/power/battery_test_app/battery_test_app.h b/applications/debug/battery_test_app/battery_test_app.h similarity index 82% rename from applications/power/battery_test_app/battery_test_app.h rename to applications/debug/battery_test_app/battery_test_app.h index 9c17626da92..497cdde823f 100644 --- a/applications/power/battery_test_app/battery_test_app.h +++ b/applications/debug/battery_test_app/battery_test_app.h @@ -6,14 +6,15 @@ #include #include -#include +// FIXME +#include "../settings/power_settings_app/views/battery_info.h" typedef struct { Power* power; Gui* gui; NotificationApp* notifications; ViewDispatcher* view_dispatcher; - BatteryInfo* batery_info; + BatteryInfo* battery_info; DialogEx* dialog; PowerInfo info; } BatteryTestApp; diff --git a/applications/debug/blink_test/application.fam b/applications/debug/blink_test/application.fam new file mode 100644 index 00000000000..c6a8a922a41 --- /dev/null +++ b/applications/debug/blink_test/application.fam @@ -0,0 +1,11 @@ +App( + appid="blink_test", + name="Blink Test", + apptype=FlipperAppType.DEBUG, + entry_point="blink_test_app", + cdefines=["APP_BLINK"], + requires=["gui"], + stack_size=1 * 1024, + order=10, + fap_category="Debug", +) diff --git a/applications/debug_tools/blink_test.c b/applications/debug/blink_test/blink_test.c similarity index 100% rename from applications/debug_tools/blink_test.c rename to applications/debug/blink_test/blink_test.c diff --git a/applications/debug/bt_debug_app/application.fam b/applications/debug/bt_debug_app/application.fam new file mode 100644 index 00000000000..8ed1ccc054f --- /dev/null +++ b/applications/debug/bt_debug_app/application.fam @@ -0,0 +1,18 @@ +App( + appid="bt_debug", + name="Bluetooth Debug", + apptype=FlipperAppType.DEBUG, + entry_point="bt_debug_app", + cdefines=["SRV_BT"], + requires=[ + "bt", + "gui", + "dialogs", + ], + provides=[ + "bt_debug", + ], + stack_size=1 * 1024, + order=110, + fap_category="Debug", +) diff --git a/applications/bt/bt_debug_app/bt_debug_app.c b/applications/debug/bt_debug_app/bt_debug_app.c similarity index 100% rename from applications/bt/bt_debug_app/bt_debug_app.c rename to applications/debug/bt_debug_app/bt_debug_app.c diff --git a/applications/bt/bt_debug_app/bt_debug_app.h b/applications/debug/bt_debug_app/bt_debug_app.h similarity index 95% rename from applications/bt/bt_debug_app/bt_debug_app.h rename to applications/debug/bt_debug_app/bt_debug_app.h index c3657626e46..cd59e4d0034 100644 --- a/applications/bt/bt_debug_app/bt_debug_app.h +++ b/applications/debug/bt_debug_app/bt_debug_app.h @@ -9,7 +9,7 @@ #include #include "views/bt_carrier_test.h" #include "views/bt_packet_test.h" -#include "../bt_settings.h" +#include typedef struct { BtSettings settings; diff --git a/applications/bt/bt_debug_app/views/bt_carrier_test.c b/applications/debug/bt_debug_app/views/bt_carrier_test.c similarity index 100% rename from applications/bt/bt_debug_app/views/bt_carrier_test.c rename to applications/debug/bt_debug_app/views/bt_carrier_test.c diff --git a/applications/bt/bt_debug_app/views/bt_carrier_test.h b/applications/debug/bt_debug_app/views/bt_carrier_test.h similarity index 100% rename from applications/bt/bt_debug_app/views/bt_carrier_test.h rename to applications/debug/bt_debug_app/views/bt_carrier_test.h diff --git a/applications/bt/bt_debug_app/views/bt_packet_test.c b/applications/debug/bt_debug_app/views/bt_packet_test.c similarity index 100% rename from applications/bt/bt_debug_app/views/bt_packet_test.c rename to applications/debug/bt_debug_app/views/bt_packet_test.c diff --git a/applications/bt/bt_debug_app/views/bt_packet_test.h b/applications/debug/bt_debug_app/views/bt_packet_test.h similarity index 100% rename from applications/bt/bt_debug_app/views/bt_packet_test.h rename to applications/debug/bt_debug_app/views/bt_packet_test.h diff --git a/applications/bt/bt_debug_app/views/bt_test.c b/applications/debug/bt_debug_app/views/bt_test.c similarity index 100% rename from applications/bt/bt_debug_app/views/bt_test.c rename to applications/debug/bt_debug_app/views/bt_test.c diff --git a/applications/bt/bt_debug_app/views/bt_test.h b/applications/debug/bt_debug_app/views/bt_test.h similarity index 100% rename from applications/bt/bt_debug_app/views/bt_test.h rename to applications/debug/bt_debug_app/views/bt_test.h diff --git a/applications/bt/bt_debug_app/views/bt_test_types.h b/applications/debug/bt_debug_app/views/bt_test_types.h similarity index 100% rename from applications/bt/bt_debug_app/views/bt_test_types.h rename to applications/debug/bt_debug_app/views/bt_test_types.h diff --git a/applications/debug/display_test/application.fam b/applications/debug/display_test/application.fam new file mode 100644 index 00000000000..4b40322fbd1 --- /dev/null +++ b/applications/debug/display_test/application.fam @@ -0,0 +1,11 @@ +App( + appid="display_test", + name="Display Test", + apptype=FlipperAppType.DEBUG, + entry_point="display_test_app", + cdefines=["APP_DISPLAY_TEST"], + requires=["gui"], + stack_size=1 * 1024, + order=120, + fap_category="Debug", +) diff --git a/applications/debug_tools/display_test/display_test.c b/applications/debug/display_test/display_test.c similarity index 100% rename from applications/debug_tools/display_test/display_test.c rename to applications/debug/display_test/display_test.c diff --git a/applications/debug_tools/display_test/display_test.h b/applications/debug/display_test/display_test.h similarity index 100% rename from applications/debug_tools/display_test/display_test.h rename to applications/debug/display_test/display_test.h diff --git a/applications/debug_tools/display_test/view_display_test.c b/applications/debug/display_test/view_display_test.c similarity index 100% rename from applications/debug_tools/display_test/view_display_test.c rename to applications/debug/display_test/view_display_test.c diff --git a/applications/debug_tools/display_test/view_display_test.h b/applications/debug/display_test/view_display_test.h similarity index 100% rename from applications/debug_tools/display_test/view_display_test.h rename to applications/debug/display_test/view_display_test.h diff --git a/applications/debug/file_browser_test/application.fam b/applications/debug/file_browser_test/application.fam new file mode 100644 index 00000000000..5e4c7f467b0 --- /dev/null +++ b/applications/debug/file_browser_test/application.fam @@ -0,0 +1,11 @@ +App( + appid="file_browser_test", + name="File Browser Test", + apptype=FlipperAppType.DEBUG, + entry_point="file_browser_app", + cdefines=["APP_FILE_BROWSER_TEST"], + requires=["gui"], + stack_size=2 * 1024, + order=150, + fap_category="Debug", +) diff --git a/applications/debug_tools/file_browser_test/file_browser_app.c b/applications/debug/file_browser_test/file_browser_app.c similarity index 100% rename from applications/debug_tools/file_browser_test/file_browser_app.c rename to applications/debug/file_browser_test/file_browser_app.c diff --git a/applications/debug_tools/file_browser_test/file_browser_app_i.h b/applications/debug/file_browser_test/file_browser_app_i.h similarity index 100% rename from applications/debug_tools/file_browser_test/file_browser_app_i.h rename to applications/debug/file_browser_test/file_browser_app_i.h diff --git a/applications/debug_tools/file_browser_test/scenes/file_browser_scene.c b/applications/debug/file_browser_test/scenes/file_browser_scene.c similarity index 100% rename from applications/debug_tools/file_browser_test/scenes/file_browser_scene.c rename to applications/debug/file_browser_test/scenes/file_browser_scene.c diff --git a/applications/debug_tools/file_browser_test/scenes/file_browser_scene.h b/applications/debug/file_browser_test/scenes/file_browser_scene.h similarity index 100% rename from applications/debug_tools/file_browser_test/scenes/file_browser_scene.h rename to applications/debug/file_browser_test/scenes/file_browser_scene.h diff --git a/applications/debug_tools/file_browser_test/scenes/file_browser_scene_browser.c b/applications/debug/file_browser_test/scenes/file_browser_scene_browser.c similarity index 100% rename from applications/debug_tools/file_browser_test/scenes/file_browser_scene_browser.c rename to applications/debug/file_browser_test/scenes/file_browser_scene_browser.c diff --git a/applications/debug_tools/file_browser_test/scenes/file_browser_scene_config.h b/applications/debug/file_browser_test/scenes/file_browser_scene_config.h similarity index 100% rename from applications/debug_tools/file_browser_test/scenes/file_browser_scene_config.h rename to applications/debug/file_browser_test/scenes/file_browser_scene_config.h diff --git a/applications/debug_tools/file_browser_test/scenes/file_browser_scene_result.c b/applications/debug/file_browser_test/scenes/file_browser_scene_result.c similarity index 100% rename from applications/debug_tools/file_browser_test/scenes/file_browser_scene_result.c rename to applications/debug/file_browser_test/scenes/file_browser_scene_result.c diff --git a/applications/debug_tools/file_browser_test/scenes/file_browser_scene_start.c b/applications/debug/file_browser_test/scenes/file_browser_scene_start.c similarity index 100% rename from applications/debug_tools/file_browser_test/scenes/file_browser_scene_start.c rename to applications/debug/file_browser_test/scenes/file_browser_scene_start.c diff --git a/applications/debug/keypad_test/application.fam b/applications/debug/keypad_test/application.fam new file mode 100644 index 00000000000..6859af26f69 --- /dev/null +++ b/applications/debug/keypad_test/application.fam @@ -0,0 +1,11 @@ +App( + appid="keypad_test", + name="Keypad Test", + apptype=FlipperAppType.DEBUG, + entry_point="keypad_test_app", + cdefines=["APP_KEYPAD_TEST"], + requires=["gui"], + stack_size=1 * 1024, + order=30, + fap_category="Debug", +) diff --git a/applications/debug_tools/keypad_test.c b/applications/debug/keypad_test/keypad_test.c similarity index 100% rename from applications/debug_tools/keypad_test.c rename to applications/debug/keypad_test/keypad_test.c diff --git a/applications/lfrfid_debug/application.fam b/applications/debug/lfrfid_debug/application.fam similarity index 74% rename from applications/lfrfid_debug/application.fam rename to applications/debug/lfrfid_debug/application.fam index 910f65cf3cb..6844f9291be 100644 --- a/applications/lfrfid_debug/application.fam +++ b/applications/debug/lfrfid_debug/application.fam @@ -5,8 +5,11 @@ App( entry_point="lfrfid_debug_app", requires=[ "gui", - "lfrfid", + ], + provides=[ + "lfrfid_debug", ], stack_size=1 * 1024, order=100, + fap_category="Debug", ) diff --git a/applications/lfrfid_debug/lfrfid_debug.c b/applications/debug/lfrfid_debug/lfrfid_debug.c similarity index 100% rename from applications/lfrfid_debug/lfrfid_debug.c rename to applications/debug/lfrfid_debug/lfrfid_debug.c diff --git a/applications/lfrfid_debug/lfrfid_debug_i.h b/applications/debug/lfrfid_debug/lfrfid_debug_i.h similarity index 82% rename from applications/lfrfid_debug/lfrfid_debug_i.h rename to applications/debug/lfrfid_debug/lfrfid_debug_i.h index 368f1f15862..8e4e72388f1 100644 --- a/applications/lfrfid_debug/lfrfid_debug_i.h +++ b/applications/debug/lfrfid_debug/lfrfid_debug_i.h @@ -9,9 +9,8 @@ #include -#include - -#include +#include "views/lfrfid_debug_view_tune.h" +#include "scenes/lfrfid_debug_scene.h" typedef struct LfRfidDebug LfRfidDebug; diff --git a/applications/lfrfid_debug/scenes/lfrfid_debug_app_scene_start.c b/applications/debug/lfrfid_debug/scenes/lfrfid_debug_app_scene_start.c similarity index 100% rename from applications/lfrfid_debug/scenes/lfrfid_debug_app_scene_start.c rename to applications/debug/lfrfid_debug/scenes/lfrfid_debug_app_scene_start.c diff --git a/applications/lfrfid_debug/scenes/lfrfid_debug_app_scene_tune.c b/applications/debug/lfrfid_debug/scenes/lfrfid_debug_app_scene_tune.c similarity index 100% rename from applications/lfrfid_debug/scenes/lfrfid_debug_app_scene_tune.c rename to applications/debug/lfrfid_debug/scenes/lfrfid_debug_app_scene_tune.c diff --git a/applications/lfrfid_debug/scenes/lfrfid_debug_scene.c b/applications/debug/lfrfid_debug/scenes/lfrfid_debug_scene.c similarity index 100% rename from applications/lfrfid_debug/scenes/lfrfid_debug_scene.c rename to applications/debug/lfrfid_debug/scenes/lfrfid_debug_scene.c diff --git a/applications/lfrfid_debug/scenes/lfrfid_debug_scene.h b/applications/debug/lfrfid_debug/scenes/lfrfid_debug_scene.h similarity index 100% rename from applications/lfrfid_debug/scenes/lfrfid_debug_scene.h rename to applications/debug/lfrfid_debug/scenes/lfrfid_debug_scene.h diff --git a/applications/lfrfid_debug/scenes/lfrfid_debug_scene_config.h b/applications/debug/lfrfid_debug/scenes/lfrfid_debug_scene_config.h similarity index 100% rename from applications/lfrfid_debug/scenes/lfrfid_debug_scene_config.h rename to applications/debug/lfrfid_debug/scenes/lfrfid_debug_scene_config.h diff --git a/applications/lfrfid_debug/views/lfrfid_debug_view_tune.c b/applications/debug/lfrfid_debug/views/lfrfid_debug_view_tune.c similarity index 100% rename from applications/lfrfid_debug/views/lfrfid_debug_view_tune.c rename to applications/debug/lfrfid_debug/views/lfrfid_debug_view_tune.c diff --git a/applications/lfrfid_debug/views/lfrfid_debug_view_tune.h b/applications/debug/lfrfid_debug/views/lfrfid_debug_view_tune.h similarity index 100% rename from applications/lfrfid_debug/views/lfrfid_debug_view_tune.h rename to applications/debug/lfrfid_debug/views/lfrfid_debug_view_tune.h diff --git a/applications/debug/text_box_test/application.fam b/applications/debug/text_box_test/application.fam new file mode 100644 index 00000000000..3e54df9cc57 --- /dev/null +++ b/applications/debug/text_box_test/application.fam @@ -0,0 +1,11 @@ +App( + appid="text_box_test", + name="Text Box Test", + apptype=FlipperAppType.DEBUG, + entry_point="text_box_test_app", + cdefines=["APP_TEXT_BOX_TEST"], + requires=["gui"], + stack_size=1 * 1024, + order=140, + fap_category="Debug", +) diff --git a/applications/debug_tools/text_box_test.c b/applications/debug/text_box_test/text_box_test.c similarity index 100% rename from applications/debug_tools/text_box_test.c rename to applications/debug/text_box_test/text_box_test.c diff --git a/applications/debug/uart_echo/application.fam b/applications/debug/uart_echo/application.fam new file mode 100644 index 00000000000..8863a1a9424 --- /dev/null +++ b/applications/debug/uart_echo/application.fam @@ -0,0 +1,11 @@ +App( + appid="uart_echo", + name="UART Echo", + apptype=FlipperAppType.DEBUG, + entry_point="uart_echo_app", + cdefines=["APP_UART_ECHO"], + requires=["gui"], + stack_size=2 * 1024, + order=70, + fap_category="Debug", +) diff --git a/applications/debug_tools/uart_echo.c b/applications/debug/uart_echo/uart_echo.c similarity index 100% rename from applications/debug_tools/uart_echo.c rename to applications/debug/uart_echo/uart_echo.c diff --git a/applications/unit_tests/application.fam b/applications/debug/unit_tests/application.fam similarity index 90% rename from applications/unit_tests/application.fam rename to applications/debug/unit_tests/application.fam index 54e6feaee08..949bb3fc292 100644 --- a/applications/unit_tests/application.fam +++ b/applications/debug/unit_tests/application.fam @@ -10,7 +10,7 @@ App( App( appid="delay_test", name="Delay Test", - apptype=FlipperAppType.DEBUG, + apptype=FlipperAppType.SYSTEM, entry_point="delay_test_app", stack_size=1 * 1024, requires=["unit_tests"], diff --git a/applications/unit_tests/flipper_format/flipper_format_string_test.c b/applications/debug/unit_tests/flipper_format/flipper_format_string_test.c similarity index 100% rename from applications/unit_tests/flipper_format/flipper_format_string_test.c rename to applications/debug/unit_tests/flipper_format/flipper_format_string_test.c diff --git a/applications/unit_tests/flipper_format/flipper_format_test.c b/applications/debug/unit_tests/flipper_format/flipper_format_test.c similarity index 100% rename from applications/unit_tests/flipper_format/flipper_format_test.c rename to applications/debug/unit_tests/flipper_format/flipper_format_test.c diff --git a/applications/unit_tests/furi/furi_memmgr_test.c b/applications/debug/unit_tests/furi/furi_memmgr_test.c similarity index 100% rename from applications/unit_tests/furi/furi_memmgr_test.c rename to applications/debug/unit_tests/furi/furi_memmgr_test.c diff --git a/applications/unit_tests/furi/furi_pubsub_test.c b/applications/debug/unit_tests/furi/furi_pubsub_test.c similarity index 100% rename from applications/unit_tests/furi/furi_pubsub_test.c rename to applications/debug/unit_tests/furi/furi_pubsub_test.c diff --git a/applications/unit_tests/furi/furi_record_test.c b/applications/debug/unit_tests/furi/furi_record_test.c similarity index 100% rename from applications/unit_tests/furi/furi_record_test.c rename to applications/debug/unit_tests/furi/furi_record_test.c diff --git a/applications/unit_tests/furi/furi_test.c b/applications/debug/unit_tests/furi/furi_test.c similarity index 100% rename from applications/unit_tests/furi/furi_test.c rename to applications/debug/unit_tests/furi/furi_test.c diff --git a/applications/unit_tests/furi/furi_valuemutex_test.c b/applications/debug/unit_tests/furi/furi_valuemutex_test.c similarity index 100% rename from applications/unit_tests/furi/furi_valuemutex_test.c rename to applications/debug/unit_tests/furi/furi_valuemutex_test.c diff --git a/applications/unit_tests/infrared/infrared_test.c b/applications/debug/unit_tests/infrared/infrared_test.c similarity index 100% rename from applications/unit_tests/infrared/infrared_test.c rename to applications/debug/unit_tests/infrared/infrared_test.c diff --git a/applications/unit_tests/lfrfid/bit_lib_test.c b/applications/debug/unit_tests/lfrfid/bit_lib_test.c similarity index 100% rename from applications/unit_tests/lfrfid/bit_lib_test.c rename to applications/debug/unit_tests/lfrfid/bit_lib_test.c diff --git a/applications/unit_tests/lfrfid/lfrfid_protocols.c b/applications/debug/unit_tests/lfrfid/lfrfid_protocols.c similarity index 100% rename from applications/unit_tests/lfrfid/lfrfid_protocols.c rename to applications/debug/unit_tests/lfrfid/lfrfid_protocols.c diff --git a/applications/unit_tests/minunit.h b/applications/debug/unit_tests/minunit.h similarity index 100% rename from applications/unit_tests/minunit.h rename to applications/debug/unit_tests/minunit.h diff --git a/applications/unit_tests/minunit_vars.h b/applications/debug/unit_tests/minunit_vars.h similarity index 100% rename from applications/unit_tests/minunit_vars.h rename to applications/debug/unit_tests/minunit_vars.h diff --git a/applications/unit_tests/minunit_vars_ex.h b/applications/debug/unit_tests/minunit_vars_ex.h similarity index 100% rename from applications/unit_tests/minunit_vars_ex.h rename to applications/debug/unit_tests/minunit_vars_ex.h diff --git a/applications/unit_tests/nfc/nfc_test.c b/applications/debug/unit_tests/nfc/nfc_test.c similarity index 99% rename from applications/unit_tests/nfc/nfc_test.c rename to applications/debug/unit_tests/nfc/nfc_test.c index dcd162d139a..e8119992091 100644 --- a/applications/unit_tests/nfc/nfc_test.c +++ b/applications/debug/unit_tests/nfc/nfc_test.c @@ -1,6 +1,6 @@ #include #include -#include +#include #include #include #include diff --git a/applications/unit_tests/protocol_dict/protocol_dict_test.c b/applications/debug/unit_tests/protocol_dict/protocol_dict_test.c similarity index 100% rename from applications/unit_tests/protocol_dict/protocol_dict_test.c rename to applications/debug/unit_tests/protocol_dict/protocol_dict_test.c diff --git a/applications/unit_tests/rpc/rpc_test.c b/applications/debug/unit_tests/rpc/rpc_test.c similarity index 100% rename from applications/unit_tests/rpc/rpc_test.c rename to applications/debug/unit_tests/rpc/rpc_test.c diff --git a/applications/unit_tests/storage/dirwalk_test.c b/applications/debug/unit_tests/storage/dirwalk_test.c similarity index 100% rename from applications/unit_tests/storage/dirwalk_test.c rename to applications/debug/unit_tests/storage/dirwalk_test.c diff --git a/applications/unit_tests/storage/storage_test.c b/applications/debug/unit_tests/storage/storage_test.c similarity index 100% rename from applications/unit_tests/storage/storage_test.c rename to applications/debug/unit_tests/storage/storage_test.c diff --git a/applications/unit_tests/stream/stream_test.c b/applications/debug/unit_tests/stream/stream_test.c similarity index 100% rename from applications/unit_tests/stream/stream_test.c rename to applications/debug/unit_tests/stream/stream_test.c diff --git a/applications/unit_tests/subghz/subghz_test.c b/applications/debug/unit_tests/subghz/subghz_test.c similarity index 100% rename from applications/unit_tests/subghz/subghz_test.c rename to applications/debug/unit_tests/subghz/subghz_test.c diff --git a/applications/unit_tests/test_index.c b/applications/debug/unit_tests/test_index.c similarity index 100% rename from applications/unit_tests/test_index.c rename to applications/debug/unit_tests/test_index.c diff --git a/applications/unit_tests/varint/varint_test.c b/applications/debug/unit_tests/varint/varint_test.c similarity index 100% rename from applications/unit_tests/varint/varint_test.c rename to applications/debug/unit_tests/varint/varint_test.c diff --git a/applications/debug/usb_mouse/application.fam b/applications/debug/usb_mouse/application.fam new file mode 100644 index 00000000000..5c434004510 --- /dev/null +++ b/applications/debug/usb_mouse/application.fam @@ -0,0 +1,11 @@ +App( + appid="usb_mouse", + name="USB Mouse Demo", + apptype=FlipperAppType.DEBUG, + entry_point="usb_mouse_app", + cdefines=["APP_USB_MOUSE"], + requires=["gui"], + stack_size=1 * 1024, + order=60, + fap_category="Debug", +) diff --git a/applications/debug_tools/usb_mouse.c b/applications/debug/usb_mouse/usb_mouse.c similarity index 100% rename from applications/debug_tools/usb_mouse.c rename to applications/debug/usb_mouse/usb_mouse.c diff --git a/applications/debug/usb_test/application.fam b/applications/debug/usb_test/application.fam new file mode 100644 index 00000000000..27395c34d40 --- /dev/null +++ b/applications/debug/usb_test/application.fam @@ -0,0 +1,11 @@ +App( + appid="usb_test", + name="USB Test", + apptype=FlipperAppType.DEBUG, + entry_point="usb_test_app", + cdefines=["APP_USB_TEST"], + requires=["gui"], + stack_size=1 * 1024, + order=50, + fap_category="Debug", +) diff --git a/applications/debug_tools/usb_test.c b/applications/debug/usb_test/usb_test.c similarity index 100% rename from applications/debug_tools/usb_test.c rename to applications/debug/usb_test/usb_test.c diff --git a/applications/debug/vibro_test/application.fam b/applications/debug/vibro_test/application.fam new file mode 100644 index 00000000000..f7115cc9621 --- /dev/null +++ b/applications/debug/vibro_test/application.fam @@ -0,0 +1,11 @@ +App( + appid="vibro_test", + name="Vibro Test", + apptype=FlipperAppType.DEBUG, + entry_point="vibro_test_app", + cdefines=["APP_VIBRO_TEST"], + requires=["gui"], + stack_size=1 * 1024, + order=20, + fap_category="Debug", +) diff --git a/applications/debug_tools/vibro_test.c b/applications/debug/vibro_test/vibro_test.c similarity index 100% rename from applications/debug_tools/vibro_test.c rename to applications/debug/vibro_test/vibro_test.c diff --git a/applications/debug_tools/application.fam b/applications/debug_tools/application.fam deleted file mode 100644 index 8cb495b0c4a..00000000000 --- a/applications/debug_tools/application.fam +++ /dev/null @@ -1,115 +0,0 @@ -App( - appid="debug_apps", - name="Basic debug apps bundle", - apptype=FlipperAppType.METAPACKAGE, - provides=[ - "blink_test", - "vibro_test", - "keypad_test", - "usb_test", - "usb_mouse", - "uart_echo", - "display_test", - "text_box_test", - "file_browser_test", - ], -) - -App( - appid="blink_test", - name="Blink Test", - apptype=FlipperAppType.DEBUG, - entry_point="blink_test_app", - cdefines=["APP_BLINK"], - requires=["gui"], - stack_size=1 * 1024, - order=10, -) - -App( - appid="vibro_test", - name="Vibro Test", - apptype=FlipperAppType.DEBUG, - entry_point="vibro_test_app", - cdefines=["APP_VIBRO_TEST"], - requires=["gui"], - stack_size=1 * 1024, - order=20, -) - -App( - appid="keypad_test", - name="Keypad Test", - apptype=FlipperAppType.DEBUG, - entry_point="keypad_test_app", - cdefines=["APP_KEYPAD_TEST"], - requires=["gui"], - stack_size=1 * 1024, - order=30, -) - -App( - appid="usb_test", - name="USB Test", - apptype=FlipperAppType.DEBUG, - entry_point="usb_test_app", - cdefines=["APP_USB_TEST"], - requires=["gui"], - stack_size=1 * 1024, - order=50, -) - -App( - appid="usb_mouse", - name="USB Mouse Demo", - apptype=FlipperAppType.DEBUG, - entry_point="usb_mouse_app", - cdefines=["APP_USB_MOUSE"], - requires=["gui"], - stack_size=1 * 1024, - order=60, -) - -App( - appid="uart_echo", - name="UART Echo", - apptype=FlipperAppType.DEBUG, - entry_point="uart_echo_app", - cdefines=["APP_UART_ECHO"], - requires=["gui"], - stack_size=2 * 1024, - order=70, -) - -App( - appid="display_test", - name="Display Test", - apptype=FlipperAppType.DEBUG, - entry_point="display_test_app", - cdefines=["APP_DISPLAY_TEST"], - requires=["gui"], - stack_size=1 * 1024, - order=120, -) - -App( - appid="text_box_test", - name="Text Box Test", - apptype=FlipperAppType.DEBUG, - entry_point="text_box_test_app", - cdefines=["APP_TEXT_BOX_TEST"], - requires=["gui"], - stack_size=1 * 1024, - order=140, -) - -App( - appid="file_browser_test", - name="File Browser Test", - apptype=FlipperAppType.DEBUG, - entry_point="file_browser_app", - cdefines=["APP_FILE_BROWSER_TEST"], - requires=["gui"], - stack_size=2 * 1024, - order=150, -) diff --git a/applications/extapps.scons b/applications/extapps.scons deleted file mode 100644 index 11b59757620..00000000000 --- a/applications/extapps.scons +++ /dev/null @@ -1,58 +0,0 @@ -Import("ENV") - - -from fbt.appmanifest import FlipperAppType - -appenv = ENV.Clone(tools=["fbt_extapps"]) - -appenv.Replace( - LINKER_SCRIPT="application-ext", - STRIPFLAGS=[ - "--strip-debug", - "--strip-unneeded", - "-d", - "-g", - "-S", - ], -) - -appenv.AppendUnique( - CCFLAGS=[ - "-Os", - "-ggdb3", - "-mword-relocations", - "-mlong-calls", - "-fno-common", - "-nostdlib", - "-fvisibility=hidden", - ], - LINKFLAGS=[ - "-r", - "-s", - # "-Bsymbolic", - "-nostartfiles", - "-mlong-calls", - "-fno-common", - "-nostdlib", - "-Wl,--gc-sections", - "-Wl,--no-export-dynamic", - "-fvisibility=hidden", - "-Wl,-e${APP_ENTRY}", - "-Xlinker", - "-Map=${TARGET}.map", - ], -) - - -extapps = [] -for apptype in (FlipperAppType.PLUGIN, FlipperAppType.EXTERNAL): - for app in appenv["APPBUILD"].get_apps_of_type(apptype): - extapps.append(appenv.BuildAppElf(app)) - -# Ugly access to global option -if extra_app_list := GetOption("extra_ext_apps"): - for extra_app in extra_app_list.split(","): - extapps.append(appenv.BuildAppElf(appenv["APPMGR"].get(extra_app))) - -Alias(appenv["FIRMWARE_BUILD_CFG"] + "_extapps", extapps) -Return("extapps") diff --git a/applications/gui/application.fam b/applications/gui/application.fam deleted file mode 100644 index 0116f5b88a3..00000000000 --- a/applications/gui/application.fam +++ /dev/null @@ -1,13 +0,0 @@ -App( - appid="gui", - name="GuiSrv", - apptype=FlipperAppType.SERVICE, - entry_point="gui_srv", - cdefines=["SRV_GUI"], - requires=[ - "input", - "notification", - ], - stack_size=2 * 1024, - order=70, -) diff --git a/applications/gui/modules/dialog.c b/applications/gui/modules/dialog.c deleted file mode 100644 index 5d36f93f7a1..00000000000 --- a/applications/gui/modules/dialog.c +++ /dev/null @@ -1,131 +0,0 @@ -#include "dialog.h" -#include -#include - -struct Dialog { - View* view; - void* context; - DialogResultCallback callback; -}; - -typedef struct { - const char* header_text; - const char* text; - const char* left_text; - const char* right_text; -} DialogModel; - -static void dialog_view_draw_callback(Canvas* canvas, void* _model) { - DialogModel* model = _model; - uint8_t canvas_center = canvas_width(canvas) / 2; - - // Prepare canvas - canvas_clear(canvas); - canvas_set_color(canvas, ColorBlack); - - // Draw header - canvas_set_font(canvas, FontPrimary); - canvas_draw_str_aligned( - canvas, canvas_center, 17, AlignCenter, AlignBottom, model->header_text); - - // Draw text - canvas_set_font(canvas, FontSecondary); - elements_multiline_text_aligned( - canvas, canvas_center, 32, AlignCenter, AlignCenter, model->text); - - // Draw buttons - elements_button_left(canvas, model->left_text); - elements_button_right(canvas, model->right_text); -} - -static bool dialog_view_input_callback(InputEvent* event, void* context) { - Dialog* dialog = context; - bool consumed = false; - - // Process key presses only - if(event->type == InputTypeShort && dialog->callback) { - if(event->key == InputKeyLeft) { - dialog->callback(DialogResultLeft, dialog->context); - consumed = true; - } else if(event->key == InputKeyRight) { - dialog->callback(DialogResultRight, dialog->context); - consumed = true; - } else if(event->key == InputKeyBack) { - dialog->callback(DialogResultBack, dialog->context); - consumed = true; - } - } - - return consumed; -} - -Dialog* dialog_alloc() { - Dialog* dialog = malloc(sizeof(Dialog)); - dialog->view = view_alloc(); - view_set_context(dialog->view, dialog); - view_allocate_model(dialog->view, ViewModelTypeLockFree, sizeof(DialogModel)); - view_set_draw_callback(dialog->view, dialog_view_draw_callback); - view_set_input_callback(dialog->view, dialog_view_input_callback); - return dialog; -} - -void dialog_free(Dialog* dialog) { - furi_assert(dialog); - view_free(dialog->view); - free(dialog); -} - -View* dialog_get_view(Dialog* dialog) { - furi_assert(dialog); - return dialog->view; -} - -void dialog_set_result_callback(Dialog* dialog, DialogResultCallback callback) { - furi_assert(dialog); - dialog->callback = callback; -} - -void dialog_set_context(Dialog* dialog, void* context) { - furi_assert(dialog); - dialog->context = context; -} - -void dialog_set_header_text(Dialog* dialog, const char* text) { - furi_assert(dialog); - furi_assert(text); - with_view_model( - dialog->view, (DialogModel * model) { - model->header_text = text; - return true; - }); -} - -void dialog_set_text(Dialog* dialog, const char* text) { - furi_assert(dialog); - furi_assert(text); - with_view_model( - dialog->view, (DialogModel * model) { - model->text = text; - return true; - }); -} - -void dialog_set_left_button_text(Dialog* dialog, const char* text) { - furi_assert(dialog); - furi_assert(text); - with_view_model( - dialog->view, (DialogModel * model) { - model->left_text = text; - return true; - }); -} - -void dialog_set_right_button_text(Dialog* dialog, const char* text) { - furi_assert(dialog); - furi_assert(text); - with_view_model( - dialog->view, (DialogModel * model) { - model->right_text = text; - return true; - }); -} diff --git a/applications/gui/modules/dialog.h b/applications/gui/modules/dialog.h deleted file mode 100644 index e9160a351fb..00000000000 --- a/applications/gui/modules/dialog.h +++ /dev/null @@ -1,95 +0,0 @@ -/** - * @file dialog.h - * GUI: Dialog view module API - */ - -#pragma once - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/** Dialog anonymous structure */ -typedef struct Dialog Dialog; - -/** Dialog result */ -typedef enum { - DialogResultLeft, - DialogResultRight, - DialogResultBack, -} DialogResult; - -/** Dialog result callback type - * @warning comes from GUI thread - */ -typedef void (*DialogResultCallback)(DialogResult result, void* context); - -/** Allocate and initialize dialog - * - * This dialog used to ask simple questions like Yes/ - * - * @return Dialog instance - */ -Dialog* dialog_alloc(); - -/** Deinitialize and free dialog - * - * @param dialog Dialog instance - */ -void dialog_free(Dialog* dialog); - -/** Get dialog view - * - * @param dialog Dialog instance - * - * @return View instance that can be used for embedding - */ -View* dialog_get_view(Dialog* dialog); - -/** Set dialog result callback - * - * @param dialog Dialog instance - * @param callback result callback function - */ -void dialog_set_result_callback(Dialog* dialog, DialogResultCallback callback); - -/** Set dialog context - * - * @param dialog Dialog instance - * @param context context pointer, will be passed to result callback - */ -void dialog_set_context(Dialog* dialog, void* context); - -/** Set dialog header text - * - * @param dialog Dialog instance - * @param text text to be shown - */ -void dialog_set_header_text(Dialog* dialog, const char* text); - -/** Set dialog text - * - * @param dialog Dialog instance - * @param text text to be shown - */ -void dialog_set_text(Dialog* dialog, const char* text); - -/** Set left button text - * - * @param dialog Dialog instance - * @param text text to be shown - */ -void dialog_set_left_button_text(Dialog* dialog, const char* text); - -/** Set right button text - * - * @param dialog Dialog instance - * @param text text to be shown - */ -void dialog_set_right_button_text(Dialog* dialog, const char* text); - -#ifdef __cplusplus -} -#endif diff --git a/applications/main/application.fam b/applications/main/application.fam new file mode 100644 index 00000000000..1fc3099059b --- /dev/null +++ b/applications/main/application.fam @@ -0,0 +1,17 @@ +App( + appid="main_apps", + name="Basic applications for main menu", + apptype=FlipperAppType.METAPACKAGE, + provides=[ + "gpio", + "ibutton", + "infrared", + "lfrfid", + "nfc", + "subghz", + "bad_usb", + "u2f", + "fap_loader", + "archive", + ], +) diff --git a/applications/archive/application.fam b/applications/main/archive/application.fam similarity index 100% rename from applications/archive/application.fam rename to applications/main/archive/application.fam diff --git a/applications/archive/archive.c b/applications/main/archive/archive.c similarity index 100% rename from applications/archive/archive.c rename to applications/main/archive/archive.c diff --git a/applications/archive/archive.h b/applications/main/archive/archive.h similarity index 100% rename from applications/archive/archive.h rename to applications/main/archive/archive.h diff --git a/applications/archive/archive_i.h b/applications/main/archive/archive_i.h similarity index 100% rename from applications/archive/archive_i.h rename to applications/main/archive/archive_i.h diff --git a/applications/archive/helpers/archive_apps.c b/applications/main/archive/helpers/archive_apps.c similarity index 100% rename from applications/archive/helpers/archive_apps.c rename to applications/main/archive/helpers/archive_apps.c diff --git a/applications/archive/helpers/archive_apps.h b/applications/main/archive/helpers/archive_apps.h similarity index 100% rename from applications/archive/helpers/archive_apps.h rename to applications/main/archive/helpers/archive_apps.h diff --git a/applications/archive/helpers/archive_browser.c b/applications/main/archive/helpers/archive_browser.c similarity index 99% rename from applications/archive/helpers/archive_browser.c rename to applications/main/archive/helpers/archive_browser.c index 2dfb9484b81..b08e5898322 100644 --- a/applications/archive/helpers/archive_browser.c +++ b/applications/main/archive/helpers/archive_browser.c @@ -1,4 +1,4 @@ -#include "archive/views/archive_browser_view.h" +#include #include "archive_files.h" #include "archive_apps.h" #include "archive_browser.h" diff --git a/applications/archive/helpers/archive_browser.h b/applications/main/archive/helpers/archive_browser.h similarity index 100% rename from applications/archive/helpers/archive_browser.h rename to applications/main/archive/helpers/archive_browser.h diff --git a/applications/archive/helpers/archive_favorites.c b/applications/main/archive/helpers/archive_favorites.c similarity index 100% rename from applications/archive/helpers/archive_favorites.c rename to applications/main/archive/helpers/archive_favorites.c diff --git a/applications/archive/helpers/archive_favorites.h b/applications/main/archive/helpers/archive_favorites.h similarity index 100% rename from applications/archive/helpers/archive_favorites.h rename to applications/main/archive/helpers/archive_favorites.h diff --git a/applications/archive/helpers/archive_files.c b/applications/main/archive/helpers/archive_files.c similarity index 100% rename from applications/archive/helpers/archive_files.c rename to applications/main/archive/helpers/archive_files.c diff --git a/applications/archive/helpers/archive_files.h b/applications/main/archive/helpers/archive_files.h similarity index 100% rename from applications/archive/helpers/archive_files.h rename to applications/main/archive/helpers/archive_files.h diff --git a/applications/archive/scenes/archive_scene.c b/applications/main/archive/scenes/archive_scene.c similarity index 100% rename from applications/archive/scenes/archive_scene.c rename to applications/main/archive/scenes/archive_scene.c diff --git a/applications/archive/scenes/archive_scene.h b/applications/main/archive/scenes/archive_scene.h similarity index 100% rename from applications/archive/scenes/archive_scene.h rename to applications/main/archive/scenes/archive_scene.h diff --git a/applications/archive/scenes/archive_scene_browser.c b/applications/main/archive/scenes/archive_scene_browser.c similarity index 100% rename from applications/archive/scenes/archive_scene_browser.c rename to applications/main/archive/scenes/archive_scene_browser.c diff --git a/applications/archive/scenes/archive_scene_config.h b/applications/main/archive/scenes/archive_scene_config.h similarity index 100% rename from applications/archive/scenes/archive_scene_config.h rename to applications/main/archive/scenes/archive_scene_config.h diff --git a/applications/archive/scenes/archive_scene_delete.c b/applications/main/archive/scenes/archive_scene_delete.c similarity index 100% rename from applications/archive/scenes/archive_scene_delete.c rename to applications/main/archive/scenes/archive_scene_delete.c diff --git a/applications/archive/scenes/archive_scene_rename.c b/applications/main/archive/scenes/archive_scene_rename.c similarity index 100% rename from applications/archive/scenes/archive_scene_rename.c rename to applications/main/archive/scenes/archive_scene_rename.c diff --git a/applications/archive/views/archive_browser_view.c b/applications/main/archive/views/archive_browser_view.c similarity index 99% rename from applications/archive/views/archive_browser_view.c rename to applications/main/archive/views/archive_browser_view.c index 174071ad49a..ddd6637dbc8 100644 --- a/applications/archive/views/archive_browser_view.c +++ b/applications/main/archive/views/archive_browser_view.c @@ -194,7 +194,7 @@ static void archive_render_status_bar(Canvas* canvas, ArchiveBrowserViewModel* m canvas_set_color(canvas, ColorBlack); } -void archive_view_render(Canvas* canvas, void* mdl) { +static void archive_view_render(Canvas* canvas, void* mdl) { ArchiveBrowserViewModel* model = mdl; archive_render_status_bar(canvas, mdl); @@ -234,7 +234,7 @@ static bool is_file_list_load_required(ArchiveBrowserViewModel* model) { return false; } -bool archive_view_input(InputEvent* event, void* context) { +static bool archive_view_input(InputEvent* event, void* context) { furi_assert(event); furi_assert(context); diff --git a/applications/archive/views/archive_browser_view.h b/applications/main/archive/views/archive_browser_view.h similarity index 100% rename from applications/archive/views/archive_browser_view.h rename to applications/main/archive/views/archive_browser_view.h diff --git a/applications/bad_usb/application.fam b/applications/main/bad_usb/application.fam similarity index 100% rename from applications/bad_usb/application.fam rename to applications/main/bad_usb/application.fam diff --git a/applications/bad_usb/bad_usb_app.c b/applications/main/bad_usb/bad_usb_app.c similarity index 100% rename from applications/bad_usb/bad_usb_app.c rename to applications/main/bad_usb/bad_usb_app.c diff --git a/applications/bad_usb/bad_usb_app.h b/applications/main/bad_usb/bad_usb_app.h similarity index 100% rename from applications/bad_usb/bad_usb_app.h rename to applications/main/bad_usb/bad_usb_app.h diff --git a/applications/bad_usb/bad_usb_app_i.h b/applications/main/bad_usb/bad_usb_app_i.h similarity index 100% rename from applications/bad_usb/bad_usb_app_i.h rename to applications/main/bad_usb/bad_usb_app_i.h diff --git a/applications/bad_usb/bad_usb_script.c b/applications/main/bad_usb/bad_usb_script.c similarity index 100% rename from applications/bad_usb/bad_usb_script.c rename to applications/main/bad_usb/bad_usb_script.c diff --git a/applications/bad_usb/bad_usb_script.h b/applications/main/bad_usb/bad_usb_script.h similarity index 100% rename from applications/bad_usb/bad_usb_script.h rename to applications/main/bad_usb/bad_usb_script.h diff --git a/applications/bad_usb/scenes/bad_usb_scene.c b/applications/main/bad_usb/scenes/bad_usb_scene.c similarity index 100% rename from applications/bad_usb/scenes/bad_usb_scene.c rename to applications/main/bad_usb/scenes/bad_usb_scene.c diff --git a/applications/bad_usb/scenes/bad_usb_scene.h b/applications/main/bad_usb/scenes/bad_usb_scene.h similarity index 100% rename from applications/bad_usb/scenes/bad_usb_scene.h rename to applications/main/bad_usb/scenes/bad_usb_scene.h diff --git a/applications/bad_usb/scenes/bad_usb_scene_config.h b/applications/main/bad_usb/scenes/bad_usb_scene_config.h similarity index 100% rename from applications/bad_usb/scenes/bad_usb_scene_config.h rename to applications/main/bad_usb/scenes/bad_usb_scene_config.h diff --git a/applications/bad_usb/scenes/bad_usb_scene_error.c b/applications/main/bad_usb/scenes/bad_usb_scene_error.c similarity index 100% rename from applications/bad_usb/scenes/bad_usb_scene_error.c rename to applications/main/bad_usb/scenes/bad_usb_scene_error.c diff --git a/applications/bad_usb/scenes/bad_usb_scene_file_select.c b/applications/main/bad_usb/scenes/bad_usb_scene_file_select.c similarity index 80% rename from applications/bad_usb/scenes/bad_usb_scene_file_select.c rename to applications/main/bad_usb/scenes/bad_usb_scene_file_select.c index 1e6ba895a18..c562fc2de51 100644 --- a/applications/bad_usb/scenes/bad_usb_scene_file_select.c +++ b/applications/main/bad_usb/scenes/bad_usb_scene_file_select.c @@ -5,15 +5,12 @@ static bool bad_usb_file_select(BadUsbApp* bad_usb) { furi_assert(bad_usb); + DialogsFileBrowserOptions browser_options; + dialog_file_browser_set_basic_options(&browser_options, BAD_USB_APP_EXTENSION, &I_badusb_10px); + // Input events and views are managed by file_browser bool res = dialog_file_browser_show( - bad_usb->dialogs, - bad_usb->file_path, - bad_usb->file_path, - BAD_USB_APP_EXTENSION, - true, - &I_badusb_10px, - true); + bad_usb->dialogs, bad_usb->file_path, bad_usb->file_path, &browser_options); return res; } diff --git a/applications/bad_usb/scenes/bad_usb_scene_work.c b/applications/main/bad_usb/scenes/bad_usb_scene_work.c similarity index 100% rename from applications/bad_usb/scenes/bad_usb_scene_work.c rename to applications/main/bad_usb/scenes/bad_usb_scene_work.c diff --git a/applications/bad_usb/views/bad_usb_view.c b/applications/main/bad_usb/views/bad_usb_view.c similarity index 100% rename from applications/bad_usb/views/bad_usb_view.c rename to applications/main/bad_usb/views/bad_usb_view.c diff --git a/applications/bad_usb/views/bad_usb_view.h b/applications/main/bad_usb/views/bad_usb_view.h similarity index 100% rename from applications/bad_usb/views/bad_usb_view.h rename to applications/main/bad_usb/views/bad_usb_view.h diff --git a/applications/main/fap_loader/application.fam b/applications/main/fap_loader/application.fam new file mode 100644 index 00000000000..bd0403e0eb9 --- /dev/null +++ b/applications/main/fap_loader/application.fam @@ -0,0 +1,13 @@ +App( + appid="fap_loader", + name="Applications", + apptype=FlipperAppType.APP, + entry_point="fap_loader_app", + requires=[ + "gui", + "storage", + ], + stack_size=int(1.5 * 1024), + icon="A_Plugins_14", + order=90, +) diff --git a/applications/main/fap_loader/elf_cpp/compilesort.hpp b/applications/main/fap_loader/elf_cpp/compilesort.hpp new file mode 100644 index 00000000000..746611697f0 --- /dev/null +++ b/applications/main/fap_loader/elf_cpp/compilesort.hpp @@ -0,0 +1,111 @@ +/** + * Implementation of compile-time sort for symbol table entries. + */ + +#pragma once + +#include +#include + +namespace cstd { + +template +constexpr RAIt next(RAIt it, typename std::iterator_traits::difference_type n = 1) { + return it + n; +} + +template +constexpr auto distance(RAIt first, RAIt last) { + return last - first; +} + +template +constexpr void iter_swap(ForwardIt1 a, ForwardIt2 b) { + auto temp = std::move(*a); + *a = std::move(*b); + *b = std::move(temp); +} + +template +constexpr InputIt find_if_not(InputIt first, InputIt last, UnaryPredicate q) { + for(; first != last; ++first) { + if(!q(*first)) { + return first; + } + } + return last; +} + +template +constexpr ForwardIt partition(ForwardIt first, ForwardIt last, UnaryPredicate p) { + first = cstd::find_if_not(first, last, p); + if(first == last) return first; + + for(ForwardIt i = cstd::next(first); i != last; ++i) { + if(p(*i)) { + cstd::iter_swap(i, first); + ++first; + } + } + return first; +} + +} + +template > +constexpr void quick_sort(RAIt first, RAIt last, Compare cmp = Compare{}) { + auto const N = cstd::distance(first, last); + if(N <= 1) return; + auto const pivot = *cstd::next(first, N / 2); + auto const middle1 = + cstd::partition(first, last, [=](auto const& elem) { return cmp(elem, pivot); }); + auto const middle2 = + cstd::partition(middle1, last, [=](auto const& elem) { return !cmp(pivot, elem); }); + quick_sort(first, middle1, cmp); // assert(std::is_sorted(first, middle1, cmp)); + quick_sort(middle2, last, cmp); // assert(std::is_sorted(middle2, last, cmp)); +} + +template +constexpr auto sort(Range&& range) { + quick_sort(std::begin(range), std::end(range)); + return range; +} + +template +constexpr auto array_of(T&&... t) -> std::array { + return {{std::forward(t)...}}; +} + +template +constexpr auto my_make_array(N&&... args) -> std::array { + return {std::forward(args)...}; +} + +namespace traits { +template +struct array_type { + using type = T; +}; + +template +static constexpr bool are_same_type() { + return std::conjunction_v...>; +} + +} + +template +constexpr auto create_array(const T&&... values) { + using array_type = typename traits::array_type::type; + static_assert(sizeof...(T) > 0, "an array must have at least one element"); + static_assert(traits::are_same_type(), "all elements must have same type"); + return std::array{values...}; +} + +template +constexpr auto create_array_t(const Ts&&... values) { + using array_type = T; + static_assert(sizeof...(Ts) > 0, "an array must have at least one element"); + static_assert(traits::are_same_type(), "all elements must have same type"); + return std::array{static_cast(values)...}; +} diff --git a/applications/main/fap_loader/elf_cpp/elf_hashtable.cpp b/applications/main/fap_loader/elf_cpp/elf_hashtable.cpp new file mode 100644 index 00000000000..17e2ba83fce --- /dev/null +++ b/applications/main/fap_loader/elf_cpp/elf_hashtable.cpp @@ -0,0 +1,48 @@ +#include "compilesort.hpp" +#include "elf_hashtable.h" +#include "elf_hashtable_entry.h" +#include "elf_hashtable_checks.hpp" + +#include +#include + +/* Generated table */ +#include + +#define TAG "elf_hashtable" + +static_assert(!has_hash_collisions(elf_api_table), "Detected API method hash collision!"); + +/** + * Get function address by function name + * @param name function name + * @param address output for function address + * @return true if the table contains a function + */ + +bool elf_resolve_from_hashtable(const char* name, Elf32_Addr* address) { + bool result = false; + uint32_t gnu_sym_hash = elf_gnu_hash(name); + + sym_entry key = { + .hash = gnu_sym_hash, + .address = 0, + }; + + auto find_res = std::lower_bound(elf_api_table.cbegin(), elf_api_table.cend(), key); + if((find_res == elf_api_table.cend() || (find_res->hash != gnu_sym_hash))) { + FURI_LOG_W(TAG, "Cant find symbol '%s' (hash %x)!", name, gnu_sym_hash); + result = false; + } else { + result = true; + *address = find_res->address; + } + + return result; +} + +const ElfApiInterface hashtable_api_interface = { + .api_version_major = (elf_api_version >> 16), + .api_version_minor = (elf_api_version & 0xFFFF), + .resolver_callback = &elf_resolve_from_hashtable, +}; diff --git a/applications/main/fap_loader/elf_cpp/elf_hashtable.h b/applications/main/fap_loader/elf_cpp/elf_hashtable.h new file mode 100644 index 00000000000..e574f11690c --- /dev/null +++ b/applications/main/fap_loader/elf_cpp/elf_hashtable.h @@ -0,0 +1,14 @@ +#pragma once +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern const ElfApiInterface hashtable_api_interface; + +#ifdef __cplusplus +} +#endif diff --git a/applications/main/fap_loader/elf_cpp/elf_hashtable_checks.hpp b/applications/main/fap_loader/elf_cpp/elf_hashtable_checks.hpp new file mode 100644 index 00000000000..61ee80e916f --- /dev/null +++ b/applications/main/fap_loader/elf_cpp/elf_hashtable_checks.hpp @@ -0,0 +1,18 @@ +/** + * Check for multiple entries with the same hash value at compilation time. + */ + +#pragma once +#include +#include "elf_hashtable_entry.h" + +template +constexpr bool has_hash_collisions(const std::array api_methods) { + for(std::size_t i = 0; i < (N - 1); ++i) { + if(api_methods[i].hash == api_methods[i + 1].hash) { + return true; + } + } + + return false; +} diff --git a/applications/main/fap_loader/elf_cpp/elf_hashtable_entry.h b/applications/main/fap_loader/elf_cpp/elf_hashtable_entry.h new file mode 100644 index 00000000000..7b540fba6a7 --- /dev/null +++ b/applications/main/fap_loader/elf_cpp/elf_hashtable_entry.h @@ -0,0 +1,41 @@ +#pragma once +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct sym_entry { + uint32_t hash; + uint32_t address; +}; + +#ifdef __cplusplus +} + +#include +#include + +#define API_METHOD(x, ret_type, args_type) \ + sym_entry { \ + .hash = elf_gnu_hash(#x), .address = (uint32_t)(static_cast(x)) \ + } + +#define API_VARIABLE(x, var_type) \ + sym_entry { \ + .hash = elf_gnu_hash(#x), .address = (uint32_t)(&(x)), \ + } + +constexpr bool operator<(const sym_entry& k1, const sym_entry& k2) { + return k1.hash < k2.hash; +} + +constexpr uint32_t elf_gnu_hash(const char* s) { + uint32_t h = 0x1505; + for(unsigned char c = *s; c != '\0'; c = *++s) { + h = (h << 5) + h + c; + } + return h; +} + +#endif diff --git a/applications/main/fap_loader/fap_loader_app.c b/applications/main/fap_loader/fap_loader_app.c new file mode 100644 index 00000000000..14da2f32037 --- /dev/null +++ b/applications/main/fap_loader/fap_loader_app.c @@ -0,0 +1,176 @@ +#include +#include +#include +#include +#include +#include +#include "elf_cpp/elf_hashtable.h" +#include + +#define TAG "fap_loader_app" + +typedef struct { + FlipperApplication* app; + Storage* storage; + DialogsApp* dialogs; + Gui* gui; + string_t fap_path; +} FapLoader; + +static bool + fap_loader_item_callback(string_t path, void* context, uint8_t** icon_ptr, string_t item_name) { + FapLoader* loader = context; + furi_assert(loader); + + FlipperApplication* app = flipper_application_alloc(loader->storage, &hashtable_api_interface); + + FlipperApplicationPreloadStatus preload_res = + flipper_application_preload(app, string_get_cstr(path)); + + bool load_success = false; + + if(preload_res == FlipperApplicationPreloadStatusSuccess) { + const FlipperApplicationManifest* manifest = flipper_application_get_manifest(app); + if(manifest->has_icon) { + memcpy(*icon_ptr, manifest->icon, FAP_MANIFEST_MAX_ICON_SIZE); + } + string_set_str(item_name, manifest->name); + load_success = true; + } else { + FURI_LOG_E(TAG, "FAP Loader failed to preload %s", string_get_cstr(path)); + load_success = false; + } + + flipper_application_free(app); + return load_success; +} + +static bool fap_loader_run_selected_app(FapLoader* loader) { + furi_assert(loader); + + string_t error_message; + + string_init_set(error_message, "unknown error"); + + bool file_selected = false; + bool show_error = true; + do { + file_selected = true; + loader->app = flipper_application_alloc(loader->storage, &hashtable_api_interface); + + FURI_LOG_I(TAG, "FAP Loader is loading %s", string_get_cstr(loader->fap_path)); + + FlipperApplicationPreloadStatus preload_res = + flipper_application_preload(loader->app, string_get_cstr(loader->fap_path)); + if(preload_res != FlipperApplicationPreloadStatusSuccess) { + const char* err_msg = flipper_application_preload_status_to_string(preload_res); + string_printf(error_message, "Preload failed: %s", err_msg); + FURI_LOG_E( + TAG, + "FAP Loader failed to preload %s: %s", + string_get_cstr(loader->fap_path), + err_msg); + break; + } + + FURI_LOG_I(TAG, "FAP Loader is mapping"); + FlipperApplicationLoadStatus load_status = flipper_application_map_to_memory(loader->app); + if(load_status != FlipperApplicationLoadStatusSuccess) { + const char* err_msg = flipper_application_load_status_to_string(load_status); + string_printf(error_message, "Load failed: %s", err_msg); + FURI_LOG_E( + TAG, + "FAP Loader failed to map to memory %s: %s", + string_get_cstr(loader->fap_path), + err_msg); + break; + } + + FURI_LOG_I(TAG, "FAP Loader is staring app"); + + FuriThread* thread = flipper_application_spawn(loader->app, NULL); + furi_thread_start(thread); + furi_thread_join(thread); + + show_error = false; + int ret = furi_thread_get_return_code(thread); + + FURI_LOG_I(TAG, "FAP app returned: %i", ret); + } while(0); + + if(show_error) { + DialogMessage* message = dialog_message_alloc(); + dialog_message_set_header(message, "Error", 64, 0, AlignCenter, AlignTop); + dialog_message_set_buttons(message, NULL, NULL, NULL); + + string_t buffer; + string_init(buffer); + string_printf(buffer, "%s", string_get_cstr(error_message)); + string_replace_str(buffer, ":", "\n"); + dialog_message_set_text( + message, string_get_cstr(buffer), 64, 32, AlignCenter, AlignCenter); + + dialog_message_show(loader->dialogs, message); + dialog_message_free(message); + string_clear(buffer); + } + + string_clear(error_message); + + if(file_selected) { + flipper_application_free(loader->app); + } + + return file_selected; +} + +static bool fap_loader_select_app(FapLoader* loader) { + const DialogsFileBrowserOptions browser_options = { + .extension = ".fap", + .skip_assets = true, + .icon = &I_badusb_10px, + .hide_ext = true, + .item_loader_callback = fap_loader_item_callback, + .item_loader_context = loader, + }; + + return dialog_file_browser_show( + loader->dialogs, loader->fap_path, loader->fap_path, &browser_options); +} + +int32_t fap_loader_app(void* p) { + FapLoader* loader = malloc(sizeof(FapLoader)); + loader->storage = furi_record_open(RECORD_STORAGE); + loader->dialogs = furi_record_open(RECORD_DIALOGS); + loader->gui = furi_record_open(RECORD_GUI); + + ViewDispatcher* view_dispatcher = view_dispatcher_alloc(); + Loading* loading = loading_alloc(); + + view_dispatcher_enable_queue(view_dispatcher); + view_dispatcher_attach_to_gui(view_dispatcher, loader->gui, ViewDispatcherTypeFullscreen); + view_dispatcher_add_view(view_dispatcher, 0, loading_get_view(loading)); + + if(p) { + string_init_set(loader->fap_path, (const char*)p); + fap_loader_run_selected_app(loader); + } else { + string_init_set(loader->fap_path, EXT_PATH("apps")); + + while(fap_loader_select_app(loader)) { + view_dispatcher_switch_to_view(view_dispatcher, 0); + fap_loader_run_selected_app(loader); + }; + } + + view_dispatcher_remove_view(view_dispatcher, 0); + loading_free(loading); + view_dispatcher_free(view_dispatcher); + + string_clear(loader->fap_path); + furi_record_close(RECORD_GUI); + furi_record_close(RECORD_DIALOGS); + furi_record_close(RECORD_STORAGE); + free(loader); + return 0; +} \ No newline at end of file diff --git a/applications/gpio/application.fam b/applications/main/gpio/application.fam similarity index 100% rename from applications/gpio/application.fam rename to applications/main/gpio/application.fam diff --git a/applications/gpio/gpio_app.c b/applications/main/gpio/gpio_app.c similarity index 100% rename from applications/gpio/gpio_app.c rename to applications/main/gpio/gpio_app.c diff --git a/applications/gpio/gpio_app.h b/applications/main/gpio/gpio_app.h similarity index 100% rename from applications/gpio/gpio_app.h rename to applications/main/gpio/gpio_app.h diff --git a/applications/gpio/gpio_app_i.h b/applications/main/gpio/gpio_app_i.h similarity index 100% rename from applications/gpio/gpio_app_i.h rename to applications/main/gpio/gpio_app_i.h diff --git a/applications/gpio/gpio_custom_event.h b/applications/main/gpio/gpio_custom_event.h similarity index 100% rename from applications/gpio/gpio_custom_event.h rename to applications/main/gpio/gpio_custom_event.h diff --git a/applications/gpio/gpio_item.c b/applications/main/gpio/gpio_item.c similarity index 100% rename from applications/gpio/gpio_item.c rename to applications/main/gpio/gpio_item.c diff --git a/applications/gpio/gpio_item.h b/applications/main/gpio/gpio_item.h similarity index 100% rename from applications/gpio/gpio_item.h rename to applications/main/gpio/gpio_item.h diff --git a/applications/gpio/scenes/gpio_scene.c b/applications/main/gpio/scenes/gpio_scene.c similarity index 100% rename from applications/gpio/scenes/gpio_scene.c rename to applications/main/gpio/scenes/gpio_scene.c diff --git a/applications/gpio/scenes/gpio_scene.h b/applications/main/gpio/scenes/gpio_scene.h similarity index 100% rename from applications/gpio/scenes/gpio_scene.h rename to applications/main/gpio/scenes/gpio_scene.h diff --git a/applications/gpio/scenes/gpio_scene_config.h b/applications/main/gpio/scenes/gpio_scene_config.h similarity index 100% rename from applications/gpio/scenes/gpio_scene_config.h rename to applications/main/gpio/scenes/gpio_scene_config.h diff --git a/applications/gpio/scenes/gpio_scene_start.c b/applications/main/gpio/scenes/gpio_scene_start.c similarity index 100% rename from applications/gpio/scenes/gpio_scene_start.c rename to applications/main/gpio/scenes/gpio_scene_start.c diff --git a/applications/gpio/scenes/gpio_scene_test.c b/applications/main/gpio/scenes/gpio_scene_test.c similarity index 100% rename from applications/gpio/scenes/gpio_scene_test.c rename to applications/main/gpio/scenes/gpio_scene_test.c diff --git a/applications/gpio/scenes/gpio_scene_usb_uart.c b/applications/main/gpio/scenes/gpio_scene_usb_uart.c similarity index 100% rename from applications/gpio/scenes/gpio_scene_usb_uart.c rename to applications/main/gpio/scenes/gpio_scene_usb_uart.c diff --git a/applications/gpio/scenes/gpio_scene_usb_uart_close_rpc.c b/applications/main/gpio/scenes/gpio_scene_usb_uart_close_rpc.c similarity index 100% rename from applications/gpio/scenes/gpio_scene_usb_uart_close_rpc.c rename to applications/main/gpio/scenes/gpio_scene_usb_uart_close_rpc.c diff --git a/applications/gpio/scenes/gpio_scene_usb_uart_config.c b/applications/main/gpio/scenes/gpio_scene_usb_uart_config.c similarity index 100% rename from applications/gpio/scenes/gpio_scene_usb_uart_config.c rename to applications/main/gpio/scenes/gpio_scene_usb_uart_config.c diff --git a/applications/gpio/usb_uart_bridge.c b/applications/main/gpio/usb_uart_bridge.c similarity index 99% rename from applications/gpio/usb_uart_bridge.c rename to applications/main/gpio/usb_uart_bridge.c index 4623c4af1cf..02f58ed1084 100644 --- a/applications/gpio/usb_uart_bridge.c +++ b/applications/main/gpio/usb_uart_bridge.c @@ -1,7 +1,7 @@ #include "usb_uart_bridge.h" #include "furi_hal.h" #include -#include +#include #include "usb_cdc.h" #include "cli/cli_vcp.h" #include "cli/cli.h" diff --git a/applications/gpio/usb_uart_bridge.h b/applications/main/gpio/usb_uart_bridge.h similarity index 100% rename from applications/gpio/usb_uart_bridge.h rename to applications/main/gpio/usb_uart_bridge.h diff --git a/applications/gpio/views/gpio_test.c b/applications/main/gpio/views/gpio_test.c similarity index 100% rename from applications/gpio/views/gpio_test.c rename to applications/main/gpio/views/gpio_test.c diff --git a/applications/gpio/views/gpio_test.h b/applications/main/gpio/views/gpio_test.h similarity index 100% rename from applications/gpio/views/gpio_test.h rename to applications/main/gpio/views/gpio_test.h diff --git a/applications/gpio/views/gpio_usb_uart.c b/applications/main/gpio/views/gpio_usb_uart.c similarity index 100% rename from applications/gpio/views/gpio_usb_uart.c rename to applications/main/gpio/views/gpio_usb_uart.c diff --git a/applications/gpio/views/gpio_usb_uart.h b/applications/main/gpio/views/gpio_usb_uart.h similarity index 100% rename from applications/gpio/views/gpio_usb_uart.h rename to applications/main/gpio/views/gpio_usb_uart.h diff --git a/applications/ibutton/application.fam b/applications/main/ibutton/application.fam similarity index 100% rename from applications/ibutton/application.fam rename to applications/main/ibutton/application.fam diff --git a/applications/ibutton/ibutton.c b/applications/main/ibutton/ibutton.c similarity index 98% rename from applications/ibutton/ibutton.c rename to applications/main/ibutton/ibutton.c index 7ee1110e149..e9ec614ecd4 100644 --- a/applications/ibutton/ibutton.c +++ b/applications/main/ibutton/ibutton.c @@ -216,14 +216,11 @@ void ibutton_free(iButton* ibutton) { } bool ibutton_file_select(iButton* ibutton) { + DialogsFileBrowserOptions browser_options; + dialog_file_browser_set_basic_options(&browser_options, IBUTTON_APP_EXTENSION, &I_ibutt_10px); + bool success = dialog_file_browser_show( - ibutton->dialogs, - ibutton->file_path, - ibutton->file_path, - IBUTTON_APP_EXTENSION, - true, - &I_ibutt_10px, - true); + ibutton->dialogs, ibutton->file_path, ibutton->file_path, &browser_options); if(success) { success = ibutton_load_key_data(ibutton, ibutton->file_path, true); diff --git a/applications/ibutton/ibutton.h b/applications/main/ibutton/ibutton.h similarity index 100% rename from applications/ibutton/ibutton.h rename to applications/main/ibutton/ibutton.h diff --git a/applications/ibutton/ibutton_cli.c b/applications/main/ibutton/ibutton_cli.c similarity index 100% rename from applications/ibutton/ibutton_cli.c rename to applications/main/ibutton/ibutton_cli.c diff --git a/applications/ibutton/ibutton_custom_event.h b/applications/main/ibutton/ibutton_custom_event.h similarity index 100% rename from applications/ibutton/ibutton_custom_event.h rename to applications/main/ibutton/ibutton_custom_event.h diff --git a/applications/ibutton/ibutton_i.h b/applications/main/ibutton/ibutton_i.h similarity index 100% rename from applications/ibutton/ibutton_i.h rename to applications/main/ibutton/ibutton_i.h diff --git a/applications/ibutton/scenes/ibutton_scene.c b/applications/main/ibutton/scenes/ibutton_scene.c similarity index 100% rename from applications/ibutton/scenes/ibutton_scene.c rename to applications/main/ibutton/scenes/ibutton_scene.c diff --git a/applications/ibutton/scenes/ibutton_scene.h b/applications/main/ibutton/scenes/ibutton_scene.h similarity index 100% rename from applications/ibutton/scenes/ibutton_scene.h rename to applications/main/ibutton/scenes/ibutton_scene.h diff --git a/applications/ibutton/scenes/ibutton_scene_add_type.c b/applications/main/ibutton/scenes/ibutton_scene_add_type.c similarity index 100% rename from applications/ibutton/scenes/ibutton_scene_add_type.c rename to applications/main/ibutton/scenes/ibutton_scene_add_type.c diff --git a/applications/ibutton/scenes/ibutton_scene_add_value.c b/applications/main/ibutton/scenes/ibutton_scene_add_value.c similarity index 100% rename from applications/ibutton/scenes/ibutton_scene_add_value.c rename to applications/main/ibutton/scenes/ibutton_scene_add_value.c diff --git a/applications/ibutton/scenes/ibutton_scene_config.h b/applications/main/ibutton/scenes/ibutton_scene_config.h similarity index 100% rename from applications/ibutton/scenes/ibutton_scene_config.h rename to applications/main/ibutton/scenes/ibutton_scene_config.h diff --git a/applications/ibutton/scenes/ibutton_scene_delete_confirm.c b/applications/main/ibutton/scenes/ibutton_scene_delete_confirm.c similarity index 100% rename from applications/ibutton/scenes/ibutton_scene_delete_confirm.c rename to applications/main/ibutton/scenes/ibutton_scene_delete_confirm.c diff --git a/applications/ibutton/scenes/ibutton_scene_delete_success.c b/applications/main/ibutton/scenes/ibutton_scene_delete_success.c similarity index 100% rename from applications/ibutton/scenes/ibutton_scene_delete_success.c rename to applications/main/ibutton/scenes/ibutton_scene_delete_success.c diff --git a/applications/ibutton/scenes/ibutton_scene_emulate.c b/applications/main/ibutton/scenes/ibutton_scene_emulate.c similarity index 100% rename from applications/ibutton/scenes/ibutton_scene_emulate.c rename to applications/main/ibutton/scenes/ibutton_scene_emulate.c diff --git a/applications/ibutton/scenes/ibutton_scene_exit_confirm.c b/applications/main/ibutton/scenes/ibutton_scene_exit_confirm.c similarity index 100% rename from applications/ibutton/scenes/ibutton_scene_exit_confirm.c rename to applications/main/ibutton/scenes/ibutton_scene_exit_confirm.c diff --git a/applications/ibutton/scenes/ibutton_scene_info.c b/applications/main/ibutton/scenes/ibutton_scene_info.c similarity index 100% rename from applications/ibutton/scenes/ibutton_scene_info.c rename to applications/main/ibutton/scenes/ibutton_scene_info.c diff --git a/applications/ibutton/scenes/ibutton_scene_read.c b/applications/main/ibutton/scenes/ibutton_scene_read.c similarity index 100% rename from applications/ibutton/scenes/ibutton_scene_read.c rename to applications/main/ibutton/scenes/ibutton_scene_read.c diff --git a/applications/ibutton/scenes/ibutton_scene_read_crc_error.c b/applications/main/ibutton/scenes/ibutton_scene_read_crc_error.c similarity index 100% rename from applications/ibutton/scenes/ibutton_scene_read_crc_error.c rename to applications/main/ibutton/scenes/ibutton_scene_read_crc_error.c diff --git a/applications/ibutton/scenes/ibutton_scene_read_key_menu.c b/applications/main/ibutton/scenes/ibutton_scene_read_key_menu.c similarity index 100% rename from applications/ibutton/scenes/ibutton_scene_read_key_menu.c rename to applications/main/ibutton/scenes/ibutton_scene_read_key_menu.c diff --git a/applications/ibutton/scenes/ibutton_scene_read_not_key_error.c b/applications/main/ibutton/scenes/ibutton_scene_read_not_key_error.c similarity index 100% rename from applications/ibutton/scenes/ibutton_scene_read_not_key_error.c rename to applications/main/ibutton/scenes/ibutton_scene_read_not_key_error.c diff --git a/applications/ibutton/scenes/ibutton_scene_read_success.c b/applications/main/ibutton/scenes/ibutton_scene_read_success.c similarity index 100% rename from applications/ibutton/scenes/ibutton_scene_read_success.c rename to applications/main/ibutton/scenes/ibutton_scene_read_success.c diff --git a/applications/ibutton/scenes/ibutton_scene_retry_confirm.c b/applications/main/ibutton/scenes/ibutton_scene_retry_confirm.c similarity index 100% rename from applications/ibutton/scenes/ibutton_scene_retry_confirm.c rename to applications/main/ibutton/scenes/ibutton_scene_retry_confirm.c diff --git a/applications/ibutton/scenes/ibutton_scene_rpc.c b/applications/main/ibutton/scenes/ibutton_scene_rpc.c similarity index 100% rename from applications/ibutton/scenes/ibutton_scene_rpc.c rename to applications/main/ibutton/scenes/ibutton_scene_rpc.c diff --git a/applications/ibutton/scenes/ibutton_scene_save_name.c b/applications/main/ibutton/scenes/ibutton_scene_save_name.c similarity index 100% rename from applications/ibutton/scenes/ibutton_scene_save_name.c rename to applications/main/ibutton/scenes/ibutton_scene_save_name.c diff --git a/applications/ibutton/scenes/ibutton_scene_save_success.c b/applications/main/ibutton/scenes/ibutton_scene_save_success.c similarity index 100% rename from applications/ibutton/scenes/ibutton_scene_save_success.c rename to applications/main/ibutton/scenes/ibutton_scene_save_success.c diff --git a/applications/ibutton/scenes/ibutton_scene_saved_key_menu.c b/applications/main/ibutton/scenes/ibutton_scene_saved_key_menu.c similarity index 100% rename from applications/ibutton/scenes/ibutton_scene_saved_key_menu.c rename to applications/main/ibutton/scenes/ibutton_scene_saved_key_menu.c diff --git a/applications/ibutton/scenes/ibutton_scene_select_key.c b/applications/main/ibutton/scenes/ibutton_scene_select_key.c similarity index 100% rename from applications/ibutton/scenes/ibutton_scene_select_key.c rename to applications/main/ibutton/scenes/ibutton_scene_select_key.c diff --git a/applications/ibutton/scenes/ibutton_scene_start.c b/applications/main/ibutton/scenes/ibutton_scene_start.c similarity index 100% rename from applications/ibutton/scenes/ibutton_scene_start.c rename to applications/main/ibutton/scenes/ibutton_scene_start.c diff --git a/applications/ibutton/scenes/ibutton_scene_write.c b/applications/main/ibutton/scenes/ibutton_scene_write.c similarity index 100% rename from applications/ibutton/scenes/ibutton_scene_write.c rename to applications/main/ibutton/scenes/ibutton_scene_write.c diff --git a/applications/ibutton/scenes/ibutton_scene_write_success.c b/applications/main/ibutton/scenes/ibutton_scene_write_success.c similarity index 100% rename from applications/ibutton/scenes/ibutton_scene_write_success.c rename to applications/main/ibutton/scenes/ibutton_scene_write_success.c diff --git a/applications/infrared/application.fam b/applications/main/infrared/application.fam similarity index 100% rename from applications/infrared/application.fam rename to applications/main/infrared/application.fam diff --git a/applications/infrared/infrared.c b/applications/main/infrared/infrared.c similarity index 100% rename from applications/infrared/infrared.c rename to applications/main/infrared/infrared.c diff --git a/applications/infrared/infrared.h b/applications/main/infrared/infrared.h similarity index 100% rename from applications/infrared/infrared.h rename to applications/main/infrared/infrared.h diff --git a/applications/infrared/infrared_brute_force.c b/applications/main/infrared/infrared_brute_force.c similarity index 100% rename from applications/infrared/infrared_brute_force.c rename to applications/main/infrared/infrared_brute_force.c diff --git a/applications/infrared/infrared_brute_force.h b/applications/main/infrared/infrared_brute_force.h similarity index 100% rename from applications/infrared/infrared_brute_force.h rename to applications/main/infrared/infrared_brute_force.h diff --git a/applications/infrared/infrared_cli.c b/applications/main/infrared/infrared_cli.c similarity index 100% rename from applications/infrared/infrared_cli.c rename to applications/main/infrared/infrared_cli.c diff --git a/applications/infrared/infrared_custom_event.h b/applications/main/infrared/infrared_custom_event.h similarity index 100% rename from applications/infrared/infrared_custom_event.h rename to applications/main/infrared/infrared_custom_event.h diff --git a/applications/infrared/infrared_i.h b/applications/main/infrared/infrared_i.h similarity index 100% rename from applications/infrared/infrared_i.h rename to applications/main/infrared/infrared_i.h diff --git a/applications/infrared/infrared_remote.c b/applications/main/infrared/infrared_remote.c similarity index 100% rename from applications/infrared/infrared_remote.c rename to applications/main/infrared/infrared_remote.c diff --git a/applications/infrared/infrared_remote.h b/applications/main/infrared/infrared_remote.h similarity index 100% rename from applications/infrared/infrared_remote.h rename to applications/main/infrared/infrared_remote.h diff --git a/applications/infrared/infrared_remote_button.c b/applications/main/infrared/infrared_remote_button.c similarity index 100% rename from applications/infrared/infrared_remote_button.c rename to applications/main/infrared/infrared_remote_button.c diff --git a/applications/infrared/infrared_remote_button.h b/applications/main/infrared/infrared_remote_button.h similarity index 100% rename from applications/infrared/infrared_remote_button.h rename to applications/main/infrared/infrared_remote_button.h diff --git a/applications/infrared/infrared_signal.c b/applications/main/infrared/infrared_signal.c similarity index 100% rename from applications/infrared/infrared_signal.c rename to applications/main/infrared/infrared_signal.c diff --git a/applications/infrared/infrared_signal.h b/applications/main/infrared/infrared_signal.h similarity index 100% rename from applications/infrared/infrared_signal.h rename to applications/main/infrared/infrared_signal.h diff --git a/applications/infrared/scenes/common/infrared_scene_universal_common.c b/applications/main/infrared/scenes/common/infrared_scene_universal_common.c similarity index 100% rename from applications/infrared/scenes/common/infrared_scene_universal_common.c rename to applications/main/infrared/scenes/common/infrared_scene_universal_common.c diff --git a/applications/infrared/scenes/common/infrared_scene_universal_common.h b/applications/main/infrared/scenes/common/infrared_scene_universal_common.h similarity index 100% rename from applications/infrared/scenes/common/infrared_scene_universal_common.h rename to applications/main/infrared/scenes/common/infrared_scene_universal_common.h diff --git a/applications/infrared/scenes/infrared_scene.c b/applications/main/infrared/scenes/infrared_scene.c similarity index 100% rename from applications/infrared/scenes/infrared_scene.c rename to applications/main/infrared/scenes/infrared_scene.c diff --git a/applications/infrared/scenes/infrared_scene.h b/applications/main/infrared/scenes/infrared_scene.h similarity index 100% rename from applications/infrared/scenes/infrared_scene.h rename to applications/main/infrared/scenes/infrared_scene.h diff --git a/applications/infrared/scenes/infrared_scene_ask_back.c b/applications/main/infrared/scenes/infrared_scene_ask_back.c similarity index 100% rename from applications/infrared/scenes/infrared_scene_ask_back.c rename to applications/main/infrared/scenes/infrared_scene_ask_back.c diff --git a/applications/infrared/scenes/infrared_scene_ask_retry.c b/applications/main/infrared/scenes/infrared_scene_ask_retry.c similarity index 100% rename from applications/infrared/scenes/infrared_scene_ask_retry.c rename to applications/main/infrared/scenes/infrared_scene_ask_retry.c diff --git a/applications/infrared/scenes/infrared_scene_config.h b/applications/main/infrared/scenes/infrared_scene_config.h similarity index 100% rename from applications/infrared/scenes/infrared_scene_config.h rename to applications/main/infrared/scenes/infrared_scene_config.h diff --git a/applications/infrared/scenes/infrared_scene_debug.c b/applications/main/infrared/scenes/infrared_scene_debug.c similarity index 100% rename from applications/infrared/scenes/infrared_scene_debug.c rename to applications/main/infrared/scenes/infrared_scene_debug.c diff --git a/applications/infrared/scenes/infrared_scene_edit.c b/applications/main/infrared/scenes/infrared_scene_edit.c similarity index 100% rename from applications/infrared/scenes/infrared_scene_edit.c rename to applications/main/infrared/scenes/infrared_scene_edit.c diff --git a/applications/infrared/scenes/infrared_scene_edit_button_select.c b/applications/main/infrared/scenes/infrared_scene_edit_button_select.c similarity index 100% rename from applications/infrared/scenes/infrared_scene_edit_button_select.c rename to applications/main/infrared/scenes/infrared_scene_edit_button_select.c diff --git a/applications/infrared/scenes/infrared_scene_edit_delete.c b/applications/main/infrared/scenes/infrared_scene_edit_delete.c similarity index 100% rename from applications/infrared/scenes/infrared_scene_edit_delete.c rename to applications/main/infrared/scenes/infrared_scene_edit_delete.c diff --git a/applications/infrared/scenes/infrared_scene_edit_delete_done.c b/applications/main/infrared/scenes/infrared_scene_edit_delete_done.c similarity index 100% rename from applications/infrared/scenes/infrared_scene_edit_delete_done.c rename to applications/main/infrared/scenes/infrared_scene_edit_delete_done.c diff --git a/applications/infrared/scenes/infrared_scene_edit_rename.c b/applications/main/infrared/scenes/infrared_scene_edit_rename.c similarity index 100% rename from applications/infrared/scenes/infrared_scene_edit_rename.c rename to applications/main/infrared/scenes/infrared_scene_edit_rename.c diff --git a/applications/infrared/scenes/infrared_scene_edit_rename_done.c b/applications/main/infrared/scenes/infrared_scene_edit_rename_done.c similarity index 100% rename from applications/infrared/scenes/infrared_scene_edit_rename_done.c rename to applications/main/infrared/scenes/infrared_scene_edit_rename_done.c diff --git a/applications/infrared/scenes/infrared_scene_error_databases.c b/applications/main/infrared/scenes/infrared_scene_error_databases.c similarity index 100% rename from applications/infrared/scenes/infrared_scene_error_databases.c rename to applications/main/infrared/scenes/infrared_scene_error_databases.c diff --git a/applications/infrared/scenes/infrared_scene_learn.c b/applications/main/infrared/scenes/infrared_scene_learn.c similarity index 100% rename from applications/infrared/scenes/infrared_scene_learn.c rename to applications/main/infrared/scenes/infrared_scene_learn.c diff --git a/applications/infrared/scenes/infrared_scene_learn_done.c b/applications/main/infrared/scenes/infrared_scene_learn_done.c similarity index 100% rename from applications/infrared/scenes/infrared_scene_learn_done.c rename to applications/main/infrared/scenes/infrared_scene_learn_done.c diff --git a/applications/infrared/scenes/infrared_scene_learn_enter_name.c b/applications/main/infrared/scenes/infrared_scene_learn_enter_name.c similarity index 100% rename from applications/infrared/scenes/infrared_scene_learn_enter_name.c rename to applications/main/infrared/scenes/infrared_scene_learn_enter_name.c diff --git a/applications/infrared/scenes/infrared_scene_learn_success.c b/applications/main/infrared/scenes/infrared_scene_learn_success.c similarity index 100% rename from applications/infrared/scenes/infrared_scene_learn_success.c rename to applications/main/infrared/scenes/infrared_scene_learn_success.c diff --git a/applications/infrared/scenes/infrared_scene_remote.c b/applications/main/infrared/scenes/infrared_scene_remote.c similarity index 100% rename from applications/infrared/scenes/infrared_scene_remote.c rename to applications/main/infrared/scenes/infrared_scene_remote.c diff --git a/applications/infrared/scenes/infrared_scene_remote_list.c b/applications/main/infrared/scenes/infrared_scene_remote_list.c similarity index 82% rename from applications/infrared/scenes/infrared_scene_remote_list.c rename to applications/main/infrared/scenes/infrared_scene_remote_list.c index b0038c1a33f..1667352d156 100644 --- a/applications/infrared/scenes/infrared_scene_remote_list.c +++ b/applications/main/infrared/scenes/infrared_scene_remote_list.c @@ -5,14 +5,11 @@ void infrared_scene_remote_list_on_enter(void* context) { SceneManager* scene_manager = infrared->scene_manager; ViewDispatcher* view_dispatcher = infrared->view_dispatcher; + DialogsFileBrowserOptions browser_options; + dialog_file_browser_set_basic_options(&browser_options, INFRARED_APP_EXTENSION, &I_ir_10px); + bool success = dialog_file_browser_show( - infrared->dialogs, - infrared->file_path, - infrared->file_path, - INFRARED_APP_EXTENSION, - true, - &I_ir_10px, - true); + infrared->dialogs, infrared->file_path, infrared->file_path, &browser_options); if(success) { view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical); diff --git a/applications/infrared/scenes/infrared_scene_rpc.c b/applications/main/infrared/scenes/infrared_scene_rpc.c similarity index 100% rename from applications/infrared/scenes/infrared_scene_rpc.c rename to applications/main/infrared/scenes/infrared_scene_rpc.c diff --git a/applications/infrared/scenes/infrared_scene_start.c b/applications/main/infrared/scenes/infrared_scene_start.c similarity index 100% rename from applications/infrared/scenes/infrared_scene_start.c rename to applications/main/infrared/scenes/infrared_scene_start.c diff --git a/applications/infrared/scenes/infrared_scene_universal.c b/applications/main/infrared/scenes/infrared_scene_universal.c similarity index 100% rename from applications/infrared/scenes/infrared_scene_universal.c rename to applications/main/infrared/scenes/infrared_scene_universal.c diff --git a/applications/infrared/scenes/infrared_scene_universal_tv.c b/applications/main/infrared/scenes/infrared_scene_universal_tv.c similarity index 100% rename from applications/infrared/scenes/infrared_scene_universal_tv.c rename to applications/main/infrared/scenes/infrared_scene_universal_tv.c diff --git a/applications/infrared/views/infrared_debug_view.c b/applications/main/infrared/views/infrared_debug_view.c similarity index 100% rename from applications/infrared/views/infrared_debug_view.c rename to applications/main/infrared/views/infrared_debug_view.c diff --git a/applications/infrared/views/infrared_debug_view.h b/applications/main/infrared/views/infrared_debug_view.h similarity index 100% rename from applications/infrared/views/infrared_debug_view.h rename to applications/main/infrared/views/infrared_debug_view.h diff --git a/applications/infrared/views/infrared_progress_view.c b/applications/main/infrared/views/infrared_progress_view.c similarity index 100% rename from applications/infrared/views/infrared_progress_view.c rename to applications/main/infrared/views/infrared_progress_view.c diff --git a/applications/infrared/views/infrared_progress_view.h b/applications/main/infrared/views/infrared_progress_view.h similarity index 100% rename from applications/infrared/views/infrared_progress_view.h rename to applications/main/infrared/views/infrared_progress_view.h diff --git a/applications/lfrfid/application.fam b/applications/main/lfrfid/application.fam similarity index 95% rename from applications/lfrfid/application.fam rename to applications/main/lfrfid/application.fam index 8722bb4f8cd..4a1498181c5 100644 --- a/applications/lfrfid/application.fam +++ b/applications/main/lfrfid/application.fam @@ -10,7 +10,6 @@ App( ], provides=[ "lfrfid_start", - "lfrfid_debug", ], icon="A_125khz_14", stack_size=2 * 1024, diff --git a/applications/lfrfid/lfrfid.c b/applications/main/lfrfid/lfrfid.c similarity index 96% rename from applications/lfrfid/lfrfid.c rename to applications/main/lfrfid/lfrfid.c index 87332b1ac4d..dbed9f3af7b 100644 --- a/applications/lfrfid/lfrfid.c +++ b/applications/main/lfrfid/lfrfid.c @@ -225,14 +225,12 @@ bool lfrfid_save_key(LfRfid* app) { bool lfrfid_load_key_from_file_select(LfRfid* app) { furi_assert(app); - bool result = dialog_file_browser_show( - app->dialogs, - app->file_path, - app->file_path, - LFRFID_APP_EXTENSION, - true, - &I_125_10px, - true); + DialogsFileBrowserOptions browser_options; + dialog_file_browser_set_basic_options(&browser_options, LFRFID_APP_EXTENSION, &I_125_10px); + + // Input events and views are managed by file_browser + bool result = + dialog_file_browser_show(app->dialogs, app->file_path, app->file_path, &browser_options); if(result) { result = lfrfid_load_key_data(app, app->file_path, true); diff --git a/applications/lfrfid/lfrfid_cli.c b/applications/main/lfrfid/lfrfid_cli.c similarity index 100% rename from applications/lfrfid/lfrfid_cli.c rename to applications/main/lfrfid/lfrfid_cli.c diff --git a/applications/lfrfid/lfrfid_i.h b/applications/main/lfrfid/lfrfid_i.h similarity index 100% rename from applications/lfrfid/lfrfid_i.h rename to applications/main/lfrfid/lfrfid_i.h diff --git a/applications/lfrfid/scenes/lfrfid_scene.c b/applications/main/lfrfid/scenes/lfrfid_scene.c similarity index 100% rename from applications/lfrfid/scenes/lfrfid_scene.c rename to applications/main/lfrfid/scenes/lfrfid_scene.c diff --git a/applications/lfrfid/scenes/lfrfid_scene.h b/applications/main/lfrfid/scenes/lfrfid_scene.h similarity index 100% rename from applications/lfrfid/scenes/lfrfid_scene.h rename to applications/main/lfrfid/scenes/lfrfid_scene.h diff --git a/applications/lfrfid/scenes/lfrfid_scene_config.h b/applications/main/lfrfid/scenes/lfrfid_scene_config.h similarity index 100% rename from applications/lfrfid/scenes/lfrfid_scene_config.h rename to applications/main/lfrfid/scenes/lfrfid_scene_config.h diff --git a/applications/lfrfid/scenes/lfrfid_scene_delete_confirm.c b/applications/main/lfrfid/scenes/lfrfid_scene_delete_confirm.c similarity index 100% rename from applications/lfrfid/scenes/lfrfid_scene_delete_confirm.c rename to applications/main/lfrfid/scenes/lfrfid_scene_delete_confirm.c diff --git a/applications/lfrfid/scenes/lfrfid_scene_delete_success.c b/applications/main/lfrfid/scenes/lfrfid_scene_delete_success.c similarity index 100% rename from applications/lfrfid/scenes/lfrfid_scene_delete_success.c rename to applications/main/lfrfid/scenes/lfrfid_scene_delete_success.c diff --git a/applications/lfrfid/scenes/lfrfid_scene_emulate.c b/applications/main/lfrfid/scenes/lfrfid_scene_emulate.c similarity index 100% rename from applications/lfrfid/scenes/lfrfid_scene_emulate.c rename to applications/main/lfrfid/scenes/lfrfid_scene_emulate.c diff --git a/applications/lfrfid/scenes/lfrfid_scene_exit_confirm.c b/applications/main/lfrfid/scenes/lfrfid_scene_exit_confirm.c similarity index 100% rename from applications/lfrfid/scenes/lfrfid_scene_exit_confirm.c rename to applications/main/lfrfid/scenes/lfrfid_scene_exit_confirm.c diff --git a/applications/lfrfid/scenes/lfrfid_scene_extra_actions.c b/applications/main/lfrfid/scenes/lfrfid_scene_extra_actions.c similarity index 100% rename from applications/lfrfid/scenes/lfrfid_scene_extra_actions.c rename to applications/main/lfrfid/scenes/lfrfid_scene_extra_actions.c diff --git a/applications/lfrfid/scenes/lfrfid_scene_raw_info.c b/applications/main/lfrfid/scenes/lfrfid_scene_raw_info.c similarity index 100% rename from applications/lfrfid/scenes/lfrfid_scene_raw_info.c rename to applications/main/lfrfid/scenes/lfrfid_scene_raw_info.c diff --git a/applications/lfrfid/scenes/lfrfid_scene_raw_name.c b/applications/main/lfrfid/scenes/lfrfid_scene_raw_name.c similarity index 100% rename from applications/lfrfid/scenes/lfrfid_scene_raw_name.c rename to applications/main/lfrfid/scenes/lfrfid_scene_raw_name.c diff --git a/applications/lfrfid/scenes/lfrfid_scene_raw_read.c b/applications/main/lfrfid/scenes/lfrfid_scene_raw_read.c similarity index 100% rename from applications/lfrfid/scenes/lfrfid_scene_raw_read.c rename to applications/main/lfrfid/scenes/lfrfid_scene_raw_read.c diff --git a/applications/lfrfid/scenes/lfrfid_scene_raw_success.c b/applications/main/lfrfid/scenes/lfrfid_scene_raw_success.c similarity index 100% rename from applications/lfrfid/scenes/lfrfid_scene_raw_success.c rename to applications/main/lfrfid/scenes/lfrfid_scene_raw_success.c diff --git a/applications/lfrfid/scenes/lfrfid_scene_read.c b/applications/main/lfrfid/scenes/lfrfid_scene_read.c similarity index 100% rename from applications/lfrfid/scenes/lfrfid_scene_read.c rename to applications/main/lfrfid/scenes/lfrfid_scene_read.c diff --git a/applications/lfrfid/scenes/lfrfid_scene_read_key_menu.c b/applications/main/lfrfid/scenes/lfrfid_scene_read_key_menu.c similarity index 100% rename from applications/lfrfid/scenes/lfrfid_scene_read_key_menu.c rename to applications/main/lfrfid/scenes/lfrfid_scene_read_key_menu.c diff --git a/applications/lfrfid/scenes/lfrfid_scene_read_success.c b/applications/main/lfrfid/scenes/lfrfid_scene_read_success.c similarity index 100% rename from applications/lfrfid/scenes/lfrfid_scene_read_success.c rename to applications/main/lfrfid/scenes/lfrfid_scene_read_success.c diff --git a/applications/lfrfid/scenes/lfrfid_scene_retry_confirm.c b/applications/main/lfrfid/scenes/lfrfid_scene_retry_confirm.c similarity index 100% rename from applications/lfrfid/scenes/lfrfid_scene_retry_confirm.c rename to applications/main/lfrfid/scenes/lfrfid_scene_retry_confirm.c diff --git a/applications/lfrfid/scenes/lfrfid_scene_rpc.c b/applications/main/lfrfid/scenes/lfrfid_scene_rpc.c similarity index 100% rename from applications/lfrfid/scenes/lfrfid_scene_rpc.c rename to applications/main/lfrfid/scenes/lfrfid_scene_rpc.c diff --git a/applications/lfrfid/scenes/lfrfid_scene_save_data.c b/applications/main/lfrfid/scenes/lfrfid_scene_save_data.c similarity index 100% rename from applications/lfrfid/scenes/lfrfid_scene_save_data.c rename to applications/main/lfrfid/scenes/lfrfid_scene_save_data.c diff --git a/applications/lfrfid/scenes/lfrfid_scene_save_name.c b/applications/main/lfrfid/scenes/lfrfid_scene_save_name.c similarity index 100% rename from applications/lfrfid/scenes/lfrfid_scene_save_name.c rename to applications/main/lfrfid/scenes/lfrfid_scene_save_name.c diff --git a/applications/lfrfid/scenes/lfrfid_scene_save_success.c b/applications/main/lfrfid/scenes/lfrfid_scene_save_success.c similarity index 100% rename from applications/lfrfid/scenes/lfrfid_scene_save_success.c rename to applications/main/lfrfid/scenes/lfrfid_scene_save_success.c diff --git a/applications/lfrfid/scenes/lfrfid_scene_save_type.c b/applications/main/lfrfid/scenes/lfrfid_scene_save_type.c similarity index 100% rename from applications/lfrfid/scenes/lfrfid_scene_save_type.c rename to applications/main/lfrfid/scenes/lfrfid_scene_save_type.c diff --git a/applications/lfrfid/scenes/lfrfid_scene_saved_info.c b/applications/main/lfrfid/scenes/lfrfid_scene_saved_info.c similarity index 100% rename from applications/lfrfid/scenes/lfrfid_scene_saved_info.c rename to applications/main/lfrfid/scenes/lfrfid_scene_saved_info.c diff --git a/applications/lfrfid/scenes/lfrfid_scene_saved_key_menu.c b/applications/main/lfrfid/scenes/lfrfid_scene_saved_key_menu.c similarity index 100% rename from applications/lfrfid/scenes/lfrfid_scene_saved_key_menu.c rename to applications/main/lfrfid/scenes/lfrfid_scene_saved_key_menu.c diff --git a/applications/lfrfid/scenes/lfrfid_scene_select_key.c b/applications/main/lfrfid/scenes/lfrfid_scene_select_key.c similarity index 100% rename from applications/lfrfid/scenes/lfrfid_scene_select_key.c rename to applications/main/lfrfid/scenes/lfrfid_scene_select_key.c diff --git a/applications/lfrfid/scenes/lfrfid_scene_start.c b/applications/main/lfrfid/scenes/lfrfid_scene_start.c similarity index 100% rename from applications/lfrfid/scenes/lfrfid_scene_start.c rename to applications/main/lfrfid/scenes/lfrfid_scene_start.c diff --git a/applications/lfrfid/scenes/lfrfid_scene_write.c b/applications/main/lfrfid/scenes/lfrfid_scene_write.c similarity index 100% rename from applications/lfrfid/scenes/lfrfid_scene_write.c rename to applications/main/lfrfid/scenes/lfrfid_scene_write.c diff --git a/applications/lfrfid/scenes/lfrfid_scene_write_success.c b/applications/main/lfrfid/scenes/lfrfid_scene_write_success.c similarity index 100% rename from applications/lfrfid/scenes/lfrfid_scene_write_success.c rename to applications/main/lfrfid/scenes/lfrfid_scene_write_success.c diff --git a/applications/lfrfid/views/lfrfid_view_read.c b/applications/main/lfrfid/views/lfrfid_view_read.c similarity index 100% rename from applications/lfrfid/views/lfrfid_view_read.c rename to applications/main/lfrfid/views/lfrfid_view_read.c diff --git a/applications/lfrfid/views/lfrfid_view_read.h b/applications/main/lfrfid/views/lfrfid_view_read.h similarity index 100% rename from applications/lfrfid/views/lfrfid_view_read.h rename to applications/main/lfrfid/views/lfrfid_view_read.h diff --git a/applications/nfc/application.fam b/applications/main/nfc/application.fam similarity index 100% rename from applications/nfc/application.fam rename to applications/main/nfc/application.fam diff --git a/applications/nfc/helpers/nfc_custom_event.h b/applications/main/nfc/helpers/nfc_custom_event.h similarity index 100% rename from applications/nfc/helpers/nfc_custom_event.h rename to applications/main/nfc/helpers/nfc_custom_event.h diff --git a/applications/nfc/helpers/nfc_emv_parser.c b/applications/main/nfc/helpers/nfc_emv_parser.c similarity index 100% rename from applications/nfc/helpers/nfc_emv_parser.c rename to applications/main/nfc/helpers/nfc_emv_parser.c diff --git a/applications/nfc/helpers/nfc_emv_parser.h b/applications/main/nfc/helpers/nfc_emv_parser.h similarity index 100% rename from applications/nfc/helpers/nfc_emv_parser.h rename to applications/main/nfc/helpers/nfc_emv_parser.h diff --git a/applications/nfc/helpers/nfc_generators.c b/applications/main/nfc/helpers/nfc_generators.c similarity index 100% rename from applications/nfc/helpers/nfc_generators.c rename to applications/main/nfc/helpers/nfc_generators.c diff --git a/applications/nfc/helpers/nfc_generators.h b/applications/main/nfc/helpers/nfc_generators.h similarity index 100% rename from applications/nfc/helpers/nfc_generators.h rename to applications/main/nfc/helpers/nfc_generators.h diff --git a/applications/nfc/nfc.c b/applications/main/nfc/nfc.c similarity index 100% rename from applications/nfc/nfc.c rename to applications/main/nfc/nfc.c diff --git a/applications/nfc/nfc.h b/applications/main/nfc/nfc.h similarity index 100% rename from applications/nfc/nfc.h rename to applications/main/nfc/nfc.h diff --git a/applications/nfc/nfc_cli.c b/applications/main/nfc/nfc_cli.c similarity index 100% rename from applications/nfc/nfc_cli.c rename to applications/main/nfc/nfc_cli.c diff --git a/applications/nfc/nfc_i.h b/applications/main/nfc/nfc_i.h similarity index 100% rename from applications/nfc/nfc_i.h rename to applications/main/nfc/nfc_i.h diff --git a/applications/nfc/scenes/nfc_scene.c b/applications/main/nfc/scenes/nfc_scene.c similarity index 100% rename from applications/nfc/scenes/nfc_scene.c rename to applications/main/nfc/scenes/nfc_scene.c diff --git a/applications/nfc/scenes/nfc_scene.h b/applications/main/nfc/scenes/nfc_scene.h similarity index 100% rename from applications/nfc/scenes/nfc_scene.h rename to applications/main/nfc/scenes/nfc_scene.h diff --git a/applications/nfc/scenes/nfc_scene_config.h b/applications/main/nfc/scenes/nfc_scene_config.h similarity index 100% rename from applications/nfc/scenes/nfc_scene_config.h rename to applications/main/nfc/scenes/nfc_scene_config.h diff --git a/applications/nfc/scenes/nfc_scene_debug.c b/applications/main/nfc/scenes/nfc_scene_debug.c similarity index 100% rename from applications/nfc/scenes/nfc_scene_debug.c rename to applications/main/nfc/scenes/nfc_scene_debug.c diff --git a/applications/nfc/scenes/nfc_scene_delete.c b/applications/main/nfc/scenes/nfc_scene_delete.c similarity index 100% rename from applications/nfc/scenes/nfc_scene_delete.c rename to applications/main/nfc/scenes/nfc_scene_delete.c diff --git a/applications/nfc/scenes/nfc_scene_delete_success.c b/applications/main/nfc/scenes/nfc_scene_delete_success.c similarity index 100% rename from applications/nfc/scenes/nfc_scene_delete_success.c rename to applications/main/nfc/scenes/nfc_scene_delete_success.c diff --git a/applications/nfc/scenes/nfc_scene_detect_reader.c b/applications/main/nfc/scenes/nfc_scene_detect_reader.c similarity index 100% rename from applications/nfc/scenes/nfc_scene_detect_reader.c rename to applications/main/nfc/scenes/nfc_scene_detect_reader.c diff --git a/applications/nfc/scenes/nfc_scene_device_info.c b/applications/main/nfc/scenes/nfc_scene_device_info.c similarity index 100% rename from applications/nfc/scenes/nfc_scene_device_info.c rename to applications/main/nfc/scenes/nfc_scene_device_info.c diff --git a/applications/nfc/scenes/nfc_scene_dict_not_found.c b/applications/main/nfc/scenes/nfc_scene_dict_not_found.c similarity index 100% rename from applications/nfc/scenes/nfc_scene_dict_not_found.c rename to applications/main/nfc/scenes/nfc_scene_dict_not_found.c diff --git a/applications/nfc/scenes/nfc_scene_emulate_apdu_sequence.c b/applications/main/nfc/scenes/nfc_scene_emulate_apdu_sequence.c similarity index 100% rename from applications/nfc/scenes/nfc_scene_emulate_apdu_sequence.c rename to applications/main/nfc/scenes/nfc_scene_emulate_apdu_sequence.c diff --git a/applications/nfc/scenes/nfc_scene_emulate_uid.c b/applications/main/nfc/scenes/nfc_scene_emulate_uid.c similarity index 100% rename from applications/nfc/scenes/nfc_scene_emulate_uid.c rename to applications/main/nfc/scenes/nfc_scene_emulate_uid.c diff --git a/applications/nfc/scenes/nfc_scene_emv_menu.c b/applications/main/nfc/scenes/nfc_scene_emv_menu.c similarity index 100% rename from applications/nfc/scenes/nfc_scene_emv_menu.c rename to applications/main/nfc/scenes/nfc_scene_emv_menu.c diff --git a/applications/nfc/scenes/nfc_scene_emv_read_success.c b/applications/main/nfc/scenes/nfc_scene_emv_read_success.c similarity index 100% rename from applications/nfc/scenes/nfc_scene_emv_read_success.c rename to applications/main/nfc/scenes/nfc_scene_emv_read_success.c diff --git a/applications/nfc/scenes/nfc_scene_exit_confirm.c b/applications/main/nfc/scenes/nfc_scene_exit_confirm.c similarity index 100% rename from applications/nfc/scenes/nfc_scene_exit_confirm.c rename to applications/main/nfc/scenes/nfc_scene_exit_confirm.c diff --git a/applications/nfc/scenes/nfc_scene_extra_actions.c b/applications/main/nfc/scenes/nfc_scene_extra_actions.c similarity index 100% rename from applications/nfc/scenes/nfc_scene_extra_actions.c rename to applications/main/nfc/scenes/nfc_scene_extra_actions.c diff --git a/applications/nfc/scenes/nfc_scene_field.c b/applications/main/nfc/scenes/nfc_scene_field.c similarity index 100% rename from applications/nfc/scenes/nfc_scene_field.c rename to applications/main/nfc/scenes/nfc_scene_field.c diff --git a/applications/nfc/scenes/nfc_scene_file_select.c b/applications/main/nfc/scenes/nfc_scene_file_select.c similarity index 100% rename from applications/nfc/scenes/nfc_scene_file_select.c rename to applications/main/nfc/scenes/nfc_scene_file_select.c diff --git a/applications/nfc/scenes/nfc_scene_generate_info.c b/applications/main/nfc/scenes/nfc_scene_generate_info.c similarity index 100% rename from applications/nfc/scenes/nfc_scene_generate_info.c rename to applications/main/nfc/scenes/nfc_scene_generate_info.c diff --git a/applications/nfc/scenes/nfc_scene_mf_classic_dict_attack.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c similarity index 100% rename from applications/nfc/scenes/nfc_scene_mf_classic_dict_attack.c rename to applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c diff --git a/applications/nfc/scenes/nfc_scene_mf_classic_emulate.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_emulate.c similarity index 100% rename from applications/nfc/scenes/nfc_scene_mf_classic_emulate.c rename to applications/main/nfc/scenes/nfc_scene_mf_classic_emulate.c diff --git a/applications/nfc/scenes/nfc_scene_mf_classic_keys.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys.c similarity index 100% rename from applications/nfc/scenes/nfc_scene_mf_classic_keys.c rename to applications/main/nfc/scenes/nfc_scene_mf_classic_keys.c diff --git a/applications/nfc/scenes/nfc_scene_mf_classic_keys_add.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_add.c similarity index 100% rename from applications/nfc/scenes/nfc_scene_mf_classic_keys_add.c rename to applications/main/nfc/scenes/nfc_scene_mf_classic_keys_add.c diff --git a/applications/nfc/scenes/nfc_scene_mf_classic_menu.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_menu.c similarity index 100% rename from applications/nfc/scenes/nfc_scene_mf_classic_menu.c rename to applications/main/nfc/scenes/nfc_scene_mf_classic_menu.c diff --git a/applications/nfc/scenes/nfc_scene_mf_classic_read_success.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_read_success.c similarity index 100% rename from applications/nfc/scenes/nfc_scene_mf_classic_read_success.c rename to applications/main/nfc/scenes/nfc_scene_mf_classic_read_success.c diff --git a/applications/nfc/scenes/nfc_scene_mf_desfire_app.c b/applications/main/nfc/scenes/nfc_scene_mf_desfire_app.c similarity index 100% rename from applications/nfc/scenes/nfc_scene_mf_desfire_app.c rename to applications/main/nfc/scenes/nfc_scene_mf_desfire_app.c diff --git a/applications/nfc/scenes/nfc_scene_mf_desfire_data.c b/applications/main/nfc/scenes/nfc_scene_mf_desfire_data.c similarity index 100% rename from applications/nfc/scenes/nfc_scene_mf_desfire_data.c rename to applications/main/nfc/scenes/nfc_scene_mf_desfire_data.c diff --git a/applications/nfc/scenes/nfc_scene_mf_desfire_menu.c b/applications/main/nfc/scenes/nfc_scene_mf_desfire_menu.c similarity index 100% rename from applications/nfc/scenes/nfc_scene_mf_desfire_menu.c rename to applications/main/nfc/scenes/nfc_scene_mf_desfire_menu.c diff --git a/applications/nfc/scenes/nfc_scene_mf_desfire_read_success.c b/applications/main/nfc/scenes/nfc_scene_mf_desfire_read_success.c similarity index 100% rename from applications/nfc/scenes/nfc_scene_mf_desfire_read_success.c rename to applications/main/nfc/scenes/nfc_scene_mf_desfire_read_success.c diff --git a/applications/nfc/scenes/nfc_scene_mf_ultralight_data.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_data.c similarity index 100% rename from applications/nfc/scenes/nfc_scene_mf_ultralight_data.c rename to applications/main/nfc/scenes/nfc_scene_mf_ultralight_data.c diff --git a/applications/nfc/scenes/nfc_scene_mf_ultralight_emulate.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_emulate.c similarity index 100% rename from applications/nfc/scenes/nfc_scene_mf_ultralight_emulate.c rename to applications/main/nfc/scenes/nfc_scene_mf_ultralight_emulate.c diff --git a/applications/nfc/scenes/nfc_scene_mf_ultralight_key_input.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_key_input.c similarity index 100% rename from applications/nfc/scenes/nfc_scene_mf_ultralight_key_input.c rename to applications/main/nfc/scenes/nfc_scene_mf_ultralight_key_input.c diff --git a/applications/nfc/scenes/nfc_scene_mf_ultralight_menu.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_menu.c similarity index 100% rename from applications/nfc/scenes/nfc_scene_mf_ultralight_menu.c rename to applications/main/nfc/scenes/nfc_scene_mf_ultralight_menu.c diff --git a/applications/nfc/scenes/nfc_scene_mf_ultralight_read_auth.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_auth.c similarity index 100% rename from applications/nfc/scenes/nfc_scene_mf_ultralight_read_auth.c rename to applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_auth.c diff --git a/applications/nfc/scenes/nfc_scene_mf_ultralight_read_auth_result.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_auth_result.c similarity index 100% rename from applications/nfc/scenes/nfc_scene_mf_ultralight_read_auth_result.c rename to applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_auth_result.c diff --git a/applications/nfc/scenes/nfc_scene_mf_ultralight_read_success.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_success.c similarity index 100% rename from applications/nfc/scenes/nfc_scene_mf_ultralight_read_success.c rename to applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_success.c diff --git a/applications/nfc/scenes/nfc_scene_mf_ultralight_unlock_menu.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_menu.c similarity index 100% rename from applications/nfc/scenes/nfc_scene_mf_ultralight_unlock_menu.c rename to applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_menu.c diff --git a/applications/nfc/scenes/nfc_scene_mf_ultralight_unlock_warn.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_warn.c similarity index 100% rename from applications/nfc/scenes/nfc_scene_mf_ultralight_unlock_warn.c rename to applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_warn.c diff --git a/applications/nfc/scenes/nfc_scene_mfkey_complete.c b/applications/main/nfc/scenes/nfc_scene_mfkey_complete.c similarity index 100% rename from applications/nfc/scenes/nfc_scene_mfkey_complete.c rename to applications/main/nfc/scenes/nfc_scene_mfkey_complete.c diff --git a/applications/nfc/scenes/nfc_scene_mfkey_nonces_info.c b/applications/main/nfc/scenes/nfc_scene_mfkey_nonces_info.c similarity index 100% rename from applications/nfc/scenes/nfc_scene_mfkey_nonces_info.c rename to applications/main/nfc/scenes/nfc_scene_mfkey_nonces_info.c diff --git a/applications/nfc/scenes/nfc_scene_nfc_data_info.c b/applications/main/nfc/scenes/nfc_scene_nfc_data_info.c similarity index 100% rename from applications/nfc/scenes/nfc_scene_nfc_data_info.c rename to applications/main/nfc/scenes/nfc_scene_nfc_data_info.c diff --git a/applications/nfc/scenes/nfc_scene_nfca_menu.c b/applications/main/nfc/scenes/nfc_scene_nfca_menu.c similarity index 100% rename from applications/nfc/scenes/nfc_scene_nfca_menu.c rename to applications/main/nfc/scenes/nfc_scene_nfca_menu.c diff --git a/applications/nfc/scenes/nfc_scene_nfca_read_success.c b/applications/main/nfc/scenes/nfc_scene_nfca_read_success.c similarity index 100% rename from applications/nfc/scenes/nfc_scene_nfca_read_success.c rename to applications/main/nfc/scenes/nfc_scene_nfca_read_success.c diff --git a/applications/nfc/scenes/nfc_scene_read.c b/applications/main/nfc/scenes/nfc_scene_read.c similarity index 100% rename from applications/nfc/scenes/nfc_scene_read.c rename to applications/main/nfc/scenes/nfc_scene_read.c diff --git a/applications/nfc/scenes/nfc_scene_read_card_success.c b/applications/main/nfc/scenes/nfc_scene_read_card_success.c similarity index 100% rename from applications/nfc/scenes/nfc_scene_read_card_success.c rename to applications/main/nfc/scenes/nfc_scene_read_card_success.c diff --git a/applications/nfc/scenes/nfc_scene_restore_original.c b/applications/main/nfc/scenes/nfc_scene_restore_original.c similarity index 100% rename from applications/nfc/scenes/nfc_scene_restore_original.c rename to applications/main/nfc/scenes/nfc_scene_restore_original.c diff --git a/applications/nfc/scenes/nfc_scene_restore_original_confirm.c b/applications/main/nfc/scenes/nfc_scene_restore_original_confirm.c similarity index 100% rename from applications/nfc/scenes/nfc_scene_restore_original_confirm.c rename to applications/main/nfc/scenes/nfc_scene_restore_original_confirm.c diff --git a/applications/nfc/scenes/nfc_scene_retry_confirm.c b/applications/main/nfc/scenes/nfc_scene_retry_confirm.c similarity index 100% rename from applications/nfc/scenes/nfc_scene_retry_confirm.c rename to applications/main/nfc/scenes/nfc_scene_retry_confirm.c diff --git a/applications/nfc/scenes/nfc_scene_rpc.c b/applications/main/nfc/scenes/nfc_scene_rpc.c similarity index 100% rename from applications/nfc/scenes/nfc_scene_rpc.c rename to applications/main/nfc/scenes/nfc_scene_rpc.c diff --git a/applications/nfc/scenes/nfc_scene_save_name.c b/applications/main/nfc/scenes/nfc_scene_save_name.c similarity index 100% rename from applications/nfc/scenes/nfc_scene_save_name.c rename to applications/main/nfc/scenes/nfc_scene_save_name.c diff --git a/applications/nfc/scenes/nfc_scene_save_success.c b/applications/main/nfc/scenes/nfc_scene_save_success.c similarity index 100% rename from applications/nfc/scenes/nfc_scene_save_success.c rename to applications/main/nfc/scenes/nfc_scene_save_success.c diff --git a/applications/nfc/scenes/nfc_scene_saved_menu.c b/applications/main/nfc/scenes/nfc_scene_saved_menu.c similarity index 100% rename from applications/nfc/scenes/nfc_scene_saved_menu.c rename to applications/main/nfc/scenes/nfc_scene_saved_menu.c diff --git a/applications/nfc/scenes/nfc_scene_set_atqa.c b/applications/main/nfc/scenes/nfc_scene_set_atqa.c similarity index 100% rename from applications/nfc/scenes/nfc_scene_set_atqa.c rename to applications/main/nfc/scenes/nfc_scene_set_atqa.c diff --git a/applications/nfc/scenes/nfc_scene_set_sak.c b/applications/main/nfc/scenes/nfc_scene_set_sak.c similarity index 100% rename from applications/nfc/scenes/nfc_scene_set_sak.c rename to applications/main/nfc/scenes/nfc_scene_set_sak.c diff --git a/applications/nfc/scenes/nfc_scene_set_type.c b/applications/main/nfc/scenes/nfc_scene_set_type.c similarity index 100% rename from applications/nfc/scenes/nfc_scene_set_type.c rename to applications/main/nfc/scenes/nfc_scene_set_type.c diff --git a/applications/nfc/scenes/nfc_scene_set_uid.c b/applications/main/nfc/scenes/nfc_scene_set_uid.c similarity index 100% rename from applications/nfc/scenes/nfc_scene_set_uid.c rename to applications/main/nfc/scenes/nfc_scene_set_uid.c diff --git a/applications/nfc/scenes/nfc_scene_start.c b/applications/main/nfc/scenes/nfc_scene_start.c similarity index 100% rename from applications/nfc/scenes/nfc_scene_start.c rename to applications/main/nfc/scenes/nfc_scene_start.c diff --git a/applications/nfc/views/detect_reader.c b/applications/main/nfc/views/detect_reader.c similarity index 100% rename from applications/nfc/views/detect_reader.c rename to applications/main/nfc/views/detect_reader.c diff --git a/applications/nfc/views/detect_reader.h b/applications/main/nfc/views/detect_reader.h similarity index 100% rename from applications/nfc/views/detect_reader.h rename to applications/main/nfc/views/detect_reader.h diff --git a/applications/nfc/views/dict_attack.c b/applications/main/nfc/views/dict_attack.c similarity index 100% rename from applications/nfc/views/dict_attack.c rename to applications/main/nfc/views/dict_attack.c diff --git a/applications/nfc/views/dict_attack.h b/applications/main/nfc/views/dict_attack.h similarity index 100% rename from applications/nfc/views/dict_attack.h rename to applications/main/nfc/views/dict_attack.h diff --git a/applications/subghz/application.fam b/applications/main/subghz/application.fam similarity index 100% rename from applications/subghz/application.fam rename to applications/main/subghz/application.fam diff --git a/applications/subghz/helpers/subghz_chat.c b/applications/main/subghz/helpers/subghz_chat.c similarity index 100% rename from applications/subghz/helpers/subghz_chat.c rename to applications/main/subghz/helpers/subghz_chat.c diff --git a/applications/subghz/helpers/subghz_chat.h b/applications/main/subghz/helpers/subghz_chat.h similarity index 100% rename from applications/subghz/helpers/subghz_chat.h rename to applications/main/subghz/helpers/subghz_chat.h diff --git a/applications/subghz/helpers/subghz_custom_event.h b/applications/main/subghz/helpers/subghz_custom_event.h similarity index 100% rename from applications/subghz/helpers/subghz_custom_event.h rename to applications/main/subghz/helpers/subghz_custom_event.h diff --git a/applications/subghz/helpers/subghz_frequency_analyzer_worker.c b/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.c similarity index 100% rename from applications/subghz/helpers/subghz_frequency_analyzer_worker.c rename to applications/main/subghz/helpers/subghz_frequency_analyzer_worker.c diff --git a/applications/subghz/helpers/subghz_frequency_analyzer_worker.h b/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.h similarity index 100% rename from applications/subghz/helpers/subghz_frequency_analyzer_worker.h rename to applications/main/subghz/helpers/subghz_frequency_analyzer_worker.h diff --git a/applications/subghz/helpers/subghz_testing.c b/applications/main/subghz/helpers/subghz_testing.c similarity index 100% rename from applications/subghz/helpers/subghz_testing.c rename to applications/main/subghz/helpers/subghz_testing.c diff --git a/applications/subghz/helpers/subghz_testing.h b/applications/main/subghz/helpers/subghz_testing.h similarity index 100% rename from applications/subghz/helpers/subghz_testing.h rename to applications/main/subghz/helpers/subghz_testing.h diff --git a/applications/subghz/helpers/subghz_types.h b/applications/main/subghz/helpers/subghz_types.h similarity index 100% rename from applications/subghz/helpers/subghz_types.h rename to applications/main/subghz/helpers/subghz_types.h diff --git a/applications/subghz/scenes/subghz_scene.c b/applications/main/subghz/scenes/subghz_scene.c similarity index 100% rename from applications/subghz/scenes/subghz_scene.c rename to applications/main/subghz/scenes/subghz_scene.c diff --git a/applications/subghz/scenes/subghz_scene.h b/applications/main/subghz/scenes/subghz_scene.h similarity index 100% rename from applications/subghz/scenes/subghz_scene.h rename to applications/main/subghz/scenes/subghz_scene.h diff --git a/applications/subghz/scenes/subghz_scene_config.h b/applications/main/subghz/scenes/subghz_scene_config.h similarity index 100% rename from applications/subghz/scenes/subghz_scene_config.h rename to applications/main/subghz/scenes/subghz_scene_config.h diff --git a/applications/subghz/scenes/subghz_scene_delete.c b/applications/main/subghz/scenes/subghz_scene_delete.c similarity index 100% rename from applications/subghz/scenes/subghz_scene_delete.c rename to applications/main/subghz/scenes/subghz_scene_delete.c diff --git a/applications/subghz/scenes/subghz_scene_delete_raw.c b/applications/main/subghz/scenes/subghz_scene_delete_raw.c similarity index 100% rename from applications/subghz/scenes/subghz_scene_delete_raw.c rename to applications/main/subghz/scenes/subghz_scene_delete_raw.c diff --git a/applications/subghz/scenes/subghz_scene_delete_success.c b/applications/main/subghz/scenes/subghz_scene_delete_success.c similarity index 100% rename from applications/subghz/scenes/subghz_scene_delete_success.c rename to applications/main/subghz/scenes/subghz_scene_delete_success.c diff --git a/applications/subghz/scenes/subghz_scene_frequency_analyzer.c b/applications/main/subghz/scenes/subghz_scene_frequency_analyzer.c similarity index 100% rename from applications/subghz/scenes/subghz_scene_frequency_analyzer.c rename to applications/main/subghz/scenes/subghz_scene_frequency_analyzer.c diff --git a/applications/subghz/scenes/subghz_scene_more_raw.c b/applications/main/subghz/scenes/subghz_scene_more_raw.c similarity index 100% rename from applications/subghz/scenes/subghz_scene_more_raw.c rename to applications/main/subghz/scenes/subghz_scene_more_raw.c diff --git a/applications/subghz/scenes/subghz_scene_need_saving.c b/applications/main/subghz/scenes/subghz_scene_need_saving.c similarity index 100% rename from applications/subghz/scenes/subghz_scene_need_saving.c rename to applications/main/subghz/scenes/subghz_scene_need_saving.c diff --git a/applications/subghz/scenes/subghz_scene_read_raw.c b/applications/main/subghz/scenes/subghz_scene_read_raw.c similarity index 100% rename from applications/subghz/scenes/subghz_scene_read_raw.c rename to applications/main/subghz/scenes/subghz_scene_read_raw.c diff --git a/applications/subghz/scenes/subghz_scene_receiver.c b/applications/main/subghz/scenes/subghz_scene_receiver.c similarity index 100% rename from applications/subghz/scenes/subghz_scene_receiver.c rename to applications/main/subghz/scenes/subghz_scene_receiver.c diff --git a/applications/subghz/scenes/subghz_scene_receiver_config.c b/applications/main/subghz/scenes/subghz_scene_receiver_config.c similarity index 100% rename from applications/subghz/scenes/subghz_scene_receiver_config.c rename to applications/main/subghz/scenes/subghz_scene_receiver_config.c diff --git a/applications/subghz/scenes/subghz_scene_receiver_info.c b/applications/main/subghz/scenes/subghz_scene_receiver_info.c similarity index 100% rename from applications/subghz/scenes/subghz_scene_receiver_info.c rename to applications/main/subghz/scenes/subghz_scene_receiver_info.c diff --git a/applications/subghz/scenes/subghz_scene_rpc.c b/applications/main/subghz/scenes/subghz_scene_rpc.c similarity index 100% rename from applications/subghz/scenes/subghz_scene_rpc.c rename to applications/main/subghz/scenes/subghz_scene_rpc.c diff --git a/applications/subghz/scenes/subghz_scene_save_name.c b/applications/main/subghz/scenes/subghz_scene_save_name.c similarity index 100% rename from applications/subghz/scenes/subghz_scene_save_name.c rename to applications/main/subghz/scenes/subghz_scene_save_name.c diff --git a/applications/subghz/scenes/subghz_scene_save_success.c b/applications/main/subghz/scenes/subghz_scene_save_success.c similarity index 100% rename from applications/subghz/scenes/subghz_scene_save_success.c rename to applications/main/subghz/scenes/subghz_scene_save_success.c diff --git a/applications/subghz/scenes/subghz_scene_saved.c b/applications/main/subghz/scenes/subghz_scene_saved.c similarity index 100% rename from applications/subghz/scenes/subghz_scene_saved.c rename to applications/main/subghz/scenes/subghz_scene_saved.c diff --git a/applications/subghz/scenes/subghz_scene_saved_menu.c b/applications/main/subghz/scenes/subghz_scene_saved_menu.c similarity index 100% rename from applications/subghz/scenes/subghz_scene_saved_menu.c rename to applications/main/subghz/scenes/subghz_scene_saved_menu.c diff --git a/applications/subghz/scenes/subghz_scene_set_type.c b/applications/main/subghz/scenes/subghz_scene_set_type.c similarity index 100% rename from applications/subghz/scenes/subghz_scene_set_type.c rename to applications/main/subghz/scenes/subghz_scene_set_type.c diff --git a/applications/subghz/scenes/subghz_scene_show_error.c b/applications/main/subghz/scenes/subghz_scene_show_error.c similarity index 100% rename from applications/subghz/scenes/subghz_scene_show_error.c rename to applications/main/subghz/scenes/subghz_scene_show_error.c diff --git a/applications/subghz/scenes/subghz_scene_show_error_sub.c b/applications/main/subghz/scenes/subghz_scene_show_error_sub.c similarity index 100% rename from applications/subghz/scenes/subghz_scene_show_error_sub.c rename to applications/main/subghz/scenes/subghz_scene_show_error_sub.c diff --git a/applications/subghz/scenes/subghz_scene_show_only_rx.c b/applications/main/subghz/scenes/subghz_scene_show_only_rx.c similarity index 100% rename from applications/subghz/scenes/subghz_scene_show_only_rx.c rename to applications/main/subghz/scenes/subghz_scene_show_only_rx.c diff --git a/applications/subghz/scenes/subghz_scene_start.c b/applications/main/subghz/scenes/subghz_scene_start.c similarity index 100% rename from applications/subghz/scenes/subghz_scene_start.c rename to applications/main/subghz/scenes/subghz_scene_start.c diff --git a/applications/subghz/scenes/subghz_scene_test.c b/applications/main/subghz/scenes/subghz_scene_test.c similarity index 100% rename from applications/subghz/scenes/subghz_scene_test.c rename to applications/main/subghz/scenes/subghz_scene_test.c diff --git a/applications/subghz/scenes/subghz_scene_test_carrier.c b/applications/main/subghz/scenes/subghz_scene_test_carrier.c similarity index 100% rename from applications/subghz/scenes/subghz_scene_test_carrier.c rename to applications/main/subghz/scenes/subghz_scene_test_carrier.c diff --git a/applications/subghz/scenes/subghz_scene_test_packet.c b/applications/main/subghz/scenes/subghz_scene_test_packet.c similarity index 100% rename from applications/subghz/scenes/subghz_scene_test_packet.c rename to applications/main/subghz/scenes/subghz_scene_test_packet.c diff --git a/applications/subghz/scenes/subghz_scene_test_static.c b/applications/main/subghz/scenes/subghz_scene_test_static.c similarity index 100% rename from applications/subghz/scenes/subghz_scene_test_static.c rename to applications/main/subghz/scenes/subghz_scene_test_static.c diff --git a/applications/subghz/scenes/subghz_scene_transmitter.c b/applications/main/subghz/scenes/subghz_scene_transmitter.c similarity index 100% rename from applications/subghz/scenes/subghz_scene_transmitter.c rename to applications/main/subghz/scenes/subghz_scene_transmitter.c diff --git a/applications/subghz/subghz.c b/applications/main/subghz/subghz.c similarity index 100% rename from applications/subghz/subghz.c rename to applications/main/subghz/subghz.c diff --git a/applications/subghz/subghz.h b/applications/main/subghz/subghz.h similarity index 100% rename from applications/subghz/subghz.h rename to applications/main/subghz/subghz.h diff --git a/applications/subghz/subghz_cli.c b/applications/main/subghz/subghz_cli.c similarity index 99% rename from applications/subghz/subghz_cli.c rename to applications/main/subghz/subghz_cli.c index cda50c79cab..d921979f0d2 100644 --- a/applications/subghz/subghz_cli.c +++ b/applications/main/subghz/subghz_cli.c @@ -862,5 +862,7 @@ void subghz_on_system_start() { furi_record_close(RECORD_STORAGE); #else UNUSED(subghz_cli_command); + UNUSED(subghz_on_system_start_istream_decode_band); + UNUSED(subghz_on_system_start_istream_read); #endif } diff --git a/applications/subghz/subghz_cli.h b/applications/main/subghz/subghz_cli.h similarity index 100% rename from applications/subghz/subghz_cli.h rename to applications/main/subghz/subghz_cli.h diff --git a/applications/subghz/subghz_history.c b/applications/main/subghz/subghz_history.c similarity index 100% rename from applications/subghz/subghz_history.c rename to applications/main/subghz/subghz_history.c diff --git a/applications/subghz/subghz_history.h b/applications/main/subghz/subghz_history.h similarity index 100% rename from applications/subghz/subghz_history.h rename to applications/main/subghz/subghz_history.h diff --git a/applications/subghz/subghz_i.c b/applications/main/subghz/subghz_i.c similarity index 98% rename from applications/subghz/subghz_i.c rename to applications/main/subghz/subghz_i.c index dc0d71e56cb..91404fc71d9 100644 --- a/applications/subghz/subghz_i.c +++ b/applications/main/subghz/subghz_i.c @@ -7,9 +7,9 @@ #include #include #include +#include #include #include -#include "../notification/notification.h" #include "views/receiver.h" #include @@ -461,15 +461,12 @@ bool subghz_load_protocol_from_file(SubGhz* subghz) { string_t file_path; string_init(file_path); + DialogsFileBrowserOptions browser_options; + dialog_file_browser_set_basic_options(&browser_options, SUBGHZ_APP_EXTENSION, &I_sub1_10px); + // Input events and views are managed by file_select bool res = dialog_file_browser_show( - subghz->dialogs, - subghz->file_path, - subghz->file_path, - SUBGHZ_APP_EXTENSION, - true, - &I_sub1_10px, - true); + subghz->dialogs, subghz->file_path, subghz->file_path, &browser_options); if(res) { res = subghz_key_load(subghz, string_get_cstr(subghz->file_path), true); diff --git a/applications/subghz/subghz_i.h b/applications/main/subghz/subghz_i.h similarity index 100% rename from applications/subghz/subghz_i.h rename to applications/main/subghz/subghz_i.h diff --git a/applications/subghz/subghz_setting.c b/applications/main/subghz/subghz_setting.c similarity index 100% rename from applications/subghz/subghz_setting.c rename to applications/main/subghz/subghz_setting.c diff --git a/applications/subghz/subghz_setting.h b/applications/main/subghz/subghz_setting.h similarity index 100% rename from applications/subghz/subghz_setting.h rename to applications/main/subghz/subghz_setting.h diff --git a/applications/subghz/views/receiver.c b/applications/main/subghz/views/receiver.c similarity index 100% rename from applications/subghz/views/receiver.c rename to applications/main/subghz/views/receiver.c diff --git a/applications/subghz/views/receiver.h b/applications/main/subghz/views/receiver.h similarity index 100% rename from applications/subghz/views/receiver.h rename to applications/main/subghz/views/receiver.h diff --git a/applications/subghz/views/subghz_frequency_analyzer.c b/applications/main/subghz/views/subghz_frequency_analyzer.c similarity index 100% rename from applications/subghz/views/subghz_frequency_analyzer.c rename to applications/main/subghz/views/subghz_frequency_analyzer.c diff --git a/applications/subghz/views/subghz_frequency_analyzer.h b/applications/main/subghz/views/subghz_frequency_analyzer.h similarity index 100% rename from applications/subghz/views/subghz_frequency_analyzer.h rename to applications/main/subghz/views/subghz_frequency_analyzer.h diff --git a/applications/subghz/views/subghz_read_raw.c b/applications/main/subghz/views/subghz_read_raw.c similarity index 100% rename from applications/subghz/views/subghz_read_raw.c rename to applications/main/subghz/views/subghz_read_raw.c diff --git a/applications/subghz/views/subghz_read_raw.h b/applications/main/subghz/views/subghz_read_raw.h similarity index 100% rename from applications/subghz/views/subghz_read_raw.h rename to applications/main/subghz/views/subghz_read_raw.h diff --git a/applications/subghz/views/subghz_test_carrier.c b/applications/main/subghz/views/subghz_test_carrier.c similarity index 100% rename from applications/subghz/views/subghz_test_carrier.c rename to applications/main/subghz/views/subghz_test_carrier.c diff --git a/applications/subghz/views/subghz_test_carrier.h b/applications/main/subghz/views/subghz_test_carrier.h similarity index 100% rename from applications/subghz/views/subghz_test_carrier.h rename to applications/main/subghz/views/subghz_test_carrier.h diff --git a/applications/subghz/views/subghz_test_packet.c b/applications/main/subghz/views/subghz_test_packet.c similarity index 100% rename from applications/subghz/views/subghz_test_packet.c rename to applications/main/subghz/views/subghz_test_packet.c diff --git a/applications/subghz/views/subghz_test_packet.h b/applications/main/subghz/views/subghz_test_packet.h similarity index 100% rename from applications/subghz/views/subghz_test_packet.h rename to applications/main/subghz/views/subghz_test_packet.h diff --git a/applications/subghz/views/subghz_test_static.c b/applications/main/subghz/views/subghz_test_static.c similarity index 100% rename from applications/subghz/views/subghz_test_static.c rename to applications/main/subghz/views/subghz_test_static.c diff --git a/applications/subghz/views/subghz_test_static.h b/applications/main/subghz/views/subghz_test_static.h similarity index 100% rename from applications/subghz/views/subghz_test_static.h rename to applications/main/subghz/views/subghz_test_static.h diff --git a/applications/subghz/views/transmitter.c b/applications/main/subghz/views/transmitter.c similarity index 100% rename from applications/subghz/views/transmitter.c rename to applications/main/subghz/views/transmitter.c diff --git a/applications/subghz/views/transmitter.h b/applications/main/subghz/views/transmitter.h similarity index 100% rename from applications/subghz/views/transmitter.h rename to applications/main/subghz/views/transmitter.h diff --git a/applications/u2f/application.fam b/applications/main/u2f/application.fam similarity index 100% rename from applications/u2f/application.fam rename to applications/main/u2f/application.fam diff --git a/applications/u2f/scenes/u2f_scene.c b/applications/main/u2f/scenes/u2f_scene.c similarity index 100% rename from applications/u2f/scenes/u2f_scene.c rename to applications/main/u2f/scenes/u2f_scene.c diff --git a/applications/u2f/scenes/u2f_scene.h b/applications/main/u2f/scenes/u2f_scene.h similarity index 100% rename from applications/u2f/scenes/u2f_scene.h rename to applications/main/u2f/scenes/u2f_scene.h diff --git a/applications/u2f/scenes/u2f_scene_config.h b/applications/main/u2f/scenes/u2f_scene_config.h similarity index 100% rename from applications/u2f/scenes/u2f_scene_config.h rename to applications/main/u2f/scenes/u2f_scene_config.h diff --git a/applications/u2f/scenes/u2f_scene_error.c b/applications/main/u2f/scenes/u2f_scene_error.c similarity index 100% rename from applications/u2f/scenes/u2f_scene_error.c rename to applications/main/u2f/scenes/u2f_scene_error.c diff --git a/applications/u2f/scenes/u2f_scene_main.c b/applications/main/u2f/scenes/u2f_scene_main.c similarity index 100% rename from applications/u2f/scenes/u2f_scene_main.c rename to applications/main/u2f/scenes/u2f_scene_main.c diff --git a/applications/u2f/u2f.c b/applications/main/u2f/u2f.c similarity index 100% rename from applications/u2f/u2f.c rename to applications/main/u2f/u2f.c diff --git a/applications/u2f/u2f.h b/applications/main/u2f/u2f.h similarity index 100% rename from applications/u2f/u2f.h rename to applications/main/u2f/u2f.h diff --git a/applications/u2f/u2f_app.c b/applications/main/u2f/u2f_app.c similarity index 100% rename from applications/u2f/u2f_app.c rename to applications/main/u2f/u2f_app.c diff --git a/applications/u2f/u2f_app.h b/applications/main/u2f/u2f_app.h similarity index 100% rename from applications/u2f/u2f_app.h rename to applications/main/u2f/u2f_app.h diff --git a/applications/u2f/u2f_app_i.h b/applications/main/u2f/u2f_app_i.h similarity index 100% rename from applications/u2f/u2f_app_i.h rename to applications/main/u2f/u2f_app_i.h diff --git a/applications/u2f/u2f_data.c b/applications/main/u2f/u2f_data.c similarity index 100% rename from applications/u2f/u2f_data.c rename to applications/main/u2f/u2f_data.c diff --git a/applications/u2f/u2f_data.h b/applications/main/u2f/u2f_data.h similarity index 100% rename from applications/u2f/u2f_data.h rename to applications/main/u2f/u2f_data.h diff --git a/applications/u2f/u2f_hid.c b/applications/main/u2f/u2f_hid.c similarity index 100% rename from applications/u2f/u2f_hid.c rename to applications/main/u2f/u2f_hid.c diff --git a/applications/u2f/u2f_hid.h b/applications/main/u2f/u2f_hid.h similarity index 100% rename from applications/u2f/u2f_hid.h rename to applications/main/u2f/u2f_hid.h diff --git a/applications/u2f/views/u2f_view.c b/applications/main/u2f/views/u2f_view.c similarity index 100% rename from applications/u2f/views/u2f_view.c rename to applications/main/u2f/views/u2f_view.c diff --git a/applications/u2f/views/u2f_view.h b/applications/main/u2f/views/u2f_view.h similarity index 100% rename from applications/u2f/views/u2f_view.h rename to applications/main/u2f/views/u2f_view.h diff --git a/applications/meta/application.fam b/applications/meta/application.fam deleted file mode 100644 index 8b873b5fba6..00000000000 --- a/applications/meta/application.fam +++ /dev/null @@ -1,41 +0,0 @@ -App( - appid="basic_services", - name="Basic services", - apptype=FlipperAppType.METAPACKAGE, - provides=[ - "rpc_start", - "bt", - "desktop", - "loader", - "power", - ], -) - - -App( - appid="basic_apps", - name="Basic applications for main menu", - apptype=FlipperAppType.METAPACKAGE, - provides=[ - "gpio", - "ibutton", - "infrared", - "lfrfid", - "nfc", - "subghz", - "bad_usb", - "u2f", - ], -) - - -App( - appid="basic_plugins", - name="Basic applications for plug-in menu", - apptype=FlipperAppType.METAPACKAGE, - provides=[ - "music_player", - "snake_game", - "bt_hid", - ], -) diff --git a/applications/picopass/application.fam b/applications/picopass/application.fam deleted file mode 100644 index ffc4b5182b8..00000000000 --- a/applications/picopass/application.fam +++ /dev/null @@ -1,11 +0,0 @@ -App( - appid="picopass", - name="PicoPass Reader", - apptype=FlipperAppType.PLUGIN, - entry_point="picopass_app", - cdefines=["APP_PICOPASS"], - requires=["storage", "gui"], - stack_size=4 * 1024, - icon="A_Plugins_14", - order=30, -) diff --git a/applications/plugins/application.fam b/applications/plugins/application.fam new file mode 100644 index 00000000000..c88f6d2890c --- /dev/null +++ b/applications/plugins/application.fam @@ -0,0 +1,10 @@ +App( + appid="basic_plugins", + name="Basic applications for plug-in menu", + apptype=FlipperAppType.METAPACKAGE, + provides=[ + "music_player", + "snake_game", + "bt_hid", + ], +) diff --git a/applications/plugins/bt_hid_app/application.fam b/applications/plugins/bt_hid_app/application.fam new file mode 100644 index 00000000000..e6a3b1752d4 --- /dev/null +++ b/applications/plugins/bt_hid_app/application.fam @@ -0,0 +1,15 @@ +App( + appid="bt_hid", + name="Bluetooth Remote", + apptype=FlipperAppType.PLUGIN, + entry_point="bt_hid_app", + stack_size=1 * 1024, + cdefines=["APP_BLE_HID"], + requires=[ + "bt", + "gui", + ], + order=10, + fap_icon="bt_remote_10px.png", + fap_category="Tools", +) diff --git a/applications/bt/bt_hid_app/bt_hid.c b/applications/plugins/bt_hid_app/bt_hid.c similarity index 99% rename from applications/bt/bt_hid_app/bt_hid.c rename to applications/plugins/bt_hid_app/bt_hid.c index 0827bd0ad0f..b653fb37d0f 100644 --- a/applications/bt/bt_hid_app/bt_hid.c +++ b/applications/plugins/bt_hid_app/bt_hid.c @@ -1,6 +1,6 @@ #include "bt_hid.h" #include -#include +#include #define TAG "BtHidApp" diff --git a/applications/bt/bt_hid_app/bt_hid.h b/applications/plugins/bt_hid_app/bt_hid.h similarity index 94% rename from applications/bt/bt_hid_app/bt_hid.h rename to applications/plugins/bt_hid_app/bt_hid.h index 81d092db4d0..0f4c7be9fd5 100644 --- a/applications/bt/bt_hid_app/bt_hid.h +++ b/applications/plugins/bt_hid_app/bt_hid.h @@ -5,7 +5,7 @@ #include #include #include -#include +#include #include #include diff --git a/applications/plugins/bt_hid_app/bt_remote_10px.png b/applications/plugins/bt_hid_app/bt_remote_10px.png new file mode 100644 index 0000000000000000000000000000000000000000..d4d30afe0465e3bd0d87355a09bbdfc6c44aa5d9 GIT binary patch literal 151 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2VGmzZ%#=aj&u?6^qxc>kDAIJlDSNs& zhE&W+PH5C8xG literal 0 HcmV?d00001 diff --git a/applications/bt/bt_hid_app/views/bt_hid_keyboard.c b/applications/plugins/bt_hid_app/views/bt_hid_keyboard.c similarity index 100% rename from applications/bt/bt_hid_app/views/bt_hid_keyboard.c rename to applications/plugins/bt_hid_app/views/bt_hid_keyboard.c diff --git a/applications/bt/bt_hid_app/views/bt_hid_keyboard.h b/applications/plugins/bt_hid_app/views/bt_hid_keyboard.h similarity index 100% rename from applications/bt/bt_hid_app/views/bt_hid_keyboard.h rename to applications/plugins/bt_hid_app/views/bt_hid_keyboard.h diff --git a/applications/bt/bt_hid_app/views/bt_hid_keynote.c b/applications/plugins/bt_hid_app/views/bt_hid_keynote.c similarity index 100% rename from applications/bt/bt_hid_app/views/bt_hid_keynote.c rename to applications/plugins/bt_hid_app/views/bt_hid_keynote.c diff --git a/applications/bt/bt_hid_app/views/bt_hid_keynote.h b/applications/plugins/bt_hid_app/views/bt_hid_keynote.h similarity index 100% rename from applications/bt/bt_hid_app/views/bt_hid_keynote.h rename to applications/plugins/bt_hid_app/views/bt_hid_keynote.h diff --git a/applications/bt/bt_hid_app/views/bt_hid_media.c b/applications/plugins/bt_hid_app/views/bt_hid_media.c similarity index 100% rename from applications/bt/bt_hid_app/views/bt_hid_media.c rename to applications/plugins/bt_hid_app/views/bt_hid_media.c diff --git a/applications/bt/bt_hid_app/views/bt_hid_media.h b/applications/plugins/bt_hid_app/views/bt_hid_media.h similarity index 100% rename from applications/bt/bt_hid_app/views/bt_hid_media.h rename to applications/plugins/bt_hid_app/views/bt_hid_media.h diff --git a/applications/bt/bt_hid_app/views/bt_hid_mouse.c b/applications/plugins/bt_hid_app/views/bt_hid_mouse.c similarity index 100% rename from applications/bt/bt_hid_app/views/bt_hid_mouse.c rename to applications/plugins/bt_hid_app/views/bt_hid_mouse.c diff --git a/applications/bt/bt_hid_app/views/bt_hid_mouse.h b/applications/plugins/bt_hid_app/views/bt_hid_mouse.h similarity index 100% rename from applications/bt/bt_hid_app/views/bt_hid_mouse.h rename to applications/plugins/bt_hid_app/views/bt_hid_mouse.h diff --git a/applications/music_player/application.fam b/applications/plugins/music_player/application.fam similarity index 84% rename from applications/music_player/application.fam rename to applications/plugins/music_player/application.fam index 70e2974323a..76787e09719 100644 --- a/applications/music_player/application.fam +++ b/applications/plugins/music_player/application.fam @@ -11,6 +11,8 @@ App( provides=["music_player_start"], stack_size=2 * 1024, order=20, + fap_icon="../../../assets/icons/Archive/music_10px.png", + fap_category="Misc", ) App( diff --git a/applications/music_player/music_player.c b/applications/plugins/music_player/music_player.c similarity index 96% rename from applications/music_player/music_player.c rename to applications/plugins/music_player/music_player.c index 121efa0f980..40e9085fb50 100644 --- a/applications/music_player/music_player.c +++ b/applications/plugins/music_player/music_player.c @@ -305,15 +305,13 @@ int32_t music_player_app(void* p) { } else { string_set_str(file_path, MUSIC_PLAYER_APP_PATH_FOLDER); + DialogsFileBrowserOptions browser_options; + dialog_file_browser_set_basic_options( + &browser_options, MUSIC_PLAYER_APP_EXTENSION, &I_music_10px); + browser_options.hide_ext = false; + DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); - bool res = dialog_file_browser_show( - dialogs, - file_path, - file_path, - MUSIC_PLAYER_APP_EXTENSION, - true, - &I_music_10px, - false); + bool res = dialog_file_browser_show(dialogs, file_path, file_path, &browser_options); furi_record_close(RECORD_DIALOGS); if(!res) { diff --git a/applications/music_player/music_player_cli.c b/applications/plugins/music_player/music_player_cli.c similarity index 100% rename from applications/music_player/music_player_cli.c rename to applications/plugins/music_player/music_player_cli.c diff --git a/applications/music_player/music_player_worker.c b/applications/plugins/music_player/music_player_worker.c similarity index 100% rename from applications/music_player/music_player_worker.c rename to applications/plugins/music_player/music_player_worker.c diff --git a/applications/music_player/music_player_worker.h b/applications/plugins/music_player/music_player_worker.h similarity index 100% rename from applications/music_player/music_player_worker.h rename to applications/plugins/music_player/music_player_worker.h diff --git a/applications/plugins/picopass/application.fam b/applications/plugins/picopass/application.fam new file mode 100644 index 00000000000..887d0324c2a --- /dev/null +++ b/applications/plugins/picopass/application.fam @@ -0,0 +1,17 @@ +App( + appid="picopass", + name="PicoPass Reader", + apptype=FlipperAppType.PLUGIN, + entry_point="picopass_app", + requires=[ + "storage", + "gui", + ], + stack_size=4 * 1024, + order=30, + fap_icon="../../../assets/icons/Archive/125_10px.png", + fap_libs=[ + "mbedtls", + ], + fap_category="Tools", +) diff --git a/lib/loclass/optimized_cipher.c b/applications/plugins/picopass/loclass/optimized_cipher.c similarity index 73% rename from lib/loclass/optimized_cipher.c rename to applications/plugins/picopass/loclass/optimized_cipher.c index e4f6a58c355..eba95538f12 100644 --- a/lib/loclass/optimized_cipher.c +++ b/applications/plugins/picopass/loclass/optimized_cipher.c @@ -82,23 +82,17 @@ #include "optimized_cipherutils.h" static const uint8_t loclass_opt_select_LUT[256] = { - 00, 03, 02, 01, 02, 03, 00, 01, 04, 07, 07, 04, 06, 07, 05, 04, - 01, 02, 03, 00, 02, 03, 00, 01, 05, 06, 06, 05, 06, 07, 05, 04, - 06, 05, 04, 07, 04, 05, 06, 07, 06, 05, 05, 06, 04, 05, 07, 06, - 07, 04, 05, 06, 04, 05, 06, 07, 07, 04, 04, 07, 04, 05, 07, 06, - 06, 05, 04, 07, 04, 05, 06, 07, 02, 01, 01, 02, 00, 01, 03, 02, - 03, 00, 01, 02, 00, 01, 02, 03, 07, 04, 04, 07, 04, 05, 07, 06, - 00, 03, 02, 01, 02, 03, 00, 01, 00, 03, 03, 00, 02, 03, 01, 00, - 05, 06, 07, 04, 06, 07, 04, 05, 05, 06, 06, 05, 06, 07, 05, 04, - 02, 01, 00, 03, 00, 01, 02, 03, 06, 05, 05, 06, 04, 05, 07, 06, - 03, 00, 01, 02, 00, 01, 02, 03, 07, 04, 04, 07, 04, 05, 07, 06, - 02, 01, 00, 03, 00, 01, 02, 03, 02, 01, 01, 02, 00, 01, 03, 02, - 03, 00, 01, 02, 00, 01, 02, 03, 03, 00, 00, 03, 00, 01, 03, 02, - 04, 07, 06, 05, 06, 07, 04, 05, 00, 03, 03, 00, 02, 03, 01, 00, - 01, 02, 03, 00, 02, 03, 00, 01, 05, 06, 06, 05, 06, 07, 05, 04, - 04, 07, 06, 05, 06, 07, 04, 05, 04, 07, 07, 04, 06, 07, 05, 04, - 01, 02, 03, 00, 02, 03, 00, 01, 01, 02, 02, 01, 02, 03, 01, 00 -}; + 00, 03, 02, 01, 02, 03, 00, 01, 04, 07, 07, 04, 06, 07, 05, 04, 01, 02, 03, 00, 02, 03, 00, 01, + 05, 06, 06, 05, 06, 07, 05, 04, 06, 05, 04, 07, 04, 05, 06, 07, 06, 05, 05, 06, 04, 05, 07, 06, + 07, 04, 05, 06, 04, 05, 06, 07, 07, 04, 04, 07, 04, 05, 07, 06, 06, 05, 04, 07, 04, 05, 06, 07, + 02, 01, 01, 02, 00, 01, 03, 02, 03, 00, 01, 02, 00, 01, 02, 03, 07, 04, 04, 07, 04, 05, 07, 06, + 00, 03, 02, 01, 02, 03, 00, 01, 00, 03, 03, 00, 02, 03, 01, 00, 05, 06, 07, 04, 06, 07, 04, 05, + 05, 06, 06, 05, 06, 07, 05, 04, 02, 01, 00, 03, 00, 01, 02, 03, 06, 05, 05, 06, 04, 05, 07, 06, + 03, 00, 01, 02, 00, 01, 02, 03, 07, 04, 04, 07, 04, 05, 07, 06, 02, 01, 00, 03, 00, 01, 02, 03, + 02, 01, 01, 02, 00, 01, 03, 02, 03, 00, 01, 02, 00, 01, 02, 03, 03, 00, 00, 03, 00, 01, 03, 02, + 04, 07, 06, 05, 06, 07, 04, 05, 00, 03, 03, 00, 02, 03, 01, 00, 01, 02, 03, 00, 02, 03, 00, 01, + 05, 06, 06, 05, 06, 07, 05, 04, 04, 07, 06, 05, 06, 07, 04, 05, 04, 07, 07, 04, 06, 07, 05, 04, + 01, 02, 03, 00, 02, 03, 00, 01, 01, 02, 02, 01, 02, 03, 01, 00}; /********************** the table above has been generated with this code: ******** #include "util.h" @@ -116,12 +110,12 @@ static void init_opt_select_LUT(void) { } ***********************************************************************************/ -#define loclass_opt__select(x,y,r) (4 & (((r & (r << 2)) >> 5) ^ ((r & ~(r << 2)) >> 4) ^ ( (r | r << 2) >> 3)))\ - |(2 & (((r | r << 2) >> 6) ^ ( (r | r << 2) >> 1) ^ (r >> 5) ^ r ^ ((x^y) << 1)))\ - |(1 & (((r & ~(r << 2)) >> 4) ^ ((r & (r << 2)) >> 3) ^ r ^ x)) +#define loclass_opt__select(x, y, r) \ + (4 & (((r & (r << 2)) >> 5) ^ ((r & ~(r << 2)) >> 4) ^ ((r | r << 2) >> 3))) | \ + (2 & (((r | r << 2) >> 6) ^ ((r | r << 2) >> 1) ^ (r >> 5) ^ r ^ ((x ^ y) << 1))) | \ + (1 & (((r & ~(r << 2)) >> 4) ^ ((r & (r << 2)) >> 3) ^ r ^ x)) - -static void loclass_opt_successor(const uint8_t *k, LoclassState_t *s, uint8_t y) { +static void loclass_opt_successor(const uint8_t* k, LoclassState_t* s, uint8_t y) { uint16_t Tt = s->t & 0xc533; Tt = Tt ^ (Tt >> 1); Tt = Tt ^ (Tt >> 4); @@ -144,12 +138,17 @@ static void loclass_opt_successor(const uint8_t *k, LoclassState_t *s, uint8_t y opt_select |= (loclass_opt_select_LUT[s->r] ^ Tt) & 0x01; uint8_t r = s->r; - s->r = (k[opt_select] ^ s->b) + s->l ; + s->r = (k[opt_select] ^ s->b) + s->l; s->l = s->r + r; } -static void loclass_opt_suc(const uint8_t *k, LoclassState_t *s, const uint8_t *in, uint8_t length, bool add32Zeroes) { - for (int i = 0; i < length; i++) { +static void loclass_opt_suc( + const uint8_t* k, + LoclassState_t* s, + const uint8_t* in, + uint8_t length, + bool add32Zeroes) { + for(int i = 0; i < length; i++) { uint8_t head; head = in[i]; loclass_opt_successor(k, s, head); @@ -176,16 +175,16 @@ static void loclass_opt_suc(const uint8_t *k, LoclassState_t *s, const uint8_t * loclass_opt_successor(k, s, head); } //For tag MAC, an additional 32 zeroes - if (add32Zeroes) { - for (int i = 0; i < 16; i++) { + if(add32Zeroes) { + for(int i = 0; i < 16; i++) { loclass_opt_successor(k, s, 0); loclass_opt_successor(k, s, 0); } } } -static void loclass_opt_output(const uint8_t *k, LoclassState_t *s, uint8_t *buffer) { - for (uint8_t times = 0; times < 4; times++) { +static void loclass_opt_output(const uint8_t* k, LoclassState_t* s, uint8_t* buffer) { + for(uint8_t times = 0; times < 4; times++) { uint8_t bout = 0; bout |= (s->r & 0x4) >> 2; loclass_opt_successor(k, s, 0); @@ -207,10 +206,10 @@ static void loclass_opt_output(const uint8_t *k, LoclassState_t *s, uint8_t *bu } } -static void loclass_opt_MAC(uint8_t *k, uint8_t *input, uint8_t *out) { - LoclassState_t _init = { - ((k[0] ^ 0x4c) + 0xEC) & 0xFF,// l - ((k[0] ^ 0x4c) + 0x21) & 0xFF,// r +static void loclass_opt_MAC(uint8_t* k, uint8_t* input, uint8_t* out) { + LoclassState_t _init = { + ((k[0] ^ 0x4c) + 0xEC) & 0xFF, // l + ((k[0] ^ 0x4c) + 0x21) & 0xFF, // r 0x4c, // b 0xE012 // t }; @@ -219,10 +218,10 @@ static void loclass_opt_MAC(uint8_t *k, uint8_t *input, uint8_t *out) { loclass_opt_output(k, &_init, out); } -static void loclass_opt_MAC_N(uint8_t *k, uint8_t *input, uint8_t in_size, uint8_t *out) { - LoclassState_t _init = { - ((k[0] ^ 0x4c) + 0xEC) & 0xFF,// l - ((k[0] ^ 0x4c) + 0x21) & 0xFF,// r +static void loclass_opt_MAC_N(uint8_t* k, uint8_t* input, uint8_t in_size, uint8_t* out) { + LoclassState_t _init = { + ((k[0] ^ 0x4c) + 0xEC) & 0xFF, // l + ((k[0] ^ 0x4c) + 0x21) & 0xFF, // r 0x4c, // b 0xE012 // t }; @@ -231,28 +230,31 @@ static void loclass_opt_MAC_N(uint8_t *k, uint8_t *input, uint8_t in_size, uint8 loclass_opt_output(k, &_init, out); } -void loclass_opt_doReaderMAC(uint8_t *cc_nr_p, uint8_t *div_key_p, uint8_t mac[4]) { - uint8_t dest [] = {0, 0, 0, 0, 0, 0, 0, 0}; +void loclass_opt_doReaderMAC(uint8_t* cc_nr_p, uint8_t* div_key_p, uint8_t mac[4]) { + uint8_t dest[] = {0, 0, 0, 0, 0, 0, 0, 0}; loclass_opt_MAC(div_key_p, cc_nr_p, dest); memcpy(mac, dest, 4); } -void loclass_opt_doReaderMAC_2(LoclassState_t _init, uint8_t *nr, uint8_t mac[4], const uint8_t *div_key_p) { +void loclass_opt_doReaderMAC_2( + LoclassState_t _init, + uint8_t* nr, + uint8_t mac[4], + const uint8_t* div_key_p) { loclass_opt_suc(div_key_p, &_init, nr, 4, false); loclass_opt_output(div_key_p, &_init, mac); } - -void loclass_doMAC_N(uint8_t *in_p, uint8_t in_size, uint8_t *div_key_p, uint8_t mac[4]) { - uint8_t dest [] = {0, 0, 0, 0, 0, 0, 0, 0}; +void loclass_doMAC_N(uint8_t* in_p, uint8_t in_size, uint8_t* div_key_p, uint8_t mac[4]) { + uint8_t dest[] = {0, 0, 0, 0, 0, 0, 0, 0}; loclass_opt_MAC_N(div_key_p, in_p, in_size, dest); memcpy(mac, dest, 4); } -void loclass_opt_doTagMAC(uint8_t *cc_p, const uint8_t *div_key_p, uint8_t mac[4]) { - LoclassState_t _init = { - ((div_key_p[0] ^ 0x4c) + 0xEC) & 0xFF,// l - ((div_key_p[0] ^ 0x4c) + 0x21) & 0xFF,// r +void loclass_opt_doTagMAC(uint8_t* cc_p, const uint8_t* div_key_p, uint8_t mac[4]) { + LoclassState_t _init = { + ((div_key_p[0] ^ 0x4c) + 0xEC) & 0xFF, // l + ((div_key_p[0] ^ 0x4c) + 0x21) & 0xFF, // r 0x4c, // b 0xE012 // t }; @@ -268,10 +270,10 @@ void loclass_opt_doTagMAC(uint8_t *cc_p, const uint8_t *div_key_p, uint8_t mac[4 * @param div_key_p * @return the cipher state */ -LoclassState_t loclass_opt_doTagMAC_1(uint8_t *cc_p, const uint8_t *div_key_p) { - LoclassState_t _init = { - ((div_key_p[0] ^ 0x4c) + 0xEC) & 0xFF,// l - ((div_key_p[0] ^ 0x4c) + 0x21) & 0xFF,// r +LoclassState_t loclass_opt_doTagMAC_1(uint8_t* cc_p, const uint8_t* div_key_p) { + LoclassState_t _init = { + ((div_key_p[0] ^ 0x4c) + 0xEC) & 0xFF, // l + ((div_key_p[0] ^ 0x4c) + 0x21) & 0xFF, // r 0x4c, // b 0xE012 // t }; @@ -288,21 +290,24 @@ LoclassState_t loclass_opt_doTagMAC_1(uint8_t *cc_p, const uint8_t *div_key_p) { * @param mac - where to store the MAC * @param div_key_p - the key to use */ -void loclass_opt_doTagMAC_2(LoclassState_t _init, uint8_t *nr, uint8_t mac[4], const uint8_t *div_key_p) { +void loclass_opt_doTagMAC_2( + LoclassState_t _init, + uint8_t* nr, + uint8_t mac[4], + const uint8_t* div_key_p) { loclass_opt_suc(div_key_p, &_init, nr, 4, true); loclass_opt_output(div_key_p, &_init, mac); } -void loclass_iclass_calc_div_key(uint8_t *csn, uint8_t *key, uint8_t *div_key, bool elite) { - if (elite) { +void loclass_iclass_calc_div_key(uint8_t* csn, uint8_t* key, uint8_t* div_key, bool elite) { + if(elite) { uint8_t keytable[128] = {0}; uint8_t key_index[8] = {0}; - uint8_t key_sel[8] = { 0 }; - uint8_t key_sel_p[8] = { 0 }; + uint8_t key_sel[8] = {0}; + uint8_t key_sel_p[8] = {0}; loclass_hash2(key, keytable); loclass_hash1(csn, key_index); - for (uint8_t i = 0; i < 8 ; i++) - key_sel[i] = keytable[key_index[i]]; + for(uint8_t i = 0; i < 8; i++) key_sel[i] = keytable[key_index[i]]; //Permute from iclass format to standard format loclass_permutekey_rev(key_sel, key_sel_p); diff --git a/lib/loclass/optimized_cipher.h b/applications/plugins/picopass/loclass/optimized_cipher.h similarity index 84% rename from lib/loclass/optimized_cipher.h rename to applications/plugins/picopass/loclass/optimized_cipher.h index e7b8cbd67ff..2158f0acf75 100644 --- a/lib/loclass/optimized_cipher.h +++ b/applications/plugins/picopass/loclass/optimized_cipher.h @@ -56,14 +56,18 @@ typedef struct { /** The reader MAC is MAC(key, CC * NR ) **/ -void loclass_opt_doReaderMAC(uint8_t *cc_nr_p, uint8_t *div_key_p, uint8_t mac[4]); +void loclass_opt_doReaderMAC(uint8_t* cc_nr_p, uint8_t* div_key_p, uint8_t mac[4]); -void loclass_opt_doReaderMAC_2(LoclassState_t _init, uint8_t *nr, uint8_t mac[4], const uint8_t *div_key_p); +void loclass_opt_doReaderMAC_2( + LoclassState_t _init, + uint8_t* nr, + uint8_t mac[4], + const uint8_t* div_key_p); /** * The tag MAC is MAC(key, CC * NR * 32x0)) */ -void loclass_opt_doTagMAC(uint8_t *cc_p, const uint8_t *div_key_p, uint8_t mac[4]); +void loclass_opt_doTagMAC(uint8_t* cc_p, const uint8_t* div_key_p, uint8_t mac[4]); /** * The tag MAC can be divided (both can, but no point in dividing the reader mac) into @@ -73,7 +77,7 @@ void loclass_opt_doTagMAC(uint8_t *cc_p, const uint8_t *div_key_p, uint8_t mac[4 * @param div_key_p * @return the cipher state */ -LoclassState_t loclass_opt_doTagMAC_1(uint8_t *cc_p, const uint8_t *div_key_p); +LoclassState_t loclass_opt_doTagMAC_1(uint8_t* cc_p, const uint8_t* div_key_p); /** * The second part of the tag MAC calculation, since the CC is already calculated into the state, * this function is fed only the NR, and internally feeds the remaining 32 0-bits to generate the tag @@ -83,8 +87,12 @@ LoclassState_t loclass_opt_doTagMAC_1(uint8_t *cc_p, const uint8_t *div_key_p); * @param mac - where to store the MAC * @param div_key_p - the key to use */ -void loclass_opt_doTagMAC_2(LoclassState_t _init, uint8_t *nr, uint8_t mac[4], const uint8_t *div_key_p); +void loclass_opt_doTagMAC_2( + LoclassState_t _init, + uint8_t* nr, + uint8_t mac[4], + const uint8_t* div_key_p); -void loclass_doMAC_N(uint8_t *in_p, uint8_t in_size, uint8_t *div_key_p, uint8_t mac[4]); -void loclass_iclass_calc_div_key(uint8_t *csn, uint8_t *key, uint8_t *div_key, bool elite); +void loclass_doMAC_N(uint8_t* in_p, uint8_t in_size, uint8_t* div_key_p, uint8_t mac[4]); +void loclass_iclass_calc_div_key(uint8_t* csn, uint8_t* key, uint8_t* div_key, bool elite); #endif // OPTIMIZED_CIPHER_H diff --git a/lib/loclass/optimized_cipherutils.c b/applications/plugins/picopass/loclass/optimized_cipherutils.c similarity index 84% rename from lib/loclass/optimized_cipherutils.c rename to applications/plugins/picopass/loclass/optimized_cipherutils.c index c5bcbaccdad..e6a87c4a760 100644 --- a/lib/loclass/optimized_cipherutils.c +++ b/applications/plugins/picopass/loclass/optimized_cipherutils.c @@ -40,7 +40,7 @@ * @param stream * @return */ -bool loclass_headBit(LoclassBitstreamIn_t *stream) { +bool loclass_headBit(LoclassBitstreamIn_t* stream) { int bytepos = stream->position >> 3; // divide by 8 int bitpos = (stream->position++) & 7; // mask out 00000111 return (*(stream->buffer + bytepos) >> (7 - bitpos)) & 1; @@ -50,7 +50,7 @@ bool loclass_headBit(LoclassBitstreamIn_t *stream) { * @param stream * @return */ -bool loclass_tailBit(LoclassBitstreamIn_t *stream) { +bool loclass_tailBit(LoclassBitstreamIn_t* stream) { int bitpos = stream->numbits - 1 - (stream->position++); int bytepos = bitpos >> 3; @@ -62,7 +62,7 @@ bool loclass_tailBit(LoclassBitstreamIn_t *stream) { * @param stream * @param bit */ -void loclass_pushBit(LoclassBitstreamOut_t *stream, bool bit) { +void loclass_pushBit(LoclassBitstreamOut_t* stream, bool bit) { int bytepos = stream->position >> 3; // divide by 8 int bitpos = stream->position & 7; *(stream->buffer + bytepos) |= (bit) << (7 - bitpos); @@ -76,7 +76,7 @@ void loclass_pushBit(LoclassBitstreamOut_t *stream, bool bit) { * @param stream * @param bits */ -void loclass_push6bits(LoclassBitstreamOut_t *stream, uint8_t bits) { +void loclass_push6bits(LoclassBitstreamOut_t* stream, uint8_t bits) { loclass_pushBit(stream, bits & 0x20); loclass_pushBit(stream, bits & 0x10); loclass_pushBit(stream, bits & 0x08); @@ -90,7 +90,7 @@ void loclass_push6bits(LoclassBitstreamOut_t *stream, uint8_t bits) { * @param stream * @return number of bits left in stream */ -int loclass_bitsLeft(LoclassBitstreamIn_t *stream) { +int loclass_bitsLeft(LoclassBitstreamIn_t* stream) { return stream->numbits - stream->position; } /** @@ -98,16 +98,16 @@ int loclass_bitsLeft(LoclassBitstreamIn_t *stream) { * @param stream * @return Number of bits stored in stream */ -void loclass_x_num_to_bytes(uint64_t n, size_t len, uint8_t *dest) { - while (len--) { - dest[len] = (uint8_t) n; +void loclass_x_num_to_bytes(uint64_t n, size_t len, uint8_t* dest) { + while(len--) { + dest[len] = (uint8_t)n; n >>= 8; } } -uint64_t loclass_x_bytes_to_num(uint8_t *src, size_t len) { +uint64_t loclass_x_bytes_to_num(uint8_t* src, size_t len) { uint64_t num = 0; - while (len--) { + while(len--) { num = (num << 8) | (*src); src++; } @@ -121,17 +121,16 @@ uint8_t loclass_reversebytes(uint8_t b) { return b; } -void loclass_reverse_arraybytes(uint8_t *arr, size_t len) { +void loclass_reverse_arraybytes(uint8_t* arr, size_t len) { uint8_t i; - for (i = 0; i < len ; i++) { + for(i = 0; i < len; i++) { arr[i] = loclass_reversebytes(arr[i]); } } -void loclass_reverse_arraycopy(uint8_t *arr, uint8_t *dest, size_t len) { +void loclass_reverse_arraycopy(uint8_t* arr, uint8_t* dest, size_t len) { uint8_t i; - for (i = 0; i < len ; i++) { + for(i = 0; i < len; i++) { dest[i] = loclass_reversebytes(arr[i]); } } - diff --git a/lib/loclass/optimized_cipherutils.h b/applications/plugins/picopass/loclass/optimized_cipherutils.h similarity index 78% rename from lib/loclass/optimized_cipherutils.h rename to applications/plugins/picopass/loclass/optimized_cipherutils.h index cb9d2724ae0..05b6820792d 100644 --- a/lib/loclass/optimized_cipherutils.h +++ b/applications/plugins/picopass/loclass/optimized_cipherutils.h @@ -39,26 +39,26 @@ #include typedef struct { - uint8_t *buffer; + uint8_t* buffer; uint8_t numbits; uint8_t position; } LoclassBitstreamIn_t; typedef struct { - uint8_t *buffer; + uint8_t* buffer; uint8_t numbits; uint8_t position; } LoclassBitstreamOut_t; -bool loclass_headBit(LoclassBitstreamIn_t *stream); -bool loclass_tailBit(LoclassBitstreamIn_t *stream); -void loclass_pushBit(LoclassBitstreamOut_t *stream, bool bit); -int loclass_bitsLeft(LoclassBitstreamIn_t *stream); +bool loclass_headBit(LoclassBitstreamIn_t* stream); +bool loclass_tailBit(LoclassBitstreamIn_t* stream); +void loclass_pushBit(LoclassBitstreamOut_t* stream, bool bit); +int loclass_bitsLeft(LoclassBitstreamIn_t* stream); -void loclass_push6bits(LoclassBitstreamOut_t *stream, uint8_t bits); -void loclass_x_num_to_bytes(uint64_t n, size_t len, uint8_t *dest); -uint64_t loclass_x_bytes_to_num(uint8_t *src, size_t len); +void loclass_push6bits(LoclassBitstreamOut_t* stream, uint8_t bits); +void loclass_x_num_to_bytes(uint64_t n, size_t len, uint8_t* dest); +uint64_t loclass_x_bytes_to_num(uint8_t* src, size_t len); uint8_t loclass_reversebytes(uint8_t b); -void loclass_reverse_arraybytes(uint8_t *arr, size_t len); -void loclass_reverse_arraycopy(uint8_t *arr, uint8_t *dest, size_t len); +void loclass_reverse_arraybytes(uint8_t* arr, size_t len); +void loclass_reverse_arraycopy(uint8_t* arr, uint8_t* dest, size_t len); #endif // CIPHERUTILS_H diff --git a/lib/loclass/optimized_elite.c b/applications/plugins/picopass/loclass/optimized_elite.c similarity index 92% rename from lib/loclass/optimized_elite.c rename to applications/plugins/picopass/loclass/optimized_elite.c index fc1e5d7484e..c175f3986b5 100644 --- a/lib/loclass/optimized_elite.c +++ b/applications/plugins/picopass/loclass/optimized_elite.c @@ -62,7 +62,7 @@ */ void loclass_permutekey(const uint8_t key[8], uint8_t dest[8]) { int i; - for (i = 0 ; i < 8 ; i++) { + for(i = 0; i < 8; i++) { dest[i] = (((key[7] & (0x80 >> i)) >> (7 - i)) << 7) | (((key[6] & (0x80 >> i)) >> (7 - i)) << 6) | (((key[5] & (0x80 >> i)) >> (7 - i)) << 5) | @@ -81,7 +81,7 @@ void loclass_permutekey(const uint8_t key[8], uint8_t dest[8]) { */ void loclass_permutekey_rev(const uint8_t key[8], uint8_t dest[8]) { int i; - for (i = 0 ; i < 8 ; i++) { + for(i = 0; i < 8; i++) { dest[7 - i] = (((key[0] & (0x80 >> i)) >> (7 - i)) << 7) | (((key[1] & (0x80 >> i)) >> (7 - i)) << 6) | (((key[2] & (0x80 >> i)) >> (7 - i)) << 5) | @@ -153,12 +153,11 @@ Definition 14. Define the rotate key function loclass_rk : (F 82 ) 8 × N → (F loclass_rk(x [0] . . . x [7] , 0) = x [0] . . . x [7] loclass_rk(x [0] . . . x [7] , n + 1) = loclass_rk(loclass_rl(x [0] ) . . . loclass_rl(x [7] ), n) **/ -static void loclass_rk(uint8_t *key, uint8_t n, uint8_t *outp_key) { +static void loclass_rk(uint8_t* key, uint8_t n, uint8_t* outp_key) { memcpy(outp_key, key, 8); uint8_t j; - while (n-- > 0) { - for (j = 0; j < 8 ; j++) - outp_key[j] = loclass_rl(outp_key[j]); + while(n-- > 0) { + for(j = 0; j < 8; j++) outp_key[j] = loclass_rl(outp_key[j]); } return; } @@ -166,14 +165,14 @@ static void loclass_rk(uint8_t *key, uint8_t n, uint8_t *outp_key) { static mbedtls_des_context loclass_ctx_enc; static mbedtls_des_context loclass_ctx_dec; -static void loclass_desdecrypt_iclass(uint8_t *iclass_key, uint8_t *input, uint8_t *output) { +static void loclass_desdecrypt_iclass(uint8_t* iclass_key, uint8_t* input, uint8_t* output) { uint8_t key_std_format[8] = {0}; loclass_permutekey_rev(iclass_key, key_std_format); mbedtls_des_setkey_dec(&loclass_ctx_dec, key_std_format); mbedtls_des_crypt_ecb(&loclass_ctx_dec, input, output); } -static void loclass_desencrypt_iclass(uint8_t *iclass_key, uint8_t *input, uint8_t *output) { +static void loclass_desencrypt_iclass(uint8_t* iclass_key, uint8_t* input, uint8_t* output) { uint8_t key_std_format[8] = {0}; loclass_permutekey_rev(iclass_key, key_std_format); mbedtls_des_setkey_enc(&loclass_ctx_enc, key_std_format); @@ -186,7 +185,7 @@ static void loclass_desencrypt_iclass(uint8_t *iclass_key, uint8_t *input, uint8 * @param loclass_hash1 loclass_hash1 * @param key_sel output key_sel=h[loclass_hash1[i]] */ -void hash2(uint8_t *key64, uint8_t *outp_keytable) { +void hash2(uint8_t* key64, uint8_t* outp_keytable) { /** *Expected: * High Security Key Table @@ -207,8 +206,7 @@ void hash2(uint8_t *key64, uint8_t *outp_keytable) { //calculate complement of key int i; - for (i = 0; i < 8; i++) - key64_negated[i] = ~key64[i]; + for(i = 0; i < 8; i++) key64_negated[i] = ~key64[i]; // Once again, key is on iclass-format loclass_desencrypt_iclass(key64, key64_negated, z[0]); @@ -219,14 +217,14 @@ void hash2(uint8_t *key64, uint8_t *outp_keytable) { // Once again, key is on iclass-format loclass_desdecrypt_iclass(z[0], key64_negated, y[0]); - for (i = 1; i < 8; i++) { + for(i = 1; i < 8; i++) { loclass_rk(key64, i, temp_output); loclass_desdecrypt_iclass(temp_output, z[i - 1], z[i]); loclass_desencrypt_iclass(temp_output, y[i - 1], y[i]); } - if (outp_keytable != NULL) { - for (i = 0 ; i < 8 ; i++) { + if(outp_keytable != NULL) { + for(i = 0; i < 8; i++) { memcpy(outp_keytable + i * 16, y[i], 8); memcpy(outp_keytable + 8 + i * 16, z[i], 8); } diff --git a/lib/loclass/optimized_elite.h b/applications/plugins/picopass/loclass/optimized_elite.h similarity index 95% rename from lib/loclass/optimized_elite.h rename to applications/plugins/picopass/loclass/optimized_elite.h index 9bc30e575f8..5343ebb0740 100644 --- a/lib/loclass/optimized_elite.h +++ b/applications/plugins/picopass/loclass/optimized_elite.h @@ -52,7 +52,7 @@ void loclass_permutekey_rev(const uint8_t key[8], uint8_t dest[8]); * @param csn the CSN used * @param k output */ -void loclass_hash1(const uint8_t *csn, uint8_t *k); -void loclass_hash2(uint8_t *key64, uint8_t *outp_keytable); +void loclass_hash1(const uint8_t* csn, uint8_t* k); +void loclass_hash2(uint8_t* key64, uint8_t* outp_keytable); #endif diff --git a/lib/loclass/optimized_ikeys.c b/applications/plugins/picopass/loclass/optimized_ikeys.c similarity index 90% rename from lib/loclass/optimized_ikeys.c rename to applications/plugins/picopass/loclass/optimized_ikeys.c index 9531c16c281..1e6f12c5678 100644 --- a/lib/loclass/optimized_ikeys.c +++ b/applications/plugins/picopass/loclass/optimized_ikeys.c @@ -61,13 +61,10 @@ From "Dismantling iclass": #include #include "optimized_cipherutils.h" -static const uint8_t loclass_pi[35] = { - 0x0F, 0x17, 0x1B, 0x1D, 0x1E, 0x27, 0x2B, 0x2D, - 0x2E, 0x33, 0x35, 0x39, 0x36, 0x3A, 0x3C, 0x47, - 0x4B, 0x4D, 0x4E, 0x53, 0x55, 0x56, 0x59, 0x5A, - 0x5C, 0x63, 0x65, 0x66, 0x69, 0x6A, 0x6C, 0x71, - 0x72, 0x74, 0x78 -}; +static const uint8_t loclass_pi[35] = {0x0F, 0x17, 0x1B, 0x1D, 0x1E, 0x27, 0x2B, 0x2D, 0x2E, + 0x33, 0x35, 0x39, 0x36, 0x3A, 0x3C, 0x47, 0x4B, 0x4D, + 0x4E, 0x53, 0x55, 0x56, 0x59, 0x5A, 0x5C, 0x63, 0x65, + 0x66, 0x69, 0x6A, 0x6C, 0x71, 0x72, 0x74, 0x78}; /** * @brief The key diversification algorithm uses 6-bit bytes. @@ -90,7 +87,7 @@ static uint8_t loclass_getSixBitByte(uint64_t c, int n) { * @param z the value to place there * @param n bitnumber. */ -static void loclass_pushbackSixBitByte(uint64_t *c, uint8_t z, int n) { +static void loclass_pushbackSixBitByte(uint64_t* c, uint8_t z, int n) { //0x XXXX YYYY ZZZZ ZZZZ ZZZZ // ^z0 ^z7 //z0: 1111 1100 0000 0000 @@ -106,7 +103,6 @@ static void loclass_pushbackSixBitByte(uint64_t *c, uint8_t z, int n) { eraser = ~eraser; (*c) &= eraser; (*c) |= masked; - } /** * @brief Swaps the z-values. @@ -133,21 +129,21 @@ static uint64_t loclass_swapZvalues(uint64_t c) { * @return 4 six-bit bytes chunked into a uint64_t,as 00..00a0a1a2a3 */ static uint64_t loclass_ck(int i, int j, uint64_t z) { - if (i == 1 && j == -1) { + if(i == 1 && j == -1) { // loclass_ck(1, −1, z [0] . . . z [3] ) = z [0] . . . z [3] return z; - } else if (j == -1) { + } else if(j == -1) { // loclass_ck(i, −1, z [0] . . . z [3] ) = loclass_ck(i − 1, i − 2, z [0] . . . z [3] ) return loclass_ck(i - 1, i - 2, z); } - if (loclass_getSixBitByte(z, i) == loclass_getSixBitByte(z, j)) { + if(loclass_getSixBitByte(z, i) == loclass_getSixBitByte(z, j)) { //loclass_ck(i, j − 1, z [0] . . . z [i] ← j . . . z [3] ) uint64_t newz = 0; int c; - for (c = 0; c < 4; c++) { + for(c = 0; c < 4; c++) { uint8_t val = loclass_getSixBitByte(z, c); - if (c == i) + if(c == i) loclass_pushbackSixBitByte(&newz, j, c); else loclass_pushbackSixBitByte(&newz, val, c); @@ -191,12 +187,16 @@ static uint64_t loclass_check(uint64_t z) { return ck1 | ck2 >> 24; } -static void loclass_permute(LoclassBitstreamIn_t *p_in, uint64_t z, int l, int r, LoclassBitstreamOut_t *out) { - if (loclass_bitsLeft(p_in) == 0) - return; +static void loclass_permute( + LoclassBitstreamIn_t* p_in, + uint64_t z, + int l, + int r, + LoclassBitstreamOut_t* out) { + if(loclass_bitsLeft(p_in) == 0) return; bool pn = loclass_tailBit(p_in); - if (pn) { // pn = 1 + if(pn) { // pn = 1 uint8_t zl = loclass_getSixBitByte(z, l); loclass_push6bits(out, zl + 1); @@ -231,7 +231,7 @@ void loclass_hash0(uint64_t c, uint8_t k[8]) { uint8_t y = (c & 0x00FF000000000000) >> 48; uint64_t zP = 0; - for (int n = 0; n < 4 ; n++) { + for(int n = 0; n < 4; n++) { uint8_t zn = loclass_getSixBitByte(c, n); uint8_t zn4 = loclass_getSixBitByte(c, n + 4); uint8_t _zn = (zn % (63 - n)) + n; @@ -243,10 +243,10 @@ void loclass_hash0(uint64_t c, uint8_t k[8]) { uint64_t zCaret = loclass_check(zP); uint8_t p = loclass_pi[x % 35]; - if (x & 1) //Check if x7 is 1 + if(x & 1) //Check if x7 is 1 p = ~p; - LoclassBitstreamIn_t p_in = { &p, 8, 0 }; + LoclassBitstreamIn_t p_in = {&p, 8, 0}; uint8_t outbuffer[] = {0, 0, 0, 0, 0, 0, 0, 0}; LoclassBitstreamOut_t out = {outbuffer, 0, 0}; loclass_permute(&p_in, zCaret, 0, 4, &out); //returns 48 bits? or 6 8-bytes @@ -259,7 +259,7 @@ void loclass_hash0(uint64_t c, uint8_t k[8]) { zTilde >>= 16; - for (int i = 0; i < 8; i++) { + for(int i = 0; i < 8; i++) { // the key on index i is first a bit from y // then six bits from z, // then a bit from p @@ -270,7 +270,7 @@ void loclass_hash0(uint64_t c, uint8_t k[8]) { //k[i] |= (y << i) & 0x80 ; // First, place y(7-i) leftmost in k - k[i] |= (y << (7 - i)) & 0x80 ; + k[i] |= (y << (7 - i)) & 0x80; uint8_t zTilde_i = loclass_getSixBitByte(zTilde, i); // zTildeI is now on the form 00XXXXXX @@ -285,7 +285,7 @@ void loclass_hash0(uint64_t c, uint8_t k[8]) { //Shift bit i into rightmost location (mask only after complement) uint8_t p_i = p >> i & 0x1; - if (k[i]) { // yi = 1 + if(k[i]) { // yi = 1 k[i] |= ~zTilde_i & 0x7E; k[i] |= p_i & 1; k[i] += 1; @@ -302,7 +302,7 @@ void loclass_hash0(uint64_t c, uint8_t k[8]) { * @param key * @param div_key */ -void loclass_diversifyKey(uint8_t *csn, const uint8_t *key, uint8_t *div_key) { +void loclass_diversifyKey(uint8_t* csn, const uint8_t* key, uint8_t* div_key) { mbedtls_des_context loclass_ctx_enc; // Prepare the DES key @@ -318,4 +318,3 @@ void loclass_diversifyKey(uint8_t *csn, const uint8_t *key, uint8_t *div_key) { loclass_hash0(c_csn, div_key); } - diff --git a/lib/loclass/optimized_ikeys.h b/applications/plugins/picopass/loclass/optimized_ikeys.h similarity index 96% rename from lib/loclass/optimized_ikeys.h rename to applications/plugins/picopass/loclass/optimized_ikeys.h index e960b8be279..f2711d31ef5 100644 --- a/lib/loclass/optimized_ikeys.h +++ b/applications/plugins/picopass/loclass/optimized_ikeys.h @@ -56,7 +56,7 @@ void loclass_hash0(uint64_t c, uint8_t k[8]); * @param div_key */ -void loclass_diversifyKey(uint8_t *csn, const uint8_t *key, uint8_t *div_key); +void loclass_diversifyKey(uint8_t* csn, const uint8_t* key, uint8_t* div_key); /** * @brief Permutes a key from standard NIST format to Iclass specific format * @param key diff --git a/applications/picopass/picopass.c b/applications/plugins/picopass/picopass.c similarity index 100% rename from applications/picopass/picopass.c rename to applications/plugins/picopass/picopass.c diff --git a/applications/picopass/picopass.h b/applications/plugins/picopass/picopass.h similarity index 100% rename from applications/picopass/picopass.h rename to applications/plugins/picopass/picopass.h diff --git a/applications/picopass/picopass_device.c b/applications/plugins/picopass/picopass_device.c similarity index 98% rename from applications/picopass/picopass_device.c rename to applications/plugins/picopass/picopass_device.c index 4cd6faaab7c..04d35d19bf9 100644 --- a/applications/picopass/picopass_device.c +++ b/applications/plugins/picopass/picopass_device.c @@ -226,14 +226,13 @@ bool picopass_file_select(PicopassDevice* dev) { // Input events and views are managed by file_browser string_t picopass_app_folder; string_init_set_str(picopass_app_folder, PICOPASS_APP_FOLDER); + + DialogsFileBrowserOptions browser_options; + dialog_file_browser_set_basic_options(&browser_options, PICOPASS_APP_EXTENSION, &I_Nfc_10px); + bool res = dialog_file_browser_show( - dev->dialogs, - dev->load_path, - picopass_app_folder, - PICOPASS_APP_EXTENSION, - true, - &I_Nfc_10px, - true); + dev->dialogs, dev->load_path, picopass_app_folder, &browser_options); + string_clear(picopass_app_folder); if(res) { string_t filename; diff --git a/applications/picopass/picopass_device.h b/applications/plugins/picopass/picopass_device.h similarity index 96% rename from applications/picopass/picopass_device.h rename to applications/plugins/picopass/picopass_device.h index 0415b8794c3..931b86306b2 100644 --- a/applications/picopass/picopass_device.h +++ b/applications/plugins/picopass/picopass_device.h @@ -4,12 +4,11 @@ #include #include #include - -#include - #include -#include -#include + +#include "rfal_picopass.h" +#include "loclass/optimized_ikeys.h" +#include "loclass/optimized_cipher.h" #define PICOPASS_DEV_NAME_MAX_LEN 22 #define PICOPASS_READER_DATA_MAX_SIZE 64 diff --git a/applications/picopass/picopass_i.h b/applications/plugins/picopass/picopass_i.h similarity index 96% rename from applications/picopass/picopass_i.h rename to applications/plugins/picopass/picopass_i.h index d295f53ac12..dec5a865f8d 100644 --- a/applications/picopass/picopass_i.h +++ b/applications/plugins/picopass/picopass_i.h @@ -4,7 +4,7 @@ #include "picopass_worker.h" #include "picopass_device.h" -#include +#include "rfal_picopass.h" #include #include @@ -20,7 +20,7 @@ #include -#include +#include "scenes/picopass_scene.h" #include #include diff --git a/applications/picopass/picopass_worker.c b/applications/plugins/picopass/picopass_worker.c similarity index 98% rename from applications/picopass/picopass_worker.c rename to applications/plugins/picopass/picopass_worker.c index 3ca56d0065f..e7331524822 100644 --- a/applications/picopass/picopass_worker.c +++ b/applications/plugins/picopass/picopass_worker.c @@ -6,14 +6,14 @@ const uint8_t picopass_iclass_key[] = {0xaf, 0xa7, 0x85, 0xa7, 0xda, 0xb3, 0x33, const uint8_t picopass_factory_key[] = {0x76, 0x65, 0x54, 0x43, 0x32, 0x21, 0x10, 0x00}; static void picopass_worker_enable_field() { - st25r3916TxRxOn(); - rfalLowPowerModeStop(); - rfalWorker(); + furi_hal_nfc_ll_txrx_on(); + furi_hal_nfc_exit_sleep(); + furi_hal_nfc_ll_poll(); } static ReturnCode picopass_worker_disable_field(ReturnCode rc) { - st25r3916TxRxOff(); - rfalLowPowerModeStart(); + furi_hal_nfc_ll_txrx_off(); + furi_hal_nfc_start_sleep(); return rc; } diff --git a/applications/picopass/picopass_worker.h b/applications/plugins/picopass/picopass_worker.h similarity index 100% rename from applications/picopass/picopass_worker.h rename to applications/plugins/picopass/picopass_worker.h diff --git a/applications/picopass/picopass_worker_i.h b/applications/plugins/picopass/picopass_worker_i.h similarity index 100% rename from applications/picopass/picopass_worker_i.h rename to applications/plugins/picopass/picopass_worker_i.h diff --git a/lib/ST25RFAL002/source/rfal_picopass.c b/applications/plugins/picopass/rfal_picopass.c similarity index 50% rename from lib/ST25RFAL002/source/rfal_picopass.c rename to applications/plugins/picopass/rfal_picopass.c index f33fcdf8c11..50cd4e95de7 100644 --- a/lib/ST25RFAL002/source/rfal_picopass.c +++ b/applications/plugins/picopass/rfal_picopass.c @@ -1,7 +1,10 @@ - #include "rfal_picopass.h" #include "utils.h" +#define RFAL_PICOPASS_TXRX_FLAGS \ + (FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_TX_MANUAL | FURI_HAL_NFC_LL_TXRX_FLAGS_AGC_ON | \ + FURI_HAL_NFC_LL_TXRX_FLAGS_PAR_RX_REMV | FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_RX_KEEP) + #define TAG "RFAL_PICOPASS" typedef struct { @@ -15,40 +18,68 @@ typedef struct { uint8_t mac[4]; } rfalPicoPassCheckReq; -ReturnCode rfalPicoPassPollerInitialize(void) { - ReturnCode ret; +static uint16_t rfalPicoPassUpdateCcitt(uint16_t crcSeed, uint8_t dataByte) { + uint16_t crc = crcSeed; + uint8_t dat = dataByte; + + dat ^= (uint8_t)(crc & 0xFFU); + dat ^= (dat << 4); + + crc = (crc >> 8) ^ (((uint16_t)dat) << 8) ^ (((uint16_t)dat) << 3) ^ (((uint16_t)dat) >> 4); + + return crc; +} + +static uint16_t + rfalPicoPassCalculateCcitt(uint16_t preloadValue, const uint8_t* buf, uint16_t length) { + uint16_t crc = preloadValue; + uint16_t index; + + for(index = 0; index < length; index++) { + crc = rfalPicoPassUpdateCcitt(crc, buf[index]); + } + + return crc; +} + +FuriHalNfcReturn rfalPicoPassPollerInitialize(void) { + FuriHalNfcReturn ret; - EXIT_ON_ERR(ret, rfalSetMode(RFAL_MODE_POLL_PICOPASS, RFAL_BR_26p48, RFAL_BR_26p48)); - rfalSetErrorHandling(RFAL_ERRORHANDLING_NFC); + ret = furi_hal_nfc_ll_set_mode( + FuriHalNfcModePollPicopass, FuriHalNfcBitrate26p48, FuriHalNfcBitrate26p48); + if(ret != FuriHalNfcReturnOk) { + return ret; + }; - rfalSetGT(RFAL_GT_PICOPASS); - rfalSetFDTListen(RFAL_FDT_LISTEN_PICOPASS_POLLER); - rfalSetFDTPoll(RFAL_FDT_POLL_PICOPASS_POLLER); + furi_hal_nfc_ll_set_error_handling(FuriHalNfcErrorHandlingNfc); + furi_hal_nfc_ll_set_guard_time(FURI_HAL_NFC_LL_GT_PICOPASS); + furi_hal_nfc_ll_set_fdt_listen(FURI_HAL_NFC_LL_FDT_LISTEN_PICOPASS_POLLER); + furi_hal_nfc_ll_set_fdt_poll(FURI_HAL_NFC_LL_FDT_POLL_PICOPASS_POLLER); - return ERR_NONE; + return FuriHalNfcReturnOk; } -ReturnCode rfalPicoPassPollerCheckPresence(void) { - ReturnCode ret; +FuriHalNfcReturn rfalPicoPassPollerCheckPresence(void) { + FuriHalNfcReturn ret; uint8_t txBuf[1] = {RFAL_PICOPASS_CMD_ACTALL}; uint8_t rxBuf[32] = {0}; uint16_t recvLen = 0; uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS; - uint32_t fwt = rfalConvMsTo1fc(20); + uint32_t fwt = furi_hal_nfc_ll_ms2fc(20); - ret = rfalTransceiveBlockingTxRx(txBuf, 1, rxBuf, 32, &recvLen, flags, fwt); + ret = furi_hal_nfc_ll_txrx(txBuf, 1, rxBuf, 32, &recvLen, flags, fwt); return ret; } -ReturnCode rfalPicoPassPollerIdentify(rfalPicoPassIdentifyRes* idRes) { - ReturnCode ret; +FuriHalNfcReturn rfalPicoPassPollerIdentify(rfalPicoPassIdentifyRes* idRes) { + FuriHalNfcReturn ret; uint8_t txBuf[1] = {RFAL_PICOPASS_CMD_IDENTIFY}; uint16_t recvLen = 0; uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS; - uint32_t fwt = rfalConvMsTo1fc(20); + uint32_t fwt = furi_hal_nfc_ll_ms2fc(20); - ret = rfalTransceiveBlockingTxRx( + ret = furi_hal_nfc_ll_txrx( txBuf, sizeof(txBuf), (uint8_t*)idRes, @@ -61,17 +92,17 @@ ReturnCode rfalPicoPassPollerIdentify(rfalPicoPassIdentifyRes* idRes) { return ret; } -ReturnCode rfalPicoPassPollerSelect(uint8_t* csn, rfalPicoPassSelectRes* selRes) { - ReturnCode ret; +FuriHalNfcReturn rfalPicoPassPollerSelect(uint8_t* csn, rfalPicoPassSelectRes* selRes) { + FuriHalNfcReturn ret; rfalPicoPassSelectReq selReq; selReq.CMD = RFAL_PICOPASS_CMD_SELECT; ST_MEMCPY(selReq.CSN, csn, RFAL_PICOPASS_UID_LEN); uint16_t recvLen = 0; uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS; - uint32_t fwt = rfalConvMsTo1fc(20); + uint32_t fwt = furi_hal_nfc_ll_ms2fc(20); - ret = rfalTransceiveBlockingTxRx( + ret = furi_hal_nfc_ll_txrx( (uint8_t*)&selReq, sizeof(rfalPicoPassSelectReq), (uint8_t*)selRes, @@ -80,21 +111,21 @@ ReturnCode rfalPicoPassPollerSelect(uint8_t* csn, rfalPicoPassSelectRes* selRes) flags, fwt); // printf("select rx: %d %s\n", recvLen, hex2Str(selRes->CSN, RFAL_PICOPASS_UID_LEN)); - if(ret == ERR_TIMEOUT) { - return ERR_NONE; + if(ret == FuriHalNfcReturnTimeout) { + return FuriHalNfcReturnOk; } return ret; } -ReturnCode rfalPicoPassPollerReadCheck(rfalPicoPassReadCheckRes* rcRes) { - ReturnCode ret; +FuriHalNfcReturn rfalPicoPassPollerReadCheck(rfalPicoPassReadCheckRes* rcRes) { + FuriHalNfcReturn ret; uint8_t txBuf[2] = {RFAL_PICOPASS_CMD_READCHECK, 0x02}; uint16_t recvLen = 0; uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS; - uint32_t fwt = rfalConvMsTo1fc(20); + uint32_t fwt = furi_hal_nfc_ll_ms2fc(20); - ret = rfalTransceiveBlockingTxRx( + ret = furi_hal_nfc_ll_txrx( txBuf, sizeof(txBuf), (uint8_t*)rcRes, @@ -104,25 +135,25 @@ ReturnCode rfalPicoPassPollerReadCheck(rfalPicoPassReadCheckRes* rcRes) { fwt); // printf("readcheck rx: %d %s\n", recvLen, hex2Str(rcRes->CCNR, 8)); - if(ret == ERR_CRC) { - return ERR_NONE; + if(ret == FuriHalNfcReturnCrc) { + return FuriHalNfcReturnOk; } return ret; } -ReturnCode rfalPicoPassPollerCheck(uint8_t* mac, rfalPicoPassCheckRes* chkRes) { - ReturnCode ret; +FuriHalNfcReturn rfalPicoPassPollerCheck(uint8_t* mac, rfalPicoPassCheckRes* chkRes) { + FuriHalNfcReturn ret; rfalPicoPassCheckReq chkReq; chkReq.CMD = RFAL_PICOPASS_CMD_CHECK; ST_MEMCPY(chkReq.mac, mac, 4); ST_MEMSET(chkReq.null, 0, 4); uint16_t recvLen = 0; uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS; - uint32_t fwt = rfalConvMsTo1fc(20); + uint32_t fwt = furi_hal_nfc_ll_ms2fc(20); // printf("check tx: %s\n", hex2Str((uint8_t *)&chkReq, sizeof(rfalPicoPassCheckReq))); - ret = rfalTransceiveBlockingTxRx( + ret = furi_hal_nfc_ll_txrx( (uint8_t*)&chkReq, sizeof(rfalPicoPassCheckReq), (uint8_t*)chkRes, @@ -131,26 +162,26 @@ ReturnCode rfalPicoPassPollerCheck(uint8_t* mac, rfalPicoPassCheckRes* chkRes) { flags, fwt); // printf("check rx: %d %s\n", recvLen, hex2Str(chkRes->mac, 4)); - if(ret == ERR_CRC) { - return ERR_NONE; + if(ret == FuriHalNfcReturnCrc) { + return FuriHalNfcReturnOk; } return ret; } -ReturnCode rfalPicoPassPollerReadBlock(uint8_t blockNum, rfalPicoPassReadBlockRes* readRes) { - ReturnCode ret; +FuriHalNfcReturn rfalPicoPassPollerReadBlock(uint8_t blockNum, rfalPicoPassReadBlockRes* readRes) { + FuriHalNfcReturn ret; uint8_t txBuf[4] = {RFAL_PICOPASS_CMD_READ, 0, 0, 0}; txBuf[1] = blockNum; - uint16_t crc = rfalCrcCalculateCcitt(0xE012, txBuf + 1, 1); + uint16_t crc = rfalPicoPassCalculateCcitt(0xE012, txBuf + 1, 1); memcpy(txBuf + 2, &crc, sizeof(uint16_t)); uint16_t recvLen = 0; uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS; - uint32_t fwt = rfalConvMsTo1fc(20); + uint32_t fwt = furi_hal_nfc_ll_ms2fc(20); - ret = rfalTransceiveBlockingTxRx( + ret = furi_hal_nfc_ll_txrx( txBuf, sizeof(txBuf), (uint8_t*)readRes, @@ -161,8 +192,8 @@ ReturnCode rfalPicoPassPollerReadBlock(uint8_t blockNum, rfalPicoPassReadBlockRe return ret; } -ReturnCode rfalPicoPassPollerWriteBlock(uint8_t blockNum, uint8_t data[8], uint8_t mac[4]) { - ReturnCode ret; +FuriHalNfcReturn rfalPicoPassPollerWriteBlock(uint8_t blockNum, uint8_t data[8], uint8_t mac[4]) { + FuriHalNfcReturn ret; uint8_t txBuf[14] = {RFAL_PICOPASS_CMD_WRITE, blockNum, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; memcpy(txBuf + 2, data, RFAL_PICOPASS_MAX_BLOCK_LEN); @@ -170,13 +201,13 @@ ReturnCode rfalPicoPassPollerWriteBlock(uint8_t blockNum, uint8_t data[8], uint8 uint16_t recvLen = 0; uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS; - uint32_t fwt = rfalConvMsTo1fc(20); + uint32_t fwt = furi_hal_nfc_ll_ms2fc(20); rfalPicoPassReadBlockRes block; - ret = rfalTransceiveBlockingTxRx( + ret = furi_hal_nfc_ll_txrx( txBuf, sizeof(txBuf), (uint8_t*)&block, sizeof(block), &recvLen, flags, fwt); - if(ret == ERR_NONE) { + if(ret == FuriHalNfcReturnOk) { // TODO: compare response } diff --git a/applications/plugins/picopass/rfal_picopass.h b/applications/plugins/picopass/rfal_picopass.h new file mode 100644 index 00000000000..6926b2a79d6 --- /dev/null +++ b/applications/plugins/picopass/rfal_picopass.h @@ -0,0 +1,48 @@ +#pragma once + +#include + +#define RFAL_PICOPASS_UID_LEN 8 +#define RFAL_PICOPASS_MAX_BLOCK_LEN 8 + +enum { + RFAL_PICOPASS_CMD_ACTALL = 0x0A, + RFAL_PICOPASS_CMD_IDENTIFY = 0x0C, + RFAL_PICOPASS_CMD_SELECT = 0x81, + RFAL_PICOPASS_CMD_READCHECK = 0x88, + RFAL_PICOPASS_CMD_CHECK = 0x05, + RFAL_PICOPASS_CMD_READ = 0x0C, + RFAL_PICOPASS_CMD_WRITE = 0x87, +}; + +typedef struct { + uint8_t CSN[RFAL_PICOPASS_UID_LEN]; // Anti-collision CSN + uint8_t crc[2]; +} rfalPicoPassIdentifyRes; + +typedef struct { + uint8_t CSN[RFAL_PICOPASS_UID_LEN]; // Real CSN + uint8_t crc[2]; +} rfalPicoPassSelectRes; + +typedef struct { + uint8_t CCNR[8]; +} rfalPicoPassReadCheckRes; + +typedef struct { + uint8_t mac[4]; +} rfalPicoPassCheckRes; + +typedef struct { + uint8_t data[RFAL_PICOPASS_MAX_BLOCK_LEN]; + uint8_t crc[2]; +} rfalPicoPassReadBlockRes; + +FuriHalNfcReturn rfalPicoPassPollerInitialize(void); +FuriHalNfcReturn rfalPicoPassPollerCheckPresence(void); +FuriHalNfcReturn rfalPicoPassPollerIdentify(rfalPicoPassIdentifyRes* idRes); +FuriHalNfcReturn rfalPicoPassPollerSelect(uint8_t* csn, rfalPicoPassSelectRes* selRes); +FuriHalNfcReturn rfalPicoPassPollerReadCheck(rfalPicoPassReadCheckRes* rcRes); +FuriHalNfcReturn rfalPicoPassPollerCheck(uint8_t* mac, rfalPicoPassCheckRes* chkRes); +FuriHalNfcReturn rfalPicoPassPollerReadBlock(uint8_t blockNum, rfalPicoPassReadBlockRes* readRes); +FuriHalNfcReturn rfalPicoPassPollerWriteBlock(uint8_t blockNum, uint8_t data[8], uint8_t mac[4]); diff --git a/applications/picopass/scenes/picopass_scene.c b/applications/plugins/picopass/scenes/picopass_scene.c similarity index 100% rename from applications/picopass/scenes/picopass_scene.c rename to applications/plugins/picopass/scenes/picopass_scene.c diff --git a/applications/picopass/scenes/picopass_scene.h b/applications/plugins/picopass/scenes/picopass_scene.h similarity index 100% rename from applications/picopass/scenes/picopass_scene.h rename to applications/plugins/picopass/scenes/picopass_scene.h diff --git a/applications/picopass/scenes/picopass_scene_card_menu.c b/applications/plugins/picopass/scenes/picopass_scene_card_menu.c similarity index 100% rename from applications/picopass/scenes/picopass_scene_card_menu.c rename to applications/plugins/picopass/scenes/picopass_scene_card_menu.c diff --git a/applications/picopass/scenes/picopass_scene_config.h b/applications/plugins/picopass/scenes/picopass_scene_config.h similarity index 100% rename from applications/picopass/scenes/picopass_scene_config.h rename to applications/plugins/picopass/scenes/picopass_scene_config.h diff --git a/applications/picopass/scenes/picopass_scene_delete.c b/applications/plugins/picopass/scenes/picopass_scene_delete.c similarity index 100% rename from applications/picopass/scenes/picopass_scene_delete.c rename to applications/plugins/picopass/scenes/picopass_scene_delete.c diff --git a/applications/picopass/scenes/picopass_scene_delete_success.c b/applications/plugins/picopass/scenes/picopass_scene_delete_success.c similarity index 100% rename from applications/picopass/scenes/picopass_scene_delete_success.c rename to applications/plugins/picopass/scenes/picopass_scene_delete_success.c diff --git a/applications/picopass/scenes/picopass_scene_device_info.c b/applications/plugins/picopass/scenes/picopass_scene_device_info.c similarity index 100% rename from applications/picopass/scenes/picopass_scene_device_info.c rename to applications/plugins/picopass/scenes/picopass_scene_device_info.c diff --git a/applications/picopass/scenes/picopass_scene_file_select.c b/applications/plugins/picopass/scenes/picopass_scene_file_select.c similarity index 95% rename from applications/picopass/scenes/picopass_scene_file_select.c rename to applications/plugins/picopass/scenes/picopass_scene_file_select.c index b3d4c3d73ec..2fc64746ebb 100644 --- a/applications/picopass/scenes/picopass_scene_file_select.c +++ b/applications/plugins/picopass/scenes/picopass_scene_file_select.c @@ -1,5 +1,5 @@ #include "../picopass_i.h" -#include "picopass/picopass_device.h" +#include "../picopass_device.h" void picopass_scene_file_select_on_enter(void* context) { Picopass* picopass = context; diff --git a/applications/picopass/scenes/picopass_scene_read_card.c b/applications/plugins/picopass/scenes/picopass_scene_read_card.c similarity index 100% rename from applications/picopass/scenes/picopass_scene_read_card.c rename to applications/plugins/picopass/scenes/picopass_scene_read_card.c diff --git a/applications/picopass/scenes/picopass_scene_read_card_success.c b/applications/plugins/picopass/scenes/picopass_scene_read_card_success.c similarity index 100% rename from applications/picopass/scenes/picopass_scene_read_card_success.c rename to applications/plugins/picopass/scenes/picopass_scene_read_card_success.c diff --git a/applications/picopass/scenes/picopass_scene_save_name.c b/applications/plugins/picopass/scenes/picopass_scene_save_name.c similarity index 100% rename from applications/picopass/scenes/picopass_scene_save_name.c rename to applications/plugins/picopass/scenes/picopass_scene_save_name.c diff --git a/applications/picopass/scenes/picopass_scene_save_success.c b/applications/plugins/picopass/scenes/picopass_scene_save_success.c similarity index 100% rename from applications/picopass/scenes/picopass_scene_save_success.c rename to applications/plugins/picopass/scenes/picopass_scene_save_success.c diff --git a/applications/picopass/scenes/picopass_scene_saved_menu.c b/applications/plugins/picopass/scenes/picopass_scene_saved_menu.c similarity index 100% rename from applications/picopass/scenes/picopass_scene_saved_menu.c rename to applications/plugins/picopass/scenes/picopass_scene_saved_menu.c diff --git a/applications/picopass/scenes/picopass_scene_start.c b/applications/plugins/picopass/scenes/picopass_scene_start.c similarity index 100% rename from applications/picopass/scenes/picopass_scene_start.c rename to applications/plugins/picopass/scenes/picopass_scene_start.c diff --git a/applications/picopass/scenes/picopass_scene_write_card.c b/applications/plugins/picopass/scenes/picopass_scene_write_card.c similarity index 100% rename from applications/picopass/scenes/picopass_scene_write_card.c rename to applications/plugins/picopass/scenes/picopass_scene_write_card.c diff --git a/applications/picopass/scenes/picopass_scene_write_card_success.c b/applications/plugins/picopass/scenes/picopass_scene_write_card_success.c similarity index 100% rename from applications/picopass/scenes/picopass_scene_write_card_success.c rename to applications/plugins/picopass/scenes/picopass_scene_write_card_success.c diff --git a/applications/snake_game/application.fam b/applications/plugins/snake_game/application.fam similarity index 79% rename from applications/snake_game/application.fam rename to applications/plugins/snake_game/application.fam index 5d6ad527631..d55f53bb14f 100644 --- a/applications/snake_game/application.fam +++ b/applications/plugins/snake_game/application.fam @@ -6,6 +6,7 @@ App( cdefines=["APP_SNAKE_GAME"], requires=["gui"], stack_size=1 * 1024, - icon="A_Plugins_14", order=30, + fap_icon="snake_10px.png", + fap_category="Games", ) diff --git a/applications/plugins/snake_game/snake_10px.png b/applications/plugins/snake_game/snake_10px.png new file mode 100644 index 0000000000000000000000000000000000000000..52d9fa7e0e1b884774a6e58abb1965b1b1905767 GIT binary patch literal 158 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2VGmzZ%#=aj&u?6^qxc>kDAIJlX?eOh zhE&W+PDn^dVNp_G*rbrd!ONE5$kL$2+HH6!MA=j6#)(e`V#>-4Tp0`o%bUNP1L~43 zag8Vm&QB{TPb^AhaL6gmODsst%q!6^$V=Bv&QD2A{^~3#2UN)5>FVdQ&MBb@0GSOf APyhe` literal 0 HcmV?d00001 diff --git a/applications/snake_game/snake_game.c b/applications/plugins/snake_game/snake_game.c similarity index 100% rename from applications/snake_game/snake_game.c rename to applications/plugins/snake_game/snake_game.c diff --git a/applications/power/application.fam b/applications/power/application.fam deleted file mode 100644 index 1e503749a21..00000000000 --- a/applications/power/application.fam +++ /dev/null @@ -1,53 +0,0 @@ -App( - appid="power", - name="PowerSrv", - apptype=FlipperAppType.SERVICE, - entry_point="power_srv", - cdefines=["SRV_POWER"], - requires=[ - "gui", - "cli", - ], - provides=[ - "power_settings", - "power_start", - ], - stack_size=1 * 1024, - order=110, -) - -App( - appid="power_settings", - name="Power", - apptype=FlipperAppType.SETTINGS, - entry_point="power_settings_app", - requires=[ - "gui", - "power", - ], - flags=["InsomniaSafe"], - stack_size=1 * 1024, - order=40, -) - -App( - appid="power_start", - apptype=FlipperAppType.STARTUP, - entry_point="power_on_system_start", - requires=["power"], - order=80, -) - -App( - appid="battery_test", - name="Battery Test", - apptype=FlipperAppType.DEBUG, - entry_point="battery_test_app", - cdefines=["APP_BATTERY_TEST"], - requires=[ - "gui", - "power", - ], - stack_size=1 * 1024, - order=130, -) diff --git a/applications/power/power_cli.h b/applications/power/power_cli.h deleted file mode 100644 index ef55d869ca1..00000000000 --- a/applications/power/power_cli.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -void power_on_system_start(); diff --git a/applications/services/application.fam b/applications/services/application.fam new file mode 100644 index 00000000000..aec49b2312e --- /dev/null +++ b/applications/services/application.fam @@ -0,0 +1,13 @@ +App( + appid="basic_services", + name="Basic services", + apptype=FlipperAppType.METAPACKAGE, + provides=[ + "crypto_start", + "rpc_start", + "bt", + "desktop", + "loader", + "power", + ], +) diff --git a/applications/applications.h b/applications/services/applications.h similarity index 100% rename from applications/applications.h rename to applications/services/applications.h diff --git a/applications/services/bt/application.fam b/applications/services/bt/application.fam new file mode 100644 index 00000000000..2e97dc1d65d --- /dev/null +++ b/applications/services/bt/application.fam @@ -0,0 +1,25 @@ +App( + appid="bt", + name="BtSrv", + apptype=FlipperAppType.SERVICE, + entry_point="bt_srv", + cdefines=["SRV_BT"], + requires=[ + "cli", + "dialogs", + ], + provides=[ + "bt_start", + "bt_settings", + ], + stack_size=1 * 1024, + order=20, + sdk_headers=["bt_service/bt.h"], +) + +App( + appid="bt_start", + apptype=FlipperAppType.STARTUP, + entry_point="bt_on_system_start", + order=70, +) diff --git a/applications/bt/bt_cli.c b/applications/services/bt/bt_cli.c similarity index 99% rename from applications/bt/bt_cli.c rename to applications/services/bt/bt_cli.c index 79500fac48d..ff5ebb448a3 100644 --- a/applications/bt/bt_cli.c +++ b/applications/services/bt/bt_cli.c @@ -1,6 +1,6 @@ #include #include -#include +#include #include #include diff --git a/applications/bt/bt_service/bt.c b/applications/services/bt/bt_service/bt.c similarity index 100% rename from applications/bt/bt_service/bt.c rename to applications/services/bt/bt_service/bt.c diff --git a/applications/bt/bt_service/bt.h b/applications/services/bt/bt_service/bt.h similarity index 100% rename from applications/bt/bt_service/bt.h rename to applications/services/bt/bt_service/bt.h diff --git a/applications/bt/bt_service/bt_api.c b/applications/services/bt/bt_service/bt_api.c similarity index 100% rename from applications/bt/bt_service/bt_api.c rename to applications/services/bt/bt_service/bt_api.c diff --git a/applications/bt/bt_service/bt_i.h b/applications/services/bt/bt_service/bt_i.h similarity index 92% rename from applications/bt/bt_service/bt_i.h rename to applications/services/bt/bt_service/bt_i.h index a45a36c9eaa..5769243ecb1 100644 --- a/applications/bt/bt_service/bt_i.h +++ b/applications/services/bt/bt_service/bt_i.h @@ -11,10 +11,10 @@ #include #include -#include -#include +#include +#include -#include "../bt_settings.h" +#include #define BT_API_UNLOCK_EVENT (1UL << 0) diff --git a/applications/bt/bt_service/bt_keys_filename.h b/applications/services/bt/bt_service/bt_keys_filename.h similarity index 100% rename from applications/bt/bt_service/bt_keys_filename.h rename to applications/services/bt/bt_service/bt_keys_filename.h diff --git a/applications/bt/bt_service/bt_keys_storage.c b/applications/services/bt/bt_service/bt_keys_storage.c similarity index 100% rename from applications/bt/bt_service/bt_keys_storage.c rename to applications/services/bt/bt_service/bt_keys_storage.c diff --git a/applications/bt/bt_service/bt_keys_storage.h b/applications/services/bt/bt_service/bt_keys_storage.h similarity index 100% rename from applications/bt/bt_service/bt_keys_storage.h rename to applications/services/bt/bt_service/bt_keys_storage.h diff --git a/applications/bt/bt_settings.c b/applications/services/bt/bt_settings.c similarity index 100% rename from applications/bt/bt_settings.c rename to applications/services/bt/bt_settings.c diff --git a/applications/bt/bt_settings.h b/applications/services/bt/bt_settings.h similarity index 100% rename from applications/bt/bt_settings.h rename to applications/services/bt/bt_settings.h diff --git a/applications/bt/bt_settings_filename.h b/applications/services/bt/bt_settings_filename.h similarity index 100% rename from applications/bt/bt_settings_filename.h rename to applications/services/bt/bt_settings_filename.h diff --git a/applications/cli/application.fam b/applications/services/cli/application.fam similarity index 81% rename from applications/cli/application.fam rename to applications/services/cli/application.fam index 73c70aa44ed..7a57bb6076b 100644 --- a/applications/cli/application.fam +++ b/applications/services/cli/application.fam @@ -6,4 +6,5 @@ App( cdefines=["SRV_CLI"], stack_size=4 * 1024, order=30, + sdk_headers=["cli.h", "cli_vcp.h"], ) diff --git a/applications/cli/cli.c b/applications/services/cli/cli.c similarity index 100% rename from applications/cli/cli.c rename to applications/services/cli/cli.c diff --git a/applications/cli/cli.h b/applications/services/cli/cli.h similarity index 100% rename from applications/cli/cli.h rename to applications/services/cli/cli.h diff --git a/applications/cli/cli_command_gpio.c b/applications/services/cli/cli_command_gpio.c similarity index 100% rename from applications/cli/cli_command_gpio.c rename to applications/services/cli/cli_command_gpio.c diff --git a/applications/cli/cli_command_gpio.h b/applications/services/cli/cli_command_gpio.h similarity index 100% rename from applications/cli/cli_command_gpio.h rename to applications/services/cli/cli_command_gpio.h diff --git a/applications/cli/cli_commands.c b/applications/services/cli/cli_commands.c similarity index 100% rename from applications/cli/cli_commands.c rename to applications/services/cli/cli_commands.c diff --git a/applications/cli/cli_commands.h b/applications/services/cli/cli_commands.h similarity index 100% rename from applications/cli/cli_commands.h rename to applications/services/cli/cli_commands.h diff --git a/applications/cli/cli_i.h b/applications/services/cli/cli_i.h similarity index 92% rename from applications/cli/cli_i.h rename to applications/services/cli/cli_i.h index 8f0bd85d510..ba4582d0dfc 100644 --- a/applications/cli/cli_i.h +++ b/applications/services/cli/cli_i.h @@ -9,17 +9,21 @@ #include #include +#include "cli_vcp.h" + #define CLI_LINE_SIZE_MAX #define CLI_COMMANDS_TREE_RANK 4 +#ifdef __cplusplus +extern "C" { +#endif + typedef struct { CliCallback callback; void* context; uint32_t flags; } CliCommand; -typedef struct CliSession CliSession; - struct CliSession { void (*init)(void); void (*deinit)(void); @@ -57,3 +61,7 @@ void cli_reset(Cli* cli); void cli_putc(Cli* cli, char c); void cli_stdout_callback(void* _cookie, const char* data, size_t size); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/applications/cli/cli_vcp.c b/applications/services/cli/cli_vcp.c similarity index 99% rename from applications/cli/cli_vcp.c rename to applications/services/cli/cli_vcp.c index 5a8b44dcf30..f2893a48b85 100644 --- a/applications/cli/cli_vcp.c +++ b/applications/services/cli/cli_vcp.c @@ -1,4 +1,4 @@ -#include +#include #include #include #include diff --git a/applications/cli/cli_vcp.h b/applications/services/cli/cli_vcp.h similarity index 80% rename from applications/cli/cli_vcp.h rename to applications/services/cli/cli_vcp.h index 2bd47efb28a..3aef2ef7083 100644 --- a/applications/cli/cli_vcp.h +++ b/applications/services/cli/cli_vcp.h @@ -5,12 +5,12 @@ #pragma once -#include "cli_i.h" - #ifdef __cplusplus extern "C" { #endif +typedef struct CliSession CliSession; + extern CliSession cli_vcp; #ifdef __cplusplus diff --git a/applications/crypto/application.fam b/applications/services/crypto/application.fam similarity index 100% rename from applications/crypto/application.fam rename to applications/services/crypto/application.fam diff --git a/applications/crypto/crypto_cli.c b/applications/services/crypto/crypto_cli.c similarity index 100% rename from applications/crypto/crypto_cli.c rename to applications/services/crypto/crypto_cli.c diff --git a/applications/desktop/animations/animation_manager.c b/applications/services/desktop/animations/animation_manager.c similarity index 100% rename from applications/desktop/animations/animation_manager.c rename to applications/services/desktop/animations/animation_manager.c diff --git a/applications/desktop/animations/animation_manager.h b/applications/services/desktop/animations/animation_manager.h similarity index 100% rename from applications/desktop/animations/animation_manager.h rename to applications/services/desktop/animations/animation_manager.h diff --git a/applications/desktop/animations/animation_storage.c b/applications/services/desktop/animations/animation_storage.c similarity index 100% rename from applications/desktop/animations/animation_storage.c rename to applications/services/desktop/animations/animation_storage.c diff --git a/applications/desktop/animations/animation_storage.h b/applications/services/desktop/animations/animation_storage.h similarity index 100% rename from applications/desktop/animations/animation_storage.h rename to applications/services/desktop/animations/animation_storage.h diff --git a/applications/desktop/animations/animation_storage_i.h b/applications/services/desktop/animations/animation_storage_i.h similarity index 100% rename from applications/desktop/animations/animation_storage_i.h rename to applications/services/desktop/animations/animation_storage_i.h diff --git a/applications/desktop/animations/views/bubble_animation_view.c b/applications/services/desktop/animations/views/bubble_animation_view.c similarity index 100% rename from applications/desktop/animations/views/bubble_animation_view.c rename to applications/services/desktop/animations/views/bubble_animation_view.c diff --git a/applications/desktop/animations/views/bubble_animation_view.h b/applications/services/desktop/animations/views/bubble_animation_view.h similarity index 100% rename from applications/desktop/animations/views/bubble_animation_view.h rename to applications/services/desktop/animations/views/bubble_animation_view.h diff --git a/applications/desktop/animations/views/one_shot_animation_view.c b/applications/services/desktop/animations/views/one_shot_animation_view.c similarity index 100% rename from applications/desktop/animations/views/one_shot_animation_view.c rename to applications/services/desktop/animations/views/one_shot_animation_view.c diff --git a/applications/desktop/animations/views/one_shot_animation_view.h b/applications/services/desktop/animations/views/one_shot_animation_view.h similarity index 100% rename from applications/desktop/animations/views/one_shot_animation_view.h rename to applications/services/desktop/animations/views/one_shot_animation_view.h diff --git a/applications/desktop/application.fam b/applications/services/desktop/application.fam similarity index 59% rename from applications/desktop/application.fam rename to applications/services/desktop/application.fam index 5f3dcdfdac9..da6e2b8020c 100644 --- a/applications/desktop/application.fam +++ b/applications/services/desktop/application.fam @@ -15,16 +15,3 @@ App( stack_size=2 * 1024, order=60, ) - -App( - appid="desktop_settings", - name="Desktop", - apptype=FlipperAppType.SETTINGS, - entry_point="desktop_settings_app", - requires=[ - "desktop", - "gui", - ], - stack_size=1 * 1024, - order=50, -) diff --git a/applications/desktop/desktop.c b/applications/services/desktop/desktop.c similarity index 100% rename from applications/desktop/desktop.c rename to applications/services/desktop/desktop.c diff --git a/applications/desktop/desktop.h b/applications/services/desktop/desktop.h similarity index 100% rename from applications/desktop/desktop.h rename to applications/services/desktop/desktop.h diff --git a/applications/desktop/desktop_i.h b/applications/services/desktop/desktop_i.h similarity index 97% rename from applications/desktop/desktop_i.h rename to applications/services/desktop/desktop_i.h index 0b5d607d194..65ca563ddfc 100644 --- a/applications/desktop/desktop_i.h +++ b/applications/services/desktop/desktop_i.h @@ -9,7 +9,7 @@ #include "views/desktop_view_lock_menu.h" #include "views/desktop_view_debug.h" #include "views/desktop_view_slideshow.h" -#include "desktop/desktop_settings/desktop_settings.h" +#include #include #include diff --git a/applications/desktop/desktop_settings/desktop_settings.h b/applications/services/desktop/desktop_settings.h similarity index 100% rename from applications/desktop/desktop_settings/desktop_settings.h rename to applications/services/desktop/desktop_settings.h diff --git a/applications/desktop/desktop_settings/desktop_settings_filename.h b/applications/services/desktop/desktop_settings_filename.h similarity index 100% rename from applications/desktop/desktop_settings/desktop_settings_filename.h rename to applications/services/desktop/desktop_settings_filename.h diff --git a/applications/desktop/helpers/pin_lock.c b/applications/services/desktop/helpers/pin_lock.c similarity index 99% rename from applications/desktop/helpers/pin_lock.c rename to applications/services/desktop/helpers/pin_lock.c index 0495b675d74..9e17a925096 100644 --- a/applications/desktop/helpers/pin_lock.c +++ b/applications/services/desktop/helpers/pin_lock.c @@ -8,6 +8,7 @@ #include "../helpers/pin_lock.h" #include "../desktop_i.h" +#include #include static const NotificationSequence sequence_pin_fail = { diff --git a/applications/desktop/helpers/pin_lock.h b/applications/services/desktop/helpers/pin_lock.h similarity index 91% rename from applications/desktop/helpers/pin_lock.h rename to applications/services/desktop/helpers/pin_lock.h index 4454cda54f5..028ae6d22f8 100644 --- a/applications/desktop/helpers/pin_lock.h +++ b/applications/services/desktop/helpers/pin_lock.h @@ -2,7 +2,7 @@ #include #include #include "../desktop.h" -#include "../desktop_settings/desktop_settings.h" +#include void desktop_pin_lock_error_notify(); diff --git a/applications/desktop/helpers/slideshow.c b/applications/services/desktop/helpers/slideshow.c similarity index 100% rename from applications/desktop/helpers/slideshow.c rename to applications/services/desktop/helpers/slideshow.c diff --git a/applications/desktop/helpers/slideshow.h b/applications/services/desktop/helpers/slideshow.h similarity index 100% rename from applications/desktop/helpers/slideshow.h rename to applications/services/desktop/helpers/slideshow.h diff --git a/applications/desktop/helpers/slideshow_filename.h b/applications/services/desktop/helpers/slideshow_filename.h similarity index 100% rename from applications/desktop/helpers/slideshow_filename.h rename to applications/services/desktop/helpers/slideshow_filename.h diff --git a/applications/desktop/scenes/desktop_scene.c b/applications/services/desktop/scenes/desktop_scene.c similarity index 100% rename from applications/desktop/scenes/desktop_scene.c rename to applications/services/desktop/scenes/desktop_scene.c diff --git a/applications/desktop/scenes/desktop_scene.h b/applications/services/desktop/scenes/desktop_scene.h similarity index 100% rename from applications/desktop/scenes/desktop_scene.h rename to applications/services/desktop/scenes/desktop_scene.h diff --git a/applications/desktop/scenes/desktop_scene_config.h b/applications/services/desktop/scenes/desktop_scene_config.h similarity index 100% rename from applications/desktop/scenes/desktop_scene_config.h rename to applications/services/desktop/scenes/desktop_scene_config.h diff --git a/applications/desktop/scenes/desktop_scene_debug.c b/applications/services/desktop/scenes/desktop_scene_debug.c similarity index 100% rename from applications/desktop/scenes/desktop_scene_debug.c rename to applications/services/desktop/scenes/desktop_scene_debug.c diff --git a/applications/desktop/scenes/desktop_scene_fault.c b/applications/services/desktop/scenes/desktop_scene_fault.c similarity index 100% rename from applications/desktop/scenes/desktop_scene_fault.c rename to applications/services/desktop/scenes/desktop_scene_fault.c diff --git a/applications/desktop/scenes/desktop_scene_hw_mismatch.c b/applications/services/desktop/scenes/desktop_scene_hw_mismatch.c similarity index 100% rename from applications/desktop/scenes/desktop_scene_hw_mismatch.c rename to applications/services/desktop/scenes/desktop_scene_hw_mismatch.c diff --git a/applications/desktop/scenes/desktop_scene_i.h b/applications/services/desktop/scenes/desktop_scene_i.h similarity index 100% rename from applications/desktop/scenes/desktop_scene_i.h rename to applications/services/desktop/scenes/desktop_scene_i.h diff --git a/applications/desktop/scenes/desktop_scene_lock_menu.c b/applications/services/desktop/scenes/desktop_scene_lock_menu.c similarity index 98% rename from applications/desktop/scenes/desktop_scene_lock_menu.c rename to applications/services/desktop/scenes/desktop_scene_lock_menu.c index a86bb184b6c..bdd84cadb9f 100644 --- a/applications/desktop/scenes/desktop_scene_lock_menu.c +++ b/applications/services/desktop/scenes/desktop_scene_lock_menu.c @@ -6,7 +6,7 @@ #include #include "../desktop_i.h" -#include "../desktop_settings/desktop_settings.h" +#include #include "../views/desktop_view_lock_menu.h" #include "desktop_scene_i.h" #include "desktop_scene.h" diff --git a/applications/desktop/scenes/desktop_scene_locked.c b/applications/services/desktop/scenes/desktop_scene_locked.c similarity index 100% rename from applications/desktop/scenes/desktop_scene_locked.c rename to applications/services/desktop/scenes/desktop_scene_locked.c diff --git a/applications/desktop/scenes/desktop_scene_main.c b/applications/services/desktop/scenes/desktop_scene_main.c similarity index 100% rename from applications/desktop/scenes/desktop_scene_main.c rename to applications/services/desktop/scenes/desktop_scene_main.c diff --git a/applications/desktop/scenes/desktop_scene_pin_input.c b/applications/services/desktop/scenes/desktop_scene_pin_input.c similarity index 100% rename from applications/desktop/scenes/desktop_scene_pin_input.c rename to applications/services/desktop/scenes/desktop_scene_pin_input.c diff --git a/applications/desktop/scenes/desktop_scene_pin_timeout.c b/applications/services/desktop/scenes/desktop_scene_pin_timeout.c similarity index 100% rename from applications/desktop/scenes/desktop_scene_pin_timeout.c rename to applications/services/desktop/scenes/desktop_scene_pin_timeout.c diff --git a/applications/desktop/scenes/desktop_scene_slideshow.c b/applications/services/desktop/scenes/desktop_scene_slideshow.c similarity index 100% rename from applications/desktop/scenes/desktop_scene_slideshow.c rename to applications/services/desktop/scenes/desktop_scene_slideshow.c diff --git a/applications/desktop/views/desktop_events.h b/applications/services/desktop/views/desktop_events.h similarity index 100% rename from applications/desktop/views/desktop_events.h rename to applications/services/desktop/views/desktop_events.h diff --git a/applications/desktop/views/desktop_view_debug.c b/applications/services/desktop/views/desktop_view_debug.c similarity index 100% rename from applications/desktop/views/desktop_view_debug.c rename to applications/services/desktop/views/desktop_view_debug.c diff --git a/applications/desktop/views/desktop_view_debug.h b/applications/services/desktop/views/desktop_view_debug.h similarity index 100% rename from applications/desktop/views/desktop_view_debug.h rename to applications/services/desktop/views/desktop_view_debug.h diff --git a/applications/desktop/views/desktop_view_lock_menu.c b/applications/services/desktop/views/desktop_view_lock_menu.c similarity index 100% rename from applications/desktop/views/desktop_view_lock_menu.c rename to applications/services/desktop/views/desktop_view_lock_menu.c diff --git a/applications/desktop/views/desktop_view_lock_menu.h b/applications/services/desktop/views/desktop_view_lock_menu.h similarity index 100% rename from applications/desktop/views/desktop_view_lock_menu.h rename to applications/services/desktop/views/desktop_view_lock_menu.h diff --git a/applications/desktop/views/desktop_view_locked.c b/applications/services/desktop/views/desktop_view_locked.c similarity index 99% rename from applications/desktop/views/desktop_view_locked.c rename to applications/services/desktop/views/desktop_view_locked.c index 58ed4face47..915b26103c6 100644 --- a/applications/desktop/views/desktop_view_locked.c +++ b/applications/services/desktop/views/desktop_view_locked.c @@ -6,7 +6,7 @@ #include #include -#include "../desktop_settings/desktop_settings.h" +#include #include "../desktop_i.h" #include "desktop_view_locked.h" diff --git a/applications/desktop/views/desktop_view_locked.h b/applications/services/desktop/views/desktop_view_locked.h similarity index 94% rename from applications/desktop/views/desktop_view_locked.h rename to applications/services/desktop/views/desktop_view_locked.h index b0a0aa30301..ea065e39812 100644 --- a/applications/desktop/views/desktop_view_locked.h +++ b/applications/services/desktop/views/desktop_view_locked.h @@ -1,6 +1,6 @@ #pragma once -#include "../desktop_settings/desktop_settings.h" +#include #include "../views/desktop_events.h" #include diff --git a/applications/desktop/views/desktop_view_main.c b/applications/services/desktop/views/desktop_view_main.c similarity index 100% rename from applications/desktop/views/desktop_view_main.c rename to applications/services/desktop/views/desktop_view_main.c diff --git a/applications/desktop/views/desktop_view_main.h b/applications/services/desktop/views/desktop_view_main.h similarity index 100% rename from applications/desktop/views/desktop_view_main.h rename to applications/services/desktop/views/desktop_view_main.h diff --git a/applications/desktop/views/desktop_view_pin_input.c b/applications/services/desktop/views/desktop_view_pin_input.c similarity index 99% rename from applications/desktop/views/desktop_view_pin_input.c rename to applications/services/desktop/views/desktop_view_pin_input.c index d6ce1a02a45..5502d5f6d8b 100644 --- a/applications/desktop/views/desktop_view_pin_input.c +++ b/applications/services/desktop/views/desktop_view_pin_input.c @@ -6,7 +6,7 @@ #include #include "desktop_view_pin_input.h" -#include "../desktop_settings/desktop_settings.h" +#include #define NO_ACTIVITY_TIMEOUT 15000 diff --git a/applications/desktop/views/desktop_view_pin_input.h b/applications/services/desktop/views/desktop_view_pin_input.h similarity index 96% rename from applications/desktop/views/desktop_view_pin_input.h rename to applications/services/desktop/views/desktop_view_pin_input.h index 3e39fd207e4..40eee4cc9b6 100644 --- a/applications/desktop/views/desktop_view_pin_input.h +++ b/applications/services/desktop/views/desktop_view_pin_input.h @@ -1,7 +1,7 @@ #pragma once #include -#include "desktop/desktop_settings/desktop_settings.h" +#include typedef void (*DesktopViewPinInputCallback)(void*); typedef void (*DesktopViewPinInputDoneCallback)(const PinCode* pin_code, void*); diff --git a/applications/desktop/views/desktop_view_pin_setup_done.c b/applications/services/desktop/views/desktop_view_pin_setup_done.c similarity index 100% rename from applications/desktop/views/desktop_view_pin_setup_done.c rename to applications/services/desktop/views/desktop_view_pin_setup_done.c diff --git a/applications/desktop/views/desktop_view_pin_setup_done.h b/applications/services/desktop/views/desktop_view_pin_setup_done.h similarity index 100% rename from applications/desktop/views/desktop_view_pin_setup_done.h rename to applications/services/desktop/views/desktop_view_pin_setup_done.h diff --git a/applications/desktop/views/desktop_view_pin_timeout.c b/applications/services/desktop/views/desktop_view_pin_timeout.c similarity index 100% rename from applications/desktop/views/desktop_view_pin_timeout.c rename to applications/services/desktop/views/desktop_view_pin_timeout.c diff --git a/applications/desktop/views/desktop_view_pin_timeout.h b/applications/services/desktop/views/desktop_view_pin_timeout.h similarity index 100% rename from applications/desktop/views/desktop_view_pin_timeout.h rename to applications/services/desktop/views/desktop_view_pin_timeout.h diff --git a/applications/desktop/views/desktop_view_slideshow.c b/applications/services/desktop/views/desktop_view_slideshow.c similarity index 100% rename from applications/desktop/views/desktop_view_slideshow.c rename to applications/services/desktop/views/desktop_view_slideshow.c diff --git a/applications/desktop/views/desktop_view_slideshow.h b/applications/services/desktop/views/desktop_view_slideshow.h similarity index 100% rename from applications/desktop/views/desktop_view_slideshow.h rename to applications/services/desktop/views/desktop_view_slideshow.h diff --git a/applications/dialogs/application.fam b/applications/services/dialogs/application.fam similarity index 87% rename from applications/dialogs/application.fam rename to applications/services/dialogs/application.fam index 0a18c79a3ba..eb2b40e28fb 100644 --- a/applications/dialogs/application.fam +++ b/applications/services/dialogs/application.fam @@ -7,4 +7,5 @@ App( requires=["gui"], stack_size=1 * 1024, order=40, + sdk_headers=["dialogs.h"], ) diff --git a/applications/dialogs/dialogs.c b/applications/services/dialogs/dialogs.c similarity index 78% rename from applications/dialogs/dialogs.c rename to applications/services/dialogs/dialogs.c index 381da1635f4..e2db4a0f834 100644 --- a/applications/dialogs/dialogs.c +++ b/applications/services/dialogs/dialogs.c @@ -4,6 +4,18 @@ #include "dialogs_module_file_browser.h" #include "dialogs_module_message.h" +void dialog_file_browser_set_basic_options( + DialogsFileBrowserOptions* options, + const char* extension, + const Icon* icon) { + options->extension = extension; + options->skip_assets = true; + options->icon = icon; + options->hide_ext = true; + options->item_loader_callback = NULL; + options->item_loader_context = NULL; +} + static DialogsApp* dialogs_app_alloc() { DialogsApp* app = malloc(sizeof(DialogsApp)); app->message_queue = furi_message_queue_alloc(8, sizeof(DialogsAppMessage)); diff --git a/applications/dialogs/dialogs.h b/applications/services/dialogs/dialogs.h similarity index 80% rename from applications/dialogs/dialogs.h rename to applications/services/dialogs/dialogs.h index 946ab6cf03a..2522c8b54fd 100644 --- a/applications/dialogs/dialogs.h +++ b/applications/services/dialogs/dialogs.h @@ -2,6 +2,7 @@ #include #include #include "m-string.h" +#include #ifdef __cplusplus extern "C" { @@ -16,24 +17,48 @@ typedef struct DialogsApp DialogsApp; /****************** FILE BROWSER ******************/ /** - * Shows and processes the file browser dialog - * @param context api pointer - * @param result_path selected file path string pointer - * @param path preselected file path string pointer + * File browser dialog extra options * @param extension file extension to be offered for selection * @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 + * @param item_loader_callback callback function for providing custom icon & entry name + * @param hide_ext callback context + */ +typedef struct { + const char* extension; + bool skip_assets; + const Icon* icon; + bool hide_ext; + FileBrowserLoadItemCallback item_loader_callback; + void* item_loader_context; +} DialogsFileBrowserOptions; + +/** + * Initialize file browser dialog options + * and set default values + * @param options pointer to options structure + * @param extension file extension to filter + * @param icon file icon pointer, NULL for default icon + */ +void dialog_file_browser_set_basic_options( + DialogsFileBrowserOptions* options, + const char* extension, + const Icon* icon); + +/** + * Shows and processes the file browser dialog + * @param context api pointer + * @param result_path selected file path string pointer + * @param path preselected file path string pointer + * @param options file browser dialog extra options, may be null * @return bool whether a file was selected */ bool dialog_file_browser_show( DialogsApp* context, string_ptr result_path, string_ptr path, - const char* extension, - bool skip_assets, - const Icon* icon, - bool hide_ext); + const DialogsFileBrowserOptions* options); /****************** MESSAGE ******************/ diff --git a/applications/dialogs/dialogs_api.c b/applications/services/dialogs/dialogs_api.c similarity index 82% rename from applications/dialogs/dialogs_api.c rename to applications/services/dialogs/dialogs_api.c index 72aa2a1b852..fd3b2e961df 100644 --- a/applications/dialogs/dialogs_api.c +++ b/applications/services/dialogs/dialogs_api.c @@ -9,22 +9,20 @@ bool dialog_file_browser_show( DialogsApp* context, string_ptr result_path, string_ptr path, - const char* extension, - bool skip_assets, - const Icon* icon, - bool hide_ext) { + const DialogsFileBrowserOptions* options) { FuriApiLock lock = API_LOCK_INIT_LOCKED(); furi_check(lock != NULL); DialogsAppData data = { .file_browser = { - .extension = extension, + .extension = options ? options->extension : "", .result_path = result_path, - .file_icon = icon, - .hide_ext = hide_ext, - .skip_assets = skip_assets, + .file_icon = options ? options->icon : NULL, + .hide_ext = options ? options->hide_ext : true, + .skip_assets = options ? options->skip_assets : true, .preselected_filename = path, - + .item_callback = options ? options->item_loader_callback : NULL, + .item_callback_context = options ? options->item_loader_context : NULL, }}; DialogsAppReturn return_data; diff --git a/applications/dialogs/dialogs_api_lock.h b/applications/services/dialogs/dialogs_api_lock.h similarity index 100% rename from applications/dialogs/dialogs_api_lock.h rename to applications/services/dialogs/dialogs_api_lock.h diff --git a/applications/dialogs/dialogs_i.h b/applications/services/dialogs/dialogs_i.h similarity index 85% rename from applications/dialogs/dialogs_i.h rename to applications/services/dialogs/dialogs_i.h index 36bee0248b6..76495d31b0a 100644 --- a/applications/dialogs/dialogs_i.h +++ b/applications/services/dialogs/dialogs_i.h @@ -2,6 +2,7 @@ #include "dialogs.h" #include "dialogs_message.h" #include "view_holder.h" +#include #ifdef __cplusplus extern "C" { diff --git a/applications/dialogs/dialogs_message.h b/applications/services/dialogs/dialogs_message.h similarity index 92% rename from applications/dialogs/dialogs_message.h rename to applications/services/dialogs/dialogs_message.h index ccfbdece5f3..2dc66f1334e 100644 --- a/applications/dialogs/dialogs_message.h +++ b/applications/services/dialogs/dialogs_message.h @@ -15,6 +15,8 @@ typedef struct { const Icon* file_icon; string_ptr result_path; string_ptr preselected_filename; + FileBrowserLoadItemCallback item_callback; + void* item_callback_context; } DialogsAppMessageDataFileBrowser; typedef struct { diff --git a/applications/dialogs/dialogs_module_file_browser.c b/applications/services/dialogs/dialogs_module_file_browser.c similarity index 95% rename from applications/dialogs/dialogs_module_file_browser.c rename to applications/services/dialogs/dialogs_module_file_browser.c index f5355571a13..166fd111333 100644 --- a/applications/dialogs/dialogs_module_file_browser.c +++ b/applications/services/dialogs/dialogs_module_file_browser.c @@ -39,6 +39,7 @@ bool dialogs_app_process_module_file_browser(const DialogsAppMessageDataFileBrow 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_set_item_callback(file_browser, data->item_callback, data->item_callback_context); file_browser_start(file_browser, data->preselected_filename); view_holder_set_view(view_holder, file_browser_get_view(file_browser)); diff --git a/applications/dialogs/dialogs_module_file_browser.h b/applications/services/dialogs/dialogs_module_file_browser.h similarity index 100% rename from applications/dialogs/dialogs_module_file_browser.h rename to applications/services/dialogs/dialogs_module_file_browser.h diff --git a/applications/dialogs/dialogs_module_message.c b/applications/services/dialogs/dialogs_module_message.c similarity index 100% rename from applications/dialogs/dialogs_module_message.c rename to applications/services/dialogs/dialogs_module_message.c diff --git a/applications/dialogs/dialogs_module_message.h b/applications/services/dialogs/dialogs_module_message.h similarity index 100% rename from applications/dialogs/dialogs_module_message.h rename to applications/services/dialogs/dialogs_module_message.h diff --git a/applications/dialogs/view_holder.c b/applications/services/dialogs/view_holder.c similarity index 100% rename from applications/dialogs/view_holder.c rename to applications/services/dialogs/view_holder.c diff --git a/applications/dialogs/view_holder.h b/applications/services/dialogs/view_holder.h similarity index 100% rename from applications/dialogs/view_holder.h rename to applications/services/dialogs/view_holder.h diff --git a/applications/services/dolphin/application.fam b/applications/services/dolphin/application.fam new file mode 100644 index 00000000000..78a097e678c --- /dev/null +++ b/applications/services/dolphin/application.fam @@ -0,0 +1,10 @@ +App( + appid="dolphin", + name="DolphinSrv", + apptype=FlipperAppType.SERVICE, + entry_point="dolphin_srv", + cdefines=["SRV_DOLPHIN"], + stack_size=1 * 1024, + order=50, + sdk_headers=["dolphin.h"], +) diff --git a/applications/dolphin/dolphin.c b/applications/services/dolphin/dolphin.c similarity index 100% rename from applications/dolphin/dolphin.c rename to applications/services/dolphin/dolphin.c diff --git a/applications/dolphin/dolphin.h b/applications/services/dolphin/dolphin.h similarity index 100% rename from applications/dolphin/dolphin.h rename to applications/services/dolphin/dolphin.h diff --git a/applications/dolphin/dolphin_i.h b/applications/services/dolphin/dolphin_i.h similarity index 100% rename from applications/dolphin/dolphin_i.h rename to applications/services/dolphin/dolphin_i.h diff --git a/applications/dolphin/helpers/dolphin_deed.c b/applications/services/dolphin/helpers/dolphin_deed.c similarity index 100% rename from applications/dolphin/helpers/dolphin_deed.c rename to applications/services/dolphin/helpers/dolphin_deed.c diff --git a/applications/dolphin/helpers/dolphin_deed.h b/applications/services/dolphin/helpers/dolphin_deed.h similarity index 100% rename from applications/dolphin/helpers/dolphin_deed.h rename to applications/services/dolphin/helpers/dolphin_deed.h diff --git a/applications/dolphin/helpers/dolphin_state.c b/applications/services/dolphin/helpers/dolphin_state.c similarity index 100% rename from applications/dolphin/helpers/dolphin_state.c rename to applications/services/dolphin/helpers/dolphin_state.c diff --git a/applications/dolphin/helpers/dolphin_state.h b/applications/services/dolphin/helpers/dolphin_state.h similarity index 100% rename from applications/dolphin/helpers/dolphin_state.h rename to applications/services/dolphin/helpers/dolphin_state.h diff --git a/applications/dolphin/helpers/dolphin_state_filename.h b/applications/services/dolphin/helpers/dolphin_state_filename.h similarity index 100% rename from applications/dolphin/helpers/dolphin_state_filename.h rename to applications/services/dolphin/helpers/dolphin_state_filename.h diff --git a/applications/services/gui/application.fam b/applications/services/gui/application.fam new file mode 100644 index 00000000000..7fad7b4ea85 --- /dev/null +++ b/applications/services/gui/application.fam @@ -0,0 +1,36 @@ +App( + appid="gui", + name="GuiSrv", + apptype=FlipperAppType.SERVICE, + entry_point="gui_srv", + cdefines=["SRV_GUI"], + requires=[ + "input", + "notification", + ], + stack_size=2 * 1024, + order=70, + sdk_headers=[ + "gui.h", + "elements.h", + "view_dispatcher.h", + "view_stack.h", + "modules/button_menu.h", + "modules/byte_input.h", + "modules/popup.h", + "modules/text_input.h", + "modules/widget.h", + "modules/validators.h", + "modules/file_browser.h", + "modules/button_panel.h", + "modules/variable_item_list.h", + "modules/file_browser_worker.h", + "modules/menu.h", + "modules/dialog_ex.h", + "modules/loading.h", + "modules/text_box.h", + "modules/submenu.h", + "modules/widget_elements/widget_element.h", + "modules/empty_screen.h", + ], +) diff --git a/applications/gui/canvas.c b/applications/services/gui/canvas.c similarity index 100% rename from applications/gui/canvas.c rename to applications/services/gui/canvas.c diff --git a/applications/gui/canvas.h b/applications/services/gui/canvas.h similarity index 100% rename from applications/gui/canvas.h rename to applications/services/gui/canvas.h diff --git a/applications/gui/canvas_i.h b/applications/services/gui/canvas_i.h similarity index 100% rename from applications/gui/canvas_i.h rename to applications/services/gui/canvas_i.h diff --git a/applications/gui/elements.c b/applications/services/gui/elements.c similarity index 100% rename from applications/gui/elements.c rename to applications/services/gui/elements.c diff --git a/applications/gui/elements.h b/applications/services/gui/elements.h similarity index 100% rename from applications/gui/elements.h rename to applications/services/gui/elements.h diff --git a/applications/gui/gui.c b/applications/services/gui/gui.c similarity index 100% rename from applications/gui/gui.c rename to applications/services/gui/gui.c diff --git a/applications/gui/gui.h b/applications/services/gui/gui.h similarity index 100% rename from applications/gui/gui.h rename to applications/services/gui/gui.h diff --git a/applications/gui/gui_i.h b/applications/services/gui/gui_i.h similarity index 100% rename from applications/gui/gui_i.h rename to applications/services/gui/gui_i.h diff --git a/applications/gui/icon.c b/applications/services/gui/icon.c similarity index 100% rename from applications/gui/icon.c rename to applications/services/gui/icon.c diff --git a/applications/gui/icon.h b/applications/services/gui/icon.h similarity index 100% rename from applications/gui/icon.h rename to applications/services/gui/icon.h diff --git a/applications/gui/icon_animation.c b/applications/services/gui/icon_animation.c similarity index 100% rename from applications/gui/icon_animation.c rename to applications/services/gui/icon_animation.c diff --git a/applications/gui/icon_animation.h b/applications/services/gui/icon_animation.h similarity index 100% rename from applications/gui/icon_animation.h rename to applications/services/gui/icon_animation.h diff --git a/applications/gui/icon_animation_i.h b/applications/services/gui/icon_animation_i.h similarity index 100% rename from applications/gui/icon_animation_i.h rename to applications/services/gui/icon_animation_i.h diff --git a/applications/gui/icon_i.h b/applications/services/gui/icon_i.h similarity index 100% rename from applications/gui/icon_i.h rename to applications/services/gui/icon_i.h diff --git a/applications/gui/modules/button_menu.c b/applications/services/gui/modules/button_menu.c similarity index 100% rename from applications/gui/modules/button_menu.c rename to applications/services/gui/modules/button_menu.c diff --git a/applications/gui/modules/button_menu.h b/applications/services/gui/modules/button_menu.h similarity index 100% rename from applications/gui/modules/button_menu.h rename to applications/services/gui/modules/button_menu.h diff --git a/applications/gui/modules/button_panel.c b/applications/services/gui/modules/button_panel.c similarity index 100% rename from applications/gui/modules/button_panel.c rename to applications/services/gui/modules/button_panel.c diff --git a/applications/gui/modules/button_panel.h b/applications/services/gui/modules/button_panel.h similarity index 100% rename from applications/gui/modules/button_panel.h rename to applications/services/gui/modules/button_panel.h diff --git a/applications/gui/modules/byte_input.c b/applications/services/gui/modules/byte_input.c similarity index 100% rename from applications/gui/modules/byte_input.c rename to applications/services/gui/modules/byte_input.c diff --git a/applications/gui/modules/byte_input.h b/applications/services/gui/modules/byte_input.h similarity index 100% rename from applications/gui/modules/byte_input.h rename to applications/services/gui/modules/byte_input.h diff --git a/applications/gui/modules/dialog_ex.c b/applications/services/gui/modules/dialog_ex.c similarity index 100% rename from applications/gui/modules/dialog_ex.c rename to applications/services/gui/modules/dialog_ex.c diff --git a/applications/gui/modules/dialog_ex.h b/applications/services/gui/modules/dialog_ex.h similarity index 100% rename from applications/gui/modules/dialog_ex.h rename to applications/services/gui/modules/dialog_ex.h diff --git a/applications/gui/modules/empty_screen.c b/applications/services/gui/modules/empty_screen.c similarity index 100% rename from applications/gui/modules/empty_screen.c rename to applications/services/gui/modules/empty_screen.c diff --git a/applications/gui/modules/empty_screen.h b/applications/services/gui/modules/empty_screen.h similarity index 100% rename from applications/gui/modules/empty_screen.h rename to applications/services/gui/modules/empty_screen.h diff --git a/applications/gui/modules/file_browser.c b/applications/services/gui/modules/file_browser.c similarity index 85% rename from applications/gui/modules/file_browser.c rename to applications/services/gui/modules/file_browser.c index a987eb1ef7d..1c0c8c74cb7 100644 --- a/applications/gui/modules/file_browser.c +++ b/applications/services/gui/modules/file_browser.c @@ -16,7 +16,9 @@ #define FRAME_HEIGHT 12 #define Y_OFFSET 3 -#define ITEM_LIST_LEN_MAX 100 +#define ITEM_LIST_LEN_MAX 50 + +#define CUSTOM_ICON_MAX_SIZE 32 typedef enum { BrowserItemTypeLoading, @@ -28,25 +30,47 @@ typedef enum { typedef struct { string_t path; BrowserItemType type; + uint8_t* custom_icon_data; + string_t display_name; } BrowserItem_t; static void BrowserItem_t_init(BrowserItem_t* obj) { obj->type = BrowserItemTypeLoading; string_init(obj->path); + string_init(obj->display_name); + obj->custom_icon_data = NULL; } static void BrowserItem_t_init_set(BrowserItem_t* obj, const BrowserItem_t* src) { obj->type = src->type; string_init_set(obj->path, src->path); + string_init_set(obj->display_name, src->display_name); + if(src->custom_icon_data) { + obj->custom_icon_data = malloc(CUSTOM_ICON_MAX_SIZE); + memcpy(obj->custom_icon_data, src->custom_icon_data, CUSTOM_ICON_MAX_SIZE); + } else { + obj->custom_icon_data = NULL; + } } static void BrowserItem_t_set(BrowserItem_t* obj, const BrowserItem_t* src) { obj->type = src->type; string_set(obj->path, src->path); + string_set(obj->display_name, src->display_name); + if(src->custom_icon_data) { + obj->custom_icon_data = malloc(CUSTOM_ICON_MAX_SIZE); + memcpy(obj->custom_icon_data, src->custom_icon_data, CUSTOM_ICON_MAX_SIZE); + } else { + obj->custom_icon_data = NULL; + } } static void BrowserItem_t_clear(BrowserItem_t* obj) { string_clear(obj->path); + string_clear(obj->display_name); + if(obj->custom_icon_data) { + free(obj->custom_icon_data); + } } ARRAY_DEF( @@ -62,10 +86,14 @@ struct FileBrowser { BrowserWorker* worker; const char* ext_filter; bool skip_assets; + bool hide_ext; FileBrowserCallback callback; void* context; + FileBrowserLoadItemCallback item_callback; + void* item_context; + string_ptr result_path; }; @@ -148,6 +176,7 @@ void file_browser_configure( browser->ext_filter = extension; browser->skip_assets = skip_assets; + browser->hide_ext = hide_ext; with_view_model( browser->view, (FileBrowserModel * model) { @@ -186,6 +215,14 @@ void file_browser_set_callback(FileBrowser* browser, FileBrowserCallback callbac browser->callback = callback; } +void file_browser_set_item_callback( + FileBrowser* browser, + FileBrowserLoadItemCallback callback, + void* context) { + browser->item_context = context; + browser->item_callback = callback; +} + static bool browser_is_item_in_array(FileBrowserModel* model, uint32_t idx) { size_t array_size = items_array_size(model->items); @@ -304,18 +341,43 @@ static void browser_list_item_cb(void* context, string_t item_path, bool is_fold FileBrowser* browser = (FileBrowser*)context; BrowserItem_t item; + item.custom_icon_data = NULL; if(!is_last) { - BrowserItem_t_init(&item); - string_set(item.path, item_path); - item.type = (is_folder) ? BrowserItemTypeFolder : BrowserItemTypeFile; + string_init_set(item.path, item_path); + string_init(item.display_name); + if(is_folder) { + item.type = BrowserItemTypeFolder; + } else { + item.type = BrowserItemTypeFile; + if(browser->item_callback) { + item.custom_icon_data = malloc(CUSTOM_ICON_MAX_SIZE); + if(!browser->item_callback( + item_path, + browser->item_context, + &item.custom_icon_data, + item.display_name)) { + free(item.custom_icon_data); + item.custom_icon_data = NULL; + } + } + } + + if(string_empty_p(item.display_name)) { + path_extract_filename( + item_path, + item.display_name, + (browser->hide_ext) && (item.type == BrowserItemTypeFile)); + } with_view_model( browser->view, (FileBrowserModel * model) { items_array_push_back(model->items, item); - return false; + // TODO: calculate if element is visible + return true; }); - BrowserItem_t_clear(&item); + string_clear(item.display_name); + string_clear(item.path); } else { with_view_model( browser->view, (FileBrowserModel * model) { @@ -372,13 +434,16 @@ static void browser_draw_list(Canvas* canvas, FileBrowserModel* model) { int32_t idx = CLAMP((uint32_t)(i + model->list_offset), model->item_cnt, 0u); BrowserItemType item_type = BrowserItemTypeLoading; + uint8_t* custom_icon_data = NULL; 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)); + string_set(filename, item->display_name); + if(item_type == BrowserItemTypeFile) { + custom_icon_data = item->custom_icon_data; + } } else { string_set_str(filename, "---"); } @@ -396,7 +461,11 @@ static void browser_draw_list(Canvas* canvas, FileBrowserModel* model) { canvas_set_color(canvas, ColorBlack); } - if((item_type == BrowserItemTypeFile) && (model->file_icon)) { + if(custom_icon_data) { + // Currently only 10*10 icons are supported + canvas_draw_bitmap( + canvas, 2, Y_OFFSET + 1 + i * FRAME_HEIGHT, 10, 10, custom_icon_data); + } else 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( diff --git a/applications/gui/modules/file_browser.h b/applications/services/gui/modules/file_browser.h similarity index 76% rename from applications/gui/modules/file_browser.h rename to applications/services/gui/modules/file_browser.h index ebc64509afe..57a9f096a9e 100644 --- a/applications/gui/modules/file_browser.h +++ b/applications/services/gui/modules/file_browser.h @@ -15,6 +15,9 @@ extern "C" { typedef struct FileBrowser FileBrowser; typedef void (*FileBrowserCallback)(void* context); +typedef bool ( + *FileBrowserLoadItemCallback)(string_t path, void* context, uint8_t** icon, string_t item_name); + FileBrowser* file_browser_alloc(string_ptr result_path); void file_browser_free(FileBrowser* browser); @@ -34,6 +37,11 @@ void file_browser_stop(FileBrowser* browser); void file_browser_set_callback(FileBrowser* browser, FileBrowserCallback callback, void* context); +void file_browser_set_item_callback( + FileBrowser* browser, + FileBrowserLoadItemCallback callback, + void* context); + #ifdef __cplusplus } #endif diff --git a/applications/gui/modules/file_browser_worker.c b/applications/services/gui/modules/file_browser_worker.c similarity index 100% rename from applications/gui/modules/file_browser_worker.c rename to applications/services/gui/modules/file_browser_worker.c diff --git a/applications/gui/modules/file_browser_worker.h b/applications/services/gui/modules/file_browser_worker.h similarity index 100% rename from applications/gui/modules/file_browser_worker.h rename to applications/services/gui/modules/file_browser_worker.h diff --git a/applications/gui/modules/loading.c b/applications/services/gui/modules/loading.c similarity index 100% rename from applications/gui/modules/loading.c rename to applications/services/gui/modules/loading.c diff --git a/applications/gui/modules/loading.h b/applications/services/gui/modules/loading.h similarity index 100% rename from applications/gui/modules/loading.h rename to applications/services/gui/modules/loading.h diff --git a/applications/gui/modules/menu.c b/applications/services/gui/modules/menu.c similarity index 100% rename from applications/gui/modules/menu.c rename to applications/services/gui/modules/menu.c diff --git a/applications/gui/modules/menu.h b/applications/services/gui/modules/menu.h similarity index 100% rename from applications/gui/modules/menu.h rename to applications/services/gui/modules/menu.h diff --git a/applications/gui/modules/popup.c b/applications/services/gui/modules/popup.c similarity index 100% rename from applications/gui/modules/popup.c rename to applications/services/gui/modules/popup.c diff --git a/applications/gui/modules/popup.h b/applications/services/gui/modules/popup.h similarity index 100% rename from applications/gui/modules/popup.h rename to applications/services/gui/modules/popup.h diff --git a/applications/gui/modules/submenu.c b/applications/services/gui/modules/submenu.c similarity index 100% rename from applications/gui/modules/submenu.c rename to applications/services/gui/modules/submenu.c diff --git a/applications/gui/modules/submenu.h b/applications/services/gui/modules/submenu.h similarity index 100% rename from applications/gui/modules/submenu.h rename to applications/services/gui/modules/submenu.h diff --git a/applications/gui/modules/text_box.c b/applications/services/gui/modules/text_box.c similarity index 100% rename from applications/gui/modules/text_box.c rename to applications/services/gui/modules/text_box.c diff --git a/applications/gui/modules/text_box.h b/applications/services/gui/modules/text_box.h similarity index 100% rename from applications/gui/modules/text_box.h rename to applications/services/gui/modules/text_box.h diff --git a/applications/gui/modules/text_input.c b/applications/services/gui/modules/text_input.c similarity index 100% rename from applications/gui/modules/text_input.c rename to applications/services/gui/modules/text_input.c diff --git a/applications/gui/modules/text_input.h b/applications/services/gui/modules/text_input.h similarity index 100% rename from applications/gui/modules/text_input.h rename to applications/services/gui/modules/text_input.h diff --git a/applications/gui/modules/validators.c b/applications/services/gui/modules/validators.c similarity index 97% rename from applications/gui/modules/validators.c rename to applications/services/gui/modules/validators.c index 52f9946ca2c..d5fb0fa22cd 100644 --- a/applications/gui/modules/validators.c +++ b/applications/services/gui/modules/validators.c @@ -1,6 +1,6 @@ #include #include "validators.h" -#include "applications/storage/storage.h" +#include struct ValidatorIsFile { char* app_path_folder; diff --git a/applications/gui/modules/validators.h b/applications/services/gui/modules/validators.h similarity index 100% rename from applications/gui/modules/validators.h rename to applications/services/gui/modules/validators.h diff --git a/applications/gui/modules/variable_item_list.c b/applications/services/gui/modules/variable_item_list.c similarity index 100% rename from applications/gui/modules/variable_item_list.c rename to applications/services/gui/modules/variable_item_list.c diff --git a/applications/gui/modules/variable_item_list.h b/applications/services/gui/modules/variable_item_list.h similarity index 100% rename from applications/gui/modules/variable_item_list.h rename to applications/services/gui/modules/variable_item_list.h diff --git a/applications/gui/modules/widget.c b/applications/services/gui/modules/widget.c similarity index 99% rename from applications/gui/modules/widget.c rename to applications/services/gui/modules/widget.c index b37a64701f7..802f76e243f 100644 --- a/applications/gui/modules/widget.c +++ b/applications/services/gui/modules/widget.c @@ -1,6 +1,7 @@ #include #include "widget.h" #include +#include "widget_elements/widget_element_i.h" ARRAY_DEF(ElementArray, WidgetElement*, M_PTR_OPLIST); diff --git a/applications/gui/modules/widget.h b/applications/services/gui/modules/widget.h similarity index 98% rename from applications/gui/modules/widget.h rename to applications/services/gui/modules/widget.h index 03586165c2d..50c26175169 100644 --- a/applications/gui/modules/widget.h +++ b/applications/services/gui/modules/widget.h @@ -4,8 +4,8 @@ */ #pragma once - -#include "widget_elements/widget_element_i.h" +#include +#include "widget_elements/widget_element.h" #ifdef __cplusplus extern "C" { diff --git a/applications/services/gui/modules/widget_elements/widget_element.h b/applications/services/gui/modules/widget_elements/widget_element.h new file mode 100644 index 00000000000..67990417387 --- /dev/null +++ b/applications/services/gui/modules/widget_elements/widget_element.h @@ -0,0 +1,22 @@ +/** + * @file widget_element_i.h + * GUI: internal Widget Element API + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + GuiButtonTypeLeft, + GuiButtonTypeCenter, + GuiButtonTypeRight, +} GuiButtonType; + +typedef void (*ButtonCallback)(GuiButtonType result, InputType type, void* context); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/applications/gui/modules/widget_elements/widget_element_button.c b/applications/services/gui/modules/widget_elements/widget_element_button.c similarity index 100% rename from applications/gui/modules/widget_elements/widget_element_button.c rename to applications/services/gui/modules/widget_elements/widget_element_button.c diff --git a/applications/gui/modules/widget_elements/widget_element_frame.c b/applications/services/gui/modules/widget_elements/widget_element_frame.c similarity index 100% rename from applications/gui/modules/widget_elements/widget_element_frame.c rename to applications/services/gui/modules/widget_elements/widget_element_frame.c diff --git a/applications/gui/modules/widget_elements/widget_element_i.h b/applications/services/gui/modules/widget_elements/widget_element_i.h similarity index 90% rename from applications/gui/modules/widget_elements/widget_element_i.h rename to applications/services/gui/modules/widget_elements/widget_element_i.h index 316ed740069..67dea4b1f69 100644 --- a/applications/gui/modules/widget_elements/widget_element_i.h +++ b/applications/services/gui/modules/widget_elements/widget_element_i.h @@ -7,14 +7,11 @@ #include #include #include +#include "widget_element.h" -typedef enum { - GuiButtonTypeLeft, - GuiButtonTypeCenter, - GuiButtonTypeRight, -} GuiButtonType; - -typedef void (*ButtonCallback)(GuiButtonType result, InputType type, void* context); +#ifdef __cplusplus +extern "C" { +#endif typedef struct WidgetElement WidgetElement; typedef struct Widget Widget; @@ -88,3 +85,7 @@ WidgetElement* widget_element_text_scroll_create( uint8_t width, uint8_t height, const char* text); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/applications/gui/modules/widget_elements/widget_element_icon.c b/applications/services/gui/modules/widget_elements/widget_element_icon.c similarity index 100% rename from applications/gui/modules/widget_elements/widget_element_icon.c rename to applications/services/gui/modules/widget_elements/widget_element_icon.c diff --git a/applications/gui/modules/widget_elements/widget_element_string.c b/applications/services/gui/modules/widget_elements/widget_element_string.c similarity index 100% rename from applications/gui/modules/widget_elements/widget_element_string.c rename to applications/services/gui/modules/widget_elements/widget_element_string.c diff --git a/applications/gui/modules/widget_elements/widget_element_string_multiline.c b/applications/services/gui/modules/widget_elements/widget_element_string_multiline.c similarity index 100% rename from applications/gui/modules/widget_elements/widget_element_string_multiline.c rename to applications/services/gui/modules/widget_elements/widget_element_string_multiline.c diff --git a/applications/gui/modules/widget_elements/widget_element_text_box.c b/applications/services/gui/modules/widget_elements/widget_element_text_box.c similarity index 100% rename from applications/gui/modules/widget_elements/widget_element_text_box.c rename to applications/services/gui/modules/widget_elements/widget_element_text_box.c diff --git a/applications/gui/modules/widget_elements/widget_element_text_scroll.c b/applications/services/gui/modules/widget_elements/widget_element_text_scroll.c similarity index 100% rename from applications/gui/modules/widget_elements/widget_element_text_scroll.c rename to applications/services/gui/modules/widget_elements/widget_element_text_scroll.c diff --git a/applications/gui/scene_manager.c b/applications/services/gui/scene_manager.c similarity index 100% rename from applications/gui/scene_manager.c rename to applications/services/gui/scene_manager.c diff --git a/applications/gui/scene_manager.h b/applications/services/gui/scene_manager.h similarity index 100% rename from applications/gui/scene_manager.h rename to applications/services/gui/scene_manager.h diff --git a/applications/gui/scene_manager_i.h b/applications/services/gui/scene_manager_i.h similarity index 100% rename from applications/gui/scene_manager_i.h rename to applications/services/gui/scene_manager_i.h diff --git a/applications/gui/view.c b/applications/services/gui/view.c similarity index 100% rename from applications/gui/view.c rename to applications/services/gui/view.c diff --git a/applications/gui/view.h b/applications/services/gui/view.h similarity index 100% rename from applications/gui/view.h rename to applications/services/gui/view.h diff --git a/applications/gui/view_dispatcher.c b/applications/services/gui/view_dispatcher.c similarity index 100% rename from applications/gui/view_dispatcher.c rename to applications/services/gui/view_dispatcher.c diff --git a/applications/gui/view_dispatcher.h b/applications/services/gui/view_dispatcher.h similarity index 100% rename from applications/gui/view_dispatcher.h rename to applications/services/gui/view_dispatcher.h diff --git a/applications/gui/view_dispatcher_i.h b/applications/services/gui/view_dispatcher_i.h similarity index 100% rename from applications/gui/view_dispatcher_i.h rename to applications/services/gui/view_dispatcher_i.h diff --git a/applications/gui/view_i.h b/applications/services/gui/view_i.h similarity index 100% rename from applications/gui/view_i.h rename to applications/services/gui/view_i.h diff --git a/applications/gui/view_port.c b/applications/services/gui/view_port.c similarity index 100% rename from applications/gui/view_port.c rename to applications/services/gui/view_port.c diff --git a/applications/gui/view_port.h b/applications/services/gui/view_port.h similarity index 100% rename from applications/gui/view_port.h rename to applications/services/gui/view_port.h diff --git a/applications/gui/view_port_i.h b/applications/services/gui/view_port_i.h similarity index 100% rename from applications/gui/view_port_i.h rename to applications/services/gui/view_port_i.h diff --git a/applications/gui/view_stack.c b/applications/services/gui/view_stack.c similarity index 100% rename from applications/gui/view_stack.c rename to applications/services/gui/view_stack.c diff --git a/applications/gui/view_stack.h b/applications/services/gui/view_stack.h similarity index 100% rename from applications/gui/view_stack.h rename to applications/services/gui/view_stack.h diff --git a/applications/input/application.fam b/applications/services/input/application.fam similarity index 86% rename from applications/input/application.fam rename to applications/services/input/application.fam index 545630e6ef3..d344fc350a8 100644 --- a/applications/input/application.fam +++ b/applications/services/input/application.fam @@ -6,4 +6,5 @@ App( cdefines=["SRV_INPUT"], stack_size=1 * 1024, order=80, + sdk_headers=["input.h"], ) diff --git a/applications/input/input.c b/applications/services/input/input.c similarity index 100% rename from applications/input/input.c rename to applications/services/input/input.c diff --git a/applications/input/input.h b/applications/services/input/input.h similarity index 94% rename from applications/input/input.h rename to applications/services/input/input.h index 001ab1e2e83..bd0ba390201 100644 --- a/applications/input/input.h +++ b/applications/services/input/input.h @@ -7,6 +7,10 @@ #include +#ifdef __cplusplus +extern "C" { +#endif + #define RECORD_INPUT_EVENTS "input_events" /** Input Types @@ -38,3 +42,7 @@ const char* input_get_key_name(InputKey key); * @return string */ const char* input_get_type_name(InputType type); + +#ifdef __cplusplus +} +#endif diff --git a/applications/input/input_cli.c b/applications/services/input/input_cli.c similarity index 100% rename from applications/input/input_cli.c rename to applications/services/input/input_cli.c diff --git a/applications/input/input_i.h b/applications/services/input/input_i.h similarity index 100% rename from applications/input/input_i.h rename to applications/services/input/input_i.h diff --git a/applications/loader/application.fam b/applications/services/loader/application.fam similarity index 87% rename from applications/loader/application.fam rename to applications/services/loader/application.fam index c1ba4e5490b..91103e46e3c 100644 --- a/applications/loader/application.fam +++ b/applications/services/loader/application.fam @@ -7,4 +7,5 @@ App( requires=["gui"], stack_size=2 * 1024, order=90, + sdk_headers=["loader.h"], ) diff --git a/applications/loader/loader.c b/applications/services/loader/loader.c similarity index 99% rename from applications/loader/loader.c rename to applications/services/loader/loader.c index 9ece7f27144..cf7e5a1084e 100644 --- a/applications/loader/loader.c +++ b/applications/services/loader/loader.c @@ -388,7 +388,7 @@ static void loader_build_menu() { loader_submenu_callback, (void*)LoaderMenuViewPlugins); } - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug) && (FLIPPER_DEBUG_APPS_COUNT > 0)) { menu_add_item( loader_instance->primary_menu, "Debug Tools", diff --git a/applications/loader/loader.h b/applications/services/loader/loader.h similarity index 93% rename from applications/loader/loader.h rename to applications/services/loader/loader.h index 8f95d81b2be..954e79c572b 100644 --- a/applications/loader/loader.h +++ b/applications/services/loader/loader.h @@ -3,6 +3,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + #define RECORD_LOADER "loader" typedef struct Loader Loader; @@ -49,3 +53,7 @@ void loader_update_menu(); /** Show primary loader */ FuriPubSub* loader_get_pubsub(Loader* instance); + +#ifdef __cplusplus +} +#endif diff --git a/applications/loader/loader_i.h b/applications/services/loader/loader_i.h similarity index 100% rename from applications/loader/loader_i.h rename to applications/services/loader/loader_i.h diff --git a/applications/notification/application.fam b/applications/services/notification/application.fam similarity index 54% rename from applications/notification/application.fam rename to applications/services/notification/application.fam index be730d51000..82f94085a72 100644 --- a/applications/notification/application.fam +++ b/applications/services/notification/application.fam @@ -8,14 +8,5 @@ App( provides=["notification_settings"], stack_size=int(1.5 * 1024), order=100, -) - -App( - appid="notification_settings", - name="LCD and Notifications", - apptype=FlipperAppType.SETTINGS, - entry_point="notification_settings_app", - requires=["notification"], - stack_size=1 * 1024, - order=20, + sdk_headers=["notification.h", "notification_messages.h"], ) diff --git a/applications/notification/notification.h b/applications/services/notification/notification.h similarity index 100% rename from applications/notification/notification.h rename to applications/services/notification/notification.h diff --git a/applications/notification/notification_app.c b/applications/services/notification/notification_app.c similarity index 100% rename from applications/notification/notification_app.c rename to applications/services/notification/notification_app.c diff --git a/applications/notification/notification_app.h b/applications/services/notification/notification_app.h similarity index 100% rename from applications/notification/notification_app.h rename to applications/services/notification/notification_app.h diff --git a/applications/notification/notification_app_api.c b/applications/services/notification/notification_app_api.c similarity index 100% rename from applications/notification/notification_app_api.c rename to applications/services/notification/notification_app_api.c diff --git a/applications/notification/notification_messages.c b/applications/services/notification/notification_messages.c similarity index 100% rename from applications/notification/notification_messages.c rename to applications/services/notification/notification_messages.c diff --git a/applications/notification/notification_messages.h b/applications/services/notification/notification_messages.h similarity index 100% rename from applications/notification/notification_messages.h rename to applications/services/notification/notification_messages.h diff --git a/applications/notification/notification_messages_notes.c b/applications/services/notification/notification_messages_notes.c similarity index 100% rename from applications/notification/notification_messages_notes.c rename to applications/services/notification/notification_messages_notes.c diff --git a/applications/notification/notification_messages_notes.h b/applications/services/notification/notification_messages_notes.h similarity index 100% rename from applications/notification/notification_messages_notes.h rename to applications/services/notification/notification_messages_notes.h diff --git a/applications/notification/notification_settings_filename.h b/applications/services/notification/notification_settings_filename.h similarity index 100% rename from applications/notification/notification_settings_filename.h rename to applications/services/notification/notification_settings_filename.h diff --git a/applications/services/power/application.fam b/applications/services/power/application.fam new file mode 100644 index 00000000000..f14d88c5426 --- /dev/null +++ b/applications/services/power/application.fam @@ -0,0 +1,26 @@ +App( + appid="power", + name="PowerSrv", + apptype=FlipperAppType.SERVICE, + entry_point="power_srv", + cdefines=["SRV_POWER"], + requires=[ + "gui", + "cli", + ], + provides=[ + "power_settings", + "power_start", + ], + stack_size=1 * 1024, + order=110, + sdk_headers=["power_service/power.h"], +) + +App( + appid="power_start", + apptype=FlipperAppType.STARTUP, + entry_point="power_on_system_start", + requires=["power"], + order=80, +) diff --git a/applications/power/power_cli.c b/applications/services/power/power_cli.c similarity index 100% rename from applications/power/power_cli.c rename to applications/services/power/power_cli.c diff --git a/applications/services/power/power_cli.h b/applications/services/power/power_cli.h new file mode 100644 index 00000000000..1517c61409f --- /dev/null +++ b/applications/services/power/power_cli.h @@ -0,0 +1,11 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +void power_on_system_start(); + +#ifdef __cplusplus +} +#endif diff --git a/applications/power/power_service/power.c b/applications/services/power/power_service/power.c similarity index 100% rename from applications/power/power_service/power.c rename to applications/services/power/power_service/power.c diff --git a/applications/power/power_service/power.h b/applications/services/power/power_service/power.h similarity index 96% rename from applications/power/power_service/power.h rename to applications/services/power/power_service/power.h index c516f28f6bf..b623452ee38 100644 --- a/applications/power/power_service/power.h +++ b/applications/services/power/power_service/power.h @@ -4,6 +4,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + #define RECORD_POWER "power" typedef struct Power Power; @@ -87,3 +91,7 @@ bool power_is_battery_healthy(Power* power); * @param enable true - enable, false - disable */ void power_enable_low_battery_level_notification(Power* power, bool enable); + +#ifdef __cplusplus +} +#endif diff --git a/applications/power/power_service/power_api.c b/applications/services/power/power_service/power_api.c similarity index 100% rename from applications/power/power_service/power_api.c rename to applications/services/power/power_service/power_api.c diff --git a/applications/power/power_service/power_i.h b/applications/services/power/power_service/power_i.h similarity index 100% rename from applications/power/power_service/power_i.h rename to applications/services/power/power_service/power_i.h diff --git a/applications/power/power_service/views/power_off.c b/applications/services/power/power_service/views/power_off.c similarity index 100% rename from applications/power/power_service/views/power_off.c rename to applications/services/power/power_service/views/power_off.c diff --git a/applications/power/power_service/views/power_off.h b/applications/services/power/power_service/views/power_off.h similarity index 100% rename from applications/power/power_service/views/power_off.h rename to applications/services/power/power_service/views/power_off.h diff --git a/applications/power/power_service/views/power_unplug_usb.c b/applications/services/power/power_service/views/power_unplug_usb.c similarity index 100% rename from applications/power/power_service/views/power_unplug_usb.c rename to applications/services/power/power_service/views/power_unplug_usb.c diff --git a/applications/power/power_service/views/power_unplug_usb.h b/applications/services/power/power_service/views/power_unplug_usb.h similarity index 100% rename from applications/power/power_service/views/power_unplug_usb.h rename to applications/services/power/power_service/views/power_unplug_usb.h diff --git a/applications/rpc/application.fam b/applications/services/rpc/application.fam similarity index 84% rename from applications/rpc/application.fam rename to applications/services/rpc/application.fam index 3a139cb3b09..7c0b6813369 100644 --- a/applications/rpc/application.fam +++ b/applications/services/rpc/application.fam @@ -5,4 +5,5 @@ App( cdefines=["SRV_RPC"], requires=["cli"], order=10, + sdk_headers=["rpc_app.h"], ) diff --git a/applications/rpc/rpc.c b/applications/services/rpc/rpc.c similarity index 100% rename from applications/rpc/rpc.c rename to applications/services/rpc/rpc.c diff --git a/applications/rpc/rpc.h b/applications/services/rpc/rpc.h similarity index 98% rename from applications/rpc/rpc.h rename to applications/services/rpc/rpc.h index dea8b749f10..40493949daf 100644 --- a/applications/rpc/rpc.h +++ b/applications/services/rpc/rpc.h @@ -5,6 +5,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + #define RPC_BUFFER_SIZE (1024) #define RPC_MAX_MESSAGE_SIZE (1536) @@ -114,3 +118,7 @@ size_t rpc_session_feed(RpcSession* session, uint8_t* buffer, size_t size, TickT * @return bytes available in buffer */ size_t rpc_session_get_available_size(RpcSession* session); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/applications/rpc/rpc_app.c b/applications/services/rpc/rpc_app.c similarity index 100% rename from applications/rpc/rpc_app.c rename to applications/services/rpc/rpc_app.c diff --git a/applications/rpc/rpc_app.h b/applications/services/rpc/rpc_app.h similarity index 100% rename from applications/rpc/rpc_app.h rename to applications/services/rpc/rpc_app.h diff --git a/applications/rpc/rpc_cli.c b/applications/services/rpc/rpc_cli.c similarity index 100% rename from applications/rpc/rpc_cli.c rename to applications/services/rpc/rpc_cli.c diff --git a/applications/rpc/rpc_debug.c b/applications/services/rpc/rpc_debug.c similarity index 100% rename from applications/rpc/rpc_debug.c rename to applications/services/rpc/rpc_debug.c diff --git a/applications/rpc/rpc_gpio.c b/applications/services/rpc/rpc_gpio.c similarity index 100% rename from applications/rpc/rpc_gpio.c rename to applications/services/rpc/rpc_gpio.c diff --git a/applications/rpc/rpc_gui.c b/applications/services/rpc/rpc_gui.c similarity index 100% rename from applications/rpc/rpc_gui.c rename to applications/services/rpc/rpc_gui.c diff --git a/applications/rpc/rpc_i.h b/applications/services/rpc/rpc_i.h similarity index 100% rename from applications/rpc/rpc_i.h rename to applications/services/rpc/rpc_i.h diff --git a/applications/rpc/rpc_storage.c b/applications/services/rpc/rpc_storage.c similarity index 100% rename from applications/rpc/rpc_storage.c rename to applications/services/rpc/rpc_storage.c diff --git a/applications/rpc/rpc_system.c b/applications/services/rpc/rpc_system.c similarity index 100% rename from applications/rpc/rpc_system.c rename to applications/services/rpc/rpc_system.c diff --git a/applications/storage/application.fam b/applications/services/storage/application.fam similarity index 92% rename from applications/storage/application.fam rename to applications/services/storage/application.fam index 21089635bd8..7aa721cc368 100644 --- a/applications/storage/application.fam +++ b/applications/services/storage/application.fam @@ -8,6 +8,7 @@ App( provides=["storage_start"], stack_size=3 * 1024, order=120, + sdk_headers=["storage.h"], ) App( diff --git a/applications/storage/filesystem_api.c b/applications/services/storage/filesystem_api.c similarity index 100% rename from applications/storage/filesystem_api.c rename to applications/services/storage/filesystem_api.c diff --git a/applications/storage/filesystem_api_defines.h b/applications/services/storage/filesystem_api_defines.h similarity index 100% rename from applications/storage/filesystem_api_defines.h rename to applications/services/storage/filesystem_api_defines.h diff --git a/applications/storage/filesystem_api_internal.h b/applications/services/storage/filesystem_api_internal.h similarity index 100% rename from applications/storage/filesystem_api_internal.h rename to applications/services/storage/filesystem_api_internal.h diff --git a/applications/storage/storage.c b/applications/services/storage/storage.c similarity index 100% rename from applications/storage/storage.c rename to applications/services/storage/storage.c diff --git a/applications/storage/storage.h b/applications/services/storage/storage.h similarity index 100% rename from applications/storage/storage.h rename to applications/services/storage/storage.h diff --git a/applications/storage/storage_cli.c b/applications/services/storage/storage_cli.c similarity index 100% rename from applications/storage/storage_cli.c rename to applications/services/storage/storage_cli.c diff --git a/applications/storage/storage_external_api.c b/applications/services/storage/storage_external_api.c similarity index 100% rename from applications/storage/storage_external_api.c rename to applications/services/storage/storage_external_api.c diff --git a/applications/storage/storage_glue.c b/applications/services/storage/storage_glue.c similarity index 100% rename from applications/storage/storage_glue.c rename to applications/services/storage/storage_glue.c diff --git a/applications/storage/storage_glue.h b/applications/services/storage/storage_glue.h similarity index 100% rename from applications/storage/storage_glue.h rename to applications/services/storage/storage_glue.h diff --git a/applications/storage/storage_i.h b/applications/services/storage/storage_i.h similarity index 100% rename from applications/storage/storage_i.h rename to applications/services/storage/storage_i.h diff --git a/applications/storage/storage_internal_api.c b/applications/services/storage/storage_internal_api.c similarity index 100% rename from applications/storage/storage_internal_api.c rename to applications/services/storage/storage_internal_api.c diff --git a/applications/storage/storage_message.h b/applications/services/storage/storage_message.h similarity index 100% rename from applications/storage/storage_message.h rename to applications/services/storage/storage_message.h diff --git a/applications/storage/storage_processing.c b/applications/services/storage/storage_processing.c similarity index 100% rename from applications/storage/storage_processing.c rename to applications/services/storage/storage_processing.c diff --git a/applications/storage/storage_processing.h b/applications/services/storage/storage_processing.h similarity index 100% rename from applications/storage/storage_processing.h rename to applications/services/storage/storage_processing.h diff --git a/applications/storage/storage_sd_api.c b/applications/services/storage/storage_sd_api.c similarity index 100% rename from applications/storage/storage_sd_api.c rename to applications/services/storage/storage_sd_api.c diff --git a/applications/storage/storage_sd_api.h b/applications/services/storage/storage_sd_api.h similarity index 100% rename from applications/storage/storage_sd_api.h rename to applications/services/storage/storage_sd_api.h diff --git a/applications/storage/storage_test_app.c b/applications/services/storage/storage_test_app.c similarity index 100% rename from applications/storage/storage_test_app.c rename to applications/services/storage/storage_test_app.c diff --git a/applications/storage/storages/sd_notify.c b/applications/services/storage/storages/sd_notify.c similarity index 100% rename from applications/storage/storages/sd_notify.c rename to applications/services/storage/storages/sd_notify.c diff --git a/applications/storage/storages/sd_notify.h b/applications/services/storage/storages/sd_notify.h similarity index 100% rename from applications/storage/storages/sd_notify.h rename to applications/services/storage/storages/sd_notify.h diff --git a/applications/storage/storages/storage_ext.c b/applications/services/storage/storages/storage_ext.c similarity index 100% rename from applications/storage/storages/storage_ext.c rename to applications/services/storage/storages/storage_ext.c diff --git a/applications/storage/storages/storage_ext.h b/applications/services/storage/storages/storage_ext.h similarity index 100% rename from applications/storage/storages/storage_ext.h rename to applications/services/storage/storages/storage_ext.h diff --git a/applications/storage/storages/storage_int.c b/applications/services/storage/storages/storage_int.c similarity index 100% rename from applications/storage/storages/storage_int.c rename to applications/services/storage/storages/storage_int.c diff --git a/applications/storage/storages/storage_int.h b/applications/services/storage/storages/storage_int.h similarity index 100% rename from applications/storage/storages/storage_int.h rename to applications/services/storage/storages/storage_int.h diff --git a/applications/about/about.c b/applications/settings/about/about.c similarity index 100% rename from applications/about/about.c rename to applications/settings/about/about.c diff --git a/applications/about/application.fam b/applications/settings/about/application.fam similarity index 100% rename from applications/about/application.fam rename to applications/settings/about/application.fam diff --git a/applications/settings/application.fam b/applications/settings/application.fam new file mode 100644 index 00000000000..cc4b9703dcb --- /dev/null +++ b/applications/settings/application.fam @@ -0,0 +1,10 @@ +App( + appid="settings_apps", + name="Basic settings apps bundle", + apptype=FlipperAppType.METAPACKAGE, + provides=[ + "passport", + "system_settings", + "about", + ], +) diff --git a/applications/settings/bt_settings_app/application.fam b/applications/settings/bt_settings_app/application.fam new file mode 100644 index 00000000000..7a985046b9e --- /dev/null +++ b/applications/settings/bt_settings_app/application.fam @@ -0,0 +1,12 @@ +App( + appid="bt_settings", + name="Bluetooth", + apptype=FlipperAppType.SETTINGS, + entry_point="bt_settings_app", + stack_size=1 * 1024, + requires=[ + "bt", + "gui", + ], + order=10, +) diff --git a/applications/bt/bt_settings_app/bt_settings_app.c b/applications/settings/bt_settings_app/bt_settings_app.c similarity index 100% rename from applications/bt/bt_settings_app/bt_settings_app.c rename to applications/settings/bt_settings_app/bt_settings_app.c diff --git a/applications/bt/bt_settings_app/bt_settings_app.h b/applications/settings/bt_settings_app/bt_settings_app.h similarity index 96% rename from applications/bt/bt_settings_app/bt_settings_app.h rename to applications/settings/bt_settings_app/bt_settings_app.h index 5b4a8d13928..c45ff3db0fb 100644 --- a/applications/bt/bt_settings_app/bt_settings_app.h +++ b/applications/settings/bt_settings_app/bt_settings_app.h @@ -11,7 +11,7 @@ #include #include -#include "../bt_settings.h" +#include #include "scenes/bt_settings_scene.h" enum BtSettingsCustomEvent { diff --git a/applications/bt/bt_settings_app/scenes/bt_settings_scene.c b/applications/settings/bt_settings_app/scenes/bt_settings_scene.c similarity index 100% rename from applications/bt/bt_settings_app/scenes/bt_settings_scene.c rename to applications/settings/bt_settings_app/scenes/bt_settings_scene.c diff --git a/applications/bt/bt_settings_app/scenes/bt_settings_scene.h b/applications/settings/bt_settings_app/scenes/bt_settings_scene.h similarity index 100% rename from applications/bt/bt_settings_app/scenes/bt_settings_scene.h rename to applications/settings/bt_settings_app/scenes/bt_settings_scene.h diff --git a/applications/bt/bt_settings_app/scenes/bt_settings_scene_config.h b/applications/settings/bt_settings_app/scenes/bt_settings_scene_config.h similarity index 100% rename from applications/bt/bt_settings_app/scenes/bt_settings_scene_config.h rename to applications/settings/bt_settings_app/scenes/bt_settings_scene_config.h diff --git a/applications/bt/bt_settings_app/scenes/bt_settings_scene_forget_dev_confirm.c b/applications/settings/bt_settings_app/scenes/bt_settings_scene_forget_dev_confirm.c similarity index 100% rename from applications/bt/bt_settings_app/scenes/bt_settings_scene_forget_dev_confirm.c rename to applications/settings/bt_settings_app/scenes/bt_settings_scene_forget_dev_confirm.c diff --git a/applications/bt/bt_settings_app/scenes/bt_settings_scene_forget_dev_success.c b/applications/settings/bt_settings_app/scenes/bt_settings_scene_forget_dev_success.c similarity index 100% rename from applications/bt/bt_settings_app/scenes/bt_settings_scene_forget_dev_success.c rename to applications/settings/bt_settings_app/scenes/bt_settings_scene_forget_dev_success.c diff --git a/applications/bt/bt_settings_app/scenes/bt_settings_scene_start.c b/applications/settings/bt_settings_app/scenes/bt_settings_scene_start.c similarity index 100% rename from applications/bt/bt_settings_app/scenes/bt_settings_scene_start.c rename to applications/settings/bt_settings_app/scenes/bt_settings_scene_start.c diff --git a/applications/settings/desktop_settings/application.fam b/applications/settings/desktop_settings/application.fam new file mode 100644 index 00000000000..d01a28d36f3 --- /dev/null +++ b/applications/settings/desktop_settings/application.fam @@ -0,0 +1,12 @@ +App( + appid="desktop_settings", + name="Desktop", + apptype=FlipperAppType.SETTINGS, + entry_point="desktop_settings_app", + requires=[ + "desktop", + "gui", + ], + stack_size=1 * 1024, + order=50, +) diff --git a/applications/desktop/desktop_settings/desktop_settings_app.c b/applications/settings/desktop_settings/desktop_settings_app.c similarity index 98% rename from applications/desktop/desktop_settings/desktop_settings_app.c rename to applications/settings/desktop_settings/desktop_settings_app.c index 89513a8b867..2b9593590f7 100644 --- a/applications/desktop/desktop_settings/desktop_settings_app.c +++ b/applications/settings/desktop_settings/desktop_settings_app.c @@ -4,7 +4,7 @@ #include "desktop_settings_app.h" #include "scenes/desktop_settings_scene.h" -#include "../views/desktop_view_pin_input.h" +#include static bool desktop_settings_custom_event_callback(void* context, uint32_t event) { furi_assert(context); diff --git a/applications/desktop/desktop_settings/desktop_settings_app.h b/applications/settings/desktop_settings/desktop_settings_app.h similarity index 92% rename from applications/desktop/desktop_settings/desktop_settings_app.h rename to applications/settings/desktop_settings/desktop_settings_app.h index 93ca7b35c75..25c5b3adb26 100644 --- a/applications/desktop/desktop_settings/desktop_settings_app.h +++ b/applications/settings/desktop_settings/desktop_settings_app.h @@ -7,8 +7,8 @@ #include #include -#include "desktop_settings.h" -#include "desktop/views/desktop_view_pin_input.h" +#include +#include #include "views/desktop_settings_view_pin_setup_howto.h" #include "views/desktop_settings_view_pin_setup_howto2.h" diff --git a/applications/desktop/desktop_settings/scenes/desktop_settings_scene.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene.c similarity index 100% rename from applications/desktop/desktop_settings/scenes/desktop_settings_scene.c rename to applications/settings/desktop_settings/scenes/desktop_settings_scene.c diff --git a/applications/desktop/desktop_settings/scenes/desktop_settings_scene.h b/applications/settings/desktop_settings/scenes/desktop_settings_scene.h similarity index 100% rename from applications/desktop/desktop_settings/scenes/desktop_settings_scene.h rename to applications/settings/desktop_settings/scenes/desktop_settings_scene.h diff --git a/applications/desktop/desktop_settings/scenes/desktop_settings_scene_config.h b/applications/settings/desktop_settings/scenes/desktop_settings_scene_config.h similarity index 100% rename from applications/desktop/desktop_settings/scenes/desktop_settings_scene_config.h rename to applications/settings/desktop_settings/scenes/desktop_settings_scene_config.h diff --git a/applications/desktop/desktop_settings/scenes/desktop_settings_scene_favorite.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c similarity index 100% rename from applications/desktop/desktop_settings/scenes/desktop_settings_scene_favorite.c rename to applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c diff --git a/applications/desktop/desktop_settings/scenes/desktop_settings_scene_i.h b/applications/settings/desktop_settings/scenes/desktop_settings_scene_i.h similarity index 100% rename from applications/desktop/desktop_settings/scenes/desktop_settings_scene_i.h rename to applications/settings/desktop_settings/scenes/desktop_settings_scene_i.h diff --git a/applications/desktop/desktop_settings/scenes/desktop_settings_scene_pin_auth.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_auth.c similarity index 96% rename from applications/desktop/desktop_settings/scenes/desktop_settings_scene_pin_auth.c rename to applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_auth.c index c43fff6ad55..ce191487d55 100644 --- a/applications/desktop/desktop_settings/scenes/desktop_settings_scene_pin_auth.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_auth.c @@ -1,10 +1,10 @@ #include #include #include -#include "../../helpers/pin_lock.h" +#include #include "../desktop_settings_app.h" -#include "desktop/desktop_settings/desktop_settings.h" -#include "desktop/views/desktop_view_pin_input.h" +#include +#include #include "desktop_settings_scene.h" #include "desktop_settings_scene_i.h" diff --git a/applications/desktop/desktop_settings/scenes/desktop_settings_scene_pin_disable.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_disable.c similarity index 95% rename from applications/desktop/desktop_settings/scenes/desktop_settings_scene_pin_disable.c rename to applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_disable.c index f6f6b2562c4..dcbba5df0bc 100644 --- a/applications/desktop/desktop_settings/scenes/desktop_settings_scene_pin_disable.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_disable.c @@ -4,8 +4,7 @@ #include #include "../desktop_settings_app.h" -#include "../desktop_settings.h" -#include "desktop/desktop_settings/desktop_settings.h" +#include #include "desktop_settings_scene.h" #define SCENE_EVENT_EXIT (0U) diff --git a/applications/desktop/desktop_settings/scenes/desktop_settings_scene_pin_error.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_error.c similarity index 95% rename from applications/desktop/desktop_settings/scenes/desktop_settings_scene_pin_error.c rename to applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_error.c index 38852dd8bd8..dd1e8579572 100644 --- a/applications/desktop/desktop_settings/scenes/desktop_settings_scene_pin_error.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_error.c @@ -2,11 +2,11 @@ #include #include -#include "desktop/desktop_settings/desktop_settings.h" -#include "desktop/views/desktop_view_pin_input.h" +#include +#include #include "desktop_settings_scene.h" #include "desktop_settings_scene_i.h" -#include "../../helpers/pin_lock.h" +#include #include "../desktop_settings_app.h" #define SCENE_EVENT_EXIT (0U) diff --git a/applications/desktop/desktop_settings/scenes/desktop_settings_scene_pin_menu.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_menu.c similarity index 100% rename from applications/desktop/desktop_settings/scenes/desktop_settings_scene_pin_menu.c rename to applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_menu.c diff --git a/applications/desktop/desktop_settings/scenes/desktop_settings_scene_pin_setup.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup.c similarity index 96% rename from applications/desktop/desktop_settings/scenes/desktop_settings_scene_pin_setup.c rename to applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup.c index b536a343a8f..bf0f48ae6ed 100644 --- a/applications/desktop/desktop_settings/scenes/desktop_settings_scene_pin_setup.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup.c @@ -3,11 +3,11 @@ #include #include "../desktop_settings_app.h" -#include "desktop/desktop_settings/desktop_settings.h" -#include "desktop/views/desktop_view_pin_input.h" +#include +#include #include "desktop_settings_scene.h" #include "desktop_settings_scene_i.h" -#include "../../helpers/pin_lock.h" +#include #define SCENE_EVENT_EXIT (0U) #define SCENE_EVENT_1ST_PIN_ENTERED (1U) diff --git a/applications/desktop/desktop_settings/scenes/desktop_settings_scene_pin_setup_done.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup_done.c similarity index 96% rename from applications/desktop/desktop_settings/scenes/desktop_settings_scene_pin_setup_done.c rename to applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup_done.c index 424084288dc..b2d07e99fa4 100644 --- a/applications/desktop/desktop_settings/scenes/desktop_settings_scene_pin_setup_done.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup_done.c @@ -6,8 +6,8 @@ #include #include "../desktop_settings_app.h" -#include "desktop/desktop_settings/desktop_settings.h" -#include "desktop/views/desktop_view_pin_input.h" +#include +#include #include "desktop_settings_scene.h" #define SCENE_EVENT_DONE (0U) diff --git a/applications/desktop/desktop_settings/scenes/desktop_settings_scene_pin_setup_howto.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup_howto.c similarity index 100% rename from applications/desktop/desktop_settings/scenes/desktop_settings_scene_pin_setup_howto.c rename to applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup_howto.c diff --git a/applications/desktop/desktop_settings/scenes/desktop_settings_scene_pin_setup_howto2.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup_howto2.c similarity index 100% rename from applications/desktop/desktop_settings/scenes/desktop_settings_scene_pin_setup_howto2.c rename to applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup_howto2.c diff --git a/applications/desktop/desktop_settings/scenes/desktop_settings_scene_start.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_start.c similarity index 100% rename from applications/desktop/desktop_settings/scenes/desktop_settings_scene_start.c rename to applications/settings/desktop_settings/scenes/desktop_settings_scene_start.c diff --git a/applications/desktop/desktop_settings/views/desktop_settings_view_pin_setup_howto.c b/applications/settings/desktop_settings/views/desktop_settings_view_pin_setup_howto.c similarity index 100% rename from applications/desktop/desktop_settings/views/desktop_settings_view_pin_setup_howto.c rename to applications/settings/desktop_settings/views/desktop_settings_view_pin_setup_howto.c diff --git a/applications/desktop/desktop_settings/views/desktop_settings_view_pin_setup_howto.h b/applications/settings/desktop_settings/views/desktop_settings_view_pin_setup_howto.h similarity index 100% rename from applications/desktop/desktop_settings/views/desktop_settings_view_pin_setup_howto.h rename to applications/settings/desktop_settings/views/desktop_settings_view_pin_setup_howto.h diff --git a/applications/desktop/desktop_settings/views/desktop_settings_view_pin_setup_howto2.c b/applications/settings/desktop_settings/views/desktop_settings_view_pin_setup_howto2.c similarity index 100% rename from applications/desktop/desktop_settings/views/desktop_settings_view_pin_setup_howto2.c rename to applications/settings/desktop_settings/views/desktop_settings_view_pin_setup_howto2.c diff --git a/applications/desktop/desktop_settings/views/desktop_settings_view_pin_setup_howto2.h b/applications/settings/desktop_settings/views/desktop_settings_view_pin_setup_howto2.h similarity index 100% rename from applications/desktop/desktop_settings/views/desktop_settings_view_pin_setup_howto2.h rename to applications/settings/desktop_settings/views/desktop_settings_view_pin_setup_howto2.h diff --git a/applications/dolphin/application.fam b/applications/settings/dolphin_passport/application.fam similarity index 56% rename from applications/dolphin/application.fam rename to applications/settings/dolphin_passport/application.fam index 1ee0e3cfe4a..4167e9dcd59 100644 --- a/applications/dolphin/application.fam +++ b/applications/settings/dolphin_passport/application.fam @@ -1,13 +1,3 @@ -App( - appid="dolphin", - name="DolphinSrv", - apptype=FlipperAppType.SERVICE, - entry_point="dolphin_srv", - cdefines=["SRV_DOLPHIN"], - stack_size=1 * 1024, - order=50, -) - App( appid="passport", name="Passport", diff --git a/applications/dolphin/passport/passport.c b/applications/settings/dolphin_passport/passport.c similarity index 100% rename from applications/dolphin/passport/passport.c rename to applications/settings/dolphin_passport/passport.c diff --git a/applications/settings/notification_settings/application.fam b/applications/settings/notification_settings/application.fam new file mode 100644 index 00000000000..117a83870ed --- /dev/null +++ b/applications/settings/notification_settings/application.fam @@ -0,0 +1,9 @@ +App( + appid="notification_settings", + name="LCD and Notifications", + apptype=FlipperAppType.SETTINGS, + entry_point="notification_settings_app", + requires=["notification"], + stack_size=1 * 1024, + order=20, +) diff --git a/applications/notification/notification_settings_app.c b/applications/settings/notification_settings/notification_settings_app.c similarity index 99% rename from applications/notification/notification_settings_app.c rename to applications/settings/notification_settings/notification_settings_app.c index 894938f4c34..d462163ad7e 100644 --- a/applications/notification/notification_settings_app.c +++ b/applications/settings/notification_settings/notification_settings_app.c @@ -1,5 +1,5 @@ #include -#include "notification_app.h" +#include #include #include #include diff --git a/applications/settings/power_settings_app/application.fam b/applications/settings/power_settings_app/application.fam new file mode 100644 index 00000000000..a5b1966b2bf --- /dev/null +++ b/applications/settings/power_settings_app/application.fam @@ -0,0 +1,13 @@ +App( + appid="power_settings", + name="Power", + apptype=FlipperAppType.SETTINGS, + entry_point="power_settings_app", + requires=[ + "gui", + "power", + ], + flags=["InsomniaSafe"], + stack_size=1 * 1024, + order=40, +) diff --git a/applications/power/power_settings_app/power_settings_app.c b/applications/settings/power_settings_app/power_settings_app.c similarity index 100% rename from applications/power/power_settings_app/power_settings_app.c rename to applications/settings/power_settings_app/power_settings_app.c diff --git a/applications/power/power_settings_app/power_settings_app.h b/applications/settings/power_settings_app/power_settings_app.h similarity index 100% rename from applications/power/power_settings_app/power_settings_app.h rename to applications/settings/power_settings_app/power_settings_app.h diff --git a/applications/power/power_settings_app/scenes/power_settings_scene.c b/applications/settings/power_settings_app/scenes/power_settings_scene.c similarity index 100% rename from applications/power/power_settings_app/scenes/power_settings_scene.c rename to applications/settings/power_settings_app/scenes/power_settings_scene.c diff --git a/applications/power/power_settings_app/scenes/power_settings_scene.h b/applications/settings/power_settings_app/scenes/power_settings_scene.h similarity index 100% rename from applications/power/power_settings_app/scenes/power_settings_scene.h rename to applications/settings/power_settings_app/scenes/power_settings_scene.h diff --git a/applications/power/power_settings_app/scenes/power_settings_scene_battery_info.c b/applications/settings/power_settings_app/scenes/power_settings_scene_battery_info.c similarity index 100% rename from applications/power/power_settings_app/scenes/power_settings_scene_battery_info.c rename to applications/settings/power_settings_app/scenes/power_settings_scene_battery_info.c diff --git a/applications/power/power_settings_app/scenes/power_settings_scene_config.h b/applications/settings/power_settings_app/scenes/power_settings_scene_config.h similarity index 100% rename from applications/power/power_settings_app/scenes/power_settings_scene_config.h rename to applications/settings/power_settings_app/scenes/power_settings_scene_config.h diff --git a/applications/power/power_settings_app/scenes/power_settings_scene_power_off.c b/applications/settings/power_settings_app/scenes/power_settings_scene_power_off.c similarity index 100% rename from applications/power/power_settings_app/scenes/power_settings_scene_power_off.c rename to applications/settings/power_settings_app/scenes/power_settings_scene_power_off.c diff --git a/applications/power/power_settings_app/scenes/power_settings_scene_reboot.c b/applications/settings/power_settings_app/scenes/power_settings_scene_reboot.c similarity index 100% rename from applications/power/power_settings_app/scenes/power_settings_scene_reboot.c rename to applications/settings/power_settings_app/scenes/power_settings_scene_reboot.c diff --git a/applications/power/power_settings_app/scenes/power_settings_scene_start.c b/applications/settings/power_settings_app/scenes/power_settings_scene_start.c similarity index 100% rename from applications/power/power_settings_app/scenes/power_settings_scene_start.c rename to applications/settings/power_settings_app/scenes/power_settings_scene_start.c diff --git a/applications/power/power_settings_app/views/battery_info.c b/applications/settings/power_settings_app/views/battery_info.c similarity index 100% rename from applications/power/power_settings_app/views/battery_info.c rename to applications/settings/power_settings_app/views/battery_info.c diff --git a/applications/power/power_settings_app/views/battery_info.h b/applications/settings/power_settings_app/views/battery_info.h similarity index 100% rename from applications/power/power_settings_app/views/battery_info.h rename to applications/settings/power_settings_app/views/battery_info.h diff --git a/applications/storage_settings/application.fam b/applications/settings/storage_settings/application.fam similarity index 100% rename from applications/storage_settings/application.fam rename to applications/settings/storage_settings/application.fam diff --git a/applications/storage_settings/scenes/storage_settings_scene.c b/applications/settings/storage_settings/scenes/storage_settings_scene.c similarity index 100% rename from applications/storage_settings/scenes/storage_settings_scene.c rename to applications/settings/storage_settings/scenes/storage_settings_scene.c diff --git a/applications/storage_settings/scenes/storage_settings_scene.h b/applications/settings/storage_settings/scenes/storage_settings_scene.h similarity index 100% rename from applications/storage_settings/scenes/storage_settings_scene.h rename to applications/settings/storage_settings/scenes/storage_settings_scene.h diff --git a/applications/storage_settings/scenes/storage_settings_scene_benchmark.c b/applications/settings/storage_settings/scenes/storage_settings_scene_benchmark.c similarity index 100% rename from applications/storage_settings/scenes/storage_settings_scene_benchmark.c rename to applications/settings/storage_settings/scenes/storage_settings_scene_benchmark.c diff --git a/applications/storage_settings/scenes/storage_settings_scene_config.h b/applications/settings/storage_settings/scenes/storage_settings_scene_config.h similarity index 100% rename from applications/storage_settings/scenes/storage_settings_scene_config.h rename to applications/settings/storage_settings/scenes/storage_settings_scene_config.h diff --git a/applications/storage_settings/scenes/storage_settings_scene_factory_reset.c b/applications/settings/storage_settings/scenes/storage_settings_scene_factory_reset.c similarity index 100% rename from applications/storage_settings/scenes/storage_settings_scene_factory_reset.c rename to applications/settings/storage_settings/scenes/storage_settings_scene_factory_reset.c diff --git a/applications/storage_settings/scenes/storage_settings_scene_format_confirm.c b/applications/settings/storage_settings/scenes/storage_settings_scene_format_confirm.c similarity index 100% rename from applications/storage_settings/scenes/storage_settings_scene_format_confirm.c rename to applications/settings/storage_settings/scenes/storage_settings_scene_format_confirm.c diff --git a/applications/storage_settings/scenes/storage_settings_scene_formatting.c b/applications/settings/storage_settings/scenes/storage_settings_scene_formatting.c similarity index 100% rename from applications/storage_settings/scenes/storage_settings_scene_formatting.c rename to applications/settings/storage_settings/scenes/storage_settings_scene_formatting.c diff --git a/applications/storage_settings/scenes/storage_settings_scene_internal_info.c b/applications/settings/storage_settings/scenes/storage_settings_scene_internal_info.c similarity index 100% rename from applications/storage_settings/scenes/storage_settings_scene_internal_info.c rename to applications/settings/storage_settings/scenes/storage_settings_scene_internal_info.c diff --git a/applications/storage_settings/scenes/storage_settings_scene_sd_info.c b/applications/settings/storage_settings/scenes/storage_settings_scene_sd_info.c similarity index 100% rename from applications/storage_settings/scenes/storage_settings_scene_sd_info.c rename to applications/settings/storage_settings/scenes/storage_settings_scene_sd_info.c diff --git a/applications/storage_settings/scenes/storage_settings_scene_start.c b/applications/settings/storage_settings/scenes/storage_settings_scene_start.c similarity index 100% rename from applications/storage_settings/scenes/storage_settings_scene_start.c rename to applications/settings/storage_settings/scenes/storage_settings_scene_start.c diff --git a/applications/storage_settings/scenes/storage_settings_scene_unmount_confirm.c b/applications/settings/storage_settings/scenes/storage_settings_scene_unmount_confirm.c similarity index 100% rename from applications/storage_settings/scenes/storage_settings_scene_unmount_confirm.c rename to applications/settings/storage_settings/scenes/storage_settings_scene_unmount_confirm.c diff --git a/applications/storage_settings/scenes/storage_settings_scene_unmounted.c b/applications/settings/storage_settings/scenes/storage_settings_scene_unmounted.c similarity index 100% rename from applications/storage_settings/scenes/storage_settings_scene_unmounted.c rename to applications/settings/storage_settings/scenes/storage_settings_scene_unmounted.c diff --git a/applications/storage_settings/storage_settings.c b/applications/settings/storage_settings/storage_settings.c similarity index 100% rename from applications/storage_settings/storage_settings.c rename to applications/settings/storage_settings/storage_settings.c diff --git a/applications/storage_settings/storage_settings.h b/applications/settings/storage_settings/storage_settings.h similarity index 100% rename from applications/storage_settings/storage_settings.h rename to applications/settings/storage_settings/storage_settings.h diff --git a/applications/settings/system/application.fam b/applications/settings/system/application.fam new file mode 100644 index 00000000000..0fc456b2f88 --- /dev/null +++ b/applications/settings/system/application.fam @@ -0,0 +1,9 @@ +App( + appid="system_settings", + name="System", + apptype=FlipperAppType.SETTINGS, + entry_point="system_settings_app", + requires=["gui"], + stack_size=1 * 1024, + order=70, +) diff --git a/applications/system/system_settings.c b/applications/settings/system/system_settings.c similarity index 100% rename from applications/system/system_settings.c rename to applications/settings/system/system_settings.c diff --git a/applications/system/system_settings.h b/applications/settings/system/system_settings.h similarity index 100% rename from applications/system/system_settings.h rename to applications/settings/system/system_settings.h diff --git a/applications/system/application.fam b/applications/system/application.fam index 0fc456b2f88..a59f840e456 100644 --- a/applications/system/application.fam +++ b/applications/system/application.fam @@ -1,9 +1,10 @@ App( - appid="system_settings", - name="System", - apptype=FlipperAppType.SETTINGS, - entry_point="system_settings_app", - requires=["gui"], - stack_size=1 * 1024, - order=70, + appid="system_apps", + name="Applications not shown in menus", + apptype=FlipperAppType.METAPACKAGE, + provides=[ + "updater_app", + "storage_move_to_sd", + # "archive", + ], ) diff --git a/applications/storage_move_to_sd/application.fam b/applications/system/storage_move_to_sd/application.fam similarity index 100% rename from applications/storage_move_to_sd/application.fam rename to applications/system/storage_move_to_sd/application.fam diff --git a/applications/storage_move_to_sd/scenes/storage_move_to_sd_scene.c b/applications/system/storage_move_to_sd/scenes/storage_move_to_sd_scene.c similarity index 100% rename from applications/storage_move_to_sd/scenes/storage_move_to_sd_scene.c rename to applications/system/storage_move_to_sd/scenes/storage_move_to_sd_scene.c diff --git a/applications/storage_move_to_sd/scenes/storage_move_to_sd_scene.h b/applications/system/storage_move_to_sd/scenes/storage_move_to_sd_scene.h similarity index 100% rename from applications/storage_move_to_sd/scenes/storage_move_to_sd_scene.h rename to applications/system/storage_move_to_sd/scenes/storage_move_to_sd_scene.h diff --git a/applications/storage_move_to_sd/scenes/storage_move_to_sd_scene_config.h b/applications/system/storage_move_to_sd/scenes/storage_move_to_sd_scene_config.h similarity index 100% rename from applications/storage_move_to_sd/scenes/storage_move_to_sd_scene_config.h rename to applications/system/storage_move_to_sd/scenes/storage_move_to_sd_scene_config.h diff --git a/applications/storage_move_to_sd/scenes/storage_move_to_sd_scene_confirm.c b/applications/system/storage_move_to_sd/scenes/storage_move_to_sd_scene_confirm.c similarity index 100% rename from applications/storage_move_to_sd/scenes/storage_move_to_sd_scene_confirm.c rename to applications/system/storage_move_to_sd/scenes/storage_move_to_sd_scene_confirm.c diff --git a/applications/storage_move_to_sd/scenes/storage_move_to_sd_scene_progress.c b/applications/system/storage_move_to_sd/scenes/storage_move_to_sd_scene_progress.c similarity index 100% rename from applications/storage_move_to_sd/scenes/storage_move_to_sd_scene_progress.c rename to applications/system/storage_move_to_sd/scenes/storage_move_to_sd_scene_progress.c diff --git a/applications/storage_move_to_sd/storage_move_to_sd.c b/applications/system/storage_move_to_sd/storage_move_to_sd.c similarity index 100% rename from applications/storage_move_to_sd/storage_move_to_sd.c rename to applications/system/storage_move_to_sd/storage_move_to_sd.c diff --git a/applications/storage_move_to_sd/storage_move_to_sd.h b/applications/system/storage_move_to_sd/storage_move_to_sd.h similarity index 100% rename from applications/storage_move_to_sd/storage_move_to_sd.h rename to applications/system/storage_move_to_sd/storage_move_to_sd.h diff --git a/applications/updater/application.fam b/applications/system/updater/application.fam similarity index 100% rename from applications/updater/application.fam rename to applications/system/updater/application.fam diff --git a/applications/updater/cli/updater_cli.c b/applications/system/updater/cli/updater_cli.c similarity index 100% rename from applications/updater/cli/updater_cli.c rename to applications/system/updater/cli/updater_cli.c diff --git a/applications/updater/scenes/updater_scene.c b/applications/system/updater/scenes/updater_scene.c similarity index 100% rename from applications/updater/scenes/updater_scene.c rename to applications/system/updater/scenes/updater_scene.c diff --git a/applications/updater/scenes/updater_scene.h b/applications/system/updater/scenes/updater_scene.h similarity index 100% rename from applications/updater/scenes/updater_scene.h rename to applications/system/updater/scenes/updater_scene.h diff --git a/applications/updater/scenes/updater_scene_config.h b/applications/system/updater/scenes/updater_scene_config.h similarity index 100% rename from applications/updater/scenes/updater_scene_config.h rename to applications/system/updater/scenes/updater_scene_config.h diff --git a/applications/updater/scenes/updater_scene_error.c b/applications/system/updater/scenes/updater_scene_error.c similarity index 98% rename from applications/updater/scenes/updater_scene_error.c rename to applications/system/updater/scenes/updater_scene_error.c index 362c471a681..21bf1637224 100644 --- a/applications/updater/scenes/updater_scene_error.c +++ b/applications/system/updater/scenes/updater_scene_error.c @@ -1,4 +1,4 @@ -#include "updater/updater_i.h" +#include "../updater_i.h" #include "updater_scene.h" #include diff --git a/applications/updater/scenes/updater_scene_loadcfg.c b/applications/system/updater/scenes/updater_scene_loadcfg.c similarity index 99% rename from applications/updater/scenes/updater_scene_loadcfg.c rename to applications/system/updater/scenes/updater_scene_loadcfg.c index 1fd87d002a9..c7f48c78b8f 100644 --- a/applications/updater/scenes/updater_scene_loadcfg.c +++ b/applications/system/updater/scenes/updater_scene_loadcfg.c @@ -1,4 +1,4 @@ -#include "updater/updater_i.h" +#include "../updater_i.h" #include "updater_scene.h" #include diff --git a/applications/updater/scenes/updater_scene_main.c b/applications/system/updater/scenes/updater_scene_main.c similarity index 98% rename from applications/updater/scenes/updater_scene_main.c rename to applications/system/updater/scenes/updater_scene_main.c index 5f7aeaca4b6..2ef0732ca27 100644 --- a/applications/updater/scenes/updater_scene_main.c +++ b/applications/system/updater/scenes/updater_scene_main.c @@ -3,8 +3,8 @@ #include #include -#include "updater/updater_i.h" -#include "updater/views/updater_main.h" +#include "../updater_i.h" +#include "../views/updater_main.h" #include "updater_scene.h" static void sd_mount_callback(const void* message, void* context) { diff --git a/applications/updater/updater.c b/applications/system/updater/updater.c similarity index 100% rename from applications/updater/updater.c rename to applications/system/updater/updater.c diff --git a/applications/updater/updater_i.h b/applications/system/updater/updater_i.h similarity index 100% rename from applications/updater/updater_i.h rename to applications/system/updater/updater_i.h diff --git a/applications/updater/util/update_task.c b/applications/system/updater/util/update_task.c similarity index 100% rename from applications/updater/util/update_task.c rename to applications/system/updater/util/update_task.c diff --git a/applications/updater/util/update_task.h b/applications/system/updater/util/update_task.h similarity index 100% rename from applications/updater/util/update_task.h rename to applications/system/updater/util/update_task.h diff --git a/applications/updater/util/update_task_i.h b/applications/system/updater/util/update_task_i.h similarity index 100% rename from applications/updater/util/update_task_i.h rename to applications/system/updater/util/update_task_i.h diff --git a/applications/updater/util/update_task_worker_backup.c b/applications/system/updater/util/update_task_worker_backup.c similarity index 100% rename from applications/updater/util/update_task_worker_backup.c rename to applications/system/updater/util/update_task_worker_backup.c diff --git a/applications/updater/util/update_task_worker_flasher.c b/applications/system/updater/util/update_task_worker_flasher.c similarity index 100% rename from applications/updater/util/update_task_worker_flasher.c rename to applications/system/updater/util/update_task_worker_flasher.c diff --git a/applications/updater/views/updater_main.c b/applications/system/updater/views/updater_main.c similarity index 100% rename from applications/updater/views/updater_main.c rename to applications/system/updater/views/updater_main.c diff --git a/applications/updater/views/updater_main.h b/applications/system/updater/views/updater_main.h similarity index 100% rename from applications/updater/views/updater_main.h rename to applications/system/updater/views/updater_main.h diff --git a/applications_user/.gitignore b/applications_user/.gitignore new file mode 100644 index 00000000000..72e8ffc0db8 --- /dev/null +++ b/applications_user/.gitignore @@ -0,0 +1 @@ +* diff --git a/applications_user/README.md b/applications_user/README.md new file mode 100644 index 00000000000..8bb7823c1a5 --- /dev/null +++ b/applications_user/README.md @@ -0,0 +1 @@ +Put your custom applications in this folder. \ No newline at end of file diff --git a/assets/.gitignore b/assets/.gitignore index eb20456f178..9bc0bdc0c73 100644 --- a/assets/.gitignore +++ b/assets/.gitignore @@ -1,2 +1,3 @@ -/headers /core2_firmware +/resources/Manifest +/resources/apps/* \ No newline at end of file diff --git a/assets/SConscript b/assets/SConscript index ecb1095679d..6328035faca 100644 --- a/assets/SConscript +++ b/assets/SConscript @@ -27,7 +27,9 @@ if not assetsenv["VERBOSE"]: icons_src = assetsenv.GlobRecursive("*.png", "icons") icons_src += assetsenv.GlobRecursive("frame_rate", "icons") -icons = assetsenv.IconBuilder(Dir("compiled"), Dir("#/assets/icons")) +icons = assetsenv.IconBuilder( + assetsenv.Dir("compiled"), ICON_SRC_DIR=assetsenv.Dir("#/assets/icons") +) assetsenv.Depends(icons, icons_src) assetsenv.Alias("icons", icons) @@ -92,7 +94,6 @@ if assetsenv["IS_BASE_FIRMWARE"]: assetsenv.Alias("dolphin_ext", dolphin_external) # Resources manifest - resources = assetsenv.Command( "#/assets/resources/Manifest", assetsenv.GlobRecursive("*", "resources", exclude="Manifest"), @@ -101,9 +102,11 @@ if assetsenv["IS_BASE_FIRMWARE"]: "${RESMANIFESTCOMSTR}", ), ) - assetsenv.Precious(resources) - assetsenv.NoClean(resources) assetsenv.AlwaysBuild(resources) + assetsenv.Clean( + resources, + assetsenv.Dir("#/assets/resources/apps"), + ) # Exporting resources node to external environment env["FW_RESOURCES"] = resources diff --git a/assets/resources/Manifest b/assets/resources/Manifest deleted file mode 100644 index 8bfdcef7e3b..00000000000 --- a/assets/resources/Manifest +++ /dev/null @@ -1,241 +0,0 @@ -V:0 -T:1660218073 -D:badusb -D:dolphin -D:infrared -D:music_player -D:nfc -D:subghz -D:u2f -F:0e41ba26498b7511d7c9e6e6b5e3b149:1592:badusb/demo_macos.txt -F:46a332993ca94b9aa692030ebaa19c70:1552:badusb/demo_windows.txt -D:dolphin/L1_Boxing_128x64 -D:dolphin/L1_Cry_128x64 -D:dolphin/L1_Furippa1_128x64 -D:dolphin/L1_Laptop_128x51 -D:dolphin/L1_Leaving_sad_128x64 -D:dolphin/L1_Mad_fist_128x64 -D:dolphin/L1_Read_books_128x64 -D:dolphin/L1_Recording_128x51 -D:dolphin/L1_Sleep_128x64 -D:dolphin/L1_Waves_128x50 -D:dolphin/L2_Furippa2_128x64 -D:dolphin/L2_Hacking_pc_128x64 -D:dolphin/L2_Soldering_128x64 -D:dolphin/L3_Furippa3_128x64 -D:dolphin/L3_Hijack_radio_128x64 -D:dolphin/L3_Lab_research_128x54 -F:d1148ab5354eaf4fa7f959589d840932:1563:dolphin/manifest.txt -F:d37be8444102ec5cde5fe3a85d55b57d:481:dolphin/L1_Boxing_128x64/frame_0.bm -F:54fb07443bc153ded9589b74d23b4263:461:dolphin/L1_Boxing_128x64/frame_1.bm -F:e007afe130d699c715b99ce8e5b407bd:531:dolphin/L1_Boxing_128x64/frame_2.bm -F:a999a9a6c76c66158f1aa5ccb56de7c9:437:dolphin/L1_Boxing_128x64/frame_3.bm -F:ec6af9cb451ab16c0fa62e95e8134b49:459:dolphin/L1_Boxing_128x64/frame_4.bm -F:2aa0c1e7bf1131b9dc172aa595ec01f2:450:dolphin/L1_Boxing_128x64/frame_5.bm -F:bbc8f750d17d156438c5cfe1122ec7f4:442:dolphin/L1_Boxing_128x64/frame_6.bm -F:f6e51ada3e3285e330714dab5b4277dd:418:dolphin/L1_Boxing_128x64/meta.txt -F:ab33a6f37209541f3db938d1cfe1706f:889:dolphin/L1_Cry_128x64/frame_0.bm -F:1b3fdeb404af0f7402caa5a5e091a8f8:911:dolphin/L1_Cry_128x64/frame_1.bm -F:4db644b173af72f3d371d2bd81f76b05:910:dolphin/L1_Cry_128x64/frame_2.bm -F:cd4c0ef67a8e514edecd9600242db068:923:dolphin/L1_Cry_128x64/frame_3.bm -F:ee02e9589e0714d3e2bc0d93aa294ccb:894:dolphin/L1_Cry_128x64/frame_4.bm -F:7703a7d9745d13b45d73ce4b86b4cdc8:940:dolphin/L1_Cry_128x64/frame_5.bm -F:ee6de6a0ed903317c4948cb445e0a9a8:915:dolphin/L1_Cry_128x64/frame_6.bm -F:a3892e45826c66f48d3d64fb81521446:934:dolphin/L1_Cry_128x64/frame_7.bm -F:680b12cc4dad722d6583b7e710bfc297:516:dolphin/L1_Cry_128x64/meta.txt -F:4911eaa7cb84ced19e5dea2af51b91a5:294:dolphin/L1_Furippa1_128x64/frame_0.bm -F:5669bee57c7b3d93a1665dd87fd5372a:325:dolphin/L1_Furippa1_128x64/frame_1.bm -F:80b48a77682b853e6236cd1c89083e6f:465:dolphin/L1_Furippa1_128x64/frame_10.bm -F:9d8ea10bf3d3831cb4a94957dc0b41c6:698:dolphin/L1_Furippa1_128x64/frame_11.bm -F:2dbb3125ea63550906fba8f7ec7b1da3:541:dolphin/L1_Furippa1_128x64/frame_12.bm -F:6a06b718957dca9caa63a4f3baa73abb:584:dolphin/L1_Furippa1_128x64/frame_13.bm -F:5450bf16c3d2fceaf5e6ea585b7ef7c1:610:dolphin/L1_Furippa1_128x64/frame_14.bm -F:535c0eca62703eb7df36f17334a6191b:719:dolphin/L1_Furippa1_128x64/frame_15.bm -F:7c03af85ade9b791755f3a4d106c2b7c:458:dolphin/L1_Furippa1_128x64/frame_16.bm -F:41b8fea16ad8705f4594e6119eade395:400:dolphin/L1_Furippa1_128x64/frame_17.bm -F:2db7fd3da5208a8e41902ae27cf41702:333:dolphin/L1_Furippa1_128x64/frame_18.bm -F:7e47428442e0f04959fc6afde979936e:351:dolphin/L1_Furippa1_128x64/frame_2.bm -F:0eb187078f169d7a852e97ecf430aea0:324:dolphin/L1_Furippa1_128x64/frame_3.bm -F:967c402971a442a5bf28eba804bb3ff4:387:dolphin/L1_Furippa1_128x64/frame_4.bm -F:175cb930fba0fc86f54a3a109b741708:390:dolphin/L1_Furippa1_128x64/frame_5.bm -F:f8c3ee1ab657549d1d00c1c72d8d2ff5:407:dolphin/L1_Furippa1_128x64/frame_6.bm -F:4911eaa7cb84ced19e5dea2af51b91a5:294:dolphin/L1_Furippa1_128x64/frame_7.bm -F:8f649ff34b224f4e564644a4494c54ed:283:dolphin/L1_Furippa1_128x64/frame_8.bm -F:3ec3c40d26bf8d3e691b1335d20d4ec0:312:dolphin/L1_Furippa1_128x64/frame_9.bm -F:ebe088426d184cf6651288accd21add6:241:dolphin/L1_Furippa1_128x64/meta.txt -F:d02fdfd1a3b89da00d2acf32bd09da80:555:dolphin/L1_Laptop_128x51/frame_0.bm -F:7e29ea503d41023fa3895d15458f106d:557:dolphin/L1_Laptop_128x51/frame_1.bm -F:eb55e0629de873f537d8412ced528eb4:560:dolphin/L1_Laptop_128x51/frame_2.bm -F:1516472ab3c140dd5bd4d089caa44747:556:dolphin/L1_Laptop_128x51/frame_3.bm -F:61172f89cf0a17bd7f978edccdeed166:560:dolphin/L1_Laptop_128x51/frame_4.bm -F:9d54913928c7e9477b6b8a43f3767621:554:dolphin/L1_Laptop_128x51/frame_5.bm -F:5243d6272bbb213e9c17af07ee011402:553:dolphin/L1_Laptop_128x51/frame_6.bm -F:aa68e0f28f117891ba0f4d7613224fc6:560:dolphin/L1_Laptop_128x51/frame_7.bm -F:9ef1935ab29fe70bbc517f4b602547d7:403:dolphin/L1_Laptop_128x51/meta.txt -F:6ce34e62c5bf4764a4163101afe63e60:514:dolphin/L1_Leaving_sad_128x64/frame_0.bm -F:19a0e0c518d222d91d24b8712ab6bb80:526:dolphin/L1_Leaving_sad_128x64/frame_1.bm -F:837bfb424c8d8a3bfbda7d6a28ba5a5c:316:dolphin/L1_Leaving_sad_128x64/frame_10.bm -F:1a69b6f63a96e0958837ea8b21db3966:294:dolphin/L1_Leaving_sad_128x64/frame_11.bm -F:c3ea827593a4563d544dfb7e99d73885:322:dolphin/L1_Leaving_sad_128x64/frame_12.bm -F:1e3842669191fe9599f830ac133e0751:542:dolphin/L1_Leaving_sad_128x64/frame_2.bm -F:9161660e6827bd776a15eefa2a8add19:557:dolphin/L1_Leaving_sad_128x64/frame_3.bm -F:d01a79fdb4f84397d82bf9927aeb71e0:488:dolphin/L1_Leaving_sad_128x64/frame_4.bm -F:316e30ef319c080fab2a79c21e526319:469:dolphin/L1_Leaving_sad_128x64/frame_5.bm -F:09a812d59b60b5fe7724057daa14ad60:499:dolphin/L1_Leaving_sad_128x64/frame_6.bm -F:9eb07b76cc864a0ce2918d68e41d4500:486:dolphin/L1_Leaving_sad_128x64/frame_7.bm -F:cf8c4cc4abbd700b096037b7ebfd0e31:403:dolphin/L1_Leaving_sad_128x64/frame_8.bm -F:889728ded689203aa82193e573912d18:317:dolphin/L1_Leaving_sad_128x64/frame_9.bm -F:2bff1f09ad1e9059a60e08990ca1d414:477:dolphin/L1_Leaving_sad_128x64/meta.txt -F:c31a882e95ed5c69fd63226db2188710:520:dolphin/L1_Mad_fist_128x64/frame_0.bm -F:740326828f6ba6e29373943ba835e77f:540:dolphin/L1_Mad_fist_128x64/frame_1.bm -F:0c9693dda040fd73ca6d773a10924bd8:542:dolphin/L1_Mad_fist_128x64/frame_10.bm -F:425c1d101debd1e9502db2628640b704:505:dolphin/L1_Mad_fist_128x64/frame_11.bm -F:aa576f7dbd14ec682f6c50314165fb14:501:dolphin/L1_Mad_fist_128x64/frame_12.bm -F:712335eabefb8c7bb7fb2f4301419c10:500:dolphin/L1_Mad_fist_128x64/frame_13.bm -F:b6e11711ea4dcc2e64f267d888f91baf:515:dolphin/L1_Mad_fist_128x64/frame_2.bm -F:61bdd22a2b1e67efe093b6acf7dfadce:538:dolphin/L1_Mad_fist_128x64/frame_3.bm -F:20ae06a3ce7a07656e578edb024e2b3f:512:dolphin/L1_Mad_fist_128x64/frame_4.bm -F:45cf2bd55365a7328df39fe98a496cc9:519:dolphin/L1_Mad_fist_128x64/frame_5.bm -F:4b8840eebb3a4a1ead69a7130816047e:524:dolphin/L1_Mad_fist_128x64/frame_6.bm -F:0de4497a5fbf80cc93e523465c5e3122:515:dolphin/L1_Mad_fist_128x64/frame_7.bm -F:32d8ddeb19bfa415fe283666b1e323a2:517:dolphin/L1_Mad_fist_128x64/frame_8.bm -F:a42a0578c2d0411500fb3485a3beb536:526:dolphin/L1_Mad_fist_128x64/frame_9.bm -F:10a521c78168a5928c859494e2a61cd2:349:dolphin/L1_Mad_fist_128x64/meta.txt -F:61565b7be9a69a60ce2dbae0273df347:653:dolphin/L1_Read_books_128x64/frame_0.bm -F:cf5a2d423540e3af37e789d70c9c1fbf:653:dolphin/L1_Read_books_128x64/frame_1.bm -F:c91935861979d024e6637b8810889878:650:dolphin/L1_Read_books_128x64/frame_2.bm -F:0c007a30f396f3e7a0ded2b24080357d:646:dolphin/L1_Read_books_128x64/frame_3.bm -F:323a52816dd79d6d3186f451e26e06ad:650:dolphin/L1_Read_books_128x64/frame_4.bm -F:494f27958f4cea9b94d09cf27725c5cd:652:dolphin/L1_Read_books_128x64/frame_5.bm -F:a6a7491fe80255e1745c9f293da52805:646:dolphin/L1_Read_books_128x64/frame_6.bm -F:238497e6643fd491cd6002e98c615c05:647:dolphin/L1_Read_books_128x64/frame_7.bm -F:300651e8f53d9a29ae38d4b9292c73cf:643:dolphin/L1_Read_books_128x64/frame_8.bm -F:3d9568deeff646b677092902a98f9ceb:325:dolphin/L1_Read_books_128x64/meta.txt -F:2aba555567ab70cff003ded4138c6721:663:dolphin/L1_Recording_128x51/frame_0.bm -F:8456c6e86825957e5662e2f08eb6c116:657:dolphin/L1_Recording_128x51/frame_1.bm -F:2e4a1aca5afa5a6ab254884210875eb4:629:dolphin/L1_Recording_128x51/frame_10.bm -F:9f1cf96598e3d935879b1d0c97705778:659:dolphin/L1_Recording_128x51/frame_11.bm -F:409abfeca974e5649affcd1faafea988:628:dolphin/L1_Recording_128x51/frame_2.bm -F:66b2a5abf05acbf79f9943e01b8b8cec:654:dolphin/L1_Recording_128x51/frame_3.bm -F:d55c5ed28c2ff48f42ab30b420d64fa3:662:dolphin/L1_Recording_128x51/frame_4.bm -F:2ce12d8cfdd953c9dadb9459c580a320:622:dolphin/L1_Recording_128x51/frame_5.bm -F:da631e3837fcdf3ee9e6abdf17fb764b:664:dolphin/L1_Recording_128x51/frame_6.bm -F:604a7cdac2491c9bc2e88b9e91c99dcc:626:dolphin/L1_Recording_128x51/frame_7.bm -F:fc94649dc98244dd9a0ab7fe62721d3c:663:dolphin/L1_Recording_128x51/frame_8.bm -F:b2475ab8ee26cbd9a403ee603520bd35:661:dolphin/L1_Recording_128x51/frame_9.bm -F:a7c2b3b420706712149cc2426c68df4f:219:dolphin/L1_Recording_128x51/meta.txt -F:9858fd34b55cebcb9be50c5710212a13:580:dolphin/L1_Sleep_128x64/frame_0.bm -F:e47ef8c846083b8fde028b1724861444:589:dolphin/L1_Sleep_128x64/frame_1.bm -F:9749bd05b47fd07cc3a41ab201f86bf4:582:dolphin/L1_Sleep_128x64/frame_2.bm -F:edf11266b20b846ace622e41cd36906b:597:dolphin/L1_Sleep_128x64/frame_3.bm -F:8fbb96a9d809d85fa6bad931fe4e6fe2:510:dolphin/L1_Sleep_128x64/meta.txt -F:283b41f1b2c581c510ff176293b7288a:443:dolphin/L1_Waves_128x50/frame_0.bm -F:c9fc5127e1d8a4217b6b177716725ba0:448:dolphin/L1_Waves_128x50/frame_1.bm -F:8e0797bf26d5d8d3cbeb99798c222b80:463:dolphin/L1_Waves_128x50/frame_2.bm -F:da02b1deb3119b31f2b8f182d5bf3242:472:dolphin/L1_Waves_128x50/frame_3.bm -F:8e6fb4133acbda7e5bb9adad0aed306c:620:dolphin/L1_Waves_128x50/meta.txt -F:be80d2fa903e3250b69c063a1eef0621:350:dolphin/L2_Furippa2_128x64/frame_0.bm -F:9e628f5e154f12d6c57b13befed1f5f6:385:dolphin/L2_Furippa2_128x64/frame_1.bm -F:80b48a77682b853e6236cd1c89083e6f:465:dolphin/L2_Furippa2_128x64/frame_10.bm -F:9d8ea10bf3d3831cb4a94957dc0b41c6:698:dolphin/L2_Furippa2_128x64/frame_11.bm -F:2dbb3125ea63550906fba8f7ec7b1da3:541:dolphin/L2_Furippa2_128x64/frame_12.bm -F:6a06b718957dca9caa63a4f3baa73abb:584:dolphin/L2_Furippa2_128x64/frame_13.bm -F:5450bf16c3d2fceaf5e6ea585b7ef7c1:610:dolphin/L2_Furippa2_128x64/frame_14.bm -F:e3c92103f403857b502081d3b058e53a:740:dolphin/L2_Furippa2_128x64/frame_15.bm -F:432669d796bbf7be1d14f5b7db036a92:533:dolphin/L2_Furippa2_128x64/frame_16.bm -F:53485c6b465c80a1ce8ddf03c4976039:451:dolphin/L2_Furippa2_128x64/frame_17.bm -F:333b75b16c088428a28259c931630fb9:397:dolphin/L2_Furippa2_128x64/frame_18.bm -F:ed02d68380382361f3f01cbf01d13b0c:402:dolphin/L2_Furippa2_128x64/frame_2.bm -F:b0ba042d7b60dc5681182b1d4005f0a2:374:dolphin/L2_Furippa2_128x64/frame_3.bm -F:518a84fa5a4e9e7f84246d5d82e87f15:440:dolphin/L2_Furippa2_128x64/frame_4.bm -F:9b7b0ae6f4f55d30cb43b0465216aa25:449:dolphin/L2_Furippa2_128x64/frame_5.bm -F:03b153949b0dae2efe1fc5f0dc57a0ef:466:dolphin/L2_Furippa2_128x64/frame_6.bm -F:be80d2fa903e3250b69c063a1eef0621:350:dolphin/L2_Furippa2_128x64/frame_7.bm -F:a8433f451cf3efc4ce2fb04a38c1f84f:319:dolphin/L2_Furippa2_128x64/frame_8.bm -F:d32a11bf9779d57191c1e59fe69cf83d:317:dolphin/L2_Furippa2_128x64/frame_9.bm -F:ebe088426d184cf6651288accd21add6:241:dolphin/L2_Furippa2_128x64/meta.txt -F:af4ec0085c29732085c51b18dc97bc27:543:dolphin/L2_Hacking_pc_128x64/frame_0.bm -F:eb141965fb6fb9f8b28766bac92abe1a:545:dolphin/L2_Hacking_pc_128x64/frame_1.bm -F:f7b33d3541dab08aaf4e8375e262b982:548:dolphin/L2_Hacking_pc_128x64/frame_2.bm -F:03634d90c54fd235aa76c0f9f794c80b:608:dolphin/L2_Hacking_pc_128x64/frame_3.bm -F:4c77406302f3fb74f8bdba568097082a:609:dolphin/L2_Hacking_pc_128x64/frame_4.bm -F:b0d1783358094534ac95b3455124d5fe:409:dolphin/L2_Hacking_pc_128x64/meta.txt -F:584c92e6fb15e99389b84d567e6d4d02:699:dolphin/L2_Soldering_128x64/frame_0.bm -F:3fa01b93460379204b6d14f43573b4f3:688:dolphin/L2_Soldering_128x64/frame_1.bm -F:6fad29757d4b7231b1d0ec53d0529b45:699:dolphin/L2_Soldering_128x64/frame_10.bm -F:e82c83e5a03abf4f6a1efd0a0f1ca33a:689:dolphin/L2_Soldering_128x64/frame_2.bm -F:7f9f310e22ef85af225dd1aefa2c47ba:689:dolphin/L2_Soldering_128x64/frame_3.bm -F:1ff31af6f90f07c0cdfa3283f52a5adc:693:dolphin/L2_Soldering_128x64/frame_4.bm -F:1a8f25aff949860cc6ffc79b4f48d5dd:696:dolphin/L2_Soldering_128x64/frame_5.bm -F:dbaa75feb8aebaf9b1cc5201c29952b8:712:dolphin/L2_Soldering_128x64/frame_6.bm -F:ee356bd981fba90c402d8e08d3015792:732:dolphin/L2_Soldering_128x64/frame_7.bm -F:09d5c5a685df606562d407bb9dac798e:705:dolphin/L2_Soldering_128x64/frame_8.bm -F:5451816e73bad029b3b9f3f55d294582:698:dolphin/L2_Soldering_128x64/frame_9.bm -F:c38ffad11987faf5ba6e363ead705e78:319:dolphin/L2_Soldering_128x64/meta.txt -F:2e083023ab65d1f99bba71f9aae6db9a:398:dolphin/L3_Furippa3_128x64/frame_0.bm -F:8cf20e07d84fd6a1157ba932beca70ea:438:dolphin/L3_Furippa3_128x64/frame_1.bm -F:018344c951691b7b1d77c1c6729d3e42:559:dolphin/L3_Furippa3_128x64/frame_10.bm -F:07008e2508064ab7a8467802472a9803:728:dolphin/L3_Furippa3_128x64/frame_11.bm -F:2dbb3125ea63550906fba8f7ec7b1da3:541:dolphin/L3_Furippa3_128x64/frame_12.bm -F:6a06b718957dca9caa63a4f3baa73abb:584:dolphin/L3_Furippa3_128x64/frame_13.bm -F:5450bf16c3d2fceaf5e6ea585b7ef7c1:610:dolphin/L3_Furippa3_128x64/frame_14.bm -F:e333224a4bed87b606df57a252ed4887:741:dolphin/L3_Furippa3_128x64/frame_15.bm -F:a20a6abfbd66fc3f92c66adacc4444a3:559:dolphin/L3_Furippa3_128x64/frame_16.bm -F:c1e051dce6b90e4f69b4792d0356a6b3:492:dolphin/L3_Furippa3_128x64/frame_17.bm -F:377f3621507c6590120cbc1c8ca92999:445:dolphin/L3_Furippa3_128x64/frame_18.bm -F:81f09c0fcd2bddb8a107a199e7149230:463:dolphin/L3_Furippa3_128x64/frame_2.bm -F:ed7fd1ada1070493462c1899f7372baf:424:dolphin/L3_Furippa3_128x64/frame_3.bm -F:e5fb2cdc4e08d6abff3191d37a1007ed:499:dolphin/L3_Furippa3_128x64/frame_4.bm -F:923a05250e5a93c7db7bbbf48448d164:504:dolphin/L3_Furippa3_128x64/frame_5.bm -F:1e9628db28a9a908c4a4b24cb16c5d20:521:dolphin/L3_Furippa3_128x64/frame_6.bm -F:2e083023ab65d1f99bba71f9aae6db9a:398:dolphin/L3_Furippa3_128x64/frame_7.bm -F:f1ec6e12daba9490f9e2e0e308ae3f83:419:dolphin/L3_Furippa3_128x64/frame_8.bm -F:106997120ad4cd23bd51e6f26bd7d74d:435:dolphin/L3_Furippa3_128x64/frame_9.bm -F:ebe088426d184cf6651288accd21add6:241:dolphin/L3_Furippa3_128x64/meta.txt -F:42970030123b2468984785fea7c60318:524:dolphin/L3_Hijack_radio_128x64/frame_0.bm -F:491c6d8ef21e48ca0f6b5fbd269c820b:527:dolphin/L3_Hijack_radio_128x64/frame_1.bm -F:ff499c8716c5f7fc1110a5ee82bda20c:550:dolphin/L3_Hijack_radio_128x64/frame_10.bm -F:ee39d82d6efc6a6992d19b6d75a6c509:572:dolphin/L3_Hijack_radio_128x64/frame_11.bm -F:5d14e8cb9d67bf8f597d6c749d07a135:539:dolphin/L3_Hijack_radio_128x64/frame_12.bm -F:9461004b75a34a36097668159c4cabe6:579:dolphin/L3_Hijack_radio_128x64/frame_13.bm -F:c925d4b1dff9c81463944cf930d7da8d:526:dolphin/L3_Hijack_radio_128x64/frame_2.bm -F:f98ed80cfab3a94b580be81654401c89:529:dolphin/L3_Hijack_radio_128x64/frame_3.bm -F:97ba548c27732be9e05fb8f7be8204ce:571:dolphin/L3_Hijack_radio_128x64/frame_4.bm -F:524932eb2391057fc1dea7237c7086e3:574:dolphin/L3_Hijack_radio_128x64/frame_5.bm -F:8eb9672f719926ac9c4c158575f388cd:524:dolphin/L3_Hijack_radio_128x64/frame_6.bm -F:7ca93fbab93bc278d4a11089d624a07b:655:dolphin/L3_Hijack_radio_128x64/frame_7.bm -F:37b4368f0b7235f3a7347bf499541666:645:dolphin/L3_Hijack_radio_128x64/frame_8.bm -F:ea9c3d7bab4756c2916369d5e130fa71:611:dolphin/L3_Hijack_radio_128x64/frame_9.bm -F:8583743f18a12ff647d3478e7aebdad6:230:dolphin/L3_Hijack_radio_128x64/meta.txt -F:f5f02a9df03bba734bdb7ed3297795f0:611:dolphin/L3_Lab_research_128x54/frame_0.bm -F:8f9655ad286464159443922d00e45620:614:dolphin/L3_Lab_research_128x54/frame_1.bm -F:7793b1bc107d4ea2e311e92dc16bf946:576:dolphin/L3_Lab_research_128x54/frame_10.bm -F:f24b8409f9dc770f3845424fe0ab489e:585:dolphin/L3_Lab_research_128x54/frame_11.bm -F:4ea93c4482dac43f40b67cc308f21e6d:571:dolphin/L3_Lab_research_128x54/frame_12.bm -F:cf3bb68dc78c568db22f37057a9fdd66:615:dolphin/L3_Lab_research_128x54/frame_13.bm -F:79719219aaebc95ea525def9173cabf5:618:dolphin/L3_Lab_research_128x54/frame_2.bm -F:05572cfd756704acd6ce9d6c15d03fc0:608:dolphin/L3_Lab_research_128x54/frame_3.bm -F:a26604a0d5427d5cf62a7a911a68b16c:615:dolphin/L3_Lab_research_128x54/frame_4.bm -F:9edc345fe53017970f93dc680818e63e:618:dolphin/L3_Lab_research_128x54/frame_5.bm -F:cf3bb68dc78c568db22f37057a9fdd66:615:dolphin/L3_Lab_research_128x54/frame_6.bm -F:5442895c85f769349288aa3df0990f9d:585:dolphin/L3_Lab_research_128x54/frame_7.bm -F:33b8fde22f34ef556b64b77164bc19b0:578:dolphin/L3_Lab_research_128x54/frame_8.bm -F:f267f0654781049ca323b11bb4375519:581:dolphin/L3_Lab_research_128x54/frame_9.bm -F:41106c0cbc5144f151b2b2d3daaa0527:727:dolphin/L3_Lab_research_128x54/meta.txt -D:infrared/assets -F:a565c3a381695a5f2ba7a0698460238c:74833:infrared/assets/tv.ir -F:a157a80f5a668700403d870c23b9567d:470:music_player/Marble_Machine.fmf -D:nfc/assets -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 -D:subghz/assets -F:dda1ef895b8a25fde57c874feaaef997:650:subghz/assets/came_atomo -F:788eef2cc74e29f3388463d6607dab0d:3264:subghz/assets/keeloq_mfcodes -F:9214f9c10463b746a27e82ce0b96e040:465:subghz/assets/keeloq_mfcodes_user -F:653bd8d349055a41e1152e557d4a52d3:202:subghz/assets/nice_flor_s -F:c1c63fbd5f5aa3ea504027014652191f:1150: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/debug/flipperapps.py b/debug/flipperapps.py new file mode 100644 index 00000000000..c8d3fcdb998 --- /dev/null +++ b/debug/flipperapps.py @@ -0,0 +1,157 @@ +from dataclasses import dataclass +from typing import Tuple, Dict +import struct +import posixpath +import os +import zlib + +import gdb + + +def get_file_crc32(filename): + with open(filename, "rb") as f: + return zlib.crc32(f.read()) + + +@dataclass +class AppState: + name: str + text_address: int = 0 + entry_address: int = 0 + other_sections: Dict[str, int] = None + debug_link_elf: str = "" + debug_link_crc: int = 0 + + def __post_init__(self): + if self.other_sections is None: + self.other_sections = {} + + def get_original_elf_path(self, elf_path="build/latest/.extapps") -> str: + return ( + posixpath.join(elf_path, self.debug_link_elf) + if elf_path + else self.debug_link_elf + ) + + def is_debug_available(self) -> bool: + have_debug_info = bool(self.debug_link_elf and self.debug_link_crc) + if not have_debug_info: + print("No debug info available for this app") + return False + debug_elf_path = self.get_original_elf_path() + debug_elf_crc32 = get_file_crc32(debug_elf_path) + if self.debug_link_crc != debug_elf_crc32: + print( + f"Debug info ({debug_elf_path}) CRC mismatch: {self.debug_link_crc:08x} != {debug_elf_crc32:08x}, rebuild app" + ) + return False + return True + + def get_gdb_load_command(self) -> str: + load_path = self.get_original_elf_path() + print(f"Loading debug information from {load_path}") + load_command = ( + f"add-symbol-file -readnow {load_path} 0x{self.text_address:08x} " + ) + load_command += " ".join( + f"-s {name} 0x{address:08x}" + for name, address in self.other_sections.items() + ) + return load_command + + def get_gdb_unload_command(self) -> str: + return f"remove-symbol-file -a 0x{self.text_address:08x}" + + def is_loaded_in_gdb(self, gdb_app) -> bool: + # Avoid constructing full app wrapper for comparison + return self.entry_address == int(gdb_app["entry"]) + + @staticmethod + def parse_debug_link_data(section_data: bytes) -> Tuple[str, int]: + # Debug link format: a null-terminated string with debuggable file name + # Padded with 0's to multiple of 4 bytes + # Followed by 4 bytes of CRC32 checksum of that file + elf_name = section_data[:-4].decode("utf-8").split("\x00")[0] + crc32 = struct.unpack(" "AppState": + state = AppState(str(gdb_app["manifest"]["name"].string())) + state.entry_address = int(gdb_app["entry"]) + + app_state = gdb_app["state"] + if debug_link_size := int(app_state["debug_link_size"]): + debug_link_data = ( + gdb.selected_inferior() + .read_memory(int(app_state["debug_link"]), debug_link_size) + .tobytes() + ) + state.debug_link_elf, state.debug_link_crc = AppState.parse_debug_link_data( + debug_link_data + ) + + for idx in range(app_state["mmap_entry_count"]): + mmap_entry = app_state["mmap_entries"][idx] + section_name = mmap_entry["name"].string() + section_addr = int(mmap_entry["address"]) + if section_name == ".text": + state.text_address = section_addr + else: + state.other_sections[section_name] = section_addr + + return state + + +class FlipperAppDebugHelper: + def __init__(self): + self.app_ptr = None + self.app_type_ptr = None + self.current_app: AppState = None + + def attach_fw(self) -> None: + self.app_ptr = gdb.lookup_global_symbol("last_loaded_app") + self.app_type_ptr = gdb.lookup_type("FlipperApplication").pointer() + self._check_app_state() + + def _check_app_state(self) -> None: + app_ptr_value = self.app_ptr.value() + if not app_ptr_value and self.current_app: + # There is an ELF loaded in GDB, but nothing is running on the device + self._unload_debug_elf() + elif app_ptr_value: + # There is an app running on the device + loaded_app = app_ptr_value.cast(self.app_type_ptr).dereference() + + if self.current_app and not self.current_app.is_loaded_in_gdb(loaded_app): + # Currently loaded ELF is not the one running on the device + self._unload_debug_elf() + + if not self.current_app: + # Load ELF for the app running on the device + self._load_debug_elf(loaded_app) + + def _unload_debug_elf(self) -> None: + try: + gdb.execute(self.current_app.get_gdb_unload_command()) + except gdb.error as e: + print(f"Failed to unload debug ELF: {e} (might not be an error)") + self.current_app = None + + def _load_debug_elf(self, app_object) -> None: + self.current_app = AppState.from_gdb(app_object) + + if self.current_app.is_debug_available(): + gdb.execute(self.current_app.get_gdb_load_command()) + + def handle_stop(self, event) -> None: + self._check_app_state() + + +helper = FlipperAppDebugHelper() +try: + helper.attach_fw() + print("Support for Flipper external apps debug is enabled") + gdb.events.stop.connect(helper.handle_stop) +except gdb.error as e: + print(f"Support for Flipper external apps debug is not available: {e}") diff --git a/documentation/AppManifests.md b/documentation/AppManifests.md index 7b117789003..5e7ceb939d8 100644 --- a/documentation/AppManifests.md +++ b/documentation/AppManifests.md @@ -1,3 +1,79 @@ # Flipper Application Manifests (.fam) -TBD \ No newline at end of file +All components of Flipper Zero firmware — services, user applications, system settings — are developed independently. Each component has a build system manifest file, named `application.fam`, defining basic properties of a components and its relations to other parts of the system. + +When building firmware, **`fbt`** collects all application manifests, processes their dependencies and builds only those components that are utilized in current build configuration. See [fbt docs](./fbt.md#firmware-application-set) for details on build configurations. + +## Application definition + +Properties of a firmware component are declared in a form of a Python code snippet, forming a call to App() function with various parameters. + +Only 2 parameters are mandatoty: ***appid*** and ***apptype***, others are optional and may be meaningful only for certain application types. + +### Keys + +* **appid**: string, application id within the build system. Used for specifying which applications to include in build configuration and to resolve dependencies and conflicts. + +* **apptype**: member of FlipperAppType.* enumeration. Valid values are: + +| Enum member | Firmware component type | +|--------------|--------------------------| +| SERVICE | System service, created at early startup | +| SYSTEM | Application not being shown in any menus. Can be started by other apps or from CLI | +| APP | Regular application for main menu | +| PLUGIN | Application to be built as .fap plugin | +| DEBUG | Application only visible in Debug menu with debug mode enabled | +| ARCHIVE | One and only Archive app | +| SETTINGS | Application to be placed in System settings menu | +| STARTUP | Callback function to run at system startup. Does not define a separate app | +| EXTERNAL | Application to be built as .fap plugin | +| METAPACKAGE | Does not define any code to be run, used for declaring dependencies and application bundles | + +* **name**: Name to show in menus. +* **entry_point**: C function to be used as applicaiton's entry point. +* **flags**: Internal flags for system apps. Do not use. +* **cdefines**: C preprocessor definitions to declare globally for other apps when current application is included in active build configuration. +* **requires**: List of application IDs to also include in build configuration, when current application is referenced in list of applications to build. +* **conflicts**: List of application IDs that current application conflicts with. If any of them is found in constructed application list, **`fbt`** will abort firmware build process. +* **provides**: Functionally identical to ***requires*** field. +* **stack_size**: Stack size, in bytes, to allocate for application on its startup. Note that allocating a stack too small for app to run will cause system crash due to stack overflow, and allocating too much stack will reduce usable heap memory size for app to process data. *Note: you can use `ps` and `free` CLI commands to profile you app's memory usage.* +* **icon**: Animated icon name from built-in assets to be used when building app as a part of firmware. +* **order**: Order of an application within its group when sorting entries in it. The lower the order is, the closer to the start of the list the items is located. Used for ordering startup hooks and menu entries. +* **sdk_headers**: List of C header files from this app's code to include in API definitions for external applicaions. + +The following parameters are used only for [FAPs](./AppsOnSDCard.md): + +* **sources**: list of file name masks, used for gathering sources within app folder. Default value of ["\*.c\*"] includes C and CPP source files. +* **version**: string, 2 numbers in form of "x.y": application version to be embedded within .fap file. +* **fap_icon**: name of .png file, 1-bit color depth, 10x10px, to be embedded within .fap file. +* **fap_libs**: list of extra libraries to link application against. Provides access to extra functions that are not exported as a part of main firmware at expense of increased .fap file size and RAM consumption. +* **fap_category**: string, may be empty. App subcategory, also works as path of FAP within apps folder in the file system. + + +## .fam file contents + +.fam file contains one or more Application definitions. For example, here's a part of `applications/service/bt/application.fam`: + +```python +App( + appid="bt_start", + apptype=FlipperAppType.STARTUP, + entry_point="bt_on_system_start", + order=70, +) + +App( + appid="bt_settings", + name="Bluetooth", + apptype=FlipperAppType.SETTINGS, + entry_point="bt_settings_app", + stack_size=1 * 1024, + requires=[ + "bt", + "gui", + ], + order=10, +) +``` + +For more examples, see application.fam files for basic firmware components. \ No newline at end of file diff --git a/documentation/AppsOnSDCard.md b/documentation/AppsOnSDCard.md new file mode 100644 index 00000000000..42d3871f40f --- /dev/null +++ b/documentation/AppsOnSDCard.md @@ -0,0 +1,72 @@ +### FAP (Flipper Application Package) + +[fbt](./fbt.md) has support for building applications as FAP files. FAP are essentially .elf executables with extra metadata and resources bundled in. + +FAPs are built with `firmware_extapps` (or `plugin_dist`) **`fbt`** targets. + +FAPs do not depend on being ran on a specific firmware version. Compatibility is determined by the FAP's metadata, which includes the required [API version](#api-versioning). + + +## How to set up an application to be built as a FAP + +FAPs are created and developed the same way as internal applications that are part of the firmware. + +To build your application as a FAP, just create a folder with your app's source code in `applications_user` folder, then write its code the way you'd do when creating a regular built-in application. Then configure its `application.fam` manifest — and set its *apptype* to FlipperAppType.EXTERNAL. See [Application Manifests](./AppManifests.md#application-definition) for more details. + + * To build your application, run `./fbt firmware_{APPID}`, where APPID is your application's ID in its manifest. + * To build your app, upload it over USB & run it on Flipper, run `./fbt launch_app APPSRC=applications/path/to/app`. This action is configured in default [VSCode profile](../.vscode/ReadMe.md) as "Launch App on Flipper" build action (Ctrl+Shift+B menu). + * To build all FAPs, run `./fbt plugin_dist`. + + +## Debugging FAPs + +**`fbt`** includes a script for gdb-py to provide debugging support for FAPs, `debug/flipperapps.py`. It is loaded in default debugging configurations by **`fbt`** and stock VSCode configurations. + +With it, you can debug FAPs as if they were a part of main firmware — inspect variables, set breakpoints, step through the code, etc. + +### Setting up debugging environment + +Debugging support script looks up debugging information in latest firmware build dir (`build/latest`). That directory is symlinked by fbt to the latest firmware configuration (Debug or Release) build dir, when you run `./fbt` for chosen configuration. See [fbt docs](./fbt.md#nb) for details. + +So, to debug FAPs, do the following: +1. Build firmware with `./fbt` +2. Flash it with `./fbt flash` +3. [Build your FAP](#how-to-set-up-an-application-to-be-built-as-a-fap) and run it on Flipper. + +After that, you can attach with `./fbt debug` or VSCode and use all debug features. + +It is **important** that firmware and application build type (debug/release) match, and that matching firmware folder is linked as `build/latest`. Otherwise, debugging will not work. + + +## How Flipper runs an application from SD card + +Flipper's MCU cannot run code directly from external storage, so it needs to be copied to RAM first. This is done by the App Loader application, which is responsible for loading the FAP from SD card, verifying its integrity and compatibility and copying it to RAM. + +Since FAP has to be loaded to RAM to be executed, the amount of RAM available for allocations from heap is reduced compared to running the same app from flash, as a part of firmware. Note that amount of occupied RAM is less than total FAP file size, since only code and data sections are allocated, and FAP file includes extra information only used at app load time. + +Applications are built for a specific API version. It is a part of hardware target's definition and contains a major and minor version number. Application loader checks if the application's major API version matches firmware's major API version. + +App loader allocates memory for the application and copies it to RAM, processing relocations and providing concrete addresses for imported symbols using the [symbol table](#symbol-table). Then it starts the application. + + +## API Versioning + +Not all parts of firmware are available for external applications. Subset of available functions and variables is defined in "api_symbols.csv" file, which is a part of firmware target definition in `firmware/targets/` directory. + +**`fbt`** uses semantic versioning for API versioning. Major version is incremented when there are breaking changes in the API, minor version is incremented when there are new features added. + +Breaking changes include: +- removal of a function or a global variable; +- changing the signature of a function. + +API versioning is mostly automated by **`fbt`**. When rebuilding the firmware, **`fbt`** checks if there are any changes in the API exposed by headers gathered from `SDK_HEADERS`. If there are, it stops the build, adjusts API version and asks the user to go through the changes in .csv file. New entries are marked with "`?`" mark, and the user is supposed to change the mark to "`+`" for the entry to be exposed for FAPs, "`-`" for it to be unavailable. + +**`fbt`** will not allow building a firmware until all "`?`" entries are changed to "`+`" or "`-`". + +**NB:** **`fbt`** automatically manages the API version. The only case where manually incrementing the major API version is allowed (and required) is when existing "`+`" entries are to be changed to "`-`". + +### Symbol Table + +Symbol table is a list of symbols exported by firmware and available for external applications. It is generated by **`fbt`** from the API symbols file and is used by the App Loader to resolve addresses of imported symbols. It is build as a part of the `fap_loader` application. + +**`fbt`** also checks if all imported symbols are present in the symbol table. If there are any missing symbols, it will issue a warning listing them. Such application won't be able to run on the device until all requires symbols are provided in the symbol table. diff --git a/documentation/fbt.md b/documentation/fbt.md index 48726273e70..3eee6baaa0f 100644 --- a/documentation/fbt.md +++ b/documentation/fbt.md @@ -38,11 +38,12 @@ To run cleanup (think of `make clean`) for specified targets, add `-c` option. ## FBT targets -FBT keeps track of internal dependencies, so you only need to build the highest-level target you need, and FBT will make sure everything they depend on is up-to-date. +**`fbt`** keeps track of internal dependencies, so you only need to build the highest-level target you need, and **`fbt`** will make sure everything they depend on is up-to-date. ### High-level (what you most likely need) - `fw_dist` - build & publish firmware to `dist` folder. This is a default target, when no other are specified +- `plugin_dist` - build external plugins & publish to `dist` folder - `updater_package`, `updater_minpackage` - build self-update package. Minimal version only inclues firmware's DFU file; full version also includes radio stack & resources for SD card - `copro_dist` - bundle Core2 FUS+stack binaries for qFlipper - `flash` - flash attached device with OpenOCD over ST-Link @@ -53,7 +54,7 @@ FBT keeps track of internal dependencies, so you only need to build the highest- - `blackmagic` - debug firmware with Blackmagic probe (WiFi dev board) - `openocd` - just start OpenOCD - `get_blackmagic` - output blackmagic address in gdb remote format. Useful for IDE integration -- `lint`, `format` - run clang-tidy on C source code to check and reformat it according to `.clang-format` specs +- `lint`, `format` - run clang-format on C source code to check and reformat it according to `.clang-format` specs - `lint_py`, `format_py` - run [black](https://black.readthedocs.io/en/stable/index.html) on Python source code, build system files & application manifests ### Firmware targets @@ -82,7 +83,7 @@ FBT keeps track of internal dependencies, so you only need to build the highest- ## Command-line parameters - `--options optionfile.py` (default value `fbt_options.py`) - load file with multiple configuration values -- `--with-updater` - enables updater-related targets and dependency tracking. Enabling this option introduces extra startup time costs, so use it when bundling update packages. _Explicily enabling this should no longer be required, fbt now has specific handling for updater-related targets_ +- `--with-updater` - enables updater-related targets and dependency tracking. Enabling this option introduces extra startup time costs, so use it when bundling update packages. _Explicily enabling this should no longer be required, **`fbt`** now has specific handling for updater-related targets_ - `--extra-int-apps=app1,app2,appN` - forces listed apps to be built as internal with `firmware` target - `--extra-ext-apps=app1,app2,appN` - forces listed apps to be built as external with `firmware_extapps` target diff --git a/fbt_options.py b/fbt_options.py index b154d26aa46..bdf8fc03ba1 100644 --- a/fbt_options.py +++ b/fbt_options.py @@ -66,22 +66,17 @@ FIRMWARE_APPS = { "default": [ - "crypto_start", # Svc "basic_services", # Apps - "basic_apps", - "updater_app", - "storage_move_to_sd", - "archive", + "main_apps", + "system_apps", # Settings - "passport", - "system_settings", - "about", + "settings_apps", # Plugins - "basic_plugins", + # "basic_plugins", # Debug - "debug_apps", + # "debug_apps", ], "unit_tests": [ "basic_services", diff --git a/firmware.scons b/firmware.scons index f47e9ff265d..962c7072557 100644 --- a/firmware.scons +++ b/firmware.scons @@ -1,6 +1,6 @@ Import("ENV", "fw_build_meta") -import os +import itertools from fbt.util import ( should_gen_cdb_and_link_dir, @@ -13,6 +13,7 @@ env = ENV.Clone( ("compilation_db", {"COMPILATIONDB_COMSTR": "\tCDB\t${TARGET}"}), "fwbin", "fbt_apps", + "fbt_sdk", ], COMPILATIONDB_USE_ABSPATH=False, BUILD_DIR=fw_build_meta["build_dir"], @@ -28,7 +29,7 @@ env = ENV.Clone( ], CPPPATH=[ "#/furi", - "#/applications", + *(f"#/{app_dir[0]}" for app_dir in ENV["APPDIRS"] if app_dir[1]), "#/firmware/targets/f${TARGET_HW}/ble_glue", "#/firmware/targets/f${TARGET_HW}/fatfs", "#/firmware/targets/f${TARGET_HW}/furi_hal", @@ -58,6 +59,15 @@ env = ENV.Clone( "FURI_DEBUG" if ENV["DEBUG"] else "FURI_NDEBUG", ], }, + "flipper_application": { + "CCFLAGS": [ + "-Og", + ], + "CPPDEFINES": [ + "NDEBUG", + "FURI_DEBUG" if ENV["DEBUG"] else "FURI_NDEBUG", + ], + }, }, ) @@ -80,6 +90,12 @@ if not env["VERBOSE"]: HEXCOMSTR="\tHEX\t${TARGET}", BINCOMSTR="\tBIN\t${TARGET}", DFUCOMSTR="\tDFU\t${TARGET}", + SDK_PREGEN_COMSTR="\tPREGEN\t${TARGET}", + SDK_COMSTR="\tSDKSRC\t${TARGET}", + SDKSYM_UPDATER_COMSTR="\tSDKCHK\t${TARGET}", + SDKSYM_GENERATOR_COMSTR="\tSDKSYM\t${TARGET}", + APPMETA_COMSTR="\tAPPMETA\t${TARGET}", + APPMETAEMBED_COMSTR="\tFAP\t${TARGET}", ) @@ -127,11 +143,13 @@ fwenv.LoadApplicationManifests() fwenv.PrepareApplicationsBuild() # Build external apps -extapps = SConscript("applications/extapps.scons", exports={"ENV": fwenv}) +extapps = fwenv["FW_EXTAPPS"] = SConscript( + "site_scons/extapps.scons", exports={"ENV": fwenv} +) # Add preprocessor definitions for current set of apps -fwenv.AppendUnique( +fwenv.Append( CPPDEFINES=fwenv["APPBUILD"].get_apps_cdefs(), ) @@ -141,23 +159,26 @@ apps_c = fwenv.ApplicationsC( "applications/applications.c", [Value(fwenv["APPS"]), Value(fwenv["LOADER_AUTOSTART"])], ) + # Adding dependency on manifest files so apps.c is rebuilt when any manifest is changed -fwenv.Depends(apps_c, fwenv.GlobRecursive("*.fam", "#/applications")) +for app_dir, _ in env["APPDIRS"]: + app_dir_node = env.Dir("#").Dir(app_dir) + fwenv.Depends(apps_c, fwenv.GlobRecursive("*.fam", app_dir_node)) sources = [apps_c] # Gather sources only from app folders in current configuration -for app_folder in fwenv["APPBUILD"].get_builtin_app_folders(): - sources += fwenv.GlobRecursive("*.c*", os.path.join("applications", app_folder)) +sources.extend( + itertools.chain.from_iterable( + fwenv.GlobRecursive(source_type, appdir.relpath) + for appdir, source_type in fwenv["APPBUILD"].get_builtin_app_folders() + ) +) fwenv.AppendUnique( LINKFLAGS=[ "-specs=nano.specs", "-specs=nosys.specs", - "-Wl,--start-group", - "-lstdc++", - "-lsupc++", - "-Wl,--end-group", "-Wl,--gc-sections", "-Wl,--undefined=uxTopUsedPriority", "-Wl,--wrap,_malloc_r", @@ -174,7 +195,6 @@ fwenv.AppendUnique( # print(fwenv.Dump()) # Full firmware definition - fwelf = fwenv["FW_ELF"] = fwenv.Program( "${FIRMWARE_BUILD_CFG}", sources, @@ -199,8 +219,8 @@ fwelf = fwenv["FW_ELF"] = fwenv.Program( "assets", "misc", "mbedtls", - "loclass", "lfrfid", + "flipper_application", # 2nd round "flipperformat", "toolbox", @@ -248,6 +268,8 @@ if should_gen_cdb_and_link_dir(fwenv, BUILD_TARGETS): Precious(fwcdb) NoClean(fwcdb) Alias(fwenv["FIRMWARE_BUILD_CFG"] + "_cdb", fwcdb) + AlwaysBuild(fwenv["FIRMWARE_BUILD_CFG"] + "_cdb", fwcdb) + Alias(fwcdb, "") fw_artifacts.append(fwcdb) # Adding as a phony target, so folder link is updated even if elf didn't change @@ -263,5 +285,42 @@ if should_gen_cdb_and_link_dir(fwenv, BUILD_TARGETS): Alias(fwenv["FIRMWARE_BUILD_CFG"] + "_all", fw_artifacts) +if fwenv["IS_BASE_FIRMWARE"]: + sdk_source = fwenv.SDKPrebuilder( + "sdk_origin", + [], + # Filtering out things cxxheaderparser cannot handle + SDK_PP_FLAGS=[ + '-D"_Static_assert(x,y)="', + '-D"__asm__(x)="', + '-D"__attribute__(x)="', + "-Drestrict=", + "-D_Noreturn=", + "-D__restrict=", + "-D__extension__=", + "-D__inline=inline", + "-D__inline__=inline", + ], + ) + Depends(sdk_source, fwenv["SDK_HEADERS"]) + + sdk_tree = fwenv.SDKTree("sdk/sdk.opts", "sdk_origin") + AlwaysBuild(sdk_tree) + Alias("sdk_tree", sdk_tree) + + sdk_apicheck = fwenv.SDKSymUpdater(fwenv.subst("$SDK_DEFINITION"), "sdk_origin") + Precious(sdk_apicheck) + NoClean(sdk_apicheck) + AlwaysBuild(sdk_apicheck) + Alias("sdk_check", sdk_apicheck) + + sdk_apisyms = fwenv.SDKSymGenerator( + "assets/compiled/symbols.h", fwenv.subst("$SDK_DEFINITION") + ) + Alias("api_syms", sdk_apisyms) + + if fwenv["FORCE"]: + fwenv.AlwaysBuild(sdk_source, sdk_tree, sdk_apicheck, sdk_apisyms) + Return("fwenv") diff --git a/firmware/SConscript b/firmware/SConscript index 3530ed82783..5795e242b80 100644 --- a/firmware/SConscript +++ b/firmware/SConscript @@ -1,6 +1,16 @@ Import("env") -env.Append(LINT_SOURCES=["firmware"]) +env.Append( + LINT_SOURCES=["firmware"], + # SDK_HEADERS=[env.File("#/firmware/targets/furi_hal_include/furi_hal.h")], + SDK_HEADERS=[ + *env.GlobRecursive("*.h", "#/firmware/targets/furi_hal_include", "*_i.h"), + *env.GlobRecursive("*.h", "#/firmware/targets/f${TARGET_HW}/furi_hal", "*_i.h"), + File("#/firmware/targets/f7/platform_specific/intrinsic_export.h"), + ], +) +env.SetDefault(SDK_DEFINITION=env.File("./targets/f${TARGET_HW}/api_symbols.csv")) + libenv = env.Clone(FW_LIB_NAME="flipper${TARGET_HW}") libenv.Append( diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv new file mode 100644 index 00000000000..545e1d644c5 --- /dev/null +++ b/firmware/targets/f7/api_symbols.csv @@ -0,0 +1,2870 @@ +entry,status,name,type,params +Version,+,1.0,, +Header,+,applications/services/bt/bt_service/bt.h,, +Header,+,applications/services/cli/cli.h,, +Header,+,applications/services/cli/cli_vcp.h,, +Header,+,applications/services/dialogs/dialogs.h,, +Header,+,applications/services/dolphin/dolphin.h,, +Header,+,applications/services/gui/elements.h,, +Header,+,applications/services/gui/gui.h,, +Header,+,applications/services/gui/modules/button_menu.h,, +Header,+,applications/services/gui/modules/button_panel.h,, +Header,+,applications/services/gui/modules/byte_input.h,, +Header,+,applications/services/gui/modules/dialog_ex.h,, +Header,+,applications/services/gui/modules/empty_screen.h,, +Header,+,applications/services/gui/modules/file_browser.h,, +Header,+,applications/services/gui/modules/file_browser_worker.h,, +Header,+,applications/services/gui/modules/loading.h,, +Header,+,applications/services/gui/modules/menu.h,, +Header,+,applications/services/gui/modules/popup.h,, +Header,+,applications/services/gui/modules/submenu.h,, +Header,+,applications/services/gui/modules/text_box.h,, +Header,+,applications/services/gui/modules/text_input.h,, +Header,+,applications/services/gui/modules/validators.h,, +Header,+,applications/services/gui/modules/variable_item_list.h,, +Header,+,applications/services/gui/modules/widget.h,, +Header,+,applications/services/gui/modules/widget_elements/widget_element.h,, +Header,+,applications/services/gui/view_dispatcher.h,, +Header,+,applications/services/gui/view_stack.h,, +Header,+,applications/services/input/input.h,, +Header,+,applications/services/loader/loader.h,, +Header,+,applications/services/notification/notification.h,, +Header,+,applications/services/notification/notification_messages.h,, +Header,+,applications/services/power/power_service/power.h,, +Header,+,applications/services/rpc/rpc_app.h,, +Header,+,applications/services/storage/storage.h,, +Header,+,firmware/targets/f7/furi_hal/furi_hal_clock.h,, +Header,+,firmware/targets/f7/furi_hal/furi_hal_console.h,, +Header,+,firmware/targets/f7/furi_hal/furi_hal_flash.h,, +Header,+,firmware/targets/f7/furi_hal/furi_hal_gpio.h,, +Header,+,firmware/targets/f7/furi_hal/furi_hal_i2c_config.h,, +Header,+,firmware/targets/f7/furi_hal/furi_hal_i2c_types.h,, +Header,+,firmware/targets/f7/furi_hal/furi_hal_idle_timer.h,, +Header,+,firmware/targets/f7/furi_hal/furi_hal_interrupt.h,, +Header,+,firmware/targets/f7/furi_hal/furi_hal_os.h,, +Header,+,firmware/targets/f7/furi_hal/furi_hal_resources.h,, +Header,+,firmware/targets/f7/furi_hal/furi_hal_spi_config.h,, +Header,+,firmware/targets/f7/furi_hal/furi_hal_spi_types.h,, +Header,+,firmware/targets/f7/furi_hal/furi_hal_subghz_configs.h,, +Header,+,firmware/targets/f7/furi_hal/furi_hal_uart.h,, +Header,+,firmware/targets/f7/furi_hal/furi_hal_usb_cdc.h,, +Header,+,firmware/targets/f7/platform_specific/intrinsic_export.h,, +Header,+,firmware/targets/furi_hal_include/furi_hal.h,, +Header,+,firmware/targets/furi_hal_include/furi_hal_bt.h,, +Header,+,firmware/targets/furi_hal_include/furi_hal_bt_hid.h,, +Header,+,firmware/targets/furi_hal_include/furi_hal_bt_serial.h,, +Header,+,firmware/targets/furi_hal_include/furi_hal_compress.h,, +Header,+,firmware/targets/furi_hal_include/furi_hal_cortex.h,, +Header,+,firmware/targets/furi_hal_include/furi_hal_crypto.h,, +Header,+,firmware/targets/furi_hal_include/furi_hal_debug.h,, +Header,+,firmware/targets/furi_hal_include/furi_hal_i2c.h,, +Header,+,firmware/targets/furi_hal_include/furi_hal_ibutton.h,, +Header,+,firmware/targets/furi_hal_include/furi_hal_info.h,, +Header,+,firmware/targets/furi_hal_include/furi_hal_infrared.h,, +Header,+,firmware/targets/furi_hal_include/furi_hal_light.h,, +Header,+,firmware/targets/furi_hal_include/furi_hal_memory.h,, +Header,+,firmware/targets/furi_hal_include/furi_hal_mpu.h,, +Header,+,firmware/targets/furi_hal_include/furi_hal_nfc.h,, +Header,+,firmware/targets/furi_hal_include/furi_hal_power.h,, +Header,+,firmware/targets/furi_hal_include/furi_hal_random.h,, +Header,+,firmware/targets/furi_hal_include/furi_hal_region.h,, +Header,+,firmware/targets/furi_hal_include/furi_hal_rfid.h,, +Header,+,firmware/targets/furi_hal_include/furi_hal_rtc.h,, +Header,+,firmware/targets/furi_hal_include/furi_hal_sd.h,, +Header,+,firmware/targets/furi_hal_include/furi_hal_speaker.h,, +Header,+,firmware/targets/furi_hal_include/furi_hal_spi.h,, +Header,+,firmware/targets/furi_hal_include/furi_hal_subghz.h,, +Header,+,firmware/targets/furi_hal_include/furi_hal_usb.h,, +Header,+,firmware/targets/furi_hal_include/furi_hal_usb_hid.h,, +Header,+,firmware/targets/furi_hal_include/furi_hal_usb_hid_u2f.h,, +Header,+,firmware/targets/furi_hal_include/furi_hal_version.h,, +Header,+,firmware/targets/furi_hal_include/furi_hal_vibro.h,, +Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_adc.h,, +Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_bus.h,, +Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_comp.h,, +Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_cortex.h,, +Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_crc.h,, +Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_crs.h,, +Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_dma.h,, +Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_dmamux.h,, +Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_exti.h,, +Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_gpio.h,, +Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_hsem.h,, +Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_i2c.h,, +Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_ipcc.h,, +Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_iwdg.h,, +Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_lptim.h,, +Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_lpuart.h,, +Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_pka.h,, +Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_pwr.h,, +Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_rcc.h,, +Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_rng.h,, +Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_rtc.h,, +Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_spi.h,, +Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_system.h,, +Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_tim.h,, +Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_usart.h,, +Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_utils.h,, +Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_wwdg.h,, +Header,+,lib/flipper_application/flipper_application.h,, +Header,+,lib/flipper_format/flipper_format.h,, +Header,+,lib/flipper_format/flipper_format_i.h,, +Header,+,lib/micro-ecc/uECC.h,, +Header,+,lib/one_wire/ibutton/ibutton_worker.h,, +Header,+,lib/one_wire/maxim_crc.h,, +Header,+,lib/one_wire/one_wire_device.h,, +Header,+,lib/one_wire/one_wire_host.h,, +Header,+,lib/one_wire/one_wire_host_timing.h,, +Header,+,lib/one_wire/one_wire_slave.h,, +Header,+,lib/print/wrappers.h,, +Header,+,lib/subghz/environment.h,, +Header,+,lib/subghz/receiver.h,, +Header,+,lib/subghz/subghz_tx_rx_worker.h,, +Header,+,lib/subghz/subghz_worker.h,, +Header,+,lib/subghz/transmitter.h,, +Header,+,lib/toolbox/args.h,, +Header,+,lib/toolbox/crc32_calc.h,, +Header,+,lib/toolbox/dir_walk.h,, +Header,+,lib/toolbox/hmac_sha256.h,, +Header,+,lib/toolbox/manchester_decoder.h,, +Header,+,lib/toolbox/manchester_encoder.h,, +Header,+,lib/toolbox/md5.h,, +Header,+,lib/toolbox/path.h,, +Header,+,lib/toolbox/random_name.h,, +Header,+,lib/toolbox/saved_struct.h,, +Header,+,lib/toolbox/stream/file_stream.h,, +Header,+,lib/toolbox/stream/stream.h,, +Header,+,lib/toolbox/stream/string_stream.h,, +Header,+,lib/toolbox/tar/tar_archive.h,, +Header,+,lib/toolbox/version.h,, +Function,-,LL_ADC_CommonDeInit,ErrorStatus,ADC_Common_TypeDef* +Function,-,LL_ADC_CommonInit,ErrorStatus,"ADC_Common_TypeDef*, LL_ADC_CommonInitTypeDef*" +Function,-,LL_ADC_CommonStructInit,void,LL_ADC_CommonInitTypeDef* +Function,-,LL_ADC_DeInit,ErrorStatus,ADC_TypeDef* +Function,-,LL_ADC_INJ_Init,ErrorStatus,"ADC_TypeDef*, LL_ADC_INJ_InitTypeDef*" +Function,-,LL_ADC_INJ_StructInit,void,LL_ADC_INJ_InitTypeDef* +Function,-,LL_ADC_Init,ErrorStatus,"ADC_TypeDef*, LL_ADC_InitTypeDef*" +Function,-,LL_ADC_REG_Init,ErrorStatus,"ADC_TypeDef*, LL_ADC_REG_InitTypeDef*" +Function,-,LL_ADC_REG_StructInit,void,LL_ADC_REG_InitTypeDef* +Function,-,LL_ADC_StructInit,void,LL_ADC_InitTypeDef* +Function,-,LL_COMP_DeInit,ErrorStatus,COMP_TypeDef* +Function,-,LL_COMP_Init,ErrorStatus,"COMP_TypeDef*, LL_COMP_InitTypeDef*" +Function,-,LL_COMP_StructInit,void,LL_COMP_InitTypeDef* +Function,-,LL_CRC_DeInit,ErrorStatus,CRC_TypeDef* +Function,-,LL_CRS_DeInit,ErrorStatus, +Function,-,LL_DMA_DeInit,ErrorStatus,"DMA_TypeDef*, uint32_t" +Function,-,LL_DMA_Init,ErrorStatus,"DMA_TypeDef*, uint32_t, LL_DMA_InitTypeDef*" +Function,-,LL_DMA_StructInit,void,LL_DMA_InitTypeDef* +Function,-,LL_EXTI_DeInit,ErrorStatus, +Function,-,LL_EXTI_Init,ErrorStatus,LL_EXTI_InitTypeDef* +Function,-,LL_EXTI_StructInit,void,LL_EXTI_InitTypeDef* +Function,-,LL_GPIO_DeInit,ErrorStatus,GPIO_TypeDef* +Function,-,LL_GPIO_Init,ErrorStatus,"GPIO_TypeDef*, LL_GPIO_InitTypeDef*" +Function,-,LL_GPIO_StructInit,void,LL_GPIO_InitTypeDef* +Function,-,LL_I2C_DeInit,ErrorStatus,I2C_TypeDef* +Function,-,LL_I2C_Init,ErrorStatus,"I2C_TypeDef*, LL_I2C_InitTypeDef*" +Function,-,LL_I2C_StructInit,void,LL_I2C_InitTypeDef* +Function,-,LL_Init1msTick,void,uint32_t +Function,-,LL_LPTIM_DeInit,ErrorStatus,LPTIM_TypeDef* +Function,-,LL_LPTIM_Disable,void,LPTIM_TypeDef* +Function,-,LL_LPTIM_Init,ErrorStatus,"LPTIM_TypeDef*, LL_LPTIM_InitTypeDef*" +Function,-,LL_LPTIM_StructInit,void,LL_LPTIM_InitTypeDef* +Function,-,LL_LPUART_DeInit,ErrorStatus,USART_TypeDef* +Function,-,LL_LPUART_Init,ErrorStatus,"USART_TypeDef*, LL_LPUART_InitTypeDef*" +Function,-,LL_LPUART_StructInit,void,LL_LPUART_InitTypeDef* +Function,-,LL_PKA_DeInit,ErrorStatus,PKA_TypeDef* +Function,-,LL_PKA_Init,ErrorStatus,"PKA_TypeDef*, LL_PKA_InitTypeDef*" +Function,-,LL_PKA_StructInit,void,LL_PKA_InitTypeDef* +Function,-,LL_PLL_ConfigSystemClock_HSE,ErrorStatus,"uint32_t, LL_UTILS_PLLInitTypeDef*, LL_UTILS_ClkInitTypeDef*" +Function,-,LL_PLL_ConfigSystemClock_HSI,ErrorStatus,"LL_UTILS_PLLInitTypeDef*, LL_UTILS_ClkInitTypeDef*" +Function,-,LL_PLL_ConfigSystemClock_MSI,ErrorStatus,"LL_UTILS_PLLInitTypeDef*, LL_UTILS_ClkInitTypeDef*" +Function,-,LL_PWR_DeInit,ErrorStatus, +Function,-,LL_RCC_DeInit,ErrorStatus, +Function,-,LL_RCC_GetADCClockFreq,uint32_t,uint32_t +Function,-,LL_RCC_GetCLK48ClockFreq,uint32_t,uint32_t +Function,-,LL_RCC_GetI2CClockFreq,uint32_t,uint32_t +Function,-,LL_RCC_GetLPTIMClockFreq,uint32_t,uint32_t +Function,-,LL_RCC_GetLPUARTClockFreq,uint32_t,uint32_t +Function,-,LL_RCC_GetRFWKPClockFreq,uint32_t, +Function,-,LL_RCC_GetRNGClockFreq,uint32_t,uint32_t +Function,-,LL_RCC_GetRTCClockFreq,uint32_t, +Function,-,LL_RCC_GetSAIClockFreq,uint32_t,uint32_t +Function,-,LL_RCC_GetSMPSClockFreq,uint32_t, +Function,-,LL_RCC_GetSystemClocksFreq,void,LL_RCC_ClocksTypeDef* +Function,-,LL_RCC_GetUSARTClockFreq,uint32_t,uint32_t +Function,-,LL_RCC_GetUSBClockFreq,uint32_t,uint32_t +Function,-,LL_RNG_DeInit,ErrorStatus,RNG_TypeDef* +Function,-,LL_RNG_Init,ErrorStatus,"RNG_TypeDef*, LL_RNG_InitTypeDef*" +Function,-,LL_RNG_StructInit,void,LL_RNG_InitTypeDef* +Function,-,LL_RTC_ALMA_Init,ErrorStatus,"RTC_TypeDef*, uint32_t, LL_RTC_AlarmTypeDef*" +Function,-,LL_RTC_ALMA_StructInit,void,LL_RTC_AlarmTypeDef* +Function,-,LL_RTC_ALMB_Init,ErrorStatus,"RTC_TypeDef*, uint32_t, LL_RTC_AlarmTypeDef*" +Function,-,LL_RTC_ALMB_StructInit,void,LL_RTC_AlarmTypeDef* +Function,-,LL_RTC_DATE_Init,ErrorStatus,"RTC_TypeDef*, uint32_t, LL_RTC_DateTypeDef*" +Function,-,LL_RTC_DATE_StructInit,void,LL_RTC_DateTypeDef* +Function,-,LL_RTC_DeInit,ErrorStatus,RTC_TypeDef* +Function,-,LL_RTC_EnterInitMode,ErrorStatus,RTC_TypeDef* +Function,-,LL_RTC_ExitInitMode,ErrorStatus,RTC_TypeDef* +Function,-,LL_RTC_Init,ErrorStatus,"RTC_TypeDef*, LL_RTC_InitTypeDef*" +Function,-,LL_RTC_StructInit,void,LL_RTC_InitTypeDef* +Function,-,LL_RTC_TIME_Init,ErrorStatus,"RTC_TypeDef*, uint32_t, LL_RTC_TimeTypeDef*" +Function,-,LL_RTC_TIME_StructInit,void,LL_RTC_TimeTypeDef* +Function,-,LL_RTC_WaitForSynchro,ErrorStatus,RTC_TypeDef* +Function,-,LL_SPI_DeInit,ErrorStatus,SPI_TypeDef* +Function,-,LL_SPI_Init,ErrorStatus,"SPI_TypeDef*, LL_SPI_InitTypeDef*" +Function,-,LL_SPI_StructInit,void,LL_SPI_InitTypeDef* +Function,-,LL_SetFlashLatency,ErrorStatus,uint32_t +Function,-,LL_SetSystemCoreClock,void,uint32_t +Function,-,LL_TIM_BDTR_Init,ErrorStatus,"TIM_TypeDef*, LL_TIM_BDTR_InitTypeDef*" +Function,-,LL_TIM_BDTR_StructInit,void,LL_TIM_BDTR_InitTypeDef* +Function,-,LL_TIM_DeInit,ErrorStatus,TIM_TypeDef* +Function,-,LL_TIM_ENCODER_Init,ErrorStatus,"TIM_TypeDef*, LL_TIM_ENCODER_InitTypeDef*" +Function,-,LL_TIM_ENCODER_StructInit,void,LL_TIM_ENCODER_InitTypeDef* +Function,-,LL_TIM_HALLSENSOR_Init,ErrorStatus,"TIM_TypeDef*, LL_TIM_HALLSENSOR_InitTypeDef*" +Function,-,LL_TIM_HALLSENSOR_StructInit,void,LL_TIM_HALLSENSOR_InitTypeDef* +Function,-,LL_TIM_IC_Init,ErrorStatus,"TIM_TypeDef*, uint32_t, LL_TIM_IC_InitTypeDef*" +Function,-,LL_TIM_IC_StructInit,void,LL_TIM_IC_InitTypeDef* +Function,+,LL_TIM_Init,ErrorStatus,"TIM_TypeDef*, LL_TIM_InitTypeDef*" +Function,+,LL_TIM_OC_Init,ErrorStatus,"TIM_TypeDef*, uint32_t, LL_TIM_OC_InitTypeDef*" +Function,-,LL_TIM_OC_StructInit,void,LL_TIM_OC_InitTypeDef* +Function,-,LL_TIM_StructInit,void,LL_TIM_InitTypeDef* +Function,-,LL_USART_ClockInit,ErrorStatus,"USART_TypeDef*, LL_USART_ClockInitTypeDef*" +Function,-,LL_USART_ClockStructInit,void,LL_USART_ClockInitTypeDef* +Function,-,LL_USART_DeInit,ErrorStatus,USART_TypeDef* +Function,-,LL_USART_Init,ErrorStatus,"USART_TypeDef*, LL_USART_InitTypeDef*" +Function,-,LL_USART_StructInit,void,LL_USART_InitTypeDef* +Function,-,LL_mDelay,void,uint32_t +Function,-,SystemCoreClockUpdate,void, +Function,-,SystemInit,void, +Function,-,_Exit,void,int +Function,-,__assert,void,"const char*, int, const char*" +Function,+,__assert_func,void,"const char*, int, const char*, const char*" +Function,+,__clear_cache,void,"void*, void*" +Function,-,__eprintf,void,"const char*, const char*, unsigned int, const char*" +Function,+,__errno,int*, +Function,-,__fpclassifyd,int,double +Function,-,__fpclassifyf,int,float +Function,-,__getdelim,ssize_t,"char**, size_t*, int, FILE*" +Function,-,__getline,ssize_t,"char**, size_t*, FILE*" +Function,-,__isinfd,int,double +Function,-,__isinff,int,float +Function,-,__isnand,int,double +Function,-,__isnanf,int,float +Function,-,__itoa,char*,"int, char*, int" +Function,-,__locale_mb_cur_max,int, +Function,+,__retarget_lock_acquire,void,_LOCK_T +Function,+,__retarget_lock_acquire_recursive,void,_LOCK_T +Function,-,__retarget_lock_close,void,_LOCK_T +Function,+,__retarget_lock_close_recursive,void,_LOCK_T +Function,-,__retarget_lock_init,void,_LOCK_T* +Function,+,__retarget_lock_init_recursive,void,_LOCK_T* +Function,+,__retarget_lock_release,void,_LOCK_T +Function,+,__retarget_lock_release_recursive,void,_LOCK_T +Function,-,__retarget_lock_try_acquire,int,_LOCK_T +Function,-,__retarget_lock_try_acquire_recursive,int,_LOCK_T +Function,-,__signbitd,int,double +Function,-,__signbitf,int,float +Function,-,__signgam,int*, +Function,-,__srget_r,int,"_reent*, FILE*" +Function,-,__swbuf_r,int,"_reent*, int, FILE*" +Function,-,__utoa,char*,"unsigned, char*, int" +Function,+,__wrap___assert,void,"const char*, int, const char*" +Function,+,__wrap___assert_func,void,"const char*, int, const char*, const char*" +Function,+,__wrap_fflush,int,FILE* +Function,+,__wrap_printf,int,"const char*, ..." +Function,+,__wrap_putc,int,"int, FILE*" +Function,+,__wrap_putchar,int,int +Function,+,__wrap_puts,int,const char* +Function,+,__wrap_snprintf,int,"char*, size_t, const char*, ..." +Function,+,__wrap_vsnprintf,int,"char*, size_t, const char*, va_list" +Function,-,_asiprintf_r,int,"_reent*, char**, const char*, ..." +Function,-,_asniprintf_r,char*,"_reent*, char*, size_t*, const char*, ..." +Function,-,_asnprintf_r,char*,"_reent*, char*, size_t*, const char*, ..." +Function,-,_asprintf_r,int,"_reent*, char**, const char*, ..." +Function,-,_atoi_r,int,"_reent*, const char*" +Function,-,_atol_r,long,"_reent*, const char*" +Function,-,_atoll_r,long long,"_reent*, const char*" +Function,-,_calloc_r,void*,"_reent*, size_t, size_t" +Function,-,_diprintf_r,int,"_reent*, int, const char*, ..." +Function,-,_dprintf_r,int,"_reent*, int, const char*, ..." +Function,-,_drand48_r,double,_reent* +Function,-,_dtoa_r,char*,"_reent*, double, int, int, int*, int*, char**" +Function,-,_erand48_r,double,"_reent*, unsigned short[3]" +Function,-,_fclose_r,int,"_reent*, FILE*" +Function,-,_fcloseall_r,int,_reent* +Function,-,_fdopen_r,FILE*,"_reent*, int, const char*" +Function,-,_fflush_r,int,"_reent*, FILE*" +Function,-,_fgetc_r,int,"_reent*, FILE*" +Function,-,_fgetc_unlocked_r,int,"_reent*, FILE*" +Function,-,_fgetpos_r,int,"_reent*, FILE*, fpos_t*" +Function,-,_fgets_r,char*,"_reent*, char*, int, FILE*" +Function,-,_fgets_unlocked_r,char*,"_reent*, char*, int, FILE*" +Function,-,_findenv,char*,"const char*, int*" +Function,-,_findenv_r,char*,"_reent*, const char*, int*" +Function,-,_fiprintf_r,int,"_reent*, FILE*, const char*, ..." +Function,-,_fiscanf_r,int,"_reent*, FILE*, const char*, ..." +Function,-,_fmemopen_r,FILE*,"_reent*, void*, size_t, const char*" +Function,-,_fopen_r,FILE*,"_reent*, const char*, const char*" +Function,-,_fopencookie_r,FILE*,"_reent*, void*, const char*, cookie_io_functions_t" +Function,-,_fprintf_r,int,"_reent*, FILE*, const char*, ..." +Function,-,_fpurge_r,int,"_reent*, FILE*" +Function,-,_fputc_r,int,"_reent*, int, FILE*" +Function,-,_fputc_unlocked_r,int,"_reent*, int, FILE*" +Function,-,_fputs_r,int,"_reent*, const char*, FILE*" +Function,-,_fputs_unlocked_r,int,"_reent*, const char*, FILE*" +Function,-,_fread_r,size_t,"_reent*, void*, size_t, size_t, FILE*" +Function,-,_fread_unlocked_r,size_t,"_reent*, void*, size_t, size_t, FILE*" +Function,-,_free_r,void,"_reent*, void*" +Function,-,_freopen_r,FILE*,"_reent*, const char*, const char*, FILE*" +Function,-,_fscanf_r,int,"_reent*, FILE*, const char*, ..." +Function,-,_fseek_r,int,"_reent*, FILE*, long, int" +Function,-,_fseeko_r,int,"_reent*, FILE*, _off_t, int" +Function,-,_fsetpos_r,int,"_reent*, FILE*, const fpos_t*" +Function,-,_ftell_r,long,"_reent*, FILE*" +Function,-,_ftello_r,_off_t,"_reent*, FILE*" +Function,-,_funopen_r,FILE*,"_reent*, const void*, int (*)(void*, char*, int), int (*)(void*, const char*, int), fpos_t (*)(void*, fpos_t, int), int (*)(void*)" +Function,-,_fwrite_r,size_t,"_reent*, const void*, size_t, size_t, FILE*" +Function,-,_fwrite_unlocked_r,size_t,"_reent*, const void*, size_t, size_t, FILE*" +Function,-,_getc_r,int,"_reent*, FILE*" +Function,-,_getc_unlocked_r,int,"_reent*, FILE*" +Function,-,_getchar_r,int,_reent* +Function,-,_getchar_unlocked_r,int,_reent* +Function,-,_getenv_r,char*,"_reent*, const char*" +Function,-,_gets_r,char*,"_reent*, char*" +Function,-,_iprintf_r,int,"_reent*, const char*, ..." +Function,-,_iscanf_r,int,"_reent*, const char*, ..." +Function,-,_jrand48_r,long,"_reent*, unsigned short[3]" +Function,-,_l64a_r,char*,"_reent*, long" +Function,-,_lcong48_r,void,"_reent*, unsigned short[7]" +Function,-,_lrand48_r,long,_reent* +Function,-,_malloc_r,void*,"_reent*, size_t" +Function,-,_mblen_r,int,"_reent*, const char*, size_t, _mbstate_t*" +Function,-,_mbstowcs_r,size_t,"_reent*, wchar_t*, const char*, size_t, _mbstate_t*" +Function,-,_mbtowc_r,int,"_reent*, wchar_t*, const char*, size_t, _mbstate_t*" +Function,-,_mkdtemp_r,char*,"_reent*, char*" +Function,-,_mkostemp_r,int,"_reent*, char*, int" +Function,-,_mkostemps_r,int,"_reent*, char*, int, int" +Function,-,_mkstemp_r,int,"_reent*, char*" +Function,-,_mkstemps_r,int,"_reent*, char*, int" +Function,-,_mktemp_r,char*,"_reent*, char*" +Function,-,_mrand48_r,long,_reent* +Function,-,_mstats_r,void,"_reent*, char*" +Function,-,_nrand48_r,long,"_reent*, unsigned short[3]" +Function,-,_open_memstream_r,FILE*,"_reent*, char**, size_t*" +Function,-,_perror_r,void,"_reent*, const char*" +Function,-,_printf_r,int,"_reent*, const char*, ..." +Function,-,_putc_r,int,"_reent*, int, FILE*" +Function,-,_putc_unlocked_r,int,"_reent*, int, FILE*" +Function,-,_putchar,void,char +Function,-,_putchar_r,int,"_reent*, int" +Function,-,_putchar_unlocked_r,int,"_reent*, int" +Function,-,_putenv_r,int,"_reent*, char*" +Function,-,_puts_r,int,"_reent*, const char*" +Function,-,_realloc_r,void*,"_reent*, void*, size_t" +Function,-,_reallocf_r,void*,"_reent*, void*, size_t" +Function,-,_reclaim_reent,void,_reent* +Function,-,_remove_r,int,"_reent*, const char*" +Function,-,_rename_r,int,"_reent*, const char*, const char*" +Function,-,_rewind_r,void,"_reent*, FILE*" +Function,-,_scanf_r,int,"_reent*, const char*, ..." +Function,-,_seed48_r,unsigned short*,"_reent*, unsigned short[3]" +Function,-,_setenv_r,int,"_reent*, const char*, const char*, int" +Function,-,_siprintf_r,int,"_reent*, char*, const char*, ..." +Function,-,_siscanf_r,int,"_reent*, const char*, const char*, ..." +Function,-,_sniprintf_r,int,"_reent*, char*, size_t, const char*, ..." +Function,-,_snprintf_r,int,"_reent*, char*, size_t, const char*, ..." +Function,-,_sprintf_r,int,"_reent*, char*, const char*, ..." +Function,-,_srand48_r,void,"_reent*, long" +Function,-,_sscanf_r,int,"_reent*, const char*, const char*, ..." +Function,-,_strdup_r,char*,"_reent*, const char*" +Function,-,_strerror_r,char*,"_reent*, int, int, int*" +Function,-,_strndup_r,char*,"_reent*, const char*, size_t" +Function,-,_strtod_r,double,"_reent*, const char*, char**" +Function,-,_strtol_r,long,"_reent*, const char*, char**, int" +Function,-,_strtold_r,long double,"_reent*, const char*, char**" +Function,-,_strtoll_r,long long,"_reent*, const char*, char**, int" +Function,-,_strtoul_r,unsigned long,"_reent*, const char*, char**, int" +Function,-,_strtoull_r,unsigned long long,"_reent*, const char*, char**, int" +Function,-,_system_r,int,"_reent*, const char*" +Function,-,_tempnam_r,char*,"_reent*, const char*, const char*" +Function,-,_tmpfile_r,FILE*,_reent* +Function,-,_tmpnam_r,char*,"_reent*, char*" +Function,-,_ungetc_r,int,"_reent*, int, FILE*" +Function,-,_unsetenv_r,int,"_reent*, const char*" +Function,-,_vasiprintf_r,int,"_reent*, char**, const char*, __gnuc_va_list" +Function,-,_vasniprintf_r,char*,"_reent*, char*, size_t*, const char*, __gnuc_va_list" +Function,-,_vasnprintf_r,char*,"_reent*, char*, size_t*, const char*, __gnuc_va_list" +Function,-,_vasprintf_r,int,"_reent*, char**, const char*, __gnuc_va_list" +Function,-,_vdiprintf_r,int,"_reent*, int, const char*, __gnuc_va_list" +Function,-,_vdprintf_r,int,"_reent*, int, const char*, __gnuc_va_list" +Function,-,_vfiprintf_r,int,"_reent*, FILE*, const char*, __gnuc_va_list" +Function,-,_vfiscanf_r,int,"_reent*, FILE*, const char*, __gnuc_va_list" +Function,-,_vfprintf_r,int,"_reent*, FILE*, const char*, __gnuc_va_list" +Function,-,_vfscanf_r,int,"_reent*, FILE*, const char*, __gnuc_va_list" +Function,-,_viprintf_r,int,"_reent*, const char*, __gnuc_va_list" +Function,-,_viscanf_r,int,"_reent*, const char*, __gnuc_va_list" +Function,-,_vprintf_r,int,"_reent*, const char*, __gnuc_va_list" +Function,-,_vscanf_r,int,"_reent*, const char*, __gnuc_va_list" +Function,-,_vsiprintf_r,int,"_reent*, char*, const char*, __gnuc_va_list" +Function,-,_vsiscanf_r,int,"_reent*, const char*, const char*, __gnuc_va_list" +Function,-,_vsniprintf_r,int,"_reent*, char*, size_t, const char*, __gnuc_va_list" +Function,-,_vsnprintf_r,int,"_reent*, char*, size_t, const char*, __gnuc_va_list" +Function,-,_vsprintf_r,int,"_reent*, char*, const char*, __gnuc_va_list" +Function,-,_vsscanf_r,int,"_reent*, const char*, const char*, __gnuc_va_list" +Function,-,_wcstombs_r,size_t,"_reent*, char*, const wchar_t*, size_t, _mbstate_t*" +Function,-,_wctomb_r,int,"_reent*, char*, wchar_t, _mbstate_t*" +Function,-,a64l,long,const char* +Function,+,abort,void, +Function,-,abs,int,int +Function,-,acos,double,double +Function,-,acosf,float,float +Function,-,acosh,double,double +Function,-,acoshf,float,float +Function,-,acoshl,long double,long double +Function,-,acosl,long double,long double +Function,+,acquire_mutex,void*,"ValueMutex*, uint32_t" +Function,-,aligned_alloc,void*,"size_t, size_t" +Function,-,aligned_free,void,void* +Function,-,aligned_malloc,void*,"size_t, size_t" +Function,-,arc4random,__uint32_t, +Function,-,arc4random_buf,void,"void*, size_t" +Function,-,arc4random_uniform,__uint32_t,__uint32_t +Function,+,args_char_to_hex,_Bool,"char, char, uint8_t*" +Function,+,args_get_first_word_length,size_t,string_t +Function,+,args_length,size_t,string_t +Function,+,args_read_hex_bytes,_Bool,"string_t, uint8_t*, size_t" +Function,+,args_read_int_and_trim,_Bool,"string_t, int*" +Function,+,args_read_probably_quoted_string_and_trim,_Bool,"string_t, string_t" +Function,+,args_read_string_and_trim,_Bool,"string_t, string_t" +Function,-,asin,double,double +Function,-,asinf,float,float +Function,-,asinh,double,double +Function,-,asinhf,float,float +Function,-,asinhl,long double,long double +Function,-,asinl,long double,long double +Function,-,asiprintf,int,"char**, const char*, ..." +Function,-,asniprintf,char*,"char*, size_t*, const char*, ..." +Function,-,asnprintf,char*,"char*, size_t*, const char*, ..." +Function,-,asprintf,int,"char**, const char*, ..." +Function,-,at_quick_exit,int,void (*)() +Function,-,atan,double,double +Function,-,atan2,double,"double, double" +Function,-,atan2f,float,"float, float" +Function,-,atan2l,long double,"long double, long double" +Function,-,atanf,float,float +Function,-,atanh,double,double +Function,-,atanhf,float,float +Function,-,atanhl,long double,long double +Function,-,atanl,long double,long double +Function,-,atexit,int,void (*)() +Function,-,atof,double,const char* +Function,-,atoff,float,const char* +Function,+,atoi,int,const char* +Function,-,atol,long,const char* +Function,-,atoll,long long,const char* +Function,-,basename,char*,const char* +Function,-,bcmp,int,"const void*, const void*, size_t" +Function,-,bcopy,void,"const void*, void*, size_t" +Function,+,ble_app_get_key_storage_buff,void,"uint8_t**, uint16_t*" +Function,+,ble_app_init,_Bool, +Function,+,ble_app_thread_stop,void, +Function,+,ble_glue_force_c2_mode,BleGlueCommandResult,BleGlueC2Mode +Function,-,ble_glue_fus_get_status,BleGlueCommandResult, +Function,-,ble_glue_fus_stack_delete,BleGlueCommandResult, +Function,-,ble_glue_fus_stack_install,BleGlueCommandResult,"uint32_t, uint32_t" +Function,-,ble_glue_fus_wait_operation,BleGlueCommandResult, +Function,+,ble_glue_get_c2_info,const BleGlueC2Info*, +Function,-,ble_glue_get_c2_status,BleGlueStatus, +Function,+,ble_glue_init,void, +Function,+,ble_glue_is_alive,_Bool, +Function,+,ble_glue_is_radio_stack_ready,_Bool, +Function,+,ble_glue_reinit_c2,_Bool, +Function,+,ble_glue_set_key_storage_changed_callback,void,"BleGlueKeyStorageChangedCallback, void*" +Function,+,ble_glue_start,_Bool, +Function,+,ble_glue_thread_stop,void, +Function,+,ble_glue_wait_for_c2_start,_Bool,int32_t +Function,-,bsearch,void*,"const void*, const void*, size_t, size_t, __compar_fn_t" +Function,+,bt_disconnect,void,Bt* +Function,+,bt_forget_bonded_devices,void,Bt* +Function,+,bt_set_profile,_Bool,"Bt*, BtProfile" +Function,+,bt_set_status_changed_callback,void,"Bt*, BtStatusChangedCallback, void*" +Function,+,button_menu_add_item,ButtonMenuItem*,"ButtonMenu*, const char*, int32_t, ButtonMenuItemCallback, ButtonMenuItemType, void*" +Function,+,button_menu_alloc,ButtonMenu*, +Function,+,button_menu_free,void,ButtonMenu* +Function,+,button_menu_get_view,View*,ButtonMenu* +Function,+,button_menu_reset,void,ButtonMenu* +Function,+,button_menu_set_header,void,"ButtonMenu*, const char*" +Function,+,button_menu_set_selected_item,void,"ButtonMenu*, uint32_t" +Function,+,button_panel_add_item,void,"ButtonPanel*, uint32_t, uint16_t, uint16_t, uint16_t, uint16_t, const Icon*, const Icon*, ButtonItemCallback, void*" +Function,+,button_panel_add_label,void,"ButtonPanel*, uint16_t, uint16_t, Font, const char*" +Function,+,button_panel_alloc,ButtonPanel*, +Function,+,button_panel_free,void,ButtonPanel* +Function,+,button_panel_get_view,View*,ButtonPanel* +Function,+,button_panel_reserve,void,"ButtonPanel*, size_t, size_t" +Function,+,button_panel_reset,void,ButtonPanel* +Function,+,byte_input_alloc,ByteInput*, +Function,+,byte_input_free,void,ByteInput* +Function,+,byte_input_get_view,View*,ByteInput* +Function,+,byte_input_set_header_text,void,"ByteInput*, const char*" +Function,+,byte_input_set_result_callback,void,"ByteInput*, ByteInputCallback, ByteChangedCallback, void*, uint8_t*, uint8_t" +Function,-,bzero,void,"void*, size_t" +Function,-,calloc,void*,"size_t, size_t" +Function,+,canvas_clear,void,Canvas* +Function,+,canvas_current_font_height,uint8_t,Canvas* +Function,+,canvas_draw_bitmap,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, const uint8_t*" +Function,+,canvas_draw_box,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t" +Function,+,canvas_draw_circle,void,"Canvas*, uint8_t, uint8_t, uint8_t" +Function,+,canvas_draw_disc,void,"Canvas*, uint8_t, uint8_t, uint8_t" +Function,+,canvas_draw_dot,void,"Canvas*, uint8_t, uint8_t" +Function,+,canvas_draw_frame,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t" +Function,+,canvas_draw_glyph,void,"Canvas*, uint8_t, uint8_t, uint16_t" +Function,+,canvas_draw_icon,void,"Canvas*, uint8_t, uint8_t, const Icon*" +Function,+,canvas_draw_icon_animation,void,"Canvas*, uint8_t, uint8_t, IconAnimation*" +Function,+,canvas_draw_line,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t" +Function,+,canvas_draw_rbox,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t" +Function,+,canvas_draw_rframe,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t" +Function,+,canvas_draw_str,void,"Canvas*, uint8_t, uint8_t, const char*" +Function,+,canvas_draw_str_aligned,void,"Canvas*, uint8_t, uint8_t, Align, Align, const char*" +Function,+,canvas_draw_triangle,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, CanvasDirection" +Function,+,canvas_draw_xbm,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, const uint8_t*" +Function,+,canvas_get_font_params,CanvasFontParameters*,"Canvas*, Font" +Function,+,canvas_glyph_width,uint8_t,"Canvas*, char" +Function,+,canvas_height,uint8_t,Canvas* +Function,+,canvas_invert_color,void,Canvas* +Function,+,canvas_set_bitmap_mode,void,"Canvas*, _Bool" +Function,+,canvas_set_color,void,"Canvas*, Color" +Function,+,canvas_set_font,void,"Canvas*, Font" +Function,+,canvas_set_font_direction,void,"Canvas*, CanvasDirection" +Function,+,canvas_string_width,uint16_t,"Canvas*, const char*" +Function,+,canvas_width,uint8_t,Canvas* +Function,-,cbrt,double,double +Function,-,cbrtf,float,float +Function,-,cbrtl,long double,long double +Function,+,cc1101_calibrate,void,FuriHalSpiBusHandle* +Function,+,cc1101_flush_rx,void,FuriHalSpiBusHandle* +Function,+,cc1101_flush_tx,void,FuriHalSpiBusHandle* +Function,-,cc1101_get_partnumber,uint8_t,FuriHalSpiBusHandle* +Function,+,cc1101_get_rssi,uint8_t,FuriHalSpiBusHandle* +Function,+,cc1101_get_status,CC1101Status,FuriHalSpiBusHandle* +Function,-,cc1101_get_version,uint8_t,FuriHalSpiBusHandle* +Function,+,cc1101_read_fifo,uint8_t,"FuriHalSpiBusHandle*, uint8_t*, uint8_t*" +Function,+,cc1101_read_reg,CC1101Status,"FuriHalSpiBusHandle*, uint8_t, uint8_t*" +Function,+,cc1101_reset,void,FuriHalSpiBusHandle* +Function,+,cc1101_set_frequency,uint32_t,"FuriHalSpiBusHandle*, uint32_t" +Function,-,cc1101_set_intermediate_frequency,uint32_t,"FuriHalSpiBusHandle*, uint32_t" +Function,+,cc1101_set_pa_table,void,"FuriHalSpiBusHandle*, const uint8_t[8]" +Function,+,cc1101_shutdown,void,FuriHalSpiBusHandle* +Function,+,cc1101_strobe,CC1101Status,"FuriHalSpiBusHandle*, uint8_t" +Function,+,cc1101_switch_to_idle,void,FuriHalSpiBusHandle* +Function,+,cc1101_switch_to_rx,void,FuriHalSpiBusHandle* +Function,+,cc1101_switch_to_tx,void,FuriHalSpiBusHandle* +Function,+,cc1101_write_fifo,uint8_t,"FuriHalSpiBusHandle*, const uint8_t*, uint8_t" +Function,+,cc1101_write_reg,CC1101Status,"FuriHalSpiBusHandle*, uint8_t, uint8_t" +Function,-,ceil,double,double +Function,-,ceilf,float,float +Function,-,ceill,long double,long double +Function,-,cfree,void,void* +Function,-,clearerr,void,FILE* +Function,-,clearerr_unlocked,void,FILE* +Function,+,cli_add_command,void,"Cli*, const char*, CliCommandFlag, CliCallback, void*" +Function,+,cli_cmd_interrupt_received,_Bool,Cli* +Function,+,cli_delete_command,void,"Cli*, const char*" +Function,+,cli_getc,char,Cli* +Function,+,cli_is_connected,_Bool,Cli* +Function,+,cli_nl,void, +Function,+,cli_print_usage,void,"const char*, const char*, const char*" +Function,+,cli_read,size_t,"Cli*, uint8_t*, size_t" +Function,+,cli_read_timeout,size_t,"Cli*, uint8_t*, size_t, uint32_t" +Function,+,cli_session_close,void,Cli* +Function,+,cli_session_open,void,"Cli*, void*" +Function,+,cli_write,void,"Cli*, const uint8_t*, size_t" +Function,-,copysign,double,"double, double" +Function,-,copysignf,float,"float, float" +Function,-,copysignl,long double,"long double, long double" +Function,-,cos,double,double +Function,-,cosf,float,float +Function,-,cosh,double,double +Function,-,coshf,float,float +Function,-,coshl,long double,long double +Function,-,cosl,long double,long double +Function,+,crc32_calc_buffer,uint32_t,"uint32_t, const void*, size_t" +Function,+,crc32_calc_file,uint32_t,"File*, const FileCrcProgressCb, void*" +Function,-,ctermid,char*,char* +Function,-,cuserid,char*,char* +Function,+,delete_mutex,_Bool,ValueMutex* +Function,+,dialog_ex_alloc,DialogEx*, +Function,+,dialog_ex_disable_extended_events,void,DialogEx* +Function,+,dialog_ex_enable_extended_events,void,DialogEx* +Function,+,dialog_ex_free,void,DialogEx* +Function,+,dialog_ex_get_view,View*,DialogEx* +Function,+,dialog_ex_reset,void,DialogEx* +Function,+,dialog_ex_set_center_button_text,void,"DialogEx*, const char*" +Function,+,dialog_ex_set_context,void,"DialogEx*, void*" +Function,+,dialog_ex_set_header,void,"DialogEx*, const char*, uint8_t, uint8_t, Align, Align" +Function,+,dialog_ex_set_icon,void,"DialogEx*, uint8_t, uint8_t, const Icon*" +Function,+,dialog_ex_set_left_button_text,void,"DialogEx*, const char*" +Function,+,dialog_ex_set_result_callback,void,"DialogEx*, DialogExResultCallback" +Function,+,dialog_ex_set_right_button_text,void,"DialogEx*, const char*" +Function,+,dialog_ex_set_text,void,"DialogEx*, const char*, uint8_t, uint8_t, Align, Align" +Function,+,dialog_file_browser_set_basic_options,void,"DialogsFileBrowserOptions*, const char*, const Icon*" +Function,+,dialog_file_browser_show,_Bool,"DialogsApp*, string_ptr, string_ptr, const DialogsFileBrowserOptions*" +Function,+,dialog_message_alloc,DialogMessage*, +Function,+,dialog_message_free,void,DialogMessage* +Function,+,dialog_message_set_buttons,void,"DialogMessage*, const char*, const char*, const char*" +Function,+,dialog_message_set_header,void,"DialogMessage*, const char*, uint8_t, uint8_t, Align, Align" +Function,+,dialog_message_set_icon,void,"DialogMessage*, const Icon*, uint8_t, uint8_t" +Function,+,dialog_message_set_text,void,"DialogMessage*, const char*, uint8_t, uint8_t, Align, Align" +Function,+,dialog_message_show,DialogMessageButton,"DialogsApp*, const DialogMessage*" +Function,+,dialog_message_show_storage_error,void,"DialogsApp*, const char*" +Function,-,digital_signal_alloc,DigitalSignal*,uint32_t +Function,-,digital_signal_append,_Bool,"DigitalSignal*, DigitalSignal*" +Function,-,digital_signal_free,void,DigitalSignal* +Function,-,digital_signal_get_edge,uint32_t,"DigitalSignal*, uint32_t" +Function,-,digital_signal_get_edges_cnt,uint32_t,DigitalSignal* +Function,-,digital_signal_get_start_level,_Bool,DigitalSignal* +Function,-,digital_signal_prepare_arr,void,DigitalSignal* +Function,-,digital_signal_send,void,"DigitalSignal*, const GpioPin*" +Function,-,diprintf,int,"int, const char*, ..." +Function,+,dir_walk_alloc,DirWalk*,Storage* +Function,+,dir_walk_close,void,DirWalk* +Function,+,dir_walk_free,void,DirWalk* +Function,+,dir_walk_get_error,FS_Error,DirWalk* +Function,+,dir_walk_open,_Bool,"DirWalk*, const char*" +Function,+,dir_walk_read,DirWalkResult,"DirWalk*, string_t, FileInfo*" +Function,+,dir_walk_set_filter_cb,void,"DirWalk*, DirWalkFilterCb, void*" +Function,+,dir_walk_set_recursive,void,"DirWalk*, _Bool" +Function,-,div,div_t,"int, int" +Function,+,dolphin_deed,void,"Dolphin*, DolphinDeed" +Function,+,dolphin_deed_get_app,DolphinApp,DolphinDeed +Function,+,dolphin_deed_get_app_limit,uint8_t,DolphinApp +Function,+,dolphin_deed_get_weight,uint8_t,DolphinDeed +Function,+,dolphin_flush,void,Dolphin* +Function,+,dolphin_get_pubsub,FuriPubSub*,Dolphin* +Function,+,dolphin_stats,DolphinStats,Dolphin* +Function,+,dolphin_upgrade_level,void,Dolphin* +Function,-,dprintf,int,"int, const char*, ..." +Function,-,drand48,double, +Function,-,drem,double,"double, double" +Function,-,dremf,float,"float, float" +Function,-,eTaskConfirmSleepModeStatus,eSleepModeStatus, +Function,-,eTaskGetState,eTaskState,TaskHandle_t +Function,+,elements_bold_rounded_frame,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t" +Function,+,elements_bubble,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t" +Function,+,elements_bubble_str,void,"Canvas*, uint8_t, uint8_t, const char*, Align, Align" +Function,+,elements_button_center,void,"Canvas*, const char*" +Function,+,elements_button_left,void,"Canvas*, const char*" +Function,+,elements_button_right,void,"Canvas*, const char*" +Function,+,elements_frame,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t" +Function,+,elements_multiline_text,void,"Canvas*, uint8_t, uint8_t, const char*" +Function,+,elements_multiline_text_aligned,void,"Canvas*, uint8_t, uint8_t, Align, Align, const char*" +Function,+,elements_multiline_text_framed,void,"Canvas*, uint8_t, uint8_t, const char*" +Function,+,elements_progress_bar,void,"Canvas*, uint8_t, uint8_t, uint8_t, float" +Function,+,elements_scrollbar,void,"Canvas*, uint16_t, uint16_t" +Function,+,elements_scrollbar_pos,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint16_t, uint16_t" +Function,+,elements_slightly_rounded_box,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t" +Function,+,elements_slightly_rounded_frame,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t" +Function,+,elements_string_fit_width,void,"Canvas*, string_t, uint8_t" +Function,+,elements_text_box,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, Align, Align, const char*, _Bool" +Function,+,empty_screen_alloc,EmptyScreen*, +Function,+,empty_screen_free,void,EmptyScreen* +Function,+,empty_screen_get_view,View*,EmptyScreen* +Function,-,erand48,double,unsigned short[3] +Function,-,erf,double,double +Function,-,erfc,double,double +Function,-,erfcf,float,float +Function,-,erfcl,long double,long double +Function,-,erff,float,float +Function,-,erfl,long double,long double +Function,-,exit,void,int +Function,-,exp,double,double +Function,-,exp10,double,double +Function,-,exp10f,float,float +Function,-,exp2,double,double +Function,-,exp2f,float,float +Function,-,exp2l,long double,long double +Function,-,expf,float,float +Function,-,expl,long double,long double +Function,-,explicit_bzero,void,"void*, size_t" +Function,-,expm1,double,double +Function,-,expm1f,float,float +Function,-,expm1l,long double,long double +Function,-,fabs,double,double +Function,-,fabsf,float,float +Function,-,fabsl,long double,long double +Function,-,fclose,int,FILE* +Function,-,fcloseall,int, +Function,-,fdim,double,"double, double" +Function,-,fdimf,float,"float, float" +Function,-,fdiml,long double,"long double, long double" +Function,-,fdopen,FILE*,"int, const char*" +Function,-,feof,int,FILE* +Function,-,feof_unlocked,int,FILE* +Function,-,ferror,int,FILE* +Function,-,ferror_unlocked,int,FILE* +Function,-,fflush,int,FILE* +Function,-,fflush_unlocked,int,FILE* +Function,-,ffs,int,int +Function,-,ffsl,int,long +Function,-,ffsll,int,long long +Function,-,fgetc,int,FILE* +Function,-,fgetc_unlocked,int,FILE* +Function,-,fgetpos,int,"FILE*, fpos_t*" +Function,-,fgets,char*,"char*, int, FILE*" +Function,-,fgets_unlocked,char*,"char*, int, FILE*" +Function,+,file_browser_alloc,FileBrowser*,string_ptr +Function,+,file_browser_configure,void,"FileBrowser*, const char*, _Bool, const Icon*, _Bool" +Function,+,file_browser_free,void,FileBrowser* +Function,+,file_browser_get_view,View*,FileBrowser* +Function,+,file_browser_set_callback,void,"FileBrowser*, FileBrowserCallback, void*" +Function,+,file_browser_set_item_callback,void,"FileBrowser*, FileBrowserLoadItemCallback, void*" +Function,+,file_browser_start,void,"FileBrowser*, string_t" +Function,+,file_browser_stop,void,FileBrowser* +Function,+,file_browser_worker_alloc,BrowserWorker*,"string_t, const char*, _Bool" +Function,+,file_browser_worker_folder_enter,void,"BrowserWorker*, string_t, int32_t" +Function,+,file_browser_worker_folder_exit,void,BrowserWorker* +Function,+,file_browser_worker_folder_refresh,void,"BrowserWorker*, int32_t" +Function,+,file_browser_worker_free,void,BrowserWorker* +Function,+,file_browser_worker_load,void,"BrowserWorker*, uint32_t, uint32_t" +Function,+,file_browser_worker_set_callback_context,void,"BrowserWorker*, void*" +Function,+,file_browser_worker_set_config,void,"BrowserWorker*, string_t, const char*, _Bool" +Function,+,file_browser_worker_set_folder_callback,void,"BrowserWorker*, BrowserWorkerFolderOpenCallback" +Function,+,file_browser_worker_set_item_callback,void,"BrowserWorker*, BrowserWorkerListItemCallback" +Function,+,file_browser_worker_set_list_callback,void,"BrowserWorker*, BrowserWorkerListLoadCallback" +Function,+,file_browser_worker_set_long_load_callback,void,"BrowserWorker*, BrowserWorkerLongLoadCallback" +Function,+,file_stream_alloc,Stream*,Storage* +Function,+,file_stream_close,_Bool,Stream* +Function,+,file_stream_get_error,FS_Error,Stream* +Function,+,file_stream_open,_Bool,"Stream*, const char*, FS_AccessMode, FS_OpenMode" +Function,-,fileno,int,FILE* +Function,-,fileno_unlocked,int,FILE* +Function,+,filesystem_api_error_get_desc,const char*,FS_Error +Function,-,finite,int,double +Function,-,finitef,int,float +Function,-,finitel,int,long double +Function,-,fiprintf,int,"FILE*, const char*, ..." +Function,-,fiscanf,int,"FILE*, const char*, ..." +Function,+,flipper_application_alloc,FlipperApplication*,"Storage*, const ElfApiInterface*" +Function,+,flipper_application_free,void,FlipperApplication* +Function,-,flipper_application_get_entry_address,const void*,FlipperApplication* +Function,+,flipper_application_get_manifest,const FlipperApplicationManifest*,FlipperApplication* +Function,-,flipper_application_get_state,const FlipperApplicationState*,FlipperApplication* +Function,-,flipper_application_get_thread,FuriThread*,FlipperApplication* +Function,+,flipper_application_load_status_to_string,const char*,FlipperApplicationLoadStatus +Function,+,flipper_application_map_to_memory,FlipperApplicationLoadStatus,FlipperApplication* +Function,+,flipper_application_preload,FlipperApplicationPreloadStatus,"FlipperApplication*, const char*" +Function,-,flipper_application_preload_status_to_string,const char*,FlipperApplicationPreloadStatus +Function,+,flipper_application_spawn,FuriThread*,"FlipperApplication*, void*" +Function,+,flipper_format_buffered_file_alloc,FlipperFormat*,Storage* +Function,+,flipper_format_buffered_file_close,_Bool,FlipperFormat* +Function,+,flipper_format_buffered_file_open_existing,_Bool,"FlipperFormat*, const char*" +Function,+,flipper_format_delete_key,_Bool,"FlipperFormat*, const char*" +Function,+,flipper_format_file_alloc,FlipperFormat*,Storage* +Function,+,flipper_format_file_close,_Bool,FlipperFormat* +Function,+,flipper_format_file_open_always,_Bool,"FlipperFormat*, const char*" +Function,+,flipper_format_file_open_append,_Bool,"FlipperFormat*, const char*" +Function,+,flipper_format_file_open_existing,_Bool,"FlipperFormat*, const char*" +Function,+,flipper_format_file_open_new,_Bool,"FlipperFormat*, const char*" +Function,+,flipper_format_free,void,FlipperFormat* +Function,+,flipper_format_get_raw_stream,Stream*,FlipperFormat* +Function,+,flipper_format_get_value_count,_Bool,"FlipperFormat*, const char*, uint32_t*" +Function,+,flipper_format_insert_or_update_bool,_Bool,"FlipperFormat*, const char*, const _Bool*, const uint16_t" +Function,+,flipper_format_insert_or_update_float,_Bool,"FlipperFormat*, const char*, const float*, const uint16_t" +Function,+,flipper_format_insert_or_update_hex,_Bool,"FlipperFormat*, const char*, const uint8_t*, const uint16_t" +Function,+,flipper_format_insert_or_update_int32,_Bool,"FlipperFormat*, const char*, const int32_t*, const uint16_t" +Function,+,flipper_format_insert_or_update_string,_Bool,"FlipperFormat*, const char*, string_t" +Function,+,flipper_format_insert_or_update_string_cstr,_Bool,"FlipperFormat*, const char*, const char*" +Function,+,flipper_format_insert_or_update_uint32,_Bool,"FlipperFormat*, const char*, const uint32_t*, const uint16_t" +Function,+,flipper_format_key_exist,_Bool,"FlipperFormat*, const char*" +Function,+,flipper_format_read_bool,_Bool,"FlipperFormat*, const char*, _Bool*, const uint16_t" +Function,+,flipper_format_read_float,_Bool,"FlipperFormat*, const char*, float*, const uint16_t" +Function,+,flipper_format_read_header,_Bool,"FlipperFormat*, string_t, uint32_t*" +Function,+,flipper_format_read_hex,_Bool,"FlipperFormat*, const char*, uint8_t*, const uint16_t" +Function,+,flipper_format_read_hex_uint64,_Bool,"FlipperFormat*, const char*, uint64_t*, const uint16_t" +Function,+,flipper_format_read_int32,_Bool,"FlipperFormat*, const char*, int32_t*, const uint16_t" +Function,+,flipper_format_read_string,_Bool,"FlipperFormat*, const char*, string_t" +Function,+,flipper_format_read_uint32,_Bool,"FlipperFormat*, const char*, uint32_t*, const uint16_t" +Function,+,flipper_format_rewind,_Bool,FlipperFormat* +Function,+,flipper_format_seek_to_end,_Bool,FlipperFormat* +Function,+,flipper_format_set_strict_mode,void,"FlipperFormat*, _Bool" +Function,+,flipper_format_string_alloc,FlipperFormat*, +Function,+,flipper_format_update_bool,_Bool,"FlipperFormat*, const char*, const _Bool*, const uint16_t" +Function,+,flipper_format_update_float,_Bool,"FlipperFormat*, const char*, const float*, const uint16_t" +Function,+,flipper_format_update_hex,_Bool,"FlipperFormat*, const char*, const uint8_t*, const uint16_t" +Function,+,flipper_format_update_int32,_Bool,"FlipperFormat*, const char*, const int32_t*, const uint16_t" +Function,+,flipper_format_update_string,_Bool,"FlipperFormat*, const char*, string_t" +Function,+,flipper_format_update_string_cstr,_Bool,"FlipperFormat*, const char*, const char*" +Function,+,flipper_format_update_uint32,_Bool,"FlipperFormat*, const char*, const uint32_t*, const uint16_t" +Function,+,flipper_format_write_bool,_Bool,"FlipperFormat*, const char*, const _Bool*, const uint16_t" +Function,+,flipper_format_write_comment,_Bool,"FlipperFormat*, string_t" +Function,+,flipper_format_write_comment_cstr,_Bool,"FlipperFormat*, const char*" +Function,+,flipper_format_write_float,_Bool,"FlipperFormat*, const char*, const float*, const uint16_t" +Function,+,flipper_format_write_header,_Bool,"FlipperFormat*, string_t, const uint32_t" +Function,+,flipper_format_write_header_cstr,_Bool,"FlipperFormat*, const char*, const uint32_t" +Function,+,flipper_format_write_hex,_Bool,"FlipperFormat*, const char*, const uint8_t*, const uint16_t" +Function,+,flipper_format_write_hex_uint64,_Bool,"FlipperFormat*, const char*, const uint64_t*, const uint16_t" +Function,+,flipper_format_write_int32,_Bool,"FlipperFormat*, const char*, const int32_t*, const uint16_t" +Function,+,flipper_format_write_string,_Bool,"FlipperFormat*, const char*, string_t" +Function,+,flipper_format_write_string_cstr,_Bool,"FlipperFormat*, const char*, const char*" +Function,+,flipper_format_write_uint32,_Bool,"FlipperFormat*, const char*, const uint32_t*, const uint16_t" +Function,-,flockfile,void,FILE* +Function,-,floor,double,double +Function,-,floorf,float,float +Function,-,floorl,long double,long double +Function,-,fls,int,int +Function,-,flsl,int,long +Function,-,flsll,int,long long +Function,-,fma,double,"double, double, double" +Function,-,fmaf,float,"float, float, float" +Function,-,fmal,long double,"long double, long double, long double" +Function,-,fmax,double,"double, double" +Function,-,fmaxf,float,"float, float" +Function,-,fmaxl,long double,"long double, long double" +Function,-,fmemopen,FILE*,"void*, size_t, const char*" +Function,-,fmin,double,"double, double" +Function,-,fminf,float,"float, float" +Function,-,fminl,long double,"long double, long double" +Function,-,fmod,double,"double, double" +Function,-,fmodf,float,"float, float" +Function,-,fmodl,long double,"long double, long double" +Function,-,fopen,FILE*,"const char*, const char*" +Function,-,fopencookie,FILE*,"void*, const char*, cookie_io_functions_t" +Function,-,fprintf,int,"FILE*, const char*, ..." +Function,-,fpurge,int,FILE* +Function,-,fputc,int,"int, FILE*" +Function,-,fputc_unlocked,int,"int, FILE*" +Function,-,fputs,int,"const char*, FILE*" +Function,-,fputs_unlocked,int,"const char*, FILE*" +Function,-,fread,size_t,"void*, size_t, size_t, FILE*" +Function,-,fread_unlocked,size_t,"void*, size_t, size_t, FILE*" +Function,+,free,void,void* +Function,-,freopen,FILE*,"const char*, const char*, FILE*" +Function,-,frexp,double,"double, int*" +Function,-,frexpf,float,"float, int*" +Function,-,frexpl,long double,"long double, int*" +Function,-,fscanf,int,"FILE*, const char*, ..." +Function,-,fseek,int,"FILE*, long, int" +Function,-,fseeko,int,"FILE*, off_t, int" +Function,-,fsetpos,int,"FILE*, const fpos_t*" +Function,-,ftell,long,FILE* +Function,-,ftello,off_t,FILE* +Function,-,ftrylockfile,int,FILE* +Function,-,funlockfile,void,FILE* +Function,-,funopen,FILE*,"const void*, int (*)(void*, char*, int), int (*)(void*, const char*, int), fpos_t (*)(void*, fpos_t, int), int (*)(void*)" +Function,+,furi_crash,void,const char* +Function,+,furi_delay_ms,void,uint32_t +Function,+,furi_delay_tick,void,uint32_t +Function,+,furi_delay_until_tick,FuriStatus,uint32_t +Function,+,furi_delay_us,void,uint32_t +Function,+,furi_event_flag_alloc,FuriEventFlag*, +Function,+,furi_event_flag_clear,uint32_t,"FuriEventFlag*, uint32_t" +Function,+,furi_event_flag_free,void,FuriEventFlag* +Function,+,furi_event_flag_get,uint32_t,FuriEventFlag* +Function,+,furi_event_flag_set,uint32_t,"FuriEventFlag*, uint32_t" +Function,+,furi_event_flag_wait,uint32_t,"FuriEventFlag*, uint32_t, uint32_t, uint32_t" +Function,+,furi_get_tick,uint32_t, +Function,+,furi_hal_bt_change_app,_Bool,"FuriHalBtProfile, GapEventCallback, void*" +Function,+,furi_hal_bt_clear_white_list,_Bool, +Function,+,furi_hal_bt_dump_state,void,string_t +Function,+,furi_hal_bt_ensure_c2_mode,_Bool,BleGlueC2Mode +Function,+,furi_hal_bt_get_key_storage_buff,void,"uint8_t**, uint16_t*" +Function,+,furi_hal_bt_get_radio_stack,FuriHalBtStack, +Function,+,furi_hal_bt_get_rssi,float, +Function,+,furi_hal_bt_get_transmitted_packets,uint32_t, +Function,+,furi_hal_bt_hid_consumer_key_press,_Bool,uint16_t +Function,+,furi_hal_bt_hid_consumer_key_release,_Bool,uint16_t +Function,+,furi_hal_bt_hid_consumer_key_release_all,_Bool, +Function,+,furi_hal_bt_hid_kb_press,_Bool,uint16_t +Function,+,furi_hal_bt_hid_kb_release,_Bool,uint16_t +Function,+,furi_hal_bt_hid_kb_release_all,_Bool, +Function,+,furi_hal_bt_hid_mouse_move,_Bool,"int8_t, int8_t" +Function,+,furi_hal_bt_hid_mouse_press,_Bool,uint8_t +Function,+,furi_hal_bt_hid_mouse_release,_Bool,uint8_t +Function,+,furi_hal_bt_hid_mouse_release_all,_Bool, +Function,+,furi_hal_bt_hid_mouse_scroll,_Bool,int8_t +Function,+,furi_hal_bt_hid_start,void, +Function,+,furi_hal_bt_hid_stop,void, +Function,-,furi_hal_bt_init,void, +Function,+,furi_hal_bt_is_active,_Bool, +Function,+,furi_hal_bt_is_alive,_Bool, +Function,+,furi_hal_bt_is_ble_gatt_gap_supported,_Bool, +Function,+,furi_hal_bt_is_testing_supported,_Bool, +Function,+,furi_hal_bt_lock_core2,void, +Function,+,furi_hal_bt_nvm_sram_sem_acquire,void, +Function,+,furi_hal_bt_nvm_sram_sem_release,void, +Function,+,furi_hal_bt_reinit,void, +Function,+,furi_hal_bt_serial_notify_buffer_is_empty,void, +Function,+,furi_hal_bt_serial_set_event_callback,void,"uint16_t, FuriHalBtSerialCallback, void*" +Function,+,furi_hal_bt_serial_start,void, +Function,+,furi_hal_bt_serial_stop,void, +Function,+,furi_hal_bt_serial_tx,_Bool,"uint8_t*, uint16_t" +Function,+,furi_hal_bt_set_key_storage_change_callback,void,"BleGlueKeyStorageChangedCallback, void*" +Function,+,furi_hal_bt_start_advertising,void, +Function,+,furi_hal_bt_start_app,_Bool,"FuriHalBtProfile, GapEventCallback, void*" +Function,+,furi_hal_bt_start_packet_rx,void,"uint8_t, uint8_t" +Function,+,furi_hal_bt_start_packet_tx,void,"uint8_t, uint8_t, uint8_t" +Function,+,furi_hal_bt_start_radio_stack,_Bool, +Function,+,furi_hal_bt_start_rx,void,uint8_t +Function,+,furi_hal_bt_start_tone_tx,void,"uint8_t, uint8_t" +Function,+,furi_hal_bt_stop_advertising,void, +Function,+,furi_hal_bt_stop_packet_test,uint16_t, +Function,+,furi_hal_bt_stop_rx,void, +Function,+,furi_hal_bt_stop_tone_tx,void, +Function,+,furi_hal_bt_unlock_core2,void, +Function,+,furi_hal_bt_update_battery_level,void,uint8_t +Function,+,furi_hal_bt_update_power_state,void, +Function,+,furi_hal_cdc_get_ctrl_line_state,uint8_t,uint8_t +Function,+,furi_hal_cdc_get_port_settings,usb_cdc_line_coding*,uint8_t +Function,+,furi_hal_cdc_receive,int32_t,"uint8_t, uint8_t*, uint16_t" +Function,+,furi_hal_cdc_send,void,"uint8_t, uint8_t*, uint16_t" +Function,+,furi_hal_cdc_set_callbacks,void,"uint8_t, CdcCallbacks*, void*" +Function,+,furi_hal_clock_deinit_early,void, +Function,-,furi_hal_clock_init,void, +Function,-,furi_hal_clock_init_early,void, +Function,-,furi_hal_clock_resume_tick,void, +Function,-,furi_hal_clock_suspend_tick,void, +Function,-,furi_hal_clock_switch_to_hsi,void, +Function,-,furi_hal_clock_switch_to_pll,void, +Function,-,furi_hal_compress_alloc,FuriHalCompress*,uint16_t +Function,-,furi_hal_compress_decode,_Bool,"FuriHalCompress*, uint8_t*, size_t, uint8_t*, size_t, size_t*" +Function,-,furi_hal_compress_encode,_Bool,"FuriHalCompress*, uint8_t*, size_t, uint8_t*, size_t, size_t*" +Function,-,furi_hal_compress_free,void,FuriHalCompress* +Function,-,furi_hal_compress_icon_decode,void,"const uint8_t*, uint8_t**" +Function,-,furi_hal_compress_icon_init,void, +Function,+,furi_hal_console_disable,void, +Function,+,furi_hal_console_enable,void, +Function,-,furi_hal_console_init,void, +Function,+,furi_hal_console_printf,void,"const char[], ..." +Function,+,furi_hal_console_puts,void,const char* +Function,+,furi_hal_console_set_tx_callback,void,"FuriHalConsoleTxCallback, void*" +Function,+,furi_hal_console_tx,void,"const uint8_t*, size_t" +Function,+,furi_hal_console_tx_with_new_line,void,"const uint8_t*, size_t" +Function,+,furi_hal_cortex_delay_us,void,uint32_t +Function,-,furi_hal_cortex_init_early,void, +Function,+,furi_hal_cortex_instructions_per_microsecond,uint32_t, +Function,+,furi_hal_crypto_decrypt,_Bool,"const uint8_t*, uint8_t*, size_t" +Function,+,furi_hal_crypto_encrypt,_Bool,"const uint8_t*, uint8_t*, size_t" +Function,-,furi_hal_crypto_init,void, +Function,+,furi_hal_crypto_store_add_key,_Bool,"FuriHalCryptoKey*, uint8_t*" +Function,+,furi_hal_crypto_store_load_key,_Bool,"uint8_t, const uint8_t*" +Function,+,furi_hal_crypto_store_unload_key,_Bool,uint8_t +Function,+,furi_hal_crypto_verify_enclave,_Bool,"uint8_t*, uint8_t*" +Function,+,furi_hal_crypto_verify_key,_Bool,uint8_t +Function,+,furi_hal_debug_disable,void, +Function,+,furi_hal_debug_enable,void, +Function,-,furi_hal_deinit_early,void, +Function,-,furi_hal_flash_erase,_Bool,uint8_t +Function,-,furi_hal_flash_get_base,size_t, +Function,-,furi_hal_flash_get_cycles_count,size_t, +Function,-,furi_hal_flash_get_free_end_address,const void*, +Function,-,furi_hal_flash_get_free_page_count,size_t, +Function,-,furi_hal_flash_get_free_page_start_address,size_t, +Function,-,furi_hal_flash_get_free_start_address,const void*, +Function,-,furi_hal_flash_get_page_number,int16_t,size_t +Function,-,furi_hal_flash_get_page_size,size_t, +Function,-,furi_hal_flash_get_read_block_size,size_t, +Function,-,furi_hal_flash_get_write_block_size,size_t, +Function,-,furi_hal_flash_init,void, +Function,-,furi_hal_flash_ob_apply,void, +Function,-,furi_hal_flash_ob_get_raw_ptr,const FuriHalFlashRawOptionByteData*, +Function,-,furi_hal_flash_ob_set_word,_Bool,"size_t, const uint32_t" +Function,-,furi_hal_flash_program_page,_Bool,"const uint8_t, const uint8_t*, uint16_t" +Function,-,furi_hal_flash_write_dword,_Bool,"size_t, uint64_t" +Function,+,furi_hal_gpio_add_int_callback,void,"const GpioPin*, GpioExtiCallback, void*" +Function,+,furi_hal_gpio_disable_int_callback,void,const GpioPin* +Function,+,furi_hal_gpio_enable_int_callback,void,const GpioPin* +Function,+,furi_hal_gpio_init,void,"const GpioPin*, const GpioMode, const GpioPull, const GpioSpeed" +Function,+,furi_hal_gpio_init_ex,void,"const GpioPin*, const GpioMode, const GpioPull, const GpioSpeed, const GpioAltFn" +Function,+,furi_hal_gpio_init_simple,void,"const GpioPin*, const GpioMode" +Function,+,furi_hal_gpio_remove_int_callback,void,const GpioPin* +Function,+,furi_hal_hid_consumer_key_press,_Bool,uint16_t +Function,+,furi_hal_hid_consumer_key_release,_Bool,uint16_t +Function,+,furi_hal_hid_get_led_state,uint8_t, +Function,+,furi_hal_hid_is_connected,_Bool, +Function,+,furi_hal_hid_kb_press,_Bool,uint16_t +Function,+,furi_hal_hid_kb_release,_Bool,uint16_t +Function,+,furi_hal_hid_kb_release_all,_Bool, +Function,+,furi_hal_hid_mouse_move,_Bool,"int8_t, int8_t" +Function,+,furi_hal_hid_mouse_press,_Bool,uint8_t +Function,+,furi_hal_hid_mouse_release,_Bool,uint8_t +Function,+,furi_hal_hid_mouse_scroll,_Bool,int8_t +Function,+,furi_hal_hid_set_state_callback,void,"HidStateCallback, void*" +Function,+,furi_hal_hid_u2f_get_request,uint32_t,uint8_t* +Function,+,furi_hal_hid_u2f_is_connected,_Bool, +Function,+,furi_hal_hid_u2f_send_response,void,"uint8_t*, uint8_t" +Function,+,furi_hal_hid_u2f_set_callback,void,"HidU2fCallback, void*" +Function,+,furi_hal_i2c_acquire,void,FuriHalI2cBusHandle* +Function,+,furi_hal_i2c_deinit_early,void, +Function,-,furi_hal_i2c_init,void, +Function,-,furi_hal_i2c_init_early,void, +Function,+,furi_hal_i2c_is_device_ready,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint32_t" +Function,+,furi_hal_i2c_read_mem,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint8_t*, uint8_t, uint32_t" +Function,+,furi_hal_i2c_read_reg_16,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint16_t*, uint32_t" +Function,+,furi_hal_i2c_read_reg_8,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint8_t*, uint32_t" +Function,+,furi_hal_i2c_release,void,FuriHalI2cBusHandle* +Function,+,furi_hal_i2c_rx,_Bool,"FuriHalI2cBusHandle*, const uint8_t, uint8_t*, const uint8_t, uint32_t" +Function,+,furi_hal_i2c_trx,_Bool,"FuriHalI2cBusHandle*, const uint8_t, const uint8_t*, const uint8_t, uint8_t*, const uint8_t, uint32_t" +Function,+,furi_hal_i2c_tx,_Bool,"FuriHalI2cBusHandle*, const uint8_t, const uint8_t*, const uint8_t, uint32_t" +Function,+,furi_hal_i2c_write_mem,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint8_t*, uint8_t, uint32_t" +Function,+,furi_hal_i2c_write_reg_16,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint16_t, uint32_t" +Function,+,furi_hal_i2c_write_reg_8,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint8_t, uint32_t" +Function,+,furi_hal_ibutton_add_interrupt,void,"GpioExtiCallback, void*" +Function,+,furi_hal_ibutton_emulate_set_next,void,uint32_t +Function,+,furi_hal_ibutton_emulate_start,void,"uint32_t, FuriHalIbuttonEmulateCallback, void*" +Function,+,furi_hal_ibutton_emulate_stop,void, +Function,-,furi_hal_ibutton_init,void, +Function,+,furi_hal_ibutton_pin_get_level,_Bool, +Function,+,furi_hal_ibutton_pin_high,void, +Function,+,furi_hal_ibutton_pin_low,void, +Function,+,furi_hal_ibutton_remove_interrupt,void, +Function,+,furi_hal_ibutton_start_drive,void, +Function,+,furi_hal_ibutton_start_drive_in_isr,void, +Function,+,furi_hal_ibutton_start_interrupt,void, +Function,+,furi_hal_ibutton_start_interrupt_in_isr,void, +Function,+,furi_hal_ibutton_stop,void, +Function,+,furi_hal_info_get,void,"FuriHalInfoValueCallback, void*" +Function,+,furi_hal_infrared_async_rx_set_capture_isr_callback,void,"FuriHalInfraredRxCaptureCallback, void*" +Function,+,furi_hal_infrared_async_rx_set_timeout,void,uint32_t +Function,+,furi_hal_infrared_async_rx_set_timeout_isr_callback,void,"FuriHalInfraredRxTimeoutCallback, void*" +Function,+,furi_hal_infrared_async_rx_start,void, +Function,+,furi_hal_infrared_async_rx_stop,void, +Function,+,furi_hal_infrared_async_tx_set_data_isr_callback,void,"FuriHalInfraredTxGetDataISRCallback, void*" +Function,+,furi_hal_infrared_async_tx_set_signal_sent_isr_callback,void,"FuriHalInfraredTxSignalSentISRCallback, void*" +Function,+,furi_hal_infrared_async_tx_start,void,"uint32_t, float" +Function,+,furi_hal_infrared_async_tx_stop,void, +Function,+,furi_hal_infrared_async_tx_wait_termination,void, +Function,+,furi_hal_infrared_is_busy,_Bool, +Function,-,furi_hal_init,void, +Function,-,furi_hal_init_early,void, +Function,-,furi_hal_interrupt_init,void, +Function,+,furi_hal_interrupt_set_isr,void,"FuriHalInterruptId, FuriHalInterruptISR, void*" +Function,+,furi_hal_interrupt_set_isr_ex,void,"FuriHalInterruptId, uint16_t, FuriHalInterruptISR, void*" +Function,+,furi_hal_light_blink_set_color,void,Light +Function,+,furi_hal_light_blink_start,void,"Light, uint8_t, uint16_t, uint16_t" +Function,+,furi_hal_light_blink_stop,void, +Function,-,furi_hal_light_init,void, +Function,+,furi_hal_light_sequence,void,const char* +Function,+,furi_hal_light_set,void,"Light, uint8_t" +Function,+,furi_hal_memory_alloc,void*,size_t +Function,+,furi_hal_memory_get_free,size_t, +Function,+,furi_hal_memory_init,void, +Function,+,furi_hal_memory_max_pool_block,size_t, +Function,+,furi_hal_mpu_disable,void, +Function,+,furi_hal_mpu_enable,void, +Function,-,furi_hal_mpu_init,void, +Function,+,furi_hal_mpu_protect_disable,void,FuriHalMpuRegion +Function,+,furi_hal_mpu_protect_no_access,void,"FuriHalMpuRegion, uint32_t, FuriHalMPURegionSize" +Function,+,furi_hal_mpu_protect_read_only,void,"FuriHalMpuRegion, uint32_t, FuriHalMPURegionSize" +Function,+,furi_hal_nfc_activate_nfca,_Bool,"uint32_t, uint32_t*" +Function,+,furi_hal_nfc_detect,_Bool,"FuriHalNfcDevData*, uint32_t" +Function,+,furi_hal_nfc_emulate_nfca,_Bool,"uint8_t*, uint8_t, uint8_t*, uint8_t, FuriHalNfcEmulateCallback, void*, uint32_t" +Function,+,furi_hal_nfc_exit_sleep,void, +Function,+,furi_hal_nfc_field_off,void, +Function,+,furi_hal_nfc_field_on,void, +Function,-,furi_hal_nfc_init,void, +Function,+,furi_hal_nfc_is_busy,_Bool, +Function,+,furi_hal_nfc_listen,_Bool,"uint8_t*, uint8_t, uint8_t*, uint8_t, _Bool, uint32_t" +Function,+,furi_hal_nfc_listen_rx,_Bool,"FuriHalNfcTxRxContext*, uint32_t" +Function,+,furi_hal_nfc_listen_sleep,void, +Function,+,furi_hal_nfc_listen_start,void,FuriHalNfcDevData* +Function,+,furi_hal_nfc_ll_poll,void, +Function,+,furi_hal_nfc_ll_set_error_handling,void,FuriHalNfcErrorHandling +Function,+,furi_hal_nfc_ll_set_fdt_listen,void,uint32_t +Function,+,furi_hal_nfc_ll_set_fdt_poll,void,uint32_t +Function,+,furi_hal_nfc_ll_set_guard_time,void,uint32_t +Function,+,furi_hal_nfc_ll_set_mode,FuriHalNfcReturn,"FuriHalNfcMode, FuriHalNfcBitrate, FuriHalNfcBitrate" +Function,+,furi_hal_nfc_ll_txrx,FuriHalNfcReturn,"uint8_t*, uint16_t, uint8_t*, uint16_t, uint16_t*, uint32_t, uint32_t" +Function,+,furi_hal_nfc_ll_txrx_off,void, +Function,+,furi_hal_nfc_ll_txrx_on,void, +Function,+,furi_hal_nfc_sleep,void, +Function,+,furi_hal_nfc_start_sleep,void, +Function,+,furi_hal_nfc_stop,void, +Function,+,furi_hal_nfc_stop_cmd,void, +Function,+,furi_hal_nfc_tx_rx,_Bool,"FuriHalNfcTxRxContext*, uint16_t" +Function,+,furi_hal_nfc_tx_rx_full,_Bool,FuriHalNfcTxRxContext* +Function,-,furi_hal_os_init,void, +Function,+,furi_hal_os_tick,void, +Function,+,furi_hal_power_check_otg_status,void, +Function,+,furi_hal_power_deep_sleep_available,_Bool, +Function,+,furi_hal_power_disable_external_3_3v,void, +Function,+,furi_hal_power_disable_otg,void, +Function,+,furi_hal_power_dump_state,void, +Function,+,furi_hal_power_enable_external_3_3v,void, +Function,+,furi_hal_power_enable_otg,void, +Function,+,furi_hal_power_gauge_is_ok,_Bool, +Function,+,furi_hal_power_get_bat_health_pct,uint8_t, +Function,+,furi_hal_power_get_battery_current,float,FuriHalPowerIC +Function,+,furi_hal_power_get_battery_design_capacity,uint32_t, +Function,+,furi_hal_power_get_battery_full_capacity,uint32_t, +Function,+,furi_hal_power_get_battery_remaining_capacity,uint32_t, +Function,+,furi_hal_power_get_battery_temperature,float,FuriHalPowerIC +Function,+,furi_hal_power_get_battery_voltage,float,FuriHalPowerIC +Function,+,furi_hal_power_get_pct,uint8_t, +Function,-,furi_hal_power_get_system_voltage,float, +Function,+,furi_hal_power_get_usb_voltage,float, +Function,+,furi_hal_power_info_get,void,"FuriHalPowerInfoCallback, void*" +Function,-,furi_hal_power_init,void, +Function,+,furi_hal_power_insomnia_enter,void, +Function,+,furi_hal_power_insomnia_exit,void, +Function,-,furi_hal_power_insomnia_level,uint16_t, +Function,+,furi_hal_power_is_charging,_Bool, +Function,+,furi_hal_power_is_otg_enabled,_Bool, +Function,+,furi_hal_power_off,void, +Function,+,furi_hal_power_reset,void, +Function,+,furi_hal_power_shutdown,void, +Function,+,furi_hal_power_sleep,void, +Function,+,furi_hal_power_sleep_available,_Bool, +Function,+,furi_hal_power_suppress_charge_enter,void, +Function,+,furi_hal_power_suppress_charge_exit,void, +Function,+,furi_hal_random_fill_buf,void,"uint8_t*, uint32_t" +Function,+,furi_hal_random_get,uint32_t, +Function,+,furi_hal_region_get,const FuriHalRegion*, +Function,+,furi_hal_region_get_band,const FuriHalRegionBand*,uint32_t +Function,+,furi_hal_region_get_name,const char*, +Function,-,furi_hal_region_init,void, +Function,+,furi_hal_region_is_frequency_allowed,_Bool,uint32_t +Function,+,furi_hal_region_is_provisioned,_Bool, +Function,+,furi_hal_region_set,void,FuriHalRegion* +Function,+,furi_hal_resources_deinit_early,void, +Function,-,furi_hal_resources_init,void, +Function,-,furi_hal_resources_init_early,void, +Function,+,furi_hal_rfid_change_read_config,void,"float, float" +Function,+,furi_hal_rfid_comp_set_callback,void,"FuriHalRfidCompCallback, void*" +Function,+,furi_hal_rfid_comp_start,void, +Function,+,furi_hal_rfid_comp_stop,void, +Function,-,furi_hal_rfid_init,void, +Function,+,furi_hal_rfid_pin_pull_pulldown,void, +Function,+,furi_hal_rfid_pin_pull_release,void, +Function,+,furi_hal_rfid_pins_emulate,void, +Function,+,furi_hal_rfid_pins_read,void, +Function,+,furi_hal_rfid_pins_reset,void, +Function,+,furi_hal_rfid_set_emulate_period,void,uint32_t +Function,+,furi_hal_rfid_set_emulate_pulse,void,uint32_t +Function,+,furi_hal_rfid_set_read_period,void,uint32_t +Function,+,furi_hal_rfid_set_read_pulse,void,uint32_t +Function,+,furi_hal_rfid_tim_emulate,void,float +Function,+,furi_hal_rfid_tim_emulate_dma_start,void,"uint32_t*, uint32_t*, size_t, FuriHalRfidDMACallback, void*" +Function,+,furi_hal_rfid_tim_emulate_dma_stop,void, +Function,+,furi_hal_rfid_tim_emulate_start,void,"FuriHalRfidEmulateCallback, void*" +Function,+,furi_hal_rfid_tim_emulate_stop,void, +Function,+,furi_hal_rfid_tim_read,void,"float, float" +Function,+,furi_hal_rfid_tim_read_capture_start,void,"FuriHalRfidReadCaptureCallback, void*" +Function,+,furi_hal_rfid_tim_read_capture_stop,void, +Function,+,furi_hal_rfid_tim_read_start,void, +Function,+,furi_hal_rfid_tim_read_stop,void, +Function,+,furi_hal_rfid_tim_reset,void, +Function,+,furi_hal_rtc_datetime_to_timestamp,uint32_t,FuriHalRtcDateTime* +Function,+,furi_hal_rtc_deinit_early,void, +Function,+,furi_hal_rtc_get_boot_mode,FuriHalRtcBootMode, +Function,+,furi_hal_rtc_get_datetime,void,FuriHalRtcDateTime* +Function,+,furi_hal_rtc_get_fault_data,uint32_t, +Function,+,furi_hal_rtc_get_log_level,uint8_t, +Function,+,furi_hal_rtc_get_pin_fails,uint32_t, +Function,+,furi_hal_rtc_get_register,uint32_t,FuriHalRtcRegister +Function,-,furi_hal_rtc_init,void, +Function,-,furi_hal_rtc_init_early,void, +Function,+,furi_hal_rtc_is_flag_set,_Bool,FuriHalRtcFlag +Function,+,furi_hal_rtc_reset_flag,void,FuriHalRtcFlag +Function,+,furi_hal_rtc_set_boot_mode,void,FuriHalRtcBootMode +Function,+,furi_hal_rtc_set_datetime,void,FuriHalRtcDateTime* +Function,+,furi_hal_rtc_set_fault_data,void,uint32_t +Function,+,furi_hal_rtc_set_flag,void,FuriHalRtcFlag +Function,+,furi_hal_rtc_set_log_level,void,uint8_t +Function,+,furi_hal_rtc_set_pin_fails,void,uint32_t +Function,+,furi_hal_rtc_set_register,void,"FuriHalRtcRegister, uint32_t" +Function,+,furi_hal_rtc_validate_datetime,_Bool,FuriHalRtcDateTime* +Function,-,furi_hal_speaker_init,void, +Function,+,furi_hal_speaker_set_volume,void,float +Function,+,furi_hal_speaker_start,void,"float, float" +Function,+,furi_hal_speaker_stop,void, +Function,+,furi_hal_spi_acquire,void,FuriHalSpiBusHandle* +Function,+,furi_hal_spi_bus_deinit,void,FuriHalSpiBus* +Function,+,furi_hal_spi_bus_handle_deinit,void,FuriHalSpiBusHandle* +Function,+,furi_hal_spi_bus_handle_init,void,FuriHalSpiBusHandle* +Function,+,furi_hal_spi_bus_init,void,FuriHalSpiBus* +Function,+,furi_hal_spi_bus_rx,_Bool,"FuriHalSpiBusHandle*, uint8_t*, size_t, uint32_t" +Function,+,furi_hal_spi_bus_trx,_Bool,"FuriHalSpiBusHandle*, uint8_t*, uint8_t*, size_t, uint32_t" +Function,+,furi_hal_spi_bus_tx,_Bool,"FuriHalSpiBusHandle*, uint8_t*, size_t, uint32_t" +Function,+,furi_hal_spi_deinit_early,void, +Function,-,furi_hal_spi_init,void, +Function,+,furi_hal_spi_init_early,void, +Function,+,furi_hal_spi_release,void,FuriHalSpiBusHandle* +Function,-,furi_hal_subghz_dump_state,void, +Function,+,furi_hal_subghz_flush_rx,void, +Function,+,furi_hal_subghz_flush_tx,void, +Function,+,furi_hal_subghz_get_lqi,uint8_t, +Function,+,furi_hal_subghz_get_rssi,float, +Function,+,furi_hal_subghz_idle,void, +Function,-,furi_hal_subghz_init,void, +Function,+,furi_hal_subghz_is_async_tx_complete,_Bool, +Function,+,furi_hal_subghz_is_frequency_valid,_Bool,uint32_t +Function,+,furi_hal_subghz_is_rx_data_crc_valid,_Bool, +Function,+,furi_hal_subghz_load_custom_preset,void,uint8_t* +Function,+,furi_hal_subghz_load_patable,void,const uint8_t[8] +Function,+,furi_hal_subghz_load_preset,void,FuriHalSubGhzPreset +Function,+,furi_hal_subghz_load_registers,void,uint8_t* +Function,+,furi_hal_subghz_read_packet,void,"uint8_t*, uint8_t*" +Function,+,furi_hal_subghz_reset,void, +Function,+,furi_hal_subghz_rx,void, +Function,+,furi_hal_subghz_rx_pipe_not_empty,_Bool, +Function,+,furi_hal_subghz_set_frequency,uint32_t,uint32_t +Function,+,furi_hal_subghz_set_frequency_and_path,uint32_t,uint32_t +Function,+,furi_hal_subghz_set_path,void,FuriHalSubGhzPath +Function,-,furi_hal_subghz_shutdown,void, +Function,+,furi_hal_subghz_sleep,void, +Function,+,furi_hal_subghz_start_async_rx,void,"FuriHalSubGhzCaptureCallback, void*" +Function,+,furi_hal_subghz_start_async_tx,_Bool,"FuriHalSubGhzAsyncTxCallback, void*" +Function,+,furi_hal_subghz_stop_async_rx,void, +Function,+,furi_hal_subghz_stop_async_tx,void, +Function,+,furi_hal_subghz_tx,_Bool, +Function,+,furi_hal_subghz_write_packet,void,"const uint8_t*, uint8_t" +Function,+,furi_hal_switch,void,void* +Function,+,furi_hal_uart_deinit,void,FuriHalUartId +Function,+,furi_hal_uart_init,void,"FuriHalUartId, uint32_t" +Function,+,furi_hal_uart_resume,void,FuriHalUartId +Function,+,furi_hal_uart_set_br,void,"FuriHalUartId, uint32_t" +Function,+,furi_hal_uart_set_irq_cb,void,"FuriHalUartId, void (*)(UartIrqEvent, uint8_t, void*), void*" +Function,+,furi_hal_uart_suspend,void,FuriHalUartId +Function,+,furi_hal_uart_tx,void,"FuriHalUartId, uint8_t*, size_t" +Function,+,furi_hal_usb_disable,void, +Function,+,furi_hal_usb_enable,void, +Function,+,furi_hal_usb_get_config,FuriHalUsbInterface*, +Function,-,furi_hal_usb_init,void, +Function,+,furi_hal_usb_is_locked,_Bool, +Function,+,furi_hal_usb_lock,void, +Function,+,furi_hal_usb_reinit,void, +Function,+,furi_hal_usb_set_config,_Bool,"FuriHalUsbInterface*, void*" +Function,-,furi_hal_usb_set_state_callback,void,"FuriHalUsbStateCallback, void*" +Function,+,furi_hal_usb_unlock,void, +Function,+,furi_hal_version_do_i_belong_here,_Bool, +Function,+,furi_hal_version_get_ble_local_device_name_ptr,const char*, +Function,+,furi_hal_version_get_ble_mac,const uint8_t*, +Function,+,furi_hal_version_get_device_name_ptr,const char*, +Function,+,furi_hal_version_get_firmware_version,const Version*, +Function,+,furi_hal_version_get_hw_body,uint8_t, +Function,+,furi_hal_version_get_hw_color,FuriHalVersionColor, +Function,+,furi_hal_version_get_hw_connect,uint8_t, +Function,+,furi_hal_version_get_hw_display,FuriHalVersionDisplay, +Function,+,furi_hal_version_get_hw_region,FuriHalVersionRegion, +Function,+,furi_hal_version_get_hw_region_name,const char*, +Function,+,furi_hal_version_get_hw_target,uint8_t, +Function,+,furi_hal_version_get_hw_timestamp,uint32_t, +Function,+,furi_hal_version_get_hw_version,uint8_t, +Function,+,furi_hal_version_get_model_name,const char*, +Function,+,furi_hal_version_get_name_ptr,const char*, +Function,+,furi_hal_version_get_otp_version,FuriHalVersionOtpVersion, +Function,-,furi_hal_version_init,void, +Function,+,furi_hal_version_uid,const uint8_t*, +Function,+,furi_hal_version_uid_size,size_t, +Function,-,furi_hal_vibro_init,void, +Function,+,furi_hal_vibro_on,void,_Bool +Function,+,furi_halt,void,const char* +Function,-,furi_init,void, +Function,+,furi_kernel_get_tick_frequency,uint32_t, +Function,+,furi_kernel_lock,int32_t, +Function,+,furi_kernel_restore_lock,int32_t,int32_t +Function,+,furi_kernel_unlock,int32_t, +Function,+,furi_log_get_level,FuriLogLevel, +Function,-,furi_log_init,void, +Function,+,furi_log_print_format,void,"FuriLogLevel, const char*, const char*, ..." +Function,+,furi_log_set_level,void,FuriLogLevel +Function,-,furi_log_set_puts,void,FuriLogPuts +Function,-,furi_log_set_timestamp,void,FuriLogTimestamp +Function,+,furi_message_queue_alloc,FuriMessageQueue*,"uint32_t, uint32_t" +Function,+,furi_message_queue_free,void,FuriMessageQueue* +Function,+,furi_message_queue_get,FuriStatus,"FuriMessageQueue*, void*, uint32_t" +Function,+,furi_message_queue_get_capacity,uint32_t,FuriMessageQueue* +Function,+,furi_message_queue_get_count,uint32_t,FuriMessageQueue* +Function,+,furi_message_queue_get_message_size,uint32_t,FuriMessageQueue* +Function,+,furi_message_queue_get_space,uint32_t,FuriMessageQueue* +Function,+,furi_message_queue_put,FuriStatus,"FuriMessageQueue*, const void*, uint32_t" +Function,+,furi_message_queue_reset,FuriStatus,FuriMessageQueue* +Function,+,furi_ms_to_ticks,uint32_t,uint32_t +Function,+,furi_mutex_acquire,FuriStatus,"FuriMutex*, uint32_t" +Function,+,furi_mutex_alloc,FuriMutex*,FuriMutexType +Function,+,furi_mutex_free,void,FuriMutex* +Function,+,furi_mutex_get_owner,FuriThreadId,FuriMutex* +Function,+,furi_mutex_release,FuriStatus,FuriMutex* +Function,+,furi_pubsub_alloc,FuriPubSub*, +Function,-,furi_pubsub_free,void,FuriPubSub* +Function,+,furi_pubsub_publish,void,"FuriPubSub*, void*" +Function,+,furi_pubsub_subscribe,FuriPubSubSubscription*,"FuriPubSub*, FuriPubSubCallback, void*" +Function,+,furi_pubsub_unsubscribe,void,"FuriPubSub*, FuriPubSubSubscription*" +Function,+,furi_record_close,void,const char* +Function,+,furi_record_create,void,"const char*, void*" +Function,-,furi_record_destroy,_Bool,const char* +Function,+,furi_record_exists,_Bool,const char* +Function,-,furi_record_init,void, +Function,+,furi_record_open,void*,const char* +Function,+,furi_run,void, +Function,+,furi_semaphore_acquire,FuriStatus,"FuriSemaphore*, uint32_t" +Function,+,furi_semaphore_alloc,FuriSemaphore*,"uint32_t, uint32_t" +Function,+,furi_semaphore_free,void,FuriSemaphore* +Function,+,furi_semaphore_get_count,uint32_t,FuriSemaphore* +Function,+,furi_semaphore_release,FuriStatus,FuriSemaphore* +Function,+,furi_thread_alloc,FuriThread*, +Function,+,furi_thread_catch,void, +Function,-,furi_thread_disable_heap_trace,void,FuriThread* +Function,+,furi_thread_enable_heap_trace,void,FuriThread* +Function,+,furi_thread_enumerate,uint32_t,"FuriThreadId*, uint32_t" +Function,+,furi_thread_flags_clear,uint32_t,uint32_t +Function,+,furi_thread_flags_get,uint32_t, +Function,+,furi_thread_flags_set,uint32_t,"FuriThreadId, uint32_t" +Function,+,furi_thread_flags_wait,uint32_t,"uint32_t, uint32_t, uint32_t" +Function,+,furi_thread_free,void,FuriThread* +Function,+,furi_thread_get_current,FuriThread*, +Function,+,furi_thread_get_current_id,FuriThreadId, +Function,+,furi_thread_get_heap_size,size_t,FuriThread* +Function,+,furi_thread_get_id,FuriThreadId,FuriThread* +Function,+,furi_thread_get_name,const char*,FuriThreadId +Function,+,furi_thread_get_return_code,int32_t,FuriThread* +Function,+,furi_thread_get_stack_space,uint32_t,FuriThreadId +Function,+,furi_thread_get_state,FuriThreadState,FuriThread* +Function,+,furi_thread_join,_Bool,FuriThread* +Function,+,furi_thread_mark_as_service,void,FuriThread* +Function,+,furi_thread_set_callback,void,"FuriThread*, FuriThreadCallback" +Function,+,furi_thread_set_context,void,"FuriThread*, void*" +Function,+,furi_thread_set_name,void,"FuriThread*, const char*" +Function,+,furi_thread_set_priority,void,"FuriThread*, FuriThreadPriority" +Function,+,furi_thread_set_stack_size,void,"FuriThread*, size_t" +Function,+,furi_thread_set_state_callback,void,"FuriThread*, FuriThreadStateCallback" +Function,+,furi_thread_set_state_context,void,"FuriThread*, void*" +Function,+,furi_thread_set_stdout_callback,_Bool,FuriThreadStdoutWriteCallback +Function,+,furi_thread_start,void,FuriThread* +Function,+,furi_thread_stdout_flush,int32_t, +Function,+,furi_thread_stdout_write,size_t,"const char*, size_t" +Function,+,furi_thread_yield,void, +Function,+,furi_timer_alloc,FuriTimer*,"FuriTimerCallback, FuriTimerType, void*" +Function,+,furi_timer_free,void,FuriTimer* +Function,+,furi_timer_is_running,uint32_t,FuriTimer* +Function,+,furi_timer_start,FuriStatus,"FuriTimer*, uint32_t" +Function,+,furi_timer_stop,FuriStatus,FuriTimer* +Function,-,fwrite,size_t,"const void*, size_t, size_t, FILE*" +Function,-,fwrite_unlocked,size_t,"const void*, size_t, size_t, FILE*" +Function,-,gamma,double,double +Function,-,gamma_r,double,"double, int*" +Function,-,gammaf,float,float +Function,-,gammaf_r,float,"float, int*" +Function,-,gap_get_state,GapState, +Function,-,gap_init,_Bool,"GapConfig*, GapEventCallback, void*" +Function,-,gap_start_advertising,void, +Function,-,gap_stop_advertising,void, +Function,-,gap_thread_stop,void, +Function,-,getc,int,FILE* +Function,-,getc_unlocked,int,FILE* +Function,-,getchar,int, +Function,-,getchar_unlocked,int, +Function,-,getenv,char*,const char* +Function,-,gets,char*,char* +Function,-,getsubopt,int,"char**, char**, char**" +Function,-,getw,int,FILE* +Function,+,gui_add_framebuffer_callback,void,"Gui*, GuiCanvasCommitCallback, void*" +Function,+,gui_add_view_port,void,"Gui*, ViewPort*, GuiLayer" +Function,+,gui_get_framebuffer_size,size_t,Gui* +Function,+,gui_remove_framebuffer_callback,void,"Gui*, GuiCanvasCommitCallback, void*" +Function,+,gui_remove_view_port,void,"Gui*, ViewPort*" +Function,+,gui_set_lockdown,void,"Gui*, _Bool" +Function,-,gui_view_port_send_to_back,void,"Gui*, ViewPort*" +Function,+,gui_view_port_send_to_front,void,"Gui*, ViewPort*" +Function,+,hal_sd_detect,_Bool, +Function,+,hal_sd_detect_init,void, +Function,+,hal_sd_detect_set_low,void, +Function,+,hmac_sha256_finish,void,"const hmac_sha256_context*, const uint8_t*, uint8_t*" +Function,+,hmac_sha256_init,void,"hmac_sha256_context*, const uint8_t*" +Function,+,hmac_sha256_update,void,"const hmac_sha256_context*, const uint8_t*, unsigned" +Function,-,hypot,double,"double, double" +Function,-,hypotf,float,"float, float" +Function,-,hypotl,long double,"long double, long double" +Function,+,ibutton_key_alloc,iButtonKey*, +Function,+,ibutton_key_clear_data,void,iButtonKey* +Function,+,ibutton_key_dallas_crc_is_valid,_Bool,iButtonKey* +Function,+,ibutton_key_dallas_is_1990_key,_Bool,iButtonKey* +Function,+,ibutton_key_free,void,iButtonKey* +Function,+,ibutton_key_get_data_p,const uint8_t*,iButtonKey* +Function,+,ibutton_key_get_data_size,uint8_t,iButtonKey* +Function,+,ibutton_key_get_max_size,uint8_t, +Function,+,ibutton_key_get_size_by_type,uint8_t,iButtonKeyType +Function,+,ibutton_key_get_string_by_type,const char*,iButtonKeyType +Function,+,ibutton_key_get_type,iButtonKeyType,iButtonKey* +Function,+,ibutton_key_get_type_by_string,_Bool,"const char*, iButtonKeyType*" +Function,+,ibutton_key_set,void,"iButtonKey*, const iButtonKey*" +Function,+,ibutton_key_set_data,void,"iButtonKey*, uint8_t*, uint8_t" +Function,+,ibutton_key_set_type,void,"iButtonKey*, iButtonKeyType" +Function,+,ibutton_worker_alloc,iButtonWorker*, +Function,+,ibutton_worker_emulate_set_callback,void,"iButtonWorker*, iButtonWorkerEmulateCallback, void*" +Function,+,ibutton_worker_emulate_start,void,"iButtonWorker*, iButtonKey*" +Function,+,ibutton_worker_free,void,iButtonWorker* +Function,+,ibutton_worker_read_set_callback,void,"iButtonWorker*, iButtonWorkerReadCallback, void*" +Function,+,ibutton_worker_read_start,void,"iButtonWorker*, iButtonKey*" +Function,+,ibutton_worker_start_thread,void,iButtonWorker* +Function,+,ibutton_worker_stop,void,iButtonWorker* +Function,+,ibutton_worker_stop_thread,void,iButtonWorker* +Function,+,ibutton_worker_write_set_callback,void,"iButtonWorker*, iButtonWorkerWriteCallback, void*" +Function,+,ibutton_worker_write_start,void,"iButtonWorker*, iButtonKey*" +Function,+,icon_animation_alloc,IconAnimation*,const Icon* +Function,+,icon_animation_free,void,IconAnimation* +Function,+,icon_animation_get_height,uint8_t,IconAnimation* +Function,+,icon_animation_get_width,uint8_t,IconAnimation* +Function,+,icon_animation_is_last_frame,_Bool,IconAnimation* +Function,+,icon_animation_set_update_callback,void,"IconAnimation*, IconAnimationCallback, void*" +Function,+,icon_animation_start,void,IconAnimation* +Function,+,icon_animation_stop,void,IconAnimation* +Function,+,icon_get_data,const uint8_t*,const Icon* +Function,+,icon_get_height,uint8_t,const Icon* +Function,+,icon_get_width,uint8_t,const Icon* +Function,-,ilogb,int,double +Function,-,ilogbf,int,float +Function,-,ilogbl,int,long double +Function,-,index,char*,"const char*, int" +Function,-,infinity,double, +Function,-,infinityf,float, +Function,+,init_mutex,_Bool,"ValueMutex*, void*, size_t" +Function,-,initstate,char*,"unsigned, char*, size_t" +Function,+,input_get_key_name,const char*,InputKey +Function,+,input_get_type_name,const char*,InputType +Function,-,iprintf,int,"const char*, ..." +Function,-,isalnum,int,int +Function,-,isalnum_l,int,"int, locale_t" +Function,-,isalpha,int,int +Function,-,isalpha_l,int,"int, locale_t" +Function,-,isascii,int,int +Function,-,isascii_l,int,"int, locale_t" +Function,-,isblank,int,int +Function,-,isblank_l,int,"int, locale_t" +Function,-,iscanf,int,"const char*, ..." +Function,-,iscntrl,int,int +Function,-,iscntrl_l,int,"int, locale_t" +Function,-,isdigit,int,int +Function,-,isdigit_l,int,"int, locale_t" +Function,-,isgraph,int,int +Function,-,isgraph_l,int,"int, locale_t" +Function,-,isinf,int,double +Function,-,isinff,int,float +Function,-,islower,int,int +Function,-,islower_l,int,"int, locale_t" +Function,-,isnan,int,double +Function,-,isnanf,int,float +Function,-,isprint,int,int +Function,-,isprint_l,int,"int, locale_t" +Function,-,ispunct,int,int +Function,-,ispunct_l,int,"int, locale_t" +Function,-,isspace,int,int +Function,-,isspace_l,int,"int, locale_t" +Function,-,isupper,int,int +Function,-,isupper_l,int,"int, locale_t" +Function,-,isxdigit,int,int +Function,-,isxdigit_l,int,"int, locale_t" +Function,-,itoa,char*,"int, char*, int" +Function,-,j0,double,double +Function,-,j0f,float,float +Function,-,j1,double,double +Function,-,j1f,float,float +Function,-,jn,double,"int, double" +Function,-,jnf,float,"int, float" +Function,-,jrand48,long,unsigned short[3] +Function,-,l64a,char*,long +Function,-,labs,long,long +Function,-,lcong48,void,unsigned short[7] +Function,-,ldexp,double,"double, int" +Function,-,ldexpf,float,"float, int" +Function,-,ldexpl,long double,"long double, int" +Function,-,ldiv,ldiv_t,"long, long" +Function,-,lgamma,double,double +Function,-,lgamma_r,double,"double, int*" +Function,-,lgammaf,float,float +Function,-,lgammaf_r,float,"float, int*" +Function,-,lgammal,long double,long double +Function,-,llabs,long long,long long +Function,-,lldiv,lldiv_t,"long long, long long" +Function,-,llrint,long long int,double +Function,-,llrintf,long long int,float +Function,-,llrintl,long long int,long double +Function,-,llround,long long int,double +Function,-,llroundf,long long int,float +Function,-,llroundl,long long int,long double +Function,+,loader_get_pubsub,FuriPubSub*,Loader* +Function,+,loader_is_locked,_Bool,Loader* +Function,+,loader_lock,_Bool,Loader* +Function,+,loader_show_menu,void, +Function,+,loader_start,LoaderStatus,"Loader*, const char*, const char*" +Function,+,loader_unlock,void,Loader* +Function,+,loader_update_menu,void, +Function,+,loading_alloc,Loading*, +Function,+,loading_free,void,Loading* +Function,+,loading_get_view,View*,Loading* +Function,-,log,double,double +Function,-,log10,double,double +Function,-,log10f,float,float +Function,-,log10l,long double,long double +Function,-,log1p,double,double +Function,-,log1pf,float,float +Function,-,log1pl,long double,long double +Function,-,log2,double,double +Function,-,log2f,float,float +Function,-,log2l,long double,long double +Function,-,logb,double,double +Function,-,logbf,float,float +Function,-,logbl,long double,long double +Function,-,logf,float,float +Function,-,logl,long double,long double +Function,-,lrand48,long, +Function,-,lrint,long int,double +Function,-,lrintf,long int,float +Function,-,lrintl,long int,long double +Function,-,lround,long int,double +Function,-,lroundf,long int,float +Function,-,lroundl,long,long double +Function,+,malloc,void*,size_t +Function,+,manchester_advance,_Bool,"ManchesterState, ManchesterEvent, ManchesterState*, _Bool*" +Function,+,manchester_encoder_advance,_Bool,"ManchesterEncoderState*, const _Bool, ManchesterEncoderResult*" +Function,+,manchester_encoder_finish,ManchesterEncoderResult,ManchesterEncoderState* +Function,+,manchester_encoder_reset,void,ManchesterEncoderState* +Function,+,maxim_crc8,uint8_t,"const uint8_t*, const uint8_t, const uint8_t" +Function,-,mblen,int,"const char*, size_t" +Function,-,mbstowcs,size_t,"wchar_t*, const char*, size_t" +Function,-,mbtowc,int,"wchar_t*, const char*, size_t" +Function,+,md5,void,"const unsigned char*, size_t, unsigned char[16]" +Function,+,md5_finish,void,"md5_context*, unsigned char[16]" +Function,+,md5_process,void,"md5_context*, const unsigned char[64]" +Function,+,md5_starts,void,md5_context* +Function,+,md5_update,void,"md5_context*, const unsigned char*, size_t" +Function,-,memccpy,void*,"void*, const void*, int, size_t" +Function,+,memchr,void*,"const void*, int, size_t" +Function,+,memcmp,int,"const void*, const void*, size_t" +Function,+,memcpy,void*,"void*, const void*, size_t" +Function,-,memmem,void*,"const void*, size_t, const void*, size_t" +Function,-,memmgr_alloc_from_pool,void*,size_t +Function,+,memmgr_get_free_heap,size_t, +Function,+,memmgr_get_minimum_free_heap,size_t, +Function,+,memmgr_get_total_heap,size_t, +Function,+,memmgr_heap_disable_thread_trace,void,FuriThreadId +Function,+,memmgr_heap_enable_thread_trace,void,FuriThreadId +Function,+,memmgr_heap_get_max_free_block,size_t, +Function,+,memmgr_heap_get_thread_memory,size_t,FuriThreadId +Function,+,memmgr_heap_printf_free_blocks,void, +Function,-,memmgr_pool_get_free,size_t, +Function,-,memmgr_pool_get_max_block,size_t, +Function,+,memmove,void*,"void*, const void*, size_t" +Function,-,mempcpy,void*,"void*, const void*, size_t" +Function,-,memrchr,void*,"const void*, int, size_t" +Function,+,memset,void*,"void*, int, size_t" +Function,+,menu_add_item,void,"Menu*, const char*, const Icon*, uint32_t, MenuItemCallback, void*" +Function,+,menu_alloc,Menu*, +Function,+,menu_free,void,Menu* +Function,+,menu_get_view,View*,Menu* +Function,+,menu_reset,void,Menu* +Function,+,menu_set_selected_item,void,"Menu*, uint32_t" +Function,-,mkdtemp,char*,char* +Function,-,mkostemp,int,"char*, int" +Function,-,mkostemps,int,"char*, int, int" +Function,-,mkstemp,int,char* +Function,-,mkstemps,int,"char*, int" +Function,-,mktemp,char*,char* +Function,-,modf,double,"double, double*" +Function,-,modff,float,"float, float*" +Function,-,modfl,long double,"long double, long double*" +Function,-,mrand48,long, +Function,-,nan,double,const char* +Function,-,nanf,float,const char* +Function,-,nanl,long double,const char* +Function,-,nearbyint,double,double +Function,-,nearbyintf,float,float +Function,-,nearbyintl,long double,long double +Function,-,nextafter,double,"double, double" +Function,-,nextafterf,float,"float, float" +Function,-,nextafterl,long double,"long double, long double" +Function,-,nexttoward,double,"double, long double" +Function,-,nexttowardf,float,"float, long double" +Function,-,nexttowardl,long double,"long double, long double" +Function,-,nfca_append_crc16,void,"uint8_t*, uint16_t" +Function,-,nfca_emulation_handler,_Bool,"uint8_t*, uint16_t, uint8_t*, uint16_t*" +Function,-,nfca_get_crc16,uint16_t,"uint8_t*, uint16_t" +Function,-,nfca_signal_alloc,NfcaSignal*, +Function,-,nfca_signal_encode,void,"NfcaSignal*, uint8_t*, uint16_t, uint8_t*" +Function,-,nfca_signal_free,void,NfcaSignal* +Function,+,notification_internal_message,void,"NotificationApp*, const NotificationSequence*" +Function,+,notification_internal_message_block,void,"NotificationApp*, const NotificationSequence*" +Function,+,notification_message,void,"NotificationApp*, const NotificationSequence*" +Function,+,notification_message_block,void,"NotificationApp*, const NotificationSequence*" +Function,-,nrand48,long,unsigned short[3] +Function,-,on_exit,int,"void (*)(int, void*), void*" +Function,+,onewire_device_alloc,OneWireDevice*,"uint8_t, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t" +Function,+,onewire_device_attach,void,"OneWireDevice*, OneWireSlave*" +Function,+,onewire_device_detach,void,OneWireDevice* +Function,+,onewire_device_free,void,OneWireDevice* +Function,+,onewire_device_get_id_p,uint8_t*,OneWireDevice* +Function,+,onewire_device_send_id,void,OneWireDevice* +Function,+,onewire_host_alloc,OneWireHost*, +Function,+,onewire_host_free,void,OneWireHost* +Function,+,onewire_host_read,uint8_t,OneWireHost* +Function,+,onewire_host_read_bit,_Bool,OneWireHost* +Function,+,onewire_host_read_bytes,void,"OneWireHost*, uint8_t*, uint16_t" +Function,+,onewire_host_reset,_Bool,OneWireHost* +Function,+,onewire_host_reset_search,void,OneWireHost* +Function,+,onewire_host_search,uint8_t,"OneWireHost*, uint8_t*, OneWireHostSearchMode" +Function,+,onewire_host_skip,void,OneWireHost* +Function,+,onewire_host_start,void,OneWireHost* +Function,+,onewire_host_stop,void,OneWireHost* +Function,+,onewire_host_target_search,void,"OneWireHost*, uint8_t" +Function,+,onewire_host_write,void,"OneWireHost*, uint8_t" +Function,+,onewire_host_write_bit,void,"OneWireHost*, _Bool" +Function,+,onewire_slave_alloc,OneWireSlave*, +Function,+,onewire_slave_attach,void,"OneWireSlave*, OneWireDevice*" +Function,+,onewire_slave_detach,void,OneWireSlave* +Function,+,onewire_slave_free,void,OneWireSlave* +Function,+,onewire_slave_set_result_callback,void,"OneWireSlave*, OneWireSlaveResultCallback, void*" +Function,+,onewire_slave_start,void,OneWireSlave* +Function,+,onewire_slave_stop,void,OneWireSlave* +Function,-,open_memstream,FILE*,"char**, size_t*" +Function,+,path_append,void,"string_t, const char*" +Function,+,path_concat,void,"const char*, const char*, string_t" +Function,+,path_contains_only_ascii,_Bool,const char* +Function,+,path_extract_basename,void,"const char*, string_t" +Function,+,path_extract_dirname,void,"const char*, string_t" +Function,+,path_extract_extension,void,"string_t, char*, size_t" +Function,+,path_extract_filename,void,"string_t, string_t, _Bool" +Function,+,path_extract_filename_no_ext,void,"const char*, string_t" +Function,-,pcTaskGetName,char*,TaskHandle_t +Function,-,pcTimerGetName,const char*,TimerHandle_t +Function,-,pclose,int,FILE* +Function,-,perror,void,const char* +Function,-,platformDisableIrqCallback,void, +Function,-,platformEnableIrqCallback,void, +Function,-,platformProtectST25RComm,void, +Function,-,platformSetIrqCallback,void,PlatformIrqCallback +Function,-,platformSpiTxRx,_Bool,"const uint8_t*, uint8_t*, uint16_t" +Function,-,platformUnprotectST25RComm,void, +Function,-,popen,FILE*,"const char*, const char*" +Function,+,popup_alloc,Popup*, +Function,+,popup_disable_timeout,void,Popup* +Function,+,popup_enable_timeout,void,Popup* +Function,+,popup_free,void,Popup* +Function,+,popup_get_view,View*,Popup* +Function,+,popup_reset,void,Popup* +Function,+,popup_set_callback,void,"Popup*, PopupCallback" +Function,+,popup_set_context,void,"Popup*, void*" +Function,+,popup_set_header,void,"Popup*, const char*, uint8_t, uint8_t, Align, Align" +Function,+,popup_set_icon,void,"Popup*, uint8_t, uint8_t, const Icon*" +Function,+,popup_set_text,void,"Popup*, const char*, uint8_t, uint8_t, Align, Align" +Function,+,popup_set_timeout,void,"Popup*, uint32_t" +Function,-,posix_memalign,int,"void**, size_t, size_t" +Function,-,pow,double,"double, double" +Function,-,pow10,double,double +Function,-,pow10f,float,float +Function,-,power_enable_low_battery_level_notification,void,"Power*, _Bool" +Function,+,power_get_info,void,"Power*, PowerInfo*" +Function,+,power_get_pubsub,FuriPubSub*,Power* +Function,+,power_is_battery_healthy,_Bool,Power* +Function,+,power_off,void,Power* +Function,+,power_reboot,void,PowerBootMode +Function,+,powf,float,"float, float" +Function,-,powl,long double,"long double, long double" +Function,-,printf,int,"const char*, ..." +Function,-,pselect,int,"int, fd_set*, fd_set*, fd_set*, const timespec*, const sigset_t*" +Function,-,putc,int,"int, FILE*" +Function,-,putc_unlocked,int,"int, FILE*" +Function,-,putchar,int,int +Function,-,putchar_unlocked,int,int +Function,-,putenv,int,char* +Function,-,puts,int,const char* +Function,-,putw,int,"int, FILE*" +Function,-,pvPortMalloc,void*,size_t +Function,-,pvTaskGetThreadLocalStoragePointer,void*,"TaskHandle_t, BaseType_t" +Function,-,pvTaskIncrementMutexHeldCount,TaskHandle_t, +Function,-,pvTimerGetTimerID,void*,const TimerHandle_t +Function,-,pxPortInitialiseStack,StackType_t*,"StackType_t*, TaskFunction_t, void*" +Function,-,qsort,void,"void*, size_t, size_t, __compar_fn_t" +Function,-,qsort_r,void,"void*, size_t, size_t, int (*)(const void*, const void*, void*), void*" +Function,-,quick_exit,void,int +Function,+,rand,int, +Function,-,rand_r,int,unsigned* +Function,+,random,long, +Function,-,rawmemchr,void*,"const void*, int" +Function,-,read_mutex,_Bool,"ValueMutex*, void*, size_t, uint32_t" +Function,+,realloc,void*,"void*, size_t" +Function,-,reallocarray,void*,"void*, size_t, size_t" +Function,-,reallocf,void*,"void*, size_t" +Function,-,realpath,char*,"const char*, char*" +Function,+,release_mutex,_Bool,"ValueMutex*, const void*" +Function,-,remainder,double,"double, double" +Function,-,remainderf,float,"float, float" +Function,-,remainderl,long double,"long double, long double" +Function,-,remove,int,const char* +Function,-,remquo,double,"double, double, int*" +Function,-,remquof,float,"float, float, int*" +Function,-,remquol,long double,"long double, long double, int*" +Function,-,rename,int,"const char*, const char*" +Function,-,renameat,int,"int, const char*, int, const char*" +Function,-,rewind,void,FILE* +Function,-,rfalAdjustRegulators,ReturnCode,uint16_t* +Function,-,rfalCalibrate,ReturnCode, +Function,-,rfalDeinitialize,ReturnCode, +Function,-,rfalDisableObsvMode,void, +Function,-,rfalFeliCaPoll,ReturnCode,"rfalFeliCaPollSlots, uint16_t, uint8_t, rfalFeliCaPollRes*, uint8_t, uint8_t*, uint8_t*" +Function,-,rfalFieldOff,ReturnCode, +Function,+,rfalFieldOnAndStartGT,ReturnCode, +Function,-,rfalGetBitRate,ReturnCode,"rfalBitRate*, rfalBitRate*" +Function,-,rfalGetErrorHandling,rfalEHandling, +Function,-,rfalGetFDTListen,uint32_t, +Function,-,rfalGetFDTPoll,uint32_t, +Function,-,rfalGetGT,uint32_t, +Function,-,rfalGetMode,rfalMode, +Function,-,rfalGetObsvMode,void,"uint8_t*, uint8_t*" +Function,-,rfalGetTransceiveRSSI,ReturnCode,uint16_t* +Function,-,rfalGetTransceiveState,rfalTransceiveState, +Function,-,rfalGetTransceiveStatus,ReturnCode, +Function,-,rfalISO14443ATransceiveAnticollisionFrame,ReturnCode,"uint8_t*, uint8_t*, uint8_t*, uint16_t*, uint32_t" +Function,-,rfalISO14443ATransceiveShortFrame,ReturnCode,"rfal14443AShortFrameCmd, uint8_t*, uint8_t, uint16_t*, uint32_t" +Function,-,rfalISO15693TransceiveAnticollisionFrame,ReturnCode,"uint8_t*, uint8_t, uint8_t*, uint8_t, uint16_t*" +Function,-,rfalISO15693TransceiveEOF,ReturnCode,"uint8_t*, uint8_t, uint16_t*" +Function,-,rfalISO15693TransceiveEOFAnticollision,ReturnCode,"uint8_t*, uint8_t, uint16_t*" +Function,-,rfalInitialize,ReturnCode, +Function,-,rfalIsExtFieldOn,_Bool, +Function,-,rfalIsGTExpired,_Bool, +Function,-,rfalIsTransceiveInRx,_Bool, +Function,-,rfalIsTransceiveInTx,_Bool, +Function,-,rfalIsoDepATTRIB,ReturnCode,"const uint8_t*, uint8_t, rfalBitRate, rfalBitRate, rfalIsoDepFSxI, uint8_t, uint8_t, const uint8_t*, uint8_t, uint32_t, rfalIsoDepAttribRes*, uint8_t*" +Function,-,rfalIsoDepDeselect,ReturnCode, +Function,-,rfalIsoDepFSxI2FSx,uint16_t,uint8_t +Function,-,rfalIsoDepFWI2FWT,uint32_t,uint8_t +Function,-,rfalIsoDepGetApduTransceiveStatus,ReturnCode, +Function,-,rfalIsoDepGetMaxInfLen,uint16_t, +Function,-,rfalIsoDepGetTransceiveStatus,ReturnCode, +Function,-,rfalIsoDepInitialize,void, +Function,-,rfalIsoDepInitializeWithParams,void,"rfalComplianceMode, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t" +Function,-,rfalIsoDepIsAttrib,_Bool,"const uint8_t*, uint8_t" +Function,-,rfalIsoDepIsRats,_Bool,"const uint8_t*, uint8_t" +Function,-,rfalIsoDepListenGetActivationStatus,ReturnCode, +Function,-,rfalIsoDepListenStartActivation,ReturnCode,"rfalIsoDepAtsParam*, const rfalIsoDepAttribResParam*, const uint8_t*, uint16_t, rfalIsoDepListenActvParam" +Function,-,rfalIsoDepPPS,ReturnCode,"uint8_t, rfalBitRate, rfalBitRate, rfalIsoDepPpsRes*" +Function,-,rfalIsoDepPollAGetActivationStatus,ReturnCode, +Function,-,rfalIsoDepPollAHandleActivation,ReturnCode,"rfalIsoDepFSxI, uint8_t, rfalBitRate, rfalIsoDepDevice*" +Function,-,rfalIsoDepPollAStartActivation,ReturnCode,"rfalIsoDepFSxI, uint8_t, rfalBitRate, rfalIsoDepDevice*" +Function,-,rfalIsoDepPollBGetActivationStatus,ReturnCode, +Function,-,rfalIsoDepPollBHandleActivation,ReturnCode,"rfalIsoDepFSxI, uint8_t, rfalBitRate, uint8_t, const rfalNfcbListenDevice*, const uint8_t*, uint8_t, rfalIsoDepDevice*" +Function,-,rfalIsoDepPollBStartActivation,ReturnCode,"rfalIsoDepFSxI, uint8_t, rfalBitRate, uint8_t, const rfalNfcbListenDevice*, const uint8_t*, uint8_t, rfalIsoDepDevice*" +Function,-,rfalIsoDepPollHandleSParameters,ReturnCode,"rfalIsoDepDevice*, rfalBitRate, rfalBitRate" +Function,-,rfalIsoDepRATS,ReturnCode,"rfalIsoDepFSxI, uint8_t, rfalIsoDepAts*, uint8_t*" +Function,-,rfalIsoDepStartApduTransceive,ReturnCode,rfalIsoDepApduTxRxParam +Function,-,rfalIsoDepStartTransceive,ReturnCode,rfalIsoDepTxRxParam +Function,-,rfalListenGetState,rfalLmState,"_Bool*, rfalBitRate*" +Function,-,rfalListenSetState,ReturnCode,rfalLmState +Function,-,rfalListenSleepStart,ReturnCode,"rfalLmState, uint8_t*, uint16_t, uint16_t*" +Function,-,rfalListenStart,ReturnCode,"uint32_t, const rfalLmConfPA*, const rfalLmConfPB*, const rfalLmConfPF*, uint8_t*, uint16_t, uint16_t*" +Function,-,rfalListenStop,ReturnCode, +Function,+,rfalLowPowerModeStart,ReturnCode, +Function,+,rfalLowPowerModeStop,ReturnCode, +Function,-,rfalNfcDataExchangeCustomStart,ReturnCode,"uint8_t*, uint16_t, uint8_t**, uint16_t**, uint32_t, uint32_t" +Function,-,rfalNfcDataExchangeGetStatus,ReturnCode, +Function,-,rfalNfcDataExchangeStart,ReturnCode,"uint8_t*, uint16_t, uint8_t**, uint16_t**, uint32_t, uint32_t" +Function,-,rfalNfcDeactivate,ReturnCode,_Bool +Function,-,rfalNfcDepATR,ReturnCode,"const rfalNfcDepAtrParam*, rfalNfcDepAtrRes*, uint8_t*" +Function,-,rfalNfcDepCalculateRWT,uint32_t,uint8_t +Function,-,rfalNfcDepDSL,ReturnCode, +Function,-,rfalNfcDepGetPduTransceiveStatus,ReturnCode, +Function,-,rfalNfcDepGetTransceiveStatus,ReturnCode, +Function,-,rfalNfcDepInitialize,void, +Function,-,rfalNfcDepInitiatorHandleActivation,ReturnCode,"rfalNfcDepAtrParam*, rfalBitRate, rfalNfcDepDevice*" +Function,-,rfalNfcDepIsAtrReq,_Bool,"const uint8_t*, uint16_t, uint8_t*" +Function,-,rfalNfcDepListenGetActivationStatus,ReturnCode, +Function,-,rfalNfcDepListenStartActivation,ReturnCode,"const rfalNfcDepTargetParam*, const uint8_t*, uint16_t, rfalNfcDepListenActvParam" +Function,-,rfalNfcDepPSL,ReturnCode,"uint8_t, uint8_t" +Function,-,rfalNfcDepRLS,ReturnCode, +Function,-,rfalNfcDepSetDeactivatingCallback,void,rfalNfcDepDeactCallback +Function,-,rfalNfcDepStartPduTransceive,ReturnCode,rfalNfcDepPduTxRxParam +Function,-,rfalNfcDepStartTransceive,ReturnCode,const rfalNfcDepTxRxParam* +Function,-,rfalNfcDepTargetRcvdATR,_Bool, +Function,-,rfalNfcDiscover,ReturnCode,const rfalNfcDiscoverParam* +Function,-,rfalNfcGetActiveDevice,ReturnCode,rfalNfcDevice** +Function,-,rfalNfcGetDevicesFound,ReturnCode,"rfalNfcDevice**, uint8_t*" +Function,-,rfalNfcGetState,rfalNfcState, +Function,-,rfalNfcInitialize,ReturnCode, +Function,-,rfalNfcSelect,ReturnCode,uint8_t +Function,-,rfalNfcWorker,void, +Function,-,rfalNfcaListenerIsSleepReq,_Bool,"const uint8_t*, uint16_t" +Function,-,rfalNfcaPollerCheckPresence,ReturnCode,"rfal14443AShortFrameCmd, rfalNfcaSensRes*" +Function,-,rfalNfcaPollerFullCollisionResolution,ReturnCode,"rfalComplianceMode, uint8_t, rfalNfcaListenDevice*, uint8_t*" +Function,-,rfalNfcaPollerGetFullCollisionResolutionStatus,ReturnCode, +Function,-,rfalNfcaPollerInitialize,ReturnCode, +Function,-,rfalNfcaPollerSelect,ReturnCode,"const uint8_t*, uint8_t, rfalNfcaSelRes*" +Function,-,rfalNfcaPollerSingleCollisionResolution,ReturnCode,"uint8_t, _Bool*, rfalNfcaSelRes*, uint8_t*, uint8_t*" +Function,-,rfalNfcaPollerSleep,ReturnCode, +Function,-,rfalNfcaPollerSleepFullCollisionResolution,ReturnCode,"uint8_t, rfalNfcaListenDevice*, uint8_t*" +Function,-,rfalNfcaPollerStartFullCollisionResolution,ReturnCode,"rfalComplianceMode, uint8_t, rfalNfcaListenDevice*, uint8_t*" +Function,-,rfalNfcaPollerTechnologyDetection,ReturnCode,"rfalComplianceMode, rfalNfcaSensRes*" +Function,-,rfalNfcbPollerCheckPresence,ReturnCode,"rfalNfcbSensCmd, rfalNfcbSlots, rfalNfcbSensbRes*, uint8_t*" +Function,-,rfalNfcbPollerCollisionResolution,ReturnCode,"rfalComplianceMode, uint8_t, rfalNfcbListenDevice*, uint8_t*" +Function,-,rfalNfcbPollerInitialize,ReturnCode, +Function,-,rfalNfcbPollerInitializeWithParams,ReturnCode,"uint8_t, uint8_t" +Function,-,rfalNfcbPollerSleep,ReturnCode,const uint8_t* +Function,-,rfalNfcbPollerSlotMarker,ReturnCode,"uint8_t, rfalNfcbSensbRes*, uint8_t*" +Function,-,rfalNfcbPollerSlottedCollisionResolution,ReturnCode,"rfalComplianceMode, uint8_t, rfalNfcbSlots, rfalNfcbSlots, rfalNfcbListenDevice*, uint8_t*, _Bool*" +Function,-,rfalNfcbPollerTechnologyDetection,ReturnCode,"rfalComplianceMode, rfalNfcbSensbRes*, uint8_t*" +Function,-,rfalNfcbTR2ToFDT,uint32_t,uint8_t +Function,-,rfalNfcfListenerIsT3TReq,_Bool,"const uint8_t*, uint16_t, uint8_t*" +Function,-,rfalNfcfPollerCheck,ReturnCode,"const uint8_t*, const rfalNfcfServBlockListParam*, uint8_t*, uint16_t, uint16_t*" +Function,-,rfalNfcfPollerCheckPresence,ReturnCode, +Function,-,rfalNfcfPollerCollisionResolution,ReturnCode,"rfalComplianceMode, uint8_t, rfalNfcfListenDevice*, uint8_t*" +Function,-,rfalNfcfPollerInitialize,ReturnCode,rfalBitRate +Function,-,rfalNfcfPollerPoll,ReturnCode,"rfalFeliCaPollSlots, uint16_t, uint8_t, rfalFeliCaPollRes*, uint8_t*, uint8_t*" +Function,-,rfalNfcfPollerUpdate,ReturnCode,"const uint8_t*, const rfalNfcfServBlockListParam*, uint8_t*, uint16_t, const uint8_t*, uint8_t*, uint16_t" +Function,-,rfalNfcvPollerCheckPresence,ReturnCode,rfalNfcvInventoryRes* +Function,-,rfalNfcvPollerCollisionResolution,ReturnCode,"rfalComplianceMode, uint8_t, rfalNfcvListenDevice*, uint8_t*" +Function,-,rfalNfcvPollerExtendedGetSystemInformation,ReturnCode,"uint8_t, const uint8_t*, uint8_t, uint8_t*, uint16_t, uint16_t*" +Function,-,rfalNfcvPollerExtendedLockSingleBlock,ReturnCode,"uint8_t, const uint8_t*, uint16_t" +Function,-,rfalNfcvPollerExtendedReadMultipleBlocks,ReturnCode,"uint8_t, const uint8_t*, uint16_t, uint16_t, uint8_t*, uint16_t, uint16_t*" +Function,-,rfalNfcvPollerExtendedReadSingleBlock,ReturnCode,"uint8_t, const uint8_t*, uint16_t, uint8_t*, uint16_t, uint16_t*" +Function,-,rfalNfcvPollerExtendedWriteMultipleBlocks,ReturnCode,"uint8_t, const uint8_t*, uint16_t, uint16_t, uint8_t*, uint16_t, uint8_t, const uint8_t*, uint16_t" +Function,-,rfalNfcvPollerExtendedWriteSingleBlock,ReturnCode,"uint8_t, const uint8_t*, uint16_t, const uint8_t*, uint8_t" +Function,-,rfalNfcvPollerGetSystemInformation,ReturnCode,"uint8_t, const uint8_t*, uint8_t*, uint16_t, uint16_t*" +Function,-,rfalNfcvPollerInitialize,ReturnCode, +Function,-,rfalNfcvPollerInventory,ReturnCode,"rfalNfcvNumSlots, uint8_t, const uint8_t*, rfalNfcvInventoryRes*, uint16_t*" +Function,-,rfalNfcvPollerLockBlock,ReturnCode,"uint8_t, const uint8_t*, uint8_t" +Function,-,rfalNfcvPollerReadMultipleBlocks,ReturnCode,"uint8_t, const uint8_t*, uint8_t, uint8_t, uint8_t*, uint16_t, uint16_t*" +Function,-,rfalNfcvPollerReadSingleBlock,ReturnCode,"uint8_t, const uint8_t*, uint8_t, uint8_t*, uint16_t, uint16_t*" +Function,-,rfalNfcvPollerSelect,ReturnCode,"uint8_t, const uint8_t*" +Function,-,rfalNfcvPollerSleep,ReturnCode,"uint8_t, const uint8_t*" +Function,-,rfalNfcvPollerSleepCollisionResolution,ReturnCode,"uint8_t, rfalNfcvListenDevice*, uint8_t*" +Function,-,rfalNfcvPollerTransceiveReq,ReturnCode,"uint8_t, uint8_t, uint8_t, const uint8_t*, const uint8_t*, uint16_t, uint8_t*, uint16_t, uint16_t*" +Function,-,rfalNfcvPollerWriteMultipleBlocks,ReturnCode,"uint8_t, const uint8_t*, uint8_t, uint8_t, uint8_t*, uint16_t, uint8_t, const uint8_t*, uint16_t" +Function,-,rfalNfcvPollerWriteSingleBlock,ReturnCode,"uint8_t, const uint8_t*, uint8_t, const uint8_t*, uint8_t" +Function,-,rfalSetBitRate,ReturnCode,"rfalBitRate, rfalBitRate" +Function,-,rfalSetErrorHandling,void,rfalEHandling +Function,-,rfalSetFDTListen,void,uint32_t +Function,-,rfalSetFDTPoll,void,uint32_t +Function,-,rfalSetGT,void,uint32_t +Function,-,rfalSetMode,ReturnCode,"rfalMode, rfalBitRate, rfalBitRate" +Function,-,rfalSetObsvMode,void,"uint8_t, uint8_t" +Function,-,rfalSetPostTxRxCallback,void,rfalPostTxRxCallback +Function,-,rfalSetPreTxRxCallback,void,rfalPreTxRxCallback +Function,-,rfalSetUpperLayerCallback,void,rfalUpperLayerCallback +Function,-,rfalSt25tbPollerCheckPresence,ReturnCode,uint8_t* +Function,-,rfalSt25tbPollerCollisionResolution,ReturnCode,"uint8_t, rfalSt25tbListenDevice*, uint8_t*" +Function,-,rfalSt25tbPollerCompletion,ReturnCode, +Function,-,rfalSt25tbPollerGetUID,ReturnCode,rfalSt25tbUID* +Function,-,rfalSt25tbPollerInitialize,ReturnCode, +Function,-,rfalSt25tbPollerInitiate,ReturnCode,uint8_t* +Function,-,rfalSt25tbPollerPcall,ReturnCode,uint8_t* +Function,-,rfalSt25tbPollerReadBlock,ReturnCode,"uint8_t, rfalSt25tbBlock*" +Function,-,rfalSt25tbPollerResetToInventory,ReturnCode, +Function,-,rfalSt25tbPollerSelect,ReturnCode,uint8_t +Function,-,rfalSt25tbPollerSlotMarker,ReturnCode,"uint8_t, uint8_t*" +Function,-,rfalSt25tbPollerWriteBlock,ReturnCode,"uint8_t, const rfalSt25tbBlock*" +Function,-,rfalStartTransceive,ReturnCode,const rfalTransceiveContext* +Function,-,rfalT1TPollerInitialize,ReturnCode, +Function,-,rfalT1TPollerRall,ReturnCode,"const uint8_t*, uint8_t*, uint16_t, uint16_t*" +Function,-,rfalT1TPollerRid,ReturnCode,rfalT1TRidRes* +Function,-,rfalT1TPollerWrite,ReturnCode,"const uint8_t*, uint8_t, uint8_t" +Function,-,rfalTransceiveBitsBlockingTx,ReturnCode,"uint8_t*, uint16_t, uint8_t*, uint16_t, uint16_t*, uint32_t, uint32_t" +Function,-,rfalTransceiveBlockingRx,ReturnCode, +Function,-,rfalTransceiveBlockingTx,ReturnCode,"uint8_t*, uint16_t, uint8_t*, uint16_t, uint16_t*, uint32_t, uint32_t" +Function,-,rfalTransceiveBlockingTxRx,ReturnCode,"uint8_t*, uint16_t, uint8_t*, uint16_t, uint16_t*, uint32_t, uint32_t" +Function,-,rfalWakeUpModeHasWoke,_Bool, +Function,-,rfalWakeUpModeStart,ReturnCode,const rfalWakeUpConfig* +Function,-,rfalWakeUpModeStop,ReturnCode, +Function,+,rfalWorker,void, +Function,-,rfal_platform_spi_acquire,void, +Function,-,rfal_platform_spi_release,void, +Function,-,rfal_set_callback_context,void,void* +Function,-,rfal_set_state_changed_callback,void,RfalStateChangedCallback +Function,-,rindex,char*,"const char*, int" +Function,-,rint,double,double +Function,-,rintf,float,float +Function,-,rintl,long double,long double +Function,-,round,double,double +Function,+,roundf,float,float +Function,-,roundl,long double,long double +Function,+,rpc_session_close,void,RpcSession* +Function,+,rpc_session_feed,size_t,"RpcSession*, uint8_t*, size_t, TickType_t" +Function,+,rpc_session_get_available_size,size_t,RpcSession* +Function,+,rpc_session_open,RpcSession*,Rpc* +Function,+,rpc_session_set_buffer_is_empty_callback,void,"RpcSession*, RpcBufferIsEmptyCallback" +Function,+,rpc_session_set_close_callback,void,"RpcSession*, RpcSessionClosedCallback" +Function,+,rpc_session_set_context,void,"RpcSession*, void*" +Function,+,rpc_session_set_send_bytes_callback,void,"RpcSession*, RpcSendBytesCallback" +Function,+,rpc_session_set_terminated_callback,void,"RpcSession*, RpcSessionTerminatedCallback" +Function,+,rpc_system_app_confirm,void,"RpcAppSystem*, RpcAppSystemEvent, _Bool" +Function,+,rpc_system_app_get_data,const char*,RpcAppSystem* +Function,+,rpc_system_app_send_exited,void,RpcAppSystem* +Function,+,rpc_system_app_send_started,void,RpcAppSystem* +Function,+,rpc_system_app_set_callback,void,"RpcAppSystem*, RpcAppSystemCallback, void*" +Function,-,rpmatch,int,const char* +Function,+,saved_struct_load,_Bool,"const char*, void*, size_t, uint8_t, uint8_t" +Function,+,saved_struct_save,_Bool,"const char*, void*, size_t, uint8_t, uint8_t" +Function,-,scalbln,double,"double, long int" +Function,-,scalblnf,float,"float, long int" +Function,-,scalblnl,long double,"long double, long" +Function,-,scalbn,double,"double, int" +Function,+,scalbnf,float,"float, int" +Function,-,scalbnl,long double,"long double, int" +Function,-,scanf,int,"const char*, ..." +Function,+,scene_manager_alloc,SceneManager*,"const SceneManagerHandlers*, void*" +Function,+,scene_manager_free,void,SceneManager* +Function,+,scene_manager_get_scene_state,uint32_t,"SceneManager*, uint32_t" +Function,+,scene_manager_handle_back_event,_Bool,SceneManager* +Function,+,scene_manager_handle_custom_event,_Bool,"SceneManager*, uint32_t" +Function,+,scene_manager_handle_tick_event,void,SceneManager* +Function,+,scene_manager_has_previous_scene,_Bool,"SceneManager*, uint32_t" +Function,+,scene_manager_next_scene,void,"SceneManager*, uint32_t" +Function,+,scene_manager_previous_scene,_Bool,SceneManager* +Function,+,scene_manager_search_and_switch_to_another_scene,_Bool,"SceneManager*, uint32_t" +Function,+,scene_manager_search_and_switch_to_previous_scene,_Bool,"SceneManager*, uint32_t" +Function,+,scene_manager_search_and_switch_to_previous_scene_one_of,_Bool,"SceneManager*, const uint32_t*, size_t" +Function,+,scene_manager_set_scene_state,void,"SceneManager*, uint32_t, uint32_t" +Function,+,scene_manager_stop,void,SceneManager* +Function,+,sd_api_get_fs_type_text,const char*,SDFsType +Function,-,secure_getenv,char*,const char* +Function,-,seed48,unsigned short*,unsigned short[3] +Function,-,select,int,"int, fd_set*, fd_set*, fd_set*, timeval*" +Function,-,serial_svc_is_started,_Bool, +Function,-,serial_svc_notify_buffer_is_empty,void, +Function,-,serial_svc_set_callbacks,void,"uint16_t, SerialServiceEventCallback, void*" +Function,-,serial_svc_start,void, +Function,-,serial_svc_stop,void, +Function,-,serial_svc_update_tx,_Bool,"uint8_t*, uint16_t" +Function,+,set_random_name,void,"char*, uint8_t" +Function,-,setbuf,void,"FILE*, char*" +Function,-,setbuffer,void,"FILE*, char*, int" +Function,-,setenv,int,"const char*, const char*, int" +Function,-,setkey,void,const char* +Function,-,setlinebuf,int,FILE* +Function,-,setstate,char*,char* +Function,-,setvbuf,int,"FILE*, char*, int, size_t" +Function,+,sha256,void,"const unsigned char*, unsigned int, unsigned char[32]" +Function,+,sha256_finish,void,"sha256_context*, unsigned char[32]" +Function,+,sha256_process,void,sha256_context* +Function,+,sha256_start,void,sha256_context* +Function,+,sha256_update,void,"sha256_context*, const unsigned char*, unsigned int" +Function,-,sin,double,double +Function,-,sincos,void,"double, double*, double*" +Function,-,sincosf,void,"float, float*, float*" +Function,-,sinf,float,float +Function,-,sinh,double,double +Function,-,sinhf,float,float +Function,-,sinhl,long double,long double +Function,-,sinl,long double,long double +Function,-,siprintf,int,"char*, const char*, ..." +Function,-,siscanf,int,"const char*, const char*, ..." +Function,-,sniprintf,int,"char*, size_t, const char*, ..." +Function,+,snprintf,int,"char*, size_t, const char*, ..." +Function,-,sprintf,int,"char*, const char*, ..." +Function,-,sqrt,double,double +Function,-,sqrtf,float,float +Function,-,sqrtl,long double,long double +Function,+,srand,void,unsigned +Function,-,srand48,void,long +Function,-,srandom,void,unsigned +Function,+,sscanf,int,"const char*, const char*, ..." +Function,+,storage_common_copy,FS_Error,"Storage*, const char*, const char*" +Function,+,storage_common_fs_info,FS_Error,"Storage*, const char*, uint64_t*, uint64_t*" +Function,+,storage_common_merge,FS_Error,"Storage*, const char*, const char*" +Function,+,storage_common_mkdir,FS_Error,"Storage*, const char*" +Function,+,storage_common_remove,FS_Error,"Storage*, const char*" +Function,+,storage_common_rename,FS_Error,"Storage*, const char*, const char*" +Function,+,storage_common_stat,FS_Error,"Storage*, const char*, FileInfo*" +Function,+,storage_dir_close,_Bool,File* +Function,+,storage_dir_open,_Bool,"File*, const char*" +Function,+,storage_dir_read,_Bool,"File*, FileInfo*, char*, uint16_t" +Function,-,storage_dir_rewind,_Bool,File* +Function,+,storage_error_get_desc,const char*,FS_Error +Function,+,storage_file_alloc,File*,Storage* +Function,+,storage_file_close,_Bool,File* +Function,+,storage_file_eof,_Bool,File* +Function,+,storage_file_exists,_Bool,"Storage*, const char*" +Function,+,storage_file_free,void,File* +Function,+,storage_file_get_error,FS_Error,File* +Function,+,storage_file_get_error_desc,const char*,File* +Function,-,storage_file_get_internal_error,int32_t,File* +Function,+,storage_file_is_dir,_Bool,File* +Function,+,storage_file_is_open,_Bool,File* +Function,+,storage_file_open,_Bool,"File*, const char*, FS_AccessMode, FS_OpenMode" +Function,+,storage_file_read,uint16_t,"File*, void*, uint16_t" +Function,+,storage_file_seek,_Bool,"File*, uint32_t, _Bool" +Function,+,storage_file_size,uint64_t,File* +Function,-,storage_file_sync,_Bool,File* +Function,+,storage_file_tell,uint64_t,File* +Function,+,storage_file_truncate,_Bool,File* +Function,+,storage_file_write,uint16_t,"File*, const void*, uint16_t" +Function,+,storage_get_next_filename,void,"Storage*, const char*, const char*, const char*, string_t, uint8_t" +Function,+,storage_get_pubsub,FuriPubSub*,Storage* +Function,+,storage_int_backup,FS_Error,"Storage*, const char*" +Function,+,storage_int_restore,FS_Error,"Storage*, const char*, Storage_name_converter" +Function,+,storage_sd_format,FS_Error,Storage* +Function,+,storage_sd_info,FS_Error,"Storage*, SDInfo*" +Function,+,storage_sd_status,FS_Error,Storage* +Function,+,storage_sd_unmount,FS_Error,Storage* +Function,+,storage_simply_mkdir,_Bool,"Storage*, const char*" +Function,+,storage_simply_remove,_Bool,"Storage*, const char*" +Function,+,storage_simply_remove_recursive,_Bool,"Storage*, const char*" +Function,-,stpcpy,char*,"char*, const char*" +Function,-,stpncpy,char*,"char*, const char*, size_t" +Function,-,strcasecmp,int,"const char*, const char*" +Function,-,strcasecmp_l,int,"const char*, const char*, locale_t" +Function,+,strcasestr,char*,"const char*, const char*" +Function,-,strcat,char*,"char*, const char*" +Function,+,strchr,char*,"const char*, int" +Function,-,strchrnul,char*,"const char*, int" +Function,+,strcmp,int,"const char*, const char*" +Function,-,strcoll,int,"const char*, const char*" +Function,-,strcoll_l,int,"const char*, const char*, locale_t" +Function,+,strcpy,char*,"char*, const char*" +Function,+,strcspn,size_t,"const char*, const char*" +Function,+,strdup,char*,const char* +Function,+,stream_clean,void,Stream* +Function,+,stream_copy,size_t,"Stream*, Stream*, size_t" +Function,+,stream_copy_full,size_t,"Stream*, Stream*" +Function,+,stream_delete,_Bool,"Stream*, size_t" +Function,+,stream_delete_and_insert,_Bool,"Stream*, size_t, StreamWriteCB, const void*" +Function,+,stream_delete_and_insert_char,_Bool,"Stream*, size_t, char" +Function,+,stream_delete_and_insert_cstring,_Bool,"Stream*, size_t, const char*" +Function,+,stream_delete_and_insert_format,_Bool,"Stream*, size_t, const char*, ..." +Function,+,stream_delete_and_insert_string,_Bool,"Stream*, size_t, string_t" +Function,+,stream_delete_and_insert_vaformat,_Bool,"Stream*, size_t, const char*, va_list" +Function,+,stream_dump_data,void,Stream* +Function,+,stream_eof,_Bool,Stream* +Function,+,stream_free,void,Stream* +Function,+,stream_insert,_Bool,"Stream*, const uint8_t*, size_t" +Function,+,stream_insert_char,_Bool,"Stream*, char" +Function,+,stream_insert_cstring,_Bool,"Stream*, const char*" +Function,+,stream_insert_format,_Bool,"Stream*, const char*, ..." +Function,+,stream_insert_string,_Bool,"Stream*, string_t" +Function,+,stream_insert_vaformat,_Bool,"Stream*, const char*, va_list" +Function,+,stream_load_from_file,size_t,"Stream*, Storage*, const char*" +Function,+,stream_read,size_t,"Stream*, uint8_t*, size_t" +Function,+,stream_read_line,_Bool,"Stream*, string_t" +Function,+,stream_rewind,_Bool,Stream* +Function,+,stream_save_to_file,size_t,"Stream*, Storage*, const char*, FS_OpenMode" +Function,+,stream_seek,_Bool,"Stream*, int32_t, StreamOffset" +Function,+,stream_size,size_t,Stream* +Function,+,stream_split,_Bool,"Stream*, Stream*, Stream*" +Function,+,stream_tell,size_t,Stream* +Function,+,stream_write,size_t,"Stream*, const uint8_t*, size_t" +Function,+,stream_write_char,size_t,"Stream*, char" +Function,+,stream_write_cstring,size_t,"Stream*, const char*" +Function,+,stream_write_format,size_t,"Stream*, const char*, ..." +Function,+,stream_write_string,size_t,"Stream*, string_t" +Function,+,stream_write_vaformat,size_t,"Stream*, const char*, va_list" +Function,-,strerror,char*,int +Function,-,strerror_l,char*,"int, locale_t" +Function,-,strerror_r,char*,"int, char*, size_t" +Function,+,string_stream_alloc,Stream*, +Function,-,strlcat,size_t,"char*, const char*, size_t" +Function,+,strlcpy,size_t,"char*, const char*, size_t" +Function,+,strlen,size_t,const char* +Function,-,strlwr,char*,char* +Function,+,strncasecmp,int,"const char*, const char*, size_t" +Function,-,strncasecmp_l,int,"const char*, const char*, size_t, locale_t" +Function,-,strncat,char*,"char*, const char*, size_t" +Function,+,strncmp,int,"const char*, const char*, size_t" +Function,+,strncpy,char*,"char*, const char*, size_t" +Function,-,strndup,char*,"const char*, size_t" +Function,-,strnlen,size_t,"const char*, size_t" +Function,-,strnstr,char*,"const char*, const char*, size_t" +Function,-,strpbrk,char*,"const char*, const char*" +Function,+,strrchr,char*,"const char*, int" +Function,-,strsep,char*,"char**, const char*" +Function,-,strsignal,char*,int +Function,+,strspn,size_t,"const char*, const char*" +Function,+,strstr,char*,"const char*, const char*" +Function,-,strtod,double,"const char*, char**" +Function,-,strtod_l,double,"const char*, char**, locale_t" +Function,+,strtof,float,"const char*, char**" +Function,-,strtof_l,float,"const char*, char**, locale_t" +Function,-,strtok,char*,"char*, const char*" +Function,-,strtok_r,char*,"char*, const char*, char**" +Function,+,strtol,long,"const char*, char**, int" +Function,-,strtol_l,long,"const char*, char**, int, locale_t" +Function,-,strtold,long double,"const char*, char**" +Function,-,strtold_l,long double,"const char*, char**, locale_t" +Function,-,strtoll,long long,"const char*, char**, int" +Function,-,strtoll_l,long long,"const char*, char**, int, locale_t" +Function,+,strtoul,unsigned long,"const char*, char**, int" +Function,-,strtoul_l,unsigned long,"const char*, char**, int, locale_t" +Function,+,strtoull,unsigned long long,"const char*, char**, int" +Function,-,strtoull_l,unsigned long long,"const char*, char**, int, locale_t" +Function,-,strupr,char*,char* +Function,-,strverscmp,int,"const char*, const char*" +Function,-,strxfrm,size_t,"char*, const char*, size_t" +Function,-,strxfrm_l,size_t,"char*, const char*, size_t, locale_t" +Function,+,subghz_environment_alloc,SubGhzEnvironment*, +Function,+,subghz_environment_free,void,SubGhzEnvironment* +Function,-,subghz_environment_get_came_atomo_rainbow_table_file_name,const char*,SubGhzEnvironment* +Function,-,subghz_environment_get_keystore,SubGhzKeystore*,SubGhzEnvironment* +Function,-,subghz_environment_get_nice_flor_s_rainbow_table_file_name,const char*,SubGhzEnvironment* +Function,+,subghz_environment_load_keystore,_Bool,"SubGhzEnvironment*, const char*" +Function,-,subghz_environment_set_came_atomo_rainbow_table_file_name,void,"SubGhzEnvironment*, const char*" +Function,-,subghz_environment_set_nice_flor_s_rainbow_table_file_name,void,"SubGhzEnvironment*, const char*" +Function,-,subghz_keystore_alloc,SubGhzKeystore*, +Function,-,subghz_keystore_free,void,SubGhzKeystore* +Function,-,subghz_keystore_get_data,SubGhzKeyArray_t*,SubGhzKeystore* +Function,-,subghz_keystore_load,_Bool,"SubGhzKeystore*, const char*" +Function,-,subghz_keystore_raw_encrypted_save,_Bool,"const char*, const char*, uint8_t*" +Function,-,subghz_keystore_raw_get_data,_Bool,"const char*, size_t, uint8_t*, size_t" +Function,-,subghz_keystore_save,_Bool,"SubGhzKeystore*, const char*, uint8_t*" +Function,-,subghz_protocol_decoder_base_deserialize,_Bool,"SubGhzProtocolDecoderBase*, FlipperFormat*" +Function,-,subghz_protocol_decoder_base_get_hash_data,uint8_t,SubGhzProtocolDecoderBase* +Function,-,subghz_protocol_decoder_base_get_string,_Bool,"SubGhzProtocolDecoderBase*, string_t" +Function,+,subghz_protocol_decoder_base_serialize,_Bool,"SubGhzProtocolDecoderBase*, FlipperFormat*, SubGhzPresetDefinition*" +Function,-,subghz_protocol_decoder_base_set_decoder_callback,void,"SubGhzProtocolDecoderBase*, SubGhzProtocolDecoderBaseRxCallback, void*" +Function,+,subghz_receiver_alloc_init,SubGhzReceiver*,SubGhzEnvironment* +Function,+,subghz_receiver_decode,void,"SubGhzReceiver*, _Bool, uint32_t" +Function,+,subghz_receiver_free,void,SubGhzReceiver* +Function,+,subghz_receiver_reset,void,SubGhzReceiver* +Function,+,subghz_receiver_search_decoder_base_by_name,SubGhzProtocolDecoderBase*,"SubGhzReceiver*, const char*" +Function,+,subghz_receiver_set_filter,void,"SubGhzReceiver*, SubGhzProtocolFlag" +Function,+,subghz_receiver_set_rx_callback,void,"SubGhzReceiver*, SubGhzReceiverCallback, void*" +Function,+,subghz_transmitter_alloc_init,SubGhzTransmitter*,"SubGhzEnvironment*, const char*" +Function,+,subghz_transmitter_deserialize,_Bool,"SubGhzTransmitter*, FlipperFormat*" +Function,+,subghz_transmitter_free,void,SubGhzTransmitter* +Function,+,subghz_transmitter_get_protocol_instance,SubGhzProtocolEncoderBase*,SubGhzTransmitter* +Function,+,subghz_transmitter_stop,_Bool,SubGhzTransmitter* +Function,+,subghz_transmitter_yield,LevelDuration,void* +Function,+,subghz_tx_rx_worker_alloc,SubGhzTxRxWorker*, +Function,+,subghz_tx_rx_worker_available,size_t,SubGhzTxRxWorker* +Function,+,subghz_tx_rx_worker_free,void,SubGhzTxRxWorker* +Function,+,subghz_tx_rx_worker_is_running,_Bool,SubGhzTxRxWorker* +Function,+,subghz_tx_rx_worker_read,size_t,"SubGhzTxRxWorker*, uint8_t*, size_t" +Function,+,subghz_tx_rx_worker_set_callback_have_read,void,"SubGhzTxRxWorker*, SubGhzTxRxWorkerCallbackHaveRead, void*" +Function,+,subghz_tx_rx_worker_start,_Bool,"SubGhzTxRxWorker*, uint32_t" +Function,+,subghz_tx_rx_worker_stop,void,SubGhzTxRxWorker* +Function,+,subghz_tx_rx_worker_write,_Bool,"SubGhzTxRxWorker*, uint8_t*, size_t" +Function,+,subghz_worker_alloc,SubGhzWorker*, +Function,+,subghz_worker_free,void,SubGhzWorker* +Function,+,subghz_worker_is_running,_Bool,SubGhzWorker* +Function,+,subghz_worker_rx_callback,void,"_Bool, uint32_t, void*" +Function,+,subghz_worker_set_context,void,"SubGhzWorker*, void*" +Function,+,subghz_worker_set_overrun_callback,void,"SubGhzWorker*, SubGhzWorkerOverrunCallback" +Function,+,subghz_worker_set_pair_callback,void,"SubGhzWorker*, SubGhzWorkerPairCallback" +Function,+,subghz_worker_start,void,SubGhzWorker* +Function,+,subghz_worker_stop,void,SubGhzWorker* +Function,+,submenu_add_item,void,"Submenu*, const char*, uint32_t, SubmenuItemCallback, void*" +Function,+,submenu_alloc,Submenu*, +Function,+,submenu_free,void,Submenu* +Function,+,submenu_get_view,View*,Submenu* +Function,+,submenu_reset,void,Submenu* +Function,+,submenu_set_header,void,"Submenu*, const char*" +Function,+,submenu_set_selected_item,void,"Submenu*, uint32_t" +Function,-,system,int,const char* +Function,-,tan,double,double +Function,-,tanf,float,float +Function,-,tanh,double,double +Function,-,tanhf,float,float +Function,-,tanhl,long double,long double +Function,-,tanl,long double,long double +Function,+,tar_archive_add_dir,_Bool,"TarArchive*, const char*, const char*" +Function,+,tar_archive_add_file,_Bool,"TarArchive*, const char*, const char*, const int32_t" +Function,+,tar_archive_alloc,TarArchive*,Storage* +Function,+,tar_archive_dir_add_element,_Bool,"TarArchive*, const char*" +Function,+,tar_archive_file_add_data_block,_Bool,"TarArchive*, const uint8_t*, const int32_t" +Function,+,tar_archive_file_add_header,_Bool,"TarArchive*, const char*, const int32_t" +Function,+,tar_archive_file_finalize,_Bool,TarArchive* +Function,+,tar_archive_finalize,_Bool,TarArchive* +Function,+,tar_archive_free,void,TarArchive* +Function,+,tar_archive_get_entries_count,int32_t,TarArchive* +Function,+,tar_archive_open,_Bool,"TarArchive*, const char*, TarOpenMode" +Function,+,tar_archive_set_file_callback,void,"TarArchive*, tar_unpack_file_cb, void*" +Function,+,tar_archive_store_data,_Bool,"TarArchive*, const char*, const uint8_t*, const int32_t" +Function,+,tar_archive_unpack_to,_Bool,"TarArchive*, const char*, Storage_name_converter" +Function,-,tempnam,char*,"const char*, const char*" +Function,+,text_box_alloc,TextBox*, +Function,+,text_box_free,void,TextBox* +Function,+,text_box_get_view,View*,TextBox* +Function,+,text_box_reset,void,TextBox* +Function,+,text_box_set_focus,void,"TextBox*, TextBoxFocus" +Function,+,text_box_set_font,void,"TextBox*, TextBoxFont" +Function,+,text_box_set_text,void,"TextBox*, const char*" +Function,+,text_input_alloc,TextInput*, +Function,+,text_input_free,void,TextInput* +Function,+,text_input_get_validator_callback,TextInputValidatorCallback,TextInput* +Function,+,text_input_get_validator_callback_context,void*,TextInput* +Function,+,text_input_get_view,View*,TextInput* +Function,+,text_input_reset,void,TextInput* +Function,+,text_input_set_header_text,void,"TextInput*, const char*" +Function,+,text_input_set_result_callback,void,"TextInput*, TextInputCallback, void*, char*, size_t, _Bool" +Function,+,text_input_set_validator,void,"TextInput*, TextInputValidatorCallback, void*" +Function,-,tgamma,double,double +Function,-,tgammaf,float,float +Function,-,tgammal,long double,long double +Function,+,timerCalculateTimer,uint32_t,uint16_t +Function,-,timerDelay,void,uint16_t +Function,+,timerIsExpired,_Bool,uint32_t +Function,-,timerStopwatchMeasure,uint32_t, +Function,-,timerStopwatchStart,void, +Function,-,timingsafe_bcmp,int,"const void*, const void*, size_t" +Function,-,timingsafe_memcmp,int,"const void*, const void*, size_t" +Function,-,tmpfile,FILE*, +Function,-,tmpnam,char*,char* +Function,-,toascii,int,int +Function,-,toascii_l,int,"int, locale_t" +Function,-,tolower,int,int +Function,-,tolower_l,int,"int, locale_t" +Function,-,toupper,int,int +Function,-,toupper_l,int,"int, locale_t" +Function,-,trunc,double,double +Function,-,truncf,float,float +Function,-,truncl,long double,long double +Function,-,uECC_compress,void,"const uint8_t*, uint8_t*, uECC_Curve" +Function,+,uECC_compute_public_key,int,"const uint8_t*, uint8_t*, uECC_Curve" +Function,-,uECC_curve_private_key_size,int,uECC_Curve +Function,-,uECC_curve_public_key_size,int,uECC_Curve +Function,-,uECC_decompress,void,"const uint8_t*, uint8_t*, uECC_Curve" +Function,-,uECC_get_rng,uECC_RNG_Function, +Function,-,uECC_make_key,int,"uint8_t*, uint8_t*, uECC_Curve" +Function,-,uECC_secp160r1,uECC_Curve, +Function,-,uECC_secp192r1,uECC_Curve, +Function,-,uECC_secp224r1,uECC_Curve, +Function,-,uECC_secp256k1,uECC_Curve, +Function,+,uECC_secp256r1,uECC_Curve, +Function,+,uECC_set_rng,void,uECC_RNG_Function +Function,-,uECC_shared_secret,int,"const uint8_t*, const uint8_t*, uint8_t*, uECC_Curve" +Function,+,uECC_sign,int,"const uint8_t*, const uint8_t*, unsigned, uint8_t*, uECC_Curve" +Function,-,uECC_sign_deterministic,int,"const uint8_t*, const uint8_t*, unsigned, const uECC_HashContext*, uint8_t*, uECC_Curve" +Function,-,uECC_valid_public_key,int,"const uint8_t*, uECC_Curve" +Function,-,uECC_verify,int,"const uint8_t*, const uint8_t*, unsigned, const uint8_t*, uECC_Curve" +Function,-,ulTaskGenericNotifyTake,uint32_t,"UBaseType_t, BaseType_t, TickType_t" +Function,-,ulTaskGenericNotifyValueClear,uint32_t,"TaskHandle_t, UBaseType_t, uint32_t" +Function,-,ulTaskGetIdleRunTimeCounter,uint32_t, +Function,-,ulTaskGetIdleRunTimePercent,uint32_t, +Function,-,ungetc,int,"int, FILE*" +Function,-,unsetenv,int,const char* +Function,-,usbd_poll,void,usbd_device* +Function,-,utoa,char*,"unsigned, char*, int" +Function,-,uxListRemove,UBaseType_t,ListItem_t* +Function,-,uxTaskGetNumberOfTasks,UBaseType_t, +Function,-,uxTaskGetStackHighWaterMark,UBaseType_t,TaskHandle_t +Function,-,uxTaskGetStackHighWaterMark2,uint16_t,TaskHandle_t +Function,-,uxTaskGetSystemState,UBaseType_t,"TaskStatus_t*, const UBaseType_t, uint32_t*" +Function,-,uxTaskGetTaskNumber,UBaseType_t,TaskHandle_t +Function,-,uxTaskPriorityGet,UBaseType_t,const TaskHandle_t +Function,-,uxTaskPriorityGetFromISR,UBaseType_t,const TaskHandle_t +Function,-,uxTaskResetEventItemValue,TickType_t, +Function,-,uxTimerGetReloadMode,UBaseType_t,TimerHandle_t +Function,-,uxTimerGetTimerNumber,UBaseType_t,TimerHandle_t +Function,-,vApplicationGetIdleTaskMemory,void,"StaticTask_t**, StackType_t**, uint32_t*" +Function,-,vApplicationGetTimerTaskMemory,void,"StaticTask_t**, StackType_t**, uint32_t*" +Function,-,vListInitialise,void,List_t* +Function,-,vListInitialiseItem,void,ListItem_t* +Function,-,vListInsert,void,"List_t*, ListItem_t*" +Function,-,vListInsertEnd,void,"List_t*, ListItem_t*" +Function,-,vPortDefineHeapRegions,void,const HeapRegion_t* +Function,-,vPortEndScheduler,void, +Function,+,vPortEnterCritical,void, +Function,+,vPortExitCritical,void, +Function,-,vPortFree,void,void* +Function,-,vPortGetHeapStats,void,HeapStats_t* +Function,-,vPortInitialiseBlocks,void, +Function,-,vPortSuppressTicksAndSleep,void,TickType_t +Function,-,vTaskAllocateMPURegions,void,"TaskHandle_t, const MemoryRegion_t*" +Function,-,vTaskDelay,void,const TickType_t +Function,-,vTaskDelete,void,TaskHandle_t +Function,-,vTaskEndScheduler,void, +Function,-,vTaskGenericNotifyGiveFromISR,void,"TaskHandle_t, UBaseType_t, BaseType_t*" +Function,-,vTaskGetInfo,void,"TaskHandle_t, TaskStatus_t*, BaseType_t, eTaskState" +Function,-,vTaskGetRunTimeStats,void,char* +Function,-,vTaskInternalSetTimeOutState,void,TimeOut_t* +Function,-,vTaskList,void,char* +Function,-,vTaskMissedYield,void, +Function,-,vTaskPlaceOnEventList,void,"List_t*, const TickType_t" +Function,-,vTaskPlaceOnEventListRestricted,void,"List_t*, TickType_t, const BaseType_t" +Function,-,vTaskPlaceOnUnorderedEventList,void,"List_t*, const TickType_t, const TickType_t" +Function,-,vTaskPriorityDisinheritAfterTimeout,void,"const TaskHandle_t, UBaseType_t" +Function,+,vTaskPrioritySet,void,"TaskHandle_t, UBaseType_t" +Function,-,vTaskRemoveFromUnorderedEventList,void,"ListItem_t*, const TickType_t" +Function,-,vTaskResume,void,TaskHandle_t +Function,-,vTaskSetTaskNumber,void,"TaskHandle_t, const UBaseType_t" +Function,-,vTaskSetThreadLocalStoragePointer,void,"TaskHandle_t, BaseType_t, void*" +Function,-,vTaskSetTimeOutState,void,TimeOut_t* +Function,-,vTaskStartScheduler,void, +Function,-,vTaskStepTick,void,const TickType_t +Function,-,vTaskSuspend,void,TaskHandle_t +Function,-,vTaskSuspendAll,void, +Function,-,vTaskSwitchContext,void, +Function,-,vTimerSetReloadMode,void,"TimerHandle_t, const UBaseType_t" +Function,-,vTimerSetTimerID,void,"TimerHandle_t, void*" +Function,-,vTimerSetTimerNumber,void,"TimerHandle_t, UBaseType_t" +Function,+,validator_is_file_alloc_init,ValidatorIsFile*,"const char*, const char*, const char*" +Function,+,validator_is_file_callback,_Bool,"const char*, string_t, void*" +Function,+,validator_is_file_free,void,ValidatorIsFile* +Function,+,variable_item_get_context,void*,VariableItem* +Function,+,variable_item_get_current_value_index,uint8_t,VariableItem* +Function,+,variable_item_list_add,VariableItem*,"VariableItemList*, const char*, uint8_t, VariableItemChangeCallback, void*" +Function,+,variable_item_list_alloc,VariableItemList*, +Function,+,variable_item_list_free,void,VariableItemList* +Function,+,variable_item_list_get_selected_item_index,uint8_t,VariableItemList* +Function,+,variable_item_list_get_view,View*,VariableItemList* +Function,+,variable_item_list_reset,void,VariableItemList* +Function,+,variable_item_list_set_enter_callback,void,"VariableItemList*, VariableItemListEnterCallback, void*" +Function,+,variable_item_list_set_selected_item,void,"VariableItemList*, uint8_t" +Function,+,variable_item_set_current_value_index,void,"VariableItem*, uint8_t" +Function,+,variable_item_set_current_value_text,void,"VariableItem*, const char*" +Function,-,vasiprintf,int,"char**, const char*, __gnuc_va_list" +Function,-,vasniprintf,char*,"char*, size_t*, const char*, __gnuc_va_list" +Function,-,vasnprintf,char*,"char*, size_t*, const char*, __gnuc_va_list" +Function,-,vasprintf,int,"char**, const char*, __gnuc_va_list" +Function,-,vdiprintf,int,"int, const char*, __gnuc_va_list" +Function,-,vdprintf,int,"int, const char*, __gnuc_va_list" +Function,+,version_get,const Version*, +Function,+,version_get_builddate,const char*,const Version* +Function,+,version_get_dirty_flag,_Bool,const Version* +Function,+,version_get_gitbranch,const char*,const Version* +Function,+,version_get_gitbranchnum,const char*,const Version* +Function,+,version_get_githash,const char*,const Version* +Function,+,version_get_target,uint8_t,const Version* +Function,+,version_get_version,const char*,const Version* +Function,-,vfiprintf,int,"FILE*, const char*, __gnuc_va_list" +Function,-,vfiscanf,int,"FILE*, const char*, __gnuc_va_list" +Function,-,vfprintf,int,"FILE*, const char*, __gnuc_va_list" +Function,-,vfscanf,int,"FILE*, const char*, __gnuc_va_list" +Function,+,view_alloc,View*, +Function,+,view_allocate_model,void,"View*, ViewModelType, size_t" +Function,+,view_commit_model,void,"View*, _Bool" +Function,+,view_dispatcher_add_view,void,"ViewDispatcher*, uint32_t, View*" +Function,+,view_dispatcher_alloc,ViewDispatcher*, +Function,+,view_dispatcher_attach_to_gui,void,"ViewDispatcher*, Gui*, ViewDispatcherType" +Function,+,view_dispatcher_enable_queue,void,ViewDispatcher* +Function,+,view_dispatcher_free,void,ViewDispatcher* +Function,+,view_dispatcher_remove_view,void,"ViewDispatcher*, uint32_t" +Function,+,view_dispatcher_run,void,ViewDispatcher* +Function,+,view_dispatcher_send_custom_event,void,"ViewDispatcher*, uint32_t" +Function,+,view_dispatcher_send_to_back,void,ViewDispatcher* +Function,+,view_dispatcher_send_to_front,void,ViewDispatcher* +Function,+,view_dispatcher_set_custom_event_callback,void,"ViewDispatcher*, ViewDispatcherCustomEventCallback" +Function,+,view_dispatcher_set_event_callback_context,void,"ViewDispatcher*, void*" +Function,+,view_dispatcher_set_navigation_event_callback,void,"ViewDispatcher*, ViewDispatcherNavigationEventCallback" +Function,+,view_dispatcher_set_tick_event_callback,void,"ViewDispatcher*, ViewDispatcherTickEventCallback, uint32_t" +Function,+,view_dispatcher_stop,void,ViewDispatcher* +Function,+,view_dispatcher_switch_to_view,void,"ViewDispatcher*, uint32_t" +Function,+,view_free,void,View* +Function,+,view_free_model,void,View* +Function,+,view_get_model,void*,View* +Function,+,view_port_alloc,ViewPort*, +Function,+,view_port_draw_callback_set,void,"ViewPort*, ViewPortDrawCallback, void*" +Function,+,view_port_enabled_set,void,"ViewPort*, _Bool" +Function,+,view_port_free,void,ViewPort* +Function,-,view_port_get_height,uint8_t,ViewPort* +Function,+,view_port_get_orientation,ViewPortOrientation,const ViewPort* +Function,+,view_port_get_width,uint8_t,ViewPort* +Function,+,view_port_input_callback_set,void,"ViewPort*, ViewPortInputCallback, void*" +Function,+,view_port_is_enabled,_Bool,ViewPort* +Function,-,view_port_set_height,void,"ViewPort*, uint8_t" +Function,+,view_port_set_orientation,void,"ViewPort*, ViewPortOrientation" +Function,+,view_port_set_width,void,"ViewPort*, uint8_t" +Function,+,view_port_update,void,ViewPort* +Function,+,view_set_context,void,"View*, void*" +Function,-,view_set_custom_callback,void,"View*, ViewCustomCallback" +Function,+,view_set_draw_callback,void,"View*, ViewDrawCallback" +Function,+,view_set_enter_callback,void,"View*, ViewCallback" +Function,+,view_set_exit_callback,void,"View*, ViewCallback" +Function,+,view_set_input_callback,void,"View*, ViewInputCallback" +Function,+,view_set_orientation,void,"View*, ViewOrientation" +Function,+,view_set_previous_callback,void,"View*, ViewNavigationCallback" +Function,+,view_set_update_callback,void,"View*, ViewUpdateCallback" +Function,+,view_set_update_callback_context,void,"View*, void*" +Function,+,view_stack_add_view,void,"ViewStack*, View*" +Function,+,view_stack_alloc,ViewStack*, +Function,+,view_stack_free,void,ViewStack* +Function,+,view_stack_get_view,View*,ViewStack* +Function,+,view_stack_remove_view,void,"ViewStack*, View*" +Function,+,view_tie_icon_animation,void,"View*, IconAnimation*" +Function,-,viprintf,int,"const char*, __gnuc_va_list" +Function,-,viscanf,int,"const char*, __gnuc_va_list" +Function,-,vprintf,int,"const char*, __gnuc_va_list" +Function,-,vscanf,int,"const char*, __gnuc_va_list" +Function,-,vsiprintf,int,"char*, const char*, __gnuc_va_list" +Function,-,vsiscanf,int,"const char*, const char*, __gnuc_va_list" +Function,-,vsniprintf,int,"char*, size_t, const char*, __gnuc_va_list" +Function,-,vsnprintf,int,"char*, size_t, const char*, __gnuc_va_list" +Function,-,vsprintf,int,"char*, const char*, __gnuc_va_list" +Function,-,vsscanf,int,"const char*, const char*, __gnuc_va_list" +Function,-,wcstombs,size_t,"char*, const wchar_t*, size_t" +Function,-,wctomb,int,"char*, wchar_t" +Function,+,widget_add_button_element,void,"Widget*, GuiButtonType, const char*, ButtonCallback, void*" +Function,+,widget_add_frame_element,void,"Widget*, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t" +Function,+,widget_add_icon_element,void,"Widget*, uint8_t, uint8_t, const Icon*" +Function,+,widget_add_string_element,void,"Widget*, uint8_t, uint8_t, Align, Align, Font, const char*" +Function,+,widget_add_string_multiline_element,void,"Widget*, uint8_t, uint8_t, Align, Align, Font, const char*" +Function,+,widget_add_text_box_element,void,"Widget*, uint8_t, uint8_t, uint8_t, uint8_t, Align, Align, const char*, _Bool" +Function,+,widget_add_text_scroll_element,void,"Widget*, uint8_t, uint8_t, uint8_t, uint8_t, const char*" +Function,+,widget_alloc,Widget*, +Function,+,widget_free,void,Widget* +Function,+,widget_get_view,View*,Widget* +Function,+,widget_reset,void,Widget* +Function,-,write_mutex,_Bool,"ValueMutex*, void*, size_t, uint32_t" +Function,-,xPortGetFreeHeapSize,size_t, +Function,-,xPortGetMinimumEverFreeHeapSize,size_t, +Function,-,xPortStartScheduler,BaseType_t, +Function,-,xTaskAbortDelay,BaseType_t,TaskHandle_t +Function,-,xTaskCallApplicationTaskHook,BaseType_t,"TaskHandle_t, void*" +Function,-,xTaskCatchUpTicks,BaseType_t,TickType_t +Function,-,xTaskCheckForTimeOut,BaseType_t,"TimeOut_t*, TickType_t*" +Function,-,xTaskCreate,BaseType_t,"TaskFunction_t, const char*, const uint16_t, void*, UBaseType_t, TaskHandle_t*" +Function,-,xTaskCreateStatic,TaskHandle_t,"TaskFunction_t, const char*, const uint32_t, void*, UBaseType_t, StackType_t*, StaticTask_t*" +Function,-,xTaskDelayUntil,BaseType_t,"TickType_t*, const TickType_t" +Function,-,xTaskGenericNotify,BaseType_t,"TaskHandle_t, UBaseType_t, uint32_t, eNotifyAction, uint32_t*" +Function,-,xTaskGenericNotifyFromISR,BaseType_t,"TaskHandle_t, UBaseType_t, uint32_t, eNotifyAction, uint32_t*, BaseType_t*" +Function,-,xTaskGenericNotifyStateClear,BaseType_t,"TaskHandle_t, UBaseType_t" +Function,-,xTaskGenericNotifyWait,BaseType_t,"UBaseType_t, uint32_t, uint32_t, uint32_t*, TickType_t" +Function,-,xTaskGetCurrentTaskHandle,TaskHandle_t, +Function,+,xTaskGetHandle,TaskHandle_t,const char* +Function,-,xTaskGetIdleTaskHandle,TaskHandle_t, +Function,+,xTaskGetSchedulerState,BaseType_t, +Function,+,xTaskGetTickCount,TickType_t, +Function,-,xTaskGetTickCountFromISR,TickType_t, +Function,-,xTaskIncrementTick,BaseType_t, +Function,-,xTaskPriorityDisinherit,BaseType_t,const TaskHandle_t +Function,-,xTaskPriorityInherit,BaseType_t,const TaskHandle_t +Function,-,xTaskRemoveFromEventList,BaseType_t,const List_t* +Function,-,xTaskResumeAll,BaseType_t, +Function,-,xTaskResumeFromISR,BaseType_t,TaskHandle_t +Function,-,xTimerCreate,TimerHandle_t,"const char*, const TickType_t, const UBaseType_t, void*, TimerCallbackFunction_t" +Function,-,xTimerCreateStatic,TimerHandle_t,"const char*, const TickType_t, const UBaseType_t, void*, TimerCallbackFunction_t, StaticTimer_t*" +Function,-,xTimerCreateTimerTask,BaseType_t, +Function,-,xTimerGenericCommand,BaseType_t,"TimerHandle_t, const BaseType_t, const TickType_t, BaseType_t*, const TickType_t" +Function,-,xTimerGetExpiryTime,TickType_t,TimerHandle_t +Function,-,xTimerGetPeriod,TickType_t,TimerHandle_t +Function,-,xTimerGetTimerDaemonTaskHandle,TaskHandle_t, +Function,-,xTimerIsTimerActive,BaseType_t,TimerHandle_t +Function,-,xTimerPendFunctionCall,BaseType_t,"PendedFunction_t, void*, uint32_t, TickType_t" +Function,-,xTimerPendFunctionCallFromISR,BaseType_t,"PendedFunction_t, void*, uint32_t, BaseType_t*" +Function,-,y0,double,double +Function,-,y0f,float,float +Function,-,y1,double,double +Function,-,y1f,float,float +Function,-,yn,double,"int, double" +Function,-,ynf,float,"int, float" +Variable,-,AHBPrescTable,const uint32_t[16], +Variable,-,APBPrescTable,const uint32_t[8], +Variable,+,A_125khz_14,const Icon, +Variable,+,A_BadUsb_14,const Icon, +Variable,+,A_Debug_14,const Icon, +Variable,+,A_FileManager_14,const Icon, +Variable,+,A_GPIO_14,const Icon, +Variable,+,A_Infrared_14,const Icon, +Variable,+,A_Levelup1_128x64,const Icon, +Variable,+,A_Levelup2_128x64,const Icon, +Variable,+,A_Loading_24,const Icon, +Variable,+,A_NFC_14,const Icon, +Variable,+,A_Plugins_14,const Icon, +Variable,+,A_Round_loader_8x8,const Icon, +Variable,+,A_Settings_14,const Icon, +Variable,+,A_Sub1ghz_14,const Icon, +Variable,+,A_U2F_14,const Icon, +Variable,+,A_iButton_14,const Icon, +Variable,-,ITM_RxBuffer,volatile int32_t, +Variable,+,I_125_10px,const Icon, +Variable,+,I_ActiveConnection_50x64,const Icon, +Variable,+,I_ArrowC_1_36x36,const Icon, +Variable,+,I_ArrowDownEmpty_14x15,const Icon, +Variable,+,I_ArrowDownFilled_14x15,const Icon, +Variable,+,I_ArrowUpEmpty_14x15,const Icon, +Variable,+,I_ArrowUpFilled_14x15,const Icon, +Variable,+,I_Attention_5x8,const Icon, +Variable,+,I_Auth_62x31,const Icon, +Variable,+,I_BLE_Pairing_128x64,const Icon, +Variable,+,I_Background_128x11,const Icon, +Variable,+,I_BatteryBody_52x28,const Icon, +Variable,+,I_Battery_16x16,const Icon, +Variable,+,I_Battery_26x8,const Icon, +Variable,+,I_Ble_connected_15x15,const Icon, +Variable,+,I_Ble_disconnected_15x15,const Icon, +Variable,+,I_Bluetooth_Connected_16x8,const Icon, +Variable,+,I_Bluetooth_Idle_5x8,const Icon, +Variable,+,I_ButtonCenter_7x7,const Icon, +Variable,+,I_ButtonDown_7x4,const Icon, +Variable,+,I_ButtonLeftSmall_3x5,const Icon, +Variable,+,I_ButtonLeft_4x7,const Icon, +Variable,+,I_ButtonRightSmall_3x5,const Icon, +Variable,+,I_ButtonRight_4x7,const Icon, +Variable,+,I_ButtonUp_7x4,const Icon, +Variable,+,I_Button_18x18,const Icon, +Variable,+,I_Certification1_103x56,const Icon, +Variable,+,I_Certification2_98x33,const Icon, +Variable,+,I_Charging_lightning_9x10,const Icon, +Variable,+,I_Charging_lightning_mask_9x10,const Icon, +Variable,+,I_Circles_47x47,const Icon, +Variable,+,I_Clock_18x18,const Icon, +Variable,+,I_Connect_me_62x31,const Icon, +Variable,+,I_Connected_62x31,const Icon, +Variable,+,I_Cry_dolph_55x52,const Icon, +Variable,+,I_DFU_128x50,const Icon, +Variable,+,I_Detailed_chip_17x13,const Icon, +Variable,+,I_DolphinCommon_56x48,const Icon, +Variable,+,I_DolphinMafia_115x62,const Icon, +Variable,+,I_DolphinNice_96x59,const Icon, +Variable,+,I_DolphinReadingSuccess_59x63,const Icon, +Variable,+,I_DolphinWait_61x59,const Icon, +Variable,+,I_DoorLeft_70x55,const Icon, +Variable,+,I_DoorRight_70x55,const Icon, +Variable,+,I_Down_25x27,const Icon, +Variable,+,I_Down_hvr_25x27,const Icon, +Variable,+,I_Drive_112x35,const Icon, +Variable,+,I_Error_18x18,const Icon, +Variable,+,I_Error_62x31,const Icon, +Variable,+,I_EviSmile1_18x21,const Icon, +Variable,+,I_EviSmile2_18x21,const Icon, +Variable,+,I_EviWaiting1_18x21,const Icon, +Variable,+,I_EviWaiting2_18x21,const Icon, +Variable,+,I_FaceCharging_29x14,const Icon, +Variable,+,I_FaceConfused_29x14,const Icon, +Variable,+,I_FaceNopower_29x14,const Icon, +Variable,+,I_FaceNormal_29x14,const Icon, +Variable,+,I_Health_16x16,const Icon, +Variable,+,I_InfraredArrowDown_4x8,const Icon, +Variable,+,I_InfraredArrowUp_4x8,const Icon, +Variable,+,I_InfraredLearnShort_128x31,const Icon, +Variable,+,I_KeyBackspaceSelected_16x9,const Icon, +Variable,+,I_KeyBackspace_16x9,const Icon, +Variable,+,I_KeySaveSelected_24x11,const Icon, +Variable,+,I_KeySave_24x11,const Icon, +Variable,+,I_Keychain,const Icon, +Variable,+,I_Left_mouse_icon_9x9,const Icon, +Variable,+,I_Lock_7x8,const Icon, +Variable,+,I_Lock_8x8,const Icon, +Variable,+,I_MHz_25x11,const Icon, +Variable,+,I_Medium_chip_22x21,const Icon, +Variable,+,I_Mute_25x27,const Icon, +Variable,+,I_Mute_hvr_25x27,const Icon, +Variable,+,I_NFC_manual,const Icon, +Variable,+,I_Nfc_10px,const Icon, +Variable,+,I_Ok_btn_9x9,const Icon, +Variable,+,I_Ok_btn_pressed_13x13,const Icon, +Variable,+,I_Percent_10x14,const Icon, +Variable,+,I_Pin_arrow_down_7x9,const Icon, +Variable,+,I_Pin_arrow_left_9x7,const Icon, +Variable,+,I_Pin_arrow_right_9x7,const Icon, +Variable,+,I_Pin_arrow_up7x9,const Icon, +Variable,+,I_Pin_attention_dpad_29x29,const Icon, +Variable,+,I_Pin_back_arrow_10x8,const Icon, +Variable,+,I_Pin_back_full_40x8,const Icon, +Variable,+,I_Pin_pointer_5x3,const Icon, +Variable,+,I_Pin_star_7x7,const Icon, +Variable,+,I_Power_25x27,const Icon, +Variable,+,I_Power_hvr_25x27,const Icon, +Variable,+,I_Pressed_Button_13x13,const Icon, +Variable,+,I_Quest_7x8,const Icon, +Variable,+,I_RFIDBigChip_37x36,const Icon, +Variable,+,I_RFIDDolphinReceive_97x61,const Icon, +Variable,+,I_RFIDDolphinSend_97x61,const Icon, +Variable,+,I_RFIDDolphinSuccess_108x57,const Icon, +Variable,+,I_Restoring,const Icon, +Variable,+,I_Right_mouse_icon_9x9,const Icon, +Variable,+,I_SDQuestion_35x43,const Icon, +Variable,+,I_SDcardFail_11x8,const Icon, +Variable,+,I_SDcardMounted_11x8,const Icon, +Variable,+,I_Scanning_123x52,const Icon, +Variable,+,I_Smile_18x18,const Icon, +Variable,+,I_Space_65x18,const Icon, +Variable,+,I_Tap_reader_36x38,const Icon, +Variable,+,I_Temperature_16x16,const Icon, +Variable,+,I_Unlock_7x8,const Icon, +Variable,+,I_Unplug_bg_bottom_128x10,const Icon, +Variable,+,I_Unplug_bg_top_128x14,const Icon, +Variable,+,I_Up_25x27,const Icon, +Variable,+,I_Up_hvr_25x27,const Icon, +Variable,+,I_Updating_32x40,const Icon, +Variable,+,I_UsbTree_48x22,const Icon, +Variable,+,I_Vol_down_25x27,const Icon, +Variable,+,I_Vol_down_hvr_25x27,const Icon, +Variable,+,I_Vol_up_25x27,const Icon, +Variable,+,I_Vol_up_hvr_25x27,const Icon, +Variable,+,I_Voldwn_6x6,const Icon, +Variable,+,I_Voltage_16x16,const Icon, +Variable,+,I_Volup_8x6,const Icon, +Variable,+,I_WarningDolphin_45x42,const Icon, +Variable,+,I_Warning_30x23,const Icon, +Variable,+,I_back_10px,const Icon, +Variable,+,I_badusb_10px,const Icon, +Variable,+,I_dir_10px,const Icon, +Variable,+,I_iButtonDolphinVerySuccess_108x52,const Icon, +Variable,+,I_iButtonKey_49x44,const Icon, +Variable,+,I_ibutt_10px,const Icon, +Variable,+,I_ir_10px,const Icon, +Variable,+,I_loading_10px,const Icon, +Variable,+,I_music_10px,const Icon, +Variable,+,I_passport_bad1_46x49,const Icon, +Variable,+,I_passport_bad2_46x49,const Icon, +Variable,+,I_passport_bad3_46x49,const Icon, +Variable,+,I_passport_bottom_128x18,const Icon, +Variable,+,I_passport_happy1_46x49,const Icon, +Variable,+,I_passport_happy2_46x49,const Icon, +Variable,+,I_passport_happy3_46x49,const Icon, +Variable,+,I_passport_left_6x46,const Icon, +Variable,+,I_passport_okay1_46x49,const Icon, +Variable,+,I_passport_okay2_46x49,const Icon, +Variable,+,I_passport_okay3_46x49,const Icon, +Variable,+,I_sub1_10px,const Icon, +Variable,+,I_u2f_10px,const Icon, +Variable,+,I_unknown_10px,const Icon, +Variable,+,I_update_10px,const Icon, +Variable,-,MSIRangeTable,const uint32_t[16], +Variable,-,SmpsPrescalerTable,const uint32_t[4][6], +Variable,+,SystemCoreClock,uint32_t, +Variable,+,_ctype_,const char[], +Variable,+,_global_impure_ptr,_reent*, +Variable,+,_impure_ptr,_reent*, +Variable,-,_sys_errlist,const char*[], +Variable,-,_sys_nerr,int, +Variable,+,cli_vcp,CliSession, +Variable,+,furi_hal_i2c_bus_external,FuriHalI2cBus, +Variable,+,furi_hal_i2c_bus_power,FuriHalI2cBus, +Variable,+,furi_hal_i2c_handle_external,FuriHalI2cBusHandle, +Variable,+,furi_hal_i2c_handle_power,FuriHalI2cBusHandle, +Variable,+,furi_hal_sd_spi_handle,FuriHalSpiBusHandle*, +Variable,+,furi_hal_spi_bus_d,FuriHalSpiBus, +Variable,+,furi_hal_spi_bus_handle_display,FuriHalSpiBusHandle, +Variable,+,furi_hal_spi_bus_handle_external,FuriHalSpiBusHandle, +Variable,+,furi_hal_spi_bus_handle_nfc,FuriHalSpiBusHandle, +Variable,+,furi_hal_spi_bus_handle_sd_fast,FuriHalSpiBusHandle, +Variable,+,furi_hal_spi_bus_handle_sd_slow,FuriHalSpiBusHandle, +Variable,+,furi_hal_spi_bus_handle_subghz,FuriHalSpiBusHandle, +Variable,+,furi_hal_spi_bus_r,FuriHalSpiBus, +Variable,+,furi_hal_spi_preset_1edge_low_16m,const LL_SPI_InitTypeDef, +Variable,+,furi_hal_spi_preset_1edge_low_2m,const LL_SPI_InitTypeDef, +Variable,+,furi_hal_spi_preset_1edge_low_4m,const LL_SPI_InitTypeDef, +Variable,+,furi_hal_spi_preset_1edge_low_8m,const LL_SPI_InitTypeDef, +Variable,+,furi_hal_spi_preset_2edge_low_8m,const LL_SPI_InitTypeDef, +Variable,+,gpio_button_back,const GpioPin, +Variable,+,gpio_button_down,const GpioPin, +Variable,+,gpio_button_left,const GpioPin, +Variable,+,gpio_button_ok,const GpioPin, +Variable,+,gpio_button_right,const GpioPin, +Variable,+,gpio_button_up,const GpioPin, +Variable,+,gpio_cc1101_g0,const GpioPin, +Variable,+,gpio_display_cs,const GpioPin, +Variable,+,gpio_display_di,const GpioPin, +Variable,+,gpio_display_rst_n,const GpioPin, +Variable,+,gpio_ext_pa4,const GpioPin, +Variable,+,gpio_ext_pa6,const GpioPin, +Variable,+,gpio_ext_pa7,const GpioPin, +Variable,+,gpio_ext_pb2,const GpioPin, +Variable,+,gpio_ext_pb3,const GpioPin, +Variable,+,gpio_ext_pc0,const GpioPin, +Variable,+,gpio_ext_pc1,const GpioPin, +Variable,+,gpio_ext_pc3,const GpioPin, +Variable,+,gpio_i2c_power_scl,const GpioPin, +Variable,+,gpio_i2c_power_sda,const GpioPin, +Variable,+,gpio_infrared_rx,const GpioPin, +Variable,+,gpio_infrared_tx,const GpioPin, +Variable,+,gpio_nfc_cs,const GpioPin, +Variable,+,gpio_nfc_irq_rfid_pull,const GpioPin, +Variable,+,gpio_rf_sw_0,const GpioPin, +Variable,+,gpio_rfid_carrier,const GpioPin, +Variable,+,gpio_rfid_carrier_out,const GpioPin, +Variable,+,gpio_rfid_data_in,const GpioPin, +Variable,+,gpio_sdcard_cd,const GpioPin, +Variable,+,gpio_sdcard_cs,const GpioPin, +Variable,+,gpio_speaker,const GpioPin, +Variable,+,gpio_spi_d_miso,const GpioPin, +Variable,+,gpio_spi_d_mosi,const GpioPin, +Variable,+,gpio_spi_d_sck,const GpioPin, +Variable,+,gpio_spi_r_miso,const GpioPin, +Variable,+,gpio_spi_r_mosi,const GpioPin, +Variable,+,gpio_spi_r_sck,const GpioPin, +Variable,+,gpio_subghz_cs,const GpioPin, +Variable,+,gpio_usart_rx,const GpioPin, +Variable,+,gpio_usart_tx,const GpioPin, +Variable,+,gpio_usb_dm,const GpioPin, +Variable,+,gpio_usb_dp,const GpioPin, +Variable,+,ibutton_gpio,const GpioPin, +Variable,+,input_pins,const InputPin[], +Variable,+,input_pins_count,const size_t, +Variable,+,message_blink_set_color_blue,const NotificationMessage, +Variable,+,message_blink_set_color_cyan,const NotificationMessage, +Variable,+,message_blink_set_color_green,const NotificationMessage, +Variable,+,message_blink_set_color_magenta,const NotificationMessage, +Variable,+,message_blink_set_color_red,const NotificationMessage, +Variable,+,message_blink_set_color_white,const NotificationMessage, +Variable,+,message_blink_set_color_yellow,const NotificationMessage, +Variable,+,message_blink_start_10,const NotificationMessage, +Variable,+,message_blink_start_100,const NotificationMessage, +Variable,+,message_blink_stop,const NotificationMessage, +Variable,+,message_blue_0,const NotificationMessage, +Variable,+,message_blue_255,const NotificationMessage, +Variable,+,message_click,const NotificationMessage, +Variable,+,message_delay_1,const NotificationMessage, +Variable,+,message_delay_10,const NotificationMessage, +Variable,+,message_delay_100,const NotificationMessage, +Variable,+,message_delay_1000,const NotificationMessage, +Variable,+,message_delay_25,const NotificationMessage, +Variable,+,message_delay_250,const NotificationMessage, +Variable,+,message_delay_50,const NotificationMessage, +Variable,+,message_delay_500,const NotificationMessage, +Variable,+,message_display_backlight_enforce_auto,const NotificationMessage, +Variable,+,message_display_backlight_enforce_on,const NotificationMessage, +Variable,+,message_display_backlight_off,const NotificationMessage, +Variable,+,message_display_backlight_on,const NotificationMessage, +Variable,+,message_do_not_reset,const NotificationMessage, +Variable,+,message_force_display_brightness_setting_1f,const NotificationMessage, +Variable,+,message_force_speaker_volume_setting_1f,const NotificationMessage, +Variable,+,message_force_vibro_setting_off,const NotificationMessage, +Variable,+,message_force_vibro_setting_on,const NotificationMessage, +Variable,+,message_green_0,const NotificationMessage, +Variable,+,message_green_255,const NotificationMessage, +Variable,+,message_note_a0,const NotificationMessage, +Variable,+,message_note_a1,const NotificationMessage, +Variable,+,message_note_a2,const NotificationMessage, +Variable,+,message_note_a3,const NotificationMessage, +Variable,+,message_note_a4,const NotificationMessage, +Variable,+,message_note_a5,const NotificationMessage, +Variable,+,message_note_a6,const NotificationMessage, +Variable,+,message_note_a7,const NotificationMessage, +Variable,+,message_note_a8,const NotificationMessage, +Variable,+,message_note_as0,const NotificationMessage, +Variable,+,message_note_as1,const NotificationMessage, +Variable,+,message_note_as2,const NotificationMessage, +Variable,+,message_note_as3,const NotificationMessage, +Variable,+,message_note_as4,const NotificationMessage, +Variable,+,message_note_as5,const NotificationMessage, +Variable,+,message_note_as6,const NotificationMessage, +Variable,+,message_note_as7,const NotificationMessage, +Variable,+,message_note_as8,const NotificationMessage, +Variable,+,message_note_b0,const NotificationMessage, +Variable,+,message_note_b1,const NotificationMessage, +Variable,+,message_note_b2,const NotificationMessage, +Variable,+,message_note_b3,const NotificationMessage, +Variable,+,message_note_b4,const NotificationMessage, +Variable,+,message_note_b5,const NotificationMessage, +Variable,+,message_note_b6,const NotificationMessage, +Variable,+,message_note_b7,const NotificationMessage, +Variable,+,message_note_b8,const NotificationMessage, +Variable,+,message_note_c0,const NotificationMessage, +Variable,+,message_note_c1,const NotificationMessage, +Variable,+,message_note_c2,const NotificationMessage, +Variable,+,message_note_c3,const NotificationMessage, +Variable,+,message_note_c4,const NotificationMessage, +Variable,+,message_note_c5,const NotificationMessage, +Variable,+,message_note_c6,const NotificationMessage, +Variable,+,message_note_c7,const NotificationMessage, +Variable,+,message_note_c8,const NotificationMessage, +Variable,+,message_note_cs0,const NotificationMessage, +Variable,+,message_note_cs1,const NotificationMessage, +Variable,+,message_note_cs2,const NotificationMessage, +Variable,+,message_note_cs3,const NotificationMessage, +Variable,+,message_note_cs4,const NotificationMessage, +Variable,+,message_note_cs5,const NotificationMessage, +Variable,+,message_note_cs6,const NotificationMessage, +Variable,+,message_note_cs7,const NotificationMessage, +Variable,+,message_note_cs8,const NotificationMessage, +Variable,+,message_note_d0,const NotificationMessage, +Variable,+,message_note_d1,const NotificationMessage, +Variable,+,message_note_d2,const NotificationMessage, +Variable,+,message_note_d3,const NotificationMessage, +Variable,+,message_note_d4,const NotificationMessage, +Variable,+,message_note_d5,const NotificationMessage, +Variable,+,message_note_d6,const NotificationMessage, +Variable,+,message_note_d7,const NotificationMessage, +Variable,+,message_note_d8,const NotificationMessage, +Variable,+,message_note_ds0,const NotificationMessage, +Variable,+,message_note_ds1,const NotificationMessage, +Variable,+,message_note_ds2,const NotificationMessage, +Variable,+,message_note_ds3,const NotificationMessage, +Variable,+,message_note_ds4,const NotificationMessage, +Variable,+,message_note_ds5,const NotificationMessage, +Variable,+,message_note_ds6,const NotificationMessage, +Variable,+,message_note_ds7,const NotificationMessage, +Variable,+,message_note_ds8,const NotificationMessage, +Variable,+,message_note_e0,const NotificationMessage, +Variable,+,message_note_e1,const NotificationMessage, +Variable,+,message_note_e2,const NotificationMessage, +Variable,+,message_note_e3,const NotificationMessage, +Variable,+,message_note_e4,const NotificationMessage, +Variable,+,message_note_e5,const NotificationMessage, +Variable,+,message_note_e6,const NotificationMessage, +Variable,+,message_note_e7,const NotificationMessage, +Variable,+,message_note_e8,const NotificationMessage, +Variable,+,message_note_f0,const NotificationMessage, +Variable,+,message_note_f1,const NotificationMessage, +Variable,+,message_note_f2,const NotificationMessage, +Variable,+,message_note_f3,const NotificationMessage, +Variable,+,message_note_f4,const NotificationMessage, +Variable,+,message_note_f5,const NotificationMessage, +Variable,+,message_note_f6,const NotificationMessage, +Variable,+,message_note_f7,const NotificationMessage, +Variable,+,message_note_f8,const NotificationMessage, +Variable,+,message_note_fs0,const NotificationMessage, +Variable,+,message_note_fs1,const NotificationMessage, +Variable,+,message_note_fs2,const NotificationMessage, +Variable,+,message_note_fs3,const NotificationMessage, +Variable,+,message_note_fs4,const NotificationMessage, +Variable,+,message_note_fs5,const NotificationMessage, +Variable,+,message_note_fs6,const NotificationMessage, +Variable,+,message_note_fs7,const NotificationMessage, +Variable,+,message_note_fs8,const NotificationMessage, +Variable,+,message_note_g0,const NotificationMessage, +Variable,+,message_note_g1,const NotificationMessage, +Variable,+,message_note_g2,const NotificationMessage, +Variable,+,message_note_g3,const NotificationMessage, +Variable,+,message_note_g4,const NotificationMessage, +Variable,+,message_note_g5,const NotificationMessage, +Variable,+,message_note_g6,const NotificationMessage, +Variable,+,message_note_g7,const NotificationMessage, +Variable,+,message_note_g8,const NotificationMessage, +Variable,+,message_note_gs0,const NotificationMessage, +Variable,+,message_note_gs1,const NotificationMessage, +Variable,+,message_note_gs2,const NotificationMessage, +Variable,+,message_note_gs3,const NotificationMessage, +Variable,+,message_note_gs4,const NotificationMessage, +Variable,+,message_note_gs5,const NotificationMessage, +Variable,+,message_note_gs6,const NotificationMessage, +Variable,+,message_note_gs7,const NotificationMessage, +Variable,+,message_note_gs8,const NotificationMessage, +Variable,+,message_red_0,const NotificationMessage, +Variable,+,message_red_255,const NotificationMessage, +Variable,+,message_sound_off,const NotificationMessage, +Variable,+,message_vibro_off,const NotificationMessage, +Variable,+,message_vibro_on,const NotificationMessage, +Variable,+,periph_power,const GpioPin, +Variable,+,sequence_audiovisual_alert,const NotificationSequence, +Variable,+,sequence_blink_blue_10,const NotificationSequence, +Variable,+,sequence_blink_blue_100,const NotificationSequence, +Variable,+,sequence_blink_cyan_10,const NotificationSequence, +Variable,+,sequence_blink_cyan_100,const NotificationSequence, +Variable,+,sequence_blink_green_10,const NotificationSequence, +Variable,+,sequence_blink_green_100,const NotificationSequence, +Variable,+,sequence_blink_magenta_10,const NotificationSequence, +Variable,+,sequence_blink_magenta_100,const NotificationSequence, +Variable,+,sequence_blink_red_10,const NotificationSequence, +Variable,+,sequence_blink_red_100,const NotificationSequence, +Variable,+,sequence_blink_start_blue,const NotificationSequence, +Variable,+,sequence_blink_start_cyan,const NotificationSequence, +Variable,+,sequence_blink_start_green,const NotificationSequence, +Variable,+,sequence_blink_start_magenta,const NotificationSequence, +Variable,+,sequence_blink_start_red,const NotificationSequence, +Variable,+,sequence_blink_start_yellow,const NotificationSequence, +Variable,+,sequence_blink_stop,const NotificationSequence, +Variable,+,sequence_blink_white_100,const NotificationSequence, +Variable,+,sequence_blink_yellow_10,const NotificationSequence, +Variable,+,sequence_blink_yellow_100,const NotificationSequence, +Variable,+,sequence_charged,const NotificationSequence, +Variable,+,sequence_charging,const NotificationSequence, +Variable,+,sequence_display_backlight_enforce_auto,const NotificationSequence, +Variable,+,sequence_display_backlight_enforce_on,const NotificationSequence, +Variable,+,sequence_display_backlight_off,const NotificationSequence, +Variable,+,sequence_display_backlight_off_delay_1000,const NotificationSequence, +Variable,+,sequence_display_backlight_on,const NotificationSequence, +Variable,+,sequence_double_vibro,const NotificationSequence, +Variable,+,sequence_error,const NotificationSequence, +Variable,+,sequence_not_charging,const NotificationSequence, +Variable,+,sequence_reset_blue,const NotificationSequence, +Variable,+,sequence_reset_display,const NotificationSequence, +Variable,+,sequence_reset_green,const NotificationSequence, +Variable,+,sequence_reset_red,const NotificationSequence, +Variable,+,sequence_reset_rgb,const NotificationSequence, +Variable,+,sequence_reset_sound,const NotificationSequence, +Variable,+,sequence_reset_vibro,const NotificationSequence, +Variable,+,sequence_set_blue_255,const NotificationSequence, +Variable,+,sequence_set_green_255,const NotificationSequence, +Variable,+,sequence_set_only_blue_255,const NotificationSequence, +Variable,+,sequence_set_only_green_255,const NotificationSequence, +Variable,+,sequence_set_only_red_255,const NotificationSequence, +Variable,+,sequence_set_red_255,const NotificationSequence, +Variable,+,sequence_set_vibro_on,const NotificationSequence, +Variable,+,sequence_single_vibro,const NotificationSequence, +Variable,+,sequence_solid_yellow,const NotificationSequence, +Variable,+,sequence_success,const NotificationSequence, +Variable,-,suboptarg,char*, +Variable,+,usb_cdc_dual,FuriHalUsbInterface, +Variable,+,usb_cdc_single,FuriHalUsbInterface, +Variable,+,usb_hid,FuriHalUsbInterface, +Variable,+,usb_hid_u2f,FuriHalUsbInterface, +Variable,+,usbd_devfs,const usbd_driver, +Variable,+,vibro_gpio,const GpioPin, diff --git a/firmware/targets/f7/application-ext.ld b/firmware/targets/f7/application-ext.ld index 45fe431cdd3..8f79675be2e 100644 --- a/firmware/targets/f7/application-ext.ld +++ b/firmware/targets/f7/application-ext.ld @@ -1,9 +1,17 @@ +FORCE_COMMON_ALLOCATION + SECTIONS { - .text 0x00000000 : + .text 0x00000000 : ALIGN(4) { *(.text) + *(.stub) + *(.text*) *(.text.*) + *(.text._*) + + KEEP (*(.init)) + KEEP (*(.fini)) } .rodata : @@ -20,15 +28,22 @@ SECTIONS *(.data.*) } + .bss : { *(.bss) - *(.bss.*) + *(.bss*) *(.sbss) - *(.sbss.*) + *(.sbss*) *(COMMON) } + .ARM.attributes : + { + *(.ARM.attributes) + *(.ARM.attributes.*) + } + /DISCARD/ : { *(.comment) diff --git a/firmware/targets/f7/furi_hal/furi_hal_clock.h b/firmware/targets/f7/furi_hal/furi_hal_clock.h index 9cb11db4b98..6cebb20c626 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_clock.h +++ b/firmware/targets/f7/furi_hal/furi_hal_clock.h @@ -1,5 +1,9 @@ #pragma once +#ifdef __cplusplus +extern "C" { +#endif + /** Early initialization */ void furi_hal_clock_init_early(); @@ -20,3 +24,7 @@ void furi_hal_clock_suspend_tick(); /** Continue SysTick counter operation */ void furi_hal_clock_resume_tick(); + +#ifdef __cplusplus +} +#endif diff --git a/firmware/targets/f7/furi_hal/furi_hal_flash.h b/firmware/targets/f7/furi_hal/furi_hal_flash.h index eac80d95ec8..1ed4c039960 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_flash.h +++ b/firmware/targets/f7/furi_hal/furi_hal_flash.h @@ -4,6 +4,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + #define FURI_HAL_FLASH_OB_RAW_SIZE_BYTES 0x80 #define FURI_HAL_FLASH_OB_SIZE_WORDS (FURI_HAL_FLASH_OB_RAW_SIZE_BYTES / sizeof(uint32_t)) #define FURI_HAL_FLASH_OB_TOTAL_VALUES (FURI_HAL_FLASH_OB_SIZE_WORDS / 2) @@ -142,3 +146,7 @@ void furi_hal_flash_ob_apply(); * @return pointer to read-only data of OB (raw + complementary values) */ const FuriHalFlashRawOptionByteData* furi_hal_flash_ob_get_raw_ptr(); + +#ifdef __cplusplus +} +#endif diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc.c b/firmware/targets/f7/furi_hal/furi_hal_nfc.c index 94677481955..1a5afa1ec67 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_nfc.c +++ b/firmware/targets/f7/furi_hal/furi_hal_nfc.c @@ -124,9 +124,9 @@ bool furi_hal_nfc_detect(FuriHalNfcDevData* nfc_data, uint32_t timeout) { } nfc_data->cuid = (cuid_start[0] << 24) | (cuid_start[1] << 16) | (cuid_start[2] << 8) | (cuid_start[3]); - } else if(dev_list[0].type == RFAL_NFC_LISTEN_TYPE_NFCB || - dev_list[0].type == RFAL_NFC_LISTEN_TYPE_ST25TB) - { + } else if( + dev_list[0].type == RFAL_NFC_LISTEN_TYPE_NFCB || + dev_list[0].type == RFAL_NFC_LISTEN_TYPE_ST25TB) { nfc_data->type = FuriHalNfcTypeB; } else if(dev_list[0].type == RFAL_NFC_LISTEN_TYPE_NFCF) { nfc_data->type = FuriHalNfcTypeF; @@ -738,3 +738,46 @@ void furi_hal_nfc_sleep() { rfalNfcDeactivate(false); rfalLowPowerModeStart(); } + +FuriHalNfcReturn furi_hal_nfc_ll_set_mode(FuriHalNfcMode mode, FuriHalNfcBitrate txBR, FuriHalNfcBitrate rxBR) { + return rfalSetMode((rfalMode)mode, (rfalBitRate)txBR, (rfalBitRate)rxBR); +} + +void furi_hal_nfc_ll_set_error_handling(FuriHalNfcErrorHandling eHandling) { + rfalSetErrorHandling((rfalEHandling)eHandling); +} + +void furi_hal_nfc_ll_set_guard_time(uint32_t cycles) { + rfalSetGT(cycles); +} + +void furi_hal_nfc_ll_set_fdt_listen(uint32_t cycles) { + rfalSetFDTListen(cycles); +} + +void furi_hal_nfc_ll_set_fdt_poll(uint32_t FDTPoll) { + rfalSetFDTPoll(FDTPoll); +} + +void furi_hal_nfc_ll_txrx_on() { + st25r3916TxRxOn(); +} + +void furi_hal_nfc_ll_txrx_off() { + st25r3916TxRxOff(); +} + +FuriHalNfcReturn furi_hal_nfc_ll_txrx( + uint8_t* txBuf, + uint16_t txBufLen, + uint8_t* rxBuf, + uint16_t rxBufLen, + uint16_t* actLen, + uint32_t flags, + uint32_t fwt) { + return rfalTransceiveBlockingTxRx(txBuf, txBufLen, rxBuf, rxBufLen, actLen, flags, fwt); +} + +void furi_hal_nfc_ll_poll() { + rfalWorker(); +} \ No newline at end of file diff --git a/firmware/targets/f7/furi_hal/furi_hal_subghz.c b/firmware/targets/f7/furi_hal/furi_hal_subghz.c index 7afb88bc332..b73d074fd99 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_subghz.c +++ b/firmware/targets/f7/furi_hal/furi_hal_subghz.c @@ -17,7 +17,6 @@ #define TAG "FuriHalSubGhz" - /* * Uncomment define to enable duplication of * IO GO0 CC1101 to an external comb. @@ -33,7 +32,7 @@ * Attention this setting switches pin to output. * Make sure it is not connected directly to power or ground */ - + //#define SUBGHZ_DEBUG_CC1101_PIN gpio_ext_pa7 #ifdef SUBGHZ_DEBUG_CC1101_PIN uint32_t subghz_debug_gpio_buff[2]; diff --git a/firmware/targets/f7/furi_hal/furi_hal_usb_cdc.c b/firmware/targets/f7/furi_hal/furi_hal_usb_cdc.c index 69a7fb3ce61..88deafdcc08 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_usb_cdc.c +++ b/firmware/targets/f7/furi_hal/furi_hal_usb_cdc.c @@ -1,7 +1,7 @@ #include "furi_hal_version.h" #include "furi_hal_usb_i.h" #include "furi_hal_usb.h" -#include "furi_hal_usb_cdc_i.h" +#include "furi_hal_usb_cdc.h" #include #include "usb.h" diff --git a/firmware/targets/f7/furi_hal/furi_hal_usb_cdc_i.h b/firmware/targets/f7/furi_hal/furi_hal_usb_cdc.h similarity index 91% rename from firmware/targets/f7/furi_hal/furi_hal_usb_cdc_i.h rename to firmware/targets/f7/furi_hal/furi_hal_usb_cdc.h index 4ba150eb5a1..89b68991b95 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_usb_cdc_i.h +++ b/firmware/targets/f7/furi_hal/furi_hal_usb_cdc.h @@ -5,6 +5,10 @@ #define CDC_DATA_SZ 64 +#ifdef __cplusplus +extern "C" { +#endif + typedef struct { void (*tx_ep_callback)(void* context); void (*rx_ep_callback)(void* context); @@ -22,3 +26,7 @@ uint8_t furi_hal_cdc_get_ctrl_line_state(uint8_t if_num); void furi_hal_cdc_send(uint8_t if_num, uint8_t* buf, uint16_t len); int32_t furi_hal_cdc_receive(uint8_t if_num, uint8_t* buf, uint16_t max_len); + +#ifdef __cplusplus +} +#endif diff --git a/firmware/targets/f7/platform_specific/intrinsic_export.h b/firmware/targets/f7/platform_specific/intrinsic_export.h new file mode 100644 index 00000000000..8dbc4bd0320 --- /dev/null +++ b/firmware/targets/f7/platform_specific/intrinsic_export.h @@ -0,0 +1,11 @@ +#include + +#ifdef __cplusplus +extern "C" { +#endif + +void __clear_cache(void*, void*); + +#ifdef __cplusplus +} +#endif diff --git a/firmware/targets/f7/stm32wb55xx_flash.ld b/firmware/targets/f7/stm32wb55xx_flash.ld index 4124c096d90..e1fb98b9fed 100644 --- a/firmware/targets/f7/stm32wb55xx_flash.ld +++ b/firmware/targets/f7/stm32wb55xx_flash.ld @@ -131,7 +131,7 @@ SECTIONS _sidata = LOADADDR(.data); /* Initialized data sections goes into RAM, load LMA copy after code */ - .data : + .data : { . = ALIGN(4); _sdata = .; /* create a global symbol at data start */ @@ -143,7 +143,6 @@ SECTIONS _edata = .; /* define a global symbol at data end */ } >RAM1 AT> FLASH - /* Uninitialized data section */ . = ALIGN(4); .bss : @@ -161,7 +160,8 @@ SECTIONS } >RAM1 /* User_heap_stack section, used to check that there is enough RAM left */ - ._user_heap_stack(NOLOAD): + /* DSECT: https://software-dl.ti.com/ccs/esd/documents/sdto_cgt_linker_special_section_types.html */ + ._user_heap_stack(DSECT): { . = ALIGN(8); __heap_start__ = .; @@ -172,11 +172,11 @@ SECTIONS } >RAM1 /* Free Flash space, that can be used for internal storage */ - .free_flash(NOLOAD): + .free_flash(DSECT): { __free_flash_start__ = .; . = ORIGIN(FLASH) + LENGTH(FLASH); - } >FLASH + } >FLASH /* Remove information from the standard libraries */ /DISCARD/ : diff --git a/firmware/targets/f7/stm32wb55xx_ram_fw.ld b/firmware/targets/f7/stm32wb55xx_ram_fw.ld index 1b7fe3600a9..db9e407c9d7 100644 --- a/firmware/targets/f7/stm32wb55xx_ram_fw.ld +++ b/firmware/targets/f7/stm32wb55xx_ram_fw.ld @@ -159,7 +159,7 @@ SECTIONS } >RAM1 /* User_heap_stack section, used to check that there is enough RAM left */ - ._user_heap_stack : + ._user_heap_stack(DSECT) : { . = ALIGN(8); __heap_start__ = .; @@ -170,7 +170,7 @@ SECTIONS } >RAM1 /* Free Flash space, that can be used for internal storage */ - /*.free_flash(NOLOAD): + /*.free_flash(DSECT): { __free_flash_start__ = .; . = ORIGIN(FLASH) + LENGTH(FLASH); diff --git a/firmware/targets/furi_hal_include/furi_hal.h b/firmware/targets/furi_hal_include/furi_hal.h index 2a372a6c3e6..8613c4d5e04 100644 --- a/firmware/targets/furi_hal_include/furi_hal.h +++ b/firmware/targets/furi_hal_include/furi_hal.h @@ -6,7 +6,8 @@ #pragma once #ifdef __cplusplus -template struct STOP_EXTERNING_ME {}; +template +struct STOP_EXTERNING_ME {}; #endif #include "furi_hal_cortex.h" diff --git a/firmware/targets/furi_hal_include/furi_hal_bt_hid.h b/firmware/targets/furi_hal_include/furi_hal_bt_hid.h index e35edd01c06..4e74bbda751 100644 --- a/firmware/targets/furi_hal_include/furi_hal_bt_hid.h +++ b/firmware/targets/furi_hal_include/furi_hal_bt_hid.h @@ -3,6 +3,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + /** Start Hid Keyboard Profile */ void furi_hal_bt_hid_start(); @@ -81,3 +85,7 @@ bool furi_hal_bt_hid_consumer_key_release(uint16_t button); * @param button key code */ bool furi_hal_bt_hid_consumer_key_release_all(); + +#ifdef __cplusplus +} +#endif diff --git a/firmware/targets/furi_hal_include/furi_hal_bt_serial.h b/firmware/targets/furi_hal_include/furi_hal_bt_serial.h index 9cc4ba5bbc1..e1b7af224eb 100644 --- a/firmware/targets/furi_hal_include/furi_hal_bt_serial.h +++ b/firmware/targets/furi_hal_include/furi_hal_bt_serial.h @@ -2,6 +2,10 @@ #include "serial_service.h" +#ifdef __cplusplus +extern "C" { +#endif + #define FURI_HAL_BT_SERIAL_PACKET_SIZE_MAX SERIAL_SVC_DATA_LEN_MAX /** Serial service callback type */ @@ -38,3 +42,7 @@ void furi_hal_bt_serial_notify_buffer_is_empty(); * @return true on success */ bool furi_hal_bt_serial_tx(uint8_t* data, uint16_t size); + +#ifdef __cplusplus +} +#endif diff --git a/firmware/targets/furi_hal_include/furi_hal_compress.h b/firmware/targets/furi_hal_include/furi_hal_compress.h index 17ce3e69139..f80aee5162f 100644 --- a/firmware/targets/furi_hal_include/furi_hal_compress.h +++ b/firmware/targets/furi_hal_include/furi_hal_compress.h @@ -8,6 +8,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + /** Defines encoder and decoder window size */ #define FURI_HAL_COMPRESS_EXP_BUFF_SIZE_LOG (8) @@ -77,3 +81,7 @@ bool furi_hal_compress_decode( uint8_t* data_out, size_t data_out_size, size_t* data_res_size); + +#ifdef __cplusplus +} +#endif diff --git a/firmware/targets/furi_hal_include/furi_hal_crypto.h b/firmware/targets/furi_hal_include/furi_hal_crypto.h index fc3974bbe45..81788e96e11 100644 --- a/firmware/targets/furi_hal_include/furi_hal_crypto.h +++ b/firmware/targets/furi_hal_include/furi_hal_crypto.h @@ -8,6 +8,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + /** FuriHalCryptoKey Type */ typedef enum { FuriHalCryptoKeyTypeMaster, /**< Master key */ @@ -82,3 +86,7 @@ bool furi_hal_crypto_encrypt(const uint8_t* input, uint8_t* output, size_t size) * @return true on success */ bool furi_hal_crypto_decrypt(const uint8_t* input, uint8_t* output, size_t size); + +#ifdef __cplusplus +} +#endif diff --git a/firmware/targets/furi_hal_include/furi_hal_nfc.h b/firmware/targets/furi_hal_include/furi_hal_nfc.h index 0bcb45068d6..aa1c61d9c6e 100644 --- a/firmware/targets/furi_hal_include/furi_hal_nfc.h +++ b/firmware/targets/furi_hal_include/furi_hal_nfc.h @@ -5,16 +5,15 @@ #pragma once -#include #include #include #include -#include - #ifdef __cplusplus extern "C" { #endif +#include +#include #define FURI_HAL_NFC_UID_MAX_LEN 10 #define FURI_HAL_NFC_DATA_BUFF_SIZE (512) @@ -229,6 +228,158 @@ void furi_hal_nfc_sleep(); void furi_hal_nfc_stop(); + +/* Low level transport API, use it to implement your own transport layers */ + +#define furi_hal_nfc_ll_ms2fc rfalConvMsTo1fc + +#define FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_TX_MANUAL RFAL_TXRX_FLAGS_CRC_TX_MANUAL +#define FURI_HAL_NFC_LL_TXRX_FLAGS_AGC_ON RFAL_TXRX_FLAGS_AGC_ON +#define FURI_HAL_NFC_LL_TXRX_FLAGS_PAR_RX_REMV RFAL_TXRX_FLAGS_PAR_RX_REMV +#define FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_RX_KEEP RFAL_TXRX_FLAGS_CRC_RX_KEEP + +typedef enum { + FuriHalNfcReturnOk = 0, /*!< no error occurred */ + FuriHalNfcReturnNomem = 1, /*!< not enough memory to perform the requested operation */ + FuriHalNfcReturnBusy = 2, /*!< device or resource busy */ + FuriHalNfcReturnIo = 3, /*!< generic IO error */ + FuriHalNfcReturnTimeout = 4, /*!< error due to timeout */ + FuriHalNfcReturnRequest = 5, /*!< invalid request or requested function can't be executed at the moment */ + FuriHalNfcReturnNomsg = 6, /*!< No message of desired type */ + FuriHalNfcReturnParam = 7, /*!< Parameter error */ + FuriHalNfcReturnSystem = 8, /*!< System error */ + FuriHalNfcReturnFraming = 9, /*!< Framing error */ + FuriHalNfcReturnOverrun = 10, /*!< lost one or more received bytes */ + FuriHalNfcReturnProto = 11, /*!< protocol error */ + FuriHalNfcReturnInternal = 12, /*!< Internal Error */ + FuriHalNfcReturnAgain = 13, /*!< Call again */ + FuriHalNfcReturnMemCorrupt = 14, /*!< memory corruption */ + FuriHalNfcReturnNotImplemented = 15, /*!< not implemented */ + FuriHalNfcReturnPcCorrupt = 16, /*!< Program Counter has been manipulated or spike/noise trigger illegal operation */ + FuriHalNfcReturnSend = 17, /*!< error sending*/ + FuriHalNfcReturnIgnore = 18, /*!< indicates error detected but to be ignored */ + FuriHalNfcReturnSemantic = 19, /*!< indicates error in state machine (unexpected cmd) */ + FuriHalNfcReturnSyntax = 20, /*!< indicates error in state machine (unknown cmd) */ + FuriHalNfcReturnCrc = 21, /*!< crc error */ + FuriHalNfcReturnNotfound = 22, /*!< transponder not found */ + FuriHalNfcReturnNotunique = 23, /*!< transponder not unique - more than one transponder in field */ + FuriHalNfcReturnNotsupp = 24, /*!< requested operation not supported */ + FuriHalNfcReturnWrite = 25, /*!< write error */ + FuriHalNfcReturnFifo = 26, /*!< fifo over or underflow error */ + FuriHalNfcReturnPar = 27, /*!< parity error */ + FuriHalNfcReturnDone = 28, /*!< transfer has already finished */ + FuriHalNfcReturnRfCollision = 29, /*!< collision error (Bit Collision or during RF Collision avoidance ) */ + FuriHalNfcReturnHwOverrun = 30, /*!< lost one or more received bytes */ + FuriHalNfcReturnReleaseReq = 31, /*!< device requested release */ + FuriHalNfcReturnSleepReq = 32, /*!< device requested sleep */ + FuriHalNfcReturnWrongState = 33, /*!< incorrent state for requested operation */ + FuriHalNfcReturnMaxReruns = 34, /*!< blocking procedure reached maximum runs */ + FuriHalNfcReturnDisabled = 35, /*!< operation aborted due to disabled configuration */ + FuriHalNfcReturnHwMismatch = 36, /*!< expected hw do not match */ + FuriHalNfcReturnLinkLoss = 37, /*!< Other device's field didn't behave as expected: turned off by Initiator in Passive mode, or AP2P did not turn on field */ + FuriHalNfcReturnInvalidHandle = 38, /*!< invalid or not initalized device handle */ + FuriHalNfcReturnIncompleteByte = 40, /*!< Incomplete byte rcvd */ + FuriHalNfcReturnIncompleteByte01 = 41, /*!< Incomplete byte rcvd - 1 bit */ + FuriHalNfcReturnIncompleteByte02 = 42, /*!< Incomplete byte rcvd - 2 bit */ + FuriHalNfcReturnIncompleteByte03 = 43, /*!< Incomplete byte rcvd - 3 bit */ + FuriHalNfcReturnIncompleteByte04 = 44, /*!< Incomplete byte rcvd - 4 bit */ + FuriHalNfcReturnIncompleteByte05 = 45, /*!< Incomplete byte rcvd - 5 bit */ + FuriHalNfcReturnIncompleteByte06 = 46, /*!< Incomplete byte rcvd - 6 bit */ + FuriHalNfcReturnIncompleteByte07 = 47, /*!< Incomplete byte rcvd - 7 bit */ +} FuriHalNfcReturn; + +typedef enum { + FuriHalNfcModeNone = 0, /*!< No mode selected/defined */ + FuriHalNfcModePollNfca = 1, /*!< Mode to perform as NFCA (ISO14443A) Poller (PCD) */ + FuriHalNfcModePollNfcaT1t = 2, /*!< Mode to perform as NFCA T1T (Topaz) Poller (PCD) */ + FuriHalNfcModePollNfcb = 3, /*!< Mode to perform as NFCB (ISO14443B) Poller (PCD) */ + FuriHalNfcModePollBPrime = 4, /*!< Mode to perform as B' Calypso (Innovatron) (PCD) */ + FuriHalNfcModePollBCts = 5, /*!< Mode to perform as CTS Poller (PCD) */ + FuriHalNfcModePollNfcf = 6, /*!< Mode to perform as NFCF (FeliCa) Poller (PCD) */ + FuriHalNfcModePollNfcv = 7, /*!< Mode to perform as NFCV (ISO15963) Poller (PCD) */ + FuriHalNfcModePollPicopass = 8, /*!< Mode to perform as PicoPass / iClass Poller (PCD) */ + FuriHalNfcModePollActiveP2p = 9, /*!< Mode to perform as Active P2P (ISO18092) Initiator */ + FuriHalNfcModeListenNfca = 10, /*!< Mode to perform as NFCA (ISO14443A) Listener (PICC) */ + FuriHalNfcModeListenNfcb = 11, /*!< Mode to perform as NFCA (ISO14443B) Listener (PICC) */ + FuriHalNfcModeListenNfcf = 12, /*!< Mode to perform as NFCA (ISO15963) Listener (PICC) */ + FuriHalNfcModeListenActiveP2p = 13 /*!< Mode to perform as Active P2P (ISO18092) Target */ +} FuriHalNfcMode; + +typedef enum { + FuriHalNfcBitrate106 = 0, /*!< Bit Rate 106 kbit/s (fc/128) */ + FuriHalNfcBitrate212 = 1, /*!< Bit Rate 212 kbit/s (fc/64) */ + FuriHalNfcBitrate424 = 2, /*!< Bit Rate 424 kbit/s (fc/32) */ + FuriHalNfcBitrate848 = 3, /*!< Bit Rate 848 kbit/s (fc/16) */ + FuriHalNfcBitrate1695 = 4, /*!< Bit Rate 1695 kbit/s (fc/8) */ + FuriHalNfcBitrate3390 = 5, /*!< Bit Rate 3390 kbit/s (fc/4) */ + FuriHalNfcBitrate6780 = 6, /*!< Bit Rate 6780 kbit/s (fc/2) */ + FuriHalNfcBitrate13560 = 7, /*!< Bit Rate 13560 kbit/s (fc) */ + FuriHalNfcBitrate52p97 = 0xEB, /*!< Bit Rate 52.97 kbit/s (fc/256) Fast Mode VICC->VCD */ + FuriHalNfcBitrate26p48 = 0xEC, /*!< Bit Rate 26,48 kbit/s (fc/512) NFCV VICC->VCD & VCD->VICC 1of4 */ + FuriHalNfcBitrate1p66 = 0xED, /*!< Bit Rate 1,66 kbit/s (fc/8192) NFCV VCD->VICC 1of256 */ + FuriHalNfcBitrateKeep = 0xFF /*!< Value indicating to keep the same previous bit rate */ +} FuriHalNfcBitrate; + +FuriHalNfcReturn furi_hal_nfc_ll_set_mode(FuriHalNfcMode mode, FuriHalNfcBitrate txBR, FuriHalNfcBitrate rxBR); + +#define FURI_HAL_NFC_LL_GT_NFCA furi_hal_nfc_ll_ms2fc(5U) /*!< GTA Digital 2.0 6.10.4.1 & B.2 */ +#define FURI_HAL_NFC_LL_GT_NFCB furi_hal_nfc_ll_ms2fc(5U) /*!< GTB Digital 2.0 7.9.4.1 & B.3 */ +#define FURI_HAL_NFC_LL_GT_NFCF furi_hal_nfc_ll_ms2fc(20U) /*!< GTF Digital 2.0 8.7.4.1 & B.4 */ +#define FURI_HAL_NFC_LL_GT_NFCV furi_hal_nfc_ll_ms2fc(5U) /*!< GTV Digital 2.0 9.7.5.1 & B.5 */ +#define FURI_HAL_NFC_LL_GT_PICOPASS furi_hal_nfc_ll_ms2fc(1U) /*!< GT Picopass */ +#define FURI_HAL_NFC_LL_GT_AP2P furi_hal_nfc_ll_ms2fc(5U) /*!< TIRFG Ecma 340 11.1.1 */ +#define FURI_HAL_NFC_LL_GT_AP2P_ADJUSTED furi_hal_nfc_ll_ms2fc(5U + 25U) /*!< Adjusted GT for greater interoperability (Sony XPERIA P, Nokia N9, Huawei P2) */ + +void furi_hal_nfc_ll_set_guard_time(uint32_t cycles); + +typedef enum { + FuriHalNfcErrorHandlingNone = 0, /*!< No special error handling will be performed */ + FuriHalNfcErrorHandlingNfc = 1, /*!< Error handling set to perform as NFC compliant device */ + FuriHalNfcErrorHandlingEmvco = 2 /*!< Error handling set to perform as EMVCo compliant device */ +} FuriHalNfcErrorHandling; + +void furi_hal_nfc_ll_set_error_handling(FuriHalNfcErrorHandling eHandling); + +/* RFAL Frame Delay Time (FDT) Listen default values */ +#define FURI_HAL_NFC_LL_FDT_LISTEN_NFCA_POLLER 1172U /*!< FDTA,LISTEN,MIN (n=9) Last bit: Logic "1" - tnn,min/2 Digital 1.1 6.10 ; EMV CCP Spec Book D v2.01 4.8.1.3 */ +#define FURI_HAL_NFC_LL_FDT_LISTEN_NFCB_POLLER 1008U /*!< TR0B,MIN Digital 1.1 7.1.3 & A.3 ; EMV CCP Spec Book D v2.01 4.8.1.3 & Table A.5 */ +#define FURI_HAL_NFC_LL_FDT_LISTEN_NFCF_POLLER 2672U /*!< TR0F,LISTEN,MIN Digital 1.1 8.7.1.1 & A.4 */ +#define FURI_HAL_NFC_LL_FDT_LISTEN_NFCV_POLLER 4310U /*!< FDTV,LISTEN,MIN t1 min Digital 2.1 B.5 ; ISO15693-3 2009 9.1 */ +#define FURI_HAL_NFC_LL_FDT_LISTEN_PICOPASS_POLLER 3400U /*!< ISO15693 t1 min - observed adjustment */ +#define FURI_HAL_NFC_LL_FDT_LISTEN_AP2P_POLLER 64U /*!< FDT AP2P No actual FDTListen is required as fields switch and collision avoidance */ +#define FURI_HAL_NFC_LL_FDT_LISTEN_NFCA_LISTENER 1172U /*!< FDTA,LISTEN,MIN Digital 1.1 6.10 */ +#define FURI_HAL_NFC_LL_FDT_LISTEN_NFCB_LISTENER 1024U /*!< TR0B,MIN Digital 1.1 7.1.3 & A.3 ; EMV CCP Spec Book D v2.01 4.8.1.3 & Table A.5 */ +#define FURI_HAL_NFC_LL_FDT_LISTEN_NFCF_LISTENER 2688U /*!< TR0F,LISTEN,MIN Digital 2.1 8.7.1.1 & B.4 */ +#define FURI_HAL_NFC_LL_FDT_LISTEN_AP2P_LISTENER 64U /*!< FDT AP2P No actual FDTListen exists as fields switch and collision avoidance */ + +void furi_hal_nfc_ll_set_fdt_listen(uint32_t cycles); + +/* RFAL Frame Delay Time (FDT) Poll default values */ +#define FURI_HAL_NFC_LL_FDT_POLL_NFCA_POLLER 6780U /*!< FDTA,POLL,MIN Digital 1.1 6.10.3.1 & A.2 */ +#define FURI_HAL_NFC_LL_FDT_POLL_NFCA_T1T_POLLER 384U /*!< RRDDT1T,MIN,B1 Digital 1.1 10.7.1 & A.5 */ +#define FURI_HAL_NFC_LL_FDT_POLL_NFCB_POLLER 6780U /*!< FDTB,POLL,MIN = TR2B,MIN,DEFAULT Digital 1.1 7.9.3 & A.3 ; EMVCo 3.0 FDTB,PCD,MIN Table A.5 */ +#define FURI_HAL_NFC_LL_FDT_POLL_NFCF_POLLER 6800U /*!< FDTF,POLL,MIN Digital 2.1 8.7.3 & B.4 */ +#define FURI_HAL_NFC_LL_FDT_POLL_NFCV_POLLER 4192U /*!< FDTV,POLL Digital 2.1 9.7.3.1 & B.5 */ +#define FURI_HAL_NFC_LL_FDT_POLL_PICOPASS_POLLER 1790U /*!< FDT Max */ +#define FURI_HAL_NFC_LL_FDT_POLL_AP2P_POLLER 0U /*!< FDT AP2P No actual FDTPoll exists as fields switch and collision avoidance */ + +void furi_hal_nfc_ll_set_fdt_poll(uint32_t FDTPoll); + +void furi_hal_nfc_ll_txrx_on(); + +void furi_hal_nfc_ll_txrx_off(); + +FuriHalNfcReturn furi_hal_nfc_ll_txrx( + uint8_t* txBuf, + uint16_t txBufLen, + uint8_t* rxBuf, + uint16_t rxBufLen, + uint16_t* actLen, + uint32_t flags, + uint32_t fwt); + +void furi_hal_nfc_ll_poll(); + #ifdef __cplusplus } #endif diff --git a/firmware/targets/furi_hal_include/furi_hal_region.h b/firmware/targets/furi_hal_include/furi_hal_region.h index 10d519ab73c..accd7e127cc 100644 --- a/firmware/targets/furi_hal_include/furi_hal_region.h +++ b/firmware/targets/furi_hal_include/furi_hal_region.h @@ -4,6 +4,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + typedef struct { uint32_t start; uint32_t end; @@ -71,3 +75,7 @@ bool furi_hal_region_is_frequency_allowed(uint32_t frequency); * @return { description_of_the_return_value } */ const FuriHalRegionBand* furi_hal_region_get_band(uint32_t frequency); + +#ifdef __cplusplus +} +#endif diff --git a/firmware/targets/furi_hal_include/furi_hal_usb.h b/firmware/targets/furi_hal_include/furi_hal_usb.h index 33115c116a3..8b49f6c6537 100644 --- a/firmware/targets/furi_hal_include/furi_hal_usb.h +++ b/firmware/targets/furi_hal_include/furi_hal_usb.h @@ -2,6 +2,10 @@ #include "usb.h" +#ifdef __cplusplus +extern "C" { +#endif + typedef struct FuriHalUsbInterface FuriHalUsbInterface; struct FuriHalUsbInterface { @@ -81,3 +85,7 @@ void furi_hal_usb_set_state_callback(FuriHalUsbStateCallback cb, void* ctx); /** Restart USB device */ void furi_hal_usb_reinit(); + +#ifdef __cplusplus +} +#endif diff --git a/firmware/targets/furi_hal_include/furi_hal_usb_hid.h b/firmware/targets/furi_hal_include/furi_hal_usb_hid.h index 97bac7f049f..f4b40c89d5e 100644 --- a/firmware/targets/furi_hal_include/furi_hal_usb_hid.h +++ b/firmware/targets/furi_hal_include/furi_hal_usb_hid.h @@ -5,6 +5,10 @@ #include "hid_usage_consumer.h" #include "hid_usage_led.h" +#ifdef __cplusplus +extern "C" { +#endif + #define HID_KEYBOARD_NONE 0x00 // Remapping the colon key which is shift + ; to comma #define HID_KEYBOARD_COMMA HID_KEYBOARD_COLON @@ -251,3 +255,7 @@ bool furi_hal_hid_consumer_key_press(uint16_t button); * @param button key code */ bool furi_hal_hid_consumer_key_release(uint16_t button); + +#ifdef __cplusplus +} +#endif diff --git a/firmware/targets/furi_hal_include/furi_hal_usb_hid_u2f.h b/firmware/targets/furi_hal_include/furi_hal_usb_hid_u2f.h index e9bf57657dc..1b20a33c487 100644 --- a/firmware/targets/furi_hal_include/furi_hal_usb_hid_u2f.h +++ b/firmware/targets/furi_hal_include/furi_hal_usb_hid_u2f.h @@ -1,5 +1,9 @@ #pragma once +#ifdef __cplusplus +extern "C" { +#endif + #define HID_U2F_PACKET_LEN 64 typedef enum { @@ -34,3 +38,7 @@ uint32_t furi_hal_hid_u2f_get_request(uint8_t* data); * @param len packet length */ void furi_hal_hid_u2f_send_response(uint8_t* data, uint8_t len); + +#ifdef __cplusplus +} +#endif diff --git a/furi/core/memmgr.c b/furi/core/memmgr.c index dba1a707409..768adc05dfa 100644 --- a/furi/core/memmgr.c +++ b/furi/core/memmgr.c @@ -92,4 +92,20 @@ size_t memmgr_pool_get_free(void) { size_t memmgr_pool_get_max_block(void) { return furi_hal_memory_max_pool_block(); +} + +void* aligned_malloc(size_t size, size_t alignment) { + void* p1; // original block + void** p2; // aligned block + int offset = alignment - 1 + sizeof(void*); + if((p1 = (void*)malloc(size + offset)) == NULL) { + return NULL; + } + p2 = (void**)(((size_t)(p1) + offset) & ~(alignment - 1)); + p2[-1] = p1; + return p2; +} + +void aligned_free(void* p) { + free(((void**)p)[-1]); } \ No newline at end of file diff --git a/furi/core/memmgr.h b/furi/core/memmgr.h index fdecfd72da4..d3ad0c87c95 100644 --- a/furi/core/memmgr.h +++ b/furi/core/memmgr.h @@ -35,6 +35,21 @@ size_t memmgr_get_total_heap(void); */ size_t memmgr_get_minimum_free_heap(void); +/** + * An aligned version of malloc, used when you need to get the aligned space on the heap + * Freeing the received address is performed ONLY through the aligned_free function + * @param size + * @param alignment + * @return void* + */ +void* aligned_malloc(size_t size, size_t alignment); + +/** + * Freed space obtained through the aligned_malloc function + * @param p pointer to result of aligned_malloc + */ +void aligned_free(void* p); + /** * @brief Allocate memory from separate memory pool. That memory can't be freed. * diff --git a/lib/SConscript b/lib/SConscript index 4498c4c9c6f..2822684bca7 100644 --- a/lib/SConscript +++ b/lib/SConscript @@ -15,7 +15,15 @@ env.Append( "lib/u8g2", "lib/update_util", "lib/print", - ] + ], + SDK_HEADERS=[ + File("#/lib/one_wire/one_wire_host_timing.h"), + File("#/lib/one_wire/one_wire_host.h"), + File("#/lib/one_wire/one_wire_slave.h"), + File("#/lib/one_wire/one_wire_device.h"), + File("#/lib/one_wire/ibutton/ibutton_worker.h"), + File("#/lib/one_wire/maxim_crc.h"), + ], ) env.Append( @@ -24,7 +32,7 @@ env.Append( "#/lib", # TODO: remove! "#/lib/mlib", # Ugly hack - "${BUILD_DIR}/assets/compiled", + Dir("../assets/compiled"), ], CPPDEFINES=[ '"M_MEMORY_FULL(x)=abort()"', @@ -76,8 +84,8 @@ libs = env.BuildModules( "nfc", "appframe", "misc", - "loclass", "lfrfid", + "flipper_application", ], ) diff --git a/lib/ST25RFAL002/include/rfal_picopass.h b/lib/ST25RFAL002/include/rfal_picopass.h deleted file mode 100644 index e7fb272ceef..00000000000 --- a/lib/ST25RFAL002/include/rfal_picopass.h +++ /dev/null @@ -1,64 +0,0 @@ - -#ifndef RFAL_PICOPASS_H -#define RFAL_PICOPASS_H - -/* - ****************************************************************************** - * INCLUDES - ****************************************************************************** - */ -#include "platform.h" -#include "rfal_rf.h" -#include "rfal_crc.h" -#include "st_errno.h" - -#define RFAL_PICOPASS_UID_LEN 8 -#define RFAL_PICOPASS_MAX_BLOCK_LEN 8 - -#define RFAL_PICOPASS_TXRX_FLAGS \ - (RFAL_TXRX_FLAGS_CRC_TX_MANUAL | RFAL_TXRX_FLAGS_AGC_ON | RFAL_TXRX_FLAGS_PAR_RX_REMV | \ - RFAL_TXRX_FLAGS_CRC_RX_KEEP) - -enum { - RFAL_PICOPASS_CMD_ACTALL = 0x0A, - RFAL_PICOPASS_CMD_IDENTIFY = 0x0C, - RFAL_PICOPASS_CMD_SELECT = 0x81, - RFAL_PICOPASS_CMD_READCHECK = 0x88, - RFAL_PICOPASS_CMD_CHECK = 0x05, - RFAL_PICOPASS_CMD_READ = 0x0C, - RFAL_PICOPASS_CMD_WRITE = 0x87, -}; - -typedef struct { - uint8_t CSN[RFAL_PICOPASS_UID_LEN]; // Anti-collision CSN - uint8_t crc[2]; -} rfalPicoPassIdentifyRes; - -typedef struct { - uint8_t CSN[RFAL_PICOPASS_UID_LEN]; // Real CSN - uint8_t crc[2]; -} rfalPicoPassSelectRes; - -typedef struct { - uint8_t CCNR[8]; -} rfalPicoPassReadCheckRes; - -typedef struct { - uint8_t mac[4]; -} rfalPicoPassCheckRes; - -typedef struct { - uint8_t data[RFAL_PICOPASS_MAX_BLOCK_LEN]; - uint8_t crc[2]; -} rfalPicoPassReadBlockRes; - -ReturnCode rfalPicoPassPollerInitialize(void); -ReturnCode rfalPicoPassPollerCheckPresence(void); -ReturnCode rfalPicoPassPollerIdentify(rfalPicoPassIdentifyRes* idRes); -ReturnCode rfalPicoPassPollerSelect(uint8_t* csn, rfalPicoPassSelectRes* selRes); -ReturnCode rfalPicoPassPollerReadCheck(rfalPicoPassReadCheckRes* rcRes); -ReturnCode rfalPicoPassPollerCheck(uint8_t* mac, rfalPicoPassCheckRes* chkRes); -ReturnCode rfalPicoPassPollerReadBlock(uint8_t blockNum, rfalPicoPassReadBlockRes* readRes); -ReturnCode rfalPicoPassPollerWriteBlock(uint8_t blockNum, uint8_t data[8], uint8_t mac[4]); - -#endif /* RFAL_PICOPASS_H */ diff --git a/lib/STM32CubeWB.scons b/lib/STM32CubeWB.scons index 80d06b5fcc9..e8350ea99ae 100644 --- a/lib/STM32CubeWB.scons +++ b/lib/STM32CubeWB.scons @@ -13,6 +13,11 @@ env.Append( "USE_FULL_ASSERT", "USE_FULL_LL_DRIVER", ], + SDK_HEADERS=env.GlobRecursive( + "*_ll_*.h", + "#/lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/", + exclude="*usb.h", + ), ) if env["RAM_EXEC"]: diff --git a/lib/app-scened-template/generic_scene.hpp b/lib/app-scened-template/generic_scene.hpp index 979e974787b..bcdf0b46413 100644 --- a/lib/app-scened-template/generic_scene.hpp +++ b/lib/app-scened-template/generic_scene.hpp @@ -1,4 +1,5 @@ -template class GenericScene { +template +class GenericScene { public: virtual void on_enter(TApp* app, bool need_restore) = 0; virtual bool on_event(TApp* app, typename TApp::Event* event) = 0; diff --git a/lib/app-scened-template/record_controller.hpp b/lib/app-scened-template/record_controller.hpp index 9b14274a8ef..171115e15bf 100644 --- a/lib/app-scened-template/record_controller.hpp +++ b/lib/app-scened-template/record_controller.hpp @@ -6,7 +6,8 @@ * * @tparam TRecordClass record class */ -template class RecordController { +template +class RecordController { public: /** * @brief Construct a new Record Controller object for record with record name diff --git a/lib/app-scened-template/scene_controller.hpp b/lib/app-scened-template/scene_controller.hpp index 678f6c0df7b..052eca73166 100644 --- a/lib/app-scened-template/scene_controller.hpp +++ b/lib/app-scened-template/scene_controller.hpp @@ -11,7 +11,8 @@ * @tparam TScene generic scene class * @tparam TApp application class */ -template class SceneController { +template +class SceneController { public: /** * @brief Add scene to scene container diff --git a/lib/app-scened-template/typeindex_no_rtti.hpp b/lib/app-scened-template/typeindex_no_rtti.hpp index 4ac52725f18..0f399385ab5 100644 --- a/lib/app-scened-template/typeindex_no_rtti.hpp +++ b/lib/app-scened-template/typeindex_no_rtti.hpp @@ -33,12 +33,14 @@ namespace ext { /** * Dummy type for tag-dispatching. */ -template struct tag_type {}; +template +struct tag_type {}; /** * A value of tag_type. */ -template constexpr tag_type tag{}; +template +constexpr tag_type tag{}; /** * A type_index implementation without RTTI. @@ -47,7 +49,8 @@ struct type_index { /** * Creates a type_index object for the specified type. */ - template type_index(tag_type) noexcept : hash_code_{index} { + template + type_index(tag_type) noexcept : hash_code_{index} { } /** @@ -61,7 +64,8 @@ struct type_index { /** * Unique integral index associated to template type argument. */ - template static std::size_t const index; + template + static std::size_t const index; /** * Global counter for generating index values. @@ -75,14 +79,16 @@ struct type_index { std::size_t hash_code_; }; -template std::size_t const type_index::index = type_index::counter()++; +template +std::size_t const type_index::index = type_index::counter()++; /** * Creates a type_index object for the specified type. * * Equivalent to `ext::type_index{ext::tag}`. */ -template type_index make_type_index() noexcept { +template +type_index make_type_index() noexcept { return tag; } @@ -111,7 +117,8 @@ inline bool operator>=(type_index const& a, type_index const& b) noexcept { } } -template <> struct std::hash { +template <> +struct std::hash { using argument_type = ext::type_index; using result_type = std::size_t; diff --git a/lib/app-scened-template/view_controller.hpp b/lib/app-scened-template/view_controller.hpp index 15028f533f9..8c48fcf747a 100644 --- a/lib/app-scened-template/view_controller.hpp +++ b/lib/app-scened-template/view_controller.hpp @@ -12,7 +12,8 @@ * @tparam TApp application class * @tparam TViewModules variadic list of ViewModules */ -template class ViewController { +template +class ViewController { public: ViewController() { event_queue = furi_message_queue_alloc(10, sizeof(typename TApp::Event)); @@ -44,7 +45,8 @@ template class ViewController { * @tparam T Concrete ViewModule class * @return T* ViewModule pointer */ - template T* get() { + template + T* get() { uint32_t view_index = ext::make_type_index().hash_code(); furi_check(holder.count(view_index) != 0); return static_cast(holder[view_index]); @@ -56,7 +58,8 @@ template class ViewController { * @tparam T Concrete ViewModule class * @return T* ViewModule pointer */ - template operator T*() { + template + operator T*() { uint32_t view_index = ext::make_type_index().hash_code(); furi_check(holder.count(view_index) != 0); return static_cast(holder[view_index]); @@ -68,7 +71,8 @@ template class ViewController { * @tparam T Concrete ViewModule class * @return T* ViewModule pointer */ - template void switch_to() { + template + void switch_to() { uint32_t view_index = ext::make_type_index().hash_code(); furi_check(holder.count(view_index) != 0); view_dispatcher_switch_to_view(view_dispatcher, view_index); diff --git a/lib/cxxheaderparser b/lib/cxxheaderparser new file mode 160000 index 00000000000..ba4222560fc --- /dev/null +++ b/lib/cxxheaderparser @@ -0,0 +1 @@ +Subproject commit ba4222560fc1040670b1a917d5d357198e8ec5d6 diff --git a/lib/digital_signal/digital_signal.h b/lib/digital_signal/digital_signal.h index d828444ca18..90905d74b79 100644 --- a/lib/digital_signal/digital_signal.h +++ b/lib/digital_signal/digital_signal.h @@ -6,6 +6,10 @@ #include +#ifdef __cplusplus +extern "C" { +#endif + typedef struct { bool start_level; uint32_t edge_cnt; @@ -29,3 +33,7 @@ uint32_t digital_signal_get_edges_cnt(DigitalSignal* signal); uint32_t digital_signal_get_edge(DigitalSignal* signal, uint32_t edge_num); void digital_signal_send(DigitalSignal* signal, const GpioPin* gpio); + +#ifdef __cplusplus +} +#endif diff --git a/lib/flipper_application/SConscript b/lib/flipper_application/SConscript new file mode 100644 index 00000000000..3a5e7f4db74 --- /dev/null +++ b/lib/flipper_application/SConscript @@ -0,0 +1,20 @@ +Import("env") + +env.Append( + CPPPATH=[ + "#/lib/flipper_application", + ], + SDK_HEADERS=[ + File("#/lib/flipper_application/flipper_application.h"), + ], +) + + +libenv = env.Clone(FW_LIB_NAME="flipper_application") +libenv.ApplyLibFlags() + +sources = libenv.GlobRecursive("*.c") + +lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources) +libenv.Install("${LIB_DIST_DIR}", lib) +Return("lib") diff --git a/lib/flipper_application/application_manifest.h b/lib/flipper_application/application_manifest.h new file mode 100644 index 00000000000..6aa20e48187 --- /dev/null +++ b/lib/flipper_application/application_manifest.h @@ -0,0 +1,45 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define FAP_MANIFEST_MAGIC 0x52474448 +#define FAP_MANIFEST_SUPPORTED_VERSION 1 + +#define FAP_MANIFEST_MAX_APP_NAME_LENGTH 32 +#define FAP_MANIFEST_MAX_ICON_SIZE 32 // TODO: reduce size? + +#pragma pack(push, 1) + +typedef struct { + uint32_t manifest_magic; + uint32_t manifest_version; + union { + struct { + uint16_t minor; + uint16_t major; + }; + uint32_t version; + } api_version; + uint16_t hardware_target_id; +} FlipperApplicationManifestBase; + +typedef struct { + FlipperApplicationManifestBase base; + uint16_t stack_size; + uint32_t app_version; + char name[FAP_MANIFEST_MAX_APP_NAME_LENGTH]; + char has_icon; + char icon[FAP_MANIFEST_MAX_ICON_SIZE]; +} FlipperApplicationManifestV1; + +typedef FlipperApplicationManifestV1 FlipperApplicationManifest; + +#pragma pack(pop) + +#ifdef __cplusplus +} +#endif diff --git a/lib/flipper_application/elf/elf.h b/lib/flipper_application/elf/elf.h new file mode 100644 index 00000000000..f1697ba484c --- /dev/null +++ b/lib/flipper_application/elf/elf.h @@ -0,0 +1,1148 @@ +#ifndef _ELF_H +#define _ELF_H 1 + +/* Standard ELF types. */ + +#include + +/* Type for a 16-bit quantity. */ +typedef uint16_t Elf32_Half; +typedef uint16_t Elf64_Half; + +/* Types for signed and unsigned 32-bit quantities. */ +typedef uint32_t Elf32_Word; +typedef int32_t Elf32_Sword; +typedef uint32_t Elf64_Word; +typedef int32_t Elf64_Sword; + +/* Types for signed and unsigned 64-bit quantities. */ +typedef uint64_t Elf32_Xword; +typedef int64_t Elf32_Sxword; +typedef uint64_t Elf64_Xword; +typedef int64_t Elf64_Sxword; + +/* Type of addresses. */ +typedef uint32_t Elf32_Addr; +typedef uint64_t Elf64_Addr; + +/* Type of file offsets. */ +typedef uint32_t Elf32_Off; +typedef uint64_t Elf64_Off; + +/* Type for section indices, which are 16-bit quantities. */ +typedef uint16_t Elf32_Section; +typedef uint16_t Elf64_Section; + +/* Type for version symbol information. */ +typedef Elf32_Half Elf32_Versym; +typedef Elf64_Half Elf64_Versym; + +/* The ELF file header. This appears at the start of every ELF file. */ + +#define EI_NIDENT (16) + +typedef struct { + unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */ + Elf32_Half e_type; /* Object file type */ + Elf32_Half e_machine; /* Architecture */ + Elf32_Word e_version; /* Object file version */ + Elf32_Addr e_entry; /* Entry point virtual address */ + Elf32_Off e_phoff; /* Program header table file offset */ + Elf32_Off e_shoff; /* Section header table file offset */ + Elf32_Word e_flags; /* Processor-specific flags */ + Elf32_Half e_ehsize; /* ELF header size in bytes */ + Elf32_Half e_phentsize; /* Program header table entry size */ + Elf32_Half e_phnum; /* Program header table entry count */ + Elf32_Half e_shentsize; /* Section header table entry size */ + Elf32_Half e_shnum; /* Section header table entry count */ + Elf32_Half e_shstrndx; /* Section header string table index */ +} Elf32_Ehdr; + +typedef struct { + unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */ + Elf64_Half e_type; /* Object file type */ + Elf64_Half e_machine; /* Architecture */ + Elf64_Word e_version; /* Object file version */ + Elf64_Addr e_entry; /* Entry point virtual address */ + Elf64_Off e_phoff; /* Program header table file offset */ + Elf64_Off e_shoff; /* Section header table file offset */ + Elf64_Word e_flags; /* Processor-specific flags */ + Elf64_Half e_ehsize; /* ELF header size in bytes */ + Elf64_Half e_phentsize; /* Program header table entry size */ + Elf64_Half e_phnum; /* Program header table entry count */ + Elf64_Half e_shentsize; /* Section header table entry size */ + Elf64_Half e_shnum; /* Section header table entry count */ + Elf64_Half e_shstrndx; /* Section header string table index */ +} Elf64_Ehdr; + +/* Fields in the e_ident array. The EI_* macros are indices into the + array. The macros under each EI_* macro are the values the byte + may have. */ + +#define EI_MAG0 0 /* File identification byte 0 index */ +#define ELFMAG0 0x7f /* Magic number byte 0 */ + +#define EI_MAG1 1 /* File identification byte 1 index */ +#define ELFMAG1 'E' /* Magic number byte 1 */ + +#define EI_MAG2 2 /* File identification byte 2 index */ +#define ELFMAG2 'L' /* Magic number byte 2 */ + +#define EI_MAG3 3 /* File identification byte 3 index */ +#define ELFMAG3 'F' /* Magic number byte 3 */ + +/* Conglomeration of the identification bytes, for easy testing as a word. */ +#define ELFMAG "\177ELF" +#define SELFMAG 4 + +#define EI_CLASS 4 /* File class byte index */ +#define ELFCLASSNONE 0 /* Invalid class */ +#define ELFCLASS32 1 /* 32-bit objects */ +#define ELFCLASS64 2 /* 64-bit objects */ +#define ELFCLASSNUM 3 + +#define EI_DATA 5 /* Data encoding byte index */ +#define ELFDATANONE 0 /* Invalid data encoding */ +#define ELFDATA2LSB 1 /* 2's complement, little endian */ +#define ELFDATA2MSB 2 /* 2's complement, big endian */ +#define ELFDATANUM 3 + +#define EI_VERSION 6 /* File version byte index */ +/* Value must be EV_CURRENT */ + +#define EI_OSABI 7 /* OS ABI identification */ +#define ELFOSABI_NONE 0 /* UNIX System V ABI */ +#define ELFOSABI_SYSV 0 /* Alias. */ +#define ELFOSABI_HPUX 1 /* HP-UX */ +#define ELFOSABI_NETBSD 2 /* NetBSD. */ +#define ELFOSABI_LINUX 3 /* Linux. */ +#define ELFOSABI_SOLARIS 6 /* Sun Solaris. */ +#define ELFOSABI_AIX 7 /* IBM AIX. */ +#define ELFOSABI_IRIX 8 /* SGI Irix. */ +#define ELFOSABI_FREEBSD 9 /* FreeBSD. */ +#define ELFOSABI_TRU64 10 /* Compaq TRU64 UNIX. */ +#define ELFOSABI_MODESTO 11 /* Novell Modesto. */ +#define ELFOSABI_OPENBSD 12 /* OpenBSD. */ +#define ELFOSABI_ARM 97 /* ARM */ +#define ELFOSABI_STANDALONE 255 /* Standalone (embedded) application */ + +#define EI_ABIVERSION 8 /* ABI version */ + +#define EI_PAD 9 /* Byte index of padding bytes */ + +/* Legal values for e_type (object file type). */ + +#define ET_NONE 0 /* No file type */ +#define ET_REL 1 /* Relocatable file */ +#define ET_EXEC 2 /* Executable file */ +#define ET_DYN 3 /* Shared object file */ +#define ET_CORE 4 /* Core file */ +#define ET_NUM 5 /* Number of defined types */ +#define ET_LOOS 0xfe00 /* OS-specific range start */ +#define ET_HIOS 0xfeff /* OS-specific range end */ +#define ET_LOPROC 0xff00 /* Processor-specific range start */ +#define ET_HIPROC 0xffff /* Processor-specific range end */ + +/* Legal values for e_machine (architecture). */ + +#define EM_NONE 0 /* No machine */ +#define EM_M32 1 /* AT&T WE 32100 */ +#define EM_SPARC 2 /* SUN SPARC */ +#define EM_386 3 /* Intel 80386 */ +#define EM_68K 4 /* Motorola m68k family */ +#define EM_88K 5 /* Motorola m88k family */ +#define EM_860 7 /* Intel 80860 */ +#define EM_MIPS 8 /* MIPS R3000 big-endian */ +#define EM_S370 9 /* IBM System/370 */ +#define EM_MIPS_RS3_LE 10 /* MIPS R3000 little-endian */ + +#define EM_PARISC 15 /* HPPA */ +#define EM_VPP500 17 /* Fujitsu VPP500 */ +#define EM_SPARC32PLUS 18 /* Sun's "v8plus" */ +#define EM_960 19 /* Intel 80960 */ +#define EM_PPC 20 /* PowerPC */ +#define EM_PPC64 21 /* PowerPC 64-bit */ +#define EM_S390 22 /* IBM S390 */ + +#define EM_V800 36 /* NEC V800 series */ +#define EM_FR20 37 /* Fujitsu FR20 */ +#define EM_RH32 38 /* TRW RH-32 */ +#define EM_RCE 39 /* Motorola RCE */ +#define EM_ARM 40 /* ARM */ +#define EM_FAKE_ALPHA 41 /* Digital Alpha */ +#define EM_SH 42 /* Hitachi SH */ +#define EM_SPARCV9 43 /* SPARC v9 64-bit */ +#define EM_TRICORE 44 /* Siemens Tricore */ +#define EM_ARC 45 /* Argonaut RISC Core */ +#define EM_H8_300 46 /* Hitachi H8/300 */ +#define EM_H8_300H 47 /* Hitachi H8/300H */ +#define EM_H8S 48 /* Hitachi H8S */ +#define EM_H8_500 49 /* Hitachi H8/500 */ +#define EM_IA_64 50 /* Intel Merced */ +#define EM_MIPS_X 51 /* Stanford MIPS-X */ +#define EM_COLDFIRE 52 /* Motorola Coldfire */ +#define EM_68HC12 53 /* Motorola M68HC12 */ +#define EM_MMA 54 /* Fujitsu MMA Multimedia Accelerator*/ +#define EM_PCP 55 /* Siemens PCP */ +#define EM_NCPU 56 /* Sony nCPU embeeded RISC */ +#define EM_NDR1 57 /* Denso NDR1 microprocessor */ +#define EM_STARCORE 58 /* Motorola Start*Core processor */ +#define EM_ME16 59 /* Toyota ME16 processor */ +#define EM_ST100 60 /* STMicroelectronic ST100 processor */ +#define EM_TINYJ 61 /* Advanced Logic Corp. Tinyj emb.fam*/ +#define EM_X86_64 62 /* AMD x86-64 architecture */ +#define EM_PDSP 63 /* Sony DSP Processor */ + +#define EM_FX66 66 /* Siemens FX66 microcontroller */ +#define EM_ST9PLUS 67 /* STMicroelectronics ST9+ 8/16 mc */ +#define EM_ST7 68 /* STmicroelectronics ST7 8 bit mc */ +#define EM_68HC16 69 /* Motorola MC68HC16 microcontroller */ +#define EM_68HC11 70 /* Motorola MC68HC11 microcontroller */ +#define EM_68HC08 71 /* Motorola MC68HC08 microcontroller */ +#define EM_68HC05 72 /* Motorola MC68HC05 microcontroller */ +#define EM_SVX 73 /* Silicon Graphics SVx */ +#define EM_ST19 74 /* STMicroelectronics ST19 8 bit mc */ +#define EM_VAX 75 /* Digital VAX */ +#define EM_CRIS 76 /* Axis Communications 32-bit embedded processor */ +#define EM_JAVELIN 77 /* Infineon Technologies 32-bit embedded processor */ +#define EM_FIREPATH 78 /* Element 14 64-bit DSP Processor */ +#define EM_ZSP 79 /* LSI Logic 16-bit DSP Processor */ +#define EM_MMIX 80 /* Donald Knuth's educational 64-bit processor */ +#define EM_HUANY 81 /* Harvard University machine-independent object files */ +#define EM_PRISM 82 /* SiTera Prism */ +#define EM_AVR 83 /* Atmel AVR 8-bit microcontroller */ +#define EM_FR30 84 /* Fujitsu FR30 */ +#define EM_D10V 85 /* Mitsubishi D10V */ +#define EM_D30V 86 /* Mitsubishi D30V */ +#define EM_V850 87 /* NEC v850 */ +#define EM_M32R 88 /* Mitsubishi M32R */ +#define EM_MN10300 89 /* Matsushita MN10300 */ +#define EM_MN10200 90 /* Matsushita MN10200 */ +#define EM_PJ 91 /* picoJava */ +#define EM_OPENRISC 92 /* OpenRISC 32-bit embedded processor */ +#define EM_ARC_A5 93 /* ARC Cores Tangent-A5 */ +#define EM_XTENSA 94 /* Tensilica Xtensa Architecture */ +#define EM_NUM 95 + +/* Legal values for e_version (version). */ + +#define EV_NONE 0 /* Invalid ELF version */ +#define EV_CURRENT 1 /* Current version */ +#define EV_NUM 2 + +/* Section header. */ + +typedef struct { + Elf32_Word sh_name; /* Section name (string tbl index) */ + Elf32_Word sh_type; /* Section type */ + Elf32_Word sh_flags; /* Section flags */ + Elf32_Addr sh_addr; /* Section virtual addr at execution */ + Elf32_Off sh_offset; /* Section file offset */ + Elf32_Word sh_size; /* Section size in bytes */ + Elf32_Word sh_link; /* Link to another section */ + Elf32_Word sh_info; /* Additional section information */ + Elf32_Word sh_addralign; /* Section alignment */ + Elf32_Word sh_entsize; /* Entry size if section holds table */ +} Elf32_Shdr; + +typedef struct { + Elf64_Word sh_name; /* Section name (string tbl index) */ + Elf64_Word sh_type; /* Section type */ + Elf64_Xword sh_flags; /* Section flags */ + Elf64_Addr sh_addr; /* Section virtual addr at execution */ + Elf64_Off sh_offset; /* Section file offset */ + Elf64_Xword sh_size; /* Section size in bytes */ + Elf64_Word sh_link; /* Link to another section */ + Elf64_Word sh_info; /* Additional section information */ + Elf64_Xword sh_addralign; /* Section alignment */ + Elf64_Xword sh_entsize; /* Entry size if section holds table */ +} Elf64_Shdr; + +/* Special section indices. */ + +#define SHN_UNDEF 0 /* Undefined section */ +#define SHN_LORESERVE 0xff00 /* Start of reserved indices */ +#define SHN_LOPROC 0xff00 /* Start of processor-specific */ +#define SHN_BEFORE \ + 0xff00 /* Order section before all others + (Solaris). */ +#define SHN_AFTER \ + 0xff01 /* Order section after all others + (Solaris). */ +#define SHN_HIPROC 0xff1f /* End of processor-specific */ +#define SHN_LOOS 0xff20 /* Start of OS-specific */ +#define SHN_HIOS 0xff3f /* End of OS-specific */ +#define SHN_ABS 0xfff1 /* Associated symbol is absolute */ +#define SHN_COMMON 0xfff2 /* Associated symbol is common */ +#define SHN_XINDEX 0xffff /* Index is in extra table. */ +#define SHN_HIRESERVE 0xffff /* End of reserved indices */ + +/* Legal values for sh_type (section type). */ + +#define SHT_NULL 0 /* Section header table entry unused */ +#define SHT_PROGBITS 1 /* Program data */ +#define SHT_SYMTAB 2 /* Symbol table */ +#define SHT_STRTAB 3 /* String table */ +#define SHT_RELA 4 /* Relocation entries with addends */ +#define SHT_HASH 5 /* Symbol hash table */ +#define SHT_DYNAMIC 6 /* Dynamic linking information */ +#define SHT_NOTE 7 /* Notes */ +#define SHT_NOBITS 8 /* Program space with no data (bss) */ +#define SHT_REL 9 /* Relocation entries, no addends */ +#define SHT_SHLIB 10 /* Reserved */ +#define SHT_DYNSYM 11 /* Dynamic linker symbol table */ +#define SHT_INIT_ARRAY 14 /* Array of constructors */ +#define SHT_FINI_ARRAY 15 /* Array of destructors */ +#define SHT_PREINIT_ARRAY 16 /* Array of pre-constructors */ +#define SHT_GROUP 17 /* Section group */ +#define SHT_SYMTAB_SHNDX 18 /* Extended section indeces */ +#define SHT_NUM 19 /* Number of defined types. */ +#define SHT_LOOS 0x60000000 /* Start OS-specific. */ +#define SHT_GNU_ATTRIBUTES 0x6ffffff5 /* Object attributes. */ +#define SHT_GNU_HASH 0x6ffffff6 /* GNU-style hash table. */ +#define SHT_GNU_LIBLIST 0x6ffffff7 /* Prelink library list */ +#define SHT_CHECKSUM 0x6ffffff8 /* Checksum for DSO content. */ +#define SHT_LOSUNW 0x6ffffffa /* Sun-specific low bound. */ +#define SHT_SUNW_move 0x6ffffffa +#define SHT_SUNW_COMDAT 0x6ffffffb +#define SHT_SUNW_syminfo 0x6ffffffc +#define SHT_GNU_verdef 0x6ffffffd /* Version definition section. */ +#define SHT_GNU_verneed 0x6ffffffe /* Version needs section. */ +#define SHT_GNU_versym 0x6fffffff /* Version symbol table. */ +#define SHT_HISUNW 0x6fffffff /* Sun-specific high bound. */ +#define SHT_HIOS 0x6fffffff /* End OS-specific type */ +#define SHT_LOPROC 0x70000000 /* Start of processor-specific */ +#define SHT_HIPROC 0x7fffffff /* End of processor-specific */ +#define SHT_LOUSER 0x80000000 /* Start of application-specific */ +#define SHT_HIUSER 0x8fffffff /* End of application-specific */ + +/* Legal values for sh_flags (section flags). */ + +#define SHF_WRITE (1 << 0) /* Writable */ +#define SHF_ALLOC (1 << 1) /* Occupies memory during execution */ +#define SHF_EXECINSTR (1 << 2) /* Executable */ +#define SHF_MERGE (1 << 4) /* Might be merged */ +#define SHF_STRINGS (1 << 5) /* Contains nul-terminated strings */ +#define SHF_INFO_LINK (1 << 6) /* `sh_info' contains SHT index */ +#define SHF_LINK_ORDER (1 << 7) /* Preserve order after combining */ +#define SHF_OS_NONCONFORMING \ + (1 << 8) /* Non-standard OS specific handling + required */ +#define SHF_GROUP (1 << 9) /* Section is member of a group. */ +#define SHF_TLS (1 << 10) /* Section hold thread-local data. */ +#define SHF_MASKOS 0x0ff00000 /* OS-specific. */ +#define SHF_MASKPROC 0xf0000000 /* Processor-specific */ +#define SHF_ORDERED \ + (1 << 30) /* Special ordering requirement + (Solaris). */ +#define SHF_EXCLUDE \ + (1 << 31) /* Section is excluded unless + referenced or allocated (Solaris).*/ + +/* Section group handling. */ +#define GRP_COMDAT 0x1 /* Mark group as COMDAT. */ + +/* Symbol table entry. */ + +typedef struct { + Elf32_Word st_name; /* Symbol name (string tbl index) */ + Elf32_Addr st_value; /* Symbol value */ + Elf32_Word st_size; /* Symbol size */ + unsigned char st_info; /* Symbol type and binding */ + unsigned char st_other; /* Symbol visibility */ + Elf32_Section st_shndx; /* Section index */ +} Elf32_Sym; + +typedef struct { + Elf64_Word st_name; /* Symbol name (string tbl index) */ + unsigned char st_info; /* Symbol type and binding */ + unsigned char st_other; /* Symbol visibility */ + Elf64_Section st_shndx; /* Section index */ + Elf64_Addr st_value; /* Symbol value */ + Elf64_Xword st_size; /* Symbol size */ +} Elf64_Sym; + +/* The syminfo section if available contains additional information about + every dynamic symbol. */ + +typedef struct { + Elf32_Half si_boundto; /* Direct bindings, symbol bound to */ + Elf32_Half si_flags; /* Per symbol flags */ +} Elf32_Syminfo; + +typedef struct { + Elf64_Half si_boundto; /* Direct bindings, symbol bound to */ + Elf64_Half si_flags; /* Per symbol flags */ +} Elf64_Syminfo; + +/* Possible values for si_boundto. */ +#define SYMINFO_BT_SELF 0xffff /* Symbol bound to self */ +#define SYMINFO_BT_PARENT 0xfffe /* Symbol bound to parent */ +#define SYMINFO_BT_LOWRESERVE 0xff00 /* Beginning of reserved entries */ + +/* Possible bitmasks for si_flags. */ +#define SYMINFO_FLG_DIRECT 0x0001 /* Direct bound symbol */ +#define SYMINFO_FLG_PASSTHRU 0x0002 /* Pass-thru symbol for translator */ +#define SYMINFO_FLG_COPY 0x0004 /* Symbol is a copy-reloc */ +#define SYMINFO_FLG_LAZYLOAD \ + 0x0008 /* Symbol bound to object to be lazy + loaded */ +/* Syminfo version values. */ +#define SYMINFO_NONE 0 +#define SYMINFO_CURRENT 1 +#define SYMINFO_NUM 2 + +/* How to extract and insert information held in the st_info field. */ + +#define ELF32_ST_BIND(val) (((unsigned char)(val)) >> 4) +#define ELF32_ST_TYPE(val) ((val)&0xf) +#define ELF32_ST_INFO(bind, type) (((bind) << 4) + ((type)&0xf)) + +/* Both Elf32_Sym and Elf64_Sym use the same one-byte st_info field. */ +#define ELF64_ST_BIND(val) ELF32_ST_BIND(val) +#define ELF64_ST_TYPE(val) ELF32_ST_TYPE(val) +#define ELF64_ST_INFO(bind, type) ELF32_ST_INFO((bind), (type)) + +/* Legal values for ST_BIND subfield of st_info (symbol binding). */ + +#define STB_LOCAL 0 /* Local symbol */ +#define STB_GLOBAL 1 /* Global symbol */ +#define STB_WEAK 2 /* Weak symbol */ +#define STB_NUM 3 /* Number of defined types. */ +#define STB_LOOS 10 /* Start of OS-specific */ +#define STB_GNU_UNIQUE 10 /* Unique symbol. */ +#define STB_HIOS 12 /* End of OS-specific */ +#define STB_LOPROC 13 /* Start of processor-specific */ +#define STB_HIPROC 15 /* End of processor-specific */ + +/* Legal values for ST_TYPE subfield of st_info (symbol type). */ + +#define STT_NOTYPE 0 /* Symbol type is unspecified */ +#define STT_OBJECT 1 /* Symbol is a data object */ +#define STT_FUNC 2 /* Symbol is a code object */ +#define STT_SECTION 3 /* Symbol associated with a section */ +#define STT_FILE 4 /* Symbol's name is file name */ +#define STT_COMMON 5 /* Symbol is a common data object */ +#define STT_TLS 6 /* Symbol is thread-local data object*/ +#define STT_NUM 7 /* Number of defined types. */ +#define STT_LOOS 10 /* Start of OS-specific */ +#define STT_GNU_IFUNC 10 /* Symbol is indirect code object */ +#define STT_HIOS 12 /* End of OS-specific */ +#define STT_LOPROC 13 /* Start of processor-specific */ +#define STT_HIPROC 15 /* End of processor-specific */ + +/* Symbol table indices are found in the hash buckets and chain table + of a symbol hash table section. This special index value indicates + the end of a chain, meaning no further symbols are found in that bucket. */ + +#define STN_UNDEF 0 /* End of a chain. */ + +/* How to extract and insert information held in the st_other field. */ + +#define ELF32_ST_VISIBILITY(o) ((o)&0x03) + +/* For ELF64 the definitions are the same. */ +#define ELF64_ST_VISIBILITY(o) ELF32_ST_VISIBILITY(o) + +/* Symbol visibility specification encoded in the st_other field. */ +#define STV_DEFAULT 0 /* Default symbol visibility rules */ +#define STV_INTERNAL 1 /* Processor specific hidden class */ +#define STV_HIDDEN 2 /* Sym unavailable in other modules */ +#define STV_PROTECTED 3 /* Not preemptible, not exported */ + +/* Relocation table entry without addend (in section of type SHT_REL). */ + +typedef struct { + Elf32_Addr r_offset; /* Address */ + Elf32_Word r_info; /* Relocation type and symbol index */ +} Elf32_Rel; + +/* I have seen two different definitions of the Elf64_Rel and + Elf64_Rela structures, so we'll leave them out until Novell (or + whoever) gets their act together. */ +/* The following, at least, is used on Sparc v9, MIPS, and Alpha. */ + +typedef struct { + Elf64_Addr r_offset; /* Address */ + Elf64_Xword r_info; /* Relocation type and symbol index */ +} Elf64_Rel; + +/* Relocation table entry with addend (in section of type SHT_RELA). */ + +typedef struct { + Elf32_Addr r_offset; /* Address */ + Elf32_Word r_info; /* Relocation type and symbol index */ + Elf32_Sword r_addend; /* Addend */ +} Elf32_Rela; + +typedef struct { + Elf64_Addr r_offset; /* Address */ + Elf64_Xword r_info; /* Relocation type and symbol index */ + Elf64_Sxword r_addend; /* Addend */ +} Elf64_Rela; + +/* How to extract and insert information held in the r_info field. */ + +#define ELF32_R_SYM(val) ((val) >> 8) +#define ELF32_R_TYPE(val) ((val)&0xff) +#define ELF32_R_INFO(sym, type) (((sym) << 8) + ((type)&0xff)) + +#define ELF64_R_SYM(i) ((i) >> 32) +#define ELF64_R_TYPE(i) ((i)&0xffffffff) +#define ELF64_R_INFO(sym, type) ((((Elf64_Xword)(sym)) << 32) + (type)) + +/* Program segment header. */ + +typedef struct { + Elf32_Word p_type; /* Segment type */ + Elf32_Off p_offset; /* Segment file offset */ + Elf32_Addr p_vaddr; /* Segment virtual address */ + Elf32_Addr p_paddr; /* Segment physical address */ + Elf32_Word p_filesz; /* Segment size in file */ + Elf32_Word p_memsz; /* Segment size in memory */ + Elf32_Word p_flags; /* Segment flags */ + Elf32_Word p_align; /* Segment alignment */ +} Elf32_Phdr; + +typedef struct { + Elf64_Word p_type; /* Segment type */ + Elf64_Word p_flags; /* Segment flags */ + Elf64_Off p_offset; /* Segment file offset */ + Elf64_Addr p_vaddr; /* Segment virtual address */ + Elf64_Addr p_paddr; /* Segment physical address */ + Elf64_Xword p_filesz; /* Segment size in file */ + Elf64_Xword p_memsz; /* Segment size in memory */ + Elf64_Xword p_align; /* Segment alignment */ +} Elf64_Phdr; + +/* Legal values for p_type (segment type). */ + +#define PT_NULL 0 /* Program header table entry unused */ +#define PT_LOAD 1 /* Loadable program segment */ +#define PT_DYNAMIC 2 /* Dynamic linking information */ +#define PT_INTERP 3 /* Program interpreter */ +#define PT_NOTE 4 /* Auxiliary information */ +#define PT_SHLIB 5 /* Reserved */ +#define PT_PHDR 6 /* Entry for header table itself */ +#define PT_TLS 7 /* Thread-local storage segment */ +#define PT_NUM 8 /* Number of defined types */ +#define PT_LOOS 0x60000000 /* Start of OS-specific */ +#define PT_GNU_EH_FRAME 0x6474e550 /* GCC .eh_frame_hdr segment */ +#define PT_GNU_STACK 0x6474e551 /* Indicates stack executability */ +#define PT_GNU_RELRO 0x6474e552 /* Read-only after relocation */ +#define PT_LOSUNW 0x6ffffffa +#define PT_SUNWBSS 0x6ffffffa /* Sun Specific segment */ +#define PT_SUNWSTACK 0x6ffffffb /* Stack segment */ +#define PT_HISUNW 0x6fffffff +#define PT_HIOS 0x6fffffff /* End of OS-specific */ +#define PT_LOPROC 0x70000000 /* Start of processor-specific */ +#define PT_HIPROC 0x7fffffff /* End of processor-specific */ + +/* Legal values for p_flags (segment flags). */ + +#define PF_X (1 << 0) /* Segment is executable */ +#define PF_W (1 << 1) /* Segment is writable */ +#define PF_R (1 << 2) /* Segment is readable */ +#define PF_MASKOS 0x0ff00000 /* OS-specific */ +#define PF_MASKPROC 0xf0000000 /* Processor-specific */ + +/* Legal values for note segment descriptor types for core files. */ + +#define NT_PRSTATUS 1 /* Contains copy of prstatus struct */ +#define NT_FPREGSET 2 /* Contains copy of fpregset struct */ +#define NT_PRPSINFO 3 /* Contains copy of prpsinfo struct */ +#define NT_PRXREG 4 /* Contains copy of prxregset struct */ +#define NT_TASKSTRUCT 4 /* Contains copy of task structure */ +#define NT_PLATFORM 5 /* String from sysinfo(SI_PLATFORM) */ +#define NT_AUXV 6 /* Contains copy of auxv array */ +#define NT_GWINDOWS 7 /* Contains copy of gwindows struct */ +#define NT_ASRS 8 /* Contains copy of asrset struct */ +#define NT_PSTATUS 10 /* Contains copy of pstatus struct */ +#define NT_PSINFO 13 /* Contains copy of psinfo struct */ +#define NT_PRCRED 14 /* Contains copy of prcred struct */ +#define NT_UTSNAME 15 /* Contains copy of utsname struct */ +#define NT_LWPSTATUS 16 /* Contains copy of lwpstatus struct */ +#define NT_LWPSINFO 17 /* Contains copy of lwpinfo struct */ +#define NT_PRFPXREG 20 /* Contains copy of fprxregset struct */ +#define NT_PRXFPREG 0x46e62b7f /* Contains copy of user_fxsr_struct */ +#define NT_PPC_VMX 0x100 /* PowerPC Altivec/VMX registers */ +#define NT_PPC_SPE 0x101 /* PowerPC SPE/EVR registers */ +#define NT_PPC_VSX 0x102 /* PowerPC VSX registers */ +#define NT_386_TLS 0x200 /* i386 TLS slots (struct user_desc) */ +#define NT_386_IOPERM 0x201 /* x86 io permission bitmap (1=deny) */ + +/* Legal values for the note segment descriptor types for object files. */ + +#define NT_VERSION 1 /* Contains a version string. */ + +/* Dynamic section entry. */ + +typedef struct { + Elf32_Sword d_tag; /* Dynamic entry type */ + union { + Elf32_Word d_val; /* Integer value */ + Elf32_Addr d_ptr; /* Address value */ + } d_un; +} Elf32_Dyn; + +typedef struct { + Elf64_Sxword d_tag; /* Dynamic entry type */ + union { + Elf64_Xword d_val; /* Integer value */ + Elf64_Addr d_ptr; /* Address value */ + } d_un; +} Elf64_Dyn; + +/* Legal values for d_tag (dynamic entry type). */ + +#define DT_NULL 0 /* Marks end of dynamic section */ +#define DT_NEEDED 1 /* Name of needed library */ +#define DT_PLTRELSZ 2 /* Size in bytes of PLT relocs */ +#define DT_PLTGOT 3 /* Processor defined value */ +#define DT_HASH 4 /* Address of symbol hash table */ +#define DT_STRTAB 5 /* Address of string table */ +#define DT_SYMTAB 6 /* Address of symbol table */ +#define DT_RELA 7 /* Address of Rela relocs */ +#define DT_RELASZ 8 /* Total size of Rela relocs */ +#define DT_RELAENT 9 /* Size of one Rela reloc */ +#define DT_STRSZ 10 /* Size of string table */ +#define DT_SYMENT 11 /* Size of one symbol table entry */ +#define DT_INIT 12 /* Address of init function */ +#define DT_FINI 13 /* Address of termination function */ +#define DT_SONAME 14 /* Name of shared object */ +#define DT_RPATH 15 /* Library search path (deprecated) */ +#define DT_SYMBOLIC 16 /* Start symbol search here */ +#define DT_REL 17 /* Address of Rel relocs */ +#define DT_RELSZ 18 /* Total size of Rel relocs */ +#define DT_RELENT 19 /* Size of one Rel reloc */ +#define DT_PLTREL 20 /* Type of reloc in PLT */ +#define DT_DEBUG 21 /* For debugging; unspecified */ +#define DT_TEXTREL 22 /* Reloc might modify .text */ +#define DT_JMPREL 23 /* Address of PLT relocs */ +#define DT_BIND_NOW 24 /* Process relocations of object */ +#define DT_INIT_ARRAY 25 /* Array with addresses of init fct */ +#define DT_FINI_ARRAY 26 /* Array with addresses of fini fct */ +#define DT_INIT_ARRAYSZ 27 /* Size in bytes of DT_INIT_ARRAY */ +#define DT_FINI_ARRAYSZ 28 /* Size in bytes of DT_FINI_ARRAY */ +#define DT_RUNPATH 29 /* Library search path */ +#define DT_FLAGS 30 /* Flags for the object being loaded */ +#define DT_ENCODING 32 /* Start of encoded range */ +#define DT_PREINIT_ARRAY 32 /* Array with addresses of preinit fct*/ +#define DT_PREINIT_ARRAYSZ 33 /* size in bytes of DT_PREINIT_ARRAY */ +#define DT_NUM 34 /* Number used */ +#define DT_LOOS 0x6000000d /* Start of OS-specific */ +#define DT_HIOS 0x6ffff000 /* End of OS-specific */ +#define DT_LOPROC 0x70000000 /* Start of processor-specific */ +#define DT_HIPROC 0x7fffffff /* End of processor-specific */ +#define DT_PROCNUM DT_MIPS_NUM /* Most used by any processor */ + +/* DT_* entries which fall between DT_VALRNGHI & DT_VALRNGLO use the + Dyn.d_un.d_val field of the Elf*_Dyn structure. This follows Sun's + approach. */ +#define DT_VALRNGLO 0x6ffffd00 +#define DT_GNU_PRELINKED 0x6ffffdf5 /* Prelinking timestamp */ +#define DT_GNU_CONFLICTSZ 0x6ffffdf6 /* Size of conflict section */ +#define DT_GNU_LIBLISTSZ 0x6ffffdf7 /* Size of library list */ +#define DT_CHECKSUM 0x6ffffdf8 +#define DT_PLTPADSZ 0x6ffffdf9 +#define DT_MOVEENT 0x6ffffdfa +#define DT_MOVESZ 0x6ffffdfb +#define DT_FEATURE_1 0x6ffffdfc /* Feature selection (DTF_*). */ +#define DT_POSFLAG_1 \ + 0x6ffffdfd /* Flags for DT_* entries, effecting + the following DT_* entry. */ +#define DT_SYMINSZ 0x6ffffdfe /* Size of syminfo table (in bytes) */ +#define DT_SYMINENT 0x6ffffdff /* Entry size of syminfo */ +#define DT_VALRNGHI 0x6ffffdff +#define DT_VALTAGIDX(tag) (DT_VALRNGHI - (tag)) /* Reverse order! */ +#define DT_VALNUM 12 + +/* DT_* entries which fall between DT_ADDRRNGHI & DT_ADDRRNGLO use the + Dyn.d_un.d_ptr field of the Elf*_Dyn structure. + + If any adjustment is made to the ELF object after it has been + built these entries will need to be adjusted. */ +#define DT_ADDRRNGLO 0x6ffffe00 +#define DT_GNU_HASH 0x6ffffef5 /* GNU-style hash table. */ +#define DT_TLSDESC_PLT 0x6ffffef6 +#define DT_TLSDESC_GOT 0x6ffffef7 +#define DT_GNU_CONFLICT 0x6ffffef8 /* Start of conflict section */ +#define DT_GNU_LIBLIST 0x6ffffef9 /* Library list */ +#define DT_CONFIG 0x6ffffefa /* Configuration information. */ +#define DT_DEPAUDIT 0x6ffffefb /* Dependency auditing. */ +#define DT_AUDIT 0x6ffffefc /* Object auditing. */ +#define DT_PLTPAD 0x6ffffefd /* PLT padding. */ +#define DT_MOVETAB 0x6ffffefe /* Move table. */ +#define DT_SYMINFO 0x6ffffeff /* Syminfo table. */ +#define DT_ADDRRNGHI 0x6ffffeff +#define DT_ADDRTAGIDX(tag) (DT_ADDRRNGHI - (tag)) /* Reverse order! */ +#define DT_ADDRNUM 11 + +/* The versioning entry types. The next are defined as part of the + GNU extension. */ +#define DT_VERSYM 0x6ffffff0 + +#define DT_RELACOUNT 0x6ffffff9 +#define DT_RELCOUNT 0x6ffffffa + +/* These were chosen by Sun. */ +#define DT_FLAGS_1 0x6ffffffb /* State flags, see DF_1_* below. */ +#define DT_VERDEF \ + 0x6ffffffc /* Address of version definition + table */ +#define DT_VERDEFNUM 0x6ffffffd /* Number of version definitions */ +#define DT_VERNEED \ + 0x6ffffffe /* Address of table with needed + versions */ +#define DT_VERNEEDNUM 0x6fffffff /* Number of needed versions */ +#define DT_VERSIONTAGIDX(tag) (DT_VERNEEDNUM - (tag)) /* Reverse order! */ +#define DT_VERSIONTAGNUM 16 + +/* Sun added these machine-independent extensions in the "processor-specific" + range. Be compatible. */ +#define DT_AUXILIARY 0x7ffffffd /* Shared object to load before self */ +#define DT_FILTER 0x7fffffff /* Shared object to get values from */ +#define DT_EXTRATAGIDX(tag) ((Elf32_Word) - ((Elf32_Sword)(tag) << 1 >> 1) - 1) +#define DT_EXTRANUM 3 + +/* Values of `d_un.d_val' in the DT_FLAGS entry. */ +#define DF_ORIGIN 0x00000001 /* Object may use DF_ORIGIN */ +#define DF_SYMBOLIC 0x00000002 /* Symbol resolutions starts here */ +#define DF_TEXTREL 0x00000004 /* Object contains text relocations */ +#define DF_BIND_NOW 0x00000008 /* No lazy binding for this object */ +#define DF_STATIC_TLS 0x00000010 /* Module uses the static TLS model */ + +/* State flags selectable in the `d_un.d_val' element of the DT_FLAGS_1 + entry in the dynamic section. */ +#define DF_1_NOW 0x00000001 /* Set RTLD_NOW for this object. */ +#define DF_1_GLOBAL 0x00000002 /* Set RTLD_GLOBAL for this object. */ +#define DF_1_GROUP 0x00000004 /* Set RTLD_GROUP for this object. */ +#define DF_1_NODELETE 0x00000008 /* Set RTLD_NODELETE for this object.*/ +#define DF_1_LOADFLTR 0x00000010 /* Trigger filtee loading at runtime.*/ +#define DF_1_INITFIRST 0x00000020 /* Set RTLD_INITFIRST for this object*/ +#define DF_1_NOOPEN 0x00000040 /* Set RTLD_NOOPEN for this object. */ +#define DF_1_ORIGIN 0x00000080 /* $ORIGIN must be handled. */ +#define DF_1_DIRECT 0x00000100 /* Direct binding enabled. */ +#define DF_1_TRANS 0x00000200 +#define DF_1_INTERPOSE 0x00000400 /* Object is used to interpose. */ +#define DF_1_NODEFLIB 0x00000800 /* Ignore default lib search path. */ +#define DF_1_NODUMP 0x00001000 /* Object can't be dldump'ed. */ +#define DF_1_CONFALT 0x00002000 /* Configuration alternative created.*/ +#define DF_1_ENDFILTEE 0x00004000 /* Filtee terminates filters search. */ +#define DF_1_DISPRELDNE 0x00008000 /* Disp reloc applied at build time. */ +#define DF_1_DISPRELPND 0x00010000 /* Disp reloc applied at run-time. */ + +/* Flags for the feature selection in DT_FEATURE_1. */ +#define DTF_1_PARINIT 0x00000001 +#define DTF_1_CONFEXP 0x00000002 + +/* Flags in the DT_POSFLAG_1 entry effecting only the next DT_* entry. */ +#define DF_P1_LAZYLOAD 0x00000001 /* Lazyload following object. */ +#define DF_P1_GROUPPERM \ + 0x00000002 /* Symbols from next object are not + generally available. */ + +/* Version definition sections. */ + +typedef struct { + Elf32_Half vd_version; /* Version revision */ + Elf32_Half vd_flags; /* Version information */ + Elf32_Half vd_ndx; /* Version Index */ + Elf32_Half vd_cnt; /* Number of associated aux entries */ + Elf32_Word vd_hash; /* Version name hash value */ + Elf32_Word vd_aux; /* Offset in bytes to verdaux array */ + Elf32_Word vd_next; /* Offset in bytes to next verdef + entry */ +} Elf32_Verdef; + +typedef struct { + Elf64_Half vd_version; /* Version revision */ + Elf64_Half vd_flags; /* Version information */ + Elf64_Half vd_ndx; /* Version Index */ + Elf64_Half vd_cnt; /* Number of associated aux entries */ + Elf64_Word vd_hash; /* Version name hash value */ + Elf64_Word vd_aux; /* Offset in bytes to verdaux array */ + Elf64_Word vd_next; /* Offset in bytes to next verdef + entry */ +} Elf64_Verdef; + +/* Legal values for vd_version (version revision). */ +#define VER_DEF_NONE 0 /* No version */ +#define VER_DEF_CURRENT 1 /* Current version */ +#define VER_DEF_NUM 2 /* Given version number */ + +/* Legal values for vd_flags (version information flags). */ +#define VER_FLG_BASE 0x1 /* Version definition of file itself */ +#define VER_FLG_WEAK 0x2 /* Weak version identifier */ + +/* Versym symbol index values. */ +#define VER_NDX_LOCAL 0 /* Symbol is local. */ +#define VER_NDX_GLOBAL 1 /* Symbol is global. */ +#define VER_NDX_LORESERVE 0xff00 /* Beginning of reserved entries. */ +#define VER_NDX_ELIMINATE 0xff01 /* Symbol is to be eliminated. */ + +/* Auxialiary version information. */ + +typedef struct { + Elf32_Word vda_name; /* Version or dependency names */ + Elf32_Word vda_next; /* Offset in bytes to next verdaux + entry */ +} Elf32_Verdaux; + +typedef struct { + Elf64_Word vda_name; /* Version or dependency names */ + Elf64_Word vda_next; /* Offset in bytes to next verdaux + entry */ +} Elf64_Verdaux; + +/* Version dependency section. */ + +typedef struct { + Elf32_Half vn_version; /* Version of structure */ + Elf32_Half vn_cnt; /* Number of associated aux entries */ + Elf32_Word vn_file; /* Offset of filename for this + dependency */ + Elf32_Word vn_aux; /* Offset in bytes to vernaux array */ + Elf32_Word vn_next; /* Offset in bytes to next verneed + entry */ +} Elf32_Verneed; + +typedef struct { + Elf64_Half vn_version; /* Version of structure */ + Elf64_Half vn_cnt; /* Number of associated aux entries */ + Elf64_Word vn_file; /* Offset of filename for this + dependency */ + Elf64_Word vn_aux; /* Offset in bytes to vernaux array */ + Elf64_Word vn_next; /* Offset in bytes to next verneed + entry */ +} Elf64_Verneed; + +/* Legal values for vn_version (version revision). */ +#define VER_NEED_NONE 0 /* No version */ +#define VER_NEED_CURRENT 1 /* Current version */ +#define VER_NEED_NUM 2 /* Given version number */ + +/* Auxiliary needed version information. */ + +typedef struct { + Elf32_Word vna_hash; /* Hash value of dependency name */ + Elf32_Half vna_flags; /* Dependency specific information */ + Elf32_Half vna_other; /* Unused */ + Elf32_Word vna_name; /* Dependency name string offset */ + Elf32_Word vna_next; /* Offset in bytes to next vernaux + entry */ +} Elf32_Vernaux; + +typedef struct { + Elf64_Word vna_hash; /* Hash value of dependency name */ + Elf64_Half vna_flags; /* Dependency specific information */ + Elf64_Half vna_other; /* Unused */ + Elf64_Word vna_name; /* Dependency name string offset */ + Elf64_Word vna_next; /* Offset in bytes to next vernaux + entry */ +} Elf64_Vernaux; + +/* Legal values for vna_flags. */ +#define VER_FLG_WEAK 0x2 /* Weak version identifier */ + +/* Auxiliary vector. */ + +/* This vector is normally only used by the program interpreter. The + usual definition in an ABI supplement uses the name auxv_t. The + vector is not usually defined in a standard file, but it + can't hurt. We rename it to avoid conflicts. The sizes of these + types are an arrangement between the exec server and the program + interpreter, so we don't fully specify them here. */ + +typedef struct { + uint32_t a_type; /* Entry type */ + union { + uint32_t a_val; /* Integer value */ + /* We use to have pointer elements added here. We cannot do that, + though, since it does not work when using 32-bit definitions + on 64-bit platforms and vice versa. */ + } a_un; +} Elf32_auxv_t; + +typedef struct { + uint64_t a_type; /* Entry type */ + union { + uint64_t a_val; /* Integer value */ + /* We use to have pointer elements added here. We cannot do that, + though, since it does not work when using 32-bit definitions + on 64-bit platforms and vice versa. */ + } a_un; +} Elf64_auxv_t; + +/* Legal values for a_type (entry type). */ + +#define AT_NULL 0 /* End of vector */ +#define AT_IGNORE 1 /* Entry should be ignored */ +#define AT_EXECFD 2 /* File descriptor of program */ +#define AT_PHDR 3 /* Program headers for program */ +#define AT_PHENT 4 /* Size of program header entry */ +#define AT_PHNUM 5 /* Number of program headers */ +#define AT_PAGESZ 6 /* System page size */ +#define AT_BASE 7 /* Base address of interpreter */ +#define AT_FLAGS 8 /* Flags */ +#define AT_ENTRY 9 /* Entry point of program */ +#define AT_NOTELF 10 /* Program is not ELF */ +#define AT_UID 11 /* Real uid */ +#define AT_EUID 12 /* Effective uid */ +#define AT_GID 13 /* Real gid */ +#define AT_EGID 14 /* Effective gid */ +#define AT_CLKTCK 17 /* Frequency of times() */ + +/* Some more special a_type values describing the hardware. */ +#define AT_PLATFORM 15 /* String identifying platform. */ +#define AT_HWCAP \ + 16 /* Machine dependent hints about + processor capabilities. */ + +/* This entry gives some information about the FPU initialization + performed by the kernel. */ +#define AT_FPUCW 18 /* Used FPU control word. */ + +/* Cache block sizes. */ +#define AT_DCACHEBSIZE 19 /* Data cache block size. */ +#define AT_ICACHEBSIZE 20 /* Instruction cache block size. */ +#define AT_UCACHEBSIZE 21 /* Unified cache block size. */ + +/* A special ignored value for PPC, used by the kernel to control the + interpretation of the AUXV. Must be > 16. */ +#define AT_IGNOREPPC 22 /* Entry should be ignored. */ + +#define AT_SECURE 23 /* Boolean, was exec setuid-like? */ + +#define AT_BASE_PLATFORM 24 /* String identifying real platforms.*/ + +#define AT_RANDOM 25 /* Address of 16 random bytes. */ + +#define AT_EXECFN 31 /* Filename of executable. */ + +/* Pointer to the global system page used for system calls and other + nice things. */ +#define AT_SYSINFO 32 +#define AT_SYSINFO_EHDR 33 + +/* Shapes of the caches. Bits 0-3 contains associativity; bits 4-7 contains + log2 of line size; mask those to get cache size. */ +#define AT_L1I_CACHESHAPE 34 +#define AT_L1D_CACHESHAPE 35 +#define AT_L2_CACHESHAPE 36 +#define AT_L3_CACHESHAPE 37 + +/* Note section contents. Each entry in the note section begins with + a header of a fixed form. */ + +typedef struct { + Elf32_Word n_namesz; /* Length of the note's name. */ + Elf32_Word n_descsz; /* Length of the note's descriptor. */ + Elf32_Word n_type; /* Type of the note. */ +} Elf32_Nhdr; + +typedef struct { + Elf64_Word n_namesz; /* Length of the note's name. */ + Elf64_Word n_descsz; /* Length of the note's descriptor. */ + Elf64_Word n_type; /* Type of the note. */ +} Elf64_Nhdr; + +/* Known names of notes. */ + +/* Solaris entries in the note section have this name. */ +#define ELF_NOTE_SOLARIS "SUNW Solaris" + +/* Note entries for GNU systems have this name. */ +#define ELF_NOTE_GNU "GNU" + +/* Defined types of notes for Solaris. */ + +/* Value of descriptor (one word) is desired pagesize for the binary. */ +#define ELF_NOTE_PAGESIZE_HINT 1 + +/* Defined note types for GNU systems. */ + +/* ABI information. The descriptor consists of words: + word 0: OS descriptor + word 1: major version of the ABI + word 2: minor version of the ABI + word 3: subminor version of the ABI +*/ +#define NT_GNU_ABI_TAG 1 +#define ELF_NOTE_ABI NT_GNU_ABI_TAG /* Old name. */ + +/* Known OSes. These values can appear in word 0 of an + NT_GNU_ABI_TAG note section entry. */ +#define ELF_NOTE_OS_LINUX 0 +#define ELF_NOTE_OS_GNU 1 +#define ELF_NOTE_OS_SOLARIS2 2 +#define ELF_NOTE_OS_FREEBSD 3 + +/* Synthetic hwcap information. The descriptor begins with two words: + word 0: number of entries + word 1: bitmask of enabled entries + Then follow variable-length entries, one byte followed by a + '\0'-terminated hwcap name string. The byte gives the bit + number to test if enabled, (1U << bit) & bitmask. */ +#define NT_GNU_HWCAP 2 + +/* Build ID bits as generated by ld --build-id. + The descriptor consists of any nonzero number of bytes. */ +#define NT_GNU_BUILD_ID 3 + +/* Version note generated by GNU gold containing a version string. */ +#define NT_GNU_GOLD_VERSION 4 + +/* Move records. */ +typedef struct { + Elf32_Xword m_value; /* Symbol value. */ + Elf32_Word m_info; /* Size and index. */ + Elf32_Word m_poffset; /* Symbol offset. */ + Elf32_Half m_repeat; /* Repeat count. */ + Elf32_Half m_stride; /* Stride info. */ +} Elf32_Move; + +typedef struct { + Elf64_Xword m_value; /* Symbol value. */ + Elf64_Xword m_info; /* Size and index. */ + Elf64_Xword m_poffset; /* Symbol offset. */ + Elf64_Half m_repeat; /* Repeat count. */ + Elf64_Half m_stride; /* Stride info. */ +} Elf64_Move; + +/* Macro to construct move records. */ +#define ELF32_M_SYM(info) ((info) >> 8) +#define ELF32_M_SIZE(info) ((unsigned char)(info)) +#define ELF32_M_INFO(sym, size) (((sym) << 8) + (unsigned char)(size)) + +#define ELF64_M_SYM(info) ELF32_M_SYM(info) +#define ELF64_M_SIZE(info) ELF32_M_SIZE(info) +#define ELF64_M_INFO(sym, size) ELF32_M_INFO(sym, size) + +/* ARM specific declarations */ + +/* Processor specific flags for the ELF header e_flags field. */ +#define EF_ARM_RELEXEC 0x01 +#define EF_ARM_HASENTRY 0x02 +#define EF_ARM_INTERWORK 0x04 +#define EF_ARM_APCS_26 0x08 +#define EF_ARM_APCS_FLOAT 0x10 +#define EF_ARM_PIC 0x20 +#define EF_ARM_ALIGN8 0x40 /* 8-bit structure alignment is in use */ +#define EF_ARM_NEW_ABI 0x80 +#define EF_ARM_OLD_ABI 0x100 +#define EF_ARM_SOFT_FLOAT 0x200 +#define EF_ARM_VFP_FLOAT 0x400 +#define EF_ARM_MAVERICK_FLOAT 0x800 + +/* Other constants defined in the ARM ELF spec. version B-01. */ +/* NB. These conflict with values defined above. */ +#define EF_ARM_SYMSARESORTED 0x04 +#define EF_ARM_DYNSYMSUSESEGIDX 0x08 +#define EF_ARM_MAPSYMSFIRST 0x10 +#define EF_ARM_EABIMASK 0XFF000000 + +/* Constants defined in AAELF. */ +#define EF_ARM_BE8 0x00800000 +#define EF_ARM_LE8 0x00400000 + +#define EF_ARM_EABI_VERSION(flags) ((flags)&EF_ARM_EABIMASK) +#define EF_ARM_EABI_UNKNOWN 0x00000000 +#define EF_ARM_EABI_VER1 0x01000000 +#define EF_ARM_EABI_VER2 0x02000000 +#define EF_ARM_EABI_VER3 0x03000000 +#define EF_ARM_EABI_VER4 0x04000000 +#define EF_ARM_EABI_VER5 0x05000000 + +/* Additional symbol types for Thumb. */ +#define STT_ARM_TFUNC STT_LOPROC /* A Thumb function. */ +#define STT_ARM_16BIT STT_HIPROC /* A Thumb label. */ + +/* ARM-specific values for sh_flags */ +#define SHF_ARM_ENTRYSECT 0x10000000 /* Section contains an entry point */ +#define SHF_ARM_COMDEF \ + 0x80000000 /* Section may be multiply defined + in the input to a link step. */ + +/* ARM-specific program header flags */ +#define PF_ARM_SB \ + 0x10000000 /* Segment contains the location + addressed by the static base. */ +#define PF_ARM_PI 0x20000000 /* Position-independent segment. */ +#define PF_ARM_ABS 0x40000000 /* Absolute segment. */ + +/* Processor specific values for the Phdr p_type field. */ +#define PT_ARM_EXIDX (PT_LOPROC + 1) /* ARM unwind segment. */ + +/* Processor specific values for the Shdr sh_type field. */ +#define SHT_ARM_EXIDX (SHT_LOPROC + 1) /* ARM unwind section. */ +#define SHT_ARM_PREEMPTMAP (SHT_LOPROC + 2) /* Preemption details. */ +#define SHT_ARM_ATTRIBUTES (SHT_LOPROC + 3) /* ARM attributes section. */ + +/* ARM relocs. */ + +#define R_ARM_NONE 0 /* No reloc */ +#define R_ARM_PC24 1 /* PC relative 26 bit branch */ +#define R_ARM_ABS32 2 /* Direct 32 bit */ +#define R_ARM_REL32 3 /* PC relative 32 bit */ +#define R_ARM_PC13 4 +#define R_ARM_ABS16 5 /* Direct 16 bit */ +#define R_ARM_ABS12 6 /* Direct 12 bit */ +#define R_ARM_THM_ABS5 7 +#define R_ARM_ABS8 8 /* Direct 8 bit */ +#define R_ARM_SBREL32 9 +#define R_ARM_THM_PC22 10 +#define R_ARM_THM_PC8 11 +#define R_ARM_AMP_VCALL9 12 +#define R_ARM_SWI24 13 +#define R_ARM_THM_SWI8 14 +#define R_ARM_XPC25 15 +#define R_ARM_THM_XPC22 16 +#define R_ARM_TLS_DTPMOD32 17 /* ID of module containing symbol */ +#define R_ARM_TLS_DTPOFF32 18 /* Offset in TLS block */ +#define R_ARM_TLS_TPOFF32 19 /* Offset in static TLS block */ +#define R_ARM_COPY 20 /* Copy symbol at runtime */ +#define R_ARM_GLOB_DAT 21 /* Create GOT entry */ +#define R_ARM_JUMP_SLOT 22 /* Create PLT entry */ +#define R_ARM_RELATIVE 23 /* Adjust by program base */ +#define R_ARM_GOTOFF 24 /* 32 bit offset to GOT */ +#define R_ARM_GOTPC 25 /* 32 bit PC relative offset to GOT */ +#define R_ARM_GOT32 26 /* 32 bit GOT entry */ +#define R_ARM_PLT32 27 /* 32 bit PLT address */ +#define R_ARM_THM_JUMP24 30 /* Thumb32 ((S + A) | T) - P */ +#define R_ARM_ALU_PCREL_7_0 32 +#define R_ARM_ALU_PCREL_15_8 33 +#define R_ARM_ALU_PCREL_23_15 34 +#define R_ARM_LDR_SBREL_11_0 35 +#define R_ARM_ALU_SBREL_19_12 36 +#define R_ARM_ALU_SBREL_27_20 37 +#define R_ARM_GNU_VTENTRY 100 +#define R_ARM_GNU_VTINHERIT 101 +#define R_ARM_THM_PC11 102 /* thumb unconditional branch */ +#define R_ARM_THM_PC9 103 /* thumb conditional branch */ +#define R_ARM_TLS_GD32 \ + 104 /* PC-rel 32 bit for global dynamic + thread local data */ +#define R_ARM_TLS_LDM32 \ + 105 /* PC-rel 32 bit for local dynamic + thread local data */ +#define R_ARM_TLS_LDO32 \ + 106 /* 32 bit offset relative to TLS + block */ +#define R_ARM_TLS_IE32 \ + 107 /* PC-rel 32 bit for GOT entry of + static TLS block offset */ +#define R_ARM_TLS_LE32 \ + 108 /* 32 bit offset relative to static + TLS block */ +#define R_ARM_RXPC25 249 +#define R_ARM_RSBREL32 250 +#define R_ARM_THM_RPC22 251 +#define R_ARM_RREL32 252 +#define R_ARM_RABS22 253 +#define R_ARM_RPC24 254 +#define R_ARM_RBASE 255 +/* Keep this the last entry. */ +#define R_ARM_NUM 256 + +#endif /* elf.h */ diff --git a/lib/flipper_application/elf/elf_api_interface.h b/lib/flipper_application/elf/elf_api_interface.h new file mode 100644 index 00000000000..505f4f71838 --- /dev/null +++ b/lib/flipper_application/elf/elf_api_interface.h @@ -0,0 +1,12 @@ +#pragma once + +#include +#include + +#define ELF_INVALID_ADDRESS 0xFFFFFFFF + +typedef struct { + uint16_t api_version_major; + uint16_t api_version_minor; + bool (*resolver_callback)(const char* name, Elf32_Addr* address); +} ElfApiInterface; diff --git a/lib/flipper_application/flipper_applicaiton_i.c b/lib/flipper_application/flipper_applicaiton_i.c new file mode 100644 index 00000000000..a2a069eebc5 --- /dev/null +++ b/lib/flipper_application/flipper_applicaiton_i.c @@ -0,0 +1,477 @@ +#include "flipper_application_i.h" +#include + +#define TAG "fapp-i" + +#define RESOLVER_THREAD_YIELD_STEP 30 + +#define IS_FLAGS_SET(v, m) ((v & m) == m) +#define SECTION_OFFSET(e, n) (e->section_table + n * sizeof(Elf32_Shdr)) +#define SYMBOL_OFFSET(e, n) (e->_table + n * sizeof(Elf32_Shdr)) + +bool flipper_application_load_elf_headers(FlipperApplication* e, const char* path) { + Elf32_Ehdr h; + Elf32_Shdr sH; + + if(!storage_file_open(e->fd, path, FSAM_READ, FSOM_OPEN_EXISTING) || + !storage_file_seek(e->fd, 0, true) || + storage_file_read(e->fd, &h, sizeof(h)) != sizeof(h) || + !storage_file_seek(e->fd, h.e_shoff + h.e_shstrndx * sizeof(sH), true) || + storage_file_read(e->fd, &sH, sizeof(Elf32_Shdr)) != sizeof(Elf32_Shdr)) { + return false; + } + + e->entry = h.e_entry; + e->sections = h.e_shnum; + e->section_table = h.e_shoff; + e->section_table_strings = sH.sh_offset; + return true; +} + +static bool flipper_application_load_metadata(FlipperApplication* e, Elf32_Shdr* sh) { + if(sh->sh_size < sizeof(e->manifest)) { + return false; + } + + return storage_file_seek(e->fd, sh->sh_offset, true) && + storage_file_read(e->fd, &e->manifest, sh->sh_size) == sh->sh_size; +} + +static bool flipper_application_load_debug_link(FlipperApplication* e, Elf32_Shdr* sh) { + e->state.debug_link_size = sh->sh_size; + e->state.debug_link = malloc(sh->sh_size); + + return storage_file_seek(e->fd, sh->sh_offset, true) && + storage_file_read(e->fd, e->state.debug_link, sh->sh_size) == sh->sh_size; +} + +static FindFlags_t flipper_application_preload_section( + FlipperApplication* e, + Elf32_Shdr* sh, + const char* name, + int n) { + FURI_LOG_D(TAG, "Processing: %s", name); + + const struct { + const char* name; + uint16_t* ptr_section_idx; + FindFlags_t flags; + } lookup_sections[] = { + {".text", &e->text.sec_idx, FoundText}, + {".rodata", &e->rodata.sec_idx, FoundRodata}, + {".data", &e->data.sec_idx, FoundData}, + {".bss", &e->bss.sec_idx, FoundBss}, + {".rel.text", &e->text.rel_sec_idx, FoundRelText}, + {".rel.rodata", &e->rodata.rel_sec_idx, FoundRelRodata}, + {".rel.data", &e->data.rel_sec_idx, FoundRelData}, + }; + + for(size_t i = 0; i < COUNT_OF(lookup_sections); i++) { + if(strcmp(name, lookup_sections[i].name) == 0) { + *lookup_sections[i].ptr_section_idx = n; + return lookup_sections[i].flags; + } + } + + if(strcmp(name, ".symtab") == 0) { + e->symbol_table = sh->sh_offset; + e->symbol_count = sh->sh_size / sizeof(Elf32_Sym); + return FoundSymTab; + } else if(strcmp(name, ".strtab") == 0) { + e->symbol_table_strings = sh->sh_offset; + return FoundStrTab; + } else if(strcmp(name, ".fapmeta") == 0) { + // Load metadata immediately + if(flipper_application_load_metadata(e, sh)) { + return FoundFappManifest; + } + } else if(strcmp(name, ".gnu_debuglink") == 0) { + if(flipper_application_load_debug_link(e, sh)) { + return FoundDebugLink; + } + } + return FoundERROR; +} + +static bool + read_string_from_offset(FlipperApplication* e, off_t offset, char* buffer, size_t buffer_size) { + bool success = false; + + off_t old = storage_file_tell(e->fd); + if(storage_file_seek(e->fd, offset, true) && + (storage_file_read(e->fd, buffer, buffer_size) == buffer_size)) { + success = true; + } + storage_file_seek(e->fd, old, true); + + return success; +} + +static bool read_section_name(FlipperApplication* e, off_t off, char* buf, size_t max) { + return read_string_from_offset(e, e->section_table_strings + off, buf, max); +} + +static bool read_symbol_name(FlipperApplication* e, off_t off, char* buf, size_t max) { + return read_string_from_offset(e, e->symbol_table_strings + off, buf, max); +} + +static bool read_section_header(FlipperApplication* e, int n, Elf32_Shdr* h) { + off_t offset = SECTION_OFFSET(e, n); + return storage_file_seek(e->fd, offset, true) && + storage_file_read(e->fd, h, sizeof(Elf32_Shdr)) == sizeof(Elf32_Shdr); +} + +static bool read_section(FlipperApplication* e, int n, Elf32_Shdr* h, char* name, size_t nlen) { + if(!read_section_header(e, n, h)) { + return false; + } + if(!h->sh_name) { + return true; + } + return read_section_name(e, h->sh_name, name, nlen); +} + +bool flipper_application_load_section_table(FlipperApplication* e) { + furi_check(e->state.mmap_entry_count == 0); + + size_t n; + FindFlags_t found = FoundERROR; + FURI_LOG_D(TAG, "Scan ELF indexs..."); + for(n = 1; n < e->sections; n++) { + Elf32_Shdr section_header; + char name[33] = {0}; + if(!read_section_header(e, n, §ion_header)) { + return false; + } + if(section_header.sh_name && + !read_section_name(e, section_header.sh_name, name, sizeof(name))) { + return false; + } + + FURI_LOG_T(TAG, "Examining section %d %s", n, name); + FindFlags_t section_flags = + flipper_application_preload_section(e, §ion_header, name, n); + found |= section_flags; + if((section_flags & FoundGdbSection) != 0) { + e->state.mmap_entry_count++; + } + if(IS_FLAGS_SET(found, FoundAll)) { + return true; + } + } + + FURI_LOG_D(TAG, "Load symbols done"); + return IS_FLAGS_SET(found, FoundValid); +} + +static const char* type_to_str(int symt) { +#define STRCASE(name) \ + case name: \ + return #name; + switch(symt) { + STRCASE(R_ARM_NONE) + STRCASE(R_ARM_ABS32) + STRCASE(R_ARM_THM_PC22) + STRCASE(R_ARM_THM_JUMP24) + default: + return "R_"; + } +#undef STRCASE +} + +static void relocate_jmp_call(Elf32_Addr relAddr, int type, Elf32_Addr symAddr) { + UNUSED(type); + uint16_t upper_insn = ((uint16_t*)relAddr)[0]; + uint16_t lower_insn = ((uint16_t*)relAddr)[1]; + uint32_t S = (upper_insn >> 10) & 1; + uint32_t J1 = (lower_insn >> 13) & 1; + uint32_t J2 = (lower_insn >> 11) & 1; + + int32_t offset = (S << 24) | /* S -> offset[24] */ + ((~(J1 ^ S) & 1) << 23) | /* J1 -> offset[23] */ + ((~(J2 ^ S) & 1) << 22) | /* J2 -> offset[22] */ + ((upper_insn & 0x03ff) << 12) | /* imm10 -> offset[12:21] */ + ((lower_insn & 0x07ff) << 1); /* imm11 -> offset[1:11] */ + if(offset & 0x01000000) offset -= 0x02000000; + + offset += symAddr - relAddr; + + S = (offset >> 24) & 1; + J1 = S ^ (~(offset >> 23) & 1); + J2 = S ^ (~(offset >> 22) & 1); + + upper_insn = ((upper_insn & 0xf800) | (S << 10) | ((offset >> 12) & 0x03ff)); + ((uint16_t*)relAddr)[0] = upper_insn; + + lower_insn = ((lower_insn & 0xd000) | (J1 << 13) | (J2 << 11) | ((offset >> 1) & 0x07ff)); + ((uint16_t*)relAddr)[1] = lower_insn; +} + +static bool relocate_symbol(Elf32_Addr relAddr, int type, Elf32_Addr symAddr) { + switch(type) { + case R_ARM_ABS32: + *((uint32_t*)relAddr) += symAddr; + FURI_LOG_D(TAG, " R_ARM_ABS32 relocated is 0x%08X", (unsigned int)*((uint32_t*)relAddr)); + break; + case R_ARM_THM_PC22: + case R_ARM_THM_JUMP24: + relocate_jmp_call(relAddr, type, symAddr); + FURI_LOG_D( + TAG, " R_ARM_THM_CALL/JMP relocated is 0x%08X", (unsigned int)*((uint32_t*)relAddr)); + break; + default: + FURI_LOG_D(TAG, " Undefined relocation %d", type); + return false; + } + return true; +} + +static ELFSection_t* section_of(FlipperApplication* e, int index) { + if(e->text.sec_idx == index) { + return &e->text; + } else if(e->data.sec_idx == index) { + return &e->data; + } else if(e->bss.sec_idx == index) { + return &e->bss; + } else if(e->rodata.sec_idx == index) { + return &e->rodata; + } + return NULL; +} + +static Elf32_Addr address_of(FlipperApplication* e, Elf32_Sym* sym, const char* sName) { + if(sym->st_shndx == SHN_UNDEF) { + Elf32_Addr addr = 0; + if(e->api_interface->resolver_callback(sName, &addr)) { + return addr; + } + } else { + ELFSection_t* symSec = section_of(e, sym->st_shndx); + if(symSec) { + return ((Elf32_Addr)symSec->data) + sym->st_value; + } + } + FURI_LOG_D(TAG, " Can not find address for symbol %s", sName); + return ELF_INVALID_ADDRESS; +} + +static bool read_symbol(FlipperApplication* e, int n, Elf32_Sym* sym, char* name, size_t nlen) { + bool success = false; + off_t old = storage_file_tell(e->fd); + off_t pos = e->symbol_table + n * sizeof(Elf32_Sym); + if(storage_file_seek(e->fd, pos, true) && + storage_file_read(e->fd, sym, sizeof(Elf32_Sym)) == sizeof(Elf32_Sym)) { + if(sym->st_name) + success = read_symbol_name(e, sym->st_name, name, nlen); + else { + Elf32_Shdr shdr; + success = read_section(e, sym->st_shndx, &shdr, name, nlen); + } + } + storage_file_seek(e->fd, old, true); + return success; +} + +static bool + relocation_cache_get(RelocationAddressCache_t cache, int symEntry, Elf32_Addr* symAddr) { + Elf32_Addr* addr = RelocationAddressCache_get(cache, symEntry); + if(addr) { + *symAddr = *addr; + return true; + } else { + return false; + } +} + +static void + relocation_cache_put(RelocationAddressCache_t cache, int symEntry, Elf32_Addr symAddr) { + RelocationAddressCache_set_at(cache, symEntry, symAddr); +} + +#define MAX_SYMBOL_NAME_LEN 128u + +static bool relocate(FlipperApplication* e, Elf32_Shdr* h, ELFSection_t* s) { + if(s->data) { + Elf32_Rel rel; + size_t relEntries = h->sh_size / sizeof(rel); + size_t relCount; + (void)storage_file_seek(e->fd, h->sh_offset, true); + FURI_LOG_D(TAG, " Offset Info Type Name"); + + int relocate_result = true; + char symbol_name[MAX_SYMBOL_NAME_LEN + 1] = {0}; + + for(relCount = 0; relCount < relEntries; relCount++) { + if(relCount % RESOLVER_THREAD_YIELD_STEP == 0) { + FURI_LOG_D(TAG, " reloc YIELD"); + furi_delay_tick(1); + } + + if(storage_file_read(e->fd, &rel, sizeof(Elf32_Rel)) != sizeof(Elf32_Rel)) { + FURI_LOG_E(TAG, " reloc read fail"); + return false; + } + + Elf32_Addr symAddr; + + int symEntry = ELF32_R_SYM(rel.r_info); + int relType = ELF32_R_TYPE(rel.r_info); + Elf32_Addr relAddr = ((Elf32_Addr)s->data) + rel.r_offset; + + if(!relocation_cache_get(e->relocation_cache, symEntry, &symAddr)) { + Elf32_Sym sym; + if(!read_symbol(e, symEntry, &sym, symbol_name, MAX_SYMBOL_NAME_LEN)) { + FURI_LOG_E(TAG, " symbol read fail"); + return false; + } + + FURI_LOG_D( + TAG, + " %08X %08X %-16s %s", + (unsigned int)rel.r_offset, + (unsigned int)rel.r_info, + type_to_str(relType), + symbol_name); + + symAddr = address_of(e, &sym, symbol_name); + relocation_cache_put(e->relocation_cache, symEntry, symAddr); + } + + if(symAddr != ELF_INVALID_ADDRESS) { + FURI_LOG_D( + TAG, + " symAddr=%08X relAddr=%08X", + (unsigned int)symAddr, + (unsigned int)relAddr); + if(!relocate_symbol(relAddr, relType, symAddr)) { + relocate_result = false; + } + } else { + FURI_LOG_D(TAG, " No symbol address of %s", symbol_name); + relocate_result = false; + } + } + + return relocate_result; + } else + FURI_LOG_I(TAG, "Section not loaded"); + + return false; +} + +static bool flipper_application_load_section_data(FlipperApplication* e, ELFSection_t* s) { + Elf32_Shdr section_header; + if(s->sec_idx == 0) { + FURI_LOG_I(TAG, "Section is not present"); + return true; + } + + if(!read_section_header(e, s->sec_idx, §ion_header)) { + return false; + } + + if(section_header.sh_size == 0) { + FURI_LOG_I(TAG, "No data for section"); + return true; + } + + s->data = aligned_malloc(section_header.sh_size, section_header.sh_addralign); + // e->state.mmap_entry_count++; + + if(section_header.sh_type == SHT_NOBITS) { + /* section is empty (.bss?) */ + /* no need to memset - allocator already did that */ + /* memset(s->data, 0, h->sh_size); */ + FURI_LOG_D(TAG, "0x%X", s->data); + return true; + } + + if((!storage_file_seek(e->fd, section_header.sh_offset, true)) || + (storage_file_read(e->fd, s->data, section_header.sh_size) != section_header.sh_size)) { + FURI_LOG_E(TAG, " seek/read fail"); + flipper_application_free_section(s); + return false; + } + + FURI_LOG_D(TAG, "0x%X", s->data); + return true; +} + +static bool flipper_application_relocate_section(FlipperApplication* e, ELFSection_t* s) { + Elf32_Shdr section_header; + if(s->rel_sec_idx) { + FURI_LOG_D(TAG, "Relocating section"); + if(read_section_header(e, s->rel_sec_idx, §ion_header)) + return relocate(e, §ion_header, s); + else { + FURI_LOG_E(TAG, "Error reading section header"); + return false; + } + } else + FURI_LOG_D(TAG, "No relocation index"); /* Not an error */ + return true; +} + +FlipperApplicationLoadStatus flipper_application_load_sections(FlipperApplication* e) { + FlipperApplicationLoadStatus status = FlipperApplicationLoadStatusSuccess; + RelocationAddressCache_init(e->relocation_cache); + size_t start = furi_get_tick(); + + struct { + ELFSection_t* section; + const char* name; + } sections[] = { + {&e->text, ".text"}, + {&e->rodata, ".rodata"}, + {&e->data, ".data"}, + {&e->bss, ".bss"}, + }; + + for(size_t i = 0; i < COUNT_OF(sections); i++) { + if(!flipper_application_load_section_data(e, sections[i].section)) { + FURI_LOG_E(TAG, "Error loading section '%s'", sections[i].name); + status = FlipperApplicationLoadStatusUnspecifiedError; + } + } + + if(status == FlipperApplicationLoadStatusSuccess) { + for(size_t i = 0; i < COUNT_OF(sections); i++) { + if(!flipper_application_relocate_section(e, sections[i].section)) { + FURI_LOG_E(TAG, "Error relocating section '%s'", sections[i].name); + status = FlipperApplicationLoadStatusMissingImports; + } + } + } + + if(status == FlipperApplicationLoadStatusSuccess) { + e->state.mmap_entries = + malloc(sizeof(FlipperApplicationMemoryMapEntry) * e->state.mmap_entry_count); + uint32_t mmap_entry_idx = 0; + for(size_t i = 0; i < COUNT_OF(sections); i++) { + const void* data_ptr = sections[i].section->data; + if(data_ptr) { + FURI_LOG_I(TAG, "0x%X %s", (uint32_t)data_ptr, sections[i].name); + e->state.mmap_entries[mmap_entry_idx].address = (uint32_t)data_ptr; + e->state.mmap_entries[mmap_entry_idx].name = sections[i].name; + mmap_entry_idx++; + } + } + furi_check(mmap_entry_idx == e->state.mmap_entry_count); + + /* Fixing up entry point */ + e->entry += (uint32_t)e->text.data; + } + + FURI_LOG_D(TAG, "Relocation cache size: %u", RelocationAddressCache_size(e->relocation_cache)); + RelocationAddressCache_clear(e->relocation_cache); + FURI_LOG_I(TAG, "Loaded in %ums", (size_t)(furi_get_tick() - start)); + + return status; +} + +void flipper_application_free_section(ELFSection_t* s) { + if(s->data) { + aligned_free(s->data); + } + s->data = NULL; +} diff --git a/lib/flipper_application/flipper_application.c b/lib/flipper_application/flipper_application.c new file mode 100644 index 00000000000..6e84cce38a0 --- /dev/null +++ b/lib/flipper_application/flipper_application.c @@ -0,0 +1,131 @@ +#include "flipper_application.h" +#include "flipper_application_i.h" + +#define TAG "fapp" + +/* For debugger access to app state */ +FlipperApplication* last_loaded_app = NULL; + +FlipperApplication* + flipper_application_alloc(Storage* storage, const ElfApiInterface* api_interface) { + FlipperApplication* app = malloc(sizeof(FlipperApplication)); + app->api_interface = api_interface; + app->fd = storage_file_alloc(storage); + app->thread = NULL; + return app; +} + +void flipper_application_free(FlipperApplication* app) { + furi_assert(app); + + if(app->thread) { + furi_thread_join(app->thread); + furi_thread_free(app->thread); + } + + last_loaded_app = NULL; + + if(app->state.debug_link_size) { + free(app->state.debug_link); + } + + if(app->state.mmap_entries) { + free(app->state.mmap_entries); + } + + ELFSection_t* sections[] = {&app->text, &app->rodata, &app->data, &app->bss}; + for(size_t i = 0; i < COUNT_OF(sections); i++) { + flipper_application_free_section(sections[i]); + } + + storage_file_free(app->fd); + + free(app); +} + +/* Parse headers, load manifest */ +FlipperApplicationPreloadStatus + flipper_application_preload(FlipperApplication* app, const char* path) { + if(!flipper_application_load_elf_headers(app, path) || + !flipper_application_load_section_table(app)) { + return FlipperApplicationPreloadStatusInvalidFile; + } + + if((app->manifest.base.manifest_magic != FAP_MANIFEST_MAGIC) && + (app->manifest.base.manifest_version == FAP_MANIFEST_SUPPORTED_VERSION)) { + return FlipperApplicationPreloadStatusInvalidManifest; + } + + if(app->manifest.base.api_version.major != app->api_interface->api_version_major /* || + app->manifest.base.api_version.minor > app->api_interface->api_version_minor */) { + return FlipperApplicationPreloadStatusApiMismatch; + } + + return FlipperApplicationPreloadStatusSuccess; +} + +const FlipperApplicationManifest* flipper_application_get_manifest(FlipperApplication* app) { + return &app->manifest; +} + +FlipperApplicationLoadStatus flipper_application_map_to_memory(FlipperApplication* app) { + last_loaded_app = app; + return flipper_application_load_sections(app); +} + +const FlipperApplicationState* flipper_application_get_state(FlipperApplication* app) { + return &app->state; +} + +FuriThread* flipper_application_spawn(FlipperApplication* app, void* args) { + furi_check(app->thread == NULL); + + const FlipperApplicationManifest* manifest = flipper_application_get_manifest(app); + furi_check(manifest->stack_size > 0); + + app->thread = furi_thread_alloc(); + furi_thread_set_stack_size(app->thread, manifest->stack_size); + furi_thread_set_name(app->thread, manifest->name); + furi_thread_set_callback(app->thread, (entry_t*)app->entry); + furi_thread_set_context(app->thread, args); + + return app->thread; +} + +FuriThread* flipper_application_get_thread(FlipperApplication* app) { + return app->thread; +} + +void const* flipper_application_get_entry_address(FlipperApplication* app) { + return (void*)app->entry; +} + +static const char* preload_status_strings[] = { + [FlipperApplicationPreloadStatusSuccess] = "Success", + [FlipperApplicationPreloadStatusUnspecifiedError] = "Unknown error", + [FlipperApplicationPreloadStatusInvalidFile] = "Invalid file", + [FlipperApplicationPreloadStatusInvalidManifest] = "Invalid file manifest", + [FlipperApplicationPreloadStatusApiMismatch] = "API version mismatch", + [FlipperApplicationPreloadStatusTargetMismatch] = "Hardware target mismatch", +}; + +static const char* load_status_strings[] = { + [FlipperApplicationLoadStatusSuccess] = "Success", + [FlipperApplicationLoadStatusUnspecifiedError] = "Unknown error", + [FlipperApplicationLoadStatusNoFreeMemory] = "Out of memory", + [FlipperApplicationLoadStatusMissingImports] = "Found unsatisfied imports", +}; + +const char* flipper_application_preload_status_to_string(FlipperApplicationPreloadStatus status) { + if(status >= COUNT_OF(preload_status_strings) || preload_status_strings[status] == NULL) { + return "Unknown error"; + } + return preload_status_strings[status]; +} + +const char* flipper_application_load_status_to_string(FlipperApplicationLoadStatus status) { + if(status >= COUNT_OF(load_status_strings) || load_status_strings[status] == NULL) { + return "Unknown error"; + } + return load_status_strings[status]; +} diff --git a/lib/flipper_application/flipper_application.h b/lib/flipper_application/flipper_application.h new file mode 100644 index 00000000000..34de4038886 --- /dev/null +++ b/lib/flipper_application/flipper_application.h @@ -0,0 +1,129 @@ +#pragma once + +#include "application_manifest.h" +#include "elf/elf_api_interface.h" + +#include +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + FlipperApplicationPreloadStatusSuccess = 0, + FlipperApplicationPreloadStatusUnspecifiedError, + FlipperApplicationPreloadStatusInvalidFile, + FlipperApplicationPreloadStatusInvalidManifest, + FlipperApplicationPreloadStatusApiMismatch, + FlipperApplicationPreloadStatusTargetMismatch, +} FlipperApplicationPreloadStatus; + +typedef enum { + FlipperApplicationLoadStatusSuccess = 0, + FlipperApplicationLoadStatusUnspecifiedError, + FlipperApplicationLoadStatusNoFreeMemory, + FlipperApplicationLoadStatusMissingImports, +} FlipperApplicationLoadStatus; + +/** + * @brief Get text description of preload status + * @param status Status code + * @return String pointer to description + */ +const char* flipper_application_preload_status_to_string(FlipperApplicationPreloadStatus status); + +/** + * @brief Get text description of load status + * @param status Status code + * @return String pointer to description + */ +const char* flipper_application_load_status_to_string(FlipperApplicationLoadStatus status); + +typedef struct FlipperApplication FlipperApplication; + +typedef struct { + const char* name; + uint32_t address; +} FlipperApplicationMemoryMapEntry; + +typedef struct { + uint32_t mmap_entry_count; + FlipperApplicationMemoryMapEntry* mmap_entries; + uint32_t debug_link_size; + uint8_t* debug_link; +} FlipperApplicationState; + +/** + * @brief Initialize FlipperApplication object + * @param storage Storage instance + * @param api_interface ELF API interface to use for pre-loading and symbol resolving + * @return Application instance + */ +FlipperApplication* + flipper_application_alloc(Storage* storage, const ElfApiInterface* api_interface); + +/** + * @brief Destroy FlipperApplication object + * @param app Application pointer + */ +void flipper_application_free(FlipperApplication* app); + +/** + * @brief Validate elf file and load application metadata + * @param app Application pointer + * @return Preload result code + */ +FlipperApplicationPreloadStatus + flipper_application_preload(FlipperApplication* app, const char* path); + +/** + * @brief Get pointer to application manifest for preloaded application + * @param app Application pointer + * @return Pointer to application manifest + */ +const FlipperApplicationManifest* flipper_application_get_manifest(FlipperApplication* app); + +/** + * @brief Load sections and process relocations for already pre-loaded application + * @param app Application pointer + * @return Load result code + */ +FlipperApplicationLoadStatus flipper_application_map_to_memory(FlipperApplication* app); + +/** + * @brief Get state object for loaded application + * @param app Application pointer + * @return Pointer to state object + */ +const FlipperApplicationState* flipper_application_get_state(FlipperApplication* app); + +/** + * @brief Create application thread at entry point address, using app name and + * stack size from metadata. Returned thread isn't started yet. + * Can be only called once for application instance. + * @param app Applicaiton pointer + * @param args Object to pass to app's entry point + * @return Created thread + */ +FuriThread* flipper_application_spawn(FlipperApplication* app, void* args); + +/** + * @brief Get previously spawned thread + * @param app Application pointer + * @return Created thread + */ +FuriThread* flipper_application_get_thread(FlipperApplication* app); + +/** + * @brief Return relocated and valid address of app's entry point + * @param app Application pointer + * @return Address of app's entry point + */ +void const* flipper_application_get_entry_address(FlipperApplication* app); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/flipper_application/flipper_application_i.h b/lib/flipper_application/flipper_application_i.h new file mode 100644 index 00000000000..8adf5c0d2d5 --- /dev/null +++ b/lib/flipper_application/flipper_application_i.h @@ -0,0 +1,99 @@ +#pragma once + +#include "elf.h" +#include "flipper_application.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +DICT_DEF2(RelocationAddressCache, int, M_DEFAULT_OPLIST, Elf32_Addr, M_DEFAULT_OPLIST) + +/** + * Callable elf entry type + */ +typedef int32_t(entry_t)(void*); + +typedef struct { + void* data; + uint16_t sec_idx; + uint16_t rel_sec_idx; +} ELFSection_t; + +struct FlipperApplication { + const ElfApiInterface* api_interface; + File* fd; + FlipperApplicationState state; + FlipperApplicationManifest manifest; + + size_t sections; + off_t section_table; + off_t section_table_strings; + + size_t symbol_count; + off_t symbol_table; + off_t symbol_table_strings; + off_t entry; + + ELFSection_t text; + ELFSection_t rodata; + ELFSection_t data; + ELFSection_t bss; + + FuriThread* thread; + RelocationAddressCache_t relocation_cache; +}; + +typedef enum { + FoundERROR = 0, + FoundSymTab = (1 << 0), + FoundStrTab = (1 << 2), + FoundText = (1 << 3), + FoundRodata = (1 << 4), + FoundData = (1 << 5), + FoundBss = (1 << 6), + FoundRelText = (1 << 7), + FoundRelRodata = (1 << 8), + FoundRelData = (1 << 9), + FoundRelBss = (1 << 10), + FoundFappManifest = (1 << 11), + FoundDebugLink = (1 << 12), + FoundValid = FoundSymTab | FoundStrTab | FoundFappManifest, + FoundExec = FoundValid | FoundText, + FoundGdbSection = FoundText | FoundRodata | FoundData | FoundBss, + FoundAll = FoundSymTab | FoundStrTab | FoundText | FoundRodata | FoundData | FoundBss | + FoundRelText | FoundRelRodata | FoundRelData | FoundRelBss | FoundDebugLink, +} FindFlags_t; + +/** + * @brief Load and validate basic ELF file headers + * @param e Application instance + * @param path FS path to application file + * @return true if ELF file is valid + */ +bool flipper_application_load_elf_headers(FlipperApplication* e, const char* path); + +/** + * @brief Iterate over all sections and save related indexes + * @param e Application instance + * @return true if all required sections are found + */ +bool flipper_application_load_section_table(FlipperApplication* e); + +/** + * @brief Load section data to memory and process relocations + * @param e Application instance + * @return Status code + */ +FlipperApplicationLoadStatus flipper_application_load_sections(FlipperApplication* e); + +/** + * @brief Release section data + * @param s section pointer + */ +void flipper_application_free_section(ELFSection_t* s); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/flipper_format/SConscript b/lib/flipper_format/SConscript index e5d61a075aa..5e185678b8a 100644 --- a/lib/flipper_format/SConscript +++ b/lib/flipper_format/SConscript @@ -4,6 +4,10 @@ env.Append( CPPPATH=[ "#/lib/flipper_format", ], + SDK_HEADERS=[ + File("#/lib/flipper_format/flipper_format.h"), + File("#/lib/flipper_format/flipper_format_i.h"), + ], ) diff --git a/lib/loclass.scons b/lib/loclass.scons deleted file mode 100644 index a657f0424c2..00000000000 --- a/lib/loclass.scons +++ /dev/null @@ -1,17 +0,0 @@ -Import("env") - -env.Append( - CPPPATH=[ - "#/lib/loclass", - ], -) - - -libenv = env.Clone(FW_LIB_NAME="loclass") -libenv.ApplyLibFlags() - -sources = Glob("loclass/*.c", source=True) - -lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources) -libenv.Install("${LIB_DIST_DIR}", lib) -Return("lib") diff --git a/lib/misc.scons b/lib/misc.scons index 5a826b18d29..b7d8554b590 100644 --- a/lib/misc.scons +++ b/lib/misc.scons @@ -12,6 +12,9 @@ env.Append( CPPDEFINES=[ "PB_ENABLE_MALLOC", ], + SDK_HEADERS=[ + File("#/lib/micro-ecc/uECC.h"), + ], ) diff --git a/lib/nfc/nfc_device.c b/lib/nfc/nfc_device.c index ea06ae70f89..dd78e2daf06 100644 --- a/lib/nfc/nfc_device.c +++ b/lib/nfc/nfc_device.c @@ -1182,8 +1182,19 @@ bool nfc_file_select(NfcDevice* dev) { // 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); + + const DialogsFileBrowserOptions browser_options = { + .extension = NFC_APP_EXTENSION, + .skip_assets = true, + .icon = &I_Nfc_10px, + .hide_ext = true, + .item_loader_callback = NULL, + .item_loader_context = NULL, + }; + + bool res = + dialog_file_browser_show(dev->dialogs, dev->load_path, nfc_app_folder, &browser_options); + string_clear(nfc_app_folder); if(res) { string_t filename; diff --git a/lib/print/SConscript b/lib/print/SConscript index 412d17a660b..d4a55ab8471 100644 --- a/lib/print/SConscript +++ b/lib/print/SConscript @@ -96,6 +96,12 @@ for wrapped_fn in wrapped_fn_list: ] ) +env.Append( + SDK_HEADERS=[ + File("#/lib/print/wrappers.h"), + ], +) + libenv = env.Clone(FW_LIB_NAME="print") libenv.ApplyLibFlags() libenv.Append(CCFLAGS=["-Wno-double-promotion"]) diff --git a/lib/print/wrappers.c b/lib/print/wrappers.c index 3fe446657c4..5cfe1060043 100644 --- a/lib/print/wrappers.c +++ b/lib/print/wrappers.c @@ -1,11 +1,10 @@ -#include -#include +#include "wrappers.h" + #include #include #include #include #include -#include #include "printf_tiny.h" void _putchar(char character) { diff --git a/lib/print/wrappers.h b/lib/print/wrappers.h new file mode 100644 index 00000000000..b6f0f004b65 --- /dev/null +++ b/lib/print/wrappers.h @@ -0,0 +1,25 @@ +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +void _putchar(char character); +int __wrap_printf(const char* format, ...); +int __wrap_vsnprintf(char* str, size_t size, const char* format, va_list args); +int __wrap_puts(const char* str); +int __wrap_putchar(int ch); +int __wrap_putc(int ch, FILE* stream); +int __wrap_snprintf(char* str, size_t size, const char* format, ...); +int __wrap_fflush(FILE* stream); + +__attribute__((__noreturn__)) void __wrap___assert(const char* file, int line, const char* e); + +__attribute__((__noreturn__)) void + __wrap___assert_func(const char* file, int line, const char* func, const char* e); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/subghz/SConscript b/lib/subghz/SConscript index 3c6728afe67..50c92cbd16f 100644 --- a/lib/subghz/SConscript +++ b/lib/subghz/SConscript @@ -4,6 +4,13 @@ env.Append( CPPPATH=[ "#/lib/subghz", ], + SDK_HEADERS=[ + File("#/lib/subghz/environment.h"), + File("#/lib/subghz/receiver.h"), + File("#/lib/subghz/subghz_worker.h"), + File("#/lib/subghz/subghz_tx_rx_worker.h"), + File("#/lib/subghz/transmitter.h"), + ], ) libenv = env.Clone(FW_LIB_NAME="subghz") diff --git a/lib/subghz/environment.h b/lib/subghz/environment.h index b8e73e33b5a..d4678e41349 100644 --- a/lib/subghz/environment.h +++ b/lib/subghz/environment.h @@ -4,6 +4,10 @@ #include "subghz_keystore.h" +#ifdef __cplusplus +extern "C" { +#endif + typedef struct SubGhzEnvironment SubGhzEnvironment; /** @@ -64,3 +68,7 @@ void subghz_environment_set_nice_flor_s_rainbow_table_file_name( */ const char* subghz_environment_get_nice_flor_s_rainbow_table_file_name(SubGhzEnvironment* instance); + +#ifdef __cplusplus +} +#endif diff --git a/lib/subghz/protocols/base.h b/lib/subghz/protocols/base.h index a1a7478d945..fdd135671f1 100644 --- a/lib/subghz/protocols/base.h +++ b/lib/subghz/protocols/base.h @@ -2,6 +2,10 @@ #include "../types.h" +#ifdef __cplusplus +extern "C" { +#endif + typedef struct SubGhzProtocolDecoderBase SubGhzProtocolDecoderBase; typedef void ( @@ -77,3 +81,7 @@ struct SubGhzProtocolEncoderBase { // Callback section }; + +#ifdef __cplusplus +} +#endif diff --git a/lib/subghz/receiver.h b/lib/subghz/receiver.h index 1357ecbed91..2ef722d1f25 100644 --- a/lib/subghz/receiver.h +++ b/lib/subghz/receiver.h @@ -3,6 +3,10 @@ #include "types.h" #include "protocols/base.h" +#ifdef __cplusplus +extern "C" { +#endif + typedef struct SubGhzReceiver SubGhzReceiver; typedef void (*SubGhzReceiverCallback)( @@ -63,3 +67,7 @@ void subghz_receiver_set_filter(SubGhzReceiver* instance, SubGhzProtocolFlag fil */ SubGhzProtocolDecoderBase* subghz_receiver_search_decoder_base_by_name(SubGhzReceiver* instance, const char* decoder_name); + +#ifdef __cplusplus +} +#endif diff --git a/lib/subghz/subghz_keystore.h b/lib/subghz/subghz_keystore.h index be9a19df61c..2b7880f07b2 100644 --- a/lib/subghz/subghz_keystore.h +++ b/lib/subghz/subghz_keystore.h @@ -4,6 +4,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + typedef struct { string_t name; uint64_t key; @@ -70,3 +74,7 @@ bool subghz_keystore_raw_encrypted_save( * @return true On success */ bool subghz_keystore_raw_get_data(const char* file_name, size_t offset, uint8_t* data, size_t len); + +#ifdef __cplusplus +} +#endif diff --git a/lib/subghz/subghz_tx_rx_worker.h b/lib/subghz/subghz_tx_rx_worker.h index 3f3f82769ea..ddc02e749d8 100644 --- a/lib/subghz/subghz_tx_rx_worker.h +++ b/lib/subghz/subghz_tx_rx_worker.h @@ -2,6 +2,10 @@ #include +#ifdef __cplusplus +extern "C" { +#endif + typedef void (*SubGhzTxRxWorkerCallbackHaveRead)(void* context); typedef struct SubGhzTxRxWorker SubGhzTxRxWorker; @@ -79,3 +83,7 @@ void subghz_tx_rx_worker_stop(SubGhzTxRxWorker* instance); * @return bool - true if running */ bool subghz_tx_rx_worker_is_running(SubGhzTxRxWorker* instance); + +#ifdef __cplusplus +} +#endif diff --git a/lib/subghz/subghz_worker.h b/lib/subghz/subghz_worker.h index bb303165c8a..f85b1fdc7aa 100644 --- a/lib/subghz/subghz_worker.h +++ b/lib/subghz/subghz_worker.h @@ -2,6 +2,10 @@ #include +#ifdef __cplusplus +extern "C" { +#endif + typedef struct SubGhzWorker SubGhzWorker; typedef void (*SubGhzWorkerOverrunCallback)(void* context); @@ -62,3 +66,7 @@ void subghz_worker_stop(SubGhzWorker* instance); * @return bool - true if running */ bool subghz_worker_is_running(SubGhzWorker* instance); + +#ifdef __cplusplus +} +#endif diff --git a/lib/subghz/transmitter.h b/lib/subghz/transmitter.h index 53f10b07565..cce98a463aa 100644 --- a/lib/subghz/transmitter.h +++ b/lib/subghz/transmitter.h @@ -4,6 +4,10 @@ #include "environment.h" #include "protocols/base.h" +#ifdef __cplusplus +extern "C" { +#endif + typedef struct SubGhzTransmitter SubGhzTransmitter; /** @@ -45,3 +49,7 @@ bool subghz_transmitter_deserialize(SubGhzTransmitter* instance, FlipperFormat* * @return LevelDuration */ LevelDuration subghz_transmitter_yield(void* context); + +#ifdef __cplusplus +} +#endif diff --git a/lib/toolbox/SConscript b/lib/toolbox/SConscript index e4f2cbd0200..6ced34d7c68 100644 --- a/lib/toolbox/SConscript +++ b/lib/toolbox/SConscript @@ -7,6 +7,23 @@ env.Append( CPPPATH=[ "#/lib/toolbox", ], + SDK_HEADERS=[ + File("#/lib/toolbox/manchester_decoder.h"), + File("#/lib/toolbox/manchester_encoder.h"), + File("#/lib/toolbox/path.h"), + File("#/lib/toolbox/random_name.h"), + File("#/lib/toolbox/hmac_sha256.h"), + File("#/lib/toolbox/crc32_calc.h"), + File("#/lib/toolbox/dir_walk.h"), + File("#/lib/toolbox/md5.h"), + File("#/lib/toolbox/args.h"), + File("#/lib/toolbox/saved_struct.h"), + File("#/lib/toolbox/version.h"), + File("#/lib/toolbox/tar/tar_archive.h"), + File("#/lib/toolbox/stream/stream.h"), + File("#/lib/toolbox/stream/file_stream.h"), + File("#/lib/toolbox/stream/string_stream.h"), + ], ) diff --git a/lib/toolbox/hmac_sha256.h b/lib/toolbox/hmac_sha256.h index 01fc0b1465c..add123142d4 100644 --- a/lib/toolbox/hmac_sha256.h +++ b/lib/toolbox/hmac_sha256.h @@ -1,3 +1,10 @@ +#pragma once + +#include "sha256.h" + +#ifdef __cplusplus +extern "C" { +#endif typedef struct hmac_context { void (*init_hash)(const struct hmac_context* context); @@ -25,3 +32,7 @@ void hmac_sha256_update( unsigned message_size); void hmac_sha256_finish(const hmac_sha256_context* ctx, const uint8_t* K, uint8_t* hash_result); + +#ifdef __cplusplus +} +#endif diff --git a/lib/toolbox/saved_struct.h b/lib/toolbox/saved_struct.h index aae41bd61c6..aaa04eef5c2 100644 --- a/lib/toolbox/saved_struct.h +++ b/lib/toolbox/saved_struct.h @@ -4,6 +4,14 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + bool saved_struct_load(const char* path, void* data, size_t size, uint8_t magic, uint8_t version); bool saved_struct_save(const char* path, void* data, size_t size, uint8_t magic, uint8_t version); + +#ifdef __cplusplus +} +#endif diff --git a/lib/toolbox/sha256.h b/lib/toolbox/sha256.h index 85b9709dbbd..c544d3ebb1d 100644 --- a/lib/toolbox/sha256.h +++ b/lib/toolbox/sha256.h @@ -1,3 +1,9 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + #define SHA256_DIGEST_SIZE 32 #define SHA256_BLOCK_SIZE 64 @@ -12,3 +18,7 @@ void sha256_start(sha256_context* ctx); void sha256_finish(sha256_context* ctx, unsigned char output[32]); void sha256_update(sha256_context* ctx, const unsigned char* input, unsigned int ilen); void sha256_process(sha256_context* ctx); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/update_util/lfs_backup.c b/lib/update_util/lfs_backup.c index 724da3657f7..b4a0aaabbe5 100644 --- a/lib/update_util/lfs_backup.c +++ b/lib/update_util/lfs_backup.c @@ -6,7 +6,7 @@ #include #include #include -#include +#include #include #define LFS_BACKUP_DEFAULT_LOCATION EXT_PATH(LFS_BACKUP_DEFAULT_FILENAME) diff --git a/scripts/assets.py b/scripts/assets.py index b27b29efd05..1f36a135b74 100755 --- a/scripts/assets.py +++ b/scripts/assets.py @@ -3,16 +3,12 @@ from flipper.app import App from flipper.assets.icon import file2image -import logging -import argparse -import subprocess -import io import os -import sys ICONS_SUPPORTED_FORMATS = ["png"] ICONS_TEMPLATE_H_HEADER = """#pragma once + #include """ diff --git a/scripts/runfap.py b/scripts/runfap.py new file mode 100644 index 00000000000..c2c0f78d5cb --- /dev/null +++ b/scripts/runfap.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python3 + +import posixpath +from typing import final +from flipper.app import App +from flipper.storage import FlipperStorage +from flipper.utils.cdc import resolve_port + +import logging +import os +import pathlib +import serial.tools.list_ports as list_ports + + +class Main(App): + def init(self): + self.parser.add_argument("-p", "--port", help="CDC Port", default="auto") + + self.parser.add_argument("fap_src_path", help="App file to upload") + self.parser.add_argument( + "--fap_dst_dir", help="Upload path", default="/ext/apps", required=False + ) + self.parser.set_defaults(func=self.install) + + # logging + self.logger = logging.getLogger() + + # make directory with exist check + def mkdir_on_storage(self, storage, flipper_dir_path): + if not storage.exist_dir(flipper_dir_path): + self.logger.debug(f'"{flipper_dir_path}" does not exist, creating') + if not storage.mkdir(flipper_dir_path): + self.logger.error(f"Error: {storage.last_error}") + return False + else: + self.logger.debug(f'"{flipper_dir_path}" already exists') + return True + + # send file with exist check and hash check + def send_file_to_storage(self, storage, flipper_file_path, local_file_path, force): + exists = storage.exist_file(flipper_file_path) + do_upload = not exists + if exists: + hash_local = storage.hash_local(local_file_path) + hash_flipper = storage.hash_flipper(flipper_file_path) + self.logger.debug(f"hash check: local {hash_local}, flipper {hash_flipper}") + do_upload = force or (hash_local != hash_flipper) + + if do_upload: + self.logger.info(f'Sending "{local_file_path}" to "{flipper_file_path}"') + if not storage.send_file(local_file_path, flipper_file_path): + self.logger.error(f"Error: {storage.last_error}") + return False + return True + + def install(self): + if not (port := resolve_port(self.logger, self.args.port)): + return 1 + + storage = FlipperStorage(port) + storage.start() + + try: + fap_local_path = self.args.fap_src_path + self.args.fap_dst_dir = self.args.fap_dst_dir.rstrip("/\\") + + if not os.path.isfile(fap_local_path): + self.logger.error(f"Error: source .fap ({fap_local_path}) not found") + return -1 + + fap_dst_path = posixpath.join( + self.args.fap_dst_dir, os.path.basename(fap_local_path) + ) + + self.logger.info(f'Installing "{fap_local_path}" to {fap_dst_path}') + + if not self.mkdir_on_storage(storage, self.args.fap_dst_dir): + self.logger.error(f"Error: cannot create dir: {storage.last_error}") + return -2 + + if not self.send_file_to_storage( + storage, fap_dst_path, fap_local_path, False + ): + self.logger.error(f"Error: upload failed: {storage.last_error}") + return -3 + + storage.send_and_wait_eol(f'loader open "Applications" {fap_dst_path}\r') + result = storage.read.until(storage.CLI_EOL) + if len(result): + self.logger.error(f"Unexpected response: {result.decode('ascii')}") + return -4 + + return 0 + finally: + storage.stop() + + +if __name__ == "__main__": + Main()() diff --git a/site_scons/cc.scons b/site_scons/cc.scons index e71db2ba29d..c923b387226 100644 --- a/site_scons/cc.scons +++ b/site_scons/cc.scons @@ -11,6 +11,7 @@ ENV.AppendUnique( "-fno-use-cxa-atexit", "-fno-exceptions", "-fno-threadsafe-statics", + "-ftemplate-depth=4096", ], CCFLAGS=[ "-mcpu=cortex-m4", diff --git a/site_scons/commandline.scons b/site_scons/commandline.scons index 4c96268b693..2eeda24798b 100644 --- a/site_scons/commandline.scons +++ b/site_scons/commandline.scons @@ -68,6 +68,11 @@ vars.AddVariables( "7", ], ), + BoolVariable( + "DEBUG_TOOLS", + help="Enable debug tools to be built", + default=False, + ), ) vars.Add( @@ -186,21 +191,17 @@ vars.Add( help="Map of (configuration_name->application_list)", default={ "default": ( - "crypto_start", # Svc "basic_services", # Apps - "basic_apps", - "updater_app", - "archive", + "main_apps", + "system_apps", # Settings - "passport", - "system_settings", - "about", + "settings_apps", # Plugins - "basic_plugins", + # "basic_plugins", # Debug - "debug_apps", + # "debug_apps", ) }, ) @@ -211,4 +212,26 @@ vars.Add( default="default", ) +vars.Add( + "APPSRC", + help="Application source directory for app to build & upload", + default="", +) + +# List of tuples (directory, add_to_global_include_path) +vars.Add( + "APPDIRS", + help="Directories to search for firmware components & external apps", + default=[ + ("applications", False), + ("applications/services", True), + ("applications/main", True), + ("applications/settings", False), + ("applications/system", False), + ("applications/debug", False), + ("applications/plugins", False), + ("applications_user", False), + ], +) + Return("vars") diff --git a/site_scons/extapps.scons b/site_scons/extapps.scons new file mode 100644 index 00000000000..cfffda04f62 --- /dev/null +++ b/site_scons/extapps.scons @@ -0,0 +1,94 @@ +Import("ENV") + + +from fbt.appmanifest import FlipperAppType + +appenv = ENV.Clone( + tools=[("fbt_extapps", {"EXT_APPS_WORK_DIR": ENV.subst("${BUILD_DIR}/.extapps")})] +) + +appenv.Replace( + LINKER_SCRIPT="application-ext", +) + +appenv.AppendUnique( + CCFLAGS=[ + "-ggdb3", + "-mword-relocations", + "-mlong-calls", + "-fno-common", + "-nostdlib", + "-fvisibility=hidden", + ], + LINKFLAGS=[ + "-Ur", + "-Wl,-Ur", + # "-Wl,--orphan-handling=error", + "-Bsymbolic", + "-nostartfiles", + "-mlong-calls", + "-fno-common", + "-nostdlib", + "-Wl,--gc-sections", + "-Wl,--no-export-dynamic", + "-fvisibility=hidden", + "-Wl,-e${APP_ENTRY}", + "-Xlinker", + "-Map=${TARGET}.map", + "-specs=nano.specs", + "-specs=nosys.specs", + ], + LIBS=[ + "m", + "gcc", + "stdc++", + "supc++", + ], +) + + +extapps = appenv["_extapps"] = { + "compact": {}, + "debug": {}, + "validators": {}, + "dist": {}, +} + + +def build_app_as_external(env, appdef): + compact_elf, debug_elf, validator = env.BuildAppElf(appdef) + extapps["compact"][appdef.appid] = compact_elf + extapps["debug"][appdef.appid] = debug_elf + extapps["validators"][appdef.appid] = validator + extapps["dist"][appdef.appid] = (appdef.fap_category, compact_elf) + + +apps_to_build_as_faps = [FlipperAppType.PLUGIN, FlipperAppType.EXTERNAL] +if appenv["DEBUG_TOOLS"]: + apps_to_build_as_faps.append(FlipperAppType.DEBUG) + +for apptype in apps_to_build_as_faps: + for app in appenv["APPBUILD"].get_apps_of_type(apptype, True): + build_app_as_external(appenv, app) + +# Ugly access to global option +if extra_app_list := GetOption("extra_ext_apps"): + for extra_app in extra_app_list.split(","): + build_app_as_external(appenv, appenv["APPMGR"].get(extra_app)) + + +if appenv["FORCE"]: + appenv.AlwaysBuild(extapps["compact"].values()) + +Alias(appenv["FIRMWARE_BUILD_CFG"] + "_extapps", extapps["compact"].values()) + +if appsrc := appenv.subst("$APPSRC"): + app_manifest, fap_file = appenv.GetExtAppFromPath(appsrc) + appenv.PhonyTarget( + "launch_app", + '${PYTHON3} scripts/runfap.py ${SOURCE} --fap_dst_dir "/ext/apps/${FAP_CATEGORY}"', + source=fap_file, + FAP_CATEGORY=app_manifest.fap_category, + ) + +Return("extapps") diff --git a/site_scons/fbt/appmanifest.py b/site_scons/fbt/appmanifest.py index aacf248eccd..a41717d6a37 100644 --- a/site_scons/fbt/appmanifest.py +++ b/site_scons/fbt/appmanifest.py @@ -1,5 +1,5 @@ from dataclasses import dataclass, field -from typing import List, Optional +from typing import List, Optional, Tuple from enum import Enum import os @@ -25,7 +25,7 @@ class FlipperAppType(Enum): class FlipperApplication: appid: str apptype: FlipperAppType - name: Optional[str] = None + name: Optional[str] = "" entry_point: Optional[str] = None flags: List[str] = field(default_factory=lambda: ["Default"]) cdefines: List[str] = field(default_factory=list) @@ -35,7 +35,14 @@ class FlipperApplication: stack_size: int = 2048 icon: Optional[str] = None order: int = 0 - _appdir: Optional[str] = None + sdk_headers: List[str] = field(default_factory=list) + version: Tuple[int] = field(default_factory=lambda: (0, 0)) + sources: List[str] = field(default_factory=lambda: ["*.c*"]) + fap_icon: Optional[str] = None + fap_libs: List[str] = field(default_factory=list) + fap_category: str = "" + _appdir: Optional[object] = None + _apppath: Optional[str] = None class AppManager: @@ -50,7 +57,13 @@ def get(self, appname: str): f"Missing application manifest for '{appname}'" ) - def load_manifest(self, app_manifest_path: str, app_dir_name: str): + def find_by_appdir(self, appdir: str): + for app in self.known_apps.values(): + if app._appdir.name == appdir: + return app + return None + + def load_manifest(self, app_manifest_path: str, app_dir_node: object): if not os.path.exists(app_manifest_path): raise FlipperManifestException( f"App manifest not found at path {app_manifest_path}" @@ -61,7 +74,14 @@ def load_manifest(self, app_manifest_path: str, app_dir_name: str): def App(*args, **kw): nonlocal app_manifests - app_manifests.append(FlipperApplication(*args, **kw, _appdir=app_dir_name)) + app_manifests.append( + FlipperApplication( + *args, + **kw, + _appdir=app_dir_node, + _apppath=os.path.dirname(app_manifest_path), + ), + ) try: with open(app_manifest_path, "rt") as manifest_file: @@ -172,19 +192,32 @@ def get_apps_cdefs(self): cdefs.update(app.cdefines) return sorted(list(cdefs)) - def get_apps_of_type(self, apptype: FlipperAppType): + def get_sdk_headers(self): + sdk_headers = [] + for app in self.apps: + sdk_headers.extend([app._appdir.File(header) for header in app.sdk_headers]) + return sdk_headers + + def get_apps_of_type(self, apptype: FlipperAppType, all_known: bool = False): return sorted( - filter(lambda app: app.apptype == apptype, self.apps), + filter( + lambda app: app.apptype == apptype, + self.appmgr.known_apps.values() if all_known else self.apps, + ), key=lambda app: app.order, ) + def get_builtin_apps(self): + return list( + filter(lambda app: app.apptype in self.BUILTIN_APP_TYPES, self.apps) + ) + def get_builtin_app_folders(self): return sorted( set( - app._appdir - for app in filter( - lambda app: app.apptype in self.BUILTIN_APP_TYPES, self.apps - ) + (app._appdir, source_type) + for app in self.get_builtin_apps() + for source_type in app.sources ) ) diff --git a/site_scons/fbt/elfmanifest.py b/site_scons/fbt/elfmanifest.py new file mode 100644 index 00000000000..2f54810d98a --- /dev/null +++ b/site_scons/fbt/elfmanifest.py @@ -0,0 +1,85 @@ +from dataclasses import dataclass +import os + +import struct +from dataclasses import dataclass, field + +from .appmanifest import FlipperApplication + + +_MANIFEST_MAGIC = 0x52474448 + + +@dataclass +class ElfManifestBaseHeader: + manifest_version: int + api_version: int + hardware_target_id: int + + manifest_magic: int = 0x52474448 + + def as_bytes(self): + return struct.pack( + " 32: + raise ValueError( + f"Flipper app icon must be 32 bytes or less, but {len(image.data)} bytes were given" + ) + image_data = image.data + + app_version_as_int = ((app_manifest.version[0] & 0xFFFF) << 16) | ( + app_manifest.version[1] & 0xFFFF + ) + + data = ElfManifestBaseHeader( + manifest_version=1, + api_version=sdk_version, + hardware_target_id=hardware_target, + ).as_bytes() + data += ElfManifestV1( + stack_size=app_manifest.stack_size, + app_version=app_version_as_int, + name=app_manifest.name, + icon=image_data, + ).as_bytes() + + return data diff --git a/site_scons/fbt/sdk.py b/site_scons/fbt/sdk.py new file mode 100644 index 00000000000..2e951040d6c --- /dev/null +++ b/site_scons/fbt/sdk.py @@ -0,0 +1,514 @@ +import operator +import os +import csv +import operator + +from enum import Enum, auto +from typing import List, Set, ClassVar, Any +from dataclasses import dataclass, field + +from cxxheaderparser.parser import CxxParser + + +# 'Fixing' complaints about typedefs +CxxParser._fundamentals.discard("wchar_t") + +from cxxheaderparser.types import ( + EnumDecl, + Field, + ForwardDecl, + FriendDecl, + Function, + Method, + Typedef, + UsingAlias, + UsingDecl, + Variable, + Pointer, + Type, + PQName, + NameSpecifier, + FundamentalSpecifier, + Parameter, + Array, + Value, + Token, + FunctionType, +) + +from cxxheaderparser.parserstate import ( + State, + EmptyBlockState, + ClassBlockState, + ExternBlockState, + NamespaceBlockState, +) + + +@dataclass(frozen=True) +class ApiEntryFunction: + name: str + returns: str + params: str + + csv_type: ClassVar[str] = "Function" + + def dictify(self): + return dict(name=self.name, type=self.returns, params=self.params) + + +@dataclass(frozen=True) +class ApiEntryVariable: + name: str + var_type: str + + csv_type: ClassVar[str] = "Variable" + + def dictify(self): + return dict(name=self.name, type=self.var_type, params=None) + + +@dataclass(frozen=True) +class ApiHeader: + name: str + + csv_type: ClassVar[str] = "Header" + + def dictify(self): + return dict(name=self.name, type=None, params=None) + + +@dataclass +class ApiEntries: + # These are sets, to avoid creating duplicates when we have multiple + # declarations with same signature + functions: Set[ApiEntryFunction] = field(default_factory=set) + variables: Set[ApiEntryVariable] = field(default_factory=set) + headers: Set[ApiHeader] = field(default_factory=set) + + +class SymbolManager: + def __init__(self): + self.api = ApiEntries() + self.name_hashes = set() + + # Calculate hash of name and raise exception if it already is in the set + def _name_check(self, name: str): + name_hash = gnu_sym_hash(name) + if name_hash in self.name_hashes: + raise Exception(f"Hash collision on {name}") + self.name_hashes.add(name_hash) + + def add_function(self, function_def: ApiEntryFunction): + if function_def in self.api.functions: + return + self._name_check(function_def.name) + self.api.functions.add(function_def) + + def add_variable(self, variable_def: ApiEntryVariable): + if variable_def in self.api.variables: + return + self._name_check(variable_def.name) + self.api.variables.add(variable_def) + + def add_header(self, header: str): + self.api.headers.add(ApiHeader(header)) + + +def gnu_sym_hash(name: str): + h = 0x1505 + for c in name: + h = (h << 5) + h + ord(c) + return str(hex(h))[-8:] + + +class SdkCollector: + def __init__(self): + self.symbol_manager = SymbolManager() + + def add_header_to_sdk(self, header: str): + self.symbol_manager.add_header(header) + + def process_source_file_for_sdk(self, file_path: str): + visitor = SdkCxxVisitor(self.symbol_manager) + with open(file_path, "rt") as f: + content = f.read() + parser = CxxParser(file_path, content, visitor, None) + parser.parse() + + def get_api(self): + return self.symbol_manager.api + + +def stringify_array_dimension(size_descr): + if not size_descr: + return "" + return stringify_descr(size_descr) + + +def stringify_array_descr(type_descr): + assert isinstance(type_descr, Array) + return ( + stringify_descr(type_descr.array_of), + stringify_array_dimension(type_descr.size), + ) + + +def stringify_descr(type_descr): + if isinstance(type_descr, (NameSpecifier, FundamentalSpecifier)): + return type_descr.name + elif isinstance(type_descr, PQName): + return "::".join(map(stringify_descr, type_descr.segments)) + elif isinstance(type_descr, Pointer): + # Hack + if isinstance(type_descr.ptr_to, FunctionType): + return stringify_descr(type_descr.ptr_to) + return f"{stringify_descr(type_descr.ptr_to)}*" + elif isinstance(type_descr, Type): + return ( + f"{'const ' if type_descr.const else ''}" + f"{'volatile ' if type_descr.volatile else ''}" + f"{stringify_descr(type_descr.typename)}" + ) + elif isinstance(type_descr, Parameter): + return stringify_descr(type_descr.type) + elif isinstance(type_descr, Array): + # Hack for 2d arrays + if isinstance(type_descr.array_of, Array): + argtype, dimension = stringify_array_descr(type_descr.array_of) + return ( + f"{argtype}[{stringify_array_dimension(type_descr.size)}][{dimension}]" + ) + return f"{stringify_descr(type_descr.array_of)}[{stringify_array_dimension(type_descr.size)}]" + elif isinstance(type_descr, Value): + return " ".join(map(stringify_descr, type_descr.tokens)) + elif isinstance(type_descr, FunctionType): + return f"{stringify_descr(type_descr.return_type)} (*)({', '.join(map(stringify_descr, type_descr.parameters))})" + elif isinstance(type_descr, Token): + return type_descr.value + elif type_descr is None: + return "" + else: + raise Exception("unsupported type_descr: %s" % type_descr) + + +class SdkCxxVisitor: + def __init__(self, symbol_manager: SymbolManager): + self.api = symbol_manager + + def on_variable(self, state: State, v: Variable) -> None: + if not v.extern: + return + + self.api.add_variable( + ApiEntryVariable( + stringify_descr(v.name), + stringify_descr(v.type), + ) + ) + + def on_function(self, state: State, fn: Function) -> None: + if fn.inline or fn.has_body: + return + + self.api.add_function( + ApiEntryFunction( + stringify_descr(fn.name), + stringify_descr(fn.return_type), + ", ".join(map(stringify_descr, fn.parameters)) + + (", ..." if fn.vararg else ""), + ) + ) + + def on_define(self, state: State, content: str) -> None: + pass + + def on_pragma(self, state: State, content: str) -> None: + pass + + def on_include(self, state: State, filename: str) -> None: + pass + + def on_empty_block_start(self, state: EmptyBlockState) -> None: + pass + + def on_empty_block_end(self, state: EmptyBlockState) -> None: + pass + + def on_extern_block_start(self, state: ExternBlockState) -> None: + pass + + def on_extern_block_end(self, state: ExternBlockState) -> None: + pass + + def on_namespace_start(self, state: NamespaceBlockState) -> None: + pass + + def on_namespace_end(self, state: NamespaceBlockState) -> None: + pass + + def on_forward_decl(self, state: State, fdecl: ForwardDecl) -> None: + pass + + def on_typedef(self, state: State, typedef: Typedef) -> None: + pass + + def on_using_namespace(self, state: State, namespace: List[str]) -> None: + pass + + def on_using_alias(self, state: State, using: UsingAlias) -> None: + pass + + def on_using_declaration(self, state: State, using: UsingDecl) -> None: + pass + + def on_enum(self, state: State, enum: EnumDecl) -> None: + pass + + def on_class_start(self, state: ClassBlockState) -> None: + pass + + def on_class_field(self, state: State, f: Field) -> None: + pass + + def on_class_method(self, state: ClassBlockState, method: Method) -> None: + pass + + def on_class_friend(self, state: ClassBlockState, friend: FriendDecl) -> None: + pass + + def on_class_end(self, state: ClassBlockState) -> None: + pass + + +@dataclass(frozen=True) +class SdkVersion: + major: int = 0 + minor: int = 0 + + csv_type: ClassVar[str] = "Version" + + def __str__(self) -> str: + return f"{self.major}.{self.minor}" + + def as_int(self) -> int: + return ((self.major & 0xFFFF) << 16) | (self.minor & 0xFFFF) + + @staticmethod + def from_str(s: str) -> "SdkVersion": + major, minor = s.split(".") + return SdkVersion(int(major), int(minor)) + + def dictify(self) -> dict: + return dict(name=str(self), type=None, params=None) + + +class VersionBump(Enum): + NONE = auto() + MAJOR = auto() + MINOR = auto() + + +class ApiEntryState(Enum): + PENDING = "?" + APPROVED = "+" + DISABLED = "-" + # Special value for API version entry so users have less incentive to edit it + VERSION_PENDING = "v" + + +# Class that stores all known API entries, both enabled and disabled. +# Also keeps track of API versioning +# Allows comparison and update from newly-generated API +class SdkCache: + CSV_FIELD_NAMES = ("entry", "status", "name", "type", "params") + + def __init__(self, cache_file: str, load_version_only=False): + self.cache_file_name = cache_file + self.version = SdkVersion(0, 0) + self.sdk = ApiEntries() + self.disabled_entries = set() + self.new_entries = set() + self.loaded_dirty = False + self.loaded_dirty_version = False + + self.version_action = VersionBump.NONE + self._load_version_only = load_version_only + self.load_cache() + + def is_buildable(self) -> bool: + return ( + self.version != SdkVersion(0, 0) + and self.version_action == VersionBump.NONE + and not self.loaded_dirty + and not self.new_entries + ) + + def _filter_enabled(self, sdk_entries): + return sorted( + filter(lambda e: e not in self.disabled_entries, sdk_entries), + key=operator.attrgetter("name"), + ) + + def get_valid_names(self): + syms = set(map(lambda e: e.name, self.get_functions())) + syms.update(map(lambda e: e.name, self.get_variables())) + return syms + + def get_functions(self): + return self._filter_enabled(self.sdk.functions) + + def get_variables(self): + return self._filter_enabled(self.sdk.variables) + + def get_headers(self): + return self._filter_enabled(self.sdk.headers) + + def _get_entry_status(self, entry) -> str: + if entry in self.disabled_entries: + return ApiEntryState.DISABLED + elif entry in self.new_entries: + if isinstance(entry, SdkVersion): + return ApiEntryState.VERSION_PENDING + return ApiEntryState.PENDING + else: + return ApiEntryState.APPROVED + + def _format_entry(self, obj): + obj_dict = obj.dictify() + obj_dict.update( + dict( + entry=obj.csv_type, + status=self._get_entry_status(obj).value, + ) + ) + return obj_dict + + def save(self) -> None: + if self._load_version_only: + raise Exception("Only SDK version was loaded, cannot save") + + version_is_clean = True + if self.loaded_dirty: + # There are still new entries and version was already updated + version_is_clean = False + + if self.version_action == VersionBump.MINOR: + self.version = SdkVersion(self.version.major, self.version.minor + 1) + version_is_clean = False + elif self.version_action == VersionBump.MAJOR: + self.version = SdkVersion(self.version.major + 1, 0) + version_is_clean = False + + if version_is_clean: + print(f"API version {self.version} is up to date") + else: + self.new_entries.add(self.version) + print( + f"API version is still WIP: {self.version}. Review the changes and re-run command." + ) + print(f"Entries to review:") + print( + "\n".join( + map( + str, + filter( + lambda e: not isinstance(e, SdkVersion), self.new_entries + ), + ) + ) + ) + + if not version_is_clean or self.loaded_dirty_version: + # Regenerate cache file + str_cache_entries = [self.version] + name_getter = operator.attrgetter("name") + str_cache_entries.extend(sorted(self.sdk.headers, key=name_getter)) + str_cache_entries.extend(sorted(self.sdk.functions, key=name_getter)) + str_cache_entries.extend(sorted(self.sdk.variables, key=name_getter)) + + with open(self.cache_file_name, "w", newline="") as f: + writer = csv.DictWriter(f, fieldnames=SdkCache.CSV_FIELD_NAMES) + writer.writeheader() + + for entry in str_cache_entries: + writer.writerow(self._format_entry(entry)) + + def _process_entry(self, entry_dict: dict) -> None: + entry_class = entry_dict["entry"] + entry_status = entry_dict["status"] + entry_name = entry_dict["name"] + + entry = None + if entry_class == SdkVersion.csv_type: + self.version = SdkVersion.from_str(entry_name) + if entry_status == ApiEntryState.VERSION_PENDING.value: + self.loaded_dirty_version = True + elif entry_class == ApiHeader.csv_type: + self.sdk.headers.add(entry := ApiHeader(entry_name)) + elif entry_class == ApiEntryFunction.csv_type: + self.sdk.functions.add( + entry := ApiEntryFunction( + entry_name, + entry_dict["type"], + entry_dict["params"], + ) + ) + elif entry_class == ApiEntryVariable.csv_type: + self.sdk.variables.add( + entry := ApiEntryVariable(entry_name, entry_dict["type"]) + ) + else: + print(entry_dict) + raise Exception("Unknown entry type: %s" % entry_class) + + if entry is None: + return + + if entry_status == ApiEntryState.DISABLED.value: + self.disabled_entries.add(entry) + elif entry_status == ApiEntryState.PENDING.value: + self.new_entries.add(entry) + + def load_cache(self) -> None: + if not os.path.exists(self.cache_file_name): + raise Exception( + f"Cannot load symbol cache '{self.cache_file_name}'! File does not exist" + ) + + with open(self.cache_file_name, "r") as f: + reader = csv.DictReader(f) + for row in reader: + self._process_entry(row) + if self._load_version_only and row.get("entry") == SdkVersion.csv_type: + break + self.loaded_dirty = bool(self.new_entries) + + def sync_sets(self, known_set: Set[Any], new_set: Set[Any]): + new_entries = new_set - known_set + if new_entries: + print(f"New: {new_entries}") + known_set |= new_entries + self.new_entries |= new_entries + if self.version_action == VersionBump.NONE: + self.version_action = VersionBump.MINOR + removed_entries = known_set - new_set + if removed_entries: + print(f"Removed: {removed_entries}") + known_set -= removed_entries + # If any of removed entries was part of active API, that's a major bump + if any( + filter( + lambda e: e not in self.disabled_entries + and e not in self.new_entries, + removed_entries, + ) + ): + self.version_action = VersionBump.MAJOR + self.disabled_entries -= removed_entries + self.new_entries -= removed_entries + + def validate_api(self, api: ApiEntries) -> None: + self.sync_sets(self.sdk.headers, api.headers) + self.sync_sets(self.sdk.functions, api.functions) + self.sync_sets(self.sdk.variables, api.variables) diff --git a/site_scons/site_init.py b/site_scons/site_init.py index 817269e286a..2d83d8816c8 100644 --- a/site_scons/site_init.py +++ b/site_scons/site_init.py @@ -5,6 +5,7 @@ import atexit sys.path.insert(0, os.path.join(os.getcwd(), "scripts")) +sys.path.insert(0, os.path.join(os.getcwd(), "lib/cxxheaderparser")) def bf_to_str(bf): diff --git a/site_scons/site_tools/blackmagic.py b/site_scons/site_tools/blackmagic.py index ec48c15fd08..0b11426834a 100644 --- a/site_scons/site_tools/blackmagic.py +++ b/site_scons/site_tools/blackmagic.py @@ -63,7 +63,7 @@ def __str__(self): if probe := self.get_serial() or self.get_networked(): return probe - raise Exception("Please specify BLACKMAGIC=...") + raise StopError("Please specify BLACKMAGIC=...") def generate(env): diff --git a/site_scons/site_tools/fbt_apps.py b/site_scons/site_tools/fbt_apps.py index 3dc35049b63..ef5e9b9d934 100644 --- a/site_scons/site_tools/fbt_apps.py +++ b/site_scons/site_tools/fbt_apps.py @@ -18,18 +18,26 @@ def LoadApplicationManifests(env): appmgr = env["APPMGR"] = AppManager() - for entry in env.Glob("#/applications/*", ondisk=True, source=True): - if isinstance(entry, SCons.Node.FS.Dir) and not str(entry).startswith("."): - try: - app_manifest_file_path = os.path.join(entry.abspath, "application.fam") - appmgr.load_manifest(app_manifest_file_path, entry.name) - env.Append(PY_LINT_SOURCES=[app_manifest_file_path]) - except FlipperManifestException as e: - warn(WarningOnByDefault, str(e)) + for app_dir, _ in env["APPDIRS"]: + app_dir_node = env.Dir("#").Dir(app_dir) + + for entry in app_dir_node.glob("*", ondisk=True, source=True): + if isinstance(entry, SCons.Node.FS.Dir) and not str(entry).startswith("."): + try: + app_manifest_file_path = os.path.join( + entry.abspath, "application.fam" + ) + appmgr.load_manifest(app_manifest_file_path, entry) + env.Append(PY_LINT_SOURCES=[app_manifest_file_path]) + except FlipperManifestException as e: + warn(WarningOnByDefault, str(e)) def PrepareApplicationsBuild(env): - env["APPBUILD"] = env["APPMGR"].filter_apps(env["APPS"]) + appbuild = env["APPBUILD"] = env["APPMGR"].filter_apps(env["APPS"]) + env.Append( + SDK_HEADERS=appbuild.get_sdk_headers(), + ) env["APPBUILD_DUMP"] = env.Action( DumpApplicationConfig, "\tINFO\t", diff --git a/site_scons/site_tools/fbt_assets.py b/site_scons/site_tools/fbt_assets.py index c844db36b52..877948471bb 100644 --- a/site_scons/site_tools/fbt_assets.py +++ b/site_scons/site_tools/fbt_assets.py @@ -13,11 +13,11 @@ def icons_emitter(target, source, env): "compiled/assets_icons.c", "compiled/assets_icons.h", ] + source = env.GlobRecursive("*.*", env["ICON_SRC_DIR"]) return target, source def proto_emitter(target, source, env): - out_path = target[0].path target = [] for src in source: basename = os.path.splitext(src.name)[0] @@ -109,7 +109,7 @@ def generate(env): BUILDERS={ "IconBuilder": Builder( action=Action( - '${PYTHON3} "${ASSETS_COMPILER}" icons ${SOURCE.posix} ${TARGET.dir.posix}', + '${PYTHON3} "${ASSETS_COMPILER}" icons ${ICON_SRC_DIR} ${TARGET.dir}', "${ICONSCOMSTR}", ), emitter=icons_emitter, diff --git a/site_scons/site_tools/fbt_dist.py b/site_scons/site_tools/fbt_dist.py index 399b7ecdd60..2b5c83dfff5 100644 --- a/site_scons/site_tools/fbt_dist.py +++ b/site_scons/site_tools/fbt_dist.py @@ -4,7 +4,7 @@ from SCons.Defaults import Touch -def get_variant_dirname(env, project=None): +def GetProjetDirName(env, project=None): parts = [f"f{env['TARGET_HW']}"] if project: parts.append(project) @@ -21,7 +21,7 @@ def get_variant_dirname(env, project=None): def create_fw_build_targets(env, configuration_name): - flavor = get_variant_dirname(env, configuration_name) + flavor = GetProjetDirName(env, configuration_name) build_dir = env.Dir("build").Dir(flavor).abspath return env.SConscript( "firmware.scons", @@ -49,7 +49,7 @@ def AddFwProject(env, base_env, fw_type, fw_env_key): ], ) - env.Replace(DIST_DIR=get_variant_dirname(env)) + env.Replace(DIST_DIR=env.GetProjetDirName()) return project_env @@ -115,6 +115,7 @@ def generate(env): env.AddMethod(AddFwProject) env.AddMethod(DistCommand) env.AddMethod(AddOpenOCDFlashTarget) + env.AddMethod(GetProjetDirName) env.AddMethod(AddJFlashTarget) env.AddMethod(AddUsbFlashTarget) diff --git a/site_scons/site_tools/fbt_extapps.py b/site_scons/site_tools/fbt_extapps.py index 17c4bf6c545..4b584530525 100644 --- a/site_scons/site_tools/fbt_extapps.py +++ b/site_scons/site_tools/fbt_extapps.py @@ -1,29 +1,151 @@ +from SCons.Builder import Builder +from SCons.Action import Action +from SCons.Errors import UserError +import SCons.Warnings + import os +import pathlib +from fbt.elfmanifest import assemble_manifest_data +from fbt.sdk import SdkCache +import itertools def BuildAppElf(env, app): work_dir = env.subst("$EXT_APPS_WORK_DIR") - app_target_name = os.path.join(work_dir, app.appid) + app_alias = f"{env['FIRMWARE_BUILD_CFG']}_{app.appid}" - app_elf = env.Program( - app_target_name, - env.GlobRecursive("*.c*", os.path.join(work_dir, app._appdir)), + app_original_elf = os.path.join(work_dir, f"{app.appid}_d") + app_sources = list( + itertools.chain.from_iterable( + env.GlobRecursive(source_type, os.path.join(work_dir, app._appdir.relpath)) + for source_type in app.sources + ) + ) + app_elf_raw = env.Program( + app_original_elf, + app_sources, APP_ENTRY=app.entry_point, + LIBS=env["LIBS"] + app.fap_libs, ) - app_elf_dump = env.ObjDump(app_target_name) + + app_elf_dump = env.ObjDump(app_elf_raw) env.Alias(f"{app_alias}_list", app_elf_dump) - app_stripped_elf = env.ELFStripper( - os.path.join(env.subst("$PLUGIN_ELF_DIR"), app.appid), app_elf + app_elf_augmented = env.EmbedAppMetadata( + os.path.join(env.subst("$PLUGIN_ELF_DIR"), app.appid), + app_elf_raw, + APP=app, ) - env.Alias(app_alias, app_stripped_elf) - return app_stripped_elf + + env.Depends(app_elf_augmented, [env["SDK_DEFINITION"], env.Value(app)]) + if app.fap_icon: + env.Depends( + app_elf_augmented, + env.File(f"{app._apppath}/{app.fap_icon}"), + ) + env.Alias(app_alias, app_elf_augmented) + + app_elf_import_validator = env.ValidateAppImports(app_elf_augmented) + env.AlwaysBuild(app_elf_import_validator) + return (app_elf_augmented, app_elf_raw, app_elf_import_validator) + + +def prepare_app_metadata(target, source, env): + sdk_cache = SdkCache(env.subst("$SDK_DEFINITION"), load_version_only=True) + + if not sdk_cache.is_buildable(): + raise UserError( + "SDK version is not finalized, please review changes and re-run operation" + ) + + app = env["APP"] + meta_file_name = source[0].path + ".meta" + with open(meta_file_name, "wb") as f: + # f.write(f"hello this is {app}") + f.write( + assemble_manifest_data( + app_manifest=app, + hardware_target=int(env.subst("$TARGET_HW")), + sdk_version=sdk_cache.version.as_int(), + ) + ) + + +def validate_app_imports(target, source, env): + sdk_cache = SdkCache(env.subst("$SDK_DEFINITION"), load_version_only=False) + app_syms = set() + with open(target[0].path, "rt") as f: + for line in f: + app_syms.add(line.split()[0]) + unresolved_syms = app_syms - sdk_cache.get_valid_names() + if unresolved_syms: + SCons.Warnings.warn( + SCons.Warnings.LinkWarning, + f"{source[0].path}: app won't run. Unresolved symbols: {unresolved_syms}", + ) + + +def GetExtAppFromPath(env, app_dir): + if not app_dir: + raise UserError("APPSRC= not set") + + appmgr = env["APPMGR"] + + app = None + for dir_part in reversed(pathlib.Path(app_dir).parts): + if app := appmgr.find_by_appdir(dir_part): + break + if not app: + raise UserError(f"Failed to resolve application for given APPSRC={app_dir}") + + app_elf = env["_extapps"]["compact"].get(app.appid, None) + if not app_elf: + raise UserError(f"No external app found for {app.appid}") + + return (app, app_elf[0]) def generate(env, **kw): - env.SetDefault(EXT_APPS_WORK_DIR=kw.get("EXT_APPS_WORK_DIR", ".extapps")) - env.VariantDir(env.subst("$EXT_APPS_WORK_DIR"), ".", duplicate=False) + env.SetDefault(EXT_APPS_WORK_DIR=kw.get("EXT_APPS_WORK_DIR")) + env.VariantDir(env.subst("$EXT_APPS_WORK_DIR"), env.Dir("#"), duplicate=False) + env.AddMethod(BuildAppElf) + env.AddMethod(GetExtAppFromPath) + env.Append( + BUILDERS={ + "EmbedAppMetadata": Builder( + action=[ + Action(prepare_app_metadata, "$APPMETA_COMSTR"), + Action( + "${OBJCOPY} " + "--remove-section .ARM.attributes " + "--add-section .fapmeta=${SOURCE}.meta " + "--set-section-flags .fapmeta=contents,noload,readonly,data " + "--strip-debug --strip-unneeded " + "--add-gnu-debuglink=${SOURCE} " + "${SOURCES} ${TARGET}", + "$APPMETAEMBED_COMSTR", + ), + ], + suffix=".fap", + src_suffix=".elf", + ), + "ValidateAppImports": Builder( + action=[ + Action( + "@${NM} -P -u ${SOURCE} > ${TARGET}", + None, # "$APPDUMP_COMSTR", + ), + Action( + validate_app_imports, + None, # "$APPCHECK_COMSTR", + ), + ], + suffix=".impsyms", + src_suffix=".fap", + ), + } + ) def exists(env): diff --git a/site_scons/site_tools/fbt_sdk.py b/site_scons/site_tools/fbt_sdk.py new file mode 100644 index 00000000000..26d663650a4 --- /dev/null +++ b/site_scons/site_tools/fbt_sdk.py @@ -0,0 +1,208 @@ +from SCons.Builder import Builder +from SCons.Action import Action +from SCons.Errors import UserError + +# from SCons.Scanner import C +from SCons.Script import Mkdir, Copy, Delete, Entry +from SCons.Util import LogicalLines + +import os.path +import posixpath +import pathlib + +from fbt.sdk import SdkCollector, SdkCache + + +def prebuild_sdk_emitter(target, source, env): + target.append(env.ChangeFileExtension(target[0], ".d")) + return target, source + + +def prebuild_sdk_create_origin_file(target, source, env): + mega_file = env.subst("${TARGET}.c", target=target[0]) + with open(mega_file, "wt") as sdk_c: + sdk_c.write("\n".join(f"#include <{h.path}>" for h in env["SDK_HEADERS"])) + + +class SdkTreeBuilder: + def __init__(self, env, target, source) -> None: + self.env = env + self.target = target + self.source = source + + self.header_depends = [] + self.header_dirs = [] + + self.target_sdk_dir = env.subst("f${TARGET_HW}_sdk") + self.sdk_deploy_dir = target[0].Dir(self.target_sdk_dir) + + def _parse_sdk_depends(self): + deps_file = self.source[0] + with open(deps_file.path, "rt") as deps_f: + lines = LogicalLines(deps_f).readlines() + _, depends = lines[0].split(":", 1) + self.header_depends = list( + filter(lambda fname: fname.endswith(".h"), depends.split()), + ) + self.header_dirs = sorted( + set(map(os.path.normpath, map(os.path.dirname, self.header_depends))) + ) + + def _generate_sdk_meta(self): + filtered_paths = [self.target_sdk_dir] + full_fw_paths = list( + map( + os.path.normpath, + (self.env.Dir(inc_dir).relpath for inc_dir in self.env["CPPPATH"]), + ) + ) + + sdk_dirs = ", ".join(f"'{dir}'" for dir in self.header_dirs) + for dir in full_fw_paths: + if dir in sdk_dirs: + filtered_paths.append( + posixpath.normpath(posixpath.join(self.target_sdk_dir, dir)) + ) + + sdk_env = self.env.Clone() + sdk_env.Replace(CPPPATH=filtered_paths) + with open(self.target[0].path, "wt") as f: + cmdline_options = sdk_env.subst( + "$CCFLAGS $_CCCOMCOM", target=Entry("dummy") + ) + f.write(cmdline_options.replace("\\", "/")) + f.write("\n") + + def _create_deploy_commands(self): + dirs_to_create = set( + self.sdk_deploy_dir.Dir(dirpath) for dirpath in self.header_dirs + ) + actions = [ + Delete(self.sdk_deploy_dir), + Mkdir(self.sdk_deploy_dir), + ] + actions += [Mkdir(d) for d in dirs_to_create] + + actions += [ + Copy( + self.sdk_deploy_dir.File(h).path, + h, + ) + for h in self.header_depends + ] + return actions + + def generate_actions(self): + self._parse_sdk_depends() + self._generate_sdk_meta() + + return self._create_deploy_commands() + + +def deploy_sdk_tree(target, source, env, for_signature): + if for_signature: + return [] + + sdk_tree = SdkTreeBuilder(env, target, source) + return sdk_tree.generate_actions() + + +def gen_sdk_data(sdk_cache: SdkCache): + api_def = [] + api_def.extend( + (f"#include <{h.name}>" for h in sdk_cache.get_headers()), + ) + + api_def.append(f"const int elf_api_version = {sdk_cache.version.as_int()};") + + api_def.append( + "static constexpr auto elf_api_table = sort(create_array_t(" + ) + + api_lines = [] + for fun_def in sdk_cache.get_functions(): + api_lines.append( + f"API_METHOD({fun_def.name}, {fun_def.returns}, ({fun_def.params}))" + ) + + for var_def in sdk_cache.get_variables(): + api_lines.append(f"API_VARIABLE({var_def.name}, {var_def.var_type })") + + api_def.append(",\n".join(api_lines)) + + api_def.append("));") + return api_def + + +def _check_sdk_is_up2date(sdk_cache: SdkCache): + if not sdk_cache.is_buildable(): + raise UserError( + "SDK version is not finalized, please review changes and re-run operation" + ) + + +def validate_sdk_cache(source, target, env): + # print(f"Generating SDK for {source[0]} to {target[0]}") + current_sdk = SdkCollector() + current_sdk.process_source_file_for_sdk(source[0].path) + for h in env["SDK_HEADERS"]: + current_sdk.add_header_to_sdk(pathlib.Path(h.path).as_posix()) + + sdk_cache = SdkCache(target[0].path) + sdk_cache.validate_api(current_sdk.get_api()) + sdk_cache.save() + _check_sdk_is_up2date(sdk_cache) + + +def generate_sdk_symbols(source, target, env): + sdk_cache = SdkCache(source[0].path) + _check_sdk_is_up2date(sdk_cache) + + api_def = gen_sdk_data(sdk_cache) + with open(target[0].path, "wt") as f: + f.write("\n".join(api_def)) + + +def generate(env, **kw): + env.Append( + BUILDERS={ + "SDKPrebuilder": Builder( + emitter=prebuild_sdk_emitter, + action=[ + Action( + prebuild_sdk_create_origin_file, + "$SDK_PREGEN_COMSTR", + ), + Action( + "$CC -o $TARGET -E -P $CCFLAGS $_CCCOMCOM $SDK_PP_FLAGS -MMD ${TARGET}.c", + "$SDK_COMSTR", + ), + ], + suffix=".i", + ), + "SDKTree": Builder( + generator=deploy_sdk_tree, + src_suffix=".d", + ), + "SDKSymUpdater": Builder( + action=Action( + validate_sdk_cache, + "$SDKSYM_UPDATER_COMSTR", + ), + suffix=".csv", + src_suffix=".i", + ), + "SDKSymGenerator": Builder( + action=Action( + generate_sdk_symbols, + "$SDKSYM_GENERATOR_COMSTR", + ), + suffix=".h", + src_suffix=".csv", + ), + } + ) + + +def exists(env): + return True diff --git a/site_scons/site_tools/fwbin.py b/site_scons/site_tools/fwbin.py index 37e64e56da8..678b0499822 100644 --- a/site_scons/site_tools/fwbin.py +++ b/site_scons/site_tools/fwbin.py @@ -3,12 +3,14 @@ import SCons __OBJCOPY_ARM_BIN = "arm-none-eabi-objcopy" +__NM_ARM_BIN = "arm-none-eabi-nm" def generate(env): env.SetDefault( BIN2DFU="${ROOT_DIR.abspath}/scripts/bin2dfu.py", OBJCOPY=__OBJCOPY_ARM_BIN, # FIXME + NM=__NM_ARM_BIN, # FIXME ) env.Append( BUILDERS={ diff --git a/site_scons/site_tools/sconsmodular.py b/site_scons/site_tools/sconsmodular.py index 778c664e47c..b115706cbab 100644 --- a/site_scons/site_tools/sconsmodular.py +++ b/site_scons/site_tools/sconsmodular.py @@ -40,10 +40,15 @@ def PhonyTarget(env, name, action, source=None, **kw): return command +def ChangeFileExtension(env, fnode, ext): + return env.File(f"#{os.path.splitext(fnode.path)[0]}{ext}") + + def generate(env): env.AddMethod(BuildModule) env.AddMethod(BuildModules) env.AddMethod(PhonyTarget) + env.AddMethod(ChangeFileExtension) def exists(env): From 28beff1ab66892bafe73769b0c46dd56523bec4a Mon Sep 17 00:00:00 2001 From: hedger Date: Thu, 15 Sep 2022 17:40:26 +0400 Subject: [PATCH 054/824] Fbt: fixed gdb-py path for MacOS, docs: spelling fixes, codeowners (#1736) * fbt: fixed gdb-py path for MacOS, docs: spelling fixes * Gtihub: update codeowners Co-authored-by: Aleksandr Kutuzov --- .github/CODEOWNERS | 108 +++++++++++++--------------------- .vscode/example/settings.json | 2 +- applications/ReadMe.md | 5 +- documentation/AppsOnSDCard.md | 20 +++---- documentation/OTA.md | 22 +++---- 5 files changed, 66 insertions(+), 91 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 6dac0496ab5..c9b8ff3f593 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -2,82 +2,58 @@ * @skotopes @DrZlo13 @hedger # Apps -/applications/about/ @skotopes @DrZlo13 @hedger -/applications/accessor/ @skotopes @DrZlo13 @hedger -/applications/archive/ @skotopes @DrZlo13 @hedger @nminaylov -/applications/bad_usb/ @skotopes @DrZlo13 @hedger @nminaylov -/applications/bt/ @skotopes @DrZlo13 @hedger @gornekich -/applications/cli/ @skotopes @DrZlo13 @hedger @nminaylov -/applications/crypto/ @skotopes @DrZlo13 @hedger @nminaylov -/applications/debug_tools/ @skotopes @DrZlo13 @hedger -/applications/desktop/ @skotopes @DrZlo13 @hedger @nminaylov -/applications/dialogs/ @skotopes @DrZlo13 @hedger -/applications/dolphin/ @skotopes @DrZlo13 @hedger -/applications/gpio/ @skotopes @DrZlo13 @hedger @nminaylov -/applications/gui/ @skotopes @DrZlo13 @hedger -/applications/ibutton/ @skotopes @DrZlo13 @hedger @gsurkov -/applications/infrared/ @skotopes @DrZlo13 @hedger @gsurkov -/applications/input/ @skotopes @DrZlo13 @hedger -/applications/lfrfid/ @skotopes @DrZlo13 @hedger -/applications/lfrfid_debug/ @skotopes @DrZlo13 @hedger -/applications/loader/ @skotopes @DrZlo13 @hedger -/applications/music_player/ @skotopes @DrZlo13 @hedger -/applications/nfc/ @skotopes @DrZlo13 @hedger @gornekich -/applications/notification/ @skotopes @DrZlo13 @hedger -/applications/power/ @skotopes @DrZlo13 @hedger -/applications/rpc/ @skotopes @DrZlo13 @hedger @nminaylov -/applications/snake_game/ @skotopes @DrZlo13 @hedger -/applications/storage/ @skotopes @DrZlo13 @hedger -/applications/storage_settings/ @skotopes @DrZlo13 @hedger -/applications/subghz/ @skotopes @DrZlo13 @hedger @Skorpionm -/applications/system/ @skotopes @DrZlo13 @hedger -/applications/u2f/ @skotopes @DrZlo13 @hedger @nminaylov -/applications/unit_tests/ @skotopes @DrZlo13 @hedger -/applications/updater/ @skotopes @DrZlo13 @hedger - -# Assets -/assets/ @skotopes @DrZlo13 @hedger - -# Furi Core -/furi/ @skotopes @DrZlo13 @hedger - -# Debug tools and plugins -/debug/ @skotopes @DrZlo13 @hedger +/applications/debug/bt_debug_app/ @skotopes @DrZlo13 @hedger @gornekich +/applications/debug/accessor/ @skotopes @DrZlo13 @hedger @nminaylov +/applications/debug/battery_test_app/ @skotopes @DrZlo13 @hedger @gornekich +/applications/debug/bt_debug_app/ @skotopes @DrZlo13 @hedger @gornekich +/applications/debug/file_browser_test/ @skotopes @DrZlo13 @hedger @nminaylov +/applications/debug/lfrfid_debug/ @skotopes @DrZlo13 @hedger @nminaylov +/applications/debug/text_box_test/ @skotopes @DrZlo13 @hedger @nminaylov +/applications/debug/uart_echo/ @skotopes @DrZlo13 @hedger @nminaylov +/applications/debug/usb_mouse/ @skotopes @DrZlo13 @hedger @nminaylov +/applications/debug/usb_test/ @skotopes @DrZlo13 @hedger @nminaylov + +/applications/main/archive/ @skotopes @DrZlo13 @hedger @nminaylov +/applications/main/bad_usb/ @skotopes @DrZlo13 @hedger @nminaylov +/applications/main/gpio/ @skotopes @DrZlo13 @hedger @nminaylov +/applications/main/ibutton/ @skotopes @DrZlo13 @hedger @gsurkov +/applications/main/infrared/ @skotopes @DrZlo13 @hedger @gsurkov +/applications/main/nfc/ @skotopes @DrZlo13 @hedger @gornekich +/applications/main/subghz/ @skotopes @DrZlo13 @hedger @Skorpionm +/applications/main/u2f/ @skotopes @DrZlo13 @hedger @nminaylov + +/applications/plugins/bt_hid_app/ @skotopes @DrZlo13 @hedger @gornekich +/applications/plugins/picopass/ @skotopes @DrZlo13 @hedger @gornekich + +/applications/services/bt/ @skotopes @DrZlo13 @hedger @gornekich +/applications/services/cli/ @skotopes @DrZlo13 @hedger @nminaylov +/applications/services/crypto/ @skotopes @DrZlo13 @hedger @nminaylov +/applications/services/desktop/ @skotopes @DrZlo13 @hedger @nminaylov +/applications/services/dolphin/ @skotopes @DrZlo13 @hedger @nminaylov +/applications/services/power/ @skotopes @DrZlo13 @hedger @gornekich +/applications/services/rpc/ @skotopes @DrZlo13 @hedger @nminaylov + +/applications/services/bt_settings_app/ @skotopes @DrZlo13 @hedger @gornekich +/applications/services/desktop_settings/ @skotopes @DrZlo13 @hedger @nminaylov +/applications/services/dolphin_passport/ @skotopes @DrZlo13 @hedger @nminaylov +/applications/services/power_settings_app/ @skotopes @DrZlo13 @hedger @gornekich + +/applications/system/storage_move_to_sd/ @skotopes @DrZlo13 @hedger @nminaylov # Documentation /documentation/ @skotopes @DrZlo13 @hedger @drunkbatya - -# Firmware targets -/firmware/ @skotopes @DrZlo13 @hedger +/scripts/toolchain/ @skotopes @DrZlo13 @hedger @drunkbatya # Lib -/lib/FreeRTOS-Kernel/ @skotopes @DrZlo13 @hedger -/lib/FreeRTOS-glue/ @skotopes @DrZlo13 @hedger /lib/ST25RFAL002/ @skotopes @DrZlo13 @hedger @gornekich /lib/STM32CubeWB/ @skotopes @DrZlo13 @hedger @gornekich -/lib/app-scened-template/ @skotopes @DrZlo13 @hedger -/lib/callback-connector/ @skotopes @DrZlo13 @hedger /lib/digital_signal/ @skotopes @DrZlo13 @hedger @gornekich -/lib/drivers/ @skotopes @DrZlo13 @hedger -/lib/fatfs/ @skotopes @DrZlo13 @hedger -/lib/flipper_format/ @skotopes @DrZlo13 @hedger -/lib/fnv1a-hash/ @skotopes @DrZlo13 @hedger -/lib/heatshrink/ @skotopes @DrZlo13 @hedger /lib/infrared/ @skotopes @DrZlo13 @hedger @gsurkov +/lib/lfrfid/ @skotopes @DrZlo13 @hedger @nminaylov /lib/libusb_stm32/ @skotopes @DrZlo13 @hedger @nminaylov -/lib/littlefs/ @skotopes @DrZlo13 @hedger -/lib/lfs_config.h @skotopes @DrZlo13 @hedger +/lib/mbedtls/ @skotopes @DrZlo13 @hedger @nminaylov /lib/micro-ecc/ @skotopes @DrZlo13 @hedger @nminaylov -/lib/microtar/ @skotopes @DrZlo13 @hedger -/lib/mlib/ @skotopes @DrZlo13 @hedger -/lib/nanopb/ @skotopes @DrZlo13 @hedger +/lib/nanopb/ @skotopes @DrZlo13 @hedger @nminaylov /lib/nfc/ @skotopes @DrZlo13 @hedger @gornekich -/lib/one_wire/ @skotopes @DrZlo13 @hedger -/lib/qrcode/ @skotopes @DrZlo13 @hedger +/lib/one_wire/ @skotopes @DrZlo13 @hedger @gsurkov /lib/subghz/ @skotopes @DrZlo13 @hedger @Skorpionm -/lib/toolbox/ @skotopes @DrZlo13 @hedger -/lib/u8g2/ @skotopes @DrZlo13 @hedger -/lib/update_util/ @skotopes @DrZlo13 @hedger - -# Helper scripts -/scripts/ @skotopes @DrZlo13 @hedger diff --git a/.vscode/example/settings.json b/.vscode/example/settings.json index 0a05da67cc8..d2917a90ef8 100644 --- a/.vscode/example/settings.json +++ b/.vscode/example/settings.json @@ -14,7 +14,7 @@ "cortex-debug.openocdPath.osx": "${workspaceFolder}/toolchain/x86_64-darwin/openocd/bin/openocd", "cortex-debug.gdbPath.windows": "${workspaceFolder}/toolchain/i686-windows/bin/arm-none-eabi-gdb-py.bat", "cortex-debug.gdbPath.linux": "${workspaceFolder}/toolchain/x86_64-linux/bin/arm-none-eabi-gdb-py", - "cortex-debug.gdbPath.osx": "${workspaceFolder}/toolchain/x86_64-darwin/bin/arm-none-eabi-gdb", + "cortex-debug.gdbPath.osx": "${workspaceFolder}/toolchain/x86_64-darwin/bin/arm-none-eabi-gdb-py", "editor.formatOnSave": true, "files.associations": { "*.scons": "python", diff --git a/applications/ReadMe.md b/applications/ReadMe.md index aee612578ee..6224cb45a7a 100644 --- a/applications/ReadMe.md +++ b/applications/ReadMe.md @@ -1,8 +1,5 @@ # Structure -- `application.h` - Firmware application list header - - ## debug Applications for factory testing the Flipper. @@ -53,6 +50,8 @@ Extra apps for Plugins & App Loader menus. Background services providing system APIs to applications. +- `applications.h` - Firmware application list header + - `bt` - BLE service and application - `cli` - Console service and API - `crypto` - Crypto cli tools diff --git a/documentation/AppsOnSDCard.md b/documentation/AppsOnSDCard.md index 42d3871f40f..52582153073 100644 --- a/documentation/AppsOnSDCard.md +++ b/documentation/AppsOnSDCard.md @@ -1,20 +1,20 @@ -### FAP (Flipper Application Package) +# FAP (Flipper Application Package) [fbt](./fbt.md) has support for building applications as FAP files. FAP are essentially .elf executables with extra metadata and resources bundled in. FAPs are built with `firmware_extapps` (or `plugin_dist`) **`fbt`** targets. -FAPs do not depend on being ran on a specific firmware version. Compatibility is determined by the FAP's metadata, which includes the required [API version](#api-versioning). +FAPs do not depend on being run on a specific firmware version. Compatibility is determined by the FAP's metadata, which includes the required [API version](#api-versioning). ## How to set up an application to be built as a FAP FAPs are created and developed the same way as internal applications that are part of the firmware. -To build your application as a FAP, just create a folder with your app's source code in `applications_user` folder, then write its code the way you'd do when creating a regular built-in application. Then configure its `application.fam` manifest — and set its *apptype* to FlipperAppType.EXTERNAL. See [Application Manifests](./AppManifests.md#application-definition) for more details. +To build your application as a FAP, just create a folder with your app's source code in `applications_user`, then write its code the way you'd do when creating a regular built-in application. Then configure its `application.fam` manifest — and set its *apptype* to FlipperAppType.EXTERNAL. See [Application Manifests](./AppManifests.md#application-definition) for more details. * To build your application, run `./fbt firmware_{APPID}`, where APPID is your application's ID in its manifest. - * To build your app, upload it over USB & run it on Flipper, run `./fbt launch_app APPSRC=applications/path/to/app`. This action is configured in default [VSCode profile](../.vscode/ReadMe.md) as "Launch App on Flipper" build action (Ctrl+Shift+B menu). + * To build your app, then upload it over USB & run it on Flipper, use `./fbt launch_app APPSRC=applications/path/to/app`. This command is configured in default [VSCode profile](../.vscode/ReadMe.md) as "Launch App on Flipper" build action (Ctrl+Shift+B menu). * To build all FAPs, run `./fbt plugin_dist`. @@ -40,18 +40,18 @@ It is **important** that firmware and application build type (debug/release) mat ## How Flipper runs an application from SD card -Flipper's MCU cannot run code directly from external storage, so it needs to be copied to RAM first. This is done by the App Loader application, which is responsible for loading the FAP from SD card, verifying its integrity and compatibility and copying it to RAM. +Flipper's MCU cannot run code directly from external storage, so it needs to be copied to RAM first. That is done by the App Loader application, which is responsible for loading the FAP from SD card, verifying its integrity and compatibility, copying it to RAM and adjusting it for its new location. -Since FAP has to be loaded to RAM to be executed, the amount of RAM available for allocations from heap is reduced compared to running the same app from flash, as a part of firmware. Note that amount of occupied RAM is less than total FAP file size, since only code and data sections are allocated, and FAP file includes extra information only used at app load time. +Since FAP has to be loaded to RAM to be executed, the amount of RAM available for allocations from heap is reduced compared to running the same app from flash, as a part of firmware. Note that the amount of occupied RAM is less than total FAP file size, since only code and data sections are allocated, while FAP file includes extra information only used at app load time. -Applications are built for a specific API version. It is a part of hardware target's definition and contains a major and minor version number. Application loader checks if the application's major API version matches firmware's major API version. +Applications are built for a specific API version. It is a part of the hardware target's definition and contains a major and minor version number. Application loader checks if the application's major API version matches firmware's major API version. App loader allocates memory for the application and copies it to RAM, processing relocations and providing concrete addresses for imported symbols using the [symbol table](#symbol-table). Then it starts the application. ## API Versioning -Not all parts of firmware are available for external applications. Subset of available functions and variables is defined in "api_symbols.csv" file, which is a part of firmware target definition in `firmware/targets/` directory. +Not all parts of firmware are available for external applications. A subset of available functions and variables is defined in "api_symbols.csv" file, which is a part of firmware target definition in `firmware/targets/` directory. **`fbt`** uses semantic versioning for API versioning. Major version is incremented when there are breaking changes in the API, minor version is incremented when there are new features added. @@ -59,7 +59,7 @@ Breaking changes include: - removal of a function or a global variable; - changing the signature of a function. -API versioning is mostly automated by **`fbt`**. When rebuilding the firmware, **`fbt`** checks if there are any changes in the API exposed by headers gathered from `SDK_HEADERS`. If there are, it stops the build, adjusts API version and asks the user to go through the changes in .csv file. New entries are marked with "`?`" mark, and the user is supposed to change the mark to "`+`" for the entry to be exposed for FAPs, "`-`" for it to be unavailable. +API versioning is mostly automated by **`fbt`**. When rebuilding the firmware, **`fbt`** checks if there are any changes in the API exposed by headers gathered from `SDK_HEADERS`. If there are, it stops the build, adjusts the API version and asks the user to go through the changes in .csv file. New entries are marked with "`?`" mark, and the user is supposed to change the mark to "`+`" for the entry to be exposed for FAPs, "`-`" for it to be unavailable. **`fbt`** will not allow building a firmware until all "`?`" entries are changed to "`+`" or "`-`". @@ -67,6 +67,6 @@ API versioning is mostly automated by **`fbt`**. When rebuilding the firmware, * ### Symbol Table -Symbol table is a list of symbols exported by firmware and available for external applications. It is generated by **`fbt`** from the API symbols file and is used by the App Loader to resolve addresses of imported symbols. It is build as a part of the `fap_loader` application. +The symbol table is a list of symbols exported by firmware and available for external applications. It is generated by **`fbt`** from the API symbols file and is used by the App Loader to resolve addresses of imported symbols. It is build as a part of the `fap_loader` application. **`fbt`** also checks if all imported symbols are present in the symbol table. If there are any missing symbols, it will issue a warning listing them. Such application won't be able to run on the device until all requires symbols are provided in the symbol table. diff --git a/documentation/OTA.md b/documentation/OTA.md index 2a6b09846c6..836bc1b71aa 100644 --- a/documentation/OTA.md +++ b/documentation/OTA.md @@ -1,8 +1,8 @@ # Executing code from RAM -In Flipper firmware, we have a special boot mode that loads a specially crafted system image into RAM and transfers control to it. System image executing in RAM has full write access to whole Flipper's flash memory — something that's not possible when running main code from same flash. +In Flipper firmware, we have a special boot mode that loads a specially crafted system image into RAM and transfers control to it. System image executing in RAM has full write access to whole Flipper's flash memory — something that's not possible when running main code from the same flash. -We leverage that boot mode to perform OTA firmware updates, including operations on radio stack running on second MCU core. +We leverage that boot mode to perform OTA firmware updates, including operations on a radio stack running on the second MCU core. # How does Flipper OTA work? @@ -22,16 +22,16 @@ For that, main firmware loads an updater image - a customized build of main Flip First, if there's a Radio stack image bundled with the update, updater compares its version with currently installed one. If they don't match, updater performs stack deinstallation followed by writing and installing a new one. The installation itself is performed by proprietary software, FUS, running on Core2, and leads to a series of system restarts. -Then updater validates and corrects Option Bytes — a special memory region containing low-level configuration for Flipper's MCU. +Then, updater validates and corrects Option Bytes — a special memory region containing low-level configuration for Flipper's MCU. After that, updater loads a `.dfu` file with firmware to be flashed, checks its integrity using CRC32, writes it to system flash and validates written data. ## 3. Restoring internal storage and updating resources -After performing operations on flash memory, system restarts into newly flashed firmware. Then it performs restoration of previously backed up `/int` contents. +After performing operations on flash memory, the system restarts into newly flashed firmware. Then it performs restoration of previously backed up `/int` contents. -If update package contains an additional resources archive, it is extracted onto SD card. +If the update package contains an additional resources archive, it is extracted onto SD card. # Update manifest @@ -40,13 +40,13 @@ Update packages come with a manifest that contains a description of its contents ## Mandatory fields -Update manifest must contain the following keys in given order: +An update manifest must contain the following keys in given order: * __Filetype__: a constant string, "Flipper firmware upgrade configuration"; * __Version__: manifest version. Current value is 2; -* __Info__: arbitraty string, describing package contents; +* __Info__: arbitrary string, describing package contents; * __Target__: hardware revision the package is built for; @@ -56,7 +56,7 @@ Update manifest must contain the following keys in given order: ## Optional fields -Other fields may have empty values, is such case updater skips all operations related to such values. +Other fields may have empty values, is such case, updater skips all operations related to such values. * __Radio__: file name of radio stack image, provided by STM; @@ -66,16 +66,16 @@ Other fields may have empty values, is such case updater skips all operations re * __Radio CRC__: CRC32 of radio image; -* __Resources__: file name of TAR acrhive with resources to be extracted on SD card; +* __Resources__: file name of TAR archive with resources to be extracted on SD card; * __OB reference__, __OB mask__, __OB write mask__: reference values for validating and correcting option bytes. # OTA update error codes -We designed the OTA update process to be as fail-safe as possible. We don't start any risky operation before validating all related pieces of data to ensure we don't leave the device in partially updated, or bricked, state. +We designed the OTA update process to be as fail-safe as possible. We don't start any risky operation before validating all related pieces of data to ensure we don't leave the device in a partially updated, or bricked, state. -Even if something goes wrong, Updater gives you an option to retry failed operations, and reports its state with an error code. These error codes have an `[XX-YY]` format, where `XX` encodes an operation that failed, and `YY` contains extra details on its progress where the error occured. +Even if something goes wrong, Updater gives you an option to retry failed operations, and reports its state with an error code. These error codes have an `[XX-YY]` format, where `XX` encodes an operation that failed, and `YY` contains extra details on its progress where the error occurred. | Stage description | Code | Progress | Description | |:-----------------------:|-------:|------------|--------------------------------------------| From 007a3d295ebe340617e2f6db8498c17a379f8eea Mon Sep 17 00:00:00 2001 From: hedger Date: Thu, 15 Sep 2022 17:55:55 +0400 Subject: [PATCH 055/824] [FL-2819] updater: fixed failing backups on /int with empty files in it #1735 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- lib/toolbox/tar/tar_archive.c | 1 + lib/update_util/lfs_backup.c | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/toolbox/tar/tar_archive.c b/lib/toolbox/tar/tar_archive.c index 5ac89a0fd3c..0d42d162c2a 100644 --- a/lib/toolbox/tar/tar_archive.c +++ b/lib/toolbox/tar/tar_archive.c @@ -294,6 +294,7 @@ bool tar_archive_add_file( break; } + success = true; // if file is empty, that's not an error uint16_t bytes_read = 0; while((bytes_read = storage_file_read(src_file, file_buffer, FILE_BLOCK_SIZE))) { success = tar_archive_file_add_data_block(archive, file_buffer, bytes_read); diff --git a/lib/update_util/lfs_backup.c b/lib/update_util/lfs_backup.c index b4a0aaabbe5..089f032d4c5 100644 --- a/lib/update_util/lfs_backup.c +++ b/lib/update_util/lfs_backup.c @@ -42,8 +42,7 @@ bool lfs_backup_create(Storage* storage, const char* destination) { bool lfs_backup_exists(Storage* storage, const char* source) { const char* final_source = source && strlen(source) ? source : LFS_BACKUP_DEFAULT_LOCATION; - FileInfo fi; - return storage_common_stat(storage, final_source, &fi) == FSE_OK; + return storage_common_stat(storage, final_source, NULL) == FSE_OK; } bool lfs_backup_unpack(Storage* storage, const char* source) { From a6052be0f16934b854293bc9241329b1d675586f Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Thu, 15 Sep 2022 17:13:48 +0300 Subject: [PATCH 056/824] Charging icon update fix (#1733) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Power: refresh battery indicator on charger plug/unplug Co-authored-by: SG Co-authored-by: あく --- applications/services/power/power_service/power.c | 2 ++ applications/services/power/power_service/power.h | 1 + 2 files changed, 3 insertions(+) diff --git a/applications/services/power/power_service/power.c b/applications/services/power/power_service/power.c index 3a2c6cf3b1f..85d217f290f 100644 --- a/applications/services/power/power_service/power.c +++ b/applications/services/power/power_service/power.c @@ -127,6 +127,7 @@ static void power_check_charging_state(Power* power) { static bool power_update_info(Power* power) { PowerInfo info; + info.is_charging = furi_hal_power_is_charging(); info.gauge_is_ok = furi_hal_power_gauge_is_ok(); info.charge = furi_hal_power_get_pct(); info.health = furi_hal_power_get_bat_health_pct(); @@ -142,6 +143,7 @@ static bool power_update_info(Power* power) { furi_mutex_acquire(power->api_mtx, FuriWaitForever); bool need_refresh = power->info.charge != info.charge; + need_refresh |= power->info.is_charging != info.is_charging; power->info = info; furi_mutex_release(power->api_mtx); diff --git a/applications/services/power/power_service/power.h b/applications/services/power/power_service/power.h index b623452ee38..bdf5fa52974 100644 --- a/applications/services/power/power_service/power.h +++ b/applications/services/power/power_service/power.h @@ -36,6 +36,7 @@ typedef struct { typedef struct { bool gauge_is_ok; + bool is_charging; float current_charger; float current_gauge; From 8b05bd1106f0b72056fea40a9040341daae7ee21 Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Thu, 15 Sep 2022 19:42:44 +0400 Subject: [PATCH 057/824] [FL-2814] SubGhz: fix display information in the file if the frequency is not available for transmission in the given region (#1724) --- applications/main/subghz/helpers/subghz_types.h | 1 - applications/main/subghz/subghz_i.c | 11 ----------- 2 files changed, 12 deletions(-) diff --git a/applications/main/subghz/helpers/subghz_types.h b/applications/main/subghz/helpers/subghz_types.h index 8da4c8f53fe..7fe1e7ce78d 100644 --- a/applications/main/subghz/helpers/subghz_types.h +++ b/applications/main/subghz/helpers/subghz_types.h @@ -47,7 +47,6 @@ typedef enum { SubGhzLoadKeyStateUnknown, SubGhzLoadKeyStateOK, SubGhzLoadKeyStateParseErr, - SubGhzLoadKeyStateOnlyRx, } SubGhzLoadKeyState; /** SubGhzLock */ diff --git a/applications/main/subghz/subghz_i.c b/applications/main/subghz/subghz_i.c index 91404fc71d9..6ed8fd5399d 100644 --- a/applications/main/subghz/subghz_i.c +++ b/applications/main/subghz/subghz_i.c @@ -278,11 +278,6 @@ bool subghz_key_load(SubGhz* subghz, const char* file_path, bool show_dialog) { break; } - if(!furi_hal_region_is_frequency_allowed(temp_data32)) { - FURI_LOG_E(TAG, "This frequency can only be used for RX in your region"); - load_key_state = SubGhzLoadKeyStateOnlyRx; - break; - } subghz->txrx->preset->frequency = temp_data32; if(!flipper_format_read_string(fff_data_file, "Preset", temp_str)) { @@ -354,12 +349,6 @@ bool subghz_key_load(SubGhz* subghz, const char* file_path, bool show_dialog) { } return false; - case SubGhzLoadKeyStateOnlyRx: - if(show_dialog) { - subghz_dialog_message_show_only_rx(subghz); - } - return false; - case SubGhzLoadKeyStateOK: return true; From 60bce7b8d8f2712d985352d1e38795dd8f18f3a3 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Thu, 15 Sep 2022 18:49:10 +0300 Subject: [PATCH 058/824] [FL-2780] NFC Notifications fix (#1731) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Show error popup when NFC chip is not init/disconnected * Move to dialogs for the error message * NFC notifications fixed to match the intended design * Revert "Move to dialogs for the error message" * Revert "Show error popup when NFC chip is not init/disconnected" Co-authored-by: SG Co-authored-by: あく --- applications/main/nfc/nfc.c | 12 ++++++++++-- applications/main/nfc/nfc_i.h | 6 +++++- .../main/nfc/scenes/nfc_scene_detect_reader.c | 2 +- .../nfc/scenes/nfc_scene_emulate_apdu_sequence.c | 2 +- applications/main/nfc/scenes/nfc_scene_emulate_uid.c | 2 +- .../main/nfc/scenes/nfc_scene_emv_read_success.c | 4 ++++ .../nfc/scenes/nfc_scene_mf_classic_dict_attack.c | 2 +- .../main/nfc/scenes/nfc_scene_mf_classic_emulate.c | 2 +- .../nfc/scenes/nfc_scene_mf_classic_read_success.c | 4 ++++ .../nfc/scenes/nfc_scene_mf_desfire_read_success.c | 4 ++++ .../nfc/scenes/nfc_scene_mf_ultralight_emulate.c | 2 +- .../nfc/scenes/nfc_scene_mf_ultralight_read_auth.c | 2 +- .../scenes/nfc_scene_mf_ultralight_read_success.c | 4 ++++ .../main/nfc/scenes/nfc_scene_nfca_read_success.c | 4 ++++ applications/main/nfc/scenes/nfc_scene_read.c | 4 +++- applications/main/nfc/scenes/nfc_scene_rpc.c | 2 +- 16 files changed, 46 insertions(+), 12 deletions(-) diff --git a/applications/main/nfc/nfc.c b/applications/main/nfc/nfc.c index 57e25f81efe..43263020415 100644 --- a/applications/main/nfc/nfc.c +++ b/applications/main/nfc/nfc.c @@ -201,8 +201,16 @@ void nfc_text_store_clear(Nfc* nfc) { memset(nfc->text_store, 0, sizeof(nfc->text_store)); } -void nfc_blink_start(Nfc* nfc) { - notification_message(nfc->notifications, &sequence_blink_start_blue); +void nfc_blink_read_start(Nfc* nfc) { + notification_message(nfc->notifications, &sequence_blink_start_cyan); +} + +void nfc_blink_emulate_start(Nfc* nfc) { + notification_message(nfc->notifications, &sequence_blink_start_magenta); +} + +void nfc_blink_detect_start(Nfc* nfc) { + notification_message(nfc->notifications, &sequence_blink_start_yellow); } void nfc_blink_stop(Nfc* nfc) { diff --git a/applications/main/nfc/nfc_i.h b/applications/main/nfc/nfc_i.h index c9ba7fff718..b5d284d5b68 100644 --- a/applications/main/nfc/nfc_i.h +++ b/applications/main/nfc/nfc_i.h @@ -98,7 +98,11 @@ void nfc_text_store_set(Nfc* nfc, const char* text, ...); void nfc_text_store_clear(Nfc* nfc); -void nfc_blink_start(Nfc* nfc); +void nfc_blink_read_start(Nfc* nfc); + +void nfc_blink_emulate_start(Nfc* nfc); + +void nfc_blink_detect_start(Nfc* nfc); void nfc_blink_stop(Nfc* nfc); diff --git a/applications/main/nfc/scenes/nfc_scene_detect_reader.c b/applications/main/nfc/scenes/nfc_scene_detect_reader.c index 8945febafa3..5f4582d8ef5 100644 --- a/applications/main/nfc/scenes/nfc_scene_detect_reader.c +++ b/applications/main/nfc/scenes/nfc_scene_detect_reader.c @@ -29,7 +29,7 @@ void nfc_scene_detect_reader_on_enter(void* context) { view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewDetectReader); - nfc_blink_start(nfc); + nfc_blink_read_start(nfc); } bool nfc_scene_detect_reader_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/main/nfc/scenes/nfc_scene_emulate_apdu_sequence.c b/applications/main/nfc/scenes/nfc_scene_emulate_apdu_sequence.c index e6062ba4982..358ad2ab6f5 100644 --- a/applications/main/nfc/scenes/nfc_scene_emulate_apdu_sequence.c +++ b/applications/main/nfc/scenes/nfc_scene_emulate_apdu_sequence.c @@ -12,7 +12,7 @@ void nfc_scene_emulate_apdu_sequence_on_enter(void* context) { view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); nfc_worker_start(nfc->worker, NfcWorkerStateEmulateApdu, &nfc->dev->dev_data, NULL, nfc); - nfc_blink_start(nfc); + nfc_blink_emulate_start(nfc); } bool nfc_scene_emulate_apdu_sequence_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/main/nfc/scenes/nfc_scene_emulate_uid.c b/applications/main/nfc/scenes/nfc_scene_emulate_uid.c index 0d92c9f040a..f6402301072 100644 --- a/applications/main/nfc/scenes/nfc_scene_emulate_uid.c +++ b/applications/main/nfc/scenes/nfc_scene_emulate_uid.c @@ -82,7 +82,7 @@ void nfc_scene_emulate_uid_on_enter(void* context) { nfc_emulate_uid_worker_callback, nfc); - nfc_blink_start(nfc); + nfc_blink_emulate_start(nfc); } bool nfc_scene_emulate_uid_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/main/nfc/scenes/nfc_scene_emv_read_success.c b/applications/main/nfc/scenes/nfc_scene_emv_read_success.c index 9cf7ff9e903..a40b4c1c9e0 100644 --- a/applications/main/nfc/scenes/nfc_scene_emv_read_success.c +++ b/applications/main/nfc/scenes/nfc_scene_emv_read_success.c @@ -55,6 +55,8 @@ void nfc_scene_emv_read_success_on_enter(void* context) { string_clear(country_name); } + notification_message_block(nfc->notifications, &sequence_set_green_255); + widget_add_text_scroll_element(nfc->widget, 0, 0, 128, 52, string_get_cstr(temp_str)); string_clear(temp_str); @@ -83,6 +85,8 @@ bool nfc_scene_emv_read_success_on_event(void* context, SceneManagerEvent event) void nfc_scene_emv_read_success_on_exit(void* context) { Nfc* nfc = context; + notification_message_block(nfc->notifications, &sequence_reset_green); + // Clear view widget_reset(nfc->widget); } diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c index d821c182da6..b23f4b8f114 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c @@ -90,7 +90,7 @@ void nfc_scene_mf_classic_dict_attack_on_enter(void* context) { Nfc* nfc = context; nfc_scene_mf_classic_dict_attack_prepare_view(nfc, DictAttackStateIdle); view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewDictAttack); - nfc_blink_start(nfc); + nfc_blink_read_start(nfc); } bool nfc_scene_mf_classic_dict_attack_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_emulate.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_emulate.c index 044388b8b57..65639b2b40c 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_emulate.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_emulate.c @@ -35,7 +35,7 @@ void nfc_scene_mf_classic_emulate_on_enter(void* context) { &nfc->dev->dev_data, nfc_mf_classic_emulate_worker_callback, nfc); - nfc_blink_start(nfc); + nfc_blink_emulate_start(nfc); } bool nfc_scene_mf_classic_emulate_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_read_success.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_read_success.c index efe676706d4..3ca24416afb 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_read_success.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_read_success.c @@ -48,6 +48,8 @@ void nfc_scene_mf_classic_read_success_on_enter(void* context) { widget_add_text_scroll_element(widget, 0, 0, 128, 52, string_get_cstr(temp_str)); string_clear(temp_str); + notification_message_block(nfc->notifications, &sequence_set_green_255); + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); } @@ -76,6 +78,8 @@ bool nfc_scene_mf_classic_read_success_on_event(void* context, SceneManagerEvent void nfc_scene_mf_classic_read_success_on_exit(void* context) { Nfc* nfc = context; + notification_message_block(nfc->notifications, &sequence_reset_green); + // Clear view widget_reset(nfc->widget); } diff --git a/applications/main/nfc/scenes/nfc_scene_mf_desfire_read_success.c b/applications/main/nfc/scenes/nfc_scene_mf_desfire_read_success.c index 4827c28513d..12047c15a72 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_desfire_read_success.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_desfire_read_success.c @@ -52,6 +52,8 @@ void nfc_scene_mf_desfire_read_success_on_enter(void* context) { string_push_back(temp_str, 's'); } + notification_message_block(nfc->notifications, &sequence_set_green_255); + // Add text scroll element widget_add_text_scroll_element(widget, 0, 0, 128, 52, string_get_cstr(temp_str)); string_clear(temp_str); @@ -88,6 +90,8 @@ bool nfc_scene_mf_desfire_read_success_on_event(void* context, SceneManagerEvent void nfc_scene_mf_desfire_read_success_on_exit(void* context) { Nfc* nfc = context; + notification_message_block(nfc->notifications, &sequence_reset_green); + // Clean dialog widget_reset(nfc->widget); } diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_emulate.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_emulate.c index ce375554126..712ddc077d9 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_emulate.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_emulate.c @@ -35,7 +35,7 @@ void nfc_scene_mf_ultralight_emulate_on_enter(void* context) { &nfc->dev->dev_data, nfc_mf_ultralight_emulate_worker_callback, nfc); - nfc_blink_start(nfc); + nfc_blink_emulate_start(nfc); } bool nfc_scene_mf_ultralight_emulate_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_auth.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_auth.c index 853ccb05570..1a106bdb4d4 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_auth.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_auth.c @@ -65,7 +65,7 @@ void nfc_scene_mf_ultralight_read_auth_on_enter(void* context) { nfc_scene_mf_ultralight_read_auth_worker_callback, nfc); - nfc_blink_start(nfc); + nfc_blink_read_start(nfc); } bool nfc_scene_mf_ultralight_read_auth_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_success.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_success.c index d775bb71d90..56fa2057895 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_success.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_success.c @@ -47,6 +47,8 @@ void nfc_scene_mf_ultralight_read_success_on_enter(void* context) { widget_add_text_scroll_element(widget, 0, 0, 128, 52, string_get_cstr(temp_str)); string_clear(temp_str); + notification_message_block(nfc->notifications, &sequence_set_green_255); + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); } @@ -73,6 +75,8 @@ bool nfc_scene_mf_ultralight_read_success_on_event(void* context, SceneManagerEv void nfc_scene_mf_ultralight_read_success_on_exit(void* context) { Nfc* nfc = context; + notification_message_block(nfc->notifications, &sequence_reset_green); + // Clean view widget_reset(nfc->widget); } diff --git a/applications/main/nfc/scenes/nfc_scene_nfca_read_success.c b/applications/main/nfc/scenes/nfc_scene_nfca_read_success.c index 3467a03b634..c695da247a3 100644 --- a/applications/main/nfc/scenes/nfc_scene_nfca_read_success.c +++ b/applications/main/nfc/scenes/nfc_scene_nfca_read_success.c @@ -25,6 +25,8 @@ void nfc_scene_nfca_read_success_on_enter(void* context) { string_t temp_str; string_init_set_str(temp_str, "\e#Unknown ISO tag\n"); + notification_message_block(nfc->notifications, &sequence_set_green_255); + char iso_type = FURI_BIT(data->sak, 5) ? '4' : '3'; string_cat_printf(temp_str, "ISO 14443-%c (NFC-A)\n", iso_type); string_cat_printf(temp_str, "UID:"); @@ -67,6 +69,8 @@ bool nfc_scene_nfca_read_success_on_event(void* context, SceneManagerEvent event void nfc_scene_nfca_read_success_on_exit(void* context) { Nfc* nfc = context; + notification_message_block(nfc->notifications, &sequence_reset_green); + // Clear view widget_reset(nfc->widget); } diff --git a/applications/main/nfc/scenes/nfc_scene_read.c b/applications/main/nfc/scenes/nfc_scene_read.c index 00b7c8fac76..da21b9f3ddc 100644 --- a/applications/main/nfc/scenes/nfc_scene_read.c +++ b/applications/main/nfc/scenes/nfc_scene_read.c @@ -49,7 +49,7 @@ void nfc_scene_read_on_enter(void* context) { nfc_worker_start( nfc->worker, NfcWorkerStateRead, &nfc->dev->dev_data, nfc_scene_read_worker_callback, nfc); - nfc_blink_start(nfc); + nfc_blink_read_start(nfc); } bool nfc_scene_read_on_event(void* context, SceneManagerEvent event) { @@ -92,9 +92,11 @@ bool nfc_scene_read_on_event(void* context, SceneManagerEvent event) { consumed = true; } else if(event.event == NfcWorkerEventCardDetected) { nfc_scene_read_set_state(nfc, NfcSceneReadStateReading); + nfc_blink_detect_start(nfc); consumed = true; } else if(event.event == NfcWorkerEventNoCardDetected) { nfc_scene_read_set_state(nfc, NfcSceneReadStateDetecting); + nfc_blink_read_start(nfc); consumed = true; } } diff --git a/applications/main/nfc/scenes/nfc_scene_rpc.c b/applications/main/nfc/scenes/nfc_scene_rpc.c index 7a9eb450339..e5128a52fbe 100644 --- a/applications/main/nfc/scenes/nfc_scene_rpc.c +++ b/applications/main/nfc/scenes/nfc_scene_rpc.c @@ -62,7 +62,7 @@ bool nfc_scene_rpc_on_event(void* context, SceneManagerEvent event) { nfc->rpc_state = NfcRpcStateEmulating; result = true; - nfc_blink_start(nfc); + nfc_blink_emulate_start(nfc); nfc_text_store_set(nfc, "emulating\n%s", nfc->dev->dev_name); popup_set_text(popup, nfc->text_store, 89, 44, AlignCenter, AlignTop); } From 787df44c7992d8718fe634f13f4b64118486e89c Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Mon, 19 Sep 2022 15:30:18 +0300 Subject: [PATCH 059/824] [FL-2800] Fix Mifare Classic 4K reading of the last 8 sectors (#1712) * Fix FURI_BIT_SET Co-authored-by: gornekich Co-authored-by: SG --- furi/core/core_defines.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/furi/core/core_defines.h b/furi/core/core_defines.h index 801810f6a14..487fe2ca30c 100644 --- a/furi/core/core_defines.h +++ b/furi/core/core_defines.h @@ -85,7 +85,11 @@ extern "C" { #endif #ifndef FURI_BIT_SET -#define FURI_BIT_SET(x, n) ((x) |= (1 << (n))) +#define FURI_BIT_SET(x, n) \ + ({ \ + __typeof__(x) _x = (1); \ + (x) |= (_x << (n)); \ + }) #endif #ifndef FURI_BIT_CLEAR From ed385594a35983c21a285af7e783d753cf014283 Mon Sep 17 00:00:00 2001 From: hedger Date: Mon, 19 Sep 2022 16:39:00 +0400 Subject: [PATCH 060/824] faploader: more subsystem headers in API table (#1742) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * faploader: more subsystem headers in API table; not counting header entries for SDK version change * subghz: removed dead function * Adjusted API version * hal: removed furi_hal_power_get_system_voltage * lib: mbedtls: additional flags for .fap linkage * fbt: rebuilding assets when git commit id changes * fbt: removed assets rebuild on git commit id change; added explicit dependency for SDK source on compiled assets parts; removed unneeded sdk regeneration runs * fbt: changed stock plugins to EXTERNAL apps; restored building app as a PLUGIN as a part of main fw as well as a .fap; readme fixes * fbt: restored certain apps to PLUGIN type * fbt: app manifests: renamed version->fap_version, added extra fields * fbt: fixed version processing after rename Co-authored-by: あく --- applications/plugins/picopass/application.fam | 2 +- assets/SConscript | 9 +- documentation/AppManifests.md | 29 ++--- fbt_options.py | 3 +- firmware.scons | 2 +- firmware/targets/f7/api_symbols.csv | 103 +++++++++++++++++- .../targets/furi_hal_include/furi_hal_power.h | 6 - lib/lfrfid/SConscript | 8 ++ lib/mbedtls.scons | 8 ++ lib/subghz/SConscript | 1 + lib/subghz/protocols/raw.h | 14 ++- lib/toolbox/SConscript | 2 + site_scons/extapps.scons | 5 +- site_scons/fbt/appmanifest.py | 7 +- site_scons/fbt/elfmanifest.py | 4 +- site_scons/fbt/sdk.py | 51 +++++---- site_scons/fbt/version.py | 2 + site_scons/site_tools/fbt_sdk.py | 1 + 18 files changed, 192 insertions(+), 65 deletions(-) diff --git a/applications/plugins/picopass/application.fam b/applications/plugins/picopass/application.fam index 887d0324c2a..7a81e080495 100644 --- a/applications/plugins/picopass/application.fam +++ b/applications/plugins/picopass/application.fam @@ -1,7 +1,7 @@ App( appid="picopass", name="PicoPass Reader", - apptype=FlipperAppType.PLUGIN, + apptype=FlipperAppType.EXTERNAL, entry_point="picopass_app", requires=[ "storage", diff --git a/assets/SConscript b/assets/SConscript index 6328035faca..47713d1a609 100644 --- a/assets/SConscript +++ b/assets/SConscript @@ -1,13 +1,5 @@ Import("env") -# HACHHACK -# Currently injected to CPPPATH by libs - since they are built earlier and depend on assets -# env.Append( -# CPPPATH=[ -# Dir("./compiled"), -# ] -# ) - assetsenv = env.Clone( tools=["fbt_assets"], FW_LIB_NAME="assets", @@ -109,6 +101,7 @@ if assetsenv["IS_BASE_FIRMWARE"]: ) # Exporting resources node to external environment + env["FW_ASSETS_HEADERS"] = assets_parts env["FW_RESOURCES"] = resources assetsenv.Alias("resources", resources) diff --git a/documentation/AppManifests.md b/documentation/AppManifests.md index 5e7ceb939d8..7bc8d0a477b 100644 --- a/documentation/AppManifests.md +++ b/documentation/AppManifests.md @@ -1,16 +1,16 @@ # Flipper Application Manifests (.fam) -All components of Flipper Zero firmware — services, user applications, system settings — are developed independently. Each component has a build system manifest file, named `application.fam`, defining basic properties of a components and its relations to other parts of the system. +All components of Flipper Zero firmware — services, user applications, system settings — are developed independently. Each component has a build system manifest file, named `application.fam`, which defines basic properties of that component and its relations to other parts of the system. -When building firmware, **`fbt`** collects all application manifests, processes their dependencies and builds only those components that are utilized in current build configuration. See [fbt docs](./fbt.md#firmware-application-set) for details on build configurations. +When building firmware, **`fbt`** collects all application manifests and processes their dependencies. Then it builds only those components that are referenced in the current build configuration. See [fbt docs](./fbt.md#firmware-application-set) for details on build configurations. ## Application definition Properties of a firmware component are declared in a form of a Python code snippet, forming a call to App() function with various parameters. -Only 2 parameters are mandatoty: ***appid*** and ***apptype***, others are optional and may be meaningful only for certain application types. +Only 2 parameters are mandatory: ***appid*** and ***apptype***, others are optional and may only be meaningful for certain application types. -### Keys +### Parameters * **appid**: string, application id within the build system. Used for specifying which applications to include in build configuration and to resolve dependencies and conflicts. @@ -21,7 +21,7 @@ Only 2 parameters are mandatoty: ***appid*** and ***apptype***, others are optio | SERVICE | System service, created at early startup | | SYSTEM | Application not being shown in any menus. Can be started by other apps or from CLI | | APP | Regular application for main menu | -| PLUGIN | Application to be built as .fap plugin | +| PLUGIN | Application to be built as a part of firmware an to be placed in Plugins menu | | DEBUG | Application only visible in Debug menu with debug mode enabled | | ARCHIVE | One and only Archive app | | SETTINGS | Application to be placed in System settings menu | @@ -29,25 +29,28 @@ Only 2 parameters are mandatoty: ***appid*** and ***apptype***, others are optio | EXTERNAL | Application to be built as .fap plugin | | METAPACKAGE | Does not define any code to be run, used for declaring dependencies and application bundles | -* **name**: Name to show in menus. -* **entry_point**: C function to be used as applicaiton's entry point. +* **name**: Name that is displayed in menus. +* **entry_point**: C function to be used as application's entry point. * **flags**: Internal flags for system apps. Do not use. * **cdefines**: C preprocessor definitions to declare globally for other apps when current application is included in active build configuration. * **requires**: List of application IDs to also include in build configuration, when current application is referenced in list of applications to build. * **conflicts**: List of application IDs that current application conflicts with. If any of them is found in constructed application list, **`fbt`** will abort firmware build process. * **provides**: Functionally identical to ***requires*** field. -* **stack_size**: Stack size, in bytes, to allocate for application on its startup. Note that allocating a stack too small for app to run will cause system crash due to stack overflow, and allocating too much stack will reduce usable heap memory size for app to process data. *Note: you can use `ps` and `free` CLI commands to profile you app's memory usage.* +* **stack_size**: Stack size, in bytes, to allocate for application on its startup. Note that allocating a stack that is too small for an app to run will cause system crash due to stack overflow, and allocating too much stack space will reduce usable heap memory size for apps to process data. *Note: you can use `ps` and `free` CLI commands to profile your app's memory usage.* * **icon**: Animated icon name from built-in assets to be used when building app as a part of firmware. -* **order**: Order of an application within its group when sorting entries in it. The lower the order is, the closer to the start of the list the items is located. Used for ordering startup hooks and menu entries. -* **sdk_headers**: List of C header files from this app's code to include in API definitions for external applicaions. +* **order**: Order of an application within its group when sorting entries in it. The lower the order is, the closer to the start of the list the item is placed. *Used for ordering startup hooks and menu entries.* +* **sdk_headers**: List of C header files from this app's code to include in API definitions for external applications. The following parameters are used only for [FAPs](./AppsOnSDCard.md): * **sources**: list of file name masks, used for gathering sources within app folder. Default value of ["\*.c\*"] includes C and CPP source files. -* **version**: string, 2 numbers in form of "x.y": application version to be embedded within .fap file. -* **fap_icon**: name of .png file, 1-bit color depth, 10x10px, to be embedded within .fap file. +* **fap_version**: string, 2 numbers in form of "x.y": application version to be embedded within .fap file. +* **fap_icon**: name of a .png file, 1-bit color depth, 10x10px, to be embedded within .fap file. * **fap_libs**: list of extra libraries to link application against. Provides access to extra functions that are not exported as a part of main firmware at expense of increased .fap file size and RAM consumption. * **fap_category**: string, may be empty. App subcategory, also works as path of FAP within apps folder in the file system. +* **fap_description**: string, may be empty. Short application descriotion. +* **fap_author**: string, may be empty. Application's author. +* **fap_weburl**: string, may be empty. Application's homepage. ## .fam file contents @@ -76,4 +79,4 @@ App( ) ``` -For more examples, see application.fam files for basic firmware components. \ No newline at end of file +For more examples, see .fam files from various firmware parts. diff --git a/fbt_options.py b/fbt_options.py index bdf8fc03ba1..716f99fdfa4 100644 --- a/fbt_options.py +++ b/fbt_options.py @@ -73,7 +73,8 @@ "system_apps", # Settings "settings_apps", - # Plugins + # Stock plugins - no longer built into fw, now they're .faps + # Yet you can still build them as a part of fw # "basic_plugins", # Debug # "debug_apps", diff --git a/firmware.scons b/firmware.scons index 962c7072557..530634ef261 100644 --- a/firmware.scons +++ b/firmware.scons @@ -302,7 +302,7 @@ if fwenv["IS_BASE_FIRMWARE"]: "-D__inline__=inline", ], ) - Depends(sdk_source, fwenv["SDK_HEADERS"]) + Depends(sdk_source, (fwenv["SDK_HEADERS"], fwenv["FW_ASSETS_HEADERS"])) sdk_tree = fwenv.SDKTree("sdk/sdk.opts", "sdk_origin") AlwaysBuild(sdk_tree) diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 545e1d644c5..3f2a86b24db 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,1.0,, +Version,+,1.3,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -109,6 +109,12 @@ Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_wwdg.h,, Header,+,lib/flipper_application/flipper_application.h,, Header,+,lib/flipper_format/flipper_format.h,, Header,+,lib/flipper_format/flipper_format_i.h,, +Header,+,lib/lfrfid/lfrfid_dict_file.h,, +Header,+,lib/lfrfid/lfrfid_raw_file.h,, +Header,+,lib/lfrfid/lfrfid_raw_worker.h,, +Header,+,lib/lfrfid/lfrfid_worker.h,, +Header,+,lib/lfrfid/protocols/lfrfid_protocols.h,, +Header,+,lib/lfrfid/tools/bit_lib.h,, Header,+,lib/micro-ecc/uECC.h,, Header,+,lib/one_wire/ibutton/ibutton_worker.h,, Header,+,lib/one_wire/maxim_crc.h,, @@ -118,6 +124,7 @@ Header,+,lib/one_wire/one_wire_host_timing.h,, Header,+,lib/one_wire/one_wire_slave.h,, Header,+,lib/print/wrappers.h,, Header,+,lib/subghz/environment.h,, +Header,+,lib/subghz/protocols/raw.h,, Header,+,lib/subghz/receiver.h,, Header,+,lib/subghz/subghz_tx_rx_worker.h,, Header,+,lib/subghz/subghz_worker.h,, @@ -130,8 +137,10 @@ Header,+,lib/toolbox/manchester_decoder.h,, Header,+,lib/toolbox/manchester_encoder.h,, Header,+,lib/toolbox/md5.h,, Header,+,lib/toolbox/path.h,, +Header,+,lib/toolbox/protocols/protocol_dict.h,, Header,+,lib/toolbox/random_name.h,, Header,+,lib/toolbox/saved_struct.h,, +Header,+,lib/toolbox/stream/buffered_file_stream.h,, Header,+,lib/toolbox/stream/file_stream.h,, Header,+,lib/toolbox/stream/stream.h,, Header,+,lib/toolbox/stream/string_stream.h,, @@ -465,6 +474,26 @@ Function,-,atoll,long long,const char* Function,-,basename,char*,const char* Function,-,bcmp,int,"const void*, const void*, size_t" Function,-,bcopy,void,"const void*, void*, size_t" +Function,+,bit_lib_add_parity,size_t,"const uint8_t*, size_t, uint8_t*, size_t, uint8_t, uint8_t, BitLibParity" +Function,+,bit_lib_copy_bits,void,"uint8_t*, size_t, size_t, const uint8_t*, size_t" +Function,+,bit_lib_crc16,uint16_t,"const uint8_t*, size_t, uint16_t, uint16_t, _Bool, _Bool, uint16_t" +Function,+,bit_lib_crc8,uint16_t,"const uint8_t*, size_t, uint8_t, uint8_t, _Bool, _Bool, uint8_t" +Function,+,bit_lib_get_bit,_Bool,"const uint8_t*, size_t" +Function,+,bit_lib_get_bit_count,uint8_t,uint32_t +Function,+,bit_lib_get_bits,uint8_t,"const uint8_t*, size_t, uint8_t" +Function,+,bit_lib_get_bits_16,uint16_t,"const uint8_t*, size_t, uint8_t" +Function,+,bit_lib_get_bits_32,uint32_t,"const uint8_t*, size_t, uint8_t" +Function,+,bit_lib_print_bits,void,"const uint8_t*, size_t" +Function,+,bit_lib_print_regions,void,"const BitLibRegion*, size_t, const uint8_t*, size_t" +Function,+,bit_lib_push_bit,void,"uint8_t*, size_t, _Bool" +Function,+,bit_lib_remove_bit_every_nth,size_t,"uint8_t*, size_t, uint8_t, uint8_t" +Function,+,bit_lib_reverse_16_fast,uint16_t,uint16_t +Function,+,bit_lib_reverse_8_fast,uint8_t,uint8_t +Function,+,bit_lib_reverse_bits,void,"uint8_t*, size_t, uint8_t" +Function,+,bit_lib_set_bit,void,"uint8_t*, size_t, _Bool" +Function,+,bit_lib_set_bits,void,"uint8_t*, size_t, uint8_t, uint8_t" +Function,+,bit_lib_test_parity,_Bool,"const uint8_t*, size_t, uint8_t, BitLibParity, uint8_t" +Function,+,bit_lib_test_parity_32,_Bool,"uint32_t, BitLibParity" Function,+,ble_app_get_key_storage_buff,void,"uint8_t**, uint16_t*" Function,+,ble_app_init,_Bool, Function,+,ble_app_thread_stop,void, @@ -488,6 +517,11 @@ Function,+,bt_disconnect,void,Bt* Function,+,bt_forget_bonded_devices,void,Bt* Function,+,bt_set_profile,_Bool,"Bt*, BtProfile" Function,+,bt_set_status_changed_callback,void,"Bt*, BtStatusChangedCallback, void*" +Function,+,buffered_file_stream_alloc,Stream*,Storage* +Function,+,buffered_file_stream_close,_Bool,Stream* +Function,+,buffered_file_stream_get_error,FS_Error,Stream* +Function,+,buffered_file_stream_open,_Bool,"Stream*, const char*, FS_AccessMode, FS_OpenMode" +Function,+,buffered_file_stream_sync,_Bool,Stream* Function,+,button_menu_add_item,ButtonMenuItem*,"ButtonMenu*, const char*, int32_t, ButtonMenuItemCallback, ButtonMenuItemType, void*" Function,+,button_menu_alloc,ButtonMenu*, Function,+,button_menu_free,void,ButtonMenu* @@ -1099,7 +1133,6 @@ Function,+,furi_hal_power_get_battery_remaining_capacity,uint32_t, Function,+,furi_hal_power_get_battery_temperature,float,FuriHalPowerIC Function,+,furi_hal_power_get_battery_voltage,float,FuriHalPowerIC Function,+,furi_hal_power_get_pct,uint8_t, -Function,-,furi_hal_power_get_system_voltage,float, Function,+,furi_hal_power_get_usb_voltage,float, Function,+,furi_hal_power_info_get,void,"FuriHalPowerInfoCallback, void*" Function,-,furi_hal_power_init,void, @@ -1468,6 +1501,31 @@ Function,-,ldexp,double,"double, int" Function,-,ldexpf,float,"float, int" Function,-,ldexpl,long double,"long double, int" Function,-,ldiv,ldiv_t,"long, long" +Function,+,lfrfid_dict_file_load,ProtocolId,"ProtocolDict*, const char*" +Function,+,lfrfid_dict_file_save,_Bool,"ProtocolDict*, ProtocolId, const char*" +Function,+,lfrfid_raw_file_alloc,LFRFIDRawFile*,Storage* +Function,+,lfrfid_raw_file_free,void,LFRFIDRawFile* +Function,+,lfrfid_raw_file_open_read,_Bool,"LFRFIDRawFile*, const char*" +Function,+,lfrfid_raw_file_open_write,_Bool,"LFRFIDRawFile*, const char*" +Function,+,lfrfid_raw_file_read_header,_Bool,"LFRFIDRawFile*, float*, float*" +Function,+,lfrfid_raw_file_read_pair,_Bool,"LFRFIDRawFile*, uint32_t*, uint32_t*, _Bool*" +Function,+,lfrfid_raw_file_write_buffer,_Bool,"LFRFIDRawFile*, uint8_t*, size_t" +Function,+,lfrfid_raw_file_write_header,_Bool,"LFRFIDRawFile*, float, float, uint32_t" +Function,+,lfrfid_raw_worker_alloc,LFRFIDRawWorker*, +Function,+,lfrfid_raw_worker_free,void,LFRFIDRawWorker* +Function,+,lfrfid_raw_worker_start_emulate,void,"LFRFIDRawWorker*, const char*, LFRFIDWorkerEmulateRawCallback, void*" +Function,+,lfrfid_raw_worker_start_read,void,"LFRFIDRawWorker*, const char*, float, float, LFRFIDWorkerReadRawCallback, void*" +Function,+,lfrfid_raw_worker_stop,void,LFRFIDRawWorker* +Function,+,lfrfid_worker_alloc,LFRFIDWorker*,ProtocolDict* +Function,+,lfrfid_worker_emulate_raw_start,void,"LFRFIDWorker*, const char*, LFRFIDWorkerEmulateRawCallback, void*" +Function,+,lfrfid_worker_emulate_start,void,"LFRFIDWorker*, LFRFIDProtocol" +Function,+,lfrfid_worker_free,void,LFRFIDWorker* +Function,+,lfrfid_worker_read_raw_start,void,"LFRFIDWorker*, const char*, LFRFIDWorkerReadType, LFRFIDWorkerReadRawCallback, void*" +Function,+,lfrfid_worker_read_start,void,"LFRFIDWorker*, LFRFIDWorkerReadType, LFRFIDWorkerReadCallback, void*" +Function,+,lfrfid_worker_start_thread,void,LFRFIDWorker* +Function,+,lfrfid_worker_stop,void,LFRFIDWorker* +Function,+,lfrfid_worker_stop_thread,void,LFRFIDWorker* +Function,+,lfrfid_worker_write_start,void,"LFRFIDWorker*, LFRFIDProtocol, LFRFIDWorkerWriteCallback, void*" Function,-,lgamma,double,double Function,-,lgamma_r,double,"double, int*" Function,-,lgammaf,float,float @@ -1659,6 +1717,26 @@ Function,+,power_reboot,void,PowerBootMode Function,+,powf,float,"float, float" Function,-,powl,long double,"long double, long double" Function,-,printf,int,"const char*, ..." +Function,+,protocol_dict_alloc,ProtocolDict*,"const ProtocolBase**, size_t" +Function,+,protocol_dict_decoders_feed,ProtocolId,"ProtocolDict*, _Bool, uint32_t" +Function,+,protocol_dict_decoders_feed_by_feature,ProtocolId,"ProtocolDict*, uint32_t, _Bool, uint32_t" +Function,+,protocol_dict_decoders_feed_by_id,ProtocolId,"ProtocolDict*, size_t, _Bool, uint32_t" +Function,+,protocol_dict_decoders_start,void,ProtocolDict* +Function,+,protocol_dict_encoder_start,_Bool,"ProtocolDict*, size_t" +Function,+,protocol_dict_encoder_yield,LevelDuration,"ProtocolDict*, size_t" +Function,+,protocol_dict_free,void,ProtocolDict* +Function,+,protocol_dict_get_data,void,"ProtocolDict*, size_t, uint8_t*, size_t" +Function,+,protocol_dict_get_data_size,size_t,"ProtocolDict*, size_t" +Function,+,protocol_dict_get_features,uint32_t,"ProtocolDict*, size_t" +Function,+,protocol_dict_get_manufacturer,const char*,"ProtocolDict*, size_t" +Function,+,protocol_dict_get_max_data_size,size_t,ProtocolDict* +Function,+,protocol_dict_get_name,const char*,"ProtocolDict*, size_t" +Function,+,protocol_dict_get_protocol_by_name,ProtocolId,"ProtocolDict*, const char*" +Function,+,protocol_dict_get_validate_count,uint32_t,"ProtocolDict*, size_t" +Function,+,protocol_dict_get_write_data,_Bool,"ProtocolDict*, size_t, void*" +Function,+,protocol_dict_render_brief_data,void,"ProtocolDict*, string_t, size_t" +Function,+,protocol_dict_render_data,void,"ProtocolDict*, string_t, size_t" +Function,+,protocol_dict_set_data,void,"ProtocolDict*, size_t, const uint8_t*, size_t" Function,-,pselect,int,"int, fd_set*, fd_set*, fd_set*, const timespec*, const sigset_t*" Function,-,putc,int,"int, FILE*" Function,-,putc_unlocked,int,"int, FILE*" @@ -2105,6 +2183,22 @@ Function,-,subghz_protocol_decoder_base_get_hash_data,uint8_t,SubGhzProtocolDeco Function,-,subghz_protocol_decoder_base_get_string,_Bool,"SubGhzProtocolDecoderBase*, string_t" Function,+,subghz_protocol_decoder_base_serialize,_Bool,"SubGhzProtocolDecoderBase*, FlipperFormat*, SubGhzPresetDefinition*" Function,-,subghz_protocol_decoder_base_set_decoder_callback,void,"SubGhzProtocolDecoderBase*, SubGhzProtocolDecoderBaseRxCallback, void*" +Function,+,subghz_protocol_decoder_raw_alloc,void*,SubGhzEnvironment* +Function,+,subghz_protocol_decoder_raw_deserialize,_Bool,"void*, FlipperFormat*" +Function,+,subghz_protocol_decoder_raw_feed,void,"void*, _Bool, uint32_t" +Function,+,subghz_protocol_decoder_raw_free,void,void* +Function,+,subghz_protocol_decoder_raw_get_string,void,"void*, string_t" +Function,+,subghz_protocol_decoder_raw_reset,void,void* +Function,+,subghz_protocol_encoder_raw_alloc,void*,SubGhzEnvironment* +Function,+,subghz_protocol_encoder_raw_deserialize,_Bool,"void*, FlipperFormat*" +Function,+,subghz_protocol_encoder_raw_free,void,void* +Function,+,subghz_protocol_encoder_raw_stop,void,void* +Function,+,subghz_protocol_encoder_raw_yield,LevelDuration,void* +Function,+,subghz_protocol_raw_file_encoder_worker_set_callback_end,void,"SubGhzProtocolEncoderRAW*, SubGhzProtocolEncoderRAWCallbackEnd, void*" +Function,+,subghz_protocol_raw_gen_fff_data,void,"FlipperFormat*, const char*" +Function,+,subghz_protocol_raw_get_sample_write,size_t,SubGhzProtocolDecoderRAW* +Function,+,subghz_protocol_raw_save_to_file_init,_Bool,"SubGhzProtocolDecoderRAW*, const char*, SubGhzPresetDefinition*" +Function,+,subghz_protocol_raw_save_to_file_stop,void,SubGhzProtocolDecoderRAW* Function,+,subghz_receiver_alloc_init,SubGhzReceiver*,SubGhzEnvironment* Function,+,subghz_receiver_decode,void,"SubGhzReceiver*, _Bool, uint32_t" Function,+,subghz_receiver_free,void,SubGhzReceiver* @@ -2144,6 +2238,7 @@ Function,+,submenu_reset,void,Submenu* Function,+,submenu_set_header,void,"Submenu*, const char*" Function,+,submenu_set_selected_item,void,"Submenu*, uint32_t" Function,-,system,int,const char* +Function,+,t5577_write,void,LFRFIDT5577* Function,-,tan,double,double Function,-,tanf,float,float Function,-,tanh,double,double @@ -2667,6 +2762,7 @@ Variable,+,gpio_usb_dp,const GpioPin, Variable,+,ibutton_gpio,const GpioPin, Variable,+,input_pins,const InputPin[], Variable,+,input_pins_count,const size_t, +Variable,+,lfrfid_protocols,const ProtocolBase*[], Variable,+,message_blink_set_color_blue,const NotificationMessage, Variable,+,message_blink_set_color_cyan,const NotificationMessage, Variable,+,message_blink_set_color_green,const NotificationMessage, @@ -2861,6 +2957,9 @@ Variable,+,sequence_set_vibro_on,const NotificationSequence, Variable,+,sequence_single_vibro,const NotificationSequence, Variable,+,sequence_solid_yellow,const NotificationSequence, Variable,+,sequence_success,const NotificationSequence, +Variable,+,subghz_protocol_raw,const SubGhzProtocol, +Variable,+,subghz_protocol_raw_decoder,const SubGhzProtocolDecoder, +Variable,+,subghz_protocol_raw_encoder,const SubGhzProtocolEncoder, Variable,-,suboptarg,char*, Variable,+,usb_cdc_dual,FuriHalUsbInterface, Variable,+,usb_cdc_single,FuriHalUsbInterface, diff --git a/firmware/targets/furi_hal_include/furi_hal_power.h b/firmware/targets/furi_hal_include/furi_hal_power.h index 16af959cf5b..3ab30c42480 100644 --- a/firmware/targets/furi_hal_include/furi_hal_power.h +++ b/firmware/targets/furi_hal_include/furi_hal_power.h @@ -156,12 +156,6 @@ float furi_hal_power_get_battery_current(FuriHalPowerIC ic); */ float furi_hal_power_get_battery_temperature(FuriHalPowerIC ic); -/** Get System voltage in V - * - * @return voltage in V - */ -float furi_hal_power_get_system_voltage(); - /** Get USB voltage in V * * @return voltage in V diff --git a/lib/lfrfid/SConscript b/lib/lfrfid/SConscript index 8d354288321..fd29ca2ef54 100644 --- a/lib/lfrfid/SConscript +++ b/lib/lfrfid/SConscript @@ -7,6 +7,14 @@ env.Append( CPPPATH=[ "#/lib/lfrfid", ], + SDK_HEADERS=[ + File("#/lib/lfrfid/lfrfid_worker.h"), + File("#/lib/lfrfid/lfrfid_raw_worker.h"), + File("#/lib/lfrfid/lfrfid_raw_file.h"), + File("#/lib/lfrfid/lfrfid_dict_file.h"), + File("#/lib/lfrfid/tools/bit_lib.h"), + File("#/lib/lfrfid/protocols/lfrfid_protocols.h"), + ], ) libenv = env.Clone(FW_LIB_NAME="lfrfid") diff --git a/lib/mbedtls.scons b/lib/mbedtls.scons index e39d9dae852..b57221a498e 100644 --- a/lib/mbedtls.scons +++ b/lib/mbedtls.scons @@ -11,6 +11,14 @@ env.Append( libenv = env.Clone(FW_LIB_NAME="mbedtls") libenv.ApplyLibFlags() +libenv.AppendUnique( + CCFLAGS=[ + # Required for lib to be linkable with .faps + "-mword-relocations", + "-mlong-calls", + ], +) + sources = [ "mbedtls/library/des.c", "mbedtls/library/sha1.c", diff --git a/lib/subghz/SConscript b/lib/subghz/SConscript index 50c92cbd16f..eff2f168eac 100644 --- a/lib/subghz/SConscript +++ b/lib/subghz/SConscript @@ -10,6 +10,7 @@ env.Append( File("#/lib/subghz/subghz_worker.h"), File("#/lib/subghz/subghz_tx_rx_worker.h"), File("#/lib/subghz/transmitter.h"), + File("#/lib/subghz/protocols/raw.h"), ], ) diff --git a/lib/subghz/protocols/raw.h b/lib/subghz/protocols/raw.h index 00654ad28eb..b52a7d84e18 100644 --- a/lib/subghz/protocols/raw.h +++ b/lib/subghz/protocols/raw.h @@ -4,6 +4,10 @@ #define SUBGHZ_PROTOCOL_RAW_NAME "RAW" +#ifdef __cplusplus +extern "C" { +#endif + typedef void (*SubGhzProtocolEncoderRAWCallbackEnd)(void* context); typedef struct SubGhzProtocolDecoderRAW SubGhzProtocolDecoderRAW; @@ -99,12 +103,6 @@ void subghz_protocol_encoder_raw_free(void* context); */ void subghz_protocol_encoder_raw_stop(void* context); -/** - * Сallback on completion of file transfer. - * @param context Pointer to a SubGhzProtocolEncoderRAW instance - */ -void subghz_protocol_raw_file_encoder_worker_callback_end(void* context); - /** * Set callback on completion of file transfer. * @param instance Pointer to a SubGhzProtocolEncoderRAW instance @@ -137,3 +135,7 @@ bool subghz_protocol_encoder_raw_deserialize(void* context, FlipperFormat* flipp * @return LevelDuration */ LevelDuration subghz_protocol_encoder_raw_yield(void* context); + +#ifdef __cplusplus +} +#endif diff --git a/lib/toolbox/SConscript b/lib/toolbox/SConscript index 6ced34d7c68..d631431ee8c 100644 --- a/lib/toolbox/SConscript +++ b/lib/toolbox/SConscript @@ -23,6 +23,8 @@ env.Append( File("#/lib/toolbox/stream/stream.h"), File("#/lib/toolbox/stream/file_stream.h"), File("#/lib/toolbox/stream/string_stream.h"), + File("#/lib/toolbox/stream/buffered_file_stream.h"), + File("#/lib/toolbox/protocols/protocol_dict.h"), ], ) diff --git a/site_scons/extapps.scons b/site_scons/extapps.scons index cfffda04f62..4cb5e35cfd4 100644 --- a/site_scons/extapps.scons +++ b/site_scons/extapps.scons @@ -63,7 +63,10 @@ def build_app_as_external(env, appdef): extapps["dist"][appdef.appid] = (appdef.fap_category, compact_elf) -apps_to_build_as_faps = [FlipperAppType.PLUGIN, FlipperAppType.EXTERNAL] +apps_to_build_as_faps = [ + FlipperAppType.PLUGIN, + FlipperAppType.EXTERNAL, +] if appenv["DEBUG_TOOLS"]: apps_to_build_as_faps.append(FlipperAppType.DEBUG) diff --git a/site_scons/fbt/appmanifest.py b/site_scons/fbt/appmanifest.py index a41717d6a37..a0ab5e7c6c4 100644 --- a/site_scons/fbt/appmanifest.py +++ b/site_scons/fbt/appmanifest.py @@ -36,11 +36,16 @@ class FlipperApplication: icon: Optional[str] = None order: int = 0 sdk_headers: List[str] = field(default_factory=list) - version: Tuple[int] = field(default_factory=lambda: (0, 0)) + # .fap-specific sources: List[str] = field(default_factory=lambda: ["*.c*"]) + fap_version: Tuple[int] = field(default_factory=lambda: (0, 0)) fap_icon: Optional[str] = None fap_libs: List[str] = field(default_factory=list) fap_category: str = "" + fap_description: str = "" + fap_author: str = "" + fap_weburl: str = "" + # Internally used by fbt _appdir: Optional[object] = None _apppath: Optional[str] = None diff --git a/site_scons/fbt/elfmanifest.py b/site_scons/fbt/elfmanifest.py index 2f54810d98a..313a64c092e 100644 --- a/site_scons/fbt/elfmanifest.py +++ b/site_scons/fbt/elfmanifest.py @@ -66,8 +66,8 @@ def assemble_manifest_data( ) image_data = image.data - app_version_as_int = ((app_manifest.version[0] & 0xFFFF) << 16) | ( - app_manifest.version[1] & 0xFFFF + app_version_as_int = ((app_manifest.fap_version[0] & 0xFFFF) << 16) | ( + app_manifest.fap_version[1] & 0xFFFF ) data = ElfManifestBaseHeader( diff --git a/site_scons/fbt/sdk.py b/site_scons/fbt/sdk.py index 2e951040d6c..3d4a17b2f21 100644 --- a/site_scons/fbt/sdk.py +++ b/site_scons/fbt/sdk.py @@ -329,7 +329,6 @@ def __init__(self, cache_file: str, load_version_only=False): self.sdk = ApiEntries() self.disabled_entries = set() self.new_entries = set() - self.loaded_dirty = False self.loaded_dirty_version = False self.version_action = VersionBump.NONE @@ -340,8 +339,7 @@ def is_buildable(self) -> bool: return ( self.version != SdkVersion(0, 0) and self.version_action == VersionBump.NONE - and not self.loaded_dirty - and not self.new_entries + and not self._have_pending_entries() ) def _filter_enabled(self, sdk_entries): @@ -388,21 +386,12 @@ def save(self) -> None: if self._load_version_only: raise Exception("Only SDK version was loaded, cannot save") - version_is_clean = True - if self.loaded_dirty: - # There are still new entries and version was already updated - version_is_clean = False - if self.version_action == VersionBump.MINOR: self.version = SdkVersion(self.version.major, self.version.minor + 1) - version_is_clean = False elif self.version_action == VersionBump.MAJOR: self.version = SdkVersion(self.version.major + 1, 0) - version_is_clean = False - if version_is_clean: - print(f"API version {self.version} is up to date") - else: + if self._have_pending_entries(): self.new_entries.add(self.version) print( f"API version is still WIP: {self.version}. Review the changes and re-run command." @@ -418,16 +407,23 @@ def save(self) -> None: ) ) ) + else: + print(f"API version {self.version} is up to date") - if not version_is_clean or self.loaded_dirty_version: - # Regenerate cache file + regenerate_csv = ( + self.loaded_dirty_version + or self._have_pending_entries() + or self.version_action != VersionBump.NONE + ) + + if regenerate_csv: str_cache_entries = [self.version] name_getter = operator.attrgetter("name") str_cache_entries.extend(sorted(self.sdk.headers, key=name_getter)) str_cache_entries.extend(sorted(self.sdk.functions, key=name_getter)) str_cache_entries.extend(sorted(self.sdk.variables, key=name_getter)) - with open(self.cache_file_name, "w", newline="") as f: + with open(self.cache_file_name, "wt", newline="") as f: writer = csv.DictWriter(f, fieldnames=SdkCache.CSV_FIELD_NAMES) writer.writeheader() @@ -476,28 +472,37 @@ def load_cache(self) -> None: f"Cannot load symbol cache '{self.cache_file_name}'! File does not exist" ) - with open(self.cache_file_name, "r") as f: + with open(self.cache_file_name, "rt") as f: reader = csv.DictReader(f) for row in reader: self._process_entry(row) if self._load_version_only and row.get("entry") == SdkVersion.csv_type: break - self.loaded_dirty = bool(self.new_entries) - def sync_sets(self, known_set: Set[Any], new_set: Set[Any]): + def _have_pending_entries(self) -> bool: + return any( + filter( + lambda e: not isinstance(e, SdkVersion), + self.new_entries, + ) + ) + + def sync_sets( + self, known_set: Set[Any], new_set: Set[Any], update_version: bool = True + ): new_entries = new_set - known_set if new_entries: print(f"New: {new_entries}") known_set |= new_entries self.new_entries |= new_entries - if self.version_action == VersionBump.NONE: + if update_version and self.version_action == VersionBump.NONE: self.version_action = VersionBump.MINOR removed_entries = known_set - new_set if removed_entries: print(f"Removed: {removed_entries}") known_set -= removed_entries - # If any of removed entries was part of active API, that's a major bump - if any( + # If any of removed entries was a part of active API, that's a major bump + if update_version and any( filter( lambda e: e not in self.disabled_entries and e not in self.new_entries, @@ -509,6 +514,6 @@ def sync_sets(self, known_set: Set[Any], new_set: Set[Any]): self.new_entries -= removed_entries def validate_api(self, api: ApiEntries) -> None: - self.sync_sets(self.sdk.headers, api.headers) + self.sync_sets(self.sdk.headers, api.headers, False) self.sync_sets(self.sdk.functions, api.functions) self.sync_sets(self.sdk.variables, api.variables) diff --git a/site_scons/fbt/version.py b/site_scons/fbt/version.py index 4745c7002ca..1b1c166f28b 100644 --- a/site_scons/fbt/version.py +++ b/site_scons/fbt/version.py @@ -1,7 +1,9 @@ import subprocess import datetime +from functools import cache +@cache def get_fast_git_version_id(): try: version = ( diff --git a/site_scons/site_tools/fbt_sdk.py b/site_scons/site_tools/fbt_sdk.py index 26d663650a4..f6c2d452e41 100644 --- a/site_scons/site_tools/fbt_sdk.py +++ b/site_scons/site_tools/fbt_sdk.py @@ -15,6 +15,7 @@ def prebuild_sdk_emitter(target, source, env): target.append(env.ChangeFileExtension(target[0], ".d")) + target.append(env.ChangeFileExtension(target[0], ".i.c")) return target, source From 3d3c422751c7be83297ec4d369851ce51902fe3d Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Mon, 19 Sep 2022 15:46:56 +0300 Subject: [PATCH 061/824] [FL-2674] Show error popup when NFC chip is not init/disconnected (#1722) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Show error popup when NFC chip is not init/disconnected * Move to dialogs for the error message * Fix a memory leak and wrap the hal check * F7: update api_symbols.csv, add furi_hal_nfc_is_init Co-authored-by: SG Co-authored-by: あく --- applications/main/nfc/nfc.c | 23 +++++++++++++++++++ applications/main/nfc/nfc_i.h | 2 ++ firmware/targets/f7/api_symbols.csv | 3 ++- firmware/targets/f7/furi_hal/furi_hal_nfc.c | 4 ++++ .../targets/furi_hal_include/furi_hal_nfc.h | 6 +++++ 5 files changed, 37 insertions(+), 1 deletion(-) diff --git a/applications/main/nfc/nfc.c b/applications/main/nfc/nfc.c index 43263020415..f47a5bac29f 100644 --- a/applications/main/nfc/nfc.c +++ b/applications/main/nfc/nfc.c @@ -231,7 +231,30 @@ void nfc_show_loading_popup(void* context, bool show) { } } +static bool nfc_is_hal_ready() { + if(!furi_hal_nfc_is_init()) { + // No connection to the chip, show an error screen + DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); + DialogMessage* message = dialog_message_alloc(); + dialog_message_set_text( + message, + "Error!\nNFC chip failed to start\n\n\nSend a photo of this to:\nsupport@flipperzero.one", + 0, + 0, + AlignLeft, + AlignTop); + dialog_message_show(dialogs, message); + dialog_message_free(message); + furi_record_close(RECORD_DIALOGS); + return false; + } else { + return true; + } +} + int32_t nfc_app(void* p) { + if(!nfc_is_hal_ready()) return 0; + Nfc* nfc = nfc_alloc(); char* args = p; diff --git a/applications/main/nfc/nfc_i.h b/applications/main/nfc/nfc_i.h index b5d284d5b68..60e1c199721 100644 --- a/applications/main/nfc/nfc_i.h +++ b/applications/main/nfc/nfc_i.h @@ -33,6 +33,8 @@ #include #include +#include + #include "rpc/rpc_app.h" #define NFC_TEXT_STORE_SIZE 128 diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 3f2a86b24db..906a7b8b678 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,1.3,, +Version,+,1.4,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -1096,6 +1096,7 @@ Function,+,furi_hal_nfc_field_off,void, Function,+,furi_hal_nfc_field_on,void, Function,-,furi_hal_nfc_init,void, Function,+,furi_hal_nfc_is_busy,_Bool, +Function,+,furi_hal_nfc_is_init,_Bool, Function,+,furi_hal_nfc_listen,_Bool,"uint8_t*, uint8_t, uint8_t*, uint8_t, _Bool, uint32_t" Function,+,furi_hal_nfc_listen_rx,_Bool,"FuriHalNfcTxRxContext*, uint32_t" Function,+,furi_hal_nfc_listen_sleep,void, diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc.c b/firmware/targets/f7/furi_hal/furi_hal_nfc.c index 1a5afa1ec67..de67bbc358f 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_nfc.c +++ b/firmware/targets/f7/furi_hal/furi_hal_nfc.c @@ -39,6 +39,10 @@ bool furi_hal_nfc_is_busy() { return rfalNfcGetState() != RFAL_NFC_STATE_IDLE; } +bool furi_hal_nfc_is_init() { + return rfalNfcGetState() != RFAL_NFC_STATE_NOTINIT; +} + void furi_hal_nfc_field_on() { furi_hal_nfc_exit_sleep(); st25r3916TxRxOn(); diff --git a/firmware/targets/furi_hal_include/furi_hal_nfc.h b/firmware/targets/furi_hal_include/furi_hal_nfc.h index aa1c61d9c6e..537e0abf020 100644 --- a/firmware/targets/furi_hal_include/furi_hal_nfc.h +++ b/firmware/targets/furi_hal_include/furi_hal_nfc.h @@ -107,6 +107,12 @@ void furi_hal_nfc_init(); */ bool furi_hal_nfc_is_busy(); +/** Check if nfc is initialized + * + * @return true if initialized + */ + bool furi_hal_nfc_is_init(); + /** NFC field on */ void furi_hal_nfc_field_on(); From d80329b323440dbb9b2f7f3a3cdb0c2175e832c9 Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Mon, 19 Sep 2022 16:03:42 +0300 Subject: [PATCH 062/824] [FL-2815, FL-2821] Dummy mode (#1739) * Dummy mode implementation * dumb -> dummy * F7: Add new api_symbols: game icon * Starting snake game from dummy mode Co-authored-by: Aleksandr Kutuzov --- .../main/archive/helpers/archive_browser.c | 8 +- applications/services/desktop/desktop.c | 46 +++++-- applications/services/desktop/desktop_i.h | 4 +- .../services/desktop/desktop_settings.h | 7 +- .../services/desktop/helpers/pin_lock.c | 6 +- .../desktop/scenes/desktop_scene_lock_menu.c | 21 ++- .../desktop/scenes/desktop_scene_locked.c | 4 +- .../desktop/scenes/desktop_scene_main.c | 19 ++- .../services/desktop/views/desktop_events.h | 7 +- .../desktop/views/desktop_view_lock_menu.c | 128 +++++++++++------- .../desktop/views/desktop_view_lock_menu.h | 7 +- .../desktop/views/desktop_view_main.c | 56 +++++--- .../desktop/views/desktop_view_main.h | 1 + .../desktop_settings/desktop_settings_app.c | 2 +- .../scenes/desktop_settings_scene_favorite.c | 2 +- .../scenes/desktop_settings_scene_pin_auth.c | 2 +- .../desktop_settings_scene_pin_disable.c | 2 +- .../desktop_settings_scene_pin_setup_done.c | 2 +- .../scenes/desktop_settings_scene_start.c | 2 +- assets/icons/StatusBar/GameMode_11x8.png | Bin 0 -> 3610 bytes firmware/targets/f7/api_symbols.csv | 3 +- 21 files changed, 216 insertions(+), 113 deletions(-) create mode 100644 assets/icons/StatusBar/GameMode_11x8.png diff --git a/applications/main/archive/helpers/archive_browser.c b/applications/main/archive/helpers/archive_browser.c index b08e5898322..00bb6b06331 100644 --- a/applications/main/archive/helpers/archive_browser.c +++ b/applications/main/archive/helpers/archive_browser.c @@ -270,7 +270,7 @@ void archive_file_array_load(ArchiveBrowserView* browser, int8_t dir) { ArchiveFile_t* archive_get_current_file(ArchiveBrowserView* browser) { furi_assert(browser); - ArchiveFile_t* selected; + ArchiveFile_t* selected = NULL; with_view_model( browser->view, (ArchiveBrowserViewModel * model) { selected = files_array_size(model->files) ? @@ -284,7 +284,7 @@ ArchiveFile_t* archive_get_current_file(ArchiveBrowserView* browser) { ArchiveFile_t* archive_get_file_at(ArchiveBrowserView* browser, size_t idx) { furi_assert(browser); - ArchiveFile_t* selected; + ArchiveFile_t* selected = NULL; with_view_model( browser->view, (ArchiveBrowserViewModel * model) { @@ -298,7 +298,7 @@ ArchiveFile_t* archive_get_file_at(ArchiveBrowserView* browser, size_t idx) { ArchiveTabEnum archive_get_tab(ArchiveBrowserView* browser) { furi_assert(browser); - ArchiveTabEnum tab_id; + ArchiveTabEnum tab_id = 0; with_view_model( browser->view, (ArchiveBrowserViewModel * model) { tab_id = model->tab_idx; @@ -451,8 +451,6 @@ void archive_switch_tab(ArchiveBrowserView* browser, InputKey key) { archive_file_browser_set_path( browser, browser->path, archive_get_tab_ext(tab), skip_assets); tab_empty = false; // Empty check will be performed later - } else { - tab_empty = true; } } diff --git a/applications/services/desktop/desktop.c b/applications/services/desktop/desktop.c index 578066a6a41..b45a9d62186 100644 --- a/applications/services/desktop/desktop.c +++ b/applications/services/desktop/desktop.c @@ -32,13 +32,18 @@ static void desktop_loader_callback(const void* message, void* context) { view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopGlobalAfterAppFinished); } } - -static void desktop_lock_icon_callback(Canvas* canvas, void* context) { +static void desktop_lock_icon_draw_callback(Canvas* canvas, void* context) { UNUSED(context); furi_assert(canvas); canvas_draw_icon(canvas, 0, 0, &I_Lock_8x8); } +static void desktop_dummy_mode_icon_draw_callback(Canvas* canvas, void* context) { + UNUSED(context); + furi_assert(canvas); + canvas_draw_icon(canvas, 0, 0, &I_GameMode_11x8); +} + static bool desktop_custom_event_callback(void* context, uint32_t event) { furi_assert(context); Desktop* desktop = (Desktop*)context; @@ -52,7 +57,7 @@ static bool desktop_custom_event_callback(void* context, uint32_t event) { animation_manager_load_and_continue_animation(desktop->animation_manager); // TODO: Implement a message mechanism for loading settings and (optionally) // locking and unlocking - LOAD_DESKTOP_SETTINGS(&desktop->settings); + DESKTOP_SETTINGS_LOAD(&desktop->settings); desktop_auto_lock_arm(desktop); return true; case DesktopGlobalAutoLock: @@ -127,7 +132,7 @@ void desktop_lock(Desktop* desktop) { } void desktop_unlock(Desktop* desktop) { - view_port_enabled_set(desktop->lock_viewport, false); + view_port_enabled_set(desktop->lock_icon_viewport, false); Gui* gui = furi_record_open(RECORD_GUI); gui_set_lockdown(gui, false); furi_record_close(RECORD_GUI); @@ -136,6 +141,13 @@ void desktop_unlock(Desktop* desktop) { desktop_auto_lock_arm(desktop); } +void desktop_set_dummy_mode_state(Desktop* desktop, bool enabled) { + view_port_enabled_set(desktop->dummy_mode_icon_viewport, enabled); + desktop_main_set_dummy_mode_state(desktop->main_view, enabled); + desktop->settings.dummy_mode = enabled; + DESKTOP_SETTINGS_SAVE(&desktop->settings); +} + Desktop* desktop_alloc() { Desktop* desktop = malloc(sizeof(Desktop)); @@ -212,11 +224,20 @@ Desktop* desktop_alloc() { desktop_view_slideshow_get_view(desktop->slideshow_view)); // Lock icon - desktop->lock_viewport = view_port_alloc(); - view_port_set_width(desktop->lock_viewport, icon_get_width(&I_Lock_8x8)); - view_port_draw_callback_set(desktop->lock_viewport, desktop_lock_icon_callback, desktop); - view_port_enabled_set(desktop->lock_viewport, false); - gui_add_view_port(desktop->gui, desktop->lock_viewport, GuiLayerStatusBarLeft); + desktop->lock_icon_viewport = view_port_alloc(); + view_port_set_width(desktop->lock_icon_viewport, icon_get_width(&I_Lock_8x8)); + view_port_draw_callback_set( + desktop->lock_icon_viewport, desktop_lock_icon_draw_callback, desktop); + view_port_enabled_set(desktop->lock_icon_viewport, false); + gui_add_view_port(desktop->gui, desktop->lock_icon_viewport, GuiLayerStatusBarLeft); + + // Dummy mode icon + desktop->dummy_mode_icon_viewport = view_port_alloc(); + view_port_set_width(desktop->dummy_mode_icon_viewport, icon_get_width(&I_GameMode_11x8)); + view_port_draw_callback_set( + desktop->dummy_mode_icon_viewport, desktop_dummy_mode_icon_draw_callback, desktop); + view_port_enabled_set(desktop->dummy_mode_icon_viewport, false); + gui_add_view_port(desktop->gui, desktop->dummy_mode_icon_viewport, GuiLayerStatusBarLeft); // Special case: autostart application is already running desktop->loader = furi_record_open(RECORD_LOADER); @@ -301,12 +322,15 @@ int32_t desktop_srv(void* p) { UNUSED(p); Desktop* desktop = desktop_alloc(); - bool loaded = LOAD_DESKTOP_SETTINGS(&desktop->settings); + bool loaded = DESKTOP_SETTINGS_LOAD(&desktop->settings); if(!loaded) { memset(&desktop->settings, 0, sizeof(desktop->settings)); - SAVE_DESKTOP_SETTINGS(&desktop->settings); + DESKTOP_SETTINGS_SAVE(&desktop->settings); } + view_port_enabled_set(desktop->dummy_mode_icon_viewport, desktop->settings.dummy_mode); + desktop_main_set_dummy_mode_state(desktop->main_view, desktop->settings.dummy_mode); + scene_manager_next_scene(desktop->scene_manager, DesktopSceneMain); desktop_pin_lock_init(&desktop->settings); diff --git a/applications/services/desktop/desktop_i.h b/applications/services/desktop/desktop_i.h index 65ca563ddfc..8034bbc3ac0 100644 --- a/applications/services/desktop/desktop_i.h +++ b/applications/services/desktop/desktop_i.h @@ -57,7 +57,8 @@ struct Desktop { DesktopSettings settings; DesktopViewPinInput* pin_input_view; - ViewPort* lock_viewport; + ViewPort* lock_icon_viewport; + ViewPort* dummy_mode_icon_viewport; AnimationManager* animation_manager; @@ -75,3 +76,4 @@ Desktop* desktop_alloc(); void desktop_free(Desktop* desktop); void desktop_lock(Desktop* desktop); void desktop_unlock(Desktop* desktop); +void desktop_set_dummy_mode_state(Desktop* desktop, bool enabled); diff --git a/applications/services/desktop/desktop_settings.h b/applications/services/desktop/desktop_settings.h index 800847d56a7..3b10fbb17ea 100644 --- a/applications/services/desktop/desktop_settings.h +++ b/applications/services/desktop/desktop_settings.h @@ -8,7 +8,7 @@ #include #include -#define DESKTOP_SETTINGS_VER (4) +#define DESKTOP_SETTINGS_VER (5) #define DESKTOP_SETTINGS_PATH INT_PATH(DESKTOP_SETTINGS_FILE_NAME) #define DESKTOP_SETTINGS_MAGIC (0x17) @@ -16,7 +16,7 @@ #define DESKTOP_SETTINGS_RUN_PIN_SETUP_ARG "run_pin_setup" -#define SAVE_DESKTOP_SETTINGS(x) \ +#define DESKTOP_SETTINGS_SAVE(x) \ saved_struct_save( \ DESKTOP_SETTINGS_PATH, \ (x), \ @@ -24,7 +24,7 @@ DESKTOP_SETTINGS_MAGIC, \ DESKTOP_SETTINGS_VER) -#define LOAD_DESKTOP_SETTINGS(x) \ +#define DESKTOP_SETTINGS_LOAD(x) \ saved_struct_load( \ DESKTOP_SETTINGS_PATH, \ (x), \ @@ -46,4 +46,5 @@ typedef struct { PinCode pin_code; uint8_t is_locked; uint32_t auto_lock_delay_ms; + uint8_t dummy_mode; } DesktopSettings; diff --git a/applications/services/desktop/helpers/pin_lock.c b/applications/services/desktop/helpers/pin_lock.c index 9e17a925096..22fcabe7d50 100644 --- a/applications/services/desktop/helpers/pin_lock.c +++ b/applications/services/desktop/helpers/pin_lock.c @@ -72,7 +72,7 @@ void desktop_pin_lock(DesktopSettings* settings) { cli_session_close(cli); furi_record_close(RECORD_CLI); settings->is_locked = 1; - SAVE_DESKTOP_SETTINGS(settings); + DESKTOP_SETTINGS_SAVE(settings); } void desktop_pin_unlock(DesktopSettings* settings) { @@ -83,7 +83,7 @@ void desktop_pin_unlock(DesktopSettings* settings) { cli_session_open(cli, &cli_vcp); furi_record_close(RECORD_CLI); settings->is_locked = 0; - SAVE_DESKTOP_SETTINGS(settings); + DESKTOP_SETTINGS_SAVE(settings); } void desktop_pin_lock_init(DesktopSettings* settings) { @@ -95,7 +95,7 @@ void desktop_pin_lock_init(DesktopSettings* settings) { } else { if(desktop_pin_lock_is_locked()) { settings->is_locked = 1; - SAVE_DESKTOP_SETTINGS(settings); + DESKTOP_SETTINGS_SAVE(settings); } } } else { diff --git a/applications/services/desktop/scenes/desktop_scene_lock_menu.c b/applications/services/desktop/scenes/desktop_scene_lock_menu.c index bdd84cadb9f..365fe17023c 100644 --- a/applications/services/desktop/scenes/desktop_scene_lock_menu.c +++ b/applications/services/desktop/scenes/desktop_scene_lock_menu.c @@ -22,10 +22,11 @@ void desktop_scene_lock_menu_callback(DesktopEvent event, void* context) { void desktop_scene_lock_menu_on_enter(void* context) { Desktop* desktop = (Desktop*)context; - LOAD_DESKTOP_SETTINGS(&desktop->settings); + DESKTOP_SETTINGS_LOAD(&desktop->settings); scene_manager_set_scene_state(desktop->scene_manager, DesktopSceneLockMenu, 0); desktop_lock_menu_set_callback(desktop->lock_menu, desktop_scene_lock_menu_callback, desktop); - desktop_lock_menu_pin_set(desktop->lock_menu, desktop->settings.pin_code.length > 0); + desktop_lock_menu_set_pin_state(desktop->lock_menu, desktop->settings.pin_code.length > 0); + desktop_lock_menu_set_dummy_mode_state(desktop->lock_menu, desktop->settings.dummy_mode); desktop_lock_menu_set_idx(desktop->lock_menu, 0); view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewIdLockMenu); @@ -39,9 +40,9 @@ bool desktop_scene_lock_menu_on_event(void* context, SceneManagerEvent event) { bool check_pin_changed = scene_manager_get_scene_state(desktop->scene_manager, DesktopSceneLockMenu); if(check_pin_changed) { - LOAD_DESKTOP_SETTINGS(&desktop->settings); + DESKTOP_SETTINGS_LOAD(&desktop->settings); if(desktop->settings.pin_code.length > 0) { - desktop_lock_menu_pin_set(desktop->lock_menu, 1); + desktop_lock_menu_set_pin_state(desktop->lock_menu, true); scene_manager_set_scene_state(desktop->scene_manager, DesktopSceneLockMenu, 0); } } @@ -67,15 +68,21 @@ bool desktop_scene_lock_menu_on_event(void* context, SceneManagerEvent event) { } consumed = true; break; - case DesktopLockMenuEventExit: - scene_manager_set_scene_state(desktop->scene_manager, DesktopSceneLockMenu, 0); + case DesktopLockMenuEventDummyModeOn: + desktop_set_dummy_mode_state(desktop, true); + scene_manager_search_and_switch_to_previous_scene( + desktop->scene_manager, DesktopSceneMain); + break; + case DesktopLockMenuEventDummyModeOff: + desktop_set_dummy_mode_state(desktop, false); scene_manager_search_and_switch_to_previous_scene( desktop->scene_manager, DesktopSceneMain); - consumed = true; break; default: break; } + } else if(event.type == SceneManagerEventTypeBack) { + scene_manager_set_scene_state(desktop->scene_manager, DesktopSceneLockMenu, 0); } return consumed; } diff --git a/applications/services/desktop/scenes/desktop_scene_locked.c b/applications/services/desktop/scenes/desktop_scene_locked.c index c377d40ab3b..af19efc7479 100644 --- a/applications/services/desktop/scenes/desktop_scene_locked.c +++ b/applications/services/desktop/scenes/desktop_scene_locked.c @@ -46,13 +46,13 @@ void desktop_scene_locked_on_enter(void* context) { uint32_t state = scene_manager_get_scene_state(desktop->scene_manager, DesktopSceneLocked); if(state == SCENE_LOCKED_FIRST_ENTER) { bool pin_locked = desktop_pin_lock_is_locked(); - view_port_enabled_set(desktop->lock_viewport, true); + view_port_enabled_set(desktop->lock_icon_viewport, true); Gui* gui = furi_record_open(RECORD_GUI); gui_set_lockdown(gui, true); furi_record_close(RECORD_GUI); if(pin_locked) { - LOAD_DESKTOP_SETTINGS(&desktop->settings); + DESKTOP_SETTINGS_LOAD(&desktop->settings); desktop_view_locked_lock(desktop->locked_view, true); uint32_t pin_timeout = desktop_pin_lock_get_fail_timeout(); if(pin_timeout > 0) { diff --git a/applications/services/desktop/scenes/desktop_scene_main.c b/applications/services/desktop/scenes/desktop_scene_main.c index bc4101ffd5b..dc9ac04d0ee 100644 --- a/applications/services/desktop/scenes/desktop_scene_main.c +++ b/applications/services/desktop/scenes/desktop_scene_main.c @@ -113,7 +113,7 @@ bool desktop_scene_main_on_event(void* context, SceneManagerEvent event) { } case DesktopMainEventOpenFavoritePrimary: - LOAD_DESKTOP_SETTINGS(&desktop->settings); + DESKTOP_SETTINGS_LOAD(&desktop->settings); if(desktop->settings.favorite_primary < FLIPPER_APPS_COUNT) { LoaderStatus status = loader_start( desktop->loader, FLIPPER_APPS[desktop->settings.favorite_primary].name, NULL); @@ -126,7 +126,7 @@ bool desktop_scene_main_on_event(void* context, SceneManagerEvent event) { consumed = true; break; case DesktopMainEventOpenFavoriteSecondary: - LOAD_DESKTOP_SETTINGS(&desktop->settings); + DESKTOP_SETTINGS_LOAD(&desktop->settings); if(desktop->settings.favorite_secondary < FLIPPER_APPS_COUNT) { LoaderStatus status = loader_start( desktop->loader, @@ -157,6 +157,21 @@ bool desktop_scene_main_on_event(void* context, SceneManagerEvent event) { } consumed = true; break; + case DesktopMainEventOpenPassport: { + LoaderStatus status = loader_start(desktop->loader, "Passport", NULL); + if(status != LoaderStatusOk) { + FURI_LOG_E(TAG, "loader_start failed: %d", status); + } + break; + } + case DesktopMainEventOpenGameMenu: { + LoaderStatus status = loader_start( + desktop->loader, "Applications", EXT_PATH("/apps/Games/snake_game.fap")); + if(status != LoaderStatusOk) { + FURI_LOG_E(TAG, "loader_start failed: %d", status); + } + break; + } case DesktopLockedEventUpdate: desktop_view_locked_update(desktop->locked_view); consumed = true; diff --git a/applications/services/desktop/views/desktop_events.h b/applications/services/desktop/views/desktop_events.h index 5d130be9bdf..a7e610fff1b 100644 --- a/applications/services/desktop/views/desktop_events.h +++ b/applications/services/desktop/views/desktop_events.h @@ -7,9 +7,11 @@ typedef enum { DesktopMainEventOpenFavoriteSecondary, DesktopMainEventOpenMenu, DesktopMainEventOpenDebug, - DesktopMainEventOpenPassport, /**< Broken, don't use it */ + DesktopMainEventOpenPassport, DesktopMainEventOpenPowerOff, + DesktopMainEventOpenGameMenu, + DesktopLockedEventUnlocked, DesktopLockedEventUpdate, DesktopLockedEventShowPinInput, @@ -28,7 +30,8 @@ typedef enum { DesktopLockMenuEventLock, DesktopLockMenuEventPinLock, - DesktopLockMenuEventExit, + DesktopLockMenuEventDummyModeOn, + DesktopLockMenuEventDummyModeOff, DesktopAnimationEventCheckAnimation, DesktopAnimationEventNewIdleAnimation, diff --git a/applications/services/desktop/views/desktop_view_lock_menu.c b/applications/services/desktop/views/desktop_view_lock_menu.c index 97d3c4898e5..294191bf292 100644 --- a/applications/services/desktop/views/desktop_view_lock_menu.c +++ b/applications/services/desktop/views/desktop_view_lock_menu.c @@ -4,7 +4,13 @@ #include "../desktop_i.h" #include "desktop_view_lock_menu.h" -#define LOCK_MENU_ITEMS_NB 3 +typedef enum { + DesktopLockMenuIndexLock, + DesktopLockMenuIndexPinLock, + DesktopLockMenuIndexDummy, + + DesktopLockMenuIndexTotalCount +} DesktopLockMenuIndex; void desktop_lock_menu_set_callback( DesktopLockMenuView* lock_menu, @@ -16,60 +22,59 @@ void desktop_lock_menu_set_callback( lock_menu->context = context; } -void desktop_lock_menu_pin_set(DesktopLockMenuView* lock_menu, bool pin_is_set) { +void desktop_lock_menu_set_pin_state(DesktopLockMenuView* lock_menu, bool pin_is_set) { with_view_model( lock_menu->view, (DesktopLockMenuViewModel * model) { - model->pin_set = pin_is_set; + model->pin_is_set = pin_is_set; return true; }); } -void desktop_lock_menu_set_idx(DesktopLockMenuView* lock_menu, uint8_t idx) { - furi_assert(idx < LOCK_MENU_ITEMS_NB); +void desktop_lock_menu_set_dummy_mode_state(DesktopLockMenuView* lock_menu, bool dummy_mode) { with_view_model( lock_menu->view, (DesktopLockMenuViewModel * model) { - model->idx = idx; + model->dummy_mode = dummy_mode; return true; }); } -static void lock_menu_callback(void* context, uint8_t index) { - furi_assert(context); - DesktopLockMenuView* lock_menu = context; - switch(index) { - case 0: // lock - lock_menu->callback(DesktopLockMenuEventLock, lock_menu->context); - break; - case 1: // lock - lock_menu->callback(DesktopLockMenuEventPinLock, lock_menu->context); - break; - default: // wip message - with_view_model( - lock_menu->view, (DesktopLockMenuViewModel * model) { - model->hint_timeout = HINT_TIMEOUT; - return true; - }); - break; - } +void desktop_lock_menu_set_idx(DesktopLockMenuView* lock_menu, uint8_t idx) { + furi_assert(idx < DesktopLockMenuIndexTotalCount); + with_view_model( + lock_menu->view, (DesktopLockMenuViewModel * model) { + model->idx = idx; + return true; + }); } -void desktop_lock_menu_render(Canvas* canvas, void* model) { - const char* Lockmenu_Items[LOCK_MENU_ITEMS_NB] = {"Lock", "Lock with PIN", "DUMB mode"}; - +void desktop_lock_menu_draw_callback(Canvas* canvas, void* model) { DesktopLockMenuViewModel* m = model; - canvas_clear(canvas); + canvas_set_color(canvas, ColorBlack); canvas_draw_icon(canvas, -57, 0 + STATUS_BAR_Y_SHIFT, &I_DoorLeft_70x55); canvas_draw_icon(canvas, 116, 0 + STATUS_BAR_Y_SHIFT, &I_DoorRight_70x55); canvas_set_font(canvas, FontSecondary); - for(uint8_t i = 0; i < LOCK_MENU_ITEMS_NB; ++i) { - const char* str = Lockmenu_Items[i]; + for(uint8_t i = 0; i < DesktopLockMenuIndexTotalCount; ++i) { + const char* str = NULL; - if(i == 1 && !m->pin_set) str = "Set PIN"; - if(m->hint_timeout && m->idx == 2 && m->idx == i) str = "Not Implemented"; + if(i == DesktopLockMenuIndexLock) { + str = "Lock"; + } else if(i == DesktopLockMenuIndexPinLock) { + if(m->pin_is_set) { + str = "Lock with PIN"; + } else { + str = "Set PIN"; + } + } else if(i == DesktopLockMenuIndexDummy) { + if(m->dummy_mode) { + str = "Brainiac Mode"; + } else { + str = "Dummy Mode"; + } + } - if(str != NULL) + if(str) canvas_draw_str_aligned( canvas, 64, 9 + (i * 17) + STATUS_BAR_Y_SHIFT, AlignCenter, AlignCenter, str); @@ -82,33 +87,58 @@ View* desktop_lock_menu_get_view(DesktopLockMenuView* lock_menu) { return lock_menu->view; } -bool desktop_lock_menu_input(InputEvent* event, void* context) { +bool desktop_lock_menu_input_callback(InputEvent* event, void* context) { furi_assert(event); furi_assert(context); DesktopLockMenuView* lock_menu = context; - uint8_t idx; + uint8_t idx = 0; + bool consumed = false; + bool dummy_mode = false; - if(event->type != InputTypeShort) return false; with_view_model( lock_menu->view, (DesktopLockMenuViewModel * model) { - model->hint_timeout = 0; // clear hint timeout - if(event->key == InputKeyUp) { - model->idx = CLAMP(model->idx - 1, LOCK_MENU_ITEMS_NB - 1, 0); - } else if(event->key == InputKeyDown) { - model->idx = CLAMP(model->idx + 1, LOCK_MENU_ITEMS_NB - 1, 0); + bool ret = false; + if((event->type == InputTypeShort) || (event->type == InputTypeRepeat)) { + if(event->key == InputKeyUp) { + if(model->idx == 0) { + model->idx = DesktopLockMenuIndexTotalCount - 1; + } else { + model->idx = CLAMP(model->idx - 1, DesktopLockMenuIndexTotalCount - 1, 0); + } + ret = true; + consumed = true; + } else if(event->key == InputKeyDown) { + if(model->idx == DesktopLockMenuIndexTotalCount - 1) { + model->idx = 0; + } else { + model->idx = CLAMP(model->idx + 1, DesktopLockMenuIndexTotalCount - 1, 0); + } + ret = true; + consumed = true; + } } idx = model->idx; - return true; + dummy_mode = model->dummy_mode; + return ret; }); - if(event->key == InputKeyBack) { - lock_menu->callback(DesktopLockMenuEventExit, lock_menu->context); - } else if(event->key == InputKeyOk) { - lock_menu_callback(lock_menu, idx); + if(event->key == InputKeyOk) { + if((idx == DesktopLockMenuIndexLock) && (event->type == InputTypeShort)) { + lock_menu->callback(DesktopLockMenuEventLock, lock_menu->context); + } else if((idx == DesktopLockMenuIndexPinLock) && (event->type == InputTypeShort)) { + lock_menu->callback(DesktopLockMenuEventPinLock, lock_menu->context); + } else if(idx == DesktopLockMenuIndexDummy) { + if((dummy_mode == false) && (event->type == InputTypeShort)) { + lock_menu->callback(DesktopLockMenuEventDummyModeOn, lock_menu->context); + } else if((dummy_mode == true) && (event->type == InputTypeShort)) { + lock_menu->callback(DesktopLockMenuEventDummyModeOff, lock_menu->context); + } + } + consumed = true; } - return true; + return consumed; } DesktopLockMenuView* desktop_lock_menu_alloc() { @@ -116,8 +146,8 @@ DesktopLockMenuView* desktop_lock_menu_alloc() { lock_menu->view = view_alloc(); view_allocate_model(lock_menu->view, ViewModelTypeLocking, sizeof(DesktopLockMenuViewModel)); view_set_context(lock_menu->view, lock_menu); - view_set_draw_callback(lock_menu->view, (ViewDrawCallback)desktop_lock_menu_render); - view_set_input_callback(lock_menu->view, desktop_lock_menu_input); + view_set_draw_callback(lock_menu->view, (ViewDrawCallback)desktop_lock_menu_draw_callback); + view_set_input_callback(lock_menu->view, desktop_lock_menu_input_callback); return lock_menu; } diff --git a/applications/services/desktop/views/desktop_view_lock_menu.h b/applications/services/desktop/views/desktop_view_lock_menu.h index e9928a38551..812aa9f99e8 100644 --- a/applications/services/desktop/views/desktop_view_lock_menu.h +++ b/applications/services/desktop/views/desktop_view_lock_menu.h @@ -17,8 +17,8 @@ struct DesktopLockMenuView { typedef struct { uint8_t idx; - uint8_t hint_timeout; - bool pin_set; + bool pin_is_set; + bool dummy_mode; } DesktopLockMenuViewModel; void desktop_lock_menu_set_callback( @@ -27,7 +27,8 @@ void desktop_lock_menu_set_callback( void* context); View* desktop_lock_menu_get_view(DesktopLockMenuView* lock_menu); -void desktop_lock_menu_pin_set(DesktopLockMenuView* lock_menu, bool pin_is_set); +void desktop_lock_menu_set_pin_state(DesktopLockMenuView* lock_menu, bool pin_is_set); +void desktop_lock_menu_set_dummy_mode_state(DesktopLockMenuView* lock_menu, bool dummy_mode); void desktop_lock_menu_set_idx(DesktopLockMenuView* lock_menu, uint8_t idx); DesktopLockMenuView* desktop_lock_menu_alloc(); void desktop_lock_menu_free(DesktopLockMenuView* lock_menu); diff --git a/applications/services/desktop/views/desktop_view_main.c b/applications/services/desktop/views/desktop_view_main.c index 2b1d61f41f7..0edc0b703ef 100644 --- a/applications/services/desktop/views/desktop_view_main.c +++ b/applications/services/desktop/views/desktop_view_main.c @@ -14,6 +14,7 @@ struct DesktopMainView { DesktopMainViewCallback callback; void* context; TimerHandle_t poweroff_timer; + bool dummy_mode; }; #define DESKTOP_MAIN_VIEW_POWEROFF_TIMEOUT 5000 @@ -38,29 +39,48 @@ View* desktop_main_get_view(DesktopMainView* main_view) { return main_view->view; } -bool desktop_main_input(InputEvent* event, void* context) { +void desktop_main_set_dummy_mode_state(DesktopMainView* main_view, bool dummy_mode) { + furi_assert(main_view); + main_view->dummy_mode = dummy_mode; +} + +bool desktop_main_input_callback(InputEvent* event, void* context) { furi_assert(event); furi_assert(context); DesktopMainView* main_view = context; - if(event->type == InputTypeShort) { - if(event->key == InputKeyOk) { - main_view->callback(DesktopMainEventOpenMenu, main_view->context); - } else if(event->key == InputKeyUp) { - main_view->callback(DesktopMainEventOpenLockMenu, main_view->context); - } else if(event->key == InputKeyDown) { - main_view->callback(DesktopMainEventOpenArchive, main_view->context); - } else if(event->key == InputKeyLeft) { - main_view->callback(DesktopMainEventOpenFavoritePrimary, main_view->context); - } else if(event->key == InputKeyRight) { - main_view->callback(DesktopMainEventOpenPassport, main_view->context); + if(main_view->dummy_mode == false) { + if(event->type == InputTypeShort) { + if(event->key == InputKeyOk) { + main_view->callback(DesktopMainEventOpenMenu, main_view->context); + } else if(event->key == InputKeyUp) { + main_view->callback(DesktopMainEventOpenLockMenu, main_view->context); + } else if(event->key == InputKeyDown) { + main_view->callback(DesktopMainEventOpenArchive, main_view->context); + } else if(event->key == InputKeyLeft) { + main_view->callback(DesktopMainEventOpenFavoritePrimary, main_view->context); + } + // Right key is handled by animation manager + } else if(event->type == InputTypeLong) { + if(event->key == InputKeyDown) { + main_view->callback(DesktopMainEventOpenDebug, main_view->context); + } else if(event->key == InputKeyLeft) { + main_view->callback(DesktopMainEventOpenFavoriteSecondary, main_view->context); + } } - } else if(event->type == InputTypeLong) { - if(event->key == InputKeyDown) { - main_view->callback(DesktopMainEventOpenDebug, main_view->context); - } else if(event->key == InputKeyLeft) { - main_view->callback(DesktopMainEventOpenFavoriteSecondary, main_view->context); + } else { + if(event->type == InputTypeShort) { + if(event->key == InputKeyOk) { + main_view->callback(DesktopMainEventOpenGameMenu, main_view->context); + } else if(event->key == InputKeyUp) { + main_view->callback(DesktopMainEventOpenLockMenu, main_view->context); + } else if(event->key == InputKeyDown) { + main_view->callback(DesktopMainEventOpenPassport, main_view->context); + } else if(event->key == InputKeyLeft) { + main_view->callback(DesktopMainEventOpenPassport, main_view->context); + } + // Right key is handled by animation manager } } @@ -84,7 +104,7 @@ DesktopMainView* desktop_main_alloc() { main_view->view = view_alloc(); view_allocate_model(main_view->view, ViewModelTypeLockFree, 1); view_set_context(main_view->view, main_view); - view_set_input_callback(main_view->view, desktop_main_input); + view_set_input_callback(main_view->view, desktop_main_input_callback); main_view->poweroff_timer = xTimerCreate( NULL, diff --git a/applications/services/desktop/views/desktop_view_main.h b/applications/services/desktop/views/desktop_view_main.h index 329d9548682..b5492b1be98 100644 --- a/applications/services/desktop/views/desktop_view_main.h +++ b/applications/services/desktop/views/desktop_view_main.h @@ -13,5 +13,6 @@ void desktop_main_set_callback( void* context); View* desktop_main_get_view(DesktopMainView* main_view); +void desktop_main_set_dummy_mode_state(DesktopMainView* main_view, bool dummy_mode); DesktopMainView* desktop_main_alloc(); void desktop_main_free(DesktopMainView* main_view); diff --git a/applications/settings/desktop_settings/desktop_settings_app.c b/applications/settings/desktop_settings/desktop_settings_app.c index 2b9593590f7..a2ed8b7240b 100644 --- a/applications/settings/desktop_settings/desktop_settings_app.c +++ b/applications/settings/desktop_settings/desktop_settings_app.c @@ -89,7 +89,7 @@ void desktop_settings_app_free(DesktopSettingsApp* app) { extern int32_t desktop_settings_app(void* p) { DesktopSettingsApp* app = desktop_settings_app_alloc(); - LOAD_DESKTOP_SETTINGS(&app->settings); + DESKTOP_SETTINGS_LOAD(&app->settings); if(p && (strcmp(p, DESKTOP_SETTINGS_RUN_PIN_SETUP_ARG) == 0)) { scene_manager_next_scene(app->scene_manager, DesktopSettingsAppScenePinSetupHowto); } else { diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c index 0ec18af7ac6..d8c579b3e22 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c @@ -56,6 +56,6 @@ bool desktop_settings_scene_favorite_on_event(void* context, SceneManagerEvent e void desktop_settings_scene_favorite_on_exit(void* context) { DesktopSettingsApp* app = context; - SAVE_DESKTOP_SETTINGS(&app->settings); + DESKTOP_SETTINGS_SAVE(&app->settings); submenu_reset(app->submenu); } diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_auth.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_auth.c index ce191487d55..5fed235cecb 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_auth.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_auth.c @@ -33,7 +33,7 @@ static void pin_auth_back_callback(void* context) { void desktop_settings_scene_pin_auth_on_enter(void* context) { DesktopSettingsApp* app = context; - LOAD_DESKTOP_SETTINGS(&app->settings); + DESKTOP_SETTINGS_LOAD(&app->settings); furi_assert(app->settings.pin_code.length > 0); desktop_view_pin_input_set_context(app->pin_input_view, app); diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_disable.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_disable.c index dcbba5df0bc..7fbcc325219 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_disable.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_disable.c @@ -20,7 +20,7 @@ void desktop_settings_scene_pin_disable_on_enter(void* context) { DesktopSettingsApp* app = context; app->settings.pin_code.length = 0; memset(app->settings.pin_code.data, '0', sizeof(app->settings.pin_code.data)); - SAVE_DESKTOP_SETTINGS(&app->settings); + DESKTOP_SETTINGS_SAVE(&app->settings); popup_set_context(app->popup, app); popup_set_callback(app->popup, pin_disable_back_callback); diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup_done.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup_done.c index b2d07e99fa4..f6ceb96de99 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup_done.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup_done.c @@ -24,7 +24,7 @@ void desktop_settings_scene_pin_setup_done_on_enter(void* context) { DesktopSettingsApp* app = context; app->settings.pin_code = app->pincode_buffer; - SAVE_DESKTOP_SETTINGS(&app->settings); + DESKTOP_SETTINGS_SAVE(&app->settings); NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); notification_message(notification, &sequence_single_vibro); furi_record_close(RECORD_NOTIFICATION); diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_start.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_start.c index 8e649c6951d..8530a1a7db3 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_start.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_start.c @@ -96,5 +96,5 @@ bool desktop_settings_scene_start_on_event(void* context, SceneManagerEvent even void desktop_settings_scene_start_on_exit(void* context) { DesktopSettingsApp* app = context; variable_item_list_reset(app->variable_item_list); - SAVE_DESKTOP_SETTINGS(&app->settings); + DESKTOP_SETTINGS_SAVE(&app->settings); } diff --git a/assets/icons/StatusBar/GameMode_11x8.png b/assets/icons/StatusBar/GameMode_11x8.png new file mode 100644 index 0000000000000000000000000000000000000000..49f2e25bf32025b68f659fb354abe59d61ba2235 GIT binary patch literal 3610 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 literal 0 HcmV?d00001 diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 906a7b8b678..1f0490344f9 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,1.4,, +Version,+,1.5,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -2603,6 +2603,7 @@ Variable,+,I_FaceCharging_29x14,const Icon, Variable,+,I_FaceConfused_29x14,const Icon, Variable,+,I_FaceNopower_29x14,const Icon, Variable,+,I_FaceNormal_29x14,const Icon, +Variable,+,I_GameMode_11x8,const Icon, Variable,+,I_Health_16x16,const Icon, Variable,+,I_InfraredArrowDown_4x8,const Icon, Variable,+,I_InfraredArrowUp_4x8,const Icon, From fb476c29e6c042eaafbc0d796802a0ebffe797b8 Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Mon, 19 Sep 2022 16:21:40 +0300 Subject: [PATCH 063/824] RFID: fix read info screen (#1723) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * RFID: fix read info screen * Fix line break for long data string * Protocol data redecoding before write Co-authored-by: SG Co-authored-by: あく --- .../main/lfrfid/scenes/lfrfid_scene_read_success.c | 11 +++++------ lib/lfrfid/protocols/protocol_awid.c | 4 ++++ lib/lfrfid/protocols/protocol_em4100.c | 8 ++++++++ lib/lfrfid/protocols/protocol_fdx_a.c | 4 ++++ lib/lfrfid/protocols/protocol_fdx_b.c | 4 ++++ lib/lfrfid/protocols/protocol_gallagher.c | 4 ++++ lib/lfrfid/protocols/protocol_h10301.c | 4 ++++ lib/lfrfid/protocols/protocol_hid_ex_generic.c | 4 ++++ lib/lfrfid/protocols/protocol_hid_generic.c | 4 ++++ lib/lfrfid/protocols/protocol_io_prox_xsf.c | 4 ++++ lib/lfrfid/protocols/protocol_jablotron.c | 4 ++++ lib/lfrfid/protocols/protocol_pac_stanley.c | 4 ++++ lib/lfrfid/protocols/protocol_paradox.c | 4 ++++ lib/lfrfid/protocols/protocol_pyramid.c | 4 ++++ lib/lfrfid/protocols/protocol_viking.c | 4 ++++ 15 files changed, 65 insertions(+), 6 deletions(-) diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_read_success.c b/applications/main/lfrfid/scenes/lfrfid_scene_read_success.c index 6761dcfe623..5ae6f0f1ab7 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_read_success.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_read_success.c @@ -24,14 +24,13 @@ void lfrfid_scene_read_success_on_enter(void* context) { uint8_t* data = (uint8_t*)malloc(size); protocol_dict_get_data(app->dict, app->protocol_id, data, size); for(uint8_t i = 0; i < size; i++) { - if(i != 0) { - string_cat_printf(tmp_string, " "); - } - if(i >= 9) { - string_cat_printf(tmp_string, "..."); + string_cat_printf(tmp_string, ".."); break; } else { + if(i != 0) { + string_cat_printf(tmp_string, " "); + } string_cat_printf(tmp_string, "%02X", data[i]); } } @@ -43,7 +42,7 @@ void lfrfid_scene_read_success_on_enter(void* context) { string_cat_printf(tmp_string, "\r\n%s", string_get_cstr(render_data)); string_clear(render_data); - widget_add_string_element( + widget_add_string_multiline_element( widget, 0, 16, AlignLeft, AlignTop, FontSecondary, string_get_cstr(tmp_string)); notification_message_block(app->notifications, &sequence_set_green_255); diff --git a/lib/lfrfid/protocols/protocol_awid.c b/lib/lfrfid/protocols/protocol_awid.c index 97c07d7b21d..7131d30dc8d 100644 --- a/lib/lfrfid/protocols/protocol_awid.c +++ b/lib/lfrfid/protocols/protocol_awid.c @@ -205,6 +205,10 @@ bool protocol_awid_write_data(ProtocolAwid* protocol, void* data) { LFRFIDWriteRequest* request = (LFRFIDWriteRequest*)data; bool result = false; + // Correct protocol data by redecoding + protocol_awid_encode(protocol->data, (uint8_t*)protocol->encoded_data); + protocol_awid_decode(protocol->encoded_data, protocol->data); + protocol_awid_encode(protocol->data, (uint8_t*)protocol->encoded_data); if(request->write_type == LFRFIDWriteTypeT5577) { diff --git a/lib/lfrfid/protocols/protocol_em4100.c b/lib/lfrfid/protocols/protocol_em4100.c index 17f57421a0e..6959f753b25 100644 --- a/lib/lfrfid/protocols/protocol_em4100.c +++ b/lib/lfrfid/protocols/protocol_em4100.c @@ -248,6 +248,14 @@ bool protocol_em4100_write_data(ProtocolEM4100* protocol, void* data) { LFRFIDWriteRequest* request = (LFRFIDWriteRequest*)data; bool result = false; + // Correct protocol data by redecoding + protocol_em4100_encoder_start(protocol); + em4100_decode( + (uint8_t*)&protocol->encoded_data, + sizeof(EM4100DecodedData), + protocol->data, + EM4100_DECODED_DATA_SIZE); + protocol_em4100_encoder_start(protocol); if(request->write_type == LFRFIDWriteTypeT5577) { diff --git a/lib/lfrfid/protocols/protocol_fdx_a.c b/lib/lfrfid/protocols/protocol_fdx_a.c index 23f9e2857f8..554b9071e3b 100644 --- a/lib/lfrfid/protocols/protocol_fdx_a.c +++ b/lib/lfrfid/protocols/protocol_fdx_a.c @@ -178,6 +178,10 @@ bool protocol_fdx_a_write_data(ProtocolFDXA* protocol, void* data) { LFRFIDWriteRequest* request = (LFRFIDWriteRequest*)data; bool result = false; + // Correct protocol data by redecoding + protocol_fdx_a_encoder_start(protocol); + protocol_fdx_a_decode(protocol->encoded_data, protocol->data); + protocol_fdx_a_encoder_start(protocol); if(request->write_type == LFRFIDWriteTypeT5577) { diff --git a/lib/lfrfid/protocols/protocol_fdx_b.c b/lib/lfrfid/protocols/protocol_fdx_b.c index f68a884e853..f42b4ed9343 100644 --- a/lib/lfrfid/protocols/protocol_fdx_b.c +++ b/lib/lfrfid/protocols/protocol_fdx_b.c @@ -334,6 +334,10 @@ bool protocol_fdx_b_write_data(ProtocolFDXB* protocol, void* data) { LFRFIDWriteRequest* request = (LFRFIDWriteRequest*)data; bool result = false; + // Correct protocol data by redecoding + protocol_fdx_b_encoder_start(protocol); + protocol_fdx_b_decode(protocol); + protocol_fdx_b_encoder_start(protocol); if(request->write_type == LFRFIDWriteTypeT5577) { diff --git a/lib/lfrfid/protocols/protocol_gallagher.c b/lib/lfrfid/protocols/protocol_gallagher.c index c205ab8a502..5d8245301db 100644 --- a/lib/lfrfid/protocols/protocol_gallagher.c +++ b/lib/lfrfid/protocols/protocol_gallagher.c @@ -249,6 +249,10 @@ bool protocol_gallagher_write_data(ProtocolGallagher* protocol, void* data) { LFRFIDWriteRequest* request = (LFRFIDWriteRequest*)data; bool result = false; + // Correct protocol data by redecoding + protocol_gallagher_encoder_start(protocol); + protocol_gallagher_decode(protocol); + protocol_gallagher_encoder_start(protocol); if(request->write_type == LFRFIDWriteTypeT5577) { diff --git a/lib/lfrfid/protocols/protocol_h10301.c b/lib/lfrfid/protocols/protocol_h10301.c index f30f75facfa..6c50c667a3d 100644 --- a/lib/lfrfid/protocols/protocol_h10301.c +++ b/lib/lfrfid/protocols/protocol_h10301.c @@ -337,6 +337,10 @@ bool protocol_h10301_write_data(ProtocolH10301* protocol, void* data) { LFRFIDWriteRequest* request = (LFRFIDWriteRequest*)data; bool result = false; + // Correct protocol data by redecoding + protocol_h10301_encoder_start(protocol); + protocol_h10301_decode(protocol->encoded_data, protocol->data); + protocol_h10301_encoder_start(protocol); if(request->write_type == LFRFIDWriteTypeT5577) { diff --git a/lib/lfrfid/protocols/protocol_hid_ex_generic.c b/lib/lfrfid/protocols/protocol_hid_ex_generic.c index e0a85266184..17b75528c4f 100644 --- a/lib/lfrfid/protocols/protocol_hid_ex_generic.c +++ b/lib/lfrfid/protocols/protocol_hid_ex_generic.c @@ -171,6 +171,10 @@ bool protocol_hid_ex_generic_write_data(ProtocolHIDEx* protocol, void* data) { LFRFIDWriteRequest* request = (LFRFIDWriteRequest*)data; bool result = false; + // Correct protocol data by redecoding + protocol_hid_ex_generic_encoder_start(protocol); + protocol_hid_ex_generic_decode(protocol->encoded_data, protocol->data); + protocol_hid_ex_generic_encoder_start(protocol); if(request->write_type == LFRFIDWriteTypeT5577) { diff --git a/lib/lfrfid/protocols/protocol_hid_generic.c b/lib/lfrfid/protocols/protocol_hid_generic.c index 2516d681058..da5f5b7c8ac 100644 --- a/lib/lfrfid/protocols/protocol_hid_generic.c +++ b/lib/lfrfid/protocols/protocol_hid_generic.c @@ -203,6 +203,10 @@ bool protocol_hid_generic_write_data(ProtocolHID* protocol, void* data) { LFRFIDWriteRequest* request = (LFRFIDWriteRequest*)data; bool result = false; + // Correct protocol data by redecoding + protocol_hid_generic_encoder_start(protocol); + protocol_hid_generic_decode(protocol->encoded_data, protocol->data); + protocol_hid_generic_encoder_start(protocol); if(request->write_type == LFRFIDWriteTypeT5577) { diff --git a/lib/lfrfid/protocols/protocol_io_prox_xsf.c b/lib/lfrfid/protocols/protocol_io_prox_xsf.c index 66b1610bf29..f53eac686bd 100644 --- a/lib/lfrfid/protocols/protocol_io_prox_xsf.c +++ b/lib/lfrfid/protocols/protocol_io_prox_xsf.c @@ -259,6 +259,10 @@ bool protocol_io_prox_xsf_write_data(ProtocolIOProxXSF* protocol, void* data) { LFRFIDWriteRequest* request = (LFRFIDWriteRequest*)data; bool result = false; + // Correct protocol data by redecoding + protocol_io_prox_xsf_encode(protocol->data, protocol->encoded_data); + protocol_io_prox_xsf_decode(protocol->encoded_data, protocol->data); + protocol_io_prox_xsf_encode(protocol->data, protocol->encoded_data); if(request->write_type == LFRFIDWriteTypeT5577) { diff --git a/lib/lfrfid/protocols/protocol_jablotron.c b/lib/lfrfid/protocols/protocol_jablotron.c index e00b1e59c96..89a59a2d7a0 100644 --- a/lib/lfrfid/protocols/protocol_jablotron.c +++ b/lib/lfrfid/protocols/protocol_jablotron.c @@ -169,6 +169,10 @@ bool protocol_jablotron_write_data(ProtocolJablotron* protocol, void* data) { LFRFIDWriteRequest* request = (LFRFIDWriteRequest*)data; bool result = false; + // Correct protocol data by redecoding + protocol_jablotron_encoder_start(protocol); + protocol_jablotron_decode(protocol); + protocol_jablotron_encoder_start(protocol); if(request->write_type == LFRFIDWriteTypeT5577) { diff --git a/lib/lfrfid/protocols/protocol_pac_stanley.c b/lib/lfrfid/protocols/protocol_pac_stanley.c index 7ab16a103e8..b5e1ebddc1b 100644 --- a/lib/lfrfid/protocols/protocol_pac_stanley.c +++ b/lib/lfrfid/protocols/protocol_pac_stanley.c @@ -182,6 +182,10 @@ bool protocol_pac_stanley_write_data(ProtocolPACStanley* protocol, void* data) { LFRFIDWriteRequest* request = (LFRFIDWriteRequest*)data; bool result = false; + // Correct protocol data by redecoding + protocol_pac_stanley_encoder_start(protocol); + protocol_pac_stanley_decode(protocol); + protocol_pac_stanley_encoder_start(protocol); if(request->write_type == LFRFIDWriteTypeT5577) { diff --git a/lib/lfrfid/protocols/protocol_paradox.c b/lib/lfrfid/protocols/protocol_paradox.c index be627600bcf..b0b3e71d65f 100644 --- a/lib/lfrfid/protocols/protocol_paradox.c +++ b/lib/lfrfid/protocols/protocol_paradox.c @@ -162,6 +162,10 @@ bool protocol_paradox_write_data(ProtocolParadox* protocol, void* data) { LFRFIDWriteRequest* request = (LFRFIDWriteRequest*)data; bool result = false; + // Correct protocol data by redecoding + protocol_paradox_encode(protocol->data, (uint8_t*)protocol->encoded_data); + protocol_paradox_decode(protocol->encoded_data, protocol->data); + protocol_paradox_encode(protocol->data, (uint8_t*)protocol->encoded_data); if(request->write_type == LFRFIDWriteTypeT5577) { diff --git a/lib/lfrfid/protocols/protocol_pyramid.c b/lib/lfrfid/protocols/protocol_pyramid.c index a0404b48568..1bba9871832 100644 --- a/lib/lfrfid/protocols/protocol_pyramid.c +++ b/lib/lfrfid/protocols/protocol_pyramid.c @@ -219,6 +219,10 @@ bool protocol_pyramid_write_data(ProtocolPyramid* protocol, void* data) { LFRFIDWriteRequest* request = (LFRFIDWriteRequest*)data; bool result = false; + // Correct protocol data by redecoding + protocol_pyramid_encode(protocol); + protocol_pyramid_decode(protocol); + protocol_pyramid_encoder_start(protocol); if(request->write_type == LFRFIDWriteTypeT5577) { diff --git a/lib/lfrfid/protocols/protocol_viking.c b/lib/lfrfid/protocols/protocol_viking.c index 6119b734831..f252cc2c355 100644 --- a/lib/lfrfid/protocols/protocol_viking.c +++ b/lib/lfrfid/protocols/protocol_viking.c @@ -157,6 +157,10 @@ bool protocol_viking_write_data(ProtocolViking* protocol, void* data) { LFRFIDWriteRequest* request = (LFRFIDWriteRequest*)data; bool result = false; + // Correct protocol data by redecoding + protocol_viking_encoder_start(protocol); + protocol_viking_decode(protocol); + protocol_viking_encoder_start(protocol); if(request->write_type == LFRFIDWriteTypeT5577) { From c7cd5721edc986c2e80cdbe85af86fbdf613d19b Mon Sep 17 00:00:00 2001 From: Patrick Cunningham Date: Mon, 19 Sep 2022 06:37:12 -0700 Subject: [PATCH 064/824] Picopass: detect and show SE / SIO (#1701) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * detect and show SE / SIO * fix fault * remove bad read check Co-authored-by: あく --- .../plugins/picopass/picopass_device.c | 6 +- .../plugins/picopass/picopass_device.h | 1 + .../plugins/picopass/picopass_worker.c | 98 +++++++++++++++---- .../plugins/picopass/picopass_worker.h | 1 + .../scenes/picopass_scene_read_card_success.c | 32 +++--- 5 files changed, 103 insertions(+), 35 deletions(-) diff --git a/applications/plugins/picopass/picopass_device.c b/applications/plugins/picopass/picopass_device.c index 04d35d19bf9..e7f3e0bedc1 100644 --- a/applications/plugins/picopass/picopass_device.c +++ b/applications/plugins/picopass/picopass_device.c @@ -309,10 +309,6 @@ ReturnCode picopass_device_decrypt(uint8_t* enc_data, uint8_t* dec_data) { ReturnCode picopass_device_parse_credential(PicopassBlock* AA1, PicopassPacs* pacs) { ReturnCode err; - // Thank you proxmark! - pacs->legacy = (memcmp(AA1[5].data, "\xff\xff\xff\xff\xff\xff\xff\xff", 8) == 0); - pacs->se_enabled = (memcmp(AA1[5].data, "\xff\xff\xff\x00\x06\xff\xff\xff", 8) == 0); - pacs->biometrics = AA1[6].data[4]; pacs->pin_length = AA1[6].data[6] & 0x0F; pacs->encryption = AA1[6].data[7]; @@ -347,6 +343,8 @@ ReturnCode picopass_device_parse_credential(PicopassBlock* AA1, PicopassPacs* pa FURI_LOG_D(TAG, "Unknown encryption"); } + pacs->sio = (AA1[10].data[0] == 0x30); // rough check + return ERR_NONE; } diff --git a/applications/plugins/picopass/picopass_device.h b/applications/plugins/picopass/picopass_device.h index 931b86306b2..745b64bd5ec 100644 --- a/applications/plugins/picopass/picopass_device.h +++ b/applications/plugins/picopass/picopass_device.h @@ -47,6 +47,7 @@ typedef struct { typedef struct { bool legacy; bool se_enabled; + bool sio; bool biometrics; uint8_t pin_length; PicopassEncryption encryption; diff --git a/applications/plugins/picopass/picopass_worker.c b/applications/plugins/picopass/picopass_worker.c index e7331524822..532effd9ad9 100644 --- a/applications/plugins/picopass/picopass_worker.c +++ b/applications/plugins/picopass/picopass_worker.c @@ -112,18 +112,12 @@ ReturnCode picopass_detect_card(int timeout) { return ERR_NONE; } -ReturnCode picopass_read_card(PicopassBlock* AA1) { +ReturnCode picopass_read_preauth(PicopassBlock* AA1) { rfalPicoPassIdentifyRes idRes; rfalPicoPassSelectRes selRes; - rfalPicoPassReadCheckRes rcRes; - rfalPicoPassCheckRes chkRes; ReturnCode err; - uint8_t div_key[8] = {0}; - uint8_t mac[4] = {0}; - uint8_t ccnr[12] = {0}; - err = rfalPicoPassPollerIdentify(&idRes); if(err != ERR_NONE) { FURI_LOG_E(TAG, "rfalPicoPassPollerIdentify error %d", err); @@ -136,6 +130,62 @@ ReturnCode picopass_read_card(PicopassBlock* AA1) { return err; } + memcpy(AA1[PICOPASS_CSN_BLOCK_INDEX].data, selRes.CSN, sizeof(selRes.CSN)); + FURI_LOG_D( + TAG, + "csn %02x%02x%02x%02x%02x%02x%02x%02x", + AA1[PICOPASS_CSN_BLOCK_INDEX].data[0], + AA1[PICOPASS_CSN_BLOCK_INDEX].data[1], + AA1[PICOPASS_CSN_BLOCK_INDEX].data[2], + AA1[PICOPASS_CSN_BLOCK_INDEX].data[3], + AA1[PICOPASS_CSN_BLOCK_INDEX].data[4], + AA1[PICOPASS_CSN_BLOCK_INDEX].data[5], + AA1[PICOPASS_CSN_BLOCK_INDEX].data[6], + AA1[PICOPASS_CSN_BLOCK_INDEX].data[7]); + + rfalPicoPassReadBlockRes cfg = {0}; + err = rfalPicoPassPollerReadBlock(PICOPASS_CONFIG_BLOCK_INDEX, &cfg); + memcpy(AA1[PICOPASS_CONFIG_BLOCK_INDEX].data, cfg.data, sizeof(cfg.data)); + FURI_LOG_D( + TAG, + "config %02x%02x%02x%02x%02x%02x%02x%02x", + AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[0], + AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[1], + AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[2], + AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[3], + AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[4], + AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[5], + AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[6], + AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[7]); + + rfalPicoPassReadBlockRes aia; + err = rfalPicoPassPollerReadBlock(PICOPASS_AIA_BLOCK_INDEX, &aia); + memcpy(AA1[PICOPASS_AIA_BLOCK_INDEX].data, aia.data, sizeof(aia.data)); + FURI_LOG_D( + TAG, + "aia %02x%02x%02x%02x%02x%02x%02x%02x", + AA1[PICOPASS_AIA_BLOCK_INDEX].data[0], + AA1[PICOPASS_AIA_BLOCK_INDEX].data[1], + AA1[PICOPASS_AIA_BLOCK_INDEX].data[2], + AA1[PICOPASS_AIA_BLOCK_INDEX].data[3], + AA1[PICOPASS_AIA_BLOCK_INDEX].data[4], + AA1[PICOPASS_AIA_BLOCK_INDEX].data[5], + AA1[PICOPASS_AIA_BLOCK_INDEX].data[6], + AA1[PICOPASS_AIA_BLOCK_INDEX].data[7]); + + return ERR_NONE; +} + +ReturnCode picopass_read_card(PicopassBlock* AA1) { + rfalPicoPassReadCheckRes rcRes; + rfalPicoPassCheckRes chkRes; + + ReturnCode err; + + uint8_t div_key[8] = {0}; + uint8_t mac[4] = {0}; + uint8_t ccnr[12] = {0}; + err = rfalPicoPassPollerReadCheck(&rcRes); if(err != ERR_NONE) { FURI_LOG_E(TAG, "rfalPicoPassPollerReadCheck error %d", err); @@ -143,7 +193,7 @@ ReturnCode picopass_read_card(PicopassBlock* AA1) { } memcpy(ccnr, rcRes.CCNR, sizeof(rcRes.CCNR)); // last 4 bytes left 0 - loclass_diversifyKey(selRes.CSN, picopass_iclass_key, div_key); + loclass_diversifyKey(AA1[PICOPASS_CSN_BLOCK_INDEX].data, picopass_iclass_key, div_key); loclass_opt_doReaderMAC(ccnr, div_key, mac); err = rfalPicoPassPollerCheck(mac, &chkRes); @@ -152,18 +202,11 @@ ReturnCode picopass_read_card(PicopassBlock* AA1) { return err; } - rfalPicoPassReadBlockRes csn; - err = rfalPicoPassPollerReadBlock(PICOPASS_CSN_BLOCK_INDEX, &csn); - memcpy(AA1[PICOPASS_CSN_BLOCK_INDEX].data, csn.data, sizeof(csn.data)); - - rfalPicoPassReadBlockRes cfg; - err = rfalPicoPassPollerReadBlock(PICOPASS_CONFIG_BLOCK_INDEX, &cfg); - memcpy(AA1[PICOPASS_CONFIG_BLOCK_INDEX].data, cfg.data, sizeof(cfg.data)); - - size_t app_limit = cfg.data[0] < PICOPASS_MAX_APP_LIMIT ? cfg.data[0] : PICOPASS_MAX_APP_LIMIT; + size_t app_limit = AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[0] < PICOPASS_MAX_APP_LIMIT ? + AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[0] : + PICOPASS_MAX_APP_LIMIT; for(size_t i = 2; i < app_limit; i++) { - FURI_LOG_D(TAG, "rfalPicoPassPollerReadBlock block %d", i); rfalPicoPassReadBlockRes block; err = rfalPicoPassPollerReadBlock(i, &block); if(err != ERR_NONE) { @@ -287,11 +330,30 @@ void picopass_worker_detect(PicopassWorker* picopass_worker) { PicopassPacs* pacs = &dev_data->pacs; ReturnCode err; + // reset device data + for(size_t i = 0; i < PICOPASS_MAX_APP_LIMIT; i++) { + memset(AA1[i].data, 0, sizeof(AA1[i].data)); + } + memset(pacs, 0, sizeof(PicopassPacs)); + PicopassWorkerEvent nextState = PicopassWorkerEventSuccess; while(picopass_worker->state == PicopassWorkerStateDetect) { if(picopass_detect_card(1000) == ERR_NONE) { // Process first found device + err = picopass_read_preauth(AA1); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "picopass_read_preauth error %d", err); + nextState = PicopassWorkerEventFail; + } + + // Thank you proxmark! + pacs->legacy = (memcmp(AA1[5].data, "\xff\xff\xff\xff\xff\xff\xff\xff", 8) == 0); + pacs->se_enabled = (memcmp(AA1[5].data, "\xff\xff\xff\x00\x06\xff\xff\xff", 8) == 0); + if(pacs->se_enabled) { + FURI_LOG_D(TAG, "SE enabled"); + } + err = picopass_read_card(AA1); if(err != ERR_NONE) { FURI_LOG_E(TAG, "picopass_read_card error %d", err); diff --git a/applications/plugins/picopass/picopass_worker.h b/applications/plugins/picopass/picopass_worker.h index 6ad70905835..29a890a188b 100644 --- a/applications/plugins/picopass/picopass_worker.h +++ b/applications/plugins/picopass/picopass_worker.h @@ -24,6 +24,7 @@ typedef enum { PicopassWorkerEventSuccess, PicopassWorkerEventFail, PicopassWorkerEventNoCardDetected, + PicopassWorkerEventSeEnabled, PicopassWorkerEventStartReading, } PicopassWorkerEvent; diff --git a/applications/plugins/picopass/scenes/picopass_scene_read_card_success.c b/applications/plugins/picopass/scenes/picopass_scene_read_card_success.c index c281b32ca27..785f3a7dde5 100644 --- a/applications/plugins/picopass/scenes/picopass_scene_read_card_success.c +++ b/applications/plugins/picopass/scenes/picopass_scene_read_card_success.c @@ -17,8 +17,10 @@ void picopass_scene_read_card_success_on_enter(void* context) { Picopass* picopass = context; string_t credential_str; string_t wiegand_str; + string_t sio_str; string_init(credential_str); string_init(wiegand_str); + string_init(sio_str); DOLPHIN_DEED(DolphinDeedNfcReadSuccess); @@ -32,6 +34,10 @@ void picopass_scene_read_card_success_on_enter(void* context) { if(pacs->record.bitLength == 0) { string_cat_printf(wiegand_str, "Read Failed"); + if(pacs->se_enabled) { + string_cat_printf(credential_str, "SE enabled"); + } + widget_add_button_element( widget, GuiButtonTypeLeft, @@ -39,8 +45,6 @@ void picopass_scene_read_card_success_on_enter(void* context) { picopass_scene_read_card_success_widget_callback, picopass); - widget_add_string_element( - widget, 64, 12, AlignCenter, AlignCenter, FontPrimary, string_get_cstr(wiegand_str)); } else { size_t bytesLength = 1 + pacs->record.bitLength / 8; string_set_str(credential_str, ""); @@ -55,6 +59,10 @@ void picopass_scene_read_card_success_on_enter(void* context) { string_cat_printf(wiegand_str, "%d bits", pacs->record.bitLength); } + if(pacs->sio) { + string_cat_printf(sio_str, "+SIO"); + } + widget_add_button_element( widget, GuiButtonTypeLeft, @@ -68,20 +76,18 @@ void picopass_scene_read_card_success_on_enter(void* context) { "More", picopass_scene_read_card_success_widget_callback, picopass); - - widget_add_string_element( - widget, 64, 12, AlignCenter, AlignCenter, FontPrimary, string_get_cstr(wiegand_str)); - widget_add_string_element( - widget, - 64, - 32, - AlignCenter, - AlignCenter, - FontSecondary, - string_get_cstr(credential_str)); } + + widget_add_string_element( + widget, 64, 12, AlignCenter, AlignCenter, FontPrimary, string_get_cstr(wiegand_str)); + widget_add_string_element( + widget, 64, 32, AlignCenter, AlignCenter, FontSecondary, string_get_cstr(credential_str)); + widget_add_string_element( + widget, 64, 42, AlignCenter, AlignCenter, FontSecondary, string_get_cstr(sio_str)); + string_clear(credential_str); string_clear(wiegand_str); + string_clear(sio_str); view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewWidget); } From d003db04041ef0ab51e7f8c5dc2b3d0d1156b6c7 Mon Sep 17 00:00:00 2001 From: Max Lapan Date: Mon, 19 Sep 2022 16:24:24 +0200 Subject: [PATCH 065/824] SubGhz: Oregon v2.1 decoder (#1678) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Oregon v2.1 decoder * Refactor FSM to switch * Refactor headers * Format strings * Unit tests of oregon2 * Cleanups * Add oregon2 raw data to random_test_raw.sub * Adjust count of packets detected on random test * Format sources Co-authored-by: あく --- .../debug/unit_tests/subghz/subghz_test.c | 10 +- assets/unit_tests/subghz/oregon2_raw.sub | 20 ++ assets/unit_tests/subghz/test_random_raw.sub | 15 + lib/subghz/protocols/oregon2.c | 324 ++++++++++++++++++ lib/subghz/protocols/oregon2.h | 5 + lib/subghz/protocols/registry.c | 4 +- lib/subghz/protocols/registry.h | 1 + 7 files changed, 375 insertions(+), 4 deletions(-) create mode 100644 assets/unit_tests/subghz/oregon2_raw.sub create mode 100644 lib/subghz/protocols/oregon2.c create mode 100644 lib/subghz/protocols/oregon2.h diff --git a/applications/debug/unit_tests/subghz/subghz_test.c b/applications/debug/unit_tests/subghz/subghz_test.c index df269ed046f..36f5b94c2ac 100644 --- a/applications/debug/unit_tests/subghz/subghz_test.c +++ b/applications/debug/unit_tests/subghz/subghz_test.c @@ -13,7 +13,7 @@ #define CAME_ATOMO_DIR_NAME EXT_PATH("subghz/assets/came_atomo") #define NICE_FLOR_S_DIR_NAME EXT_PATH("subghz/assets/nice_flor_s") #define TEST_RANDOM_DIR_NAME EXT_PATH("unit_tests/subghz/test_random_raw.sub") -#define TEST_RANDOM_COUNT_PARSE 232 +#define TEST_RANDOM_COUNT_PARSE 233 #define TEST_TIMEOUT 10000 static SubGhzEnvironment* environment_handler; @@ -434,6 +434,13 @@ MU_TEST(subghz_decoder_clemsa_test) { "Test decoder " SUBGHZ_PROTOCOL_CLEMSA_NAME " error\r\n"); } +MU_TEST(subghz_decoder_oregon2_test) { + mu_assert( + subghz_decoder_test( + EXT_PATH("unit_tests/subghz/oregon2_raw.sub"), SUBGHZ_PROTOCOL_OREGON2_NAME), + "Test decoder " SUBGHZ_PROTOCOL_OREGON2_NAME " error\r\n"); +} + //test encoders MU_TEST(subghz_encoder_princeton_test) { mu_assert( @@ -595,6 +602,7 @@ MU_TEST_SUITE(subghz) { MU_RUN_TEST(subghz_decoder_magellen_test); MU_RUN_TEST(subghz_decoder_intertechno_v3_test); MU_RUN_TEST(subghz_decoder_clemsa_test); + MU_RUN_TEST(subghz_decoder_oregon2_test); MU_RUN_TEST(subghz_encoder_princeton_test); MU_RUN_TEST(subghz_encoder_came_test); diff --git a/assets/unit_tests/subghz/oregon2_raw.sub b/assets/unit_tests/subghz/oregon2_raw.sub new file mode 100644 index 00000000000..549a60db348 --- /dev/null +++ b/assets/unit_tests/subghz/oregon2_raw.sub @@ -0,0 +1,20 @@ +Filetype: Flipper SubGhz RAW File +Version: 1 +Frequency: 433920000 +Preset: FuriHalSubGhzPresetOok270Async +Protocol: RAW +RAW_Data: 889 -130 325 -64 457 -560 165 -68 199 -170 67 -66 265 -132 133 -666 67 -166 431 -66 201 -98 297 -100 595 -66 199 -134 65 -100 795 -132 99 -168 501 -200 331 -132 265 -102 265 -134 423 -98 521 -226 65 -166 431 -134 99 -100 133 -464 195 -326 623 -100 673 -98 321 -200 65 -136 369 -166 65 -68 97 -166 165 -334 265 -102 231 -166 101 -170 65 -170 265 -136 931 -100 133 -134 563 -66 333 -100 427 -66 163 -390 231 -66 193 -130 461 -166 557 -100 99 -198 263 -100 197 -294 231 -232 299 -134 199 -170 267 -134 631 -98 235 -100 499 -68 463 -100 65 -134 335 -170 273 -134 297 -100 67 -66 197 -166 67 -134 301 -168 537 -470 99 -134 433 -132 199 -192 261 -100 523 -164 459 -132 259 -332 359 -64 227 -96 131 -132 687 -132 363 -136 329 -434 99 -334 133 -100 401 -132 233 -700 233 -170 337 -66 371 -68 233 -202 531 -266 731 -66 465 -100 167 -100 133 -232 335 -166 239 -102 367 -232 231 -100 167 -134 201 -136 301 -168 199 -300 231 -98 237 -134 233 -102 329 -132 261 -134 199 -66 265 -136 99 -170 167 -134 199 -166 167 -136 367 -298 197 -200 99 -166 469 -136 439 -66 303 -134 295 -100 433 -134 899 -266 363 -132 197 -160 555 -324 129 -96 97 -128 257 -132 97 -394 257 -98 195 -166 459 -332 395 -132 633 -134 301 -100 131 -332 169 -168 395 -166 263 -540 783 -100 287 -130 295 -96 225 -296 133 -98 99 -100 461 -164 545 -130 99 -66 301 -68 265 -100 235 -134 235 -70 333 -102 497 -66 233 -364 301 -170 103 -66 165 -336 733 -200 133 -100 263 -102 65 -136 465 -200 1035 -198 165 -170 67 -302 631 -100 429 -332 65 -128 129 -130 159 -128 159 -66 161 -96 325 -164 261 -100 197 -162 65 -96 99 -130 65 -102 333 -100 199 -98 389 -330 129 -128 229 -66 425 -366 229 -64 261 -100 227 -96 227 -526 301 -200 97 -66 699 -334 67 -100 399 -198 787 -98 297 -134 429 -100 3245 -64 527 -98 131 -526 633 -68 133 -302 1459 -164 971 -102 237 -136 1439 -266 1131 -66 599 -200 303 -332 325 -130 389 -166 371 -66 333 -102 65 -100 233 -234 327 -266 233 -166 297 -100 225 -130 163 -336 99 -596 199 -330 131 -66 331 -338 263 -358 197 -168 877 -66 227 -96 63 -130 263 -162 225 -290 197 -198 357 -132 297 -262 165 -456 227 -98 399 -296 95 -132 99 -98 457 -200 199 -168 535 -100 567 -134 327 -130 193 -130 683 -102 101 -132 233 -170 943 -166 827 -66 267 -102 503 -68 1325 -164 +RAW_Data: 1607 -68 233 -166 1167 -70 531 -134 335 -168 131 -66 299 -402 899 -66 461 -66 457 -98 953 -98 165 -66 293 -230 881 -64 393 -166 589 -66 289 -66 1093 -204 333 -98 2745 -132 2019 -170 925 -68 269 -102 1469 -136 2301 -68 1355 -100 527 -66 975 -68 1445 -98 2397 -100 1733 -66 703 -100 995 -100 135 -136 235 -202 167 -134 2071 -166 339 -170 201 -268 129 -66 465 -66 365 -100 197 -164 129 -98 161 -96 423 -66 675 -66 1543 -136 567 -200 767 -202 65 -100 1401 -66 623 -136 567 -234 67 -236 197 -194 97 -66 263 -66 1827 -392 1893 -98 165 -268 133 -132 231 -162 225 -98 695 -198 563 -100 301 -332 267 -102 341 -66 99 -132 1299 -130 525 -68 161 -96 357 -98 353 -100 131 -100 131 -98 163 -132 323 -100 535 -66 1323 -130 133 -66 235 -134 1497 -132 387 -98 129 -162 2623 -134 163 -68 167 -66 959 -232 495 -68 131 -134 867 -134 865 -66 333 -98 305 -134 231 -98 765 -198 397 -432 165 -66 165 -366 265 -102 541 -100 261 -162 331 -134 457 -66 491 -196 97 -266 193 -262 65 -166 231 -266 497 -360 263 -98 587 -164 259 -98 231 -66 359 -100 267 -102 271 -168 97 -262 63 -66 261 -130 227 -130 295 -164 65 -66 265 -200 597 -134 267 -170 603 -100 97 -466 231 -264 97 -168 99 -66 65 -200 199 -100 267 -404 303 -102 201 -204 235 -134 131 -198 335 -298 327 -130 291 -164 63 -162 295 -262 197 -130 95 -130 195 -96 159 -130 161 -66 231 -100 165 -66 199 -134 363 -66 267 -168 165 -168 167 -100 165 -530 363 -432 99 -232 65 -132 395 -328 229 -98 197 -132 161 -96 191 -292 197 -204 133 -100 399 -166 531 -332 235 -168 99 -66 325 -158 553 -132 129 -226 231 -134 99 -462 129 -64 289 -100 193 -66 355 -164 291 -198 131 -298 197 -198 373 -268 335 -234 427 -68 199 -132 267 -232 131 -66 783 -326 63 -162 161 -130 227 -66 259 -562 233 -464 303 -102 201 -334 301 -134 297 -198 229 -66 127 -166 99 -100 197 -198 571 -66 457 -134 361 -424 131 -328 163 -98 63 -100 505 -102 201 -1094 229 -164 65 -230 789 -236 2505 -166 201 -170 163 -64 1139 -66 927 -100 295 -198 723 -100 365 -66 459 -196 3033 -272 199 -66 499 -202 1319 -232 295 -298 131 -362 97 -164 129 -132 65 -98 197 -130 129 -98 261 -130 97 -98 229 -96 425 -66 227 -166 483 -66 163 -326 567 -68 235 -68 67 -66 167 -66 235 -330 425 -164 63 -66 427 -102 167 -66 669 -132 429 -200 65 -102 133 -100 197 -368 +RAW_Data: 65 -134 2481 -228 65 -130 229 -228 763 -136 603 -166 1619 -98 1763 -102 837 -166 321 -66 951 -130 2067 -66 259 -132 1835 -66 437 -102 701 -66 565 -68 363 -70 1113 -66 1989 -164 257 -128 351 -162 1055 -232 265 -170 309 -200 435 -166 833 -102 2467 -132 595 -66 773 -166 1615 -98 131 -96 485 -64 517 -166 197 -68 1231 -68 403 -100 263 -134 233 -100 503 -100 333 -266 729 -66 199 -100 369 -68 1239 -100 197 -68 299 -170 337 -100 825 -132 163 -66 4205 -64 161 -100 635 -66 907 -66 1017 -166 1709 -100 201 -266 657 -68 463 -166 331 -164 293 -64 259 -162 129 -262 597 -134 701 -136 67 -168 235 -136 303 -170 1417 -66 263 -98 857 -100 659 -166 97 -100 2497 -64 2495 -98 719 -128 227 -130 2217 -164 623 -264 719 -134 329 -98 1371 -100 553 -294 165 -66 1163 -100 329 -196 649 -200 1123 -68 263 -100 593 -266 333 -102 1133 -136 131 -132 603 -200 1819 -66 489 -66 563 -266 1113 -230 165 -66 423 -68 335 -100 101 -100 1073 -132 897 -100 101 -100 499 -134 173 -138 763 -238 371 -130 403 -166 203 -102 271 -136 269 -166 99 -168 263 -96 425 -66 331 -234 133 -400 231 -132 453 -66 459 -164 199 -68 237 -132 163 -198 161 -196 265 -132 65 -64 195 -130 357 -164 663 -68 167 -600 131 -98 133 -304 203 -134 433 -98 261 -130 199 -100 237 -100 229 -326 99 -98 331 -132 99 -294 165 -66 303 -134 99 -232 133 -136 99 -68 267 -198 233 -138 67 -166 367 -100 333 -168 267 -200 369 -266 135 -404 1939 -132 231 -160 161 -64 293 -98 331 -132 339 -104 135 -100 197 -430 263 -202 233 -64 195 -162 129 -64 227 -298 265 -68 697 -66 301 -68 231 -300 131 -368 769 -234 265 -98 195 -324 97 -752 229 -126 355 -98 257 -98 287 -64 427 -132 295 -262 197 -170 369 -102 267 -100 169 -68 201 -102 2551 -136 635 -134 639 -134 99 -132 197 -200 371 -66 731 -132 199 -138 733 -304 433 -68 729 -440 197 -68 99 -102 165 -266 261 -164 491 -296 489 -194 257 -164 133 -134 237 -68 335 -98 227 -130 229 -98 295 -98 231 -202 267 -236 233 -136 331 -130 195 -128 261 -430 261 -162 97 -224 99 -130 193 -96 197 -162 229 -396 97 -98 227 -364 267 -100 99 -100 233 -236 697 -164 227 -196 63 -98 327 -230 325 -66 129 -196 95 -98 195 -130 325 -430 131 -194 129 -454 161 -196 235 -68 433 -134 667 -164 355 -236 101 -98 2143 -134 1827 -198 63 -198 65 -64 2859 -64 619 -66 97 -130 3157 -66 679 -194 1491 -98 +RAW_Data: 951 -64 393 -100 955 -132 4715 -100 131 -66 199 -204 1541 -66 929 -130 1347 -166 665 -132 233 -132 67 -102 433 -100 595 -228 997 -66 505 -68 133 -98 231 -68 571 -134 1371 -232 231 -270 135 -102 97 -66 867 -100 269 -68 967 -100 1649 -66 65 -66 951 -68 65 -202 363 -200 779 -102 1449 -294 419 -130 361 -230 1079 -164 163 -260 893 -102 333 -100 533 -166 467 -100 135 -66 135 -202 369 -100 199 -100 269 -134 301 -166 229 -66 101 -134 199 -134 1293 -64 779 -62 831 -66 1243 -68 267 -102 197 -100 395 -98 455 -64 621 -132 877 -98 199 -100 2101 -134 503 -100 2035 -134 735 -236 475 -136 237 -132 133 -134 1229 -100 133 -66 167 -68 2655 -100 1807 -100 1095 -264 825 -98 163 -66 491 -98 161 -128 953 -100 773 -100 131 -66 67 -134 457 -130 63 -64 389 -98 715 -66 425 -300 97 -100 1515 -66 303 -68 99 -98 721 -64 887 -132 65 -132 165 -66 635 -68 2801 -66 1561 -100 751 -98 129 -64 725 -136 201 -100 333 -204 573 -104 1745 -134 99 -66 129 -64 595 -134 167 -102 337 -134 567 -134 1131 -138 1207 -100 269 -68 135 -100 1143 -134 2139 -68 1701 -162 991 -596 431 -66 99 -132 657 -66 391 -320 357 -260 259 -98 429 -66 163 -228 65 -130 227 -66 261 -166 99 -98 131 -366 199 -134 463 -102 201 -98 231 -102 639 -238 301 -568 169 -610 265 -102 841 -198 297 -100 335 -132 263 -266 265 -68 469 -134 267 -68 933 -298 333 -298 729 -168 135 -136 437 -132 1137 -134 199 -68 265 -132 463 -166 129 -130 227 -98 297 -98 65 -132 97 -202 199 -232 305 -66 165 -198 365 -66 99 -98 299 -170 65 -136 301 -232 99 -564 133 -132 233 -170 99 -102 131 -134 65 -204 101 -98 297 -98 167 -762 233 -298 99 -326 395 -66 299 -132 369 -504 333 -98 483 -200 457 -164 63 -164 329 -162 65 -622 231 -268 131 -132 133 -134 131 -134 131 -66 99 -100 231 -66 167 -336 165 -98 197 -100 97 -264 321 -98 521 -132 163 -130 129 -294 297 -134 101 -102 265 -168 497 -68 197 -68 499 -134 269 -398 267 -130 203 -302 65 -498 271 -136 465 -292 131 -294 163 -198 329 -96 129 -98 193 -130 391 -330 165 -134 167 -170 297 -102 133 -136 135 -366 199 -132 423 -132 395 -168 65 -166 401 -98 229 -98 329 -98 99 -130 129 -228 261 -160 127 -426 389 -162 193 -132 131 -100 231 -168 67 -304 201 -68 765 -132 161 -162 193 -64 195 -64 295 -130 787 -98 419 -528 429 -66 363 -134 131 -100 133 -200 331 -98 +RAW_Data: 431 -66 1167 -68 937 -68 1003 -66 99 -132 941 -134 65 -66 365 -274 165 -236 367 -96 557 -134 675 -66 261 -164 127 -96 391 -164 161 -98 391 -292 163 -98 519 -196 165 -98 523 -66 195 -160 3343 -66 661 -100 2589 -136 307 -100 629 -136 639 -100 133 -168 405 -100 267 -66 465 -132 1171 -64 749 -64 165 -98 983 -100 163 -202 537 -66 327 -100 669 -100 401 -236 2885 -164 439 -134 97 -426 1931 -66 1385 -98 715 -98 519 -66 289 -162 97 -360 297 -166 163 -66 289 -66 555 -334 167 -230 429 -102 267 -132 943 -136 401 -68 929 -130 193 -68 467 -198 335 -66 963 -100 597 -132 197 -260 523 -232 1115 -102 1935 -66 1395 -134 305 -100 99 -66 199 -66 1071 -66 2357 -66 367 -498 769 -234 163 -130 191 -64 1211 -200 133 -102 201 -100 561 -366 361 -98 195 -100 537 -64 165 -196 1041 -332 133 -102 441 -230 4217 -66 1033 -66 167 -66 933 -100 565 -66 331 -164 673 -104 441 -66 533 -66 2095 -164 525 -66 297 -170 965 -198 421 -100 663 -832 65 -100 331 -164 231 -166 135 -168 237 -466 761 -134 891 -196 791 -198 257 -160 161 -98 293 -66 1081 -98 229 -130 327 -66 1301 -200 331 -166 101 -66 461 -100 2619 -132 1663 -98 1609 -134 499 -332 165 -370 67 -264 97 -96 259 -98 701 -402 197 -128 527 -236 233 -102 167 -134 303 -134 99 -166 299 -132 165 -200 467 -68 305 -168 207 -102 465 -102 729 -136 101 -374 327 -96 259 -98 467 -202 65 -66 673 -98 335 -404 135 -66 339 -204 99 -366 233 -68 365 -166 133 -102 867 -198 163 -162 163 -294 463 -332 165 -68 269 -268 331 -100 131 -166 299 -132 231 -400 263 -164 131 -266 267 -264 367 -66 371 -134 229 -104 267 -232 67 -466 265 -100 101 -100 165 -200 65 -200 301 -66 199 -168 233 -98 267 -66 67 -134 261 -196 261 -234 427 -294 65 -194 193 -66 259 -132 849 -96 63 -198 167 -294 95 -98 361 -164 261 -196 131 -132 437 -100 597 -262 327 -162 295 -98 295 -164 259 -196 425 -230 321 -66 195 -66 261 -496 99 -200 529 -132 133 -966 133 -132 165 -66 63 -128 491 -402 65 -262 299 -66 299 -202 265 -100 99 -668 97 -134 65 -100 101 -66 65 -266 691 -66 431 -166 167 -134 199 -370 899 -134 99 -100 1093 -166 163 -166 399 -98 327 -100 99 -168 135 -200 133 -202 429 -98 65 -98 197 -556 65 -66 97 -326 331 -166 333 -200 135 -100 235 -234 265 -98 65 -68 135 -66 335 -66 133 -298 99 -66 233 -164 435 -232 97 -132 97 -392 +RAW_Data: 99 -198 819 -66 1235 -98 321 -132 1091 -66 1307 -98 3059 -164 3305 -64 227 -98 591 -98 129 -66 229 -98 2143 -98 939 -68 563 -100 361 -232 945 -164 257 -96 229 -230 387 -64 195 -130 981 -294 587 -162 193 -98 1337 -66 293 -98 2665 -66 297 -98 647 -66 459 -132 491 -164 489 -96 595 -66 899 -66 837 -64 1151 -196 259 -98 357 -164 891 -132 1359 -134 197 -98 97 -98 261 -64 229 -96 461 -136 693 -100 201 -98 865 -66 599 -100 517 -132 709 -66 293 -298 655 -66 197 -130 129 -66 197 -98 4291 -66 673 -66 667 -132 1473 -132 133 -104 99 -66 163 -168 333 -134 1743 -132 1097 -132 99 -68 167 -602 1323 -352 99 -166 753 -98 423 -98 97 -66 1317 -228 1309 -98 1849 -66 1939 -132 601 -100 665 -100 1875 -66 695 -132 425 -66 425 -66 263 -134 165 -134 99 -98 829 -66 601 -166 131 -102 565 -66 301 -100 1099 -100 601 -138 533 -66 667 -234 561 -66 99 -68 2741 -98 199 -100 531 -168 101 -434 1027 -68 431 -66 403 -132 99 -98 565 -132 135 -100 399 -166 271 -236 233 -166 197 -366 99 -66 99 -168 503 -66 199 -170 207 -100 673 -368 99 -66 263 -168 133 -98 397 -268 337 -66 131 -132 231 -132 501 -134 99 -168 567 -138 103 -136 267 -298 231 -134 197 -160 321 -332 231 -98 131 -164 257 -64 163 -328 395 -66 331 -202 65 -168 133 -68 167 -100 233 -102 335 -66 197 -326 1101 -132 589 -100 811 -132 399 -136 269 -102 497 -66 559 -100 129 -98 855 -68 637 -102 65 -200 875 -68 233 -166 167 -66 529 -202 235 -102 231 -66 1237 -66 733 -98 1723 -132 101 -100 297 -66 829 -232 197 -100 367 -134 169 -166 167 -434 633 -100 235 -200 131 -134 233 -100 131 -100 331 -134 495 -432 65 -528 161 -130 295 -132 337 -136 133 -166 165 -100 269 -240 201 -336 133 -166 165 -238 199 -202 431 -434 99 -134 501 -166 231 -96 559 -202 167 -66 717 -98 987 -198 65 -64 163 -64 227 -98 555 -164 199 -64 361 -66 163 -98 129 -162 97 -130 161 -460 197 -230 681 -98 197 -98 329 -100 267 -266 291 -264 65 -100 329 -100 459 -200 363 -98 165 -134 231 -134 301 -134 231 -302 99 -132 101 -134 267 -136 233 -68 393 -422 163 -166 361 -166 99 -134 365 -134 133 -336 401 -66 495 -132 401 -168 133 -402 501 -136 1093 -862 165 -132 293 -300 289 -66 131 -164 391 -134 99 -360 359 -130 323 -200 423 -98 195 -162 295 -132 161 -98 129 -782 131 -426 227 -64 259 -166 63 -160 323 -98 261 -230 +RAW_Data: 231 -66 921 -66 355 -64 1019 -98 227 -258 163 -66 597 -232 1313 -132 163 -404 467 -236 901 -164 483 -98 195 -96 489 -134 103 -238 169 -66 67 -68 299 -100 497 -68 65 -134 1635 -304 1153 -100 539 -168 265 -200 499 -166 535 -100 397 -168 931 -100 131 -66 631 -134 897 -270 1233 -100 65 -132 131 -334 663 -66 163 -66 131 -132 705 -98 571 -200 433 -100 237 -234 229 -132 1627 -66 569 -100 715 -66 1863 -272 265 -68 301 -98 465 -68 97 -134 99 -66 395 -136 1405 -66 529 -132 63 -196 579 -132 413 -260 129 -136 101 -166 1201 -134 833 -134 393 -66 335 -172 201 -68 1027 -96 753 -64 815 -66 97 -64 1341 -132 289 -160 127 -66 99 -228 1083 -96 163 -66 259 -64 159 -98 2409 -168 767 -200 367 -66 1675 -66 1067 -98 3407 -200 99 -66 1403 -166 99 -134 439 -200 329 -136 599 -66 637 -66 835 -66 1099 -98 99 -66 463 -166 165 -100 461 -164 3037 -66 655 -66 97 -98 229 -130 355 -132 1443 -66 527 -98 881 -98 229 -162 127 -96 583 -64 65 -162 489 -166 885 -194 257 -98 1539 -66 293 -166 229 -132 655 -98 757 -49522 271 -758 689 -1264 737 -670 293 -1152 811 -1144 341 -664 773 -678 327 -1118 807 -1144 835 -1146 781 -1126 873 -1096 347 -622 877 -624 321 -1106 843 -1098 871 -1098 843 -1106 379 -610 841 -584 381 -1122 365 -602 845 -1116 837 -610 381 -1056 889 -1078 383 -614 827 -1110 877 -592 353 -1108 845 -1120 839 -1120 347 -602 849 -1110 865 -612 361 -1072 869 -1114 351 -618 861 -618 343 -1090 853 -1106 387 -618 797 -674 347 -1084 389 -574 867 -584 381 -1114 841 -1102 845 -1116 839 -1112 843 -1098 875 -1086 383 -584 865 -588 375 -1100 861 -1112 851 -1084 853 -1108 847 -1106 381 -584 857 -610 383 -1080 357 -602 871 -602 385 -1084 383 -616 823 -610 373 -1086 381 -590 871 -1084 839 -628 353 -1102 875 -1100 349 -9404 875 -1060 871 -1086 887 -1088 879 -1058 863 -1086 855 -1132 845 -1078 871 -1076 857 -1098 881 -1082 861 -1088 843 -1120 853 -1074 879 -1074 879 -1068 889 -614 341 -1090 387 -616 863 -624 345 -1088 391 -590 857 -612 385 -1058 393 -596 843 -1088 889 -1078 879 -578 387 -1082 875 -1076 415 -550 881 -1070 877 -592 391 -1114 821 -1104 373 -620 821 -624 361 -1072 903 -1086 855 -1092 843 -1086 905 -1054 387 -614 863 -618 347 -1088 853 -1114 845 -1090 867 -1070 381 -610 885 -584 385 -1052 407 -578 877 -1052 899 -600 389 -1048 907 -1074 383 -586 877 -1072 877 -594 359 -1076 875 -1082 891 -1088 363 -616 855 -1084 857 -592 381 -1088 883 -1086 385 -572 +RAW_Data: 889 -624 353 -1082 853 -1096 379 -594 853 -624 353 -1092 417 -582 847 -612 385 -1076 847 -1080 883 -1052 913 -1044 907 -1076 849 -1088 383 -602 867 -616 361 -1068 901 -1072 865 -1104 831 -1080 879 -1098 397 -586 855 -626 355 -1084 381 -592 873 -616 351 -1084 385 -624 821 -620 359 -1086 387 -584 883 -1086 877 -592 355 -1106 853 -1086 387 -69570 97 -100 99 -2620 131 -636 333 -102 235 -236 67 -68 363 -66 201 -100 567 -102 267 -164 101 -134 65 -68 197 -68 297 -166 671 -100 469 -336 165 -100 201 -66 169 -230 169 -204 329 -624 67 -98 265 -232 193 -168 299 -100 235 -138 101 -370 165 -294 333 -622 231 -130 129 -130 353 -132 195 -162 359 -164 67 -68 333 -100 133 -688 235 -236 497 -198 293 -98 129 -296 293 -164 229 -128 229 -132 193 -400 165 -66 163 -98 361 -164 355 -196 587 -164 131 -98 263 -554 99 -130 129 -130 191 -464 99 -132 67 -100 167 -604 329 -66 199 -68 133 -102 163 -66 2971 -132 785 -66 329 -96 323 -100 201 -136 301 -66 1959 -166 867 -134 467 -66 297 -100 835 -100 753 -166 165 -64 67 -370 335 -66 559 -232 165 -334 65 -162 129 -354 163 -64 131 -134 265 -300 263 -132 267 -296 327 -198 99 -132 535 -132 469 -866 231 -860 99 -232 503 -134 99 -198 233 -134 267 -200 97 -358 297 -164 259 -98 227 -166 135 -66 323 -100 97 -294 131 -164 129 -98 295 -96 129 -426 299 -100 67 -102 623 -100 163 -194 127 -360 563 -134 199 -428 493 -98 229 -130 257 -64 165 -100 131 -98 163 -692 357 -64 161 -98 321 -64 389 -230 65 -692 227 -130 261 -132 231 -162 287 -298 97 -460 393 -130 301 -168 331 -100 269 -202 101 -134 201 -102 99 -132 199 -204 235 -664 65 -562 133 -328 463 -100 291 -194 159 -162 227 -98 293 -328 165 -128 227 -574 535 -332 197 -168 65 -300 131 -66 389 -1078 131 -64 259 -64 223 -98 257 -164 63 -328 433 -134 65 -602 131 -68 333 -136 369 -66 297 -264 427 -66 97 -130 429 -102 133 -136 203 -240 167 -236 329 -526 67 -132 133 -168 331 -360 65 -66 331 -296 267 -134 469 -132 595 -230 661 -662 299 -100 265 -200 203 -168 801 -100 133 -68 399 -132 99 -100 161 -390 65 -298 65 -98 261 -130 161 -128 257 -66 67 -134 621 -98 227 -328 99 -230 129 -294 193 -96 195 -318 425 -526 129 -196 163 -162 65 -132 293 -130 63 -66 325 -128 63 -130 293 -66 199 -200 269 -206 133 -198 325 -98 163 -100 97 -98 261 -164 67 -98 167 -430 131 -494 131 -164 +RAW_Data: 97 -98 861 -66 1199 -166 231 -100 651 -166 197 -104 439 -98 131 -64 493 -98 883 -96 99 -98 3327 -66 131 -264 733 -134 2133 -166 131 -102 303 -136 535 -134 701 -98 355 -228 131 -202 99 -134 99 -100 791 -166 169 -202 671 -100 741 -100 263 -66 165 -68 935 -132 197 -198 673 -100 605 -66 1457 -98 1195 -166 2347 -134 505 -100 1469 -66 391 -100 229 -100 1171 -98 939 -100 459 -170 369 -134 231 -162 127 -98 95 -66 195 -98 195 -66 299 -100 331 -98 65 -232 369 -132 201 -68 167 -166 1481 -102 501 -160 1257 -66 2307 -64 623 -164 2079 -66 1101 -98 423 -64 659 -68 431 -136 99 -100 435 -130 167 -168 835 -200 135 -104 133 -100 503 -68 1437 -232 821 -132 357 -96 463 -66 263 -64 683 -132 165 -96 655 -166 3939 -100 1169 -132 2443 -98 197 -132 425 -234 233 -162 1043 -66 197 -100 2793 -134 167 -104 675 -100 197 -134 1367 -102 763 -132 265 -230 133 -102 365 -100 167 -66 1069 -66 837 -100 295 -160 97 -64 129 -132 617 -164 197 -100 133 -136 337 -172 133 -66 557 -98 951 -66 263 -130 587 -66 729 -196 335 -166 933 -432 369 -100 199 -296 225 -98 355 -66 129 -64 557 -98 289 -66 355 -128 193 -162 267 -134 299 -98 165 -170 303 -640 1031 -134 99 -66 135 -68 771 -166 171 -104 201 -134 131 -68 635 -428 661 -292 749 -430 1161 -100 905 -98 65 -98 657 -262 2837 -132 67 -66 265 -132 631 -66 1037 -296 97 -98 1703 -302 367 -100 505 -232 497 -362 333 -134 591 -100 755 -232 67 -130 587 -66 231 -168 65 -332 99 -66 267 -232 393 -134 65 -132 131 -428 133 -200 165 -202 199 -168 165 -102 269 -100 333 -852 201 -134 233 -202 65 -200 563 -768 265 -136 169 -102 169 -598 333 -202 267 -134 267 -328 163 -130 625 -500 199 -200 99 -270 65 -134 65 -198 65 -100 99 -596 493 -66 99 -66 331 -232 103 -136 373 -168 831 -170 65 -672 163 -102 133 -136 331 -100 333 -234 101 -100 99 -200 99 -100 201 -302 199 -600 301 -202 135 -134 705 -166 435 -530 97 -198 131 -198 195 -66 163 -392 293 -66 295 -370 229 -198 65 -100 405 -134 165 -134 133 -170 337 -236 205 -274 267 -134 329 -132 195 -132 503 -132 133 -136 133 -334 197 -196 299 -168 101 -100 233 -100 439 -134 301 -332 331 -298 433 -406 433 -68 167 -100 203 -100 101 -102 99 -328 397 -234 205 -168 133 -364 63 -202 397 -198 95 -394 267 -134 569 -66 201 -102 133 -136 101 -102 99 -132 99 -196 197 -498 197 -102 135 -170 +RAW_Data: 331 -164 63 -162 1267 -66 163 -130 129 -66 725 -164 231 -64 853 -66 101 -134 199 -102 99 -68 365 -66 357 -130 815 -64 357 -98 97 -98 97 -66 65 -466 231 -172 3749 -66 849 -130 917 -64 327 -64 1013 -98 555 -332 795 -100 571 -132 769 -132 401 -134 1297 -134 377 -138 435 -100 401 -100 667 -100 1761 -66 667 -66 1533 -236 233 -98 885 -130 457 -66 999 -66 165 -66 833 -134 695 -166 501 -66 499 -200 329 -64 197 -134 441 -100 2099 -98 491 -134 197 -130 2225 -132 65 -100 689 -64 193 -160 159 -96 195 -98 323 -164 259 -98 535 -472 771 -66 665 -270 665 -66 595 -266 2191 -64 643 -98 1287 -98 741 -100 233 -200 569 -194 261 -68 637 -100 97 -66 491 -158 395 -138 1017 -66 627 -262 559 -64 327 -98 263 -134 99 -102 201 -102 337 -66 167 -68 679 -100 471 -134 195 -66 133 -202 693 -96 197 -98 391 -164 99 -98 3883 -194 461 -100 237 -168 1891 -68 301 -68 969 -166 1439 -294 551 -130 389 -98 99 -196 167 -102 505 -66 569 -234 901 -98 407 -136 469 -66 769 -98 769 -166 1263 -266 297 -98 1701 -200 203 -168 329 -232 65 -100 329 -164 803 -100 135 -200 233 -166 135 -272 265 -134 197 -100 133 -134 539 -232 197 -396 165 -366 263 -68 233 -102 365 -132 233 -100 135 -266 199 -234 167 -232 97 -524 127 -128 389 -98 305 -364 261 -130 257 -162 589 -464 361 -66 229 -134 161 -100 203 -432 265 -66 199 -66 199 -366 229 -236 99 -134 99 -100 131 -168 133 -100 131 -236 267 -132 297 -264 291 -132 167 -234 65 -100 199 -66 333 -730 237 -440 365 -102 99 -100 99 -132 99 -100 1429 -134 427 -100 97 -100 131 -164 799 -170 1077 -100 431 -66 133 -168 737 -134 197 -230 65 -102 803 -132 491 -98 429 -198 471 -134 365 -66 299 -236 65 -66 2837 -102 399 -64 585 -64 523 -196 97 -98 295 -196 555 -160 261 -500 299 -396 333 -236 133 -68 327 -100 199 -204 699 -66 701 -100 65 -164 65 -370 195 -196 97 -66 193 -130 129 -360 195 -130 231 -96 291 -64 455 -228 293 -196 291 -162 97 -194 621 -130 847 -66 395 -66 161 -128 193 -130 293 -98 231 -170 67 -134 297 -360 167 -266 263 -526 263 -132 229 -98 191 -160 159 -100 721 -234 101 -100 99 -130 259 -258 265 -632 687 -164 133 -134 631 -100 199 -102 165 -560 299 -200 265 -332 431 -870 99 -266 503 -364 135 -66 269 -68 499 -100 265 -102 263 -102 569 -234 719 -132 99 -196 419 -262 163 -688 95 -66 165 -128 95 -66 +RAW_Data: 295 -98 987 -196 517 -100 489 -66 355 -132 563 -198 867 -134 1413 -134 541 -134 767 -100 193 -98 1799 -102 467 -134 299 -96 323 -66 261 -100 259 -66 229 -96 851 -66 369 -266 469 -66 101 -98 163 -136 267 -432 859 -130 523 -66 197 -134 1027 -132 227 -194 393 -98 807 -166 235 -100 133 -66 165 -102 133 -136 371 -162 1411 -132 865 -200 471 -100 133 -68 299 -66 633 -98 329 -234 401 -98 1505 -132 133 -134 331 -262 163 -66 261 -98 289 -64 201 -68 1055 -96 391 -66 951 -298 265 -202 297 -66 401 -68 131 -100 1733 -98 941 -66 803 -98 847 -64 3701 -100 721 -160 357 -166 1799 -66 329 -100 99 -102 363 -198 167 -136 197 -66 567 -66 199 -236 1247 -166 2455 -68 1107 -200 235 -100 2355 -130 913 -98 877 -98 163 -196 97 -66 427 -100 801 -134 867 -98 263 -68 441 -134 561 -98 1671 -134 865 -68 935 -132 163 -102 975 -66 1343 -132 1339 -134 369 -100 1107 -66 1167 -168 631 -232 835 -66 1027 -132 333 -166 265 -98 1207 -98 223 -98 455 -64 2095 -134 933 -136 233 -68 335 -136 305 -100 1737 -66 427 -100 263 -130 323 -66 227 -66 717 -100 265 -100 65 -128 355 -66 367 -132 95 -230 229 -100 131 -64 493 -132 291 -396 393 -130 259 -196 227 -288 397 -68 229 -430 99 -302 237 -700 65 -66 65 -100 133 -200 101 -336 133 -166 237 -202 67 -302 67 -68 333 -132 263 -102 267 -296 163 -166 233 -168 363 -64 295 -298 537 -166 431 -200 431 -166 63 -258 363 -164 563 -234 199 -68 299 -100 325 -754 295 -196 65 -98 165 -132 301 -134 131 -134 97 -68 405 -68 233 -134 271 -134 67 -168 101 -136 133 -366 99 -132 67 -132 265 -200 233 -100 201 -136 101 -66 263 -132 129 -66 293 -582 263 -132 1103 -134 203 -168 97 -66 197 -264 131 -168 133 -132 65 -134 199 -134 101 -100 131 -436 99 -232 97 -398 231 -362 65 -202 301 -396 297 -98 199 -134 265 -164 101 -168 267 -102 405 -170 99 -102 397 -132 97 -98 295 -98 1179 -100 135 -136 131 -134 765 -134 465 -168 439 -232 403 -100 65 -134 931 -100 169 -136 237 -68 231 -234 199 -68 401 -134 541 -166 429 -166 1607 -368 533 -66 363 -66 133 -134 433 -166 297 -238 201 -100 201 -170 199 -134 273 -136 99 -134 167 -238 133 -66 265 -134 165 -132 165 -132 97 -228 723 -198 415 -64 491 -298 257 -66 231 -192 225 -96 227 -98 193 -96 521 -198 65 -66 231 -166 163 -98 465 -66 133 -132 195 -130 225 -162 521 -130 63 -66 199 -228 +RAW_Data: 817 -162 449 -160 719 -198 469 -68 133 -68 1101 -132 593 -230 1105 -100 131 -134 231 -66 329 -196 685 -96 557 -68 1263 -68 101 -68 397 -100 65 -66 625 -66 97 -132 1099 -66 493 -66 757 -98 1151 -66 303 -134 1901 -66 99 -100 665 -262 991 -98 791 -66 1925 -168 865 -232 835 -98 505 -102 99 -100 535 -100 169 -134 427 -132 863 -68 167 -134 975 -100 133 -268 1339 -100 1453 -66 1445 -162 195 -64 3623 -66 237 -68 1063 -308 1449 -98 1111 -132 167 -102 855 -270 199 -134 297 -134 267 -168 863 -234 637 -66 567 -230 99 -200 3325 -198 845 -66 289 -66 131 -66 815 -130 1093 -100 167 -100 429 -98 1703 -166 195 -64 971 -98 163 -192 195 -168 439 -132 329 -132 67 -134 67 -134 1591 -168 407 -100 867 -68 399 -134 661 -100 663 -66 237 -136 395 -232 131 -66 695 -100 627 -264 913 -66 1083 -98 287 -66 199 -132 335 -100 1031 -68 99 -100 3815 -98 165 -66 129 -98 163 -128 563 -98 779 -96 223 -64 161 -164 2025 -66 1741 -172 101 -136 203 -102 665 -100 475 -64 167 -100 637 -98 997 -170 1207 -136 233 -166 233 -168 635 -132 199 -100 235 -270 199 -98 131 -102 169 -170 293 -98 323 -164 427 -334 233 -168 267 -68 369 -100 263 -368 101 -66 665 -98 265 -100 133 -100 99 -168 133 -66 133 -132 133 -66 269 -134 435 -68 267 -136 271 -500 163 -100 163 -166 355 -132 97 -98 323 -194 63 -688 463 -130 97 -396 65 -100 357 -194 461 -98 161 -130 223 -162 165 -352 461 -300 267 -166 233 -464 329 -100 293 -362 163 -228 289 -66 229 -66 195 -162 325 -66 261 -98 127 -424 299 -302 367 -68 265 -272 429 -98 161 -98 393 -296 65 -130 161 -196 261 -66 473 -234 97 -98 263 -160 323 -98 67 -132 697 -298 99 -134 233 -202 97 -134 301 -200 307 -100 101 -134 865 -166 231 -202 233 -100 301 -170 169 -102 169 -200 65 -98 595 -166 231 -234 661 -66 473 -334 165 -304 365 -266 97 -502 363 -134 133 -236 65 -100 99 -134 99 -170 235 -66 333 -100 195 -100 133 -300 133 -102 301 -304 65 -100 99 -100 131 -202 135 -134 65 -200 363 -66 263 -498 67 -68 295 -194 321 -368 435 -100 97 -664 99 -100 569 -66 133 -66 67 -134 199 -136 101 -68 301 -68 405 -198 133 -132 581 -132 165 -98 159 -98 197 -66 229 -130 131 -294 133 -96 423 -100 427 -300 357 -132 291 -64 95 -194 455 -98 263 -100 359 -196 65 -162 227 -162 157 -96 157 -230 589 -132 325 -134 535 -66 267 -100 135 -302 +RAW_Data: 131 -134 599 -166 393 -98 369 -236 197 -100 401 -232 569 -134 135 -70 337 -134 101 -136 135 -100 1895 -66 401 -170 503 -66 1633 -66 601 -66 355 -96 683 -100 729 -68 133 -132 433 -68 569 -100 133 -68 201 -132 835 -100 465 -68 527 -98 193 -200 1129 -166 535 -100 199 -98 259 -132 227 -64 1597 -98 261 -192 753 -100 911 -66 667 -298 131 -100 263 -66 1051 -230 787 -66 935 -66 233 -98 885 -236 431 -66 197 -162 521 -68 167 -196 263 -96 589 -98 517 -66 1439 -64 777 -66 3219 -132 679 -134 205 -68 507 -198 749 -200 199 -168 167 -100 133 -134 201 -68 731 -66 495 -198 737 -66 237 -68 135 -100 167 -234 1535 -68 873 -66 373 -66 67 -232 297 -68 65 -66 1095 -68 327 -130 63 -132 1715 -66 2261 -100 321 -132 197 -164 457 -232 1291 -132 405 -68 1001 -68 1133 -272 471 -66 99 -134 1403 -68 167 -68 1091 -336 933 -134 1207 -132 265 -68 267 -66 99 -366 265 -66 1469 -258 367 -168 429 -132 129 -66 491 -132 343 -100 65 -100 263 -136 199 -164 273 -204 791 -100 901 -66 167 -98 165 -64 559 -132 619 -132 1087 -128 2283 -398 1467 -164 259 -130 1927 -130 421 -98 1085 -66 705 -68 1843 -168 875 -170 203 -136 341 -640 199 -66 133 -554 161 -196 63 -66 521 -292 163 -160 95 -158 127 -192 197 -100 587 -130 397 -662 261 -66 193 -130 259 -66 361 -64 459 -98 197 -560 655 -130 389 -66 1135 -100 133 -130 131 -98 1011 -100 561 -66 685 -164 457 -132 2469 -200 609 -66 665 -66 67 -132 327 -200 1657 -134 919 -132 651 -100 327 -230 191 -130 263 -358 95 -130 549 -98 99 -68 299 -100 461 -132 99 -472 165 -134 99 -66 99 -132 399 -102 169 -102 697 -166 233 -132 333 -632 197 -164 865 -266 101 -68 533 -166 299 -100 163 -228 259 -66 327 -200 65 -66 229 -100 363 -230 197 -336 165 -102 893 -300 65 -132 231 -370 265 -230 99 -98 229 -518 199 -100 401 -724 225 -98 63 -96 231 -64 291 -292 65 -98 131 -98 159 -158 127 -194 161 -292 65 -98 133 -66 297 -66 303 -168 97 -168 231 -234 269 -532 135 -168 99 -168 301 -528 99 -506 199 -368 399 -132 329 -372 99 -68 133 -264 197 -100 201 -200 67 -134 131 -270 133 -134 133 -198 327 -200 65 -100 331 -262 161 -166 469 -534 167 -738 131 -100 367 -232 101 -100 265 -604 65 -170 99 -166 299 -102 169 -132 99 -398 229 -330 197 -166 335 -366 97 -98 131 -200 269 -100 199 -168 131 -134 537 -98 265 -100 335 -236 99 -366 +RAW_Data: 459 -100 453 -130 419 -130 519 -96 63 -130 2077 -66 767 -64 127 -134 1961 -296 529 -202 637 -134 527 -100 201 -68 633 -66 163 -360 1029 -68 765 -100 867 -66 503 -100 131 -66 841 -98 165 -68 237 -66 509 -100 501 -302 235 -66 99 -164 227 -130 551 -196 327 -66 1571 -132 99 -68 867 -66 163 -96 161 -130 129 -130 549 -130 487 -166 1801 -66 229 -66 197 -232 325 -66 425 -198 131 -64 295 -166 735 -66 533 -98 227 -130 129 -262 425 -100 263 -66 129 -132 97 -168 971 -170 405 -68 199 -134 475 -202 297 -98 1445 -98 395 -196 161 -66 225 -134 1803 -100 473 -102 1499 -66 199 -100 701 -132 165 -68 133 -102 303 -98 735 -102 805 -100 827 -100 235 -100 65 -266 637 -68 693 -66 1383 -228 819 -66 233 -304 435 -198 203 -136 1135 -270 1709 -64 227 -64 581 -134 505 -66 2203 -64 293 -64 753 -66 551 -132 747 -64 1303 -64 463 -66 229 -102 1877 -266 871 -166 1357 -64 819 -66 465 -198 693 -68 165 -64 95 -128 3785 -132 1465 -100 299 -102 329 -164 595 -134 1029 -66 299 -168 1263 -166 331 -68 967 -100 101 -102 603 -260 165 -132 467 -66 233 -66 235 -102 475 -100 135 -68 301 -134 297 -98 131 -102 269 -466 99 -134 237 -166 135 -168 203 -102 265 -68 503 -66 233 -66 637 -134 101 -200 199 -166 293 -554 361 -328 367 -264 533 -238 167 -68 135 -170 99 -300 591 -298 133 -236 299 -66 231 -368 263 -232 435 -136 133 -102 133 -200 133 -134 163 -134 167 -168 299 -66 265 -100 133 -240 135 -132 263 -170 269 -200 501 -396 263 -98 227 -132 129 -292 427 -66 165 -102 627 -602 99 -66 301 -168 199 -100 563 -330 165 -134 233 -136 65 -332 499 -100 131 -232 325 -96 65 -132 195 -98 393 -624 323 -68 133 -98 195 -162 231 -100 263 -132 231 -102 133 -236 99 -236 231 -166 65 -102 133 -268 101 -102 299 -136 267 -164 493 -64 229 -258 291 -326 263 -198 391 -134 167 -202 365 -594 133 -102 201 -134 503 -396 429 -204 169 -400 197 -170 267 -132 403 -466 297 -98 469 -234 395 -132 233 -100 165 -100 165 -66 197 -68 297 -166 501 -134 133 -100 65 -166 631 -68 297 -134 199 -100 165 -68 299 -266 133 -66 165 -100 231 -490 557 -134 371 -164 299 -170 733 -164 239 -334 335 -66 299 -300 199 -170 103 -100 233 -102 641 -168 65 -100 995 -66 265 -160 259 -130 129 -226 425 -100 355 -726 97 -688 99 -66 233 -266 299 -942 167 -102 167 -166 65 -100 367 -136 99 -134 199 -134 267 -164 +RAW_Data: 67 -68 233 -66 899 -66 163 -96 485 -98 355 -130 943 -100 235 -168 499 -104 1367 -98 297 -100 635 -68 1169 -100 67 -134 835 -264 959 -164 129 -98 419 -196 589 -66 421 -66 1717 -100 133 -100 265 -134 227 -356 455 -166 163 -66 1055 -100 1455 -134 463 -98 2191 -132 295 -132 335 -66 709 -64 619 -98 959 -68 835 -170 603 -134 1033 -134 635 -168 759 -232 397 -198 397 -164 1267 -166 257 -198 1295 -100 239 -104 563 -204 335 -198 203 -68 901 -68 1255 -134 1697 -66 793 -66 1691 -68 201 -100 765 -66 165 -132 131 -230 131 -66 917 -66 335 -338 231 -170 827 -98 199 -136 301 -196 65 -98 199 -200 765 -134 403 -98 333 -68 1691 -132 2565 -64 569 -170 1255 -264 65 -132 1243 -132 2527 -66 259 -66 1739 -100 1309 -198 167 -238 337 -66 131 -68 1973 -362 299 -100 1387 -96 129 -164 423 -230 3875 -96 4283 -98 165 -98 515 -134 469 -68 171 -102 1163 -100 65 -298 461 -66 367 -136 205 -168 371 -98 491 -164 161 -262 1093 -100 299 -100 269 -334 1205 -98 63 -98 261 -64 457 -98 diff --git a/assets/unit_tests/subghz/test_random_raw.sub b/assets/unit_tests/subghz/test_random_raw.sub index 928838d3c8f..7d342bb93ad 100644 --- a/assets/unit_tests/subghz/test_random_raw.sub +++ b/assets/unit_tests/subghz/test_random_raw.sub @@ -145,3 +145,18 @@ RAW_Data: -66 133 -66 97 -166 561 -100 895 -132 1323 -66 10873 -3752 99 -722 229 RAW_Data: -5434 65 -298 133 -132 131 -68 231 -200 661 -132 9517 -424 97 -1456 99 -1694 393 -100 131 -560 131 -196 197 -298 65 -428 229 -196 297 -266 131 -166 2435 -66 10161 -11230 65 -1320 131 -298 265 -532 231 -200 1291 -68 631 -66 12645 -4048 133 -66 67 -132 167 -266 163 -66 397 -132 197 -132 299 -98 197 -198 2903 -66 2361 -66 9627 -3588 197 -332 165 -68 331 -68 197 -132 99 -100 663 -66 363 -230 231 -166 131 -100 201 -298 163 -132 133 -202 363 -300 397 -102 263 -100 165 -66 1221 -66 1479 -132 165 -98 229 -12976 263 -66 363 -134 231 -66 629 -132 327 -100 97 -130 99 -164 227 -64 297 -132 397 -164 425 -198 97 -198 99 -66 365 -164 199 -102 97 -66 1817 -13524 231 -134 16907 -4086 233 -630 65 -396 201 -66 165 -198 67 -198 99 -664 2117 -166 12473 -446 2649 -440 2661 -420 2651 -422 2681 -418 2703 -400 365 -2724 387 -2696 2695 -414 357 -2704 2707 -386 389 -2700 2687 -392 405 -2706 2695 -402 363 -21268 2707 -388 377 -2706 2691 -404 2699 -382 2717 -382 2707 -378 2693 -416 2687 -396 363 -2736 355 -2748 2659 -416 365 -2708 2715 -388 377 -2708 2697 -404 363 -2730 2673 -420 355 -21268 2655 -460 319 -2766 2663 -448 2631 -436 2665 -418 2683 -410 2681 -416 2701 -386 383 -2700 375 -2744 2669 -416 353 -2730 2685 -416 357 -2708 2721 -380 369 -2724 2697 -382 385 -21260 2701 -418 353 -2720 2673 -418 2675 -408 2693 -384 2715 -386 2717 -386 2691 -404 363 -2732 387 -2702 2669 -412 359 -2736 2699 -380 381 -2728 2675 -416 381 -2720 2675 -414 347 -21280 2685 -390 377 -2724 2689 -416 2673 -408 2705 -382 2695 -410 2689 -414 2661 -418 385 -2704 369 -2704 2693 -416 375 -2726 2661 -420 355 -2728 2711 -388 375 -2702 2691 -410 363 -21252 2659 -488 287 -2794 2651 -448 2629 -436 2671 -416 2695 -416 2663 -406 2699 -384 383 -2730 367 -2702 2695 -418 385 -2702 2685 -412 349 -2744 2693 -366 389 -2714 2693 -394 381 -21266 2685 -418 363 -2730 2683 -382 2693 -418 2675 -410 2699 -384 2719 -382 2707 -380 359 -2734 387 -2704 2709 -380 361 -2732 2699 -418 357 -2728 2667 -416 383 -2696 2709 -380 391 -21228 2685 -458 307 -2800 2647 -412 2659 -432 2667 -416 2695 -416 2675 -406 2675 -416 383 -2700 361 -2730 2687 -414 375 -2696 2701 -420 353 -2720 2711 -382 367 -2728 2675 -416 385 -21222 2735 -386 355 -2744 2687 -396 2679 -418 2701 -386 2705 -382 2681 -410 2697 -384 385 -2736 365 -2704 2715 -384 377 -2696 2697 -416 349 -2722 2707 -386 379 -2732 2671 -410 361 -21258 2681 -464 297 -2796 2629 -456 2655 -420 2661 -448 2663 -404 2695 -382 2715 -380 371 -2740 355 -2744 2679 -384 391 -2728 2675 -388 379 RAW_Data: -2728 2695 -414 357 -2704 2705 -418 357 -21262 2673 -416 383 -2696 2709 -380 2703 -384 2699 -418 2671 -408 2695 -382 2713 -386 379 -2730 357 -2732 2695 -384 383 -2730 2679 -416 357 -2708 2701 -410 349 -2736 2697 -382 385 -21252 2669 -478 289 -2790 2647 -426 2651 -444 2653 -430 2659 -418 2695 -414 2681 -402 349 -2738 383 -2722 2677 -414 347 -2744 2691 -382 369 -2730 2691 -384 383 -2734 2679 -414 347 -21264 2705 -386 379 -2736 2667 -410 2695 -382 2715 -380 2709 -420 2665 -392 2713 -382 383 -2730 365 -2728 2665 -418 383 -2696 2693 -418 357 -2710 2711 -380 375 -2718 2701 -416 357 -21238 2677 -484 311 -2766 2635 -444 2657 -420 2663 -422 2695 -416 2667 -428 2675 -396 363 -73890 133 -98 131 -132 129 -658 99 -66 853 -100 63 -100 361 -98 1589 -66 1231 -132 65 -100 297 -198 65 -132 265 -66 9857 -4672 165 -1030 97 -1394 65 -200 2687 -68 6873 -8336 99 -1156 97 -66 163 -232 163 -262 197 -132 295 -132 263 -166 953 -100 263 -130 393 -164 295 -64 329 -66 393 -164 823 -130 165 -66 6133 -8436 165 -164 265 -266 65 -362 197 -696 3181 -132 363 -98 65 -166 131 -66 399 -132 663 -396 329 -66 7335 -7578 497 -230 627 -264 99 -366 99 -132 131 -134 265 -498 163 -100 1323 -66 265 -66 1129 -100 399 -132 365 -100 795 -68 397 -98 597 -364 297 -132 361 -132 265 -132 8591 -4740 65 -100 131 -166 199 -1088 97 -296 99 -528 131 -98 661 -66 401 -198 1157 -166 361 -164 495 -100 165 -66 297 -100 1423 -66 3067 -5658 67 -6406 197 -1092 65 -530 659 -68 265 -100 991 -68 231 -230 297 -66 327 -66 131 -132 659 -134 131 -100 1183 -132 263 -98 621 -66 2075 -6976 65 -5138 67 -132 129 -664 67 -132 165 -100 331 -466 231 -68 467 -98 563 -66 231 -100 531 -66 465 -66 1023 -166 297 -134 3409 -12290 67 -164 99 -532 133 -166 263 -66 231 -66 721 -64 131 -68 959 -134 495 -100 299 -98 497 -98 365 -100 397 -232 297 -98 531 -66 3029 -12216 265 -132 99 -364 199 -234 131 -66 431 -166 333 -166 397 -132 327 -100 395 -66 197 -132 395 -66 527 -98 295 -100 97 -98 789 -132 363 -132 297 -200 2815 -4914 65 -6620 65 -462 65 -134 297 -66 497 -264 231 -198 2773 -134 365 -100 831 -166 131 -100 297 -132 861 -132 299 -100 561 -66 1381 -6946 65 -5516 231 -266 97 -1362 1093 -68 1621 -134 165 -332 297 -98 361 -228 97 -132 797 -98 3487 -13224 229 -164 65 -132 913 -66 1123 -98 527 -134 929 -98 723 -100 12259 -270 165 -132 67 -132 165 -1326 99 -98 65 -1194 431 -66 695 -66 733 -134 197 RAW_Data: -134 10801 -166 67 -6130 133 -198 231 -334 365 -98 229 -132 165 -68 231 -166 14501 -524 65 -328 131 -498 129 -1288 65 -494 163 -64 165 -66 527 -132 131 -132 1019 -198 129 -166 393 -198 65 -164 6411 -66 3255 -10642 65 -1320 165 -164 493 -492 559 -264 2555 -66 695 -66 1657 -164 855 -66 4001 -10526 97 -596 133 -298 67 -264 65 -300 65 -100 263 -166 231 -134 99 -100 2703 -68 13643 -4922 297 -100 65 -232 133 -198 331 -300 231 -66 331 -100 12047 -3872 97 -196 65 -494 329 -66 65 -890 97 -98 229 -164 195 -596 797 -66 861 -132 65 -66 231 -100 565 -66 65 -66 1297 -132 265 -66 363 -134 265 -364 297 -164 299 -134 297 -134 495 -98 11309 -3790 131 -1380 65 -758 65 -164 129 -460 65 -360 199 -100 563 -68 497 -198 363 -266 263 -100 165 -66 697 -66 1933 -13594 65 -762 1223 -132 1119 -196 361 -134 131 -100 793 -166 695 -68 231 -68 463 -66 11727 -4204 363 -264 131 -132 133 -1124 97 -100 163 -100 327 -100 331 -198 397 -66 397 -100 395 -100 163 -66 197 -564 1059 -7962 65 -100 65 -198 129 -362 99 -394 197 -296 495 -100 1357 -68 459 -66 593 -66 265 -68 301 -132 465 -66 231 -200 397 -66 397 -232 199 -298 12077 -4350 231 -796 363 -198 133 -264 65 -1132 597 -332 3295 -100 755 -98 231 -164 97 -264 459 -166 759 -164 3265 -12138 99 -232 99 -1228 1025 -100 393 -66 531 -132 693 -132 1063 -66 427 -64 297 -294 229 -98 9723 -5404 67 -466 99 -796 267 -98 201 -100 167 -264 461 -98 1415 -66 861 -66 267 -66 331 -134 1663 -66 2089 -7012 65 -100 101 -4804 431 -728 99 -100 65 -100 995 -134 165 -66 929 -100 65 -66 927 -100 1093 -168 99 -100 497 -66 665 -200 6517 -8312 165 -66 129 -66 559 -166 99 -430 65 -398 67 -66 593 -198 459 -132 261 -132 263 -130 723 -66 459 -100 325 -166 67 -198 559 -66 493 -66 11475 -3896 99 -266 99 -66 197 -1092 129 -198 361 -166 163 -98 263 -196 759 -100 265 -100 365 -630 4635 -12748 65 -1712 461 -100 497 -66 395 -98 265 -98 229 -164 529 -132 297 -66 565 -132 987 -132 8665 -2820 2265 -450 313 -2774 2643 -442 325 -2772 2665 -416 359 -2734 2667 -386 379 -21274 2657 -474 293 -2810 2619 -466 2613 -476 2629 -452 2663 -388 2683 -418 2705 -400 365 -2722 387 -2700 2697 -380 361 -2732 2691 -418 361 -2732 2667 -416 383 -2698 2697 -416 357 -21238 2715 -384 383 -2732 2685 -416 2667 -416 2695 -398 2671 -418 2687 -390 2713 -382 383 -2730 365 -2728 2661 -416 379 -2716 2685 -384 379 -2720 2703 -378 401 -2718 2671 +RAW_Data: 889 -130 325 -64 457 -560 165 -68 199 -170 67 -66 265 -132 133 -666 67 -166 431 -66 201 -98 297 -100 595 -66 199 -134 65 -100 795 -132 99 -168 501 -200 331 -132 265 -102 265 -134 423 -98 521 -226 65 -166 431 -134 99 -100 133 -464 195 -326 623 -100 673 -98 321 -200 65 -136 369 -166 65 -68 97 -166 165 -334 265 -102 231 -166 101 -170 65 -170 265 -136 931 -100 133 -134 563 -66 333 -100 427 -66 163 -390 231 -66 193 -130 461 -166 557 -100 99 -198 263 -100 197 -294 231 -232 299 -134 199 -170 267 -134 631 -98 235 -100 499 -68 463 -100 65 -134 335 -170 273 -134 297 -100 67 -66 197 -166 67 -134 301 -168 537 -470 99 -134 433 -132 199 -192 261 -100 523 -164 459 -132 259 -332 359 -64 227 -96 131 -132 687 -132 363 -136 329 -434 99 -334 133 -100 401 -132 233 -700 233 -170 337 -66 371 -68 233 -202 531 -266 731 -66 465 -100 167 -100 133 -232 335 -166 239 -102 367 -232 231 -100 167 -134 201 -136 301 -168 199 -300 231 -98 237 -134 233 -102 329 -132 261 -134 199 -66 265 -136 99 -170 167 -134 199 -166 167 -136 367 -298 197 -200 99 -166 469 -136 439 -66 303 -134 295 -100 433 -134 899 -266 363 -132 197 -160 555 -324 129 -96 97 -128 257 -132 97 -394 257 -98 195 -166 459 -332 395 -132 633 -134 301 -100 131 -332 169 -168 395 -166 263 -540 783 -100 287 -130 295 -96 225 -296 133 -98 99 -100 461 -164 545 -130 99 -66 301 -68 265 -100 235 -134 235 -70 333 -102 497 -66 233 -364 301 -170 103 -66 165 -336 733 -200 133 -100 263 -102 65 -136 465 -200 1035 -198 165 -170 67 -302 631 -100 429 -332 65 -128 129 -130 159 -128 159 -66 161 -96 325 -164 261 -100 197 -162 65 -96 99 -130 65 -102 333 -100 199 -98 389 -330 129 -128 229 -66 425 -366 229 -64 261 -100 227 -96 227 -526 301 -200 97 -66 699 -334 67 -100 399 -198 787 -98 297 -134 429 -100 3245 -64 527 -98 131 -526 633 -68 133 -302 1459 -164 971 -102 237 -136 1439 -266 1131 -66 599 -200 303 -332 325 -130 389 -166 371 -66 333 -102 65 -100 233 -234 327 -266 233 -166 297 -100 225 -130 163 -336 99 -596 199 -330 131 -66 331 -338 263 -358 197 -168 877 -66 227 -96 63 -130 263 -162 225 -290 197 -198 357 -132 297 -262 165 -456 227 -98 399 -296 95 -132 99 -98 457 -200 199 -168 535 -100 567 -134 327 -130 193 -130 683 -102 101 -132 233 -170 943 -166 827 -66 267 -102 503 -68 1325 -164 +RAW_Data: 1607 -68 233 -166 1167 -70 531 -134 335 -168 131 -66 299 -402 899 -66 461 -66 457 -98 953 -98 165 -66 293 -230 881 -64 393 -166 589 -66 289 -66 1093 -204 333 -98 2745 -132 2019 -170 925 -68 269 -102 1469 -136 2301 -68 1355 -100 527 -66 975 -68 1445 -98 2397 -100 1733 -66 703 -100 995 -100 135 -136 235 -202 167 -134 2071 -166 339 -170 201 -268 129 -66 465 -66 365 -100 197 -164 129 -98 161 -96 423 -66 675 -66 1543 -136 567 -200 767 -202 65 -100 1401 -66 623 -136 567 -234 67 -236 197 -194 97 -66 263 -66 1827 -392 1893 -98 165 -268 133 -132 231 -162 225 -98 695 -198 563 -100 301 -332 267 -102 341 -66 99 -132 1299 -130 525 -68 161 -96 357 -98 353 -100 131 -100 131 -98 163 -132 323 -100 535 -66 1323 -130 133 -66 235 -134 1497 -132 387 -98 129 -162 2623 -134 163 -68 167 -66 959 -232 495 -68 131 -134 867 -134 865 -66 333 -98 305 -134 231 -98 765 -198 397 -432 165 -66 165 -366 265 -102 541 -100 261 -162 331 -134 457 -66 491 -196 97 -266 193 -262 65 -166 231 -266 497 -360 263 -98 587 -164 259 -98 231 -66 359 -100 267 -102 271 -168 97 -262 63 -66 261 -130 227 -130 295 -164 65 -66 265 -200 597 -134 267 -170 603 -100 97 -466 231 -264 97 -168 99 -66 65 -200 199 -100 267 -404 303 -102 201 -204 235 -134 131 -198 335 -298 327 -130 291 -164 63 -162 295 -262 197 -130 95 -130 195 -96 159 -130 161 -66 231 -100 165 -66 199 -134 363 -66 267 -168 165 -168 167 -100 165 -530 363 -432 99 -232 65 -132 395 -328 229 -98 197 -132 161 -96 191 -292 197 -204 133 -100 399 -166 531 -332 235 -168 99 -66 325 -158 553 -132 129 -226 231 -134 99 -462 129 -64 289 -100 193 -66 355 -164 291 -198 131 -298 197 -198 373 -268 335 -234 427 -68 199 -132 267 -232 131 -66 783 -326 63 -162 161 -130 227 -66 259 -562 233 -464 303 -102 201 -334 301 -134 297 -198 229 -66 127 -166 99 -100 197 -198 571 -66 457 -134 361 -424 131 -328 163 -98 63 -100 505 -102 201 -1094 229 -164 65 -230 789 -236 2505 -166 201 -170 163 -64 1139 -66 927 -100 295 -198 723 -100 365 -66 459 -196 3033 -272 199 -66 499 -202 1319 -232 295 -298 131 -362 97 -164 129 -132 65 -98 197 -130 129 -98 261 -130 97 -98 229 -96 425 -66 227 -166 483 -66 163 -326 567 -68 235 -68 67 -66 167 -66 235 -330 425 -164 63 -66 427 -102 167 -66 669 -132 429 -200 65 -102 133 -100 197 -368 +RAW_Data: 65 -134 2481 -228 65 -130 229 -228 763 -136 603 -166 1619 -98 1763 -102 837 -166 321 -66 951 -130 2067 -66 259 -132 1835 -66 437 -102 701 -66 565 -68 363 -70 1113 -66 1989 -164 257 -128 351 -162 1055 -232 265 -170 309 -200 435 -166 833 -102 2467 -132 595 -66 773 -166 1615 -98 131 -96 485 -64 517 -166 197 -68 1231 -68 403 -100 263 -134 233 -100 503 -100 333 -266 729 -66 199 -100 369 -68 1239 -100 197 -68 299 -170 337 -100 825 -132 163 -66 4205 -64 161 -100 635 -66 907 -66 1017 -166 1709 -100 201 -266 657 -68 463 -166 331 -164 293 -64 259 -162 129 -262 597 -134 701 -136 67 -168 235 -136 303 -170 1417 -66 263 -98 857 -100 659 -166 97 -100 2497 -64 2495 -98 719 -128 227 -130 2217 -164 623 -264 719 -134 329 -98 1371 -100 553 -294 165 -66 1163 -100 329 -196 649 -200 1123 -68 263 -100 593 -266 333 -102 1133 -136 131 -132 603 -200 1819 -66 489 -66 563 -266 1113 -230 165 -66 423 -68 335 -100 101 -100 1073 -132 897 -100 101 -100 499 -134 173 -138 763 -238 371 -130 403 -166 203 -102 271 -136 269 -166 99 -168 263 -96 425 -66 331 -234 133 -400 231 -132 453 -66 459 -164 199 -68 237 -132 163 -198 161 -196 265 -132 65 -64 195 -130 357 -164 663 -68 167 -600 131 -98 133 -304 203 -134 433 -98 261 -130 199 -100 237 -100 229 -326 99 -98 331 -132 99 -294 165 -66 303 -134 99 -232 133 -136 99 -68 267 -198 233 -138 67 -166 367 -100 333 -168 267 -200 369 -266 135 -404 1939 -132 231 -160 161 -64 293 -98 331 -132 339 -104 135 -100 197 -430 263 -202 233 -64 195 -162 129 -64 227 -298 265 -68 697 -66 301 -68 231 -300 131 -368 769 -234 265 -98 195 -324 97 -752 229 -126 355 -98 257 -98 287 -64 427 -132 295 -262 197 -170 369 -102 267 -100 169 -68 201 -102 2551 -136 635 -134 639 -134 99 -132 197 -200 371 -66 731 -132 199 -138 733 -304 433 -68 729 -440 197 -68 99 -102 165 -266 261 -164 491 -296 489 -194 257 -164 133 -134 237 -68 335 -98 227 -130 229 -98 295 -98 231 -202 267 -236 233 -136 331 -130 195 -128 261 -430 261 -162 97 -224 99 -130 193 -96 197 -162 229 -396 97 -98 227 -364 267 -100 99 -100 233 -236 697 -164 227 -196 63 -98 327 -230 325 -66 129 -196 95 -98 195 -130 325 -430 131 -194 129 -454 161 -196 235 -68 433 -134 667 -164 355 -236 101 -98 2143 -134 1827 -198 63 -198 65 -64 2859 -64 619 -66 97 -130 3157 -66 679 -194 1491 -98 +RAW_Data: 951 -64 393 -100 955 -132 4715 -100 131 -66 199 -204 1541 -66 929 -130 1347 -166 665 -132 233 -132 67 -102 433 -100 595 -228 997 -66 505 -68 133 -98 231 -68 571 -134 1371 -232 231 -270 135 -102 97 -66 867 -100 269 -68 967 -100 1649 -66 65 -66 951 -68 65 -202 363 -200 779 -102 1449 -294 419 -130 361 -230 1079 -164 163 -260 893 -102 333 -100 533 -166 467 -100 135 -66 135 -202 369 -100 199 -100 269 -134 301 -166 229 -66 101 -134 199 -134 1293 -64 779 -62 831 -66 1243 -68 267 -102 197 -100 395 -98 455 -64 621 -132 877 -98 199 -100 2101 -134 503 -100 2035 -134 735 -236 475 -136 237 -132 133 -134 1229 -100 133 -66 167 -68 2655 -100 1807 -100 1095 -264 825 -98 163 -66 491 -98 161 -128 953 -100 773 -100 131 -66 67 -134 457 -130 63 -64 389 -98 715 -66 425 -300 97 -100 1515 -66 303 -68 99 -98 721 -64 887 -132 65 -132 165 -66 635 -68 2801 -66 1561 -100 751 -98 129 -64 725 -136 201 -100 333 -204 573 -104 1745 -134 99 -66 129 -64 595 -134 167 -102 337 -134 567 -134 1131 -138 1207 -100 269 -68 135 -100 1143 -134 2139 -68 1701 -162 991 -596 431 -66 99 -132 657 -66 391 -320 357 -260 259 -98 429 -66 163 -228 65 -130 227 -66 261 -166 99 -98 131 -366 199 -134 463 -102 201 -98 231 -102 639 -238 301 -568 169 -610 265 -102 841 -198 297 -100 335 -132 263 -266 265 -68 469 -134 267 -68 933 -298 333 -298 729 -168 135 -136 437 -132 1137 -134 199 -68 265 -132 463 -166 129 -130 227 -98 297 -98 65 -132 97 -202 199 -232 305 -66 165 -198 365 -66 99 -98 299 -170 65 -136 301 -232 99 -564 133 -132 233 -170 99 -102 131 -134 65 -204 101 -98 297 -98 167 -762 233 -298 99 -326 395 -66 299 -132 369 -504 333 -98 483 -200 457 -164 63 -164 329 -162 65 -622 231 -268 131 -132 133 -134 131 -134 131 -66 99 -100 231 -66 167 -336 165 -98 197 -100 97 -264 321 -98 521 -132 163 -130 129 -294 297 -134 101 -102 265 -168 497 -68 197 -68 499 -134 269 -398 267 -130 203 -302 65 -498 271 -136 465 -292 131 -294 163 -198 329 -96 129 -98 193 -130 391 -330 165 -134 167 -170 297 -102 133 -136 135 -366 199 -132 423 -132 395 -168 65 -166 401 -98 229 -98 329 -98 99 -130 129 -228 261 -160 127 -426 389 -162 193 -132 131 -100 231 -168 67 -304 201 -68 765 -132 161 -162 193 -64 195 -64 295 -130 787 -98 419 -528 429 -66 363 -134 131 -100 133 -200 331 -98 +RAW_Data: 431 -66 1167 -68 937 -68 1003 -66 99 -132 941 -134 65 -66 365 -274 165 -236 367 -96 557 -134 675 -66 261 -164 127 -96 391 -164 161 -98 391 -292 163 -98 519 -196 165 -98 523 -66 195 -160 3343 -66 661 -100 2589 -136 307 -100 629 -136 639 -100 133 -168 405 -100 267 -66 465 -132 1171 -64 749 -64 165 -98 983 -100 163 -202 537 -66 327 -100 669 -100 401 -236 2885 -164 439 -134 97 -426 1931 -66 1385 -98 715 -98 519 -66 289 -162 97 -360 297 -166 163 -66 289 -66 555 -334 167 -230 429 -102 267 -132 943 -136 401 -68 929 -130 193 -68 467 -198 335 -66 963 -100 597 -132 197 -260 523 -232 1115 -102 1935 -66 1395 -134 305 -100 99 -66 199 -66 1071 -66 2357 -66 367 -498 769 -234 163 -130 191 -64 1211 -200 133 -102 201 -100 561 -366 361 -98 195 -100 537 -64 165 -196 1041 -332 133 -102 441 -230 4217 -66 1033 -66 167 -66 933 -100 565 -66 331 -164 673 -104 441 -66 533 -66 2095 -164 525 -66 297 -170 965 -198 421 -100 663 -832 65 -100 331 -164 231 -166 135 -168 237 -466 761 -134 891 -196 791 -198 257 -160 161 -98 293 -66 1081 -98 229 -130 327 -66 1301 -200 331 -166 101 -66 461 -100 2619 -132 1663 -98 1609 -134 499 -332 165 -370 67 -264 97 -96 259 -98 701 -402 197 -128 527 -236 233 -102 167 -134 303 -134 99 -166 299 -132 165 -200 467 -68 305 -168 207 -102 465 -102 729 -136 101 -374 327 -96 259 -98 467 -202 65 -66 673 -98 335 -404 135 -66 339 -204 99 -366 233 -68 365 -166 133 -102 867 -198 163 -162 163 -294 463 -332 165 -68 269 -268 331 -100 131 -166 299 -132 231 -400 263 -164 131 -266 267 -264 367 -66 371 -134 229 -104 267 -232 67 -466 265 -100 101 -100 165 -200 65 -200 301 -66 199 -168 233 -98 267 -66 67 -134 261 -196 261 -234 427 -294 65 -194 193 -66 259 -132 849 -96 63 -198 167 -294 95 -98 361 -164 261 -196 131 -132 437 -100 597 -262 327 -162 295 -98 295 -164 259 -196 425 -230 321 -66 195 -66 261 -496 99 -200 529 -132 133 -966 133 -132 165 -66 63 -128 491 -402 65 -262 299 -66 299 -202 265 -100 99 -668 97 -134 65 -100 101 -66 65 -266 691 -66 431 -166 167 -134 199 -370 899 -134 99 -100 1093 -166 163 -166 399 -98 327 -100 99 -168 135 -200 133 -202 429 -98 65 -98 197 -556 65 -66 97 -326 331 -166 333 -200 135 -100 235 -234 265 -98 65 -68 135 -66 335 -66 133 -298 99 -66 233 -164 435 -232 97 -132 97 -392 +RAW_Data: 99 -198 819 -66 1235 -98 321 -132 1091 -66 1307 -98 3059 -164 3305 -64 227 -98 591 -98 129 -66 229 -98 2143 -98 939 -68 563 -100 361 -232 945 -164 257 -96 229 -230 387 -64 195 -130 981 -294 587 -162 193 -98 1337 -66 293 -98 2665 -66 297 -98 647 -66 459 -132 491 -164 489 -96 595 -66 899 -66 837 -64 1151 -196 259 -98 357 -164 891 -132 1359 -134 197 -98 97 -98 261 -64 229 -96 461 -136 693 -100 201 -98 865 -66 599 -100 517 -132 709 -66 293 -298 655 -66 197 -130 129 -66 197 -98 4291 -66 673 -66 667 -132 1473 -132 133 -104 99 -66 163 -168 333 -134 1743 -132 1097 -132 99 -68 167 -602 1323 -352 99 -166 753 -98 423 -98 97 -66 1317 -228 1309 -98 1849 -66 1939 -132 601 -100 665 -100 1875 -66 695 -132 425 -66 425 -66 263 -134 165 -134 99 -98 829 -66 601 -166 131 -102 565 -66 301 -100 1099 -100 601 -138 533 -66 667 -234 561 -66 99 -68 2741 -98 199 -100 531 -168 101 -434 1027 -68 431 -66 403 -132 99 -98 565 -132 135 -100 399 -166 271 -236 233 -166 197 -366 99 -66 99 -168 503 -66 199 -170 207 -100 673 -368 99 -66 263 -168 133 -98 397 -268 337 -66 131 -132 231 -132 501 -134 99 -168 567 -138 103 -136 267 -298 231 -134 197 -160 321 -332 231 -98 131 -164 257 -64 163 -328 395 -66 331 -202 65 -168 133 -68 167 -100 233 -102 335 -66 197 -326 1101 -132 589 -100 811 -132 399 -136 269 -102 497 -66 559 -100 129 -98 855 -68 637 -102 65 -200 875 -68 233 -166 167 -66 529 -202 235 -102 231 -66 1237 -66 733 -98 1723 -132 101 -100 297 -66 829 -232 197 -100 367 -134 169 -166 167 -434 633 -100 235 -200 131 -134 233 -100 131 -100 331 -134 495 -432 65 -528 161 -130 295 -132 337 -136 133 -166 165 -100 269 -240 201 -336 133 -166 165 -238 199 -202 431 -434 99 -134 501 -166 231 -96 559 -202 167 -66 717 -98 987 -198 65 -64 163 -64 227 -98 555 -164 199 -64 361 -66 163 -98 129 -162 97 -130 161 -460 197 -230 681 -98 197 -98 329 -100 267 -266 291 -264 65 -100 329 -100 459 -200 363 -98 165 -134 231 -134 301 -134 231 -302 99 -132 101 -134 267 -136 233 -68 393 -422 163 -166 361 -166 99 -134 365 -134 133 -336 401 -66 495 -132 401 -168 133 -402 501 -136 1093 -862 165 -132 293 -300 289 -66 131 -164 391 -134 99 -360 359 -130 323 -200 423 -98 195 -162 295 -132 161 -98 129 -782 131 -426 227 -64 259 -166 63 -160 323 -98 261 -230 +RAW_Data: 231 -66 921 -66 355 -64 1019 -98 227 -258 163 -66 597 -232 1313 -132 163 -404 467 -236 901 -164 483 -98 195 -96 489 -134 103 -238 169 -66 67 -68 299 -100 497 -68 65 -134 1635 -304 1153 -100 539 -168 265 -200 499 -166 535 -100 397 -168 931 -100 131 -66 631 -134 897 -270 1233 -100 65 -132 131 -334 663 -66 163 -66 131 -132 705 -98 571 -200 433 -100 237 -234 229 -132 1627 -66 569 -100 715 -66 1863 -272 265 -68 301 -98 465 -68 97 -134 99 -66 395 -136 1405 -66 529 -132 63 -196 579 -132 413 -260 129 -136 101 -166 1201 -134 833 -134 393 -66 335 -172 201 -68 1027 -96 753 -64 815 -66 97 -64 1341 -132 289 -160 127 -66 99 -228 1083 -96 163 -66 259 -64 159 -98 2409 -168 767 -200 367 -66 1675 -66 1067 -98 3407 -200 99 -66 1403 -166 99 -134 439 -200 329 -136 599 -66 637 -66 835 -66 1099 -98 99 -66 463 -166 165 -100 461 -164 3037 -66 655 -66 97 -98 229 -130 355 -132 1443 -66 527 -98 881 -98 229 -162 127 -96 583 -64 65 -162 489 -166 885 -194 257 -98 1539 -66 293 -166 229 -132 655 -98 757 -49522 271 -758 689 -1264 737 -670 293 -1152 811 -1144 341 -664 773 -678 327 -1118 807 -1144 835 -1146 781 -1126 873 -1096 347 -622 877 -624 321 -1106 843 -1098 871 -1098 843 -1106 379 -610 841 -584 381 -1122 365 -602 845 -1116 837 -610 381 -1056 889 -1078 383 -614 827 -1110 877 -592 353 -1108 845 -1120 839 -1120 347 -602 849 -1110 865 -612 361 -1072 869 -1114 351 -618 861 -618 343 -1090 853 -1106 387 -618 797 -674 347 -1084 389 -574 867 -584 381 -1114 841 -1102 845 -1116 839 -1112 843 -1098 875 -1086 383 -584 865 -588 375 -1100 861 -1112 851 -1084 853 -1108 847 -1106 381 -584 857 -610 383 -1080 357 -602 871 -602 385 -1084 383 -616 823 -610 373 -1086 381 -590 871 -1084 839 -628 353 -1102 875 -1100 349 -9404 875 -1060 871 -1086 887 -1088 879 -1058 863 -1086 855 -1132 845 -1078 871 -1076 857 -1098 881 -1082 861 -1088 843 -1120 853 -1074 879 -1074 879 -1068 889 -614 341 -1090 387 -616 863 -624 345 -1088 391 -590 857 -612 385 -1058 393 -596 843 -1088 889 -1078 879 -578 387 -1082 875 -1076 415 -550 881 -1070 877 -592 391 -1114 821 -1104 373 -620 821 -624 361 -1072 903 -1086 855 -1092 843 -1086 905 -1054 387 -614 863 -618 347 -1088 853 -1114 845 -1090 867 -1070 381 -610 885 -584 385 -1052 407 -578 877 -1052 899 -600 389 -1048 907 -1074 383 -586 877 -1072 877 -594 359 -1076 875 -1082 891 -1088 363 -616 855 -1084 857 -592 381 -1088 883 -1086 385 -572 +RAW_Data: 889 -624 353 -1082 853 -1096 379 -594 853 -624 353 -1092 417 -582 847 -612 385 -1076 847 -1080 883 -1052 913 -1044 907 -1076 849 -1088 383 -602 867 -616 361 -1068 901 -1072 865 -1104 831 -1080 879 -1098 397 -586 855 -626 355 -1084 381 -592 873 -616 351 -1084 385 -624 821 -620 359 -1086 387 -584 883 -1086 877 -592 355 -1106 853 -1086 387 -69570 97 -100 99 -2620 131 -636 333 -102 235 -236 67 -68 363 -66 201 -100 567 -102 267 -164 101 -134 65 -68 197 -68 297 -166 671 -100 469 -336 165 -100 201 -66 169 -230 169 -204 329 -624 67 -98 265 -232 193 -168 299 -100 235 -138 101 -370 165 -294 333 -622 231 -130 129 -130 353 -132 195 -162 359 -164 67 -68 333 -100 133 -688 235 -236 497 -198 293 -98 129 -296 293 -164 229 -128 229 -132 193 -400 165 -66 163 -98 361 -164 355 -196 587 -164 131 -98 263 -554 99 -130 129 -130 191 -464 99 -132 67 -100 167 -604 329 -66 199 -68 133 -102 163 -66 2971 -132 785 -66 329 -96 323 -100 201 -136 301 -66 1959 -166 867 -134 467 -66 297 -100 835 -100 753 -166 165 -64 67 -370 335 -66 559 -232 165 -334 65 -162 129 -354 163 -64 131 -134 265 -300 263 -132 267 -296 327 -198 99 -132 535 -132 469 -866 231 -860 99 -232 503 -134 99 -198 233 -134 267 -200 97 -358 297 -164 259 -98 227 -166 135 -66 323 -100 97 -294 131 -164 129 -98 295 -96 129 -426 299 -100 67 -102 623 -100 163 -194 127 -360 563 -134 199 -428 493 -98 229 -130 257 -64 165 -100 131 -98 163 -692 357 -64 161 -98 321 -64 389 -230 65 -692 227 -130 261 -132 231 -162 287 -298 97 -460 393 -130 301 -168 331 -100 269 -202 101 -134 201 -102 99 -132 199 -204 235 -664 65 -562 133 -328 463 -100 291 -194 159 -162 227 -98 293 -328 165 -128 227 -574 535 -332 197 -168 65 -300 131 -66 389 -1078 131 -64 259 -64 223 -98 257 -164 63 -328 433 -134 65 -602 131 -68 333 -136 369 -66 297 -264 427 -66 97 -130 429 -102 133 -136 203 -240 167 -236 329 -526 67 -132 133 -168 331 -360 65 -66 331 -296 267 -134 469 -132 595 -230 661 -662 299 -100 265 -200 203 -168 801 -100 133 -68 399 -132 99 -100 161 -390 65 -298 65 -98 261 -130 161 -128 257 -66 67 -134 621 -98 227 -328 99 -230 129 -294 193 -96 195 -318 425 -526 129 -196 163 -162 65 -132 293 -130 63 -66 325 -128 63 -130 293 -66 199 -200 269 -206 133 -198 325 -98 163 -100 97 -98 261 -164 67 -98 167 -430 131 -494 131 -164 +RAW_Data: 97 -98 861 -66 1199 -166 231 -100 651 -166 197 -104 439 -98 131 -64 493 -98 883 -96 99 -98 3327 -66 131 -264 733 -134 2133 -166 131 -102 303 -136 535 -134 701 -98 355 -228 131 -202 99 -134 99 -100 791 -166 169 -202 671 -100 741 -100 263 -66 165 -68 935 -132 197 -198 673 -100 605 -66 1457 -98 1195 -166 2347 -134 505 -100 1469 -66 391 -100 229 -100 1171 -98 939 -100 459 -170 369 -134 231 -162 127 -98 95 -66 195 -98 195 -66 299 -100 331 -98 65 -232 369 -132 201 -68 167 -166 1481 -102 501 -160 1257 -66 2307 -64 623 -164 2079 -66 1101 -98 423 -64 659 -68 431 -136 99 -100 435 -130 167 -168 835 -200 135 -104 133 -100 503 -68 1437 -232 821 -132 357 -96 463 -66 263 -64 683 -132 165 -96 655 -166 3939 -100 1169 -132 2443 -98 197 -132 425 -234 233 -162 1043 -66 197 -100 2793 -134 167 -104 675 -100 197 -134 1367 -102 763 -132 265 -230 133 -102 365 -100 167 -66 1069 -66 837 -100 295 -160 97 -64 129 -132 617 -164 197 -100 133 -136 337 -172 133 -66 557 -98 951 -66 263 -130 587 -66 729 -196 335 -166 933 -432 369 -100 199 -296 225 -98 355 -66 129 -64 557 -98 289 -66 355 -128 193 -162 267 -134 299 -98 165 -170 303 -640 1031 -134 99 -66 135 -68 771 -166 171 -104 201 -134 131 -68 635 -428 661 -292 749 -430 1161 -100 905 -98 65 -98 657 -262 2837 -132 67 -66 265 -132 631 -66 1037 -296 97 -98 1703 -302 367 -100 505 -232 497 -362 333 -134 591 -100 755 -232 67 -130 587 -66 231 -168 65 -332 99 -66 267 -232 393 -134 65 -132 131 -428 133 -200 165 -202 199 -168 165 -102 269 -100 333 -852 201 -134 233 -202 65 -200 563 -768 265 -136 169 -102 169 -598 333 -202 267 -134 267 -328 163 -130 625 -500 199 -200 99 -270 65 -134 65 -198 65 -100 99 -596 493 -66 99 -66 331 -232 103 -136 373 -168 831 -170 65 -672 163 -102 133 -136 331 -100 333 -234 101 -100 99 -200 99 -100 201 -302 199 -600 301 -202 135 -134 705 -166 435 -530 97 -198 131 -198 195 -66 163 -392 293 -66 295 -370 229 -198 65 -100 405 -134 165 -134 133 -170 337 -236 205 -274 267 -134 329 -132 195 -132 503 -132 133 -136 133 -334 197 -196 299 -168 101 -100 233 -100 439 -134 301 -332 331 -298 433 -406 433 -68 167 -100 203 -100 101 -102 99 -328 397 -234 205 -168 133 -364 63 -202 397 -198 95 -394 267 -134 569 -66 201 -102 133 -136 101 -102 99 -132 99 -196 197 -498 197 -102 135 -170 +RAW_Data: 331 -164 63 -162 1267 -66 163 -130 129 -66 725 -164 231 -64 853 -66 101 -134 199 -102 99 -68 365 -66 357 -130 815 -64 357 -98 97 -98 97 -66 65 -466 231 -172 3749 -66 849 -130 917 -64 327 -64 1013 -98 555 -332 795 -100 571 -132 769 -132 401 -134 1297 -134 377 -138 435 -100 401 -100 667 -100 1761 -66 667 -66 1533 -236 233 -98 885 -130 457 -66 999 -66 165 -66 833 -134 695 -166 501 -66 499 -200 329 -64 197 -134 441 -100 2099 -98 491 -134 197 -130 2225 -132 65 -100 689 -64 193 -160 159 -96 195 -98 323 -164 259 -98 535 -472 771 -66 665 -270 665 -66 595 -266 2191 -64 643 -98 1287 -98 741 -100 233 -200 569 -194 261 -68 637 -100 97 -66 491 -158 395 -138 1017 -66 627 -262 559 -64 327 -98 263 -134 99 -102 201 -102 337 -66 167 -68 679 -100 471 -134 195 -66 133 -202 693 -96 197 -98 391 -164 99 -98 3883 -194 461 -100 237 -168 1891 -68 301 -68 969 -166 1439 -294 551 -130 389 -98 99 -196 167 -102 505 -66 569 -234 901 -98 407 -136 469 -66 769 -98 769 -166 1263 -266 297 -98 1701 -200 203 -168 329 -232 65 -100 329 -164 803 -100 135 -200 233 -166 135 -272 265 -134 197 -100 133 -134 539 -232 197 -396 165 -366 263 -68 233 -102 365 -132 233 -100 135 -266 199 -234 167 -232 97 -524 127 -128 389 -98 305 -364 261 -130 257 -162 589 -464 361 -66 229 -134 161 -100 203 -432 265 -66 199 -66 199 -366 229 -236 99 -134 99 -100 131 -168 133 -100 131 -236 267 -132 297 -264 291 -132 167 -234 65 -100 199 -66 333 -730 237 -440 365 -102 99 -100 99 -132 99 -100 1429 -134 427 -100 97 -100 131 -164 799 -170 1077 -100 431 -66 133 -168 737 -134 197 -230 65 -102 803 -132 491 -98 429 -198 471 -134 365 -66 299 -236 65 -66 2837 -102 399 -64 585 -64 523 -196 97 -98 295 -196 555 -160 261 -500 299 -396 333 -236 133 -68 327 -100 199 -204 699 -66 701 -100 65 -164 65 -370 195 -196 97 -66 193 -130 129 -360 195 -130 231 -96 291 -64 455 -228 293 -196 291 -162 97 -194 621 -130 847 -66 395 -66 161 -128 193 -130 293 -98 231 -170 67 -134 297 -360 167 -266 263 -526 263 -132 229 -98 191 -160 159 -100 721 -234 101 -100 99 -130 259 -258 265 -632 687 -164 133 -134 631 -100 199 -102 165 -560 299 -200 265 -332 431 -870 99 -266 503 -364 135 -66 269 -68 499 -100 265 -102 263 -102 569 -234 719 -132 99 -196 419 -262 163 -688 95 -66 165 -128 95 -66 +RAW_Data: 295 -98 987 -196 517 -100 489 -66 355 -132 563 -198 867 -134 1413 -134 541 -134 767 -100 193 -98 1799 -102 467 -134 299 -96 323 -66 261 -100 259 -66 229 -96 851 -66 369 -266 469 -66 101 -98 163 -136 267 -432 859 -130 523 -66 197 -134 1027 -132 227 -194 393 -98 807 -166 235 -100 133 -66 165 -102 133 -136 371 -162 1411 -132 865 -200 471 -100 133 -68 299 -66 633 -98 329 -234 401 -98 1505 -132 133 -134 331 -262 163 -66 261 -98 289 -64 201 -68 1055 -96 391 -66 951 -298 265 -202 297 -66 401 -68 131 -100 1733 -98 941 -66 803 -98 847 -64 3701 -100 721 -160 357 -166 1799 -66 329 -100 99 -102 363 -198 167 -136 197 -66 567 -66 199 -236 1247 -166 2455 -68 1107 -200 235 -100 2355 -130 913 -98 877 -98 163 -196 97 -66 427 -100 801 -134 867 -98 263 -68 441 -134 561 -98 1671 -134 865 -68 935 -132 163 -102 975 -66 1343 -132 1339 -134 369 -100 1107 -66 1167 -168 631 -232 835 -66 1027 -132 333 -166 265 -98 1207 -98 223 -98 455 -64 2095 -134 933 -136 233 -68 335 -136 305 -100 1737 -66 427 -100 263 -130 323 -66 227 -66 717 -100 265 -100 65 -128 355 -66 367 -132 95 -230 229 -100 131 -64 493 -132 291 -396 393 -130 259 -196 227 -288 397 -68 229 -430 99 -302 237 -700 65 -66 65 -100 133 -200 101 -336 133 -166 237 -202 67 -302 67 -68 333 -132 263 -102 267 -296 163 -166 233 -168 363 -64 295 -298 537 -166 431 -200 431 -166 63 -258 363 -164 563 -234 199 -68 299 -100 325 -754 295 -196 65 -98 165 -132 301 -134 131 -134 97 -68 405 -68 233 -134 271 -134 67 -168 101 -136 133 -366 99 -132 67 -132 265 -200 233 -100 201 -136 101 -66 263 -132 129 -66 293 -582 263 -132 1103 -134 203 -168 97 -66 197 -264 131 -168 133 -132 65 -134 199 -134 101 -100 131 -436 99 -232 97 -398 231 -362 65 -202 301 -396 297 -98 199 -134 265 -164 101 -168 267 -102 405 -170 99 -102 397 -132 97 -98 295 -98 1179 -100 135 -136 131 -134 765 -134 465 -168 439 -232 403 -100 65 -134 931 -100 169 -136 237 -68 231 -234 199 -68 401 -134 541 -166 429 -166 1607 -368 533 -66 363 -66 133 -134 433 -166 297 -238 201 -100 201 -170 199 -134 273 -136 99 -134 167 -238 133 -66 265 -134 165 -132 165 -132 97 -228 723 -198 415 -64 491 -298 257 -66 231 -192 225 -96 227 -98 193 -96 521 -198 65 -66 231 -166 163 -98 465 -66 133 -132 195 -130 225 -162 521 -130 63 -66 199 -228 +RAW_Data: 817 -162 449 -160 719 -198 469 -68 133 -68 1101 -132 593 -230 1105 -100 131 -134 231 -66 329 -196 685 -96 557 -68 1263 -68 101 -68 397 -100 65 -66 625 -66 97 -132 1099 -66 493 -66 757 -98 1151 -66 303 -134 1901 -66 99 -100 665 -262 991 -98 791 -66 1925 -168 865 -232 835 -98 505 -102 99 -100 535 -100 169 -134 427 -132 863 -68 167 -134 975 -100 133 -268 1339 -100 1453 -66 1445 -162 195 -64 3623 -66 237 -68 1063 -308 1449 -98 1111 -132 167 -102 855 -270 199 -134 297 -134 267 -168 863 -234 637 -66 567 -230 99 -200 3325 -198 845 -66 289 -66 131 -66 815 -130 1093 -100 167 -100 429 -98 1703 -166 195 -64 971 -98 163 -192 195 -168 439 -132 329 -132 67 -134 67 -134 1591 -168 407 -100 867 -68 399 -134 661 -100 663 -66 237 -136 395 -232 131 -66 695 -100 627 -264 913 -66 1083 -98 287 -66 199 -132 335 -100 1031 -68 99 -100 3815 -98 165 -66 129 -98 163 -128 563 -98 779 -96 223 -64 161 -164 2025 -66 1741 -172 101 -136 203 -102 665 -100 475 -64 167 -100 637 -98 997 -170 1207 -136 233 -166 233 -168 635 -132 199 -100 235 -270 199 -98 131 -102 169 -170 293 -98 323 -164 427 -334 233 -168 267 -68 369 -100 263 -368 101 -66 665 -98 265 -100 133 -100 99 -168 133 -66 133 -132 133 -66 269 -134 435 -68 267 -136 271 -500 163 -100 163 -166 355 -132 97 -98 323 -194 63 -688 463 -130 97 -396 65 -100 357 -194 461 -98 161 -130 223 -162 165 -352 461 -300 267 -166 233 -464 329 -100 293 -362 163 -228 289 -66 229 -66 195 -162 325 -66 261 -98 127 -424 299 -302 367 -68 265 -272 429 -98 161 -98 393 -296 65 -130 161 -196 261 -66 473 -234 97 -98 263 -160 323 -98 67 -132 697 -298 99 -134 233 -202 97 -134 301 -200 307 -100 101 -134 865 -166 231 -202 233 -100 301 -170 169 -102 169 -200 65 -98 595 -166 231 -234 661 -66 473 -334 165 -304 365 -266 97 -502 363 -134 133 -236 65 -100 99 -134 99 -170 235 -66 333 -100 195 -100 133 -300 133 -102 301 -304 65 -100 99 -100 131 -202 135 -134 65 -200 363 -66 263 -498 67 -68 295 -194 321 -368 435 -100 97 -664 99 -100 569 -66 133 -66 67 -134 199 -136 101 -68 301 -68 405 -198 133 -132 581 -132 165 -98 159 -98 197 -66 229 -130 131 -294 133 -96 423 -100 427 -300 357 -132 291 -64 95 -194 455 -98 263 -100 359 -196 65 -162 227 -162 157 -96 157 -230 589 -132 325 -134 535 -66 267 -100 135 -302 +RAW_Data: 131 -134 599 -166 393 -98 369 -236 197 -100 401 -232 569 -134 135 -70 337 -134 101 -136 135 -100 1895 -66 401 -170 503 -66 1633 -66 601 -66 355 -96 683 -100 729 -68 133 -132 433 -68 569 -100 133 -68 201 -132 835 -100 465 -68 527 -98 193 -200 1129 -166 535 -100 199 -98 259 -132 227 -64 1597 -98 261 -192 753 -100 911 -66 667 -298 131 -100 263 -66 1051 -230 787 -66 935 -66 233 -98 885 -236 431 -66 197 -162 521 -68 167 -196 263 -96 589 -98 517 -66 1439 -64 777 -66 3219 -132 679 -134 205 -68 507 -198 749 -200 199 -168 167 -100 133 -134 201 -68 731 -66 495 -198 737 -66 237 -68 135 -100 167 -234 1535 -68 873 -66 373 -66 67 -232 297 -68 65 -66 1095 -68 327 -130 63 -132 1715 -66 2261 -100 321 -132 197 -164 457 -232 1291 -132 405 -68 1001 -68 1133 -272 471 -66 99 -134 1403 -68 167 -68 1091 -336 933 -134 1207 -132 265 -68 267 -66 99 -366 265 -66 1469 -258 367 -168 429 -132 129 -66 491 -132 343 -100 65 -100 263 -136 199 -164 273 -204 791 -100 901 -66 167 -98 165 -64 559 -132 619 -132 1087 -128 2283 -398 1467 -164 259 -130 1927 -130 421 -98 1085 -66 705 -68 1843 -168 875 -170 203 -136 341 -640 199 -66 133 -554 161 -196 63 -66 521 -292 163 -160 95 -158 127 -192 197 -100 587 -130 397 -662 261 -66 193 -130 259 -66 361 -64 459 -98 197 -560 655 -130 389 -66 1135 -100 133 -130 131 -98 1011 -100 561 -66 685 -164 457 -132 2469 -200 609 -66 665 -66 67 -132 327 -200 1657 -134 919 -132 651 -100 327 -230 191 -130 263 -358 95 -130 549 -98 99 -68 299 -100 461 -132 99 -472 165 -134 99 -66 99 -132 399 -102 169 -102 697 -166 233 -132 333 -632 197 -164 865 -266 101 -68 533 -166 299 -100 163 -228 259 -66 327 -200 65 -66 229 -100 363 -230 197 -336 165 -102 893 -300 65 -132 231 -370 265 -230 99 -98 229 -518 199 -100 401 -724 225 -98 63 -96 231 -64 291 -292 65 -98 131 -98 159 -158 127 -194 161 -292 65 -98 133 -66 297 -66 303 -168 97 -168 231 -234 269 -532 135 -168 99 -168 301 -528 99 -506 199 -368 399 -132 329 -372 99 -68 133 -264 197 -100 201 -200 67 -134 131 -270 133 -134 133 -198 327 -200 65 -100 331 -262 161 -166 469 -534 167 -738 131 -100 367 -232 101 -100 265 -604 65 -170 99 -166 299 -102 169 -132 99 -398 229 -330 197 -166 335 -366 97 -98 131 -200 269 -100 199 -168 131 -134 537 -98 265 -100 335 -236 99 -366 +RAW_Data: 459 -100 453 -130 419 -130 519 -96 63 -130 2077 -66 767 -64 127 -134 1961 -296 529 -202 637 -134 527 -100 201 -68 633 -66 163 -360 1029 -68 765 -100 867 -66 503 -100 131 -66 841 -98 165 -68 237 -66 509 -100 501 -302 235 -66 99 -164 227 -130 551 -196 327 -66 1571 -132 99 -68 867 -66 163 -96 161 -130 129 -130 549 -130 487 -166 1801 -66 229 -66 197 -232 325 -66 425 -198 131 -64 295 -166 735 -66 533 -98 227 -130 129 -262 425 -100 263 -66 129 -132 97 -168 971 -170 405 -68 199 -134 475 -202 297 -98 1445 -98 395 -196 161 -66 225 -134 1803 -100 473 -102 1499 -66 199 -100 701 -132 165 -68 133 -102 303 -98 735 -102 805 -100 827 -100 235 -100 65 -266 637 -68 693 -66 1383 -228 819 -66 233 -304 435 -198 203 -136 1135 -270 1709 -64 227 -64 581 -134 505 -66 2203 -64 293 -64 753 -66 551 -132 747 -64 1303 -64 463 -66 229 -102 1877 -266 871 -166 1357 -64 819 -66 465 -198 693 -68 165 -64 95 -128 3785 -132 1465 -100 299 -102 329 -164 595 -134 1029 -66 299 -168 1263 -166 331 -68 967 -100 101 -102 603 -260 165 -132 467 -66 233 -66 235 -102 475 -100 135 -68 301 -134 297 -98 131 -102 269 -466 99 -134 237 -166 135 -168 203 -102 265 -68 503 -66 233 -66 637 -134 101 -200 199 -166 293 -554 361 -328 367 -264 533 -238 167 -68 135 -170 99 -300 591 -298 133 -236 299 -66 231 -368 263 -232 435 -136 133 -102 133 -200 133 -134 163 -134 167 -168 299 -66 265 -100 133 -240 135 -132 263 -170 269 -200 501 -396 263 -98 227 -132 129 -292 427 -66 165 -102 627 -602 99 -66 301 -168 199 -100 563 -330 165 -134 233 -136 65 -332 499 -100 131 -232 325 -96 65 -132 195 -98 393 -624 323 -68 133 -98 195 -162 231 -100 263 -132 231 -102 133 -236 99 -236 231 -166 65 -102 133 -268 101 -102 299 -136 267 -164 493 -64 229 -258 291 -326 263 -198 391 -134 167 -202 365 -594 133 -102 201 -134 503 -396 429 -204 169 -400 197 -170 267 -132 403 -466 297 -98 469 -234 395 -132 233 -100 165 -100 165 -66 197 -68 297 -166 501 -134 133 -100 65 -166 631 -68 297 -134 199 -100 165 -68 299 -266 133 -66 165 -100 231 -490 557 -134 371 -164 299 -170 733 -164 239 -334 335 -66 299 -300 199 -170 103 -100 233 -102 641 -168 65 -100 995 -66 265 -160 259 -130 129 -226 425 -100 355 -726 97 -688 99 -66 233 -266 299 -942 167 -102 167 -166 65 -100 367 -136 99 -134 199 -134 267 -164 +RAW_Data: 67 -68 233 -66 899 -66 163 -96 485 -98 355 -130 943 -100 235 -168 499 -104 1367 -98 297 -100 635 -68 1169 -100 67 -134 835 -264 959 -164 129 -98 419 -196 589 -66 421 -66 1717 -100 133 -100 265 -134 227 -356 455 -166 163 -66 1055 -100 1455 -134 463 -98 2191 -132 295 -132 335 -66 709 -64 619 -98 959 -68 835 -170 603 -134 1033 -134 635 -168 759 -232 397 -198 397 -164 1267 -166 257 -198 1295 -100 239 -104 563 -204 335 -198 203 -68 901 -68 1255 -134 1697 -66 793 -66 1691 -68 201 -100 765 -66 165 -132 131 -230 131 -66 917 -66 335 -338 231 -170 827 -98 199 -136 301 -196 65 -98 199 -200 765 -134 403 -98 333 -68 1691 -132 2565 -64 569 -170 1255 -264 65 -132 1243 -132 2527 -66 259 -66 1739 -100 1309 -198 167 -238 337 -66 131 -68 1973 -362 299 -100 1387 -96 129 -164 423 -230 3875 -96 4283 -98 165 -98 515 -134 469 -68 171 -102 1163 -100 65 -298 461 -66 367 -136 205 -168 371 -98 491 -164 161 -262 1093 -100 299 -100 269 -334 1205 -98 63 -98 261 -64 457 -98 diff --git a/lib/subghz/protocols/oregon2.c b/lib/subghz/protocols/oregon2.c new file mode 100644 index 00000000000..84bb38be494 --- /dev/null +++ b/lib/subghz/protocols/oregon2.c @@ -0,0 +1,324 @@ +#include "oregon2.h" +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" +#include +#include +#include + +#define TAG "SubGhzProtocolOregon2" + +static const SubGhzBlockConst oregon2_const = { + .te_long = 1000, + .te_short = 500, + .te_delta = 200, + .min_count_bit_for_found = 32, +}; + +#define OREGON2_PREAMBLE_BITS 19 +#define OREGON2_PREAMBLE_MASK ((1 << (OREGON2_PREAMBLE_BITS + 1)) - 1) +#define OREGON2_SENSOR_ID(d) (((d) >> 16) & 0xFFFF) +#define OREGON2_CHECKSUM_BITS 8 + +// 15 ones + 0101 (inverted A) +#define OREGON2_PREAMBLE 0b1111111111111110101 + +// bit indicating the low battery +#define OREGON2_FLAG_BAT_LOW 0x4 + +struct SubGhzProtocolDecoderOregon2 { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; + ManchesterState manchester_state; + bool prev_bit; + bool have_bit; + + uint8_t var_bits; + uint32_t var_data; +}; + +typedef struct SubGhzProtocolDecoderOregon2 SubGhzProtocolDecoderOregon2; + +typedef enum { + Oregon2DecoderStepReset = 0, + Oregon2DecoderStepFoundPreamble, + Oregon2DecoderStepVarData, +} Oregon2DecoderStep; + +void* subghz_protocol_decoder_oregon2_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderOregon2* instance = malloc(sizeof(SubGhzProtocolDecoderOregon2)); + instance->base.protocol = &subghz_protocol_oregon2; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_oregon2_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderOregon2* instance = context; + free(instance); +} + +void subghz_protocol_decoder_oregon2_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderOregon2* instance = context; + instance->decoder.parser_step = Oregon2DecoderStepReset; + instance->decoder.decode_data = 0UL; + instance->decoder.decode_count_bit = 0; + manchester_advance( + instance->manchester_state, ManchesterEventReset, &instance->manchester_state, NULL); + instance->have_bit = false; + instance->var_data = 0; + instance->var_bits = 0; +} + +static ManchesterEvent level_and_duration_to_event(bool level, uint32_t duration) { + bool is_long = false; + + if(DURATION_DIFF(duration, oregon2_const.te_long) < oregon2_const.te_delta) { + is_long = true; + } else if(DURATION_DIFF(duration, oregon2_const.te_short) < oregon2_const.te_delta) { + is_long = false; + } else { + return ManchesterEventReset; + } + + if(level) + return is_long ? ManchesterEventLongHigh : ManchesterEventShortHigh; + else + return is_long ? ManchesterEventLongLow : ManchesterEventShortLow; +} + +// From sensor id code return amount of bits in variable section +static uint8_t oregon2_sensor_id_var_bits(uint16_t sensor_id) { + if(sensor_id == 0xEC40) return 16; + return 0; +} + +void subghz_protocol_decoder_oregon2_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderOregon2* instance = context; + // oregon v2.1 signal is inverted + ManchesterEvent event = level_and_duration_to_event(!level, duration); + bool data; + + // low-level bit sequence decoding + if(event == ManchesterEventReset) { + instance->decoder.parser_step = Oregon2DecoderStepReset; + instance->have_bit = false; + instance->decoder.decode_data = 0UL; + instance->decoder.decode_count_bit = 0; + } + if(manchester_advance(instance->manchester_state, event, &instance->manchester_state, &data)) { + if(instance->have_bit) { + if(!instance->prev_bit && data) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + } else if(instance->prev_bit && !data) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + } else { + subghz_protocol_decoder_oregon2_reset(context); + } + instance->have_bit = false; + } else { + instance->prev_bit = data; + instance->have_bit = true; + } + } + + switch(instance->decoder.parser_step) { + case Oregon2DecoderStepReset: + // waiting for fixed oregon2 preamble + if(instance->decoder.decode_count_bit >= OREGON2_PREAMBLE_BITS && + ((instance->decoder.decode_data & OREGON2_PREAMBLE_MASK) == OREGON2_PREAMBLE)) { + instance->decoder.parser_step = Oregon2DecoderStepFoundPreamble; + instance->decoder.decode_count_bit = 0; + instance->decoder.decode_data = 0UL; + } + break; + case Oregon2DecoderStepFoundPreamble: + // waiting for fixed oregon2 data + if(instance->decoder.decode_count_bit == 32) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + instance->decoder.decode_data = 0UL; + instance->decoder.decode_count_bit = 0; + + // reverse nibbles in decoded data + instance->generic.data = (instance->generic.data & 0x55555555) << 1 | + (instance->generic.data & 0xAAAAAAAA) >> 1; + instance->generic.data = (instance->generic.data & 0x33333333) << 2 | + (instance->generic.data & 0xCCCCCCCC) >> 2; + + instance->var_bits = + oregon2_sensor_id_var_bits(OREGON2_SENSOR_ID(instance->generic.data)); + + if(!instance->var_bits) { + // sensor is not supported, stop decoding, but showing the decoded fixed part + instance->decoder.parser_step = Oregon2DecoderStepReset; + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } else { + instance->decoder.parser_step = Oregon2DecoderStepVarData; + } + } + break; + case Oregon2DecoderStepVarData: + // waiting for variable (sensor-specific data) + if(instance->decoder.decode_count_bit == instance->var_bits + OREGON2_CHECKSUM_BITS) { + instance->var_data = instance->decoder.decode_data & 0xFFFFFFFF; + + // reverse nibbles in var data + instance->var_data = (instance->var_data & 0x55555555) << 1 | + (instance->var_data & 0xAAAAAAAA) >> 1; + instance->var_data = (instance->var_data & 0x33333333) << 2 | + (instance->var_data & 0xCCCCCCCC) >> 2; + + instance->decoder.parser_step = Oregon2DecoderStepReset; + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + break; + } +} + +uint8_t subghz_protocol_decoder_oregon2_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderOregon2* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_oregon2_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzPresetDefinition* preset) { + furi_assert(context); + SubGhzProtocolDecoderOregon2* instance = context; + if(!subghz_block_generic_serialize(&instance->generic, flipper_format, preset)) return false; + uint32_t temp = instance->var_bits; + if(!flipper_format_write_uint32(flipper_format, "VarBits", &temp, 1)) { + FURI_LOG_E(TAG, "Error adding VarBits"); + return false; + } + if(!flipper_format_write_hex( + flipper_format, + "VarData", + (const uint8_t*)&instance->var_data, + sizeof(instance->var_data))) { + FURI_LOG_E(TAG, "Error adding VarData"); + return false; + } + return true; +} + +bool subghz_protocol_decoder_oregon2_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderOregon2* instance = context; + bool ret = false; + uint32_t temp_data; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if(!flipper_format_read_uint32(flipper_format, "VarBits", &temp_data, 1)) { + FURI_LOG_E(TAG, "Missing VarLen"); + break; + } + instance->var_bits = (uint8_t)temp_data; + if(!flipper_format_read_hex( + flipper_format, + "VarData", + (uint8_t*)&instance->var_data, + sizeof(instance->var_data))) { + FURI_LOG_E(TAG, "Missing VarData"); + break; + } + if(instance->generic.data_count_bit != oregon2_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key: %d", instance->generic.data_count_bit); + break; + } + ret = true; + } while(false); + return ret; +} + +// append string of the variable data +static void + oregon2_var_data_append_string(uint16_t sensor_id, uint32_t var_data, string_t output) { + uint32_t val; + + if(sensor_id == 0xEC40) { + val = ((var_data >> 4) & 0xF) * 10 + ((var_data >> 8) & 0xF); + string_cat_printf( + output, + "Temp: %s%d.%d C\r\n", + (var_data & 0xF) ? "-" : "+", + val, + (uint32_t)(var_data >> 12) & 0xF); + } +} + +static void oregon2_append_check_sum(uint32_t fix_data, uint32_t var_data, string_t output) { + uint8_t sum = fix_data & 0xF; + uint8_t ref_sum = var_data & 0xFF; + var_data >>= 8; + + for(uint8_t i = 1; i < 8; i++) { + fix_data >>= 4; + var_data >>= 4; + sum += (fix_data & 0xF) + (var_data & 0xF); + } + + // swap calculated sum nibbles + sum = (((sum >> 4) & 0xF) | (sum << 4)) & 0xFF; + if(sum == ref_sum) + string_cat_printf(output, "Sum ok: 0x%hhX", ref_sum); + else + string_cat_printf(output, "Sum err: 0x%hhX vs 0x%hhX", ref_sum, sum); +} + +void subghz_protocol_decoder_oregon2_get_string(void* context, string_t output) { + furi_assert(context); + SubGhzProtocolDecoderOregon2* instance = context; + uint16_t sensor_id = OREGON2_SENSOR_ID(instance->generic.data); + string_cat_printf( + output, + "%s\r\n" + "ID: 0x%04lX, ch: %d%s, rc: 0x%02lX\r\n", + instance->generic.protocol_name, + (uint32_t)sensor_id, + (uint32_t)(instance->generic.data >> 12) & 0xF, + ((instance->generic.data & OREGON2_FLAG_BAT_LOW) ? ", low bat" : ""), + (uint32_t)(instance->generic.data >> 4) & 0xFF); + + if(instance->var_bits > 0) { + oregon2_var_data_append_string( + sensor_id, instance->var_data >> OREGON2_CHECKSUM_BITS, output); + oregon2_append_check_sum((uint32_t)instance->generic.data, instance->var_data, output); + } +} + +const SubGhzProtocolDecoder subghz_protocol_oregon2_decoder = { + .alloc = subghz_protocol_decoder_oregon2_alloc, + .free = subghz_protocol_decoder_oregon2_free, + + .feed = subghz_protocol_decoder_oregon2_feed, + .reset = subghz_protocol_decoder_oregon2_reset, + + .get_hash_data = subghz_protocol_decoder_oregon2_get_hash_data, + .serialize = subghz_protocol_decoder_oregon2_serialize, + .deserialize = subghz_protocol_decoder_oregon2_deserialize, + .get_string = subghz_protocol_decoder_oregon2_get_string, +}; + +const SubGhzProtocol subghz_protocol_oregon2 = { + .name = SUBGHZ_PROTOCOL_OREGON2_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save, + + .decoder = &subghz_protocol_oregon2_decoder, +}; diff --git a/lib/subghz/protocols/oregon2.h b/lib/subghz/protocols/oregon2.h new file mode 100644 index 00000000000..981b2599974 --- /dev/null +++ b/lib/subghz/protocols/oregon2.h @@ -0,0 +1,5 @@ +#pragma once + +#include "base.h" +#define SUBGHZ_PROTOCOL_OREGON2_NAME "Oregon2" +extern const SubGhzProtocol subghz_protocol_oregon2; diff --git a/lib/subghz/protocols/registry.c b/lib/subghz/protocols/registry.c index 8bb45e9aded..453fa747a0e 100644 --- a/lib/subghz/protocols/registry.c +++ b/lib/subghz/protocols/registry.c @@ -12,9 +12,7 @@ const SubGhzProtocol* subghz_protocol_registry[] = { &subghz_protocol_chamb_code, &subghz_protocol_power_smart, &subghz_protocol_marantec, &subghz_protocol_bett, &subghz_protocol_doitrand, &subghz_protocol_phoenix_v2, &subghz_protocol_honeywell_wdb, &subghz_protocol_magellen, &subghz_protocol_intertechno_v3, - &subghz_protocol_clemsa - -}; + &subghz_protocol_clemsa, &subghz_protocol_oregon2}; const SubGhzProtocol* subghz_protocol_registry_get_by_name(const char* name) { for(size_t i = 0; i < subghz_protocol_registry_count(); i++) { diff --git a/lib/subghz/protocols/registry.h b/lib/subghz/protocols/registry.h index ad1151024b8..8eb464ada3a 100644 --- a/lib/subghz/protocols/registry.h +++ b/lib/subghz/protocols/registry.h @@ -36,6 +36,7 @@ #include "magellen.h" #include "intertechno_v3.h" #include "clemsa.h" +#include "oregon2.h" /** * Registration by name SubGhzProtocol. From 9f3b80e606bab67d61c365be9bf90a2498c0a4f9 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Mon, 19 Sep 2022 19:05:04 +0300 Subject: [PATCH 066/824] Add new card parsers (#1503) * Add the "Two cities" parser * Add plantain and plantain4k parsers * Add new parsers to the supported list * United card PoC * Fix nfc device not sleeping * Completely read the 4K troika variants * Correct naming * Update to reflect upstream changes * Add support for MfUl info * Fix parsers * Card type detection fixes * Remove debug info * Fixes for the verification of cards * nfc: fix verification for supported cards * nfc: remove unused vars * Improve card reading reliability and fix plantain * plantain: change log level Co-authored-by: gornekich --- .../main/nfc/scenes/nfc_scene_device_info.c | 4 +- .../nfc_scene_mf_ultralight_read_success.c | 22 ++- .../main/nfc/scenes/nfc_scene_saved_menu.c | 4 +- lib/nfc/nfc_worker.c | 25 ++- lib/nfc/parsers/all_in_one.c | 113 ++++++++++++ lib/nfc/parsers/all_in_one.h | 9 + lib/nfc/parsers/nfc_supported_card.c | 50 ++++- lib/nfc/parsers/nfc_supported_card.h | 7 +- lib/nfc/parsers/plantain_4k_parser.c | 153 ++++++++++++++++ lib/nfc/parsers/plantain_4k_parser.h | 9 + lib/nfc/parsers/plantain_parser.c | 147 +++++++++++++++ lib/nfc/parsers/plantain_parser.h | 13 ++ lib/nfc/parsers/troika_4k_parser.c | 104 +++++++++++ lib/nfc/parsers/troika_4k_parser.h | 9 + .../{troyka_parser.c => troika_parser.c} | 42 +++-- lib/nfc/parsers/troika_parser.h | 9 + lib/nfc/parsers/troyka_parser.h | 9 - lib/nfc/parsers/two_cities.c | 171 ++++++++++++++++++ lib/nfc/parsers/two_cities.h | 9 + 19 files changed, 864 insertions(+), 45 deletions(-) create mode 100644 lib/nfc/parsers/all_in_one.c create mode 100644 lib/nfc/parsers/all_in_one.h create mode 100644 lib/nfc/parsers/plantain_4k_parser.c create mode 100644 lib/nfc/parsers/plantain_4k_parser.h create mode 100644 lib/nfc/parsers/plantain_parser.c create mode 100644 lib/nfc/parsers/plantain_parser.h create mode 100644 lib/nfc/parsers/troika_4k_parser.c create mode 100644 lib/nfc/parsers/troika_4k_parser.h rename lib/nfc/parsers/{troyka_parser.c => troika_parser.c} (68%) create mode 100644 lib/nfc/parsers/troika_parser.h delete mode 100644 lib/nfc/parsers/troyka_parser.h create mode 100644 lib/nfc/parsers/two_cities.c create mode 100644 lib/nfc/parsers/two_cities.h diff --git a/applications/main/nfc/scenes/nfc_scene_device_info.c b/applications/main/nfc/scenes/nfc_scene_device_info.c index 8228c7ea3b6..245aea5c594 100644 --- a/applications/main/nfc/scenes/nfc_scene_device_info.c +++ b/applications/main/nfc/scenes/nfc_scene_device_info.c @@ -47,7 +47,9 @@ void nfc_scene_device_info_on_enter(void* context) { } string_clear(country_name); } - } else if(dev_data->protocol == NfcDeviceProtocolMifareClassic) { + } else if( + dev_data->protocol == NfcDeviceProtocolMifareClassic || + dev_data->protocol == NfcDeviceProtocolMifareUl) { string_set(temp_str, nfc->dev->dev_data.parsed_data); } diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_success.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_success.c index 56fa2057895..f6dc5984e2f 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_success.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_success.c @@ -34,15 +34,19 @@ void nfc_scene_mf_ultralight_read_success_on_enter(void* context) { nfc); string_t temp_str; - string_init_printf(temp_str, "\e#%s\n", nfc_mf_ul_type(mf_ul_data->type, true)); - string_cat_printf(temp_str, "UID:"); - for(size_t i = 0; i < data->uid_len; i++) { - string_cat_printf(temp_str, " %02X", data->uid[i]); - } - string_cat_printf( - temp_str, "\nPages Read: %d/%d", mf_ul_data->data_read / 4, mf_ul_data->data_size / 4); - if(mf_ul_data->data_read != mf_ul_data->data_size) { - string_cat_printf(temp_str, "\nPassword-protected pages!"); + if(string_size(nfc->dev->dev_data.parsed_data)) { + string_init_set(temp_str, nfc->dev->dev_data.parsed_data); + } else { + string_init_printf(temp_str, "\e#%s\n", nfc_mf_ul_type(mf_ul_data->type, true)); + string_cat_printf(temp_str, "UID:"); + for(size_t i = 0; i < data->uid_len; i++) { + string_cat_printf(temp_str, " %02X", data->uid[i]); + } + string_cat_printf( + temp_str, "\nPages Read: %d/%d", mf_ul_data->data_read / 4, mf_ul_data->data_size / 4); + if(mf_ul_data->data_read != mf_ul_data->data_size) { + string_cat_printf(temp_str, "\nPassword-protected pages!"); + } } widget_add_text_scroll_element(widget, 0, 0, 128, 52, string_get_cstr(temp_str)); string_clear(temp_str); diff --git a/applications/main/nfc/scenes/nfc_scene_saved_menu.c b/applications/main/nfc/scenes/nfc_scene_saved_menu.c index c7aec5d8713..c1043b3a7a4 100644 --- a/applications/main/nfc/scenes/nfc_scene_saved_menu.c +++ b/applications/main/nfc/scenes/nfc_scene_saved_menu.c @@ -91,7 +91,9 @@ bool nfc_scene_saved_menu_on_event(void* context, SceneManagerEvent event) { bool application_info_present = false; if(dev_data->protocol == NfcDeviceProtocolEMV) { application_info_present = true; - } else if(dev_data->protocol == NfcDeviceProtocolMifareClassic) { + } else if( + dev_data->protocol == NfcDeviceProtocolMifareClassic || + dev_data->protocol == NfcDeviceProtocolMifareUl) { application_info_present = nfc_supported_card_verify_and_parse(dev_data); } diff --git a/lib/nfc/nfc_worker.c b/lib/nfc/nfc_worker.c index 6355f8d1ea2..2feae443f7a 100644 --- a/lib/nfc/nfc_worker.c +++ b/lib/nfc/nfc_worker.c @@ -123,7 +123,25 @@ static bool nfc_worker_read_mf_ultralight(NfcWorker* nfc_worker, FuriHalNfcTxRxC } do { - // Read card + // Try to read supported card + FURI_LOG_I(TAG, "Trying to read a supported card ..."); + for(size_t i = 0; i < NfcSupportedCardTypeEnd; i++) { + if(nfc_supported_card[i].protocol == NfcDeviceProtocolMifareUl) { + if(nfc_supported_card[i].verify(nfc_worker, tx_rx)) { + if(nfc_supported_card[i].read(nfc_worker, tx_rx)) { + read_success = true; + nfc_supported_card[i].parse(nfc_worker->dev_data); + break; + } + } else { + furi_hal_nfc_sleep(); + } + } + } + if(read_success) break; + furi_hal_nfc_sleep(); + + // Otherwise, try to read as usual if(!furi_hal_nfc_detect(&nfc_worker->dev_data->nfc_data, 200)) break; if(!mf_ul_read_card(tx_rx, &reader, &data)) break; // Copy data @@ -149,14 +167,17 @@ static bool nfc_worker_read_mf_classic(NfcWorker* nfc_worker, FuriHalNfcTxRxCont do { // Try to read supported card - FURI_LOG_I(TAG, "Try read supported card ..."); + FURI_LOG_I(TAG, "Trying to read a supported card ..."); for(size_t i = 0; i < NfcSupportedCardTypeEnd; i++) { if(nfc_supported_card[i].protocol == NfcDeviceProtocolMifareClassic) { if(nfc_supported_card[i].verify(nfc_worker, tx_rx)) { if(nfc_supported_card[i].read(nfc_worker, tx_rx)) { read_success = true; nfc_supported_card[i].parse(nfc_worker->dev_data); + break; } + } else { + furi_hal_nfc_sleep(); } } } diff --git a/lib/nfc/parsers/all_in_one.c b/lib/nfc/parsers/all_in_one.c new file mode 100644 index 00000000000..b49a32f7c1c --- /dev/null +++ b/lib/nfc/parsers/all_in_one.c @@ -0,0 +1,113 @@ +#include "nfc_supported_card.h" +#include "all_in_one.h" + +#include +#include + +#include "furi_hal.h" + +#define ALL_IN_ONE_LAYOUT_UNKNOWN 0 +#define ALL_IN_ONE_LAYOUT_A 1 +#define ALL_IN_ONE_LAYOUT_D 2 +#define ALL_IN_ONE_LAYOUT_E2 3 +#define ALL_IN_ONE_LAYOUT_E3 4 +#define ALL_IN_ONE_LAYOUT_E5 5 +#define ALL_IN_ONE_LAYOUT_2 6 + +uint8_t all_in_one_get_layout(NfcDeviceData* dev_data) { + // I absolutely hate what's about to happen here. + + // Switch on the second half of the third byte of page 5 + FURI_LOG_I("all_in_one", "Layout byte: %02x", dev_data->mf_ul_data.data[(4 * 5) + 2]); + FURI_LOG_I( + "all_in_one", "Layout half-byte: %02x", dev_data->mf_ul_data.data[(4 * 5) + 3] & 0x0F); + switch(dev_data->mf_ul_data.data[(4 * 5) + 2] & 0x0F) { + // If it is A, the layout type is a type A layout + case 0x0A: + return ALL_IN_ONE_LAYOUT_A; + case 0x0D: + return ALL_IN_ONE_LAYOUT_D; + case 0x02: + return ALL_IN_ONE_LAYOUT_2; + default: + FURI_LOG_I( + "all_in_one", + "Unknown layout type: %d", + dev_data->mf_ul_data.data[(4 * 5) + 2] & 0x0F); + return ALL_IN_ONE_LAYOUT_UNKNOWN; + } +} + +bool all_in_one_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { + UNUSED(nfc_worker); + // If this is a all_in_one pass, first 2 bytes of page 4 are 0x45 0xD9 + MfUltralightReader reader = {}; + MfUltralightData data = {}; + + if(!mf_ul_read_card(tx_rx, &reader, &data)) { + return false; + } else { + if(data.data[4 * 4] == 0x45 && data.data[4 * 4 + 1] == 0xD9) { + FURI_LOG_I("all_in_one", "Pass verified"); + return true; + } + } + return false; +} + +bool all_in_one_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { + MfUltralightReader reader = {}; + MfUltralightData data = {}; + if(!mf_ul_read_card(tx_rx, &reader, &data)) { + return false; + } else { + memcpy(&nfc_worker->dev_data->mf_ul_data, &data, sizeof(data)); + FURI_LOG_I("all_in_one", "Card read"); + return true; + } +} + +bool all_in_one_parser_parse(NfcDeviceData* dev_data) { + if(dev_data->mf_ul_data.data[4 * 4] != 0x45 || dev_data->mf_ul_data.data[4 * 4 + 1] != 0xD9) { + FURI_LOG_I("all_in_one", "Pass not verified"); + return false; + } + + // If the layout is a then the ride count is stored in the first byte of page 8 + uint8_t ride_count = 0; + uint32_t serial = 0; + if(all_in_one_get_layout(dev_data) == ALL_IN_ONE_LAYOUT_A) { + ride_count = dev_data->mf_ul_data.data[4 * 8]; + } else if(all_in_one_get_layout(dev_data) == ALL_IN_ONE_LAYOUT_D) { + // If the layout is D, the ride count is stored in the second byte of page 9 + ride_count = dev_data->mf_ul_data.data[4 * 9 + 1]; + // I hate this with a burning passion. + + // The number starts at the second half of the third byte on page 4, and is 32 bits long + // So we get the second half of the third byte, then bytes 4-6, and then the first half of the 7th byte + // B8 17 A2 A4 BD becomes 81 7A 2A 4B + serial = (dev_data->mf_ul_data.data[4 * 4 + 2] & 0x0F) << 28 | + dev_data->mf_ul_data.data[4 * 4 + 3] << 20 | + dev_data->mf_ul_data.data[4 * 4 + 4] << 12 | + dev_data->mf_ul_data.data[4 * 4 + 5] << 4 | + (dev_data->mf_ul_data.data[4 * 4 + 6] >> 4); + } else { + FURI_LOG_I("all_in_one", "Unknown layout: %d", all_in_one_get_layout(dev_data)); + ride_count = 137; + } + + // I hate this with a burning passion. + + // The number starts at the second half of the third byte on page 4, and is 32 bits long + // So we get the second half of the third byte, then bytes 4-6, and then the first half of the 7th byte + // B8 17 A2 A4 BD becomes 81 7A 2A 4B + serial = + (dev_data->mf_ul_data.data[4 * 4 + 2] & 0x0F) << 28 | + dev_data->mf_ul_data.data[4 * 4 + 3] << 20 | dev_data->mf_ul_data.data[4 * 4 + 4] << 12 | + dev_data->mf_ul_data.data[4 * 4 + 5] << 4 | (dev_data->mf_ul_data.data[4 * 4 + 6] >> 4); + + // Format string for rides count + string_printf( + dev_data->parsed_data, "\e#All-In-One\nNumber: %u\nRides left: %u", serial, ride_count); + return true; +} \ No newline at end of file diff --git a/lib/nfc/parsers/all_in_one.h b/lib/nfc/parsers/all_in_one.h new file mode 100644 index 00000000000..9b646d4dc30 --- /dev/null +++ b/lib/nfc/parsers/all_in_one.h @@ -0,0 +1,9 @@ +#pragma once + +#include "nfc_supported_card.h" + +bool all_in_one_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx); + +bool all_in_one_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx); + +bool all_in_one_parser_parse(NfcDeviceData* dev_data); \ No newline at end of file diff --git a/lib/nfc/parsers/nfc_supported_card.c b/lib/nfc/parsers/nfc_supported_card.c index 480c970e7ec..fc2dc34e0ee 100644 --- a/lib/nfc/parsers/nfc_supported_card.c +++ b/lib/nfc/parsers/nfc_supported_card.c @@ -1,14 +1,54 @@ #include "nfc_supported_card.h" -#include "troyka_parser.h" +#include "plantain_parser.h" +#include "troika_parser.h" +#include "plantain_4k_parser.h" +#include "troika_4k_parser.h" +#include "two_cities.h" +#include "all_in_one.h" NfcSupportedCard nfc_supported_card[NfcSupportedCardTypeEnd] = { - [NfcSupportedCardTypeTroyka] = + [NfcSupportedCardTypePlantain] = { .protocol = NfcDeviceProtocolMifareClassic, - .verify = troyka_parser_verify, - .read = troyka_parser_read, - .parse = troyka_parser_parse, + .verify = plantain_parser_verify, + .read = plantain_parser_read, + .parse = plantain_parser_parse, + }, + [NfcSupportedCardTypeTroika] = + { + .protocol = NfcDeviceProtocolMifareClassic, + .verify = troika_parser_verify, + .read = troika_parser_read, + .parse = troika_parser_parse, + }, + [NfcSupportedCardTypePlantain4K] = + { + .protocol = NfcDeviceProtocolMifareClassic, + .verify = plantain_4k_parser_verify, + .read = plantain_4k_parser_read, + .parse = plantain_4k_parser_parse, + }, + [NfcSupportedCardTypeTroika4K] = + { + .protocol = NfcDeviceProtocolMifareClassic, + .verify = troika_4k_parser_verify, + .read = troika_4k_parser_read, + .parse = troika_4k_parser_parse, + }, + [NfcSupportedCardTypeTwoCities] = + { + .protocol = NfcDeviceProtocolMifareClassic, + .verify = two_cities_parser_verify, + .read = two_cities_parser_read, + .parse = two_cities_parser_parse, + }, + [NfcSupportedCardTypeAllInOne] = + { + .protocol = NfcDeviceProtocolMifareUl, + .verify = all_in_one_parser_verify, + .read = all_in_one_parser_read, + .parse = all_in_one_parser_parse, }, }; diff --git a/lib/nfc/parsers/nfc_supported_card.h b/lib/nfc/parsers/nfc_supported_card.h index 9b5d1c05326..d34b5794a20 100644 --- a/lib/nfc/parsers/nfc_supported_card.h +++ b/lib/nfc/parsers/nfc_supported_card.h @@ -7,7 +7,12 @@ #include typedef enum { - NfcSupportedCardTypeTroyka, + NfcSupportedCardTypePlantain, + NfcSupportedCardTypeTroika, + NfcSupportedCardTypePlantain4K, + NfcSupportedCardTypeTroika4K, + NfcSupportedCardTypeTwoCities, + NfcSupportedCardTypeAllInOne, NfcSupportedCardTypeEnd, } NfcSupportedCardType; diff --git a/lib/nfc/parsers/plantain_4k_parser.c b/lib/nfc/parsers/plantain_4k_parser.c new file mode 100644 index 00000000000..77387707b14 --- /dev/null +++ b/lib/nfc/parsers/plantain_4k_parser.c @@ -0,0 +1,153 @@ +#include "nfc_supported_card.h" +#include "plantain_parser.h" // For luhn and string_push_uint64 + +#include +#include + +#include "furi_hal.h" + +static const MfClassicAuthContext plantain_keys_4k[] = { + {.sector = 0, .key_a = 0xFFFFFFFFFFFF, .key_b = 0xFFFFFFFFFFFF}, + {.sector = 1, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, + {.sector = 2, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, + {.sector = 3, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, + {.sector = 4, .key_a = 0xe56ac127dd45, .key_b = 0x19fc84a3784b}, + {.sector = 5, .key_a = 0x77dabc9825e1, .key_b = 0x9764fec3154a}, + {.sector = 6, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, + {.sector = 7, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, + {.sector = 8, .key_a = 0x26973ea74321, .key_b = 0xd27058c6e2c7}, + {.sector = 9, .key_a = 0xeb0a8ff88ade, .key_b = 0x578a9ada41e3}, + {.sector = 10, .key_a = 0xea0fd73cb149, .key_b = 0x29c35fa068fb}, + {.sector = 11, .key_a = 0xc76bf71a2509, .key_b = 0x9ba241db3f56}, + {.sector = 12, .key_a = 0xacffffffffff, .key_b = 0x71f3a315ad26}, + {.sector = 13, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, + {.sector = 14, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, + {.sector = 15, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, + {.sector = 16, .key_a = 0x72f96bdd3714, .key_b = 0x462225cd34cf}, + {.sector = 17, .key_a = 0x044ce1872bc3, .key_b = 0x8c90c70cff4a}, + {.sector = 18, .key_a = 0xbc2d1791dec1, .key_b = 0xca96a487de0b}, + {.sector = 19, .key_a = 0x8791b2ccb5c4, .key_b = 0xc956c3b80da3}, + {.sector = 20, .key_a = 0x8e26e45e7d65, .key_b = 0x8e65b3af7d22}, + {.sector = 21, .key_a = 0x0f318130ed18, .key_b = 0x0c420a20e056}, + {.sector = 22, .key_a = 0x045ceca15535, .key_b = 0x31bec3d9e510}, + {.sector = 23, .key_a = 0x9d993c5d4ef4, .key_b = 0x86120e488abf}, + {.sector = 24, .key_a = 0xc65d4eaa645b, .key_b = 0xb69d40d1a439}, + {.sector = 25, .key_a = 0x3a8a139c20b4, .key_b = 0x8818a9c5d406}, + {.sector = 26, .key_a = 0xbaff3053b496, .key_b = 0x4b7cb25354d3}, + {.sector = 27, .key_a = 0x7413b599c4ea, .key_b = 0xb0a2AAF3A1BA}, + {.sector = 28, .key_a = 0x0ce7cd2cc72b, .key_b = 0xfa1fbb3f0f1f}, + {.sector = 29, .key_a = 0x0be5fac8b06a, .key_b = 0x6f95887a4fd3}, + {.sector = 30, .key_a = 0x0eb23cc8110b, .key_b = 0x04dc35277635}, + {.sector = 31, .key_a = 0xbc4580b7f20b, .key_b = 0xd0a4131fb290}, + {.sector = 32, .key_a = 0x7a396f0d633d, .key_b = 0xad2bdc097023}, + {.sector = 33, .key_a = 0xa3faa6daff67, .key_b = 0x7600e889adf9}, + {.sector = 34, .key_a = 0xfd8705e721b0, .key_b = 0x296fc317a513}, + {.sector = 35, .key_a = 0x22052b480d11, .key_b = 0xe19504c39461}, + {.sector = 36, .key_a = 0xa7141147d430, .key_b = 0xff16014fefc7}, + {.sector = 37, .key_a = 0x8a8d88151a00, .key_b = 0x038b5f9b5a2a}, + {.sector = 38, .key_a = 0xb27addfb64b0, .key_b = 0x152fd0c420a7}, + {.sector = 39, .key_a = 0x7259fa0197c6, .key_b = 0x5583698df085}, +}; + +bool plantain_4k_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { + furi_assert(nfc_worker); + UNUSED(nfc_worker); + + if(nfc_worker->dev_data->mf_classic_data.type != MfClassicType4k) { + return false; + } + + uint8_t sector = 8; + uint8_t block = mf_classic_get_sector_trailer_block_num_by_sector(sector); + FURI_LOG_D("Plant4K", "Verifying sector %d", sector); + if(mf_classic_authenticate(tx_rx, block, 0x26973ea74321, MfClassicKeyA)) { + FURI_LOG_D("Plant4K", "Sector %d verified", sector); + return true; + } + return false; +} + +bool plantain_4k_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { + furi_assert(nfc_worker); + + MfClassicReader reader = {}; + FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data; + reader.type = mf_classic_get_classic_type(nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak); + for(size_t i = 0; i < COUNT_OF(plantain_keys_4k); i++) { + mf_classic_reader_add_sector( + &reader, + plantain_keys_4k[i].sector, + plantain_keys_4k[i].key_a, + plantain_keys_4k[i].key_b); + FURI_LOG_T("plant4k", "Added sector %d", plantain_keys_4k[i].sector); + } + for(int i = 0; i < 5; i++) { + if(mf_classic_read_card(tx_rx, &reader, &nfc_worker->dev_data->mf_classic_data) == 40) { + return true; + } + } + return false; +} + +bool plantain_4k_parser_parse(NfcDeviceData* dev_data) { + MfClassicData* data = &dev_data->mf_classic_data; + + // Verify key + MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, 8); + uint64_t key = nfc_util_bytes2num(sec_tr->key_a, 6); + if(key != plantain_keys_4k[8].key_a) return false; + + // Point to block 0 of sector 4, value 0 + uint8_t* temp_ptr = &data->block[4 * 4].value[0]; + // Read first 4 bytes of block 0 of sector 4 from last to first and convert them to uint32_t + // 38 18 00 00 becomes 00 00 18 38, and equals to 6200 decimal + uint32_t balance = + ((temp_ptr[3] << 24) | (temp_ptr[2] << 16) | (temp_ptr[1] << 8) | temp_ptr[0]) / 100; + // Read card number + // Point to block 0 of sector 0, value 0 + temp_ptr = &data->block[0 * 4].value[0]; + // Read first 7 bytes of block 0 of sector 0 from last to first and convert them to uint64_t + // 80 5C 23 8A 16 31 04 becomes 04 31 16 8A 23 5C 80, and equals to 36130104729284868 decimal + uint8_t card_number_arr[7]; + for(size_t i = 0; i < 7; i++) { + card_number_arr[i] = temp_ptr[6 - i]; + } + // Copy card number to uint64_t + uint64_t card_number = 0; + for(size_t i = 0; i < 7; i++) { + card_number = (card_number << 8) | card_number_arr[i]; + } + // Convert card number to string + string_t card_number_str; + string_init(card_number_str); + // Should look like "361301047292848684" + // %llu doesn't work for some reason in sprintf, so we use string_push_uint64 instead + string_push_uint64(card_number, card_number_str); + // Add suffix with luhn checksum (1 digit) to the card number string + string_t card_number_suffix; + string_init(card_number_suffix); + + // The number to calculate the checksum on doesn't fit into uint64_t, idk + //uint8_t luhn_checksum = plantain_calculate_luhn(card_number); + + // // Convert luhn checksum to string + // string_t luhn_checksum_str; + // string_init(luhn_checksum_str); + // string_push_uint64(luhn_checksum, luhn_checksum_str); + + string_cat_printf(card_number_suffix, "-"); + // FURI_LOG_D("plant4k", "Card checksum: %d", luhn_checksum); + string_cat_printf(card_number_str, string_get_cstr(card_number_suffix)); + // Free all not needed strings + string_clear(card_number_suffix); + // string_clear(luhn_checksum_str); + + string_printf( + dev_data->parsed_data, + "\e#Plantain\nN:%s\nBalance:%d\n", + string_get_cstr(card_number_str), + balance); + string_clear(card_number_str); + + return true; +} diff --git a/lib/nfc/parsers/plantain_4k_parser.h b/lib/nfc/parsers/plantain_4k_parser.h new file mode 100644 index 00000000000..29998af15ea --- /dev/null +++ b/lib/nfc/parsers/plantain_4k_parser.h @@ -0,0 +1,9 @@ +#pragma once + +#include "nfc_supported_card.h" + +bool plantain_4k_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx); + +bool plantain_4k_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx); + +bool plantain_4k_parser_parse(NfcDeviceData* dev_data); diff --git a/lib/nfc/parsers/plantain_parser.c b/lib/nfc/parsers/plantain_parser.c new file mode 100644 index 00000000000..ff81b8e9b5b --- /dev/null +++ b/lib/nfc/parsers/plantain_parser.c @@ -0,0 +1,147 @@ +#include "nfc_supported_card.h" + +#include +#include + +#include "furi_hal.h" + +static const MfClassicAuthContext plantain_keys[] = { + {.sector = 0, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, + {.sector = 1, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, + {.sector = 2, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, + {.sector = 3, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, + {.sector = 4, .key_a = 0xe56ac127dd45, .key_b = 0x19fc84a3784b}, + {.sector = 5, .key_a = 0x77dabc9825e1, .key_b = 0x9764fec3154a}, + {.sector = 6, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, + {.sector = 7, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, + {.sector = 8, .key_a = 0x26973ea74321, .key_b = 0xd27058c6e2c7}, + {.sector = 9, .key_a = 0xeb0a8ff88ade, .key_b = 0x578a9ada41e3}, + {.sector = 10, .key_a = 0xea0fd73cb149, .key_b = 0x29c35fa068fb}, + {.sector = 11, .key_a = 0xc76bf71a2509, .key_b = 0x9ba241db3f56}, + {.sector = 12, .key_a = 0xacffffffffff, .key_b = 0x71f3a315ad26}, + {.sector = 13, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, + {.sector = 14, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, + {.sector = 15, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, +}; + +bool plantain_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { + furi_assert(nfc_worker); + UNUSED(nfc_worker); + if(nfc_worker->dev_data->mf_classic_data.type != MfClassicType1k) { + return false; + } + + uint8_t sector = 8; + uint8_t block = mf_classic_get_sector_trailer_block_num_by_sector(sector); + FURI_LOG_D("Plant", "Verifying sector %d", sector); + if(mf_classic_authenticate(tx_rx, block, 0x26973ea74321, MfClassicKeyA)) { + FURI_LOG_D("Plant", "Sector %d verified", sector); + return true; + } + return false; +} + +bool plantain_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { + furi_assert(nfc_worker); + + MfClassicReader reader = {}; + FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data; + reader.type = mf_classic_get_classic_type(nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak); + for(size_t i = 0; i < COUNT_OF(plantain_keys); i++) { + mf_classic_reader_add_sector( + &reader, plantain_keys[i].sector, plantain_keys[i].key_a, plantain_keys[i].key_b); + } + + return mf_classic_read_card(tx_rx, &reader, &nfc_worker->dev_data->mf_classic_data) == 16; +} + +void string_push_uint64(uint64_t input, string_t output) { + const uint8_t base = 10; + + do { + char c = input % base; + input /= base; + + if(c < 10) + c += '0'; + else + c += 'A' - 10; + string_push_back(output, c); + } while(input); + + // reverse string + for(uint8_t i = 0; i < string_size(output) / 2; i++) { + char c = string_get_char(output, i); + string_set_char(output, i, string_get_char(output, string_size(output) - i - 1)); + string_set_char(output, string_size(output) - i - 1, c); + } +} + +uint8_t plantain_calculate_luhn(uint64_t number) { + // No. + UNUSED(number); + return 0; +} + +bool plantain_parser_parse(NfcDeviceData* dev_data) { + MfClassicData* data = &dev_data->mf_classic_data; + + // Verify key + MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, 8); + uint64_t key = nfc_util_bytes2num(sec_tr->key_a, 6); + if(key != plantain_keys[8].key_a) return false; + + // Point to block 0 of sector 4, value 0 + uint8_t* temp_ptr = &data->block[4 * 4].value[0]; + // Read first 4 bytes of block 0 of sector 4 from last to first and convert them to uint32_t + // 38 18 00 00 becomes 00 00 18 38, and equals to 6200 decimal + uint32_t balance = + ((temp_ptr[3] << 24) | (temp_ptr[2] << 16) | (temp_ptr[1] << 8) | temp_ptr[0]) / 100; + // Read card number + // Point to block 0 of sector 0, value 0 + temp_ptr = &data->block[0 * 4].value[0]; + // Read first 7 bytes of block 0 of sector 0 from last to first and convert them to uint64_t + // 80 5C 23 8A 16 31 04 becomes 04 31 16 8A 23 5C 80, and equals to 36130104729284868 decimal + uint8_t card_number_arr[7]; + for(size_t i = 0; i < 7; i++) { + card_number_arr[i] = temp_ptr[6 - i]; + } + // Copy card number to uint64_t + uint64_t card_number = 0; + for(size_t i = 0; i < 7; i++) { + card_number = (card_number << 8) | card_number_arr[i]; + } + // Convert card number to string + string_t card_number_str; + string_init(card_number_str); + // Should look like "361301047292848684" + // %llu doesn't work for some reason in sprintf, so we use string_push_uint64 instead + string_push_uint64(card_number, card_number_str); + // Add suffix with luhn checksum (1 digit) to the card number string + string_t card_number_suffix; + string_init(card_number_suffix); + + // The number to calculate the checksum on doesn't fit into uint64_t, idk + //uint8_t luhn_checksum = plantain_calculate_luhn(card_number); + + // // Convert luhn checksum to string + // string_t luhn_checksum_str; + // string_init(luhn_checksum_str); + // string_push_uint64(luhn_checksum, luhn_checksum_str); + + string_cat_printf(card_number_suffix, "-"); + // FURI_LOG_D("plant4k", "Card checksum: %d", luhn_checksum); + string_cat_printf(card_number_str, string_get_cstr(card_number_suffix)); + // Free all not needed strings + string_clear(card_number_suffix); + // string_clear(luhn_checksum_str); + + string_printf( + dev_data->parsed_data, + "\e#Plantain\nN:%s\nBalance:%d\n", + string_get_cstr(card_number_str), + balance); + string_clear(card_number_str); + + return true; +} diff --git a/lib/nfc/parsers/plantain_parser.h b/lib/nfc/parsers/plantain_parser.h new file mode 100644 index 00000000000..d37f0dd481b --- /dev/null +++ b/lib/nfc/parsers/plantain_parser.h @@ -0,0 +1,13 @@ +#pragma once + +#include "nfc_supported_card.h" + +bool plantain_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx); + +bool plantain_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx); + +bool plantain_parser_parse(NfcDeviceData* dev_data); + +void string_push_uint64(uint64_t input, string_t output); + +uint8_t plantain_calculate_luhn(uint64_t number); diff --git a/lib/nfc/parsers/troika_4k_parser.c b/lib/nfc/parsers/troika_4k_parser.c new file mode 100644 index 00000000000..8c32381f29f --- /dev/null +++ b/lib/nfc/parsers/troika_4k_parser.c @@ -0,0 +1,104 @@ +#include "nfc_supported_card.h" + +#include +#include + +static const MfClassicAuthContext troika_4k_keys[] = { + {.sector = 0, .key_a = 0xa0a1a2a3a4a5, .key_b = 0xfbf225dc5d58}, + {.sector = 1, .key_a = 0xa82607b01c0d, .key_b = 0x2910989b6880}, + {.sector = 2, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99}, + {.sector = 3, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99}, + {.sector = 4, .key_a = 0x73068f118c13, .key_b = 0x2b7f3253fac5}, + {.sector = 5, .key_a = 0xFBC2793D540B, .key_b = 0xd3a297dc2698}, + {.sector = 6, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99}, + {.sector = 7, .key_a = 0xae3d65a3dad4, .key_b = 0x0f1c63013dbb}, + {.sector = 8, .key_a = 0xa73f5dc1d333, .key_b = 0xe35173494a81}, + {.sector = 9, .key_a = 0x69a32f1c2f19, .key_b = 0x6b8bd9860763}, + {.sector = 10, .key_a = 0x9becdf3d9273, .key_b = 0xf8493407799d}, + {.sector = 11, .key_a = 0x08b386463229, .key_b = 0x5efbaecef46b}, + {.sector = 12, .key_a = 0xcd4c61c26e3d, .key_b = 0x31c7610de3b0}, + {.sector = 13, .key_a = 0xa82607b01c0d, .key_b = 0x2910989b6880}, + {.sector = 14, .key_a = 0x0e8f64340ba4, .key_b = 0x4acec1205d75}, + {.sector = 15, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99}, + {.sector = 16, .key_a = 0x6b02733bb6ec, .key_b = 0x7038cd25c408}, + {.sector = 17, .key_a = 0x403d706ba880, .key_b = 0xb39d19a280df}, + {.sector = 18, .key_a = 0xc11f4597efb5, .key_b = 0x70d901648cb9}, + {.sector = 19, .key_a = 0x0db520c78c1c, .key_b = 0x73e5b9d9d3a4}, + {.sector = 20, .key_a = 0x3ebce0925b2f, .key_b = 0x372cc880f216}, + {.sector = 21, .key_a = 0x16a27af45407, .key_b = 0x9868925175ba}, + {.sector = 22, .key_a = 0xaba208516740, .key_b = 0xce26ecb95252}, + {.sector = 23, .key_a = 0xCD64E567ABCD, .key_b = 0x8f79c4fd8a01}, + {.sector = 24, .key_a = 0x764cd061f1e6, .key_b = 0xa74332f74994}, + {.sector = 25, .key_a = 0x1cc219e9fec1, .key_b = 0xb90de525ceb6}, + {.sector = 26, .key_a = 0x2fe3cb83ea43, .key_b = 0xfba88f109b32}, + {.sector = 27, .key_a = 0x07894ffec1d6, .key_b = 0xefcb0e689db3}, + {.sector = 28, .key_a = 0x04c297b91308, .key_b = 0xc8454c154cb5}, + {.sector = 29, .key_a = 0x7a38e3511a38, .key_b = 0xab16584c972a}, + {.sector = 30, .key_a = 0x7545df809202, .key_b = 0xecf751084a80}, + {.sector = 31, .key_a = 0x5125974cd391, .key_b = 0xd3eafb5df46d}, + {.sector = 32, .key_a = 0x7a86aa203788, .key_b = 0xe41242278ca2}, + {.sector = 33, .key_a = 0xafcef64c9913, .key_b = 0x9db96dca4324}, + {.sector = 34, .key_a = 0x04eaa462f70b, .key_b = 0xac17b93e2fae}, + {.sector = 35, .key_a = 0xe734c210f27e, .key_b = 0x29ba8c3e9fda}, + {.sector = 36, .key_a = 0xd5524f591eed, .key_b = 0x5daf42861b4d}, + {.sector = 37, .key_a = 0xe4821a377b75, .key_b = 0xe8709e486465}, + {.sector = 38, .key_a = 0x518dc6eea089, .key_b = 0x97c64ac98ca4}, + {.sector = 39, .key_a = 0xbb52f8cce07f, .key_b = 0x6b6119752c70}, +}; + +bool troika_4k_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { + furi_assert(nfc_worker); + + if(nfc_worker->dev_data->mf_classic_data.type != MfClassicType4k) { + return false; + } + + uint8_t sector = 11; + uint8_t block = mf_classic_get_sector_trailer_block_num_by_sector(sector); + FURI_LOG_D("Troika", "Verifying sector %d", sector); + if(mf_classic_authenticate(tx_rx, block, 0x08b386463229, MfClassicKeyA)) { + FURI_LOG_D("Troika", "Sector %d verified", sector); + return true; + } + return false; +} + +bool troika_4k_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { + furi_assert(nfc_worker); + + MfClassicReader reader = {}; + FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data; + reader.type = mf_classic_get_classic_type(nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak); + for(size_t i = 0; i < COUNT_OF(troika_4k_keys); i++) { + mf_classic_reader_add_sector( + &reader, troika_4k_keys[i].sector, troika_4k_keys[i].key_a, troika_4k_keys[i].key_b); + } + + return mf_classic_read_card(tx_rx, &reader, &nfc_worker->dev_data->mf_classic_data) == 40; +} + +bool troika_4k_parser_parse(NfcDeviceData* dev_data) { + MfClassicData* data = &dev_data->mf_classic_data; + + // Verify key + MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, 4); + uint64_t key = nfc_util_bytes2num(sec_tr->key_a, 6); + if(key != troika_4k_keys[4].key_a) return false; + + // Verify card type + if(data->type != MfClassicType4k) return false; + + uint8_t* temp_ptr = &data->block[8 * 4 + 1].value[5]; + uint16_t balance = ((temp_ptr[0] << 8) | temp_ptr[1]) / 25; + temp_ptr = &data->block[8 * 4].value[3]; + uint32_t number = 0; + for(size_t i = 0; i < 4; i++) { + number <<= 8; + number |= temp_ptr[i]; + } + number >>= 4; + + string_printf(dev_data->parsed_data, "\e#Troika\nNum: %ld\nBalance: %d rur.", number, balance); + + return true; +} diff --git a/lib/nfc/parsers/troika_4k_parser.h b/lib/nfc/parsers/troika_4k_parser.h new file mode 100644 index 00000000000..c1d6f01d3aa --- /dev/null +++ b/lib/nfc/parsers/troika_4k_parser.h @@ -0,0 +1,9 @@ +#pragma once + +#include "nfc_supported_card.h" + +bool troika_4k_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx); + +bool troika_4k_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx); + +bool troika_4k_parser_parse(NfcDeviceData* dev_data); diff --git a/lib/nfc/parsers/troyka_parser.c b/lib/nfc/parsers/troika_parser.c similarity index 68% rename from lib/nfc/parsers/troyka_parser.c rename to lib/nfc/parsers/troika_parser.c index 51ffa42e14f..f396b168034 100644 --- a/lib/nfc/parsers/troyka_parser.c +++ b/lib/nfc/parsers/troika_parser.c @@ -3,7 +3,7 @@ #include #include -static const MfClassicAuthContext troyka_keys[] = { +static const MfClassicAuthContext troika_keys[] = { {.sector = 0, .key_a = 0xa0a1a2a3a4a5, .key_b = 0xfbf225dc5d58}, {.sector = 1, .key_a = 0xa82607b01c0d, .key_b = 0x2910989b6880}, {.sector = 2, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99}, @@ -22,42 +22,50 @@ static const MfClassicAuthContext troyka_keys[] = { {.sector = 15, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99}, }; -bool troyka_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { +bool troika_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { furi_assert(nfc_worker); UNUSED(nfc_worker); + if(nfc_worker->dev_data->mf_classic_data.type != MfClassicType1k) { + return false; + } - MfClassicAuthContext auth_ctx = { - .key_a = MF_CLASSIC_NO_KEY, - .key_b = MF_CLASSIC_NO_KEY, - .sector = 8, - }; - return mf_classic_auth_attempt(tx_rx, &auth_ctx, 0xa73f5dc1d333); + uint8_t sector = 11; + uint8_t block = mf_classic_get_sector_trailer_block_num_by_sector(sector); + FURI_LOG_D("Troika", "Verifying sector %d", sector); + if(mf_classic_authenticate(tx_rx, block, 0x08b386463229, MfClassicKeyA)) { + FURI_LOG_D("Troika", "Sector %d verified", sector); + return true; + } + return false; } -bool troyka_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { +bool troika_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { furi_assert(nfc_worker); MfClassicReader reader = {}; FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data; reader.type = mf_classic_get_classic_type(nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak); - for(size_t i = 0; i < COUNT_OF(troyka_keys); i++) { + for(size_t i = 0; i < COUNT_OF(troika_keys); i++) { mf_classic_reader_add_sector( - &reader, troyka_keys[i].sector, troyka_keys[i].key_a, troyka_keys[i].key_b); + &reader, troika_keys[i].sector, troika_keys[i].key_a, troika_keys[i].key_b); } return mf_classic_read_card(tx_rx, &reader, &nfc_worker->dev_data->mf_classic_data) == 16; } -bool troyka_parser_parse(NfcDeviceData* dev_data) { +bool troika_parser_parse(NfcDeviceData* dev_data) { MfClassicData* data = &dev_data->mf_classic_data; - bool troyka_parsed = false; + bool troika_parsed = false; do { // Verify key MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, 8); uint64_t key = nfc_util_bytes2num(sec_tr->key_a, 6); - if(key != troyka_keys[8].key_a) break; + if(key != troika_keys[8].key_a) break; + + // Verify card type + if(data->type != MfClassicType1k) break; // Parse data uint8_t* temp_ptr = &data->block[8 * 4 + 1].value[5]; @@ -71,9 +79,9 @@ bool troyka_parser_parse(NfcDeviceData* dev_data) { number >>= 4; string_printf( - dev_data->parsed_data, "\e#Troyka\nNum: %ld\nBalance: %d rur.", number, balance); - troyka_parsed = true; + dev_data->parsed_data, "\e#Troika\nNum: %ld\nBalance: %d rur.", number, balance); + troika_parsed = true; } while(false); - return troyka_parsed; + return troika_parsed; } diff --git a/lib/nfc/parsers/troika_parser.h b/lib/nfc/parsers/troika_parser.h new file mode 100644 index 00000000000..2aae48d29a5 --- /dev/null +++ b/lib/nfc/parsers/troika_parser.h @@ -0,0 +1,9 @@ +#pragma once + +#include "nfc_supported_card.h" + +bool troika_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx); + +bool troika_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx); + +bool troika_parser_parse(NfcDeviceData* dev_data); diff --git a/lib/nfc/parsers/troyka_parser.h b/lib/nfc/parsers/troyka_parser.h deleted file mode 100644 index 445fe40e55a..00000000000 --- a/lib/nfc/parsers/troyka_parser.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once - -#include "nfc_supported_card.h" - -bool troyka_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx); - -bool troyka_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx); - -bool troyka_parser_parse(NfcDeviceData* dev_data); diff --git a/lib/nfc/parsers/two_cities.c b/lib/nfc/parsers/two_cities.c new file mode 100644 index 00000000000..d052dff194f --- /dev/null +++ b/lib/nfc/parsers/two_cities.c @@ -0,0 +1,171 @@ +#include "nfc_supported_card.h" +#include "plantain_parser.h" // For plantain-specific stuff + +#include +#include + +#include "furi_hal.h" + +static const MfClassicAuthContext two_cities_keys_4k[] = { + {.sector = 0, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, + {.sector = 1, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, + {.sector = 2, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99}, + {.sector = 3, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99}, + {.sector = 4, .key_a = 0xe56ac127dd45, .key_b = 0x19fc84a3784b}, + {.sector = 5, .key_a = 0x77dabc9825e1, .key_b = 0x9764fec3154a}, + {.sector = 6, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99}, + {.sector = 7, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, + {.sector = 8, .key_a = 0xa73f5dc1d333, .key_b = 0xe35173494a81}, + {.sector = 9, .key_a = 0x69a32f1c2f19, .key_b = 0x6b8bd9860763}, + {.sector = 10, .key_a = 0xea0fd73cb149, .key_b = 0x29c35fa068fb}, + {.sector = 11, .key_a = 0xc76bf71a2509, .key_b = 0x9ba241db3f56}, + {.sector = 12, .key_a = 0xacffffffffff, .key_b = 0x71f3a315ad26}, + {.sector = 13, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, + {.sector = 14, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, + {.sector = 15, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99}, + {.sector = 16, .key_a = 0x72f96bdd3714, .key_b = 0x462225cd34cf}, + {.sector = 17, .key_a = 0x044ce1872bc3, .key_b = 0x8c90c70cff4a}, + {.sector = 18, .key_a = 0xbc2d1791dec1, .key_b = 0xca96a487de0b}, + {.sector = 19, .key_a = 0x8791b2ccb5c4, .key_b = 0xc956c3b80da3}, + {.sector = 20, .key_a = 0x8e26e45e7d65, .key_b = 0x8e65b3af7d22}, + {.sector = 21, .key_a = 0x0f318130ed18, .key_b = 0x0c420a20e056}, + {.sector = 22, .key_a = 0x045ceca15535, .key_b = 0x31bec3d9e510}, + {.sector = 23, .key_a = 0x9d993c5d4ef4, .key_b = 0x86120e488abf}, + {.sector = 24, .key_a = 0xc65d4eaa645b, .key_b = 0xb69d40d1a439}, + {.sector = 25, .key_a = 0x3a8a139c20b4, .key_b = 0x8818a9c5d406}, + {.sector = 26, .key_a = 0xbaff3053b496, .key_b = 0x4b7cb25354d3}, + {.sector = 27, .key_a = 0x7413b599c4ea, .key_b = 0xb0a2AAF3A1BA}, + {.sector = 28, .key_a = 0x0ce7cd2cc72b, .key_b = 0xfa1fbb3f0f1f}, + {.sector = 29, .key_a = 0x0be5fac8b06a, .key_b = 0x6f95887a4fd3}, + {.sector = 30, .key_a = 0x26973ea74321, .key_b = 0xd27058c6e2c7}, + {.sector = 31, .key_a = 0xeb0a8ff88ade, .key_b = 0x578a9ada41e3}, + {.sector = 32, .key_a = 0x7a396f0d633d, .key_b = 0xad2bdc097023}, + {.sector = 33, .key_a = 0xa3faa6daff67, .key_b = 0x7600e889adf9}, + {.sector = 34, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99}, + {.sector = 35, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99}, + {.sector = 36, .key_a = 0xa7141147d430, .key_b = 0xff16014fefc7}, + {.sector = 37, .key_a = 0x8a8d88151a00, .key_b = 0x038b5f9b5a2a}, + {.sector = 38, .key_a = 0xb27addfb64b0, .key_b = 0x152fd0c420a7}, + {.sector = 39, .key_a = 0x7259fa0197c6, .key_b = 0x5583698df085}, +}; + +bool two_cities_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { + furi_assert(nfc_worker); + UNUSED(nfc_worker); + + if(nfc_worker->dev_data->mf_classic_data.type != MfClassicType4k) { + return false; + } + + uint8_t sector = 4; + uint8_t block = mf_classic_get_sector_trailer_block_num_by_sector(sector); + FURI_LOG_D("2cities", "Verifying sector %d", sector); + if(mf_classic_authenticate(tx_rx, block, 0xe56ac127dd45, MfClassicKeyA)) { + FURI_LOG_D("2cities", "Sector %d verified", sector); + return true; + } + return false; +} + +bool two_cities_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { + furi_assert(nfc_worker); + + MfClassicReader reader = {}; + FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data; + reader.type = mf_classic_get_classic_type(nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak); + for(size_t i = 0; i < COUNT_OF(two_cities_keys_4k); i++) { + mf_classic_reader_add_sector( + &reader, + two_cities_keys_4k[i].sector, + two_cities_keys_4k[i].key_a, + two_cities_keys_4k[i].key_b); + FURI_LOG_T("2cities", "Added sector %d", two_cities_keys_4k[i].sector); + } + + return mf_classic_read_card(tx_rx, &reader, &nfc_worker->dev_data->mf_classic_data) == 40; +} + +bool two_cities_parser_parse(NfcDeviceData* dev_data) { + MfClassicData* data = &dev_data->mf_classic_data; + + // Verify key + MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, 4); + uint64_t key = nfc_util_bytes2num(sec_tr->key_a, 6); + if(key != two_cities_keys_4k[4].key_a) return false; + + // ===== + // PLANTAIN + // ===== + + // Point to block 0 of sector 4, value 0 + uint8_t* temp_ptr = &data->block[4 * 4].value[0]; + // Read first 4 bytes of block 0 of sector 4 from last to first and convert them to uint32_t + // 38 18 00 00 becomes 00 00 18 38, and equals to 6200 decimal + uint32_t balance = + ((temp_ptr[3] << 24) | (temp_ptr[2] << 16) | (temp_ptr[1] << 8) | temp_ptr[0]) / 100; + // Read card number + // Point to block 0 of sector 0, value 0 + temp_ptr = &data->block[0 * 4].value[0]; + // Read first 7 bytes of block 0 of sector 0 from last to first and convert them to uint64_t + // 80 5C 23 8A 16 31 04 becomes 04 31 16 8A 23 5C 80, and equals to 36130104729284868 decimal + uint8_t card_number_arr[7]; + for(size_t i = 0; i < 7; i++) { + card_number_arr[i] = temp_ptr[6 - i]; + } + // Copy card number to uint64_t + uint64_t card_number = 0; + for(size_t i = 0; i < 7; i++) { + card_number = (card_number << 8) | card_number_arr[i]; + } + // Convert card number to string + string_t card_number_str; + string_init(card_number_str); + // Should look like "361301047292848684" + // %llu doesn't work for some reason in sprintf, so we use string_push_uint64 instead + string_push_uint64(card_number, card_number_str); + // Add suffix with luhn checksum (1 digit) to the card number string + string_t card_number_suffix; + string_init(card_number_suffix); + + // The number to calculate the checksum on doesn't fit into uint64_t, idk + //uint8_t luhn_checksum = two_cities_calculate_luhn(card_number); + + // // Convert luhn checksum to string + // string_t luhn_checksum_str; + // string_init(luhn_checksum_str); + // string_push_uint64(luhn_checksum, luhn_checksum_str); + + string_cat_printf(card_number_suffix, "-"); + // FURI_LOG_D("plant4k", "Card checksum: %d", luhn_checksum); + string_cat_printf(card_number_str, string_get_cstr(card_number_suffix)); + // Free all not needed strings + string_clear(card_number_suffix); + // string_clear(luhn_checksum_str); + + // ===== + // --PLANTAIN-- + // ===== + // TROIKA + // ===== + + uint8_t* troika_temp_ptr = &data->block[8 * 4 + 1].value[5]; + uint16_t troika_balance = ((troika_temp_ptr[0] << 8) | troika_temp_ptr[1]) / 25; + troika_temp_ptr = &data->block[8 * 4].value[3]; + uint32_t troika_number = 0; + for(size_t i = 0; i < 4; i++) { + troika_number <<= 8; + troika_number |= troika_temp_ptr[i]; + } + troika_number >>= 4; + + string_printf( + dev_data->parsed_data, + "\e#Troika+Plantain\nPN: %s\nPB: %d rur.\nTN: %d\nTB: %d rur.\n", + string_get_cstr(card_number_str), + balance, + troika_number, + troika_balance); + string_clear(card_number_str); + + return true; +} diff --git a/lib/nfc/parsers/two_cities.h b/lib/nfc/parsers/two_cities.h new file mode 100644 index 00000000000..e735bea8e1a --- /dev/null +++ b/lib/nfc/parsers/two_cities.h @@ -0,0 +1,9 @@ +#pragma once + +#include "nfc_supported_card.h" + +bool two_cities_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx); + +bool two_cities_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx); + +bool two_cities_parser_parse(NfcDeviceData* dev_data); From f5ff6438d11c44e33a8e7178499b43ccc0084208 Mon Sep 17 00:00:00 2001 From: David Date: Mon, 19 Sep 2022 11:43:53 -0500 Subject: [PATCH 067/824] NFC user dict list, delete, and de-duplication. (#1533) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add MFC user keys list * Leakey submenu fix * Set next target for Save/Delete success scenes * Delete individual user keys * Update count of total keys * Fix memory leak * Check for duplicate keys * Remove a submodule that I never added? * Swap and position icons * Revamp according to design doc * Rename icons to include size and replace keychain icon with smaller variant * Fix typos * Final fixes * Fufill requested changes * Cleanup comments * Merge dev after SD app loading * Fixing icon names * Revert merge mistakes and API version * Scene switching adjustments * F7: add/change/remove some nfc icons in api_symbols.csv Co-authored-by: あく --- .vscode/example/settings.json | 2 +- .../main/lfrfid/views/lfrfid_view_read.c | 2 +- applications/main/nfc/nfc_i.h | 5 + .../main/nfc/scenes/nfc_scene_config.h | 3 + .../nfc/scenes/nfc_scene_delete_success.c | 9 +- .../nfc/scenes/nfc_scene_dict_not_found.c | 5 +- .../main/nfc/scenes/nfc_scene_extra_actions.c | 2 +- .../nfc/scenes/nfc_scene_mf_classic_keys.c | 24 +- .../scenes/nfc_scene_mf_classic_keys_add.c | 13 +- .../scenes/nfc_scene_mf_classic_keys_delete.c | 77 +++++++ .../scenes/nfc_scene_mf_classic_keys_list.c | 60 +++++ ...nfc_scene_mf_classic_keys_warn_duplicate.c | 47 ++++ .../nfc_scene_mf_ultralight_read_auth.c | 2 +- applications/main/nfc/scenes/nfc_scene_read.c | 2 +- .../nfc_scene_restore_original_confirm.c | 2 +- .../main/nfc/scenes/nfc_scene_save_success.c | 5 +- .../bt_hid_app/views/bt_hid_keyboard.c | 2 +- .../plugins/bt_hid_app/views/bt_hid_mouse.c | 2 +- .../desktop/views/desktop_view_pin_input.c | 2 +- assets/icons/NFC/Keychain.png | Bin 3750 -> 0 bytes assets/icons/NFC/Keychain_39x36.png | Bin 0 -> 3775 bytes .../{NFC_manual.png => NFC_manual_60x50.png} | Bin assets/icons/NFC/Reader_detect_43x40.png | Bin 0 -> 3799 bytes .../{Restoring.png => Restoring_38x32.png} | Bin ...n_arrow_up7x9.png => Pin_arrow_up_7x9.png} | Bin firmware/targets/f7/api_symbols.csv | 11 +- lib/nfc/helpers/mf_classic_dict.c | 210 ++++++++++++++++-- lib/nfc/helpers/mf_classic_dict.h | 20 +- 28 files changed, 454 insertions(+), 53 deletions(-) create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_classic_keys_delete.c create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_classic_keys_list.c create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_classic_keys_warn_duplicate.c delete mode 100644 assets/icons/NFC/Keychain.png create mode 100644 assets/icons/NFC/Keychain_39x36.png rename assets/icons/NFC/{NFC_manual.png => NFC_manual_60x50.png} (100%) create mode 100644 assets/icons/NFC/Reader_detect_43x40.png rename assets/icons/NFC/{Restoring.png => Restoring_38x32.png} (100%) rename assets/icons/PIN/{Pin_arrow_up7x9.png => Pin_arrow_up_7x9.png} (100%) diff --git a/.vscode/example/settings.json b/.vscode/example/settings.json index d2917a90ef8..d84707e071b 100644 --- a/.vscode/example/settings.json +++ b/.vscode/example/settings.json @@ -22,4 +22,4 @@ "SConstruct": "python", "*.fam": "python", } -} \ No newline at end of file +} diff --git a/applications/main/lfrfid/views/lfrfid_view_read.c b/applications/main/lfrfid/views/lfrfid_view_read.c index 2b63175da2f..66caf8df747 100644 --- a/applications/main/lfrfid/views/lfrfid_view_read.c +++ b/applications/main/lfrfid/views/lfrfid_view_read.c @@ -16,7 +16,7 @@ static void lfrfid_view_read_draw_callback(Canvas* canvas, void* _model) { LfRfidReadViewModel* model = _model; canvas_set_color(canvas, ColorBlack); - canvas_draw_icon(canvas, 0, 8, &I_NFC_manual); + canvas_draw_icon(canvas, 0, 8, &I_NFC_manual_60x50); canvas_set_font(canvas, FontPrimary); diff --git a/applications/main/nfc/nfc_i.h b/applications/main/nfc/nfc_i.h index 60e1c199721..15ea5348f9f 100644 --- a/applications/main/nfc/nfc_i.h +++ b/applications/main/nfc/nfc_i.h @@ -37,6 +37,10 @@ #include "rpc/rpc_app.h" +#include + +ARRAY_DEF(MfClassicUserKeys, char*, M_PTR_OPLIST); + #define NFC_TEXT_STORE_SIZE 128 typedef enum { @@ -60,6 +64,7 @@ struct Nfc { char text_store[NFC_TEXT_STORE_SIZE + 1]; string_t text_box_store; uint8_t byte_input_store[6]; + MfClassicUserKeys_t mfc_key_strs; // Used in MFC key listing void* rpc_ctx; NfcRpcState rpc_state; diff --git a/applications/main/nfc/scenes/nfc_scene_config.h b/applications/main/nfc/scenes/nfc_scene_config.h index 540fe10987d..a25850c8456 100644 --- a/applications/main/nfc/scenes/nfc_scene_config.h +++ b/applications/main/nfc/scenes/nfc_scene_config.h @@ -32,6 +32,9 @@ ADD_SCENE(nfc, mf_classic_menu, MfClassicMenu) ADD_SCENE(nfc, mf_classic_emulate, MfClassicEmulate) ADD_SCENE(nfc, mf_classic_keys, MfClassicKeys) ADD_SCENE(nfc, mf_classic_keys_add, MfClassicKeysAdd) +ADD_SCENE(nfc, mf_classic_keys_list, MfClassicKeysList) +ADD_SCENE(nfc, mf_classic_keys_delete, MfClassicKeysDelete) +ADD_SCENE(nfc, mf_classic_keys_warn_duplicate, MfClassicKeysWarnDuplicate) ADD_SCENE(nfc, mf_classic_dict_attack, MfClassicDictAttack) ADD_SCENE(nfc, emv_read_success, EmvReadSuccess) ADD_SCENE(nfc, emv_menu, EmvMenu) diff --git a/applications/main/nfc/scenes/nfc_scene_delete_success.c b/applications/main/nfc/scenes/nfc_scene_delete_success.c index 713b99ebf8a..1664a9e5bb1 100644 --- a/applications/main/nfc/scenes/nfc_scene_delete_success.c +++ b/applications/main/nfc/scenes/nfc_scene_delete_success.c @@ -25,8 +25,13 @@ bool nfc_scene_delete_success_on_event(void* context, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { if(event.event == NfcCustomEventViewExit) { - consumed = scene_manager_search_and_switch_to_previous_scene( - nfc->scene_manager, NfcSceneFileSelect); + if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneMfClassicKeys)) { + consumed = scene_manager_search_and_switch_to_previous_scene( + nfc->scene_manager, NfcSceneMfClassicKeys); + } else { + consumed = scene_manager_search_and_switch_to_previous_scene( + nfc->scene_manager, NfcSceneStart); + } } } return consumed; diff --git a/applications/main/nfc/scenes/nfc_scene_dict_not_found.c b/applications/main/nfc/scenes/nfc_scene_dict_not_found.c index dc21b08b1cc..781c5a93254 100644 --- a/applications/main/nfc/scenes/nfc_scene_dict_not_found.c +++ b/applications/main/nfc/scenes/nfc_scene_dict_not_found.c @@ -31,7 +31,10 @@ bool nfc_scene_dict_not_found_on_event(void* context, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { if(event.event == NfcCustomEventViewExit) { - if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneExtraActions)) { + if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneMfClassicKeys)) { + consumed = scene_manager_search_and_switch_to_previous_scene( + nfc->scene_manager, NfcSceneMfClassicKeys); + } else if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneExtraActions)) { consumed = scene_manager_search_and_switch_to_previous_scene( nfc->scene_manager, NfcSceneExtraActions); } else { diff --git a/applications/main/nfc/scenes/nfc_scene_extra_actions.c b/applications/main/nfc/scenes/nfc_scene_extra_actions.c index 43e49e5a0ff..e888e9d357c 100644 --- a/applications/main/nfc/scenes/nfc_scene_extra_actions.c +++ b/applications/main/nfc/scenes/nfc_scene_extra_actions.c @@ -17,7 +17,7 @@ void nfc_scene_extra_actions_on_enter(void* context) { submenu_add_item( submenu, - "Mf Classic Keys", + "Mifare Classic Keys", SubmenuIndexMfClassicKeys, nfc_scene_extra_actions_submenu_callback, nfc); diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys.c index fcb8bc189f7..a2e6ae745fc 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys.c @@ -26,15 +26,25 @@ void nfc_scene_mf_classic_keys_on_enter(void* context) { } widget_add_string_element( - nfc->widget, 0, 0, AlignLeft, AlignTop, FontPrimary, "MF Classic Keys"); + nfc->widget, 0, 0, AlignLeft, AlignTop, FontPrimary, "Mifare Classic Keys"); char temp_str[32]; - snprintf(temp_str, sizeof(temp_str), "Flipper dict: %ld", flipper_dict_keys_total); + snprintf(temp_str, sizeof(temp_str), "Flipper list: %ld", flipper_dict_keys_total); widget_add_string_element(nfc->widget, 0, 20, AlignLeft, AlignTop, FontSecondary, temp_str); - snprintf(temp_str, sizeof(temp_str), "User dict: %ld", user_dict_keys_total); + snprintf(temp_str, sizeof(temp_str), "User list: %ld", user_dict_keys_total); widget_add_string_element(nfc->widget, 0, 32, AlignLeft, AlignTop, FontSecondary, temp_str); widget_add_button_element( nfc->widget, GuiButtonTypeCenter, "Add", nfc_scene_mf_classic_keys_widget_callback, nfc); - widget_add_icon_element(nfc->widget, 90, 12, &I_Keychain); + widget_add_button_element( + nfc->widget, GuiButtonTypeLeft, "Back", nfc_scene_mf_classic_keys_widget_callback, nfc); + widget_add_icon_element(nfc->widget, 87, 13, &I_Keychain_39x36); + if(user_dict_keys_total > 0) { + widget_add_button_element( + nfc->widget, + GuiButtonTypeRight, + "List", + nfc_scene_mf_classic_keys_widget_callback, + nfc); + } view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); } @@ -47,6 +57,12 @@ bool nfc_scene_mf_classic_keys_on_event(void* context, SceneManagerEvent event) if(event.event == GuiButtonTypeCenter) { scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicKeysAdd); consumed = true; + } else if(event.event == GuiButtonTypeLeft) { + scene_manager_previous_scene(nfc->scene_manager); + consumed = true; + } else if(event.event == GuiButtonTypeRight) { + scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicKeysList); + consumed = true; } } diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_add.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_add.c index 9f56b0f452e..2921d21c96e 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_add.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_add.c @@ -29,15 +29,16 @@ bool nfc_scene_mf_classic_keys_add_on_event(void* context, SceneManagerEvent eve if(event.type == SceneManagerEventTypeCustom) { if(event.event == NfcCustomEventByteInputDone) { // Add key to dict - bool key_added = false; MfClassicDict* dict = mf_classic_dict_alloc(MfClassicDictTypeUser); if(dict) { - if(mf_classic_dict_add_key(dict, nfc->byte_input_store)) { - key_added = true; + if(mf_classic_dict_is_key_present(dict, nfc->byte_input_store)) { + scene_manager_next_scene( + nfc->scene_manager, NfcSceneMfClassicKeysWarnDuplicate); + } else if(mf_classic_dict_add_key(dict, nfc->byte_input_store)) { + scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveSuccess); + } else { + scene_manager_next_scene(nfc->scene_manager, NfcSceneDictNotFound); } - } - if(key_added) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveSuccess); } else { scene_manager_next_scene(nfc->scene_manager, NfcSceneDictNotFound); } diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_delete.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_delete.c new file mode 100644 index 00000000000..16a189da626 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_delete.c @@ -0,0 +1,77 @@ +#include "../nfc_i.h" + +void nfc_scene_mf_classic_keys_delete_widget_callback( + GuiButtonType result, + InputType type, + void* context) { + Nfc* nfc = context; + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(nfc->view_dispatcher, result); + } +} + +void nfc_scene_mf_classic_keys_delete_on_enter(void* context) { + Nfc* nfc = context; + MfClassicDict* dict = mf_classic_dict_alloc(MfClassicDictTypeUser); + uint32_t key_index = + scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfClassicKeysDelete); + // Setup Custom Widget view + string_t key_str; + string_init(key_str); + + widget_add_string_element( + nfc->widget, 64, 0, AlignCenter, AlignTop, FontPrimary, "Delete this key?"); + widget_add_button_element( + nfc->widget, + GuiButtonTypeLeft, + "Cancel", + nfc_scene_mf_classic_keys_delete_widget_callback, + nfc); + widget_add_button_element( + nfc->widget, + GuiButtonTypeRight, + "Delete", + nfc_scene_mf_classic_keys_delete_widget_callback, + nfc); + + mf_classic_dict_get_key_at_index_str(dict, key_str, key_index); + widget_add_string_element( + nfc->widget, 64, 32, AlignCenter, AlignCenter, FontSecondary, string_get_cstr(key_str)); + + string_clear(key_str); + mf_classic_dict_free(dict); + + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); +} + +bool nfc_scene_mf_classic_keys_delete_on_event(void* context, SceneManagerEvent event) { + Nfc* nfc = context; + bool consumed = false; + uint32_t key_index = + scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfClassicKeysDelete); + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == GuiButtonTypeLeft) { + consumed = scene_manager_search_and_switch_to_previous_scene( + nfc->scene_manager, NfcSceneMfClassicKeys); + } else if(event.event == GuiButtonTypeRight) { + MfClassicDict* dict = mf_classic_dict_alloc(MfClassicDictTypeUser); + if(mf_classic_dict_delete_index(dict, key_index)) { + scene_manager_next_scene(nfc->scene_manager, NfcSceneDeleteSuccess); + } else { + scene_manager_search_and_switch_to_previous_scene( + nfc->scene_manager, NfcSceneMfClassicKeys); + } + mf_classic_dict_free(dict); + consumed = true; + } + } + + return consumed; +} + +void nfc_scene_mf_classic_keys_delete_on_exit(void* context) { + Nfc* nfc = context; + + widget_reset(nfc->widget); +} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_list.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_list.c new file mode 100644 index 00000000000..36f01897e98 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_list.c @@ -0,0 +1,60 @@ +#include "../nfc_i.h" + +void nfc_scene_mf_classic_keys_list_submenu_callback(void* context, uint32_t index) { + Nfc* nfc = context; + + view_dispatcher_send_custom_event(nfc->view_dispatcher, index); +} + +void nfc_scene_mf_classic_keys_list_on_enter(void* context) { + Nfc* nfc = context; + Submenu* submenu = nfc->submenu; + MfClassicDict* dict = mf_classic_dict_alloc(MfClassicDictTypeUser); + uint32_t index = 0; + string_t temp_key; + MfClassicUserKeys_init(nfc->mfc_key_strs); + string_init(temp_key); + if(dict) { + mf_classic_dict_rewind(dict); + while(mf_classic_dict_get_next_key_str(dict, temp_key)) { + char* current_key = (char*)malloc(sizeof(char) * 13); + strncpy(current_key, string_get_cstr(temp_key), 12); + MfClassicUserKeys_push_back(nfc->mfc_key_strs, current_key); + FURI_LOG_D("ListKeys", "Key %d: %s", index, current_key); + submenu_add_item( + submenu, + current_key, + index++, + nfc_scene_mf_classic_keys_list_submenu_callback, + nfc); + } + } + submenu_set_header(submenu, "Select key to delete:"); + mf_classic_dict_free(dict); + string_clear(temp_key); + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); +} + +bool nfc_scene_mf_classic_keys_list_on_event(void* context, SceneManagerEvent event) { + Nfc* nfc = context; + bool consumed = false; + if(event.type == SceneManagerEventTypeCustom) { + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneMfClassicKeysDelete, event.event); + scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicKeysDelete); + consumed = true; + } + return consumed; +} + +void nfc_scene_mf_classic_keys_list_on_exit(void* context) { + Nfc* nfc = context; + + MfClassicUserKeys_it_t it; + for(MfClassicUserKeys_it(it, nfc->mfc_key_strs); !MfClassicUserKeys_end_p(it); + MfClassicUserKeys_next(it)) { + free(*MfClassicUserKeys_ref(it)); + } + MfClassicUserKeys_clear(nfc->mfc_key_strs); + submenu_reset(nfc->submenu); +} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_warn_duplicate.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_warn_duplicate.c new file mode 100644 index 00000000000..ab41989b2c7 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_warn_duplicate.c @@ -0,0 +1,47 @@ +#include "../nfc_i.h" + +void nfc_scene_mf_classic_keys_warn_duplicate_popup_callback(void* context) { + Nfc* nfc = context; + view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit); +} + +void nfc_scene_mf_classic_keys_warn_duplicate_on_enter(void* context) { + Nfc* nfc = context; + + // Setup view + Popup* popup = nfc->popup; + popup_set_icon(popup, 72, 16, &I_DolphinCommon_56x48); + popup_set_header(popup, "Key already exists!", 64, 3, AlignCenter, AlignTop); + popup_set_text( + popup, + "Please enter a\n" + "different key.", + 4, + 24, + AlignLeft, + AlignTop); + popup_set_timeout(popup, 5000); + popup_set_context(popup, nfc); + popup_set_callback(popup, nfc_scene_mf_classic_keys_warn_duplicate_popup_callback); + popup_enable_timeout(popup); + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); +} + +bool nfc_scene_mf_classic_keys_warn_duplicate_on_event(void* context, SceneManagerEvent event) { + Nfc* nfc = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcCustomEventViewExit) { + consumed = scene_manager_search_and_switch_to_previous_scene( + nfc->scene_manager, NfcSceneMfClassicKeysAdd); + } + } + return consumed; +} + +void nfc_scene_mf_classic_keys_warn_duplicate_on_exit(void* context) { + Nfc* nfc = context; + + popup_reset(nfc->popup); +} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_auth.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_auth.c index 1a106bdb4d4..25008004bc4 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_auth.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_auth.c @@ -27,7 +27,7 @@ void nfc_scene_mf_ultralight_read_auth_set_state(Nfc* nfc, NfcSceneMfUlReadState popup_reset(nfc->popup); popup_set_text( nfc->popup, "Apply card to\nFlipper's back", 97, 24, AlignCenter, AlignTop); - popup_set_icon(nfc->popup, 0, 8, &I_NFC_manual); + popup_set_icon(nfc->popup, 0, 8, &I_NFC_manual_60x50); } else if(state == NfcSceneMfUlReadStateReading) { popup_reset(nfc->popup); popup_set_header( diff --git a/applications/main/nfc/scenes/nfc_scene_read.c b/applications/main/nfc/scenes/nfc_scene_read.c index da21b9f3ddc..e6df476f0dc 100644 --- a/applications/main/nfc/scenes/nfc_scene_read.c +++ b/applications/main/nfc/scenes/nfc_scene_read.c @@ -26,7 +26,7 @@ void nfc_scene_read_set_state(Nfc* nfc, NfcSceneReadState state) { popup_reset(nfc->popup); popup_set_text( nfc->popup, "Apply card to\nFlipper's back", 97, 24, AlignCenter, AlignTop); - popup_set_icon(nfc->popup, 0, 8, &I_NFC_manual); + popup_set_icon(nfc->popup, 0, 8, &I_NFC_manual_60x50); } else if(state == NfcSceneReadStateReading) { popup_reset(nfc->popup); popup_set_header( diff --git a/applications/main/nfc/scenes/nfc_scene_restore_original_confirm.c b/applications/main/nfc/scenes/nfc_scene_restore_original_confirm.c index 2c12749dfa1..730dd41e858 100644 --- a/applications/main/nfc/scenes/nfc_scene_restore_original_confirm.c +++ b/applications/main/nfc/scenes/nfc_scene_restore_original_confirm.c @@ -11,7 +11,7 @@ void nfc_scene_restore_original_confirm_on_enter(void* context) { DialogEx* dialog_ex = nfc->dialog_ex; dialog_ex_set_header(dialog_ex, "Restore Card Data?", 64, 0, AlignCenter, AlignTop); - dialog_ex_set_icon(dialog_ex, 5, 15, &I_Restoring); + dialog_ex_set_icon(dialog_ex, 5, 15, &I_Restoring_38x32); dialog_ex_set_text( dialog_ex, "It will be returned\nto its original state.", 47, 21, AlignLeft, AlignTop); dialog_ex_set_left_button_text(dialog_ex, "Cancel"); diff --git a/applications/main/nfc/scenes/nfc_scene_save_success.c b/applications/main/nfc/scenes/nfc_scene_save_success.c index a3b17451f01..dcd2519f1eb 100644 --- a/applications/main/nfc/scenes/nfc_scene_save_success.c +++ b/applications/main/nfc/scenes/nfc_scene_save_success.c @@ -27,7 +27,10 @@ bool nfc_scene_save_success_on_event(void* context, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { if(event.event == NfcCustomEventViewExit) { - if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSavedMenu)) { + if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneMfClassicKeys)) { + consumed = scene_manager_search_and_switch_to_previous_scene( + nfc->scene_manager, NfcSceneMfClassicKeys); + } else if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSavedMenu)) { consumed = scene_manager_search_and_switch_to_previous_scene( nfc->scene_manager, NfcSceneSavedMenu); } else { diff --git a/applications/plugins/bt_hid_app/views/bt_hid_keyboard.c b/applications/plugins/bt_hid_app/views/bt_hid_keyboard.c index 1088e2959d6..3617dc0f1f6 100644 --- a/applications/plugins/bt_hid_app/views/bt_hid_keyboard.c +++ b/applications/plugins/bt_hid_app/views/bt_hid_keyboard.c @@ -109,7 +109,7 @@ const BtHidKeyboardKey bt_hid_keyboard_keyset[ROW_COUNT][COLUMN_COUNT] = { {.width = 1, .icon = NULL, .key = "-", .shift_key = "_", .value = HID_KEYBOARD_MINUS}, }, { - {.width = 1, .icon = &I_Pin_arrow_up7x9, .value = HID_KEYBOARD_L_SHIFT}, + {.width = 1, .icon = &I_Pin_arrow_up_7x9, .value = HID_KEYBOARD_L_SHIFT}, {.width = 1, .icon = NULL, .key = ",", .shift_key = "<", .value = HID_KEYPAD_COMMA}, {.width = 1, .icon = NULL, .key = ".", .shift_key = ">", .value = HID_KEYBOARD_DOT}, {.width = 4, .icon = NULL, .key = " ", .value = HID_KEYBOARD_SPACEBAR}, diff --git a/applications/plugins/bt_hid_app/views/bt_hid_mouse.c b/applications/plugins/bt_hid_app/views/bt_hid_mouse.c index f9d84f9fbd4..395cb52c93a 100644 --- a/applications/plugins/bt_hid_app/views/bt_hid_mouse.c +++ b/applications/plugins/bt_hid_app/views/bt_hid_mouse.c @@ -53,7 +53,7 @@ static void bt_hid_mouse_draw_callback(Canvas* canvas, void* context) { canvas_set_bitmap_mode(canvas, 0); canvas_set_color(canvas, ColorWhite); } - canvas_draw_icon(canvas, 84, 10, &I_Pin_arrow_up7x9); + canvas_draw_icon(canvas, 84, 10, &I_Pin_arrow_up_7x9); canvas_set_color(canvas, ColorBlack); // Down diff --git a/applications/services/desktop/views/desktop_view_pin_input.c b/applications/services/desktop/views/desktop_view_pin_input.c index 5502d5f6d8b..bf05f06b9c1 100644 --- a/applications/services/desktop/views/desktop_view_pin_input.c +++ b/applications/services/desktop/views/desktop_view_pin_input.c @@ -117,7 +117,7 @@ static void desktop_view_pin_input_draw_cells(Canvas* canvas, DesktopViewPinInpu canvas_draw_icon(canvas, x + 3, y + 2, &I_Pin_arrow_down_7x9); break; case InputKeyUp: - canvas_draw_icon(canvas, x + 3, y + 2, &I_Pin_arrow_up7x9); + canvas_draw_icon(canvas, x + 3, y + 2, &I_Pin_arrow_up_7x9); break; case InputKeyLeft: canvas_draw_icon(canvas, x + 2, y + 3, &I_Pin_arrow_left_9x7); diff --git a/assets/icons/NFC/Keychain.png b/assets/icons/NFC/Keychain.png deleted file mode 100644 index 7ba1b11da6fde4e2b4b148a35593937f2a2ac471..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3750 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 } -bool mf_classic_dict_get_next_key(MfClassicDict* dict, uint64_t* key) { +bool mf_classic_dict_rewind(MfClassicDict* dict) { furi_assert(dict); furi_assert(dict->stream); - uint8_t key_byte_tmp = 0; - string_t next_line; - string_init(next_line); + return stream_rewind(dict->stream); +} + +bool mf_classic_dict_get_next_key_str(MfClassicDict* dict, string_t key) { + furi_assert(dict); + furi_assert(dict->stream); bool key_read = false; - *key = 0ULL; + string_reset(key); while(!key_read) { - if(!stream_read_line(dict->stream, next_line)) break; - if(string_get_char(next_line, 0) == '#') continue; - if(string_size(next_line) != NFC_MF_CLASSIC_KEY_LEN) continue; - for(uint8_t i = 0; i < 12; i += 2) { - args_char_to_hex( - string_get_char(next_line, i), string_get_char(next_line, i + 1), &key_byte_tmp); - *key |= (uint64_t)key_byte_tmp << 8 * (5 - i / 2); - } + if(!stream_read_line(dict->stream, key)) break; + if(string_get_char(key, 0) == '#') continue; + if(string_size(key) != NFC_MF_CLASSIC_KEY_LEN) continue; + string_left(key, 12); key_read = true; } - string_clear(next_line); return key_read; } -bool mf_classic_dict_rewind(MfClassicDict* dict) { +bool mf_classic_dict_get_next_key(MfClassicDict* dict, uint64_t* key) { furi_assert(dict); furi_assert(dict->stream); - return stream_rewind(dict->stream); + string_t temp_key; + string_init(temp_key); + bool key_read = mf_classic_dict_get_next_key_str(dict, temp_key); + if(key_read) { + mf_classic_dict_str_to_int(temp_key, key); + } + string_clear(temp_key); + return key_read; } -bool mf_classic_dict_add_key(MfClassicDict* dict, uint8_t* key) { +bool mf_classic_dict_is_key_present_str(MfClassicDict* dict, string_t key) { furi_assert(dict); furi_assert(dict->stream); - string_t key_str; - string_init(key_str); - for(size_t i = 0; i < 6; i++) { - string_cat_printf(key_str, "%02X", key[i]); + string_t next_line; + string_init(next_line); + + bool key_found = false; + stream_rewind(dict->stream); + while(!key_found) { + if(!stream_read_line(dict->stream, next_line)) break; + if(string_get_char(next_line, 0) == '#') continue; + if(string_size(next_line) != NFC_MF_CLASSIC_KEY_LEN) continue; + string_left(next_line, 12); + if(!string_equal_p(key, next_line)) continue; + key_found = true; } - string_cat_printf(key_str, "\n"); + + string_clear(next_line); + return key_found; +} + +bool mf_classic_dict_is_key_present(MfClassicDict* dict, uint8_t* key) { + string_t temp_key; + + string_init(temp_key); + mf_classic_dict_int_to_str(key, temp_key); + bool key_found = mf_classic_dict_is_key_present_str(dict, temp_key); + string_clear(temp_key); + return key_found; +} + +bool mf_classic_dict_add_key_str(MfClassicDict* dict, string_t key) { + furi_assert(dict); + furi_assert(dict->stream); + + string_cat_printf(key, "\n"); bool key_added = false; do { if(!stream_seek(dict->stream, 0, StreamOffsetFromEnd)) break; - if(!stream_insert_string(dict->stream, key_str)) break; + if(!stream_insert_string(dict->stream, key)) break; + dict->total_keys++; key_added = true; } while(false); - string_clear(key_str); + string_left(key, 12); return key_added; } + +bool mf_classic_dict_add_key(MfClassicDict* dict, uint8_t* key) { + furi_assert(dict); + furi_assert(dict->stream); + + string_t temp_key; + string_init(temp_key); + mf_classic_dict_int_to_str(key, temp_key); + bool key_added = mf_classic_dict_add_key_str(dict, temp_key); + + string_clear(temp_key); + return key_added; +} + +bool mf_classic_dict_get_key_at_index_str(MfClassicDict* dict, string_t key, uint32_t target) { + furi_assert(dict); + furi_assert(dict->stream); + + string_t next_line; + uint32_t index = 0; + string_init(next_line); + string_reset(key); + + bool key_found = false; + while(!key_found) { + if(!stream_read_line(dict->stream, next_line)) break; + if(string_get_char(next_line, 0) == '#') continue; + if(string_size(next_line) != NFC_MF_CLASSIC_KEY_LEN) continue; + if(index++ != target) continue; + string_set_n(key, next_line, 0, 12); + key_found = true; + } + + string_clear(next_line); + return key_found; +} + +bool mf_classic_dict_get_key_at_index(MfClassicDict* dict, uint64_t* key, uint32_t target) { + furi_assert(dict); + furi_assert(dict->stream); + + string_t temp_key; + string_init(temp_key); + bool key_found = mf_classic_dict_get_key_at_index_str(dict, temp_key, target); + if(key_found) { + mf_classic_dict_str_to_int(temp_key, key); + } + string_clear(temp_key); + return key_found; +} + +bool mf_classic_dict_find_index_str(MfClassicDict* dict, string_t key, uint32_t* target) { + furi_assert(dict); + furi_assert(dict->stream); + + string_t next_line; + string_init(next_line); + + bool key_found = false; + uint32_t index = 0; + stream_rewind(dict->stream); + while(!key_found) { + if(!stream_read_line(dict->stream, next_line)) break; + if(string_get_char(next_line, 0) == '#') continue; + if(string_size(next_line) != NFC_MF_CLASSIC_KEY_LEN) continue; + string_left(next_line, 12); + if(!string_equal_p(key, next_line)) continue; + key_found = true; + *target = index; + } + + string_clear(next_line); + return key_found; +} + +bool mf_classic_dict_find_index(MfClassicDict* dict, uint8_t* key, uint32_t* target) { + furi_assert(dict); + furi_assert(dict->stream); + + string_t temp_key; + string_init(temp_key); + mf_classic_dict_int_to_str(key, temp_key); + bool key_found = mf_classic_dict_find_index_str(dict, temp_key, target); + + string_clear(temp_key); + return key_found; +} + +bool mf_classic_dict_delete_index(MfClassicDict* dict, uint32_t target) { + furi_assert(dict); + furi_assert(dict->stream); + + string_t next_line; + string_init(next_line); + uint32_t index = 0; + + bool key_removed = false; + while(!key_removed) { + if(!stream_read_line(dict->stream, next_line)) break; + if(string_get_char(next_line, 0) == '#') continue; + if(string_size(next_line) != NFC_MF_CLASSIC_KEY_LEN) continue; + if(index++ != target) continue; + stream_seek(dict->stream, -NFC_MF_CLASSIC_KEY_LEN, StreamOffsetFromCurrent); + if(!stream_delete(dict->stream, NFC_MF_CLASSIC_KEY_LEN)) break; + dict->total_keys--; + key_removed = true; + } + + string_clear(next_line); + return key_removed; +} diff --git a/lib/nfc/helpers/mf_classic_dict.h b/lib/nfc/helpers/mf_classic_dict.h index 2654e668c54..aaea3c40005 100644 --- a/lib/nfc/helpers/mf_classic_dict.h +++ b/lib/nfc/helpers/mf_classic_dict.h @@ -21,8 +21,26 @@ void mf_classic_dict_free(MfClassicDict* dict); uint32_t mf_classic_dict_get_total_keys(MfClassicDict* dict); +bool mf_classic_dict_rewind(MfClassicDict* dict); + +bool mf_classic_dict_is_key_present(MfClassicDict* dict, uint8_t* key); + +bool mf_classic_dict_is_key_present_str(MfClassicDict* dict, string_t key); + bool mf_classic_dict_get_next_key(MfClassicDict* dict, uint64_t* key); -bool mf_classic_dict_rewind(MfClassicDict* dict); +bool mf_classic_dict_get_next_key_str(MfClassicDict* dict, string_t key); + +bool mf_classic_dict_get_key_at_index(MfClassicDict* dict, uint64_t* key, uint32_t target); + +bool mf_classic_dict_get_key_at_index_str(MfClassicDict* dict, string_t key, uint32_t target); bool mf_classic_dict_add_key(MfClassicDict* dict, uint8_t* key); + +bool mf_classic_dict_add_key_str(MfClassicDict* dict, string_t key); + +bool mf_classic_dict_find_index(MfClassicDict* dict, uint8_t* key, uint32_t* target); + +bool mf_classic_dict_find_index_str(MfClassicDict* dict, string_t key, uint32_t* target); + +bool mf_classic_dict_delete_index(MfClassicDict* dict, uint32_t target); From 066da4080bf5950ece9cb71cbe6f2c75ad0992be Mon Sep 17 00:00:00 2001 From: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> Date: Tue, 20 Sep 2022 08:09:37 +0300 Subject: [PATCH 068/824] [FL-2792] AC Universal Remote (#1725) * Add Universal AC Remote scene * Implement AC gui * Basic working implemetation * Another Universal AC Remote implementation * Update icons * Adjust button positions * Revert old ButtonPanel class * Update resource manifest * [FL-2627] Flipper applications: SDK, build and debug system (#1387) * Update api definitions * Add UniversalRemotes documentation * Use more Flipper-friendly signal names Co-authored-by: SG --- .../infrared/scenes/infrared_scene_config.h | 1 + .../scenes/infrared_scene_universal.c | 14 ++- .../scenes/infrared_scene_universal_ac.c | 109 ++++++++++++++++++ assets/icons/Infrared/CoolHi_25x27.png | Bin 0 -> 3680 bytes assets/icons/Infrared/CoolHi_hvr_25x27.png | Bin 0 -> 3669 bytes assets/icons/Infrared/CoolLo_25x27.png | Bin 0 -> 3676 bytes assets/icons/Infrared/CoolLo_hvr_25x27.png | Bin 0 -> 3657 bytes assets/icons/Infrared/Dehumidify_25x27.png | Bin 0 -> 3665 bytes .../icons/Infrared/Dehumidify_hvr_25x27.png | Bin 0 -> 3652 bytes assets/icons/Infrared/HeatHi_25x27.png | Bin 0 -> 3676 bytes assets/icons/Infrared/HeatHi_hvr_25x27.png | Bin 0 -> 3661 bytes assets/icons/Infrared/HeatLo_25x27.png | Bin 0 -> 3670 bytes assets/icons/Infrared/HeatLo_hvr_25x27.png | Bin 0 -> 3655 bytes assets/icons/Infrared/Off_25x27.png | Bin 0 -> 9530 bytes assets/icons/Infrared/Off_hvr_25x27.png | Bin 0 -> 8460 bytes assets/resources/infrared/assets/ac.ir | 38 ++++++ documentation/UniversalRemotes.md | 36 ++++++ firmware/targets/f7/api_symbols.csv | 14 ++- 18 files changed, 207 insertions(+), 5 deletions(-) create mode 100644 applications/main/infrared/scenes/infrared_scene_universal_ac.c create mode 100644 assets/icons/Infrared/CoolHi_25x27.png create mode 100644 assets/icons/Infrared/CoolHi_hvr_25x27.png create mode 100644 assets/icons/Infrared/CoolLo_25x27.png create mode 100644 assets/icons/Infrared/CoolLo_hvr_25x27.png create mode 100644 assets/icons/Infrared/Dehumidify_25x27.png create mode 100644 assets/icons/Infrared/Dehumidify_hvr_25x27.png create mode 100644 assets/icons/Infrared/HeatHi_25x27.png create mode 100644 assets/icons/Infrared/HeatHi_hvr_25x27.png create mode 100644 assets/icons/Infrared/HeatLo_25x27.png create mode 100644 assets/icons/Infrared/HeatLo_hvr_25x27.png create mode 100644 assets/icons/Infrared/Off_25x27.png create mode 100644 assets/icons/Infrared/Off_hvr_25x27.png create mode 100644 assets/resources/infrared/assets/ac.ir create mode 100644 documentation/UniversalRemotes.md diff --git a/applications/main/infrared/scenes/infrared_scene_config.h b/applications/main/infrared/scenes/infrared_scene_config.h index 26a92056deb..22125fb795e 100644 --- a/applications/main/infrared/scenes/infrared_scene_config.h +++ b/applications/main/infrared/scenes/infrared_scene_config.h @@ -15,6 +15,7 @@ ADD_SCENE(infrared, remote, Remote) ADD_SCENE(infrared, remote_list, RemoteList) ADD_SCENE(infrared, universal, Universal) ADD_SCENE(infrared, universal_tv, UniversalTV) +ADD_SCENE(infrared, universal_ac, UniversalAC) ADD_SCENE(infrared, debug, Debug) ADD_SCENE(infrared, error_databases, ErrorDatabases) ADD_SCENE(infrared, rpc, Rpc) diff --git a/applications/main/infrared/scenes/infrared_scene_universal.c b/applications/main/infrared/scenes/infrared_scene_universal.c index cc6568834cc..2bd7082c451 100644 --- a/applications/main/infrared/scenes/infrared_scene_universal.c +++ b/applications/main/infrared/scenes/infrared_scene_universal.c @@ -2,8 +2,8 @@ typedef enum { SubmenuIndexUniversalTV, + SubmenuIndexUniversalAC, SubmenuIndexUniversalAudio, - SubmenuIndexUniversalAirConditioner, } SubmenuIndex; static void infrared_scene_universal_submenu_callback(void* context, uint32_t index) { @@ -21,6 +21,12 @@ void infrared_scene_universal_on_enter(void* context) { SubmenuIndexUniversalTV, infrared_scene_universal_submenu_callback, context); + submenu_add_item( + submenu, + "Air Conditioners", + SubmenuIndexUniversalAC, + infrared_scene_universal_submenu_callback, + context); submenu_set_selected_item(submenu, 0); view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewSubmenu); @@ -35,12 +41,12 @@ bool infrared_scene_universal_on_event(void* context, SceneManagerEvent event) { if(event.event == SubmenuIndexUniversalTV) { scene_manager_next_scene(scene_manager, InfraredSceneUniversalTV); consumed = true; + } else if(event.event == SubmenuIndexUniversalAC) { + scene_manager_next_scene(scene_manager, InfraredSceneUniversalAC); + consumed = true; } else if(event.event == SubmenuIndexUniversalAudio) { //TODO Implement Audio universal remote consumed = true; - } else if(event.event == SubmenuIndexUniversalAirConditioner) { - //TODO Implement A/C universal remote - consumed = true; } } diff --git a/applications/main/infrared/scenes/infrared_scene_universal_ac.c b/applications/main/infrared/scenes/infrared_scene_universal_ac.c new file mode 100644 index 00000000000..58f067735bc --- /dev/null +++ b/applications/main/infrared/scenes/infrared_scene_universal_ac.c @@ -0,0 +1,109 @@ +#include "../infrared_i.h" + +#include "common/infrared_scene_universal_common.h" + +void infrared_scene_universal_ac_on_enter(void* context) { + infrared_scene_universal_common_on_enter(context); + + Infrared* infrared = context; + ButtonPanel* button_panel = infrared->button_panel; + InfraredBruteForce* brute_force = infrared->brute_force; + + infrared_brute_force_set_db_filename(brute_force, EXT_PATH("infrared/assets/ac.ir")); + + button_panel_reserve(button_panel, 2, 3); + uint32_t i = 0; + button_panel_add_item( + button_panel, + i, + 0, + 0, + 3, + 22, + &I_Off_25x27, + &I_Off_hvr_25x27, + infrared_scene_universal_common_item_callback, + context); + infrared_brute_force_add_record(brute_force, i++, "Off"); + button_panel_add_item( + button_panel, + i, + 1, + 0, + 36, + 22, + &I_Dehumidify_25x27, + &I_Dehumidify_hvr_25x27, + infrared_scene_universal_common_item_callback, + context); + infrared_brute_force_add_record(brute_force, i++, "Dh"); + button_panel_add_item( + button_panel, + i, + 0, + 1, + 3, + 59, + &I_CoolHi_25x27, + &I_CoolHi_hvr_25x27, + infrared_scene_universal_common_item_callback, + context); + infrared_brute_force_add_record(brute_force, i++, "Cool_hi"); + button_panel_add_item( + button_panel, + i, + 1, + 1, + 36, + 59, + &I_HeatHi_25x27, + &I_HeatHi_hvr_25x27, + infrared_scene_universal_common_item_callback, + context); + infrared_brute_force_add_record(brute_force, i++, "Heat_hi"); + button_panel_add_item( + button_panel, + i, + 0, + 2, + 3, + 91, + &I_CoolLo_25x27, + &I_CoolLo_hvr_25x27, + infrared_scene_universal_common_item_callback, + context); + infrared_brute_force_add_record(brute_force, i++, "Cool_lo"); + button_panel_add_item( + button_panel, + i, + 1, + 2, + 36, + 91, + &I_HeatLo_25x27, + &I_HeatLo_hvr_25x27, + infrared_scene_universal_common_item_callback, + context); + infrared_brute_force_add_record(brute_force, i++, "Heat_lo"); + + button_panel_add_label(button_panel, 6, 10, FontPrimary, "AC remote"); + + view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical); + view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack); + + infrared_show_loading_popup(infrared, true); + bool success = infrared_brute_force_calculate_messages(brute_force); + infrared_show_loading_popup(infrared, false); + + if(!success) { + scene_manager_next_scene(infrared->scene_manager, InfraredSceneErrorDatabases); + } +} + +bool infrared_scene_universal_ac_on_event(void* context, SceneManagerEvent event) { + return infrared_scene_universal_common_on_event(context, event); +} + +void infrared_scene_universal_ac_on_exit(void* context) { + infrared_scene_universal_common_on_exit(context); +} diff --git a/assets/icons/Infrared/CoolHi_25x27.png b/assets/icons/Infrared/CoolHi_25x27.png new file mode 100644 index 0000000000000000000000000000000000000000..cea29a5b9e764c88ae56f305368568d04e544876 GIT binary patch literal 3680 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 literal 0 HcmV?d00001 diff --git a/assets/icons/Infrared/CoolHi_hvr_25x27.png b/assets/icons/Infrared/CoolHi_hvr_25x27.png new file mode 100644 index 0000000000000000000000000000000000000000..692ac7b8be718dfe02b460c9ce9a710857feb562 GIT binary patch literal 3669 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 literal 0 HcmV?d00001 diff --git a/assets/icons/Infrared/CoolLo_25x27.png b/assets/icons/Infrared/CoolLo_25x27.png new file mode 100644 index 0000000000000000000000000000000000000000..23288e44f158d4034bebfd6a2a9005514b132a9d GIT binary patch literal 3676 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 literal 0 HcmV?d00001 diff --git a/assets/icons/Infrared/CoolLo_hvr_25x27.png b/assets/icons/Infrared/CoolLo_hvr_25x27.png new file mode 100644 index 0000000000000000000000000000000000000000..ae5316e4d49992b01a39a4298e36cac1fd365388 GIT binary patch literal 3657 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 P_yK!sCtMlE=iI*m`3F!x literal 0 HcmV?d00001 diff --git a/assets/icons/Infrared/Dehumidify_25x27.png b/assets/icons/Infrared/Dehumidify_25x27.png new file mode 100644 index 0000000000000000000000000000000000000000..dca77ae410bcc2196a693023640372da1b1912ea GIT binary patch literal 3665 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 Y0_*BPx59$cJUb46GqS>#p?%qL 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 Tfe|}@hk%WR1FiydCgT49&LB(v literal 0 HcmV?d00001 diff --git a/assets/icons/Infrared/HeatHi_25x27.png b/assets/icons/Infrared/HeatHi_25x27.png new file mode 100644 index 0000000000000000000000000000000000000000..a1724f995562587ec54740789f37729d567e0f88 GIT binary patch literal 3676 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 literal 0 HcmV?d00001 diff --git a/assets/icons/Infrared/HeatHi_hvr_25x27.png b/assets/icons/Infrared/HeatHi_hvr_25x27.png new file mode 100644 index 0000000000000000000000000000000000000000..b92108d68de2ab09bed6b451e36396881e5611ea GIT binary patch literal 3661 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 T-|@M}j{(?QJ7Y`Hry~9hE1FGt literal 0 HcmV?d00001 diff --git a/assets/icons/Infrared/HeatLo_25x27.png b/assets/icons/Infrared/HeatLo_25x27.png new file mode 100644 index 0000000000000000000000000000000000000000..af2e59d4940aefa657c280acb8073800408dabba GIT binary patch literal 3670 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 O0dr$(Y#G`u;{O0;Ay2me literal 0 HcmV?d00001 diff --git a/assets/icons/Infrared/Off_25x27.png b/assets/icons/Infrared/Off_25x27.png new file mode 100644 index 0000000000000000000000000000000000000000..c15100606ac9b2c46fec8d5ed290d72345fb22e2 GIT binary patch literal 9530 zcmeHrcQjnxyT49|lIVR931XBngBiV-AdF5V#u!Yr!C=%VA<;t+C2I62i5f(UmS{IoU@d`S$;^q>!pWEWqTD)n z>+{kEJFgbyG;~F|XJpl8tugLw?LV5%nuyzAf44!);p!9n{m6VU;QM^QA;$*}wu-sN z;GSm+OWX=Yvs^9D&wd zq?xNxoiQx*Gls+yycu>m>XVT@ ztB%TtG1{$nqNVx8)@z>ZSx>C54-`L54foZ{f>Otgd$L1Zh!wah6qZfn$RAml8|K-J z2Zk@bZRQD{iQRd~0?p*8HeV+cJNF<~r^{D1Xsk3&a^kYggR;(hq~wr-b$<*e?Kn$@ zm=^Q%t9#)(bA$f;?*dAqV(X|ki0yE^2OlMxc{VU66!Ckna_Cfm2P`(xf(vFgF~EHW zxQ^hbyzKGH*KHqD3o7-F7p8RST#%OBc zlVoh_=i5JxK0Me98(Uq2)XC*vS|}XDNiBBMnao}&d5uYtXq($C6#-;t9IOgSk`!hd zW?7BIGrkpnRU}XUn5#V$VZr<$U~k~ryur8kx4x#onhl+K-{Lz^v=aEa{&nDCk7F)Q zsW-V9N7$`lp@9)^xU+95#H;njf=`v^*|TGXQ3-yUz1qTqmo6ty*zoSd z%W8M;nJbx%j#T?c*235$pIY!VWwk>Hc^YUe(mrEy*W$#Rz{zok)aU+4GI_wV8dF==vG zm0KH^+%H*|(WFh{oC-;@s5fKt$nxOhuDF)PUOD=*Qh+_I9`fPRc4lYAJ-FMWGLlM0 zbWYw989jSh>$s6~kW18y#8`7nq9Kd-9vo#Zj`^dqip)>?G5!On+NN+SAO5<&#F${l zvwa?WXU6waC04HZncdW&00s- z-ga_~SL6&Q3X5u^PjZ6qb)hs|9*13X%Jf>rOuB*9vVu{25nn2H-!e;!pqYS5O_sug z2MM3}{m>{z*}$i@=Ow5zn^+2Jim0L?glk;gB>~~rkgU|Ce8N=E>^qwW81q#b_2X}e zYHUrBw@AB>Ohi8^lIKwK@-i7=Gp?PGWP3Zb>A;brcu_S~kdTckQRxbXGRi3ROP8Kd znVmqt2*qZNh(eTZu4*p7HsbXoI8kR$M=;&Mo4f}LR*xU^3S%7jQUWZ`^|C7*zKk*o za?TX^xU7>YqG;RiAECL=!|1Ff~Xqivhh9Rj<`w=o~936yd;Kq z*ve!X48my>z0TY|D!G_Rsss?>Z5NU-*n8p+9Zrq1>jn6jIfOzK#mlJ;D&E*kBdAn@ zMcx&URm3ekxbdVni}9fkuKQzNuiym5v;2?fcUoS{mq>~DB@a0<=v`t(pgggA(ltWO z4$4t)5>*(;?HCy{722Xnw<*Lqnq%CzxQFJ46s3*qhemRUDH|j! z-f!g5Y{Df3o(rOldT=a{nhh^74|c!jIvJc{S-; z8Kuj*4MXpLWhghO(I1Z(rwG^?5hQZKT%?$m=^Q0A;UiRM(CGR+lELPauTQNu$SbOt z2a^NmL^!yy1YaO8H6To^hw&i?mSjmA9lw>p-v~DfwhLIqhLgpQK?E7D%Sf`-l)9QO zex{jMtaEvs7;o~-3!q}2Rdpy z5xkXOYvx{_8MBcbe)kozZ6FSHn}4H|G3Rht%`&`V+#crNxi`85eu}*mB=GJ6O#&Qz zqdGQpQv3e-o0z!{>UVo_L`j9*paKm_M|4kHDSgAi+Fj6Cuss-&h44daaR^y0t*P6#Gtg)AYBJBYp8D zde$#&N*y00@PYSwx|#0S$?^62)0Cy_PH(EVm{Fnk4V~_$0B0#bx$H)hRlet|)M;EVNd4S1wKUOA<54w5 znetH_4SlpW`ar;jyoVbrz(-a|e6h%FNJP5Bk3MC?DchVTuMKcb_FloW){hq;Yl_dw zS{Lq}a1iXD-w1tSL~_&I(;EuztR!6v7uELEj}xd#AO+kt_9|6Pc4*0ry!&u!S*THN&(KPz@!my$q#Z^UaDVLhkBBPSCgM z=D6Ulvf}Dm8#O7dD3GWk2G)7DaTMB>{>M!!k3@q|PL|z_}*;@xV zx{MyhQZ;W9mzxR;=;=qZJtuLMWObK`plmCnmU&WLw}K%aXecK9bVJbkX4+Ptbh$1V ziwi)<(wcQ%V5jl0dn=q5<9n?YY0S^{tq8_&=Rk;aB+guDCX_o&cclB$8p*|DQbO#c z$-rFl)Aaq7zC{`$Wx)hl*q+K*HC$-LcqoyxPPQ+{SSVO{)kiLL@`Nep;!(p>`r*ZS7 z;Dnq;v{7+LP7>Xq;&yU;KtI>##|#T1v<74f!R_OSvBu1&XisX)c?N%OI# z>b9wz7JzqW%HD9_o$#_fBO!~BFB_b=MwxHG^xQj^N;W-+vblUn0IJt~@DNa3dEhj` z#A^HW0(H{2>Zc*Af^l^jBR$TRs7ZvL_YmC9uSdJdKDUF|>9NJyzA@N<>_Ha`ixqhg zDaq&?lV=kzB{%!kW0NYH&bW==br}*BOTEozMs$Ao!)V@?5gRwD;yJ+CL7nV^CpzAQ z1t0tc`jZ(rVA1DA^~T1TItNh+(##1L+nO~lQc85amCJ^{G4@s}+>sfh=$?C8{7GLu zxI<-Wnf8Fzb&Y&$=I5>IyUG!h6XR5MIm;l+I*bTvQ`5D!_oeY1X~`q`k}i@L1`S?} zsn?Qob%m9IbyhCnbJ_cB6z0{+i1D&#)9kLHW?GO##}qydY)WV%te_>@o?mON^cZYSrRxqSlYiMIR~C`avO zpRw|6P*Cuj+o}v_Z^1&&EX6Eg!gNcj&s?fZqpBff1m+dju~%znGMyr@V_k7Y}Bjp`a6JW=s~fs|>W%yL2LZSw_=L zF{bFeQ5RrMHDdSGO=k|t{^cfS?+1iijNVWbV1XLNE=1V5Ff}c#YdI!4yw&9k=A!d3 zr>mqfQdl93ZS^V7SsSH>QwK)9;MqOA7bO4}7tjn(E!mN&I=}bMxxj(l&~=j{ZM{sd ziE44d@sKP}C0c$kH=P0PLmOXoDJFVP<+@~qN=TJAS0t>ncK2?ajC_gFOt*kJI`Jh>zA4v zj@NfXTK8Mv!fH8%O*=x7ER23R`q;5`*~@wLaE$r00Mj@Q1M@IITH5!}+9V{@(8{b}BHXaNh1$PP zU*s5P8JAWA9O-$LjbS)Hg5aAkcJ}4mqBGoVK&qcPDL906zh3v+&^+*qb(xOe?pIv2 z$ilrKyN;r)3pC-$;Vv{9QS&oV-`Sa9-`qYFy0csb?yn*rxz!tAxN)MYF2%SuBXOM3 zMVt2Bq@~Xg7?s)ZOf|x`I)!M;b$G zlL2R+u&a1)4XIa@5qrQ?0=>f%j~oi^cjb}yJeC#QK&#tA;J-a<>{g}&F_+TpT7~>3+V>9sXUe?h!cY1DrfC%wJ z@1A8uZ;;Lm%}a<3!qGt+zenrNw33 zeN?;ZZ|d21wRd19cA@Z6ptnC*vhSUh$%bpCFYe&A)5&L-m`AP-2Rn7qW?zBmYE89% zp}SWVw!Lm^=bALl&ZVhWch6HABZe$*k@!t)Gr5Hk)gZh|x>cJZT~r*kIS18~RMt8m z76R%yI$_bk4;rzrDuhauZZ%S)19wSN;zUN}uux2PgRGY=Q;1F&v_mKLTgg6!2dce4 z{I-|ZsGz)XVf3L4dCDDfBfdNNvTeLufTkzFxo9}hjDjoDFi~)^Kaq-r*MpT&ijo-f zQiFMQlEr7r3Fw>8Zoq!T*>x?F%``%MoQgce#-2sm9firgpZWN++$Cp%z7!846!x-a zn{4^RGa=?lTIY>@HLkpnweYl5Q*5s%M=O4zR`O-j3@sdGzN4t=OMCAYHsz{eUaQ6% z!k-2W!D`mKs&jMX3znYiGRSQ& z?esQ8rm(`N;+@#YCrvSd$#v?zQ)Y_EHF>gIZ_S=Cne7%n{V=tl;qx)O_iYbZ@Pv%* zM&0wk%2`D7`%N6hovhXRsYj_}&Jo?^9fwZ?K{8uo$0^(c14rngk5W|f{BS>Tk# z@L`MxTk%NJVLoT<_5M;KYza$bP=uFguJ&^HUG7huiC@PGcs#mtPmV7xm0#$CU0Tu@ zl+#pX1q{h_aT=4ctbggOwY*W;V3Nxq@}|l#x#&@?Mboi`cwvTnPTjeWsBm`QM_B2j z#PbP>Bzrj=pi4z)GRdS%A)_BsN(+2fa-KU!{wi7=>MJiM;8Njp1qy+gkpnXv-ET=D- zk~TP);JdkN;_xKBo<1Fy#oeQI!JF-J^z`&@eQGXX(wia`C# zY{cLxNr~dTl-`QFIi{*~1?TDFQ&D}|aJJ@^6)wW&8Gc;yNt1$Ug~byVI=Tq^6vSl5 zN{3;3ZJSS)bXQNMw!fb2O?YTW+%;3#!1-(L<{whUCDK#*V$Vh8)0n_gl5}Hm3DV^^ zVV|g=amE9d4{L75)Es;ijb_^Z#9wYm%@al{tQKq2{M7OncJJR4S^BY zDC>o+Kc;k(fnSz$AJGQB9u?Ay_S1TM;mz@!G@=i=eGTiPYXFRaf7-+hlE z6Fb)OZ7S=G3u<=$FxOBf=-2v*M(m62%Ys#HC7~QMlIhn=>0cF3fN!dn`^!9IhsXg} zsY+Y?o zA~yV2Of3qae-@tQ((#dI+$CxhKf@+!zv%Hm*n0A~~y0l+ysxp=^Ea=fRwF#Pimu_!O#)CB7&$7`mm4^VM+M*+Yh zU=cB(8V>CR;+3Za$hzCw!QiUuzaa1a+aoH4q(zr(wD z{9*ymhbRt#5fv8^6LoeL{j-M$R?Q0!@++Xf_3$vlpRtR=Q68?I?nsoH7s>_8_g4rw z>W`fM2M~DkzW``x>FWO;!VgaD(axAtR`BfqC0bkL?{*kZcc)VuTcjw;3FVBB$^)OE z_+RX?XuJOq*$>SrAAimSKi%*6f1AgtG2Tj77pCfp^!zcYmZ}^tULs?0^Vqgcwi~YAXe?fq-m4AlpAdTy;m|OBvzxcaexgAtY)@!<6*3R~yQ3$N7yOFD_lN|4lSOGsEe^=|`d(akvMW`aM zDEw?Q1oCGJ9tbbg&&9%H{Zt_x5H9v8{Fd{pM1PZ`|E3fqPzZ4( zQp^sB0)r%gV6cQ3(8kWj7Knr(ByGh(C@2aAJ!Ro{bPrcMtT)0PrDTui5ziIAg-*Ew z2%N5<;9sr19Z)}(2TvGK3<~`3go*x2SoFt6^IOKUqW_(fUo@$r+|l@i)!kj4PwDW$ z;+q_1`=5J6;{Vwrc*cJC{~a^GdJOa*{ZB)eJq`R%J0?D#pECSTjNjNrf8W`EttFnv z|I6R68u`DR0s#1Tl7GbSzjXae*FR$59~u8!UH{Vcj~Mtz#{X8={~BGCe|#IFT<~va z-uN%Sr+1xr@n4N(`Z`8x`1{GpN&ZCE6#j|O=8A?Y!Cg{LP5j^tM$_DbfPjkr$3;kx z_K*cHB*$v$s*x|8p*TY#X?@e93NO;pQdKgd7(?P(Ti>^0PEKaX!jDf*AY#KOC$EFj zi*RrHTJ;my(p|tHkYuWL;!r_oxW6a-m{m T&A>rCE`gSso@%+Wb;y4K-_9ky literal 0 HcmV?d00001 diff --git a/assets/icons/Infrared/Off_hvr_25x27.png b/assets/icons/Infrared/Off_hvr_25x27.png new file mode 100644 index 0000000000000000000000000000000000000000..d5e5e6f45d78e070d06e033bd529d0067de94301 GIT binary patch literal 8460 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 literal 0 HcmV?d00001 diff --git a/assets/resources/infrared/assets/ac.ir b/assets/resources/infrared/assets/ac.ir new file mode 100644 index 00000000000..b1075b2f36e --- /dev/null +++ b/assets/resources/infrared/assets/ac.ir @@ -0,0 +1,38 @@ +Filetype: IR library file +Version: 1 +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 502 3436 510 475 509 476 508 477 507 477 507 479 505 480 504 480 504 490 504 481 502 482 501 483 563 420 511 474 510 475 509 476 508 485 561 423 508 476 508 477 507 478 506 479 505 480 504 481 503 517 508 476 508 478 506 479 505 479 505 481 503 483 521 1456 501 498 507 479 505 480 504 481 503 482 501 483 563 421 562 422 509 499 506 479 505 480 504 481 503 482 502 484 510 1451 506 479 505 1542 562 1396 509 471 502 476 508 469 504 3425 511 +# +name: Dh +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 507 3430 506 479 505 480 504 481 503 481 503 483 501 485 509 1453 504 1465 503 482 502 483 511 473 500 485 509 476 508 477 507 478 506 487 507 477 507 478 506 479 505 480 504 482 502 483 501 484 500 523 503 482 502 484 500 485 509 476 508 476 508 478 506 1456 501 501 504 482 502 483 501 484 500 485 509 476 508 477 507 1455 502 509 506 479 505 1457 500 485 509 476 508 1454 503 482 502 483 501 568 499 1459 509 1450 507 471 502 474 510 3421 505 +# +name: Cool_hi +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 504 3433 503 482 502 484 510 474 510 475 509 476 508 478 506 1456 564 1405 510 475 509 476 508 502 482 477 507 478 506 479 505 480 504 489 505 480 504 481 503 482 502 483 511 473 511 474 510 475 509 509 506 479 505 480 504 481 503 482 512 473 511 474 510 476 508 1469 509 475 509 476 508 477 507 478 506 479 505 480 504 481 503 505 510 475 509 502 482 503 481 504 480 505 478 507 477 1459 509 560 507 1451 506 473 511 493 480 1450 507 3422 503 +# +name: Cool_lo +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 525 3615 530 506 561 474 562 474 562 473 563 473 531 505 562 1502 528 1542 562 474 562 474 530 505 531 504 532 504 532 504 616 419 533 510 589 447 526 509 527 509 527 509 527 508 528 508 528 507 529 542 525 510 526 509 527 509 527 509 527 508 528 508 528 1535 527 524 533 503 533 503 533 502 534 502 534 502 534 501 535 501 525 534 533 502 534 502 534 501 535 502 534 1529 533 503 533 503 533 587 533 497 528 501 524 1536 526 501 524 3609 526 +# +name: Heat_hi +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 531 3406 530 455 529 456 528 457 537 447 537 448 535 450 534 1429 528 1442 536 448 536 449 534 451 532 452 532 453 530 454 530 455 529 464 530 454 529 456 528 457 537 448 536 449 535 450 533 451 533 490 535 449 534 450 534 451 533 452 532 453 531 455 529 1433 534 1443 535 449 535 450 534 452 531 453 530 454 530 455 529 456 538 472 532 452 532 454 530 1433 535 1427 530 1432 536 1427 530 1431 537 1511 530 448 536 1422 535 1423 534 1422 535 3395 530 +# +name: Heat_lo +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 506 3430 506 478 506 479 505 480 504 481 503 482 502 484 500 1463 505 1465 503 482 502 483 501 484 500 485 509 476 508 477 507 478 506 486 508 477 507 478 506 479 505 480 504 481 503 482 502 483 500 523 502 482 502 483 501 484 500 485 509 476 508 478 506 1455 502 498 507 478 506 479 505 481 503 482 501 483 500 484 500 485 509 500 505 481 502 482 502 1461 507 1455 502 1459 509 476 508 477 507 563 504 1453 504 1454 503 1454 503 1453 504 3426 499 diff --git a/documentation/UniversalRemotes.md b/documentation/UniversalRemotes.md new file mode 100644 index 00000000000..ac98d451f4f --- /dev/null +++ b/documentation/UniversalRemotes.md @@ -0,0 +1,36 @@ +# Universal Remotes +## Air Conditioners +### Recording signals +Air conditioners differ from most other infrared-controlled devices because their state is tracked by the remote. +The majority of A/C remotes have a small display which shows current mode, temperature and other settings. +When the user presses a button, a whole set of parameters is transmitted to the device, which must be recorded and used as a whole. + +In order to add a particular air conditioner to the universal remote, 6 signals must be recorded: `Off`, `Dh`, `Cool_hi`, `Cool_lo`, `Heat_hi`, `Heat_lo`. +Each signal (except `Off`) is recorded using the following algorithm: + +1. Get the remote and press the **Power Button** so that the display shows that A/C is ON. +2. Set the A/C to the corresponding mode (see table below), while leaving other parameters such as fan speed or vane on **AUTO** (if applicable). +3. Press the **POWER** button to switch the A/C off. +4. Start learning a new remote on Flipper if it's the first button or press `+` to add a new button otherwise. +5. Point the remote to Flipper's IR receiver as directed and press **POWER** button once again. +6. Save the resulting signal under the specified name. +7. Repeat the steps 2-6 for each signal from the table below. + +| Signal | Mode | Temperature | Note | +| :-----: | :--------: | :---------: | ----------------------------------- | +| Dh | Dehumidify | N/A | | +| Cool_hi | Cooling | See note | Lowest temperature in cooling mode | +| Cool_lo | Cooling | 23°C | | +| Heat_hi | Heating | See note | Highest temperature in heating mode | +| Heat_lo | Heating | 23°C | | + +Finally, record the `Off` signal: +1. Make sure the display shows that A/C is ON. +2. Start learning a new signal on Flipper and point the remote towards the IR receiver. +3. Press the **POWER** button so that the remote shows the OFF state. +4. Save the resulting signal under the name `Off`. + +The resulting remote file should now contain 6 signals. Any of them can be omitted, but that will mean that this functionality will not be used. +Test the file against the actual device. Every signal must do what it's supposed to. +If everything checks out, add these signals to the [A/C universal remote file](/assets/resources/infrared/assets/ac.ir) +and open a pull request. diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 331cda812e8..8dff220ee01 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,1.6,, +Version,+,1.7,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -2580,8 +2580,14 @@ Variable,+,I_Circles_47x47,const Icon, Variable,+,I_Clock_18x18,const Icon, Variable,+,I_Connect_me_62x31,const Icon, Variable,+,I_Connected_62x31,const Icon, +Variable,+,I_CoolHi_25x27,const Icon, +Variable,+,I_CoolHi_hvr_25x27,const Icon, +Variable,+,I_CoolLo_25x27,const Icon, +Variable,+,I_CoolLo_hvr_25x27,const Icon, Variable,+,I_Cry_dolph_55x52,const Icon, Variable,+,I_DFU_128x50,const Icon, +Variable,+,I_Dehumidify_25x27,const Icon, +Variable,+,I_Dehumidify_hvr_25x27,const Icon, Variable,+,I_Detailed_chip_17x13,const Icon, Variable,+,I_DolphinCommon_56x48,const Icon, Variable,+,I_DolphinMafia_115x62,const Icon, @@ -2605,6 +2611,10 @@ Variable,+,I_FaceNopower_29x14,const Icon, Variable,+,I_FaceNormal_29x14,const Icon, Variable,+,I_GameMode_11x8,const Icon, Variable,+,I_Health_16x16,const Icon, +Variable,+,I_HeatHi_25x27,const Icon, +Variable,+,I_HeatHi_hvr_25x27,const Icon, +Variable,+,I_HeatLo_25x27,const Icon, +Variable,+,I_HeatLo_hvr_25x27,const Icon, Variable,+,I_InfraredArrowDown_4x8,const Icon, Variable,+,I_InfraredArrowUp_4x8,const Icon, Variable,+,I_InfraredLearnShort_128x31,const Icon, @@ -2622,6 +2632,8 @@ Variable,+,I_Mute_25x27,const Icon, Variable,+,I_Mute_hvr_25x27,const Icon, Variable,+,I_NFC_manual_60x50,const Icon, Variable,+,I_Nfc_10px,const Icon, +Variable,+,I_Off_25x27,const Icon, +Variable,+,I_Off_hvr_25x27,const Icon, Variable,+,I_Ok_btn_9x9,const Icon, Variable,+,I_Ok_btn_pressed_13x13,const Icon, Variable,+,I_Percent_10x14,const Icon, From 3360f818a1d10771c2e70616ddedccca37d33a86 Mon Sep 17 00:00:00 2001 From: Max Lapan Date: Tue, 20 Sep 2022 07:29:10 +0200 Subject: [PATCH 069/824] Subghz: Adding checks for get_upload functions (#1704) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Adding checks for get_upload functions Almost in every protocol, function which generates upload might fail and return false. But we don't check this result, which might end up sending random memory contents to the air. * Format sources and fix crash on ivalid bit count in chamberlain Co-authored-by: あく --- lib/subghz/protocols/bett.c | 2 +- lib/subghz/protocols/came.c | 2 +- lib/subghz/protocols/chamberlain_code.c | 4 ++-- lib/subghz/protocols/clemsa.c | 2 +- lib/subghz/protocols/doitrand.c | 2 +- lib/subghz/protocols/gate_tx.c | 2 +- lib/subghz/protocols/holtek.c | 2 +- lib/subghz/protocols/honeywell_wdb.c | 2 +- lib/subghz/protocols/hormann.c | 2 +- lib/subghz/protocols/intertechno_v3.c | 2 +- lib/subghz/protocols/keeloq.c | 2 +- lib/subghz/protocols/linear.c | 2 +- lib/subghz/protocols/magellen.c | 2 +- lib/subghz/protocols/megacode.c | 2 +- lib/subghz/protocols/nero_radio.c | 2 +- lib/subghz/protocols/nero_sketch.c | 2 +- lib/subghz/protocols/nice_flo.c | 2 +- lib/subghz/protocols/phoenix_v2.c | 2 +- lib/subghz/protocols/princeton.c | 2 +- 19 files changed, 20 insertions(+), 20 deletions(-) diff --git a/lib/subghz/protocols/bett.c b/lib/subghz/protocols/bett.c index 08080dc6c9f..c80702577e3 100644 --- a/lib/subghz/protocols/bett.c +++ b/lib/subghz/protocols/bett.c @@ -173,7 +173,7 @@ bool subghz_protocol_encoder_bett_deserialize(void* context, FlipperFormat* flip flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - subghz_protocol_encoder_bett_get_upload(instance); + if(!subghz_protocol_encoder_bett_get_upload(instance)) break; instance->encoder.is_running = true; res = true; diff --git a/lib/subghz/protocols/came.c b/lib/subghz/protocols/came.c index 14c66b7fa99..53d3d078868 100644 --- a/lib/subghz/protocols/came.c +++ b/lib/subghz/protocols/came.c @@ -162,7 +162,7 @@ bool subghz_protocol_encoder_came_deserialize(void* context, FlipperFormat* flip flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - subghz_protocol_encoder_came_get_upload(instance); + if(!subghz_protocol_encoder_came_get_upload(instance)) break; instance->encoder.is_running = true; res = true; diff --git a/lib/subghz/protocols/chamberlain_code.c b/lib/subghz/protocols/chamberlain_code.c index 51f2bcd323a..66d230d1383 100644 --- a/lib/subghz/protocols/chamberlain_code.c +++ b/lib/subghz/protocols/chamberlain_code.c @@ -155,7 +155,7 @@ static bool break; default: - furi_crash(TAG " unknown protocol."); + FURI_LOG_E(TAG, "Invalid bits count"); return false; break; } @@ -224,7 +224,7 @@ bool subghz_protocol_encoder_chamb_code_deserialize(void* context, FlipperFormat flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - subghz_protocol_encoder_chamb_code_get_upload(instance); + if(!subghz_protocol_encoder_chamb_code_get_upload(instance)) break; instance->encoder.is_running = true; res = true; diff --git a/lib/subghz/protocols/clemsa.c b/lib/subghz/protocols/clemsa.c index 357a0b06ded..337346934b8 100644 --- a/lib/subghz/protocols/clemsa.c +++ b/lib/subghz/protocols/clemsa.c @@ -173,7 +173,7 @@ bool subghz_protocol_encoder_clemsa_deserialize(void* context, FlipperFormat* fl flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - subghz_protocol_encoder_clemsa_get_upload(instance); + if(!subghz_protocol_encoder_clemsa_get_upload(instance)) break; instance->encoder.is_running = true; res = true; diff --git a/lib/subghz/protocols/doitrand.c b/lib/subghz/protocols/doitrand.c index 9a0a58190df..9122c19350b 100644 --- a/lib/subghz/protocols/doitrand.c +++ b/lib/subghz/protocols/doitrand.c @@ -154,7 +154,7 @@ bool subghz_protocol_encoder_doitrand_deserialize(void* context, FlipperFormat* flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - subghz_protocol_encoder_doitrand_get_upload(instance); + if(!subghz_protocol_encoder_doitrand_get_upload(instance)) break; instance->encoder.is_running = true; res = true; diff --git a/lib/subghz/protocols/gate_tx.c b/lib/subghz/protocols/gate_tx.c index d7efb3862cb..56c224aefcf 100644 --- a/lib/subghz/protocols/gate_tx.c +++ b/lib/subghz/protocols/gate_tx.c @@ -147,7 +147,7 @@ bool subghz_protocol_encoder_gate_tx_deserialize(void* context, FlipperFormat* f flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - subghz_protocol_encoder_gate_tx_get_upload(instance); + if(!subghz_protocol_encoder_gate_tx_get_upload(instance)) break; instance->encoder.is_running = true; res = true; diff --git a/lib/subghz/protocols/holtek.c b/lib/subghz/protocols/holtek.c index 137ba85d388..5cd1606338c 100644 --- a/lib/subghz/protocols/holtek.c +++ b/lib/subghz/protocols/holtek.c @@ -160,7 +160,7 @@ bool subghz_protocol_encoder_holtek_deserialize(void* context, FlipperFormat* fl flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - subghz_protocol_encoder_holtek_get_upload(instance); + if(!subghz_protocol_encoder_holtek_get_upload(instance)) break; instance->encoder.is_running = true; res = true; diff --git a/lib/subghz/protocols/honeywell_wdb.c b/lib/subghz/protocols/honeywell_wdb.c index e1e21426d2a..451a13f506e 100644 --- a/lib/subghz/protocols/honeywell_wdb.c +++ b/lib/subghz/protocols/honeywell_wdb.c @@ -162,7 +162,7 @@ bool subghz_protocol_encoder_honeywell_wdb_deserialize( flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - subghz_protocol_encoder_honeywell_wdb_get_upload(instance); + if(!subghz_protocol_encoder_honeywell_wdb_get_upload(instance)) break; instance->encoder.is_running = true; res = true; diff --git a/lib/subghz/protocols/hormann.c b/lib/subghz/protocols/hormann.c index 0197f59e6b5..d78bc9273c3 100644 --- a/lib/subghz/protocols/hormann.c +++ b/lib/subghz/protocols/hormann.c @@ -163,7 +163,7 @@ bool subghz_protocol_encoder_hormann_deserialize(void* context, FlipperFormat* f flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - subghz_protocol_encoder_hormann_get_upload(instance); + if(!subghz_protocol_encoder_hormann_get_upload(instance)) break; instance->encoder.is_running = true; res = true; diff --git a/lib/subghz/protocols/intertechno_v3.c b/lib/subghz/protocols/intertechno_v3.c index e70bb8c8b7e..ffe52e8754f 100644 --- a/lib/subghz/protocols/intertechno_v3.c +++ b/lib/subghz/protocols/intertechno_v3.c @@ -179,7 +179,7 @@ bool subghz_protocol_encoder_intertechno_v3_deserialize( flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - subghz_protocol_encoder_intertechno_v3_get_upload(instance); + if(!subghz_protocol_encoder_intertechno_v3_get_upload(instance)) break; instance->encoder.is_running = true; res = true; diff --git a/lib/subghz/protocols/keeloq.c b/lib/subghz/protocols/keeloq.c index 0321a876773..99b3c5fb342 100644 --- a/lib/subghz/protocols/keeloq.c +++ b/lib/subghz/protocols/keeloq.c @@ -280,7 +280,7 @@ bool subghz_protocol_encoder_keeloq_deserialize(void* context, FlipperFormat* fl flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - subghz_protocol_encoder_keeloq_get_upload(instance, instance->generic.btn); + if(!subghz_protocol_encoder_keeloq_get_upload(instance, instance->generic.btn)) break; if(!flipper_format_rewind(flipper_format)) { FURI_LOG_E(TAG, "Rewind error"); diff --git a/lib/subghz/protocols/linear.c b/lib/subghz/protocols/linear.c index 92ba02a8fbd..8f7aed794b0 100644 --- a/lib/subghz/protocols/linear.c +++ b/lib/subghz/protocols/linear.c @@ -165,7 +165,7 @@ bool subghz_protocol_encoder_linear_deserialize(void* context, FlipperFormat* fl flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - subghz_protocol_encoder_linear_get_upload(instance); + if(!subghz_protocol_encoder_linear_get_upload(instance)) break; instance->encoder.is_running = true; res = true; diff --git a/lib/subghz/protocols/magellen.c b/lib/subghz/protocols/magellen.c index bb0600a7453..52ef5a72470 100644 --- a/lib/subghz/protocols/magellen.c +++ b/lib/subghz/protocols/magellen.c @@ -168,7 +168,7 @@ bool subghz_protocol_encoder_magellen_deserialize(void* context, FlipperFormat* flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - subghz_protocol_encoder_magellen_get_upload(instance); + if(!subghz_protocol_encoder_magellen_get_upload(instance)) break; instance->encoder.is_running = true; res = true; diff --git a/lib/subghz/protocols/megacode.c b/lib/subghz/protocols/megacode.c index 909e721714e..1501580d8b5 100644 --- a/lib/subghz/protocols/megacode.c +++ b/lib/subghz/protocols/megacode.c @@ -193,7 +193,7 @@ bool subghz_protocol_encoder_megacode_deserialize(void* context, FlipperFormat* flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - subghz_protocol_encoder_megacode_get_upload(instance); + if(!subghz_protocol_encoder_megacode_get_upload(instance)) break; instance->encoder.is_running = true; res = true; diff --git a/lib/subghz/protocols/nero_radio.c b/lib/subghz/protocols/nero_radio.c index 69326f5a088..b5a7e8c0ebe 100644 --- a/lib/subghz/protocols/nero_radio.c +++ b/lib/subghz/protocols/nero_radio.c @@ -172,7 +172,7 @@ bool subghz_protocol_encoder_nero_radio_deserialize(void* context, FlipperFormat flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - subghz_protocol_encoder_nero_radio_get_upload(instance); + if(!subghz_protocol_encoder_nero_radio_get_upload(instance)) break; instance->encoder.is_running = true; res = true; diff --git a/lib/subghz/protocols/nero_sketch.c b/lib/subghz/protocols/nero_sketch.c index c93b36a53a5..66ee569c22d 100644 --- a/lib/subghz/protocols/nero_sketch.c +++ b/lib/subghz/protocols/nero_sketch.c @@ -166,7 +166,7 @@ bool subghz_protocol_encoder_nero_sketch_deserialize(void* context, FlipperForma flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - subghz_protocol_encoder_nero_sketch_get_upload(instance); + if(!subghz_protocol_encoder_nero_sketch_get_upload(instance)) break; instance->encoder.is_running = true; res = true; diff --git a/lib/subghz/protocols/nice_flo.c b/lib/subghz/protocols/nice_flo.c index 07b18e3ea37..f07e9efcc1d 100644 --- a/lib/subghz/protocols/nice_flo.c +++ b/lib/subghz/protocols/nice_flo.c @@ -149,7 +149,7 @@ bool subghz_protocol_encoder_nice_flo_deserialize(void* context, FlipperFormat* flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - subghz_protocol_encoder_nice_flo_get_upload(instance); + if(!subghz_protocol_encoder_nice_flo_get_upload(instance)) break; instance->encoder.is_running = true; res = true; diff --git a/lib/subghz/protocols/phoenix_v2.c b/lib/subghz/protocols/phoenix_v2.c index 3d2796e441b..d680b2e62b7 100644 --- a/lib/subghz/protocols/phoenix_v2.c +++ b/lib/subghz/protocols/phoenix_v2.c @@ -150,7 +150,7 @@ bool subghz_protocol_encoder_phoenix_v2_deserialize(void* context, FlipperFormat flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - subghz_protocol_encoder_phoenix_v2_get_upload(instance); + if(!subghz_protocol_encoder_phoenix_v2_get_upload(instance)) break; instance->encoder.is_running = true; res = true; diff --git a/lib/subghz/protocols/princeton.c b/lib/subghz/protocols/princeton.c index 2ddfa2cb683..a5b8134d87b 100644 --- a/lib/subghz/protocols/princeton.c +++ b/lib/subghz/protocols/princeton.c @@ -167,7 +167,7 @@ bool subghz_protocol_encoder_princeton_deserialize(void* context, FlipperFormat* flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - subghz_protocol_encoder_princeton_get_upload(instance); + if(!subghz_protocol_encoder_princeton_get_upload(instance)) break; instance->encoder.is_running = true; res = true; From 432ff41d6a33478adeb012cf84f626fa89fc0e65 Mon Sep 17 00:00:00 2001 From: hedger Date: Wed, 21 Sep 2022 18:42:59 +0400 Subject: [PATCH 070/824] [FL-2844] desktop: removing slideshow file when leaving slideshow view (#1762) * [FL-2844] desktop: removing slideshow file when leaving slideshow view; vscode: fix for BM port fetcher; fap api: more symbols for LL * desktop: actually removing slideshow file * desktop: moved slideshow removal to scene code; fbt: better blackmagic device handling * fbt: disabled pagination for gdb * vscode: restored blackmagic command line * fbt: fixed debug_other target; added debug_other_blackmagic * furi: added furi_thread_suspend API group; fixed null-pointer deref for thread name; cleaned up RTOS config * furi: changed thread state check to eTaskGetState --- .vscode/example/launch.json | 4 ++ SConstruct | 13 ++++++- .../desktop/scenes/desktop_scene_slideshow.c | 8 ++-- documentation/fbt.md | 2 +- firmware/targets/f7/Inc/FreeRTOSConfig.h | 13 +------ firmware/targets/f7/api_symbols.csv | 37 ++++++++++--------- furi/core/thread.c | 25 ++++++++++++- furi/core/thread.h | 19 ++++++++++ 8 files changed, 84 insertions(+), 37 deletions(-) diff --git a/.vscode/example/launch.json b/.vscode/example/launch.json index f9470a74091..7cb2542de3e 100644 --- a/.vscode/example/launch.json +++ b/.vscode/example/launch.json @@ -9,6 +9,10 @@ "type": "command", "command": "shellCommand.execute", "args": { + "useSingleResult": true, + "env": { + "PATH": "${workspaceFolder};${env:PATH}" + }, "command": "./fbt get_blackmagic", "description": "Get Blackmagic device", } diff --git a/SConstruct b/SConstruct index f39bba686fc..5ad2ac3c8ae 100644 --- a/SConstruct +++ b/SConstruct @@ -44,6 +44,8 @@ distenv = coreenv.Clone( "target extended-remote ${GDBREMOTE}", "-ex", "set confirm off", + "-ex", + "set pagination off", ], GDBOPTS_BLACKMAGIC=[ "-ex", @@ -234,10 +236,19 @@ distenv.PhonyTarget( distenv.PhonyTarget( "debug_other", "${GDBPYCOM}", - GDBPYOPTS='-ex "source debug/PyCortexMDebug/PyCortexMDebug.py" ', + GDBOPTS="${GDBOPTS_BASE}", GDBREMOTE="${OPENOCD_GDB_PIPE}", + GDBPYOPTS='-ex "source debug/PyCortexMDebug/PyCortexMDebug.py" ', ) +distenv.PhonyTarget( + "debug_other_blackmagic", + "${GDBPYCOM}", + GDBOPTS="${GDBOPTS_BASE} ${GDBOPTS_BLACKMAGIC}", + GDBREMOTE="$${BLACKMAGIC_ADDR}", +) + + # Just start OpenOCD distenv.PhonyTarget( "openocd", diff --git a/applications/services/desktop/scenes/desktop_scene_slideshow.c b/applications/services/desktop/scenes/desktop_scene_slideshow.c index cab7bf62b18..012aff75196 100644 --- a/applications/services/desktop/scenes/desktop_scene_slideshow.c +++ b/applications/services/desktop/scenes/desktop_scene_slideshow.c @@ -22,15 +22,11 @@ void desktop_scene_slideshow_on_enter(void* context) { bool desktop_scene_slideshow_on_event(void* context, SceneManagerEvent event) { Desktop* desktop = (Desktop*)context; bool consumed = false; - Storage* storage = NULL; Power* power = NULL; if(event.type == SceneManagerEventTypeCustom) { switch(event.event) { case DesktopSlideshowCompleted: - storage = furi_record_open(RECORD_STORAGE); - storage_common_remove(storage, SLIDESHOW_FS_PATH); - furi_record_close(RECORD_STORAGE); scene_manager_previous_scene(desktop->scene_manager); consumed = true; break; @@ -50,4 +46,8 @@ bool desktop_scene_slideshow_on_event(void* context, SceneManagerEvent event) { void desktop_scene_slideshow_on_exit(void* context) { UNUSED(context); + + Storage* storage = furi_record_open(RECORD_STORAGE); + storage_common_remove(storage, SLIDESHOW_FS_PATH); + furi_record_close(RECORD_STORAGE); } diff --git a/documentation/fbt.md b/documentation/fbt.md index 3eee6baaa0f..090ff78f0ee 100644 --- a/documentation/fbt.md +++ b/documentation/fbt.md @@ -49,7 +49,7 @@ To run cleanup (think of `make clean`) for specified targets, add `-c` option. - `flash` - flash attached device with OpenOCD over ST-Link - `flash_usb`, `flash_usb_full` - build, upload and install update package to device over USB. See details on `updater_package`, `updater_minpackage` - `debug` - build and flash firmware, then attach with gdb with firmware's .elf loaded -- `debug_other` - attach gdb without loading any .elf. Allows to manually add external elf files with `add-symbol-file` in gdb +- `debug_other`, `debug_other_blackmagic` - attach gdb without loading any .elf. Allows to manually add external elf files with `add-symbol-file` in gdb - `updater_debug` - attach gdb with updater's .elf loaded - `blackmagic` - debug firmware with Blackmagic probe (WiFi dev board) - `openocd` - just start OpenOCD diff --git a/firmware/targets/f7/Inc/FreeRTOSConfig.h b/firmware/targets/f7/Inc/FreeRTOSConfig.h index ab2dc14ef43..69ef9406bb2 100644 --- a/firmware/targets/f7/Inc/FreeRTOSConfig.h +++ b/firmware/targets/f7/Inc/FreeRTOSConfig.h @@ -76,19 +76,8 @@ to exclude the API function. */ #define INCLUDE_xTaskGetSchedulerState 1 #define INCLUDE_xTimerPendFunctionCall 1 -/* CMSIS-RTOS V2 flags */ -#define configUSE_OS2_THREAD_SUSPEND_RESUME 1 -#define configUSE_OS2_THREAD_ENUMERATE 1 -#define configUSE_OS2_THREAD_FLAGS 1 -#define configUSE_OS2_TIMER 1 -#define configUSE_OS2_MUTEX 1 - -// NEVER TO BE USED, because of their hard realtime nature -// #define configUSE_OS2_EVENTFLAGS_FROM_ISR 1 - -/* CMSIS-RTOS */ +/* Furi-specific */ #define configTASK_NOTIFICATION_ARRAY_ENTRIES 2 -#define CMSIS_TASK_NOTIFY_INDEX 1 extern __attribute__((__noreturn__)) void furi_thread_catch(); #define configTASK_RETURN_ADDRESS (furi_thread_catch + 2) diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 8dff220ee01..ac4df046d57 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,1.7,, +Version,+,1.9,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -157,29 +157,29 @@ Function,-,LL_ADC_REG_Init,ErrorStatus,"ADC_TypeDef*, LL_ADC_REG_InitTypeDef*" Function,-,LL_ADC_REG_StructInit,void,LL_ADC_REG_InitTypeDef* Function,-,LL_ADC_StructInit,void,LL_ADC_InitTypeDef* Function,-,LL_COMP_DeInit,ErrorStatus,COMP_TypeDef* -Function,-,LL_COMP_Init,ErrorStatus,"COMP_TypeDef*, LL_COMP_InitTypeDef*" +Function,+,LL_COMP_Init,ErrorStatus,"COMP_TypeDef*, LL_COMP_InitTypeDef*" Function,-,LL_COMP_StructInit,void,LL_COMP_InitTypeDef* Function,-,LL_CRC_DeInit,ErrorStatus,CRC_TypeDef* Function,-,LL_CRS_DeInit,ErrorStatus, -Function,-,LL_DMA_DeInit,ErrorStatus,"DMA_TypeDef*, uint32_t" -Function,-,LL_DMA_Init,ErrorStatus,"DMA_TypeDef*, uint32_t, LL_DMA_InitTypeDef*" +Function,+,LL_DMA_DeInit,ErrorStatus,"DMA_TypeDef*, uint32_t" +Function,+,LL_DMA_Init,ErrorStatus,"DMA_TypeDef*, uint32_t, LL_DMA_InitTypeDef*" Function,-,LL_DMA_StructInit,void,LL_DMA_InitTypeDef* Function,-,LL_EXTI_DeInit,ErrorStatus, Function,-,LL_EXTI_Init,ErrorStatus,LL_EXTI_InitTypeDef* Function,-,LL_EXTI_StructInit,void,LL_EXTI_InitTypeDef* Function,-,LL_GPIO_DeInit,ErrorStatus,GPIO_TypeDef* -Function,-,LL_GPIO_Init,ErrorStatus,"GPIO_TypeDef*, LL_GPIO_InitTypeDef*" +Function,+,LL_GPIO_Init,ErrorStatus,"GPIO_TypeDef*, LL_GPIO_InitTypeDef*" Function,-,LL_GPIO_StructInit,void,LL_GPIO_InitTypeDef* Function,-,LL_I2C_DeInit,ErrorStatus,I2C_TypeDef* -Function,-,LL_I2C_Init,ErrorStatus,"I2C_TypeDef*, LL_I2C_InitTypeDef*" +Function,+,LL_I2C_Init,ErrorStatus,"I2C_TypeDef*, LL_I2C_InitTypeDef*" Function,-,LL_I2C_StructInit,void,LL_I2C_InitTypeDef* Function,-,LL_Init1msTick,void,uint32_t -Function,-,LL_LPTIM_DeInit,ErrorStatus,LPTIM_TypeDef* +Function,+,LL_LPTIM_DeInit,ErrorStatus,LPTIM_TypeDef* Function,-,LL_LPTIM_Disable,void,LPTIM_TypeDef* -Function,-,LL_LPTIM_Init,ErrorStatus,"LPTIM_TypeDef*, LL_LPTIM_InitTypeDef*" +Function,+,LL_LPTIM_Init,ErrorStatus,"LPTIM_TypeDef*, LL_LPTIM_InitTypeDef*" Function,-,LL_LPTIM_StructInit,void,LL_LPTIM_InitTypeDef* Function,-,LL_LPUART_DeInit,ErrorStatus,USART_TypeDef* -Function,-,LL_LPUART_Init,ErrorStatus,"USART_TypeDef*, LL_LPUART_InitTypeDef*" +Function,+,LL_LPUART_Init,ErrorStatus,"USART_TypeDef*, LL_LPUART_InitTypeDef*" Function,-,LL_LPUART_StructInit,void,LL_LPUART_InitTypeDef* Function,-,LL_PKA_DeInit,ErrorStatus,PKA_TypeDef* Function,-,LL_PKA_Init,ErrorStatus,"PKA_TypeDef*, LL_PKA_InitTypeDef*" @@ -193,14 +193,14 @@ Function,-,LL_RCC_GetADCClockFreq,uint32_t,uint32_t Function,-,LL_RCC_GetCLK48ClockFreq,uint32_t,uint32_t Function,-,LL_RCC_GetI2CClockFreq,uint32_t,uint32_t Function,-,LL_RCC_GetLPTIMClockFreq,uint32_t,uint32_t -Function,-,LL_RCC_GetLPUARTClockFreq,uint32_t,uint32_t +Function,+,LL_RCC_GetLPUARTClockFreq,uint32_t,uint32_t Function,-,LL_RCC_GetRFWKPClockFreq,uint32_t, Function,-,LL_RCC_GetRNGClockFreq,uint32_t,uint32_t Function,-,LL_RCC_GetRTCClockFreq,uint32_t, Function,-,LL_RCC_GetSAIClockFreq,uint32_t,uint32_t Function,-,LL_RCC_GetSMPSClockFreq,uint32_t, Function,-,LL_RCC_GetSystemClocksFreq,void,LL_RCC_ClocksTypeDef* -Function,-,LL_RCC_GetUSARTClockFreq,uint32_t,uint32_t +Function,+,LL_RCC_GetUSARTClockFreq,uint32_t,uint32_t Function,-,LL_RCC_GetUSBClockFreq,uint32_t,uint32_t Function,-,LL_RNG_DeInit,ErrorStatus,RNG_TypeDef* Function,-,LL_RNG_Init,ErrorStatus,"RNG_TypeDef*, LL_RNG_InitTypeDef*" @@ -212,21 +212,21 @@ Function,-,LL_RTC_ALMB_StructInit,void,LL_RTC_AlarmTypeDef* Function,-,LL_RTC_DATE_Init,ErrorStatus,"RTC_TypeDef*, uint32_t, LL_RTC_DateTypeDef*" Function,-,LL_RTC_DATE_StructInit,void,LL_RTC_DateTypeDef* Function,-,LL_RTC_DeInit,ErrorStatus,RTC_TypeDef* -Function,-,LL_RTC_EnterInitMode,ErrorStatus,RTC_TypeDef* +Function,+,LL_RTC_EnterInitMode,ErrorStatus,RTC_TypeDef* Function,-,LL_RTC_ExitInitMode,ErrorStatus,RTC_TypeDef* -Function,-,LL_RTC_Init,ErrorStatus,"RTC_TypeDef*, LL_RTC_InitTypeDef*" +Function,+,LL_RTC_Init,ErrorStatus,"RTC_TypeDef*, LL_RTC_InitTypeDef*" Function,-,LL_RTC_StructInit,void,LL_RTC_InitTypeDef* Function,-,LL_RTC_TIME_Init,ErrorStatus,"RTC_TypeDef*, uint32_t, LL_RTC_TimeTypeDef*" Function,-,LL_RTC_TIME_StructInit,void,LL_RTC_TimeTypeDef* Function,-,LL_RTC_WaitForSynchro,ErrorStatus,RTC_TypeDef* Function,-,LL_SPI_DeInit,ErrorStatus,SPI_TypeDef* -Function,-,LL_SPI_Init,ErrorStatus,"SPI_TypeDef*, LL_SPI_InitTypeDef*" +Function,+,LL_SPI_Init,ErrorStatus,"SPI_TypeDef*, LL_SPI_InitTypeDef*" Function,-,LL_SPI_StructInit,void,LL_SPI_InitTypeDef* Function,-,LL_SetFlashLatency,ErrorStatus,uint32_t -Function,-,LL_SetSystemCoreClock,void,uint32_t +Function,+,LL_SetSystemCoreClock,void,uint32_t Function,-,LL_TIM_BDTR_Init,ErrorStatus,"TIM_TypeDef*, LL_TIM_BDTR_InitTypeDef*" Function,-,LL_TIM_BDTR_StructInit,void,LL_TIM_BDTR_InitTypeDef* -Function,-,LL_TIM_DeInit,ErrorStatus,TIM_TypeDef* +Function,+,LL_TIM_DeInit,ErrorStatus,TIM_TypeDef* Function,-,LL_TIM_ENCODER_Init,ErrorStatus,"TIM_TypeDef*, LL_TIM_ENCODER_InitTypeDef*" Function,-,LL_TIM_ENCODER_StructInit,void,LL_TIM_ENCODER_InitTypeDef* Function,-,LL_TIM_HALLSENSOR_Init,ErrorStatus,"TIM_TypeDef*, LL_TIM_HALLSENSOR_InitTypeDef*" @@ -240,7 +240,7 @@ Function,-,LL_TIM_StructInit,void,LL_TIM_InitTypeDef* Function,-,LL_USART_ClockInit,ErrorStatus,"USART_TypeDef*, LL_USART_ClockInitTypeDef*" Function,-,LL_USART_ClockStructInit,void,LL_USART_ClockInitTypeDef* Function,-,LL_USART_DeInit,ErrorStatus,USART_TypeDef* -Function,-,LL_USART_Init,ErrorStatus,"USART_TypeDef*, LL_USART_InitTypeDef*" +Function,+,LL_USART_Init,ErrorStatus,"USART_TypeDef*, LL_USART_InitTypeDef*" Function,-,LL_USART_StructInit,void,LL_USART_InitTypeDef* Function,-,LL_mDelay,void,uint32_t Function,-,SystemCoreClockUpdate,void, @@ -1353,8 +1353,10 @@ Function,+,furi_thread_get_name,const char*,FuriThreadId Function,+,furi_thread_get_return_code,int32_t,FuriThread* Function,+,furi_thread_get_stack_space,uint32_t,FuriThreadId Function,+,furi_thread_get_state,FuriThreadState,FuriThread* +Function,+,furi_thread_is_suspended,_Bool,FuriThreadId Function,+,furi_thread_join,_Bool,FuriThread* Function,+,furi_thread_mark_as_service,void,FuriThread* +Function,+,furi_thread_resume,void,FuriThreadId Function,+,furi_thread_set_callback,void,"FuriThread*, FuriThreadCallback" Function,+,furi_thread_set_context,void,"FuriThread*, void*" Function,+,furi_thread_set_name,void,"FuriThread*, const char*" @@ -1366,6 +1368,7 @@ Function,+,furi_thread_set_stdout_callback,_Bool,FuriThreadStdoutWriteCallback Function,+,furi_thread_start,void,FuriThread* Function,+,furi_thread_stdout_flush,int32_t, Function,+,furi_thread_stdout_write,size_t,"const char*, size_t" +Function,+,furi_thread_suspend,void,FuriThreadId Function,+,furi_thread_yield,void, Function,+,furi_timer_alloc,FuriTimer*,"FuriTimerCallback, FuriTimerType, void*" Function,+,furi_timer_free,void,FuriTimer* diff --git a/furi/core/thread.c b/furi/core/thread.c index a68472b56a6..58cb9bc0991 100644 --- a/furi/core/thread.c +++ b/furi/core/thread.c @@ -89,7 +89,9 @@ static void furi_thread_body(void* context) { if(thread->is_service) { FURI_LOG_E( - "Service", "%s thread exited. Thread memory cannot be reclaimed.", thread->name); + "Service", + "%s thread exited. Thread memory cannot be reclaimed.", + thread->name ? thread->name : ""); } // clear thread local storage @@ -515,4 +517,23 @@ size_t furi_thread_stdout_write(const char* data, size_t size) { int32_t furi_thread_stdout_flush() { return __furi_thread_stdout_flush(furi_thread_get_current()); -} \ No newline at end of file +} + +void furi_thread_suspend(FuriThreadId thread_id) { + TaskHandle_t hTask = (TaskHandle_t)thread_id; + vTaskSuspend(hTask); +} + +void furi_thread_resume(FuriThreadId thread_id) { + TaskHandle_t hTask = (TaskHandle_t)thread_id; + if(FURI_IS_IRQ_MODE()) { + xTaskResumeFromISR(hTask); + } else { + vTaskResume(hTask); + } +} + +bool furi_thread_is_suspended(FuriThreadId thread_id) { + TaskHandle_t hTask = (TaskHandle_t)thread_id; + return eTaskGetState(hTask) == eSuspended; +} diff --git a/furi/core/thread.h b/furi/core/thread.h index f15b9ff661f..fda81bb3a8f 100644 --- a/furi/core/thread.h +++ b/furi/core/thread.h @@ -236,6 +236,25 @@ size_t furi_thread_stdout_write(const char* data, size_t size); */ int32_t furi_thread_stdout_flush(); +/** Suspend thread + * + * @param thread_id thread id + */ +void furi_thread_suspend(FuriThreadId thread_id); + +/** Resume thread + * + * @param thread_id thread id + */ +void furi_thread_resume(FuriThreadId thread_id); + +/** Get thread suspended state + * + * @param thread_id thread id + * @return true if thread is suspended + */ +bool furi_thread_is_suspended(FuriThreadId thread_id); + #ifdef __cplusplus } #endif From e70121e20f00082887ec7118a58b246752976794 Mon Sep 17 00:00:00 2001 From: gornekich Date: Wed, 21 Sep 2022 18:53:25 +0300 Subject: [PATCH 071/824] [FL-2843] NFC fixes (#1764) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * nfc: fix empty desfire card message * nfc: limit total user keys to list * nfc: increase popup timeout Co-authored-by: あく --- .../scenes/nfc_scene_mf_classic_keys_list.c | 88 ++++++++++---- .../nfc/scenes/nfc_scene_mf_desfire_app.c | 111 ++++++++++-------- 2 files changed, 125 insertions(+), 74 deletions(-) diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_list.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_list.c index 36f01897e98..6670ae1328f 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_list.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_list.c @@ -1,48 +1,87 @@ #include "../nfc_i.h" +#define NFC_SCENE_MF_CLASSIC_KEYS_LIST_MAX (100) + void nfc_scene_mf_classic_keys_list_submenu_callback(void* context, uint32_t index) { - Nfc* nfc = context; + furi_assert(context); + Nfc* nfc = context; view_dispatcher_send_custom_event(nfc->view_dispatcher, index); } -void nfc_scene_mf_classic_keys_list_on_enter(void* context) { +void nfc_scene_mf_classic_keys_list_popup_callback(void* context) { + furi_assert(context); + Nfc* nfc = context; + view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit); +} + +void nfc_scene_mf_classic_keys_list_prepare(Nfc* nfc, MfClassicDict* dict) { Submenu* submenu = nfc->submenu; - MfClassicDict* dict = mf_classic_dict_alloc(MfClassicDictTypeUser); uint32_t index = 0; string_t temp_key; - MfClassicUserKeys_init(nfc->mfc_key_strs); string_init(temp_key); + + submenu_set_header(submenu, "Select key to delete:"); + while(mf_classic_dict_get_next_key_str(dict, temp_key)) { + char* current_key = (char*)malloc(sizeof(char) * 13); + strncpy(current_key, string_get_cstr(temp_key), 12); + MfClassicUserKeys_push_back(nfc->mfc_key_strs, current_key); + FURI_LOG_D("ListKeys", "Key %d: %s", index, current_key); + submenu_add_item( + submenu, current_key, index++, nfc_scene_mf_classic_keys_list_submenu_callback, nfc); + } + string_clear(temp_key); +} + +void nfc_scene_mf_classic_keys_list_on_enter(void* context) { + Nfc* nfc = context; + MfClassicDict* dict = mf_classic_dict_alloc(MfClassicDictTypeUser); + MfClassicUserKeys_init(nfc->mfc_key_strs); if(dict) { - mf_classic_dict_rewind(dict); - while(mf_classic_dict_get_next_key_str(dict, temp_key)) { - char* current_key = (char*)malloc(sizeof(char) * 13); - strncpy(current_key, string_get_cstr(temp_key), 12); - MfClassicUserKeys_push_back(nfc->mfc_key_strs, current_key); - FURI_LOG_D("ListKeys", "Key %d: %s", index, current_key); - submenu_add_item( - submenu, - current_key, - index++, - nfc_scene_mf_classic_keys_list_submenu_callback, - nfc); + uint32_t total_user_keys = mf_classic_dict_get_total_keys(dict); + if(total_user_keys < NFC_SCENE_MF_CLASSIC_KEYS_LIST_MAX) { + nfc_scene_mf_classic_keys_list_prepare(nfc, dict); + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); + } else { + popup_set_header(nfc->popup, "Too many keys!", 64, 0, AlignCenter, AlignTop); + popup_set_text( + nfc->popup, + "Edit user dictionary\nwith file browser", + 64, + 12, + AlignCenter, + AlignTop); + popup_set_callback(nfc->popup, nfc_scene_mf_classic_keys_list_popup_callback); + popup_set_context(nfc->popup, nfc); + popup_set_timeout(nfc->popup, 3000); + popup_enable_timeout(nfc->popup); + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); } + mf_classic_dict_free(dict); + } else { + popup_set_header( + nfc->popup, "Failed to load dictionary", 64, 32, AlignCenter, AlignCenter); + popup_set_callback(nfc->popup, nfc_scene_mf_classic_keys_list_popup_callback); + popup_set_context(nfc->popup, nfc); + popup_set_timeout(nfc->popup, 3000); + popup_enable_timeout(nfc->popup); + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); } - submenu_set_header(submenu, "Select key to delete:"); - mf_classic_dict_free(dict); - string_clear(temp_key); - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); } bool nfc_scene_mf_classic_keys_list_on_event(void* context, SceneManagerEvent event) { Nfc* nfc = context; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneMfClassicKeysDelete, event.event); - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicKeysDelete); - consumed = true; + if(event.event == NfcCustomEventViewExit) { + consumed = scene_manager_previous_scene(nfc->scene_manager); + } else { + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneMfClassicKeysDelete, event.event); + scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicKeysDelete); + consumed = true; + } } return consumed; } @@ -57,4 +96,5 @@ void nfc_scene_mf_classic_keys_list_on_exit(void* context) { } MfClassicUserKeys_clear(nfc->mfc_key_strs); submenu_reset(nfc->submenu); + popup_reset(nfc->popup); } diff --git a/applications/main/nfc/scenes/nfc_scene_mf_desfire_app.c b/applications/main/nfc/scenes/nfc_scene_mf_desfire_app.c index 7faafdcf0ba..dd8424641ce 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_desfire_app.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_desfire_app.c @@ -7,6 +7,13 @@ enum SubmenuIndex { SubmenuIndexDynamic, // dynamic indexes start here }; +void nfc_scene_mf_desfire_popup_callback(void* context) { + furi_assert(context); + + Nfc* nfc = context; + view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit); +} + MifareDesfireApplication* nfc_scene_mf_desfire_app_get_app(Nfc* nfc) { uint32_t app_idx = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfDesfireApp) >> 1; @@ -25,46 +32,45 @@ void nfc_scene_mf_desfire_app_submenu_callback(void* context, uint32_t index) { void nfc_scene_mf_desfire_app_on_enter(void* context) { Nfc* nfc = context; - Submenu* submenu = nfc->submenu; MifareDesfireApplication* app = nfc_scene_mf_desfire_app_get_app(nfc); if(!app) { popup_set_icon(nfc->popup, 5, 5, &I_WarningDolphin_45x42); - popup_set_header(nfc->popup, "Internal Error!", 55, 12, AlignLeft, AlignBottom); - popup_set_text( - nfc->popup, - "No app selected.\nThis should\nnever happen,\nplease file a bug.", - 55, - 15, - AlignLeft, - AlignTop); + popup_set_header(nfc->popup, "Empty card!", 55, 12, AlignLeft, AlignBottom); + popup_set_callback(nfc->popup, nfc_scene_mf_desfire_popup_callback); + popup_set_context(nfc->popup, nfc); + popup_set_timeout(nfc->popup, 3000); + popup_enable_timeout(nfc->popup); + popup_set_text(nfc->popup, "No application\nfound.", 55, 15, AlignLeft, AlignTop); view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); - FURI_LOG_E(TAG, "Bad state. No app selected?"); - return; - } - - text_box_set_font(nfc->text_box, TextBoxFontHex); + } else { + text_box_set_font(nfc->text_box, TextBoxFontHex); + submenu_add_item( + nfc->submenu, + "App info", + SubmenuIndexAppInfo, + nfc_scene_mf_desfire_app_submenu_callback, + nfc); - submenu_add_item( - submenu, "App info", SubmenuIndexAppInfo, nfc_scene_mf_desfire_app_submenu_callback, nfc); - - uint16_t cap = NFC_TEXT_STORE_SIZE; - char* buf = nfc->text_store; - int idx = SubmenuIndexDynamic; - for(MifareDesfireFile* file = app->file_head; file; file = file->next) { - int size = snprintf(buf, cap, "File %d", file->id); - if(size < 0 || size >= cap) { - FURI_LOG_W( - TAG, - "Exceeded NFC_TEXT_STORE_SIZE when preparing file id strings; menu truncated"); - break; + uint16_t cap = NFC_TEXT_STORE_SIZE; + char* buf = nfc->text_store; + int idx = SubmenuIndexDynamic; + for(MifareDesfireFile* file = app->file_head; file; file = file->next) { + int size = snprintf(buf, cap, "File %d", file->id); + if(size < 0 || size >= cap) { + FURI_LOG_W( + TAG, + "Exceeded NFC_TEXT_STORE_SIZE when preparing file id strings; menu truncated"); + break; + } + char* label = buf; + cap -= size + 1; + buf += size + 1; + submenu_add_item( + nfc->submenu, label, idx++, nfc_scene_mf_desfire_app_submenu_callback, nfc); } - char* label = buf; - cap -= size + 1; - buf += size + 1; - submenu_add_item(submenu, label, idx++, nfc_scene_mf_desfire_app_submenu_callback, nfc); - } - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); + } } bool nfc_scene_mf_desfire_app_on_event(void* context, SceneManagerEvent event) { @@ -73,26 +79,30 @@ bool nfc_scene_mf_desfire_app_on_event(void* context, SceneManagerEvent event) { uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfDesfireApp); if(event.type == SceneManagerEventTypeCustom) { - MifareDesfireApplication* app = nfc_scene_mf_desfire_app_get_app(nfc); - TextBox* text_box = nfc->text_box; - string_reset(nfc->text_box_store); - if(event.event == SubmenuIndexAppInfo) { - mf_df_cat_application_info(app, nfc->text_box_store); + if(event.event == NfcCustomEventViewExit) { + consumed = scene_manager_previous_scene(nfc->scene_manager); } else { - uint16_t index = event.event - SubmenuIndexDynamic; - MifareDesfireFile* file = app->file_head; - for(int i = 0; file && i < index; i++) { - file = file->next; - } - if(!file) { - return false; + MifareDesfireApplication* app = nfc_scene_mf_desfire_app_get_app(nfc); + TextBox* text_box = nfc->text_box; + string_reset(nfc->text_box_store); + if(event.event == SubmenuIndexAppInfo) { + mf_df_cat_application_info(app, nfc->text_box_store); + } else { + uint16_t index = event.event - SubmenuIndexDynamic; + MifareDesfireFile* file = app->file_head; + for(int i = 0; file && i < index; i++) { + file = file->next; + } + if(!file) { + return false; + } + mf_df_cat_file(file, nfc->text_box_store); } - mf_df_cat_file(file, nfc->text_box_store); + text_box_set_text(text_box, string_get_cstr(nfc->text_box_store)); + scene_manager_set_scene_state(nfc->scene_manager, NfcSceneMfDesfireApp, state | 1); + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewTextBox); + consumed = true; } - text_box_set_text(text_box, string_get_cstr(nfc->text_box_store)); - scene_manager_set_scene_state(nfc->scene_manager, NfcSceneMfDesfireApp, state | 1); - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewTextBox); - consumed = true; } else if(event.type == SceneManagerEventTypeBack) { if(state & 1) { view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); @@ -108,6 +118,7 @@ void nfc_scene_mf_desfire_app_on_exit(void* context) { Nfc* nfc = context; // Clear views + popup_reset(nfc->popup); text_box_reset(nfc->text_box); string_reset(nfc->text_box_store); submenu_reset(nfc->submenu); From 17d01f5c297808cc7ca9735d6a6cf40cf7c1f199 Mon Sep 17 00:00:00 2001 From: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> Date: Thu, 22 Sep 2022 19:13:00 +0300 Subject: [PATCH 072/824] [FL-2848] Universal Remote fix (#1770) * Reset BruteForce on exit from Universal Remote * Reset current button in ButtonPanel --- applications/main/infrared/infrared_brute_force.c | 4 ++++ applications/main/infrared/infrared_brute_force.h | 1 + .../infrared/scenes/common/infrared_scene_universal_common.c | 1 + applications/services/gui/modules/button_panel.c | 2 ++ 4 files changed, 8 insertions(+) diff --git a/applications/main/infrared/infrared_brute_force.c b/applications/main/infrared/infrared_brute_force.c index 8dbc2301274..575fa05ecd3 100644 --- a/applications/main/infrared/infrared_brute_force.c +++ b/applications/main/infrared/infrared_brute_force.c @@ -153,3 +153,7 @@ void infrared_brute_force_add_record( InfraredBruteForceRecordDict_set_at(brute_force->records, key, value); string_clear(key); } + +void infrared_brute_force_reset(InfraredBruteForce* brute_force) { + InfraredBruteForceRecordDict_reset(brute_force->records); +} diff --git a/applications/main/infrared/infrared_brute_force.h b/applications/main/infrared/infrared_brute_force.h index acf0d7b6e12..042d1556b74 100644 --- a/applications/main/infrared/infrared_brute_force.h +++ b/applications/main/infrared/infrared_brute_force.h @@ -20,3 +20,4 @@ void infrared_brute_force_add_record( InfraredBruteForce* brute_force, uint32_t index, const char* name); +void infrared_brute_force_reset(InfraredBruteForce* brute_force); diff --git a/applications/main/infrared/scenes/common/infrared_scene_universal_common.c b/applications/main/infrared/scenes/common/infrared_scene_universal_common.c index 57ac81168e2..f823ca93103 100644 --- a/applications/main/infrared/scenes/common/infrared_scene_universal_common.c +++ b/applications/main/infrared/scenes/common/infrared_scene_universal_common.c @@ -87,5 +87,6 @@ void infrared_scene_universal_common_on_exit(void* context) { Infrared* infrared = context; ButtonPanel* button_panel = infrared->button_panel; view_stack_remove_view(infrared->view_stack, button_panel_get_view(button_panel)); + infrared_brute_force_reset(infrared->brute_force); button_panel_reset(button_panel); } diff --git a/applications/services/gui/modules/button_panel.c b/applications/services/gui/modules/button_panel.c index e3ae59a3671..c823e4b1887 100644 --- a/applications/services/gui/modules/button_panel.c +++ b/applications/services/gui/modules/button_panel.c @@ -133,6 +133,8 @@ void button_panel_reset(ButtonPanel* button_panel) { } model->reserve_x = 0; model->reserve_y = 0; + model->selected_item_x = 0; + model->selected_item_y = 0; LabelList_reset(model->labels); ButtonMatrix_reset(model->button_matrix); return true; From 3846852f2bd4cad37f5e4ce95fe453aa129f978f Mon Sep 17 00:00:00 2001 From: Andrea Sacchi Date: Thu, 22 Sep 2022 19:35:28 +0200 Subject: [PATCH 073/824] NFC Fix Mifare Classic (#1769) * Fix Mifare Classic key str to int conversion: Wrong cast lead to unexpected behavior converting key from str to int. * Nfc: fix type cast in mf_classic_dict and add basic unit tests Co-authored-by: Aleksandr Kutuzov --- applications/debug/unit_tests/nfc/nfc_test.c | 50 ++++++++++++++++++ lib/nfc/helpers/mf_classic_dict.c | 15 +++++- lib/nfc/helpers/mf_classic_dict.h | 53 ++++++++++++++++++++ 3 files changed, 117 insertions(+), 1 deletion(-) diff --git a/applications/debug/unit_tests/nfc/nfc_test.c b/applications/debug/unit_tests/nfc/nfc_test.c index e8119992091..580943f242a 100644 --- a/applications/debug/unit_tests/nfc/nfc_test.c +++ b/applications/debug/unit_tests/nfc/nfc_test.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -170,10 +171,59 @@ MU_TEST(nfc_digital_signal_test) { "NFC long digital signal test failed\r\n"); } +MU_TEST(mf_classic_dict_test) { + MfClassicDict* instance = NULL; + uint64_t key = 0; + string_t temp_str; + string_init(temp_str); + + instance = mf_classic_dict_alloc(MfClassicDictTypeUnitTest); + mu_assert(instance != NULL, "mf_classic_dict_alloc\r\n"); + + mu_assert( + mf_classic_dict_get_total_keys(instance) == 0, + "mf_classic_dict_get_total_keys == 0 assert failed\r\n"); + + string_set(temp_str, "2196FAD8115B"); + mu_assert( + mf_classic_dict_add_key_str(instance, temp_str), + "mf_classic_dict_add_key == true assert failed\r\n"); + + mu_assert( + mf_classic_dict_get_total_keys(instance) == 1, + "mf_classic_dict_get_total_keys == 1 assert failed\r\n"); + + mu_assert(mf_classic_dict_rewind(instance), "mf_classic_dict_rewind == 1 assert failed\r\n"); + + mu_assert( + mf_classic_dict_get_key_at_index_str(instance, temp_str, 0), + "mf_classic_dict_get_key_at_index_str == true assert failed\r\n"); + mu_assert( + string_cmp(temp_str, "2196FAD8115B") == 0, + "string_cmp(temp_str, \"2196FAD8115B\") == 0 assert failed\r\n"); + + mu_assert(mf_classic_dict_rewind(instance), "mf_classic_dict_rewind == 1 assert failed\r\n"); + + mu_assert( + mf_classic_dict_get_key_at_index(instance, &key, 0), + "mf_classic_dict_get_key_at_index == true assert failed\r\n"); + mu_assert(key == 0x2196FAD8115B, "key == 0x2196FAD8115B assert failed\r\n"); + + mu_assert(mf_classic_dict_rewind(instance), "mf_classic_dict_rewind == 1 assert failed\r\n"); + + mu_assert( + mf_classic_dict_delete_index(instance, 0), + "mf_classic_dict_delete_index == true assert failed\r\n"); + + mf_classic_dict_free(instance); + string_clear(temp_str); +} + MU_TEST_SUITE(nfc) { nfc_test_alloc(); MU_RUN_TEST(nfc_digital_signal_test); + MU_RUN_TEST(mf_classic_dict_test); nfc_test_free(); } diff --git a/lib/nfc/helpers/mf_classic_dict.c b/lib/nfc/helpers/mf_classic_dict.c index a615e7899a2..5bb67145a1f 100644 --- a/lib/nfc/helpers/mf_classic_dict.c +++ b/lib/nfc/helpers/mf_classic_dict.c @@ -5,6 +5,7 @@ #define MF_CLASSIC_DICT_FLIPPER_PATH EXT_PATH("nfc/assets/mf_classic_dict.nfc") #define MF_CLASSIC_DICT_USER_PATH EXT_PATH("nfc/assets/mf_classic_dict_user.nfc") +#define MF_CLASSIC_DICT_UNIT_TEST_PATH EXT_PATH("unit_tests/mf_classic_dict.nfc") #define TAG "MfClassicDict" @@ -23,6 +24,9 @@ bool mf_classic_dict_check_presence(MfClassicDictType dict_type) { dict_present = storage_common_stat(storage, MF_CLASSIC_DICT_FLIPPER_PATH, NULL) == FSE_OK; } else if(dict_type == MfClassicDictTypeUser) { dict_present = storage_common_stat(storage, MF_CLASSIC_DICT_USER_PATH, NULL) == FSE_OK; + } else if(dict_type == MfClassicDictTypeUnitTest) { + dict_present = storage_common_stat(storage, MF_CLASSIC_DICT_UNIT_TEST_PATH, NULL) == + FSE_OK; } furi_record_close(RECORD_STORAGE); @@ -50,6 +54,15 @@ MfClassicDict* mf_classic_dict_alloc(MfClassicDictType dict_type) { buffered_file_stream_close(dict->stream); break; } + } else if(dict_type == MfClassicDictTypeUnitTest) { + if(!buffered_file_stream_open( + dict->stream, + MF_CLASSIC_DICT_UNIT_TEST_PATH, + FSAM_READ_WRITE, + FSOM_CREATE_ALWAYS)) { + buffered_file_stream_close(dict->stream); + break; + } } // Read total amount of keys @@ -100,7 +113,7 @@ static void mf_classic_dict_str_to_int(string_t key_str, uint64_t* key_int) { for(uint8_t i = 0; i < 12; i += 2) { args_char_to_hex( string_get_char(key_str, i), string_get_char(key_str, i + 1), &key_byte_tmp); - *key_int |= (uint8_t)key_byte_tmp << 8 * (5 - i / 2); + *key_int |= (uint64_t)key_byte_tmp << 8 * (5 - i / 2); } } diff --git a/lib/nfc/helpers/mf_classic_dict.h b/lib/nfc/helpers/mf_classic_dict.h index aaea3c40005..9241a37b9e7 100644 --- a/lib/nfc/helpers/mf_classic_dict.h +++ b/lib/nfc/helpers/mf_classic_dict.h @@ -9,18 +9,41 @@ typedef enum { MfClassicDictTypeUser, MfClassicDictTypeFlipper, + MfClassicDictTypeUnitTest, } MfClassicDictType; typedef struct MfClassicDict MfClassicDict; bool mf_classic_dict_check_presence(MfClassicDictType dict_type); +/** Allocate MfClassicDict instance + * + * @param[in] dict_type The dictionary type + * + * @return MfClassicDict instance + */ MfClassicDict* mf_classic_dict_alloc(MfClassicDictType dict_type); +/** Free MfClassicDict instance + * + * @param dict MfClassicDict instance + */ void mf_classic_dict_free(MfClassicDict* dict); +/** Get total keys count + * + * @param dict MfClassicDict instance + * + * @return total keys count + */ uint32_t mf_classic_dict_get_total_keys(MfClassicDict* dict); +/** Rewind to the beginning + * + * @param dict MfClassicDict instance + * + * @return true on success + */ bool mf_classic_dict_rewind(MfClassicDict* dict); bool mf_classic_dict_is_key_present(MfClassicDict* dict, uint8_t* key); @@ -31,16 +54,46 @@ bool mf_classic_dict_get_next_key(MfClassicDict* dict, uint64_t* key); bool mf_classic_dict_get_next_key_str(MfClassicDict* dict, string_t key); +/** Get key at target offset as uint64_t + * + * @param dict MfClassicDict instance + * @param[out] key Pointer to the uint64_t key + * @param[in] target Target offset from current position + * + * @return true on success + */ bool mf_classic_dict_get_key_at_index(MfClassicDict* dict, uint64_t* key, uint32_t target); +/** Get key at target offset as string_t + * + * @param dict MfClassicDict instance + * @param[out] key Found key destination buffer + * @param[in] target Target offset from current position + * + * @return true on success + */ bool mf_classic_dict_get_key_at_index_str(MfClassicDict* dict, string_t key, uint32_t target); bool mf_classic_dict_add_key(MfClassicDict* dict, uint8_t* key); +/** Add string representation of the key + * + * @param dict MfClassicDict instance + * @param[in] key String representation of the key + * + * @return true on success + */ bool mf_classic_dict_add_key_str(MfClassicDict* dict, string_t key); bool mf_classic_dict_find_index(MfClassicDict* dict, uint8_t* key, uint32_t* target); bool mf_classic_dict_find_index_str(MfClassicDict* dict, string_t key, uint32_t* target); +/** Delete key at target offset + * + * @param dict MfClassicDict instance + * @param[in] target Target offset from current position + * + * @return true on success + */ bool mf_classic_dict_delete_index(MfClassicDict* dict, uint32_t target); From 6d2b0a3b6c791082bd77a5c097c37c83ba09d329 Mon Sep 17 00:00:00 2001 From: Yoanndp <13591243+Yoanndp@users.noreply.github.com> Date: Sat, 24 Sep 2022 12:36:11 +0200 Subject: [PATCH 074/824] Update ReadMe.md (#1766) --- ReadMe.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index e848e18a4fe..c091d7e3f0f 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -24,7 +24,7 @@ Check out details on [how to build firmware](documentation/fbt.md), [write appli Flipper Zero's firmware consists of two components: -- Core2 firmware set - proprietary components by ST: FUS + radio stack. FUS is flashed at factory and you should never update it. +- Core2 firmware set - proprietary components by ST: FUS + radio stack. FUS is flashed at factory, and you should never update it. - Core1 Firmware - HAL + OS + Drivers + Applications. They both must be flashed in the order described. @@ -52,7 +52,7 @@ Prerequisites: - [arm-gcc-none-eabi](https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm/downloads) - openocd -One liner: `./fbt firmware_flash` +One-liner: `./fbt firmware_flash` ## With USB DFU @@ -128,7 +128,7 @@ Connect your device via ST-Link and run: - `debug` - Debug tool: GDB-plugins, SVD-file and etc - `documentation` - Documentation generation system configs and input files - `firmware` - Firmware source code -- `lib` - Our and 3rd party libraries, drivers and etc... +- `lib` - Our and 3rd party libraries, drivers, etc. - `scripts` - Supplementary scripts and python libraries home -Also pay attention to `ReadMe.md` files inside of those directories. +Also pay attention to `ReadMe.md` files inside those directories. From eadd7801afea681841ea1a2ba28e1e64d819977f Mon Sep 17 00:00:00 2001 From: ghettorce <799240+ghettorce@users.noreply.github.com> Date: Sat, 24 Sep 2022 14:30:19 +0300 Subject: [PATCH 075/824] fbt: exclude user site-packages directory from sys.path (#1778) * fbt: exclude user site-packages directory from sys.path * fbt: python path fixes for *nix * fbt: fixed cli target on Windows Co-authored-by: hedger --- scripts/flipper/utils/cdc.py | 2 +- scripts/serial_cli.py | 14 +++++++++++++- scripts/toolchain/fbtenv.cmd | 1 + scripts/toolchain/fbtenv.sh | 13 +++++++++++++ site_scons/environ.scons | 9 ++++++++- 5 files changed, 36 insertions(+), 3 deletions(-) diff --git a/scripts/flipper/utils/cdc.py b/scripts/flipper/utils/cdc.py index 081705cc257..7047db2a686 100644 --- a/scripts/flipper/utils/cdc.py +++ b/scripts/flipper/utils/cdc.py @@ -14,4 +14,4 @@ def resolve_port(logger, portname: str = "auto"): logger.error("Failed to find connected Flipper") elif len(flippers) > 1: logger.error("More than one Flipper is attached") - logger.error("Failed to guess which port to use. Specify --port") + logger.error("Failed to guess which port to use") diff --git a/scripts/serial_cli.py b/scripts/serial_cli.py index e07e6bfb4b8..441bc7cc8e1 100644 --- a/scripts/serial_cli.py +++ b/scripts/serial_cli.py @@ -1,13 +1,25 @@ import logging import subprocess from flipper.utils.cdc import resolve_port +import os +import sys def main(): logger = logging.getLogger() if not (port := resolve_port(logger, "auto")): + logger.error("Is Flipper connected over USB and isn't in DFU mode?") return 1 - subprocess.call(["python3", "-m", "serial.tools.miniterm", "--raw", port, "230400"]) + subprocess.call( + [ + os.path.basename(sys.executable), + "-m", + "serial.tools.miniterm", + "--raw", + port, + "230400", + ] + ) if __name__ == "__main__": diff --git a/scripts/toolchain/fbtenv.cmd b/scripts/toolchain/fbtenv.cmd index 1403837d940..5eaf16f9443 100644 --- a/scripts/toolchain/fbtenv.cmd +++ b/scripts/toolchain/fbtenv.cmd @@ -32,6 +32,7 @@ if not "%REAL_TOOLCHAIN_VERSION%" == "%FLIPPER_TOOLCHAIN_VERSION%" ( set "HOME=%USERPROFILE%" set "PYTHONHOME=%FBT_TOOLCHAIN_ROOT%\python" set "PYTHONPATH=" +set "PYTHONNOUSERSITE=1" set "PATH=%FBT_TOOLCHAIN_ROOT%\python;%FBT_TOOLCHAIN_ROOT%\bin;%FBT_TOOLCHAIN_ROOT%\protoc\bin;%FBT_TOOLCHAIN_ROOT%\openocd\bin;%PATH%" set "PROMPT=(fbt) %PROMPT%" diff --git a/scripts/toolchain/fbtenv.sh b/scripts/toolchain/fbtenv.sh index f68e4c0bad8..7c501803fbf 100755 --- a/scripts/toolchain/fbtenv.sh +++ b/scripts/toolchain/fbtenv.sh @@ -40,6 +40,13 @@ fbtenv_restore_env() elif [ -n "${PROMPT:-""}" ]; then PROMPT="$(echo "$PROMPT" | sed 's/\[fbt\]//g')"; fi + + PYTHONNOUSERSITE="$SAVED_PYTHONNOUSERSITE"; + PYTHONPATH="$SAVED_PYTHONPATH"; + + unset SAVED_PYTHONNOUSERSITE; + unset SAVED_PYTHONPATH; + unset SCRIPT_PATH; unset FBT_TOOLCHAIN_VERSION; unset FBT_TOOLCHAIN_PATH; @@ -276,6 +283,12 @@ fbtenv_main() PATH="$TOOLCHAIN_ARCH_DIR/bin:$PATH"; PATH="$TOOLCHAIN_ARCH_DIR/protobuf/bin:$PATH"; PATH="$TOOLCHAIN_ARCH_DIR/openocd/bin:$PATH"; + + SAVED_PYTHONNOUSERSITE="${PYTHONNOUSERSITE:-""}"; + SAVED_PYTHONPATH="${PYTHONPATH:-""}"; + + PYTHONNOUSERSITE=1; + PYTHONPATH=; } fbtenv_main "${1:-""}"; diff --git a/site_scons/environ.scons b/site_scons/environ.scons index 99d4cc0b562..c61f29616e5 100644 --- a/site_scons/environ.scons +++ b/site_scons/environ.scons @@ -12,7 +12,14 @@ forward_os_env = { "PATH": os.environ["PATH"], } # Proxying CI environment to child processes & scripts -for env_value_name in ("WORKFLOW_BRANCH_OR_TAG", "DIST_SUFFIX", "HOME", "APPDATA"): +for env_value_name in ( + "WORKFLOW_BRANCH_OR_TAG", + "DIST_SUFFIX", + "HOME", + "APPDATA", + "PYTHONHOME", + "PYTHONNOUSERSITE", +): if environ_value := os.environ.get(env_value_name, None): forward_os_env[env_value_name] = environ_value From 92e440c77dce7ede20b25d116ee740009f40c31e Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Sun, 25 Sep 2022 21:48:57 +1000 Subject: [PATCH 076/824] Core: simplify record container (#1776) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- furi/core/record.c | 62 ++++++++++++++++------------------------ lib/toolbox/m_cstr_dup.h | 17 +++++++++++ 2 files changed, 42 insertions(+), 37 deletions(-) create mode 100644 lib/toolbox/m_cstr_dup.h diff --git a/furi/core/record.c b/furi/core/record.c index 666d50761a8..63dfdbe477d 100644 --- a/furi/core/record.c +++ b/furi/core/record.c @@ -6,6 +6,7 @@ #include #include +#include #define FURI_RECORD_FLAG_READY (0x1) @@ -15,7 +16,7 @@ typedef struct { size_t holders_count; } FuriRecordData; -DICT_DEF2(FuriRecordDataDict, string_t, STRING_OPLIST, FuriRecordData, M_POD_OPLIST) +DICT_DEF2(FuriRecordDataDict, const char*, M_CSTR_DUP_OPLIST, FuriRecordData, M_POD_OPLIST) typedef struct { FuriMutex* mutex; @@ -24,6 +25,19 @@ typedef struct { static FuriRecord* furi_record = NULL; +static FuriRecordData* furi_record_get(const char* name) { + return FuriRecordDataDict_get(furi_record->records, name); +} + +static void furi_record_put(const char* name, FuriRecordData* record_data) { + FuriRecordDataDict_set_at(furi_record->records, name, *record_data); +} + +static void furi_record_erase(const char* name, FuriRecordData* record_data) { + furi_event_flag_free(record_data->flags); + FuriRecordDataDict_erase(furi_record->records, name); +} + void furi_record_init() { furi_record = malloc(sizeof(FuriRecord)); furi_record->mutex = furi_mutex_alloc(FuriMutexTypeNormal); @@ -31,16 +45,16 @@ void furi_record_init() { FuriRecordDataDict_init(furi_record->records); } -static FuriRecordData* furi_record_data_get_or_create(string_t name_str) { +static FuriRecordData* furi_record_data_get_or_create(const char* name) { furi_assert(furi_record); - FuriRecordData* record_data = FuriRecordDataDict_get(furi_record->records, name_str); + FuriRecordData* record_data = furi_record_get(name); if(!record_data) { FuriRecordData new_record; new_record.flags = furi_event_flag_alloc(); new_record.data = NULL; new_record.holders_count = 0; - FuriRecordDataDict_set_at(furi_record->records, name_str, new_record); - record_data = FuriRecordDataDict_get(furi_record->records, name_str); + furi_record_put(name, &new_record); + record_data = furi_record_get(name); } return record_data; } @@ -59,35 +73,25 @@ bool furi_record_exists(const char* name) { bool ret = false; - string_t name_str; - string_init_set_str(name_str, name); - furi_record_lock(); - ret = (FuriRecordDataDict_get(furi_record->records, name_str) != NULL); + ret = (furi_record_get(name) != NULL); furi_record_unlock(); - string_clear(name_str); - return ret; } void furi_record_create(const char* name, void* data) { furi_assert(furi_record); - string_t name_str; - string_init_set_str(name_str, name); - furi_record_lock(); // Get record data and fill it - FuriRecordData* record_data = furi_record_data_get_or_create(name_str); + FuriRecordData* record_data = furi_record_data_get_or_create(name); furi_assert(record_data->data == NULL); record_data->data = data; furi_event_flag_set(record_data->flags, FURI_RECORD_FLAG_READY); furi_record_unlock(); - - string_clear(name_str); } bool furi_record_destroy(const char* name) { @@ -95,35 +99,26 @@ bool furi_record_destroy(const char* name) { bool ret = false; - string_t name_str; - string_init_set_str(name_str, name); - furi_record_lock(); - FuriRecordData* record_data = FuriRecordDataDict_get(furi_record->records, name_str); + FuriRecordData* record_data = furi_record_get(name); furi_assert(record_data); if(record_data->holders_count == 0) { - furi_event_flag_free(record_data->flags); - FuriRecordDataDict_erase(furi_record->records, name_str); + furi_record_erase(name, record_data); ret = true; } furi_record_unlock(); - string_clear(name_str); - return ret; } void* furi_record_open(const char* name) { furi_assert(furi_record); - string_t name_str; - string_init_set_str(name_str, name); - furi_record_lock(); - FuriRecordData* record_data = furi_record_data_get_or_create(name_str); + FuriRecordData* record_data = furi_record_data_get_or_create(name); record_data->holders_count++; furi_record_unlock(); @@ -136,24 +131,17 @@ void* furi_record_open(const char* name) { FuriFlagWaitAny | FuriFlagNoClear, FuriWaitForever) == FURI_RECORD_FLAG_READY); - string_clear(name_str); - return record_data->data; } void furi_record_close(const char* name) { furi_assert(furi_record); - string_t name_str; - string_init_set_str(name_str, name); - furi_record_lock(); - FuriRecordData* record_data = FuriRecordDataDict_get(furi_record->records, name_str); + FuriRecordData* record_data = furi_record_get(name); furi_assert(record_data); record_data->holders_count--; furi_record_unlock(); - - string_clear(name_str); } diff --git a/lib/toolbox/m_cstr_dup.h b/lib/toolbox/m_cstr_dup.h new file mode 100644 index 00000000000..2bc35c87774 --- /dev/null +++ b/lib/toolbox/m_cstr_dup.h @@ -0,0 +1,17 @@ +#pragma once +#include + +#define M_INIT_DUP(a) ((a) = strdup("")) +#define M_SET_DUP(a, b) (M_CHECK_DEFAULT_TYPE(a), free((void*)a), (a) = strdup(b)) +#define M_CLEAR_DUP(a) (free((void*)a)) + +#define M_CSTR_DUP_OPLIST \ + (INIT(M_INIT_DUP), \ + INIT_SET(M_SET_DUP), \ + SET(M_SET_DUP), \ + CLEAR(M_CLEAR_DUP), \ + HASH(m_core_cstr_hash), \ + EQUAL(M_CSTR_EQUAL), \ + CMP(strcmp), \ + TYPE(const char*), \ + OUT_STR(M_CSTR_OUT_STR)) From 7e2008095e9151fd3d0883e742d158926a18db5f Mon Sep 17 00:00:00 2001 From: Jauder Ho Date: Sun, 25 Sep 2022 04:56:53 -0700 Subject: [PATCH 077/824] Bump protobuf from 3.20.1 to 3.20.2 in /scripts (#1774) --- scripts/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/requirements.txt b/scripts/requirements.txt index 4c4b7279c74..35cac774211 100644 --- a/scripts/requirements.txt +++ b/scripts/requirements.txt @@ -3,5 +3,5 @@ heatshrink2==0.11.0 Pillow==9.1.1 grpcio==1.47.0 grpcio-tools==1.47.0 -protobuf==3.20.1 +protobuf==3.20.2 python3-protobuf==2.5.0 From e6d22ed1475a05db858b39b95ae9933bbd2252ca Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Mon, 26 Sep 2022 00:11:29 +1000 Subject: [PATCH 078/824] ELF-Loader: C++ plugin support, loader overhaul. (#1744) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fap-loader: load all code and data sections * fap-loader: relocate all code and data sections * fap-loader: remove old elf loader * fap-loader: new jmp call relocation * openocd: resume on detach * fap-loader: trampoline for big jumps * fap-loader: rename cache * fap-loader: init_array support * fap-loader: untangled flipper_application into separate entities * fap-loader: fix debug * fap-loader: optimize section container * fap-loader: optimize key for section container * fap-loader: disable debug log * documentation * F7: bump api symbols version * Lib: cleanup elf_file.c Co-authored-by: あく --- applications/main/fap_loader/fap_loader_app.c | 2 +- debug/flipperapps.py | 8 +- debug/stm32wbx.cfg | 4 + firmware/targets/f7/api_symbols.csv | 8 +- .../application_manifest.c | 21 + .../application_manifest.h | 25 + .../elf/elf_api_interface.h | 2 +- lib/flipper_application/elf/elf_file.c | 794 ++++++++++++++++++ lib/flipper_application/elf/elf_file.h | 127 +++ lib/flipper_application/elf/elf_file_i.h | 46 + .../flipper_applicaiton_i.c | 477 ----------- lib/flipper_application/flipper_application.c | 87 +- lib/flipper_application/flipper_application.h | 33 +- .../flipper_application_i.h | 99 --- 14 files changed, 1089 insertions(+), 644 deletions(-) create mode 100644 lib/flipper_application/application_manifest.c create mode 100644 lib/flipper_application/elf/elf_file.c create mode 100644 lib/flipper_application/elf/elf_file.h create mode 100644 lib/flipper_application/elf/elf_file_i.h delete mode 100644 lib/flipper_application/flipper_applicaiton_i.c delete mode 100644 lib/flipper_application/flipper_application_i.h diff --git a/applications/main/fap_loader/fap_loader_app.c b/applications/main/fap_loader/fap_loader_app.c index 14da2f32037..9050ddf7834 100644 --- a/applications/main/fap_loader/fap_loader_app.c +++ b/applications/main/fap_loader/fap_loader_app.c @@ -25,7 +25,7 @@ static bool FlipperApplication* app = flipper_application_alloc(loader->storage, &hashtable_api_interface); FlipperApplicationPreloadStatus preload_res = - flipper_application_preload(app, string_get_cstr(path)); + flipper_application_preload_manifest(app, string_get_cstr(path)); bool load_success = false; diff --git a/debug/flipperapps.py b/debug/flipperapps.py index c8d3fcdb998..8e1aa2daf90 100644 --- a/debug/flipperapps.py +++ b/debug/flipperapps.py @@ -64,7 +64,7 @@ def get_gdb_unload_command(self) -> str: def is_loaded_in_gdb(self, gdb_app) -> bool: # Avoid constructing full app wrapper for comparison - return self.entry_address == int(gdb_app["entry"]) + return self.entry_address == int(gdb_app["state"]["entry"]) @staticmethod def parse_debug_link_data(section_data: bytes) -> Tuple[str, int]: @@ -78,13 +78,13 @@ def parse_debug_link_data(section_data: bytes) -> Tuple[str, int]: @staticmethod def from_gdb(gdb_app: "AppState") -> "AppState": state = AppState(str(gdb_app["manifest"]["name"].string())) - state.entry_address = int(gdb_app["entry"]) + state.entry_address = int(gdb_app["state"]["entry"]) app_state = gdb_app["state"] - if debug_link_size := int(app_state["debug_link_size"]): + if debug_link_size := int(app_state["debug_link_info"]["debug_link_size"]): debug_link_data = ( gdb.selected_inferior() - .read_memory(int(app_state["debug_link"]), debug_link_size) + .read_memory(int(app_state["debug_link_info"]["debug_link"]), debug_link_size) .tobytes() ) state.debug_link_elf, state.debug_link_crc = AppState.parse_debug_link_data( diff --git a/debug/stm32wbx.cfg b/debug/stm32wbx.cfg index f100c3ccda1..ba383831bd4 100644 --- a/debug/stm32wbx.cfg +++ b/debug/stm32wbx.cfg @@ -101,3 +101,7 @@ $_TARGETNAME configure -event trace-config { # assignment mmw 0xE0042004 0x00000020 0 } + +$_TARGETNAME configure -event gdb-detach { + resume +} \ No newline at end of file diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index ac4df046d57..39365c397fe 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,1.9,, +Version,+,1.10,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -779,13 +779,13 @@ Function,-,fiprintf,int,"FILE*, const char*, ..." Function,-,fiscanf,int,"FILE*, const char*, ..." Function,+,flipper_application_alloc,FlipperApplication*,"Storage*, const ElfApiInterface*" Function,+,flipper_application_free,void,FlipperApplication* -Function,-,flipper_application_get_entry_address,const void*,FlipperApplication* Function,+,flipper_application_get_manifest,const FlipperApplicationManifest*,FlipperApplication* -Function,-,flipper_application_get_state,const FlipperApplicationState*,FlipperApplication* -Function,-,flipper_application_get_thread,FuriThread*,FlipperApplication* Function,+,flipper_application_load_status_to_string,const char*,FlipperApplicationLoadStatus +Function,+,flipper_application_manifest_is_compatible,_Bool,"const FlipperApplicationManifest*, const ElfApiInterface*" +Function,+,flipper_application_manifest_is_valid,_Bool,const FlipperApplicationManifest* Function,+,flipper_application_map_to_memory,FlipperApplicationLoadStatus,FlipperApplication* Function,+,flipper_application_preload,FlipperApplicationPreloadStatus,"FlipperApplication*, const char*" +Function,+,flipper_application_preload_manifest,FlipperApplicationPreloadStatus,"FlipperApplication*, const char*" Function,-,flipper_application_preload_status_to_string,const char*,FlipperApplicationPreloadStatus Function,+,flipper_application_spawn,FuriThread*,"FlipperApplication*, void*" Function,+,flipper_format_buffered_file_alloc,FlipperFormat*,Storage* diff --git a/lib/flipper_application/application_manifest.c b/lib/flipper_application/application_manifest.c new file mode 100644 index 00000000000..ab92e493046 --- /dev/null +++ b/lib/flipper_application/application_manifest.c @@ -0,0 +1,21 @@ +#include "application_manifest.h" + +bool flipper_application_manifest_is_valid(const FlipperApplicationManifest* manifest) { + if((manifest->base.manifest_magic != FAP_MANIFEST_MAGIC) || + (manifest->base.manifest_version != FAP_MANIFEST_SUPPORTED_VERSION)) { + return false; + } + + return true; +} + +bool flipper_application_manifest_is_compatible( + const FlipperApplicationManifest* manifest, + const ElfApiInterface* api_interface) { + if(manifest->base.api_version.major != api_interface->api_version_major /* || + manifest->base.api_version.minor > app->api_interface->api_version_minor */) { + return false; + } + + return true; +} diff --git a/lib/flipper_application/application_manifest.h b/lib/flipper_application/application_manifest.h index 6aa20e48187..f46d44fd7d0 100644 --- a/lib/flipper_application/application_manifest.h +++ b/lib/flipper_application/application_manifest.h @@ -1,6 +1,12 @@ +/** + * @file application_manifest.h + * Flipper application manifest + */ #pragma once #include +#include +#include "elf/elf_api_interface.h" #ifdef __cplusplus extern "C" { @@ -40,6 +46,25 @@ typedef FlipperApplicationManifestV1 FlipperApplicationManifest; #pragma pack(pop) +/** + * @brief Check if manifest is valid + * + * @param manifest + * @return bool + */ +bool flipper_application_manifest_is_valid(const FlipperApplicationManifest* manifest); + +/** + * @brief Check if manifest is compatible with current ELF API interface + * + * @param manifest + * @param api_interface + * @return bool + */ +bool flipper_application_manifest_is_compatible( + const FlipperApplicationManifest* manifest, + const ElfApiInterface* api_interface); + #ifdef __cplusplus } #endif diff --git a/lib/flipper_application/elf/elf_api_interface.h b/lib/flipper_application/elf/elf_api_interface.h index 505f4f71838..ca31fc4836a 100644 --- a/lib/flipper_application/elf/elf_api_interface.h +++ b/lib/flipper_application/elf/elf_api_interface.h @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #define ELF_INVALID_ADDRESS 0xFFFFFFFF diff --git a/lib/flipper_application/elf/elf_file.c b/lib/flipper_application/elf/elf_file.c new file mode 100644 index 00000000000..202d0a8756e --- /dev/null +++ b/lib/flipper_application/elf/elf_file.c @@ -0,0 +1,794 @@ +#include +#include "elf_file.h" +#include "elf_file_i.h" +#include "elf_api_interface.h" + +#define TAG "elf" + +#define ELF_NAME_BUFFER_LEN 32 +#define SECTION_OFFSET(e, n) (e->section_table + n * sizeof(Elf32_Shdr)) +#define IS_FLAGS_SET(v, m) ((v & m) == m) +#define RESOLVER_THREAD_YIELD_STEP 30 + +// #define ELF_DEBUG_LOG 1 + +#ifndef ELF_DEBUG_LOG +#undef FURI_LOG_D +#define FURI_LOG_D(...) +#endif + +#define TRAMPOLINE_CODE_SIZE 6 + +/** +ldr r12, [pc, #2] +bx r12 +*/ +const uint8_t trampoline_code_little_endian[TRAMPOLINE_CODE_SIZE] = + {0xdf, 0xf8, 0x02, 0xc0, 0x60, 0x47}; + +typedef struct { + uint8_t code[TRAMPOLINE_CODE_SIZE]; + uint32_t addr; +} __attribute__((packed)) JMPTrampoline; + +/**************************************************************************************************/ +/********************************************* Caches *********************************************/ +/**************************************************************************************************/ + +static bool address_cache_get(AddressCache_t cache, int symEntry, Elf32_Addr* symAddr) { + Elf32_Addr* addr = AddressCache_get(cache, symEntry); + if(addr) { + *symAddr = *addr; + return true; + } else { + return false; + } +} + +static void address_cache_put(AddressCache_t cache, int symEntry, Elf32_Addr symAddr) { + AddressCache_set_at(cache, symEntry, symAddr); +} + +/**************************************************************************************************/ +/********************************************** ELF ***********************************************/ +/**************************************************************************************************/ + +static ELFSection* elf_file_get_section(ELFFile* elf, const char* name) { + return ELFSectionDict_get(elf->sections, name); +} + +static void elf_file_put_section(ELFFile* elf, const char* name, ELFSection* section) { + ELFSectionDict_set_at(elf->sections, strdup(name), *section); +} + +static bool elf_read_string_from_offset(ELFFile* elf, off_t offset, string_t name) { + bool result = false; + + off_t old = storage_file_tell(elf->fd); + + do { + if(!storage_file_seek(elf->fd, offset, true)) break; + + char buffer[ELF_NAME_BUFFER_LEN + 1]; + buffer[ELF_NAME_BUFFER_LEN] = 0; + + while(true) { + uint16_t read = storage_file_read(elf->fd, buffer, ELF_NAME_BUFFER_LEN); + string_cat_str(name, buffer); + if(strlen(buffer) < ELF_NAME_BUFFER_LEN) { + result = true; + break; + } + + if(storage_file_get_error(elf->fd) != FSE_OK || read == 0) break; + } + + } while(false); + storage_file_seek(elf->fd, old, true); + + return result; +} + +static bool elf_read_section_name(ELFFile* elf, off_t offset, string_t name) { + return elf_read_string_from_offset(elf, elf->section_table_strings + offset, name); +} + +static bool elf_read_symbol_name(ELFFile* elf, off_t offset, string_t name) { + return elf_read_string_from_offset(elf, elf->symbol_table_strings + offset, name); +} + +static bool elf_read_section_header(ELFFile* elf, size_t section_idx, Elf32_Shdr* section_header) { + off_t offset = SECTION_OFFSET(elf, section_idx); + return storage_file_seek(elf->fd, offset, true) && + storage_file_read(elf->fd, section_header, sizeof(Elf32_Shdr)) == sizeof(Elf32_Shdr); +} + +static bool + elf_read_section(ELFFile* elf, size_t section_idx, Elf32_Shdr* section_header, string_t name) { + if(!elf_read_section_header(elf, section_idx, section_header)) { + return false; + } + + if(section_header->sh_name && !elf_read_section_name(elf, section_header->sh_name, name)) { + return false; + } + + return true; +} + +static bool elf_read_symbol(ELFFile* elf, int n, Elf32_Sym* sym, string_t name) { + bool success = false; + off_t old = storage_file_tell(elf->fd); + off_t pos = elf->symbol_table + n * sizeof(Elf32_Sym); + if(storage_file_seek(elf->fd, pos, true) && + storage_file_read(elf->fd, sym, sizeof(Elf32_Sym)) == sizeof(Elf32_Sym)) { + if(sym->st_name) + success = elf_read_symbol_name(elf, sym->st_name, name); + else { + Elf32_Shdr shdr; + success = elf_read_section(elf, sym->st_shndx, &shdr, name); + } + } + storage_file_seek(elf->fd, old, true); + return success; +} + +static ELFSection* elf_section_of(ELFFile* elf, int index) { + ELFSectionDict_it_t it; + for(ELFSectionDict_it(it, elf->sections); !ELFSectionDict_end_p(it); ELFSectionDict_next(it)) { + ELFSectionDict_itref_t* itref = ELFSectionDict_ref(it); + if(itref->value.sec_idx == index) { + return &itref->value; + } + } + + return NULL; +} + +static Elf32_Addr elf_address_of(ELFFile* elf, Elf32_Sym* sym, const char* sName) { + if(sym->st_shndx == SHN_UNDEF) { + Elf32_Addr addr = 0; + if(elf->api_interface->resolver_callback(sName, &addr)) { + return addr; + } + } else { + ELFSection* symSec = elf_section_of(elf, sym->st_shndx); + if(symSec) { + return ((Elf32_Addr)symSec->data) + sym->st_value; + } + } + FURI_LOG_D(TAG, " Can not find address for symbol %s", sName); + return ELF_INVALID_ADDRESS; +} + +__attribute__((unused)) static const char* elf_reloc_type_to_str(int symt) { +#define STRCASE(name) \ + case name: \ + return #name; + switch(symt) { + STRCASE(R_ARM_NONE) + STRCASE(R_ARM_TARGET1) + STRCASE(R_ARM_ABS32) + STRCASE(R_ARM_THM_PC22) + STRCASE(R_ARM_THM_JUMP24) + default: + return "R_"; + } +#undef STRCASE +} + +static JMPTrampoline* elf_create_trampoline(Elf32_Addr addr) { + JMPTrampoline* trampoline = malloc(sizeof(JMPTrampoline)); + memcpy(trampoline->code, trampoline_code_little_endian, TRAMPOLINE_CODE_SIZE); + trampoline->addr = addr; + return trampoline; +} + +static void elf_relocate_jmp_call(ELFFile* elf, Elf32_Addr relAddr, int type, Elf32_Addr symAddr) { + int offset, hi, lo, s, j1, j2, i1, i2, imm10, imm11; + int to_thumb, is_call, blx_bit = 1 << 12; + + /* Get initial offset */ + hi = ((uint16_t*)relAddr)[0]; + lo = ((uint16_t*)relAddr)[1]; + s = (hi >> 10) & 1; + j1 = (lo >> 13) & 1; + j2 = (lo >> 11) & 1; + i1 = (j1 ^ s) ^ 1; + i2 = (j2 ^ s) ^ 1; + imm10 = hi & 0x3ff; + imm11 = lo & 0x7ff; + offset = (s << 24) | (i1 << 23) | (i2 << 22) | (imm10 << 12) | (imm11 << 1); + if(offset & 0x01000000) offset -= 0x02000000; + + to_thumb = symAddr & 1; + is_call = (type == R_ARM_THM_PC22); + + /* Store offset */ + int offset_copy = offset; + + /* Compute final offset */ + offset += symAddr - relAddr; + if(!to_thumb && is_call) { + blx_bit = 0; /* bl -> blx */ + offset = (offset + 3) & -4; /* Compute offset from aligned PC */ + } + + /* Check that relocation is possible + * offset must not be out of range + * if target is to be entered in arm mode: + - bit 1 must not set + - instruction must be a call (bl) or a jump to PLT */ + if(!to_thumb || offset >= 0x1000000 || offset < -0x1000000) { + if(to_thumb || (symAddr & 2) || (!is_call)) { + FURI_LOG_D( + TAG, + "can't relocate value at %x, %s, doing trampoline", + relAddr, + elf_reloc_type_to_str(type)); + + Elf32_Addr addr; + if(!address_cache_get(elf->trampoline_cache, symAddr, &addr)) { + addr = (Elf32_Addr)elf_create_trampoline(symAddr); + address_cache_put(elf->trampoline_cache, symAddr, addr); + } + + offset = offset_copy; + offset += (int)addr - relAddr; + if(!to_thumb && is_call) { + blx_bit = 0; /* bl -> blx */ + offset = (offset + 3) & -4; /* Compute offset from aligned PC */ + } + } + } + + /* Compute and store final offset */ + s = (offset >> 24) & 1; + i1 = (offset >> 23) & 1; + i2 = (offset >> 22) & 1; + j1 = s ^ (i1 ^ 1); + j2 = s ^ (i2 ^ 1); + imm10 = (offset >> 12) & 0x3ff; + imm11 = (offset >> 1) & 0x7ff; + (*(uint16_t*)relAddr) = (uint16_t)((hi & 0xf800) | (s << 10) | imm10); + (*(uint16_t*)(relAddr + 2)) = + (uint16_t)((lo & 0xc000) | (j1 << 13) | blx_bit | (j2 << 11) | imm11); +} + +static bool elf_relocate_symbol(ELFFile* elf, Elf32_Addr relAddr, int type, Elf32_Addr symAddr) { + switch(type) { + case R_ARM_TARGET1: + case R_ARM_ABS32: + *((uint32_t*)relAddr) += symAddr; + FURI_LOG_D(TAG, " R_ARM_ABS32 relocated is 0x%08X", (unsigned int)*((uint32_t*)relAddr)); + break; + case R_ARM_THM_PC22: + case R_ARM_THM_JUMP24: + elf_relocate_jmp_call(elf, relAddr, type, symAddr); + FURI_LOG_D( + TAG, " R_ARM_THM_CALL/JMP relocated is 0x%08X", (unsigned int)*((uint32_t*)relAddr)); + break; + default: + FURI_LOG_E(TAG, " Undefined relocation %d", type); + return false; + } + return true; +} + +static bool elf_relocate(ELFFile* elf, Elf32_Shdr* h, ELFSection* s) { + if(s->data) { + Elf32_Rel rel; + size_t relEntries = h->sh_size / sizeof(rel); + size_t relCount; + (void)storage_file_seek(elf->fd, h->sh_offset, true); + FURI_LOG_D(TAG, " Offset Info Type Name"); + + int relocate_result = true; + string_t symbol_name; + string_init(symbol_name); + + for(relCount = 0; relCount < relEntries; relCount++) { + if(relCount % RESOLVER_THREAD_YIELD_STEP == 0) { + FURI_LOG_D(TAG, " reloc YIELD"); + furi_delay_tick(1); + } + + if(storage_file_read(elf->fd, &rel, sizeof(Elf32_Rel)) != sizeof(Elf32_Rel)) { + FURI_LOG_E(TAG, " reloc read fail"); + string_clear(symbol_name); + return false; + } + + Elf32_Addr symAddr; + + int symEntry = ELF32_R_SYM(rel.r_info); + int relType = ELF32_R_TYPE(rel.r_info); + Elf32_Addr relAddr = ((Elf32_Addr)s->data) + rel.r_offset; + + if(!address_cache_get(elf->relocation_cache, symEntry, &symAddr)) { + Elf32_Sym sym; + string_reset(symbol_name); + if(!elf_read_symbol(elf, symEntry, &sym, symbol_name)) { + FURI_LOG_E(TAG, " symbol read fail"); + string_clear(symbol_name); + return false; + } + + FURI_LOG_D( + TAG, + " %08X %08X %-16s %s", + (unsigned int)rel.r_offset, + (unsigned int)rel.r_info, + elf_reloc_type_to_str(relType), + string_get_cstr(symbol_name)); + + symAddr = elf_address_of(elf, &sym, string_get_cstr(symbol_name)); + address_cache_put(elf->relocation_cache, symEntry, symAddr); + } + + if(symAddr != ELF_INVALID_ADDRESS) { + FURI_LOG_D( + TAG, + " symAddr=%08X relAddr=%08X", + (unsigned int)symAddr, + (unsigned int)relAddr); + if(!elf_relocate_symbol(elf, relAddr, relType, symAddr)) { + relocate_result = false; + } + } else { + FURI_LOG_E(TAG, " No symbol address of %s", string_get_cstr(symbol_name)); + relocate_result = false; + } + } + string_clear(symbol_name); + + return relocate_result; + } else { + FURI_LOG_D(TAG, "Section not loaded"); + } + + return false; +} + +/**************************************************************************************************/ +/********************************************* MISC ***********************************************/ +/**************************************************************************************************/ + +static bool cstr_prefix(const char* prefix, const char* string) { + return strncmp(prefix, string, strlen(prefix)) == 0; +} + +/**************************************************************************************************/ +/************************************ Internal FAP interfaces *************************************/ +/**************************************************************************************************/ +typedef enum { + SectionTypeERROR = 0, + SectionTypeUnused = 1 << 0, + SectionTypeData = 1 << 1, + SectionTypeRelData = 1 << 2, + SectionTypeSymTab = 1 << 3, + SectionTypeStrTab = 1 << 4, + SectionTypeManifest = 1 << 5, + SectionTypeDebugLink = 1 << 6, + + SectionTypeValid = SectionTypeSymTab | SectionTypeStrTab | SectionTypeManifest, +} SectionType; + +static bool elf_load_metadata( + ELFFile* elf, + Elf32_Shdr* section_header, + FlipperApplicationManifest* manifest) { + if(section_header->sh_size < sizeof(FlipperApplicationManifest)) { + return false; + } + + if(manifest == NULL) { + return true; + } + + return storage_file_seek(elf->fd, section_header->sh_offset, true) && + storage_file_read(elf->fd, manifest, section_header->sh_size) == + section_header->sh_size; +} + +static bool elf_load_debug_link(ELFFile* elf, Elf32_Shdr* section_header) { + elf->debug_link_info.debug_link_size = section_header->sh_size; + elf->debug_link_info.debug_link = malloc(section_header->sh_size); + + return storage_file_seek(elf->fd, section_header->sh_offset, true) && + storage_file_read(elf->fd, elf->debug_link_info.debug_link, section_header->sh_size) == + section_header->sh_size; +} + +static SectionType elf_preload_section( + ELFFile* elf, + size_t section_idx, + Elf32_Shdr* section_header, + string_t name_string, + FlipperApplicationManifest* manifest) { + const char* name = string_get_cstr(name_string); + + const struct { + const char* prefix; + SectionType type; + } lookup_sections[] = { + {".text", SectionTypeData}, + {".rodata", SectionTypeData}, + {".data", SectionTypeData}, + {".bss", SectionTypeData}, + {".preinit_array", SectionTypeData}, + {".init_array", SectionTypeData}, + {".fini_array", SectionTypeData}, + {".rel.text", SectionTypeRelData}, + {".rel.rodata", SectionTypeRelData}, + {".rel.data", SectionTypeRelData}, + {".rel.preinit_array", SectionTypeRelData}, + {".rel.init_array", SectionTypeRelData}, + {".rel.fini_array", SectionTypeRelData}, + }; + + for(size_t i = 0; i < COUNT_OF(lookup_sections); i++) { + if(cstr_prefix(lookup_sections[i].prefix, name)) { + FURI_LOG_D(TAG, "Found section %s", lookup_sections[i].prefix); + + if(lookup_sections[i].type == SectionTypeRelData) { + name = name + strlen(".rel"); + } + + ELFSection* section_p = elf_file_get_section(elf, name); + if(!section_p) { + ELFSection section = { + .data = NULL, + .sec_idx = 0, + .rel_sec_idx = 0, + .size = 0, + }; + + elf_file_put_section(elf, name, §ion); + section_p = elf_file_get_section(elf, name); + } + + if(lookup_sections[i].type == SectionTypeRelData) { + section_p->rel_sec_idx = section_idx; + } else { + section_p->sec_idx = section_idx; + } + + return lookup_sections[i].type; + } + } + + if(strcmp(name, ".symtab") == 0) { + FURI_LOG_D(TAG, "Found .symtab section"); + elf->symbol_table = section_header->sh_offset; + elf->symbol_count = section_header->sh_size / sizeof(Elf32_Sym); + return SectionTypeSymTab; + } else if(strcmp(name, ".strtab") == 0) { + FURI_LOG_D(TAG, "Found .strtab section"); + elf->symbol_table_strings = section_header->sh_offset; + return SectionTypeStrTab; + } else if(strcmp(name, ".fapmeta") == 0) { + FURI_LOG_D(TAG, "Found .fapmeta section"); + if(elf_load_metadata(elf, section_header, manifest)) { + return SectionTypeManifest; + } else { + return SectionTypeERROR; + } + } else if(strcmp(name, ".gnu_debuglink") == 0) { + FURI_LOG_D(TAG, "Found .gnu_debuglink section"); + if(elf_load_debug_link(elf, section_header)) { + return SectionTypeDebugLink; + } else { + return SectionTypeERROR; + } + } + + return SectionTypeUnused; +} + +static bool elf_load_section_data(ELFFile* elf, ELFSection* section) { + Elf32_Shdr section_header; + if(section->sec_idx == 0) { + FURI_LOG_D(TAG, "Section is not present"); + return true; + } + + if(!elf_read_section_header(elf, section->sec_idx, §ion_header)) { + return false; + } + + if(section_header.sh_size == 0) { + FURI_LOG_D(TAG, "No data for section"); + return true; + } + + section->data = aligned_malloc(section_header.sh_size, section_header.sh_addralign); + section->size = section_header.sh_size; + + if(section_header.sh_type == SHT_NOBITS) { + /* section is empty (.bss?) */ + /* no need to memset - allocator already did that */ + return true; + } + + if((!storage_file_seek(elf->fd, section_header.sh_offset, true)) || + (storage_file_read(elf->fd, section->data, section_header.sh_size) != + section_header.sh_size)) { + FURI_LOG_E(TAG, " seek/read fail"); + return false; + } + + FURI_LOG_D(TAG, "0x%X", section->data); + return true; +} + +static bool elf_relocate_section(ELFFile* elf, ELFSection* section) { + Elf32_Shdr section_header; + if(section->rel_sec_idx) { + FURI_LOG_D(TAG, "Relocating section"); + if(elf_read_section_header(elf, section->rel_sec_idx, §ion_header)) + return elf_relocate(elf, §ion_header, section); + else { + FURI_LOG_E(TAG, "Error reading section header"); + return false; + } + } else { + FURI_LOG_D(TAG, "No relocation index"); /* Not an error */ + } + return true; +} + +static void elf_file_call_section_list(ELFFile* elf, const char* name, bool reverse_order) { + ELFSection* section = elf_file_get_section(elf, name); + + if(section && section->size) { + const uint32_t* start = section->data; + const uint32_t* end = section->data + section->size; + + if(reverse_order) { + while(end > start) { + end--; + ((void (*)(void))(*end))(); + } + } else { + while(start < end) { + ((void (*)(void))(*start))(); + start++; + } + } + } +} + +/**************************************************************************************************/ +/********************************************* Public *********************************************/ +/**************************************************************************************************/ + +ELFFile* elf_file_alloc(Storage* storage, const ElfApiInterface* api_interface) { + ELFFile* elf = malloc(sizeof(ELFFile)); + elf->fd = storage_file_alloc(storage); + elf->api_interface = api_interface; + ELFSectionDict_init(elf->sections); + AddressCache_init(elf->trampoline_cache); + return elf; +} + +void elf_file_free(ELFFile* elf) { + // free sections data + { + ELFSectionDict_it_t it; + for(ELFSectionDict_it(it, elf->sections); !ELFSectionDict_end_p(it); + ELFSectionDict_next(it)) { + const ELFSectionDict_itref_t* itref = ELFSectionDict_cref(it); + if(itref->value.data) { + aligned_free(itref->value.data); + } + free((void*)itref->key); + } + + ELFSectionDict_clear(elf->sections); + } + + // free trampoline data + { + AddressCache_it_t it; + for(AddressCache_it(it, elf->trampoline_cache); !AddressCache_end_p(it); + AddressCache_next(it)) { + const AddressCache_itref_t* itref = AddressCache_cref(it); + free((void*)itref->value); + } + + AddressCache_clear(elf->trampoline_cache); + } + + if(elf->debug_link_info.debug_link) { + free(elf->debug_link_info.debug_link); + } + + storage_file_free(elf->fd); + free(elf); +} + +bool elf_file_open(ELFFile* elf, const char* path) { + Elf32_Ehdr h; + Elf32_Shdr sH; + + if(!storage_file_open(elf->fd, path, FSAM_READ, FSOM_OPEN_EXISTING) || + !storage_file_seek(elf->fd, 0, true) || + storage_file_read(elf->fd, &h, sizeof(h)) != sizeof(h) || + !storage_file_seek(elf->fd, h.e_shoff + h.e_shstrndx * sizeof(sH), true) || + storage_file_read(elf->fd, &sH, sizeof(Elf32_Shdr)) != sizeof(Elf32_Shdr)) { + return false; + } + + elf->entry = h.e_entry; + elf->sections_count = h.e_shnum; + elf->section_table = h.e_shoff; + elf->section_table_strings = sH.sh_offset; + return true; +} + +bool elf_file_load_manifest(ELFFile* elf, FlipperApplicationManifest* manifest) { + bool result = false; + string_t name; + string_init(name); + + FURI_LOG_D(TAG, "Looking for manifest section"); + for(size_t section_idx = 1; section_idx < elf->sections_count; section_idx++) { + Elf32_Shdr section_header; + + string_reset(name); + if(!elf_read_section(elf, section_idx, §ion_header, name)) { + break; + } + + if(string_cmp(name, ".fapmeta") == 0) { + if(elf_load_metadata(elf, §ion_header, manifest)) { + FURI_LOG_D(TAG, "Load manifest done"); + result = true; + break; + } else { + break; + } + } + } + + string_clear(name); + return result; +} + +bool elf_file_load_section_table(ELFFile* elf, FlipperApplicationManifest* manifest) { + SectionType loaded_sections = SectionTypeERROR; + string_t name; + string_init(name); + + FURI_LOG_D(TAG, "Scan ELF indexs..."); + for(size_t section_idx = 1; section_idx < elf->sections_count; section_idx++) { + Elf32_Shdr section_header; + + string_reset(name); + if(!elf_read_section(elf, section_idx, §ion_header, name)) { + loaded_sections = SectionTypeERROR; + break; + } + + FURI_LOG_D(TAG, "Preloading data for section #%d %s", section_idx, string_get_cstr(name)); + SectionType section_type = + elf_preload_section(elf, section_idx, §ion_header, name, manifest); + loaded_sections |= section_type; + + if(section_type == SectionTypeERROR) { + loaded_sections = SectionTypeERROR; + break; + } + } + + string_clear(name); + FURI_LOG_D(TAG, "Load symbols done"); + + return IS_FLAGS_SET(loaded_sections, SectionTypeValid); +} + +ELFFileLoadStatus elf_file_load_sections(ELFFile* elf) { + ELFFileLoadStatus status = ELFFileLoadStatusSuccess; + ELFSectionDict_it_t it; + + AddressCache_init(elf->relocation_cache); + size_t start = furi_get_tick(); + + for(ELFSectionDict_it(it, elf->sections); !ELFSectionDict_end_p(it); ELFSectionDict_next(it)) { + ELFSectionDict_itref_t* itref = ELFSectionDict_ref(it); + FURI_LOG_D(TAG, "Loading section '%s'", itref->key); + if(!elf_load_section_data(elf, &itref->value)) { + FURI_LOG_E(TAG, "Error loading section '%s'", itref->key); + status = ELFFileLoadStatusUnspecifiedError; + } + } + + if(status == ELFFileLoadStatusSuccess) { + for(ELFSectionDict_it(it, elf->sections); !ELFSectionDict_end_p(it); + ELFSectionDict_next(it)) { + ELFSectionDict_itref_t* itref = ELFSectionDict_ref(it); + FURI_LOG_D(TAG, "Relocating section '%s'", itref->key); + if(!elf_relocate_section(elf, &itref->value)) { + FURI_LOG_E(TAG, "Error relocating section '%s'", itref->key); + status = ELFFileLoadStatusMissingImports; + } + } + } + + /* Fixing up entry point */ + if(status == ELFFileLoadStatusSuccess) { + ELFSection* text_section = elf_file_get_section(elf, ".text"); + + if(text_section == NULL) { + FURI_LOG_E(TAG, "No .text section found"); + status = ELFFileLoadStatusUnspecifiedError; + } else { + elf->entry += (uint32_t)text_section->data; + } + } + + FURI_LOG_D(TAG, "Relocation cache size: %u", AddressCache_size(elf->relocation_cache)); + FURI_LOG_D(TAG, "Trampoline cache size: %u", AddressCache_size(elf->trampoline_cache)); + AddressCache_clear(elf->relocation_cache); + FURI_LOG_I(TAG, "Loaded in %ums", (size_t)(furi_get_tick() - start)); + + return status; +} + +void elf_file_pre_run(ELFFile* elf) { + elf_file_call_section_list(elf, ".preinit_array", false); + elf_file_call_section_list(elf, ".init_array", false); +} + +int32_t elf_file_run(ELFFile* elf, void* args) { + int32_t result; + result = ((int32_t(*)(void*))elf->entry)(args); + return result; +} + +void elf_file_post_run(ELFFile* elf) { + elf_file_call_section_list(elf, ".fini_array", true); +} + +const ElfApiInterface* elf_file_get_api_interface(ELFFile* elf_file) { + return elf_file->api_interface; +} + +void elf_file_init_debug_info(ELFFile* elf, ELFDebugInfo* debug_info) { + // set entry + debug_info->entry = elf->entry; + + // copy debug info + memcpy(&debug_info->debug_link_info, &elf->debug_link_info, sizeof(ELFDebugLinkInfo)); + + // init mmap + debug_info->mmap_entry_count = ELFSectionDict_size(elf->sections); + debug_info->mmap_entries = malloc(sizeof(ELFMemoryMapEntry) * debug_info->mmap_entry_count); + uint32_t mmap_entry_idx = 0; + + ELFSectionDict_it_t it; + for(ELFSectionDict_it(it, elf->sections); !ELFSectionDict_end_p(it); ELFSectionDict_next(it)) { + const ELFSectionDict_itref_t* itref = ELFSectionDict_cref(it); + + const void* data_ptr = itref->value.data; + if(data_ptr) { + debug_info->mmap_entries[mmap_entry_idx].address = (uint32_t)data_ptr; + debug_info->mmap_entries[mmap_entry_idx].name = itref->key; + mmap_entry_idx++; + } + } +} + +void elf_file_clear_debug_info(ELFDebugInfo* debug_info) { + // clear debug info + memset(&debug_info->debug_link_info, 0, sizeof(ELFDebugLinkInfo)); + + // clear mmap + if(debug_info->mmap_entries) { + free(debug_info->mmap_entries); + debug_info->mmap_entries = NULL; + } + + debug_info->mmap_entry_count = 0; +} diff --git a/lib/flipper_application/elf/elf_file.h b/lib/flipper_application/elf/elf_file.h new file mode 100644 index 00000000000..673f165ccca --- /dev/null +++ b/lib/flipper_application/elf/elf_file.h @@ -0,0 +1,127 @@ +/** + * @file elf_file.h + * ELF file loader + */ +#pragma once +#include +#include "../application_manifest.h" +#include "elf_api_interface.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct ELFFile ELFFile; + +typedef struct { + const char* name; + uint32_t address; +} ELFMemoryMapEntry; + +typedef struct { + uint32_t debug_link_size; + uint8_t* debug_link; +} ELFDebugLinkInfo; + +typedef struct { + uint32_t mmap_entry_count; + ELFMemoryMapEntry* mmap_entries; + ELFDebugLinkInfo debug_link_info; + off_t entry; +} ELFDebugInfo; + +typedef enum { + ELFFileLoadStatusSuccess = 0, + ELFFileLoadStatusUnspecifiedError, + ELFFileLoadStatusNoFreeMemory, + ELFFileLoadStatusMissingImports, +} ELFFileLoadStatus; + +/** + * @brief Allocate ELFFile instance + * @param storage + * @param api_interface + * @return ELFFile* + */ +ELFFile* elf_file_alloc(Storage* storage, const ElfApiInterface* api_interface); + +/** + * @brief Free ELFFile instance + * @param elf_file + */ +void elf_file_free(ELFFile* elf_file); + +/** + * @brief Open ELF file + * @param elf_file + * @param path + * @return bool + */ +bool elf_file_open(ELFFile* elf_file, const char* path); + +/** + * @brief Load ELF file manifest + * @param elf + * @param manifest + * @return bool + */ +bool elf_file_load_manifest(ELFFile* elf, FlipperApplicationManifest* manifest); + +/** + * @brief Load ELF file section table (load stage #1) + * @param elf_file + * @param manifest + * @return bool + */ +bool elf_file_load_section_table(ELFFile* elf_file, FlipperApplicationManifest* manifest); + +/** + * @brief Load and relocate ELF file sections (load stage #2) + * @param elf_file + * @return ELFFileLoadStatus + */ +ELFFileLoadStatus elf_file_load_sections(ELFFile* elf_file); + +/** + * @brief Execute ELF file pre-run stage, call static constructors for example (load stage #3) + * @param elf + */ +void elf_file_pre_run(ELFFile* elf); + +/** + * @brief Run ELF file (load stage #4) + * @param elf_file + * @param args + * @return int32_t + */ +int32_t elf_file_run(ELFFile* elf_file, void* args); + +/** + * @brief Execute ELF file post-run stage, call static destructors for example (load stage #5) + * @param elf + */ +void elf_file_post_run(ELFFile* elf); + +/** + * @brief Get ELF file API interface + * @param elf_file + * @return const ElfApiInterface* + */ +const ElfApiInterface* elf_file_get_api_interface(ELFFile* elf_file); + +/** + * @brief Get ELF file debug info + * @param elf_file + * @param debug_info + */ +void elf_file_init_debug_info(ELFFile* elf_file, ELFDebugInfo* debug_info); + +/** + * @brief Clear ELF file debug info generated by elf_file_init_debug_info + * @param debug_info + */ +void elf_file_clear_debug_info(ELFDebugInfo* debug_info); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/flipper_application/elf/elf_file_i.h b/lib/flipper_application/elf/elf_file_i.h new file mode 100644 index 00000000000..1df075f062b --- /dev/null +++ b/lib/flipper_application/elf/elf_file_i.h @@ -0,0 +1,46 @@ +#pragma once +#include "elf_file.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +DICT_DEF2(AddressCache, int, M_DEFAULT_OPLIST, Elf32_Addr, M_DEFAULT_OPLIST) + +/** + * Callable elf entry type + */ +typedef int32_t(entry_t)(void*); + +typedef struct { + void* data; + uint16_t sec_idx; + uint16_t rel_sec_idx; + Elf32_Word size; +} ELFSection; + +DICT_DEF2(ELFSectionDict, const char*, M_CSTR_OPLIST, ELFSection, M_POD_OPLIST) + +struct ELFFile { + size_t sections_count; + off_t section_table; + off_t section_table_strings; + + size_t symbol_count; + off_t symbol_table; + off_t symbol_table_strings; + off_t entry; + ELFSectionDict_t sections; + + AddressCache_t relocation_cache; + AddressCache_t trampoline_cache; + + File* fd; + const ElfApiInterface* api_interface; + ELFDebugLinkInfo debug_link_info; +}; + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/flipper_application/flipper_applicaiton_i.c b/lib/flipper_application/flipper_applicaiton_i.c deleted file mode 100644 index a2a069eebc5..00000000000 --- a/lib/flipper_application/flipper_applicaiton_i.c +++ /dev/null @@ -1,477 +0,0 @@ -#include "flipper_application_i.h" -#include - -#define TAG "fapp-i" - -#define RESOLVER_THREAD_YIELD_STEP 30 - -#define IS_FLAGS_SET(v, m) ((v & m) == m) -#define SECTION_OFFSET(e, n) (e->section_table + n * sizeof(Elf32_Shdr)) -#define SYMBOL_OFFSET(e, n) (e->_table + n * sizeof(Elf32_Shdr)) - -bool flipper_application_load_elf_headers(FlipperApplication* e, const char* path) { - Elf32_Ehdr h; - Elf32_Shdr sH; - - if(!storage_file_open(e->fd, path, FSAM_READ, FSOM_OPEN_EXISTING) || - !storage_file_seek(e->fd, 0, true) || - storage_file_read(e->fd, &h, sizeof(h)) != sizeof(h) || - !storage_file_seek(e->fd, h.e_shoff + h.e_shstrndx * sizeof(sH), true) || - storage_file_read(e->fd, &sH, sizeof(Elf32_Shdr)) != sizeof(Elf32_Shdr)) { - return false; - } - - e->entry = h.e_entry; - e->sections = h.e_shnum; - e->section_table = h.e_shoff; - e->section_table_strings = sH.sh_offset; - return true; -} - -static bool flipper_application_load_metadata(FlipperApplication* e, Elf32_Shdr* sh) { - if(sh->sh_size < sizeof(e->manifest)) { - return false; - } - - return storage_file_seek(e->fd, sh->sh_offset, true) && - storage_file_read(e->fd, &e->manifest, sh->sh_size) == sh->sh_size; -} - -static bool flipper_application_load_debug_link(FlipperApplication* e, Elf32_Shdr* sh) { - e->state.debug_link_size = sh->sh_size; - e->state.debug_link = malloc(sh->sh_size); - - return storage_file_seek(e->fd, sh->sh_offset, true) && - storage_file_read(e->fd, e->state.debug_link, sh->sh_size) == sh->sh_size; -} - -static FindFlags_t flipper_application_preload_section( - FlipperApplication* e, - Elf32_Shdr* sh, - const char* name, - int n) { - FURI_LOG_D(TAG, "Processing: %s", name); - - const struct { - const char* name; - uint16_t* ptr_section_idx; - FindFlags_t flags; - } lookup_sections[] = { - {".text", &e->text.sec_idx, FoundText}, - {".rodata", &e->rodata.sec_idx, FoundRodata}, - {".data", &e->data.sec_idx, FoundData}, - {".bss", &e->bss.sec_idx, FoundBss}, - {".rel.text", &e->text.rel_sec_idx, FoundRelText}, - {".rel.rodata", &e->rodata.rel_sec_idx, FoundRelRodata}, - {".rel.data", &e->data.rel_sec_idx, FoundRelData}, - }; - - for(size_t i = 0; i < COUNT_OF(lookup_sections); i++) { - if(strcmp(name, lookup_sections[i].name) == 0) { - *lookup_sections[i].ptr_section_idx = n; - return lookup_sections[i].flags; - } - } - - if(strcmp(name, ".symtab") == 0) { - e->symbol_table = sh->sh_offset; - e->symbol_count = sh->sh_size / sizeof(Elf32_Sym); - return FoundSymTab; - } else if(strcmp(name, ".strtab") == 0) { - e->symbol_table_strings = sh->sh_offset; - return FoundStrTab; - } else if(strcmp(name, ".fapmeta") == 0) { - // Load metadata immediately - if(flipper_application_load_metadata(e, sh)) { - return FoundFappManifest; - } - } else if(strcmp(name, ".gnu_debuglink") == 0) { - if(flipper_application_load_debug_link(e, sh)) { - return FoundDebugLink; - } - } - return FoundERROR; -} - -static bool - read_string_from_offset(FlipperApplication* e, off_t offset, char* buffer, size_t buffer_size) { - bool success = false; - - off_t old = storage_file_tell(e->fd); - if(storage_file_seek(e->fd, offset, true) && - (storage_file_read(e->fd, buffer, buffer_size) == buffer_size)) { - success = true; - } - storage_file_seek(e->fd, old, true); - - return success; -} - -static bool read_section_name(FlipperApplication* e, off_t off, char* buf, size_t max) { - return read_string_from_offset(e, e->section_table_strings + off, buf, max); -} - -static bool read_symbol_name(FlipperApplication* e, off_t off, char* buf, size_t max) { - return read_string_from_offset(e, e->symbol_table_strings + off, buf, max); -} - -static bool read_section_header(FlipperApplication* e, int n, Elf32_Shdr* h) { - off_t offset = SECTION_OFFSET(e, n); - return storage_file_seek(e->fd, offset, true) && - storage_file_read(e->fd, h, sizeof(Elf32_Shdr)) == sizeof(Elf32_Shdr); -} - -static bool read_section(FlipperApplication* e, int n, Elf32_Shdr* h, char* name, size_t nlen) { - if(!read_section_header(e, n, h)) { - return false; - } - if(!h->sh_name) { - return true; - } - return read_section_name(e, h->sh_name, name, nlen); -} - -bool flipper_application_load_section_table(FlipperApplication* e) { - furi_check(e->state.mmap_entry_count == 0); - - size_t n; - FindFlags_t found = FoundERROR; - FURI_LOG_D(TAG, "Scan ELF indexs..."); - for(n = 1; n < e->sections; n++) { - Elf32_Shdr section_header; - char name[33] = {0}; - if(!read_section_header(e, n, §ion_header)) { - return false; - } - if(section_header.sh_name && - !read_section_name(e, section_header.sh_name, name, sizeof(name))) { - return false; - } - - FURI_LOG_T(TAG, "Examining section %d %s", n, name); - FindFlags_t section_flags = - flipper_application_preload_section(e, §ion_header, name, n); - found |= section_flags; - if((section_flags & FoundGdbSection) != 0) { - e->state.mmap_entry_count++; - } - if(IS_FLAGS_SET(found, FoundAll)) { - return true; - } - } - - FURI_LOG_D(TAG, "Load symbols done"); - return IS_FLAGS_SET(found, FoundValid); -} - -static const char* type_to_str(int symt) { -#define STRCASE(name) \ - case name: \ - return #name; - switch(symt) { - STRCASE(R_ARM_NONE) - STRCASE(R_ARM_ABS32) - STRCASE(R_ARM_THM_PC22) - STRCASE(R_ARM_THM_JUMP24) - default: - return "R_"; - } -#undef STRCASE -} - -static void relocate_jmp_call(Elf32_Addr relAddr, int type, Elf32_Addr symAddr) { - UNUSED(type); - uint16_t upper_insn = ((uint16_t*)relAddr)[0]; - uint16_t lower_insn = ((uint16_t*)relAddr)[1]; - uint32_t S = (upper_insn >> 10) & 1; - uint32_t J1 = (lower_insn >> 13) & 1; - uint32_t J2 = (lower_insn >> 11) & 1; - - int32_t offset = (S << 24) | /* S -> offset[24] */ - ((~(J1 ^ S) & 1) << 23) | /* J1 -> offset[23] */ - ((~(J2 ^ S) & 1) << 22) | /* J2 -> offset[22] */ - ((upper_insn & 0x03ff) << 12) | /* imm10 -> offset[12:21] */ - ((lower_insn & 0x07ff) << 1); /* imm11 -> offset[1:11] */ - if(offset & 0x01000000) offset -= 0x02000000; - - offset += symAddr - relAddr; - - S = (offset >> 24) & 1; - J1 = S ^ (~(offset >> 23) & 1); - J2 = S ^ (~(offset >> 22) & 1); - - upper_insn = ((upper_insn & 0xf800) | (S << 10) | ((offset >> 12) & 0x03ff)); - ((uint16_t*)relAddr)[0] = upper_insn; - - lower_insn = ((lower_insn & 0xd000) | (J1 << 13) | (J2 << 11) | ((offset >> 1) & 0x07ff)); - ((uint16_t*)relAddr)[1] = lower_insn; -} - -static bool relocate_symbol(Elf32_Addr relAddr, int type, Elf32_Addr symAddr) { - switch(type) { - case R_ARM_ABS32: - *((uint32_t*)relAddr) += symAddr; - FURI_LOG_D(TAG, " R_ARM_ABS32 relocated is 0x%08X", (unsigned int)*((uint32_t*)relAddr)); - break; - case R_ARM_THM_PC22: - case R_ARM_THM_JUMP24: - relocate_jmp_call(relAddr, type, symAddr); - FURI_LOG_D( - TAG, " R_ARM_THM_CALL/JMP relocated is 0x%08X", (unsigned int)*((uint32_t*)relAddr)); - break; - default: - FURI_LOG_D(TAG, " Undefined relocation %d", type); - return false; - } - return true; -} - -static ELFSection_t* section_of(FlipperApplication* e, int index) { - if(e->text.sec_idx == index) { - return &e->text; - } else if(e->data.sec_idx == index) { - return &e->data; - } else if(e->bss.sec_idx == index) { - return &e->bss; - } else if(e->rodata.sec_idx == index) { - return &e->rodata; - } - return NULL; -} - -static Elf32_Addr address_of(FlipperApplication* e, Elf32_Sym* sym, const char* sName) { - if(sym->st_shndx == SHN_UNDEF) { - Elf32_Addr addr = 0; - if(e->api_interface->resolver_callback(sName, &addr)) { - return addr; - } - } else { - ELFSection_t* symSec = section_of(e, sym->st_shndx); - if(symSec) { - return ((Elf32_Addr)symSec->data) + sym->st_value; - } - } - FURI_LOG_D(TAG, " Can not find address for symbol %s", sName); - return ELF_INVALID_ADDRESS; -} - -static bool read_symbol(FlipperApplication* e, int n, Elf32_Sym* sym, char* name, size_t nlen) { - bool success = false; - off_t old = storage_file_tell(e->fd); - off_t pos = e->symbol_table + n * sizeof(Elf32_Sym); - if(storage_file_seek(e->fd, pos, true) && - storage_file_read(e->fd, sym, sizeof(Elf32_Sym)) == sizeof(Elf32_Sym)) { - if(sym->st_name) - success = read_symbol_name(e, sym->st_name, name, nlen); - else { - Elf32_Shdr shdr; - success = read_section(e, sym->st_shndx, &shdr, name, nlen); - } - } - storage_file_seek(e->fd, old, true); - return success; -} - -static bool - relocation_cache_get(RelocationAddressCache_t cache, int symEntry, Elf32_Addr* symAddr) { - Elf32_Addr* addr = RelocationAddressCache_get(cache, symEntry); - if(addr) { - *symAddr = *addr; - return true; - } else { - return false; - } -} - -static void - relocation_cache_put(RelocationAddressCache_t cache, int symEntry, Elf32_Addr symAddr) { - RelocationAddressCache_set_at(cache, symEntry, symAddr); -} - -#define MAX_SYMBOL_NAME_LEN 128u - -static bool relocate(FlipperApplication* e, Elf32_Shdr* h, ELFSection_t* s) { - if(s->data) { - Elf32_Rel rel; - size_t relEntries = h->sh_size / sizeof(rel); - size_t relCount; - (void)storage_file_seek(e->fd, h->sh_offset, true); - FURI_LOG_D(TAG, " Offset Info Type Name"); - - int relocate_result = true; - char symbol_name[MAX_SYMBOL_NAME_LEN + 1] = {0}; - - for(relCount = 0; relCount < relEntries; relCount++) { - if(relCount % RESOLVER_THREAD_YIELD_STEP == 0) { - FURI_LOG_D(TAG, " reloc YIELD"); - furi_delay_tick(1); - } - - if(storage_file_read(e->fd, &rel, sizeof(Elf32_Rel)) != sizeof(Elf32_Rel)) { - FURI_LOG_E(TAG, " reloc read fail"); - return false; - } - - Elf32_Addr symAddr; - - int symEntry = ELF32_R_SYM(rel.r_info); - int relType = ELF32_R_TYPE(rel.r_info); - Elf32_Addr relAddr = ((Elf32_Addr)s->data) + rel.r_offset; - - if(!relocation_cache_get(e->relocation_cache, symEntry, &symAddr)) { - Elf32_Sym sym; - if(!read_symbol(e, symEntry, &sym, symbol_name, MAX_SYMBOL_NAME_LEN)) { - FURI_LOG_E(TAG, " symbol read fail"); - return false; - } - - FURI_LOG_D( - TAG, - " %08X %08X %-16s %s", - (unsigned int)rel.r_offset, - (unsigned int)rel.r_info, - type_to_str(relType), - symbol_name); - - symAddr = address_of(e, &sym, symbol_name); - relocation_cache_put(e->relocation_cache, symEntry, symAddr); - } - - if(symAddr != ELF_INVALID_ADDRESS) { - FURI_LOG_D( - TAG, - " symAddr=%08X relAddr=%08X", - (unsigned int)symAddr, - (unsigned int)relAddr); - if(!relocate_symbol(relAddr, relType, symAddr)) { - relocate_result = false; - } - } else { - FURI_LOG_D(TAG, " No symbol address of %s", symbol_name); - relocate_result = false; - } - } - - return relocate_result; - } else - FURI_LOG_I(TAG, "Section not loaded"); - - return false; -} - -static bool flipper_application_load_section_data(FlipperApplication* e, ELFSection_t* s) { - Elf32_Shdr section_header; - if(s->sec_idx == 0) { - FURI_LOG_I(TAG, "Section is not present"); - return true; - } - - if(!read_section_header(e, s->sec_idx, §ion_header)) { - return false; - } - - if(section_header.sh_size == 0) { - FURI_LOG_I(TAG, "No data for section"); - return true; - } - - s->data = aligned_malloc(section_header.sh_size, section_header.sh_addralign); - // e->state.mmap_entry_count++; - - if(section_header.sh_type == SHT_NOBITS) { - /* section is empty (.bss?) */ - /* no need to memset - allocator already did that */ - /* memset(s->data, 0, h->sh_size); */ - FURI_LOG_D(TAG, "0x%X", s->data); - return true; - } - - if((!storage_file_seek(e->fd, section_header.sh_offset, true)) || - (storage_file_read(e->fd, s->data, section_header.sh_size) != section_header.sh_size)) { - FURI_LOG_E(TAG, " seek/read fail"); - flipper_application_free_section(s); - return false; - } - - FURI_LOG_D(TAG, "0x%X", s->data); - return true; -} - -static bool flipper_application_relocate_section(FlipperApplication* e, ELFSection_t* s) { - Elf32_Shdr section_header; - if(s->rel_sec_idx) { - FURI_LOG_D(TAG, "Relocating section"); - if(read_section_header(e, s->rel_sec_idx, §ion_header)) - return relocate(e, §ion_header, s); - else { - FURI_LOG_E(TAG, "Error reading section header"); - return false; - } - } else - FURI_LOG_D(TAG, "No relocation index"); /* Not an error */ - return true; -} - -FlipperApplicationLoadStatus flipper_application_load_sections(FlipperApplication* e) { - FlipperApplicationLoadStatus status = FlipperApplicationLoadStatusSuccess; - RelocationAddressCache_init(e->relocation_cache); - size_t start = furi_get_tick(); - - struct { - ELFSection_t* section; - const char* name; - } sections[] = { - {&e->text, ".text"}, - {&e->rodata, ".rodata"}, - {&e->data, ".data"}, - {&e->bss, ".bss"}, - }; - - for(size_t i = 0; i < COUNT_OF(sections); i++) { - if(!flipper_application_load_section_data(e, sections[i].section)) { - FURI_LOG_E(TAG, "Error loading section '%s'", sections[i].name); - status = FlipperApplicationLoadStatusUnspecifiedError; - } - } - - if(status == FlipperApplicationLoadStatusSuccess) { - for(size_t i = 0; i < COUNT_OF(sections); i++) { - if(!flipper_application_relocate_section(e, sections[i].section)) { - FURI_LOG_E(TAG, "Error relocating section '%s'", sections[i].name); - status = FlipperApplicationLoadStatusMissingImports; - } - } - } - - if(status == FlipperApplicationLoadStatusSuccess) { - e->state.mmap_entries = - malloc(sizeof(FlipperApplicationMemoryMapEntry) * e->state.mmap_entry_count); - uint32_t mmap_entry_idx = 0; - for(size_t i = 0; i < COUNT_OF(sections); i++) { - const void* data_ptr = sections[i].section->data; - if(data_ptr) { - FURI_LOG_I(TAG, "0x%X %s", (uint32_t)data_ptr, sections[i].name); - e->state.mmap_entries[mmap_entry_idx].address = (uint32_t)data_ptr; - e->state.mmap_entries[mmap_entry_idx].name = sections[i].name; - mmap_entry_idx++; - } - } - furi_check(mmap_entry_idx == e->state.mmap_entry_count); - - /* Fixing up entry point */ - e->entry += (uint32_t)e->text.data; - } - - FURI_LOG_D(TAG, "Relocation cache size: %u", RelocationAddressCache_size(e->relocation_cache)); - RelocationAddressCache_clear(e->relocation_cache); - FURI_LOG_I(TAG, "Loaded in %ums", (size_t)(furi_get_tick() - start)); - - return status; -} - -void flipper_application_free_section(ELFSection_t* s) { - if(s->data) { - aligned_free(s->data); - } - s->data = NULL; -} diff --git a/lib/flipper_application/flipper_application.c b/lib/flipper_application/flipper_application.c index 6e84cce38a0..cf44eebb2ab 100644 --- a/lib/flipper_application/flipper_application.c +++ b/lib/flipper_application/flipper_application.c @@ -1,16 +1,22 @@ #include "flipper_application.h" -#include "flipper_application_i.h" +#include "elf/elf_file.h" #define TAG "fapp" +struct FlipperApplication { + ELFDebugInfo state; + FlipperApplicationManifest manifest; + ELFFile* elf; + FuriThread* thread; +}; + /* For debugger access to app state */ FlipperApplication* last_loaded_app = NULL; FlipperApplication* flipper_application_alloc(Storage* storage, const ElfApiInterface* api_interface) { FlipperApplication* app = malloc(sizeof(FlipperApplication)); - app->api_interface = api_interface; - app->fd = storage_file_alloc(storage); + app->elf = elf_file_alloc(storage, api_interface); app->thread = NULL; return app; } @@ -25,43 +31,43 @@ void flipper_application_free(FlipperApplication* app) { last_loaded_app = NULL; - if(app->state.debug_link_size) { - free(app->state.debug_link); - } + elf_file_clear_debug_info(&app->state); + elf_file_free(app->elf); + free(app); +} - if(app->state.mmap_entries) { - free(app->state.mmap_entries); +static FlipperApplicationPreloadStatus + flipper_application_validate_manifest(FlipperApplication* app) { + if(!flipper_application_manifest_is_valid(&app->manifest)) { + return FlipperApplicationPreloadStatusInvalidManifest; } - ELFSection_t* sections[] = {&app->text, &app->rodata, &app->data, &app->bss}; - for(size_t i = 0; i < COUNT_OF(sections); i++) { - flipper_application_free_section(sections[i]); + if(!flipper_application_manifest_is_compatible( + &app->manifest, elf_file_get_api_interface(app->elf))) { + return FlipperApplicationPreloadStatusApiMismatch; } - storage_file_free(app->fd); - - free(app); + return FlipperApplicationPreloadStatusSuccess; } /* Parse headers, load manifest */ FlipperApplicationPreloadStatus - flipper_application_preload(FlipperApplication* app, const char* path) { - if(!flipper_application_load_elf_headers(app, path) || - !flipper_application_load_section_table(app)) { + flipper_application_preload_manifest(FlipperApplication* app, const char* path) { + if(!elf_file_open(app->elf, path) || !elf_file_load_manifest(app->elf, &app->manifest)) { return FlipperApplicationPreloadStatusInvalidFile; } - if((app->manifest.base.manifest_magic != FAP_MANIFEST_MAGIC) && - (app->manifest.base.manifest_version == FAP_MANIFEST_SUPPORTED_VERSION)) { - return FlipperApplicationPreloadStatusInvalidManifest; - } + return flipper_application_validate_manifest(app); +} - if(app->manifest.base.api_version.major != app->api_interface->api_version_major /* || - app->manifest.base.api_version.minor > app->api_interface->api_version_minor */) { - return FlipperApplicationPreloadStatusApiMismatch; +/* Parse headers, load full file */ +FlipperApplicationPreloadStatus + flipper_application_preload(FlipperApplication* app, const char* path) { + if(!elf_file_open(app->elf, path) || !elf_file_load_section_table(app->elf, &app->manifest)) { + return FlipperApplicationPreloadStatusInvalidFile; } - return FlipperApplicationPreloadStatusSuccess; + return flipper_application_validate_manifest(app); } const FlipperApplicationManifest* flipper_application_get_manifest(FlipperApplication* app) { @@ -70,11 +76,26 @@ const FlipperApplicationManifest* flipper_application_get_manifest(FlipperApplic FlipperApplicationLoadStatus flipper_application_map_to_memory(FlipperApplication* app) { last_loaded_app = app; - return flipper_application_load_sections(app); + ELFFileLoadStatus status = elf_file_load_sections(app->elf); + + switch(status) { + case ELFFileLoadStatusSuccess: + elf_file_init_debug_info(app->elf, &app->state); + return FlipperApplicationLoadStatusSuccess; + case ELFFileLoadStatusNoFreeMemory: + return FlipperApplicationLoadStatusNoFreeMemory; + case ELFFileLoadStatusMissingImports: + return FlipperApplicationLoadStatusMissingImports; + default: + return FlipperApplicationLoadStatusUnspecifiedError; + } } -const FlipperApplicationState* flipper_application_get_state(FlipperApplication* app) { - return &app->state; +static int32_t flipper_application_thread(void* context) { + elf_file_pre_run(last_loaded_app->elf); + int32_t result = elf_file_run(last_loaded_app->elf, context); + elf_file_post_run(last_loaded_app->elf); + return result; } FuriThread* flipper_application_spawn(FlipperApplication* app, void* args) { @@ -86,20 +107,12 @@ FuriThread* flipper_application_spawn(FlipperApplication* app, void* args) { app->thread = furi_thread_alloc(); furi_thread_set_stack_size(app->thread, manifest->stack_size); furi_thread_set_name(app->thread, manifest->name); - furi_thread_set_callback(app->thread, (entry_t*)app->entry); + furi_thread_set_callback(app->thread, flipper_application_thread); furi_thread_set_context(app->thread, args); return app->thread; } -FuriThread* flipper_application_get_thread(FlipperApplication* app) { - return app->thread; -} - -void const* flipper_application_get_entry_address(FlipperApplication* app) { - return (void*)app->entry; -} - static const char* preload_status_strings[] = { [FlipperApplicationPreloadStatusSuccess] = "Success", [FlipperApplicationPreloadStatusUnspecifiedError] = "Unknown error", diff --git a/lib/flipper_application/flipper_application.h b/lib/flipper_application/flipper_application.h index 34de4038886..b3e5996bbd5 100644 --- a/lib/flipper_application/flipper_application.h +++ b/lib/flipper_application/flipper_application.h @@ -1,3 +1,7 @@ +/** + * @file flipper_application.h + * Flipper application + */ #pragma once #include "application_manifest.h" @@ -79,6 +83,14 @@ void flipper_application_free(FlipperApplication* app); FlipperApplicationPreloadStatus flipper_application_preload(FlipperApplication* app, const char* path); +/** + * @brief Validate elf file and load application manifest + * @param app Application pointer + * @return Preload result code + */ +FlipperApplicationPreloadStatus + flipper_application_preload_manifest(FlipperApplication* app, const char* path); + /** * @brief Get pointer to application manifest for preloaded application * @param app Application pointer @@ -93,13 +105,6 @@ const FlipperApplicationManifest* flipper_application_get_manifest(FlipperApplic */ FlipperApplicationLoadStatus flipper_application_map_to_memory(FlipperApplication* app); -/** - * @brief Get state object for loaded application - * @param app Application pointer - * @return Pointer to state object - */ -const FlipperApplicationState* flipper_application_get_state(FlipperApplication* app); - /** * @brief Create application thread at entry point address, using app name and * stack size from metadata. Returned thread isn't started yet. @@ -110,20 +115,6 @@ const FlipperApplicationState* flipper_application_get_state(FlipperApplication* */ FuriThread* flipper_application_spawn(FlipperApplication* app, void* args); -/** - * @brief Get previously spawned thread - * @param app Application pointer - * @return Created thread - */ -FuriThread* flipper_application_get_thread(FlipperApplication* app); - -/** - * @brief Return relocated and valid address of app's entry point - * @param app Application pointer - * @return Address of app's entry point - */ -void const* flipper_application_get_entry_address(FlipperApplication* app); - #ifdef __cplusplus } #endif \ No newline at end of file diff --git a/lib/flipper_application/flipper_application_i.h b/lib/flipper_application/flipper_application_i.h deleted file mode 100644 index 8adf5c0d2d5..00000000000 --- a/lib/flipper_application/flipper_application_i.h +++ /dev/null @@ -1,99 +0,0 @@ -#pragma once - -#include "elf.h" -#include "flipper_application.h" -#include - -#ifdef __cplusplus -extern "C" { -#endif - -DICT_DEF2(RelocationAddressCache, int, M_DEFAULT_OPLIST, Elf32_Addr, M_DEFAULT_OPLIST) - -/** - * Callable elf entry type - */ -typedef int32_t(entry_t)(void*); - -typedef struct { - void* data; - uint16_t sec_idx; - uint16_t rel_sec_idx; -} ELFSection_t; - -struct FlipperApplication { - const ElfApiInterface* api_interface; - File* fd; - FlipperApplicationState state; - FlipperApplicationManifest manifest; - - size_t sections; - off_t section_table; - off_t section_table_strings; - - size_t symbol_count; - off_t symbol_table; - off_t symbol_table_strings; - off_t entry; - - ELFSection_t text; - ELFSection_t rodata; - ELFSection_t data; - ELFSection_t bss; - - FuriThread* thread; - RelocationAddressCache_t relocation_cache; -}; - -typedef enum { - FoundERROR = 0, - FoundSymTab = (1 << 0), - FoundStrTab = (1 << 2), - FoundText = (1 << 3), - FoundRodata = (1 << 4), - FoundData = (1 << 5), - FoundBss = (1 << 6), - FoundRelText = (1 << 7), - FoundRelRodata = (1 << 8), - FoundRelData = (1 << 9), - FoundRelBss = (1 << 10), - FoundFappManifest = (1 << 11), - FoundDebugLink = (1 << 12), - FoundValid = FoundSymTab | FoundStrTab | FoundFappManifest, - FoundExec = FoundValid | FoundText, - FoundGdbSection = FoundText | FoundRodata | FoundData | FoundBss, - FoundAll = FoundSymTab | FoundStrTab | FoundText | FoundRodata | FoundData | FoundBss | - FoundRelText | FoundRelRodata | FoundRelData | FoundRelBss | FoundDebugLink, -} FindFlags_t; - -/** - * @brief Load and validate basic ELF file headers - * @param e Application instance - * @param path FS path to application file - * @return true if ELF file is valid - */ -bool flipper_application_load_elf_headers(FlipperApplication* e, const char* path); - -/** - * @brief Iterate over all sections and save related indexes - * @param e Application instance - * @return true if all required sections are found - */ -bool flipper_application_load_section_table(FlipperApplication* e); - -/** - * @brief Load section data to memory and process relocations - * @param e Application instance - * @return Status code - */ -FlipperApplicationLoadStatus flipper_application_load_sections(FlipperApplication* e); - -/** - * @brief Release section data - * @param s section pointer - */ -void flipper_application_free_section(ELFSection_t* s); - -#ifdef __cplusplus -} -#endif \ No newline at end of file From bc777b2eff816613c6a703e46ab5e24c7d694127 Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Sun, 25 Sep 2022 18:34:52 +0400 Subject: [PATCH 079/824] SubGhz: fix config menu (#1748) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * SubGhz: fix config menu * SubGhz: fix gui Magellen protocol * SubGhz: fix gui Transmit SubGhz * SubGhz: keeloq, new gen manufacture code * SubGhz: Update keeloq_mfcodes Co-authored-by: あく --- .../scenes/subghz_scene_receiver_config.c | 1 + applications/main/subghz/views/transmitter.c | 7 +- assets/resources/subghz/assets/keeloq_mfcodes | 92 ++++++++++--------- lib/subghz/protocols/keeloq.c | 12 +++ lib/subghz/protocols/keeloq_common.c | 12 +++ lib/subghz/protocols/keeloq_common.h | 9 ++ lib/subghz/protocols/magellen.c | 2 +- 7 files changed, 87 insertions(+), 48 deletions(-) diff --git a/applications/main/subghz/scenes/subghz_scene_receiver_config.c b/applications/main/subghz/scenes/subghz_scene_receiver_config.c index c59630f7ea4..541ec0e0d4b 100644 --- a/applications/main/subghz/scenes/subghz_scene_receiver_config.c +++ b/applications/main/subghz/scenes/subghz_scene_receiver_config.c @@ -223,6 +223,7 @@ bool subghz_scene_receiver_config_on_event(void* context, SceneManagerEvent even void subghz_scene_receiver_config_on_exit(void* context) { SubGhz* subghz = context; + variable_item_list_set_selected_item(subghz->variable_item_list, 0); variable_item_list_reset(subghz->variable_item_list); scene_manager_set_scene_state( subghz->scene_manager, SubGhzSceneReadRAW, SubGhzCustomEventManagerNoSet); diff --git a/applications/main/subghz/views/transmitter.c b/applications/main/subghz/views/transmitter.c index 3cbcf098a85..dd2b6d321b1 100644 --- a/applications/main/subghz/views/transmitter.c +++ b/applications/main/subghz/views/transmitter.c @@ -45,7 +45,7 @@ void subghz_view_transmitter_add_data_to_show( } static void subghz_view_transmitter_button_right(Canvas* canvas, const char* str) { - const uint8_t button_height = 13; + const uint8_t button_height = 12; const uint8_t vertical_offset = 3; const uint8_t horizontal_offset = 1; const uint8_t string_width = canvas_string_width(canvas, str); @@ -69,7 +69,10 @@ static void subghz_view_transmitter_button_right(Canvas* canvas, const char* str canvas_invert_color(canvas); canvas_draw_icon( - canvas, x + horizontal_offset, y - button_height + vertical_offset, &I_ButtonCenter_7x7); + canvas, + x + horizontal_offset, + y - button_height + vertical_offset - 1, + &I_ButtonCenter_7x7); canvas_draw_str( canvas, x + horizontal_offset + icon_width_with_offset, y - vertical_offset, str); canvas_invert_color(canvas); diff --git a/assets/resources/subghz/assets/keeloq_mfcodes b/assets/resources/subghz/assets/keeloq_mfcodes index f9771285edc..b8fc36903f2 100644 --- a/assets/resources/subghz/assets/keeloq_mfcodes +++ b/assets/resources/subghz/assets/keeloq_mfcodes @@ -1,48 +1,50 @@ Filetype: Flipper SubGhz Keystore File Version: 0 Encryption: 1 -IV: F2 D4 F5 5A B3 CC 3F 21 28 3A AF ED D1 EB 73 DF -BBFA4D79A73C384D6E07E717F761F32A625F28AA1DB2261B8B19A18261E30AB6 -CE4004AB56111B0B3D486770705FAD8BD616A80957EA2C537BAF1FD09E552DA3 -F974561612C9C751237C64D978F706B41873FDBE38851306574F436CB02D9ECA -E29CAB7C2C2D9853D0F4DF69F582562E8182234E78B355540F6FE3F78C73D518 -97ABE18993A700A607E37DC88E1434F84DDD1C2771693978C9D2FA4CE4F2AB7BBC7C3EB3E8545B37FBBE1C1F1CA03543 -E86ABD0AAE5A4B4A9414C9CB6112CA49B3A8EC29723B14DCA85902F41B05ADDC -C1FBE921035F408C59DA6AD5E76E3887AC9BC90146619B3CAE445BED556E96AC -232C9F86915B927888352797B45F159268FE78956CF09B8D241CDC393D3B0225 -3D9E2A3C701C9D4DD4D72038D4536CA6F515C547CAB0AD18BA71204BD2ABFB74 -4D69A4506D2C97EF8EC68F90CF1AD1065A1EB909793EEB3AF71B0D75E55B9E76 -5A7F4595DFA181C3E946EBEE4974DBD6DA85AF6FCAD0B3725FDD28667175A421D69A2122853E57927C38CCF368732476 -6A946FAEDE134155B5A88EC01AA535E7A778947D360218B560381A64CAF9ACE896079D04C14718D5AD5C0D4EE3005F52 -88AC0C723AAA875A1885C8392A616FA43B205119B0E8D299193979A1921FC8B3 -40588AADA5E1A8BE214B2CCF32D268B48C6B783AE0DD10D88BDF3FF88E921E09 -A7BE05D05DEC9B9A3AE1575D411BF7B12366AD78B726F3E3E843E7BF199961A4 -79F973A155A4367F0EAA078AA0857A2A2A82FC4C8A5AE9E567E7CBF62C2A5CE2 -C38296EEABDA1F95D0C401CC6DDC8656476DC19248588EEF1CB93773D94CDB02A40C902970C4FCB14FABEFFB4F8BC208 -B0B7699B3C3573EE4D88D8CE65FAF3532B5A741D1F20892C0F38BAA2BCE98F2D -6E401D6BDB1B33A404DEB668F3FB353166475487BAADE4A348E3CFDEB3B1B54B -0E44B87878617559783CC6A7C65BE9F99950FE8956ED4BB04894BC53085E3A09CA19915B1E8C143A68D1B7A97F5D1ECB -AC19E55638429C65E6E567C0E96DA9648F8FB80215CF693D7FD5DD86FE7989AC7AC7BAE86BBD4FFF7161AFFB405FFA98 -BCE70C69D90AD639A737813FC8FD26F40F803137BD36E47651C266A671428D6F -F053CF5255AD2E1875A5C38635F7BF203B1DAE1433B162C30AE8695AC8A5589D -B7EFC77FFA98B173E429B3566A27842C4DC5E91B0BC01F07A6A98332C4E1F42A -D7C7950FFB2C5E7D9BCDBC230BF5F1BFFC0FE6F1CF5C8C6013DD90E41AE403FE -50667B2E5909FD5F9D6385788A81DE5F72E56512EAD6BF5EACCA959CB6AF0DEF -6435E07E5E952124B0F80F76E0F68265B8289087387E35C6D51831B299335480 -D7DE1F7748FB8BF90561151CC6AEADC160CA883FE5228768A3737A89F358AF58 -FA206F860C6F981FD4A358FDEA5E1860353406D8416FF2A811D17EBA09C803EA -F2F7B2C6705D1457315F2AAA859AB53592241D63B84C045BC742D220BA110144 -3F0E05E572D1DF5E2B0BBB20EF8F3EB4D198CDF2794F86089E1DB0EF975E9337 -7D54D088C22AA3BA9A97FAB64371B8D512CDEC2A4355116BE2B74BCEC7FEC852 -0FD951F13E19F0FC1A25655DA430640034BE34659C526238E62B6042691998CB -FCA04B0BF98FA89AAEF41A78AE7141EF7783E0D0CBAAB1B6F00C0AD3EAA84A54759D46E1A9BEEDCCE68BA12902802111 -6AD801CE08D58A380B689574BD7FCACC5DF768BDD93AD7EE1AA514A2351EF13A -0A820F47699AFC4A5E3285BF521771FC5B6C5FB7C6C08A1990DA3B3A6766E860 -A7AAC90972DB24D20B57DDD46DC2624FC6169D529426E64B0544AC383799BB2A -AF6088873BC71ED672FA39D50B386523825218C43CDB35D691B0C5895B7EF5C2 -774DFAC8D285241368CB377DA947D7A94951A1520017DF77FE2E6A517D5C6A1FC768BB1E2398F5AF71B10D1806C04CCD -AA788A707E64C40E2A0EB8154FE795EAC68B936FD6BAC5DEF7677A4D5FE344DD -A193EF5D1B223B0FA3C231052EDBDD7A31B0C192BCD8E7E37E11D4D899476ACD -F6986E08949122D46BFA7F218B089E8DB00DCFA6971C5F2468CDDD179E5BBC40 -EDC23A07689EF6229081D1AB9E249E68527BD33EB72C242BA97727E64AF15BCC -70CC64359A2A5DE40D5A30E916DE6532BCC511E7489CD3A2E5DEC269D303FDBD83B7EA14BF13B40E3C960C6D3D12774B +IV: 2A 34 F1 5A AF 6F F5 1A 83 A6 1E DA DE B7 3D F1 +06B63DF24AE073A2F2B19C55CA9E8364FBECD26E49C551990153F6513BDE5267 +6139C78C74C341EB7474085CF1D047BD6FB005F80A72AF3EF3F89D58EF5DF500 +D85F11689020ECA47FBE9C2B67EE41A81E1F06DE2A35AF958965E3ECE29EA701 +1AE9073A42FE0E439544FE6945F6B33CF15A7A4A279020B5E0B3BE33FD189A7E +E161F007854BB33E0056FA09A2E2DEE66789B5C87C8D6D3DE2C8C1BD2B48983EB9D1C5697CA6E95996918F7C47B761B0 +59AE4644DCB3D720C38B5115F230DA58E7BE0A697907F6174BB05AB7886ACDB1 +634DF0BCC185C4C1F7E1B1594B4438D051ABAE092433078963063B51D961D08C +1EBEBCB49E498B9BE977D53EC21B9A546155B627737BD0AA832D496035729346 +4DFA93E639197772D57E8ACE04512CEFC045B8CC965C175A25ED525B630CBB63 +C2D5235D1014A319B249EAE8A5EE350F18D5AB8A498EF222704BD4EB1435F388 +F66D1937160E1392197F463A52E87FCE938A92070892113443C348D7553327A5715CF615CE2F2C96284F47759E043419 +841D29E7CBE040188E2283BFBA9F26EF2F65CCB085B56C3515E8C46C3F20BD75BAA963550869435FDAF509CEEE66A2C4 +7D87E24487D307635E7A17B989B8547EE11F3BF3468D055F0B44633B631BA42C +B4916043973501B95A82B329196D6EBA69FBBC3AF8FD914583104E0E18CE82F6 +E4649F9C2A5465D2EA6F3E9724DD06CD6962FE2BAEB14F1453C14D1559232AE1 +96E15D890DF7FD348441F5E429A875754C6BF0520A787F8E9D8C5415674783CC +CB52005EDED47B57F795BC92FB0522EAB18D23EE028B8D10ED57828C250EB285BFEC6E4A4BE8DABCE0D57ECAA20D90C3 +8E5A50C7D5C374445E88752301D20F0B3D6E4988B61D90FD63779B0EDEF9C60D +49D6CB276A0E5FF134A38062503F01351F44CD6455708B50B5F07D03FC477C33 +CB45B56613DF208E79E4E10A6510F07DC1AA49210C7B94E8BBAECD2C35EC6ABC99FB10FD7C96DD6BB6A6685E9FAD93FB +0743F3CC51200F763C242F1956B4D775C092ADF1A5C19ACAE96EB60C2990CF214F8FEA8FC6749286F6BDAB67657C479A +E5608B28A058787D64A145F0362DEFD98CAE0B5A0F22C6DA7C6D278C7B5F95E3 +D4C113D43E7FB6D2EFA9E87471AA76A61B26872607B4AF5B87F9D72113835CE6 +2DC502800BFD21B76126390CA64A08C5432A2254E822F214CDE1EA11430084C5 +CA22C73010B0F1CB8009601BE2AF0B3674D83D5880E4A26C2A3FF0EA0A098CEA +E53B2B102FDB000E9BB747F957156976E5A0C0E3898AA844C13AE8A9CEE7013B +95CF1A46FFC252BE92919531C92BF6A3AA1B16C170DF4461EC54BE07A55C2387 +2EC7E24090F6DFFF6F2F2D8874D2F36AA769995F31F29FBE3B0EA6A16C3EE833 +C1145B1D9AC70761EA902B86455C1BE1BB1153552A1F7327411DECABE538827B +18D596CADD2EE544200A58716C7A4690B658E58CC2B97334740F70894A6C90FA +6A2F8859DFF01E13AC6C5300AD4A2218810FC91A6FB64A560E99FE6C99226AD2 +48D2EB5A08E35AF89A3B7A1CFDEE829FC0C2DDD2E965F4E3D043B0B14CB7825E +91039325D53CDD0236D1CD13047973A013C14B45A32DE0784A73BFABCEAFBCD1 +51B4EAC87C4DC49B007F40D38B8166C388A1AF25E8D2FF6598E8EDE8726E6E14AD88443114D2A0F5E7721E304F3870DA +3A179DDF65B9868CD84C7C04931F40D5D204C97B20DCBF1A70C241E59BFD7F14 +AF538FD16104DCAF03F4DDF05026D6741898DFC247E48A8F72E652DDF2DFD289 +E67F16AEC9D84B6C06F77B806CA6FBC7618BFBECD0D7A04EC3AE1D1DD06BEC5B +FA4D9F8920EBF2F4293C6D4E99083AA4A71A9DDFFDB07EEBDC552DACEC4DA24A +5BF23E630AC81E2CD533803E225BCB3C481B8D650A9858CF2B5219BAE1CDA01A +17B57E8C1032481E69247EA9A0C9EA41F6C0EA9B3F11170CA69C0842423F0455 +96EA848B8527A647DC9DACDB16C5D92B0081EB1CD77B99B47F56C2E249190BD3BE4306333F37487133DD3AD8E57F3092 +B0E9411274D799BE5989D52E74E00DE310CCA2BD47D7A8FA554D66BB04CD787A +D0D28476E3D8832975653D93F545C35278EC1F0B7AD70CA2F36EB476CC207937 +933195E37014619F997B73F5CF4C0110865A822CA8CB0ED1D977D49A1B06A37F +E790CAC2A26452BF941A9E1BABF0A85598EA1CC8F8CFED637C9B40D5E027B518 +49C1F179ABA5BD4F2C45257A33701730E9CC4728677EFF07808ABE31D3CE6FD5C805F43EA5ABB7261B220C82F0794092 diff --git a/lib/subghz/protocols/keeloq.c b/lib/subghz/protocols/keeloq.c index 99b3c5fb342..cfb92fe8b07 100644 --- a/lib/subghz/protocols/keeloq.c +++ b/lib/subghz/protocols/keeloq.c @@ -521,6 +521,15 @@ static uint8_t subghz_protocol_keeloq_check_remote_controller_selector( return 1; } break; + case KEELOQ_LEARNING_MAGIC_SERIAL_TYPE_1: + man = subghz_protocol_keeloq_common_magic_serial_type1_learning( + fix, manufacture_code->key); + decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); + if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { + *manufacture_name = string_get_cstr(manufacture_code->name); + return 1; + } + break; case KEELOQ_LEARNING_UNKNOWN: // Simple Learning decrypt = subghz_protocol_keeloq_common_decrypt(hop, manufacture_code->key); @@ -528,6 +537,7 @@ static uint8_t subghz_protocol_keeloq_check_remote_controller_selector( *manufacture_name = string_get_cstr(manufacture_code->name); return 1; } + // Check for mirrored man uint64_t man_rev = 0; uint64_t man_rev_byte = 0; @@ -535,11 +545,13 @@ static uint8_t subghz_protocol_keeloq_check_remote_controller_selector( man_rev_byte = (uint8_t)(manufacture_code->key >> i); man_rev = man_rev | man_rev_byte << (56 - i); } + decrypt = subghz_protocol_keeloq_common_decrypt(hop, man_rev); if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { *manufacture_name = string_get_cstr(manufacture_code->name); return 1; } + //########################### // Normal Learning // https://phreakerclub.com/forum/showpost.php?p=43557&postcount=37 diff --git a/lib/subghz/protocols/keeloq_common.c b/lib/subghz/protocols/keeloq_common.c index 0f8c763db24..4c0c1d4ef1a 100644 --- a/lib/subghz/protocols/keeloq_common.c +++ b/lib/subghz/protocols/keeloq_common.c @@ -86,3 +86,15 @@ inline uint64_t data &= 0x0FFFFFFF; return (((uint64_t)data << 32) | data) ^ xor; } + +/** Magic_serial_type1 Learning + * @param data - serial number (28bit) + * @param man - magic man (64bit) + * @return manufacture for this serial number (64bit) + */ + +inline uint64_t + subghz_protocol_keeloq_common_magic_serial_type1_learning(uint32_t data, uint64_t man) { + return man | ((uint64_t)data << 40) | + ((uint64_t)(((data & 0xff) + ((data >> 8) & 0xFF)) & 0xFF) << 32); +} diff --git a/lib/subghz/protocols/keeloq_common.h b/lib/subghz/protocols/keeloq_common.h index aa07a7f5858..448388f0a62 100644 --- a/lib/subghz/protocols/keeloq_common.h +++ b/lib/subghz/protocols/keeloq_common.h @@ -21,6 +21,7 @@ #define KEELOQ_LEARNING_NORMAL 2u #define KEELOQ_LEARNING_SECURE 3u #define KEELOQ_LEARNING_MAGIC_XOR_TYPE_1 4u +#define KEELOQ_LEARNING_MAGIC_SERIAL_TYPE_1 5u /** * Simple Learning Encrypt @@ -63,3 +64,11 @@ uint64_t * @return manufacture for this serial number (64bit) */ uint64_t subghz_protocol_keeloq_common_magic_xor_type1_learning(uint32_t data, uint64_t xor); + +/** Magic_serial_type1 Learning + * @param data - serial number (28bit) + * @param man - magic man (64bit) + * @return manufacture for this serial number (64bit) + */ + +uint64_t subghz_protocol_keeloq_common_magic_serial_type1_learning(uint32_t data, uint64_t man); diff --git a/lib/subghz/protocols/magellen.c b/lib/subghz/protocols/magellen.c index 52ef5a72470..6dcc83e5648 100644 --- a/lib/subghz/protocols/magellen.c +++ b/lib/subghz/protocols/magellen.c @@ -381,7 +381,7 @@ static void subghz_protocol_magellen_get_event_serialize(uint8_t event, string_t "%s%s%s%s%s%s%s%s", ((event >> 4) & 0x1 ? (event & 0x1 ? " Open" : " Close") : (event & 0x1 ? " Motion" : " Ok")), - ((event >> 1) & 0x1 ? ", Tamper On (Alarm)" : ""), + ((event >> 1) & 0x1 ? ", Tamper On\n(Alarm)" : ""), ((event >> 2) & 0x1 ? ", ?" : ""), ((event >> 3) & 0x1 ? ", Power On" : ""), ((event >> 4) & 0x1 ? ", MT:Wireless_Reed" : ""), From f86eada292462754001329c8a4158a7b0e785fe6 Mon Sep 17 00:00:00 2001 From: Kowalski Dragon Date: Sun, 25 Sep 2022 16:39:06 +0200 Subject: [PATCH 080/824] Remove unused headers (#1751) --- applications/services/power/power_service/power.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/applications/services/power/power_service/power.c b/applications/services/power/power_service/power.c index 85d217f290f..757d7718ab9 100644 --- a/applications/services/power/power_service/power.c +++ b/applications/services/power/power_service/power.c @@ -1,10 +1,7 @@ #include "power_i.h" -#include "views/power_off.h" #include #include -#include -#include #define POWER_OFF_TIMEOUT 90 From 2a2078d9b5d07b4e689a6827705891427bd4bc57 Mon Sep 17 00:00:00 2001 From: Chris van Marle Date: Sun, 25 Sep 2022 18:17:09 +0200 Subject: [PATCH 081/824] Text input overwrite max size template (#1687) --- applications/services/gui/modules/text_input.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/applications/services/gui/modules/text_input.c b/applications/services/gui/modules/text_input.c index b8098a3b9f2..58d7ecab0d0 100644 --- a/applications/services/gui/modules/text_input.c +++ b/applications/services/gui/modules/text_input.c @@ -318,15 +318,17 @@ static void text_input_handle_ok(TextInput* text_input, TextInputModel* model, b } } else if(selected == BACKSPACE_KEY) { text_input_backspace_cb(model); - } else if(text_length < (model->text_buffer_size - 1)) { + } else { if(model->clear_default_text) { text_length = 0; } - if(text_length == 0 && char_is_lowercase(selected)) { - selected = char_to_uppercase(selected); + if(text_length < (model->text_buffer_size - 1)) { + if(text_length == 0 && char_is_lowercase(selected)) { + selected = char_to_uppercase(selected); + } + model->text_buffer[text_length] = selected; + model->text_buffer[text_length + 1] = 0; } - model->text_buffer[text_length] = selected; - model->text_buffer[text_length + 1] = 0; } model->clear_default_text = false; } From a6b98ccbbe8b1aa7e3da4eefacca5987ad05b03e Mon Sep 17 00:00:00 2001 From: David Coles Date: Sun, 25 Sep 2022 14:06:46 -0700 Subject: [PATCH 082/824] Preliminary Rust support (#1781) * Add support for R_ARM_THM_MOVW_ABS_NC/THM_MOVT_ABS These are sometimes emitted by the Rust LLVM compiler. Ref: https://github.com/ARM-software/abi-aa/blob/main/aaelf32/aaelf32.rst#56relocation * Discard LLVM bitcode from extension applications LLVM-based compilers may include uncompressed bitcode in object files to help with link-time optimization. However this can bloat binary sizes from KB to MB. * Expose alligned_malloc/free functions to applications This is required to implement a global allocator in Rust. --- firmware/targets/f7/api_symbols.csv | 4 +-- firmware/targets/f7/application-ext.ld | 2 ++ lib/flipper_application/elf/elf.h | 2 ++ lib/flipper_application/elf/elf_file.c | 41 ++++++++++++++++++++++++++ 4 files changed, 47 insertions(+), 2 deletions(-) diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 39365c397fe..11b719ddb43 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -433,8 +433,8 @@ Function,-,acoshl,long double,long double Function,-,acosl,long double,long double Function,+,acquire_mutex,void*,"ValueMutex*, uint32_t" Function,-,aligned_alloc,void*,"size_t, size_t" -Function,-,aligned_free,void,void* -Function,-,aligned_malloc,void*,"size_t, size_t" +Function,+,aligned_free,void,void* +Function,+,aligned_malloc,void*,"size_t, size_t" Function,-,arc4random,__uint32_t, Function,-,arc4random_buf,void,"void*, size_t" Function,-,arc4random_uniform,__uint32_t,__uint32_t diff --git a/firmware/targets/f7/application-ext.ld b/firmware/targets/f7/application-ext.ld index 8f79675be2e..01bb021b655 100644 --- a/firmware/targets/f7/application-ext.ld +++ b/firmware/targets/f7/application-ext.ld @@ -48,5 +48,7 @@ SECTIONS { *(.comment) *(.comment.*) + *(.llvmbc) + *(.llvmcmd) } } diff --git a/lib/flipper_application/elf/elf.h b/lib/flipper_application/elf/elf.h index f1697ba484c..a36622b52dc 100644 --- a/lib/flipper_application/elf/elf.h +++ b/lib/flipper_application/elf/elf.h @@ -1116,6 +1116,8 @@ typedef struct { #define R_ARM_LDR_SBREL_11_0 35 #define R_ARM_ALU_SBREL_19_12 36 #define R_ARM_ALU_SBREL_27_20 37 +#define R_ARM_THM_MOVW_ABS_NC 47 /* Direct 16 bit (Thumb32 MOVW) */ +#define R_ARM_THM_MOVT_ABS 48 /* Direct high 16 bit */ #define R_ARM_GNU_VTENTRY 100 #define R_ARM_GNU_VTINHERIT 101 #define R_ARM_THM_PC11 102 /* thumb unconditional branch */ diff --git a/lib/flipper_application/elf/elf_file.c b/lib/flipper_application/elf/elf_file.c index 202d0a8756e..e2616549558 100644 --- a/lib/flipper_application/elf/elf_file.c +++ b/lib/flipper_application/elf/elf_file.c @@ -255,6 +255,42 @@ static void elf_relocate_jmp_call(ELFFile* elf, Elf32_Addr relAddr, int type, El (uint16_t)((lo & 0xc000) | (j1 << 13) | blx_bit | (j2 << 11) | imm11); } +static void elf_relocate_mov(Elf32_Addr relAddr, int type, Elf32_Addr symAddr) { + uint16_t upper_insn = ((uint16_t*)relAddr)[0]; + uint16_t lower_insn = ((uint16_t*)relAddr)[1]; + + /* MOV* ,# + * + * i = upper[10] + * imm4 = upper[3:0] + * imm3 = lower[14:12] + * imm8 = lower[7:0] + * + * imm16 = imm4:i:imm3:imm8 + */ + uint32_t i = (upper_insn >> 10) & 1; /* upper[10] */ + uint32_t imm4 = upper_insn & 0x000F; /* upper[3:0] */ + uint32_t imm3 = (lower_insn >> 12) & 0x7; /* lower[14:12] */ + uint32_t imm8 = lower_insn & 0x00FF; /* lower[7:0] */ + + int32_t addend = (imm4 << 12) | (i << 11) | (imm3 << 8) | imm8; /* imm16 */ + + uint32_t addr = (symAddr + addend); + if (type == R_ARM_THM_MOVT_ABS) { + addr >>= 16; /* upper 16 bits */ + } else { + addr &= 0x0000FFFF; /* lower 16 bits */ + } + + /* Re-encode */ + ((uint16_t*)relAddr)[0] = (upper_insn & 0xFBF0) + | (((addr >> 11) & 1) << 10) /* i */ + | ((addr >> 12) & 0x000F); /* imm4 */ + ((uint16_t*)relAddr)[1] = (lower_insn & 0x8F00) + | (((addr >> 8) & 0x7) << 12) /* imm3 */ + | (addr & 0x00FF); /* imm8 */ +} + static bool elf_relocate_symbol(ELFFile* elf, Elf32_Addr relAddr, int type, Elf32_Addr symAddr) { switch(type) { case R_ARM_TARGET1: @@ -268,6 +304,11 @@ static bool elf_relocate_symbol(ELFFile* elf, Elf32_Addr relAddr, int type, Elf3 FURI_LOG_D( TAG, " R_ARM_THM_CALL/JMP relocated is 0x%08X", (unsigned int)*((uint32_t*)relAddr)); break; + case R_ARM_THM_MOVW_ABS_NC: + case R_ARM_THM_MOVT_ABS: + elf_relocate_mov(relAddr, type, symAddr); + FURI_LOG_D(TAG, " R_ARM_THM_MOVW_ABS_NC/MOVT_ABS relocated is 0x%08X", (unsigned int)*((uint32_t*)relAddr)); + break; default: FURI_LOG_E(TAG, " Undefined relocation %d", type); return false; From efb09380bd4fcc5e2702ddf66d67db89f9772443 Mon Sep 17 00:00:00 2001 From: hedger Date: Mon, 26 Sep 2022 15:03:21 +0400 Subject: [PATCH 083/824] [FL-2836] Fast flash programming mode (#1782) * updater: lowered logging level for resources unpacking; hal: implemented fast flash write mode * hal: reworked fast flash programming; clearing most error flags on flash init; changed some flash functions return type from bool to void; scripts: fixed malformed CRC values in update bundles in certain cases; * hal: flash: larger critical section * hal: flash: enabling fast write inside critical section * api_symbols: bump minor version --- .../services/storage/storages/storage_int.c | 14 +- .../updater/util/update_task_worker_flasher.c | 12 +- firmware/targets/f7/api_symbols.csv | 8 +- firmware/targets/f7/furi_hal/furi_hal_flash.c | 141 +++++++++++------- firmware/targets/f7/furi_hal/furi_hal_flash.h | 12 +- lib/toolbox/tar/tar_archive.c | 2 +- scripts/update.py | 2 +- 7 files changed, 109 insertions(+), 82 deletions(-) diff --git a/applications/services/storage/storages/storage_int.c b/applications/services/storage/storages/storage_int.c index cae61f16e47..7583973543f 100644 --- a/applications/services/storage/storages/storage_int.c +++ b/applications/services/storage/storages/storage_int.c @@ -109,10 +109,7 @@ static int storage_int_device_prog( int ret = 0; while(size > 0) { - if(!furi_hal_flash_write_dword(address, *(uint64_t*)buffer)) { - ret = -1; - break; - } + furi_hal_flash_write_dword(address, *(uint64_t*)buffer); address += c->prog_size; buffer += c->prog_size; size -= c->prog_size; @@ -127,16 +124,13 @@ static int storage_int_device_erase(const struct lfs_config* c, lfs_block_t bloc FURI_LOG_D(TAG, "Device erase: page %d, translated page: %x", block, page); - if(furi_hal_flash_erase(page)) { - return 0; - } else { - return -1; - } + furi_hal_flash_erase(page); + return 0; } static int storage_int_device_sync(const struct lfs_config* c) { UNUSED(c); - FURI_LOG_D(TAG, "Device sync: skipping, cause "); + FURI_LOG_D(TAG, "Device sync: skipping"); return 0; } diff --git a/applications/system/updater/util/update_task_worker_flasher.c b/applications/system/updater/util/update_task_worker_flasher.c index d56b4ae0a9f..7b598c50b10 100644 --- a/applications/system/updater/util/update_task_worker_flasher.c +++ b/applications/system/updater/util/update_task_worker_flasher.c @@ -52,11 +52,19 @@ static bool check_address_boundaries(const size_t address) { return ((address >= min_allowed_address) && (address < max_allowed_address)); } +static bool update_task_flash_program_page( + const uint8_t i_page, + const uint8_t* update_block, + uint16_t update_block_len) { + furi_hal_flash_program_page(i_page, update_block, update_block_len); + return true; +} + static bool update_task_write_dfu(UpdateTask* update_task) { DfuUpdateTask page_task = { .address_cb = &check_address_boundaries, .progress_cb = &update_task_file_progress, - .task_cb = &furi_hal_flash_program_page, + .task_cb = &update_task_flash_program_page, .context = update_task, }; @@ -117,7 +125,7 @@ static bool update_task_write_stack_data(UpdateTask* update_task) { furi_hal_flash_get_page_number(update_task->manifest->radio_address + element_offs); CHECK_RESULT(i_page >= 0); - CHECK_RESULT(furi_hal_flash_program_page(i_page, fw_block, bytes_read)); + furi_hal_flash_program_page(i_page, fw_block, bytes_read); element_offs += bytes_read; update_task_set_progress( diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 11b719ddb43..c8e6a6cf491 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,1.10,, +Version,+,1.11,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -986,7 +986,7 @@ Function,+,furi_hal_crypto_verify_key,_Bool,uint8_t Function,+,furi_hal_debug_disable,void, Function,+,furi_hal_debug_enable,void, Function,-,furi_hal_deinit_early,void, -Function,-,furi_hal_flash_erase,_Bool,uint8_t +Function,-,furi_hal_flash_erase,void,uint8_t Function,-,furi_hal_flash_get_base,size_t, Function,-,furi_hal_flash_get_cycles_count,size_t, Function,-,furi_hal_flash_get_free_end_address,const void*, @@ -1001,8 +1001,8 @@ Function,-,furi_hal_flash_init,void, Function,-,furi_hal_flash_ob_apply,void, Function,-,furi_hal_flash_ob_get_raw_ptr,const FuriHalFlashRawOptionByteData*, Function,-,furi_hal_flash_ob_set_word,_Bool,"size_t, const uint32_t" -Function,-,furi_hal_flash_program_page,_Bool,"const uint8_t, const uint8_t*, uint16_t" -Function,-,furi_hal_flash_write_dword,_Bool,"size_t, uint64_t" +Function,-,furi_hal_flash_program_page,void,"const uint8_t, const uint8_t*, uint16_t" +Function,-,furi_hal_flash_write_dword,void,"size_t, uint64_t" Function,+,furi_hal_gpio_add_int_callback,void,"const GpioPin*, GpioExtiCallback, void*" Function,+,furi_hal_gpio_disable_int_callback,void,const GpioPin* Function,+,furi_hal_gpio_enable_int_callback,void,const GpioPin* diff --git a/firmware/targets/f7/furi_hal/furi_hal_flash.c b/firmware/targets/f7/furi_hal/furi_hal_flash.c index 9e05dc1238a..f99cf8c3da8 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_flash.c +++ b/firmware/targets/f7/furi_hal/furi_hal_flash.c @@ -21,7 +21,6 @@ (FLASH_SR_OPERR | FLASH_SR_PROGERR | FLASH_SR_WRPERR | FLASH_SR_PGAERR | FLASH_SR_SIZERR | \ FLASH_SR_PGSERR | FLASH_SR_MISERR | FLASH_SR_FASTERR | FLASH_SR_RDERR | FLASH_SR_OPTVERR) -//#define FURI_HAL_FLASH_OB_START_ADDRESS 0x1FFF8000 #define FURI_HAL_FLASH_OPT_KEY1 0x08192A3B #define FURI_HAL_FLASH_OPT_KEY2 0x4C5D6E7F #define FURI_HAL_FLASH_OB_TOTAL_WORDS (0x80 / (sizeof(uint32_t) * 2)) @@ -80,9 +79,13 @@ size_t furi_hal_flash_get_free_page_count() { } void furi_hal_flash_init() { - // Errata 2.2.9, Flash OPTVERR flag is always set after system reset - WRITE_REG(FLASH->SR, FLASH_SR_OPTVERR); - //__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_OPTVERR); + /* Errata 2.2.9, Flash OPTVERR flag is always set after system reset */ + // WRITE_REG(FLASH->SR, FLASH_SR_OPTVERR); + /* Actually, reset all error flags on start */ + if(READ_BIT(FLASH->SR, FURI_HAL_FLASH_SR_ERRORS)) { + FURI_LOG_E(TAG, "FLASH->SR 0x%08X", FLASH->SR); + WRITE_REG(FLASH->SR, FURI_HAL_FLASH_SR_ERRORS); + } } static void furi_hal_flash_unlock() { @@ -91,6 +94,7 @@ static void furi_hal_flash_unlock() { /* Authorize the FLASH Registers access */ WRITE_REG(FLASH->KEYR, FURI_HAL_FLASH_KEY1); + __ISB(); WRITE_REG(FLASH->KEYR, FURI_HAL_FLASH_KEY2); /* verify Flash is unlocked */ @@ -110,38 +114,38 @@ static void furi_hal_flash_lock(void) { } static void furi_hal_flash_begin_with_core2(bool erase_flag) { - // Take flash controller ownership + /* Take flash controller ownership */ while(LL_HSEM_1StepLock(HSEM, CFG_HW_FLASH_SEMID) != 0) { furi_thread_yield(); } - // Unlock flash operation + /* Unlock flash operation */ furi_hal_flash_unlock(); - // Erase activity notification + /* Erase activity notification */ if(erase_flag) SHCI_C2_FLASH_EraseActivity(ERASE_ACTIVITY_ON); - // 64mHz 5us core2 flag protection + /* 64mHz 5us core2 flag protection */ for(volatile uint32_t i = 0; i < 35; i++) ; while(true) { - // Wait till flash controller become usable + /* Wait till flash controller become usable */ while(LL_FLASH_IsActiveFlag_OperationSuspended()) { furi_thread_yield(); }; - // Just a little more love + /* Just a little more love */ taskENTER_CRITICAL(); - // Actually we already have mutex for it, but specification is specification + /* Actually we already have mutex for it, but specification is specification */ if(LL_HSEM_IsSemaphoreLocked(HSEM, CFG_HW_BLOCK_FLASH_REQ_BY_CPU1_SEMID)) { taskEXIT_CRITICAL(); furi_thread_yield(); continue; } - // Take sempahopre and prevent core2 from anything funky + /* Take sempahopre and prevent core2 from anything funky */ if(LL_HSEM_1StepLock(HSEM, CFG_HW_BLOCK_FLASH_REQ_BY_CPU2_SEMID) != 0) { taskEXIT_CRITICAL(); furi_thread_yield(); @@ -153,10 +157,10 @@ static void furi_hal_flash_begin_with_core2(bool erase_flag) { } static void furi_hal_flash_begin(bool erase_flag) { - // Acquire dangerous ops mutex + /* Acquire dangerous ops mutex */ furi_hal_bt_lock_core2(); - // If Core2 is running use IPC locking + /* If Core2 is running use IPC locking */ if(furi_hal_bt_is_alive()) { furi_hal_flash_begin_with_core2(erase_flag); } else { @@ -165,36 +169,36 @@ static void furi_hal_flash_begin(bool erase_flag) { } static void furi_hal_flash_end_with_core2(bool erase_flag) { - // Funky ops are ok at this point + /* Funky ops are ok at this point */ LL_HSEM_ReleaseLock(HSEM, CFG_HW_BLOCK_FLASH_REQ_BY_CPU2_SEMID, 0); - // Task switching is ok + /* Task switching is ok */ taskEXIT_CRITICAL(); - // Doesn't make much sense, does it? + /* Doesn't make much sense, does it? */ while(READ_BIT(FLASH->SR, FLASH_SR_BSY)) { furi_thread_yield(); } - // Erase activity over, core2 can continue + /* Erase activity over, core2 can continue */ if(erase_flag) SHCI_C2_FLASH_EraseActivity(ERASE_ACTIVITY_OFF); - // Lock flash controller + /* Lock flash controller */ furi_hal_flash_lock(); - // Release flash controller ownership + /* Release flash controller ownership */ LL_HSEM_ReleaseLock(HSEM, CFG_HW_FLASH_SEMID, 0); } static void furi_hal_flash_end(bool erase_flag) { - // If Core2 is running use IPC locking + /* If Core2 is running - use IPC locking */ if(furi_hal_bt_is_alive()) { furi_hal_flash_end_with_core2(erase_flag); } else { furi_hal_flash_lock(); } - // Release dangerous ops mutex + /* Release dangerous ops mutex */ furi_hal_bt_unlock_core2(); } @@ -226,9 +230,9 @@ bool furi_hal_flash_wait_last_operation(uint32_t timeout) { uint32_t error = 0; uint32_t countdown = 0; - // Wait for the FLASH operation to complete by polling on BUSY flag to be reset. - // Even if the FLASH operation fails, the BUSY flag will be reset and an error - // flag will be set + /* Wait for the FLASH operation to complete by polling on BUSY flag to be reset. + Even if the FLASH operation fails, the BUSY flag will be reset and an error + flag will be set */ countdown = timeout; while(READ_BIT(FLASH->SR, FLASH_SR_BSY)) { if(LL_SYSTICK_IsActiveCounterFlag()) { @@ -269,10 +273,10 @@ bool furi_hal_flash_wait_last_operation(uint32_t timeout) { return true; } -bool furi_hal_flash_erase(uint8_t page) { +void furi_hal_flash_erase(uint8_t page) { furi_hal_flash_begin(true); - // Ensure that controller state is valid + /* Ensure that controller state is valid */ furi_check(FLASH->SR == 0); /* Verify that next operation can be proceed */ @@ -292,30 +296,31 @@ bool furi_hal_flash_erase(uint8_t page) { furi_hal_flush_cache(); furi_hal_flash_end(true); - - return true; } -static inline bool furi_hal_flash_write_dword_internal(size_t address, uint64_t* data) { +static inline void furi_hal_flash_write_dword_internal_nowait(size_t address, uint64_t* data) { /* Program first word */ *(uint32_t*)address = (uint32_t)*data; - // Barrier to ensure programming is performed in 2 steps, in right order - // (independently of compiler optimization behavior) + /* Barrier to ensure programming is performed in 2 steps, in right order + (independently of compiler optimization behavior) */ __ISB(); /* Program second word */ *(uint32_t*)(address + 4U) = (uint32_t)(*data >> 32U); +} + +static inline void furi_hal_flash_write_dword_internal(size_t address, uint64_t* data) { + furi_hal_flash_write_dword_internal_nowait(address, data); /* Wait for last operation to be completed */ furi_check(furi_hal_flash_wait_last_operation(FURI_HAL_FLASH_TIMEOUT)); - return true; } -bool furi_hal_flash_write_dword(size_t address, uint64_t data) { +void furi_hal_flash_write_dword(size_t address, uint64_t data) { furi_hal_flash_begin(false); - // Ensure that controller state is valid + /* Ensure that controller state is valid */ furi_check(FLASH->SR == 0); /* Check the parameters */ @@ -326,7 +331,7 @@ bool furi_hal_flash_write_dword(size_t address, uint64_t data) { SET_BIT(FLASH->CR, FLASH_CR_PG); /* Do the thing */ - furi_check(furi_hal_flash_write_dword_internal(address, &data)); + furi_hal_flash_write_dword_internal(address, &data); /* If the program operation is completed, disable the PG or FSTPG Bit */ CLEAR_BIT(FLASH->CR, FLASH_CR_PG); @@ -335,14 +340,13 @@ bool furi_hal_flash_write_dword(size_t address, uint64_t data) { /* Wait for last operation to be completed */ furi_check(furi_hal_flash_wait_last_operation(FURI_HAL_FLASH_TIMEOUT)); - return true; } static size_t furi_hal_flash_get_page_address(uint8_t page) { return furi_hal_flash_get_base() + page * FURI_HAL_FLASH_PAGE_SIZE; } -bool furi_hal_flash_program_page(const uint8_t page, const uint8_t* data, uint16_t _length) { +void furi_hal_flash_program_page(const uint8_t page, const uint8_t* data, uint16_t _length) { uint16_t length = _length; furi_check(length <= FURI_HAL_FLASH_PAGE_SIZE); @@ -350,37 +354,63 @@ bool furi_hal_flash_program_page(const uint8_t page, const uint8_t* data, uint16 furi_hal_flash_begin(false); - // Ensure that controller state is valid + furi_check(furi_hal_flash_wait_last_operation(FURI_HAL_FLASH_TIMEOUT)); + + /* Ensure that controller state is valid */ furi_check(FLASH->SR == 0); size_t page_start_address = furi_hal_flash_get_page_address(page); - /* Set PG bit */ + size_t length_written = 0; + + const uint16_t FAST_PROG_BLOCK_SIZE = 512; + const uint8_t DWORD_PROG_BLOCK_SIZE = 8; + + /* Write as much data as we can in fast mode */ + if(length >= FAST_PROG_BLOCK_SIZE) { + taskENTER_CRITICAL(); + /* Enable fast flash programming mode */ + SET_BIT(FLASH->CR, FLASH_CR_FSTPG); + + while(length_written < (length / FAST_PROG_BLOCK_SIZE * FAST_PROG_BLOCK_SIZE)) { + /* No context switch in the middle of the operation */ + furi_hal_flash_write_dword_internal_nowait( + page_start_address + length_written, (uint64_t*)(data + length_written)); + length_written += DWORD_PROG_BLOCK_SIZE; + + if((length_written % FAST_PROG_BLOCK_SIZE) == 0) { + /* Wait for block operation to be completed */ + furi_check(furi_hal_flash_wait_last_operation(FURI_HAL_FLASH_TIMEOUT)); + } + } + CLEAR_BIT(FLASH->CR, FLASH_CR_FSTPG); + taskEXIT_CRITICAL(); + } + + /* Enable regular (dword) programming mode */ SET_BIT(FLASH->CR, FLASH_CR_PG); - size_t i_dwords = 0; - for(i_dwords = 0; i_dwords < (length / 8); ++i_dwords) { - /* Do the thing */ - size_t data_offset = i_dwords * 8; - furi_check(furi_hal_flash_write_dword_internal( - page_start_address + data_offset, (uint64_t*)&data[data_offset])); + if((length % FAST_PROG_BLOCK_SIZE) != 0) { + /* Write tail in regular, dword mode */ + while(length_written < (length / DWORD_PROG_BLOCK_SIZE * DWORD_PROG_BLOCK_SIZE)) { + furi_hal_flash_write_dword_internal( + page_start_address + length_written, (uint64_t*)&data[length_written]); + length_written += DWORD_PROG_BLOCK_SIZE; + } } - if((length % 8) != 0) { + + if((length % DWORD_PROG_BLOCK_SIZE) != 0) { /* there are more bytes, not fitting into dwords */ uint64_t tail_data = 0; - size_t data_offset = i_dwords * 8; - for(int32_t tail_i = 0; tail_i < (length % 8); ++tail_i) { - tail_data |= (((uint64_t)data[data_offset + tail_i]) << (tail_i * 8)); + for(int32_t tail_i = 0; tail_i < (length % DWORD_PROG_BLOCK_SIZE); ++tail_i) { + tail_data |= (((uint64_t)data[length_written + tail_i]) << (tail_i * 8)); } - furi_check( - furi_hal_flash_write_dword_internal(page_start_address + data_offset, &tail_data)); + furi_hal_flash_write_dword_internal(page_start_address + length_written, &tail_data); } - - /* If the program operation is completed, disable the PG or FSTPG Bit */ + /* Disable the PG Bit */ CLEAR_BIT(FLASH->CR, FLASH_CR_PG); furi_hal_flash_end(false); - return true; } int16_t furi_hal_flash_get_page_number(size_t address) { @@ -462,6 +492,7 @@ static const FuriHalFlashObMapping furi_hal_flash_ob_reg_map[FURI_HAL_FLASH_OB_T OB_REG_DEF(FuriHalFlashObRegisterSecureFlash, (NULL)), OB_REG_DEF(FuriHalFlashObRegisterC2Opts, (NULL)), }; +#undef OB_REG_DEF void furi_hal_flash_ob_apply() { furi_hal_flash_ob_unlock(); diff --git a/firmware/targets/f7/furi_hal/furi_hal_flash.h b/firmware/targets/f7/furi_hal/furi_hal_flash.h index 1ed4c039960..9fa8f94af9d 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_flash.h +++ b/firmware/targets/f7/furi_hal/furi_hal_flash.h @@ -90,10 +90,8 @@ size_t furi_hal_flash_get_free_page_count(); * @warning locking operation with critical section, stalls execution * * @param page The page to erase - * - * @return true on success */ -bool furi_hal_flash_erase(uint8_t page); +void furi_hal_flash_erase(uint8_t page); /** Write double word (64 bits) * @@ -101,10 +99,8 @@ bool furi_hal_flash_erase(uint8_t page); * * @param address destination address, must be double word aligned. * @param data data to write - * - * @return true on success */ -bool furi_hal_flash_write_dword(size_t address, uint64_t data); +void furi_hal_flash_write_dword(size_t address, uint64_t data); /** Write aligned page data (up to page size) * @@ -113,10 +109,8 @@ bool furi_hal_flash_write_dword(size_t address, uint64_t data); * @param address destination address, must be page aligned. * @param data data to write * @param length data length - * - * @return true on success */ -bool furi_hal_flash_program_page(const uint8_t page, const uint8_t* data, uint16_t length); +void furi_hal_flash_program_page(const uint8_t page, const uint8_t* data, uint16_t length); /** Get flash page number for address * diff --git a/lib/toolbox/tar/tar_archive.c b/lib/toolbox/tar/tar_archive.c index 0d42d162c2a..f51d6231786 100644 --- a/lib/toolbox/tar/tar_archive.c +++ b/lib/toolbox/tar/tar_archive.c @@ -209,7 +209,7 @@ static int archive_extract_foreach_cb(mtar_t* tar, const mtar_header_t* header, path_concat(op_params->work_dir, string_get_cstr(converted_fname), full_extracted_fname); string_clear(converted_fname); - FURI_LOG_I(TAG, "Extracting %d bytes to '%s'", header->size, header->name); + FURI_LOG_D(TAG, "Extracting %d bytes to '%s'", header->size, header->name); File* out_file = storage_file_alloc(archive->storage); uint8_t* readbuf = malloc(FILE_BLOCK_SIZE); diff --git a/scripts/update.py b/scripts/update.py index ee485f44d2a..52391965be8 100755 --- a/scripts/update.py +++ b/scripts/update.py @@ -229,7 +229,7 @@ def bytes2ffhex(value: bytes): @staticmethod def int2ffhex(value: int, n_hex_syms=8): if value: - n_hex_syms = math.ceil(math.ceil(math.log2(value)) / 8) * 2 + n_hex_syms = max(math.ceil(math.ceil(math.log2(value)) / 8) * 2, n_hex_syms) fmtstr = f"%0{n_hex_syms}X" hexstr = fmtstr % value return " ".join(list(Main.batch(hexstr, 2))[::-1]) From 3e3a167764132a426752cb5bed52afeaf3e93f3e Mon Sep 17 00:00:00 2001 From: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> Date: Mon, 26 Sep 2022 16:49:18 +0300 Subject: [PATCH 084/824] [FL-2852] Update Universal Remote documentation (#1786) * Update Universal Remote documentation * Change formatting --- documentation/UniversalRemotes.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/documentation/UniversalRemotes.md b/documentation/UniversalRemotes.md index ac98d451f4f..6ecf3b11b54 100644 --- a/documentation/UniversalRemotes.md +++ b/documentation/UniversalRemotes.md @@ -32,5 +32,7 @@ Finally, record the `Off` signal: The resulting remote file should now contain 6 signals. Any of them can be omitted, but that will mean that this functionality will not be used. Test the file against the actual device. Every signal must do what it's supposed to. -If everything checks out, add these signals to the [A/C universal remote file](/assets/resources/infrared/assets/ac.ir) -and open a pull request. + +If everything checks out, add these signals to the [A/C universal remote file](/assets/resources/infrared/assets/ac.ir). +Keep the signals of the same type grouped together (e.g. an `Off` signal must follow a previous `Off` one). +When done, open a pull request containing the changed file. From 9f501034c3240c3d9506cf07270aa4b0818e5e02 Mon Sep 17 00:00:00 2001 From: Shane Synan Date: Mon, 26 Sep 2022 11:34:59 -0400 Subject: [PATCH 085/824] Power: Also ask charger if charge done (#1378) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * power: Also ask charger if charge done * F7: bump API Symbols version * Lib: remove double include in bq25896.c Co-authored-by: あく --- applications/services/power/power_service/power.c | 2 +- firmware/targets/f7/api_symbols.csv | 3 ++- firmware/targets/f7/furi_hal/furi_hal_power.c | 7 +++++++ firmware/targets/furi_hal_include/furi_hal_power.h | 6 ++++++ lib/drivers/bq25896.c | 14 +++++++++++--- lib/drivers/bq25896.h | 8 ++++++++ 6 files changed, 35 insertions(+), 5 deletions(-) diff --git a/applications/services/power/power_service/power.c b/applications/services/power/power_service/power.c index 757d7718ab9..89886b0f85e 100644 --- a/applications/services/power/power_service/power.c +++ b/applications/services/power/power_service/power.c @@ -96,7 +96,7 @@ void power_free(Power* power) { static void power_check_charging_state(Power* power) { if(furi_hal_power_is_charging()) { - if(power->info.charge == 100) { + if((power->info.charge == 100) || (furi_hal_power_is_charging_done())) { if(power->state != PowerStateCharged) { notification_internal_message(power->notification, &sequence_charged); power->state = PowerStateCharged; diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index c8e6a6cf491..c18780acbe5 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,1.11,, +Version,+,1.12,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -1141,6 +1141,7 @@ Function,+,furi_hal_power_insomnia_enter,void, Function,+,furi_hal_power_insomnia_exit,void, Function,-,furi_hal_power_insomnia_level,uint16_t, Function,+,furi_hal_power_is_charging,_Bool, +Function,+,furi_hal_power_is_charging_done,_Bool, Function,+,furi_hal_power_is_otg_enabled,_Bool, Function,+,furi_hal_power_off,void, Function,+,furi_hal_power_reset,void, diff --git a/firmware/targets/f7/furi_hal/furi_hal_power.c b/firmware/targets/f7/furi_hal/furi_hal_power.c index 246383921fb..524fae0b1cc 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_power.c +++ b/firmware/targets/f7/furi_hal/furi_hal_power.c @@ -266,6 +266,13 @@ bool furi_hal_power_is_charging() { return ret; } +bool furi_hal_power_is_charging_done() { + furi_hal_i2c_acquire(&furi_hal_i2c_handle_power); + bool ret = bq25896_is_charging_done(&furi_hal_i2c_handle_power); + furi_hal_i2c_release(&furi_hal_i2c_handle_power); + return ret; +} + void furi_hal_power_shutdown() { furi_hal_power_insomnia_enter(); diff --git a/firmware/targets/furi_hal_include/furi_hal_power.h b/firmware/targets/furi_hal_include/furi_hal_power.h index 3ab30c42480..f8eaa5c3ab0 100644 --- a/firmware/targets/furi_hal_include/furi_hal_power.h +++ b/firmware/targets/furi_hal_include/furi_hal_power.h @@ -85,6 +85,12 @@ uint8_t furi_hal_power_get_bat_health_pct(); */ bool furi_hal_power_is_charging(); +/** Get charge complete status + * + * @return true if done charging and connected to charger + */ +bool furi_hal_power_is_charging_done(); + /** Switch MCU to SHUTDOWN */ void furi_hal_power_shutdown(); diff --git a/lib/drivers/bq25896.c b/lib/drivers/bq25896.c index 73135d93a27..1fb9d53e71e 100644 --- a/lib/drivers/bq25896.c +++ b/lib/drivers/bq25896.c @@ -1,5 +1,4 @@ #include "bq25896.h" -#include "bq25896_reg.h" #include @@ -81,7 +80,7 @@ void bq25896_poweroff(FuriHalI2cBusHandle* handle) { handle, BQ25896_ADDRESS, 0x09, *(uint8_t*)&bq25896_regs.r09, BQ25896_I2C_TIMEOUT); } -bool bq25896_is_charging(FuriHalI2cBusHandle* handle) { +ChrgStat bq25896_get_charge_status(FuriHalI2cBusHandle* handle) { furi_hal_i2c_read_mem( handle, BQ25896_ADDRESS, @@ -91,7 +90,16 @@ bool bq25896_is_charging(FuriHalI2cBusHandle* handle) { BQ25896_I2C_TIMEOUT); furi_hal_i2c_read_reg_8( handle, BQ25896_ADDRESS, 0x0B, (uint8_t*)&bq25896_regs.r0B, BQ25896_I2C_TIMEOUT); - return bq25896_regs.r0B.CHRG_STAT != ChrgStatNo; + return bq25896_regs.r0B.CHRG_STAT; +} + +bool bq25896_is_charging(FuriHalI2cBusHandle* handle) { + // Include precharge, fast charging, and charging termination done as "charging" + return bq25896_get_charge_status(handle) != ChrgStatNo; +} + +bool bq25896_is_charging_done(FuriHalI2cBusHandle* handle) { + return bq25896_get_charge_status(handle) == ChrgStatDone; } void bq25896_enable_charging(FuriHalI2cBusHandle* handle) { diff --git a/lib/drivers/bq25896.h b/lib/drivers/bq25896.h index 39d343c3385..c8da0a0643d 100644 --- a/lib/drivers/bq25896.h +++ b/lib/drivers/bq25896.h @@ -1,5 +1,7 @@ #pragma once +#include "bq25896_reg.h" + #include #include #include @@ -10,9 +12,15 @@ void bq25896_init(FuriHalI2cBusHandle* handle); /** Send device into shipping mode */ void bq25896_poweroff(FuriHalI2cBusHandle* handle); +/** Get charging status */ +ChrgStat bq25896_get_charge_status(FuriHalI2cBusHandle* handle); + /** Is currently charging */ bool bq25896_is_charging(FuriHalI2cBusHandle* handle); +/** Is charging completed while connected to charger */ +bool bq25896_is_charging_done(FuriHalI2cBusHandle* handle); + /** Enable charging */ void bq25896_enable_charging(FuriHalI2cBusHandle* handle); From f201062819d75035e4e5529e6cb33015e652f59e Mon Sep 17 00:00:00 2001 From: phreakocious Date: Mon, 26 Sep 2022 10:42:29 -0500 Subject: [PATCH 086/824] Add Hisense A/C IR signals.. (#1773) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add Hisense A/C IR signals.. note that using any will toggle the power and apply the settings * re-order the entries to be grouped by function Co-authored-by: あく --- assets/resources/infrared/assets/ac.ir | 36 ++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/assets/resources/infrared/assets/ac.ir b/assets/resources/infrared/assets/ac.ir index b1075b2f36e..49586b555be 100644 --- a/assets/resources/infrared/assets/ac.ir +++ b/assets/resources/infrared/assets/ac.ir @@ -7,32 +7,68 @@ frequency: 38000 duty_cycle: 0.33 data: 502 3436 510 475 509 476 508 477 507 477 507 479 505 480 504 480 504 490 504 481 502 482 501 483 563 420 511 474 510 475 509 476 508 485 561 423 508 476 508 477 507 478 506 479 505 480 504 481 503 517 508 476 508 478 506 479 505 479 505 481 503 483 521 1456 501 498 507 479 505 480 504 481 503 482 501 483 563 421 562 422 509 499 506 479 505 480 504 481 503 482 502 484 510 1451 506 479 505 1542 562 1396 509 471 502 476 508 469 504 3425 511 # +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8974 4505 598 1647 595 1651 591 539 592 542 600 537 594 547 595 549 593 1662 591 532 599 1649 593 1659 594 540 602 538 593 548 594 552 600 535 596 528 593 535 596 536 595 540 602 538 593 548 593 551 601 532 599 525 596 1651 591 539 592 543 599 1660 593 1669 594 1672 601 533 598 526 595 533 598 533 598 536 595 543 599 543 599 547 595 540 602 524 597 530 601 530 601 534 597 541 601 541 601 545 597 522 599 7938 591 532 599 528 593 537 594 541 601 537 594 546 596 549 593 1663 600 524 597 530 601 530 601 533 598 540 591 550 592 553 599 536 595 528 593 536 595 536 595 540 591 547 595 548 593 552 600 535 596 529 592 536 595 535 596 538 593 545 597 546 596 550 592 542 600 524 597 531 600 531 600 534 597 542 600 542 600 545 597 538 593 530 601 526 595 537 594 540 591 547 595 546 595 550 592 543 599 526 595 532 599 531 601 535 596 542 600 542 600 546 596 538 593 531 600 1648 594 536 595 539 592 1669 594 1669 594 1671 602 1637 595 7947 592 532 599 529 592 539 592 543 599 540 591 551 601 544 598 537 594 531 600 1647 595 535 596 539 592 545 597 546 596 550 592 543 599 526 595 533 598 534 597 538 593 545 597 546 596 550 602 534 597 527 594 533 598 533 598 536 595 544 598 543 599 547 595 540 591 533 598 529 592 539 592 1663 600 538 593 547 595 551 591 543 599 524 597 530 591 539 592 542 600 537 594 547 595 550 592 542 600 525 596 1651 592 538 593 1662 591 546 596 545 597 548 594 523 629 +# name: Dh type: raw frequency: 38000 duty_cycle: 0.33 data: 507 3430 506 479 505 480 504 481 503 481 503 483 501 485 509 1453 504 1465 503 482 502 483 511 473 500 485 509 476 508 477 507 478 506 487 507 477 507 478 506 479 505 480 504 482 502 483 501 484 500 523 503 482 502 484 500 485 509 476 508 476 508 478 506 1456 501 501 504 482 502 483 501 484 500 485 509 476 508 477 507 1455 502 509 506 479 505 1457 500 485 509 476 508 1454 503 482 502 483 501 568 499 1459 509 1450 507 471 502 474 510 3421 505 # +name: Dh +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8990 4494 599 1648 595 1654 599 533 598 537 594 544 598 544 598 548 594 1662 601 523 598 1651 592 1660 593 542 600 539 593 550 592 553 599 536 596 527 594 531 601 1648 595 538 593 542 600 540 592 551 601 532 600 1643 600 1650 593 538 593 542 600 1660 593 1671 592 1673 601 534 598 527 594 534 597 533 599 536 595 543 599 542 600 545 597 537 595 530 591 536 596 535 596 538 593 544 598 543 599 546 596 522 599 7935 595 530 591 536 596 536 596 539 592 545 597 544 598 547 595 1660 593 530 591 536 596 535 596 536 595 542 600 541 591 552 600 534 597 525 596 531 601 529 592 541 601 537 595 546 596 548 594 540 591 532 600 527 594 536 595 538 594 544 598 543 599 546 596 538 594 531 600 527 594 536 595 539 593 545 597 543 599 546 596 538 593 530 591 535 596 532 599 532 600 536 595 543 599 544 598 535 596 525 596 530 591 538 593 538 593 542 600 540 591 551 601 532 600 1640 593 1651 592 1655 598 535 596 1657 596 1663 601 1661 592 1641 592 7941 599 526 595 533 599 532 600 535 597 541 601 541 601 544 598 537 595 1651 592 535 597 535 597 538 594 545 597 545 597 548 594 540 592 532 600 528 593 539 593 542 600 539 592 549 593 551 601 533 598 524 597 528 593 536 595 538 593 544 598 543 599 546 596 539 593 531 601 528 593 538 593 1661 592 546 596 545 597 547 595 539 592 532 600 527 594 536 596 538 593 543 599 542 600 544 598 535 596 1646 597 531 601 529 592 1663 601 537 595 547 595 550 592 524 597 +# name: Cool_hi type: raw frequency: 38000 duty_cycle: 0.33 data: 504 3433 503 482 502 484 510 474 510 475 509 476 508 478 506 1456 564 1405 510 475 509 476 508 502 482 477 507 478 506 479 505 480 504 489 505 480 504 481 503 482 502 483 511 473 511 474 510 475 509 509 506 479 505 480 504 481 503 482 512 473 511 474 510 476 508 1469 509 475 509 476 508 477 507 478 506 479 505 480 504 481 503 505 510 475 509 502 482 503 481 504 480 505 478 507 477 1459 509 560 507 1451 506 473 511 493 480 1450 507 3422 503 # +name: Cool_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8982 4489 604 1643 600 1649 594 537 594 540 602 537 594 547 595 550 592 1664 599 525 596 1652 601 1651 592 542 600 538 593 549 593 552 600 534 597 1646 597 530 601 1651 602 534 597 541 601 541 601 544 598 537 594 529 592 1655 598 533 598 536 595 542 600 542 600 546 596 539 592 532 599 527 594 537 594 540 591 546 596 545 597 548 594 540 602 523 598 529 592 539 592 542 600 539 592 549 593 550 592 524 597 7929 600 525 596 532 599 533 598 537 594 543 599 542 600 544 598 1654 599 524 597 530 591 538 593 540 591 544 598 543 599 544 598 535 596 526 595 531 600 529 592 544 598 541 601 542 600 546 596 540 602 522 599 529 602 530 601 534 597 541 601 541 601 545 597 539 592 532 599 528 593 539 592 541 601 537 594 547 595 551 601 535 596 529 592 536 595 537 594 541 601 538 593 549 593 553 599 536 595 529 592 536 595 534 597 537 594 544 598 543 599 546 596 539 592 1653 600 1650 593 1660 593 543 599 541 601 541 601 546 596 1643 600 7943 596 529 602 527 594 538 593 541 601 538 593 549 593 553 599 536 595 1649 594 535 596 535 596 539 592 546 596 546 596 549 593 542 600 525 596 531 600 530 601 533 598 540 602 539 592 552 600 535 596 527 594 533 598 533 598 536 595 543 599 543 599 545 597 537 594 530 601 526 595 536 595 1660 593 546 596 547 595 550 602 533 598 526 595 533 598 534 597 538 593 546 596 547 595 551 601 534 597 1647 596 532 599 532 599 1656 597 541 601 542 600 545 597 522 599 +# name: Cool_lo type: raw frequency: 38000 duty_cycle: 0.33 data: 525 3615 530 506 561 474 562 474 562 473 563 473 531 505 562 1502 528 1542 562 474 562 474 530 505 531 504 532 504 532 504 616 419 533 510 589 447 526 509 527 509 527 509 527 508 528 508 528 507 529 542 525 510 526 509 527 509 527 509 527 508 528 508 528 1535 527 524 533 503 533 503 533 502 534 502 534 502 534 501 535 501 525 534 533 502 534 502 534 501 535 502 534 1529 533 503 533 503 533 587 533 497 528 501 524 1536 526 501 524 3609 526 # +name: Cool_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8967 4495 597 1645 597 1648 594 535 596 537 594 542 600 541 601 543 599 1655 598 525 596 1651 592 1658 595 539 592 545 597 544 598 546 596 538 593 1648 595 532 599 1648 594 539 592 545 597 543 599 545 597 536 595 528 593 1651 592 538 593 541 601 1654 599 1661 592 1671 592 541 601 523 598 529 592 538 593 541 601 536 595 544 598 547 595 539 592 531 600 526 595 535 596 537 594 543 599 541 601 543 598 518 593 7937 602 522 599 528 593 537 594 539 592 545 597 544 598 546 596 1656 597 525 596 530 601 528 593 539 592 544 598 543 599 544 598 535 596 526 595 531 600 530 601 532 599 538 593 547 595 549 593 540 602 521 600 526 595 535 596 537 594 543 599 541 601 543 599 536 595 527 594 532 600 530 601 532 599 538 593 546 596 548 594 539 592 531 600 525 596 534 597 535 596 540 591 549 593 551 601 532 599 524 597 529 592 537 594 538 593 543 599 540 591 551 601 532 599 1641 591 1654 599 1650 593 540 591 1664 599 1660 593 1671 592 1643 600 7922 596 528 593 533 598 532 599 535 596 540 591 549 593 552 600 533 598 1644 599 529 592 538 593 539 592 544 598 541 590 550 592 539 592 528 593 531 590 537 594 536 595 539 592 546 596 546 596 536 595 526 595 529 592 535 596 535 596 538 593 546 596 546 596 535 596 524 597 527 594 533 598 1649 593 541 601 538 593 549 593 538 593 528 593 532 599 528 593 539 592 542 600 538 593 548 594 538 593 1643 599 525 596 532 599 1649 593 541 601 538 593 548 594 520 591 +# name: Heat_hi type: raw frequency: 38000 duty_cycle: 0.33 data: 531 3406 530 455 529 456 528 457 537 447 537 448 535 450 534 1429 528 1442 536 448 536 449 534 451 532 452 532 453 530 454 530 455 529 464 530 454 529 456 528 457 537 448 536 449 535 450 533 451 533 490 535 449 534 450 534 451 533 452 532 453 531 455 529 1433 534 1443 535 449 535 450 534 452 531 453 530 454 530 455 529 456 538 472 532 452 532 454 530 1433 535 1427 530 1432 536 1427 530 1431 537 1511 530 448 536 1422 535 1423 534 1422 535 3395 530 # +name: Heat_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8970 4496 597 1648 595 1652 601 530 602 533 598 541 601 541 601 543 599 1652 601 523 598 1649 594 1656 597 538 593 545 597 545 597 549 593 541 601 523 598 529 592 1658 595 540 592 546 596 545 597 548 594 541 601 523 598 529 592 539 593 542 600 538 593 1668 595 1670 593 1662 591 533 599 529 592 539 593 542 600 538 593 547 595 549 593 542 600 524 597 530 602 529 592 543 599 539 593 549 593 551 601 516 595 7937 593 532 599 527 594 536 596 539 592 546 596 545 597 548 594 1661 592 532 600 528 593 538 593 541 601 537 594 547 595 550 602 533 599 526 595 533 599 533 599 536 595 543 599 541 601 544 598 536 595 529 592 535 596 535 596 538 594 544 598 544 598 547 595 541 601 523 598 528 593 537 594 540 602 536 595 546 596 550 602 533 598 526 595 532 600 531 600 534 597 541 601 541 601 545 597 539 593 532 600 528 593 538 593 541 601 537 594 547 595 550 602 532 599 523 598 527 594 1653 600 533 598 538 593 1664 599 1662 591 523 598 7926 593 529 592 534 597 532 599 534 597 538 593 546 596 547 595 537 594 1648 595 532 600 532 599 536 595 543 599 543 599 546 596 540 602 522 599 529 592 538 594 541 601 536 595 546 596 549 593 542 600 523 598 529 592 538 593 541 601 538 593 548 594 552 600 534 597 527 594 534 597 534 597 1657 596 543 599 543 599 548 594 542 600 524 597 530 601 530 602 533 598 538 593 547 595 551 601 533 599 1644 599 528 593 538 593 1661 592 545 597 545 597 548 594 524 597 +# name: Heat_lo type: raw frequency: 38000 duty_cycle: 0.33 data: 506 3430 506 478 506 479 505 480 504 481 503 482 502 484 500 1463 505 1465 503 482 502 483 501 484 500 485 509 476 508 477 507 478 506 486 508 477 507 478 506 479 505 480 504 481 503 482 502 483 500 523 502 482 502 483 501 484 500 485 509 476 508 478 506 1455 502 498 507 478 506 479 505 481 503 482 501 483 500 484 500 485 509 500 505 481 502 482 502 1461 507 1455 502 1459 509 476 508 477 507 563 504 1453 504 1454 503 1454 503 1453 504 3426 499 +# +name: Heat_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8972 4491 592 1651 592 1655 598 532 599 535 597 542 600 541 601 544 598 1656 597 526 595 1652 591 1658 595 539 593 545 597 545 597 546 596 537 594 529 592 535 596 1653 600 534 597 541 601 539 592 552 600 533 598 525 596 530 591 538 593 539 592 1665 598 1662 591 1673 601 533 598 526 595 533 598 532 600 534 597 540 591 548 594 550 592 542 600 523 598 528 593 536 595 537 594 543 599 542 600 543 599 517 594 7937 593 531 601 526 595 535 597 537 594 542 600 541 601 543 599 1654 599 523 598 528 593 536 596 538 594 542 600 541 590 552 600 532 599 524 597 528 593 536 595 537 595 541 601 539 593 551 591 542 600 522 599 527 594 536 595 537 594 543 599 540 591 552 600 532 600 523 598 527 594 535 596 537 595 542 600 540 591 552 600 532 600 523 598 528 593 536 595 538 593 543 599 541 601 543 599 535 596 527 594 532 600 531 601 534 597 540 592 549 593 552 600 534 597 525 596 529 592 1655 598 534 597 1656 597 1661 592 1671 592 1644 599 7934 596 529 592 535 597 535 597 538 593 544 598 543 599 545 597 538 593 1650 593 535 596 534 597 536 595 540 591 547 595 547 595 536 595 526 595 529 592 536 595 535 596 539 593 546 596 547 595 538 593 528 593 531 601 529 592 541 601 536 596 545 597 548 594 540 592 532 600 526 595 535 596 1656 597 541 601 540 592 553 599 534 597 526 595 532 599 531 600 533 598 539 593 548 594 552 600 535 596 1647 596 531 590 538 593 1656 597 538 594 545 597 545 597 518 593 From 5bb7cabea6cb02ce2ad281484da70db4e83d2df1 Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Tue, 27 Sep 2022 01:59:28 +1000 Subject: [PATCH 087/824] Applications loader: do not use view dispatcher queue #1788 --- applications/main/fap_loader/fap_loader_app.c | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/applications/main/fap_loader/fap_loader_app.c b/applications/main/fap_loader/fap_loader_app.c index 9050ddf7834..c0f60ceca98 100644 --- a/applications/main/fap_loader/fap_loader_app.c +++ b/applications/main/fap_loader/fap_loader_app.c @@ -15,6 +15,9 @@ typedef struct { DialogsApp* dialogs; Gui* gui; string_t fap_path; + + ViewDispatcher* view_dispatcher; + Loading* loading; } FapLoader; static bool @@ -144,12 +147,12 @@ int32_t fap_loader_app(void* p) { loader->dialogs = furi_record_open(RECORD_DIALOGS); loader->gui = furi_record_open(RECORD_GUI); - ViewDispatcher* view_dispatcher = view_dispatcher_alloc(); - Loading* loading = loading_alloc(); + loader->view_dispatcher = view_dispatcher_alloc(); + loader->loading = loading_alloc(); - view_dispatcher_enable_queue(view_dispatcher); - view_dispatcher_attach_to_gui(view_dispatcher, loader->gui, ViewDispatcherTypeFullscreen); - view_dispatcher_add_view(view_dispatcher, 0, loading_get_view(loading)); + view_dispatcher_attach_to_gui( + loader->view_dispatcher, loader->gui, ViewDispatcherTypeFullscreen); + view_dispatcher_add_view(loader->view_dispatcher, 0, loading_get_view(loader->loading)); if(p) { string_init_set(loader->fap_path, (const char*)p); @@ -158,14 +161,14 @@ int32_t fap_loader_app(void* p) { string_init_set(loader->fap_path, EXT_PATH("apps")); while(fap_loader_select_app(loader)) { - view_dispatcher_switch_to_view(view_dispatcher, 0); + view_dispatcher_switch_to_view(loader->view_dispatcher, 0); fap_loader_run_selected_app(loader); }; } - view_dispatcher_remove_view(view_dispatcher, 0); - loading_free(loading); - view_dispatcher_free(view_dispatcher); + view_dispatcher_remove_view(loader->view_dispatcher, 0); + loading_free(loader->loading); + view_dispatcher_free(loader->view_dispatcher); string_clear(loader->fap_path); furi_record_close(RECORD_GUI); From e6e1e7fe15f14610ce83bbd25d94c06a41148102 Mon Sep 17 00:00:00 2001 From: Tom Samstag Date: Tue, 27 Sep 2022 10:00:50 -0700 Subject: [PATCH 088/824] Add formatting to DESfire data dump (#1784) Co-authored-by: gornekich --- lib/nfc/protocols/mifare_desfire.c | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/lib/nfc/protocols/mifare_desfire.c b/lib/nfc/protocols/mifare_desfire.c index 1822d5c1ab6..f969cdde642 100644 --- a/lib/nfc/protocols/mifare_desfire.c +++ b/lib/nfc/protocols/mifare_desfire.c @@ -209,8 +209,24 @@ void mf_df_cat_file(MifareDesfireFile* file, string_t out) { uint8_t* data = file->contents; if(data) { for(int rec = 0; rec < num; rec++) { - for(int ch = 0; ch < size; ch++) { - string_cat_printf(out, "%02x", data[rec * size + ch]); + string_cat_printf(out, "record %d\n", rec); + for(int ch = 0; ch < size; ch += 4) { + string_cat_printf(out, "%03x|", ch); + for(int i = 0; i < 4; i++) { + if(ch + i < size) { + string_cat_printf(out, "%02x ", data[rec * size + ch + i]); + } else { + string_cat_printf(out, " "); + } + } + for(int i = 0; i < 4 && ch + i < size; i++) { + if(isprint(data[rec * size + ch + i])) { + string_cat_printf(out, "%c", data[rec * size + ch + i]); + } else { + string_cat_printf(out, "."); + } + } + string_cat_printf(out, "\n"); } string_cat_printf(out, " \n"); } From 12a6290e911f837d67383eb7d2e8dd1f568af396 Mon Sep 17 00:00:00 2001 From: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> Date: Tue, 27 Sep 2022 20:11:28 +0300 Subject: [PATCH 089/824] [FL-2853] Reorganise Universal A/C library (#1792) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Reorganise A/C universal remote library file * Refactor infrared brute force code * Update UniversalRemotes.md Co-authored-by: あく --- .../main/infrared/infrared_brute_force.c | 49 +++++++-------- applications/main/infrared/infrared_signal.c | 63 +++++++++++++++---- applications/main/infrared/infrared_signal.h | 4 ++ assets/resources/infrared/assets/ac.ir | 54 ++++++++-------- documentation/UniversalRemotes.md | 6 +- 5 files changed, 108 insertions(+), 68 deletions(-) diff --git a/applications/main/infrared/infrared_brute_force.c b/applications/main/infrared/infrared_brute_force.c index 575fa05ecd3..0edc5f7425b 100644 --- a/applications/main/infrared/infrared_brute_force.c +++ b/applications/main/infrared/infrared_brute_force.c @@ -23,30 +23,36 @@ struct InfraredBruteForce { FlipperFormat* ff; const char* db_filename; string_t current_record_name; + InfraredSignal* current_signal; InfraredBruteForceRecordDict_t records; + bool is_started; }; InfraredBruteForce* infrared_brute_force_alloc() { InfraredBruteForce* brute_force = malloc(sizeof(InfraredBruteForce)); brute_force->ff = NULL; brute_force->db_filename = NULL; + brute_force->current_signal = NULL; + brute_force->is_started = false; string_init(brute_force->current_record_name); InfraredBruteForceRecordDict_init(brute_force->records); return brute_force; } void infrared_brute_force_free(InfraredBruteForce* brute_force) { - furi_assert(!brute_force->ff); + furi_assert(!brute_force->is_started); InfraredBruteForceRecordDict_clear(brute_force->records); string_clear(brute_force->current_record_name); free(brute_force); } void infrared_brute_force_set_db_filename(InfraredBruteForce* brute_force, const char* db_filename) { + furi_assert(!brute_force->is_started); brute_force->db_filename = db_filename; } bool infrared_brute_force_calculate_messages(InfraredBruteForce* brute_force) { + furi_assert(!brute_force->is_started); furi_assert(brute_force->db_filename); bool success = false; @@ -76,6 +82,7 @@ bool infrared_brute_force_start( InfraredBruteForce* brute_force, uint32_t index, uint32_t* record_count) { + furi_assert(!brute_force->is_started); bool success = false; *record_count = 0; @@ -96,50 +103,37 @@ bool infrared_brute_force_start( if(*record_count) { Storage* storage = furi_record_open(RECORD_STORAGE); brute_force->ff = flipper_format_buffered_file_alloc(storage); + brute_force->current_signal = infrared_signal_alloc(); + brute_force->is_started = true; success = flipper_format_buffered_file_open_existing(brute_force->ff, brute_force->db_filename); - if(!success) { - flipper_format_free(brute_force->ff); - brute_force->ff = NULL; - furi_record_close(RECORD_STORAGE); - } + if(!success) infrared_brute_force_stop(brute_force); } return success; } bool infrared_brute_force_is_started(InfraredBruteForce* brute_force) { - return brute_force->ff; + return brute_force->is_started; } void infrared_brute_force_stop(InfraredBruteForce* brute_force) { - furi_assert(string_size(brute_force->current_record_name)); - furi_assert(brute_force->ff); - + furi_assert(brute_force->is_started); string_reset(brute_force->current_record_name); + infrared_signal_free(brute_force->current_signal); flipper_format_free(brute_force->ff); - furi_record_close(RECORD_STORAGE); + brute_force->current_signal = NULL; brute_force->ff = NULL; + brute_force->is_started = false; + furi_record_close(RECORD_STORAGE); } bool infrared_brute_force_send_next(InfraredBruteForce* brute_force) { - furi_assert(string_size(brute_force->current_record_name)); - furi_assert(brute_force->ff); - bool success = false; - - string_t signal_name; - string_init(signal_name); - InfraredSignal* signal = infrared_signal_alloc(); - - do { - success = infrared_signal_read(signal, brute_force->ff, signal_name); - } while(success && !string_equal_p(brute_force->current_record_name, signal_name)); - + furi_assert(brute_force->is_started); + const bool success = infrared_signal_search_and_read( + brute_force->current_signal, brute_force->ff, brute_force->current_record_name); if(success) { - infrared_signal_transmit(signal); + infrared_signal_transmit(brute_force->current_signal); } - - infrared_signal_free(signal); - string_clear(signal_name); return success; } @@ -155,5 +149,6 @@ void infrared_brute_force_add_record( } void infrared_brute_force_reset(InfraredBruteForce* brute_force) { + furi_assert(!brute_force->is_started); InfraredBruteForceRecordDict_reset(brute_force->records); } diff --git a/applications/main/infrared/infrared_signal.c b/applications/main/infrared/infrared_signal.c index b76717dd35e..f2e359c8a09 100644 --- a/applications/main/infrared/infrared_signal.c +++ b/applications/main/infrared/infrared_signal.c @@ -146,6 +146,26 @@ static inline bool infrared_signal_read_raw(InfraredSignal* signal, FlipperForma return success; } +static bool infrared_signal_read_body(InfraredSignal* signal, FlipperFormat* ff) { + string_t tmp; + string_init(tmp); + bool success = false; + + do { + if(!flipper_format_read_string(ff, "type", tmp)) break; + if(string_equal_p(tmp, "raw")) { + success = infrared_signal_read_raw(signal, ff); + } else if(string_equal_p(tmp, "parsed")) { + success = infrared_signal_read_message(signal, ff); + } else { + FURI_LOG_E(TAG, "Unknown signal type"); + } + } while(false); + + string_clear(tmp); + return success; +} + InfraredSignal* infrared_signal_alloc() { InfraredSignal* signal = malloc(sizeof(InfraredSignal)); @@ -227,24 +247,41 @@ bool infrared_signal_save(InfraredSignal* signal, FlipperFormat* ff, const char* } bool infrared_signal_read(InfraredSignal* signal, FlipperFormat* ff, string_t name) { - string_t buf; - string_init(buf); + string_t tmp; + string_init(tmp); bool success = false; do { - if(!flipper_format_read_string(ff, "name", buf)) break; - string_set(name, buf); - if(!flipper_format_read_string(ff, "type", buf)) break; - if(!string_cmp_str(buf, "raw")) { - success = infrared_signal_read_raw(signal, ff); - } else if(!string_cmp_str(buf, "parsed")) { - success = infrared_signal_read_message(signal, ff); - } else { - FURI_LOG_E(TAG, "Unknown type of signal (allowed - raw/parsed) "); - } + if(!flipper_format_read_string(ff, "name", tmp)) break; + string_set(name, tmp); + if(!infrared_signal_read_body(signal, ff)) break; + success = true; } while(0); - string_clear(buf); + string_clear(tmp); + return success; +} + +bool infrared_signal_search_and_read( + InfraredSignal* signal, + FlipperFormat* ff, + const string_t name) { + bool success = false; + string_t tmp; + string_init(tmp); + + do { + bool is_name_found = false; + while(flipper_format_read_string(ff, "name", tmp)) { + is_name_found = string_equal_p(name, tmp); + if(is_name_found) break; + } + if(!is_name_found) break; + if(!infrared_signal_read_body(signal, ff)) break; + success = true; + } while(false); + + string_clear(tmp); return success; } diff --git a/applications/main/infrared/infrared_signal.h b/applications/main/infrared/infrared_signal.h index 2dbaa75faab..ad2f5d57a4c 100644 --- a/applications/main/infrared/infrared_signal.h +++ b/applications/main/infrared/infrared_signal.h @@ -37,5 +37,9 @@ InfraredMessage* infrared_signal_get_message(InfraredSignal* signal); bool infrared_signal_save(InfraredSignal* signal, FlipperFormat* ff, const char* name); bool infrared_signal_read(InfraredSignal* signal, FlipperFormat* ff, string_t name); +bool infrared_signal_search_and_read( + InfraredSignal* signal, + FlipperFormat* ff, + const string_t name); void infrared_signal_transmit(InfraredSignal* signal); diff --git a/assets/resources/infrared/assets/ac.ir b/assets/resources/infrared/assets/ac.ir index 49586b555be..7866febc693 100644 --- a/assets/resources/infrared/assets/ac.ir +++ b/assets/resources/infrared/assets/ac.ir @@ -1,71 +1,73 @@ Filetype: IR library file Version: 1 # +# Model: Electrolux EACM-16 HP/N3 name: Off type: raw frequency: 38000 duty_cycle: 0.33 data: 502 3436 510 475 509 476 508 477 507 477 507 479 505 480 504 480 504 490 504 481 502 482 501 483 563 420 511 474 510 475 509 476 508 485 561 423 508 476 508 477 507 478 506 479 505 480 504 481 503 517 508 476 508 478 506 479 505 479 505 481 503 483 521 1456 501 498 507 479 505 480 504 481 503 482 501 483 563 421 562 422 509 499 506 479 505 480 504 481 503 482 502 484 510 1451 506 479 505 1542 562 1396 509 471 502 476 508 469 504 3425 511 # -name: Off -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 8974 4505 598 1647 595 1651 591 539 592 542 600 537 594 547 595 549 593 1662 591 532 599 1649 593 1659 594 540 602 538 593 548 594 552 600 535 596 528 593 535 596 536 595 540 602 538 593 548 593 551 601 532 599 525 596 1651 591 539 592 543 599 1660 593 1669 594 1672 601 533 598 526 595 533 598 533 598 536 595 543 599 543 599 547 595 540 602 524 597 530 601 530 601 534 597 541 601 541 601 545 597 522 599 7938 591 532 599 528 593 537 594 541 601 537 594 546 596 549 593 1663 600 524 597 530 601 530 601 533 598 540 591 550 592 553 599 536 595 528 593 536 595 536 595 540 591 547 595 548 593 552 600 535 596 529 592 536 595 535 596 538 593 545 597 546 596 550 592 542 600 524 597 531 600 531 600 534 597 542 600 542 600 545 597 538 593 530 601 526 595 537 594 540 591 547 595 546 595 550 592 543 599 526 595 532 599 531 601 535 596 542 600 542 600 546 596 538 593 531 600 1648 594 536 595 539 592 1669 594 1669 594 1671 602 1637 595 7947 592 532 599 529 592 539 592 543 599 540 591 551 601 544 598 537 594 531 600 1647 595 535 596 539 592 545 597 546 596 550 592 543 599 526 595 533 598 534 597 538 593 545 597 546 596 550 602 534 597 527 594 533 598 533 598 536 595 544 598 543 599 547 595 540 591 533 598 529 592 539 592 1663 600 538 593 547 595 551 591 543 599 524 597 530 591 539 592 542 600 537 594 547 595 550 592 542 600 525 596 1651 592 538 593 1662 591 546 596 545 597 548 594 523 629 -# name: Dh type: raw frequency: 38000 duty_cycle: 0.33 data: 507 3430 506 479 505 480 504 481 503 481 503 483 501 485 509 1453 504 1465 503 482 502 483 511 473 500 485 509 476 508 477 507 478 506 487 507 477 507 478 506 479 505 480 504 482 502 483 501 484 500 523 503 482 502 484 500 485 509 476 508 476 508 478 506 1456 501 501 504 482 502 483 501 484 500 485 509 476 508 477 507 1455 502 509 506 479 505 1457 500 485 509 476 508 1454 503 482 502 483 501 568 499 1459 509 1450 507 471 502 474 510 3421 505 # -name: Dh +name: Cool_hi type: raw frequency: 38000 -duty_cycle: 0.330000 -data: 8990 4494 599 1648 595 1654 599 533 598 537 594 544 598 544 598 548 594 1662 601 523 598 1651 592 1660 593 542 600 539 593 550 592 553 599 536 596 527 594 531 601 1648 595 538 593 542 600 540 592 551 601 532 600 1643 600 1650 593 538 593 542 600 1660 593 1671 592 1673 601 534 598 527 594 534 597 533 599 536 595 543 599 542 600 545 597 537 595 530 591 536 596 535 596 538 593 544 598 543 599 546 596 522 599 7935 595 530 591 536 596 536 596 539 592 545 597 544 598 547 595 1660 593 530 591 536 596 535 596 536 595 542 600 541 591 552 600 534 597 525 596 531 601 529 592 541 601 537 595 546 596 548 594 540 591 532 600 527 594 536 595 538 594 544 598 543 599 546 596 538 594 531 600 527 594 536 595 539 593 545 597 543 599 546 596 538 593 530 591 535 596 532 599 532 600 536 595 543 599 544 598 535 596 525 596 530 591 538 593 538 593 542 600 540 591 551 601 532 600 1640 593 1651 592 1655 598 535 596 1657 596 1663 601 1661 592 1641 592 7941 599 526 595 533 599 532 600 535 597 541 601 541 601 544 598 537 595 1651 592 535 597 535 597 538 594 545 597 545 597 548 594 540 592 532 600 528 593 539 593 542 600 539 592 549 593 551 601 533 598 524 597 528 593 536 595 538 593 544 598 543 599 546 596 539 593 531 601 528 593 538 593 1661 592 546 596 545 597 547 595 539 592 532 600 527 594 536 596 538 593 543 599 542 600 544 598 535 596 1646 597 531 601 529 592 1663 601 537 595 547 595 550 592 524 597 +duty_cycle: 0.33 +data: 504 3433 503 482 502 484 510 474 510 475 509 476 508 478 506 1456 564 1405 510 475 509 476 508 502 482 477 507 478 506 479 505 480 504 489 505 480 504 481 503 482 502 483 511 473 511 474 510 475 509 509 506 479 505 480 504 481 503 482 512 473 511 474 510 476 508 1469 509 475 509 476 508 477 507 478 506 479 505 480 504 481 503 505 510 475 509 502 482 503 481 504 480 505 478 507 477 1459 509 560 507 1451 506 473 511 493 480 1450 507 3422 503 # -name: Cool_hi +name: Cool_lo type: raw frequency: 38000 duty_cycle: 0.33 -data: 504 3433 503 482 502 484 510 474 510 475 509 476 508 478 506 1456 564 1405 510 475 509 476 508 502 482 477 507 478 506 479 505 480 504 489 505 480 504 481 503 482 502 483 511 473 511 474 510 475 509 509 506 479 505 480 504 481 503 482 512 473 511 474 510 476 508 1469 509 475 509 476 508 477 507 478 506 479 505 480 504 481 503 505 510 475 509 502 482 503 481 504 480 505 478 507 477 1459 509 560 507 1451 506 473 511 493 480 1450 507 3422 503 +data: 525 3615 530 506 561 474 562 474 562 473 563 473 531 505 562 1502 528 1542 562 474 562 474 530 505 531 504 532 504 532 504 616 419 533 510 589 447 526 509 527 509 527 509 527 508 528 508 528 507 529 542 525 510 526 509 527 509 527 509 527 508 528 508 528 1535 527 524 533 503 533 503 533 502 534 502 534 502 534 501 535 501 525 534 533 502 534 502 534 501 535 502 534 1529 533 503 533 503 533 587 533 497 528 501 524 1536 526 501 524 3609 526 # -name: Cool_hi +name: Heat_hi type: raw frequency: 38000 -duty_cycle: 0.330000 -data: 8982 4489 604 1643 600 1649 594 537 594 540 602 537 594 547 595 550 592 1664 599 525 596 1652 601 1651 592 542 600 538 593 549 593 552 600 534 597 1646 597 530 601 1651 602 534 597 541 601 541 601 544 598 537 594 529 592 1655 598 533 598 536 595 542 600 542 600 546 596 539 592 532 599 527 594 537 594 540 591 546 596 545 597 548 594 540 602 523 598 529 592 539 592 542 600 539 592 549 593 550 592 524 597 7929 600 525 596 532 599 533 598 537 594 543 599 542 600 544 598 1654 599 524 597 530 591 538 593 540 591 544 598 543 599 544 598 535 596 526 595 531 600 529 592 544 598 541 601 542 600 546 596 540 602 522 599 529 602 530 601 534 597 541 601 541 601 545 597 539 592 532 599 528 593 539 592 541 601 537 594 547 595 551 601 535 596 529 592 536 595 537 594 541 601 538 593 549 593 553 599 536 595 529 592 536 595 534 597 537 594 544 598 543 599 546 596 539 592 1653 600 1650 593 1660 593 543 599 541 601 541 601 546 596 1643 600 7943 596 529 602 527 594 538 593 541 601 538 593 549 593 553 599 536 595 1649 594 535 596 535 596 539 592 546 596 546 596 549 593 542 600 525 596 531 600 530 601 533 598 540 602 539 592 552 600 535 596 527 594 533 598 533 598 536 595 543 599 543 599 545 597 537 594 530 601 526 595 536 595 1660 593 546 596 547 595 550 602 533 598 526 595 533 598 534 597 538 593 546 596 547 595 551 601 534 597 1647 596 532 599 532 599 1656 597 541 601 542 600 545 597 522 599 +duty_cycle: 0.33 +data: 531 3406 530 455 529 456 528 457 537 447 537 448 535 450 534 1429 528 1442 536 448 536 449 534 451 532 452 532 453 530 454 530 455 529 464 530 454 529 456 528 457 537 448 536 449 535 450 533 451 533 490 535 449 534 450 534 451 533 452 532 453 531 455 529 1433 534 1443 535 449 535 450 534 452 531 453 530 454 530 455 529 456 538 472 532 452 532 454 530 1433 535 1427 530 1432 536 1427 530 1431 537 1511 530 448 536 1422 535 1423 534 1422 535 3395 530 # -name: Cool_lo +name: Heat_lo type: raw frequency: 38000 duty_cycle: 0.33 -data: 525 3615 530 506 561 474 562 474 562 473 563 473 531 505 562 1502 528 1542 562 474 562 474 530 505 531 504 532 504 532 504 616 419 533 510 589 447 526 509 527 509 527 509 527 508 528 508 528 507 529 542 525 510 526 509 527 509 527 509 527 508 528 508 528 1535 527 524 533 503 533 503 533 502 534 502 534 502 534 501 535 501 525 534 533 502 534 502 534 501 535 502 534 1529 533 503 533 503 533 587 533 497 528 501 524 1536 526 501 524 3609 526 +data: 506 3430 506 478 506 479 505 480 504 481 503 482 502 484 500 1463 505 1465 503 482 502 483 501 484 500 485 509 476 508 477 507 478 506 486 508 477 507 478 506 479 505 480 504 481 503 482 502 483 500 523 502 482 502 483 501 484 500 485 509 476 508 478 506 1455 502 498 507 478 506 479 505 481 503 482 501 483 500 484 500 485 509 500 505 481 502 482 502 1461 507 1455 502 1459 509 476 508 477 507 563 504 1453 504 1454 503 1454 503 1453 504 3426 499 # -name: Cool_lo +# Model: Hisense Generic +name: Off type: raw frequency: 38000 duty_cycle: 0.330000 -data: 8967 4495 597 1645 597 1648 594 535 596 537 594 542 600 541 601 543 599 1655 598 525 596 1651 592 1658 595 539 592 545 597 544 598 546 596 538 593 1648 595 532 599 1648 594 539 592 545 597 543 599 545 597 536 595 528 593 1651 592 538 593 541 601 1654 599 1661 592 1671 592 541 601 523 598 529 592 538 593 541 601 536 595 544 598 547 595 539 592 531 600 526 595 535 596 537 594 543 599 541 601 543 598 518 593 7937 602 522 599 528 593 537 594 539 592 545 597 544 598 546 596 1656 597 525 596 530 601 528 593 539 592 544 598 543 599 544 598 535 596 526 595 531 600 530 601 532 599 538 593 547 595 549 593 540 602 521 600 526 595 535 596 537 594 543 599 541 601 543 599 536 595 527 594 532 600 530 601 532 599 538 593 546 596 548 594 539 592 531 600 525 596 534 597 535 596 540 591 549 593 551 601 532 599 524 597 529 592 537 594 538 593 543 599 540 591 551 601 532 599 1641 591 1654 599 1650 593 540 591 1664 599 1660 593 1671 592 1643 600 7922 596 528 593 533 598 532 599 535 596 540 591 549 593 552 600 533 598 1644 599 529 592 538 593 539 592 544 598 541 590 550 592 539 592 528 593 531 590 537 594 536 595 539 592 546 596 546 596 536 595 526 595 529 592 535 596 535 596 538 593 546 596 546 596 535 596 524 597 527 594 533 598 1649 593 541 601 538 593 549 593 538 593 528 593 532 599 528 593 539 592 542 600 538 593 548 594 538 593 1643 599 525 596 532 599 1649 593 541 601 538 593 548 594 520 591 +data: 8974 4505 598 1647 595 1651 591 539 592 542 600 537 594 547 595 549 593 1662 591 532 599 1649 593 1659 594 540 602 538 593 548 594 552 600 535 596 528 593 535 596 536 595 540 602 538 593 548 593 551 601 532 599 525 596 1651 591 539 592 543 599 1660 593 1669 594 1672 601 533 598 526 595 533 598 533 598 536 595 543 599 543 599 547 595 540 602 524 597 530 601 530 601 534 597 541 601 541 601 545 597 522 599 7938 591 532 599 528 593 537 594 541 601 537 594 546 596 549 593 1663 600 524 597 530 601 530 601 533 598 540 591 550 592 553 599 536 595 528 593 536 595 536 595 540 591 547 595 548 593 552 600 535 596 529 592 536 595 535 596 538 593 545 597 546 596 550 592 542 600 524 597 531 600 531 600 534 597 542 600 542 600 545 597 538 593 530 601 526 595 537 594 540 591 547 595 546 595 550 592 543 599 526 595 532 599 531 601 535 596 542 600 542 600 546 596 538 593 531 600 1648 594 536 595 539 592 1669 594 1669 594 1671 602 1637 595 7947 592 532 599 529 592 539 592 543 599 540 591 551 601 544 598 537 594 531 600 1647 595 535 596 539 592 545 597 546 596 550 592 543 599 526 595 533 598 534 597 538 593 545 597 546 596 550 602 534 597 527 594 533 598 533 598 536 595 544 598 543 599 547 595 540 591 533 598 529 592 539 592 1663 600 538 593 547 595 551 591 543 599 524 597 530 591 539 592 542 600 537 594 547 595 550 592 542 600 525 596 1651 592 538 593 1662 591 546 596 545 597 548 594 523 629 # -name: Heat_hi +name: Dh type: raw frequency: 38000 -duty_cycle: 0.33 -data: 531 3406 530 455 529 456 528 457 537 447 537 448 535 450 534 1429 528 1442 536 448 536 449 534 451 532 452 532 453 530 454 530 455 529 464 530 454 529 456 528 457 537 448 536 449 535 450 533 451 533 490 535 449 534 450 534 451 533 452 532 453 531 455 529 1433 534 1443 535 449 535 450 534 452 531 453 530 454 530 455 529 456 538 472 532 452 532 454 530 1433 535 1427 530 1432 536 1427 530 1431 537 1511 530 448 536 1422 535 1423 534 1422 535 3395 530 +duty_cycle: 0.330000 +data: 8990 4494 599 1648 595 1654 599 533 598 537 594 544 598 544 598 548 594 1662 601 523 598 1651 592 1660 593 542 600 539 593 550 592 553 599 536 596 527 594 531 601 1648 595 538 593 542 600 540 592 551 601 532 600 1643 600 1650 593 538 593 542 600 1660 593 1671 592 1673 601 534 598 527 594 534 597 533 599 536 595 543 599 542 600 545 597 537 595 530 591 536 596 535 596 538 593 544 598 543 599 546 596 522 599 7935 595 530 591 536 596 536 596 539 592 545 597 544 598 547 595 1660 593 530 591 536 596 535 596 536 595 542 600 541 591 552 600 534 597 525 596 531 601 529 592 541 601 537 595 546 596 548 594 540 591 532 600 527 594 536 595 538 594 544 598 543 599 546 596 538 594 531 600 527 594 536 595 539 593 545 597 543 599 546 596 538 593 530 591 535 596 532 599 532 600 536 595 543 599 544 598 535 596 525 596 530 591 538 593 538 593 542 600 540 591 551 601 532 600 1640 593 1651 592 1655 598 535 596 1657 596 1663 601 1661 592 1641 592 7941 599 526 595 533 599 532 600 535 597 541 601 541 601 544 598 537 595 1651 592 535 597 535 597 538 594 545 597 545 597 548 594 540 592 532 600 528 593 539 593 542 600 539 592 549 593 551 601 533 598 524 597 528 593 536 595 538 593 544 598 543 599 546 596 539 593 531 601 528 593 538 593 1661 592 546 596 545 597 547 595 539 592 532 600 527 594 536 596 538 593 543 599 542 600 544 598 535 596 1646 597 531 601 529 592 1663 601 537 595 547 595 550 592 524 597 # -name: Heat_hi +name: Cool_hi type: raw frequency: 38000 duty_cycle: 0.330000 -data: 8970 4496 597 1648 595 1652 601 530 602 533 598 541 601 541 601 543 599 1652 601 523 598 1649 594 1656 597 538 593 545 597 545 597 549 593 541 601 523 598 529 592 1658 595 540 592 546 596 545 597 548 594 541 601 523 598 529 592 539 593 542 600 538 593 1668 595 1670 593 1662 591 533 599 529 592 539 593 542 600 538 593 547 595 549 593 542 600 524 597 530 602 529 592 543 599 539 593 549 593 551 601 516 595 7937 593 532 599 527 594 536 596 539 592 546 596 545 597 548 594 1661 592 532 600 528 593 538 593 541 601 537 594 547 595 550 602 533 599 526 595 533 599 533 599 536 595 543 599 541 601 544 598 536 595 529 592 535 596 535 596 538 594 544 598 544 598 547 595 541 601 523 598 528 593 537 594 540 602 536 595 546 596 550 602 533 598 526 595 532 600 531 600 534 597 541 601 541 601 545 597 539 593 532 600 528 593 538 593 541 601 537 594 547 595 550 602 532 599 523 598 527 594 1653 600 533 598 538 593 1664 599 1662 591 523 598 7926 593 529 592 534 597 532 599 534 597 538 593 546 596 547 595 537 594 1648 595 532 600 532 599 536 595 543 599 543 599 546 596 540 602 522 599 529 592 538 594 541 601 536 595 546 596 549 593 542 600 523 598 529 592 538 593 541 601 538 593 548 594 552 600 534 597 527 594 534 597 534 597 1657 596 543 599 543 599 548 594 542 600 524 597 530 601 530 602 533 598 538 593 547 595 551 601 533 599 1644 599 528 593 538 593 1661 592 545 597 545 597 548 594 524 597 +data: 8982 4489 604 1643 600 1649 594 537 594 540 602 537 594 547 595 550 592 1664 599 525 596 1652 601 1651 592 542 600 538 593 549 593 552 600 534 597 1646 597 530 601 1651 602 534 597 541 601 541 601 544 598 537 594 529 592 1655 598 533 598 536 595 542 600 542 600 546 596 539 592 532 599 527 594 537 594 540 591 546 596 545 597 548 594 540 602 523 598 529 592 539 592 542 600 539 592 549 593 550 592 524 597 7929 600 525 596 532 599 533 598 537 594 543 599 542 600 544 598 1654 599 524 597 530 591 538 593 540 591 544 598 543 599 544 598 535 596 526 595 531 600 529 592 544 598 541 601 542 600 546 596 540 602 522 599 529 602 530 601 534 597 541 601 541 601 545 597 539 592 532 599 528 593 539 592 541 601 537 594 547 595 551 601 535 596 529 592 536 595 537 594 541 601 538 593 549 593 553 599 536 595 529 592 536 595 534 597 537 594 544 598 543 599 546 596 539 592 1653 600 1650 593 1660 593 543 599 541 601 541 601 546 596 1643 600 7943 596 529 602 527 594 538 593 541 601 538 593 549 593 553 599 536 595 1649 594 535 596 535 596 539 592 546 596 546 596 549 593 542 600 525 596 531 600 530 601 533 598 540 602 539 592 552 600 535 596 527 594 533 598 533 598 536 595 543 599 543 599 545 597 537 594 530 601 526 595 536 595 1660 593 546 596 547 595 550 602 533 598 526 595 533 598 534 597 538 593 546 596 547 595 551 601 534 597 1647 596 532 599 532 599 1656 597 541 601 542 600 545 597 522 599 # -name: Heat_lo +name: Cool_lo type: raw frequency: 38000 -duty_cycle: 0.33 -data: 506 3430 506 478 506 479 505 480 504 481 503 482 502 484 500 1463 505 1465 503 482 502 483 501 484 500 485 509 476 508 477 507 478 506 486 508 477 507 478 506 479 505 480 504 481 503 482 502 483 500 523 502 482 502 483 501 484 500 485 509 476 508 478 506 1455 502 498 507 478 506 479 505 481 503 482 501 483 500 484 500 485 509 500 505 481 502 482 502 1461 507 1455 502 1459 509 476 508 477 507 563 504 1453 504 1454 503 1454 503 1453 504 3426 499 +duty_cycle: 0.330000 +data: 8967 4495 597 1645 597 1648 594 535 596 537 594 542 600 541 601 543 599 1655 598 525 596 1651 592 1658 595 539 592 545 597 544 598 546 596 538 593 1648 595 532 599 1648 594 539 592 545 597 543 599 545 597 536 595 528 593 1651 592 538 593 541 601 1654 599 1661 592 1671 592 541 601 523 598 529 592 538 593 541 601 536 595 544 598 547 595 539 592 531 600 526 595 535 596 537 594 543 599 541 601 543 598 518 593 7937 602 522 599 528 593 537 594 539 592 545 597 544 598 546 596 1656 597 525 596 530 601 528 593 539 592 544 598 543 599 544 598 535 596 526 595 531 600 530 601 532 599 538 593 547 595 549 593 540 602 521 600 526 595 535 596 537 594 543 599 541 601 543 599 536 595 527 594 532 600 530 601 532 599 538 593 546 596 548 594 539 592 531 600 525 596 534 597 535 596 540 591 549 593 551 601 532 599 524 597 529 592 537 594 538 593 543 599 540 591 551 601 532 599 1641 591 1654 599 1650 593 540 591 1664 599 1660 593 1671 592 1643 600 7922 596 528 593 533 598 532 599 535 596 540 591 549 593 552 600 533 598 1644 599 529 592 538 593 539 592 544 598 541 590 550 592 539 592 528 593 531 590 537 594 536 595 539 592 546 596 546 596 536 595 526 595 529 592 535 596 535 596 538 593 546 596 546 596 535 596 524 597 527 594 533 598 1649 593 541 601 538 593 549 593 538 593 528 593 532 599 528 593 539 592 542 600 538 593 548 594 538 593 1643 599 525 596 532 599 1649 593 541 601 538 593 548 594 520 591 +# +name: Heat_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8970 4496 597 1648 595 1652 601 530 602 533 598 541 601 541 601 543 599 1652 601 523 598 1649 594 1656 597 538 593 545 597 545 597 549 593 541 601 523 598 529 592 1658 595 540 592 546 596 545 597 548 594 541 601 523 598 529 592 539 593 542 600 538 593 1668 595 1670 593 1662 591 533 599 529 592 539 593 542 600 538 593 547 595 549 593 542 600 524 597 530 602 529 592 543 599 539 593 549 593 551 601 516 595 7937 593 532 599 527 594 536 596 539 592 546 596 545 597 548 594 1661 592 532 600 528 593 538 593 541 601 537 594 547 595 550 602 533 599 526 595 533 599 533 599 536 595 543 599 541 601 544 598 536 595 529 592 535 596 535 596 538 594 544 598 544 598 547 595 541 601 523 598 528 593 537 594 540 602 536 595 546 596 550 602 533 598 526 595 532 600 531 600 534 597 541 601 541 601 545 597 539 593 532 600 528 593 538 593 541 601 537 594 547 595 550 602 532 599 523 598 527 594 1653 600 533 598 538 593 1664 599 1662 591 523 598 7926 593 529 592 534 597 532 599 534 597 538 593 546 596 547 595 537 594 1648 595 532 600 532 599 536 595 543 599 543 599 546 596 540 602 522 599 529 592 538 594 541 601 536 595 546 596 549 593 542 600 523 598 529 592 538 593 541 601 538 593 548 594 552 600 534 597 527 594 534 597 534 597 1657 596 543 599 543 599 548 594 542 600 524 597 530 601 530 602 533 598 538 593 547 595 551 601 533 599 1644 599 528 593 538 593 1661 592 545 597 545 597 548 594 524 597 # name: Heat_lo type: raw diff --git a/documentation/UniversalRemotes.md b/documentation/UniversalRemotes.md index 6ecf3b11b54..3dd82c6158d 100644 --- a/documentation/UniversalRemotes.md +++ b/documentation/UniversalRemotes.md @@ -33,6 +33,8 @@ Finally, record the `Off` signal: The resulting remote file should now contain 6 signals. Any of them can be omitted, but that will mean that this functionality will not be used. Test the file against the actual device. Every signal must do what it's supposed to. -If everything checks out, add these signals to the [A/C universal remote file](/assets/resources/infrared/assets/ac.ir). -Keep the signals of the same type grouped together (e.g. an `Off` signal must follow a previous `Off` one). +If everything checks out, append these signals **to the end** of the [A/C universal remote file](/assets/resources/infrared/assets/ac.ir). + +The order of signals is not important, but they must be preceded by a following comment: `# Model: ` in order to keep the library organised. + When done, open a pull request containing the changed file. From 4241ad24a3e038cf2f60f83e4c6b9d1ae4bb7453 Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Wed, 28 Sep 2022 19:37:24 +0300 Subject: [PATCH 090/824] [FL-2797] Signal Generator app (#1793) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Signal Generator app * MCO pin initialization in app * furi_hal_pwm documentation Co-authored-by: あく --- .../plugins/signal_generator/application.fam | 12 + .../scenes/signal_gen_scene.c | 30 ++ .../scenes/signal_gen_scene.h | 29 ++ .../scenes/signal_gen_scene_config.h | 3 + .../scenes/signal_gen_scene_mco.c | 132 ++++++++ .../scenes/signal_gen_scene_pwm.c | 60 ++++ .../scenes/signal_gen_scene_start.c | 55 ++++ .../signal_generator/signal_gen_10px.png | Bin 0 -> 6082 bytes .../plugins/signal_generator/signal_gen_app.c | 93 ++++++ .../signal_generator/signal_gen_app_i.h | 46 +++ .../signal_generator/views/signal_gen_pwm.c | 301 ++++++++++++++++++ .../signal_generator/views/signal_gen_pwm.h | 21 ++ assets/icons/Interface/SmallArrowDown_4x7.png | Bin 0 -> 8340 bytes assets/icons/Interface/SmallArrowUp_4x7.png | Bin 0 -> 8552 bytes firmware/targets/f7/api_symbols.csv | 10 +- firmware/targets/f7/furi_hal/furi_hal_clock.c | 61 ++++ firmware/targets/f7/furi_hal/furi_hal_clock.h | 37 +++ firmware/targets/f7/furi_hal/furi_hal_pwm.c | 138 ++++++++ firmware/targets/f7/furi_hal/furi_hal_pwm.h | 42 +++ 19 files changed, 1069 insertions(+), 1 deletion(-) create mode 100644 applications/plugins/signal_generator/application.fam create mode 100644 applications/plugins/signal_generator/scenes/signal_gen_scene.c create mode 100644 applications/plugins/signal_generator/scenes/signal_gen_scene.h create mode 100644 applications/plugins/signal_generator/scenes/signal_gen_scene_config.h create mode 100644 applications/plugins/signal_generator/scenes/signal_gen_scene_mco.c create mode 100644 applications/plugins/signal_generator/scenes/signal_gen_scene_pwm.c create mode 100644 applications/plugins/signal_generator/scenes/signal_gen_scene_start.c create mode 100644 applications/plugins/signal_generator/signal_gen_10px.png create mode 100644 applications/plugins/signal_generator/signal_gen_app.c create mode 100644 applications/plugins/signal_generator/signal_gen_app_i.h create mode 100644 applications/plugins/signal_generator/views/signal_gen_pwm.c create mode 100644 applications/plugins/signal_generator/views/signal_gen_pwm.h create mode 100644 assets/icons/Interface/SmallArrowDown_4x7.png create mode 100644 assets/icons/Interface/SmallArrowUp_4x7.png create mode 100644 firmware/targets/f7/furi_hal/furi_hal_pwm.c create mode 100644 firmware/targets/f7/furi_hal/furi_hal_pwm.h diff --git a/applications/plugins/signal_generator/application.fam b/applications/plugins/signal_generator/application.fam new file mode 100644 index 00000000000..7794ee492c6 --- /dev/null +++ b/applications/plugins/signal_generator/application.fam @@ -0,0 +1,12 @@ +App( + appid="signal_generator", + name="Signal Generator", + apptype=FlipperAppType.PLUGIN, + entry_point="signal_gen_app", + cdefines=["APP_SIGNAL_GEN"], + requires=["gui"], + stack_size=1 * 1024, + order=50, + fap_icon="signal_gen_10px.png", + fap_category="Tools", +) diff --git a/applications/plugins/signal_generator/scenes/signal_gen_scene.c b/applications/plugins/signal_generator/scenes/signal_gen_scene.c new file mode 100644 index 00000000000..29b11ee3f47 --- /dev/null +++ b/applications/plugins/signal_generator/scenes/signal_gen_scene.c @@ -0,0 +1,30 @@ +#include "../signal_gen_app_i.h" + +// Generate scene on_enter handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, +void (*const signal_gen_scene_on_enter_handlers[])(void*) = { +#include "signal_gen_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 signal_gen_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = { +#include "signal_gen_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 signal_gen_scene_on_exit_handlers[])(void* context) = { +#include "signal_gen_scene_config.h" +}; +#undef ADD_SCENE + +// Initialize scene handlers configuration structure +const SceneManagerHandlers signal_gen_scene_handlers = { + .on_enter_handlers = signal_gen_scene_on_enter_handlers, + .on_event_handlers = signal_gen_scene_on_event_handlers, + .on_exit_handlers = signal_gen_scene_on_exit_handlers, + .scene_num = SignalGenSceneNum, +}; diff --git a/applications/plugins/signal_generator/scenes/signal_gen_scene.h b/applications/plugins/signal_generator/scenes/signal_gen_scene.h new file mode 100644 index 00000000000..c139afa3bf4 --- /dev/null +++ b/applications/plugins/signal_generator/scenes/signal_gen_scene.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +// Generate scene id and total number +#define ADD_SCENE(prefix, name, id) SignalGenScene##id, +typedef enum { +#include "signal_gen_scene_config.h" + SignalGenSceneNum, +} SignalGenScene; +#undef ADD_SCENE + +extern const SceneManagerHandlers signal_gen_scene_handlers; + +// Generate scene on_enter handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); +#include "signal_gen_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 "signal_gen_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 "signal_gen_scene_config.h" +#undef ADD_SCENE diff --git a/applications/plugins/signal_generator/scenes/signal_gen_scene_config.h b/applications/plugins/signal_generator/scenes/signal_gen_scene_config.h new file mode 100644 index 00000000000..b6c75025669 --- /dev/null +++ b/applications/plugins/signal_generator/scenes/signal_gen_scene_config.h @@ -0,0 +1,3 @@ +ADD_SCENE(signal_gen, start, Start) +ADD_SCENE(signal_gen, pwm, Pwm) +ADD_SCENE(signal_gen, mco, Mco) diff --git a/applications/plugins/signal_generator/scenes/signal_gen_scene_mco.c b/applications/plugins/signal_generator/scenes/signal_gen_scene_mco.c new file mode 100644 index 00000000000..632b08c75e4 --- /dev/null +++ b/applications/plugins/signal_generator/scenes/signal_gen_scene_mco.c @@ -0,0 +1,132 @@ +#include "../signal_gen_app_i.h" + +typedef enum { + LineIndexSource, + LineIndexDivision, +} LineIndex; + +static const char* const mco_source_names[] = { + "32768", + "64MHz", + "~100K", + "~200K", + "~400K", + "~800K", + "~1MHz", + "~2MHz", + "~4MHz", + "~8MHz", + "~16MHz", + "~24MHz", + "~32MHz", + "~48MHz", +}; + +static const FuriHalClockMcoSourceId mco_sources[] = { + FuriHalClockMcoLse, + FuriHalClockMcoSysclk, + FuriHalClockMcoMsi100k, + FuriHalClockMcoMsi200k, + FuriHalClockMcoMsi400k, + FuriHalClockMcoMsi800k, + FuriHalClockMcoMsi1m, + FuriHalClockMcoMsi2m, + FuriHalClockMcoMsi4m, + FuriHalClockMcoMsi8m, + FuriHalClockMcoMsi16m, + FuriHalClockMcoMsi24m, + FuriHalClockMcoMsi32m, + FuriHalClockMcoMsi48m, +}; + +static const char* const mco_divisor_names[] = { + "1", + "2", + "4", + "8", + "16", +}; + +static const FuriHalClockMcoDivisorId mco_divisors[] = { + FuriHalClockMcoDiv1, + FuriHalClockMcoDiv2, + FuriHalClockMcoDiv4, + FuriHalClockMcoDiv8, + FuriHalClockMcoDiv16, +}; + +static void mco_source_list_change_callback(VariableItem* item) { + SignalGenApp* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + variable_item_set_current_value_text(item, mco_source_names[index]); + + app->mco_src = mco_sources[index]; + + view_dispatcher_send_custom_event(app->view_dispatcher, SignalGenMcoEventUpdate); +} + +static void mco_divisor_list_change_callback(VariableItem* item) { + SignalGenApp* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + variable_item_set_current_value_text(item, mco_divisor_names[index]); + + app->mco_div = mco_divisors[index]; + + view_dispatcher_send_custom_event(app->view_dispatcher, SignalGenMcoEventUpdate); +} + +void signal_gen_scene_mco_on_enter(void* context) { + SignalGenApp* app = context; + VariableItemList* var_item_list = app->var_item_list; + + VariableItem* item; + + item = variable_item_list_add( + var_item_list, "Source", COUNT_OF(mco_source_names), mco_source_list_change_callback, app); + variable_item_set_current_value_index(item, 0); + variable_item_set_current_value_text(item, mco_source_names[0]); + + item = variable_item_list_add( + var_item_list, + "Division", + COUNT_OF(mco_divisor_names), + mco_divisor_list_change_callback, + app); + variable_item_set_current_value_index(item, 0); + variable_item_set_current_value_text(item, mco_divisor_names[0]); + + variable_item_list_set_selected_item(var_item_list, LineIndexSource); + + view_dispatcher_switch_to_view(app->view_dispatcher, SignalGenViewVarItemList); + + app->mco_src = FuriHalClockMcoLse; + app->mco_div = FuriHalClockMcoDiv1; + furi_hal_clock_mco_enable(app->mco_src, app->mco_div); + furi_hal_gpio_init_ex( + &gpio_usart_tx, GpioModeAltFunctionPushPull, GpioPullUp, GpioSpeedVeryHigh, GpioAltFn0MCO); +} + +bool signal_gen_scene_mco_on_event(void* context, SceneManagerEvent event) { + SignalGenApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SignalGenMcoEventUpdate) { + consumed = true; + furi_hal_clock_mco_enable(app->mco_src, app->mco_div); + } + } + return consumed; +} + +void signal_gen_scene_mco_on_exit(void* context) { + SignalGenApp* app = context; + variable_item_list_reset(app->var_item_list); + furi_hal_gpio_init_ex( + &gpio_usart_tx, + GpioModeAltFunctionPushPull, + GpioPullUp, + GpioSpeedVeryHigh, + GpioAltFn7USART1); + furi_hal_clock_mco_disable(); +} diff --git a/applications/plugins/signal_generator/scenes/signal_gen_scene_pwm.c b/applications/plugins/signal_generator/scenes/signal_gen_scene_pwm.c new file mode 100644 index 00000000000..f302c023228 --- /dev/null +++ b/applications/plugins/signal_generator/scenes/signal_gen_scene_pwm.c @@ -0,0 +1,60 @@ +#include "../signal_gen_app_i.h" + +static const FuriHalPwmOutputId pwm_ch_id[] = { + FuriHalPwmOutputIdTim1PA7, + FuriHalPwmOutputIdLptim2PA4, +}; + +#define DEFAULT_FREQ 1000 +#define DEFAULT_DUTY 50 + +static void + signal_gen_pwm_callback(uint8_t channel_id, uint32_t freq, uint8_t duty, void* context) { + SignalGenApp* app = context; + + app->pwm_freq = freq; + app->pwm_duty = duty; + + if(app->pwm_ch != pwm_ch_id[channel_id]) { + app->pwm_ch_prev = app->pwm_ch; + app->pwm_ch = pwm_ch_id[channel_id]; + view_dispatcher_send_custom_event(app->view_dispatcher, SignalGenPwmEventChannelChange); + } else { + app->pwm_ch = pwm_ch_id[channel_id]; + view_dispatcher_send_custom_event(app->view_dispatcher, SignalGenPwmEventUpdate); + } +} + +void signal_gen_scene_pwm_on_enter(void* context) { + SignalGenApp* app = context; + + view_dispatcher_switch_to_view(app->view_dispatcher, SignalGenViewPwm); + + signal_gen_pwm_set_callback(app->pwm_view, signal_gen_pwm_callback, app); + + signal_gen_pwm_set_params(app->pwm_view, 0, DEFAULT_FREQ, DEFAULT_DUTY); + furi_hal_pwm_start(pwm_ch_id[0], DEFAULT_FREQ, DEFAULT_DUTY); +} + +bool signal_gen_scene_pwm_on_event(void* context, SceneManagerEvent event) { + SignalGenApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SignalGenPwmEventUpdate) { + consumed = true; + furi_hal_pwm_set_params(app->pwm_ch, app->pwm_freq, app->pwm_duty); + } else if(event.event == SignalGenPwmEventChannelChange) { + consumed = true; + furi_hal_pwm_stop(app->pwm_ch_prev); + furi_hal_pwm_start(app->pwm_ch, app->pwm_freq, app->pwm_duty); + } + } + return consumed; +} + +void signal_gen_scene_pwm_on_exit(void* context) { + SignalGenApp* app = context; + variable_item_list_reset(app->var_item_list); + furi_hal_pwm_stop(app->pwm_ch); +} diff --git a/applications/plugins/signal_generator/scenes/signal_gen_scene_start.c b/applications/plugins/signal_generator/scenes/signal_gen_scene_start.c new file mode 100644 index 00000000000..91f6081d84b --- /dev/null +++ b/applications/plugins/signal_generator/scenes/signal_gen_scene_start.c @@ -0,0 +1,55 @@ +#include "../signal_gen_app_i.h" + +typedef enum { + SubmenuIndexPwm, + SubmenuIndexClockOutput, +} SubmenuIndex; + +void signal_gen_scene_start_submenu_callback(void* context, uint32_t index) { + SignalGenApp* app = context; + + view_dispatcher_send_custom_event(app->view_dispatcher, index); +} + +void signal_gen_scene_start_on_enter(void* context) { + SignalGenApp* app = context; + Submenu* submenu = app->submenu; + + submenu_add_item( + submenu, "PWM", SubmenuIndexPwm, signal_gen_scene_start_submenu_callback, app); + submenu_add_item( + submenu, + "Clock Output", + SubmenuIndexClockOutput, + signal_gen_scene_start_submenu_callback, + app); + + submenu_set_selected_item( + submenu, scene_manager_get_scene_state(app->scene_manager, SignalGenSceneStart)); + + view_dispatcher_switch_to_view(app->view_dispatcher, SignalGenViewSubmenu); +} + +bool signal_gen_scene_start_on_event(void* context, SceneManagerEvent event) { + SignalGenApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubmenuIndexPwm) { + scene_manager_next_scene(app->scene_manager, SignalGenScenePwm); + consumed = true; + } else if(event.event == SubmenuIndexClockOutput) { + scene_manager_next_scene(app->scene_manager, SignalGenSceneMco); + consumed = true; + } + scene_manager_set_scene_state(app->scene_manager, SignalGenSceneStart, event.event); + } + + return consumed; +} + +void signal_gen_scene_start_on_exit(void* context) { + SignalGenApp* app = context; + + submenu_reset(app->submenu); +} diff --git a/applications/plugins/signal_generator/signal_gen_10px.png b/applications/plugins/signal_generator/signal_gen_10px.png new file mode 100644 index 0000000000000000000000000000000000000000..9f6dcc5d0d9a9c1fbc54b7459b46c50c16c7e863 GIT binary patch literal 6082 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 literal 0 HcmV?d00001 diff --git a/applications/plugins/signal_generator/signal_gen_app.c b/applications/plugins/signal_generator/signal_gen_app.c new file mode 100644 index 00000000000..ca065d330d1 --- /dev/null +++ b/applications/plugins/signal_generator/signal_gen_app.c @@ -0,0 +1,93 @@ +#include "signal_gen_app_i.h" + +#include +#include + +static bool signal_gen_app_custom_event_callback(void* context, uint32_t event) { + furi_assert(context); + SignalGenApp* app = context; + return scene_manager_handle_custom_event(app->scene_manager, event); +} + +static bool signal_gen_app_back_event_callback(void* context) { + furi_assert(context); + SignalGenApp* app = context; + return scene_manager_handle_back_event(app->scene_manager); +} + +static void signal_gen_app_tick_event_callback(void* context) { + furi_assert(context); + SignalGenApp* app = context; + scene_manager_handle_tick_event(app->scene_manager); +} + +SignalGenApp* signal_gen_app_alloc() { + SignalGenApp* app = malloc(sizeof(SignalGenApp)); + + app->gui = furi_record_open(RECORD_GUI); + + app->view_dispatcher = view_dispatcher_alloc(); + app->scene_manager = scene_manager_alloc(&signal_gen_scene_handlers, app); + view_dispatcher_enable_queue(app->view_dispatcher); + view_dispatcher_set_event_callback_context(app->view_dispatcher, app); + + view_dispatcher_set_custom_event_callback( + app->view_dispatcher, signal_gen_app_custom_event_callback); + view_dispatcher_set_navigation_event_callback( + app->view_dispatcher, signal_gen_app_back_event_callback); + view_dispatcher_set_tick_event_callback( + app->view_dispatcher, signal_gen_app_tick_event_callback, 100); + + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + + app->var_item_list = variable_item_list_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + SignalGenViewVarItemList, + variable_item_list_get_view(app->var_item_list)); + + app->submenu = submenu_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, SignalGenViewSubmenu, submenu_get_view(app->submenu)); + + app->pwm_view = signal_gen_pwm_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, SignalGenViewPwm, signal_gen_pwm_get_view(app->pwm_view)); + + scene_manager_next_scene(app->scene_manager, SignalGenSceneStart); + + return app; +} + +void signal_gen_app_free(SignalGenApp* app) { + furi_assert(app); + + // Views + view_dispatcher_remove_view(app->view_dispatcher, SignalGenViewVarItemList); + view_dispatcher_remove_view(app->view_dispatcher, SignalGenViewSubmenu); + view_dispatcher_remove_view(app->view_dispatcher, SignalGenViewPwm); + + submenu_free(app->submenu); + variable_item_list_free(app->var_item_list); + signal_gen_pwm_free(app->pwm_view); + + // View dispatcher + view_dispatcher_free(app->view_dispatcher); + scene_manager_free(app->scene_manager); + + // Close records + furi_record_close(RECORD_GUI); + + free(app); +} + +int32_t signal_gen_app(void* p) { + UNUSED(p); + SignalGenApp* signal_gen_app = signal_gen_app_alloc(); + + view_dispatcher_run(signal_gen_app->view_dispatcher); + + signal_gen_app_free(signal_gen_app); + + return 0; +} diff --git a/applications/plugins/signal_generator/signal_gen_app_i.h b/applications/plugins/signal_generator/signal_gen_app_i.h new file mode 100644 index 00000000000..47c266475b1 --- /dev/null +++ b/applications/plugins/signal_generator/signal_gen_app_i.h @@ -0,0 +1,46 @@ +#pragma once + +#include "scenes/signal_gen_scene.h" + +#include "furi_hal_clock.h" +#include "furi_hal_pwm.h" + +#include +#include +#include +#include +#include +#include +#include "views/signal_gen_pwm.h" + +typedef struct SignalGenApp SignalGenApp; + +struct SignalGenApp { + Gui* gui; + ViewDispatcher* view_dispatcher; + SceneManager* scene_manager; + + VariableItemList* var_item_list; + Submenu* submenu; + SignalGenPwm* pwm_view; + + FuriHalClockMcoSourceId mco_src; + FuriHalClockMcoDivisorId mco_div; + + FuriHalPwmOutputId pwm_ch_prev; + FuriHalPwmOutputId pwm_ch; + uint32_t pwm_freq; + uint8_t pwm_duty; +}; + +typedef enum { + SignalGenViewVarItemList, + SignalGenViewSubmenu, + SignalGenViewPwm, +} SignalGenAppView; + +typedef enum { + SignalGenMcoEventUpdate, + SignalGenPwmEventUpdate, + SignalGenPwmEventChannelChange, +} SignalGenCustomEvent; diff --git a/applications/plugins/signal_generator/views/signal_gen_pwm.c b/applications/plugins/signal_generator/views/signal_gen_pwm.c new file mode 100644 index 00000000000..00b4ef2674b --- /dev/null +++ b/applications/plugins/signal_generator/views/signal_gen_pwm.c @@ -0,0 +1,301 @@ +#include "../signal_gen_app_i.h" +#include "furi_hal.h" +#include + +typedef enum { + LineIndexChannel, + LineIndexFrequency, + LineIndexDuty, + LineIndexTotalCount +} LineIndex; + +static const char* const pwm_ch_names[] = {"TIM1(2)", "LPTIM2(4)"}; + +struct SignalGenPwm { + View* view; + SignalGenPwmViewCallback callback; + void* context; +}; + +typedef struct { + LineIndex line_sel; + bool edit_mode; + uint8_t edit_digit; + + uint8_t channel_id; + uint32_t freq; + uint8_t duty; + +} SignalGenPwmViewModel; + +#define ITEM_H 64 / 3 +#define ITEM_W 128 + +#define VALUE_X 95 +#define VALUE_W 55 + +#define FREQ_VALUE_X 62 +#define FREQ_MAX 1000000UL +#define FREQ_DIGITS_NB 7 + +static void pwm_set_config(SignalGenPwm* pwm) { + FuriHalPwmOutputId channel; + uint32_t freq; + uint8_t duty; + + with_view_model( + pwm->view, (SignalGenPwmViewModel * model) { + channel = model->channel_id; + freq = model->freq; + duty = model->duty; + return false; + }); + + furi_assert(pwm->callback); + pwm->callback(channel, freq, duty, pwm->context); +} + +static void pwm_channel_change(SignalGenPwmViewModel* model, InputEvent* event) { + if(event->key == InputKeyLeft) { + if(model->channel_id > 0) { + model->channel_id--; + } + } else if(event->key == InputKeyRight) { + if(model->channel_id < (COUNT_OF(pwm_ch_names) - 1)) { + model->channel_id++; + } + } +} + +static void pwm_duty_change(SignalGenPwmViewModel* model, InputEvent* event) { + if(event->key == InputKeyLeft) { + if(model->duty > 0) { + model->duty--; + } + } else if(event->key == InputKeyRight) { + if(model->duty < 100) { + model->duty++; + } + } +} + +static bool pwm_freq_edit(SignalGenPwmViewModel* model, InputEvent* event) { + bool consumed = false; + if((event->type == InputTypeShort) || (event->type == InputTypeRepeat)) { + if(event->key == InputKeyRight) { + if(model->edit_digit > 0) { + model->edit_digit--; + } + consumed = true; + } else if(event->key == InputKeyLeft) { + if(model->edit_digit < (FREQ_DIGITS_NB - 1)) { + model->edit_digit++; + } + consumed = true; + } else if(event->key == InputKeyUp) { + uint32_t step = 1; + for(uint8_t i = 0; i < model->edit_digit; i++) { + step *= 10; + } + if((model->freq + step) < FREQ_MAX) { + model->freq += step; + } else { + model->freq = FREQ_MAX; + } + consumed = true; + } else if(event->key == InputKeyDown) { + uint32_t step = 1; + for(uint8_t i = 0; i < model->edit_digit; i++) { + step *= 10; + } + if(model->freq > (step + 1)) { + model->freq -= step; + } else { + model->freq = 1; + } + consumed = true; + } + } + return consumed; +} + +static void signal_gen_pwm_draw_callback(Canvas* canvas, void* _model) { + SignalGenPwmViewModel* model = _model; + char* line_label = NULL; + char val_text[16]; + + for(uint8_t line = 0; line < LineIndexTotalCount; line++) { + if(line == LineIndexChannel) { + line_label = "PWM Channel"; + } else if(line == LineIndexFrequency) { + line_label = "Frequency"; + } else if(line == LineIndexDuty) { + line_label = "Duty Cycle"; + } + + canvas_set_color(canvas, ColorBlack); + if(line == model->line_sel) { + elements_slightly_rounded_box(canvas, 0, ITEM_H * line + 1, ITEM_W, ITEM_H - 1); + canvas_set_color(canvas, ColorWhite); + } + + uint8_t text_y = ITEM_H * line + ITEM_H / 2 + 2; + + canvas_draw_str_aligned(canvas, 6, text_y, AlignLeft, AlignCenter, line_label); + + if(line == LineIndexChannel) { + snprintf(val_text, sizeof(val_text), "%s", pwm_ch_names[model->channel_id]); + canvas_draw_str_aligned(canvas, VALUE_X, text_y, AlignCenter, AlignCenter, val_text); + if(model->channel_id != 0) { + canvas_draw_str_aligned( + canvas, VALUE_X - VALUE_W / 2, text_y, AlignCenter, AlignCenter, "<"); + } + if(model->channel_id != (COUNT_OF(pwm_ch_names) - 1)) { + canvas_draw_str_aligned( + canvas, VALUE_X + VALUE_W / 2, text_y, AlignCenter, AlignCenter, ">"); + } + } else if(line == LineIndexFrequency) { + snprintf(val_text, sizeof(val_text), "%7lu Hz", model->freq); + canvas_set_font(canvas, FontKeyboard); + canvas_draw_str_aligned( + canvas, FREQ_VALUE_X, text_y, AlignLeft, AlignCenter, val_text); + canvas_set_font(canvas, FontSecondary); + + if(model->edit_mode) { + uint8_t icon_x = (FREQ_VALUE_X - 1) + (FREQ_DIGITS_NB - model->edit_digit - 1) * 6; + canvas_draw_icon(canvas, icon_x, text_y - 9, &I_SmallArrowUp_4x7); + canvas_draw_icon(canvas, icon_x, text_y + 4, &I_SmallArrowDown_4x7); + } + } else if(line == LineIndexDuty) { + snprintf(val_text, sizeof(val_text), "%d%%", model->duty); + canvas_draw_str_aligned(canvas, VALUE_X, text_y, AlignCenter, AlignCenter, val_text); + if(model->duty != 0) { + canvas_draw_str_aligned( + canvas, VALUE_X - VALUE_W / 2, text_y, AlignCenter, AlignCenter, "<"); + } + if(model->duty != 100) { + canvas_draw_str_aligned( + canvas, VALUE_X + VALUE_W / 2, text_y, AlignCenter, AlignCenter, ">"); + } + } + } +} + +static bool signal_gen_pwm_input_callback(InputEvent* event, void* context) { + furi_assert(context); + SignalGenPwm* pwm = context; + bool consumed = false; + bool need_update = false; + + with_view_model( + pwm->view, (SignalGenPwmViewModel * model) { + if(model->edit_mode == false) { + if((event->type == InputTypeShort) || (event->type == InputTypeRepeat)) { + if(event->key == InputKeyUp) { + if(model->line_sel == 0) { + model->line_sel = LineIndexTotalCount - 1; + } else { + model->line_sel = + CLAMP(model->line_sel - 1, LineIndexTotalCount - 1, 0); + } + consumed = true; + } else if(event->key == InputKeyDown) { + if(model->line_sel == LineIndexTotalCount - 1) { + model->line_sel = 0; + } else { + model->line_sel = + CLAMP(model->line_sel + 1, LineIndexTotalCount - 1, 0); + } + consumed = true; + } else if((event->key == InputKeyLeft) || (event->key == InputKeyRight)) { + if(model->line_sel == LineIndexChannel) { + pwm_channel_change(model, event); + need_update = true; + } else if(model->line_sel == LineIndexDuty) { + pwm_duty_change(model, event); + need_update = true; + } else if(model->line_sel == LineIndexFrequency) { + model->edit_mode = true; + } + consumed = true; + } else if(event->key == InputKeyOk) { + if(model->line_sel == LineIndexFrequency) { + model->edit_mode = true; + } + consumed = true; + } + } + } else { + if((event->key == InputKeyOk) || (event->key == InputKeyBack)) { + if(event->type == InputTypeShort) { + model->edit_mode = false; + consumed = true; + } + } else { + if(model->line_sel == LineIndexFrequency) { + consumed = pwm_freq_edit(model, event); + need_update = consumed; + } + } + } + return true; + }); + + if(need_update) { + pwm_set_config(pwm); + } + + return consumed; +} + +SignalGenPwm* signal_gen_pwm_alloc() { + SignalGenPwm* pwm = malloc(sizeof(SignalGenPwm)); + + pwm->view = view_alloc(); + view_allocate_model(pwm->view, ViewModelTypeLocking, sizeof(SignalGenPwmViewModel)); + view_set_context(pwm->view, pwm); + view_set_draw_callback(pwm->view, signal_gen_pwm_draw_callback); + view_set_input_callback(pwm->view, signal_gen_pwm_input_callback); + + return pwm; +} + +void signal_gen_pwm_free(SignalGenPwm* pwm) { + furi_assert(pwm); + view_free(pwm->view); + free(pwm); +} + +View* signal_gen_pwm_get_view(SignalGenPwm* pwm) { + furi_assert(pwm); + return pwm->view; +} + +void signal_gen_pwm_set_callback( + SignalGenPwm* pwm, + SignalGenPwmViewCallback callback, + void* context) { + furi_assert(pwm); + furi_assert(callback); + + with_view_model( + pwm->view, (SignalGenPwmViewModel * model) { + UNUSED(model); + pwm->callback = callback; + pwm->context = context; + return false; + }); +} + +void signal_gen_pwm_set_params(SignalGenPwm* pwm, uint8_t channel_id, uint32_t freq, uint8_t duty) { + with_view_model( + pwm->view, (SignalGenPwmViewModel * model) { + model->channel_id = channel_id; + model->freq = freq; + model->duty = duty; + return true; + }); + + furi_assert(pwm->callback); + pwm->callback(channel_id, freq, duty, pwm->context); +} diff --git a/applications/plugins/signal_generator/views/signal_gen_pwm.h b/applications/plugins/signal_generator/views/signal_gen_pwm.h new file mode 100644 index 00000000000..986794e7a28 --- /dev/null +++ b/applications/plugins/signal_generator/views/signal_gen_pwm.h @@ -0,0 +1,21 @@ +#pragma once + +#include +#include "../signal_gen_app_i.h" + +typedef struct SignalGenPwm SignalGenPwm; +typedef void ( + *SignalGenPwmViewCallback)(uint8_t channel_id, uint32_t freq, uint8_t duty, void* context); + +SignalGenPwm* signal_gen_pwm_alloc(); + +void signal_gen_pwm_free(SignalGenPwm* pwm); + +View* signal_gen_pwm_get_view(SignalGenPwm* pwm); + +void signal_gen_pwm_set_callback( + SignalGenPwm* pwm, + SignalGenPwmViewCallback callback, + void* context); + +void signal_gen_pwm_set_params(SignalGenPwm* pwm, uint8_t channel_id, uint32_t freq, uint8_t duty); diff --git a/assets/icons/Interface/SmallArrowDown_4x7.png b/assets/icons/Interface/SmallArrowDown_4x7.png new file mode 100644 index 0000000000000000000000000000000000000000..5c5252b167d2f9f9a1ce5e7b9f9c99123879c1b4 GIT binary patch literal 8340 zcmeHLc{tSV*B@j}*+Z$ulu*Vj3}!5av6FpYGG+!7W5&$LlAY{(sZ@5MMY3g!NS4UH zL=+)JkrYCChkBmh^IX5@_rC9QUGMwfGuQQ<@Ap3UIiLHS&pGG*F40D3wf1owvM_TlNP3M`sZmv}VuF%3 zx_1QJEUX^*RNcKdM;LI@i?{XYD^ImhobETB&8Ve)y!}%3*#7W{*J0GnF%3yq|43O>u2&c9J{f;uvQF zm}2B$++)y^_A-fAnA##dx$ki(`%T=G^BycGXTGq=GKbG+@>L>n6HFg|)CWYcC9ZP=7w$vYM=1b>F0x>cUS2a^P&u@o^Df$dCR_7K z@`ve~bAhULUK2?~{1b_8W32@*L`t7-wK+ zMN}Nf4Gmm2w$F6vQyV_Lnw+jta?VO;Dynm3__I}EN>aL1v+a7>kpc5}DS7uol5^!# zRX?y~Fe?SR_q>)0#2M9G5x#8V+J;c5m|@`8b}`CHp25YSMCWYc5pOhz<@0<;f(Om| z6774U_b9TOv`kj=$z7J_4T_-m|nlCz7`iJb3e4m;<`|~ zuNH%R(vmR5g)+;#@H*)UnU#SseIpss|IBhaLLZeJk2f1y+{CdadbGV2MvOMJOH?is z8(fZ?PPEl|NM79TF1k2pb+@GBjfyDx>%gO+ijR!s1x#tk36txdtl!m6@|m}(Nl>xZ zgD_JEI%c>60t>&R}2jD|&U=K`&H{UWs==oe4)Kd>nSUZs}cQ8mIB3&+EiwhH_IE9 zGGMdH*TXg$%u8Xyd5n4Gp@Wwt)`cF*=3Ou)sY??XKFox>CfnxrAQa5X!unHCU-qJ< z@EoTMvFvGvBoaIwf|UT5Wq(B7Wp8$c)X5aQ2dv^8GLBuu`VW zzMp)Bn%S1N)>1&0$_%7j^|zf2eE$39j#qU#5ES8J&jNjxS26d`azKv!IK@TVZpWXq zQ?P<@B&VyrZGRWyWq+G-&v>>m-x*#cUQNmC;G0x%z|%x}TU{bM6RzS?FF7MKg| zOBn7k4F(w>4JeE(Vu>Bp;u?NuyywfrCl!K@<;19=uT?~+!J#vWPEHFP4Ibt4^h_22Hg^n2~C^sC-xrXvR;yX)lZj8eE` zuJpLMnlNhbtJ6-FJ_%#z)@?`vfaK6-WVCT|J{w$OA~;t)xdaq*iNUyJVUI9+z>~~> z=&BB`NO=6Q?^5T@@uO>jZkcu6uU(b`hH3ywTVc#k^{v>0IkT4T1rTuE))OULUa& z7JROtY*JCgg#~BX)~n(5C$M2Oorp6nr0Ei2|Fd!ciIg$v8LBDH9gIzpfQN#od;t^M&(D48 zgIK-j=Ih{U?s{P(&3n8cO8A7GX+->GzSy_j-3GI<%`c$)<>QY{!iLj?twr8a0M6Z8 z*8ILi;Zl&|umVy6N(2+@B?f}*w{E7aUAXEH9<(ToKDyqtY8jZe5AZM_Uoo-~NhAmW z>=zh}YQINzIzj+3rS)F>13Zt^_&b<9o0M`nb#H9UPLCneO%gJ^{-ag zw(&(Vr>RApS0Ka^s9BAo~u@jw?z-02|NOjILQNKzlb~4vlOtmMT_ZM zXKZiU_4Li|&>sPoRr){L4jd0kz-po4RlLSsA8u^fFvoSjGXUJ`E}BjL=eq$7Dpt7Ej2 z3)*b?Su`|YM*3IwwjUL^G?{ZX+ag;o_pULkQF@`ufcb#Ld5*6<2AXa;oxNRi0Gbp^ zI6YQc(=zL3&*8bUIi4>H8v-n0+1NN(f>4mY$O(Zm#xmiuL;VTJOTsO2nEVo}`=}w2 zg_wQKl!s34utyvhxi9iwWL8WF2s<`OBR^6&XQy;F>eMsy2^+FF*^f|1Y zPy80A1|(=F?Tmf$qbz03zL?qFzg}#W?SuBzUcSTY@LW5(=$Wz2ZL?gO+53pTa=7`3 zCejS4?psEQ{mlGMi(jfaYEu2Jag4;Kgo^~$Ec~t^-n4qaYd~|}dR}3lx=cQEj|t&+ zImtDwTv8w=CSA8&H$ykR-BUpFaQ2~Irn&cD;2W3?uMI_Wu(_dm{rrhRxxrI+iw9>4 zeG8Wdbq8JzY7V@)BQp>%&_7Ul$NSEB{~5w{Lb%)X`47Dc_m*kpck+Wk% z;fPMzW3ra6i~S-!w}$$LKKHlx-R(o=el@%K;A3|B-8%Kn@YTHi=B)-=S!L?8d0p5D zj;71WU4^(WuzVWA>${g&&DWZ+8X3P9KV!d3ejokTzOZjdeLw#F@J8K+DGQ24l!ck4 zh^2Dha)eY9f0KR0_lV;7xOk^HIUz4$Ww?dRerT7x2cq6hP0mx^+PTk>(#el)ha1Se zCi;zV%9Y9M!S2Bh!6Q2lzhIuJ?TiHX95{PGEtVzrkUnq!<+LVue1c6MI1IcOTq~7n zEo{|nODvnO+;An5(mh_g2DC;v@wuA1_G4G>6{Ue+K5AKgW%t=`y!zhkh3)_k7mwD; zQLJ}=#`&X8Pa5lA{s;@p4QtEp#Pszxzub!`_*e%%K_Ta2USJru$c-wm?TnyV};tJr((7N0j zUU0|d?yPFJi^GrusCa+zOLv!5-Bn4_v(fI_XBT@SC;8r)ex#0DcFT`c4tlqDzad>3 zmkcToO4^p&KEzYyrDXlJNKqp~Pj+p%pmsdi=G?A}@L zOQAi%Js-2zp2js@y%caXft_n_F?)~@hR>M0_epS#gHsD;D*##HCd@qC0pU`253sdH&$9| z$m&-8`Z{m3*ex+F-ri_fG&p(*nSG0;>q}qo?FzkuD)&X{MZOGH5-TZXsU0$Fc)a>V zs<^W51%o#kN@8weZ#!x?(KVimJ7wwHp=xaq|T6v@Ct%k zkVLt(6vV$;+S%SP*`oJGPRZB61>0DG7>#CmxhnU<(WqAv!wOY|#r(SlNA-Nf1oeva zPU()5W<2WGQ#&nF&jq|nDaSv2k?r1X@xtQm(8B0g2Ao;(IcgGR939k^Mq@P z*FHQp!dMzt#y-?5)w2<{8?nb5RaDFec8pQ2*-|_j{y*%4-I#}irhTD zdAm2r!PGvnj|mG$R1W%-SGL|Rn7Y`pw05*#*@9~K&S!-EX><->=GmFdTPmqu^Y7nJ z_haky{FZJ-D^rUgk4c~0dq!4f{kzlOuYEY}`{wIb$=)u(1P7e`hni{f9cn{h))edJ zQ54Hz7R5-B$Qp>E{i1M&y#Hj(W@^7>U2#V30rJPbwyos13&t-=Z?vmK>z>zso3q#` znysXIbdOn61p_LlGy3XcHgBvQ3Y7g`Vm}X5R+(2ueR6CJQ^byyh@Zx9e+hg%C;AQ3 z9NvXqIX0@*dLlp^a@t|tXG>@3a#}(2=~LTwn=htEyr1}N!Jz_*h#z)ew)%^2o-2GZ zyxq5mU!o0v`EDs(?Cfyuv2hS}0=z)nuD$NfCsVytvUKY7^}xArLSL2_3*rnj1DV#s zS2mGZqtyVwehmWslZ};uJ_<`BN@H*&C%m)|kxc)j0{|$U_90`iZg?8d3GYJiPzEj4 zJ^=v!*%wEZY}Q@^ujSU)!`5(hf1!lC4Yq5}}|Gz`#(=S zaZ&W^9Wew1+*P5uDTAyGjDYGS3LYpcEh`NLYxoc@z(Fb;KqU&!8D)aj`~^WDDT7>T zG%^YT@%HwX_Lh+*QCuJ}BoYaM!Xa=tn63e)`g+hXK41^(u^ot?7-&2dOCgYH1d<1E z2NUB&@}en&K=gUwZ~hU<1_r;wdr*I|K<5MEgCRp;(ohJI2>GK0m8Nll4)V*PzqFv5 z(Z75_Oz>2a7X^#gxPbSd9s4tc3I1O({VyQs>VE>jVSjgk>_u_k;N9^=`dU=F z+ps_RtfgaM^t;6lhb{yndDn`btv_kf2+se(vz?h;KK>X9-TZglzl>v7nXYAEfI^e7 zUOVpTpp`*%i8PLYLm_0*YBErmf|@)`6$aB#Q&oehA~ob?Rn_5g2&k<5AIx<;s5FcR z7XO<$Vb@#%CWnwif)VoaNU$s%h5%!*P#D-526e(f5zbI&1=t@T3@HS9DP!FK$`e^A z5-y98$AX1RMcI%Hw6h7>o?kNgfN6!^k-8LgBC|O%jEOq1OO`h;hL~$Q~}c z6FVfMRE>0$L2zm4za&QP7@9NPKpCV@@bL2a*Mu2?h&QESb{K@o$;rZ@2$&pH9wCqT z74|Nk1)f5sm+uZH3@R-H-!J&VNMxvOJNbbs@9SGo# zj2UPl* zW^DqMzP|6S=w}<6;yr(keh%FUyZr|Q?sgIigZ&u-6>|ZP+x3%<^>YgAit%v4)3=;o zCHkA3@E4_kgu`WU3eGq%9P0!H%R+IoU?*9;JQ#yR(sL<`#LD7+lJq+|mE=tG#!&F8 zE_5F0T+v%%mn)#eZlWar?2ET6ekTib!oW}@_}>YG{0bPdv(fw(u@dCpIr&8s8c!k6 z1Ja(yrrw*fHtx{FKpmV*17o z`F&^ql}kF0|DV5KHS+(t1Q7VoB7ckDf9U#$uD`{=-%|dky8fZ-Z!z$e!&|1-Kc z{`GE%_n<$WdDGwgw!a$r+Y^Giobch9crB6t?+DnrSiS^G==$;`2~TTgKE-+ zYxCiOV$@(bO@`NwXVS@D>;31fZpKbRj|(}GEm}9&ett=~?v8k4+^ol3X-m+1SV<$i?3^J= zV29^bZFiW$)|?7JxM3@@+*~{oFT`}%g4BC!oZ5RApKwV=*7ieJ*;W}?XwhkCzY3{z zUn#>a>rEZxft=gbj)`(x9|Io7r?cR|eDS@J+aiTqi%+_{Dr;+^JFY+YbWw1yMQ5K} z)0Ogb2N^*X56mYoewqwaYZp#nz>Rry4LPnZq`4-&sgV+KZLE%7!|t0MYKG;8HI4Da zi8Jg!k%Tpvx@HseuKi%>@zgHfY6gbbBo@JE8r8gVac$t)< zkA(_o?hipaQ}5`+h_|NQY!x;9Ro_exE_EH5UUEi&qfo)G)} zwL*r+Z9R|Ow(PLM)l9t4aL_(IDGjL@GV4#ZBUOf;35p1L+WHraeC% zLkcrc=s@*bC@;&E%TvkE*km76(B7Z14ANS-xjDYg;6^i@Oh`%SS03oO@6ULc2JcLa z6y}~(SGQ+ixYXmG_SHzdMBF7!gTgJ4RNC%3bP&-T>XK@SK#QGDE~R=HR?`I!8cqw+z(lHY?f)`n*cf$)U{uCPGY z|Ev3ENf1zLNISFkzMX4*c<%*}g2tWnKq}UIz6={5^gD^is?8FeJpufOLtSd1Mqmk%{)~xn zx5Y!%PMO*VJFaa{AXzF(xehrzs0ibCm{xahReXgt%a-UmuxQ5`c`U~krmirb;`Wea z^o7Ivd4`^DQ01+B*QN9B1Ge+z_{cuS#PbTNoMZztt9$r1VGEc3o2ex=ENgO6+{Hy( zO&PE2yQ&qQw|=}*^wmc?k;%t+$V^{Q+*u~VM?CIB`Y-v*&7Ec6q;HJ=g6~Kvado2( zS&t=SNCv>2@sPIrC(`U4#r%q;(qlv6qTb*;?kHgkI_m52Uy9b$`EEw&bzpu81$Hoh z>{hkEcOYR{Iv)@&8^AjBE>$pYsHyZyhB!0RH-V<yYh}0A+|F+wh-EEYIwrn$NYYC-q-`LlkxI;q zzotU$U=19Joh}VF5n{5noHd+?zQU>P;o^MQw?h1tCpbk!$-wLr6UjR5d#ao0_q-{_ z8_CJQ%8}qC8>4{|M_;CE0b!zgGer^-xrsS^;Uc1-1aln$&7xB&*>iQ(nk`WWp_MC@ zDoMO7Usl#q{XUP6%v+Q z7g|!U;g$wPJC4{@d4z*kJxDS~UMXWGuNsMPi#R_&%70GRk+)gLh{}9bVk|i;j}*oy zt1#sv(5c2jrB2x8zt5E2`53&u?t7$qjQy;-bW|yrRMMSZsVGx%DU9PSCwS7pmM1R3 zTT-KOsqx%XAGI&Gkf57p`rxM>_0LwWc(iKwY-yQ65BtQ7JS_81t~ru%Cqw0io8$)0 zGxET}fL@0p;89EFjFuaBid&-U@f@e$6x9S1cV3Q^u9jAR7fT7=QC{g6y3_`CTn+)$ zt)kaiX2Qb&05&*DOUp!GOY667mcF@W-AMJZFdr6->aJI=H@V6ko6>XM)oh<0N4;K> z{Bbxdx51Mu0FV;af{HawDmZ`uzYERRN-71#US=>Yo!>8v9k@W{7fIE3E)stG$Zx4L z^R3u=@cEqj?$<6$L4&n`E8F2rPmQcuLl5Rz^N&xrL?^_#II=Jt`~+Zh2fU+}mum`c z=1CI2R3RD z4bu2M zR-RX~Fj`pJ-aIlPnJ;dm~zTIT}0%^F@Wy~p`3C!?;?;96&u zJ&67V*yi@G3F_B_A9A8L#8|$29~I=;mTZWIrf6ed6c_=GQY-D+`J$Q9H6ztGaxyl! zn!eu}u1Sp<+2H9(wcdAI{IGiPQ2_Wj50Ev$S+-{>h`m*p@oQ&XZ+hB|t?sbzK~~j9 z-`fQxLlW`2SVT3iY1hY$ZQFfFAV6E4aZX)ihLP{d{)bO^ssUW@824#_qxWBMgue^p zzruh83h{?l<{nB3D{_>%!|a{QgA7-q9->{szeA!}D>X27(=wEzyS zs$5f7-c13276}+@L8EO6k2_Pj<7w1^#0a6rXP*!#G5N5nC)cQqf!vD`r<<<7ml$!^0GY-Zh_*6$T$ybtLXW<-V&>c)k`XDx8rBl)`puHrLaF2 z6SzEaGd0gLPbvSNDT~RCJ0}Lt41nF(R(Xtd&fo0p?V175&%Cpx%22JSeAU8_c;9d@#dEbRe|qtmu%Q#k6@1`i zVvacdo{sfFgr4Sc93<~*j}FSP+R#@+l6N)wR5SHvXmzBuzA9Grw5U{rl&Kz3Qt8+w zNpZ6Hg3JxjYfwEf`I(M0m#3TeAFGLr!uA-x+#|9%5JCPDdE7!$-(Vncq zzQHg3&-?E6Ve(fkG9N7D-MCk;wH2{;i}OsIv2Jd;*7U6|d?Z^_a#GhF=M{JXP0{nCY86a}jeD$LC17CjKUe$ZwIw z3GoR|@k)ohh1C$23Y@SmWe>%M^O{N*lx>K8j-H+T_~!^?h1X>N;e$%$%7*a!a1lgQ z=aCMk4|SbUkRHKPf|_y6aUw>%{mJP~?u10!K1eu(9a1NkV5JV#9xfhjRik*H{%kifr^k&AufB(e z=ZClFb>jMZpTK)d2R}2%wX)&K#2Y@EDt_pqizV_4q9*v>n=g31wdz(Lu6pV7y!#F1^4nt}6(Lu4ly*cIY#GKv zKZM>@Z+mj>Qjo^2Tqcc|8eCzWVcLyq;#57y_O&Z@%N*0}(>|BOdO~{^^46cmH>F+< z5=&&|VlQS5IfUag6y;3*__N%*;R6=G&IiriU+b zvT8jW+aJx>?A?Z+%%9AhC=}Y18>^wJHlD~sjce6%Q^l8Z&>1#4-1{aOrWUHo6WSb>@ zv3MeA!Jb{XSvYvR;S{^^XwKR(HR*aa#_LIG7cBI63Rt%gl?uyrq0QK`vxtnzRP*FzX=Tj1Ubbg8jN(``{Kfc%%$K0$$7e6u zHahg$<=T}zbfn#$e!h98=icr6{#NtF^0sOXt(4A7l@XPs^C930c{ylcjXcrebdqIn z#?8{z;CZ_^fmrPpWuit{ZUmTw{zIf{!(*L^E z{XV6z>iW@rQIb(dLozoOR+}`U0)8n;tO#=1a3Hf(=HKF*vwwqrd96=sA#wib3Ht4C zRom^t$$%G2>tg+CmR|PneTP|}#@yt~IrSkY>%=whx!KvNetf;5|58?rnpY9@5#^J6 z&+zheVE2vL^^Yh0-mGqyvUdq3o^^KkSUW|%>-8i!camjG48wecStUv|surr^uqd3Z z95@lXb*02Z$(S}yOSqT>t&t89I z8sha1GEd&AOY`AVs97pql0BIgJoEL?%IC$x_|rMTjO!80Tj<=;8UTO`OrpP|u{Jis z;3;HzoHNCVAn!}2(%<0#0IDZ_sW|+30uAUya3Oi9ftKnXgMcJwHIS8}G0d2%MQ|nQ z`Fj$~{ZCoo{m+POpsJ@c5o3nc`3XU9sexQ+G%5xP_3`nM_fe3ic)CF0 zXfzrMLqHJ-2wel><>x`e`9eIrBz7TwU|~iQr zB2)LQ=-K*g*@em>or2s)H zA`~HLWdag{!zrMhl<{ySoPyIHlrtWqL-8cz=ruqh<6H<(s)x&7$1cej4HJDe5JDdI zhs4AkM%(I_SKA4--4PcM4;?qb4W z@(PH(p50Vn=)us*#O)S09bivRXBDI6Nx;!4o)#2}yBcWMCE%{*@7>1qIp~a|;jlOw zfes2oATTgA2BBmDM`7Sd3>+p4Q^3Ifq)%}s5&iyO+PgCjsQM$#dL%FU__-t&`=^`i^#it}(G(3hN_CHkA3^f#q|Mj#ZN zQAB450`CNaAYsl(h!c{a48b|0>AWJ*c%<_Wl72_`q7Z35I8TCx3!O(gSM({d#}yE~ zm#AZZ^~J}Pu$u)sVGtM^@{=$G2KgId(EomjP6!1UkzU#mWqOPdBwPszLBkb^5EK!K zz$v3pC?^!^uTK9jg!reL_!*)qbayTNEvc%||1<4BD*;RJB++xK<4Ga!W!#HKU&k=c z|9Kfu`oAs%^rG0c|GVV>Tt*cCqy3)iKOFzlra({G4;g*4q3>PL-#4$HxuloMfAjZq z8vHkh00RFVhfPHbFatXX(7Z9MYeG2&BLDyZ literal 0 HcmV?d00001 diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index c18780acbe5..18550984d2b 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,1.12,, +Version,+,1.13,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -42,6 +42,7 @@ Header,+,firmware/targets/f7/furi_hal/furi_hal_i2c_types.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_idle_timer.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_interrupt.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_os.h,, +Header,+,firmware/targets/f7/furi_hal/furi_hal_pwm.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_resources.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_spi_config.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_spi_types.h,, @@ -954,6 +955,8 @@ Function,+,furi_hal_cdc_set_callbacks,void,"uint8_t, CdcCallbacks*, void*" Function,+,furi_hal_clock_deinit_early,void, Function,-,furi_hal_clock_init,void, Function,-,furi_hal_clock_init_early,void, +Function,+,furi_hal_clock_mco_disable,void, +Function,+,furi_hal_clock_mco_enable,void,"FuriHalClockMcoSourceId, FuriHalClockMcoDivisorId" Function,-,furi_hal_clock_resume_tick,void, Function,-,furi_hal_clock_suspend_tick,void, Function,-,furi_hal_clock_switch_to_hsi,void, @@ -1150,6 +1153,9 @@ Function,+,furi_hal_power_sleep,void, Function,+,furi_hal_power_sleep_available,_Bool, Function,+,furi_hal_power_suppress_charge_enter,void, Function,+,furi_hal_power_suppress_charge_exit,void, +Function,+,furi_hal_pwm_set_params,void,"FuriHalPwmOutputId, uint32_t, uint8_t" +Function,+,furi_hal_pwm_start,void,"FuriHalPwmOutputId, uint32_t, uint8_t" +Function,+,furi_hal_pwm_stop,void,FuriHalPwmOutputId Function,+,furi_hal_random_fill_buf,void,"uint8_t*, uint32_t" Function,+,furi_hal_random_get,uint32_t, Function,+,furi_hal_region_get,const FuriHalRegion*, @@ -2665,6 +2671,8 @@ Variable,+,I_SDQuestion_35x43,const Icon, Variable,+,I_SDcardFail_11x8,const Icon, Variable,+,I_SDcardMounted_11x8,const Icon, Variable,+,I_Scanning_123x52,const Icon, +Variable,+,I_SmallArrowDown_4x7,const Icon, +Variable,+,I_SmallArrowUp_4x7,const Icon, Variable,+,I_Smile_18x18,const Icon, Variable,+,I_Space_65x18,const Icon, Variable,+,I_Tap_reader_36x38,const Icon, diff --git a/firmware/targets/f7/furi_hal/furi_hal_clock.c b/firmware/targets/f7/furi_hal/furi_hal_clock.c index a7c9b4d031d..cf19451ec7b 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_clock.c +++ b/firmware/targets/f7/furi_hal/furi_hal_clock.c @@ -1,4 +1,5 @@ #include +#include #include #include @@ -236,3 +237,63 @@ void furi_hal_clock_suspend_tick() { void furi_hal_clock_resume_tick() { SET_BIT(SysTick->CTRL, SysTick_CTRL_ENABLE_Msk); } + +void furi_hal_clock_mco_enable(FuriHalClockMcoSourceId source, FuriHalClockMcoDivisorId div) { + if(source == FuriHalClockMcoLse) { + LL_RCC_ConfigMCO(LL_RCC_MCO1SOURCE_LSE, div); + } else if(source == FuriHalClockMcoSysclk) { + LL_RCC_ConfigMCO(LL_RCC_MCO1SOURCE_SYSCLK, div); + } else { + LL_RCC_MSI_Enable(); + while(LL_RCC_MSI_IsReady() != 1) + ; + switch(source) { + case FuriHalClockMcoMsi100k: + LL_RCC_MSI_SetRange(LL_RCC_MSIRANGE_0); + break; + case FuriHalClockMcoMsi200k: + LL_RCC_MSI_SetRange(LL_RCC_MSIRANGE_1); + break; + case FuriHalClockMcoMsi400k: + LL_RCC_MSI_SetRange(LL_RCC_MSIRANGE_2); + break; + case FuriHalClockMcoMsi800k: + LL_RCC_MSI_SetRange(LL_RCC_MSIRANGE_3); + break; + case FuriHalClockMcoMsi1m: + LL_RCC_MSI_SetRange(LL_RCC_MSIRANGE_4); + break; + case FuriHalClockMcoMsi2m: + LL_RCC_MSI_SetRange(LL_RCC_MSIRANGE_5); + break; + case FuriHalClockMcoMsi4m: + LL_RCC_MSI_SetRange(LL_RCC_MSIRANGE_6); + break; + case FuriHalClockMcoMsi8m: + LL_RCC_MSI_SetRange(LL_RCC_MSIRANGE_7); + break; + case FuriHalClockMcoMsi16m: + LL_RCC_MSI_SetRange(LL_RCC_MSIRANGE_8); + break; + case FuriHalClockMcoMsi24m: + LL_RCC_MSI_SetRange(LL_RCC_MSIRANGE_9); + break; + case FuriHalClockMcoMsi32m: + LL_RCC_MSI_SetRange(LL_RCC_MSIRANGE_10); + break; + case FuriHalClockMcoMsi48m: + LL_RCC_MSI_SetRange(LL_RCC_MSIRANGE_11); + break; + default: + break; + } + LL_RCC_ConfigMCO(LL_RCC_MCO1SOURCE_MSI, div); + } +} + +void furi_hal_clock_mco_disable() { + LL_RCC_ConfigMCO(LL_RCC_MCO1SOURCE_NOCLOCK, FuriHalClockMcoDiv1); + LL_RCC_MSI_Disable(); + while(LL_RCC_MSI_IsReady() != 0) + ; +} \ No newline at end of file diff --git a/firmware/targets/f7/furi_hal/furi_hal_clock.h b/firmware/targets/f7/furi_hal/furi_hal_clock.h index 6cebb20c626..5e651bbd359 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_clock.h +++ b/firmware/targets/f7/furi_hal/furi_hal_clock.h @@ -4,6 +4,33 @@ extern "C" { #endif +#include + +typedef enum { + FuriHalClockMcoLse, + FuriHalClockMcoSysclk, + FuriHalClockMcoMsi100k, + FuriHalClockMcoMsi200k, + FuriHalClockMcoMsi400k, + FuriHalClockMcoMsi800k, + FuriHalClockMcoMsi1m, + FuriHalClockMcoMsi2m, + FuriHalClockMcoMsi4m, + FuriHalClockMcoMsi8m, + FuriHalClockMcoMsi16m, + FuriHalClockMcoMsi24m, + FuriHalClockMcoMsi32m, + FuriHalClockMcoMsi48m, +} FuriHalClockMcoSourceId; + +typedef enum { + FuriHalClockMcoDiv1 = LL_RCC_MCO1_DIV_1, + FuriHalClockMcoDiv2 = LL_RCC_MCO1_DIV_2, + FuriHalClockMcoDiv4 = LL_RCC_MCO1_DIV_4, + FuriHalClockMcoDiv8 = LL_RCC_MCO1_DIV_8, + FuriHalClockMcoDiv16 = LL_RCC_MCO1_DIV_16, +} FuriHalClockMcoDivisorId; + /** Early initialization */ void furi_hal_clock_init_early(); @@ -25,6 +52,16 @@ void furi_hal_clock_suspend_tick(); /** Continue SysTick counter operation */ void furi_hal_clock_resume_tick(); +/** Enable clock output on MCO pin + * + * @param source MCO clock source + * @param div MCO clock division +*/ +void furi_hal_clock_mco_enable(FuriHalClockMcoSourceId source, FuriHalClockMcoDivisorId div); + +/** Disable clock output on MCO pin */ +void furi_hal_clock_mco_disable(); + #ifdef __cplusplus } #endif diff --git a/firmware/targets/f7/furi_hal/furi_hal_pwm.c b/firmware/targets/f7/furi_hal/furi_hal_pwm.c new file mode 100644 index 00000000000..e484808d5ab --- /dev/null +++ b/firmware/targets/f7/furi_hal/furi_hal_pwm.c @@ -0,0 +1,138 @@ +#include "furi_hal_pwm.h" +#include +#include + +#include +#include +#include +#include + +#include + +const uint32_t lptim_psc_table[] = { + LL_LPTIM_PRESCALER_DIV1, + LL_LPTIM_PRESCALER_DIV2, + LL_LPTIM_PRESCALER_DIV4, + LL_LPTIM_PRESCALER_DIV8, + LL_LPTIM_PRESCALER_DIV16, + LL_LPTIM_PRESCALER_DIV32, + LL_LPTIM_PRESCALER_DIV64, + LL_LPTIM_PRESCALER_DIV128, +}; + +void furi_hal_pwm_start(FuriHalPwmOutputId channel, uint32_t freq, uint8_t duty) { + if(channel == FuriHalPwmOutputIdTim1PA7) { + furi_hal_gpio_init_ex( + &gpio_ext_pa7, + GpioModeAltFunctionPushPull, + GpioPullNo, + GpioSpeedVeryHigh, + GpioAltFn1TIM1); + + FURI_CRITICAL_ENTER(); + LL_TIM_DeInit(TIM1); + FURI_CRITICAL_EXIT(); + + LL_TIM_SetCounterMode(TIM1, LL_TIM_COUNTERMODE_UP); + LL_TIM_SetRepetitionCounter(TIM1, 0); + LL_TIM_SetClockDivision(TIM1, LL_TIM_CLOCKDIVISION_DIV1); + LL_TIM_SetClockSource(TIM1, LL_TIM_CLOCKSOURCE_INTERNAL); + LL_TIM_EnableARRPreload(TIM1); + + LL_TIM_OC_EnablePreload(TIM1, LL_TIM_CHANNEL_CH1); + LL_TIM_OC_SetMode(TIM1, LL_TIM_CHANNEL_CH1, LL_TIM_OCMODE_PWM1); + LL_TIM_OC_SetPolarity(TIM1, LL_TIM_CHANNEL_CH1N, LL_TIM_OCPOLARITY_HIGH); + LL_TIM_OC_DisableFast(TIM1, LL_TIM_CHANNEL_CH1); + LL_TIM_CC_EnableChannel(TIM1, LL_TIM_CHANNEL_CH1N); + + LL_TIM_EnableAllOutputs(TIM1); + + furi_hal_pwm_set_params(channel, freq, duty); + + LL_TIM_EnableCounter(TIM1); + } else if(channel == FuriHalPwmOutputIdLptim2PA4) { + furi_hal_gpio_init_ex( + &gpio_ext_pa4, + GpioModeAltFunctionPushPull, + GpioPullNo, + GpioSpeedVeryHigh, + GpioAltFn14LPTIM2); + + FURI_CRITICAL_ENTER(); + LL_LPTIM_DeInit(LPTIM2); + FURI_CRITICAL_EXIT(); + + LL_LPTIM_SetUpdateMode(LPTIM2, LL_LPTIM_UPDATE_MODE_ENDOFPERIOD); + LL_RCC_SetLPTIMClockSource(LL_RCC_LPTIM2_CLKSOURCE_PCLK1); + LL_LPTIM_SetClockSource(LPTIM2, LL_LPTIM_CLK_SOURCE_INTERNAL); + LL_LPTIM_ConfigOutput( + LPTIM2, LL_LPTIM_OUTPUT_WAVEFORM_PWM, LL_LPTIM_OUTPUT_POLARITY_INVERSE); + LL_LPTIM_SetCounterMode(LPTIM2, LL_LPTIM_COUNTER_MODE_INTERNAL); + + LL_LPTIM_Enable(LPTIM2); + + furi_hal_pwm_set_params(channel, freq, duty); + + LL_LPTIM_StartCounter(LPTIM2, LL_LPTIM_OPERATING_MODE_CONTINUOUS); + } +} + +void furi_hal_pwm_stop(FuriHalPwmOutputId channel) { + if(channel == FuriHalPwmOutputIdTim1PA7) { + furi_hal_gpio_init_simple(&gpio_ext_pa7, GpioModeAnalog); + FURI_CRITICAL_ENTER(); + LL_TIM_DeInit(TIM1); + FURI_CRITICAL_EXIT(); + } else if(channel == FuriHalPwmOutputIdLptim2PA4) { + furi_hal_gpio_init_simple(&gpio_ext_pa4, GpioModeAnalog); + FURI_CRITICAL_ENTER(); + LL_LPTIM_DeInit(LPTIM2); + FURI_CRITICAL_EXIT(); + } +} + +void furi_hal_pwm_set_params(FuriHalPwmOutputId channel, uint32_t freq, uint8_t duty) { + furi_assert(freq > 0); + uint32_t freq_div = 64000000LU / freq; + + if(channel == FuriHalPwmOutputIdTim1PA7) { + uint32_t prescaler = freq_div / 0x10000LU; + uint32_t period = freq_div / (prescaler + 1); + uint32_t compare = period * duty / 100; + + LL_TIM_SetPrescaler(TIM1, prescaler); + LL_TIM_SetAutoReload(TIM1, period - 1); + LL_TIM_OC_SetCompareCH1(TIM1, compare); + } else if(channel == FuriHalPwmOutputIdLptim2PA4) { + uint32_t prescaler = 0; + uint32_t period = 0; + + bool clock_lse = false; + + do { + period = freq_div / (1 << prescaler); + if(period <= 0xFFFF) { + break; + } + prescaler++; + if(prescaler > 7) { + prescaler = 0; + clock_lse = true; + period = 32768LU / freq; + break; + } + } while(1); + + uint32_t compare = period * duty / 100; + + LL_LPTIM_SetPrescaler(LPTIM2, lptim_psc_table[prescaler]); + LL_LPTIM_SetAutoReload(LPTIM2, period); + LL_LPTIM_SetCompare(LPTIM2, compare); + + if(clock_lse) { + LL_RCC_SetLPTIMClockSource(LL_RCC_LPTIM2_CLKSOURCE_LSE); + } else { + LL_RCC_SetLPTIMClockSource(LL_RCC_LPTIM2_CLKSOURCE_PCLK1); + } + } +} diff --git a/firmware/targets/f7/furi_hal/furi_hal_pwm.h b/firmware/targets/f7/furi_hal/furi_hal_pwm.h new file mode 100644 index 00000000000..a8682c5fbb1 --- /dev/null +++ b/firmware/targets/f7/furi_hal/furi_hal_pwm.h @@ -0,0 +1,42 @@ +/** + * @file furi_hal_pwm.h + * PWM contol HAL + */ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +typedef enum { + FuriHalPwmOutputIdTim1PA7, + FuriHalPwmOutputIdLptim2PA4, +} FuriHalPwmOutputId; + +/** Enable PWM channel and set parameters + * + * @param[in] channel PWM channel (FuriHalPwmOutputId) + * @param[in] freq Frequency in Hz + * @param[in] duty Duty cycle value in % +*/ +void furi_hal_pwm_start(FuriHalPwmOutputId channel, uint32_t freq, uint8_t duty); + +/** Disable PWM channel + * + * @param[in] channel PWM channel (FuriHalPwmOutputId) +*/ +void furi_hal_pwm_stop(FuriHalPwmOutputId channel); + +/** Set PWM channel parameters + * + * @param[in] channel PWM channel (FuriHalPwmOutputId) + * @param[in] freq Frequency in Hz + * @param[in] duty Duty cycle value in % +*/ +void furi_hal_pwm_set_params(FuriHalPwmOutputId channel, uint32_t freq, uint8_t duty); + +#ifdef __cplusplus +} +#endif From e25b4241881cabeb6dff957303179cc0ba254cf6 Mon Sep 17 00:00:00 2001 From: Vyacheslav Tumanov Date: Wed, 28 Sep 2022 21:52:46 +0500 Subject: [PATCH 091/824] Typos fix in some strings/comments #1794 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- applications/services/gui/canvas.h | 6 +++--- applications/services/gui/modules/button_panel.h | 2 +- documentation/AppManifests.md | 2 +- scripts/assets.py | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/applications/services/gui/canvas.h b/applications/services/gui/canvas.h index 4923e2e6618..49bbd7d6875 100644 --- a/applications/services/gui/canvas.h +++ b/applications/services/gui/canvas.h @@ -61,7 +61,7 @@ typedef struct { uint8_t descender; } CanvasFontParameters; -/** Canvas anonymouse structure */ +/** Canvas anonymous structure */ typedef struct Canvas Canvas; /** Get Canvas width @@ -297,7 +297,7 @@ void canvas_draw_disc(Canvas* canvas, uint8_t x, uint8_t y, uint8_t r); * @param y y coordinate of base and height intersection * @param base length of triangle side * @param height length of triangle height - * @param dir CanvasDirection triangle orientaion + * @param dir CanvasDirection triangle orientation */ void canvas_draw_triangle( Canvas* canvas, @@ -323,7 +323,7 @@ void canvas_draw_glyph(Canvas* canvas, uint8_t x, uint8_t y, uint16_t ch); */ void canvas_set_bitmap_mode(Canvas* canvas, bool alpha); -/** Draw rounded-corner frame of width, height at x,y, with round value raduis +/** Draw rounded-corner frame of width, height at x,y, with round value radius * * @param canvas Canvas instance * @param x x coordinate diff --git a/applications/services/gui/modules/button_panel.h b/applications/services/gui/modules/button_panel.h index 0c17e3a7c6a..f3b0bae703c 100644 --- a/applications/services/gui/modules/button_panel.h +++ b/applications/services/gui/modules/button_panel.h @@ -53,7 +53,7 @@ void button_panel_reserve(ButtonPanel* button_panel, size_t reserve_x, size_t re * @param button_panel ButtonPanel instance * @param index value to pass to callback * @param matrix_place_x coordinates by x-axis on virtual grid, it - * is only used for naviagation + * is only used for navigation * @param matrix_place_y coordinates by y-axis on virtual grid, it * is only used for naviagation * @param x x-coordinate to draw icon on diff --git a/documentation/AppManifests.md b/documentation/AppManifests.md index 7bc8d0a477b..5b14b7ddb67 100644 --- a/documentation/AppManifests.md +++ b/documentation/AppManifests.md @@ -48,7 +48,7 @@ The following parameters are used only for [FAPs](./AppsOnSDCard.md): * **fap_icon**: name of a .png file, 1-bit color depth, 10x10px, to be embedded within .fap file. * **fap_libs**: list of extra libraries to link application against. Provides access to extra functions that are not exported as a part of main firmware at expense of increased .fap file size and RAM consumption. * **fap_category**: string, may be empty. App subcategory, also works as path of FAP within apps folder in the file system. -* **fap_description**: string, may be empty. Short application descriotion. +* **fap_description**: string, may be empty. Short application description. * **fap_author**: string, may be empty. Application's author. * **fap_weburl**: string, may be empty. Application's homepage. diff --git a/scripts/assets.py b/scripts/assets.py index 1f36a135b74..6f94101377f 100755 --- a/scripts/assets.py +++ b/scripts/assets.py @@ -111,7 +111,7 @@ def icons(self): if not filenames: continue if "frame_rate" in filenames: - self.logger.debug(f"Folder contatins animation") + self.logger.debug(f"Folder contains animation") icon_name = "A_" + os.path.split(dirpath)[1].replace("-", "_") width = height = None frame_count = 0 From f8b532f06317298566b730a4c6bc17dd6ee35f4e Mon Sep 17 00:00:00 2001 From: hedger Date: Wed, 28 Sep 2022 21:13:12 +0400 Subject: [PATCH 092/824] [FL-2831] Resources cleanup in updater (#1796) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * updater: remove files from existing resources Manifest file before deploying new resources * toolbox: tar: single file extraction API Co-authored-by: あく --- .../system/updater/util/update_task.h | 2 +- .../updater/util/update_task_worker_backup.c | 41 ++++++- .../updater/util/update_task_worker_flasher.c | 2 +- firmware/targets/f7/api_symbols.csv | 1 + .../targets/f7/furi_hal/furi_hal_cortex.c | 3 + lib/toolbox/tar/tar_archive.c | 98 ++++++++------- lib/toolbox/tar/tar_archive.h | 5 + lib/update_util/resources/manifest.c | 115 ++++++++++++++++++ lib/update_util/resources/manifest.h | 58 +++++++++ 9 files changed, 280 insertions(+), 45 deletions(-) create mode 100644 lib/update_util/resources/manifest.c create mode 100644 lib/update_util/resources/manifest.h diff --git a/applications/system/updater/util/update_task.h b/applications/system/updater/util/update_task.h index 1f291556852..ad8c50857d1 100644 --- a/applications/system/updater/util/update_task.h +++ b/applications/system/updater/util/update_task.h @@ -10,7 +10,7 @@ extern "C" { #include #include -#define UPDATE_DELAY_OPERATION_OK 300 +#define UPDATE_DELAY_OPERATION_OK 10 #define UPDATE_DELAY_OPERATION_ERROR INT_MAX typedef enum { diff --git a/applications/system/updater/util/update_task_worker_backup.c b/applications/system/updater/util/update_task_worker_backup.c index 09e4595333c..046be372dc5 100644 --- a/applications/system/updater/util/update_task_worker_backup.c +++ b/applications/system/updater/util/update_task_worker_backup.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -50,10 +51,46 @@ static bool update_task_resource_unpack_cb(const char* name, bool is_directory, update_task_set_progress( unpack_progress->update_task, UpdateTaskStageProgress, - unpack_progress->processed_files * 100 / (unpack_progress->total_files + 1)); + /* For this stage, last 70% of progress = extraction */ + 30 + (unpack_progress->processed_files * 70) / (unpack_progress->total_files + 1)); return true; } +static void + update_task_cleanup_resources(UpdateTask* update_task, uint32_t n_approx_file_entries) { + ResourceManifestReader* manifest_reader = resource_manifest_reader_alloc(update_task->storage); + do { + FURI_LOG_I(TAG, "Cleaning up old manifest"); + if(!resource_manifest_reader_open(manifest_reader, EXT_PATH("Manifest"))) { + FURI_LOG_W(TAG, "No existing manifest"); + break; + } + + /* We got # of entries in TAR file. Approx 1/4th is dir entries, we skip them */ + n_approx_file_entries = n_approx_file_entries * 3 / 4 + 1; + uint32_t n_processed_files = 0; + + ResourceManifestEntry* entry_ptr = NULL; + while((entry_ptr = resource_manifest_reader_next(manifest_reader))) { + if(entry_ptr->type == ResourceManifestEntryTypeFile) { + update_task_set_progress( + update_task, + UpdateTaskStageProgress, + /* For this stage, first 30% of progress = cleanup */ + (n_processed_files++ * 30) / (n_approx_file_entries + 1)); + + string_t file_path; + string_init(file_path); + path_concat(STORAGE_EXT_PATH_PREFIX, string_get_cstr(entry_ptr->name), file_path); + FURI_LOG_D(TAG, "Removing %s", string_get_cstr(file_path)); + storage_simply_remove(update_task->storage, string_get_cstr(file_path)); + string_clear(file_path); + } + } + } while(false); + resource_manifest_reader_free(manifest_reader); +} + static bool update_task_post_update(UpdateTask* update_task) { bool success = false; @@ -88,6 +125,8 @@ static bool update_task_post_update(UpdateTask* update_task) { progress.total_files = tar_archive_get_entries_count(archive); if(progress.total_files > 0) { + update_task_cleanup_resources(update_task, progress.total_files); + CHECK_RESULT(tar_archive_unpack_to(archive, STORAGE_EXT_PATH_PREFIX, NULL)); } } diff --git a/applications/system/updater/util/update_task_worker_flasher.c b/applications/system/updater/util/update_task_worker_flasher.c index 7b598c50b10..b235d0018f8 100644 --- a/applications/system/updater/util/update_task_worker_flasher.c +++ b/applications/system/updater/util/update_task_worker_flasher.c @@ -308,7 +308,7 @@ bool update_task_validate_optionbytes(UpdateTask* update_task) { } } } else { - FURI_LOG_I( + FURI_LOG_D( TAG, "OB MATCH: #%d: real %08X == %08X (exp.)", idx, diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 18550984d2b..2cbdae77c13 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -2269,6 +2269,7 @@ Function,+,tar_archive_get_entries_count,int32_t,TarArchive* Function,+,tar_archive_open,_Bool,"TarArchive*, const char*, TarOpenMode" Function,+,tar_archive_set_file_callback,void,"TarArchive*, tar_unpack_file_cb, void*" Function,+,tar_archive_store_data,_Bool,"TarArchive*, const char*, const uint8_t*, const int32_t" +Function,+,tar_archive_unpack_file,_Bool,"TarArchive*, const char*, const char*" Function,+,tar_archive_unpack_to,_Bool,"TarArchive*, const char*, Storage_name_converter" Function,-,tempnam,char*,"const char*, const char*" Function,+,text_box_alloc,TextBox*, diff --git a/firmware/targets/f7/furi_hal/furi_hal_cortex.c b/firmware/targets/f7/furi_hal/furi_hal_cortex.c index 2b4ea6e99b2..c9c8400a748 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_cortex.c +++ b/firmware/targets/f7/furi_hal/furi_hal_cortex.c @@ -6,6 +6,9 @@ void furi_hal_cortex_init_early() { CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; DWT->CYCCNT = 0U; + + /* Enable instruction prefetch */ + SET_BIT(FLASH->ACR, FLASH_ACR_PRFTEN); } void furi_hal_cortex_delay_us(uint32_t microseconds) { diff --git a/lib/toolbox/tar/tar_archive.c b/lib/toolbox/tar/tar_archive.c index f51d6231786..1a542b2e0da 100644 --- a/lib/toolbox/tar/tar_archive.c +++ b/lib/toolbox/tar/tar_archive.c @@ -168,7 +168,44 @@ typedef struct { Storage_name_converter converter; } TarArchiveDirectoryOpParams; +static bool archive_extract_current_file(TarArchive* archive, const char* dst_path) { + mtar_t* tar = &archive->tar; + File* out_file = storage_file_alloc(archive->storage); + uint8_t* readbuf = malloc(FILE_BLOCK_SIZE); + + bool success = true; + uint8_t n_tries = FILE_OPEN_NTRIES; + do { + while(n_tries-- > 0) { + if(storage_file_open(out_file, dst_path, FSAM_WRITE, FSOM_CREATE_ALWAYS)) { + break; + } + FURI_LOG_W(TAG, "Failed to open '%s', reties: %d", dst_path, n_tries); + storage_file_close(out_file); + furi_delay_ms(FILE_OPEN_RETRY_DELAY); + } + + if(!storage_file_is_open(out_file)) { + success = false; + break; + } + + while(!mtar_eof_data(tar)) { + int32_t readcnt = mtar_read_data(tar, readbuf, FILE_BLOCK_SIZE); + if(!readcnt || !storage_file_write(out_file, readbuf, readcnt)) { + success = false; + break; + } + } + } while(false); + storage_file_free(out_file); + free(readbuf); + + return success; +} + static int archive_extract_foreach_cb(mtar_t* tar, const mtar_header_t* header, void* param) { + UNUSED(tar); TarArchiveDirectoryOpParams* op_params = param; TarArchive* archive = op_params->archive; @@ -199,58 +236,22 @@ static int archive_extract_foreach_cb(mtar_t* tar, const mtar_header_t* header, return 0; } - string_init(full_extracted_fname); + FURI_LOG_D(TAG, "Extracting %d bytes to '%s'", header->size, header->name); string_t converted_fname; string_init_set(converted_fname, header->name); if(op_params->converter) { op_params->converter(converted_fname); } - path_concat(op_params->work_dir, string_get_cstr(converted_fname), full_extracted_fname); - string_clear(converted_fname); - FURI_LOG_D(TAG, "Extracting %d bytes to '%s'", header->size, header->name); - File* out_file = storage_file_alloc(archive->storage); - uint8_t* readbuf = malloc(FILE_BLOCK_SIZE); - - bool failed = false; - uint8_t n_tries = FILE_OPEN_NTRIES; - do { - while(n_tries-- > 0) { - if(storage_file_open( - out_file, - string_get_cstr(full_extracted_fname), - FSAM_WRITE, - FSOM_CREATE_ALWAYS)) { - break; - } - FURI_LOG_W( - TAG, - "Failed to open '%s', reties: %d", - string_get_cstr(full_extracted_fname), - n_tries); - storage_file_close(out_file); - furi_delay_ms(FILE_OPEN_RETRY_DELAY); - } - - if(!storage_file_is_open(out_file)) { - failed = true; - break; - } + string_init(full_extracted_fname); + path_concat(op_params->work_dir, string_get_cstr(converted_fname), full_extracted_fname); - while(!mtar_eof_data(tar)) { - int32_t readcnt = mtar_read_data(tar, readbuf, FILE_BLOCK_SIZE); - if(!readcnt || !storage_file_write(out_file, readbuf, readcnt)) { - failed = true; - break; - } - } - } while(false); + bool success = archive_extract_current_file(archive, string_get_cstr(full_extracted_fname)); - storage_file_free(out_file); - free(readbuf); + string_clear(converted_fname); string_clear(full_extracted_fname); - return failed ? -1 : 0; + return success ? 0 : -1; } bool tar_archive_unpack_to( @@ -369,3 +370,16 @@ bool tar_archive_add_dir(TarArchive* archive, const char* fs_full_path, const ch storage_file_free(directory); return success; } + +bool tar_archive_unpack_file( + TarArchive* archive, + const char* archive_fname, + const char* destination) { + furi_assert(archive); + furi_assert(archive_fname); + furi_assert(destination); + if(mtar_find(&archive->tar, archive_fname) != MTAR_ESUCCESS) { + return false; + } + return archive_extract_current_file(archive, destination); +} \ No newline at end of file diff --git a/lib/toolbox/tar/tar_archive.h b/lib/toolbox/tar/tar_archive.h index 88cb3dd4d96..ceaf82ee914 100644 --- a/lib/toolbox/tar/tar_archive.h +++ b/lib/toolbox/tar/tar_archive.h @@ -41,6 +41,11 @@ bool tar_archive_add_dir(TarArchive* archive, const char* fs_full_path, const ch int32_t tar_archive_get_entries_count(TarArchive* archive); +bool tar_archive_unpack_file( + TarArchive* archive, + const char* archive_fname, + const char* destination); + /* Optional per-entry callback on unpacking - return false to skip entry */ typedef bool (*tar_unpack_file_cb)(const char* name, bool is_directory, void* context); diff --git a/lib/update_util/resources/manifest.c b/lib/update_util/resources/manifest.c new file mode 100644 index 00000000000..4f8a7d1abde --- /dev/null +++ b/lib/update_util/resources/manifest.c @@ -0,0 +1,115 @@ +#include "manifest.h" + +#include +#include + +struct ResourceManifestReader { + Storage* storage; + Stream* stream; + string_t linebuf; + ResourceManifestEntry entry; +}; + +ResourceManifestReader* resource_manifest_reader_alloc(Storage* storage) { + ResourceManifestReader* resource_manifest = + (ResourceManifestReader*)malloc(sizeof(ResourceManifestReader)); + resource_manifest->storage = storage; + resource_manifest->stream = buffered_file_stream_alloc(resource_manifest->storage); + memset(&resource_manifest->entry, 0, sizeof(ResourceManifestEntry)); + string_init(resource_manifest->entry.name); + string_init(resource_manifest->linebuf); + return resource_manifest; +} + +void resource_manifest_reader_free(ResourceManifestReader* resource_manifest) { + furi_assert(resource_manifest); + + string_clear(resource_manifest->linebuf); + string_clear(resource_manifest->entry.name); + buffered_file_stream_close(resource_manifest->stream); + stream_free(resource_manifest->stream); + free(resource_manifest); +} + +bool resource_manifest_reader_open(ResourceManifestReader* resource_manifest, const char* filename) { + furi_assert(resource_manifest); + + return buffered_file_stream_open( + resource_manifest->stream, filename, FSAM_READ, FSOM_OPEN_EXISTING); +} + +/* Read entries in format of + * F::: + * D: + */ +ResourceManifestEntry* resource_manifest_reader_next(ResourceManifestReader* resource_manifest) { + furi_assert(resource_manifest); + + string_reset(resource_manifest->entry.name); + resource_manifest->entry.type = ResourceManifestEntryTypeUnknown; + resource_manifest->entry.size = 0; + memset(resource_manifest->entry.hash, 0, sizeof(resource_manifest->entry.hash)); + + do { + if(!stream_read_line(resource_manifest->stream, resource_manifest->linebuf)) { + return NULL; + } + + /* Trim end of line */ + string_strim(resource_manifest->linebuf); + + char type_code = string_get_char(resource_manifest->linebuf, 0); + switch(type_code) { + case 'F': + resource_manifest->entry.type = ResourceManifestEntryTypeFile; + break; + case 'D': + resource_manifest->entry.type = ResourceManifestEntryTypeDirectory; + break; + default: /* Skip other entries - version, timestamp, etc */ + continue; + }; + + if(resource_manifest->entry.type == ResourceManifestEntryTypeFile) { + /* Parse file entry + F::: */ + + /* Remove entry type code */ + string_right(resource_manifest->linebuf, 2); + + if(string_search_char(resource_manifest->linebuf, ':') != + sizeof(resource_manifest->entry.hash) * 2) { + /* Invalid hash */ + continue; + } + + /* Read hash */ + hex_chars_to_uint8( + string_get_cstr(resource_manifest->linebuf), resource_manifest->entry.hash); + + /* Remove hash */ + string_right( + resource_manifest->linebuf, sizeof(resource_manifest->entry.hash) * 2 + 1); + + resource_manifest->entry.size = atoi(string_get_cstr(resource_manifest->linebuf)); + + /* Remove size */ + size_t offs = string_search_char(resource_manifest->linebuf, ':'); + string_right(resource_manifest->linebuf, offs + 1); + + string_set(resource_manifest->entry.name, resource_manifest->linebuf); + } else if(resource_manifest->entry.type == ResourceManifestEntryTypeDirectory) { + /* Parse directory entry + D: */ + + /* Remove entry type code */ + string_right(resource_manifest->linebuf, 2); + + string_set(resource_manifest->entry.name, resource_manifest->linebuf); + } + + return &resource_manifest->entry; + } while(true); + + return NULL; +} diff --git a/lib/update_util/resources/manifest.h b/lib/update_util/resources/manifest.h new file mode 100644 index 00000000000..092b7badb04 --- /dev/null +++ b/lib/update_util/resources/manifest.h @@ -0,0 +1,58 @@ +#pragma once + +#include + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + ResourceManifestEntryTypeUnknown = 0, + ResourceManifestEntryTypeDirectory, + ResourceManifestEntryTypeFile, +} ResourceManifestEntryType; + +typedef struct { + ResourceManifestEntryType type; + string_t name; + uint32_t size; + uint8_t hash[16]; +} ResourceManifestEntry; + +typedef struct ResourceManifestReader ResourceManifestReader; + +/** + * @brief Initialize resource manifest reader + * @param storage Storage API pointer + * @return allocated object + */ +ResourceManifestReader* resource_manifest_reader_alloc(Storage* storage); + +/** + * @brief Release resource manifest reader + * @param resource_manifest allocated object + */ +void resource_manifest_reader_free(ResourceManifestReader* resource_manifest); + +/** + * @brief Initialize resource manifest reader iteration + * @param resource_manifest allocated object + * @param filename manifest file name + * @return true if file opened + */ +bool resource_manifest_reader_open(ResourceManifestReader* resource_manifest, const char* filename); + +/** + * @brief Read next file/dir entry from manifest + * @param resource_manifest allocated object + * @return entry or NULL if end of file + */ +ResourceManifestEntry* resource_manifest_reader_next(ResourceManifestReader* resource_manifest); + +#ifdef __cplusplus +} // extern "C" +#endif \ No newline at end of file From 5883e134d4c870a2b08f705eb48a2e56f8a24a23 Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Thu, 29 Sep 2022 03:37:07 +1000 Subject: [PATCH 093/824] Furi Thread: don't use thread pointer after FuriThreadStateStopped callback (#1799) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Furi Thread: correct furi_thread_join, do not use thread pointer after FuriThreadStateStopped callback * Furi: a little bit easier way to do harakiri * Furi: crash on thread self join attempt Co-authored-by: あく --- furi/core/thread.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/furi/core/thread.c b/furi/core/thread.c index 58cb9bc0991..538ae4d87db 100644 --- a/furi/core/thread.c +++ b/furi/core/thread.c @@ -85,7 +85,6 @@ static void furi_thread_body(void* context) { } furi_assert(thread->state == FuriThreadStateRunning); - furi_thread_set_state(thread, FuriThreadStateStopped); if(thread->is_service) { FURI_LOG_E( @@ -99,7 +98,10 @@ static void furi_thread_body(void* context) { furi_assert(pvTaskGetThreadLocalStoragePointer(NULL, 0) != NULL); vTaskSetThreadLocalStoragePointer(NULL, 0, NULL); - vTaskDelete(thread->task_handle); + // from here we can't use thread pointer + furi_thread_set_state(thread, FuriThreadStateStopped); + + vTaskDelete(NULL); furi_thread_catch(); } @@ -205,7 +207,9 @@ void furi_thread_start(FuriThread* thread) { bool furi_thread_join(FuriThread* thread) { furi_assert(thread); - while(thread->state != FuriThreadStateStopped) { + furi_check(furi_thread_get_current() != thread); + + while(eTaskGetState(thread->task_handle) != eDeleted) { furi_delay_ms(10); } From bcfb12bf28ab8784732a76cb0400318de7a12585 Mon Sep 17 00:00:00 2001 From: Mewa Date: Wed, 28 Sep 2022 19:44:24 +0200 Subject: [PATCH 094/824] Keyboard: show Uppercase keys when replacing content (#1548) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- applications/services/gui/modules/text_input.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/applications/services/gui/modules/text_input.c b/applications/services/gui/modules/text_input.c index 58d7ecab0d0..b2aba03fc03 100644 --- a/applications/services/gui/modules/text_input.c +++ b/applications/services/gui/modules/text_input.c @@ -232,7 +232,8 @@ static void text_input_view_draw_callback(Canvas* canvas, void* _model) { canvas_set_color(canvas, ColorBlack); } - if(text_length == 0 && char_is_lowercase(keys[column].text)) { + if(model->clear_default_text || + (text_length == 0 && char_is_lowercase(keys[column].text))) { canvas_draw_glyph( canvas, keyboard_origin_x + keys[column].x, From aba20b6af8eb15e23ff544cf29dfa77207695aea Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Thu, 29 Sep 2022 20:42:15 +1000 Subject: [PATCH 095/824] Furi Thread: fixed furi_thread_join, check if thread has not been started (#1803) * furi thread: fixed furi_thread_join, check if thread has not been started * Furi: correct returns in furi_thread_join Co-authored-by: Aleksandr Kutuzov --- furi/core/thread.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/furi/core/thread.c b/furi/core/thread.c index 538ae4d87db..d326e389b33 100644 --- a/furi/core/thread.c +++ b/furi/core/thread.c @@ -209,11 +209,17 @@ bool furi_thread_join(FuriThread* thread) { furi_check(furi_thread_get_current() != thread); + // Check if thread was started + if(thread->task_handle == NULL) { + return false; + } + + // Wait for thread to stop while(eTaskGetState(thread->task_handle) != eDeleted) { furi_delay_ms(10); } - return FuriStatusOk; + return true; } FuriThreadId furi_thread_get_id(FuriThread* thread) { From 76d38e832e2da86457d0114beb20b88b6dc24221 Mon Sep 17 00:00:00 2001 From: hedger Date: Thu, 29 Sep 2022 15:00:22 +0400 Subject: [PATCH 096/824] fbt: reproducible manifest builds & improvements (#1801) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fbt: reproducible manifest builds, less rebuild on small updates; scripts: assets: using timestamp from commandline af available * fbt: added app import validation for launch_app & single app build targets * fbt: COMSTR for app imports validation * docs: minor fixes * docs: markdown fix * vscode: comments for RTOS startup Co-authored-by: あく --- .vscode/example/launch.json | 3 ++- assets/SConscript | 6 +++++- documentation/AppManifests.md | 4 ++-- firmware.scons | 1 + scripts/assets.py | 9 ++++++++- scripts/flipper/assets/manifest.py | 4 ++-- site_scons/extapps.scons | 3 ++- site_scons/fbt/appmanifest.py | 2 +- site_scons/fbt/version.py | 5 +++++ site_scons/site_tools/fbt_extapps.py | 21 +++++++++++++++++---- 10 files changed, 45 insertions(+), 13 deletions(-) diff --git a/.vscode/example/launch.json b/.vscode/example/launch.json index 7cb2542de3e..f4df9659275 100644 --- a/.vscode/example/launch.json +++ b/.vscode/example/launch.json @@ -28,13 +28,14 @@ "servertype": "openocd", "device": "stlink", "svdFile": "./debug/STM32WB55_CM4.svd", + // If you're debugging early in the boot process, before OS scheduler is running, + // you have to comment out the following line. "rtos": "FreeRTOS", "configFiles": [ "interface/stlink.cfg", "./debug/stm32wbx.cfg", ], "postAttachCommands": [ - // "attach 1", // "compare-sections", "source debug/flipperapps.py", // "source debug/FreeRTOS/FreeRTOS.py", diff --git a/assets/SConscript b/assets/SConscript index 47713d1a609..a0b3b13abdb 100644 --- a/assets/SConscript +++ b/assets/SConscript @@ -1,8 +1,11 @@ Import("env") +from fbt.version import get_git_commit_unix_timestamp + assetsenv = env.Clone( tools=["fbt_assets"], FW_LIB_NAME="assets", + GIT_UNIX_TIMESTAMP=get_git_commit_unix_timestamp(), ) assetsenv.ApplyLibFlags() @@ -90,10 +93,11 @@ if assetsenv["IS_BASE_FIRMWARE"]: "#/assets/resources/Manifest", assetsenv.GlobRecursive("*", "resources", exclude="Manifest"), action=Action( - '${PYTHON3} "${ASSETS_COMPILER}" manifest "${TARGET.dir.posix}"', + '${PYTHON3} "${ASSETS_COMPILER}" manifest "${TARGET.dir.posix}" --timestamp=${GIT_UNIX_TIMESTAMP}', "${RESMANIFESTCOMSTR}", ), ) + assetsenv.Precious(resources) assetsenv.AlwaysBuild(resources) assetsenv.Clean( resources, diff --git a/documentation/AppManifests.md b/documentation/AppManifests.md index 5b14b7ddb67..0a4f5e9b778 100644 --- a/documentation/AppManifests.md +++ b/documentation/AppManifests.md @@ -43,8 +43,8 @@ Only 2 parameters are mandatory: ***appid*** and ***apptype***, others are optio The following parameters are used only for [FAPs](./AppsOnSDCard.md): -* **sources**: list of file name masks, used for gathering sources within app folder. Default value of ["\*.c\*"] includes C and CPP source files. -* **fap_version**: string, 2 numbers in form of "x.y": application version to be embedded within .fap file. +* **sources**: list of strings, file name masks, used for gathering sources within app folder. Default value of `["*.c*"]` includes C and CPP source files. +* **fap_version**: tuple, 2 numbers in form of (x,y): application version to be embedded within .fap file. Default value is (0,1), meanig version "0.1". * **fap_icon**: name of a .png file, 1-bit color depth, 10x10px, to be embedded within .fap file. * **fap_libs**: list of extra libraries to link application against. Provides access to extra functions that are not exported as a part of main firmware at expense of increased .fap file size and RAM consumption. * **fap_category**: string, may be empty. App subcategory, also works as path of FAP within apps folder in the file system. diff --git a/firmware.scons b/firmware.scons index 530634ef261..0970541e825 100644 --- a/firmware.scons +++ b/firmware.scons @@ -96,6 +96,7 @@ if not env["VERBOSE"]: SDKSYM_GENERATOR_COMSTR="\tSDKSYM\t${TARGET}", APPMETA_COMSTR="\tAPPMETA\t${TARGET}", APPMETAEMBED_COMSTR="\tFAP\t${TARGET}", + APPCHECK_COMSTR="\tAPPCHK\t${SOURCE}", ) diff --git a/scripts/assets.py b/scripts/assets.py index 6f94101377f..9b4ee5b61f7 100755 --- a/scripts/assets.py +++ b/scripts/assets.py @@ -39,6 +39,13 @@ def init(self): "manifest", help="Create directory Manifest" ) self.parser_manifest.add_argument("local_path", help="local_path") + self.parser_manifest.add_argument( + "--timestamp", + help="timestamp value to embed", + default=0, + type=int, + required=False, + ) self.parser_manifest.set_defaults(func=self.manifest) self.parser_copro = self.subparsers.add_parser( @@ -213,7 +220,7 @@ def manifest(self): self.logger.info( f'Creating temporary Manifest for directory "{directory_path}"' ) - new_manifest = Manifest() + new_manifest = Manifest(self.args.timestamp) new_manifest.create(directory_path) self.logger.info(f"Comparing new manifest with existing") diff --git a/scripts/flipper/assets/manifest.py b/scripts/flipper/assets/manifest.py index 840761d729d..a8f6855a476 100644 --- a/scripts/flipper/assets/manifest.py +++ b/scripts/flipper/assets/manifest.py @@ -106,11 +106,11 @@ def toLine(self): class Manifest: - def __init__(self): + def __init__(self, timestamp_value=None): self.version = None self.records = [] self.records.append(ManifestRecordVersion(MANIFEST_VERSION)) - self.records.append(ManifestRecordTimestamp(timestamp())) + self.records.append(ManifestRecordTimestamp(timestamp_value or timestamp())) self.logger = logging.getLogger(self.__class__.__name__) def load(self, filename): diff --git a/site_scons/extapps.scons b/site_scons/extapps.scons index 4cb5e35cfd4..66002915f01 100644 --- a/site_scons/extapps.scons +++ b/site_scons/extapps.scons @@ -86,12 +86,13 @@ if appenv["FORCE"]: Alias(appenv["FIRMWARE_BUILD_CFG"] + "_extapps", extapps["compact"].values()) if appsrc := appenv.subst("$APPSRC"): - app_manifest, fap_file = appenv.GetExtAppFromPath(appsrc) + app_manifest, fap_file, app_validator = appenv.GetExtAppFromPath(appsrc) appenv.PhonyTarget( "launch_app", '${PYTHON3} scripts/runfap.py ${SOURCE} --fap_dst_dir "/ext/apps/${FAP_CATEGORY}"', source=fap_file, FAP_CATEGORY=app_manifest.fap_category, ) + appenv.Alias("launch_app", app_validator) Return("extapps") diff --git a/site_scons/fbt/appmanifest.py b/site_scons/fbt/appmanifest.py index a0ab5e7c6c4..a1132eeaba0 100644 --- a/site_scons/fbt/appmanifest.py +++ b/site_scons/fbt/appmanifest.py @@ -38,7 +38,7 @@ class FlipperApplication: sdk_headers: List[str] = field(default_factory=list) # .fap-specific sources: List[str] = field(default_factory=lambda: ["*.c*"]) - fap_version: Tuple[int] = field(default_factory=lambda: (0, 0)) + fap_version: Tuple[int] = field(default_factory=lambda: (0, 1)) fap_icon: Optional[str] = None fap_libs: List[str] = field(default_factory=list) fap_category: str = "" diff --git a/site_scons/fbt/version.py b/site_scons/fbt/version.py index 1b1c166f28b..e7fe2edaf64 100644 --- a/site_scons/fbt/version.py +++ b/site_scons/fbt/version.py @@ -3,6 +3,11 @@ from functools import cache +@cache +def get_git_commit_unix_timestamp(): + return int(subprocess.check_output(["git", "show", "-s", "--format=%ct"])) + + @cache def get_fast_git_version_id(): try: diff --git a/site_scons/site_tools/fbt_extapps.py b/site_scons/site_tools/fbt_extapps.py index 4b584530525..fec2407105d 100644 --- a/site_scons/site_tools/fbt_extapps.py +++ b/site_scons/site_tools/fbt_extapps.py @@ -37,7 +37,15 @@ def BuildAppElf(env, app): APP=app, ) - env.Depends(app_elf_augmented, [env["SDK_DEFINITION"], env.Value(app)]) + manifest_vals = vars(app) + manifest_vals = { + k: v for k, v in manifest_vals.items() if k not in ("_appdir", "_apppath") + } + + env.Depends( + app_elf_augmented, + [env["SDK_DEFINITION"], env.Value(manifest_vals)], + ) if app.fap_icon: env.Depends( app_elf_augmented, @@ -47,6 +55,7 @@ def BuildAppElf(env, app): app_elf_import_validator = env.ValidateAppImports(app_elf_augmented) env.AlwaysBuild(app_elf_import_validator) + env.Alias(app_alias, app_elf_import_validator) return (app_elf_augmented, app_elf_raw, app_elf_import_validator) @@ -100,9 +109,13 @@ def GetExtAppFromPath(env, app_dir): app_elf = env["_extapps"]["compact"].get(app.appid, None) if not app_elf: - raise UserError(f"No external app found for {app.appid}") + raise UserError( + f"Application {app.appid} is not configured for building as external" + ) + + app_validator = env["_extapps"]["validators"].get(app.appid, None) - return (app, app_elf[0]) + return (app, app_elf[0], app_validator[0]) def generate(env, **kw): @@ -138,7 +151,7 @@ def generate(env, **kw): ), Action( validate_app_imports, - None, # "$APPCHECK_COMSTR", + "$APPCHECK_COMSTR", ), ], suffix=".impsyms", From c92217a1099b4a1e573c2dafe81cee9ea0c1951e Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Fri, 30 Sep 2022 20:59:11 +1000 Subject: [PATCH 097/824] Thread: Clear TLS after thread stop (#1807) --- furi/core/thread.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/furi/core/thread.c b/furi/core/thread.c index d326e389b33..d06fa8b3679 100644 --- a/furi/core/thread.c +++ b/furi/core/thread.c @@ -93,14 +93,16 @@ static void furi_thread_body(void* context) { thread->name ? thread->name : ""); } - // clear thread local storage + // flush stdout __furi_thread_stdout_flush(thread); - furi_assert(pvTaskGetThreadLocalStoragePointer(NULL, 0) != NULL); - vTaskSetThreadLocalStoragePointer(NULL, 0, NULL); // from here we can't use thread pointer furi_thread_set_state(thread, FuriThreadStateStopped); + // clear thread local storage + furi_assert(pvTaskGetThreadLocalStoragePointer(NULL, 0) != NULL); + vTaskSetThreadLocalStoragePointer(NULL, 0, NULL); + vTaskDelete(NULL); furi_thread_catch(); } From 836de3df16ca834f985ae6672e6bb318e5d1dd23 Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Fri, 30 Sep 2022 15:56:12 +0300 Subject: [PATCH 098/824] [FL-2825] iButton GUI fixes (#1805) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Ibutton GUI fixes * Fix memory leak in lfRFID write Co-authored-by: あく --- .../scenes/ibutton_scene_delete_confirm.c | 10 ++-- .../ibutton/scenes/ibutton_scene_emulate.c | 45 +++++------------- .../main/ibutton/scenes/ibutton_scene_info.c | 12 ++--- .../scenes/ibutton_scene_save_success.c | 6 +-- .../main/ibutton/scenes/ibutton_scene_write.c | 46 +++++-------------- .../main/lfrfid/scenes/lfrfid_scene_write.c | 2 - 6 files changed, 36 insertions(+), 85 deletions(-) diff --git a/applications/main/ibutton/scenes/ibutton_scene_delete_confirm.c b/applications/main/ibutton/scenes/ibutton_scene_delete_confirm.c index 51f1f27947b..fccefe6ec0e 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_delete_confirm.c +++ b/applications/main/ibutton/scenes/ibutton_scene_delete_confirm.c @@ -23,7 +23,7 @@ void ibutton_scene_delete_confirm_on_enter(void* context) { 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, 0, 0, 128, 27, AlignCenter, AlignCenter, ibutton->text_store, true); widget_add_button_element( widget, GuiButtonTypeLeft, "Cancel", ibutton_scene_delete_confirm_widget_callback, ibutton); widget_add_button_element( @@ -47,24 +47,24 @@ void ibutton_scene_delete_confirm_on_enter(void* context) { key_data[6], key_data[7]); widget_add_string_element( - widget, 64, 45, AlignCenter, AlignBottom, FontSecondary, "Dallas"); + widget, 64, 34, AlignCenter, AlignBottom, FontSecondary, "Dallas"); break; case iButtonKeyCyfral: ibutton_text_store_set(ibutton, "%02X %02X", key_data[0], key_data[1]); widget_add_string_element( - widget, 64, 45, AlignCenter, AlignBottom, FontSecondary, "Cyfral"); + widget, 64, 34, AlignCenter, AlignBottom, FontSecondary, "Cyfral"); break; case iButtonKeyMetakom: ibutton_text_store_set( ibutton, "%02X %02X %02X %02X", key_data[0], key_data[1], key_data[2], key_data[3]); widget_add_string_element( - widget, 64, 45, AlignCenter, AlignBottom, FontSecondary, "Metakom"); + widget, 64, 34, AlignCenter, AlignBottom, FontSecondary, "Metakom"); break; } widget_add_string_element( - widget, 64, 33, AlignCenter, AlignBottom, FontSecondary, ibutton->text_store); + widget, 64, 46, AlignCenter, AlignBottom, FontSecondary, ibutton->text_store); view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget); diff --git a/applications/main/ibutton/scenes/ibutton_scene_emulate.c b/applications/main/ibutton/scenes/ibutton_scene_emulate.c index 3420f8471c7..e6e0ec68850 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_emulate.c +++ b/applications/main/ibutton/scenes/ibutton_scene_emulate.c @@ -15,7 +15,7 @@ static void ibutton_scene_emulate_callback(void* context, bool emulated) { void ibutton_scene_emulate_on_enter(void* context) { iButton* ibutton = context; - Popup* popup = ibutton->popup; + Widget* widget = ibutton->widget; iButtonKey* key = ibutton->key; const uint8_t* key_data = ibutton_key_get_data_p(key); @@ -26,20 +26,18 @@ void ibutton_scene_emulate_on_enter(void* context) { path_extract_filename(ibutton->file_path, key_name, true); } - uint8_t line_count = 2; DOLPHIN_DEED(DolphinDeedIbuttonEmulate); // check that stored key has name if(!string_empty_p(key_name)) { - ibutton_text_store_set(ibutton, "emulating\n%s", string_get_cstr(key_name)); - line_count = 2; + ibutton_text_store_set(ibutton, "%s", string_get_cstr(key_name)); } else { // if not, show key data switch(ibutton_key_get_type(key)) { case iButtonKeyDS1990: ibutton_text_store_set( ibutton, - "emulating\n%02X %02X %02X %02X\n%02X %02X %02X %02X", + "%02X %02X %02X %02X\n%02X %02X %02X %02X", key_data[0], key_data[1], key_data[2], @@ -48,40 +46,24 @@ void ibutton_scene_emulate_on_enter(void* context) { key_data[5], key_data[6], key_data[7]); - line_count = 3; break; case iButtonKeyCyfral: - ibutton_text_store_set(ibutton, "emulating\n%02X %02X", key_data[0], key_data[1]); - line_count = 2; + ibutton_text_store_set(ibutton, "%02X %02X", key_data[0], key_data[1]); break; case iButtonKeyMetakom: ibutton_text_store_set( - ibutton, - "emulating\n%02X %02X %02X %02X", - key_data[0], - key_data[1], - key_data[2], - key_data[3]); - line_count = 2; + ibutton, "%02X %02X %02X %02X", key_data[0], key_data[1], key_data[2], key_data[3]); break; } } - switch(line_count) { - case 3: - popup_set_header(popup, "iButton", 82, 18, AlignCenter, AlignBottom); - popup_set_text(popup, ibutton->text_store, 82, 22, AlignCenter, AlignTop); - break; - - default: - popup_set_header(popup, "iButton", 82, 24, AlignCenter, AlignBottom); - popup_set_text(popup, ibutton->text_store, 82, 28, AlignCenter, AlignTop); - break; - } - - popup_set_icon(popup, 2, 10, &I_iButtonKey_49x44); + widget_add_string_multiline_element( + widget, 90, 10, AlignCenter, AlignTop, FontPrimary, "iButton\nemulating"); + widget_add_icon_element(widget, 3, 10, &I_iButtonKey_49x44); + widget_add_text_box_element( + widget, 54, 39, 75, 22, AlignCenter, AlignCenter, ibutton->text_store, true); - view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewPopup); + view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget); ibutton_worker_emulate_set_callback( ibutton->key_worker, ibutton_scene_emulate_callback, ibutton); @@ -122,10 +104,7 @@ bool ibutton_scene_emulate_on_event(void* context, SceneManagerEvent event) { void ibutton_scene_emulate_on_exit(void* context) { iButton* ibutton = context; - Popup* popup = ibutton->popup; ibutton_worker_stop(ibutton->key_worker); - popup_set_header(popup, NULL, 0, 0, AlignCenter, AlignBottom); - popup_set_text(popup, NULL, 0, 0, AlignCenter, AlignTop); - popup_set_icon(popup, 0, 0, NULL); + widget_reset(ibutton->widget); ibutton_notification_message(ibutton, iButtonNotificationMessageBlinkStop); } diff --git a/applications/main/ibutton/scenes/ibutton_scene_info.c b/applications/main/ibutton/scenes/ibutton_scene_info.c index bd364ada8d0..9eec2bee1b7 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_info.c +++ b/applications/main/ibutton/scenes/ibutton_scene_info.c @@ -14,7 +14,7 @@ void ibutton_scene_info_on_enter(void* context) { 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); + widget, 0, 0, 128, 23, AlignCenter, AlignCenter, ibutton->text_store, true); switch(ibutton_key_get_type(key)) { case iButtonKeyDS1990: @@ -29,26 +29,24 @@ void ibutton_scene_info_on_enter(void* context) { key_data[5], key_data[6], key_data[7]); - widget_add_string_element( - widget, 64, 51, AlignCenter, AlignBottom, FontSecondary, "Dallas"); + widget_add_string_element(widget, 64, 36, AlignCenter, AlignBottom, FontPrimary, "Dallas"); break; case iButtonKeyMetakom: ibutton_text_store_set( ibutton, "%02X %02X %02X %02X", key_data[0], key_data[1], key_data[2], key_data[3]); widget_add_string_element( - widget, 64, 51, AlignCenter, AlignBottom, FontSecondary, "Metakom"); + widget, 64, 36, AlignCenter, AlignBottom, FontPrimary, "Metakom"); break; case iButtonKeyCyfral: ibutton_text_store_set(ibutton, "%02X %02X", key_data[0], key_data[1]); - widget_add_string_element( - widget, 64, 51, AlignCenter, AlignBottom, FontSecondary, "Cyfral"); + widget_add_string_element(widget, 64, 36, AlignCenter, AlignBottom, FontPrimary, "Cyfral"); break; } widget_add_string_element( - widget, 64, 35, AlignCenter, AlignBottom, FontPrimary, ibutton->text_store); + widget, 64, 50, AlignCenter, AlignBottom, FontSecondary, ibutton->text_store); view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget); diff --git a/applications/main/ibutton/scenes/ibutton_scene_save_success.c b/applications/main/ibutton/scenes/ibutton_scene_save_success.c index 6c24a897c73..43237f4292d 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_save_success.c +++ b/applications/main/ibutton/scenes/ibutton_scene_save_success.c @@ -29,10 +29,8 @@ bool ibutton_scene_save_success_on_event(void* context, SceneManagerEvent event) if(event.type == SceneManagerEventTypeCustom) { consumed = true; if(event.event == iButtonCustomEventBack) { - const uint32_t possible_scenes[] = { - iButtonSceneReadKeyMenu, iButtonSceneSavedKeyMenu, iButtonSceneAddType}; - scene_manager_search_and_switch_to_previous_scene_one_of( - ibutton->scene_manager, possible_scenes, COUNT_OF(possible_scenes)); + scene_manager_search_and_switch_to_another_scene( + ibutton->scene_manager, iButtonSceneSelectKey); } } diff --git a/applications/main/ibutton/scenes/ibutton_scene_write.c b/applications/main/ibutton/scenes/ibutton_scene_write.c index 195758d2ca1..af65575c24a 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_write.c +++ b/applications/main/ibutton/scenes/ibutton_scene_write.c @@ -14,8 +14,8 @@ static void ibutton_scene_write_callback(void* context, iButtonWorkerWriteResult void ibutton_scene_write_on_enter(void* context) { iButton* ibutton = context; - Popup* popup = ibutton->popup; iButtonKey* key = ibutton->key; + Widget* widget = ibutton->widget; iButtonWorker* worker = ibutton->key_worker; const uint8_t* key_data = ibutton_key_get_data_p(key); @@ -26,19 +26,16 @@ void ibutton_scene_write_on_enter(void* context) { path_extract_filename(ibutton->file_path, key_name, true); } - uint8_t line_count = 2; - // check that stored key has name if(!string_empty_p(key_name)) { - ibutton_text_store_set(ibutton, "writing\n%s", string_get_cstr(key_name)); - line_count = 2; + ibutton_text_store_set(ibutton, "%s", string_get_cstr(key_name)); } else { // if not, show key data switch(ibutton_key_get_type(key)) { case iButtonKeyDS1990: ibutton_text_store_set( ibutton, - "writing\n%02X %02X %02X %02X\n%02X %02X %02X %02X", + "%02X %02X %02X %02X\n%02X %02X %02X %02X", key_data[0], key_data[1], key_data[2], @@ -47,40 +44,24 @@ void ibutton_scene_write_on_enter(void* context) { key_data[5], key_data[6], key_data[7]); - line_count = 3; break; case iButtonKeyCyfral: - ibutton_text_store_set(ibutton, "writing\n%02X %02X", key_data[0], key_data[1]); - line_count = 2; + ibutton_text_store_set(ibutton, "%02X %02X", key_data[0], key_data[1]); break; case iButtonKeyMetakom: ibutton_text_store_set( - ibutton, - "writing\n%02X %02X %02X %02X", - key_data[0], - key_data[1], - key_data[2], - key_data[3]); - line_count = 2; + ibutton, "%02X %02X %02X %02X", key_data[0], key_data[1], key_data[2], key_data[3]); break; } } - switch(line_count) { - case 3: - popup_set_header(popup, "iButton", 82, 18, AlignCenter, AlignBottom); - popup_set_text(popup, ibutton->text_store, 82, 22, AlignCenter, AlignTop); - break; - - default: - popup_set_header(popup, "iButton", 82, 24, AlignCenter, AlignBottom); - popup_set_text(popup, ibutton->text_store, 82, 28, AlignCenter, AlignTop); - break; - } - - popup_set_icon(popup, 2, 10, &I_iButtonKey_49x44); + widget_add_string_multiline_element( + widget, 90, 10, AlignCenter, AlignTop, FontPrimary, "iButton\nwriting"); + widget_add_icon_element(widget, 3, 10, &I_iButtonKey_49x44); + widget_add_text_box_element( + widget, 54, 39, 75, 22, AlignCenter, AlignCenter, ibutton->text_store, true); - view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewPopup); + view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget); ibutton_worker_write_set_callback(worker, ibutton_scene_write_callback, ibutton); ibutton_worker_write_start(worker, key); @@ -114,11 +95,8 @@ bool ibutton_scene_write_on_event(void* context, SceneManagerEvent event) { void ibutton_scene_write_on_exit(void* context) { iButton* ibutton = context; - Popup* popup = ibutton->popup; ibutton_worker_stop(ibutton->key_worker); - popup_set_header(popup, NULL, 0, 0, AlignCenter, AlignBottom); - popup_set_text(popup, NULL, 0, 0, AlignCenter, AlignTop); - popup_set_icon(popup, 0, 0, NULL); + widget_reset(ibutton->widget); ibutton_notification_message(ibutton, iButtonNotificationMessageBlinkStop); } diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_write.c b/applications/main/lfrfid/scenes/lfrfid_scene_write.c index 4b03bac15a1..8e791d529ad 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_write.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_write.c @@ -38,7 +38,6 @@ void lfrfid_scene_write_on_enter(void* context) { view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewPopup); size_t size = protocol_dict_get_data_size(app->dict, app->protocol_id); - app->old_key_data = (uint8_t*)malloc(size); protocol_dict_get_data(app->dict, app->protocol_id, app->old_key_data, size); lfrfid_worker_start_thread(app->lfworker); @@ -92,5 +91,4 @@ void lfrfid_scene_write_on_exit(void* context) { size_t size = protocol_dict_get_data_size(app->dict, app->protocol_id); protocol_dict_set_data(app->dict, app->protocol_id, app->old_key_data, size); - free(app->old_key_data); } From 0f9ea925d30f7a6183abe1002d680e3d1613a159 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Fri, 30 Sep 2022 22:03:57 +0900 Subject: [PATCH 099/824] UnitTests: fix thread join test (#1808) --- applications/debug/unit_tests/storage/storage_test.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/applications/debug/unit_tests/storage/storage_test.c b/applications/debug/unit_tests/storage/storage_test.c index c3628a4f9da..099be0d5ca3 100644 --- a/applications/debug/unit_tests/storage/storage_test.c +++ b/applications/debug/unit_tests/storage/storage_test.c @@ -58,7 +58,7 @@ MU_TEST(storage_file_open_lock) { storage_file_close(file); // file_locker thread stop - mu_check(furi_thread_join(locker_thread) == FuriStatusOk); + mu_check(furi_thread_join(locker_thread)); furi_thread_free(locker_thread); // clean data @@ -148,7 +148,7 @@ MU_TEST(storage_dir_open_lock) { storage_dir_close(file); // file_locker thread stop - mu_check(furi_thread_join(locker_thread) == FuriStatusOk); + mu_check(furi_thread_join(locker_thread)); furi_thread_free(locker_thread); // clean data From 4bf29827f8f0bbfc1444107e26e4a382c2762da0 Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Thu, 6 Oct 2022 01:15:23 +1000 Subject: [PATCH 100/824] M*LIB: non-inlined strings, FuriString primitive (#1795) * Quicksave 1 * Header stage complete * Source stage complete * Lint & merge fixes * Includes * Documentation step 1 * FBT: output free size considering BT STACK * Documentation step 2 * py lint * Fix music player plugin * unit test stage 1: string allocator, mem, getters, setters, appends, compare, search. * unit test: string equality * unit test: string replace * unit test: string start_with, end_with * unit test: string trim * unit test: utf-8 * Rename * Revert fw_size changes * Simplify CLI backspace handling * Simplify CLI character insert * Merge fixes * Furi: correct filenaming and spelling * Bt: remove furi string include Co-authored-by: Aleksandr Kutuzov --- .../debug/bt_debug_app/views/bt_test.c | 12 +- .../debug/display_test/display_test.c | 10 +- .../file_browser_test/file_browser_app.c | 5 +- .../file_browser_test/file_browser_app_i.h | 2 +- .../scenes/file_browser_scene_browser.c | 5 +- .../scenes/file_browser_scene_result.c | 11 +- .../scenes/file_browser_scene_start.c | 2 +- applications/debug/uart_echo/uart_echo.c | 18 +- .../flipper_format_string_test.c | 26 +- .../flipper_format/flipper_format_test.c | 28 +- .../debug/unit_tests/furi/furi_string_test.c | 469 +++++++++++ .../debug/unit_tests/infrared/infrared_test.c | 88 ++- applications/debug/unit_tests/nfc/nfc_test.c | 19 +- .../debug/unit_tests/storage/dirwalk_test.c | 48 +- .../debug/unit_tests/storage/storage_test.c | 28 +- .../debug/unit_tests/stream/stream_test.c | 36 +- .../debug/unit_tests/subghz/subghz_test.c | 21 +- applications/debug/unit_tests/test_index.c | 10 +- applications/main/archive/archive.c | 5 +- applications/main/archive/archive_i.h | 2 +- .../main/archive/helpers/archive_browser.c | 47 +- .../main/archive/helpers/archive_browser.h | 2 +- .../main/archive/helpers/archive_favorites.c | 110 +-- .../main/archive/helpers/archive_files.c | 28 +- .../main/archive/helpers/archive_files.h | 12 +- .../archive/scenes/archive_scene_browser.c | 8 +- .../archive/scenes/archive_scene_delete.c | 9 +- .../archive/scenes/archive_scene_rename.c | 22 +- .../main/archive/views/archive_browser_view.c | 43 +- .../main/archive/views/archive_browser_view.h | 2 +- applications/main/bad_usb/bad_usb_app.c | 11 +- applications/main/bad_usb/bad_usb_app_i.h | 2 +- applications/main/bad_usb/bad_usb_script.c | 46 +- applications/main/bad_usb/bad_usb_script.h | 3 +- .../main/bad_usb/scenes/bad_usb_scene_work.c | 9 +- .../main/bad_usb/views/bad_usb_view.c | 38 +- applications/main/fap_loader/fap_loader_app.c | 51 +- applications/main/ibutton/ibutton.c | 33 +- applications/main/ibutton/ibutton_cli.c | 56 +- applications/main/ibutton/ibutton_i.h | 4 +- .../ibutton/scenes/ibutton_scene_add_type.c | 3 +- .../scenes/ibutton_scene_delete_confirm.c | 8 +- .../ibutton/scenes/ibutton_scene_emulate.c | 12 +- .../main/ibutton/scenes/ibutton_scene_info.c | 8 +- .../main/ibutton/scenes/ibutton_scene_read.c | 2 +- .../main/ibutton/scenes/ibutton_scene_rpc.c | 18 +- .../ibutton/scenes/ibutton_scene_save_name.c | 23 +- .../main/ibutton/scenes/ibutton_scene_start.c | 2 +- .../main/ibutton/scenes/ibutton_scene_write.c | 13 +- applications/main/infrared/infrared.c | 89 ++- .../main/infrared/infrared_brute_force.c | 27 +- applications/main/infrared/infrared_cli.c | 83 +- applications/main/infrared/infrared_i.h | 2 +- applications/main/infrared/infrared_remote.c | 49 +- applications/main/infrared/infrared_remote.h | 2 +- .../main/infrared/infrared_remote_button.c | 11 +- applications/main/infrared/infrared_signal.c | 37 +- applications/main/infrared/infrared_signal.h | 4 +- .../scenes/infrared_scene_edit_rename.c | 12 +- .../main/infrared/scenes/infrared_scene_rpc.c | 2 +- .../infrared/scenes/infrared_scene_start.c | 2 +- .../infrared/views/infrared_progress_view.c | 1 - applications/main/lfrfid/lfrfid.c | 34 +- applications/main/lfrfid/lfrfid_cli.c | 131 ++-- applications/main/lfrfid/lfrfid_i.h | 12 +- .../scenes/lfrfid_scene_delete_confirm.c | 18 +- .../main/lfrfid/scenes/lfrfid_scene_emulate.c | 4 +- .../scenes/lfrfid_scene_extra_actions.c | 2 +- .../lfrfid/scenes/lfrfid_scene_raw_info.c | 4 +- .../lfrfid/scenes/lfrfid_scene_raw_name.c | 6 +- .../lfrfid/scenes/lfrfid_scene_raw_read.c | 18 +- .../main/lfrfid/scenes/lfrfid_scene_read.c | 2 +- .../scenes/lfrfid_scene_read_key_menu.c | 2 +- .../lfrfid/scenes/lfrfid_scene_read_success.c | 28 +- .../main/lfrfid/scenes/lfrfid_scene_rpc.c | 5 +- .../lfrfid/scenes/lfrfid_scene_save_name.c | 27 +- .../lfrfid/scenes/lfrfid_scene_save_type.c | 15 +- .../lfrfid/scenes/lfrfid_scene_saved_info.c | 24 +- .../main/lfrfid/scenes/lfrfid_scene_start.c | 4 +- .../main/lfrfid/scenes/lfrfid_scene_write.c | 4 +- .../main/nfc/helpers/nfc_emv_parser.c | 40 +- .../main/nfc/helpers/nfc_emv_parser.h | 7 +- applications/main/nfc/nfc.c | 4 +- applications/main/nfc/nfc_cli.c | 20 +- applications/main/nfc/nfc_i.h | 2 +- .../main/nfc/scenes/nfc_scene_delete.c | 28 +- .../main/nfc/scenes/nfc_scene_device_info.c | 37 +- .../main/nfc/scenes/nfc_scene_emulate_uid.c | 30 +- .../nfc/scenes/nfc_scene_emv_read_success.c | 32 +- .../main/nfc/scenes/nfc_scene_generate_info.c | 12 +- .../scenes/nfc_scene_mf_classic_keys_delete.c | 14 +- .../scenes/nfc_scene_mf_classic_keys_list.c | 8 +- .../nfc_scene_mf_classic_read_success.c | 20 +- .../nfc/scenes/nfc_scene_mf_desfire_app.c | 6 +- .../nfc/scenes/nfc_scene_mf_desfire_data.c | 6 +- .../nfc_scene_mf_desfire_read_success.c | 25 +- .../nfc/scenes/nfc_scene_mf_ultralight_data.c | 8 +- ...nfc_scene_mf_ultralight_read_auth_result.c | 24 +- .../nfc_scene_mf_ultralight_read_success.c | 20 +- .../nfc/scenes/nfc_scene_mfkey_nonces_info.c | 12 +- .../main/nfc/scenes/nfc_scene_nfc_data_info.c | 53 +- .../nfc/scenes/nfc_scene_nfca_read_success.c | 18 +- .../nfc/scenes/nfc_scene_read_card_success.c | 16 +- .../main/nfc/scenes/nfc_scene_save_name.c | 15 +- .../main/nfc/scenes/nfc_scene_set_type.c | 3 +- applications/main/nfc/views/dict_attack.c | 14 +- .../main/subghz/helpers/subghz_types.h | 3 +- .../main/subghz/scenes/subghz_scene_delete.c | 32 +- .../subghz/scenes/subghz_scene_delete_raw.c | 32 +- .../subghz/scenes/subghz_scene_more_raw.c | 2 +- .../subghz/scenes/subghz_scene_read_raw.c | 54 +- .../subghz/scenes/subghz_scene_receiver.c | 48 +- .../scenes/subghz_scene_receiver_config.c | 2 +- .../scenes/subghz_scene_receiver_info.c | 28 +- .../main/subghz/scenes/subghz_scene_rpc.c | 10 +- .../subghz/scenes/subghz_scene_save_name.c | 43 +- .../subghz/scenes/subghz_scene_set_type.c | 6 +- .../subghz/scenes/subghz_scene_show_error.c | 4 +- .../scenes/subghz_scene_show_error_sub.c | 4 +- .../subghz/scenes/subghz_scene_transmitter.c | 26 +- applications/main/subghz/subghz.c | 23 +- applications/main/subghz/subghz_cli.c | 229 +++--- applications/main/subghz/subghz_history.c | 67 +- applications/main/subghz/subghz_history.h | 8 +- applications/main/subghz/subghz_i.c | 128 +-- applications/main/subghz/subghz_i.h | 10 +- applications/main/subghz/subghz_setting.c | 34 +- applications/main/subghz/views/receiver.c | 63 +- .../main/subghz/views/subghz_read_raw.c | 56 +- applications/main/subghz/views/transmitter.c | 36 +- applications/main/u2f/u2f_data.c | 24 +- .../plugins/music_player/music_player.c | 14 +- .../plugins/music_player/music_player_cli.c | 10 +- .../music_player/music_player_worker.c | 25 +- .../plugins/picopass/picopass_device.c | 77 +- .../plugins/picopass/picopass_device.h | 2 +- applications/plugins/picopass/picopass_i.h | 2 +- .../scenes/picopass_scene_device_info.c | 30 +- .../scenes/picopass_scene_read_card_success.c | 44 +- .../scenes/picopass_scene_save_name.c | 15 +- applications/services/bt/bt_cli.c | 36 +- applications/services/bt/bt_service/bt.c | 8 +- applications/services/cli/cli.c | 140 ++-- applications/services/cli/cli.h | 5 +- applications/services/cli/cli_command_gpio.c | 52 +- applications/services/cli/cli_command_gpio.h | 2 +- applications/services/cli/cli_commands.c | 84 +- applications/services/cli/cli_i.h | 8 +- applications/services/crypto/crypto_cli.c | 65 +- .../desktop/animations/animation_manager.c | 21 +- .../desktop/animations/animation_storage.c | 88 ++- .../desktop/animations/animation_storage.h | 1 - applications/services/dialogs/dialogs.h | 5 +- applications/services/dialogs/dialogs_api.c | 5 +- .../services/dialogs/dialogs_message.h | 5 +- applications/services/gui/elements.c | 45 +- applications/services/gui/elements.h | 4 +- .../services/gui/modules/button_menu.c | 16 +- .../services/gui/modules/file_browser.c | 60 +- .../services/gui/modules/file_browser.h | 12 +- .../gui/modules/file_browser_worker.c | 118 +-- .../gui/modules/file_browser_worker.h | 10 +- applications/services/gui/modules/submenu.c | 8 +- applications/services/gui/modules/text_box.c | 19 +- .../services/gui/modules/text_input.c | 10 +- .../services/gui/modules/text_input.h | 3 +- .../services/gui/modules/validators.c | 12 +- .../services/gui/modules/validators.h | 3 +- .../services/gui/modules/variable_item_list.c | 12 +- .../widget_elements/widget_element_button.c | 13 +- .../widget_elements/widget_element_string.c | 11 +- .../widget_element_string_multiline.c | 11 +- .../widget_elements/widget_element_text_box.c | 11 +- .../widget_element_text_scroll.c | 45 +- applications/services/input/input_cli.c | 42 +- applications/services/input/input_i.h | 3 +- applications/services/loader/loader.c | 30 +- applications/services/power/power_cli.c | 48 +- applications/services/rpc/rpc.c | 1 - applications/services/rpc/rpc_cli.c | 2 +- applications/services/rpc/rpc_debug.c | 147 ++-- applications/services/rpc/rpc_i.h | 2 +- applications/services/storage/storage.h | 5 +- applications/services/storage/storage_cli.c | 155 ++-- .../services/storage/storage_external_api.c | 133 ++-- applications/services/storage/storage_glue.c | 20 +- applications/services/storage/storage_glue.h | 11 +- .../services/storage/storage_internal_api.c | 1 - .../services/storage/storage_processing.c | 40 +- .../services/storage/storage_test_app.c | 33 +- .../services/storage/storages/storage_int.c | 9 +- applications/settings/about/about.c | 27 +- .../scenes/storage_settings_scene_benchmark.c | 12 +- .../storage_settings_scene_factory_reset.c | 6 +- .../storage_settings_scene_internal_info.c | 6 +- .../scenes/storage_settings_scene_sd_info.c | 6 +- .../storage_settings/storage_settings.c | 4 +- .../storage_settings/storage_settings.h | 2 +- .../storage_move_to_sd/storage_move_to_sd.c | 26 +- applications/system/updater/cli/updater_cli.c | 39 +- .../updater/scenes/updater_scene_loadcfg.c | 8 +- applications/system/updater/updater.c | 8 +- applications/system/updater/updater_i.h | 4 +- .../system/updater/util/update_task.c | 54 +- .../system/updater/util/update_task.h | 3 +- .../system/updater/util/update_task_i.h | 4 +- .../updater/util/update_task_worker_backup.c | 53 +- .../system/updater/views/updater_main.c | 14 +- firmware/targets/f7/Src/update.c | 45 +- firmware/targets/f7/api_symbols.csv | 134 +++- .../targets/f7/ble_glue/dev_info_service.c | 14 +- firmware/targets/f7/furi_hal/furi_hal_bt.c | 6 +- .../targets/f7/furi_hal/furi_hal_console.c | 9 +- firmware/targets/f7/furi_hal/furi_hal_info.c | 136 ++-- firmware/targets/f7/furi_hal/furi_hal_nfc.c | 4 +- firmware/targets/f7/furi_hal/furi_hal_power.c | 46 +- .../targets/furi_hal_include/furi_hal_bt.h | 6 +- .../targets/furi_hal_include/furi_hal_power.h | 1 - furi/core/check.c | 1 + furi/core/kernel.h | 2 +- furi/core/log.c | 16 +- furi/core/record.c | 1 - furi/core/string.c | 302 +++++++ furi/core/string.h | 738 ++++++++++++++++++ furi/core/thread.c | 18 +- furi/furi.h | 1 + lib/flipper_application/elf/elf_file.c | 77 +- lib/flipper_format/flipper_format.c | 23 +- lib/flipper_format/flipper_format.h | 23 +- lib/flipper_format/flipper_format_stream.c | 96 +-- lib/flipper_format/flipper_format_stream.h | 1 - lib/lfrfid/lfrfid_dict_file.c | 12 +- lib/lfrfid/lfrfid_raw_worker.c | 14 +- lib/lfrfid/lfrfid_worker_modes.c | 12 +- lib/lfrfid/protocols/protocol_awid.c | 20 +- lib/lfrfid/protocols/protocol_em4100.c | 13 +- lib/lfrfid/protocols/protocol_fdx_a.c | 4 +- lib/lfrfid/protocols/protocol_fdx_b.c | 22 +- lib/lfrfid/protocols/protocol_gallagher.c | 6 +- lib/lfrfid/protocols/protocol_h10301.c | 6 +- .../protocols/protocol_hid_ex_generic.c | 4 +- lib/lfrfid/protocols/protocol_hid_generic.c | 22 +- lib/lfrfid/protocols/protocol_indala26.c | 13 +- lib/lfrfid/protocols/protocol_io_prox_xsf.c | 8 +- lib/lfrfid/protocols/protocol_jablotron.c | 4 +- lib/lfrfid/protocols/protocol_keri.c | 4 +- lib/lfrfid/protocols/protocol_pac_stanley.c | 52 +- lib/lfrfid/protocols/protocol_paradox.c | 14 +- lib/lfrfid/protocols/protocol_pyramid.c | 8 +- lib/lfrfid/protocols/protocol_viking.c | 4 +- lib/nfc/helpers/mf_classic_dict.c | 124 +-- lib/nfc/helpers/mf_classic_dict.h | 10 +- lib/nfc/helpers/mfkey32.c | 22 +- lib/nfc/helpers/mfkey32.h | 3 +- lib/nfc/helpers/nfc_debug_log.c | 13 +- lib/nfc/nfc_device.c | 566 +++++++------- lib/nfc/nfc_device.h | 4 +- lib/nfc/parsers/all_in_one.c | 2 +- lib/nfc/parsers/nfc_supported_card.h | 2 - lib/nfc/parsers/plantain_4k_parser.c | 26 +- lib/nfc/parsers/plantain_parser.c | 39 +- lib/nfc/parsers/plantain_parser.h | 2 +- lib/nfc/parsers/troika_4k_parser.c | 3 +- lib/nfc/parsers/troika_parser.c | 2 +- lib/nfc/parsers/two_cities.c | 26 +- lib/nfc/protocols/mifare_desfire.c | 76 +- lib/nfc/protocols/mifare_desfire.h | 17 +- lib/nfc/protocols/mifare_ultralight.c | 56 +- lib/subghz/blocks/generic.c | 23 +- lib/subghz/blocks/generic.h | 2 +- lib/subghz/protocols/base.c | 2 +- lib/subghz/protocols/base.h | 7 +- lib/subghz/protocols/bett.c | 4 +- lib/subghz/protocols/bett.h | 2 +- lib/subghz/protocols/came.c | 4 +- lib/subghz/protocols/came.h | 2 +- lib/subghz/protocols/came_atomo.c | 4 +- lib/subghz/protocols/came_atomo.h | 2 +- lib/subghz/protocols/came_twee.c | 4 +- lib/subghz/protocols/came_twee.h | 2 +- lib/subghz/protocols/chamberlain_code.c | 10 +- lib/subghz/protocols/chamberlain_code.h | 2 +- lib/subghz/protocols/clemsa.c | 4 +- lib/subghz/protocols/clemsa.h | 2 +- lib/subghz/protocols/doitrand.c | 4 +- lib/subghz/protocols/doitrand.h | 2 +- lib/subghz/protocols/faac_slh.c | 4 +- lib/subghz/protocols/faac_slh.h | 2 +- lib/subghz/protocols/gate_tx.c | 4 +- lib/subghz/protocols/gate_tx.h | 2 +- lib/subghz/protocols/holtek.c | 8 +- lib/subghz/protocols/holtek.h | 2 +- lib/subghz/protocols/honeywell_wdb.c | 4 +- lib/subghz/protocols/honeywell_wdb.h | 2 +- lib/subghz/protocols/hormann.c | 4 +- lib/subghz/protocols/hormann.h | 2 +- lib/subghz/protocols/ido.c | 4 +- lib/subghz/protocols/ido.h | 2 +- lib/subghz/protocols/intertechno_v3.c | 10 +- lib/subghz/protocols/intertechno_v3.h | 2 +- lib/subghz/protocols/keeloq.c | 33 +- lib/subghz/protocols/keeloq.h | 2 +- lib/subghz/protocols/keeloq_common.c | 1 - lib/subghz/protocols/kia.c | 4 +- lib/subghz/protocols/kia.h | 2 +- lib/subghz/protocols/linear.c | 4 +- lib/subghz/protocols/linear.h | 2 +- lib/subghz/protocols/magellen.c | 8 +- lib/subghz/protocols/magellen.h | 2 +- lib/subghz/protocols/marantec.c | 4 +- lib/subghz/protocols/marantec.h | 2 +- lib/subghz/protocols/megacode.c | 4 +- lib/subghz/protocols/megacode.h | 2 +- lib/subghz/protocols/nero_radio.c | 4 +- lib/subghz/protocols/nero_radio.h | 2 +- lib/subghz/protocols/nero_sketch.c | 4 +- lib/subghz/protocols/nero_sketch.h | 2 +- lib/subghz/protocols/nice_flo.c | 4 +- lib/subghz/protocols/nice_flo.h | 2 +- lib/subghz/protocols/nice_flor_s.c | 4 +- lib/subghz/protocols/nice_flor_s.h | 2 +- lib/subghz/protocols/oregon2.c | 15 +- lib/subghz/protocols/phoenix_v2.c | 4 +- lib/subghz/protocols/phoenix_v2.h | 2 +- lib/subghz/protocols/power_smart.c | 4 +- lib/subghz/protocols/power_smart.h | 2 +- lib/subghz/protocols/princeton.c | 4 +- lib/subghz/protocols/princeton.h | 2 +- lib/subghz/protocols/raw.c | 47 +- lib/subghz/protocols/raw.h | 2 +- lib/subghz/protocols/scher_khan.c | 4 +- lib/subghz/protocols/scher_khan.h | 2 +- lib/subghz/protocols/secplus_v1.c | 24 +- lib/subghz/protocols/secplus_v1.h | 2 +- lib/subghz/protocols/secplus_v2.c | 4 +- lib/subghz/protocols/secplus_v2.h | 2 +- lib/subghz/protocols/somfy_keytis.c | 4 +- lib/subghz/protocols/somfy_keytis.h | 2 +- lib/subghz/protocols/somfy_telis.c | 4 +- lib/subghz/protocols/somfy_telis.h | 2 +- lib/subghz/protocols/star_line.c | 17 +- lib/subghz/protocols/star_line.h | 2 +- lib/subghz/subghz_file_encoder_worker.c | 24 +- lib/subghz/subghz_keystore.c | 32 +- lib/subghz/subghz_keystore.h | 4 +- lib/subghz/types.h | 2 +- lib/toolbox/args.c | 44 +- lib/toolbox/args.h | 14 +- lib/toolbox/dir_walk.c | 30 +- lib/toolbox/dir_walk.h | 2 +- lib/toolbox/m_cstr_dup.h | 3 +- lib/toolbox/path.c | 81 +- lib/toolbox/path.h | 17 +- lib/toolbox/protocols/protocol.h | 4 +- lib/toolbox/protocols/protocol_dict.c | 4 +- lib/toolbox/protocols/protocol_dict.h | 4 +- lib/toolbox/stream/file_stream.c | 19 +- lib/toolbox/stream/stream.c | 35 +- lib/toolbox/stream/stream.h | 9 +- lib/toolbox/stream/string_stream.c | 34 +- lib/toolbox/stream/string_stream.h | 1 - lib/toolbox/tar/tar_archive.c | 48 +- lib/toolbox/tar/tar_archive.h | 1 - lib/update_util/lfs_backup.c | 8 +- lib/update_util/resources/manifest.c | 36 +- lib/update_util/resources/manifest.h | 3 +- lib/update_util/update_manifest.c | 36 +- lib/update_util/update_manifest.h | 14 +- lib/update_util/update_operation.c | 34 +- lib/update_util/update_operation.h | 5 +- 370 files changed, 5599 insertions(+), 3965 deletions(-) create mode 100644 applications/debug/unit_tests/furi/furi_string_test.c create mode 100644 furi/core/string.c create mode 100644 furi/core/string.h diff --git a/applications/debug/bt_debug_app/views/bt_test.c b/applications/debug/bt_debug_app/views/bt_test.c index 70f57c12c18..9f2830d348f 100644 --- a/applications/debug/bt_debug_app/views/bt_test.c +++ b/applications/debug/bt_debug_app/views/bt_test.c @@ -3,14 +3,13 @@ #include #include #include -#include #include #include struct BtTestParam { const char* label; uint8_t current_value_index; - string_t current_value_text; + FuriString* current_value_text; uint8_t values_count; BtTestParamChangeCallback change_callback; void* context; @@ -85,7 +84,8 @@ static void bt_test_draw_callback(Canvas* canvas, void* _model) { canvas_draw_str(canvas, 50, param_text_y, "<"); } - canvas_draw_str(canvas, 61, param_text_y, string_get_cstr(param->current_value_text)); + canvas_draw_str( + canvas, 61, param_text_y, furi_string_get_cstr(param->current_value_text)); if(param->current_value_index < (param->values_count - 1)) { canvas_draw_str(canvas, 113, param_text_y, ">"); @@ -322,7 +322,7 @@ void bt_test_free(BtTest* bt_test) { BtTestParamArray_it_t it; for(BtTestParamArray_it(it, model->params); !BtTestParamArray_end_p(it); BtTestParamArray_next(it)) { - string_clear(BtTestParamArray_ref(it)->current_value_text); + furi_string_free(BtTestParamArray_ref(it)->current_value_text); } BtTestParamArray_clear(model->params); return false; @@ -354,7 +354,7 @@ BtTestParam* bt_test_param_add( param->change_callback = change_callback; param->context = context; param->current_value_index = 0; - string_init(param->current_value_text); + param->current_value_text = furi_string_alloc(); return true; }); @@ -410,7 +410,7 @@ void bt_test_set_current_value_index(BtTestParam* param, uint8_t current_value_i } void bt_test_set_current_value_text(BtTestParam* param, const char* current_value_text) { - string_set_str(param->current_value_text, current_value_text); + furi_string_set(param->current_value_text, current_value_text); } uint8_t bt_test_get_current_value_index(BtTestParam* param) { diff --git a/applications/debug/display_test/display_test.c b/applications/debug/display_test/display_test.c index 947e4dc8b23..e7f366cbb3a 100644 --- a/applications/debug/display_test/display_test.c +++ b/applications/debug/display_test/display_test.c @@ -113,11 +113,11 @@ static void display_config_set_regulation_ratio(VariableItem* item) { static void display_config_set_contrast(VariableItem* item) { DisplayTest* instance = variable_item_get_context(item); uint8_t index = variable_item_get_current_value_index(item); - string_t temp; - string_init(temp); - string_cat_printf(temp, "%d", index); - variable_item_set_current_value_text(item, string_get_cstr(temp)); - string_clear(temp); + FuriString* temp; + temp = furi_string_alloc(); + furi_string_cat_printf(temp, "%d", index); + variable_item_set_current_value_text(item, furi_string_get_cstr(temp)); + furi_string_free(temp); instance->config_contrast = index; display_test_reload_config(instance); } diff --git a/applications/debug/file_browser_test/file_browser_app.c b/applications/debug/file_browser_test/file_browser_app.c index c7f461d40d8..5c7b93bcbaf 100644 --- a/applications/debug/file_browser_test/file_browser_app.c +++ b/applications/debug/file_browser_test/file_browser_app.c @@ -1,7 +1,6 @@ #include "assets_icons.h" #include "file_browser_app_i.h" #include "gui/modules/file_browser.h" -#include "m-string.h" #include #include #include @@ -47,7 +46,7 @@ FileBrowserApp* file_browser_app_alloc(char* arg) { app->widget = widget_alloc(); - string_init(app->file_path); + app->file_path = furi_string_alloc(); app->file_browser = file_browser_alloc(app->file_path); file_browser_configure(app->file_browser, "*", true, &I_badusb_10px, true); @@ -84,7 +83,7 @@ void file_browser_app_free(FileBrowserApp* app) { furi_record_close(RECORD_NOTIFICATION); furi_record_close(RECORD_DIALOGS); - string_clear(app->file_path); + furi_string_free(app->file_path); free(app); } diff --git a/applications/debug/file_browser_test/file_browser_app_i.h b/applications/debug/file_browser_test/file_browser_app_i.h index 6e8412c9bd3..20f4c3a038b 100644 --- a/applications/debug/file_browser_test/file_browser_app_i.h +++ b/applications/debug/file_browser_test/file_browser_app_i.h @@ -22,7 +22,7 @@ struct FileBrowserApp { Widget* widget; FileBrowser* file_browser; - string_t file_path; + FuriString* file_path; }; typedef enum { diff --git a/applications/debug/file_browser_test/scenes/file_browser_scene_browser.c b/applications/debug/file_browser_test/scenes/file_browser_scene_browser.c index d9bd1ac5314..0bf70e9c63f 100644 --- a/applications/debug/file_browser_test/scenes/file_browser_scene_browser.c +++ b/applications/debug/file_browser_test/scenes/file_browser_scene_browser.c @@ -1,8 +1,5 @@ #include "../file_browser_app_i.h" -#include -#include -#include "furi_hal.h" -#include "m-string.h" +#include #define DEFAULT_PATH "/" #define EXTENSION "*" diff --git a/applications/debug/file_browser_test/scenes/file_browser_scene_result.c b/applications/debug/file_browser_test/scenes/file_browser_scene_result.c index 53576cef4b4..4c68d80f773 100644 --- a/applications/debug/file_browser_test/scenes/file_browser_scene_result.c +++ b/applications/debug/file_browser_test/scenes/file_browser_scene_result.c @@ -1,6 +1,5 @@ #include "../file_browser_app_i.h" -#include "furi_hal.h" -#include "m-string.h" +#include void file_browser_scene_result_ok_callback(InputType type, void* context) { furi_assert(context); @@ -24,7 +23,13 @@ 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)); + app->widget, + 64, + 10, + AlignCenter, + AlignTop, + FontSecondary, + furi_string_get_cstr(app->file_path)); view_dispatcher_switch_to_view(app->view_dispatcher, FileBrowserAppViewResult); } diff --git a/applications/debug/file_browser_test/scenes/file_browser_scene_start.c b/applications/debug/file_browser_test/scenes/file_browser_scene_start.c index b3381f0ad0e..9eb26944ffc 100644 --- a/applications/debug/file_browser_test/scenes/file_browser_scene_start.c +++ b/applications/debug/file_browser_test/scenes/file_browser_scene_start.c @@ -19,7 +19,7 @@ bool file_browser_scene_start_on_event(void* context, SceneManagerEvent event) { bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { - string_set_str(app->file_path, ANY_PATH("badusb/demo_windows.txt")); + furi_string_set(app->file_path, ANY_PATH("badusb/demo_windows.txt")); scene_manager_next_scene(app->scene_manager, FileBrowserSceneBrowser); consumed = true; } else if(event.type == SceneManagerEventTypeTick) { diff --git a/applications/debug/uart_echo/uart_echo.c b/applications/debug/uart_echo/uart_echo.c index 7a0f5c3933c..03b6a31a6a9 100644 --- a/applications/debug/uart_echo/uart_echo.c +++ b/applications/debug/uart_echo/uart_echo.c @@ -1,5 +1,4 @@ #include -#include #include #include #include @@ -25,7 +24,7 @@ typedef struct { } UartEchoApp; typedef struct { - string_t text; + FuriString* text; } ListElement; struct UartDumpModel { @@ -64,10 +63,11 @@ static void uart_echo_view_draw_callback(Canvas* canvas, void* _model) { canvas, 0, (i + 1) * (canvas_current_font_height(canvas) - 1), - string_get_cstr(model->list[i]->text)); + furi_string_get_cstr(model->list[i]->text)); if(i == model->line) { - uint8_t width = canvas_string_width(canvas, string_get_cstr(model->list[i]->text)); + uint8_t width = + canvas_string_width(canvas, furi_string_get_cstr(model->list[i]->text)); canvas_draw_box( canvas, @@ -113,7 +113,7 @@ static void uart_echo_push_to_list(UartDumpModel* model, const char data) { model->escape = true; } else if((data >= ' ' && data <= '~') || (data == '\n' || data == '\r')) { bool new_string_needed = false; - if(string_size(model->list[model->line]->text) >= COLUMNS_ON_SCREEN) { + if(furi_string_size(model->list[model->line]->text) >= COLUMNS_ON_SCREEN) { new_string_needed = true; } else if((data == '\n' || data == '\r')) { // pack line breaks @@ -132,13 +132,13 @@ static void uart_echo_push_to_list(UartDumpModel* model, const char data) { model->list[i - 1] = model->list[i]; } - string_reset(first->text); + furi_string_reset(first->text); model->list[model->line] = first; } } if(data != '\n' && data != '\r') { - string_push_back(model->list[model->line]->text, data); + furi_string_push_back(model->list[model->line]->text, data); } } model->last_char = data; @@ -208,7 +208,7 @@ static UartEchoApp* uart_echo_app_alloc() { model->line = 0; model->escape = false; model->list[i] = malloc(sizeof(ListElement)); - string_init(model->list[i]->text); + model->list[i]->text = furi_string_alloc(); } return true; }); @@ -247,7 +247,7 @@ static void uart_echo_app_free(UartEchoApp* app) { with_view_model( app->view, (UartDumpModel * model) { for(size_t i = 0; i < LINES_ON_SCREEN; i++) { - string_clear(model->list[i]->text); + furi_string_free(model->list[i]->text); free(model->list[i]); } return true; diff --git a/applications/debug/unit_tests/flipper_format/flipper_format_string_test.c b/applications/debug/unit_tests/flipper_format/flipper_format_string_test.c index b22b333a394..920a22a43bf 100644 --- a/applications/debug/unit_tests/flipper_format/flipper_format_string_test.c +++ b/applications/debug/unit_tests/flipper_format/flipper_format_string_test.c @@ -58,7 +58,7 @@ static const char* test_data_win = "Filetype: Flipper Format test\r\n" #define ARRAY_W_BSIZE(x) (x), (sizeof(x)) MU_TEST_1(flipper_format_read_and_update_test, FlipperFormat* flipper_format) { - string_t tmpstr; + FuriString* tmpstr; uint32_t version; uint32_t uint32_data[COUNT_OF(test_uint_data)]; int32_t int32_data[COUNT_OF(test_int_data)]; @@ -101,14 +101,14 @@ MU_TEST_1(flipper_format_read_and_update_test, FlipperFormat* flipper_format) { mu_assert_int_eq(position_before, stream_tell(flipper_format_get_raw_stream(flipper_format))); // read test - string_init(tmpstr); + tmpstr = furi_string_alloc(); mu_check(flipper_format_read_header(flipper_format, tmpstr, &version)); - mu_assert_string_eq(test_filetype, string_get_cstr(tmpstr)); + mu_assert_string_eq(test_filetype, furi_string_get_cstr(tmpstr)); mu_assert_int_eq(test_version, version); mu_check(flipper_format_read_string(flipper_format, test_string_key, tmpstr)); - mu_assert_string_eq(test_string_data, string_get_cstr(tmpstr)); + mu_assert_string_eq(test_string_data, furi_string_get_cstr(tmpstr)); mu_check(flipper_format_get_value_count(flipper_format, test_int_key, &count)); mu_assert_int_eq(COUNT_OF(test_int_data), count); @@ -133,7 +133,7 @@ MU_TEST_1(flipper_format_read_and_update_test, FlipperFormat* flipper_format) { mu_check(!flipper_format_read_string(flipper_format, "Key that doesn't exist", tmpstr)); - string_clear(tmpstr); + furi_string_free(tmpstr); // update data mu_check(flipper_format_rewind(flipper_format)); @@ -155,14 +155,14 @@ MU_TEST_1(flipper_format_read_and_update_test, FlipperFormat* flipper_format) { uint8_t hex_updated_data[COUNT_OF(test_hex_updated_data)]; mu_check(flipper_format_rewind(flipper_format)); - string_init(tmpstr); + tmpstr = furi_string_alloc(); mu_check(flipper_format_read_header(flipper_format, tmpstr, &version)); - mu_assert_string_eq(test_filetype, string_get_cstr(tmpstr)); + mu_assert_string_eq(test_filetype, furi_string_get_cstr(tmpstr)); mu_assert_int_eq(test_version, version); mu_check(flipper_format_read_string(flipper_format, test_string_key, tmpstr)); - mu_assert_string_eq(test_string_updated_data, string_get_cstr(tmpstr)); + mu_assert_string_eq(test_string_updated_data, furi_string_get_cstr(tmpstr)); mu_check(flipper_format_get_value_count(flipper_format, test_int_key, &count)); mu_assert_int_eq(COUNT_OF(test_int_updated_data), count); @@ -190,7 +190,7 @@ MU_TEST_1(flipper_format_read_and_update_test, FlipperFormat* flipper_format) { mu_check(!flipper_format_read_string(flipper_format, "Key that doesn't exist", tmpstr)); - string_clear(tmpstr); + furi_string_free(tmpstr); // update data mu_check(flipper_format_rewind(flipper_format)); @@ -214,14 +214,14 @@ MU_TEST_1(flipper_format_read_and_update_test, FlipperFormat* flipper_format) { uint8_t hex_new_data[COUNT_OF(test_hex_new_data)]; mu_check(flipper_format_rewind(flipper_format)); - string_init(tmpstr); + tmpstr = furi_string_alloc(); mu_check(flipper_format_read_header(flipper_format, tmpstr, &version)); - mu_assert_string_eq(test_filetype, string_get_cstr(tmpstr)); + mu_assert_string_eq(test_filetype, furi_string_get_cstr(tmpstr)); mu_assert_int_eq(test_version, version); mu_check(flipper_format_read_string(flipper_format, test_string_key, tmpstr)); - mu_assert_string_eq(test_string_updated_2_data, string_get_cstr(tmpstr)); + mu_assert_string_eq(test_string_updated_2_data, furi_string_get_cstr(tmpstr)); mu_check(flipper_format_get_value_count(flipper_format, test_int_key, &count)); mu_assert_int_eq(COUNT_OF(test_int_updated_2_data), count); @@ -255,7 +255,7 @@ MU_TEST_1(flipper_format_read_and_update_test, FlipperFormat* flipper_format) { mu_check(!flipper_format_read_string(flipper_format, "Key that doesn't exist", tmpstr)); - string_clear(tmpstr); + furi_string_free(tmpstr); // delete key test mu_check(flipper_format_rewind(flipper_format)); diff --git a/applications/debug/unit_tests/flipper_format/flipper_format_test.c b/applications/debug/unit_tests/flipper_format/flipper_format_test.c index 86e2df21eff..ccd5e751f17 100644 --- a/applications/debug/unit_tests/flipper_format/flipper_format_test.c +++ b/applications/debug/unit_tests/flipper_format/flipper_format_test.c @@ -102,8 +102,8 @@ static bool test_read(const char* file_name) { bool result = false; FlipperFormat* file = flipper_format_file_alloc(storage); - string_t string_value; - string_init(string_value); + FuriString* string_value; + string_value = furi_string_alloc(); uint32_t uint32_value; void* scratchpad = malloc(512); @@ -111,11 +111,11 @@ static bool test_read(const char* file_name) { if(!flipper_format_file_open_existing(file, file_name)) break; if(!flipper_format_read_header(file, string_value, &uint32_value)) break; - if(string_cmp_str(string_value, test_filetype) != 0) break; + if(furi_string_cmp_str(string_value, test_filetype) != 0) break; if(uint32_value != test_version) break; if(!flipper_format_read_string(file, test_string_key, string_value)) break; - if(string_cmp_str(string_value, test_string_data) != 0) break; + if(furi_string_cmp_str(string_value, test_string_data) != 0) break; if(!flipper_format_get_value_count(file, test_int_key, &uint32_value)) break; if(uint32_value != COUNT_OF(test_int_data)) break; @@ -150,7 +150,7 @@ static bool test_read(const char* file_name) { } while(false); free(scratchpad); - string_clear(string_value); + furi_string_free(string_value); flipper_format_free(file); @@ -164,8 +164,8 @@ static bool test_read_updated(const char* file_name) { bool result = false; FlipperFormat* file = flipper_format_file_alloc(storage); - string_t string_value; - string_init(string_value); + FuriString* string_value; + string_value = furi_string_alloc(); uint32_t uint32_value; void* scratchpad = malloc(512); @@ -173,11 +173,11 @@ static bool test_read_updated(const char* file_name) { if(!flipper_format_file_open_existing(file, file_name)) break; if(!flipper_format_read_header(file, string_value, &uint32_value)) break; - if(string_cmp_str(string_value, test_filetype) != 0) break; + if(furi_string_cmp_str(string_value, test_filetype) != 0) break; if(uint32_value != test_version) break; if(!flipper_format_read_string(file, test_string_key, string_value)) break; - if(string_cmp_str(string_value, test_string_updated_data) != 0) break; + if(furi_string_cmp_str(string_value, test_string_updated_data) != 0) break; if(!flipper_format_get_value_count(file, test_int_key, &uint32_value)) break; if(uint32_value != COUNT_OF(test_int_updated_data)) break; @@ -228,7 +228,7 @@ static bool test_read_updated(const char* file_name) { } while(false); free(scratchpad); - string_clear(string_value); + furi_string_free(string_value); flipper_format_free(file); @@ -401,14 +401,14 @@ static bool test_read_multikey(const char* file_name) { bool result = false; FlipperFormat* file = flipper_format_file_alloc(storage); - string_t string_value; - string_init(string_value); + FuriString* string_value; + string_value = furi_string_alloc(); uint32_t uint32_value; do { if(!flipper_format_file_open_existing(file, file_name)) break; if(!flipper_format_read_header(file, string_value, &uint32_value)) break; - if(string_cmp_str(string_value, test_filetype) != 0) break; + if(furi_string_cmp_str(string_value, test_filetype) != 0) break; if(uint32_value != test_version) break; bool error = false; @@ -429,7 +429,7 @@ static bool test_read_multikey(const char* file_name) { result = true; } while(false); - string_clear(string_value); + furi_string_free(string_value); flipper_format_free(file); furi_record_close(RECORD_STORAGE); diff --git a/applications/debug/unit_tests/furi/furi_string_test.c b/applications/debug/unit_tests/furi/furi_string_test.c new file mode 100644 index 00000000000..3ea95b92b86 --- /dev/null +++ b/applications/debug/unit_tests/furi/furi_string_test.c @@ -0,0 +1,469 @@ +#include +#include "../minunit.h" + +static void test_setup(void) { +} + +static void test_teardown(void) { +} + +static FuriString* furi_string_alloc_vprintf_test(const char format[], ...) { + va_list args; + va_start(args, format); + FuriString* string = furi_string_alloc_vprintf(format, args); + va_end(args); + return string; +} + +MU_TEST(mu_test_furi_string_alloc_free) { + FuriString* tmp; + FuriString* string; + + // test alloc and free + string = furi_string_alloc(); + mu_check(string != NULL); + mu_check(furi_string_empty(string)); + furi_string_free(string); + + // test furi_string_alloc_set_str and free + string = furi_string_alloc_set_str("test"); + mu_check(string != NULL); + mu_check(!furi_string_empty(string)); + mu_check(furi_string_cmp(string, "test") == 0); + furi_string_free(string); + + // test furi_string_alloc_set and free + tmp = furi_string_alloc_set("more"); + string = furi_string_alloc_set(tmp); + furi_string_free(tmp); + mu_check(string != NULL); + mu_check(!furi_string_empty(string)); + mu_check(furi_string_cmp(string, "more") == 0); + furi_string_free(string); + + // test alloc_printf and free + string = furi_string_alloc_printf("test %d %s %c 0x%02x", 1, "two", '3', 0x04); + mu_check(string != NULL); + mu_check(!furi_string_empty(string)); + mu_check(furi_string_cmp(string, "test 1 two 3 0x04") == 0); + furi_string_free(string); + + // test alloc_vprintf and free + string = furi_string_alloc_vprintf_test("test %d %s %c 0x%02x", 4, "five", '6', 0x07); + mu_check(string != NULL); + mu_check(!furi_string_empty(string)); + mu_check(furi_string_cmp(string, "test 4 five 6 0x07") == 0); + furi_string_free(string); + + // test alloc_move and free + tmp = furi_string_alloc_set("move"); + string = furi_string_alloc_move(tmp); + mu_check(string != NULL); + mu_check(!furi_string_empty(string)); + mu_check(furi_string_cmp(string, "move") == 0); + furi_string_free(string); +} + +MU_TEST(mu_test_furi_string_mem) { + FuriString* string = furi_string_alloc_set("test"); + mu_check(string != NULL); + mu_check(!furi_string_empty(string)); + + // TODO: how to test furi_string_reserve? + + // test furi_string_reset + furi_string_reset(string); + mu_check(furi_string_empty(string)); + + // test furi_string_swap + furi_string_set(string, "test"); + FuriString* swap_string = furi_string_alloc_set("swap"); + furi_string_swap(string, swap_string); + mu_check(furi_string_cmp(string, "swap") == 0); + mu_check(furi_string_cmp(swap_string, "test") == 0); + furi_string_free(swap_string); + + // test furi_string_move + FuriString* move_string = furi_string_alloc_set("move"); + furi_string_move(string, move_string); + mu_check(furi_string_cmp(string, "move") == 0); + // move_string is now empty + // and tested by leaked memory check at the end of the tests + + furi_string_set(string, "abracadabra"); + + // test furi_string_hash + mu_assert_int_eq(0xc3bc16d7, furi_string_hash(string)); + + // test furi_string_size + mu_assert_int_eq(11, furi_string_size(string)); + + // test furi_string_empty + mu_check(!furi_string_empty(string)); + furi_string_reset(string); + mu_check(furi_string_empty(string)); + + furi_string_free(string); +} + +MU_TEST(mu_test_furi_string_getters) { + FuriString* string = furi_string_alloc_set("test"); + + // test furi_string_get_char + mu_check(furi_string_get_char(string, 0) == 't'); + mu_check(furi_string_get_char(string, 1) == 'e'); + mu_check(furi_string_get_char(string, 2) == 's'); + mu_check(furi_string_get_char(string, 3) == 't'); + + // test furi_string_get_cstr + mu_assert_string_eq("test", furi_string_get_cstr(string)); + furi_string_free(string); +} + +static FuriString* furi_string_vprintf_test(FuriString* string, const char format[], ...) { + va_list args; + va_start(args, format); + furi_string_vprintf(string, format, args); + va_end(args); + return string; +} + +MU_TEST(mu_test_furi_string_setters) { + FuriString* tmp; + FuriString* string = furi_string_alloc(); + + // test furi_string_set_str + furi_string_set_str(string, "test"); + mu_assert_string_eq("test", furi_string_get_cstr(string)); + + // test furi_string_set + tmp = furi_string_alloc_set("more"); + furi_string_set(string, tmp); + furi_string_free(tmp); + mu_assert_string_eq("more", furi_string_get_cstr(string)); + + // test furi_string_set_strn + furi_string_set_strn(string, "test", 2); + mu_assert_string_eq("te", furi_string_get_cstr(string)); + + // test furi_string_set_char + furi_string_set_char(string, 0, 'a'); + furi_string_set_char(string, 1, 'b'); + mu_assert_string_eq("ab", furi_string_get_cstr(string)); + + // test furi_string_set_n + tmp = furi_string_alloc_set("dodecahedron"); + furi_string_set_n(string, tmp, 4, 5); + furi_string_free(tmp); + mu_assert_string_eq("cahed", furi_string_get_cstr(string)); + + // test furi_string_printf + furi_string_printf(string, "test %d %s %c 0x%02x", 1, "two", '3', 0x04); + mu_assert_string_eq("test 1 two 3 0x04", furi_string_get_cstr(string)); + + // test furi_string_vprintf + furi_string_vprintf_test(string, "test %d %s %c 0x%02x", 4, "five", '6', 0x07); + mu_assert_string_eq("test 4 five 6 0x07", furi_string_get_cstr(string)); + + furi_string_free(string); +} + +static FuriString* furi_string_cat_vprintf_test(FuriString* string, const char format[], ...) { + va_list args; + va_start(args, format); + furi_string_cat_vprintf(string, format, args); + va_end(args); + return string; +} + +MU_TEST(mu_test_furi_string_appends) { + FuriString* tmp; + FuriString* string = furi_string_alloc(); + + // test furi_string_push_back + furi_string_push_back(string, 't'); + furi_string_push_back(string, 'e'); + furi_string_push_back(string, 's'); + furi_string_push_back(string, 't'); + mu_assert_string_eq("test", furi_string_get_cstr(string)); + furi_string_push_back(string, '!'); + mu_assert_string_eq("test!", furi_string_get_cstr(string)); + + // test furi_string_cat_str + furi_string_cat_str(string, "test"); + mu_assert_string_eq("test!test", furi_string_get_cstr(string)); + + // test furi_string_cat + tmp = furi_string_alloc_set("more"); + furi_string_cat(string, tmp); + furi_string_free(tmp); + mu_assert_string_eq("test!testmore", furi_string_get_cstr(string)); + + // test furi_string_cat_printf + furi_string_cat_printf(string, "test %d %s %c 0x%02x", 1, "two", '3', 0x04); + mu_assert_string_eq("test!testmoretest 1 two 3 0x04", furi_string_get_cstr(string)); + + // test furi_string_cat_vprintf + furi_string_cat_vprintf_test(string, "test %d %s %c 0x%02x", 4, "five", '6', 0x07); + mu_assert_string_eq( + "test!testmoretest 1 two 3 0x04test 4 five 6 0x07", furi_string_get_cstr(string)); + + furi_string_free(string); +} + +MU_TEST(mu_test_furi_string_compare) { + FuriString* string_1 = furi_string_alloc_set("string_1"); + FuriString* string_2 = furi_string_alloc_set("string_2"); + + // test furi_string_cmp + mu_assert_int_eq(0, furi_string_cmp(string_1, string_1)); + mu_assert_int_eq(0, furi_string_cmp(string_2, string_2)); + mu_assert_int_eq(-1, furi_string_cmp(string_1, string_2)); + mu_assert_int_eq(1, furi_string_cmp(string_2, string_1)); + + // test furi_string_cmp_str + mu_assert_int_eq(0, furi_string_cmp_str(string_1, "string_1")); + mu_assert_int_eq(0, furi_string_cmp_str(string_2, "string_2")); + mu_assert_int_eq(-1, furi_string_cmp_str(string_1, "string_2")); + mu_assert_int_eq(1, furi_string_cmp_str(string_2, "string_1")); + + // test furi_string_cmpi + furi_string_set(string_1, "string"); + furi_string_set(string_2, "StrIng"); + mu_assert_int_eq(0, furi_string_cmpi(string_1, string_1)); + mu_assert_int_eq(0, furi_string_cmpi(string_2, string_2)); + mu_assert_int_eq(0, furi_string_cmpi(string_1, string_2)); + mu_assert_int_eq(0, furi_string_cmpi(string_2, string_1)); + furi_string_set(string_1, "string_1"); + furi_string_set(string_2, "StrIng_2"); + mu_assert_int_eq(32, furi_string_cmp(string_1, string_2)); + mu_assert_int_eq(-32, furi_string_cmp(string_2, string_1)); + mu_assert_int_eq(-1, furi_string_cmpi(string_1, string_2)); + mu_assert_int_eq(1, furi_string_cmpi(string_2, string_1)); + + // test furi_string_cmpi_str + furi_string_set(string_1, "string"); + mu_assert_int_eq(0, furi_string_cmp_str(string_1, "string")); + mu_assert_int_eq(32, furi_string_cmp_str(string_1, "String")); + mu_assert_int_eq(32, furi_string_cmp_str(string_1, "STring")); + mu_assert_int_eq(32, furi_string_cmp_str(string_1, "STRing")); + mu_assert_int_eq(32, furi_string_cmp_str(string_1, "STRIng")); + mu_assert_int_eq(32, furi_string_cmp_str(string_1, "STRINg")); + mu_assert_int_eq(32, furi_string_cmp_str(string_1, "STRING")); + mu_assert_int_eq(0, furi_string_cmpi_str(string_1, "string")); + mu_assert_int_eq(0, furi_string_cmpi_str(string_1, "String")); + mu_assert_int_eq(0, furi_string_cmpi_str(string_1, "STring")); + mu_assert_int_eq(0, furi_string_cmpi_str(string_1, "STRing")); + mu_assert_int_eq(0, furi_string_cmpi_str(string_1, "STRIng")); + mu_assert_int_eq(0, furi_string_cmpi_str(string_1, "STRINg")); + mu_assert_int_eq(0, furi_string_cmpi_str(string_1, "STRING")); + + furi_string_free(string_1); + furi_string_free(string_2); +} + +MU_TEST(mu_test_furi_string_search) { + // 012345678901234567 + FuriString* haystack = furi_string_alloc_set("test321test123test"); + FuriString* needle = furi_string_alloc_set("test"); + + // test furi_string_search + mu_assert_int_eq(0, furi_string_search(haystack, needle)); + mu_assert_int_eq(7, furi_string_search(haystack, needle, 1)); + mu_assert_int_eq(14, furi_string_search(haystack, needle, 8)); + mu_assert_int_eq(FURI_STRING_FAILURE, furi_string_search(haystack, needle, 15)); + + FuriString* tmp = furi_string_alloc_set("testnone"); + mu_assert_int_eq(FURI_STRING_FAILURE, furi_string_search(haystack, tmp)); + furi_string_free(tmp); + + // test furi_string_search_str + mu_assert_int_eq(0, furi_string_search_str(haystack, "test")); + mu_assert_int_eq(7, furi_string_search_str(haystack, "test", 1)); + mu_assert_int_eq(14, furi_string_search_str(haystack, "test", 8)); + mu_assert_int_eq(4, furi_string_search_str(haystack, "321")); + mu_assert_int_eq(11, furi_string_search_str(haystack, "123")); + mu_assert_int_eq(FURI_STRING_FAILURE, furi_string_search_str(haystack, "testnone")); + mu_assert_int_eq(FURI_STRING_FAILURE, furi_string_search_str(haystack, "test", 15)); + + // test furi_string_search_char + mu_assert_int_eq(0, furi_string_search_char(haystack, 't')); + mu_assert_int_eq(1, furi_string_search_char(haystack, 'e')); + mu_assert_int_eq(2, furi_string_search_char(haystack, 's')); + mu_assert_int_eq(3, furi_string_search_char(haystack, 't', 1)); + mu_assert_int_eq(7, furi_string_search_char(haystack, 't', 4)); + mu_assert_int_eq(FURI_STRING_FAILURE, furi_string_search_char(haystack, 'x')); + + // test furi_string_search_rchar + mu_assert_int_eq(17, furi_string_search_rchar(haystack, 't')); + mu_assert_int_eq(15, furi_string_search_rchar(haystack, 'e')); + mu_assert_int_eq(16, furi_string_search_rchar(haystack, 's')); + mu_assert_int_eq(13, furi_string_search_rchar(haystack, '3')); + mu_assert_int_eq(FURI_STRING_FAILURE, furi_string_search_rchar(haystack, '3', 14)); + mu_assert_int_eq(FURI_STRING_FAILURE, furi_string_search_rchar(haystack, 'x')); + + furi_string_free(haystack); + furi_string_free(needle); +} + +MU_TEST(mu_test_furi_string_equality) { + FuriString* string = furi_string_alloc_set("test"); + FuriString* string_eq = furi_string_alloc_set("test"); + FuriString* string_neq = furi_string_alloc_set("test2"); + + // test furi_string_equal + mu_check(furi_string_equal(string, string_eq)); + mu_check(!furi_string_equal(string, string_neq)); + + // test furi_string_equal_str + mu_check(furi_string_equal_str(string, "test")); + mu_check(!furi_string_equal_str(string, "test2")); + mu_check(furi_string_equal_str(string_neq, "test2")); + mu_check(!furi_string_equal_str(string_neq, "test")); + + furi_string_free(string); + furi_string_free(string_eq); + furi_string_free(string_neq); +} + +MU_TEST(mu_test_furi_string_replace) { + FuriString* needle = furi_string_alloc_set("test"); + FuriString* replace = furi_string_alloc_set("replace"); + FuriString* string = furi_string_alloc_set("test123test"); + + // test furi_string_replace_at + furi_string_replace_at(string, 4, 3, "!biglongword!"); + mu_assert_string_eq("test!biglongword!test", furi_string_get_cstr(string)); + + // test furi_string_replace + mu_assert_int_eq(17, furi_string_replace(string, needle, replace, 1)); + mu_assert_string_eq("test!biglongword!replace", furi_string_get_cstr(string)); + mu_assert_int_eq(0, furi_string_replace(string, needle, replace)); + mu_assert_string_eq("replace!biglongword!replace", furi_string_get_cstr(string)); + mu_assert_int_eq(FURI_STRING_FAILURE, furi_string_replace(string, needle, replace)); + mu_assert_string_eq("replace!biglongword!replace", furi_string_get_cstr(string)); + + // test furi_string_replace_str + mu_assert_int_eq(20, furi_string_replace_str(string, "replace", "test", 1)); + mu_assert_string_eq("replace!biglongword!test", furi_string_get_cstr(string)); + mu_assert_int_eq(0, furi_string_replace_str(string, "replace", "test")); + mu_assert_string_eq("test!biglongword!test", furi_string_get_cstr(string)); + mu_assert_int_eq(FURI_STRING_FAILURE, furi_string_replace_str(string, "replace", "test")); + mu_assert_string_eq("test!biglongword!test", furi_string_get_cstr(string)); + + // test furi_string_replace_all + furi_string_replace_all(string, needle, replace); + mu_assert_string_eq("replace!biglongword!replace", furi_string_get_cstr(string)); + + // test furi_string_replace_all_str + furi_string_replace_all_str(string, "replace", "test"); + mu_assert_string_eq("test!biglongword!test", furi_string_get_cstr(string)); + + furi_string_free(string); + furi_string_free(needle); + furi_string_free(replace); +} + +MU_TEST(mu_test_furi_string_start_end) { + FuriString* string = furi_string_alloc_set("start_end"); + FuriString* start = furi_string_alloc_set("start"); + FuriString* end = furi_string_alloc_set("end"); + + // test furi_string_start_with + mu_check(furi_string_start_with(string, start)); + mu_check(!furi_string_start_with(string, end)); + + // test furi_string_start_with_str + mu_check(furi_string_start_with_str(string, "start")); + mu_check(!furi_string_start_with_str(string, "end")); + + // test furi_string_end_with + mu_check(furi_string_end_with(string, end)); + mu_check(!furi_string_end_with(string, start)); + + // test furi_string_end_with_str + mu_check(furi_string_end_with_str(string, "end")); + mu_check(!furi_string_end_with_str(string, "start")); + + furi_string_free(string); + furi_string_free(start); + furi_string_free(end); +} + +MU_TEST(mu_test_furi_string_trim) { + FuriString* string = furi_string_alloc_set("biglongstring"); + + // test furi_string_left + furi_string_left(string, 7); + mu_assert_string_eq("biglong", furi_string_get_cstr(string)); + + // test furi_string_right + furi_string_right(string, 3); + mu_assert_string_eq("long", furi_string_get_cstr(string)); + + // test furi_string_mid + furi_string_mid(string, 1, 2); + mu_assert_string_eq("on", furi_string_get_cstr(string)); + + // test furi_string_trim + furi_string_set(string, " \n\r\tbiglongstring \n\r\t "); + furi_string_trim(string); + mu_assert_string_eq("biglongstring", furi_string_get_cstr(string)); + furi_string_set(string, "aaaabaaaabbaaabaaaabbtestaaaaaabbaaabaababaa"); + furi_string_trim(string, "ab"); + mu_assert_string_eq("test", furi_string_get_cstr(string)); + + furi_string_free(string); +} + +MU_TEST(mu_test_furi_string_utf8) { + FuriString* utf8_string = furi_string_alloc_set("イルカ"); + + // test furi_string_utf8_length + mu_assert_int_eq(9, furi_string_size(utf8_string)); + mu_assert_int_eq(3, furi_string_utf8_length(utf8_string)); + + // test furi_string_utf8_decode + const uint8_t dolphin_emoji_array[4] = {0xF0, 0x9F, 0x90, 0xAC}; + FuriStringUTF8State state = FuriStringUTF8StateStarting; + FuriStringUnicodeValue value = 0; + furi_string_utf8_decode(dolphin_emoji_array[0], &state, &value); + mu_assert_int_eq(FuriStringUTF8StateDecoding3, state); + furi_string_utf8_decode(dolphin_emoji_array[1], &state, &value); + mu_assert_int_eq(FuriStringUTF8StateDecoding2, state); + furi_string_utf8_decode(dolphin_emoji_array[2], &state, &value); + mu_assert_int_eq(FuriStringUTF8StateDecoding1, state); + furi_string_utf8_decode(dolphin_emoji_array[3], &state, &value); + mu_assert_int_eq(FuriStringUTF8StateStarting, state); + mu_assert_int_eq(0x1F42C, value); + + // test furi_string_utf8_push + furi_string_set(utf8_string, ""); + furi_string_utf8_push(utf8_string, value); + mu_assert_string_eq("🐬", furi_string_get_cstr(utf8_string)); + + furi_string_free(utf8_string); +} + +MU_TEST_SUITE(test_suite) { + MU_SUITE_CONFIGURE(&test_setup, &test_teardown); + + MU_RUN_TEST(mu_test_furi_string_alloc_free); + MU_RUN_TEST(mu_test_furi_string_mem); + MU_RUN_TEST(mu_test_furi_string_getters); + MU_RUN_TEST(mu_test_furi_string_setters); + MU_RUN_TEST(mu_test_furi_string_appends); + MU_RUN_TEST(mu_test_furi_string_compare); + MU_RUN_TEST(mu_test_furi_string_search); + MU_RUN_TEST(mu_test_furi_string_equality); + MU_RUN_TEST(mu_test_furi_string_replace); + MU_RUN_TEST(mu_test_furi_string_start_end); + MU_RUN_TEST(mu_test_furi_string_trim); + MU_RUN_TEST(mu_test_furi_string_utf8); +} + +int run_minunit_test_furi_string() { + MU_RUN_SUITE(test_suite); + + return MU_EXIT_CODE; +} \ No newline at end of file diff --git a/applications/debug/unit_tests/infrared/infrared_test.c b/applications/debug/unit_tests/infrared/infrared_test.c index 32266c48e59..d861f266ed2 100644 --- a/applications/debug/unit_tests/infrared/infrared_test.c +++ b/applications/debug/unit_tests/infrared/infrared_test.c @@ -11,7 +11,7 @@ typedef struct { InfraredDecoderHandler* decoder_handler; InfraredEncoderHandler* encoder_handler; - string_t file_path; + FuriString* file_path; FlipperFormat* ff; } InfraredTest; @@ -23,7 +23,7 @@ static void infrared_test_alloc() { test->decoder_handler = infrared_alloc_decoder(); test->encoder_handler = infrared_alloc_encoder(); test->ff = flipper_format_buffered_file_alloc(storage); - string_init(test->file_path); + test->file_path = furi_string_alloc(); } static void infrared_test_free() { @@ -31,18 +31,18 @@ static void infrared_test_free() { infrared_free_decoder(test->decoder_handler); infrared_free_encoder(test->encoder_handler); flipper_format_free(test->ff); - string_clear(test->file_path); + furi_string_free(test->file_path); furi_record_close(RECORD_STORAGE); free(test); test = NULL; } static bool infrared_test_prepare_file(const char* protocol_name) { - string_t file_type; - string_init(file_type); + FuriString* file_type; + file_type = furi_string_alloc(); bool success = false; - string_printf( + furi_string_printf( test->file_path, "%s%s%s%s", IR_TEST_FILES_DIR, @@ -52,14 +52,15 @@ static bool infrared_test_prepare_file(const char* protocol_name) { do { uint32_t format_version; - if(!flipper_format_buffered_file_open_existing(test->ff, string_get_cstr(test->file_path))) + if(!flipper_format_buffered_file_open_existing( + test->ff, furi_string_get_cstr(test->file_path))) break; if(!flipper_format_read_header(test->ff, file_type, &format_version)) break; - if(string_cmp_str(file_type, "IR tests file") || format_version != 1) break; + if(furi_string_cmp_str(file_type, "IR tests file") || format_version != 1) break; success = true; } while(false); - string_clear(file_type); + furi_string_free(file_type); return success; } @@ -68,18 +69,18 @@ static bool infrared_test_load_raw_signal( const char* signal_name, uint32_t** timings, uint32_t* timings_count) { - string_t buf; - string_init(buf); + FuriString* buf; + buf = furi_string_alloc(); bool success = false; do { bool is_name_found = false; for(; !is_name_found && flipper_format_read_string(ff, "name", buf); - is_name_found = !string_cmp_str(buf, signal_name)) + is_name_found = !furi_string_cmp(buf, signal_name)) ; if(!is_name_found) break; - if(!flipper_format_read_string(ff, "type", buf) || string_cmp_str(buf, "raw")) break; + if(!flipper_format_read_string(ff, "type", buf) || furi_string_cmp_str(buf, "raw")) break; if(!flipper_format_get_value_count(ff, "data", timings_count)) break; if(!*timings_count) break; @@ -91,18 +92,18 @@ static bool infrared_test_load_raw_signal( success = true; } while(false); - string_clear(buf); + furi_string_free(buf); return success; } static bool infrared_test_read_message(FlipperFormat* ff, InfraredMessage* message) { - string_t buf; - string_init(buf); + FuriString* buf; + buf = furi_string_alloc(); bool success = false; do { if(!flipper_format_read_string(ff, "protocol", buf)) break; - message->protocol = infrared_get_protocol_by_name(string_get_cstr(buf)); + message->protocol = infrared_get_protocol_by_name(furi_string_get_cstr(buf)); if(!infrared_is_protocol_valid(message->protocol)) break; if(!flipper_format_read_hex(ff, "address", (uint8_t*)&message->address, sizeof(uint32_t))) break; @@ -112,7 +113,7 @@ static bool infrared_test_read_message(FlipperFormat* ff, InfraredMessage* messa success = true; } while(false); - string_clear(buf); + furi_string_free(buf); return success; } @@ -121,18 +122,19 @@ static bool infrared_test_load_messages( const char* signal_name, InfraredMessage** messages, uint32_t* messages_count) { - string_t buf; - string_init(buf); + FuriString* buf; + buf = furi_string_alloc(); bool success = false; do { bool is_name_found = false; for(; !is_name_found && flipper_format_read_string(ff, "name", buf); - is_name_found = !string_cmp_str(buf, signal_name)) + is_name_found = !furi_string_cmp(buf, signal_name)) ; if(!is_name_found) break; - if(!flipper_format_read_string(ff, "type", buf) || string_cmp_str(buf, "parsed_array")) + if(!flipper_format_read_string(ff, "type", buf) || + furi_string_cmp_str(buf, "parsed_array")) break; if(!flipper_format_read_uint32(ff, "count", messages_count, 1)) break; if(!*messages_count) break; @@ -151,7 +153,7 @@ static bool infrared_test_load_messages( success = true; } while(false); - string_clear(buf); + furi_string_free(buf); return success; } @@ -213,26 +215,26 @@ static void infrared_test_run_encoder(InfraredProtocol protocol, uint32_t test_i InfraredMessage* input_messages; uint32_t input_messages_count; - string_t buf; - string_init(buf); + FuriString* buf; + buf = furi_string_alloc(); const char* protocol_name = infrared_get_protocol_name(protocol); mu_assert(infrared_test_prepare_file(protocol_name), "Failed to prepare test file"); - string_printf(buf, "encoder_input%d", test_index); + furi_string_printf(buf, "encoder_input%d", test_index); mu_assert( infrared_test_load_messages( - test->ff, string_get_cstr(buf), &input_messages, &input_messages_count), + test->ff, furi_string_get_cstr(buf), &input_messages, &input_messages_count), "Failed to load messages from file"); - string_printf(buf, "encoder_expected%d", test_index); + furi_string_printf(buf, "encoder_expected%d", test_index); mu_assert( infrared_test_load_raw_signal( - test->ff, string_get_cstr(buf), &expected_timings, &expected_timings_count), + test->ff, furi_string_get_cstr(buf), &expected_timings, &expected_timings_count), "Failed to load raw signal from file"); flipper_format_buffered_file_close(test->ff); - string_clear(buf); + furi_string_free(buf); uint32_t j = 0; timings = malloc(sizeof(uint32_t) * timings_count); @@ -267,22 +269,22 @@ static void infrared_test_run_encoder_decoder(InfraredProtocol protocol, uint32_ uint32_t input_messages_count; bool level = false; - string_t buf; - string_init(buf); + FuriString* buf; + buf = furi_string_alloc(); timings = malloc(sizeof(uint32_t) * timings_count); const char* protocol_name = infrared_get_protocol_name(protocol); mu_assert(infrared_test_prepare_file(protocol_name), "Failed to prepare test file"); - string_printf(buf, "encoder_decoder_input%d", test_index); + furi_string_printf(buf, "encoder_decoder_input%d", test_index); mu_assert( infrared_test_load_messages( - test->ff, string_get_cstr(buf), &input_messages, &input_messages_count), + test->ff, furi_string_get_cstr(buf), &input_messages, &input_messages_count), "Failed to load messages from file"); flipper_format_buffered_file_close(test->ff); - string_clear(buf); + furi_string_free(buf); for(uint32_t message_counter = 0; message_counter < input_messages_count; ++message_counter) { const InfraredMessage* message_encoded = &input_messages[message_counter]; @@ -327,25 +329,27 @@ static void infrared_test_run_decoder(InfraredProtocol protocol, uint32_t test_i InfraredMessage* messages; uint32_t messages_count; - string_t buf; - string_init(buf); + FuriString* buf; + buf = furi_string_alloc(); mu_assert( infrared_test_prepare_file(infrared_get_protocol_name(protocol)), "Failed to prepare test file"); - string_printf(buf, "decoder_input%d", test_index); + furi_string_printf(buf, "decoder_input%d", test_index); mu_assert( - infrared_test_load_raw_signal(test->ff, string_get_cstr(buf), &timings, &timings_count), + infrared_test_load_raw_signal( + test->ff, furi_string_get_cstr(buf), &timings, &timings_count), "Failed to load raw signal from file"); - string_printf(buf, "decoder_expected%d", test_index); + furi_string_printf(buf, "decoder_expected%d", test_index); mu_assert( - infrared_test_load_messages(test->ff, string_get_cstr(buf), &messages, &messages_count), + infrared_test_load_messages( + test->ff, furi_string_get_cstr(buf), &messages, &messages_count), "Failed to load messages from file"); flipper_format_buffered_file_close(test->ff); - string_clear(buf); + furi_string_free(buf); InfraredMessage message_decoded_check_local; bool level = 0; diff --git a/applications/debug/unit_tests/nfc/nfc_test.c b/applications/debug/unit_tests/nfc/nfc_test.c index 580943f242a..c1468c86fcb 100644 --- a/applications/debug/unit_tests/nfc/nfc_test.c +++ b/applications/debug/unit_tests/nfc/nfc_test.c @@ -53,14 +53,15 @@ static bool nfc_test_read_signal_from_file(const char* file_name) { bool success = false; FlipperFormat* file = flipper_format_file_alloc(nfc_test->storage); - string_t file_type; - string_init(file_type); + FuriString* file_type; + file_type = furi_string_alloc(); uint32_t file_version = 0; do { if(!flipper_format_file_open_existing(file, file_name)) break; if(!flipper_format_read_header(file, file_type, &file_version)) break; - if(string_cmp_str(file_type, nfc_test_file_type) || file_version != nfc_test_file_version) + if(furi_string_cmp_str(file_type, nfc_test_file_type) || + file_version != nfc_test_file_version) break; if(!flipper_format_read_uint32(file, "Data length", &nfc_test->test_data_len, 1)) break; if(nfc_test->test_data_len > NFC_TEST_DATA_MAX_LEN) break; @@ -76,7 +77,7 @@ static bool nfc_test_read_signal_from_file(const char* file_name) { success = true; } while(false); - string_clear(file_type); + furi_string_free(file_type); flipper_format_free(file); return success; @@ -174,8 +175,8 @@ MU_TEST(nfc_digital_signal_test) { MU_TEST(mf_classic_dict_test) { MfClassicDict* instance = NULL; uint64_t key = 0; - string_t temp_str; - string_init(temp_str); + FuriString* temp_str; + temp_str = furi_string_alloc(); instance = mf_classic_dict_alloc(MfClassicDictTypeUnitTest); mu_assert(instance != NULL, "mf_classic_dict_alloc\r\n"); @@ -184,7 +185,7 @@ MU_TEST(mf_classic_dict_test) { mf_classic_dict_get_total_keys(instance) == 0, "mf_classic_dict_get_total_keys == 0 assert failed\r\n"); - string_set(temp_str, "2196FAD8115B"); + furi_string_set(temp_str, "2196FAD8115B"); mu_assert( mf_classic_dict_add_key_str(instance, temp_str), "mf_classic_dict_add_key == true assert failed\r\n"); @@ -199,7 +200,7 @@ MU_TEST(mf_classic_dict_test) { mf_classic_dict_get_key_at_index_str(instance, temp_str, 0), "mf_classic_dict_get_key_at_index_str == true assert failed\r\n"); mu_assert( - string_cmp(temp_str, "2196FAD8115B") == 0, + furi_string_cmp(temp_str, "2196FAD8115B") == 0, "string_cmp(temp_str, \"2196FAD8115B\") == 0 assert failed\r\n"); mu_assert(mf_classic_dict_rewind(instance), "mf_classic_dict_rewind == 1 assert failed\r\n"); @@ -216,7 +217,7 @@ MU_TEST(mf_classic_dict_test) { "mf_classic_dict_delete_index == true assert failed\r\n"); mf_classic_dict_free(instance); - string_clear(temp_str); + furi_string_free(temp_str); } MU_TEST_SUITE(nfc) { diff --git a/applications/debug/unit_tests/storage/dirwalk_test.c b/applications/debug/unit_tests/storage/dirwalk_test.c index db3d91a96e0..97aaa358071 100644 --- a/applications/debug/unit_tests/storage/dirwalk_test.c +++ b/applications/debug/unit_tests/storage/dirwalk_test.c @@ -75,7 +75,7 @@ typedef struct { bool visited; } StorageTestPath; -DICT_DEF2(StorageTestPathDict, string_t, STRING_OPLIST, StorageTestPath, M_POD_OPLIST) +DICT_DEF2(StorageTestPathDict, FuriString*, FURI_STRING_OPLIST, StorageTestPath, M_POD_OPLIST) static StorageTestPathDict_t* storage_test_paths_alloc(const StorageTestPathDesc paths[], size_t paths_count) { @@ -83,15 +83,15 @@ static StorageTestPathDict_t* StorageTestPathDict_init(*data); for(size_t i = 0; i < paths_count; i++) { - string_t key; - string_init_set(key, paths[i].path); + FuriString* key; + key = furi_string_alloc_set(paths[i].path); StorageTestPath value = { .is_dir = paths[i].is_dir, .visited = false, }; StorageTestPathDict_set_at(*data, key, value); - string_clear(key); + furi_string_free(key); } return data; @@ -102,7 +102,7 @@ static void storage_test_paths_free(StorageTestPathDict_t* data) { free(data); } -static bool storage_test_paths_mark(StorageTestPathDict_t* data, string_t path, bool is_dir) { +static bool storage_test_paths_mark(StorageTestPathDict_t* data, FuriString* path, bool is_dir) { bool found = false; StorageTestPath* record = StorageTestPathDict_get(*data, path); @@ -148,27 +148,27 @@ static bool write_file_13DA(Storage* storage, const char* path) { } static void storage_dirs_create(Storage* storage, const char* base) { - string_t path; - string_init(path); + FuriString* path; + path = furi_string_alloc(); storage_common_mkdir(storage, base); for(size_t i = 0; i < COUNT_OF(storage_test_dirwalk_paths); i++) { - string_printf(path, "%s/%s", base, storage_test_dirwalk_paths[i]); - storage_common_mkdir(storage, string_get_cstr(path)); + furi_string_printf(path, "%s/%s", base, storage_test_dirwalk_paths[i]); + storage_common_mkdir(storage, furi_string_get_cstr(path)); } for(size_t i = 0; i < COUNT_OF(storage_test_dirwalk_files); i++) { - string_printf(path, "%s/%s", base, storage_test_dirwalk_files[i]); - write_file_13DA(storage, string_get_cstr(path)); + furi_string_printf(path, "%s/%s", base, storage_test_dirwalk_files[i]); + write_file_13DA(storage, furi_string_get_cstr(path)); } - string_clear(path); + furi_string_free(path); } MU_TEST_1(test_dirwalk_full, Storage* storage) { - string_t path; - string_init(path); + FuriString* path; + path = furi_string_alloc(); FileInfo fileinfo; StorageTestPathDict_t* paths = @@ -178,12 +178,12 @@ MU_TEST_1(test_dirwalk_full, Storage* storage) { mu_check(dir_walk_open(dir_walk, EXT_PATH("dirwalk"))); while(dir_walk_read(dir_walk, path, &fileinfo) == DirWalkOK) { - string_right(path, strlen(EXT_PATH("dirwalk/"))); + furi_string_right(path, strlen(EXT_PATH("dirwalk/"))); mu_check(storage_test_paths_mark(paths, path, (fileinfo.flags & FSF_DIRECTORY))); } dir_walk_free(dir_walk); - string_clear(path); + furi_string_free(path); mu_check(storage_test_paths_check(paths) == false); @@ -191,8 +191,8 @@ MU_TEST_1(test_dirwalk_full, Storage* storage) { } MU_TEST_1(test_dirwalk_no_recursive, Storage* storage) { - string_t path; - string_init(path); + FuriString* path; + path = furi_string_alloc(); FileInfo fileinfo; StorageTestPathDict_t* paths = storage_test_paths_alloc( @@ -203,12 +203,12 @@ MU_TEST_1(test_dirwalk_no_recursive, Storage* storage) { mu_check(dir_walk_open(dir_walk, EXT_PATH("dirwalk"))); while(dir_walk_read(dir_walk, path, &fileinfo) == DirWalkOK) { - string_right(path, strlen(EXT_PATH("dirwalk/"))); + furi_string_right(path, strlen(EXT_PATH("dirwalk/"))); mu_check(storage_test_paths_mark(paths, path, (fileinfo.flags & FSF_DIRECTORY))); } dir_walk_free(dir_walk); - string_clear(path); + furi_string_free(path); mu_check(storage_test_paths_check(paths) == false); @@ -230,8 +230,8 @@ static bool test_dirwalk_filter_no_folder_ext(const char* name, FileInfo* filein } MU_TEST_1(test_dirwalk_filter, Storage* storage) { - string_t path; - string_init(path); + FuriString* path; + path = furi_string_alloc(); FileInfo fileinfo; StorageTestPathDict_t* paths = storage_test_paths_alloc( @@ -242,12 +242,12 @@ MU_TEST_1(test_dirwalk_filter, Storage* storage) { mu_check(dir_walk_open(dir_walk, EXT_PATH("dirwalk"))); while(dir_walk_read(dir_walk, path, &fileinfo) == DirWalkOK) { - string_right(path, strlen(EXT_PATH("dirwalk/"))); + furi_string_right(path, strlen(EXT_PATH("dirwalk/"))); mu_check(storage_test_paths_mark(paths, path, (fileinfo.flags & FSF_DIRECTORY))); } dir_walk_free(dir_walk); - string_clear(path); + furi_string_free(path); mu_check(storage_test_paths_check(paths) == false); diff --git a/applications/debug/unit_tests/storage/storage_test.c b/applications/debug/unit_tests/storage/storage_test.c index 099be0d5ca3..7c1c669ff4a 100644 --- a/applications/debug/unit_tests/storage/storage_test.c +++ b/applications/debug/unit_tests/storage/storage_test.c @@ -211,22 +211,22 @@ static bool check_file_13DA(Storage* storage, const char* path) { } static void storage_dir_create(Storage* storage, const char* base) { - string_t path; - string_init(path); + FuriString* path; + path = furi_string_alloc(); storage_common_mkdir(storage, base); for(size_t i = 0; i < COUNT_OF(storage_copy_test_paths); i++) { - string_printf(path, "%s/%s", base, storage_copy_test_paths[i]); - storage_common_mkdir(storage, string_get_cstr(path)); + furi_string_printf(path, "%s/%s", base, storage_copy_test_paths[i]); + storage_common_mkdir(storage, furi_string_get_cstr(path)); } for(size_t i = 0; i < COUNT_OF(storage_copy_test_files); i++) { - string_printf(path, "%s/%s", base, storage_copy_test_files[i]); - write_file_13DA(storage, string_get_cstr(path)); + furi_string_printf(path, "%s/%s", base, storage_copy_test_files[i]); + write_file_13DA(storage, furi_string_get_cstr(path)); } - string_clear(path); + furi_string_free(path); } static void storage_dir_remove(Storage* storage, const char* base) { @@ -235,15 +235,15 @@ static void storage_dir_remove(Storage* storage, const char* base) { static bool storage_dir_rename_check(Storage* storage, const char* base) { bool result = false; - string_t path; - string_init(path); + FuriString* path; + path = furi_string_alloc(); result = (storage_common_stat(storage, base, NULL) == FSE_OK); if(result) { for(size_t i = 0; i < COUNT_OF(storage_copy_test_paths); i++) { - string_printf(path, "%s/%s", base, storage_copy_test_paths[i]); - result = (storage_common_stat(storage, string_get_cstr(path), NULL) == FSE_OK); + furi_string_printf(path, "%s/%s", base, storage_copy_test_paths[i]); + result = (storage_common_stat(storage, furi_string_get_cstr(path), NULL) == FSE_OK); if(!result) { break; } @@ -252,15 +252,15 @@ static bool storage_dir_rename_check(Storage* storage, const char* base) { if(result) { for(size_t i = 0; i < COUNT_OF(storage_copy_test_files); i++) { - string_printf(path, "%s/%s", base, storage_copy_test_files[i]); - result = check_file_13DA(storage, string_get_cstr(path)); + furi_string_printf(path, "%s/%s", base, storage_copy_test_files[i]); + result = check_file_13DA(storage, furi_string_get_cstr(path)); if(!result) { break; } } } - string_clear(path); + furi_string_free(path); return result; } diff --git a/applications/debug/unit_tests/stream/stream_test.c b/applications/debug/unit_tests/stream/stream_test.c index b5a2d398065..c04e119c626 100644 --- a/applications/debug/unit_tests/stream/stream_test.c +++ b/applications/debug/unit_tests/stream/stream_test.c @@ -18,8 +18,8 @@ static const char* stream_test_right_data = MU_TEST_1(stream_composite_subtest, Stream* stream) { const size_t data_size = 128; uint8_t data[data_size]; - string_t string_lee; - string_init_set(string_lee, "lee"); + FuriString* string_lee; + string_lee = furi_string_alloc_set("lee"); // test that stream is empty // "" -> "" @@ -267,7 +267,7 @@ MU_TEST_1(stream_composite_subtest, Stream* stream) { mu_assert_int_eq(9, stream_tell(stream)); mu_check(stream_eof(stream)); - string_clear(string_lee); + furi_string_free(string_lee); } MU_TEST(stream_composite_test) { @@ -416,10 +416,10 @@ MU_TEST(stream_buffered_write_after_read_test) { } MU_TEST(stream_buffered_large_file_test) { - string_t input_data; - string_t output_data; - string_init(input_data); - string_init(output_data); + FuriString* input_data; + FuriString* output_data; + input_data = furi_string_alloc(); + output_data = furi_string_alloc(); Storage* storage = furi_record_open(RECORD_STORAGE); @@ -429,7 +429,7 @@ MU_TEST(stream_buffered_large_file_test) { const size_t rep_count = data_size / line_size + 1; for(size_t i = 0; i < rep_count; ++i) { - string_cat_printf(input_data, "%s\n", stream_test_data); + furi_string_cat_printf(input_data, "%s\n", stream_test_data); } // write test data to file @@ -437,8 +437,8 @@ MU_TEST(stream_buffered_large_file_test) { mu_check(buffered_file_stream_open( stream, EXT_PATH("filestream.str"), FSAM_READ_WRITE, FSOM_CREATE_ALWAYS)); mu_assert_int_eq(0, stream_size(stream)); - mu_assert_int_eq(string_size(input_data), stream_write_string(stream, input_data)); - mu_assert_int_eq(string_size(input_data), stream_size(stream)); + mu_assert_int_eq(furi_string_size(input_data), stream_write_string(stream, input_data)); + mu_assert_int_eq(furi_string_size(input_data), stream_size(stream)); const size_t substr_start = 8; const size_t substr_len = 11; @@ -475,23 +475,23 @@ MU_TEST(stream_buffered_large_file_test) { // read the whole file mu_check(stream_rewind(stream)); - string_t tmp; - string_init(tmp); + FuriString* tmp; + tmp = furi_string_alloc(); while(stream_read_line(stream, tmp)) { - string_cat(output_data, tmp); + furi_string_cat(output_data, tmp); } - string_clear(tmp); + furi_string_free(tmp); // check against generated data - mu_assert_int_eq(string_size(input_data), string_size(output_data)); - mu_check(string_equal_p(input_data, output_data)); + mu_assert_int_eq(furi_string_size(input_data), furi_string_size(output_data)); + mu_check(furi_string_equal(input_data, output_data)); mu_check(stream_eof(stream)); stream_free(stream); furi_record_close(RECORD_STORAGE); - string_clear(input_data); - string_clear(output_data); + furi_string_free(input_data); + furi_string_free(output_data); } MU_TEST_SUITE(stream_suite) { diff --git a/applications/debug/unit_tests/subghz/subghz_test.c b/applications/debug/unit_tests/subghz/subghz_test.c index 36f5b94c2ac..210d3770f06 100644 --- a/applications/debug/unit_tests/subghz/subghz_test.c +++ b/applications/debug/unit_tests/subghz/subghz_test.c @@ -28,12 +28,12 @@ static void subghz_test_rx_callback( void* context) { UNUSED(receiver); UNUSED(context); - string_t text; - string_init(text); + FuriString* text; + text = furi_string_alloc(); subghz_protocol_decoder_base_get_string(decoder_base, text); subghz_receiver_reset(receiver_handler); - FURI_LOG_T(TAG, "\r\n%s", string_get_cstr(text)); - string_clear(text); + FURI_LOG_T(TAG, "\r\n%s", furi_string_get_cstr(text)); + furi_string_free(text); subghz_test_decoder_count++; } @@ -141,8 +141,8 @@ static bool subghz_decode_random_test(const char* path) { static bool subghz_encoder_test(const char* path) { subghz_test_decoder_count = 0; uint32_t test_start = furi_get_tick(); - string_t temp_str; - string_init(temp_str); + FuriString* temp_str; + temp_str = furi_string_alloc(); bool file_load = false; Storage* storage = furi_record_open(RECORD_STORAGE); @@ -167,11 +167,11 @@ static bool subghz_encoder_test(const char* path) { } while(false); if(file_load) { SubGhzTransmitter* transmitter = - subghz_transmitter_alloc_init(environment_handler, string_get_cstr(temp_str)); + subghz_transmitter_alloc_init(environment_handler, furi_string_get_cstr(temp_str)); subghz_transmitter_deserialize(transmitter, fff_data_file); SubGhzProtocolDecoderBase* decoder = subghz_receiver_search_decoder_base_by_name( - receiver_handler, string_get_cstr(temp_str)); + receiver_handler, furi_string_get_cstr(temp_str)); if(decoder) { LevelDuration level_duration; @@ -192,10 +192,11 @@ static bool subghz_encoder_test(const char* path) { flipper_format_free(fff_data_file); FURI_LOG_T(TAG, "\r\n Decoder count parse \033[0;33m%d\033[0m ", subghz_test_decoder_count); if(furi_get_tick() - test_start > TEST_TIMEOUT) { - printf("\033[0;31mTest encoder %s ERROR TimeOut\033[0m\r\n", string_get_cstr(temp_str)); + printf( + "\033[0;31mTest encoder %s ERROR TimeOut\033[0m\r\n", furi_string_get_cstr(temp_str)); subghz_test_decoder_count = 0; } - string_clear(temp_str); + furi_string_free(temp_str); return subghz_test_decoder_count ? true : false; } diff --git a/applications/debug/unit_tests/test_index.c b/applications/debug/unit_tests/test_index.c index 81d891b2ba7..2009d4a5bad 100644 --- a/applications/debug/unit_tests/test_index.c +++ b/applications/debug/unit_tests/test_index.c @@ -1,5 +1,3 @@ -#include "m-string.h" - #include #include #include @@ -11,6 +9,7 @@ #define TAG "UnitTests" int run_minunit_test_furi(); +int run_minunit_test_furi_string(); int run_minunit_test_infrared(); int run_minunit_test_rpc(); int run_minunit_test_flipper_format(); @@ -33,6 +32,7 @@ typedef struct { const UnitTest unit_tests[] = { {.name = "furi", .entry = run_minunit_test_furi}, + {.name = "furi_string", .entry = run_minunit_test_furi_string}, {.name = "storage", .entry = run_minunit_test_storage}, {.name = "stream", .entry = run_minunit_test_stream}, {.name = "dirwalk", .entry = run_minunit_test_dirwalk}, @@ -63,7 +63,7 @@ void minunit_print_fail(const char* str) { printf(FURI_LOG_CLR_E "%s\r\n" FURI_LOG_CLR_RESET, str); } -void unit_tests_cli(Cli* cli, string_t args, void* context) { +void unit_tests_cli(Cli* cli, FuriString* args, void* context) { UNUSED(cli); UNUSED(args); UNUSED(context); @@ -91,8 +91,8 @@ void unit_tests_cli(Cli* cli, string_t args, void* context) { break; } - if(string_size(args)) { - if(string_cmp_str(args, unit_tests[i].name) == 0) { + if(furi_string_size(args)) { + if(furi_string_cmp_str(args, unit_tests[i].name) == 0) { failed_tests += unit_tests[i].entry(); } else { printf("Skipping %s\r\n", unit_tests[i].name); diff --git a/applications/main/archive/archive.c b/applications/main/archive/archive.c index bbe532c8c0d..b8609cf2d88 100644 --- a/applications/main/archive/archive.c +++ b/applications/main/archive/archive.c @@ -1,5 +1,4 @@ #include "archive_i.h" -#include "m-string.h" bool archive_custom_event_callback(void* context, uint32_t event) { furi_assert(context); @@ -18,7 +17,7 @@ ArchiveApp* archive_alloc() { archive->gui = furi_record_open(RECORD_GUI); archive->text_input = text_input_alloc(); - string_init(archive->fav_move_str); + archive->fav_move_str = furi_string_alloc(); archive->view_dispatcher = view_dispatcher_alloc(); archive->scene_manager = scene_manager_alloc(&archive_scene_handlers, archive); @@ -58,7 +57,7 @@ void archive_free(ArchiveApp* archive) { view_dispatcher_free(archive->view_dispatcher); scene_manager_free(archive->scene_manager); browser_free(archive->browser); - string_clear(archive->fav_move_str); + furi_string_free(archive->fav_move_str); text_input_free(archive->text_input); diff --git a/applications/main/archive/archive_i.h b/applications/main/archive/archive_i.h index 865e837dcbc..d444aef8fcb 100644 --- a/applications/main/archive/archive_i.h +++ b/applications/main/archive/archive_i.h @@ -28,7 +28,7 @@ struct ArchiveApp { TextInput* text_input; Widget* widget; FuriPubSubSubscription* loader_stop_subscription; - string_t fav_move_str; + FuriString* fav_move_str; char text_store[MAX_NAME_LEN]; char file_extension[MAX_EXT_LEN + 1]; }; diff --git a/applications/main/archive/helpers/archive_browser.c b/applications/main/archive/helpers/archive_browser.c index 00bb6b06331..1f4ca0f794a 100644 --- a/applications/main/archive/helpers/archive_browser.c +++ b/applications/main/archive/helpers/archive_browser.c @@ -5,7 +5,6 @@ #include #include #include "gui/modules/file_browser_worker.h" -#include "m-string.h" #include static void @@ -19,7 +18,7 @@ static void if((item_cnt == 0) && (archive_is_home(browser)) && (tab != ArchiveTabBrowser)) { archive_switch_tab(browser, browser->last_tab_switch_dir); - } else if(!string_start_with_str_p(browser->path, "/app:")) { + } else if(!furi_string_start_with_str(browser->path, "/app:")) { with_view_model( browser->view, (ArchiveBrowserViewModel * model) { files_array_reset(model->files); @@ -51,12 +50,13 @@ static void archive_list_load_cb(void* context, uint32_t list_load_offset) { }); } -static void archive_list_item_cb(void* context, string_t item_path, bool is_folder, bool is_last) { +static void + archive_list_item_cb(void* context, FuriString* item_path, bool is_folder, bool is_last) { furi_assert(context); ArchiveBrowserView* browser = (ArchiveBrowserView*)context; if(!is_last) { - archive_add_file_item(browser, is_folder, string_get_cstr(item_path)); + archive_add_file_item(browser, is_folder, furi_string_get_cstr(item_path)); } else { with_view_model( browser->view, (ArchiveBrowserViewModel * model) { @@ -79,7 +79,7 @@ static void archive_long_load_cb(void* context) { static void archive_file_browser_set_path( ArchiveBrowserView* browser, - string_t path, + FuriString* path, const char* filter_ext, bool skip_assets) { furi_assert(browser); @@ -133,7 +133,7 @@ void archive_update_focus(ArchiveBrowserView* browser, const char* target) { furi_assert(browser); furi_assert(target); - archive_get_items(browser, string_get_cstr(browser->path)); + archive_get_items(browser, furi_string_get_cstr(browser->path)); if(!archive_file_get_array_size(browser) && archive_is_home(browser)) { archive_switch_tab(browser, TAB_RIGHT); @@ -143,7 +143,7 @@ void archive_update_focus(ArchiveBrowserView* browser, const char* target) { uint16_t idx = 0; while(idx < files_array_size(model->files)) { ArchiveFile_t* current = files_array_get(model->files, idx); - if(!string_search(current->path, target)) { + if(!furi_string_search(current->path, target)) { model->item_idx = idx + model->array_offset; break; } @@ -315,12 +315,12 @@ bool archive_is_home(ArchiveBrowserView* browser) { } const char* default_path = archive_get_default_path(archive_get_tab(browser)); - return (string_cmp_str(browser->path, default_path) == 0); + return (furi_string_cmp_str(browser->path, default_path) == 0); } const char* archive_get_name(ArchiveBrowserView* browser) { ArchiveFile_t* selected = archive_get_current_file(browser); - return string_get_cstr(selected->path); + return furi_string_get_cstr(selected->path); } void archive_set_tab(ArchiveBrowserView* browser, ArchiveTabEnum tab) { @@ -339,7 +339,7 @@ void archive_add_app_item(ArchiveBrowserView* browser, const char* name) { ArchiveFile_t item; ArchiveFile_t_init(&item); - string_set_str(item.path, name); + furi_string_set(item.path, name); archive_set_file_type(&item, name, false, true); with_view_model( @@ -358,8 +358,8 @@ void archive_add_file_item(ArchiveBrowserView* browser, bool is_folder, const ch ArchiveFile_t item; ArchiveFile_t_init(&item); - string_init_set_str(item.path, name); - archive_set_file_type(&item, string_get_cstr(browser->path), is_folder, false); + item.path = furi_string_alloc_set(name); + archive_set_file_type(&item, furi_string_get_cstr(browser->path), is_folder, false); with_view_model( browser->view, (ArchiveBrowserViewModel * model) { @@ -379,7 +379,8 @@ void archive_show_file_menu(ArchiveBrowserView* browser, bool show) { model->menu_idx = 0; ArchiveFile_t* selected = files_array_get(model->files, model->item_idx - model->array_offset); - selected->fav = archive_is_favorite("%s", string_get_cstr(selected->path)); + selected->fav = + archive_is_favorite("%s", furi_string_get_cstr(selected->path)); } } else { model->menu = false; @@ -400,14 +401,14 @@ void archive_favorites_move_mode(ArchiveBrowserView* browser, bool active) { }); } -static bool archive_is_dir_exists(string_t path) { - if(string_equal_str_p(path, STORAGE_ANY_PATH_PREFIX)) { +static bool archive_is_dir_exists(FuriString* path) { + if(furi_string_equal(path, STORAGE_ANY_PATH_PREFIX)) { return true; } bool state = false; FileInfo file_info; Storage* storage = furi_record_open(RECORD_STORAGE); - if(storage_common_stat(storage, string_get_cstr(path), &file_info) == FSE_OK) { + if(storage_common_stat(storage, furi_string_get_cstr(path), &file_info) == FSE_OK) { if(file_info.flags & FSF_DIRECTORY) { state = true; } @@ -431,16 +432,16 @@ void archive_switch_tab(ArchiveBrowserView* browser, InputKey key) { browser->is_root = true; archive_set_tab(browser, tab); - string_set_str(browser->path, archive_get_default_path(tab)); + furi_string_set(browser->path, archive_get_default_path(tab)); bool tab_empty = true; if(tab == ArchiveTabFavorites) { if(archive_favorites_count(browser) > 0) { tab_empty = false; } - } else if(string_start_with_str_p(browser->path, "/app:")) { - char* app_name = strchr(string_get_cstr(browser->path), ':'); + } else if(furi_string_start_with_str(browser->path, "/app:")) { + char* app_name = strchr(furi_string_get_cstr(browser->path), ':'); if(app_name != NULL) { - if(archive_app_is_available(browser, string_get_cstr(browser->path))) { + if(archive_app_is_available(browser, furi_string_get_cstr(browser->path))) { tab_empty = false; } } @@ -463,12 +464,12 @@ void archive_switch_tab(ArchiveBrowserView* browser, InputKey key) { model->array_offset = 0; return false; }); - archive_get_items(browser, string_get_cstr(browser->path)); + archive_get_items(browser, furi_string_get_cstr(browser->path)); archive_update_offset(browser); } } -void archive_enter_dir(ArchiveBrowserView* browser, string_t path) { +void archive_enter_dir(ArchiveBrowserView* browser, FuriString* path) { furi_assert(browser); furi_assert(path); @@ -480,7 +481,7 @@ void archive_enter_dir(ArchiveBrowserView* browser, string_t path) { return false; }); - string_set(browser->path, path); + furi_string_set(browser->path, path); file_browser_worker_folder_enter(browser->worker, path, idx_temp); } diff --git a/applications/main/archive/helpers/archive_browser.h b/applications/main/archive/helpers/archive_browser.h index ad64a984532..c9e3bfa3083 100644 --- a/applications/main/archive/helpers/archive_browser.h +++ b/applications/main/archive/helpers/archive_browser.h @@ -84,6 +84,6 @@ void archive_show_file_menu(ArchiveBrowserView* browser, bool show); void archive_favorites_move_mode(ArchiveBrowserView* browser, bool active); void archive_switch_tab(ArchiveBrowserView* browser, InputKey key); -void archive_enter_dir(ArchiveBrowserView* browser, string_t name); +void archive_enter_dir(ArchiveBrowserView* browser, FuriString* name); void archive_leave_dir(ArchiveBrowserView* browser); void archive_refresh_dir(ArchiveBrowserView* browser); diff --git a/applications/main/archive/helpers/archive_favorites.c b/applications/main/archive/helpers/archive_favorites.c index 4d5b555f5a3..86a294f789c 100644 --- a/applications/main/archive/helpers/archive_favorites.c +++ b/applications/main/archive/helpers/archive_favorites.c @@ -6,8 +6,8 @@ #define ARCHIVE_FAV_FILE_BUF_LEN 32 -static bool archive_favorites_read_line(File* file, string_t str_result) { - string_reset(str_result); +static bool archive_favorites_read_line(File* file, FuriString* str_result) { + furi_string_reset(str_result); uint8_t buffer[ARCHIVE_FAV_FILE_BUF_LEN]; bool result = false; @@ -34,7 +34,7 @@ static bool archive_favorites_read_line(File* file, string_t str_result) { result = true; break; } else { - string_push_back(str_result, buffer[i]); + furi_string_push_back(str_result, buffer[i]); } } @@ -52,8 +52,8 @@ uint16_t archive_favorites_count(void* context) { Storage* fs_api = furi_record_open(RECORD_STORAGE); File* file = storage_file_alloc(fs_api); - string_t buffer; - string_init(buffer); + FuriString* buffer; + buffer = furi_string_alloc(); bool result = storage_file_open(file, ARCHIVE_FAV_PATH, FSAM_READ, FSOM_OPEN_EXISTING); uint16_t lines = 0; @@ -63,7 +63,7 @@ uint16_t archive_favorites_count(void* context) { if(!archive_favorites_read_line(file, buffer)) { break; } - if(!string_size(buffer)) { + if(!furi_string_size(buffer)) { continue; // Skip empty lines } ++lines; @@ -72,7 +72,7 @@ uint16_t archive_favorites_count(void* context) { storage_file_close(file); - string_clear(buffer); + furi_string_free(buffer); storage_file_free(file); furi_record_close(RECORD_STORAGE); @@ -80,8 +80,8 @@ uint16_t archive_favorites_count(void* context) { } static bool archive_favourites_rescan() { - string_t buffer; - string_init(buffer); + FuriString* buffer; + buffer = furi_string_alloc(); Storage* storage = furi_record_open(RECORD_STORAGE); File* file = storage_file_alloc(storage); @@ -91,23 +91,25 @@ static bool archive_favourites_rescan() { if(!archive_favorites_read_line(file, buffer)) { break; } - if(!string_size(buffer)) { + if(!furi_string_size(buffer)) { continue; } - if(string_search(buffer, "/app:") == 0) { - if(archive_app_is_available(NULL, string_get_cstr(buffer))) { - archive_file_append(ARCHIVE_FAV_TEMP_PATH, "%s\n", string_get_cstr(buffer)); + if(furi_string_search(buffer, "/app:") == 0) { + if(archive_app_is_available(NULL, furi_string_get_cstr(buffer))) { + archive_file_append( + ARCHIVE_FAV_TEMP_PATH, "%s\n", furi_string_get_cstr(buffer)); } } else { - if(storage_file_exists(storage, string_get_cstr(buffer))) { - archive_file_append(ARCHIVE_FAV_TEMP_PATH, "%s\n", string_get_cstr(buffer)); + if(storage_file_exists(storage, furi_string_get_cstr(buffer))) { + archive_file_append( + ARCHIVE_FAV_TEMP_PATH, "%s\n", furi_string_get_cstr(buffer)); } } } } - string_clear(buffer); + furi_string_free(buffer); storage_file_close(file); storage_common_remove(storage, ARCHIVE_FAV_PATH); @@ -127,9 +129,9 @@ bool archive_favorites_read(void* context) { Storage* storage = furi_record_open(RECORD_STORAGE); File* file = storage_file_alloc(storage); - string_t buffer; + FuriString* buffer; FileInfo file_info; - string_init(buffer); + buffer = furi_string_alloc(); bool need_refresh = false; uint16_t file_count = 0; @@ -143,33 +145,33 @@ bool archive_favorites_read(void* context) { if(!archive_favorites_read_line(file, buffer)) { break; } - if(!string_size(buffer)) { + if(!furi_string_size(buffer)) { continue; } - if(string_search(buffer, "/app:") == 0) { - if(archive_app_is_available(browser, string_get_cstr(buffer))) { - archive_add_app_item(browser, string_get_cstr(buffer)); + if(furi_string_search(buffer, "/app:") == 0) { + if(archive_app_is_available(browser, furi_string_get_cstr(buffer))) { + archive_add_app_item(browser, furi_string_get_cstr(buffer)); file_count++; } else { need_refresh = true; } } else { - if(storage_file_exists(storage, string_get_cstr(buffer))) { - storage_common_stat(storage, string_get_cstr(buffer), &file_info); + if(storage_file_exists(storage, furi_string_get_cstr(buffer))) { + storage_common_stat(storage, furi_string_get_cstr(buffer), &file_info); archive_add_file_item( - browser, (file_info.flags & FSF_DIRECTORY), string_get_cstr(buffer)); + browser, (file_info.flags & FSF_DIRECTORY), furi_string_get_cstr(buffer)); file_count++; } else { need_refresh = true; } } - string_reset(buffer); + furi_string_reset(buffer); } } storage_file_close(file); - string_clear(buffer); + furi_string_free(buffer); storage_file_free(file); furi_record_close(RECORD_STORAGE); @@ -183,14 +185,14 @@ bool archive_favorites_read(void* context) { } bool archive_favorites_delete(const char* format, ...) { - string_t buffer; - string_t filename; + FuriString* buffer; + FuriString* filename; va_list args; va_start(args, format); - string_init_vprintf(filename, format, args); + filename = furi_string_alloc_vprintf(format, args); va_end(args); - string_init(buffer); + buffer = furi_string_alloc(); Storage* fs_api = furi_record_open(RECORD_STORAGE); File* file = storage_file_alloc(fs_api); @@ -201,18 +203,18 @@ bool archive_favorites_delete(const char* format, ...) { if(!archive_favorites_read_line(file, buffer)) { break; } - if(!string_size(buffer)) { + if(!furi_string_size(buffer)) { continue; } - if(string_search(buffer, filename)) { - archive_file_append(ARCHIVE_FAV_TEMP_PATH, "%s\n", string_get_cstr(buffer)); + if(furi_string_search(buffer, filename)) { + archive_file_append(ARCHIVE_FAV_TEMP_PATH, "%s\n", furi_string_get_cstr(buffer)); } } } - string_clear(buffer); - string_clear(filename); + furi_string_free(buffer); + furi_string_free(filename); storage_file_close(file); storage_common_remove(fs_api, ARCHIVE_FAV_PATH); @@ -226,14 +228,14 @@ bool archive_favorites_delete(const char* format, ...) { } bool archive_is_favorite(const char* format, ...) { - string_t buffer; - string_t filename; + FuriString* buffer; + FuriString* filename; va_list args; va_start(args, format); - string_init_vprintf(filename, format, args); + filename = furi_string_alloc_vprintf(format, args); va_end(args); - string_init(buffer); + buffer = furi_string_alloc(); Storage* fs_api = furi_record_open(RECORD_STORAGE); File* file = storage_file_alloc(fs_api); @@ -245,10 +247,10 @@ bool archive_is_favorite(const char* format, ...) { if(!archive_favorites_read_line(file, buffer)) { break; } - if(!string_size(buffer)) { + if(!furi_string_size(buffer)) { continue; } - if(!string_search(buffer, filename)) { + if(!furi_string_search(buffer, filename)) { found = true; break; } @@ -256,8 +258,8 @@ bool archive_is_favorite(const char* format, ...) { } storage_file_close(file); - string_clear(buffer); - string_clear(filename); + furi_string_free(buffer); + furi_string_free(filename); storage_file_free(file); furi_record_close(RECORD_STORAGE); @@ -271,13 +273,13 @@ bool archive_favorites_rename(const char* src, const char* dst) { Storage* fs_api = furi_record_open(RECORD_STORAGE); File* file = storage_file_alloc(fs_api); - string_t path; - string_t buffer; + FuriString* path; + FuriString* buffer; - string_init(buffer); - string_init(path); + buffer = furi_string_alloc(); + path = furi_string_alloc(); - string_printf(path, "%s", src); + furi_string_printf(path, "%s", src); bool result = storage_file_open(file, ARCHIVE_FAV_PATH, FSAM_READ, FSOM_OPEN_EXISTING); if(result) { @@ -285,19 +287,19 @@ bool archive_favorites_rename(const char* src, const char* dst) { if(!archive_favorites_read_line(file, buffer)) { break; } - if(!string_size(buffer)) { + if(!furi_string_size(buffer)) { continue; } archive_file_append( ARCHIVE_FAV_TEMP_PATH, "%s\n", - string_search(buffer, path) ? string_get_cstr(buffer) : dst); + furi_string_search(buffer, path) ? furi_string_get_cstr(buffer) : dst); } } - string_clear(buffer); - string_clear(path); + furi_string_free(buffer); + furi_string_free(path); storage_file_close(file); storage_common_remove(fs_api, ARCHIVE_FAV_PATH); @@ -325,7 +327,7 @@ void archive_favorites_save(void* context) { for(size_t i = 0; i < archive_file_get_array_size(browser); i++) { ArchiveFile_t* item = archive_get_file_at(browser, i); - archive_file_append(ARCHIVE_FAV_TEMP_PATH, "%s\n", string_get_cstr(item->path)); + archive_file_append(ARCHIVE_FAV_TEMP_PATH, "%s\n", furi_string_get_cstr(item->path)); } storage_common_remove(fs_api, ARCHIVE_FAV_PATH); diff --git a/applications/main/archive/helpers/archive_files.c b/applications/main/archive/helpers/archive_files.c index 9f8b4ee1bac..87265a45d24 100644 --- a/applications/main/archive/helpers/archive_files.c +++ b/applications/main/archive/helpers/archive_files.c @@ -15,10 +15,10 @@ void archive_set_file_type(ArchiveFile_t* file, const char* path, bool is_folder } else { for(size_t i = 0; i < COUNT_OF(known_ext); i++) { if((known_ext[i][0] == '?') || (known_ext[i][0] == '*')) continue; - if(string_search_str(file->path, known_ext[i], 0) != STRING_FAILURE) { + if(furi_string_search(file->path, known_ext[i], 0) != FURI_STRING_FAILURE) { if(i == ArchiveFileTypeBadUsb) { - if(string_search_str(file->path, archive_get_default_path(ArchiveTabBadUsb)) == - 0) { + if(furi_string_search( + file->path, archive_get_default_path(ArchiveTabBadUsb)) == 0) { file->type = i; return; // *.txt file is a BadUSB script only if it is in BadUSB folder } @@ -54,10 +54,10 @@ bool archive_get_items(void* context, const char* path) { void archive_file_append(const char* path, const char* format, ...) { furi_assert(path); - string_t string; + FuriString* string; va_list args; va_start(args, format); - string_init_vprintf(string, format, args); + string = furi_string_alloc_vprintf(format, args); va_end(args); Storage* fs_api = furi_record_open(RECORD_STORAGE); @@ -66,7 +66,7 @@ void archive_file_append(const char* path, const char* format, ...) { bool res = storage_file_open(file, path, FSAM_WRITE, FSOM_OPEN_APPEND); if(res) { - storage_file_write(file, string_get_cstr(string), string_size(string)); + storage_file_write(file, furi_string_get_cstr(string), furi_string_size(string)); } storage_file_close(file); @@ -77,35 +77,35 @@ void archive_file_append(const char* path, const char* format, ...) { void archive_delete_file(void* context, const char* format, ...) { furi_assert(context); - string_t filename; + FuriString* filename; va_list args; va_start(args, format); - string_init_vprintf(filename, format, args); + filename = furi_string_alloc_vprintf(format, args); va_end(args); ArchiveBrowserView* browser = context; Storage* fs_api = furi_record_open(RECORD_STORAGE); FileInfo fileinfo; - storage_common_stat(fs_api, string_get_cstr(filename), &fileinfo); + storage_common_stat(fs_api, furi_string_get_cstr(filename), &fileinfo); bool res = false; if(fileinfo.flags & FSF_DIRECTORY) { - res = storage_simply_remove_recursive(fs_api, string_get_cstr(filename)); + res = storage_simply_remove_recursive(fs_api, furi_string_get_cstr(filename)); } else { - res = (storage_common_remove(fs_api, string_get_cstr(filename)) == FSE_OK); + res = (storage_common_remove(fs_api, furi_string_get_cstr(filename)) == FSE_OK); } furi_record_close(RECORD_STORAGE); - if(archive_is_favorite("%s", string_get_cstr(filename))) { - archive_favorites_delete("%s", string_get_cstr(filename)); + if(archive_is_favorite("%s", furi_string_get_cstr(filename))) { + archive_favorites_delete("%s", furi_string_get_cstr(filename)); } if(res) { archive_file_array_rm_selected(browser); } - string_clear(filename); + furi_string_free(filename); } diff --git a/applications/main/archive/helpers/archive_files.h b/applications/main/archive/helpers/archive_files.h index 84b7e24a62f..a50a1d5304a 100644 --- a/applications/main/archive/helpers/archive_files.h +++ b/applications/main/archive/helpers/archive_files.h @@ -1,7 +1,7 @@ #pragma once #include -#include +#include #include typedef enum { @@ -19,7 +19,7 @@ typedef enum { } ArchiveFileTypeEnum; typedef struct { - string_t path; + FuriString* path; ArchiveFileTypeEnum type; bool fav; bool is_app; @@ -29,25 +29,25 @@ static void ArchiveFile_t_init(ArchiveFile_t* obj) { obj->type = ArchiveFileTypeUnknown; obj->is_app = false; obj->fav = false; - string_init(obj->path); + obj->path = furi_string_alloc(); } static void ArchiveFile_t_init_set(ArchiveFile_t* obj, const ArchiveFile_t* src) { obj->type = src->type; obj->is_app = src->is_app; obj->fav = src->fav; - string_init_set(obj->path, src->path); + obj->path = furi_string_alloc_set(src->path); } static void ArchiveFile_t_set(ArchiveFile_t* obj, const ArchiveFile_t* src) { obj->type = src->type; obj->is_app = src->is_app; obj->fav = src->fav; - string_set(obj->path, src->path); + furi_string_set(obj->path, src->path); } static void ArchiveFile_t_clear(ArchiveFile_t* obj) { - string_clear(obj->path); + furi_string_free(obj->path); } ARRAY_DEF( diff --git a/applications/main/archive/scenes/archive_scene_browser.c b/applications/main/archive/scenes/archive_scene_browser.c index e22ac792fcb..b4fd0482342 100644 --- a/applications/main/archive/scenes/archive_scene_browser.c +++ b/applications/main/archive/scenes/archive_scene_browser.c @@ -40,14 +40,14 @@ static void archive_run_in_app(ArchiveBrowserView* browser, ArchiveFile_t* selec LoaderStatus status; if(selected->is_app) { - char* param = strrchr(string_get_cstr(selected->path), '/'); + char* param = strrchr(furi_string_get_cstr(selected->path), '/'); if(param != NULL) { param++; } status = loader_start(loader, flipper_app_name[selected->type], param); } else { status = loader_start( - loader, flipper_app_name[selected->type], string_get_cstr(selected->path)); + loader, flipper_app_name[selected->type], furi_string_get_cstr(selected->path)); } if(status != LoaderStatusOk) { @@ -159,13 +159,13 @@ bool archive_scene_browser_on_event(void* context, SceneManagerEvent event) { consumed = true; break; case ArchiveBrowserEventEnterFavMove: - string_set(archive->fav_move_str, selected->path); + furi_string_set(archive->fav_move_str, selected->path); archive_show_file_menu(browser, false); archive_favorites_move_mode(archive->browser, true); consumed = true; break; case ArchiveBrowserEventExitFavMove: - archive_update_focus(browser, string_get_cstr(archive->fav_move_str)); + archive_update_focus(browser, furi_string_get_cstr(archive->fav_move_str)); archive_favorites_move_mode(archive->browser, false); consumed = true; break; diff --git a/applications/main/archive/scenes/archive_scene_delete.c b/applications/main/archive/scenes/archive_scene_delete.c index d882fd066e0..d3964d0febf 100644 --- a/applications/main/archive/scenes/archive_scene_delete.c +++ b/applications/main/archive/scenes/archive_scene_delete.c @@ -4,7 +4,6 @@ #include "../helpers/archive_apps.h" #include "../helpers/archive_browser.h" #include "toolbox/path.h" -#include "m-string.h" #define SCENE_DELETE_CUSTOM_EVENT (0UL) #define MAX_TEXT_INPUT_LEN 22 @@ -26,18 +25,18 @@ void archive_scene_delete_on_enter(void* context) { widget_add_button_element( app->widget, GuiButtonTypeRight, "Delete", archive_scene_delete_widget_callback, app); - string_t filename; - string_init(filename); + FuriString* filename; + filename = furi_string_alloc(); ArchiveFile_t* current = archive_get_current_file(app->browser); path_extract_filename(current->path, filename, false); char delete_str[64]; - snprintf(delete_str, sizeof(delete_str), "\e#Delete %s?\e#", string_get_cstr(filename)); + snprintf(delete_str, sizeof(delete_str), "\e#Delete %s?\e#", furi_string_get_cstr(filename)); widget_add_text_box_element( app->widget, 0, 0, 128, 23, AlignCenter, AlignCenter, delete_str, false); - string_clear(filename); + furi_string_free(filename); view_dispatcher_switch_to_view(app->view_dispatcher, ArchiveViewWidget); } diff --git a/applications/main/archive/scenes/archive_scene_rename.c b/applications/main/archive/scenes/archive_scene_rename.c index 293fa89affd..1451428bdf9 100644 --- a/applications/main/archive/scenes/archive_scene_rename.c +++ b/applications/main/archive/scenes/archive_scene_rename.c @@ -19,10 +19,10 @@ void archive_scene_rename_on_enter(void* context) { TextInput* text_input = archive->text_input; ArchiveFile_t* current = archive_get_current_file(archive->browser); - string_t filename; - string_init(filename); + FuriString* filename; + filename = furi_string_alloc(); path_extract_filename(current->path, filename, true); - strlcpy(archive->text_store, string_get_cstr(filename), MAX_NAME_LEN); + strlcpy(archive->text_store, furi_string_get_cstr(filename), MAX_NAME_LEN); path_extract_extension(current->path, archive->file_extension, MAX_EXT_LEN); @@ -37,10 +37,10 @@ void archive_scene_rename_on_enter(void* context) { false); ValidatorIsFile* validator_is_file = validator_is_file_alloc_init( - string_get_cstr(archive->browser->path), archive->file_extension, ""); + furi_string_get_cstr(archive->browser->path), archive->file_extension, ""); text_input_set_validator(text_input, validator_is_file_callback, validator_is_file); - string_clear(filename); + furi_string_free(filename); view_dispatcher_switch_to_view(archive->view_dispatcher, ArchiveViewTextInput); } @@ -56,19 +56,19 @@ bool archive_scene_rename_on_event(void* context, SceneManagerEvent event) { const char* path_src = archive_get_name(archive->browser); ArchiveFile_t* file = archive_get_current_file(archive->browser); - string_t path_dst; - string_init(path_dst); + FuriString* path_dst; + path_dst = furi_string_alloc(); path_extract_dirname(path_src, path_dst); - string_cat_printf(path_dst, "/%s%s", archive->text_store, known_ext[file->type]); + furi_string_cat_printf(path_dst, "/%s%s", archive->text_store, known_ext[file->type]); - storage_common_rename(fs_api, path_src, string_get_cstr(path_dst)); + storage_common_rename(fs_api, path_src, furi_string_get_cstr(path_dst)); furi_record_close(RECORD_STORAGE); if(file->fav) { - archive_favorites_rename(path_src, string_get_cstr(path_dst)); + archive_favorites_rename(path_src, furi_string_get_cstr(path_dst)); } - string_clear(path_dst); + furi_string_free(path_dst); scene_manager_next_scene(archive->scene_manager, ArchiveAppSceneBrowser); consumed = true; diff --git a/applications/main/archive/views/archive_browser_view.c b/applications/main/archive/views/archive_browser_view.c index ddd6637dbc8..4a0f2f3cb48 100644 --- a/applications/main/archive/views/archive_browser_view.c +++ b/applications/main/archive/views/archive_browser_view.c @@ -47,35 +47,35 @@ static void render_item_menu(Canvas* canvas, ArchiveBrowserViewModel* model) { canvas_set_color(canvas, ColorBlack); elements_slightly_rounded_frame(canvas, 70, 16, 58, 48); - string_t menu[MENU_ITEMS]; + FuriString* menu[MENU_ITEMS]; - string_init_set_str(menu[0], "Run in app"); - string_init_set_str(menu[1], "Pin"); - string_init_set_str(menu[2], "Rename"); - string_init_set_str(menu[3], "Delete"); + menu[0] = furi_string_alloc_set("Run in app"); + menu[1] = furi_string_alloc_set("Pin"); + menu[2] = furi_string_alloc_set("Rename"); + menu[3] = furi_string_alloc_set("Delete"); ArchiveFile_t* selected = files_array_get(model->files, model->item_idx - model->array_offset); if((selected->fav) || (model->tab_idx == ArchiveTabFavorites)) { - string_set_str(menu[1], "Unpin"); + furi_string_set(menu[1], "Unpin"); } if(!archive_is_known_app(selected->type)) { - string_set_str(menu[0], "---"); - string_set_str(menu[1], "---"); - string_set_str(menu[2], "---"); + furi_string_set(menu[0], "---"); + furi_string_set(menu[1], "---"); + furi_string_set(menu[2], "---"); } else { if(model->tab_idx == ArchiveTabFavorites) { - string_set_str(menu[2], "Move"); - string_set_str(menu[3], "---"); + furi_string_set(menu[2], "Move"); + furi_string_set(menu[3], "---"); } else if(selected->is_app) { - string_set_str(menu[2], "---"); + furi_string_set(menu[2], "---"); } } for(size_t i = 0; i < MENU_ITEMS; i++) { - canvas_draw_str(canvas, 82, 27 + i * 11, string_get_cstr(menu[i])); - string_clear(menu[i]); + canvas_draw_str(canvas, 82, 27 + i * 11, furi_string_get_cstr(menu[i])); + furi_string_free(menu[i]); } canvas_draw_icon(canvas, 74, 20 + model->menu_idx * 11, &I_ButtonRight_4x7); @@ -118,8 +118,8 @@ static void draw_list(Canvas* canvas, ArchiveBrowserViewModel* model) { bool scrollbar = model->item_cnt > 4; for(uint32_t i = 0; i < MIN(model->item_cnt, MENU_ITEMS); ++i) { - string_t str_buf; - string_init(str_buf); + FuriString* str_buf; + str_buf = furi_string_alloc(); int32_t idx = CLAMP((uint32_t)(i + model->list_offset), model->item_cnt, 0u); uint8_t x_offset = (model->move_fav && model->item_idx == idx) ? MOVE_OFFSET : 0; @@ -131,7 +131,7 @@ static void draw_list(Canvas* canvas, ArchiveBrowserViewModel* model) { path_extract_filename(file->path, str_buf, archive_is_known_app(file->type)); file_type = file->type; } else { - string_set_str(str_buf, "---"); + furi_string_set(str_buf, "---"); } elements_string_fit_width( @@ -144,9 +144,10 @@ static void draw_list(Canvas* canvas, ArchiveBrowserViewModel* model) { } canvas_draw_icon(canvas, 2 + x_offset, 16 + i * FRAME_HEIGHT, ArchiveItemIcons[file_type]); - canvas_draw_str(canvas, 15 + x_offset, 24 + i * FRAME_HEIGHT, string_get_cstr(str_buf)); + canvas_draw_str( + canvas, 15 + x_offset, 24 + i * FRAME_HEIGHT, furi_string_get_cstr(str_buf)); - string_clear(str_buf); + furi_string_free(str_buf); } if(scrollbar) { @@ -361,7 +362,7 @@ ArchiveBrowserView* browser_alloc() { view_set_draw_callback(browser->view, archive_view_render); view_set_input_callback(browser->view, archive_view_input); - string_init_set_str(browser->path, archive_get_default_path(TAB_DEFAULT)); + browser->path = furi_string_alloc_set(archive_get_default_path(TAB_DEFAULT)); with_view_model( browser->view, (ArchiveBrowserViewModel * model) { @@ -386,7 +387,7 @@ void browser_free(ArchiveBrowserView* browser) { return false; }); - string_clear(browser->path); + furi_string_free(browser->path); view_free(browser->view); free(browser); diff --git a/applications/main/archive/views/archive_browser_view.h b/applications/main/archive/views/archive_browser_view.h index 5c649c389de..447cb0e1c0a 100644 --- a/applications/main/archive/views/archive_browser_view.h +++ b/applications/main/archive/views/archive_browser_view.h @@ -77,7 +77,7 @@ struct ArchiveBrowserView { bool worker_running; ArchiveBrowserViewCallback callback; void* context; - string_t path; + FuriString* path; InputKey last_tab_switch_dir; bool is_root; }; diff --git a/applications/main/bad_usb/bad_usb_app.c b/applications/main/bad_usb/bad_usb_app.c index 09d7d346834..6fd29cd7047 100644 --- a/applications/main/bad_usb/bad_usb_app.c +++ b/applications/main/bad_usb/bad_usb_app.c @@ -1,5 +1,4 @@ #include "bad_usb_app_i.h" -#include "m-string.h" #include #include #include @@ -26,10 +25,10 @@ static void bad_usb_app_tick_event_callback(void* context) { BadUsbApp* bad_usb_app_alloc(char* arg) { BadUsbApp* app = malloc(sizeof(BadUsbApp)); - string_init(app->file_path); + app->file_path = furi_string_alloc(); if(arg && strlen(arg)) { - string_set_str(app->file_path, arg); + furi_string_set(app->file_path, arg); } app->gui = furi_record_open(RECORD_GUI); @@ -64,10 +63,10 @@ BadUsbApp* bad_usb_app_alloc(char* arg) { app->error = BadUsbAppErrorCloseRpc; scene_manager_next_scene(app->scene_manager, BadUsbSceneError); } else { - if(!string_empty_p(app->file_path)) { + if(!furi_string_empty(app->file_path)) { scene_manager_next_scene(app->scene_manager, BadUsbSceneWork); } else { - string_set_str(app->file_path, BAD_USB_APP_PATH_FOLDER); + furi_string_set(app->file_path, BAD_USB_APP_PATH_FOLDER); scene_manager_next_scene(app->scene_manager, BadUsbSceneFileSelect); } } @@ -95,7 +94,7 @@ void bad_usb_app_free(BadUsbApp* app) { furi_record_close(RECORD_NOTIFICATION); furi_record_close(RECORD_DIALOGS); - string_clear(app->file_path); + furi_string_free(app->file_path); free(app); } diff --git a/applications/main/bad_usb/bad_usb_app_i.h b/applications/main/bad_usb/bad_usb_app_i.h index 6378bddfb21..85fd56ff0c4 100644 --- a/applications/main/bad_usb/bad_usb_app_i.h +++ b/applications/main/bad_usb/bad_usb_app_i.h @@ -31,7 +31,7 @@ struct BadUsbApp { Widget* widget; BadUsbAppError error; - string_t file_path; + FuriString* file_path; BadUsb* bad_usb_view; BadUsbScript* bad_usb_script; }; diff --git a/applications/main/bad_usb/bad_usb_script.c b/applications/main/bad_usb/bad_usb_script.c index 9d9d3e39719..4166642bd37 100644 --- a/applications/main/bad_usb/bad_usb_script.c +++ b/applications/main/bad_usb/bad_usb_script.c @@ -26,16 +26,16 @@ typedef enum { struct BadUsbScript { FuriHalUsbHidConfig hid_cfg; BadUsbState st; - string_t file_path; + FuriString* file_path; uint32_t defdelay; FuriThread* thread; uint8_t file_buf[FILE_BUFFER_LEN + 1]; uint8_t buf_start; uint8_t buf_len; bool file_end; - string_t line; + FuriString* line; - string_t line_prev; + FuriString* line_prev; uint32_t repeat_cnt; }; @@ -230,9 +230,9 @@ static uint16_t ducky_get_keycode(const char* param, bool accept_chars) { return 0; } -static int32_t ducky_parse_line(BadUsbScript* bad_usb, string_t line) { - uint32_t line_len = string_size(line); - const char* line_tmp = string_get_cstr(line); +static int32_t ducky_parse_line(BadUsbScript* bad_usb, FuriString* line) { + uint32_t line_len = furi_string_size(line); + const char* line_tmp = furi_string_get_cstr(line); bool state = false; for(uint32_t i = 0; i < line_len; i++) { @@ -337,7 +337,7 @@ static bool ducky_script_preload(BadUsbScript* bad_usb, File* script_file) { uint8_t ret = 0; uint32_t line_len = 0; - string_reset(bad_usb->line); + furi_string_reset(bad_usb->line); do { ret = storage_file_read(script_file, bad_usb->file_buf, FILE_BUFFER_LEN); @@ -347,7 +347,7 @@ static bool ducky_script_preload(BadUsbScript* bad_usb, File* script_file) { line_len = 0; } else { if(bad_usb->st.line_nb == 0) { // Save first line - string_push_back(bad_usb->line, bad_usb->file_buf[i]); + furi_string_push_back(bad_usb->line, bad_usb->file_buf[i]); } line_len++; } @@ -360,7 +360,7 @@ static bool ducky_script_preload(BadUsbScript* bad_usb, File* script_file) { } } while(ret > 0); - const char* line_tmp = string_get_cstr(bad_usb->line); + const char* line_tmp = furi_string_get_cstr(bad_usb->line); bool id_set = false; // Looking for ID command at first line if(strncmp(line_tmp, ducky_cmd_id, strlen(ducky_cmd_id)) == 0) { id_set = ducky_set_usb_id(bad_usb, &line_tmp[strlen(ducky_cmd_id) + 1]); @@ -373,7 +373,7 @@ static bool ducky_script_preload(BadUsbScript* bad_usb, File* script_file) { } storage_file_seek(script_file, 0, true); - string_reset(bad_usb->line); + furi_string_reset(bad_usb->line); return true; } @@ -395,8 +395,8 @@ static int32_t ducky_script_execute_next(BadUsbScript* bad_usb, File* script_fil } } - string_set(bad_usb->line_prev, bad_usb->line); - string_reset(bad_usb->line); + furi_string_set(bad_usb->line_prev, bad_usb->line); + furi_string_reset(bad_usb->line); while(1) { if(bad_usb->buf_len == 0) { @@ -413,7 +413,7 @@ static int32_t ducky_script_execute_next(BadUsbScript* bad_usb, File* script_fil if(bad_usb->buf_len == 0) return SCRIPT_STATE_END; } for(uint8_t i = bad_usb->buf_start; i < (bad_usb->buf_start + bad_usb->buf_len); i++) { - if(bad_usb->file_buf[i] == '\n' && string_size(bad_usb->line) > 0) { + if(bad_usb->file_buf[i] == '\n' && furi_string_size(bad_usb->line) > 0) { bad_usb->st.line_cur++; bad_usb->buf_len = bad_usb->buf_len + bad_usb->buf_start - (i + 1); bad_usb->buf_start = i + 1; @@ -426,7 +426,7 @@ static int32_t ducky_script_execute_next(BadUsbScript* bad_usb, File* script_fil return (delay_val + bad_usb->defdelay); } } else { - string_push_back(bad_usb->line, bad_usb->file_buf[i]); + furi_string_push_back(bad_usb->line, bad_usb->file_buf[i]); } } bad_usb->buf_len = 0; @@ -456,8 +456,8 @@ static int32_t bad_usb_worker(void* context) { FURI_LOG_I(WORKER_TAG, "Init"); File* script_file = storage_file_alloc(furi_record_open(RECORD_STORAGE)); - string_init(bad_usb->line); - string_init(bad_usb->line_prev); + bad_usb->line = furi_string_alloc(); + bad_usb->line_prev = furi_string_alloc(); furi_hal_hid_set_state_callback(bad_usb_hid_state_callback, bad_usb); @@ -465,7 +465,7 @@ static int32_t bad_usb_worker(void* context) { if(worker_state == BadUsbStateInit) { // State: initialization if(storage_file_open( script_file, - string_get_cstr(bad_usb->file_path), + furi_string_get_cstr(bad_usb->file_path), FSAM_READ, FSOM_OPEN_EXISTING)) { if((ducky_script_preload(bad_usb, script_file)) && (bad_usb->st.line_nb > 0)) { @@ -577,20 +577,20 @@ static int32_t bad_usb_worker(void* context) { storage_file_close(script_file); storage_file_free(script_file); - string_clear(bad_usb->line); - string_clear(bad_usb->line_prev); + furi_string_free(bad_usb->line); + furi_string_free(bad_usb->line_prev); FURI_LOG_I(WORKER_TAG, "End"); return 0; } -BadUsbScript* bad_usb_script_open(string_t file_path) { +BadUsbScript* bad_usb_script_open(FuriString* file_path) { furi_assert(file_path); BadUsbScript* bad_usb = malloc(sizeof(BadUsbScript)); - string_init(bad_usb->file_path); - string_set(bad_usb->file_path, file_path); + bad_usb->file_path = furi_string_alloc(); + furi_string_set(bad_usb->file_path, file_path); bad_usb->st.state = BadUsbStateInit; @@ -609,7 +609,7 @@ void bad_usb_script_close(BadUsbScript* bad_usb) { furi_thread_flags_set(furi_thread_get_id(bad_usb->thread), WorkerEvtEnd); furi_thread_join(bad_usb->thread); furi_thread_free(bad_usb->thread); - string_clear(bad_usb->file_path); + furi_string_free(bad_usb->file_path); free(bad_usb); } diff --git a/applications/main/bad_usb/bad_usb_script.h b/applications/main/bad_usb/bad_usb_script.h index 88921de381e..5fee6505f51 100644 --- a/applications/main/bad_usb/bad_usb_script.h +++ b/applications/main/bad_usb/bad_usb_script.h @@ -5,7 +5,6 @@ extern "C" { #endif #include -#include typedef struct BadUsbScript BadUsbScript; @@ -28,7 +27,7 @@ typedef struct { uint16_t error_line; } BadUsbState; -BadUsbScript* bad_usb_script_open(string_t file_path); +BadUsbScript* bad_usb_script_open(FuriString* file_path); void bad_usb_script_close(BadUsbScript* bad_usb); diff --git a/applications/main/bad_usb/scenes/bad_usb_scene_work.c b/applications/main/bad_usb/scenes/bad_usb_scene_work.c index 516cbde3af4..689f3b4ea9c 100644 --- a/applications/main/bad_usb/scenes/bad_usb_scene_work.c +++ b/applications/main/bad_usb/scenes/bad_usb_scene_work.c @@ -2,7 +2,6 @@ #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) { @@ -27,14 +26,14 @@ bool bad_usb_scene_work_on_event(void* context, SceneManagerEvent event) { void bad_usb_scene_work_on_enter(void* context) { BadUsbApp* app = context; - string_t file_name; - string_init(file_name); + FuriString* file_name; + file_name = furi_string_alloc(); path_extract_filename(app->file_path, file_name, true); - bad_usb_set_file_name(app->bad_usb_view, string_get_cstr(file_name)); + bad_usb_set_file_name(app->bad_usb_view, furi_string_get_cstr(file_name)); app->bad_usb_script = bad_usb_script_open(app->file_path); - string_clear(file_name); + furi_string_free(file_name); bad_usb_set_state(app->bad_usb_view, bad_usb_script_get_state(app->bad_usb_script)); diff --git a/applications/main/bad_usb/views/bad_usb_view.c b/applications/main/bad_usb/views/bad_usb_view.c index 0679669f6b5..4e025b99c32 100644 --- a/applications/main/bad_usb/views/bad_usb_view.c +++ b/applications/main/bad_usb/views/bad_usb_view.c @@ -19,12 +19,12 @@ typedef struct { static void bad_usb_draw_callback(Canvas* canvas, void* _model) { BadUsbModel* model = _model; - string_t disp_str; - string_init_set_str(disp_str, model->file_name); + FuriString* disp_str; + disp_str = furi_string_alloc_set(model->file_name); elements_string_fit_width(canvas, disp_str, 128 - 2); canvas_set_font(canvas, FontSecondary); - canvas_draw_str(canvas, 2, 8, string_get_cstr(disp_str)); - string_reset(disp_str); + canvas_draw_str(canvas, 2, 8, furi_string_get_cstr(disp_str)); + furi_string_reset(disp_str); canvas_draw_icon(canvas, 22, 20, &I_UsbTree_48x22); @@ -49,10 +49,10 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) { canvas_set_font(canvas, FontPrimary); canvas_draw_str_aligned(canvas, 127, 33, AlignRight, AlignBottom, "ERROR:"); canvas_set_font(canvas, FontSecondary); - string_printf(disp_str, "line %u", model->state.error_line); + furi_string_printf(disp_str, "line %u", model->state.error_line); canvas_draw_str_aligned( - canvas, 127, 46, AlignRight, AlignBottom, string_get_cstr(disp_str)); - string_reset(disp_str); + canvas, 127, 46, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); + furi_string_reset(disp_str); } else if(model->state.state == BadUsbStateIdle) { canvas_draw_icon(canvas, 4, 22, &I_Smile_18x18); canvas_set_font(canvas, FontBigNumbers); @@ -65,16 +65,17 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) { canvas_draw_icon(canvas, 4, 19, &I_EviSmile2_18x21); } canvas_set_font(canvas, FontBigNumbers); - string_printf(disp_str, "%u", ((model->state.line_cur - 1) * 100) / model->state.line_nb); + furi_string_printf( + disp_str, "%u", ((model->state.line_cur - 1) * 100) / model->state.line_nb); canvas_draw_str_aligned( - canvas, 114, 36, AlignRight, AlignBottom, string_get_cstr(disp_str)); - string_reset(disp_str); + canvas, 114, 36, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); + furi_string_reset(disp_str); canvas_draw_icon(canvas, 117, 22, &I_Percent_10x14); } else if(model->state.state == BadUsbStateDone) { canvas_draw_icon(canvas, 4, 19, &I_EviSmile1_18x21); canvas_set_font(canvas, FontBigNumbers); canvas_draw_str_aligned(canvas, 114, 36, AlignRight, AlignBottom, "100"); - string_reset(disp_str); + furi_string_reset(disp_str); canvas_draw_icon(canvas, 117, 22, &I_Percent_10x14); } else if(model->state.state == BadUsbStateDelay) { if(model->anim_frame == 0) { @@ -83,21 +84,22 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) { canvas_draw_icon(canvas, 4, 19, &I_EviWaiting2_18x21); } canvas_set_font(canvas, FontBigNumbers); - string_printf(disp_str, "%u", ((model->state.line_cur - 1) * 100) / model->state.line_nb); + furi_string_printf( + disp_str, "%u", ((model->state.line_cur - 1) * 100) / model->state.line_nb); canvas_draw_str_aligned( - canvas, 114, 36, AlignRight, AlignBottom, string_get_cstr(disp_str)); - string_reset(disp_str); + canvas, 114, 36, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); + furi_string_reset(disp_str); canvas_draw_icon(canvas, 117, 22, &I_Percent_10x14); canvas_set_font(canvas, FontSecondary); - string_printf(disp_str, "delay %us", model->state.delay_remain); + furi_string_printf(disp_str, "delay %us", model->state.delay_remain); canvas_draw_str_aligned( - canvas, 127, 46, AlignRight, AlignBottom, string_get_cstr(disp_str)); - string_reset(disp_str); + canvas, 127, 46, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); + furi_string_reset(disp_str); } else { canvas_draw_icon(canvas, 4, 22, &I_Clock_18x18); } - string_clear(disp_str); + furi_string_free(disp_str); } static bool bad_usb_input_callback(InputEvent* event, void* context) { diff --git a/applications/main/fap_loader/fap_loader_app.c b/applications/main/fap_loader/fap_loader_app.c index c0f60ceca98..1486cc1a105 100644 --- a/applications/main/fap_loader/fap_loader_app.c +++ b/applications/main/fap_loader/fap_loader_app.c @@ -14,21 +14,24 @@ typedef struct { Storage* storage; DialogsApp* dialogs; Gui* gui; - string_t fap_path; + FuriString* fap_path; ViewDispatcher* view_dispatcher; Loading* loading; } FapLoader; -static bool - fap_loader_item_callback(string_t path, void* context, uint8_t** icon_ptr, string_t item_name) { +static bool fap_loader_item_callback( + FuriString* path, + void* context, + uint8_t** icon_ptr, + FuriString* item_name) { FapLoader* loader = context; furi_assert(loader); FlipperApplication* app = flipper_application_alloc(loader->storage, &hashtable_api_interface); FlipperApplicationPreloadStatus preload_res = - flipper_application_preload_manifest(app, string_get_cstr(path)); + flipper_application_preload_manifest(app, furi_string_get_cstr(path)); bool load_success = false; @@ -37,10 +40,10 @@ static bool if(manifest->has_icon) { memcpy(*icon_ptr, manifest->icon, FAP_MANIFEST_MAX_ICON_SIZE); } - string_set_str(item_name, manifest->name); + furi_string_set(item_name, manifest->name); load_success = true; } else { - FURI_LOG_E(TAG, "FAP Loader failed to preload %s", string_get_cstr(path)); + FURI_LOG_E(TAG, "FAP Loader failed to preload %s", furi_string_get_cstr(path)); load_success = false; } @@ -51,9 +54,9 @@ static bool static bool fap_loader_run_selected_app(FapLoader* loader) { furi_assert(loader); - string_t error_message; + FuriString* error_message; - string_init_set(error_message, "unknown error"); + error_message = furi_string_alloc_set("unknown error"); bool file_selected = false; bool show_error = true; @@ -61,17 +64,17 @@ static bool fap_loader_run_selected_app(FapLoader* loader) { file_selected = true; loader->app = flipper_application_alloc(loader->storage, &hashtable_api_interface); - FURI_LOG_I(TAG, "FAP Loader is loading %s", string_get_cstr(loader->fap_path)); + FURI_LOG_I(TAG, "FAP Loader is loading %s", furi_string_get_cstr(loader->fap_path)); FlipperApplicationPreloadStatus preload_res = - flipper_application_preload(loader->app, string_get_cstr(loader->fap_path)); + flipper_application_preload(loader->app, furi_string_get_cstr(loader->fap_path)); if(preload_res != FlipperApplicationPreloadStatusSuccess) { const char* err_msg = flipper_application_preload_status_to_string(preload_res); - string_printf(error_message, "Preload failed: %s", err_msg); + furi_string_printf(error_message, "Preload failed: %s", err_msg); FURI_LOG_E( TAG, "FAP Loader failed to preload %s: %s", - string_get_cstr(loader->fap_path), + furi_string_get_cstr(loader->fap_path), err_msg); break; } @@ -80,11 +83,11 @@ static bool fap_loader_run_selected_app(FapLoader* loader) { FlipperApplicationLoadStatus load_status = flipper_application_map_to_memory(loader->app); if(load_status != FlipperApplicationLoadStatusSuccess) { const char* err_msg = flipper_application_load_status_to_string(load_status); - string_printf(error_message, "Load failed: %s", err_msg); + furi_string_printf(error_message, "Load failed: %s", err_msg); FURI_LOG_E( TAG, "FAP Loader failed to map to memory %s: %s", - string_get_cstr(loader->fap_path), + furi_string_get_cstr(loader->fap_path), err_msg); break; } @@ -106,19 +109,19 @@ static bool fap_loader_run_selected_app(FapLoader* loader) { dialog_message_set_header(message, "Error", 64, 0, AlignCenter, AlignTop); dialog_message_set_buttons(message, NULL, NULL, NULL); - string_t buffer; - string_init(buffer); - string_printf(buffer, "%s", string_get_cstr(error_message)); - string_replace_str(buffer, ":", "\n"); + FuriString* buffer; + buffer = furi_string_alloc(); + furi_string_printf(buffer, "%s", furi_string_get_cstr(error_message)); + furi_string_replace(buffer, ":", "\n"); dialog_message_set_text( - message, string_get_cstr(buffer), 64, 32, AlignCenter, AlignCenter); + message, furi_string_get_cstr(buffer), 64, 32, AlignCenter, AlignCenter); dialog_message_show(loader->dialogs, message); dialog_message_free(message); - string_clear(buffer); + furi_string_free(buffer); } - string_clear(error_message); + furi_string_free(error_message); if(file_selected) { flipper_application_free(loader->app); @@ -155,10 +158,10 @@ int32_t fap_loader_app(void* p) { view_dispatcher_add_view(loader->view_dispatcher, 0, loading_get_view(loader->loading)); if(p) { - string_init_set(loader->fap_path, (const char*)p); + loader->fap_path = furi_string_alloc_set((const char*)p); fap_loader_run_selected_app(loader); } else { - string_init_set(loader->fap_path, EXT_PATH("apps")); + loader->fap_path = furi_string_alloc_set(EXT_PATH("apps")); while(fap_loader_select_app(loader)) { view_dispatcher_switch_to_view(loader->view_dispatcher, 0); @@ -170,7 +173,7 @@ int32_t fap_loader_app(void* p) { loading_free(loader->loading); view_dispatcher_free(loader->view_dispatcher); - string_clear(loader->fap_path); + furi_string_free(loader->fap_path); furi_record_close(RECORD_GUI); furi_record_close(RECORD_DIALOGS); furi_record_close(RECORD_STORAGE); diff --git a/applications/main/ibutton/ibutton.c b/applications/main/ibutton/ibutton.c index e9ec614ecd4..887fb3e75f4 100644 --- a/applications/main/ibutton/ibutton.c +++ b/applications/main/ibutton/ibutton.c @@ -2,7 +2,6 @@ #include "assets_icons.h" #include "ibutton_i.h" #include "ibutton/scenes/ibutton_scene.h" -#include "m-string.h" #include #include #include @@ -39,25 +38,25 @@ static void ibutton_make_app_folder(iButton* ibutton) { } } -bool ibutton_load_key_data(iButton* ibutton, string_t key_path, bool show_dialog) { +bool ibutton_load_key_data(iButton* ibutton, FuriString* key_path, bool show_dialog) { FlipperFormat* file = flipper_format_file_alloc(ibutton->storage); bool result = false; - string_t data; - string_init(data); + FuriString* data; + data = furi_string_alloc(); do { - if(!flipper_format_file_open_existing(file, string_get_cstr(key_path))) break; + if(!flipper_format_file_open_existing(file, furi_string_get_cstr(key_path))) break; // header uint32_t version; if(!flipper_format_read_header(file, data, &version)) break; - if(string_cmp_str(data, IBUTTON_APP_FILE_TYPE) != 0) break; + if(furi_string_cmp_str(data, IBUTTON_APP_FILE_TYPE) != 0) break; if(version != 1) break; // key type iButtonKeyType type; if(!flipper_format_read_string(file, "Key type", data)) break; - if(!ibutton_key_get_type_by_string(string_get_cstr(data), &type)) break; + if(!ibutton_key_get_type_by_string(furi_string_get_cstr(data), &type)) break; // key data uint8_t key_data[IBUTTON_KEY_DATA_SIZE] = {0}; @@ -71,7 +70,7 @@ bool ibutton_load_key_data(iButton* ibutton, string_t key_path, bool show_dialog } while(false); flipper_format_free(file); - string_clear(data); + furi_string_free(data); if((!result) && (show_dialog)) { dialog_message_show_storage_error(ibutton->dialogs, "Cannot load\nkey file"); @@ -119,7 +118,7 @@ void ibutton_tick_event_callback(void* context) { iButton* ibutton_alloc() { iButton* ibutton = malloc(sizeof(iButton)); - string_init(ibutton->file_path); + ibutton->file_path = furi_string_alloc(); ibutton->scene_manager = scene_manager_alloc(&ibutton_scene_handlers, ibutton); @@ -210,7 +209,7 @@ void ibutton_free(iButton* ibutton) { ibutton_worker_free(ibutton->key_worker); ibutton_key_free(ibutton->key); - string_clear(ibutton->file_path); + furi_string_free(ibutton->file_path); free(ibutton); } @@ -240,19 +239,19 @@ bool ibutton_save_key(iButton* ibutton, const char* key_name) { do { // Check if we has old key - if(string_end_with_str_p(ibutton->file_path, IBUTTON_APP_EXTENSION)) { + if(furi_string_end_with(ibutton->file_path, IBUTTON_APP_EXTENSION)) { // First remove old key ibutton_delete_key(ibutton); // Remove old key name from path - size_t filename_start = string_search_rchar(ibutton->file_path, '/'); - string_left(ibutton->file_path, filename_start); + size_t filename_start = furi_string_search_rchar(ibutton->file_path, '/'); + furi_string_left(ibutton->file_path, filename_start); } - string_cat_printf(ibutton->file_path, "/%s%s", key_name, IBUTTON_APP_EXTENSION); + furi_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(ibutton->file_path))) break; + if(!flipper_format_file_open_always(file, furi_string_get_cstr(ibutton->file_path))) break; // Write header if(!flipper_format_write_header_cstr(file, IBUTTON_APP_FILE_TYPE, 1)) break; @@ -286,7 +285,7 @@ bool ibutton_save_key(iButton* ibutton, const char* key_name) { bool ibutton_delete_key(iButton* ibutton) { bool result = false; - result = storage_simply_remove(ibutton->storage, string_get_cstr(ibutton->file_path)); + result = storage_simply_remove(ibutton->storage, furi_string_get_cstr(ibutton->file_path)); return result; } @@ -326,7 +325,7 @@ int32_t ibutton_app(void* p) { rpc_system_app_set_callback(ibutton->rpc_ctx, ibutton_rpc_command_callback, ibutton); rpc_system_app_send_started(ibutton->rpc_ctx); } else { - string_set_str(ibutton->file_path, (const char*)p); + furi_string_set(ibutton->file_path, (const char*)p); if(ibutton_load_key_data(ibutton, ibutton->file_path, true)) { key_loaded = true; // TODO: Display an error if the key from p could not be loaded diff --git a/applications/main/ibutton/ibutton_cli.c b/applications/main/ibutton/ibutton_cli.c index d36d3dffd23..fab1ccf05ab 100644 --- a/applications/main/ibutton/ibutton_cli.c +++ b/applications/main/ibutton/ibutton_cli.c @@ -6,8 +6,8 @@ #include #include -static void ibutton_cli(Cli* cli, string_t args, void* context); -static void onewire_cli(Cli* cli, string_t args, void* context); +static void ibutton_cli(Cli* cli, FuriString* args, void* context); +static void onewire_cli(Cli* cli, FuriString* args, void* context); // app cli function void ibutton_on_system_start() { @@ -34,16 +34,16 @@ void ibutton_cli_print_usage() { printf("\t are hex-formatted\r\n"); }; -bool ibutton_cli_get_key_type(string_t data, iButtonKeyType* type) { +bool ibutton_cli_get_key_type(FuriString* data, iButtonKeyType* type) { bool result = false; - if(string_cmp_str(data, "Dallas") == 0 || string_cmp_str(data, "dallas") == 0) { + if(furi_string_cmp_str(data, "Dallas") == 0 || furi_string_cmp_str(data, "dallas") == 0) { result = true; *type = iButtonKeyDS1990; - } else if(string_cmp_str(data, "Cyfral") == 0 || string_cmp_str(data, "cyfral") == 0) { + } else if(furi_string_cmp_str(data, "Cyfral") == 0 || furi_string_cmp_str(data, "cyfral") == 0) { result = true; *type = iButtonKeyCyfral; - } else if(string_cmp_str(data, "Metakom") == 0 || string_cmp_str(data, "metakom") == 0) { + } else if(furi_string_cmp_str(data, "Metakom") == 0 || furi_string_cmp_str(data, "metakom") == 0) { result = true; *type = iButtonKeyMetakom; } @@ -123,17 +123,17 @@ static void ibutton_cli_worker_write_cb(void* context, iButtonWorkerWriteResult furi_event_flag_set(write_context->event, EVENT_FLAG_IBUTTON_COMPLETE); } -void ibutton_cli_write(Cli* cli, string_t args) { +void ibutton_cli_write(Cli* cli, FuriString* args) { iButtonKey* key = ibutton_key_alloc(); iButtonWorker* worker = ibutton_worker_alloc(); iButtonKeyType type; iButtonWriteContext write_context; uint8_t key_data[IBUTTON_KEY_DATA_SIZE]; - string_t data; + FuriString* data; write_context.event = furi_event_flag_alloc(); - string_init(data); + data = furi_string_alloc(); ibutton_worker_start_thread(worker); ibutton_worker_write_set_callback(worker, ibutton_cli_worker_write_cb, &write_context); @@ -186,7 +186,7 @@ void ibutton_cli_write(Cli* cli, string_t args) { ibutton_worker_stop(worker); } while(false); - string_clear(data); + furi_string_free(data); ibutton_worker_stop_thread(worker); ibutton_worker_free(worker); ibutton_key_free(key); @@ -194,14 +194,14 @@ void ibutton_cli_write(Cli* cli, string_t args) { furi_event_flag_free(write_context.event); }; -void ibutton_cli_emulate(Cli* cli, string_t args) { +void ibutton_cli_emulate(Cli* cli, FuriString* args) { iButtonKey* key = ibutton_key_alloc(); iButtonWorker* worker = ibutton_worker_alloc(); iButtonKeyType type; uint8_t key_data[IBUTTON_KEY_DATA_SIZE]; - string_t data; + FuriString* data; - string_init(data); + data = furi_string_alloc(); ibutton_worker_start_thread(worker); do { @@ -234,34 +234,34 @@ void ibutton_cli_emulate(Cli* cli, string_t args) { ibutton_worker_stop(worker); } while(false); - string_clear(data); + furi_string_free(data); ibutton_worker_stop_thread(worker); ibutton_worker_free(worker); ibutton_key_free(key); }; -static void ibutton_cli(Cli* cli, string_t args, void* context) { +static void ibutton_cli(Cli* cli, FuriString* args, void* context) { UNUSED(context); - string_t cmd; - string_init(cmd); + FuriString* cmd; + cmd = furi_string_alloc(); if(!args_read_string_and_trim(args, cmd)) { - string_clear(cmd); + furi_string_free(cmd); ibutton_cli_print_usage(); return; } - if(string_cmp_str(cmd, "read") == 0) { + if(furi_string_cmp_str(cmd, "read") == 0) { ibutton_cli_read(cli); - } else if(string_cmp_str(cmd, "write") == 0) { + } else if(furi_string_cmp_str(cmd, "write") == 0) { ibutton_cli_write(cli, args); - } else if(string_cmp_str(cmd, "emulate") == 0) { + } else if(furi_string_cmp_str(cmd, "emulate") == 0) { ibutton_cli_emulate(cli, args); } else { ibutton_cli_print_usage(); } - string_clear(cmd); + furi_string_free(cmd); } void onewire_cli_print_usage() { @@ -299,20 +299,20 @@ static void onewire_cli_search(Cli* cli) { onewire_host_free(onewire); } -void onewire_cli(Cli* cli, string_t args, void* context) { +void onewire_cli(Cli* cli, FuriString* args, void* context) { UNUSED(context); - string_t cmd; - string_init(cmd); + FuriString* cmd; + cmd = furi_string_alloc(); if(!args_read_string_and_trim(args, cmd)) { - string_clear(cmd); + furi_string_free(cmd); onewire_cli_print_usage(); return; } - if(string_cmp_str(cmd, "search") == 0) { + if(furi_string_cmp_str(cmd, "search") == 0) { onewire_cli_search(cli); } - string_clear(cmd); + furi_string_free(cmd); } diff --git a/applications/main/ibutton/ibutton_i.h b/applications/main/ibutton/ibutton_i.h index fd11b8c198e..a9515195e9d 100644 --- a/applications/main/ibutton/ibutton_i.h +++ b/applications/main/ibutton/ibutton_i.h @@ -41,7 +41,7 @@ struct iButton { iButtonWorker* key_worker; iButtonKey* key; - string_t file_path; + FuriString* file_path; char text_store[IBUTTON_TEXT_STORE_SIZE + 1]; Submenu* submenu; @@ -78,7 +78,7 @@ typedef enum { } iButtonNotificationMessage; bool ibutton_file_select(iButton* ibutton); -bool ibutton_load_key_data(iButton* ibutton, string_t key_path, bool show_dialog); +bool ibutton_load_key_data(iButton* ibutton, FuriString* key_path, bool show_dialog); 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/main/ibutton/scenes/ibutton_scene_add_type.c b/applications/main/ibutton/scenes/ibutton_scene_add_type.c index 9a0583a58b9..38373999c97 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_add_type.c +++ b/applications/main/ibutton/scenes/ibutton_scene_add_type.c @@ -1,5 +1,4 @@ #include "../ibutton_i.h" -#include "m-string.h" enum SubmenuIndex { SubmenuIndexCyfral, @@ -47,7 +46,7 @@ bool ibutton_scene_add_type_on_event(void* context, SceneManagerEvent event) { furi_crash("Unknown key type"); } - string_set_str(ibutton->file_path, IBUTTON_APP_FOLDER); + furi_string_set(ibutton->file_path, IBUTTON_APP_FOLDER); ibutton_key_clear_data(key); scene_manager_next_scene(ibutton->scene_manager, iButtonSceneAddValue); } diff --git a/applications/main/ibutton/scenes/ibutton_scene_delete_confirm.c b/applications/main/ibutton/scenes/ibutton_scene_delete_confirm.c index fccefe6ec0e..3d609e833f2 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_delete_confirm.c +++ b/applications/main/ibutton/scenes/ibutton_scene_delete_confirm.c @@ -17,11 +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); - string_t key_name; - string_init(key_name); + FuriString* key_name; + key_name = furi_string_alloc(); path_extract_filename(ibutton->file_path, key_name, true); - ibutton_text_store_set(ibutton, "\e#Delete %s?\e#", string_get_cstr(key_name)); + ibutton_text_store_set(ibutton, "\e#Delete %s?\e#", furi_string_get_cstr(key_name)); widget_add_text_box_element( widget, 0, 0, 128, 27, AlignCenter, AlignCenter, ibutton->text_store, true); widget_add_button_element( @@ -68,7 +68,7 @@ void ibutton_scene_delete_confirm_on_enter(void* context) { view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget); - string_clear(key_name); + furi_string_free(key_name); } bool ibutton_scene_delete_confirm_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/main/ibutton/scenes/ibutton_scene_emulate.c b/applications/main/ibutton/scenes/ibutton_scene_emulate.c index e6e0ec68850..b3bc38ead2c 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_emulate.c +++ b/applications/main/ibutton/scenes/ibutton_scene_emulate.c @@ -20,17 +20,17 @@ void ibutton_scene_emulate_on_enter(void* context) { const uint8_t* key_data = ibutton_key_get_data_p(key); - string_t key_name; - string_init(key_name); - if(string_end_with_str_p(ibutton->file_path, IBUTTON_APP_EXTENSION)) { + FuriString* key_name; + key_name = furi_string_alloc(); + if(furi_string_end_with(ibutton->file_path, IBUTTON_APP_EXTENSION)) { path_extract_filename(ibutton->file_path, key_name, true); } DOLPHIN_DEED(DolphinDeedIbuttonEmulate); // check that stored key has name - if(!string_empty_p(key_name)) { - ibutton_text_store_set(ibutton, "%s", string_get_cstr(key_name)); + if(!furi_string_empty(key_name)) { + ibutton_text_store_set(ibutton, "%s", furi_string_get_cstr(key_name)); } else { // if not, show key data switch(ibutton_key_get_type(key)) { @@ -69,7 +69,7 @@ void ibutton_scene_emulate_on_enter(void* context) { ibutton->key_worker, ibutton_scene_emulate_callback, ibutton); ibutton_worker_emulate_start(ibutton->key_worker, key); - string_clear(key_name); + furi_string_free(key_name); ibutton_notification_message(ibutton, iButtonNotificationMessageEmulateStart); } diff --git a/applications/main/ibutton/scenes/ibutton_scene_info.c b/applications/main/ibutton/scenes/ibutton_scene_info.c index 9eec2bee1b7..15648f6f293 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_info.c +++ b/applications/main/ibutton/scenes/ibutton_scene_info.c @@ -8,11 +8,11 @@ void ibutton_scene_info_on_enter(void* context) { const uint8_t* key_data = ibutton_key_get_data_p(key); - string_t key_name; - string_init(key_name); + FuriString* key_name; + key_name = furi_string_alloc(); path_extract_filename(ibutton->file_path, key_name, true); - ibutton_text_store_set(ibutton, "%s", string_get_cstr(key_name)); + ibutton_text_store_set(ibutton, "%s", furi_string_get_cstr(key_name)); widget_add_text_box_element( widget, 0, 0, 128, 23, AlignCenter, AlignCenter, ibutton->text_store, true); @@ -50,7 +50,7 @@ void ibutton_scene_info_on_enter(void* context) { view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget); - string_clear(key_name); + furi_string_free(key_name); } bool ibutton_scene_info_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/main/ibutton/scenes/ibutton_scene_read.c b/applications/main/ibutton/scenes/ibutton_scene_read.c index 7af351f0624..05920a0adbd 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_read.c +++ b/applications/main/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); - string_set_str(ibutton->file_path, IBUTTON_APP_FOLDER); + furi_string_set(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/main/ibutton/scenes/ibutton_scene_rpc.c b/applications/main/ibutton/scenes/ibutton_scene_rpc.c index 0755c8ff81d..b25b1b8dd86 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_rpc.c +++ b/applications/main/ibutton/scenes/ibutton_scene_rpc.c @@ -29,19 +29,19 @@ bool ibutton_scene_rpc_on_event(void* context, SceneManagerEvent event) { if(event.event == iButtonCustomEventRpcLoad) { const char* arg = rpc_system_app_get_data(ibutton->rpc_ctx); bool result = false; - if(arg && (string_empty_p(ibutton->file_path))) { - string_set_str(ibutton->file_path, arg); + if(arg && (furi_string_empty(ibutton->file_path))) { + furi_string_set(ibutton->file_path, arg); if(ibutton_load_key_data(ibutton, ibutton->file_path, false)) { ibutton_worker_emulate_start(ibutton->key_worker, ibutton->key); - string_t key_name; - string_init(key_name); - if(string_end_with_str_p(ibutton->file_path, IBUTTON_APP_EXTENSION)) { + FuriString* key_name; + key_name = furi_string_alloc(); + if(furi_string_end_with(ibutton->file_path, IBUTTON_APP_EXTENSION)) { path_extract_filename(ibutton->file_path, key_name, true); } - if(!string_empty_p(key_name)) { + if(!furi_string_empty(key_name)) { ibutton_text_store_set( - ibutton, "emulating\n%s", string_get_cstr(key_name)); + ibutton, "emulating\n%s", furi_string_get_cstr(key_name)); } else { ibutton_text_store_set(ibutton, "emulating"); } @@ -49,10 +49,10 @@ bool ibutton_scene_rpc_on_event(void* context, SceneManagerEvent event) { ibutton_notification_message(ibutton, iButtonNotificationMessageEmulateStart); - string_clear(key_name); + furi_string_free(key_name); result = true; } else { - string_reset(ibutton->file_path); + furi_string_reset(ibutton->file_path); } } rpc_system_app_confirm(ibutton->rpc_ctx, RpcAppEventLoadFile, result); diff --git a/applications/main/ibutton/scenes/ibutton_scene_save_name.c b/applications/main/ibutton/scenes/ibutton_scene_save_name.c index be64038740b..773b93e0c8a 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_save_name.c +++ b/applications/main/ibutton/scenes/ibutton_scene_save_name.c @@ -1,5 +1,4 @@ #include "../ibutton_i.h" -#include "m-string.h" #include #include @@ -12,17 +11,17 @@ void ibutton_scene_save_name_on_enter(void* context) { iButton* ibutton = context; TextInput* text_input = ibutton->text_input; - string_t key_name; - string_init(key_name); - if(string_end_with_str_p(ibutton->file_path, IBUTTON_APP_EXTENSION)) { + FuriString* key_name; + key_name = furi_string_alloc(); + if(furi_string_end_with(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); + const bool key_name_is_empty = furi_string_empty(key_name); if(key_name_is_empty) { set_random_name(ibutton->text_store, IBUTTON_TEXT_STORE_SIZE); } else { - ibutton_text_store_set(ibutton, "%s", string_get_cstr(key_name)); + ibutton_text_store_set(ibutton, "%s", furi_string_get_cstr(key_name)); } text_input_set_header_text(text_input, "Name the key"); @@ -34,19 +33,19 @@ void ibutton_scene_save_name_on_enter(void* context) { IBUTTON_KEY_NAME_SIZE, key_name_is_empty); - string_t folder_path; - string_init(folder_path); + FuriString* folder_path; + folder_path = furi_string_alloc(); - path_extract_dirname(string_get_cstr(ibutton->file_path), folder_path); + path_extract_dirname(furi_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)); + furi_string_get_cstr(folder_path), IBUTTON_APP_EXTENSION, furi_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); + furi_string_free(key_name); + furi_string_free(folder_path); } bool ibutton_scene_save_name_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/main/ibutton/scenes/ibutton_scene_start.c b/applications/main/ibutton/scenes/ibutton_scene_start.c index c2844cde91f..dde224e157c 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_start.c +++ b/applications/main/ibutton/scenes/ibutton_scene_start.c @@ -39,7 +39,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); + furi_string_set(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/main/ibutton/scenes/ibutton_scene_write.c b/applications/main/ibutton/scenes/ibutton_scene_write.c index af65575c24a..cdea04db3d4 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_write.c +++ b/applications/main/ibutton/scenes/ibutton_scene_write.c @@ -1,5 +1,4 @@ #include "../ibutton_i.h" -#include "m-string.h" #include "toolbox/path.h" typedef enum { @@ -20,15 +19,15 @@ void ibutton_scene_write_on_enter(void* context) { const uint8_t* key_data = ibutton_key_get_data_p(key); - string_t key_name; - string_init(key_name); - if(string_end_with_str_p(ibutton->file_path, IBUTTON_APP_EXTENSION)) { + FuriString* key_name; + key_name = furi_string_alloc(); + if(furi_string_end_with(ibutton->file_path, IBUTTON_APP_EXTENSION)) { path_extract_filename(ibutton->file_path, key_name, true); } // check that stored key has name - if(!string_empty_p(key_name)) { - ibutton_text_store_set(ibutton, "%s", string_get_cstr(key_name)); + if(!furi_string_empty(key_name)) { + ibutton_text_store_set(ibutton, "%s", furi_string_get_cstr(key_name)); } else { // if not, show key data switch(ibutton_key_get_type(key)) { @@ -66,7 +65,7 @@ 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); + furi_string_free(key_name); ibutton_notification_message(ibutton, iButtonNotificationMessageEmulateStart); } diff --git a/applications/main/infrared/infrared.c b/applications/main/infrared/infrared.c index 60809e7871e..a647b158e12 100644 --- a/applications/main/infrared/infrared.c +++ b/applications/main/infrared/infrared.c @@ -65,51 +65,52 @@ static void infrared_rpc_command_callback(RpcAppSystemEvent event, void* context } } -static void infrared_find_vacant_remote_name(string_t name, const char* path) { +static void infrared_find_vacant_remote_name(FuriString* name, const char* path) { Storage* storage = furi_record_open(RECORD_STORAGE); - string_t base_path; - string_init_set_str(base_path, path); + FuriString* base_path; + base_path = furi_string_alloc_set(path); - if(string_end_with_str_p(base_path, INFRARED_APP_EXTENSION)) { - size_t filename_start = string_search_rchar(base_path, '/'); - string_left(base_path, filename_start); + if(furi_string_end_with(base_path, INFRARED_APP_EXTENSION)) { + size_t filename_start = furi_string_search_rchar(base_path, '/'); + furi_string_left(base_path, filename_start); } - string_printf(base_path, "%s/%s%s", path, string_get_cstr(name), INFRARED_APP_EXTENSION); + furi_string_printf( + base_path, "%s/%s%s", path, furi_string_get_cstr(name), INFRARED_APP_EXTENSION); - FS_Error status = storage_common_stat(storage, string_get_cstr(base_path), NULL); + FS_Error status = storage_common_stat(storage, furi_string_get_cstr(base_path), NULL); if(status == FSE_OK) { /* If the suggested name is occupied, try another one (name2, name3, etc) */ - size_t dot = string_search_rchar(base_path, '.'); - string_left(base_path, dot); + size_t dot = furi_string_search_rchar(base_path, '.'); + furi_string_left(base_path, dot); - string_t path_temp; - string_init(path_temp); + FuriString* path_temp; + path_temp = furi_string_alloc(); uint32_t i = 1; do { - string_printf( - path_temp, "%s%u%s", string_get_cstr(base_path), ++i, INFRARED_APP_EXTENSION); - status = storage_common_stat(storage, string_get_cstr(path_temp), NULL); + furi_string_printf( + path_temp, "%s%u%s", furi_string_get_cstr(base_path), ++i, INFRARED_APP_EXTENSION); + status = storage_common_stat(storage, furi_string_get_cstr(path_temp), NULL); } while(status == FSE_OK); - string_clear(path_temp); + furi_string_free(path_temp); if(status == FSE_NOT_EXIST) { - string_cat_printf(name, "%u", i); + furi_string_cat_printf(name, "%u", i); } } - string_clear(base_path); + furi_string_free(base_path); furi_record_close(RECORD_STORAGE); } static Infrared* infrared_alloc() { Infrared* infrared = malloc(sizeof(Infrared)); - string_init(infrared->file_path); + infrared->file_path = furi_string_alloc(); InfraredAppState* app_state = &infrared->app_state; app_state->is_learning_new_remote = false; @@ -232,7 +233,7 @@ static void infrared_free(Infrared* infrared) { furi_record_close(RECORD_GUI); infrared->gui = NULL; - string_clear(infrared->file_path); + furi_string_free(infrared->file_path); free(infrared); } @@ -243,19 +244,20 @@ bool infrared_add_remote_with_button( InfraredSignal* signal) { InfraredRemote* remote = infrared->remote; - string_t new_name, new_path; - string_init_set_str(new_name, INFRARED_DEFAULT_REMOTE_NAME); - string_init_set_str(new_path, INFRARED_APP_FOLDER); + FuriString *new_name, *new_path; + new_name = furi_string_alloc_set(INFRARED_DEFAULT_REMOTE_NAME); + new_path = furi_string_alloc_set(INFRARED_APP_FOLDER); - infrared_find_vacant_remote_name(new_name, string_get_cstr(new_path)); - string_cat_printf(new_path, "/%s%s", string_get_cstr(new_name), INFRARED_APP_EXTENSION); + infrared_find_vacant_remote_name(new_name, furi_string_get_cstr(new_path)); + furi_string_cat_printf( + new_path, "/%s%s", furi_string_get_cstr(new_name), INFRARED_APP_EXTENSION); infrared_remote_reset(remote); - infrared_remote_set_name(remote, string_get_cstr(new_name)); - infrared_remote_set_path(remote, string_get_cstr(new_path)); + infrared_remote_set_name(remote, furi_string_get_cstr(new_name)); + infrared_remote_set_path(remote, furi_string_get_cstr(new_path)); - string_clear(new_name); - string_clear(new_path); + furi_string_free(new_name); + furi_string_free(new_path); return infrared_remote_add_button(remote, button_name, signal); } @@ -267,28 +269,29 @@ bool infrared_rename_current_remote(Infrared* infrared, const char* name) { return true; } - string_t new_name; - string_init_set_str(new_name, name); + FuriString* new_name; + new_name = furi_string_alloc_set(name); infrared_find_vacant_remote_name(new_name, remote_path); - string_t new_path; - string_init_set(new_path, infrared_remote_get_path(remote)); - if(string_end_with_str_p(new_path, INFRARED_APP_EXTENSION)) { - size_t filename_start = string_search_rchar(new_path, '/'); - string_left(new_path, filename_start); + FuriString* new_path; + new_path = furi_string_alloc_set(infrared_remote_get_path(remote)); + if(furi_string_end_with(new_path, INFRARED_APP_EXTENSION)) { + size_t filename_start = furi_string_search_rchar(new_path, '/'); + furi_string_left(new_path, filename_start); } - string_cat_printf(new_path, "/%s%s", string_get_cstr(new_name), INFRARED_APP_EXTENSION); + furi_string_cat_printf( + new_path, "/%s%s", furi_string_get_cstr(new_name), INFRARED_APP_EXTENSION); Storage* storage = furi_record_open(RECORD_STORAGE); FS_Error status = storage_common_rename( - storage, infrared_remote_get_path(remote), string_get_cstr(new_path)); - infrared_remote_set_name(remote, string_get_cstr(new_name)); - infrared_remote_set_path(remote, string_get_cstr(new_path)); + storage, infrared_remote_get_path(remote), furi_string_get_cstr(new_path)); + infrared_remote_set_name(remote, furi_string_get_cstr(new_name)); + infrared_remote_set_path(remote, furi_string_get_cstr(new_path)); - string_clear(new_name); - string_clear(new_path); + furi_string_free(new_name); + furi_string_free(new_path); furi_record_close(RECORD_STORAGE); return (status == FSE_OK || status == FSE_EXIST); @@ -435,7 +438,7 @@ int32_t infrared_app(void* p) { rpc_system_app_send_started(infrared->rpc_ctx); is_rpc_mode = true; } else { - string_set_str(infrared->file_path, (const char*)p); + furi_string_set(infrared->file_path, (const char*)p); is_remote_loaded = infrared_remote_load(infrared->remote, infrared->file_path); if(!is_remote_loaded) { dialog_message_show_storage_error( diff --git a/applications/main/infrared/infrared_brute_force.c b/applications/main/infrared/infrared_brute_force.c index 0edc5f7425b..3f426f1dce0 100644 --- a/applications/main/infrared/infrared_brute_force.c +++ b/applications/main/infrared/infrared_brute_force.c @@ -2,7 +2,6 @@ #include #include -#include #include #include "infrared_signal.h" @@ -14,15 +13,15 @@ typedef struct { DICT_DEF2( InfraredBruteForceRecordDict, - string_t, - STRING_OPLIST, + FuriString*, + FURI_STRING_OPLIST, InfraredBruteForceRecord, M_POD_OPLIST); struct InfraredBruteForce { FlipperFormat* ff; const char* db_filename; - string_t current_record_name; + FuriString* current_record_name; InfraredSignal* current_signal; InfraredBruteForceRecordDict_t records; bool is_started; @@ -34,7 +33,7 @@ InfraredBruteForce* infrared_brute_force_alloc() { brute_force->db_filename = NULL; brute_force->current_signal = NULL; brute_force->is_started = false; - string_init(brute_force->current_record_name); + brute_force->current_record_name = furi_string_alloc(); InfraredBruteForceRecordDict_init(brute_force->records); return brute_force; } @@ -42,7 +41,7 @@ InfraredBruteForce* infrared_brute_force_alloc() { void infrared_brute_force_free(InfraredBruteForce* brute_force) { furi_assert(!brute_force->is_started); InfraredBruteForceRecordDict_clear(brute_force->records); - string_clear(brute_force->current_record_name); + furi_string_free(brute_force->current_record_name); free(brute_force); } @@ -61,8 +60,8 @@ bool infrared_brute_force_calculate_messages(InfraredBruteForce* brute_force) { success = flipper_format_buffered_file_open_existing(ff, brute_force->db_filename); if(success) { - string_t signal_name; - string_init(signal_name); + FuriString* signal_name; + signal_name = furi_string_alloc(); while(flipper_format_read_string(ff, "name", signal_name)) { InfraredBruteForceRecord* record = InfraredBruteForceRecordDict_get(brute_force->records, signal_name); @@ -70,7 +69,7 @@ bool infrared_brute_force_calculate_messages(InfraredBruteForce* brute_force) { ++(record->count); } } - string_clear(signal_name); + furi_string_free(signal_name); } flipper_format_free(ff); @@ -94,7 +93,7 @@ bool infrared_brute_force_start( if(record->value.index == index) { *record_count = record->value.count; if(*record_count) { - string_set(brute_force->current_record_name, record->key); + furi_string_set(brute_force->current_record_name, record->key); } break; } @@ -118,7 +117,7 @@ bool infrared_brute_force_is_started(InfraredBruteForce* brute_force) { void infrared_brute_force_stop(InfraredBruteForce* brute_force) { furi_assert(brute_force->is_started); - string_reset(brute_force->current_record_name); + furi_string_reset(brute_force->current_record_name); infrared_signal_free(brute_force->current_signal); flipper_format_free(brute_force->ff); brute_force->current_signal = NULL; @@ -142,10 +141,10 @@ void infrared_brute_force_add_record( uint32_t index, const char* name) { InfraredBruteForceRecord value = {.index = index, .count = 0}; - string_t key; - string_init_set_str(key, name); + FuriString* key; + key = furi_string_alloc_set(name); InfraredBruteForceRecordDict_set_at(brute_force->records, key, value); - string_clear(key); + furi_string_free(key); } void infrared_brute_force_reset(InfraredBruteForce* brute_force) { diff --git a/applications/main/infrared/infrared_cli.c b/applications/main/infrared/infrared_cli.c index 693e191ee6a..5ec57c75745 100644 --- a/applications/main/infrared/infrared_cli.c +++ b/applications/main/infrared/infrared_cli.c @@ -1,4 +1,3 @@ -#include #include #include #include @@ -10,13 +9,13 @@ #define INFRARED_CLI_BUF_SIZE 10 -static void infrared_cli_start_ir_rx(Cli* cli, string_t args); -static void infrared_cli_start_ir_tx(Cli* cli, string_t args); -static void infrared_cli_process_decode(Cli* cli, string_t args); +static void infrared_cli_start_ir_rx(Cli* cli, FuriString* args); +static void infrared_cli_start_ir_tx(Cli* cli, FuriString* args); +static void infrared_cli_process_decode(Cli* cli, FuriString* args); static const struct { const char* cmd; - void (*process_function)(Cli* cli, string_t args); + void (*process_function)(Cli* cli, FuriString* args); } infrared_cli_commands[] = { {.cmd = "rx", .process_function = infrared_cli_start_ir_rx}, {.cmd = "tx", .process_function = infrared_cli_start_ir_tx}, @@ -58,7 +57,7 @@ static void signal_received_callback(void* context, InfraredWorkerSignal* receiv } } -static void infrared_cli_start_ir_rx(Cli* cli, string_t args) { +static void infrared_cli_start_ir_rx(Cli* cli, FuriString* args) { UNUSED(cli); UNUSED(args); InfraredWorker* worker = infrared_worker_alloc(); @@ -151,9 +150,9 @@ static bool infrared_cli_parse_raw(const char* str, InfraredSignal* signal) { return infrared_signal_is_valid(signal); } -static void infrared_cli_start_ir_tx(Cli* cli, string_t args) { +static void infrared_cli_start_ir_tx(Cli* cli, FuriString* args) { UNUSED(cli); - const char* str = string_get_cstr(args); + const char* str = furi_string_get_cstr(args); InfraredSignal* signal = infrared_signal_alloc(); bool success = infrared_cli_parse_message(str, signal) || infrared_cli_parse_raw(str, signal); @@ -231,8 +230,8 @@ static bool infrared_cli_decode_file(FlipperFormat* input_file, FlipperFormat* o InfraredSignal* signal = infrared_signal_alloc(); InfraredDecoderHandler* decoder = infrared_alloc_decoder(); - string_t tmp; - string_init(tmp); + FuriString* tmp; + tmp = furi_string_alloc(); while(infrared_signal_read(signal, input_file, tmp)) { ret = false; @@ -242,7 +241,7 @@ static bool infrared_cli_decode_file(FlipperFormat* input_file, FlipperFormat* o } if(!infrared_signal_is_raw(signal)) { if(output_file && - !infrared_cli_save_signal(signal, output_file, string_get_cstr(tmp))) { + !infrared_cli_save_signal(signal, output_file, furi_string_get_cstr(tmp))) { break; } else { printf("Skipping decoded signal\r\n"); @@ -250,31 +249,33 @@ static bool infrared_cli_decode_file(FlipperFormat* input_file, FlipperFormat* o } } InfraredRawSignal* raw_signal = infrared_signal_get_raw_signal(signal); - printf("Raw signal: %s, %u samples\r\n", string_get_cstr(tmp), raw_signal->timings_size); - if(!infrared_cli_decode_raw_signal(raw_signal, decoder, output_file, string_get_cstr(tmp))) + printf( + "Raw signal: %s, %u samples\r\n", furi_string_get_cstr(tmp), raw_signal->timings_size); + if(!infrared_cli_decode_raw_signal( + raw_signal, decoder, output_file, furi_string_get_cstr(tmp))) break; ret = true; } infrared_free_decoder(decoder); infrared_signal_free(signal); - string_clear(tmp); + furi_string_free(tmp); return ret; } -static void infrared_cli_process_decode(Cli* cli, string_t args) { +static void infrared_cli_process_decode(Cli* cli, FuriString* args) { UNUSED(cli); Storage* storage = furi_record_open(RECORD_STORAGE); FlipperFormat* input_file = flipper_format_buffered_file_alloc(storage); FlipperFormat* output_file = NULL; uint32_t version; - string_t tmp, header, input_path, output_path; - string_init(tmp); - string_init(header); - string_init(input_path); - string_init(output_path); + FuriString *tmp, *header, *input_path, *output_path; + tmp = furi_string_alloc(); + header = furi_string_alloc(); + input_path = furi_string_alloc(); + output_path = furi_string_alloc(); do { if(!args_read_probably_quoted_string_and_trim(args, input_path)) { @@ -283,26 +284,32 @@ static void infrared_cli_process_decode(Cli* cli, string_t args) { break; } args_read_probably_quoted_string_and_trim(args, output_path); - if(!flipper_format_buffered_file_open_existing(input_file, string_get_cstr(input_path))) { - printf("Failed to open file for reading: \"%s\"\r\n", string_get_cstr(input_path)); + if(!flipper_format_buffered_file_open_existing( + input_file, furi_string_get_cstr(input_path))) { + printf( + "Failed to open file for reading: \"%s\"\r\n", furi_string_get_cstr(input_path)); break; } if(!flipper_format_read_header(input_file, header, &version) || - (!string_start_with_str_p(header, "IR")) || version != 1) { - printf("Invalid or corrupted input file: \"%s\"\r\n", string_get_cstr(input_path)); + (!furi_string_start_with_str(header, "IR")) || version != 1) { + printf( + "Invalid or corrupted input file: \"%s\"\r\n", furi_string_get_cstr(input_path)); break; } - if(!string_empty_p(output_path)) { - printf("Writing output to file: \"%s\"\r\n", string_get_cstr(output_path)); + if(!furi_string_empty(output_path)) { + printf("Writing output to file: \"%s\"\r\n", furi_string_get_cstr(output_path)); output_file = flipper_format_file_alloc(storage); } if(output_file && - !flipper_format_file_open_always(output_file, string_get_cstr(output_path))) { - printf("Failed to open file for writing: \"%s\"\r\n", string_get_cstr(output_path)); + !flipper_format_file_open_always(output_file, furi_string_get_cstr(output_path))) { + printf( + "Failed to open file for writing: \"%s\"\r\n", furi_string_get_cstr(output_path)); break; } if(output_file && !flipper_format_write_header(output_file, header, version)) { - printf("Failed to write to the output file: \"%s\"\r\n", string_get_cstr(output_path)); + printf( + "Failed to write to the output file: \"%s\"\r\n", + furi_string_get_cstr(output_path)); break; } if(!infrared_cli_decode_file(input_file, output_file)) { @@ -311,31 +318,31 @@ static void infrared_cli_process_decode(Cli* cli, string_t args) { printf("File successfully decoded.\r\n"); } while(false); - string_clear(tmp); - string_clear(header); - string_clear(input_path); - string_clear(output_path); + furi_string_free(tmp); + furi_string_free(header); + furi_string_free(input_path); + furi_string_free(output_path); flipper_format_free(input_file); if(output_file) flipper_format_free(output_file); furi_record_close(RECORD_STORAGE); } -static void infrared_cli_start_ir(Cli* cli, string_t args, void* context) { +static void infrared_cli_start_ir(Cli* cli, FuriString* args, void* context) { UNUSED(context); if(furi_hal_infrared_is_busy()) { printf("INFRARED is busy. Exiting."); return; } - string_t command; - string_init(command); + FuriString* command; + command = furi_string_alloc(); args_read_string_and_trim(args, command); size_t i = 0; for(; i < COUNT_OF(infrared_cli_commands); ++i) { size_t cmd_len = strlen(infrared_cli_commands[i].cmd); - if(!strncmp(string_get_cstr(command), infrared_cli_commands[i].cmd, cmd_len)) { + if(!strncmp(furi_string_get_cstr(command), infrared_cli_commands[i].cmd, cmd_len)) { break; } } @@ -346,7 +353,7 @@ static void infrared_cli_start_ir(Cli* cli, string_t args, void* context) { infrared_cli_print_usage(); } - string_clear(command); + furi_string_free(command); } void infrared_on_system_start() { #ifdef SRV_CLI diff --git a/applications/main/infrared/infrared_i.h b/applications/main/infrared/infrared_i.h index 9521525875f..6d25d160947 100644 --- a/applications/main/infrared/infrared_i.h +++ b/applications/main/infrared/infrared_i.h @@ -96,7 +96,7 @@ struct Infrared { Loading* loading; InfraredProgressView* progress; - string_t file_path; + FuriString* file_path; char text_store[INFRARED_TEXT_STORE_NUM][INFRARED_TEXT_STORE_SIZE + 1]; InfraredAppState app_state; diff --git a/applications/main/infrared/infrared_remote.c b/applications/main/infrared/infrared_remote.c index 4417c3c733f..3a528a65606 100644 --- a/applications/main/infrared/infrared_remote.c +++ b/applications/main/infrared/infrared_remote.c @@ -3,7 +3,6 @@ #include #include #include -#include #include #include #include @@ -15,8 +14,8 @@ ARRAY_DEF(InfraredButtonArray, InfraredRemoteButton*, M_PTR_OPLIST); struct InfraredRemote { InfraredButtonArray_t buttons; - string_t name; - string_t path; + FuriString* name; + FuriString* path; }; static void infrared_remote_clear_buttons(InfraredRemote* remote) { @@ -31,39 +30,39 @@ static void infrared_remote_clear_buttons(InfraredRemote* remote) { InfraredRemote* infrared_remote_alloc() { InfraredRemote* remote = malloc(sizeof(InfraredRemote)); InfraredButtonArray_init(remote->buttons); - string_init(remote->name); - string_init(remote->path); + remote->name = furi_string_alloc(); + remote->path = furi_string_alloc(); return remote; } void infrared_remote_free(InfraredRemote* remote) { infrared_remote_clear_buttons(remote); InfraredButtonArray_clear(remote->buttons); - string_clear(remote->path); - string_clear(remote->name); + furi_string_free(remote->path); + furi_string_free(remote->name); free(remote); } void infrared_remote_reset(InfraredRemote* remote) { infrared_remote_clear_buttons(remote); - string_reset(remote->name); - string_reset(remote->path); + furi_string_reset(remote->name); + furi_string_reset(remote->path); } void infrared_remote_set_name(InfraredRemote* remote, const char* name) { - string_set_str(remote->name, name); + furi_string_set(remote->name, name); } const char* infrared_remote_get_name(InfraredRemote* remote) { - return string_get_cstr(remote->name); + return furi_string_get_cstr(remote->name); } void infrared_remote_set_path(InfraredRemote* remote, const char* path) { - string_set_str(remote->path, path); + furi_string_set(remote->path, path); } const char* infrared_remote_get_path(InfraredRemote* remote) { - return string_get_cstr(remote->path); + return furi_string_get_cstr(remote->path); } size_t infrared_remote_get_button_count(InfraredRemote* remote) { @@ -112,7 +111,7 @@ bool infrared_remote_delete_button(InfraredRemote* remote, size_t index) { bool infrared_remote_store(InfraredRemote* remote) { Storage* storage = furi_record_open(RECORD_STORAGE); FlipperFormat* ff = flipper_format_file_alloc(storage); - const char* path = string_get_cstr(remote->path); + const char* path = furi_string_get_cstr(remote->path); FURI_LOG_I(TAG, "store file: \'%s\'", path); @@ -138,33 +137,33 @@ bool infrared_remote_store(InfraredRemote* remote) { return success; } -bool infrared_remote_load(InfraredRemote* remote, string_t path) { +bool infrared_remote_load(InfraredRemote* remote, FuriString* path) { Storage* storage = furi_record_open(RECORD_STORAGE); FlipperFormat* ff = flipper_format_buffered_file_alloc(storage); - string_t buf; - string_init(buf); + FuriString* buf; + buf = furi_string_alloc(); - FURI_LOG_I(TAG, "load file: \'%s\'", string_get_cstr(path)); - bool success = flipper_format_buffered_file_open_existing(ff, string_get_cstr(path)); + FURI_LOG_I(TAG, "load file: \'%s\'", furi_string_get_cstr(path)); + bool success = flipper_format_buffered_file_open_existing(ff, furi_string_get_cstr(path)); if(success) { uint32_t version; success = flipper_format_read_header(ff, buf, &version) && - !string_cmp_str(buf, "IR signals file") && (version == 1); + !furi_string_cmp(buf, "IR signals file") && (version == 1); } if(success) { path_extract_filename(path, buf, true); infrared_remote_clear_buttons(remote); - infrared_remote_set_name(remote, string_get_cstr(buf)); - infrared_remote_set_path(remote, string_get_cstr(path)); + infrared_remote_set_name(remote, furi_string_get_cstr(buf)); + infrared_remote_set_path(remote, furi_string_get_cstr(path)); for(bool can_read = true; can_read;) { InfraredRemoteButton* button = infrared_remote_button_alloc(); can_read = infrared_signal_read(infrared_remote_button_get_signal(button), ff, buf); if(can_read) { - infrared_remote_button_set_name(button, string_get_cstr(buf)); + infrared_remote_button_set_name(button, furi_string_get_cstr(buf)); InfraredButtonArray_push_back(remote->buttons, button); } else { infrared_remote_button_free(button); @@ -172,7 +171,7 @@ bool infrared_remote_load(InfraredRemote* remote, string_t path) { } } - string_clear(buf); + furi_string_free(buf); flipper_format_free(ff); furi_record_close(RECORD_STORAGE); return success; @@ -181,7 +180,7 @@ bool infrared_remote_load(InfraredRemote* remote, string_t path) { bool infrared_remote_remove(InfraredRemote* remote) { Storage* storage = furi_record_open(RECORD_STORAGE); - FS_Error status = storage_common_remove(storage, string_get_cstr(remote->path)); + FS_Error status = storage_common_remove(storage, furi_string_get_cstr(remote->path)); infrared_remote_reset(remote); furi_record_close(RECORD_STORAGE); diff --git a/applications/main/infrared/infrared_remote.h b/applications/main/infrared/infrared_remote.h index b6f63a198cb..6eac193d3a0 100644 --- a/applications/main/infrared/infrared_remote.h +++ b/applications/main/infrared/infrared_remote.h @@ -25,5 +25,5 @@ bool infrared_remote_rename_button(InfraredRemote* remote, const char* new_name, bool infrared_remote_delete_button(InfraredRemote* remote, size_t index); bool infrared_remote_store(InfraredRemote* remote); -bool infrared_remote_load(InfraredRemote* remote, string_t path); +bool infrared_remote_load(InfraredRemote* remote, FuriString* path); bool infrared_remote_remove(InfraredRemote* remote); diff --git a/applications/main/infrared/infrared_remote_button.c b/applications/main/infrared/infrared_remote_button.c index 7525ce48f7e..1f6315ec528 100644 --- a/applications/main/infrared/infrared_remote_button.c +++ b/applications/main/infrared/infrared_remote_button.c @@ -1,32 +1,31 @@ #include "infrared_remote_button.h" #include -#include struct InfraredRemoteButton { - string_t name; + FuriString* name; InfraredSignal* signal; }; InfraredRemoteButton* infrared_remote_button_alloc() { InfraredRemoteButton* button = malloc(sizeof(InfraredRemoteButton)); - string_init(button->name); + button->name = furi_string_alloc(); button->signal = infrared_signal_alloc(); return button; } void infrared_remote_button_free(InfraredRemoteButton* button) { - string_clear(button->name); + furi_string_free(button->name); infrared_signal_free(button->signal); free(button); } void infrared_remote_button_set_name(InfraredRemoteButton* button, const char* name) { - string_set_str(button->name, name); + furi_string_set(button->name, name); } const char* infrared_remote_button_get_name(InfraredRemoteButton* button) { - return string_get_cstr(button->name); + return furi_string_get_cstr(button->name); } void infrared_remote_button_set_signal(InfraredRemoteButton* button, InfraredSignal* signal) { diff --git a/applications/main/infrared/infrared_signal.c b/applications/main/infrared/infrared_signal.c index f2e359c8a09..30459d60f4f 100644 --- a/applications/main/infrared/infrared_signal.c +++ b/applications/main/infrared/infrared_signal.c @@ -100,15 +100,15 @@ static inline bool infrared_signal_save_raw(InfraredRawSignal* raw, FlipperForma } static inline bool infrared_signal_read_message(InfraredSignal* signal, FlipperFormat* ff) { - string_t buf; - string_init(buf); + FuriString* buf; + buf = furi_string_alloc(); bool success = false; do { if(!flipper_format_read_string(ff, "protocol", buf)) break; InfraredMessage message; - message.protocol = infrared_get_protocol_by_name(string_get_cstr(buf)); + message.protocol = infrared_get_protocol_by_name(furi_string_get_cstr(buf)); success = flipper_format_read_hex(ff, "address", (uint8_t*)&message.address, 4) && flipper_format_read_hex(ff, "command", (uint8_t*)&message.command, 4) && @@ -119,7 +119,7 @@ static inline bool infrared_signal_read_message(InfraredSignal* signal, FlipperF infrared_signal_set_message(signal, &message); } while(0); - string_clear(buf); + furi_string_free(buf); return success; } @@ -147,22 +147,22 @@ static inline bool infrared_signal_read_raw(InfraredSignal* signal, FlipperForma } static bool infrared_signal_read_body(InfraredSignal* signal, FlipperFormat* ff) { - string_t tmp; - string_init(tmp); + FuriString* tmp = furi_string_alloc(); + bool success = false; do { if(!flipper_format_read_string(ff, "type", tmp)) break; - if(string_equal_p(tmp, "raw")) { + if(furi_string_equal(tmp, "raw")) { success = infrared_signal_read_raw(signal, ff); - } else if(string_equal_p(tmp, "parsed")) { + } else if(furi_string_equal(tmp, "parsed")) { success = infrared_signal_read_message(signal, ff); } else { FURI_LOG_E(TAG, "Unknown signal type"); } } while(false); - string_clear(tmp); + furi_string_free(tmp); return success; } @@ -246,34 +246,33 @@ bool infrared_signal_save(InfraredSignal* signal, FlipperFormat* ff, const char* } } -bool infrared_signal_read(InfraredSignal* signal, FlipperFormat* ff, string_t name) { - string_t tmp; - string_init(tmp); +bool infrared_signal_read(InfraredSignal* signal, FlipperFormat* ff, FuriString* name) { + FuriString* tmp = furi_string_alloc(); + bool success = false; do { if(!flipper_format_read_string(ff, "name", tmp)) break; - string_set(name, tmp); + furi_string_set(name, tmp); if(!infrared_signal_read_body(signal, ff)) break; success = true; } while(0); - string_clear(tmp); + furi_string_free(tmp); return success; } bool infrared_signal_search_and_read( InfraredSignal* signal, FlipperFormat* ff, - const string_t name) { + const FuriString* name) { bool success = false; - string_t tmp; - string_init(tmp); + FuriString* tmp = furi_string_alloc(); do { bool is_name_found = false; while(flipper_format_read_string(ff, "name", tmp)) { - is_name_found = string_equal_p(name, tmp); + is_name_found = furi_string_equal(name, tmp); if(is_name_found) break; } if(!is_name_found) break; @@ -281,7 +280,7 @@ bool infrared_signal_search_and_read( success = true; } while(false); - string_clear(tmp); + furi_string_free(tmp); return success; } diff --git a/applications/main/infrared/infrared_signal.h b/applications/main/infrared/infrared_signal.h index ad2f5d57a4c..29c66193834 100644 --- a/applications/main/infrared/infrared_signal.h +++ b/applications/main/infrared/infrared_signal.h @@ -36,10 +36,10 @@ void infrared_signal_set_message(InfraredSignal* signal, const InfraredMessage* InfraredMessage* infrared_signal_get_message(InfraredSignal* signal); bool infrared_signal_save(InfraredSignal* signal, FlipperFormat* ff, const char* name); -bool infrared_signal_read(InfraredSignal* signal, FlipperFormat* ff, string_t name); +bool infrared_signal_read(InfraredSignal* signal, FlipperFormat* ff, FuriString* name); bool infrared_signal_search_and_read( InfraredSignal* signal, FlipperFormat* ff, - const string_t name); + const FuriString* name); void infrared_signal_transmit(InfraredSignal* signal); diff --git a/applications/main/infrared/scenes/infrared_scene_edit_rename.c b/applications/main/infrared/scenes/infrared_scene_edit_rename.c index ebe7c2a96d4..a2199215d74 100644 --- a/applications/main/infrared/scenes/infrared_scene_edit_rename.c +++ b/applications/main/infrared/scenes/infrared_scene_edit_rename.c @@ -29,20 +29,20 @@ void infrared_scene_edit_rename_on_enter(void* context) { enter_name_length = INFRARED_MAX_REMOTE_NAME_LENGTH; strncpy(infrared->text_store[0], infrared_remote_get_name(remote), enter_name_length); - string_t folder_path; - string_init(folder_path); + FuriString* folder_path; + folder_path = furi_string_alloc(); - if(string_end_with_str_p(infrared->file_path, INFRARED_APP_EXTENSION)) { - path_extract_dirname(string_get_cstr(infrared->file_path), folder_path); + if(furi_string_end_with(infrared->file_path, INFRARED_APP_EXTENSION)) { + path_extract_dirname(furi_string_get_cstr(infrared->file_path), folder_path); } ValidatorIsFile* validator_is_file = validator_is_file_alloc_init( - string_get_cstr(folder_path), + furi_string_get_cstr(folder_path), INFRARED_APP_EXTENSION, infrared_remote_get_name(remote)); text_input_set_validator(text_input, validator_is_file_callback, validator_is_file); - string_clear(folder_path); + furi_string_free(folder_path); } else { furi_assert(0); } diff --git a/applications/main/infrared/scenes/infrared_scene_rpc.c b/applications/main/infrared/scenes/infrared_scene_rpc.c index ca7bbd8df45..8044e8318fc 100644 --- a/applications/main/infrared/scenes/infrared_scene_rpc.c +++ b/applications/main/infrared/scenes/infrared_scene_rpc.c @@ -42,7 +42,7 @@ bool infrared_scene_rpc_on_event(void* context, SceneManagerEvent event) { bool result = false; const char* arg = rpc_system_app_get_data(infrared->rpc_ctx); if(arg && (state == InfraredRpcStateIdle)) { - string_set_str(infrared->file_path, arg); + furi_string_set(infrared->file_path, arg); result = infrared_remote_load(infrared->remote, infrared->file_path); if(result) { scene_manager_set_scene_state( diff --git a/applications/main/infrared/scenes/infrared_scene_start.c b/applications/main/infrared/scenes/infrared_scene_start.c index d188a6c36dc..c7df0f45ba6 100644 --- a/applications/main/infrared/scenes/infrared_scene_start.c +++ b/applications/main/infrared/scenes/infrared_scene_start.c @@ -66,7 +66,7 @@ bool infrared_scene_start_on_event(void* context, SceneManagerEvent event) { scene_manager_next_scene(scene_manager, InfraredSceneLearn); consumed = true; } else if(submenu_index == SubmenuIndexSavedRemotes) { - string_set_str(infrared->file_path, INFRARED_APP_FOLDER); + furi_string_set(infrared->file_path, INFRARED_APP_FOLDER); scene_manager_next_scene(scene_manager, InfraredSceneRemoteList); consumed = true; } else if(submenu_index == SubmenuIndexDebug) { diff --git a/applications/main/infrared/views/infrared_progress_view.c b/applications/main/infrared/views/infrared_progress_view.c index 5c84ce0d95d..3c50f89e42c 100644 --- a/applications/main/infrared/views/infrared_progress_view.c +++ b/applications/main/infrared/views/infrared_progress_view.c @@ -4,7 +4,6 @@ #include "gui/canvas.h" #include "gui/view.h" #include "input/input.h" -#include "m-string.h" #include #include #include "infrared_progress_view.h" diff --git a/applications/main/lfrfid/lfrfid.c b/applications/main/lfrfid/lfrfid.c index dbed9f3af7b..b0f98937497 100644 --- a/applications/main/lfrfid/lfrfid.c +++ b/applications/main/lfrfid/lfrfid.c @@ -36,9 +36,9 @@ static LfRfid* lfrfid_alloc() { lfrfid->storage = furi_record_open(RECORD_STORAGE); lfrfid->dialogs = furi_record_open(RECORD_DIALOGS); - string_init(lfrfid->file_name); - string_init(lfrfid->raw_file_name); - string_init_set_str(lfrfid->file_path, LFRFID_APP_FOLDER); + lfrfid->file_name = furi_string_alloc(); + lfrfid->raw_file_name = furi_string_alloc(); + lfrfid->file_path = furi_string_alloc_set(LFRFID_APP_FOLDER); lfrfid->dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax); @@ -104,9 +104,9 @@ static LfRfid* lfrfid_alloc() { static void lfrfid_free(LfRfid* lfrfid) { furi_assert(lfrfid); - string_clear(lfrfid->raw_file_name); - string_clear(lfrfid->file_name); - string_clear(lfrfid->file_path); + furi_string_free(lfrfid->raw_file_name); + furi_string_free(lfrfid->file_name); + furi_string_free(lfrfid->file_path); protocol_dict_free(lfrfid->dict); lfrfid_worker_free(lfrfid->lfworker); @@ -183,7 +183,7 @@ int32_t lfrfid_app(void* p) { app->view_dispatcher, app->gui, ViewDispatcherTypeDesktop); scene_manager_next_scene(app->scene_manager, LfRfidSceneRpc); } else { - string_set_str(app->file_path, args); + furi_string_set(app->file_path, args); lfrfid_load_key_data(app, app->file_path, true); view_dispatcher_attach_to_gui( app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); @@ -210,13 +210,13 @@ bool lfrfid_save_key(LfRfid* app) { lfrfid_make_app_folder(app); - if(string_end_with_str_p(app->file_path, LFRFID_APP_EXTENSION)) { - size_t filename_start = string_search_rchar(app->file_path, '/'); - string_left(app->file_path, filename_start); + if(furi_string_end_with(app->file_path, LFRFID_APP_EXTENSION)) { + size_t filename_start = furi_string_search_rchar(app->file_path, '/'); + furi_string_left(app->file_path, filename_start); } - string_cat_printf( - app->file_path, "/%s%s", string_get_cstr(app->file_name), LFRFID_APP_EXTENSION); + furi_string_cat_printf( + app->file_path, "/%s%s", furi_string_get_cstr(app->file_name), LFRFID_APP_EXTENSION); result = lfrfid_save_key_data(app, app->file_path); return result; @@ -242,14 +242,14 @@ bool lfrfid_load_key_from_file_select(LfRfid* app) { bool lfrfid_delete_key(LfRfid* app) { furi_assert(app); - return storage_simply_remove(app->storage, string_get_cstr(app->file_path)); + return storage_simply_remove(app->storage, furi_string_get_cstr(app->file_path)); } -bool lfrfid_load_key_data(LfRfid* app, string_t path, bool show_dialog) { +bool lfrfid_load_key_data(LfRfid* app, FuriString* path, bool show_dialog) { bool result = false; do { - app->protocol_id = lfrfid_dict_file_load(app->dict, string_get_cstr(path)); + app->protocol_id = lfrfid_dict_file_load(app->dict, furi_string_get_cstr(path)); if(app->protocol_id == PROTOCOL_NO) break; path_extract_filename(path, app->file_name, true); @@ -263,8 +263,8 @@ bool lfrfid_load_key_data(LfRfid* app, string_t path, bool show_dialog) { return result; } -bool lfrfid_save_key_data(LfRfid* app, string_t path) { - bool result = lfrfid_dict_file_save(app->dict, app->protocol_id, string_get_cstr(path)); +bool lfrfid_save_key_data(LfRfid* app, FuriString* path) { + bool result = lfrfid_dict_file_save(app->dict, app->protocol_id, furi_string_get_cstr(path)); if(!result) { dialog_message_show_storage_error(app->dialogs, "Cannot save\nkey file"); diff --git a/applications/main/lfrfid/lfrfid_cli.c b/applications/main/lfrfid/lfrfid_cli.c index 281a0e9638d..6402745297a 100644 --- a/applications/main/lfrfid/lfrfid_cli.c +++ b/applications/main/lfrfid/lfrfid_cli.c @@ -14,7 +14,7 @@ #include #include -static void lfrfid_cli(Cli* cli, string_t args, void* context); +static void lfrfid_cli(Cli* cli, FuriString* args, void* context); // app cli function void lfrfid_on_system_start() { @@ -46,27 +46,28 @@ static void lfrfid_cli_read_callback(LFRFIDWorkerReadResult result, ProtocolId p furi_event_flag_set(context->event, 1 << result); } -static void lfrfid_cli_read(Cli* cli, string_t args) { - string_t type_string; - string_init(type_string); +static void lfrfid_cli_read(Cli* cli, FuriString* args) { + FuriString* type_string; + type_string = furi_string_alloc(); LFRFIDWorkerReadType type = LFRFIDWorkerReadTypeAuto; if(args_read_string_and_trim(args, type_string)) { - if(string_cmp_str(type_string, "normal") == 0 || string_cmp_str(type_string, "ask") == 0) { + if(furi_string_cmp_str(type_string, "normal") == 0 || + furi_string_cmp_str(type_string, "ask") == 0) { // ask type = LFRFIDWorkerReadTypeASKOnly; } else if( - string_cmp_str(type_string, "indala") == 0 || - string_cmp_str(type_string, "psk") == 0) { + furi_string_cmp_str(type_string, "indala") == 0 || + furi_string_cmp_str(type_string, "psk") == 0) { // psk type = LFRFIDWorkerReadTypePSKOnly; } else { lfrfid_cli_print_usage(); - string_clear(type_string); + furi_string_free(type_string); return; } } - string_clear(type_string); + furi_string_free(type_string); ProtocolDict* dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax); LFRFIDWorker* worker = lfrfid_worker_alloc(dict); @@ -111,13 +112,13 @@ static void lfrfid_cli_read(Cli* cli, string_t args) { printf("\r\n"); free(data); - string_t info; - string_init(info); + FuriString* info; + info = furi_string_alloc(); protocol_dict_render_data(dict, info, context.protocol); - if(!string_empty_p(info)) { - printf("%s\r\n", string_get_cstr(info)); + if(!furi_string_empty(info)) { + printf("%s\r\n", furi_string_get_cstr(info)); } - string_clear(info); + furi_string_free(info); } printf("Reading stopped\r\n"); @@ -126,11 +127,11 @@ static void lfrfid_cli_read(Cli* cli, string_t args) { furi_event_flag_free(context.event); } -static bool lfrfid_cli_parse_args(string_t args, ProtocolDict* dict, ProtocolId* protocol) { +static bool lfrfid_cli_parse_args(FuriString* args, ProtocolDict* dict, ProtocolId* protocol) { bool result = false; - string_t protocol_name, data_text; - string_init(protocol_name); - string_init(data_text); + FuriString *protocol_name, *data_text; + protocol_name = furi_string_alloc(); + data_text = furi_string_alloc(); size_t data_size = protocol_dict_get_max_data_size(dict); uint8_t* data = malloc(data_size); @@ -143,12 +144,12 @@ static bool lfrfid_cli_parse_args(string_t args, ProtocolDict* dict, ProtocolId* } // check protocol arg - *protocol = protocol_dict_get_protocol_by_name(dict, string_get_cstr(protocol_name)); + *protocol = protocol_dict_get_protocol_by_name(dict, furi_string_get_cstr(protocol_name)); if(*protocol == PROTOCOL_NO) { printf( "Unknown protocol: %s\r\n" "Available protocols:\r\n", - string_get_cstr(protocol_name)); + furi_string_get_cstr(protocol_name)); for(ProtocolId i = 0; i < LFRFIDProtocolMax; i++) { printf( @@ -177,8 +178,8 @@ static bool lfrfid_cli_parse_args(string_t args, ProtocolDict* dict, ProtocolId* } while(false); free(data); - string_clear(protocol_name); - string_clear(data_text); + furi_string_free(protocol_name); + furi_string_free(data_text); return result; } @@ -188,7 +189,7 @@ static void lfrfid_cli_write_callback(LFRFIDWorkerWriteResult result, void* ctx) furi_event_flag_set(events, 1 << result); } -static void lfrfid_cli_write(Cli* cli, string_t args) { +static void lfrfid_cli_write(Cli* cli, FuriString* args) { ProtocolDict* dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax); ProtocolId protocol; @@ -235,7 +236,7 @@ static void lfrfid_cli_write(Cli* cli, string_t args) { furi_event_flag_free(event); } -static void lfrfid_cli_emulate(Cli* cli, string_t args) { +static void lfrfid_cli_emulate(Cli* cli, FuriString* args) { ProtocolDict* dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax); ProtocolId protocol; @@ -261,11 +262,11 @@ static void lfrfid_cli_emulate(Cli* cli, string_t args) { protocol_dict_free(dict); } -static void lfrfid_cli_raw_analyze(Cli* cli, string_t args) { +static void lfrfid_cli_raw_analyze(Cli* cli, FuriString* args) { UNUSED(cli); - string_t filepath, info_string; - string_init(filepath); - string_init(info_string); + FuriString *filepath, *info_string; + filepath = furi_string_alloc(); + info_string = furi_string_alloc(); Storage* storage = furi_record_open(RECORD_STORAGE); LFRFIDRawFile* file = lfrfid_raw_file_alloc(storage); @@ -278,7 +279,7 @@ static void lfrfid_cli_raw_analyze(Cli* cli, string_t args) { break; } - if(!lfrfid_raw_file_open_read(file, string_get_cstr(filepath))) { + if(!lfrfid_raw_file_open_read(file, furi_string_get_cstr(filepath))) { printf("Failed to open file\r\n"); break; } @@ -308,10 +309,10 @@ static void lfrfid_cli_raw_analyze(Cli* cli, string_t args) { warn = true; } - string_printf(info_string, "[%ld %ld]", pulse, duration); - printf("%-16s", string_get_cstr(info_string)); - string_printf(info_string, "[%ld %ld]", pulse, duration - pulse); - printf("%-16s", string_get_cstr(info_string)); + furi_string_printf(info_string, "[%ld %ld]", pulse, duration); + printf("%-16s", furi_string_get_cstr(info_string)); + furi_string_printf(info_string, "[%ld %ld]", pulse, duration - pulse); + printf("%-16s", furi_string_get_cstr(info_string)); if(warn) { printf(" <<----"); @@ -366,7 +367,7 @@ static void lfrfid_cli_raw_analyze(Cli* cli, string_t args) { printf("]\r\n"); protocol_dict_render_data(dict, info_string, total_protocol); - printf("%s\r\n", string_get_cstr(info_string)); + printf("%s\r\n", furi_string_get_cstr(info_string)); free(data); } else { @@ -376,8 +377,8 @@ static void lfrfid_cli_raw_analyze(Cli* cli, string_t args) { protocol_dict_free(dict); } while(false); - string_clear(filepath); - string_clear(info_string); + furi_string_free(filepath); + furi_string_free(info_string); lfrfid_raw_file_free(file); furi_record_close(RECORD_STORAGE); } @@ -388,23 +389,23 @@ static void lfrfid_cli_raw_read_callback(LFRFIDWorkerReadRawResult result, void* furi_event_flag_set(event, 1 << result); } -static void lfrfid_cli_raw_read(Cli* cli, string_t args) { +static void lfrfid_cli_raw_read(Cli* cli, FuriString* args) { UNUSED(cli); - string_t filepath, type_string; - string_init(filepath); - string_init(type_string); + FuriString *filepath, *type_string; + filepath = furi_string_alloc(); + type_string = furi_string_alloc(); LFRFIDWorkerReadType type = LFRFIDWorkerReadTypeAuto; do { if(args_read_string_and_trim(args, type_string)) { - if(string_cmp_str(type_string, "normal") == 0 || - string_cmp_str(type_string, "ask") == 0) { + if(furi_string_cmp_str(type_string, "normal") == 0 || + furi_string_cmp_str(type_string, "ask") == 0) { // ask type = LFRFIDWorkerReadTypeASKOnly; } else if( - string_cmp_str(type_string, "indala") == 0 || - string_cmp_str(type_string, "psk") == 0) { + furi_string_cmp_str(type_string, "indala") == 0 || + furi_string_cmp_str(type_string, "psk") == 0) { // psk type = LFRFIDWorkerReadTypePSKOnly; } else { @@ -430,7 +431,7 @@ static void lfrfid_cli_raw_read(Cli* cli, string_t args) { (1 << LFRFIDWorkerReadRawOverrun); lfrfid_worker_read_raw_start( - worker, string_get_cstr(filepath), type, lfrfid_cli_raw_read_callback, event); + worker, furi_string_get_cstr(filepath), type, lfrfid_cli_raw_read_callback, event); while(true) { uint32_t flags = furi_event_flag_wait(event, available_flags, FuriFlagWaitAny, 100); @@ -465,8 +466,8 @@ static void lfrfid_cli_raw_read(Cli* cli, string_t args) { } while(false); - string_clear(filepath); - string_clear(type_string); + furi_string_free(filepath); + furi_string_free(type_string); } static void lfrfid_cli_raw_emulate_callback(LFRFIDWorkerEmulateRawResult result, void* context) { @@ -475,11 +476,11 @@ static void lfrfid_cli_raw_emulate_callback(LFRFIDWorkerEmulateRawResult result, furi_event_flag_set(event, 1 << result); } -static void lfrfid_cli_raw_emulate(Cli* cli, string_t args) { +static void lfrfid_cli_raw_emulate(Cli* cli, FuriString* args) { UNUSED(cli); - string_t filepath; - string_init(filepath); + FuriString* filepath; + filepath = furi_string_alloc(); Storage* storage = furi_record_open(RECORD_STORAGE); do { @@ -488,8 +489,8 @@ static void lfrfid_cli_raw_emulate(Cli* cli, string_t args) { break; } - if(!storage_file_exists(storage, string_get_cstr(filepath))) { - printf("File not found: \"%s\"\r\n", string_get_cstr(filepath)); + if(!storage_file_exists(storage, furi_string_get_cstr(filepath))) { + printf("File not found: \"%s\"\r\n", furi_string_get_cstr(filepath)); break; } @@ -505,7 +506,7 @@ static void lfrfid_cli_raw_emulate(Cli* cli, string_t args) { (1 << LFRFIDWorkerEmulateRawOverrun); lfrfid_worker_emulate_raw_start( - worker, string_get_cstr(filepath), lfrfid_cli_raw_emulate_callback, event); + worker, furi_string_get_cstr(filepath), lfrfid_cli_raw_emulate_callback, event); while(true) { uint32_t flags = furi_event_flag_wait(event, available_flags, FuriFlagWaitAny, 100); @@ -541,35 +542,35 @@ static void lfrfid_cli_raw_emulate(Cli* cli, string_t args) { } while(false); furi_record_close(RECORD_STORAGE); - string_clear(filepath); + furi_string_free(filepath); } -static void lfrfid_cli(Cli* cli, string_t args, void* context) { +static void lfrfid_cli(Cli* cli, FuriString* args, void* context) { UNUSED(context); - string_t cmd; - string_init(cmd); + FuriString* cmd; + cmd = furi_string_alloc(); if(!args_read_string_and_trim(args, cmd)) { - string_clear(cmd); + furi_string_free(cmd); lfrfid_cli_print_usage(); return; } - if(string_cmp_str(cmd, "read") == 0) { + if(furi_string_cmp_str(cmd, "read") == 0) { lfrfid_cli_read(cli, args); - } else if(string_cmp_str(cmd, "write") == 0) { + } else if(furi_string_cmp_str(cmd, "write") == 0) { lfrfid_cli_write(cli, args); - } else if(string_cmp_str(cmd, "emulate") == 0) { + } else if(furi_string_cmp_str(cmd, "emulate") == 0) { lfrfid_cli_emulate(cli, args); - } else if(string_cmp_str(cmd, "raw_read") == 0) { + } else if(furi_string_cmp_str(cmd, "raw_read") == 0) { lfrfid_cli_raw_read(cli, args); - } else if(string_cmp_str(cmd, "raw_emulate") == 0) { + } else if(furi_string_cmp_str(cmd, "raw_emulate") == 0) { lfrfid_cli_raw_emulate(cli, args); - } else if(string_cmp_str(cmd, "raw_analyze") == 0) { + } else if(furi_string_cmp_str(cmd, "raw_analyze") == 0) { lfrfid_cli_raw_analyze(cli, args); } else { lfrfid_cli_print_usage(); } - string_clear(cmd); + furi_string_free(cmd); } \ No newline at end of file diff --git a/applications/main/lfrfid/lfrfid_i.h b/applications/main/lfrfid/lfrfid_i.h index 77e872527e1..71917c0c2cc 100644 --- a/applications/main/lfrfid/lfrfid_i.h +++ b/applications/main/lfrfid/lfrfid_i.h @@ -1,7 +1,5 @@ #pragma once -#include "m-string.h" - #include #include @@ -86,9 +84,9 @@ struct LfRfid { Widget* widget; char text_store[LFRFID_TEXT_STORE_SIZE + 1]; - string_t file_path; - string_t file_name; - string_t raw_file_name; + FuriString* file_path; + FuriString* file_name; + FuriString* raw_file_name; ProtocolDict* dict; ProtocolId protocol_id; @@ -128,9 +126,9 @@ bool lfrfid_load_key_from_file_select(LfRfid* app); bool lfrfid_delete_key(LfRfid* app); -bool lfrfid_load_key_data(LfRfid* app, string_t path, bool show_dialog); +bool lfrfid_load_key_data(LfRfid* app, FuriString* path, bool show_dialog); -bool lfrfid_save_key_data(LfRfid* app, string_t path); +bool lfrfid_save_key_data(LfRfid* app, FuriString* path); void lfrfid_make_app_folder(LfRfid* app); diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_delete_confirm.c b/applications/main/lfrfid/scenes/lfrfid_scene_delete_confirm.c index dc1c3df268f..b7702e26915 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_delete_confirm.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_delete_confirm.c @@ -4,31 +4,31 @@ void lfrfid_scene_delete_confirm_on_enter(void* context) { LfRfid* app = context; Widget* widget = app->widget; - string_t tmp_string; - string_init(tmp_string); + FuriString* tmp_string; + tmp_string = furi_string_alloc(); widget_add_button_element(widget, GuiButtonTypeLeft, "Back", lfrfid_widget_callback, app); widget_add_button_element(widget, GuiButtonTypeRight, "Delete", lfrfid_widget_callback, app); - string_printf(tmp_string, "Delete %s?", string_get_cstr(app->file_name)); + furi_string_printf(tmp_string, "Delete %s?", furi_string_get_cstr(app->file_name)); widget_add_string_element( - widget, 64, 0, AlignCenter, AlignTop, FontPrimary, string_get_cstr(tmp_string)); + widget, 64, 0, AlignCenter, AlignTop, FontPrimary, furi_string_get_cstr(tmp_string)); - string_reset(tmp_string); + furi_string_reset(tmp_string); size_t size = protocol_dict_get_data_size(app->dict, app->protocol_id); uint8_t* data = (uint8_t*)malloc(size); protocol_dict_get_data(app->dict, app->protocol_id, data, size); for(uint8_t i = 0; i < MIN(size, (size_t)8); i++) { if(i != 0) { - string_cat_printf(tmp_string, " "); + furi_string_cat_printf(tmp_string, " "); } - string_cat_printf(tmp_string, "%02X", data[i]); + furi_string_cat_printf(tmp_string, "%02X", data[i]); } free(data); widget_add_string_element( - widget, 64, 19, AlignCenter, AlignTop, FontSecondary, string_get_cstr(tmp_string)); + widget, 64, 19, AlignCenter, AlignTop, FontSecondary, furi_string_get_cstr(tmp_string)); widget_add_string_element( widget, 64, @@ -39,7 +39,7 @@ void lfrfid_scene_delete_confirm_on_enter(void* context) { protocol_dict_get_name(app->dict, app->protocol_id)); view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewWidget); - string_clear(tmp_string); + furi_string_free(tmp_string); } bool lfrfid_scene_delete_confirm_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_emulate.c b/applications/main/lfrfid/scenes/lfrfid_scene_emulate.c index 70cc2418c58..2725982f0ef 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_emulate.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_emulate.c @@ -8,8 +8,8 @@ void lfrfid_scene_emulate_on_enter(void* context) { DOLPHIN_DEED(DolphinDeedRfidEmulate); popup_set_header(popup, "Emulating", 89, 30, AlignCenter, AlignTop); - if(!string_empty_p(app->file_name)) { - popup_set_text(popup, string_get_cstr(app->file_name), 89, 43, AlignCenter, AlignTop); + if(!furi_string_empty(app->file_name)) { + popup_set_text(popup, furi_string_get_cstr(app->file_name), 89, 43, AlignCenter, AlignTop); } else { popup_set_text( popup, diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_extra_actions.c b/applications/main/lfrfid/scenes/lfrfid_scene_extra_actions.c index 43e3de99e72..d7fd93e19de 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_extra_actions.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_extra_actions.c @@ -42,7 +42,7 @@ void lfrfid_scene_extra_actions_on_enter(void* context) { submenu, scene_manager_get_scene_state(app->scene_manager, LfRfidSceneExtraActions)); // clear key - string_reset(app->file_name); + furi_string_reset(app->file_name); app->protocol_id = PROTOCOL_NO; app->read_type = LFRFIDWorkerReadTypeAuto; diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_raw_info.c b/applications/main/lfrfid/scenes/lfrfid_scene_raw_info.c index f60dd6243db..e5193521df9 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_raw_info.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_raw_info.c @@ -4,8 +4,8 @@ void lfrfid_scene_raw_info_on_enter(void* context) { LfRfid* app = context; Widget* widget = app->widget; - // string_t tmp_string; - // string_init(tmp_string); + // FuriString* tmp_string; + // tmp_string = furi_string_alloc(); bool sd_exist = storage_sd_status(app->storage) == FSE_OK; if(!sd_exist) { diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_raw_name.c b/applications/main/lfrfid/scenes/lfrfid_scene_raw_name.c index 512f9ee4c93..3e09dbf083c 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_raw_name.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_raw_name.c @@ -4,9 +4,9 @@ void lfrfid_scene_raw_name_on_enter(void* context) { LfRfid* app = context; TextInput* text_input = app->text_input; - const char* key_name = string_get_cstr(app->raw_file_name); + const char* key_name = furi_string_get_cstr(app->raw_file_name); - bool key_name_is_empty = string_empty_p(app->file_name); + bool key_name_is_empty = furi_string_empty(app->file_name); if(key_name_is_empty) { lfrfid_text_store_set(app, "RfidRecord"); } else { @@ -38,7 +38,7 @@ bool lfrfid_scene_raw_name_on_event(void* context, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { if(event.event == LfRfidEventNext) { consumed = true; - string_set_str(app->raw_file_name, app->text_store); + furi_string_set(app->raw_file_name, app->text_store); scene_manager_next_scene(scene_manager, LfRfidSceneRawInfo); } } diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_raw_read.c b/applications/main/lfrfid/scenes/lfrfid_scene_raw_read.c index d0c03ffa947..b2c7c364e10 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_raw_read.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_raw_read.c @@ -3,7 +3,7 @@ #define RAW_READ_TIME 5000 typedef struct { - string_t string_file_name; + FuriString* string_file_name; FuriTimer* timer; bool is_psk; bool error; @@ -31,7 +31,7 @@ void lfrfid_scene_raw_read_on_enter(void* context) { LfRfidReadRawState* state = malloc(sizeof(LfRfidReadRawState)); scene_manager_set_scene_state(app->scene_manager, LfRfidSceneRawRead, (uint32_t)state); - string_init(state->string_file_name); + state->string_file_name = furi_string_alloc(); popup_set_icon(popup, 0, 3, &I_RFIDDolphinReceive_97x61); view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewPopup); @@ -40,16 +40,16 @@ void lfrfid_scene_raw_read_on_enter(void* context) { state->timer = furi_timer_alloc(timer_callback, FuriTimerTypeOnce, app); furi_timer_start(state->timer, RAW_READ_TIME); - string_printf( + furi_string_printf( state->string_file_name, "%s/%s%s", LFRFID_SD_FOLDER, - string_get_cstr(app->raw_file_name), + furi_string_get_cstr(app->raw_file_name), LFRFID_APP_RAW_ASK_EXTENSION); popup_set_header(popup, "Reading\nRAW RFID\nASK", 89, 30, AlignCenter, AlignTop); lfrfid_worker_read_raw_start( app->lfworker, - string_get_cstr(state->string_file_name), + furi_string_get_cstr(state->string_file_name), LFRFIDWorkerReadTypeASKOnly, lfrfid_read_callback, app); @@ -88,15 +88,15 @@ bool lfrfid_scene_raw_read_on_event(void* context, SceneManagerEvent event) { popup, "Reading\nRAW RFID\nPSK", 89, 30, AlignCenter, AlignTop); notification_message(app->notifications, &sequence_blink_start_yellow); lfrfid_worker_stop(app->lfworker); - string_printf( + furi_string_printf( state->string_file_name, "%s/%s%s", LFRFID_SD_FOLDER, - string_get_cstr(app->raw_file_name), + furi_string_get_cstr(app->raw_file_name), LFRFID_APP_RAW_PSK_EXTENSION); lfrfid_worker_read_raw_start( app->lfworker, - string_get_cstr(state->string_file_name), + furi_string_get_cstr(state->string_file_name), LFRFIDWorkerReadTypePSKOnly, lfrfid_read_callback, app); @@ -121,6 +121,6 @@ void lfrfid_scene_raw_read_on_exit(void* context) { lfrfid_worker_stop_thread(app->lfworker); furi_timer_free(state->timer); - string_clear(state->string_file_name); + furi_string_free(state->string_file_name); free(state); } diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_read.c b/applications/main/lfrfid/scenes/lfrfid_scene_read.c index 661680381ea..4bdb215d165 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_read.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_read.c @@ -81,7 +81,7 @@ bool lfrfid_scene_read_on_event(void* context, SceneManagerEvent event) { app->protocol_id = app->protocol_id_next; DOLPHIN_DEED(DolphinDeedRfidReadSuccess); notification_message(app->notifications, &sequence_success); - string_reset(app->file_name); + furi_string_reset(app->file_name); scene_manager_next_scene(app->scene_manager, LfRfidSceneReadSuccess); consumed = true; } else if(event.event == LfRfidEventReadStartPSK) { diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_read_key_menu.c b/applications/main/lfrfid/scenes/lfrfid_scene_read_key_menu.c index 221cc00844d..7480304b6d4 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_read_key_menu.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_read_key_menu.c @@ -38,7 +38,7 @@ bool lfrfid_scene_read_key_menu_on_event(void* context, SceneManagerEvent event) scene_manager_next_scene(app->scene_manager, LfRfidSceneWrite); consumed = true; } else if(event.event == SubmenuIndexSave) { - string_reset(app->file_name); + furi_string_reset(app->file_name); scene_manager_next_scene(app->scene_manager, LfRfidSceneSaveName); consumed = true; } else if(event.event == SubmenuIndexEmulate) { diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_read_success.c b/applications/main/lfrfid/scenes/lfrfid_scene_read_success.c index 5ae6f0f1ab7..b83ef4a3992 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_read_success.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_read_success.c @@ -4,51 +4,51 @@ void lfrfid_scene_read_success_on_enter(void* context) { LfRfid* app = context; Widget* widget = app->widget; - string_t tmp_string; - string_init(tmp_string); + FuriString* tmp_string; + tmp_string = furi_string_alloc(); widget_add_button_element(widget, GuiButtonTypeLeft, "Retry", lfrfid_widget_callback, app); widget_add_button_element(widget, GuiButtonTypeRight, "More", lfrfid_widget_callback, app); - string_printf( + furi_string_printf( tmp_string, "%s[%s]", protocol_dict_get_name(app->dict, app->protocol_id), protocol_dict_get_manufacturer(app->dict, app->protocol_id)); widget_add_string_element( - widget, 0, 2, AlignLeft, AlignTop, FontPrimary, string_get_cstr(tmp_string)); + widget, 0, 2, AlignLeft, AlignTop, FontPrimary, furi_string_get_cstr(tmp_string)); - string_reset(tmp_string); + furi_string_reset(tmp_string); size_t size = protocol_dict_get_data_size(app->dict, app->protocol_id); uint8_t* data = (uint8_t*)malloc(size); protocol_dict_get_data(app->dict, app->protocol_id, data, size); for(uint8_t i = 0; i < size; i++) { if(i >= 9) { - string_cat_printf(tmp_string, ".."); + furi_string_cat_printf(tmp_string, ".."); break; } else { if(i != 0) { - string_cat_printf(tmp_string, " "); + furi_string_cat_printf(tmp_string, " "); } - string_cat_printf(tmp_string, "%02X", data[i]); + furi_string_cat_printf(tmp_string, "%02X", data[i]); } } free(data); - string_t render_data; - string_init(render_data); + FuriString* render_data; + render_data = furi_string_alloc(); protocol_dict_render_brief_data(app->dict, render_data, app->protocol_id); - string_cat_printf(tmp_string, "\r\n%s", string_get_cstr(render_data)); - string_clear(render_data); + furi_string_cat_printf(tmp_string, "\r\n%s", furi_string_get_cstr(render_data)); + furi_string_free(render_data); widget_add_string_multiline_element( - widget, 0, 16, AlignLeft, AlignTop, FontSecondary, string_get_cstr(tmp_string)); + widget, 0, 16, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(tmp_string)); notification_message_block(app->notifications, &sequence_set_green_255); view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewWidget); - string_clear(tmp_string); + furi_string_free(tmp_string); } bool lfrfid_scene_read_success_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_rpc.c b/applications/main/lfrfid/scenes/lfrfid_scene_rpc.c index a69d6453a3c..156dd97afa1 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_rpc.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_rpc.c @@ -34,13 +34,14 @@ bool lfrfid_scene_rpc_on_event(void* context, SceneManagerEvent event) { const char* arg = rpc_system_app_get_data(app->rpc_ctx); bool result = false; if(arg && (app->rpc_state == LfRfidRpcStateIdle)) { - string_set_str(app->file_path, arg); + furi_string_set(app->file_path, arg); if(lfrfid_load_key_data(app, app->file_path, false)) { lfrfid_worker_start_thread(app->lfworker); lfrfid_worker_emulate_start(app->lfworker, (LFRFIDProtocol)app->protocol_id); app->rpc_state = LfRfidRpcStateEmulating; - lfrfid_text_store_set(app, "emulating\n%s", string_get_cstr(app->file_name)); + lfrfid_text_store_set( + app, "emulating\n%s", furi_string_get_cstr(app->file_name)); popup_set_text(popup, app->text_store, 89, 44, AlignCenter, AlignTop); notification_message(app->notifications, &sequence_blink_start_magenta); diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_save_name.c b/applications/main/lfrfid/scenes/lfrfid_scene_save_name.c index febf30a418c..ca9a52de0ad 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_save_name.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_save_name.c @@ -1,21 +1,20 @@ -#include "m-string.h" #include #include "../lfrfid_i.h" void lfrfid_scene_save_name_on_enter(void* context) { LfRfid* app = context; TextInput* text_input = app->text_input; - string_t folder_path; - string_init(folder_path); + FuriString* folder_path; + folder_path = furi_string_alloc(); - bool key_name_is_empty = string_empty_p(app->file_name); + bool key_name_is_empty = furi_string_empty(app->file_name); if(key_name_is_empty) { - string_set_str(app->file_path, LFRFID_APP_FOLDER); + furi_string_set(app->file_path, LFRFID_APP_FOLDER); set_random_name(app->text_store, LFRFID_TEXT_STORE_SIZE); - string_set_str(folder_path, LFRFID_APP_FOLDER); + furi_string_set(folder_path, LFRFID_APP_FOLDER); } else { - lfrfid_text_store_set(app, "%s", string_get_cstr(app->file_name)); - path_extract_dirname(string_get_cstr(app->file_path), folder_path); + lfrfid_text_store_set(app, "%s", furi_string_get_cstr(app->file_name)); + path_extract_dirname(furi_string_get_cstr(app->file_path), folder_path); } text_input_set_header_text(text_input, "Name the card"); @@ -27,13 +26,15 @@ void lfrfid_scene_save_name_on_enter(void* context) { LFRFID_KEY_NAME_SIZE, key_name_is_empty); - FURI_LOG_I("", "%s %s", string_get_cstr(folder_path), app->text_store); + FURI_LOG_I("", "%s %s", furi_string_get_cstr(folder_path), app->text_store); ValidatorIsFile* validator_is_file = validator_is_file_alloc_init( - string_get_cstr(folder_path), LFRFID_APP_EXTENSION, string_get_cstr(app->file_name)); + furi_string_get_cstr(folder_path), + LFRFID_APP_EXTENSION, + furi_string_get_cstr(app->file_name)); text_input_set_validator(text_input, validator_is_file_callback, validator_is_file); - string_clear(folder_path); + furi_string_free(folder_path); view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewTextInput); } @@ -46,11 +47,11 @@ bool lfrfid_scene_save_name_on_event(void* context, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { if(event.event == LfRfidEventNext) { consumed = true; - if(!string_empty_p(app->file_name)) { + if(!furi_string_empty(app->file_name)) { lfrfid_delete_key(app); } - string_set_str(app->file_name, app->text_store); + furi_string_set(app->file_name, app->text_store); if(lfrfid_save_key(app)) { scene_manager_next_scene(scene_manager, LfRfidSceneSaveSuccess); diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_save_type.c b/applications/main/lfrfid/scenes/lfrfid_scene_save_type.c index 4c111600725..0ba51f0665b 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_save_type.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_save_type.c @@ -1,7 +1,7 @@ #include "../lfrfid_i.h" typedef struct { - string_t menu_item_name[LFRFIDProtocolMax]; + FuriString* menu_item_name[LFRFIDProtocolMax]; uint32_t line_sel; } SaveTypeCtx; @@ -20,18 +20,17 @@ void lfrfid_scene_save_type_on_enter(void* context) { if(strcmp( protocol_dict_get_manufacturer(app->dict, i), protocol_dict_get_name(app->dict, i)) != 0) { - string_init_printf( - state->menu_item_name[i], + state->menu_item_name[i] = furi_string_alloc_printf( "%s %s", protocol_dict_get_manufacturer(app->dict, i), protocol_dict_get_name(app->dict, i)); } else { - string_init_printf( - state->menu_item_name[i], "%s", protocol_dict_get_name(app->dict, i)); + state->menu_item_name[i] = + furi_string_alloc_printf("%s", protocol_dict_get_name(app->dict, i)); } submenu_add_item( submenu, - string_get_cstr(state->menu_item_name[i]), + furi_string_get_cstr(state->menu_item_name[i]), i, lfrfid_scene_save_type_submenu_callback, app); @@ -43,7 +42,7 @@ void lfrfid_scene_save_type_on_enter(void* context) { scene_manager_set_scene_state(app->scene_manager, LfRfidSceneSaveType, (uint32_t)state); // clear key name - string_reset(app->file_name); + furi_string_reset(app->file_name); view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewSubmenu); } @@ -75,7 +74,7 @@ void lfrfid_scene_save_type_on_exit(void* context) { submenu_reset(app->submenu); for(uint8_t i = 0; i < LFRFIDProtocolMax; i++) { - string_clear(state->menu_item_name[i]); + furi_string_free(state->menu_item_name[i]); } uint32_t line_sel = state->line_sel; diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_saved_info.c b/applications/main/lfrfid/scenes/lfrfid_scene_saved_info.c index 1496c6b4b2b..3f1c2d400e4 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_saved_info.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_saved_info.c @@ -4,13 +4,13 @@ void lfrfid_scene_saved_info_on_enter(void* context) { LfRfid* app = context; Widget* widget = app->widget; - string_t tmp_string; - string_init(tmp_string); + FuriString* tmp_string; + tmp_string = furi_string_alloc(); - string_printf( + furi_string_printf( tmp_string, "%s [%s]\r\n", - string_get_cstr(app->file_name), + furi_string_get_cstr(app->file_name), protocol_dict_get_name(app->dict, app->protocol_id)); size_t size = protocol_dict_get_data_size(app->dict, app->protocol_id); @@ -18,24 +18,24 @@ void lfrfid_scene_saved_info_on_enter(void* context) { protocol_dict_get_data(app->dict, app->protocol_id, data, size); for(uint8_t i = 0; i < size; i++) { if(i != 0) { - string_cat_printf(tmp_string, " "); + furi_string_cat_printf(tmp_string, " "); } - string_cat_printf(tmp_string, "%02X", data[i]); + furi_string_cat_printf(tmp_string, "%02X", data[i]); } free(data); - string_t render_data; - string_init(render_data); + FuriString* render_data; + render_data = furi_string_alloc(); protocol_dict_render_data(app->dict, render_data, app->protocol_id); - string_cat_printf(tmp_string, "\r\n%s", string_get_cstr(render_data)); - string_clear(render_data); + furi_string_cat_printf(tmp_string, "\r\n%s", furi_string_get_cstr(render_data)); + furi_string_free(render_data); widget_add_string_multiline_element( - widget, 0, 1, AlignLeft, AlignTop, FontSecondary, string_get_cstr(tmp_string)); + widget, 0, 1, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(tmp_string)); view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewWidget); - string_clear(tmp_string); + furi_string_free(tmp_string); } bool lfrfid_scene_saved_info_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_start.c b/applications/main/lfrfid/scenes/lfrfid_scene_start.c index 9074e859b45..d6d87f441ed 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_start.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_start.c @@ -33,7 +33,7 @@ void lfrfid_scene_start_on_enter(void* context) { submenu, scene_manager_get_scene_state(app->scene_manager, LfRfidSceneStart)); // clear key - string_reset(app->file_name); + furi_string_reset(app->file_name); app->protocol_id = PROTOCOL_NO; app->read_type = LFRFIDWorkerReadTypeAuto; @@ -49,7 +49,7 @@ bool lfrfid_scene_start_on_event(void* context, SceneManagerEvent event) { scene_manager_next_scene(app->scene_manager, LfRfidSceneRead); consumed = true; } else if(event.event == SubmenuIndexSaved) { - string_set_str(app->file_path, LFRFID_APP_FOLDER); + furi_string_set(app->file_path, LFRFID_APP_FOLDER); scene_manager_next_scene(app->scene_manager, LfRfidSceneSelectKey); consumed = true; } else if(event.event == SubmenuIndexAddManually) { diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_write.c b/applications/main/lfrfid/scenes/lfrfid_scene_write.c index 8e791d529ad..b7faed69ff4 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_write.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_write.c @@ -22,8 +22,8 @@ void lfrfid_scene_write_on_enter(void* context) { Popup* popup = app->popup; popup_set_header(popup, "Writing", 89, 30, AlignCenter, AlignTop); - if(!string_empty_p(app->file_name)) { - popup_set_text(popup, string_get_cstr(app->file_name), 89, 43, AlignCenter, AlignTop); + if(!furi_string_empty(app->file_name)) { + popup_set_text(popup, furi_string_get_cstr(app->file_name), 89, 43, AlignCenter, AlignTop); } else { popup_set_text( popup, diff --git a/applications/main/nfc/helpers/nfc_emv_parser.c b/applications/main/nfc/helpers/nfc_emv_parser.c index 0d7cb5a3371..30e102405e0 100644 --- a/applications/main/nfc/helpers/nfc_emv_parser.c +++ b/applications/main/nfc/helpers/nfc_emv_parser.c @@ -7,12 +7,12 @@ static const uint32_t nfc_resources_file_version = 1; static bool nfc_emv_parser_search_data( Storage* storage, const char* file_name, - string_t key, - string_t data) { + FuriString* key, + FuriString* data) { bool parsed = false; FlipperFormat* file = flipper_format_file_alloc(storage); - string_t temp_str; - string_init(temp_str); + FuriString* temp_str; + temp_str = furi_string_alloc(); do { // Open file @@ -20,14 +20,14 @@ static bool nfc_emv_parser_search_data( // Read file header and version uint32_t version = 0; if(!flipper_format_read_header(file, temp_str, &version)) break; - if(string_cmp_str(temp_str, nfc_resources_header) || + if(furi_string_cmp_str(temp_str, nfc_resources_header) || (version != nfc_resources_file_version)) break; - if(!flipper_format_read_string(file, string_get_cstr(key), data)) break; + if(!flipper_format_read_string(file, furi_string_get_cstr(key), data)) break; parsed = true; } while(false); - string_clear(temp_str); + furi_string_free(temp_str); flipper_format_free(file); return parsed; } @@ -36,47 +36,47 @@ bool nfc_emv_parser_get_aid_name( Storage* storage, uint8_t* aid, uint8_t aid_len, - string_t aid_name) { + FuriString* aid_name) { furi_assert(storage); bool parsed = false; - string_t key; - string_init(key); + FuriString* key; + key = furi_string_alloc(); for(uint8_t i = 0; i < aid_len; i++) { - string_cat_printf(key, "%02X", aid[i]); + furi_string_cat_printf(key, "%02X", aid[i]); } if(nfc_emv_parser_search_data(storage, EXT_PATH("nfc/assets/aid.nfc"), key, aid_name)) { parsed = true; } - string_clear(key); + furi_string_free(key); return parsed; } bool nfc_emv_parser_get_country_name( Storage* storage, uint16_t country_code, - string_t country_name) { + FuriString* country_name) { bool parsed = false; - string_t key; - string_init_printf(key, "%04X", country_code); + FuriString* key; + key = furi_string_alloc_printf("%04X", country_code); if(nfc_emv_parser_search_data( storage, EXT_PATH("nfc/assets/country_code.nfc"), key, country_name)) { parsed = true; } - string_clear(key); + furi_string_free(key); return parsed; } bool nfc_emv_parser_get_currency_name( Storage* storage, uint16_t currency_code, - string_t currency_name) { + FuriString* currency_name) { bool parsed = false; - string_t key; - string_init_printf(key, "%04X", currency_code); + FuriString* key; + key = furi_string_alloc_printf("%04X", currency_code); if(nfc_emv_parser_search_data( storage, EXT_PATH("nfc/assets/currency_code.nfc"), key, currency_name)) { parsed = true; } - string_clear(key); + furi_string_free(key); return parsed; } diff --git a/applications/main/nfc/helpers/nfc_emv_parser.h b/applications/main/nfc/helpers/nfc_emv_parser.h index 5948ed34066..abe57f470fd 100644 --- a/applications/main/nfc/helpers/nfc_emv_parser.h +++ b/applications/main/nfc/helpers/nfc_emv_parser.h @@ -2,7 +2,6 @@ #include #include -#include #include /** Get EMV application name by number @@ -16,7 +15,7 @@ bool nfc_emv_parser_get_aid_name( Storage* storage, uint8_t* aid, uint8_t aid_len, - string_t aid_name); + FuriString* aid_name); /** Get country name by country code * @param storage Storage instance @@ -27,7 +26,7 @@ bool nfc_emv_parser_get_aid_name( bool nfc_emv_parser_get_country_name( Storage* storage, uint16_t country_code, - string_t country_name); + FuriString* country_name); /** Get currency name by currency code * @param storage Storage instance @@ -38,4 +37,4 @@ bool nfc_emv_parser_get_country_name( bool nfc_emv_parser_get_currency_name( Storage* storage, uint16_t currency_code, - string_t currency_name); + FuriString* currency_name); diff --git a/applications/main/nfc/nfc.c b/applications/main/nfc/nfc.c index f47a5bac29f..44a0f63687b 100644 --- a/applications/main/nfc/nfc.c +++ b/applications/main/nfc/nfc.c @@ -83,7 +83,7 @@ Nfc* nfc_alloc() { nfc->text_box = text_box_alloc(); view_dispatcher_add_view( nfc->view_dispatcher, NfcViewTextBox, text_box_get_view(nfc->text_box)); - string_init(nfc->text_box_store); + nfc->text_box_store = furi_string_alloc(); // Custom Widget nfc->widget = widget_alloc(); @@ -153,7 +153,7 @@ void nfc_free(Nfc* nfc) { // TextBox view_dispatcher_remove_view(nfc->view_dispatcher, NfcViewTextBox); text_box_free(nfc->text_box); - string_clear(nfc->text_box_store); + furi_string_free(nfc->text_box_store); // Custom Widget view_dispatcher_remove_view(nfc->view_dispatcher, NfcViewWidget); diff --git a/applications/main/nfc/nfc_cli.c b/applications/main/nfc/nfc_cli.c index 4ac95f0437d..a6475ca681d 100644 --- a/applications/main/nfc/nfc_cli.c +++ b/applications/main/nfc/nfc_cli.c @@ -17,7 +17,7 @@ static void nfc_cli_print_usage() { } } -static void nfc_cli_detect(Cli* cli, string_t args) { +static void nfc_cli_detect(Cli* cli, FuriString* args) { UNUSED(args); // Check if nfc worker is not busy if(furi_hal_nfc_is_busy()) { @@ -46,7 +46,7 @@ static void nfc_cli_detect(Cli* cli, string_t args) { furi_hal_nfc_sleep(); } -static void nfc_cli_emulate(Cli* cli, string_t args) { +static void nfc_cli_emulate(Cli* cli, FuriString* args) { UNUSED(args); // Check if nfc worker is not busy if(furi_hal_nfc_is_busy()) { @@ -76,7 +76,7 @@ static void nfc_cli_emulate(Cli* cli, string_t args) { furi_hal_nfc_sleep(); } -static void nfc_cli_field(Cli* cli, string_t args) { +static void nfc_cli_field(Cli* cli, FuriString* args) { UNUSED(args); // Check if nfc worker is not busy if(furi_hal_nfc_is_busy()) { @@ -98,27 +98,27 @@ static void nfc_cli_field(Cli* cli, string_t args) { furi_hal_nfc_sleep(); } -static void nfc_cli(Cli* cli, string_t args, void* context) { +static void nfc_cli(Cli* cli, FuriString* args, void* context) { UNUSED(context); - string_t cmd; - string_init(cmd); + FuriString* cmd; + cmd = furi_string_alloc(); do { if(!args_read_string_and_trim(args, cmd)) { nfc_cli_print_usage(); break; } - if(string_cmp_str(cmd, "detect") == 0) { + if(furi_string_cmp_str(cmd, "detect") == 0) { nfc_cli_detect(cli, args); break; } - if(string_cmp_str(cmd, "emulate") == 0) { + if(furi_string_cmp_str(cmd, "emulate") == 0) { nfc_cli_emulate(cli, args); break; } if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - if(string_cmp_str(cmd, "field") == 0) { + if(furi_string_cmp_str(cmd, "field") == 0) { nfc_cli_field(cli, args); break; } @@ -127,7 +127,7 @@ static void nfc_cli(Cli* cli, string_t args, void* context) { nfc_cli_print_usage(); } while(false); - string_clear(cmd); + furi_string_free(cmd); } void nfc_on_system_start() { diff --git a/applications/main/nfc/nfc_i.h b/applications/main/nfc/nfc_i.h index 15ea5348f9f..fa5b54edc57 100644 --- a/applications/main/nfc/nfc_i.h +++ b/applications/main/nfc/nfc_i.h @@ -62,7 +62,7 @@ struct Nfc { FuriHalNfcDevData dev_edit_data; char text_store[NFC_TEXT_STORE_SIZE + 1]; - string_t text_box_store; + FuriString* text_box_store; uint8_t byte_input_store[6]; MfClassicUserKeys_t mfc_key_strs; // Used in MFC key listing diff --git a/applications/main/nfc/scenes/nfc_scene_delete.c b/applications/main/nfc/scenes/nfc_scene_delete.c index 987927e19c4..cbb52bfd0f2 100644 --- a/applications/main/nfc/scenes/nfc_scene_delete.c +++ b/applications/main/nfc/scenes/nfc_scene_delete.c @@ -12,40 +12,40 @@ void nfc_scene_delete_on_enter(void* context) { FuriHalNfcDevData* nfc_data = &nfc->dev->dev_data.nfc_data; // Setup Custom Widget view - string_t temp_str; - string_init(temp_str); + FuriString* temp_str; + temp_str = furi_string_alloc(); - string_printf(temp_str, "\e#Delete %s?\e#", nfc->dev->dev_name); + furi_string_printf(temp_str, "\e#Delete %s?\e#", nfc->dev->dev_name); widget_add_text_box_element( - nfc->widget, 0, 0, 128, 23, AlignCenter, AlignCenter, string_get_cstr(temp_str), false); + nfc->widget, 0, 0, 128, 23, AlignCenter, AlignCenter, furi_string_get_cstr(temp_str), false); widget_add_button_element( nfc->widget, GuiButtonTypeLeft, "Cancel", nfc_scene_delete_widget_callback, nfc); widget_add_button_element( nfc->widget, GuiButtonTypeRight, "Delete", nfc_scene_delete_widget_callback, nfc); - string_set_str(temp_str, "UID:"); + furi_string_set(temp_str, "UID:"); for(size_t i = 0; i < nfc_data->uid_len; i++) { - string_cat_printf(temp_str, " %02X", nfc_data->uid[i]); + furi_string_cat_printf(temp_str, " %02X", nfc_data->uid[i]); } widget_add_string_element( - nfc->widget, 64, 24, AlignCenter, AlignTop, FontSecondary, string_get_cstr(temp_str)); + nfc->widget, 64, 24, AlignCenter, AlignTop, FontSecondary, furi_string_get_cstr(temp_str)); NfcProtocol protocol = nfc->dev->dev_data.protocol; if(protocol == NfcDeviceProtocolEMV) { - string_set_str(temp_str, "EMV bank card"); + furi_string_set(temp_str, "EMV bank card"); } else if(protocol == NfcDeviceProtocolMifareUl) { - string_set_str(temp_str, nfc_mf_ul_type(nfc->dev->dev_data.mf_ul_data.type, true)); + furi_string_set(temp_str, nfc_mf_ul_type(nfc->dev->dev_data.mf_ul_data.type, true)); } else if(protocol == NfcDeviceProtocolMifareClassic) { - string_set_str(temp_str, nfc_mf_classic_type(nfc->dev->dev_data.mf_classic_data.type)); + furi_string_set(temp_str, nfc_mf_classic_type(nfc->dev->dev_data.mf_classic_data.type)); } else if(protocol == NfcDeviceProtocolMifareDesfire) { - string_set_str(temp_str, "MIFARE DESFire"); + furi_string_set(temp_str, "MIFARE DESFire"); } else { - string_set_str(temp_str, "Unknown ISO tag"); + furi_string_set(temp_str, "Unknown ISO tag"); } widget_add_string_element( - nfc->widget, 64, 34, AlignCenter, AlignTop, FontSecondary, string_get_cstr(temp_str)); + nfc->widget, 64, 34, AlignCenter, AlignTop, FontSecondary, furi_string_get_cstr(temp_str)); widget_add_string_element(nfc->widget, 64, 44, AlignCenter, AlignTop, FontSecondary, "NFC-A"); - string_clear(temp_str); + furi_string_free(temp_str); view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); } diff --git a/applications/main/nfc/scenes/nfc_scene_device_info.c b/applications/main/nfc/scenes/nfc_scene_device_info.c index 245aea5c594..9780ffe41d2 100644 --- a/applications/main/nfc/scenes/nfc_scene_device_info.c +++ b/applications/main/nfc/scenes/nfc_scene_device_info.c @@ -12,49 +12,52 @@ void nfc_scene_device_info_on_enter(void* context) { Nfc* nfc = context; NfcDeviceData* dev_data = &nfc->dev->dev_data; - string_t temp_str; - string_init(temp_str); + FuriString* temp_str; + temp_str = furi_string_alloc(); if(dev_data->protocol == NfcDeviceProtocolEMV) { EmvData* emv_data = &dev_data->emv_data; - string_printf(temp_str, "\e#%s\n", emv_data->name); + furi_string_printf(temp_str, "\e#%s\n", emv_data->name); for(uint8_t i = 0; i < emv_data->number_len; i += 2) { - string_cat_printf(temp_str, "%02X%02X ", emv_data->number[i], emv_data->number[i + 1]); + furi_string_cat_printf( + temp_str, "%02X%02X ", emv_data->number[i], emv_data->number[i + 1]); } - string_strim(temp_str); + furi_string_trim(temp_str); // Add expiration date if(emv_data->exp_mon) { - string_cat_printf(temp_str, "\nExp: %02X/%02X", emv_data->exp_mon, emv_data->exp_year); + furi_string_cat_printf( + temp_str, "\nExp: %02X/%02X", emv_data->exp_mon, emv_data->exp_year); } // Parse currency code if((emv_data->currency_code)) { - string_t currency_name; - string_init(currency_name); + FuriString* currency_name; + currency_name = furi_string_alloc(); if(nfc_emv_parser_get_currency_name( nfc->dev->storage, emv_data->currency_code, currency_name)) { - string_cat_printf(temp_str, "\nCur: %s ", string_get_cstr(currency_name)); + furi_string_cat_printf( + temp_str, "\nCur: %s ", furi_string_get_cstr(currency_name)); } - string_clear(currency_name); + furi_string_free(currency_name); } // Parse country code if((emv_data->country_code)) { - string_t country_name; - string_init(country_name); + FuriString* country_name; + country_name = furi_string_alloc(); if(nfc_emv_parser_get_country_name( nfc->dev->storage, emv_data->country_code, country_name)) { - string_cat_printf(temp_str, "Reg: %s", string_get_cstr(country_name)); + furi_string_cat_printf(temp_str, "Reg: %s", furi_string_get_cstr(country_name)); } - string_clear(country_name); + furi_string_free(country_name); } } else if( dev_data->protocol == NfcDeviceProtocolMifareClassic || dev_data->protocol == NfcDeviceProtocolMifareUl) { - string_set(temp_str, nfc->dev->dev_data.parsed_data); + furi_string_set(temp_str, nfc->dev->dev_data.parsed_data); } - widget_add_text_scroll_element(nfc->widget, 0, 0, 128, 52, string_get_cstr(temp_str)); - string_clear(temp_str); + widget_add_text_scroll_element(nfc->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); + furi_string_free(temp_str); widget_add_button_element( nfc->widget, GuiButtonTypeRight, "More", nfc_scene_device_info_widget_callback, nfc); diff --git a/applications/main/nfc/scenes/nfc_scene_emulate_uid.c b/applications/main/nfc/scenes/nfc_scene_emulate_uid.c index f6402301072..8bb20796005 100644 --- a/applications/main/nfc/scenes/nfc_scene_emulate_uid.c +++ b/applications/main/nfc/scenes/nfc_scene_emulate_uid.c @@ -35,22 +35,22 @@ static void nfc_scene_emulate_uid_widget_config(Nfc* nfc, bool data_received) { FuriHalNfcDevData* data = &nfc->dev->dev_data.nfc_data; Widget* widget = nfc->widget; widget_reset(widget); - string_t info_str; - string_init(info_str); + FuriString* info_str; + info_str = furi_string_alloc(); widget_add_icon_element(widget, 0, 3, &I_RFIDDolphinSend_97x61); widget_add_string_element(widget, 89, 32, AlignCenter, AlignTop, FontPrimary, "Emulating UID"); if(strcmp(nfc->dev->dev_name, "")) { - string_printf(info_str, "%s", nfc->dev->dev_name); + furi_string_printf(info_str, "%s", nfc->dev->dev_name); } else { for(uint8_t i = 0; i < data->uid_len; i++) { - string_cat_printf(info_str, "%02X ", data->uid[i]); + furi_string_cat_printf(info_str, "%02X ", data->uid[i]); } } - string_strim(info_str); + furi_string_trim(info_str); widget_add_text_box_element( - widget, 56, 43, 70, 21, AlignCenter, AlignTop, string_get_cstr(info_str), true); - string_clear(info_str); + widget, 56, 43, 70, 21, AlignCenter, AlignTop, furi_string_get_cstr(info_str), true); + furi_string_free(info_str); if(data_received) { widget_add_button_element( widget, GuiButtonTypeCenter, "Log", nfc_scene_emulate_uid_widget_callback, nfc); @@ -67,7 +67,7 @@ void nfc_scene_emulate_uid_on_enter(void* context) { TextBox* text_box = nfc->text_box; text_box_set_font(text_box, TextBoxFontHex); text_box_set_focus(text_box, TextBoxFocusEnd); - string_reset(nfc->text_box_store); + furi_string_reset(nfc->text_box_store); // Set Widget state and view scene_manager_set_scene_state( @@ -94,17 +94,17 @@ bool nfc_scene_emulate_uid_on_event(void* context, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { if(event.event == NfcCustomEventWorkerExit) { // Add data button to widget if data is received for the first time - if(!string_size(nfc->text_box_store)) { + if(!furi_string_size(nfc->text_box_store)) { nfc_scene_emulate_uid_widget_config(nfc, true); } // Update TextBox data - if(string_size(nfc->text_box_store) < NFC_SCENE_EMULATE_UID_LOG_SIZE_MAX) { - string_cat_printf(nfc->text_box_store, "R:"); + if(furi_string_size(nfc->text_box_store) < NFC_SCENE_EMULATE_UID_LOG_SIZE_MAX) { + furi_string_cat_printf(nfc->text_box_store, "R:"); for(uint16_t i = 0; i < reader_data->size; i++) { - string_cat_printf(nfc->text_box_store, " %02X", reader_data->data[i]); + furi_string_cat_printf(nfc->text_box_store, " %02X", reader_data->data[i]); } - string_push_back(nfc->text_box_store, '\n'); - text_box_set_text(nfc->text_box, string_get_cstr(nfc->text_box_store)); + furi_string_push_back(nfc->text_box_store, '\n'); + text_box_set_text(nfc->text_box, furi_string_get_cstr(nfc->text_box_store)); } memset(reader_data, 0, sizeof(NfcReaderRequestData)); consumed = true; @@ -140,7 +140,7 @@ void nfc_scene_emulate_uid_on_exit(void* context) { // Clear view widget_reset(nfc->widget); text_box_reset(nfc->text_box); - string_reset(nfc->text_box_store); + furi_string_reset(nfc->text_box_store); nfc_blink_stop(nfc); } diff --git a/applications/main/nfc/scenes/nfc_scene_emv_read_success.c b/applications/main/nfc/scenes/nfc_scene_emv_read_success.c index a40b4c1c9e0..8b6ab160eee 100644 --- a/applications/main/nfc/scenes/nfc_scene_emv_read_success.c +++ b/applications/main/nfc/scenes/nfc_scene_emv_read_success.c @@ -23,42 +23,44 @@ void nfc_scene_emv_read_success_on_enter(void* context) { widget_add_button_element( nfc->widget, GuiButtonTypeRight, "More", nfc_scene_emv_read_success_widget_callback, nfc); - string_t temp_str; - string_init_printf(temp_str, "\e#%s\n", emv_data->name); + FuriString* temp_str; + temp_str = furi_string_alloc_printf("\e#%s\n", emv_data->name); for(uint8_t i = 0; i < emv_data->number_len; i += 2) { - string_cat_printf(temp_str, "%02X%02X ", emv_data->number[i], emv_data->number[i + 1]); + furi_string_cat_printf( + temp_str, "%02X%02X ", emv_data->number[i], emv_data->number[i + 1]); } - string_strim(temp_str); + furi_string_trim(temp_str); // Add expiration date if(emv_data->exp_mon) { - string_cat_printf(temp_str, "\nExp: %02X/%02X", emv_data->exp_mon, emv_data->exp_year); + furi_string_cat_printf( + temp_str, "\nExp: %02X/%02X", emv_data->exp_mon, emv_data->exp_year); } // Parse currency code if((emv_data->currency_code)) { - string_t currency_name; - string_init(currency_name); + FuriString* currency_name; + currency_name = furi_string_alloc(); if(nfc_emv_parser_get_currency_name( nfc->dev->storage, emv_data->currency_code, currency_name)) { - string_cat_printf(temp_str, "\nCur: %s ", string_get_cstr(currency_name)); + furi_string_cat_printf(temp_str, "\nCur: %s ", furi_string_get_cstr(currency_name)); } - string_clear(currency_name); + furi_string_free(currency_name); } // Parse country code if((emv_data->country_code)) { - string_t country_name; - string_init(country_name); + FuriString* country_name; + country_name = furi_string_alloc(); if(nfc_emv_parser_get_country_name( nfc->dev->storage, emv_data->country_code, country_name)) { - string_cat_printf(temp_str, "Reg: %s", string_get_cstr(country_name)); + furi_string_cat_printf(temp_str, "Reg: %s", furi_string_get_cstr(country_name)); } - string_clear(country_name); + furi_string_free(country_name); } notification_message_block(nfc->notifications, &sequence_set_green_255); - widget_add_text_scroll_element(nfc->widget, 0, 0, 128, 52, string_get_cstr(temp_str)); - string_clear(temp_str); + widget_add_text_scroll_element(nfc->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); + furi_string_free(temp_str); view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); } diff --git a/applications/main/nfc/scenes/nfc_scene_generate_info.c b/applications/main/nfc/scenes/nfc_scene_generate_info.c index 7fb7eb94207..66900767e54 100644 --- a/applications/main/nfc/scenes/nfc_scene_generate_info.c +++ b/applications/main/nfc/scenes/nfc_scene_generate_info.c @@ -16,15 +16,15 @@ void nfc_scene_generate_info_on_enter(void* context) { dialog_ex_set_right_button_text(dialog_ex, "More"); // Create info text - string_t info_str; - string_init_printf( - info_str, "%s\n%s\nUID:", nfc->generator->name, nfc_get_dev_type(data->type)); + FuriString* info_str = furi_string_alloc_printf( + "%s\n%s\nUID:", nfc->generator->name, nfc_get_dev_type(data->type)); + // Append UID for(int i = 0; i < data->uid_len; ++i) { - string_cat_printf(info_str, " %02X", data->uid[i]); + furi_string_cat_printf(info_str, " %02X", data->uid[i]); } - nfc_text_store_set(nfc, string_get_cstr(info_str)); - string_clear(info_str); + nfc_text_store_set(nfc, furi_string_get_cstr(info_str)); + furi_string_free(info_str); dialog_ex_set_text(dialog_ex, nfc->text_store, 0, 0, AlignLeft, AlignTop); dialog_ex_set_context(dialog_ex, nfc); diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_delete.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_delete.c index 16a189da626..0ea3f59a45e 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_delete.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_delete.c @@ -16,8 +16,8 @@ void nfc_scene_mf_classic_keys_delete_on_enter(void* context) { uint32_t key_index = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfClassicKeysDelete); // Setup Custom Widget view - string_t key_str; - string_init(key_str); + FuriString* key_str; + key_str = furi_string_alloc(); widget_add_string_element( nfc->widget, 64, 0, AlignCenter, AlignTop, FontPrimary, "Delete this key?"); @@ -36,9 +36,15 @@ void nfc_scene_mf_classic_keys_delete_on_enter(void* context) { mf_classic_dict_get_key_at_index_str(dict, key_str, key_index); widget_add_string_element( - nfc->widget, 64, 32, AlignCenter, AlignCenter, FontSecondary, string_get_cstr(key_str)); + nfc->widget, + 64, + 32, + AlignCenter, + AlignCenter, + FontSecondary, + furi_string_get_cstr(key_str)); - string_clear(key_str); + furi_string_free(key_str); mf_classic_dict_free(dict); view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_list.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_list.c index 6670ae1328f..5649ea870c0 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_list.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_list.c @@ -19,19 +19,19 @@ void nfc_scene_mf_classic_keys_list_popup_callback(void* context) { void nfc_scene_mf_classic_keys_list_prepare(Nfc* nfc, MfClassicDict* dict) { Submenu* submenu = nfc->submenu; uint32_t index = 0; - string_t temp_key; - string_init(temp_key); + FuriString* temp_key; + temp_key = furi_string_alloc(); submenu_set_header(submenu, "Select key to delete:"); while(mf_classic_dict_get_next_key_str(dict, temp_key)) { char* current_key = (char*)malloc(sizeof(char) * 13); - strncpy(current_key, string_get_cstr(temp_key), 12); + strncpy(current_key, furi_string_get_cstr(temp_key), 12); MfClassicUserKeys_push_back(nfc->mfc_key_strs, current_key); FURI_LOG_D("ListKeys", "Key %d: %s", index, current_key); submenu_add_item( submenu, current_key, index++, nfc_scene_mf_classic_keys_list_submenu_callback, nfc); } - string_clear(temp_key); + furi_string_free(temp_key); } void nfc_scene_mf_classic_keys_list_on_enter(void* context) { diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_read_success.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_read_success.c index 3ca24416afb..0cdb86464bc 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_read_success.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_read_success.c @@ -27,26 +27,26 @@ void nfc_scene_mf_classic_read_success_on_enter(void* context) { widget_add_button_element( widget, GuiButtonTypeRight, "More", nfc_scene_mf_classic_read_success_widget_callback, nfc); - string_t temp_str; - if(string_size(nfc->dev->dev_data.parsed_data)) { - string_init_set(temp_str, nfc->dev->dev_data.parsed_data); + FuriString* temp_str; + if(furi_string_size(nfc->dev->dev_data.parsed_data)) { + temp_str = furi_string_alloc_set(nfc->dev->dev_data.parsed_data); } else { - string_init_printf(temp_str, "\e#%s\n", nfc_mf_classic_type(mf_data->type)); - string_cat_printf(temp_str, "UID:"); + temp_str = furi_string_alloc_printf("\e#%s\n", nfc_mf_classic_type(mf_data->type)); + furi_string_cat_printf(temp_str, "UID:"); for(size_t i = 0; i < dev_data->nfc_data.uid_len; i++) { - string_cat_printf(temp_str, " %02X", dev_data->nfc_data.uid[i]); + furi_string_cat_printf(temp_str, " %02X", dev_data->nfc_data.uid[i]); } uint8_t sectors_total = mf_classic_get_total_sectors_num(mf_data->type); uint8_t keys_total = sectors_total * 2; uint8_t keys_found = 0; uint8_t sectors_read = 0; mf_classic_get_read_sectors_and_keys(mf_data, §ors_read, &keys_found); - string_cat_printf(temp_str, "\nKeys Found: %d/%d", keys_found, keys_total); - string_cat_printf(temp_str, "\nSectors Read: %d/%d", sectors_read, sectors_total); + furi_string_cat_printf(temp_str, "\nKeys Found: %d/%d", keys_found, keys_total); + furi_string_cat_printf(temp_str, "\nSectors Read: %d/%d", sectors_read, sectors_total); } - widget_add_text_scroll_element(widget, 0, 0, 128, 52, string_get_cstr(temp_str)); - string_clear(temp_str); + widget_add_text_scroll_element(widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); + furi_string_free(temp_str); notification_message_block(nfc->notifications, &sequence_set_green_255); diff --git a/applications/main/nfc/scenes/nfc_scene_mf_desfire_app.c b/applications/main/nfc/scenes/nfc_scene_mf_desfire_app.c index dd8424641ce..afc5f0dee4b 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_desfire_app.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_desfire_app.c @@ -84,7 +84,7 @@ bool nfc_scene_mf_desfire_app_on_event(void* context, SceneManagerEvent event) { } else { MifareDesfireApplication* app = nfc_scene_mf_desfire_app_get_app(nfc); TextBox* text_box = nfc->text_box; - string_reset(nfc->text_box_store); + furi_string_reset(nfc->text_box_store); if(event.event == SubmenuIndexAppInfo) { mf_df_cat_application_info(app, nfc->text_box_store); } else { @@ -98,7 +98,7 @@ bool nfc_scene_mf_desfire_app_on_event(void* context, SceneManagerEvent event) { } mf_df_cat_file(file, nfc->text_box_store); } - text_box_set_text(text_box, string_get_cstr(nfc->text_box_store)); + text_box_set_text(text_box, furi_string_get_cstr(nfc->text_box_store)); scene_manager_set_scene_state(nfc->scene_manager, NfcSceneMfDesfireApp, state | 1); view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewTextBox); consumed = true; @@ -120,6 +120,6 @@ void nfc_scene_mf_desfire_app_on_exit(void* context) { // Clear views popup_reset(nfc->popup); text_box_reset(nfc->text_box); - string_reset(nfc->text_box_store); + furi_string_reset(nfc->text_box_store); submenu_reset(nfc->submenu); } diff --git a/applications/main/nfc/scenes/nfc_scene_mf_desfire_data.c b/applications/main/nfc/scenes/nfc_scene_mf_desfire_data.c index 0019a0846a4..e619d0377c9 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_desfire_data.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_desfire_data.c @@ -67,10 +67,10 @@ bool nfc_scene_mf_desfire_data_on_event(void* context, SceneManagerEvent event) if(event.type == SceneManagerEventTypeCustom) { TextBox* text_box = nfc->text_box; - string_reset(nfc->text_box_store); + furi_string_reset(nfc->text_box_store); if(event.event == SubmenuIndexCardInfo) { mf_df_cat_card_info(data, nfc->text_box_store); - text_box_set_text(text_box, string_get_cstr(nfc->text_box_store)); + text_box_set_text(text_box, furi_string_get_cstr(nfc->text_box_store)); view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewTextBox); scene_manager_set_scene_state( nfc->scene_manager, @@ -102,6 +102,6 @@ void nfc_scene_mf_desfire_data_on_exit(void* context) { // Clear views text_box_reset(nfc->text_box); - string_reset(nfc->text_box_store); + furi_string_reset(nfc->text_box_store); submenu_reset(nfc->submenu); } diff --git a/applications/main/nfc/scenes/nfc_scene_mf_desfire_read_success.c b/applications/main/nfc/scenes/nfc_scene_mf_desfire_read_success.c index 12047c15a72..c5b8cfa2027 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_desfire_read_success.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_desfire_read_success.c @@ -20,20 +20,19 @@ void nfc_scene_mf_desfire_read_success_on_enter(void* context) { Widget* widget = nfc->widget; // Prepare string for data display - string_t temp_str; - string_init_printf(temp_str, "\e#MIFARE DESfire\n"); - string_cat_printf(temp_str, "UID:"); + FuriString* temp_str = furi_string_alloc_printf("\e#MIFARE DESfire\n"); + furi_string_cat_printf(temp_str, "UID:"); for(size_t i = 0; i < nfc_data->uid_len; i++) { - string_cat_printf(temp_str, " %02X", nfc_data->uid[i]); + furi_string_cat_printf(temp_str, " %02X", nfc_data->uid[i]); } uint32_t bytes_total = 1 << (data->version.sw_storage >> 1); uint32_t bytes_free = data->free_memory ? data->free_memory->bytes : 0; - string_cat_printf(temp_str, "\n%d", bytes_total); + furi_string_cat_printf(temp_str, "\n%d", bytes_total); if(data->version.sw_storage & 1) { - string_push_back(temp_str, '+'); + furi_string_push_back(temp_str, '+'); } - string_cat_printf(temp_str, " bytes, %d bytes free\n", bytes_free); + furi_string_cat_printf(temp_str, " bytes, %d bytes free\n", bytes_free); uint16_t n_apps = 0; uint16_t n_files = 0; @@ -43,20 +42,20 @@ void nfc_scene_mf_desfire_read_success_on_enter(void* context) { n_files++; } } - string_cat_printf(temp_str, "%d Application", n_apps); + furi_string_cat_printf(temp_str, "%d Application", n_apps); if(n_apps != 1) { - string_push_back(temp_str, 's'); + furi_string_push_back(temp_str, 's'); } - string_cat_printf(temp_str, ", %d file", n_files); + furi_string_cat_printf(temp_str, ", %d file", n_files); if(n_files != 1) { - string_push_back(temp_str, 's'); + furi_string_push_back(temp_str, 's'); } notification_message_block(nfc->notifications, &sequence_set_green_255); // Add text scroll element - widget_add_text_scroll_element(widget, 0, 0, 128, 52, string_get_cstr(temp_str)); - string_clear(temp_str); + widget_add_text_scroll_element(widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); + furi_string_free(temp_str); // Add button elements widget_add_button_element( diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_data.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_data.c index d4184a6b41a..8cd223ee64d 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_data.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_data.c @@ -8,11 +8,11 @@ void nfc_scene_mf_ultralight_data_on_enter(void* context) { text_box_set_font(text_box, TextBoxFontHex); for(uint16_t i = 0; i < data->data_size; i += 2) { if(!(i % 8) && i) { - string_push_back(nfc->text_box_store, '\n'); + furi_string_push_back(nfc->text_box_store, '\n'); } - string_cat_printf(nfc->text_box_store, "%02X%02X ", data->data[i], data->data[i + 1]); + furi_string_cat_printf(nfc->text_box_store, "%02X%02X ", data->data[i], data->data[i + 1]); } - text_box_set_text(text_box, string_get_cstr(nfc->text_box_store)); + text_box_set_text(text_box, furi_string_get_cstr(nfc->text_box_store)); view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewTextBox); } @@ -28,5 +28,5 @@ void nfc_scene_mf_ultralight_data_on_exit(void* context) { // Clean view text_box_reset(nfc->text_box); - string_reset(nfc->text_box_store); + furi_string_reset(nfc->text_box_store); } \ No newline at end of file diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_auth_result.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_auth_result.c index b9421545596..f0c53c9ba85 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_auth_result.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_auth_result.c @@ -21,8 +21,8 @@ void nfc_scene_mf_ultralight_read_auth_result_on_enter(void* context) { MfUltralightData* mf_ul_data = &nfc->dev->dev_data.mf_ul_data; MfUltralightConfigPages* config_pages = mf_ultralight_get_config_pages(mf_ul_data); Widget* widget = nfc->widget; - string_t temp_str; - string_init(temp_str); + FuriString* temp_str; + temp_str = furi_string_alloc(); if((mf_ul_data->data_read == mf_ul_data->data_size) && (mf_ul_data->data_read > 0)) { widget_add_string_element( @@ -31,14 +31,14 @@ void nfc_scene_mf_ultralight_read_auth_result_on_enter(void* context) { widget_add_string_element( widget, 64, 0, AlignCenter, AlignTop, FontPrimary, "Not all pages unlocked!"); } - string_set_str(temp_str, "UID:"); + furi_string_set(temp_str, "UID:"); for(size_t i = 0; i < nfc_data->uid_len; i++) { - string_cat_printf(temp_str, " %02X", nfc_data->uid[i]); + furi_string_cat_printf(temp_str, " %02X", nfc_data->uid[i]); } widget_add_string_element( - widget, 0, 17, AlignLeft, AlignTop, FontSecondary, string_get_cstr(temp_str)); + widget, 0, 17, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(temp_str)); if(mf_ul_data->auth_success) { - string_printf( + furi_string_printf( temp_str, "Password: %02X %02X %02X %02X", config_pages->auth_data.pwd.raw[0], @@ -46,19 +46,19 @@ void nfc_scene_mf_ultralight_read_auth_result_on_enter(void* context) { config_pages->auth_data.pwd.raw[2], config_pages->auth_data.pwd.raw[3]); widget_add_string_element( - widget, 0, 28, AlignLeft, AlignTop, FontSecondary, string_get_cstr(temp_str)); - string_printf( + widget, 0, 28, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(temp_str)); + furi_string_printf( temp_str, "PACK: %02X %02X", config_pages->auth_data.pack.raw[0], config_pages->auth_data.pack.raw[1]); widget_add_string_element( - widget, 0, 39, AlignLeft, AlignTop, FontSecondary, string_get_cstr(temp_str)); + widget, 0, 39, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(temp_str)); } - string_printf( + furi_string_printf( temp_str, "Pages Read: %d/%d", mf_ul_data->data_read / 4, mf_ul_data->data_size / 4); widget_add_string_element( - widget, 0, 50, AlignLeft, AlignTop, FontSecondary, string_get_cstr(temp_str)); + widget, 0, 50, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(temp_str)); widget_add_button_element( widget, GuiButtonTypeRight, @@ -66,7 +66,7 @@ void nfc_scene_mf_ultralight_read_auth_result_on_enter(void* context) { nfc_scene_mf_ultralight_read_auth_result_widget_callback, nfc); - string_clear(temp_str); + furi_string_free(temp_str); view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); } diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_success.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_success.c index f6dc5984e2f..77034ea8061 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_success.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_success.c @@ -33,23 +33,23 @@ void nfc_scene_mf_ultralight_read_success_on_enter(void* context) { nfc_scene_mf_ultralight_read_success_widget_callback, nfc); - string_t temp_str; - if(string_size(nfc->dev->dev_data.parsed_data)) { - string_init_set(temp_str, nfc->dev->dev_data.parsed_data); + FuriString* temp_str; + if(furi_string_size(nfc->dev->dev_data.parsed_data)) { + temp_str = furi_string_alloc_set(nfc->dev->dev_data.parsed_data); } else { - string_init_printf(temp_str, "\e#%s\n", nfc_mf_ul_type(mf_ul_data->type, true)); - string_cat_printf(temp_str, "UID:"); + temp_str = furi_string_alloc_printf("\e#%s\n", nfc_mf_ul_type(mf_ul_data->type, true)); + furi_string_cat_printf(temp_str, "UID:"); for(size_t i = 0; i < data->uid_len; i++) { - string_cat_printf(temp_str, " %02X", data->uid[i]); + furi_string_cat_printf(temp_str, " %02X", data->uid[i]); } - string_cat_printf( + furi_string_cat_printf( temp_str, "\nPages Read: %d/%d", mf_ul_data->data_read / 4, mf_ul_data->data_size / 4); if(mf_ul_data->data_read != mf_ul_data->data_size) { - string_cat_printf(temp_str, "\nPassword-protected pages!"); + furi_string_cat_printf(temp_str, "\nPassword-protected pages!"); } } - widget_add_text_scroll_element(widget, 0, 0, 128, 52, string_get_cstr(temp_str)); - string_clear(temp_str); + widget_add_text_scroll_element(widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); + furi_string_free(temp_str); notification_message_block(nfc->notifications, &sequence_set_green_255); diff --git a/applications/main/nfc/scenes/nfc_scene_mfkey_nonces_info.c b/applications/main/nfc/scenes/nfc_scene_mfkey_nonces_info.c index b45b690d38a..9fd4f5ae0c4 100644 --- a/applications/main/nfc/scenes/nfc_scene_mfkey_nonces_info.c +++ b/applications/main/nfc/scenes/nfc_scene_mfkey_nonces_info.c @@ -11,21 +11,21 @@ void nfc_scene_mfkey_nonces_info_callback(GuiButtonType result, InputType type, void nfc_scene_mfkey_nonces_info_on_enter(void* context) { Nfc* nfc = context; - string_t temp_str; - string_init(temp_str); + FuriString* temp_str; + temp_str = furi_string_alloc(); uint16_t nonces_saved = mfkey32_get_auth_sectors(temp_str); - widget_add_text_scroll_element(nfc->widget, 0, 22, 128, 42, string_get_cstr(temp_str)); - string_printf(temp_str, "Nonces saved %d", nonces_saved); + widget_add_text_scroll_element(nfc->widget, 0, 22, 128, 42, furi_string_get_cstr(temp_str)); + furi_string_printf(temp_str, "Nonces saved %d", nonces_saved); widget_add_string_element( - nfc->widget, 0, 0, AlignLeft, AlignTop, FontPrimary, string_get_cstr(temp_str)); + nfc->widget, 0, 0, AlignLeft, AlignTop, FontPrimary, furi_string_get_cstr(temp_str)); widget_add_string_element( nfc->widget, 0, 12, AlignLeft, AlignTop, FontSecondary, "Authenticated sectors:"); widget_add_button_element( nfc->widget, GuiButtonTypeRight, "Next", nfc_scene_mfkey_nonces_info_callback, nfc); - string_clear(temp_str); + furi_string_free(temp_str); view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); } diff --git a/applications/main/nfc/scenes/nfc_scene_nfc_data_info.c b/applications/main/nfc/scenes/nfc_scene_nfc_data_info.c index 33f5e44af6e..bb7d58f7c35 100644 --- a/applications/main/nfc/scenes/nfc_scene_nfc_data_info.c +++ b/applications/main/nfc/scenes/nfc_scene_nfc_data_info.c @@ -22,47 +22,48 @@ void nfc_scene_nfc_data_info_on_enter(void* context) { text_scroll_height = 64; } - string_t temp_str; - string_init(temp_str); + FuriString* temp_str; + temp_str = furi_string_alloc(); // Set name if present if(nfc->dev->dev_name[0] != '\0') { - string_printf(temp_str, "\ec%s\n", nfc->dev->dev_name); + furi_string_printf(temp_str, "\ec%s\n", nfc->dev->dev_name); } // Set tag type if(protocol == NfcDeviceProtocolEMV) { - string_cat_printf(temp_str, "\e#EMV Bank Card\n"); + furi_string_cat_printf(temp_str, "\e#EMV Bank Card\n"); } else if(protocol == NfcDeviceProtocolMifareUl) { - string_cat_printf(temp_str, "\e#%s\n", nfc_mf_ul_type(dev_data->mf_ul_data.type, true)); + furi_string_cat_printf( + temp_str, "\e#%s\n", nfc_mf_ul_type(dev_data->mf_ul_data.type, true)); } else if(protocol == NfcDeviceProtocolMifareClassic) { - string_cat_printf( + furi_string_cat_printf( temp_str, "\e#%s\n", nfc_mf_classic_type(dev_data->mf_classic_data.type)); } else if(protocol == NfcDeviceProtocolMifareDesfire) { - string_cat_printf(temp_str, "\e#MIFARE DESfire\n"); + furi_string_cat_printf(temp_str, "\e#MIFARE DESfire\n"); } else { - string_cat_printf(temp_str, "\e#Unknown ISO tag\n"); + furi_string_cat_printf(temp_str, "\e#Unknown ISO tag\n"); } // Set tag iso data char iso_type = FURI_BIT(nfc_data->sak, 5) ? '4' : '3'; - string_cat_printf(temp_str, "ISO 14443-%c (NFC-A)\n", iso_type); - string_cat_printf(temp_str, "UID:"); + furi_string_cat_printf(temp_str, "ISO 14443-%c (NFC-A)\n", iso_type); + furi_string_cat_printf(temp_str, "UID:"); for(size_t i = 0; i < nfc_data->uid_len; i++) { - string_cat_printf(temp_str, " %02X", nfc_data->uid[i]); + furi_string_cat_printf(temp_str, " %02X", nfc_data->uid[i]); } - string_cat_printf(temp_str, "\nATQA: %02X %02X ", nfc_data->atqa[1], nfc_data->atqa[0]); - string_cat_printf(temp_str, " SAK: %02X", nfc_data->sak); + furi_string_cat_printf(temp_str, "\nATQA: %02X %02X ", nfc_data->atqa[1], nfc_data->atqa[0]); + furi_string_cat_printf(temp_str, " SAK: %02X", nfc_data->sak); // Set application specific data if(protocol == NfcDeviceProtocolMifareDesfire) { MifareDesfireData* data = &dev_data->mf_df_data; uint32_t bytes_total = 1 << (data->version.sw_storage >> 1); uint32_t bytes_free = data->free_memory ? data->free_memory->bytes : 0; - string_cat_printf(temp_str, "\n%d", bytes_total); + furi_string_cat_printf(temp_str, "\n%d", bytes_total); if(data->version.sw_storage & 1) { - string_push_back(temp_str, '+'); + furi_string_push_back(temp_str, '+'); } - string_cat_printf(temp_str, " bytes, %d bytes free\n", bytes_free); + furi_string_cat_printf(temp_str, " bytes, %d bytes free\n", bytes_free); uint16_t n_apps = 0; uint16_t n_files = 0; @@ -72,20 +73,20 @@ void nfc_scene_nfc_data_info_on_enter(void* context) { n_files++; } } - string_cat_printf(temp_str, "%d Application", n_apps); + furi_string_cat_printf(temp_str, "%d Application", n_apps); if(n_apps != 1) { - string_push_back(temp_str, 's'); + furi_string_push_back(temp_str, 's'); } - string_cat_printf(temp_str, ", %d file", n_files); + furi_string_cat_printf(temp_str, ", %d file", n_files); if(n_files != 1) { - string_push_back(temp_str, 's'); + furi_string_push_back(temp_str, 's'); } } else if(protocol == NfcDeviceProtocolMifareUl) { MfUltralightData* data = &dev_data->mf_ul_data; - string_cat_printf( + furi_string_cat_printf( temp_str, "\nPages Read %d/%d", data->data_read / 4, data->data_size / 4); if(data->data_size > data->data_read) { - string_cat_printf(temp_str, "\nPassword-protected"); + furi_string_cat_printf(temp_str, "\nPassword-protected"); } } else if(protocol == NfcDeviceProtocolMifareClassic) { MfClassicData* data = &dev_data->mf_classic_data; @@ -94,14 +95,14 @@ void nfc_scene_nfc_data_info_on_enter(void* context) { uint8_t keys_found = 0; uint8_t sectors_read = 0; mf_classic_get_read_sectors_and_keys(data, §ors_read, &keys_found); - string_cat_printf(temp_str, "\nKeys Found %d/%d", keys_found, keys_total); - string_cat_printf(temp_str, "\nSectors Read %d/%d", sectors_read, sectors_total); + furi_string_cat_printf(temp_str, "\nKeys Found %d/%d", keys_found, keys_total); + furi_string_cat_printf(temp_str, "\nSectors Read %d/%d", sectors_read, sectors_total); } // Add text scroll widget widget_add_text_scroll_element( - widget, 0, 0, 128, text_scroll_height, string_get_cstr(temp_str)); - string_clear(temp_str); + widget, 0, 0, 128, text_scroll_height, furi_string_get_cstr(temp_str)); + furi_string_free(temp_str); view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); } diff --git a/applications/main/nfc/scenes/nfc_scene_nfca_read_success.c b/applications/main/nfc/scenes/nfc_scene_nfca_read_success.c index c695da247a3..2ea7c99217a 100644 --- a/applications/main/nfc/scenes/nfc_scene_nfca_read_success.c +++ b/applications/main/nfc/scenes/nfc_scene_nfca_read_success.c @@ -22,22 +22,22 @@ void nfc_scene_nfca_read_success_on_enter(void* context) { FuriHalNfcDevData* data = &nfc->dev->dev_data.nfc_data; Widget* widget = nfc->widget; - string_t temp_str; - string_init_set_str(temp_str, "\e#Unknown ISO tag\n"); + FuriString* temp_str; + temp_str = furi_string_alloc_set("\e#Unknown ISO tag\n"); notification_message_block(nfc->notifications, &sequence_set_green_255); char iso_type = FURI_BIT(data->sak, 5) ? '4' : '3'; - string_cat_printf(temp_str, "ISO 14443-%c (NFC-A)\n", iso_type); - string_cat_printf(temp_str, "UID:"); + furi_string_cat_printf(temp_str, "ISO 14443-%c (NFC-A)\n", iso_type); + furi_string_cat_printf(temp_str, "UID:"); for(size_t i = 0; i < data->uid_len; i++) { - string_cat_printf(temp_str, " %02X", data->uid[i]); + furi_string_cat_printf(temp_str, " %02X", data->uid[i]); } - string_cat_printf(temp_str, "\nATQA: %02X %02X ", data->atqa[1], data->atqa[0]); - string_cat_printf(temp_str, " SAK: %02X", data->sak); + furi_string_cat_printf(temp_str, "\nATQA: %02X %02X ", data->atqa[1], data->atqa[0]); + furi_string_cat_printf(temp_str, " SAK: %02X", data->sak); - widget_add_text_scroll_element(widget, 0, 0, 128, 52, string_get_cstr(temp_str)); - string_clear(temp_str); + widget_add_text_scroll_element(widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); + furi_string_free(temp_str); widget_add_button_element( widget, GuiButtonTypeLeft, "Retry", nfc_scene_nfca_read_success_widget_callback, nfc); diff --git a/applications/main/nfc/scenes/nfc_scene_read_card_success.c b/applications/main/nfc/scenes/nfc_scene_read_card_success.c index 0cb38cbdf43..352cb4a7e52 100644 --- a/applications/main/nfc/scenes/nfc_scene_read_card_success.c +++ b/applications/main/nfc/scenes/nfc_scene_read_card_success.c @@ -16,26 +16,26 @@ void nfc_scene_read_card_success_widget_callback( void nfc_scene_read_card_success_on_enter(void* context) { Nfc* nfc = context; - string_t temp_str; - string_init(temp_str); + FuriString* temp_str; + temp_str = furi_string_alloc(); DOLPHIN_DEED(DolphinDeedNfcReadSuccess); // Setup view FuriHalNfcDevData* data = &nfc->dev->dev_data.nfc_data; Widget* widget = nfc->widget; - string_set_str(temp_str, nfc_get_dev_type(data->type)); + furi_string_set(temp_str, nfc_get_dev_type(data->type)); widget_add_string_element( - widget, 64, 12, AlignCenter, AlignBottom, FontPrimary, string_get_cstr(temp_str)); - string_set_str(temp_str, "UID:"); + widget, 64, 12, AlignCenter, AlignBottom, FontPrimary, furi_string_get_cstr(temp_str)); + furi_string_set(temp_str, "UID:"); for(uint8_t i = 0; i < data->uid_len; i++) { - string_cat_printf(temp_str, " %02X", data->uid[i]); + furi_string_cat_printf(temp_str, " %02X", data->uid[i]); } widget_add_string_element( - widget, 64, 32, AlignCenter, AlignCenter, FontSecondary, string_get_cstr(temp_str)); + widget, 64, 32, AlignCenter, AlignCenter, FontSecondary, furi_string_get_cstr(temp_str)); widget_add_button_element( widget, GuiButtonTypeLeft, "Retry", nfc_scene_read_card_success_widget_callback, nfc); - string_clear(temp_str); + furi_string_free(temp_str); view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); } diff --git a/applications/main/nfc/scenes/nfc_scene_save_name.c b/applications/main/nfc/scenes/nfc_scene_save_name.c index d5e05472ad7..736eab7de50 100644 --- a/applications/main/nfc/scenes/nfc_scene_save_name.c +++ b/applications/main/nfc/scenes/nfc_scene_save_name.c @@ -1,5 +1,4 @@ #include "../nfc_i.h" -#include "m-string.h" #include #include #include @@ -31,22 +30,22 @@ void nfc_scene_save_name_on_enter(void* context) { NFC_DEV_NAME_MAX_LEN, dev_name_empty); - string_t folder_path; - string_init(folder_path); + FuriString* folder_path; + folder_path = furi_string_alloc(); - 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); + if(furi_string_end_with(nfc->dev->load_path, NFC_APP_EXTENSION)) { + path_extract_dirname(furi_string_get_cstr(nfc->dev->load_path), folder_path); } else { - string_set_str(folder_path, NFC_APP_FOLDER); + furi_string_set(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); + furi_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); + furi_string_free(folder_path); } bool nfc_scene_save_name_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/main/nfc/scenes/nfc_scene_set_type.c b/applications/main/nfc/scenes/nfc_scene_set_type.c index ec6d11447ad..3e08aeb3fe1 100644 --- a/applications/main/nfc/scenes/nfc_scene_set_type.c +++ b/applications/main/nfc/scenes/nfc_scene_set_type.c @@ -1,5 +1,4 @@ #include "../nfc_i.h" -#include "m-string.h" #include "../helpers/nfc_generators.h" enum SubmenuIndex { @@ -19,7 +18,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, ""); + furi_string_set(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/main/nfc/views/dict_attack.c b/applications/main/nfc/views/dict_attack.c index b4674fd3117..cbafbf69a42 100644 --- a/applications/main/nfc/views/dict_attack.c +++ b/applications/main/nfc/views/dict_attack.c @@ -1,6 +1,5 @@ #include "dict_attack.h" -#include #include typedef enum { @@ -17,7 +16,7 @@ struct DictAttack { typedef struct { DictAttackState state; MfClassicType type; - string_t header; + FuriString* header; uint8_t sectors_total; uint8_t sectors_read; uint8_t sector_current; @@ -38,7 +37,8 @@ static void dict_attack_draw_callback(Canvas* canvas, void* model) { } else if(m->state == DictAttackStateRead) { char draw_str[32] = {}; canvas_set_font(canvas, FontPrimary); - canvas_draw_str_aligned(canvas, 64, 2, AlignCenter, AlignTop, string_get_cstr(m->header)); + canvas_draw_str_aligned( + canvas, 64, 2, AlignCenter, AlignTop, furi_string_get_cstr(m->header)); canvas_set_font(canvas, FontSecondary); float dict_progress = m->dict_keys_total == 0 ? 0 : @@ -81,7 +81,7 @@ DictAttack* dict_attack_alloc() { view_set_context(dict_attack->view, dict_attack); with_view_model( dict_attack->view, (DictAttackViewModel * model) { - string_init(model->header); + model->header = furi_string_alloc(); return false; }); return dict_attack; @@ -91,7 +91,7 @@ void dict_attack_free(DictAttack* dict_attack) { furi_assert(dict_attack); with_view_model( dict_attack->view, (DictAttackViewModel * model) { - string_clear(model->header); + furi_string_free(model->header); return false; }); view_free(dict_attack->view); @@ -111,7 +111,7 @@ void dict_attack_reset(DictAttack* dict_attack) { model->keys_found = 0; model->dict_keys_total = 0; model->dict_keys_current = 0; - string_reset(model->header); + furi_string_reset(model->header); return false; }); } @@ -134,7 +134,7 @@ void dict_attack_set_header(DictAttack* dict_attack, const char* header) { with_view_model( dict_attack->view, (DictAttackViewModel * model) { - string_set_str(model->header, header); + furi_string_set(model->header, header); return true; }); } diff --git a/applications/main/subghz/helpers/subghz_types.h b/applications/main/subghz/helpers/subghz_types.h index 7fe1e7ce78d..023fb4b6f1c 100644 --- a/applications/main/subghz/helpers/subghz_types.h +++ b/applications/main/subghz/helpers/subghz_types.h @@ -1,6 +1,5 @@ #pragma once -#include "m-string.h" #include #include @@ -72,7 +71,7 @@ typedef enum { } SubGhzViewId; struct SubGhzPresetDefinition { - string_t name; + FuriString* name; uint32_t frequency; uint8_t* data; size_t data_size; diff --git a/applications/main/subghz/scenes/subghz_scene_delete.c b/applications/main/subghz/scenes/subghz_scene_delete.c index 43151de209c..94814b14324 100644 --- a/applications/main/subghz/scenes/subghz_scene_delete.c +++ b/applications/main/subghz/scenes/subghz_scene_delete.c @@ -11,17 +11,23 @@ void subghz_scene_delete_callback(GuiButtonType result, InputType type, void* co void subghz_scene_delete_on_enter(void* context) { SubGhz* subghz = context; - string_t frequency_str; - string_t modulation_str; - string_t text; + FuriString* frequency_str; + FuriString* modulation_str; + FuriString* text; - string_init(frequency_str); - string_init(modulation_str); - string_init(text); + frequency_str = furi_string_alloc(); + modulation_str = furi_string_alloc(); + text = furi_string_alloc(); subghz_get_frequency_modulation(subghz, frequency_str, modulation_str); widget_add_string_element( - subghz->widget, 78, 0, AlignLeft, AlignTop, FontSecondary, string_get_cstr(frequency_str)); + subghz->widget, + 78, + 0, + AlignLeft, + AlignTop, + FontSecondary, + furi_string_get_cstr(frequency_str)); widget_add_string_element( subghz->widget, @@ -30,14 +36,14 @@ void subghz_scene_delete_on_enter(void* context) { AlignLeft, AlignTop, FontSecondary, - string_get_cstr(modulation_str)); + furi_string_get_cstr(modulation_str)); subghz_protocol_decoder_base_get_string(subghz->txrx->decoder_result, text); widget_add_string_multiline_element( - subghz->widget, 0, 0, AlignLeft, AlignTop, FontSecondary, string_get_cstr(text)); + subghz->widget, 0, 0, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(text)); - string_clear(frequency_str); - string_clear(modulation_str); - string_clear(text); + furi_string_free(frequency_str); + furi_string_free(modulation_str); + furi_string_free(text); widget_add_button_element( subghz->widget, GuiButtonTypeRight, "Delete", subghz_scene_delete_callback, subghz); @@ -49,7 +55,7 @@ bool subghz_scene_delete_on_event(void* context, SceneManagerEvent event) { SubGhz* subghz = context; if(event.type == SceneManagerEventTypeCustom) { if(event.event == SubGhzCustomEventSceneDelete) { - string_set(subghz->file_path_tmp, subghz->file_path); + furi_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/main/subghz/scenes/subghz_scene_delete_raw.c b/applications/main/subghz/scenes/subghz_scene_delete_raw.c index a20968d5bb9..fa4fc6f6424 100644 --- a/applications/main/subghz/scenes/subghz_scene_delete_raw.c +++ b/applications/main/subghz/scenes/subghz_scene_delete_raw.c @@ -15,18 +15,18 @@ void subghz_scene_delete_raw_callback(GuiButtonType result, InputType type, void void subghz_scene_delete_raw_on_enter(void* context) { SubGhz* subghz = context; - string_t frequency_str; - string_t modulation_str; + FuriString* frequency_str; + FuriString* modulation_str; - string_init(frequency_str); - string_init(modulation_str); + frequency_str = furi_string_alloc(); + modulation_str = furi_string_alloc(); char delete_str[SUBGHZ_MAX_LEN_NAME + 16]; - string_t file_name; - string_init(file_name); + FuriString* file_name; + file_name = furi_string_alloc(); 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); + snprintf(delete_str, sizeof(delete_str), "\e#Delete %s?\e#", furi_string_get_cstr(file_name)); + furi_string_free(file_name); widget_add_text_box_element( subghz->widget, 0, 0, 128, 23, AlignCenter, AlignCenter, delete_str, false); @@ -35,7 +35,13 @@ void subghz_scene_delete_raw_on_enter(void* context) { subghz->widget, 38, 25, AlignLeft, AlignTop, FontSecondary, "RAW signal"); subghz_get_frequency_modulation(subghz, frequency_str, modulation_str); widget_add_string_element( - subghz->widget, 35, 37, AlignLeft, AlignTop, FontSecondary, string_get_cstr(frequency_str)); + subghz->widget, + 35, + 37, + AlignLeft, + AlignTop, + FontSecondary, + furi_string_get_cstr(frequency_str)); widget_add_string_element( subghz->widget, @@ -44,10 +50,10 @@ void subghz_scene_delete_raw_on_enter(void* context) { AlignLeft, AlignTop, FontSecondary, - string_get_cstr(modulation_str)); + furi_string_get_cstr(modulation_str)); - string_clear(frequency_str); - string_clear(modulation_str); + furi_string_free(frequency_str); + furi_string_free(modulation_str); widget_add_button_element( subghz->widget, GuiButtonTypeRight, "Delete", subghz_scene_delete_raw_callback, subghz); @@ -61,7 +67,7 @@ bool subghz_scene_delete_raw_on_event(void* context, SceneManagerEvent event) { SubGhz* subghz = context; if(event.type == SceneManagerEventTypeCustom) { if(event.event == SubGhzCustomEventSceneDeleteRAW) { - string_set(subghz->file_path_tmp, subghz->file_path); + furi_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/main/subghz/scenes/subghz_scene_more_raw.c b/applications/main/subghz/scenes/subghz_scene_more_raw.c index a5bade927ff..d75ab13c74b 100644 --- a/applications/main/subghz/scenes/subghz_scene_more_raw.c +++ b/applications/main/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) { - string_reset(subghz->file_path_tmp); + furi_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/main/subghz/scenes/subghz_scene_read_raw.c b/applications/main/subghz/scenes/subghz_scene_read_raw.c index 38b73e07d03..5dacefd6600 100644 --- a/applications/main/subghz/scenes/subghz_scene_read_raw.c +++ b/applications/main/subghz/scenes/subghz_scene_read_raw.c @@ -10,8 +10,8 @@ bool subghz_scene_read_raw_update_filename(SubGhz* subghz) { bool ret = false; //set the path to read the file - string_t temp_str; - string_init(temp_str); + FuriString* temp_str; + temp_str = furi_string_alloc(); do { if(!flipper_format_rewind(subghz->txrx->fff_data)) { FURI_LOG_E(TAG, "Rewind error"); @@ -23,12 +23,12 @@ bool subghz_scene_read_raw_update_filename(SubGhz* subghz) { break; } - string_set(subghz->file_path, temp_str); + furi_string_set(subghz->file_path, temp_str); ret = true; } while(false); - string_clear(temp_str); + furi_string_free(temp_str); return ret; } @@ -36,18 +36,20 @@ static void subghz_scene_read_raw_update_statusbar(void* context) { furi_assert(context); SubGhz* subghz = context; - string_t frequency_str; - string_t modulation_str; + FuriString* frequency_str; + FuriString* modulation_str; - string_init(frequency_str); - string_init(modulation_str); + frequency_str = furi_string_alloc(); + modulation_str = furi_string_alloc(); subghz_get_frequency_modulation(subghz, frequency_str, modulation_str); subghz_read_raw_add_data_statusbar( - subghz->subghz_read_raw, string_get_cstr(frequency_str), string_get_cstr(modulation_str)); + subghz->subghz_read_raw, + furi_string_get_cstr(frequency_str), + furi_string_get_cstr(modulation_str)); - string_clear(frequency_str); - string_clear(modulation_str); + furi_string_free(frequency_str); + furi_string_free(modulation_str); } void subghz_scene_read_raw_callback(SubGhzCustomEvent event, void* context) { @@ -65,8 +67,8 @@ void subghz_scene_read_raw_callback_end_tx(void* context) { void subghz_scene_read_raw_on_enter(void* context) { SubGhz* subghz = context; - string_t file_name; - string_init(file_name); + FuriString* file_name; + file_name = furi_string_alloc(); switch(subghz->txrx->rx_key_state) { case SubGhzRxKeyStateBack: @@ -75,13 +77,15 @@ void subghz_scene_read_raw_on_enter(void* context) { case SubGhzRxKeyStateRAWLoad: 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->subghz_read_raw, + SubGhzReadRAWStatusLoadKeyTX, + furi_string_get_cstr(file_name)); subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE; break; case SubGhzRxKeyStateRAWSave: path_extract_filename(subghz->file_path, file_name, true); subghz_read_raw_set_status( - subghz->subghz_read_raw, SubGhzReadRAWStatusSaveKey, string_get_cstr(file_name)); + subghz->subghz_read_raw, SubGhzReadRAWStatusSaveKey, furi_string_get_cstr(file_name)); subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE; break; default: @@ -89,7 +93,7 @@ void subghz_scene_read_raw_on_enter(void* context) { subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE; break; } - string_clear(file_name); + furi_string_free(file_name); subghz_scene_read_raw_update_statusbar(subghz); //set callback view raw @@ -174,7 +178,7 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { 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); + furi_string_set(subghz->file_path_tmp, subghz->file_path); subghz_delete_file(subghz); } } @@ -245,12 +249,13 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { subghz_protocol_raw_save_to_file_stop( (SubGhzProtocolDecoderRAW*)subghz->txrx->decoder_result); - string_t temp_str; - string_init(temp_str); - string_printf( + FuriString* temp_str; + temp_str = furi_string_alloc(); + furi_string_printf( temp_str, "%s/%s%s", SUBGHZ_RAW_FOLDER, RAW_FILE_NAME, SUBGHZ_APP_EXTENSION); - subghz_protocol_raw_gen_fff_data(subghz->txrx->fff_data, string_get_cstr(temp_str)); - string_clear(temp_str); + subghz_protocol_raw_gen_fff_data( + subghz->txrx->fff_data, furi_string_get_cstr(temp_str)); + furi_string_free(temp_str); if(spl_count > 0) { notification_message(subghz->notifications, &sequence_set_green_255); @@ -279,13 +284,14 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { subghz_begin( subghz, subghz_setting_get_preset_data_by_name( - subghz->setting, string_get_cstr(subghz->txrx->preset->name))); + subghz->setting, + furi_string_get_cstr(subghz->txrx->preset->name))); subghz_rx(subghz, subghz->txrx->preset->frequency); } subghz->state_notifications = SubGhzNotificationStateRx; subghz->txrx->rx_key_state = SubGhzRxKeyStateAddKey; } else { - string_set_str(subghz->error_str, "Function requires\nan SD card."); + furi_string_set(subghz->error_str, "Function requires\nan SD card."); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError); } } diff --git a/applications/main/subghz/scenes/subghz_scene_receiver.c b/applications/main/subghz/scenes/subghz_scene_receiver.c index 7c1f016d09a..77a92145714 100644 --- a/applications/main/subghz/scenes/subghz_scene_receiver.c +++ b/applications/main/subghz/scenes/subghz_scene_receiver.c @@ -33,31 +33,31 @@ static const NotificationSequence subghs_sequence_rx_locked = { static void subghz_scene_receiver_update_statusbar(void* context) { SubGhz* subghz = context; - string_t history_stat_str; - string_init(history_stat_str); + FuriString* history_stat_str; + history_stat_str = furi_string_alloc(); if(!subghz_history_get_text_space_left(subghz->txrx->history, history_stat_str)) { - string_t frequency_str; - string_t modulation_str; + FuriString* frequency_str; + FuriString* modulation_str; - string_init(frequency_str); - string_init(modulation_str); + frequency_str = furi_string_alloc(); + modulation_str = furi_string_alloc(); subghz_get_frequency_modulation(subghz, frequency_str, modulation_str); subghz_view_receiver_add_data_statusbar( subghz->subghz_receiver, - string_get_cstr(frequency_str), - string_get_cstr(modulation_str), - string_get_cstr(history_stat_str)); + furi_string_get_cstr(frequency_str), + furi_string_get_cstr(modulation_str), + furi_string_get_cstr(history_stat_str)); - string_clear(frequency_str); - string_clear(modulation_str); + furi_string_free(frequency_str); + furi_string_free(modulation_str); } else { subghz_view_receiver_add_data_statusbar( - subghz->subghz_receiver, string_get_cstr(history_stat_str), "", ""); + subghz->subghz_receiver, furi_string_get_cstr(history_stat_str), "", ""); subghz->state_notifications = SubGhzNotificationStateIDLE; } - string_clear(history_stat_str); + furi_string_free(history_stat_str); } void subghz_scene_receiver_callback(SubGhzCustomEvent event, void* context) { @@ -72,11 +72,11 @@ static void subghz_scene_add_to_history_callback( void* context) { furi_assert(context); SubGhz* subghz = context; - string_t str_buff; - string_init(str_buff); + FuriString* str_buff; + str_buff = furi_string_alloc(); if(subghz_history_add_to_history(subghz->txrx->history, decoder_base, subghz->txrx->preset)) { - string_reset(str_buff); + furi_string_reset(str_buff); subghz->state_notifications = SubGhzNotificationStateRxDone; @@ -84,22 +84,22 @@ static void subghz_scene_add_to_history_callback( subghz->txrx->history, str_buff, subghz_history_get_item(subghz->txrx->history) - 1); subghz_view_receiver_add_item_to_menu( subghz->subghz_receiver, - string_get_cstr(str_buff), + furi_string_get_cstr(str_buff), subghz_history_get_type_protocol( subghz->txrx->history, subghz_history_get_item(subghz->txrx->history) - 1)); subghz_scene_receiver_update_statusbar(subghz); } subghz_receiver_reset(receiver); - string_clear(str_buff); + furi_string_free(str_buff); subghz->txrx->rx_key_state = SubGhzRxKeyStateAddKey; } void subghz_scene_receiver_on_enter(void* context) { SubGhz* subghz = context; - string_t str_buff; - string_init(str_buff); + FuriString* str_buff; + str_buff = furi_string_alloc(); if(subghz->txrx->rx_key_state == SubGhzRxKeyStateIDLE) { subghz_preset_init( @@ -113,15 +113,15 @@ void subghz_scene_receiver_on_enter(void* context) { //Load history to receiver subghz_view_receiver_exit(subghz->subghz_receiver); for(uint8_t i = 0; i < subghz_history_get_item(subghz->txrx->history); i++) { - string_reset(str_buff); + furi_string_reset(str_buff); subghz_history_get_text_item_menu(subghz->txrx->history, str_buff, i); subghz_view_receiver_add_item_to_menu( subghz->subghz_receiver, - string_get_cstr(str_buff), + furi_string_get_cstr(str_buff), subghz_history_get_type_protocol(subghz->txrx->history, i)); subghz->txrx->rx_key_state = SubGhzRxKeyStateAddKey; } - string_clear(str_buff); + furi_string_free(str_buff); subghz_scene_receiver_update_statusbar(subghz); subghz_view_receiver_set_callback( subghz->subghz_receiver, subghz_scene_receiver_callback, subghz); @@ -137,7 +137,7 @@ void subghz_scene_receiver_on_enter(void* context) { subghz_begin( subghz, subghz_setting_get_preset_data_by_name( - subghz->setting, string_get_cstr(subghz->txrx->preset->name))); + subghz->setting, furi_string_get_cstr(subghz->txrx->preset->name))); subghz_rx(subghz, subghz->txrx->preset->frequency); } subghz_view_receiver_set_idx_menu(subghz->subghz_receiver, subghz->txrx->idx_menu_chosen); diff --git a/applications/main/subghz/scenes/subghz_scene_receiver_config.c b/applications/main/subghz/scenes/subghz_scene_receiver_config.c index 541ec0e0d4b..5f022d6e153 100644 --- a/applications/main/subghz/scenes/subghz_scene_receiver_config.c +++ b/applications/main/subghz/scenes/subghz_scene_receiver_config.c @@ -191,7 +191,7 @@ void subghz_scene_receiver_config_on_enter(void* context) { subghz_scene_receiver_config_set_preset, subghz); value_index = subghz_scene_receiver_config_next_preset( - string_get_cstr(subghz->txrx->preset->name), subghz); + furi_string_get_cstr(subghz->txrx->preset->name), subghz); variable_item_set_current_value_index(item, value_index); variable_item_set_current_value_text( item, subghz_setting_get_preset_name(subghz->setting, value_index)); diff --git a/applications/main/subghz/scenes/subghz_scene_receiver_info.c b/applications/main/subghz/scenes/subghz_scene_receiver_info.c index 6f3e6fd1f9c..2e833edd193 100644 --- a/applications/main/subghz/scenes/subghz_scene_receiver_info.c +++ b/applications/main/subghz/scenes/subghz_scene_receiver_info.c @@ -32,7 +32,7 @@ static bool subghz_scene_receiver_info_update_parser(void* context) { subghz_history_get_preset_def(subghz->txrx->history, subghz->txrx->idx_menu_chosen); subghz_preset_init( subghz, - string_get_cstr(preset->name), + furi_string_get_cstr(preset->name), preset->frequency, preset->data, preset->data_size); @@ -47,13 +47,13 @@ void subghz_scene_receiver_info_on_enter(void* context) { DOLPHIN_DEED(DolphinDeedSubGhzReceiverInfo); if(subghz_scene_receiver_info_update_parser(subghz)) { - string_t frequency_str; - string_t modulation_str; - string_t text; + FuriString* frequency_str; + FuriString* modulation_str; + FuriString* text; - string_init(frequency_str); - string_init(modulation_str); - string_init(text); + frequency_str = furi_string_alloc(); + modulation_str = furi_string_alloc(); + text = furi_string_alloc(); subghz_get_frequency_modulation(subghz, frequency_str, modulation_str); widget_add_string_element( @@ -63,7 +63,7 @@ void subghz_scene_receiver_info_on_enter(void* context) { AlignLeft, AlignTop, FontSecondary, - string_get_cstr(frequency_str)); + furi_string_get_cstr(frequency_str)); widget_add_string_element( subghz->widget, @@ -72,14 +72,14 @@ void subghz_scene_receiver_info_on_enter(void* context) { AlignLeft, AlignTop, FontSecondary, - string_get_cstr(modulation_str)); + furi_string_get_cstr(modulation_str)); subghz_protocol_decoder_base_get_string(subghz->txrx->decoder_result, text); widget_add_string_multiline_element( - subghz->widget, 0, 0, AlignLeft, AlignTop, FontSecondary, string_get_cstr(text)); + subghz->widget, 0, 0, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(text)); - string_clear(frequency_str); - string_clear(modulation_str); - string_clear(text); + furi_string_free(frequency_str); + furi_string_free(modulation_str); + furi_string_free(text); if((subghz->txrx->decoder_result->protocol->flag & SubGhzProtocolFlag_Save) == SubGhzProtocolFlag_Save) { @@ -146,7 +146,7 @@ bool subghz_scene_receiver_info_on_event(void* context, SceneManagerEvent event) subghz_begin( subghz, subghz_setting_get_preset_data_by_name( - subghz->setting, string_get_cstr(subghz->txrx->preset->name))); + subghz->setting, furi_string_get_cstr(subghz->txrx->preset->name))); subghz_rx(subghz, subghz->txrx->preset->frequency); } if(subghz->txrx->hopper_state == SubGhzHopperStatePause) { diff --git a/applications/main/subghz/scenes/subghz_scene_rpc.c b/applications/main/subghz/scenes/subghz_scene_rpc.c index 652499d0576..1f06f4f9e41 100644 --- a/applications/main/subghz/scenes/subghz_scene_rpc.c +++ b/applications/main/subghz/scenes/subghz_scene_rpc.c @@ -61,20 +61,20 @@ bool subghz_scene_rpc_on_event(void* context, SceneManagerEvent event) { if(subghz_key_load(subghz, arg, false)) { scene_manager_set_scene_state( subghz->scene_manager, SubGhzSceneRpc, SubGhzRpcStateLoaded); - string_set_str(subghz->file_path, arg); + furi_string_set(subghz->file_path, arg); result = true; - string_t file_name; - string_init(file_name); + FuriString* file_name; + file_name = furi_string_alloc(); path_extract_filename(subghz->file_path, file_name, true); snprintf( subghz->file_name_tmp, SUBGHZ_MAX_LEN_NAME, "loaded\n%s", - string_get_cstr(file_name)); + furi_string_get_cstr(file_name)); popup_set_text(popup, subghz->file_name_tmp, 89, 44, AlignCenter, AlignTop); - string_clear(file_name); + furi_string_free(file_name); } } rpc_system_app_confirm(subghz->rpc_ctx, RpcAppEventLoadFile, result); diff --git a/applications/main/subghz/scenes/subghz_scene_save_name.c b/applications/main/subghz/scenes/subghz_scene_save_name.c index 12ec9868187..b9bb3636d75 100644 --- a/applications/main/subghz/scenes/subghz_scene_save_name.c +++ b/applications/main/subghz/scenes/subghz_scene_save_name.c @@ -1,5 +1,4 @@ #include "../subghz_i.h" -#include "m-string.h" #include "subghz/types.h" #include #include "../helpers/subghz_custom_event.h" @@ -21,21 +20,21 @@ void subghz_scene_save_name_on_enter(void* context) { TextInput* text_input = subghz->text_input; bool dev_name_empty = false; - string_t file_name; - string_t dir_name; - string_init(file_name); - string_init(dir_name); + FuriString* file_name; + FuriString* dir_name; + file_name = furi_string_alloc(); + dir_name = furi_string_alloc(); 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_str(file_name, file_name_buf); - string_set_str(subghz->file_path, SUBGHZ_APP_FOLDER); + furi_string_set(file_name, file_name_buf); + furi_string_set(subghz->file_path, SUBGHZ_APP_FOLDER); //highlighting the entire filename by default dev_name_empty = true; } else { - string_set(subghz->file_path_tmp, subghz->file_path); - path_extract_dirname(string_get_cstr(subghz->file_path), dir_name); + furi_string_set(subghz->file_path_tmp, subghz->file_path); + path_extract_dirname(furi_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) { @@ -46,10 +45,10 @@ void subghz_scene_save_name_on_enter(void* context) { } path_extract_filename(subghz->file_path, file_name, true); } - string_set(subghz->file_path, dir_name); + furi_string_set(subghz->file_path, dir_name); } - strncpy(subghz->file_name_tmp, string_get_cstr(file_name), SUBGHZ_MAX_LEN_NAME); + strncpy(subghz->file_name_tmp, furi_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, @@ -59,12 +58,12 @@ void subghz_scene_save_name_on_enter(void* context) { MAX_TEXT_INPUT_LEN, // buffer size dev_name_empty); - ValidatorIsFile* validator_is_file = - validator_is_file_alloc_init(string_get_cstr(subghz->file_path), SUBGHZ_APP_EXTENSION, ""); + ValidatorIsFile* validator_is_file = validator_is_file_alloc_init( + furi_string_get_cstr(subghz->file_path), SUBGHZ_APP_EXTENSION, ""); text_input_set_validator(text_input, validator_is_file_callback, validator_is_file); - string_clear(file_name); - string_clear(dir_name); + furi_string_free(file_name); + furi_string_free(dir_name); view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdTextInput); } @@ -75,14 +74,14 @@ bool subghz_scene_save_name_on_event(void* context, SceneManagerEvent event) { if(!strcmp(subghz->file_name_tmp, "") || scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAW) != SubGhzCustomEventManagerNoSet) { - string_set(subghz->file_path, subghz->file_path_tmp); + furi_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_name_tmp, "")) { - string_cat_printf( + furi_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)) { @@ -92,7 +91,9 @@ 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, string_get_cstr(subghz->file_path)); + subghz, + subghz->txrx->fff_data, + furi_string_get_cstr(subghz->file_path)); scene_manager_set_scene_state( subghz->scene_manager, SubGhzSceneSetType, @@ -102,14 +103,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), - string_get_cstr(subghz->file_path)); + furi_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, string_get_cstr(subghz->file_path)); + subghz->txrx->fff_data, furi_string_get_cstr(subghz->file_path)); scene_manager_set_scene_state( subghz->scene_manager, SubGhzSceneReadRAW, SubGhzCustomEventManagerNoSet); } else { @@ -119,7 +120,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_str(subghz->error_str, "No name file"); + furi_string_set(subghz->error_str, "No name file"); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowErrorSub); return true; } diff --git a/applications/main/subghz/scenes/subghz_scene_set_type.c b/applications/main/subghz/scenes/subghz_scene_set_type.c index 1a359542ef3..1e7a7474bcd 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_type.c +++ b/applications/main/subghz/scenes/subghz_scene_set_type.c @@ -27,7 +27,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_str(subghz->error_str, "Protocol not\nfound!"); + furi_string_set(subghz->error_str, "Protocol not\nfound!"); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowErrorSub); return false; } @@ -261,7 +261,7 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { } subghz_transmitter_free(subghz->txrx->transmitter); if(!generated_protocol) { - string_set_str( + furi_string_set( subghz->error_str, "Function requires\nan SD card with\nfresh databases."); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError); } @@ -285,7 +285,7 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { } subghz_transmitter_free(subghz->txrx->transmitter); if(!generated_protocol) { - string_set_str( + furi_string_set( subghz->error_str, "Function requires\nan SD card with\nfresh databases."); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError); } diff --git a/applications/main/subghz/scenes/subghz_scene_show_error.c b/applications/main/subghz/scenes/subghz_scene_show_error.c index 5632a859e8a..eadfb21146a 100644 --- a/applications/main/subghz/scenes/subghz_scene_show_error.c +++ b/applications/main/subghz/scenes/subghz_scene_show_error.c @@ -33,7 +33,7 @@ void subghz_scene_show_error_on_enter(void* context) { AlignCenter, AlignCenter, FontSecondary, - string_get_cstr(subghz->error_str)); + furi_string_get_cstr(subghz->error_str)); if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneShowError) == SubGhzCustomEventManagerSet) { widget_add_button_element( @@ -89,6 +89,6 @@ void subghz_scene_show_error_on_exit(void* context) { scene_manager_set_scene_state( subghz->scene_manager, SubGhzSceneShowError, SubGhzCustomEventManagerNoSet); widget_reset(subghz->widget); - string_reset(subghz->error_str); + furi_string_reset(subghz->error_str); notification_message(subghz->notifications, &sequence_reset_rgb); } diff --git a/applications/main/subghz/scenes/subghz_scene_show_error_sub.c b/applications/main/subghz/scenes/subghz_scene_show_error_sub.c index 74e034323e9..2720b2b94db 100644 --- a/applications/main/subghz/scenes/subghz_scene_show_error_sub.c +++ b/applications/main/subghz/scenes/subghz_scene_show_error_sub.c @@ -12,7 +12,7 @@ void subghz_scene_show_error_sub_on_enter(void* context) { // Setup view Popup* popup = subghz->popup; popup_set_icon(popup, 72, 17, &I_DolphinCommon_56x48); - popup_set_header(popup, string_get_cstr(subghz->error_str), 14, 15, AlignLeft, AlignTop); + popup_set_header(popup, furi_string_get_cstr(subghz->error_str), 14, 15, AlignLeft, AlignTop); popup_set_timeout(popup, 1500); popup_set_context(popup, subghz); popup_set_callback(popup, subghz_scene_show_error_sub_popup_callback); @@ -46,7 +46,7 @@ void subghz_scene_show_error_sub_on_exit(void* context) { popup_set_context(popup, NULL); popup_set_timeout(popup, 0); popup_disable_timeout(popup); - string_reset(subghz->error_str); + furi_string_reset(subghz->error_str); notification_message(subghz->notifications, &sequence_reset_rgb); } diff --git a/applications/main/subghz/scenes/subghz_scene_transmitter.c b/applications/main/subghz/scenes/subghz_scene_transmitter.c index b3f8d079911..5da6f4300e4 100644 --- a/applications/main/subghz/scenes/subghz_scene_transmitter.c +++ b/applications/main/subghz/scenes/subghz_scene_transmitter.c @@ -13,13 +13,13 @@ bool subghz_scene_transmitter_update_data_show(void* context) { SubGhz* subghz = context; if(subghz->txrx->decoder_result) { - string_t key_str; - string_t frequency_str; - string_t modulation_str; + FuriString* key_str; + FuriString* frequency_str; + FuriString* modulation_str; - string_init(key_str); - string_init(frequency_str); - string_init(modulation_str); + key_str = furi_string_alloc(); + frequency_str = furi_string_alloc(); + modulation_str = furi_string_alloc(); uint8_t show_button = 0; subghz_protocol_decoder_base_deserialize( @@ -34,14 +34,14 @@ bool subghz_scene_transmitter_update_data_show(void* context) { subghz_get_frequency_modulation(subghz, frequency_str, modulation_str); subghz_view_transmitter_add_data_to_show( subghz->subghz_transmitter, - string_get_cstr(key_str), - string_get_cstr(frequency_str), - string_get_cstr(modulation_str), + furi_string_get_cstr(key_str), + furi_string_get_cstr(frequency_str), + furi_string_get_cstr(modulation_str), show_button); - string_clear(frequency_str); - string_clear(modulation_str); - string_clear(key_str); + furi_string_free(frequency_str); + furi_string_free(modulation_str); + furi_string_free(key_str); return true; } @@ -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_str(subghz->error_str, "Protocol not\nfound!"); + furi_string_set(subghz->error_str, "Protocol not\nfound!"); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowErrorSub); } } else if(event.type == SceneManagerEventTypeTick) { diff --git a/applications/main/subghz/subghz.c b/applications/main/subghz/subghz.c index 61960218b32..71c6bc2cd42 100644 --- a/applications/main/subghz/subghz.c +++ b/applications/main/subghz/subghz.c @@ -1,6 +1,5 @@ /* Abandon hope, all ye who enter here. */ -#include "m-string.h" #include "subghz/types.h" #include "subghz_i.h" #include @@ -62,8 +61,8 @@ void subghz_blink_stop(SubGhz* instance) { SubGhz* subghz_alloc() { SubGhz* subghz = malloc(sizeof(SubGhz)); - string_init(subghz->file_path); - string_init(subghz->file_path_tmp); + subghz->file_path = furi_string_alloc(); + subghz->file_path_tmp = furi_string_alloc(); // GUI subghz->gui = furi_record_open(RECORD_GUI); @@ -171,7 +170,7 @@ SubGhz* subghz_alloc() { subghz->lock = SubGhzLockOff; subghz->txrx = malloc(sizeof(SubGhzTxRx)); subghz->txrx->preset = malloc(sizeof(SubGhzPresetDefinition)); - string_init(subghz->txrx->preset->name); + subghz->txrx->preset->name = furi_string_alloc(); subghz_preset_init( subghz, "AM650", subghz_setting_get_default_frequency(subghz->setting), NULL, 0); @@ -197,7 +196,7 @@ SubGhz* subghz_alloc() { subghz_worker_set_context(subghz->txrx->worker, subghz->txrx->receiver); //Init Error_str - string_init(subghz->error_str); + subghz->error_str = furi_string_alloc(); return subghz; } @@ -282,20 +281,20 @@ void subghz_free(SubGhz* subghz) { subghz_worker_free(subghz->txrx->worker); flipper_format_free(subghz->txrx->fff_data); subghz_history_free(subghz->txrx->history); - string_clear(subghz->txrx->preset->name); + furi_string_free(subghz->txrx->preset->name); free(subghz->txrx->preset); free(subghz->txrx); //Error string - string_clear(subghz->error_str); + furi_string_free(subghz->error_str); // Notifications furi_record_close(RECORD_NOTIFICATION); subghz->notifications = NULL; // Path strings - string_clear(subghz->file_path); - string_clear(subghz->file_path_tmp); + furi_string_free(subghz->file_path); + furi_string_free(subghz->file_path_tmp); // The rest free(subghz); @@ -329,7 +328,7 @@ int32_t subghz_app(void* p) { view_dispatcher_attach_to_gui( subghz->view_dispatcher, subghz->gui, ViewDispatcherTypeFullscreen); if(subghz_key_load(subghz, p, true)) { - string_set_str(subghz->file_path, p); + furi_string_set(subghz->file_path, (const char*)p); if((!strcmp(subghz->txrx->decoder_result->protocol->name, "RAW"))) { //Load Raw TX @@ -348,13 +347,13 @@ int32_t subghz_app(void* p) { } else { view_dispatcher_attach_to_gui( subghz->view_dispatcher, subghz->gui, ViewDispatcherTypeFullscreen); - string_set_str(subghz->file_path, SUBGHZ_APP_FOLDER); + furi_string_set(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_str( + furi_string_set( 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/main/subghz/subghz_cli.c b/applications/main/subghz/subghz_cli.c index d921979f0d2..c6e196fb669 100644 --- a/applications/main/subghz/subghz_cli.c +++ b/applications/main/subghz/subghz_cli.c @@ -24,15 +24,15 @@ #define SUBGHZ_REGION_FILENAME "/int/.region_data" -void subghz_cli_command_tx_carrier(Cli* cli, string_t args, void* context) { +void subghz_cli_command_tx_carrier(Cli* cli, FuriString* args, void* context) { UNUSED(context); uint32_t frequency = 433920000; - if(string_size(args)) { - int ret = sscanf(string_get_cstr(args), "%lu", &frequency); + if(furi_string_size(args)) { + int ret = sscanf(furi_string_get_cstr(args), "%lu", &frequency); if(ret != 1) { printf("sscanf returned %d, frequency: %lu\r\n", ret, frequency); - cli_print_usage("subghz tx_carrier", "", string_get_cstr(args)); + cli_print_usage("subghz tx_carrier", "", furi_string_get_cstr(args)); return; } if(!furi_hal_subghz_is_frequency_valid(frequency)) { @@ -68,15 +68,15 @@ void subghz_cli_command_tx_carrier(Cli* cli, string_t args, void* context) { furi_hal_power_suppress_charge_exit(); } -void subghz_cli_command_rx_carrier(Cli* cli, string_t args, void* context) { +void subghz_cli_command_rx_carrier(Cli* cli, FuriString* args, void* context) { UNUSED(context); uint32_t frequency = 433920000; - if(string_size(args)) { - int ret = sscanf(string_get_cstr(args), "%lu", &frequency); + if(furi_string_size(args)) { + int ret = sscanf(furi_string_get_cstr(args), "%lu", &frequency); if(ret != 1) { printf("sscanf returned %d, frequency: %lu\r\n", ret, frequency); - cli_print_usage("subghz rx_carrier", "", string_get_cstr(args)); + cli_print_usage("subghz rx_carrier", "", furi_string_get_cstr(args)); return; } if(!furi_hal_subghz_is_frequency_valid(frequency)) { @@ -109,15 +109,16 @@ void subghz_cli_command_rx_carrier(Cli* cli, string_t args, void* context) { furi_hal_subghz_sleep(); } -void subghz_cli_command_tx(Cli* cli, string_t args, void* context) { +void subghz_cli_command_tx(Cli* cli, FuriString* args, void* context) { UNUSED(context); uint32_t frequency = 433920000; uint32_t key = 0x0074BADE; uint32_t repeat = 10; uint32_t te = 403; - if(string_size(args)) { - int ret = sscanf(string_get_cstr(args), "%lx %lu %lu %lu", &key, &frequency, &te, &repeat); + if(furi_string_size(args)) { + int ret = + sscanf(furi_string_get_cstr(args), "%lx %lu %lu %lu", &key, &frequency, &te, &repeat); if(ret != 4) { printf( "sscanf returned %d, key: %lx, frequency: %lu, te:%lu, repeat: %lu\r\n", @@ -129,7 +130,7 @@ void subghz_cli_command_tx(Cli* cli, string_t args, void* context) { cli_print_usage( "subghz tx", "<3 Byte Key: in hex> ", - string_get_cstr(args)); + furi_string_get_cstr(args)); return; } if(!furi_hal_subghz_is_frequency_valid(frequency)) { @@ -147,9 +148,7 @@ void subghz_cli_command_tx(Cli* cli, string_t args, void* context) { te, repeat); - string_t flipper_format_string; - string_init_printf( - flipper_format_string, + FuriString* flipper_format_string = furi_string_alloc_printf( "Protocol: Princeton\n" "Bit: 24\n" "Key: 00 00 00 00 00 %02X %02X %02X\n" @@ -163,7 +162,7 @@ void subghz_cli_command_tx(Cli* cli, string_t args, void* context) { FlipperFormat* flipper_format = flipper_format_string_alloc(); Stream* stream = flipper_format_get_raw_stream(flipper_format); stream_clean(stream); - stream_write_cstring(stream, string_get_cstr(flipper_format_string)); + stream_write_cstring(stream, furi_string_get_cstr(flipper_format_string)); SubGhzEnvironment* environment = subghz_environment_alloc(); @@ -221,23 +220,23 @@ static void subghz_cli_command_rx_callback( SubGhzCliCommandRx* instance = context; instance->packet_count++; - string_t text; - string_init(text); + FuriString* text; + text = furi_string_alloc(); subghz_protocol_decoder_base_get_string(decoder_base, text); subghz_receiver_reset(receiver); - printf("%s", string_get_cstr(text)); - string_clear(text); + printf("%s", furi_string_get_cstr(text)); + furi_string_free(text); } -void subghz_cli_command_rx(Cli* cli, string_t args, void* context) { +void subghz_cli_command_rx(Cli* cli, FuriString* args, void* context) { UNUSED(context); uint32_t frequency = 433920000; - if(string_size(args)) { - int ret = sscanf(string_get_cstr(args), "%lu", &frequency); + if(furi_string_size(args)) { + int ret = sscanf(furi_string_get_cstr(args), "%lu", &frequency); if(ret != 1) { printf("sscanf returned %d, frequency: %lu\r\n", ret, frequency); - cli_print_usage("subghz rx", "", string_get_cstr(args)); + cli_print_usage("subghz rx", "", furi_string_get_cstr(args)); return; } if(!furi_hal_subghz_is_frequency_valid(frequency)) { @@ -309,32 +308,32 @@ void subghz_cli_command_rx(Cli* cli, string_t args, void* context) { free(instance); } -void subghz_cli_command_decode_raw(Cli* cli, string_t args, void* context) { +void subghz_cli_command_decode_raw(Cli* cli, FuriString* args, void* context) { UNUSED(context); - string_t file_name; - string_init(file_name); - string_set_str(file_name, ANY_PATH("subghz/test.sub")); + FuriString* file_name; + file_name = furi_string_alloc(); + furi_string_set(file_name, ANY_PATH("subghz/test.sub")); Storage* storage = furi_record_open(RECORD_STORAGE); FlipperFormat* fff_data_file = flipper_format_file_alloc(storage); - string_t temp_str; - string_init(temp_str); + FuriString* temp_str; + temp_str = furi_string_alloc(); uint32_t temp_data32; bool check_file = false; do { - if(string_size(args)) { + if(furi_string_size(args)) { if(!args_read_string_and_trim(args, file_name)) { cli_print_usage( - "subghz decode_raw", "", string_get_cstr(args)); + "subghz decode_raw", "", furi_string_get_cstr(args)); break; } } - if(!flipper_format_file_open_existing(fff_data_file, string_get_cstr(file_name))) { + if(!flipper_format_file_open_existing(fff_data_file, furi_string_get_cstr(file_name))) { printf( "subghz decode_raw \033[0;31mError open file\033[0m %s\r\n", - string_get_cstr(file_name)); + furi_string_get_cstr(file_name)); break; } @@ -343,7 +342,7 @@ void subghz_cli_command_decode_raw(Cli* cli, string_t args, void* context) { break; } - if(!strcmp(string_get_cstr(temp_str), SUBGHZ_RAW_FILE_TYPE) && + if(!strcmp(furi_string_get_cstr(temp_str), SUBGHZ_RAW_FILE_TYPE) && temp_data32 == SUBGHZ_KEY_FILE_VERSION) { } else { printf("subghz decode_raw \033[0;31mType or version mismatch\033[0m\r\n"); @@ -353,7 +352,7 @@ void subghz_cli_command_decode_raw(Cli* cli, string_t args, void* context) { check_file = true; } while(false); - string_clear(temp_str); + furi_string_free(temp_str); flipper_format_free(fff_data_file); furi_record_close(RECORD_STORAGE); @@ -385,14 +384,14 @@ void subghz_cli_command_decode_raw(Cli* cli, string_t args, void* context) { subghz_receiver_set_rx_callback(receiver, subghz_cli_command_rx_callback, instance); SubGhzFileEncoderWorker* file_worker_encoder = subghz_file_encoder_worker_alloc(); - if(subghz_file_encoder_worker_start(file_worker_encoder, string_get_cstr(file_name))) { + if(subghz_file_encoder_worker_start(file_worker_encoder, furi_string_get_cstr(file_name))) { //the worker needs a file in order to open and read part of the file furi_delay_ms(100); } printf( "Listening at \033[0;33m%s\033[0m.\r\n\r\nPress CTRL+C to stop\r\n\r\n", - string_get_cstr(file_name)); + furi_string_get_cstr(file_name)); LevelDuration level_duration; while(!cli_cmd_interrupt_received(cli)) { @@ -419,7 +418,7 @@ void subghz_cli_command_decode_raw(Cli* cli, string_t args, void* context) { subghz_file_encoder_worker_free(file_worker_encoder); free(instance); } - string_clear(file_name); + furi_string_free(file_name); } static void subghz_cli_command_print_usage() { @@ -445,14 +444,14 @@ static void subghz_cli_command_print_usage() { } } -static void subghz_cli_command_encrypt_keeloq(Cli* cli, string_t args) { +static void subghz_cli_command_encrypt_keeloq(Cli* cli, FuriString* args) { UNUSED(cli); uint8_t iv[16]; - string_t source; - string_t destination; - string_init(source); - string_init(destination); + FuriString* source; + FuriString* destination; + source = furi_string_alloc(); + destination = furi_string_alloc(); SubGhzKeystore* keystore = subghz_keystore_alloc(); @@ -472,30 +471,30 @@ static void subghz_cli_command_encrypt_keeloq(Cli* cli, string_t args) { break; } - if(!subghz_keystore_load(keystore, string_get_cstr(source))) { + if(!subghz_keystore_load(keystore, furi_string_get_cstr(source))) { printf("Failed to load Keystore"); break; } - if(!subghz_keystore_save(keystore, string_get_cstr(destination), iv)) { + if(!subghz_keystore_save(keystore, furi_string_get_cstr(destination), iv)) { printf("Failed to save Keystore"); break; } } while(false); subghz_keystore_free(keystore); - string_clear(destination); - string_clear(source); + furi_string_free(destination); + furi_string_free(source); } -static void subghz_cli_command_encrypt_raw(Cli* cli, string_t args) { +static void subghz_cli_command_encrypt_raw(Cli* cli, FuriString* args) { UNUSED(cli); uint8_t iv[16]; - string_t source; - string_t destination; - string_init(source); - string_init(destination); + FuriString* source; + FuriString* destination; + source = furi_string_alloc(); + destination = furi_string_alloc(); do { if(!args_read_string_and_trim(args, source)) { @@ -514,25 +513,25 @@ static void subghz_cli_command_encrypt_raw(Cli* cli, string_t args) { } if(!subghz_keystore_raw_encrypted_save( - string_get_cstr(source), string_get_cstr(destination), iv)) { + furi_string_get_cstr(source), furi_string_get_cstr(destination), iv)) { printf("Failed to save Keystore"); break; } } while(false); - string_clear(destination); - string_clear(source); + furi_string_free(destination); + furi_string_free(source); } -static void subghz_cli_command_chat(Cli* cli, string_t args) { +static void subghz_cli_command_chat(Cli* cli, FuriString* args) { uint32_t frequency = 433920000; - if(string_size(args)) { - int ret = sscanf(string_get_cstr(args), "%lu", &frequency); + if(furi_string_size(args)) { + int ret = sscanf(furi_string_get_cstr(args), "%lu", &frequency); if(ret != 1) { printf("sscanf returned %d, frequency: %lu\r\n", ret, frequency); - cli_print_usage("subghz chat", "", string_get_cstr(args)); + cli_print_usage("subghz chat", "", furi_string_get_cstr(args)); return; } if(!furi_hal_subghz_is_frequency_valid(frequency)) { @@ -568,22 +567,22 @@ static void subghz_cli_command_chat(Cli* cli, string_t args) { size_t message_max_len = 64; uint8_t message[64] = {0}; - string_t input; - string_init(input); - string_t name; - string_init(name); - string_t output; - string_init(output); - string_t sysmsg; - string_init(sysmsg); + FuriString* input; + input = furi_string_alloc(); + FuriString* name; + name = furi_string_alloc(); + FuriString* output; + output = furi_string_alloc(); + FuriString* sysmsg; + sysmsg = furi_string_alloc(); bool exit = false; SubGhzChatEvent chat_event; NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); - string_printf(name, "\033[0;33m%s\033[0m: ", furi_hal_version_get_name_ptr()); - string_set(input, name); - printf("%s", string_get_cstr(input)); + furi_string_printf(name, "\033[0;33m%s\033[0m: ", furi_hal_version_get_name_ptr()); + furi_string_set(input, name); + printf("%s", furi_string_get_cstr(input)); fflush(stdout); while(!exit) { @@ -597,63 +596,63 @@ static void subghz_cli_command_chat(Cli* cli, string_t args) { break; } else if( (chat_event.c == CliSymbolAsciiBackspace) || (chat_event.c == CliSymbolAsciiDel)) { - size_t len = string_length_u(input); - if(len > string_length_u(name)) { + size_t len = furi_string_utf8_length(input); + if(len > furi_string_utf8_length(name)) { printf("%s", "\e[D\e[1P"); fflush(stdout); //delete 1 char UTF - const char* str = string_get_cstr(input); + const char* str = furi_string_get_cstr(input); size_t size = 0; - m_str1ng_utf8_state_e s = M_STRING_UTF8_STARTING; - string_unicode_t u = 0; - string_reset(sysmsg); + FuriStringUTF8State s = FuriStringUTF8StateStarting; + FuriStringUnicodeValue u = 0; + furi_string_reset(sysmsg); while(*str) { - m_str1ng_utf8_decode(*str, &s, &u); - if((s == M_STRING_UTF8_ERROR) || s == M_STRING_UTF8_STARTING) { - string_push_u(sysmsg, u); + furi_string_utf8_decode(*str, &s, &u); + if((s == FuriStringUTF8StateError) || s == FuriStringUTF8StateStarting) { + furi_string_utf8_push(sysmsg, u); if(++size >= len - 1) break; - s = M_STRING_UTF8_STARTING; + s = FuriStringUTF8StateStarting; } str++; } - string_set(input, sysmsg); + furi_string_set(input, sysmsg); } } else if(chat_event.c == CliSymbolAsciiCR) { printf("\r\n"); - string_push_back(input, '\r'); - string_push_back(input, '\n'); + furi_string_push_back(input, '\r'); + furi_string_push_back(input, '\n'); while(!subghz_chat_worker_write( subghz_chat, - (uint8_t*)string_get_cstr(input), - strlen(string_get_cstr(input)))) { + (uint8_t*)furi_string_get_cstr(input), + strlen(furi_string_get_cstr(input)))) { furi_delay_ms(10); } - string_printf(input, "%s", string_get_cstr(name)); - printf("%s", string_get_cstr(input)); + furi_string_printf(input, "%s", furi_string_get_cstr(name)); + printf("%s", furi_string_get_cstr(input)); fflush(stdout); } else if(chat_event.c == CliSymbolAsciiLF) { //cut out the symbol \n } else { putc(chat_event.c, stdout); fflush(stdout); - string_push_back(input, chat_event.c); + furi_string_push_back(input, chat_event.c); break; case SubGhzChatEventRXData: do { memset(message, 0x00, message_max_len); size_t len = subghz_chat_worker_read(subghz_chat, message, message_max_len); for(size_t i = 0; i < len; i++) { - string_push_back(output, message[i]); + furi_string_push_back(output, message[i]); if(message[i] == '\n') { printf("\r"); for(uint8_t i = 0; i < 80; i++) { printf(" "); } - printf("\r %s", string_get_cstr(output)); - printf("%s", string_get_cstr(input)); + printf("\r %s", furi_string_get_cstr(output)); + printf("%s", furi_string_get_cstr(input)); fflush(stdout); - string_reset(output); + furi_string_reset(output); } } } while(subghz_chat_worker_available(subghz_chat)); @@ -662,22 +661,22 @@ static void subghz_cli_command_chat(Cli* cli, string_t args) { notification_message(notification, &sequence_single_vibro); break; case SubGhzChatEventUserEntrance: - string_printf( + furi_string_printf( sysmsg, "\033[0;34m%s joined chat.\033[0m\r\n", furi_hal_version_get_name_ptr()); subghz_chat_worker_write( subghz_chat, - (uint8_t*)string_get_cstr(sysmsg), - strlen(string_get_cstr(sysmsg))); + (uint8_t*)furi_string_get_cstr(sysmsg), + strlen(furi_string_get_cstr(sysmsg))); break; case SubGhzChatEventUserExit: - string_printf( + furi_string_printf( sysmsg, "\033[0;31m%s left chat.\033[0m\r\n", furi_hal_version_get_name_ptr()); subghz_chat_worker_write( subghz_chat, - (uint8_t*)string_get_cstr(sysmsg), - strlen(string_get_cstr(sysmsg))); + (uint8_t*)furi_string_get_cstr(sysmsg), + strlen(furi_string_get_cstr(sysmsg))); furi_delay_ms(10); exit = true; break; @@ -693,10 +692,10 @@ static void subghz_cli_command_chat(Cli* cli, string_t args) { } } - string_clear(input); - string_clear(name); - string_clear(output); - string_clear(sysmsg); + furi_string_free(input); + furi_string_free(name); + furi_string_free(output); + furi_string_free(sysmsg); furi_hal_power_suppress_charge_exit(); furi_record_close(RECORD_NOTIFICATION); @@ -707,9 +706,9 @@ static void subghz_cli_command_chat(Cli* cli, string_t args) { printf("\r\nExit chat\r\n"); } -static void subghz_cli_command(Cli* cli, string_t args, void* context) { - string_t cmd; - string_init(cmd); +static void subghz_cli_command(Cli* cli, FuriString* args, void* context) { + FuriString* cmd; + cmd = furi_string_alloc(); do { if(!args_read_string_and_trim(args, cmd)) { @@ -717,43 +716,43 @@ static void subghz_cli_command(Cli* cli, string_t args, void* context) { break; } - if(string_cmp_str(cmd, "chat") == 0) { + if(furi_string_cmp_str(cmd, "chat") == 0) { subghz_cli_command_chat(cli, args); break; } - if(string_cmp_str(cmd, "tx") == 0) { + if(furi_string_cmp_str(cmd, "tx") == 0) { subghz_cli_command_tx(cli, args, context); break; } - if(string_cmp_str(cmd, "rx") == 0) { + if(furi_string_cmp_str(cmd, "rx") == 0) { subghz_cli_command_rx(cli, args, context); break; } - if(string_cmp_str(cmd, "decode_raw") == 0) { + if(furi_string_cmp_str(cmd, "decode_raw") == 0) { subghz_cli_command_decode_raw(cli, args, context); break; } if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - if(string_cmp_str(cmd, "encrypt_keeloq") == 0) { + if(furi_string_cmp_str(cmd, "encrypt_keeloq") == 0) { subghz_cli_command_encrypt_keeloq(cli, args); break; } - if(string_cmp_str(cmd, "encrypt_raw") == 0) { + if(furi_string_cmp_str(cmd, "encrypt_raw") == 0) { subghz_cli_command_encrypt_raw(cli, args); break; } - if(string_cmp_str(cmd, "tx_carrier") == 0) { + if(furi_string_cmp_str(cmd, "tx_carrier") == 0) { subghz_cli_command_tx_carrier(cli, args, context); break; } - if(string_cmp_str(cmd, "rx_carrier") == 0) { + if(furi_string_cmp_str(cmd, "rx_carrier") == 0) { subghz_cli_command_rx_carrier(cli, args, context); break; } @@ -762,7 +761,7 @@ static void subghz_cli_command(Cli* cli, string_t args, void* context) { subghz_cli_command_print_usage(); } while(false); - string_clear(cmd); + furi_string_free(cmd); } static bool diff --git a/applications/main/subghz/subghz_history.c b/applications/main/subghz/subghz_history.c index 820a13d1442..0f67b4fc9f4 100644 --- a/applications/main/subghz/subghz_history.c +++ b/applications/main/subghz/subghz_history.c @@ -3,13 +3,12 @@ #include #include -#include #define SUBGHZ_HISTORY_MAX 50 #define TAG "SubGhzHistory" typedef struct { - string_t item_str; + FuriString* item_str; FlipperFormat* flipper_string; uint8_t type; SubGhzPresetDefinition* preset; @@ -27,13 +26,13 @@ struct SubGhzHistory { uint32_t last_update_timestamp; uint16_t last_index_write; uint8_t code_last_hash_data; - string_t tmp_string; + FuriString* tmp_string; SubGhzHistoryStruct* history; }; SubGhzHistory* subghz_history_alloc(void) { SubGhzHistory* instance = malloc(sizeof(SubGhzHistory)); - string_init(instance->tmp_string); + instance->tmp_string = furi_string_alloc(); instance->history = malloc(sizeof(SubGhzHistoryStruct)); SubGhzHistoryItemArray_init(instance->history->data); return instance; @@ -41,11 +40,11 @@ SubGhzHistory* subghz_history_alloc(void) { void subghz_history_free(SubGhzHistory* instance) { furi_assert(instance); - string_clear(instance->tmp_string); + furi_string_free(instance->tmp_string); for M_EACH(item, instance->history->data, SubGhzHistoryItemArray_t) { - string_clear(item->item_str); - string_clear(item->preset->name); + furi_string_free(item->item_str); + furi_string_free(item->preset->name); free(item->preset); flipper_format_free(item->flipper_string); item->type = 0; @@ -70,16 +69,16 @@ SubGhzPresetDefinition* subghz_history_get_preset_def(SubGhzHistory* instance, u const char* subghz_history_get_preset(SubGhzHistory* instance, uint16_t idx) { furi_assert(instance); SubGhzHistoryItem* item = SubGhzHistoryItemArray_get(instance->history->data, idx); - return string_get_cstr(item->preset->name); + return furi_string_get_cstr(item->preset->name); } void subghz_history_reset(SubGhzHistory* instance) { furi_assert(instance); - string_reset(instance->tmp_string); + furi_string_reset(instance->tmp_string); for M_EACH(item, instance->history->data, SubGhzHistoryItemArray_t) { - string_clear(item->item_str); - string_clear(item->preset->name); + furi_string_free(item->item_str); + furi_string_free(item->preset->name); free(item->preset); flipper_format_free(item->flipper_string); item->type = 0; @@ -106,9 +105,9 @@ const char* subghz_history_get_protocol_name(SubGhzHistory* instance, uint16_t i flipper_format_rewind(item->flipper_string); if(!flipper_format_read_string(item->flipper_string, "Protocol", instance->tmp_string)) { FURI_LOG_E(TAG, "Missing Protocol"); - string_reset(instance->tmp_string); + furi_string_reset(instance->tmp_string); } - return string_get_cstr(instance->tmp_string); + return furi_string_get_cstr(instance->tmp_string); } FlipperFormat* subghz_history_get_raw_data(SubGhzHistory* instance, uint16_t idx) { @@ -120,20 +119,20 @@ FlipperFormat* subghz_history_get_raw_data(SubGhzHistory* instance, uint16_t idx return NULL; } } -bool subghz_history_get_text_space_left(SubGhzHistory* instance, string_t output) { +bool subghz_history_get_text_space_left(SubGhzHistory* instance, FuriString* output) { furi_assert(instance); if(instance->last_index_write == SUBGHZ_HISTORY_MAX) { - if(output != NULL) string_printf(output, "Memory is FULL"); + if(output != NULL) furi_string_printf(output, "Memory is FULL"); return true; } if(output != NULL) - string_printf(output, "%02u/%02u", instance->last_index_write, SUBGHZ_HISTORY_MAX); + furi_string_printf(output, "%02u/%02u", instance->last_index_write, SUBGHZ_HISTORY_MAX); return false; } -void subghz_history_get_text_item_menu(SubGhzHistory* instance, string_t output, uint16_t idx) { +void subghz_history_get_text_item_menu(SubGhzHistory* instance, FuriString* output, uint16_t idx) { SubGhzHistoryItem* item = SubGhzHistoryItemArray_get(instance->history->data, idx); - string_set(output, item->item_str); + furi_string_set(output, item->item_str); } bool subghz_history_add_to_history( @@ -156,18 +155,18 @@ bool subghz_history_add_to_history( instance->code_last_hash_data = subghz_protocol_decoder_base_get_hash_data(decoder_base); instance->last_update_timestamp = furi_get_tick(); - string_t text; - string_init(text); + FuriString* text; + text = furi_string_alloc(); SubGhzHistoryItem* item = SubGhzHistoryItemArray_push_raw(instance->history->data); item->preset = malloc(sizeof(SubGhzPresetDefinition)); item->type = decoder_base->protocol->type; item->preset->frequency = preset->frequency; - string_init(item->preset->name); - string_set(item->preset->name, preset->name); + item->preset->name = furi_string_alloc(); + furi_string_set(item->preset->name, preset->name); item->preset->data = preset->data; item->preset->data_size = preset->data_size; - string_init(item->item_str); + item->item_str = furi_string_alloc(); item->flipper_string = flipper_format_string_alloc(); subghz_protocol_decoder_base_serialize(decoder_base, item->flipper_string, preset); @@ -180,20 +179,20 @@ bool subghz_history_add_to_history( FURI_LOG_E(TAG, "Missing Protocol"); break; } - if(!strcmp(string_get_cstr(instance->tmp_string), "KeeLoq")) { - string_set_str(instance->tmp_string, "KL "); + if(!strcmp(furi_string_get_cstr(instance->tmp_string), "KeeLoq")) { + furi_string_set(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_str(instance->tmp_string, "SL "); + furi_string_cat(instance->tmp_string, text); + } else if(!strcmp(furi_string_get_cstr(instance->tmp_string), "Star Line")) { + furi_string_set(instance->tmp_string, "SL "); if(!flipper_format_read_string(item->flipper_string, "Manufacture", text)) { FURI_LOG_E(TAG, "Missing Protocol"); break; } - string_cat(instance->tmp_string, text); + furi_string_cat(instance->tmp_string, text); } if(!flipper_format_rewind(item->flipper_string)) { FURI_LOG_E(TAG, "Rewind error"); @@ -209,22 +208,22 @@ bool subghz_history_add_to_history( data = (data << 8) | key_data[i]; } if(!(uint32_t)(data >> 32)) { - string_printf( + furi_string_printf( item->item_str, "%s %lX", - string_get_cstr(instance->tmp_string), + furi_string_get_cstr(instance->tmp_string), (uint32_t)(data & 0xFFFFFFFF)); } else { - string_printf( + furi_string_printf( item->item_str, "%s %lX%08lX", - string_get_cstr(instance->tmp_string), + furi_string_get_cstr(instance->tmp_string), (uint32_t)(data >> 32), (uint32_t)(data & 0xFFFFFFFF)); } } while(false); - string_clear(text); + furi_string_free(text); instance->last_index_write++; return true; } diff --git a/applications/main/subghz/subghz_history.h b/applications/main/subghz/subghz_history.h index adbcfc18aaa..7bff3df5ef0 100644 --- a/applications/main/subghz/subghz_history.h +++ b/applications/main/subghz/subghz_history.h @@ -71,18 +71,18 @@ const char* subghz_history_get_protocol_name(SubGhzHistory* instance, uint16_t i /** Get string item menu to history[idx] * * @param instance - SubGhzHistory instance - * @param output - string_t output + * @param output - FuriString* output * @param idx - record index */ -void subghz_history_get_text_item_menu(SubGhzHistory* instance, string_t output, uint16_t idx); +void subghz_history_get_text_item_menu(SubGhzHistory* instance, FuriString* output, uint16_t idx); /** Get string the remaining number of records to history * * @param instance - SubGhzHistory instance - * @param output - string_t output + * @param output - FuriString* output * @return bool - is FUUL */ -bool subghz_history_get_text_space_left(SubGhzHistory* instance, string_t output); +bool subghz_history_get_text_space_left(SubGhzHistory* instance, FuriString* output); /** Add protocol to history * diff --git a/applications/main/subghz/subghz_i.c b/applications/main/subghz/subghz_i.c index 6ed8fd5399d..f2658c0e48b 100644 --- a/applications/main/subghz/subghz_i.c +++ b/applications/main/subghz/subghz_i.c @@ -26,7 +26,7 @@ void subghz_preset_init( size_t preset_data_size) { furi_assert(context); SubGhz* subghz = context; - string_set(subghz->txrx->preset->name, preset_name); + furi_string_set(subghz->txrx->preset->name, preset_name); subghz->txrx->preset->frequency = frequency; subghz->txrx->preset->data = preset_data; subghz->txrx->preset->data_size = preset_data_size; @@ -34,15 +34,15 @@ void subghz_preset_init( bool subghz_set_preset(SubGhz* subghz, const char* preset) { if(!strcmp(preset, "FuriHalSubGhzPresetOok270Async")) { - string_set(subghz->txrx->preset->name, "AM270"); + furi_string_set(subghz->txrx->preset->name, "AM270"); } else if(!strcmp(preset, "FuriHalSubGhzPresetOok650Async")) { - string_set(subghz->txrx->preset->name, "AM650"); + furi_string_set(subghz->txrx->preset->name, "AM650"); } else if(!strcmp(preset, "FuriHalSubGhzPreset2FSKDev238Async")) { - string_set(subghz->txrx->preset->name, "FM238"); + furi_string_set(subghz->txrx->preset->name, "FM238"); } else if(!strcmp(preset, "FuriHalSubGhzPreset2FSKDev476Async")) { - string_set(subghz->txrx->preset->name, "FM476"); + furi_string_set(subghz->txrx->preset->name, "FM476"); } else if(!strcmp(preset, "FuriHalSubGhzPresetCustom")) { - string_set(subghz->txrx->preset->name, "CUSTOM"); + furi_string_set(subghz->txrx->preset->name, "CUSTOM"); } else { FURI_LOG_E(TAG, "Unknown preset"); return false; @@ -50,17 +50,17 @@ bool subghz_set_preset(SubGhz* subghz, const char* preset) { return true; } -void subghz_get_frequency_modulation(SubGhz* subghz, string_t frequency, string_t modulation) { +void subghz_get_frequency_modulation(SubGhz* subghz, FuriString* frequency, FuriString* modulation) { furi_assert(subghz); if(frequency != NULL) { - string_printf( + furi_string_printf( frequency, "%03ld.%02ld", subghz->txrx->preset->frequency / 1000000 % 1000, subghz->txrx->preset->frequency / 10000 % 100); } if(modulation != NULL) { - string_printf(modulation, "%0.2s", string_get_cstr(subghz->txrx->preset->name)); + furi_string_printf(modulation, "%0.2s", furi_string_get_cstr(subghz->txrx->preset->name)); } } @@ -137,8 +137,8 @@ bool subghz_tx_start(SubGhz* subghz, FlipperFormat* flipper_format) { furi_assert(subghz); bool ret = false; - string_t temp_str; - string_init(temp_str); + FuriString* temp_str; + temp_str = furi_string_alloc(); uint32_t repeat = 200; do { if(!flipper_format_rewind(flipper_format)) { @@ -155,21 +155,21 @@ bool subghz_tx_start(SubGhz* subghz, FlipperFormat* flipper_format) { break; } - subghz->txrx->transmitter = - subghz_transmitter_alloc_init(subghz->txrx->environment, string_get_cstr(temp_str)); + subghz->txrx->transmitter = subghz_transmitter_alloc_init( + subghz->txrx->environment, furi_string_get_cstr(temp_str)); if(subghz->txrx->transmitter) { if(subghz_transmitter_deserialize(subghz->txrx->transmitter, flipper_format)) { - if(strcmp(string_get_cstr(subghz->txrx->preset->name), "")) { + if(strcmp(furi_string_get_cstr(subghz->txrx->preset->name), "")) { subghz_begin( subghz, subghz_setting_get_preset_data_by_name( - subghz->setting, string_get_cstr(subghz->txrx->preset->name))); + subghz->setting, furi_string_get_cstr(subghz->txrx->preset->name))); } else { FURI_LOG_E( TAG, "Unknown name preset \" %s \"", - string_get_cstr(subghz->txrx->preset->name)); + furi_string_get_cstr(subghz->txrx->preset->name)); subghz_begin( subghz, subghz_setting_get_preset_data_by_name(subghz->setting, "AM650")); } @@ -193,7 +193,7 @@ bool subghz_tx_start(SubGhz* subghz, FlipperFormat* flipper_format) { } } while(false); - string_clear(temp_str); + furi_string_free(temp_str); return ret; } @@ -209,7 +209,7 @@ void subghz_tx_stop(SubGhz* subghz) { if((subghz->txrx->decoder_result->protocol->type == SubGhzProtocolTypeDynamic) && (subghz_path_is_file(subghz->file_path))) { subghz_save_protocol_to_file( - subghz, subghz->txrx->fff_data, string_get_cstr(subghz->file_path)); + subghz, subghz->txrx->fff_data, furi_string_get_cstr(subghz->file_path)); } subghz_idle(subghz); notification_message(subghz->notifications, &sequence_reset_red); @@ -244,8 +244,8 @@ bool subghz_key_load(SubGhz* subghz, const char* file_path, bool show_dialog) { Stream* fff_data_stream = flipper_format_get_raw_stream(subghz->txrx->fff_data); SubGhzLoadKeyState load_key_state = SubGhzLoadKeyStateParseErr; - string_t temp_str; - string_init(temp_str); + FuriString* temp_str; + temp_str = furi_string_alloc(); uint32_t temp_data32; do { @@ -260,8 +260,8 @@ bool subghz_key_load(SubGhz* subghz, const char* file_path, bool show_dialog) { break; } - if(((!strcmp(string_get_cstr(temp_str), SUBGHZ_KEY_FILE_TYPE)) || - (!strcmp(string_get_cstr(temp_str), SUBGHZ_RAW_FILE_TYPE))) && + if(((!strcmp(furi_string_get_cstr(temp_str), SUBGHZ_KEY_FILE_TYPE)) || + (!strcmp(furi_string_get_cstr(temp_str), SUBGHZ_RAW_FILE_TYPE))) && temp_data32 == SUBGHZ_KEY_FILE_VERSION) { } else { FURI_LOG_E(TAG, "Type or version mismatch"); @@ -285,27 +285,29 @@ bool subghz_key_load(SubGhz* subghz, const char* file_path, bool show_dialog) { break; } - if(!subghz_set_preset(subghz, string_get_cstr(temp_str))) { + if(!subghz_set_preset(subghz, furi_string_get_cstr(temp_str))) { break; } - if(!strcmp(string_get_cstr(temp_str), "FuriHalSubGhzPresetCustom")) { + if(!strcmp(furi_string_get_cstr(temp_str), "FuriHalSubGhzPresetCustom")) { //Todo add Custom_preset_module //delete preset if it already exists subghz_setting_delete_custom_preset( - subghz->setting, string_get_cstr(subghz->txrx->preset->name)); + subghz->setting, furi_string_get_cstr(subghz->txrx->preset->name)); //load custom preset from file if(!subghz_setting_load_custom_preset( - subghz->setting, string_get_cstr(subghz->txrx->preset->name), fff_data_file)) { + subghz->setting, + furi_string_get_cstr(subghz->txrx->preset->name), + fff_data_file)) { FURI_LOG_E(TAG, "Missing Custom preset"); break; } } size_t preset_index = subghz_setting_get_inx_preset_by_name( - subghz->setting, string_get_cstr(subghz->txrx->preset->name)); + subghz->setting, furi_string_get_cstr(subghz->txrx->preset->name)); subghz_preset_init( subghz, - string_get_cstr(subghz->txrx->preset->name), + furi_string_get_cstr(subghz->txrx->preset->name), subghz->txrx->preset->frequency, subghz_setting_get_preset_data(subghz->setting, preset_index), subghz_setting_get_preset_data_size(subghz->setting, preset_index)); @@ -314,7 +316,7 @@ bool subghz_key_load(SubGhz* subghz, const char* file_path, bool show_dialog) { FURI_LOG_E(TAG, "Missing Protocol"); break; } - if(!strcmp(string_get_cstr(temp_str), "RAW")) { + if(!strcmp(furi_string_get_cstr(temp_str), "RAW")) { //if RAW subghz_protocol_raw_gen_fff_data(subghz->txrx->fff_data, file_path); } else { @@ -324,7 +326,7 @@ bool subghz_key_load(SubGhz* subghz, const char* file_path, bool show_dialog) { } subghz->txrx->decoder_result = subghz_receiver_search_decoder_base_by_name( - subghz->txrx->receiver, string_get_cstr(temp_str)); + subghz->txrx->receiver, furi_string_get_cstr(temp_str)); if(subghz->txrx->decoder_result) { if(!subghz_protocol_decoder_base_deserialize( subghz->txrx->decoder_result, subghz->txrx->fff_data)) { @@ -338,7 +340,7 @@ bool subghz_key_load(SubGhz* subghz, const char* file_path, bool show_dialog) { load_key_state = SubGhzLoadKeyStateOK; } while(0); - string_clear(temp_str); + furi_string_free(temp_str); flipper_format_free(fff_data_file); furi_record_close(RECORD_STORAGE); @@ -362,42 +364,42 @@ bool subghz_get_next_name_file(SubGhz* subghz, uint8_t max_len) { furi_assert(subghz); Storage* storage = furi_record_open(RECORD_STORAGE); - string_t temp_str; - string_t file_name; - string_t file_path; + FuriString* temp_str; + FuriString* file_name; + FuriString* file_path; - string_init(temp_str); - string_init(file_name); - string_init(file_path); + temp_str = furi_string_alloc(); + file_name = furi_string_alloc(); + file_path = furi_string_alloc(); bool res = false; if(subghz_path_is_file(subghz->file_path)) { //get the name of the next free file path_extract_filename(subghz->file_path, file_name, true); - path_extract_dirname(string_get_cstr(subghz->file_path), file_path); + path_extract_dirname(furi_string_get_cstr(subghz->file_path), file_path); storage_get_next_filename( storage, - string_get_cstr(file_path), - string_get_cstr(file_name), + furi_string_get_cstr(file_path), + furi_string_get_cstr(file_name), SUBGHZ_APP_EXTENSION, file_name, max_len); - string_printf( + furi_string_printf( temp_str, "%s/%s%s", - string_get_cstr(file_path), - string_get_cstr(file_name), + furi_string_get_cstr(file_path), + furi_string_get_cstr(file_name), SUBGHZ_APP_EXTENSION); - string_set(subghz->file_path, temp_str); + furi_string_set(subghz->file_path, temp_str); res = true; } - string_clear(temp_str); - string_clear(file_path); - string_clear(file_name); + furi_string_free(temp_str); + furi_string_free(file_path); + furi_string_free(file_name); furi_record_close(RECORD_STORAGE); return res; @@ -415,8 +417,8 @@ bool subghz_save_protocol_to_file( Stream* flipper_format_stream = flipper_format_get_raw_stream(flipper_format); bool saved = false; - string_t file_dir; - string_init(file_dir); + FuriString* file_dir; + file_dir = furi_string_alloc(); path_extract_dirname(dev_file_name, file_dir); do { @@ -425,7 +427,7 @@ bool subghz_save_protocol_to_file( flipper_format_delete_key(flipper_format, "Manufacture"); // Create subghz folder directory if necessary - if(!storage_simply_mkdir(storage, string_get_cstr(file_dir))) { + if(!storage_simply_mkdir(storage, furi_string_get_cstr(file_dir))) { dialog_message_show_storage_error(subghz->dialogs, "Cannot create\nfolder"); break; } @@ -439,7 +441,7 @@ bool subghz_save_protocol_to_file( saved = true; } while(0); - string_clear(file_dir); + furi_string_free(file_dir); furi_record_close(RECORD_STORAGE); return saved; } @@ -447,8 +449,8 @@ bool subghz_save_protocol_to_file( bool subghz_load_protocol_from_file(SubGhz* subghz) { furi_assert(subghz); - string_t file_path; - string_init(file_path); + FuriString* file_path; + file_path = furi_string_alloc(); DialogsFileBrowserOptions browser_options; dialog_file_browser_set_basic_options(&browser_options, SUBGHZ_APP_EXTENSION, &I_sub1_10px); @@ -458,10 +460,10 @@ bool subghz_load_protocol_from_file(SubGhz* subghz) { subghz->dialogs, subghz->file_path, subghz->file_path, &browser_options); if(res) { - res = subghz_key_load(subghz, string_get_cstr(subghz->file_path), true); + res = subghz_key_load(subghz, furi_string_get_cstr(subghz->file_path), true); } - string_clear(file_path); + furi_string_free(file_path); return res; } @@ -472,9 +474,11 @@ bool subghz_rename_file(SubGhz* subghz) { Storage* storage = furi_record_open(RECORD_STORAGE); - if(string_cmp(subghz->file_path_tmp, subghz->file_path)) { + if(furi_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)); + storage, + furi_string_get_cstr(subghz->file_path_tmp), + furi_string_get_cstr(subghz->file_path)); if(fs_result != FSE_OK) { dialog_message_show_storage_error(subghz->dialogs, "Cannot rename\n file/directory"); @@ -490,7 +494,7 @@ bool subghz_delete_file(SubGhz* subghz) { furi_assert(subghz); Storage* storage = furi_record_open(RECORD_STORAGE); - bool result = storage_simply_remove(storage, string_get_cstr(subghz->file_path_tmp)); + bool result = storage_simply_remove(storage, furi_string_get_cstr(subghz->file_path_tmp)); furi_record_close(RECORD_STORAGE); subghz_file_name_clear(subghz); @@ -500,12 +504,12 @@ bool subghz_delete_file(SubGhz* subghz) { void subghz_file_name_clear(SubGhz* subghz) { furi_assert(subghz); - string_set_str(subghz->file_path, SUBGHZ_APP_FOLDER); - string_reset(subghz->file_path_tmp); + furi_string_set(subghz->file_path, SUBGHZ_APP_FOLDER); + furi_string_reset(subghz->file_path_tmp); } -bool subghz_path_is_file(string_t path) { - return string_end_with_str_p(path, SUBGHZ_APP_EXTENSION); +bool subghz_path_is_file(FuriString* path) { + return furi_string_end_with(path, SUBGHZ_APP_EXTENSION); } uint32_t subghz_random_serial(void) { diff --git a/applications/main/subghz/subghz_i.h b/applications/main/subghz/subghz_i.h index 99a0f8a28e0..58d307170fa 100644 --- a/applications/main/subghz/subghz_i.h +++ b/applications/main/subghz/subghz_i.h @@ -75,8 +75,8 @@ struct SubGhz { TextInput* text_input; Widget* widget; DialogsApp* dialogs; - string_t file_path; - string_t file_path_tmp; + FuriString* file_path; + FuriString* file_path_tmp; char file_name_tmp[SUBGHZ_MAX_LEN_NAME]; SubGhzNotificationState state_notifications; @@ -89,7 +89,7 @@ struct SubGhz { SubGhzTestStatic* subghz_test_static; SubGhzTestCarrier* subghz_test_carrier; SubGhzTestPacket* subghz_test_packet; - string_t error_str; + FuriString* error_str; SubGhzSetting* setting; SubGhzLock lock; @@ -103,7 +103,7 @@ void subghz_preset_init( uint8_t* preset_data, size_t preset_data_size); bool subghz_set_preset(SubGhz* subghz, const char* preset); -void subghz_get_frequency_modulation(SubGhz* subghz, string_t frequency, string_t modulation); +void subghz_get_frequency_modulation(SubGhz* subghz, FuriString* frequency, FuriString* modulation); void subghz_begin(SubGhz* subghz, uint8_t* preset_data); uint32_t subghz_rx(SubGhz* subghz, uint32_t frequency); void subghz_rx_end(SubGhz* subghz); @@ -125,6 +125,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); +bool subghz_path_is_file(FuriString* path); uint32_t subghz_random_serial(void); void subghz_hopper_update(SubGhz* subghz); diff --git a/applications/main/subghz/subghz_setting.c b/applications/main/subghz/subghz_setting.c index b7c143cd5a2..e0322f6a977 100644 --- a/applications/main/subghz/subghz_setting.c +++ b/applications/main/subghz/subghz_setting.c @@ -158,7 +158,7 @@ static const uint32_t subghz_hopper_frequency_list_region_jp[] = { }; typedef struct { - string_t custom_preset_name; + FuriString* custom_preset_name; uint8_t* custom_preset_data; size_t custom_preset_data_size; } SubGhzSettingCustomPresetItem; @@ -194,7 +194,7 @@ SubGhzSetting* subghz_setting_alloc(void) { static void subghz_setting_preset_reset(SubGhzSetting* instance) { for M_EACH(item, instance->preset->data, SubGhzSettingCustomPresetItemArray_t) { - string_clear(item->custom_preset_name); + furi_string_free(item->custom_preset_name); free(item->custom_preset_data); } SubGhzSettingCustomPresetItemArray_reset(instance->preset->data); @@ -206,7 +206,7 @@ void subghz_setting_free(SubGhzSetting* instance) { FrequencyList_clear(instance->hopper_frequencies); for M_EACH(item, instance->preset->data, SubGhzSettingCustomPresetItemArray_t) { - string_clear(item->custom_preset_name); + furi_string_free(item->custom_preset_name); free(item->custom_preset_data); } SubGhzSettingCustomPresetItemArray_clear(instance->preset->data); @@ -225,8 +225,8 @@ static void subghz_setting_load_default_preset( SubGhzSettingCustomPresetItem* item = SubGhzSettingCustomPresetItemArray_push_raw(instance->preset->data); - string_init(item->custom_preset_name); - string_set(item->custom_preset_name, preset_name); + item->custom_preset_name = furi_string_alloc(); + furi_string_set(item->custom_preset_name, preset_name); while(preset_data[preset_data_count]) { preset_data_count += 2; @@ -314,8 +314,8 @@ void subghz_setting_load(SubGhzSetting* instance, const char* file_path) { Storage* storage = furi_record_open(RECORD_STORAGE); FlipperFormat* fff_data_file = flipper_format_file_alloc(storage); - string_t temp_str; - string_init(temp_str); + FuriString* temp_str; + temp_str = furi_string_alloc(); uint32_t temp_data32; bool temp_bool; @@ -333,7 +333,7 @@ void subghz_setting_load(SubGhzSetting* instance, const char* file_path) { break; } - if((!strcmp(string_get_cstr(temp_str), SUBGHZ_SETTING_FILE_TYPE)) && + if((!strcmp(furi_string_get_cstr(temp_str), SUBGHZ_SETTING_FILE_TYPE)) && temp_data32 == SUBGHZ_SETTING_FILE_VERSION) { } else { FURI_LOG_E(TAG, "Type or version mismatch"); @@ -402,15 +402,15 @@ void subghz_setting_load(SubGhzSetting* instance, const char* file_path) { break; } while(flipper_format_read_string(fff_data_file, "Custom_preset_name", temp_str)) { - FURI_LOG_I(TAG, "Custom preset loaded %s", string_get_cstr(temp_str)); + FURI_LOG_I(TAG, "Custom preset loaded %s", furi_string_get_cstr(temp_str)); subghz_setting_load_custom_preset( - instance, string_get_cstr(temp_str), fff_data_file); + instance, furi_string_get_cstr(temp_str), fff_data_file); } } while(false); } - string_clear(temp_str); + furi_string_free(temp_str); flipper_format_free(fff_data_file); furi_record_close(RECORD_STORAGE); @@ -440,7 +440,7 @@ const char* subghz_setting_get_preset_name(SubGhzSetting* instance, size_t idx) furi_assert(instance); SubGhzSettingCustomPresetItem* item = SubGhzSettingCustomPresetItemArray_get(instance->preset->data, idx); - return string_get_cstr(item->custom_preset_name); + return furi_string_get_cstr(item->custom_preset_name); } int subghz_setting_get_inx_preset_by_name(SubGhzSetting* instance, const char* preset_name) { @@ -448,7 +448,7 @@ int subghz_setting_get_inx_preset_by_name(SubGhzSetting* instance, const char* p size_t idx = 0; for M_EACH(item, instance->preset->data, SubGhzSettingCustomPresetItemArray_t) { - if(strcmp(string_get_cstr(item->custom_preset_name), preset_name) == 0) { + if(strcmp(furi_string_get_cstr(item->custom_preset_name), preset_name) == 0) { return idx; } idx++; @@ -466,8 +466,8 @@ bool subghz_setting_load_custom_preset( uint32_t temp_data32; SubGhzSettingCustomPresetItem* item = SubGhzSettingCustomPresetItemArray_push_raw(instance->preset->data); - string_init(item->custom_preset_name); - string_set(item->custom_preset_name, preset_name); + item->custom_preset_name = furi_string_alloc(); + furi_string_set(item->custom_preset_name, preset_name); do { if(!flipper_format_get_value_count(fff_data_file, "Custom_preset_data", &temp_data32)) break; @@ -497,8 +497,8 @@ bool subghz_setting_delete_custom_preset(SubGhzSetting* instance, const char* pr SubGhzSettingCustomPresetItemArray_it_last(it, instance->preset->data); while(!SubGhzSettingCustomPresetItemArray_end_p(it)) { SubGhzSettingCustomPresetItem* item = SubGhzSettingCustomPresetItemArray_ref(it); - if(strcmp(string_get_cstr(item->custom_preset_name), preset_name) == 0) { - string_clear(item->custom_preset_name); + if(strcmp(furi_string_get_cstr(item->custom_preset_name), preset_name) == 0) { + furi_string_free(item->custom_preset_name); free(item->custom_preset_data); SubGhzSettingCustomPresetItemArray_remove(instance->preset->data, it); return true; diff --git a/applications/main/subghz/views/receiver.c b/applications/main/subghz/views/receiver.c index c28c3363613..6ec12e78134 100644 --- a/applications/main/subghz/views/receiver.c +++ b/applications/main/subghz/views/receiver.c @@ -5,7 +5,6 @@ #include #include #include -#include #include #define FRAME_HEIGHT 12 @@ -14,7 +13,7 @@ #define UNLOCK_CNT 3 typedef struct { - string_t item_str; + FuriString* item_str; uint8_t type; } SubGhzReceiverMenuItem; @@ -52,9 +51,9 @@ struct SubGhzViewReceiver { }; typedef struct { - string_t frequency_str; - string_t preset_str; - string_t history_stat_str; + FuriString* frequency_str; + FuriString* preset_str; + FuriString* history_stat_str; SubGhzReceiverHistory* history; uint16_t idx; uint16_t list_offset; @@ -121,7 +120,7 @@ void subghz_view_receiver_add_item_to_menu( subghz_receiver->view, (SubGhzViewReceiverModel * model) { SubGhzReceiverMenuItem* item_menu = SubGhzReceiverMenuItemArray_push_raw(model->history->data); - string_init_set_str(item_menu->item_str, name); + item_menu->item_str = furi_string_alloc_set(name); item_menu->type = type; if((model->idx == model->history_item - 1)) { model->history_item++; @@ -143,9 +142,9 @@ void subghz_view_receiver_add_data_statusbar( furi_assert(subghz_receiver); with_view_model( subghz_receiver->view, (SubGhzViewReceiverModel * model) { - 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); + furi_string_set(model->frequency_str, frequency_str); + furi_string_set(model->preset_str, preset_str); + furi_string_set(model->history_stat_str, history_stat_str); return true; }); } @@ -173,15 +172,15 @@ void subghz_view_receiver_draw(Canvas* canvas, SubGhzViewReceiverModel* model) { canvas_draw_line(canvas, 46, 51, 125, 51); bool scrollbar = model->history_item > 4; - string_t str_buff; - string_init(str_buff); + FuriString* str_buff; + str_buff = furi_string_alloc(); SubGhzReceiverMenuItem* item_menu; for(size_t i = 0; i < MIN(model->history_item, MENU_ITEMS); ++i) { size_t idx = CLAMP((uint16_t)(i + model->list_offset), model->history_item, 0); item_menu = SubGhzReceiverMenuItemArray_get(model->history->data, idx); - string_set(str_buff, item_menu->item_str); + furi_string_set(str_buff, item_menu->item_str); elements_string_fit_width(canvas, str_buff, scrollbar ? MAX_LEN_PX - 6 : MAX_LEN_PX); if(model->idx == idx) { subghz_view_receiver_draw_frame(canvas, i, scrollbar); @@ -189,13 +188,13 @@ void subghz_view_receiver_draw(Canvas* canvas, SubGhzViewReceiverModel* model) { canvas_set_color(canvas, ColorBlack); } canvas_draw_icon(canvas, 1, 2 + i * FRAME_HEIGHT, ReceiverItemIcons[item_menu->type]); - canvas_draw_str(canvas, 15, 9 + i * FRAME_HEIGHT, string_get_cstr(str_buff)); - string_reset(str_buff); + canvas_draw_str(canvas, 15, 9 + i * FRAME_HEIGHT, furi_string_get_cstr(str_buff)); + furi_string_reset(str_buff); } if(scrollbar) { elements_scrollbar_pos(canvas, 128, 0, 49, model->idx, model->history_item); } - string_clear(str_buff); + furi_string_free(str_buff); canvas_set_color(canvas, ColorBlack); @@ -213,9 +212,9 @@ void subghz_view_receiver_draw(Canvas* canvas, SubGhzViewReceiverModel* model) { canvas_draw_str(canvas, 74, 62, "Locked"); break; case SubGhzViewReceiverBarShowToUnlockPress: - canvas_draw_str(canvas, 44, 62, string_get_cstr(model->frequency_str)); - canvas_draw_str(canvas, 79, 62, string_get_cstr(model->preset_str)); - canvas_draw_str(canvas, 96, 62, string_get_cstr(model->history_stat_str)); + canvas_draw_str(canvas, 44, 62, furi_string_get_cstr(model->frequency_str)); + canvas_draw_str(canvas, 79, 62, furi_string_get_cstr(model->preset_str)); + canvas_draw_str(canvas, 96, 62, furi_string_get_cstr(model->history_stat_str)); canvas_set_font(canvas, FontSecondary); elements_bold_rounded_frame(canvas, 14, 8, 99, 48); elements_multiline_text(canvas, 65, 26, "To unlock\npress:"); @@ -230,9 +229,9 @@ void subghz_view_receiver_draw(Canvas* canvas, SubGhzViewReceiverModel* model) { canvas_draw_str(canvas, 74, 62, "Unlocked"); break; default: - canvas_draw_str(canvas, 44, 62, string_get_cstr(model->frequency_str)); - canvas_draw_str(canvas, 79, 62, string_get_cstr(model->preset_str)); - canvas_draw_str(canvas, 96, 62, string_get_cstr(model->history_stat_str)); + canvas_draw_str(canvas, 44, 62, furi_string_get_cstr(model->frequency_str)); + canvas_draw_str(canvas, 79, 62, furi_string_get_cstr(model->preset_str)); + canvas_draw_str(canvas, 96, 62, furi_string_get_cstr(model->history_stat_str)); break; } } @@ -331,12 +330,12 @@ void subghz_view_receiver_exit(void* context) { SubGhzViewReceiver* subghz_receiver = context; with_view_model( subghz_receiver->view, (SubGhzViewReceiverModel * model) { - string_reset(model->frequency_str); - string_reset(model->preset_str); - string_reset(model->history_stat_str); + furi_string_reset(model->frequency_str); + furi_string_reset(model->preset_str); + furi_string_reset(model->history_stat_str); for M_EACH(item_menu, model->history->data, SubGhzReceiverMenuItemArray_t) { - string_clear(item_menu->item_str); + furi_string_free(item_menu->item_str); item_menu->type = 0; } SubGhzReceiverMenuItemArray_reset(model->history->data); @@ -366,9 +365,9 @@ SubGhzViewReceiver* subghz_view_receiver_alloc() { with_view_model( subghz_receiver->view, (SubGhzViewReceiverModel * model) { - string_init(model->frequency_str); - string_init(model->preset_str); - string_init(model->history_stat_str); + model->frequency_str = furi_string_alloc(); + model->preset_str = furi_string_alloc(); + model->history_stat_str = furi_string_alloc(); model->bar_show = SubGhzViewReceiverBarShowDefault; model->history = malloc(sizeof(SubGhzReceiverHistory)); SubGhzReceiverMenuItemArray_init(model->history->data); @@ -384,12 +383,12 @@ void subghz_view_receiver_free(SubGhzViewReceiver* subghz_receiver) { with_view_model( subghz_receiver->view, (SubGhzViewReceiverModel * model) { - string_clear(model->frequency_str); - string_clear(model->preset_str); - string_clear(model->history_stat_str); + furi_string_free(model->frequency_str); + furi_string_free(model->preset_str); + furi_string_free(model->history_stat_str); for M_EACH(item_menu, model->history->data, SubGhzReceiverMenuItemArray_t) { - string_clear(item_menu->item_str); + furi_string_free(item_menu->item_str); item_menu->type = 0; } SubGhzReceiverMenuItemArray_clear(model->history->data); diff --git a/applications/main/subghz/views/subghz_read_raw.c b/applications/main/subghz/views/subghz_read_raw.c index ccffaf42f7f..0b4305b7e30 100644 --- a/applications/main/subghz/views/subghz_read_raw.c +++ b/applications/main/subghz/views/subghz_read_raw.c @@ -18,10 +18,10 @@ struct SubGhzReadRAW { }; typedef struct { - string_t frequency_str; - string_t preset_str; - string_t sample_write; - string_t file_name; + FuriString* frequency_str; + FuriString* preset_str; + FuriString* sample_write; + FuriString* file_name; uint8_t* rssi_history; bool rssi_history_end; uint8_t ind_write; @@ -46,8 +46,8 @@ void subghz_read_raw_add_data_statusbar( furi_assert(instance); with_view_model( instance->view, (SubGhzReadRAWModel * model) { - string_set_str(model->frequency_str, frequency_str); - string_set_str(model->preset_str, preset_str); + furi_string_set(model->frequency_str, frequency_str); + furi_string_set(model->preset_str, preset_str); return true; }); } @@ -78,7 +78,7 @@ void subghz_read_raw_update_sample_write(SubGhzReadRAW* instance, size_t sample) with_view_model( instance->view, (SubGhzReadRAWModel * model) { - string_printf(model->sample_write, "%d spl.", sample); + furi_string_printf(model->sample_write, "%d spl.", sample); return false; }); } @@ -216,10 +216,10 @@ void subghz_read_raw_draw(Canvas* canvas, SubGhzReadRAWModel* model) { uint8_t graphics_mode = 1; canvas_set_color(canvas, ColorBlack); canvas_set_font(canvas, FontSecondary); - canvas_draw_str(canvas, 5, 7, string_get_cstr(model->frequency_str)); - canvas_draw_str(canvas, 40, 7, string_get_cstr(model->preset_str)); + canvas_draw_str(canvas, 5, 7, furi_string_get_cstr(model->frequency_str)); + canvas_draw_str(canvas, 40, 7, furi_string_get_cstr(model->preset_str)); canvas_draw_str_aligned( - canvas, 126, 0, AlignRight, AlignTop, string_get_cstr(model->sample_write)); + canvas, 126, 0, AlignRight, AlignTop, furi_string_get_cstr(model->sample_write)); canvas_draw_line(canvas, 0, 14, 115, 14); canvas_draw_line(canvas, 0, 48, 115, 48); @@ -243,7 +243,7 @@ void subghz_read_raw_draw(Canvas* canvas, SubGhzReadRAWModel* model) { 30, AlignCenter, AlignCenter, - string_get_cstr(model->file_name), + furi_string_get_cstr(model->file_name), true); break; @@ -372,8 +372,8 @@ bool subghz_read_raw_input(InputEvent* event, void* context) { model->status = SubGhzReadRAWStatusStart; model->rssi_history_end = false; model->ind_write = 0; - string_set_str(model->sample_write, "0 spl."); - string_reset(model->file_name); + furi_string_set(model->sample_write, "0 spl."); + furi_string_reset(model->file_name); instance->callback(SubGhzCustomEventViewReadRAWErase, instance->context); } return true; @@ -423,8 +423,8 @@ void subghz_read_raw_set_status( model->status = SubGhzReadRAWStatusStart; model->rssi_history_end = false; model->ind_write = 0; - string_reset(model->file_name); - string_set_str(model->sample_write, "0 spl."); + furi_string_reset(model->file_name); + furi_string_set(model->sample_write, "0 spl."); return true; }); break; @@ -441,8 +441,8 @@ void subghz_read_raw_set_status( model->status = SubGhzReadRAWStatusLoadKeyIDLE; model->rssi_history_end = false; model->ind_write = 0; - string_set_str(model->file_name, file_name); - string_set_str(model->sample_write, "RAW"); + furi_string_set(model->file_name, file_name); + furi_string_set(model->sample_write, "RAW"); return true; }); break; @@ -451,10 +451,10 @@ void subghz_read_raw_set_status( instance->view, (SubGhzReadRAWModel * model) { model->status = SubGhzReadRAWStatusLoadKeyIDLE; if(!model->ind_write) { - string_set_str(model->file_name, file_name); - string_set_str(model->sample_write, "RAW"); + furi_string_set(model->file_name, file_name); + furi_string_set(model->sample_write, "RAW"); } else { - string_reset(model->file_name); + furi_string_reset(model->file_name); } return true; }); @@ -501,10 +501,10 @@ SubGhzReadRAW* subghz_read_raw_alloc() { with_view_model( instance->view, (SubGhzReadRAWModel * model) { - string_init(model->frequency_str); - string_init(model->preset_str); - string_init(model->sample_write); - string_init(model->file_name); + model->frequency_str = furi_string_alloc(); + model->preset_str = furi_string_alloc(); + model->sample_write = furi_string_alloc(); + model->file_name = furi_string_alloc(); model->rssi_history = malloc(SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE * sizeof(uint8_t)); return true; }); @@ -517,10 +517,10 @@ void subghz_read_raw_free(SubGhzReadRAW* instance) { with_view_model( instance->view, (SubGhzReadRAWModel * model) { - string_clear(model->frequency_str); - string_clear(model->preset_str); - string_clear(model->sample_write); - string_clear(model->file_name); + furi_string_free(model->frequency_str); + furi_string_free(model->preset_str); + furi_string_free(model->sample_write); + furi_string_free(model->file_name); free(model->rssi_history); return true; }); diff --git a/applications/main/subghz/views/transmitter.c b/applications/main/subghz/views/transmitter.c index dd2b6d321b1..1094c5c58b3 100644 --- a/applications/main/subghz/views/transmitter.c +++ b/applications/main/subghz/views/transmitter.c @@ -11,9 +11,9 @@ struct SubGhzViewTransmitter { }; typedef struct { - string_t frequency_str; - string_t preset_str; - string_t key_str; + FuriString* frequency_str; + FuriString* preset_str; + FuriString* key_str; uint8_t show_button; } SubGhzViewTransmitterModel; @@ -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_str(model->key_str, key_str); - string_set_str(model->frequency_str, frequency_str); - string_set_str(model->preset_str, preset_str); + furi_string_set(model->key_str, key_str); + furi_string_set(model->frequency_str, frequency_str); + furi_string_set(model->preset_str, preset_str); model->show_button = show_button; return true; }); @@ -82,9 +82,9 @@ void subghz_view_transmitter_draw(Canvas* canvas, SubGhzViewTransmitterModel* mo canvas_clear(canvas); canvas_set_color(canvas, ColorBlack); canvas_set_font(canvas, FontSecondary); - elements_multiline_text(canvas, 0, 8, string_get_cstr(model->key_str)); - canvas_draw_str(canvas, 78, 8, string_get_cstr(model->frequency_str)); - canvas_draw_str(canvas, 113, 8, string_get_cstr(model->preset_str)); + elements_multiline_text(canvas, 0, 8, furi_string_get_cstr(model->key_str)); + canvas_draw_str(canvas, 78, 8, furi_string_get_cstr(model->frequency_str)); + canvas_draw_str(canvas, 113, 8, furi_string_get_cstr(model->preset_str)); if(model->show_button) subghz_view_transmitter_button_right(canvas, "Send"); } @@ -96,9 +96,9 @@ bool subghz_view_transmitter_input(InputEvent* event, void* context) { if(event->key == InputKeyBack && event->type == InputTypeShort) { with_view_model( subghz_transmitter->view, (SubGhzViewTransmitterModel * model) { - string_reset(model->frequency_str); - string_reset(model->preset_str); - string_reset(model->key_str); + furi_string_reset(model->frequency_str); + furi_string_reset(model->preset_str); + furi_string_reset(model->key_str); model->show_button = 0; return false; }); @@ -150,9 +150,9 @@ SubGhzViewTransmitter* subghz_view_transmitter_alloc() { with_view_model( subghz_transmitter->view, (SubGhzViewTransmitterModel * model) { - string_init(model->frequency_str); - string_init(model->preset_str); - string_init(model->key_str); + model->frequency_str = furi_string_alloc(); + model->preset_str = furi_string_alloc(); + model->key_str = furi_string_alloc(); return true; }); return subghz_transmitter; @@ -163,9 +163,9 @@ void subghz_view_transmitter_free(SubGhzViewTransmitter* subghz_transmitter) { with_view_model( subghz_transmitter->view, (SubGhzViewTransmitterModel * model) { - string_clear(model->frequency_str); - string_clear(model->preset_str); - string_clear(model->key_str); + furi_string_free(model->frequency_str); + furi_string_free(model->preset_str); + furi_string_free(model->key_str); return true; }); view_free(subghz_transmitter->view); diff --git a/applications/main/u2f/u2f_data.c b/applications/main/u2f/u2f_data.c index 117fbdbe36d..900af462af5 100644 --- a/applications/main/u2f/u2f_data.c +++ b/applications/main/u2f/u2f_data.c @@ -181,8 +181,8 @@ bool u2f_data_cert_key_load(uint8_t* cert_key) { // Check if unique key exists in secure eclave and generate it if missing if(!furi_hal_crypto_verify_key(U2F_DATA_FILE_ENCRYPTION_KEY_SLOT_UNIQUE)) return false; - string_t filetype; - string_init(filetype); + FuriString* filetype; + filetype = furi_string_alloc(); Storage* storage = furi_record_open(RECORD_STORAGE); FlipperFormat* flipper_format = flipper_format_file_alloc(storage); @@ -194,7 +194,7 @@ bool u2f_data_cert_key_load(uint8_t* cert_key) { break; } - if(strcmp(string_get_cstr(filetype), U2F_CERT_KEY_FILE_TYPE) != 0 || + if(strcmp(furi_string_get_cstr(filetype), U2F_CERT_KEY_FILE_TYPE) != 0 || version != U2F_CERT_KEY_VERSION) { FURI_LOG_E(TAG, "Type or version mismatch"); break; @@ -250,7 +250,7 @@ bool u2f_data_cert_key_load(uint8_t* cert_key) { flipper_format_free(flipper_format); furi_record_close(RECORD_STORAGE); - string_clear(filetype); + furi_string_free(filetype); if(cert_type == U2F_CERT_USER_UNENCRYPTED) { return u2f_data_cert_key_encrypt(cert_key); @@ -267,8 +267,8 @@ bool u2f_data_key_load(uint8_t* device_key) { uint8_t key[48]; uint32_t version = 0; - string_t filetype; - string_init(filetype); + FuriString* filetype; + filetype = furi_string_alloc(); Storage* storage = furi_record_open(RECORD_STORAGE); FlipperFormat* flipper_format = flipper_format_file_alloc(storage); @@ -279,7 +279,7 @@ bool u2f_data_key_load(uint8_t* device_key) { FURI_LOG_E(TAG, "Missing or incorrect header"); break; } - if(strcmp(string_get_cstr(filetype), U2F_DEVICE_KEY_FILE_TYPE) != 0 || + if(strcmp(furi_string_get_cstr(filetype), U2F_DEVICE_KEY_FILE_TYPE) != 0 || version != U2F_DEVICE_KEY_VERSION) { FURI_LOG_E(TAG, "Type or version mismatch"); break; @@ -308,7 +308,7 @@ bool u2f_data_key_load(uint8_t* device_key) { } flipper_format_free(flipper_format); furi_record_close(RECORD_STORAGE); - string_clear(filetype); + furi_string_free(filetype); return state; } @@ -366,8 +366,8 @@ bool u2f_data_cnt_read(uint32_t* cnt_val) { uint8_t cnt_encr[48]; uint32_t version = 0; - string_t filetype; - string_init(filetype); + FuriString* filetype; + filetype = furi_string_alloc(); Storage* storage = furi_record_open(RECORD_STORAGE); FlipperFormat* flipper_format = flipper_format_file_alloc(storage); @@ -378,7 +378,7 @@ bool u2f_data_cnt_read(uint32_t* cnt_val) { FURI_LOG_E(TAG, "Missing or incorrect header"); break; } - if(strcmp(string_get_cstr(filetype), U2F_COUNTER_FILE_TYPE) != 0) { + if(strcmp(furi_string_get_cstr(filetype), U2F_COUNTER_FILE_TYPE) != 0) { FURI_LOG_E(TAG, "Type mismatch"); break; } @@ -417,7 +417,7 @@ bool u2f_data_cnt_read(uint32_t* cnt_val) { } flipper_format_free(flipper_format); furi_record_close(RECORD_STORAGE); - string_clear(filetype); + furi_string_free(filetype); if(old_counter && state) { // Change counter endianness and rewrite counter file diff --git a/applications/plugins/music_player/music_player.c b/applications/plugins/music_player/music_player.c index 40e9085fb50..6d3c4483e0d 100644 --- a/applications/plugins/music_player/music_player.c +++ b/applications/plugins/music_player/music_player.c @@ -8,8 +8,6 @@ #include #include -#include - #define TAG "MusicPlayer" #define MUSIC_PLAYER_APP_PATH_FOLDER ANY_PATH("music_player") @@ -296,14 +294,14 @@ void music_player_free(MusicPlayer* instance) { int32_t music_player_app(void* p) { MusicPlayer* music_player = music_player_alloc(); - string_t file_path; - string_init(file_path); + FuriString* file_path; + file_path = furi_string_alloc(); do { if(p && strlen(p)) { - string_cat_str(file_path, p); + furi_string_cat(file_path, (const char*)p); } else { - string_set_str(file_path, MUSIC_PLAYER_APP_PATH_FOLDER); + furi_string_set(file_path, MUSIC_PLAYER_APP_PATH_FOLDER); DialogsFileBrowserOptions browser_options; dialog_file_browser_set_basic_options( @@ -320,7 +318,7 @@ int32_t music_player_app(void* p) { } } - if(!music_player_worker_load(music_player->worker, string_get_cstr(file_path))) { + if(!music_player_worker_load(music_player->worker, furi_string_get_cstr(file_path))) { FURI_LOG_E(TAG, "Unable to load file"); break; } @@ -354,7 +352,7 @@ int32_t music_player_app(void* p) { music_player_worker_stop(music_player->worker); } while(0); - string_clear(file_path); + furi_string_free(file_path); music_player_free(music_player); return 0; diff --git a/applications/plugins/music_player/music_player_cli.c b/applications/plugins/music_player/music_player_cli.c index 782004439af..90060d7ee00 100644 --- a/applications/plugins/music_player/music_player_cli.c +++ b/applications/plugins/music_player/music_player_cli.c @@ -3,20 +3,20 @@ #include #include "music_player_worker.h" -static void music_player_cli(Cli* cli, string_t args, void* context) { +static void music_player_cli(Cli* cli, FuriString* args, void* context) { UNUSED(context); MusicPlayerWorker* music_player_worker = music_player_worker_alloc(); Storage* storage = furi_record_open(RECORD_STORAGE); do { - if(storage_common_stat(storage, string_get_cstr(args), NULL) == FSE_OK) { - if(!music_player_worker_load(music_player_worker, string_get_cstr(args))) { - printf("Failed to open file %s\r\n", string_get_cstr(args)); + if(storage_common_stat(storage, furi_string_get_cstr(args), NULL) == FSE_OK) { + if(!music_player_worker_load(music_player_worker, furi_string_get_cstr(args))) { + printf("Failed to open file %s\r\n", furi_string_get_cstr(args)); break; } } else { if(!music_player_worker_load_rtttl_from_string( - music_player_worker, string_get_cstr(args))) { + music_player_worker, furi_string_get_cstr(args))) { printf("Argument is not a file or RTTTL\r\n"); break; } diff --git a/applications/plugins/music_player/music_player_worker.c b/applications/plugins/music_player/music_player_worker.c index ca4f1d8c9f2..af09f05352d 100644 --- a/applications/plugins/music_player/music_player_worker.c +++ b/applications/plugins/music_player/music_player_worker.c @@ -326,8 +326,8 @@ bool music_player_worker_load_fmf_from_file(MusicPlayerWorker* instance, const c furi_assert(file_path); bool result = false; - string_t temp_str; - string_init(temp_str); + FuriString* temp_str; + temp_str = furi_string_alloc(); Storage* storage = furi_record_open(RECORD_STORAGE); FlipperFormat* file = flipper_format_file_alloc(storage); @@ -337,7 +337,8 @@ bool music_player_worker_load_fmf_from_file(MusicPlayerWorker* instance, const c uint32_t version = 0; if(!flipper_format_read_header(file, temp_str, &version)) break; - if(string_cmp_str(temp_str, MUSIC_PLAYER_FILETYPE) || (version != MUSIC_PLAYER_VERSION)) { + if(furi_string_cmp_str(temp_str, MUSIC_PLAYER_FILETYPE) || + (version != MUSIC_PLAYER_VERSION)) { FURI_LOG_E(TAG, "Incorrect file format or version"); break; } @@ -360,7 +361,7 @@ bool music_player_worker_load_fmf_from_file(MusicPlayerWorker* instance, const c break; } - if(!music_player_worker_parse_notes(instance, string_get_cstr(temp_str))) { + if(!music_player_worker_parse_notes(instance, furi_string_get_cstr(temp_str))) { break; } @@ -369,7 +370,7 @@ bool music_player_worker_load_fmf_from_file(MusicPlayerWorker* instance, const c furi_record_close(RECORD_STORAGE); flipper_format_free(file); - string_clear(temp_str); + furi_string_free(temp_str); return result; } @@ -379,8 +380,8 @@ bool music_player_worker_load_rtttl_from_file(MusicPlayerWorker* instance, const furi_assert(file_path); bool result = false; - string_t content; - string_init(content); + FuriString* content; + content = furi_string_alloc(); Storage* storage = furi_record_open(RECORD_STORAGE); File* file = storage_file_alloc(storage); @@ -395,17 +396,17 @@ bool music_player_worker_load_rtttl_from_file(MusicPlayerWorker* instance, const uint8_t buffer[65] = {0}; ret = storage_file_read(file, buffer, sizeof(buffer) - 1); for(size_t i = 0; i < ret; i++) { - string_push_back(content, buffer[i]); + furi_string_push_back(content, buffer[i]); } } while(ret > 0); - string_strim(content); - if(!string_size(content)) { + furi_string_trim(content); + if(!furi_string_size(content)) { FURI_LOG_E(TAG, "Empty file"); break; } - if(!music_player_worker_load_rtttl_from_string(instance, string_get_cstr(content))) { + if(!music_player_worker_load_rtttl_from_string(instance, furi_string_get_cstr(content))) { FURI_LOG_E(TAG, "Invalid file content"); break; } @@ -415,7 +416,7 @@ bool music_player_worker_load_rtttl_from_file(MusicPlayerWorker* instance, const storage_file_free(file); furi_record_close(RECORD_STORAGE); - string_clear(content); + furi_string_free(content); return result; } diff --git a/applications/plugins/picopass/picopass_device.c b/applications/plugins/picopass/picopass_device.c index e7f3e0bedc1..b6e69cc21bd 100644 --- a/applications/plugins/picopass/picopass_device.c +++ b/applications/plugins/picopass/picopass_device.c @@ -18,7 +18,7 @@ PicopassDevice* picopass_device_alloc() { picopass_dev->dev_data.pacs.pin_length = 0; picopass_dev->storage = furi_record_open(RECORD_STORAGE); picopass_dev->dialogs = furi_record_open(RECORD_DIALOGS); - string_init(picopass_dev->load_path); + picopass_dev->load_path = furi_string_alloc(); return picopass_dev; } @@ -40,25 +40,25 @@ static bool picopass_device_save_file( FlipperFormat* file = flipper_format_file_alloc(dev->storage); PicopassPacs* pacs = &dev->dev_data.pacs; PicopassBlock* AA1 = dev->dev_data.AA1; - string_t temp_str; - string_init(temp_str); + FuriString* temp_str; + temp_str = furi_string_alloc(); do { - if(use_load_path && !string_empty_p(dev->load_path)) { + if(use_load_path && !furi_string_empty(dev->load_path)) { // Get directory name - path_extract_dirname(string_get_cstr(dev->load_path), temp_str); + path_extract_dirname(furi_string_get_cstr(dev->load_path), temp_str); // Create picopass directory if necessary - if(!storage_simply_mkdir(dev->storage, string_get_cstr(temp_str))) break; + if(!storage_simply_mkdir(dev->storage, furi_string_get_cstr(temp_str))) break; // Make path to file to save - string_cat_printf(temp_str, "/%s%s", dev_name, extension); + furi_string_cat_printf(temp_str, "/%s%s", dev_name, extension); } else { // Create picopass directory if necessary if(!storage_simply_mkdir(dev->storage, PICOPASS_APP_FOLDER)) break; // First remove picopass device file if it was saved - string_printf(temp_str, "%s/%s%s", folder, dev_name, extension); + furi_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; + if(!flipper_format_file_open_always(file, furi_string_get_cstr(temp_str))) break; if(dev->format == PicopassDeviceSaveFormatHF) { uint32_t fc = pacs->record.FacilityCode; @@ -87,9 +87,9 @@ static bool picopass_device_save_file( AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[0] : PICOPASS_MAX_APP_LIMIT; for(size_t i = 0; i < app_limit; i++) { - string_printf(temp_str, "Block %d", i); + furi_string_printf(temp_str, "Block %d", i); if(!flipper_format_write_hex( - file, string_get_cstr(temp_str), AA1[i].data, PICOPASS_BLOCK_LEN)) { + file, furi_string_get_cstr(temp_str), AA1[i].data, PICOPASS_BLOCK_LEN)) { block_saved = false; break; } @@ -117,7 +117,7 @@ static bool picopass_device_save_file( if(!saved) { dialog_message_show_storage_error(dev->dialogs, "Can not save\nfile"); } - string_clear(temp_str); + furi_string_free(temp_str); flipper_format_free(file); return saved; } @@ -132,13 +132,13 @@ bool picopass_device_save(PicopassDevice* dev, const char* dev_name) { return false; } -static bool picopass_device_load_data(PicopassDevice* dev, string_t path, bool show_dialog) { +static bool picopass_device_load_data(PicopassDevice* dev, FuriString* path, bool show_dialog) { bool parsed = false; FlipperFormat* file = flipper_format_file_alloc(dev->storage); PicopassBlock* AA1 = dev->dev_data.AA1; PicopassPacs* pacs = &dev->dev_data.pacs; - string_t temp_str; - string_init(temp_str); + FuriString* temp_str; + temp_str = furi_string_alloc(); bool deprecated_version = false; if(dev->loading_cb) { @@ -146,12 +146,13 @@ static bool picopass_device_load_data(PicopassDevice* dev, string_t path, bool s } do { - if(!flipper_format_file_open_existing(file, string_get_cstr(path))) break; + if(!flipper_format_file_open_existing(file, furi_string_get_cstr(path))) break; // Read and verify file header uint32_t version = 0; if(!flipper_format_read_header(file, temp_str, &version)) break; - if(string_cmp_str(temp_str, picopass_file_header) || (version != picopass_file_version)) { + if(furi_string_cmp_str(temp_str, picopass_file_header) || + (version != picopass_file_version)) { deprecated_version = true; break; } @@ -159,9 +160,9 @@ static bool picopass_device_load_data(PicopassDevice* dev, string_t path, bool s // Parse header blocks bool block_read = true; for(size_t i = 0; i < 6; i++) { - string_printf(temp_str, "Block %d", i); + furi_string_printf(temp_str, "Block %d", i); if(!flipper_format_read_hex( - file, string_get_cstr(temp_str), AA1[i].data, PICOPASS_BLOCK_LEN)) { + file, furi_string_get_cstr(temp_str), AA1[i].data, PICOPASS_BLOCK_LEN)) { block_read = false; break; } @@ -169,9 +170,9 @@ static bool picopass_device_load_data(PicopassDevice* dev, string_t path, bool s size_t app_limit = AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[0]; for(size_t i = 6; i < app_limit; i++) { - string_printf(temp_str, "Block %d", i); + furi_string_printf(temp_str, "Block %d", i); if(!flipper_format_read_hex( - file, string_get_cstr(temp_str), AA1[i].data, PICOPASS_BLOCK_LEN)) { + file, furi_string_get_cstr(temp_str), AA1[i].data, PICOPASS_BLOCK_LEN)) { block_read = false; break; } @@ -196,7 +197,7 @@ static bool picopass_device_load_data(PicopassDevice* dev, string_t path, bool s } } - string_clear(temp_str); + furi_string_free(temp_str); flipper_format_free(file); return parsed; @@ -208,7 +209,7 @@ void picopass_device_clear(PicopassDevice* dev) { picopass_device_data_clear(&dev->dev_data); memset(&dev->dev_data, 0, sizeof(dev->dev_data)); dev->format = PicopassDeviceSaveFormatHF; - string_reset(dev->load_path); + furi_string_reset(dev->load_path); } void picopass_device_free(PicopassDevice* picopass_dev) { @@ -216,7 +217,7 @@ void picopass_device_free(PicopassDevice* picopass_dev) { picopass_device_clear(picopass_dev); furi_record_close(RECORD_STORAGE); furi_record_close(RECORD_DIALOGS); - string_clear(picopass_dev->load_path); + furi_string_free(picopass_dev->load_path); free(picopass_dev); } @@ -224,8 +225,8 @@ bool picopass_file_select(PicopassDevice* dev) { furi_assert(dev); // Input events and views are managed by file_browser - string_t picopass_app_folder; - string_init_set_str(picopass_app_folder, PICOPASS_APP_FOLDER); + FuriString* picopass_app_folder; + picopass_app_folder = furi_string_alloc_set(PICOPASS_APP_FOLDER); DialogsFileBrowserOptions browser_options; dialog_file_browser_set_basic_options(&browser_options, PICOPASS_APP_EXTENSION, &I_Nfc_10px); @@ -233,17 +234,17 @@ bool picopass_file_select(PicopassDevice* dev) { bool res = dialog_file_browser_show( dev->dialogs, dev->load_path, picopass_app_folder, &browser_options); - string_clear(picopass_app_folder); + furi_string_free(picopass_app_folder); if(res) { - string_t filename; - string_init(filename); + FuriString* filename; + filename = furi_string_alloc(); path_extract_filename(dev->load_path, filename, true); - strncpy(dev->dev_name, string_get_cstr(filename), PICOPASS_DEV_NAME_MAX_LEN); + strncpy(dev->dev_name, furi_string_get_cstr(filename), PICOPASS_DEV_NAME_MAX_LEN); res = picopass_device_load_data(dev, dev->load_path, true); if(res) { picopass_device_set_name(dev, dev->dev_name); } - string_clear(filename); + furi_string_free(filename); } return res; @@ -262,18 +263,18 @@ bool picopass_device_delete(PicopassDevice* dev, bool use_load_path) { furi_assert(dev); bool deleted = false; - string_t file_path; - string_init(file_path); + FuriString* file_path; + file_path = furi_string_alloc(); do { // Delete original file - if(use_load_path && !string_empty_p(dev->load_path)) { - string_set(file_path, dev->load_path); + if(use_load_path && !furi_string_empty(dev->load_path)) { + furi_string_set(file_path, dev->load_path); } else { - string_printf( + furi_string_printf( file_path, "%s/%s%s", PICOPASS_APP_FOLDER, dev->dev_name, PICOPASS_APP_EXTENSION); } - if(!storage_simply_remove(dev->storage, string_get_cstr(file_path))) break; + if(!storage_simply_remove(dev->storage, furi_string_get_cstr(file_path))) break; deleted = true; } while(0); @@ -281,7 +282,7 @@ bool picopass_device_delete(PicopassDevice* dev, bool use_load_path) { dialog_message_show_storage_error(dev->dialogs, "Can not remove file"); } - string_clear(file_path); + furi_string_free(file_path); return deleted; } diff --git a/applications/plugins/picopass/picopass_device.h b/applications/plugins/picopass/picopass_device.h index 745b64bd5ec..cbb43d6ce71 100644 --- a/applications/plugins/picopass/picopass_device.h +++ b/applications/plugins/picopass/picopass_device.h @@ -71,7 +71,7 @@ typedef struct { DialogsApp* dialogs; PicopassDeviceData dev_data; char dev_name[PICOPASS_DEV_NAME_MAX_LEN + 1]; - string_t load_path; + FuriString* load_path; PicopassDeviceSaveFormat format; PicopassLoadingCallback loading_cb; void* loading_cb_ctx; diff --git a/applications/plugins/picopass/picopass_i.h b/applications/plugins/picopass/picopass_i.h index dec5a865f8d..8e011f22249 100644 --- a/applications/plugins/picopass/picopass_i.h +++ b/applications/plugins/picopass/picopass_i.h @@ -51,7 +51,7 @@ struct Picopass { PicopassDevice* dev; char text_store[PICOPASS_TEXT_STORE_SIZE + 1]; - string_t text_box_store; + FuriString* text_box_store; // Common Views Submenu* submenu; diff --git a/applications/plugins/picopass/scenes/picopass_scene_device_info.c b/applications/plugins/picopass/scenes/picopass_scene_device_info.c index 38891b67334..046e9c8e450 100644 --- a/applications/plugins/picopass/scenes/picopass_scene_device_info.c +++ b/applications/plugins/picopass/scenes/picopass_scene_device_info.c @@ -14,10 +14,10 @@ void picopass_scene_device_info_widget_callback( void picopass_scene_device_info_on_enter(void* context) { Picopass* picopass = context; - string_t credential_str; - string_t wiegand_str; - string_init(credential_str); - string_init(wiegand_str); + FuriString* credential_str; + FuriString* wiegand_str; + credential_str = furi_string_alloc(); + wiegand_str = furi_string_alloc(); DOLPHIN_DEED(DolphinDeedNfcReadSuccess); @@ -26,25 +26,31 @@ void picopass_scene_device_info_on_enter(void* context) { Widget* widget = picopass->widget; size_t bytesLength = 1 + pacs->record.bitLength / 8; - string_set_str(credential_str, ""); + furi_string_set(credential_str, ""); for(uint8_t i = PICOPASS_BLOCK_LEN - bytesLength; i < PICOPASS_BLOCK_LEN; i++) { - string_cat_printf(credential_str, " %02X", pacs->credential[i]); + furi_string_cat_printf(credential_str, " %02X", pacs->credential[i]); } if(pacs->record.valid) { - string_cat_printf( + furi_string_cat_printf( wiegand_str, "FC: %u CN: %u", pacs->record.FacilityCode, pacs->record.CardNumber); } else { - string_cat_printf(wiegand_str, "%d bits", pacs->record.bitLength); + furi_string_cat_printf(wiegand_str, "%d bits", pacs->record.bitLength); } widget_add_string_element( - widget, 64, 12, AlignCenter, AlignCenter, FontPrimary, string_get_cstr(wiegand_str)); + widget, 64, 12, AlignCenter, AlignCenter, FontPrimary, furi_string_get_cstr(wiegand_str)); widget_add_string_element( - widget, 64, 32, AlignCenter, AlignCenter, FontSecondary, string_get_cstr(credential_str)); + widget, + 64, + 32, + AlignCenter, + AlignCenter, + FontSecondary, + furi_string_get_cstr(credential_str)); - string_clear(credential_str); - string_clear(wiegand_str); + furi_string_free(credential_str); + furi_string_free(wiegand_str); widget_add_button_element( picopass->widget, diff --git a/applications/plugins/picopass/scenes/picopass_scene_read_card_success.c b/applications/plugins/picopass/scenes/picopass_scene_read_card_success.c index 785f3a7dde5..37f1db4f26e 100644 --- a/applications/plugins/picopass/scenes/picopass_scene_read_card_success.c +++ b/applications/plugins/picopass/scenes/picopass_scene_read_card_success.c @@ -15,12 +15,12 @@ void picopass_scene_read_card_success_widget_callback( void picopass_scene_read_card_success_on_enter(void* context) { Picopass* picopass = context; - string_t credential_str; - string_t wiegand_str; - string_t sio_str; - string_init(credential_str); - string_init(wiegand_str); - string_init(sio_str); + FuriString* credential_str; + FuriString* wiegand_str; + FuriString* sio_str; + credential_str = furi_string_alloc(); + wiegand_str = furi_string_alloc(); + sio_str = furi_string_alloc(); DOLPHIN_DEED(DolphinDeedNfcReadSuccess); @@ -32,10 +32,10 @@ void picopass_scene_read_card_success_on_enter(void* context) { Widget* widget = picopass->widget; if(pacs->record.bitLength == 0) { - string_cat_printf(wiegand_str, "Read Failed"); + furi_string_cat_printf(wiegand_str, "Read Failed"); if(pacs->se_enabled) { - string_cat_printf(credential_str, "SE enabled"); + furi_string_cat_printf(credential_str, "SE enabled"); } widget_add_button_element( @@ -47,20 +47,20 @@ void picopass_scene_read_card_success_on_enter(void* context) { } else { size_t bytesLength = 1 + pacs->record.bitLength / 8; - string_set_str(credential_str, ""); + furi_string_set(credential_str, ""); for(uint8_t i = PICOPASS_BLOCK_LEN - bytesLength; i < PICOPASS_BLOCK_LEN; i++) { - string_cat_printf(credential_str, " %02X", pacs->credential[i]); + furi_string_cat_printf(credential_str, " %02X", pacs->credential[i]); } if(pacs->record.valid) { - string_cat_printf( + furi_string_cat_printf( wiegand_str, "FC: %u CN: %u", pacs->record.FacilityCode, pacs->record.CardNumber); } else { - string_cat_printf(wiegand_str, "%d bits", pacs->record.bitLength); + furi_string_cat_printf(wiegand_str, "%d bits", pacs->record.bitLength); } if(pacs->sio) { - string_cat_printf(sio_str, "+SIO"); + furi_string_cat_printf(sio_str, "+SIO"); } widget_add_button_element( @@ -79,15 +79,21 @@ void picopass_scene_read_card_success_on_enter(void* context) { } widget_add_string_element( - widget, 64, 12, AlignCenter, AlignCenter, FontPrimary, string_get_cstr(wiegand_str)); + widget, 64, 12, AlignCenter, AlignCenter, FontPrimary, furi_string_get_cstr(wiegand_str)); widget_add_string_element( - widget, 64, 32, AlignCenter, AlignCenter, FontSecondary, string_get_cstr(credential_str)); + widget, + 64, + 32, + AlignCenter, + AlignCenter, + FontSecondary, + furi_string_get_cstr(credential_str)); widget_add_string_element( - widget, 64, 42, AlignCenter, AlignCenter, FontSecondary, string_get_cstr(sio_str)); + widget, 64, 42, AlignCenter, AlignCenter, FontSecondary, furi_string_get_cstr(sio_str)); - string_clear(credential_str); - string_clear(wiegand_str); - string_clear(sio_str); + furi_string_free(credential_str); + furi_string_free(wiegand_str); + furi_string_free(sio_str); view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewWidget); } diff --git a/applications/plugins/picopass/scenes/picopass_scene_save_name.c b/applications/plugins/picopass/scenes/picopass_scene_save_name.c index c5fa7dd1f1a..17ad5927a12 100644 --- a/applications/plugins/picopass/scenes/picopass_scene_save_name.c +++ b/applications/plugins/picopass/scenes/picopass_scene_save_name.c @@ -1,5 +1,4 @@ #include "../picopass_i.h" -#include "m-string.h" #include #include #include @@ -31,22 +30,22 @@ void picopass_scene_save_name_on_enter(void* context) { PICOPASS_DEV_NAME_MAX_LEN, dev_name_empty); - string_t folder_path; - string_init(folder_path); + FuriString* folder_path; + folder_path = furi_string_alloc(); - if(string_end_with_str_p(picopass->dev->load_path, PICOPASS_APP_EXTENSION)) { - path_extract_dirname(string_get_cstr(picopass->dev->load_path), folder_path); + if(furi_string_end_with(picopass->dev->load_path, PICOPASS_APP_EXTENSION)) { + path_extract_dirname(furi_string_get_cstr(picopass->dev->load_path), folder_path); } else { - string_set_str(folder_path, PICOPASS_APP_FOLDER); + furi_string_set(folder_path, PICOPASS_APP_FOLDER); } ValidatorIsFile* validator_is_file = validator_is_file_alloc_init( - string_get_cstr(folder_path), PICOPASS_APP_EXTENSION, picopass->dev->dev_name); + furi_string_get_cstr(folder_path), PICOPASS_APP_EXTENSION, picopass->dev->dev_name); text_input_set_validator(text_input, validator_is_file_callback, validator_is_file); view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewTextInput); - string_clear(folder_path); + furi_string_free(folder_path); } bool picopass_scene_save_name_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/services/bt/bt_cli.c b/applications/services/bt/bt_cli.c index ff5ebb448a3..02bf6cee4a3 100644 --- a/applications/services/bt/bt_cli.c +++ b/applications/services/bt/bt_cli.c @@ -7,18 +7,18 @@ #include "bt_settings.h" #include "bt_service/bt.h" -static void bt_cli_command_hci_info(Cli* cli, string_t args, void* context) { +static void bt_cli_command_hci_info(Cli* cli, FuriString* args, void* context) { UNUSED(cli); UNUSED(args); UNUSED(context); - string_t buffer; - string_init(buffer); + FuriString* buffer; + buffer = furi_string_alloc(); furi_hal_bt_dump_state(buffer); - printf("%s", string_get_cstr(buffer)); - string_clear(buffer); + printf("%s", furi_string_get_cstr(buffer)); + furi_string_free(buffer); } -static void bt_cli_command_carrier_tx(Cli* cli, string_t args, void* context) { +static void bt_cli_command_carrier_tx(Cli* cli, FuriString* args, void* context) { UNUSED(context); int channel = 0; int power = 0; @@ -50,7 +50,7 @@ static void bt_cli_command_carrier_tx(Cli* cli, string_t args, void* context) { } while(false); } -static void bt_cli_command_carrier_rx(Cli* cli, string_t args, void* context) { +static void bt_cli_command_carrier_rx(Cli* cli, FuriString* args, void* context) { UNUSED(context); int channel = 0; @@ -81,7 +81,7 @@ static void bt_cli_command_carrier_rx(Cli* cli, string_t args, void* context) { } while(false); } -static void bt_cli_command_packet_tx(Cli* cli, string_t args, void* context) { +static void bt_cli_command_packet_tx(Cli* cli, FuriString* args, void* context) { UNUSED(context); int channel = 0; int pattern = 0; @@ -129,7 +129,7 @@ static void bt_cli_command_packet_tx(Cli* cli, string_t args, void* context) { } while(false); } -static void bt_cli_command_packet_rx(Cli* cli, string_t args, void* context) { +static void bt_cli_command_packet_rx(Cli* cli, FuriString* args, void* context) { UNUSED(context); int channel = 0; int datarate = 1; @@ -178,12 +178,12 @@ static void bt_cli_print_usage() { } } -static void bt_cli(Cli* cli, string_t args, void* context) { +static void bt_cli(Cli* cli, FuriString* args, void* context) { UNUSED(context); furi_record_open(RECORD_BT); - string_t cmd; - string_init(cmd); + FuriString* cmd; + cmd = furi_string_alloc(); BtSettings bt_settings; bt_settings_load(&bt_settings); @@ -192,24 +192,24 @@ static void bt_cli(Cli* cli, string_t args, void* context) { bt_cli_print_usage(); break; } - if(string_cmp_str(cmd, "hci_info") == 0) { + if(furi_string_cmp_str(cmd, "hci_info") == 0) { bt_cli_command_hci_info(cli, args, NULL); break; } if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug) && furi_hal_bt_is_testing_supported()) { - if(string_cmp_str(cmd, "tx_carrier") == 0) { + if(furi_string_cmp_str(cmd, "tx_carrier") == 0) { bt_cli_command_carrier_tx(cli, args, NULL); break; } - if(string_cmp_str(cmd, "rx_carrier") == 0) { + if(furi_string_cmp_str(cmd, "rx_carrier") == 0) { bt_cli_command_carrier_rx(cli, args, NULL); break; } - if(string_cmp_str(cmd, "tx_packet") == 0) { + if(furi_string_cmp_str(cmd, "tx_packet") == 0) { bt_cli_command_packet_tx(cli, args, NULL); break; } - if(string_cmp_str(cmd, "rx_packet") == 0) { + if(furi_string_cmp_str(cmd, "rx_packet") == 0) { bt_cli_command_packet_rx(cli, args, NULL); break; } @@ -222,7 +222,7 @@ static void bt_cli(Cli* cli, string_t args, void* context) { furi_hal_bt_start_advertising(); } - string_clear(cmd); + furi_string_free(cmd); furi_record_close(RECORD_BT); } diff --git a/applications/services/bt/bt_service/bt.c b/applications/services/bt/bt_service/bt.c index bc80acc151a..2bb083f278a 100644 --- a/applications/services/bt/bt_service/bt.c +++ b/applications/services/bt/bt_service/bt.c @@ -75,14 +75,14 @@ static void bt_pin_code_hide(Bt* bt) { static bool bt_pin_code_verify_event_handler(Bt* bt, uint32_t pin) { furi_assert(bt); notification_message(bt->notification, &sequence_display_backlight_on); - string_t pin_str; + FuriString* pin_str; dialog_message_set_icon(bt->dialog_message, &I_BLE_Pairing_128x64, 0, 0); - string_init_printf(pin_str, "Verify code\n%06d", pin); + pin_str = furi_string_alloc_printf("Verify code\n%06d", pin); dialog_message_set_text( - bt->dialog_message, string_get_cstr(pin_str), 64, 4, AlignCenter, AlignTop); + bt->dialog_message, furi_string_get_cstr(pin_str), 64, 4, AlignCenter, AlignTop); dialog_message_set_buttons(bt->dialog_message, "Cancel", "OK", NULL); DialogMessageButton button = dialog_message_show(bt->dialogs, bt->dialog_message); - string_clear(pin_str); + furi_string_free(pin_str); return button == DialogMessageButtonCenter; } diff --git a/applications/services/cli/cli.c b/applications/services/cli/cli.c index e554ac898e3..f29dca9ce0b 100644 --- a/applications/services/cli/cli.c +++ b/applications/services/cli/cli.c @@ -11,8 +11,8 @@ Cli* cli_alloc() { CliCommandTree_init(cli->commands); - string_init(cli->last_line); - string_init(cli->line); + cli->last_line = furi_string_alloc(); + cli->line = furi_string_alloc(); cli->session = NULL; @@ -138,34 +138,26 @@ void cli_nl(Cli* cli) { void cli_prompt(Cli* cli) { UNUSED(cli); - printf("\r\n>: %s", string_get_cstr(cli->line)); + printf("\r\n>: %s", furi_string_get_cstr(cli->line)); fflush(stdout); } void cli_reset(Cli* cli) { // cli->last_line is cleared and cli->line's buffer moved to cli->last_line - string_move(cli->last_line, cli->line); + furi_string_move(cli->last_line, cli->line); // Reiniting cli->line - string_init(cli->line); + cli->line = furi_string_alloc(); cli->cursor_position = 0; } static void cli_handle_backspace(Cli* cli) { if(cli->cursor_position > 0) { - furi_assert(string_size(cli->line) > 0); + furi_assert(furi_string_size(cli->line) > 0); // Other side printf("\e[D\e[1P"); fflush(stdout); // Our side - string_t temp; - string_init(temp); - string_reserve(temp, string_size(cli->line) - 1); - string_set_strn(temp, string_get_cstr(cli->line), cli->cursor_position - 1); - string_cat_str(temp, string_get_cstr(cli->line) + cli->cursor_position); - - // cli->line is cleared and temp's buffer moved to cli->line - string_move(cli->line, temp); - // NO MEMORY LEAK, STOP REPORTING IT + furi_string_replace_at(cli->line, cli->cursor_position - 1, 1, ""); cli->cursor_position--; } else { @@ -174,11 +166,11 @@ static void cli_handle_backspace(Cli* cli) { } static void cli_normalize_line(Cli* cli) { - string_strim(cli->line); - cli->cursor_position = string_size(cli->line); + furi_string_trim(cli->line); + cli->cursor_position = furi_string_size(cli->line); } -static void cli_execute_command(Cli* cli, CliCommand* command, string_t args) { +static void cli_execute_command(Cli* cli, CliCommand* command, FuriString* args) { if(!(command->flags & CliCommandFlagInsomniaSafe)) { furi_hal_power_insomnia_enter(); } @@ -208,25 +200,25 @@ static void cli_execute_command(Cli* cli, CliCommand* command, string_t args) { static void cli_handle_enter(Cli* cli) { cli_normalize_line(cli); - if(string_size(cli->line) == 0) { + if(furi_string_size(cli->line) == 0) { cli_prompt(cli); return; } // Command and args container - string_t command; - string_init(command); - string_t args; - string_init(args); + FuriString* command; + command = furi_string_alloc(); + FuriString* args; + args = furi_string_alloc(); // Split command and args - size_t ws = string_search_char(cli->line, ' '); - if(ws == STRING_FAILURE) { - string_set(command, cli->line); + size_t ws = furi_string_search_char(cli->line, ' '); + if(ws == FURI_STRING_FAILURE) { + furi_string_set(command, cli->line); } else { - string_set_n(command, cli->line, 0, ws); - string_set_n(args, cli->line, ws, string_size(cli->line)); - string_strim(args); + furi_string_set_n(command, cli->line, 0, ws); + furi_string_set_n(args, cli->line, ws, furi_string_size(cli->line)); + furi_string_trim(args); } // Search for command @@ -244,7 +236,7 @@ static void cli_handle_enter(Cli* cli) { cli_nl(cli); printf( "`%s` command not found, use `help` or `?` to list all available commands", - string_get_cstr(command)); + furi_string_get_cstr(command)); cli_putc(cli, CliSymbolAsciiBell); } @@ -252,59 +244,59 @@ static void cli_handle_enter(Cli* cli) { cli_prompt(cli); // Cleanup command and args - string_clear(command); - string_clear(args); + furi_string_free(command); + furi_string_free(args); } static void cli_handle_autocomplete(Cli* cli) { cli_normalize_line(cli); - if(string_size(cli->line) == 0) { + if(furi_string_size(cli->line) == 0) { return; } cli_nl(cli); // Prepare common base for autocomplete - string_t common; - string_init(common); + FuriString* common; + common = furi_string_alloc(); // Iterate throw commands for M_EACH(cli_command, cli->commands, CliCommandTree_t) { // Process only if starts with line buffer - if(string_start_with_string_p(*cli_command->key_ptr, cli->line)) { + if(furi_string_start_with(*cli_command->key_ptr, cli->line)) { // Show autocomplete option - printf("%s\r\n", string_get_cstr(*cli_command->key_ptr)); + printf("%s\r\n", furi_string_get_cstr(*cli_command->key_ptr)); // Process common base for autocomplete - if(string_size(common) > 0) { + if(furi_string_size(common) > 0) { // Choose shortest string - const size_t key_size = string_size(*cli_command->key_ptr); - const size_t common_size = string_size(common); + const size_t key_size = furi_string_size(*cli_command->key_ptr); + const size_t common_size = furi_string_size(common); const size_t min_size = key_size > common_size ? common_size : key_size; size_t i = 0; while(i < min_size) { // Stop when do not match - if(string_get_char(*cli_command->key_ptr, i) != - string_get_char(common, i)) { + if(furi_string_get_char(*cli_command->key_ptr, i) != + furi_string_get_char(common, i)) { break; } i++; } // Cut right part if any - string_left(common, i); + furi_string_left(common, i); } else { // Start with something - string_set(common, *cli_command->key_ptr); + furi_string_set(common, *cli_command->key_ptr); } } } // Replace line buffer if autocomplete better - if(string_size(common) > string_size(cli->line)) { - string_set(cli->line, common); - cli->cursor_position = string_size(cli->line); + if(furi_string_size(common) > furi_string_size(cli->line)) { + furi_string_set(cli->line, common); + cli->cursor_position = furi_string_size(cli->line); } // Cleanup - string_clear(common); + furi_string_free(common); // Show prompt cli_prompt(cli); } @@ -312,16 +304,16 @@ static void cli_handle_autocomplete(Cli* cli) { static void cli_handle_escape(Cli* cli, char c) { if(c == 'A') { // Use previous command if line buffer is empty - if(string_size(cli->line) == 0 && string_cmp(cli->line, cli->last_line) != 0) { + if(furi_string_size(cli->line) == 0 && furi_string_cmp(cli->line, cli->last_line) != 0) { // Set line buffer and cursor position - string_set(cli->line, cli->last_line); - cli->cursor_position = string_size(cli->line); + furi_string_set(cli->line, cli->last_line); + cli->cursor_position = furi_string_size(cli->line); // Show new line to user - printf("%s", string_get_cstr(cli->line)); + printf("%s", furi_string_get_cstr(cli->line)); } } else if(c == 'B') { } else if(c == 'C') { - if(cli->cursor_position < string_size(cli->line)) { + if(cli->cursor_position < furi_string_size(cli->line)) { cli->cursor_position++; printf("\e[C"); } @@ -362,21 +354,13 @@ void cli_process_input(Cli* cli) { } else if(in_chr == CliSymbolAsciiCR) { cli_handle_enter(cli); } else if(in_chr >= 0x20 && in_chr < 0x7F) { - if(cli->cursor_position == string_size(cli->line)) { - string_push_back(cli->line, in_chr); + if(cli->cursor_position == furi_string_size(cli->line)) { + furi_string_push_back(cli->line, in_chr); cli_putc(cli, in_chr); } else { - // ToDo: better way? - string_t temp; - string_init(temp); - string_reserve(temp, string_size(cli->line) + 1); - string_set_strn(temp, string_get_cstr(cli->line), cli->cursor_position); - string_push_back(temp, in_chr); - string_cat_str(temp, string_get_cstr(cli->line) + cli->cursor_position); - - // cli->line is cleared and temp's buffer moved to cli->line - string_move(cli->line, temp); - // NO MEMORY LEAK, STOP REPORTING IT + // Insert character to line buffer + const char in_str[2] = {in_chr, 0}; + furi_string_replace_at(cli->line, cli->cursor_position, 0, in_str); // Print character in replace mode printf("\e[4h%c\e[4l", in_chr); @@ -394,14 +378,14 @@ void cli_add_command( CliCommandFlag flags, CliCallback callback, void* context) { - string_t name_str; - string_init_set_str(name_str, name); - string_strim(name_str); + FuriString* name_str; + name_str = furi_string_alloc_set(name); + furi_string_trim(name_str); size_t name_replace; do { - name_replace = string_replace_str(name_str, " ", "_"); - } while(name_replace != STRING_FAILURE); + name_replace = furi_string_replace(name_str, " ", "_"); + } while(name_replace != FURI_STRING_FAILURE); CliCommand c; c.callback = callback; @@ -412,24 +396,24 @@ void cli_add_command( CliCommandTree_set_at(cli->commands, name_str, c); furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk); - string_clear(name_str); + furi_string_free(name_str); } void cli_delete_command(Cli* cli, const char* name) { - string_t name_str; - string_init_set_str(name_str, name); - string_strim(name_str); + FuriString* name_str; + name_str = furi_string_alloc_set(name); + furi_string_trim(name_str); size_t name_replace; do { - name_replace = string_replace_str(name_str, " ", "_"); - } while(name_replace != STRING_FAILURE); + name_replace = furi_string_replace(name_str, " ", "_"); + } while(name_replace != FURI_STRING_FAILURE); furi_check(furi_mutex_acquire(cli->mutex, FuriWaitForever) == FuriStatusOk); CliCommandTree_erase(cli->commands, name_str); furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk); - string_clear(name_str); + furi_string_free(name_str); } void cli_session_open(Cli* cli, void* session) { diff --git a/applications/services/cli/cli.h b/applications/services/cli/cli.h index 549e72cc523..09f54f056f0 100644 --- a/applications/services/cli/cli.h +++ b/applications/services/cli/cli.h @@ -4,8 +4,7 @@ */ #pragma once - -#include +#include #ifdef __cplusplus extern "C" { @@ -43,7 +42,7 @@ typedef struct Cli Cli; * @param args string with what was passed after command * @param context pointer to whatever you gave us on cli_add_command */ -typedef void (*CliCallback)(Cli* cli, string_t args, void* context); +typedef void (*CliCallback)(Cli* cli, FuriString* args, void* context); /** Add cli command Registers you command callback * diff --git a/applications/services/cli/cli_command_gpio.c b/applications/services/cli/cli_command_gpio.c index d5ec8d65440..54671eda439 100644 --- a/applications/services/cli/cli_command_gpio.c +++ b/applications/services/cli/cli_command_gpio.c @@ -35,11 +35,11 @@ void cli_command_gpio_print_usage() { printf("\tread \t - Read gpio value\r\n"); } -static bool pin_name_to_int(string_t pin_name, size_t* result) { +static bool pin_name_to_int(FuriString* pin_name, size_t* result) { bool found = false; bool debug = furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug); for(size_t i = 0; i < COUNT_OF(cli_command_gpio_pins); i++) { - if(!string_cmp(pin_name, cli_command_gpio_pins[i].name)) { + if(!furi_string_cmp(pin_name, cli_command_gpio_pins[i].name)) { if(!cli_command_gpio_pins[i].debug || (cli_command_gpio_pins[i].debug && debug)) { *result = i; found = true; @@ -63,29 +63,29 @@ static void gpio_print_pins(void) { typedef enum { OK, ERR_CMD_SYNTAX, ERR_PIN, ERR_VALUE } GpioParseError; -static GpioParseError gpio_command_parse(string_t args, size_t* pin_num, uint8_t* value) { - string_t pin_name; - string_init(pin_name); +static GpioParseError gpio_command_parse(FuriString* args, size_t* pin_num, uint8_t* value) { + FuriString* pin_name; + pin_name = furi_string_alloc(); - size_t ws = string_search_char(args, ' '); - if(ws == STRING_FAILURE) { + size_t ws = furi_string_search_char(args, ' '); + if(ws == FURI_STRING_FAILURE) { return ERR_CMD_SYNTAX; } - string_set_n(pin_name, args, 0, ws); - string_right(args, ws); - string_strim(args); + furi_string_set_n(pin_name, args, 0, ws); + furi_string_right(args, ws); + furi_string_trim(args); if(!pin_name_to_int(pin_name, pin_num)) { - string_clear(pin_name); + furi_string_free(pin_name); return ERR_PIN; } - string_clear(pin_name); + furi_string_free(pin_name); - if(!string_cmp(args, "0")) { + if(!furi_string_cmp(args, "0")) { *value = 0; - } else if(!string_cmp(args, "1")) { + } else if(!furi_string_cmp(args, "1")) { *value = 1; } else { return ERR_VALUE; @@ -94,7 +94,7 @@ static GpioParseError gpio_command_parse(string_t args, size_t* pin_num, uint8_t return OK; } -void cli_command_gpio_mode(Cli* cli, string_t args, void* context) { +void cli_command_gpio_mode(Cli* cli, FuriString* args, void* context) { UNUSED(cli); UNUSED(context); @@ -104,7 +104,7 @@ void cli_command_gpio_mode(Cli* cli, string_t args, void* context) { GpioParseError err = gpio_command_parse(args, &num, &value); if(ERR_CMD_SYNTAX == err) { - cli_print_usage("gpio mode", " <0|1>", string_get_cstr(args)); + cli_print_usage("gpio mode", " <0|1>", furi_string_get_cstr(args)); return; } else if(ERR_PIN == err) { gpio_print_pins(); @@ -134,7 +134,7 @@ void cli_command_gpio_mode(Cli* cli, string_t args, void* context) { } } -void cli_command_gpio_read(Cli* cli, string_t args, void* context) { +void cli_command_gpio_read(Cli* cli, FuriString* args, void* context) { UNUSED(cli); UNUSED(context); @@ -156,7 +156,7 @@ void cli_command_gpio_read(Cli* cli, string_t args, void* context) { printf("Pin %s <= %u", cli_command_gpio_pins[num].name, val); } -void cli_command_gpio_set(Cli* cli, string_t args, void* context) { +void cli_command_gpio_set(Cli* cli, FuriString* args, void* context) { UNUSED(context); size_t num = 0; @@ -164,7 +164,7 @@ void cli_command_gpio_set(Cli* cli, string_t args, void* context) { GpioParseError err = gpio_command_parse(args, &num, &value); if(ERR_CMD_SYNTAX == err) { - cli_print_usage("gpio set", " <0|1>", string_get_cstr(args)); + cli_print_usage("gpio set", " <0|1>", furi_string_get_cstr(args)); return; } else if(ERR_PIN == err) { gpio_print_pins(); @@ -196,9 +196,9 @@ void cli_command_gpio_set(Cli* cli, string_t args, void* context) { printf("Pin %s => %u", cli_command_gpio_pins[num].name, !!value); } -void cli_command_gpio(Cli* cli, string_t args, void* context) { - string_t cmd; - string_init(cmd); +void cli_command_gpio(Cli* cli, FuriString* args, void* context) { + FuriString* cmd; + cmd = furi_string_alloc(); do { if(!args_read_string_and_trim(args, cmd)) { @@ -206,17 +206,17 @@ void cli_command_gpio(Cli* cli, string_t args, void* context) { break; } - if(string_cmp_str(cmd, "mode") == 0) { + if(furi_string_cmp_str(cmd, "mode") == 0) { cli_command_gpio_mode(cli, args, context); break; } - if(string_cmp_str(cmd, "set") == 0) { + if(furi_string_cmp_str(cmd, "set") == 0) { cli_command_gpio_set(cli, args, context); break; } - if(string_cmp_str(cmd, "read") == 0) { + if(furi_string_cmp_str(cmd, "read") == 0) { cli_command_gpio_read(cli, args, context); break; } @@ -224,5 +224,5 @@ void cli_command_gpio(Cli* cli, string_t args, void* context) { cli_command_gpio_print_usage(); } while(false); - string_clear(cmd); + furi_string_free(cmd); } diff --git a/applications/services/cli/cli_command_gpio.h b/applications/services/cli/cli_command_gpio.h index c9b908a0853..7ae5aa62599 100644 --- a/applications/services/cli/cli_command_gpio.h +++ b/applications/services/cli/cli_command_gpio.h @@ -2,4 +2,4 @@ #include "cli_i.h" -void cli_command_gpio(Cli* cli, string_t args, void* context); +void cli_command_gpio(Cli* cli, FuriString* args, void* context); diff --git a/applications/services/cli/cli_commands.c b/applications/services/cli/cli_commands.c index a6dd672fc14..21927f81f28 100644 --- a/applications/services/cli/cli_commands.c +++ b/applications/services/cli/cli_commands.c @@ -22,13 +22,13 @@ void cli_command_device_info_callback(const char* key, const char* value, bool l * Device Info Command * This command is intended to be used by humans */ -void cli_command_device_info(Cli* cli, string_t args, void* context) { +void cli_command_device_info(Cli* cli, FuriString* args, void* context) { UNUSED(cli); UNUSED(args); furi_hal_info_get(cli_command_device_info_callback, context); } -void cli_command_help(Cli* cli, string_t args, void* context) { +void cli_command_help(Cli* cli, FuriString* args, void* context) { UNUSED(args); UNUSED(context); printf("Commands we have:"); @@ -49,34 +49,34 @@ void cli_command_help(Cli* cli, string_t args, void* context) { printf("\r\n"); // Left Column if(!CliCommandTree_end_p(it_left)) { - printf("%-30s", string_get_cstr(*CliCommandTree_ref(it_left)->key_ptr)); + printf("%-30s", furi_string_get_cstr(*CliCommandTree_ref(it_left)->key_ptr)); CliCommandTree_next(it_left); } // Right Column if(!CliCommandTree_end_p(it_right)) { - printf("%s", string_get_cstr(*CliCommandTree_ref(it_right)->key_ptr)); + printf("%s", furi_string_get_cstr(*CliCommandTree_ref(it_right)->key_ptr)); CliCommandTree_next(it_right); } }; - if(string_size(args) > 0) { + if(furi_string_size(args) > 0) { cli_nl(); printf("Also I have no clue what '"); - printf("%s", string_get_cstr(args)); + printf("%s", furi_string_get_cstr(args)); printf("' is."); } } -void cli_command_date(Cli* cli, string_t args, void* context) { +void cli_command_date(Cli* cli, FuriString* args, void* context) { UNUSED(cli); UNUSED(context); FuriHalRtcDateTime datetime = {0}; - if(string_size(args) > 0) { + if(furi_string_size(args) > 0) { uint16_t hours, minutes, seconds, month, day, year, weekday; int ret = sscanf( - string_get_cstr(args), + furi_string_get_cstr(args), "%hu-%hu-%hu %hu:%hu:%hu %hu", &year, &month, @@ -101,7 +101,7 @@ void cli_command_date(Cli* cli, string_t args, void* context) { "Invalid datetime format, use `%s`. sscanf %d %s", "%Y-%m-%d %H:%M:%S %u", ret, - string_get_cstr(args)); + furi_string_get_cstr(args)); return; } @@ -143,7 +143,7 @@ void cli_command_log_tx_callback(const uint8_t* buffer, size_t size, void* conte xStreamBufferSend(context, buffer, size, 0); } -void cli_command_log(Cli* cli, string_t args, void* context) { +void cli_command_log(Cli* cli, FuriString* args, void* context) { UNUSED(args); UNUSED(context); StreamBufferHandle_t ring = xStreamBufferCreate(CLI_COMMAND_LOG_RING_SIZE, 1); @@ -162,75 +162,75 @@ void cli_command_log(Cli* cli, string_t args, void* context) { vStreamBufferDelete(ring); } -void cli_command_vibro(Cli* cli, string_t args, void* context) { +void cli_command_vibro(Cli* cli, FuriString* args, void* context) { UNUSED(cli); UNUSED(context); - if(!string_cmp(args, "0")) { + if(!furi_string_cmp(args, "0")) { NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); notification_message_block(notification, &sequence_reset_vibro); furi_record_close(RECORD_NOTIFICATION); - } else if(!string_cmp(args, "1")) { + } else if(!furi_string_cmp(args, "1")) { NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); notification_message_block(notification, &sequence_set_vibro_on); furi_record_close(RECORD_NOTIFICATION); } else { - cli_print_usage("vibro", "<1|0>", string_get_cstr(args)); + cli_print_usage("vibro", "<1|0>", furi_string_get_cstr(args)); } } -void cli_command_debug(Cli* cli, string_t args, void* context) { +void cli_command_debug(Cli* cli, FuriString* args, void* context) { UNUSED(cli); UNUSED(context); - if(!string_cmp(args, "0")) { + if(!furi_string_cmp(args, "0")) { furi_hal_rtc_reset_flag(FuriHalRtcFlagDebug); loader_update_menu(); printf("Debug disabled."); - } else if(!string_cmp(args, "1")) { + } else if(!furi_string_cmp(args, "1")) { furi_hal_rtc_set_flag(FuriHalRtcFlagDebug); loader_update_menu(); printf("Debug enabled."); } else { - cli_print_usage("debug", "<1|0>", string_get_cstr(args)); + cli_print_usage("debug", "<1|0>", furi_string_get_cstr(args)); } } -void cli_command_led(Cli* cli, string_t args, void* context) { +void cli_command_led(Cli* cli, FuriString* args, void* context) { UNUSED(cli); UNUSED(context); // Get first word as light name NotificationMessage notification_led_message; - string_t light_name; - string_init(light_name); - size_t ws = string_search_char(args, ' '); - if(ws == STRING_FAILURE) { - cli_print_usage("led", " <0-255>", string_get_cstr(args)); - string_clear(light_name); + FuriString* light_name; + light_name = furi_string_alloc(); + size_t ws = furi_string_search_char(args, ' '); + if(ws == FURI_STRING_FAILURE) { + cli_print_usage("led", " <0-255>", furi_string_get_cstr(args)); + furi_string_free(light_name); return; } else { - string_set_n(light_name, args, 0, ws); - string_right(args, ws); - string_strim(args); + furi_string_set_n(light_name, args, 0, ws); + furi_string_right(args, ws); + furi_string_trim(args); } // Check light name - if(!string_cmp(light_name, "r")) { + if(!furi_string_cmp(light_name, "r")) { notification_led_message.type = NotificationMessageTypeLedRed; - } else if(!string_cmp(light_name, "g")) { + } else if(!furi_string_cmp(light_name, "g")) { notification_led_message.type = NotificationMessageTypeLedGreen; - } else if(!string_cmp(light_name, "b")) { + } else if(!furi_string_cmp(light_name, "b")) { notification_led_message.type = NotificationMessageTypeLedBlue; - } else if(!string_cmp(light_name, "bl")) { + } else if(!furi_string_cmp(light_name, "bl")) { notification_led_message.type = NotificationMessageTypeLedDisplayBacklight; } else { - cli_print_usage("led", " <0-255>", string_get_cstr(args)); - string_clear(light_name); + cli_print_usage("led", " <0-255>", furi_string_get_cstr(args)); + furi_string_free(light_name); return; } - string_clear(light_name); + furi_string_free(light_name); // Read light value from the rest of the string char* end_ptr; - uint32_t value = strtoul(string_get_cstr(args), &end_ptr, 0); + uint32_t value = strtoul(furi_string_get_cstr(args), &end_ptr, 0); if(!(value < 256 && *end_ptr == '\0')) { - cli_print_usage("led", " <0-255>", string_get_cstr(args)); + cli_print_usage("led", " <0-255>", furi_string_get_cstr(args)); return; } @@ -249,7 +249,7 @@ void cli_command_led(Cli* cli, string_t args, void* context) { furi_record_close(RECORD_NOTIFICATION); } -void cli_command_ps(Cli* cli, string_t args, void* context) { +void cli_command_ps(Cli* cli, FuriString* args, void* context) { UNUSED(cli); UNUSED(args); UNUSED(context); @@ -272,7 +272,7 @@ void cli_command_ps(Cli* cli, string_t args, void* context) { printf("\r\nTotal: %d", thread_num); } -void cli_command_free(Cli* cli, string_t args, void* context) { +void cli_command_free(Cli* cli, FuriString* args, void* context) { UNUSED(cli); UNUSED(args); UNUSED(context); @@ -286,7 +286,7 @@ void cli_command_free(Cli* cli, string_t args, void* context) { printf("Maximum pool block: %d\r\n", memmgr_pool_get_max_block()); } -void cli_command_free_blocks(Cli* cli, string_t args, void* context) { +void cli_command_free_blocks(Cli* cli, FuriString* args, void* context) { UNUSED(cli); UNUSED(args); UNUSED(context); @@ -294,7 +294,7 @@ void cli_command_free_blocks(Cli* cli, string_t args, void* context) { memmgr_heap_printf_free_blocks(); } -void cli_command_i2c(Cli* cli, string_t args, void* context) { +void cli_command_i2c(Cli* cli, FuriString* args, void* context) { UNUSED(cli); UNUSED(args); UNUSED(context); diff --git a/applications/services/cli/cli_i.h b/applications/services/cli/cli_i.h index ba4582d0dfc..858cd0c8f82 100644 --- a/applications/services/cli/cli_i.h +++ b/applications/services/cli/cli_i.h @@ -36,8 +36,8 @@ struct CliSession { BPTREE_DEF2( CliCommandTree, CLI_COMMANDS_TREE_RANK, - string_t, - STRING_OPLIST, + FuriString*, + FURI_STRING_OPLIST, CliCommand, M_POD_OPLIST) @@ -47,8 +47,8 @@ struct Cli { CliCommandTree_t commands; FuriMutex* mutex; FuriSemaphore* idle_sem; - string_t last_line; - string_t line; + FuriString* last_line; + FuriString* line; CliSession* session; size_t cursor_position; diff --git a/applications/services/crypto/crypto_cli.c b/applications/services/crypto/crypto_cli.c index 26e1fb9c0db..a64a3ad0b04 100644 --- a/applications/services/crypto/crypto_cli.c +++ b/applications/services/crypto/crypto_cli.c @@ -17,7 +17,7 @@ void crypto_cli_print_usage() { "\tstore_key \t - Store key in secure enclave. !!! NON-REVERSABLE OPERATION - READ MANUAL FIRST !!!\r\n"); }; -void crypto_cli_encrypt(Cli* cli, string_t args) { +void crypto_cli_encrypt(Cli* cli, FuriString* args) { int key_slot = 0; bool key_loaded = false; uint8_t iv[16]; @@ -41,8 +41,8 @@ void crypto_cli_encrypt(Cli* cli, string_t args) { printf("Enter plain text and press Ctrl+C to complete encryption:\r\n"); - string_t input; - string_init(input); + FuriString* input; + input = furi_string_alloc(); char c; while(cli_read(cli, (uint8_t*)&c, 1) == 1) { if(c == CliSymbolAsciiETX) { @@ -51,14 +51,14 @@ void crypto_cli_encrypt(Cli* cli, string_t args) { } else if(c >= 0x20 && c < 0x7F) { putc(c, stdout); fflush(stdout); - string_push_back(input, c); + furi_string_push_back(input, c); } else if(c == CliSymbolAsciiCR) { printf("\r\n"); - string_cat_str(input, "\r\n"); + furi_string_cat(input, "\r\n"); } } - size_t size = string_size(input); + size_t size = furi_string_size(input); if(size > 0) { // C-string null termination and block alignments size++; @@ -66,9 +66,10 @@ void crypto_cli_encrypt(Cli* cli, string_t args) { if(remain) { size = size - remain + 16; } - string_reserve(input, size); + furi_string_reserve(input, size); uint8_t* output = malloc(size); - if(!furi_hal_crypto_encrypt((const uint8_t*)string_get_cstr(input), output, size)) { + if(!furi_hal_crypto_encrypt( + (const uint8_t*)furi_string_get_cstr(input), output, size)) { printf("Failed to encrypt input"); } else { printf("Hex-encoded encrypted data:\r\n"); @@ -83,7 +84,7 @@ void crypto_cli_encrypt(Cli* cli, string_t args) { printf("No input"); } - string_clear(input); + furi_string_free(input); } while(0); if(key_loaded) { @@ -91,7 +92,7 @@ void crypto_cli_encrypt(Cli* cli, string_t args) { } } -void crypto_cli_decrypt(Cli* cli, string_t args) { +void crypto_cli_decrypt(Cli* cli, FuriString* args) { int key_slot = 0; bool key_loaded = false; uint8_t iv[16]; @@ -115,8 +116,8 @@ void crypto_cli_decrypt(Cli* cli, string_t args) { printf("Enter Hex-encoded data and press Ctrl+C to complete decryption:\r\n"); - string_t hex_input; - string_init(hex_input); + FuriString* hex_input; + hex_input = furi_string_alloc(); char c; while(cli_read(cli, (uint8_t*)&c, 1) == 1) { if(c == CliSymbolAsciiETX) { @@ -125,14 +126,14 @@ void crypto_cli_decrypt(Cli* cli, string_t args) { } else if(c >= 0x20 && c < 0x7F) { putc(c, stdout); fflush(stdout); - string_push_back(hex_input, c); + furi_string_push_back(hex_input, c); } else if(c == CliSymbolAsciiCR) { printf("\r\n"); } } - string_strim(hex_input); - size_t hex_size = string_size(hex_input); + furi_string_trim(hex_input); + size_t hex_size = furi_string_size(hex_input); if(hex_size > 0 && hex_size % 2 == 0) { size_t size = hex_size / 2; uint8_t* input = malloc(size); @@ -155,7 +156,7 @@ void crypto_cli_decrypt(Cli* cli, string_t args) { printf("Invalid or empty input"); } - string_clear(hex_input); + furi_string_free(hex_input); } while(0); if(key_loaded) { @@ -163,7 +164,7 @@ void crypto_cli_decrypt(Cli* cli, string_t args) { } } -void crypto_cli_has_key(Cli* cli, string_t args) { +void crypto_cli_has_key(Cli* cli, FuriString* args) { UNUSED(cli); int key_slot = 0; uint8_t iv[16]; @@ -185,12 +186,12 @@ void crypto_cli_has_key(Cli* cli, string_t args) { } while(0); } -void crypto_cli_store_key(Cli* cli, string_t args) { +void crypto_cli_store_key(Cli* cli, FuriString* args) { UNUSED(cli); int key_slot = 0; int key_size = 0; - string_t key_type; - string_init(key_type); + FuriString* key_type; + key_type = furi_string_alloc(); uint8_t data[32 + 12] = {}; FuriHalCryptoKey key; @@ -207,19 +208,19 @@ void crypto_cli_store_key(Cli* cli, string_t args) { break; } - if(string_cmp_str(key_type, "master") == 0) { + if(furi_string_cmp_str(key_type, "master") == 0) { if(key_slot != 0) { printf("Master keyslot must be is 0"); break; } key.type = FuriHalCryptoKeyTypeMaster; - } else if(string_cmp_str(key_type, "simple") == 0) { + } else if(furi_string_cmp_str(key_type, "simple") == 0) { if(key_slot < 1 || key_slot > 99) { printf("Simple keyslot must be in range"); break; } key.type = FuriHalCryptoKeyTypeSimple; - } else if(string_cmp_str(key_type, "encrypted") == 0) { + } else if(furi_string_cmp_str(key_type, "encrypted") == 0) { key.type = FuriHalCryptoKeyTypeEncrypted; data_size += 12; } else { @@ -275,13 +276,13 @@ void crypto_cli_store_key(Cli* cli, string_t args) { } } while(0); - string_clear(key_type); + furi_string_free(key_type); } -static void crypto_cli(Cli* cli, string_t args, void* context) { +static void crypto_cli(Cli* cli, FuriString* args, void* context) { UNUSED(context); - string_t cmd; - string_init(cmd); + FuriString* cmd; + cmd = furi_string_alloc(); do { if(!args_read_string_and_trim(args, cmd)) { @@ -289,22 +290,22 @@ static void crypto_cli(Cli* cli, string_t args, void* context) { break; } - if(string_cmp_str(cmd, "encrypt") == 0) { + if(furi_string_cmp_str(cmd, "encrypt") == 0) { crypto_cli_encrypt(cli, args); break; } - if(string_cmp_str(cmd, "decrypt") == 0) { + if(furi_string_cmp_str(cmd, "decrypt") == 0) { crypto_cli_decrypt(cli, args); break; } - if(string_cmp_str(cmd, "has_key") == 0) { + if(furi_string_cmp_str(cmd, "has_key") == 0) { crypto_cli_has_key(cli, args); break; } - if(string_cmp_str(cmd, "store_key") == 0) { + if(furi_string_cmp_str(cmd, "store_key") == 0) { crypto_cli_store_key(cli, args); break; } @@ -312,7 +313,7 @@ static void crypto_cli(Cli* cli, string_t args, void* context) { crypto_cli_print_usage(); } while(false); - string_clear(cmd); + furi_string_free(cmd); } void crypto_on_system_start() { diff --git a/applications/services/desktop/animations/animation_manager.c b/applications/services/desktop/animations/animation_manager.c index 1e2a521e121..36c5b3975d3 100644 --- a/applications/services/desktop/animations/animation_manager.c +++ b/applications/services/desktop/animations/animation_manager.c @@ -2,7 +2,6 @@ #include #include #include -#include #include #include #include @@ -50,7 +49,7 @@ struct AnimationManager { AnimationManagerSetNewIdleAnimationCallback new_idle_callback; AnimationManagerSetNewIdleAnimationCallback check_blocking_callback; void* context; - string_t freezed_animation_name; + FuriString* freezed_animation_name; int32_t freezed_animation_time_left; ViewStack* view_stack; }; @@ -279,7 +278,7 @@ AnimationManager* animation_manager_alloc(void) { animation_manager->view_stack = view_stack_alloc(); View* animation_view = bubble_animation_get_view(animation_manager->animation_view); view_stack_add_view(animation_manager->view_stack, animation_view); - string_init(animation_manager->freezed_animation_name); + animation_manager->freezed_animation_name = furi_string_alloc(); animation_manager->idle_animation_timer = furi_timer_alloc(animation_manager_timer_callback, FuriTimerTypeOnce, animation_manager); @@ -317,7 +316,7 @@ void animation_manager_free(AnimationManager* animation_manager) { storage_get_pubsub(storage), animation_manager->pubsub_subscription_storage); furi_record_close(RECORD_STORAGE); - string_clear(animation_manager->freezed_animation_name); + furi_string_free(animation_manager->freezed_animation_name); View* animation_view = bubble_animation_get_view(animation_manager->animation_view); view_stack_remove_view(animation_manager->view_stack, animation_view); bubble_animation_view_free(animation_manager->animation_view); @@ -433,7 +432,7 @@ bool animation_manager_is_animation_loaded(AnimationManager* animation_manager) void animation_manager_unload_and_stall_animation(AnimationManager* animation_manager) { furi_assert(animation_manager); furi_assert(animation_manager->current_animation); - furi_assert(!string_size(animation_manager->freezed_animation_name)); + furi_assert(!furi_string_size(animation_manager->freezed_animation_name)); furi_assert( (animation_manager->state == AnimationManagerStateIdle) || (animation_manager->state == AnimationManagerStateBlocked)); @@ -461,7 +460,7 @@ void animation_manager_unload_and_stall_animation(AnimationManager* animation_ma StorageAnimationManifestInfo* meta = animation_storage_get_meta(animation_manager->current_animation); /* copy str, not move, because it can be internal animation */ - string_set_str(animation_manager->freezed_animation_name, meta->name); + furi_string_set(animation_manager->freezed_animation_name, meta->name); bubble_animation_freeze(animation_manager->animation_view); animation_storage_free_storage_animation(&animation_manager->current_animation); @@ -470,14 +469,14 @@ void animation_manager_unload_and_stall_animation(AnimationManager* animation_ma void animation_manager_load_and_continue_animation(AnimationManager* animation_manager) { furi_assert(animation_manager); furi_assert(!animation_manager->current_animation); - furi_assert(string_size(animation_manager->freezed_animation_name)); + furi_assert(furi_string_size(animation_manager->freezed_animation_name)); furi_assert( (animation_manager->state == AnimationManagerStateFreezedIdle) || (animation_manager->state == AnimationManagerStateFreezedBlocked)); if(animation_manager->state == AnimationManagerStateFreezedBlocked) { StorageAnimation* restore_animation = animation_storage_find_animation( - string_get_cstr(animation_manager->freezed_animation_name)); + furi_string_get_cstr(animation_manager->freezed_animation_name)); /* all blocked animations must be in flipper -> we can * always find blocking animation */ furi_assert(restore_animation); @@ -489,7 +488,7 @@ void animation_manager_load_and_continue_animation(AnimationManager* animation_m if(!blocked) { /* if no blocking - try restore last one idle */ StorageAnimation* restore_animation = animation_storage_find_animation( - string_get_cstr(animation_manager->freezed_animation_name)); + furi_string_get_cstr(animation_manager->freezed_animation_name)); if(restore_animation) { Dolphin* dolphin = furi_record_open(RECORD_DOLPHIN); DolphinStats stats = dolphin_stats(dolphin); @@ -517,7 +516,7 @@ void animation_manager_load_and_continue_animation(AnimationManager* animation_m FURI_LOG_E( TAG, "Failed to restore \'%s\'", - string_get_cstr(animation_manager->freezed_animation_name)); + furi_string_get_cstr(animation_manager->freezed_animation_name)); } } } else { @@ -535,7 +534,7 @@ void animation_manager_load_and_continue_animation(AnimationManager* animation_m animation_storage_get_meta(animation_manager->current_animation)->name); bubble_animation_unfreeze(animation_manager->animation_view); - string_reset(animation_manager->freezed_animation_name); + furi_string_reset(animation_manager->freezed_animation_name); furi_assert(animation_manager->current_animation); } diff --git a/applications/services/desktop/animations/animation_storage.c b/applications/services/desktop/animations/animation_storage.c index e0c6bf4110a..922d7dc85af 100644 --- a/applications/services/desktop/animations/animation_storage.c +++ b/applications/services/desktop/animations/animation_storage.c @@ -5,7 +5,6 @@ #include #include #include -#include #include "animation_manager.h" #include "animation_storage.h" @@ -32,8 +31,8 @@ static bool animation_storage_load_single_manifest_info( Storage* storage = furi_record_open(RECORD_STORAGE); FlipperFormat* file = flipper_format_file_alloc(storage); flipper_format_set_strict_mode(file, true); - string_t read_string; - string_init(read_string); + FuriString* read_string; + read_string = furi_string_alloc(); do { uint32_t u32value; @@ -41,20 +40,20 @@ static bool animation_storage_load_single_manifest_info( if(!flipper_format_file_open_existing(file, ANIMATION_MANIFEST_FILE)) break; if(!flipper_format_read_header(file, read_string, &u32value)) break; - if(string_cmp_str(read_string, "Flipper Animation Manifest")) break; + if(furi_string_cmp_str(read_string, "Flipper Animation Manifest")) break; manifest_info->name = NULL; /* skip other animation names */ flipper_format_set_strict_mode(file, false); while(flipper_format_read_string(file, "Name", read_string) && - string_cmp_str(read_string, name)) + furi_string_cmp_str(read_string, name)) ; - if(string_cmp_str(read_string, name)) break; + if(furi_string_cmp_str(read_string, name)) break; flipper_format_set_strict_mode(file, true); - manifest_info->name = malloc(string_size(read_string) + 1); - strcpy((char*)manifest_info->name, string_get_cstr(read_string)); + manifest_info->name = malloc(furi_string_size(read_string) + 1); + strcpy((char*)manifest_info->name, furi_string_get_cstr(read_string)); if(!flipper_format_read_uint32(file, "Min butthurt", &u32value, 1)) break; manifest_info->min_butthurt = u32value; @@ -72,7 +71,7 @@ static bool animation_storage_load_single_manifest_info( if(!result && manifest_info->name) { free((void*)manifest_info->name); } - string_clear(read_string); + furi_string_free(read_string); flipper_format_free(file); furi_record_close(RECORD_STORAGE); @@ -88,8 +87,8 @@ void animation_storage_fill_animation_list(StorageAnimationList_t* animation_lis FlipperFormat* file = flipper_format_file_alloc(storage); /* Forbid skipping fields */ flipper_format_set_strict_mode(file, true); - string_t read_string; - string_init(read_string); + FuriString* read_string; + read_string = furi_string_alloc(); do { uint32_t u32value; @@ -98,7 +97,7 @@ void animation_storage_fill_animation_list(StorageAnimationList_t* animation_lis if(FSE_OK != storage_sd_status(storage)) break; if(!flipper_format_file_open_existing(file, ANIMATION_MANIFEST_FILE)) break; if(!flipper_format_read_header(file, read_string, &u32value)) break; - if(string_cmp_str(read_string, "Flipper Animation Manifest")) break; + if(furi_string_cmp_str(read_string, "Flipper Animation Manifest")) break; do { storage_animation = malloc(sizeof(StorageAnimation)); storage_animation->external = true; @@ -106,8 +105,9 @@ void animation_storage_fill_animation_list(StorageAnimationList_t* animation_lis storage_animation->manifest_info.name = NULL; if(!flipper_format_read_string(file, "Name", read_string)) break; - storage_animation->manifest_info.name = malloc(string_size(read_string) + 1); - strcpy((char*)storage_animation->manifest_info.name, string_get_cstr(read_string)); + storage_animation->manifest_info.name = malloc(furi_string_size(read_string) + 1); + strcpy( + (char*)storage_animation->manifest_info.name, furi_string_get_cstr(read_string)); if(!flipper_format_read_uint32(file, "Min butthurt", &u32value, 1)) break; storage_animation->manifest_info.min_butthurt = u32value; @@ -126,7 +126,7 @@ void animation_storage_fill_animation_list(StorageAnimationList_t* animation_lis animation_storage_free_storage_animation(&storage_animation); } while(0); - string_clear(read_string); + furi_string_free(read_string); flipper_format_free(file); // add hard-coded animations @@ -231,16 +231,16 @@ void animation_storage_free_storage_animation(StorageAnimation** storage_animati *storage_animation = NULL; } -static bool animation_storage_cast_align(string_t align_str, Align* align) { - if(!string_cmp_str(align_str, "Bottom")) { +static bool animation_storage_cast_align(FuriString* align_str, Align* align) { + if(!furi_string_cmp(align_str, "Bottom")) { *align = AlignBottom; - } else if(!string_cmp_str(align_str, "Top")) { + } else if(!furi_string_cmp(align_str, "Top")) { *align = AlignTop; - } else if(!string_cmp_str(align_str, "Left")) { + } else if(!furi_string_cmp(align_str, "Left")) { *align = AlignLeft; - } else if(!string_cmp_str(align_str, "Right")) { + } else if(!furi_string_cmp(align_str, "Right")) { *align = AlignRight; - } else if(!string_cmp_str(align_str, "Center")) { + } else if(!furi_string_cmp(align_str, "Center")) { *align = AlignCenter; } else { return false; @@ -291,15 +291,16 @@ static bool animation_storage_load_frames( bool frames_ok = false; File* file = storage_file_alloc(storage); FileInfo file_info; - string_t filename; - string_init(filename); + FuriString* filename; + filename = furi_string_alloc(); size_t max_filesize = ROUND_UP_TO(width, 8) * height + 1; for(int i = 0; i < icon->frame_count; ++i) { frames_ok = false; - string_printf(filename, ANIMATION_DIR "/%s/frame_%d.bm", name, i); + furi_string_printf(filename, ANIMATION_DIR "/%s/frame_%d.bm", name, i); - if(storage_common_stat(storage, string_get_cstr(filename), &file_info) != FSE_OK) break; + if(storage_common_stat(storage, furi_string_get_cstr(filename), &file_info) != FSE_OK) + break; if(file_info.size > max_filesize) { FURI_LOG_E( TAG, @@ -310,14 +311,15 @@ static bool animation_storage_load_frames( height); break; } - if(!storage_file_open(file, string_get_cstr(filename), FSAM_READ, FSOM_OPEN_EXISTING)) { - FURI_LOG_E(TAG, "Can't open file \'%s\'", string_get_cstr(filename)); + if(!storage_file_open( + file, furi_string_get_cstr(filename), FSAM_READ, FSOM_OPEN_EXISTING)) { + FURI_LOG_E(TAG, "Can't open file \'%s\'", furi_string_get_cstr(filename)); break; } FURI_CONST_ASSIGN_PTR(icon->frames[i], malloc(file_info.size)); if(storage_file_read(file, (void*)icon->frames[i], file_info.size) != file_info.size) { - FURI_LOG_E(TAG, "Read failed: \'%s\'", string_get_cstr(filename)); + FURI_LOG_E(TAG, "Read failed: \'%s\'", furi_string_get_cstr(filename)); break; } storage_file_close(file); @@ -328,7 +330,7 @@ static bool animation_storage_load_frames( FURI_LOG_E( TAG, "Load \'%s\' failed, %dx%d, size: %d", - string_get_cstr(filename), + furi_string_get_cstr(filename), width, height, file_info.size); @@ -341,15 +343,15 @@ static bool animation_storage_load_frames( } storage_file_free(file); - string_clear(filename); + furi_string_free(filename); return frames_ok; } static bool animation_storage_load_bubbles(BubbleAnimation* animation, FlipperFormat* ff) { uint32_t u32value; - string_t str; - string_init(str); + FuriString* str; + str = furi_string_alloc(); bool success = false; furi_assert(!animation->frame_bubble_sequences); @@ -396,12 +398,12 @@ static bool animation_storage_load_bubbles(BubbleAnimation* animation, FlipperFo FURI_CONST_ASSIGN(bubble->bubble.y, u32value); if(!flipper_format_read_string(ff, "Text", str)) break; - if(string_size(str) > 100) break; + if(furi_string_size(str) > 100) break; - string_replace_all_str(str, "\\n", "\n"); + furi_string_replace_all(str, "\\n", "\n"); - FURI_CONST_ASSIGN_PTR(bubble->bubble.text, malloc(string_size(str) + 1)); - strcpy((char*)bubble->bubble.text, string_get_cstr(str)); + FURI_CONST_ASSIGN_PTR(bubble->bubble.text, malloc(furi_string_size(str) + 1)); + strcpy((char*)bubble->bubble.text, furi_string_get_cstr(str)); if(!flipper_format_read_string(ff, "AlignH", str)) break; if(!animation_storage_cast_align(str, (Align*)&bubble->bubble.align_h)) break; @@ -423,7 +425,7 @@ static bool animation_storage_load_bubbles(BubbleAnimation* animation, FlipperFo } } - string_clear(str); + furi_string_free(str); return success; } @@ -438,8 +440,8 @@ static BubbleAnimation* animation_storage_load_animation(const char* name) { FlipperFormat* ff = flipper_format_file_alloc(storage); /* Forbid skipping fields */ flipper_format_set_strict_mode(ff, true); - string_t str; - string_init(str); + FuriString* str; + str = furi_string_alloc(); animation->frame_bubble_sequences = NULL; bool success = false; @@ -448,10 +450,10 @@ static BubbleAnimation* animation_storage_load_animation(const char* name) { if(FSE_OK != storage_sd_status(storage)) break; - string_printf(str, ANIMATION_DIR "/%s/" ANIMATION_META_FILE, name); - if(!flipper_format_file_open_existing(ff, string_get_cstr(str))) break; + furi_string_printf(str, ANIMATION_DIR "/%s/" ANIMATION_META_FILE, name); + if(!flipper_format_file_open_existing(ff, furi_string_get_cstr(str))) break; if(!flipper_format_read_header(ff, str, &u32value)) break; - if(string_cmp_str(str, "Flipper Animation")) break; + if(furi_string_cmp_str(str, "Flipper Animation")) break; if(!flipper_format_read_uint32(ff, "Width", &width, 1)) break; if(!flipper_format_read_uint32(ff, "Height", &height, 1)) break; @@ -492,7 +494,7 @@ static BubbleAnimation* animation_storage_load_animation(const char* name) { success = true; } while(0); - string_clear(str); + furi_string_free(str); flipper_format_free(ff); if(u32array) { free(u32array); diff --git a/applications/services/desktop/animations/animation_storage.h b/applications/services/desktop/animations/animation_storage.h index e53c1133fbe..16c0feab4b1 100644 --- a/applications/services/desktop/animations/animation_storage.h +++ b/applications/services/desktop/animations/animation_storage.h @@ -2,7 +2,6 @@ #include #include #include "views/bubble_animation_view.h" -#include /** Main structure to handle animation data. * Contains all, including animation playing data (BubbleAnimation), diff --git a/applications/services/dialogs/dialogs.h b/applications/services/dialogs/dialogs.h index 2522c8b54fd..4e836e3655f 100644 --- a/applications/services/dialogs/dialogs.h +++ b/applications/services/dialogs/dialogs.h @@ -1,7 +1,6 @@ #pragma once #include #include -#include "m-string.h" #include #ifdef __cplusplus @@ -56,8 +55,8 @@ void dialog_file_browser_set_basic_options( */ bool dialog_file_browser_show( DialogsApp* context, - string_ptr result_path, - string_ptr path, + FuriString* result_path, + FuriString* path, const DialogsFileBrowserOptions* options); /****************** MESSAGE ******************/ diff --git a/applications/services/dialogs/dialogs_api.c b/applications/services/dialogs/dialogs_api.c index fd3b2e961df..6fd51782de8 100644 --- a/applications/services/dialogs/dialogs_api.c +++ b/applications/services/dialogs/dialogs_api.c @@ -1,14 +1,13 @@ #include "dialogs/dialogs_message.h" #include "dialogs_i.h" #include "dialogs_api_lock.h" -#include "m-string.h" /****************** File browser ******************/ bool dialog_file_browser_show( DialogsApp* context, - string_ptr result_path, - string_ptr path, + FuriString* result_path, + FuriString* path, const DialogsFileBrowserOptions* options) { FuriApiLock lock = API_LOCK_INIT_LOCKED(); furi_check(lock != NULL); diff --git a/applications/services/dialogs/dialogs_message.h b/applications/services/dialogs/dialogs_message.h index 2dc66f1334e..91e040ce43d 100644 --- a/applications/services/dialogs/dialogs_message.h +++ b/applications/services/dialogs/dialogs_message.h @@ -2,7 +2,6 @@ #include #include "dialogs_i.h" #include "dialogs_api_lock.h" -#include "m-string.h" #ifdef __cplusplus extern "C" { @@ -13,8 +12,8 @@ typedef struct { bool skip_assets; bool hide_ext; const Icon* file_icon; - string_ptr result_path; - string_ptr preselected_filename; + FuriString* result_path; + FuriString* preselected_filename; FileBrowserLoadItemCallback item_callback; void* item_callback_context; } DialogsAppMessageDataFileBrowser; diff --git a/applications/services/gui/elements.c b/applications/services/gui/elements.c index 58b446038d2..0f7cf73f4ca 100644 --- a/applications/services/gui/elements.c +++ b/applications/services/gui/elements.c @@ -8,7 +8,6 @@ #include #include -#include #include #include "canvas_i.h" @@ -189,12 +188,12 @@ static size_t end = text + strlen(text); } size_t text_size = end - text; - string_t str; - string_init_set_str(str, text); - string_left(str, text_size); + FuriString* str; + str = furi_string_alloc_set(text); + furi_string_left(str, text_size); size_t result = 0; - uint16_t len_px = canvas_string_width(canvas, string_get_cstr(str)); + uint16_t len_px = canvas_string_width(canvas, furi_string_get_cstr(str)); uint8_t px_left = 0; if(horizontal == AlignCenter) { if(x > (canvas_width(canvas) / 2)) { @@ -224,7 +223,7 @@ static size_t result = text_size; } - string_clear(str); + furi_string_free(str); return result; } @@ -240,7 +239,7 @@ void elements_multiline_text_aligned( uint8_t lines_count = 0; uint8_t font_height = canvas_current_font_height(canvas); - string_t line; + FuriString* line; /* go through text line by line and count lines */ for(const char* start = text; start[0];) { @@ -261,14 +260,14 @@ void elements_multiline_text_aligned( size_t chars_fit = elements_get_max_chars_to_fit(canvas, horizontal, start, x); if((start[chars_fit] == '\n') || (start[chars_fit] == 0)) { - string_init_printf(line, "%.*s", chars_fit, start); + line = furi_string_alloc_printf("%.*s", chars_fit, start); } else if((y + font_height) > canvas_height(canvas)) { - string_init_printf(line, "%.*s...\n", chars_fit, start); + line = furi_string_alloc_printf("%.*s...\n", chars_fit, start); } else { - string_init_printf(line, "%.*s-\n", chars_fit, start); + line = furi_string_alloc_printf("%.*s-\n", chars_fit, start); } - canvas_draw_str_aligned(canvas, x, y, horizontal, vertical, string_get_cstr(line)); - string_clear(line); + canvas_draw_str_aligned(canvas, x, y, horizontal, vertical, furi_string_get_cstr(line)); + furi_string_free(line); y += font_height; if(y > canvas_height(canvas)) { break; @@ -284,22 +283,22 @@ void elements_multiline_text(Canvas* canvas, uint8_t x, uint8_t y, const char* t furi_assert(text); uint8_t font_height = canvas_current_font_height(canvas); - string_t str; - string_init(str); + FuriString* str; + str = furi_string_alloc(); const char* start = text; char* end; do { end = strchr(start, '\n'); if(end) { - string_set_strn(str, start, end - start); + furi_string_set_strn(str, start, end - start); } else { - string_set_str(str, start); + furi_string_set(str, start); } - canvas_draw_str(canvas, x, y, string_get_cstr(str)); + canvas_draw_str(canvas, x, y, furi_string_get_cstr(str)); start = end + 1; y += font_height; } while(end && y < 64); - string_clear(str); + furi_string_free(str); } void elements_multiline_text_framed(Canvas* canvas, uint8_t x, uint8_t y, const char* text) { @@ -533,18 +532,18 @@ void elements_bubble_str( canvas_draw_line(canvas, x2, y2, x3, y3); } -void elements_string_fit_width(Canvas* canvas, string_t string, uint8_t width) { +void elements_string_fit_width(Canvas* canvas, FuriString* string, uint8_t width) { furi_assert(canvas); furi_assert(string); - uint16_t len_px = canvas_string_width(canvas, string_get_cstr(string)); + uint16_t len_px = canvas_string_width(canvas, furi_string_get_cstr(string)); if(len_px > width) { width -= canvas_string_width(canvas, "..."); do { - string_left(string, string_size(string) - 1); - len_px = canvas_string_width(canvas, string_get_cstr(string)); + furi_string_left(string, furi_string_size(string) - 1); + len_px = canvas_string_width(canvas, furi_string_get_cstr(string)); } while(len_px > width); - string_cat(string, "..."); + furi_string_cat(string, "..."); } } diff --git a/applications/services/gui/elements.h b/applications/services/gui/elements.h index 2329ca27b32..b2d204de7e2 100644 --- a/applications/services/gui/elements.h +++ b/applications/services/gui/elements.h @@ -9,7 +9,7 @@ #pragma once #include -#include +#include #include "canvas.h" #ifdef __cplusplus @@ -190,7 +190,7 @@ void elements_bubble_str( * @param string string to trim * @param width max width */ -void elements_string_fit_width(Canvas* canvas, string_t string, uint8_t width); +void elements_string_fit_width(Canvas* canvas, FuriString* string, uint8_t width); /** Draw text box element * diff --git a/applications/services/gui/modules/button_menu.c b/applications/services/gui/modules/button_menu.c index 84fea7888b4..1dce014d80e 100644 --- a/applications/services/gui/modules/button_menu.c +++ b/applications/services/gui/modules/button_menu.c @@ -79,8 +79,8 @@ static void button_menu_draw_common_button( canvas_draw_rframe(canvas, item_x, item_y, ITEM_WIDTH, ITEM_HEIGHT, 5); } - string_t disp_str; - string_init_set_str(disp_str, text); + FuriString* disp_str; + disp_str = furi_string_alloc_set(text); elements_string_fit_width(canvas, disp_str, ITEM_WIDTH - 6); canvas_draw_str_aligned( @@ -89,9 +89,9 @@ static void button_menu_draw_common_button( item_y + (ITEM_HEIGHT / 2), AlignCenter, AlignCenter, - string_get_cstr(disp_str)); + furi_string_get_cstr(disp_str)); - string_clear(disp_str); + furi_string_free(disp_str); } static void button_menu_view_draw_callback(Canvas* canvas, void* _model) { @@ -116,12 +116,12 @@ static void button_menu_view_draw_callback(Canvas* canvas, void* _model) { } if(model->header) { - string_t disp_str; - string_init_set_str(disp_str, model->header); + FuriString* disp_str; + disp_str = furi_string_alloc_set(model->header); elements_string_fit_width(canvas, disp_str, ITEM_WIDTH - 6); canvas_draw_str_aligned( - canvas, 32, 10, AlignCenter, AlignCenter, string_get_cstr(disp_str)); - string_clear(disp_str); + canvas, 32, 10, AlignCenter, AlignCenter, furi_string_get_cstr(disp_str)); + furi_string_free(disp_str); } for(ButtonMenuItemArray_it(it, model->items); !ButtonMenuItemArray_end_p(it); diff --git a/applications/services/gui/modules/file_browser.c b/applications/services/gui/modules/file_browser.c index 1c0c8c74cb7..f15b09f6b5e 100644 --- a/applications/services/gui/modules/file_browser.c +++ b/applications/services/gui/modules/file_browser.c @@ -5,7 +5,6 @@ #include #include #include "furi_hal_resources.h" -#include "m-string.h" #include #include #include @@ -28,23 +27,23 @@ typedef enum { } BrowserItemType; typedef struct { - string_t path; + FuriString* path; BrowserItemType type; uint8_t* custom_icon_data; - string_t display_name; + FuriString* display_name; } BrowserItem_t; static void BrowserItem_t_init(BrowserItem_t* obj) { obj->type = BrowserItemTypeLoading; - string_init(obj->path); - string_init(obj->display_name); + obj->path = furi_string_alloc(); + obj->display_name = furi_string_alloc(); obj->custom_icon_data = NULL; } static void BrowserItem_t_init_set(BrowserItem_t* obj, const BrowserItem_t* src) { obj->type = src->type; - string_init_set(obj->path, src->path); - string_init_set(obj->display_name, src->display_name); + obj->path = furi_string_alloc_set(src->path); + obj->display_name = furi_string_alloc_set(src->display_name); if(src->custom_icon_data) { obj->custom_icon_data = malloc(CUSTOM_ICON_MAX_SIZE); memcpy(obj->custom_icon_data, src->custom_icon_data, CUSTOM_ICON_MAX_SIZE); @@ -55,8 +54,8 @@ static void BrowserItem_t_init_set(BrowserItem_t* obj, const BrowserItem_t* src) static void BrowserItem_t_set(BrowserItem_t* obj, const BrowserItem_t* src) { obj->type = src->type; - string_set(obj->path, src->path); - string_set(obj->display_name, src->display_name); + furi_string_set(obj->path, src->path); + furi_string_set(obj->display_name, src->display_name); if(src->custom_icon_data) { obj->custom_icon_data = malloc(CUSTOM_ICON_MAX_SIZE); memcpy(obj->custom_icon_data, src->custom_icon_data, CUSTOM_ICON_MAX_SIZE); @@ -66,8 +65,8 @@ static void BrowserItem_t_set(BrowserItem_t* obj, const BrowserItem_t* src) { } static void BrowserItem_t_clear(BrowserItem_t* obj) { - string_clear(obj->path); - string_clear(obj->display_name); + furi_string_free(obj->path); + furi_string_free(obj->display_name); if(obj->custom_icon_data) { free(obj->custom_icon_data); } @@ -94,7 +93,7 @@ struct FileBrowser { FileBrowserLoadItemCallback item_callback; void* item_context; - string_ptr result_path; + FuriString* result_path; }; typedef struct { @@ -125,10 +124,11 @@ 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_list_item_cb(void* context, FuriString* item_path, bool is_folder, bool is_last); static void browser_long_load_cb(void* context); -FileBrowser* file_browser_alloc(string_ptr result_path) { +FileBrowser* file_browser_alloc(FuriString* result_path) { furi_assert(result_path); FileBrowser* browser = malloc(sizeof(FileBrowser)); browser->view = view_alloc(); @@ -186,7 +186,7 @@ void file_browser_configure( }); } -void file_browser_start(FileBrowser* browser, string_t path) { +void file_browser_start(FileBrowser* browser, FuriString* 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); @@ -336,7 +336,8 @@ static void browser_list_load_cb(void* context, uint32_t list_load_offset) { BrowserItem_t_clear(&back_item); } -static void browser_list_item_cb(void* context, string_t item_path, bool is_folder, bool is_last) { +static void + browser_list_item_cb(void* context, FuriString* item_path, bool is_folder, bool is_last) { furi_assert(context); FileBrowser* browser = (FileBrowser*)context; @@ -344,8 +345,8 @@ static void browser_list_item_cb(void* context, string_t item_path, bool is_fold item.custom_icon_data = NULL; if(!is_last) { - string_init_set(item.path, item_path); - string_init(item.display_name); + item.path = furi_string_alloc_set(item_path); + item.display_name = furi_string_alloc(); if(is_folder) { item.type = BrowserItemTypeFolder; } else { @@ -363,7 +364,7 @@ static void browser_list_item_cb(void* context, string_t item_path, bool is_fold } } - if(string_empty_p(item.display_name)) { + if(furi_string_empty(item.display_name)) { path_extract_filename( item_path, item.display_name, @@ -376,8 +377,8 @@ static void browser_list_item_cb(void* context, string_t item_path, bool is_fold // TODO: calculate if element is visible return true; }); - string_clear(item.display_name); - string_clear(item.path); + furi_string_free(item.display_name); + furi_string_free(item.path); } else { with_view_model( browser->view, (FileBrowserModel * model) { @@ -427,8 +428,8 @@ 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); + FuriString* filename; + filename = furi_string_alloc(); 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); @@ -440,16 +441,16 @@ static void browser_draw_list(Canvas* canvas, FileBrowserModel* model) { BrowserItem_t* item = items_array_get( model->items, CLAMP(idx - model->array_offset, (int32_t)(array_size - 1), 0)); item_type = item->type; - string_set(filename, item->display_name); + furi_string_set(filename, item->display_name); if(item_type == BrowserItemTypeFile) { custom_icon_data = item->custom_icon_data; } } else { - string_set_str(filename, "---"); + furi_string_set(filename, "---"); } if(item_type == BrowserItemTypeBack) { - string_set_str(filename, ". ."); + furi_string_set(filename, ". ."); } elements_string_fit_width( @@ -471,7 +472,8 @@ static void browser_draw_list(Canvas* canvas, FileBrowserModel* model) { 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)); + canvas_draw_str( + canvas, 15, Y_OFFSET + 9 + i * FRAME_HEIGHT, furi_string_get_cstr(filename)); } if(show_scrollbar) { @@ -484,7 +486,7 @@ static void browser_draw_list(Canvas* canvas, FileBrowserModel* model) { model->item_cnt); } - string_clear(filename); + furi_string_free(filename); } static void file_browser_view_draw_callback(Canvas* canvas, void* _model) { @@ -568,7 +570,7 @@ static bool file_browser_view_input_callback(InputEvent* event, void* context) { 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); + furi_string_set(browser->result_path, selected_item->path); if(browser->callback) { browser->callback(browser->context); } diff --git a/applications/services/gui/modules/file_browser.h b/applications/services/gui/modules/file_browser.h index 57a9f096a9e..c9fdddb55d7 100644 --- a/applications/services/gui/modules/file_browser.h +++ b/applications/services/gui/modules/file_browser.h @@ -5,7 +5,6 @@ #pragma once -#include "m-string.h" #include #ifdef __cplusplus @@ -15,10 +14,13 @@ extern "C" { typedef struct FileBrowser FileBrowser; typedef void (*FileBrowserCallback)(void* context); -typedef bool ( - *FileBrowserLoadItemCallback)(string_t path, void* context, uint8_t** icon, string_t item_name); +typedef bool (*FileBrowserLoadItemCallback)( + FuriString* path, + void* context, + uint8_t** icon, + FuriString* item_name); -FileBrowser* file_browser_alloc(string_ptr result_path); +FileBrowser* file_browser_alloc(FuriString* result_path); void file_browser_free(FileBrowser* browser); @@ -31,7 +33,7 @@ void file_browser_configure( const Icon* file_icon, bool hide_ext); -void file_browser_start(FileBrowser* browser, string_t path); +void file_browser_start(FileBrowser* browser, FuriString* path); void file_browser_stop(FileBrowser* browser); diff --git a/applications/services/gui/modules/file_browser_worker.c b/applications/services/gui/modules/file_browser_worker.c index 36df6cc83c7..319304f9256 100644 --- a/applications/services/gui/modules/file_browser_worker.c +++ b/applications/services/gui/modules/file_browser_worker.c @@ -1,7 +1,6 @@ #include "file_browser_worker.h" #include #include -#include "m-string.h" #include "storage/filesystem_api_defines.h" #include #include @@ -35,8 +34,8 @@ ARRAY_DEF(idx_last_array, int32_t) struct BrowserWorker { FuriThread* thread; - string_t filter_extension; - string_t path_next; + FuriString* filter_extension; + FuriString* path_next; int32_t item_sel_idx; uint32_t load_offset; uint32_t load_count; @@ -50,11 +49,11 @@ struct BrowserWorker { BrowserWorkerLongLoadCallback long_load_cb; }; -static bool browser_path_is_file(string_t path) { +static bool browser_path_is_file(FuriString* path) { bool state = false; FileInfo file_info; Storage* storage = furi_record_open(RECORD_STORAGE); - if(storage_common_stat(storage, string_get_cstr(path), &file_info) == FSE_OK) { + if(storage_common_stat(storage, furi_string_get_cstr(path), &file_info) == FSE_OK) { if((file_info.flags & FSF_DIRECTORY) == 0) { state = true; } @@ -63,50 +62,50 @@ static bool browser_path_is_file(string_t path) { return state; } -static bool browser_path_trim(string_t path) { +static bool browser_path_trim(FuriString* 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); + size_t filename_start = furi_string_search_rchar(path, '/'); + furi_string_left(path, filename_start); + if((furi_string_empty(path)) || (filename_start == FURI_STRING_FAILURE)) { + furi_string_set(path, BROWSER_ROOT); is_root = true; } return is_root; } -static bool browser_filter_by_name(BrowserWorker* browser, string_t name, bool is_folder) { +static bool browser_filter_by_name(BrowserWorker* browser, FuriString* 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)); + return ((furi_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)) { + if((furi_string_empty(browser->filter_extension)) || + (furi_string_cmp_str(browser->filter_extension, "*") == 0)) { return true; } - if(string_end_with_string_p(name, browser->filter_extension)) { + if(furi_string_end_with(name, browser->filter_extension)) { return true; } } return false; } -static bool browser_folder_check_and_switch(string_t path) { +static bool browser_folder_check_and_switch(FuriString* path) { FileInfo file_info; Storage* storage = furi_record_open(RECORD_STORAGE); bool is_root = false; - if(string_search_rchar(path, '/') == 0) { + if(furi_string_search_rchar(path, '/') == 0) { is_root = true; } 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(storage_common_stat(storage, furi_string_get_cstr(path), &file_info) == FSE_OK) { if(file_info.flags & FSF_DIRECTORY) { break; } @@ -122,8 +121,8 @@ static bool browser_folder_check_and_switch(string_t path) { static bool browser_folder_init( BrowserWorker* browser, - string_t path, - string_t filename, + FuriString* path, + FuriString* filename, uint32_t* item_cnt, int32_t* file_idx) { bool state = false; @@ -134,13 +133,13 @@ static bool browser_folder_init( File* directory = storage_file_alloc(storage); char name_temp[FILE_NAME_LEN_MAX]; - string_t name_str; - string_init(name_str); + FuriString* name_str; + name_str = furi_string_alloc(); *item_cnt = 0; *file_idx = -1; - if(storage_dir_open(directory, string_get_cstr(path))) { + if(storage_dir_open(directory, furi_string_get_cstr(path))) { state = true; while(1) { if(!storage_dir_read(directory, &file_info, name_temp, FILE_NAME_LEN_MAX)) { @@ -148,10 +147,10 @@ static bool browser_folder_init( } if((storage_file_get_error(directory) == FSE_OK) && (name_temp[0] != '\0')) { total_files_cnt++; - string_set_str(name_str, name_temp); + furi_string_set(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) { + if(!furi_string_empty(filename)) { + if(furi_string_cmp(name_str, filename) == 0) { *file_idx = *item_cnt; } } @@ -167,7 +166,7 @@ static bool browser_folder_init( } } - string_clear(name_str); + furi_string_free(name_str); storage_dir_close(directory); storage_file_free(directory); @@ -178,20 +177,20 @@ static bool browser_folder_init( } static bool - browser_folder_load(BrowserWorker* browser, string_t path, uint32_t offset, uint32_t count) { + browser_folder_load(BrowserWorker* browser, FuriString* path, uint32_t offset, uint32_t count) { FileInfo file_info; Storage* storage = furi_record_open(RECORD_STORAGE); File* directory = storage_file_alloc(storage); char name_temp[FILE_NAME_LEN_MAX]; - string_t name_str; - string_init(name_str); + FuriString* name_str; + name_str = furi_string_alloc(); uint32_t items_cnt = 0; do { - if(!storage_dir_open(directory, string_get_cstr(path))) { + if(!storage_dir_open(directory, furi_string_get_cstr(path))) { break; } @@ -201,7 +200,7 @@ static bool break; } if(storage_file_get_error(directory) == FSE_OK) { - string_set_str(name_str, name_temp); + furi_string_set(name_str, name_temp); if(browser_filter_by_name(browser, name_str, (file_info.flags & FSF_DIRECTORY))) { items_cnt++; } @@ -223,9 +222,9 @@ static bool break; } if(storage_file_get_error(directory) == FSE_OK) { - string_set_str(name_str, name_temp); + furi_string_set(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); + furi_string_printf(name_str, "%s/%s", furi_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); @@ -241,7 +240,7 @@ static bool } } while(0); - string_clear(name_str); + furi_string_free(name_str); storage_dir_close(directory); storage_file_free(directory); @@ -257,12 +256,12 @@ static int32_t browser_worker(void* context) { FURI_LOG_D(TAG, "Start"); uint32_t items_cnt = 0; - string_t path; - string_init_set_str(path, BROWSER_ROOT); + FuriString* path; + path = furi_string_alloc_set(BROWSER_ROOT); browser->item_sel_idx = -1; - string_t filename; - string_init(filename); + FuriString* filename; + filename = furi_string_alloc(); furi_thread_flags_set(furi_thread_get_id(browser->thread), WorkerEvtConfigChange); @@ -282,7 +281,7 @@ static int32_t browser_worker(void* context) { } if(flags & WorkerEvtFolderEnter) { - string_set(path, browser->path_next); + furi_string_set(path, browser->path_next); bool is_root = browser_folder_check_and_switch(path); // Push previous selected item index to history array @@ -293,13 +292,13 @@ static int32_t browser_worker(void* context) { FURI_LOG_D( TAG, "Enter folder: %s items: %u idx: %d", - string_get_cstr(path), + furi_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); + furi_string_reset(filename); } if(flags & WorkerEvtFolderExit) { @@ -313,7 +312,11 @@ static int32_t browser_worker(void* context) { 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); + TAG, + "Exit to: %s items: %u idx: %d", + furi_string_get_cstr(path), + items_cnt, + file_idx); if(browser->folder_cb) { browser->folder_cb(browser->cb_ctx, items_cnt, file_idx, is_root); } @@ -323,12 +326,12 @@ static int32_t browser_worker(void* context) { bool is_root = browser_folder_check_and_switch(path); int32_t file_idx = 0; - string_reset(filename); + furi_string_reset(filename); browser_folder_init(browser, path, filename, &items_cnt, &file_idx); FURI_LOG_D( TAG, "Refresh folder: %s items: %u idx: %d", - string_get_cstr(path), + furi_string_get_cstr(path), items_cnt, browser->item_sel_idx); if(browser->folder_cb) { @@ -346,21 +349,22 @@ static int32_t browser_worker(void* context) { } } - string_clear(filename); - string_clear(path); + furi_string_free(filename); + furi_string_free(path); FURI_LOG_D(TAG, "End"); return 0; } -BrowserWorker* file_browser_worker_alloc(string_t path, const char* filter_ext, bool skip_assets) { +BrowserWorker* + file_browser_worker_alloc(FuriString* 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->filter_extension = furi_string_alloc_set(filter_ext); browser->skip_assets = skip_assets; - string_init_set(browser->path_next, path); + browser->path_next = furi_string_alloc_set(path); browser->thread = furi_thread_alloc(); furi_thread_set_name(browser->thread, "BrowserWorker"); @@ -379,8 +383,8 @@ void file_browser_worker_free(BrowserWorker* browser) { furi_thread_join(browser->thread); furi_thread_free(browser->thread); - string_clear(browser->filter_extension); - string_clear(browser->path_next); + furi_string_free(browser->filter_extension); + furi_string_free(browser->path_next); idx_last_array_clear(browser->idx_last); @@ -422,19 +426,19 @@ void file_browser_worker_set_long_load_callback( void file_browser_worker_set_config( BrowserWorker* browser, - string_t path, + FuriString* path, const char* filter_ext, bool skip_assets) { furi_assert(browser); - string_set(browser->path_next, path); - string_set_str(browser->filter_extension, filter_ext); + furi_string_set(browser->path_next, path); + furi_string_set(browser->filter_extension, filter_ext); browser->skip_assets = skip_assets; furi_thread_flags_set(furi_thread_get_id(browser->thread), WorkerEvtConfigChange); } -void file_browser_worker_folder_enter(BrowserWorker* browser, string_t path, int32_t item_idx) { +void file_browser_worker_folder_enter(BrowserWorker* browser, FuriString* path, int32_t item_idx) { furi_assert(browser); - string_set(browser->path_next, path); + furi_string_set(browser->path_next, path); browser->item_sel_idx = item_idx; furi_thread_flags_set(furi_thread_get_id(browser->thread), WorkerEvtFolderEnter); } diff --git a/applications/services/gui/modules/file_browser_worker.h b/applications/services/gui/modules/file_browser_worker.h index 18c0b48174c..230bb5b45b4 100644 --- a/applications/services/gui/modules/file_browser_worker.h +++ b/applications/services/gui/modules/file_browser_worker.h @@ -1,6 +1,5 @@ #pragma once -#include "m-string.h" #include #include @@ -17,12 +16,13 @@ typedef void (*BrowserWorkerFolderOpenCallback)( typedef void (*BrowserWorkerListLoadCallback)(void* context, uint32_t list_load_offset); typedef void (*BrowserWorkerListItemCallback)( void* context, - string_t item_path, + FuriString* 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); +BrowserWorker* + file_browser_worker_alloc(FuriString* path, const char* filter_ext, bool skip_assets); void file_browser_worker_free(BrowserWorker* browser); @@ -46,11 +46,11 @@ void file_browser_worker_set_long_load_callback( void file_browser_worker_set_config( BrowserWorker* browser, - string_t path, + FuriString* path, const char* filter_ext, bool skip_assets); -void file_browser_worker_folder_enter(BrowserWorker* browser, string_t path, int32_t item_idx); +void file_browser_worker_folder_enter(BrowserWorker* browser, FuriString* path, int32_t item_idx); void file_browser_worker_folder_exit(BrowserWorker* browser); diff --git a/applications/services/gui/modules/submenu.c b/applications/services/gui/modules/submenu.c index 9fefeca0802..8d40d97b770 100644 --- a/applications/services/gui/modules/submenu.c +++ b/applications/services/gui/modules/submenu.c @@ -65,17 +65,17 @@ static void submenu_view_draw_callback(Canvas* canvas, void* _model) { canvas_set_color(canvas, ColorBlack); } - string_t disp_str; - string_init_set_str(disp_str, SubmenuItemArray_cref(it)->label); + FuriString* disp_str; + disp_str = furi_string_alloc_set(SubmenuItemArray_cref(it)->label); elements_string_fit_width(canvas, disp_str, item_width - 20); canvas_draw_str( canvas, 6, y_offset + (item_position * item_height) + item_height - 4, - string_get_cstr(disp_str)); + furi_string_get_cstr(disp_str)); - string_clear(disp_str); + furi_string_free(disp_str); } position++; diff --git a/applications/services/gui/modules/text_box.c b/applications/services/gui/modules/text_box.c index 52307ec25d5..65ee28301c1 100644 --- a/applications/services/gui/modules/text_box.c +++ b/applications/services/gui/modules/text_box.c @@ -1,6 +1,5 @@ #include "text_box.h" #include "gui/canvas.h" -#include #include #include #include @@ -12,7 +11,7 @@ struct TextBox { typedef struct { const char* text; char* text_pos; - string_t text_formatted; + FuriString* text_formatted; int32_t scroll_pos; int32_t scroll_num; TextBoxFont font; @@ -66,17 +65,17 @@ static void text_box_insert_endline(Canvas* canvas, TextBoxModel* model) { if(line_width + glyph_width > text_width) { line_num++; line_width = 0; - string_push_back(model->text_formatted, '\n'); + furi_string_push_back(model->text_formatted, '\n'); } line_width += glyph_width; } else { line_num++; line_width = 0; } - string_push_back(model->text_formatted, symb); + furi_string_push_back(model->text_formatted, symb); } line_num++; - model->text = string_get_cstr(model->text_formatted); + model->text = furi_string_get_cstr(model->text_formatted); model->text_pos = (char*)model->text; if(model->focus == TextBoxFocusEnd && line_num > 5) { // Set text position to 5th line from the end @@ -140,7 +139,7 @@ TextBox* text_box_alloc() { with_view_model( text_box->view, (TextBoxModel * model) { model->text = NULL; - string_init_set_str(model->text_formatted, ""); + model->text_formatted = furi_string_alloc_set(""); model->formatted = false; model->font = TextBoxFontText; return true; @@ -154,7 +153,7 @@ void text_box_free(TextBox* text_box) { with_view_model( text_box->view, (TextBoxModel * model) { - string_clear(model->text_formatted); + furi_string_free(model->text_formatted); return true; }); view_free(text_box->view); @@ -172,7 +171,7 @@ void text_box_reset(TextBox* text_box) { with_view_model( text_box->view, (TextBoxModel * model) { model->text = NULL; - string_set_str(model->text_formatted, ""); + furi_string_set(model->text_formatted, ""); model->font = TextBoxFontText; model->focus = TextBoxFocusStart; return true; @@ -186,8 +185,8 @@ void text_box_set_text(TextBox* text_box, const char* text) { with_view_model( text_box->view, (TextBoxModel * model) { model->text = text; - string_reset(model->text_formatted); - string_reserve(model->text_formatted, strlen(text)); + furi_string_reset(model->text_formatted); + furi_string_reserve(model->text_formatted, strlen(text)); model->formatted = false; return true; }); diff --git a/applications/services/gui/modules/text_input.c b/applications/services/gui/modules/text_input.c index b2aba03fc03..d7e00940d65 100644 --- a/applications/services/gui/modules/text_input.c +++ b/applications/services/gui/modules/text_input.c @@ -27,7 +27,7 @@ typedef struct { TextInputValidatorCallback validator_callback; void* validator_callback_context; - string_t validator_text; + FuriString* validator_text; bool valadator_message_visible; } TextInputModel; @@ -257,7 +257,7 @@ static void text_input_view_draw_callback(Canvas* canvas, void* _model) { canvas_draw_icon(canvas, 10, 14, &I_WarningDolphin_45x42); canvas_draw_rframe(canvas, 8, 8, 112, 50, 3); canvas_draw_rframe(canvas, 9, 9, 110, 48, 2); - elements_multiline_text(canvas, 62, 20, string_get_cstr(model->validator_text)); + elements_multiline_text(canvas, 62, 20, furi_string_get_cstr(model->validator_text)); canvas_set_font(canvas, FontKeyboard); } } @@ -447,7 +447,7 @@ TextInput* text_input_alloc() { with_view_model( text_input->view, (TextInputModel * model) { - string_init(model->validator_text); + model->validator_text = furi_string_alloc(); return false; }); @@ -460,7 +460,7 @@ void text_input_free(TextInput* text_input) { furi_assert(text_input); with_view_model( text_input->view, (TextInputModel * model) { - string_clear(model->validator_text); + furi_string_free(model->validator_text); return false; }); @@ -489,7 +489,7 @@ void text_input_reset(TextInput* text_input) { model->callback_context = NULL; model->validator_callback = NULL; model->validator_callback_context = NULL; - string_reset(model->validator_text); + furi_string_reset(model->validator_text); model->valadator_message_visible = false; return true; }); diff --git a/applications/services/gui/modules/text_input.h b/applications/services/gui/modules/text_input.h index d30fcd4caae..893fbd5330a 100644 --- a/applications/services/gui/modules/text_input.h +++ b/applications/services/gui/modules/text_input.h @@ -7,7 +7,6 @@ #include #include "validators.h" -#include #ifdef __cplusplus extern "C" { @@ -16,7 +15,7 @@ extern "C" { /** Text input anonymous structure */ typedef struct TextInput TextInput; typedef void (*TextInputCallback)(void* context); -typedef bool (*TextInputValidatorCallback)(const char* text, string_t error, void* context); +typedef bool (*TextInputValidatorCallback)(const char* text, FuriString* error, void* context); /** Allocate and initialize text input * diff --git a/applications/services/gui/modules/validators.c b/applications/services/gui/modules/validators.c index d5fb0fa22cd..0463b1c26b8 100644 --- a/applications/services/gui/modules/validators.c +++ b/applications/services/gui/modules/validators.c @@ -8,7 +8,7 @@ struct ValidatorIsFile { char* current_name; }; -bool validator_is_file_callback(const char* text, string_t error, void* context) { +bool validator_is_file_callback(const char* text, FuriString* error, void* context) { furi_assert(context); ValidatorIsFile* instance = context; @@ -19,16 +19,16 @@ bool validator_is_file_callback(const char* text, string_t error, void* context) } bool ret = true; - string_t path; - string_init_printf(path, "%s/%s%s", instance->app_path_folder, text, instance->app_extension); + FuriString* path = furi_string_alloc_printf( + "%s/%s%s", instance->app_path_folder, text, instance->app_extension); Storage* storage = furi_record_open(RECORD_STORAGE); - if(storage_common_stat(storage, string_get_cstr(path), NULL) == FSE_OK) { + if(storage_common_stat(storage, furi_string_get_cstr(path), NULL) == FSE_OK) { ret = false; - string_printf(error, "This name\nexists!\nChoose\nanother one."); + furi_string_printf(error, "This name\nexists!\nChoose\nanother one."); } else { ret = true; } - string_clear(path); + furi_string_free(path); furi_record_close(RECORD_STORAGE); return ret; diff --git a/applications/services/gui/modules/validators.h b/applications/services/gui/modules/validators.h index c4c4ef54c14..d9200b6dbfa 100644 --- a/applications/services/gui/modules/validators.h +++ b/applications/services/gui/modules/validators.h @@ -1,6 +1,5 @@ #pragma once -#include #include #ifdef __cplusplus @@ -15,7 +14,7 @@ ValidatorIsFile* validator_is_file_alloc_init( void validator_is_file_free(ValidatorIsFile* instance); -bool validator_is_file_callback(const char* text, string_t error, void* context); +bool validator_is_file_callback(const char* text, FuriString* error, void* context); #ifdef __cplusplus } diff --git a/applications/services/gui/modules/variable_item_list.c b/applications/services/gui/modules/variable_item_list.c index 4e5e4664f2c..ec8fd3d20ac 100644 --- a/applications/services/gui/modules/variable_item_list.c +++ b/applications/services/gui/modules/variable_item_list.c @@ -8,7 +8,7 @@ struct VariableItem { const char* label; uint8_t current_value_index; - string_t current_value_text; + FuriString* current_value_text; uint8_t values_count; VariableItemChangeCallback change_callback; void* context; @@ -77,7 +77,7 @@ static void variable_item_list_draw_callback(Canvas* canvas, void* _model) { item_text_y, AlignCenter, AlignBottom, - string_get_cstr(item->current_value_text)); + furi_string_get_cstr(item->current_value_text)); if(item->current_value_index < (item->values_count - 1)) { canvas_draw_str(canvas, 115, item_text_y, ">"); @@ -303,7 +303,7 @@ void variable_item_list_free(VariableItemList* variable_item_list) { VariableItemArray_it_t it; for(VariableItemArray_it(it, model->items); !VariableItemArray_end_p(it); VariableItemArray_next(it)) { - string_clear(VariableItemArray_ref(it)->current_value_text); + furi_string_free(VariableItemArray_ref(it)->current_value_text); } VariableItemArray_clear(model->items); return false; @@ -320,7 +320,7 @@ void variable_item_list_reset(VariableItemList* variable_item_list) { VariableItemArray_it_t it; for(VariableItemArray_it(it, model->items); !VariableItemArray_end_p(it); VariableItemArray_next(it)) { - string_clear(VariableItemArray_ref(it)->current_value_text); + furi_string_free(VariableItemArray_ref(it)->current_value_text); } VariableItemArray_reset(model->items); return false; @@ -350,7 +350,7 @@ VariableItem* variable_item_list_add( item->change_callback = change_callback; item->context = context; item->current_value_index = 0; - string_init(item->current_value_text); + item->current_value_text = furi_string_alloc(); return true; }); @@ -376,7 +376,7 @@ void variable_item_set_current_value_index(VariableItem* item, uint8_t current_v } void variable_item_set_current_value_text(VariableItem* item, const char* current_value_text) { - string_set_str(item->current_value_text, current_value_text); + furi_string_set(item->current_value_text, current_value_text); } uint8_t variable_item_get_current_value_index(VariableItem* item) { diff --git a/applications/services/gui/modules/widget_elements/widget_element_button.c b/applications/services/gui/modules/widget_elements/widget_element_button.c index 92be25907d5..be33b1897ea 100644 --- a/applications/services/gui/modules/widget_elements/widget_element_button.c +++ b/applications/services/gui/modules/widget_elements/widget_element_button.c @@ -1,10 +1,9 @@ #include "widget_element_i.h" #include -#include typedef struct { GuiButtonType button_type; - string_t text; + FuriString* text; ButtonCallback callback; void* context; } GuiButtonModel; @@ -18,11 +17,11 @@ static void gui_button_draw(Canvas* canvas, WidgetElement* element) { canvas_set_font(canvas, FontSecondary); if(model->button_type == GuiButtonTypeLeft) { - elements_button_left(canvas, string_get_cstr(model->text)); + elements_button_left(canvas, furi_string_get_cstr(model->text)); } else if(model->button_type == GuiButtonTypeRight) { - elements_button_right(canvas, string_get_cstr(model->text)); + elements_button_right(canvas, furi_string_get_cstr(model->text)); } else if(model->button_type == GuiButtonTypeCenter) { - elements_button_center(canvas, string_get_cstr(model->text)); + elements_button_center(canvas, furi_string_get_cstr(model->text)); } } @@ -50,7 +49,7 @@ static void gui_button_free(WidgetElement* gui_button) { furi_assert(gui_button); GuiButtonModel* model = gui_button->model; - string_clear(model->text); + furi_string_free(model->text); free(gui_button->model); free(gui_button); } @@ -65,7 +64,7 @@ WidgetElement* widget_element_button_create( model->button_type = button_type; model->callback = callback; model->context = context; - string_init_set_str(model->text, text); + model->text = furi_string_alloc_set(text); // Allocate and init Element WidgetElement* gui_button = malloc(sizeof(WidgetElement)); diff --git a/applications/services/gui/modules/widget_elements/widget_element_string.c b/applications/services/gui/modules/widget_elements/widget_element_string.c index a03c2b6d864..feb22ad1c1b 100644 --- a/applications/services/gui/modules/widget_elements/widget_element_string.c +++ b/applications/services/gui/modules/widget_elements/widget_element_string.c @@ -1,5 +1,4 @@ #include "widget_element_i.h" -#include typedef struct { uint8_t x; @@ -7,7 +6,7 @@ typedef struct { Align horizontal; Align vertical; Font font; - string_t text; + FuriString* text; } GuiStringModel; static void gui_string_draw(Canvas* canvas, WidgetElement* element) { @@ -15,7 +14,7 @@ static void gui_string_draw(Canvas* canvas, WidgetElement* element) { furi_assert(element); GuiStringModel* model = element->model; - if(string_size(model->text)) { + if(furi_string_size(model->text)) { canvas_set_font(canvas, model->font); canvas_draw_str_aligned( canvas, @@ -23,7 +22,7 @@ static void gui_string_draw(Canvas* canvas, WidgetElement* element) { model->y, model->horizontal, model->vertical, - string_get_cstr(model->text)); + furi_string_get_cstr(model->text)); } } @@ -31,7 +30,7 @@ static void gui_string_free(WidgetElement* gui_string) { furi_assert(gui_string); GuiStringModel* model = gui_string->model; - string_clear(model->text); + furi_string_free(model->text); free(gui_string->model); free(gui_string); } @@ -52,7 +51,7 @@ WidgetElement* widget_element_string_create( model->horizontal = horizontal; model->vertical = vertical; model->font = font; - string_init_set_str(model->text, text); + model->text = furi_string_alloc_set(text); // Allocate and init Element WidgetElement* gui_string = malloc(sizeof(WidgetElement)); diff --git a/applications/services/gui/modules/widget_elements/widget_element_string_multiline.c b/applications/services/gui/modules/widget_elements/widget_element_string_multiline.c index 01a70a0d087..9ad2a1a8340 100644 --- a/applications/services/gui/modules/widget_elements/widget_element_string_multiline.c +++ b/applications/services/gui/modules/widget_elements/widget_element_string_multiline.c @@ -1,5 +1,4 @@ #include "widget_element_i.h" -#include #include typedef struct { @@ -8,7 +7,7 @@ typedef struct { Align horizontal; Align vertical; Font font; - string_t text; + FuriString* text; } GuiStringMultiLineModel; static void gui_string_multiline_draw(Canvas* canvas, WidgetElement* element) { @@ -16,7 +15,7 @@ static void gui_string_multiline_draw(Canvas* canvas, WidgetElement* element) { furi_assert(element); GuiStringMultiLineModel* model = element->model; - if(string_size(model->text)) { + if(furi_string_size(model->text)) { canvas_set_font(canvas, model->font); elements_multiline_text_aligned( canvas, @@ -24,7 +23,7 @@ static void gui_string_multiline_draw(Canvas* canvas, WidgetElement* element) { model->y, model->horizontal, model->vertical, - string_get_cstr(model->text)); + furi_string_get_cstr(model->text)); } } @@ -32,7 +31,7 @@ static void gui_string_multiline_free(WidgetElement* gui_string) { furi_assert(gui_string); GuiStringMultiLineModel* model = gui_string->model; - string_clear(model->text); + furi_string_free(model->text); free(gui_string->model); free(gui_string); } @@ -53,7 +52,7 @@ WidgetElement* widget_element_string_multiline_create( model->horizontal = horizontal; model->vertical = vertical; model->font = font; - string_init_set_str(model->text, text); + model->text = furi_string_alloc_set(text); // Allocate and init Element WidgetElement* gui_string = malloc(sizeof(WidgetElement)); diff --git a/applications/services/gui/modules/widget_elements/widget_element_text_box.c b/applications/services/gui/modules/widget_elements/widget_element_text_box.c index 4750f8f8fcf..2c69482021e 100644 --- a/applications/services/gui/modules/widget_elements/widget_element_text_box.c +++ b/applications/services/gui/modules/widget_elements/widget_element_text_box.c @@ -1,5 +1,4 @@ #include "widget_element_i.h" -#include #include typedef struct { @@ -9,7 +8,7 @@ typedef struct { uint8_t height; Align horizontal; Align vertical; - string_t text; + FuriString* text; bool strip_to_dots; } GuiTextBoxModel; @@ -18,7 +17,7 @@ static void gui_text_box_draw(Canvas* canvas, WidgetElement* element) { furi_assert(element); GuiTextBoxModel* model = element->model; - if(string_size(model->text)) { + if(furi_string_size(model->text)) { elements_text_box( canvas, model->x, @@ -27,7 +26,7 @@ static void gui_text_box_draw(Canvas* canvas, WidgetElement* element) { model->height, model->horizontal, model->vertical, - string_get_cstr(model->text), + furi_string_get_cstr(model->text), model->strip_to_dots); } } @@ -36,7 +35,7 @@ static void gui_text_box_free(WidgetElement* gui_string) { furi_assert(gui_string); GuiTextBoxModel* model = gui_string->model; - string_clear(model->text); + furi_string_free(model->text); free(gui_string->model); free(gui_string); } @@ -60,7 +59,7 @@ WidgetElement* widget_element_text_box_create( model->height = height; model->horizontal = horizontal; model->vertical = vertical; - string_init_set_str(model->text, text); + model->text = furi_string_alloc_set(text); model->strip_to_dots = strip_to_dots; // Allocate and init Element diff --git a/applications/services/gui/modules/widget_elements/widget_element_text_scroll.c b/applications/services/gui/modules/widget_elements/widget_element_text_scroll.c index 6682b106a88..a4d76638939 100644 --- a/applications/services/gui/modules/widget_elements/widget_element_text_scroll.c +++ b/applications/services/gui/modules/widget_elements/widget_element_text_scroll.c @@ -1,5 +1,4 @@ #include "widget_element_i.h" -#include #include #include @@ -8,7 +7,7 @@ typedef struct { Font font; Align horizontal; - string_t text; + FuriString* text; } TextScrollLineArray; ARRAY_DEF(TextScrollLineArray, TextScrollLineArray, M_POD_OPLIST) @@ -19,19 +18,19 @@ typedef struct { uint8_t y; uint8_t width; uint8_t height; - string_t text; + FuriString* text; uint8_t scroll_pos_total; uint8_t scroll_pos_current; bool text_formatted; } WidgetElementTextScrollModel; static bool - widget_element_text_scroll_process_ctrl_symbols(TextScrollLineArray* line, string_t text) { + widget_element_text_scroll_process_ctrl_symbols(TextScrollLineArray* line, FuriString* text) { bool processed = false; do { - if(string_get_char(text, 0) != '\e') break; - char ctrl_symbol = string_get_char(text, 1); + if(furi_string_get_char(text, 0) != '\e') break; + char ctrl_symbol = furi_string_get_char(text, 1); if(ctrl_symbol == 'c') { line->horizontal = AlignCenter; } else if(ctrl_symbol == 'r') { @@ -39,7 +38,7 @@ static bool } else if(ctrl_symbol == '#') { line->font = FontPrimary; } - string_right(text, 2); + furi_string_right(text, 2); processed = true; } while(false); @@ -51,7 +50,7 @@ void widget_element_text_scroll_add_line(WidgetElement* element, TextScrollLineA TextScrollLineArray new_line; new_line.font = line->font; new_line.horizontal = line->horizontal; - string_init_set(new_line.text, line->text); + new_line.text = furi_string_alloc_set(line->text); TextScrollLineArray_push_back(model->line_array, new_line); } @@ -59,7 +58,7 @@ static void widget_element_text_scroll_fill_lines(Canvas* canvas, WidgetElement* WidgetElementTextScrollModel* model = element->model; TextScrollLineArray line_tmp; bool all_text_processed = false; - string_init(line_tmp.text); + line_tmp.text = furi_string_alloc(); bool reached_new_line = true; uint16_t total_height = 0; @@ -68,7 +67,7 @@ static void widget_element_text_scroll_fill_lines(Canvas* canvas, WidgetElement* // Set default line properties line_tmp.font = FontSecondary; line_tmp.horizontal = AlignLeft; - string_reset(line_tmp.text); + furi_string_reset(line_tmp.text); // Process control symbols while(widget_element_text_scroll_process_ctrl_symbols(&line_tmp, model->text)) ; @@ -84,38 +83,38 @@ static void widget_element_text_scroll_fill_lines(Canvas* canvas, WidgetElement* uint8_t line_width = 0; uint16_t char_i = 0; while(true) { - char next_char = string_get_char(model->text, char_i++); + char next_char = furi_string_get_char(model->text, char_i++); if(next_char == '\0') { - string_push_back(line_tmp.text, '\0'); + furi_string_push_back(line_tmp.text, '\0'); widget_element_text_scroll_add_line(element, &line_tmp); total_height += params->leading_default - params->height; all_text_processed = true; break; } else if(next_char == '\n') { - string_push_back(line_tmp.text, '\0'); + furi_string_push_back(line_tmp.text, '\0'); widget_element_text_scroll_add_line(element, &line_tmp); - string_right(model->text, char_i); + furi_string_right(model->text, char_i); total_height += params->leading_default - params->height; reached_new_line = true; break; } else { line_width += canvas_glyph_width(canvas, next_char); if(line_width > model->width) { - string_push_back(line_tmp.text, '\0'); + furi_string_push_back(line_tmp.text, '\0'); widget_element_text_scroll_add_line(element, &line_tmp); - string_right(model->text, char_i - 1); - string_reset(line_tmp.text); + furi_string_right(model->text, char_i - 1); + furi_string_reset(line_tmp.text); total_height += params->leading_default - params->height; reached_new_line = false; break; } else { - string_push_back(line_tmp.text, next_char); + furi_string_push_back(line_tmp.text, next_char); } } } } - string_clear(line_tmp.text); + furi_string_free(line_tmp.text); } static void widget_element_text_scroll_draw(Canvas* canvas, WidgetElement* element) { @@ -150,7 +149,7 @@ static void widget_element_text_scroll_draw(Canvas* canvas, WidgetElement* eleme x = model->x + model->width; } canvas_draw_str_aligned( - canvas, x, y, line->horizontal, AlignTop, string_get_cstr(line->text)); + canvas, x, y, line->horizontal, AlignTop, furi_string_get_cstr(line->text)); y += params->leading_default; } // Draw scroll bar @@ -205,10 +204,10 @@ static void widget_element_text_scroll_free(WidgetElement* text_scroll) { for(TextScrollLineArray_it(it, model->line_array); !TextScrollLineArray_end_p(it); TextScrollLineArray_next(it)) { TextScrollLineArray* line = TextScrollLineArray_ref(it); - string_clear(line->text); + furi_string_free(line->text); } TextScrollLineArray_clear(model->line_array); - string_clear(model->text); + furi_string_free(model->text); free(text_scroll->model); furi_mutex_free(text_scroll->model_mutex); free(text_scroll); @@ -231,7 +230,7 @@ WidgetElement* widget_element_text_scroll_create( model->scroll_pos_current = 0; model->scroll_pos_total = 1; TextScrollLineArray_init(model->line_array); - string_init_set_str(model->text, text); + model->text = furi_string_alloc_set(text); WidgetElement* text_scroll = malloc(sizeof(WidgetElement)); text_scroll->parent = NULL; diff --git a/applications/services/input/input_cli.c b/applications/services/input/input_cli.c index 037ac53e52b..d9a8eaeba8c 100644 --- a/applications/services/input/input_cli.c +++ b/applications/services/input/input_cli.c @@ -19,7 +19,7 @@ static void input_cli_dump_events_callback(const void* value, void* ctx) { furi_message_queue_put(input_queue, value, FuriWaitForever); } -static void input_cli_dump(Cli* cli, string_t args, Input* input) { +static void input_cli_dump(Cli* cli, FuriString* args, Input* input) { UNUSED(args); FuriMessageQueue* input_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); FuriPubSubSubscription* input_subscription = @@ -47,11 +47,11 @@ static void input_cli_send_print_usage() { printf("\t\t \t - one of 'press', 'release', 'short', 'long'\r\n"); } -static void input_cli_send(Cli* cli, string_t args, Input* input) { +static void input_cli_send(Cli* cli, FuriString* args, Input* input) { UNUSED(cli); InputEvent event; - string_t key_str; - string_init(key_str); + FuriString* key_str; + key_str = furi_string_alloc(); bool parsed = false; do { @@ -59,29 +59,29 @@ static void input_cli_send(Cli* cli, string_t args, Input* input) { if(!args_read_string_and_trim(args, key_str)) { break; } - if(!string_cmp(key_str, "up")) { + if(!furi_string_cmp(key_str, "up")) { event.key = InputKeyUp; - } else if(!string_cmp(key_str, "down")) { + } else if(!furi_string_cmp(key_str, "down")) { event.key = InputKeyDown; - } else if(!string_cmp(key_str, "left")) { + } else if(!furi_string_cmp(key_str, "left")) { event.key = InputKeyLeft; - } else if(!string_cmp(key_str, "right")) { + } else if(!furi_string_cmp(key_str, "right")) { event.key = InputKeyRight; - } else if(!string_cmp(key_str, "ok")) { + } else if(!furi_string_cmp(key_str, "ok")) { event.key = InputKeyOk; - } else if(!string_cmp(key_str, "back")) { + } else if(!furi_string_cmp(key_str, "back")) { event.key = InputKeyBack; } else { break; } // Parse Type - if(!string_cmp(args, "press")) { + if(!furi_string_cmp(args, "press")) { event.type = InputTypePress; - } else if(!string_cmp(args, "release")) { + } else if(!furi_string_cmp(args, "release")) { event.type = InputTypeRelease; - } else if(!string_cmp(args, "short")) { + } else if(!furi_string_cmp(args, "short")) { event.type = InputTypeShort; - } else if(!string_cmp(args, "long")) { + } else if(!furi_string_cmp(args, "long")) { event.type = InputTypeLong; } else { break; @@ -94,26 +94,26 @@ static void input_cli_send(Cli* cli, string_t args, Input* input) { } else { input_cli_send_print_usage(); } - string_clear(key_str); + furi_string_free(key_str); } -void input_cli(Cli* cli, string_t args, void* context) { +void input_cli(Cli* cli, FuriString* args, void* context) { furi_assert(cli); furi_assert(context); Input* input = context; - string_t cmd; - string_init(cmd); + FuriString* cmd; + cmd = furi_string_alloc(); do { if(!args_read_string_and_trim(args, cmd)) { input_cli_usage(); break; } - if(string_cmp_str(cmd, "dump") == 0) { + if(furi_string_cmp_str(cmd, "dump") == 0) { input_cli_dump(cli, args, input); break; } - if(string_cmp_str(cmd, "send") == 0) { + if(furi_string_cmp_str(cmd, "send") == 0) { input_cli_send(cli, args, input); break; } @@ -121,5 +121,5 @@ void input_cli(Cli* cli, string_t args, void* context) { input_cli_usage(); } while(false); - string_clear(cmd); + furi_string_free(cmd); } diff --git a/applications/services/input/input_i.h b/applications/services/input/input_i.h index f709e0487c9..14d8b0735a5 100644 --- a/applications/services/input/input_i.h +++ b/applications/services/input/input_i.h @@ -11,7 +11,6 @@ #include #include #include -#include #include #define INPUT_DEBOUNCE_TICKS_HALF (INPUT_DEBOUNCE_TICKS / 2) @@ -46,4 +45,4 @@ void input_press_timer_callback(void* arg); void input_isr(void* _ctx); /** Input CLI command handler */ -void input_cli(Cli* cli, string_t args, void* context); +void input_cli(Cli* cli, FuriString* args, void* context); diff --git a/applications/services/loader/loader.c b/applications/services/loader/loader.c index cf7e5a1084e..51cddec706a 100644 --- a/applications/services/loader/loader.c +++ b/applications/services/loader/loader.c @@ -98,15 +98,15 @@ const FlipperApplication* loader_find_application_by_name(const char* name) { return application; } -void loader_cli_open(Cli* cli, string_t args, Loader* instance) { +void loader_cli_open(Cli* cli, FuriString* args, Loader* instance) { UNUSED(cli); if(loader_is_locked(instance)) { printf("Can't start, furi application is running"); return; } - string_t application_name; - string_init(application_name); + FuriString* application_name; + application_name = furi_string_alloc(); do { if(!args_read_probably_quoted_string_and_trim(args, application_name)) { @@ -115,14 +115,14 @@ void loader_cli_open(Cli* cli, string_t args, Loader* instance) { } const FlipperApplication* application = - loader_find_application_by_name(string_get_cstr(application_name)); + loader_find_application_by_name(furi_string_get_cstr(application_name)); if(!application) { - printf("%s doesn't exists\r\n", string_get_cstr(application_name)); + printf("%s doesn't exists\r\n", furi_string_get_cstr(application_name)); break; } - string_strim(args); - if(!loader_start_application(application, string_get_cstr(args))) { + furi_string_trim(args); + if(!loader_start_application(application, furi_string_get_cstr(args))) { printf("Can't start, furi application is running"); return; } else { @@ -134,10 +134,10 @@ void loader_cli_open(Cli* cli, string_t args, Loader* instance) { } } while(false); - string_clear(application_name); + furi_string_free(application_name); } -void loader_cli_list(Cli* cli, string_t args, Loader* instance) { +void loader_cli_list(Cli* cli, FuriString* args, Loader* instance) { UNUSED(cli); UNUSED(args); UNUSED(instance); @@ -159,12 +159,12 @@ void loader_cli_list(Cli* cli, string_t args, Loader* instance) { } } -static void loader_cli(Cli* cli, string_t args, void* _ctx) { +static void loader_cli(Cli* cli, FuriString* args, void* _ctx) { furi_assert(_ctx); Loader* instance = _ctx; - string_t cmd; - string_init(cmd); + FuriString* cmd; + cmd = furi_string_alloc(); do { if(!args_read_string_and_trim(args, cmd)) { @@ -172,12 +172,12 @@ static void loader_cli(Cli* cli, string_t args, void* _ctx) { break; } - if(string_cmp_str(cmd, "list") == 0) { + if(furi_string_cmp_str(cmd, "list") == 0) { loader_cli_list(cli, args, instance); break; } - if(string_cmp_str(cmd, "open") == 0) { + if(furi_string_cmp_str(cmd, "open") == 0) { loader_cli_open(cli, args, instance); break; } @@ -185,7 +185,7 @@ static void loader_cli(Cli* cli, string_t args, void* _ctx) { loader_cli_print_usage(); } while(false); - string_clear(cmd); + furi_string_free(cmd); } LoaderStatus loader_start(Loader* instance, const char* name, const char* args) { diff --git a/applications/services/power/power_cli.c b/applications/services/power/power_cli.c index 6af396318bc..5c28137e9b6 100644 --- a/applications/services/power/power_cli.c +++ b/applications/services/power/power_cli.c @@ -5,7 +5,7 @@ #include #include -void power_cli_off(Cli* cli, string_t args) { +void power_cli_off(Cli* cli, FuriString* args) { UNUSED(cli); UNUSED(args); Power* power = furi_record_open(RECORD_POWER); @@ -14,13 +14,13 @@ void power_cli_off(Cli* cli, string_t args) { power_off(power); } -void power_cli_reboot(Cli* cli, string_t args) { +void power_cli_reboot(Cli* cli, FuriString* args) { UNUSED(cli); UNUSED(args); power_reboot(PowerBootModeNormal); } -void power_cli_reboot2dfu(Cli* cli, string_t args) { +void power_cli_reboot2dfu(Cli* cli, FuriString* args) { UNUSED(cli); UNUSED(args); power_reboot(PowerBootModeDfu); @@ -32,37 +32,37 @@ static void power_cli_info_callback(const char* key, const char* value, bool las printf("%-24s: %s\r\n", key, value); } -void power_cli_info(Cli* cli, string_t args) { +void power_cli_info(Cli* cli, FuriString* args) { UNUSED(cli); UNUSED(args); furi_hal_power_info_get(power_cli_info_callback, NULL); } -void power_cli_debug(Cli* cli, string_t args) { +void power_cli_debug(Cli* cli, FuriString* args) { UNUSED(cli); UNUSED(args); furi_hal_power_dump_state(); } -void power_cli_5v(Cli* cli, string_t args) { +void power_cli_5v(Cli* cli, FuriString* args) { UNUSED(cli); - if(!string_cmp(args, "0")) { + if(!furi_string_cmp(args, "0")) { furi_hal_power_disable_otg(); - } else if(!string_cmp(args, "1")) { + } else if(!furi_string_cmp(args, "1")) { furi_hal_power_enable_otg(); } else { - cli_print_usage("power_otg", "<1|0>", string_get_cstr(args)); + cli_print_usage("power_otg", "<1|0>", furi_string_get_cstr(args)); } } -void power_cli_3v3(Cli* cli, string_t args) { +void power_cli_3v3(Cli* cli, FuriString* args) { UNUSED(cli); - if(!string_cmp(args, "0")) { + if(!furi_string_cmp(args, "0")) { furi_hal_power_disable_external_3_3v(); - } else if(!string_cmp(args, "1")) { + } else if(!furi_string_cmp(args, "1")) { furi_hal_power_enable_external_3_3v(); } else { - cli_print_usage("power_ext", "<1|0>", string_get_cstr(args)); + cli_print_usage("power_ext", "<1|0>", furi_string_get_cstr(args)); } } @@ -82,10 +82,10 @@ static void power_cli_command_print_usage() { } } -void power_cli(Cli* cli, string_t args, void* context) { +void power_cli(Cli* cli, FuriString* args, void* context) { UNUSED(context); - string_t cmd; - string_init(cmd); + FuriString* cmd; + cmd = furi_string_alloc(); do { if(!args_read_string_and_trim(args, cmd)) { @@ -93,38 +93,38 @@ void power_cli(Cli* cli, string_t args, void* context) { break; } - if(string_cmp_str(cmd, "off") == 0) { + if(furi_string_cmp_str(cmd, "off") == 0) { power_cli_off(cli, args); break; } - if(string_cmp_str(cmd, "reboot") == 0) { + if(furi_string_cmp_str(cmd, "reboot") == 0) { power_cli_reboot(cli, args); break; } - if(string_cmp_str(cmd, "reboot2dfu") == 0) { + if(furi_string_cmp_str(cmd, "reboot2dfu") == 0) { power_cli_reboot2dfu(cli, args); break; } - if(string_cmp_str(cmd, "info") == 0) { + if(furi_string_cmp_str(cmd, "info") == 0) { power_cli_info(cli, args); break; } - if(string_cmp_str(cmd, "debug") == 0) { + if(furi_string_cmp_str(cmd, "debug") == 0) { power_cli_debug(cli, args); break; } - if(string_cmp_str(cmd, "5v") == 0) { + if(furi_string_cmp_str(cmd, "5v") == 0) { power_cli_5v(cli, args); break; } if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - if(string_cmp_str(cmd, "3v3") == 0) { + if(furi_string_cmp_str(cmd, "3v3") == 0) { power_cli_3v3(cli, args); break; } @@ -133,7 +133,7 @@ void power_cli(Cli* cli, string_t args, void* context) { power_cli_command_print_usage(); } while(false); - string_clear(cmd); + furi_string_free(cmd); } void power_on_system_start() { diff --git a/applications/services/rpc/rpc.c b/applications/services/rpc/rpc.c index abba6ea42a9..4e8c29b443b 100644 --- a/applications/services/rpc/rpc.c +++ b/applications/services/rpc/rpc.c @@ -14,7 +14,6 @@ #include #include #include -#include #include #define TAG "RpcSrv" diff --git a/applications/services/rpc/rpc_cli.c b/applications/services/rpc/rpc_cli.c index 8cb0f76acdf..d03e2198c1b 100644 --- a/applications/services/rpc/rpc_cli.c +++ b/applications/services/rpc/rpc_cli.c @@ -37,7 +37,7 @@ static void rpc_cli_session_terminated_callback(void* context) { furi_semaphore_release(cli_rpc->terminate_semaphore); } -void rpc_cli_command_start_session(Cli* cli, string_t args, void* context) { +void rpc_cli_command_start_session(Cli* cli, FuriString* args, void* context) { UNUSED(args); furi_assert(cli); furi_assert(context); diff --git a/applications/services/rpc/rpc_debug.c b/applications/services/rpc/rpc_debug.c index 9c04bd89a55..a060654d844 100644 --- a/applications/services/rpc/rpc_debug.c +++ b/applications/services/rpc/rpc_debug.c @@ -1,15 +1,14 @@ #include "rpc_i.h" -#include static size_t rpc_debug_print_file_msg( - string_t str, + FuriString* str, const char* prefix, const PB_Storage_File* msg_file, size_t msg_files_size) { size_t cnt = 0; for(size_t i = 0; i < msg_files_size; ++i, ++msg_file) { - string_cat_printf( + furi_string_cat_printf( str, "%s[%c] size: %5ld", prefix, @@ -17,11 +16,11 @@ static size_t rpc_debug_print_file_msg( msg_file->size); if(msg_file->name) { - string_cat_printf(str, " \'%s\'", msg_file->name); + furi_string_cat_printf(str, " \'%s\'", msg_file->name); } if(msg_file->data && msg_file->data->size) { - string_cat_printf( + furi_string_cat_printf( str, " (%d):\'%.*s%s\'", msg_file->data->size, @@ -30,42 +29,42 @@ static size_t rpc_debug_print_file_msg( msg_file->data->size > 30 ? "..." : ""); } - string_cat_printf(str, "\r\n"); + furi_string_cat_printf(str, "\r\n"); } return cnt; } void rpc_debug_print_data(const char* prefix, uint8_t* buffer, size_t size) { - string_t str; - string_init(str); - string_reserve(str, 100 + size * 5); + FuriString* str; + str = furi_string_alloc(); + furi_string_reserve(str, 100 + size * 5); - string_cat_printf(str, "\r\n%s DEC(%d): {", prefix, size); + furi_string_cat_printf(str, "\r\n%s DEC(%d): {", prefix, size); for(size_t i = 0; i < size; ++i) { - string_cat_printf(str, "%d, ", buffer[i]); + furi_string_cat_printf(str, "%d, ", buffer[i]); } - string_cat_printf(str, "}\r\n"); + furi_string_cat_printf(str, "}\r\n"); - printf("%s", string_get_cstr(str)); - string_reset(str); - string_reserve(str, 100 + size * 3); + printf("%s", furi_string_get_cstr(str)); + furi_string_reset(str); + furi_string_reserve(str, 100 + size * 3); - string_cat_printf(str, "%s HEX(%d): {", prefix, size); + furi_string_cat_printf(str, "%s HEX(%d): {", prefix, size); for(size_t i = 0; i < size; ++i) { - string_cat_printf(str, "%02X", buffer[i]); + furi_string_cat_printf(str, "%02X", buffer[i]); } - string_cat_printf(str, "}\r\n\r\n"); + furi_string_cat_printf(str, "}\r\n\r\n"); - printf("%s", string_get_cstr(str)); - string_clear(str); + printf("%s", furi_string_get_cstr(str)); + furi_string_free(str); } void rpc_debug_print_message(const PB_Main* message) { - string_t str; - string_init(str); + FuriString* str; + str = furi_string_alloc(); - string_cat_printf( + furi_string_cat_printf( str, "PB_Main: {\r\n\tresult: %d cmd_id: %ld (%s)\r\n", message->command_status, @@ -74,106 +73,106 @@ void rpc_debug_print_message(const PB_Main* message) { switch(message->which_content) { default: /* not implemented yet */ - string_cat_printf(str, "\tNOT_IMPLEMENTED (%d) {\r\n", message->which_content); + furi_string_cat_printf(str, "\tNOT_IMPLEMENTED (%d) {\r\n", message->which_content); break; case PB_Main_stop_session_tag: - string_cat_printf(str, "\tstop_session {\r\n"); + furi_string_cat_printf(str, "\tstop_session {\r\n"); break; case PB_Main_app_start_request_tag: { - string_cat_printf(str, "\tapp_start {\r\n"); + furi_string_cat_printf(str, "\tapp_start {\r\n"); const char* name = message->content.app_start_request.name; const char* args = message->content.app_start_request.args; if(name) { - string_cat_printf(str, "\t\tname: %s\r\n", name); + furi_string_cat_printf(str, "\t\tname: %s\r\n", name); } if(args) { - string_cat_printf(str, "\t\targs: %s\r\n", args); + furi_string_cat_printf(str, "\t\targs: %s\r\n", args); } break; } case PB_Main_app_lock_status_request_tag: { - string_cat_printf(str, "\tapp_lock_status_request {\r\n"); + furi_string_cat_printf(str, "\tapp_lock_status_request {\r\n"); break; } case PB_Main_app_lock_status_response_tag: { - string_cat_printf(str, "\tapp_lock_status_response {\r\n"); + furi_string_cat_printf(str, "\tapp_lock_status_response {\r\n"); bool lock_status = message->content.app_lock_status_response.locked; - string_cat_printf(str, "\t\tlocked: %s\r\n", lock_status ? "true" : "false"); + furi_string_cat_printf(str, "\t\tlocked: %s\r\n", lock_status ? "true" : "false"); break; } case PB_Main_storage_md5sum_request_tag: { - string_cat_printf(str, "\tmd5sum_request {\r\n"); + furi_string_cat_printf(str, "\tmd5sum_request {\r\n"); const char* path = message->content.storage_md5sum_request.path; if(path) { - string_cat_printf(str, "\t\tpath: %s\r\n", path); + furi_string_cat_printf(str, "\t\tpath: %s\r\n", path); } break; } case PB_Main_storage_md5sum_response_tag: { - string_cat_printf(str, "\tmd5sum_response {\r\n"); + furi_string_cat_printf(str, "\tmd5sum_response {\r\n"); const char* path = message->content.storage_md5sum_response.md5sum; if(path) { - string_cat_printf(str, "\t\tmd5sum: %s\r\n", path); + furi_string_cat_printf(str, "\t\tmd5sum: %s\r\n", path); } break; } case PB_Main_system_ping_request_tag: - string_cat_printf(str, "\tping_request {\r\n"); + furi_string_cat_printf(str, "\tping_request {\r\n"); break; case PB_Main_system_ping_response_tag: - string_cat_printf(str, "\tping_response {\r\n"); + furi_string_cat_printf(str, "\tping_response {\r\n"); break; case PB_Main_system_device_info_request_tag: - string_cat_printf(str, "\tdevice_info_request {\r\n"); + furi_string_cat_printf(str, "\tdevice_info_request {\r\n"); break; case PB_Main_system_device_info_response_tag: - string_cat_printf(str, "\tdevice_info_response {\r\n"); - string_cat_printf( + furi_string_cat_printf(str, "\tdevice_info_response {\r\n"); + furi_string_cat_printf( str, "\t\t%s: %s\r\n", message->content.system_device_info_response.key, message->content.system_device_info_response.value); break; case PB_Main_storage_mkdir_request_tag: - string_cat_printf(str, "\tmkdir {\r\n"); + furi_string_cat_printf(str, "\tmkdir {\r\n"); break; case PB_Main_storage_delete_request_tag: { - string_cat_printf(str, "\tdelete {\r\n"); + furi_string_cat_printf(str, "\tdelete {\r\n"); const char* path = message->content.storage_delete_request.path; if(path) { - string_cat_printf(str, "\t\tpath: %s\r\n", path); + furi_string_cat_printf(str, "\t\tpath: %s\r\n", path); } break; } case PB_Main_empty_tag: - string_cat_printf(str, "\tempty {\r\n"); + furi_string_cat_printf(str, "\tempty {\r\n"); break; case PB_Main_storage_info_request_tag: { - string_cat_printf(str, "\tinfo_request {\r\n"); + furi_string_cat_printf(str, "\tinfo_request {\r\n"); const char* path = message->content.storage_info_request.path; if(path) { - string_cat_printf(str, "\t\tpath: %s\r\n", path); + furi_string_cat_printf(str, "\t\tpath: %s\r\n", path); } break; } case PB_Main_storage_info_response_tag: { - string_cat_printf(str, "\tinfo_response {\r\n"); - string_cat_printf( + furi_string_cat_printf(str, "\tinfo_response {\r\n"); + furi_string_cat_printf( str, "\t\ttotal_space: %lu\r\n", message->content.storage_info_response.total_space); - string_cat_printf( + furi_string_cat_printf( str, "\t\tfree_space: %lu\r\n", message->content.storage_info_response.free_space); break; } case PB_Main_storage_stat_request_tag: { - string_cat_printf(str, "\tstat_request {\r\n"); + furi_string_cat_printf(str, "\tstat_request {\r\n"); const char* path = message->content.storage_stat_request.path; if(path) { - string_cat_printf(str, "\t\tpath: %s\r\n", path); + furi_string_cat_printf(str, "\t\tpath: %s\r\n", path); } break; } case PB_Main_storage_stat_response_tag: { - string_cat_printf(str, "\tstat_response {\r\n"); + furi_string_cat_printf(str, "\tstat_response {\r\n"); if(message->content.storage_stat_response.has_file) { const PB_Storage_File* msg_file = &message->content.storage_stat_response.file; rpc_debug_print_file_msg(str, "\t\t\t", msg_file, 1); @@ -181,26 +180,26 @@ void rpc_debug_print_message(const PB_Main* message) { break; } case PB_Main_storage_list_request_tag: { - string_cat_printf(str, "\tlist_request {\r\n"); + furi_string_cat_printf(str, "\tlist_request {\r\n"); const char* path = message->content.storage_list_request.path; if(path) { - string_cat_printf(str, "\t\tpath: %s\r\n", path); + furi_string_cat_printf(str, "\t\tpath: %s\r\n", path); } break; } case PB_Main_storage_read_request_tag: { - string_cat_printf(str, "\tread_request {\r\n"); + furi_string_cat_printf(str, "\tread_request {\r\n"); const char* path = message->content.storage_read_request.path; if(path) { - string_cat_printf(str, "\t\tpath: %s\r\n", path); + furi_string_cat_printf(str, "\t\tpath: %s\r\n", path); } break; } case PB_Main_storage_write_request_tag: { - string_cat_printf(str, "\twrite_request {\r\n"); + furi_string_cat_printf(str, "\twrite_request {\r\n"); const char* path = message->content.storage_write_request.path; if(path) { - string_cat_printf(str, "\t\tpath: %s\r\n", path); + furi_string_cat_printf(str, "\t\tpath: %s\r\n", path); } if(message->content.storage_write_request.has_file) { const PB_Storage_File* msg_file = &message->content.storage_write_request.file; @@ -209,7 +208,7 @@ void rpc_debug_print_message(const PB_Main* message) { break; } case PB_Main_storage_read_response_tag: - string_cat_printf(str, "\tread_response {\r\n"); + furi_string_cat_printf(str, "\tread_response {\r\n"); if(message->content.storage_read_response.has_file) { const PB_Storage_File* msg_file = &message->content.storage_read_response.file; rpc_debug_print_file_msg(str, "\t\t\t", msg_file, 1); @@ -218,43 +217,43 @@ void rpc_debug_print_message(const PB_Main* message) { case PB_Main_storage_list_response_tag: { const PB_Storage_File* msg_file = message->content.storage_list_response.file; size_t msg_file_count = message->content.storage_list_response.file_count; - string_cat_printf(str, "\tlist_response {\r\n"); + furi_string_cat_printf(str, "\tlist_response {\r\n"); rpc_debug_print_file_msg(str, "\t\t", msg_file, msg_file_count); break; } case PB_Main_storage_rename_request_tag: { - string_cat_printf(str, "\trename_request {\r\n"); - string_cat_printf( + furi_string_cat_printf(str, "\trename_request {\r\n"); + furi_string_cat_printf( str, "\t\told_path: %s\r\n", message->content.storage_rename_request.old_path); - string_cat_printf( + furi_string_cat_printf( str, "\t\tnew_path: %s\r\n", message->content.storage_rename_request.new_path); break; } case PB_Main_gui_start_screen_stream_request_tag: - string_cat_printf(str, "\tstart_screen_stream {\r\n"); + furi_string_cat_printf(str, "\tstart_screen_stream {\r\n"); break; case PB_Main_gui_stop_screen_stream_request_tag: - string_cat_printf(str, "\tstop_screen_stream {\r\n"); + furi_string_cat_printf(str, "\tstop_screen_stream {\r\n"); break; case PB_Main_gui_screen_frame_tag: - string_cat_printf(str, "\tscreen_frame {\r\n"); + furi_string_cat_printf(str, "\tscreen_frame {\r\n"); break; case PB_Main_gui_send_input_event_request_tag: - string_cat_printf(str, "\tsend_input_event {\r\n"); - string_cat_printf( + furi_string_cat_printf(str, "\tsend_input_event {\r\n"); + furi_string_cat_printf( str, "\t\tkey: %d\r\n", message->content.gui_send_input_event_request.key); - string_cat_printf( + furi_string_cat_printf( str, "\t\type: %d\r\n", message->content.gui_send_input_event_request.type); break; case PB_Main_gui_start_virtual_display_request_tag: - string_cat_printf(str, "\tstart_virtual_display {\r\n"); + furi_string_cat_printf(str, "\tstart_virtual_display {\r\n"); break; case PB_Main_gui_stop_virtual_display_request_tag: - string_cat_printf(str, "\tstop_virtual_display {\r\n"); + furi_string_cat_printf(str, "\tstop_virtual_display {\r\n"); break; } - string_cat_printf(str, "\t}\r\n}\r\n"); - printf("%s", string_get_cstr(str)); + furi_string_cat_printf(str, "\t}\r\n}\r\n"); + printf("%s", furi_string_get_cstr(str)); - string_clear(str); + furi_string_free(str); } diff --git a/applications/services/rpc/rpc_i.h b/applications/services/rpc/rpc_i.h index 9ffd054a0c6..af9033f0a34 100644 --- a/applications/services/rpc/rpc_i.h +++ b/applications/services/rpc/rpc_i.h @@ -38,6 +38,6 @@ void rpc_system_gpio_free(void* ctx); void rpc_debug_print_message(const PB_Main* message); void rpc_debug_print_data(const char* prefix, uint8_t* buffer, size_t size); -void rpc_cli_command_start_session(Cli* cli, string_t args, void* context); +void rpc_cli_command_start_session(Cli* cli, FuriString* args, void* context); PB_CommandStatus rpc_system_storage_get_error(FS_Error fs_error); diff --git a/applications/services/storage/storage.h b/applications/services/storage/storage.h index 1a7c934950a..968b69048e2 100644 --- a/applications/services/storage/storage.h +++ b/applications/services/storage/storage.h @@ -1,6 +1,5 @@ #pragma once #include -#include #include "filesystem_api_defines.h" #include "storage_sd_api.h" @@ -292,7 +291,7 @@ FS_Error storage_sd_status(Storage* api); /******************* Internal LFS Functions *******************/ -typedef void (*Storage_name_converter)(string_t); +typedef void (*Storage_name_converter)(FuriString*); /** Backs up internal storage to a tar archive * @param api pointer to the api @@ -350,7 +349,7 @@ void storage_get_next_filename( const char* dirname, const char* filename, const char* fileextension, - string_t nextfilename, + FuriString* nextfilename, uint8_t max_len); #ifdef __cplusplus diff --git a/applications/services/storage/storage_cli.c b/applications/services/storage/storage_cli.c index 802ebd54805..5e72dce8e72 100644 --- a/applications/services/storage/storage_cli.c +++ b/applications/services/storage/storage_cli.c @@ -38,11 +38,11 @@ static void storage_cli_print_error(FS_Error error) { printf("Storage error: %s\r\n", storage_error_get_desc(error)); } -static void storage_cli_info(Cli* cli, string_t path) { +static void storage_cli_info(Cli* cli, FuriString* path) { UNUSED(cli); Storage* api = furi_record_open(RECORD_STORAGE); - if(string_cmp_str(path, STORAGE_INT_PATH_PREFIX) == 0) { + if(furi_string_cmp_str(path, STORAGE_INT_PATH_PREFIX) == 0) { uint64_t total_space; uint64_t free_space; FS_Error error = @@ -57,7 +57,7 @@ static void storage_cli_info(Cli* cli, string_t path) { (uint32_t)(total_space / 1024), (uint32_t)(free_space / 1024)); } - } else if(string_cmp_str(path, STORAGE_EXT_PATH_PREFIX) == 0) { + } else if(furi_string_cmp_str(path, STORAGE_EXT_PATH_PREFIX) == 0) { SDInfo sd_info; FS_Error error = storage_sd_info(api, &sd_info); @@ -78,10 +78,10 @@ static void storage_cli_info(Cli* cli, string_t path) { furi_record_close(RECORD_STORAGE); }; -static void storage_cli_format(Cli* cli, string_t path) { - if(string_cmp_str(path, STORAGE_INT_PATH_PREFIX) == 0) { +static void storage_cli_format(Cli* cli, FuriString* path) { + if(furi_string_cmp_str(path, STORAGE_INT_PATH_PREFIX) == 0) { storage_cli_print_error(FSE_NOT_IMPLEMENTED); - } else if(string_cmp_str(path, STORAGE_EXT_PATH_PREFIX) == 0) { + } else if(furi_string_cmp_str(path, STORAGE_EXT_PATH_PREFIX) == 0) { printf("Formatting SD card, All data will be lost! Are you sure (y/n)?\r\n"); char answer = cli_getc(cli); if(answer == 'y' || answer == 'Y') { @@ -104,9 +104,9 @@ static void storage_cli_format(Cli* cli, string_t path) { } }; -static void storage_cli_list(Cli* cli, string_t path) { +static void storage_cli_list(Cli* cli, FuriString* path) { UNUSED(cli); - if(string_cmp_str(path, "/") == 0) { + if(furi_string_cmp_str(path, "/") == 0) { printf("\t[D] int\r\n"); printf("\t[D] ext\r\n"); printf("\t[D] any\r\n"); @@ -114,7 +114,7 @@ static void storage_cli_list(Cli* cli, string_t path) { Storage* api = furi_record_open(RECORD_STORAGE); File* file = storage_file_alloc(api); - if(storage_dir_open(file, string_get_cstr(path))) { + if(storage_dir_open(file, furi_string_get_cstr(path))) { FileInfo fileinfo; char name[MAX_NAME_LENGTH]; bool read_done = false; @@ -141,28 +141,31 @@ static void storage_cli_list(Cli* cli, string_t path) { } } -static void storage_cli_tree(Cli* cli, string_t path) { - if(string_cmp_str(path, "/") == 0) { - string_set(path, STORAGE_INT_PATH_PREFIX); +static void storage_cli_tree(Cli* cli, FuriString* path) { + if(furi_string_cmp_str(path, "/") == 0) { + furi_string_set(path, STORAGE_INT_PATH_PREFIX); storage_cli_tree(cli, path); - string_set(path, STORAGE_EXT_PATH_PREFIX); + furi_string_set(path, STORAGE_EXT_PATH_PREFIX); storage_cli_tree(cli, path); } else { Storage* api = furi_record_open(RECORD_STORAGE); DirWalk* dir_walk = dir_walk_alloc(api); - string_t name; - string_init(name); + FuriString* name; + name = furi_string_alloc(); - if(dir_walk_open(dir_walk, string_get_cstr(path))) { + if(dir_walk_open(dir_walk, furi_string_get_cstr(path))) { FileInfo fileinfo; bool read_done = false; while(dir_walk_read(dir_walk, name, &fileinfo) == DirWalkOK) { read_done = true; if(fileinfo.flags & FSF_DIRECTORY) { - printf("\t[D] %s\r\n", string_get_cstr(name)); + printf("\t[D] %s\r\n", furi_string_get_cstr(name)); } else { - printf("\t[F] %s %lub\r\n", string_get_cstr(name), (uint32_t)(fileinfo.size)); + printf( + "\t[F] %s %lub\r\n", + furi_string_get_cstr(name), + (uint32_t)(fileinfo.size)); } } @@ -173,18 +176,18 @@ static void storage_cli_tree(Cli* cli, string_t path) { storage_cli_print_error(dir_walk_get_error(dir_walk)); } - string_clear(name); + furi_string_free(name); dir_walk_free(dir_walk); furi_record_close(RECORD_STORAGE); } } -static void storage_cli_read(Cli* cli, string_t path) { +static void storage_cli_read(Cli* cli, FuriString* path) { UNUSED(cli); Storage* api = furi_record_open(RECORD_STORAGE); File* file = storage_file_alloc(api); - if(storage_file_open(file, string_get_cstr(path), FSAM_READ, FSOM_OPEN_EXISTING)) { + if(storage_file_open(file, furi_string_get_cstr(path), FSAM_READ, FSOM_OPEN_EXISTING)) { const uint16_t buffer_size = 128; uint16_t read_size = 0; uint8_t* data = malloc(buffer_size); @@ -210,14 +213,14 @@ static void storage_cli_read(Cli* cli, string_t path) { furi_record_close(RECORD_STORAGE); } -static void storage_cli_write(Cli* cli, string_t path) { +static void storage_cli_write(Cli* cli, FuriString* path) { Storage* api = furi_record_open(RECORD_STORAGE); File* file = storage_file_alloc(api); const uint16_t buffer_size = 512; uint8_t* buffer = malloc(buffer_size); - if(storage_file_open(file, string_get_cstr(path), FSAM_WRITE, FSOM_OPEN_APPEND)) { + if(storage_file_open(file, furi_string_get_cstr(path), FSAM_WRITE, FSOM_OPEN_APPEND)) { printf("Just write your text data. New line by Ctrl+Enter, exit by Ctrl+C.\r\n"); uint32_t read_index = 0; @@ -264,16 +267,16 @@ static void storage_cli_write(Cli* cli, string_t path) { furi_record_close(RECORD_STORAGE); } -static void storage_cli_read_chunks(Cli* cli, string_t path, string_t args) { +static void storage_cli_read_chunks(Cli* cli, FuriString* path, FuriString* args) { Storage* api = furi_record_open(RECORD_STORAGE); File* file = storage_file_alloc(api); uint32_t buffer_size; - int parsed_count = sscanf(string_get_cstr(args), "%lu", &buffer_size); + int parsed_count = sscanf(furi_string_get_cstr(args), "%lu", &buffer_size); if(parsed_count == EOF || parsed_count != 1) { storage_cli_print_usage(); - } else if(storage_file_open(file, string_get_cstr(path), FSAM_READ, FSOM_OPEN_EXISTING)) { + } else if(storage_file_open(file, furi_string_get_cstr(path), FSAM_READ, FSOM_OPEN_EXISTING)) { uint64_t file_size = storage_file_size(file); printf("Size: %lu\r\n", (uint32_t)file_size); @@ -304,17 +307,17 @@ static void storage_cli_read_chunks(Cli* cli, string_t path, string_t args) { furi_record_close(RECORD_STORAGE); } -static void storage_cli_write_chunk(Cli* cli, string_t path, string_t args) { +static void storage_cli_write_chunk(Cli* cli, FuriString* path, FuriString* args) { Storage* api = furi_record_open(RECORD_STORAGE); File* file = storage_file_alloc(api); uint32_t buffer_size; - int parsed_count = sscanf(string_get_cstr(args), "%lu", &buffer_size); + int parsed_count = sscanf(furi_string_get_cstr(args), "%lu", &buffer_size); if(parsed_count == EOF || parsed_count != 1) { storage_cli_print_usage(); } else { - if(storage_file_open(file, string_get_cstr(path), FSAM_WRITE, FSOM_OPEN_APPEND)) { + if(storage_file_open(file, furi_string_get_cstr(path), FSAM_WRITE, FSOM_OPEN_APPEND)) { printf("Ready\r\n"); if(buffer_size) { @@ -342,20 +345,20 @@ static void storage_cli_write_chunk(Cli* cli, string_t path, string_t args) { furi_record_close(RECORD_STORAGE); } -static void storage_cli_stat(Cli* cli, string_t path) { +static void storage_cli_stat(Cli* cli, FuriString* path) { UNUSED(cli); Storage* api = furi_record_open(RECORD_STORAGE); - if(string_cmp_str(path, "/") == 0) { + if(furi_string_cmp_str(path, "/") == 0) { printf("Storage\r\n"); } else if( - string_cmp_str(path, STORAGE_EXT_PATH_PREFIX) == 0 || - string_cmp_str(path, STORAGE_INT_PATH_PREFIX) == 0 || - string_cmp_str(path, STORAGE_ANY_PATH_PREFIX) == 0) { + furi_string_cmp_str(path, STORAGE_EXT_PATH_PREFIX) == 0 || + furi_string_cmp_str(path, STORAGE_INT_PATH_PREFIX) == 0 || + furi_string_cmp_str(path, STORAGE_ANY_PATH_PREFIX) == 0) { uint64_t total_space; uint64_t free_space; FS_Error error = - storage_common_fs_info(api, string_get_cstr(path), &total_space, &free_space); + storage_common_fs_info(api, furi_string_get_cstr(path), &total_space, &free_space); if(error != FSE_OK) { storage_cli_print_error(error); @@ -367,7 +370,7 @@ static void storage_cli_stat(Cli* cli, string_t path) { } } else { FileInfo fileinfo; - FS_Error error = storage_common_stat(api, string_get_cstr(path), &fileinfo); + FS_Error error = storage_common_stat(api, furi_string_get_cstr(path), &fileinfo); if(error == FSE_OK) { if(fileinfo.flags & FSF_DIRECTORY) { @@ -383,31 +386,31 @@ static void storage_cli_stat(Cli* cli, string_t path) { furi_record_close(RECORD_STORAGE); } -static void storage_cli_copy(Cli* cli, string_t old_path, string_t args) { +static void storage_cli_copy(Cli* cli, FuriString* old_path, FuriString* args) { UNUSED(cli); Storage* api = furi_record_open(RECORD_STORAGE); - string_t new_path; - string_init(new_path); + FuriString* new_path; + new_path = furi_string_alloc(); if(!args_read_probably_quoted_string_and_trim(args, new_path)) { storage_cli_print_usage(); } else { - FS_Error error = - storage_common_copy(api, string_get_cstr(old_path), string_get_cstr(new_path)); + FS_Error error = storage_common_copy( + api, furi_string_get_cstr(old_path), furi_string_get_cstr(new_path)); if(error != FSE_OK) { storage_cli_print_error(error); } } - string_clear(new_path); + furi_string_free(new_path); furi_record_close(RECORD_STORAGE); } -static void storage_cli_remove(Cli* cli, string_t path) { +static void storage_cli_remove(Cli* cli, FuriString* path) { UNUSED(cli); Storage* api = furi_record_open(RECORD_STORAGE); - FS_Error error = storage_common_remove(api, string_get_cstr(path)); + FS_Error error = storage_common_remove(api, furi_string_get_cstr(path)); if(error != FSE_OK) { storage_cli_print_error(error); @@ -416,31 +419,31 @@ static void storage_cli_remove(Cli* cli, string_t path) { furi_record_close(RECORD_STORAGE); } -static void storage_cli_rename(Cli* cli, string_t old_path, string_t args) { +static void storage_cli_rename(Cli* cli, FuriString* old_path, FuriString* args) { UNUSED(cli); Storage* api = furi_record_open(RECORD_STORAGE); - string_t new_path; - string_init(new_path); + FuriString* new_path; + new_path = furi_string_alloc(); if(!args_read_probably_quoted_string_and_trim(args, new_path)) { storage_cli_print_usage(); } else { - FS_Error error = - storage_common_rename(api, string_get_cstr(old_path), string_get_cstr(new_path)); + FS_Error error = storage_common_rename( + api, furi_string_get_cstr(old_path), furi_string_get_cstr(new_path)); if(error != FSE_OK) { storage_cli_print_error(error); } } - string_clear(new_path); + furi_string_free(new_path); furi_record_close(RECORD_STORAGE); } -static void storage_cli_mkdir(Cli* cli, string_t path) { +static void storage_cli_mkdir(Cli* cli, FuriString* path) { UNUSED(cli); Storage* api = furi_record_open(RECORD_STORAGE); - FS_Error error = storage_common_mkdir(api, string_get_cstr(path)); + FS_Error error = storage_common_mkdir(api, furi_string_get_cstr(path)); if(error != FSE_OK) { storage_cli_print_error(error); @@ -449,12 +452,12 @@ static void storage_cli_mkdir(Cli* cli, string_t path) { furi_record_close(RECORD_STORAGE); } -static void storage_cli_md5(Cli* cli, string_t path) { +static void storage_cli_md5(Cli* cli, FuriString* path) { UNUSED(cli); Storage* api = furi_record_open(RECORD_STORAGE); File* file = storage_file_alloc(api); - if(storage_file_open(file, string_get_cstr(path), FSAM_READ, FSOM_OPEN_EXISTING)) { + if(storage_file_open(file, furi_string_get_cstr(path), FSAM_READ, FSOM_OPEN_EXISTING)) { const uint16_t buffer_size = 512; const uint8_t hash_size = 16; uint8_t* data = malloc(buffer_size); @@ -487,12 +490,12 @@ static void storage_cli_md5(Cli* cli, string_t path) { furi_record_close(RECORD_STORAGE); } -void storage_cli(Cli* cli, string_t args, void* context) { +void storage_cli(Cli* cli, FuriString* args, void* context) { UNUSED(context); - string_t cmd; - string_t path; - string_init(cmd); - string_init(path); + FuriString* cmd; + FuriString* path; + cmd = furi_string_alloc(); + path = furi_string_alloc(); do { if(!args_read_string_and_trim(args, cmd)) { @@ -505,72 +508,72 @@ void storage_cli(Cli* cli, string_t args, void* context) { break; } - if(string_cmp_str(cmd, "info") == 0) { + if(furi_string_cmp_str(cmd, "info") == 0) { storage_cli_info(cli, path); break; } - if(string_cmp_str(cmd, "format") == 0) { + if(furi_string_cmp_str(cmd, "format") == 0) { storage_cli_format(cli, path); break; } - if(string_cmp_str(cmd, "list") == 0) { + if(furi_string_cmp_str(cmd, "list") == 0) { storage_cli_list(cli, path); break; } - if(string_cmp_str(cmd, "tree") == 0) { + if(furi_string_cmp_str(cmd, "tree") == 0) { storage_cli_tree(cli, path); break; } - if(string_cmp_str(cmd, "read") == 0) { + if(furi_string_cmp_str(cmd, "read") == 0) { storage_cli_read(cli, path); break; } - if(string_cmp_str(cmd, "read_chunks") == 0) { + if(furi_string_cmp_str(cmd, "read_chunks") == 0) { storage_cli_read_chunks(cli, path, args); break; } - if(string_cmp_str(cmd, "write") == 0) { + if(furi_string_cmp_str(cmd, "write") == 0) { storage_cli_write(cli, path); break; } - if(string_cmp_str(cmd, "write_chunk") == 0) { + if(furi_string_cmp_str(cmd, "write_chunk") == 0) { storage_cli_write_chunk(cli, path, args); break; } - if(string_cmp_str(cmd, "copy") == 0) { + if(furi_string_cmp_str(cmd, "copy") == 0) { storage_cli_copy(cli, path, args); break; } - if(string_cmp_str(cmd, "remove") == 0) { + if(furi_string_cmp_str(cmd, "remove") == 0) { storage_cli_remove(cli, path); break; } - if(string_cmp_str(cmd, "rename") == 0) { + if(furi_string_cmp_str(cmd, "rename") == 0) { storage_cli_rename(cli, path, args); break; } - if(string_cmp_str(cmd, "mkdir") == 0) { + if(furi_string_cmp_str(cmd, "mkdir") == 0) { storage_cli_mkdir(cli, path); break; } - if(string_cmp_str(cmd, "md5") == 0) { + if(furi_string_cmp_str(cmd, "md5") == 0) { storage_cli_md5(cli, path); break; } - if(string_cmp_str(cmd, "stat") == 0) { + if(furi_string_cmp_str(cmd, "stat") == 0) { storage_cli_stat(cli, path); break; } @@ -578,11 +581,11 @@ void storage_cli(Cli* cli, string_t args, void* context) { storage_cli_print_usage(); } while(false); - string_clear(path); - string_clear(cmd); + furi_string_free(path); + furi_string_free(cmd); } -static void storage_cli_factory_reset(Cli* cli, string_t args, void* context) { +static void storage_cli_factory_reset(Cli* cli, FuriString* args, void* context) { UNUSED(args); UNUSED(context); printf("All data will be lost! Are you sure (y/n)?\r\n"); diff --git a/applications/services/storage/storage_external_api.c b/applications/services/storage/storage_external_api.c index 80cafb2828c..379fc4ed110 100644 --- a/applications/services/storage/storage_external_api.c +++ b/applications/services/storage/storage_external_api.c @@ -1,6 +1,5 @@ #include #include -#include #include "storage.h" #include "storage_i.h" #include "storage_message.h" @@ -374,13 +373,13 @@ static FS_Error storage_copy_recursive(Storage* storage, const char* old_path, const char* new_path) { FS_Error error = storage_common_mkdir(storage, new_path); DirWalk* dir_walk = dir_walk_alloc(storage); - string_t path; - string_t tmp_new_path; - string_t tmp_old_path; + FuriString* path; + FuriString* tmp_new_path; + FuriString* tmp_old_path; FileInfo fileinfo; - string_init(path); - string_init(tmp_new_path); - string_init(tmp_old_path); + path = furi_string_alloc(); + tmp_new_path = furi_string_alloc(); + tmp_old_path = furi_string_alloc(); do { if(error != FSE_OK) break; @@ -399,15 +398,17 @@ static FS_Error } else if(res == DirWalkLast) { break; } else { - string_set(tmp_old_path, path); - string_right(path, strlen(old_path)); - string_printf(tmp_new_path, "%s%s", new_path, string_get_cstr(path)); + furi_string_set(tmp_old_path, path); + furi_string_right(path, strlen(old_path)); + furi_string_printf(tmp_new_path, "%s%s", new_path, furi_string_get_cstr(path)); if(fileinfo.flags & FSF_DIRECTORY) { - error = storage_common_mkdir(storage, string_get_cstr(tmp_new_path)); + error = storage_common_mkdir(storage, furi_string_get_cstr(tmp_new_path)); } else { error = storage_common_copy( - storage, string_get_cstr(tmp_old_path), string_get_cstr(tmp_new_path)); + storage, + furi_string_get_cstr(tmp_old_path), + furi_string_get_cstr(tmp_new_path)); } if(error != FSE_OK) break; @@ -416,9 +417,9 @@ static FS_Error } while(false); - string_clear(tmp_new_path); - string_clear(tmp_old_path); - string_clear(path); + furi_string_free(tmp_new_path); + furi_string_free(tmp_old_path); + furi_string_free(path); dir_walk_free(dir_walk); return error; } @@ -459,11 +460,11 @@ static FS_Error storage_merge_recursive(Storage* storage, const char* old_path, const char* new_path) { FS_Error error = storage_common_mkdir(storage, new_path); DirWalk* dir_walk = dir_walk_alloc(storage); - string_t path, file_basename, tmp_new_path; + FuriString *path, *file_basename, *tmp_new_path; FileInfo fileinfo; - string_init(path); - string_init(file_basename); - string_init(tmp_new_path); + path = furi_string_alloc(); + file_basename = furi_string_alloc(); + tmp_new_path = furi_string_alloc(); do { if((error != FSE_OK) && (error != FSE_EXIST)) break; @@ -483,14 +484,15 @@ static FS_Error } else if(res == DirWalkLast) { break; } else { - path_extract_basename(string_get_cstr(path), file_basename); - path_concat(new_path, string_get_cstr(file_basename), tmp_new_path); + path_extract_basename(furi_string_get_cstr(path), file_basename); + path_concat(new_path, furi_string_get_cstr(file_basename), tmp_new_path); if(fileinfo.flags & FSF_DIRECTORY) { - if(storage_common_stat(storage, string_get_cstr(tmp_new_path), &fileinfo) == - FSE_OK) { + if(storage_common_stat( + storage, furi_string_get_cstr(tmp_new_path), &fileinfo) == FSE_OK) { if(fileinfo.flags & FSF_DIRECTORY) { - error = storage_common_mkdir(storage, string_get_cstr(tmp_new_path)); + error = + storage_common_mkdir(storage, furi_string_get_cstr(tmp_new_path)); if(error != FSE_OK) { break; } @@ -498,7 +500,7 @@ static FS_Error } } error = storage_common_merge( - storage, string_get_cstr(path), string_get_cstr(tmp_new_path)); + storage, furi_string_get_cstr(path), furi_string_get_cstr(tmp_new_path)); if(error != FSE_OK) { break; @@ -508,9 +510,9 @@ static FS_Error } while(false); - string_clear(tmp_new_path); - string_clear(file_basename); - string_clear(path); + furi_string_free(tmp_new_path); + furi_string_free(file_basename); + furi_string_free(path); dir_walk_free(dir_walk); return error; } @@ -518,8 +520,8 @@ static FS_Error FS_Error storage_common_merge(Storage* storage, const char* old_path, const char* new_path) { FS_Error error; const char* new_path_tmp; - string_t new_path_next; - string_init(new_path_next); + FuriString* new_path_next; + new_path_next = furi_string_alloc(); FileInfo fileinfo; error = storage_common_stat(storage, old_path, &fileinfo); @@ -530,13 +532,13 @@ FS_Error storage_common_merge(Storage* storage, const char* old_path, const char } else { error = storage_common_stat(storage, new_path, &fileinfo); if(error == FSE_OK) { - string_set_str(new_path_next, new_path); - string_t dir_path; - string_t filename; + furi_string_set(new_path_next, new_path); + FuriString* dir_path; + FuriString* filename; char extension[MAX_EXT_LEN]; - string_init(dir_path); - string_init(filename); + dir_path = furi_string_alloc(); + filename = furi_string_alloc(); path_extract_filename(new_path_next, filename, true); path_extract_dirname(new_path, dir_path); @@ -544,17 +546,18 @@ FS_Error storage_common_merge(Storage* storage, const char* old_path, const char storage_get_next_filename( storage, - string_get_cstr(dir_path), - string_get_cstr(filename), + furi_string_get_cstr(dir_path), + furi_string_get_cstr(filename), extension, new_path_next, 255); - string_cat_printf(dir_path, "/%s%s", string_get_cstr(new_path_next), extension); - string_set(new_path_next, dir_path); + furi_string_cat_printf( + dir_path, "/%s%s", furi_string_get_cstr(new_path_next), extension); + furi_string_set(new_path_next, dir_path); - string_clear(dir_path); - string_clear(filename); - new_path_tmp = string_get_cstr(new_path_next); + furi_string_free(dir_path); + furi_string_free(filename); + new_path_tmp = furi_string_get_cstr(new_path_next); } else { new_path_tmp = new_path; } @@ -577,7 +580,7 @@ FS_Error storage_common_merge(Storage* storage, const char* old_path, const char } } - string_clear(new_path_next); + furi_string_free(new_path_next); return error; } @@ -707,8 +710,8 @@ bool storage_simply_remove_recursive(Storage* storage, const char* path) { furi_assert(path); FileInfo fileinfo; bool result = false; - string_t fullname; - string_t cur_dir; + FuriString* fullname; + FuriString* cur_dir; if(storage_simply_remove(storage, path)) { return true; @@ -716,26 +719,26 @@ bool storage_simply_remove_recursive(Storage* storage, const char* path) { char* name = malloc(MAX_NAME_LENGTH + 1); File* dir = storage_file_alloc(storage); - string_init_set_str(cur_dir, path); + cur_dir = furi_string_alloc_set(path); bool go_deeper = false; while(1) { - if(!storage_dir_open(dir, string_get_cstr(cur_dir))) { + if(!storage_dir_open(dir, furi_string_get_cstr(cur_dir))) { storage_dir_close(dir); break; } while(storage_dir_read(dir, &fileinfo, name, MAX_NAME_LENGTH)) { if(fileinfo.flags & FSF_DIRECTORY) { - string_cat_printf(cur_dir, "/%s", name); + furi_string_cat_printf(cur_dir, "/%s", name); go_deeper = true; break; } - string_init_printf(fullname, "%s/%s", string_get_cstr(cur_dir), name); - FS_Error error = storage_common_remove(storage, string_get_cstr(fullname)); + fullname = furi_string_alloc_printf("%s/%s", furi_string_get_cstr(cur_dir), name); + FS_Error error = storage_common_remove(storage, furi_string_get_cstr(fullname)); furi_check(error == FSE_OK); - string_clear(fullname); + furi_string_free(fullname); } storage_dir_close(dir); @@ -744,13 +747,13 @@ bool storage_simply_remove_recursive(Storage* storage, const char* path) { continue; } - FS_Error error = storage_common_remove(storage, string_get_cstr(cur_dir)); + FS_Error error = storage_common_remove(storage, furi_string_get_cstr(cur_dir)); furi_check(error == FSE_OK); - if(string_cmp(cur_dir, path)) { - size_t last_char = string_search_rchar(cur_dir, '/'); - furi_assert(last_char != STRING_FAILURE); - string_left(cur_dir, last_char); + if(furi_string_cmp(cur_dir, path)) { + size_t last_char = furi_string_search_rchar(cur_dir, '/'); + furi_assert(last_char != FURI_STRING_FAILURE); + furi_string_left(cur_dir, last_char); } else { result = true; break; @@ -758,7 +761,7 @@ bool storage_simply_remove_recursive(Storage* storage, const char* path) { } storage_file_free(dir); - string_clear(cur_dir); + furi_string_free(cur_dir); free(name); return result; } @@ -780,22 +783,22 @@ void storage_get_next_filename( const char* dirname, const char* filename, const char* fileextension, - string_t nextfilename, + FuriString* nextfilename, uint8_t max_len) { - string_t temp_str; + FuriString* temp_str; uint16_t num = 0; - string_init_printf(temp_str, "%s/%s%s", dirname, filename, fileextension); + temp_str = furi_string_alloc_printf("%s/%s%s", dirname, filename, fileextension); - while(storage_common_stat(storage, string_get_cstr(temp_str), NULL) == FSE_OK) { + while(storage_common_stat(storage, furi_string_get_cstr(temp_str), NULL) == FSE_OK) { num++; - string_printf(temp_str, "%s/%s%d%s", dirname, filename, num, fileextension); + furi_string_printf(temp_str, "%s/%s%d%s", dirname, filename, num, fileextension); } if(num && (max_len > strlen(filename))) { - string_printf(nextfilename, "%s%d", filename, num); + furi_string_printf(nextfilename, "%s%d", filename, num); } else { - string_printf(nextfilename, "%s", filename); + furi_string_printf(nextfilename, "%s", filename); } - string_clear(temp_str); + furi_string_free(temp_str); } diff --git a/applications/services/storage/storage_glue.c b/applications/services/storage/storage_glue.c index d9d599c5c9e..c5682f67bf7 100644 --- a/applications/services/storage/storage_glue.c +++ b/applications/services/storage/storage_glue.c @@ -7,25 +7,25 @@ void storage_file_init(StorageFile* obj) { obj->file = NULL; obj->type = ST_ERROR; obj->file_data = NULL; - string_init(obj->path); + obj->path = furi_string_alloc(); } void storage_file_init_set(StorageFile* obj, const StorageFile* src) { obj->file = src->file; obj->type = src->type; obj->file_data = src->file_data; - string_init_set(obj->path, src->path); + obj->path = furi_string_alloc_set(src->path); } void storage_file_set(StorageFile* obj, const StorageFile* src) { obj->file = src->file; obj->type = src->type; obj->file_data = src->file_data; - string_set(obj->path, src->path); + furi_string_set(obj->path, src->path); } void storage_file_clear(StorageFile* obj) { - string_clear(obj->path); + furi_string_free(obj->path); } /****************** storage data ******************/ @@ -101,7 +101,7 @@ bool storage_has_file(const File* file, StorageData* storage_data) { return result; } -bool storage_path_already_open(string_t path, StorageFileList_t array) { +bool storage_path_already_open(FuriString* path, StorageFileList_t array) { bool open = false; StorageFileList_it_t it; @@ -109,7 +109,7 @@ bool storage_path_already_open(string_t path, StorageFileList_t array) { for(StorageFileList_it(it, array); !StorageFileList_end_p(it); StorageFileList_next(it)) { const StorageFile* storage_file = StorageFileList_cref(it); - if(string_cmp(storage_file->path, path) == 0) { + if(furi_string_cmp(storage_file->path, path) == 0) { open = true; break; } @@ -158,14 +158,18 @@ void* storage_get_storage_file_data(const File* file, StorageData* storage) { return founded_file->file_data; } -void storage_push_storage_file(File* file, string_t path, StorageType type, StorageData* storage) { +void storage_push_storage_file( + File* file, + FuriString* path, + StorageType type, + StorageData* storage) { StorageFile* storage_file = StorageFileList_push_new(storage->files); furi_check(storage_file != NULL); file->file_id = (uint32_t)storage_file; storage_file->file = file; storage_file->type = type; - string_set(storage_file->path, path); + furi_string_set(storage_file->path, path); } bool storage_pop_storage_file(File* file, StorageData* storage) { diff --git a/applications/services/storage/storage_glue.h b/applications/services/storage/storage_glue.h index 7cf2e072a9e..53fa0de1909 100644 --- a/applications/services/storage/storage_glue.h +++ b/applications/services/storage/storage_glue.h @@ -2,7 +2,6 @@ #include #include "filesystem_api_internal.h" -#include #include #ifdef __cplusplus @@ -21,7 +20,7 @@ typedef struct { File* file; StorageType type; void* file_data; - string_t path; + FuriString* path; } StorageFile; typedef enum { @@ -62,12 +61,16 @@ struct StorageData { }; bool storage_has_file(const File* file, StorageData* storage_data); -bool storage_path_already_open(string_t path, StorageFileList_t files); +bool storage_path_already_open(FuriString* path, StorageFileList_t files); void storage_set_storage_file_data(const File* file, void* file_data, StorageData* storage); void* storage_get_storage_file_data(const File* file, StorageData* storage); -void storage_push_storage_file(File* file, string_t path, StorageType type, StorageData* storage); +void storage_push_storage_file( + File* file, + FuriString* path, + StorageType type, + StorageData* storage); bool storage_pop_storage_file(File* file, StorageData* storage); #ifdef __cplusplus diff --git a/applications/services/storage/storage_internal_api.c b/applications/services/storage/storage_internal_api.c index 620eae3673e..6d620b9c07b 100644 --- a/applications/services/storage/storage_internal_api.c +++ b/applications/services/storage/storage_internal_api.c @@ -1,5 +1,4 @@ #include -#include #include "storage.h" #include diff --git a/applications/services/storage/storage_processing.c b/applications/services/storage/storage_processing.c index 46ca4e16582..8643e974e67 100644 --- a/applications/services/storage/storage_processing.c +++ b/applications/services/storage/storage_processing.c @@ -1,7 +1,6 @@ #include "storage_processing.h" #include #include -#include #define FS_CALL(_storage, _fn) \ storage_data_lock(_storage); \ @@ -68,21 +67,22 @@ static StorageType storage_get_type_by_path(Storage* app, const char* path) { return type; } -static void storage_path_change_to_real_storage(string_t path, StorageType real_storage) { - if(memcmp(string_get_cstr(path), STORAGE_ANY_PATH_PREFIX, strlen(STORAGE_ANY_PATH_PREFIX)) == +static void storage_path_change_to_real_storage(FuriString* path, StorageType real_storage) { + if(memcmp( + furi_string_get_cstr(path), STORAGE_ANY_PATH_PREFIX, strlen(STORAGE_ANY_PATH_PREFIX)) == 0) { switch(real_storage) { case ST_EXT: - string_set_char(path, 0, STORAGE_EXT_PATH_PREFIX[0]); - string_set_char(path, 1, STORAGE_EXT_PATH_PREFIX[1]); - string_set_char(path, 2, STORAGE_EXT_PATH_PREFIX[2]); - string_set_char(path, 3, STORAGE_EXT_PATH_PREFIX[3]); + furi_string_set_char(path, 0, STORAGE_EXT_PATH_PREFIX[0]); + furi_string_set_char(path, 1, STORAGE_EXT_PATH_PREFIX[1]); + furi_string_set_char(path, 2, STORAGE_EXT_PATH_PREFIX[2]); + furi_string_set_char(path, 3, STORAGE_EXT_PATH_PREFIX[3]); break; case ST_INT: - string_set_char(path, 0, STORAGE_INT_PATH_PREFIX[0]); - string_set_char(path, 1, STORAGE_INT_PATH_PREFIX[1]); - string_set_char(path, 2, STORAGE_INT_PATH_PREFIX[2]); - string_set_char(path, 3, STORAGE_INT_PATH_PREFIX[3]); + furi_string_set_char(path, 0, STORAGE_INT_PATH_PREFIX[0]); + furi_string_set_char(path, 1, STORAGE_INT_PATH_PREFIX[1]); + furi_string_set_char(path, 2, STORAGE_INT_PATH_PREFIX[2]); + furi_string_set_char(path, 3, STORAGE_INT_PATH_PREFIX[3]); break; default: break; @@ -107,8 +107,8 @@ bool storage_process_file_open( file->error_id = FSE_INVALID_NAME; } else { storage = storage_get_storage_by_type(app, type); - string_t real_path; - string_init_set(real_path, path); + FuriString* real_path; + real_path = furi_string_alloc_set(path); storage_path_change_to_real_storage(real_path, type); if(storage_path_already_open(real_path, storage->files)) { @@ -118,7 +118,7 @@ bool storage_process_file_open( FS_CALL(storage, file.open(storage, file, remove_vfs(path), access_mode, open_mode)); } - string_clear(real_path); + furi_string_free(real_path); } return ret; @@ -266,8 +266,8 @@ bool storage_process_dir_open(Storage* app, File* file, const char* path) { file->error_id = FSE_INVALID_NAME; } else { storage = storage_get_storage_by_type(app, type); - string_t real_path; - string_init_set(real_path, path); + FuriString* real_path; + real_path = furi_string_alloc_set(path); storage_path_change_to_real_storage(real_path, type); if(storage_path_already_open(real_path, storage->files)) { @@ -276,7 +276,7 @@ bool storage_process_dir_open(Storage* app, File* file, const char* path) { storage_push_storage_file(file, real_path, type, storage); FS_CALL(storage, dir.open(storage, file, remove_vfs(path))); } - string_clear(real_path); + furi_string_free(real_path); } return ret; @@ -350,8 +350,8 @@ static FS_Error storage_process_common_remove(Storage* app, const char* path) { FS_Error ret = FSE_OK; StorageType type = storage_get_type_by_path(app, path); - string_t real_path; - string_init_set(real_path, path); + FuriString* real_path; + real_path = furi_string_alloc_set(path); storage_path_change_to_real_storage(real_path, type); do { @@ -369,7 +369,7 @@ static FS_Error storage_process_common_remove(Storage* app, const char* path) { FS_CALL(storage, common.remove(storage, remove_vfs(path))); } while(false); - string_clear(real_path); + furi_string_free(real_path); return ret; } diff --git a/applications/services/storage/storage_test_app.c b/applications/services/storage/storage_test_app.c index 8bfa9826c6e..852953e9935 100644 --- a/applications/services/storage/storage_test_app.c +++ b/applications/services/storage/storage_test_app.c @@ -224,13 +224,12 @@ static void do_dir_test(Storage* api, const char* path) { } static void do_test_start(Storage* api, const char* path) { - string_t str_path; - string_init_printf(str_path, "%s/test-folder", path); + FuriString* str_path = furi_string_alloc_printf("%s/test-folder", path); FURI_LOG_I(TAG, "--------- START \"%s\" ---------", path); // mkdir - FS_Error result = storage_common_mkdir(api, string_get_cstr(str_path)); + FS_Error result = storage_common_mkdir(api, furi_string_get_cstr(str_path)); if(result == FSE_OK) { FURI_LOG_I(TAG, "mkdir ok"); @@ -240,7 +239,7 @@ static void do_test_start(Storage* api, const char* path) { // stat FileInfo fileinfo; - result = storage_common_stat(api, string_get_cstr(str_path), &fileinfo); + result = storage_common_stat(api, furi_string_get_cstr(str_path), &fileinfo); if(result == FSE_OK) { if(fileinfo.flags & FSF_DIRECTORY) { @@ -252,16 +251,14 @@ static void do_test_start(Storage* api, const char* path) { FURI_LOG_E(TAG, "stat #1, %s", storage_error_get_desc(result)); } - string_clear(str_path); + furi_string_free(str_path); } static void do_test_end(Storage* api, const char* path) { uint64_t total_space; uint64_t free_space; - string_t str_path_1; - string_t str_path_2; - string_init_printf(str_path_1, "%s/test-folder", path); - string_init_printf(str_path_2, "%s/test-folder2", path); + FuriString* str_path_1 = furi_string_alloc_printf("%s/test-folder", path); + FuriString* str_path_2 = furi_string_alloc_printf("%s/test-folder2", path); FURI_LOG_I(TAG, "--------- END \"%s\" ---------", path); @@ -277,7 +274,8 @@ static void do_test_end(Storage* api, const char* path) { } // rename #1 - result = storage_common_rename(api, string_get_cstr(str_path_1), string_get_cstr(str_path_2)); + result = storage_common_rename( + api, furi_string_get_cstr(str_path_1), furi_string_get_cstr(str_path_2)); if(result == FSE_OK) { FURI_LOG_I(TAG, "rename #1 ok"); } else { @@ -285,7 +283,7 @@ static void do_test_end(Storage* api, const char* path) { } // remove #1 - result = storage_common_remove(api, string_get_cstr(str_path_2)); + result = storage_common_remove(api, furi_string_get_cstr(str_path_2)); if(result == FSE_OK) { FURI_LOG_I(TAG, "remove #1 ok"); } else { @@ -293,10 +291,11 @@ static void do_test_end(Storage* api, const char* path) { } // rename #2 - string_printf(str_path_1, "%s/test.txt", path); - string_printf(str_path_2, "%s/test2.txt", path); + furi_string_printf(str_path_1, "%s/test.txt", path); + furi_string_printf(str_path_2, "%s/test2.txt", path); - result = storage_common_rename(api, string_get_cstr(str_path_1), string_get_cstr(str_path_2)); + result = storage_common_rename( + api, furi_string_get_cstr(str_path_1), furi_string_get_cstr(str_path_2)); if(result == FSE_OK) { FURI_LOG_I(TAG, "rename #2 ok"); } else { @@ -304,15 +303,15 @@ static void do_test_end(Storage* api, const char* path) { } // remove #2 - result = storage_common_remove(api, string_get_cstr(str_path_2)); + result = storage_common_remove(api, furi_string_get_cstr(str_path_2)); if(result == FSE_OK) { FURI_LOG_I(TAG, "remove #2 ok"); } else { FURI_LOG_E(TAG, "remove #2, %s", storage_error_get_desc(result)); } - string_clear(str_path_1); - string_clear(str_path_2); + furi_string_free(str_path_1); + furi_string_free(str_path_2); } int32_t storage_test_app(void* p) { diff --git a/applications/services/storage/storages/storage_int.c b/applications/services/storage/storages/storage_int.c index 7583973543f..ab0255014ee 100644 --- a/applications/services/storage/storages/storage_int.c +++ b/applications/services/storage/storages/storage_int.c @@ -338,11 +338,12 @@ static bool storage_int_file_open( storage_set_storage_file_data(file, handle, storage); if(!enough_free_space) { - string_t filename; - string_init(filename); + FuriString* filename; + filename = furi_string_alloc(); path_extract_basename(path, filename); - bool is_dot_file = (!string_empty_p(filename) && (string_get_char(filename, 0) == '.')); - string_clear(filename); + bool is_dot_file = + (!furi_string_empty(filename) && (furi_string_get_char(filename, 0) == '.')); + furi_string_free(filename); /* Restrict write & creation access to all non-dot files */ if(!is_dot_file && (flags & (LFS_O_CREAT | LFS_O_WRONLY))) { diff --git a/applications/settings/about/about.c b/applications/settings/about/about.c index 6b4489f0a3d..a42969b2bfe 100644 --- a/applications/settings/about/about.c +++ b/applications/settings/about/about.c @@ -3,7 +3,6 @@ #include #include #include -#include #include #include #include @@ -78,11 +77,11 @@ static DialogMessageButton icon2_screen(DialogsApp* dialogs, DialogMessage* mess static DialogMessageButton hw_version_screen(DialogsApp* dialogs, DialogMessage* message) { DialogMessageButton result; - string_t buffer; - string_init(buffer); + FuriString* buffer; + buffer = furi_string_alloc(); const char* my_name = furi_hal_version_get_name_ptr(); - string_cat_printf( + furi_string_cat_printf( buffer, "%d.F%dB%dC%d %s:%s %s\n", furi_hal_version_get_hw_version(), @@ -93,26 +92,26 @@ static DialogMessageButton hw_version_screen(DialogsApp* dialogs, DialogMessage* furi_hal_region_get_name(), my_name ? my_name : "Unknown"); - string_cat_printf(buffer, "Serial Number:\n"); + furi_string_cat_printf(buffer, "Serial Number:\n"); const uint8_t* uid = furi_hal_version_uid(); for(size_t i = 0; i < furi_hal_version_uid_size(); i++) { - string_cat_printf(buffer, "%02X", uid[i]); + furi_string_cat_printf(buffer, "%02X", uid[i]); } dialog_message_set_header(message, "HW Version Info:", 0, 0, AlignLeft, AlignTop); - dialog_message_set_text(message, string_get_cstr(buffer), 0, 13, AlignLeft, AlignTop); + dialog_message_set_text(message, furi_string_get_cstr(buffer), 0, 13, AlignLeft, AlignTop); result = dialog_message_show(dialogs, message); dialog_message_set_text(message, NULL, 0, 0, AlignLeft, AlignTop); dialog_message_set_header(message, NULL, 0, 0, AlignLeft, AlignTop); - string_clear(buffer); + furi_string_free(buffer); return result; } static DialogMessageButton fw_version_screen(DialogsApp* dialogs, DialogMessage* message) { DialogMessageButton result; - string_t buffer; - string_init(buffer); + FuriString* buffer; + buffer = furi_string_alloc(); const Version* ver = furi_hal_version_get_firmware_version(); const BleGlueC2Info* c2_ver = NULL; #ifdef SRV_BT @@ -120,9 +119,9 @@ static DialogMessageButton fw_version_screen(DialogsApp* dialogs, DialogMessage* #endif if(!ver) { - string_cat_printf(buffer, "No info\n"); + furi_string_cat_printf(buffer, "No info\n"); } else { - string_cat_printf( + furi_string_cat_printf( buffer, "%s [%s]\n%s%s [%s] %s\n[%d] %s", version_get_version(ver), @@ -136,11 +135,11 @@ static DialogMessageButton fw_version_screen(DialogsApp* dialogs, DialogMessage* } dialog_message_set_header(message, "FW Version Info:", 0, 0, AlignLeft, AlignTop); - dialog_message_set_text(message, string_get_cstr(buffer), 0, 13, AlignLeft, AlignTop); + dialog_message_set_text(message, furi_string_get_cstr(buffer), 0, 13, AlignLeft, AlignTop); result = dialog_message_show(dialogs, message); dialog_message_set_text(message, NULL, 0, 0, AlignLeft, AlignTop); dialog_message_set_header(message, NULL, 0, 0, AlignLeft, AlignTop); - string_clear(buffer); + furi_string_free(buffer); return result; } diff --git a/applications/settings/storage_settings/scenes/storage_settings_scene_benchmark.c b/applications/settings/storage_settings/scenes/storage_settings_scene_benchmark.c index ddeea4eba02..71a3df78b15 100644 --- a/applications/settings/storage_settings/scenes/storage_settings_scene_benchmark.c +++ b/applications/settings/storage_settings/scenes/storage_settings_scene_benchmark.c @@ -92,19 +92,19 @@ static void storage_settings_scene_benchmark(StorageSettings* app) { app->fs_api, bench_size[i], bench_data, &bench_w_speed[i])) break; - if(i > 0) string_cat_printf(app->text_string, "\n"); - string_cat_printf(app->text_string, "%ub : W %luK ", bench_size[i], bench_w_speed[i]); + if(i > 0) furi_string_cat_printf(app->text_string, "\n"); + furi_string_cat_printf(app->text_string, "%ub : W %luK ", bench_size[i], bench_w_speed[i]); dialog_ex_set_header(dialog_ex, NULL, 0, 0, AlignCenter, AlignCenter); dialog_ex_set_text( - dialog_ex, string_get_cstr(app->text_string), 0, 32, AlignLeft, AlignCenter); + dialog_ex, furi_string_get_cstr(app->text_string), 0, 32, AlignLeft, AlignCenter); if(!storage_settings_scene_bench_read( app->fs_api, bench_size[i], bench_data, &bench_r_speed[i])) break; - string_cat_printf(app->text_string, "R %luK", bench_r_speed[i]); + furi_string_cat_printf(app->text_string, "R %luK", bench_r_speed[i]); dialog_ex_set_text( - dialog_ex, string_get_cstr(app->text_string), 0, 32, AlignLeft, AlignCenter); + dialog_ex, furi_string_get_cstr(app->text_string), 0, 32, AlignLeft, AlignCenter); } free(bench_data); @@ -159,5 +159,5 @@ void storage_settings_scene_benchmark_on_exit(void* context) { dialog_ex_reset(dialog_ex); - string_reset(app->text_string); + furi_string_reset(app->text_string); } diff --git a/applications/settings/storage_settings/scenes/storage_settings_scene_factory_reset.c b/applications/settings/storage_settings/scenes/storage_settings_scene_factory_reset.c index a69479681ea..64d2b96b18b 100644 --- a/applications/settings/storage_settings/scenes/storage_settings_scene_factory_reset.c +++ b/applications/settings/storage_settings/scenes/storage_settings_scene_factory_reset.c @@ -49,13 +49,13 @@ bool storage_settings_scene_factory_reset_on_event(void* context, SceneManagerEv case DialogExResultRight: counter++; if(counter < STORAGE_SETTINGS_SCENE_FACTORY_RESET_CONFIRM_COUNT) { - string_printf( + furi_string_printf( app->text_string, "%ld presses left", STORAGE_SETTINGS_SCENE_FACTORY_RESET_CONFIRM_COUNT - counter); dialog_ex_set_text( app->dialog_ex, - string_get_cstr(app->text_string), + furi_string_get_cstr(app->text_string), 64, 32, AlignCenter, @@ -83,5 +83,5 @@ void storage_settings_scene_factory_reset_on_exit(void* context) { dialog_ex_reset(dialog_ex); - string_reset(app->text_string); + furi_string_reset(app->text_string); } diff --git a/applications/settings/storage_settings/scenes/storage_settings_scene_internal_info.c b/applications/settings/storage_settings/scenes/storage_settings_scene_internal_info.c index 76c7fd0eca9..d2d4ecd8a57 100644 --- a/applications/settings/storage_settings/scenes/storage_settings_scene_internal_info.c +++ b/applications/settings/storage_settings/scenes/storage_settings_scene_internal_info.c @@ -25,14 +25,14 @@ void storage_settings_scene_internal_info_on_enter(void* context) { dialog_ex_set_text( dialog_ex, storage_error_get_desc(error), 64, 32, AlignCenter, AlignCenter); } else { - string_printf( + furi_string_printf( app->text_string, "Label: %s\nType: LittleFS\n%lu KB total\n%lu KB free", furi_hal_version_get_name_ptr() ? furi_hal_version_get_name_ptr() : "Unknown", (uint32_t)(total_space / 1024), (uint32_t)(free_space / 1024)); dialog_ex_set_text( - dialog_ex, string_get_cstr(app->text_string), 4, 4, AlignLeft, AlignTop); + dialog_ex, furi_string_get_cstr(app->text_string), 4, 4, AlignLeft, AlignTop); } view_dispatcher_switch_to_view(app->view_dispatcher, StorageSettingsViewDialogEx); @@ -58,5 +58,5 @@ void storage_settings_scene_internal_info_on_exit(void* context) { dialog_ex_reset(dialog_ex); - string_reset(app->text_string); + furi_string_reset(app->text_string); } diff --git a/applications/settings/storage_settings/scenes/storage_settings_scene_sd_info.c b/applications/settings/storage_settings/scenes/storage_settings_scene_sd_info.c index cfb4f310dc5..f5d286c7f49 100644 --- a/applications/settings/storage_settings/scenes/storage_settings_scene_sd_info.c +++ b/applications/settings/storage_settings/scenes/storage_settings_scene_sd_info.c @@ -24,7 +24,7 @@ void storage_settings_scene_sd_info_on_enter(void* context) { dialog_ex, "Try to reinsert\nor format SD\ncard.", 3, 19, AlignLeft, AlignTop); dialog_ex_set_center_button_text(dialog_ex, "Ok"); } else { - string_printf( + furi_string_printf( app->text_string, "Label: %s\nType: %s\n%lu KB total\n%lu KB free", sd_info.label, @@ -32,7 +32,7 @@ void storage_settings_scene_sd_info_on_enter(void* context) { sd_info.kb_total, sd_info.kb_free); dialog_ex_set_text( - dialog_ex, string_get_cstr(app->text_string), 4, 4, AlignLeft, AlignTop); + dialog_ex, furi_string_get_cstr(app->text_string), 4, 4, AlignLeft, AlignTop); } view_dispatcher_switch_to_view(app->view_dispatcher, StorageSettingsViewDialogEx); @@ -70,5 +70,5 @@ void storage_settings_scene_sd_info_on_exit(void* context) { dialog_ex_reset(dialog_ex); - string_reset(app->text_string); + furi_string_reset(app->text_string); } diff --git a/applications/settings/storage_settings/storage_settings.c b/applications/settings/storage_settings/storage_settings.c index f580e636917..77a8f0f2269 100644 --- a/applications/settings/storage_settings/storage_settings.c +++ b/applications/settings/storage_settings/storage_settings.c @@ -21,7 +21,7 @@ static StorageSettings* storage_settings_alloc() { app->view_dispatcher = view_dispatcher_alloc(); app->scene_manager = scene_manager_alloc(&storage_settings_scene_handlers, app); - string_init(app->text_string); + app->text_string = furi_string_alloc(); view_dispatcher_enable_queue(app->view_dispatcher); view_dispatcher_set_event_callback_context(app->view_dispatcher, app); @@ -60,7 +60,7 @@ static void storage_settings_free(StorageSettings* app) { furi_record_close(RECORD_STORAGE); furi_record_close(RECORD_NOTIFICATION); - string_clear(app->text_string); + furi_string_free(app->text_string); free(app); } diff --git a/applications/settings/storage_settings/storage_settings.h b/applications/settings/storage_settings/storage_settings.h index f2d071c47c5..4cf185e0c90 100644 --- a/applications/settings/storage_settings/storage_settings.h +++ b/applications/settings/storage_settings/storage_settings.h @@ -34,7 +34,7 @@ typedef struct { DialogEx* dialog_ex; // text - string_t text_string; + FuriString* text_string; } StorageSettings; typedef enum { diff --git a/applications/system/storage_move_to_sd/storage_move_to_sd.c b/applications/system/storage_move_to_sd/storage_move_to_sd.c index e5b195d55d8..2027bd23759 100644 --- a/applications/system/storage_move_to_sd/storage_move_to_sd.c +++ b/applications/system/storage_move_to_sd/storage_move_to_sd.c @@ -2,7 +2,6 @@ #include #include #include "loader/loader.h" -#include "m-string.h" #include #include #include @@ -28,25 +27,26 @@ bool storage_move_to_sd_perform(void) { dir_walk_set_recursive(dir_walk, false); dir_walk_set_filter_cb(dir_walk, storage_move_to_sd_check_entry, NULL); - string_t path_src, path_dst; + FuriString *path_src, *path_dst; - string_init(path_dst); - string_init(path_src); + path_dst = furi_string_alloc(); + path_src = furi_string_alloc(); if(dir_walk_open(dir_walk, STORAGE_INT_PATH_PREFIX)) { while(dir_walk_read(dir_walk, path_src, NULL) == DirWalkOK) { - string_set(path_dst, path_src); - string_replace_at( + furi_string_set(path_dst, path_src); + furi_string_replace_at( path_dst, 0, strlen(STORAGE_INT_PATH_PREFIX), STORAGE_EXT_PATH_PREFIX); - storage_common_merge(storage, string_get_cstr(path_src), string_get_cstr(path_dst)); - storage_simply_remove_recursive(storage, string_get_cstr(path_src)); + storage_common_merge( + storage, furi_string_get_cstr(path_src), furi_string_get_cstr(path_dst)); + storage_simply_remove_recursive(storage, furi_string_get_cstr(path_src)); } } dir_walk_free(dir_walk); - string_clear(path_dst); - string_clear(path_src); + furi_string_free(path_dst); + furi_string_free(path_src); furi_record_close(RECORD_STORAGE); @@ -62,8 +62,8 @@ static bool storage_move_to_sd_check(void) { dir_walk_set_recursive(dir_walk, false); dir_walk_set_filter_cb(dir_walk, storage_move_to_sd_check_entry, NULL); - string_t name; - string_init(name); + FuriString* name; + name = furi_string_alloc(); if(dir_walk_open(dir_walk, STORAGE_INT_PATH_PREFIX)) { // if at least 1 entry is present, we should migrate @@ -71,7 +71,7 @@ static bool storage_move_to_sd_check(void) { } dir_walk_free(dir_walk); - string_clear(name); + furi_string_free(name); furi_record_close(RECORD_STORAGE); diff --git a/applications/system/updater/cli/updater_cli.c b/applications/system/updater/cli/updater_cli.c index ec209bd1df0..c3cdbb5f799 100644 --- a/applications/system/updater/cli/updater_cli.c +++ b/applications/system/updater/cli/updater_cli.c @@ -1,7 +1,6 @@ #include #include -#include #include #include #include @@ -12,16 +11,16 @@ #include #include -typedef void (*cmd_handler)(string_t args); +typedef void (*cmd_handler)(FuriString* args); typedef struct { const char* command; const cmd_handler handler; } CliSubcommand; -static void updater_cli_install(string_t manifest_path) { - printf("Verifying update package at '%s'\r\n", string_get_cstr(manifest_path)); +static void updater_cli_install(FuriString* manifest_path) { + printf("Verifying update package at '%s'\r\n", furi_string_get_cstr(manifest_path)); - UpdatePrepareResult result = update_operation_prepare(string_get_cstr(manifest_path)); + UpdatePrepareResult result = update_operation_prepare(furi_string_get_cstr(manifest_path)); if(result != UpdatePrepareResultOK) { printf( "Error: %s. Stopping update.\r\n", @@ -33,23 +32,23 @@ static void updater_cli_install(string_t manifest_path) { furi_hal_power_reset(); } -static void updater_cli_backup(string_t args) { - printf("Backup /int to '%s'\r\n", string_get_cstr(args)); +static void updater_cli_backup(FuriString* args) { + printf("Backup /int to '%s'\r\n", furi_string_get_cstr(args)); Storage* storage = furi_record_open(RECORD_STORAGE); - bool success = lfs_backup_create(storage, string_get_cstr(args)); + bool success = lfs_backup_create(storage, furi_string_get_cstr(args)); furi_record_close(RECORD_STORAGE); printf("Result: %s\r\n", success ? "OK" : "FAIL"); } -static void updater_cli_restore(string_t args) { - printf("Restore /int from '%s'\r\n", string_get_cstr(args)); +static void updater_cli_restore(FuriString* args) { + printf("Restore /int from '%s'\r\n", furi_string_get_cstr(args)); Storage* storage = furi_record_open(RECORD_STORAGE); - bool success = lfs_backup_unpack(storage, string_get_cstr(args)); + bool success = lfs_backup_unpack(storage, furi_string_get_cstr(args)); furi_record_close(RECORD_STORAGE); printf("Result: %s\r\n", success ? "OK" : "FAIL"); } -static void updater_cli_help(string_t args) { +static void updater_cli_help(FuriString* args) { UNUSED(args); printf("Commands:\r\n" "\tinstall /ext/path/to/update.fuf - verify & apply update package\r\n" @@ -64,25 +63,25 @@ static const CliSubcommand update_cli_subcommands[] = { {.command = "help", .handler = updater_cli_help}, }; -static void updater_cli_ep(Cli* cli, string_t args, void* context) { +static void updater_cli_ep(Cli* cli, FuriString* args, void* context) { UNUSED(cli); UNUSED(context); - string_t subcommand; - string_init(subcommand); - if(!args_read_string_and_trim(args, subcommand) || string_empty_p(args)) { + FuriString* subcommand; + subcommand = furi_string_alloc(); + if(!args_read_string_and_trim(args, subcommand) || furi_string_empty(args)) { updater_cli_help(args); - string_clear(subcommand); + furi_string_free(subcommand); return; } for(size_t idx = 0; idx < COUNT_OF(update_cli_subcommands); ++idx) { const CliSubcommand* subcmd_def = &update_cli_subcommands[idx]; - if(string_cmp_str(subcommand, subcmd_def->command) == 0) { - string_clear(subcommand); + if(furi_string_cmp_str(subcommand, subcmd_def->command) == 0) { + furi_string_free(subcommand); subcmd_def->handler(args); return; } } - string_clear(subcommand); + furi_string_free(subcommand); updater_cli_help(args); } diff --git a/applications/system/updater/scenes/updater_scene_loadcfg.c b/applications/system/updater/scenes/updater_scene_loadcfg.c index c7f48c78b8f..14f7b203af2 100644 --- a/applications/system/updater/scenes/updater_scene_loadcfg.c +++ b/applications/system/updater/scenes/updater_scene_loadcfg.c @@ -25,7 +25,7 @@ void updater_scene_loadcfg_on_enter(void* context) { malloc(sizeof(UpdaterManifestProcessingState)); pending_upd->manifest = update_manifest_alloc(); - if(update_manifest_init(pending_upd->manifest, string_get_cstr(updater->startup_arg))) { + if(update_manifest_init(pending_upd->manifest, furi_string_get_cstr(updater->startup_arg))) { widget_add_string_element( updater->widget, 64, 12, AlignCenter, AlignCenter, FontPrimary, "Update"); @@ -37,7 +37,7 @@ void updater_scene_loadcfg_on_enter(void* context) { 32, AlignCenter, AlignCenter, - string_get_cstr(pending_upd->manifest->version), + furi_string_get_cstr(pending_upd->manifest->version), true); widget_add_button_element( @@ -72,7 +72,7 @@ bool updater_scene_loadcfg_on_event(void* context, SceneManagerEvent event) { switch(event.event) { case UpdaterCustomEventStartUpdate: updater->preparation_result = - update_operation_prepare(string_get_cstr(updater->startup_arg)); + update_operation_prepare(furi_string_get_cstr(updater->startup_arg)); if(updater->preparation_result == UpdatePrepareResultOK) { furi_hal_power_reset(); } else { @@ -99,7 +99,7 @@ void updater_scene_loadcfg_on_exit(void* context) { if(updater->pending_update) { update_manifest_free(updater->pending_update->manifest); - string_clear(updater->pending_update->message); + furi_string_free(updater->pending_update->message); } widget_reset(updater->widget); diff --git a/applications/system/updater/updater.c b/applications/system/updater/updater.c index e9bedc72e1c..e749f3ce6e6 100644 --- a/applications/system/updater/updater.c +++ b/applications/system/updater/updater.c @@ -35,10 +35,10 @@ static void Updater* updater_alloc(const char* arg) { Updater* updater = malloc(sizeof(Updater)); if(arg && strlen(arg)) { - string_init_set_str(updater->startup_arg, arg); - string_replace_str(updater->startup_arg, ANY_PATH(""), EXT_PATH("")); + updater->startup_arg = furi_string_alloc_set(arg); + furi_string_replace(updater->startup_arg, ANY_PATH(""), EXT_PATH("")); } else { - string_init(updater->startup_arg); + updater->startup_arg = furi_string_alloc(); } updater->storage = furi_record_open(RECORD_STORAGE); @@ -94,7 +94,7 @@ Updater* updater_alloc(const char* arg) { void updater_free(Updater* updater) { furi_assert(updater); - string_clear(updater->startup_arg); + furi_string_free(updater->startup_arg); if(updater->update_task) { update_task_set_progress_cb(updater->update_task, NULL, NULL); update_task_free(updater->update_task); diff --git a/applications/system/updater/updater_i.h b/applications/system/updater/updater_i.h index 8a021a08df9..ae249f38f17 100644 --- a/applications/system/updater/updater_i.h +++ b/applications/system/updater/updater_i.h @@ -35,7 +35,7 @@ typedef enum { typedef struct UpdaterManifestProcessingState { UpdateManifest* manifest; - string_t message; + FuriString* message; bool ready_to_be_applied; } UpdaterManifestProcessingState; @@ -54,7 +54,7 @@ typedef struct { UpdateTask* update_task; Widget* widget; - string_t startup_arg; + FuriString* startup_arg; int32_t idle_ticks; } Updater; diff --git a/applications/system/updater/util/update_task.c b/applications/system/updater/util/update_task.c index b047731977e..de172dd4944 100644 --- a/applications/system/updater/util/update_task.c +++ b/applications/system/updater/util/update_task.c @@ -69,19 +69,19 @@ static const UpdateTaskStageGroupMap update_task_stage_progress[] = { static UpdateTaskStageGroup update_task_get_task_groups(UpdateTask* update_task) { UpdateTaskStageGroup ret = UpdateTaskStageGroupPreUpdate | UpdateTaskStageGroupPostUpdate; UpdateManifest* manifest = update_task->manifest; - if(!string_empty_p(manifest->radio_image)) { + if(!furi_string_empty(manifest->radio_image)) { ret |= UpdateTaskStageGroupRadio; } if(update_manifest_has_obdata(manifest)) { ret |= UpdateTaskStageGroupOptionBytes; } - if(!string_empty_p(manifest->firmware_dfu_image)) { + if(!furi_string_empty(manifest->firmware_dfu_image)) { ret |= UpdateTaskStageGroupFirmware; } - if(!string_empty_p(manifest->resource_bundle)) { + if(!furi_string_empty(manifest->resource_bundle)) { ret |= UpdateTaskStageGroupResources; } - if(!string_empty_p(manifest->splash_file)) { + if(!furi_string_empty(manifest->splash_file)) { ret |= UpdateTaskStageGroupSplashscreen; } return ret; @@ -109,14 +109,14 @@ void update_task_set_progress(UpdateTask* update_task, UpdateTaskStage stage, ui } /* Build error message with code "[stage_idx-stage_percent]" */ if(stage >= UpdateTaskStageError) { - string_printf( + furi_string_printf( update_task->state.status, "%s #[%d-%d]", update_task_stage_descr[stage], update_task->state.stage, update_task->state.stage_progress); } else { - string_set_str(update_task->state.status, update_task_stage_descr[stage]); + furi_string_set(update_task->state.status, update_task_stage_descr[stage]); } /* Store stage update */ update_task->state.stage = stage; @@ -149,7 +149,7 @@ void update_task_set_progress(UpdateTask* update_task, UpdateTaskStage stage, ui if(update_task->status_change_cb) { (update_task->status_change_cb)( - string_get_cstr(update_task->state.status), + furi_string_get_cstr(update_task->state.status), adapted_progress, update_stage_is_error(update_task->state.stage), update_task->status_change_cb_state); @@ -165,26 +165,26 @@ static void update_task_close_file(UpdateTask* update_task) { storage_file_close(update_task->file); } -static bool update_task_check_file_exists(UpdateTask* update_task, string_t filename) { +static bool update_task_check_file_exists(UpdateTask* update_task, FuriString* filename) { furi_assert(update_task); - string_t tmp_path; - string_init_set(tmp_path, update_task->update_path); - path_append(tmp_path, string_get_cstr(filename)); - bool exists = storage_file_exists(update_task->storage, string_get_cstr(tmp_path)); - string_clear(tmp_path); + FuriString* tmp_path; + tmp_path = furi_string_alloc_set(update_task->update_path); + path_append(tmp_path, furi_string_get_cstr(filename)); + bool exists = storage_file_exists(update_task->storage, furi_string_get_cstr(tmp_path)); + furi_string_free(tmp_path); return exists; } -bool update_task_open_file(UpdateTask* update_task, string_t filename) { +bool update_task_open_file(UpdateTask* update_task, FuriString* filename) { furi_assert(update_task); update_task_close_file(update_task); - string_t tmp_path; - string_init_set(tmp_path, update_task->update_path); - path_append(tmp_path, string_get_cstr(filename)); + FuriString* tmp_path; + tmp_path = furi_string_alloc_set(update_task->update_path); + path_append(tmp_path, furi_string_get_cstr(filename)); bool open_success = storage_file_open( - update_task->file, string_get_cstr(tmp_path), FSAM_READ, FSOM_OPEN_EXISTING); - string_clear(tmp_path); + update_task->file, furi_string_get_cstr(tmp_path), FSAM_READ, FSOM_OPEN_EXISTING); + furi_string_free(tmp_path); return open_success; } @@ -207,14 +207,14 @@ UpdateTask* update_task_alloc() { update_task->state.stage = UpdateTaskStageProgress; update_task->state.stage_progress = 0; update_task->state.overall_progress = 0; - string_init(update_task->state.status); + update_task->state.status = furi_string_alloc(); update_task->manifest = update_manifest_alloc(); update_task->storage = furi_record_open(RECORD_STORAGE); update_task->file = storage_file_alloc(update_task->storage); update_task->status_change_cb = NULL; update_task->boot_mode = furi_hal_rtc_get_boot_mode(); - string_init(update_task->update_path); + update_task->update_path = furi_string_alloc(); FuriThread* thread = update_task->thread = furi_thread_alloc(); @@ -246,7 +246,7 @@ void update_task_free(UpdateTask* update_task) { update_manifest_free(update_task->manifest); furi_record_close(RECORD_STORAGE); - string_clear(update_task->update_path); + furi_string_free(update_task->update_path); free(update_task); } @@ -261,8 +261,8 @@ bool update_task_parse_manifest(UpdateTask* update_task) { update_task_set_progress(update_task, UpdateTaskStageReadManifest, 0); bool result = false; - string_t manifest_path; - string_init(manifest_path); + FuriString* manifest_path; + manifest_path = furi_string_alloc(); do { update_task_set_progress(update_task, UpdateTaskStageProgress, 13); @@ -276,11 +276,11 @@ bool update_task_parse_manifest(UpdateTask* update_task) { break; } - path_extract_dirname(string_get_cstr(manifest_path), update_task->update_path); + path_extract_dirname(furi_string_get_cstr(manifest_path), update_task->update_path); update_task_set_progress(update_task, UpdateTaskStageProgress, 30); UpdateManifest* manifest = update_task->manifest; - if(!update_manifest_init(manifest, string_get_cstr(manifest_path))) { + if(!update_manifest_init(manifest, furi_string_get_cstr(manifest_path))) { break; } @@ -320,7 +320,7 @@ bool update_task_parse_manifest(UpdateTask* update_task) { result = true; } while(false); - string_clear(manifest_path); + furi_string_free(manifest_path); return result; } diff --git a/applications/system/updater/util/update_task.h b/applications/system/updater/util/update_task.h index ad8c50857d1..b3ac3f2f825 100644 --- a/applications/system/updater/util/update_task.h +++ b/applications/system/updater/util/update_task.h @@ -8,7 +8,6 @@ extern "C" { #include #include -#include #define UPDATE_DELAY_OPERATION_OK 10 #define UPDATE_DELAY_OPERATION_ERROR INT_MAX @@ -59,7 +58,7 @@ typedef enum { typedef struct { UpdateTaskStage stage; uint8_t overall_progress, stage_progress; - string_t status; + FuriString* status; UpdateTaskStageGroup groups; uint32_t total_progress_points; uint32_t completed_stages_points; diff --git a/applications/system/updater/util/update_task_i.h b/applications/system/updater/util/update_task_i.h index 95c63f64432..0dbeca5f491 100644 --- a/applications/system/updater/util/update_task_i.h +++ b/applications/system/updater/util/update_task_i.h @@ -8,7 +8,7 @@ typedef struct UpdateTask { UpdateTaskState state; - string_t update_path; + FuriString* update_path; UpdateManifest* manifest; FuriThread* thread; Storage* storage; @@ -20,7 +20,7 @@ typedef struct UpdateTask { void update_task_set_progress(UpdateTask* update_task, UpdateTaskStage stage, uint8_t progress); bool update_task_parse_manifest(UpdateTask* update_task); -bool update_task_open_file(UpdateTask* update_task, string_t filename); +bool update_task_open_file(UpdateTask* update_task, FuriString* filename); int32_t update_task_worker_flash_writer(void* context); int32_t update_task_worker_backup_restore(void* context); diff --git a/applications/system/updater/util/update_task_worker_backup.c b/applications/system/updater/util/update_task_worker_backup.c index 046be372dc5..ce62da2a1d9 100644 --- a/applications/system/updater/util/update_task_worker_backup.c +++ b/applications/system/updater/util/update_task_worker_backup.c @@ -22,19 +22,22 @@ static bool update_task_pre_update(UpdateTask* update_task) { bool success = false; - string_t backup_file_path; - string_init(backup_file_path); + FuriString* backup_file_path; + backup_file_path = furi_string_alloc(); path_concat( - string_get_cstr(update_task->update_path), LFS_BACKUP_DEFAULT_FILENAME, backup_file_path); + furi_string_get_cstr(update_task->update_path), + LFS_BACKUP_DEFAULT_FILENAME, + backup_file_path); update_task_set_progress(update_task, UpdateTaskStageLfsBackup, 0); /* to avoid bootloops */ furi_hal_rtc_set_boot_mode(FuriHalRtcBootModeNormal); - if((success = lfs_backup_create(update_task->storage, string_get_cstr(backup_file_path)))) { + if((success = + lfs_backup_create(update_task->storage, furi_string_get_cstr(backup_file_path)))) { furi_hal_rtc_set_boot_mode(FuriHalRtcBootModeUpdate); } - string_clear(backup_file_path); + furi_string_free(backup_file_path); return success; } @@ -79,12 +82,12 @@ static void /* For this stage, first 30% of progress = cleanup */ (n_processed_files++ * 30) / (n_approx_file_entries + 1)); - string_t file_path; - string_init(file_path); - path_concat(STORAGE_EXT_PATH_PREFIX, string_get_cstr(entry_ptr->name), file_path); - FURI_LOG_D(TAG, "Removing %s", string_get_cstr(file_path)); - storage_simply_remove(update_task->storage, string_get_cstr(file_path)); - string_clear(file_path); + FuriString* file_path = furi_string_alloc(); + path_concat( + STORAGE_EXT_PATH_PREFIX, furi_string_get_cstr(entry_ptr->name), file_path); + FURI_LOG_D(TAG, "Removing %s", furi_string_get_cstr(file_path)); + storage_simply_remove(update_task->storage, furi_string_get_cstr(file_path)); + furi_string_free(file_path); } } } while(false); @@ -94,17 +97,19 @@ static void static bool update_task_post_update(UpdateTask* update_task) { bool success = false; - string_t file_path; - string_init(file_path); + FuriString* file_path; + file_path = furi_string_alloc(); TarArchive* archive = tar_archive_alloc(update_task->storage); do { path_concat( - string_get_cstr(update_task->update_path), LFS_BACKUP_DEFAULT_FILENAME, file_path); + furi_string_get_cstr(update_task->update_path), + LFS_BACKUP_DEFAULT_FILENAME, + file_path); update_task_set_progress(update_task, UpdateTaskStageLfsRestore, 0); - CHECK_RESULT(lfs_backup_unpack(update_task->storage, string_get_cstr(file_path))); + CHECK_RESULT(lfs_backup_unpack(update_task->storage, furi_string_get_cstr(file_path))); if(update_task->state.groups & UpdateTaskStageGroupResources) { TarUnpackProgress progress = { @@ -115,13 +120,13 @@ static bool update_task_post_update(UpdateTask* update_task) { update_task_set_progress(update_task, UpdateTaskStageResourcesUpdate, 0); path_concat( - string_get_cstr(update_task->update_path), - string_get_cstr(update_task->manifest->resource_bundle), + furi_string_get_cstr(update_task->update_path), + furi_string_get_cstr(update_task->manifest->resource_bundle), file_path); tar_archive_set_file_callback(archive, update_task_resource_unpack_cb, &progress); CHECK_RESULT( - tar_archive_open(archive, string_get_cstr(file_path), TAR_OPEN_MODE_READ)); + tar_archive_open(archive, furi_string_get_cstr(file_path), TAR_OPEN_MODE_READ)); progress.total_files = tar_archive_get_entries_count(archive); if(progress.total_files > 0) { @@ -133,23 +138,23 @@ static bool update_task_post_update(UpdateTask* update_task) { if(update_task->state.groups & UpdateTaskStageGroupSplashscreen) { update_task_set_progress(update_task, UpdateTaskStageSplashscreenInstall, 0); - string_t tmp_path; - string_init_set(tmp_path, update_task->update_path); - path_append(tmp_path, string_get_cstr(update_task->manifest->splash_file)); + FuriString* tmp_path; + tmp_path = furi_string_alloc_set(update_task->update_path); + path_append(tmp_path, furi_string_get_cstr(update_task->manifest->splash_file)); if(storage_common_copy( update_task->storage, - string_get_cstr(tmp_path), + furi_string_get_cstr(tmp_path), INT_PATH(SLIDESHOW_FILE_NAME)) != FSE_OK) { // actually, not critical } - string_clear(tmp_path); + furi_string_free(tmp_path); update_task_set_progress(update_task, UpdateTaskStageSplashscreenInstall, 100); } success = true; } while(false); tar_archive_free(archive); - string_clear(file_path); + furi_string_free(file_path); return success; } diff --git a/applications/system/updater/views/updater_main.c b/applications/system/updater/views/updater_main.c index 72541b9abd6..8d6694d896f 100644 --- a/applications/system/updater/views/updater_main.c +++ b/applications/system/updater/views/updater_main.c @@ -18,7 +18,7 @@ struct UpdaterMainView { static const uint8_t PROGRESS_RENDER_STEP = 1; /* percent, to limit rendering rate */ typedef struct { - string_t status; + FuriString* status; uint8_t progress, rendered_progress; bool failed; } UpdaterProgressModel; @@ -32,8 +32,8 @@ void updater_main_model_set_state( main_view->view, (UpdaterProgressModel * model) { model->failed = failed; model->progress = progress; - if(string_cmp_str(model->status, message)) { - string_set(model->status, message); + if(furi_string_cmp_str(model->status, message)) { + furi_string_set(model->status, message); model->rendered_progress = progress; return true; } @@ -80,7 +80,7 @@ static void updater_main_draw_callback(Canvas* canvas, void* _model) { canvas_draw_str_aligned(canvas, 42, 16, AlignLeft, AlignTop, "Update Failed!"); canvas_set_font(canvas, FontSecondary); canvas_draw_str_aligned( - canvas, 42, 32, AlignLeft, AlignTop, string_get_cstr(model->status)); + canvas, 42, 32, AlignLeft, AlignTop, furi_string_get_cstr(model->status)); canvas_draw_icon(canvas, 7, 16, &I_Warning_30x23); canvas_draw_str_aligned( @@ -91,7 +91,7 @@ static void updater_main_draw_callback(Canvas* canvas, void* _model) { canvas_draw_str_aligned(canvas, 55, 14, AlignLeft, AlignTop, "UPDATING"); canvas_set_font(canvas, FontSecondary); canvas_draw_str_aligned( - canvas, 64, 51, AlignCenter, AlignTop, string_get_cstr(model->status)); + canvas, 64, 51, AlignCenter, AlignTop, furi_string_get_cstr(model->status)); canvas_draw_icon(canvas, 4, 5, &I_Updating_32x40); elements_progress_bar(canvas, 42, 29, 80, (float)model->progress / 100); } @@ -105,7 +105,7 @@ UpdaterMainView* updater_main_alloc() { with_view_model( main_view->view, (UpdaterProgressModel * model) { - string_init_set(model->status, "Waiting for SD card"); + model->status = furi_string_alloc_set("Waiting for SD card"); return true; }); @@ -120,7 +120,7 @@ void updater_main_free(UpdaterMainView* main_view) { furi_assert(main_view); with_view_model( main_view->view, (UpdaterProgressModel * model) { - string_clear(model->status); + furi_string_free(model->status); return false; }); view_free(main_view->view); diff --git a/firmware/targets/f7/Src/update.c b/firmware/targets/f7/Src/update.c index 36204829e29..722a7b61605 100644 --- a/firmware/targets/f7/Src/update.c +++ b/firmware/targets/f7/Src/update.c @@ -54,20 +54,21 @@ static bool flipper_update_init() { return flipper_update_mount_sd(); } -static bool flipper_update_load_stage(const string_t work_dir, UpdateManifest* manifest) { +static bool flipper_update_load_stage(const FuriString* work_dir, UpdateManifest* manifest) { FIL file; FILINFO stat; - string_t loader_img_path; - string_init_set(loader_img_path, work_dir); - path_append(loader_img_path, string_get_cstr(manifest->staged_loader_file)); + FuriString* loader_img_path; + loader_img_path = furi_string_alloc_set(work_dir); + path_append(loader_img_path, furi_string_get_cstr(manifest->staged_loader_file)); - if((f_stat(string_get_cstr(loader_img_path), &stat) != FR_OK) || - (f_open(&file, string_get_cstr(loader_img_path), FA_OPEN_EXISTING | FA_READ) != FR_OK)) { - string_clear(loader_img_path); + if((f_stat(furi_string_get_cstr(loader_img_path), &stat) != FR_OK) || + (f_open(&file, furi_string_get_cstr(loader_img_path), FA_OPEN_EXISTING | FA_READ) != + FR_OK)) { + furi_string_free(loader_img_path); return false; } - string_clear(loader_img_path); + furi_string_free(loader_img_path); void* img = malloc(stat.fsize); uint32_t bytes_read = 0; @@ -110,13 +111,13 @@ static bool flipper_update_load_stage(const string_t work_dir, UpdateManifest* m return false; } -static bool flipper_update_get_manifest_path(string_t out_path) { +static bool flipper_update_get_manifest_path(FuriString* out_path) { FIL file; FILINFO stat; uint16_t size_read = 0; char manifest_name_buf[UPDATE_OPERATION_MAX_MANIFEST_PATH_LEN] = {0}; - string_reset(out_path); + furi_string_reset(out_path); CHECK_FRESULT(f_stat(UPDATE_POINTER_FILE_PATH, &stat)); CHECK_FRESULT(f_open(&file, UPDATE_POINTER_FILE_PATH, FA_OPEN_EXISTING | FA_READ)); do { @@ -128,19 +129,19 @@ static bool flipper_update_get_manifest_path(string_t out_path) { if((size_read == 0) || (size_read == UPDATE_OPERATION_MAX_MANIFEST_PATH_LEN)) { break; } - string_set_str(out_path, manifest_name_buf); - string_right(out_path, strlen(STORAGE_EXT_PATH_PREFIX)); + furi_string_set(out_path, manifest_name_buf); + furi_string_right(out_path, strlen(STORAGE_EXT_PATH_PREFIX)); } while(0); f_close(&file); - return !string_empty_p(out_path); + return !furi_string_empty(out_path); } -static UpdateManifest* flipper_update_process_manifest(const string_t manifest_path) { +static UpdateManifest* flipper_update_process_manifest(const FuriString* manifest_path) { FIL file; FILINFO stat; - CHECK_FRESULT(f_stat(string_get_cstr(manifest_path), &stat)); - CHECK_FRESULT(f_open(&file, string_get_cstr(manifest_path), FA_OPEN_EXISTING | FA_READ)); + CHECK_FRESULT(f_stat(furi_string_get_cstr(manifest_path), &stat)); + CHECK_FRESULT(f_open(&file, furi_string_get_cstr(manifest_path), FA_OPEN_EXISTING | FA_READ)); uint8_t* manifest_data = malloc(stat.fsize); uint32_t bytes_read = 0; @@ -177,9 +178,9 @@ void flipper_boot_update_exec() { return; } - string_t work_dir, manifest_path; - string_init(work_dir); - string_init(manifest_path); + FuriString* work_dir = furi_string_alloc(); + FuriString* manifest_path = furi_string_alloc(); + do { if(!flipper_update_get_manifest_path(manifest_path)) { break; @@ -190,12 +191,12 @@ void flipper_boot_update_exec() { break; } - path_extract_dirname(string_get_cstr(manifest_path), work_dir); + path_extract_dirname(furi_string_get_cstr(manifest_path), work_dir); if(!flipper_update_load_stage(work_dir, manifest)) { update_manifest_free(manifest); } } while(false); - string_clear(manifest_path); - string_clear(work_dir); + furi_string_free(manifest_path); + furi_string_free(work_dir); free(pfs); } diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 2cbdae77c13..a140ff0bef5 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,1.13,, +Version,+,2.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -440,12 +440,12 @@ Function,-,arc4random,__uint32_t, Function,-,arc4random_buf,void,"void*, size_t" Function,-,arc4random_uniform,__uint32_t,__uint32_t Function,+,args_char_to_hex,_Bool,"char, char, uint8_t*" -Function,+,args_get_first_word_length,size_t,string_t -Function,+,args_length,size_t,string_t -Function,+,args_read_hex_bytes,_Bool,"string_t, uint8_t*, size_t" -Function,+,args_read_int_and_trim,_Bool,"string_t, int*" -Function,+,args_read_probably_quoted_string_and_trim,_Bool,"string_t, string_t" -Function,+,args_read_string_and_trim,_Bool,"string_t, string_t" +Function,+,args_get_first_word_length,size_t,FuriString* +Function,+,args_length,size_t,FuriString* +Function,+,args_read_hex_bytes,_Bool,"FuriString*, uint8_t*, size_t" +Function,+,args_read_int_and_trim,_Bool,"FuriString*, int*" +Function,+,args_read_probably_quoted_string_and_trim,_Bool,"FuriString*, FuriString*" +Function,+,args_read_string_and_trim,_Bool,"FuriString*, FuriString*" Function,-,asin,double,double Function,-,asinf,float,float Function,-,asinh,double,double @@ -642,7 +642,7 @@ Function,+,dialog_ex_set_result_callback,void,"DialogEx*, DialogExResultCallback Function,+,dialog_ex_set_right_button_text,void,"DialogEx*, const char*" Function,+,dialog_ex_set_text,void,"DialogEx*, const char*, uint8_t, uint8_t, Align, Align" Function,+,dialog_file_browser_set_basic_options,void,"DialogsFileBrowserOptions*, const char*, const Icon*" -Function,+,dialog_file_browser_show,_Bool,"DialogsApp*, string_ptr, string_ptr, const DialogsFileBrowserOptions*" +Function,+,dialog_file_browser_show,_Bool,"DialogsApp*, FuriString*, FuriString*, const DialogsFileBrowserOptions*" Function,+,dialog_message_alloc,DialogMessage*, Function,+,dialog_message_free,void,DialogMessage* Function,+,dialog_message_set_buttons,void,"DialogMessage*, const char*, const char*, const char*" @@ -665,7 +665,7 @@ Function,+,dir_walk_close,void,DirWalk* Function,+,dir_walk_free,void,DirWalk* Function,+,dir_walk_get_error,FS_Error,DirWalk* Function,+,dir_walk_open,_Bool,"DirWalk*, const char*" -Function,+,dir_walk_read,DirWalkResult,"DirWalk*, string_t, FileInfo*" +Function,+,dir_walk_read,DirWalkResult,"DirWalk*, FuriString*, FileInfo*" Function,+,dir_walk_set_filter_cb,void,"DirWalk*, DirWalkFilterCb, void*" Function,+,dir_walk_set_recursive,void,"DirWalk*, _Bool" Function,-,div,div_t,"int, int" @@ -698,7 +698,7 @@ Function,+,elements_scrollbar,void,"Canvas*, uint16_t, uint16_t" Function,+,elements_scrollbar_pos,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint16_t, uint16_t" Function,+,elements_slightly_rounded_box,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t" Function,+,elements_slightly_rounded_frame,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t" -Function,+,elements_string_fit_width,void,"Canvas*, string_t, uint8_t" +Function,+,elements_string_fit_width,void,"Canvas*, FuriString*, uint8_t" Function,+,elements_text_box,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, Align, Align, const char*, _Bool" Function,+,empty_screen_alloc,EmptyScreen*, Function,+,empty_screen_free,void,EmptyScreen* @@ -746,22 +746,22 @@ Function,-,fgetc_unlocked,int,FILE* Function,-,fgetpos,int,"FILE*, fpos_t*" Function,-,fgets,char*,"char*, int, FILE*" Function,-,fgets_unlocked,char*,"char*, int, FILE*" -Function,+,file_browser_alloc,FileBrowser*,string_ptr +Function,+,file_browser_alloc,FileBrowser*,FuriString* Function,+,file_browser_configure,void,"FileBrowser*, const char*, _Bool, const Icon*, _Bool" Function,+,file_browser_free,void,FileBrowser* Function,+,file_browser_get_view,View*,FileBrowser* Function,+,file_browser_set_callback,void,"FileBrowser*, FileBrowserCallback, void*" Function,+,file_browser_set_item_callback,void,"FileBrowser*, FileBrowserLoadItemCallback, void*" -Function,+,file_browser_start,void,"FileBrowser*, string_t" +Function,+,file_browser_start,void,"FileBrowser*, FuriString*" Function,+,file_browser_stop,void,FileBrowser* -Function,+,file_browser_worker_alloc,BrowserWorker*,"string_t, const char*, _Bool" -Function,+,file_browser_worker_folder_enter,void,"BrowserWorker*, string_t, int32_t" +Function,+,file_browser_worker_alloc,BrowserWorker*,"FuriString*, const char*, _Bool" +Function,+,file_browser_worker_folder_enter,void,"BrowserWorker*, FuriString*, int32_t" Function,+,file_browser_worker_folder_exit,void,BrowserWorker* Function,+,file_browser_worker_folder_refresh,void,"BrowserWorker*, int32_t" Function,+,file_browser_worker_free,void,BrowserWorker* Function,+,file_browser_worker_load,void,"BrowserWorker*, uint32_t, uint32_t" Function,+,file_browser_worker_set_callback_context,void,"BrowserWorker*, void*" -Function,+,file_browser_worker_set_config,void,"BrowserWorker*, string_t, const char*, _Bool" +Function,+,file_browser_worker_set_config,void,"BrowserWorker*, FuriString*, const char*, _Bool" Function,+,file_browser_worker_set_folder_callback,void,"BrowserWorker*, BrowserWorkerFolderOpenCallback" Function,+,file_browser_worker_set_item_callback,void,"BrowserWorker*, BrowserWorkerListItemCallback" Function,+,file_browser_worker_set_list_callback,void,"BrowserWorker*, BrowserWorkerListLoadCallback" @@ -806,17 +806,17 @@ Function,+,flipper_format_insert_or_update_bool,_Bool,"FlipperFormat*, const cha Function,+,flipper_format_insert_or_update_float,_Bool,"FlipperFormat*, const char*, const float*, const uint16_t" Function,+,flipper_format_insert_or_update_hex,_Bool,"FlipperFormat*, const char*, const uint8_t*, const uint16_t" Function,+,flipper_format_insert_or_update_int32,_Bool,"FlipperFormat*, const char*, const int32_t*, const uint16_t" -Function,+,flipper_format_insert_or_update_string,_Bool,"FlipperFormat*, const char*, string_t" +Function,+,flipper_format_insert_or_update_string,_Bool,"FlipperFormat*, const char*, FuriString*" Function,+,flipper_format_insert_or_update_string_cstr,_Bool,"FlipperFormat*, const char*, const char*" Function,+,flipper_format_insert_or_update_uint32,_Bool,"FlipperFormat*, const char*, const uint32_t*, const uint16_t" Function,+,flipper_format_key_exist,_Bool,"FlipperFormat*, const char*" Function,+,flipper_format_read_bool,_Bool,"FlipperFormat*, const char*, _Bool*, const uint16_t" Function,+,flipper_format_read_float,_Bool,"FlipperFormat*, const char*, float*, const uint16_t" -Function,+,flipper_format_read_header,_Bool,"FlipperFormat*, string_t, uint32_t*" +Function,+,flipper_format_read_header,_Bool,"FlipperFormat*, FuriString*, uint32_t*" Function,+,flipper_format_read_hex,_Bool,"FlipperFormat*, const char*, uint8_t*, const uint16_t" Function,+,flipper_format_read_hex_uint64,_Bool,"FlipperFormat*, const char*, uint64_t*, const uint16_t" Function,+,flipper_format_read_int32,_Bool,"FlipperFormat*, const char*, int32_t*, const uint16_t" -Function,+,flipper_format_read_string,_Bool,"FlipperFormat*, const char*, string_t" +Function,+,flipper_format_read_string,_Bool,"FlipperFormat*, const char*, FuriString*" Function,+,flipper_format_read_uint32,_Bool,"FlipperFormat*, const char*, uint32_t*, const uint16_t" Function,+,flipper_format_rewind,_Bool,FlipperFormat* Function,+,flipper_format_seek_to_end,_Bool,FlipperFormat* @@ -826,19 +826,19 @@ Function,+,flipper_format_update_bool,_Bool,"FlipperFormat*, const char*, const Function,+,flipper_format_update_float,_Bool,"FlipperFormat*, const char*, const float*, const uint16_t" Function,+,flipper_format_update_hex,_Bool,"FlipperFormat*, const char*, const uint8_t*, const uint16_t" Function,+,flipper_format_update_int32,_Bool,"FlipperFormat*, const char*, const int32_t*, const uint16_t" -Function,+,flipper_format_update_string,_Bool,"FlipperFormat*, const char*, string_t" +Function,+,flipper_format_update_string,_Bool,"FlipperFormat*, const char*, FuriString*" Function,+,flipper_format_update_string_cstr,_Bool,"FlipperFormat*, const char*, const char*" Function,+,flipper_format_update_uint32,_Bool,"FlipperFormat*, const char*, const uint32_t*, const uint16_t" Function,+,flipper_format_write_bool,_Bool,"FlipperFormat*, const char*, const _Bool*, const uint16_t" -Function,+,flipper_format_write_comment,_Bool,"FlipperFormat*, string_t" +Function,+,flipper_format_write_comment,_Bool,"FlipperFormat*, FuriString*" Function,+,flipper_format_write_comment_cstr,_Bool,"FlipperFormat*, const char*" Function,+,flipper_format_write_float,_Bool,"FlipperFormat*, const char*, const float*, const uint16_t" -Function,+,flipper_format_write_header,_Bool,"FlipperFormat*, string_t, const uint32_t" +Function,+,flipper_format_write_header,_Bool,"FlipperFormat*, FuriString*, const uint32_t" Function,+,flipper_format_write_header_cstr,_Bool,"FlipperFormat*, const char*, const uint32_t" Function,+,flipper_format_write_hex,_Bool,"FlipperFormat*, const char*, const uint8_t*, const uint16_t" Function,+,flipper_format_write_hex_uint64,_Bool,"FlipperFormat*, const char*, const uint64_t*, const uint16_t" Function,+,flipper_format_write_int32,_Bool,"FlipperFormat*, const char*, const int32_t*, const uint16_t" -Function,+,flipper_format_write_string,_Bool,"FlipperFormat*, const char*, string_t" +Function,+,flipper_format_write_string,_Bool,"FlipperFormat*, const char*, FuriString*" Function,+,flipper_format_write_string_cstr,_Bool,"FlipperFormat*, const char*, const char*" Function,+,flipper_format_write_uint32,_Bool,"FlipperFormat*, const char*, const uint32_t*, const uint16_t" Function,-,flockfile,void,FILE* @@ -899,7 +899,7 @@ Function,+,furi_event_flag_wait,uint32_t,"FuriEventFlag*, uint32_t, uint32_t, ui Function,+,furi_get_tick,uint32_t, Function,+,furi_hal_bt_change_app,_Bool,"FuriHalBtProfile, GapEventCallback, void*" Function,+,furi_hal_bt_clear_white_list,_Bool, -Function,+,furi_hal_bt_dump_state,void,string_t +Function,+,furi_hal_bt_dump_state,void,FuriString* Function,+,furi_hal_bt_ensure_c2_mode,_Bool,BleGlueC2Mode Function,+,furi_hal_bt_get_key_storage_buff,void,"uint8_t**, uint16_t*" Function,+,furi_hal_bt_get_radio_stack,FuriHalBtStack, @@ -1342,6 +1342,60 @@ Function,+,furi_semaphore_alloc,FuriSemaphore*,"uint32_t, uint32_t" Function,+,furi_semaphore_free,void,FuriSemaphore* Function,+,furi_semaphore_get_count,uint32_t,FuriSemaphore* Function,+,furi_semaphore_release,FuriStatus,FuriSemaphore* +Function,+,furi_string_alloc,FuriString*, +Function,+,furi_string_alloc_move,FuriString*,FuriString* +Function,+,furi_string_alloc_printf,FuriString*,"const char[], ..." +Function,+,furi_string_alloc_set,FuriString*,const FuriString* +Function,+,furi_string_alloc_set_str,FuriString*,const char[] +Function,+,furi_string_alloc_vprintf,FuriString*,"const char[], va_list" +Function,+,furi_string_cat,void,"FuriString*, const FuriString*" +Function,+,furi_string_cat_printf,int,"FuriString*, const char[], ..." +Function,+,furi_string_cat_str,void,"FuriString*, const char[]" +Function,+,furi_string_cat_vprintf,int,"FuriString*, const char[], va_list" +Function,+,furi_string_cmp,int,"const FuriString*, const FuriString*" +Function,+,furi_string_cmp_str,int,"const FuriString*, const char[]" +Function,+,furi_string_cmpi,int,"const FuriString*, const FuriString*" +Function,+,furi_string_cmpi_str,int,"const FuriString*, const char[]" +Function,+,furi_string_empty,_Bool,const FuriString* +Function,+,furi_string_end_with,_Bool,"const FuriString*, const FuriString*" +Function,+,furi_string_end_with_str,_Bool,"const FuriString*, const char[]" +Function,+,furi_string_equal,_Bool,"const FuriString*, const FuriString*" +Function,+,furi_string_equal_str,_Bool,"const FuriString*, const char[]" +Function,+,furi_string_free,void,FuriString* +Function,+,furi_string_get_char,char,"const FuriString*, size_t" +Function,+,furi_string_get_cstr,const char*,const FuriString* +Function,+,furi_string_hash,size_t,const FuriString* +Function,+,furi_string_left,void,"FuriString*, size_t" +Function,+,furi_string_mid,void,"FuriString*, size_t, size_t" +Function,+,furi_string_move,void,"FuriString*, FuriString*" +Function,+,furi_string_printf,int,"FuriString*, const char[], ..." +Function,+,furi_string_push_back,void,"FuriString*, char" +Function,+,furi_string_replace,size_t,"FuriString*, FuriString*, FuriString*, size_t" +Function,+,furi_string_replace_all,void,"FuriString*, const FuriString*, const FuriString*" +Function,+,furi_string_replace_all_str,void,"FuriString*, const char[], const char[]" +Function,+,furi_string_replace_at,void,"FuriString*, size_t, size_t, const char[]" +Function,+,furi_string_replace_str,size_t,"FuriString*, const char[], const char[], size_t" +Function,+,furi_string_reserve,void,"FuriString*, size_t" +Function,+,furi_string_reset,void,FuriString* +Function,+,furi_string_right,void,"FuriString*, size_t" +Function,+,furi_string_search,size_t,"const FuriString*, const FuriString*, size_t" +Function,+,furi_string_search_char,size_t,"const FuriString*, char, size_t" +Function,+,furi_string_search_rchar,size_t,"const FuriString*, char, size_t" +Function,+,furi_string_search_str,size_t,"const FuriString*, const char[], size_t" +Function,+,furi_string_set,void,"FuriString*, FuriString*" +Function,+,furi_string_set_char,void,"FuriString*, size_t, const char" +Function,+,furi_string_set_n,void,"FuriString*, const FuriString*, size_t, size_t" +Function,+,furi_string_set_str,void,"FuriString*, const char[]" +Function,+,furi_string_set_strn,void,"FuriString*, const char[], size_t" +Function,+,furi_string_size,size_t,const FuriString* +Function,+,furi_string_start_with,_Bool,"const FuriString*, const FuriString*" +Function,+,furi_string_start_with_str,_Bool,"const FuriString*, const char[]" +Function,+,furi_string_swap,void,"FuriString*, FuriString*" +Function,+,furi_string_trim,void,"FuriString*, const char[]" +Function,+,furi_string_utf8_decode,void,"char, FuriStringUTF8State*, FuriStringUnicodeValue*" +Function,+,furi_string_utf8_length,size_t,FuriString* +Function,+,furi_string_utf8_push,void,"FuriString*, FuriStringUnicodeValue" +Function,+,furi_string_vprintf,int,"FuriString*, const char[], va_list" Function,+,furi_thread_alloc,FuriThread*, Function,+,furi_thread_catch,void, Function,-,furi_thread_disable_heap_trace,void,FuriThread* @@ -1684,14 +1738,14 @@ Function,+,onewire_slave_set_result_callback,void,"OneWireSlave*, OneWireSlaveRe Function,+,onewire_slave_start,void,OneWireSlave* Function,+,onewire_slave_stop,void,OneWireSlave* Function,-,open_memstream,FILE*,"char**, size_t*" -Function,+,path_append,void,"string_t, const char*" -Function,+,path_concat,void,"const char*, const char*, string_t" +Function,+,path_append,void,"FuriString*, const char*" +Function,+,path_concat,void,"const char*, const char*, FuriString*" Function,+,path_contains_only_ascii,_Bool,const char* -Function,+,path_extract_basename,void,"const char*, string_t" -Function,+,path_extract_dirname,void,"const char*, string_t" -Function,+,path_extract_extension,void,"string_t, char*, size_t" -Function,+,path_extract_filename,void,"string_t, string_t, _Bool" -Function,+,path_extract_filename_no_ext,void,"const char*, string_t" +Function,+,path_extract_basename,void,"const char*, FuriString*" +Function,+,path_extract_dirname,void,"const char*, FuriString*" +Function,+,path_extract_extension,void,"FuriString*, char*, size_t" +Function,+,path_extract_filename,void,"FuriString*, FuriString*, _Bool" +Function,+,path_extract_filename_no_ext,void,"const char*, FuriString*" Function,-,pcTaskGetName,char*,TaskHandle_t Function,-,pcTimerGetName,const char*,TimerHandle_t Function,-,pclose,int,FILE* @@ -1745,8 +1799,8 @@ Function,+,protocol_dict_get_name,const char*,"ProtocolDict*, size_t" Function,+,protocol_dict_get_protocol_by_name,ProtocolId,"ProtocolDict*, const char*" Function,+,protocol_dict_get_validate_count,uint32_t,"ProtocolDict*, size_t" Function,+,protocol_dict_get_write_data,_Bool,"ProtocolDict*, size_t, void*" -Function,+,protocol_dict_render_brief_data,void,"ProtocolDict*, string_t, size_t" -Function,+,protocol_dict_render_data,void,"ProtocolDict*, string_t, size_t" +Function,+,protocol_dict_render_brief_data,void,"ProtocolDict*, FuriString*, size_t" +Function,+,protocol_dict_render_data,void,"ProtocolDict*, FuriString*, size_t" Function,+,protocol_dict_set_data,void,"ProtocolDict*, size_t, const uint8_t*, size_t" Function,-,pselect,int,"int, fd_set*, fd_set*, fd_set*, const timespec*, const sigset_t*" Function,-,putc,int,"int, FILE*" @@ -2073,7 +2127,7 @@ Function,-,storage_file_sync,_Bool,File* Function,+,storage_file_tell,uint64_t,File* Function,+,storage_file_truncate,_Bool,File* Function,+,storage_file_write,uint16_t,"File*, const void*, uint16_t" -Function,+,storage_get_next_filename,void,"Storage*, const char*, const char*, const char*, string_t, uint8_t" +Function,+,storage_get_next_filename,void,"Storage*, const char*, const char*, const char*, FuriString*, uint8_t" Function,+,storage_get_pubsub,FuriPubSub*,Storage* Function,+,storage_int_backup,FS_Error,"Storage*, const char*" Function,+,storage_int_restore,FS_Error,"Storage*, const char*, Storage_name_converter" @@ -2106,7 +2160,7 @@ Function,+,stream_delete_and_insert,_Bool,"Stream*, size_t, StreamWriteCB, const Function,+,stream_delete_and_insert_char,_Bool,"Stream*, size_t, char" Function,+,stream_delete_and_insert_cstring,_Bool,"Stream*, size_t, const char*" Function,+,stream_delete_and_insert_format,_Bool,"Stream*, size_t, const char*, ..." -Function,+,stream_delete_and_insert_string,_Bool,"Stream*, size_t, string_t" +Function,+,stream_delete_and_insert_string,_Bool,"Stream*, size_t, FuriString*" Function,+,stream_delete_and_insert_vaformat,_Bool,"Stream*, size_t, const char*, va_list" Function,+,stream_dump_data,void,Stream* Function,+,stream_eof,_Bool,Stream* @@ -2115,11 +2169,11 @@ Function,+,stream_insert,_Bool,"Stream*, const uint8_t*, size_t" Function,+,stream_insert_char,_Bool,"Stream*, char" Function,+,stream_insert_cstring,_Bool,"Stream*, const char*" Function,+,stream_insert_format,_Bool,"Stream*, const char*, ..." -Function,+,stream_insert_string,_Bool,"Stream*, string_t" +Function,+,stream_insert_string,_Bool,"Stream*, FuriString*" Function,+,stream_insert_vaformat,_Bool,"Stream*, const char*, va_list" Function,+,stream_load_from_file,size_t,"Stream*, Storage*, const char*" Function,+,stream_read,size_t,"Stream*, uint8_t*, size_t" -Function,+,stream_read_line,_Bool,"Stream*, string_t" +Function,+,stream_read_line,_Bool,"Stream*, FuriString*" Function,+,stream_rewind,_Bool,Stream* Function,+,stream_save_to_file,size_t,"Stream*, Storage*, const char*, FS_OpenMode" Function,+,stream_seek,_Bool,"Stream*, int32_t, StreamOffset" @@ -2130,7 +2184,7 @@ Function,+,stream_write,size_t,"Stream*, const uint8_t*, size_t" Function,+,stream_write_char,size_t,"Stream*, char" Function,+,stream_write_cstring,size_t,"Stream*, const char*" Function,+,stream_write_format,size_t,"Stream*, const char*, ..." -Function,+,stream_write_string,size_t,"Stream*, string_t" +Function,+,stream_write_string,size_t,"Stream*, FuriString*" Function,+,stream_write_vaformat,size_t,"Stream*, const char*, va_list" Function,-,strerror,char*,int Function,-,strerror_l,char*,"int, locale_t" @@ -2191,14 +2245,14 @@ Function,-,subghz_keystore_raw_get_data,_Bool,"const char*, size_t, uint8_t*, si Function,-,subghz_keystore_save,_Bool,"SubGhzKeystore*, const char*, uint8_t*" Function,-,subghz_protocol_decoder_base_deserialize,_Bool,"SubGhzProtocolDecoderBase*, FlipperFormat*" Function,-,subghz_protocol_decoder_base_get_hash_data,uint8_t,SubGhzProtocolDecoderBase* -Function,-,subghz_protocol_decoder_base_get_string,_Bool,"SubGhzProtocolDecoderBase*, string_t" +Function,-,subghz_protocol_decoder_base_get_string,_Bool,"SubGhzProtocolDecoderBase*, FuriString*" Function,+,subghz_protocol_decoder_base_serialize,_Bool,"SubGhzProtocolDecoderBase*, FlipperFormat*, SubGhzPresetDefinition*" Function,-,subghz_protocol_decoder_base_set_decoder_callback,void,"SubGhzProtocolDecoderBase*, SubGhzProtocolDecoderBaseRxCallback, void*" Function,+,subghz_protocol_decoder_raw_alloc,void*,SubGhzEnvironment* Function,+,subghz_protocol_decoder_raw_deserialize,_Bool,"void*, FlipperFormat*" Function,+,subghz_protocol_decoder_raw_feed,void,"void*, _Bool, uint32_t" Function,+,subghz_protocol_decoder_raw_free,void,void* -Function,+,subghz_protocol_decoder_raw_get_string,void,"void*, string_t" +Function,+,subghz_protocol_decoder_raw_get_string,void,"void*, FuriString*" Function,+,subghz_protocol_decoder_raw_reset,void,void* Function,+,subghz_protocol_encoder_raw_alloc,void*,SubGhzEnvironment* Function,+,subghz_protocol_encoder_raw_deserialize,_Bool,"void*, FlipperFormat*" @@ -2389,7 +2443,7 @@ Function,-,vTimerSetReloadMode,void,"TimerHandle_t, const UBaseType_t" Function,-,vTimerSetTimerID,void,"TimerHandle_t, void*" Function,-,vTimerSetTimerNumber,void,"TimerHandle_t, UBaseType_t" Function,+,validator_is_file_alloc_init,ValidatorIsFile*,"const char*, const char*, const char*" -Function,+,validator_is_file_callback,_Bool,"const char*, string_t, void*" +Function,+,validator_is_file_callback,_Bool,"const char*, FuriString*, void*" Function,+,validator_is_file_free,void,ValidatorIsFile* Function,+,variable_item_get_context,void*,VariableItem* Function,+,variable_item_get_current_value_index,uint8_t,VariableItem* diff --git a/firmware/targets/f7/ble_glue/dev_info_service.c b/firmware/targets/f7/ble_glue/dev_info_service.c index ecfa08b1755..8bdb2eea84c 100755 --- a/firmware/targets/f7/ble_glue/dev_info_service.c +++ b/firmware/targets/f7/ble_glue/dev_info_service.c @@ -3,7 +3,6 @@ #include #include -#include #include #include @@ -16,7 +15,7 @@ typedef struct { uint16_t firmware_rev_char_handle; uint16_t software_rev_char_handle; uint16_t rpc_version_char_handle; - string_t version_string; + FuriString* version_string; char hardware_revision[4]; } DevInfoSvc; @@ -31,8 +30,7 @@ static const uint8_t dev_info_rpc_version_uuid[] = void dev_info_svc_start() { dev_info_svc = malloc(sizeof(DevInfoSvc)); - string_init_printf( - dev_info_svc->version_string, + dev_info_svc->version_string = furi_string_alloc_printf( "%s %s %s %s", version_get_githash(NULL), version_get_gitbranch(NULL), @@ -104,7 +102,7 @@ void dev_info_svc_start() { dev_info_svc->service_handle, UUID_TYPE_16, (Char_UUID_t*)&uuid, - string_size(dev_info_svc->version_string), + furi_string_size(dev_info_svc->version_string), CHAR_PROP_READ, ATTR_PERMISSION_AUTHEN_READ, GATT_DONT_NOTIFY_EVENTS, @@ -161,8 +159,8 @@ void dev_info_svc_start() { dev_info_svc->service_handle, dev_info_svc->software_rev_char_handle, 0, - string_size(dev_info_svc->version_string), - (uint8_t*)string_get_cstr(dev_info_svc->version_string)); + furi_string_size(dev_info_svc->version_string), + (uint8_t*)furi_string_get_cstr(dev_info_svc->version_string)); if(status) { FURI_LOG_E(TAG, "Failed to update software revision char: %d", status); } @@ -180,7 +178,7 @@ void dev_info_svc_start() { void dev_info_svc_stop() { tBleStatus status; if(dev_info_svc) { - string_clear(dev_info_svc->version_string); + furi_string_free(dev_info_svc->version_string); // Delete service characteristics status = aci_gatt_del_char(dev_info_svc->service_handle, dev_info_svc->man_name_char_handle); diff --git a/firmware/targets/f7/furi_hal/furi_hal_bt.c b/firmware/targets/f7/furi_hal/furi_hal_bt.c index 1a2b436d28a..fc1c25c7e57 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_bt.c +++ b/firmware/targets/f7/furi_hal/furi_hal_bt.c @@ -340,7 +340,7 @@ bool furi_hal_bt_clear_white_list() { return status != BLE_STATUS_SUCCESS; } -void furi_hal_bt_dump_state(string_t buffer) { +void furi_hal_bt_dump_state(FuriString* buffer) { if(furi_hal_bt_is_alive()) { uint8_t HCI_Version; uint16_t HCI_Revision; @@ -351,7 +351,7 @@ void furi_hal_bt_dump_state(string_t buffer) { tBleStatus ret = hci_read_local_version_information( &HCI_Version, &HCI_Revision, &LMP_PAL_Version, &Manufacturer_Name, &LMP_PAL_Subversion); - string_cat_printf( + furi_string_cat_printf( buffer, "Ret: %d, HCI_Version: %d, HCI_Revision: %d, LMP_PAL_Version: %d, Manufacturer_Name: %d, LMP_PAL_Subversion: %d", ret, @@ -361,7 +361,7 @@ void furi_hal_bt_dump_state(string_t buffer) { Manufacturer_Name, LMP_PAL_Subversion); } else { - string_cat_printf(buffer, "BLE not ready"); + furi_string_cat_printf(buffer, "BLE not ready"); } } diff --git a/firmware/targets/f7/furi_hal/furi_hal_console.c b/firmware/targets/f7/furi_hal/furi_hal_console.c index e5db927bfe1..2bdc57250ad 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_console.c +++ b/firmware/targets/f7/furi_hal/furi_hal_console.c @@ -4,7 +4,6 @@ #include #include #include -#include #include @@ -88,13 +87,13 @@ void furi_hal_console_tx_with_new_line(const uint8_t* buffer, size_t buffer_size } void furi_hal_console_printf(const char format[], ...) { - string_t string; + FuriString* string; va_list args; va_start(args, format); - string_init_vprintf(string, format, args); + string = furi_string_alloc_vprintf(format, args); va_end(args); - furi_hal_console_tx((const uint8_t*)string_get_cstr(string), string_size(string)); - string_clear(string); + furi_hal_console_tx((const uint8_t*)furi_string_get_cstr(string), furi_string_size(string)); + furi_string_free(string); } void furi_hal_console_puts(const char* data) { diff --git a/firmware/targets/f7/furi_hal/furi_hal_info.c b/firmware/targets/f7/furi_hal/furi_hal_info.c index 25ea42bb6d8..15ce41a207c 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_info.c +++ b/firmware/targets/f7/furi_hal/furi_hal_info.c @@ -5,12 +5,12 @@ #include #include -#include +#include #include void furi_hal_info_get(FuriHalInfoValueCallback out, void* context) { - string_t value; - string_init(value); + FuriString* value; + value = furi_string_alloc(); // Device Info version out("device_info_major", "2", false, context); @@ -20,36 +20,36 @@ void furi_hal_info_get(FuriHalInfoValueCallback out, void* context) { out("hardware_model", furi_hal_version_get_model_name(), false, context); // Unique ID - string_reset(value); + furi_string_reset(value); const uint8_t* uid = furi_hal_version_uid(); for(size_t i = 0; i < furi_hal_version_uid_size(); i++) { - string_cat_printf(value, "%02X", uid[i]); + furi_string_cat_printf(value, "%02X", uid[i]); } - out("hardware_uid", string_get_cstr(value), false, context); + out("hardware_uid", furi_string_get_cstr(value), false, context); // OTP Revision - string_printf(value, "%d", furi_hal_version_get_otp_version()); - out("hardware_otp_ver", string_get_cstr(value), false, context); - string_printf(value, "%lu", furi_hal_version_get_hw_timestamp()); - out("hardware_timestamp", string_get_cstr(value), false, context); + furi_string_printf(value, "%d", furi_hal_version_get_otp_version()); + out("hardware_otp_ver", furi_string_get_cstr(value), false, context); + furi_string_printf(value, "%lu", furi_hal_version_get_hw_timestamp()); + out("hardware_timestamp", furi_string_get_cstr(value), false, context); // Board Revision - string_printf(value, "%d", furi_hal_version_get_hw_version()); - out("hardware_ver", string_get_cstr(value), false, context); - string_printf(value, "%d", furi_hal_version_get_hw_target()); - out("hardware_target", string_get_cstr(value), false, context); - string_printf(value, "%d", furi_hal_version_get_hw_body()); - out("hardware_body", string_get_cstr(value), false, context); - string_printf(value, "%d", furi_hal_version_get_hw_connect()); - out("hardware_connect", string_get_cstr(value), false, context); - string_printf(value, "%d", furi_hal_version_get_hw_display()); - out("hardware_display", string_get_cstr(value), false, context); + furi_string_printf(value, "%d", furi_hal_version_get_hw_version()); + out("hardware_ver", furi_string_get_cstr(value), false, context); + furi_string_printf(value, "%d", furi_hal_version_get_hw_target()); + out("hardware_target", furi_string_get_cstr(value), false, context); + furi_string_printf(value, "%d", furi_hal_version_get_hw_body()); + out("hardware_body", furi_string_get_cstr(value), false, context); + furi_string_printf(value, "%d", furi_hal_version_get_hw_connect()); + out("hardware_connect", furi_string_get_cstr(value), false, context); + furi_string_printf(value, "%d", furi_hal_version_get_hw_display()); + out("hardware_display", furi_string_get_cstr(value), false, context); // Board Personification - string_printf(value, "%d", furi_hal_version_get_hw_color()); - out("hardware_color", string_get_cstr(value), false, context); - string_printf(value, "%d", furi_hal_version_get_hw_region()); - out("hardware_region", string_get_cstr(value), false, context); + furi_string_printf(value, "%d", furi_hal_version_get_hw_color()); + out("hardware_color", furi_string_get_cstr(value), false, context); + furi_string_printf(value, "%d", furi_hal_version_get_hw_region()); + out("hardware_region", furi_string_get_cstr(value), false, context); out("hardware_region_provisioned", furi_hal_region_get_name(), false, context); const char* name = furi_hal_version_get_name_ptr(); if(name) { @@ -68,8 +68,8 @@ void furi_hal_info_get(FuriHalInfoValueCallback out, void* context) { out("firmware_branch_num", version_get_gitbranchnum(firmware_version), false, context); out("firmware_version", version_get_version(firmware_version), false, context); out("firmware_build_date", version_get_builddate(firmware_version), false, context); - string_printf(value, "%d", version_get_target(firmware_version)); - out("firmware_target", string_get_cstr(value), false, context); + furi_string_printf(value, "%d", version_get_target(firmware_version)); + out("firmware_target", furi_string_get_cstr(value), false, context); } if(furi_hal_bt_is_alive()) { @@ -78,64 +78,64 @@ void furi_hal_info_get(FuriHalInfoValueCallback out, void* context) { out("radio_mode", ble_c2_info->mode == BleGlueC2ModeFUS ? "FUS" : "Stack", false, context); // FUS Info - string_printf(value, "%d", ble_c2_info->FusVersionMajor); - out("radio_fus_major", string_get_cstr(value), false, context); - string_printf(value, "%d", ble_c2_info->FusVersionMinor); - out("radio_fus_minor", string_get_cstr(value), false, context); - string_printf(value, "%d", ble_c2_info->FusVersionSub); - out("radio_fus_sub", string_get_cstr(value), false, context); - string_printf(value, "%dK", ble_c2_info->FusMemorySizeSram2B); - out("radio_fus_sram2b", string_get_cstr(value), false, context); - string_printf(value, "%dK", ble_c2_info->FusMemorySizeSram2A); - out("radio_fus_sram2a", string_get_cstr(value), false, context); - string_printf(value, "%dK", ble_c2_info->FusMemorySizeFlash * 4); - out("radio_fus_flash", string_get_cstr(value), false, context); + furi_string_printf(value, "%d", ble_c2_info->FusVersionMajor); + out("radio_fus_major", furi_string_get_cstr(value), false, context); + furi_string_printf(value, "%d", ble_c2_info->FusVersionMinor); + out("radio_fus_minor", furi_string_get_cstr(value), false, context); + furi_string_printf(value, "%d", ble_c2_info->FusVersionSub); + out("radio_fus_sub", furi_string_get_cstr(value), false, context); + furi_string_printf(value, "%dK", ble_c2_info->FusMemorySizeSram2B); + out("radio_fus_sram2b", furi_string_get_cstr(value), false, context); + furi_string_printf(value, "%dK", ble_c2_info->FusMemorySizeSram2A); + out("radio_fus_sram2a", furi_string_get_cstr(value), false, context); + furi_string_printf(value, "%dK", ble_c2_info->FusMemorySizeFlash * 4); + out("radio_fus_flash", furi_string_get_cstr(value), false, context); // Stack Info - string_printf(value, "%d", ble_c2_info->StackType); - out("radio_stack_type", string_get_cstr(value), false, context); - string_printf(value, "%d", ble_c2_info->VersionMajor); - out("radio_stack_major", string_get_cstr(value), false, context); - string_printf(value, "%d", ble_c2_info->VersionMinor); - out("radio_stack_minor", string_get_cstr(value), false, context); - string_printf(value, "%d", ble_c2_info->VersionSub); - out("radio_stack_sub", string_get_cstr(value), false, context); - string_printf(value, "%d", ble_c2_info->VersionBranch); - out("radio_stack_branch", string_get_cstr(value), false, context); - string_printf(value, "%d", ble_c2_info->VersionReleaseType); - out("radio_stack_release", string_get_cstr(value), false, context); - string_printf(value, "%dK", ble_c2_info->MemorySizeSram2B); - out("radio_stack_sram2b", string_get_cstr(value), false, context); - string_printf(value, "%dK", ble_c2_info->MemorySizeSram2A); - out("radio_stack_sram2a", string_get_cstr(value), false, context); - string_printf(value, "%dK", ble_c2_info->MemorySizeSram1); - out("radio_stack_sram1", string_get_cstr(value), false, context); - string_printf(value, "%dK", ble_c2_info->MemorySizeFlash * 4); - out("radio_stack_flash", string_get_cstr(value), false, context); + furi_string_printf(value, "%d", ble_c2_info->StackType); + out("radio_stack_type", furi_string_get_cstr(value), false, context); + furi_string_printf(value, "%d", ble_c2_info->VersionMajor); + out("radio_stack_major", furi_string_get_cstr(value), false, context); + furi_string_printf(value, "%d", ble_c2_info->VersionMinor); + out("radio_stack_minor", furi_string_get_cstr(value), false, context); + furi_string_printf(value, "%d", ble_c2_info->VersionSub); + out("radio_stack_sub", furi_string_get_cstr(value), false, context); + furi_string_printf(value, "%d", ble_c2_info->VersionBranch); + out("radio_stack_branch", furi_string_get_cstr(value), false, context); + furi_string_printf(value, "%d", ble_c2_info->VersionReleaseType); + out("radio_stack_release", furi_string_get_cstr(value), false, context); + furi_string_printf(value, "%dK", ble_c2_info->MemorySizeSram2B); + out("radio_stack_sram2b", furi_string_get_cstr(value), false, context); + furi_string_printf(value, "%dK", ble_c2_info->MemorySizeSram2A); + out("radio_stack_sram2a", furi_string_get_cstr(value), false, context); + furi_string_printf(value, "%dK", ble_c2_info->MemorySizeSram1); + out("radio_stack_sram1", furi_string_get_cstr(value), false, context); + furi_string_printf(value, "%dK", ble_c2_info->MemorySizeFlash * 4); + out("radio_stack_flash", furi_string_get_cstr(value), false, context); // Mac address - string_reset(value); + furi_string_reset(value); const uint8_t* ble_mac = furi_hal_version_get_ble_mac(); for(size_t i = 0; i < 6; i++) { - string_cat_printf(value, "%02X", ble_mac[i]); + furi_string_cat_printf(value, "%02X", ble_mac[i]); } - out("radio_ble_mac", string_get_cstr(value), false, context); + out("radio_ble_mac", furi_string_get_cstr(value), false, context); // Signature verification uint8_t enclave_keys = 0; uint8_t enclave_valid_keys = 0; bool enclave_valid = furi_hal_crypto_verify_enclave(&enclave_keys, &enclave_valid_keys); - string_printf(value, "%d", enclave_valid_keys); - out("enclave_valid_keys", string_get_cstr(value), false, context); + furi_string_printf(value, "%d", enclave_valid_keys); + out("enclave_valid_keys", furi_string_get_cstr(value), false, context); out("enclave_valid", enclave_valid ? "true" : "false", false, context); } else { out("radio_alive", "false", false, context); } - string_printf(value, "%u", PROTOBUF_MAJOR_VERSION); - out("protobuf_version_major", string_get_cstr(value), false, context); - string_printf(value, "%u", PROTOBUF_MINOR_VERSION); - out("protobuf_version_minor", string_get_cstr(value), true, context); + furi_string_printf(value, "%u", PROTOBUF_MAJOR_VERSION); + out("protobuf_version_major", furi_string_get_cstr(value), false, context); + furi_string_printf(value, "%u", PROTOBUF_MINOR_VERSION); + out("protobuf_version_minor", furi_string_get_cstr(value), true, context); - string_clear(value); + furi_string_free(value); } diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc.c b/firmware/targets/f7/furi_hal/furi_hal_nfc.c index de67bbc358f..069ac4ea41c 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_nfc.c +++ b/firmware/targets/f7/furi_hal/furi_hal_nfc.c @@ -4,7 +4,6 @@ #include #include #include -#include #include #include @@ -743,7 +742,8 @@ void furi_hal_nfc_sleep() { rfalLowPowerModeStart(); } -FuriHalNfcReturn furi_hal_nfc_ll_set_mode(FuriHalNfcMode mode, FuriHalNfcBitrate txBR, FuriHalNfcBitrate rxBR) { +FuriHalNfcReturn + furi_hal_nfc_ll_set_mode(FuriHalNfcMode mode, FuriHalNfcBitrate txBR, FuriHalNfcBitrate rxBR) { return rfalSetMode((rfalMode)mode, (rfalBitRate)txBR, (rfalBitRate)rxBR); } diff --git a/firmware/targets/f7/furi_hal/furi_hal_power.c b/firmware/targets/f7/furi_hal/furi_hal_power.c index 524fae0b1cc..bc98f108d4d 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_power.c +++ b/firmware/targets/f7/furi_hal/furi_hal_power.c @@ -529,8 +529,8 @@ void furi_hal_power_suppress_charge_exit() { void furi_hal_power_info_get(FuriHalPowerInfoCallback out, void* context) { furi_assert(out); - string_t value; - string_init(value); + FuriString* value; + value = furi_string_alloc(); // Power Info version out("power_info_major", "1", false, context); @@ -538,45 +538,45 @@ void furi_hal_power_info_get(FuriHalPowerInfoCallback out, void* context) { uint8_t charge = furi_hal_power_get_pct(); - string_printf(value, "%u", charge); - out("charge_level", string_get_cstr(value), false, context); + furi_string_printf(value, "%u", charge); + out("charge_level", furi_string_get_cstr(value), false, context); if(furi_hal_power_is_charging()) { if(charge < 100) { - string_printf(value, "charging"); + furi_string_printf(value, "charging"); } else { - string_printf(value, "charged"); + furi_string_printf(value, "charged"); } } else { - string_printf(value, "discharging"); + furi_string_printf(value, "discharging"); } - out("charge_state", string_get_cstr(value), false, context); + out("charge_state", furi_string_get_cstr(value), false, context); uint16_t voltage = (uint16_t)(furi_hal_power_get_battery_voltage(FuriHalPowerICFuelGauge) * 1000.f); - string_printf(value, "%u", voltage); - out("battery_voltage", string_get_cstr(value), false, context); + furi_string_printf(value, "%u", voltage); + out("battery_voltage", furi_string_get_cstr(value), false, context); int16_t current = (int16_t)(furi_hal_power_get_battery_current(FuriHalPowerICFuelGauge) * 1000.f); - string_printf(value, "%d", current); - out("battery_current", string_get_cstr(value), false, context); + furi_string_printf(value, "%d", current); + out("battery_current", furi_string_get_cstr(value), false, context); int16_t temperature = (int16_t)furi_hal_power_get_battery_temperature(FuriHalPowerICFuelGauge); - string_printf(value, "%d", temperature); - out("gauge_temp", string_get_cstr(value), false, context); + furi_string_printf(value, "%d", temperature); + out("gauge_temp", furi_string_get_cstr(value), false, context); - string_printf(value, "%u", furi_hal_power_get_bat_health_pct()); - out("battery_health", string_get_cstr(value), false, context); + furi_string_printf(value, "%u", furi_hal_power_get_bat_health_pct()); + out("battery_health", furi_string_get_cstr(value), false, context); - string_printf(value, "%u", furi_hal_power_get_battery_remaining_capacity()); - out("capacity_remain", string_get_cstr(value), false, context); + furi_string_printf(value, "%u", furi_hal_power_get_battery_remaining_capacity()); + out("capacity_remain", furi_string_get_cstr(value), false, context); - string_printf(value, "%u", furi_hal_power_get_battery_full_capacity()); - out("capacity_full", string_get_cstr(value), false, context); + furi_string_printf(value, "%u", furi_hal_power_get_battery_full_capacity()); + out("capacity_full", furi_string_get_cstr(value), false, context); - string_printf(value, "%u", furi_hal_power_get_battery_design_capacity()); - out("capacity_design", string_get_cstr(value), true, context); + furi_string_printf(value, "%u", furi_hal_power_get_battery_design_capacity()); + out("capacity_design", furi_string_get_cstr(value), true, context); - string_clear(value); + furi_string_free(value); } diff --git a/firmware/targets/furi_hal_include/furi_hal_bt.h b/firmware/targets/furi_hal_include/furi_hal_bt.h index 3f1169d1675..800fc3fe3f7 100644 --- a/firmware/targets/furi_hal_include/furi_hal_bt.h +++ b/firmware/targets/furi_hal_include/furi_hal_bt.h @@ -5,7 +5,7 @@ #pragma once -#include +#include #include #include #include @@ -122,9 +122,9 @@ void furi_hal_bt_stop_advertising(); /** Get BT/BLE system component state * - * @param[in] buffer string_t buffer to write to + * @param[in] buffer FuriString* buffer to write to */ -void furi_hal_bt_dump_state(string_t buffer); +void furi_hal_bt_dump_state(FuriString* buffer); /** Get BT/BLE system component state * diff --git a/firmware/targets/furi_hal_include/furi_hal_power.h b/firmware/targets/furi_hal_include/furi_hal_power.h index f8eaa5c3ab0..e94877afcb1 100644 --- a/firmware/targets/furi_hal_include/furi_hal_power.h +++ b/firmware/targets/furi_hal_include/furi_hal_power.h @@ -7,7 +7,6 @@ #include #include -#include #ifdef __cplusplus extern "C" { diff --git a/furi/core/check.c b/furi/core/check.c index 3d0cd7a0463..613b52f4666 100644 --- a/furi/core/check.c +++ b/furi/core/check.c @@ -9,6 +9,7 @@ #include #include #include +#include extern size_t xPortGetTotalHeapSize(void); extern size_t xPortGetFreeHeapSize(void); diff --git a/furi/core/kernel.h b/furi/core/kernel.h index 28afffd4564..f30f109bb84 100644 --- a/furi/core/kernel.h +++ b/furi/core/kernel.h @@ -1,5 +1,5 @@ /** - * @file kenrel.h + * @file kernel.h * Furi Kernel primitives */ #pragma once diff --git a/furi/core/log.c b/furi/core/log.c index 8a36a930be3..bb163c9566a 100644 --- a/furi/core/log.c +++ b/furi/core/log.c @@ -25,8 +25,8 @@ void furi_log_init() { void furi_log_print_format(FuriLogLevel level, const char* tag, const char* format, ...) { if(level <= furi_log.log_level && furi_mutex_acquire(furi_log.mutex, FuriWaitForever) == FuriStatusOk) { - string_t string; - string_init(string); + FuriString* string; + string = furi_string_alloc(); const char* color = FURI_LOG_CLR_RESET; const char* log_letter = " "; @@ -56,23 +56,23 @@ void furi_log_print_format(FuriLogLevel level, const char* tag, const char* form } // Timestamp - string_printf( + furi_string_printf( string, "%lu %s[%s][%s] " FURI_LOG_CLR_RESET, furi_log.timetamp(), color, log_letter, tag); - furi_log.puts(string_get_cstr(string)); - string_reset(string); + furi_log.puts(furi_string_get_cstr(string)); + furi_string_reset(string); va_list args; va_start(args, format); - string_vprintf(string, format, args); + furi_string_vprintf(string, format, args); va_end(args); - furi_log.puts(string_get_cstr(string)); - string_clear(string); + furi_log.puts(furi_string_get_cstr(string)); + furi_string_free(string); furi_log.puts("\r\n"); diff --git a/furi/core/record.c b/furi/core/record.c index 63dfdbe477d..773585e7efc 100644 --- a/furi/core/record.c +++ b/furi/core/record.c @@ -4,7 +4,6 @@ #include "mutex.h" #include "event_flag.h" -#include #include #include diff --git a/furi/core/string.c b/furi/core/string.c new file mode 100644 index 00000000000..099f70c11fc --- /dev/null +++ b/furi/core/string.c @@ -0,0 +1,302 @@ +#include "string.h" +#include + +struct FuriString { + string_t string; +}; + +#undef furi_string_alloc_set +#undef furi_string_set +#undef furi_string_cmp +#undef furi_string_cmpi +#undef furi_string_search +#undef furi_string_search_str +#undef furi_string_equal +#undef furi_string_replace +#undef furi_string_replace_str +#undef furi_string_replace_all +#undef furi_string_start_with +#undef furi_string_end_with +#undef furi_string_search_char +#undef furi_string_search_rchar +#undef furi_string_trim +#undef furi_string_cat + +FuriString* furi_string_alloc() { + FuriString* string = malloc(sizeof(FuriString)); + string_init(string->string); + return string; +} + +FuriString* furi_string_alloc_set(const FuriString* s) { + FuriString* string = malloc(sizeof(FuriString)); + string_init_set(string->string, s->string); + return string; +} + +FuriString* furi_string_alloc_set_str(const char cstr[]) { + FuriString* string = malloc(sizeof(FuriString)); + string_init_set(string->string, cstr); + return string; +} + +FuriString* furi_string_alloc_printf(const char format[], ...) { + va_list args; + va_start(args, format); + FuriString* string = furi_string_alloc_vprintf(format, args); + va_end(args); + return string; +} + +FuriString* furi_string_alloc_vprintf(const char format[], va_list args) { + FuriString* string = malloc(sizeof(FuriString)); + string_init_vprintf(string->string, format, args); + return string; +} + +FuriString* furi_string_alloc_move(FuriString* s) { + FuriString* string = malloc(sizeof(FuriString)); + string_init_move(string->string, s->string); + free(s); + return string; +} + +void furi_string_free(FuriString* s) { + string_clear(s->string); + free(s); +} + +void furi_string_reserve(FuriString* s, size_t alloc) { + string_reserve(s->string, alloc); +} + +void furi_string_reset(FuriString* s) { + string_reset(s->string); +} + +void furi_string_swap(FuriString* v1, FuriString* v2) { + string_swap(v1->string, v2->string); +} + +void furi_string_move(FuriString* v1, FuriString* v2) { + string_clear(v1->string); + string_init_move(v1->string, v2->string); + free(v2); +} + +size_t furi_string_hash(const FuriString* v) { + return string_hash(v->string); +} + +char furi_string_get_char(const FuriString* v, size_t index) { + return string_get_char(v->string, index); +} + +const char* furi_string_get_cstr(const FuriString* s) { + return string_get_cstr(s->string); +} + +void furi_string_set(FuriString* s, FuriString* source) { + string_set(s->string, source->string); +} + +void furi_string_set_str(FuriString* s, const char cstr[]) { + string_set(s->string, cstr); +} + +void furi_string_set_strn(FuriString* s, const char str[], size_t n) { + string_set_strn(s->string, str, n); +} + +void furi_string_set_char(FuriString* s, size_t index, const char c) { + string_set_char(s->string, index, c); +} + +int furi_string_cmp(const FuriString* s1, const FuriString* s2) { + return string_cmp(s1->string, s2->string); +} + +int furi_string_cmp_str(const FuriString* s1, const char str[]) { + return string_cmp(s1->string, str); +} + +int furi_string_cmpi(const FuriString* v1, const FuriString* v2) { + return string_cmpi(v1->string, v2->string); +} + +int furi_string_cmpi_str(const FuriString* v1, const char p2[]) { + return string_cmpi_str(v1->string, p2); +} + +size_t furi_string_search(const FuriString* v, const FuriString* needle, size_t start) { + return string_search(v->string, needle->string, start); +} + +size_t furi_string_search_str(const FuriString* v, const char needle[], size_t start) { + return string_search(v->string, needle, start); +} + +bool furi_string_equal(const FuriString* v1, const FuriString* v2) { + return string_equal_p(v1->string, v2->string); +} + +bool furi_string_equal_str(const FuriString* v1, const char v2[]) { + return string_equal_p(v1->string, v2); +} + +void furi_string_push_back(FuriString* v, char c) { + string_push_back(v->string, c); +} + +size_t furi_string_size(const FuriString* s) { + return string_size(s->string); +} + +int furi_string_printf(FuriString* v, const char format[], ...) { + va_list args; + va_start(args, format); + int result = furi_string_vprintf(v, format, args); + va_end(args); + return result; +} + +int furi_string_vprintf(FuriString* v, const char format[], va_list args) { + return string_vprintf(v->string, format, args); +} + +int furi_string_cat_printf(FuriString* v, const char format[], ...) { + va_list args; + va_start(args, format); + int result = furi_string_cat_vprintf(v, format, args); + va_end(args); + return result; +} + +int furi_string_cat_vprintf(FuriString* v, const char format[], va_list args) { + FuriString* string = furi_string_alloc(); + int ret = furi_string_vprintf(string, format, args); + furi_string_cat(v, string); + furi_string_free(string); + return ret; +} + +bool furi_string_empty(const FuriString* v) { + return string_empty_p(v->string); +} + +void furi_string_replace_at(FuriString* v, size_t pos, size_t len, const char str2[]) { + string_replace_at(v->string, pos, len, str2); +} + +size_t + furi_string_replace(FuriString* string, FuriString* needle, FuriString* replace, size_t start) { + return string_replace(string->string, needle->string, replace->string, start); +} + +size_t furi_string_replace_str(FuriString* v, const char str1[], const char str2[], size_t start) { + return string_replace_str(v->string, str1, str2, start); +} + +void furi_string_replace_all_str(FuriString* v, const char str1[], const char str2[]) { + string_replace_all_str(v->string, str1, str2); +} + +void furi_string_replace_all(FuriString* v, const FuriString* str1, const FuriString* str2) { + string_replace_all(v->string, str1->string, str2->string); +} + +bool furi_string_start_with(const FuriString* v, const FuriString* v2) { + return string_start_with_string_p(v->string, v2->string); +} + +bool furi_string_start_with_str(const FuriString* v, const char str[]) { + return string_start_with_str_p(v->string, str); +} + +bool furi_string_end_with(const FuriString* v, const FuriString* v2) { + return string_end_with_string_p(v->string, v2->string); +} + +bool furi_string_end_with_str(const FuriString* v, const char str[]) { + return string_end_with_str_p(v->string, str); +} + +size_t furi_string_search_char(const FuriString* v, char c, size_t start) { + return string_search_char(v->string, c, start); +} + +size_t furi_string_search_rchar(const FuriString* v, char c, size_t start) { + return string_search_rchar(v->string, c, start); +} + +void furi_string_left(FuriString* v, size_t index) { + string_left(v->string, index); +} + +void furi_string_right(FuriString* v, size_t index) { + string_right(v->string, index); +} + +void furi_string_mid(FuriString* v, size_t index, size_t size) { + string_mid(v->string, index, size); +} + +void furi_string_trim(FuriString* v, const char charac[]) { + string_strim(v->string, charac); +} + +void furi_string_cat(FuriString* v, const FuriString* v2) { + string_cat(v->string, v2->string); +} + +void furi_string_cat_str(FuriString* v, const char str[]) { + string_cat(v->string, str); +} + +void furi_string_set_n(FuriString* v, const FuriString* ref, size_t offset, size_t length) { + string_set_n(v->string, ref->string, offset, length); +} + +size_t furi_string_utf8_length(FuriString* str) { + return string_length_u(str->string); +} + +void furi_string_utf8_push(FuriString* str, FuriStringUnicodeValue u) { + string_push_u(str->string, u); +} + +static m_str1ng_utf8_state_e furi_state_to_state(FuriStringUTF8State state) { + switch(state) { + case FuriStringUTF8StateStarting: + return M_STRING_UTF8_STARTING; + case FuriStringUTF8StateDecoding1: + return M_STRING_UTF8_DECODING_1; + case FuriStringUTF8StateDecoding2: + return M_STRING_UTF8_DECODING_2; + case FuriStringUTF8StateDecoding3: + return M_STRING_UTF8_DOCODING_3; + default: + return M_STRING_UTF8_ERROR; + } +} + +static FuriStringUTF8State state_to_furi_state(m_str1ng_utf8_state_e state) { + switch(state) { + case M_STRING_UTF8_STARTING: + return FuriStringUTF8StateStarting; + case M_STRING_UTF8_DECODING_1: + return FuriStringUTF8StateDecoding1; + case M_STRING_UTF8_DECODING_2: + return FuriStringUTF8StateDecoding2; + case M_STRING_UTF8_DOCODING_3: + return FuriStringUTF8StateDecoding3; + default: + return FuriStringUTF8StateError; + } +} + +void furi_string_utf8_decode(char c, FuriStringUTF8State* state, FuriStringUnicodeValue* unicode) { + m_str1ng_utf8_state_e m_state = furi_state_to_state(*state); + m_str1ng_utf8_decode(c, &m_state, unicode); + *state = state_to_furi_state(m_state); +} \ No newline at end of file diff --git a/furi/core/string.h b/furi/core/string.h new file mode 100644 index 00000000000..b6700c9cade --- /dev/null +++ b/furi/core/string.h @@ -0,0 +1,738 @@ +/** + * @file string.h + * Furi string primitive + */ +#pragma once + +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Furi string failure constant. + */ +#define FURI_STRING_FAILURE ((size_t)-1) + +/** + * @brief Furi string primitive. + */ +typedef struct FuriString FuriString; + +//--------------------------------------------------------------------------- +// Constructors +//--------------------------------------------------------------------------- + +/** + * @brief Allocate new FuriString. + * @return FuriString* + */ +FuriString* furi_string_alloc(); + +/** + * @brief Allocate new FuriString and set it to string. + * Allocate & Set the string a to the string. + * @param source + * @return FuriString* + */ +FuriString* furi_string_alloc_set(const FuriString* source); + +/** + * @brief Allocate new FuriString and set it to C string. + * Allocate & Set the string a to the C string. + * @param cstr_source + * @return FuriString* + */ +FuriString* furi_string_alloc_set_str(const char cstr_source[]); + +/** + * @brief Allocate new FuriString and printf to it. + * Initialize and set a string to the given formatted value. + * @param format + * @param ... + * @return FuriString* + */ +FuriString* furi_string_alloc_printf(const char format[], ...); + +/** + * @brief Allocate new FuriString and printf to it. + * Initialize and set a string to the given formatted value. + * @param format + * @param args + * @return FuriString* + */ +FuriString* furi_string_alloc_vprintf(const char format[], va_list args); + +/** + * @brief Allocate new FuriString and move source string content to it. + * Allocate the string, set it to the other one, and destroy the other one. + * @param source + * @return FuriString* + */ +FuriString* furi_string_alloc_move(FuriString* source); + +//--------------------------------------------------------------------------- +// Destructors +//--------------------------------------------------------------------------- + +/** + * @brief Free FuriString. + * @param string + */ +void furi_string_free(FuriString* string); + +//--------------------------------------------------------------------------- +// String memory management +//--------------------------------------------------------------------------- + +/** + * @brief Reserve memory for string. + * Modify the string capacity to be able to handle at least 'alloc' characters (including final null char). + * @param string + * @param size + */ +void furi_string_reserve(FuriString* string, size_t size); + +/** + * @brief Reset string. + * Make the string empty. + * @param s + */ +void furi_string_reset(FuriString* string); + +/** + * @brief Swap two strings. + * Swap the two strings string_1 and string_2. + * @param string_1 + * @param string_2 + */ +void furi_string_swap(FuriString* string_1, FuriString* string_2); + +/** + * @brief Move string_2 content to string_1. + * Set the string to the other one, and destroy the other one. + * @param string_1 + * @param string_2 + */ +void furi_string_move(FuriString* string_1, FuriString* string_2); + +/** + * @brief Compute a hash for the string. + * @param string + * @return size_t + */ +size_t furi_string_hash(const FuriString* string); + +/** + * @brief Get string size (usually length, but not for UTF-8) + * @param string + * @return size_t + */ +size_t furi_string_size(const FuriString* string); + +/** + * @brief Check that string is empty or not + * @param string + * @return bool + */ +bool furi_string_empty(const FuriString* string); + +//--------------------------------------------------------------------------- +// Getters +//--------------------------------------------------------------------------- + +/** + * @brief Get the character at the given index. + * Return the selected character of the string. + * @param string + * @param index + * @return char + */ +char furi_string_get_char(const FuriString* string, size_t index); + +/** + * @brief Return the string view a classic C string. + * @param string + * @return const char* + */ +const char* furi_string_get_cstr(const FuriString* string); + +//--------------------------------------------------------------------------- +// Setters +//--------------------------------------------------------------------------- + +/** + * @brief Set the string to the other string. + * Set the string to the source string. + * @param string + * @param source + */ +void furi_string_set(FuriString* string, FuriString* source); + +/** + * @brief Set the string to the other C string. + * Set the string to the source C string. + * @param string + * @param source + */ +void furi_string_set_str(FuriString* string, const char source[]); + +/** + * @brief Set the string to the n first characters of the C string. + * @param string + * @param source + * @param length + */ +void furi_string_set_strn(FuriString* string, const char source[], size_t length); + +/** + * @brief Set the character at the given index. + * @param string + * @param index + * @param c + */ +void furi_string_set_char(FuriString* string, size_t index, const char c); + +/** + * @brief Set the string to the n first characters of other one. + * @param string + * @param source + * @param offset + * @param length + */ +void furi_string_set_n(FuriString* string, const FuriString* source, size_t offset, size_t length); + +/** + * @brief Format in the string the given printf format + * @param string + * @param format + * @param ... + * @return int + */ +int furi_string_printf(FuriString* string, const char format[], ...); + +/** + * @brief Format in the string the given printf format + * @param string + * @param format + * @param args + * @return int + */ +int furi_string_vprintf(FuriString* string, const char format[], va_list args); + +//--------------------------------------------------------------------------- +// Appending +//--------------------------------------------------------------------------- + +/** + * @brief Append a character to the string. + * @param string + * @param c + */ +void furi_string_push_back(FuriString* string, char c); + +/** + * @brief Append a string to the string. + * Concatenate the string with the other string. + * @param string_1 + * @param string_2 + */ +void furi_string_cat(FuriString* string_1, const FuriString* string_2); + +/** + * @brief Append a C string to the string. + * Concatenate the string with the C string. + * @param string_1 + * @param cstring_2 + */ +void furi_string_cat_str(FuriString* string_1, const char cstring_2[]); + +/** + * @brief Append to the string the formatted string of the given printf format. + * @param string + * @param format + * @param ... + * @return int + */ +int furi_string_cat_printf(FuriString* string, const char format[], ...); + +/** + * @brief Append to the string the formatted string of the given printf format. + * @param string + * @param format + * @param args + * @return int + */ +int furi_string_cat_vprintf(FuriString* string, const char format[], va_list args); + +//--------------------------------------------------------------------------- +// Comparators +//--------------------------------------------------------------------------- + +/** + * @brief Compare two strings and return the sort order. + * @param string_1 + * @param string_2 + * @return int + */ +int furi_string_cmp(const FuriString* string_1, const FuriString* string_2); + +/** + * @brief Compare string with C string and return the sort order. + * @param string_1 + * @param cstring_2 + * @return int + */ +int furi_string_cmp_str(const FuriString* string_1, const char cstring_2[]); + +/** + * @brief Compare two strings (case insensitive according to the current locale) and return the sort order. + * Note: doesn't work with UTF-8 strings. + * @param string_1 + * @param string_2 + * @return int + */ +int furi_string_cmpi(const FuriString* string_1, const FuriString* string_2); + +/** + * @brief Compare string with C string (case insensitive according to the current locale) and return the sort order. + * Note: doesn't work with UTF-8 strings. + * @param string_1 + * @param cstring_2 + * @return int + */ +int furi_string_cmpi_str(const FuriString* string_1, const char cstring_2[]); + +//--------------------------------------------------------------------------- +// Search +//--------------------------------------------------------------------------- + +/** + * @brief Search the first occurrence of the needle in the string from the position start. + * Return STRING_FAILURE if not found. + * By default, start is zero. + * @param string + * @param needle + * @param start + * @return size_t + */ +size_t furi_string_search(const FuriString* string, const FuriString* needle, size_t start); + +/** + * @brief Search the first occurrence of the needle in the string from the position start. + * Return STRING_FAILURE if not found. + * @param string + * @param needle + * @param start + * @return size_t + */ +size_t furi_string_search_str(const FuriString* string, const char needle[], size_t start); + +/** + * @brief Search for the position of the character c from the position start (include) in the string. + * Return STRING_FAILURE if not found. + * By default, start is zero. + * @param string + * @param c + * @param start + * @return size_t + */ +size_t furi_string_search_char(const FuriString* string, char c, size_t start); + +/** + * @brief Reverse search for the position of the character c from the position start (include) in the string. + * Return STRING_FAILURE if not found. + * By default, start is zero. + * @param string + * @param c + * @param start + * @return size_t + */ +size_t furi_string_search_rchar(const FuriString* string, char c, size_t start); + +//--------------------------------------------------------------------------- +// Equality +//--------------------------------------------------------------------------- + +/** + * @brief Test if two strings are equal. + * @param string_1 + * @param string_2 + * @return bool + */ +bool furi_string_equal(const FuriString* string_1, const FuriString* string_2); + +/** + * @brief Test if the string is equal to the C string. + * @param string_1 + * @param cstring_2 + * @return bool + */ +bool furi_string_equal_str(const FuriString* string_1, const char cstring_2[]); + +//--------------------------------------------------------------------------- +// Replace +//--------------------------------------------------------------------------- + +/** + * @brief Replace in the string the sub-string at position 'pos' for 'len' bytes into the C string 'replace'. + * @param string + * @param pos + * @param len + * @param replace + */ +void furi_string_replace_at(FuriString* string, size_t pos, size_t len, const char replace[]); + +/** + * @brief Replace a string 'needle' to string 'replace' in a string from 'start' position. + * By default, start is zero. + * Return STRING_FAILURE if 'needle' not found or replace position. + * @param string + * @param needle + * @param replace + * @param start + * @return size_t + */ +size_t + furi_string_replace(FuriString* string, FuriString* needle, FuriString* replace, size_t start); + +/** + * @brief Replace a C string 'needle' to C string 'replace' in a string from 'start' position. + * By default, start is zero. + * Return STRING_FAILURE if 'needle' not found or replace position. + * @param string + * @param needle + * @param replace + * @param start + * @return size_t + */ +size_t furi_string_replace_str( + FuriString* string, + const char needle[], + const char replace[], + size_t start); + +/** + * @brief Replace all occurrences of 'needle' string into 'replace' string. + * @param string + * @param needle + * @param replace + */ +void furi_string_replace_all( + FuriString* string, + const FuriString* needle, + const FuriString* replace); + +/** + * @brief Replace all occurrences of 'needle' C string into 'replace' C string. + * @param string + * @param needle + * @param replace + */ +void furi_string_replace_all_str(FuriString* string, const char needle[], const char replace[]); + +//--------------------------------------------------------------------------- +// Start / End tests +//--------------------------------------------------------------------------- + +/** + * @brief Test if the string starts with the given string. + * @param string + * @param start + * @return bool + */ +bool furi_string_start_with(const FuriString* string, const FuriString* start); + +/** + * @brief Test if the string starts with the given C string. + * @param string + * @param start + * @return bool + */ +bool furi_string_start_with_str(const FuriString* string, const char start[]); + +/** + * @brief Test if the string ends with the given string. + * @param string + * @param end + * @return bool + */ +bool furi_string_end_with(const FuriString* string, const FuriString* end); + +/** + * @brief Test if the string ends with the given C string. + * @param string + * @param end + * @return bool + */ +bool furi_string_end_with_str(const FuriString* string, const char end[]); + +//--------------------------------------------------------------------------- +// Trim +//--------------------------------------------------------------------------- + +/** + * @brief Trim the string left to the first 'index' bytes. + * @param string + * @param index + */ +void furi_string_left(FuriString* string, size_t index); + +/** + * @brief Trim the string right from the 'index' position to the last position. + * @param string + * @param index + */ +void furi_string_right(FuriString* string, size_t index); + +/** + * @brief Trim the string from position index to size bytes. + * See also furi_string_set_n. + * @param string + * @param index + * @param size + */ +void furi_string_mid(FuriString* string, size_t index, size_t size); + +/** + * @brief Trim a string from the given set of characters (default is " \n\r\t"). + * @param string + * @param chars + */ +void furi_string_trim(FuriString* string, const char chars[]); + +//--------------------------------------------------------------------------- +// UTF8 +//--------------------------------------------------------------------------- + +/** + * @brief An unicode value. + */ +typedef unsigned int FuriStringUnicodeValue; + +/** + * @brief Compute the length in UTF8 characters in the string. + * @param string + * @return size_t + */ +size_t furi_string_utf8_length(FuriString* string); + +/** + * @brief Push unicode into string, encoding it in UTF8. + * @param string + * @param unicode + */ +void furi_string_utf8_push(FuriString* string, FuriStringUnicodeValue unicode); + +/** + * @brief State of the UTF8 decoding machine state. + */ +typedef enum { + FuriStringUTF8StateStarting, + FuriStringUTF8StateDecoding1, + FuriStringUTF8StateDecoding2, + FuriStringUTF8StateDecoding3, + FuriStringUTF8StateError +} FuriStringUTF8State; + +/** + * @brief Main generic UTF8 decoder. + * It takes a character, and the previous state and the previous value of the unicode value. + * It updates the state and the decoded unicode value. + * A decoded unicode encoded value is valid only when the state is FuriStringUTF8StateStarting. + * @param c + * @param state + * @param unicode + */ +void furi_string_utf8_decode(char c, FuriStringUTF8State* state, FuriStringUnicodeValue* unicode); + +//--------------------------------------------------------------------------- +// Lasciate ogne speranza, voi ch’entrate +//--------------------------------------------------------------------------- + +/** + * + * Select either the string function or the str function depending on + * the b operand to the function. + * func1 is the string function / func2 is the str function. + */ + +/** + * @brief Select for 1 argument + */ +#define FURI_STRING_SELECT1(func1, func2, a) \ + _Generic((a), char* : func2, const char* : func2, FuriString* : func1, const FuriString* : func1)(a) + +/** + * @brief Select for 2 arguments + */ +#define FURI_STRING_SELECT2(func1, func2, a, b) \ + _Generic((b), char* : func2, const char* : func2, FuriString* : func1, const FuriString* : func1)(a, b) + +/** + * @brief Select for 3 arguments + */ +#define FURI_STRING_SELECT3(func1, func2, a, b, c) \ + _Generic((b), char* : func2, const char* : func2, FuriString* : func1, const FuriString* : func1)(a, b, c) + +/** + * @brief Select for 4 arguments + */ +#define FURI_STRING_SELECT4(func1, func2, a, b, c, d) \ + _Generic((b), char* : func2, const char* : func2, FuriString* : func1, const FuriString* : func1)(a, b, c, d) + +/** + * @brief Allocate new FuriString and set it content to string (or C string). + * ([c]string) + */ +#define furi_string_alloc_set(a) \ + FURI_STRING_SELECT1(furi_string_alloc_set, furi_string_alloc_set_str, a) + +/** + * @brief Set the string content to string (or C string). + * (string, [c]string) + */ +#define furi_string_set(a, b) FURI_STRING_SELECT2(furi_string_set, furi_string_set_str, a, b) + +/** + * @brief Compare string with string (or C string) and return the sort order. + * Note: doesn't work with UTF-8 strings. + * (string, [c]string) + */ +#define furi_string_cmp(a, b) FURI_STRING_SELECT2(furi_string_cmp, furi_string_cmp_str, a, b) + +/** + * @brief Compare string with string (or C string) (case insensitive according to the current locale) and return the sort order. + * Note: doesn't work with UTF-8 strings. + * (string, [c]string) + */ +#define furi_string_cmpi(a, b) FURI_STRING_SELECT2(furi_string_cmpi, furi_string_cmpi_str, a, b) + +/** + * @brief Test if the string is equal to the string (or C string). + * (string, [c]string) + */ +#define furi_string_equal(a, b) FURI_STRING_SELECT2(furi_string_equal, furi_string_equal_str, a, b) + +/** + * @brief Replace all occurrences of string into string (or C string to another C string) in a string. + * (string, [c]string, [c]string) + */ +#define furi_string_replace_all(a, b, c) \ + FURI_STRING_SELECT3(furi_string_replace_all, furi_string_replace_all_str, a, b, c) + +/** + * @brief Search for a string (or C string) in a string + * (string, [c]string[, start=0]) + */ +#define furi_string_search(v, ...) \ + M_APPLY( \ + FURI_STRING_SELECT3, \ + furi_string_search, \ + furi_string_search_str, \ + v, \ + M_IF_DEFAULT1(0, __VA_ARGS__)) + +/** + * @brief Search for a C string in a string + * (string, cstring[, start=0]) + */ +#define furi_string_search_str(v, ...) \ + M_APPLY(furi_string_search_str, v, M_IF_DEFAULT1(0, __VA_ARGS__)) + +/** + * @brief Test if the string starts with the given string (or C string). + * (string, [c]string) + */ +#define furi_string_start_with(a, b) \ + FURI_STRING_SELECT2(furi_string_start_with, furi_string_start_with_str, a, b) + +/** + * @brief Test if the string ends with the given string (or C string). + * (string, [c]string) + */ +#define furi_string_end_with(a, b) \ + FURI_STRING_SELECT2(furi_string_end_with, furi_string_end_with_str, a, b) + +/** + * @brief Append a string (or C string) to the string. + * (string, [c]string) + */ +#define furi_string_cat(a, b) FURI_STRING_SELECT2(furi_string_cat, furi_string_cat_str, a, b) + +/** + * @brief Trim a string from the given set of characters (default is " \n\r\t"). + * (string[, set=" \n\r\t"]) + */ +#define furi_string_trim(...) M_APPLY(furi_string_trim, M_IF_DEFAULT1(" \n\r\t", __VA_ARGS__)) + +/** + * @brief Search for a character in a string. + * (string, character[, start=0]) + */ +#define furi_string_search_char(v, ...) \ + M_APPLY(furi_string_search_char, v, M_IF_DEFAULT1(0, __VA_ARGS__)) + +/** + * @brief Reverse Search for a character in a string. + * (string, character[, start=0]) + */ +#define furi_string_search_rchar(v, ...) \ + M_APPLY(furi_string_search_rchar, v, M_IF_DEFAULT1(0, __VA_ARGS__)) + +/** + * @brief Replace a string to another string (or C string to another C string) in a string. + * (string, [c]string, [c]string[, start=0]) + */ +#define furi_string_replace(a, b, ...) \ + M_APPLY( \ + FURI_STRING_SELECT4, \ + furi_string_replace, \ + furi_string_replace_str, \ + a, \ + b, \ + M_IF_DEFAULT1(0, __VA_ARGS__)) + +/** + * @brief Replace a C string to another C string in a string. + * (string, cstring, cstring[, start=0]) + */ +#define furi_string_replace_str(a, b, ...) \ + M_APPLY(furi_string_replace_str, a, b, M_IF_DEFAULT1(0, __VA_ARGS__)) + +/** + * @brief INIT OPLIST for FuriString. + */ +#define F_STR_INIT(a) ((a) = furi_string_alloc()) + +/** + * @brief INIT SET OPLIST for FuriString. + */ +#define F_STR_INIT_SET(a, b) ((a) = furi_string_alloc_set(b)) + +/** + * @brief OPLIST for FuriString. + */ +#define FURI_STRING_OPLIST \ + (INIT(F_STR_INIT), \ + INIT_SET(F_STR_INIT_SET), \ + SET(furi_string_set), \ + INIT_MOVE(furi_string_alloc_move), \ + MOVE(furi_string_move), \ + SWAP(furi_string_swap), \ + RESET(furi_string_reset), \ + EMPTY_P(furi_string_empty), \ + CLEAR(furi_string_free), \ + HASH(furi_string_hash), \ + EQUAL(furi_string_equal), \ + CMP(furi_string_cmp), \ + TYPE(FuriString*)) + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/furi/core/thread.c b/furi/core/thread.c index d06fa8b3679..3ebca807efe 100644 --- a/furi/core/thread.c +++ b/furi/core/thread.c @@ -5,10 +5,10 @@ #include "check.h" #include "common_defines.h" #include "mutex.h" +#include "string.h" #include #include "log.h" -#include #include #include @@ -18,7 +18,7 @@ typedef struct FuriThreadStdout FuriThreadStdout; struct FuriThreadStdout { FuriThreadStdoutWriteCallback write_callback; - string_t buffer; + FuriString* buffer; }; struct FuriThread { @@ -109,7 +109,7 @@ static void furi_thread_body(void* context) { FuriThread* furi_thread_alloc() { FuriThread* thread = malloc(sizeof(FuriThread)); - string_init(thread->output.buffer); + thread->output.buffer = furi_string_alloc(); thread->is_service = false; return thread; } @@ -119,7 +119,7 @@ void furi_thread_free(FuriThread* thread) { furi_assert(thread->state == FuriThreadStateStopped); if(thread->name) free((void*)thread->name); - string_clear(thread->output.buffer); + furi_string_free(thread->output.buffer); free(thread); } @@ -485,11 +485,11 @@ static size_t __furi_thread_stdout_write(FuriThread* thread, const char* data, s } static int32_t __furi_thread_stdout_flush(FuriThread* thread) { - string_ptr buffer = thread->output.buffer; - size_t size = string_size(buffer); + FuriString* buffer = thread->output.buffer; + size_t size = furi_string_size(buffer); if(size > 0) { - __furi_thread_stdout_write(thread, string_get_cstr(buffer), size); - string_reset(buffer); + __furi_thread_stdout_write(thread, furi_string_get_cstr(buffer), size); + furi_string_reset(buffer); } return 0; } @@ -516,7 +516,7 @@ size_t furi_thread_stdout_write(const char* data, size_t size) { } else { // string_cat doesn't work here because we need to write the exact size data for(size_t i = 0; i < size; i++) { - string_push_back(thread->output.buffer, data[i]); + furi_string_push_back(thread->output.buffer, data[i]); if(data[i] == '\n') { __furi_thread_stdout_flush(thread); } diff --git a/furi/furi.h b/furi/furi.h index 68914b502fd..306c9b949eb 100644 --- a/furi/furi.h +++ b/furi/furi.h @@ -17,6 +17,7 @@ #include #include #include +#include #include diff --git a/lib/flipper_application/elf/elf_file.c b/lib/flipper_application/elf/elf_file.c index e2616549558..b66e27d9ac7 100644 --- a/lib/flipper_application/elf/elf_file.c +++ b/lib/flipper_application/elf/elf_file.c @@ -61,7 +61,7 @@ static void elf_file_put_section(ELFFile* elf, const char* name, ELFSection* sec ELFSectionDict_set_at(elf->sections, strdup(name), *section); } -static bool elf_read_string_from_offset(ELFFile* elf, off_t offset, string_t name) { +static bool elf_read_string_from_offset(ELFFile* elf, off_t offset, FuriString* name) { bool result = false; off_t old = storage_file_tell(elf->fd); @@ -74,7 +74,7 @@ static bool elf_read_string_from_offset(ELFFile* elf, off_t offset, string_t nam while(true) { uint16_t read = storage_file_read(elf->fd, buffer, ELF_NAME_BUFFER_LEN); - string_cat_str(name, buffer); + furi_string_cat(name, buffer); if(strlen(buffer) < ELF_NAME_BUFFER_LEN) { result = true; break; @@ -89,11 +89,11 @@ static bool elf_read_string_from_offset(ELFFile* elf, off_t offset, string_t nam return result; } -static bool elf_read_section_name(ELFFile* elf, off_t offset, string_t name) { +static bool elf_read_section_name(ELFFile* elf, off_t offset, FuriString* name) { return elf_read_string_from_offset(elf, elf->section_table_strings + offset, name); } -static bool elf_read_symbol_name(ELFFile* elf, off_t offset, string_t name) { +static bool elf_read_symbol_name(ELFFile* elf, off_t offset, FuriString* name) { return elf_read_string_from_offset(elf, elf->symbol_table_strings + offset, name); } @@ -103,8 +103,11 @@ static bool elf_read_section_header(ELFFile* elf, size_t section_idx, Elf32_Shdr storage_file_read(elf->fd, section_header, sizeof(Elf32_Shdr)) == sizeof(Elf32_Shdr); } -static bool - elf_read_section(ELFFile* elf, size_t section_idx, Elf32_Shdr* section_header, string_t name) { +static bool elf_read_section( + ELFFile* elf, + size_t section_idx, + Elf32_Shdr* section_header, + FuriString* name) { if(!elf_read_section_header(elf, section_idx, section_header)) { return false; } @@ -116,7 +119,7 @@ static bool return true; } -static bool elf_read_symbol(ELFFile* elf, int n, Elf32_Sym* sym, string_t name) { +static bool elf_read_symbol(ELFFile* elf, int n, Elf32_Sym* sym, FuriString* name) { bool success = false; off_t old = storage_file_tell(elf->fd); off_t pos = elf->symbol_table + n * sizeof(Elf32_Sym); @@ -276,19 +279,17 @@ static void elf_relocate_mov(Elf32_Addr relAddr, int type, Elf32_Addr symAddr) { int32_t addend = (imm4 << 12) | (i << 11) | (imm3 << 8) | imm8; /* imm16 */ uint32_t addr = (symAddr + addend); - if (type == R_ARM_THM_MOVT_ABS) { + if(type == R_ARM_THM_MOVT_ABS) { addr >>= 16; /* upper 16 bits */ } else { addr &= 0x0000FFFF; /* lower 16 bits */ } /* Re-encode */ - ((uint16_t*)relAddr)[0] = (upper_insn & 0xFBF0) - | (((addr >> 11) & 1) << 10) /* i */ - | ((addr >> 12) & 0x000F); /* imm4 */ - ((uint16_t*)relAddr)[1] = (lower_insn & 0x8F00) - | (((addr >> 8) & 0x7) << 12) /* imm3 */ - | (addr & 0x00FF); /* imm8 */ + ((uint16_t*)relAddr)[0] = (upper_insn & 0xFBF0) | (((addr >> 11) & 1) << 10) /* i */ + | ((addr >> 12) & 0x000F); /* imm4 */ + ((uint16_t*)relAddr)[1] = (lower_insn & 0x8F00) | (((addr >> 8) & 0x7) << 12) /* imm3 */ + | (addr & 0x00FF); /* imm8 */ } static bool elf_relocate_symbol(ELFFile* elf, Elf32_Addr relAddr, int type, Elf32_Addr symAddr) { @@ -307,7 +308,10 @@ static bool elf_relocate_symbol(ELFFile* elf, Elf32_Addr relAddr, int type, Elf3 case R_ARM_THM_MOVW_ABS_NC: case R_ARM_THM_MOVT_ABS: elf_relocate_mov(relAddr, type, symAddr); - FURI_LOG_D(TAG, " R_ARM_THM_MOVW_ABS_NC/MOVT_ABS relocated is 0x%08X", (unsigned int)*((uint32_t*)relAddr)); + FURI_LOG_D( + TAG, + " R_ARM_THM_MOVW_ABS_NC/MOVT_ABS relocated is 0x%08X", + (unsigned int)*((uint32_t*)relAddr)); break; default: FURI_LOG_E(TAG, " Undefined relocation %d", type); @@ -325,8 +329,8 @@ static bool elf_relocate(ELFFile* elf, Elf32_Shdr* h, ELFSection* s) { FURI_LOG_D(TAG, " Offset Info Type Name"); int relocate_result = true; - string_t symbol_name; - string_init(symbol_name); + FuriString* symbol_name; + symbol_name = furi_string_alloc(); for(relCount = 0; relCount < relEntries; relCount++) { if(relCount % RESOLVER_THREAD_YIELD_STEP == 0) { @@ -336,7 +340,7 @@ static bool elf_relocate(ELFFile* elf, Elf32_Shdr* h, ELFSection* s) { if(storage_file_read(elf->fd, &rel, sizeof(Elf32_Rel)) != sizeof(Elf32_Rel)) { FURI_LOG_E(TAG, " reloc read fail"); - string_clear(symbol_name); + furi_string_free(symbol_name); return false; } @@ -348,10 +352,10 @@ static bool elf_relocate(ELFFile* elf, Elf32_Shdr* h, ELFSection* s) { if(!address_cache_get(elf->relocation_cache, symEntry, &symAddr)) { Elf32_Sym sym; - string_reset(symbol_name); + furi_string_reset(symbol_name); if(!elf_read_symbol(elf, symEntry, &sym, symbol_name)) { FURI_LOG_E(TAG, " symbol read fail"); - string_clear(symbol_name); + furi_string_free(symbol_name); return false; } @@ -361,9 +365,9 @@ static bool elf_relocate(ELFFile* elf, Elf32_Shdr* h, ELFSection* s) { (unsigned int)rel.r_offset, (unsigned int)rel.r_info, elf_reloc_type_to_str(relType), - string_get_cstr(symbol_name)); + furi_string_get_cstr(symbol_name)); - symAddr = elf_address_of(elf, &sym, string_get_cstr(symbol_name)); + symAddr = elf_address_of(elf, &sym, furi_string_get_cstr(symbol_name)); address_cache_put(elf->relocation_cache, symEntry, symAddr); } @@ -377,11 +381,11 @@ static bool elf_relocate(ELFFile* elf, Elf32_Shdr* h, ELFSection* s) { relocate_result = false; } } else { - FURI_LOG_E(TAG, " No symbol address of %s", string_get_cstr(symbol_name)); + FURI_LOG_E(TAG, " No symbol address of %s", furi_string_get_cstr(symbol_name)); relocate_result = false; } } - string_clear(symbol_name); + furi_string_free(symbol_name); return relocate_result; } else { @@ -445,9 +449,9 @@ static SectionType elf_preload_section( ELFFile* elf, size_t section_idx, Elf32_Shdr* section_header, - string_t name_string, + FuriString* name_string, FlipperApplicationManifest* manifest) { - const char* name = string_get_cstr(name_string); + const char* name = furi_string_get_cstr(name_string); const struct { const char* prefix; @@ -670,19 +674,19 @@ bool elf_file_open(ELFFile* elf, const char* path) { bool elf_file_load_manifest(ELFFile* elf, FlipperApplicationManifest* manifest) { bool result = false; - string_t name; - string_init(name); + FuriString* name; + name = furi_string_alloc(); FURI_LOG_D(TAG, "Looking for manifest section"); for(size_t section_idx = 1; section_idx < elf->sections_count; section_idx++) { Elf32_Shdr section_header; - string_reset(name); + furi_string_reset(name); if(!elf_read_section(elf, section_idx, §ion_header, name)) { break; } - if(string_cmp(name, ".fapmeta") == 0) { + if(furi_string_cmp(name, ".fapmeta") == 0) { if(elf_load_metadata(elf, §ion_header, manifest)) { FURI_LOG_D(TAG, "Load manifest done"); result = true; @@ -693,26 +697,27 @@ bool elf_file_load_manifest(ELFFile* elf, FlipperApplicationManifest* manifest) } } - string_clear(name); + furi_string_free(name); return result; } bool elf_file_load_section_table(ELFFile* elf, FlipperApplicationManifest* manifest) { SectionType loaded_sections = SectionTypeERROR; - string_t name; - string_init(name); + FuriString* name; + name = furi_string_alloc(); FURI_LOG_D(TAG, "Scan ELF indexs..."); for(size_t section_idx = 1; section_idx < elf->sections_count; section_idx++) { Elf32_Shdr section_header; - string_reset(name); + furi_string_reset(name); if(!elf_read_section(elf, section_idx, §ion_header, name)) { loaded_sections = SectionTypeERROR; break; } - FURI_LOG_D(TAG, "Preloading data for section #%d %s", section_idx, string_get_cstr(name)); + FURI_LOG_D( + TAG, "Preloading data for section #%d %s", section_idx, furi_string_get_cstr(name)); SectionType section_type = elf_preload_section(elf, section_idx, §ion_header, name, manifest); loaded_sections |= section_type; @@ -723,7 +728,7 @@ bool elf_file_load_section_table(ELFFile* elf, FlipperApplicationManifest* manif } } - string_clear(name); + furi_string_free(name); FURI_LOG_D(TAG, "Load symbols done"); return IS_FLAGS_SET(loaded_sections, SectionTypeValid); diff --git a/lib/flipper_format/flipper_format.c b/lib/flipper_format/flipper_format.c index 620051276f4..292dab5f1b4 100644 --- a/lib/flipper_format/flipper_format.c +++ b/lib/flipper_format/flipper_format.c @@ -137,7 +137,7 @@ bool flipper_format_key_exist(FlipperFormat* flipper_format, const char* key) { bool flipper_format_read_header( FlipperFormat* flipper_format, - string_t filetype, + FuriString* filetype, uint32_t* version) { furi_assert(flipper_format); return flipper_format_read_string(flipper_format, flipper_format_filetype_key, filetype) && @@ -146,10 +146,11 @@ bool flipper_format_read_header( bool flipper_format_write_header( FlipperFormat* flipper_format, - string_t filetype, + FuriString* filetype, const uint32_t version) { furi_assert(flipper_format); - return flipper_format_write_header_cstr(flipper_format, string_get_cstr(filetype), version); + return flipper_format_write_header_cstr( + flipper_format, furi_string_get_cstr(filetype), version); } bool flipper_format_write_header_cstr( @@ -171,18 +172,18 @@ bool flipper_format_get_value_count( flipper_format->stream, key, count, flipper_format->strict_mode); } -bool flipper_format_read_string(FlipperFormat* flipper_format, const char* key, string_t data) { +bool flipper_format_read_string(FlipperFormat* flipper_format, const char* key, FuriString* data) { furi_assert(flipper_format); return flipper_format_stream_read_value_line( flipper_format->stream, key, FlipperStreamValueStr, data, 1, flipper_format->strict_mode); } -bool flipper_format_write_string(FlipperFormat* flipper_format, const char* key, string_t data) { +bool flipper_format_write_string(FlipperFormat* flipper_format, const char* key, FuriString* data) { furi_assert(flipper_format); FlipperStreamWriteData write_data = { .key = key, .type = FlipperStreamValueStr, - .data = string_get_cstr(data), + .data = furi_string_get_cstr(data), .data_size = 1, }; bool result = flipper_format_stream_write_value_line(flipper_format->stream, &write_data); @@ -386,9 +387,9 @@ bool flipper_format_write_hex( return result; } -bool flipper_format_write_comment(FlipperFormat* flipper_format, string_t data) { +bool flipper_format_write_comment(FlipperFormat* flipper_format, FuriString* data) { furi_assert(flipper_format); - return flipper_format_write_comment_cstr(flipper_format, string_get_cstr(data)); + return flipper_format_write_comment_cstr(flipper_format, furi_string_get_cstr(data)); } bool flipper_format_write_comment_cstr(FlipperFormat* flipper_format, const char* data) { @@ -409,12 +410,12 @@ bool flipper_format_delete_key(FlipperFormat* flipper_format, const char* key) { return result; } -bool flipper_format_update_string(FlipperFormat* flipper_format, const char* key, string_t data) { +bool flipper_format_update_string(FlipperFormat* flipper_format, const char* key, FuriString* data) { furi_assert(flipper_format); FlipperStreamWriteData write_data = { .key = key, .type = FlipperStreamValueStr, - .data = string_get_cstr(data), + .data = furi_string_get_cstr(data), .data_size = 1, }; bool result = flipper_format_stream_delete_key_and_write( @@ -522,7 +523,7 @@ bool flipper_format_update_hex( bool flipper_format_insert_or_update_string( FlipperFormat* flipper_format, const char* key, - string_t data) { + FuriString* data) { bool result = false; if(!flipper_format_key_exist(flipper_format, key)) { diff --git a/lib/flipper_format/flipper_format.h b/lib/flipper_format/flipper_format.h index 9e82bb31100..743918e3bc0 100644 --- a/lib/flipper_format/flipper_format.h +++ b/lib/flipper_format/flipper_format.h @@ -70,13 +70,13 @@ * * do { * uint32_t version = 1; - * string_t file_type; - * string_t string_value; + * FuriString* file_type; + * FuriString* string_value; * uint32_t uint32_value = 1; * uint16_t array_size = 4; * uint8_t* array[array_size] = {0}; - * string_init(file_type); - * string_init(string_value); + * file_type = furi_string_alloc(); + * string_value = furi_string_alloc(); * * if(!flipper_format_file_open_existing(file, EXT_PATH("flipper_format_test"))) break; * if(!flipper_format_read_header(file, file_type, &version)) break; @@ -94,7 +94,6 @@ #pragma once #include -#include #include #ifdef __cplusplus @@ -227,7 +226,7 @@ bool flipper_format_key_exist(FlipperFormat* flipper_format, const char* key); */ bool flipper_format_read_header( FlipperFormat* flipper_format, - string_t filetype, + FuriString* filetype, uint32_t* version); /** @@ -239,7 +238,7 @@ bool flipper_format_read_header( */ bool flipper_format_write_header( FlipperFormat* flipper_format, - string_t filetype, + FuriString* filetype, const uint32_t version); /** @@ -273,7 +272,7 @@ bool flipper_format_get_value_count( * @param data Value * @return True on success */ -bool flipper_format_read_string(FlipperFormat* flipper_format, const char* key, string_t data); +bool flipper_format_read_string(FlipperFormat* flipper_format, const char* key, FuriString* data); /** * Write key and string @@ -282,7 +281,7 @@ bool flipper_format_read_string(FlipperFormat* flipper_format, const char* key, * @param data Value * @return True on success */ -bool flipper_format_write_string(FlipperFormat* flipper_format, const char* key, string_t data); +bool flipper_format_write_string(FlipperFormat* flipper_format, const char* key, FuriString* data); /** * Write key and string. Plain C string version. @@ -470,7 +469,7 @@ bool flipper_format_write_hex( * @param data Comment text * @return True on success */ -bool flipper_format_write_comment(FlipperFormat* flipper_format, string_t data); +bool flipper_format_write_comment(FlipperFormat* flipper_format, FuriString* data); /** * Write comment. Plain C string version. @@ -495,7 +494,7 @@ bool flipper_format_delete_key(FlipperFormat* flipper_format, const char* key); * @param data Value * @return True on success */ -bool flipper_format_update_string(FlipperFormat* flipper_format, const char* key, string_t data); +bool flipper_format_update_string(FlipperFormat* flipper_format, const char* key, FuriString* data); /** * Updates the value of the first matching key to a string value. Plain C version. Sets the RW pointer to a position at the end of inserted data. @@ -585,7 +584,7 @@ bool flipper_format_update_hex( bool flipper_format_insert_or_update_string( FlipperFormat* flipper_format, const char* key, - string_t data); + FuriString* data); /** * Updates the value of the first matching key to a string value, or adds the key and value if the key did not exist. diff --git a/lib/flipper_format/flipper_format_stream.c b/lib/flipper_format/flipper_format_stream.c index e4b7b30030b..ecc68d4ede8 100644 --- a/lib/flipper_format/flipper_format_stream.c +++ b/lib/flipper_format/flipper_format_stream.c @@ -26,8 +26,8 @@ bool flipper_format_stream_write_eol(Stream* stream) { return flipper_format_stream_write(stream, &flipper_format_eoln, 1); } -static bool flipper_format_stream_read_valid_key(Stream* stream, string_t key) { - string_reset(key); +static bool flipper_format_stream_read_valid_key(Stream* stream, FuriString* key) { + furi_string_reset(key); const size_t buffer_size = 32; uint8_t buffer[buffer_size]; @@ -44,7 +44,7 @@ static bool flipper_format_stream_read_valid_key(Stream* stream, string_t key) { uint8_t data = buffer[i]; if(data == flipper_format_eoln) { // EOL found, clean data, start accumulating data and set the new_line flag - string_reset(key); + furi_string_reset(key); accumulate = true; new_line = true; } else if(data == flipper_format_eolr) { @@ -60,7 +60,7 @@ static bool flipper_format_stream_read_valid_key(Stream* stream, string_t key) { // this can only be if we have previously found some kind of key, so // clear the data, set the flag that we no longer want to accumulate data // and reset the new_line flag - string_reset(key); + furi_string_reset(key); accumulate = false; new_line = false; } else { @@ -82,7 +82,7 @@ static bool flipper_format_stream_read_valid_key(Stream* stream, string_t key) { new_line = false; if(accumulate) { // and accumulate data if we want - string_push_back(key, data); + furi_string_push_back(key, data); } } } @@ -95,13 +95,13 @@ static bool flipper_format_stream_read_valid_key(Stream* stream, string_t key) { bool flipper_format_stream_seek_to_key(Stream* stream, const char* key, bool strict_mode) { bool found = false; - string_t read_key; + FuriString* read_key; - string_init(read_key); + read_key = furi_string_alloc(); while(!stream_eof(stream)) { if(flipper_format_stream_read_valid_key(stream, read_key)) { - if(string_cmp_str(read_key, key) == 0) { + if(furi_string_cmp_str(read_key, key) == 0) { if(!stream_seek(stream, 2, StreamOffsetFromCurrent)) break; found = true; @@ -112,13 +112,13 @@ bool flipper_format_stream_seek_to_key(Stream* stream, const char* key, bool str } } } - string_clear(read_key); + furi_string_free(read_key); return found; } -static bool flipper_format_stream_read_value(Stream* stream, string_t value, bool* last) { - string_reset(value); +static bool flipper_format_stream_read_value(Stream* stream, FuriString* value, bool* last) { + furi_string_reset(value); const size_t buffer_size = 32; uint8_t buffer[buffer_size]; bool result = false; @@ -129,7 +129,7 @@ static bool flipper_format_stream_read_value(Stream* stream, string_t value, boo if(was_read == 0) { // check EOF - if(stream_eof(stream) && string_size(value) > 0) { + if(stream_eof(stream) && furi_string_size(value) > 0) { result = true; *last = true; break; @@ -139,7 +139,7 @@ static bool flipper_format_stream_read_value(Stream* stream, string_t value, boo for(uint16_t i = 0; i < was_read; i++) { uint8_t data = buffer[i]; if(data == flipper_format_eoln) { - if(string_size(value) > 0) { + if(furi_string_size(value) > 0) { if(!stream_seek(stream, i - was_read, StreamOffsetFromCurrent)) { error = true; break; @@ -152,7 +152,7 @@ static bool flipper_format_stream_read_value(Stream* stream, string_t value, boo error = true; } } else if(data == ' ') { - if(string_size(value) > 0) { + if(furi_string_size(value) > 0) { if(!stream_seek(stream, i - was_read, StreamOffsetFromCurrent)) { error = true; break; @@ -166,7 +166,7 @@ static bool flipper_format_stream_read_value(Stream* stream, string_t value, boo } else if(data == flipper_format_eolr) { // Ignore } else { - string_push_back(value, data); + furi_string_push_back(value, data); } } @@ -176,8 +176,8 @@ static bool flipper_format_stream_read_value(Stream* stream, string_t value, boo return result; } -static bool flipper_format_stream_read_line(Stream* stream, string_t str_result) { - string_reset(str_result); +static bool flipper_format_stream_read_line(Stream* stream, FuriString* str_result) { + furi_string_reset(str_result); const size_t buffer_size = 32; uint8_t buffer[buffer_size]; @@ -201,7 +201,7 @@ static bool flipper_format_stream_read_line(Stream* stream, string_t str_result) } else if(data == flipper_format_eolr) { // Ignore } else { - string_push_back(str_result, data); + furi_string_push_back(str_result, data); } } @@ -210,7 +210,7 @@ static bool flipper_format_stream_read_line(Stream* stream, string_t str_result) } } while(true); - return string_size(str_result) != 0; + return furi_string_size(str_result) != 0; } static bool flipper_format_stream_seek_to_next_line(Stream* stream) { @@ -254,8 +254,8 @@ bool flipper_format_stream_write_value_line(Stream* stream, FlipperStreamWriteDa if(write_data->type == FlipperStreamValueIgnore) { result = true; } else { - string_t value; - string_init(value); + FuriString* value; + value = furi_string_alloc(); do { if(!flipper_format_stream_write_key(stream, write_data->key)) break; @@ -267,45 +267,45 @@ bool flipper_format_stream_write_value_line(Stream* stream, FlipperStreamWriteDa switch(write_data->type) { case FlipperStreamValueStr: { const char* data = write_data->data; - string_printf(value, "%s", data); + furi_string_printf(value, "%s", data); }; break; case FlipperStreamValueHex: { const uint8_t* data = write_data->data; - string_printf(value, "%02X", data[i]); + furi_string_printf(value, "%02X", data[i]); }; break; #ifndef FLIPPER_STREAM_LITE case FlipperStreamValueFloat: { const float* data = write_data->data; - string_printf(value, "%f", (double)data[i]); + furi_string_printf(value, "%f", (double)data[i]); }; break; #endif case FlipperStreamValueInt32: { const int32_t* data = write_data->data; - string_printf(value, "%" PRIi32, data[i]); + furi_string_printf(value, "%" PRIi32, data[i]); }; break; case FlipperStreamValueUint32: { const uint32_t* data = write_data->data; - string_printf(value, "%" PRId32, data[i]); + furi_string_printf(value, "%" PRId32, data[i]); }; break; case FlipperStreamValueHexUint64: { const uint64_t* data = write_data->data; - string_printf( + furi_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"); + furi_string_printf(value, data[i] ? "true" : "false"); }; break; default: furi_crash("Unknown FF type"); } if((size_t)(i + 1) < write_data->data_size) { - string_cat(value, " "); + furi_string_cat(value, " "); } if(!flipper_format_stream_write( - stream, string_get_cstr(value), string_size(value))) { + stream, furi_string_get_cstr(value), furi_string_size(value))) { cycle_error = true; break; } @@ -316,7 +316,7 @@ bool flipper_format_stream_write_value_line(Stream* stream, FlipperStreamWriteDa result = true; } while(false); - string_clear(value); + furi_string_free(value); } return result; @@ -335,15 +335,15 @@ bool flipper_format_stream_read_value_line( if(!flipper_format_stream_seek_to_key(stream, key, strict_mode)) break; if(type == FlipperStreamValueStr) { - string_ptr data = (string_ptr)_data; + FuriString* data = (FuriString*)_data; if(flipper_format_stream_read_line(stream, data)) { result = true; break; } } else { result = true; - string_t value; - string_init(value); + FuriString* value; + value = furi_string_alloc(); for(size_t i = 0; i < data_size; i++) { bool last = false; @@ -354,11 +354,11 @@ bool flipper_format_stream_read_value_line( switch(type) { case FlipperStreamValueHex: { uint8_t* data = _data; - if(string_size(value) >= 2) { + if(furi_string_size(value) >= 2) { // sscanf "%02X" does not work here if(hex_char_to_uint8( - string_get_char(value, 0), - string_get_char(value, 1), + furi_string_get_char(value, 0), + furi_string_get_char(value, 1), &data[i])) { scan_values = 1; } @@ -368,9 +368,9 @@ bool flipper_format_stream_read_value_line( case FlipperStreamValueFloat: { float* data = _data; // newlib-nano does not have sscanf for floats - // scan_values = sscanf(string_get_cstr(value), "%f", &data[i]); + // scan_values = sscanf(furi_string_get_cstr(value), "%f", &data[i]); char* end_char; - data[i] = strtof(string_get_cstr(value), &end_char); + data[i] = strtof(furi_string_get_cstr(value), &end_char); if(*end_char == 0) { // most likely ok scan_values = 1; @@ -379,23 +379,23 @@ bool flipper_format_stream_read_value_line( #endif case FlipperStreamValueInt32: { int32_t* data = _data; - scan_values = sscanf(string_get_cstr(value), "%" PRIi32, &data[i]); + scan_values = sscanf(furi_string_get_cstr(value), "%" PRIi32, &data[i]); }; break; case FlipperStreamValueUint32: { uint32_t* data = _data; - scan_values = sscanf(string_get_cstr(value), "%" PRId32, &data[i]); + scan_values = sscanf(furi_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])) { + if(furi_string_size(value) >= 16) { + if(hex_chars_to_uint64(furi_string_get_cstr(value), &data[i])) { scan_values = 1; } } }; break; case FlipperStreamValueBool: { bool* data = _data; - data[i] = !string_cmpi_str(value, "true"); + data[i] = !furi_string_cmpi(value, "true"); scan_values = 1; }; break; default: @@ -416,7 +416,7 @@ bool flipper_format_stream_read_value_line( } } - string_clear(value); + furi_string_free(value); } } while(false); @@ -431,8 +431,8 @@ bool flipper_format_stream_get_value_count( bool result = false; bool last = false; - string_t value; - string_init(value); + FuriString* value; + value = furi_string_alloc(); uint32_t position = stream_tell(stream); do { @@ -456,7 +456,7 @@ bool flipper_format_stream_get_value_count( result = false; } - string_clear(value); + furi_string_free(value); return result; } diff --git a/lib/flipper_format/flipper_format_stream.h b/lib/flipper_format/flipper_format_stream.h index 75eaef20e93..88b096b2233 100644 --- a/lib/flipper_format/flipper_format_stream.h +++ b/lib/flipper_format/flipper_format_stream.h @@ -2,7 +2,6 @@ #include #include #include -#include #ifdef __cplusplus extern "C" { diff --git a/lib/lfrfid/lfrfid_dict_file.c b/lib/lfrfid/lfrfid_dict_file.c index bb6af39a498..7ae84f8b687 100644 --- a/lib/lfrfid/lfrfid_dict_file.c +++ b/lib/lfrfid/lfrfid_dict_file.c @@ -143,8 +143,8 @@ ProtocolId lfrfid_dict_file_load(ProtocolDict* dict, const char* filename) { FlipperFormat* file = flipper_format_file_alloc(storage); ProtocolId result = PROTOCOL_NO; uint8_t* data = malloc(protocol_dict_get_max_data_size(dict)); - string_t str_result; - string_init(str_result); + FuriString* str_result; + str_result = furi_string_alloc(); do { if(!flipper_format_file_open_existing(file, filename)) break; @@ -152,16 +152,16 @@ ProtocolId lfrfid_dict_file_load(ProtocolDict* dict, const char* filename) { // header uint32_t version; if(!flipper_format_read_header(file, str_result, &version)) break; - if(string_cmp_str(str_result, LFRFID_DICT_FILETYPE) != 0) break; + if(furi_string_cmp_str(str_result, LFRFID_DICT_FILETYPE) != 0) break; if(version != 1) break; // type if(!flipper_format_read_string(file, "Key type", str_result)) break; ProtocolId protocol; - protocol = protocol_dict_get_protocol_by_name(dict, string_get_cstr(str_result)); + protocol = protocol_dict_get_protocol_by_name(dict, furi_string_get_cstr(str_result)); if(protocol == PROTOCOL_NO) { - protocol = lfrfid_dict_protocol_fallback(dict, string_get_cstr(str_result), file); + protocol = lfrfid_dict_protocol_fallback(dict, furi_string_get_cstr(str_result), file); if(protocol == PROTOCOL_NO) break; } else { // data @@ -174,7 +174,7 @@ ProtocolId lfrfid_dict_file_load(ProtocolDict* dict, const char* filename) { } while(false); free(data); - string_clear(str_result); + furi_string_free(str_result); flipper_format_free(file); furi_record_close(RECORD_STORAGE); diff --git a/lib/lfrfid/lfrfid_raw_worker.c b/lib/lfrfid/lfrfid_raw_worker.c index 4050a8ca8a5..b277bbd3488 100644 --- a/lib/lfrfid/lfrfid_raw_worker.c +++ b/lib/lfrfid/lfrfid_raw_worker.c @@ -40,7 +40,7 @@ typedef struct { // main worker struct LFRFIDRawWorker { - string_t file_path; + FuriString* file_path; FuriThread* thread; FuriEventFlag* events; @@ -69,14 +69,14 @@ LFRFIDRawWorker* lfrfid_raw_worker_alloc() { worker->events = furi_event_flag_alloc(NULL); - string_init(worker->file_path); + worker->file_path = furi_string_alloc(); return worker; } void lfrfid_raw_worker_free(LFRFIDRawWorker* worker) { furi_thread_free(worker->thread); furi_event_flag_free(worker->events); - string_clear(worker->file_path); + furi_string_free(worker->file_path); free(worker); } @@ -89,7 +89,7 @@ void lfrfid_raw_worker_start_read( void* context) { furi_check(furi_thread_get_state(worker->thread) == FuriThreadStateStopped); - string_set(worker->file_path, file_path); + furi_string_set(worker->file_path, file_path); worker->frequency = freq; worker->duty_cycle = duty_cycle; @@ -107,7 +107,7 @@ void lfrfid_raw_worker_start_emulate( LFRFIDWorkerEmulateRawCallback callback, void* context) { furi_check(furi_thread_get_state(worker->thread) == FuriThreadStateStopped); - string_set(worker->file_path, file_path); + furi_string_set(worker->file_path, file_path); worker->emulate_callback = callback; worker->context = context; furi_thread_set_callback(worker->thread, lfrfid_raw_emulate_worker_thread); @@ -147,7 +147,7 @@ static int32_t lfrfid_raw_read_worker_thread(void* thread_context) { Storage* storage = furi_record_open(RECORD_STORAGE); LFRFIDRawFile* file = lfrfid_raw_file_alloc(storage); - const char* filename = string_get_cstr(worker->file_path); + const char* filename = furi_string_get_cstr(worker->file_path); bool file_valid = lfrfid_raw_file_open_write(file, filename); LFRFIDRawWorkerReadData* data = malloc(sizeof(LFRFIDRawWorkerReadData)); @@ -256,7 +256,7 @@ static int32_t lfrfid_raw_emulate_worker_thread(void* thread_context) { LFRFIDRawFile* file = lfrfid_raw_file_alloc(storage); do { - file_valid = lfrfid_raw_file_open_read(file, string_get_cstr(worker->file_path)); + file_valid = lfrfid_raw_file_open_read(file, furi_string_get_cstr(worker->file_path)); if(!file_valid) break; file_valid = lfrfid_raw_file_read_header(file, &worker->frequency, &worker->duty_cycle); if(!file_valid) break; diff --git a/lib/lfrfid/lfrfid_worker_modes.c b/lib/lfrfid/lfrfid_worker_modes.c index 16936cca14c..56447057661 100644 --- a/lib/lfrfid/lfrfid_worker_modes.c +++ b/lib/lfrfid/lfrfid_worker_modes.c @@ -270,14 +270,14 @@ static LFRFIDWorkerReadState lfrfid_worker_read_internal( } if(furi_log_get_level() >= FuriLogLevelDebug) { - string_t string_info; - string_init(string_info); + FuriString* string_info; + string_info = furi_string_alloc(); for(uint8_t i = 0; i < protocol_data_size; i++) { if(i != 0) { - string_cat_printf(string_info, " "); + furi_string_cat_printf(string_info, " "); } - string_cat_printf(string_info, "%02X", protocol_data[i]); + furi_string_cat_printf(string_info, "%02X", protocol_data[i]); } FURI_LOG_D( @@ -285,8 +285,8 @@ static LFRFIDWorkerReadState lfrfid_worker_read_internal( "%s, %d, [%s]", protocol_dict_get_name(worker->protocols, protocol), last_read_count, - string_get_cstr(string_info)); - string_clear(string_info); + furi_string_get_cstr(string_info)); + furi_string_free(string_info); } protocol_dict_decoders_start(worker->protocols); diff --git a/lib/lfrfid/protocols/protocol_awid.c b/lib/lfrfid/protocols/protocol_awid.c index 7131d30dc8d..69409b3123f 100644 --- a/lib/lfrfid/protocols/protocol_awid.c +++ b/lib/lfrfid/protocols/protocol_awid.c @@ -147,7 +147,7 @@ LevelDuration protocol_awid_encoder_yield(ProtocolAwid* protocol) { return level_duration_make(level, duration); }; -void protocol_awid_render_data(ProtocolAwid* protocol, string_t result) { +void protocol_awid_render_data(ProtocolAwid* protocol, FuriString* result) { // Index map // 0 10 20 30 40 50 60 // | | | | | | | @@ -164,7 +164,7 @@ void protocol_awid_render_data(ProtocolAwid* protocol, string_t result) { uint8_t* decoded_data = protocol->data; uint8_t format_length = decoded_data[0]; - string_cat_printf(result, "Format: %d\r\n", format_length); + furi_string_cat_printf(result, "Format: %d\r\n", format_length); if(format_length == 26) { uint8_t facility; bit_lib_copy_bits(&facility, 0, 8, decoded_data, 9); @@ -172,22 +172,22 @@ void protocol_awid_render_data(ProtocolAwid* protocol, string_t result) { uint16_t card_id; bit_lib_copy_bits((uint8_t*)&card_id, 8, 8, decoded_data, 17); bit_lib_copy_bits((uint8_t*)&card_id, 0, 8, decoded_data, 25); - string_cat_printf(result, "Facility: %d\r\n", facility); - string_cat_printf(result, "Card: %d", card_id); + furi_string_cat_printf(result, "Facility: %d\r\n", facility); + furi_string_cat_printf(result, "Card: %d", card_id); } else { // print 66 bits as hex - string_cat_printf(result, "Data: "); + furi_string_cat_printf(result, "Data: "); for(size_t i = 0; i < AWID_DECODED_DATA_SIZE; i++) { - string_cat_printf(result, "%02X", decoded_data[i]); + furi_string_cat_printf(result, "%02X", decoded_data[i]); } } }; -void protocol_awid_render_brief_data(ProtocolAwid* protocol, string_t result) { +void protocol_awid_render_brief_data(ProtocolAwid* protocol, FuriString* result) { uint8_t* decoded_data = protocol->data; uint8_t format_length = decoded_data[0]; - string_cat_printf(result, "Format: %d\r\n", format_length); + furi_string_cat_printf(result, "Format: %d\r\n", format_length); if(format_length == 26) { uint8_t facility; bit_lib_copy_bits(&facility, 0, 8, decoded_data, 9); @@ -195,9 +195,9 @@ void protocol_awid_render_brief_data(ProtocolAwid* protocol, string_t result) { uint16_t card_id; bit_lib_copy_bits((uint8_t*)&card_id, 8, 8, decoded_data, 17); bit_lib_copy_bits((uint8_t*)&card_id, 0, 8, decoded_data, 25); - string_cat_printf(result, "ID: %03u,%05u", facility, card_id); + furi_string_cat_printf(result, "ID: %03u,%05u", facility, card_id); } else { - string_cat_printf(result, "Data: unknown"); + furi_string_cat_printf(result, "Data: unknown"); } }; diff --git a/lib/lfrfid/protocols/protocol_em4100.c b/lib/lfrfid/protocols/protocol_em4100.c index 6959f753b25..4b720dffdab 100644 --- a/lib/lfrfid/protocols/protocol_em4100.c +++ b/lib/lfrfid/protocols/protocol_em4100.c @@ -251,10 +251,10 @@ bool protocol_em4100_write_data(ProtocolEM4100* protocol, void* data) { // Correct protocol data by redecoding protocol_em4100_encoder_start(protocol); em4100_decode( - (uint8_t*)&protocol->encoded_data, - sizeof(EM4100DecodedData), - protocol->data, - EM4100_DECODED_DATA_SIZE); + (uint8_t*)&protocol->encoded_data, + sizeof(EM4100DecodedData), + protocol->data, + EM4100_DECODED_DATA_SIZE); protocol_em4100_encoder_start(protocol); @@ -270,9 +270,10 @@ bool protocol_em4100_write_data(ProtocolEM4100* protocol, void* data) { return result; }; -void protocol_em4100_render_data(ProtocolEM4100* protocol, string_t result) { +void protocol_em4100_render_data(ProtocolEM4100* protocol, FuriString* result) { uint8_t* data = protocol->data; - string_printf(result, "FC: %03u, Card: %05u", data[2], (uint16_t)((data[3] << 8) | (data[4]))); + furi_string_printf( + result, "FC: %03u, Card: %05u", data[2], (uint16_t)((data[3] << 8) | (data[4]))); }; const ProtocolBase protocol_em4100 = { diff --git a/lib/lfrfid/protocols/protocol_fdx_a.c b/lib/lfrfid/protocols/protocol_fdx_a.c index 554b9071e3b..058dc553e6a 100644 --- a/lib/lfrfid/protocols/protocol_fdx_a.c +++ b/lib/lfrfid/protocols/protocol_fdx_a.c @@ -196,7 +196,7 @@ bool protocol_fdx_a_write_data(ProtocolFDXA* protocol, void* data) { return result; }; -void protocol_fdx_a_render_data(ProtocolFDXA* protocol, string_t result) { +void protocol_fdx_a_render_data(ProtocolFDXA* protocol, FuriString* result) { uint8_t data[FDXA_DECODED_DATA_SIZE]; memcpy(data, protocol->data, FDXA_DECODED_DATA_SIZE); @@ -206,7 +206,7 @@ void protocol_fdx_a_render_data(ProtocolFDXA* protocol, string_t result) { data[i] &= 0x7F; } - string_printf( + furi_string_printf( result, "ID: %02X%02X%02X%02X%02X\r\n" "Parity: %s", diff --git a/lib/lfrfid/protocols/protocol_fdx_b.c b/lib/lfrfid/protocols/protocol_fdx_b.c index f42b4ed9343..855356f2afd 100644 --- a/lib/lfrfid/protocols/protocol_fdx_b.c +++ b/lib/lfrfid/protocols/protocol_fdx_b.c @@ -273,7 +273,7 @@ static bool protocol_fdx_b_get_temp(const uint8_t* data, float* temp) { } } -void protocol_fdx_b_render_data(ProtocolFDXB* protocol, string_t result) { +void protocol_fdx_b_render_data(ProtocolFDXB* protocol, FuriString* result) { // 38 bits of national code uint64_t national_code = protocol_fdx_b_get_national_code(protocol->data); @@ -287,19 +287,19 @@ void protocol_fdx_b_render_data(ProtocolFDXB* protocol, string_t result) { uint8_t replacement_number = bit_lib_get_bits(protocol->data, 60, 3); bool animal_flag = bit_lib_get_bit(protocol->data, 63); - string_printf(result, "ID: %03u-%012llu\r\n", country_code, national_code); - string_cat_printf(result, "Animal: %s, ", animal_flag ? "Yes" : "No"); + furi_string_printf(result, "ID: %03u-%012llu\r\n", country_code, national_code); + furi_string_cat_printf(result, "Animal: %s, ", animal_flag ? "Yes" : "No"); float temperature; if(protocol_fdx_b_get_temp(protocol->data, &temperature)) { float temperature_c = (temperature - 32) / 1.8; - string_cat_printf( + furi_string_cat_printf( result, "T: %.2fF, %.2fC\r\n", (double)temperature, (double)temperature_c); } else { - string_cat_printf(result, "T: ---\r\n"); + furi_string_cat_printf(result, "T: ---\r\n"); } - string_cat_printf( + furi_string_cat_printf( result, "Bits: %X-%X-%X-%X-%X", block_status, @@ -309,7 +309,7 @@ void protocol_fdx_b_render_data(ProtocolFDXB* protocol, string_t result) { replacement_number); }; -void protocol_fdx_b_render_brief_data(ProtocolFDXB* protocol, string_t result) { +void protocol_fdx_b_render_brief_data(ProtocolFDXB* protocol, FuriString* result) { // 38 bits of national code uint64_t national_code = protocol_fdx_b_get_national_code(protocol->data); @@ -318,15 +318,15 @@ void protocol_fdx_b_render_brief_data(ProtocolFDXB* protocol, string_t result) { bool animal_flag = bit_lib_get_bit(protocol->data, 63); - string_printf(result, "ID: %03u-%012llu\r\n", country_code, national_code); - string_cat_printf(result, "Animal: %s, ", animal_flag ? "Yes" : "No"); + furi_string_printf(result, "ID: %03u-%012llu\r\n", country_code, national_code); + furi_string_cat_printf(result, "Animal: %s, ", animal_flag ? "Yes" : "No"); float temperature; if(protocol_fdx_b_get_temp(protocol->data, &temperature)) { float temperature_c = (temperature - 32) / 1.8; - string_cat_printf(result, "T: %.2fC", (double)temperature_c); + furi_string_cat_printf(result, "T: %.2fC", (double)temperature_c); } else { - string_cat_printf(result, "T: ---"); + furi_string_cat_printf(result, "T: ---"); } }; diff --git a/lib/lfrfid/protocols/protocol_gallagher.c b/lib/lfrfid/protocols/protocol_gallagher.c index 5d8245301db..460c23a39b5 100644 --- a/lib/lfrfid/protocols/protocol_gallagher.c +++ b/lib/lfrfid/protocols/protocol_gallagher.c @@ -268,15 +268,15 @@ bool protocol_gallagher_write_data(ProtocolGallagher* protocol, void* data) { return result; }; -void protocol_gallagher_render_data(ProtocolGallagher* protocol, string_t result) { +void protocol_gallagher_render_data(ProtocolGallagher* protocol, FuriString* result) { UNUSED(protocol); uint8_t rc = bit_lib_get_bits(protocol->data, 0, 4); uint8_t il = bit_lib_get_bits(protocol->data, 4, 4); uint32_t fc = bit_lib_get_bits_32(protocol->data, 8, 24); uint32_t card_id = bit_lib_get_bits_32(protocol->data, 32, 32); - string_cat_printf(result, "Region: %u, Issue Level: %u\r\n", rc, il); - string_cat_printf(result, "FC: %u, C: %lu\r\n", fc, card_id); + furi_string_cat_printf(result, "Region: %u, Issue Level: %u\r\n", rc, il); + furi_string_cat_printf(result, "FC: %u, C: %lu\r\n", fc, card_id); }; const ProtocolBase protocol_gallagher = { diff --git a/lib/lfrfid/protocols/protocol_h10301.c b/lib/lfrfid/protocols/protocol_h10301.c index 6c50c667a3d..2d7a3e669e2 100644 --- a/lib/lfrfid/protocols/protocol_h10301.c +++ b/lib/lfrfid/protocols/protocol_h10301.c @@ -340,7 +340,7 @@ bool protocol_h10301_write_data(ProtocolH10301* protocol, void* data) { // Correct protocol data by redecoding protocol_h10301_encoder_start(protocol); protocol_h10301_decode(protocol->encoded_data, protocol->data); - + protocol_h10301_encoder_start(protocol); if(request->write_type == LFRFIDWriteTypeT5577) { @@ -355,9 +355,9 @@ bool protocol_h10301_write_data(ProtocolH10301* protocol, void* data) { return result; }; -void protocol_h10301_render_data(ProtocolH10301* protocol, string_t result) { +void protocol_h10301_render_data(ProtocolH10301* protocol, FuriString* result) { uint8_t* data = protocol->data; - string_printf( + furi_string_printf( result, "FC: %u\r\n" "Card: %u", diff --git a/lib/lfrfid/protocols/protocol_hid_ex_generic.c b/lib/lfrfid/protocols/protocol_hid_ex_generic.c index 17b75528c4f..240128cbec0 100644 --- a/lib/lfrfid/protocols/protocol_hid_ex_generic.c +++ b/lib/lfrfid/protocols/protocol_hid_ex_generic.c @@ -192,10 +192,10 @@ bool protocol_hid_ex_generic_write_data(ProtocolHIDEx* protocol, void* data) { return result; }; -void protocol_hid_ex_generic_render_data(ProtocolHIDEx* protocol, string_t result) { +void protocol_hid_ex_generic_render_data(ProtocolHIDEx* protocol, FuriString* result) { // TODO: parser and render functions UNUSED(protocol); - string_printf(result, "Generic HID Extended\r\nData: Unknown"); + furi_string_printf(result, "Generic HID Extended\r\nData: Unknown"); }; const ProtocolBase protocol_hid_ex_generic = { diff --git a/lib/lfrfid/protocols/protocol_hid_generic.c b/lib/lfrfid/protocols/protocol_hid_generic.c index da5f5b7c8ac..e07021403de 100644 --- a/lib/lfrfid/protocols/protocol_hid_generic.c +++ b/lib/lfrfid/protocols/protocol_hid_generic.c @@ -221,25 +221,29 @@ bool protocol_hid_generic_write_data(ProtocolHID* protocol, void* data) { return result; }; -static void protocol_hid_generic_string_cat_protocol_bits(ProtocolHID* protocol, uint8_t protocol_size, string_t result) { +static void protocol_hid_generic_string_cat_protocol_bits( + ProtocolHID* protocol, + uint8_t protocol_size, + FuriString* result) { // round up to the nearest nibble const uint8_t hex_character_count = (protocol_size + 3) / 4; const uint8_t protocol_bit_index = HID_DECODED_BIT_SIZE - protocol_size; for(size_t i = 0; i < hex_character_count; i++) { - uint8_t nibble = - i == 0 ? bit_lib_get_bits( - protocol->data, protocol_bit_index, protocol_size % 4 == 0 ? 4 : protocol_size % 4) : - bit_lib_get_bits(protocol->data, protocol_bit_index + i * 4, 4); - string_cat_printf(result, "%X", nibble & 0xF); + uint8_t nibble = i == 0 ? bit_lib_get_bits( + protocol->data, + protocol_bit_index, + protocol_size % 4 == 0 ? 4 : protocol_size % 4) : + bit_lib_get_bits(protocol->data, protocol_bit_index + i * 4, 4); + furi_string_cat_printf(result, "%X", nibble & 0xF); } } -void protocol_hid_generic_render_data(ProtocolHID* protocol, string_t result) { +void protocol_hid_generic_render_data(ProtocolHID* protocol, FuriString* result) { const uint8_t protocol_size = protocol_hid_generic_decode_protocol_size(protocol); if(protocol_size == HID_PROTOCOL_SIZE_UNKNOWN) { - string_printf( + furi_string_printf( result, "Generic HID Proximity\r\n" "Data: %02X%02X%02X%02X%02X%X", @@ -250,7 +254,7 @@ void protocol_hid_generic_render_data(ProtocolHID* protocol, string_t result) { protocol->data[4], protocol->data[5] >> 4); } else { - string_printf( + furi_string_printf( result, "%hhu-bit HID Proximity\r\n" "Data: ", diff --git a/lib/lfrfid/protocols/protocol_indala26.c b/lib/lfrfid/protocols/protocol_indala26.c index 136ececf8df..cafc5848948 100644 --- a/lib/lfrfid/protocols/protocol_indala26.c +++ b/lib/lfrfid/protocols/protocol_indala26.c @@ -236,7 +236,10 @@ static uint16_t get_cn(const uint8_t* data) { return cn; } -void protocol_indala26_render_data_internal(ProtocolIndala* protocol, string_t result, bool brief) { +void protocol_indala26_render_data_internal( + ProtocolIndala* protocol, + FuriString* result, + bool brief) { bool wiegand_correct = true; bool checksum_correct = true; @@ -284,7 +287,7 @@ void protocol_indala26_render_data_internal(ProtocolIndala* protocol, string_t r if(odd_parity_sum % 2 != odd_parity) wiegand_correct = false; if(brief) { - string_printf( + furi_string_printf( result, "FC: %u\r\nCard: %u, Parity:%s%s", fc, @@ -292,7 +295,7 @@ void protocol_indala26_render_data_internal(ProtocolIndala* protocol, string_t r (checksum_correct ? "+" : "-"), (wiegand_correct ? "+" : "-")); } else { - string_printf( + furi_string_printf( result, "FC: %u\r\n" "Card: %u\r\n" @@ -304,10 +307,10 @@ void protocol_indala26_render_data_internal(ProtocolIndala* protocol, string_t r (wiegand_correct ? "+" : "-")); } } -void protocol_indala26_render_data(ProtocolIndala* protocol, string_t result) { +void protocol_indala26_render_data(ProtocolIndala* protocol, FuriString* result) { protocol_indala26_render_data_internal(protocol, result, false); } -void protocol_indala26_render_brief_data(ProtocolIndala* protocol, string_t result) { +void protocol_indala26_render_brief_data(ProtocolIndala* protocol, FuriString* result) { protocol_indala26_render_data_internal(protocol, result, true); } diff --git a/lib/lfrfid/protocols/protocol_io_prox_xsf.c b/lib/lfrfid/protocols/protocol_io_prox_xsf.c index f53eac686bd..71314856840 100644 --- a/lib/lfrfid/protocols/protocol_io_prox_xsf.c +++ b/lib/lfrfid/protocols/protocol_io_prox_xsf.c @@ -232,9 +232,9 @@ LevelDuration protocol_io_prox_xsf_encoder_yield(ProtocolIOProxXSF* protocol) { return level_duration_make(level, duration); }; -void protocol_io_prox_xsf_render_data(ProtocolIOProxXSF* protocol, string_t result) { +void protocol_io_prox_xsf_render_data(ProtocolIOProxXSF* protocol, FuriString* result) { uint8_t* data = protocol->data; - string_printf( + furi_string_printf( result, "FC: %u\r\n" "VС: %u\r\n" @@ -244,9 +244,9 @@ void protocol_io_prox_xsf_render_data(ProtocolIOProxXSF* protocol, string_t resu (uint16_t)((data[2] << 8) | (data[3]))); } -void protocol_io_prox_xsf_render_brief_data(ProtocolIOProxXSF* protocol, string_t result) { +void protocol_io_prox_xsf_render_brief_data(ProtocolIOProxXSF* protocol, FuriString* result) { uint8_t* data = protocol->data; - string_printf( + furi_string_printf( result, "FC: %u, VС: %u\r\n" "Card: %u", diff --git a/lib/lfrfid/protocols/protocol_jablotron.c b/lib/lfrfid/protocols/protocol_jablotron.c index 89a59a2d7a0..d2c7ea79ff8 100644 --- a/lib/lfrfid/protocols/protocol_jablotron.c +++ b/lib/lfrfid/protocols/protocol_jablotron.c @@ -160,9 +160,9 @@ LevelDuration protocol_jablotron_encoder_yield(ProtocolJablotron* protocol) { return level_duration_make(protocol->last_level, duration); }; -void protocol_jablotron_render_data(ProtocolJablotron* protocol, string_t result) { +void protocol_jablotron_render_data(ProtocolJablotron* protocol, FuriString* result) { uint64_t id = protocol_jablotron_card_id(protocol->data); - string_printf(result, "ID: %llX\r\n", id); + furi_string_printf(result, "ID: %llX\r\n", id); }; bool protocol_jablotron_write_data(ProtocolJablotron* protocol, void* data) { diff --git a/lib/lfrfid/protocols/protocol_keri.c b/lib/lfrfid/protocols/protocol_keri.c index 7e66255463b..20e44a702e9 100644 --- a/lib/lfrfid/protocols/protocol_keri.c +++ b/lib/lfrfid/protocols/protocol_keri.c @@ -211,13 +211,13 @@ LevelDuration protocol_keri_encoder_yield(ProtocolKeri* protocol) { return level_duration; }; -void protocol_keri_render_data(ProtocolKeri* protocol, string_t result) { +void protocol_keri_render_data(ProtocolKeri* protocol, FuriString* result) { uint32_t data = bit_lib_get_bits_32(protocol->data, 0, 32); uint32_t internal_id = data & 0x7FFFFFFF; uint32_t fc = 0; uint32_t cn = 0; protocol_keri_descramble(&fc, &cn, &data); - string_printf(result, "Internal ID: %u\r\nFC: %u, Card: %u\r\n", internal_id, fc, cn); + furi_string_printf(result, "Internal ID: %u\r\nFC: %u, Card: %u\r\n", internal_id, fc, cn); } bool protocol_keri_write_data(ProtocolKeri* protocol, void* data) { diff --git a/lib/lfrfid/protocols/protocol_pac_stanley.c b/lib/lfrfid/protocols/protocol_pac_stanley.c index b5e1ebddc1b..59aaf1e6aab 100644 --- a/lib/lfrfid/protocols/protocol_pac_stanley.c +++ b/lib/lfrfid/protocols/protocol_pac_stanley.c @@ -57,31 +57,31 @@ static void protocol_pac_stanley_decode(ProtocolPACStanley* protocol) { } static bool protocol_pac_stanley_can_be_decoded(ProtocolPACStanley* protocol) { - // Check preamble - if(bit_lib_get_bits(protocol->encoded_data, 0, 8) != 0b11111111) return false; - if(bit_lib_get_bit(protocol->encoded_data, 8) != 0) return false; - if(bit_lib_get_bit(protocol->encoded_data, 9) != 0) return false; - if(bit_lib_get_bit(protocol->encoded_data, 10) != 1) return false; - if(bit_lib_get_bits(protocol->encoded_data, 11, 8) != 0b00000010) return false; - - // Check next preamble - if(bit_lib_get_bits(protocol->encoded_data, 128, 8) != 0b11111111) return false; - - // Checksum - uint8_t checksum = 0; - uint8_t stripped_byte; - for(size_t idx = 0; idx < 9; idx++) { - uint8_t byte = bit_lib_reverse_8_fast(bit_lib_get_bits( - protocol->encoded_data, - PAC_STANLEY_DATA_START_INDEX + (PAC_STANLEY_BYTE_LENGTH * idx), - 8)); - stripped_byte = byte & 0x7F; // discard the parity bit - if(bit_lib_test_parity_32(stripped_byte, BitLibParityOdd) != (byte & 0x80) >> 7) { - return false; - } - if(idx < 8) checksum ^= stripped_byte; + // Check preamble + if(bit_lib_get_bits(protocol->encoded_data, 0, 8) != 0b11111111) return false; + if(bit_lib_get_bit(protocol->encoded_data, 8) != 0) return false; + if(bit_lib_get_bit(protocol->encoded_data, 9) != 0) return false; + if(bit_lib_get_bit(protocol->encoded_data, 10) != 1) return false; + if(bit_lib_get_bits(protocol->encoded_data, 11, 8) != 0b00000010) return false; + + // Check next preamble + if(bit_lib_get_bits(protocol->encoded_data, 128, 8) != 0b11111111) return false; + + // Checksum + uint8_t checksum = 0; + uint8_t stripped_byte; + for(size_t idx = 0; idx < 9; idx++) { + uint8_t byte = bit_lib_reverse_8_fast(bit_lib_get_bits( + protocol->encoded_data, + PAC_STANLEY_DATA_START_INDEX + (PAC_STANLEY_BYTE_LENGTH * idx), + 8)); + stripped_byte = byte & 0x7F; // discard the parity bit + if(bit_lib_test_parity_32(stripped_byte, BitLibParityOdd) != (byte & 0x80) >> 7) { + return false; } - if(stripped_byte != checksum) return false; + if(idx < 8) checksum ^= stripped_byte; + } + if(stripped_byte != checksum) return false; return true; } @@ -201,9 +201,9 @@ bool protocol_pac_stanley_write_data(ProtocolPACStanley* protocol, void* data) { return result; } -void protocol_pac_stanley_render_data(ProtocolPACStanley* protocol, string_t result) { +void protocol_pac_stanley_render_data(ProtocolPACStanley* protocol, FuriString* result) { uint8_t* data = protocol->data; - string_printf(result, "CIN: %02X%02X%02X%02X", data[0], data[1], data[2], data[3]); + furi_string_printf(result, "CIN: %02X%02X%02X%02X", data[0], data[1], data[2], data[3]); } const ProtocolBase protocol_pac_stanley = { diff --git a/lib/lfrfid/protocols/protocol_paradox.c b/lib/lfrfid/protocols/protocol_paradox.c index b0b3e71d65f..6a8e33ba407 100644 --- a/lib/lfrfid/protocols/protocol_paradox.c +++ b/lib/lfrfid/protocols/protocol_paradox.c @@ -136,26 +136,26 @@ LevelDuration protocol_paradox_encoder_yield(ProtocolParadox* protocol) { return level_duration_make(level, duration); }; -void protocol_paradox_render_data(ProtocolParadox* protocol, string_t result) { +void protocol_paradox_render_data(ProtocolParadox* protocol, FuriString* result) { uint8_t* decoded_data = protocol->data; uint8_t fc = bit_lib_get_bits(decoded_data, 10, 8); uint16_t card_id = bit_lib_get_bits_16(decoded_data, 18, 16); - string_cat_printf(result, "Facility: %u\r\n", fc); - string_cat_printf(result, "Card: %lu\r\n", card_id); - string_cat_printf(result, "Data: "); + furi_string_cat_printf(result, "Facility: %u\r\n", fc); + furi_string_cat_printf(result, "Card: %lu\r\n", card_id); + furi_string_cat_printf(result, "Data: "); for(size_t i = 0; i < PARADOX_DECODED_DATA_SIZE; i++) { - string_cat_printf(result, "%02X", decoded_data[i]); + furi_string_cat_printf(result, "%02X", decoded_data[i]); } }; -void protocol_paradox_render_brief_data(ProtocolParadox* protocol, string_t result) { +void protocol_paradox_render_brief_data(ProtocolParadox* protocol, FuriString* result) { uint8_t* decoded_data = protocol->data; uint8_t fc = bit_lib_get_bits(decoded_data, 10, 8); uint16_t card_id = bit_lib_get_bits_16(decoded_data, 18, 16); - string_cat_printf(result, "FC: %03u, Card: %05u", fc, card_id); + furi_string_cat_printf(result, "FC: %03u, Card: %05u", fc, card_id); }; bool protocol_paradox_write_data(ProtocolParadox* protocol, void* data) { diff --git a/lib/lfrfid/protocols/protocol_pyramid.c b/lib/lfrfid/protocols/protocol_pyramid.c index 1bba9871832..2e1b57edcad 100644 --- a/lib/lfrfid/protocols/protocol_pyramid.c +++ b/lib/lfrfid/protocols/protocol_pyramid.c @@ -238,11 +238,11 @@ bool protocol_pyramid_write_data(ProtocolPyramid* protocol, void* data) { return result; }; -void protocol_pyramid_render_data(ProtocolPyramid* protocol, string_t result) { +void protocol_pyramid_render_data(ProtocolPyramid* protocol, FuriString* result) { uint8_t* decoded_data = protocol->data; uint8_t format_length = decoded_data[0]; - string_cat_printf(result, "Format: 26\r\n", format_length); + furi_string_cat_printf(result, "Format: 26\r\n", format_length); if(format_length == 26) { uint8_t facility; bit_lib_copy_bits(&facility, 0, 8, decoded_data, 8); @@ -250,9 +250,9 @@ void protocol_pyramid_render_data(ProtocolPyramid* protocol, string_t result) { uint16_t card_id; bit_lib_copy_bits((uint8_t*)&card_id, 8, 8, decoded_data, 16); bit_lib_copy_bits((uint8_t*)&card_id, 0, 8, decoded_data, 24); - string_cat_printf(result, "FC: %03u, Card: %05u", facility, card_id); + furi_string_cat_printf(result, "FC: %03u, Card: %05u", facility, card_id); } else { - string_cat_printf(result, "Data: unknown"); + furi_string_cat_printf(result, "Data: unknown"); } }; diff --git a/lib/lfrfid/protocols/protocol_viking.c b/lib/lfrfid/protocols/protocol_viking.c index f252cc2c355..8083f6d915f 100644 --- a/lib/lfrfid/protocols/protocol_viking.c +++ b/lib/lfrfid/protocols/protocol_viking.c @@ -175,9 +175,9 @@ bool protocol_viking_write_data(ProtocolViking* protocol, void* data) { return result; }; -void protocol_viking_render_data(ProtocolViking* protocol, string_t result) { +void protocol_viking_render_data(ProtocolViking* protocol, FuriString* result) { uint32_t id = bit_lib_get_bits_32(protocol->data, 0, 32); - string_printf(result, "ID: %08lX\r\n", id); + furi_string_printf(result, "ID: %08lX\r\n", id); }; const ProtocolBase protocol_viking = { diff --git a/lib/nfc/helpers/mf_classic_dict.c b/lib/nfc/helpers/mf_classic_dict.c index 5bb67145a1f..57841afc1df 100644 --- a/lib/nfc/helpers/mf_classic_dict.c +++ b/lib/nfc/helpers/mf_classic_dict.c @@ -66,15 +66,15 @@ MfClassicDict* mf_classic_dict_alloc(MfClassicDictType dict_type) { } // Read total amount of keys - string_t next_line; - string_init(next_line); + FuriString* next_line; + next_line = furi_string_alloc(); while(true) { if(!stream_read_line(dict->stream, next_line)) break; - if(string_get_char(next_line, 0) == '#') continue; - if(string_size(next_line) != NFC_MF_CLASSIC_KEY_LEN) continue; + if(furi_string_get_char(next_line, 0) == '#') continue; + if(furi_string_size(next_line) != NFC_MF_CLASSIC_KEY_LEN) continue; dict->total_keys++; } - string_clear(next_line); + furi_string_free(next_line); stream_rewind(dict->stream); dict_loaded = true; @@ -99,20 +99,20 @@ void mf_classic_dict_free(MfClassicDict* dict) { free(dict); } -static void mf_classic_dict_int_to_str(uint8_t* key_int, string_t key_str) { - string_reset(key_str); +static void mf_classic_dict_int_to_str(uint8_t* key_int, FuriString* key_str) { + furi_string_reset(key_str); for(size_t i = 0; i < 6; i++) { - string_cat_printf(key_str, "%02X", key_int[i]); + furi_string_cat_printf(key_str, "%02X", key_int[i]); } } -static void mf_classic_dict_str_to_int(string_t key_str, uint64_t* key_int) { +static void mf_classic_dict_str_to_int(FuriString* key_str, uint64_t* key_int) { uint8_t key_byte_tmp; *key_int = 0ULL; for(uint8_t i = 0; i < 12; i += 2) { args_char_to_hex( - string_get_char(key_str, i), string_get_char(key_str, i + 1), &key_byte_tmp); + furi_string_get_char(key_str, i), furi_string_get_char(key_str, i + 1), &key_byte_tmp); *key_int |= (uint64_t)key_byte_tmp << 8 * (5 - i / 2); } } @@ -130,17 +130,17 @@ bool mf_classic_dict_rewind(MfClassicDict* dict) { return stream_rewind(dict->stream); } -bool mf_classic_dict_get_next_key_str(MfClassicDict* dict, string_t key) { +bool mf_classic_dict_get_next_key_str(MfClassicDict* dict, FuriString* key) { furi_assert(dict); furi_assert(dict->stream); bool key_read = false; - string_reset(key); + furi_string_reset(key); while(!key_read) { if(!stream_read_line(dict->stream, key)) break; - if(string_get_char(key, 0) == '#') continue; - if(string_size(key) != NFC_MF_CLASSIC_KEY_LEN) continue; - string_left(key, 12); + if(furi_string_get_char(key, 0) == '#') continue; + if(furi_string_size(key) != NFC_MF_CLASSIC_KEY_LEN) continue; + furi_string_left(key, 12); key_read = true; } @@ -151,53 +151,53 @@ bool mf_classic_dict_get_next_key(MfClassicDict* dict, uint64_t* key) { furi_assert(dict); furi_assert(dict->stream); - string_t temp_key; - string_init(temp_key); + FuriString* temp_key; + temp_key = furi_string_alloc(); bool key_read = mf_classic_dict_get_next_key_str(dict, temp_key); if(key_read) { mf_classic_dict_str_to_int(temp_key, key); } - string_clear(temp_key); + furi_string_free(temp_key); return key_read; } -bool mf_classic_dict_is_key_present_str(MfClassicDict* dict, string_t key) { +bool mf_classic_dict_is_key_present_str(MfClassicDict* dict, FuriString* key) { furi_assert(dict); furi_assert(dict->stream); - string_t next_line; - string_init(next_line); + FuriString* next_line; + next_line = furi_string_alloc(); bool key_found = false; stream_rewind(dict->stream); while(!key_found) { if(!stream_read_line(dict->stream, next_line)) break; - if(string_get_char(next_line, 0) == '#') continue; - if(string_size(next_line) != NFC_MF_CLASSIC_KEY_LEN) continue; - string_left(next_line, 12); - if(!string_equal_p(key, next_line)) continue; + if(furi_string_get_char(next_line, 0) == '#') continue; + if(furi_string_size(next_line) != NFC_MF_CLASSIC_KEY_LEN) continue; + furi_string_left(next_line, 12); + if(!furi_string_equal(key, next_line)) continue; key_found = true; } - string_clear(next_line); + furi_string_free(next_line); return key_found; } bool mf_classic_dict_is_key_present(MfClassicDict* dict, uint8_t* key) { - string_t temp_key; + FuriString* temp_key; - string_init(temp_key); + temp_key = furi_string_alloc(); mf_classic_dict_int_to_str(key, temp_key); bool key_found = mf_classic_dict_is_key_present_str(dict, temp_key); - string_clear(temp_key); + furi_string_free(temp_key); return key_found; } -bool mf_classic_dict_add_key_str(MfClassicDict* dict, string_t key) { +bool mf_classic_dict_add_key_str(MfClassicDict* dict, FuriString* key) { furi_assert(dict); furi_assert(dict->stream); - string_cat_printf(key, "\n"); + furi_string_cat_printf(key, "\n"); bool key_added = false; do { @@ -207,7 +207,7 @@ bool mf_classic_dict_add_key_str(MfClassicDict* dict, string_t key) { key_added = true; } while(false); - string_left(key, 12); + furi_string_left(key, 12); return key_added; } @@ -215,35 +215,35 @@ bool mf_classic_dict_add_key(MfClassicDict* dict, uint8_t* key) { furi_assert(dict); furi_assert(dict->stream); - string_t temp_key; - string_init(temp_key); + FuriString* temp_key; + temp_key = furi_string_alloc(); mf_classic_dict_int_to_str(key, temp_key); bool key_added = mf_classic_dict_add_key_str(dict, temp_key); - string_clear(temp_key); + furi_string_free(temp_key); return key_added; } -bool mf_classic_dict_get_key_at_index_str(MfClassicDict* dict, string_t key, uint32_t target) { +bool mf_classic_dict_get_key_at_index_str(MfClassicDict* dict, FuriString* key, uint32_t target) { furi_assert(dict); furi_assert(dict->stream); - string_t next_line; + FuriString* next_line; uint32_t index = 0; - string_init(next_line); - string_reset(key); + next_line = furi_string_alloc(); + furi_string_reset(key); bool key_found = false; while(!key_found) { if(!stream_read_line(dict->stream, next_line)) break; - if(string_get_char(next_line, 0) == '#') continue; - if(string_size(next_line) != NFC_MF_CLASSIC_KEY_LEN) continue; + if(furi_string_get_char(next_line, 0) == '#') continue; + if(furi_string_size(next_line) != NFC_MF_CLASSIC_KEY_LEN) continue; if(index++ != target) continue; - string_set_n(key, next_line, 0, 12); + furi_string_set_n(key, next_line, 0, 12); key_found = true; } - string_clear(next_line); + furi_string_free(next_line); return key_found; } @@ -251,37 +251,37 @@ bool mf_classic_dict_get_key_at_index(MfClassicDict* dict, uint64_t* key, uint32 furi_assert(dict); furi_assert(dict->stream); - string_t temp_key; - string_init(temp_key); + FuriString* temp_key; + temp_key = furi_string_alloc(); bool key_found = mf_classic_dict_get_key_at_index_str(dict, temp_key, target); if(key_found) { mf_classic_dict_str_to_int(temp_key, key); } - string_clear(temp_key); + furi_string_free(temp_key); return key_found; } -bool mf_classic_dict_find_index_str(MfClassicDict* dict, string_t key, uint32_t* target) { +bool mf_classic_dict_find_index_str(MfClassicDict* dict, FuriString* key, uint32_t* target) { furi_assert(dict); furi_assert(dict->stream); - string_t next_line; - string_init(next_line); + FuriString* next_line; + next_line = furi_string_alloc(); bool key_found = false; uint32_t index = 0; stream_rewind(dict->stream); while(!key_found) { if(!stream_read_line(dict->stream, next_line)) break; - if(string_get_char(next_line, 0) == '#') continue; - if(string_size(next_line) != NFC_MF_CLASSIC_KEY_LEN) continue; - string_left(next_line, 12); - if(!string_equal_p(key, next_line)) continue; + if(furi_string_get_char(next_line, 0) == '#') continue; + if(furi_string_size(next_line) != NFC_MF_CLASSIC_KEY_LEN) continue; + furi_string_left(next_line, 12); + if(!furi_string_equal(key, next_line)) continue; key_found = true; *target = index; } - string_clear(next_line); + furi_string_free(next_line); return key_found; } @@ -289,12 +289,12 @@ bool mf_classic_dict_find_index(MfClassicDict* dict, uint8_t* key, uint32_t* tar furi_assert(dict); furi_assert(dict->stream); - string_t temp_key; - string_init(temp_key); + FuriString* temp_key; + temp_key = furi_string_alloc(); mf_classic_dict_int_to_str(key, temp_key); bool key_found = mf_classic_dict_find_index_str(dict, temp_key, target); - string_clear(temp_key); + furi_string_free(temp_key); return key_found; } @@ -302,15 +302,15 @@ bool mf_classic_dict_delete_index(MfClassicDict* dict, uint32_t target) { furi_assert(dict); furi_assert(dict->stream); - string_t next_line; - string_init(next_line); + FuriString* next_line; + next_line = furi_string_alloc(); uint32_t index = 0; bool key_removed = false; while(!key_removed) { if(!stream_read_line(dict->stream, next_line)) break; - if(string_get_char(next_line, 0) == '#') continue; - if(string_size(next_line) != NFC_MF_CLASSIC_KEY_LEN) continue; + if(furi_string_get_char(next_line, 0) == '#') continue; + if(furi_string_size(next_line) != NFC_MF_CLASSIC_KEY_LEN) continue; if(index++ != target) continue; stream_seek(dict->stream, -NFC_MF_CLASSIC_KEY_LEN, StreamOffsetFromCurrent); if(!stream_delete(dict->stream, NFC_MF_CLASSIC_KEY_LEN)) break; @@ -318,6 +318,6 @@ bool mf_classic_dict_delete_index(MfClassicDict* dict, uint32_t target) { key_removed = true; } - string_clear(next_line); + furi_string_free(next_line); return key_removed; } diff --git a/lib/nfc/helpers/mf_classic_dict.h b/lib/nfc/helpers/mf_classic_dict.h index 9241a37b9e7..5b0ee312ac6 100644 --- a/lib/nfc/helpers/mf_classic_dict.h +++ b/lib/nfc/helpers/mf_classic_dict.h @@ -48,11 +48,11 @@ bool mf_classic_dict_rewind(MfClassicDict* dict); bool mf_classic_dict_is_key_present(MfClassicDict* dict, uint8_t* key); -bool mf_classic_dict_is_key_present_str(MfClassicDict* dict, string_t key); +bool mf_classic_dict_is_key_present_str(MfClassicDict* dict, FuriString* key); bool mf_classic_dict_get_next_key(MfClassicDict* dict, uint64_t* key); -bool mf_classic_dict_get_next_key_str(MfClassicDict* dict, string_t key); +bool mf_classic_dict_get_next_key_str(MfClassicDict* dict, FuriString* key); /** Get key at target offset as uint64_t * @@ -72,7 +72,7 @@ bool mf_classic_dict_get_key_at_index(MfClassicDict* dict, uint64_t* key, uint32 * * @return true on success */ -bool mf_classic_dict_get_key_at_index_str(MfClassicDict* dict, string_t key, uint32_t target); +bool mf_classic_dict_get_key_at_index_str(MfClassicDict* dict, FuriString* key, uint32_t target); bool mf_classic_dict_add_key(MfClassicDict* dict, uint8_t* key); @@ -83,11 +83,11 @@ bool mf_classic_dict_add_key(MfClassicDict* dict, uint8_t* key); * * @return true on success */ -bool mf_classic_dict_add_key_str(MfClassicDict* dict, string_t key); +bool mf_classic_dict_add_key_str(MfClassicDict* dict, FuriString* key); bool mf_classic_dict_find_index(MfClassicDict* dict, uint8_t* key, uint32_t* target); -bool mf_classic_dict_find_index_str(MfClassicDict* dict, string_t key, uint32_t* target); +bool mf_classic_dict_find_index_str(MfClassicDict* dict, FuriString* key, uint32_t* target); /** Delete key at target offset * diff --git a/lib/nfc/helpers/mfkey32.c b/lib/nfc/helpers/mfkey32.c index 64b06e259c8..8eb417eb166 100644 --- a/lib/nfc/helpers/mfkey32.c +++ b/lib/nfc/helpers/mfkey32.c @@ -91,9 +91,7 @@ void mfkey32_set_callback(Mfkey32* instance, Mfkey32ParseDataCallback callback, } static bool mfkey32_write_params(Mfkey32* instance, Mfkey32Params* params) { - string_t str; - string_init_printf( - str, + FuriString* str = furi_string_alloc_printf( "Sector %d key %c cuid %08x nt0 %08x nr0 %08x ar0 %08x nt1 %08x nr1 %08x ar1 %08x\n", params->sector, params->key == MfClassicKeyA ? 'A' : 'B', @@ -105,7 +103,7 @@ static bool mfkey32_write_params(Mfkey32* instance, Mfkey32Params* params) { params->nr1, params->ar1); bool write_success = stream_write_string(instance->file_stream, str); - string_clear(str); + furi_string_free(str); return write_success; } @@ -199,14 +197,14 @@ void mfkey32_process_data( } } -uint16_t mfkey32_get_auth_sectors(string_t data_str) { +uint16_t mfkey32_get_auth_sectors(FuriString* data_str) { furi_assert(data_str); uint16_t nonces_num = 0; Storage* storage = furi_record_open(RECORD_STORAGE); Stream* file_stream = buffered_file_stream_alloc(storage); - string_t temp_str; - string_init(temp_str); + FuriString* temp_str; + temp_str = furi_string_alloc(); do { if(!buffered_file_stream_open( @@ -214,17 +212,17 @@ uint16_t mfkey32_get_auth_sectors(string_t data_str) { break; while(true) { if(!stream_read_line(file_stream, temp_str)) break; - size_t uid_pos = string_search_str(temp_str, "cuid"); - string_left(temp_str, uid_pos); - string_push_back(temp_str, '\n'); - string_cat(data_str, temp_str); + size_t uid_pos = furi_string_search(temp_str, "cuid"); + furi_string_left(temp_str, uid_pos); + furi_string_push_back(temp_str, '\n'); + furi_string_cat(data_str, temp_str); nonces_num++; } } while(false); buffered_file_stream_close(file_stream); stream_free(file_stream); - string_clear(temp_str); + furi_string_free(temp_str); return nonces_num; } diff --git a/lib/nfc/helpers/mfkey32.h b/lib/nfc/helpers/mfkey32.h index c4f13cc2c49..e1f472e50c8 100644 --- a/lib/nfc/helpers/mfkey32.h +++ b/lib/nfc/helpers/mfkey32.h @@ -1,7 +1,6 @@ #pragma once #include -#include typedef struct Mfkey32 Mfkey32; @@ -24,4 +23,4 @@ void mfkey32_process_data( void mfkey32_set_callback(Mfkey32* instance, Mfkey32ParseDataCallback callback, void* context); -uint16_t mfkey32_get_auth_sectors(string_t string); +uint16_t mfkey32_get_auth_sectors(FuriString* string); diff --git a/lib/nfc/helpers/nfc_debug_log.c b/lib/nfc/helpers/nfc_debug_log.c index 1cbc5224f67..0bfbc2c62f6 100644 --- a/lib/nfc/helpers/nfc_debug_log.c +++ b/lib/nfc/helpers/nfc_debug_log.c @@ -1,6 +1,5 @@ #include "nfc_debug_log.h" -#include #include #include @@ -10,7 +9,7 @@ struct NfcDebugLog { Stream* file_stream; - string_t data_str; + FuriString* data_str; }; NfcDebugLog* nfc_debug_log_alloc() { @@ -30,7 +29,7 @@ NfcDebugLog* nfc_debug_log_alloc() { free(instance); instance = NULL; } else { - string_init(instance->data_str); + instance->data_str = furi_string_alloc(); } furi_record_close(RECORD_STORAGE); @@ -44,7 +43,7 @@ void nfc_debug_log_free(NfcDebugLog* instance) { buffered_file_stream_close(instance->file_stream); stream_free(instance->file_stream); - string_clear(instance->data_str); + furi_string_free(instance->data_str); free(instance); } @@ -61,12 +60,12 @@ void nfc_debug_log_process_data( furi_assert(data); UNUSED(crc_dropped); - string_printf(instance->data_str, "%lu %c:", furi_get_tick(), reader_to_tag ? 'R' : 'T'); + furi_string_printf(instance->data_str, "%lu %c:", furi_get_tick(), reader_to_tag ? 'R' : 'T'); uint16_t data_len = len; for(size_t i = 0; i < data_len; i++) { - string_cat_printf(instance->data_str, " %02x", data[i]); + furi_string_cat_printf(instance->data_str, " %02x", data[i]); } - string_push_back(instance->data_str, '\n'); + furi_string_push_back(instance->data_str, '\n'); stream_write_string(instance->file_stream, instance->data_str); } diff --git a/lib/nfc/nfc_device.c b/lib/nfc/nfc_device.c index dd78e2daf06..f28d4d5bd32 100644 --- a/lib/nfc/nfc_device.c +++ b/lib/nfc/nfc_device.c @@ -1,6 +1,5 @@ #include "nfc_device.h" #include "assets_icons.h" -#include "m-string.h" #include "nfc_types.h" #include @@ -25,8 +24,8 @@ NfcDevice* nfc_device_alloc() { NfcDevice* nfc_dev = malloc(sizeof(NfcDevice)); nfc_dev->storage = furi_record_open(RECORD_STORAGE); nfc_dev->dialogs = furi_record_open(RECORD_DIALOGS); - string_init(nfc_dev->load_path); - string_init(nfc_dev->dev_data.parsed_data); + nfc_dev->load_path = furi_string_alloc(); + nfc_dev->dev_data.parsed_data = furi_string_alloc(); return nfc_dev; } @@ -35,53 +34,53 @@ void nfc_device_free(NfcDevice* nfc_dev) { nfc_device_clear(nfc_dev); furi_record_close(RECORD_STORAGE); furi_record_close(RECORD_DIALOGS); - string_clear(nfc_dev->load_path); - string_clear(nfc_dev->dev_data.parsed_data); + furi_string_free(nfc_dev->load_path); + furi_string_free(nfc_dev->dev_data.parsed_data); free(nfc_dev); } -static void nfc_device_prepare_format_string(NfcDevice* dev, string_t format_string) { +static void nfc_device_prepare_format_string(NfcDevice* dev, FuriString* format_string) { if(dev->format == NfcDeviceSaveFormatUid) { - string_set_str(format_string, "UID"); + furi_string_set(format_string, "UID"); } else if(dev->format == NfcDeviceSaveFormatBankCard) { - string_set_str(format_string, "Bank card"); + furi_string_set(format_string, "Bank card"); } else if(dev->format == NfcDeviceSaveFormatMifareUl) { - string_set_str(format_string, nfc_mf_ul_type(dev->dev_data.mf_ul_data.type, true)); + furi_string_set(format_string, nfc_mf_ul_type(dev->dev_data.mf_ul_data.type, true)); } else if(dev->format == NfcDeviceSaveFormatMifareClassic) { - string_set_str(format_string, "Mifare Classic"); + furi_string_set(format_string, "Mifare Classic"); } else if(dev->format == NfcDeviceSaveFormatMifareDesfire) { - string_set_str(format_string, "Mifare DESFire"); + furi_string_set(format_string, "Mifare DESFire"); } else { - string_set_str(format_string, "Unknown"); + furi_string_set(format_string, "Unknown"); } } -static bool nfc_device_parse_format_string(NfcDevice* dev, string_t format_string) { - if(string_start_with_str_p(format_string, "UID")) { +static bool nfc_device_parse_format_string(NfcDevice* dev, FuriString* format_string) { + if(furi_string_start_with_str(format_string, "UID")) { dev->format = NfcDeviceSaveFormatUid; dev->dev_data.protocol = NfcDeviceProtocolUnknown; return true; } - if(string_start_with_str_p(format_string, "Bank card")) { + if(furi_string_start_with_str(format_string, "Bank card")) { dev->format = NfcDeviceSaveFormatBankCard; dev->dev_data.protocol = NfcDeviceProtocolEMV; return true; } // Check Mifare Ultralight types for(MfUltralightType type = MfUltralightTypeUnknown; type < MfUltralightTypeNum; type++) { - if(string_equal_str_p(format_string, nfc_mf_ul_type(type, true))) { + if(furi_string_equal(format_string, nfc_mf_ul_type(type, true))) { dev->format = NfcDeviceSaveFormatMifareUl; dev->dev_data.protocol = NfcDeviceProtocolMifareUl; dev->dev_data.mf_ul_data.type = type; return true; } } - if(string_start_with_str_p(format_string, "Mifare Classic")) { + if(furi_string_start_with_str(format_string, "Mifare Classic")) { dev->format = NfcDeviceSaveFormatMifareClassic; dev->dev_data.protocol = NfcDeviceProtocolMifareClassic; return true; } - if(string_start_with_str_p(format_string, "Mifare DESFire")) { + if(furi_string_start_with_str(format_string, "Mifare DESFire")) { dev->format = NfcDeviceSaveFormatMifareDesfire; dev->dev_data.protocol = NfcDeviceProtocolMifareDesfire; return true; @@ -92,8 +91,8 @@ static bool nfc_device_parse_format_string(NfcDevice* dev, string_t format_strin static bool nfc_device_save_mifare_ul_data(FlipperFormat* file, NfcDevice* dev) { bool saved = false; MfUltralightData* data = &dev->dev_data.mf_ul_data; - string_t temp_str; - string_init(temp_str); + FuriString* temp_str; + temp_str = furi_string_alloc(); // Save Mifare Ultralight specific data do { @@ -109,14 +108,15 @@ static bool nfc_device_save_mifare_ul_data(FlipperFormat* file, NfcDevice* dev) // Write conters and tearing flags data bool counters_saved = true; for(uint8_t i = 0; i < 3; i++) { - string_printf(temp_str, "Counter %d", i); + furi_string_printf(temp_str, "Counter %d", i); if(!flipper_format_write_uint32( - file, string_get_cstr(temp_str), &data->counter[i], 1)) { + file, furi_string_get_cstr(temp_str), &data->counter[i], 1)) { counters_saved = false; break; } - string_printf(temp_str, "Tearing %d", i); - if(!flipper_format_write_hex(file, string_get_cstr(temp_str), &data->tearing[i], 1)) { + furi_string_printf(temp_str, "Tearing %d", i); + if(!flipper_format_write_hex( + file, furi_string_get_cstr(temp_str), &data->tearing[i], 1)) { counters_saved = false; break; } @@ -129,8 +129,8 @@ static bool nfc_device_save_mifare_ul_data(FlipperFormat* file, NfcDevice* dev) if(!flipper_format_write_uint32(file, "Pages read", &pages_read, 1)) break; bool pages_saved = true; for(uint16_t i = 0; i < data->data_size; i += 4) { - string_printf(temp_str, "Page %d", i / 4); - if(!flipper_format_write_hex(file, string_get_cstr(temp_str), &data->data[i], 4)) { + furi_string_printf(temp_str, "Page %d", i / 4); + if(!flipper_format_write_hex(file, furi_string_get_cstr(temp_str), &data->data[i], 4)) { pages_saved = false; break; } @@ -145,15 +145,15 @@ static bool nfc_device_save_mifare_ul_data(FlipperFormat* file, NfcDevice* dev) saved = true; } while(false); - string_clear(temp_str); + furi_string_free(temp_str); return saved; } bool nfc_device_load_mifare_ul_data(FlipperFormat* file, NfcDevice* dev) { bool parsed = false; MfUltralightData* data = &dev->dev_data.mf_ul_data; - string_t temp_str; - string_init(temp_str); + FuriString* temp_str; + temp_str = furi_string_alloc(); uint32_t data_format_version = 0; do { @@ -172,13 +172,15 @@ bool nfc_device_load_mifare_ul_data(FlipperFormat* file, NfcDevice* dev) { // Read counters and tearing flags bool counters_parsed = true; for(uint8_t i = 0; i < 3; i++) { - string_printf(temp_str, "Counter %d", i); - if(!flipper_format_read_uint32(file, string_get_cstr(temp_str), &data->counter[i], 1)) { + furi_string_printf(temp_str, "Counter %d", i); + if(!flipper_format_read_uint32( + file, furi_string_get_cstr(temp_str), &data->counter[i], 1)) { counters_parsed = false; break; } - string_printf(temp_str, "Tearing %d", i); - if(!flipper_format_read_hex(file, string_get_cstr(temp_str), &data->tearing[i], 1)) { + furi_string_printf(temp_str, "Tearing %d", i); + if(!flipper_format_read_hex( + file, furi_string_get_cstr(temp_str), &data->tearing[i], 1)) { counters_parsed = false; break; } @@ -198,8 +200,9 @@ bool nfc_device_load_mifare_ul_data(FlipperFormat* file, NfcDevice* dev) { if(data->data_size > MF_UL_MAX_DUMP_SIZE || data->data_read > MF_UL_MAX_DUMP_SIZE) break; bool pages_parsed = true; for(uint16_t i = 0; i < pages_total; i++) { - string_printf(temp_str, "Page %d", i); - if(!flipper_format_read_hex(file, string_get_cstr(temp_str), &data->data[i * 4], 4)) { + furi_string_printf(temp_str, "Page %d", i); + if(!flipper_format_read_hex( + file, furi_string_get_cstr(temp_str), &data->data[i * 4], 4)) { pages_parsed = false; break; } @@ -214,7 +217,7 @@ bool nfc_device_load_mifare_ul_data(FlipperFormat* file, NfcDevice* dev) { parsed = true; } while(false); - string_clear(temp_str); + furi_string_free(temp_str); return parsed; } @@ -223,38 +226,40 @@ static bool nfc_device_save_mifare_df_key_settings( MifareDesfireKeySettings* ks, const char* prefix) { bool saved = false; - string_t key; - string_init(key); + FuriString* key; + key = furi_string_alloc(); do { - string_printf(key, "%s Change Key ID", prefix); - if(!flipper_format_write_hex(file, string_get_cstr(key), &ks->change_key_id, 1)) break; - string_printf(key, "%s Config Changeable", prefix); - if(!flipper_format_write_bool(file, string_get_cstr(key), &ks->config_changeable, 1)) + furi_string_printf(key, "%s Change Key ID", prefix); + if(!flipper_format_write_hex(file, furi_string_get_cstr(key), &ks->change_key_id, 1)) break; - string_printf(key, "%s Free Create Delete", prefix); - if(!flipper_format_write_bool(file, string_get_cstr(key), &ks->free_create_delete, 1)) + furi_string_printf(key, "%s Config Changeable", prefix); + if(!flipper_format_write_bool(file, furi_string_get_cstr(key), &ks->config_changeable, 1)) break; - string_printf(key, "%s Free Directory List", prefix); - if(!flipper_format_write_bool(file, string_get_cstr(key), &ks->free_directory_list, 1)) + furi_string_printf(key, "%s Free Create Delete", prefix); + if(!flipper_format_write_bool(file, furi_string_get_cstr(key), &ks->free_create_delete, 1)) break; - string_printf(key, "%s Key Changeable", prefix); - if(!flipper_format_write_bool(file, string_get_cstr(key), &ks->master_key_changeable, 1)) + furi_string_printf(key, "%s Free Directory List", prefix); + if(!flipper_format_write_bool(file, furi_string_get_cstr(key), &ks->free_directory_list, 1)) + break; + furi_string_printf(key, "%s Key Changeable", prefix); + if(!flipper_format_write_bool( + file, furi_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; + furi_string_printf(key, "%s Flags", prefix); + if(!flipper_format_write_hex(file, furi_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; + furi_string_printf(key, "%s Max Keys", prefix); + if(!flipper_format_write_hex(file, furi_string_get_cstr(key), &ks->max_keys, 1)) break; for(MifareDesfireKeyVersion* kv = ks->key_version_head; kv; kv = kv->next) { - string_printf(key, "%s Key %d Version", prefix, kv->id); - if(!flipper_format_write_hex(file, string_get_cstr(key), &kv->version, 1)) break; + furi_string_printf(key, "%s Key %d Version", prefix, kv->id); + if(!flipper_format_write_hex(file, furi_string_get_cstr(key), &kv->version, 1)) break; } saved = true; } while(false); - string_clear(key); + furi_string_free(key); return saved; } @@ -263,36 +268,38 @@ bool nfc_device_load_mifare_df_key_settings( MifareDesfireKeySettings* ks, const char* prefix) { bool parsed = false; - string_t key; - string_init(key); + FuriString* key; + key = furi_string_alloc(); do { - string_printf(key, "%s Change Key ID", prefix); - if(!flipper_format_read_hex(file, string_get_cstr(key), &ks->change_key_id, 1)) break; - string_printf(key, "%s Config Changeable", prefix); - if(!flipper_format_read_bool(file, string_get_cstr(key), &ks->config_changeable, 1)) break; - string_printf(key, "%s Free Create Delete", prefix); - if(!flipper_format_read_bool(file, string_get_cstr(key), &ks->free_create_delete, 1)) + furi_string_printf(key, "%s Change Key ID", prefix); + if(!flipper_format_read_hex(file, furi_string_get_cstr(key), &ks->change_key_id, 1)) break; + furi_string_printf(key, "%s Config Changeable", prefix); + if(!flipper_format_read_bool(file, furi_string_get_cstr(key), &ks->config_changeable, 1)) + break; + furi_string_printf(key, "%s Free Create Delete", prefix); + if(!flipper_format_read_bool(file, furi_string_get_cstr(key), &ks->free_create_delete, 1)) break; - string_printf(key, "%s Free Directory List", prefix); - if(!flipper_format_read_bool(file, string_get_cstr(key), &ks->free_directory_list, 1)) + furi_string_printf(key, "%s Free Directory List", prefix); + if(!flipper_format_read_bool(file, furi_string_get_cstr(key), &ks->free_directory_list, 1)) break; - string_printf(key, "%s Key Changeable", prefix); - if(!flipper_format_read_bool(file, string_get_cstr(key), &ks->master_key_changeable, 1)) + furi_string_printf(key, "%s Key Changeable", prefix); + if(!flipper_format_read_bool( + file, furi_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; + furi_string_printf(key, "%s Flags", prefix); + if(flipper_format_key_exist(file, furi_string_get_cstr(key))) { + if(!flipper_format_read_hex(file, furi_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; + furi_string_printf(key, "%s Max Keys", prefix); + if(!flipper_format_read_hex(file, furi_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); + furi_string_printf(key, "%s Key %d Version", prefix, key_id); uint8_t version; - if(flipper_format_read_hex(file, string_get_cstr(key), &version, 1)) { + if(flipper_format_read_hex(file, furi_string_get_cstr(key), &version, 1)) { MifareDesfireKeyVersion* kv = malloc(sizeof(MifareDesfireKeyVersion)); memset(kv, 0, sizeof(MifareDesfireKeyVersion)); kv->id = key_id; @@ -304,21 +311,22 @@ bool nfc_device_load_mifare_df_key_settings( parsed = true; } while(false); - string_clear(key); + furi_string_free(key); return parsed; } static bool nfc_device_save_mifare_df_app(FlipperFormat* file, MifareDesfireApplication* app) { bool saved = false; - string_t prefix, key; - string_init_printf(prefix, "Application %02x%02x%02x", app->id[0], app->id[1], app->id[2]); - string_init(key); + FuriString *prefix, *key; + prefix = + furi_string_alloc_printf("Application %02x%02x%02x", app->id[0], app->id[1], app->id[2]); + key = furi_string_alloc(); uint8_t* tmp = NULL; do { if(app->key_settings) { if(!nfc_device_save_mifare_df_key_settings( - file, app->key_settings, string_get_cstr(prefix))) + file, app->key_settings, furi_string_get_cstr(prefix))) break; } if(!app->file_head) break; @@ -331,68 +339,75 @@ static bool nfc_device_save_mifare_df_app(FlipperFormat* file, MifareDesfireAppl for(MifareDesfireFile* f = app->file_head; f; f = f->next) { tmp[i++] = f->id; } - string_printf(key, "%s File IDs", string_get_cstr(prefix)); - if(!flipper_format_write_hex(file, string_get_cstr(key), tmp, n_files)) break; + furi_string_printf(key, "%s File IDs", furi_string_get_cstr(prefix)); + if(!flipper_format_write_hex(file, furi_string_get_cstr(key), tmp, n_files)) break; bool saved_files = true; for(MifareDesfireFile* f = app->file_head; f; f = f->next) { saved_files = false; - string_printf(key, "%s File %d Type", string_get_cstr(prefix), f->id); - if(!flipper_format_write_hex(file, string_get_cstr(key), &f->type, 1)) break; - string_printf( - key, "%s File %d Communication Settings", string_get_cstr(prefix), f->id); - if(!flipper_format_write_hex(file, string_get_cstr(key), &f->comm, 1)) break; - string_printf(key, "%s File %d Access Rights", string_get_cstr(prefix), f->id); + furi_string_printf(key, "%s File %d Type", furi_string_get_cstr(prefix), f->id); + if(!flipper_format_write_hex(file, furi_string_get_cstr(key), &f->type, 1)) break; + furi_string_printf( + key, "%s File %d Communication Settings", furi_string_get_cstr(prefix), f->id); + if(!flipper_format_write_hex(file, furi_string_get_cstr(key), &f->comm, 1)) break; + furi_string_printf( + key, "%s File %d Access Rights", furi_string_get_cstr(prefix), f->id); if(!flipper_format_write_hex( - file, string_get_cstr(key), (uint8_t*)&f->access_rights, 2)) + file, furi_string_get_cstr(key), (uint8_t*)&f->access_rights, 2)) break; uint16_t size = 0; if(f->type == MifareDesfireFileTypeStandard || f->type == MifareDesfireFileTypeBackup) { size = f->settings.data.size; - string_printf(key, "%s File %d Size", string_get_cstr(prefix), f->id); + furi_string_printf(key, "%s File %d Size", furi_string_get_cstr(prefix), f->id); if(!flipper_format_write_uint32( - file, string_get_cstr(key), &f->settings.data.size, 1)) + file, furi_string_get_cstr(key), &f->settings.data.size, 1)) break; } else if(f->type == MifareDesfireFileTypeValue) { - string_printf(key, "%s File %d Hi Limit", string_get_cstr(prefix), f->id); + furi_string_printf( + key, "%s File %d Hi Limit", furi_string_get_cstr(prefix), f->id); if(!flipper_format_write_uint32( - file, string_get_cstr(key), &f->settings.value.hi_limit, 1)) + file, furi_string_get_cstr(key), &f->settings.value.hi_limit, 1)) break; - string_printf(key, "%s File %d Lo Limit", string_get_cstr(prefix), f->id); + furi_string_printf( + key, "%s File %d Lo Limit", furi_string_get_cstr(prefix), f->id); if(!flipper_format_write_uint32( - file, string_get_cstr(key), &f->settings.value.lo_limit, 1)) + file, furi_string_get_cstr(key), &f->settings.value.lo_limit, 1)) break; - string_printf( - key, "%s File %d Limited Credit Value", string_get_cstr(prefix), f->id); + furi_string_printf( + key, "%s File %d Limited Credit Value", furi_string_get_cstr(prefix), f->id); if(!flipper_format_write_uint32( - file, string_get_cstr(key), &f->settings.value.limited_credit_value, 1)) + file, furi_string_get_cstr(key), &f->settings.value.limited_credit_value, 1)) break; - string_printf( - key, "%s File %d Limited Credit Enabled", string_get_cstr(prefix), f->id); + furi_string_printf( + key, "%s File %d Limited Credit Enabled", furi_string_get_cstr(prefix), f->id); if(!flipper_format_write_bool( - file, string_get_cstr(key), &f->settings.value.limited_credit_enabled, 1)) + file, + furi_string_get_cstr(key), + &f->settings.value.limited_credit_enabled, + 1)) break; size = 4; } else if( f->type == MifareDesfireFileTypeLinearRecord || f->type == MifareDesfireFileTypeCyclicRecord) { - string_printf(key, "%s File %d Size", string_get_cstr(prefix), f->id); + furi_string_printf(key, "%s File %d Size", furi_string_get_cstr(prefix), f->id); if(!flipper_format_write_uint32( - file, string_get_cstr(key), &f->settings.record.size, 1)) + file, furi_string_get_cstr(key), &f->settings.record.size, 1)) break; - string_printf(key, "%s File %d Max", string_get_cstr(prefix), f->id); + furi_string_printf(key, "%s File %d Max", furi_string_get_cstr(prefix), f->id); if(!flipper_format_write_uint32( - file, string_get_cstr(key), &f->settings.record.max, 1)) + file, furi_string_get_cstr(key), &f->settings.record.max, 1)) break; - string_printf(key, "%s File %d Cur", string_get_cstr(prefix), f->id); + furi_string_printf(key, "%s File %d Cur", furi_string_get_cstr(prefix), f->id); if(!flipper_format_write_uint32( - file, string_get_cstr(key), &f->settings.record.cur, 1)) + file, furi_string_get_cstr(key), &f->settings.record.cur, 1)) break; size = f->settings.record.size * f->settings.record.cur; } if(f->contents) { - string_printf(key, "%s File %d", string_get_cstr(prefix), f->id); - if(!flipper_format_write_hex(file, string_get_cstr(key), f->contents, size)) break; + furi_string_printf(key, "%s File %d", furi_string_get_cstr(prefix), f->id); + if(!flipper_format_write_hex(file, furi_string_get_cstr(key), f->contents, size)) + break; } saved_files = true; } @@ -403,16 +418,17 @@ static bool nfc_device_save_mifare_df_app(FlipperFormat* file, MifareDesfireAppl } while(false); free(tmp); - string_clear(prefix); - string_clear(key); + furi_string_free(prefix); + furi_string_free(key); return saved; } bool nfc_device_load_mifare_df_app(FlipperFormat* file, MifareDesfireApplication* app) { bool parsed = false; - string_t prefix, key; - string_init_printf(prefix, "Application %02x%02x%02x", app->id[0], app->id[1], app->id[2]); - string_init(key); + FuriString *prefix, *key; + prefix = + furi_string_alloc_printf("Application %02x%02x%02x", app->id[0], app->id[1], app->id[2]); + key = furi_string_alloc(); uint8_t* tmp = NULL; MifareDesfireFile* f = NULL; @@ -420,16 +436,16 @@ bool nfc_device_load_mifare_df_app(FlipperFormat* file, MifareDesfireApplication app->key_settings = malloc(sizeof(MifareDesfireKeySettings)); memset(app->key_settings, 0, sizeof(MifareDesfireKeySettings)); if(!nfc_device_load_mifare_df_key_settings( - file, app->key_settings, string_get_cstr(prefix))) { + file, app->key_settings, furi_string_get_cstr(prefix))) { free(app->key_settings); app->key_settings = NULL; break; } - string_printf(key, "%s File IDs", string_get_cstr(prefix)); + furi_string_printf(key, "%s File IDs", furi_string_get_cstr(prefix)); uint32_t n_files; - if(!flipper_format_get_value_count(file, string_get_cstr(key), &n_files)) break; + if(!flipper_format_get_value_count(file, furi_string_get_cstr(key), &n_files)) break; tmp = malloc(n_files); - if(!flipper_format_read_hex(file, string_get_cstr(key), tmp, n_files)) break; + if(!flipper_format_read_hex(file, furi_string_get_cstr(key), tmp, n_files)) break; MifareDesfireFile** file_head = &app->file_head; bool parsed_files = true; for(uint32_t i = 0; i < n_files; i++) { @@ -437,61 +453,69 @@ bool nfc_device_load_mifare_df_app(FlipperFormat* file, MifareDesfireApplication f = malloc(sizeof(MifareDesfireFile)); memset(f, 0, sizeof(MifareDesfireFile)); f->id = tmp[i]; - string_printf(key, "%s File %d Type", string_get_cstr(prefix), f->id); - if(!flipper_format_read_hex(file, string_get_cstr(key), &f->type, 1)) break; - string_printf( - key, "%s File %d Communication Settings", string_get_cstr(prefix), f->id); - if(!flipper_format_read_hex(file, string_get_cstr(key), &f->comm, 1)) break; - string_printf(key, "%s File %d Access Rights", string_get_cstr(prefix), f->id); - if(!flipper_format_read_hex(file, string_get_cstr(key), (uint8_t*)&f->access_rights, 2)) + furi_string_printf(key, "%s File %d Type", furi_string_get_cstr(prefix), f->id); + if(!flipper_format_read_hex(file, furi_string_get_cstr(key), &f->type, 1)) break; + furi_string_printf( + key, "%s File %d Communication Settings", furi_string_get_cstr(prefix), f->id); + if(!flipper_format_read_hex(file, furi_string_get_cstr(key), &f->comm, 1)) break; + furi_string_printf( + key, "%s File %d Access Rights", furi_string_get_cstr(prefix), f->id); + if(!flipper_format_read_hex( + file, furi_string_get_cstr(key), (uint8_t*)&f->access_rights, 2)) break; if(f->type == MifareDesfireFileTypeStandard || f->type == MifareDesfireFileTypeBackup) { - string_printf(key, "%s File %d Size", string_get_cstr(prefix), f->id); + furi_string_printf(key, "%s File %d Size", furi_string_get_cstr(prefix), f->id); if(!flipper_format_read_uint32( - file, string_get_cstr(key), &f->settings.data.size, 1)) + file, furi_string_get_cstr(key), &f->settings.data.size, 1)) break; } else if(f->type == MifareDesfireFileTypeValue) { - string_printf(key, "%s File %d Hi Limit", string_get_cstr(prefix), f->id); + furi_string_printf( + key, "%s File %d Hi Limit", furi_string_get_cstr(prefix), f->id); if(!flipper_format_read_uint32( - file, string_get_cstr(key), &f->settings.value.hi_limit, 1)) + file, furi_string_get_cstr(key), &f->settings.value.hi_limit, 1)) break; - string_printf(key, "%s File %d Lo Limit", string_get_cstr(prefix), f->id); + furi_string_printf( + key, "%s File %d Lo Limit", furi_string_get_cstr(prefix), f->id); if(!flipper_format_read_uint32( - file, string_get_cstr(key), &f->settings.value.lo_limit, 1)) + file, furi_string_get_cstr(key), &f->settings.value.lo_limit, 1)) break; - string_printf( - key, "%s File %d Limited Credit Value", string_get_cstr(prefix), f->id); + furi_string_printf( + key, "%s File %d Limited Credit Value", furi_string_get_cstr(prefix), f->id); if(!flipper_format_read_uint32( - file, string_get_cstr(key), &f->settings.value.limited_credit_value, 1)) + file, furi_string_get_cstr(key), &f->settings.value.limited_credit_value, 1)) break; - string_printf( - key, "%s File %d Limited Credit Enabled", string_get_cstr(prefix), f->id); + furi_string_printf( + key, "%s File %d Limited Credit Enabled", furi_string_get_cstr(prefix), f->id); if(!flipper_format_read_bool( - file, string_get_cstr(key), &f->settings.value.limited_credit_enabled, 1)) + file, + furi_string_get_cstr(key), + &f->settings.value.limited_credit_enabled, + 1)) break; } else if( f->type == MifareDesfireFileTypeLinearRecord || f->type == MifareDesfireFileTypeCyclicRecord) { - string_printf(key, "%s File %d Size", string_get_cstr(prefix), f->id); + furi_string_printf(key, "%s File %d Size", furi_string_get_cstr(prefix), f->id); if(!flipper_format_read_uint32( - file, string_get_cstr(key), &f->settings.record.size, 1)) + file, furi_string_get_cstr(key), &f->settings.record.size, 1)) break; - string_printf(key, "%s File %d Max", string_get_cstr(prefix), f->id); + furi_string_printf(key, "%s File %d Max", furi_string_get_cstr(prefix), f->id); if(!flipper_format_read_uint32( - file, string_get_cstr(key), &f->settings.record.max, 1)) + file, furi_string_get_cstr(key), &f->settings.record.max, 1)) break; - string_printf(key, "%s File %d Cur", string_get_cstr(prefix), f->id); + furi_string_printf(key, "%s File %d Cur", furi_string_get_cstr(prefix), f->id); if(!flipper_format_read_uint32( - file, string_get_cstr(key), &f->settings.record.cur, 1)) + file, furi_string_get_cstr(key), &f->settings.record.cur, 1)) break; } - string_printf(key, "%s File %d", string_get_cstr(prefix), f->id); - if(flipper_format_key_exist(file, string_get_cstr(key))) { + furi_string_printf(key, "%s File %d", furi_string_get_cstr(prefix), f->id); + if(flipper_format_key_exist(file, furi_string_get_cstr(key))) { uint32_t size; - if(!flipper_format_get_value_count(file, string_get_cstr(key), &size)) break; + if(!flipper_format_get_value_count(file, furi_string_get_cstr(key), &size)) break; f->contents = malloc(size); - if(!flipper_format_read_hex(file, string_get_cstr(key), f->contents, size)) break; + if(!flipper_format_read_hex(file, furi_string_get_cstr(key), f->contents, size)) + break; } *file_head = f; file_head = &f->next; @@ -509,8 +533,8 @@ bool nfc_device_load_mifare_df_app(FlipperFormat* file, MifareDesfireApplication free(f); } free(tmp); - string_clear(prefix); - string_clear(key); + furi_string_free(prefix); + furi_string_free(key); return parsed; } @@ -646,8 +670,8 @@ bool nfc_device_load_bank_card_data(FlipperFormat* file, NfcDevice* dev) { EmvData* data = &dev->dev_data.emv_data; memset(data, 0, sizeof(EmvData)); uint32_t data_cnt = 0; - string_t temp_str; - string_init(temp_str); + FuriString* temp_str; + temp_str = furi_string_alloc(); do { // Load essential data @@ -655,7 +679,7 @@ bool nfc_device_load_bank_card_data(FlipperFormat* file, NfcDevice* dev) { data->aid_len = data_cnt; if(!flipper_format_read_hex(file, "AID", data->aid, data->aid_len)) break; if(!flipper_format_read_string(file, "Name", temp_str)) break; - strlcpy(data->name, string_get_cstr(temp_str), sizeof(data->name)); + strlcpy(data->name, furi_string_get_cstr(temp_str), sizeof(data->name)); if(!flipper_format_get_value_count(file, "Number", &data_cnt)) break; data->number_len = data_cnt; if(!flipper_format_read_hex(file, "Number", data->number, data->number_len)) break; @@ -674,15 +698,15 @@ bool nfc_device_load_bank_card_data(FlipperFormat* file, NfcDevice* dev) { } } while(false); - string_clear(temp_str); + furi_string_free(temp_str); return parsed; } static void nfc_device_write_mifare_classic_block( - string_t block_str, + FuriString* block_str, MfClassicData* data, uint8_t block_num) { - string_reset(block_str); + furi_string_reset(block_str); bool is_sec_trailer = mf_classic_is_sector_trailer(block_num); if(is_sec_trailer) { uint8_t sector_num = mf_classic_get_sector_by_block(block_num); @@ -690,45 +714,45 @@ static void nfc_device_write_mifare_classic_block( // Write key A for(size_t i = 0; i < sizeof(sec_tr->key_a); i++) { if(mf_classic_is_key_found(data, sector_num, MfClassicKeyA)) { - string_cat_printf(block_str, "%02X ", sec_tr->key_a[i]); + furi_string_cat_printf(block_str, "%02X ", sec_tr->key_a[i]); } else { - string_cat_printf(block_str, "?? "); + furi_string_cat_printf(block_str, "?? "); } } // Write Access bytes for(size_t i = 0; i < MF_CLASSIC_ACCESS_BYTES_SIZE; i++) { if(mf_classic_is_block_read(data, block_num)) { - string_cat_printf(block_str, "%02X ", sec_tr->access_bits[i]); + furi_string_cat_printf(block_str, "%02X ", sec_tr->access_bits[i]); } else { - string_cat_printf(block_str, "?? "); + furi_string_cat_printf(block_str, "?? "); } } // Write key B for(size_t i = 0; i < sizeof(sec_tr->key_b); i++) { if(mf_classic_is_key_found(data, sector_num, MfClassicKeyB)) { - string_cat_printf(block_str, "%02X ", sec_tr->key_b[i]); + furi_string_cat_printf(block_str, "%02X ", sec_tr->key_b[i]); } else { - string_cat_printf(block_str, "?? "); + furi_string_cat_printf(block_str, "?? "); } } } else { // Write data block for(size_t i = 0; i < MF_CLASSIC_BLOCK_SIZE; i++) { if(mf_classic_is_block_read(data, block_num)) { - string_cat_printf(block_str, "%02X ", data->block[block_num].value[i]); + furi_string_cat_printf(block_str, "%02X ", data->block[block_num].value[i]); } else { - string_cat_printf(block_str, "?? "); + furi_string_cat_printf(block_str, "?? "); } } } - string_strim(block_str); + furi_string_trim(block_str); } static bool nfc_device_save_mifare_classic_data(FlipperFormat* file, NfcDevice* dev) { bool saved = false; MfClassicData* data = &dev->dev_data.mf_classic_data; - string_t temp_str; - string_init(temp_str); + FuriString* temp_str; + temp_str = furi_string_alloc(); uint16_t blocks = 0; // Save Mifare Classic specific data @@ -749,39 +773,39 @@ static bool nfc_device_save_mifare_classic_data(FlipperFormat* file, NfcDevice* file, "Mifare Classic blocks, \'??\' means unknown data")) break; bool block_saved = true; - string_t block_str; - string_init(block_str); + FuriString* block_str; + block_str = furi_string_alloc(); for(size_t i = 0; i < blocks; i++) { - string_printf(temp_str, "Block %d", i); + furi_string_printf(temp_str, "Block %d", i); nfc_device_write_mifare_classic_block(block_str, data, i); - if(!flipper_format_write_string(file, string_get_cstr(temp_str), block_str)) { + if(!flipper_format_write_string(file, furi_string_get_cstr(temp_str), block_str)) { block_saved = false; break; } } - string_clear(block_str); + furi_string_free(block_str); if(!block_saved) break; saved = true; } while(false); - string_clear(temp_str); + furi_string_free(temp_str); return saved; } static void nfc_device_load_mifare_classic_block( - string_t block_str, + FuriString* block_str, MfClassicData* data, uint8_t block_num) { - string_strim(block_str); + furi_string_trim(block_str); MfClassicBlock block_tmp = {}; bool is_sector_trailer = mf_classic_is_sector_trailer(block_num); uint8_t sector_num = mf_classic_get_sector_by_block(block_num); uint16_t block_unknown_bytes_mask = 0; - string_strim(block_str); + furi_string_trim(block_str); for(size_t i = 0; i < MF_CLASSIC_BLOCK_SIZE; i++) { - char hi = string_get_char(block_str, 3 * i); - char low = string_get_char(block_str, 3 * i + 1); + char hi = furi_string_get_char(block_str, 3 * i); + char low = furi_string_get_char(block_str, 3 * i + 1); uint8_t byte = 0; if(hex_char_to_uint8(hi, low, &byte)) { block_tmp.value[i] = byte; @@ -824,19 +848,19 @@ static void nfc_device_load_mifare_classic_block( static bool nfc_device_load_mifare_classic_data(FlipperFormat* file, NfcDevice* dev) { bool parsed = false; MfClassicData* data = &dev->dev_data.mf_classic_data; - string_t temp_str; + FuriString* temp_str; uint32_t data_format_version = 0; - string_init(temp_str); + temp_str = furi_string_alloc(); uint16_t data_blocks = 0; memset(data, 0, sizeof(MfClassicData)); do { // Read Mifare Classic type if(!flipper_format_read_string(file, "Mifare Classic type", temp_str)) break; - if(!string_cmp_str(temp_str, "1K")) { + if(!furi_string_cmp(temp_str, "1K")) { data->type = MfClassicType1k; data_blocks = 64; - } else if(!string_cmp_str(temp_str, "4K")) { + } else if(!furi_string_cmp(temp_str, "4K")) { data->type = MfClassicType4k; data_blocks = 256; } else { @@ -857,17 +881,17 @@ static bool nfc_device_load_mifare_classic_data(FlipperFormat* file, NfcDevice* // Read Mifare Classic blocks bool block_read = true; - string_t block_str; - string_init(block_str); + FuriString* block_str; + block_str = furi_string_alloc(); for(size_t i = 0; i < data_blocks; i++) { - string_printf(temp_str, "Block %d", i); - if(!flipper_format_read_string(file, string_get_cstr(temp_str), block_str)) { + furi_string_printf(temp_str, "Block %d", i); + if(!flipper_format_read_string(file, furi_string_get_cstr(temp_str), block_str)) { block_read = false; break; } nfc_device_load_mifare_classic_block(block_str, data, i); } - string_clear(block_str); + furi_string_free(block_str); if(!block_read) break; // Set keys and blocks as unknown for backward compatibility @@ -880,32 +904,32 @@ static bool nfc_device_load_mifare_classic_data(FlipperFormat* file, NfcDevice* parsed = true; } while(false); - string_clear(temp_str); + furi_string_free(temp_str); return parsed; } -static void nfc_device_get_key_cache_file_path(NfcDevice* dev, string_t file_path) { +static void nfc_device_get_key_cache_file_path(NfcDevice* dev, FuriString* file_path) { uint8_t* uid = dev->dev_data.nfc_data.uid; uint8_t uid_len = dev->dev_data.nfc_data.uid_len; - string_set_str(file_path, NFC_DEVICE_KEYS_FOLDER "/"); + furi_string_set(file_path, NFC_DEVICE_KEYS_FOLDER "/"); for(size_t i = 0; i < uid_len; i++) { - string_cat_printf(file_path, "%02X", uid[i]); + furi_string_cat_printf(file_path, "%02X", uid[i]); } - string_cat_printf(file_path, NFC_DEVICE_KEYS_EXTENSION); + furi_string_cat_printf(file_path, NFC_DEVICE_KEYS_EXTENSION); } static bool nfc_device_save_mifare_classic_keys(NfcDevice* dev) { FlipperFormat* file = flipper_format_file_alloc(dev->storage); MfClassicData* data = &dev->dev_data.mf_classic_data; - string_t temp_str; - string_init(temp_str); + FuriString* temp_str; + temp_str = furi_string_alloc(); nfc_device_get_key_cache_file_path(dev, temp_str); bool save_success = false; do { if(!storage_simply_mkdir(dev->storage, NFC_DEVICE_KEYS_FOLDER)) break; - if(!storage_simply_remove(dev->storage, string_get_cstr(temp_str))) break; - if(!flipper_format_file_open_always(file, string_get_cstr(temp_str))) break; + if(!storage_simply_remove(dev->storage, furi_string_get_cstr(temp_str))) break; + if(!flipper_format_file_open_always(file, furi_string_get_cstr(temp_str))) break; if(!flipper_format_write_header_cstr(file, nfc_keys_file_header, nfc_keys_file_version)) break; if(data->type == MfClassicType1k) { @@ -920,29 +944,29 @@ static bool nfc_device_save_mifare_classic_keys(NfcDevice* dev) { for(size_t i = 0; (i < sector_num) && (key_save_success); i++) { MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, i); if(FURI_BIT(data->key_a_mask, i)) { - string_printf(temp_str, "Key A sector %d", i); - key_save_success = - flipper_format_write_hex(file, string_get_cstr(temp_str), sec_tr->key_a, 6); + furi_string_printf(temp_str, "Key A sector %d", i); + key_save_success = flipper_format_write_hex( + file, furi_string_get_cstr(temp_str), sec_tr->key_a, 6); } if(!key_save_success) break; if(FURI_BIT(data->key_a_mask, i)) { - string_printf(temp_str, "Key B sector %d", i); - key_save_success = - flipper_format_write_hex(file, string_get_cstr(temp_str), sec_tr->key_b, 6); + furi_string_printf(temp_str, "Key B sector %d", i); + key_save_success = flipper_format_write_hex( + file, furi_string_get_cstr(temp_str), sec_tr->key_b, 6); } } save_success = key_save_success; } while(false); flipper_format_free(file); - string_clear(temp_str); + furi_string_free(temp_str); return save_success; } bool nfc_device_load_key_cache(NfcDevice* dev) { furi_assert(dev); - string_t temp_str; - string_init(temp_str); + FuriString* temp_str; + temp_str = furi_string_alloc(); MfClassicData* data = &dev->dev_data.mf_classic_data; nfc_device_get_key_cache_file_path(dev, temp_str); @@ -950,16 +974,17 @@ bool nfc_device_load_key_cache(NfcDevice* dev) { bool load_success = false; do { - if(storage_common_stat(dev->storage, string_get_cstr(temp_str), NULL) != FSE_OK) break; - if(!flipper_format_file_open_existing(file, string_get_cstr(temp_str))) break; + if(storage_common_stat(dev->storage, furi_string_get_cstr(temp_str), NULL) != FSE_OK) + break; + if(!flipper_format_file_open_existing(file, furi_string_get_cstr(temp_str))) break; uint32_t version = 0; if(!flipper_format_read_header(file, temp_str, &version)) break; - if(string_cmp_str(temp_str, nfc_keys_file_header)) break; + if(furi_string_cmp_str(temp_str, nfc_keys_file_header)) break; if(version != nfc_keys_file_version) break; if(!flipper_format_read_string(file, "Mifare Classic type", temp_str)) break; - if(!string_cmp_str(temp_str, "1K")) { + if(!furi_string_cmp(temp_str, "1K")) { data->type = MfClassicType1k; - } else if(!string_cmp_str(temp_str, "4K")) { + } else if(!furi_string_cmp(temp_str, "4K")) { data->type = MfClassicType4k; } else { break; @@ -971,21 +996,21 @@ bool nfc_device_load_key_cache(NfcDevice* dev) { for(size_t i = 0; (i < sectors) && (key_read_success); i++) { MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, i); if(FURI_BIT(data->key_a_mask, i)) { - string_printf(temp_str, "Key A sector %d", i); - key_read_success = - flipper_format_read_hex(file, string_get_cstr(temp_str), sec_tr->key_a, 6); + furi_string_printf(temp_str, "Key A sector %d", i); + key_read_success = flipper_format_read_hex( + file, furi_string_get_cstr(temp_str), sec_tr->key_a, 6); } if(!key_read_success) break; if(FURI_BIT(data->key_b_mask, i)) { - string_printf(temp_str, "Key B sector %d", i); - key_read_success = - flipper_format_read_hex(file, string_get_cstr(temp_str), sec_tr->key_b, 6); + furi_string_printf(temp_str, "Key B sector %d", i); + key_read_success = flipper_format_read_hex( + file, furi_string_get_cstr(temp_str), sec_tr->key_b, 6); } } load_success = key_read_success; } while(false); - string_clear(temp_str); + furi_string_free(temp_str); flipper_format_free(file); return load_success; @@ -997,16 +1022,16 @@ 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) { +static void nfc_device_get_path_without_ext(FuriString* orig_path, FuriString* 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); + size_t ext_start = furi_string_search(orig_path, NFC_APP_EXTENSION); + furi_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) { +static void nfc_device_get_shadow_path(FuriString* orig_path, FuriString* shadow_path) { nfc_device_get_path_without_ext(orig_path, shadow_path); - string_cat_printf(shadow_path, "%s", NFC_APP_SHADOW_EXTENSION); + furi_string_cat_printf(shadow_path, "%s", NFC_APP_SHADOW_EXTENSION); } static bool nfc_device_save_file( @@ -1020,25 +1045,25 @@ static bool nfc_device_save_file( bool saved = false; FlipperFormat* file = flipper_format_file_alloc(dev->storage); FuriHalNfcDevData* data = &dev->dev_data.nfc_data; - string_t temp_str; - string_init(temp_str); + FuriString* temp_str; + temp_str = furi_string_alloc(); do { - if(use_load_path && !string_empty_p(dev->load_path)) { + if(use_load_path && !furi_string_empty(dev->load_path)) { // Get directory name - path_extract_dirname(string_get_cstr(dev->load_path), temp_str); + path_extract_dirname(furi_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; + if(!storage_simply_mkdir(dev->storage, furi_string_get_cstr(temp_str))) break; // Make path to file to save - string_cat_printf(temp_str, "/%s%s", dev_name, extension); + furi_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); + furi_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; + if(!flipper_format_file_open_always(file, furi_string_get_cstr(temp_str))) break; // Write header if(!flipper_format_write_header_cstr(file, nfc_file_header, nfc_file_version)) break; // Write nfc device type @@ -1072,7 +1097,7 @@ static bool nfc_device_save_file( if(!saved) { dialog_message_show_storage_error(dev->dialogs, "Can not save\nkey file"); } - string_clear(temp_str); + furi_string_free(temp_str); flipper_format_free(file); return saved; } @@ -1086,13 +1111,13 @@ bool nfc_device_save_shadow(NfcDevice* dev, const char* dev_name) { 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, bool show_dialog) { +static bool nfc_device_load_data(NfcDevice* dev, FuriString* path, bool show_dialog) { bool parsed = false; FlipperFormat* file = flipper_format_file_alloc(dev->storage); FuriHalNfcDevData* data = &dev->dev_data.nfc_data; uint32_t data_cnt = 0; - string_t temp_str; - string_init(temp_str); + FuriString* temp_str; + temp_str = furi_string_alloc(); bool deprecated_version = false; if(dev->loading_cb) { @@ -1103,17 +1128,17 @@ static bool nfc_device_load_data(NfcDevice* dev, string_t path, bool show_dialog // Check existance of shadow file 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; + storage_common_stat(dev->storage, furi_string_get_cstr(temp_str), NULL) == FSE_OK; // Open shadow file if it exists. If not - open original if(dev->shadow_file_exist) { - if(!flipper_format_file_open_existing(file, string_get_cstr(temp_str))) break; + if(!flipper_format_file_open_existing(file, furi_string_get_cstr(temp_str))) break; } else { - if(!flipper_format_file_open_existing(file, string_get_cstr(path))) break; + if(!flipper_format_file_open_existing(file, furi_string_get_cstr(path))) break; } // Read and verify file header uint32_t version = 0; if(!flipper_format_read_header(file, temp_str, &version)) break; - if(string_cmp_str(temp_str, nfc_file_header) || (version != nfc_file_version)) { + if(furi_string_cmp_str(temp_str, nfc_file_header) || (version != nfc_file_version)) { deprecated_version = true; break; } @@ -1152,7 +1177,7 @@ static bool nfc_device_load_data(NfcDevice* dev, string_t path, bool show_dialog } } - string_clear(temp_str); + furi_string_free(temp_str); flipper_format_free(file); return parsed; } @@ -1162,15 +1187,15 @@ bool nfc_device_load(NfcDevice* dev, const char* file_path, bool show_dialog) { furi_assert(file_path); // Load device data - string_set_str(dev->load_path, file_path); + furi_string_set(dev->load_path, file_path); bool dev_load = nfc_device_load_data(dev, dev->load_path, show_dialog); if(dev_load) { // Set device name - string_t filename; - string_init(filename); + FuriString* filename; + filename = furi_string_alloc(); path_extract_filename_no_ext(file_path, filename); - nfc_device_set_name(dev, string_get_cstr(filename)); - string_clear(filename); + nfc_device_set_name(dev, furi_string_get_cstr(filename)); + furi_string_free(filename); } return dev_load; @@ -1180,8 +1205,8 @@ bool nfc_file_select(NfcDevice* dev) { furi_assert(dev); // Input events and views are managed by file_browser - string_t nfc_app_folder; - string_init_set_str(nfc_app_folder, NFC_APP_FOLDER); + FuriString* nfc_app_folder; + nfc_app_folder = furi_string_alloc_set(NFC_APP_FOLDER); const DialogsFileBrowserOptions browser_options = { .extension = NFC_APP_EXTENSION, @@ -1195,17 +1220,17 @@ bool nfc_file_select(NfcDevice* dev) { bool res = dialog_file_browser_show(dev->dialogs, dev->load_path, nfc_app_folder, &browser_options); - string_clear(nfc_app_folder); + furi_string_free(nfc_app_folder); if(res) { - string_t filename; - string_init(filename); + FuriString* filename; + filename = furi_string_alloc(); path_extract_filename(dev->load_path, filename, true); - strncpy(dev->dev_name, string_get_cstr(filename), NFC_DEV_NAME_MAX_LEN); + strncpy(dev->dev_name, furi_string_get_cstr(filename), NFC_DEV_NAME_MAX_LEN); res = nfc_device_load_data(dev, dev->load_path, true); if(res) { nfc_device_set_name(dev, dev->dev_name); } - string_clear(filename); + furi_string_free(filename); } return res; @@ -1223,7 +1248,7 @@ void nfc_device_data_clear(NfcDeviceData* dev_data) { } memset(&dev_data->nfc_data, 0, sizeof(FuriHalNfcDevData)); dev_data->protocol = NfcDeviceProtocolUnknown; - string_reset(dev_data->parsed_data); + furi_string_reset(dev_data->parsed_data); } void nfc_device_clear(NfcDevice* dev) { @@ -1232,33 +1257,34 @@ void nfc_device_clear(NfcDevice* dev) { nfc_device_set_name(dev, ""); nfc_device_data_clear(&dev->dev_data); dev->format = NfcDeviceSaveFormatUid; - string_reset(dev->load_path); + furi_string_reset(dev->load_path); } bool nfc_device_delete(NfcDevice* dev, bool use_load_path) { furi_assert(dev); bool deleted = false; - string_t file_path; - string_init(file_path); + FuriString* file_path; + file_path = furi_string_alloc(); do { // Delete original file - if(use_load_path && !string_empty_p(dev->load_path)) { - string_set(file_path, dev->load_path); + if(use_load_path && !furi_string_empty(dev->load_path)) { + furi_string_set(file_path, dev->load_path); } else { - string_printf(file_path, "%s/%s%s", NFC_APP_FOLDER, dev->dev_name, NFC_APP_EXTENSION); + furi_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; + if(!storage_simply_remove(dev->storage, furi_string_get_cstr(file_path))) break; // Delete shadow file if it exists if(dev->shadow_file_exist) { - if(use_load_path && !string_empty_p(dev->load_path)) { + if(use_load_path && !furi_string_empty(dev->load_path)) { nfc_device_get_shadow_path(dev->load_path, file_path); } else { - string_printf( + furi_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; + if(!storage_simply_remove(dev->storage, furi_string_get_cstr(file_path))) break; } deleted = true; } while(0); @@ -1267,7 +1293,7 @@ bool nfc_device_delete(NfcDevice* dev, bool use_load_path) { dialog_message_show_storage_error(dev->dialogs, "Can not remove file"); } - string_clear(file_path); + furi_string_free(file_path); return deleted; } @@ -1276,29 +1302,29 @@ bool nfc_device_restore(NfcDevice* dev, bool use_load_path) { furi_assert(dev->shadow_file_exist); bool restored = false; - string_t path; + FuriString* path; - string_init(path); + path = furi_string_alloc(); do { - if(use_load_path && !string_empty_p(dev->load_path)) { + if(use_load_path && !furi_string_empty(dev->load_path)) { nfc_device_get_shadow_path(dev->load_path, path); } else { - string_printf( + furi_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; + if(!storage_simply_remove(dev->storage, furi_string_get_cstr(path))) break; dev->shadow_file_exist = false; - if(use_load_path && !string_empty_p(dev->load_path)) { - string_set(path, dev->load_path); + if(use_load_path && !furi_string_empty(dev->load_path)) { + furi_string_set(path, dev->load_path); } else { - string_printf(path, "%s/%s%s", NFC_APP_FOLDER, dev->dev_name, NFC_APP_EXTENSION); + furi_string_printf(path, "%s/%s%s", NFC_APP_FOLDER, dev->dev_name, NFC_APP_EXTENSION); } if(!nfc_device_load_data(dev, path, true)) break; restored = true; } while(0); - string_clear(path); + furi_string_free(path); return restored; } diff --git a/lib/nfc/nfc_device.h b/lib/nfc/nfc_device.h index f9a0b24baa8..6cac72c6b74 100644 --- a/lib/nfc/nfc_device.h +++ b/lib/nfc/nfc_device.h @@ -60,7 +60,7 @@ typedef struct { MfClassicData mf_classic_data; MifareDesfireData mf_df_data; }; - string_t parsed_data; + FuriString* parsed_data; } NfcDeviceData; typedef struct { @@ -68,7 +68,7 @@ typedef struct { DialogsApp* dialogs; NfcDeviceData dev_data; char dev_name[NFC_DEV_NAME_MAX_LEN + 1]; - string_t load_path; + FuriString* load_path; NfcDeviceSaveFormat format; bool shadow_file_exist; diff --git a/lib/nfc/parsers/all_in_one.c b/lib/nfc/parsers/all_in_one.c index b49a32f7c1c..f63fb7e6de3 100644 --- a/lib/nfc/parsers/all_in_one.c +++ b/lib/nfc/parsers/all_in_one.c @@ -107,7 +107,7 @@ bool all_in_one_parser_parse(NfcDeviceData* dev_data) { dev_data->mf_ul_data.data[4 * 4 + 5] << 4 | (dev_data->mf_ul_data.data[4 * 4 + 6] >> 4); // Format string for rides count - string_printf( + furi_string_printf( dev_data->parsed_data, "\e#All-In-One\nNumber: %u\nRides left: %u", serial, ride_count); return true; } \ No newline at end of file diff --git a/lib/nfc/parsers/nfc_supported_card.h b/lib/nfc/parsers/nfc_supported_card.h index d34b5794a20..4af59aded63 100644 --- a/lib/nfc/parsers/nfc_supported_card.h +++ b/lib/nfc/parsers/nfc_supported_card.h @@ -4,8 +4,6 @@ #include "../nfc_worker.h" #include "../nfc_device.h" -#include - typedef enum { NfcSupportedCardTypePlantain, NfcSupportedCardTypeTroika, diff --git a/lib/nfc/parsers/plantain_4k_parser.c b/lib/nfc/parsers/plantain_4k_parser.c index 77387707b14..c970d74e558 100644 --- a/lib/nfc/parsers/plantain_4k_parser.c +++ b/lib/nfc/parsers/plantain_4k_parser.c @@ -118,36 +118,36 @@ bool plantain_4k_parser_parse(NfcDeviceData* dev_data) { card_number = (card_number << 8) | card_number_arr[i]; } // Convert card number to string - string_t card_number_str; - string_init(card_number_str); + FuriString* card_number_str; + card_number_str = furi_string_alloc(); // Should look like "361301047292848684" // %llu doesn't work for some reason in sprintf, so we use string_push_uint64 instead string_push_uint64(card_number, card_number_str); // Add suffix with luhn checksum (1 digit) to the card number string - string_t card_number_suffix; - string_init(card_number_suffix); + FuriString* card_number_suffix; + card_number_suffix = furi_string_alloc(); // The number to calculate the checksum on doesn't fit into uint64_t, idk //uint8_t luhn_checksum = plantain_calculate_luhn(card_number); // // Convert luhn checksum to string - // string_t luhn_checksum_str; - // string_init(luhn_checksum_str); + // FuriString* luhn_checksum_str; + // luhn_checksum_str = furi_string_alloc(); // string_push_uint64(luhn_checksum, luhn_checksum_str); - string_cat_printf(card_number_suffix, "-"); + furi_string_cat_printf(card_number_suffix, "-"); // FURI_LOG_D("plant4k", "Card checksum: %d", luhn_checksum); - string_cat_printf(card_number_str, string_get_cstr(card_number_suffix)); + furi_string_cat_printf(card_number_str, furi_string_get_cstr(card_number_suffix)); // Free all not needed strings - string_clear(card_number_suffix); - // string_clear(luhn_checksum_str); + furi_string_free(card_number_suffix); + // furi_string_free(luhn_checksum_str); - string_printf( + furi_string_printf( dev_data->parsed_data, "\e#Plantain\nN:%s\nBalance:%d\n", - string_get_cstr(card_number_str), + furi_string_get_cstr(card_number_str), balance); - string_clear(card_number_str); + furi_string_free(card_number_str); return true; } diff --git a/lib/nfc/parsers/plantain_parser.c b/lib/nfc/parsers/plantain_parser.c index ff81b8e9b5b..8ef8ee14282 100644 --- a/lib/nfc/parsers/plantain_parser.c +++ b/lib/nfc/parsers/plantain_parser.c @@ -55,7 +55,7 @@ bool plantain_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { return mf_classic_read_card(tx_rx, &reader, &nfc_worker->dev_data->mf_classic_data) == 16; } -void string_push_uint64(uint64_t input, string_t output) { +void string_push_uint64(uint64_t input, FuriString* output) { const uint8_t base = 10; do { @@ -66,14 +66,15 @@ void string_push_uint64(uint64_t input, string_t output) { c += '0'; else c += 'A' - 10; - string_push_back(output, c); + furi_string_push_back(output, c); } while(input); // reverse string - for(uint8_t i = 0; i < string_size(output) / 2; i++) { - char c = string_get_char(output, i); - string_set_char(output, i, string_get_char(output, string_size(output) - i - 1)); - string_set_char(output, string_size(output) - i - 1, c); + for(uint8_t i = 0; i < furi_string_size(output) / 2; i++) { + char c = furi_string_get_char(output, i); + furi_string_set_char( + output, i, furi_string_get_char(output, furi_string_size(output) - i - 1)); + furi_string_set_char(output, furi_string_size(output) - i - 1, c); } } @@ -112,36 +113,36 @@ bool plantain_parser_parse(NfcDeviceData* dev_data) { card_number = (card_number << 8) | card_number_arr[i]; } // Convert card number to string - string_t card_number_str; - string_init(card_number_str); + FuriString* card_number_str; + card_number_str = furi_string_alloc(); // Should look like "361301047292848684" // %llu doesn't work for some reason in sprintf, so we use string_push_uint64 instead string_push_uint64(card_number, card_number_str); // Add suffix with luhn checksum (1 digit) to the card number string - string_t card_number_suffix; - string_init(card_number_suffix); + FuriString* card_number_suffix; + card_number_suffix = furi_string_alloc(); // The number to calculate the checksum on doesn't fit into uint64_t, idk //uint8_t luhn_checksum = plantain_calculate_luhn(card_number); // // Convert luhn checksum to string - // string_t luhn_checksum_str; - // string_init(luhn_checksum_str); + // FuriString* luhn_checksum_str; + // luhn_checksum_str = furi_string_alloc(); // string_push_uint64(luhn_checksum, luhn_checksum_str); - string_cat_printf(card_number_suffix, "-"); + furi_string_cat_printf(card_number_suffix, "-"); // FURI_LOG_D("plant4k", "Card checksum: %d", luhn_checksum); - string_cat_printf(card_number_str, string_get_cstr(card_number_suffix)); + furi_string_cat_printf(card_number_str, furi_string_get_cstr(card_number_suffix)); // Free all not needed strings - string_clear(card_number_suffix); - // string_clear(luhn_checksum_str); + furi_string_free(card_number_suffix); + // furi_string_free(luhn_checksum_str); - string_printf( + furi_string_printf( dev_data->parsed_data, "\e#Plantain\nN:%s\nBalance:%d\n", - string_get_cstr(card_number_str), + furi_string_get_cstr(card_number_str), balance); - string_clear(card_number_str); + furi_string_free(card_number_str); return true; } diff --git a/lib/nfc/parsers/plantain_parser.h b/lib/nfc/parsers/plantain_parser.h index d37f0dd481b..65f396f1d93 100644 --- a/lib/nfc/parsers/plantain_parser.h +++ b/lib/nfc/parsers/plantain_parser.h @@ -8,6 +8,6 @@ bool plantain_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx); bool plantain_parser_parse(NfcDeviceData* dev_data); -void string_push_uint64(uint64_t input, string_t output); +void string_push_uint64(uint64_t input, FuriString* output); uint8_t plantain_calculate_luhn(uint64_t number); diff --git a/lib/nfc/parsers/troika_4k_parser.c b/lib/nfc/parsers/troika_4k_parser.c index 8c32381f29f..d87b4eba7b3 100644 --- a/lib/nfc/parsers/troika_4k_parser.c +++ b/lib/nfc/parsers/troika_4k_parser.c @@ -98,7 +98,8 @@ bool troika_4k_parser_parse(NfcDeviceData* dev_data) { } number >>= 4; - string_printf(dev_data->parsed_data, "\e#Troika\nNum: %ld\nBalance: %d rur.", number, balance); + furi_string_printf( + dev_data->parsed_data, "\e#Troika\nNum: %ld\nBalance: %d rur.", number, balance); return true; } diff --git a/lib/nfc/parsers/troika_parser.c b/lib/nfc/parsers/troika_parser.c index f396b168034..9c16296f34c 100644 --- a/lib/nfc/parsers/troika_parser.c +++ b/lib/nfc/parsers/troika_parser.c @@ -78,7 +78,7 @@ bool troika_parser_parse(NfcDeviceData* dev_data) { } number >>= 4; - string_printf( + furi_string_printf( dev_data->parsed_data, "\e#Troika\nNum: %ld\nBalance: %d rur.", number, balance); troika_parsed = true; } while(false); diff --git a/lib/nfc/parsers/two_cities.c b/lib/nfc/parsers/two_cities.c index d052dff194f..3dc97586982 100644 --- a/lib/nfc/parsers/two_cities.c +++ b/lib/nfc/parsers/two_cities.c @@ -118,29 +118,29 @@ bool two_cities_parser_parse(NfcDeviceData* dev_data) { card_number = (card_number << 8) | card_number_arr[i]; } // Convert card number to string - string_t card_number_str; - string_init(card_number_str); + FuriString* card_number_str; + card_number_str = furi_string_alloc(); // Should look like "361301047292848684" // %llu doesn't work for some reason in sprintf, so we use string_push_uint64 instead string_push_uint64(card_number, card_number_str); // Add suffix with luhn checksum (1 digit) to the card number string - string_t card_number_suffix; - string_init(card_number_suffix); + FuriString* card_number_suffix; + card_number_suffix = furi_string_alloc(); // The number to calculate the checksum on doesn't fit into uint64_t, idk //uint8_t luhn_checksum = two_cities_calculate_luhn(card_number); // // Convert luhn checksum to string - // string_t luhn_checksum_str; - // string_init(luhn_checksum_str); + // FuriString* luhn_checksum_str; + // luhn_checksum_str = furi_string_alloc(); // string_push_uint64(luhn_checksum, luhn_checksum_str); - string_cat_printf(card_number_suffix, "-"); + furi_string_cat_printf(card_number_suffix, "-"); // FURI_LOG_D("plant4k", "Card checksum: %d", luhn_checksum); - string_cat_printf(card_number_str, string_get_cstr(card_number_suffix)); + furi_string_cat_printf(card_number_str, furi_string_get_cstr(card_number_suffix)); // Free all not needed strings - string_clear(card_number_suffix); - // string_clear(luhn_checksum_str); + furi_string_free(card_number_suffix); + // furi_string_free(luhn_checksum_str); // ===== // --PLANTAIN-- @@ -158,14 +158,14 @@ bool two_cities_parser_parse(NfcDeviceData* dev_data) { } troika_number >>= 4; - string_printf( + furi_string_printf( dev_data->parsed_data, "\e#Troika+Plantain\nPN: %s\nPB: %d rur.\nTN: %d\nTB: %d rur.\n", - string_get_cstr(card_number_str), + furi_string_get_cstr(card_number_str), balance, troika_number, troika_balance); - string_clear(card_number_str); + furi_string_free(card_number_str); return true; } diff --git a/lib/nfc/protocols/mifare_desfire.c b/lib/nfc/protocols/mifare_desfire.c index f969cdde642..9dd37f1ee83 100644 --- a/lib/nfc/protocols/mifare_desfire.c +++ b/lib/nfc/protocols/mifare_desfire.c @@ -42,14 +42,14 @@ void mf_df_clear(MifareDesfireData* data) { data->app_head = NULL; } -void mf_df_cat_data(MifareDesfireData* data, string_t out) { +void mf_df_cat_data(MifareDesfireData* data, FuriString* out) { mf_df_cat_card_info(data, out); for(MifareDesfireApplication* app = data->app_head; app; app = app->next) { mf_df_cat_application(app, out); } } -void mf_df_cat_card_info(MifareDesfireData* data, string_t out) { +void mf_df_cat_card_info(MifareDesfireData* data, FuriString* out) { mf_df_cat_version(&data->version, out); if(data->free_memory) { mf_df_cat_free_mem(data->free_memory, out); @@ -59,8 +59,8 @@ void mf_df_cat_card_info(MifareDesfireData* data, string_t out) { } } -void mf_df_cat_version(MifareDesfireVersion* version, string_t out) { - string_cat_printf( +void mf_df_cat_version(MifareDesfireVersion* version, FuriString* out) { + furi_string_cat_printf( out, "%02x:%02x:%02x:%02x:%02x:%02x:%02x\n", version->uid[0], @@ -70,7 +70,7 @@ void mf_df_cat_version(MifareDesfireVersion* version, string_t out) { version->uid[4], version->uid[5], version->uid[6]); - string_cat_printf( + furi_string_cat_printf( out, "hw %02x type %02x sub %02x\n" " maj %02x min %02x\n" @@ -82,7 +82,7 @@ void mf_df_cat_version(MifareDesfireVersion* version, string_t out) { version->hw_minor, version->hw_storage, version->hw_proto); - string_cat_printf( + furi_string_cat_printf( out, "sw %02x type %02x sub %02x\n" " maj %02x min %02x\n" @@ -94,7 +94,7 @@ void mf_df_cat_version(MifareDesfireVersion* version, string_t out) { version->sw_minor, version->sw_storage, version->sw_proto); - string_cat_printf( + furi_string_cat_printf( out, "batch %02x:%02x:%02x:%02x:%02x\n" "week %d year %d\n", @@ -107,40 +107,40 @@ void mf_df_cat_version(MifareDesfireVersion* version, string_t out) { version->prod_year); } -void mf_df_cat_free_mem(MifareDesfireFreeMemory* free_mem, string_t out) { - string_cat_printf(out, "freeMem %d\n", free_mem->bytes); +void mf_df_cat_free_mem(MifareDesfireFreeMemory* free_mem, FuriString* out) { + furi_string_cat_printf(out, "freeMem %d\n", free_mem->bytes); } -void mf_df_cat_key_settings(MifareDesfireKeySettings* ks, string_t out) { - string_cat_printf(out, "changeKeyID %d\n", ks->change_key_id); - string_cat_printf(out, "configChangeable %d\n", ks->config_changeable); - 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); +void mf_df_cat_key_settings(MifareDesfireKeySettings* ks, FuriString* out) { + furi_string_cat_printf(out, "changeKeyID %d\n", ks->change_key_id); + furi_string_cat_printf(out, "configChangeable %d\n", ks->config_changeable); + furi_string_cat_printf(out, "freeCreateDelete %d\n", ks->free_create_delete); + furi_string_cat_printf(out, "freeDirectoryList %d\n", ks->free_directory_list); + furi_string_cat_printf(out, "masterChangeable %d\n", ks->master_key_changeable); if(ks->flags) { - string_cat_printf(out, "flags %d\n", ks->flags); + furi_string_cat_printf(out, "flags %d\n", ks->flags); } - string_cat_printf(out, "maxKeys %d\n", ks->max_keys); + furi_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); + furi_string_cat_printf(out, "key %d version %d\n", kv->id, kv->version); } } -void mf_df_cat_application_info(MifareDesfireApplication* app, string_t out) { - string_cat_printf(out, "Application %02x%02x%02x\n", app->id[0], app->id[1], app->id[2]); +void mf_df_cat_application_info(MifareDesfireApplication* app, FuriString* out) { + furi_string_cat_printf(out, "Application %02x%02x%02x\n", app->id[0], app->id[1], app->id[2]); if(app->key_settings) { mf_df_cat_key_settings(app->key_settings, out); } } -void mf_df_cat_application(MifareDesfireApplication* app, string_t out) { +void mf_df_cat_application(MifareDesfireApplication* app, FuriString* out) { mf_df_cat_application_info(app, out); for(MifareDesfireFile* file = app->file_head; file; file = file->next) { mf_df_cat_file(file, out); } } -void mf_df_cat_file(MifareDesfireFile* file, string_t out) { +void mf_df_cat_file(MifareDesfireFile* file, FuriString* out) { char* type = "unknown"; switch(file->type) { case MifareDesfireFileTypeStandard: @@ -171,9 +171,9 @@ void mf_df_cat_file(MifareDesfireFile* file, string_t out) { comm = "enciphered"; break; } - string_cat_printf(out, "File %d\n", file->id); - string_cat_printf(out, "%s %s\n", type, comm); - string_cat_printf( + furi_string_cat_printf(out, "File %d\n", file->id); + furi_string_cat_printf(out, "%s %s\n", type, comm); + furi_string_cat_printf( out, "r %d w %d rw %d c %d\n", file->access_rights >> 12 & 0xF, @@ -186,13 +186,13 @@ void mf_df_cat_file(MifareDesfireFile* file, string_t out) { case MifareDesfireFileTypeStandard: case MifareDesfireFileTypeBackup: size = file->settings.data.size; - string_cat_printf(out, "size %d\n", size); + furi_string_cat_printf(out, "size %d\n", size); break; case MifareDesfireFileTypeValue: size = 4; - string_cat_printf( + furi_string_cat_printf( out, "lo %d hi %d\n", file->settings.value.lo_limit, file->settings.value.hi_limit); - string_cat_printf( + furi_string_cat_printf( out, "limit %d enabled %d\n", file->settings.value.limited_credit_value, @@ -202,33 +202,33 @@ void mf_df_cat_file(MifareDesfireFile* file, string_t out) { case MifareDesfireFileTypeCyclicRecord: size = file->settings.record.size; num = file->settings.record.cur; - string_cat_printf(out, "size %d\n", size); - string_cat_printf(out, "num %d max %d\n", num, file->settings.record.max); + furi_string_cat_printf(out, "size %d\n", size); + furi_string_cat_printf(out, "num %d max %d\n", num, file->settings.record.max); break; } uint8_t* data = file->contents; if(data) { for(int rec = 0; rec < num; rec++) { - string_cat_printf(out, "record %d\n", rec); + furi_string_cat_printf(out, "record %d\n", rec); for(int ch = 0; ch < size; ch += 4) { - string_cat_printf(out, "%03x|", ch); + furi_string_cat_printf(out, "%03x|", ch); for(int i = 0; i < 4; i++) { if(ch + i < size) { - string_cat_printf(out, "%02x ", data[rec * size + ch + i]); + furi_string_cat_printf(out, "%02x ", data[rec * size + ch + i]); } else { - string_cat_printf(out, " "); + furi_string_cat_printf(out, " "); } } for(int i = 0; i < 4 && ch + i < size; i++) { if(isprint(data[rec * size + ch + i])) { - string_cat_printf(out, "%c", data[rec * size + ch + i]); + furi_string_cat_printf(out, "%c", data[rec * size + ch + i]); } else { - string_cat_printf(out, "."); + furi_string_cat_printf(out, "."); } } - string_cat_printf(out, "\n"); + furi_string_cat_printf(out, "\n"); } - string_cat_printf(out, " \n"); + furi_string_cat_printf(out, " \n"); } } } diff --git a/lib/nfc/protocols/mifare_desfire.h b/lib/nfc/protocols/mifare_desfire.h index e59743a2fed..963a18f585b 100644 --- a/lib/nfc/protocols/mifare_desfire.h +++ b/lib/nfc/protocols/mifare_desfire.h @@ -1,6 +1,5 @@ #pragma once -#include #include #include @@ -120,14 +119,14 @@ typedef struct { void mf_df_clear(MifareDesfireData* data); -void mf_df_cat_data(MifareDesfireData* data, string_t out); -void mf_df_cat_card_info(MifareDesfireData* data, string_t out); -void mf_df_cat_version(MifareDesfireVersion* version, string_t out); -void mf_df_cat_free_mem(MifareDesfireFreeMemory* free_mem, string_t out); -void mf_df_cat_key_settings(MifareDesfireKeySettings* ks, string_t out); -void mf_df_cat_application_info(MifareDesfireApplication* app, string_t out); -void mf_df_cat_application(MifareDesfireApplication* app, string_t out); -void mf_df_cat_file(MifareDesfireFile* file, string_t out); +void mf_df_cat_data(MifareDesfireData* data, FuriString* out); +void mf_df_cat_card_info(MifareDesfireData* data, FuriString* out); +void mf_df_cat_version(MifareDesfireVersion* version, FuriString* out); +void mf_df_cat_free_mem(MifareDesfireFreeMemory* free_mem, FuriString* out); +void mf_df_cat_key_settings(MifareDesfireKeySettings* ks, FuriString* out); +void mf_df_cat_application_info(MifareDesfireApplication* app, FuriString* out); +void mf_df_cat_application(MifareDesfireApplication* app, FuriString* out); +void mf_df_cat_file(MifareDesfireFile* file, FuriString* out); bool mf_df_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK); diff --git a/lib/nfc/protocols/mifare_ultralight.c b/lib/nfc/protocols/mifare_ultralight.c index b3d80deb3c4..ffabe88ab6b 100644 --- a/lib/nfc/protocols/mifare_ultralight.c +++ b/lib/nfc/protocols/mifare_ultralight.c @@ -4,7 +4,6 @@ #include "nfc_util.h" #include #include "furi_hal_nfc.h" -#include #define TAG "MfUltralight" @@ -1005,7 +1004,7 @@ static bool mf_ul_check_lock(MfUltralightEmulator* emulator, int16_t write_page) return (dynamic_lock_bytes & (1 << shift)) == 0; } -static void mf_ul_make_ascii_mirror(MfUltralightEmulator* emulator, string_t str) { +static void mf_ul_make_ascii_mirror(MfUltralightEmulator* emulator, FuriString* str) { // Locals to improve readability uint8_t mirror_page = emulator->config->mirror_page; uint8_t mirror_byte = emulator->config->mirror.mirror_byte; @@ -1020,14 +1019,14 @@ static void mf_ul_make_ascii_mirror(MfUltralightEmulator* emulator, string_t str if(mirror_conf == MfUltralightMirrorUid) return; // NTAG21x has the peculiar behavior when UID+counter selected, if UID does not fit but // counter will fit, it will actually mirror the counter - string_cat_str(str, " "); + furi_string_cat(str, " "); } else { for(int i = 0; i < 3; ++i) { - string_cat_printf(str, "%02X", emulator->data.data[i]); + furi_string_cat_printf(str, "%02X", emulator->data.data[i]); } // Skip BCC0 for(int i = 4; i < 8; ++i) { - string_cat_printf(str, "%02X", emulator->data.data[i]); + furi_string_cat_printf(str, "%02X", emulator->data.data[i]); } uid_printed = true; } @@ -1049,9 +1048,9 @@ static void mf_ul_make_ascii_mirror(MfUltralightEmulator* emulator, string_t str if(mirror_page == last_user_page_index - 1 && mirror_byte > 2) return; if(mirror_conf == MfUltralightMirrorUidCounter) - string_cat_str(str, uid_printed ? "x" : " "); + furi_string_cat(str, uid_printed ? "x" : " "); - string_cat_printf(str, "%06X", emulator->data.counter[2]); + furi_string_cat_printf(str, "%06X", emulator->data.counter[2]); } } } @@ -1267,14 +1266,14 @@ bool mf_ul_prepare_emulation_response( bool reset_idle = false; #ifdef FURI_DEBUG - string_t debug_buf; - string_init(debug_buf); + FuriString* debug_buf; + debug_buf = furi_string_alloc(); for(int i = 0; i < (buff_rx_len + 7) / 8; ++i) { - string_cat_printf(debug_buf, "%02x ", buff_rx[i]); + furi_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); + furi_string_trim(debug_buf); + FURI_LOG_T(TAG, "Emu RX (%d): %s", buff_rx_len, furi_string_get_cstr(debug_buf)); + furi_string_reset(debug_buf); #endif // Check composite commands @@ -1328,7 +1327,7 @@ bool mf_ul_prepare_emulation_response( uint8_t src_page = start_page; uint8_t last_page_plus_one = start_page + 4; uint8_t pwd_page = emulator->page_num - 2; - string_t ascii_mirror; + FuriString* ascii_mirror = NULL; size_t ascii_mirror_len = 0; const char* ascii_mirror_cptr = NULL; uint8_t ascii_mirror_curr_page = 0; @@ -1353,10 +1352,10 @@ bool mf_ul_prepare_emulation_response( if(last_page_plus_one > ascii_mirror_curr_page && start_page + 3 >= ascii_mirror_curr_page && start_page <= ascii_mirror_curr_page + 6) { - string_init(ascii_mirror); + ascii_mirror = furi_string_alloc(); mf_ul_make_ascii_mirror(emulator, ascii_mirror); - ascii_mirror_len = string_length_u(ascii_mirror); - ascii_mirror_cptr = string_get_cstr(ascii_mirror); + ascii_mirror_len = furi_string_utf8_length(ascii_mirror); + ascii_mirror_cptr = furi_string_get_cstr(ascii_mirror); // Move pointer to where it should be to start copying if(ascii_mirror_len > 0 && ascii_mirror_curr_page < start_page && @@ -1414,8 +1413,8 @@ bool mf_ul_prepare_emulation_response( ++src_page; if(src_page >= last_page_plus_one) src_page = 0; } - if(ascii_mirror_cptr != NULL) { - string_clear(ascii_mirror); + if(ascii_mirror != NULL) { + furi_string_free(ascii_mirror); } *data_type = FURI_HAL_NFC_TXRX_DEFAULT; command_parsed = true; @@ -1512,12 +1511,13 @@ bool mf_ul_prepare_emulation_response( // Copy ASCII mirror // Less stringent check here, because expecting FAST_READ to // only be issued once rather than repeatedly - string_t ascii_mirror; - string_init(ascii_mirror); + FuriString* ascii_mirror; + ascii_mirror = furi_string_alloc(); mf_ul_make_ascii_mirror(emulator, ascii_mirror); - size_t ascii_mirror_len = string_length_u(ascii_mirror); + size_t ascii_mirror_len = + furi_string_utf8_length(ascii_mirror); const char* ascii_mirror_cptr = - string_get_cstr(ascii_mirror); + furi_string_get_cstr(ascii_mirror); int16_t mirror_start_offset = (emulator->config->mirror_page - start_page) * 4 + emulator->config->mirror.mirror_byte; @@ -1547,7 +1547,7 @@ bool mf_ul_prepare_emulation_response( ++ascii_mirror_cptr; } } - string_clear(ascii_mirror); + furi_string_free(ascii_mirror); } if(emulator->supported_features & MfUltralightSupportAuth) { @@ -1851,11 +1851,11 @@ bool mf_ul_prepare_emulation_response( } 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]); + furi_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); + furi_string_trim(debug_buf); + FURI_LOG_T(TAG, "Emu TX (%d): %s", *buff_tx_len, furi_string_get_cstr(debug_buf)); + furi_string_free(debug_buf); } else { FURI_LOG_T(TAG, "Emu TX: HALT"); } diff --git a/lib/subghz/blocks/generic.c b/lib/subghz/blocks/generic.c index 353ff18bfae..7496aea3dcc 100644 --- a/lib/subghz/blocks/generic.c +++ b/lib/subghz/blocks/generic.c @@ -4,7 +4,7 @@ #define TAG "SubGhzBlockGeneric" -void subghz_block_generic_get_preset_name(const char* preset_name, string_t preset_str) { +void subghz_block_generic_get_preset_name(const char* preset_name, FuriString* preset_str) { const char* preset_name_temp; if(!strcmp(preset_name, "AM270")) { preset_name_temp = "FuriHalSubGhzPresetOok270Async"; @@ -17,7 +17,7 @@ void subghz_block_generic_get_preset_name(const char* preset_name, string_t pres } else { preset_name_temp = "FuriHalSubGhzPresetCustom"; } - string_set(preset_str, preset_name_temp); + furi_string_set(preset_str, preset_name_temp); } bool subghz_block_generic_serialize( @@ -26,8 +26,8 @@ bool subghz_block_generic_serialize( SubGhzPresetDefinition* preset) { furi_assert(instance); bool res = false; - string_t temp_str; - string_init(temp_str); + FuriString* temp_str; + temp_str = furi_string_alloc(); do { stream_clean(flipper_format_get_raw_stream(flipper_format)); if(!flipper_format_write_header_cstr( @@ -41,12 +41,13 @@ bool subghz_block_generic_serialize( break; } - subghz_block_generic_get_preset_name(string_get_cstr(preset->name), temp_str); - if(!flipper_format_write_string_cstr(flipper_format, "Preset", string_get_cstr(temp_str))) { + subghz_block_generic_get_preset_name(furi_string_get_cstr(preset->name), temp_str); + if(!flipper_format_write_string_cstr( + flipper_format, "Preset", furi_string_get_cstr(temp_str))) { FURI_LOG_E(TAG, "Unable to add Preset"); break; } - if(!strcmp(string_get_cstr(temp_str), "FuriHalSubGhzPresetCustom")) { + if(!strcmp(furi_string_get_cstr(temp_str), "FuriHalSubGhzPresetCustom")) { if(!flipper_format_write_string_cstr( flipper_format, "Custom_preset_module", "CC1101")) { FURI_LOG_E(TAG, "Unable to add Custom_preset_module"); @@ -79,15 +80,15 @@ bool subghz_block_generic_serialize( } res = true; } while(false); - string_clear(temp_str); + furi_string_free(temp_str); return res; } bool subghz_block_generic_deserialize(SubGhzBlockGeneric* instance, FlipperFormat* flipper_format) { furi_assert(instance); bool res = false; - string_t temp_str; - string_init(temp_str); + FuriString* temp_str; + temp_str = furi_string_alloc(); uint32_t temp_data = 0; do { @@ -113,7 +114,7 @@ bool subghz_block_generic_deserialize(SubGhzBlockGeneric* instance, FlipperForma res = true; } while(0); - string_clear(temp_str); + furi_string_free(temp_str); return res; } diff --git a/lib/subghz/blocks/generic.h b/lib/subghz/blocks/generic.h index 56a7fc2d33b..300807daadb 100644 --- a/lib/subghz/blocks/generic.h +++ b/lib/subghz/blocks/generic.h @@ -25,7 +25,7 @@ struct SubGhzBlockGeneric { * @param preset_name name preset * @param preset_str Output name preset */ -void subghz_block_generic_get_preset_name(const char* preset_name, string_t preset_str); +void subghz_block_generic_get_preset_name(const char* preset_name, FuriString* preset_str); /** * Serialize data SubGhzBlockGeneric. diff --git a/lib/subghz/protocols/base.c b/lib/subghz/protocols/base.c index 06542f5eb1c..4ee7a3f8ac0 100644 --- a/lib/subghz/protocols/base.c +++ b/lib/subghz/protocols/base.c @@ -11,7 +11,7 @@ void subghz_protocol_decoder_base_set_decoder_callback( bool subghz_protocol_decoder_base_get_string( SubGhzProtocolDecoderBase* decoder_base, - string_t output) { + FuriString* output) { bool status = false; if(decoder_base->protocol && decoder_base->protocol->decoder && diff --git a/lib/subghz/protocols/base.h b/lib/subghz/protocols/base.h index fdd135671f1..47b4e482b45 100644 --- a/lib/subghz/protocols/base.h +++ b/lib/subghz/protocols/base.h @@ -11,8 +11,9 @@ typedef struct SubGhzProtocolDecoderBase SubGhzProtocolDecoderBase; typedef void ( *SubGhzProtocolDecoderBaseRxCallback)(SubGhzProtocolDecoderBase* instance, void* context); -typedef void ( - *SubGhzProtocolDecoderBaseSerialize)(SubGhzProtocolDecoderBase* decoder_base, string_t output); +typedef void (*SubGhzProtocolDecoderBaseSerialize)( + SubGhzProtocolDecoderBase* decoder_base, + FuriString* output); struct SubGhzProtocolDecoderBase { // Decoder general section @@ -41,7 +42,7 @@ void subghz_protocol_decoder_base_set_decoder_callback( */ bool subghz_protocol_decoder_base_get_string( SubGhzProtocolDecoderBase* decoder_base, - string_t output); + FuriString* output); /** * Serialize data SubGhzProtocolDecoderBase. diff --git a/lib/subghz/protocols/bett.c b/lib/subghz/protocols/bett.c index c80702577e3..605a922c602 100644 --- a/lib/subghz/protocols/bett.c +++ b/lib/subghz/protocols/bett.c @@ -323,11 +323,11 @@ bool subghz_protocol_decoder_bett_deserialize(void* context, FlipperFormat* flip return ret; } -void subghz_protocol_decoder_bett_get_string(void* context, string_t output) { +void subghz_protocol_decoder_bett_get_string(void* context, FuriString* output) { furi_assert(context); SubGhzProtocolDecoderBETT* instance = context; uint32_t data = (uint32_t)(instance->generic.data & 0x3FFFF); - string_cat_printf( + furi_string_cat_printf( output, "%s %dbit\r\n" "Key:%05lX\r\n" diff --git a/lib/subghz/protocols/bett.h b/lib/subghz/protocols/bett.h index 48f32b3e0d3..04bf46b56b8 100644 --- a/lib/subghz/protocols/bett.h +++ b/lib/subghz/protocols/bett.h @@ -104,4 +104,4 @@ bool subghz_protocol_decoder_bett_deserialize(void* context, FlipperFormat* flip * @param context Pointer to a SubGhzProtocolDecoderBETT instance * @param output Resulting text */ -void subghz_protocol_decoder_bett_get_string(void* context, string_t output); +void subghz_protocol_decoder_bett_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/came.c b/lib/subghz/protocols/came.c index 53d3d078868..7c037bd129c 100644 --- a/lib/subghz/protocols/came.c +++ b/lib/subghz/protocols/came.c @@ -322,7 +322,7 @@ bool subghz_protocol_decoder_came_deserialize(void* context, FlipperFormat* flip return ret; } -void subghz_protocol_decoder_came_get_string(void* context, string_t output) { +void subghz_protocol_decoder_came_get_string(void* context, FuriString* output) { furi_assert(context); SubGhzProtocolDecoderCame* instance = context; @@ -333,7 +333,7 @@ void subghz_protocol_decoder_came_get_string(void* context, string_t output) { uint32_t code_found_reverse_lo = code_found_reverse & 0x00000000ffffffff; - string_cat_printf( + furi_string_cat_printf( output, "%s %dbit\r\n" "Key:0x%08lX\r\n" diff --git a/lib/subghz/protocols/came.h b/lib/subghz/protocols/came.h index c2648c057f6..d1ac7628667 100644 --- a/lib/subghz/protocols/came.h +++ b/lib/subghz/protocols/came.h @@ -104,4 +104,4 @@ bool subghz_protocol_decoder_came_deserialize(void* context, FlipperFormat* flip * @param context Pointer to a SubGhzProtocolDecoderCame instance * @param output Resulting text */ -void subghz_protocol_decoder_came_get_string(void* context, string_t output); +void subghz_protocol_decoder_came_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/came_atomo.c b/lib/subghz/protocols/came_atomo.c index 0c3cdd8adcd..1349d976ecc 100644 --- a/lib/subghz/protocols/came_atomo.c +++ b/lib/subghz/protocols/came_atomo.c @@ -325,7 +325,7 @@ bool subghz_protocol_decoder_came_atomo_deserialize(void* context, FlipperFormat return ret; } -void subghz_protocol_decoder_came_atomo_get_string(void* context, string_t output) { +void subghz_protocol_decoder_came_atomo_get_string(void* context, FuriString* output) { furi_assert(context); SubGhzProtocolDecoderCameAtomo* instance = context; subghz_protocol_came_atomo_remote_controller( @@ -333,7 +333,7 @@ void subghz_protocol_decoder_came_atomo_get_string(void* context, string_t outpu uint32_t code_found_hi = instance->generic.data >> 32; uint32_t code_found_lo = instance->generic.data & 0x00000000ffffffff; - string_cat_printf( + furi_string_cat_printf( output, "%s %db\r\n" "Key:0x%lX%08lX\r\n" diff --git a/lib/subghz/protocols/came_atomo.h b/lib/subghz/protocols/came_atomo.h index 70a79eca12c..aa1cffd051a 100644 --- a/lib/subghz/protocols/came_atomo.h +++ b/lib/subghz/protocols/came_atomo.h @@ -69,4 +69,4 @@ bool subghz_protocol_decoder_came_atomo_deserialize(void* context, FlipperFormat * @param context Pointer to a SubGhzProtocolDecoderCameAtomo instance * @param output Resulting text */ -void subghz_protocol_decoder_came_atomo_get_string(void* context, string_t output); +void subghz_protocol_decoder_came_atomo_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/came_twee.c b/lib/subghz/protocols/came_twee.c index b5b409c5904..65667be2170 100644 --- a/lib/subghz/protocols/came_twee.c +++ b/lib/subghz/protocols/came_twee.c @@ -446,14 +446,14 @@ bool subghz_protocol_decoder_came_twee_deserialize(void* context, FlipperFormat* return ret; } -void subghz_protocol_decoder_came_twee_get_string(void* context, string_t output) { +void subghz_protocol_decoder_came_twee_get_string(void* context, FuriString* output) { furi_assert(context); SubGhzProtocolDecoderCameTwee* instance = context; subghz_protocol_came_twee_remote_controller(&instance->generic); uint32_t code_found_hi = instance->generic.data >> 32; uint32_t code_found_lo = instance->generic.data & 0x00000000ffffffff; - string_cat_printf( + furi_string_cat_printf( output, "%s %db\r\n" "Key:0x%lX%08lX\r\n" diff --git a/lib/subghz/protocols/came_twee.h b/lib/subghz/protocols/came_twee.h index 42e6ddaf366..aa1f0e0db4e 100644 --- a/lib/subghz/protocols/came_twee.h +++ b/lib/subghz/protocols/came_twee.h @@ -104,4 +104,4 @@ bool subghz_protocol_decoder_came_twee_deserialize(void* context, FlipperFormat* * @param context Pointer to a SubGhzProtocolDecoderCameTwee instance * @param output Resulting text */ -void subghz_protocol_decoder_came_twee_get_string(void* context, string_t output); +void subghz_protocol_decoder_came_twee_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/chamberlain_code.c b/lib/subghz/protocols/chamberlain_code.c index 66d230d1383..d2d19af2546 100644 --- a/lib/subghz/protocols/chamberlain_code.c +++ b/lib/subghz/protocols/chamberlain_code.c @@ -451,7 +451,7 @@ bool subghz_protocol_decoder_chamb_code_deserialize(void* context, FlipperFormat return ret; } -void subghz_protocol_decoder_chamb_code_get_string(void* context, string_t output) { +void subghz_protocol_decoder_chamb_code_get_string(void* context, FuriString* output) { furi_assert(context); SubGhzProtocolDecoderChamb_Code* instance = context; @@ -462,7 +462,7 @@ void subghz_protocol_decoder_chamb_code_get_string(void* context, string_t outpu uint32_t code_found_reverse_lo = code_found_reverse & 0x00000000ffffffff; - string_cat_printf( + furi_string_cat_printf( output, "%s %db\r\n" "Key:0x%03lX\r\n" @@ -474,19 +474,19 @@ void subghz_protocol_decoder_chamb_code_get_string(void* context, string_t outpu switch(instance->generic.data_count_bit) { case 7: - string_cat_printf( + furi_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( + furi_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( + furi_string_cat_printf( output, "DIP:" CHAMBERLAIN_9_CODE_DIP_PATTERN "\r\n", CHAMBERLAIN_9_CODE_DATA_TO_DIP(code_found_lo)); diff --git a/lib/subghz/protocols/chamberlain_code.h b/lib/subghz/protocols/chamberlain_code.h index 1ac2f9f9624..923a3151b5d 100644 --- a/lib/subghz/protocols/chamberlain_code.h +++ b/lib/subghz/protocols/chamberlain_code.h @@ -104,4 +104,4 @@ bool subghz_protocol_decoder_chamb_code_deserialize(void* context, FlipperFormat * @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); +void subghz_protocol_decoder_chamb_code_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/clemsa.c b/lib/subghz/protocols/clemsa.c index 337346934b8..dbee0ac9404 100644 --- a/lib/subghz/protocols/clemsa.c +++ b/lib/subghz/protocols/clemsa.c @@ -343,12 +343,12 @@ bool subghz_protocol_decoder_clemsa_deserialize(void* context, FlipperFormat* fl return ret; } -void subghz_protocol_decoder_clemsa_get_string(void* context, string_t output) { +void subghz_protocol_decoder_clemsa_get_string(void* context, FuriString* output) { furi_assert(context); SubGhzProtocolDecoderClemsa* instance = context; subghz_protocol_clemsa_check_remote_controller(&instance->generic); //uint32_t data = (uint32_t)(instance->generic.data & 0xFFFFFF); - string_cat_printf( + furi_string_cat_printf( output, "%s %dbit\r\n" "Key:%05lX Btn %X\r\n" diff --git a/lib/subghz/protocols/clemsa.h b/lib/subghz/protocols/clemsa.h index a83983122c1..8287af23413 100644 --- a/lib/subghz/protocols/clemsa.h +++ b/lib/subghz/protocols/clemsa.h @@ -104,4 +104,4 @@ bool subghz_protocol_decoder_clemsa_deserialize(void* context, FlipperFormat* fl * @param context Pointer to a SubGhzProtocolDecoderClemsa instance * @param output Resulting text */ -void subghz_protocol_decoder_clemsa_get_string(void* context, string_t output); +void subghz_protocol_decoder_clemsa_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/doitrand.c b/lib/subghz/protocols/doitrand.c index 9122c19350b..eeab2576e54 100644 --- a/lib/subghz/protocols/doitrand.c +++ b/lib/subghz/protocols/doitrand.c @@ -337,11 +337,11 @@ bool subghz_protocol_decoder_doitrand_deserialize(void* context, FlipperFormat* return ret; } -void subghz_protocol_decoder_doitrand_get_string(void* context, string_t output) { +void subghz_protocol_decoder_doitrand_get_string(void* context, FuriString* output) { furi_assert(context); SubGhzProtocolDecoderDoitrand* instance = context; subghz_protocol_doitrand_check_remote_controller(&instance->generic); - string_cat_printf( + furi_string_cat_printf( output, "%s %dbit\r\n" "Key:%02lX%08lX\r\n" diff --git a/lib/subghz/protocols/doitrand.h b/lib/subghz/protocols/doitrand.h index f94d7389903..c96946a13da 100644 --- a/lib/subghz/protocols/doitrand.h +++ b/lib/subghz/protocols/doitrand.h @@ -104,4 +104,4 @@ bool subghz_protocol_decoder_doitrand_deserialize(void* context, FlipperFormat* * @param context Pointer to a SubGhzProtocolDecoderDoitrand instance * @param output Resulting text */ -void subghz_protocol_decoder_doitrand_get_string(void* context, string_t output); +void subghz_protocol_decoder_doitrand_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/faac_slh.c b/lib/subghz/protocols/faac_slh.c index 8b90f471c21..75f8d4e9748 100644 --- a/lib/subghz/protocols/faac_slh.c +++ b/lib/subghz/protocols/faac_slh.c @@ -207,7 +207,7 @@ bool subghz_protocol_decoder_faac_slh_deserialize(void* context, FlipperFormat* return ret; } -void subghz_protocol_decoder_faac_slh_get_string(void* context, string_t output) { +void subghz_protocol_decoder_faac_slh_get_string(void* context, FuriString* output) { furi_assert(context); SubGhzProtocolDecoderFaacSLH* instance = context; subghz_protocol_faac_slh_check_remote_controller(&instance->generic); @@ -216,7 +216,7 @@ void subghz_protocol_decoder_faac_slh_get_string(void* context, string_t output) uint32_t code_fix = code_found_reverse & 0xFFFFFFFF; uint32_t code_hop = (code_found_reverse >> 32) & 0xFFFFFFFF; - string_cat_printf( + furi_string_cat_printf( output, "%s %dbit\r\n" "Key:%lX%08lX\r\n" diff --git a/lib/subghz/protocols/faac_slh.h b/lib/subghz/protocols/faac_slh.h index fe7a79265ba..9b18f5eabfa 100644 --- a/lib/subghz/protocols/faac_slh.h +++ b/lib/subghz/protocols/faac_slh.h @@ -70,4 +70,4 @@ bool subghz_protocol_decoder_faac_slh_deserialize(void* context, FlipperFormat* * @param context Pointer to a SubGhzProtocolDecoderFaacSLH instance * @param output Resulting text */ -void subghz_protocol_decoder_faac_slh_get_string(void* context, string_t output); +void subghz_protocol_decoder_faac_slh_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/gate_tx.c b/lib/subghz/protocols/gate_tx.c index 56c224aefcf..73a5092612a 100644 --- a/lib/subghz/protocols/gate_tx.c +++ b/lib/subghz/protocols/gate_tx.c @@ -317,11 +317,11 @@ bool subghz_protocol_decoder_gate_tx_deserialize(void* context, FlipperFormat* f return ret; } -void subghz_protocol_decoder_gate_tx_get_string(void* context, string_t output) { +void subghz_protocol_decoder_gate_tx_get_string(void* context, FuriString* output) { furi_assert(context); SubGhzProtocolDecoderGateTx* instance = context; subghz_protocol_gate_tx_check_remote_controller(&instance->generic); - string_cat_printf( + furi_string_cat_printf( output, "%s %dbit\r\n" "Key:%06lX\r\n" diff --git a/lib/subghz/protocols/gate_tx.h b/lib/subghz/protocols/gate_tx.h index 17157681699..36eeb9a99d8 100644 --- a/lib/subghz/protocols/gate_tx.h +++ b/lib/subghz/protocols/gate_tx.h @@ -104,4 +104,4 @@ bool subghz_protocol_decoder_gate_tx_deserialize(void* context, FlipperFormat* f * @param context Pointer to a SubGhzProtocolDecoderGateTx instance * @param output Resulting text */ -void subghz_protocol_decoder_gate_tx_get_string(void* context, string_t output); +void subghz_protocol_decoder_gate_tx_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/holtek.c b/lib/subghz/protocols/holtek.c index 5cd1606338c..230f4cfef9c 100644 --- a/lib/subghz/protocols/holtek.c +++ b/lib/subghz/protocols/holtek.c @@ -350,12 +350,12 @@ bool subghz_protocol_decoder_holtek_deserialize(void* context, FlipperFormat* fl return ret; } -void subghz_protocol_decoder_holtek_get_string(void* context, string_t output) { +void subghz_protocol_decoder_holtek_get_string(void* context, FuriString* output) { furi_assert(context); SubGhzProtocolDecoderHoltek* instance = context; subghz_protocol_holtek_check_remote_controller(&instance->generic); - string_cat_printf( + furi_string_cat_printf( output, "%s %dbit\r\n" "Key:0x%lX%08lX\r\n" @@ -368,8 +368,8 @@ void subghz_protocol_decoder_holtek_get_string(void* context, string_t output) { instance->generic.btn >> 4); if((instance->generic.btn & 0xF) == 0xE) { - string_cat_printf(output, "ON\r\n"); + furi_string_cat_printf(output, "ON\r\n"); } else if(((instance->generic.btn & 0xF) == 0xB)) { - string_cat_printf(output, "OFF\r\n"); + furi_string_cat_printf(output, "OFF\r\n"); } } diff --git a/lib/subghz/protocols/holtek.h b/lib/subghz/protocols/holtek.h index df7dd6448b4..1dac783b1ba 100644 --- a/lib/subghz/protocols/holtek.h +++ b/lib/subghz/protocols/holtek.h @@ -104,4 +104,4 @@ bool subghz_protocol_decoder_holtek_deserialize(void* context, FlipperFormat* fl * @param context Pointer to a SubGhzProtocolDecoderHoltek instance * @param output Resulting text */ -void subghz_protocol_decoder_holtek_get_string(void* context, string_t output); +void subghz_protocol_decoder_holtek_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/honeywell_wdb.c b/lib/subghz/protocols/honeywell_wdb.c index 451a13f506e..326e79f830f 100644 --- a/lib/subghz/protocols/honeywell_wdb.c +++ b/lib/subghz/protocols/honeywell_wdb.c @@ -374,12 +374,12 @@ bool subghz_protocol_decoder_honeywell_wdb_deserialize( return ret; } -void subghz_protocol_decoder_honeywell_wdb_get_string(void* context, string_t output) { +void subghz_protocol_decoder_honeywell_wdb_get_string(void* context, FuriString* output) { furi_assert(context); SubGhzProtocolDecoderHoneywell_WDB* instance = context; subghz_protocol_honeywell_wdb_check_remote_controller(instance); - string_cat_printf( + furi_string_cat_printf( output, "%s %dbit\r\n" "Key:0x%lX%08lX\r\n" diff --git a/lib/subghz/protocols/honeywell_wdb.h b/lib/subghz/protocols/honeywell_wdb.h index 012b3699674..c61aa822fc9 100644 --- a/lib/subghz/protocols/honeywell_wdb.h +++ b/lib/subghz/protocols/honeywell_wdb.h @@ -108,4 +108,4 @@ bool subghz_protocol_decoder_honeywell_wdb_deserialize( * @param context Pointer to a SubGhzProtocolDecoderHoneywell_WDB instance * @param output Resulting text */ -void subghz_protocol_decoder_honeywell_wdb_get_string(void* context, string_t output); +void subghz_protocol_decoder_honeywell_wdb_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/hormann.c b/lib/subghz/protocols/hormann.c index d78bc9273c3..d8438604fa9 100644 --- a/lib/subghz/protocols/hormann.c +++ b/lib/subghz/protocols/hormann.c @@ -338,12 +338,12 @@ bool subghz_protocol_decoder_hormann_deserialize(void* context, FlipperFormat* f return ret; } -void subghz_protocol_decoder_hormann_get_string(void* context, string_t output) { +void subghz_protocol_decoder_hormann_get_string(void* context, FuriString* output) { furi_assert(context); SubGhzProtocolDecoderHormann* instance = context; subghz_protocol_hormann_check_remote_controller(&instance->generic); - string_cat_printf( + furi_string_cat_printf( output, "%s\r\n" "%dbit\r\n" diff --git a/lib/subghz/protocols/hormann.h b/lib/subghz/protocols/hormann.h index 45d6eb22379..743ad0b384b 100644 --- a/lib/subghz/protocols/hormann.h +++ b/lib/subghz/protocols/hormann.h @@ -104,4 +104,4 @@ bool subghz_protocol_decoder_hormann_deserialize(void* context, FlipperFormat* f * @param context Pointer to a SubGhzProtocolDecoderHormann instance * @param output Resulting text */ -void subghz_protocol_decoder_hormann_get_string(void* context, string_t output); +void subghz_protocol_decoder_hormann_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/ido.c b/lib/subghz/protocols/ido.c index 91446844582..a0332f9e5a9 100644 --- a/lib/subghz/protocols/ido.c +++ b/lib/subghz/protocols/ido.c @@ -205,7 +205,7 @@ bool subghz_protocol_decoder_ido_deserialize(void* context, FlipperFormat* flipp return ret; } -void subghz_protocol_decoder_ido_get_string(void* context, string_t output) { +void subghz_protocol_decoder_ido_get_string(void* context, FuriString* output) { furi_assert(context); SubGhzProtocolDecoderIDo* instance = context; @@ -215,7 +215,7 @@ void subghz_protocol_decoder_ido_get_string(void* context, string_t output) { uint32_t code_fix = code_found_reverse & 0xFFFFFF; uint32_t code_hop = (code_found_reverse >> 24) & 0xFFFFFF; - string_cat_printf( + furi_string_cat_printf( output, "%s %dbit\r\n" "Key:0x%lX%08lX\r\n" diff --git a/lib/subghz/protocols/ido.h b/lib/subghz/protocols/ido.h index 4fe4e740ab7..67d2b4d8f91 100644 --- a/lib/subghz/protocols/ido.h +++ b/lib/subghz/protocols/ido.h @@ -70,4 +70,4 @@ bool subghz_protocol_decoder_ido_deserialize(void* context, FlipperFormat* flipp * @param context Pointer to a SubGhzProtocolDecoderIDo instance * @param output Resulting text */ -void subghz_protocol_decoder_ido_get_string(void* context, string_t output); +void subghz_protocol_decoder_ido_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/intertechno_v3.c b/lib/subghz/protocols/intertechno_v3.c index ffe52e8754f..14e137cab3d 100644 --- a/lib/subghz/protocols/intertechno_v3.c +++ b/lib/subghz/protocols/intertechno_v3.c @@ -434,13 +434,13 @@ bool subghz_protocol_decoder_intertechno_v3_deserialize( return ret; } -void subghz_protocol_decoder_intertechno_v3_get_string(void* context, string_t output) { +void subghz_protocol_decoder_intertechno_v3_get_string(void* context, FuriString* output) { furi_assert(context); SubGhzProtocolDecoderIntertechno_V3* instance = context; subghz_protocol_intertechno_v3_check_remote_controller(&instance->generic); - string_cat_printf( + furi_string_cat_printf( output, "%.11s %db\r\n" "Key:0x%08llX\r\n" @@ -453,17 +453,17 @@ void subghz_protocol_decoder_intertechno_v3_get_string(void* context, string_t o if(instance->generic.data_count_bit == subghz_protocol_intertechno_v3_const.min_count_bit_for_found) { if(instance->generic.cnt >> 5) { - string_cat_printf( + furi_string_cat_printf( output, "Ch: All Btn:%s\r\n", (instance->generic.btn ? "On" : "Off")); } else { - string_cat_printf( + furi_string_cat_printf( output, "Ch:" CH_PATTERN " Btn:%s\r\n", CNT_TO_CH(instance->generic.cnt), (instance->generic.btn ? "On" : "Off")); } } else if(instance->generic.data_count_bit == INTERTECHNO_V3_DIMMING_COUNT_BIT) { - string_cat_printf( + furi_string_cat_printf( output, "Ch:" CH_PATTERN " Dimm:%d%%\r\n", CNT_TO_CH(instance->generic.cnt), diff --git a/lib/subghz/protocols/intertechno_v3.h b/lib/subghz/protocols/intertechno_v3.h index 65d6f61d10b..078d039782a 100644 --- a/lib/subghz/protocols/intertechno_v3.h +++ b/lib/subghz/protocols/intertechno_v3.h @@ -108,4 +108,4 @@ bool subghz_protocol_decoder_intertechno_v3_deserialize( * @param context Pointer to a SubGhzProtocolDecoderIntertechno_V3 instance * @param output Resulting text */ -void subghz_protocol_decoder_intertechno_v3_get_string(void* context, string_t output); +void subghz_protocol_decoder_intertechno_v3_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/keeloq.c b/lib/subghz/protocols/keeloq.c index cfb92fe8b07..2865309eece 100644 --- a/lib/subghz/protocols/keeloq.c +++ b/lib/subghz/protocols/keeloq.c @@ -2,7 +2,6 @@ #include "keeloq_common.h" #include "../subghz_keystore.h" -#include #include #include "../blocks/const.h" @@ -131,7 +130,7 @@ static bool subghz_protocol_keeloq_gen_data(SubGhzProtocolEncoderKeeloq* instanc for M_EACH(manufacture_code, *subghz_keystore_get_data(instance->keystore), SubGhzKeyArray_t) { - res = strcmp(string_get_cstr(manufacture_code->name), instance->manufacture_name); + res = strcmp(furi_string_get_cstr(manufacture_code->name), instance->manufacture_name); if(res == 0) { switch(manufacture_code->type) { case KEELOQ_LEARNING_SIMPLE: @@ -489,7 +488,7 @@ static uint8_t subghz_protocol_keeloq_check_remote_controller_selector( // Simple Learning decrypt = subghz_protocol_keeloq_common_decrypt(hop, manufacture_code->key); if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { - *manufacture_name = string_get_cstr(manufacture_code->name); + *manufacture_name = furi_string_get_cstr(manufacture_code->name); return 1; } break; @@ -499,7 +498,7 @@ static uint8_t subghz_protocol_keeloq_check_remote_controller_selector( man = subghz_protocol_keeloq_common_normal_learning(fix, manufacture_code->key); decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { - *manufacture_name = string_get_cstr(manufacture_code->name); + *manufacture_name = furi_string_get_cstr(manufacture_code->name); return 1; } break; @@ -508,7 +507,7 @@ static uint8_t subghz_protocol_keeloq_check_remote_controller_selector( fix, seed, manufacture_code->key); decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { - *manufacture_name = string_get_cstr(manufacture_code->name); + *manufacture_name = furi_string_get_cstr(manufacture_code->name); return 1; } break; @@ -517,7 +516,7 @@ static uint8_t subghz_protocol_keeloq_check_remote_controller_selector( fix, manufacture_code->key); decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { - *manufacture_name = string_get_cstr(manufacture_code->name); + *manufacture_name = furi_string_get_cstr(manufacture_code->name); return 1; } break; @@ -526,7 +525,7 @@ static uint8_t subghz_protocol_keeloq_check_remote_controller_selector( fix, manufacture_code->key); decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { - *manufacture_name = string_get_cstr(manufacture_code->name); + *manufacture_name = furi_string_get_cstr(manufacture_code->name); return 1; } break; @@ -534,7 +533,7 @@ static uint8_t subghz_protocol_keeloq_check_remote_controller_selector( // Simple Learning decrypt = subghz_protocol_keeloq_common_decrypt(hop, manufacture_code->key); if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { - *manufacture_name = string_get_cstr(manufacture_code->name); + *manufacture_name = furi_string_get_cstr(manufacture_code->name); return 1; } @@ -548,7 +547,7 @@ static uint8_t subghz_protocol_keeloq_check_remote_controller_selector( decrypt = subghz_protocol_keeloq_common_decrypt(hop, man_rev); if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { - *manufacture_name = string_get_cstr(manufacture_code->name); + *manufacture_name = furi_string_get_cstr(manufacture_code->name); return 1; } @@ -558,7 +557,7 @@ static uint8_t subghz_protocol_keeloq_check_remote_controller_selector( man = subghz_protocol_keeloq_common_normal_learning(fix, manufacture_code->key); decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { - *manufacture_name = string_get_cstr(manufacture_code->name); + *manufacture_name = furi_string_get_cstr(manufacture_code->name); return 1; } @@ -566,7 +565,7 @@ static uint8_t subghz_protocol_keeloq_check_remote_controller_selector( man = subghz_protocol_keeloq_common_normal_learning(fix, man_rev); decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { - *manufacture_name = string_get_cstr(manufacture_code->name); + *manufacture_name = furi_string_get_cstr(manufacture_code->name); return 1; } @@ -575,7 +574,7 @@ static uint8_t subghz_protocol_keeloq_check_remote_controller_selector( fix, seed, manufacture_code->key); decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { - *manufacture_name = string_get_cstr(manufacture_code->name); + *manufacture_name = furi_string_get_cstr(manufacture_code->name); return 1; } @@ -583,7 +582,7 @@ static uint8_t subghz_protocol_keeloq_check_remote_controller_selector( man = subghz_protocol_keeloq_common_secure_learning(fix, seed, man_rev); decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { - *manufacture_name = string_get_cstr(manufacture_code->name); + *manufacture_name = furi_string_get_cstr(manufacture_code->name); return 1; } @@ -592,7 +591,7 @@ static uint8_t subghz_protocol_keeloq_check_remote_controller_selector( fix, manufacture_code->key); decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { - *manufacture_name = string_get_cstr(manufacture_code->name); + *manufacture_name = furi_string_get_cstr(manufacture_code->name); return 1; } @@ -600,7 +599,7 @@ static uint8_t subghz_protocol_keeloq_check_remote_controller_selector( man = subghz_protocol_keeloq_common_magic_xor_type1_learning(fix, man_rev); decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { - *manufacture_name = string_get_cstr(manufacture_code->name); + *manufacture_name = furi_string_get_cstr(manufacture_code->name); return 1; } break; @@ -683,7 +682,7 @@ bool subghz_protocol_decoder_keeloq_deserialize(void* context, FlipperFormat* fl return res; } -void subghz_protocol_decoder_keeloq_get_string(void* context, string_t output) { +void subghz_protocol_decoder_keeloq_get_string(void* context, FuriString* output) { furi_assert(context); SubGhzProtocolDecoderKeeloq* instance = context; subghz_protocol_keeloq_check_remote_controller( @@ -697,7 +696,7 @@ void subghz_protocol_decoder_keeloq_get_string(void* context, string_t output) { uint32_t code_found_reverse_hi = code_found_reverse >> 32; uint32_t code_found_reverse_lo = code_found_reverse & 0x00000000ffffffff; - string_cat_printf( + furi_string_cat_printf( output, "%s %dbit\r\n" "Key:%08lX%08lX\r\n" diff --git a/lib/subghz/protocols/keeloq.h b/lib/subghz/protocols/keeloq.h index e1485e5eff4..c0575bd96b8 100644 --- a/lib/subghz/protocols/keeloq.h +++ b/lib/subghz/protocols/keeloq.h @@ -124,4 +124,4 @@ bool subghz_protocol_decoder_keeloq_deserialize(void* context, FlipperFormat* fl * @param context Pointer to a SubGhzProtocolDecoderKeeloq instance * @param output Resulting text */ -void subghz_protocol_decoder_keeloq_get_string(void* context, string_t output); +void subghz_protocol_decoder_keeloq_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/keeloq_common.c b/lib/subghz/protocols/keeloq_common.c index 4c0c1d4ef1a..6c9bc461e3f 100644 --- a/lib/subghz/protocols/keeloq_common.c +++ b/lib/subghz/protocols/keeloq_common.c @@ -2,7 +2,6 @@ #include -#include #include #define bit(x, n) (((x) >> (n)) & 1) diff --git a/lib/subghz/protocols/kia.c b/lib/subghz/protocols/kia.c index 6fc1061704e..aad26f16aa9 100644 --- a/lib/subghz/protocols/kia.c +++ b/lib/subghz/protocols/kia.c @@ -256,7 +256,7 @@ bool subghz_protocol_decoder_kia_deserialize(void* context, FlipperFormat* flipp return ret; } -void subghz_protocol_decoder_kia_get_string(void* context, string_t output) { +void subghz_protocol_decoder_kia_get_string(void* context, FuriString* output) { furi_assert(context); SubGhzProtocolDecoderKIA* instance = context; @@ -264,7 +264,7 @@ void subghz_protocol_decoder_kia_get_string(void* context, string_t output) { uint32_t code_found_hi = instance->generic.data >> 32; uint32_t code_found_lo = instance->generic.data & 0x00000000ffffffff; - string_cat_printf( + furi_string_cat_printf( output, "%s %dbit\r\n" "Key:%08lX%08lX\r\n" diff --git a/lib/subghz/protocols/kia.h b/lib/subghz/protocols/kia.h index 743ab7cbdc3..cf18692193b 100644 --- a/lib/subghz/protocols/kia.h +++ b/lib/subghz/protocols/kia.h @@ -70,4 +70,4 @@ bool subghz_protocol_decoder_kia_deserialize(void* context, FlipperFormat* flipp * @param context Pointer to a SubGhzProtocolDecoderKIA instance * @param output Resulting text */ -void subghz_protocol_decoder_kia_get_string(void* context, string_t output); +void subghz_protocol_decoder_kia_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/linear.c b/lib/subghz/protocols/linear.c index 8f7aed794b0..582c4aaf3d3 100644 --- a/lib/subghz/protocols/linear.c +++ b/lib/subghz/protocols/linear.c @@ -327,7 +327,7 @@ bool subghz_protocol_decoder_linear_deserialize(void* context, FlipperFormat* fl return ret; } -void subghz_protocol_decoder_linear_get_string(void* context, string_t output) { +void subghz_protocol_decoder_linear_get_string(void* context, FuriString* output) { furi_assert(context); SubGhzProtocolDecoderLinear* instance = context; @@ -338,7 +338,7 @@ void subghz_protocol_decoder_linear_get_string(void* context, string_t output) { uint32_t code_found_reverse_lo = code_found_reverse & 0x00000000ffffffff; - string_cat_printf( + furi_string_cat_printf( output, "%s %dbit\r\n" "Key:0x%08lX\r\n" diff --git a/lib/subghz/protocols/linear.h b/lib/subghz/protocols/linear.h index 035b130c44d..6111ad85cab 100644 --- a/lib/subghz/protocols/linear.h +++ b/lib/subghz/protocols/linear.h @@ -104,4 +104,4 @@ bool subghz_protocol_decoder_linear_deserialize(void* context, FlipperFormat* fl * @param context Pointer to a SubGhzProtocolDecoderLinear instance * @param output Resulting text */ -void subghz_protocol_decoder_linear_get_string(void* context, string_t output); +void subghz_protocol_decoder_linear_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/magellen.c b/lib/subghz/protocols/magellen.c index 6dcc83e5648..0c801c08e5a 100644 --- a/lib/subghz/protocols/magellen.c +++ b/lib/subghz/protocols/magellen.c @@ -375,8 +375,8 @@ static void subghz_protocol_magellen_check_remote_controller(SubGhzBlockGeneric* instance->btn = (data_rev >> 16) & 0xFF; } -static void subghz_protocol_magellen_get_event_serialize(uint8_t event, string_t output) { - string_cat_printf( +static void subghz_protocol_magellen_get_event_serialize(uint8_t event, FuriString* output) { + furi_string_cat_printf( output, "%s%s%s%s%s%s%s%s", ((event >> 4) & 0x1 ? (event & 0x1 ? " Open" : " Close") : @@ -424,11 +424,11 @@ bool subghz_protocol_decoder_magellen_deserialize(void* context, FlipperFormat* return ret; } -void subghz_protocol_decoder_magellen_get_string(void* context, string_t output) { +void subghz_protocol_decoder_magellen_get_string(void* context, FuriString* output) { furi_assert(context); SubGhzProtocolDecoderMagellen* instance = context; subghz_protocol_magellen_check_remote_controller(&instance->generic); - string_cat_printf( + furi_string_cat_printf( output, "%s %dbit\r\n" "Key:0x%08lX\r\n" diff --git a/lib/subghz/protocols/magellen.h b/lib/subghz/protocols/magellen.h index 224f79011be..226919c8d9a 100644 --- a/lib/subghz/protocols/magellen.h +++ b/lib/subghz/protocols/magellen.h @@ -104,4 +104,4 @@ bool subghz_protocol_decoder_magellen_deserialize(void* context, FlipperFormat* * @param context Pointer to a SubGhzProtocolDecoderMagellen instance * @param output Resulting text */ -void subghz_protocol_decoder_magellen_get_string(void* context, string_t output); +void subghz_protocol_decoder_magellen_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/marantec.c b/lib/subghz/protocols/marantec.c index bdce6593dbd..1c3997b97c1 100644 --- a/lib/subghz/protocols/marantec.c +++ b/lib/subghz/protocols/marantec.c @@ -373,12 +373,12 @@ bool subghz_protocol_decoder_marantec_deserialize(void* context, FlipperFormat* return ret; } -void subghz_protocol_decoder_marantec_get_string(void* context, string_t output) { +void subghz_protocol_decoder_marantec_get_string(void* context, FuriString* output) { furi_assert(context); SubGhzProtocolDecoderMarantec* instance = context; subghz_protocol_marantec_remote_controller(&instance->generic); - string_cat_printf( + furi_string_cat_printf( output, "%s %db\r\n" "Key:0x%lX%08lX\r\n" diff --git a/lib/subghz/protocols/marantec.h b/lib/subghz/protocols/marantec.h index 2da3c88a570..5fc042e778b 100644 --- a/lib/subghz/protocols/marantec.h +++ b/lib/subghz/protocols/marantec.h @@ -104,4 +104,4 @@ bool subghz_protocol_decoder_marantec_deserialize(void* context, FlipperFormat* * @param context Pointer to a SubGhzProtocolDecoderMarantec instance * @param output Resulting text */ -void subghz_protocol_decoder_marantec_get_string(void* context, string_t output); +void subghz_protocol_decoder_marantec_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/megacode.c b/lib/subghz/protocols/megacode.c index 1501580d8b5..40f22cfdb4f 100644 --- a/lib/subghz/protocols/megacode.c +++ b/lib/subghz/protocols/megacode.c @@ -408,12 +408,12 @@ bool subghz_protocol_decoder_megacode_deserialize(void* context, FlipperFormat* return ret; } -void subghz_protocol_decoder_megacode_get_string(void* context, string_t output) { +void subghz_protocol_decoder_megacode_get_string(void* context, FuriString* output) { furi_assert(context); SubGhzProtocolDecoderMegaCode* instance = context; subghz_protocol_megacode_check_remote_controller(&instance->generic); - string_cat_printf( + furi_string_cat_printf( output, "%s %dbit\r\n" "Key:0x%06lX\r\n" diff --git a/lib/subghz/protocols/megacode.h b/lib/subghz/protocols/megacode.h index c8010665bef..0fafddb6bb0 100644 --- a/lib/subghz/protocols/megacode.h +++ b/lib/subghz/protocols/megacode.h @@ -104,4 +104,4 @@ bool subghz_protocol_decoder_megacode_deserialize(void* context, FlipperFormat* * @param context Pointer to a SubGhzProtocolDecoderMegaCode instance * @param output Resulting text */ -void subghz_protocol_decoder_megacode_get_string(void* context, string_t output); +void subghz_protocol_decoder_megacode_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/nero_radio.c b/lib/subghz/protocols/nero_radio.c index b5a7e8c0ebe..d6925d48572 100644 --- a/lib/subghz/protocols/nero_radio.c +++ b/lib/subghz/protocols/nero_radio.c @@ -370,7 +370,7 @@ bool subghz_protocol_decoder_nero_radio_deserialize(void* context, FlipperFormat return ret; } -void subghz_protocol_decoder_nero_radio_get_string(void* context, string_t output) { +void subghz_protocol_decoder_nero_radio_get_string(void* context, FuriString* output) { furi_assert(context); SubGhzProtocolDecoderNeroRadio* instance = context; @@ -383,7 +383,7 @@ void subghz_protocol_decoder_nero_radio_get_string(void* context, string_t outpu uint32_t code_found_reverse_hi = code_found_reverse >> 32; uint32_t code_found_reverse_lo = code_found_reverse & 0x00000000ffffffff; - string_cat_printf( + furi_string_cat_printf( output, "%s %dbit\r\n" "Key:0x%lX%08lX\r\n" diff --git a/lib/subghz/protocols/nero_radio.h b/lib/subghz/protocols/nero_radio.h index f04dc2b3c24..ef270342c62 100644 --- a/lib/subghz/protocols/nero_radio.h +++ b/lib/subghz/protocols/nero_radio.h @@ -104,4 +104,4 @@ bool subghz_protocol_decoder_nero_radio_deserialize(void* context, FlipperFormat * @param context Pointer to a SubGhzProtocolDecoderNeroRadio instance * @param output Resulting text */ -void subghz_protocol_decoder_nero_radio_get_string(void* context, string_t output); +void subghz_protocol_decoder_nero_radio_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/nero_sketch.c b/lib/subghz/protocols/nero_sketch.c index 66ee569c22d..8080bf9e4bd 100644 --- a/lib/subghz/protocols/nero_sketch.c +++ b/lib/subghz/protocols/nero_sketch.c @@ -355,7 +355,7 @@ bool subghz_protocol_decoder_nero_sketch_deserialize(void* context, FlipperForma return ret; } -void subghz_protocol_decoder_nero_sketch_get_string(void* context, string_t output) { +void subghz_protocol_decoder_nero_sketch_get_string(void* context, FuriString* output) { furi_assert(context); SubGhzProtocolDecoderNeroSketch* instance = context; @@ -368,7 +368,7 @@ void subghz_protocol_decoder_nero_sketch_get_string(void* context, string_t outp uint32_t code_found_reverse_hi = code_found_reverse >> 32; uint32_t code_found_reverse_lo = code_found_reverse & 0x00000000ffffffff; - string_cat_printf( + furi_string_cat_printf( output, "%s %dbit\r\n" "Key:0x%lX%08lX\r\n" diff --git a/lib/subghz/protocols/nero_sketch.h b/lib/subghz/protocols/nero_sketch.h index ab592b48e63..4536e704355 100644 --- a/lib/subghz/protocols/nero_sketch.h +++ b/lib/subghz/protocols/nero_sketch.h @@ -104,4 +104,4 @@ bool subghz_protocol_decoder_nero_sketch_deserialize(void* context, FlipperForma * @param context Pointer to a SubGhzProtocolDecoderNeroSketch instance * @param output Resulting text */ -void subghz_protocol_decoder_nero_sketch_get_string(void* context, string_t output); +void subghz_protocol_decoder_nero_sketch_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/nice_flo.c b/lib/subghz/protocols/nice_flo.c index f07e9efcc1d..06538db1080 100644 --- a/lib/subghz/protocols/nice_flo.c +++ b/lib/subghz/protocols/nice_flo.c @@ -309,7 +309,7 @@ bool subghz_protocol_decoder_nice_flo_deserialize(void* context, FlipperFormat* return ret; } -void subghz_protocol_decoder_nice_flo_get_string(void* context, string_t output) { +void subghz_protocol_decoder_nice_flo_get_string(void* context, FuriString* output) { furi_assert(context); SubGhzProtocolDecoderNiceFlo* instance = context; @@ -318,7 +318,7 @@ void subghz_protocol_decoder_nice_flo_get_string(void* context, string_t output) instance->generic.data, instance->generic.data_count_bit); uint32_t code_found_reverse_lo = code_found_reverse & 0x00000000ffffffff; - string_cat_printf( + furi_string_cat_printf( output, "%s %dbit\r\n" "Key:0x%08lX\r\n" diff --git a/lib/subghz/protocols/nice_flo.h b/lib/subghz/protocols/nice_flo.h index 0873e81b935..dd374006b74 100644 --- a/lib/subghz/protocols/nice_flo.h +++ b/lib/subghz/protocols/nice_flo.h @@ -104,4 +104,4 @@ bool subghz_protocol_decoder_nice_flo_deserialize(void* context, FlipperFormat* * @param context Pointer to a SubGhzProtocolDecoderNiceFlo instance * @param output Resulting text */ -void subghz_protocol_decoder_nice_flo_get_string(void* context, string_t output); +void subghz_protocol_decoder_nice_flo_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/nice_flor_s.c b/lib/subghz/protocols/nice_flor_s.c index 1d370e85a12..ffd94811f2b 100644 --- a/lib/subghz/protocols/nice_flor_s.c +++ b/lib/subghz/protocols/nice_flor_s.c @@ -354,7 +354,7 @@ bool subghz_protocol_decoder_nice_flor_s_deserialize(void* context, FlipperForma return ret; } -void subghz_protocol_decoder_nice_flor_s_get_string(void* context, string_t output) { +void subghz_protocol_decoder_nice_flor_s_get_string(void* context, FuriString* output) { furi_assert(context); SubGhzProtocolDecoderNiceFlorS* instance = context; @@ -363,7 +363,7 @@ void subghz_protocol_decoder_nice_flor_s_get_string(void* context, string_t outp uint32_t code_found_hi = instance->generic.data >> 32; uint32_t code_found_lo = instance->generic.data & 0x00000000ffffffff; - string_cat_printf( + furi_string_cat_printf( output, "%s %dbit\r\n" "Key:0x%lX%08lX\r\n" diff --git a/lib/subghz/protocols/nice_flor_s.h b/lib/subghz/protocols/nice_flor_s.h index 7d98876f68f..286d03d44dc 100644 --- a/lib/subghz/protocols/nice_flor_s.h +++ b/lib/subghz/protocols/nice_flor_s.h @@ -70,4 +70,4 @@ bool subghz_protocol_decoder_nice_flor_s_deserialize(void* context, FlipperForma * @param context Pointer to a SubGhzProtocolDecoderNiceFlorS instance * @param output Resulting text */ -void subghz_protocol_decoder_nice_flor_s_get_string(void* context, string_t output); +void subghz_protocol_decoder_nice_flor_s_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/oregon2.c b/lib/subghz/protocols/oregon2.c index 84bb38be494..48435502fc9 100644 --- a/lib/subghz/protocols/oregon2.c +++ b/lib/subghz/protocols/oregon2.c @@ -5,7 +5,6 @@ #include "../blocks/math.h" #include #include -#include #define TAG "SubGhzProtocolOregon2" @@ -247,12 +246,12 @@ bool subghz_protocol_decoder_oregon2_deserialize(void* context, FlipperFormat* f // append string of the variable data static void - oregon2_var_data_append_string(uint16_t sensor_id, uint32_t var_data, string_t output) { + oregon2_var_data_append_string(uint16_t sensor_id, uint32_t var_data, FuriString* output) { uint32_t val; if(sensor_id == 0xEC40) { val = ((var_data >> 4) & 0xF) * 10 + ((var_data >> 8) & 0xF); - string_cat_printf( + furi_string_cat_printf( output, "Temp: %s%d.%d C\r\n", (var_data & 0xF) ? "-" : "+", @@ -261,7 +260,7 @@ static void } } -static void oregon2_append_check_sum(uint32_t fix_data, uint32_t var_data, string_t output) { +static void oregon2_append_check_sum(uint32_t fix_data, uint32_t var_data, FuriString* output) { uint8_t sum = fix_data & 0xF; uint8_t ref_sum = var_data & 0xFF; var_data >>= 8; @@ -275,16 +274,16 @@ static void oregon2_append_check_sum(uint32_t fix_data, uint32_t var_data, strin // swap calculated sum nibbles sum = (((sum >> 4) & 0xF) | (sum << 4)) & 0xFF; if(sum == ref_sum) - string_cat_printf(output, "Sum ok: 0x%hhX", ref_sum); + furi_string_cat_printf(output, "Sum ok: 0x%hhX", ref_sum); else - string_cat_printf(output, "Sum err: 0x%hhX vs 0x%hhX", ref_sum, sum); + furi_string_cat_printf(output, "Sum err: 0x%hhX vs 0x%hhX", ref_sum, sum); } -void subghz_protocol_decoder_oregon2_get_string(void* context, string_t output) { +void subghz_protocol_decoder_oregon2_get_string(void* context, FuriString* output) { furi_assert(context); SubGhzProtocolDecoderOregon2* instance = context; uint16_t sensor_id = OREGON2_SENSOR_ID(instance->generic.data); - string_cat_printf( + furi_string_cat_printf( output, "%s\r\n" "ID: 0x%04lX, ch: %d%s, rc: 0x%02lX\r\n", diff --git a/lib/subghz/protocols/phoenix_v2.c b/lib/subghz/protocols/phoenix_v2.c index d680b2e62b7..e97df9b65f7 100644 --- a/lib/subghz/protocols/phoenix_v2.c +++ b/lib/subghz/protocols/phoenix_v2.c @@ -320,11 +320,11 @@ bool subghz_protocol_decoder_phoenix_v2_deserialize(void* context, FlipperFormat return ret; } -void subghz_protocol_decoder_phoenix_v2_get_string(void* context, string_t output) { +void subghz_protocol_decoder_phoenix_v2_get_string(void* context, FuriString* output) { furi_assert(context); SubGhzProtocolDecoderPhoenix_V2* instance = context; subghz_protocol_phoenix_v2_check_remote_controller(&instance->generic); - string_cat_printf( + furi_string_cat_printf( output, "%s %dbit\r\n" "Key:%02lX%08lX\r\n" diff --git a/lib/subghz/protocols/phoenix_v2.h b/lib/subghz/protocols/phoenix_v2.h index 38cadc1a23c..b2f18279665 100644 --- a/lib/subghz/protocols/phoenix_v2.h +++ b/lib/subghz/protocols/phoenix_v2.h @@ -104,4 +104,4 @@ bool subghz_protocol_decoder_phoenix_v2_deserialize(void* context, FlipperFormat * @param context Pointer to a SubGhzProtocolDecoderPhoenix_V2 instance * @param output Resulting text */ -void subghz_protocol_decoder_phoenix_v2_get_string(void* context, string_t output); +void subghz_protocol_decoder_phoenix_v2_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/power_smart.c b/lib/subghz/protocols/power_smart.c index bd009d88725..3ec35c74f93 100644 --- a/lib/subghz/protocols/power_smart.c +++ b/lib/subghz/protocols/power_smart.c @@ -373,12 +373,12 @@ bool subghz_protocol_decoder_power_smart_deserialize(void* context, FlipperForma return ret; } -void subghz_protocol_decoder_power_smart_get_string(void* context, string_t output) { +void subghz_protocol_decoder_power_smart_get_string(void* context, FuriString* output) { furi_assert(context); SubGhzProtocolDecoderPowerSmart* instance = context; subghz_protocol_power_smart_remote_controller(&instance->generic); - string_cat_printf( + furi_string_cat_printf( output, "%s %db\r\n" "Key:0x%lX%08lX\r\n" diff --git a/lib/subghz/protocols/power_smart.h b/lib/subghz/protocols/power_smart.h index f6e9571b114..21e5dcf4abf 100644 --- a/lib/subghz/protocols/power_smart.h +++ b/lib/subghz/protocols/power_smart.h @@ -104,4 +104,4 @@ bool subghz_protocol_decoder_power_smart_deserialize(void* context, FlipperForma * @param context Pointer to a SubGhzProtocolDecoderPowerSmart instance * @param output Resulting text */ -void subghz_protocol_decoder_power_smart_get_string(void* context, string_t output); +void subghz_protocol_decoder_power_smart_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/princeton.c b/lib/subghz/protocols/princeton.c index a5b8134d87b..c981de1b836 100644 --- a/lib/subghz/protocols/princeton.c +++ b/lib/subghz/protocols/princeton.c @@ -351,14 +351,14 @@ bool subghz_protocol_decoder_princeton_deserialize(void* context, FlipperFormat* return res; } -void subghz_protocol_decoder_princeton_get_string(void* context, string_t output) { +void subghz_protocol_decoder_princeton_get_string(void* context, FuriString* output) { furi_assert(context); SubGhzProtocolDecoderPrinceton* instance = context; subghz_protocol_princeton_check_remote_controller(&instance->generic); uint32_t data_rev = subghz_protocol_blocks_reverse_key( instance->generic.data, instance->generic.data_count_bit); - string_cat_printf( + furi_string_cat_printf( output, "%s %dbit\r\n" "Key:0x%08lX\r\n" diff --git a/lib/subghz/protocols/princeton.h b/lib/subghz/protocols/princeton.h index 9c296c4a1ce..4e482f74798 100644 --- a/lib/subghz/protocols/princeton.h +++ b/lib/subghz/protocols/princeton.h @@ -104,4 +104,4 @@ bool subghz_protocol_decoder_princeton_deserialize(void* context, FlipperFormat* * @param context Pointer to a SubGhzProtocolDecoderPrinceton instance * @param output Resulting text */ -void subghz_protocol_decoder_princeton_get_string(void* context, string_t output); +void subghz_protocol_decoder_princeton_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/raw.c b/lib/subghz/protocols/raw.c index 0419a39a0b1..48c3ff4df7b 100644 --- a/lib/subghz/protocols/raw.c +++ b/lib/subghz/protocols/raw.c @@ -29,7 +29,7 @@ struct SubGhzProtocolDecoderRAW { Storage* storage; FlipperFormat* flipper_file; uint32_t file_is_open; - string_t file_name; + FuriString* file_name; size_t sample_write; bool last_level; }; @@ -38,7 +38,7 @@ struct SubGhzProtocolEncoderRAW { SubGhzProtocolEncoderBase base; bool is_running; - string_t file_name; + FuriString* file_name; SubGhzFileEncoderWorker* file_worker_encoder; }; @@ -90,8 +90,8 @@ bool subghz_protocol_raw_save_to_file_init( instance->storage = furi_record_open(RECORD_STORAGE); instance->flipper_file = flipper_format_file_alloc(instance->storage); - string_t temp_str; - string_init(temp_str); + FuriString* temp_str; + temp_str = furi_string_alloc(); bool init = false; do { @@ -104,16 +104,17 @@ bool subghz_protocol_raw_save_to_file_init( break; } - string_set(instance->file_name, dev_name); + furi_string_set(instance->file_name, dev_name); // First remove subghz device file if it was saved - string_printf(temp_str, "%s/%s%s", SUBGHZ_RAW_FOLDER, dev_name, SUBGHZ_APP_EXTENSION); + furi_string_printf(temp_str, "%s/%s%s", SUBGHZ_RAW_FOLDER, dev_name, SUBGHZ_APP_EXTENSION); - if(!storage_simply_remove(instance->storage, string_get_cstr(temp_str))) { + if(!storage_simply_remove(instance->storage, furi_string_get_cstr(temp_str))) { break; } // Open file - if(!flipper_format_file_open_always(instance->flipper_file, string_get_cstr(temp_str))) { + if(!flipper_format_file_open_always( + instance->flipper_file, furi_string_get_cstr(temp_str))) { FURI_LOG_E(TAG, "Unable to open file for write: %s", temp_str); break; } @@ -130,13 +131,13 @@ bool subghz_protocol_raw_save_to_file_init( break; } - subghz_block_generic_get_preset_name(string_get_cstr(preset->name), temp_str); + subghz_block_generic_get_preset_name(furi_string_get_cstr(preset->name), temp_str); if(!flipper_format_write_string_cstr( - instance->flipper_file, "Preset", string_get_cstr(temp_str))) { + instance->flipper_file, "Preset", furi_string_get_cstr(temp_str))) { FURI_LOG_E(TAG, "Unable to add Preset"); break; } - if(!strcmp(string_get_cstr(temp_str), "FuriHalSubGhzPresetCustom")) { + if(!strcmp(furi_string_get_cstr(temp_str), "FuriHalSubGhzPresetCustom")) { if(!flipper_format_write_string_cstr( instance->flipper_file, "Custom_preset_module", "CC1101")) { FURI_LOG_E(TAG, "Unable to add Custom_preset_module"); @@ -160,7 +161,7 @@ bool subghz_protocol_raw_save_to_file_init( init = true; } while(0); - string_clear(temp_str); + furi_string_free(temp_str); return init; } @@ -210,7 +211,7 @@ void* subghz_protocol_decoder_raw_alloc(SubGhzEnvironment* environment) { instance->ind_write = 0; instance->last_level = false; instance->file_is_open = RAWFileIsOpenClose; - string_init(instance->file_name); + instance->file_name = furi_string_alloc(); return instance; } @@ -218,7 +219,7 @@ void* subghz_protocol_decoder_raw_alloc(SubGhzEnvironment* environment) { void subghz_protocol_decoder_raw_free(void* context) { furi_assert(context); SubGhzProtocolDecoderRAW* instance = context; - string_clear(instance->file_name); + furi_string_free(instance->file_name); free(instance); } @@ -255,12 +256,12 @@ bool subghz_protocol_decoder_raw_deserialize(void* context, FlipperFormat* flipp return true; } -void subghz_protocol_decoder_raw_get_string(void* context, string_t output) { +void subghz_protocol_decoder_raw_get_string(void* context, FuriString* output) { furi_assert(context); //SubGhzProtocolDecoderRAW* instance = context; UNUSED(context); //ToDo no use - string_cat_printf(output, "RAW Date"); + furi_string_cat_printf(output, "RAW Date"); } void* subghz_protocol_encoder_raw_alloc(SubGhzEnvironment* environment) { @@ -268,7 +269,7 @@ void* subghz_protocol_encoder_raw_alloc(SubGhzEnvironment* environment) { SubGhzProtocolEncoderRAW* instance = malloc(sizeof(SubGhzProtocolEncoderRAW)); instance->base.protocol = &subghz_protocol_raw; - string_init(instance->file_name); + instance->file_name = furi_string_alloc(); instance->is_running = false; return instance; } @@ -286,7 +287,7 @@ void subghz_protocol_encoder_raw_free(void* context) { furi_assert(context); SubGhzProtocolEncoderRAW* instance = context; subghz_protocol_encoder_raw_stop(instance); - string_clear(instance->file_name); + furi_string_free(instance->file_name); free(instance); } @@ -305,7 +306,7 @@ static bool subghz_protocol_encoder_raw_worker_init(SubGhzProtocolEncoderRAW* in instance->file_worker_encoder = subghz_file_encoder_worker_alloc(); if(subghz_file_encoder_worker_start( - instance->file_worker_encoder, string_get_cstr(instance->file_name))) { + instance->file_worker_encoder, furi_string_get_cstr(instance->file_name))) { //the worker needs a file in order to open and read part of the file furi_delay_ms(100); instance->is_running = true; @@ -334,8 +335,8 @@ bool subghz_protocol_encoder_raw_deserialize(void* context, FlipperFormat* flipp furi_assert(context); SubGhzProtocolEncoderRAW* instance = context; bool res = false; - string_t temp_str; - string_init(temp_str); + FuriString* temp_str; + temp_str = furi_string_alloc(); do { if(!flipper_format_rewind(flipper_format)) { FURI_LOG_E(TAG, "Rewind error"); @@ -346,11 +347,11 @@ bool subghz_protocol_encoder_raw_deserialize(void* context, FlipperFormat* flipp FURI_LOG_E(TAG, "Missing File_name"); break; } - string_set(instance->file_name, temp_str); + furi_string_set(instance->file_name, temp_str); res = subghz_protocol_encoder_raw_worker_init(instance); } while(false); - string_clear(temp_str); + furi_string_free(temp_str); return res; } diff --git a/lib/subghz/protocols/raw.h b/lib/subghz/protocols/raw.h index b52a7d84e18..be03dea2747 100644 --- a/lib/subghz/protocols/raw.h +++ b/lib/subghz/protocols/raw.h @@ -82,7 +82,7 @@ bool subghz_protocol_decoder_raw_deserialize(void* context, FlipperFormat* flipp * @param context Pointer to a SubGhzProtocolDecoderRAW instance * @param output Resulting text */ -void subghz_protocol_decoder_raw_get_string(void* context, string_t output); +void subghz_protocol_decoder_raw_get_string(void* context, FuriString* output); /** * Allocate SubGhzProtocolEncoderRAW. diff --git a/lib/subghz/protocols/scher_khan.c b/lib/subghz/protocols/scher_khan.c index 1c044e9db3e..617efbbab49 100644 --- a/lib/subghz/protocols/scher_khan.c +++ b/lib/subghz/protocols/scher_khan.c @@ -263,14 +263,14 @@ bool subghz_protocol_decoder_scher_khan_deserialize(void* context, FlipperFormat return subghz_block_generic_deserialize(&instance->generic, flipper_format); } -void subghz_protocol_decoder_scher_khan_get_string(void* context, string_t output) { +void subghz_protocol_decoder_scher_khan_get_string(void* context, FuriString* output) { furi_assert(context); SubGhzProtocolDecoderScherKhan* instance = context; subghz_protocol_scher_khan_check_remote_controller( &instance->generic, &instance->protocol_name); - string_cat_printf( + furi_string_cat_printf( output, "%s %dbit\r\n" "Key:0x%lX%08lX\r\n" diff --git a/lib/subghz/protocols/scher_khan.h b/lib/subghz/protocols/scher_khan.h index 391ccc414a3..f0fe595b455 100644 --- a/lib/subghz/protocols/scher_khan.h +++ b/lib/subghz/protocols/scher_khan.h @@ -70,4 +70,4 @@ bool subghz_protocol_decoder_scher_khan_deserialize(void* context, FlipperFormat * @param context Pointer to a SubGhzProtocolDecoderScherKhan instance * @param output Resulting text */ -void subghz_protocol_decoder_scher_khan_get_string(void* context, string_t output); +void subghz_protocol_decoder_scher_khan_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/secplus_v1.c b/lib/subghz/protocols/secplus_v1.c index 77a0c62a2e9..885615b6e6c 100644 --- a/lib/subghz/protocols/secplus_v1.c +++ b/lib/subghz/protocols/secplus_v1.c @@ -555,7 +555,7 @@ bool subghz_protocol_secplus_v1_check_fixed(uint32_t fixed) { return true; } -void subghz_protocol_decoder_secplus_v1_get_string(void* context, string_t output) { +void subghz_protocol_decoder_secplus_v1_get_string(void* context, FuriString* output) { furi_assert(context); SubGhzProtocolDecoderSecPlus_v1* instance = context; @@ -567,7 +567,7 @@ void subghz_protocol_decoder_secplus_v1_get_string(void* context, string_t outpu uint8_t id1 = (fixed / 9) % 3; uint16_t pin = 0; - string_cat_printf( + furi_string_cat_printf( output, "%s %db\r\n" "Key:0x%lX%08lX\r\n" @@ -587,9 +587,9 @@ void subghz_protocol_decoder_secplus_v1_get_string(void* context, string_t outpu pin = (fixed / 59049) % 19683; if(pin <= 9999) { - string_cat_printf(output, " pin:%d", pin); + furi_string_cat_printf(output, " pin:%d", pin); } else if(10000 <= pin && pin <= 11029) { - string_cat_printf(output, " pin:enter"); + furi_string_cat_printf(output, " pin:enter"); } int pin_suffix = 0; @@ -597,13 +597,13 @@ void subghz_protocol_decoder_secplus_v1_get_string(void* context, string_t outpu pin_suffix = (fixed / 1162261467) % 3; if(pin_suffix == 1) { - string_cat_printf(output, " #\r\n"); + furi_string_cat_printf(output, " #\r\n"); } else if(pin_suffix == 2) { - string_cat_printf(output, " *\r\n"); + furi_string_cat_printf(output, " *\r\n"); } else { - string_cat_printf(output, "\r\n"); + furi_string_cat_printf(output, "\r\n"); } - string_cat_printf( + furi_string_cat_printf( output, "Sn:0x%08lX\r\n" "Cnt:0x%03X\r\n" @@ -615,14 +615,14 @@ void subghz_protocol_decoder_secplus_v1_get_string(void* context, string_t outpu //id = fixed / 27; instance->generic.serial = fixed / 27; if(instance->generic.btn == 1) { - string_cat_printf(output, " Btn:left\r\n"); + furi_string_cat_printf(output, " Btn:left\r\n"); } else if(instance->generic.btn == 0) { - string_cat_printf(output, " Btn:middle\r\n"); + furi_string_cat_printf(output, " Btn:middle\r\n"); } else if(instance->generic.btn == 2) { - string_cat_printf(output, " Btn:right\r\n"); + furi_string_cat_printf(output, " Btn:right\r\n"); } - string_cat_printf( + furi_string_cat_printf( output, "Sn:0x%08lX\r\n" "Cnt:0x%03X\r\n" diff --git a/lib/subghz/protocols/secplus_v1.h b/lib/subghz/protocols/secplus_v1.h index 8ae1c0cb216..ba1762f55de 100644 --- a/lib/subghz/protocols/secplus_v1.h +++ b/lib/subghz/protocols/secplus_v1.h @@ -110,4 +110,4 @@ bool subghz_protocol_secplus_v1_check_fixed(uint32_t fixed); * @param context Pointer to a SubGhzProtocolDecoderSecPlus_v1 instance * @param output Resulting text */ -void subghz_protocol_decoder_secplus_v1_get_string(void* context, string_t output); +void subghz_protocol_decoder_secplus_v1_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/secplus_v2.c b/lib/subghz/protocols/secplus_v2.c index c242d0b4d90..3c9b966a935 100644 --- a/lib/subghz/protocols/secplus_v2.c +++ b/lib/subghz/protocols/secplus_v2.c @@ -810,12 +810,12 @@ bool subghz_protocol_decoder_secplus_v2_deserialize(void* context, FlipperFormat return res; } -void subghz_protocol_decoder_secplus_v2_get_string(void* context, string_t output) { +void subghz_protocol_decoder_secplus_v2_get_string(void* context, FuriString* output) { furi_assert(context); SubGhzProtocolDecoderSecPlus_v2* instance = context; subghz_protocol_secplus_v2_remote_controller(&instance->generic, instance->secplus_packet_1); - string_cat_printf( + furi_string_cat_printf( output, "%s %db\r\n" "Pk1:0x%lX%08lX\r\n" diff --git a/lib/subghz/protocols/secplus_v2.h b/lib/subghz/protocols/secplus_v2.h index 9e9ae2a7de6..007609fc1be 100644 --- a/lib/subghz/protocols/secplus_v2.h +++ b/lib/subghz/protocols/secplus_v2.h @@ -122,4 +122,4 @@ bool subghz_protocol_decoder_secplus_v2_deserialize(void* context, FlipperFormat * @param context Pointer to a SubGhzProtocolDecoderSecPlus_v2 instance * @param output Resulting text */ -void subghz_protocol_decoder_secplus_v2_get_string(void* context, string_t output); +void subghz_protocol_decoder_secplus_v2_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/somfy_keytis.c b/lib/subghz/protocols/somfy_keytis.c index 7a3b2186f3a..e1b750749e8 100644 --- a/lib/subghz/protocols/somfy_keytis.c +++ b/lib/subghz/protocols/somfy_keytis.c @@ -426,13 +426,13 @@ bool subghz_protocol_decoder_somfy_keytis_deserialize(void* context, FlipperForm return res; } -void subghz_protocol_decoder_somfy_keytis_get_string(void* context, string_t output) { +void subghz_protocol_decoder_somfy_keytis_get_string(void* context, FuriString* output) { furi_assert(context); SubGhzProtocolDecoderSomfyKeytis* instance = context; subghz_protocol_somfy_keytis_check_remote_controller(&instance->generic); - string_cat_printf( + furi_string_cat_printf( output, "%s %db\r\n" "%lX%08lX%06lX\r\n" diff --git a/lib/subghz/protocols/somfy_keytis.h b/lib/subghz/protocols/somfy_keytis.h index 29f96cd6a72..af1df91d5f5 100644 --- a/lib/subghz/protocols/somfy_keytis.h +++ b/lib/subghz/protocols/somfy_keytis.h @@ -70,4 +70,4 @@ bool subghz_protocol_decoder_somfy_keytis_deserialize(void* context, FlipperForm * @param context Pointer to a SubGhzProtocolDecoderSomfyKeytis instance * @param output Resulting text */ -void subghz_protocol_decoder_somfy_keytis_get_string(void* context, string_t output); +void subghz_protocol_decoder_somfy_keytis_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/somfy_telis.c b/lib/subghz/protocols/somfy_telis.c index b9aac57775c..1dfdf0c4374 100644 --- a/lib/subghz/protocols/somfy_telis.c +++ b/lib/subghz/protocols/somfy_telis.c @@ -363,13 +363,13 @@ bool subghz_protocol_decoder_somfy_telis_deserialize(void* context, FlipperForma return ret; } -void subghz_protocol_decoder_somfy_telis_get_string(void* context, string_t output) { +void subghz_protocol_decoder_somfy_telis_get_string(void* context, FuriString* output) { furi_assert(context); SubGhzProtocolDecoderSomfyTelis* instance = context; subghz_protocol_somfy_telis_check_remote_controller(&instance->generic); - string_cat_printf( + furi_string_cat_printf( output, "%s %db\r\n" "Key:0x%lX%08lX\r\n" diff --git a/lib/subghz/protocols/somfy_telis.h b/lib/subghz/protocols/somfy_telis.h index ff5b45e230e..b6e58e34c1a 100644 --- a/lib/subghz/protocols/somfy_telis.h +++ b/lib/subghz/protocols/somfy_telis.h @@ -70,4 +70,4 @@ bool subghz_protocol_decoder_somfy_telis_deserialize(void* context, FlipperForma * @param context Pointer to a SubGhzProtocolDecoderSomfyTelis instance * @param output Resulting text */ -void subghz_protocol_decoder_somfy_telis_get_string(void* context, string_t output); +void subghz_protocol_decoder_somfy_telis_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/star_line.c b/lib/subghz/protocols/star_line.c index 757b56622d5..a8b0dad7342 100644 --- a/lib/subghz/protocols/star_line.c +++ b/lib/subghz/protocols/star_line.c @@ -2,7 +2,6 @@ #include "keeloq_common.h" #include "../subghz_keystore.h" -#include #include #include "../blocks/const.h" @@ -229,7 +228,7 @@ static uint8_t subghz_protocol_star_line_check_remote_controller_selector( //Simple Learning decrypt = subghz_protocol_keeloq_common_decrypt(hop, manufacture_code->key); if(subghz_protocol_star_line_check_decrypt(instance, decrypt, btn, end_serial)) { - *manufacture_name = string_get_cstr(manufacture_code->name); + *manufacture_name = furi_string_get_cstr(manufacture_code->name); return 1; } break; @@ -240,7 +239,7 @@ static uint8_t subghz_protocol_star_line_check_remote_controller_selector( subghz_protocol_keeloq_common_normal_learning(fix, manufacture_code->key); decrypt = subghz_protocol_keeloq_common_decrypt(hop, man_normal_learning); if(subghz_protocol_star_line_check_decrypt(instance, decrypt, btn, end_serial)) { - *manufacture_name = string_get_cstr(manufacture_code->name); + *manufacture_name = furi_string_get_cstr(manufacture_code->name); return 1; } break; @@ -248,7 +247,7 @@ static uint8_t subghz_protocol_star_line_check_remote_controller_selector( // Simple Learning decrypt = subghz_protocol_keeloq_common_decrypt(hop, manufacture_code->key); if(subghz_protocol_star_line_check_decrypt(instance, decrypt, btn, end_serial)) { - *manufacture_name = string_get_cstr(manufacture_code->name); + *manufacture_name = furi_string_get_cstr(manufacture_code->name); return 1; } // Check for mirrored man @@ -260,7 +259,7 @@ static uint8_t subghz_protocol_star_line_check_remote_controller_selector( } decrypt = subghz_protocol_keeloq_common_decrypt(hop, man_rev); if(subghz_protocol_star_line_check_decrypt(instance, decrypt, btn, end_serial)) { - *manufacture_name = string_get_cstr(manufacture_code->name); + *manufacture_name = furi_string_get_cstr(manufacture_code->name); return 1; } //########################### @@ -270,13 +269,13 @@ static uint8_t subghz_protocol_star_line_check_remote_controller_selector( subghz_protocol_keeloq_common_normal_learning(fix, manufacture_code->key); decrypt = subghz_protocol_keeloq_common_decrypt(hop, man_normal_learning); if(subghz_protocol_star_line_check_decrypt(instance, decrypt, btn, end_serial)) { - *manufacture_name = string_get_cstr(manufacture_code->name); + *manufacture_name = furi_string_get_cstr(manufacture_code->name); return 1; } man_normal_learning = subghz_protocol_keeloq_common_normal_learning(fix, man_rev); decrypt = subghz_protocol_keeloq_common_decrypt(hop, man_normal_learning); if(subghz_protocol_star_line_check_decrypt(instance, decrypt, btn, end_serial)) { - *manufacture_name = string_get_cstr(manufacture_code->name); + *manufacture_name = furi_string_get_cstr(manufacture_code->name); return 1; } break; @@ -355,7 +354,7 @@ bool subghz_protocol_decoder_star_line_deserialize(void* context, FlipperFormat* return res; } -void subghz_protocol_decoder_star_line_get_string(void* context, string_t output) { +void subghz_protocol_decoder_star_line_get_string(void* context, FuriString* output) { furi_assert(context); SubGhzProtocolDecoderStarLine* instance = context; @@ -370,7 +369,7 @@ void subghz_protocol_decoder_star_line_get_string(void* context, string_t output uint32_t code_found_reverse_hi = code_found_reverse >> 32; uint32_t code_found_reverse_lo = code_found_reverse & 0x00000000ffffffff; - string_cat_printf( + furi_string_cat_printf( output, "%s %dbit\r\n" "Key:%08lX%08lX\r\n" diff --git a/lib/subghz/protocols/star_line.h b/lib/subghz/protocols/star_line.h index e240954b1dc..74c994101e2 100644 --- a/lib/subghz/protocols/star_line.h +++ b/lib/subghz/protocols/star_line.h @@ -70,4 +70,4 @@ bool subghz_protocol_decoder_star_line_deserialize(void* context, FlipperFormat* * @param context Pointer to a SubGhzProtocolDecoderStarLine instance * @param output Resulting text */ -void subghz_protocol_decoder_star_line_get_string(void* context, string_t output); +void subghz_protocol_decoder_star_line_get_string(void* context, FuriString* output); diff --git a/lib/subghz/subghz_file_encoder_worker.c b/lib/subghz/subghz_file_encoder_worker.c index 8f65ea004e7..1b8e99f194f 100644 --- a/lib/subghz/subghz_file_encoder_worker.c +++ b/lib/subghz/subghz_file_encoder_worker.c @@ -19,8 +19,8 @@ struct SubGhzFileEncoderWorker { volatile bool worker_running; volatile bool worker_stoping; bool level; - string_t str_data; - string_t file_path; + FuriString* str_data; + FuriString* file_path; SubGhzFileEncoderWorkerCallbackEnd callback_end; void* context_end; @@ -117,9 +117,11 @@ static int32_t subghz_file_encoder_worker_thread(void* context) { Stream* stream = flipper_format_get_raw_stream(instance->flipper_format); do { if(!flipper_format_file_open_existing( - instance->flipper_format, string_get_cstr(instance->file_path))) { + instance->flipper_format, furi_string_get_cstr(instance->file_path))) { FURI_LOG_E( - TAG, "Unable to open file for read: %s", string_get_cstr(instance->file_path)); + TAG, + "Unable to open file for read: %s", + furi_string_get_cstr(instance->file_path)); break; } if(!flipper_format_read_string(instance->flipper_format, "Protocol", instance->str_data)) { @@ -138,9 +140,9 @@ static int32_t subghz_file_encoder_worker_thread(void* context) { size_t stream_free_byte = xStreamBufferSpacesAvailable(instance->stream); if((stream_free_byte / sizeof(int32_t)) >= SUBGHZ_FILE_ENCODER_LOAD) { if(stream_read_line(stream, instance->str_data)) { - string_strim(instance->str_data); + furi_string_trim(instance->str_data); if(!subghz_file_encoder_worker_data_parse( - instance, string_get_cstr(instance->str_data))) { + instance, furi_string_get_cstr(instance->str_data))) { //to stop DMA correctly subghz_file_encoder_worker_add_level_duration(instance, LEVEL_DURATION_RESET); subghz_file_encoder_worker_add_level_duration(instance, LEVEL_DURATION_RESET); @@ -186,8 +188,8 @@ SubGhzFileEncoderWorker* subghz_file_encoder_worker_alloc() { instance->storage = furi_record_open(RECORD_STORAGE); instance->flipper_format = flipper_format_file_alloc(instance->storage); - string_init(instance->str_data); - string_init(instance->file_path); + instance->str_data = furi_string_alloc(); + instance->file_path = furi_string_alloc(); instance->level = false; instance->worker_stoping = true; @@ -200,8 +202,8 @@ void subghz_file_encoder_worker_free(SubGhzFileEncoderWorker* instance) { vStreamBufferDelete(instance->stream); furi_thread_free(instance->thread); - string_clear(instance->str_data); - string_clear(instance->file_path); + furi_string_free(instance->str_data); + furi_string_free(instance->file_path); flipper_format_free(instance->flipper_format); furi_record_close(RECORD_STORAGE); @@ -214,7 +216,7 @@ bool subghz_file_encoder_worker_start(SubGhzFileEncoderWorker* instance, const c furi_assert(!instance->worker_running); xStreamBufferReset(instance->stream); - string_set(instance->file_path, file_path); + furi_string_set(instance->file_path, file_path); instance->worker_running = true; furi_thread_start(instance->thread); diff --git a/lib/subghz/subghz_keystore.c b/lib/subghz/subghz_keystore.c index 4d2eb0e50c6..1b4b3b716be 100644 --- a/lib/subghz/subghz_keystore.c +++ b/lib/subghz/subghz_keystore.c @@ -43,7 +43,7 @@ void subghz_keystore_free(SubGhzKeystore* instance) { for M_EACH(manufacture_code, instance->data, SubGhzKeyArray_t) { - string_clear(manufacture_code->name); + furi_string_free(manufacture_code->name); manufacture_code->key = 0; } SubGhzKeyArray_clear(instance->data); @@ -57,7 +57,7 @@ static void subghz_keystore_add_key( uint64_t key, uint16_t type) { SubGhzKey* manufacture_code = SubGhzKeyArray_push_raw(instance->data); - string_init_set_str(manufacture_code->name, name); + manufacture_code->name = furi_string_alloc_set(name); manufacture_code->key = key; manufacture_code->type = type; } @@ -191,8 +191,8 @@ bool subghz_keystore_load(SubGhzKeystore* instance, const char* file_name) { uint32_t version; SubGhzKeystoreEncryption encryption; - string_t filetype; - string_init(filetype); + FuriString* filetype; + filetype = furi_string_alloc(); FURI_LOG_I(TAG, "Loading keystore %s", file_name); @@ -213,7 +213,7 @@ bool subghz_keystore_load(SubGhzKeystore* instance, const char* file_name) { break; } - if(strcmp(string_get_cstr(filetype), SUBGHZ_KEYSTORE_FILE_TYPE) != 0 || + if(strcmp(furi_string_get_cstr(filetype), SUBGHZ_KEYSTORE_FILE_TYPE) != 0 || version != SUBGHZ_KEYSTORE_FILE_VERSION) { FURI_LOG_E(TAG, "Type or version mismatch"); break; @@ -238,7 +238,7 @@ bool subghz_keystore_load(SubGhzKeystore* instance, const char* file_name) { furi_record_close(RECORD_STORAGE); - string_clear(filetype); + furi_string_free(filetype); return result; } @@ -294,7 +294,7 @@ bool subghz_keystore_save(SubGhzKeystore* instance, const char* file_name, uint8 (uint32_t)(key->key >> 32), (uint32_t)key->key, key->type, - string_get_cstr(key->name)); + furi_string_get_cstr(key->name)); // Verify length and align furi_assert(len > 0); if(len % 16 != 0) { @@ -349,8 +349,8 @@ bool subghz_keystore_raw_encrypted_save( uint8_t* iv) { bool encrypted = false; uint32_t version; - string_t filetype; - string_init(filetype); + FuriString* filetype; + filetype = furi_string_alloc(); SubGhzKeystoreEncryption encryption; Storage* storage = furi_record_open(RECORD_STORAGE); @@ -373,7 +373,7 @@ bool subghz_keystore_raw_encrypted_save( break; } - if(strcmp(string_get_cstr(filetype), SUBGHZ_KEYSTORE_FILE_RAW_TYPE) != 0 || + if(strcmp(furi_string_get_cstr(filetype), SUBGHZ_KEYSTORE_FILE_RAW_TYPE) != 0 || version != SUBGHZ_KEYSTORE_FILE_VERSION) { FURI_LOG_E(TAG, "Type or version mismatch"); break; @@ -392,7 +392,9 @@ bool subghz_keystore_raw_encrypted_save( break; } if(!flipper_format_write_header_cstr( - output_flipper_format, string_get_cstr(filetype), SUBGHZ_KEYSTORE_FILE_VERSION)) { + output_flipper_format, + furi_string_get_cstr(filetype), + SUBGHZ_KEYSTORE_FILE_VERSION)) { FURI_LOG_E(TAG, "Unable to add header"); break; } @@ -488,8 +490,8 @@ bool subghz_keystore_raw_get_data(const char* file_name, size_t offset, uint8_t* uint32_t version; SubGhzKeystoreEncryption encryption; - string_t str_temp; - string_init(str_temp); + FuriString* str_temp; + str_temp = furi_string_alloc(); Storage* storage = furi_record_open(RECORD_STORAGE); char* decrypted_line = malloc(SUBGHZ_KEYSTORE_FILE_DECRYPTED_LINE_SIZE); @@ -509,7 +511,7 @@ bool subghz_keystore_raw_get_data(const char* file_name, size_t offset, uint8_t* break; } - if(strcmp(string_get_cstr(str_temp), SUBGHZ_KEYSTORE_FILE_RAW_TYPE) != 0 || + if(strcmp(furi_string_get_cstr(str_temp), SUBGHZ_KEYSTORE_FILE_RAW_TYPE) != 0 || version != SUBGHZ_KEYSTORE_FILE_VERSION) { FURI_LOG_E(TAG, "Type or version mismatch"); break; @@ -605,7 +607,7 @@ bool subghz_keystore_raw_get_data(const char* file_name, size_t offset, uint8_t* free(decrypted_line); - string_clear(str_temp); + furi_string_free(str_temp); return result; } diff --git a/lib/subghz/subghz_keystore.h b/lib/subghz/subghz_keystore.h index 2b7880f07b2..06ae8adae4d 100644 --- a/lib/subghz/subghz_keystore.h +++ b/lib/subghz/subghz_keystore.h @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #include @@ -9,7 +9,7 @@ extern "C" { #endif typedef struct { - string_t name; + FuriString* name; uint64_t key; uint16_t type; } SubGhzKey; diff --git a/lib/subghz/types.h b/lib/subghz/types.h index 32b70b65178..dd9cefb8bfa 100644 --- a/lib/subghz/types.h +++ b/lib/subghz/types.h @@ -41,7 +41,7 @@ typedef bool (*SubGhzDeserialize)(void* context, FlipperFormat* flipper_format); typedef void (*SubGhzDecoderFeed)(void* decoder, bool level, uint32_t duration); typedef void (*SubGhzDecoderReset)(void* decoder); typedef uint8_t (*SubGhzGetHashData)(void* decoder); -typedef void (*SubGhzGetString)(void* decoder, string_t output); +typedef void (*SubGhzGetString)(void* decoder, FuriString* output); // Encoder specific typedef void (*SubGhzEncoderStop)(void* encoder); diff --git a/lib/toolbox/args.c b/lib/toolbox/args.c index 287ca7efc92..aa790ad68c5 100644 --- a/lib/toolbox/args.c +++ b/lib/toolbox/args.c @@ -1,60 +1,60 @@ #include "args.h" #include "hex.h" -size_t args_get_first_word_length(string_t args) { - size_t ws = string_search_char(args, ' '); - if(ws == STRING_FAILURE) { - ws = string_size(args); +size_t args_get_first_word_length(FuriString* args) { + size_t ws = furi_string_search_char(args, ' '); + if(ws == FURI_STRING_FAILURE) { + ws = furi_string_size(args); } return ws; } -size_t args_length(string_t args) { - return string_size(args); +size_t args_length(FuriString* args) { + return furi_string_size(args); } -bool args_read_int_and_trim(string_t args, int* value) { +bool args_read_int_and_trim(FuriString* args, int* value) { size_t cmd_length = args_get_first_word_length(args); if(cmd_length == 0) { return false; } - if(sscanf(string_get_cstr(args), "%d", value) == 1) { - string_right(args, cmd_length); - string_strim(args); + if(sscanf(furi_string_get_cstr(args), "%d", value) == 1) { + furi_string_right(args, cmd_length); + furi_string_trim(args); return true; } return false; } -bool args_read_string_and_trim(string_t args, string_t word) { +bool args_read_string_and_trim(FuriString* args, FuriString* word) { size_t cmd_length = args_get_first_word_length(args); if(cmd_length == 0) { return false; } - string_set_n(word, args, 0, cmd_length); - string_right(args, cmd_length); - string_strim(args); + furi_string_set_n(word, args, 0, cmd_length); + furi_string_right(args, cmd_length); + furi_string_trim(args); return true; } -bool args_read_probably_quoted_string_and_trim(string_t args, string_t word) { - if(string_size(args) > 1 && string_get_char(args, 0) == '\"') { - size_t second_quote_pos = string_search_char(args, '\"', 1); +bool args_read_probably_quoted_string_and_trim(FuriString* args, FuriString* word) { + if(furi_string_size(args) > 1 && furi_string_get_char(args, 0) == '\"') { + size_t second_quote_pos = furi_string_search_char(args, '\"', 1); if(second_quote_pos == 0) { return false; } - string_set_n(word, args, 1, second_quote_pos - 1); - string_right(args, second_quote_pos + 1); - string_strim(args); + furi_string_set_n(word, args, 1, second_quote_pos - 1); + furi_string_right(args, second_quote_pos + 1); + furi_string_trim(args); return true; } else { return args_read_string_and_trim(args, word); @@ -76,9 +76,9 @@ bool args_char_to_hex(char hi_nibble, char low_nibble, uint8_t* byte) { return result; } -bool args_read_hex_bytes(string_t args, uint8_t* bytes, size_t bytes_count) { +bool args_read_hex_bytes(FuriString* args, uint8_t* bytes, size_t bytes_count) { bool result = true; - const char* str_pointer = string_get_cstr(args); + const char* str_pointer = furi_string_get_cstr(args); if(args_get_first_word_length(args) == (bytes_count * 2)) { for(size_t i = 0; i < bytes_count; i++) { diff --git a/lib/toolbox/args.h b/lib/toolbox/args.h index dc72bdafab8..556fd4a72b4 100644 --- a/lib/toolbox/args.h +++ b/lib/toolbox/args.h @@ -2,7 +2,7 @@ #include #include -#include +#include #ifdef __cplusplus extern "C" { @@ -15,7 +15,7 @@ extern "C" { * @return true - success * @return false - arguments string does not contain int */ -bool args_read_int_and_trim(string_t args, int* value); +bool args_read_int_and_trim(FuriString* args, int* value); /** * @brief Extract first argument from arguments string and trim arguments string @@ -25,7 +25,7 @@ bool args_read_int_and_trim(string_t args, int* value); * @return true - success * @return false - arguments string does not contain anything */ -bool args_read_string_and_trim(string_t args, string_t word); +bool args_read_string_and_trim(FuriString* args, FuriString* word); /** * @brief Extract the first quoted argument from the argument string and trim the argument string. If the argument is not quoted, calls args_read_string_and_trim. @@ -35,7 +35,7 @@ bool args_read_string_and_trim(string_t args, string_t word); * @return true - success * @return false - arguments string does not contain anything */ -bool args_read_probably_quoted_string_and_trim(string_t args, string_t word); +bool args_read_probably_quoted_string_and_trim(FuriString* args, FuriString* word); /** * @brief Convert hex ASCII values to byte array @@ -46,7 +46,7 @@ bool args_read_probably_quoted_string_and_trim(string_t args, string_t word); * @return true - success * @return false - arguments string does not contain enough values, or contain non-hex ASCII values */ -bool args_read_hex_bytes(string_t args, uint8_t* bytes, size_t bytes_count); +bool args_read_hex_bytes(FuriString* args, uint8_t* bytes, size_t bytes_count); /************************************ HELPERS ***************************************/ @@ -56,7 +56,7 @@ bool args_read_hex_bytes(string_t args, uint8_t* bytes, size_t bytes_count); * @param args arguments string * @return size_t length of first word */ -size_t args_get_first_word_length(string_t args); +size_t args_get_first_word_length(FuriString* args); /** * @brief Get length of arguments string @@ -64,7 +64,7 @@ size_t args_get_first_word_length(string_t args); * @param args arguments string * @return size_t length of arguments string */ -size_t args_length(string_t args); +size_t args_length(FuriString* args); /** * @brief Convert ASCII hex values to byte diff --git a/lib/toolbox/dir_walk.c b/lib/toolbox/dir_walk.c index 348bd5442d3..b5e2cb52bb5 100644 --- a/lib/toolbox/dir_walk.c +++ b/lib/toolbox/dir_walk.c @@ -5,7 +5,7 @@ LIST_DEF(DirIndexList, uint32_t); struct DirWalk { File* file; - string_t path; + FuriString* path; DirIndexList_t index_list; uint32_t current_index; bool recursive; @@ -15,7 +15,7 @@ struct DirWalk { DirWalk* dir_walk_alloc(Storage* storage) { DirWalk* dir_walk = malloc(sizeof(DirWalk)); - string_init(dir_walk->path); + dir_walk->path = furi_string_alloc(); dir_walk->file = storage_file_alloc(storage); DirIndexList_init(dir_walk->index_list); dir_walk->recursive = true; @@ -25,7 +25,7 @@ DirWalk* dir_walk_alloc(Storage* storage) { void dir_walk_free(DirWalk* dir_walk) { storage_file_free(dir_walk->file); - string_clear(dir_walk->path); + furi_string_free(dir_walk->path); DirIndexList_clear(dir_walk->index_list); free(dir_walk); } @@ -40,7 +40,7 @@ void dir_walk_set_filter_cb(DirWalk* dir_walk, DirWalkFilterCb cb, void* context } bool dir_walk_open(DirWalk* dir_walk, const char* path) { - string_set(dir_walk->path, path); + furi_string_set(dir_walk->path, path); dir_walk->current_index = 0; return storage_dir_open(dir_walk->file, path); } @@ -53,7 +53,8 @@ static bool dir_walk_filter(DirWalk* dir_walk, const char* name, FileInfo* filei } } -static DirWalkResult dir_walk_iter(DirWalk* dir_walk, string_t return_path, FileInfo* fileinfo) { +static DirWalkResult + dir_walk_iter(DirWalk* dir_walk, FuriString* return_path, FileInfo* fileinfo) { DirWalkResult result = DirWalkError; char* name = malloc(256); // FIXME: remove magic number FileInfo info; @@ -68,7 +69,8 @@ static DirWalkResult dir_walk_iter(DirWalk* dir_walk, string_t return_path, File if(dir_walk_filter(dir_walk, name, &info)) { if(return_path != NULL) { - string_printf(return_path, "%s/%s", string_get_cstr(dir_walk->path), name); + furi_string_printf( + return_path, "%s/%s", furi_string_get_cstr(dir_walk->path), name); } if(fileinfo != NULL) { @@ -84,8 +86,8 @@ static DirWalkResult dir_walk_iter(DirWalk* dir_walk, string_t return_path, File dir_walk->current_index = 0; storage_dir_close(dir_walk->file); - string_cat_printf(dir_walk->path, "/%s", name); - storage_dir_open(dir_walk->file, string_get_cstr(dir_walk->path)); + furi_string_cat_printf(dir_walk->path, "/%s", name); + storage_dir_open(dir_walk->file, furi_string_get_cstr(dir_walk->path)); } } else if(storage_file_get_error(dir_walk->file) == FSE_NOT_EXIST) { if(DirIndexList_size(dir_walk->index_list) == 0) { @@ -100,12 +102,12 @@ static DirWalkResult dir_walk_iter(DirWalk* dir_walk, string_t return_path, File storage_dir_close(dir_walk->file); - size_t last_char = string_search_rchar(dir_walk->path, '/'); - if(last_char != STRING_FAILURE) { - string_left(dir_walk->path, last_char); + size_t last_char = furi_string_search_rchar(dir_walk->path, '/'); + if(last_char != FURI_STRING_FAILURE) { + furi_string_left(dir_walk->path, last_char); } - storage_dir_open(dir_walk->file, string_get_cstr(dir_walk->path)); + storage_dir_open(dir_walk->file, furi_string_get_cstr(dir_walk->path)); // rewind while(true) { @@ -137,7 +139,7 @@ FS_Error dir_walk_get_error(DirWalk* dir_walk) { return storage_file_get_error(dir_walk->file); } -DirWalkResult dir_walk_read(DirWalk* dir_walk, string_t return_path, FileInfo* fileinfo) { +DirWalkResult dir_walk_read(DirWalk* dir_walk, FuriString* return_path, FileInfo* fileinfo) { return dir_walk_iter(dir_walk, return_path, fileinfo); } @@ -147,6 +149,6 @@ void dir_walk_close(DirWalk* dir_walk) { } DirIndexList_reset(dir_walk->index_list); - string_reset(dir_walk->path); + furi_string_reset(dir_walk->path); dir_walk->current_index = 0; } diff --git a/lib/toolbox/dir_walk.h b/lib/toolbox/dir_walk.h index fc65104200d..535237fc6e2 100644 --- a/lib/toolbox/dir_walk.h +++ b/lib/toolbox/dir_walk.h @@ -66,7 +66,7 @@ FS_Error dir_walk_get_error(DirWalk* dir_walk); * @param fileinfo * @return DirWalkResult */ -DirWalkResult dir_walk_read(DirWalk* dir_walk, string_t return_path, FileInfo* fileinfo); +DirWalkResult dir_walk_read(DirWalk* dir_walk, FuriString* return_path, FileInfo* fileinfo); /** * Close directory diff --git a/lib/toolbox/m_cstr_dup.h b/lib/toolbox/m_cstr_dup.h index 2bc35c87774..0555f72c651 100644 --- a/lib/toolbox/m_cstr_dup.h +++ b/lib/toolbox/m_cstr_dup.h @@ -13,5 +13,4 @@ HASH(m_core_cstr_hash), \ EQUAL(M_CSTR_EQUAL), \ CMP(strcmp), \ - TYPE(const char*), \ - OUT_STR(M_CSTR_OUT_STR)) + TYPE(const char*)) diff --git a/lib/toolbox/path.c b/lib/toolbox/path.c index 1262362eff6..53e9fc0923c 100644 --- a/lib/toolbox/path.c +++ b/lib/toolbox/path.c @@ -1,86 +1,85 @@ #include "path.h" -#include "m-string.h" #include -void path_extract_filename_no_ext(const char* path, string_t filename) { - string_set(filename, path); +void path_extract_filename_no_ext(const char* path, FuriString* filename) { + furi_string_set(filename, path); - size_t start_position = string_search_rchar(filename, '/'); - size_t end_position = string_search_rchar(filename, '.'); + size_t start_position = furi_string_search_rchar(filename, '/'); + size_t end_position = furi_string_search_rchar(filename, '.'); - if(start_position == STRING_FAILURE) { + if(start_position == FURI_STRING_FAILURE) { start_position = 0; } else { start_position += 1; } - if(end_position == STRING_FAILURE) { - end_position = string_size(filename); + if(end_position == FURI_STRING_FAILURE) { + end_position = furi_string_size(filename); } - string_mid(filename, start_position, end_position - start_position); + furi_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, '/'); +void path_extract_filename(FuriString* path, FuriString* name, bool trim_ext) { + size_t filename_start = furi_string_search_rchar(path, '/'); if(filename_start > 0) { filename_start++; - string_set_n(name, path, filename_start, string_size(path) - filename_start); + furi_string_set_n(name, path, filename_start, furi_string_size(path) - filename_start); } if(trim_ext) { - size_t dot = string_search_rchar(name, '.'); + size_t dot = furi_string_search_rchar(name, '.'); if(dot > 0) { - string_left(name, dot); + furi_string_left(name, dot); } } } -void path_extract_extension(string_t path, char* ext, size_t ext_len_max) { - size_t dot = string_search_rchar(path, '.'); - size_t filename_start = string_search_rchar(path, '/'); +void path_extract_extension(FuriString* path, char* ext, size_t ext_len_max) { + size_t dot = furi_string_search_rchar(path, '.'); + size_t filename_start = furi_string_search_rchar(path, '/'); if((dot > 0) && (filename_start < dot)) { - strlcpy(ext, &(string_get_cstr(path))[dot], ext_len_max); + strlcpy(ext, &(furi_string_get_cstr(path))[dot], ext_len_max); } } -static inline void path_cleanup(string_t path) { - string_strim(path); - while(string_end_with_str_p(path, "/")) { - string_left(path, string_size(path) - 1); +static inline void path_cleanup(FuriString* path) { + furi_string_trim(path); + while(furi_string_end_with(path, "/")) { + furi_string_left(path, furi_string_size(path) - 1); } } -void path_extract_basename(const char* path, string_t basename) { - string_set(basename, path); +void path_extract_basename(const char* path, FuriString* basename) { + furi_string_set(basename, path); path_cleanup(basename); - size_t pos = string_search_rchar(basename, '/'); - if(pos != STRING_FAILURE) { - string_right(basename, pos + 1); + size_t pos = furi_string_search_rchar(basename, '/'); + if(pos != FURI_STRING_FAILURE) { + furi_string_right(basename, pos + 1); } } -void path_extract_dirname(const char* path, string_t dirname) { - string_set(dirname, path); +void path_extract_dirname(const char* path, FuriString* dirname) { + furi_string_set(dirname, path); path_cleanup(dirname); - size_t pos = string_search_rchar(dirname, '/'); - if(pos != STRING_FAILURE) { - string_left(dirname, pos); + size_t pos = furi_string_search_rchar(dirname, '/'); + if(pos != FURI_STRING_FAILURE) { + furi_string_left(dirname, pos); } } -void path_append(string_t path, const char* suffix) { +void path_append(FuriString* path, const char* suffix) { path_cleanup(path); - string_t suffix_str; - string_init_set(suffix_str, suffix); - string_strim(suffix_str); - string_strim(suffix_str, "/"); - string_cat_printf(path, "/%s", string_get_cstr(suffix_str)); - string_clear(suffix_str); + FuriString* suffix_str; + suffix_str = furi_string_alloc_set(suffix); + furi_string_trim(suffix_str); + furi_string_trim(suffix_str, "/"); + furi_string_cat_printf(path, "/%s", furi_string_get_cstr(suffix_str)); + furi_string_free(suffix_str); } -void path_concat(const char* path, const char* suffix, string_t out_path) { - string_set(out_path, path); +void path_concat(const char* path, const char* suffix, FuriString* out_path) { + furi_string_set(out_path, path); path_append(out_path, suffix); } diff --git a/lib/toolbox/path.h b/lib/toolbox/path.h index d46b210f030..e6145b7156a 100644 --- a/lib/toolbox/path.h +++ b/lib/toolbox/path.h @@ -1,6 +1,5 @@ #pragma once - -#include +#include #ifdef __cplusplus extern "C" { @@ -12,7 +11,7 @@ extern "C" { * @param path path string * @param filename output filename string. Must be initialized before. */ -void path_extract_filename_no_ext(const char* path, string_t filename); +void path_extract_filename_no_ext(const char* path, FuriString* filename); /** * @brief Extract filename string from path. @@ -21,7 +20,7 @@ void path_extract_filename_no_ext(const char* path, string_t filename); * @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); +void path_extract_filename(FuriString* path, FuriString* filename, bool trim_ext); /** * @brief Extract file extension from path. @@ -30,7 +29,7 @@ void path_extract_filename(string_t path, string_t filename, bool trim_ext); * @param ext output extension string * @param ext_len_max maximum extension string length */ -void path_extract_extension(string_t path, char* ext, size_t ext_len_max); +void path_extract_extension(FuriString* path, char* ext, size_t ext_len_max); /** * @brief Extract last path component @@ -38,7 +37,7 @@ void path_extract_extension(string_t path, char* ext, size_t ext_len_max); * @param path path string * @param filename output string. Must be initialized before. */ -void path_extract_basename(const char* path, string_t basename); +void path_extract_basename(const char* path, FuriString* basename); /** * @brief Extract path, except for last component @@ -46,7 +45,7 @@ void path_extract_basename(const char* path, string_t basename); * @param path path string * @param filename output string. Must be initialized before. */ -void path_extract_dirname(const char* path, string_t dirname); +void path_extract_dirname(const char* path, FuriString* dirname); /** * @brief Appends new component to path, adding path delimiter @@ -54,7 +53,7 @@ void path_extract_dirname(const char* path, string_t dirname); * @param path path string * @param suffix path part to apply */ -void path_append(string_t path, const char* suffix); +void path_append(FuriString* path, const char* suffix); /** * @brief Appends new component to path, adding path delimiter @@ -63,7 +62,7 @@ void path_append(string_t path, const char* suffix); * @param suffix second path part * @param out_path output string to combine parts into. Must be initialized */ -void path_concat(const char* path, const char* suffix, string_t out_path); +void path_concat(const char* path, const char* suffix, FuriString* out_path); /** * @brief Check that path contains only ascii characters diff --git a/lib/toolbox/protocols/protocol.h b/lib/toolbox/protocols/protocol.h index 56bb1b74ca2..5a8015b1e1b 100644 --- a/lib/toolbox/protocols/protocol.h +++ b/lib/toolbox/protocols/protocol.h @@ -3,7 +3,7 @@ #include #include #include -#include +#include typedef void* (*ProtocolAlloc)(void); typedef void (*ProtocolFree)(void* protocol); @@ -15,7 +15,7 @@ typedef bool (*ProtocolDecoderFeed)(void* protocol, bool level, uint32_t duratio typedef bool (*ProtocolEncoderStart)(void* protocol); typedef LevelDuration (*ProtocolEncoderYield)(void* protocol); -typedef void (*ProtocolRenderData)(void* protocol, string_t result); +typedef void (*ProtocolRenderData)(void* protocol, FuriString* result); typedef bool (*ProtocolWriteData)(void* protocol, void* data); typedef struct { diff --git a/lib/toolbox/protocols/protocol_dict.c b/lib/toolbox/protocols/protocol_dict.c index 2a022cc406b..136dc137216 100644 --- a/lib/toolbox/protocols/protocol_dict.c +++ b/lib/toolbox/protocols/protocol_dict.c @@ -185,7 +185,7 @@ LevelDuration protocol_dict_encoder_yield(ProtocolDict* dict, size_t protocol_in } } -void protocol_dict_render_data(ProtocolDict* dict, string_t result, size_t protocol_index) { +void protocol_dict_render_data(ProtocolDict* dict, FuriString* result, size_t protocol_index) { furi_assert(protocol_index < dict->count); ProtocolRenderData fn = dict->base[protocol_index]->render_data; @@ -194,7 +194,7 @@ void protocol_dict_render_data(ProtocolDict* dict, string_t result, size_t proto } } -void protocol_dict_render_brief_data(ProtocolDict* dict, string_t result, size_t protocol_index) { +void protocol_dict_render_brief_data(ProtocolDict* dict, FuriString* result, size_t protocol_index) { furi_assert(protocol_index < dict->count); ProtocolRenderData fn = dict->base[protocol_index]->render_brief_data; diff --git a/lib/toolbox/protocols/protocol_dict.h b/lib/toolbox/protocols/protocol_dict.h index 3037ddd5e4c..cd8503952b4 100644 --- a/lib/toolbox/protocols/protocol_dict.h +++ b/lib/toolbox/protocols/protocol_dict.h @@ -58,9 +58,9 @@ bool protocol_dict_encoder_start(ProtocolDict* dict, size_t protocol_index); LevelDuration protocol_dict_encoder_yield(ProtocolDict* dict, size_t protocol_index); -void protocol_dict_render_data(ProtocolDict* dict, string_t result, size_t protocol_index); +void protocol_dict_render_data(ProtocolDict* dict, FuriString* result, size_t protocol_index); -void protocol_dict_render_brief_data(ProtocolDict* dict, string_t result, size_t protocol_index); +void protocol_dict_render_brief_data(ProtocolDict* dict, FuriString* result, size_t protocol_index); uint32_t protocol_dict_get_validate_count(ProtocolDict* dict, size_t protocol_index); diff --git a/lib/toolbox/stream/file_stream.c b/lib/toolbox/stream/file_stream.c index f7363c6be67..064912168bb 100644 --- a/lib/toolbox/stream/file_stream.c +++ b/lib/toolbox/stream/file_stream.c @@ -173,17 +173,20 @@ static bool file_stream_delete_and_insert( Stream* scratch_stream = file_stream_alloc(_stream->storage); // TODO: we need something like "storage_open_tmpfile and storage_close_tmpfile" - string_t scratch_name; - string_t tmp_name; - string_init(tmp_name); + FuriString* scratch_name; + FuriString* tmp_name; + tmp_name = furi_string_alloc(); storage_get_next_filename( _stream->storage, STORAGE_ANY_PATH_PREFIX, ".scratch", ".pad", tmp_name, 255); - string_init_printf(scratch_name, ANY_PATH("%s.pad"), string_get_cstr(tmp_name)); - string_clear(tmp_name); + scratch_name = furi_string_alloc_printf(ANY_PATH("%s.pad"), furi_string_get_cstr(tmp_name)); + furi_string_free(tmp_name); do { if(!file_stream_open( - scratch_stream, string_get_cstr(scratch_name), FSAM_READ_WRITE, FSOM_CREATE_NEW)) + scratch_stream, + furi_string_get_cstr(scratch_name), + FSAM_READ_WRITE, + FSOM_CREATE_NEW)) break; size_t current_position = stream_tell(stream); @@ -225,8 +228,8 @@ static bool file_stream_delete_and_insert( } while(false); stream_free(scratch_stream); - storage_common_remove(_stream->storage, string_get_cstr(scratch_name)); - string_clear(scratch_name); + storage_common_remove(_stream->storage, furi_string_get_cstr(scratch_name)); + furi_string_free(scratch_name); return result; } diff --git a/lib/toolbox/stream/stream.c b/lib/toolbox/stream/stream.c index 088996f0247..86d35c95970 100644 --- a/lib/toolbox/stream/stream.c +++ b/lib/toolbox/stream/stream.c @@ -67,8 +67,8 @@ static bool stream_write_struct(Stream* stream, const void* context) { return (stream_write(stream, write_data->data, write_data->size) == write_data->size); } -bool stream_read_line(Stream* stream, string_t str_result) { - string_reset(str_result); +bool stream_read_line(Stream* stream, FuriString* str_result) { + furi_string_reset(str_result); const uint8_t buffer_size = 32; uint8_t buffer[buffer_size]; @@ -84,13 +84,13 @@ bool stream_read_line(Stream* stream, string_t str_result) { error = true; break; } - string_push_back(str_result, buffer[i]); + furi_string_push_back(str_result, buffer[i]); result = true; break; } else if(buffer[i] == '\r') { // Ignore } else { - string_push_back(str_result, buffer[i]); + furi_string_push_back(str_result, buffer[i]); } } @@ -99,7 +99,7 @@ bool stream_read_line(Stream* stream, string_t str_result) { } } while(true); - return string_size(str_result) != 0; + return furi_string_size(str_result) != 0; } bool stream_rewind(Stream* stream) { @@ -112,9 +112,10 @@ size_t stream_write_char(Stream* stream, char c) { return stream_write(stream, (const uint8_t*)&c, 1); } -size_t stream_write_string(Stream* stream, string_t string) { +size_t stream_write_string(Stream* stream, FuriString* string) { furi_assert(stream); - return stream_write(stream, (const uint8_t*)string_get_cstr(string), string_size(string)); + return stream_write( + stream, (const uint8_t*)furi_string_get_cstr(string), furi_string_size(string)); } size_t stream_write_cstring(Stream* stream, const char* string) { @@ -134,10 +135,10 @@ size_t stream_write_format(Stream* stream, const char* format, ...) { size_t stream_write_vaformat(Stream* stream, const char* format, va_list args) { furi_assert(stream); - string_t data; - string_init_vprintf(data, format, args); + FuriString* data; + data = furi_string_alloc_vprintf(format, args); size_t size = stream_write_string(stream, data); - string_clear(data); + furi_string_free(data); return size; } @@ -153,7 +154,7 @@ bool stream_insert_char(Stream* stream, char c) { return stream_delete_and_insert_char(stream, 0, c); } -bool stream_insert_string(Stream* stream, string_t string) { +bool stream_insert_string(Stream* stream, FuriString* string) { furi_assert(stream); return stream_delete_and_insert_string(stream, 0, string); } @@ -184,10 +185,10 @@ bool stream_delete_and_insert_char(Stream* stream, size_t delete_size, char c) { return stream_delete_and_insert(stream, delete_size, stream_write_struct, &write_data); } -bool stream_delete_and_insert_string(Stream* stream, size_t delete_size, string_t string) { +bool stream_delete_and_insert_string(Stream* stream, size_t delete_size, FuriString* string) { furi_assert(stream); StreamWriteData write_data = { - .data = (uint8_t*)string_get_cstr(string), .size = string_size(string)}; + .data = (uint8_t*)furi_string_get_cstr(string), .size = furi_string_size(string)}; return stream_delete_and_insert(stream, delete_size, stream_write_struct, &write_data); } @@ -213,12 +214,12 @@ bool stream_delete_and_insert_vaformat( const char* format, va_list args) { furi_assert(stream); - string_t data; - string_init_vprintf(data, format, args); + FuriString* data; + data = furi_string_alloc_vprintf(format, args); StreamWriteData write_data = { - .data = (uint8_t*)string_get_cstr(data), .size = string_size(data)}; + .data = (uint8_t*)furi_string_get_cstr(data), .size = furi_string_size(data)}; bool result = stream_delete_and_insert(stream, delete_size, stream_write_struct, &write_data); - string_clear(data); + furi_string_free(data); return result; } diff --git a/lib/toolbox/stream/stream.h b/lib/toolbox/stream/stream.h index c10e9f5e5ff..1ffb40445f9 100644 --- a/lib/toolbox/stream/stream.h +++ b/lib/toolbox/stream/stream.h @@ -2,7 +2,6 @@ #include #include #include -#include #include #ifdef __cplusplus @@ -105,7 +104,7 @@ bool stream_delete_and_insert( * @return true if line lenght is not zero * @return false otherwise */ -bool stream_read_line(Stream* stream, string_t str_result); +bool stream_read_line(Stream* stream, FuriString* str_result); /** * Moves the rw pointer to the start @@ -127,7 +126,7 @@ size_t stream_write_char(Stream* stream, char c); * @param string string value * @return size_t how many bytes was written */ -size_t stream_write_string(Stream* stream, string_t string); +size_t stream_write_string(Stream* stream, FuriString* string); /** * Write const char* to the stream @@ -182,7 +181,7 @@ bool stream_insert_char(Stream* stream, char c); * @return true if the operation was successful * @return false on error */ -bool stream_insert_string(Stream* stream, string_t string); +bool stream_insert_string(Stream* stream, FuriString* string); /** * Insert const char* to the stream @@ -231,7 +230,7 @@ bool stream_delete_and_insert_char(Stream* stream, size_t delete_size, char c); * @return true if the operation was successful * @return false on error */ -bool stream_delete_and_insert_string(Stream* stream, size_t delete_size, string_t string); +bool stream_delete_and_insert_string(Stream* stream, size_t delete_size, FuriString* string); /** * Delete N chars from the stream and insert const char* to the stream diff --git a/lib/toolbox/stream/string_stream.c b/lib/toolbox/stream/string_stream.c index f3e1364d145..075f0b26fe5 100644 --- a/lib/toolbox/stream/string_stream.c +++ b/lib/toolbox/stream/string_stream.c @@ -5,7 +5,7 @@ typedef struct { Stream stream_base; - string_t string; + FuriString* string; size_t index; } StringStream; @@ -39,14 +39,14 @@ const StreamVTable string_stream_vtable = { Stream* string_stream_alloc() { StringStream* stream = malloc(sizeof(StringStream)); - string_init(stream->string); + stream->string = furi_string_alloc(); stream->index = 0; stream->stream_base.vtable = &string_stream_vtable; return (Stream*)stream; } static void string_stream_free(StringStream* stream) { - string_clear(stream->string); + furi_string_free(stream->string); free(stream); } @@ -56,7 +56,7 @@ static bool string_stream_eof(StringStream* stream) { static void string_stream_clean(StringStream* stream) { stream->index = 0; - string_reset(stream->string); + furi_string_reset(stream->string); } static bool string_stream_seek(StringStream* stream, int32_t offset, StreamOffset offset_type) { @@ -79,8 +79,8 @@ static bool string_stream_seek(StringStream* stream, int32_t offset, StreamOffse } break; case StreamOffsetFromEnd: - if(((int32_t)string_size(stream->string) + offset) >= 0) { - stream->index = string_size(stream->string) + offset; + if(((int32_t)furi_string_size(stream->string) + offset) >= 0) { + stream->index = furi_string_size(stream->string) + offset; } else { result = false; stream->index = 0; @@ -88,7 +88,7 @@ static bool string_stream_seek(StringStream* stream, int32_t offset, StreamOffse break; } - int32_t diff = (stream->index - string_size(stream->string)); + int32_t diff = (stream->index - furi_string_size(stream->string)); if(diff > 0) { stream->index -= diff; result = false; @@ -102,7 +102,7 @@ static size_t string_stream_tell(StringStream* stream) { } static size_t string_stream_size(StringStream* stream) { - return string_size(stream->string); + return furi_string_size(stream->string); } static size_t string_stream_write(StringStream* stream, const char* data, size_t size) { @@ -117,7 +117,7 @@ static size_t string_stream_write(StringStream* stream, const char* data, size_t static size_t string_stream_read(StringStream* stream, char* data, size_t size) { size_t write_index = 0; - const char* cstr = string_get_cstr(stream->string); + const char* cstr = furi_string_get_cstr(stream->string); if(!string_stream_eof(stream)) { while(true) { @@ -145,17 +145,17 @@ static bool string_stream_delete_and_insert( remain_size = MIN(delete_size, remain_size); if(remain_size != 0) { - string_replace_at(stream->string, stream->index, remain_size, ""); + furi_string_replace_at(stream->string, stream->index, remain_size, ""); } } if(write_callback) { - string_t right; - string_init_set(right, &string_get_cstr(stream->string)[stream->index]); - string_left(stream->string, string_stream_tell(stream)); + FuriString* right; + right = furi_string_alloc_set(&furi_string_get_cstr(stream->string)[stream->index]); + furi_string_left(stream->string, string_stream_tell(stream)); result &= write_callback((Stream*)stream, ctx); - string_cat(stream->string, right); - string_clear(right); + furi_string_cat(stream->string, right); + furi_string_free(right); } return result; @@ -169,9 +169,9 @@ static bool string_stream_delete_and_insert( */ static size_t string_stream_write_char(StringStream* stream, char c) { if(string_stream_eof(stream)) { - string_push_back(stream->string, c); + furi_string_push_back(stream->string, c); } else { - string_set_char(stream->string, stream->index, c); + furi_string_set_char(stream->string, stream->index, c); } stream->index++; diff --git a/lib/toolbox/stream/string_stream.h b/lib/toolbox/stream/string_stream.h index 6cccfa6ccf8..f882a246b3e 100644 --- a/lib/toolbox/stream/string_stream.h +++ b/lib/toolbox/stream/string_stream.h @@ -1,6 +1,5 @@ #pragma once #include -#include #include "stream.h" #ifdef __cplusplus diff --git a/lib/toolbox/tar/tar_archive.c b/lib/toolbox/tar/tar_archive.c index 1a542b2e0da..e8b44729138 100644 --- a/lib/toolbox/tar/tar_archive.c +++ b/lib/toolbox/tar/tar_archive.c @@ -220,14 +220,14 @@ static int archive_extract_foreach_cb(mtar_t* tar, const mtar_header_t* header, return 0; } - string_t full_extracted_fname; + FuriString* full_extracted_fname; if(header->type == MTAR_TDIR) { - string_init(full_extracted_fname); + full_extracted_fname = furi_string_alloc(); path_concat(op_params->work_dir, header->name, full_extracted_fname); bool create_res = - storage_simply_mkdir(archive->storage, string_get_cstr(full_extracted_fname)); - string_clear(full_extracted_fname); + storage_simply_mkdir(archive->storage, furi_string_get_cstr(full_extracted_fname)); + furi_string_free(full_extracted_fname); return create_res ? 0 : -1; } @@ -238,19 +238,19 @@ static int archive_extract_foreach_cb(mtar_t* tar, const mtar_header_t* header, FURI_LOG_D(TAG, "Extracting %d bytes to '%s'", header->size, header->name); - string_t converted_fname; - string_init_set(converted_fname, header->name); + FuriString* converted_fname = furi_string_alloc_set(header->name); if(op_params->converter) { op_params->converter(converted_fname); } - string_init(full_extracted_fname); - path_concat(op_params->work_dir, string_get_cstr(converted_fname), full_extracted_fname); + full_extracted_fname = furi_string_alloc(); + path_concat(op_params->work_dir, furi_string_get_cstr(converted_fname), full_extracted_fname); - bool success = archive_extract_current_file(archive, string_get_cstr(full_extracted_fname)); + bool success = + archive_extract_current_file(archive, furi_string_get_cstr(full_extracted_fname)); - string_clear(converted_fname); - string_clear(full_extracted_fname); + furi_string_free(converted_fname); + furi_string_free(full_extracted_fname); return success ? 0 : -1; } @@ -333,32 +333,32 @@ bool tar_archive_add_dir(TarArchive* archive, const char* fs_full_path, const ch break; } - string_t element_name, element_fs_abs_path; - string_init(element_name); - string_init(element_fs_abs_path); + FuriString* element_name = furi_string_alloc(); + FuriString* element_fs_abs_path = furi_string_alloc(); path_concat(fs_full_path, name, element_fs_abs_path); if(strlen(path_prefix)) { path_concat(path_prefix, name, element_name); } else { - string_init_set(element_name, name); + furi_string_set(element_name, name); } if(file_info.flags & FSF_DIRECTORY) { - success = tar_archive_dir_add_element(archive, string_get_cstr(element_name)) && - tar_archive_add_dir( - archive, - string_get_cstr(element_fs_abs_path), - string_get_cstr(element_name)); + success = + tar_archive_dir_add_element(archive, furi_string_get_cstr(element_name)) && + tar_archive_add_dir( + archive, + furi_string_get_cstr(element_fs_abs_path), + furi_string_get_cstr(element_name)); } else { success = tar_archive_add_file( archive, - string_get_cstr(element_fs_abs_path), - string_get_cstr(element_name), + furi_string_get_cstr(element_fs_abs_path), + furi_string_get_cstr(element_name), file_info.size); } - string_clear(element_name); - string_clear(element_fs_abs_path); + furi_string_free(element_name); + furi_string_free(element_fs_abs_path); if(!success) { break; diff --git a/lib/toolbox/tar/tar_archive.h b/lib/toolbox/tar/tar_archive.h index ceaf82ee914..ba2f7749f09 100644 --- a/lib/toolbox/tar/tar_archive.h +++ b/lib/toolbox/tar/tar_archive.h @@ -2,7 +2,6 @@ #include #include -#include #include #ifdef __cplusplus diff --git a/lib/update_util/lfs_backup.c b/lib/update_util/lfs_backup.c index 089f032d4c5..7786524ef64 100644 --- a/lib/update_util/lfs_backup.c +++ b/lib/update_util/lfs_backup.c @@ -11,8 +11,8 @@ #define LFS_BACKUP_DEFAULT_LOCATION EXT_PATH(LFS_BACKUP_DEFAULT_FILENAME) -static void backup_name_converter(string_t filename) { - if(string_empty_p(filename) || (string_get_char(filename, 0) == '.')) { +static void backup_name_converter(FuriString* filename) { + if(furi_string_empty(filename) || (furi_string_get_char(filename, 0) == '.')) { return; } @@ -27,8 +27,8 @@ static void backup_name_converter(string_t filename) { }; for(size_t i = 0; i < COUNT_OF(names); i++) { - if(string_equal_str_p(filename, &names[i][1])) { - string_set_str(filename, names[i]); + if(furi_string_equal(filename, &names[i][1])) { + furi_string_set(filename, names[i]); return; } } diff --git a/lib/update_util/resources/manifest.c b/lib/update_util/resources/manifest.c index 4f8a7d1abde..8b6a1b33cf8 100644 --- a/lib/update_util/resources/manifest.c +++ b/lib/update_util/resources/manifest.c @@ -6,7 +6,7 @@ struct ResourceManifestReader { Storage* storage; Stream* stream; - string_t linebuf; + FuriString* linebuf; ResourceManifestEntry entry; }; @@ -16,16 +16,16 @@ ResourceManifestReader* resource_manifest_reader_alloc(Storage* storage) { resource_manifest->storage = storage; resource_manifest->stream = buffered_file_stream_alloc(resource_manifest->storage); memset(&resource_manifest->entry, 0, sizeof(ResourceManifestEntry)); - string_init(resource_manifest->entry.name); - string_init(resource_manifest->linebuf); + resource_manifest->entry.name = furi_string_alloc(); + resource_manifest->linebuf = furi_string_alloc(); return resource_manifest; } void resource_manifest_reader_free(ResourceManifestReader* resource_manifest) { furi_assert(resource_manifest); - string_clear(resource_manifest->linebuf); - string_clear(resource_manifest->entry.name); + furi_string_free(resource_manifest->linebuf); + furi_string_free(resource_manifest->entry.name); buffered_file_stream_close(resource_manifest->stream); stream_free(resource_manifest->stream); free(resource_manifest); @@ -45,7 +45,7 @@ bool resource_manifest_reader_open(ResourceManifestReader* resource_manifest, co ResourceManifestEntry* resource_manifest_reader_next(ResourceManifestReader* resource_manifest) { furi_assert(resource_manifest); - string_reset(resource_manifest->entry.name); + furi_string_reset(resource_manifest->entry.name); resource_manifest->entry.type = ResourceManifestEntryTypeUnknown; resource_manifest->entry.size = 0; memset(resource_manifest->entry.hash, 0, sizeof(resource_manifest->entry.hash)); @@ -56,9 +56,9 @@ ResourceManifestEntry* resource_manifest_reader_next(ResourceManifestReader* res } /* Trim end of line */ - string_strim(resource_manifest->linebuf); + furi_string_trim(resource_manifest->linebuf); - char type_code = string_get_char(resource_manifest->linebuf, 0); + char type_code = furi_string_get_char(resource_manifest->linebuf, 0); switch(type_code) { case 'F': resource_manifest->entry.type = ResourceManifestEntryTypeFile; @@ -75,9 +75,9 @@ ResourceManifestEntry* resource_manifest_reader_next(ResourceManifestReader* res F::: */ /* Remove entry type code */ - string_right(resource_manifest->linebuf, 2); + furi_string_right(resource_manifest->linebuf, 2); - if(string_search_char(resource_manifest->linebuf, ':') != + if(furi_string_search_char(resource_manifest->linebuf, ':') != sizeof(resource_manifest->entry.hash) * 2) { /* Invalid hash */ continue; @@ -85,27 +85,27 @@ ResourceManifestEntry* resource_manifest_reader_next(ResourceManifestReader* res /* Read hash */ hex_chars_to_uint8( - string_get_cstr(resource_manifest->linebuf), resource_manifest->entry.hash); + furi_string_get_cstr(resource_manifest->linebuf), resource_manifest->entry.hash); /* Remove hash */ - string_right( + furi_string_right( resource_manifest->linebuf, sizeof(resource_manifest->entry.hash) * 2 + 1); - resource_manifest->entry.size = atoi(string_get_cstr(resource_manifest->linebuf)); + resource_manifest->entry.size = atoi(furi_string_get_cstr(resource_manifest->linebuf)); /* Remove size */ - size_t offs = string_search_char(resource_manifest->linebuf, ':'); - string_right(resource_manifest->linebuf, offs + 1); + size_t offs = furi_string_search_char(resource_manifest->linebuf, ':'); + furi_string_right(resource_manifest->linebuf, offs + 1); - string_set(resource_manifest->entry.name, resource_manifest->linebuf); + furi_string_set(resource_manifest->entry.name, resource_manifest->linebuf); } else if(resource_manifest->entry.type == ResourceManifestEntryTypeDirectory) { /* Parse directory entry D: */ /* Remove entry type code */ - string_right(resource_manifest->linebuf, 2); + furi_string_right(resource_manifest->linebuf, 2); - string_set(resource_manifest->entry.name, resource_manifest->linebuf); + furi_string_set(resource_manifest->entry.name, resource_manifest->linebuf); } return &resource_manifest->entry; diff --git a/lib/update_util/resources/manifest.h b/lib/update_util/resources/manifest.h index 092b7badb04..8baa1613ea2 100644 --- a/lib/update_util/resources/manifest.h +++ b/lib/update_util/resources/manifest.h @@ -2,7 +2,6 @@ #include -#include #include #include @@ -18,7 +17,7 @@ typedef enum { typedef struct { ResourceManifestEntryType type; - string_t name; + FuriString* name; uint32_t size; uint8_t hash[16]; } ResourceManifestEntry; diff --git a/lib/update_util/update_manifest.c b/lib/update_util/update_manifest.c index 1b205d9ce6f..795fdd5ebc9 100644 --- a/lib/update_util/update_manifest.c +++ b/lib/update_util/update_manifest.c @@ -21,12 +21,12 @@ UpdateManifest* update_manifest_alloc() { UpdateManifest* update_manifest = malloc(sizeof(UpdateManifest)); - string_init(update_manifest->version); - string_init(update_manifest->firmware_dfu_image); - string_init(update_manifest->radio_image); - string_init(update_manifest->staged_loader_file); - string_init(update_manifest->resource_bundle); - string_init(update_manifest->splash_file); + update_manifest->version = furi_string_alloc(); + update_manifest->firmware_dfu_image = furi_string_alloc(); + update_manifest->radio_image = furi_string_alloc(); + update_manifest->staged_loader_file = furi_string_alloc(); + update_manifest->resource_bundle = furi_string_alloc(); + update_manifest->splash_file = furi_string_alloc(); update_manifest->target = 0; update_manifest->manifest_version = 0; memset(update_manifest->ob_reference.bytes, 0, FURI_HAL_FLASH_OB_RAW_SIZE_BYTES); @@ -38,12 +38,12 @@ UpdateManifest* update_manifest_alloc() { void update_manifest_free(UpdateManifest* update_manifest) { furi_assert(update_manifest); - string_clear(update_manifest->version); - string_clear(update_manifest->firmware_dfu_image); - string_clear(update_manifest->radio_image); - string_clear(update_manifest->staged_loader_file); - string_clear(update_manifest->resource_bundle); - string_clear(update_manifest->splash_file); + furi_string_free(update_manifest->version); + furi_string_free(update_manifest->firmware_dfu_image); + furi_string_free(update_manifest->radio_image); + furi_string_free(update_manifest->staged_loader_file); + furi_string_free(update_manifest->resource_bundle); + furi_string_free(update_manifest->splash_file); free(update_manifest); } @@ -52,10 +52,10 @@ static bool furi_assert(update_manifest); furi_assert(flipper_file); - string_t filetype; + FuriString* filetype; // TODO: compare filetype? - string_init(filetype); + filetype = furi_string_alloc(); update_manifest->valid = flipper_format_read_header(flipper_file, filetype, &update_manifest->manifest_version) && flipper_format_read_string(flipper_file, MANIFEST_KEY_INFO, update_manifest->version) && @@ -68,7 +68,7 @@ static bool MANIFEST_KEY_LOADER_CRC, (uint8_t*)&update_manifest->staged_loader_crc, sizeof(uint32_t)); - string_clear(filetype); + furi_string_free(filetype); if(update_manifest->valid) { /* Optional fields - we can have dfu, radio, resources, or any combination */ @@ -114,9 +114,9 @@ static bool flipper_file, MANIFEST_KEY_SPLASH_FILE, update_manifest->splash_file); update_manifest->valid = - (!string_empty_p(update_manifest->firmware_dfu_image) || - !string_empty_p(update_manifest->radio_image) || - !string_empty_p(update_manifest->resource_bundle)); + (!furi_string_empty(update_manifest->firmware_dfu_image) || + !furi_string_empty(update_manifest->radio_image) || + !furi_string_empty(update_manifest->resource_bundle)); } return update_manifest->valid; diff --git a/lib/update_util/update_manifest.h b/lib/update_util/update_manifest.h index 8f3859471f9..c26e4f87bc1 100644 --- a/lib/update_util/update_manifest.h +++ b/lib/update_util/update_manifest.h @@ -6,7 +6,7 @@ extern "C" { #include #include -#include +#include #include /* Paths don't include /ext -- because at startup SD card is mounted as FS root */ @@ -28,20 +28,20 @@ _Static_assert(sizeof(UpdateManifestRadioVersion) == 6, "UpdateManifestRadioVers typedef struct { uint32_t manifest_version; - string_t version; + FuriString* version; uint32_t target; - string_t staged_loader_file; + FuriString* staged_loader_file; uint32_t staged_loader_crc; - string_t firmware_dfu_image; - string_t radio_image; + FuriString* firmware_dfu_image; + FuriString* radio_image; uint32_t radio_address; UpdateManifestRadioVersion radio_version; uint32_t radio_crc; - string_t resource_bundle; + FuriString* resource_bundle; FuriHalFlashRawOptionByteData ob_reference; FuriHalFlashRawOptionByteData ob_compare_mask; FuriHalFlashRawOptionByteData ob_write_mask; - string_t splash_file; + FuriString* splash_file; bool valid; } UpdateManifest; diff --git a/lib/update_util/update_operation.c b/lib/update_util/update_operation.c index 56f412a95f6..3a44605e0d9 100644 --- a/lib/update_util/update_operation.c +++ b/lib/update_util/update_operation.c @@ -4,7 +4,6 @@ #include #include -#include #include #include #include @@ -34,9 +33,9 @@ const char* update_operation_describe_preparation_result(const UpdatePrepareResu } } -static bool update_operation_get_current_package_path_rtc(Storage* storage, string_t out_path) { +static bool update_operation_get_current_package_path_rtc(Storage* storage, FuriString* out_path) { const uint32_t update_index = furi_hal_rtc_get_register(FuriHalRtcRegisterUpdateFolderFSIndex); - string_set_str(out_path, UPDATE_ROOT_DIR); + furi_string_set(out_path, UPDATE_ROOT_DIR); if(update_index == UPDATE_OPERATION_ROOT_DIR_PACKAGE_MAGIC) { return true; } @@ -63,7 +62,7 @@ static bool update_operation_get_current_package_path_rtc(Storage* storage, stri free(name_buffer); storage_file_free(dir); if(!found) { - string_reset(out_path); + furi_string_reset(out_path); } return found; @@ -72,8 +71,8 @@ static bool update_operation_get_current_package_path_rtc(Storage* storage, stri #define UPDATE_FILE_POINTER_FN EXT_PATH(UPDATE_MANIFEST_POINTER_FILE_NAME) #define UPDATE_MANIFEST_MAX_PATH_LEN 256u -bool update_operation_get_current_package_manifest_path(Storage* storage, string_t out_path) { - string_reset(out_path); +bool update_operation_get_current_package_manifest_path(Storage* storage, FuriString* out_path) { + furi_string_reset(out_path); if(storage_common_stat(storage, UPDATE_FILE_POINTER_FN, NULL) == FSE_OK) { char* manifest_name_buffer = malloc(UPDATE_MANIFEST_MAX_PATH_LEN); File* upd_file = NULL; @@ -91,23 +90,23 @@ bool update_operation_get_current_package_manifest_path(Storage* storage, string if(storage_common_stat(storage, manifest_name_buffer, NULL) != FSE_OK) { break; } - string_set_str(out_path, manifest_name_buffer); + furi_string_set(out_path, manifest_name_buffer); } while(0); free(manifest_name_buffer); storage_file_free(upd_file); } else { /* legacy, will be deprecated */ - string_t rtcpath; - string_init(rtcpath); + FuriString* rtcpath; + rtcpath = furi_string_alloc(); do { if(!update_operation_get_current_package_path_rtc(storage, rtcpath)) { break; } - path_concat(string_get_cstr(rtcpath), UPDATE_MANIFEST_DEFAULT_NAME, out_path); + path_concat(furi_string_get_cstr(rtcpath), UPDATE_MANIFEST_DEFAULT_NAME, out_path); } while(0); - string_clear(rtcpath); + furi_string_free(rtcpath); } - return !string_empty_p(out_path); + return !furi_string_empty(out_path); } static bool update_operation_persist_manifest_path(Storage* storage, const char* manifest_path) { @@ -141,8 +140,8 @@ UpdatePrepareResult update_operation_prepare(const char* manifest_file_path) { File* file = storage_file_alloc(storage); uint64_t free_int_space; - string_t stage_path; - string_init(stage_path); + FuriString* stage_path; + stage_path = furi_string_alloc(); do { if((storage_common_fs_info(storage, STORAGE_INT_PATH_PREFIX, NULL, &free_int_space) != FSE_OK) || @@ -171,9 +170,10 @@ UpdatePrepareResult update_operation_prepare(const char* manifest_file_path) { } path_extract_dirname(manifest_file_path, stage_path); - path_append(stage_path, string_get_cstr(manifest->staged_loader_file)); + path_append(stage_path, furi_string_get_cstr(manifest->staged_loader_file)); - if(!storage_file_open(file, string_get_cstr(stage_path), FSAM_READ, FSOM_OPEN_EXISTING)) { + if(!storage_file_open( + file, furi_string_get_cstr(stage_path), FSAM_READ, FSOM_OPEN_EXISTING)) { result = UpdatePrepareResultStageMissing; break; } @@ -193,7 +193,7 @@ UpdatePrepareResult update_operation_prepare(const char* manifest_file_path) { furi_hal_rtc_set_boot_mode(FuriHalRtcBootModePreUpdate); } while(false); - string_clear(stage_path); + furi_string_free(stage_path); storage_file_free(file); update_manifest_free(manifest); diff --git a/lib/update_util/update_operation.h b/lib/update_util/update_operation.h index 056520e1147..65abf8e1504 100644 --- a/lib/update_util/update_operation.h +++ b/lib/update_util/update_operation.h @@ -1,7 +1,6 @@ #pragma once #include -#include #include #ifdef __cplusplus @@ -19,7 +18,7 @@ extern "C" { * May be empty if update is in root update directory * @return bool if supplied path is valid and out_manifest_dir contains dir to apply */ -bool update_operation_get_package_dir_name(const char* full_path, string_t out_manifest_dir); +bool update_operation_get_package_dir_name(const char* full_path, FuriString* out_manifest_dir); /* When updating this enum, also update assets/protobuf/system.proto */ typedef enum { @@ -51,7 +50,7 @@ UpdatePrepareResult update_operation_prepare(const char* manifest_file_path); * @param out_path Path to manifest. Must be initialized * @return true if path was restored successfully */ -bool update_operation_get_current_package_manifest_path(Storage* storage, string_t out_path); +bool update_operation_get_current_package_manifest_path(Storage* storage, FuriString* out_path); /* * Checks if an update operation step is pending after reset From f16cdd1477451f4d8af6f2864f49003a6516f70e Mon Sep 17 00:00:00 2001 From: Matvey Gerasimov <48400794+spherebread@users.noreply.github.com> Date: Thu, 6 Oct 2022 15:18:40 +0400 Subject: [PATCH 101/824] fix: typo badusb demo windows (#1824) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix a typo in the badusb demo script for Windows. Co-authored-by: あく --- assets/resources/badusb/demo_windows.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/resources/badusb/demo_windows.txt b/assets/resources/badusb/demo_windows.txt index df43535105a..f304f5e8d57 100644 --- a/assets/resources/badusb/demo_windows.txt +++ b/assets/resources/badusb/demo_windows.txt @@ -78,7 +78,7 @@ ENTER STRING Flipper Zero BadUSB feature is compatible with USB Rubber Ducky script format ENTER -STRING More information about script synax can be found here: +STRING More information about script syntax can be found here: ENTER STRING https://github.com/hak5darren/USB-Rubber-Ducky/wiki/Duckyscript ENTER From a69e150e2f493bbe89a739bc944aa604d2520987 Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Thu, 6 Oct 2022 14:36:21 +0300 Subject: [PATCH 102/824] [FL-2812] RFID: write fix for some protocols #1828 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- lib/lfrfid/protocols/protocol_awid.c | 7 +++++++ lib/lfrfid/protocols/protocol_fdx_a.c | 9 +++++++++ lib/lfrfid/protocols/protocol_keri.c | 3 +++ lib/lfrfid/protocols/protocol_pyramid.c | 1 + 4 files changed, 20 insertions(+) diff --git a/lib/lfrfid/protocols/protocol_awid.c b/lib/lfrfid/protocols/protocol_awid.c index 69409b3123f..88093900ee7 100644 --- a/lib/lfrfid/protocols/protocol_awid.c +++ b/lib/lfrfid/protocols/protocol_awid.c @@ -205,8 +205,15 @@ bool protocol_awid_write_data(ProtocolAwid* protocol, void* data) { LFRFIDWriteRequest* request = (LFRFIDWriteRequest*)data; bool result = false; + // Fix incorrect length byte + if(protocol->data[0] != 26 && protocol->data[0] != 50 && protocol->data[0] != 37 && + protocol->data[0] != 34) { + protocol->data[0] = 26; + } + // Correct protocol data by redecoding protocol_awid_encode(protocol->data, (uint8_t*)protocol->encoded_data); + bit_lib_remove_bit_every_nth((uint8_t*)protocol->encoded_data, 8, 88, 4); protocol_awid_decode(protocol->encoded_data, protocol->data); protocol_awid_encode(protocol->data, (uint8_t*)protocol->encoded_data); diff --git a/lib/lfrfid/protocols/protocol_fdx_a.c b/lib/lfrfid/protocols/protocol_fdx_a.c index 058dc553e6a..87daa0eb642 100644 --- a/lib/lfrfid/protocols/protocol_fdx_a.c +++ b/lib/lfrfid/protocols/protocol_fdx_a.c @@ -79,6 +79,14 @@ static bool protocol_fdx_a_decode(const uint8_t* from, uint8_t* to) { return true; } +static void protocol_fdx_a_fix_parity(ProtocolFDXA* protocol) { + for(size_t i = 0; i < FDXA_DECODED_DATA_SIZE; i++) { + if(bit_lib_test_parity_32(protocol->data[i], BitLibParityOdd)) { + protocol->data[i] ^= (1 << 7); + } + } +} + static bool protocol_fdx_a_can_be_decoded(const uint8_t* data) { // check preamble if(data[0] != FDXA_PREAMBLE_0 || data[1] != FDXA_PREAMBLE_1 || data[12] != FDXA_PREAMBLE_0 || @@ -179,6 +187,7 @@ bool protocol_fdx_a_write_data(ProtocolFDXA* protocol, void* data) { bool result = false; // Correct protocol data by redecoding + protocol_fdx_a_fix_parity(protocol); protocol_fdx_a_encoder_start(protocol); protocol_fdx_a_decode(protocol->encoded_data, protocol->data); diff --git a/lib/lfrfid/protocols/protocol_keri.c b/lib/lfrfid/protocols/protocol_keri.c index 20e44a702e9..099fd1680ef 100644 --- a/lib/lfrfid/protocols/protocol_keri.c +++ b/lib/lfrfid/protocols/protocol_keri.c @@ -170,6 +170,7 @@ bool protocol_keri_encoder_start(ProtocolKeri* protocol) { memset(protocol->encoded_data, 0, KERI_ENCODED_DATA_SIZE); *(uint32_t*)&protocol->encoded_data[0] = 0b00000000000000000000000011100000; bit_lib_copy_bits(protocol->encoded_data, 32, 32, protocol->data, 0); + bit_lib_set_bits(protocol->encoded_data, 32, 1, 1); protocol->encoder.last_bit = bit_lib_get_bit(protocol->encoded_data, KERI_ENCODED_BIT_SIZE - 1); @@ -224,6 +225,8 @@ bool protocol_keri_write_data(ProtocolKeri* protocol, void* data) { LFRFIDWriteRequest* request = (LFRFIDWriteRequest*)data; bool result = false; + // Start bit should be always set + protocol->data[0] |= (1 << 7); protocol_keri_encoder_start(protocol); if(request->write_type == LFRFIDWriteTypeT5577) { diff --git a/lib/lfrfid/protocols/protocol_pyramid.c b/lib/lfrfid/protocols/protocol_pyramid.c index 2e1b57edcad..5457445809e 100644 --- a/lib/lfrfid/protocols/protocol_pyramid.c +++ b/lib/lfrfid/protocols/protocol_pyramid.c @@ -221,6 +221,7 @@ bool protocol_pyramid_write_data(ProtocolPyramid* protocol, void* data) { // Correct protocol data by redecoding protocol_pyramid_encode(protocol); + bit_lib_remove_bit_every_nth(protocol->encoded_data, 8, 15 * 8, 8); protocol_pyramid_decode(protocol); protocol_pyramid_encoder_start(protocol); From 9bf11d9fd20576165a5c8c4ece68944abeea9dba Mon Sep 17 00:00:00 2001 From: hedger Date: Thu, 6 Oct 2022 17:55:57 +0400 Subject: [PATCH 103/824] [FL-2859,2838] fbt: improvements for FAPs (#1813) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fbt: assets builder for apps WIP * fbt: automatically building private fap assets * docs: details on how to use image assets * fbt: renamed fap_assets -> fap_icons * fbt: support for fap_extbuild field * docs: info on fap_extbuild * fbt: added --proxy-env parame ter * fbt: made firmware_cdb & updater_cdb targets always available * fbt: renamed fap_icons -> fap_icon_assets * fbt: deprecated firmware_* target names for faps; new alias is "fap_APPID" * fbt: changed intermediate file locations for external apps * fbt: support for fap_private_libs; docs: updates * restored mbedtls as global lib * scripts: lint.py: skip "lib" subfolder * fbt: Sanity checks for building advanced faps as part of fw * docs: info on fap_private_libs; fbt: optimized *.fam indexing * fbt: cleanup; samples: added sample_icons app * fbt: moved example app to applications/examples * linter fix * docs: readme fixes * added applications/examples/application.fam stub * docs: more info on private libs Co-authored-by: あく --- applications/examples/application.fam | 5 + .../examples/example_images/application.fam | 10 ++ .../examples/example_images/example_images.c | 79 ++++++++++ .../example_images/images/dolphin_71x25.png | Bin 0 -> 1188 bytes applications/plugins/picopass/application.fam | 9 +- .../{ => lib}/loclass/optimized_cipher.c | 0 .../{ => lib}/loclass/optimized_cipher.h | 0 .../{ => lib}/loclass/optimized_cipherutils.c | 0 .../{ => lib}/loclass/optimized_cipherutils.h | 0 .../{ => lib}/loclass/optimized_elite.c | 0 .../{ => lib}/loclass/optimized_elite.h | 0 .../{ => lib}/loclass/optimized_ikeys.c | 0 .../{ => lib}/loclass/optimized_ikeys.h | 0 .../plugins/picopass/picopass_device.h | 4 +- assets/SConscript | 19 +-- documentation/AppManifests.md | 57 ++++++- documentation/AppsOnSDCard.md | 17 +- documentation/fbt.md | 11 +- firmware.scons | 35 +++-- scripts/assets.py | 19 ++- scripts/lint.py | 4 + site_scons/commandline.scons | 9 ++ site_scons/environ.scons | 10 +- site_scons/extapps.scons | 26 +++- site_scons/fbt/appmanifest.py | 25 +++ site_scons/site_tools/fbt_assets.py | 30 +++- site_scons/site_tools/fbt_extapps.py | 146 +++++++++++++++--- 27 files changed, 436 insertions(+), 79 deletions(-) create mode 100644 applications/examples/application.fam create mode 100644 applications/examples/example_images/application.fam create mode 100644 applications/examples/example_images/example_images.c create mode 100644 applications/examples/example_images/images/dolphin_71x25.png rename applications/plugins/picopass/{ => lib}/loclass/optimized_cipher.c (100%) rename applications/plugins/picopass/{ => lib}/loclass/optimized_cipher.h (100%) rename applications/plugins/picopass/{ => lib}/loclass/optimized_cipherutils.c (100%) rename applications/plugins/picopass/{ => lib}/loclass/optimized_cipherutils.h (100%) rename applications/plugins/picopass/{ => lib}/loclass/optimized_elite.c (100%) rename applications/plugins/picopass/{ => lib}/loclass/optimized_elite.h (100%) rename applications/plugins/picopass/{ => lib}/loclass/optimized_ikeys.c (100%) rename applications/plugins/picopass/{ => lib}/loclass/optimized_ikeys.h (100%) diff --git a/applications/examples/application.fam b/applications/examples/application.fam new file mode 100644 index 00000000000..16d240ccf43 --- /dev/null +++ b/applications/examples/application.fam @@ -0,0 +1,5 @@ +App( + appid="sample_apps", + name="Sample apps bundle", + apptype=FlipperAppType.METAPACKAGE, +) diff --git a/applications/examples/example_images/application.fam b/applications/examples/example_images/application.fam new file mode 100644 index 00000000000..9a5f8e03048 --- /dev/null +++ b/applications/examples/example_images/application.fam @@ -0,0 +1,10 @@ +App( + appid="example_images", + name="Example: Images", + apptype=FlipperAppType.EXTERNAL, + entry_point="example_images_main", + requires=["gui"], + stack_size=1 * 1024, + fap_category="Examples", + fap_icon_assets="images", +) diff --git a/applications/examples/example_images/example_images.c b/applications/examples/example_images/example_images.c new file mode 100644 index 00000000000..48fa5e77e3f --- /dev/null +++ b/applications/examples/example_images/example_images.c @@ -0,0 +1,79 @@ +#include +#include + +#include +#include + +#include "example_images_icons.h" + +typedef struct { + uint8_t x, y; +} ImagePosition; + +static ImagePosition image_position = {.x = 0, .y = 0}; + +// Screen is 128x64 px +static void app_draw_callback(Canvas* canvas, void* ctx) { + UNUSED(ctx); + + canvas_clear(canvas); + canvas_draw_icon(canvas, image_position.x % 128, image_position.y % 64, &I_dolphin_71x25); +} + +static void app_input_callback(InputEvent* input_event, void* ctx) { + furi_assert(ctx); + + FuriMessageQueue* event_queue = ctx; + furi_message_queue_put(event_queue, input_event, FuriWaitForever); +} + +int32_t example_images_main(void* p) { + UNUSED(p); + FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); + + // Configure view port + ViewPort* view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, app_draw_callback, view_port); + view_port_input_callback_set(view_port, app_input_callback, event_queue); + + // Register view port in GUI + Gui* gui = furi_record_open(RECORD_GUI); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); + + InputEvent event; + + bool running = true; + while(running) { + if(furi_message_queue_get(event_queue, &event, 100) == FuriStatusOk) { + if((event.type == InputTypePress) || (event.type == InputTypeRepeat)) { + switch(event.key) { + case InputKeyLeft: + image_position.x -= 2; + break; + case InputKeyRight: + image_position.x += 2; + break; + case InputKeyUp: + image_position.y -= 2; + break; + case InputKeyDown: + image_position.y += 2; + break; + default: + running = false; + break; + } + } + } + view_port_update(view_port); + } + + view_port_enabled_set(view_port, false); + gui_remove_view_port(gui, view_port); + view_port_free(view_port); + furi_message_queue_free(event_queue); + + furi_record_close(RECORD_GUI); + + return 0; +} diff --git a/applications/examples/example_images/images/dolphin_71x25.png b/applications/examples/example_images/images/dolphin_71x25.png new file mode 100644 index 0000000000000000000000000000000000000000..6b3f8aa59b91c644a942bbe12f16c93f4c2d4619 GIT binary patch literal 1188 zcmaJ>TWAzl7@kEf7@I;~nks=zCO(*wx$f*`#%yF}XLqwDyN&A>qX;^elkAYpoN;E7 zT_ad+1+A!okV0Py4WTxu1Va$ocu7nty*w!DqxewhOYlKo`cO5bXEwXhhv>kWbN(~m z_uv2drZ1mqY}nO+VOV3fM=78^gVxT_7W6*!a_R}%LS7*wW3%^LR*YCPxa}3AQ3{SH>$uMGA5P2T2Jp{6c<6W*XAQqH#%^s2xM9KFZk*3S#GF1*!&>f^% zK@ez$qdAU52+})Y`)Y->z4mn_H8l$Gbk}rz6WVy7R@LB$pCFLS>#V-n3T8~@-t~m;fvub zaw?2&QUafr!$gg1Y?8kkH}Y;SU2Q?+5*@V5TkW&nn$=s>o55hv8Yf7xu#|2!_;DXYyRGERL7p5^$ #include "rfal_picopass.h" -#include "loclass/optimized_ikeys.h" -#include "loclass/optimized_cipher.h" +#include +#include #define PICOPASS_DEV_NAME_MAX_LEN 22 #define PICOPASS_READER_DATA_MAX_SIZE 64 diff --git a/assets/SConscript b/assets/SConscript index a0b3b13abdb..e1bf546cc36 100644 --- a/assets/SConscript +++ b/assets/SConscript @@ -9,28 +9,13 @@ assetsenv = env.Clone( ) assetsenv.ApplyLibFlags() -if not assetsenv["VERBOSE"]: - assetsenv.SetDefault( - ICONSCOMSTR="\tICONS\t${TARGET}", - PROTOCOMSTR="\tPROTO\t${SOURCE}", - DOLPHINCOMSTR="\tDOLPHIN\t${DOLPHIN_RES_TYPE}", - RESMANIFESTCOMSTR="\tMANIFEST\t${TARGET}", - PBVERCOMSTR="\tPBVER\t${TARGET}", - ) - -# Gathering icons sources -icons_src = assetsenv.GlobRecursive("*.png", "icons") -icons_src += assetsenv.GlobRecursive("frame_rate", "icons") - -icons = assetsenv.IconBuilder( - assetsenv.Dir("compiled"), ICON_SRC_DIR=assetsenv.Dir("#/assets/icons") +icons = assetsenv.CompileIcons( + assetsenv.Dir("compiled"), assetsenv.Dir("#/assets/icons") ) -assetsenv.Depends(icons, icons_src) assetsenv.Alias("icons", icons) # Protobuf .proto -> .c + .h - proto_src = assetsenv.Glob("protobuf/*.proto", source=True) proto_options = assetsenv.Glob("protobuf/*.options", source=True) proto = assetsenv.ProtoBuilder(assetsenv.Dir("compiled"), proto_src) diff --git a/documentation/AppManifests.md b/documentation/AppManifests.md index 0a4f5e9b778..14c0ae3ac68 100644 --- a/documentation/AppManifests.md +++ b/documentation/AppManifests.md @@ -41,9 +41,12 @@ Only 2 parameters are mandatory: ***appid*** and ***apptype***, others are optio * **order**: Order of an application within its group when sorting entries in it. The lower the order is, the closer to the start of the list the item is placed. *Used for ordering startup hooks and menu entries.* * **sdk_headers**: List of C header files from this app's code to include in API definitions for external applications. + +#### Parameters for external applications + The following parameters are used only for [FAPs](./AppsOnSDCard.md): -* **sources**: list of strings, file name masks, used for gathering sources within app folder. Default value of `["*.c*"]` includes C and CPP source files. +* **sources**: list of strings, file name masks, used for gathering sources within app folder. Default value of `["*.c*"]` includes C and C++ source files. Application cannot use `"lib"` folder for their own source code, as it is reserved for **fap_private_libs**. * **fap_version**: tuple, 2 numbers in form of (x,y): application version to be embedded within .fap file. Default value is (0,1), meanig version "0.1". * **fap_icon**: name of a .png file, 1-bit color depth, 10x10px, to be embedded within .fap file. * **fap_libs**: list of extra libraries to link application against. Provides access to extra functions that are not exported as a part of main firmware at expense of increased .fap file size and RAM consumption. @@ -51,6 +54,58 @@ The following parameters are used only for [FAPs](./AppsOnSDCard.md): * **fap_description**: string, may be empty. Short application description. * **fap_author**: string, may be empty. Application's author. * **fap_weburl**: string, may be empty. Application's homepage. +* **fap_icon_assets**: string. If present, defines a folder name to be used for gathering image assets for this application. These images will be preprocessed and built alongside the application. See [FAP assets](./AppsOnSDCard.md#fap-assets) for details. +* **fap_extbuild**: provides support for parts of application sources to be build by external tools. Contains a list of `ExtFile(path="file name", command="shell command")` definitions. **`fbt`** will run the specified command for each file in the list. +Note that commands are executed at the firmware root folder's root, and all intermediate files must be placed in a application's temporary build folder. For that, you can use pattern expansion by **`fbt`**: `${FAP_WORK_DIR}` will be replaced with the path to the application's temporary build folder, and `${FAP_SRC_DIR}` will be replaced with the path to the application's source folder. You can also use other variables defined internally by **`fbt`**. + +Example for building an app from Rust sources: + +```python + sources=["target/thumbv7em-none-eabihf/release/libhello_rust.a"], + fap_extbuild=( + ExtFile( + path="${FAP_WORK_DIR}/target/thumbv7em-none-eabihf/release/libhello_rust.a", + command="cargo build --release --verbose --target thumbv7em-none-eabihf --target-dir ${FAP_WORK_DIR}/target --manifest-path ${FAP_SRC_DIR}/Cargo.toml", + ), + ), +``` + +* **fap_private_libs**: list of additional libraries that are distributed as sources alongside the application. These libraries will be built as a part of the application build process. +Library sources must be placed in a subfolder of "`lib`" folder within the application's source folder. +Each library is defined as a call to `Lib()` function, accepting the following parameters: + + - **name**: name of library's folder. Required. + - **fap_include_paths**: list of library's relative paths to add to parent fap's include path list. Default value is `["."]` meaning library's source root. + - **sources**: list of filename masks to be used for gathering include files for this library. Default value is `["*.c*"]`. + - **cflags**: list of additional compiler flags to be used for building this library. Default value is `[]`. + - **cdefines**: list of additional preprocessor definitions to be used for building this library. Default value is `[]`. + - **cincludes**: list of additional include paths to be used for building this library. Can be used for providing external search paths for this library's code - for configuration headers. Default value is `[]`. + +Example for building an app with a private library: + +```python + fap_private_libs=[ + Lib( + name="mbedtls", + fap_include_paths=["include"], + sources=[ + "library/des.c", + "library/sha1.c", + "library/platform_util.c", + ], + cdefines=["MBEDTLS_ERROR_C"], + ), + Lib( + name="loclass", + cflags=["-Wno-error"], + ), + ], +``` + +For that snippet, **`fbt`** will build 2 libraries: one from sources in `lib/mbedtls` folder, and another from sources in `lib/loclass` folder. For `mbedtls` library, **`fbt`** will add `lib/mbedtls/include` to the list of include paths for the application and compile only the files specified in `sources` list. Additionally, **`fbt`** will enable `MBEDTLS_ERROR_C` preprocessor definition for `mbedtls` sources. +For `loclass` library, **`fbt`** will add `lib/loclass` to the list of include paths for the application and build all sources in that folder. Also **`fbt`** will disable treating compiler warnings as errors for `loclass` library specifically - that can be useful when compiling large 3rd-party codebases. + +Both libraries will be linked into the application. ## .fam file contents diff --git a/documentation/AppsOnSDCard.md b/documentation/AppsOnSDCard.md index 52582153073..552be13e47d 100644 --- a/documentation/AppsOnSDCard.md +++ b/documentation/AppsOnSDCard.md @@ -2,7 +2,7 @@ [fbt](./fbt.md) has support for building applications as FAP files. FAP are essentially .elf executables with extra metadata and resources bundled in. -FAPs are built with `firmware_extapps` (or `plugin_dist`) **`fbt`** targets. +FAPs are built with `faps` **`fbt`** target. They can also be deployed to `dist` folder with `plugin_dist` **`fbt`** target. FAPs do not depend on being run on a specific firmware version. Compatibility is determined by the FAP's metadata, which includes the required [API version](#api-versioning). @@ -18,6 +18,17 @@ To build your application as a FAP, just create a folder with your app's source * To build all FAPs, run `./fbt plugin_dist`. +## FAP assets + +FAPs can include static and animated images as private assets. They will be automatically compiled alongside application sources and can be referenced the same way as assets from the main firmware. + +To use that feature, put your images in a subfolder inside your application's folder, then reference that folder in your application's manifest in `fap_icon_assets` field. See [Application Manifests](./AppManifests.md#application-definition) for more details. + +To use these assets in your application, put `#include "{APPID}_icons.h"` in your application's source code, where `{APPID}` is the `appid` value field from your application's manifest. Then you can use all icons from your application's assets the same way as if they were a part of `assets_icons.h` of the main firmware. + +Images and animated icons must follow the same [naming convention](../assets/ReadMe.md#asset-naming-rules) as those from the main firmware. + + ## Debugging FAPs **`fbt`** includes a script for gdb-py to provide debugging support for FAPs, `debug/flipperapps.py`. It is loaded in default debugging configurations by **`fbt`** and stock VSCode configurations. @@ -53,13 +64,13 @@ App loader allocates memory for the application and copies it to RAM, processing Not all parts of firmware are available for external applications. A subset of available functions and variables is defined in "api_symbols.csv" file, which is a part of firmware target definition in `firmware/targets/` directory. -**`fbt`** uses semantic versioning for API versioning. Major version is incremented when there are breaking changes in the API, minor version is incremented when there are new features added. +**`fbt`** uses semantic versioning for API. Major version is incremented when there are breaking changes in the API, minor version is incremented when new features are added. Breaking changes include: - removal of a function or a global variable; - changing the signature of a function. -API versioning is mostly automated by **`fbt`**. When rebuilding the firmware, **`fbt`** checks if there are any changes in the API exposed by headers gathered from `SDK_HEADERS`. If there are, it stops the build, adjusts the API version and asks the user to go through the changes in .csv file. New entries are marked with "`?`" mark, and the user is supposed to change the mark to "`+`" for the entry to be exposed for FAPs, "`-`" for it to be unavailable. +API versioning is mostly automated by **`fbt`**. When rebuilding the firmware, **`fbt`** checks if there are any changes in the API exposed by headers gathered from `SDK_HEADERS`. If so, it stops the build, adjusts the API version and asks the user to go through the changes in .csv file. New entries are marked with "`?`" mark, and the user is supposed to change the mark to "`+`" for the entry to be exposed for FAPs, "`-`" for it to be unavailable. **`fbt`** will not allow building a firmware until all "`?`" entries are changed to "`+`" or "`-`". diff --git a/documentation/fbt.md b/documentation/fbt.md index 090ff78f0ee..e20d4317742 100644 --- a/documentation/fbt.md +++ b/documentation/fbt.md @@ -59,10 +59,11 @@ To run cleanup (think of `make clean`) for specified targets, add `-c` option. ### Firmware targets -- `firmware_extapps` - build all plug-ins as separate .elf files - - `firmware_snake_game`, etc - build single plug-in as .elf by its name - - Check out `--extra-ext-apps` for force adding extra apps to external build - - `firmware_snake_game_list`, etc - generate source + assembler listing for app's .elf +- `faps` - build all external & plugin apps as [.faps](./AppsOnSDCard.md#fap-flipper-application-package). +- **`fbt`** also defines per-app targets. For example, for an app with `appid=snake_game` target names are: + - `fap_snake_game`, etc - build single app as .fap by its application ID. + - Check out [`--extra-ext-apps`](#command-line-parameters) for force adding extra apps to external build + - `fap_snake_game_list`, etc - generate source + assembler listing for app's .fap - `flash`, `firmware_flash` - flash current version to attached device with OpenOCD over ST-Link - `jflash` - flash current version to attached device with JFlash using J-Link probe. JFlash executable must be on your $PATH - `flash_blackmagic` - flash current version to attached device with Blackmagic probe @@ -83,9 +84,9 @@ To run cleanup (think of `make clean`) for specified targets, add `-c` option. ## Command-line parameters - `--options optionfile.py` (default value `fbt_options.py`) - load file with multiple configuration values -- `--with-updater` - enables updater-related targets and dependency tracking. Enabling this option introduces extra startup time costs, so use it when bundling update packages. _Explicily enabling this should no longer be required, **`fbt`** now has specific handling for updater-related targets_ - `--extra-int-apps=app1,app2,appN` - forces listed apps to be built as internal with `firmware` target - `--extra-ext-apps=app1,app2,appN` - forces listed apps to be built as external with `firmware_extapps` target +- `--proxy-env=VAR1,VAR2` - additional environment variables to expose to subprocesses spawned by `fbt`. By default, `fbt` sanitizes execution environment and doesn't forward all inherited environment variables. You can find list of variables that are always forwarded in `environ.scons` file. ## Configuration diff --git a/firmware.scons b/firmware.scons index 0970541e825..dd13b6b3d55 100644 --- a/firmware.scons +++ b/firmware.scons @@ -1,5 +1,6 @@ Import("ENV", "fw_build_meta") +from SCons.Errors import UserError import itertools from fbt.util import ( @@ -164,13 +165,25 @@ apps_c = fwenv.ApplicationsC( # Adding dependency on manifest files so apps.c is rebuilt when any manifest is changed for app_dir, _ in env["APPDIRS"]: app_dir_node = env.Dir("#").Dir(app_dir) - fwenv.Depends(apps_c, fwenv.GlobRecursive("*.fam", app_dir_node)) + fwenv.Depends(apps_c, app_dir_node.glob("*/application.fam")) + +# Sanity check - certain external apps are using features that are not available in base firmware +if advanced_faps := list( + filter( + lambda app: app.fap_extbuild or app.fap_private_libs or app.fap_icon_assets, + fwenv["APPBUILD"].get_builtin_apps(), + ) +): + raise UserError( + "An Application that is using fap-specific features cannot be built into base firmware." + f" Offending app(s): {', '.join(app.appid for app in advanced_faps)}" + ) sources = [apps_c] # Gather sources only from app folders in current configuration sources.extend( itertools.chain.from_iterable( - fwenv.GlobRecursive(source_type, appdir.relpath) + fwenv.GlobRecursive(source_type, appdir.relpath, exclude="lib") for appdir, source_type in fwenv["APPBUILD"].get_builtin_app_folders() ) ) @@ -259,18 +272,18 @@ fw_artifacts = fwenv["FW_ARTIFACTS"] = [ fwenv["FW_VERSION_JSON"], ] + +fwcdb = fwenv.CompilationDatabase() +# without filtering, both updater & firmware commands would be generated in same file +fwenv.Replace(COMPILATIONDB_PATH_FILTER=fwenv.subst("*${FW_FLAVOR}*")) +AlwaysBuild(fwcdb) +Precious(fwcdb) +NoClean(fwcdb) +Alias(fwenv["FIRMWARE_BUILD_CFG"] + "_cdb", fwcdb) + # If current configuration was explicitly requested, generate compilation database # and link its directory as build/latest if should_gen_cdb_and_link_dir(fwenv, BUILD_TARGETS): - fwcdb = fwenv.CompilationDatabase() - # without filtering, both updater & firmware commands would be generated - fwenv.Replace(COMPILATIONDB_PATH_FILTER=fwenv.subst("*${FW_FLAVOR}*")) - AlwaysBuild(fwcdb) - Precious(fwcdb) - NoClean(fwcdb) - Alias(fwenv["FIRMWARE_BUILD_CFG"] + "_cdb", fwcdb) - AlwaysBuild(fwenv["FIRMWARE_BUILD_CFG"] + "_cdb", fwcdb) - Alias(fwcdb, "") fw_artifacts.append(fwcdb) # Adding as a phony target, so folder link is updated even if elf didn't change diff --git a/scripts/assets.py b/scripts/assets.py index 9b4ee5b61f7..75bebcfb454 100755 --- a/scripts/assets.py +++ b/scripts/assets.py @@ -14,7 +14,7 @@ """ ICONS_TEMPLATE_H_ICON_NAME = "extern const Icon {name};\n" -ICONS_TEMPLATE_C_HEADER = """#include \"assets_icons.h\" +ICONS_TEMPLATE_C_HEADER = """#include "{assets_filename}.h" #include @@ -33,6 +33,13 @@ def init(self): ) self.parser_icons.add_argument("input_directory", help="Source directory") self.parser_icons.add_argument("output_directory", help="Output directory") + self.parser_icons.add_argument( + "--filename", + help="Base filename for file with icon data", + required=False, + default="assets_icons", + ) + self.parser_icons.set_defaults(func=self.icons) self.parser_manifest = self.subparsers.add_parser( @@ -102,13 +109,15 @@ def _iconIsSupported(self, filename): return extension in ICONS_SUPPORTED_FORMATS def icons(self): - self.logger.debug(f"Converting icons") + self.logger.debug("Converting icons") icons_c = open( - os.path.join(self.args.output_directory, "assets_icons.c"), + os.path.join(self.args.output_directory, f"{self.args.filename}.c"), "w", newline="\n", ) - icons_c.write(ICONS_TEMPLATE_C_HEADER) + icons_c.write( + ICONS_TEMPLATE_C_HEADER.format(assets_filename=self.args.filename) + ) icons = [] # Traverse icons tree, append image data to source file for dirpath, dirnames, filenames in os.walk(self.args.input_directory): @@ -194,7 +203,7 @@ def icons(self): # Create Public Header self.logger.debug(f"Creating header") icons_h = open( - os.path.join(self.args.output_directory, "assets_icons.h"), + os.path.join(self.args.output_directory, f"{self.args.filename}.h"), "w", newline="\n", ) diff --git a/scripts/lint.py b/scripts/lint.py index 30a5699a762..c178c8763a8 100755 --- a/scripts/lint.py +++ b/scripts/lint.py @@ -54,6 +54,10 @@ def _find_sources(self, folders: list): output = [] for folder in folders: for dirpath, dirnames, filenames in os.walk(folder): + # Skipping 3rd-party code - usually resides in subfolder "lib" + if "lib" in dirnames: + dirnames.remove("lib") + for filename in filenames: ext = os.path.splitext(filename.lower())[1] if not ext in SOURCE_CODE_FILE_EXTENSIONS: diff --git a/site_scons/commandline.scons b/site_scons/commandline.scons index 2eeda24798b..765af08f18c 100644 --- a/site_scons/commandline.scons +++ b/site_scons/commandline.scons @@ -34,6 +34,14 @@ AddOption( help="List of applications to forcefully build as standalone .elf", ) +AddOption( + "--proxy-env", + action="store", + dest="proxy_env", + default="", + help="Comma-separated list of additional environment variables to pass to child SCons processes", +) + # Construction environment variables @@ -230,6 +238,7 @@ vars.Add( ("applications/system", False), ("applications/debug", False), ("applications/plugins", False), + ("applications/examples", False), ("applications_user", False), ], ) diff --git a/site_scons/environ.scons b/site_scons/environ.scons index c61f29616e5..3e0c6bea786 100644 --- a/site_scons/environ.scons +++ b/site_scons/environ.scons @@ -12,14 +12,20 @@ forward_os_env = { "PATH": os.environ["PATH"], } # Proxying CI environment to child processes & scripts -for env_value_name in ( +variables_to_forward = [ "WORKFLOW_BRANCH_OR_TAG", "DIST_SUFFIX", "HOME", "APPDATA", "PYTHONHOME", "PYTHONNOUSERSITE", -): + "TMP", + "TEMP", +] +if proxy_env := GetOption("proxy_env"): + variables_to_forward.extend(proxy_env.split(",")) + +for env_value_name in variables_to_forward: if environ_value := os.environ.get(env_value_name, None): forward_os_env[env_value_name] = environ_value diff --git a/site_scons/extapps.scons b/site_scons/extapps.scons index 66002915f01..c976fbbe5b6 100644 --- a/site_scons/extapps.scons +++ b/site_scons/extapps.scons @@ -1,10 +1,23 @@ +from SCons.Errors import UserError + + Import("ENV") from fbt.appmanifest import FlipperAppType appenv = ENV.Clone( - tools=[("fbt_extapps", {"EXT_APPS_WORK_DIR": ENV.subst("${BUILD_DIR}/.extapps")})] + tools=[ + ( + "fbt_extapps", + { + "EXT_APPS_WORK_DIR": ENV.subst( + "${BUILD_DIR}/.extapps", + ) + }, + ), + "fbt_assets", + ] ) appenv.Replace( @@ -83,7 +96,16 @@ if extra_app_list := GetOption("extra_ext_apps"): if appenv["FORCE"]: appenv.AlwaysBuild(extapps["compact"].values()) -Alias(appenv["FIRMWARE_BUILD_CFG"] + "_extapps", extapps["compact"].values()) + +# Deprecation stub +def legacy_app_build_stub(**kw): + raise UserError(f"Target name 'firmware_extapps' is deprecated, use 'faps' instead") + + +appenv.PhonyTarget("firmware_extapps", appenv.Action(legacy_app_build_stub, None)) + + +Alias("faps", extapps["compact"].values()) if appsrc := appenv.subst("$APPSRC"): app_manifest, fap_file, app_validator = appenv.GetExtAppFromPath(appsrc) diff --git a/site_scons/fbt/appmanifest.py b/site_scons/fbt/appmanifest.py index a1132eeaba0..de7c6b68205 100644 --- a/site_scons/fbt/appmanifest.py +++ b/site_scons/fbt/appmanifest.py @@ -23,6 +23,22 @@ class FlipperAppType(Enum): @dataclass class FlipperApplication: + @dataclass + class ExternallyBuiltFile: + path: str + command: str + + @dataclass + class Library: + name: str + fap_include_paths: List[str] = field(default_factory=lambda: ["."]) + sources: List[str] = field(default_factory=lambda: ["*.c*"]) + cflags: List[str] = field(default_factory=list) + cdefines: List[str] = field(default_factory=list) + cincludes: List[str] = field(default_factory=list) + + PRIVATE_FIELD_PREFIX = "_" + appid: str apptype: FlipperAppType name: Optional[str] = "" @@ -45,6 +61,9 @@ class FlipperApplication: fap_description: str = "" fap_author: str = "" fap_weburl: str = "" + fap_icon_assets: Optional[str] = None + fap_extbuild: List[ExternallyBuiltFile] = field(default_factory=list) + fap_private_libs: List[Library] = field(default_factory=list) # Internally used by fbt _appdir: Optional[object] = None _apppath: Optional[str] = None @@ -88,6 +107,12 @@ def App(*args, **kw): ), ) + def ExtFile(*args, **kw): + return FlipperApplication.ExternallyBuiltFile(*args, **kw) + + def Lib(*args, **kw): + return FlipperApplication.Library(*args, **kw) + try: with open(app_manifest_path, "rt") as manifest_file: exec(manifest_file.read()) diff --git a/site_scons/site_tools/fbt_assets.py b/site_scons/site_tools/fbt_assets.py index 877948471bb..f058d15f918 100644 --- a/site_scons/site_tools/fbt_assets.py +++ b/site_scons/site_tools/fbt_assets.py @@ -10,8 +10,8 @@ def icons_emitter(target, source, env): target = [ - "compiled/assets_icons.c", - "compiled/assets_icons.h", + target[0].File(env.subst("${ICON_FILE_NAME}.c")), + target[0].File(env.subst("${ICON_FILE_NAME}.h")), ] source = env.GlobRecursive("*.*", env["ICON_SRC_DIR"]) return target, source @@ -99,17 +99,41 @@ def proto_ver_generator(target, source, env): file.write("\n".join(version_file_data)) +def CompileIcons(env, target_dir, source_dir, *, icon_bundle_name="assets_icons"): + # Gathering icons sources + icons_src = env.GlobRecursive("*.png", source_dir) + icons_src += env.GlobRecursive("frame_rate", source_dir) + + icons = env.IconBuilder( + target_dir, + ICON_SRC_DIR=source_dir, + ICON_FILE_NAME=icon_bundle_name, + ) + env.Depends(icons, icons_src) + return icons + + def generate(env): env.SetDefault( ASSETS_COMPILER="${ROOT_DIR.abspath}/scripts/assets.py", NANOPB_COMPILER="${ROOT_DIR.abspath}/lib/nanopb/generator/nanopb_generator.py", ) + env.AddMethod(CompileIcons) + + if not env["VERBOSE"]: + env.SetDefault( + ICONSCOMSTR="\tICONS\t${TARGET}", + PROTOCOMSTR="\tPROTO\t${SOURCE}", + DOLPHINCOMSTR="\tDOLPHIN\t${DOLPHIN_RES_TYPE}", + RESMANIFESTCOMSTR="\tMANIFEST\t${TARGET}", + PBVERCOMSTR="\tPBVER\t${TARGET}", + ) env.Append( BUILDERS={ "IconBuilder": Builder( action=Action( - '${PYTHON3} "${ASSETS_COMPILER}" icons ${ICON_SRC_DIR} ${TARGET.dir}', + '${PYTHON3} "${ASSETS_COMPILER}" icons ${ICON_SRC_DIR} ${TARGET.dir} --filename ${ICON_FILE_NAME}', "${ICONSCOMSTR}", ), emitter=icons_emitter, diff --git a/site_scons/site_tools/fbt_extapps.py b/site_scons/site_tools/fbt_extapps.py index fec2407105d..a1fa771406f 100644 --- a/site_scons/site_tools/fbt_extapps.py +++ b/site_scons/site_tools/fbt_extapps.py @@ -6,56 +6,146 @@ import os import pathlib from fbt.elfmanifest import assemble_manifest_data +from fbt.appmanifest import FlipperManifestException from fbt.sdk import SdkCache import itertools +from site_scons.fbt.appmanifest import FlipperApplication + def BuildAppElf(env, app): - work_dir = env.subst("$EXT_APPS_WORK_DIR") + ext_apps_work_dir = env.subst("$EXT_APPS_WORK_DIR") + app_work_dir = os.path.join(ext_apps_work_dir, app.appid) + + env.VariantDir(app_work_dir, app._appdir, duplicate=False) + + app_env = env.Clone(FAP_SRC_DIR=app._appdir, FAP_WORK_DIR=app_work_dir) + + app_alias = f"fap_{app.appid}" + + # Deprecation stub + legacy_app_taget_name = f"{app_env['FIRMWARE_BUILD_CFG']}_{app.appid}" + + def legacy_app_build_stub(**kw): + raise UserError( + f"Target name '{legacy_app_taget_name}' is deprecated, use '{app_alias}' instead" + ) + + app_env.PhonyTarget(legacy_app_taget_name, Action(legacy_app_build_stub, None)) + + externally_built_files = [] + if app.fap_extbuild: + for external_file_def in app.fap_extbuild: + externally_built_files.append(external_file_def.path) + app_env.Alias(app_alias, external_file_def.path) + app_env.AlwaysBuild( + app_env.Command( + external_file_def.path, + None, + Action( + external_file_def.command, + "" if app_env["VERBOSE"] else "\tEXTCMD\t${TARGET}", + ), + ) + ) + + if app.fap_icon_assets: + app_env.CompileIcons( + app_env.Dir(app_work_dir), + app._appdir.Dir(app.fap_icon_assets), + icon_bundle_name=f"{app.appid}_icons", + ) + + private_libs = [] + + for lib_def in app.fap_private_libs: + lib_src_root_path = os.path.join(app_work_dir, "lib", lib_def.name) + app_env.AppendUnique( + CPPPATH=list( + app_env.Dir(lib_src_root_path).Dir(incpath).srcnode() + for incpath in lib_def.fap_include_paths + ), + ) + + lib_sources = list( + itertools.chain.from_iterable( + app_env.GlobRecursive(source_type, lib_src_root_path) + for source_type in lib_def.sources + ) + ) + if len(lib_sources) == 0: + raise UserError(f"No sources gathered for private library {lib_def}") + + private_lib_env = app_env.Clone() + private_lib_env.AppendUnique( + CCFLAGS=[ + *lib_def.cflags, + ], + CPPDEFINES=lib_def.cdefines, + CPPPATH=list( + os.path.join(app._appdir.path, cinclude) + for cinclude in lib_def.cincludes + ), + ) + + lib = private_lib_env.StaticLibrary( + os.path.join(app_work_dir, lib_def.name), + lib_sources, + ) + private_libs.append(lib) - app_alias = f"{env['FIRMWARE_BUILD_CFG']}_{app.appid}" - app_original_elf = os.path.join(work_dir, f"{app.appid}_d") app_sources = list( itertools.chain.from_iterable( - env.GlobRecursive(source_type, os.path.join(work_dir, app._appdir.relpath)) + app_env.GlobRecursive( + source_type, + app_work_dir, + exclude="lib", + ) for source_type in app.sources ) ) - app_elf_raw = env.Program( - app_original_elf, + + app_env.Append( + LIBS=[*app.fap_libs, *private_libs], + CPPPATH=env.Dir(app_work_dir), + ) + + app_elf_raw = app_env.Program( + os.path.join(app_work_dir, f"{app.appid}_d"), app_sources, APP_ENTRY=app.entry_point, - LIBS=env["LIBS"] + app.fap_libs, ) - app_elf_dump = env.ObjDump(app_elf_raw) - env.Alias(f"{app_alias}_list", app_elf_dump) + app_env.Clean(app_elf_raw, [*externally_built_files, app_env.Dir(app_work_dir)]) - app_elf_augmented = env.EmbedAppMetadata( - os.path.join(env.subst("$PLUGIN_ELF_DIR"), app.appid), + app_elf_dump = app_env.ObjDump(app_elf_raw) + app_env.Alias(f"{app_alias}_list", app_elf_dump) + + app_elf_augmented = app_env.EmbedAppMetadata( + os.path.join(ext_apps_work_dir, app.appid), app_elf_raw, APP=app, ) - manifest_vals = vars(app) manifest_vals = { - k: v for k, v in manifest_vals.items() if k not in ("_appdir", "_apppath") + k: v + for k, v in vars(app).items() + if not k.startswith(FlipperApplication.PRIVATE_FIELD_PREFIX) } - env.Depends( + app_env.Depends( app_elf_augmented, - [env["SDK_DEFINITION"], env.Value(manifest_vals)], + [app_env["SDK_DEFINITION"], app_env.Value(manifest_vals)], ) if app.fap_icon: - env.Depends( + app_env.Depends( app_elf_augmented, - env.File(f"{app._apppath}/{app.fap_icon}"), + app_env.File(f"{app._apppath}/{app.fap_icon}"), ) - env.Alias(app_alias, app_elf_augmented) - app_elf_import_validator = env.ValidateAppImports(app_elf_augmented) - env.AlwaysBuild(app_elf_import_validator) - env.Alias(app_alias, app_elf_import_validator) + app_elf_import_validator = app_env.ValidateAppImports(app_elf_augmented) + app_env.AlwaysBuild(app_elf_import_validator) + app_env.Alias(app_alias, app_elf_import_validator) return (app_elf_augmented, app_elf_raw, app_elf_import_validator) @@ -101,9 +191,15 @@ def GetExtAppFromPath(env, app_dir): appmgr = env["APPMGR"] app = None - for dir_part in reversed(pathlib.Path(app_dir).parts): - if app := appmgr.find_by_appdir(dir_part): - break + try: + # Maybe used passed an appid? + app = appmgr.get(app_dir) + except FlipperManifestException as _: + # Look up path components in known app dits + for dir_part in reversed(pathlib.Path(app_dir).parts): + if app := appmgr.find_by_appdir(dir_part): + break + if not app: raise UserError(f"Failed to resolve application for given APPSRC={app_dir}") @@ -120,7 +216,7 @@ def GetExtAppFromPath(env, app_dir): def generate(env, **kw): env.SetDefault(EXT_APPS_WORK_DIR=kw.get("EXT_APPS_WORK_DIR")) - env.VariantDir(env.subst("$EXT_APPS_WORK_DIR"), env.Dir("#"), duplicate=False) + # env.VariantDir(env.subst("$EXT_APPS_WORK_DIR"), env.Dir("#"), duplicate=False) env.AddMethod(BuildAppElf) env.AddMethod(GetExtAppFromPath) From 61189c3c82cbd397bb9a18619c4ad3bce4d9293d Mon Sep 17 00:00:00 2001 From: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> Date: Thu, 6 Oct 2022 17:18:20 +0300 Subject: [PATCH 104/824] [FL-2847] FFF trailing space fix (#1811) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Improve whitespace handlilng in FFF * Add tests for odd fff user input * Adjust formatting Co-authored-by: あく --- .../flipper_format/flipper_format_test.c | 26 ++++++++ lib/flipper_format/flipper_format_stream.c | 63 +++++++++++-------- 2 files changed, 64 insertions(+), 25 deletions(-) diff --git a/applications/debug/unit_tests/flipper_format/flipper_format_test.c b/applications/debug/unit_tests/flipper_format/flipper_format_test.c index ccd5e751f17..012e905b690 100644 --- a/applications/debug/unit_tests/flipper_format/flipper_format_test.c +++ b/applications/debug/unit_tests/flipper_format/flipper_format_test.c @@ -57,6 +57,23 @@ static const char* test_data_win = "Filetype: Flipper File test\r\n" "Hex data: DE AD BE"; #define READ_TEST_FLP "ff_flp.test" +#define READ_TEST_ODD "ff_oddities.test" +static const char* test_data_odd = "Filetype: Flipper File test\n" + // Tabs before newline + "Version: 666\t\t\n" + "# This is comment\n" + // Windows newline in a UNIX file + "String data: String\r\n" + // Trailing whitespace + "Int32 data: 1234 -6345 7813 0 \n" + // Extra whitespace + "Uint32 data: 1234 0 5678 9098 7654321 \n" + // Mixed whitespace + "Float data: 1.5\t \t1000.0\n" + // Leading tabs after key + "Bool data:\t\ttrue false\n" + // Mixed trailing whitespace + "Hex data: DE AD BE\t "; // data created by user on linux machine static const char* test_file_linux = TEST_DIR READ_TEST_NIX; @@ -64,6 +81,8 @@ static const char* test_file_linux = TEST_DIR READ_TEST_NIX; static const char* test_file_windows = TEST_DIR READ_TEST_WIN; // data created by flipper itself static const char* test_file_flipper = TEST_DIR READ_TEST_FLP; +// data containing odd user input +static const char* test_file_oddities = TEST_DIR READ_TEST_ODD; static bool storage_write_string(const char* path, const char* data) { Storage* storage = furi_record_open(RECORD_STORAGE); @@ -503,6 +522,12 @@ MU_TEST(flipper_format_multikey_test) { mu_assert(test_read_multikey(TEST_DIR "ff_multiline.test"), "Multikey read test error"); } +MU_TEST(flipper_format_oddities_test) { + mu_assert( + storage_write_string(test_file_oddities, test_data_odd), "Write test error [Oddities]"); + mu_assert(test_read(test_file_linux), "Read test error [Oddities]"); +} + MU_TEST_SUITE(flipper_format) { tests_setup(); MU_RUN_TEST(flipper_format_write_test); @@ -516,6 +541,7 @@ MU_TEST_SUITE(flipper_format) { MU_RUN_TEST(flipper_format_update_2_test); MU_RUN_TEST(flipper_format_update_2_result_test); MU_RUN_TEST(flipper_format_multikey_test); + MU_RUN_TEST(flipper_format_oddities_test); tests_teardown(); } diff --git a/lib/flipper_format/flipper_format_stream.c b/lib/flipper_format/flipper_format_stream.c index ecc68d4ede8..41934a3b157 100644 --- a/lib/flipper_format/flipper_format_stream.c +++ b/lib/flipper_format/flipper_format_stream.c @@ -4,6 +4,10 @@ #include "flipper_format_stream.h" #include "flipper_format_stream_i.h" +static inline bool flipper_format_stream_is_space(char c) { + return c == ' ' || c == '\t' || c == flipper_format_eolr; +} + static bool flipper_format_stream_write(Stream* stream, const void* data, size_t data_size) { size_t bytes_written = stream_write(stream, data, data_size); return bytes_written == data_size; @@ -118,55 +122,64 @@ bool flipper_format_stream_seek_to_key(Stream* stream, const char* key, bool str } static bool flipper_format_stream_read_value(Stream* stream, FuriString* value, bool* last) { - furi_string_reset(value); + enum { LeadingSpace, ReadValue, TrailingSpace } state = LeadingSpace; const size_t buffer_size = 32; uint8_t buffer[buffer_size]; bool result = false; bool error = false; + furi_string_reset(value); + while(true) { size_t was_read = stream_read(stream, buffer, buffer_size); if(was_read == 0) { - // check EOF - if(stream_eof(stream) && furi_string_size(value) > 0) { + if(state != LeadingSpace && stream_eof(stream)) { result = true; *last = true; - break; + } else { + error = true; } } for(uint16_t i = 0; i < was_read; i++) { - uint8_t data = buffer[i]; - if(data == flipper_format_eoln) { - if(furi_string_size(value) > 0) { - if(!stream_seek(stream, i - was_read, StreamOffsetFromCurrent)) { - error = true; - break; - } + const uint8_t data = buffer[i]; - result = true; - *last = true; + if(state == LeadingSpace) { + if(flipper_format_stream_is_space(data)) { + continue; + } else if(data == flipper_format_eoln) { + stream_seek(stream, i - was_read, StreamOffsetFromCurrent); + error = true; break; } else { - error = true; + state = ReadValue; + furi_string_push_back(value, data); } - } else if(data == ' ') { - if(furi_string_size(value) > 0) { + } else if(state == ReadValue) { + if(flipper_format_stream_is_space(data)) { + state = TrailingSpace; + } else if(data == flipper_format_eoln) { if(!stream_seek(stream, i - was_read, StreamOffsetFromCurrent)) { error = true; - break; + } else { + result = true; + *last = true; } - - result = true; - *last = false; break; + } else { + furi_string_push_back(value, data); } - - } else if(data == flipper_format_eolr) { - // Ignore - } else { - furi_string_push_back(value, data); + } else if(state == TrailingSpace) { + if(flipper_format_stream_is_space(data)) { + continue; + } else if(!stream_seek(stream, i - was_read, StreamOffsetFromCurrent)) { + error = true; + } else { + *last = (data == flipper_format_eoln); + result = true; + } + break; } } From 061f53cd3c9493bfff34a370d087281fe495ca22 Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Thu, 6 Oct 2022 18:43:17 +0400 Subject: [PATCH 105/824] [FL-2849] SubGhz: read RAW auto generation of names (#1772) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * SubGhz: read RAW auto auto generation of names depending on the date of the entry * SubGhz: name generation modification RAW-YYYYMMDD-HHMMSS * SubGhz: replace m-string with FuriString Co-authored-by: あく --- .../main/subghz/scenes/subghz_scene_save_name.c | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/applications/main/subghz/scenes/subghz_scene_save_name.c b/applications/main/subghz/scenes/subghz_scene_save_name.c index b9bb3636d75..dfcb65865ba 100644 --- a/applications/main/subghz/scenes/subghz_scene_save_name.c +++ b/applications/main/subghz/scenes/subghz_scene_save_name.c @@ -13,6 +13,20 @@ void subghz_scene_save_name_text_input_callback(void* context) { view_dispatcher_send_custom_event(subghz->view_dispatcher, SubGhzCustomEventSceneSaveName); } +void subghz_scene_save_name_get_timefilename(FuriString* name) { + FuriHalRtcDateTime datetime = {0}; + furi_hal_rtc_get_datetime(&datetime); + furi_string_printf( + name, + "RAW-%.4d%.2d%.2d-%.2d%.2d%.2d", + datetime.year, + datetime.month, + datetime.day, + datetime.hour, + datetime.minute, + datetime.second); +} + void subghz_scene_save_name_on_enter(void* context) { SubGhz* subghz = context; @@ -41,9 +55,8 @@ void subghz_scene_save_name_on_enter(void* context) { if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAW) == SubGhzCustomEventManagerSetRAW) { dev_name_empty = true; - subghz_get_next_name_file(subghz, SUBGHZ_MAX_LEN_NAME); + subghz_scene_save_name_get_timefilename(file_name); } - path_extract_filename(subghz->file_path, file_name, true); } furi_string_set(subghz->file_path, dir_name); } From 11681d8ee8b62f74401164d002c81951b14db82a Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Thu, 6 Oct 2022 18:48:29 +0400 Subject: [PATCH 106/824] [FL-2866, FL-2865] SubGhz: add frequency analyzer history (#1810) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * SubGhz: frequency analyzer history * SubGhz: add vibro * SubGhz: turn on the display when receiving a signal * SubGhz: add signal reception indicator * SubGhz: fix indicator * SubGhz: fix FA history Co-authored-by: あく --- .../subghz_frequency_analyzer_worker.c | 24 ++++++- .../subghz_frequency_analyzer_worker.h | 7 ++- .../scenes/subghz_scene_frequency_analyzer.c | 2 + .../subghz/views/subghz_frequency_analyzer.c | 63 +++++++++++++++++-- 4 files changed, 85 insertions(+), 11 deletions(-) diff --git a/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.c b/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.c index 10c5a9ea81a..35abbddc190 100644 --- a/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.c +++ b/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.c @@ -71,6 +71,8 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) { .frequency_coarse = 0, .rssi_coarse = 0, .frequency_fine = 0, .rssi_fine = 0}; float rssi = 0; uint32_t frequency = 0; + float rssi_temp = 0; + uint32_t frequency_temp = 0; CC1101Status status; //Start CC1101 @@ -194,6 +196,9 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) { TAG, "=:%u:%f", frequency_rssi.frequency_fine, (double)frequency_rssi.rssi_fine); instance->sample_hold_counter = 20; + rssi_temp = frequency_rssi.rssi_fine; + frequency_temp = frequency_rssi.frequency_fine; + if(instance->filVal) { frequency_rssi.frequency_fine = subghz_frequency_analyzer_worker_expRunningAverageAdaptive( @@ -202,7 +207,10 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) { // Deliver callback if(instance->pair_callback) { instance->pair_callback( - instance->context, frequency_rssi.frequency_fine, frequency_rssi.rssi_fine); + instance->context, + frequency_rssi.frequency_fine, + frequency_rssi.rssi_fine, + true); } } else if( // Deliver results coarse (frequency_rssi.rssi_coarse > SUBGHZ_FREQUENCY_ANALYZER_THRESHOLD) && @@ -214,6 +222,8 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) { (double)frequency_rssi.rssi_coarse); instance->sample_hold_counter = 20; + rssi_temp = frequency_rssi.rssi_coarse; + frequency_temp = frequency_rssi.frequency_coarse; if(instance->filVal) { frequency_rssi.frequency_coarse = subghz_frequency_analyzer_worker_expRunningAverageAdaptive( @@ -224,14 +234,22 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) { instance->pair_callback( instance->context, frequency_rssi.frequency_coarse, - frequency_rssi.rssi_coarse); + frequency_rssi.rssi_coarse, + true); } } else { if(instance->sample_hold_counter > 0) { instance->sample_hold_counter--; + if(instance->sample_hold_counter == 18) { + if(instance->pair_callback) { + instance->pair_callback( + instance->context, frequency_temp, rssi_temp, false); + } + } } else { instance->filVal = 0; - if(instance->pair_callback) instance->pair_callback(instance->context, 0, 0); + if(instance->pair_callback) + instance->pair_callback(instance->context, 0, 0, false); } } } diff --git a/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.h b/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.h index 50687c76da2..8bd1addc41e 100644 --- a/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.h +++ b/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.h @@ -5,8 +5,11 @@ typedef struct SubGhzFrequencyAnalyzerWorker SubGhzFrequencyAnalyzerWorker; -typedef void ( - *SubGhzFrequencyAnalyzerWorkerPairCallback)(void* context, uint32_t frequency, float rssi); +typedef void (*SubGhzFrequencyAnalyzerWorkerPairCallback)( + void* context, + uint32_t frequency, + float rssi, + bool signal); typedef struct { uint32_t frequency_coarse; diff --git a/applications/main/subghz/scenes/subghz_scene_frequency_analyzer.c b/applications/main/subghz/scenes/subghz_scene_frequency_analyzer.c index b42acd4d2ca..9595c61bef4 100644 --- a/applications/main/subghz/scenes/subghz_scene_frequency_analyzer.c +++ b/applications/main/subghz/scenes/subghz_scene_frequency_analyzer.c @@ -21,6 +21,8 @@ bool subghz_scene_frequency_analyzer_on_event(void* context, SceneManagerEvent e if(event.type == SceneManagerEventTypeCustom) { if(event.event == SubGhzCustomEventSceneAnalyzerLock) { notification_message(subghz->notifications, &sequence_set_green_255); + notification_message(subghz->notifications, &sequence_single_vibro); + notification_message(subghz->notifications, &sequence_display_backlight_on); return true; } else if(event.event == SubGhzCustomEventSceneAnalyzerUnlock) { notification_message(subghz->notifications, &sequence_reset_rgb); diff --git a/applications/main/subghz/views/subghz_frequency_analyzer.c b/applications/main/subghz/views/subghz_frequency_analyzer.c index d3f773159eb..608cf5862aa 100644 --- a/applications/main/subghz/views/subghz_frequency_analyzer.c +++ b/applications/main/subghz/views/subghz_frequency_analyzer.c @@ -25,6 +25,8 @@ struct SubGhzFrequencyAnalyzer { typedef struct { uint32_t frequency; float rssi; + uint32_t history_frequency[3]; + bool signal; } SubGhzFrequencyAnalyzerModel; void subghz_frequency_analyzer_set_callback( @@ -38,8 +40,8 @@ void subghz_frequency_analyzer_set_callback( } void subghz_frequency_analyzer_draw_rssi(Canvas* canvas, float rssi) { - uint8_t x = 48; - uint8_t y = 56; + uint8_t x = 20; + uint8_t y = 64; uint8_t column_number = 0; if(rssi) { rssi = (rssi + 90) / 3; @@ -53,6 +55,31 @@ void subghz_frequency_analyzer_draw_rssi(Canvas* canvas, float rssi) { } } +static void subghz_frequency_analyzer_history_frequency_draw( + Canvas* canvas, + SubGhzFrequencyAnalyzerModel* model) { + char buffer[64]; + uint8_t x = 66; + uint8_t y = 43; + + canvas_set_font(canvas, FontKeyboard); + for(uint8_t i = 0; i < 3; i++) { + if(model->history_frequency[i]) { + snprintf( + buffer, + sizeof(buffer), + "%03ld.%03ld", + model->history_frequency[i] / 1000000 % 1000, + model->history_frequency[i] / 1000 % 1000); + canvas_draw_str(canvas, x, y + i * 10, buffer); + } else { + canvas_draw_str(canvas, x, y + i * 10, "---.---"); + } + canvas_draw_str(canvas, x + 44, y + i * 10, "MHz"); + } + canvas_set_font(canvas, FontSecondary); +} + void subghz_frequency_analyzer_draw(Canvas* canvas, SubGhzFrequencyAnalyzerModel* model) { char buffer[64]; @@ -60,9 +87,11 @@ void subghz_frequency_analyzer_draw(Canvas* canvas, SubGhzFrequencyAnalyzerModel canvas_set_font(canvas, FontSecondary); canvas_draw_str(canvas, 20, 8, "Frequency Analyzer"); - canvas_draw_str(canvas, 28, 60, "RSSI"); + canvas_draw_str(canvas, 0, 64, "RSSI"); subghz_frequency_analyzer_draw_rssi(canvas, model->rssi); + subghz_frequency_analyzer_history_frequency_draw(canvas, model); + //Frequency canvas_set_font(canvas, FontBigNumbers); snprintf( @@ -71,8 +100,14 @@ void subghz_frequency_analyzer_draw(Canvas* canvas, SubGhzFrequencyAnalyzerModel "%03ld.%03ld", model->frequency / 1000000 % 1000, model->frequency / 1000 % 1000); - canvas_draw_str(canvas, 8, 35, buffer); - canvas_draw_icon(canvas, 96, 24, &I_MHz_25x11); + if(model->signal) { + canvas_draw_box(canvas, 4, 12, 121, 22); + canvas_set_color(canvas, ColorWhite); + } else { + } + + canvas_draw_str(canvas, 8, 30, buffer); + canvas_draw_icon(canvas, 96, 19, &I_MHz_25x11); } bool subghz_frequency_analyzer_input(InputEvent* event, void* context) { @@ -85,12 +120,24 @@ bool subghz_frequency_analyzer_input(InputEvent* event, void* context) { return true; } -void subghz_frequency_analyzer_pair_callback(void* context, uint32_t frequency, float rssi) { +void subghz_frequency_analyzer_pair_callback( + void* context, + uint32_t frequency, + float rssi, + bool signal) { SubGhzFrequencyAnalyzer* instance = context; if((rssi == 0.f) && (instance->locked)) { if(instance->callback) { instance->callback(SubGhzCustomEventSceneAnalyzerUnlock, instance->context); } + //update history + with_view_model( + instance->view, (SubGhzFrequencyAnalyzerModel * model) { + model->history_frequency[2] = model->history_frequency[1]; + model->history_frequency[1] = model->history_frequency[0]; + model->history_frequency[0] = model->frequency; + return false; + }); } else if((rssi != 0.f) && (!instance->locked)) { if(instance->callback) { instance->callback(SubGhzCustomEventSceneAnalyzerLock, instance->context); @@ -102,6 +149,7 @@ void subghz_frequency_analyzer_pair_callback(void* context, uint32_t frequency, instance->view, (SubGhzFrequencyAnalyzerModel * model) { model->rssi = rssi; model->frequency = frequency; + model->signal = signal; return true; }); } @@ -124,6 +172,9 @@ void subghz_frequency_analyzer_enter(void* context) { instance->view, (SubGhzFrequencyAnalyzerModel * model) { model->rssi = 0; model->frequency = 0; + model->history_frequency[2] = 0; + model->history_frequency[1] = 0; + model->history_frequency[0] = 0; return true; }); } From d07c2dbe548a52cef6f76e513068da8657ef1bfc Mon Sep 17 00:00:00 2001 From: Max Andreev Date: Thu, 6 Oct 2022 20:37:53 +0500 Subject: [PATCH 107/824] ".fap" extention in file browser and archive tab (#1812) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add .fap extention, and Applications tab * Using new icon, renaming tab to Apps * Change tabs order * Add first ugly implementation of in-app icons in archive browser * Starting using FAPLoader callback * Getting all metafata from fap * add app filename fallback * using fap_loader_item_callback in archive_list_item_cb * FAP-Loader: removed minimal allocation * Removed strange code Co-authored-by: SG Co-authored-by: あく --- .../main/archive/helpers/archive_browser.c | 23 ++++++- .../main/archive/helpers/archive_browser.h | 3 + .../main/archive/helpers/archive_files.h | 38 +++++++++-- .../archive/scenes/archive_scene_browser.c | 1 + .../main/archive/views/archive_browser_view.c | 23 ++++++- .../main/archive/views/archive_browser_view.h | 1 + applications/main/fap_loader/fap_loader_app.c | 68 +++++++++++-------- applications/main/fap_loader/fap_loader_app.h | 27 ++++++++ 8 files changed, 145 insertions(+), 39 deletions(-) create mode 100644 applications/main/fap_loader/fap_loader_app.h diff --git a/applications/main/archive/helpers/archive_browser.c b/applications/main/archive/helpers/archive_browser.c index 1f4ca0f794a..f66030706f4 100644 --- a/applications/main/archive/helpers/archive_browser.c +++ b/applications/main/archive/helpers/archive_browser.c @@ -5,6 +5,7 @@ #include #include #include "gui/modules/file_browser_worker.h" +#include #include static void @@ -351,16 +352,32 @@ void archive_add_app_item(ArchiveBrowserView* browser, const char* name) { ArchiveFile_t_clear(&item); } +static bool archive_get_fap_meta(FuriString* file_path, FuriString* fap_name, uint8_t** icon_ptr) { + Storage* storage = furi_record_open(RECORD_STORAGE); + bool success = false; + if(fap_loader_load_name_and_icon(file_path, storage, icon_ptr, fap_name)) { + success = true; + } + furi_record_close(RECORD_STORAGE); + return success; +} + void archive_add_file_item(ArchiveBrowserView* browser, bool is_folder, const char* name) { furi_assert(browser); furi_assert(name); ArchiveFile_t item; - ArchiveFile_t_init(&item); - item.path = furi_string_alloc_set(name); - archive_set_file_type(&item, furi_string_get_cstr(browser->path), is_folder, false); + furi_string_set(item.path, name); + archive_set_file_type(&item, furi_string_get_cstr(browser->path), is_folder, false); + if(item.type == ArchiveFileTypeApplication) { + item.custom_icon_data = malloc(FAP_MANIFEST_MAX_ICON_SIZE); + if(!archive_get_fap_meta(item.path, item.custom_name, &item.custom_icon_data)) { + free(item.custom_icon_data); + item.custom_icon_data = NULL; + } + } with_view_model( browser->view, (ArchiveBrowserViewModel * model) { files_array_push_back(model->files, item); diff --git a/applications/main/archive/helpers/archive_browser.h b/applications/main/archive/helpers/archive_browser.h index c9e3bfa3083..519a34a2d0f 100644 --- a/applications/main/archive/helpers/archive_browser.h +++ b/applications/main/archive/helpers/archive_browser.h @@ -16,6 +16,7 @@ static const char* tab_default_paths[] = { [ArchiveTabInfrared] = ANY_PATH("infrared"), [ArchiveTabBadUsb] = ANY_PATH("badusb"), [ArchiveTabU2f] = "/app:u2f", + [ArchiveTabApplications] = ANY_PATH("apps"), [ArchiveTabBrowser] = STORAGE_ANY_PATH_PREFIX, }; @@ -27,6 +28,7 @@ static const char* known_ext[] = { [ArchiveFileTypeInfrared] = ".ir", [ArchiveFileTypeBadUsb] = ".txt", [ArchiveFileTypeU2f] = "?", + [ArchiveFileTypeApplication] = ".fap", [ArchiveFileTypeUpdateManifest] = ".fuf", [ArchiveFileTypeFolder] = "?", [ArchiveFileTypeUnknown] = "*", @@ -41,6 +43,7 @@ static const ArchiveFileTypeEnum known_type[] = { [ArchiveTabInfrared] = ArchiveFileTypeInfrared, [ArchiveTabBadUsb] = ArchiveFileTypeBadUsb, [ArchiveTabU2f] = ArchiveFileTypeU2f, + [ArchiveTabApplications] = ArchiveFileTypeApplication, [ArchiveTabBrowser] = ArchiveFileTypeUnknown, }; diff --git a/applications/main/archive/helpers/archive_files.h b/applications/main/archive/helpers/archive_files.h index a50a1d5304a..2017a9574f9 100644 --- a/applications/main/archive/helpers/archive_files.h +++ b/applications/main/archive/helpers/archive_files.h @@ -4,6 +4,8 @@ #include #include +#define FAP_MANIFEST_MAX_ICON_SIZE 32 + typedef enum { ArchiveFileTypeIButton, ArchiveFileTypeNFC, @@ -13,6 +15,7 @@ typedef enum { ArchiveFileTypeBadUsb, ArchiveFileTypeU2f, ArchiveFileTypeUpdateManifest, + ArchiveFileTypeApplication, ArchiveFileTypeFolder, ArchiveFileTypeUnknown, ArchiveFileTypeLoading, @@ -21,33 +24,56 @@ typedef enum { typedef struct { FuriString* path; ArchiveFileTypeEnum type; + uint8_t* custom_icon_data; + FuriString* custom_name; bool fav; bool is_app; } ArchiveFile_t; static void ArchiveFile_t_init(ArchiveFile_t* obj) { + obj->path = furi_string_alloc(); obj->type = ArchiveFileTypeUnknown; - obj->is_app = false; + obj->custom_icon_data = NULL; + obj->custom_name = furi_string_alloc(); obj->fav = false; - obj->path = furi_string_alloc(); + obj->is_app = false; } static void ArchiveFile_t_init_set(ArchiveFile_t* obj, const ArchiveFile_t* src) { + obj->path = furi_string_alloc_set(src->path); obj->type = src->type; - obj->is_app = src->is_app; + if(src->custom_icon_data) { + obj->custom_icon_data = malloc(FAP_MANIFEST_MAX_ICON_SIZE); + memcpy(obj->custom_icon_data, src->custom_icon_data, FAP_MANIFEST_MAX_ICON_SIZE); + } else { + obj->custom_icon_data = NULL; + } + obj->custom_name = furi_string_alloc_set(src->custom_name); obj->fav = src->fav; - obj->path = furi_string_alloc_set(src->path); + obj->is_app = src->is_app; } static void ArchiveFile_t_set(ArchiveFile_t* obj, const ArchiveFile_t* src) { + furi_string_set(obj->path, src->path); obj->type = src->type; - obj->is_app = src->is_app; + if(src->custom_icon_data) { + obj->custom_icon_data = malloc(FAP_MANIFEST_MAX_ICON_SIZE); + memcpy(obj->custom_icon_data, src->custom_icon_data, FAP_MANIFEST_MAX_ICON_SIZE); + } else { + obj->custom_icon_data = NULL; + } + furi_string_set(obj->custom_name, src->custom_name); obj->fav = src->fav; - furi_string_set(obj->path, src->path); + obj->is_app = src->is_app; } static void ArchiveFile_t_clear(ArchiveFile_t* obj) { furi_string_free(obj->path); + if(obj->custom_icon_data) { + free(obj->custom_icon_data); + obj->custom_icon_data = NULL; + } + furi_string_free(obj->custom_name); } ARRAY_DEF( diff --git a/applications/main/archive/scenes/archive_scene_browser.c b/applications/main/archive/scenes/archive_scene_browser.c index b4fd0482342..9dc671617ed 100644 --- a/applications/main/archive/scenes/archive_scene_browser.c +++ b/applications/main/archive/scenes/archive_scene_browser.c @@ -20,6 +20,7 @@ static const char* flipper_app_name[] = { [ArchiveFileTypeBadUsb] = "Bad USB", [ArchiveFileTypeU2f] = "U2F", [ArchiveFileTypeUpdateManifest] = "UpdaterApp", + [ArchiveFileTypeApplication] = "Applications", }; static void archive_loader_callback(const void* message, void* context) { diff --git a/applications/main/archive/views/archive_browser_view.c b/applications/main/archive/views/archive_browser_view.c index 4a0f2f3cb48..f13094a1ce4 100644 --- a/applications/main/archive/views/archive_browser_view.c +++ b/applications/main/archive/views/archive_browser_view.c @@ -14,6 +14,7 @@ static const char* ArchiveTabNames[] = { [ArchiveTabInfrared] = "Infrared", [ArchiveTabBadUsb] = "Bad USB", [ArchiveTabU2f] = "U2F", + [ArchiveTabApplications] = "Apps", [ArchiveTabBrowser] = "Browser", }; @@ -29,6 +30,7 @@ static const Icon* ArchiveItemIcons[] = { [ArchiveFileTypeFolder] = &I_dir_10px, [ArchiveFileTypeUnknown] = &I_unknown_10px, [ArchiveFileTypeLoading] = &I_loading_10px, + [ArchiveFileTypeApplication] = &I_unknown_10px, }; void archive_browser_set_callback( @@ -124,12 +126,23 @@ static void draw_list(Canvas* canvas, ArchiveBrowserViewModel* model) { uint8_t x_offset = (model->move_fav && model->item_idx == idx) ? MOVE_OFFSET : 0; ArchiveFileTypeEnum file_type = ArchiveFileTypeLoading; + uint8_t* custom_icon_data = NULL; if(archive_is_item_in_array(model, idx)) { ArchiveFile_t* file = files_array_get( model->files, CLAMP(idx - model->array_offset, (int32_t)(array_size - 1), 0)); - path_extract_filename(file->path, str_buf, archive_is_known_app(file->type)); file_type = file->type; + if(file_type == ArchiveFileTypeApplication) { + if(file->custom_icon_data) { + custom_icon_data = file->custom_icon_data; + furi_string_set(str_buf, file->custom_name); + } else { + file_type = ArchiveFileTypeUnknown; + path_extract_filename(file->path, str_buf, archive_is_known_app(file->type)); + } + } else { + path_extract_filename(file->path, str_buf, archive_is_known_app(file->type)); + } } else { furi_string_set(str_buf, "---"); } @@ -143,7 +156,13 @@ static void draw_list(Canvas* canvas, ArchiveBrowserViewModel* model) { canvas_set_color(canvas, ColorBlack); } - canvas_draw_icon(canvas, 2 + x_offset, 16 + i * FRAME_HEIGHT, ArchiveItemIcons[file_type]); + if(custom_icon_data) { + canvas_draw_bitmap( + canvas, 2 + x_offset, 16 + i * FRAME_HEIGHT, 11, 10, custom_icon_data); + } else { + canvas_draw_icon( + canvas, 2 + x_offset, 16 + i * FRAME_HEIGHT, ArchiveItemIcons[file_type]); + } canvas_draw_str( canvas, 15 + x_offset, 24 + i * FRAME_HEIGHT, furi_string_get_cstr(str_buf)); diff --git a/applications/main/archive/views/archive_browser_view.h b/applications/main/archive/views/archive_browser_view.h index 447cb0e1c0a..308af4e4d9d 100644 --- a/applications/main/archive/views/archive_browser_view.h +++ b/applications/main/archive/views/archive_browser_view.h @@ -26,6 +26,7 @@ typedef enum { ArchiveTabIButton, ArchiveTabBadUsb, ArchiveTabU2f, + ArchiveTabApplications, ArchiveTabBrowser, ArchiveTabTotal, } ArchiveTabEnum; diff --git a/applications/main/fap_loader/fap_loader_app.c b/applications/main/fap_loader/fap_loader_app.c index 1486cc1a105..fb0a300e790 100644 --- a/applications/main/fap_loader/fap_loader_app.c +++ b/applications/main/fap_loader/fap_loader_app.c @@ -1,34 +1,31 @@ #include #include #include -#include #include +#include #include -#include "elf_cpp/elf_hashtable.h" #include +#include "elf_cpp/elf_hashtable.h" +#include "fap_loader_app.h" #define TAG "fap_loader_app" -typedef struct { +struct FapLoader { FlipperApplication* app; Storage* storage; DialogsApp* dialogs; Gui* gui; FuriString* fap_path; - ViewDispatcher* view_dispatcher; Loading* loading; -} FapLoader; +}; -static bool fap_loader_item_callback( +bool fap_loader_load_name_and_icon( FuriString* path, - void* context, + Storage* storage, uint8_t** icon_ptr, FuriString* item_name) { - FapLoader* loader = context; - furi_assert(loader); - - FlipperApplication* app = flipper_application_alloc(loader->storage, &hashtable_api_interface); + FlipperApplication* app = flipper_application_alloc(storage, &hashtable_api_interface); FlipperApplicationPreloadStatus preload_res = flipper_application_preload_manifest(app, furi_string_get_cstr(path)); @@ -51,6 +48,16 @@ static bool fap_loader_item_callback( return load_success; } +static bool fap_loader_item_callback( + FuriString* path, + void* context, + uint8_t** icon_ptr, + FuriString* item_name) { + FapLoader* fap_loader = context; + furi_assert(fap_loader); + return fap_loader_load_name_and_icon(path, fap_loader->storage, icon_ptr, item_name); +} + static bool fap_loader_run_selected_app(FapLoader* loader) { furi_assert(loader); @@ -134,7 +141,7 @@ static bool fap_loader_select_app(FapLoader* loader) { const DialogsFileBrowserOptions browser_options = { .extension = ".fap", .skip_assets = true, - .icon = &I_badusb_10px, + .icon = &I_unknown_10px, .hide_ext = true, .item_loader_callback = fap_loader_item_callback, .item_loader_context = loader, @@ -144,39 +151,44 @@ static bool fap_loader_select_app(FapLoader* loader) { loader->dialogs, loader->fap_path, loader->fap_path, &browser_options); } -int32_t fap_loader_app(void* p) { +static FapLoader* fap_loader_alloc(const char* path) { FapLoader* loader = malloc(sizeof(FapLoader)); + loader->fap_path = furi_string_alloc_set(path); loader->storage = furi_record_open(RECORD_STORAGE); loader->dialogs = furi_record_open(RECORD_DIALOGS); loader->gui = furi_record_open(RECORD_GUI); - loader->view_dispatcher = view_dispatcher_alloc(); loader->loading = loading_alloc(); - view_dispatcher_attach_to_gui( loader->view_dispatcher, loader->gui, ViewDispatcherTypeFullscreen); view_dispatcher_add_view(loader->view_dispatcher, 0, loading_get_view(loader->loading)); + return loader; +} + +static void fap_loader_free(FapLoader* loader) { + view_dispatcher_remove_view(loader->view_dispatcher, 0); + loading_free(loader->loading); + view_dispatcher_free(loader->view_dispatcher); + furi_string_free(loader->fap_path); + furi_record_close(RECORD_GUI); + furi_record_close(RECORD_DIALOGS); + furi_record_close(RECORD_STORAGE); + free(loader); +} +int32_t fap_loader_app(void* p) { + FapLoader* loader; if(p) { - loader->fap_path = furi_string_alloc_set((const char*)p); + loader = fap_loader_alloc((const char*)p); fap_loader_run_selected_app(loader); } else { - loader->fap_path = furi_string_alloc_set(EXT_PATH("apps")); - + loader = fap_loader_alloc(EXT_PATH("apps")); while(fap_loader_select_app(loader)) { view_dispatcher_switch_to_view(loader->view_dispatcher, 0); fap_loader_run_selected_app(loader); }; } - view_dispatcher_remove_view(loader->view_dispatcher, 0); - loading_free(loader->loading); - view_dispatcher_free(loader->view_dispatcher); - - furi_string_free(loader->fap_path); - furi_record_close(RECORD_GUI); - furi_record_close(RECORD_DIALOGS); - furi_record_close(RECORD_STORAGE); - free(loader); + fap_loader_free(loader); return 0; -} \ No newline at end of file +} diff --git a/applications/main/fap_loader/fap_loader_app.h b/applications/main/fap_loader/fap_loader_app.h new file mode 100644 index 00000000000..9ed725efe52 --- /dev/null +++ b/applications/main/fap_loader/fap_loader_app.h @@ -0,0 +1,27 @@ +#pragma once +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct FapLoader FapLoader; + +/** + * @brief Load name and icon from FAP file. + * + * @param path Path to FAP file. + * @param storage Storage instance. + * @param icon_ptr Icon pointer. + * @param item_name Application name. + * @return true if icon and name were loaded successfully. + */ +bool fap_loader_load_name_and_icon( + FuriString* path, + Storage* storage, + uint8_t** icon_ptr, + FuriString* item_name); + +#ifdef __cplusplus +} +#endif \ No newline at end of file From e3a5df5959e401da7fcf3916f0d71ca5105df616 Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Fri, 7 Oct 2022 02:13:02 +1000 Subject: [PATCH 108/824] CLI: log command argument (#1817) Co-authored-by: Aleksandr Kutuzov --- applications/services/cli/cli_commands.c | 33 +++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/applications/services/cli/cli_commands.c b/applications/services/cli/cli_commands.c index 21927f81f28..cee2ca98cc9 100644 --- a/applications/services/cli/cli_commands.c +++ b/applications/services/cli/cli_commands.c @@ -143,11 +143,37 @@ void cli_command_log_tx_callback(const uint8_t* buffer, size_t size, void* conte xStreamBufferSend(context, buffer, size, 0); } +void cli_command_log_level_set_from_string(FuriString* level) { + if(furi_string_cmpi_str(level, "default") == 0) { + furi_log_set_level(FuriLogLevelDefault); + } else if(furi_string_cmpi_str(level, "none") == 0) { + furi_log_set_level(FuriLogLevelNone); + } else if(furi_string_cmpi_str(level, "error") == 0) { + furi_log_set_level(FuriLogLevelError); + } else if(furi_string_cmpi_str(level, "warn") == 0) { + furi_log_set_level(FuriLogLevelWarn); + } else if(furi_string_cmpi_str(level, "info") == 0) { + furi_log_set_level(FuriLogLevelInfo); + } else if(furi_string_cmpi_str(level, "debug") == 0) { + furi_log_set_level(FuriLogLevelDebug); + } else if(furi_string_cmpi_str(level, "trace") == 0) { + furi_log_set_level(FuriLogLevelTrace); + } else { + printf("Unknown log level\r\n"); + } +} + void cli_command_log(Cli* cli, FuriString* args, void* context) { - UNUSED(args); UNUSED(context); StreamBufferHandle_t ring = xStreamBufferCreate(CLI_COMMAND_LOG_RING_SIZE, 1); uint8_t buffer[CLI_COMMAND_LOG_BUFFER_SIZE]; + FuriLogLevel previous_level = furi_log_get_level(); + bool restore_log_level = false; + + if(furi_string_size(args) > 0) { + cli_command_log_level_set_from_string(args); + restore_log_level = true; + } furi_hal_console_set_tx_callback(cli_command_log_tx_callback, ring); @@ -159,6 +185,11 @@ void cli_command_log(Cli* cli, FuriString* args, void* context) { furi_hal_console_set_tx_callback(NULL, NULL); + if(restore_log_level) { + // There will be strange behaviour if log level is set from settings while log command is running + furi_log_set_level(previous_level); + } + vStreamBufferDelete(ring); } From 69b9c54b2f17856d105520dc7028d73b0c546b14 Mon Sep 17 00:00:00 2001 From: Evgenii Tereshkov Date: Thu, 6 Oct 2022 23:18:23 +0700 Subject: [PATCH 109/824] Update ac.ir: add Daichi model DA25AVQS1-W (#1819) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- assets/resources/infrared/assets/ac.ir | 38 ++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/assets/resources/infrared/assets/ac.ir b/assets/resources/infrared/assets/ac.ir index 7866febc693..97c38459167 100644 --- a/assets/resources/infrared/assets/ac.ir +++ b/assets/resources/infrared/assets/ac.ir @@ -74,3 +74,41 @@ type: raw frequency: 38000 duty_cycle: 0.330000 data: 8972 4491 592 1651 592 1655 598 532 599 535 597 542 600 541 601 544 598 1656 597 526 595 1652 591 1658 595 539 593 545 597 545 597 546 596 537 594 529 592 535 596 1653 600 534 597 541 601 539 592 552 600 533 598 525 596 530 591 538 593 539 592 1665 598 1662 591 1673 601 533 598 526 595 533 598 532 600 534 597 540 591 548 594 550 592 542 600 523 598 528 593 536 595 537 594 543 599 542 600 543 599 517 594 7937 593 531 601 526 595 535 597 537 594 542 600 541 601 543 599 1654 599 523 598 528 593 536 596 538 594 542 600 541 590 552 600 532 599 524 597 528 593 536 595 537 595 541 601 539 593 551 591 542 600 522 599 527 594 536 595 537 594 543 599 540 591 552 600 532 600 523 598 527 594 535 596 537 595 542 600 540 591 552 600 532 600 523 598 528 593 536 595 538 593 543 599 541 601 543 599 535 596 527 594 532 600 531 601 534 597 540 592 549 593 552 600 534 597 525 596 529 592 1655 598 534 597 1656 597 1661 592 1671 592 1644 599 7934 596 529 592 535 597 535 597 538 593 544 598 543 599 545 597 538 593 1650 593 535 596 534 597 536 595 540 591 547 595 547 595 536 595 526 595 529 592 536 595 535 596 539 593 546 596 547 595 538 593 528 593 531 601 529 592 541 601 536 596 545 597 548 594 540 592 532 600 526 595 535 596 1656 597 541 601 540 592 553 599 534 597 526 595 532 599 531 600 533 598 539 593 548 594 552 600 535 596 1647 596 531 590 538 593 1656 597 538 594 545 597 545 597 518 593 +# +# Model: Daichi DA25AVQS1-W +name: Dh +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9131 4318 815 389 817 1523 784 391 815 1523 784 1494 813 422 784 391 816 421 785 1524 784 392 814 422 783 1525 782 424 782 422 784 422 784 422 784 396 809 423 783 422 782 423 783 423 783 1496 812 1525 782 424 783 423 783 422 783 423 783 394 811 1526 782 424 782 1524 782 423 783 423 782 1525 782 423 675 19938 810 425 781 424 674 502 811 423 675 531 675 531 675 531 676 530 675 531 675 531 675 502 704 530 675 530 676 530 676 502 704 530 676 530 676 530 675 530 675 530 676 530 675 530 676 530 676 529 677 529 677 529 677 530 677 529 677 1631 676 529 677 1630 678 1631 677 +# +name: Cool_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9076 4455 677 1629 678 528 677 528 677 1630 677 529 677 530 676 530 676 531 675 531 675 531 675 531 675 531 674 531 675 532 674 531 674 532 674 531 675 532 674 531 674 531 674 532 674 1633 674 1633 674 532 674 532 674 532 674 532 674 532 674 1634 673 533 673 1634 673 533 674 532 674 1633 674 533 673 19965 674 531 674 532 675 531 675 531 675 531 675 531 675 531 675 531 675 532 674 531 675 531 676 531 674 531 675 531 675 531 674 531 675 531 675 532 674 532 674 531 675 532 674 532 674 531 675 531 675 532 674 532 674 532 674 532 674 1633 674 1633 674 532 674 532 674 +# +name: Cool_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9076 4453 679 1628 703 503 703 503 702 1607 700 506 699 507 699 507 699 507 699 1609 698 1610 698 1609 699 507 699 508 698 508 698 508 698 508 698 508 698 507 699 508 698 508 698 508 697 1609 698 1610 698 508 698 508 698 508 698 508 698 509 697 1610 697 508 698 1610 698 508 698 508 698 1610 697 509 697 19941 699 507 699 507 699 507 699 508 698 508 698 508 698 508 698 508 697 508 698 508 698 507 699 508 698 508 698 508 698 508 698 508 698 508 698 508 697 509 697 509 697 508 698 508 698 508 698 508 698 509 697 508 697 509 697 509 696 509 697 1635 673 533 673 1611 696 +# +name: Heat_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 148 110377 9082 4422 707 499 706 500 704 1603 703 1606 701 505 700 507 699 506 700 507 699 506 700 1607 700 1608 700 1609 699 506 699 507 699 506 699 507 699 507 699 506 700 507 699 507 699 507 699 1608 699 1607 699 507 699 507 699 507 699 507 699 507 699 1609 699 507 699 1608 699 507 699 507 699 1609 699 507 699 19940 700 506 700 506 699 507 699 507 699 506 700 506 700 507 699 507 699 507 700 507 699 506 699 507 699 507 699 507 699 507 699 507 699 507 699 507 699 507 698 507 700 507 699 507 699 507 699 507 699 507 699 508 698 508 698 507 699 507 699 508 698 1610 699 508 698 +# +name: Heat_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9081 4423 707 499 706 500 704 1604 702 1606 701 505 701 506 700 506 700 506 700 1608 700 1607 700 1609 699 506 700 506 700 506 700 506 700 506 700 507 699 506 700 506 700 507 699 507 699 1608 700 1608 699 507 699 507 699 507 699 507 699 507 699 1608 699 507 698 1609 698 507 699 507 699 1609 699 507 699 19938 699 506 700 506 700 507 699 506 699 506 700 506 699 506 700 507 699 507 699 507 700 506 699 507 699 507 699 507 699 507 699 507 698 507 699 507 699 507 699 507 699 507 699 507 699 507 699 507 700 507 699 507 698 507 699 507 699 1609 699 507 698 1609 699 1609 699 +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9106 4398 731 499 706 500 705 502 702 504 701 505 701 505 701 1606 701 505 701 1607 701 505 701 506 700 1607 700 506 700 506 700 505 700 505 701 506 700 506 700 506 699 506 700 506 700 1607 700 506 700 506 700 506 700 505 701 506 700 506 700 1608 699 506 700 1608 699 506 700 506 700 1608 700 506 700 19941 701 1606 700 505 701 505 701 506 700 505 700 506 700 506 700 506 700 506 700 506 700 506 700 506 700 506 701 506 700 506 700 506 700 506 700 506 700 506 700 506 700 506 700 506 700 506 700 506 700 506 700 506 699 506 700 506 700 1608 700 1607 700 506 700 506 700 + From 5de2c32c815d3a5322232abb7a5e77bb9052bbcf Mon Sep 17 00:00:00 2001 From: gornekich Date: Thu, 6 Oct 2022 21:58:17 +0500 Subject: [PATCH 110/824] [FL-2864] NFC update detect reader (#1820) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * nfc: update detect reader view * nfc: make detect reader more interractive * nfc: update icons * nfc: fix detect reader gui * nfc: fix gui, fix worker events * nfc: fix notifications * nfc: add nfc_worker NULL assert Co-authored-by: あく --- .../main/nfc/scenes/nfc_scene_detect_reader.c | 45 ++++++++++- .../nfc/scenes/nfc_scene_mfkey_nonces_info.c | 6 +- applications/main/nfc/views/detect_reader.c | 73 ++++++++++++++---- applications/main/nfc/views/detect_reader.h | 13 +++- assets/icons/NFC/Modern_reader_18x34.png | Bin 0 -> 3670 bytes assets/icons/NFC/Move_flipper_26x39.png | Bin 0 -> 3698 bytes assets/icons/NFC/Release_arrow_18x15.png | Bin 0 -> 3631 bytes firmware/targets/f7/api_symbols.csv | 3 + lib/nfc/helpers/mfkey32.c | 2 +- lib/nfc/nfc_worker.c | 21 ++++- lib/nfc/nfc_worker.h | 2 + 11 files changed, 140 insertions(+), 25 deletions(-) create mode 100644 assets/icons/NFC/Modern_reader_18x34.png create mode 100644 assets/icons/NFC/Move_flipper_26x39.png create mode 100644 assets/icons/NFC/Release_arrow_18x15.png diff --git a/applications/main/nfc/scenes/nfc_scene_detect_reader.c b/applications/main/nfc/scenes/nfc_scene_detect_reader.c index 5f4582d8ef5..e8c2b5ee8fb 100644 --- a/applications/main/nfc/scenes/nfc_scene_detect_reader.c +++ b/applications/main/nfc/scenes/nfc_scene_detect_reader.c @@ -1,6 +1,15 @@ #include "../nfc_i.h" #include +#define NFC_SCENE_DETECT_READER_PAIR_NONCES_MAX (10U) + +static const NotificationSequence sequence_detect_reader = { + &message_green_255, + &message_blue_255, + &message_do_not_reset, + NULL, +}; + bool nfc_detect_reader_worker_callback(NfcWorkerEvent event, void* context) { UNUSED(event); furi_assert(context); @@ -20,21 +29,26 @@ void nfc_scene_detect_reader_on_enter(void* context) { DOLPHIN_DEED(DolphinDeedNfcEmulate); detect_reader_set_callback(nfc->detect_reader, nfc_scene_detect_reader_callback, nfc); + detect_reader_set_nonces_max(nfc->detect_reader, NFC_SCENE_DETECT_READER_PAIR_NONCES_MAX); + + // Store number of collected nonces in scene state + scene_manager_set_scene_state(nfc->scene_manager, NfcSceneDetectReader, 0); + notification_message(nfc->notifications, &sequence_detect_reader); + nfc_worker_start( nfc->worker, NfcWorkerStateAnalyzeReader, &nfc->dev->dev_data, nfc_detect_reader_worker_callback, nfc); - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewDetectReader); - - nfc_blink_read_start(nfc); } bool nfc_scene_detect_reader_on_event(void* context, SceneManagerEvent event) { Nfc* nfc = context; bool consumed = false; + uint32_t nonces_collected = + scene_manager_get_scene_state(nfc->scene_manager, NfcSceneDetectReader); if(event.type == SceneManagerEventTypeCustom) { if(event.event == NfcCustomEventViewExit) { @@ -42,8 +56,29 @@ bool nfc_scene_detect_reader_on_event(void* context, SceneManagerEvent event) { scene_manager_next_scene(nfc->scene_manager, NfcSceneMfkeyNoncesInfo); consumed = true; } else if(event.event == NfcWorkerEventDetectReaderMfkeyCollected) { - detect_reader_inc_nonce_cnt(nfc->detect_reader); + nonces_collected += 2; + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneDetectReader, nonces_collected); + detect_reader_set_nonces_collected(nfc->detect_reader, nonces_collected); + if(nonces_collected >= NFC_SCENE_DETECT_READER_PAIR_NONCES_MAX) { + detect_reader_set_state(nfc->detect_reader, DetectReaderStateDone); + nfc_blink_stop(nfc); + notification_message(nfc->notifications, &sequence_single_vibro); + notification_message(nfc->notifications, &sequence_set_green_255); + nfc_worker_stop(nfc->worker); + } consumed = true; + } else if(event.event == NfcWorkerEventDetectReaderDetected) { + if(nonces_collected < NFC_SCENE_DETECT_READER_PAIR_NONCES_MAX) { + notification_message(nfc->notifications, &sequence_blink_start_cyan); + detect_reader_set_state(nfc->detect_reader, DetectReaderStateReaderDetected); + } + } else if(event.event == NfcWorkerEventDetectReaderLost) { + if(nonces_collected < NFC_SCENE_DETECT_READER_PAIR_NONCES_MAX) { + nfc_blink_stop(nfc); + notification_message(nfc->notifications, &sequence_detect_reader); + detect_reader_set_state(nfc->detect_reader, DetectReaderStateReaderLost); + } } } @@ -59,5 +94,7 @@ void nfc_scene_detect_reader_on_exit(void* context) { // Clear view detect_reader_reset(nfc->detect_reader); + // Stop notifications nfc_blink_stop(nfc); + notification_message(nfc->notifications, &sequence_reset_green); } diff --git a/applications/main/nfc/scenes/nfc_scene_mfkey_nonces_info.c b/applications/main/nfc/scenes/nfc_scene_mfkey_nonces_info.c index 9fd4f5ae0c4..6d9852f3e66 100644 --- a/applications/main/nfc/scenes/nfc_scene_mfkey_nonces_info.c +++ b/applications/main/nfc/scenes/nfc_scene_mfkey_nonces_info.c @@ -16,14 +16,14 @@ void nfc_scene_mfkey_nonces_info_on_enter(void* context) { uint16_t nonces_saved = mfkey32_get_auth_sectors(temp_str); widget_add_text_scroll_element(nfc->widget, 0, 22, 128, 42, furi_string_get_cstr(temp_str)); - furi_string_printf(temp_str, "Nonces saved %d", nonces_saved); + furi_string_printf(temp_str, "Nonce pairs saved: %d", nonces_saved); widget_add_string_element( nfc->widget, 0, 0, AlignLeft, AlignTop, FontPrimary, furi_string_get_cstr(temp_str)); widget_add_string_element( nfc->widget, 0, 12, AlignLeft, AlignTop, FontSecondary, "Authenticated sectors:"); widget_add_button_element( - nfc->widget, GuiButtonTypeRight, "Next", nfc_scene_mfkey_nonces_info_callback, nfc); + nfc->widget, GuiButtonTypeCenter, "OK", nfc_scene_mfkey_nonces_info_callback, nfc); furi_string_free(temp_str); @@ -35,7 +35,7 @@ bool nfc_scene_mfkey_nonces_info_on_event(void* context, SceneManagerEvent event bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { - if(event.event == GuiButtonTypeRight) { + if(event.event == GuiButtonTypeCenter) { scene_manager_next_scene(nfc->scene_manager, NfcSceneMfkeyComplete); consumed = true; } diff --git a/applications/main/nfc/views/detect_reader.c b/applications/main/nfc/views/detect_reader.c index 177c13f75da..9a07704384e 100644 --- a/applications/main/nfc/views/detect_reader.c +++ b/applications/main/nfc/views/detect_reader.c @@ -10,29 +10,50 @@ struct DetectReader { typedef struct { uint16_t nonces; + uint16_t nonces_max; + DetectReaderState state; } DetectReaderViewModel; static void detect_reader_draw_callback(Canvas* canvas, void* model) { DetectReaderViewModel* m = model; char text[32] = {}; - snprintf(text, sizeof(text), "Tap the reader several times"); - canvas_draw_str_aligned(canvas, 64, 0, AlignCenter, AlignTop, "Tap the reader several times"); + // Draw header and icon + canvas_draw_icon(canvas, 0, 16, &I_Modern_reader_18x34); + if(m->state == DetectReaderStateStart) { + snprintf(text, sizeof(text), "Touch the reader"); + canvas_draw_icon(canvas, 21, 13, &I_Move_flipper_26x39); + } else if(m->state == DetectReaderStateReaderDetected) { + snprintf(text, sizeof(text), "Move the Flipper away"); + canvas_draw_icon(canvas, 24, 25, &I_Release_arrow_18x15); + } else if(m->state == DetectReaderStateReaderLost) { + snprintf(text, sizeof(text), "Touch the reader again"); + canvas_draw_icon(canvas, 21, 13, &I_Move_flipper_26x39); + } + + canvas_draw_str_aligned(canvas, 64, 0, AlignCenter, AlignTop, text); - if(m->nonces == 0) { + // Draw collected nonces + if(m->state == DetectReaderStateStart) { canvas_set_font(canvas, FontPrimary); - canvas_draw_str_aligned(canvas, 52, 22, AlignLeft, AlignTop, "Emulating..."); + canvas_draw_str_aligned(canvas, 51, 22, AlignLeft, AlignTop, "Emulating..."); canvas_set_font(canvas, FontSecondary); - canvas_draw_str_aligned(canvas, 52, 35, AlignLeft, AlignTop, "MIFARE Classic"); - canvas_draw_icon(canvas, 0, 13, &I_Tap_reader_36x38); + canvas_draw_str_aligned(canvas, 51, 35, AlignLeft, AlignTop, "MIFARE MFkey32"); } else { - canvas_set_font(canvas, FontPrimary); - canvas_draw_str_aligned(canvas, 54, 22, AlignLeft, AlignTop, "Collecting..."); + if(m->state == DetectReaderStateDone) { + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 51, 22, AlignLeft, AlignTop, "Completed!"); + } else { + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 51, 22, AlignLeft, AlignTop, "Collecting..."); + } canvas_set_font(canvas, FontSecondary); - snprintf(text, sizeof(text), "Nonces: %d", m->nonces); - canvas_draw_str_aligned(canvas, 54, 35, AlignLeft, AlignTop, text); - elements_button_right(canvas, "Next"); - canvas_draw_icon(canvas, 6, 15, &I_ArrowC_1_36x36); + snprintf(text, sizeof(text), "Nonce pairs: %d/%d", m->nonces, m->nonces_max); + canvas_draw_str_aligned(canvas, 51, 35, AlignLeft, AlignTop, text); + } + // Draw button + if(m->nonces > 0) { + elements_button_center(canvas, "Done"); } } @@ -49,7 +70,7 @@ static bool detect_reader_input_callback(InputEvent* event, void* context) { }); if(event->type == InputTypeShort) { - if(event->key == InputKeyRight) { + if(event->key == InputKeyOk) { if(nonces > 0) { detect_reader->callback(detect_reader->context); consumed = true; @@ -84,6 +105,8 @@ void detect_reader_reset(DetectReader* detect_reader) { with_view_model( detect_reader->view, (DetectReaderViewModel * model) { model->nonces = 0; + model->nonces_max = 0; + model->state = DetectReaderStateStart; return false; }); } @@ -105,11 +128,31 @@ void detect_reader_set_callback( detect_reader->context = context; } -void detect_reader_inc_nonce_cnt(DetectReader* detect_reader) { +void detect_reader_set_nonces_max(DetectReader* detect_reader, uint16_t nonces_max) { + furi_assert(detect_reader); + + with_view_model( + detect_reader->view, (DetectReaderViewModel * model) { + model->nonces_max = nonces_max; + return false; + }); +} + +void detect_reader_set_nonces_collected(DetectReader* detect_reader, uint16_t nonces_collected) { furi_assert(detect_reader); + with_view_model( detect_reader->view, (DetectReaderViewModel * model) { - model->nonces++; + model->nonces = nonces_collected; return false; }); } + +void detect_reader_set_state(DetectReader* detect_reader, DetectReaderState state) { + furi_assert(detect_reader); + with_view_model( + detect_reader->view, (DetectReaderViewModel * model) { + model->state = state; + return true; + }); +} diff --git a/applications/main/nfc/views/detect_reader.h b/applications/main/nfc/views/detect_reader.h index 12cd03db496..aabdd7c87db 100644 --- a/applications/main/nfc/views/detect_reader.h +++ b/applications/main/nfc/views/detect_reader.h @@ -5,6 +5,13 @@ typedef struct DetectReader DetectReader; +typedef enum { + DetectReaderStateStart, + DetectReaderStateReaderDetected, + DetectReaderStateReaderLost, + DetectReaderStateDone, +} DetectReaderState; + typedef void (*DetectReaderDoneCallback)(void* context); DetectReader* detect_reader_alloc(); @@ -20,4 +27,8 @@ void detect_reader_set_callback( DetectReaderDoneCallback callback, void* context); -void detect_reader_inc_nonce_cnt(DetectReader* detect_reader); +void detect_reader_set_nonces_max(DetectReader* detect_reader, uint16_t nonces_max); + +void detect_reader_set_nonces_collected(DetectReader* detect_reader, uint16_t nonces_collected); + +void detect_reader_set_state(DetectReader* detect_reader, DetectReaderState state); diff --git a/assets/icons/NFC/Modern_reader_18x34.png b/assets/icons/NFC/Modern_reader_18x34.png new file mode 100644 index 0000000000000000000000000000000000000000..b19c0f30c9f3928d3129acc9da92d5a9e962d084 GIT binary patch literal 3670 zcmaJ@c{r478-GRiEm@Lu#t;&-TAE=jGh>S(jEuAxj4^2zV`?lVl&v}>Wo<-dUn)uo zWeX)lN%pcNI{1zyPGY`s&gp#LA79^lz3=-x&wbs$-~GFn_qyJMgHE9q!yUB8;Xo`l)1P*d0stWcJU1%QZCV+#GO~nqh>yJH zz;sm-2f1P|MJgt1>uE^HABfk;?N@SX*k)}lqSlrZFPxYdd0ELtU;3itd$9?PTZ!jy z$6tK8_A&f+;JezDPaPW%`^=|G7kQOkV)f$Esdh*gqe$r@?CxzJ&bKzVe4Kz-MoDV1 z0D19BKaJpZO(9@4!pv+RxL)ijAQbXON*t&sWYxoV#qs54uo*{$A}O1A7Dgbe50Ok@OvlkEv2fW)fHA8?4 z8GxeAf`{4f`^x2~^aPd4s4%P6LRm+7i5mood3Zo}>vr0!>{B!*Zy{$|LK;IeR1r~z zavv670YFZ&k|5i~^^i{4^3G1<#46e21~bn@`CuQP@r}u@5|$+ZeB?xQZ|FlScSf3u zM$$KK?U@q^I3|^IYUPrDg`DL>AZL2OW0AF48|&OF)&2dG6BF+bG-JKUFFnp~P#cfe zd#s=QBf{+a%JPS&V_H#&qfxdZs~;L)Eji}x>bfd%!Dr}GlI{0LQvC1gZ@|s=KGh^W z#c>yfphSG;@-q zi($!qBa3G@=+;I_h*-6WZzpRE#0&XcBxxp!t7OEiYBbo1C|uG4y@*$I0Xrlc*}+{e z5<%{E>I)e57F663nr15gw%-SrN|&_kymzQnxF%uQ zx9dJvL?Oz$Ucy*}iv^K)TiKBuNlx$W3PHQH47UwPm`Dg;aB0*5rxZFo(0;P*kLDdd z2zVUHPG9q#Leh4qe0V&r*+fer0f*43zOu#s{vBeELXS-k!&P%yzbMPlZl`9-ivhpD z3Nh3*ebBzPmlcJP#gq8d4OxNMU zT;evPq{G;<+$z_*E^&q14NqmFI?gNGJLHw!y8dQofJ(p$?e1sJlWoJ-cRQuM_ULJ! zw*8#;S$K&nEfcGBzBQhztD3b#YzI}9yW?)UW4`K}ORB9zmvhMMG|jQOWccj2fw(fxlxNu z3*(BZg-oKwoe0nM1X0f>$0ldo9haQ@$H!}1KvKS{l_B~Xfifkrr=pCSweNTIpE<2p zlfJHAa|u&il#9Y44-290%eK-a(MoA8(Lw3X9cIss zf|zFN(AL4*TbL7m};H&2IPF{Awe2nbvY-Tx*=(LT|aPEvl`d?Le3z z%w@U~s`K~en>w00wsySgxYhA4!zc>_??X&wO=b0EjXv@|9CBE{s<7%Y#lB+VaK7hU zRV^dtFv>HJQrA-;HY|p!zvYLWz1=UU|P9@pzs7?2NuX<5c^hovII|O` zW;Mlf{?(j)bKHE~%wz;H;(7d)N&Ta?NA1o{%D3JMF*rFDrSyLgmYQ7PfQuBua)hsy9->&~D@I`1iOYdb^z# z?DPm>SAR>cH44>wj?B}atiGUAbfwl&#&I|covoaC8bn86&~@L>rx?WL5MijC)tOOK$tuZz71th`dX)zd(-3Y-6#cv!bjPppDU@$i4vk?<0gT9Uo5 zWA;_$%fTxqH|B5hXB8S1K3=WLi*@iYP$zw=D?Nd#FbfJDlpI&ux-a&SXsOxbi&c8` zUgwfokF@fLI_)q*VAQdOm(dLmg#y1wxl2yQoc%J?H+$5X1oa$!Nd6YfQ!`gexLB?@ zsFJ31?!E3%$fQ~v^X0RQp=%F{N}8+vy8L_mr$3DtWP8b`7N>nmlV!;C4?K_=J@jC9 z`K$FHG_6B-u;zRfuKM;fv&XfRf)||~rWV9I#3kZ4qVZhM@I!LnDx-T&Exh)t;cvZz zUbQRh<}aQOx(m4zdi{GTYxZlED;DJm#nY>)YxJXKPV}JJR^cAubumrZs=n&Cz3M#} zqHEH-eP3*4TYq`F!JFqA$QaAG|9YckOp}EVotR#c7+u*dgC012IlT0v*qdKYt5emX zC$O0dnKoH&nQLA?UQe7~nRmaN843GtJNS#-4MQ`}&;yIa7qo%t=r<|Ug|5rI>%6lO zkUxgJ2X9q{Px*F^o{(eCKauBr?6Kxwnli05?L4yZn6pqZIJw>9u}9`z^l|zOXU1$J z<&AS|&5fGO^6Ddj)pKEW55xUerq!}dI)|6)LVs80zw6CLVTS7#!z(a2{al^7vRdcb<4cyaR{gl)xLymdjiLARL+4J^b8{BEhiq3wW6pPNBrhk);kG7a zB(=xN#D2-%Z;nEZS+LiqzZc-T{JONWRW@#Iw3n+WLnBsuzw~u>r+4S3Eu^J9qo2uJ zpQ-<%dUvp;v1Rwu7a>Uav86+6vklxKuKN7#Q90*{GoW+2{D431FT1@iSW8h&N#TnK zr!Rh=H@X%r_^(vuSd%zzOn(lS%%%WVeoP+<$evE7Qd}uyztEr;6f*!2)};|i91_71 z?aQP?$eTWp5IReM1^_dQ5Ej`tkir4^P^dHp20UN$3=E?AVZa_n1Q>yZqXf|G!q^nI zFejpKSfDS;4{Tu$G7CWq2_OC4Htbb@3!GBjuP%~%ok9RP~ zmGU3G|C2bF7|NnRT`9rLQ*2*B@BB44L$S~}HigV#vWZOQ$sdJ07{KH(g9Df>5CRE- zgLDaGUm9c6viDC2fq=GW1ars?Uy3~*0~U}#Xf!`G9uC9W*z89l_alwqaBKX2BnoZ? zvw|5T_oLv3CfFZXJk$3SoxWBq=v1@TiXR3HYr+1vl>^$(L^fHt@P46oqu&-haqf|+LvhFcAp;MtHb?>>aojMU&+$&Jw2#YyGzNAg?=0N~xO|1%9#hzl z?cEASvP%gSwpPE+s1{*#d-3TN&&Ml8>K`_AH+6iBd^^X2G{h(8k literal 0 HcmV?d00001 diff --git a/assets/icons/NFC/Move_flipper_26x39.png b/assets/icons/NFC/Move_flipper_26x39.png new file mode 100644 index 0000000000000000000000000000000000000000..ff4af9ff05989e5ff04d3888971b7f7801c59ed4 GIT binary patch literal 3698 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 literal 0 HcmV?d00001 diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index a140ff0bef5..4e1704d061c 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -2693,6 +2693,8 @@ Variable,+,I_Lock_7x8,const Icon, Variable,+,I_Lock_8x8,const Icon, Variable,+,I_MHz_25x11,const Icon, Variable,+,I_Medium_chip_22x21,const Icon, +Variable,+,I_Modern_reader_18x34,const Icon, +Variable,+,I_Move_flipper_26x39,const Icon, Variable,+,I_Mute_25x27,const Icon, Variable,+,I_Mute_hvr_25x27,const Icon, Variable,+,I_NFC_manual_60x50,const Icon, @@ -2720,6 +2722,7 @@ Variable,+,I_RFIDDolphinReceive_97x61,const Icon, Variable,+,I_RFIDDolphinSend_97x61,const Icon, Variable,+,I_RFIDDolphinSuccess_108x57,const Icon, Variable,+,I_Reader_detect_43x40,const Icon, +Variable,+,I_Release_arrow_18x15,const Icon, Variable,+,I_Restoring_38x32,const Icon, Variable,+,I_Right_mouse_icon_9x9,const Icon, Variable,+,I_SDQuestion_35x43,const Icon, diff --git a/lib/nfc/helpers/mfkey32.c b/lib/nfc/helpers/mfkey32.c index 8eb417eb166..fa5713f270a 100644 --- a/lib/nfc/helpers/mfkey32.c +++ b/lib/nfc/helpers/mfkey32.c @@ -92,7 +92,7 @@ void mfkey32_set_callback(Mfkey32* instance, Mfkey32ParseDataCallback callback, static bool mfkey32_write_params(Mfkey32* instance, Mfkey32Params* params) { FuriString* str = furi_string_alloc_printf( - "Sector %d key %c cuid %08x nt0 %08x nr0 %08x ar0 %08x nt1 %08x nr1 %08x ar1 %08x\n", + "Sec %d key %c cuid %08x nt0 %08x nr0 %08x ar0 %08x nt1 %08x nr1 %08x ar1 %08x\n", params->sector, params->key == MfClassicKeyA ? 'A' : 'B', params->cuid, diff --git a/lib/nfc/nfc_worker.c b/lib/nfc/nfc_worker.c index 2feae443f7a..996e68f0da7 100644 --- a/lib/nfc/nfc_worker.c +++ b/lib/nfc/nfc_worker.c @@ -647,7 +647,8 @@ static void nfc_worker_reader_analyzer_callback(ReaderAnalyzerEvent event, void* furi_assert(context); NfcWorker* nfc_worker = context; - if(event == ReaderAnalyzerEventMfkeyCollected) { + if((nfc_worker->state == NfcWorkerStateAnalyzeReader) && + (event == ReaderAnalyzerEventMfkeyCollected)) { if(nfc_worker->callback) { nfc_worker->callback(NfcWorkerEventDetectReaderMfkeyCollected, nfc_worker->context); } @@ -655,6 +656,9 @@ static void nfc_worker_reader_analyzer_callback(ReaderAnalyzerEvent event, void* } void nfc_worker_analyze_reader(NfcWorker* nfc_worker) { + furi_assert(nfc_worker); + furi_assert(nfc_worker->callback); + FuriHalNfcTxRxContext tx_rx = {}; ReaderAnalyzer* reader_analyzer = nfc_worker->reader_analyzer; @@ -673,17 +677,32 @@ void nfc_worker_analyze_reader(NfcWorker* nfc_worker) { rfal_platform_spi_acquire(); FURI_LOG_D(TAG, "Start reader analyzer"); + + uint8_t reader_no_data_received_cnt = 0; + bool reader_no_data_notified = true; + while(nfc_worker->state == NfcWorkerStateAnalyzeReader) { furi_hal_nfc_stop_cmd(); furi_delay_ms(5); furi_hal_nfc_listen_start(nfc_data); if(furi_hal_nfc_listen_rx(&tx_rx, 300)) { + if(reader_no_data_notified) { + nfc_worker->callback(NfcWorkerEventDetectReaderDetected, nfc_worker->context); + } + reader_no_data_received_cnt = 0; + reader_no_data_notified = false; NfcProtocol protocol = reader_analyzer_guess_protocol(reader_analyzer, tx_rx.rx_data, tx_rx.rx_bits / 8); if(protocol == NfcDeviceProtocolMifareClassic) { mf_classic_emulator(&emulator, &tx_rx); } } else { + reader_no_data_received_cnt++; + if(!reader_no_data_notified && (reader_no_data_received_cnt > 5)) { + nfc_worker->callback(NfcWorkerEventDetectReaderLost, nfc_worker->context); + reader_no_data_received_cnt = 0; + reader_no_data_notified = true; + } FURI_LOG_D(TAG, "No data from reader"); continue; } diff --git a/lib/nfc/nfc_worker.h b/lib/nfc/nfc_worker.h index b7bf4da9ad9..84615f5d8b8 100644 --- a/lib/nfc/nfc_worker.h +++ b/lib/nfc/nfc_worker.h @@ -56,6 +56,8 @@ typedef enum { NfcWorkerEventFoundKeyB, // Detect Reader events + NfcWorkerEventDetectReaderDetected, + NfcWorkerEventDetectReaderLost, NfcWorkerEventDetectReaderMfkeyCollected, // Mifare Ultralight events From 6dde5586af130a8ca85416c502bee1a69da5220e Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Thu, 6 Oct 2022 20:07:56 +0300 Subject: [PATCH 111/824] [FL-2803] Mifare Classic read improvements Part 1 (#1822) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Reuse found keys when reading a card * Fix keys not being read if no newline at the end of the file * Actually read all keys from the dictionary * Support for furi_string * Check only for re-used key after the current sector * Declare the key attack function as static * nfc: change logs, check worker state Co-authored-by: gornekich Co-authored-by: あく --- lib/nfc/helpers/mf_classic_dict.c | 13 ++++++-- lib/nfc/nfc_worker.c | 53 +++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 2 deletions(-) diff --git a/lib/nfc/helpers/mf_classic_dict.c b/lib/nfc/helpers/mf_classic_dict.c index 57841afc1df..356f6f7c771 100644 --- a/lib/nfc/helpers/mf_classic_dict.c +++ b/lib/nfc/helpers/mf_classic_dict.c @@ -69,9 +69,18 @@ MfClassicDict* mf_classic_dict_alloc(MfClassicDictType dict_type) { FuriString* next_line; next_line = furi_string_alloc(); while(true) { - if(!stream_read_line(dict->stream, next_line)) break; + if(!stream_read_line(dict->stream, next_line)) { + FURI_LOG_T(TAG, "No keys left in dict"); + break; + } + furi_string_trim(next_line); + FURI_LOG_T( + TAG, + "Read line: %s, len: %d", + furi_string_get_cstr(next_line), + furi_string_size(next_line)); if(furi_string_get_char(next_line, 0) == '#') continue; - if(furi_string_size(next_line) != NFC_MF_CLASSIC_KEY_LEN) continue; + if(furi_string_size(next_line) != NFC_MF_CLASSIC_KEY_LEN - 1) continue; dict->total_keys++; } furi_string_free(next_line); diff --git a/lib/nfc/nfc_worker.c b/lib/nfc/nfc_worker.c index 996e68f0da7..c61ad444c60 100644 --- a/lib/nfc/nfc_worker.c +++ b/lib/nfc/nfc_worker.c @@ -453,6 +453,54 @@ void nfc_worker_emulate_mf_ultralight(NfcWorker* nfc_worker) { } } +static void nfc_worker_mf_classic_key_attack( + NfcWorker* nfc_worker, + uint64_t key, + FuriHalNfcTxRxContext* tx_rx, + uint16_t start_sector) { + furi_assert(nfc_worker); + + MfClassicData* data = &nfc_worker->dev_data->mf_classic_data; + uint32_t total_sectors = mf_classic_get_total_sectors_num(data->type); + + furi_assert(start_sector < total_sectors); + + // Check every sector's A and B keys with the given key + for(size_t i = start_sector; i < total_sectors; i++) { + uint8_t block_num = mf_classic_get_sector_trailer_block_num_by_sector(i); + if(mf_classic_is_sector_read(data, i)) continue; + if(!mf_classic_is_key_found(data, i, MfClassicKeyA)) { + FURI_LOG_D( + TAG, + "Trying A key for sector %d, key: %04lx%08lx", + i, + (uint32_t)(key >> 32), + (uint32_t)key); + if(mf_classic_authenticate(tx_rx, block_num, key, MfClassicKeyA)) { + mf_classic_set_key_found(data, i, MfClassicKeyA, key); + FURI_LOG_D(TAG, "Key found"); + nfc_worker->callback(NfcWorkerEventFoundKeyA, nfc_worker->context); + } + } + if(!mf_classic_is_key_found(data, i, MfClassicKeyB)) { + FURI_LOG_D( + TAG, + "Trying B key for sector %d, key: %04lx%08lx", + i, + (uint32_t)(key >> 32), + (uint32_t)key); + if(mf_classic_authenticate(tx_rx, block_num, key, MfClassicKeyB)) { + mf_classic_set_key_found(data, i, MfClassicKeyB, key); + FURI_LOG_D(TAG, "Key found"); + nfc_worker->callback(NfcWorkerEventFoundKeyB, nfc_worker->context); + } + } + if(mf_classic_is_sector_read(data, i)) continue; + mf_classic_read_sector(tx_rx, data, i); + if(nfc_worker->state != NfcWorkerStateMfClassicDictAttack) break; + } +} + void nfc_worker_mf_classic_dict_attack(NfcWorker* nfc_worker) { furi_assert(nfc_worker); furi_assert(nfc_worker->callback); @@ -484,6 +532,7 @@ void nfc_worker_mf_classic_dict_attack(NfcWorker* nfc_worker) { bool is_key_b_found = mf_classic_is_key_found(data, i, MfClassicKeyB); uint16_t key_index = 0; while(mf_classic_dict_get_next_key(dict, &key)) { + FURI_LOG_T(TAG, "Key %d", key_index); if(++key_index % NFC_DICT_KEY_BATCH_SIZE == 0) { nfc_worker->callback(NfcWorkerEventNewDictKeyBatch, nfc_worker->context); } @@ -505,15 +554,19 @@ void nfc_worker_mf_classic_dict_attack(NfcWorker* nfc_worker) { is_key_a_found = mf_classic_is_key_found(data, i, MfClassicKeyA); if(mf_classic_authenticate(&tx_rx, block_num, key, MfClassicKeyA)) { mf_classic_set_key_found(data, i, MfClassicKeyA, key); + FURI_LOG_D(TAG, "Key found"); nfc_worker->callback(NfcWorkerEventFoundKeyA, nfc_worker->context); + nfc_worker_mf_classic_key_attack(nfc_worker, key, &tx_rx, i + 1); } furi_hal_nfc_sleep(); } if(!is_key_b_found) { is_key_b_found = mf_classic_is_key_found(data, i, MfClassicKeyB); if(mf_classic_authenticate(&tx_rx, block_num, key, MfClassicKeyB)) { + FURI_LOG_D(TAG, "Key found"); mf_classic_set_key_found(data, i, MfClassicKeyB, key); nfc_worker->callback(NfcWorkerEventFoundKeyB, nfc_worker->context); + nfc_worker_mf_classic_key_attack(nfc_worker, key, &tx_rx, i + 1); } } if(is_key_a_found && is_key_b_found) break; From 01f7a3e5b52fa1842bb3117d7adddf059807c9ef Mon Sep 17 00:00:00 2001 From: gornekich Date: Thu, 6 Oct 2022 22:12:45 +0500 Subject: [PATCH 112/824] [FL-2874] Remove bank card uid emulation (#1823) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * nfc: open bank card info from archive * nfc: remove emulate uid option from bank cards menu Co-authored-by: あく --- applications/main/nfc/nfc.c | 2 ++ applications/main/nfc/scenes/nfc_scene_saved_menu.c | 3 +-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/applications/main/nfc/nfc.c b/applications/main/nfc/nfc.c index 44a0f63687b..0b685f54574 100644 --- a/applications/main/nfc/nfc.c +++ b/applications/main/nfc/nfc.c @@ -277,6 +277,8 @@ int32_t nfc_app(void* p) { scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightEmulate); } else if(nfc->dev->format == NfcDeviceSaveFormatMifareClassic) { scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicEmulate); + } else if(nfc->dev->format == NfcDeviceSaveFormatBankCard) { + scene_manager_next_scene(nfc->scene_manager, NfcSceneDeviceInfo); } else { scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateUid); } diff --git a/applications/main/nfc/scenes/nfc_scene_saved_menu.c b/applications/main/nfc/scenes/nfc_scene_saved_menu.c index c1043b3a7a4..fe65b5b8ad9 100644 --- a/applications/main/nfc/scenes/nfc_scene_saved_menu.c +++ b/applications/main/nfc/scenes/nfc_scene_saved_menu.c @@ -20,8 +20,7 @@ void nfc_scene_saved_menu_on_enter(void* context) { Submenu* submenu = nfc->submenu; if(nfc->dev->format == NfcDeviceSaveFormatUid || - nfc->dev->format == NfcDeviceSaveFormatMifareDesfire || - nfc->dev->format == NfcDeviceSaveFormatBankCard) { + nfc->dev->format == NfcDeviceSaveFormatMifareDesfire) { submenu_add_item( submenu, "Emulate UID", From 19cb469e4b884c31cad69e711ede34c2acda60ae Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Thu, 6 Oct 2022 20:19:35 +0300 Subject: [PATCH 113/824] [FL-2877] Don't turn off the backlight on MFC dict attack #1826 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c index b23f4b8f114..813546905ed 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c @@ -91,6 +91,7 @@ void nfc_scene_mf_classic_dict_attack_on_enter(void* context) { nfc_scene_mf_classic_dict_attack_prepare_view(nfc, DictAttackStateIdle); view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewDictAttack); nfc_blink_read_start(nfc); + notification_message(nfc->notifications, &sequence_display_backlight_enforce_on); } bool nfc_scene_mf_classic_dict_attack_on_event(void* context, SceneManagerEvent event) { @@ -167,4 +168,5 @@ void nfc_scene_mf_classic_dict_attack_on_exit(void* context) { } dict_attack_reset(nfc->dict_attack); nfc_blink_stop(nfc); + notification_message(nfc->notifications, &sequence_display_backlight_enforce_auto); } From 72b3d7f41424c2489bb9466d2b131e0d4ff3e4e1 Mon Sep 17 00:00:00 2001 From: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> Date: Thu, 6 Oct 2022 20:25:54 +0300 Subject: [PATCH 114/824] [FL-2620] Infrared error message (#1827) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Show an error message if the remote could not be saved/renamed Co-authored-by: あく --- .../main/infrared/scenes/infrared_scene_learn_enter_name.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/applications/main/infrared/scenes/infrared_scene_learn_enter_name.c b/applications/main/infrared/scenes/infrared_scene_learn_enter_name.c index b7f4179ea2d..b6a7eac0d87 100644 --- a/applications/main/infrared/scenes/infrared_scene_learn_enter_name.c +++ b/applications/main/infrared/scenes/infrared_scene_learn_enter_name.c @@ -50,8 +50,10 @@ bool infrared_scene_learn_enter_name_on_event(void* context, SceneManagerEvent e if(success) { scene_manager_next_scene(scene_manager, InfraredSceneLearnDone); } else { - scene_manager_search_and_switch_to_previous_scene( - scene_manager, InfraredSceneRemoteList); + dialog_message_show_storage_error(infrared->dialogs, "Failed to save file"); + const uint32_t possible_scenes[] = {InfraredSceneRemoteList, InfraredSceneStart}; + scene_manager_search_and_switch_to_previous_scene_one_of( + scene_manager, possible_scenes, COUNT_OF(possible_scenes)); } consumed = true; } From dde18cd343a67a2ed42b2fe20b42bdffd05ad693 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Kimminich?= Date: Thu, 6 Oct 2022 19:40:18 +0200 Subject: [PATCH 115/824] Add exit label to keyboard connection screen (#1830) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add exit label to keyboard connection screen (resolves #1814) * BtHid: align baselines on connection screen Co-authored-by: あく --- applications/plugins/bt_hid_app/views/bt_hid_keyboard.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/applications/plugins/bt_hid_app/views/bt_hid_keyboard.c b/applications/plugins/bt_hid_app/views/bt_hid_keyboard.c index 3617dc0f1f6..8b9ae593a15 100644 --- a/applications/plugins/bt_hid_app/views/bt_hid_keyboard.c +++ b/applications/plugins/bt_hid_app/views/bt_hid_keyboard.c @@ -209,6 +209,11 @@ static void bt_hid_keyboard_draw_callback(Canvas* canvas, void* context) { canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); canvas_set_font(canvas, FontPrimary); elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Keyboard"); + + canvas_draw_icon(canvas, 68, 3, &I_Pin_back_arrow_10x8); + canvas_set_font(canvas, FontSecondary); + elements_multiline_text_aligned(canvas, 127, 4, AlignRight, AlignTop, "Hold to exit"); + elements_multiline_text_aligned( canvas, 4, 60, AlignLeft, AlignBottom, "Waiting for Connection..."); return; // Dont render the keyboard if we are not yet connected From eaa3adf98ac4dcad5dec8d5f7eb716c242eb9ee4 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Thu, 6 Oct 2022 21:51:30 +0300 Subject: [PATCH 116/824] [FL-2868] Remove string_push_uint64 (#1832) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Remove string_pust_uint64 * Oops, furi strings now * Remove dead code * Remove unneeded import Co-authored-by: あく --- lib/nfc/parsers/plantain_4k_parser.c | 14 +---------- lib/nfc/parsers/plantain_parser.c | 36 +--------------------------- lib/nfc/parsers/plantain_parser.h | 2 -- lib/nfc/parsers/two_cities.c | 13 +--------- 4 files changed, 3 insertions(+), 62 deletions(-) diff --git a/lib/nfc/parsers/plantain_4k_parser.c b/lib/nfc/parsers/plantain_4k_parser.c index c970d74e558..eddebac23f6 100644 --- a/lib/nfc/parsers/plantain_4k_parser.c +++ b/lib/nfc/parsers/plantain_4k_parser.c @@ -1,5 +1,4 @@ #include "nfc_supported_card.h" -#include "plantain_parser.h" // For luhn and string_push_uint64 #include #include @@ -121,26 +120,15 @@ bool plantain_4k_parser_parse(NfcDeviceData* dev_data) { FuriString* card_number_str; card_number_str = furi_string_alloc(); // Should look like "361301047292848684" - // %llu doesn't work for some reason in sprintf, so we use string_push_uint64 instead - string_push_uint64(card_number, card_number_str); + furi_string_printf(card_number_str, "%llu", card_number); // Add suffix with luhn checksum (1 digit) to the card number string FuriString* card_number_suffix; card_number_suffix = furi_string_alloc(); - // The number to calculate the checksum on doesn't fit into uint64_t, idk - //uint8_t luhn_checksum = plantain_calculate_luhn(card_number); - - // // Convert luhn checksum to string - // FuriString* luhn_checksum_str; - // luhn_checksum_str = furi_string_alloc(); - // string_push_uint64(luhn_checksum, luhn_checksum_str); - furi_string_cat_printf(card_number_suffix, "-"); - // FURI_LOG_D("plant4k", "Card checksum: %d", luhn_checksum); furi_string_cat_printf(card_number_str, furi_string_get_cstr(card_number_suffix)); // Free all not needed strings furi_string_free(card_number_suffix); - // furi_string_free(luhn_checksum_str); furi_string_printf( dev_data->parsed_data, diff --git a/lib/nfc/parsers/plantain_parser.c b/lib/nfc/parsers/plantain_parser.c index 8ef8ee14282..ac7a90b2466 100644 --- a/lib/nfc/parsers/plantain_parser.c +++ b/lib/nfc/parsers/plantain_parser.c @@ -55,29 +55,6 @@ bool plantain_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { return mf_classic_read_card(tx_rx, &reader, &nfc_worker->dev_data->mf_classic_data) == 16; } -void string_push_uint64(uint64_t input, FuriString* output) { - const uint8_t base = 10; - - do { - char c = input % base; - input /= base; - - if(c < 10) - c += '0'; - else - c += 'A' - 10; - furi_string_push_back(output, c); - } while(input); - - // reverse string - for(uint8_t i = 0; i < furi_string_size(output) / 2; i++) { - char c = furi_string_get_char(output, i); - furi_string_set_char( - output, i, furi_string_get_char(output, furi_string_size(output) - i - 1)); - furi_string_set_char(output, furi_string_size(output) - i - 1, c); - } -} - uint8_t plantain_calculate_luhn(uint64_t number) { // No. UNUSED(number); @@ -116,26 +93,15 @@ bool plantain_parser_parse(NfcDeviceData* dev_data) { FuriString* card_number_str; card_number_str = furi_string_alloc(); // Should look like "361301047292848684" - // %llu doesn't work for some reason in sprintf, so we use string_push_uint64 instead - string_push_uint64(card_number, card_number_str); + furi_string_printf(card_number_str, "%llu", card_number); // Add suffix with luhn checksum (1 digit) to the card number string FuriString* card_number_suffix; card_number_suffix = furi_string_alloc(); - // The number to calculate the checksum on doesn't fit into uint64_t, idk - //uint8_t luhn_checksum = plantain_calculate_luhn(card_number); - - // // Convert luhn checksum to string - // FuriString* luhn_checksum_str; - // luhn_checksum_str = furi_string_alloc(); - // string_push_uint64(luhn_checksum, luhn_checksum_str); - furi_string_cat_printf(card_number_suffix, "-"); - // FURI_LOG_D("plant4k", "Card checksum: %d", luhn_checksum); furi_string_cat_printf(card_number_str, furi_string_get_cstr(card_number_suffix)); // Free all not needed strings furi_string_free(card_number_suffix); - // furi_string_free(luhn_checksum_str); furi_string_printf( dev_data->parsed_data, diff --git a/lib/nfc/parsers/plantain_parser.h b/lib/nfc/parsers/plantain_parser.h index 65f396f1d93..1af8c50657a 100644 --- a/lib/nfc/parsers/plantain_parser.h +++ b/lib/nfc/parsers/plantain_parser.h @@ -8,6 +8,4 @@ bool plantain_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx); bool plantain_parser_parse(NfcDeviceData* dev_data); -void string_push_uint64(uint64_t input, FuriString* output); - uint8_t plantain_calculate_luhn(uint64_t number); diff --git a/lib/nfc/parsers/two_cities.c b/lib/nfc/parsers/two_cities.c index 3dc97586982..f5e31d51b18 100644 --- a/lib/nfc/parsers/two_cities.c +++ b/lib/nfc/parsers/two_cities.c @@ -121,26 +121,15 @@ bool two_cities_parser_parse(NfcDeviceData* dev_data) { FuriString* card_number_str; card_number_str = furi_string_alloc(); // Should look like "361301047292848684" - // %llu doesn't work for some reason in sprintf, so we use string_push_uint64 instead - string_push_uint64(card_number, card_number_str); + furi_string_printf(card_number_str, "%llu", card_number); // Add suffix with luhn checksum (1 digit) to the card number string FuriString* card_number_suffix; card_number_suffix = furi_string_alloc(); - // The number to calculate the checksum on doesn't fit into uint64_t, idk - //uint8_t luhn_checksum = two_cities_calculate_luhn(card_number); - - // // Convert luhn checksum to string - // FuriString* luhn_checksum_str; - // luhn_checksum_str = furi_string_alloc(); - // string_push_uint64(luhn_checksum, luhn_checksum_str); - furi_string_cat_printf(card_number_suffix, "-"); - // FURI_LOG_D("plant4k", "Card checksum: %d", luhn_checksum); furi_string_cat_printf(card_number_str, furi_string_get_cstr(card_number_suffix)); // Free all not needed strings furi_string_free(card_number_suffix); - // furi_string_free(luhn_checksum_str); // ===== // --PLANTAIN-- From 3367bc6f68b4ff0f87863d5a773307e12b24370d Mon Sep 17 00:00:00 2001 From: Matvey Gerasimov <48400794+spherebread@users.noreply.github.com> Date: Fri, 7 Oct 2022 10:23:21 +0400 Subject: [PATCH 117/824] Documentation: AppManifests.md typo fix (#1836) --- documentation/AppManifests.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/AppManifests.md b/documentation/AppManifests.md index 14c0ae3ac68..4fec9d22c4f 100644 --- a/documentation/AppManifests.md +++ b/documentation/AppManifests.md @@ -47,7 +47,7 @@ Only 2 parameters are mandatory: ***appid*** and ***apptype***, others are optio The following parameters are used only for [FAPs](./AppsOnSDCard.md): * **sources**: list of strings, file name masks, used for gathering sources within app folder. Default value of `["*.c*"]` includes C and C++ source files. Application cannot use `"lib"` folder for their own source code, as it is reserved for **fap_private_libs**. -* **fap_version**: tuple, 2 numbers in form of (x,y): application version to be embedded within .fap file. Default value is (0,1), meanig version "0.1". +* **fap_version**: tuple, 2 numbers in form of (x,y): application version to be embedded within .fap file. Default value is (0,1), meaning version "0.1". * **fap_icon**: name of a .png file, 1-bit color depth, 10x10px, to be embedded within .fap file. * **fap_libs**: list of extra libraries to link application against. Provides access to extra functions that are not exported as a part of main firmware at expense of increased .fap file size and RAM consumption. * **fap_category**: string, may be empty. App subcategory, also works as path of FAP within apps folder in the file system. From d1843c009415bf5213777044734ec5e227e70ac6 Mon Sep 17 00:00:00 2001 From: Max Andreev Date: Fri, 7 Oct 2022 11:56:19 +0500 Subject: [PATCH 118/824] Disable PVS-Studio license check (#1840) --- .github/workflows/pvs_studio.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/pvs_studio.yml b/.github/workflows/pvs_studio.yml index 7bd71de4992..9815755517c 100644 --- a/.github/workflows/pvs_studio.yml +++ b/.github/workflows/pvs_studio.yml @@ -65,6 +65,7 @@ jobs: pvs-studio-analyzer credentials ${{ secrets.PVS_STUDIO_CREDENTIALS }} pvs-studio-analyzer analyze \ @.pvsoptions \ + --disableLicenseExpirationCheck \ -j$(grep -c processor /proc/cpuinfo) \ -f build/f7-firmware-DC/compile_commands.json \ -o PVS-Studio.log From 38a82a1907008fda1d8aa655e66c80faf26e61ef Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Fri, 7 Oct 2022 22:27:11 +1000 Subject: [PATCH 119/824] [FL-2839] Furi stream buffer (#1834) * Core: stream buffer * stream buffer: API and usage * stream buffer: documentation * stream buffer: more documentation * Furi: fix spelling Co-authored-by: Aleksandr Kutuzov --- applications/debug/uart_echo/uart_echo.c | 13 +- applications/debug/unit_tests/rpc/rpc_test.c | 18 +-- applications/main/gpio/usb_uart_bridge.c | 17 +-- applications/main/subghz/subghz_cli.c | 18 +-- applications/services/cli/cli_commands.c | 9 +- applications/services/cli/cli_vcp.c | 32 ++-- applications/services/rpc/rpc.c | 21 ++- firmware/targets/f7/api_symbols.csv | 12 +- furi/core/stream_buffer.c | 77 ++++++++++ furi/core/stream_buffer.h | 152 +++++++++++++++++++ furi/furi.h | 33 ++-- lib/infrared/worker/infrared_worker.c | 38 +++-- lib/lfrfid/lfrfid_raw_worker.c | 21 +-- lib/lfrfid/lfrfid_worker_modes.c | 18 +-- lib/nfc/helpers/reader_analyzer.c | 17 +-- lib/one_wire/ibutton/ibutton_worker_modes.c | 12 +- lib/subghz/subghz_file_encoder_worker.c | 18 +-- lib/subghz/subghz_tx_rx_worker.c | 37 +++-- lib/subghz/subghz_worker.c | 18 +-- lib/toolbox/buffer_stream.c | 23 ++- lib/toolbox/buffer_stream.h | 7 +- 21 files changed, 403 insertions(+), 208 deletions(-) create mode 100644 furi/core/stream_buffer.c create mode 100644 furi/core/stream_buffer.h diff --git a/applications/debug/uart_echo/uart_echo.c b/applications/debug/uart_echo/uart_echo.c index 03b6a31a6a9..122862dd9bf 100644 --- a/applications/debug/uart_echo/uart_echo.c +++ b/applications/debug/uart_echo/uart_echo.c @@ -3,7 +3,6 @@ #include #include #include -#include #include #include #include @@ -20,7 +19,7 @@ typedef struct { ViewDispatcher* view_dispatcher; View* view; FuriThread* worker_thread; - StreamBufferHandle_t rx_stream; + FuriStreamBuffer* rx_stream; } UartEchoApp; typedef struct { @@ -92,13 +91,11 @@ static uint32_t uart_echo_exit(void* context) { static void uart_echo_on_irq_cb(UartIrqEvent ev, uint8_t data, void* context) { furi_assert(context); - BaseType_t xHigherPriorityTaskWoken = pdFALSE; UartEchoApp* app = context; if(ev == UartIrqEventRXNE) { - xStreamBufferSendFromISR(app->rx_stream, &data, 1, &xHigherPriorityTaskWoken); + furi_stream_buffer_send(app->rx_stream, &data, 1, 0); furi_thread_flags_set(furi_thread_get_id(app->worker_thread), WorkerEventRx); - portYIELD_FROM_ISR(xHigherPriorityTaskWoken); } } @@ -158,7 +155,7 @@ static int32_t uart_echo_worker(void* context) { size_t length = 0; do { uint8_t data[64]; - length = xStreamBufferReceive(app->rx_stream, data, 64, 0); + length = furi_stream_buffer_receive(app->rx_stream, data, 64, 0); if(length > 0) { furi_hal_uart_tx(FuriHalUartIdUSART1, data, length); with_view_model( @@ -186,7 +183,7 @@ static int32_t uart_echo_worker(void* context) { static UartEchoApp* uart_echo_app_alloc() { UartEchoApp* app = malloc(sizeof(UartEchoApp)); - app->rx_stream = xStreamBufferCreate(2048, 1); + app->rx_stream = furi_stream_buffer_alloc(2048, 1); // Gui app->gui = furi_record_open(RECORD_GUI); @@ -260,7 +257,7 @@ static void uart_echo_app_free(UartEchoApp* app) { furi_record_close(RECORD_NOTIFICATION); app->gui = NULL; - vStreamBufferDelete(app->rx_stream); + furi_stream_buffer_free(app->rx_stream); // Free rest free(app); diff --git a/applications/debug/unit_tests/rpc/rpc_test.c b/applications/debug/unit_tests/rpc/rpc_test.c index 6ee2aed652e..5b52df2fa39 100644 --- a/applications/debug/unit_tests/rpc/rpc_test.c +++ b/applications/debug/unit_tests/rpc/rpc_test.c @@ -10,7 +10,6 @@ #include #include "../minunit.h" #include -#include #include #include #include @@ -34,7 +33,7 @@ static uint32_t command_id = 0; typedef struct { RpcSession* session; - StreamBufferHandle_t output_stream; + FuriStreamBuffer* output_stream; SemaphoreHandle_t close_session_semaphore; SemaphoreHandle_t terminate_semaphore; TickType_t timeout; @@ -90,7 +89,7 @@ static void test_rpc_setup(void) { } furi_check(rpc_session[0].session); - rpc_session[0].output_stream = xStreamBufferCreate(1000, 1); + rpc_session[0].output_stream = furi_stream_buffer_alloc(1000, 1); rpc_session_set_send_bytes_callback(rpc_session[0].session, output_bytes_callback); rpc_session[0].close_session_semaphore = xSemaphoreCreateBinary(); rpc_session[0].terminate_semaphore = xSemaphoreCreateBinary(); @@ -110,7 +109,7 @@ static void test_rpc_setup_second_session(void) { } furi_check(rpc_session[1].session); - rpc_session[1].output_stream = xStreamBufferCreate(1000, 1); + rpc_session[1].output_stream = furi_stream_buffer_alloc(1000, 1); rpc_session_set_send_bytes_callback(rpc_session[1].session, output_bytes_callback); rpc_session[1].close_session_semaphore = xSemaphoreCreateBinary(); rpc_session[1].terminate_semaphore = xSemaphoreCreateBinary(); @@ -126,7 +125,7 @@ static void test_rpc_teardown(void) { rpc_session_close(rpc_session[0].session); furi_check(xSemaphoreTake(rpc_session[0].terminate_semaphore, portMAX_DELAY)); furi_record_close(RECORD_RPC); - vStreamBufferDelete(rpc_session[0].output_stream); + furi_stream_buffer_free(rpc_session[0].output_stream); vSemaphoreDelete(rpc_session[0].close_session_semaphore); vSemaphoreDelete(rpc_session[0].terminate_semaphore); ++command_id; @@ -141,7 +140,7 @@ static void test_rpc_teardown_second_session(void) { xSemaphoreTake(rpc_session[1].terminate_semaphore, 0); rpc_session_close(rpc_session[1].session); furi_check(xSemaphoreTake(rpc_session[1].terminate_semaphore, portMAX_DELAY)); - vStreamBufferDelete(rpc_session[1].output_stream); + furi_stream_buffer_free(rpc_session[1].output_stream); vSemaphoreDelete(rpc_session[1].close_session_semaphore); vSemaphoreDelete(rpc_session[1].terminate_semaphore); ++command_id; @@ -268,8 +267,8 @@ static PB_CommandStatus test_rpc_storage_get_file_error(File* file) { static void output_bytes_callback(void* ctx, uint8_t* got_bytes, size_t got_size) { RpcSessionContext* callbacks_context = ctx; - size_t bytes_sent = - xStreamBufferSend(callbacks_context->output_stream, got_bytes, got_size, FuriWaitForever); + size_t bytes_sent = furi_stream_buffer_send( + callbacks_context->output_stream, got_bytes, got_size, FuriWaitForever); (void)bytes_sent; furi_check(bytes_sent == got_size); } @@ -534,7 +533,8 @@ static bool test_rpc_pb_stream_read(pb_istream_t* istream, pb_byte_t* buf, size_ TickType_t now = xTaskGetTickCount(); int32_t time_left = session_context->timeout - now; time_left = MAX(time_left, 0); - bytes_received = xStreamBufferReceive(session_context->output_stream, buf, count, time_left); + bytes_received = + furi_stream_buffer_receive(session_context->output_stream, buf, count, time_left); return (count == bytes_received); } diff --git a/applications/main/gpio/usb_uart_bridge.c b/applications/main/gpio/usb_uart_bridge.c index 02f58ed1084..6e0bce736a5 100644 --- a/applications/main/gpio/usb_uart_bridge.c +++ b/applications/main/gpio/usb_uart_bridge.c @@ -1,6 +1,5 @@ #include "usb_uart_bridge.h" #include "furi_hal.h" -#include #include #include "usb_cdc.h" #include "cli/cli_vcp.h" @@ -43,7 +42,7 @@ struct UsbUartBridge { FuriThread* thread; FuriThread* tx_thread; - StreamBufferHandle_t rx_stream; + FuriStreamBuffer* rx_stream; FuriMutex* usb_mutex; @@ -74,12 +73,10 @@ static int32_t usb_uart_tx_thread(void* context); static void usb_uart_on_irq_cb(UartIrqEvent ev, uint8_t data, void* context) { UsbUartBridge* usb_uart = (UsbUartBridge*)context; - BaseType_t xHigherPriorityTaskWoken = pdFALSE; if(ev == UartIrqEventRXNE) { - xStreamBufferSendFromISR(usb_uart->rx_stream, &data, 1, &xHigherPriorityTaskWoken); + furi_stream_buffer_send(usb_uart->rx_stream, &data, 1, 0); furi_thread_flags_set(furi_thread_get_id(usb_uart->thread), WorkerEvtRxDone); - portYIELD_FROM_ISR(xHigherPriorityTaskWoken); } } @@ -156,7 +153,7 @@ static int32_t usb_uart_worker(void* context) { memcpy(&usb_uart->cfg, &usb_uart->cfg_new, sizeof(UsbUartConfig)); - usb_uart->rx_stream = xStreamBufferCreate(USB_UART_RX_BUF_SIZE, 1); + usb_uart->rx_stream = furi_stream_buffer_alloc(USB_UART_RX_BUF_SIZE, 1); usb_uart->tx_sem = furi_semaphore_alloc(1, 1); usb_uart->usb_mutex = furi_mutex_alloc(FuriMutexTypeNormal); @@ -189,8 +186,8 @@ static int32_t usb_uart_worker(void* context) { furi_check((events & FuriFlagError) == 0); if(events & WorkerEvtStop) break; if(events & WorkerEvtRxDone) { - size_t len = - xStreamBufferReceive(usb_uart->rx_stream, usb_uart->rx_buf, USB_CDC_PKT_LEN, 0); + size_t len = furi_stream_buffer_receive( + usb_uart->rx_stream, usb_uart->rx_buf, USB_CDC_PKT_LEN, 0); if(len > 0) { if(furi_semaphore_acquire(usb_uart->tx_sem, 100) == FuriStatusOk) { usb_uart->st.rx_cnt += len; @@ -199,7 +196,7 @@ static int32_t usb_uart_worker(void* context) { furi_hal_cdc_send(usb_uart->cfg.vcp_ch, usb_uart->rx_buf, len); furi_check(furi_mutex_release(usb_uart->usb_mutex) == FuriStatusOk); } else { - xStreamBufferReset(usb_uart->rx_stream); + furi_stream_buffer_reset(usb_uart->rx_stream); } } } @@ -270,7 +267,7 @@ static int32_t usb_uart_worker(void* context) { furi_thread_join(usb_uart->tx_thread); furi_thread_free(usb_uart->tx_thread); - vStreamBufferDelete(usb_uart->rx_stream); + furi_stream_buffer_free(usb_uart->rx_stream); furi_mutex_free(usb_uart->usb_mutex); furi_semaphore_free(usb_uart->tx_sem); diff --git a/applications/main/subghz/subghz_cli.c b/applications/main/subghz/subghz_cli.c index c6e196fb669..7fa0f454608 100644 --- a/applications/main/subghz/subghz_cli.c +++ b/applications/main/subghz/subghz_cli.c @@ -2,7 +2,6 @@ #include #include -#include #include #include @@ -194,23 +193,21 @@ void subghz_cli_command_tx(Cli* cli, FuriString* args, void* context) { typedef struct { volatile bool overrun; - StreamBufferHandle_t stream; + FuriStreamBuffer* stream; size_t packet_count; } SubGhzCliCommandRx; static void subghz_cli_command_rx_capture_callback(bool level, uint32_t duration, void* context) { SubGhzCliCommandRx* instance = context; - BaseType_t xHigherPriorityTaskWoken = pdFALSE; LevelDuration level_duration = level_duration_make(level, duration); if(instance->overrun) { instance->overrun = false; level_duration = level_duration_reset(); } - size_t ret = xStreamBufferSendFromISR( - instance->stream, &level_duration, sizeof(LevelDuration), &xHigherPriorityTaskWoken); + size_t ret = + furi_stream_buffer_send(instance->stream, &level_duration, sizeof(LevelDuration), 0); if(sizeof(LevelDuration) != ret) instance->overrun = true; - portYIELD_FROM_ISR(xHigherPriorityTaskWoken); } static void subghz_cli_command_rx_callback( @@ -249,7 +246,8 @@ void subghz_cli_command_rx(Cli* cli, FuriString* args, void* context) { // Allocate context and buffers SubGhzCliCommandRx* instance = malloc(sizeof(SubGhzCliCommandRx)); - instance->stream = xStreamBufferCreate(sizeof(LevelDuration) * 1024, sizeof(LevelDuration)); + instance->stream = + furi_stream_buffer_alloc(sizeof(LevelDuration) * 1024, sizeof(LevelDuration)); furi_check(instance->stream); SubGhzEnvironment* environment = subghz_environment_alloc(); @@ -279,8 +277,8 @@ void subghz_cli_command_rx(Cli* cli, FuriString* args, void* context) { printf("Listening at %lu. Press CTRL+C to stop\r\n", frequency); LevelDuration level_duration; while(!cli_cmd_interrupt_received(cli)) { - int ret = - xStreamBufferReceive(instance->stream, &level_duration, sizeof(LevelDuration), 10); + int ret = furi_stream_buffer_receive( + instance->stream, &level_duration, sizeof(LevelDuration), 10); if(ret == sizeof(LevelDuration)) { if(level_duration_is_reset(level_duration)) { printf("."); @@ -304,7 +302,7 @@ void subghz_cli_command_rx(Cli* cli, FuriString* args, void* context) { // Cleanup subghz_receiver_free(receiver); subghz_environment_free(environment); - vStreamBufferDelete(instance->stream); + furi_stream_buffer_free(instance->stream); free(instance); } diff --git a/applications/services/cli/cli_commands.c b/applications/services/cli/cli_commands.c index cee2ca98cc9..239434b7a0e 100644 --- a/applications/services/cli/cli_commands.c +++ b/applications/services/cli/cli_commands.c @@ -7,7 +7,6 @@ #include #include #include -#include // Close to ISO, `date +'%Y-%m-%d %H:%M:%S %u'` #define CLI_DATE_FORMAT "%.4d-%.2d-%.2d %.2d:%.2d:%.2d %d" @@ -140,7 +139,7 @@ void cli_command_date(Cli* cli, FuriString* args, void* context) { #define CLI_COMMAND_LOG_BUFFER_SIZE 64 void cli_command_log_tx_callback(const uint8_t* buffer, size_t size, void* context) { - xStreamBufferSend(context, buffer, size, 0); + furi_stream_buffer_send(context, buffer, size, 0); } void cli_command_log_level_set_from_string(FuriString* level) { @@ -165,7 +164,7 @@ void cli_command_log_level_set_from_string(FuriString* level) { void cli_command_log(Cli* cli, FuriString* args, void* context) { UNUSED(context); - StreamBufferHandle_t ring = xStreamBufferCreate(CLI_COMMAND_LOG_RING_SIZE, 1); + FuriStreamBuffer* ring = furi_stream_buffer_alloc(CLI_COMMAND_LOG_RING_SIZE, 1); uint8_t buffer[CLI_COMMAND_LOG_BUFFER_SIZE]; FuriLogLevel previous_level = furi_log_get_level(); bool restore_log_level = false; @@ -179,7 +178,7 @@ void cli_command_log(Cli* cli, FuriString* args, void* context) { printf("Press CTRL+C to stop...\r\n"); while(!cli_cmd_interrupt_received(cli)) { - size_t ret = xStreamBufferReceive(ring, buffer, CLI_COMMAND_LOG_BUFFER_SIZE, 50); + size_t ret = furi_stream_buffer_receive(ring, buffer, CLI_COMMAND_LOG_BUFFER_SIZE, 50); cli_write(cli, buffer, ret); } @@ -190,7 +189,7 @@ void cli_command_log(Cli* cli, FuriString* args, void* context) { furi_log_set_level(previous_level); } - vStreamBufferDelete(ring); + furi_stream_buffer_free(ring); } void cli_command_vibro(Cli* cli, FuriString* args, void* context) { diff --git a/applications/services/cli/cli_vcp.c b/applications/services/cli/cli_vcp.c index f2893a48b85..1e27e185b65 100644 --- a/applications/services/cli/cli_vcp.c +++ b/applications/services/cli/cli_vcp.c @@ -1,7 +1,6 @@ #include #include #include -#include #include "cli_i.h" #define TAG "CliVcp" @@ -29,8 +28,8 @@ typedef enum { typedef struct { FuriThread* thread; - StreamBufferHandle_t tx_stream; - StreamBufferHandle_t rx_stream; + FuriStreamBuffer* tx_stream; + FuriStreamBuffer* rx_stream; volatile bool connected; volatile bool running; @@ -62,8 +61,8 @@ static const uint8_t ascii_eot = 0x04; static void cli_vcp_init() { if(vcp == NULL) { vcp = malloc(sizeof(CliVcp)); - vcp->tx_stream = xStreamBufferCreate(VCP_TX_BUF_SIZE, 1); - vcp->rx_stream = xStreamBufferCreate(VCP_RX_BUF_SIZE, 1); + vcp->tx_stream = furi_stream_buffer_alloc(VCP_TX_BUF_SIZE, 1); + vcp->rx_stream = furi_stream_buffer_alloc(VCP_RX_BUF_SIZE, 1); } furi_assert(vcp->thread == NULL); @@ -113,7 +112,7 @@ static int32_t vcp_worker(void* context) { #endif if(vcp->connected == false) { vcp->connected = true; - xStreamBufferSend(vcp->rx_stream, &ascii_soh, 1, FuriWaitForever); + furi_stream_buffer_send(vcp->rx_stream, &ascii_soh, 1, FuriWaitForever); } } @@ -124,8 +123,8 @@ static int32_t vcp_worker(void* context) { #endif if(vcp->connected == true) { vcp->connected = false; - xStreamBufferReceive(vcp->tx_stream, vcp->data_buffer, USB_CDC_PKT_LEN, 0); - xStreamBufferSend(vcp->rx_stream, &ascii_eot, 1, FuriWaitForever); + furi_stream_buffer_receive(vcp->tx_stream, vcp->data_buffer, USB_CDC_PKT_LEN, 0); + furi_stream_buffer_send(vcp->rx_stream, &ascii_eot, 1, FuriWaitForever); } } @@ -134,7 +133,7 @@ static int32_t vcp_worker(void* context) { #ifdef CLI_VCP_DEBUG FURI_LOG_D(TAG, "StreamRx"); #endif - if(xStreamBufferSpacesAvailable(vcp->rx_stream) >= USB_CDC_PKT_LEN) { + if(furi_stream_buffer_spaces_available(vcp->rx_stream) >= USB_CDC_PKT_LEN) { flags |= VcpEvtRx; missed_rx--; } @@ -142,14 +141,15 @@ static int32_t vcp_worker(void* context) { // New data received if(flags & VcpEvtRx) { - if(xStreamBufferSpacesAvailable(vcp->rx_stream) >= USB_CDC_PKT_LEN) { + if(furi_stream_buffer_spaces_available(vcp->rx_stream) >= USB_CDC_PKT_LEN) { int32_t len = furi_hal_cdc_receive(VCP_IF_NUM, vcp->data_buffer, USB_CDC_PKT_LEN); #ifdef CLI_VCP_DEBUG FURI_LOG_D(TAG, "Rx %d", len); #endif if(len > 0) { furi_check( - xStreamBufferSend(vcp->rx_stream, vcp->data_buffer, len, FuriWaitForever) == + furi_stream_buffer_send( + vcp->rx_stream, vcp->data_buffer, len, FuriWaitForever) == (size_t)len); } } else { @@ -173,7 +173,7 @@ static int32_t vcp_worker(void* context) { // CDC write transfer done if(flags & VcpEvtTx) { size_t len = - xStreamBufferReceive(vcp->tx_stream, vcp->data_buffer, USB_CDC_PKT_LEN, 0); + furi_stream_buffer_receive(vcp->tx_stream, vcp->data_buffer, USB_CDC_PKT_LEN, 0); #ifdef CLI_VCP_DEBUG FURI_LOG_D(TAG, "Tx %d", len); #endif @@ -202,8 +202,8 @@ static int32_t vcp_worker(void* context) { furi_hal_usb_unlock(); furi_hal_usb_set_config(vcp->usb_if_prev, NULL); } - xStreamBufferReceive(vcp->tx_stream, vcp->data_buffer, USB_CDC_PKT_LEN, 0); - xStreamBufferSend(vcp->rx_stream, &ascii_eot, 1, FuriWaitForever); + furi_stream_buffer_receive(vcp->tx_stream, vcp->data_buffer, USB_CDC_PKT_LEN, 0); + furi_stream_buffer_send(vcp->rx_stream, &ascii_eot, 1, FuriWaitForever); break; } } @@ -229,7 +229,7 @@ static size_t cli_vcp_rx(uint8_t* buffer, size_t size, uint32_t timeout) { size_t batch_size = size; if(batch_size > VCP_RX_BUF_SIZE) batch_size = VCP_RX_BUF_SIZE; - size_t len = xStreamBufferReceive(vcp->rx_stream, buffer, batch_size, timeout); + size_t len = furi_stream_buffer_receive(vcp->rx_stream, buffer, batch_size, timeout); #ifdef CLI_VCP_DEBUG FURI_LOG_D(TAG, "rx %u ", batch_size); #endif @@ -262,7 +262,7 @@ static void cli_vcp_tx(const uint8_t* buffer, size_t size) { size_t batch_size = size; if(batch_size > USB_CDC_PKT_LEN) batch_size = USB_CDC_PKT_LEN; - xStreamBufferSend(vcp->tx_stream, buffer, batch_size, FuriWaitForever); + furi_stream_buffer_send(vcp->tx_stream, buffer, batch_size, FuriWaitForever); furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtStreamTx); #ifdef CLI_VCP_DEBUG FURI_LOG_D(TAG, "tx %u", batch_size); diff --git a/applications/services/rpc/rpc.c b/applications/services/rpc/rpc.c index 4e8c29b443b..06c05173c39 100644 --- a/applications/services/rpc/rpc.c +++ b/applications/services/rpc/rpc.c @@ -13,7 +13,6 @@ #include #include #include -#include #include #define TAG "RpcSrv" @@ -61,7 +60,7 @@ struct RpcSession { FuriThread* thread; RpcHandlerDict_t handlers; - StreamBufferHandle_t stream; + FuriStreamBuffer* stream; PB_Main* decoded_message; bool terminate; void** system_contexts; @@ -151,7 +150,7 @@ size_t furi_assert(encoded_bytes); furi_assert(size > 0); - size_t bytes_sent = xStreamBufferSend(session->stream, encoded_bytes, size, timeout); + size_t bytes_sent = furi_stream_buffer_send(session->stream, encoded_bytes, size, timeout); furi_thread_flags_set(furi_thread_get_id(session->thread), RpcEvtNewData); @@ -160,7 +159,7 @@ size_t size_t rpc_session_get_available_size(RpcSession* session) { furi_assert(session); - return xStreamBufferSpacesAvailable(session->stream); + return furi_stream_buffer_spaces_available(session->stream); } bool rpc_pb_stream_read(pb_istream_t* istream, pb_byte_t* buf, size_t count) { @@ -174,9 +173,9 @@ bool rpc_pb_stream_read(pb_istream_t* istream, pb_byte_t* buf, size_t count) { size_t bytes_received = 0; while(1) { - bytes_received += - xStreamBufferReceive(session->stream, buf + bytes_received, count - bytes_received, 0); - if(xStreamBufferIsEmpty(session->stream)) { + bytes_received += furi_stream_buffer_receive( + session->stream, buf + bytes_received, count - bytes_received, 0); + if(furi_stream_buffer_is_empty(session->stream)) { if(session->buffer_is_empty_callback) { session->buffer_is_empty_callback(session->context); } @@ -190,7 +189,7 @@ bool rpc_pb_stream_read(pb_istream_t* istream, pb_byte_t* buf, size_t count) { } else { flags = furi_thread_flags_wait(RPC_ALL_EVENTS, FuriFlagWaitAny, FuriWaitForever); if(flags & RpcEvtDisconnect) { - if(xStreamBufferIsEmpty(session->stream)) { + if(furi_stream_buffer_is_empty(session->stream)) { session->terminate = true; istream->bytes_left = 0; bytes_received = 0; @@ -279,7 +278,7 @@ static int32_t rpc_session_worker(void* context) { } if(message_decode_failed) { - xStreamBufferReset(session->stream); + furi_stream_buffer_reset(session->stream); if(!session->terminate) { /* Protobuf can't determine start and end of message. * Handle this by adding varint at beginning @@ -329,7 +328,7 @@ static void rpc_session_free_callback(FuriThreadState thread_state, void* contex free(session->system_contexts); free(session->decoded_message); RpcHandlerDict_clear(session->handlers); - vStreamBufferDelete(session->stream); + furi_stream_buffer_free(session->stream); furi_mutex_acquire(session->callbacks_mutex, FuriWaitForever); if(session->terminated_callback) { @@ -348,7 +347,7 @@ RpcSession* rpc_session_open(Rpc* rpc) { RpcSession* session = malloc(sizeof(RpcSession)); session->callbacks_mutex = furi_mutex_alloc(FuriMutexTypeNormal); - session->stream = xStreamBufferCreate(RPC_BUFFER_SIZE, 1); + session->stream = furi_stream_buffer_alloc(RPC_BUFFER_SIZE, 1); session->rpc = rpc; session->terminate = false; session->decode_error = false; diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 4e1704d061c..03bad58e65e 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,2.0,, +Version,+,2.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -1342,6 +1342,16 @@ Function,+,furi_semaphore_alloc,FuriSemaphore*,"uint32_t, uint32_t" Function,+,furi_semaphore_free,void,FuriSemaphore* Function,+,furi_semaphore_get_count,uint32_t,FuriSemaphore* Function,+,furi_semaphore_release,FuriStatus,FuriSemaphore* +Function,+,furi_stream_buffer_alloc,FuriStreamBuffer*,"size_t, size_t" +Function,+,furi_stream_buffer_bytes_available,size_t,FuriStreamBuffer* +Function,+,furi_stream_buffer_free,void,FuriStreamBuffer* +Function,+,furi_stream_buffer_is_empty,_Bool,FuriStreamBuffer* +Function,+,furi_stream_buffer_is_full,_Bool,FuriStreamBuffer* +Function,+,furi_stream_buffer_receive,size_t,"FuriStreamBuffer*, void*, size_t, uint32_t" +Function,+,furi_stream_buffer_reset,FuriStatus,FuriStreamBuffer* +Function,+,furi_stream_buffer_send,size_t,"FuriStreamBuffer*, const void*, size_t, uint32_t" +Function,+,furi_stream_buffer_spaces_available,size_t,FuriStreamBuffer* +Function,+,furi_stream_set_trigger_level,_Bool,"FuriStreamBuffer*, size_t" Function,+,furi_string_alloc,FuriString*, Function,+,furi_string_alloc_move,FuriString*,FuriString* Function,+,furi_string_alloc_printf,FuriString*,"const char[], ..." diff --git a/furi/core/stream_buffer.c b/furi/core/stream_buffer.c new file mode 100644 index 00000000000..b9d0629fe03 --- /dev/null +++ b/furi/core/stream_buffer.c @@ -0,0 +1,77 @@ +#include "base.h" +#include "stream_buffer.h" +#include "common_defines.h" +#include +#include + +FuriStreamBuffer* furi_stream_buffer_alloc(size_t size, size_t trigger_level) { + return xStreamBufferCreate(size, trigger_level); +}; + +void furi_stream_buffer_free(FuriStreamBuffer* stream_buffer) { + vStreamBufferDelete(stream_buffer); +}; + +bool furi_stream_set_trigger_level(FuriStreamBuffer* stream_buffer, size_t trigger_level) { + return xStreamBufferSetTriggerLevel(stream_buffer, trigger_level) == pdTRUE; +}; + +size_t furi_stream_buffer_send( + FuriStreamBuffer* stream_buffer, + const void* data, + size_t length, + uint32_t timeout) { + size_t ret; + + if(FURI_IS_IRQ_MODE() != 0U) { + BaseType_t yield; + ret = xStreamBufferSendFromISR(stream_buffer, data, length, &yield); + portYIELD_FROM_ISR(yield); + } else { + ret = xStreamBufferSend(stream_buffer, data, length, timeout); + } + + return ret; +}; + +size_t furi_stream_buffer_receive( + FuriStreamBuffer* stream_buffer, + void* data, + size_t length, + uint32_t timeout) { + size_t ret; + + if(FURI_IS_IRQ_MODE() != 0U) { + BaseType_t yield; + ret = xStreamBufferReceiveFromISR(stream_buffer, data, length, &yield); + portYIELD_FROM_ISR(yield); + } else { + ret = xStreamBufferReceive(stream_buffer, data, length, timeout); + } + + return ret; +} + +size_t furi_stream_buffer_bytes_available(FuriStreamBuffer* stream_buffer) { + return xStreamBufferBytesAvailable(stream_buffer); +}; + +size_t furi_stream_buffer_spaces_available(FuriStreamBuffer* stream_buffer) { + return xStreamBufferSpacesAvailable(stream_buffer); +}; + +bool furi_stream_buffer_is_full(FuriStreamBuffer* stream_buffer) { + return xStreamBufferIsFull(stream_buffer) == pdTRUE; +}; + +bool furi_stream_buffer_is_empty(FuriStreamBuffer* stream_buffer) { + return (xStreamBufferIsEmpty(stream_buffer) == pdTRUE); +}; + +FuriStatus furi_stream_buffer_reset(FuriStreamBuffer* stream_buffer) { + if(xStreamBufferReset(stream_buffer) == pdPASS) { + return FuriStatusOk; + } else { + return FuriStatusError; + } +} \ No newline at end of file diff --git a/furi/core/stream_buffer.h b/furi/core/stream_buffer.h new file mode 100644 index 00000000000..d07f7e60ba3 --- /dev/null +++ b/furi/core/stream_buffer.h @@ -0,0 +1,152 @@ +/** + * @file stream_buffer.h + * Furi stream buffer primitive. + * + * Stream buffers are used to send a continuous stream of data from one task or + * interrupt to another. Their implementation is light weight, making them + * particularly suited for interrupt to task and core to core communication + * scenarios. + * + * ***NOTE***: Stream buffer implementation assumes there is only one task or + * interrupt that will write to the buffer (the writer), and only one task or + * interrupt that will read from the buffer (the reader). + */ +#pragma once +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void FuriStreamBuffer; + +/** + * @brief Allocate stream buffer instance. + * Stream buffer implementation assumes there is only one task or + * interrupt that will write to the buffer (the writer), and only one task or + * interrupt that will read from the buffer (the reader). + * + * @param size The total number of bytes the stream buffer will be able to hold at any one time. + * @param trigger_level The number of bytes that must be in the stream buffer + * before a task that is blocked on the stream buffer to wait for data is moved out of the blocked state. + * @return The stream buffer instance. + */ +FuriStreamBuffer* furi_stream_buffer_alloc(size_t size, size_t trigger_level); + +/** + * @brief Free stream buffer instance + * + * @param stream_buffer The stream buffer instance. + */ +void furi_stream_buffer_free(FuriStreamBuffer* stream_buffer); + +/** + * @brief Set trigger level for stream buffer. + * A stream buffer's trigger level is the number of bytes that must be in the + * stream buffer before a task that is blocked on the stream buffer to + * wait for data is moved out of the blocked state. + * + * @param stream_buffer The stream buffer instance + * @param trigger_level The new trigger level for the stream buffer. + * @return true if trigger level can be be updated (new trigger level was less than or equal to the stream buffer's length). + * @return false if trigger level can't be be updated (new trigger level was greater than the stream buffer's length). + */ +bool furi_stream_set_trigger_level(FuriStreamBuffer* stream_buffer, size_t trigger_level); + +/** + * @brief Sends bytes to a stream buffer. The bytes are copied into the stream buffer. + * Wakes up task waiting for data to become available if called from ISR. + * + * @param stream_buffer The stream buffer instance. + * @param data A pointer to the data that is to be copied into the stream buffer. + * @param length The maximum number of bytes to copy from data into the stream buffer. + * @param timeout The maximum amount of time the task should remain in the + * Blocked state to wait for space to become available if the stream buffer is full. + * Will return immediately if timeout is zero. + * Setting timeout to FuriWaitForever will cause the task to wait indefinitely. + * Ignored if called from ISR. + * @return The number of bytes actually written to the stream buffer. + */ +size_t furi_stream_buffer_send( + FuriStreamBuffer* stream_buffer, + const void* data, + size_t length, + uint32_t timeout); + +/** + * @brief Receives bytes from a stream buffer. + * Wakes up task waiting for space to become available if called from ISR. + * + * @param stream_buffer The stream buffer instance. + * @param data A pointer to the buffer into which the received bytes will be + * copied. + * @param length The length of the buffer pointed to by the data parameter. + * @param timeout The maximum amount of time the task should remain in the + * Blocked state to wait for data to become available if the stream buffer is empty. + * Will return immediately if timeout is zero. + * Setting timeout to FuriWaitForever will cause the task to wait indefinitely. + * Ignored if called from ISR. + * @return The number of bytes read from the stream buffer, if any. + */ +size_t furi_stream_buffer_receive( + FuriStreamBuffer* stream_buffer, + void* data, + size_t length, + uint32_t timeout); + +/** + * @brief Queries a stream buffer to see how much data it contains, which is equal to + * the number of bytes that can be read from the stream buffer before the stream + * buffer would be empty. + * + * @param stream_buffer The stream buffer instance. + * @return The number of bytes that can be read from the stream buffer before + * the stream buffer would be empty. + */ +size_t furi_stream_buffer_bytes_available(FuriStreamBuffer* stream_buffer); + +/** + * @brief Queries a stream buffer to see how much free space it contains, which is + * equal to the amount of data that can be sent to the stream buffer before it + * is full. + * + * @param stream_buffer The stream buffer instance. + * @return The number of bytes that can be written to the stream buffer before + * the stream buffer would be full. + */ +size_t furi_stream_buffer_spaces_available(FuriStreamBuffer* stream_buffer); + +/** + * @brief Queries a stream buffer to see if it is full. + * + * @param stream_buffer stream buffer instance. + * @return true if the stream buffer is full. + * @return false if the stream buffer is not full. + */ +bool furi_stream_buffer_is_full(FuriStreamBuffer* stream_buffer); + +/** + * @brief Queries a stream buffer to see if it is empty. + * + * @param stream_buffer The stream buffer instance. + * @return true if the stream buffer is empty. + * @return false if the stream buffer is not empty. + */ +bool furi_stream_buffer_is_empty(FuriStreamBuffer* stream_buffer); + +/** + * @brief Resets a stream buffer to its initial, empty, state. Any data that was + * in the stream buffer is discarded. A stream buffer can only be reset if there + * are no tasks blocked waiting to either send to or receive from the stream buffer. + * + * @param stream_buffer The stream buffer instance. + * @return FuriStatusOk if the stream buffer is reset. + * @return FuriStatusError if there was a task blocked waiting to send to or read + * from the stream buffer then the stream buffer is not reset. + */ +FuriStatus furi_stream_buffer_reset(FuriStreamBuffer* stream_buffer); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/furi/furi.h b/furi/furi.h index 306c9b949eb..3ce83422743 100644 --- a/furi/furi.h +++ b/furi/furi.h @@ -2,22 +2,23 @@ #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "core/check.h" +#include "core/common_defines.h" +#include "core/event_flag.h" +#include "core/kernel.h" +#include "core/log.h" +#include "core/memmgr.h" +#include "core/memmgr_heap.h" +#include "core/message_queue.h" +#include "core/mutex.h" +#include "core/pubsub.h" +#include "core/record.h" +#include "core/semaphore.h" +#include "core/thread.h" +#include "core/timer.h" +#include "core/valuemutex.h" +#include "core/string.h" +#include "core/stream_buffer.h" #include diff --git a/lib/infrared/worker/infrared_worker.c b/lib/infrared/worker/infrared_worker.c index 2b4e3cdbbf1..86b19114c16 100644 --- a/lib/infrared/worker/infrared_worker.c +++ b/lib/infrared/worker/infrared_worker.c @@ -9,7 +9,6 @@ #include #include -#include #define INFRARED_WORKER_RX_TIMEOUT INFRARED_RAW_RX_TIMING_DELAY_US @@ -50,7 +49,7 @@ struct InfraredWorkerSignal { struct InfraredWorker { FuriThread* thread; - StreamBufferHandle_t stream; + FuriStreamBuffer* stream; InfraredWorkerSignal signal; InfraredWorkerState state; @@ -100,15 +99,13 @@ static void infrared_worker_rx_timeout_callback(void* context) { static void infrared_worker_rx_callback(void* context, bool level, uint32_t duration) { InfraredWorker* instance = context; - BaseType_t xHigherPriorityTaskWoken = pdFALSE; furi_assert(duration != 0); LevelDuration level_duration = level_duration_make(level, duration); - size_t ret = xStreamBufferSendFromISR( - instance->stream, &level_duration, sizeof(LevelDuration), &xHigherPriorityTaskWoken); + size_t ret = + furi_stream_buffer_send(instance->stream, &level_duration, sizeof(LevelDuration), 0); uint32_t events = (ret == sizeof(LevelDuration)) ? INFRARED_WORKER_RX_RECEIVED : INFRARED_WORKER_OVERRUN; - portYIELD_FROM_ISR(xHigherPriorityTaskWoken); uint32_t flags_set = furi_thread_flags_set(furi_thread_get_id(instance->thread), events); furi_check(flags_set & events); @@ -179,7 +176,7 @@ static int32_t infrared_worker_rx_thread(void* thread_context) { if(instance->signal.timings_cnt == 0) notification_message(instance->notification, &sequence_display_backlight_on); while(sizeof(LevelDuration) == - xStreamBufferReceive( + furi_stream_buffer_receive( instance->stream, &level_duration, sizeof(LevelDuration), 0)) { if(!instance->rx.overrun) { bool level = level_duration_get_level(level_duration); @@ -232,7 +229,7 @@ InfraredWorker* infrared_worker_alloc() { size_t buffer_size = MAX(sizeof(InfraredWorkerTiming) * (MAX_TIMINGS_AMOUNT + 1), sizeof(LevelDuration) * MAX_TIMINGS_AMOUNT); - instance->stream = xStreamBufferCreate(buffer_size, sizeof(InfraredWorkerTiming)); + instance->stream = furi_stream_buffer_alloc(buffer_size, sizeof(InfraredWorkerTiming)); instance->infrared_decoder = infrared_alloc_decoder(); instance->infrared_encoder = infrared_alloc_encoder(); instance->blink_enable = false; @@ -249,7 +246,7 @@ void infrared_worker_free(InfraredWorker* instance) { furi_record_close(RECORD_NOTIFICATION); infrared_free_decoder(instance->infrared_decoder); infrared_free_encoder(instance->infrared_encoder); - vStreamBufferDelete(instance->stream); + furi_stream_buffer_free(instance->stream); furi_thread_free(instance->thread); free(instance); @@ -259,7 +256,7 @@ void infrared_worker_rx_start(InfraredWorker* instance) { furi_assert(instance); furi_assert(instance->state == InfraredWorkerStateIdle); - xStreamBufferSetTriggerLevel(instance->stream, sizeof(LevelDuration)); + furi_stream_set_trigger_level(instance->stream, sizeof(LevelDuration)); furi_thread_set_callback(instance->thread, infrared_worker_rx_thread); furi_thread_start(instance->thread); @@ -285,9 +282,9 @@ void infrared_worker_rx_stop(InfraredWorker* instance) { furi_thread_flags_set(furi_thread_get_id(instance->thread), INFRARED_WORKER_EXIT); furi_thread_join(instance->thread); - BaseType_t xReturn = xStreamBufferReset(instance->stream); - furi_assert(xReturn == pdPASS); - (void)xReturn; + FuriStatus status = furi_stream_buffer_reset(instance->stream); + furi_assert(status == FuriStatusOk); + (void)status; instance->state = InfraredWorkerStateIdle; } @@ -325,7 +322,7 @@ void infrared_worker_tx_start(InfraredWorker* instance) { furi_assert(instance->tx.get_signal_callback); // size have to be greater than api hal infrared async tx buffer size - xStreamBufferSetTriggerLevel(instance->stream, sizeof(InfraredWorkerTiming)); + furi_stream_set_trigger_level(instance->stream, sizeof(InfraredWorkerTiming)); furi_thread_set_callback(instance->thread, infrared_worker_tx_thread); @@ -358,7 +355,7 @@ static FuriHalInfraredTxGetDataState FuriHalInfraredTxGetDataState state; if(sizeof(InfraredWorkerTiming) == - xStreamBufferReceiveFromISR(instance->stream, &timing, sizeof(InfraredWorkerTiming), 0)) { + furi_stream_buffer_receive(instance->stream, &timing, sizeof(InfraredWorkerTiming), 0)) { *level = timing.level; *duration = timing.duration; state = timing.state; @@ -420,7 +417,7 @@ static bool infrared_worker_tx_fill_buffer(InfraredWorker* instance) { InfraredWorkerTiming timing; InfraredStatus status = InfraredStatusError; - while(!xStreamBufferIsFull(instance->stream) && !instance->tx.need_reinitialization && + while(!furi_stream_buffer_is_full(instance->stream) && !instance->tx.need_reinitialization && new_data_available) { if(instance->signal.decoded) { status = infrared_encode(instance->infrared_encoder, &timing.duration, &timing.level); @@ -454,7 +451,7 @@ static bool infrared_worker_tx_fill_buffer(InfraredWorker* instance) { furi_assert(0); } uint32_t written_size = - xStreamBufferSend(instance->stream, &timing, sizeof(InfraredWorkerTiming), 0); + furi_stream_buffer_send(instance->stream, &timing, sizeof(InfraredWorkerTiming), 0); furi_assert(sizeof(InfraredWorkerTiming) == written_size); (void)written_size; } @@ -564,10 +561,9 @@ void infrared_worker_tx_stop(InfraredWorker* instance) { furi_hal_infrared_async_tx_set_signal_sent_isr_callback(NULL, NULL); instance->signal.timings_cnt = 0; - BaseType_t xReturn = pdFAIL; - xReturn = xStreamBufferReset(instance->stream); - furi_assert(xReturn == pdPASS); - (void)xReturn; + FuriStatus status = furi_stream_buffer_reset(instance->stream); + furi_assert(status == FuriStatusOk); + (void)status; instance->state = InfraredWorkerStateIdle; } diff --git a/lib/lfrfid/lfrfid_raw_worker.c b/lib/lfrfid/lfrfid_raw_worker.c index b277bbd3488..9bab77db1dd 100644 --- a/lib/lfrfid/lfrfid_raw_worker.c +++ b/lib/lfrfid/lfrfid_raw_worker.c @@ -2,7 +2,6 @@ #include #include #include -#include #include "lfrfid_raw_worker.h" #include "lfrfid_raw_file.h" #include "tools/varint_pair.h" @@ -16,7 +15,7 @@ // emulate mode typedef struct { size_t overrun_count; - StreamBufferHandle_t stream; + FuriStreamBuffer* stream; } RfidEmulateCtx; typedef struct { @@ -126,20 +125,13 @@ void lfrfid_raw_worker_stop(LFRFIDRawWorker* worker) { static void lfrfid_raw_worker_capture(bool level, uint32_t duration, void* context) { LFRFIDRawWorkerReadData* ctx = context; - BaseType_t xHigherPriorityTaskWoken = pdFALSE; - bool need_to_send = varint_pair_pack(ctx->pair, level, duration); if(need_to_send) { buffer_stream_send_from_isr( - ctx->stream, - varint_pair_get_data(ctx->pair), - varint_pair_get_size(ctx->pair), - &xHigherPriorityTaskWoken); + ctx->stream, varint_pair_get_data(ctx->pair), varint_pair_get_size(ctx->pair)); varint_pair_reset(ctx->pair); } - - portYIELD_FROM_ISR(xHigherPriorityTaskWoken); } static int32_t lfrfid_raw_read_worker_thread(void* thread_context) { @@ -236,7 +228,7 @@ static void rfid_emulate_dma_isr(bool half, void* context) { RfidEmulateCtx* ctx = context; uint32_t flag = half ? HalfTransfer : TransferComplete; - size_t len = xStreamBufferSendFromISR(ctx->stream, &flag, sizeof(uint32_t), pdFALSE); + size_t len = furi_stream_buffer_send(ctx->stream, &flag, sizeof(uint32_t), 0); if(len != sizeof(uint32_t)) { ctx->overrun_count++; } @@ -251,7 +243,7 @@ static int32_t lfrfid_raw_emulate_worker_thread(void* thread_context) { Storage* storage = furi_record_open(RECORD_STORAGE); data->ctx.overrun_count = 0; - data->ctx.stream = xStreamBufferCreate(sizeof(uint32_t), sizeof(uint32_t)); + data->ctx.stream = furi_stream_buffer_alloc(sizeof(uint32_t), sizeof(uint32_t)); LFRFIDRawFile* file = lfrfid_raw_file_alloc(storage); @@ -287,7 +279,8 @@ static int32_t lfrfid_raw_emulate_worker_thread(void* thread_context) { uint32_t flag = 0; while(true) { - size_t size = xStreamBufferReceive(data->ctx.stream, &flag, sizeof(uint32_t), 100); + size_t size = + furi_stream_buffer_receive(data->ctx.stream, &flag, sizeof(uint32_t), 100); if(size == sizeof(uint32_t)) { size_t start = 0; @@ -348,7 +341,7 @@ static int32_t lfrfid_raw_emulate_worker_thread(void* thread_context) { FURI_LOG_E(TAG_EMULATE, "overruns: %lu", data->ctx.overrun_count); } - vStreamBufferDelete(data->ctx.stream); + furi_stream_buffer_free(data->ctx.stream); lfrfid_raw_file_free(file); furi_record_close(RECORD_STORAGE); free(data); diff --git a/lib/lfrfid/lfrfid_worker_modes.c b/lib/lfrfid/lfrfid_worker_modes.c index 56447057661..1fbae04c81f 100644 --- a/lib/lfrfid/lfrfid_worker_modes.c +++ b/lib/lfrfid/lfrfid_worker_modes.c @@ -2,7 +2,6 @@ #include #include "lfrfid_worker_i.h" #include "tools/t5577.h" -#include #include #include #include "tools/varint_pair.h" @@ -81,17 +80,12 @@ static void lfrfid_worker_read_capture(bool level, uint32_t duration, void* cont furi_hal_gpio_write(LFRFID_WORKER_READ_DEBUG_GPIO_VALUE, level); #endif - BaseType_t xHigherPriorityTaskWoken = pdFALSE; bool need_to_send = varint_pair_pack(ctx->pair, level, duration); if(need_to_send) { buffer_stream_send_from_isr( - ctx->stream, - varint_pair_get_data(ctx->pair), - varint_pair_get_size(ctx->pair), - &xHigherPriorityTaskWoken); + ctx->stream, varint_pair_get_data(ctx->pair), varint_pair_get_size(ctx->pair)); varint_pair_reset(ctx->pair); } - portYIELD_FROM_ISR(xHigherPriorityTaskWoken); } typedef enum { @@ -407,14 +401,14 @@ typedef enum { } LFRFIDWorkerEmulateDMAEvent; static void lfrfid_worker_emulate_dma_isr(bool half, void* context) { - StreamBufferHandle_t stream = context; + FuriStreamBuffer* stream = context; uint32_t flag = half ? HalfTransfer : TransferComplete; - xStreamBufferSendFromISR(stream, &flag, sizeof(uint32_t), pdFALSE); + furi_stream_buffer_send(stream, &flag, sizeof(uint32_t), 0); } static void lfrfid_worker_mode_emulate_process(LFRFIDWorker* worker) { LFRFIDWorkerEmulateBuffer* buffer = malloc(sizeof(LFRFIDWorkerEmulateBuffer)); - StreamBufferHandle_t stream = xStreamBufferCreate(sizeof(uint32_t), sizeof(uint32_t)); + FuriStreamBuffer* stream = furi_stream_buffer_alloc(sizeof(uint32_t), sizeof(uint32_t)); LFRFIDProtocol protocol = worker->protocol; PulseGlue* pulse_glue = pulse_glue_alloc(); @@ -449,7 +443,7 @@ static void lfrfid_worker_mode_emulate_process(LFRFIDWorker* worker) { while(true) { uint32_t flag = 0; - size_t size = xStreamBufferReceive(stream, &flag, sizeof(uint32_t), 100); + size_t size = furi_stream_buffer_receive(stream, &flag, sizeof(uint32_t), 100); #ifdef LFRFID_WORKER_READ_DEBUG_GPIO furi_hal_gpio_write(LFRFID_WORKER_READ_DEBUG_GPIO_LOAD, true); @@ -497,7 +491,7 @@ static void lfrfid_worker_mode_emulate_process(LFRFIDWorker* worker) { #endif free(buffer); - vStreamBufferDelete(stream); + furi_stream_buffer_free(stream); pulse_glue_free(pulse_glue); } diff --git a/lib/nfc/helpers/reader_analyzer.c b/lib/nfc/helpers/reader_analyzer.c index 680b8cef9cd..0ba657a2eab 100644 --- a/lib/nfc/helpers/reader_analyzer.c +++ b/lib/nfc/helpers/reader_analyzer.c @@ -1,5 +1,4 @@ #include "reader_analyzer.h" -#include #include #include #include @@ -26,7 +25,7 @@ struct ReaderAnalyzer { FuriHalNfcDevData nfc_data; bool alive; - StreamBufferHandle_t stream; + FuriStreamBuffer* stream; FuriThread* thread; ReaderAnalyzerParseDataCallback callback; @@ -86,8 +85,8 @@ int32_t reader_analyzer_thread(void* context) { ReaderAnalyzer* reader_analyzer = context; uint8_t buffer[READER_ANALYZER_MAX_BUFF_SIZE] = {}; - while(reader_analyzer->alive || !xStreamBufferIsEmpty(reader_analyzer->stream)) { - size_t ret = xStreamBufferReceive( + while(reader_analyzer->alive || !furi_stream_buffer_is_empty(reader_analyzer->stream)) { + size_t ret = furi_stream_buffer_receive( reader_analyzer->stream, buffer, READER_ANALYZER_MAX_BUFF_SIZE, 50); if(ret) { reader_analyzer_parse(reader_analyzer, buffer, ret); @@ -103,7 +102,7 @@ ReaderAnalyzer* reader_analyzer_alloc() { instance->nfc_data = reader_analyzer_nfc_data[ReaderAnalyzerNfcDataMfClassic]; instance->alive = false; instance->stream = - xStreamBufferCreate(READER_ANALYZER_MAX_BUFF_SIZE, sizeof(ReaderAnalyzerHeader)); + furi_stream_buffer_alloc(READER_ANALYZER_MAX_BUFF_SIZE, sizeof(ReaderAnalyzerHeader)); instance->thread = furi_thread_alloc(); furi_thread_set_name(instance->thread, "ReaderAnalyzerWorker"); @@ -129,7 +128,7 @@ static void reader_analyzer_mfkey_callback(Mfkey32Event event, void* context) { void reader_analyzer_start(ReaderAnalyzer* instance, ReaderAnalyzerMode mode) { furi_assert(instance); - xStreamBufferReset(instance->stream); + furi_stream_buffer_reset(instance->stream); if(mode & ReaderAnalyzerModeDebugLog) { instance->debug_log = nfc_debug_log_alloc(); } @@ -171,7 +170,7 @@ void reader_analyzer_free(ReaderAnalyzer* instance) { reader_analyzer_stop(instance); furi_thread_free(instance->thread); - vStreamBufferDelete(instance->stream); + furi_stream_buffer_free(instance->stream); free(instance); } @@ -215,12 +214,12 @@ static void reader_analyzer_write( ReaderAnalyzerHeader header = { .reader_to_tag = reader_to_tag, .crc_dropped = crc_dropped, .len = len}; size_t data_sent = 0; - data_sent = xStreamBufferSend( + data_sent = furi_stream_buffer_send( instance->stream, &header, sizeof(ReaderAnalyzerHeader), FuriWaitForever); if(data_sent != sizeof(ReaderAnalyzerHeader)) { FURI_LOG_W(TAG, "Sent %d out of %d bytes", data_sent, sizeof(ReaderAnalyzerHeader)); } - data_sent = xStreamBufferSend(instance->stream, data, len, FuriWaitForever); + data_sent = furi_stream_buffer_send(instance->stream, data, len, FuriWaitForever); if(data_sent != len) { FURI_LOG_W(TAG, "Sent %d out of %d bytes", data_sent, len); } diff --git a/lib/one_wire/ibutton/ibutton_worker_modes.c b/lib/one_wire/ibutton/ibutton_worker_modes.c index d585e27f4dd..691aea9ee17 100644 --- a/lib/one_wire/ibutton/ibutton_worker_modes.c +++ b/lib/one_wire/ibutton/ibutton_worker_modes.c @@ -2,7 +2,6 @@ #include #include "ibutton_worker_i.h" #include "ibutton_key_command.h" -#include void ibutton_worker_mode_idle_start(iButtonWorker* worker); void ibutton_worker_mode_idle_tick(iButtonWorker* worker); @@ -65,7 +64,7 @@ void ibutton_worker_mode_idle_stop(iButtonWorker* worker) { typedef struct { uint32_t last_dwt_value; - StreamBufferHandle_t stream; + FuriStreamBuffer* stream; } iButtonReadContext; void ibutton_worker_comparator_callback(bool level, void* context) { @@ -75,7 +74,7 @@ void ibutton_worker_comparator_callback(bool level, void* context) { LevelDuration data = level_duration_make(level, current_dwt_value - read_context->last_dwt_value); - xStreamBufferSend(read_context->stream, &data, sizeof(LevelDuration), 0); + furi_stream_buffer_send(read_context->stream, &data, sizeof(LevelDuration), 0); read_context->last_dwt_value = current_dwt_value; } @@ -91,7 +90,7 @@ bool ibutton_worker_read_comparator(iButtonWorker* worker) { iButtonReadContext read_context = { .last_dwt_value = DWT->CYCCNT, - .stream = xStreamBufferCreate(sizeof(LevelDuration) * 512, 1), + .stream = furi_stream_buffer_alloc(sizeof(LevelDuration) * 512, 1), }; furi_hal_rfid_comp_set_callback(ibutton_worker_comparator_callback, &read_context); @@ -100,7 +99,8 @@ bool ibutton_worker_read_comparator(iButtonWorker* worker) { uint32_t tick_start = furi_get_tick(); while(true) { LevelDuration level; - size_t ret = xStreamBufferReceive(read_context.stream, &level, sizeof(LevelDuration), 100); + size_t ret = + furi_stream_buffer_receive(read_context.stream, &level, sizeof(LevelDuration), 100); if((furi_get_tick() - tick_start) > 100) { break; @@ -141,7 +141,7 @@ bool ibutton_worker_read_comparator(iButtonWorker* worker) { furi_hal_rfid_comp_set_callback(NULL, NULL); furi_hal_rfid_pins_reset(); - vStreamBufferDelete(read_context.stream); + furi_stream_buffer_free(read_context.stream); return result; } diff --git a/lib/subghz/subghz_file_encoder_worker.c b/lib/subghz/subghz_file_encoder_worker.c index 1b8e99f194f..29834b4122d 100644 --- a/lib/subghz/subghz_file_encoder_worker.c +++ b/lib/subghz/subghz_file_encoder_worker.c @@ -1,5 +1,4 @@ #include "subghz_file_encoder_worker.h" -#include #include #include @@ -11,7 +10,7 @@ struct SubGhzFileEncoderWorker { FuriThread* thread; - StreamBufferHandle_t stream; + FuriStreamBuffer* stream; Storage* storage; FlipperFormat* flipper_format; @@ -48,7 +47,7 @@ void subghz_file_encoder_worker_add_level_duration( if(res) { instance->level = !instance->level; - xStreamBufferSend(instance->stream, &duration, sizeof(int32_t), 100); + furi_stream_buffer_send(instance->stream, &duration, sizeof(int32_t), 100); } else { FURI_LOG_E(TAG, "Invalid level in the stream"); } @@ -83,10 +82,7 @@ LevelDuration subghz_file_encoder_worker_get_level_duration(void* context) { furi_assert(context); SubGhzFileEncoderWorker* instance = context; int32_t duration; - BaseType_t xHigherPriorityTaskWoken = pdFALSE; - int ret = xStreamBufferReceiveFromISR( - instance->stream, &duration, sizeof(int32_t), &xHigherPriorityTaskWoken); - portYIELD_FROM_ISR(xHigherPriorityTaskWoken); + int ret = furi_stream_buffer_receive(instance->stream, &duration, sizeof(int32_t), 0); if(ret == sizeof(int32_t)) { LevelDuration level_duration = {.level = LEVEL_DURATION_RESET}; if(duration < 0) { @@ -137,7 +133,7 @@ static int32_t subghz_file_encoder_worker_thread(void* context) { } while(0); while(res && instance->worker_running) { - size_t stream_free_byte = xStreamBufferSpacesAvailable(instance->stream); + size_t stream_free_byte = furi_stream_buffer_spaces_available(instance->stream); if((stream_free_byte / sizeof(int32_t)) >= SUBGHZ_FILE_ENCODER_LOAD) { if(stream_read_line(stream, instance->str_data)) { furi_string_trim(instance->str_data); @@ -183,7 +179,7 @@ SubGhzFileEncoderWorker* subghz_file_encoder_worker_alloc() { furi_thread_set_stack_size(instance->thread, 2048); furi_thread_set_context(instance->thread, instance); furi_thread_set_callback(instance->thread, subghz_file_encoder_worker_thread); - instance->stream = xStreamBufferCreate(sizeof(int32_t) * 2048, sizeof(int32_t)); + instance->stream = furi_stream_buffer_alloc(sizeof(int32_t) * 2048, sizeof(int32_t)); instance->storage = furi_record_open(RECORD_STORAGE); instance->flipper_format = flipper_format_file_alloc(instance->storage); @@ -199,7 +195,7 @@ SubGhzFileEncoderWorker* subghz_file_encoder_worker_alloc() { void subghz_file_encoder_worker_free(SubGhzFileEncoderWorker* instance) { furi_assert(instance); - vStreamBufferDelete(instance->stream); + furi_stream_buffer_free(instance->stream); furi_thread_free(instance->thread); furi_string_free(instance->str_data); @@ -215,7 +211,7 @@ bool subghz_file_encoder_worker_start(SubGhzFileEncoderWorker* instance, const c furi_assert(instance); furi_assert(!instance->worker_running); - xStreamBufferReset(instance->stream); + furi_stream_buffer_reset(instance->stream); furi_string_set(instance->file_path, file_path); instance->worker_running = true; furi_thread_start(instance->thread); diff --git a/lib/subghz/subghz_tx_rx_worker.c b/lib/subghz/subghz_tx_rx_worker.c index 78a18693155..37c0bfc5e96 100644 --- a/lib/subghz/subghz_tx_rx_worker.c +++ b/lib/subghz/subghz_tx_rx_worker.c @@ -1,6 +1,5 @@ #include "subghz_tx_rx_worker.h" -#include #include #define TAG "SubGhzTxRxWorker" @@ -13,8 +12,8 @@ struct SubGhzTxRxWorker { FuriThread* thread; - StreamBufferHandle_t stream_tx; - StreamBufferHandle_t stream_rx; + FuriStreamBuffer* stream_tx; + FuriStreamBuffer* stream_rx; volatile bool worker_running; volatile bool worker_stoping; @@ -30,9 +29,9 @@ struct SubGhzTxRxWorker { bool subghz_tx_rx_worker_write(SubGhzTxRxWorker* instance, uint8_t* data, size_t size) { furi_assert(instance); bool ret = false; - size_t stream_tx_free_byte = xStreamBufferSpacesAvailable(instance->stream_tx); + size_t stream_tx_free_byte = furi_stream_buffer_spaces_available(instance->stream_tx); if(size && (stream_tx_free_byte >= size)) { - if(xStreamBufferSend( + if(furi_stream_buffer_send( instance->stream_tx, data, size, SUBGHZ_TXRX_WORKER_TIMEOUT_READ_WRITE_BUF) == size) { ret = true; @@ -43,12 +42,12 @@ bool subghz_tx_rx_worker_write(SubGhzTxRxWorker* instance, uint8_t* data, size_t size_t subghz_tx_rx_worker_available(SubGhzTxRxWorker* instance) { furi_assert(instance); - return xStreamBufferBytesAvailable(instance->stream_rx); + return furi_stream_buffer_bytes_available(instance->stream_rx); } size_t subghz_tx_rx_worker_read(SubGhzTxRxWorker* instance, uint8_t* data, size_t size) { furi_assert(instance); - return xStreamBufferReceive(instance->stream_rx, data, size, 0); + return furi_stream_buffer_receive(instance->stream_rx, data, size, 0); } void subghz_tx_rx_worker_set_callback_have_read( @@ -148,11 +147,11 @@ static int32_t subghz_tx_rx_worker_thread(void* context) { while(instance->worker_running) { //transmit - size_tx = xStreamBufferBytesAvailable(instance->stream_tx); + size_tx = furi_stream_buffer_bytes_available(instance->stream_tx); if(size_tx > 0 && !timeout_tx) { timeout_tx = 10; //20ms if(size_tx > SUBGHZ_TXRX_WORKER_MAX_TXRX_SIZE) { - xStreamBufferReceive( + furi_stream_buffer_receive( instance->stream_tx, &data, SUBGHZ_TXRX_WORKER_MAX_TXRX_SIZE, @@ -160,20 +159,20 @@ static int32_t subghz_tx_rx_worker_thread(void* context) { subghz_tx_rx_worker_tx(instance, data, SUBGHZ_TXRX_WORKER_MAX_TXRX_SIZE); } else { //todo checking that he managed to write all the data to the TX buffer - xStreamBufferReceive( + furi_stream_buffer_receive( instance->stream_tx, &data, size_tx, SUBGHZ_TXRX_WORKER_TIMEOUT_READ_WRITE_BUF); subghz_tx_rx_worker_tx(instance, data, size_tx); } } else { //recive if(subghz_tx_rx_worker_rx(instance, data, size_rx)) { - if(xStreamBufferSpacesAvailable(instance->stream_rx) >= size_rx[0]) { + if(furi_stream_buffer_spaces_available(instance->stream_rx) >= size_rx[0]) { if(instance->callback_have_read && - xStreamBufferBytesAvailable(instance->stream_rx) == 0) { + furi_stream_buffer_bytes_available(instance->stream_rx) == 0) { callback_rx = true; } //todo checking that he managed to write all the data to the RX buffer - xStreamBufferSend( + furi_stream_buffer_send( instance->stream_rx, &data, size_rx[0], @@ -208,9 +207,9 @@ SubGhzTxRxWorker* subghz_tx_rx_worker_alloc() { furi_thread_set_context(instance->thread, instance); furi_thread_set_callback(instance->thread, subghz_tx_rx_worker_thread); instance->stream_tx = - xStreamBufferCreate(sizeof(uint8_t) * SUBGHZ_TXRX_WORKER_BUF_SIZE, sizeof(uint8_t)); + furi_stream_buffer_alloc(sizeof(uint8_t) * SUBGHZ_TXRX_WORKER_BUF_SIZE, sizeof(uint8_t)); instance->stream_rx = - xStreamBufferCreate(sizeof(uint8_t) * SUBGHZ_TXRX_WORKER_BUF_SIZE, sizeof(uint8_t)); + furi_stream_buffer_alloc(sizeof(uint8_t) * SUBGHZ_TXRX_WORKER_BUF_SIZE, sizeof(uint8_t)); instance->status = SubGhzTxRxWorkerStatusIDLE; instance->worker_stoping = true; @@ -221,8 +220,8 @@ SubGhzTxRxWorker* subghz_tx_rx_worker_alloc() { void subghz_tx_rx_worker_free(SubGhzTxRxWorker* instance) { furi_assert(instance); furi_assert(!instance->worker_running); - vStreamBufferDelete(instance->stream_tx); - vStreamBufferDelete(instance->stream_rx); + furi_stream_buffer_free(instance->stream_tx); + furi_stream_buffer_free(instance->stream_rx); furi_thread_free(instance->thread); free(instance); @@ -232,8 +231,8 @@ bool subghz_tx_rx_worker_start(SubGhzTxRxWorker* instance, uint32_t frequency) { furi_assert(instance); furi_assert(!instance->worker_running); bool res = false; - xStreamBufferReset(instance->stream_tx); - xStreamBufferReset(instance->stream_rx); + furi_stream_buffer_reset(instance->stream_tx); + furi_stream_buffer_reset(instance->stream_rx); instance->worker_running = true; diff --git a/lib/subghz/subghz_worker.c b/lib/subghz/subghz_worker.c index 58db8ea5d95..61146c168f5 100644 --- a/lib/subghz/subghz_worker.c +++ b/lib/subghz/subghz_worker.c @@ -1,13 +1,12 @@ #include "subghz_worker.h" -#include #include #define TAG "SubGhzWorker" struct SubGhzWorker { FuriThread* thread; - StreamBufferHandle_t stream; + FuriStreamBuffer* stream; volatile bool running; volatile bool overrun; @@ -30,16 +29,14 @@ struct SubGhzWorker { void subghz_worker_rx_callback(bool level, uint32_t duration, void* context) { SubGhzWorker* instance = context; - BaseType_t xHigherPriorityTaskWoken = pdFALSE; LevelDuration level_duration = level_duration_make(level, duration); if(instance->overrun) { instance->overrun = false; level_duration = level_duration_reset(); } - size_t ret = xStreamBufferSendFromISR( - instance->stream, &level_duration, sizeof(LevelDuration), &xHigherPriorityTaskWoken); + size_t ret = + furi_stream_buffer_send(instance->stream, &level_duration, sizeof(LevelDuration), 0); if(sizeof(LevelDuration) != ret) instance->overrun = true; - portYIELD_FROM_ISR(xHigherPriorityTaskWoken); } /** Worker callback thread @@ -52,8 +49,8 @@ static int32_t subghz_worker_thread_callback(void* context) { LevelDuration level_duration; while(instance->running) { - int ret = - xStreamBufferReceive(instance->stream, &level_duration, sizeof(LevelDuration), 10); + int ret = furi_stream_buffer_receive( + instance->stream, &level_duration, sizeof(LevelDuration), 10); if(ret == sizeof(LevelDuration)) { if(level_duration_is_reset(level_duration)) { FURI_LOG_E(TAG, "Overrun buffer"); @@ -97,7 +94,8 @@ SubGhzWorker* subghz_worker_alloc() { furi_thread_set_context(instance->thread, instance); furi_thread_set_callback(instance->thread, subghz_worker_thread_callback); - instance->stream = xStreamBufferCreate(sizeof(LevelDuration) * 4096, sizeof(LevelDuration)); + instance->stream = + furi_stream_buffer_alloc(sizeof(LevelDuration) * 4096, sizeof(LevelDuration)); //setting filter instance->filter_running = true; @@ -109,7 +107,7 @@ SubGhzWorker* subghz_worker_alloc() { void subghz_worker_free(SubGhzWorker* instance) { furi_assert(instance); - vStreamBufferDelete(instance->stream); + furi_stream_buffer_free(instance->stream); furi_thread_free(instance->thread); free(instance); diff --git a/lib/toolbox/buffer_stream.c b/lib/toolbox/buffer_stream.c index 66d2109630b..37b2514ef3d 100644 --- a/lib/toolbox/buffer_stream.c +++ b/lib/toolbox/buffer_stream.c @@ -1,5 +1,4 @@ #include "buffer_stream.h" -#include struct Buffer { volatile bool occupied; @@ -10,7 +9,7 @@ struct Buffer { struct BufferStream { size_t stream_overrun_count; - StreamBufferHandle_t stream; + FuriStreamBuffer* stream; size_t index; Buffer* buffers; @@ -54,7 +53,7 @@ BufferStream* buffer_stream_alloc(size_t buffer_size, size_t buffers_count) { buffer_stream->buffers[i].data = malloc(buffer_size); buffer_stream->buffers[i].max_data_size = buffer_size; } - buffer_stream->stream = xStreamBufferCreate( + buffer_stream->stream = furi_stream_buffer_alloc( sizeof(BufferStream*) * buffer_stream->max_buffers_count, sizeof(BufferStream*)); buffer_stream->stream_overrun_count = 0; buffer_stream->index = 0; @@ -66,7 +65,7 @@ void buffer_stream_free(BufferStream* buffer_stream) { for(size_t i = 0; i < buffer_stream->max_buffers_count; i++) { free(buffer_stream->buffers[i].data); } - vStreamBufferDelete(buffer_stream->stream); + furi_stream_buffer_free(buffer_stream->stream); free(buffer_stream->buffers); free(buffer_stream); } @@ -83,11 +82,7 @@ static inline int8_t buffer_stream_get_free_buffer(BufferStream* buffer_stream) return id; } -bool buffer_stream_send_from_isr( - BufferStream* buffer_stream, - const uint8_t* data, - size_t size, - BaseType_t* const task_woken) { +bool buffer_stream_send_from_isr(BufferStream* buffer_stream, const uint8_t* data, size_t size) { Buffer* buffer = &buffer_stream->buffers[buffer_stream->index]; bool result = true; @@ -96,7 +91,7 @@ bool buffer_stream_send_from_isr( // if buffer is full - send it buffer->occupied = true; // we always have space for buffer in stream - xStreamBufferSendFromISR(buffer_stream->stream, &buffer, sizeof(Buffer*), task_woken); + furi_stream_buffer_send(buffer_stream->stream, &buffer, sizeof(Buffer*), 0); // get new buffer from the pool int8_t index = buffer_stream_get_free_buffer(buffer_stream); @@ -119,7 +114,8 @@ bool buffer_stream_send_from_isr( Buffer* buffer_stream_receive(BufferStream* buffer_stream, TickType_t timeout) { Buffer* buffer; - size_t size = xStreamBufferReceive(buffer_stream->stream, &buffer, sizeof(Buffer*), timeout); + size_t size = + furi_stream_buffer_receive(buffer_stream->stream, &buffer, sizeof(Buffer*), timeout); if(size == sizeof(Buffer*)) { return buffer; @@ -134,9 +130,8 @@ size_t buffer_stream_get_overrun_count(BufferStream* buffer_stream) { void buffer_stream_reset(BufferStream* buffer_stream) { FURI_CRITICAL_ENTER(); - BaseType_t xReturn = xStreamBufferReset(buffer_stream->stream); - furi_assert(xReturn == pdPASS); - UNUSED(xReturn); + furi_stream_buffer_reset(buffer_stream->stream); + buffer_stream->stream_overrun_count = 0; for(size_t i = 0; i < buffer_stream->max_buffers_count; i++) { buffer_reset(&buffer_stream->buffers[i]); diff --git a/lib/toolbox/buffer_stream.h b/lib/toolbox/buffer_stream.h index d4c3cddf7cc..9db54775322 100644 --- a/lib/toolbox/buffer_stream.h +++ b/lib/toolbox/buffer_stream.h @@ -59,14 +59,9 @@ void buffer_stream_free(BufferStream* buffer_stream); * @param buffer_stream * @param data * @param size - * @param task_woken * @return bool */ -bool buffer_stream_send_from_isr( - BufferStream* buffer_stream, - const uint8_t* data, - size_t size, - BaseType_t* const task_woken); +bool buffer_stream_send_from_isr(BufferStream* buffer_stream, const uint8_t* data, size_t size); /** * @brief Receive buffer from stream From 1f742b611a793e4b17d7036541ba48557cb5e766 Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Fri, 7 Oct 2022 15:46:58 +0300 Subject: [PATCH 120/824] [FL-2651, FL-2863] App name in CLI loader command, RFID data edit fix #1835 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- applications/main/gpio/views/gpio_usb_uart.c | 4 +-- .../lfrfid/scenes/lfrfid_scene_save_data.c | 9 +++-- .../lfrfid/scenes/lfrfid_scene_save_success.c | 3 ++ applications/services/loader/loader.c | 33 +++++++++++++++++-- applications/services/storage/storage_cli.c | 6 ++-- .../storage_settings_scene_internal_info.c | 2 +- .../scenes/storage_settings_scene_sd_info.c | 2 +- 7 files changed, 44 insertions(+), 15 deletions(-) diff --git a/applications/main/gpio/views/gpio_usb_uart.c b/applications/main/gpio/views/gpio_usb_uart.c index d2d371b6822..d190ecab02c 100644 --- a/applications/main/gpio/views/gpio_usb_uart.c +++ b/applications/main/gpio/views/gpio_usb_uart.c @@ -54,7 +54,7 @@ static void gpio_usb_uart_draw_callback(Canvas* canvas, void* _model) { canvas_draw_str_aligned(canvas, 116, 24, AlignRight, AlignBottom, temp_str); } else { canvas_set_font(canvas, FontSecondary); - canvas_draw_str_aligned(canvas, 127, 24, AlignRight, AlignBottom, "KB."); + canvas_draw_str_aligned(canvas, 127, 24, AlignRight, AlignBottom, "KiB."); canvas_set_font(canvas, FontKeyboard); snprintf(temp_str, 18, "%lu", model->tx_cnt / 1024); canvas_draw_str_aligned(canvas, 111, 24, AlignRight, AlignBottom, temp_str); @@ -68,7 +68,7 @@ static void gpio_usb_uart_draw_callback(Canvas* canvas, void* _model) { canvas_draw_str_aligned(canvas, 116, 41, AlignRight, AlignBottom, temp_str); } else { canvas_set_font(canvas, FontSecondary); - canvas_draw_str_aligned(canvas, 127, 41, AlignRight, AlignBottom, "KB."); + canvas_draw_str_aligned(canvas, 127, 41, AlignRight, AlignBottom, "KiB."); canvas_set_font(canvas, FontKeyboard); snprintf(temp_str, 18, "%lu", model->rx_cnt / 1024); canvas_draw_str_aligned(canvas, 111, 41, AlignRight, AlignBottom, temp_str); diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_save_data.c b/applications/main/lfrfid/scenes/lfrfid_scene_save_data.c index 2ca1bb433cb..6c5ea2f2df0 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_save_data.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_save_data.c @@ -9,14 +9,11 @@ void lfrfid_scene_save_data_on_enter(void* context) { bool need_restore = scene_manager_get_scene_state(app->scene_manager, LfRfidSceneSaveData); - if(need_restore) { - protocol_dict_set_data(app->dict, app->protocol_id, app->old_key_data, size); - } else { + if(!need_restore) { protocol_dict_get_data(app->dict, app->protocol_id, app->old_key_data, size); + protocol_dict_get_data(app->dict, app->protocol_id, app->new_key_data, size); } - protocol_dict_get_data(app->dict, app->protocol_id, app->new_key_data, size); - byte_input_set_header_text(byte_input, "Enter the data in hex"); byte_input_set_result_callback( @@ -41,6 +38,8 @@ bool lfrfid_scene_save_data_on_event(void* context, SceneManagerEvent event) { } } else if(event.type == SceneManagerEventTypeBack) { scene_manager_set_scene_state(scene_manager, LfRfidSceneSaveData, 0); + size_t size = protocol_dict_get_data_size(app->dict, app->protocol_id); + protocol_dict_set_data(app->dict, app->protocol_id, app->old_key_data, size); } return consumed; diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_save_success.c b/applications/main/lfrfid/scenes/lfrfid_scene_save_success.c index 830ef3368c2..e91ad04e36e 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_save_success.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_save_success.c @@ -5,6 +5,9 @@ void lfrfid_scene_save_success_on_enter(void* context) { LfRfid* app = context; Popup* popup = app->popup; + // Clear state of data enter scene + scene_manager_set_scene_state(app->scene_manager, LfRfidSceneSaveData, 0); + DOLPHIN_DEED(DolphinDeedRfidSave); popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); popup_set_header(popup, "Saved!", 5, 7, AlignLeft, AlignTop); diff --git a/applications/services/loader/loader.c b/applications/services/loader/loader.c index 51cddec706a..bc456536c9c 100644 --- a/applications/services/loader/loader.c +++ b/applications/services/loader/loader.c @@ -61,6 +61,7 @@ static void loader_cli_print_usage() { printf("Cmd list:\r\n"); printf("\tlist\t - List available applications\r\n"); printf("\topen \t - Open application by name\r\n"); + printf("\tinfo\t - Show loader state\r\n"); } static FlipperApplication const* loader_find_application_by_name_in_list( @@ -98,10 +99,15 @@ const FlipperApplication* loader_find_application_by_name(const char* name) { return application; } -void loader_cli_open(Cli* cli, FuriString* args, Loader* instance) { +static void loader_cli_open(Cli* cli, FuriString* args, Loader* instance) { UNUSED(cli); if(loader_is_locked(instance)) { - printf("Can't start, furi application is running"); + if(instance->application) { + furi_assert(instance->application->name); + printf("Can't start, %s application is running", instance->application->name); + } else { + printf("Can't start, furi application is running"); + } return; } @@ -137,7 +143,7 @@ void loader_cli_open(Cli* cli, FuriString* args, Loader* instance) { furi_string_free(application_name); } -void loader_cli_list(Cli* cli, FuriString* args, Loader* instance) { +static void loader_cli_list(Cli* cli, FuriString* args, Loader* instance) { UNUSED(cli); UNUSED(args); UNUSED(instance); @@ -159,6 +165,22 @@ void loader_cli_list(Cli* cli, FuriString* args, Loader* instance) { } } +static void loader_cli_info(Cli* cli, FuriString* args, Loader* instance) { + UNUSED(cli); + UNUSED(args); + if(!loader_is_locked(instance)) { + printf("No application is running\r\n"); + } else { + printf("Running application: "); + if(instance->application) { + furi_assert(instance->application->name); + printf("%s\r\n", instance->application->name); + } else { + printf("unknown\r\n"); + } + } +} + static void loader_cli(Cli* cli, FuriString* args, void* _ctx) { furi_assert(_ctx); Loader* instance = _ctx; @@ -182,6 +204,11 @@ static void loader_cli(Cli* cli, FuriString* args, void* _ctx) { break; } + if(furi_string_cmp_str(cmd, "info") == 0) { + loader_cli_info(cli, args, instance); + break; + } + loader_cli_print_usage(); } while(false); diff --git a/applications/services/storage/storage_cli.c b/applications/services/storage/storage_cli.c index 5e72dce8e72..880fb9700e4 100644 --- a/applications/services/storage/storage_cli.c +++ b/applications/services/storage/storage_cli.c @@ -52,7 +52,7 @@ static void storage_cli_info(Cli* cli, FuriString* path) { storage_cli_print_error(error); } else { printf( - "Label: %s\r\nType: LittleFS\r\n%luKB total\r\n%luKB free\r\n", + "Label: %s\r\nType: LittleFS\r\n%luKiB total\r\n%luKiB free\r\n", furi_hal_version_get_name_ptr() ? furi_hal_version_get_name_ptr() : "Unknown", (uint32_t)(total_space / 1024), (uint32_t)(free_space / 1024)); @@ -65,7 +65,7 @@ static void storage_cli_info(Cli* cli, FuriString* path) { storage_cli_print_error(error); } else { printf( - "Label: %s\r\nType: %s\r\n%luKB total\r\n%luKB free\r\n", + "Label: %s\r\nType: %s\r\n%luKiB total\r\n%luKiB free\r\n", sd_info.label, sd_api_get_fs_type_text(sd_info.fs_type), sd_info.kb_total, @@ -364,7 +364,7 @@ static void storage_cli_stat(Cli* cli, FuriString* path) { storage_cli_print_error(error); } else { printf( - "Storage, %luKB total, %luKB free\r\n", + "Storage, %luKiB total, %luKiB free\r\n", (uint32_t)(total_space / 1024), (uint32_t)(free_space / 1024)); } diff --git a/applications/settings/storage_settings/scenes/storage_settings_scene_internal_info.c b/applications/settings/storage_settings/scenes/storage_settings_scene_internal_info.c index d2d4ecd8a57..f205efc0aee 100644 --- a/applications/settings/storage_settings/scenes/storage_settings_scene_internal_info.c +++ b/applications/settings/storage_settings/scenes/storage_settings_scene_internal_info.c @@ -27,7 +27,7 @@ void storage_settings_scene_internal_info_on_enter(void* context) { } else { furi_string_printf( app->text_string, - "Label: %s\nType: LittleFS\n%lu KB total\n%lu KB free", + "Label: %s\nType: LittleFS\n%lu KiB total\n%lu KiB free", furi_hal_version_get_name_ptr() ? furi_hal_version_get_name_ptr() : "Unknown", (uint32_t)(total_space / 1024), (uint32_t)(free_space / 1024)); diff --git a/applications/settings/storage_settings/scenes/storage_settings_scene_sd_info.c b/applications/settings/storage_settings/scenes/storage_settings_scene_sd_info.c index f5d286c7f49..ede610d0e22 100644 --- a/applications/settings/storage_settings/scenes/storage_settings_scene_sd_info.c +++ b/applications/settings/storage_settings/scenes/storage_settings_scene_sd_info.c @@ -26,7 +26,7 @@ void storage_settings_scene_sd_info_on_enter(void* context) { } else { furi_string_printf( app->text_string, - "Label: %s\nType: %s\n%lu KB total\n%lu KB free", + "Label: %s\nType: %s\n%lu KiB total\n%lu KiB free", sd_info.label, sd_api_get_fs_type_text(sd_info.fs_type), sd_info.kb_total, From 4000f0cac51fa32006c7737ea8c34494bf4003f2 Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Fri, 7 Oct 2022 23:35:15 +1000 Subject: [PATCH 121/824] [FL-2870] Printf function attributes (#1841) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Furi strings: printf attribute * Logs: printf attribute * Plugins: adapt * Plugins: accommodate * Unit tests: accommodate Co-authored-by: あく --- .../debug/unit_tests/infrared/infrared_test.c | 10 +++---- applications/debug/unit_tests/nfc/nfc_test.c | 10 +++---- .../main/archive/helpers/archive_favorites.h | 4 +-- .../main/archive/helpers/archive_files.h | 6 ++-- applications/main/bad_usb/bad_usb_script.c | 6 ++-- .../main/bad_usb/views/bad_usb_view.c | 2 +- .../main/fap_loader/elf_cpp/elf_hashtable.cpp | 2 +- applications/main/infrared/infrared.c | 4 +-- applications/main/infrared/infrared_signal.c | 4 +-- .../scenes/nfc_scene_mf_classic_keys_list.c | 2 +- .../nfc_scene_mf_desfire_read_success.c | 4 +-- .../main/nfc/scenes/nfc_scene_nfc_data_info.c | 4 +-- .../subghz_frequency_analyzer_worker.c | 8 +++--- applications/main/subghz/subghz_cli.c | 6 ++-- applications/main/subghz/subghz_i.c | 2 +- .../music_player/music_player_worker.c | 6 ++-- applications/services/bt/bt_service/bt.c | 4 +-- .../desktop/animations/animation_storage.c | 4 +-- .../services/dolphin/helpers/dolphin_state.c | 2 +- applications/services/gui/gui.c | 6 ++-- .../gui/modules/file_browser_worker.c | 9 +++--- applications/services/gui/view_dispatcher.c | 4 +-- applications/services/rpc/rpc_app.c | 18 ++++++------ applications/services/rpc/rpc_cli.c | 2 +- applications/services/rpc/rpc_debug.c | 4 +-- .../services/storage/storage_external_api.c | 28 +++++++++++++++---- .../services/storage/storages/storage_int.c | 14 +++++----- .../updater/util/update_task_worker_flasher.c | 8 +++--- firmware/targets/f7/ble_glue/gap.c | 4 +-- firmware/targets/f7/ble_glue/serial_service.c | 4 +-- .../targets/f7/furi_hal/furi_hal_console.h | 3 +- firmware/targets/f7/furi_hal/furi_hal_flash.c | 6 ++-- .../targets/f7/furi_hal/furi_hal_memory.c | 4 +-- firmware/targets/f7/furi_hal/furi_hal_power.c | 6 ++-- .../targets/f7/furi_hal/furi_hal_subghz.c | 2 +- furi/core/log.h | 3 +- furi/core/string.h | 9 ++++-- lib/lfrfid/lfrfid_raw_worker.c | 2 +- lib/lfrfid/protocols/protocol_gallagher.c | 2 +- lib/lfrfid/protocols/protocol_keri.c | 2 +- lib/lfrfid/protocols/protocol_paradox.c | 2 +- lib/lfrfid/protocols/protocol_pyramid.c | 2 +- lib/nfc/helpers/mf_classic_dict.c | 2 +- lib/nfc/helpers/mfkey32.c | 2 +- lib/nfc/nfc_worker.c | 3 +- lib/nfc/parsers/all_in_one.c | 2 +- lib/nfc/parsers/plantain_4k_parser.c | 2 +- lib/nfc/parsers/plantain_parser.c | 2 +- lib/nfc/parsers/two_cities.c | 2 +- lib/nfc/protocols/mifare_classic.c | 4 +-- lib/nfc/protocols/mifare_desfire.c | 8 +++--- lib/nfc/protocols/mifare_ultralight.c | 4 +-- lib/print/printf_tiny.h | 11 +++++--- lib/subghz/protocols/came_atomo.c | 2 +- lib/subghz/protocols/came_twee.c | 2 +- lib/subghz/protocols/doitrand.c | 2 +- lib/subghz/protocols/faac_slh.c | 2 +- lib/subghz/protocols/gate_tx.c | 2 +- lib/subghz/protocols/honeywell_wdb.c | 2 +- lib/subghz/protocols/ido.c | 2 +- lib/subghz/protocols/keeloq.c | 4 +-- lib/subghz/protocols/kia.c | 2 +- lib/subghz/protocols/magellen.c | 2 +- lib/subghz/protocols/marantec.c | 2 +- lib/subghz/protocols/megacode.c | 4 +-- lib/subghz/protocols/nice_flor_s.c | 2 +- lib/subghz/protocols/oregon2.c | 4 +-- lib/subghz/protocols/phoenix_v2.c | 2 +- lib/subghz/protocols/princeton.c | 2 +- lib/subghz/protocols/princeton_for_testing.c | 2 +- lib/subghz/protocols/raw.c | 2 +- lib/subghz/protocols/scher_khan.c | 2 +- lib/subghz/protocols/secplus_v1.c | 4 +-- lib/subghz/protocols/secplus_v2.c | 2 +- lib/subghz/protocols/somfy_keytis.c | 2 +- lib/subghz/protocols/somfy_telis.c | 2 +- lib/subghz/protocols/star_line.c | 4 +-- lib/toolbox/stream/stream.h | 9 ++++-- 78 files changed, 187 insertions(+), 156 deletions(-) diff --git a/applications/debug/unit_tests/infrared/infrared_test.c b/applications/debug/unit_tests/infrared/infrared_test.c index d861f266ed2..8879c8fc883 100644 --- a/applications/debug/unit_tests/infrared/infrared_test.c +++ b/applications/debug/unit_tests/infrared/infrared_test.c @@ -221,13 +221,13 @@ static void infrared_test_run_encoder(InfraredProtocol protocol, uint32_t test_i const char* protocol_name = infrared_get_protocol_name(protocol); mu_assert(infrared_test_prepare_file(protocol_name), "Failed to prepare test file"); - furi_string_printf(buf, "encoder_input%d", test_index); + furi_string_printf(buf, "encoder_input%ld", test_index); mu_assert( infrared_test_load_messages( test->ff, furi_string_get_cstr(buf), &input_messages, &input_messages_count), "Failed to load messages from file"); - furi_string_printf(buf, "encoder_expected%d", test_index); + furi_string_printf(buf, "encoder_expected%ld", test_index); mu_assert( infrared_test_load_raw_signal( test->ff, furi_string_get_cstr(buf), &expected_timings, &expected_timings_count), @@ -277,7 +277,7 @@ static void infrared_test_run_encoder_decoder(InfraredProtocol protocol, uint32_ const char* protocol_name = infrared_get_protocol_name(protocol); mu_assert(infrared_test_prepare_file(protocol_name), "Failed to prepare test file"); - furi_string_printf(buf, "encoder_decoder_input%d", test_index); + furi_string_printf(buf, "encoder_decoder_input%ld", test_index); mu_assert( infrared_test_load_messages( test->ff, furi_string_get_cstr(buf), &input_messages, &input_messages_count), @@ -336,13 +336,13 @@ static void infrared_test_run_decoder(InfraredProtocol protocol, uint32_t test_i infrared_test_prepare_file(infrared_get_protocol_name(protocol)), "Failed to prepare test file"); - furi_string_printf(buf, "decoder_input%d", test_index); + furi_string_printf(buf, "decoder_input%ld", test_index); mu_assert( infrared_test_load_raw_signal( test->ff, furi_string_get_cstr(buf), &timings, &timings_count), "Failed to load raw signal from file"); - furi_string_printf(buf, "decoder_expected%d", test_index); + furi_string_printf(buf, "decoder_expected%ld", test_index); mu_assert( infrared_test_load_messages( test->ff, furi_string_get_cstr(buf), &messages, &messages_count), diff --git a/applications/debug/unit_tests/nfc/nfc_test.c b/applications/debug/unit_tests/nfc/nfc_test.c index c1468c86fcb..f149508b0ab 100644 --- a/applications/debug/unit_tests/nfc/nfc_test.c +++ b/applications/debug/unit_tests/nfc/nfc_test.c @@ -112,7 +112,7 @@ static bool nfc_test_digital_signal_test_encode( // Check timings if(time > encode_max_time) { FURI_LOG_E( - TAG, "Encoding time: %d us while accepted value: %d us", time, encode_max_time); + TAG, "Encoding time: %ld us while accepted value: %ld us", time, encode_max_time); break; } @@ -132,7 +132,7 @@ static bool nfc_test_digital_signal_test_encode( ref_timings_sum += ref[i]; if(timings_diff > timing_tolerance) { FURI_LOG_E( - TAG, "Too big differece in %d timings. Ref: %d, DUT: %d", i, ref[i], dut[i]); + TAG, "Too big differece in %d timings. Ref: %ld, DUT: %ld", i, ref[i], dut[i]); timing_check_success = false; break; } @@ -143,16 +143,16 @@ static bool nfc_test_digital_signal_test_encode( if(sum_diff > timings_sum_tolerance) { FURI_LOG_E( TAG, - "Too big difference in timings sum. Ref: %d, DUT: %d", + "Too big difference in timings sum. Ref: %ld, DUT: %ld", ref_timings_sum, dut_timings_sum); break; } - FURI_LOG_I(TAG, "Encoding time: %d us. Acceptable time: %d us", time, encode_max_time); + FURI_LOG_I(TAG, "Encoding time: %ld us. Acceptable time: %ld us", time, encode_max_time); FURI_LOG_I( TAG, - "Timings sum difference: %d [1/64MHZ]. Acceptable difference: %d [1/64MHz]", + "Timings sum difference: %ld [1/64MHZ]. Acceptable difference: %ld [1/64MHz]", sum_diff, timings_sum_tolerance); success = true; diff --git a/applications/main/archive/helpers/archive_favorites.h b/applications/main/archive/helpers/archive_favorites.h index 29eedcdb6f3..db89433782a 100644 --- a/applications/main/archive/helpers/archive_favorites.h +++ b/applications/main/archive/helpers/archive_favorites.h @@ -7,8 +7,8 @@ uint16_t archive_favorites_count(void* context); bool archive_favorites_read(void* context); -bool archive_favorites_delete(const char* format, ...); -bool archive_is_favorite(const char* format, ...); +bool archive_favorites_delete(const char* format, ...) _ATTRIBUTE((__format__(__printf__, 1, 2))); +bool archive_is_favorite(const char* format, ...) _ATTRIBUTE((__format__(__printf__, 1, 2))); bool archive_favorites_rename(const char* src, const char* dst); void archive_add_to_favorites(const char* file_path); void archive_favorites_save(void* context); diff --git a/applications/main/archive/helpers/archive_files.h b/applications/main/archive/helpers/archive_files.h index 2017a9574f9..1822befa358 100644 --- a/applications/main/archive/helpers/archive_files.h +++ b/applications/main/archive/helpers/archive_files.h @@ -86,5 +86,7 @@ ARRAY_DEF( void archive_set_file_type(ArchiveFile_t* file, const char* path, bool is_folder, bool is_app); bool archive_get_items(void* context, const char* path); -void archive_file_append(const char* path, const char* format, ...); -void archive_delete_file(void* context, const char* format, ...); +void archive_file_append(const char* path, const char* format, ...) + _ATTRIBUTE((__format__(__printf__, 2, 3))); +void archive_delete_file(void* context, const char* format, ...) + _ATTRIBUTE((__format__(__printf__, 2, 3))); diff --git a/applications/main/bad_usb/bad_usb_script.c b/applications/main/bad_usb/bad_usb_script.c index 4166642bd37..1e3edf40fb1 100644 --- a/applications/main/bad_usb/bad_usb_script.c +++ b/applications/main/bad_usb/bad_usb_script.c @@ -323,7 +323,7 @@ static bool ducky_set_usb_id(BadUsbScript* bad_usb, const char* line) { } FURI_LOG_D( WORKER_TAG, - "set id: %04X:%04X mfr:%s product:%s", + "set id: %04lX:%04lX mfr:%s product:%s", bad_usb->hid_cfg.vid, bad_usb->hid_cfg.pid, bad_usb->hid_cfg.manuf, @@ -388,7 +388,7 @@ static int32_t ducky_script_execute_next(BadUsbScript* bad_usb, File* script_fil return 0; } else if(delay_val < 0) { // Script error bad_usb->st.error_line = bad_usb->st.line_cur - 1; - FURI_LOG_E(WORKER_TAG, "Unknown command at line %lu", bad_usb->st.line_cur - 1); + FURI_LOG_E(WORKER_TAG, "Unknown command at line %u", bad_usb->st.line_cur - 1); return SCRIPT_STATE_ERROR; } else { return (delay_val + bad_usb->defdelay); @@ -420,7 +420,7 @@ static int32_t ducky_script_execute_next(BadUsbScript* bad_usb, File* script_fil delay_val = ducky_parse_line(bad_usb, bad_usb->line); if(delay_val < 0) { bad_usb->st.error_line = bad_usb->st.line_cur; - FURI_LOG_E(WORKER_TAG, "Unknown command at line %lu", bad_usb->st.line_cur); + FURI_LOG_E(WORKER_TAG, "Unknown command at line %u", bad_usb->st.line_cur); return SCRIPT_STATE_ERROR; } else { return (delay_val + bad_usb->defdelay); diff --git a/applications/main/bad_usb/views/bad_usb_view.c b/applications/main/bad_usb/views/bad_usb_view.c index 4e025b99c32..b6310193b23 100644 --- a/applications/main/bad_usb/views/bad_usb_view.c +++ b/applications/main/bad_usb/views/bad_usb_view.c @@ -91,7 +91,7 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) { furi_string_reset(disp_str); canvas_draw_icon(canvas, 117, 22, &I_Percent_10x14); canvas_set_font(canvas, FontSecondary); - furi_string_printf(disp_str, "delay %us", model->state.delay_remain); + furi_string_printf(disp_str, "delay %lus", model->state.delay_remain); canvas_draw_str_aligned( canvas, 127, 46, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); furi_string_reset(disp_str); diff --git a/applications/main/fap_loader/elf_cpp/elf_hashtable.cpp b/applications/main/fap_loader/elf_cpp/elf_hashtable.cpp index 17e2ba83fce..f8adbf9d8d8 100644 --- a/applications/main/fap_loader/elf_cpp/elf_hashtable.cpp +++ b/applications/main/fap_loader/elf_cpp/elf_hashtable.cpp @@ -31,7 +31,7 @@ bool elf_resolve_from_hashtable(const char* name, Elf32_Addr* address) { auto find_res = std::lower_bound(elf_api_table.cbegin(), elf_api_table.cend(), key); if((find_res == elf_api_table.cend() || (find_res->hash != gnu_sym_hash))) { - FURI_LOG_W(TAG, "Cant find symbol '%s' (hash %x)!", name, gnu_sym_hash); + FURI_LOG_W(TAG, "Cant find symbol '%s' (hash %lx)!", name, gnu_sym_hash); result = false; } else { result = true; diff --git a/applications/main/infrared/infrared.c b/applications/main/infrared/infrared.c index a647b158e12..f62db14c124 100644 --- a/applications/main/infrared/infrared.c +++ b/applications/main/infrared/infrared.c @@ -92,14 +92,14 @@ static void infrared_find_vacant_remote_name(FuriString* name, const char* path) uint32_t i = 1; do { furi_string_printf( - path_temp, "%s%u%s", furi_string_get_cstr(base_path), ++i, INFRARED_APP_EXTENSION); + path_temp, "%s%lu%s", furi_string_get_cstr(base_path), ++i, INFRARED_APP_EXTENSION); status = storage_common_stat(storage, furi_string_get_cstr(path_temp), NULL); } while(status == FSE_OK); furi_string_free(path_temp); if(status == FSE_NOT_EXIST) { - furi_string_cat_printf(name, "%u", i); + furi_string_cat_printf(name, "%lu", i); } } diff --git a/applications/main/infrared/infrared_signal.c b/applications/main/infrared/infrared_signal.c index 30459d60f4f..d399b95879d 100644 --- a/applications/main/infrared/infrared_signal.c +++ b/applications/main/infrared/infrared_signal.c @@ -61,7 +61,7 @@ static bool infrared_signal_is_raw_valid(InfraredRawSignal* raw) { if((raw->frequency > INFRARED_MAX_FREQUENCY) || (raw->frequency < INFRARED_MIN_FREQUENCY)) { FURI_LOG_E( TAG, - "Frequency is out of range (%lX - %lX): %lX", + "Frequency is out of range (%X - %X): %lX", INFRARED_MIN_FREQUENCY, INFRARED_MAX_FREQUENCY, raw->frequency); @@ -74,7 +74,7 @@ static bool infrared_signal_is_raw_valid(InfraredRawSignal* raw) { } else if((raw->timings_size <= 0) || (raw->timings_size > MAX_TIMINGS_AMOUNT)) { FURI_LOG_E( TAG, - "Timings amount is out of range (0 - %lX): %lX", + "Timings amount is out of range (0 - %X): %X", MAX_TIMINGS_AMOUNT, raw->timings_size); return false; diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_list.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_list.c index 5649ea870c0..19d2f556f0d 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_list.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_list.c @@ -27,7 +27,7 @@ void nfc_scene_mf_classic_keys_list_prepare(Nfc* nfc, MfClassicDict* dict) { char* current_key = (char*)malloc(sizeof(char) * 13); strncpy(current_key, furi_string_get_cstr(temp_key), 12); MfClassicUserKeys_push_back(nfc->mfc_key_strs, current_key); - FURI_LOG_D("ListKeys", "Key %d: %s", index, current_key); + FURI_LOG_D("ListKeys", "Key %ld: %s", index, current_key); submenu_add_item( submenu, current_key, index++, nfc_scene_mf_classic_keys_list_submenu_callback, nfc); } diff --git a/applications/main/nfc/scenes/nfc_scene_mf_desfire_read_success.c b/applications/main/nfc/scenes/nfc_scene_mf_desfire_read_success.c index c5b8cfa2027..2ab0355ca2c 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_desfire_read_success.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_desfire_read_success.c @@ -28,11 +28,11 @@ void nfc_scene_mf_desfire_read_success_on_enter(void* context) { uint32_t bytes_total = 1 << (data->version.sw_storage >> 1); uint32_t bytes_free = data->free_memory ? data->free_memory->bytes : 0; - furi_string_cat_printf(temp_str, "\n%d", bytes_total); + furi_string_cat_printf(temp_str, "\n%ld", bytes_total); if(data->version.sw_storage & 1) { furi_string_push_back(temp_str, '+'); } - furi_string_cat_printf(temp_str, " bytes, %d bytes free\n", bytes_free); + furi_string_cat_printf(temp_str, " bytes, %ld bytes free\n", bytes_free); uint16_t n_apps = 0; uint16_t n_files = 0; diff --git a/applications/main/nfc/scenes/nfc_scene_nfc_data_info.c b/applications/main/nfc/scenes/nfc_scene_nfc_data_info.c index bb7d58f7c35..8f33972e079 100644 --- a/applications/main/nfc/scenes/nfc_scene_nfc_data_info.c +++ b/applications/main/nfc/scenes/nfc_scene_nfc_data_info.c @@ -59,11 +59,11 @@ void nfc_scene_nfc_data_info_on_enter(void* context) { MifareDesfireData* data = &dev_data->mf_df_data; uint32_t bytes_total = 1 << (data->version.sw_storage >> 1); uint32_t bytes_free = data->free_memory ? data->free_memory->bytes : 0; - furi_string_cat_printf(temp_str, "\n%d", bytes_total); + furi_string_cat_printf(temp_str, "\n%ld", bytes_total); if(data->version.sw_storage & 1) { furi_string_push_back(temp_str, '+'); } - furi_string_cat_printf(temp_str, " bytes, %d bytes free\n", bytes_free); + furi_string_cat_printf(temp_str, " bytes, %ld bytes free\n", bytes_free); uint16_t n_apps = 0; uint16_t n_files = 0; diff --git a/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.c b/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.c index 35abbddc190..0341990d8d4 100644 --- a/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.c +++ b/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.c @@ -149,7 +149,7 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) { FURI_LOG_T( TAG, - "RSSI: avg %f, max %f at %u, min %f", + "RSSI: avg %f, max %f at %lu, min %f", (double)(rssi_avg / rssi_avg_samples), (double)frequency_rssi.rssi_coarse, frequency_rssi.frequency_coarse, @@ -180,7 +180,7 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) { rssi = furi_hal_subghz_get_rssi(); - FURI_LOG_T(TAG, "#:%u:%f", frequency, (double)rssi); + FURI_LOG_T(TAG, "#:%lu:%f", frequency, (double)rssi); if(frequency_rssi.rssi_fine < rssi) { frequency_rssi.rssi_fine = rssi; @@ -193,7 +193,7 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) { // Deliver results fine if(frequency_rssi.rssi_fine > SUBGHZ_FREQUENCY_ANALYZER_THRESHOLD) { FURI_LOG_D( - TAG, "=:%u:%f", frequency_rssi.frequency_fine, (double)frequency_rssi.rssi_fine); + TAG, "=:%lu:%f", frequency_rssi.frequency_fine, (double)frequency_rssi.rssi_fine); instance->sample_hold_counter = 20; rssi_temp = frequency_rssi.rssi_fine; @@ -217,7 +217,7 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) { (instance->sample_hold_counter < 10)) { FURI_LOG_D( TAG, - "~:%u:%f", + "~:%lu:%f", frequency_rssi.frequency_coarse, (double)frequency_rssi.rssi_coarse); diff --git a/applications/main/subghz/subghz_cli.c b/applications/main/subghz/subghz_cli.c index 7fa0f454608..a1474885eb2 100644 --- a/applications/main/subghz/subghz_cli.c +++ b/applications/main/subghz/subghz_cli.c @@ -151,8 +151,8 @@ void subghz_cli_command_tx(Cli* cli, FuriString* args, void* context) { "Protocol: Princeton\n" "Bit: 24\n" "Key: 00 00 00 00 00 %02X %02X %02X\n" - "TE: %d\n" - "Repeat: %d\n", + "TE: %ld\n" + "Repeat: %ld\n", (uint8_t)((key >> 16) & 0xFF), (uint8_t)((key >> 8) & 0xFF), (uint8_t)(key & 0xFF), @@ -794,7 +794,7 @@ static bool subghz_on_system_start_istream_decode_band( FURI_LOG_I( "SubGhzOnStart", - "Add allowed band: start %dHz, stop %dHz, power_limit %ddBm, duty_cycle %d%%", + "Add allowed band: start %ldHz, stop %ldHz, power_limit %ddBm, duty_cycle %d%%", band.start, band.end, band.power_limit, diff --git a/applications/main/subghz/subghz_i.c b/applications/main/subghz/subghz_i.c index f2658c0e48b..acf07bc1461 100644 --- a/applications/main/subghz/subghz_i.c +++ b/applications/main/subghz/subghz_i.c @@ -60,7 +60,7 @@ void subghz_get_frequency_modulation(SubGhz* subghz, FuriString* frequency, Furi subghz->txrx->preset->frequency / 10000 % 100); } if(modulation != NULL) { - furi_string_printf(modulation, "%0.2s", furi_string_get_cstr(subghz->txrx->preset->name)); + furi_string_printf(modulation, "%2s", furi_string_get_cstr(subghz->txrx->preset->name)); } } diff --git a/applications/plugins/music_player/music_player_worker.c b/applications/plugins/music_player/music_player_worker.c index af09f05352d..99f0ce1e52c 100644 --- a/applications/plugins/music_player/music_player_worker.c +++ b/applications/plugins/music_player/music_player_worker.c @@ -258,7 +258,7 @@ static bool music_player_worker_parse_notes(MusicPlayerWorker* instance, const c if(!is_valid) { FURI_LOG_E( TAG, - "Invalid note: %u%c%c%u.%u", + "Invalid note: %lu%c%c%lu.%lu", duration, note_char == '\0' ? '_' : note_char, sharp_char == '\0' ? '_' : sharp_char, @@ -281,7 +281,7 @@ static bool music_player_worker_parse_notes(MusicPlayerWorker* instance, const c if(music_player_worker_add_note(instance, semitone, duration, dots)) { FURI_LOG_D( TAG, - "Added note: %c%c%u.%u = %u %u", + "Added note: %c%c%lu.%lu = %u %lu", note_char == '\0' ? '_' : note_char, sharp_char == '\0' ? '_' : sharp_char, octave, @@ -291,7 +291,7 @@ static bool music_player_worker_parse_notes(MusicPlayerWorker* instance, const c } else { FURI_LOG_E( TAG, - "Invalid note: %c%c%u.%u = %u %u", + "Invalid note: %c%c%lu.%lu = %u %lu", note_char == '\0' ? '_' : note_char, sharp_char == '\0' ? '_' : sharp_char, octave, diff --git a/applications/services/bt/bt_service/bt.c b/applications/services/bt/bt_service/bt.c index 2bb083f278a..aeb2beec9e7 100644 --- a/applications/services/bt/bt_service/bt.c +++ b/applications/services/bt/bt_service/bt.c @@ -77,7 +77,7 @@ static bool bt_pin_code_verify_event_handler(Bt* bt, uint32_t pin) { notification_message(bt->notification, &sequence_display_backlight_on); FuriString* pin_str; dialog_message_set_icon(bt->dialog_message, &I_BLE_Pairing_128x64, 0, 0); - pin_str = furi_string_alloc_printf("Verify code\n%06d", pin); + pin_str = furi_string_alloc_printf("Verify code\n%06ld", pin); dialog_message_set_text( bt->dialog_message, furi_string_get_cstr(pin_str), 64, 4, AlignCenter, AlignTop); dialog_message_set_buttons(bt->dialog_message, "Cancel", "OK", NULL); @@ -277,7 +277,7 @@ static bool bt_on_gap_event_callback(GapEvent event, void* context) { static void bt_on_key_storage_change_callback(uint8_t* addr, uint16_t size, void* context) { furi_assert(context); Bt* bt = context; - FURI_LOG_I(TAG, "Changed addr start: %08lX, size changed: %d", addr, size); + FURI_LOG_I(TAG, "Changed addr start: %p, size changed: %d", addr, size); BtMessage message = {.type = BtMessageTypeKeysStorageUpdated}; furi_check( furi_message_queue_put(bt->message_queue, &message, FuriWaitForever) == FuriStatusOk); diff --git a/applications/services/desktop/animations/animation_storage.c b/applications/services/desktop/animations/animation_storage.c index 922d7dc85af..0727fd6ae76 100644 --- a/applications/services/desktop/animations/animation_storage.c +++ b/applications/services/desktop/animations/animation_storage.c @@ -304,7 +304,7 @@ static bool animation_storage_load_frames( if(file_info.size > max_filesize) { FURI_LOG_E( TAG, - "Filesize %d, max: %d (width %d, height %d)", + "Filesize %lld, max: %d (width %d, height %d)", file_info.size, max_filesize, width, @@ -329,7 +329,7 @@ static bool animation_storage_load_frames( if(!frames_ok) { FURI_LOG_E( TAG, - "Load \'%s\' failed, %dx%d, size: %d", + "Load \'%s\' failed, %dx%d, size: %lld", furi_string_get_cstr(filename), width, height, diff --git a/applications/services/dolphin/helpers/dolphin_state.c b/applications/services/dolphin/helpers/dolphin_state.c index 95e2f42f49d..10cb85c2847 100644 --- a/applications/services/dolphin/helpers/dolphin_state.c +++ b/applications/services/dolphin/helpers/dolphin_state.c @@ -171,7 +171,7 @@ void dolphin_state_on_deed(DolphinState* dolphin_state, DolphinDeed deed) { FURI_LOG_D( TAG, - "icounter %d, butthurt %d", + "icounter %ld, butthurt %ld", dolphin_state->data.icounter, dolphin_state->data.butthurt); } diff --git a/applications/services/gui/gui.c b/applications/services/gui/gui.c index 6b4b9a0a705..0535b32b812 100644 --- a/applications/services/gui/gui.c +++ b/applications/services/gui/gui.c @@ -260,7 +260,7 @@ void gui_input(Gui* gui, InputEvent* input_event) { "non-complementary input, discarding key: %s type: %s, sequence: %p", input_get_key_name(input_event->key), input_get_type_name(input_event->type), - input_event->sequence); + (void*)input_event->sequence); return; } @@ -290,7 +290,7 @@ void gui_input(Gui* gui, InputEvent* input_event) { view_port, input_get_key_name(input_event->key), input_get_type_name(input_event->type), - input_event->sequence); + (void*)input_event->sequence); view_port_input(gui->ongoing_input_view_port, input_event); } else { FURI_LOG_D( @@ -300,7 +300,7 @@ void gui_input(Gui* gui, InputEvent* input_event) { view_port, input_get_key_name(input_event->key), input_get_type_name(input_event->type), - input_event->sequence); + (void*)input_event->sequence); } gui_unlock(gui); diff --git a/applications/services/gui/modules/file_browser_worker.c b/applications/services/gui/modules/file_browser_worker.c index 319304f9256..fdaf8273fb4 100644 --- a/applications/services/gui/modules/file_browser_worker.c +++ b/applications/services/gui/modules/file_browser_worker.c @@ -291,7 +291,7 @@ static int32_t browser_worker(void* context) { browser_folder_init(browser, path, filename, &items_cnt, &file_idx); FURI_LOG_D( TAG, - "Enter folder: %s items: %u idx: %d", + "Enter folder: %s items: %lu idx: %ld", furi_string_get_cstr(path), items_cnt, file_idx); @@ -313,7 +313,7 @@ static int32_t browser_worker(void* context) { } FURI_LOG_D( TAG, - "Exit to: %s items: %u idx: %d", + "Exit to: %s items: %lu idx: %ld", furi_string_get_cstr(path), items_cnt, file_idx); @@ -330,7 +330,7 @@ static int32_t browser_worker(void* context) { browser_folder_init(browser, path, filename, &items_cnt, &file_idx); FURI_LOG_D( TAG, - "Refresh folder: %s items: %u idx: %d", + "Refresh folder: %s items: %lu idx: %ld", furi_string_get_cstr(path), items_cnt, browser->item_sel_idx); @@ -340,7 +340,8 @@ static int32_t browser_worker(void* context) { } if(flags & WorkerEvtLoad) { - FURI_LOG_D(TAG, "Load offset: %u cnt: %u", browser->load_offset, browser->load_count); + FURI_LOG_D( + TAG, "Load offset: %lu cnt: %lu", browser->load_offset, browser->load_count); browser_folder_load(browser, path, browser->load_offset, browser->load_count); } diff --git a/applications/services/gui/view_dispatcher.c b/applications/services/gui/view_dispatcher.c index 307206c14b3..6e4ce836011 100644 --- a/applications/services/gui/view_dispatcher.c +++ b/applications/services/gui/view_dispatcher.c @@ -246,7 +246,7 @@ void view_dispatcher_handle_input(ViewDispatcher* view_dispatcher, InputEvent* e "non-complementary input, discarding key: %s, type: %s, sequence: %p", input_get_key_name(event->key), input_get_type_name(event->type), - event->sequence); + (void*)event->sequence); return; } @@ -286,7 +286,7 @@ void view_dispatcher_handle_input(ViewDispatcher* view_dispatcher, InputEvent* e view_dispatcher->current_view, input_get_key_name(event->key), input_get_type_name(event->type), - event->sequence); + (void*)event->sequence); view_input(view_dispatcher->ongoing_input_view, event); } } diff --git a/applications/services/rpc/rpc_app.c b/applications/services/rpc/rpc_app.c index b8b34170e59..3010555708f 100644 --- a/applications/services/rpc/rpc_app.c +++ b/applications/services/rpc/rpc_app.c @@ -32,7 +32,7 @@ static void rpc_system_app_start_process(const PB_Main* request, void* context) furi_assert(!rpc_app->last_id); furi_assert(!rpc_app->last_data); - FURI_LOG_D(TAG, "StartProcess: id %d", request->command_id); + FURI_LOG_D(TAG, "StartProcess: id %ld", request->command_id); PB_CommandStatus result = PB_CommandStatus_ERROR_APP_CANT_START; @@ -63,7 +63,7 @@ static void rpc_system_app_start_process(const PB_Main* request, void* context) furi_record_close(RECORD_LOADER); - FURI_LOG_D(TAG, "StartProcess: response id %d, result %d", request->command_id, result); + FURI_LOG_D(TAG, "StartProcess: response id %ld, result %d", request->command_id, result); rpc_send_and_release_empty(session, request->command_id, result); } @@ -108,7 +108,7 @@ static void rpc_system_app_exit_request(const PB_Main* request, void* context) { PB_CommandStatus status; if(rpc_app->app_callback) { - FURI_LOG_D(TAG, "ExitRequest: id %d", request->command_id); + FURI_LOG_D(TAG, "ExitRequest: id %ld", request->command_id); furi_assert(!rpc_app->last_id); furi_assert(!rpc_app->last_data); rpc_app->last_id = request->command_id; @@ -116,7 +116,7 @@ static void rpc_system_app_exit_request(const PB_Main* request, void* context) { } else { status = PB_CommandStatus_ERROR_APP_NOT_RUNNING; FURI_LOG_E( - TAG, "ExitRequest: APP_NOT_RUNNING, id %d, status: %d", request->command_id, status); + TAG, "ExitRequest: APP_NOT_RUNNING, id %ld, status: %d", request->command_id, status); rpc_send_and_release_empty(session, request->command_id, status); } } @@ -132,7 +132,7 @@ static void rpc_system_app_load_file(const PB_Main* request, void* context) { PB_CommandStatus status; if(rpc_app->app_callback) { - FURI_LOG_D(TAG, "LoadFile: id %d", request->command_id); + FURI_LOG_D(TAG, "LoadFile: id %ld", request->command_id); furi_assert(!rpc_app->last_id); furi_assert(!rpc_app->last_data); rpc_app->last_id = request->command_id; @@ -141,7 +141,7 @@ static void rpc_system_app_load_file(const PB_Main* request, void* context) { } else { status = PB_CommandStatus_ERROR_APP_NOT_RUNNING; FURI_LOG_E( - TAG, "LoadFile: APP_NOT_RUNNING, id %d, status: %d", request->command_id, status); + TAG, "LoadFile: APP_NOT_RUNNING, id %ld, status: %d", request->command_id, status); rpc_send_and_release_empty(session, request->command_id, status); } } @@ -166,7 +166,7 @@ static void rpc_system_app_button_press(const PB_Main* request, void* context) { } else { status = PB_CommandStatus_ERROR_APP_NOT_RUNNING; FURI_LOG_E( - TAG, "ButtonPress: APP_NOT_RUNNING, id %d, status: %d", request->command_id, status); + TAG, "ButtonPress: APP_NOT_RUNNING, id %ld, status: %d", request->command_id, status); rpc_send_and_release_empty(session, request->command_id, status); } } @@ -190,7 +190,7 @@ static void rpc_system_app_button_release(const PB_Main* request, void* context) } else { status = PB_CommandStatus_ERROR_APP_NOT_RUNNING; FURI_LOG_E( - TAG, "ButtonRelease: APP_NOT_RUNNING, id %d, status: %d", request->command_id, status); + TAG, "ButtonRelease: APP_NOT_RUNNING, id %ld, status: %d", request->command_id, status); rpc_send_and_release_empty(session, request->command_id, status); } } @@ -243,7 +243,7 @@ void rpc_system_app_confirm(RpcAppSystem* rpc_app, RpcAppSystemEvent event, bool free(rpc_app->last_data); rpc_app->last_data = NULL; } - FURI_LOG_D(TAG, "AppConfirm: event %d last_id %d status %d", event, last_id, status); + FURI_LOG_D(TAG, "AppConfirm: event %d last_id %ld status %d", event, last_id, status); rpc_send_and_release_empty(session, last_id, status); break; default: diff --git a/applications/services/rpc/rpc_cli.c b/applications/services/rpc/rpc_cli.c index d03e2198c1b..82023e3167e 100644 --- a/applications/services/rpc/rpc_cli.c +++ b/applications/services/rpc/rpc_cli.c @@ -44,7 +44,7 @@ void rpc_cli_command_start_session(Cli* cli, FuriString* args, void* context) { Rpc* rpc = context; uint32_t mem_before = memmgr_get_free_heap(); - FURI_LOG_D(TAG, "Free memory %d", mem_before); + FURI_LOG_D(TAG, "Free memory %ld", mem_before); furi_hal_usb_lock(); RpcSession* rpc_session = rpc_session_open(rpc); diff --git a/applications/services/rpc/rpc_debug.c b/applications/services/rpc/rpc_debug.c index a060654d844..8eb81dece1a 100644 --- a/applications/services/rpc/rpc_debug.c +++ b/applications/services/rpc/rpc_debug.c @@ -158,9 +158,9 @@ void rpc_debug_print_message(const PB_Main* message) { case PB_Main_storage_info_response_tag: { furi_string_cat_printf(str, "\tinfo_response {\r\n"); furi_string_cat_printf( - str, "\t\ttotal_space: %lu\r\n", message->content.storage_info_response.total_space); + str, "\t\ttotal_space: %llu\r\n", message->content.storage_info_response.total_space); furi_string_cat_printf( - str, "\t\tfree_space: %lu\r\n", message->content.storage_info_response.free_space); + str, "\t\tfree_space: %llu\r\n", message->content.storage_info_response.free_space); break; } case PB_Main_storage_stat_request_tag: { diff --git a/applications/services/storage/storage_external_api.c b/applications/services/storage/storage_external_api.c index 379fc4ed110..c0c730fb77b 100644 --- a/applications/services/storage/storage_external_api.c +++ b/applications/services/storage/storage_external_api.c @@ -119,7 +119,11 @@ bool storage_file_open( furi_event_flag_free(event); FURI_LOG_T( - TAG, "File %p - %p open (%s)", (uint32_t)file - SRAM_BASE, file->file_id - SRAM_BASE, path); + TAG, + "File %p - %p open (%s)", + (void*)((uint32_t)file - SRAM_BASE), + (void*)(file->file_id - SRAM_BASE), + path); return result; } @@ -132,7 +136,11 @@ bool storage_file_close(File* file) { S_API_MESSAGE(StorageCommandFileClose); S_API_EPILOGUE; - FURI_LOG_T(TAG, "File %p - %p closed", (uint32_t)file - SRAM_BASE, file->file_id - SRAM_BASE); + FURI_LOG_T( + TAG, + "File %p - %p closed", + (void*)((uint32_t)file - SRAM_BASE), + (void*)(file->file_id - SRAM_BASE)); file->type = FileTypeClosed; return S_RETURN_BOOL; @@ -291,7 +299,11 @@ bool storage_dir_open(File* file, const char* path) { furi_event_flag_free(event); FURI_LOG_T( - TAG, "Dir %p - %p open (%s)", (uint32_t)file - SRAM_BASE, file->file_id - SRAM_BASE, path); + TAG, + "Dir %p - %p open (%s)", + (void*)((uint32_t)file - SRAM_BASE), + (void*)(file->file_id - SRAM_BASE), + path); return result; } @@ -303,7 +315,11 @@ bool storage_dir_close(File* file) { S_API_MESSAGE(StorageCommandDirClose); S_API_EPILOGUE; - FURI_LOG_T(TAG, "Dir %p - %p closed", (uint32_t)file - SRAM_BASE, file->file_id - SRAM_BASE); + FURI_LOG_T( + TAG, + "Dir %p - %p closed", + (void*)((uint32_t)file - SRAM_BASE), + (void*)(file->file_id - SRAM_BASE)); file->type = FileTypeClosed; @@ -675,7 +691,7 @@ File* storage_file_alloc(Storage* storage) { file->type = FileTypeClosed; file->storage = storage; - FURI_LOG_T(TAG, "File/Dir %p alloc", (uint32_t)file - SRAM_BASE); + FURI_LOG_T(TAG, "File/Dir %p alloc", (void*)((uint32_t)file - SRAM_BASE)); return file; } @@ -697,7 +713,7 @@ void storage_file_free(File* file) { } } - FURI_LOG_T(TAG, "File/Dir %p free", (uint32_t)file - SRAM_BASE); + FURI_LOG_T(TAG, "File/Dir %p free", (void*)((uint32_t)file - SRAM_BASE)); free(file); } diff --git a/applications/services/storage/storages/storage_int.c b/applications/services/storage/storages/storage_int.c index ab0255014ee..4fa5d130c8f 100644 --- a/applications/services/storage/storages/storage_int.c +++ b/applications/services/storage/storages/storage_int.c @@ -77,12 +77,12 @@ static int storage_int_device_read( FURI_LOG_T( TAG, - "Device read: block %d, off %d, buffer: %p, size %d, translated address: %p", + "Device read: block %ld, off %ld, buffer: %p, size %ld, translated address: %p", block, off, buffer, size, - address); + (void*)address); memcpy(buffer, (void*)address, size); @@ -100,12 +100,12 @@ static int storage_int_device_prog( FURI_LOG_T( TAG, - "Device prog: block %d, off %d, buffer: %p, size %d, translated address: %p", + "Device prog: block %ld, off %ld, buffer: %p, size %ld, translated address: %p", block, off, buffer, size, - address); + (void*)address); int ret = 0; while(size > 0) { @@ -122,7 +122,7 @@ static int storage_int_device_erase(const struct lfs_config* c, lfs_block_t bloc LFSData* lfs_data = c->context; size_t page = lfs_data->start_page + block; - FURI_LOG_D(TAG, "Device erase: page %d, translated page: %x", block, page); + FURI_LOG_D(TAG, "Device erase: page %ld, translated page: %x", block, page); furi_hal_flash_erase(page); return 0; @@ -740,8 +740,8 @@ void storage_int_init(StorageData* storage) { LFSData* lfs_data = storage_int_lfs_data_alloc(); FURI_LOG_I( TAG, - "Config: start %p, read %d, write %d, page size: %d, page count: %d, cycles: %d", - lfs_data->start_address, + "Config: start %p, read %ld, write %ld, page size: %ld, page count: %ld, cycles: %ld", + (void*)lfs_data->start_address, lfs_data->config.read_size, lfs_data->config.prog_size, lfs_data->config.block_size, diff --git a/applications/system/updater/util/update_task_worker_flasher.c b/applications/system/updater/util/update_task_worker_flasher.c index b235d0018f8..7358a633412 100644 --- a/applications/system/updater/util/update_task_worker_flasher.c +++ b/applications/system/updater/util/update_task_worker_flasher.c @@ -271,7 +271,7 @@ bool update_task_validate_optionbytes(UpdateTask* update_task) { match = false; FURI_LOG_E( TAG, - "OB MISMATCH: #%d: real %08X != %08X (exp.), full %08X", + "OB MISMATCH: #%d: real %08lX != %08lX (exp.), full %08lX", idx, device_ob_value_masked, ref_value, @@ -289,7 +289,7 @@ bool update_task_validate_optionbytes(UpdateTask* update_task) { (manifest->ob_reference.obs[idx].values.base & manifest->ob_write_mask.obs[idx].values.base); - FURI_LOG_W(TAG, "Fixing up OB byte #%d to %08X", idx, patched_value); + FURI_LOG_W(TAG, "Fixing up OB byte #%d to %08lX", idx, patched_value); ob_dirty = true; bool is_fixed = furi_hal_flash_ob_set_word(idx, patched_value) && @@ -301,7 +301,7 @@ bool update_task_validate_optionbytes(UpdateTask* update_task) { * reference value */ FURI_LOG_W( TAG, - "OB #%d is FUBAR (fixed&masked %08X, not %08X)", + "OB #%d is FUBAR (fixed&masked %08lX, not %08lX)", idx, patched_value, ref_value); @@ -310,7 +310,7 @@ bool update_task_validate_optionbytes(UpdateTask* update_task) { } else { FURI_LOG_D( TAG, - "OB MATCH: #%d: real %08X == %08X (exp.)", + "OB MATCH: #%d: real %08lX == %08lX (exp.)", idx, device_ob_value_masked, ref_value); diff --git a/firmware/targets/f7/ble_glue/gap.c b/firmware/targets/f7/ble_glue/gap.c index 62db30feea3..aa8cd2c90f1 100644 --- a/firmware/targets/f7/ble_glue/gap.c +++ b/firmware/targets/f7/ble_glue/gap.c @@ -184,7 +184,7 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification(void* pckt) { if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagLock)) { FURI_LOG_I(TAG, "Pass key request event. Pin: ******"); } else { - FURI_LOG_I(TAG, "Pass key request event. Pin: %06d", pin); + FURI_LOG_I(TAG, "Pass key request event. Pin: %06ld", pin); } GapEvent event = {.type = GapEventTypePinCodeShow, .data.pin_code = pin}; gap->on_event_cb(event, gap->context); @@ -227,7 +227,7 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification(void* pckt) { case EVT_BLUE_GAP_NUMERIC_COMPARISON_VALUE: { uint32_t pin = ((aci_gap_numeric_comparison_value_event_rp0*)(blue_evt->data))->Numeric_Value; - FURI_LOG_I(TAG, "Verify numeric comparison: %06d", pin); + FURI_LOG_I(TAG, "Verify numeric comparison: %06ld", pin); GapEvent event = {.type = GapEventTypePinCodeVerify, .data.pin_code = pin}; bool result = gap->on_event_cb(event, gap->context); aci_gap_numeric_comparison_value_confirm_yesno(gap->service.connection_handle, result); diff --git a/firmware/targets/f7/ble_glue/serial_service.c b/firmware/targets/f7/ble_glue/serial_service.c index eb58ae0ec35..536557cce94 100644 --- a/firmware/targets/f7/ble_glue/serial_service.c +++ b/firmware/targets/f7/ble_glue/serial_service.c @@ -63,13 +63,13 @@ static SVCCTL_EvtAckStatus_t serial_svc_event_handler(void* event) { .size = attribute_modified->Attr_Data_Length, }}; uint32_t buff_free_size = serial_svc->callback(event, serial_svc->context); - FURI_LOG_D(TAG, "Available buff size: %d", buff_free_size); + FURI_LOG_D(TAG, "Available buff size: %ld", buff_free_size); furi_check(furi_mutex_release(serial_svc->buff_size_mtx) == FuriStatusOk); } ret = SVCCTL_EvtAckFlowEnable; } } else if(blecore_evt->ecode == ACI_GATT_SERVER_CONFIRMATION_VSEVT_CODE) { - FURI_LOG_T(TAG, "Ack received", blecore_evt->ecode); + FURI_LOG_T(TAG, "Ack received"); if(serial_svc->callback) { SerialServiceEvent event = { .event = SerialServiceEventTypeDataSent, diff --git a/firmware/targets/f7/furi_hal/furi_hal_console.h b/firmware/targets/f7/furi_hal/furi_hal_console.h index 104515ce9ce..ce31a66b33f 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_console.h +++ b/firmware/targets/f7/furi_hal/furi_hal_console.h @@ -2,6 +2,7 @@ #include #include +#include #ifdef __cplusplus extern "C" { @@ -27,7 +28,7 @@ void furi_hal_console_tx_with_new_line(const uint8_t* buffer, size_t buffer_size * @param format * @param ... */ -void furi_hal_console_printf(const char format[], ...); +void furi_hal_console_printf(const char format[], ...) _ATTRIBUTE((__format__(__printf__, 1, 2))); void furi_hal_console_puts(const char* data); diff --git a/firmware/targets/f7/furi_hal/furi_hal_flash.c b/firmware/targets/f7/furi_hal/furi_hal_flash.c index f99cf8c3da8..e49cd5f2954 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_flash.c +++ b/firmware/targets/f7/furi_hal/furi_hal_flash.c @@ -83,7 +83,7 @@ void furi_hal_flash_init() { // WRITE_REG(FLASH->SR, FLASH_SR_OPTVERR); /* Actually, reset all error flags on start */ if(READ_BIT(FLASH->SR, FURI_HAL_FLASH_SR_ERRORS)) { - FURI_LOG_E(TAG, "FLASH->SR 0x%08X", FLASH->SR); + FURI_LOG_E(TAG, "FLASH->SR 0x%08lX", FLASH->SR); WRITE_REG(FLASH->SR, FURI_HAL_FLASH_SR_ERRORS); } } @@ -514,10 +514,10 @@ bool furi_hal_flash_ob_set_word(size_t word_idx, const uint32_t value) { FURI_LOG_W( TAG, - "Setting OB reg %d for word %d (addr 0x%08X) to 0x%08X", + "Setting OB reg %d for word %d (addr 0x%08lX) to 0x%08lX", reg_def->ob_reg, word_idx, - reg_def->ob_register_address, + (uint32_t)reg_def->ob_register_address, value); /* 1. Clear OPTLOCK option lock bit with the clearing sequence */ diff --git a/firmware/targets/f7/furi_hal/furi_hal_memory.c b/firmware/targets/f7/furi_hal/furi_hal_memory.c index 43dc56f11b8..9cf2c3120d3 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_memory.c +++ b/firmware/targets/f7/furi_hal/furi_hal_memory.c @@ -55,9 +55,9 @@ void furi_hal_memory_init() { memory->region[SRAM_B].size = sram2b_unprotected_size; FURI_LOG_I( - TAG, "SRAM2A: 0x%p, %d", memory->region[SRAM_A].start, memory->region[SRAM_A].size); + TAG, "SRAM2A: 0x%p, %ld", memory->region[SRAM_A].start, memory->region[SRAM_A].size); FURI_LOG_I( - TAG, "SRAM2B: 0x%p, %d", memory->region[SRAM_B].start, memory->region[SRAM_B].size); + TAG, "SRAM2B: 0x%p, %ld", memory->region[SRAM_B].start, memory->region[SRAM_B].size); if((memory->region[SRAM_A].size > 0) || (memory->region[SRAM_B].size > 0)) { if((memory->region[SRAM_A].size > 0)) { diff --git a/firmware/targets/f7/furi_hal/furi_hal_power.c b/firmware/targets/f7/furi_hal/furi_hal_power.c index bc98f108d4d..86505c573ed 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_power.c +++ b/firmware/targets/f7/furi_hal/furi_hal_power.c @@ -569,13 +569,13 @@ void furi_hal_power_info_get(FuriHalPowerInfoCallback out, void* context) { furi_string_printf(value, "%u", furi_hal_power_get_bat_health_pct()); out("battery_health", furi_string_get_cstr(value), false, context); - furi_string_printf(value, "%u", furi_hal_power_get_battery_remaining_capacity()); + furi_string_printf(value, "%lu", furi_hal_power_get_battery_remaining_capacity()); out("capacity_remain", furi_string_get_cstr(value), false, context); - furi_string_printf(value, "%u", furi_hal_power_get_battery_full_capacity()); + furi_string_printf(value, "%lu", furi_hal_power_get_battery_full_capacity()); out("capacity_full", furi_string_get_cstr(value), false, context); - furi_string_printf(value, "%u", furi_hal_power_get_battery_design_capacity()); + furi_string_printf(value, "%lu", furi_hal_power_get_battery_design_capacity()); out("capacity_design", furi_string_get_cstr(value), true, context); furi_string_free(value); diff --git a/firmware/targets/f7/furi_hal/furi_hal_subghz.c b/firmware/targets/f7/furi_hal/furi_hal_subghz.c index b73d074fd99..5eeb4e9a571 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_subghz.c +++ b/firmware/targets/f7/furi_hal/furi_hal_subghz.c @@ -173,7 +173,7 @@ void furi_hal_subghz_load_custom_preset(uint8_t* preset_data) { i += 2; } for(uint8_t y = i; y < i + 10; y++) { - FURI_LOG_D(TAG, "PA[%lu]: %02X", y, preset_data[y]); + FURI_LOG_D(TAG, "PA[%u]: %02X", y, preset_data[y]); } } } diff --git a/furi/core/log.h b/furi/core/log.h index 30026fc44ba..cb8b3d9cc30 100644 --- a/furi/core/log.h +++ b/furi/core/log.h @@ -51,7 +51,8 @@ void furi_log_init(); * @param format * @param ... */ -void furi_log_print_format(FuriLogLevel level, const char* tag, const char* format, ...); +void furi_log_print_format(FuriLogLevel level, const char* tag, const char* format, ...) + _ATTRIBUTE((__format__(__printf__, 3, 4))); /** Set log level * diff --git a/furi/core/string.h b/furi/core/string.h index b6700c9cade..01b26cce107 100644 --- a/furi/core/string.h +++ b/furi/core/string.h @@ -57,7 +57,8 @@ FuriString* furi_string_alloc_set_str(const char cstr_source[]); * @param ... * @return FuriString* */ -FuriString* furi_string_alloc_printf(const char format[], ...); +FuriString* furi_string_alloc_printf(const char format[], ...) + _ATTRIBUTE((__format__(__printf__, 1, 2))); /** * @brief Allocate new FuriString and printf to it. @@ -214,7 +215,8 @@ void furi_string_set_n(FuriString* string, const FuriString* source, size_t offs * @param ... * @return int */ -int furi_string_printf(FuriString* string, const char format[], ...); +int furi_string_printf(FuriString* string, const char format[], ...) + _ATTRIBUTE((__format__(__printf__, 2, 3))); /** * @brief Format in the string the given printf format @@ -259,7 +261,8 @@ void furi_string_cat_str(FuriString* string_1, const char cstring_2[]); * @param ... * @return int */ -int furi_string_cat_printf(FuriString* string, const char format[], ...); +int furi_string_cat_printf(FuriString* string, const char format[], ...) + _ATTRIBUTE((__format__(__printf__, 2, 3))); /** * @brief Append to the string the formatted string of the given printf format. diff --git a/lib/lfrfid/lfrfid_raw_worker.c b/lib/lfrfid/lfrfid_raw_worker.c index 9bab77db1dd..1547d20f958 100644 --- a/lib/lfrfid/lfrfid_raw_worker.c +++ b/lib/lfrfid/lfrfid_raw_worker.c @@ -338,7 +338,7 @@ static int32_t lfrfid_raw_emulate_worker_thread(void* thread_context) { } if(data->ctx.overrun_count) { - FURI_LOG_E(TAG_EMULATE, "overruns: %lu", data->ctx.overrun_count); + FURI_LOG_E(TAG_EMULATE, "overruns: %u", data->ctx.overrun_count); } furi_stream_buffer_free(data->ctx.stream); diff --git a/lib/lfrfid/protocols/protocol_gallagher.c b/lib/lfrfid/protocols/protocol_gallagher.c index 460c23a39b5..4720d3a4df0 100644 --- a/lib/lfrfid/protocols/protocol_gallagher.c +++ b/lib/lfrfid/protocols/protocol_gallagher.c @@ -276,7 +276,7 @@ void protocol_gallagher_render_data(ProtocolGallagher* protocol, FuriString* res uint32_t card_id = bit_lib_get_bits_32(protocol->data, 32, 32); furi_string_cat_printf(result, "Region: %u, Issue Level: %u\r\n", rc, il); - furi_string_cat_printf(result, "FC: %u, C: %lu\r\n", fc, card_id); + furi_string_cat_printf(result, "FC: %lu, C: %lu\r\n", fc, card_id); }; const ProtocolBase protocol_gallagher = { diff --git a/lib/lfrfid/protocols/protocol_keri.c b/lib/lfrfid/protocols/protocol_keri.c index 099fd1680ef..f0a12863e3b 100644 --- a/lib/lfrfid/protocols/protocol_keri.c +++ b/lib/lfrfid/protocols/protocol_keri.c @@ -218,7 +218,7 @@ void protocol_keri_render_data(ProtocolKeri* protocol, FuriString* result) { uint32_t fc = 0; uint32_t cn = 0; protocol_keri_descramble(&fc, &cn, &data); - furi_string_printf(result, "Internal ID: %u\r\nFC: %u, Card: %u\r\n", internal_id, fc, cn); + furi_string_printf(result, "Internal ID: %lu\r\nFC: %lu, Card: %lu\r\n", internal_id, fc, cn); } bool protocol_keri_write_data(ProtocolKeri* protocol, void* data) { diff --git a/lib/lfrfid/protocols/protocol_paradox.c b/lib/lfrfid/protocols/protocol_paradox.c index 6a8e33ba407..7e029f1de24 100644 --- a/lib/lfrfid/protocols/protocol_paradox.c +++ b/lib/lfrfid/protocols/protocol_paradox.c @@ -142,7 +142,7 @@ void protocol_paradox_render_data(ProtocolParadox* protocol, FuriString* result) uint16_t card_id = bit_lib_get_bits_16(decoded_data, 18, 16); furi_string_cat_printf(result, "Facility: %u\r\n", fc); - furi_string_cat_printf(result, "Card: %lu\r\n", card_id); + furi_string_cat_printf(result, "Card: %u\r\n", card_id); furi_string_cat_printf(result, "Data: "); for(size_t i = 0; i < PARADOX_DECODED_DATA_SIZE; i++) { furi_string_cat_printf(result, "%02X", decoded_data[i]); diff --git a/lib/lfrfid/protocols/protocol_pyramid.c b/lib/lfrfid/protocols/protocol_pyramid.c index 5457445809e..974bb6da677 100644 --- a/lib/lfrfid/protocols/protocol_pyramid.c +++ b/lib/lfrfid/protocols/protocol_pyramid.c @@ -243,7 +243,7 @@ void protocol_pyramid_render_data(ProtocolPyramid* protocol, FuriString* result) uint8_t* decoded_data = protocol->data; uint8_t format_length = decoded_data[0]; - furi_string_cat_printf(result, "Format: 26\r\n", format_length); + furi_string_cat_printf(result, "Format: %d\r\n", format_length); if(format_length == 26) { uint8_t facility; bit_lib_copy_bits(&facility, 0, 8, decoded_data, 8); diff --git a/lib/nfc/helpers/mf_classic_dict.c b/lib/nfc/helpers/mf_classic_dict.c index 356f6f7c771..a842ed921b5 100644 --- a/lib/nfc/helpers/mf_classic_dict.c +++ b/lib/nfc/helpers/mf_classic_dict.c @@ -87,7 +87,7 @@ MfClassicDict* mf_classic_dict_alloc(MfClassicDictType dict_type) { stream_rewind(dict->stream); dict_loaded = true; - FURI_LOG_I(TAG, "Loaded dictionary with %d keys", dict->total_keys); + FURI_LOG_I(TAG, "Loaded dictionary with %ld keys", dict->total_keys); } while(false); if(!dict_loaded) { diff --git a/lib/nfc/helpers/mfkey32.c b/lib/nfc/helpers/mfkey32.c index fa5713f270a..47e7e9f6b39 100644 --- a/lib/nfc/helpers/mfkey32.c +++ b/lib/nfc/helpers/mfkey32.c @@ -92,7 +92,7 @@ void mfkey32_set_callback(Mfkey32* instance, Mfkey32ParseDataCallback callback, static bool mfkey32_write_params(Mfkey32* instance, Mfkey32Params* params) { FuriString* str = furi_string_alloc_printf( - "Sec %d key %c cuid %08x nt0 %08x nr0 %08x ar0 %08x nt1 %08x nr1 %08x ar1 %08x\n", + "Sec %d key %c cuid %08lx nt0 %08lx nr0 %08lx ar0 %08lx nt1 %08lx nr1 %08lx ar1 %08lx\n", params->sector, params->key == MfClassicKeyA ? 'A' : 'B', params->cuid, diff --git a/lib/nfc/nfc_worker.c b/lib/nfc/nfc_worker.c index c61ad444c60..732453465ab 100644 --- a/lib/nfc/nfc_worker.c +++ b/lib/nfc/nfc_worker.c @@ -522,7 +522,8 @@ void nfc_worker_mf_classic_dict_attack(NfcWorker* nfc_worker) { return; } - FURI_LOG_D(TAG, "Start Dictionary attack, Key Count %d", mf_classic_dict_get_total_keys(dict)); + FURI_LOG_D( + TAG, "Start Dictionary attack, Key Count %ld", mf_classic_dict_get_total_keys(dict)); for(size_t i = 0; i < total_sectors; i++) { FURI_LOG_I(TAG, "Sector %d", i); nfc_worker->callback(NfcWorkerEventNewSector, nfc_worker->context); diff --git a/lib/nfc/parsers/all_in_one.c b/lib/nfc/parsers/all_in_one.c index f63fb7e6de3..05cfe576437 100644 --- a/lib/nfc/parsers/all_in_one.c +++ b/lib/nfc/parsers/all_in_one.c @@ -108,6 +108,6 @@ bool all_in_one_parser_parse(NfcDeviceData* dev_data) { // Format string for rides count furi_string_printf( - dev_data->parsed_data, "\e#All-In-One\nNumber: %u\nRides left: %u", serial, ride_count); + dev_data->parsed_data, "\e#All-In-One\nNumber: %lu\nRides left: %u", serial, ride_count); return true; } \ No newline at end of file diff --git a/lib/nfc/parsers/plantain_4k_parser.c b/lib/nfc/parsers/plantain_4k_parser.c index eddebac23f6..348b5a64ca0 100644 --- a/lib/nfc/parsers/plantain_4k_parser.c +++ b/lib/nfc/parsers/plantain_4k_parser.c @@ -132,7 +132,7 @@ bool plantain_4k_parser_parse(NfcDeviceData* dev_data) { furi_string_printf( dev_data->parsed_data, - "\e#Plantain\nN:%s\nBalance:%d\n", + "\e#Plantain\nN:%s\nBalance:%ld\n", furi_string_get_cstr(card_number_str), balance); furi_string_free(card_number_str); diff --git a/lib/nfc/parsers/plantain_parser.c b/lib/nfc/parsers/plantain_parser.c index ac7a90b2466..5328b5c4f31 100644 --- a/lib/nfc/parsers/plantain_parser.c +++ b/lib/nfc/parsers/plantain_parser.c @@ -105,7 +105,7 @@ bool plantain_parser_parse(NfcDeviceData* dev_data) { furi_string_printf( dev_data->parsed_data, - "\e#Plantain\nN:%s\nBalance:%d\n", + "\e#Plantain\nN:%s\nBalance:%ld\n", furi_string_get_cstr(card_number_str), balance); furi_string_free(card_number_str); diff --git a/lib/nfc/parsers/two_cities.c b/lib/nfc/parsers/two_cities.c index f5e31d51b18..2c6184a71ff 100644 --- a/lib/nfc/parsers/two_cities.c +++ b/lib/nfc/parsers/two_cities.c @@ -149,7 +149,7 @@ bool two_cities_parser_parse(NfcDeviceData* dev_data) { furi_string_printf( dev_data->parsed_data, - "\e#Troika+Plantain\nPN: %s\nPB: %d rur.\nTN: %d\nTB: %d rur.\n", + "\e#Troika+Plantain\nPN: %s\nPB: %ld rur.\nTN: %ld\nTB: %d rur.\n", furi_string_get_cstr(card_number_str), balance, troika_number, diff --git a/lib/nfc/protocols/mifare_classic.c b/lib/nfc/protocols/mifare_classic.c index 3b53439329a..783202451d0 100644 --- a/lib/nfc/protocols/mifare_classic.c +++ b/lib/nfc/protocols/mifare_classic.c @@ -847,7 +847,7 @@ bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_ FURI_LOG_D( TAG, - "%08x key%c block %d nt/nr/ar: %08x %08x %08x", + "%08lx key%c block %d nt/nr/ar: %08lx %08lx %08lx", emulator->cuid, access_key == MfClassicKeyA ? 'A' : 'B', sector_trailer_block, @@ -858,7 +858,7 @@ bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_ 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)); + FURI_LOG_T(TAG, "Wrong AUTH! %08lX != %08lX", cardRr, prng_successor(nonce, 64)); // Don't send NACK, as the tag doesn't send it command_processed = true; break; diff --git a/lib/nfc/protocols/mifare_desfire.c b/lib/nfc/protocols/mifare_desfire.c index 9dd37f1ee83..b2247bf2090 100644 --- a/lib/nfc/protocols/mifare_desfire.c +++ b/lib/nfc/protocols/mifare_desfire.c @@ -108,7 +108,7 @@ void mf_df_cat_version(MifareDesfireVersion* version, FuriString* out) { } void mf_df_cat_free_mem(MifareDesfireFreeMemory* free_mem, FuriString* out) { - furi_string_cat_printf(out, "freeMem %d\n", free_mem->bytes); + furi_string_cat_printf(out, "freeMem %ld\n", free_mem->bytes); } void mf_df_cat_key_settings(MifareDesfireKeySettings* ks, FuriString* out) { @@ -191,10 +191,10 @@ void mf_df_cat_file(MifareDesfireFile* file, FuriString* out) { case MifareDesfireFileTypeValue: size = 4; furi_string_cat_printf( - out, "lo %d hi %d\n", file->settings.value.lo_limit, file->settings.value.hi_limit); + out, "lo %ld hi %ld\n", file->settings.value.lo_limit, file->settings.value.hi_limit); furi_string_cat_printf( out, - "limit %d enabled %d\n", + "limit %ld enabled %d\n", file->settings.value.limited_credit_value, file->settings.value.limited_credit_enabled); break; @@ -203,7 +203,7 @@ void mf_df_cat_file(MifareDesfireFile* file, FuriString* out) { size = file->settings.record.size; num = file->settings.record.cur; furi_string_cat_printf(out, "size %d\n", size); - furi_string_cat_printf(out, "num %d max %d\n", num, file->settings.record.max); + furi_string_cat_printf(out, "num %d max %ld\n", num, file->settings.record.max); break; } uint8_t* data = file->contents; diff --git a/lib/nfc/protocols/mifare_ultralight.c b/lib/nfc/protocols/mifare_ultralight.c index ffabe88ab6b..a8d1f5548e4 100644 --- a/lib/nfc/protocols/mifare_ultralight.c +++ b/lib/nfc/protocols/mifare_ultralight.c @@ -193,7 +193,7 @@ bool mf_ultralight_authenticate(FuriHalNfcTxRxContext* tx_rx, uint32_t key, uint *pack = (tx_rx->rx_data[1] << 8) | tx_rx->rx_data[0]; } - FURI_LOG_I(TAG, "Auth success. Password: %08X. PACK: %04X", key, *pack); + FURI_LOG_I(TAG, "Auth success. Password: %08lX. PACK: %04X", key, *pack); authenticated = true; } while(false); @@ -1050,7 +1050,7 @@ static void mf_ul_make_ascii_mirror(MfUltralightEmulator* emulator, FuriString* if(mirror_conf == MfUltralightMirrorUidCounter) furi_string_cat(str, uid_printed ? "x" : " "); - furi_string_cat_printf(str, "%06X", emulator->data.counter[2]); + furi_string_cat_printf(str, "%06lX", emulator->data.counter[2]); } } } diff --git a/lib/print/printf_tiny.h b/lib/print/printf_tiny.h index 8f292819e0a..58f6a673b7b 100644 --- a/lib/print/printf_tiny.h +++ b/lib/print/printf_tiny.h @@ -34,6 +34,7 @@ #include #include +#include #ifdef __cplusplus extern "C" { @@ -54,7 +55,7 @@ void _putchar(char character); * \param format A string that specifies the format of the output * \return The number of characters that are written into the array, not counting the terminating null character */ -int printf_(const char* format, ...); +int printf_(const char* format, ...) _ATTRIBUTE((__format__(__printf__, 1, 2))); /** * Tiny sprintf implementation @@ -63,7 +64,7 @@ int printf_(const char* format, ...); * \param format A string that specifies the format of the output * \return The number of characters that are WRITTEN into the buffer, not counting the terminating null character */ -int sprintf_(char* buffer, const char* format, ...); +int sprintf_(char* buffer, const char* format, ...) _ATTRIBUTE((__format__(__printf__, 2, 3))); /** * Tiny snprintf/vsnprintf implementation @@ -75,7 +76,8 @@ int sprintf_(char* buffer, const char* format, ...); * null character. A value equal or larger than count indicates truncation. Only when the returned value * is non-negative and less than count, the string has been completely written. */ -int snprintf_(char* buffer, size_t count, const char* format, ...); +int snprintf_(char* buffer, size_t count, const char* format, ...) + _ATTRIBUTE((__format__(__printf__, 3, 4))); int vsnprintf_(char* buffer, size_t count, const char* format, va_list va); /** @@ -94,7 +96,8 @@ int vprintf_(const char* format, va_list va); * \param format A string that specifies the format of the output * \return The number of characters that are sent to the output function, not counting the terminating null character */ -int fctprintf(void (*out)(char character, void* arg), void* arg, const char* format, ...); +int fctprintf(void (*out)(char character, void* arg), void* arg, const char* format, ...) + _ATTRIBUTE((__format__(__printf__, 3, 4))); #ifdef __cplusplus } diff --git a/lib/subghz/protocols/came_atomo.c b/lib/subghz/protocols/came_atomo.c index 1349d976ecc..8c2a542c2c3 100644 --- a/lib/subghz/protocols/came_atomo.c +++ b/lib/subghz/protocols/came_atomo.c @@ -338,7 +338,7 @@ void subghz_protocol_decoder_came_atomo_get_string(void* context, FuriString* ou "%s %db\r\n" "Key:0x%lX%08lX\r\n" "Sn:0x%08lX Btn:0x%01X\r\n" - "Cnt:0x%03X\r\n", + "Cnt:0x%03lX\r\n", instance->generic.protocol_name, instance->generic.data_count_bit, diff --git a/lib/subghz/protocols/came_twee.c b/lib/subghz/protocols/came_twee.c index 65667be2170..3d5029ec979 100644 --- a/lib/subghz/protocols/came_twee.c +++ b/lib/subghz/protocols/came_twee.c @@ -457,7 +457,7 @@ void subghz_protocol_decoder_came_twee_get_string(void* context, FuriString* out output, "%s %db\r\n" "Key:0x%lX%08lX\r\n" - "Btn:%lX\r\n" + "Btn:%X\r\n" "DIP:" DIP_PATTERN "\r\n", instance->generic.protocol_name, instance->generic.data_count_bit, diff --git a/lib/subghz/protocols/doitrand.c b/lib/subghz/protocols/doitrand.c index eeab2576e54..5c340214448 100644 --- a/lib/subghz/protocols/doitrand.c +++ b/lib/subghz/protocols/doitrand.c @@ -345,7 +345,7 @@ void subghz_protocol_decoder_doitrand_get_string(void* context, FuriString* outp output, "%s %dbit\r\n" "Key:%02lX%08lX\r\n" - "Btn:%lX\r\n" + "Btn:%X\r\n" "DIP:" DIP_PATTERN "\r\n", instance->generic.protocol_name, instance->generic.data_count_bit, diff --git a/lib/subghz/protocols/faac_slh.c b/lib/subghz/protocols/faac_slh.c index 75f8d4e9748..b544359025c 100644 --- a/lib/subghz/protocols/faac_slh.c +++ b/lib/subghz/protocols/faac_slh.c @@ -222,7 +222,7 @@ void subghz_protocol_decoder_faac_slh_get_string(void* context, FuriString* outp "Key:%lX%08lX\r\n" "Fix:%08lX \r\n" "Hop:%08lX \r\n" - "Sn:%07lX Btn:%lX\r\n", + "Sn:%07lX Btn:%X\r\n", instance->generic.protocol_name, instance->generic.data_count_bit, (uint32_t)(instance->generic.data >> 32), diff --git a/lib/subghz/protocols/gate_tx.c b/lib/subghz/protocols/gate_tx.c index 73a5092612a..5cf3a87141f 100644 --- a/lib/subghz/protocols/gate_tx.c +++ b/lib/subghz/protocols/gate_tx.c @@ -325,7 +325,7 @@ void subghz_protocol_decoder_gate_tx_get_string(void* context, FuriString* outpu output, "%s %dbit\r\n" "Key:%06lX\r\n" - "Sn:%05lX Btn:%lX\r\n", + "Sn:%05lX Btn:%X\r\n", instance->generic.protocol_name, instance->generic.data_count_bit, (uint32_t)(instance->generic.data & 0xFFFFFF), diff --git a/lib/subghz/protocols/honeywell_wdb.c b/lib/subghz/protocols/honeywell_wdb.c index 326e79f830f..3cd62698dcf 100644 --- a/lib/subghz/protocols/honeywell_wdb.c +++ b/lib/subghz/protocols/honeywell_wdb.c @@ -385,7 +385,7 @@ void subghz_protocol_decoder_honeywell_wdb_get_string(void* context, FuriString* "Key:0x%lX%08lX\r\n" "Sn:0x%05lX\r\n" "DT:%s Al:%s\r\n" - "SK:%01lX R:%01lX LBat:%01lX\r\n", + "SK:%01X R:%01X LBat:%01X\r\n", instance->generic.protocol_name, instance->generic.data_count_bit, (uint32_t)((instance->generic.data >> 32) & 0xFFFFFFFF), diff --git a/lib/subghz/protocols/ido.c b/lib/subghz/protocols/ido.c index a0332f9e5a9..6cc60bde823 100644 --- a/lib/subghz/protocols/ido.c +++ b/lib/subghz/protocols/ido.c @@ -221,7 +221,7 @@ void subghz_protocol_decoder_ido_get_string(void* context, FuriString* output) { "Key:0x%lX%08lX\r\n" "Fix:%06lX \r\n" "Hop:%06lX \r\n" - "Sn:%05lX Btn:%lX\r\n", + "Sn:%05lX Btn:%X\r\n", instance->generic.protocol_name, instance->generic.data_count_bit, (uint32_t)(instance->generic.data >> 32), diff --git a/lib/subghz/protocols/keeloq.c b/lib/subghz/protocols/keeloq.c index 2865309eece..039d97a6a54 100644 --- a/lib/subghz/protocols/keeloq.c +++ b/lib/subghz/protocols/keeloq.c @@ -700,8 +700,8 @@ void subghz_protocol_decoder_keeloq_get_string(void* context, FuriString* output output, "%s %dbit\r\n" "Key:%08lX%08lX\r\n" - "Fix:0x%08lX Cnt:%04X\r\n" - "Hop:0x%08lX Btn:%01lX\r\n" + "Fix:0x%08lX Cnt:%04lX\r\n" + "Hop:0x%08lX Btn:%01X\r\n" "MF:%s\r\n" "Sn:0x%07lX \r\n", instance->generic.protocol_name, diff --git a/lib/subghz/protocols/kia.c b/lib/subghz/protocols/kia.c index aad26f16aa9..d46838661b2 100644 --- a/lib/subghz/protocols/kia.c +++ b/lib/subghz/protocols/kia.c @@ -268,7 +268,7 @@ void subghz_protocol_decoder_kia_get_string(void* context, FuriString* output) { output, "%s %dbit\r\n" "Key:%08lX%08lX\r\n" - "Sn:%07lX Btn:%lX Cnt:%04X\r\n", + "Sn:%07lX Btn:%X Cnt:%04lX\r\n", instance->generic.protocol_name, instance->generic.data_count_bit, code_found_hi, diff --git a/lib/subghz/protocols/magellen.c b/lib/subghz/protocols/magellen.c index 0c801c08e5a..160ec4a2cab 100644 --- a/lib/subghz/protocols/magellen.c +++ b/lib/subghz/protocols/magellen.c @@ -432,7 +432,7 @@ void subghz_protocol_decoder_magellen_get_string(void* context, FuriString* outp output, "%s %dbit\r\n" "Key:0x%08lX\r\n" - "Sn:%03d%03d, Event:0x%02X\r\n" + "Sn:%03ld%03ld, Event:0x%02X\r\n" "Stat:", instance->generic.protocol_name, instance->generic.data_count_bit, diff --git a/lib/subghz/protocols/marantec.c b/lib/subghz/protocols/marantec.c index 1c3997b97c1..a72238a412b 100644 --- a/lib/subghz/protocols/marantec.c +++ b/lib/subghz/protocols/marantec.c @@ -383,7 +383,7 @@ void subghz_protocol_decoder_marantec_get_string(void* context, FuriString* outp "%s %db\r\n" "Key:0x%lX%08lX\r\n" "Sn:0x%07lX \r\n" - "Btn:%lX\r\n", + "Btn:%X\r\n", instance->generic.protocol_name, instance->generic.data_count_bit, (uint32_t)(instance->generic.data >> 32), diff --git a/lib/subghz/protocols/megacode.c b/lib/subghz/protocols/megacode.c index 40f22cfdb4f..baa8188d33e 100644 --- a/lib/subghz/protocols/megacode.c +++ b/lib/subghz/protocols/megacode.c @@ -417,8 +417,8 @@ void subghz_protocol_decoder_megacode_get_string(void* context, FuriString* outp output, "%s %dbit\r\n" "Key:0x%06lX\r\n" - "Sn:0x%04lX - %d\r\n" - "Facility:%X Btn:%X\r\n", + "Sn:0x%04lX - %ld\r\n" + "Facility:%lX Btn:%X\r\n", instance->generic.protocol_name, instance->generic.data_count_bit, (uint32_t)instance->generic.data, diff --git a/lib/subghz/protocols/nice_flor_s.c b/lib/subghz/protocols/nice_flor_s.c index ffd94811f2b..d567ddf2c99 100644 --- a/lib/subghz/protocols/nice_flor_s.c +++ b/lib/subghz/protocols/nice_flor_s.c @@ -368,7 +368,7 @@ void subghz_protocol_decoder_nice_flor_s_get_string(void* context, FuriString* o "%s %dbit\r\n" "Key:0x%lX%08lX\r\n" "Sn:%05lX\r\n" - "Cnt:%04X Btn:%02lX\r\n", + "Cnt:%04lX Btn:%02X\r\n", instance->generic.protocol_name, instance->generic.data_count_bit, code_found_hi, diff --git a/lib/subghz/protocols/oregon2.c b/lib/subghz/protocols/oregon2.c index 48435502fc9..561df819f64 100644 --- a/lib/subghz/protocols/oregon2.c +++ b/lib/subghz/protocols/oregon2.c @@ -253,7 +253,7 @@ static void val = ((var_data >> 4) & 0xF) * 10 + ((var_data >> 8) & 0xF); furi_string_cat_printf( output, - "Temp: %s%d.%d C\r\n", + "Temp: %s%ld.%ld C\r\n", (var_data & 0xF) ? "-" : "+", val, (uint32_t)(var_data >> 12) & 0xF); @@ -286,7 +286,7 @@ void subghz_protocol_decoder_oregon2_get_string(void* context, FuriString* outpu furi_string_cat_printf( output, "%s\r\n" - "ID: 0x%04lX, ch: %d%s, rc: 0x%02lX\r\n", + "ID: 0x%04lX, ch: %ld%s, rc: 0x%02lX\r\n", instance->generic.protocol_name, (uint32_t)sensor_id, (uint32_t)(instance->generic.data >> 12) & 0xF, diff --git a/lib/subghz/protocols/phoenix_v2.c b/lib/subghz/protocols/phoenix_v2.c index e97df9b65f7..019c3ec25aa 100644 --- a/lib/subghz/protocols/phoenix_v2.c +++ b/lib/subghz/protocols/phoenix_v2.c @@ -329,7 +329,7 @@ void subghz_protocol_decoder_phoenix_v2_get_string(void* context, FuriString* ou "%s %dbit\r\n" "Key:%02lX%08lX\r\n" "Sn:0x%07lX \r\n" - "Btn:%lX\r\n", + "Btn:%X\r\n", instance->generic.protocol_name, instance->generic.data_count_bit, (uint32_t)(instance->generic.data >> 32) & 0xFFFFFFFF, diff --git a/lib/subghz/protocols/princeton.c b/lib/subghz/protocols/princeton.c index c981de1b836..370f33fb02e 100644 --- a/lib/subghz/protocols/princeton.c +++ b/lib/subghz/protocols/princeton.c @@ -364,7 +364,7 @@ void subghz_protocol_decoder_princeton_get_string(void* context, FuriString* out "Key:0x%08lX\r\n" "Yek:0x%08lX\r\n" "Sn:0x%05lX Btn:%01X\r\n" - "Te:%dus\r\n", + "Te:%ldus\r\n", instance->generic.protocol_name, instance->generic.data_count_bit, (uint32_t)(instance->generic.data & 0xFFFFFF), diff --git a/lib/subghz/protocols/princeton_for_testing.c b/lib/subghz/protocols/princeton_for_testing.c index 43078d2e230..0987e0ad693 100644 --- a/lib/subghz/protocols/princeton_for_testing.c +++ b/lib/subghz/protocols/princeton_for_testing.c @@ -94,7 +94,7 @@ void subghz_encoder_princeton_for_testing_print_log(void* context) { ((float)instance->time_high / (instance->time_high + instance->time_low)) * 100; FURI_LOG_I( TAG "Encoder", - "Radio tx_time=%dus ON=%dus, OFF=%dus, DutyCycle=%d,%d%%", + "Radio tx_time=%ldus ON=%ldus, OFF=%ldus, DutyCycle=%ld,%ld%%", instance->time_high + instance->time_low, instance->time_high, instance->time_low, diff --git a/lib/subghz/protocols/raw.c b/lib/subghz/protocols/raw.c index 48c3ff4df7b..fd234502a63 100644 --- a/lib/subghz/protocols/raw.c +++ b/lib/subghz/protocols/raw.c @@ -115,7 +115,7 @@ bool subghz_protocol_raw_save_to_file_init( // Open file if(!flipper_format_file_open_always( instance->flipper_file, furi_string_get_cstr(temp_str))) { - FURI_LOG_E(TAG, "Unable to open file for write: %s", temp_str); + FURI_LOG_E(TAG, "Unable to open file for write: %s", furi_string_get_cstr(temp_str)); break; } diff --git a/lib/subghz/protocols/scher_khan.c b/lib/subghz/protocols/scher_khan.c index 617efbbab49..837dcf05839 100644 --- a/lib/subghz/protocols/scher_khan.c +++ b/lib/subghz/protocols/scher_khan.c @@ -274,7 +274,7 @@ void subghz_protocol_decoder_scher_khan_get_string(void* context, FuriString* ou output, "%s %dbit\r\n" "Key:0x%lX%08lX\r\n" - "Sn:%07lX Btn:%lX Cnt:%04X\r\n" + "Sn:%07lX Btn:%X Cnt:%04lX\r\n" "Pt: %s\r\n", instance->generic.protocol_name, instance->generic.data_count_bit, diff --git a/lib/subghz/protocols/secplus_v1.c b/lib/subghz/protocols/secplus_v1.c index 885615b6e6c..cfe6bdee53a 100644 --- a/lib/subghz/protocols/secplus_v1.c +++ b/lib/subghz/protocols/secplus_v1.c @@ -606,7 +606,7 @@ void subghz_protocol_decoder_secplus_v1_get_string(void* context, FuriString* ou furi_string_cat_printf( output, "Sn:0x%08lX\r\n" - "Cnt:0x%03X\r\n" + "Cnt:0x%03lX\r\n" "Sw_id:0x%X\r\n", instance->generic.serial, instance->generic.cnt, @@ -625,7 +625,7 @@ void subghz_protocol_decoder_secplus_v1_get_string(void* context, FuriString* ou furi_string_cat_printf( output, "Sn:0x%08lX\r\n" - "Cnt:0x%03X\r\n" + "Cnt:0x%03lX\r\n" "Sw_id:0x%X\r\n", instance->generic.serial, instance->generic.cnt, diff --git a/lib/subghz/protocols/secplus_v2.c b/lib/subghz/protocols/secplus_v2.c index 3c9b966a935..73bb7802b39 100644 --- a/lib/subghz/protocols/secplus_v2.c +++ b/lib/subghz/protocols/secplus_v2.c @@ -821,7 +821,7 @@ void subghz_protocol_decoder_secplus_v2_get_string(void* context, FuriString* ou "Pk1:0x%lX%08lX\r\n" "Pk2:0x%lX%08lX\r\n" "Sn:0x%08lX Btn:0x%01X\r\n" - "Cnt:0x%03X\r\n", + "Cnt:0x%03lX\r\n", instance->generic.protocol_name, instance->generic.data_count_bit, diff --git a/lib/subghz/protocols/somfy_keytis.c b/lib/subghz/protocols/somfy_keytis.c index e1b750749e8..cf627ef3732 100644 --- a/lib/subghz/protocols/somfy_keytis.c +++ b/lib/subghz/protocols/somfy_keytis.c @@ -437,7 +437,7 @@ void subghz_protocol_decoder_somfy_keytis_get_string(void* context, FuriString* "%s %db\r\n" "%lX%08lX%06lX\r\n" "Sn:0x%06lX \r\n" - "Cnt:0x%04X\r\n" + "Cnt:0x%04lX\r\n" "Btn:%s\r\n", instance->generic.protocol_name, diff --git a/lib/subghz/protocols/somfy_telis.c b/lib/subghz/protocols/somfy_telis.c index 1dfdf0c4374..6e375bb384f 100644 --- a/lib/subghz/protocols/somfy_telis.c +++ b/lib/subghz/protocols/somfy_telis.c @@ -374,7 +374,7 @@ void subghz_protocol_decoder_somfy_telis_get_string(void* context, FuriString* o "%s %db\r\n" "Key:0x%lX%08lX\r\n" "Sn:0x%06lX \r\n" - "Cnt:0x%04X\r\n" + "Cnt:0x%04lX\r\n" "Btn:%s\r\n", instance->generic.protocol_name, diff --git a/lib/subghz/protocols/star_line.c b/lib/subghz/protocols/star_line.c index a8b0dad7342..4b89bd2bf4a 100644 --- a/lib/subghz/protocols/star_line.c +++ b/lib/subghz/protocols/star_line.c @@ -373,8 +373,8 @@ void subghz_protocol_decoder_star_line_get_string(void* context, FuriString* out output, "%s %dbit\r\n" "Key:%08lX%08lX\r\n" - "Fix:0x%08lX Cnt:%04X\r\n" - "Hop:0x%08lX Btn:%02lX\r\n" + "Fix:0x%08lX Cnt:%04lX\r\n" + "Hop:0x%08lX Btn:%02X\r\n" "MF:%s\r\n" "Sn:0x%07lX \r\n", instance->generic.protocol_name, diff --git a/lib/toolbox/stream/stream.h b/lib/toolbox/stream/stream.h index 1ffb40445f9..fc3855102cb 100644 --- a/lib/toolbox/stream/stream.h +++ b/lib/toolbox/stream/stream.h @@ -143,7 +143,8 @@ size_t stream_write_cstring(Stream* stream, const char* string); * @param ... * @return size_t how many bytes was written */ -size_t stream_write_format(Stream* stream, const char* format, ...); +size_t stream_write_format(Stream* stream, const char* format, ...) + _ATTRIBUTE((__format__(__printf__, 2, 3))); /** * Write formatted string to the stream, va_list version @@ -200,7 +201,8 @@ bool stream_insert_cstring(Stream* stream, const char* string); * @return true if the operation was successful * @return false on error */ -bool stream_insert_format(Stream* stream, const char* format, ...); +bool stream_insert_format(Stream* stream, const char* format, ...) + _ATTRIBUTE((__format__(__printf__, 2, 3))); /** * Insert formatted string to the stream, va_list version @@ -251,7 +253,8 @@ bool stream_delete_and_insert_cstring(Stream* stream, size_t delete_size, const * @return true if the operation was successful * @return false on error */ -bool stream_delete_and_insert_format(Stream* stream, size_t delete_size, const char* format, ...); +bool stream_delete_and_insert_format(Stream* stream, size_t delete_size, const char* format, ...) + _ATTRIBUTE((__format__(__printf__, 3, 4))); /** * Delete N chars from the stream and insert formatted string to the stream, va_list version From 1a1f711897ad3080e903092d93e56d9cd63fa3d7 Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Fri, 7 Oct 2022 16:58:36 +0300 Subject: [PATCH 122/824] Signal Generator app: UI update (#1829) * Signal Generator: UI update * icons renamed Co-authored-by: Aleksandr Kutuzov --- .../scenes/signal_gen_scene_mco.c | 19 +++++++++++++++--- .../scenes/signal_gen_scene_start.c | 4 ++-- .../signal_generator/views/signal_gen_pwm.c | 16 +++++++-------- assets/icons/Interface/SmallArrowDown_3x5.png | Bin 0 -> 3592 bytes assets/icons/Interface/SmallArrowUp_3x5.png | Bin 0 -> 7976 bytes firmware/targets/f7/api_symbols.csv | 4 +++- 6 files changed, 29 insertions(+), 14 deletions(-) create mode 100644 assets/icons/Interface/SmallArrowDown_3x5.png create mode 100644 assets/icons/Interface/SmallArrowUp_3x5.png diff --git a/applications/plugins/signal_generator/scenes/signal_gen_scene_mco.c b/applications/plugins/signal_generator/scenes/signal_gen_scene_mco.c index 632b08c75e4..0855cde0a0b 100644 --- a/applications/plugins/signal_generator/scenes/signal_gen_scene_mco.c +++ b/applications/plugins/signal_generator/scenes/signal_gen_scene_mco.c @@ -1,12 +1,17 @@ #include "../signal_gen_app_i.h" typedef enum { + LineIndexPin, LineIndexSource, LineIndexDivision, } LineIndex; +static const char* const mco_pin_names[] = { + "13(Tx)", +}; + static const char* const mco_source_names[] = { - "32768", + "32768Hz", "64MHz", "~100K", "~200K", @@ -81,14 +86,22 @@ void signal_gen_scene_mco_on_enter(void* context) { VariableItem* item; + item = variable_item_list_add(var_item_list, "GPIO Pin", COUNT_OF(mco_pin_names), NULL, NULL); + variable_item_set_current_value_index(item, 0); + variable_item_set_current_value_text(item, mco_pin_names[0]); + item = variable_item_list_add( - var_item_list, "Source", COUNT_OF(mco_source_names), mco_source_list_change_callback, app); + var_item_list, + "Frequency", + COUNT_OF(mco_source_names), + mco_source_list_change_callback, + app); variable_item_set_current_value_index(item, 0); variable_item_set_current_value_text(item, mco_source_names[0]); item = variable_item_list_add( var_item_list, - "Division", + "Freq. divider", COUNT_OF(mco_divisor_names), mco_divisor_list_change_callback, app); diff --git a/applications/plugins/signal_generator/scenes/signal_gen_scene_start.c b/applications/plugins/signal_generator/scenes/signal_gen_scene_start.c index 91f6081d84b..3c7b9cc32a1 100644 --- a/applications/plugins/signal_generator/scenes/signal_gen_scene_start.c +++ b/applications/plugins/signal_generator/scenes/signal_gen_scene_start.c @@ -16,10 +16,10 @@ void signal_gen_scene_start_on_enter(void* context) { Submenu* submenu = app->submenu; submenu_add_item( - submenu, "PWM", SubmenuIndexPwm, signal_gen_scene_start_submenu_callback, app); + submenu, "PWM Generator", SubmenuIndexPwm, signal_gen_scene_start_submenu_callback, app); submenu_add_item( submenu, - "Clock Output", + "Clock Generator", SubmenuIndexClockOutput, signal_gen_scene_start_submenu_callback, app); diff --git a/applications/plugins/signal_generator/views/signal_gen_pwm.c b/applications/plugins/signal_generator/views/signal_gen_pwm.c index 00b4ef2674b..4afed01a628 100644 --- a/applications/plugins/signal_generator/views/signal_gen_pwm.c +++ b/applications/plugins/signal_generator/views/signal_gen_pwm.c @@ -9,7 +9,7 @@ typedef enum { LineIndexTotalCount } LineIndex; -static const char* const pwm_ch_names[] = {"TIM1(2)", "LPTIM2(4)"}; +static const char* const pwm_ch_names[] = {"2(A7)", "4(A4)"}; struct SignalGenPwm { View* view; @@ -31,8 +31,8 @@ typedef struct { #define ITEM_H 64 / 3 #define ITEM_W 128 -#define VALUE_X 95 -#define VALUE_W 55 +#define VALUE_X 100 +#define VALUE_W 45 #define FREQ_VALUE_X 62 #define FREQ_MAX 1000000UL @@ -126,11 +126,11 @@ static void signal_gen_pwm_draw_callback(Canvas* canvas, void* _model) { for(uint8_t line = 0; line < LineIndexTotalCount; line++) { if(line == LineIndexChannel) { - line_label = "PWM Channel"; + line_label = "GPIO Pin"; } else if(line == LineIndexFrequency) { line_label = "Frequency"; } else if(line == LineIndexDuty) { - line_label = "Duty Cycle"; + line_label = "Pulse width"; } canvas_set_color(canvas, ColorBlack); @@ -162,9 +162,9 @@ static void signal_gen_pwm_draw_callback(Canvas* canvas, void* _model) { canvas_set_font(canvas, FontSecondary); if(model->edit_mode) { - uint8_t icon_x = (FREQ_VALUE_X - 1) + (FREQ_DIGITS_NB - model->edit_digit - 1) * 6; - canvas_draw_icon(canvas, icon_x, text_y - 9, &I_SmallArrowUp_4x7); - canvas_draw_icon(canvas, icon_x, text_y + 4, &I_SmallArrowDown_4x7); + uint8_t icon_x = (FREQ_VALUE_X) + (FREQ_DIGITS_NB - model->edit_digit - 1) * 6; + canvas_draw_icon(canvas, icon_x, text_y - 9, &I_SmallArrowUp_3x5); + canvas_draw_icon(canvas, icon_x, text_y + 5, &I_SmallArrowDown_3x5); } } else if(line == LineIndexDuty) { snprintf(val_text, sizeof(val_text), "%d%%", model->duty); diff --git a/assets/icons/Interface/SmallArrowDown_3x5.png b/assets/icons/Interface/SmallArrowDown_3x5.png new file mode 100644 index 0000000000000000000000000000000000000000..1912e5d246268d75a20984bdc8b996d503f3d166 GIT binary patch literal 3592 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 BDRuw= literal 0 HcmV?d00001 diff --git a/assets/icons/Interface/SmallArrowUp_3x5.png b/assets/icons/Interface/SmallArrowUp_3x5.png new file mode 100644 index 0000000000000000000000000000000000000000..9c6242078d3bcc9a93eb55b6b5f358720ee6662c GIT binary patch literal 7976 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 Date: Sat, 8 Oct 2022 03:06:29 +1000 Subject: [PATCH 123/824] More correct elf loader (#1839) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ELF File: more robust section loader * ELF File: faster and smaller preinit, init and fini arrays handling * ELF File: load sections on preload stage * ELF File: naming * Furi: fix use after free in thread join Co-authored-by: あく --- applications/main/fap_loader/fap_loader_app.c | 2 + furi/core/thread.c | 8 +- lib/flipper_application/elf/elf_file.c | 214 ++++++++---------- lib/flipper_application/elf/elf_file_i.h | 8 +- 4 files changed, 101 insertions(+), 131 deletions(-) diff --git a/applications/main/fap_loader/fap_loader_app.c b/applications/main/fap_loader/fap_loader_app.c index fb0a300e790..d2f6b1f4190 100644 --- a/applications/main/fap_loader/fap_loader_app.c +++ b/applications/main/fap_loader/fap_loader_app.c @@ -70,6 +70,7 @@ static bool fap_loader_run_selected_app(FapLoader* loader) { do { file_selected = true; loader->app = flipper_application_alloc(loader->storage, &hashtable_api_interface); + size_t start = furi_get_tick(); FURI_LOG_I(TAG, "FAP Loader is loading %s", furi_string_get_cstr(loader->fap_path)); @@ -99,6 +100,7 @@ static bool fap_loader_run_selected_app(FapLoader* loader) { break; } + FURI_LOG_I(TAG, "Loaded in %ums", (size_t)(furi_get_tick() - start)); FURI_LOG_I(TAG, "FAP Loader is staring app"); FuriThread* thread = flipper_application_spawn(loader->app, NULL); diff --git a/furi/core/thread.c b/furi/core/thread.c index 3ebca807efe..508146f6312 100644 --- a/furi/core/thread.c +++ b/furi/core/thread.c @@ -103,6 +103,7 @@ static void furi_thread_body(void* context) { furi_assert(pvTaskGetThreadLocalStoragePointer(NULL, 0) != NULL); vTaskSetThreadLocalStoragePointer(NULL, 0, NULL); + thread->task_handle = NULL; vTaskDelete(NULL); furi_thread_catch(); } @@ -211,13 +212,8 @@ bool furi_thread_join(FuriThread* thread) { furi_check(furi_thread_get_current() != thread); - // Check if thread was started - if(thread->task_handle == NULL) { - return false; - } - // Wait for thread to stop - while(eTaskGetState(thread->task_handle) != eDeleted) { + while(thread->task_handle) { furi_delay_ms(10); } diff --git a/lib/flipper_application/elf/elf_file.c b/lib/flipper_application/elf/elf_file.c index b66e27d9ac7..c70af067e5f 100644 --- a/lib/flipper_application/elf/elf_file.c +++ b/lib/flipper_application/elf/elf_file.c @@ -57,8 +57,23 @@ static ELFSection* elf_file_get_section(ELFFile* elf, const char* name) { return ELFSectionDict_get(elf->sections, name); } -static void elf_file_put_section(ELFFile* elf, const char* name, ELFSection* section) { - ELFSectionDict_set_at(elf->sections, strdup(name), *section); +static ELFSection* elf_file_get_or_put_section(ELFFile* elf, const char* name) { + ELFSection* section_p = elf_file_get_section(elf, name); + if(!section_p) { + ELFSectionDict_set_at( + elf->sections, + strdup(name), + (ELFSection){ + .data = NULL, + .sec_idx = 0, + .size = 0, + .rel_count = 0, + .rel_offset = 0, + }); + section_p = elf_file_get_section(elf, name); + } + + return section_p; } static bool elf_read_string_from_offset(ELFFile* elf, off_t offset, FuriString* name) { @@ -320,12 +335,12 @@ static bool elf_relocate_symbol(ELFFile* elf, Elf32_Addr relAddr, int type, Elf3 return true; } -static bool elf_relocate(ELFFile* elf, Elf32_Shdr* h, ELFSection* s) { +static bool elf_relocate(ELFFile* elf, ELFSection* s) { if(s->data) { Elf32_Rel rel; - size_t relEntries = h->sh_size / sizeof(rel); + size_t relEntries = s->rel_count; size_t relCount; - (void)storage_file_seek(elf->fd, h->sh_offset, true); + (void)storage_file_seek(elf->fd, s->rel_offset, true); FURI_LOG_D(TAG, " Offset Info Type Name"); int relocate_result = true; @@ -395,14 +410,6 @@ static bool elf_relocate(ELFFile* elf, Elf32_Shdr* h, ELFSection* s) { return false; } -/**************************************************************************************************/ -/********************************************* MISC ***********************************************/ -/**************************************************************************************************/ - -static bool cstr_prefix(const char* prefix, const char* string) { - return strncmp(prefix, string, strlen(prefix)) == 0; -} - /**************************************************************************************************/ /************************************ Internal FAP interfaces *************************************/ /**************************************************************************************************/ @@ -445,6 +452,31 @@ static bool elf_load_debug_link(ELFFile* elf, Elf32_Shdr* section_header) { section_header->sh_size; } +static bool elf_load_section_data(ELFFile* elf, ELFSection* section, Elf32_Shdr* section_header) { + if(section_header->sh_size == 0) { + FURI_LOG_D(TAG, "No data for section"); + return true; + } + + section->data = aligned_malloc(section_header->sh_size, section_header->sh_addralign); + section->size = section_header->sh_size; + + if(section_header->sh_type == SHT_NOBITS) { + // BSS section, no data to load + return true; + } + + if((!storage_file_seek(elf->fd, section_header->sh_offset, true)) || + (storage_file_read(elf->fd, section->data, section_header->sh_size) != + section_header->sh_size)) { + FURI_LOG_E(TAG, " seek/read fail"); + return false; + } + + FURI_LOG_D(TAG, "0x%X", section->data); + return true; +} + static SectionType elf_preload_section( ELFFile* elf, size_t section_idx, @@ -453,73 +485,63 @@ static SectionType elf_preload_section( FlipperApplicationManifest* manifest) { const char* name = furi_string_get_cstr(name_string); - const struct { - const char* prefix; - SectionType type; - } lookup_sections[] = { - {".text", SectionTypeData}, - {".rodata", SectionTypeData}, - {".data", SectionTypeData}, - {".bss", SectionTypeData}, - {".preinit_array", SectionTypeData}, - {".init_array", SectionTypeData}, - {".fini_array", SectionTypeData}, - {".rel.text", SectionTypeRelData}, - {".rel.rodata", SectionTypeRelData}, - {".rel.data", SectionTypeRelData}, - {".rel.preinit_array", SectionTypeRelData}, - {".rel.init_array", SectionTypeRelData}, - {".rel.fini_array", SectionTypeRelData}, - }; - - for(size_t i = 0; i < COUNT_OF(lookup_sections); i++) { - if(cstr_prefix(lookup_sections[i].prefix, name)) { - FURI_LOG_D(TAG, "Found section %s", lookup_sections[i].prefix); - - if(lookup_sections[i].type == SectionTypeRelData) { - name = name + strlen(".rel"); - } - - ELFSection* section_p = elf_file_get_section(elf, name); - if(!section_p) { - ELFSection section = { - .data = NULL, - .sec_idx = 0, - .rel_sec_idx = 0, - .size = 0, - }; - - elf_file_put_section(elf, name, §ion); - section_p = elf_file_get_section(elf, name); - } - - if(lookup_sections[i].type == SectionTypeRelData) { - section_p->rel_sec_idx = section_idx; - } else { - section_p->sec_idx = section_idx; - } + // Load allocable section + if(section_header->sh_flags & SHF_ALLOC) { + ELFSection* section_p = elf_file_get_or_put_section(elf, name); + section_p->sec_idx = section_idx; + + if(section_header->sh_type == SHT_PREINIT_ARRAY) { + elf->preinit_array = section_p; + } else if(section_header->sh_type == SHT_INIT_ARRAY) { + elf->init_array = section_p; + } else if(section_header->sh_type == SHT_FINI_ARRAY) { + elf->fini_array = section_p; + } - return lookup_sections[i].type; + if(!elf_load_section_data(elf, section_p, section_header)) { + FURI_LOG_E(TAG, "Error loading section '%s'", name); + return SectionTypeERROR; + } else { + return SectionTypeData; } } + // Load link info section + if(section_header->sh_flags & SHF_INFO_LINK) { + name = name + strlen(".rel"); + ELFSection* section_p = elf_file_get_or_put_section(elf, name); + section_p->rel_count = section_header->sh_size / sizeof(Elf32_Rel); + section_p->rel_offset = section_header->sh_offset; + return SectionTypeRelData; + } + + // Load symbol table if(strcmp(name, ".symtab") == 0) { FURI_LOG_D(TAG, "Found .symtab section"); elf->symbol_table = section_header->sh_offset; elf->symbol_count = section_header->sh_size / sizeof(Elf32_Sym); return SectionTypeSymTab; - } else if(strcmp(name, ".strtab") == 0) { + } + + // Load string table + if(strcmp(name, ".strtab") == 0) { FURI_LOG_D(TAG, "Found .strtab section"); elf->symbol_table_strings = section_header->sh_offset; return SectionTypeStrTab; - } else if(strcmp(name, ".fapmeta") == 0) { + } + + // Load manifest section + if(strcmp(name, ".fapmeta") == 0) { FURI_LOG_D(TAG, "Found .fapmeta section"); if(elf_load_metadata(elf, section_header, manifest)) { return SectionTypeManifest; } else { return SectionTypeERROR; } - } else if(strcmp(name, ".gnu_debuglink") == 0) { + } + + // Load debug link section + if(strcmp(name, ".gnu_debuglink") == 0) { FURI_LOG_D(TAG, "Found .gnu_debuglink section"); if(elf_load_debug_link(elf, section_header)) { return SectionTypeDebugLink; @@ -531,61 +553,17 @@ static SectionType elf_preload_section( return SectionTypeUnused; } -static bool elf_load_section_data(ELFFile* elf, ELFSection* section) { - Elf32_Shdr section_header; - if(section->sec_idx == 0) { - FURI_LOG_D(TAG, "Section is not present"); - return true; - } - - if(!elf_read_section_header(elf, section->sec_idx, §ion_header)) { - return false; - } - - if(section_header.sh_size == 0) { - FURI_LOG_D(TAG, "No data for section"); - return true; - } - - section->data = aligned_malloc(section_header.sh_size, section_header.sh_addralign); - section->size = section_header.sh_size; - - if(section_header.sh_type == SHT_NOBITS) { - /* section is empty (.bss?) */ - /* no need to memset - allocator already did that */ - return true; - } - - if((!storage_file_seek(elf->fd, section_header.sh_offset, true)) || - (storage_file_read(elf->fd, section->data, section_header.sh_size) != - section_header.sh_size)) { - FURI_LOG_E(TAG, " seek/read fail"); - return false; - } - - FURI_LOG_D(TAG, "0x%X", section->data); - return true; -} - static bool elf_relocate_section(ELFFile* elf, ELFSection* section) { - Elf32_Shdr section_header; - if(section->rel_sec_idx) { + if(section->rel_count) { FURI_LOG_D(TAG, "Relocating section"); - if(elf_read_section_header(elf, section->rel_sec_idx, §ion_header)) - return elf_relocate(elf, §ion_header, section); - else { - FURI_LOG_E(TAG, "Error reading section header"); - return false; - } + return elf_relocate(elf, section); } else { FURI_LOG_D(TAG, "No relocation index"); /* Not an error */ } return true; } -static void elf_file_call_section_list(ELFFile* elf, const char* name, bool reverse_order) { - ELFSection* section = elf_file_get_section(elf, name); - +static void elf_file_call_section_list(ELFSection* section, bool reverse_order) { if(section && section->size) { const uint32_t* start = section->data; const uint32_t* end = section->data + section->size; @@ -729,7 +707,6 @@ bool elf_file_load_section_table(ELFFile* elf, FlipperApplicationManifest* manif } furi_string_free(name); - FURI_LOG_D(TAG, "Load symbols done"); return IS_FLAGS_SET(loaded_sections, SectionTypeValid); } @@ -739,16 +716,6 @@ ELFFileLoadStatus elf_file_load_sections(ELFFile* elf) { ELFSectionDict_it_t it; AddressCache_init(elf->relocation_cache); - size_t start = furi_get_tick(); - - for(ELFSectionDict_it(it, elf->sections); !ELFSectionDict_end_p(it); ELFSectionDict_next(it)) { - ELFSectionDict_itref_t* itref = ELFSectionDict_ref(it); - FURI_LOG_D(TAG, "Loading section '%s'", itref->key); - if(!elf_load_section_data(elf, &itref->value)) { - FURI_LOG_E(TAG, "Error loading section '%s'", itref->key); - status = ELFFileLoadStatusUnspecifiedError; - } - } if(status == ELFFileLoadStatusSuccess) { for(ELFSectionDict_it(it, elf->sections); !ELFSectionDict_end_p(it); @@ -777,14 +744,13 @@ ELFFileLoadStatus elf_file_load_sections(ELFFile* elf) { FURI_LOG_D(TAG, "Relocation cache size: %u", AddressCache_size(elf->relocation_cache)); FURI_LOG_D(TAG, "Trampoline cache size: %u", AddressCache_size(elf->trampoline_cache)); AddressCache_clear(elf->relocation_cache); - FURI_LOG_I(TAG, "Loaded in %ums", (size_t)(furi_get_tick() - start)); return status; } void elf_file_pre_run(ELFFile* elf) { - elf_file_call_section_list(elf, ".preinit_array", false); - elf_file_call_section_list(elf, ".init_array", false); + elf_file_call_section_list(elf->preinit_array, false); + elf_file_call_section_list(elf->init_array, false); } int32_t elf_file_run(ELFFile* elf, void* args) { @@ -794,7 +760,7 @@ int32_t elf_file_run(ELFFile* elf, void* args) { } void elf_file_post_run(ELFFile* elf) { - elf_file_call_section_list(elf, ".fini_array", true); + elf_file_call_section_list(elf->fini_array, true); } const ElfApiInterface* elf_file_get_api_interface(ELFFile* elf_file) { diff --git a/lib/flipper_application/elf/elf_file_i.h b/lib/flipper_application/elf/elf_file_i.h index 1df075f062b..9b38540b72d 100644 --- a/lib/flipper_application/elf/elf_file_i.h +++ b/lib/flipper_application/elf/elf_file_i.h @@ -16,8 +16,10 @@ typedef int32_t(entry_t)(void*); typedef struct { void* data; uint16_t sec_idx; - uint16_t rel_sec_idx; Elf32_Word size; + + size_t rel_count; + Elf32_Off rel_offset; } ELFSection; DICT_DEF2(ELFSectionDict, const char*, M_CSTR_OPLIST, ELFSection, M_POD_OPLIST) @@ -39,6 +41,10 @@ struct ELFFile { File* fd; const ElfApiInterface* api_interface; ELFDebugLinkInfo debug_link_info; + + ELFSection* preinit_array; + ELFSection* init_array; + ELFSection* fini_array; }; #ifdef __cplusplus From d10e16ca3c65288733723b330738ea3155734c94 Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Sat, 8 Oct 2022 03:27:32 +1000 Subject: [PATCH 124/824] Snake game: nokia 6110-like sound (#1844) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Snake game: nokia 6110-like sound * Snake game: blocking sound notifications * SnakeGame: flush notification queue with backlight enforce block Co-authored-by: あく --- applications/plugins/snake_game/snake_game.c | 47 +++++++++++++++++++- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/applications/plugins/snake_game/snake_game.c b/applications/plugins/snake_game/snake_game.c index b7aabb17cba..0b665a943d9 100644 --- a/applications/plugins/snake_game/snake_game.c +++ b/applications/plugins/snake_game/snake_game.c @@ -2,6 +2,8 @@ #include #include #include +#include +#include typedef struct { // +-----x @@ -59,6 +61,35 @@ typedef struct { InputEvent input; } SnakeEvent; +const NotificationSequence sequence_fail = { + &message_vibro_on, + + &message_note_ds4, + &message_delay_10, + &message_sound_off, + &message_delay_10, + + &message_note_ds4, + &message_delay_10, + &message_sound_off, + &message_delay_10, + + &message_note_ds4, + &message_delay_10, + &message_sound_off, + &message_delay_10, + + &message_vibro_off, + NULL, +}; + +const NotificationSequence sequence_eat = { + &message_note_c7, + &message_delay_50, + &message_sound_off, + NULL, +}; + static void snake_game_render_callback(Canvas* const canvas, void* ctx) { const SnakeState* snake_state = acquire_mutex((ValueMutex*)ctx, 25); if(snake_state == NULL) { @@ -230,7 +261,8 @@ static void snake_game_move_snake(SnakeState* const snake_state, Point const nex snake_state->points[0] = next_step; } -static void snake_game_process_game_step(SnakeState* const snake_state) { +static void + snake_game_process_game_step(SnakeState* const snake_state, NotificationApp* notification) { if(snake_state->state == GameStateGameOver) { return; } @@ -249,6 +281,7 @@ static void snake_game_process_game_step(SnakeState* const snake_state) { return; } else if(snake_state->state == GameStateLastChance) { snake_state->state = GameStateGameOver; + notification_message_block(notification, &sequence_fail); return; } } else { @@ -260,6 +293,7 @@ static void snake_game_process_game_step(SnakeState* const snake_state) { crush = snake_game_collision_with_tail(snake_state, next_step); if(crush) { snake_state->state = GameStateGameOver; + notification_message_block(notification, &sequence_fail); return; } @@ -268,6 +302,7 @@ static void snake_game_process_game_step(SnakeState* const snake_state) { snake_state->len++; if(snake_state->len >= MAX_SNAKE_LEN) { snake_state->state = GameStateGameOver; + notification_message_block(notification, &sequence_fail); return; } } @@ -276,6 +311,7 @@ static void snake_game_process_game_step(SnakeState* const snake_state) { if(eatFruit) { snake_state->fruit = snake_game_get_new_fruit(snake_state); + notification_message(notification, &sequence_eat); } } @@ -306,6 +342,9 @@ int32_t snake_game_app(void* p) { // Open GUI and register view_port Gui* gui = furi_record_open(RECORD_GUI); gui_add_view_port(gui, view_port, GuiLayerFullscreen); + NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); + + notification_message_block(notification, &sequence_display_backlight_enforce_on); SnakeEvent event; for(bool processing = true; processing;) { @@ -341,7 +380,7 @@ int32_t snake_game_app(void* p) { } } } else if(event.type == EventTypeTick) { - snake_game_process_game_step(snake_state); + snake_game_process_game_step(snake_state, notification); } } else { // event timeout @@ -351,10 +390,14 @@ int32_t snake_game_app(void* p) { release_mutex(&state_mutex, snake_state); } + // Wait for all notifications to be played and return backlight to normal state + notification_message_block(notification, &sequence_display_backlight_enforce_auto); + furi_timer_free(timer); view_port_enabled_set(view_port, false); gui_remove_view_port(gui, view_port); furi_record_close(RECORD_GUI); + furi_record_close(RECORD_NOTIFICATION); view_port_free(view_port); furi_message_queue_free(event_queue); delete_mutex(&state_mutex); From 88ca267466f8408d5b50492b4fb72bb938d936f1 Mon Sep 17 00:00:00 2001 From: Yukai Li Date: Sat, 8 Oct 2022 04:13:26 -0600 Subject: [PATCH 125/824] music_player: Return to browser instead of exiting on back button (#1846) * music_player: Return to browser instead of exiting on back button * music_player: Fix number and dots extraction --- applications/plugins/music_player/music_player.c | 16 ++++++++++++---- .../plugins/music_player/music_player_worker.c | 6 ++++++ .../plugins/music_player/music_player_worker.h | 2 ++ 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/applications/plugins/music_player/music_player.c b/applications/plugins/music_player/music_player.c index 6d3c4483e0d..192500c2e6c 100644 --- a/applications/plugins/music_player/music_player.c +++ b/applications/plugins/music_player/music_player.c @@ -248,12 +248,16 @@ static void music_player_worker_callback( view_port_update(music_player->view_port); } +void music_player_clear(MusicPlayer* instance) { + memset(instance->model->duration_history, 0xff, MUSIC_PLAYER_SEMITONE_HISTORY_SIZE); + memset(instance->model->semitone_history, 0xff, MUSIC_PLAYER_SEMITONE_HISTORY_SIZE); + music_player_worker_clear(instance->worker); +} + MusicPlayer* music_player_alloc() { MusicPlayer* instance = malloc(sizeof(MusicPlayer)); instance->model = malloc(sizeof(MusicPlayerModel)); - memset(instance->model->duration_history, 0xff, MUSIC_PLAYER_SEMITONE_HISTORY_SIZE); - memset(instance->model->semitone_history, 0xff, MUSIC_PLAYER_SEMITONE_HISTORY_SIZE); instance->model->volume = 3; instance->model_mutex = furi_mutex_alloc(FuriMutexTypeNormal); @@ -265,6 +269,8 @@ MusicPlayer* music_player_alloc() { instance->worker, MUSIC_PLAYER_VOLUMES[instance->model->volume]); music_player_worker_set_callback(instance->worker, music_player_worker_callback, instance); + music_player_clear(instance); + instance->view_port = view_port_alloc(); view_port_draw_callback_set(instance->view_port, render_callback, instance); view_port_input_callback_set(instance->view_port, input_callback, instance); @@ -299,7 +305,7 @@ int32_t music_player_app(void* p) { do { if(p && strlen(p)) { - furi_string_cat(file_path, (const char*)p); + furi_string_set(file_path, (const char*)p); } else { furi_string_set(file_path, MUSIC_PLAYER_APP_PATH_FOLDER); @@ -350,7 +356,9 @@ int32_t music_player_app(void* p) { } music_player_worker_stop(music_player->worker); - } while(0); + if(p && strlen(p)) break; // Exit instead of going to browser if launched with arg + music_player_clear(music_player); + } while(1); furi_string_free(file_path); music_player_free(music_player); diff --git a/applications/plugins/music_player/music_player_worker.c b/applications/plugins/music_player/music_player_worker.c index 99f0ce1e52c..3f1ac62f7aa 100644 --- a/applications/plugins/music_player/music_player_worker.c +++ b/applications/plugins/music_player/music_player_worker.c @@ -108,6 +108,10 @@ MusicPlayerWorker* music_player_worker_alloc() { return instance; } +void music_player_worker_clear(MusicPlayerWorker* instance) { + NoteBlockArray_reset(instance->notes); +} + void music_player_worker_free(MusicPlayerWorker* instance) { furi_assert(instance); furi_thread_free(instance->thread); @@ -129,6 +133,7 @@ static bool is_space(const char c) { static size_t extract_number(const char* string, uint32_t* number) { size_t ret = 0; + *number = 0; while(is_digit(*string)) { *number *= 10; *number += (*string - '0'); @@ -140,6 +145,7 @@ static size_t extract_number(const char* string, uint32_t* number) { static size_t extract_dots(const char* string, uint32_t* number) { size_t ret = 0; + *number = 0; while(*string == '.') { *number += 1; string++; diff --git a/applications/plugins/music_player/music_player_worker.h b/applications/plugins/music_player/music_player_worker.h index 3aa99ea370e..00320b11fe5 100644 --- a/applications/plugins/music_player/music_player_worker.h +++ b/applications/plugins/music_player/music_player_worker.h @@ -14,6 +14,8 @@ typedef struct MusicPlayerWorker MusicPlayerWorker; MusicPlayerWorker* music_player_worker_alloc(); +void music_player_worker_clear(MusicPlayerWorker* instance); + void music_player_worker_free(MusicPlayerWorker* instance); bool music_player_worker_load(MusicPlayerWorker* instance, const char* file_path); From c13929330e8db619a28024479ffd8d408bdb9d10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Sat, 8 Oct 2022 19:57:53 +0900 Subject: [PATCH 126/824] Gui: fix memory leak in file browser module (#1848) --- applications/services/gui/modules/file_browser.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/applications/services/gui/modules/file_browser.c b/applications/services/gui/modules/file_browser.c index f15b09f6b5e..762fe87d5a8 100644 --- a/applications/services/gui/modules/file_browser.c +++ b/applications/services/gui/modules/file_browser.c @@ -57,7 +57,6 @@ static void BrowserItem_t_set(BrowserItem_t* obj, const BrowserItem_t* src) { furi_string_set(obj->path, src->path); furi_string_set(obj->display_name, src->display_name); if(src->custom_icon_data) { - obj->custom_icon_data = malloc(CUSTOM_ICON_MAX_SIZE); memcpy(obj->custom_icon_data, src->custom_icon_data, CUSTOM_ICON_MAX_SIZE); } else { obj->custom_icon_data = NULL; @@ -379,6 +378,9 @@ static void }); furi_string_free(item.display_name); furi_string_free(item.path); + if(item.custom_icon_data) { + free(item.custom_icon_data); + } } else { with_view_model( browser->view, (FileBrowserModel * model) { From 981f7ff8b027fed55dfd92a0628e8bc7bd6b92f9 Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Sun, 9 Oct 2022 01:51:51 +1000 Subject: [PATCH 127/824] Elf loader: do not load .ARM.* sections (#1850) * Elf loader: do not load .ARM sections * Fix section name --- lib/flipper_application/elf/elf_file.c | 41 ++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/lib/flipper_application/elf/elf_file.c b/lib/flipper_application/elf/elf_file.c index c70af067e5f..3fe57162fc4 100644 --- a/lib/flipper_application/elf/elf_file.c +++ b/lib/flipper_application/elf/elf_file.c @@ -452,6 +452,10 @@ static bool elf_load_debug_link(ELFFile* elf, Elf32_Shdr* section_header) { section_header->sh_size; } +static bool str_prefix(const char* str, const char* prefix) { + return strncmp(prefix, str, strlen(prefix)) == 0; +} + static bool elf_load_section_data(ELFFile* elf, ELFSection* section, Elf32_Shdr* section_header) { if(section_header->sh_size == 0) { FURI_LOG_D(TAG, "No data for section"); @@ -485,6 +489,43 @@ static SectionType elf_preload_section( FlipperApplicationManifest* manifest) { const char* name = furi_string_get_cstr(name_string); +#ifdef ELF_DEBUG_LOG + // log section name, type and flags + FuriString* flags_string = furi_string_alloc(); + if(section_header->sh_flags & SHF_WRITE) furi_string_cat(flags_string, "W"); + if(section_header->sh_flags & SHF_ALLOC) furi_string_cat(flags_string, "A"); + if(section_header->sh_flags & SHF_EXECINSTR) furi_string_cat(flags_string, "X"); + if(section_header->sh_flags & SHF_MERGE) furi_string_cat(flags_string, "M"); + if(section_header->sh_flags & SHF_STRINGS) furi_string_cat(flags_string, "S"); + if(section_header->sh_flags & SHF_INFO_LINK) furi_string_cat(flags_string, "I"); + if(section_header->sh_flags & SHF_LINK_ORDER) furi_string_cat(flags_string, "L"); + if(section_header->sh_flags & SHF_OS_NONCONFORMING) furi_string_cat(flags_string, "O"); + if(section_header->sh_flags & SHF_GROUP) furi_string_cat(flags_string, "G"); + if(section_header->sh_flags & SHF_TLS) furi_string_cat(flags_string, "T"); + if(section_header->sh_flags & SHF_COMPRESSED) furi_string_cat(flags_string, "T"); + if(section_header->sh_flags & SHF_MASKOS) furi_string_cat(flags_string, "o"); + if(section_header->sh_flags & SHF_MASKPROC) furi_string_cat(flags_string, "p"); + if(section_header->sh_flags & SHF_ORDERED) furi_string_cat(flags_string, "R"); + if(section_header->sh_flags & SHF_EXCLUDE) furi_string_cat(flags_string, "E"); + + FURI_LOG_I( + TAG, + "Section %s: type: %ld, flags: %s", + name, + section_header->sh_type, + furi_string_get_cstr(flags_string)); + furi_string_free(flags_string); +#endif + + // ignore .ARM and .rel.ARM sections + // TODO: how to do it not by name? + // .ARM: type 0x70000001, flags SHF_ALLOC | SHF_LINK_ORDER + // .rel.ARM: type 0x9, flags SHT_REL + if(str_prefix(name, ".ARM.") || str_prefix(name, ".rel.ARM.")) { + FURI_LOG_D(TAG, "Ignoring ARM section"); + return SectionTypeUnused; + } + // Load allocable section if(section_header->sh_flags & SHF_ALLOC) { ELFSection* section_p = elf_file_get_or_put_section(elf, name); From 31c0346adc19f7bf7584c991fc4ba2722b37559f Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Sun, 9 Oct 2022 03:38:29 +1000 Subject: [PATCH 128/824] [FL-976] Removing lambdas (#1849) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Removing lambdas... * Wake the fk up, Gordon! We have a citadel to burn! * Here comes the Nihilanth * Lambda documentation Co-authored-by: あく --- .../debug/bt_debug_app/views/bt_test.c | 87 +++++---- .../debug/display_test/view_display_test.c | 13 +- .../views/lfrfid_debug_view_tune.c | 71 +++---- applications/debug/uart_echo/uart_echo.c | 29 +-- .../main/archive/helpers/archive_browser.c | 165 ++++++++-------- .../main/archive/views/archive_browser_view.c | 49 ++--- .../main/bad_usb/views/bad_usb_view.c | 24 ++- applications/main/gpio/views/gpio_test.c | 32 ++-- applications/main/gpio/views/gpio_usb_uart.c | 16 +- .../main/lfrfid/views/lfrfid_view_read.c | 31 ++- applications/main/nfc/views/detect_reader.c | 34 ++-- applications/main/nfc/views/dict_attack.c | 98 +++++----- applications/main/subghz/views/receiver.c | 126 +++++++------ .../subghz/views/subghz_frequency_analyzer.c | 34 ++-- .../main/subghz/views/subghz_read_raw.c | 146 +++++++++------ .../main/subghz/views/subghz_test_carrier.c | 26 +-- .../main/subghz/views/subghz_test_packet.c | 33 ++-- .../main/subghz/views/subghz_test_static.c | 18 +- applications/main/subghz/views/transmitter.c | 40 ++-- applications/main/u2f/views/u2f_view.c | 13 +- .../bt_hid_app/views/bt_hid_keyboard.c | 13 +- .../plugins/bt_hid_app/views/bt_hid_keynote.c | 13 +- .../plugins/bt_hid_app/views/bt_hid_media.c | 21 ++- .../plugins/bt_hid_app/views/bt_hid_mouse.c | 13 +- .../signal_generator/views/signal_gen_pwm.c | 32 ++-- .../desktop/views/desktop_view_debug.c | 22 +-- .../desktop/views/desktop_view_lock_menu.c | 35 ++-- .../services/gui/modules/button_menu.c | 69 ++++--- .../services/gui/modules/button_panel.c | 177 ++++++++++-------- .../services/gui/modules/byte_input.c | 57 +++--- applications/services/gui/modules/dialog_ex.c | 64 ++++--- .../services/gui/modules/file_browser.c | 95 +++++----- applications/services/gui/modules/menu.c | 78 +++++--- applications/services/gui/modules/popup.c | 40 ++-- applications/services/gui/modules/submenu.c | 67 ++++--- applications/services/gui/modules/text_box.c | 55 +++--- .../services/gui/modules/text_input.c | 69 +++---- .../services/gui/modules/variable_item_list.c | 89 +++++---- applications/services/gui/modules/widget.c | 34 ++-- applications/services/gui/view.h | 22 +-- .../power/power_service/views/power_off.c | 10 +- .../power_settings_app/views/battery_info.c | 8 +- .../system/updater/views/updater_main.c | 32 ++-- 43 files changed, 1193 insertions(+), 1007 deletions(-) diff --git a/applications/debug/bt_debug_app/views/bt_test.c b/applications/debug/bt_debug_app/views/bt_test.c index 9f2830d348f..9588b667b6c 100644 --- a/applications/debug/bt_debug_app/views/bt_test.c +++ b/applications/debug/bt_debug_app/views/bt_test.c @@ -154,7 +154,9 @@ static bool bt_test_input_callback(InputEvent* event, void* context) { void bt_test_process_up(BtTest* bt_test) { with_view_model( - bt_test->view, (BtTestModel * model) { + bt_test->view, + BtTestModel * model, + { uint8_t params_on_screen = 3; if(model->position > 0) { model->position--; @@ -168,13 +170,15 @@ void bt_test_process_up(BtTest* bt_test) { model->window_position = model->position - (params_on_screen - 1); } } - return true; - }); + }, + true); } void bt_test_process_down(BtTest* bt_test) { with_view_model( - bt_test->view, (BtTestModel * model) { + bt_test->view, + BtTestModel * model, + { uint8_t params_on_screen = 3; if(model->position < (BtTestParamArray_size(model->params) - 1)) { model->position++; @@ -187,8 +191,8 @@ void bt_test_process_down(BtTest* bt_test) { model->position = 0; model->window_position = 0; } - return true; - }); + }, + true); } BtTestParam* bt_test_get_selected_param(BtTestModel* model) { @@ -213,7 +217,9 @@ BtTestParam* bt_test_get_selected_param(BtTestModel* model) { void bt_test_process_left(BtTest* bt_test) { BtTestParam* param; with_view_model( - bt_test->view, (BtTestModel * model) { + bt_test->view, + BtTestModel * model, + { param = bt_test_get_selected_param(model); if(param->current_value_index > 0) { param->current_value_index--; @@ -225,8 +231,8 @@ void bt_test_process_left(BtTest* bt_test) { model->packets_num_tx = 0; } } - return true; - }); + }, + true); if(param->change_callback) { param->change_callback(param); } @@ -235,7 +241,9 @@ void bt_test_process_left(BtTest* bt_test) { void bt_test_process_right(BtTest* bt_test) { BtTestParam* param; with_view_model( - bt_test->view, (BtTestModel * model) { + bt_test->view, + BtTestModel * model, + { param = bt_test_get_selected_param(model); if(param->current_value_index < (param->values_count - 1)) { param->current_value_index++; @@ -247,8 +255,8 @@ void bt_test_process_right(BtTest* bt_test) { model->packets_num_tx = 0; } } - return true; - }); + }, + true); if(param->change_callback) { param->change_callback(param); } @@ -257,7 +265,9 @@ void bt_test_process_right(BtTest* bt_test) { void bt_test_process_ok(BtTest* bt_test) { BtTestState state; with_view_model( - bt_test->view, (BtTestModel * model) { + bt_test->view, + BtTestModel * model, + { if(model->state == BtTestStateStarted) { model->state = BtTestStateStopped; model->message = BT_TEST_START_MESSAGE; @@ -269,8 +279,8 @@ void bt_test_process_ok(BtTest* bt_test) { model->message = BT_TEST_STOP_MESSAGE; } state = model->state; - return true; - }); + }, + true); if(bt_test->change_state_callback) { bt_test->change_state_callback(state, bt_test->context); } @@ -278,13 +288,15 @@ void bt_test_process_ok(BtTest* bt_test) { void bt_test_process_back(BtTest* bt_test) { with_view_model( - bt_test->view, (BtTestModel * model) { + bt_test->view, + BtTestModel * model, + { model->state = BtTestStateStopped; model->rssi = 0.0f; model->packets_num_rx = 0; model->packets_num_tx = 0; - return false; - }); + }, + false); if(bt_test->back_callback) { bt_test->back_callback(bt_test->context); } @@ -299,7 +311,9 @@ BtTest* bt_test_alloc() { view_set_input_callback(bt_test->view, bt_test_input_callback); with_view_model( - bt_test->view, (BtTestModel * model) { + bt_test->view, + BtTestModel * model, + { model->state = BtTestStateStopped; model->message = "Ok - Start"; BtTestParamArray_init(model->params); @@ -308,8 +322,8 @@ BtTest* bt_test_alloc() { model->rssi = 0.0f; model->packets_num_tx = 0; model->packets_num_rx = 0; - return true; - }); + }, + true); return bt_test; } @@ -318,15 +332,17 @@ void bt_test_free(BtTest* bt_test) { furi_assert(bt_test); with_view_model( - bt_test->view, (BtTestModel * model) { + bt_test->view, + BtTestModel * model, + { BtTestParamArray_it_t it; for(BtTestParamArray_it(it, model->params); !BtTestParamArray_end_p(it); BtTestParamArray_next(it)) { furi_string_free(BtTestParamArray_ref(it)->current_value_text); } BtTestParamArray_clear(model->params); - return false; - }); + }, + false); view_free(bt_test->view); free(bt_test); } @@ -347,7 +363,9 @@ BtTestParam* bt_test_param_add( furi_assert(bt_test); with_view_model( - bt_test->view, (BtTestModel * model) { + bt_test->view, + BtTestModel * model, + { param = BtTestParamArray_push_new(model->params); param->label = label; param->values_count = values_count; @@ -355,8 +373,8 @@ BtTestParam* bt_test_param_add( param->context = context; param->current_value_index = 0; param->current_value_text = furi_string_alloc(); - return true; - }); + }, + true); return param; } @@ -364,28 +382,19 @@ BtTestParam* bt_test_param_add( void bt_test_set_rssi(BtTest* bt_test, float rssi) { furi_assert(bt_test); with_view_model( - bt_test->view, (BtTestModel * model) { - model->rssi = rssi; - return true; - }); + bt_test->view, BtTestModel * model, { model->rssi = rssi; }, true); } void bt_test_set_packets_tx(BtTest* bt_test, uint32_t packets_num) { furi_assert(bt_test); with_view_model( - bt_test->view, (BtTestModel * model) { - model->packets_num_tx = packets_num; - return true; - }); + bt_test->view, BtTestModel * model, { model->packets_num_tx = packets_num; }, true); } void bt_test_set_packets_rx(BtTest* bt_test, uint32_t packets_num) { furi_assert(bt_test); with_view_model( - bt_test->view, (BtTestModel * model) { - model->packets_num_rx = packets_num; - return true; - }); + bt_test->view, BtTestModel * model, { model->packets_num_rx = packets_num; }, true); } void bt_test_set_change_state_callback(BtTest* bt_test, BtTestChangeStateCallback callback) { diff --git a/applications/debug/display_test/view_display_test.c b/applications/debug/display_test/view_display_test.c index f00fe009445..b47c74c6c2c 100644 --- a/applications/debug/display_test/view_display_test.c +++ b/applications/debug/display_test/view_display_test.c @@ -110,7 +110,9 @@ static bool view_display_test_input_callback(InputEvent* event, void* context) { bool consumed = false; if(event->type == InputTypeShort || event->type == InputTypeRepeat) { with_view_model( - instance->view, (ViewDisplayTestModel * model) { + instance->view, + ViewDisplayTestModel * model, + { if(event->key == InputKeyLeft && model->test > 0) { model->test--; consumed = true; @@ -129,8 +131,8 @@ static bool view_display_test_input_callback(InputEvent* event, void* context) { model->flip_flop = !model->flip_flop; consumed = true; } - return consumed; - }); + }, + consumed); } return consumed; @@ -149,10 +151,7 @@ static void view_display_test_exit(void* context) { static void view_display_test_timer_callback(void* context) { ViewDisplayTest* instance = context; with_view_model( - instance->view, (ViewDisplayTestModel * model) { - model->counter++; - return true; - }); + instance->view, ViewDisplayTestModel * model, { model->counter++; }, true); } ViewDisplayTest* view_display_test_alloc() { diff --git a/applications/debug/lfrfid_debug/views/lfrfid_debug_view_tune.c b/applications/debug/lfrfid_debug/views/lfrfid_debug_view_tune.c index 38fe3603658..fd221c4e9d3 100644 --- a/applications/debug/lfrfid_debug/views/lfrfid_debug_view_tune.c +++ b/applications/debug/lfrfid_debug/views/lfrfid_debug_view_tune.c @@ -52,23 +52,29 @@ static void lfrfid_debug_view_tune_draw_callback(Canvas* canvas, void* _model) { static void lfrfid_debug_view_tune_button_up(LfRfidTuneView* tune_view) { with_view_model( - tune_view->view, (LfRfidTuneViewModel * model) { + tune_view->view, + LfRfidTuneViewModel * model, + { if(model->pos > 0) model->pos--; - return true; - }); + }, + true); } static void lfrfid_debug_view_tune_button_down(LfRfidTuneView* tune_view) { with_view_model( - tune_view->view, (LfRfidTuneViewModel * model) { + tune_view->view, + LfRfidTuneViewModel * model, + { if(model->pos < 1) model->pos++; - return true; - }); + }, + true); } static void lfrfid_debug_view_tune_button_left(LfRfidTuneView* tune_view) { with_view_model( - tune_view->view, (LfRfidTuneViewModel * model) { + tune_view->view, + LfRfidTuneViewModel * model, + { if(model->pos == 0) { if(model->fine) { model->ARR -= 1; @@ -84,13 +90,15 @@ static void lfrfid_debug_view_tune_button_left(LfRfidTuneView* tune_view) { } model->dirty = true; - return true; - }); + }, + true); } static void lfrfid_debug_view_tune_button_right(LfRfidTuneView* tune_view) { with_view_model( - tune_view->view, (LfRfidTuneViewModel * model) { + tune_view->view, + LfRfidTuneViewModel * model, + { if(model->pos == 0) { if(model->fine) { model->ARR += 1; @@ -106,16 +114,13 @@ static void lfrfid_debug_view_tune_button_right(LfRfidTuneView* tune_view) { } model->dirty = true; - return true; - }); + }, + true); } static void lfrfid_debug_view_tune_button_ok(LfRfidTuneView* tune_view) { with_view_model( - tune_view->view, (LfRfidTuneViewModel * model) { - model->fine = !model->fine; - return true; - }); + tune_view->view, LfRfidTuneViewModel * model, { model->fine = !model->fine; }, true); } static bool lfrfid_debug_view_tune_input_callback(InputEvent* event, void* context) { @@ -158,14 +163,16 @@ LfRfidTuneView* lfrfid_debug_view_tune_alloc() { view_allocate_model(tune_view->view, ViewModelTypeLocking, sizeof(LfRfidTuneViewModel)); with_view_model( - tune_view->view, (LfRfidTuneViewModel * model) { + tune_view->view, + LfRfidTuneViewModel * model, + { model->dirty = true; model->fine = false; model->ARR = 511; model->CCR = 255; model->pos = 0; - return true; - }); + }, + true); view_set_draw_callback(tune_view->view, lfrfid_debug_view_tune_draw_callback); view_set_input_callback(tune_view->view, lfrfid_debug_view_tune_input_callback); @@ -184,24 +191,28 @@ View* lfrfid_debug_view_tune_get_view(LfRfidTuneView* tune_view) { void lfrfid_debug_view_tune_clean(LfRfidTuneView* tune_view) { with_view_model( - tune_view->view, (LfRfidTuneViewModel * model) { + tune_view->view, + LfRfidTuneViewModel * model, + { model->dirty = true; model->fine = false; model->ARR = 511; model->CCR = 255; model->pos = 0; - return true; - }); + }, + true); } bool lfrfid_debug_view_tune_is_dirty(LfRfidTuneView* tune_view) { bool result = false; with_view_model( - tune_view->view, (LfRfidTuneViewModel * model) { + tune_view->view, + LfRfidTuneViewModel * model, + { result = model->dirty; model->dirty = false; - return false; - }); + }, + false); return result; } @@ -209,10 +220,7 @@ bool lfrfid_debug_view_tune_is_dirty(LfRfidTuneView* tune_view) { uint32_t lfrfid_debug_view_tune_get_arr(LfRfidTuneView* tune_view) { uint32_t result = false; with_view_model( - tune_view->view, (LfRfidTuneViewModel * model) { - result = model->ARR; - return false; - }); + tune_view->view, LfRfidTuneViewModel * model, { result = model->ARR; }, false); return result; } @@ -220,10 +228,7 @@ uint32_t lfrfid_debug_view_tune_get_arr(LfRfidTuneView* tune_view) { uint32_t lfrfid_debug_view_tune_get_ccr(LfRfidTuneView* tune_view) { uint32_t result = false; with_view_model( - tune_view->view, (LfRfidTuneViewModel * model) { - result = model->CCR; - return false; - }); + tune_view->view, LfRfidTuneViewModel * model, { result = model->CCR; }, false); return result; } diff --git a/applications/debug/uart_echo/uart_echo.c b/applications/debug/uart_echo/uart_echo.c index 122862dd9bf..701e5325a3f 100644 --- a/applications/debug/uart_echo/uart_echo.c +++ b/applications/debug/uart_echo/uart_echo.c @@ -159,21 +159,20 @@ static int32_t uart_echo_worker(void* context) { if(length > 0) { furi_hal_uart_tx(FuriHalUartIdUSART1, data, length); with_view_model( - app->view, (UartDumpModel * model) { + app->view, + UartDumpModel * model, + { for(size_t i = 0; i < length; i++) { uart_echo_push_to_list(model, data[i]); } - return false; - }); + }, + false); } } while(length > 0); notification_message(app->notification, &sequence_notification); with_view_model( - app->view, (UartDumpModel * model) { - UNUSED(model); - return true; - }); + app->view, UartDumpModel * model, { UNUSED(model); }, true); } } @@ -200,15 +199,17 @@ static UartEchoApp* uart_echo_app_alloc() { view_set_input_callback(app->view, uart_echo_view_input_callback); view_allocate_model(app->view, ViewModelTypeLocking, sizeof(UartDumpModel)); with_view_model( - app->view, (UartDumpModel * model) { + app->view, + UartDumpModel * model, + { for(size_t i = 0; i < LINES_ON_SCREEN; i++) { model->line = 0; model->escape = false; model->list[i] = malloc(sizeof(ListElement)); model->list[i]->text = furi_string_alloc(); } - return true; - }); + }, + true); view_set_previous_callback(app->view, uart_echo_exit); view_dispatcher_add_view(app->view_dispatcher, 0, app->view); @@ -242,13 +243,15 @@ static void uart_echo_app_free(UartEchoApp* app) { view_dispatcher_remove_view(app->view_dispatcher, 0); with_view_model( - app->view, (UartDumpModel * model) { + app->view, + UartDumpModel * model, + { for(size_t i = 0; i < LINES_ON_SCREEN; i++) { furi_string_free(model->list[i]->text); free(model->list[i]); } - return true; - }); + }, + true); view_free(app->view); view_dispatcher_free(app->view_dispatcher); diff --git a/applications/main/archive/helpers/archive_browser.c b/applications/main/archive/helpers/archive_browser.c index f66030706f4..9689454ba5a 100644 --- a/applications/main/archive/helpers/archive_browser.c +++ b/applications/main/archive/helpers/archive_browser.c @@ -21,7 +21,9 @@ static void archive_switch_tab(browser, browser->last_tab_switch_dir); } else if(!furi_string_start_with_str(browser->path, "/app:")) { with_view_model( - browser->view, (ArchiveBrowserViewModel * model) { + browser->view, + ArchiveBrowserViewModel * model, + { files_array_reset(model->files); model->item_cnt = item_cnt; model->item_idx = (file_idx > 0) ? file_idx : 0; @@ -31,8 +33,8 @@ static void model->list_offset = 0; model->list_loading = true; model->folder_loading = false; - return false; - }); + }, + false); archive_update_offset(browser); file_browser_worker_load(browser->worker, load_offset, FILE_LIST_BUF_LEN); @@ -44,11 +46,13 @@ static void archive_list_load_cb(void* context, uint32_t list_load_offset) { ArchiveBrowserView* browser = (ArchiveBrowserView*)context; with_view_model( - browser->view, (ArchiveBrowserViewModel * model) { + browser->view, + ArchiveBrowserViewModel * model, + { files_array_reset(model->files); model->array_offset = list_load_offset; - return false; - }); + }, + false); } static void @@ -60,10 +64,7 @@ static void archive_add_file_item(browser, is_folder, furi_string_get_cstr(item_path)); } else { with_view_model( - browser->view, (ArchiveBrowserViewModel * model) { - model->list_loading = false; - return true; - }); + browser->view, ArchiveBrowserViewModel * model, { model->list_loading = false; }, true); } } @@ -72,10 +73,7 @@ static void archive_long_load_cb(void* context) { ArchiveBrowserView* browser = (ArchiveBrowserView*)context; with_view_model( - browser->view, (ArchiveBrowserViewModel * model) { - model->folder_loading = true; - return true; - }); + browser->view, ArchiveBrowserViewModel * model, { model->folder_loading = true; }, true); } static void archive_file_browser_set_path( @@ -113,7 +111,9 @@ void archive_update_offset(ArchiveBrowserView* browser) { furi_assert(browser); with_view_model( - browser->view, (ArchiveBrowserViewModel * model) { + browser->view, + ArchiveBrowserViewModel * model, + { uint16_t bounds = model->item_cnt > 3 ? 2 : model->item_cnt; if((model->item_cnt > 3u) && (model->item_idx >= ((int32_t)model->item_cnt - 1))) { @@ -125,9 +125,8 @@ void archive_update_offset(ArchiveBrowserView* browser) { model->list_offset = CLAMP(model->item_idx - 1, (int32_t)model->item_cnt - bounds, 0); } - - return true; - }); + }, + true); } void archive_update_focus(ArchiveBrowserView* browser, const char* target) { @@ -140,7 +139,9 @@ void archive_update_focus(ArchiveBrowserView* browser, const char* target) { archive_switch_tab(browser, TAB_RIGHT); } else { with_view_model( - browser->view, (ArchiveBrowserViewModel * model) { + browser->view, + ArchiveBrowserViewModel * model, + { uint16_t idx = 0; while(idx < files_array_size(model->files)) { ArchiveFile_t* current = files_array_get(model->files, idx); @@ -150,8 +151,8 @@ void archive_update_focus(ArchiveBrowserView* browser, const char* target) { } ++idx; } - return false; - }); + }, + false); archive_update_offset(browser); } @@ -162,10 +163,10 @@ size_t archive_file_get_array_size(ArchiveBrowserView* browser) { uint16_t size = 0; with_view_model( - browser->view, (ArchiveBrowserViewModel * model) { - size = files_array_size(model->files); - return false; - }); + browser->view, + ArchiveBrowserViewModel * model, + { size = files_array_size(model->files); }, + false); return size; } @@ -173,11 +174,13 @@ void archive_set_item_count(ArchiveBrowserView* browser, uint32_t count) { furi_assert(browser); with_view_model( - browser->view, (ArchiveBrowserViewModel * model) { + browser->view, + ArchiveBrowserViewModel * model, + { model->item_cnt = count; model->item_idx = CLAMP(model->item_idx, (int32_t)model->item_cnt - 1, 0); - return false; - }); + }, + false); archive_update_offset(browser); } @@ -186,7 +189,9 @@ void archive_file_array_rm_selected(ArchiveBrowserView* browser) { uint32_t items_cnt = 0; with_view_model( - browser->view, (ArchiveBrowserViewModel * model) { + browser->view, + ArchiveBrowserViewModel * model, + { files_array_remove_v( model->files, model->item_idx - model->array_offset, @@ -194,8 +199,8 @@ void archive_file_array_rm_selected(ArchiveBrowserView* browser) { model->item_cnt--; model->item_idx = CLAMP(model->item_idx, (int32_t)model->item_cnt - 1, 0); items_cnt = model->item_cnt; - return false; - }); + }, + false); if((items_cnt == 0) && (archive_is_home(browser))) { archive_switch_tab(browser, TAB_RIGHT); @@ -208,7 +213,9 @@ void archive_file_array_swap(ArchiveBrowserView* browser, int8_t dir) { furi_assert(browser); with_view_model( - browser->view, (ArchiveBrowserViewModel * model) { + browser->view, + ArchiveBrowserViewModel * model, + { ArchiveFile_t temp; size_t array_size = files_array_size(model->files) - 1; uint8_t swap_idx = CLAMP((size_t)(model->item_idx + dir), array_size, 0u); @@ -226,18 +233,18 @@ void archive_file_array_swap(ArchiveBrowserView* browser, int8_t dir) { } else { files_array_swap_at(model->files, model->item_idx, swap_idx); } - return false; - }); + }, + false); } void archive_file_array_rm_all(ArchiveBrowserView* browser) { furi_assert(browser); with_view_model( - browser->view, (ArchiveBrowserViewModel * model) { - files_array_reset(model->files); - return false; - }); + browser->view, + ArchiveBrowserViewModel * model, + { files_array_reset(model->files); }, + false); } void archive_file_array_load(ArchiveBrowserView* browser, int8_t dir) { @@ -246,7 +253,9 @@ void archive_file_array_load(ArchiveBrowserView* browser, int8_t dir) { int32_t offset_new = 0; with_view_model( - browser->view, (ArchiveBrowserViewModel * model) { + browser->view, + ArchiveBrowserViewModel * model, + { if(model->item_cnt > FILE_LIST_BUF_LEN) { if(dir < 0) { offset_new = model->item_idx - FILE_LIST_BUF_LEN / 4 * 3; @@ -262,8 +271,8 @@ void archive_file_array_load(ArchiveBrowserView* browser, int8_t dir) { offset_new = 0; } } - return false; - }); + }, + false); file_browser_worker_load(browser->worker, offset_new, FILE_LIST_BUF_LEN); } @@ -273,12 +282,14 @@ ArchiveFile_t* archive_get_current_file(ArchiveBrowserView* browser) { ArchiveFile_t* selected = NULL; with_view_model( - browser->view, (ArchiveBrowserViewModel * model) { + browser->view, + ArchiveBrowserViewModel * model, + { selected = files_array_size(model->files) ? files_array_get(model->files, model->item_idx - model->array_offset) : NULL; - return false; - }); + }, + false); return selected; } @@ -288,11 +299,13 @@ ArchiveFile_t* archive_get_file_at(ArchiveBrowserView* browser, size_t idx) { ArchiveFile_t* selected = NULL; with_view_model( - browser->view, (ArchiveBrowserViewModel * model) { + browser->view, + ArchiveBrowserViewModel * model, + { idx = CLAMP(idx - model->array_offset, files_array_size(model->files), 0u); selected = files_array_size(model->files) ? files_array_get(model->files, idx) : NULL; - return false; - }); + }, + false); return selected; } @@ -301,10 +314,7 @@ ArchiveTabEnum archive_get_tab(ArchiveBrowserView* browser) { ArchiveTabEnum tab_id = 0; with_view_model( - browser->view, (ArchiveBrowserViewModel * model) { - tab_id = model->tab_idx; - return false; - }); + browser->view, ArchiveBrowserViewModel * model, { tab_id = model->tab_idx; }, false); return tab_id; } @@ -328,10 +338,7 @@ void archive_set_tab(ArchiveBrowserView* browser, ArchiveTabEnum tab) { furi_assert(browser); with_view_model( - browser->view, (ArchiveBrowserViewModel * model) { - model->tab_idx = tab; - return false; - }); + browser->view, ArchiveBrowserViewModel * model, { model->tab_idx = tab; }, false); } void archive_add_app_item(ArchiveBrowserView* browser, const char* name) { @@ -344,11 +351,13 @@ void archive_add_app_item(ArchiveBrowserView* browser, const char* name) { archive_set_file_type(&item, name, false, true); with_view_model( - browser->view, (ArchiveBrowserViewModel * model) { + browser->view, + ArchiveBrowserViewModel * model, + { files_array_push_back(model->files, item); model->item_cnt = files_array_size(model->files); - return false; - }); + }, + false); ArchiveFile_t_clear(&item); } @@ -379,17 +388,19 @@ void archive_add_file_item(ArchiveBrowserView* browser, bool is_folder, const ch } } with_view_model( - browser->view, (ArchiveBrowserViewModel * model) { - files_array_push_back(model->files, item); - return false; - }); + browser->view, + ArchiveBrowserViewModel * model, + { files_array_push_back(model->files, item); }, + false); ArchiveFile_t_clear(&item); } void archive_show_file_menu(ArchiveBrowserView* browser, bool show) { furi_assert(browser); with_view_model( - browser->view, (ArchiveBrowserViewModel * model) { + browser->view, + ArchiveBrowserViewModel * model, + { if(show) { if(archive_is_item_in_array(model, model->item_idx)) { model->menu = true; @@ -403,19 +414,15 @@ void archive_show_file_menu(ArchiveBrowserView* browser, bool show) { model->menu = false; model->menu_idx = 0; } - - return true; - }); + }, + true); } void archive_favorites_move_mode(ArchiveBrowserView* browser, bool active) { furi_assert(browser); with_view_model( - browser->view, (ArchiveBrowserViewModel * model) { - model->move_fav = active; - return true; - }); + browser->view, ArchiveBrowserViewModel * model, { model->move_fav = active; }, true); } static bool archive_is_dir_exists(FuriString* path) { @@ -476,11 +483,13 @@ void archive_switch_tab(ArchiveBrowserView* browser, InputKey key) { archive_switch_tab(browser, key); } else { with_view_model( - browser->view, (ArchiveBrowserViewModel * model) { + browser->view, + ArchiveBrowserViewModel * model, + { model->item_idx = 0; model->array_offset = 0; - return false; - }); + }, + false); archive_get_items(browser, furi_string_get_cstr(browser->path)); archive_update_offset(browser); } @@ -493,10 +502,7 @@ void archive_enter_dir(ArchiveBrowserView* browser, FuriString* path) { int32_t idx_temp = 0; with_view_model( - browser->view, (ArchiveBrowserViewModel * model) { - idx_temp = model->item_idx; - return false; - }); + browser->view, ArchiveBrowserViewModel * model, { idx_temp = model->item_idx; }, false); furi_string_set(browser->path, path); file_browser_worker_folder_enter(browser->worker, path, idx_temp); @@ -514,9 +520,6 @@ void archive_refresh_dir(ArchiveBrowserView* browser) { int32_t idx_temp = 0; with_view_model( - browser->view, (ArchiveBrowserViewModel * model) { - idx_temp = model->item_idx; - return false; - }); + browser->view, ArchiveBrowserViewModel * model, { idx_temp = model->item_idx; }, false); file_browser_worker_folder_refresh(browser->worker, idx_temp); } diff --git a/applications/main/archive/views/archive_browser_view.c b/applications/main/archive/views/archive_browser_view.c index f13094a1ce4..a2e219b9530 100644 --- a/applications/main/archive/views/archive_browser_view.c +++ b/applications/main/archive/views/archive_browser_view.c @@ -263,33 +263,37 @@ static bool archive_view_input(InputEvent* event, void* context) { bool in_menu; bool move_fav_mode; with_view_model( - browser->view, (ArchiveBrowserViewModel * model) { + browser->view, + ArchiveBrowserViewModel * model, + { in_menu = model->menu; move_fav_mode = model->move_fav; - return false; - }); + }, + false); if(in_menu) { if(event->type == InputTypeShort) { if(event->key == InputKeyUp || event->key == InputKeyDown) { with_view_model( - browser->view, (ArchiveBrowserViewModel * model) { + browser->view, + ArchiveBrowserViewModel * model, + { if(event->key == InputKeyUp) { model->menu_idx = ((model->menu_idx - 1) + MENU_ITEMS) % MENU_ITEMS; } else if(event->key == InputKeyDown) { model->menu_idx = (model->menu_idx + 1) % MENU_ITEMS; } - return true; - }); + }, + true); } if(event->key == InputKeyOk) { uint8_t idx; with_view_model( - browser->view, (ArchiveBrowserViewModel * model) { - idx = model->menu_idx; - return false; - }); + browser->view, + ArchiveBrowserViewModel * model, + { idx = model->menu_idx; }, + false); browser->callback(file_menu_actions[idx], browser->context); } else if(event->key == InputKeyBack) { browser->callback(ArchiveBrowserEventFileMenuClose, browser->context); @@ -313,7 +317,9 @@ static bool archive_view_input(InputEvent* event, void* context) { if((event->key == InputKeyUp || event->key == InputKeyDown) && (event->type == InputTypeShort || event->type == InputTypeRepeat)) { with_view_model( - browser->view, (ArchiveBrowserViewModel * model) { + browser->view, + ArchiveBrowserViewModel * model, + { if(event->key == InputKeyUp) { model->item_idx = ((model->item_idx - 1) + model->item_cnt) % model->item_cnt; @@ -334,9 +340,8 @@ static bool archive_view_input(InputEvent* event, void* context) { browser->callback(ArchiveBrowserEventFavMoveDown, browser->context); } } - - return true; - }); + }, + true); archive_update_offset(browser); } @@ -384,11 +389,13 @@ ArchiveBrowserView* browser_alloc() { browser->path = furi_string_alloc_set(archive_get_default_path(TAB_DEFAULT)); with_view_model( - browser->view, (ArchiveBrowserViewModel * model) { + browser->view, + ArchiveBrowserViewModel * model, + { files_array_init(model->files); model->tab_idx = TAB_DEFAULT; - return true; - }); + }, + true); return browser; } @@ -401,10 +408,10 @@ void browser_free(ArchiveBrowserView* browser) { } with_view_model( - browser->view, (ArchiveBrowserViewModel * model) { - files_array_clear(model->files); - return false; - }); + browser->view, + ArchiveBrowserViewModel * model, + { files_array_clear(model->files); }, + false); furi_string_free(browser->path); diff --git a/applications/main/bad_usb/views/bad_usb_view.c b/applications/main/bad_usb/views/bad_usb_view.c index b6310193b23..db10d01eea8 100644 --- a/applications/main/bad_usb/views/bad_usb_view.c +++ b/applications/main/bad_usb/views/bad_usb_view.c @@ -145,29 +145,33 @@ void bad_usb_set_ok_callback(BadUsb* bad_usb, BadUsbOkCallback callback, void* c furi_assert(bad_usb); furi_assert(callback); with_view_model( - bad_usb->view, (BadUsbModel * model) { + bad_usb->view, + BadUsbModel * model, + { UNUSED(model); bad_usb->callback = callback; bad_usb->context = context; - return true; - }); + }, + true); } void bad_usb_set_file_name(BadUsb* bad_usb, const char* name) { furi_assert(name); with_view_model( - bad_usb->view, (BadUsbModel * model) { - strlcpy(model->file_name, name, MAX_NAME_LEN); - return true; - }); + bad_usb->view, + BadUsbModel * model, + { strlcpy(model->file_name, name, MAX_NAME_LEN); }, + true); } void bad_usb_set_state(BadUsb* bad_usb, BadUsbState* st) { furi_assert(st); with_view_model( - bad_usb->view, (BadUsbModel * model) { + bad_usb->view, + BadUsbModel * model, + { memcpy(&(model->state), st, sizeof(BadUsbState)); model->anim_frame ^= 1; - return true; - }); + }, + true); } diff --git a/applications/main/gpio/views/gpio_test.c b/applications/main/gpio/views/gpio_test.c index 89c09f86492..69dc0f67bfe 100644 --- a/applications/main/gpio/views/gpio_test.c +++ b/applications/main/gpio/views/gpio_test.c @@ -48,23 +48,27 @@ static bool gpio_test_input_callback(InputEvent* event, void* context) { static bool gpio_test_process_left(GpioTest* gpio_test) { with_view_model( - gpio_test->view, (GpioTestModel * model) { + gpio_test->view, + GpioTestModel * model, + { if(model->pin_idx) { model->pin_idx--; } - return true; - }); + }, + true); return true; } static bool gpio_test_process_right(GpioTest* gpio_test) { with_view_model( - gpio_test->view, (GpioTestModel * model) { + gpio_test->view, + GpioTestModel * model, + { if(model->pin_idx < GPIO_ITEM_COUNT) { model->pin_idx++; } - return true; - }); + }, + true); return true; } @@ -72,7 +76,9 @@ static bool gpio_test_process_ok(GpioTest* gpio_test, InputEvent* event) { bool consumed = false; with_view_model( - gpio_test->view, (GpioTestModel * model) { + gpio_test->view, + GpioTestModel * model, + { if(event->type == InputTypePress) { if(model->pin_idx < GPIO_ITEM_COUNT) { gpio_item_set_pin(model->pin_idx, true); @@ -89,8 +95,8 @@ static bool gpio_test_process_ok(GpioTest* gpio_test, InputEvent* event) { consumed = true; } gpio_test->callback(event->type, gpio_test->context); - return true; - }); + }, + true); return consumed; } @@ -122,10 +128,12 @@ void gpio_test_set_ok_callback(GpioTest* gpio_test, GpioTestOkCallback callback, furi_assert(gpio_test); furi_assert(callback); with_view_model( - gpio_test->view, (GpioTestModel * model) { + gpio_test->view, + GpioTestModel * model, + { UNUSED(model); gpio_test->callback = callback; gpio_test->context = context; - return false; - }); + }, + false); } diff --git a/applications/main/gpio/views/gpio_usb_uart.c b/applications/main/gpio/views/gpio_usb_uart.c index d190ecab02c..c7406d29ba4 100644 --- a/applications/main/gpio/views/gpio_usb_uart.c +++ b/applications/main/gpio/views/gpio_usb_uart.c @@ -129,12 +129,14 @@ void gpio_usb_uart_set_callback(GpioUsbUart* usb_uart, GpioUsbUartCallback callb furi_assert(callback); with_view_model( - usb_uart->view, (GpioUsbUartModel * model) { + usb_uart->view, + GpioUsbUartModel * model, + { UNUSED(model); usb_uart->callback = callback; usb_uart->context = context; - return false; - }); + }, + false); } void gpio_usb_uart_update_state(GpioUsbUart* instance, UsbUartConfig* cfg, UsbUartState* st) { @@ -143,7 +145,9 @@ void gpio_usb_uart_update_state(GpioUsbUart* instance, UsbUartConfig* cfg, UsbUa furi_assert(st); with_view_model( - instance->view, (GpioUsbUartModel * model) { + instance->view, + GpioUsbUartModel * model, + { model->baudrate = st->baudrate_cur; model->vcp_port = cfg->vcp_ch; model->tx_pin = (cfg->uart_ch == 0) ? (13) : (15); @@ -152,6 +156,6 @@ void gpio_usb_uart_update_state(GpioUsbUart* instance, UsbUartConfig* cfg, UsbUa model->rx_active = (model->rx_cnt != st->rx_cnt); model->tx_cnt = st->tx_cnt; model->rx_cnt = st->rx_cnt; - return true; - }); + }, + true); } diff --git a/applications/main/lfrfid/views/lfrfid_view_read.c b/applications/main/lfrfid/views/lfrfid_view_read.c index 66caf8df747..0d4db61788c 100644 --- a/applications/main/lfrfid/views/lfrfid_view_read.c +++ b/applications/main/lfrfid/views/lfrfid_view_read.c @@ -56,19 +56,13 @@ static void lfrfid_view_read_draw_callback(Canvas* canvas, void* _model) { void lfrfid_view_read_enter(void* context) { LfRfidReadView* read_view = context; with_view_model( - read_view->view, (LfRfidReadViewModel * model) { - icon_animation_start(model->icon); - return true; - }); + read_view->view, LfRfidReadViewModel * model, { icon_animation_start(model->icon); }, true); } void lfrfid_view_read_exit(void* context) { LfRfidReadView* read_view = context; with_view_model( - read_view->view, (LfRfidReadViewModel * model) { - icon_animation_stop(model->icon); - return false; - }); + read_view->view, LfRfidReadViewModel * model, { icon_animation_stop(model->icon); }, false); } LfRfidReadView* lfrfid_view_read_alloc() { @@ -78,11 +72,13 @@ LfRfidReadView* lfrfid_view_read_alloc() { view_allocate_model(read_view->view, ViewModelTypeLocking, sizeof(LfRfidReadViewModel)); with_view_model( - read_view->view, (LfRfidReadViewModel * model) { + read_view->view, + LfRfidReadViewModel * model, + { model->icon = icon_animation_alloc(&A_Round_loader_8x8); view_tie_icon_animation(read_view->view, model->icon); - return false; - }); + }, + false); view_set_draw_callback(read_view->view, lfrfid_view_read_draw_callback); view_set_enter_callback(read_view->view, lfrfid_view_read_enter); @@ -93,10 +89,7 @@ LfRfidReadView* lfrfid_view_read_alloc() { void lfrfid_view_read_free(LfRfidReadView* read_view) { with_view_model( - read_view->view, (LfRfidReadViewModel * model) { - icon_animation_free(model->icon); - return false; - }); + read_view->view, LfRfidReadViewModel * model, { icon_animation_free(model->icon); }, false); view_free(read_view->view); free(read_view); @@ -108,10 +101,12 @@ View* lfrfid_view_read_get_view(LfRfidReadView* read_view) { void lfrfid_view_read_set_read_mode(LfRfidReadView* read_view, LfRfidReadViewMode mode) { with_view_model( - read_view->view, (LfRfidReadViewModel * model) { + read_view->view, + LfRfidReadViewModel * model, + { icon_animation_stop(model->icon); icon_animation_start(model->icon); model->read_mode = mode; - return true; - }); + }, + true); } diff --git a/applications/main/nfc/views/detect_reader.c b/applications/main/nfc/views/detect_reader.c index 9a07704384e..2dbb4338b92 100644 --- a/applications/main/nfc/views/detect_reader.c +++ b/applications/main/nfc/views/detect_reader.c @@ -64,10 +64,7 @@ static bool detect_reader_input_callback(InputEvent* event, void* context) { uint8_t nonces = 0; with_view_model( - detect_reader->view, (DetectReaderViewModel * model) { - nonces = model->nonces; - return false; - }); + detect_reader->view, DetectReaderViewModel * model, { nonces = model->nonces; }, false); if(event->type == InputTypeShort) { if(event->key == InputKeyOk) { @@ -103,12 +100,14 @@ void detect_reader_reset(DetectReader* detect_reader) { furi_assert(detect_reader); with_view_model( - detect_reader->view, (DetectReaderViewModel * model) { + detect_reader->view, + DetectReaderViewModel * model, + { model->nonces = 0; model->nonces_max = 0; model->state = DetectReaderStateStart; - return false; - }); + }, + false); } View* detect_reader_get_view(DetectReader* detect_reader) { @@ -132,27 +131,24 @@ void detect_reader_set_nonces_max(DetectReader* detect_reader, uint16_t nonces_m furi_assert(detect_reader); with_view_model( - detect_reader->view, (DetectReaderViewModel * model) { - model->nonces_max = nonces_max; - return false; - }); + detect_reader->view, + DetectReaderViewModel * model, + { model->nonces_max = nonces_max; }, + false); } void detect_reader_set_nonces_collected(DetectReader* detect_reader, uint16_t nonces_collected) { furi_assert(detect_reader); with_view_model( - detect_reader->view, (DetectReaderViewModel * model) { - model->nonces = nonces_collected; - return false; - }); + detect_reader->view, + DetectReaderViewModel * model, + { model->nonces = nonces_collected; }, + false); } void detect_reader_set_state(DetectReader* detect_reader, DetectReaderState state) { furi_assert(detect_reader); with_view_model( - detect_reader->view, (DetectReaderViewModel * model) { - model->state = state; - return true; - }); + detect_reader->view, DetectReaderViewModel * model, { model->state = state; }, true); } diff --git a/applications/main/nfc/views/dict_attack.c b/applications/main/nfc/views/dict_attack.c index cbafbf69a42..c5f55ae7621 100644 --- a/applications/main/nfc/views/dict_attack.c +++ b/applications/main/nfc/views/dict_attack.c @@ -80,20 +80,20 @@ DictAttack* dict_attack_alloc() { view_set_input_callback(dict_attack->view, dict_attack_input_callback); view_set_context(dict_attack->view, dict_attack); with_view_model( - dict_attack->view, (DictAttackViewModel * model) { - model->header = furi_string_alloc(); - return false; - }); + dict_attack->view, + DictAttackViewModel * model, + { model->header = furi_string_alloc(); }, + false); return dict_attack; } void dict_attack_free(DictAttack* dict_attack) { furi_assert(dict_attack); with_view_model( - dict_attack->view, (DictAttackViewModel * model) { - furi_string_free(model->header); - return false; - }); + dict_attack->view, + DictAttackViewModel * model, + { furi_string_free(model->header); }, + false); view_free(dict_attack->view); free(dict_attack); } @@ -101,7 +101,9 @@ void dict_attack_free(DictAttack* dict_attack) { void dict_attack_reset(DictAttack* dict_attack) { furi_assert(dict_attack); with_view_model( - dict_attack->view, (DictAttackViewModel * model) { + dict_attack->view, + DictAttackViewModel * model, + { model->state = DictAttackStateRead; model->type = MfClassicType1k; model->sectors_total = 0; @@ -112,8 +114,8 @@ void dict_attack_reset(DictAttack* dict_attack) { model->dict_keys_total = 0; model->dict_keys_current = 0; furi_string_reset(model->header); - return false; - }); + }, + false); } View* dict_attack_get_view(DictAttack* dict_attack) { @@ -133,99 +135,103 @@ void dict_attack_set_header(DictAttack* dict_attack, const char* header) { furi_assert(header); with_view_model( - dict_attack->view, (DictAttackViewModel * model) { - furi_string_set(model->header, header); - return true; - }); + dict_attack->view, + DictAttackViewModel * model, + { furi_string_set(model->header, header); }, + true); } void dict_attack_set_card_detected(DictAttack* dict_attack, MfClassicType type) { furi_assert(dict_attack); with_view_model( - dict_attack->view, (DictAttackViewModel * model) { + dict_attack->view, + DictAttackViewModel * model, + { model->state = DictAttackStateRead; model->sectors_total = mf_classic_get_total_sectors_num(type); model->keys_total = model->sectors_total * 2; - return true; - }); + }, + true); } void dict_attack_set_card_removed(DictAttack* dict_attack) { furi_assert(dict_attack); with_view_model( - dict_attack->view, (DictAttackViewModel * model) { - model->state = DictAttackStateCardRemoved; - return true; - }); + dict_attack->view, + DictAttackViewModel * model, + { model->state = DictAttackStateCardRemoved; }, + true); } void dict_attack_set_sector_read(DictAttack* dict_attack, uint8_t sec_read) { furi_assert(dict_attack); with_view_model( - dict_attack->view, (DictAttackViewModel * model) { - model->sectors_read = sec_read; - return true; - }); + dict_attack->view, DictAttackViewModel * model, { model->sectors_read = sec_read; }, true); } void dict_attack_set_keys_found(DictAttack* dict_attack, uint8_t keys_found) { furi_assert(dict_attack); with_view_model( - dict_attack->view, (DictAttackViewModel * model) { - model->keys_found = keys_found; - return true; - }); + dict_attack->view, DictAttackViewModel * model, { model->keys_found = keys_found; }, true); } void dict_attack_set_current_sector(DictAttack* dict_attack, uint8_t curr_sec) { furi_assert(dict_attack); with_view_model( - dict_attack->view, (DictAttackViewModel * model) { + dict_attack->view, + DictAttackViewModel * model, + { model->sector_current = curr_sec; model->dict_keys_current = 0; - return true; - }); + }, + true); } void dict_attack_inc_current_sector(DictAttack* dict_attack) { furi_assert(dict_attack); with_view_model( - dict_attack->view, (DictAttackViewModel * model) { + dict_attack->view, + DictAttackViewModel * model, + { if(model->sector_current < model->sectors_total) { model->sector_current++; model->dict_keys_current = 0; } - return true; - }); + }, + true); } void dict_attack_inc_keys_found(DictAttack* dict_attack) { furi_assert(dict_attack); with_view_model( - dict_attack->view, (DictAttackViewModel * model) { + dict_attack->view, + DictAttackViewModel * model, + { if(model->keys_found < model->keys_total) { model->keys_found++; } - return true; - }); + }, + true); } void dict_attack_set_total_dict_keys(DictAttack* dict_attack, uint16_t dict_keys_total) { furi_assert(dict_attack); with_view_model( - dict_attack->view, (DictAttackViewModel * model) { - model->dict_keys_total = dict_keys_total; - return true; - }); + dict_attack->view, + DictAttackViewModel * model, + { model->dict_keys_total = dict_keys_total; }, + true); } void dict_attack_inc_current_dict_key(DictAttack* dict_attack, uint16_t keys_tried) { furi_assert(dict_attack); with_view_model( - dict_attack->view, (DictAttackViewModel * model) { + dict_attack->view, + DictAttackViewModel * model, + { if(model->dict_keys_current + keys_tried < model->dict_keys_total) { model->dict_keys_current += keys_tried; } - return true; - }); + }, + true); } diff --git a/applications/main/subghz/views/receiver.c b/applications/main/subghz/views/receiver.c index 6ec12e78134..cdebc632037 100644 --- a/applications/main/subghz/views/receiver.c +++ b/applications/main/subghz/views/receiver.c @@ -67,17 +67,17 @@ void subghz_view_receiver_set_lock(SubGhzViewReceiver* subghz_receiver, SubGhzLo if(lock == SubGhzLockOn) { subghz_receiver->lock = lock; with_view_model( - subghz_receiver->view, (SubGhzViewReceiverModel * model) { - model->bar_show = SubGhzViewReceiverBarShowLock; - return true; - }); + subghz_receiver->view, + SubGhzViewReceiverModel * model, + { model->bar_show = SubGhzViewReceiverBarShowLock; }, + true); furi_timer_start(subghz_receiver->timer, pdMS_TO_TICKS(1000)); } else { with_view_model( - subghz_receiver->view, (SubGhzViewReceiverModel * model) { - model->bar_show = SubGhzViewReceiverBarShowDefault; - return true; - }); + subghz_receiver->view, + SubGhzViewReceiverModel * model, + { model->bar_show = SubGhzViewReceiverBarShowDefault; }, + true); } } @@ -95,7 +95,9 @@ static void subghz_view_receiver_update_offset(SubGhzViewReceiver* subghz_receiv furi_assert(subghz_receiver); with_view_model( - subghz_receiver->view, (SubGhzViewReceiverModel * model) { + subghz_receiver->view, + SubGhzViewReceiverModel * model, + { size_t history_item = model->history_item; uint16_t bounds = history_item > 3 ? 2 : history_item; @@ -107,8 +109,8 @@ static void subghz_view_receiver_update_offset(SubGhzViewReceiver* subghz_receiv } else if(model->list_offset > model->idx - bounds) { model->list_offset = CLAMP(model->idx - 1, (int16_t)(history_item - bounds), 0); } - return true; - }); + }, + true); } void subghz_view_receiver_add_item_to_menu( @@ -117,7 +119,9 @@ void subghz_view_receiver_add_item_to_menu( uint8_t type) { furi_assert(subghz_receiver); with_view_model( - subghz_receiver->view, (SubGhzViewReceiverModel * model) { + subghz_receiver->view, + SubGhzViewReceiverModel * model, + { SubGhzReceiverMenuItem* item_menu = SubGhzReceiverMenuItemArray_push_raw(model->history->data); item_menu->item_str = furi_string_alloc_set(name); @@ -128,9 +132,8 @@ void subghz_view_receiver_add_item_to_menu( } else { model->history_item++; } - - return true; - }); + }, + true); subghz_view_receiver_update_offset(subghz_receiver); } @@ -141,12 +144,14 @@ void subghz_view_receiver_add_data_statusbar( const char* history_stat_str) { furi_assert(subghz_receiver); with_view_model( - subghz_receiver->view, (SubGhzViewReceiverModel * model) { + subghz_receiver->view, + SubGhzViewReceiverModel * model, + { furi_string_set(model->frequency_str, frequency_str); furi_string_set(model->preset_str, preset_str); furi_string_set(model->history_stat_str, history_stat_str); - return true; - }); + }, + true); } static void subghz_view_receiver_draw_frame(Canvas* canvas, uint16_t idx, bool scrollbar) { @@ -240,10 +245,10 @@ static void subghz_view_receiver_timer_callback(void* context) { furi_assert(context); SubGhzViewReceiver* subghz_receiver = context; with_view_model( - subghz_receiver->view, (SubGhzViewReceiverModel * model) { - model->bar_show = SubGhzViewReceiverBarShowDefault; - return true; - }); + subghz_receiver->view, + SubGhzViewReceiverModel * model, + { model->bar_show = SubGhzViewReceiverBarShowDefault; }, + true); if(subghz_receiver->lock_count < UNLOCK_CNT) { subghz_receiver->callback( SubGhzCustomEventViewReceiverOffDisplay, subghz_receiver->context); @@ -260,10 +265,10 @@ bool subghz_view_receiver_input(InputEvent* event, void* context) { if(subghz_receiver->lock == SubGhzLockOn) { with_view_model( - subghz_receiver->view, (SubGhzViewReceiverModel * model) { - model->bar_show = SubGhzViewReceiverBarShowToUnlockPress; - return true; - }); + subghz_receiver->view, + SubGhzViewReceiverModel * model, + { model->bar_show = SubGhzViewReceiverBarShowToUnlockPress; }, + true); if(subghz_receiver->lock_count == 0) { furi_timer_start(subghz_receiver->timer, pdMS_TO_TICKS(1000)); } @@ -274,10 +279,10 @@ bool subghz_view_receiver_input(InputEvent* event, void* context) { // subghz_receiver->callback( // SubGhzCustomEventViewReceiverUnlock, subghz_receiver->context); with_view_model( - subghz_receiver->view, (SubGhzViewReceiverModel * model) { - model->bar_show = SubGhzViewReceiverBarShowUnlock; - return true; - }); + subghz_receiver->view, + SubGhzViewReceiverModel * model, + { model->bar_show = SubGhzViewReceiverBarShowUnlock; }, + true); //subghz_receiver->lock = SubGhzLockOff; furi_timer_start(subghz_receiver->timer, pdMS_TO_TICKS(650)); } @@ -291,29 +296,35 @@ bool subghz_view_receiver_input(InputEvent* event, void* context) { event->key == InputKeyUp && (event->type == InputTypeShort || event->type == InputTypeRepeat)) { with_view_model( - subghz_receiver->view, (SubGhzViewReceiverModel * model) { + subghz_receiver->view, + SubGhzViewReceiverModel * model, + { if(model->idx != 0) model->idx--; - return true; - }); + }, + true); } else if( event->key == InputKeyDown && (event->type == InputTypeShort || event->type == InputTypeRepeat)) { with_view_model( - subghz_receiver->view, (SubGhzViewReceiverModel * model) { + subghz_receiver->view, + SubGhzViewReceiverModel * model, + { if(model->idx != model->history_item - 1) model->idx++; - return true; - }); + }, + true); } else if(event->key == InputKeyLeft && event->type == InputTypeShort) { subghz_receiver->callback(SubGhzCustomEventViewReceiverConfig, subghz_receiver->context); } else if(event->key == InputKeyOk && event->type == InputTypeShort) { with_view_model( - subghz_receiver->view, (SubGhzViewReceiverModel * model) { + subghz_receiver->view, + SubGhzViewReceiverModel * model, + { if(model->history_item != 0) { subghz_receiver->callback( SubGhzCustomEventViewReceiverOK, subghz_receiver->context); } - return false; - }); + }, + false); } subghz_view_receiver_update_offset(subghz_receiver); @@ -329,7 +340,9 @@ void subghz_view_receiver_exit(void* context) { furi_assert(context); SubGhzViewReceiver* subghz_receiver = context; with_view_model( - subghz_receiver->view, (SubGhzViewReceiverModel * model) { + subghz_receiver->view, + SubGhzViewReceiverModel * model, + { furi_string_reset(model->frequency_str); furi_string_reset(model->preset_str); furi_string_reset(model->history_stat_str); @@ -342,8 +355,8 @@ void subghz_view_receiver_exit(void* context) { model->idx = 0; model->list_offset = 0; model->history_item = 0; - return false; - }); + }, + false); furi_timer_stop(subghz_receiver->timer); } @@ -364,15 +377,17 @@ SubGhzViewReceiver* subghz_view_receiver_alloc() { view_set_exit_callback(subghz_receiver->view, subghz_view_receiver_exit); with_view_model( - subghz_receiver->view, (SubGhzViewReceiverModel * model) { + subghz_receiver->view, + SubGhzViewReceiverModel * model, + { model->frequency_str = furi_string_alloc(); model->preset_str = furi_string_alloc(); model->history_stat_str = furi_string_alloc(); model->bar_show = SubGhzViewReceiverBarShowDefault; model->history = malloc(sizeof(SubGhzReceiverHistory)); SubGhzReceiverMenuItemArray_init(model->history->data); - return true; - }); + }, + true); subghz_receiver->timer = furi_timer_alloc(subghz_view_receiver_timer_callback, FuriTimerTypeOnce, subghz_receiver); return subghz_receiver; @@ -382,7 +397,9 @@ void subghz_view_receiver_free(SubGhzViewReceiver* subghz_receiver) { furi_assert(subghz_receiver); with_view_model( - subghz_receiver->view, (SubGhzViewReceiverModel * model) { + subghz_receiver->view, + SubGhzViewReceiverModel * model, + { furi_string_free(model->frequency_str); furi_string_free(model->preset_str); furi_string_free(model->history_stat_str); @@ -393,8 +410,8 @@ void subghz_view_receiver_free(SubGhzViewReceiver* subghz_receiver) { } SubGhzReceiverMenuItemArray_clear(model->history->data); free(model->history); - return false; - }); + }, + false); furi_timer_free(subghz_receiver->timer); view_free(subghz_receiver->view); free(subghz_receiver); @@ -409,20 +426,19 @@ uint16_t subghz_view_receiver_get_idx_menu(SubGhzViewReceiver* subghz_receiver) furi_assert(subghz_receiver); uint32_t idx = 0; with_view_model( - subghz_receiver->view, (SubGhzViewReceiverModel * model) { - idx = model->idx; - return false; - }); + subghz_receiver->view, SubGhzViewReceiverModel * model, { idx = model->idx; }, false); return idx; } void subghz_view_receiver_set_idx_menu(SubGhzViewReceiver* subghz_receiver, uint16_t idx) { furi_assert(subghz_receiver); with_view_model( - subghz_receiver->view, (SubGhzViewReceiverModel * model) { + subghz_receiver->view, + SubGhzViewReceiverModel * model, + { model->idx = idx; if(model->idx > 2) model->list_offset = idx - 2; - return true; - }); + }, + true); subghz_view_receiver_update_offset(subghz_receiver); } diff --git a/applications/main/subghz/views/subghz_frequency_analyzer.c b/applications/main/subghz/views/subghz_frequency_analyzer.c index 608cf5862aa..c169f3611e3 100644 --- a/applications/main/subghz/views/subghz_frequency_analyzer.c +++ b/applications/main/subghz/views/subghz_frequency_analyzer.c @@ -132,12 +132,14 @@ void subghz_frequency_analyzer_pair_callback( } //update history with_view_model( - instance->view, (SubGhzFrequencyAnalyzerModel * model) { + instance->view, + SubGhzFrequencyAnalyzerModel * model, + { model->history_frequency[2] = model->history_frequency[1]; model->history_frequency[1] = model->history_frequency[0]; model->history_frequency[0] = model->frequency; - return false; - }); + }, + false); } else if((rssi != 0.f) && (!instance->locked)) { if(instance->callback) { instance->callback(SubGhzCustomEventSceneAnalyzerLock, instance->context); @@ -146,12 +148,14 @@ void subghz_frequency_analyzer_pair_callback( instance->locked = (rssi != 0.f); with_view_model( - instance->view, (SubGhzFrequencyAnalyzerModel * model) { + instance->view, + SubGhzFrequencyAnalyzerModel * model, + { model->rssi = rssi; model->frequency = frequency; model->signal = signal; - return true; - }); + }, + true); } void subghz_frequency_analyzer_enter(void* context) { @@ -169,14 +173,16 @@ void subghz_frequency_analyzer_enter(void* context) { subghz_frequency_analyzer_worker_start(instance->worker); with_view_model( - instance->view, (SubGhzFrequencyAnalyzerModel * model) { + instance->view, + SubGhzFrequencyAnalyzerModel * model, + { model->rssi = 0; model->frequency = 0; model->history_frequency[2] = 0; model->history_frequency[1] = 0; model->history_frequency[0] = 0; - return true; - }); + }, + true); } void subghz_frequency_analyzer_exit(void* context) { @@ -190,10 +196,7 @@ void subghz_frequency_analyzer_exit(void* context) { subghz_frequency_analyzer_worker_free(instance->worker); with_view_model( - instance->view, (SubGhzFrequencyAnalyzerModel * model) { - model->rssi = 0; - return true; - }); + instance->view, SubGhzFrequencyAnalyzerModel * model, { model->rssi = 0; }, true); } SubGhzFrequencyAnalyzer* subghz_frequency_analyzer_alloc() { @@ -210,10 +213,7 @@ SubGhzFrequencyAnalyzer* subghz_frequency_analyzer_alloc() { view_set_exit_callback(instance->view, subghz_frequency_analyzer_exit); with_view_model( - instance->view, (SubGhzFrequencyAnalyzerModel * model) { - model->rssi = 0; - return true; - }); + instance->view, SubGhzFrequencyAnalyzerModel * model, { model->rssi = 0; }, true); return instance; } diff --git a/applications/main/subghz/views/subghz_read_raw.c b/applications/main/subghz/views/subghz_read_raw.c index 0b4305b7e30..2d951b11afd 100644 --- a/applications/main/subghz/views/subghz_read_raw.c +++ b/applications/main/subghz/views/subghz_read_raw.c @@ -45,11 +45,13 @@ void subghz_read_raw_add_data_statusbar( const char* preset_str) { furi_assert(instance); with_view_model( - instance->view, (SubGhzReadRAWModel * model) { + instance->view, + SubGhzReadRAWModel * model, + { furi_string_set(model->frequency_str, frequency_str); furi_string_set(model->preset_str, preset_str); - return true; - }); + }, + true); } void subghz_read_raw_add_data_rssi(SubGhzReadRAW* instance, float rssi) { @@ -63,31 +65,35 @@ void subghz_read_raw_add_data_rssi(SubGhzReadRAW* instance, float rssi) { } with_view_model( - instance->view, (SubGhzReadRAWModel * model) { + instance->view, + SubGhzReadRAWModel * model, + { model->rssi_history[model->ind_write++] = u_rssi; if(model->ind_write > SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE) { model->rssi_history_end = true; model->ind_write = 0; } - return true; - }); + }, + true); } void subghz_read_raw_update_sample_write(SubGhzReadRAW* instance, size_t sample) { furi_assert(instance); with_view_model( - instance->view, (SubGhzReadRAWModel * model) { - furi_string_printf(model->sample_write, "%d spl.", sample); - return false; - }); + instance->view, + SubGhzReadRAWModel * model, + { furi_string_printf(model->sample_write, "%d spl.", sample); }, + false); } void subghz_read_raw_stop_send(SubGhzReadRAW* instance) { furi_assert(instance); with_view_model( - instance->view, (SubGhzReadRAWModel * model) { + instance->view, + SubGhzReadRAWModel * model, + { switch(model->status) { case SubGhzReadRAWStatusTXRepeat: case SubGhzReadRAWStatusLoadKeyTXRepeat: @@ -105,19 +111,21 @@ void subghz_read_raw_stop_send(SubGhzReadRAW* instance) { model->status = SubGhzReadRAWStatusIDLE; break; } - return true; - }); + }, + true); } void subghz_read_raw_update_sin(SubGhzReadRAW* instance) { furi_assert(instance); with_view_model( - instance->view, (SubGhzReadRAWModel * model) { + instance->view, + SubGhzReadRAWModel * model, + { if(model->ind_sin++ > 62) { model->ind_sin = 0; } - return true; - }); + }, + true); } static int8_t subghz_read_raw_tab_sin(uint8_t x) { @@ -286,9 +294,11 @@ bool subghz_read_raw_input(InputEvent* event, void* context) { //further check of events is not needed, we exit return false; } else if(event->key == InputKeyOk && event->type == InputTypePress) { + uint8_t ret = false; with_view_model( - instance->view, (SubGhzReadRAWModel * model) { - uint8_t ret = false; + instance->view, + SubGhzReadRAWModel * model, + { switch(model->status) { case SubGhzReadRAWStatusIDLE: // Start TX @@ -314,11 +324,13 @@ bool subghz_read_raw_input(InputEvent* event, void* context) { default: break; } - return ret; - }); + }, + ret); } else if(event->key == InputKeyOk && event->type == InputTypeRelease) { with_view_model( - instance->view, (SubGhzReadRAWModel * model) { + instance->view, + SubGhzReadRAWModel * model, + { if(model->status == SubGhzReadRAWStatusTXRepeat) { // Stop repeat TX model->status = SubGhzReadRAWStatusTX; @@ -326,11 +338,13 @@ bool subghz_read_raw_input(InputEvent* event, void* context) { // Stop repeat TX model->status = SubGhzReadRAWStatusLoadKeyTX; } - return false; - }); + }, + false); } else if(event->key == InputKeyBack && event->type == InputTypeShort) { with_view_model( - instance->view, (SubGhzReadRAWModel * model) { + instance->view, + SubGhzReadRAWModel * model, + { switch(model->status) { case SubGhzReadRAWStatusREC: //Stop REC @@ -357,11 +371,13 @@ bool subghz_read_raw_input(InputEvent* event, void* context) { instance->callback(SubGhzCustomEventViewReadRAWBack, instance->context); break; } - return true; - }); + }, + true); } else if(event->key == InputKeyLeft && event->type == InputTypeShort) { with_view_model( - instance->view, (SubGhzReadRAWModel * model) { + instance->view, + SubGhzReadRAWModel * model, + { if(model->status == SubGhzReadRAWStatusStart) { //Config instance->callback(SubGhzCustomEventViewReadRAWConfig, instance->context); @@ -376,11 +392,13 @@ bool subghz_read_raw_input(InputEvent* event, void* context) { furi_string_reset(model->file_name); instance->callback(SubGhzCustomEventViewReadRAWErase, instance->context); } - return true; - }); + }, + true); } else if(event->key == InputKeyRight && event->type == InputTypeShort) { with_view_model( - instance->view, (SubGhzReadRAWModel * model) { + instance->view, + SubGhzReadRAWModel * model, + { if(model->status == SubGhzReadRAWStatusIDLE) { //Save instance->callback(SubGhzCustomEventViewReadRAWSave, instance->context); @@ -388,11 +406,13 @@ bool subghz_read_raw_input(InputEvent* event, void* context) { //More instance->callback(SubGhzCustomEventViewReadRAWMore, instance->context); } - return true; - }); + }, + true); } else if(event->key == InputKeyOk && event->type == InputTypeShort) { with_view_model( - instance->view, (SubGhzReadRAWModel * model) { + instance->view, + SubGhzReadRAWModel * model, + { if(model->status == SubGhzReadRAWStatusStart) { //Record instance->callback(SubGhzCustomEventViewReadRAWREC, instance->context); @@ -404,8 +424,8 @@ bool subghz_read_raw_input(InputEvent* event, void* context) { instance->callback(SubGhzCustomEventViewReadRAWIDLE, instance->context); model->status = SubGhzReadRAWStatusIDLE; } - return true; - }); + }, + true); } return true; } @@ -419,36 +439,42 @@ void subghz_read_raw_set_status( switch(status) { case SubGhzReadRAWStatusStart: with_view_model( - instance->view, (SubGhzReadRAWModel * model) { + instance->view, + SubGhzReadRAWModel * model, + { model->status = SubGhzReadRAWStatusStart; model->rssi_history_end = false; model->ind_write = 0; furi_string_reset(model->file_name); furi_string_set(model->sample_write, "0 spl."); - return true; - }); + }, + true); break; case SubGhzReadRAWStatusIDLE: with_view_model( - instance->view, (SubGhzReadRAWModel * model) { - model->status = SubGhzReadRAWStatusIDLE; - return true; - }); + instance->view, + SubGhzReadRAWModel * model, + { model->status = SubGhzReadRAWStatusIDLE; }, + true); break; case SubGhzReadRAWStatusLoadKeyTX: with_view_model( - instance->view, (SubGhzReadRAWModel * model) { + instance->view, + SubGhzReadRAWModel * model, + { model->status = SubGhzReadRAWStatusLoadKeyIDLE; model->rssi_history_end = false; model->ind_write = 0; furi_string_set(model->file_name, file_name); furi_string_set(model->sample_write, "RAW"); - return true; - }); + }, + true); break; case SubGhzReadRAWStatusSaveKey: with_view_model( - instance->view, (SubGhzReadRAWModel * model) { + instance->view, + SubGhzReadRAWModel * model, + { model->status = SubGhzReadRAWStatusLoadKeyIDLE; if(!model->ind_write) { furi_string_set(model->file_name, file_name); @@ -456,8 +482,8 @@ void subghz_read_raw_set_status( } else { furi_string_reset(model->file_name); } - return true; - }); + }, + true); break; default: @@ -476,15 +502,17 @@ void subghz_read_raw_exit(void* context) { SubGhzReadRAW* instance = context; with_view_model( - instance->view, (SubGhzReadRAWModel * model) { + instance->view, + SubGhzReadRAWModel * model, + { if(model->status != SubGhzReadRAWStatusIDLE && model->status != SubGhzReadRAWStatusStart && model->status != SubGhzReadRAWStatusLoadKeyIDLE) { instance->callback(SubGhzCustomEventViewReadRAWIDLE, instance->context); model->status = SubGhzReadRAWStatusStart; } - return true; - }); + }, + true); } SubGhzReadRAW* subghz_read_raw_alloc() { @@ -500,14 +528,16 @@ SubGhzReadRAW* subghz_read_raw_alloc() { view_set_exit_callback(instance->view, subghz_read_raw_exit); with_view_model( - instance->view, (SubGhzReadRAWModel * model) { + instance->view, + SubGhzReadRAWModel * model, + { model->frequency_str = furi_string_alloc(); model->preset_str = furi_string_alloc(); model->sample_write = furi_string_alloc(); model->file_name = furi_string_alloc(); model->rssi_history = malloc(SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE * sizeof(uint8_t)); - return true; - }); + }, + true); return instance; } @@ -516,14 +546,16 @@ void subghz_read_raw_free(SubGhzReadRAW* instance) { furi_assert(instance); with_view_model( - instance->view, (SubGhzReadRAWModel * model) { + instance->view, + SubGhzReadRAWModel * model, + { furi_string_free(model->frequency_str); furi_string_free(model->preset_str); furi_string_free(model->sample_write); furi_string_free(model->file_name); free(model->rssi_history); - return true; - }); + }, + true); view_free(instance->view); free(instance); } diff --git a/applications/main/subghz/views/subghz_test_carrier.c b/applications/main/subghz/views/subghz_test_carrier.c index 6729eaad8ea..e533a6aac4e 100644 --- a/applications/main/subghz/views/subghz_test_carrier.c +++ b/applications/main/subghz/views/subghz_test_carrier.c @@ -89,7 +89,9 @@ bool subghz_test_carrier_input(InputEvent* event, void* context) { } with_view_model( - subghz_test_carrier->view, (SubGhzTestCarrierModel * model) { + subghz_test_carrier->view, + SubGhzTestCarrierModel * model, + { furi_hal_subghz_idle(); if(event->key == InputKeyLeft) { @@ -125,9 +127,8 @@ bool subghz_test_carrier_input(InputEvent* event, void* context) { SubGhzTestCarrierEventOnlyRx, subghz_test_carrier->context); } } - - return true; - }); + }, + true); return true; } @@ -142,15 +143,17 @@ void subghz_test_carrier_enter(void* context) { furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow); with_view_model( - subghz_test_carrier->view, (SubGhzTestCarrierModel * model) { + subghz_test_carrier->view, + SubGhzTestCarrierModel * model, + { model->frequency = subghz_frequencies_433_92_testing; // 433 model->real_frequency = furi_hal_subghz_set_frequency(subghz_frequencies_testing[model->frequency]); model->path = FuriHalSubGhzPathIsolate; // isolate model->rssi = 0.0f; model->status = SubGhzTestCarrierModelStatusRx; - return true; - }); + }, + true); furi_hal_subghz_rx(); @@ -172,13 +175,14 @@ void subghz_test_carrier_rssi_timer_callback(void* context) { SubGhzTestCarrier* subghz_test_carrier = context; with_view_model( - subghz_test_carrier->view, (SubGhzTestCarrierModel * model) { + subghz_test_carrier->view, + SubGhzTestCarrierModel * model, + { if(model->status == SubGhzTestCarrierModelStatusRx) { model->rssi = furi_hal_subghz_get_rssi(); - return true; } - return false; - }); + }, + false); } SubGhzTestCarrier* subghz_test_carrier_alloc() { diff --git a/applications/main/subghz/views/subghz_test_packet.c b/applications/main/subghz/views/subghz_test_packet.c index c83aebec9c3..a42898f7796 100644 --- a/applications/main/subghz/views/subghz_test_packet.c +++ b/applications/main/subghz/views/subghz_test_packet.c @@ -68,7 +68,9 @@ static void subghz_test_packet_rssi_timer_callback(void* context) { SubGhzTestPacket* instance = context; with_view_model( - instance->view, (SubGhzTestPacketModel * model) { + instance->view, + SubGhzTestPacketModel * model, + { if(model->status == SubGhzTestPacketModelStatusRx) { model->rssi = furi_hal_subghz_get_rssi(); model->packets = instance->packet_rx; @@ -77,8 +79,8 @@ static void subghz_test_packet_rssi_timer_callback(void* context) { SUBGHZ_TEST_PACKET_COUNT - subghz_encoder_princeton_for_testing_get_repeat_left(instance->encoder); } - return true; - }); + }, + true); } static void subghz_test_packet_draw(Canvas* canvas, SubGhzTestPacketModel* model) { @@ -137,7 +139,9 @@ static bool subghz_test_packet_input(InputEvent* event, void* context) { } with_view_model( - instance->view, (SubGhzTestPacketModel * model) { + instance->view, + SubGhzTestPacketModel * model, + { if(model->status == SubGhzTestPacketModelStatusRx) { furi_hal_subghz_stop_async_rx(); } else if(model->status == SubGhzTestPacketModelStatusTx) { @@ -179,9 +183,8 @@ static bool subghz_test_packet_input(InputEvent* event, void* context) { instance->callback(SubGhzTestPacketEventOnlyRx, instance->context); } } - - return true; - }); + }, + true); return true; } @@ -194,15 +197,17 @@ void subghz_test_packet_enter(void* context) { furi_hal_subghz_load_preset(FuriHalSubGhzPresetOok650Async); with_view_model( - instance->view, (SubGhzTestPacketModel * model) { + instance->view, + SubGhzTestPacketModel * model, + { model->frequency = subghz_frequencies_433_92_testing; model->real_frequency = furi_hal_subghz_set_frequency(subghz_frequencies_testing[model->frequency]); model->path = FuriHalSubGhzPathIsolate; // isolate model->rssi = 0.0f; model->status = SubGhzTestPacketModelStatusRx; - return true; - }); + }, + true); furi_hal_subghz_start_async_rx(subghz_test_packet_rx_callback, instance); @@ -217,15 +222,17 @@ void subghz_test_packet_exit(void* context) { // Reinitialize IC to default state with_view_model( - instance->view, (SubGhzTestPacketModel * model) { + instance->view, + SubGhzTestPacketModel * model, + { if(model->status == SubGhzTestPacketModelStatusRx) { furi_hal_subghz_stop_async_rx(); } else if(model->status == SubGhzTestPacketModelStatusTx) { subghz_encoder_princeton_for_testing_stop(instance->encoder, furi_get_tick()); furi_hal_subghz_stop_async_tx(); } - return true; - }); + }, + true); furi_hal_subghz_sleep(); } diff --git a/applications/main/subghz/views/subghz_test_static.c b/applications/main/subghz/views/subghz_test_static.c index 7af54c3c05a..6abefda763e 100644 --- a/applications/main/subghz/views/subghz_test_static.c +++ b/applications/main/subghz/views/subghz_test_static.c @@ -77,7 +77,9 @@ bool subghz_test_static_input(InputEvent* event, void* context) { } with_view_model( - instance->view, (SubGhzTestStaticModel * model) { + instance->view, + SubGhzTestStaticModel * model, + { if(event->type == InputTypeShort) { if(event->key == InputKeyLeft) { if(model->frequency > 0) model->frequency--; @@ -128,9 +130,8 @@ bool subghz_test_static_input(InputEvent* event, void* context) { } furi_record_close(RECORD_NOTIFICATION); } - - return true; - }); + }, + true); return true; } @@ -147,13 +148,14 @@ void subghz_test_static_enter(void* context) { instance->status_tx = SubGhzTestStaticStatusIDLE; with_view_model( - instance->view, (SubGhzTestStaticModel * model) { + instance->view, + SubGhzTestStaticModel * model, + { model->frequency = subghz_frequencies_433_92_testing; model->real_frequency = subghz_frequencies_testing[model->frequency]; model->button = 0; - - return true; - }); + }, + true); } void subghz_test_static_exit(void* context) { diff --git a/applications/main/subghz/views/transmitter.c b/applications/main/subghz/views/transmitter.c index 1094c5c58b3..833805ccb43 100644 --- a/applications/main/subghz/views/transmitter.c +++ b/applications/main/subghz/views/transmitter.c @@ -35,13 +35,15 @@ void subghz_view_transmitter_add_data_to_show( uint8_t show_button) { furi_assert(subghz_transmitter); with_view_model( - subghz_transmitter->view, (SubGhzViewTransmitterModel * model) { + subghz_transmitter->view, + SubGhzViewTransmitterModel * model, + { furi_string_set(model->key_str, key_str); furi_string_set(model->frequency_str, frequency_str); furi_string_set(model->preset_str, preset_str); model->show_button = show_button; - return true; - }); + }, + true); } static void subghz_view_transmitter_button_right(Canvas* canvas, const char* str) { @@ -95,23 +97,27 @@ bool subghz_view_transmitter_input(InputEvent* event, void* context) { if(event->key == InputKeyBack && event->type == InputTypeShort) { with_view_model( - subghz_transmitter->view, (SubGhzViewTransmitterModel * model) { + subghz_transmitter->view, + SubGhzViewTransmitterModel * model, + { furi_string_reset(model->frequency_str); furi_string_reset(model->preset_str); furi_string_reset(model->key_str); model->show_button = 0; - return false; - }); + }, + false); return false; } with_view_model( - subghz_transmitter->view, (SubGhzViewTransmitterModel * model) { + subghz_transmitter->view, + SubGhzViewTransmitterModel * model, + { if(model->show_button) { can_be_sent = true; } - return true; - }); + }, + true); if(can_be_sent && event->key == InputKeyOk && event->type == InputTypePress) { subghz_transmitter->callback( @@ -149,12 +155,14 @@ SubGhzViewTransmitter* subghz_view_transmitter_alloc() { view_set_exit_callback(subghz_transmitter->view, subghz_view_transmitter_exit); with_view_model( - subghz_transmitter->view, (SubGhzViewTransmitterModel * model) { + subghz_transmitter->view, + SubGhzViewTransmitterModel * model, + { model->frequency_str = furi_string_alloc(); model->preset_str = furi_string_alloc(); model->key_str = furi_string_alloc(); - return true; - }); + }, + true); return subghz_transmitter; } @@ -162,12 +170,14 @@ void subghz_view_transmitter_free(SubGhzViewTransmitter* subghz_transmitter) { furi_assert(subghz_transmitter); with_view_model( - subghz_transmitter->view, (SubGhzViewTransmitterModel * model) { + subghz_transmitter->view, + SubGhzViewTransmitterModel * model, + { furi_string_free(model->frequency_str); furi_string_free(model->preset_str); furi_string_free(model->key_str); - return true; - }); + }, + true); view_free(subghz_transmitter->view); free(subghz_transmitter); } diff --git a/applications/main/u2f/views/u2f_view.c b/applications/main/u2f/views/u2f_view.c index 11e2c9b0462..fa3d6cc24a6 100644 --- a/applications/main/u2f/views/u2f_view.c +++ b/applications/main/u2f/views/u2f_view.c @@ -85,18 +85,17 @@ void u2f_view_set_ok_callback(U2fView* u2f, U2fOkCallback callback, void* contex furi_assert(u2f); furi_assert(callback); with_view_model( - u2f->view, (U2fModel * model) { + u2f->view, + U2fModel * model, + { UNUSED(model); u2f->callback = callback; u2f->context = context; - return false; - }); + }, + false); } void u2f_view_set_state(U2fView* u2f, U2fViewMsg msg) { with_view_model( - u2f->view, (U2fModel * model) { - model->display_msg = msg; - return true; - }); + u2f->view, U2fModel * model, { model->display_msg = msg; }, true); } diff --git a/applications/plugins/bt_hid_app/views/bt_hid_keyboard.c b/applications/plugins/bt_hid_app/views/bt_hid_keyboard.c index 8b9ae593a15..2c65f6ab13c 100644 --- a/applications/plugins/bt_hid_app/views/bt_hid_keyboard.c +++ b/applications/plugins/bt_hid_app/views/bt_hid_keyboard.c @@ -277,7 +277,9 @@ static void bt_hid_keyboard_get_select_key(BtHidKeyboardModel* model, BtHidKeybo static void bt_hid_keyboard_process(BtHidKeyboard* bt_hid_keyboard, InputEvent* event) { with_view_model( - bt_hid_keyboard->view, (BtHidKeyboardModel * model) { + bt_hid_keyboard->view, + BtHidKeyboardModel * model, + { if(event->key == InputKeyOk) { if(event->type == InputTypePress) { model->ok_pressed = true; @@ -338,8 +340,8 @@ static void bt_hid_keyboard_process(BtHidKeyboard* bt_hid_keyboard, InputEvent* bt_hid_keyboard_get_select_key(model, (BtHidKeyboardPoint){.x = 1, .y = 0}); } } - return true; - }); + }, + true); } static bool bt_hid_keyboard_input_callback(InputEvent* event, void* context) { @@ -382,8 +384,5 @@ View* bt_hid_keyboard_get_view(BtHidKeyboard* bt_hid_keyboard) { void bt_hid_keyboard_set_connected_status(BtHidKeyboard* bt_hid_keyboard, bool connected) { furi_assert(bt_hid_keyboard); with_view_model( - bt_hid_keyboard->view, (BtHidKeyboardModel * model) { - model->connected = connected; - return true; - }); + bt_hid_keyboard->view, BtHidKeyboardModel * model, { model->connected = connected; }, true); } diff --git a/applications/plugins/bt_hid_app/views/bt_hid_keynote.c b/applications/plugins/bt_hid_app/views/bt_hid_keynote.c index ea4ee16fa4b..db88b80002f 100644 --- a/applications/plugins/bt_hid_app/views/bt_hid_keynote.c +++ b/applications/plugins/bt_hid_app/views/bt_hid_keynote.c @@ -106,7 +106,9 @@ static void bt_hid_keynote_draw_callback(Canvas* canvas, void* context) { static void bt_hid_keynote_process(BtHidKeynote* bt_hid_keynote, InputEvent* event) { with_view_model( - bt_hid_keynote->view, (BtHidKeynoteModel * model) { + bt_hid_keynote->view, + BtHidKeynoteModel * model, + { if(event->type == InputTypePress) { if(event->key == InputKeyUp) { model->up_pressed = true; @@ -153,8 +155,8 @@ static void bt_hid_keynote_process(BtHidKeynote* bt_hid_keynote, InputEvent* eve furi_hal_bt_hid_consumer_key_release(HID_CONSUMER_AC_BACK); } } - return true; - }); + }, + true); } static bool bt_hid_keynote_input_callback(InputEvent* event, void* context) { @@ -197,8 +199,5 @@ View* bt_hid_keynote_get_view(BtHidKeynote* bt_hid_keynote) { void bt_hid_keynote_set_connected_status(BtHidKeynote* bt_hid_keynote, bool connected) { furi_assert(bt_hid_keynote); with_view_model( - bt_hid_keynote->view, (BtHidKeynoteModel * model) { - model->connected = connected; - return true; - }); + bt_hid_keynote->view, BtHidKeynoteModel * model, { model->connected = connected; }, true); } diff --git a/applications/plugins/bt_hid_app/views/bt_hid_media.c b/applications/plugins/bt_hid_app/views/bt_hid_media.c index 258ea0a404b..181cd347b75 100644 --- a/applications/plugins/bt_hid_app/views/bt_hid_media.c +++ b/applications/plugins/bt_hid_app/views/bt_hid_media.c @@ -107,7 +107,9 @@ static void bt_hid_media_draw_callback(Canvas* canvas, void* context) { static void bt_hid_media_process_press(BtHidMedia* bt_hid_media, InputEvent* event) { with_view_model( - bt_hid_media->view, (BtHidMediaModel * model) { + bt_hid_media->view, + BtHidMediaModel * model, + { if(event->key == InputKeyUp) { model->up_pressed = true; furi_hal_bt_hid_consumer_key_press(HID_CONSUMER_VOLUME_INCREMENT); @@ -124,13 +126,15 @@ static void bt_hid_media_process_press(BtHidMedia* bt_hid_media, InputEvent* eve model->ok_pressed = true; furi_hal_bt_hid_consumer_key_press(HID_CONSUMER_PLAY_PAUSE); } - return true; - }); + }, + true); } static void bt_hid_media_process_release(BtHidMedia* bt_hid_media, InputEvent* event) { with_view_model( - bt_hid_media->view, (BtHidMediaModel * model) { + bt_hid_media->view, + BtHidMediaModel * model, + { if(event->key == InputKeyUp) { model->up_pressed = false; furi_hal_bt_hid_consumer_key_release(HID_CONSUMER_VOLUME_INCREMENT); @@ -147,8 +151,8 @@ static void bt_hid_media_process_release(BtHidMedia* bt_hid_media, InputEvent* e model->ok_pressed = false; furi_hal_bt_hid_consumer_key_release(HID_CONSUMER_PLAY_PAUSE); } - return true; - }); + }, + true); } static bool bt_hid_media_input_callback(InputEvent* event, void* context) { @@ -196,8 +200,5 @@ View* bt_hid_media_get_view(BtHidMedia* bt_hid_media) { void bt_hid_media_set_connected_status(BtHidMedia* bt_hid_media, bool connected) { furi_assert(bt_hid_media); with_view_model( - bt_hid_media->view, (BtHidMediaModel * model) { - model->connected = connected; - return true; - }); + bt_hid_media->view, BtHidMediaModel * model, { model->connected = connected; }, true); } diff --git a/applications/plugins/bt_hid_app/views/bt_hid_mouse.c b/applications/plugins/bt_hid_app/views/bt_hid_mouse.c index 395cb52c93a..098adb73271 100644 --- a/applications/plugins/bt_hid_app/views/bt_hid_mouse.c +++ b/applications/plugins/bt_hid_app/views/bt_hid_mouse.c @@ -103,7 +103,9 @@ static void bt_hid_mouse_draw_callback(Canvas* canvas, void* context) { static void bt_hid_mouse_process(BtHidMouse* bt_hid_mouse, InputEvent* event) { with_view_model( - bt_hid_mouse->view, (BtHidMouseModel * model) { + bt_hid_mouse->view, + BtHidMouseModel * model, + { if(event->key == InputKeyBack) { if(event->type == InputTypeShort) { furi_hal_bt_hid_mouse_press(HID_MOUSE_BTN_RIGHT); @@ -167,8 +169,8 @@ static void bt_hid_mouse_process(BtHidMouse* bt_hid_mouse, InputEvent* event) { model->up_pressed = false; } } - return true; - }); + }, + true); } static bool bt_hid_mouse_input_callback(InputEvent* event, void* context) { @@ -211,8 +213,5 @@ View* bt_hid_mouse_get_view(BtHidMouse* bt_hid_mouse) { void bt_hid_mouse_set_connected_status(BtHidMouse* bt_hid_mouse, bool connected) { furi_assert(bt_hid_mouse); with_view_model( - bt_hid_mouse->view, (BtHidMouseModel * model) { - model->connected = connected; - return true; - }); + bt_hid_mouse->view, BtHidMouseModel * model, { model->connected = connected; }, true); } diff --git a/applications/plugins/signal_generator/views/signal_gen_pwm.c b/applications/plugins/signal_generator/views/signal_gen_pwm.c index 4afed01a628..6d1a3c1bad9 100644 --- a/applications/plugins/signal_generator/views/signal_gen_pwm.c +++ b/applications/plugins/signal_generator/views/signal_gen_pwm.c @@ -44,12 +44,14 @@ static void pwm_set_config(SignalGenPwm* pwm) { uint8_t duty; with_view_model( - pwm->view, (SignalGenPwmViewModel * model) { + pwm->view, + SignalGenPwmViewModel * model, + { channel = model->channel_id; freq = model->freq; duty = model->duty; - return false; - }); + }, + false); furi_assert(pwm->callback); pwm->callback(channel, freq, duty, pwm->context); @@ -188,7 +190,9 @@ static bool signal_gen_pwm_input_callback(InputEvent* event, void* context) { bool need_update = false; with_view_model( - pwm->view, (SignalGenPwmViewModel * model) { + pwm->view, + SignalGenPwmViewModel * model, + { if(model->edit_mode == false) { if((event->type == InputTypeShort) || (event->type == InputTypeRepeat)) { if(event->key == InputKeyUp) { @@ -238,8 +242,8 @@ static bool signal_gen_pwm_input_callback(InputEvent* event, void* context) { } } } - return true; - }); + }, + true); if(need_update) { pwm_set_config(pwm); @@ -279,22 +283,26 @@ void signal_gen_pwm_set_callback( furi_assert(callback); with_view_model( - pwm->view, (SignalGenPwmViewModel * model) { + pwm->view, + SignalGenPwmViewModel * model, + { UNUSED(model); pwm->callback = callback; pwm->context = context; - return false; - }); + }, + false); } void signal_gen_pwm_set_params(SignalGenPwm* pwm, uint8_t channel_id, uint32_t freq, uint8_t duty) { with_view_model( - pwm->view, (SignalGenPwmViewModel * model) { + pwm->view, + SignalGenPwmViewModel * model, + { model->channel_id = channel_id; model->freq = freq; model->duty = duty; - return true; - }); + }, + true); furi_assert(pwm->callback); pwm->callback(channel_id, freq, duty, pwm->context); diff --git a/applications/services/desktop/views/desktop_view_debug.c b/applications/services/desktop/views/desktop_view_debug.c index c965432f17c..f9c8aedc2ce 100644 --- a/applications/services/desktop/views/desktop_view_debug.c +++ b/applications/services/desktop/views/desktop_view_debug.c @@ -124,16 +124,17 @@ bool desktop_debug_input(InputEvent* event, void* context) { DesktopViewStatsScreens current = 0; with_view_model( - debug_view->view, (DesktopDebugViewModel * model) { - + debug_view->view, + DesktopDebugViewModel * model, + { #ifdef SRV_DOLPHIN_STATE_DEBUG if((event->key == InputKeyDown) || (event->key == InputKeyUp)) { model->screen = !model->screen; } #endif current = model->screen; - return true; - }); + }, + true); size_t count = (event->type == InputTypeRepeat) ? 10 : 1; if(current == DesktopViewStatsMeta) { @@ -181,20 +182,19 @@ void desktop_debug_get_dolphin_data(DesktopDebugView* debug_view) { Dolphin* dolphin = furi_record_open(RECORD_DOLPHIN); DolphinStats stats = dolphin_stats(dolphin); with_view_model( - debug_view->view, (DesktopDebugViewModel * model) { + debug_view->view, + DesktopDebugViewModel * model, + { model->icounter = stats.icounter; model->butthurt = stats.butthurt; model->timestamp = stats.timestamp; - return true; - }); + }, + true); furi_record_close(RECORD_DOLPHIN); } void desktop_debug_reset_screen_idx(DesktopDebugView* debug_view) { with_view_model( - debug_view->view, (DesktopDebugViewModel * model) { - model->screen = 0; - return true; - }); + debug_view->view, DesktopDebugViewModel * model, { model->screen = 0; }, true); } diff --git a/applications/services/desktop/views/desktop_view_lock_menu.c b/applications/services/desktop/views/desktop_view_lock_menu.c index 294191bf292..8cb8a7a12f9 100644 --- a/applications/services/desktop/views/desktop_view_lock_menu.c +++ b/applications/services/desktop/views/desktop_view_lock_menu.c @@ -24,27 +24,24 @@ void desktop_lock_menu_set_callback( void desktop_lock_menu_set_pin_state(DesktopLockMenuView* lock_menu, bool pin_is_set) { with_view_model( - lock_menu->view, (DesktopLockMenuViewModel * model) { - model->pin_is_set = pin_is_set; - return true; - }); + lock_menu->view, + DesktopLockMenuViewModel * model, + { model->pin_is_set = pin_is_set; }, + true); } void desktop_lock_menu_set_dummy_mode_state(DesktopLockMenuView* lock_menu, bool dummy_mode) { with_view_model( - lock_menu->view, (DesktopLockMenuViewModel * model) { - model->dummy_mode = dummy_mode; - return true; - }); + lock_menu->view, + DesktopLockMenuViewModel * model, + { model->dummy_mode = dummy_mode; }, + true); } void desktop_lock_menu_set_idx(DesktopLockMenuView* lock_menu, uint8_t idx) { furi_assert(idx < DesktopLockMenuIndexTotalCount); with_view_model( - lock_menu->view, (DesktopLockMenuViewModel * model) { - model->idx = idx; - return true; - }); + lock_menu->view, DesktopLockMenuViewModel * model, { model->idx = idx; }, true); } void desktop_lock_menu_draw_callback(Canvas* canvas, void* model) { @@ -95,10 +92,12 @@ bool desktop_lock_menu_input_callback(InputEvent* event, void* context) { uint8_t idx = 0; bool consumed = false; bool dummy_mode = false; + bool update = false; with_view_model( - lock_menu->view, (DesktopLockMenuViewModel * model) { - bool ret = false; + lock_menu->view, + DesktopLockMenuViewModel * model, + { if((event->type == InputTypeShort) || (event->type == InputTypeRepeat)) { if(event->key == InputKeyUp) { if(model->idx == 0) { @@ -106,7 +105,7 @@ bool desktop_lock_menu_input_callback(InputEvent* event, void* context) { } else { model->idx = CLAMP(model->idx - 1, DesktopLockMenuIndexTotalCount - 1, 0); } - ret = true; + update = true; consumed = true; } else if(event->key == InputKeyDown) { if(model->idx == DesktopLockMenuIndexTotalCount - 1) { @@ -114,14 +113,14 @@ bool desktop_lock_menu_input_callback(InputEvent* event, void* context) { } else { model->idx = CLAMP(model->idx + 1, DesktopLockMenuIndexTotalCount - 1, 0); } - ret = true; + update = true; consumed = true; } } idx = model->idx; dummy_mode = model->dummy_mode; - return ret; - }); + }, + update); if(event->key == InputKeyOk) { if((idx == DesktopLockMenuIndexLock) && (event->type == InputTypeShort)) { diff --git a/applications/services/gui/modules/button_menu.c b/applications/services/gui/modules/button_menu.c index 1dce014d80e..37a04326ae0 100644 --- a/applications/services/gui/modules/button_menu.c +++ b/applications/services/gui/modules/button_menu.c @@ -148,28 +148,32 @@ static void button_menu_process_up(ButtonMenu* button_menu) { furi_assert(button_menu); with_view_model( - button_menu->view, (ButtonMenuModel * model) { + button_menu->view, + ButtonMenuModel * model, + { if(model->position > 0) { model->position--; } else { model->position = ButtonMenuItemArray_size(model->items) - 1; } - return true; - }); + }, + true); } static void button_menu_process_down(ButtonMenu* button_menu) { furi_assert(button_menu); with_view_model( - button_menu->view, (ButtonMenuModel * model) { + button_menu->view, + ButtonMenuModel * model, + { if(model->position < (ButtonMenuItemArray_size(model->items) - 1)) { model->position++; } else { model->position = 0; } - return true; - }); + }, + true); } static void button_menu_process_ok(ButtonMenu* button_menu, InputType type) { @@ -178,12 +182,14 @@ static void button_menu_process_ok(ButtonMenu* button_menu, InputType type) { ButtonMenuItem* item = NULL; with_view_model( - button_menu->view, (ButtonMenuModel * model) { + button_menu->view, + ButtonMenuModel * model, + { if(model->position < (ButtonMenuItemArray_size(model->items))) { item = ButtonMenuItemArray_get(model->items, model->position); } - return false; - }); + }, + false); if(item) { if(item->type == ButtonMenuItemTypeControl) { @@ -248,22 +254,21 @@ void button_menu_reset(ButtonMenu* button_menu) { furi_assert(button_menu); with_view_model( - button_menu->view, (ButtonMenuModel * model) { + button_menu->view, + ButtonMenuModel * model, + { ButtonMenuItemArray_reset(model->items); model->position = 0; model->header = NULL; - return true; - }); + }, + true); } void button_menu_set_header(ButtonMenu* button_menu, const char* header) { furi_assert(button_menu); with_view_model( - button_menu->view, (ButtonMenuModel * model) { - model->header = header; - return true; - }); + button_menu->view, ButtonMenuModel * model, { model->header = header; }, true); } ButtonMenuItem* button_menu_add_item( @@ -278,15 +283,17 @@ ButtonMenuItem* button_menu_add_item( furi_assert(button_menu); with_view_model( - button_menu->view, (ButtonMenuModel * model) { + button_menu->view, + ButtonMenuModel * model, + { item = ButtonMenuItemArray_push_new(model->items); item->label = label; item->index = index; item->type = type; item->callback = callback; item->callback_context = callback_context; - return true; - }); + }, + true); return item; } @@ -301,12 +308,14 @@ ButtonMenu* button_menu_alloc(void) { view_set_input_callback(button_menu->view, button_menu_view_input_callback); with_view_model( - button_menu->view, (ButtonMenuModel * model) { + button_menu->view, + ButtonMenuModel * model, + { ButtonMenuItemArray_init(model->items); model->position = 0; model->header = NULL; - return true; - }); + }, + true); button_menu->freeze_input = false; return button_menu; @@ -316,10 +325,10 @@ void button_menu_free(ButtonMenu* button_menu) { furi_assert(button_menu); with_view_model( - button_menu->view, (ButtonMenuModel * model) { - ButtonMenuItemArray_clear(model->items); - return true; - }); + button_menu->view, + ButtonMenuModel * model, + { ButtonMenuItemArray_clear(model->items); }, + true); view_free(button_menu->view); free(button_menu); } @@ -328,7 +337,9 @@ void button_menu_set_selected_item(ButtonMenu* button_menu, uint32_t index) { furi_assert(button_menu); with_view_model( - button_menu->view, (ButtonMenuModel * model) { + button_menu->view, + ButtonMenuModel * model, + { uint8_t item_position = 0; ButtonMenuItemArray_it_t it; for(ButtonMenuItemArray_it(it, model->items); !ButtonMenuItemArray_end_p(it); @@ -338,6 +349,6 @@ void button_menu_set_selected_item(ButtonMenu* button_menu, uint32_t index) { break; } } - return true; - }); + }, + true); } diff --git a/applications/services/gui/modules/button_panel.c b/applications/services/gui/modules/button_panel.c index c823e4b1887..47b6ed488d6 100644 --- a/applications/services/gui/modules/button_panel.c +++ b/applications/services/gui/modules/button_panel.c @@ -70,15 +70,17 @@ ButtonPanel* button_panel_alloc() { view_set_input_callback(button_panel->view, button_panel_view_input_callback); with_view_model( - button_panel->view, (ButtonPanelModel * model) { + button_panel->view, + ButtonPanelModel * model, + { model->reserve_x = 0; model->reserve_y = 0; model->selected_item_x = 0; model->selected_item_y = 0; ButtonMatrix_init(model->button_matrix); LabelList_init(model->labels); - return true; - }); + }, + true); return button_panel; } @@ -88,7 +90,9 @@ void button_panel_reserve(ButtonPanel* button_panel, size_t reserve_x, size_t re furi_check(reserve_y > 0); with_view_model( - button_panel->view, (ButtonPanelModel * model) { + button_panel->view, + ButtonPanelModel * model, + { model->reserve_x = reserve_x; model->reserve_y = reserve_y; ButtonMatrix_reserve(model->button_matrix, model->reserve_y); @@ -99,8 +103,8 @@ void button_panel_reserve(ButtonPanel* button_panel, size_t reserve_x, size_t re // TODO: do we need to clear allocated memory of ptr-s to ButtonItem ?? } LabelList_init(model->labels); - return true; - }); + }, + true); } void button_panel_free(ButtonPanel* button_panel) { @@ -109,11 +113,13 @@ void button_panel_free(ButtonPanel* button_panel) { button_panel_reset(button_panel); with_view_model( - button_panel->view, (ButtonPanelModel * model) { + button_panel->view, + ButtonPanelModel * model, + { LabelList_clear(model->labels); ButtonMatrix_clear(model->button_matrix); - return true; - }); + }, + true); view_free(button_panel->view); free(button_panel); @@ -123,7 +129,9 @@ void button_panel_reset(ButtonPanel* button_panel) { furi_assert(button_panel); with_view_model( - button_panel->view, (ButtonPanelModel * model) { + button_panel->view, + ButtonPanelModel * model, + { for(size_t x = 0; x < model->reserve_x; ++x) { for(size_t y = 0; y < model->reserve_y; ++y) { ButtonItem** button_item = button_panel_get_item(model, x, y); @@ -137,8 +145,8 @@ void button_panel_reset(ButtonPanel* button_panel) { model->selected_item_y = 0; LabelList_reset(model->labels); ButtonMatrix_reset(model->button_matrix); - return true; - }); + }, + true); } static ButtonItem** button_panel_get_item(ButtonPanelModel* model, size_t x, size_t y) { @@ -165,7 +173,9 @@ void button_panel_add_item( furi_assert(button_panel); with_view_model( - button_panel->view, (ButtonPanelModel * model) { + button_panel->view, + ButtonPanelModel * model, + { ButtonItem** button_item_ptr = button_panel_get_item(model, matrix_place_x, matrix_place_y); furi_check(*button_item_ptr == NULL); @@ -178,8 +188,8 @@ void button_panel_add_item( button_item->icon.name = icon_name; button_item->icon.name_selected = icon_name_selected; button_item->index = index; - return true; - }); + }, + true); } View* button_panel_get_view(ButtonPanel* button_panel) { @@ -216,114 +226,123 @@ static void button_panel_view_draw_callback(Canvas* canvas, void* _model) { static void button_panel_process_down(ButtonPanel* button_panel) { with_view_model( - button_panel->view, (ButtonPanelModel * model) { + button_panel->view, + ButtonPanelModel * model, + { uint16_t new_selected_item_x = model->selected_item_x; uint16_t new_selected_item_y = model->selected_item_y; size_t i; - if(new_selected_item_y >= (model->reserve_y - 1)) return false; + if(new_selected_item_y < (model->reserve_y - 1)) { + ++new_selected_item_y; - ++new_selected_item_y; - - for(i = 0; i < model->reserve_x; ++i) { - new_selected_item_x = (model->selected_item_x + i) % model->reserve_x; - if(*button_panel_get_item(model, new_selected_item_x, new_selected_item_y)) { - break; + for(i = 0; i < model->reserve_x; ++i) { + new_selected_item_x = (model->selected_item_x + i) % model->reserve_x; + if(*button_panel_get_item(model, new_selected_item_x, new_selected_item_y)) { + break; + } + } + if(i != model->reserve_x) { + model->selected_item_x = new_selected_item_x; + model->selected_item_y = new_selected_item_y; } } - if(i == model->reserve_x) return false; - - model->selected_item_x = new_selected_item_x; - model->selected_item_y = new_selected_item_y; - - return true; - }); + }, + true); } static void button_panel_process_up(ButtonPanel* button_panel) { with_view_model( - button_panel->view, (ButtonPanelModel * model) { + button_panel->view, + ButtonPanelModel * model, + { size_t new_selected_item_x = model->selected_item_x; size_t new_selected_item_y = model->selected_item_y; size_t i; - if(new_selected_item_y <= 0) return false; + if(new_selected_item_y > 0) { + --new_selected_item_y; - --new_selected_item_y; - - for(i = 0; i < model->reserve_x; ++i) { - new_selected_item_x = (model->selected_item_x + i) % model->reserve_x; - if(*button_panel_get_item(model, new_selected_item_x, new_selected_item_y)) { - break; + for(i = 0; i < model->reserve_x; ++i) { + new_selected_item_x = (model->selected_item_x + i) % model->reserve_x; + if(*button_panel_get_item(model, new_selected_item_x, new_selected_item_y)) { + break; + } + } + if(i != model->reserve_x) { + model->selected_item_x = new_selected_item_x; + model->selected_item_y = new_selected_item_y; } } - if(i == model->reserve_x) return false; - - model->selected_item_x = new_selected_item_x; - model->selected_item_y = new_selected_item_y; - return true; - }); + }, + true); } static void button_panel_process_left(ButtonPanel* button_panel) { with_view_model( - button_panel->view, (ButtonPanelModel * model) { + button_panel->view, + ButtonPanelModel * model, + { size_t new_selected_item_x = model->selected_item_x; size_t new_selected_item_y = model->selected_item_y; size_t i; - if(new_selected_item_x <= 0) return false; + if(new_selected_item_x > 0) { + --new_selected_item_x; - --new_selected_item_x; - - for(i = 0; i < model->reserve_y; ++i) { - new_selected_item_y = (model->selected_item_y + i) % model->reserve_y; - if(*button_panel_get_item(model, new_selected_item_x, new_selected_item_y)) { - break; + for(i = 0; i < model->reserve_y; ++i) { + new_selected_item_y = (model->selected_item_y + i) % model->reserve_y; + if(*button_panel_get_item(model, new_selected_item_x, new_selected_item_y)) { + break; + } + } + if(i != model->reserve_y) { + model->selected_item_x = new_selected_item_x; + model->selected_item_y = new_selected_item_y; } } - if(i == model->reserve_y) return false; - - model->selected_item_x = new_selected_item_x; - model->selected_item_y = new_selected_item_y; - return true; - }); + }, + true); } static void button_panel_process_right(ButtonPanel* button_panel) { with_view_model( - button_panel->view, (ButtonPanelModel * model) { + button_panel->view, + ButtonPanelModel * model, + { uint16_t new_selected_item_x = model->selected_item_x; uint16_t new_selected_item_y = model->selected_item_y; size_t i; - if(new_selected_item_x >= (model->reserve_x - 1)) return false; + if(new_selected_item_x < (model->reserve_x - 1)) { + ++new_selected_item_x; - ++new_selected_item_x; - - for(i = 0; i < model->reserve_y; ++i) { - new_selected_item_y = (model->selected_item_y + i) % model->reserve_y; - if(*button_panel_get_item(model, new_selected_item_x, new_selected_item_y)) { - break; + for(i = 0; i < model->reserve_y; ++i) { + new_selected_item_y = (model->selected_item_y + i) % model->reserve_y; + if(*button_panel_get_item(model, new_selected_item_x, new_selected_item_y)) { + break; + } + } + if(i != model->reserve_y) { + model->selected_item_x = new_selected_item_x; + model->selected_item_y = new_selected_item_y; } } - if(i == model->reserve_y) return false; - - model->selected_item_x = new_selected_item_x; - model->selected_item_y = new_selected_item_y; - return true; - }); + }, + true); } void button_panel_process_ok(ButtonPanel* button_panel) { ButtonItem* button_item = NULL; with_view_model( - button_panel->view, (ButtonPanelModel * model) { + button_panel->view, + ButtonPanelModel * model, + { button_item = *button_panel_get_item(model, model->selected_item_x, model->selected_item_y); - return true; - }); + }, + true); if(button_item && button_item->callback) { button_item->callback(button_item->callback_context, button_item->index); @@ -374,12 +393,14 @@ void button_panel_add_label( furi_assert(button_panel); with_view_model( - button_panel->view, (ButtonPanelModel * model) { + button_panel->view, + ButtonPanelModel * model, + { LabelElement* label = LabelList_push_raw(model->labels); label->x = x; label->y = y; label->font = font; label->str = label_str; - return true; - }); + }, + true); } diff --git a/applications/services/gui/modules/byte_input.c b/applications/services/gui/modules/byte_input.c index bb18a107ac6..8d7e7fd4f91 100644 --- a/applications/services/gui/modules/byte_input.c +++ b/applications/services/gui/modules/byte_input.c @@ -618,42 +618,30 @@ static bool byte_input_view_input_callback(InputEvent* event, void* context) { switch(event->key) { case InputKeyLeft: with_view_model( - byte_input->view, (ByteInputModel * model) { - byte_input_handle_left(model); - return true; - }); + byte_input->view, ByteInputModel * model, { byte_input_handle_left(model); }, true); consumed = true; break; case InputKeyRight: with_view_model( - byte_input->view, (ByteInputModel * model) { - byte_input_handle_right(model); - return true; - }); + byte_input->view, + ByteInputModel * model, + { byte_input_handle_right(model); }, + true); consumed = true; break; case InputKeyUp: with_view_model( - byte_input->view, (ByteInputModel * model) { - byte_input_handle_up(model); - return true; - }); + byte_input->view, ByteInputModel * model, { byte_input_handle_up(model); }, true); consumed = true; break; case InputKeyDown: with_view_model( - byte_input->view, (ByteInputModel * model) { - byte_input_handle_down(model); - return true; - }); + byte_input->view, ByteInputModel * model, { byte_input_handle_down(model); }, true); consumed = true; break; case InputKeyOk: with_view_model( - byte_input->view, (ByteInputModel * model) { - byte_input_handle_ok(model); - return true; - }); + byte_input->view, ByteInputModel * model, { byte_input_handle_ok(model); }, true); consumed = true; break; default: @@ -664,10 +652,10 @@ static bool byte_input_view_input_callback(InputEvent* event, void* context) { if((event->type == InputTypeLong || event->type == InputTypeRepeat) && event->key == InputKeyBack) { with_view_model( - byte_input->view, (ByteInputModel * model) { - byte_input_clear_selected_byte(model); - return true; - }); + byte_input->view, + ByteInputModel * model, + { byte_input_clear_selected_byte(model); }, + true); consumed = true; } @@ -703,14 +691,16 @@ ByteInput* byte_input_alloc() { view_set_input_callback(byte_input->view, byte_input_view_input_callback); with_view_model( - byte_input->view, (ByteInputModel * model) { + byte_input->view, + ByteInputModel * model, + { model->header = ""; model->input_callback = NULL; model->changed_callback = NULL; model->callback_context = NULL; byte_input_reset_model_input_data(model); - return true; - }); + }, + true); return byte_input; } @@ -755,15 +745,17 @@ void byte_input_set_result_callback( uint8_t* bytes, uint8_t bytes_count) { with_view_model( - byte_input->view, (ByteInputModel * model) { + byte_input->view, + ByteInputModel * model, + { byte_input_reset_model_input_data(model); model->input_callback = input_callback; model->changed_callback = changed_callback; model->callback_context = callback_context; model->bytes = bytes; model->bytes_count = bytes_count; - return true; - }); + }, + true); } /** @@ -774,8 +766,5 @@ void byte_input_set_result_callback( */ void byte_input_set_header_text(ByteInput* byte_input, const char* text) { with_view_model( - byte_input->view, (ByteInputModel * model) { - model->header = text; - return true; - }); + byte_input->view, ByteInputModel * model, { model->header = text; }, true); } diff --git a/applications/services/gui/modules/dialog_ex.c b/applications/services/gui/modules/dialog_ex.c index dee2a0971c7..1cb46723247 100644 --- a/applications/services/gui/modules/dialog_ex.c +++ b/applications/services/gui/modules/dialog_ex.c @@ -90,12 +90,14 @@ static bool dialog_ex_view_input_callback(InputEvent* event, void* context) { const char* right_text = NULL; with_view_model( - dialog_ex->view, (DialogExModel * model) { + dialog_ex->view, + DialogExModel * model, + { left_text = model->left_text; center_text = model->center_text; right_text = model->right_text; - return true; - }); + }, + true); if(dialog_ex->callback) { if(event->type == InputTypeShort) { @@ -149,7 +151,9 @@ DialogEx* dialog_ex_alloc() { view_set_draw_callback(dialog_ex->view, dialog_ex_view_draw_callback); view_set_input_callback(dialog_ex->view, dialog_ex_view_input_callback); with_view_model( - dialog_ex->view, (DialogExModel * model) { + dialog_ex->view, + DialogExModel * model, + { model->header.text = NULL; model->header.x = 0; model->header.y = 0; @@ -169,9 +173,8 @@ DialogEx* dialog_ex_alloc() { model->left_text = NULL; model->center_text = NULL; model->right_text = NULL; - - return true; - }); + }, + true); dialog_ex->enable_extended_events = false; return dialog_ex; } @@ -206,14 +209,16 @@ void dialog_ex_set_header( Align vertical) { furi_assert(dialog_ex); with_view_model( - dialog_ex->view, (DialogExModel * model) { + dialog_ex->view, + DialogExModel * model, + { model->header.text = text; model->header.x = x; model->header.y = y; model->header.horizontal = horizontal; model->header.vertical = vertical; - return true; - }); + }, + true); } void dialog_ex_set_text( @@ -225,52 +230,47 @@ void dialog_ex_set_text( Align vertical) { furi_assert(dialog_ex); with_view_model( - dialog_ex->view, (DialogExModel * model) { + dialog_ex->view, + DialogExModel * model, + { model->text.text = text; model->text.x = x; model->text.y = y; model->text.horizontal = horizontal; model->text.vertical = vertical; - return true; - }); + }, + true); } void dialog_ex_set_icon(DialogEx* dialog_ex, uint8_t x, uint8_t y, const Icon* icon) { furi_assert(dialog_ex); with_view_model( - dialog_ex->view, (DialogExModel * model) { + dialog_ex->view, + DialogExModel * model, + { model->icon.x = x; model->icon.y = y; model->icon.icon = icon; - return true; - }); + }, + true); } void dialog_ex_set_left_button_text(DialogEx* dialog_ex, const char* text) { furi_assert(dialog_ex); with_view_model( - dialog_ex->view, (DialogExModel * model) { - model->left_text = text; - return true; - }); + dialog_ex->view, DialogExModel * model, { model->left_text = text; }, true); } void dialog_ex_set_center_button_text(DialogEx* dialog_ex, const char* text) { furi_assert(dialog_ex); with_view_model( - dialog_ex->view, (DialogExModel * model) { - model->center_text = text; - return true; - }); + dialog_ex->view, DialogExModel * model, { model->center_text = text; }, true); } void dialog_ex_set_right_button_text(DialogEx* dialog_ex, const char* text) { furi_assert(dialog_ex); with_view_model( - dialog_ex->view, (DialogExModel * model) { - model->right_text = text; - return true; - }); + dialog_ex->view, DialogExModel * model, { model->right_text = text; }, true); } void dialog_ex_reset(DialogEx* dialog_ex) { @@ -279,15 +279,17 @@ void dialog_ex_reset(DialogEx* dialog_ex) { .text = NULL, .x = 0, .y = 0, .horizontal = AlignLeft, .vertical = AlignLeft}; IconElement clean_icon_el = {.icon = NULL, .x = 0, .y = 0}; with_view_model( - dialog_ex->view, (DialogExModel * model) { + dialog_ex->view, + DialogExModel * model, + { model->header = clean_text_el; model->text = clean_text_el; model->icon = clean_icon_el; model->left_text = NULL; model->center_text = NULL; model->right_text = NULL; - return true; - }); + }, + true); dialog_ex->context = NULL; dialog_ex->callback = NULL; } diff --git a/applications/services/gui/modules/file_browser.c b/applications/services/gui/modules/file_browser.c index 762fe87d5a8..60e78b01c30 100644 --- a/applications/services/gui/modules/file_browser.c +++ b/applications/services/gui/modules/file_browser.c @@ -139,10 +139,7 @@ FileBrowser* file_browser_alloc(FuriString* result_path) { browser->result_path = result_path; with_view_model( - browser->view, (FileBrowserModel * model) { - items_array_init(model->items); - return false; - }); + browser->view, FileBrowserModel * model, { items_array_init(model->items); }, false); return browser; } @@ -151,10 +148,7 @@ void file_browser_free(FileBrowser* browser) { furi_assert(browser); with_view_model( - browser->view, (FileBrowserModel * model) { - items_array_clear(model->items); - return false; - }); + browser->view, FileBrowserModel * model, { items_array_clear(model->items); }, false); view_free(browser->view); free(browser); @@ -178,11 +172,13 @@ void file_browser_configure( browser->hide_ext = hide_ext; with_view_model( - browser->view, (FileBrowserModel * model) { + browser->view, + FileBrowserModel * model, + { model->file_icon = file_icon; model->hide_ext = hide_ext; - return false; - }); + }, + false); } void file_browser_start(FileBrowser* browser, FuriString* path) { @@ -199,14 +195,16 @@ void file_browser_stop(FileBrowser* browser) { furi_assert(browser); file_browser_worker_free(browser->worker); with_view_model( - browser->view, (FileBrowserModel * 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; - }); + }, + false); } void file_browser_set_callback(FileBrowser* browser, FileBrowserCallback callback, void* context) { @@ -257,7 +255,9 @@ static void browser_update_offset(FileBrowser* browser) { furi_assert(browser); with_view_model( - browser->view, (FileBrowserModel * 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)) && @@ -272,9 +272,8 @@ static void browser_update_offset(FileBrowser* browser) { model->list_offset = CLAMP(model->item_idx - 1, (int32_t)model->item_cnt - bounds, 0); } - - return false; - }); + }, + false); } static void @@ -285,7 +284,9 @@ static void int32_t load_offset = 0; with_view_model( - browser->view, (FileBrowserModel * model) { + browser->view, + FileBrowserModel * model, + { items_array_reset(model->items); if(is_root) { model->item_cnt = item_cnt; @@ -303,8 +304,8 @@ static void model->is_root = is_root; model->list_loading = true; model->folder_loading = false; - return true; - }); + }, + true); browser_update_offset(browser); file_browser_worker_load(browser->worker, load_offset, ITEM_LIST_LEN_MAX); @@ -319,7 +320,9 @@ static void browser_list_load_cb(void* context, uint32_t list_load_offset) { back_item.type = BrowserItemTypeBack; with_view_model( - browser->view, (FileBrowserModel * model) { + browser->view, + FileBrowserModel * model, + { items_array_reset(model->items); model->array_offset = list_load_offset; if(!model->is_root) { @@ -329,8 +332,8 @@ static void browser_list_load_cb(void* context, uint32_t list_load_offset) { model->array_offset += 1; } } - return false; - }); + }, + true); BrowserItem_t_clear(&back_item); } @@ -371,11 +374,13 @@ static void } with_view_model( - browser->view, (FileBrowserModel * model) { + browser->view, + FileBrowserModel * model, + { items_array_push_back(model->items, item); // TODO: calculate if element is visible - return true; - }); + }, + true); furi_string_free(item.display_name); furi_string_free(item.path); if(item.custom_icon_data) { @@ -383,10 +388,7 @@ static void } } else { with_view_model( - browser->view, (FileBrowserModel * model) { - model->list_loading = false; - return true; - }); + browser->view, FileBrowserModel * model, { model->list_loading = false; }, true); } } @@ -395,10 +397,7 @@ static void browser_long_load_cb(void* context) { FileBrowser* browser = (FileBrowser*)context; with_view_model( - browser->view, (FileBrowserModel * model) { - model->folder_loading = true; - return true; - }); + browser->view, FileBrowserModel * model, { model->folder_loading = true; }, true); } static void browser_draw_frame(Canvas* canvas, uint16_t idx, bool scrollbar) { @@ -508,17 +507,16 @@ static bool file_browser_view_input_callback(InputEvent* event, void* context) { bool is_loading = false; with_view_model( - browser->view, (FileBrowserModel * model) { - is_loading = model->folder_loading; - return false; - }); + browser->view, FileBrowserModel * model, { is_loading = model->folder_loading; }, 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) { + browser->view, + FileBrowserModel * model, + { if(event->key == InputKeyUp) { model->item_idx = ((model->item_idx - 1) + model->item_cnt) % model->item_cnt; @@ -543,8 +541,8 @@ static bool file_browser_view_input_callback(InputEvent* event, void* context) { browser->worker, load_offset, ITEM_LIST_LEN_MAX); } } - return true; - }); + }, + true); browser_update_offset(browser); consumed = true; } @@ -553,7 +551,9 @@ static bool file_browser_view_input_callback(InputEvent* event, void* context) { BrowserItem_t* selected_item = NULL; int32_t select_index = 0; with_view_model( - browser->view, (FileBrowserModel * 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); @@ -562,8 +562,8 @@ static bool file_browser_view_input_callback(InputEvent* event, void* context) { select_index -= 1; } } - return false; - }); + }, + false); if(selected_item) { if(selected_item->type == BrowserItemTypeBack) { @@ -584,10 +584,7 @@ static bool file_browser_view_input_callback(InputEvent* event, void* context) { if(event->type == InputTypeShort) { bool is_root = false; with_view_model( - browser->view, (FileBrowserModel * model) { - is_root = model->is_root; - return false; - }); + browser->view, FileBrowserModel * model, { is_root = model->is_root; }, false); if(!is_root) { file_browser_worker_folder_exit(browser->worker); } diff --git a/applications/services/gui/modules/menu.c b/applications/services/gui/modules/menu.c index 67d46d5f3b1..db0717f7760 100644 --- a/applications/services/gui/modules/menu.c +++ b/applications/services/gui/modules/menu.c @@ -103,25 +103,29 @@ static bool menu_input_callback(InputEvent* event, void* context) { static void menu_enter(void* context) { Menu* menu = context; with_view_model( - menu->view, (MenuModel * model) { + menu->view, + MenuModel * model, + { MenuItem* item = MenuItemArray_get(model->items, model->position); if(item && item->icon) { icon_animation_start(item->icon); } - return false; - }); + }, + false); } static void menu_exit(void* context) { Menu* menu = context; with_view_model( - menu->view, (MenuModel * model) { + menu->view, + MenuModel * model, + { MenuItem* item = MenuItemArray_get(model->items, model->position); if(item && item->icon) { icon_animation_stop(item->icon); } - return false; - }); + }, + false); } Menu* menu_alloc() { @@ -135,11 +139,13 @@ Menu* menu_alloc() { view_set_exit_callback(menu->view, menu_exit); with_view_model( - menu->view, (MenuModel * model) { + menu->view, + MenuModel * model, + { MenuItemArray_init(model->items); model->position = 0; - return true; - }); + }, + true); return menu; } @@ -168,7 +174,9 @@ void menu_add_item( MenuItem* item = NULL; with_view_model( - menu->view, (MenuModel * model) { + menu->view, + MenuModel * model, + { item = MenuItemArray_push_new(model->items); item->label = label; item->icon = icon ? icon_animation_alloc(icon) : icon_animation_alloc(&A_Plugins_14); @@ -176,14 +184,16 @@ void menu_add_item( item->index = index; item->callback = callback; item->callback_context = context; - return true; - }); + }, + true); } void menu_reset(Menu* menu) { furi_assert(menu); with_view_model( - menu->view, (MenuModel * model) { + menu->view, + MenuModel * model, + { for M_EACH(item, model->items, MenuItemArray_t) { icon_animation_stop(item->icon); @@ -192,25 +202,27 @@ void menu_reset(Menu* menu) { MenuItemArray_reset(model->items); model->position = 0; - return true; - }); + }, + true); } void menu_set_selected_item(Menu* menu, uint32_t index) { with_view_model( - menu->view, (MenuModel * model) { - if(index >= MenuItemArray_size(model->items)) { - return false; + menu->view, + MenuModel * model, + { + if(index < MenuItemArray_size(model->items)) { + model->position = index; } - - model->position = index; - return true; - }); + }, + true); } static void menu_process_up(Menu* menu) { with_view_model( - menu->view, (MenuModel * model) { + menu->view, + MenuModel * model, + { MenuItem* item = MenuItemArray_get(model->items, model->position); if(item && item->icon) { icon_animation_stop(item->icon); @@ -226,13 +238,15 @@ static void menu_process_up(Menu* menu) { if(item && item->icon) { icon_animation_start(item->icon); } - return true; - }); + }, + true); } static void menu_process_down(Menu* menu) { with_view_model( - menu->view, (MenuModel * model) { + menu->view, + MenuModel * model, + { MenuItem* item = MenuItemArray_get(model->items, model->position); if(item && item->icon) { icon_animation_stop(item->icon); @@ -248,19 +262,21 @@ static void menu_process_down(Menu* menu) { if(item && item->icon) { icon_animation_start(item->icon); } - return true; - }); + }, + true); } static void menu_process_ok(Menu* menu) { MenuItem* item = NULL; with_view_model( - menu->view, (MenuModel * model) { + menu->view, + MenuModel * model, + { if(model->position < MenuItemArray_size(model->items)) { item = MenuItemArray_get(model->items, model->position); } - return true; - }); + }, + true); if(item && item->callback) { item->callback(item->callback_context, item->index); } diff --git a/applications/services/gui/modules/popup.c b/applications/services/gui/modules/popup.c index b3cb5e53692..08e8d8c2b70 100644 --- a/applications/services/gui/modules/popup.c +++ b/applications/services/gui/modules/popup.c @@ -124,7 +124,9 @@ Popup* popup_alloc() { view_set_exit_callback(popup->view, popup_stop_timer); with_view_model( - popup->view, (PopupModel * model) { + popup->view, + PopupModel * model, + { model->header.text = NULL; model->header.x = 0; model->header.y = 0; @@ -140,8 +142,8 @@ Popup* popup_alloc() { model->icon.x = 0; model->icon.y = 0; model->icon.icon = NULL; - return true; - }); + }, + true); return popup; } @@ -176,14 +178,16 @@ void popup_set_header( Align vertical) { furi_assert(popup); with_view_model( - popup->view, (PopupModel * model) { + popup->view, + PopupModel * model, + { model->header.text = text; model->header.x = x; model->header.y = y; model->header.horizontal = horizontal; model->header.vertical = vertical; - return true; - }); + }, + true); } void popup_set_text( @@ -195,25 +199,29 @@ void popup_set_text( Align vertical) { furi_assert(popup); with_view_model( - popup->view, (PopupModel * model) { + popup->view, + PopupModel * model, + { model->text.text = text; model->text.x = x; model->text.y = y; model->text.horizontal = horizontal; model->text.vertical = vertical; - return true; - }); + }, + true); } void popup_set_icon(Popup* popup, uint8_t x, uint8_t y, const Icon* icon) { furi_assert(popup); with_view_model( - popup->view, (PopupModel * model) { + popup->view, + PopupModel * model, + { model->icon.x = x; model->icon.y = y; model->icon.icon = icon; - return true; - }); + }, + true); } void popup_set_timeout(Popup* popup, uint32_t timeout_in_ms) { @@ -233,12 +241,14 @@ void popup_reset(Popup* popup) { furi_assert(popup); with_view_model( - popup->view, (PopupModel * model) { + popup->view, + PopupModel * model, + { memset(&model->header, 0, sizeof(model->header)); memset(&model->text, 0, sizeof(model->text)); memset(&model->icon, 0, sizeof(model->icon)); - return false; - }); + }, + false); popup->callback = NULL; popup->context = NULL; popup->timer_enabled = false; diff --git a/applications/services/gui/modules/submenu.c b/applications/services/gui/modules/submenu.c index 8d40d97b770..949598772c2 100644 --- a/applications/services/gui/modules/submenu.c +++ b/applications/services/gui/modules/submenu.c @@ -128,13 +128,15 @@ Submenu* submenu_alloc() { view_set_input_callback(submenu->view, submenu_view_input_callback); with_view_model( - submenu->view, (SubmenuModel * model) { + submenu->view, + SubmenuModel * model, + { SubmenuItemArray_init(model->items); model->position = 0; model->window_position = 0; model->header = NULL; - return true; - }); + }, + true); return submenu; } @@ -143,10 +145,7 @@ void submenu_free(Submenu* submenu) { furi_assert(submenu); with_view_model( - submenu->view, (SubmenuModel * model) { - SubmenuItemArray_clear(model->items); - return true; - }); + submenu->view, SubmenuModel * model, { SubmenuItemArray_clear(model->items); }, true); view_free(submenu->view); free(submenu); } @@ -167,32 +166,38 @@ void submenu_add_item( furi_assert(submenu); with_view_model( - submenu->view, (SubmenuModel * model) { + submenu->view, + SubmenuModel * model, + { item = SubmenuItemArray_push_new(model->items); item->label = label; item->index = index; item->callback = callback; item->callback_context = callback_context; - return true; - }); + }, + true); } void submenu_reset(Submenu* submenu) { furi_assert(submenu); with_view_model( - submenu->view, (SubmenuModel * model) { + submenu->view, + SubmenuModel * model, + { SubmenuItemArray_reset(model->items); model->position = 0; model->window_position = 0; model->header = NULL; - return true; - }); + }, + true); } void submenu_set_selected_item(Submenu* submenu, uint32_t index) { with_view_model( - submenu->view, (SubmenuModel * model) { + submenu->view, + SubmenuModel * model, + { uint32_t position = 0; SubmenuItemArray_it_t it; for(SubmenuItemArray_it(it, model->items); !SubmenuItemArray_end_p(it); @@ -225,14 +230,15 @@ void submenu_set_selected_item(Submenu* submenu, uint32_t index) { (SubmenuItemArray_size(model->items) - items_on_screen); } } - - return true; - }); + }, + true); } void submenu_process_up(Submenu* submenu) { with_view_model( - submenu->view, (SubmenuModel * model) { + submenu->view, + SubmenuModel * model, + { uint8_t items_on_screen = model->header ? 3 : 4; if(model->position > 0) { model->position--; @@ -246,13 +252,15 @@ void submenu_process_up(Submenu* submenu) { model->window_position = model->position - (items_on_screen - 1); } } - return true; - }); + }, + true); } void submenu_process_down(Submenu* submenu) { with_view_model( - submenu->view, (SubmenuModel * model) { + submenu->view, + SubmenuModel * model, + { uint8_t items_on_screen = model->header ? 3 : 4; if(model->position < (SubmenuItemArray_size(model->items) - 1)) { model->position++; @@ -265,20 +273,22 @@ void submenu_process_down(Submenu* submenu) { model->position = 0; model->window_position = 0; } - return true; - }); + }, + true); } void submenu_process_ok(Submenu* submenu) { SubmenuItem* item = NULL; with_view_model( - submenu->view, (SubmenuModel * model) { + submenu->view, + SubmenuModel * model, + { if(model->position < (SubmenuItemArray_size(model->items))) { item = SubmenuItemArray_get(model->items, model->position); } - return true; - }); + }, + true); if(item && item->callback) { item->callback(item->callback_context, item->index); @@ -289,8 +299,5 @@ void submenu_set_header(Submenu* submenu, const char* header) { furi_assert(submenu); with_view_model( - submenu->view, (SubmenuModel * model) { - model->header = header; - return true; - }); + submenu->view, SubmenuModel * model, { model->header = header; }, true); } diff --git a/applications/services/gui/modules/text_box.c b/applications/services/gui/modules/text_box.c index 65ee28301c1..99d7d04f1b9 100644 --- a/applications/services/gui/modules/text_box.c +++ b/applications/services/gui/modules/text_box.c @@ -21,20 +21,24 @@ typedef struct { static void text_box_process_down(TextBox* text_box) { with_view_model( - text_box->view, (TextBoxModel * model) { + text_box->view, + TextBoxModel * model, + { if(model->scroll_pos < model->scroll_num - 1) { model->scroll_pos++; // Search next line start while(*model->text_pos++ != '\n') ; } - return true; - }); + }, + true); } static void text_box_process_up(TextBox* text_box) { with_view_model( - text_box->view, (TextBoxModel * model) { + text_box->view, + TextBoxModel * model, + { if(model->scroll_pos > 0) { model->scroll_pos--; // Reach last symbol of previous line @@ -46,8 +50,8 @@ static void text_box_process_up(TextBox* text_box) { model->text_pos++; } } - return true; - }); + }, + true); } static void text_box_insert_endline(Canvas* canvas, TextBoxModel* model) { @@ -137,13 +141,15 @@ TextBox* text_box_alloc() { view_set_input_callback(text_box->view, text_box_view_input_callback); with_view_model( - text_box->view, (TextBoxModel * model) { + text_box->view, + TextBoxModel * model, + { model->text = NULL; model->text_formatted = furi_string_alloc_set(""); model->formatted = false; model->font = TextBoxFontText; - return true; - }); + }, + true); return text_box; } @@ -152,10 +158,7 @@ void text_box_free(TextBox* text_box) { furi_assert(text_box); with_view_model( - text_box->view, (TextBoxModel * model) { - furi_string_free(model->text_formatted); - return true; - }); + text_box->view, TextBoxModel * model, { furi_string_free(model->text_formatted); }, true); view_free(text_box->view); free(text_box); } @@ -169,13 +172,15 @@ void text_box_reset(TextBox* text_box) { furi_assert(text_box); with_view_model( - text_box->view, (TextBoxModel * model) { + text_box->view, + TextBoxModel * model, + { model->text = NULL; furi_string_set(model->text_formatted, ""); model->font = TextBoxFontText; model->focus = TextBoxFocusStart; - return true; - }); + }, + true); } void text_box_set_text(TextBox* text_box, const char* text) { @@ -183,31 +188,27 @@ void text_box_set_text(TextBox* text_box, const char* text) { furi_assert(text); with_view_model( - text_box->view, (TextBoxModel * model) { + text_box->view, + TextBoxModel * model, + { model->text = text; furi_string_reset(model->text_formatted); furi_string_reserve(model->text_formatted, strlen(text)); model->formatted = false; - return true; - }); + }, + true); } void text_box_set_font(TextBox* text_box, TextBoxFont font) { furi_assert(text_box); with_view_model( - text_box->view, (TextBoxModel * model) { - model->font = font; - return true; - }); + text_box->view, TextBoxModel * model, { model->font = font; }, true); } void text_box_set_focus(TextBox* text_box, TextBoxFocus focus) { furi_assert(text_box); with_view_model( - text_box->view, (TextBoxModel * model) { - model->focus = focus; - return true; - }); + text_box->view, TextBoxModel * model, { model->focus = focus; }, true); } diff --git a/applications/services/gui/modules/text_input.c b/applications/services/gui/modules/text_input.c index d7e00940d65..79fa8772870 100644 --- a/applications/services/gui/modules/text_input.c +++ b/applications/services/gui/modules/text_input.c @@ -429,10 +429,10 @@ void text_input_timer_callback(void* context) { TextInput* text_input = context; with_view_model( - text_input->view, (TextInputModel * model) { - model->valadator_message_visible = false; - return true; - }); + text_input->view, + TextInputModel * model, + { model->valadator_message_visible = false; }, + true); } TextInput* text_input_alloc() { @@ -446,10 +446,10 @@ TextInput* text_input_alloc() { text_input->timer = furi_timer_alloc(text_input_timer_callback, FuriTimerTypeOnce, text_input); with_view_model( - text_input->view, (TextInputModel * model) { - model->validator_text = furi_string_alloc(); - return false; - }); + text_input->view, + TextInputModel * model, + { model->validator_text = furi_string_alloc(); }, + false); text_input_reset(text_input); @@ -459,10 +459,10 @@ TextInput* text_input_alloc() { void text_input_free(TextInput* text_input) { furi_assert(text_input); with_view_model( - text_input->view, (TextInputModel * model) { - furi_string_free(model->validator_text); - return false; - }); + text_input->view, + TextInputModel * model, + { furi_string_free(model->validator_text); }, + false); // Send stop command furi_timer_stop(text_input->timer); @@ -477,7 +477,9 @@ void text_input_free(TextInput* text_input) { void text_input_reset(TextInput* text_input) { furi_assert(text_input); with_view_model( - text_input->view, (TextInputModel * model) { + text_input->view, + TextInputModel * model, + { model->text_buffer_size = 0; model->header = ""; model->selected_row = 0; @@ -491,8 +493,8 @@ void text_input_reset(TextInput* text_input) { model->validator_callback_context = NULL; furi_string_reset(model->validator_text); model->valadator_message_visible = false; - return true; - }); + }, + true); } View* text_input_get_view(TextInput* text_input) { @@ -508,7 +510,9 @@ void text_input_set_result_callback( size_t text_buffer_size, bool clear_default_text) { with_view_model( - text_input->view, (TextInputModel * model) { + text_input->view, + TextInputModel * model, + { model->callback = callback; model->callback_context = callback_context; model->text_buffer = text_buffer; @@ -519,8 +523,8 @@ void text_input_set_result_callback( model->selected_row = 2; model->selected_column = 8; } - return true; - }); + }, + true); } void text_input_set_validator( @@ -528,37 +532,36 @@ void text_input_set_validator( TextInputValidatorCallback callback, void* callback_context) { with_view_model( - text_input->view, (TextInputModel * model) { + text_input->view, + TextInputModel * model, + { model->validator_callback = callback; model->validator_callback_context = callback_context; - return true; - }); + }, + true); } TextInputValidatorCallback text_input_get_validator_callback(TextInput* text_input) { TextInputValidatorCallback validator_callback = NULL; with_view_model( - text_input->view, (TextInputModel * model) { - validator_callback = model->validator_callback; - return false; - }); + text_input->view, + TextInputModel * model, + { validator_callback = model->validator_callback; }, + false); return validator_callback; } void* text_input_get_validator_callback_context(TextInput* text_input) { void* validator_callback_context = NULL; with_view_model( - text_input->view, (TextInputModel * model) { - validator_callback_context = model->validator_callback_context; - return false; - }); + text_input->view, + TextInputModel * model, + { validator_callback_context = model->validator_callback_context; }, + false); return validator_callback_context; } void text_input_set_header_text(TextInput* text_input, const char* text) { with_view_model( - text_input->view, (TextInputModel * model) { - model->header = text; - return true; - }); + text_input->view, TextInputModel * model, { model->header = text; }, true); } diff --git a/applications/services/gui/modules/variable_item_list.c b/applications/services/gui/modules/variable_item_list.c index ec8fd3d20ac..acd46ee4b15 100644 --- a/applications/services/gui/modules/variable_item_list.c +++ b/applications/services/gui/modules/variable_item_list.c @@ -92,7 +92,9 @@ static void variable_item_list_draw_callback(Canvas* canvas, void* _model) { void variable_item_list_set_selected_item(VariableItemList* variable_item_list, uint8_t index) { with_view_model( - variable_item_list->view, (VariableItemListModel * model) { + variable_item_list->view, + VariableItemListModel * model, + { uint8_t position = index; if(position >= VariableItemArray_size(model->items)) { position = 0; @@ -112,9 +114,8 @@ void variable_item_list_set_selected_item(VariableItemList* variable_item_list, model->window_position = (VariableItemArray_size(model->items) - 4); } } - - return true; - }); + }, + true); } uint8_t variable_item_list_get_selected_item_index(VariableItemList* variable_item_list) { @@ -181,7 +182,9 @@ static bool variable_item_list_input_callback(InputEvent* event, void* context) void variable_item_list_process_up(VariableItemList* variable_item_list) { with_view_model( - variable_item_list->view, (VariableItemListModel * model) { + variable_item_list->view, + VariableItemListModel * model, + { uint8_t items_on_screen = 4; if(model->position > 0) { model->position--; @@ -195,13 +198,15 @@ void variable_item_list_process_up(VariableItemList* variable_item_list) { model->window_position = model->position - (items_on_screen - 1); } } - return true; - }); + }, + true); } void variable_item_list_process_down(VariableItemList* variable_item_list) { with_view_model( - variable_item_list->view, (VariableItemListModel * model) { + variable_item_list->view, + VariableItemListModel * model, + { uint8_t items_on_screen = 4; if(model->position < (VariableItemArray_size(model->items) - 1)) { model->position++; @@ -214,8 +219,8 @@ void variable_item_list_process_down(VariableItemList* variable_item_list) { model->position = 0; model->window_position = 0; } - return true; - }); + }, + true); } VariableItem* variable_item_list_get_selected_item(VariableItemListModel* model) { @@ -239,7 +244,9 @@ VariableItem* variable_item_list_get_selected_item(VariableItemListModel* model) void variable_item_list_process_left(VariableItemList* variable_item_list) { with_view_model( - variable_item_list->view, (VariableItemListModel * model) { + variable_item_list->view, + VariableItemListModel * model, + { VariableItem* item = variable_item_list_get_selected_item(model); if(item->current_value_index > 0) { item->current_value_index--; @@ -247,13 +254,15 @@ void variable_item_list_process_left(VariableItemList* variable_item_list) { item->change_callback(item); } } - return true; - }); + }, + true); } void variable_item_list_process_right(VariableItemList* variable_item_list) { with_view_model( - variable_item_list->view, (VariableItemListModel * model) { + variable_item_list->view, + VariableItemListModel * model, + { VariableItem* item = variable_item_list_get_selected_item(model); if(item->current_value_index < (item->values_count - 1)) { item->current_value_index++; @@ -261,18 +270,20 @@ void variable_item_list_process_right(VariableItemList* variable_item_list) { item->change_callback(item); } } - return true; - }); + }, + true); } void variable_item_list_process_ok(VariableItemList* variable_item_list) { with_view_model( - variable_item_list->view, (VariableItemListModel * model) { + variable_item_list->view, + VariableItemListModel * model, + { if(variable_item_list->callback) { variable_item_list->callback(variable_item_list->context, model->position); } - return false; - }); + }, + false); } VariableItemList* variable_item_list_alloc() { @@ -285,12 +296,14 @@ VariableItemList* variable_item_list_alloc() { view_set_input_callback(variable_item_list->view, variable_item_list_input_callback); with_view_model( - variable_item_list->view, (VariableItemListModel * model) { + variable_item_list->view, + VariableItemListModel * model, + { VariableItemArray_init(model->items); model->position = 0; model->window_position = 0; - return true; - }); + }, + true); return variable_item_list; } @@ -299,15 +312,17 @@ void variable_item_list_free(VariableItemList* variable_item_list) { furi_assert(variable_item_list); with_view_model( - variable_item_list->view, (VariableItemListModel * model) { + variable_item_list->view, + VariableItemListModel * model, + { VariableItemArray_it_t it; for(VariableItemArray_it(it, model->items); !VariableItemArray_end_p(it); VariableItemArray_next(it)) { furi_string_free(VariableItemArray_ref(it)->current_value_text); } VariableItemArray_clear(model->items); - return false; - }); + }, + false); view_free(variable_item_list->view); free(variable_item_list); } @@ -316,15 +331,17 @@ void variable_item_list_reset(VariableItemList* variable_item_list) { furi_assert(variable_item_list); with_view_model( - variable_item_list->view, (VariableItemListModel * model) { + variable_item_list->view, + VariableItemListModel * model, + { VariableItemArray_it_t it; for(VariableItemArray_it(it, model->items); !VariableItemArray_end_p(it); VariableItemArray_next(it)) { furi_string_free(VariableItemArray_ref(it)->current_value_text); } VariableItemArray_reset(model->items); - return false; - }); + }, + false); } View* variable_item_list_get_view(VariableItemList* variable_item_list) { @@ -343,7 +360,9 @@ VariableItem* variable_item_list_add( furi_assert(variable_item_list); with_view_model( - variable_item_list->view, (VariableItemListModel * model) { + variable_item_list->view, + VariableItemListModel * model, + { item = VariableItemArray_push_new(model->items); item->label = label; item->values_count = values_count; @@ -351,8 +370,8 @@ VariableItem* variable_item_list_add( item->context = context; item->current_value_index = 0; item->current_value_text = furi_string_alloc(); - return true; - }); + }, + true); return item; } @@ -363,12 +382,14 @@ void variable_item_list_set_enter_callback( void* context) { furi_assert(callback); with_view_model( - variable_item_list->view, (VariableItemListModel * model) { + variable_item_list->view, + VariableItemListModel * model, + { UNUSED(model); variable_item_list->callback = callback; variable_item_list->context = context; - return false; - }); + }, + false); } void variable_item_set_current_value_index(VariableItem* item, uint8_t current_value_index) { diff --git a/applications/services/gui/modules/widget.c b/applications/services/gui/modules/widget.c index 802f76e243f..6153b425bde 100644 --- a/applications/services/gui/modules/widget.c +++ b/applications/services/gui/modules/widget.c @@ -36,7 +36,9 @@ static bool gui_widget_view_input_callback(InputEvent* event, void* context) { // Call all Widget Elements input handlers with_view_model( - widget->view, (GuiWidgetModel * model) { + widget->view, + GuiWidgetModel * model, + { ElementArray_it_t it; ElementArray_it(it, model->element); while(!ElementArray_end_p(it)) { @@ -46,8 +48,8 @@ static bool gui_widget_view_input_callback(InputEvent* event, void* context) { } ElementArray_next(it); } - return true; - }); + }, + true); return consumed; } @@ -61,10 +63,7 @@ Widget* widget_alloc() { view_set_input_callback(widget->view, gui_widget_view_input_callback); with_view_model( - widget->view, (GuiWidgetModel * model) { - ElementArray_init(model->element); - return true; - }); + widget->view, GuiWidgetModel * model, { ElementArray_init(model->element); }, true); return widget; } @@ -73,7 +72,9 @@ void widget_reset(Widget* widget) { furi_assert(widget); with_view_model( - widget->view, (GuiWidgetModel * model) { + widget->view, + GuiWidgetModel * model, + { ElementArray_it_t it; ElementArray_it(it, model->element); while(!ElementArray_end_p(it)) { @@ -83,8 +84,8 @@ void widget_reset(Widget* widget) { ElementArray_next(it); } ElementArray_reset(model->element); - return true; - }); + }, + true); } void widget_free(Widget* widget) { @@ -93,10 +94,7 @@ void widget_free(Widget* widget) { widget_reset(widget); // Free elements container with_view_model( - widget->view, (GuiWidgetModel * model) { - ElementArray_clear(model->element); - return true; - }); + widget->view, GuiWidgetModel * model, { ElementArray_clear(model->element); }, true); view_free(widget->view); free(widget); @@ -112,11 +110,13 @@ static void widget_add_element(Widget* widget, WidgetElement* element) { furi_assert(element); with_view_model( - widget->view, (GuiWidgetModel * model) { + widget->view, + GuiWidgetModel * model, + { element->parent = widget; ElementArray_push_back(model->element, element); - return true; - }); + }, + true); } void widget_add_string_multiline_element( diff --git a/applications/services/gui/view.h b/applications/services/gui/view.h index 5b4bf3609a6..b40f8ded5a2 100644 --- a/applications/services/gui/view.h +++ b/applications/services/gui/view.h @@ -211,25 +211,25 @@ void view_commit_model(View* view, bool update); #endif #ifdef __cplusplus -#define with_view_model_cpp(view, type, var, function_body) \ +#define with_view_model_cpp(view, type, var, code, update) \ { \ - type* p = static_cast(view_get_model(view)); \ - bool update = [&](type * var) function_body(p); \ + type var = static_cast(view_get_model(view)); \ + {code}; \ view_commit_model(view, update); \ } #else /** With clause for view model * * @param view View instance pointer - * @param function_body a (){} lambda declaration, executed within you - * parent function context + * @param type View model type + * @param code Code block that will be executed between model lock and unlock + * @param update Bool flag, if true, view will be updated after code block. Can be variable, so code block can decide if update is needed. * - * @return true if you want to emit view update, false otherwise */ -#define with_view_model(view, function_body) \ - { \ - void* p = view_get_model(view); \ - bool update = ({ bool __fn__ function_body __fn__; })(p); \ - view_commit_model(view, update); \ +#define with_view_model(view, type, code, update) \ + { \ + type = view_get_model(view); \ + {code}; \ + view_commit_model(view, update); \ } #endif diff --git a/applications/services/power/power_service/views/power_off.c b/applications/services/power/power_service/views/power_off.c index 398ebe4ab9d..b0046325c5b 100644 --- a/applications/services/power/power_service/views/power_off.c +++ b/applications/services/power/power_service/views/power_off.c @@ -87,19 +87,13 @@ View* power_off_get_view(PowerOff* power_off) { void power_off_set_time_left(PowerOff* power_off, uint8_t time_left) { furi_assert(power_off); with_view_model( - power_off->view, (PowerOffModel * model) { - model->time_left_sec = time_left; - return true; - }); + power_off->view, PowerOffModel * model, { model->time_left_sec = time_left; }, 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; - }); + power_off->view, PowerOffModel * model, { response = model->response; }, false); return response; } diff --git a/applications/settings/power_settings_app/views/battery_info.c b/applications/settings/power_settings_app/views/battery_info.c index aa3a1e962f5..1a8bc71ec05 100644 --- a/applications/settings/power_settings_app/views/battery_info.c +++ b/applications/settings/power_settings_app/views/battery_info.c @@ -119,8 +119,8 @@ void battery_info_set_data(BatteryInfo* battery_info, BatteryInfoModel* data) { furi_assert(battery_info); furi_assert(data); with_view_model( - battery_info->view, (BatteryInfoModel * model) { - memcpy(model, data, sizeof(BatteryInfoModel)); - return true; - }); + battery_info->view, + BatteryInfoModel * model, + { memcpy(model, data, sizeof(BatteryInfoModel)); }, + true); } diff --git a/applications/system/updater/views/updater_main.c b/applications/system/updater/views/updater_main.c index 8d6694d896f..5ed3c70aa9d 100644 --- a/applications/system/updater/views/updater_main.c +++ b/applications/system/updater/views/updater_main.c @@ -28,22 +28,25 @@ void updater_main_model_set_state( const char* message, uint8_t progress, bool failed) { + bool update = false; with_view_model( - main_view->view, (UpdaterProgressModel * model) { + main_view->view, + UpdaterProgressModel * model, + { model->failed = failed; model->progress = progress; if(furi_string_cmp_str(model->status, message)) { furi_string_set(model->status, message); model->rendered_progress = progress; - return true; - } - if((model->rendered_progress > progress) || - ((progress - model->rendered_progress) > PROGRESS_RENDER_STEP)) { + update = true; + } else if( + (model->rendered_progress > progress) || + ((progress - model->rendered_progress) > PROGRESS_RENDER_STEP)) { model->rendered_progress = progress; - return true; + update = true; } - return false; - }); + }, + update); } View* updater_main_get_view(UpdaterMainView* main_view) { @@ -104,10 +107,10 @@ UpdaterMainView* updater_main_alloc() { view_allocate_model(main_view->view, ViewModelTypeLocking, sizeof(UpdaterProgressModel)); with_view_model( - main_view->view, (UpdaterProgressModel * model) { - model->status = furi_string_alloc_set("Waiting for SD card"); - return true; - }); + main_view->view, + UpdaterProgressModel * model, + { model->status = furi_string_alloc_set("Waiting for SD card"); }, + true); view_set_context(main_view->view, main_view); view_set_input_callback(main_view->view, updater_main_input); @@ -119,10 +122,7 @@ UpdaterMainView* updater_main_alloc() { void updater_main_free(UpdaterMainView* main_view) { furi_assert(main_view); with_view_model( - main_view->view, (UpdaterProgressModel * model) { - furi_string_free(model->status); - return false; - }); + main_view->view, UpdaterProgressModel * model, { furi_string_free(model->status); }, false); view_free(main_view->view); free(main_view); } From a1ede0a2fcbbf237a9d239e24001874476fd5c41 Mon Sep 17 00:00:00 2001 From: Roland Kammerer Date: Sat, 8 Oct 2022 19:56:56 +0200 Subject: [PATCH 129/824] BadUSB: add SYSRQ keys (#1460) This allows sending of SysRq keys[1]. This then for example allows sending the well known 'reisub' commands to safely reboot a otherwise frozen Linux box. Or obviously any of the other magic keys. The advantage compared to sending it to /proc/sysrq-trigger is that one does not need a shell and depending on how broken the system is, one might not even be able to get a new shell. The SysRq keys still work. The cost is adding a new/"non-standard" keyword, IMO it is worth it. Example: DEFAULTDELAY 200 DELAY 1000 SYSRQ r SYSRQ e SYSRQ i SYSRQ s SYSRQ u SYSRQ b If one really wants to test it, I suggest h(elp) or w(ait). [1] https://en.wikipedia.org/wiki/Magic_SysRq_key Co-authored-by: Aleksandr Kutuzov --- applications/main/bad_usb/bad_usb_script.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/applications/main/bad_usb/bad_usb_script.c b/applications/main/bad_usb/bad_usb_script.c index 1e3edf40fb1..78aba88ed8d 100644 --- a/applications/main/bad_usb/bad_usb_script.c +++ b/applications/main/bad_usb/bad_usb_script.c @@ -109,6 +109,7 @@ static const char ducky_cmd_string[] = {"STRING "}; static const char ducky_cmd_defdelay_1[] = {"DEFAULT_DELAY "}; static const char ducky_cmd_defdelay_2[] = {"DEFAULTDELAY "}; static const char ducky_cmd_repeat[] = {"REPEAT "}; +static const char ducky_cmd_sysrq[] = {"SYSRQ "}; static const char ducky_cmd_altchar[] = {"ALTCHAR "}; static const char ducky_cmd_altstr_1[] = {"ALTSTRING "}; @@ -292,6 +293,14 @@ static int32_t ducky_parse_line(BadUsbScript* bad_usb, FuriString* line) { line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; state = ducky_get_number(line_tmp, &bad_usb->repeat_cnt); return (state) ? (0) : SCRIPT_STATE_ERROR; + } else if(strncmp(line_tmp, ducky_cmd_sysrq, strlen(ducky_cmd_sysrq)) == 0) { + // SYSRQ + line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; + uint16_t key = ducky_get_keycode(line_tmp, true); + furi_hal_hid_kb_press(KEY_MOD_LEFT_ALT | HID_KEYBOARD_PRINT_SCREEN); + furi_hal_hid_kb_press(key); + furi_hal_hid_kb_release_all(); + return (0); } else { // Special keys + modifiers uint16_t key = ducky_get_keycode(line_tmp, false); From 3fd30a913231ab33c3005b164b1fdae3772bc195 Mon Sep 17 00:00:00 2001 From: UberGuidoZ <57457139+UberGuidoZ@users.noreply.github.com> Date: Sat, 8 Oct 2022 23:20:15 -0700 Subject: [PATCH 130/824] docs: update on FAP build process (#1852) --- documentation/AppsOnSDCard.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/AppsOnSDCard.md b/documentation/AppsOnSDCard.md index 552be13e47d..aff8314dcf4 100644 --- a/documentation/AppsOnSDCard.md +++ b/documentation/AppsOnSDCard.md @@ -13,7 +13,7 @@ FAPs are created and developed the same way as internal applications that are pa To build your application as a FAP, just create a folder with your app's source code in `applications_user`, then write its code the way you'd do when creating a regular built-in application. Then configure its `application.fam` manifest — and set its *apptype* to FlipperAppType.EXTERNAL. See [Application Manifests](./AppManifests.md#application-definition) for more details. - * To build your application, run `./fbt firmware_{APPID}`, where APPID is your application's ID in its manifest. + * To build your application, run `./fbt fap_{APPID}`, where APPID is your application's ID in its manifest. * To build your app, then upload it over USB & run it on Flipper, use `./fbt launch_app APPSRC=applications/path/to/app`. This command is configured in default [VSCode profile](../.vscode/ReadMe.md) as "Launch App on Flipper" build action (Ctrl+Shift+B menu). * To build all FAPs, run `./fbt plugin_dist`. From 906124b09192947db9c3ad52a1c7911af0d3b347 Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Mon, 10 Oct 2022 08:43:15 +0400 Subject: [PATCH 131/824] [FL-2886] SubGhz: fix text overlap in read (#1853) --- applications/main/subghz/subghz_i.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/main/subghz/subghz_i.c b/applications/main/subghz/subghz_i.c index acf07bc1461..ac1036d0551 100644 --- a/applications/main/subghz/subghz_i.c +++ b/applications/main/subghz/subghz_i.c @@ -60,7 +60,7 @@ void subghz_get_frequency_modulation(SubGhz* subghz, FuriString* frequency, Furi subghz->txrx->preset->frequency / 10000 % 100); } if(modulation != NULL) { - furi_string_printf(modulation, "%2s", furi_string_get_cstr(subghz->txrx->preset->name)); + furi_string_printf(modulation, "%.2s", furi_string_get_cstr(subghz->txrx->preset->name)); } } From 04f5ad83f89bfd9c2ecbb29a12399ad9b8f64183 Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Tue, 11 Oct 2022 19:54:35 +0300 Subject: [PATCH 132/824] [FL-2828] Dolphin score points update #1809 --- .../main/gpio/scenes/gpio_scene_start.c | 2 ++ .../common/infrared_scene_universal_common.c | 2 +- .../main/nfc/scenes/nfc_scene_detect_reader.c | 2 +- .../nfc/scenes/nfc_scene_mf_classic_menu.c | 3 ++ ...nfc_scene_mf_ultralight_read_auth_result.c | 4 ++- .../main/nfc/scenes/nfc_scene_set_uid.c | 2 +- applications/plugins/bt_hid_app/bt_hid.c | 3 ++ applications/plugins/snake_game/snake_game.c | 3 ++ .../services/dolphin/helpers/dolphin_deed.c | 28 +++++++++++++------ .../services/dolphin/helpers/dolphin_deed.h | 14 ++++++++-- 10 files changed, 48 insertions(+), 15 deletions(-) diff --git a/applications/main/gpio/scenes/gpio_scene_start.c b/applications/main/gpio/scenes/gpio_scene_start.c index 41b745233fa..72992294981 100644 --- a/applications/main/gpio/scenes/gpio_scene_start.c +++ b/applications/main/gpio/scenes/gpio_scene_start.c @@ -1,6 +1,7 @@ #include "../gpio_app_i.h" #include "furi_hal_power.h" #include "furi_hal_usb.h" +#include enum GpioItem { GpioItemUsbUart, @@ -88,6 +89,7 @@ bool gpio_scene_start_on_event(void* context, SceneManagerEvent event) { } else if(event.event == GpioStartEventUsbUart) { scene_manager_set_scene_state(app->scene_manager, GpioSceneStart, GpioItemUsbUart); if(!furi_hal_usb_is_locked()) { + DOLPHIN_DEED(DolphinDeedGpioUartBridge); scene_manager_next_scene(app->scene_manager, GpioSceneUsbUart); } else { scene_manager_next_scene(app->scene_manager, GpioSceneUsbUartCloseRpc); diff --git a/applications/main/infrared/scenes/common/infrared_scene_universal_common.c b/applications/main/infrared/scenes/common/infrared_scene_universal_common.c index f823ca93103..d55d8d0a6db 100644 --- a/applications/main/infrared/scenes/common/infrared_scene_universal_common.c +++ b/applications/main/infrared/scenes/common/infrared_scene_universal_common.c @@ -70,7 +70,7 @@ bool infrared_scene_universal_common_on_event(void* context, SceneManagerEvent e uint32_t record_count; if(infrared_brute_force_start( brute_force, infrared_custom_event_get_value(event.event), &record_count)) { - DOLPHIN_DEED(DolphinDeedIrBruteForce); + DOLPHIN_DEED(DolphinDeedIrSend); infrared_scene_universal_common_show_popup(infrared, record_count); } else { scene_manager_next_scene(scene_manager, InfraredSceneErrorDatabases); diff --git a/applications/main/nfc/scenes/nfc_scene_detect_reader.c b/applications/main/nfc/scenes/nfc_scene_detect_reader.c index e8c2b5ee8fb..f0177f9c11e 100644 --- a/applications/main/nfc/scenes/nfc_scene_detect_reader.c +++ b/applications/main/nfc/scenes/nfc_scene_detect_reader.c @@ -26,7 +26,7 @@ void nfc_scene_detect_reader_callback(void* context) { void nfc_scene_detect_reader_on_enter(void* context) { Nfc* nfc = context; - DOLPHIN_DEED(DolphinDeedNfcEmulate); + DOLPHIN_DEED(DolphinDeedNfcDetectReader); detect_reader_set_callback(nfc->detect_reader, nfc_scene_detect_reader_callback, nfc); detect_reader_set_nonces_max(nfc->detect_reader, NFC_SCENE_DETECT_READER_PAIR_NONCES_MAX); diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_menu.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_menu.c index 76d02e01ea1..2cba0433753 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_menu.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_menu.c @@ -1,4 +1,5 @@ #include "../nfc_i.h" +#include enum SubmenuIndex { SubmenuIndexSave, @@ -35,6 +36,8 @@ bool nfc_scene_mf_classic_menu_on_event(void* context, SceneManagerEvent event) if(event.type == SceneManagerEventTypeCustom) { if(event.event == SubmenuIndexSave) { + DOLPHIN_DEED(DolphinDeedNfcMfcAdd); + scene_manager_set_scene_state( nfc->scene_manager, NfcSceneMfClassicMenu, SubmenuIndexSave); nfc->dev->format = NfcDeviceSaveFormatMifareClassic; diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_auth_result.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_auth_result.c index f0c53c9ba85..5a690a2135e 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_auth_result.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_auth_result.c @@ -14,7 +14,6 @@ void nfc_scene_mf_ultralight_read_auth_result_widget_callback( void nfc_scene_mf_ultralight_read_auth_result_on_enter(void* context) { Nfc* nfc = context; - DOLPHIN_DEED(DolphinDeedNfcReadSuccess); // Setup dialog view FuriHalNfcDevData* nfc_data = &nfc->dev->dev_data.nfc_data; @@ -38,6 +37,7 @@ void nfc_scene_mf_ultralight_read_auth_result_on_enter(void* context) { widget_add_string_element( widget, 0, 17, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(temp_str)); if(mf_ul_data->auth_success) { + DOLPHIN_DEED(DolphinDeedNfcReadSuccess); furi_string_printf( temp_str, "Password: %02X %02X %02X %02X", @@ -54,6 +54,8 @@ void nfc_scene_mf_ultralight_read_auth_result_on_enter(void* context) { config_pages->auth_data.pack.raw[1]); widget_add_string_element( widget, 0, 39, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(temp_str)); + } else { + DOLPHIN_DEED(DolphinDeedNfcMfulError); } furi_string_printf( temp_str, "Pages Read: %d/%d", mf_ul_data->data_read / 4, mf_ul_data->data_size / 4); diff --git a/applications/main/nfc/scenes/nfc_scene_set_uid.c b/applications/main/nfc/scenes/nfc_scene_set_uid.c index 0ff28971011..9622ba2130f 100644 --- a/applications/main/nfc/scenes/nfc_scene_set_uid.c +++ b/applications/main/nfc/scenes/nfc_scene_set_uid.c @@ -30,7 +30,7 @@ bool nfc_scene_set_uid_on_event(void* context, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { if(event.event == NfcCustomEventByteInputDone) { - DOLPHIN_DEED(DolphinDeedNfcAdd); + DOLPHIN_DEED(DolphinDeedNfcAddSave); if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSavedMenu)) { nfc->dev->dev_data.nfc_data = nfc->dev_edit_data; if(nfc_device_save(nfc->dev, nfc->dev->dev_name)) { diff --git a/applications/plugins/bt_hid_app/bt_hid.c b/applications/plugins/bt_hid_app/bt_hid.c index b653fb37d0f..a470f7bc6b2 100644 --- a/applications/plugins/bt_hid_app/bt_hid.c +++ b/applications/plugins/bt_hid_app/bt_hid.c @@ -1,6 +1,7 @@ #include "bt_hid.h" #include #include +#include #define TAG "BtHidApp" @@ -185,6 +186,8 @@ int32_t bt_hid_app(void* p) { } furi_hal_bt_start_advertising(); + DOLPHIN_DEED(DolphinDeedPluginStart); + view_dispatcher_run(app->view_dispatcher); bt_set_status_changed_callback(app->bt, NULL, NULL); diff --git a/applications/plugins/snake_game/snake_game.c b/applications/plugins/snake_game/snake_game.c index 0b665a943d9..283d017ed6f 100644 --- a/applications/plugins/snake_game/snake_game.c +++ b/applications/plugins/snake_game/snake_game.c @@ -2,6 +2,7 @@ #include #include #include +#include #include #include @@ -346,6 +347,8 @@ int32_t snake_game_app(void* p) { notification_message_block(notification, &sequence_display_backlight_enforce_on); + DOLPHIN_DEED(DolphinDeedPluginGameStart); + SnakeEvent event; for(bool processing = true; processing;) { FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100); diff --git a/applications/services/dolphin/helpers/dolphin_deed.c b/applications/services/dolphin/helpers/dolphin_deed.c index b112d82a1c9..d3c40298d7f 100644 --- a/applications/services/dolphin/helpers/dolphin_deed.c +++ b/applications/services/dolphin/helpers/dolphin_deed.c @@ -18,13 +18,15 @@ static const DolphinDeedWeight dolphin_deed_weights[] = { {1, DolphinAppNfc}, // DolphinDeedNfcRead {3, DolphinAppNfc}, // DolphinDeedNfcReadSuccess {3, DolphinAppNfc}, // DolphinDeedNfcSave + {1, DolphinAppNfc}, // DolphinDeedNfcDetectReader {2, DolphinAppNfc}, // DolphinDeedNfcEmulate - {2, DolphinAppNfc}, // DolphinDeedNfcAdd + {2, DolphinAppNfc}, // DolphinDeedNfcMfcAdd + {1, DolphinAppNfc}, // DolphinDeedNfcMfulError + {1, DolphinAppNfc}, // DolphinDeedNfcAddSave {1, DolphinAppIr}, // DolphinDeedIrSend {3, DolphinAppIr}, // DolphinDeedIrLearnSuccess {3, DolphinAppIr}, // DolphinDeedIrSave - {2, DolphinAppIr}, // DolphinDeedIrBruteForce {1, DolphinAppIbutton}, // DolphinDeedIbuttonRead {3, DolphinAppIbutton}, // DolphinDeedIbuttonReadSuccess @@ -34,16 +36,24 @@ static const DolphinDeedWeight dolphin_deed_weights[] = { {3, DolphinAppBadusb}, // DolphinDeedBadUsbPlayScript {3, DolphinAppU2f}, // DolphinDeedU2fAuthorized + + {1, DolphinAppGpio}, // DolphinDeedGpioUartBridge + + {1, DolphinAppPlugin}, // DolphinDeedPluginStart + {1, DolphinAppPlugin}, // DolphinDeedPluginGameStart + {10, DolphinAppPlugin}, // DolphinDeedPluginGameWin }; static uint8_t dolphin_deed_limits[] = { - 15, // DolphinAppSubGhz - 15, // DolphinAppRfid - 15, // DolphinAppNfc - 15, // DolphinAppIr - 15, // DolphinAppIbutton - 15, // DolphinAppBadusb - 15, // DolphinAppU2f + 20, // DolphinAppSubGhz + 20, // DolphinAppRfid + 20, // DolphinAppNfc + 20, // DolphinAppIr + 20, // DolphinAppIbutton + 20, // DolphinAppBadusb + 20, // DolphinAppU2f + 20, // DolphinAppGpio + 20, // DolphinAppPlugin }; _Static_assert(COUNT_OF(dolphin_deed_weights) == DolphinDeedMAX, "dolphin_deed_weights size error"); diff --git a/applications/services/dolphin/helpers/dolphin_deed.h b/applications/services/dolphin/helpers/dolphin_deed.h index 1f63db3ff8f..969f0d5ccda 100644 --- a/applications/services/dolphin/helpers/dolphin_deed.h +++ b/applications/services/dolphin/helpers/dolphin_deed.h @@ -14,6 +14,8 @@ typedef enum { DolphinAppIbutton, DolphinAppBadusb, DolphinAppU2f, + DolphinAppGpio, + DolphinAppPlugin, DolphinAppMAX, } DolphinApp; @@ -34,13 +36,15 @@ typedef enum { DolphinDeedNfcRead, DolphinDeedNfcReadSuccess, DolphinDeedNfcSave, + DolphinDeedNfcDetectReader, DolphinDeedNfcEmulate, - DolphinDeedNfcAdd, + DolphinDeedNfcMfcAdd, + DolphinDeedNfcMfulError, + DolphinDeedNfcAddSave, DolphinDeedIrSend, DolphinDeedIrLearnSuccess, DolphinDeedIrSave, - DolphinDeedIrBruteForce, DolphinDeedIbuttonRead, DolphinDeedIbuttonReadSuccess, @@ -52,6 +56,12 @@ typedef enum { DolphinDeedU2fAuthorized, + DolphinDeedGpioUartBridge, + + DolphinDeedPluginStart, + DolphinDeedPluginGameStart, + DolphinDeedPluginGameWin, + DolphinDeedMAX, DolphinDeedTestLeft, From 2552278a3d968d02fb264698fc9c3bd1d4bdfa66 Mon Sep 17 00:00:00 2001 From: gornekich Date: Tue, 11 Oct 2022 22:13:12 +0500 Subject: [PATCH 133/824] [FL-2883] NFC: bank card rework reading (#1858) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * nfc: remove bank card save option * nfc: remove bank card save from nfc device * nfc: remove unused function in emv * nfc: try several times to start emv application * nfc: add AID display fallback for bank cards Co-authored-by: あく --- .../main/nfc/scenes/nfc_scene_emv_menu.c | 10 +-- .../nfc/scenes/nfc_scene_emv_read_success.c | 31 ++++++++-- lib/nfc/nfc_device.c | 34 +--------- lib/nfc/nfc_worker.c | 62 ++++++++++++------- lib/nfc/protocols/emv.c | 18 ++---- lib/nfc/protocols/emv.h | 10 +-- 6 files changed, 76 insertions(+), 89 deletions(-) diff --git a/applications/main/nfc/scenes/nfc_scene_emv_menu.c b/applications/main/nfc/scenes/nfc_scene_emv_menu.c index 1da630fcf13..eb1e10043bd 100644 --- a/applications/main/nfc/scenes/nfc_scene_emv_menu.c +++ b/applications/main/nfc/scenes/nfc_scene_emv_menu.c @@ -1,7 +1,6 @@ #include "../nfc_i.h" enum SubmenuIndex { - SubmenuIndexSave, SubmenuIndexInfo, }; @@ -15,7 +14,6 @@ void nfc_scene_emv_menu_on_enter(void* context) { Nfc* nfc = context; Submenu* submenu = nfc->submenu; - submenu_add_item(submenu, "Save", SubmenuIndexSave, nfc_scene_emv_menu_submenu_callback, nfc); submenu_add_item(submenu, "Info", SubmenuIndexInfo, nfc_scene_emv_menu_submenu_callback, nfc); submenu_set_selected_item( nfc->submenu, scene_manager_get_scene_state(nfc->scene_manager, NfcSceneEmvMenu)); @@ -28,13 +26,7 @@ bool nfc_scene_emv_menu_on_event(void* context, SceneManagerEvent event) { bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SubmenuIndexSave) { - nfc->dev->format = NfcDeviceSaveFormatBankCard; - // Clear device name - nfc_device_set_name(nfc->dev, ""); - scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveName); - consumed = true; - } else if(event.event == SubmenuIndexInfo) { + if(event.event == SubmenuIndexInfo) { scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcDataInfo); consumed = true; } diff --git a/applications/main/nfc/scenes/nfc_scene_emv_read_success.c b/applications/main/nfc/scenes/nfc_scene_emv_read_success.c index 8b6ab160eee..6a0b32fadc6 100644 --- a/applications/main/nfc/scenes/nfc_scene_emv_read_success.c +++ b/applications/main/nfc/scenes/nfc_scene_emv_read_success.c @@ -24,12 +24,33 @@ void nfc_scene_emv_read_success_on_enter(void* context) { nfc->widget, GuiButtonTypeRight, "More", nfc_scene_emv_read_success_widget_callback, nfc); FuriString* temp_str; - temp_str = furi_string_alloc_printf("\e#%s\n", emv_data->name); - for(uint8_t i = 0; i < emv_data->number_len; i += 2) { - furi_string_cat_printf( - temp_str, "%02X%02X ", emv_data->number[i], emv_data->number[i + 1]); + if(emv_data->name[0] != '\0') { + temp_str = furi_string_alloc_printf("\e#%s\n", emv_data->name); + } else { + temp_str = furi_string_alloc_printf("\e#Unknown Bank Card\n"); + } + if(emv_data->number_len) { + for(uint8_t i = 0; i < emv_data->number_len; i += 2) { + furi_string_cat_printf( + temp_str, "%02X%02X ", emv_data->number[i], emv_data->number[i + 1]); + } + furi_string_trim(temp_str); + } else if(emv_data->aid_len) { + furi_string_cat_printf(temp_str, "Can't parse data from app\n"); + // Parse AID name + FuriString* aid_name; + aid_name = furi_string_alloc(); + if(nfc_emv_parser_get_aid_name( + nfc->dev->storage, emv_data->aid, emv_data->aid_len, aid_name)) { + furi_string_cat_printf(temp_str, "AID: %s", furi_string_get_cstr(aid_name)); + } else { + furi_string_cat_printf(temp_str, "AID: "); + for(uint8_t i = 0; i < emv_data->aid_len; i++) { + furi_string_cat_printf(temp_str, "%02X", emv_data->aid[i]); + } + } + furi_string_free(aid_name); } - furi_string_trim(temp_str); // Add expiration date if(emv_data->exp_mon) { diff --git a/lib/nfc/nfc_device.c b/lib/nfc/nfc_device.c index f28d4d5bd32..06d57a0c35e 100644 --- a/lib/nfc/nfc_device.c +++ b/lib/nfc/nfc_device.c @@ -636,35 +636,7 @@ bool nfc_device_load_mifare_df_data(FlipperFormat* file, NfcDevice* dev) { return parsed; } -static bool nfc_device_save_bank_card_data(FlipperFormat* file, NfcDevice* dev) { - bool saved = false; - EmvData* data = &dev->dev_data.emv_data; - uint32_t data_temp = 0; - - do { - // Write Bank card specific data - if(!flipper_format_write_comment_cstr(file, "Bank card specific data")) break; - if(!flipper_format_write_hex(file, "AID", data->aid, data->aid_len)) break; - if(!flipper_format_write_string_cstr(file, "Name", data->name)) break; - if(!flipper_format_write_hex(file, "Number", data->number, data->number_len)) break; - if(data->exp_mon) { - uint8_t exp_data[2] = {data->exp_mon, data->exp_year}; - if(!flipper_format_write_hex(file, "Exp data", exp_data, sizeof(exp_data))) break; - } - if(data->country_code) { - data_temp = data->country_code; - if(!flipper_format_write_uint32(file, "Country code", &data_temp, 1)) break; - } - if(data->currency_code) { - data_temp = data->currency_code; - if(!flipper_format_write_uint32(file, "Currency code", &data_temp, 1)) break; - } - saved = true; - } while(false); - - return saved; -} - +// Leave for backward compatibility bool nfc_device_load_bank_card_data(FlipperFormat* file, NfcDevice* dev) { bool parsed = false; EmvData* data = &dev->dev_data.emv_data; @@ -1068,7 +1040,7 @@ static bool nfc_device_save_file( if(!flipper_format_write_header_cstr(file, nfc_file_header, nfc_file_version)) break; // Write nfc device type if(!flipper_format_write_comment_cstr( - file, "Nfc device type can be UID, Mifare Ultralight, Mifare Classic, Bank card")) + file, "Nfc device type can be UID, Mifare Ultralight, Mifare Classic")) break; nfc_device_prepare_format_string(dev, temp_str); if(!flipper_format_write_string(file, "Device type", temp_str)) break; @@ -1083,8 +1055,6 @@ static bool nfc_device_save_file( if(!nfc_device_save_mifare_ul_data(file, dev)) break; } else if(dev->format == NfcDeviceSaveFormatMifareDesfire) { if(!nfc_device_save_mifare_df_data(file, dev)) break; - } else if(dev->format == NfcDeviceSaveFormatBankCard) { - if(!nfc_device_save_bank_card_data(file, dev)) break; } else if(dev->format == NfcDeviceSaveFormatMifareClassic) { // Save data if(!nfc_device_save_mifare_classic_data(file, dev)) break; diff --git a/lib/nfc/nfc_worker.c b/lib/nfc/nfc_worker.c index 732453465ab..55d67c6577a 100644 --- a/lib/nfc/nfc_worker.c +++ b/lib/nfc/nfc_worker.c @@ -233,31 +233,51 @@ static bool nfc_worker_read_bank_card(NfcWorker* nfc_worker, FuriHalNfcTxRxConte reader_analyzer_start(nfc_worker->reader_analyzer, ReaderAnalyzerModeDebugLog); } - do { - // Read card + // Bank cards require strong field to start application. If we find AID, try at least several + // times to start EMV application + uint8_t start_application_attempts = 0; + while(start_application_attempts < 3) { + if(nfc_worker->state != NfcWorkerStateRead) break; + start_application_attempts++; if(!furi_hal_nfc_detect(&nfc_worker->dev_data->nfc_data, 300)) break; - if(!emv_read_bank_card(tx_rx, &emv_app)) break; - // Copy data - // TODO Set EmvData to reader or like in mifare ultralight! - result->number_len = emv_app.card_number_len; - memcpy(result->number, emv_app.card_number, result->number_len); + if(emv_read_bank_card(tx_rx, &emv_app)) { + FURI_LOG_D(TAG, "Bank card number read from %d attempt", start_application_attempts); + break; + } else if(emv_app.aid_len && !emv_app.app_started) { + FURI_LOG_D( + TAG, + "AID found but failed to start EMV app from %d attempt", + start_application_attempts); + furi_hal_nfc_sleep(); + continue; + } else { + FURI_LOG_D(TAG, "Failed to find AID"); + break; + } + } + // Copy data + if(emv_app.aid_len) { result->aid_len = emv_app.aid_len; memcpy(result->aid, emv_app.aid, result->aid_len); - if(emv_app.name_found) { - memcpy(result->name, emv_app.name, sizeof(emv_app.name)); - } - if(emv_app.exp_month) { - result->exp_mon = emv_app.exp_month; - result->exp_year = emv_app.exp_year; - } - if(emv_app.country_code) { - result->country_code = emv_app.country_code; - } - if(emv_app.currency_code) { - result->currency_code = emv_app.currency_code; - } read_success = true; - } while(false); + } + if(emv_app.card_number_len) { + result->number_len = emv_app.card_number_len; + memcpy(result->number, emv_app.card_number, result->number_len); + } + if(emv_app.name_found) { + memcpy(result->name, emv_app.name, sizeof(emv_app.name)); + } + if(emv_app.exp_month) { + result->exp_mon = emv_app.exp_month; + result->exp_year = emv_app.exp_year; + } + if(emv_app.country_code) { + result->country_code = emv_app.country_code; + } + if(emv_app.currency_code) { + result->currency_code = emv_app.currency_code; + } if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { reader_analyzer_stop(nfc_worker->reader_analyzer); diff --git a/lib/nfc/protocols/emv.c b/lib/nfc/protocols/emv.c index 935c9f6306d..e00d09e70ab 100644 --- a/lib/nfc/protocols/emv.c +++ b/lib/nfc/protocols/emv.c @@ -182,7 +182,7 @@ static bool emv_decode_response(uint8_t* buff, uint16_t len, EmvApplication* app return success; } -bool emv_select_ppse(FuriHalNfcTxRxContext* tx_rx, EmvApplication* app) { +static bool emv_select_ppse(FuriHalNfcTxRxContext* tx_rx, EmvApplication* app) { bool app_aid_found = false; const uint8_t emv_select_ppse_cmd[] = { 0x00, 0xA4, // SELECT ppse @@ -212,8 +212,8 @@ bool emv_select_ppse(FuriHalNfcTxRxContext* tx_rx, EmvApplication* app) { return app_aid_found; } -bool emv_select_app(FuriHalNfcTxRxContext* tx_rx, EmvApplication* app) { - bool select_app_success = false; +static bool emv_select_app(FuriHalNfcTxRxContext* tx_rx, EmvApplication* app) { + app->app_started = false; const uint8_t emv_select_header[] = { 0x00, 0xA4, // SELECT application @@ -236,7 +236,7 @@ bool emv_select_app(FuriHalNfcTxRxContext* tx_rx, EmvApplication* app) { if(furi_hal_nfc_tx_rx(tx_rx, 300)) { emv_trace(tx_rx, "Start application answer:"); if(emv_decode_response(tx_rx->rx_data, tx_rx->rx_bits / 8, app)) { - select_app_success = true; + app->app_started = true; } else { FURI_LOG_E(TAG, "Failed to read PAN or PDOL"); } @@ -244,7 +244,7 @@ bool emv_select_app(FuriHalNfcTxRxContext* tx_rx, EmvApplication* app) { FURI_LOG_E(TAG, "Failed to start application"); } - return select_app_success; + return app->app_started; } static uint16_t emv_prepare_pdol(APDU* dest, APDU* src) { @@ -367,14 +367,6 @@ static bool emv_read_files(FuriHalNfcTxRxContext* tx_rx, EmvApplication* app) { return card_num_read; } -bool emv_search_application(FuriHalNfcTxRxContext* tx_rx, EmvApplication* emv_app) { - furi_assert(tx_rx); - furi_assert(emv_app); - memset(emv_app, 0, sizeof(EmvApplication)); - - return emv_select_ppse(tx_rx, emv_app); -} - bool emv_read_bank_card(FuriHalNfcTxRxContext* tx_rx, EmvApplication* emv_app) { furi_assert(tx_rx); furi_assert(emv_app); diff --git a/lib/nfc/protocols/emv.h b/lib/nfc/protocols/emv.h index b5a0c574f07..0ccf7c3e049 100644 --- a/lib/nfc/protocols/emv.h +++ b/lib/nfc/protocols/emv.h @@ -45,6 +45,7 @@ typedef struct { uint8_t priority; uint8_t aid[16]; uint8_t aid_len; + bool app_started; char name[32]; bool name_found; uint8_t card_number[10]; @@ -68,15 +69,6 @@ typedef struct { */ bool emv_read_bank_card(FuriHalNfcTxRxContext* tx_rx, EmvApplication* emv_app); -/** Search for EMV Application - * - * @param tx_rx FuriHalNfcTxRxContext instance - * @param emv_app EmvApplication instance - * - * @return true on success - */ -bool emv_search_application(FuriHalNfcTxRxContext* tx_rx, EmvApplication* emv_app); - /** Emulate bank card * @note Answer to application selection and PDOL * From b3d95233224f61778413faf6d6455b96b7cac4ed Mon Sep 17 00:00:00 2001 From: Max Andreev Date: Wed, 12 Oct 2022 19:31:25 +0400 Subject: [PATCH 134/824] Github actions on kubernetes runners (#1861) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Change toolchain path and runner tag * fix check_submdules.yml * try to fix errors * create .ssh directory * fix toolchain path * add empty line for test * testing 3 k8s nodes speed * Test speed again * change tag, move reindex job * bring reindex.yml back * fix build.yml * fix reindex.yml Co-authored-by: あく --- .github/workflows/amap_analyse.yml | 3 +++ .github/workflows/build.yml | 8 +++++--- .github/workflows/check_submodules.yml | 1 + .github/workflows/lint_c.yml | 2 +- .github/workflows/lint_python.yml | 2 +- .github/workflows/pvs_studio.yml | 6 ++++-- 6 files changed, 15 insertions(+), 7 deletions(-) diff --git a/.github/workflows/amap_analyse.yml b/.github/workflows/amap_analyse.yml index 6be99c9d1e3..a50c5436f47 100644 --- a/.github/workflows/amap_analyse.yml +++ b/.github/workflows/amap_analyse.yml @@ -62,6 +62,8 @@ jobs: - name: 'Download build artifacts' run: | + mkdir -p ~/.ssh + ssh-keyscan -p ${{ secrets.RSYNC_DEPLOY_PORT }} -H ${{ secrets.RSYNC_DEPLOY_HOST }} > ~/.ssh/known_hosts echo "${{ secrets.RSYNC_DEPLOY_KEY }}" > deploy_key; chmod 600 ./deploy_key; rsync -avzP \ @@ -97,3 +99,4 @@ jobs: ${{ secrets.AMAP_MARIADB_PORT }} \ ${{ secrets.AMAP_MARIADB_DATABASE }} \ artifacts/flipper-z-f7-firmware-$SUFFIX.elf.map.all + diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 15b3966a774..1304c5d7b0d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -62,7 +62,7 @@ jobs: run: | set -e for TARGET in ${TARGETS}; do - FBT_TOOLCHAIN_PATH=/opt ./fbt TARGET_HW="$(echo "${TARGET}" | sed 's/f//')" \ + FBT_TOOLCHAIN_PATH=/runner/_work ./fbt TARGET_HW="$(echo "${TARGET}" | sed 's/f//')" \ updater_package ${{ startsWith(github.ref, 'refs/tags') && 'DEBUG=0 COMPACT=1' || '' }} done @@ -97,7 +97,7 @@ jobs: - name: 'Bundle core2 firmware' if: ${{ !github.event.pull_request.head.repo.fork }} run: | - FBT_TOOLCHAIN_PATH=/opt ./fbt copro_dist + FBT_TOOLCHAIN_PATH=/runner/_work ./fbt copro_dist tar czpf "artifacts/flipper-z-any-core2_firmware-${SUFFIX}.tgz" -C assets core2_firmware - name: 'Copy .map file' @@ -107,6 +107,8 @@ jobs: - name: 'Upload artifacts to update server' if: ${{ !github.event.pull_request.head.repo.fork }} run: | + mkdir -p ~/.ssh + ssh-keyscan -p ${{ secrets.RSYNC_DEPLOY_PORT }} -H ${{ secrets.RSYNC_DEPLOY_HOST }} > ~/.ssh/known_hosts echo "${{ secrets.RSYNC_DEPLOY_KEY }}" > deploy_key; chmod 600 ./deploy_key; rsync -avzP --delete --mkpath \ @@ -174,6 +176,6 @@ jobs: run: | set -e for TARGET in ${TARGETS}; do - FBT_TOOLCHAIN_PATH=/opt ./fbt TARGET_HW="$(echo "${TARGET}" | sed 's/f//')" \ + FBT_TOOLCHAIN_PATH=/runner/_work ./fbt TARGET_HW="$(echo "${TARGET}" | sed 's/f//')" \ updater_package DEBUG=0 COMPACT=1 done diff --git a/.github/workflows/check_submodules.yml b/.github/workflows/check_submodules.yml index e4178c3c702..eba4affc3d4 100644 --- a/.github/workflows/check_submodules.yml +++ b/.github/workflows/check_submodules.yml @@ -27,6 +27,7 @@ jobs: - name: 'Check protobuf branch' run: | + git submodule update --init SUB_PATH="assets/protobuf"; SUB_BRANCH="dev"; SUB_COMMITS_MIN=40; diff --git a/.github/workflows/lint_c.yml b/.github/workflows/lint_c.yml index becafcab088..23dc6c699aa 100644 --- a/.github/workflows/lint_c.yml +++ b/.github/workflows/lint_c.yml @@ -30,7 +30,7 @@ jobs: - name: 'Check code formatting' id: syntax_check - run: SET_GH_OUTPUT=1 FBT_TOOLCHAIN_PATH=/opt ./fbt lint + run: SET_GH_OUTPUT=1 FBT_TOOLCHAIN_PATH=/runner/_work ./fbt lint - name: Report code formatting errors if: failure() && steps.syntax_check.outputs.errors && github.event.pull_request diff --git a/.github/workflows/lint_python.yml b/.github/workflows/lint_python.yml index d5ff834ea5b..c2f09211003 100644 --- a/.github/workflows/lint_python.yml +++ b/.github/workflows/lint_python.yml @@ -26,4 +26,4 @@ jobs: ref: ${{ github.event.pull_request.head.sha }} - name: 'Check code formatting' - run: SET_GH_OUTPUT=1 FBT_TOOLCHAIN_PATH=/opt ./fbt lint_py + run: SET_GH_OUTPUT=1 FBT_TOOLCHAIN_PATH=/runner/_work ./fbt lint_py diff --git a/.github/workflows/pvs_studio.yml b/.github/workflows/pvs_studio.yml index 9815755517c..e3d5fc1321e 100644 --- a/.github/workflows/pvs_studio.yml +++ b/.github/workflows/pvs_studio.yml @@ -57,11 +57,11 @@ jobs: - name: 'Generate compile_comands.json' run: | - FBT_TOOLCHAIN_PATH=/opt ./fbt COMPACT=1 version_json proto_ver icons firmware_cdb dolphin_internal dolphin_blocking + FBT_TOOLCHAIN_PATH=/runner/_work ./fbt COMPACT=1 version_json proto_ver icons firmware_cdb dolphin_internal dolphin_blocking - name: 'Static code analysis' run: | - FBT_TOOLCHAIN_PATH=/opt source scripts/toolchain/fbtenv.sh + FBT_TOOLCHAIN_PATH=/runner/_work source scripts/toolchain/fbtenv.sh pvs-studio-analyzer credentials ${{ secrets.PVS_STUDIO_CREDENTIALS }} pvs-studio-analyzer analyze \ @.pvsoptions \ @@ -76,6 +76,8 @@ jobs: - name: 'Upload artifacts to update server' if: ${{ !github.event.pull_request.head.repo.fork }} run: | + mkdir -p ~/.ssh + ssh-keyscan -p ${{ secrets.RSYNC_DEPLOY_PORT }} -H ${{ secrets.RSYNC_DEPLOY_HOST }} > ~/.ssh/known_hosts echo "${{ secrets.RSYNC_DEPLOY_KEY }}" > deploy_key; chmod 600 ./deploy_key; rsync -avrzP --mkpath \ From 92a738bf7784ea13208a532322a1e96d4d3ff274 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Thu, 13 Oct 2022 00:39:39 +0900 Subject: [PATCH 135/824] Dolphin: add L1_Painting animation (#1863) Co-authored-by: hedger --- .../external/L1_Painting_128x64/frame_0.png | Bin 0 -> 1607 bytes .../external/L1_Painting_128x64/frame_1.png | Bin 0 -> 1618 bytes .../external/L1_Painting_128x64/frame_10.png | Bin 0 -> 1606 bytes .../external/L1_Painting_128x64/frame_11.png | Bin 0 -> 1579 bytes .../external/L1_Painting_128x64/frame_2.png | Bin 0 -> 1608 bytes .../external/L1_Painting_128x64/frame_3.png | Bin 0 -> 1585 bytes .../external/L1_Painting_128x64/frame_4.png | Bin 0 -> 1600 bytes .../external/L1_Painting_128x64/frame_5.png | Bin 0 -> 1609 bytes .../external/L1_Painting_128x64/frame_6.png | Bin 0 -> 1588 bytes .../external/L1_Painting_128x64/frame_7.png | Bin 0 -> 1630 bytes .../external/L1_Painting_128x64/frame_8.png | Bin 0 -> 1623 bytes .../external/L1_Painting_128x64/frame_9.png | Bin 0 -> 1614 bytes .../external/L1_Painting_128x64/meta.txt | 32 ++++++++++++++++++ assets/dolphin/external/manifest.txt | 7 ++++ .../dolphin/L1_Painting_128x64/frame_0.bm | Bin 0 -> 763 bytes .../dolphin/L1_Painting_128x64/frame_1.bm | Bin 0 -> 764 bytes .../dolphin/L1_Painting_128x64/frame_10.bm | Bin 0 -> 772 bytes .../dolphin/L1_Painting_128x64/frame_11.bm | Bin 0 -> 767 bytes .../dolphin/L1_Painting_128x64/frame_2.bm | Bin 0 -> 762 bytes .../dolphin/L1_Painting_128x64/frame_3.bm | Bin 0 -> 759 bytes .../dolphin/L1_Painting_128x64/frame_4.bm | Bin 0 -> 759 bytes .../dolphin/L1_Painting_128x64/frame_5.bm | Bin 0 -> 757 bytes .../dolphin/L1_Painting_128x64/frame_6.bm | Bin 0 -> 785 bytes .../dolphin/L1_Painting_128x64/frame_7.bm | Bin 0 -> 803 bytes .../dolphin/L1_Painting_128x64/frame_8.bm | Bin 0 -> 797 bytes .../dolphin/L1_Painting_128x64/frame_9.bm | Bin 0 -> 777 bytes .../dolphin/L1_Painting_128x64/meta.txt | 32 ++++++++++++++++++ assets/resources/dolphin/manifest.txt | 7 ++++ 28 files changed, 78 insertions(+) create mode 100644 assets/dolphin/external/L1_Painting_128x64/frame_0.png create mode 100644 assets/dolphin/external/L1_Painting_128x64/frame_1.png create mode 100644 assets/dolphin/external/L1_Painting_128x64/frame_10.png create mode 100644 assets/dolphin/external/L1_Painting_128x64/frame_11.png create mode 100644 assets/dolphin/external/L1_Painting_128x64/frame_2.png create mode 100644 assets/dolphin/external/L1_Painting_128x64/frame_3.png create mode 100644 assets/dolphin/external/L1_Painting_128x64/frame_4.png create mode 100644 assets/dolphin/external/L1_Painting_128x64/frame_5.png create mode 100644 assets/dolphin/external/L1_Painting_128x64/frame_6.png create mode 100644 assets/dolphin/external/L1_Painting_128x64/frame_7.png create mode 100644 assets/dolphin/external/L1_Painting_128x64/frame_8.png create mode 100644 assets/dolphin/external/L1_Painting_128x64/frame_9.png create mode 100644 assets/dolphin/external/L1_Painting_128x64/meta.txt create mode 100644 assets/resources/dolphin/L1_Painting_128x64/frame_0.bm create mode 100644 assets/resources/dolphin/L1_Painting_128x64/frame_1.bm create mode 100644 assets/resources/dolphin/L1_Painting_128x64/frame_10.bm create mode 100644 assets/resources/dolphin/L1_Painting_128x64/frame_11.bm create mode 100644 assets/resources/dolphin/L1_Painting_128x64/frame_2.bm create mode 100644 assets/resources/dolphin/L1_Painting_128x64/frame_3.bm create mode 100644 assets/resources/dolphin/L1_Painting_128x64/frame_4.bm create mode 100644 assets/resources/dolphin/L1_Painting_128x64/frame_5.bm create mode 100644 assets/resources/dolphin/L1_Painting_128x64/frame_6.bm create mode 100644 assets/resources/dolphin/L1_Painting_128x64/frame_7.bm create mode 100644 assets/resources/dolphin/L1_Painting_128x64/frame_8.bm create mode 100644 assets/resources/dolphin/L1_Painting_128x64/frame_9.bm create mode 100644 assets/resources/dolphin/L1_Painting_128x64/meta.txt diff --git a/assets/dolphin/external/L1_Painting_128x64/frame_0.png b/assets/dolphin/external/L1_Painting_128x64/frame_0.png new file mode 100644 index 0000000000000000000000000000000000000000..b2f9bc775cc50b2914c5b6db268535d21d006399 GIT binary patch literal 1607 zcmV-N2Dtf&P) z&R|;roM609XshvMfQPl!_%gu5+G?~b5mwP??26yDNr}6iSj7YttnjWdD1wOdS5O_% zXQZKO99ILJqQ|LiHIAzRPSNAkwi;gscvxGFF9STR)%FsB93LtI`{Yq1YD)EIcL%`< zPP{V>NH%FiWO|c38l|97knnzMbE{Ip3C^o~Ku0x{V zTN_!|TVwSJo~Z>Q?ELFslfeK#6yZ2O>UgO{gaM+4j;=eBfd`z4W9vRD0 zxPOjP50|gCnK@AzPiKHxa5+#4(0=cNWFG>Q=6_KYxJHx#PDi8$$iTe?c&ER$rqb3d zcu=8vEdqp2Pr~Y;ePsl4<3*K11EtI6>q$90NKjM;>n^NverwIH0VJg1_&hWfWHulf zL33cH0D5{Fm_?VTrU2<^SrB@Pjuy;rK&G86qBjQ=Qm-m`Bj;xe@IL3oA9f%)KeOPh zp(|*61p};Nk?J5N`Zr*H0$?PZ=j6)hp|R2vg1AjTR!>?@xf0cC?cvDOR(MHaFrN)PCMApwjj)&14vl;Q2vg{(qvjjmuV&<)9U9eCu<(rukCMJ4dCu%Gc>1Yr6ew7=`*qv#Z9jzNn%BWI2~p zHZp%?eXe%?bzQ%Hh$h|xoX7w~{+C7`Eqma> z;uTz;O(JVo`@#$MqicW}Ser&2Z#fL93A9>3qkB)z4(Ah1z6sokNF^bG#Q&QO3|@^))M;ugTY*!QTaNB|i}eTa1j&17#Et zD4U>d7SMYDMCm7E=44wZd8Uq-WdQW_69KmXzN!BWoRT?4pV2-KJ8l83HNOVv_+;lJ zRC>q$qqV?DmcmoAlP2>P_lsl!CkE$V}&J{Mk0?)lp&(E z4vBtiZ)9I;O}{3EfO!T@iIFq|KCJXa#Z08v9n_w{6;!LwSvXP=1uTWjs< z8B0@me2!8Nl`oB%HBlLNXMifG9H<0HKMz6t9Rif*e^3>;N0b3hN2CYHz`O-m+24B? zX{!nvRA^m`0HM>9usUcT8G+PzQKis88M5hqTn-Hq6f=W$7gjmH_pbK<9Nchp9a;)9 z2jGk#IWW@z-MtJ{G31FQfIFHugqEV-hS>w~vXe#h=756hHA|l7{A>e0);#FL0i@<< zHmn+Y25qlkfK@CqJ4jpm9Wd_z@PyNvoY`8*uuK4WYdxb%*cAX4Bt2)`rUu|^AXlo=iMLkokoi1M2O4M@=WEYaV#W6W z9NYe1fQCv|MQ$Yg^Nf4-%Kjk2R24kYMnwAg4$#Vulv$i&CEG~Z7;z42KkF7S)AkSK zj-GjU0<_@PGD-^+`b+~42&p7R%d8cwv&L#Uv{7s;DxNK9RT>~puOK%TJp43%G&J?*dMXqgIoK8mg?*V3kXAjzcjL+7Z$a|j2 ztGa9t8k_72f=fz(MvKTgsS%%` zt_}jJ#vBIAq9zK^>(e>lw3L2JPH zT2AK?JtWMs&#qEj>H)@qSHm-?WOT{-^qJLtM|4HuazI7gI z;7s$FDOJ0CadZ!miRdyVI!s~NKw=dBoXX^Lq<~!Mi{AU$3=lD7K)0{+`La&dgKq^W6^K9q8UcbLs`zQuz&Hkm4N6UOZfA2M1 zNFSlCmE_Fwg%!BvTL5yP-H3F>+FaiFWo2p4REG%rqP~!1db*ds3ET-UL-{*TM=wZt zKY-|Gl@#xpwD%65q;3IR(UtHrRC-6}SefmidOk0Bw4k2ld}RMH18`+m!^+SBNv}xb z=K%>CsZ=1DuI;mm0l2L~41gYNH;|gkpCRB5MQV3e_Rx=(or?1{_T$fB2B1ow3`(P8 z%s5_?jTjbX4C~!dJpiKgX~^nIoryGoS|)4nB06N9k7j>s00ZzteKMpPK7xSHvp%Em z5M5R>K=0jVfPug0e2Od1AMFJ^SsEj#e-AK#9KLc@Isw%>x(X2nh=8vB2SGc>cI6W{ QG5`Po07*qoM6N<$f)%Lgd;kCd literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Painting_128x64/frame_10.png b/assets/dolphin/external/L1_Painting_128x64/frame_10.png new file mode 100644 index 0000000000000000000000000000000000000000..ae3148c323e53a435cb5d395a91dde8fca4fd0c7 GIT binary patch literal 1606 zcmV-M2D$l(P)4I23Q=PH;3&4*c%d zbzLoi-g}EaI`;n7`L(t7W_QFF9FNpf37lZOZ&)?EW|0bj4AT>g_YG?`eih(mt7Ov| zYzu%BjQ0&|HGUP~W@|Nm72sxTHTsnZD>NF1;Ga^GLU>U~gTzyARVz5bc@@sL{1Ua@pk=-Xc!?B7Jmair z5LlJOgC!3Vgc|LY8H~`ebhGg0$EH*O?s(1s?LAaT51N8A2AZXT1n)}vo_!*qd!L@M zxB!x%QKpvg2n}!bXxzO5%!1N^TEKW%y7my+PnFa9T!ar&0Zs=p17u*H0<0`YT0t~e zD9y}_5kaJ!o`lsw`#=aWX zM5ep8fmsZCVhf-K%{p+_`2*EXLQf7+a2c!UdCs5lO>5-40Br!v7{Ai6RGx}JSWy91 zRgvl-ZS6f^{sO=vjxa_9MtV38f{GacH7dgM#9)>5E&V|VYFOkXG4{`9Le{9#91`V) zel}3~9zf#Pd$0XA&~uO^Je~MaUKwPJI+J-b?@v}a|7E_gUIb|bJV6l}*&#+lg`xozg_czkB;~El(u}oo(34;Vs}-n`0bUJ&2+-q> zmH7xA>sg70tg4{#&&Y}T zCUgv}#H_K$(3q_lQKL%qciD*Yt9}o_0sSbnGE*{V_zbwwDlOK$+IT9^M_ zfR-K9NL5FzksX?$qvvUEXi68I9k{`@eUdI#5R|P=`AwjPY(=onuoZ~B4AGoumHZ4p zWMA}@vT68&_|}~=y^Lj$Z0g` zJ+8;D3b5^O12tr-O4RY%;K%#C*FG}fQ@b=!TEV!h0&M&L0W?UxSOYRgM4T_tLt241 zOmf;1Pd;o)*Gp%Bkfw@qRj|sWw9iA-`H?b?g4>5*qeJHMwSlJqM2X$tJvHk5S$(9o zLhnm^t!qU7=@~#OMb>&EMxPx(j8FlD9^b>O5T8pj>v%r0KC=u?fF#SObm2jO&JweF zWP|#7N9S`nX3O9TRJRp0^P{*;1_i?K8M_=dteW0ROQW{mdRoQ7} z*?-~lU*@%a1`^0Hp2U|%f3^Z-`u0l9>Ve?68GtImolE(1Wh>jjj6o$xGfU)|9lklP z0=x+*ID-emtn)c-S`ql?l5A8KO-BA4cUrX`5%k{w|0*~WWxGqI#yb-EvqqDG-nth_ z627M@C_J7N*LB_J6d(<3*Y)~i5^YA9)n&HNYBas;N}s6qxl0A$kcK~R?+nnkAXNuW zOM8B%9auCaKo#I5c$x1#hrk;^=|29sH)m*csw#lTD|`x|%AXPpNZ;cbOz(N%&Xann zCQJ05MsIb3hAKc*a0Ih-k<6$2v{}NEkM6NbJ1V2bqssXa{%!@}!d1a5le3_*OZ26R zK=(_I*0Nc1KqS*8nO9bTw`$&u_W+ilD~7;mAVf>Qm#tC5A%kYrF4n1q^Nc#*n=_<) z>URND`CYiK%XLcD`INqu{d6C~)0-2l{nBR@(|-hyQ=CsRGt@*4!ktX-OaDd?31kbi z+c}vd7V8FvmI!G!UAIStM2vAhp zg9hEK^R0^TJT3DjM0;JNFKVAh0>v8KR@iM-1)$IT1OFokWDyH7p8x;=07*qoM6N<$ Ef;{l{egFUf literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Painting_128x64/frame_11.png b/assets/dolphin/external/L1_Painting_128x64/frame_11.png new file mode 100644 index 0000000000000000000000000000000000000000..89d003d071e2cc1f55f2c13f361641f996385ece GIT binary patch literal 1579 zcmV+`2Gse9P)7$I zpU>yBCD40s=+^P^ACbw1+6gv zQuSP6^l-}vDp&iN2aiE2Xm&M?XBZ&E!cYs~3Q_~2EdY65N(9uvCH?A7FC z2B5pQfmsZBVhf-S<%eb_{)(!jNic)x%>f11t4f|1{Om1k?D?S|4se0l%&T;;q*n$j z7+@8PR3~X`?+Not07Q87nt>`Fscb$V&ddOrs}p)=Rbo{pt7Pwz-v&kR}Oj- ztYozk74^mInFs?6p%NV-iDPAMgc|9737!*BTJ@5(3feiDD=Xp?iq{N~?Wb92ilTa; z?Zd zRsVaIKaJ9_vlBZqptM%YM#v1H%^{H+OY|K^_B}v319;B9sx?$#IE_}ST%5Iz0ScENSA^jp0`e-BWF%?W?aaR&-YXQU9ECvm8XHj}a z$>uPP=W< zhCFm{R{z**PW$)EpdHy(0nNbK;8)pF;g_r(nR9$M)MVdh>lEOQa z+m(6`uzL`ULUIJ+B*`u-L1ua{-y?lr84Jz!V`l&amAzWC$zz4yfB!lfk>lxD9TO|@ zZ}le7t4_CYzs;)qOVjX!W;dFaS5^Do}kQj@7ELvb|70S%X*xI~bt% zE;GOYI)trGl59m;10I?P1Cb%J0?}C@qC5TU?e75wuC9CuXiGq?)C2$k literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Painting_128x64/frame_2.png b/assets/dolphin/external/L1_Painting_128x64/frame_2.png new file mode 100644 index 0000000000000000000000000000000000000000..8bfe6b33c57afb008d06a5b1743c5bd6164d2522 GIT binary patch literal 1608 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Painting_128x64/frame_4.png b/assets/dolphin/external/L1_Painting_128x64/frame_4.png new file mode 100644 index 0000000000000000000000000000000000000000..d39cddea149d9f0f70b259df9151940b13736955 GIT binary patch literal 1600 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 Hu0mjfn3n@9 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Painting_128x64/frame_6.png b/assets/dolphin/external/L1_Painting_128x64/frame_6.png new file mode 100644 index 0000000000000000000000000000000000000000..3f492eab5e1f5c86ae35be949cb4af2cc7d78776 GIT binary patch literal 1588 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Painting_128x64/frame_8.png b/assets/dolphin/external/L1_Painting_128x64/frame_8.png new file mode 100644 index 0000000000000000000000000000000000000000..a44a7315da3d4a550f3453dd8d221dd9131bee83 GIT binary patch literal 1623 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 M07*qoM6N<$g29Fd+5i9m literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Painting_128x64/meta.txt b/assets/dolphin/external/L1_Painting_128x64/meta.txt new file mode 100644 index 00000000000..6964b479bd6 --- /dev/null +++ b/assets/dolphin/external/L1_Painting_128x64/meta.txt @@ -0,0 +1,32 @@ +Filetype: Flipper Animation +Version: 1 + +Width: 128 +Height: 64 +Passive frames: 9 +Active frames: 13 +Frames order: 0 1 2 3 4 5 2 3 4 10 6 7 8 7 8 7 8 7 8 9 10 11 +Active cycles: 1 +Frame rate: 2 +Duration: 3600 +Active cooldown: 7 + +Bubble slots: 1 + +Slot: 0 +X: 57 +Y: 24 +Text: No mistakes, +AlignH: Left +AlignV: Center +StartFrame: 11 +EndFrame: 14 + +Slot: 0 +X: 57 +Y: 21 +Text: only happy\n accidents +AlignH: Left +AlignV: Center +StartFrame: 15 +EndFrame: 18 \ No newline at end of file diff --git a/assets/dolphin/external/manifest.txt b/assets/dolphin/external/manifest.txt index 197060672e2..6bf6957c38b 100644 --- a/assets/dolphin/external/manifest.txt +++ b/assets/dolphin/external/manifest.txt @@ -85,6 +85,13 @@ Min level: 1 Max level: 3 Weight: 3 +Name: L1_Painting_128x64 +Min butthurt: 0 +Max butthurt: 7 +Min level: 1 +Max level: 3 +Weight: 6 + Name: L3_Hijack_radio_128x64 Min butthurt: 0 Max butthurt: 8 diff --git a/assets/resources/dolphin/L1_Painting_128x64/frame_0.bm b/assets/resources/dolphin/L1_Painting_128x64/frame_0.bm new file mode 100644 index 0000000000000000000000000000000000000000..2694219ef9eefda1c8d52e3ce99cd1284d0d8d31 GIT binary patch literal 763 zcmVrBnnlSRlM?kSPVi2@upQgaA>f5hQr9 zSqB0O5bPdE05MQ_z-+K%nDOE|fOpudH`hRdR4*Wh)9!NbZ zIh_adpMUy@#sNqNo&#Y~)W$gb{ZIfw<%qwoMxiOILU335r~rTTb_l@W@(qL~OfVgI zeCQ0(z(yk#h%A5}HsJXK%7K8eLA8=uh6f3v53GX$#DLYLaheS-${$vcoX1}goXBPx zfcn5D6RZVhBl(EHZ3Xi-3W>5=-f&+BIKrB^N0FX!kSqp_gX>^~C7P% z5DX>*Z>qx=#&i!H0)Su~h%)?Z6x76{hvh|tRbb%t t!9;K;@{=FN1`k*l`~Wxltd|IW5PF(K20Q@x(4WHt2Oa_t4=MnBbU+(OFu?!- literal 0 HcmV?d00001 diff --git a/assets/resources/dolphin/L1_Painting_128x64/frame_1.bm b/assets/resources/dolphin/L1_Painting_128x64/frame_1.bm new file mode 100644 index 0000000000000000000000000000000000000000..3c9623d4c79a19e74bfe403f8819221e10322d62 GIT binary patch literal 764 zcmVrBnnlSRlM?kSPVi2@upQgaA>f5hQr9 zSqB0O5bPdE05MQ_z-+K%nDOE|fOpudH`hRdR4*Wh)9!NbZ zIhhCZpMUy@#sNqNo&#Y~l*Tyw{ZIfw<%qwoMxiOILNHhQr~rTTb_l@W@(qM0fI1y` ze8A)2z(yk#h$uX*!SV-{0|8)zYb3Y~1`|XdSq1}%0jo&xGzwjmKCK}+j=m#7kjym! z^?*z#SPINX!x4bm3+8MX2Lcb^*g$j`sD{KoTB}mbpce}S2VsPOSgNW4AdmpFoCpp= zlK{|ap->H1s2+Iy1O_n)fLJQO(D?O1#el=WAajU_e4xSg_)+H&Ys7>;P*?zcKhxSI zGx!D&`XEGHG4|NPF3j?jV2LDx(;Sa(OSRfd<0r8I}kAEZeiLFwq0vN0iUOd2f&LEKuLdZY`8i5i< z8v%oO0pr*_kN{$!@qoc#5PSy~Bd7)KEAWA;h5;di+#nj8@I05|qab+9VBk@$&;A4gii65A0QiH;RX?~G2Yw$Y3?v?W z3f^Wx{O8~PqA`FZL>^KNbw*Pda1O3<3vNf`iK3A0T-E2Y~?9$#579CWt@GV27^@! zWC(ox191n5U`V6=53lfSAn|+%d|<)!;BUj?(;)dlU;*>^LG^3#6A*py6WYC2(gqmt zec&E3_~dXreF&6b*bP+Z9#PN#RslkkP%eS-yj5nP^0+}Ya0+Ci0k98@5I&p;7yJJ+ zm`5rB@qtxms`G%uiK7^ihsp!0mn+Tzb2prd zf#MJ4CW20Z<$#35A@S%p^W2$+0l7vTBA0pp;6LMRac`2Roj0Qq47 z<52KG_y_TUf4}oE`B;O4D2PEY3-N=OM*bfd6h;v+jzS^$500-KFCX}Pagq23LI;E~ z41T|V`~$|r75G5M!vNw(I1n9W_#R8~QII@@g8;5i{saPDhyZQ?^wdx81_9rP$^$6} zrGGOZ{&VmDQ5e7_0Q10VDl(YIAAhO<2t2tH2nB+%DXT&-SNf;`fAV%{i-kTgngHl^ z;q#CvL>@58t@94-g323+F!{kVJJ*7zQdKu@9E2)DJtwR311;1&XSm8m-hGd*Jy}AQ}x+DgYl$ z1zbKzVX#$yq46+?0Dc<1R1HQU@`DH0{vQ}Qpo8NDfCtZkAJ%H2p<)lj0=ZT6j0QX( zZ~*=_|NQSCLPZ!h16Aq=ox}O~xXLIOK=`5ust-7XBjNazz%~K#f(O$94}^aw^El-| zJ}@N^tBwhb@xbHKF!?}rTcCMEpkK&@5(xkglm@Gv7Z?5m3h==2m<_(G3||~@JY(<( z4-^U^@vK#Fw+gNtJT?pj0&))+H2{7zXhy3BFDeEByfFI^{HU;wt^}S`6M*0$0f7&U xnhEL^hyY~V355d@|Nr0WvRoz9R8R#_@F*NdIS0mtItQFUqBt8E7-#|U(EtcKLwx`M literal 0 HcmV?d00001 diff --git a/assets/resources/dolphin/L1_Painting_128x64/frame_2.bm b/assets/resources/dolphin/L1_Painting_128x64/frame_2.bm new file mode 100644 index 0000000000000000000000000000000000000000..13916806fd62d4aed731cb58265eab34adb4b755 GIT binary patch literal 762 zcmVrBnnlSRlM?kSPVi2@upQgaA>f5hQr9 zSqB0O5bPdE05MQ_z-+K%nDOE|fOpudH`hRdR4*Wh)9!NbZ zIhhCZpMUy@#sNqNo&#Y~l*Tyw{ZIfw<%qwoMxiOILNHhQr~rTTb_l@W@(qM0fI1y` ze8A)2z(yk#h$uX*!SV-{0|8)zYb3Y~1`|XdSq1}%0jo&xGzwjmKCK}+j=m!Skjym! z^?*z#SPIBIKqF``nXq6S2tR{i0f1tn8xZ+wtxGb1Tr2?b!ayulRREAk09noeJW~Up z)k2^euTVVk_yBmqAQlR*^gcaMabPg;2pX6}3?1VOfZOV@#qpg3 z%71{MVLr?8tW|Kg3aoSm5U~%+iwLU0$L9u+&*K;q s`ALuC0|%TV!$ALk|M&W=mk53o`bKaNGbsngg#Hjg6b26fD;RuqKoWI0G5`Po literal 0 HcmV?d00001 diff --git a/assets/resources/dolphin/L1_Painting_128x64/frame_3.bm b/assets/resources/dolphin/L1_Painting_128x64/frame_3.bm new file mode 100644 index 0000000000000000000000000000000000000000..751fbc3efb282916cc38d5999018e804cd4c0a37 GIT binary patch literal 759 zcmVrBnnlSRlM?kSPVi2@upQgaA>f5hQr9 zSqB0O5bPdE05MQ_z-+K%nDOE|fOpudH`hRdR4*Wh)9!NbZ zIhhCZpMUy@#sNqNo&#Y~l*Tyw{ZIfw<%qwoMxiOILNHhQr~rTTb_l@W@(qM0fI1y` ze8A)2z(yk#h$uX*!SV-{0|8)zYb3Y~1`|XdSq1}%0jo&xGzwjmKCK}+j=m!Skjym! z^?*z#SPIBIKqF``nXq6S2tR{i0f1tn8xZ+wtxGb1Tr2?b!ayulRREAk09noeJW~Up z)k2^euTVVk_yBmqAQlR*^gcaMabPg;2pX6}ciuJ510UA7%7K_Pe8f{#_%5zxU5Qv;P^?P8vyvh z1L6-k1mfXnnnx-D@qq`{!2pj*Ba$%rKu_WiItKznA~lG7pfy{lJfqwRg#v?zz-{$d zV))L1PFMTAvg;PWVy pM*@E-G5la)^`3vg1AnT?aEIXstOyKT0Qk_K!vhDPSm*)q(Ew_YJf;8u literal 0 HcmV?d00001 diff --git a/assets/resources/dolphin/L1_Painting_128x64/frame_4.bm b/assets/resources/dolphin/L1_Painting_128x64/frame_4.bm new file mode 100644 index 0000000000000000000000000000000000000000..c1135b467cc5fe2e313984e30a04ded3dd814e7d GIT binary patch literal 759 zcmVrBnnlSRlM?kSPVi2@upQgaA>f5hQr9 zSqB0O5bPdE05MQ_z-+K%nDOE|fOpudH`hRdR4*Wh)9!NbZ zIhhCZpMUy@#sNqNo&#Y~l*Tyw{ZIfw<%qwoMxiOILNHhQr~rTTb_l@W@(qM0fI1y` ze8A)2z(yk#h$uX*!SV-{0|8)zYb3Y~1`|XdSq1}%0jo&xGzwjmKCK}+j=m!Skjym! z^?*z#SPIBIKqF``nXq6S2tR{i0f1tn8xZ+wtxGb1Tr2?b!ayulRREAk09noi2jNM8 zXf;r%2CLK$JbnTL_=UhM6<_FldZ6OKVc-xyq)a|gVEX(h^N2T!P9G>N06w4T?GhRB z4TyaZA}$#FY;hNeBzQj%2*wJrDB@^!Ve!BR%m6VA6!J+bpj`vwcn^vsRwYE?)1`0? zfP7$q@duoPae(;DW0e5-z=P{xYK{gmBM+1W{vh(cP$mHJf8L-2#t(j-z3jS2iPFmd1^0P>&*$3*xWIm`e6 literal 0 HcmV?d00001 diff --git a/assets/resources/dolphin/L1_Painting_128x64/frame_5.bm b/assets/resources/dolphin/L1_Painting_128x64/frame_5.bm new file mode 100644 index 0000000000000000000000000000000000000000..a4681af98e85a780faca27f632565d56a90abee8 GIT binary patch literal 757 zcmVB0PzBVP#_wh1V*4h^Q%=r4;6re#y}sidI#q!KOp_)f#@Ib|JA;~|NVSF zaiA#b1NRjIjbK0F{}1|oq7Vqjg1#Sx_{cBtNru2bFs=pzg&nX(e-Qe|)~{6o3|1Ks zpV(mf2-Q+6sFiCqKp~AmBoql03&9`$N9z+>rBnnlSRlM?kSPVi2@upQgaA>f5hQr9 zSqB0O5bPdE05MQ_z-+K%nDOE|fONp1LOSv&;#X!fI=t` z5Wt_t1^)lc!{uTJj)MUKz%RxQT^sm(U{M%EBB20>U_3g!aJ+xv@y18T<2XbV?!ABf z1IEJ@_(0&F2sk{U`pfV8P)bMOCA7{DnI3j{I&fY?-JF^)d}Q~(fpOcVuBl+~daEB#aeKl(capz;lbCV)B} zcznR(Ef~aNu>}W}xIRGiutBwwTm}OPq7T0k16GmXXcW6BeW3?2*TiTt8HS)f(+SoB zvk~ybU^asJ-@)U+gZMTO9R?~Pu@9E2)UzlB!odO9VIUSNs(?r&04(PM1CXS^G#aQ> z16Aq=o<9Krj6zofRsM&^stzm$9svWKL__5U53jfcU^cr6ZnJ5c|hPO1_=NUlm@GH2bT|nFn~aCm<_(G z3||@0yz$V81RMl4imn#nRgQztV0airGJroCG$U0G2b;Kj5epFfsIZEx93Ef+$V6}_ n@{=FN1`k+A`~Wxltd|IW5PHD?#lR1Z3H&fHdP0HaKo5?HvfnzS literal 0 HcmV?d00001 diff --git a/assets/resources/dolphin/L1_Painting_128x64/frame_6.bm b/assets/resources/dolphin/L1_Painting_128x64/frame_6.bm new file mode 100644 index 0000000000000000000000000000000000000000..36f2d084fe3277d555dda68b3a9675916ce237c3 GIT binary patch literal 785 zcmV+s1Md6*01X3xP#_wh1V*4h^Q%=r4;6re#y}sidI#q!KOp_)f#@Ib|JA;~|NVSF zaiA#b1NRjIjbK0F{}1|oq7Vr#7-N!1m0(_h@t{H>K=F(N$51I5i%Xy{O8~P zqA?GT_#QAVR;r^ZjB)qiVbxF|ANYBQYV}!7S`mW3)j$J(fPY{Z2nMfJ2@DJeULQue zU;^U>2bH)!Kw*qG$AA|ZE&~CC(Fe&^Fo*;~z!w=F27ybmhsbsy@q}yw;}L+!W*UI` z1H^7HYQS7U;sF~#e81rF!(erRae%-vQ4NTEy+HB2U@F0J2aXZ}VydVIst*_k0PsNZ zq(C$ps8j$DK=H4J2(Ct9{)faKRsi_Nuo3uxe4xSfoL9cEIP}6mA12U;&V96I0|$?S0TQeMhJ65_C&nR!3=9In;187I{2w?*5(45cgiJm^&;0;CSWF}W@c@W^ z1Lgkz%){kk#y~K_LH;ks4qY4gd|*a_fIwhSPxE|n!twuy#~B~EKrb*MSii>q_y>)K zEAWBQFdxVy0{$Nx@I05|TN(xf%#jdy)+hf00YyRUfS-rPe{e7k{61712lyT#p|9p- zAI^RM>IfPH1OE@;YxP>HjHWTi-+_lz4JrmYCUU%fg3b0>gwY4f01qf9^08Hs;Aj-PD13q902~~DQL2c* zWHSILht2`PPZd-iAQ7|&%l;1&0Khexs0;%Y5ZH&y)Gr_a)vBQJ!ayulRRHxt0stNc ztUgqT27^@!fNHQH^1Kbi9wu?HRez!J2cGzDAn}L?$_yVK2qb(9@#>YvKp!Y91FOM< zz(37_pWpz(A82F)@qg-r2KfMw2hOWOXO07ZKAe0w9DNCtVAu^+4mcYh1P+Y|z{+SB zK=_(R2ObBF0yrfY46XsN4~!F3%oKS@r``wz$~jOEj0>v+pM%E$BPaxFMjt2+st0Tb zmjVU^64-~z1J!|Y0p&4*cpx}j2H#bNz-P#_wh1V*4h^Q%=r4;6re#y}sidI#q!KOp_)f#@Ib|JA;~|NVSF zaiA#b1NRjIjbK0F{}0FF1|O98#2^spFn(9#Aiu!jf%=7TI}jK^e18!7$JVb^0Ss0d z5cl9e`uNpSE2x!gH9#SaK_nN81P}2Q{zvN*TBTG3F<2)8VbFkJBXCHDp=2NejXI17Q$+fWtn3P!r=2!UhHbV7^4d<2b(u&Jl!wgU5l6hzG~{|DXrU350-N0pkNN zj0^q$nTN{7jDTUFJO?1wAm!1&hsFgFtUw?zDFgxqsK*P({vRAVa1LJ-NlKe|!K)`vDBM5<3C;tKgMM3QZ4~+icU>*2;s5lSsJWYdN z%*a2S`~TENGzbU&9%3C{s-r24argS50E5T-0Kh;h^;u0?5rV(fKm&hco&Y{Dkifuo z;q;6*$AA|YC_Jsf@>Pr?0TA#7#!G;}VKhPV9f&+(7XY}(@H7ftls-W56O0>h7Z{8N zLon0_(ZE%}xP!z3Hh}qm!Q*&5czt6;s6@>TtVZ6fLN-k0qTMV0l+*E z7--To62B0rGm~YybuYLa-bg=7gh&92aW(nPzcnFK2RN04%iPa1Pllz zu@967s{-T$%3}oZKybJXzN-y@^+B=?5`@Ac7$x}DD!5yR*NqVslrCWq;X^0`@jDHw z4I)wu0u?5RSZqV`qQWk)N+Q6a(jNrD&>``YK_^g3MED**LxBE2|NH$`ON5@mG8_n=;>2_ literal 0 HcmV?d00001 diff --git a/assets/resources/dolphin/L1_Painting_128x64/frame_9.bm b/assets/resources/dolphin/L1_Painting_128x64/frame_9.bm new file mode 100644 index 0000000000000000000000000000000000000000..99ed507179a3dc7a64a99f871d18c55796a97cb1 GIT binary patch literal 777 zcmV+k1NQs@00jepP#_wh1V*4h^Q%=r4;6re#y}sidI#q!KOp_)f#@Ib|JA;~|NVSF zaiA#b1NRjIjbK0F{}0djKPmBu1I7ne55oLp7ysl0KTxg)0_;C8#6Ge0tJOe56^2A7 z@;C?jH&lx1C0fl;2xCx51p-X~fkpq3`oz|$RRIiE2rnAoJY$eZhM{C20*ye4BWHj> zZUFJ@9!LN&P$bQ$M&E2Yw$Y3=|%4 ziq2+1{O8~PqA`F$0pkY(4!)x)jB)q+pa6n~0WlJc3acrrLNHhQr~rTA!G}OF7#&@`ss42EH- z53BqiZEy(eDD+a z33bwjuI>)nnlDkWeER*d`kRx77g0 zRTmbF4nZJ-SYVLI1!Aj(xOHM{frdmRAB+SJ5kMb}8WHMI|3CNotd|LO2b2N}C@_p71Q!ta(5F)Xc|fHc0C>Pq H!XF(FZihX= literal 0 HcmV?d00001 diff --git a/assets/resources/dolphin/L1_Painting_128x64/meta.txt b/assets/resources/dolphin/L1_Painting_128x64/meta.txt new file mode 100644 index 00000000000..e5f5fc0a645 --- /dev/null +++ b/assets/resources/dolphin/L1_Painting_128x64/meta.txt @@ -0,0 +1,32 @@ +Filetype: Flipper Animation +Version: 1 + +Width: 128 +Height: 64 +Passive frames: 9 +Active frames: 13 +Frames order: 0 1 2 3 4 5 2 3 4 10 6 7 8 7 8 7 8 7 8 9 10 11 +Active cycles: 1 +Frame rate: 2 +Duration: 3600 +Active cooldown: 7 + +Bubble slots: 1 + +Slot: 0 +X: 57 +Y: 24 +Text: No mistakes, +AlignH: Left +AlignV: Center +StartFrame: 11 +EndFrame: 14 + +Slot: 0 +X: 57 +Y: 21 +Text: only happy\n accidents +AlignH: Left +AlignV: Center +StartFrame: 15 +EndFrame: 18 diff --git a/assets/resources/dolphin/manifest.txt b/assets/resources/dolphin/manifest.txt index 197060672e2..6bf6957c38b 100644 --- a/assets/resources/dolphin/manifest.txt +++ b/assets/resources/dolphin/manifest.txt @@ -85,6 +85,13 @@ Min level: 1 Max level: 3 Weight: 3 +Name: L1_Painting_128x64 +Min butthurt: 0 +Max butthurt: 7 +Min level: 1 +Max level: 3 +Weight: 6 + Name: L3_Hijack_radio_128x64 Min butthurt: 0 Max butthurt: 8 From afff1adf8fc09c37703b8a79a2c97c97bf675501 Mon Sep 17 00:00:00 2001 From: gornekich Date: Wed, 12 Oct 2022 20:48:13 +0500 Subject: [PATCH 136/824] [FL-2882] BLE tiktok controller (#1859) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * bt hid: introduce tiktok controller * assets: add bt tiktok assets * bt hid: finish tiktok draw * bt hid: add input process to tiktok view * bt hid: add tiktok swipe emulation * bt hid: fix exit from tiktok controller * ble hid: add delay to emulate double tap * ble hid: change options order * bt hid: build as external application * ble hid: fix naming Co-authored-by: あく --- applications/plugins/application.fam | 1 - .../plugins/bt_hid_app/application.fam | 11 +- .../plugins/bt_hid_app/assets/Arr_dwn_7x9.png | Bin 0 -> 3602 bytes .../plugins/bt_hid_app/assets/Arr_up_7x9.png | Bin 0 -> 3605 bytes .../bt_hid_app/assets/Ble_connected_15x15.png | Bin 0 -> 3634 bytes .../assets/Ble_disconnected_15x15.png | Bin 0 -> 3632 bytes .../bt_hid_app/assets/Button_18x18.png | Bin 0 -> 3609 bytes .../bt_hid_app/assets/Circles_47x47.png | Bin 0 -> 3712 bytes .../bt_hid_app/assets/Left_mouse_icon_9x9.png | Bin 0 -> 3622 bytes .../bt_hid_app/assets/Like_def_11x9.png | Bin 0 -> 3616 bytes .../bt_hid_app/assets/Like_pressed_17x17.png | Bin 0 -> 3643 bytes .../assets/Ok_btn_pressed_13x13.png | Bin 0 -> 3625 bytes .../assets/Pressed_Button_13x13.png | Bin 0 -> 3606 bytes .../assets/Right_mouse_icon_9x9.png | Bin 0 -> 3622 bytes .../plugins/bt_hid_app/assets/Space_65x18.png | Bin 0 -> 3619 bytes .../plugins/bt_hid_app/assets/Voldwn_6x6.png | Bin 0 -> 3593 bytes .../plugins/bt_hid_app/assets/Volup_8x6.png | Bin 0 -> 3595 bytes applications/plugins/bt_hid_app/bt_hid.c | 16 ++ applications/plugins/bt_hid_app/bt_hid.h | 3 + .../bt_hid_app/views/bt_hid_keyboard.c | 2 + .../plugins/bt_hid_app/views/bt_hid_keynote.c | 2 + .../plugins/bt_hid_app/views/bt_hid_media.c | 2 + .../plugins/bt_hid_app/views/bt_hid_mouse.c | 2 + .../plugins/bt_hid_app/views/bt_hid_tiktok.c | 207 ++++++++++++++++++ .../plugins/bt_hid_app/views/bt_hid_tiktok.h | 13 ++ 25 files changed, 250 insertions(+), 9 deletions(-) create mode 100644 applications/plugins/bt_hid_app/assets/Arr_dwn_7x9.png create mode 100644 applications/plugins/bt_hid_app/assets/Arr_up_7x9.png create mode 100644 applications/plugins/bt_hid_app/assets/Ble_connected_15x15.png create mode 100644 applications/plugins/bt_hid_app/assets/Ble_disconnected_15x15.png create mode 100644 applications/plugins/bt_hid_app/assets/Button_18x18.png create mode 100644 applications/plugins/bt_hid_app/assets/Circles_47x47.png create mode 100644 applications/plugins/bt_hid_app/assets/Left_mouse_icon_9x9.png create mode 100644 applications/plugins/bt_hid_app/assets/Like_def_11x9.png create mode 100644 applications/plugins/bt_hid_app/assets/Like_pressed_17x17.png create mode 100644 applications/plugins/bt_hid_app/assets/Ok_btn_pressed_13x13.png create mode 100644 applications/plugins/bt_hid_app/assets/Pressed_Button_13x13.png create mode 100644 applications/plugins/bt_hid_app/assets/Right_mouse_icon_9x9.png create mode 100644 applications/plugins/bt_hid_app/assets/Space_65x18.png create mode 100644 applications/plugins/bt_hid_app/assets/Voldwn_6x6.png create mode 100644 applications/plugins/bt_hid_app/assets/Volup_8x6.png create mode 100644 applications/plugins/bt_hid_app/views/bt_hid_tiktok.c create mode 100644 applications/plugins/bt_hid_app/views/bt_hid_tiktok.h diff --git a/applications/plugins/application.fam b/applications/plugins/application.fam index c88f6d2890c..6d25e45aae7 100644 --- a/applications/plugins/application.fam +++ b/applications/plugins/application.fam @@ -5,6 +5,5 @@ App( provides=[ "music_player", "snake_game", - "bt_hid", ], ) diff --git a/applications/plugins/bt_hid_app/application.fam b/applications/plugins/bt_hid_app/application.fam index e6a3b1752d4..2712fded721 100644 --- a/applications/plugins/bt_hid_app/application.fam +++ b/applications/plugins/bt_hid_app/application.fam @@ -1,15 +1,10 @@ App( appid="bt_hid", name="Bluetooth Remote", - apptype=FlipperAppType.PLUGIN, + apptype=FlipperAppType.EXTERNAL, entry_point="bt_hid_app", stack_size=1 * 1024, - cdefines=["APP_BLE_HID"], - requires=[ - "bt", - "gui", - ], - order=10, - fap_icon="bt_remote_10px.png", fap_category="Tools", + fap_icon="bt_remote_10px.png", + fap_icon_assets="assets", ) diff --git a/applications/plugins/bt_hid_app/assets/Arr_dwn_7x9.png b/applications/plugins/bt_hid_app/assets/Arr_dwn_7x9.png new file mode 100644 index 0000000000000000000000000000000000000000..d4034efc432b102f6f751d001dc6891b0763c55c GIT binary patch literal 3602 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 JTZ;Aw|2NmVF(Lo} literal 0 HcmV?d00001 diff --git a/applications/plugins/bt_hid_app/assets/Arr_up_7x9.png b/applications/plugins/bt_hid_app/assets/Arr_up_7x9.png new file mode 100644 index 0000000000000000000000000000000000000000..28b4236a292708b412629ffafe30f6d011491505 GIT binary patch literal 3605 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 literal 0 HcmV?d00001 diff --git a/applications/plugins/bt_hid_app/assets/Circles_47x47.png b/applications/plugins/bt_hid_app/assets/Circles_47x47.png new file mode 100644 index 0000000000000000000000000000000000000000..6a16ebf7bbe999fa143c683bef713a6e2f466cbb GIT binary patch literal 3712 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 biU<_Yn+9Io=xs3K`2!qm58ISjpSt)z2v?8| literal 0 HcmV?d00001 diff --git a/applications/plugins/bt_hid_app/assets/Left_mouse_icon_9x9.png b/applications/plugins/bt_hid_app/assets/Left_mouse_icon_9x9.png new file mode 100644 index 0000000000000000000000000000000000000000..c533d85729f9b778cba33227141c90dea298f5e3 GIT binary patch literal 3622 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 literal 0 HcmV?d00001 diff --git a/applications/plugins/bt_hid_app/assets/Like_def_11x9.png b/applications/plugins/bt_hid_app/assets/Like_def_11x9.png new file mode 100644 index 0000000000000000000000000000000000000000..555bea3d4b0239ae1be630d0306316d3e9494c4c GIT binary patch literal 3616 zcmaJ@XH-+!7QP75n@AB6Cj_Jk389)uC`sr|AV?4kfrJn-g%Axzks?hP5RonjD!r(n zC{1ZnL_k1#lP01AyrBpq0x!&r^WKl=yX)S2&e>~!-~M*FYu)Hmwq`>7hxq{j5VA1G zIIvd%_QS`^$$s}$EB*oi{3c{H`jiD44Wct>p5#kJ0Pq{hbR=ON7bKAz6Kg1|sNg$R zGzSS@kOL|vSUf>dRgO>8GD{s3abXXl zZob)?3Vh%_P`mN5bLZKh!FYeg!%p z%3DE@^WB!`05*g4^^b$=d0qk>etiPGK)p>yy~dHqU6IeIw6h$+H#q8<2`8+0gT(=( zfH+hhU}VY>oSCZV2xM~sZXF)(Gr%czz)k7;$37r9b2BZF18}_~C&7`O0Duk>qcDKi zNuZ?r^i2~0rvZq2S~bIgA$35*!r9Xtc>Elw?-CU#2Y3Ym4g08Y6@V)caBGv7_XBRE z0pg}B&icO}FB6?tWmhV#T)#>IZW7|ktM0?&>{+RSI8F|NM%37wqmnvoqISOg936DP~a5jvBP$aPUd) zV9L(@V@q6K=LNDaZ^U?(ix@ovvKL02SLu7TG0C}AH9R~wJ3D0AjB>@lalW=gYP?YI zynX49ApP$f>mOcDD}-pC3o+x`{LuJz%{uo;_ier#?qeV0&AvYu*!?cs2X3}-ufnN{ z&)AFk#9`87S2c6N(Wu)huaEWa5~e5Bwm1zYb%4hg4LAZ5)C0xB7ZqEF>VlR=Acms5+M*XKlJX+0{G$1Was3#}X_!2!jo`6dPi(3vqK3&3D6TR-y z{e;CO7GhG*r_04cf$&F-&2iQ^+adD;&=Cdg10#HTe4IDz8Ao20R;-2|>`Ur=nn)VW38z}AdQ~Ff z4S$kll46pKDim8-lvgxSB;d5_)PapJJnwj|%+yKCai);(eR8o=QRb;HjxvsS4N?=o%q=9TkPR)cO%h%c*5tH|VOTUWt|XT6J( zQ<8DT=Ee5KW?$-b%NFx9^Xg1$T(&}ljax01&MKLa;=A@|&N~h}j_32|OWGh2>t&E4 z?_8Oj8Vu_dHGe5J>*e|2ENfc+gn!-qwQQh5ze za+e}Ke_htJlvtN|t@_%p+ejXv$YJ4P*)y_1zE2tAh|`FP^sc*0hSy%NB`-ipxNgzz zA+4FpgB>c(OVMHVjG=ueG2bxBn28J$%ntrY-BL%@ zpa^nNe?+fZyV|e?;_33XAD4-WqE^PcF_n-nsa; z;?3wSy}Qfzb{EAO#injo=0;dKtIOg()|Fg@m+SlZkMhq*>^~lHn!7~*#m!1pO21w4 zqH{`FP@Q6cjd#fThBu)N&p5ol2srW2g4XeAsV&84v6ZuzbDKwAxN@-SeZOok66+8@ zaQuszaO*EGcQTh*>O#6gPQTu5nU<$x{AU+7_$D`w3L!?W#0Hj3@$~(2MV2HBy@*O* zNjJ@KOy6>KcdfR2YtS?Bc_QGu+2}7KceV9h{4H0p?c|Y#(7r^{N_T8#Qs%WF$RA^F zqxUNV=RLY6FN)BXt3{bpy(YUc^CxRhcAZ^$!CWaHojd6K!a4mB;sWI}^Rxa=VxL`W z&E1;xvZ}M*RZ9VN&jLL+7G$#Yy2jV){C}6+9q7-3BggAj185tsH`XU5$AcJ3+g%+s z!z`tx(ptOP3u{J;#>43G$bLiDow1?ivFjJ>S=p;SV`dxN;bGl73G4A9=>73&@f{ID z5nr-S7{KAvhK%in@A>F%Lbqa;)Xx2#jxs4pXwYW=m%*-{)SjG_m6XI+l&iVhpX+N!QZEys1E>~%495#iLH8tr1Qa3@5Avg2qWU8Ikl;Ug5$ye*843pd>B96zg8veQvpEGq(-=gM z9t5WDp`oDx(t|^Y1iYrZmM7jr4Wy}|34_Aex1Kso522}rfWbk3Uto4X2Eh~IfHD0$ z9Q%X>doh`G1Qg0*u^=oh2#rC4!r*W?R6`T0sj1HPQ1|txGVy-uRA2cY3>c!X2ZKy! zl4(@X9wXkJcA1F;v&H_E1%>_(E!Fq$O0jDO^~2MlFo?!pRzDnVZ2rG1h4PQLFVlhe zAHDyR*ca5>4|eZ7<@Z9-5oiVx&!jQ1G}@&fg*@d&W72%RXmpUK76b-T zw!wRlse2ZcKOr_Y2n(t&6HoOZT40c1HVK4GCLl06rdoP1-4j}96FnHr1akt7)DQm0= zd)?jL%^kis&fXojz!+owM%>*9Zf zDkw-(np6Qnkq**CWPm#qVWfRw?l|}RalL1qbKdveYd_C^b~$UEm=m^^V!{W70RRxg zSz#Tx>)zc*keB-wEiG>W0AX_~26F<3!GM@7h8Oh$836nT(;X=U$5|QF+UN?}Iy&Tz zHN!z#5afWq5h4|@qM;}xc|2P2!GN@V-ClEZKKYi+Xx`Y^kekx>nxfZ*`vs;HAI641 zioV{qF&^~D=VSHS=Z@_cea16|%juc>Lds2m-bEv|8;$Q9BY}(J7~SLay=Dvg40g3x-Gm zrh&2OY{1llCnP;t#SzHl1Kip@+$Vt(T7aAC)z9yNko5JGARfT=j-oVAW;_7ePmaa{ z-bO%S*U9VV08tx|^0ID(1N~ZnHqP103V2!$)OJdWlmLRFfVO>fggU?%1h};*Dft7} zQUEE7C1>OxM~fwAG`N*YDM3~!!_7lo1+{zyoSh+u)jDyqN2Lr%zmQT*A@u<%ayp@U z5}%ge0zhWGG&kGjE&opO;?7Qk*fQ~RT3=uD?||LiC%31&3Yew)7M}QD7+-+X~IEz(=5ZX#jngsy>n;EL{)J%S*?to@3 z|Dn1)!*wE?ZU)!T%8m7CNwlzM$RU=SdSMt^EwbaOf`%LPgQ0G+VS$ZAX2ozN0{)CbWQn2KD(gV!t`ioEk=!&2j9GSl9% zo*zWrGiD( zbUown?F%)p6*A!Cph2X=W>!QSqHVubF6fZ5-rhkWLm}R4_VudZgk0n{2uFH{_ZL+J>;XV1`J?$FPRma1gt)x3j#r8;oOB&0^MpPm7C7anpO|x$cckPQ zY zDtSwx>IN!5?*Sa6dtBGK)M5FKmx;h+vhVsmwyn^NT29h(@byutMfC}F`D{I#3K;pc zPkv%jBC)`#z`nq8uEwBvJ|{i9#=Od9BUIe1`MBz7RZB`-=brQ##{tKY9N`=pJPNT| z49WM&l7CQz<-DfnEF@>VIvbKliGB8QhAcrL~DAa!mpyJVvYZb zUr2SpS7fVa8`&7yG(iM@n@Q_S8!LA^<$p@EEVt|>8CNoOD%)kD ztePHi3ht6cbUJmW)S@W8=*Y*aqN<#|ITf}EwgnjHH!LL7BwVSy^4k_lKrCuNyg=cULa^U+mK5S7Vl=h$-h#=MH!F#=Pzte2 zva4TrvTT35dLuR6G3~u2MV3QBgQ(c9g<`WNt16HX{nhy&R+FBGalHpnx0mg zRzIIR^kl(cfw~YieE+T9ef10%UB7n?EtpUC)7>T__wQ=^j1>mkVeCRFFJ_dW9?*E_ zqQ0l)S)BYe(xR;KH)GcQN#jYR;i%52%el9PwdF14?RE`}jB^oVn5#-Vo;!g%-9S#r z5grO}OsH9?>n|JYftM9u$C@C9$lpo^=FM(qR+vef#f24xP1hAEdbj+3t4MKeCb=`d zlPVr@BKXV4cLJo(q#F&vqN)*55zdh&vCL@V!ERWRKBs#a<2Q!=j!ndlrcq#a@F!Zw z^)-z1A?J~UhLw7iCQT48m$$vdbRzD8^&vP!qu79c;nmpY{BqPp`h>`2kZdxv44KqRAes&eQ3DIV9e>Lgov(;bD5HF( zeD=E3UPz88*?vR6Q4T$PSD@9W^j6^>7cJp3boLj*DYZTgff5SY+3R&jOdCA0AmeDq z{M*vDp<9Oc7Vq!O@2lT8e!DCy(%M-|f%v(m@I1T(=^HR4JSn~BXyi%$LgdTqWg4_z zyMlS=q~hQjl|Z~t=-Ilqu(}sKK64^Y!qX8~=7#&`&)5;6E@Ll9-y_rIjiqC*7fTJv zCP`oIR~z=9mXBhzy-pdv^E|JhvBI;`y4b+rbFs0L&*xXa znGZpeI@E@$!pkrfk6t5RR+DpDJ3EX_2#*OXgzp4{g`SZYq`q}}_kw&-^*6oWdxu=B z*S3sXUky3&IN^J}ddVBOjnXxf;+Xu|^~4R@nIc=7?|d_F5AT+Ml6YBP#fM&n9u&bL z?&HxpOY!DkUu~x^aQbsjnq%sQtGjEZ-CN`Ck6%XvH!X*LmAI#ebO|`VOlYMJ&W62Dpe%LWOuw6cB^dJO zu-nkXvY;7{&av|njKxYx_IQu^&W#zPYNO86OE1|=B}3EuonJbqK0%zLePw?|ZYR9A zYp%Lim0DbJ+NWY6u;xXO*V?RnhGFN(N=?8YGCLo8GvKI^n&m*o+MBi2F`1EImg-h# zd({9(b)l%*uKL`H>AcwhW+bZD#C3bPe{uNg`C3lqa`&+18h=E1*LM7BoCIc1TuNMf zq*&x!#xY|!e8PmaHM^OE>GJGS$&lTCxZPeXD+3K)@15)G>`v}}khGMP@S1ixYwK(6 zoZOS4ruwGCuUh?eVP{uPZp_zlhB*q0kH#eIrY?i7s_l6H`E1qkUCu^=TtdPQA8+#V z=A!2I0Y= zK}fqk5Puqziv|Fsi9eI%;X`JF+{qLw9R*&jdJP6qJyBq1eY`fFi6MJatpZtO$3R=%hbBlzTL%V(ac@H{m?1((7XgEV{=UH6fGkfhgag*% z?{M4`3hd2hGZ9cIhr@wzbRi5D1qy@1;ZSWIsE&>n*F(!MfX*iQYtj9belTFkejY3; zlTBsNLA#73cg96F3d|Mz?<{D{e`x7`e^-iIGpIj_357wlceDE8h{ykLR~qdfZ$GvJ z`9FI9E3qFTfJufrko_1JSsvWpc`5CNVj?gsGKtM#5g3dMKMHxmo55!Ic{7+G9bE_v zq=qMXQ0coC^}ir^JOW4eW0U9}WE>U+=8{0DR8Is}-$K_AW`NPfm>a@i=GbExj3GuB zt&hbXLt_l!=pR@t!{Z{2OlSYVdj1EC{V8^LAZSc(WGtCQy+ro3U@>T*zp_S9f3C&s zr+j~7J%6qR{ZlNID+apT+yB?=A13Yq?QZ`WUhd(a@h8){Gtc4YQ z0;jHws9YPp4#h=}FrzISo|AMhZvN}rHxug+9smjf@b~stec&{Vn_(lP!vI=DFY(X1wo}3 z6%<84X#!FOq&E=|(E;92gpu~b%sB7;nD_3w_nve1+TXXoz0W>totP7L<|2Y}f&c)B zSX$s5_r|@CpPTbH)s?gZ06|kK7JI@Hiv=;5bT8@!G5`dOWI9psPV>^}^@&xCb#&+* zYr3NpKgbbtGgLA`MO{%q+$vfzXIRRie!rk8K_zR)VcF)&~UC~C9|TNuZ~|h*+SbvH&nO~b9n!U@Rp|LsTqiIn4mHP z5a+M(RP^6g;sQ28P^e?zI=)u`S3sW-KTv0zQKxk%YFF$FChas==yk3-R>E;>{!mH4 zI4BO22N;`ig=VIzI04x_fP1?KX&N}83An3X{nQ79W^SYfa{+F56s5Sb69CWwax@O` zHULVxPu?&E2wH%omvs{Y7}5l^EM2@TfXB~)x-M~{a)4hL&~k{5I12Ct1MaO#N&&$2 zG(gg9*#-66u`=;Fbxx(y%28Fy2-7e(eoa3<7Z=E3wJuAUW0HErpNQ$kkcPlCS$LR^ z*oT!40LV^|;$*wB9nd9O*43pKS1Ec<^UG`AT`-9>y))Zg%rFLkDOO0&js~o>j1#f+Z;+4CbVD~!F`nC9H78XlgVnHjQb!nhIJT(0a;8qU?Z zY+v|21huuk_Tkk>`~ZN<4pV<@BEMRHP@|6b zQ2oBKdZ8_Mz3Uj|rUr~SM$j|#5Yzo=$u*2xWancAb$94{V+EZ$2k*#4hA5=L`GqK& zA@-ffpH;6`6DGi8(#n5;s5lbMMY=&yisP3_i`Y=Cx8RYusSJ7>E$INZPSCZ0Io`m7 zoGlcV(afI^QK!vbCK$8=@M~LOLRj({8$;1!-=?JUOl*km%9=1Y9Cq+${I_WC?e5%$i5{ z6E=@Tm}#AW9uFG>A|5ueAlMM>hAav|hm>{pj|k`sa9?+5Pz5IzSU**Hx&Qa3gCsaC zieRCkG$0Xw04g3Fjcw9bmWaW^RjY3OWclPFzE`5xtk>63X)yr%yQ_ z;*JLBSZl;g=1k*^_Kf_D;osVrg_|f_kO;WvPTV z!6d6Bl_Ys}D88^LuV|u3$a%%N9UotK*6B)_nX|UjbfLie5|fB2Q`Zx!dQcDg&3-Wxi={T7o>rcwHPf0OsPL*Ns#x28v0Y4e zw5`fJnrC2RVAIms(RsgfAWb&|4I6~dWz1y^W=uYJKNWCFqq3m#1=+HE=2V{RVr7kQ z#3_VpF2VWKnF_Pg%+ezR)uq+>`}3>p677n!1}Ke>f2(|3S@>M`@$3-qXjvt#@(Phc zlA%0*Q`WecSetm|<&|Hy(R?CN!=l9srxZf`pE4zpCy^8BU3V9auDn@Io`+Hh-QwLt z+S8Q>+K)C-Go3Q}%qcRID*y16=$kRt*V-W|hL8;T=JD3r87tPB-sIhw;I`@udxoZ2rYiz}SaG32e61tb96Rzhv^y{9tK5w^gq-ULrn8aRH+V$KG+U)`ILyvG# zxMRXh!rXq^+z7g?_&UxAIZFOkKD=NOn_XohWfFg_^xABFsiJr5ueVAS*XL5Z61u3O z5hp@E54__eej?s%3=vk1h>CEDG>T(H6XbeeDZ1>QF|7Y2?mI3SH<3Ys*&`llTIs4A z7D3LVM)Y6myfkWtc)51;6EX>w7pxBvyIBXNR(4GIkuFtkUnCwd5bTK%xyvW2>B z(CuFnYIFmY-)QG*%vN1jExc7@BVse2fy|OlzXYPe(a2g@`0a#SewZRf+r&!B7s@BE zOYJ4(i1M8`zBivk4=3@x^{Kd3vd>jhuo9E^8GlM`P@S)wLU!?b-5Jw{NG{Gg*16D8 z(KdQZ|L)Sg-35sTiK*L_xslc`nhJzZwI$~f1XyNYV-sV#htsJa+->=Y%#yiFj z9Q$f6+VbllzAlt^81+k z=>5vzIghT%^J4U+m*T9cUen#1a|SgAU8k2{u$Ie5XAii%a7llJJV*P&`hwa??6YsF zzFVDMR(0B^YB8wxS+LjoynL2^*Z68};BV5q1N~VD^my$`5Pkj4`r4%QcnDKZZ5p#QfD<9kK*{zZ#vvYr^y-Y?L8nV&J?JzD zanA=5Kx1&w0Dv+IU=Tfg$Se?vOriRs!AsSz!62$98tkHLt7Xf;lD(-GK}@n!kR9G5 z$j1ZW2{tkWp#qQ`0vee`1O?D8`1&IQ(BMCKk(~LS843pd;llDkgZ~souss37(wStC zJ_M%ep{1n-(nmnZoDNfCx0YnBA2GQEf>W8DP?f-YB(f;=KXE~Dp zqxT<){qcbeGSrdmPru0Y;Ow23(q1SA63ZkLS#&0zPQUP@kSDz9EV{opodJStLtr2^ zTcQWmch7S44~VTT($d$TMfCL`TjJ1Q4he)x^+f98FuE`;eI1yVnJx@wiZj7sk7ICf z3|1em4MV{7e_(NRkBc<2FY5=^^FLS){(oTi8iK~)M8=Vs)JtSfGbWt|`Xg&3^&hlg z5ilLB-f;wnPv@Vt{E7Aa2Q7bLP5vhq$`J$I+uQ%z>mMdg1MN-!ZeGsf@AfDAa(bT0 zY3`!iXF2z3K;VQ8-jp-$h5$Pu0Q7Lr69@cE tNU_5F5@tDqw>z-XxMyOWnyrgG{91sV9boy3dsxXH+S1exSB7!F_HPPcG0^}3 literal 0 HcmV?d00001 diff --git a/applications/plugins/bt_hid_app/assets/Pressed_Button_13x13.png b/applications/plugins/bt_hid_app/assets/Pressed_Button_13x13.png new file mode 100644 index 0000000000000000000000000000000000000000..823926b842b8f9868fd70d6f1434dff071c04d5d GIT binary patch literal 3606 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 literal 0 HcmV?d00001 diff --git a/applications/plugins/bt_hid_app/assets/Voldwn_6x6.png b/applications/plugins/bt_hid_app/assets/Voldwn_6x6.png new file mode 100644 index 0000000000000000000000000000000000000000..d7a82a2df8262667a9a03419f437ff9b350e645f GIT binary patch literal 3593 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 = BtHidViewMouse; view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewMouse); + } else if(index == BtHidSubmenuIndexTikTok) { + app->view_id = BtHidViewTikTok; + view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewTikTok); } } @@ -65,6 +69,7 @@ void bt_hid_connection_status_changed_callback(BtStatus status, void* context) { bt_hid_keyboard_set_connected_status(bt_hid->bt_hid_keyboard, connected); bt_hid_media_set_connected_status(bt_hid->bt_hid_media, connected); bt_hid_mouse_set_connected_status(bt_hid->bt_hid_mouse, connected); + bt_hid_tiktok_set_connected_status(bt_hid->bt_hid_tiktok, connected); } BtHid* bt_hid_app_alloc() { @@ -91,6 +96,8 @@ BtHid* bt_hid_app_alloc() { submenu_add_item( app->submenu, "Keyboard", BtHidSubmenuIndexKeyboard, bt_hid_submenu_callback, app); submenu_add_item(app->submenu, "Media", BtHidSubmenuIndexMedia, bt_hid_submenu_callback, app); + submenu_add_item( + app->submenu, "TikTok Controller", BtHidSubmenuIndexTikTok, bt_hid_submenu_callback, app); submenu_add_item(app->submenu, "Mouse", BtHidSubmenuIndexMouse, bt_hid_submenu_callback, app); view_set_previous_callback(submenu_get_view(app->submenu), bt_hid_exit); view_dispatcher_add_view( @@ -127,6 +134,13 @@ BtHid* bt_hid_app_alloc() { view_dispatcher_add_view( app->view_dispatcher, BtHidViewMedia, bt_hid_media_get_view(app->bt_hid_media)); + // TikTok view + app->bt_hid_tiktok = bt_hid_tiktok_alloc(); + view_set_previous_callback( + bt_hid_tiktok_get_view(app->bt_hid_tiktok), bt_hid_exit_confirm_view); + view_dispatcher_add_view( + app->view_dispatcher, BtHidViewTikTok, bt_hid_tiktok_get_view(app->bt_hid_tiktok)); + // Mouse view app->bt_hid_mouse = bt_hid_mouse_alloc(); view_set_previous_callback(bt_hid_mouse_get_view(app->bt_hid_mouse), bt_hid_exit_confirm_view); @@ -159,6 +173,8 @@ void bt_hid_app_free(BtHid* app) { bt_hid_media_free(app->bt_hid_media); view_dispatcher_remove_view(app->view_dispatcher, BtHidViewMouse); bt_hid_mouse_free(app->bt_hid_mouse); + view_dispatcher_remove_view(app->view_dispatcher, BtHidViewTikTok); + bt_hid_tiktok_free(app->bt_hid_tiktok); view_dispatcher_free(app->view_dispatcher); // Close records diff --git a/applications/plugins/bt_hid_app/bt_hid.h b/applications/plugins/bt_hid_app/bt_hid.h index 0f4c7be9fd5..89e8807fec5 100644 --- a/applications/plugins/bt_hid_app/bt_hid.h +++ b/applications/plugins/bt_hid_app/bt_hid.h @@ -13,6 +13,7 @@ #include "views/bt_hid_keyboard.h" #include "views/bt_hid_media.h" #include "views/bt_hid_mouse.h" +#include "views/bt_hid_tiktok.h" typedef struct { Bt* bt; @@ -25,6 +26,7 @@ typedef struct { BtHidKeyboard* bt_hid_keyboard; BtHidMedia* bt_hid_media; BtHidMouse* bt_hid_mouse; + BtHidTikTok* bt_hid_tiktok; uint32_t view_id; } BtHid; @@ -34,5 +36,6 @@ typedef enum { BtHidViewKeyboard, BtHidViewMedia, BtHidViewMouse, + BtHidViewTikTok, BtHidViewExitConfirm, } BtHidView; diff --git a/applications/plugins/bt_hid_app/views/bt_hid_keyboard.c b/applications/plugins/bt_hid_app/views/bt_hid_keyboard.c index 2c65f6ab13c..a1077b79856 100644 --- a/applications/plugins/bt_hid_app/views/bt_hid_keyboard.c +++ b/applications/plugins/bt_hid_app/views/bt_hid_keyboard.c @@ -5,6 +5,8 @@ #include #include +#include "bt_hid_icons.h" + struct BtHidKeyboard { View* view; }; diff --git a/applications/plugins/bt_hid_app/views/bt_hid_keynote.c b/applications/plugins/bt_hid_app/views/bt_hid_keynote.c index db88b80002f..0e81c5fa09d 100644 --- a/applications/plugins/bt_hid_app/views/bt_hid_keynote.c +++ b/applications/plugins/bt_hid_app/views/bt_hid_keynote.c @@ -4,6 +4,8 @@ #include #include +#include "bt_hid_icons.h" + struct BtHidKeynote { View* view; }; diff --git a/applications/plugins/bt_hid_app/views/bt_hid_media.c b/applications/plugins/bt_hid_app/views/bt_hid_media.c index 181cd347b75..df7349a97bf 100644 --- a/applications/plugins/bt_hid_app/views/bt_hid_media.c +++ b/applications/plugins/bt_hid_app/views/bt_hid_media.c @@ -4,6 +4,8 @@ #include #include +#include "bt_hid_icons.h" + struct BtHidMedia { View* view; }; diff --git a/applications/plugins/bt_hid_app/views/bt_hid_mouse.c b/applications/plugins/bt_hid_app/views/bt_hid_mouse.c index 098adb73271..bd48bab1638 100644 --- a/applications/plugins/bt_hid_app/views/bt_hid_mouse.c +++ b/applications/plugins/bt_hid_app/views/bt_hid_mouse.c @@ -4,6 +4,8 @@ #include #include +#include "bt_hid_icons.h" + struct BtHidMouse { View* view; }; diff --git a/applications/plugins/bt_hid_app/views/bt_hid_tiktok.c b/applications/plugins/bt_hid_app/views/bt_hid_tiktok.c new file mode 100644 index 00000000000..9af00157dd5 --- /dev/null +++ b/applications/plugins/bt_hid_app/views/bt_hid_tiktok.c @@ -0,0 +1,207 @@ +#include "bt_hid_tiktok.h" +#include +#include +#include +#include + +#include "bt_hid_icons.h" + +struct BtHidTikTok { + View* view; +}; + +typedef struct { + bool left_pressed; + bool up_pressed; + bool right_pressed; + bool down_pressed; + bool ok_pressed; + bool connected; +} BtHidTikTokModel; + +static void bt_hid_tiktok_draw_callback(Canvas* canvas, void* context) { + furi_assert(context); + BtHidTikTokModel* model = context; + + // Header + if(model->connected) { + canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); + } else { + canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); + } + canvas_set_font(canvas, FontPrimary); + elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "TikTok"); + canvas_set_font(canvas, FontSecondary); + + // Keypad circles + canvas_draw_icon(canvas, 76, 8, &I_Circles_47x47); + + // Up + if(model->up_pressed) { + canvas_set_bitmap_mode(canvas, 1); + canvas_draw_icon(canvas, 93, 9, &I_Pressed_Button_13x13); + canvas_set_bitmap_mode(canvas, 0); + canvas_set_color(canvas, ColorWhite); + } + canvas_draw_icon(canvas, 96, 11, &I_Arr_up_7x9); + canvas_set_color(canvas, ColorBlack); + + // Down + if(model->down_pressed) { + canvas_set_bitmap_mode(canvas, 1); + canvas_draw_icon(canvas, 93, 41, &I_Pressed_Button_13x13); + canvas_set_bitmap_mode(canvas, 0); + canvas_set_color(canvas, ColorWhite); + } + canvas_draw_icon(canvas, 96, 44, &I_Arr_dwn_7x9); + canvas_set_color(canvas, ColorBlack); + + // Left + if(model->left_pressed) { + canvas_set_bitmap_mode(canvas, 1); + canvas_draw_icon(canvas, 77, 25, &I_Pressed_Button_13x13); + canvas_set_bitmap_mode(canvas, 0); + canvas_set_color(canvas, ColorWhite); + } + canvas_draw_icon(canvas, 81, 29, &I_Voldwn_6x6); + canvas_set_color(canvas, ColorBlack); + + // Right + if(model->right_pressed) { + canvas_set_bitmap_mode(canvas, 1); + canvas_draw_icon(canvas, 109, 25, &I_Pressed_Button_13x13); + canvas_set_bitmap_mode(canvas, 0); + canvas_set_color(canvas, ColorWhite); + } + canvas_draw_icon(canvas, 111, 29, &I_Volup_8x6); + canvas_set_color(canvas, ColorBlack); + + // Ok + if(model->ok_pressed) { + canvas_draw_icon(canvas, 91, 23, &I_Like_pressed_17x17); + } else { + canvas_draw_icon(canvas, 94, 27, &I_Like_def_11x9); + } + // Exit + canvas_draw_icon(canvas, 0, 54, &I_Pin_back_arrow_10x8); + canvas_set_font(canvas, FontSecondary); + elements_multiline_text_aligned(canvas, 13, 62, AlignLeft, AlignBottom, "Hold to exit"); +} + +static void bt_hid_tiktok_process_press(BtHidTikTok* bt_hid_tiktok, InputEvent* event) { + with_view_model( + bt_hid_tiktok->view, + BtHidTikTokModel * model, + { + if(event->key == InputKeyUp) { + model->up_pressed = true; + } else if(event->key == InputKeyDown) { + model->down_pressed = true; + } else if(event->key == InputKeyLeft) { + model->left_pressed = true; + furi_hal_bt_hid_consumer_key_press(HID_CONSUMER_VOLUME_DECREMENT); + } else if(event->key == InputKeyRight) { + model->right_pressed = true; + furi_hal_bt_hid_consumer_key_press(HID_CONSUMER_VOLUME_INCREMENT); + } else if(event->key == InputKeyOk) { + model->ok_pressed = true; + } + }, + true); +} + +static void bt_hid_tiktok_process_release(BtHidTikTok* bt_hid_tiktok, InputEvent* event) { + with_view_model( + bt_hid_tiktok->view, + BtHidTikTokModel * model, + { + if(event->key == InputKeyUp) { + model->up_pressed = false; + } else if(event->key == InputKeyDown) { + model->down_pressed = false; + } else if(event->key == InputKeyLeft) { + model->left_pressed = false; + furi_hal_bt_hid_consumer_key_release(HID_CONSUMER_VOLUME_DECREMENT); + } else if(event->key == InputKeyRight) { + model->right_pressed = false; + furi_hal_bt_hid_consumer_key_release(HID_CONSUMER_VOLUME_INCREMENT); + } else if(event->key == InputKeyOk) { + model->ok_pressed = false; + } + }, + true); +} + +static bool bt_hid_tiktok_input_callback(InputEvent* event, void* context) { + furi_assert(context); + BtHidTikTok* bt_hid_tiktok = context; + bool consumed = false; + + if(event->type == InputTypePress) { + bt_hid_tiktok_process_press(bt_hid_tiktok, event); + consumed = true; + } else if(event->type == InputTypeRelease) { + bt_hid_tiktok_process_release(bt_hid_tiktok, event); + consumed = true; + } else if(event->type == InputTypeShort) { + if(event->key == InputKeyOk) { + furi_hal_bt_hid_mouse_press(HID_MOUSE_BTN_LEFT); + furi_delay_ms(50); + furi_hal_bt_hid_mouse_release(HID_MOUSE_BTN_LEFT); + furi_delay_ms(50); + furi_hal_bt_hid_mouse_press(HID_MOUSE_BTN_LEFT); + furi_delay_ms(50); + furi_hal_bt_hid_mouse_release(HID_MOUSE_BTN_LEFT); + consumed = true; + } else if(event->key == InputKeyUp) { + // Emulate up swipe + furi_hal_bt_hid_mouse_scroll(-6); + furi_hal_bt_hid_mouse_scroll(-12); + furi_hal_bt_hid_mouse_scroll(-19); + furi_hal_bt_hid_mouse_scroll(-12); + furi_hal_bt_hid_mouse_scroll(-6); + consumed = true; + } else if(event->key == InputKeyDown) { + // Emulate down swipe + furi_hal_bt_hid_mouse_scroll(6); + furi_hal_bt_hid_mouse_scroll(12); + furi_hal_bt_hid_mouse_scroll(19); + furi_hal_bt_hid_mouse_scroll(12); + furi_hal_bt_hid_mouse_scroll(6); + consumed = true; + } else if(event->key == InputKeyBack) { + furi_hal_bt_hid_consumer_key_release_all(); + consumed = true; + } + } + + return consumed; +} + +BtHidTikTok* bt_hid_tiktok_alloc() { + BtHidTikTok* bt_hid_tiktok = malloc(sizeof(BtHidTikTok)); + bt_hid_tiktok->view = view_alloc(); + view_set_context(bt_hid_tiktok->view, bt_hid_tiktok); + view_allocate_model(bt_hid_tiktok->view, ViewModelTypeLocking, sizeof(BtHidTikTokModel)); + view_set_draw_callback(bt_hid_tiktok->view, bt_hid_tiktok_draw_callback); + view_set_input_callback(bt_hid_tiktok->view, bt_hid_tiktok_input_callback); + + return bt_hid_tiktok; +} + +void bt_hid_tiktok_free(BtHidTikTok* bt_hid_tiktok) { + furi_assert(bt_hid_tiktok); + view_free(bt_hid_tiktok->view); + free(bt_hid_tiktok); +} + +View* bt_hid_tiktok_get_view(BtHidTikTok* bt_hid_tiktok) { + furi_assert(bt_hid_tiktok); + return bt_hid_tiktok->view; +} + +void bt_hid_tiktok_set_connected_status(BtHidTikTok* bt_hid_tiktok, bool connected) { + furi_assert(bt_hid_tiktok); + with_view_model( + bt_hid_tiktok->view, BtHidTikTokModel * model, { model->connected = connected; }, true); +} diff --git a/applications/plugins/bt_hid_app/views/bt_hid_tiktok.h b/applications/plugins/bt_hid_app/views/bt_hid_tiktok.h new file mode 100644 index 00000000000..03c9afecac0 --- /dev/null +++ b/applications/plugins/bt_hid_app/views/bt_hid_tiktok.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +typedef struct BtHidTikTok BtHidTikTok; + +BtHidTikTok* bt_hid_tiktok_alloc(); + +void bt_hid_tiktok_free(BtHidTikTok* bt_hid_tiktok); + +View* bt_hid_tiktok_get_view(BtHidTikTok* bt_hid_tiktok); + +void bt_hid_tiktok_set_connected_status(BtHidTikTok* bt_hid_tiktok, bool connected); From eb4ff3c0fd4cee9e710732d98cc728bbcf2d31cf Mon Sep 17 00:00:00 2001 From: hedger Date: Wed, 12 Oct 2022 20:12:05 +0400 Subject: [PATCH 137/824] [FL-2832] fbt: more fixes & improvements (#1854) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * github: bundling debug folder with scripts; docs: fixes & updates; fbt: added FAP_EXAMPLES variable to enable building example apps. Disabled by default. fbt: added TERM to list of proxied environment variables * fbt: better help output; disabled implicit_deps_unchanged; added color to import validator reports * fbt: moved debug configuration to separate tool * fbt: proper dependency tracker for SDK source file; renamed linker script for external apps * fbt: fixed debug elf path * fbt: packaging sdk archive * scripts: fixed sconsdist.py * fbt: reworked sdk packing; docs: updates * docs: info on cli target; linter fixes * fbt: moved main code to scripts folder * scripts: packing update into .tgz * fbt, scripts: reworked copro_dist to build .tgz * scripts: fixed naming for archived updater package * Scripts: fix ぐるぐる回る Co-authored-by: Aleksandr Kutuzov --- .github/workflows/build.yml | 18 +- SConstruct | 63 ++--- .../examples/example_images/example_images.c | 2 + documentation/AppManifests.md | 4 +- documentation/AppsOnSDCard.md | 4 +- documentation/fbt.md | 3 +- firmware.scons | 17 +- ...{application-ext.ld => application_ext.ld} | 0 {site_scons => scripts}/fbt/__init__.py | 0 {site_scons => scripts}/fbt/appmanifest.py | 0 {site_scons => scripts}/fbt/elfmanifest.py | 0 {site_scons => scripts}/fbt/sdk.py | 0 {site_scons => scripts}/fbt/util.py | 22 -- {site_scons => scripts}/fbt/version.py | 0 .../fbt_tools}/blackmagic.py | 0 .../fbt_tools}/ccache.py | 0 .../fbt_tools}/crosscc.py | 0 .../fbt_tools}/fbt_apps.py | 0 .../fbt_tools}/fbt_assets.py | 0 scripts/fbt_tools/fbt_debugopts.py | 41 ++++ .../fbt_tools}/fbt_dist.py | 3 +- .../fbt_tools}/fbt_extapps.py | 8 +- scripts/fbt_tools/fbt_help.py | 44 ++++ .../fbt_tools}/fbt_sdk.py | 80 ++++++- .../fbt_tools}/fbt_version.py | 0 .../site_tools => scripts/fbt_tools}/fwbin.py | 0 .../site_tools => scripts/fbt_tools}/gdb.py | 0 .../fbt_tools}/jflash.py | 0 .../fbt_tools}/objdump.py | 0 .../fbt_tools}/openocd.py | 0 .../fbt_tools}/python3.py | 0 .../fbt_tools}/sconsmodular.py | 0 .../fbt_tools}/sconsrecursiveglob.py | 0 .../site_tools => scripts/fbt_tools}/strip.py | 0 scripts/flipper/assets/copro.py | 36 +-- scripts/guruguru.py | 2 +- scripts/sconsdist.py | 71 ++++-- site_scons/commandline.scons | 224 ++++++++---------- site_scons/environ.scons | 17 +- site_scons/extapps.scons | 3 +- site_scons/fbt_extra/util.py | 23 ++ 41 files changed, 413 insertions(+), 272 deletions(-) rename firmware/targets/f7/{application-ext.ld => application_ext.ld} (100%) rename {site_scons => scripts}/fbt/__init__.py (100%) rename {site_scons => scripts}/fbt/appmanifest.py (100%) rename {site_scons => scripts}/fbt/elfmanifest.py (100%) rename {site_scons => scripts}/fbt/sdk.py (100%) rename {site_scons => scripts}/fbt/util.py (58%) rename {site_scons => scripts}/fbt/version.py (100%) rename {site_scons/site_tools => scripts/fbt_tools}/blackmagic.py (100%) rename {site_scons/site_tools => scripts/fbt_tools}/ccache.py (100%) rename {site_scons/site_tools => scripts/fbt_tools}/crosscc.py (100%) rename {site_scons/site_tools => scripts/fbt_tools}/fbt_apps.py (100%) rename {site_scons/site_tools => scripts/fbt_tools}/fbt_assets.py (100%) create mode 100644 scripts/fbt_tools/fbt_debugopts.py rename {site_scons/site_tools => scripts/fbt_tools}/fbt_dist.py (98%) rename {site_scons/site_tools => scripts/fbt_tools}/fbt_extapps.py (96%) create mode 100644 scripts/fbt_tools/fbt_help.py rename {site_scons/site_tools => scripts/fbt_tools}/fbt_sdk.py (71%) rename {site_scons/site_tools => scripts/fbt_tools}/fbt_version.py (100%) rename {site_scons/site_tools => scripts/fbt_tools}/fwbin.py (100%) rename {site_scons/site_tools => scripts/fbt_tools}/gdb.py (100%) rename {site_scons/site_tools => scripts/fbt_tools}/jflash.py (100%) rename {site_scons/site_tools => scripts/fbt_tools}/objdump.py (100%) rename {site_scons/site_tools => scripts/fbt_tools}/openocd.py (100%) rename {site_scons/site_tools => scripts/fbt_tools}/python3.py (100%) rename {site_scons/site_tools => scripts/fbt_tools}/sconsmodular.py (100%) rename {site_scons/site_tools => scripts/fbt_tools}/sconsrecursiveglob.py (100%) rename {site_scons/site_tools => scripts/fbt_tools}/strip.py (100%) create mode 100644 site_scons/fbt_extra/util.py diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1304c5d7b0d..8fb67ed1aa5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -56,14 +56,14 @@ jobs: - name: 'Bundle scripts' if: ${{ !github.event.pull_request.head.repo.fork }} run: | - tar czpf artifacts/flipper-z-any-scripts-${SUFFIX}.tgz scripts + tar czpf artifacts/flipper-z-any-scripts-${SUFFIX}.tgz scripts debug - name: 'Build the firmware' run: | set -e for TARGET in ${TARGETS}; do FBT_TOOLCHAIN_PATH=/runner/_work ./fbt TARGET_HW="$(echo "${TARGET}" | sed 's/f//')" \ - updater_package ${{ startsWith(github.ref, 'refs/tags') && 'DEBUG=0 COMPACT=1' || '' }} + copro_dist updater_package ${{ startsWith(github.ref, 'refs/tags') && 'DEBUG=0 COMPACT=1' || '' }} done - name: 'Move upload files' @@ -74,17 +74,6 @@ jobs: mv dist/${TARGET}-*/* artifacts/ done - - name: 'Bundle self-update package' - if: ${{ !github.event.pull_request.head.repo.fork }} - run: | - set -e - for UPDATEBUNDLE in artifacts/*/; do - BUNDLE_NAME="$(echo "$UPDATEBUNDLE" | cut -d'/' -f2)" - echo Packaging "${BUNDLE_NAME}" - tar czpf "artifacts/flipper-z-${BUNDLE_NAME}.tgz" -C artifacts "${BUNDLE_NAME}" - rm -rf "artifacts/${BUNDLE_NAME}" - done - - name: "Check for uncommitted changes" run: | git diff --exit-code @@ -97,8 +86,7 @@ jobs: - name: 'Bundle core2 firmware' if: ${{ !github.event.pull_request.head.repo.fork }} run: | - FBT_TOOLCHAIN_PATH=/runner/_work ./fbt copro_dist - tar czpf "artifacts/flipper-z-any-core2_firmware-${SUFFIX}.tgz" -C assets core2_firmware + cp build/core2_firmware.tgz "artifacts/flipper-z-any-core2_firmware-${SUFFIX}.tgz" - name: 'Copy .map file' run: | diff --git a/SConstruct b/SConstruct index 5ad2ac3c8ae..74fa5667b3f 100644 --- a/SConstruct +++ b/SConstruct @@ -7,7 +7,6 @@ # construction of certain targets behind command-line options. import os -import subprocess DefaultEnvironment(tools=[]) @@ -15,17 +14,22 @@ EnsurePythonVersion(3, 8) # Progress(["OwO\r", "owo\r", "uwu\r", "owo\r"], interval=15) - # This environment is created only for loading options & validating file/dir existence fbt_variables = SConscript("site_scons/commandline.scons") -cmd_environment = Environment(tools=[], variables=fbt_variables) -Help(fbt_variables.GenerateHelpText(cmd_environment)) +cmd_environment = Environment( + toolpath=["#/scripts/fbt_tools"], + tools=[ + ("fbt_help", {"vars": fbt_variables}), + ], + variables=fbt_variables, +) # Building basic environment - tools, utility methods, cross-compilation # settings, gcc flags for Cortex-M4, basic builders and more coreenv = SConscript( "site_scons/environ.scons", exports={"VAR_ENV": cmd_environment}, + toolpath=["#/scripts/fbt_tools"], ) SConscript("site_scons/cc.scons", exports={"ENV": coreenv}) @@ -35,41 +39,13 @@ coreenv["ROOT_DIR"] = Dir(".") # Create a separate "dist" environment and add construction envs to it distenv = coreenv.Clone( - tools=["fbt_dist", "openocd", "blackmagic", "jflash"], - OPENOCD_GDB_PIPE=[ - "|openocd -c 'gdb_port pipe; log_output debug/openocd.log' ${[SINGLEQUOTEFUNC(OPENOCD_OPTS)]}" - ], - GDBOPTS_BASE=[ - "-ex", - "target extended-remote ${GDBREMOTE}", - "-ex", - "set confirm off", - "-ex", - "set pagination off", - ], - GDBOPTS_BLACKMAGIC=[ - "-ex", - "monitor swdp_scan", - "-ex", - "monitor debug_bmp enable", - "-ex", - "attach 1", - "-ex", - "set mem inaccessible-by-default off", - ], - GDBPYOPTS=[ - "-ex", - "source debug/FreeRTOS/FreeRTOS.py", - "-ex", - "source debug/flipperapps.py", - "-ex", - "source debug/PyCortexMDebug/PyCortexMDebug.py", - "-ex", - "svd_load ${SVD_FILE}", - "-ex", - "compare-sections", + tools=[ + "fbt_dist", + "fbt_debugopts", + "openocd", + "blackmagic", + "jflash", ], - JFLASHPROJECT="${ROOT_DIR.abspath}/debug/fw.jflash", ENV=os.environ, ) @@ -166,7 +142,7 @@ basic_dist = distenv.DistCommand("fw_dist", distenv["DIST_DEPENDS"]) distenv.Default(basic_dist) dist_dir = distenv.GetProjetDirName() -plugin_dist = [ +fap_dist = [ distenv.Install( f"#/dist/{dist_dir}/apps/debug_elf", firmware_env["FW_EXTAPPS"]["debug"].values(), @@ -176,9 +152,9 @@ plugin_dist = [ for dist_entry in firmware_env["FW_EXTAPPS"]["dist"].values() ), ] -Depends(plugin_dist, firmware_env["FW_EXTAPPS"]["validators"].values()) -Alias("plugin_dist", plugin_dist) -# distenv.Default(plugin_dist) +Depends(fap_dist, firmware_env["FW_EXTAPPS"]["validators"].values()) +Alias("fap_dist", fap_dist) +# distenv.Default(fap_dist) plugin_resources_dist = list( distenv.Install(f"#/assets/resources/apps/{dist_entry[0]}", dist_entry[1]) @@ -189,9 +165,10 @@ distenv.Depends(firmware_env["FW_RESOURCES"], plugin_resources_dist) # Target for bundling core2 package for qFlipper copro_dist = distenv.CoproBuilder( - distenv.Dir("assets/core2_firmware"), + "#/build/core2_firmware.tgz", [], ) +distenv.AlwaysBuild(copro_dist) distenv.Alias("copro_dist", copro_dist) firmware_flash = distenv.AddOpenOCDFlashTarget(firmware_env) diff --git a/applications/examples/example_images/example_images.c b/applications/examples/example_images/example_images.c index 48fa5e77e3f..b00818cd668 100644 --- a/applications/examples/example_images/example_images.c +++ b/applications/examples/example_images/example_images.c @@ -4,6 +4,8 @@ #include #include +/* Magic happens here -- this file is generated by fbt. + * Just set fap_icon_assets in application.fam and #include {APPID}_icons.h */ #include "example_images_icons.h" typedef struct { diff --git a/documentation/AppManifests.md b/documentation/AppManifests.md index 4fec9d22c4f..c7c73110bb0 100644 --- a/documentation/AppManifests.md +++ b/documentation/AppManifests.md @@ -30,7 +30,7 @@ Only 2 parameters are mandatory: ***appid*** and ***apptype***, others are optio | METAPACKAGE | Does not define any code to be run, used for declaring dependencies and application bundles | * **name**: Name that is displayed in menus. -* **entry_point**: C function to be used as application's entry point. +* **entry_point**: C function to be used as application's entry point. Note that C++ function names are mangled, so you need to wrap them in `extern "C"` in order to use them as entry points. * **flags**: Internal flags for system apps. Do not use. * **cdefines**: C preprocessor definitions to declare globally for other apps when current application is included in active build configuration. * **requires**: List of application IDs to also include in build configuration, when current application is referenced in list of applications to build. @@ -55,7 +55,7 @@ The following parameters are used only for [FAPs](./AppsOnSDCard.md): * **fap_author**: string, may be empty. Application's author. * **fap_weburl**: string, may be empty. Application's homepage. * **fap_icon_assets**: string. If present, defines a folder name to be used for gathering image assets for this application. These images will be preprocessed and built alongside the application. See [FAP assets](./AppsOnSDCard.md#fap-assets) for details. -* **fap_extbuild**: provides support for parts of application sources to be build by external tools. Contains a list of `ExtFile(path="file name", command="shell command")` definitions. **`fbt`** will run the specified command for each file in the list. +* **fap_extbuild**: provides support for parts of application sources to be built by external tools. Contains a list of `ExtFile(path="file name", command="shell command")` definitions. **`fbt`** will run the specified command for each file in the list. Note that commands are executed at the firmware root folder's root, and all intermediate files must be placed in a application's temporary build folder. For that, you can use pattern expansion by **`fbt`**: `${FAP_WORK_DIR}` will be replaced with the path to the application's temporary build folder, and `${FAP_SRC_DIR}` will be replaced with the path to the application's source folder. You can also use other variables defined internally by **`fbt`**. Example for building an app from Rust sources: diff --git a/documentation/AppsOnSDCard.md b/documentation/AppsOnSDCard.md index aff8314dcf4..4acb3ec373e 100644 --- a/documentation/AppsOnSDCard.md +++ b/documentation/AppsOnSDCard.md @@ -2,7 +2,7 @@ [fbt](./fbt.md) has support for building applications as FAP files. FAP are essentially .elf executables with extra metadata and resources bundled in. -FAPs are built with `faps` **`fbt`** target. They can also be deployed to `dist` folder with `plugin_dist` **`fbt`** target. +FAPs are built with `faps` target. They can also be deployed to `dist` folder with `fap_dist` target. FAPs do not depend on being run on a specific firmware version. Compatibility is determined by the FAP's metadata, which includes the required [API version](#api-versioning). @@ -15,7 +15,7 @@ To build your application as a FAP, just create a folder with your app's source * To build your application, run `./fbt fap_{APPID}`, where APPID is your application's ID in its manifest. * To build your app, then upload it over USB & run it on Flipper, use `./fbt launch_app APPSRC=applications/path/to/app`. This command is configured in default [VSCode profile](../.vscode/ReadMe.md) as "Launch App on Flipper" build action (Ctrl+Shift+B menu). - * To build all FAPs, run `./fbt plugin_dist`. + * To build all FAPs, run `./fbt faps` or `./fbt fap_dist`. ## FAP assets diff --git a/documentation/fbt.md b/documentation/fbt.md index e20d4317742..3fac7ce759e 100644 --- a/documentation/fbt.md +++ b/documentation/fbt.md @@ -43,7 +43,7 @@ To run cleanup (think of `make clean`) for specified targets, add `-c` option. ### High-level (what you most likely need) - `fw_dist` - build & publish firmware to `dist` folder. This is a default target, when no other are specified -- `plugin_dist` - build external plugins & publish to `dist` folder +- `fap_dist` - build external plugins & publish to `dist` folder - `updater_package`, `updater_minpackage` - build self-update package. Minimal version only inclues firmware's DFU file; full version also includes radio stack & resources for SD card - `copro_dist` - bundle Core2 FUS+stack binaries for qFlipper - `flash` - flash attached device with OpenOCD over ST-Link @@ -56,6 +56,7 @@ To run cleanup (think of `make clean`) for specified targets, add `-c` option. - `get_blackmagic` - output blackmagic address in gdb remote format. Useful for IDE integration - `lint`, `format` - run clang-format on C source code to check and reformat it according to `.clang-format` specs - `lint_py`, `format_py` - run [black](https://black.readthedocs.io/en/stable/index.html) on Python source code, build system files & application manifests +- `cli` - start Flipper CLI session over USB ### Firmware targets diff --git a/firmware.scons b/firmware.scons index dd13b6b3d55..d28309d0e09 100644 --- a/firmware.scons +++ b/firmware.scons @@ -3,7 +3,7 @@ Import("ENV", "fw_build_meta") from SCons.Errors import UserError import itertools -from fbt.util import ( +from fbt_extra.util import ( should_gen_cdb_and_link_dir, link_elf_dir_as_latest, ) @@ -141,6 +141,10 @@ else: if extra_int_apps := GetOption("extra_int_apps"): fwenv.Append(APPS=extra_int_apps.split(",")) + +if fwenv["FAP_EXAMPLES"]: + fwenv.Append(APPDIRS=[("applications/examples", False)]) + fwenv.LoadApplicationManifests() fwenv.PrepareApplicationsBuild() @@ -316,10 +320,13 @@ if fwenv["IS_BASE_FIRMWARE"]: "-D__inline__=inline", ], ) - Depends(sdk_source, (fwenv["SDK_HEADERS"], fwenv["FW_ASSETS_HEADERS"])) + # Depends(sdk_source, (fwenv["SDK_HEADERS"], fwenv["FW_ASSETS_HEADERS"])) + Depends(sdk_source, fwenv.ProcessSdkDepends("sdk_origin.d")) - sdk_tree = fwenv.SDKTree("sdk/sdk.opts", "sdk_origin") - AlwaysBuild(sdk_tree) + fwenv["SDK_DIR"] = fwenv.Dir("sdk") + sdk_tree = fwenv.SDKTree(fwenv["SDK_DIR"], "sdk_origin") + fw_artifacts.append(sdk_tree) + # AlwaysBuild(sdk_tree) Alias("sdk_tree", sdk_tree) sdk_apicheck = fwenv.SDKSymUpdater(fwenv.subst("$SDK_DEFINITION"), "sdk_origin") @@ -329,7 +336,7 @@ if fwenv["IS_BASE_FIRMWARE"]: Alias("sdk_check", sdk_apicheck) sdk_apisyms = fwenv.SDKSymGenerator( - "assets/compiled/symbols.h", fwenv.subst("$SDK_DEFINITION") + "assets/compiled/symbols.h", fwenv["SDK_DEFINITION"] ) Alias("api_syms", sdk_apisyms) diff --git a/firmware/targets/f7/application-ext.ld b/firmware/targets/f7/application_ext.ld similarity index 100% rename from firmware/targets/f7/application-ext.ld rename to firmware/targets/f7/application_ext.ld diff --git a/site_scons/fbt/__init__.py b/scripts/fbt/__init__.py similarity index 100% rename from site_scons/fbt/__init__.py rename to scripts/fbt/__init__.py diff --git a/site_scons/fbt/appmanifest.py b/scripts/fbt/appmanifest.py similarity index 100% rename from site_scons/fbt/appmanifest.py rename to scripts/fbt/appmanifest.py diff --git a/site_scons/fbt/elfmanifest.py b/scripts/fbt/elfmanifest.py similarity index 100% rename from site_scons/fbt/elfmanifest.py rename to scripts/fbt/elfmanifest.py diff --git a/site_scons/fbt/sdk.py b/scripts/fbt/sdk.py similarity index 100% rename from site_scons/fbt/sdk.py rename to scripts/fbt/sdk.py diff --git a/site_scons/fbt/util.py b/scripts/fbt/util.py similarity index 58% rename from site_scons/fbt/util.py rename to scripts/fbt/util.py index 8d8af518328..baa4ddfee04 100644 --- a/site_scons/fbt/util.py +++ b/scripts/fbt/util.py @@ -41,25 +41,3 @@ def link_dir(target_path, source_path, is_windows): def single_quote(arg_list): return " ".join(f"'{arg}'" if " " in arg else str(arg) for arg in arg_list) - - -def link_elf_dir_as_latest(env, elf_node): - elf_dir = elf_node.Dir(".") - latest_dir = env.Dir("#build/latest") - print(f"Setting {elf_dir} as latest built dir (./build/latest/)") - return link_dir(latest_dir.abspath, elf_dir.abspath, env["PLATFORM"] == "win32") - - -def should_gen_cdb_and_link_dir(env, requested_targets): - explicitly_building_updater = False - # Hacky way to check if updater-related targets were requested - for build_target in requested_targets: - if "updater" in str(build_target): - explicitly_building_updater = True - - is_updater = not env["IS_BASE_FIRMWARE"] - # If updater is explicitly requested, link to the latest updater - # Otherwise, link to firmware - return (is_updater and explicitly_building_updater) or ( - not is_updater and not explicitly_building_updater - ) diff --git a/site_scons/fbt/version.py b/scripts/fbt/version.py similarity index 100% rename from site_scons/fbt/version.py rename to scripts/fbt/version.py diff --git a/site_scons/site_tools/blackmagic.py b/scripts/fbt_tools/blackmagic.py similarity index 100% rename from site_scons/site_tools/blackmagic.py rename to scripts/fbt_tools/blackmagic.py diff --git a/site_scons/site_tools/ccache.py b/scripts/fbt_tools/ccache.py similarity index 100% rename from site_scons/site_tools/ccache.py rename to scripts/fbt_tools/ccache.py diff --git a/site_scons/site_tools/crosscc.py b/scripts/fbt_tools/crosscc.py similarity index 100% rename from site_scons/site_tools/crosscc.py rename to scripts/fbt_tools/crosscc.py diff --git a/site_scons/site_tools/fbt_apps.py b/scripts/fbt_tools/fbt_apps.py similarity index 100% rename from site_scons/site_tools/fbt_apps.py rename to scripts/fbt_tools/fbt_apps.py diff --git a/site_scons/site_tools/fbt_assets.py b/scripts/fbt_tools/fbt_assets.py similarity index 100% rename from site_scons/site_tools/fbt_assets.py rename to scripts/fbt_tools/fbt_assets.py diff --git a/scripts/fbt_tools/fbt_debugopts.py b/scripts/fbt_tools/fbt_debugopts.py new file mode 100644 index 00000000000..3e7b0701014 --- /dev/null +++ b/scripts/fbt_tools/fbt_debugopts.py @@ -0,0 +1,41 @@ +def generate(env, **kw): + env.SetDefault( + OPENOCD_GDB_PIPE=[ + "|openocd -c 'gdb_port pipe; log_output debug/openocd.log' ${[SINGLEQUOTEFUNC(OPENOCD_OPTS)]}" + ], + GDBOPTS_BASE=[ + "-ex", + "target extended-remote ${GDBREMOTE}", + "-ex", + "set confirm off", + "-ex", + "set pagination off", + ], + GDBOPTS_BLACKMAGIC=[ + "-ex", + "monitor swdp_scan", + "-ex", + "monitor debug_bmp enable", + "-ex", + "attach 1", + "-ex", + "set mem inaccessible-by-default off", + ], + GDBPYOPTS=[ + "-ex", + "source debug/FreeRTOS/FreeRTOS.py", + "-ex", + "source debug/flipperapps.py", + "-ex", + "source debug/PyCortexMDebug/PyCortexMDebug.py", + "-ex", + "svd_load ${SVD_FILE}", + "-ex", + "compare-sections", + ], + JFLASHPROJECT="${ROOT_DIR.abspath}/debug/fw.jflash", + ) + + +def exists(env): + return True diff --git a/site_scons/site_tools/fbt_dist.py b/scripts/fbt_tools/fbt_dist.py similarity index 98% rename from site_scons/site_tools/fbt_dist.py rename to scripts/fbt_tools/fbt_dist.py index 2b5c83dfff5..853013e9f30 100644 --- a/site_scons/site_tools/fbt_dist.py +++ b/scripts/fbt_tools/fbt_dist.py @@ -136,7 +136,6 @@ def generate(env): "CoproBuilder": Builder( action=Action( [ - Mkdir("$TARGET"), '${PYTHON3} "${ROOT_DIR.abspath}/scripts/assets.py" ' "copro ${COPRO_CUBE_DIR} " "${TARGET} ${COPRO_MCU_FAMILY} " @@ -145,7 +144,7 @@ def generate(env): '--stack_file="${COPRO_STACK_BIN}" ' "--stack_addr=${COPRO_STACK_ADDR} ", ], - "", + "\tCOPRO\t${TARGET}", ) ), } diff --git a/site_scons/site_tools/fbt_extapps.py b/scripts/fbt_tools/fbt_extapps.py similarity index 96% rename from site_scons/site_tools/fbt_extapps.py rename to scripts/fbt_tools/fbt_extapps.py index a1fa771406f..34fb942abe1 100644 --- a/site_scons/site_tools/fbt_extapps.py +++ b/scripts/fbt_tools/fbt_extapps.py @@ -6,12 +6,10 @@ import os import pathlib from fbt.elfmanifest import assemble_manifest_data -from fbt.appmanifest import FlipperManifestException +from fbt.appmanifest import FlipperApplication, FlipperManifestException from fbt.sdk import SdkCache import itertools -from site_scons.fbt.appmanifest import FlipperApplication - def BuildAppElf(env, app): ext_apps_work_dir = env.subst("$EXT_APPS_WORK_DIR") @@ -111,7 +109,7 @@ def legacy_app_build_stub(**kw): ) app_elf_raw = app_env.Program( - os.path.join(app_work_dir, f"{app.appid}_d"), + os.path.join(ext_apps_work_dir, f"{app.appid}_d"), app_sources, APP_ENTRY=app.entry_point, ) @@ -180,7 +178,7 @@ def validate_app_imports(target, source, env): if unresolved_syms: SCons.Warnings.warn( SCons.Warnings.LinkWarning, - f"{source[0].path}: app won't run. Unresolved symbols: {unresolved_syms}", + f"\033[93m{source[0].path}: app won't run. Unresolved symbols: \033[95m{unresolved_syms}\033[0m", ) diff --git a/scripts/fbt_tools/fbt_help.py b/scripts/fbt_tools/fbt_help.py new file mode 100644 index 00000000000..0475f51bc06 --- /dev/null +++ b/scripts/fbt_tools/fbt_help.py @@ -0,0 +1,44 @@ +targets_help = """Configuration variables: +""" + +tail_help = """ + +TASKS: +Building: + firmware_all, fw_dist: + Build firmware; create distribution package + faps, fap_dist: + Build all FAP apps + fap_{APPID}, launch_app APPSRC={APPID}: + Build FAP app with appid={APPID}; upload & start it over USB + +Flashing & debugging: + flash, flash_blackmagic, jflash: + Flash firmware to target using debug probe + flash_usb, flash_usb_full: + Install firmware using self-update package + debug, debug_other, blackmagic: + Start GDB + +Other: + cli: + Open a Flipper CLI session over USB + firmware_cdb, updater_cdb: + Generate сompilation_database.json + lint, lint_py: + run linters + format, format_py: + run code formatters + +For more targets & info, see documentation/fbt.md +""" + + +def generate(env, **kw): + vars = kw["vars"] + basic_help = vars.GenerateHelpText(env) + env.Help(targets_help + basic_help + tail_help) + + +def exists(env): + return True diff --git a/site_scons/site_tools/fbt_sdk.py b/scripts/fbt_tools/fbt_sdk.py similarity index 71% rename from site_scons/site_tools/fbt_sdk.py rename to scripts/fbt_tools/fbt_sdk.py index f6c2d452e41..ed0abdff674 100644 --- a/site_scons/site_tools/fbt_sdk.py +++ b/scripts/fbt_tools/fbt_sdk.py @@ -9,10 +9,32 @@ import os.path import posixpath import pathlib +import json from fbt.sdk import SdkCollector, SdkCache +def ProcessSdkDepends(env, filename): + try: + with open(filename, "r") as fin: + lines = LogicalLines(fin).readlines() + except IOError: + return [] + + _, depends = lines[0].split(":", 1) + depends = depends.split() + depends.pop(0) # remove the .c file + depends = list( + # Don't create dependency on non-existing files + # (e.g. when they were renamed since last build) + filter( + lambda file: file.exists(), + (env.File(f"#{path}") for path in depends), + ) + ) + return depends + + def prebuild_sdk_emitter(target, source, env): target.append(env.ChangeFileExtension(target[0], ".d")) target.append(env.ChangeFileExtension(target[0], ".i.c")) @@ -25,6 +47,25 @@ def prebuild_sdk_create_origin_file(target, source, env): sdk_c.write("\n".join(f"#include <{h.path}>" for h in env["SDK_HEADERS"])) +class SdkMeta: + def __init__(self, env): + self.env = env + + def save_to(self, json_manifest_path: str): + meta_contents = { + "sdk_symbols": self.env["SDK_DEFINITION"].name, + "cc_args": self._wrap_scons_vars("$CCFLAGS $_CCCOMCOM"), + "cpp_args": self._wrap_scons_vars("$CXXFLAGS $CCFLAGS $_CCCOMCOM"), + "linker_args": self._wrap_scons_vars("$LINKFLAGS"), + } + with open(json_manifest_path, "wt") as f: + json.dump(meta_contents, f, indent=4) + + def _wrap_scons_vars(self, vars: str): + expanded_vars = self.env.subst(vars, target=Entry("dummy")) + return expanded_vars.replace("\\", "/") + + class SdkTreeBuilder: def __init__(self, env, target, source) -> None: self.env = env @@ -34,8 +75,9 @@ def __init__(self, env, target, source) -> None: self.header_depends = [] self.header_dirs = [] - self.target_sdk_dir = env.subst("f${TARGET_HW}_sdk") - self.sdk_deploy_dir = target[0].Dir(self.target_sdk_dir) + self.target_sdk_dir_name = env.subst("f${TARGET_HW}_sdk") + self.sdk_root_dir = target[0].Dir(".") + self.sdk_deploy_dir = self.sdk_root_dir.Dir(self.target_sdk_dir_name) def _parse_sdk_depends(self): deps_file = self.source[0] @@ -50,7 +92,7 @@ def _parse_sdk_depends(self): ) def _generate_sdk_meta(self): - filtered_paths = [self.target_sdk_dir] + filtered_paths = [self.target_sdk_dir_name] full_fw_paths = list( map( os.path.normpath, @@ -62,17 +104,18 @@ def _generate_sdk_meta(self): for dir in full_fw_paths: if dir in sdk_dirs: filtered_paths.append( - posixpath.normpath(posixpath.join(self.target_sdk_dir, dir)) + posixpath.normpath(posixpath.join(self.target_sdk_dir_name, dir)) ) sdk_env = self.env.Clone() sdk_env.Replace(CPPPATH=filtered_paths) - with open(self.target[0].path, "wt") as f: - cmdline_options = sdk_env.subst( - "$CCFLAGS $_CCCOMCOM", target=Entry("dummy") - ) - f.write(cmdline_options.replace("\\", "/")) - f.write("\n") + meta = SdkMeta(sdk_env) + meta.save_to(self.target[0].path) + + def emitter(self, target, source, env): + target_folder = target[0] + target = [target_folder.File("sdk.opts")] + return target, source def _create_deploy_commands(self): dirs_to_create = set( @@ -81,13 +124,17 @@ def _create_deploy_commands(self): actions = [ Delete(self.sdk_deploy_dir), Mkdir(self.sdk_deploy_dir), + Copy( + self.sdk_root_dir, + self.env["SDK_DEFINITION"], + ), ] actions += [Mkdir(d) for d in dirs_to_create] actions += [ - Copy( - self.sdk_deploy_dir.File(h).path, - h, + Action( + Copy(self.sdk_deploy_dir.File(h).path, h), + # f"Copy {h} to {self.sdk_deploy_dir}", ) for h in self.header_depends ] @@ -108,6 +155,11 @@ def deploy_sdk_tree(target, source, env, for_signature): return sdk_tree.generate_actions() +def deploy_sdk_tree_emitter(target, source, env): + sdk_tree = SdkTreeBuilder(env, target, source) + return sdk_tree.emitter(target, source, env) + + def gen_sdk_data(sdk_cache: SdkCache): api_def = [] api_def.extend( @@ -165,6 +217,7 @@ def generate_sdk_symbols(source, target, env): def generate(env, **kw): + env.AddMethod(ProcessSdkDepends) env.Append( BUILDERS={ "SDKPrebuilder": Builder( @@ -183,6 +236,7 @@ def generate(env, **kw): ), "SDKTree": Builder( generator=deploy_sdk_tree, + emitter=deploy_sdk_tree_emitter, src_suffix=".d", ), "SDKSymUpdater": Builder( diff --git a/site_scons/site_tools/fbt_version.py b/scripts/fbt_tools/fbt_version.py similarity index 100% rename from site_scons/site_tools/fbt_version.py rename to scripts/fbt_tools/fbt_version.py diff --git a/site_scons/site_tools/fwbin.py b/scripts/fbt_tools/fwbin.py similarity index 100% rename from site_scons/site_tools/fwbin.py rename to scripts/fbt_tools/fwbin.py diff --git a/site_scons/site_tools/gdb.py b/scripts/fbt_tools/gdb.py similarity index 100% rename from site_scons/site_tools/gdb.py rename to scripts/fbt_tools/gdb.py diff --git a/site_scons/site_tools/jflash.py b/scripts/fbt_tools/jflash.py similarity index 100% rename from site_scons/site_tools/jflash.py rename to scripts/fbt_tools/jflash.py diff --git a/site_scons/site_tools/objdump.py b/scripts/fbt_tools/objdump.py similarity index 100% rename from site_scons/site_tools/objdump.py rename to scripts/fbt_tools/objdump.py diff --git a/site_scons/site_tools/openocd.py b/scripts/fbt_tools/openocd.py similarity index 100% rename from site_scons/site_tools/openocd.py rename to scripts/fbt_tools/openocd.py diff --git a/site_scons/site_tools/python3.py b/scripts/fbt_tools/python3.py similarity index 100% rename from site_scons/site_tools/python3.py rename to scripts/fbt_tools/python3.py diff --git a/site_scons/site_tools/sconsmodular.py b/scripts/fbt_tools/sconsmodular.py similarity index 100% rename from site_scons/site_tools/sconsmodular.py rename to scripts/fbt_tools/sconsmodular.py diff --git a/site_scons/site_tools/sconsrecursiveglob.py b/scripts/fbt_tools/sconsrecursiveglob.py similarity index 100% rename from site_scons/site_tools/sconsrecursiveglob.py rename to scripts/fbt_tools/sconsrecursiveglob.py diff --git a/site_scons/site_tools/strip.py b/scripts/fbt_tools/strip.py similarity index 100% rename from site_scons/site_tools/strip.py rename to scripts/fbt_tools/strip.py diff --git a/scripts/flipper/assets/copro.py b/scripts/flipper/assets/copro.py index 0c78e889d2e..33c3ac237c7 100644 --- a/scripts/flipper/assets/copro.py +++ b/scripts/flipper/assets/copro.py @@ -1,10 +1,9 @@ import logging -import datetime -import shutil import json -from os.path import basename - +from io import BytesIO +import tarfile import xml.etree.ElementTree as ET + from flipper.utils import * from flipper.assets.coprobin import CoproBinary, get_stack_type @@ -51,20 +50,19 @@ def loadCubeInfo(self, cube_dir, cube_version): raise Exception(f"Unsupported cube version") self.version = cube_version + @staticmethod + def _getFileName(name): + return os.path.join("core2_firmware", name) + def addFile(self, array, filename, **kwargs): source_file = os.path.join(self.mcu_copro, filename) - destination_file = os.path.join(self.output_dir, filename) - shutil.copyfile(source_file, destination_file) - array.append( - {"name": filename, "sha256": file_sha256(destination_file), **kwargs} - ) + self.output_tar.add(source_file, arcname=self._getFileName(filename)) + array.append({"name": filename, "sha256": file_sha256(source_file), **kwargs}) + + def bundle(self, output_file, stack_file_name, stack_type, stack_addr=None): + self.output_tar = tarfile.open(output_file, "w:gz") - def bundle(self, output_dir, stack_file_name, stack_type, stack_addr=None): - if not os.path.isdir(output_dir): - raise Exception(f'"{output_dir}" doesn\'t exists') - self.output_dir = output_dir stack_file = os.path.join(self.mcu_copro, stack_file_name) - manifest_file = os.path.join(self.output_dir, "Manifest.json") # Form Manifest manifest = dict(MANIFEST_TEMPLATE) manifest["manifest"]["timestamp"] = timestamp() @@ -105,6 +103,10 @@ def bundle(self, output_dir, stack_file_name, stack_type, stack_addr=None): stack_file_name, address=f"0x{stack_addr:X}", ) - # Save manifest to - with open(manifest_file, "w", newline="\n") as file: - json.dump(manifest, file) + + # Save manifest + manifest_data = json.dumps(manifest, indent=4).encode("utf-8") + info = tarfile.TarInfo(self._getFileName("Manifest.json")) + info.size = len(manifest_data) + self.output_tar.addfile(info, BytesIO(manifest_data)) + self.output_tar.close() diff --git a/scripts/guruguru.py b/scripts/guruguru.py index 3560bcd96fe..c227e17eeb6 100755 --- a/scripts/guruguru.py +++ b/scripts/guruguru.py @@ -17,7 +17,7 @@ def clearConsole(self): async def rebuild(self, line): self.clearConsole() self.logger.info(f"Triggered by: {line}") - proc = await asyncio.create_subprocess_exec("make") + proc = await asyncio.create_subprocess_exec("./fbt") await proc.wait() await asyncio.sleep(1) self.is_building = False diff --git a/scripts/sconsdist.py b/scripts/sconsdist.py index 1e95ee2f23f..45ae03e3ba4 100644 --- a/scripts/sconsdist.py +++ b/scripts/sconsdist.py @@ -1,10 +1,12 @@ #!/usr/bin/env python3 from flipper.app import App -from os.path import join, exists -from os import makedirs +from os.path import join, exists, relpath +from os import makedirs, walk from update import Main as UpdateMain import shutil +import zipfile +import tarfile class ProjectDir: @@ -17,6 +19,8 @@ def __init__(self, project_dir): class Main(App): + DIST_FILE_PREFIX = "flipper-z-" + def init(self): self.subparsers = self.parser.add_subparsers(help="sub-command help") @@ -45,9 +49,13 @@ def init(self): def get_project_filename(self, project, filetype): # Temporary fix project_name = project.project - if project_name == "firmware" and filetype != "elf": - project_name = "full" - return f"flipper-z-{self.target}-{project_name}-{self.args.suffix}.{filetype}" + if project_name == "firmware": + if filetype == "zip": + project_name = "sdk" + elif filetype != "elf": + project_name = "full" + + return f"{self.DIST_FILE_PREFIX}{self.target}-{project_name}-{self.args.suffix}.{filetype}" def get_dist_filepath(self, filename): return join(self.output_dir_path, filename) @@ -56,10 +64,28 @@ def copy_single_project(self, project): obj_directory = join("build", project.dir) for filetype in ("elf", "bin", "dfu", "json"): - shutil.copyfile( - join(obj_directory, f"{project.project}.{filetype}"), - self.get_dist_filepath(self.get_project_filename(project, filetype)), - ) + if exists(src_file := join(obj_directory, f"{project.project}.{filetype}")): + shutil.copyfile( + src_file, + self.get_dist_filepath( + self.get_project_filename(project, filetype) + ), + ) + if exists(sdk_folder := join(obj_directory, "sdk")): + with zipfile.ZipFile( + self.get_dist_filepath(self.get_project_filename(project, "zip")), + "w", + zipfile.ZIP_DEFLATED, + ) as zf: + for root, dirs, files in walk(sdk_folder): + for file in files: + zf.write( + join(root, file), + relpath( + join(root, file), + sdk_folder, + ), + ) def copy(self): self.projects = dict( @@ -103,9 +129,8 @@ def copy(self): ) if self.args.version: - bundle_dir = join( - self.output_dir_path, f"{self.target}-update-{self.args.suffix}" - ) + bundle_dir_name = f"{self.target}-update-{self.args.suffix}" + bundle_dir = join(self.output_dir_path, bundle_dir_name) bundle_args = [ "generate", "-d", @@ -131,10 +156,24 @@ def copy(self): ) ) bundle_args.extend(self.other_args) - self.logger.info( - f"Use this directory to self-update your Flipper:\n\t{bundle_dir}" - ) - return UpdateMain(no_exit=True)(bundle_args) + + if (bundle_result := UpdateMain(no_exit=True)(bundle_args)) == 0: + self.logger.info( + f"Use this directory to self-update your Flipper:\n\t{bundle_dir}" + ) + + # Create tgz archive + with tarfile.open( + join( + self.output_dir_path, + f"{self.DIST_FILE_PREFIX}{bundle_dir_name}.tgz", + ), + "w:gz", + compresslevel=9, + ) as tar: + tar.add(bundle_dir, arcname=bundle_dir_name) + + return bundle_result return 0 diff --git a/site_scons/commandline.scons b/site_scons/commandline.scons index 765af08f18c..324dfe33253 100644 --- a/site_scons/commandline.scons +++ b/site_scons/commandline.scons @@ -81,46 +81,41 @@ vars.AddVariables( help="Enable debug tools to be built", default=False, ), -) - -vars.Add( - "DIST_SUFFIX", - help="Suffix for binaries in build output for dist targets", - default="local", -) - -vars.Add( - "UPDATE_VERSION_STRING", - help="Version string for updater package", - default="${DIST_SUFFIX}", -) - - -vars.Add( - "COPRO_CUBE_VERSION", - help="Cube version", - default="", -) - -vars.Add( - "COPRO_STACK_ADDR", - help="Core2 Firmware address", - default="0", -) - -vars.Add( - "COPRO_STACK_BIN", - help="Core2 Firmware file name", - default="", -) - -vars.Add( - "COPRO_DISCLAIMER", - help="Value to pass to bundling script to confirm dangerous operations", - default="", -) - -vars.AddVariables( + BoolVariable( + "FAP_EXAMPLES", + help="Enable example applications to be built", + default=False, + ), + ( + "DIST_SUFFIX", + "Suffix for binaries in build output for dist targets", + "local", + ), + ( + "UPDATE_VERSION_STRING", + "Version string for updater package", + "${DIST_SUFFIX}", + ), + ( + "COPRO_CUBE_VERSION", + "Cube version", + "", + ), + ( + "COPRO_STACK_ADDR", + "Core2 Firmware address", + "0", + ), + ( + "COPRO_STACK_BIN", + "Core2 Firmware file name", + "", + ), + ( + "COPRO_DISCLAIMER", + "Value to pass to bundling script to confirm dangerous operations", + "", + ), PathVariable( "COPRO_OB_DATA", help="Path to OB reference data", @@ -161,86 +156,75 @@ vars.AddVariables( validator=PathVariable.PathAccept, default="", ), -) - -vars.Add( - "FBT_TOOLCHAIN_VERSIONS", - help="Whitelisted toolchain versions (leave empty for no check)", - default=tuple(), -) - -vars.Add( - "OPENOCD_OPTS", - help="Options to pass to OpenOCD", - default="", -) - -vars.Add( - "BLACKMAGIC", - help="Blackmagic probe location", - default="auto", -) - -vars.Add( - "UPDATE_SPLASH", - help="Directory name with slideshow frames to render after installing update package", - default="update_default", -) - -vars.Add( - "LOADER_AUTOSTART", - help="Application name to automatically run on Flipper boot", - default="", -) - - -vars.Add( - "FIRMWARE_APPS", - help="Map of (configuration_name->application_list)", - default={ - "default": ( - # Svc - "basic_services", - # Apps - "main_apps", - "system_apps", - # Settings - "settings_apps", - # Plugins - # "basic_plugins", - # Debug - # "debug_apps", - ) - }, -) - -vars.Add( - "FIRMWARE_APP_SET", - help="Application set to use from FIRMWARE_APPS", - default="default", -) - -vars.Add( - "APPSRC", - help="Application source directory for app to build & upload", - default="", -) - -# List of tuples (directory, add_to_global_include_path) -vars.Add( - "APPDIRS", - help="Directories to search for firmware components & external apps", - default=[ - ("applications", False), - ("applications/services", True), - ("applications/main", True), - ("applications/settings", False), - ("applications/system", False), - ("applications/debug", False), - ("applications/plugins", False), - ("applications/examples", False), - ("applications_user", False), - ], + ( + "FBT_TOOLCHAIN_VERSIONS", + "Whitelisted toolchain versions (leave empty for no check)", + tuple(), + ), + ( + "OPENOCD_OPTS", + "Options to pass to OpenOCD", + "", + ), + ( + "BLACKMAGIC", + "Blackmagic probe location", + "auto", + ), + ( + "UPDATE_SPLASH", + "Directory name with slideshow frames to render after installing update package", + "update_default", + ), + ( + "LOADER_AUTOSTART", + "Application name to automatically run on Flipper boot", + "", + ), + ( + "FIRMWARE_APPS", + "Map of (configuration_name->application_list)", + { + "default": ( + # Svc + "basic_services", + # Apps + "main_apps", + "system_apps", + # Settings + "settings_apps", + # Plugins + # "basic_plugins", + # Debug + # "debug_apps", + ) + }, + ), + ( + "FIRMWARE_APP_SET", + "Application set to use from FIRMWARE_APPS", + "default", + ), + ( + "APPSRC", + "Application source directory for app to build & upload", + "", + ), + # List of tuples (directory, add_to_global_include_path) + ( + "APPDIRS", + "Directories to search for firmware components & external apps", + [ + ("applications", False), + ("applications/services", True), + ("applications/main", True), + ("applications/settings", False), + ("applications/system", False), + ("applications/debug", False), + ("applications/plugins", False), + ("applications_user", False), + ], + ), ) Return("vars") diff --git a/site_scons/environ.scons b/site_scons/environ.scons index 3e0c6bea786..2d082838a2f 100644 --- a/site_scons/environ.scons +++ b/site_scons/environ.scons @@ -1,6 +1,5 @@ -import SCons from SCons.Platform import TempFileMunge -from fbt import util +from fbt.util import tempfile_arg_esc_func, single_quote, wrap_tempfile import os import multiprocessing @@ -13,14 +12,18 @@ forward_os_env = { } # Proxying CI environment to child processes & scripts variables_to_forward = [ + # CI/CD variables "WORKFLOW_BRANCH_OR_TAG", "DIST_SUFFIX", + # Python & other tools "HOME", "APPDATA", "PYTHONHOME", "PYTHONNOUSERSITE", "TMP", "TEMP", + # Colors for tools + "TERM", ] if proxy_env := GetOption("proxy_env"): variables_to_forward.extend(proxy_env.split(",")) @@ -79,7 +82,7 @@ if not coreenv["VERBOSE"]: SetOption("num_jobs", multiprocessing.cpu_count()) # Avoiding re-scan of all sources on every startup SetOption("implicit_cache", True) -SetOption("implicit_deps_unchanged", True) +# SetOption("implicit_deps_unchanged", True) # More aggressive caching SetOption("max_drift", 1) # Random task queue - to discover isses with build logic faster @@ -87,10 +90,10 @@ SetOption("max_drift", 1) # Setting up temp file parameters - to overcome command line length limits -coreenv["TEMPFILEARGESCFUNC"] = util.tempfile_arg_esc_func -util.wrap_tempfile(coreenv, "LINKCOM") -util.wrap_tempfile(coreenv, "ARCOM") +coreenv["TEMPFILEARGESCFUNC"] = tempfile_arg_esc_func +wrap_tempfile(coreenv, "LINKCOM") +wrap_tempfile(coreenv, "ARCOM") -coreenv["SINGLEQUOTEFUNC"] = util.single_quote +coreenv["SINGLEQUOTEFUNC"] = single_quote Return("coreenv") diff --git a/site_scons/extapps.scons b/site_scons/extapps.scons index c976fbbe5b6..9edc6b5c17a 100644 --- a/site_scons/extapps.scons +++ b/site_scons/extapps.scons @@ -21,7 +21,7 @@ appenv = ENV.Clone( ) appenv.Replace( - LINKER_SCRIPT="application-ext", + LINKER_SCRIPT="application_ext", ) appenv.AppendUnique( @@ -106,6 +106,7 @@ appenv.PhonyTarget("firmware_extapps", appenv.Action(legacy_app_build_stub, None Alias("faps", extapps["compact"].values()) +Alias("faps", extapps["validators"].values()) if appsrc := appenv.subst("$APPSRC"): app_manifest, fap_file, app_validator = appenv.GetExtAppFromPath(appsrc) diff --git a/site_scons/fbt_extra/util.py b/site_scons/fbt_extra/util.py new file mode 100644 index 00000000000..aa3d50b648a --- /dev/null +++ b/site_scons/fbt_extra/util.py @@ -0,0 +1,23 @@ +from fbt.util import link_dir + + +def link_elf_dir_as_latest(env, elf_node): + elf_dir = elf_node.Dir(".") + latest_dir = env.Dir("#build/latest") + print(f"Setting {elf_dir} as latest built dir (./build/latest/)") + return link_dir(latest_dir.abspath, elf_dir.abspath, env["PLATFORM"] == "win32") + + +def should_gen_cdb_and_link_dir(env, requested_targets): + explicitly_building_updater = False + # Hacky way to check if updater-related targets were requested + for build_target in requested_targets: + if "updater" in str(build_target): + explicitly_building_updater = True + + is_updater = not env["IS_BASE_FIRMWARE"] + # If updater is explicitly requested, link to the latest updater + # Otherwise, link to firmware + return (is_updater and explicitly_building_updater) or ( + not is_updater and not explicitly_building_updater + ) From ede3bac799f8c2c7e1184b09473fd0ecc1d2c511 Mon Sep 17 00:00:00 2001 From: Thibaut CHARLES Date: Wed, 12 Oct 2022 18:21:54 +0200 Subject: [PATCH 138/824] Badusb: show script errors on screen (#1506) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- applications/main/bad_usb/bad_usb_script.c | 39 +++++++++++++++++-- applications/main/bad_usb/bad_usb_script.h | 1 + .../main/bad_usb/views/bad_usb_view.c | 1 + 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/applications/main/bad_usb/bad_usb_script.c b/applications/main/bad_usb/bad_usb_script.c index 78aba88ed8d..8ff38ef66e7 100644 --- a/applications/main/bad_usb/bad_usb_script.c +++ b/applications/main/bad_usb/bad_usb_script.c @@ -231,7 +231,8 @@ static uint16_t ducky_get_keycode(const char* param, bool accept_chars) { return 0; } -static int32_t ducky_parse_line(BadUsbScript* bad_usb, FuriString* line) { +static int32_t + ducky_parse_line(BadUsbScript* bad_usb, FuriString* line, char* error, size_t error_len) { uint32_t line_len = furi_string_size(line); const char* line_tmp = furi_string_get_cstr(line); bool state = false; @@ -261,6 +262,9 @@ static int32_t ducky_parse_line(BadUsbScript* bad_usb, FuriString* line) { if((state) && (delay_val > 0)) { return (int32_t)delay_val; } + if(error != NULL) { + snprintf(error, error_len, "Invalid number %s", line_tmp); + } return SCRIPT_STATE_ERROR; } else if( (strncmp(line_tmp, ducky_cmd_defdelay_1, strlen(ducky_cmd_defdelay_1)) == 0) || @@ -268,17 +272,26 @@ static int32_t ducky_parse_line(BadUsbScript* bad_usb, FuriString* line) { // DEFAULT_DELAY line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; state = ducky_get_number(line_tmp, &bad_usb->defdelay); + if(!state && error != NULL) { + snprintf(error, error_len, "Invalid number %s", line_tmp); + } return (state) ? (0) : SCRIPT_STATE_ERROR; } else if(strncmp(line_tmp, ducky_cmd_string, strlen(ducky_cmd_string)) == 0) { // STRING line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; state = ducky_string(line_tmp); + if(!state && error != NULL) { + snprintf(error, error_len, "Invalid string %s", line_tmp); + } return (state) ? (0) : SCRIPT_STATE_ERROR; } else if(strncmp(line_tmp, ducky_cmd_altchar, strlen(ducky_cmd_altchar)) == 0) { // ALTCHAR line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; ducky_numlock_on(); state = ducky_altchar(line_tmp); + if(!state && error != NULL) { + snprintf(error, error_len, "Invalid altchar %s", line_tmp); + } return (state) ? (0) : SCRIPT_STATE_ERROR; } else if( (strncmp(line_tmp, ducky_cmd_altstr_1, strlen(ducky_cmd_altstr_1)) == 0) || @@ -287,11 +300,17 @@ static int32_t ducky_parse_line(BadUsbScript* bad_usb, FuriString* line) { line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; ducky_numlock_on(); state = ducky_altstring(line_tmp); + if(!state && error != NULL) { + snprintf(error, error_len, "Invalid altstring %s", line_tmp); + } return (state) ? (0) : SCRIPT_STATE_ERROR; } else if(strncmp(line_tmp, ducky_cmd_repeat, strlen(ducky_cmd_repeat)) == 0) { // REPEAT line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; state = ducky_get_number(line_tmp, &bad_usb->repeat_cnt); + if(!state && error != NULL) { + snprintf(error, error_len, "Invalid number %s", line_tmp); + } return (state) ? (0) : SCRIPT_STATE_ERROR; } else if(strncmp(line_tmp, ducky_cmd_sysrq, strlen(ducky_cmd_sysrq)) == 0) { // SYSRQ @@ -304,7 +323,12 @@ static int32_t ducky_parse_line(BadUsbScript* bad_usb, FuriString* line) { } else { // Special keys + modifiers uint16_t key = ducky_get_keycode(line_tmp, false); - if(key == HID_KEYBOARD_NONE) return SCRIPT_STATE_ERROR; + if(key == HID_KEYBOARD_NONE) { + if(error != NULL) { + snprintf(error, error_len, "No keycode defined for %s", line_tmp); + } + return SCRIPT_STATE_ERROR; + } if((key & 0xFF00) != 0) { // It's a modifier key line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; @@ -314,6 +338,9 @@ static int32_t ducky_parse_line(BadUsbScript* bad_usb, FuriString* line) { furi_hal_hid_kb_release(key); return (0); } + if(error != NULL) { + strncpy(error, "Unknown error", error_len); + } return SCRIPT_STATE_ERROR; } @@ -392,7 +419,8 @@ static int32_t ducky_script_execute_next(BadUsbScript* bad_usb, File* script_fil if(bad_usb->repeat_cnt > 0) { bad_usb->repeat_cnt--; - delay_val = ducky_parse_line(bad_usb, bad_usb->line_prev); + delay_val = ducky_parse_line( + bad_usb, bad_usb->line_prev, bad_usb->st.error, sizeof(bad_usb->st.error)); if(delay_val == SCRIPT_STATE_NEXT_LINE) { // Empty line return 0; } else if(delay_val < 0) { // Script error @@ -426,7 +454,9 @@ static int32_t ducky_script_execute_next(BadUsbScript* bad_usb, File* script_fil bad_usb->st.line_cur++; bad_usb->buf_len = bad_usb->buf_len + bad_usb->buf_start - (i + 1); bad_usb->buf_start = i + 1; - delay_val = ducky_parse_line(bad_usb, bad_usb->line); + delay_val = ducky_parse_line( + bad_usb, bad_usb->line, bad_usb->st.error, sizeof(bad_usb->st.error)); + if(delay_val < 0) { bad_usb->st.error_line = bad_usb->st.line_cur; FURI_LOG_E(WORKER_TAG, "Unknown command at line %u", bad_usb->st.line_cur); @@ -602,6 +632,7 @@ BadUsbScript* bad_usb_script_open(FuriString* file_path) { furi_string_set(bad_usb->file_path, file_path); bad_usb->st.state = BadUsbStateInit; + bad_usb->st.error[0] = '\0'; bad_usb->thread = furi_thread_alloc(); furi_thread_set_name(bad_usb->thread, "BadUsbWorker"); diff --git a/applications/main/bad_usb/bad_usb_script.h b/applications/main/bad_usb/bad_usb_script.h index 5fee6505f51..f24372fab6b 100644 --- a/applications/main/bad_usb/bad_usb_script.h +++ b/applications/main/bad_usb/bad_usb_script.h @@ -25,6 +25,7 @@ typedef struct { uint16_t line_nb; uint32_t delay_remain; uint16_t error_line; + char error[64]; } BadUsbState; BadUsbScript* bad_usb_script_open(FuriString* file_path); diff --git a/applications/main/bad_usb/views/bad_usb_view.c b/applications/main/bad_usb/views/bad_usb_view.c index db10d01eea8..6c6a15847b0 100644 --- a/applications/main/bad_usb/views/bad_usb_view.c +++ b/applications/main/bad_usb/views/bad_usb_view.c @@ -53,6 +53,7 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) { canvas_draw_str_aligned( canvas, 127, 46, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); furi_string_reset(disp_str); + canvas_draw_str_aligned(canvas, 127, 56, AlignRight, AlignBottom, model->state.error); } else if(model->state.state == BadUsbStateIdle) { canvas_draw_icon(canvas, 4, 22, &I_Smile_18x18); canvas_set_font(canvas, FontBigNumbers); From 50dc2d738991205f2d05905517fac96c619c3b5a Mon Sep 17 00:00:00 2001 From: Luke Williams Date: Wed, 12 Oct 2022 11:31:54 -0500 Subject: [PATCH 139/824] 36-bit AWID (L11601 Lenel) (#1838) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 36-bit AWID * Rfid: correct vendor name AWIG -> AWID Co-authored-by: Sergey Gavrilov Co-authored-by: あく --- lib/lfrfid/protocols/protocol_awid.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/lfrfid/protocols/protocol_awid.c b/lib/lfrfid/protocols/protocol_awid.c index 88093900ee7..38c7793b853 100644 --- a/lib/lfrfid/protocols/protocol_awid.c +++ b/lib/lfrfid/protocols/protocol_awid.c @@ -81,7 +81,7 @@ static bool protocol_awid_can_be_decoded(uint8_t* data) { // Avoid detection for invalid formats uint8_t len = bit_lib_get_bits(data, 8, 8); - if(len != 26 && len != 50 && len != 37 && len != 34) break; + if(len != 26 && len != 50 && len != 37 && len != 34 && len != 36) break; result = true; } while(false); @@ -207,7 +207,7 @@ bool protocol_awid_write_data(ProtocolAwid* protocol, void* data) { // Fix incorrect length byte if(protocol->data[0] != 26 && protocol->data[0] != 50 && protocol->data[0] != 37 && - protocol->data[0] != 34) { + protocol->data[0] != 34 && protocol->data[0] != 36 ) { protocol->data[0] = 26; } @@ -232,7 +232,7 @@ bool protocol_awid_write_data(ProtocolAwid* protocol, void* data) { const ProtocolBase protocol_awid = { .name = "AWID", - .manufacturer = "AWIG", + .manufacturer = "AWID", .data_size = AWID_DECODED_DATA_SIZE, .features = LFRFIDFeatureASK, .validate_count = 3, From 8fdee1e46065036caff72194ad887b14638effb8 Mon Sep 17 00:00:00 2001 From: hedger Date: Thu, 13 Oct 2022 19:05:07 +0400 Subject: [PATCH 140/824] Scripts: simpler tar format (#1871) * scriptsL simpler tar format * scripts: shorter names for files in update bundle * scripts: limiting max OTA package dir name length to 80 * scripts: resource bundle: checks for file name length * scripts: made resource packing errors critical --- scripts/flipper/assets/copro.py | 2 +- scripts/sconsdist.py | 6 +++++- scripts/update.py | 37 +++++++++++++++++++++++++-------- 3 files changed, 34 insertions(+), 11 deletions(-) diff --git a/scripts/flipper/assets/copro.py b/scripts/flipper/assets/copro.py index 33c3ac237c7..d39f3033359 100644 --- a/scripts/flipper/assets/copro.py +++ b/scripts/flipper/assets/copro.py @@ -60,7 +60,7 @@ def addFile(self, array, filename, **kwargs): array.append({"name": filename, "sha256": file_sha256(source_file), **kwargs}) def bundle(self, output_file, stack_file_name, stack_type, stack_addr=None): - self.output_tar = tarfile.open(output_file, "w:gz") + self.output_tar = tarfile.open(output_file, "w:gz", format=tarfile.USTAR_FORMAT) stack_file = os.path.join(self.mcu_copro, stack_file_name) # Form Manifest diff --git a/scripts/sconsdist.py b/scripts/sconsdist.py index 45ae03e3ba4..cb4f8f5a5e3 100644 --- a/scripts/sconsdist.py +++ b/scripts/sconsdist.py @@ -20,6 +20,7 @@ def __init__(self, project_dir): class Main(App): DIST_FILE_PREFIX = "flipper-z-" + DIST_FOLDER_MAX_NAME_LENGTH = 80 def init(self): self.subparsers = self.parser.add_subparsers(help="sub-command help") @@ -129,7 +130,9 @@ def copy(self): ) if self.args.version: - bundle_dir_name = f"{self.target}-update-{self.args.suffix}" + bundle_dir_name = f"{self.target}-update-{self.args.suffix}"[ + : self.DIST_FOLDER_MAX_NAME_LENGTH + ] bundle_dir = join(self.output_dir_path, bundle_dir_name) bundle_args = [ "generate", @@ -170,6 +173,7 @@ def copy(self): ), "w:gz", compresslevel=9, + format=tarfile.USTAR_FORMAT, ) as tar: tar.add(bundle_dir, arcname=bundle_dir_name) diff --git a/scripts/update.py b/scripts/update.py index 52391965be8..6be1dce066a 100755 --- a/scripts/update.py +++ b/scripts/update.py @@ -22,6 +22,7 @@ class Main(App): RESOURCE_TAR_MODE = "w:" RESOURCE_TAR_FORMAT = tarfile.USTAR_FORMAT RESOURCE_FILE_NAME = "resources.tar" + RESOURCE_ENTRY_NAME_MAX_LENGTH = 100 WHITELISTED_STACK_TYPES = set( map( @@ -76,9 +77,9 @@ def init(self): self.parser_generate.set_defaults(func=self.generate) def generate(self): - stage_basename = basename(self.args.stage) - dfu_basename = basename(self.args.dfu) - radiobin_basename = basename(self.args.radiobin) + stage_basename = "updater.bin" # used to be basename(self.args.stage) + dfu_basename = "firmware.dfu" # used to be basename(self.args.dfu) + radiobin_basename = "radio.bin" # used to be basename(self.args.radiobin) resources_basename = "" radio_version = 0 @@ -120,9 +121,10 @@ def generate(self): ) if self.args.resources: resources_basename = self.RESOURCE_FILE_NAME - self.package_resources( + if not self.package_resources( self.args.resources, join(self.args.directory, resources_basename) - ) + ): + return 3 if not self.layout_check(dfu_size, radio_addr): self.logger.warn("Memory layout looks suspicious") @@ -199,11 +201,28 @@ def disclaimer(self): "Please confirm that you REALLY want to do that with --I-understand-what-I-am-doing=yes" ) + def _tar_filter(self, tarinfo: tarfile.TarInfo): + if len(tarinfo.name) > self.RESOURCE_ENTRY_NAME_MAX_LENGTH: + self.logger.error( + f"Cannot package resource: name '{tarinfo.name}' too long" + ) + raise ValueError("Resource name too long") + return tarinfo + def package_resources(self, srcdir: str, dst_name: str): - with tarfile.open( - dst_name, self.RESOURCE_TAR_MODE, format=self.RESOURCE_TAR_FORMAT - ) as tarball: - tarball.add(srcdir, arcname="") + try: + with tarfile.open( + dst_name, self.RESOURCE_TAR_MODE, format=self.RESOURCE_TAR_FORMAT + ) as tarball: + tarball.add( + srcdir, + arcname="", + filter=self._tar_filter, + ) + return True + except ValueError as e: + self.logger.error(f"Cannot package resources: {e}") + return False @staticmethod def copro_version_as_int(coprometa, stacktype): From e46e6f8ee90109315af75f89595f0921e5be8494 Mon Sep 17 00:00:00 2001 From: Dzhos Oleksii <35292229+Programistich@users.noreply.github.com> Date: Thu, 13 Oct 2022 18:34:27 +0300 Subject: [PATCH 141/824] Update title for web updater (#1872) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8fb67ed1aa5..7d616295480 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -127,7 +127,7 @@ jobs: **Compiled firmware for commit `${{steps.names.outputs.commit_sha}}`:** - [📦 Update package](https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.branch_name}}/flipper-z-${{steps.names.outputs.default_target}}-update-${{steps.names.outputs.suffix}}.tgz) - [📥 DFU file](https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.branch_name}}/flipper-z-${{steps.names.outputs.default_target}}-full-${{steps.names.outputs.suffix}}.dfu) - - [☁️ Web updater](https://my.flipp.dev/?url=https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.branch_name}}/flipper-z-${{steps.names.outputs.default_target}}-update-${{steps.names.outputs.suffix}}.tgz&channel=${{steps.names.outputs.branch_name}}&version=${{steps.names.outputs.commit_sha}}) + - [☁️ Web/App updater](https://my.flipp.dev/?url=https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.branch_name}}/flipper-z-${{steps.names.outputs.default_target}}-update-${{steps.names.outputs.suffix}}.tgz&channel=${{steps.names.outputs.branch_name}}&version=${{steps.names.outputs.commit_sha}}) edit-mode: replace compact: From 55f8beef9f842d1b8a633562330490fe6c55a79c Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Thu, 13 Oct 2022 19:23:29 +0300 Subject: [PATCH 142/824] [FL-2876] MFC Improvements Part 2/2 (#1868) * Remove keys incorrectly added by the key cache * Improve responsiveness while checking for re-used keys and fix skipping keys when card is removed * Actually check if the card is completely read * Discard incorrect keys on a lower level * nfc: clean up Co-authored-by: gornekich --- lib/nfc/nfc_worker.c | 78 +++++++++++++++++++----------- lib/nfc/protocols/mifare_classic.c | 39 ++++++++++++++- lib/nfc/protocols/mifare_classic.h | 4 ++ 3 files changed, 92 insertions(+), 29 deletions(-) diff --git a/lib/nfc/nfc_worker.c b/lib/nfc/nfc_worker.c index 55d67c6577a..ebe203905fd 100644 --- a/lib/nfc/nfc_worker.c +++ b/lib/nfc/nfc_worker.c @@ -191,7 +191,7 @@ static bool nfc_worker_read_mf_classic(NfcWorker* nfc_worker, FuriHalNfcTxRxCont uint8_t sectors_total = mf_classic_get_total_sectors_num(nfc_worker->dev_data->mf_classic_data.type); FURI_LOG_I(TAG, "Read %d sectors out of %d total", sectors_read, sectors_total); - read_success = (sectors_read == sectors_total); + read_success = mf_classic_is_card_read(&nfc_worker->dev_data->mf_classic_data); } } while(false); @@ -480,6 +480,9 @@ static void nfc_worker_mf_classic_key_attack( uint16_t start_sector) { furi_assert(nfc_worker); + bool card_found_notified = true; + bool card_removed_notified = false; + MfClassicData* data = &nfc_worker->dev_data->mf_classic_data; uint32_t total_sectors = mf_classic_get_total_sectors_num(data->type); @@ -487,36 +490,52 @@ static void nfc_worker_mf_classic_key_attack( // Check every sector's A and B keys with the given key for(size_t i = start_sector; i < total_sectors; i++) { - uint8_t block_num = mf_classic_get_sector_trailer_block_num_by_sector(i); - if(mf_classic_is_sector_read(data, i)) continue; - if(!mf_classic_is_key_found(data, i, MfClassicKeyA)) { - FURI_LOG_D( - TAG, - "Trying A key for sector %d, key: %04lx%08lx", - i, - (uint32_t)(key >> 32), - (uint32_t)key); - if(mf_classic_authenticate(tx_rx, block_num, key, MfClassicKeyA)) { - mf_classic_set_key_found(data, i, MfClassicKeyA, key); - FURI_LOG_D(TAG, "Key found"); - nfc_worker->callback(NfcWorkerEventFoundKeyA, nfc_worker->context); + furi_hal_nfc_sleep(); + if(furi_hal_nfc_activate_nfca(200, NULL)) { + furi_hal_nfc_sleep(); + if(!card_found_notified) { + nfc_worker->callback(NfcWorkerEventCardDetected, nfc_worker->context); + card_found_notified = true; + card_removed_notified = false; } - } - if(!mf_classic_is_key_found(data, i, MfClassicKeyB)) { - FURI_LOG_D( - TAG, - "Trying B key for sector %d, key: %04lx%08lx", - i, - (uint32_t)(key >> 32), - (uint32_t)key); - if(mf_classic_authenticate(tx_rx, block_num, key, MfClassicKeyB)) { - mf_classic_set_key_found(data, i, MfClassicKeyB, key); - FURI_LOG_D(TAG, "Key found"); - nfc_worker->callback(NfcWorkerEventFoundKeyB, nfc_worker->context); + uint8_t block_num = mf_classic_get_sector_trailer_block_num_by_sector(i); + if(mf_classic_is_sector_read(data, i)) continue; + if(!mf_classic_is_key_found(data, i, MfClassicKeyA)) { + FURI_LOG_D( + TAG, + "Trying A key for sector %d, key: %04lx%08lx", + i, + (uint32_t)(key >> 32), + (uint32_t)key); + if(mf_classic_authenticate(tx_rx, block_num, key, MfClassicKeyA)) { + mf_classic_set_key_found(data, i, MfClassicKeyA, key); + FURI_LOG_D(TAG, "Key found"); + nfc_worker->callback(NfcWorkerEventFoundKeyA, nfc_worker->context); + } + } + if(!mf_classic_is_key_found(data, i, MfClassicKeyB)) { + FURI_LOG_D( + TAG, + "Trying B key for sector %d, key: %04lx%08lx", + i, + (uint32_t)(key >> 32), + (uint32_t)key); + if(mf_classic_authenticate(tx_rx, block_num, key, MfClassicKeyB)) { + mf_classic_set_key_found(data, i, MfClassicKeyB, key); + FURI_LOG_D(TAG, "Key found"); + nfc_worker->callback(NfcWorkerEventFoundKeyB, nfc_worker->context); + } + } + + if(mf_classic_is_sector_read(data, i)) continue; + mf_classic_read_sector(tx_rx, data, i); + } else { + if(!card_removed_notified) { + nfc_worker->callback(NfcWorkerEventNoCardDetected, nfc_worker->context); + card_removed_notified = true; + card_found_notified = false; } } - if(mf_classic_is_sector_read(data, i)) continue; - mf_classic_read_sector(tx_rx, data, i); if(nfc_worker->state != NfcWorkerStateMfClassicDictAttack) break; } } @@ -530,6 +549,7 @@ void nfc_worker_mf_classic_dict_attack(NfcWorker* nfc_worker) { &nfc_worker->dev_data->mf_classic_dict_attack_data; uint32_t total_sectors = mf_classic_get_total_sectors_num(data->type); uint64_t key = 0; + uint64_t prev_key = 0; FuriHalNfcTxRxContext tx_rx = {}; bool card_found_notified = true; bool card_removed_notified = false; @@ -564,6 +584,7 @@ void nfc_worker_mf_classic_dict_attack(NfcWorker* nfc_worker) { nfc_worker->callback(NfcWorkerEventCardDetected, nfc_worker->context); card_found_notified = true; card_removed_notified = false; + nfc_worker_mf_classic_key_attack(nfc_worker, prev_key, &tx_rx, i); } FURI_LOG_D( TAG, @@ -600,6 +621,7 @@ void nfc_worker_mf_classic_dict_attack(NfcWorker* nfc_worker) { } if(nfc_worker->state != NfcWorkerStateMfClassicDictAttack) break; } + memcpy(&prev_key, &key, sizeof(key)); } if(nfc_worker->state != NfcWorkerStateMfClassicDictAttack) break; mf_classic_read_sector(&tx_rx, data, i); diff --git a/lib/nfc/protocols/mifare_classic.c b/lib/nfc/protocols/mifare_classic.c index 783202451d0..e879ff4ef41 100644 --- a/lib/nfc/protocols/mifare_classic.c +++ b/lib/nfc/protocols/mifare_classic.c @@ -155,6 +155,16 @@ void mf_classic_set_key_found( } } +void mf_classic_set_key_not_found(MfClassicData* data, uint8_t sector_num, MfClassicKey key_type) { + furi_assert(data); + + if(key_type == MfClassicKeyA) { + FURI_BIT_CLEAR(data->key_a_mask, sector_num); + } else if(key_type == MfClassicKeyB) { + FURI_BIT_CLEAR(data->key_b_mask, sector_num); + } +} + bool mf_classic_is_sector_read(MfClassicData* data, uint8_t sector_num) { furi_assert(data); @@ -203,6 +213,18 @@ void mf_classic_get_read_sectors_and_keys( } } +bool mf_classic_is_card_read(MfClassicData* data) { + furi_assert(data); + + uint8_t sectors_total = mf_classic_get_total_sectors_num(data->type); + uint8_t sectors_read = 0; + uint8_t keys_found = 0; + mf_classic_get_read_sectors_and_keys(data, §ors_read, &keys_found); + bool card_read = (sectors_read == sectors_total) && (keys_found == sectors_total * 2); + + return card_read; +} + static bool mf_classic_is_allowed_access_sector_trailer( MfClassicEmulator* emulator, uint8_t block_num, @@ -612,7 +634,15 @@ static bool mf_classic_read_sector_with_reader( } // Auth to first block in sector - if(!mf_classic_auth(tx_rx, first_block, key, key_type, crypto)) break; + if(!mf_classic_auth(tx_rx, first_block, key, key_type, crypto)) { + // Set key to MF_CLASSIC_NO_KEY to prevent further attempts + if(key_type == MfClassicKeyA) { + sector_reader->key_a = MF_CLASSIC_NO_KEY; + } else { + sector_reader->key_b = MF_CLASSIC_NO_KEY; + } + break; + } sector->total_blocks = mf_classic_get_blocks_num_in_sector(sector_reader->sector_num); // Read blocks @@ -711,6 +741,13 @@ uint8_t mf_classic_update_card(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data mf_classic_set_block_read(data, first_block + j, &temp_sector.block[j]); } sectors_read++; + } else { + // Invalid key, set it to not found + if(key_a != MF_CLASSIC_NO_KEY) { + mf_classic_set_key_not_found(data, i, MfClassicKeyA); + } else { + mf_classic_set_key_not_found(data, i, MfClassicKeyB); + } } } } diff --git a/lib/nfc/protocols/mifare_classic.h b/lib/nfc/protocols/mifare_classic.h index b9921fb1ca5..ead846e4267 100644 --- a/lib/nfc/protocols/mifare_classic.h +++ b/lib/nfc/protocols/mifare_classic.h @@ -98,12 +98,16 @@ void mf_classic_set_key_found( MfClassicKey key_type, uint64_t key); +void mf_classic_set_key_not_found(MfClassicData* data, uint8_t sector_num, MfClassicKey key_type); + bool mf_classic_is_block_read(MfClassicData* data, uint8_t block_num); void mf_classic_set_block_read(MfClassicData* data, uint8_t block_num, MfClassicBlock* block_data); bool mf_classic_is_sector_read(MfClassicData* data, uint8_t sector_num); +bool mf_classic_is_card_read(MfClassicData* data); + void mf_classic_get_read_sectors_and_keys( MfClassicData* data, uint8_t* sectors_read, From d5b239595fd40f06d9f9a79e3163886723a3c336 Mon Sep 17 00:00:00 2001 From: David Date: Thu, 13 Oct 2022 13:09:37 -0500 Subject: [PATCH 143/824] Desktop: Set external apps as favorites (#1816) * MVP * Display app name and icon in browser * Pre-select favorites in submenu and browser * Show animation while external favorite is loading * A little birdie told me... (Missing record_close) * DesktopSettings: reset submenu before running dialog Co-authored-by: Aleksandr Kutuzov --- applications/main/fap_loader/fap_loader_app.c | 1 + .../services/desktop/desktop_settings.h | 14 ++- .../desktop/scenes/desktop_scene_main.c | 26 +++-- .../desktop_settings/desktop_settings_app.c | 2 + .../desktop_settings/desktop_settings_app.h | 2 + .../scenes/desktop_settings_scene_favorite.c | 108 ++++++++++++++++-- 6 files changed, 130 insertions(+), 23 deletions(-) diff --git a/applications/main/fap_loader/fap_loader_app.c b/applications/main/fap_loader/fap_loader_app.c index d2f6b1f4190..6c909aeeb25 100644 --- a/applications/main/fap_loader/fap_loader_app.c +++ b/applications/main/fap_loader/fap_loader_app.c @@ -182,6 +182,7 @@ int32_t fap_loader_app(void* p) { FapLoader* loader; if(p) { loader = fap_loader_alloc((const char*)p); + view_dispatcher_switch_to_view(loader->view_dispatcher, 0); fap_loader_run_selected_app(loader); } else { loader = fap_loader_alloc(EXT_PATH("apps")); diff --git a/applications/services/desktop/desktop_settings.h b/applications/services/desktop/desktop_settings.h index 3b10fbb17ea..e502c35f23b 100644 --- a/applications/services/desktop/desktop_settings.h +++ b/applications/services/desktop/desktop_settings.h @@ -8,7 +8,7 @@ #include #include -#define DESKTOP_SETTINGS_VER (5) +#define DESKTOP_SETTINGS_VER (6) #define DESKTOP_SETTINGS_PATH INT_PATH(DESKTOP_SETTINGS_FILE_NAME) #define DESKTOP_SETTINGS_MAGIC (0x17) @@ -34,6 +34,9 @@ #define MAX_PIN_SIZE 10 #define MIN_PIN_SIZE 4 +#define MAX_APP_LENGTH 128 + +#define FAP_LOADER_APP_NAME "Applications" typedef struct { InputKey data[MAX_PIN_SIZE]; @@ -41,8 +44,13 @@ typedef struct { } PinCode; typedef struct { - uint16_t favorite_primary; - uint16_t favorite_secondary; + bool is_external; + char name_or_path[MAX_APP_LENGTH]; +} FavoriteApp; + +typedef struct { + FavoriteApp favorite_primary; + FavoriteApp favorite_secondary; PinCode pin_code; uint8_t is_locked; uint32_t auto_lock_delay_ms; diff --git a/applications/services/desktop/scenes/desktop_scene_main.c b/applications/services/desktop/scenes/desktop_scene_main.c index dc9ac04d0ee..654de44d578 100644 --- a/applications/services/desktop/scenes/desktop_scene_main.c +++ b/applications/services/desktop/scenes/desktop_scene_main.c @@ -114,29 +114,39 @@ bool desktop_scene_main_on_event(void* context, SceneManagerEvent event) { case DesktopMainEventOpenFavoritePrimary: DESKTOP_SETTINGS_LOAD(&desktop->settings); - if(desktop->settings.favorite_primary < FLIPPER_APPS_COUNT) { + if(desktop->settings.favorite_primary.is_external) { LoaderStatus status = loader_start( - desktop->loader, FLIPPER_APPS[desktop->settings.favorite_primary].name, NULL); + desktop->loader, + FAP_LOADER_APP_NAME, + desktop->settings.favorite_primary.name_or_path); if(status != LoaderStatusOk) { FURI_LOG_E(TAG, "loader_start failed: %d", status); } } else { - FURI_LOG_E(TAG, "Can't find primary favorite application"); + LoaderStatus status = loader_start( + desktop->loader, desktop->settings.favorite_primary.name_or_path, NULL); + if(status != LoaderStatusOk) { + FURI_LOG_E(TAG, "loader_start failed: %d", status); + } } consumed = true; break; case DesktopMainEventOpenFavoriteSecondary: DESKTOP_SETTINGS_LOAD(&desktop->settings); - if(desktop->settings.favorite_secondary < FLIPPER_APPS_COUNT) { + if(desktop->settings.favorite_secondary.is_external) { LoaderStatus status = loader_start( desktop->loader, - FLIPPER_APPS[desktop->settings.favorite_secondary].name, - NULL); + FAP_LOADER_APP_NAME, + desktop->settings.favorite_secondary.name_or_path); if(status != LoaderStatusOk) { FURI_LOG_E(TAG, "loader_start failed: %d", status); } } else { - FURI_LOG_E(TAG, "Can't find secondary favorite application"); + LoaderStatus status = loader_start( + desktop->loader, desktop->settings.favorite_secondary.name_or_path, NULL); + if(status != LoaderStatusOk) { + FURI_LOG_E(TAG, "loader_start failed: %d", status); + } } consumed = true; break; @@ -166,7 +176,7 @@ bool desktop_scene_main_on_event(void* context, SceneManagerEvent event) { } case DesktopMainEventOpenGameMenu: { LoaderStatus status = loader_start( - desktop->loader, "Applications", EXT_PATH("/apps/Games/snake_game.fap")); + desktop->loader, FAP_LOADER_APP_NAME, EXT_PATH("/apps/Games/snake_game.fap")); if(status != LoaderStatusOk) { FURI_LOG_E(TAG, "loader_start failed: %d", status); } diff --git a/applications/settings/desktop_settings/desktop_settings_app.c b/applications/settings/desktop_settings/desktop_settings_app.c index a2ed8b7240b..afb5d59ec69 100644 --- a/applications/settings/desktop_settings/desktop_settings_app.c +++ b/applications/settings/desktop_settings/desktop_settings_app.c @@ -22,6 +22,7 @@ DesktopSettingsApp* desktop_settings_app_alloc() { DesktopSettingsApp* app = malloc(sizeof(DesktopSettingsApp)); app->gui = furi_record_open(RECORD_GUI); + app->dialogs = furi_record_open(RECORD_DIALOGS); app->view_dispatcher = view_dispatcher_alloc(); app->scene_manager = scene_manager_alloc(&desktop_settings_scene_handlers, app); view_dispatcher_enable_queue(app->view_dispatcher); @@ -83,6 +84,7 @@ void desktop_settings_app_free(DesktopSettingsApp* app) { view_dispatcher_free(app->view_dispatcher); scene_manager_free(app->scene_manager); // Records + furi_record_close(RECORD_DIALOGS); furi_record_close(RECORD_GUI); free(app); } diff --git a/applications/settings/desktop_settings/desktop_settings_app.h b/applications/settings/desktop_settings/desktop_settings_app.h index 25c5b3adb26..fc56c3253ac 100644 --- a/applications/settings/desktop_settings/desktop_settings_app.h +++ b/applications/settings/desktop_settings/desktop_settings_app.h @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -25,6 +26,7 @@ typedef struct { DesktopSettings settings; Gui* gui; + DialogsApp* dialogs; SceneManager* scene_manager; ViewDispatcher* view_dispatcher; VariableItemList* variable_item_list; diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c index d8c579b3e22..07ba9925f3e 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c @@ -1,6 +1,28 @@ #include "../desktop_settings_app.h" #include "applications.h" #include "desktop_settings_scene.h" +#include +#include +#include + +static bool favorite_fap_selector_item_callback( + FuriString* file_path, + void* context, + uint8_t** icon_ptr, + FuriString* item_name) { + UNUSED(context); + Storage* storage = furi_record_open(RECORD_STORAGE); + bool success = fap_loader_load_name_and_icon(file_path, storage, icon_ptr, item_name); + furi_record_close(RECORD_STORAGE); + return success; +} + +static bool favorite_fap_selector_file_exists(char* file_path) { + Storage* storage = furi_record_open(RECORD_STORAGE); + bool exists = storage_file_exists(storage, file_path); + furi_record_close(RECORD_STORAGE); + return exists; +} static void desktop_settings_scene_favorite_submenu_callback(void* context, uint32_t index) { DesktopSettingsApp* app = context; @@ -12,6 +34,10 @@ void desktop_settings_scene_favorite_on_enter(void* context) { Submenu* submenu = app->submenu; submenu_reset(submenu); + uint32_t primary_favorite = + scene_manager_get_scene_state(app->scene_manager, DesktopSettingsAppSceneFavorite); + uint32_t pre_select_item = 0; + for(size_t i = 0; i < FLIPPER_APPS_COUNT; i++) { submenu_add_item( submenu, @@ -19,38 +45,96 @@ void desktop_settings_scene_favorite_on_enter(void* context) { i, desktop_settings_scene_favorite_submenu_callback, app); - } - uint32_t primary_favorite = - scene_manager_get_scene_state(app->scene_manager, DesktopSettingsAppSceneFavorite); + if(primary_favorite) { // Select favorite item in submenu + if((app->settings.favorite_primary.is_external && + !strcmp(FLIPPER_APPS[i].name, FAP_LOADER_APP_NAME)) || + (!strcmp(FLIPPER_APPS[i].name, app->settings.favorite_primary.name_or_path))) { + pre_select_item = i; + } + } else { + if((app->settings.favorite_secondary.is_external && + !strcmp(FLIPPER_APPS[i].name, FAP_LOADER_APP_NAME)) || + (!strcmp(FLIPPER_APPS[i].name, app->settings.favorite_secondary.name_or_path))) { + pre_select_item = i; + } + } + } submenu_set_header( - app->submenu, primary_favorite ? "Primary favorite app:" : "Secondary favorite app:"); + submenu, primary_favorite ? "Primary favorite app:" : "Secondary favorite app:"); + submenu_set_selected_item(submenu, pre_select_item); // If set during loop, visual glitch. - if(primary_favorite) { - submenu_set_selected_item(app->submenu, app->settings.favorite_primary); - } else { - submenu_set_selected_item(app->submenu, app->settings.favorite_secondary); - } view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewMenu); } bool desktop_settings_scene_favorite_on_event(void* context, SceneManagerEvent event) { DesktopSettingsApp* app = context; bool consumed = false; + FuriString* temp_path = furi_string_alloc_set_str(EXT_PATH("apps")); uint32_t primary_favorite = scene_manager_get_scene_state(app->scene_manager, DesktopSettingsAppSceneFavorite); if(event.type == SceneManagerEventTypeCustom) { - if(primary_favorite) { - app->settings.favorite_primary = event.event; + if(strcmp(FLIPPER_APPS[event.event].name, FAP_LOADER_APP_NAME)) { + if(primary_favorite) { + app->settings.favorite_primary.is_external = false; + strncpy( + app->settings.favorite_primary.name_or_path, + FLIPPER_APPS[event.event].name, + MAX_APP_LENGTH); + } else { + app->settings.favorite_secondary.is_external = false; + strncpy( + app->settings.favorite_secondary.name_or_path, + FLIPPER_APPS[event.event].name, + MAX_APP_LENGTH); + } } else { - app->settings.favorite_secondary = event.event; + const DialogsFileBrowserOptions browser_options = { + .extension = ".fap", + .icon = &I_unknown_10px, + .skip_assets = true, + .hide_ext = true, + .item_loader_callback = favorite_fap_selector_item_callback, + .item_loader_context = app, + }; + + if(primary_favorite) { // Select favorite fap in file browser + if(favorite_fap_selector_file_exists( + app->settings.favorite_primary.name_or_path)) { + furi_string_set_str(temp_path, app->settings.favorite_primary.name_or_path); + } + } else { + if(favorite_fap_selector_file_exists( + app->settings.favorite_secondary.name_or_path)) { + furi_string_set_str(temp_path, app->settings.favorite_secondary.name_or_path); + } + } + + submenu_reset(app->submenu); + if(dialog_file_browser_show(app->dialogs, temp_path, temp_path, &browser_options)) { + if(primary_favorite) { + app->settings.favorite_primary.is_external = true; + strncpy( + app->settings.favorite_primary.name_or_path, + furi_string_get_cstr(temp_path), + MAX_APP_LENGTH); + } else { + app->settings.favorite_secondary.is_external = true; + strncpy( + app->settings.favorite_secondary.name_or_path, + furi_string_get_cstr(temp_path), + MAX_APP_LENGTH); + } + } } scene_manager_previous_scene(app->scene_manager); consumed = true; } + + furi_string_free(temp_path); return consumed; } From 9ff29d12b2e25e6053b9fcff41a4c64523094f23 Mon Sep 17 00:00:00 2001 From: Johannes Mittendorfer Date: Fri, 14 Oct 2022 18:54:52 +0200 Subject: [PATCH 144/824] Fix typo in fap loader logging (#1875) --- applications/main/fap_loader/fap_loader_app.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/main/fap_loader/fap_loader_app.c b/applications/main/fap_loader/fap_loader_app.c index 6c909aeeb25..faf8eefc833 100644 --- a/applications/main/fap_loader/fap_loader_app.c +++ b/applications/main/fap_loader/fap_loader_app.c @@ -101,7 +101,7 @@ static bool fap_loader_run_selected_app(FapLoader* loader) { } FURI_LOG_I(TAG, "Loaded in %ums", (size_t)(furi_get_tick() - start)); - FURI_LOG_I(TAG, "FAP Loader is staring app"); + FURI_LOG_I(TAG, "FAP Loader is starting app"); FuriThread* thread = flipper_application_spawn(loader->app, NULL); furi_thread_start(thread); From ead9f134f4e554cd29d30ed4b7c55bc184db3774 Mon Sep 17 00:00:00 2001 From: gornekich Date: Fri, 14 Oct 2022 21:21:23 +0400 Subject: [PATCH 145/824] [FL-2623] Add BLE disconnect request #1686 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: LionZXY Co-authored-by: あく --- applications/services/bt/bt_service/bt.c | 19 +++++-- firmware/targets/f7/ble_glue/serial_service.c | 53 ++++++++++++++++++- firmware/targets/f7/ble_glue/serial_service.h | 8 +++ .../targets/f7/furi_hal/furi_hal_bt_serial.c | 10 ++++ .../furi_hal_include/furi_hal_bt_serial.h | 11 ++++ 5 files changed, 97 insertions(+), 4 deletions(-) diff --git a/applications/services/bt/bt_service/bt.c b/applications/services/bt/bt_service/bt.c index aeb2beec9e7..c003013e412 100644 --- a/applications/services/bt/bt_service/bt.c +++ b/applications/services/bt/bt_service/bt.c @@ -165,6 +165,11 @@ static uint16_t bt_serial_event_callback(SerialServiceEvent event, void* context ret = rpc_session_get_available_size(bt->rpc_session); } else if(event.event == SerialServiceEventTypeDataSent) { furi_event_flag_set(bt->rpc_event, BT_RPC_EVENT_BUFF_SENT); + } else if(event.event == SerialServiceEventTypesBleResetRequest) { + FURI_LOG_I(TAG, "BLE restart request received"); + BtMessage message = {.type = BtMessageTypeSetProfile, .data.profile = BtProfileSerial}; + furi_check( + furi_message_queue_put(bt->message_queue, &message, FuriWaitForever) == FuriStatusOk); } return ret; } @@ -226,6 +231,7 @@ static bool bt_on_gap_event_callback(GapEvent event, void* context) { rpc_session_set_context(bt->rpc_session, bt); furi_hal_bt_serial_set_event_callback( RPC_BUFFER_SIZE, bt_serial_event_callback, bt); + furi_hal_bt_serial_set_rpc_status(FuriHalBtSerialRpcStatusActive); } else { FURI_LOG_W(TAG, "RPC is busy, failed to open new session"); } @@ -241,6 +247,7 @@ static bool bt_on_gap_event_callback(GapEvent event, void* context) { } else if(event.type == GapEventTypeDisconnected) { if(bt->profile == BtProfileSerial && bt->rpc_session) { FURI_LOG_I(TAG, "Close RPC connection"); + furi_hal_bt_serial_set_rpc_status(FuriHalBtSerialRpcStatusNotActive); furi_event_flag_set(bt->rpc_event, BT_RPC_EVENT_DISCONNECTED); rpc_session_close(bt->rpc_session); furi_hal_bt_serial_set_event_callback(0, NULL, NULL); @@ -330,14 +337,20 @@ static void bt_change_profile(Bt* bt, BtMessage* message) { } furi_hal_bt_set_key_storage_change_callback(bt_on_key_storage_change_callback, bt); bt->profile = message->data.profile; - *message->result = true; + if(message->result) { + *message->result = true; + } } else { FURI_LOG_E(TAG, "Failed to start Bt App"); - *message->result = false; + if(message->result) { + *message->result = false; + } } } else { bt_show_warning(bt, "Radio stack doesn't support this app"); - *message->result = false; + if(message->result) { + *message->result = false; + } } furi_event_flag_set(bt->api_event, BT_API_UNLOCK_EVENT); } diff --git a/firmware/targets/f7/ble_glue/serial_service.c b/firmware/targets/f7/ble_glue/serial_service.c index 536557cce94..c6421dc28fe 100644 --- a/firmware/targets/f7/ble_glue/serial_service.c +++ b/firmware/targets/f7/ble_glue/serial_service.c @@ -11,6 +11,7 @@ typedef struct { uint16_t rx_char_handle; uint16_t tx_char_handle; uint16_t flow_ctrl_char_handle; + uint16_t rpc_status_char_handle; FuriMutex* buff_size_mtx; uint32_t buff_size; uint16_t bytes_ready_to_receive; @@ -28,6 +29,8 @@ static const uint8_t char_rx_uuid[] = {0x00, 0x00, 0xfe, 0x62, 0x8e, 0x22, 0x45, 0x41, 0x9d, 0x4c, 0x21, 0xed, 0xae, 0x82, 0xed, 0x19}; static const uint8_t flow_ctrl_uuid[] = {0x00, 0x00, 0xfe, 0x63, 0x8e, 0x22, 0x45, 0x41, 0x9d, 0x4c, 0x21, 0xed, 0xae, 0x82, 0xed, 0x19}; +static const uint8_t rpc_status_uuid[] = + {0x00, 0x00, 0xfe, 0x64, 0x8e, 0x22, 0x45, 0x41, 0x9d, 0x4c, 0x21, 0xed, 0xae, 0x82, 0xed, 0x19}; static SVCCTL_EvtAckStatus_t serial_svc_event_handler(void* event) { SVCCTL_EvtAckStatus_t ret = SVCCTL_EvtNotAck; @@ -67,6 +70,17 @@ static SVCCTL_EvtAckStatus_t serial_svc_event_handler(void* event) { furi_check(furi_mutex_release(serial_svc->buff_size_mtx) == FuriStatusOk); } ret = SVCCTL_EvtAckFlowEnable; + } else if(attribute_modified->Attr_Handle == serial_svc->rpc_status_char_handle + 1) { + SerialServiceRpcStatus* rpc_status = + (SerialServiceRpcStatus*)attribute_modified->Attr_Data; + if(*rpc_status == SerialServiceRpcStatusNotActive) { + if(serial_svc->callback) { + SerialServiceEvent event = { + .event = SerialServiceEventTypesBleResetRequest, + }; + serial_svc->callback(event, serial_svc->context); + } + } } } else if(blecore_evt->ecode == ACI_GATT_SERVER_CONFIRMATION_VSEVT_CODE) { FURI_LOG_T(TAG, "Ack received"); @@ -82,6 +96,18 @@ static SVCCTL_EvtAckStatus_t serial_svc_event_handler(void* event) { return ret; } +static void serial_svc_update_rpc_char(SerialServiceRpcStatus status) { + tBleStatus ble_status = aci_gatt_update_char_value( + serial_svc->svc_handle, + serial_svc->rpc_status_char_handle, + 0, + sizeof(SerialServiceRpcStatus), + (uint8_t*)&status); + if(ble_status) { + FURI_LOG_E(TAG, "Failed to update RPC status char: %d", ble_status); + } +} + void serial_svc_start() { tBleStatus status; serial_svc = malloc(sizeof(SerialSvc)); @@ -90,7 +116,7 @@ void serial_svc_start() { // Add service status = aci_gatt_add_service( - UUID_TYPE_128, (Service_UUID_t*)service_uuid, PRIMARY_SERVICE, 10, &serial_svc->svc_handle); + UUID_TYPE_128, (Service_UUID_t*)service_uuid, PRIMARY_SERVICE, 12, &serial_svc->svc_handle); if(status) { FURI_LOG_E(TAG, "Failed to add Serial service: %d", status); } @@ -141,6 +167,22 @@ void serial_svc_start() { if(status) { FURI_LOG_E(TAG, "Failed to add Flow Control characteristic: %d", status); } + // Add RPC status characteristic + status = aci_gatt_add_char( + serial_svc->svc_handle, + UUID_TYPE_128, + (const Char_UUID_t*)rpc_status_uuid, + sizeof(SerialServiceRpcStatus), + CHAR_PROP_READ | CHAR_PROP_WRITE | CHAR_PROP_NOTIFY, + ATTR_PERMISSION_AUTHEN_READ | ATTR_PERMISSION_AUTHEN_WRITE, + GATT_NOTIFY_ATTRIBUTE_WRITE, + 10, + CHAR_VALUE_LEN_CONSTANT, + &serial_svc->rpc_status_char_handle); + if(status) { + FURI_LOG_E(TAG, "Failed to add RPC status characteristic: %d", status); + } + serial_svc_update_rpc_char(SerialServiceRpcStatusNotActive); // Allocate buffer size mutex serial_svc->buff_size_mtx = furi_mutex_alloc(FuriMutexTypeNormal); } @@ -198,6 +240,10 @@ void serial_svc_stop() { if(status) { FURI_LOG_E(TAG, "Failed to delete Flow Control characteristic: %d", status); } + status = aci_gatt_del_char(serial_svc->svc_handle, serial_svc->rpc_status_char_handle); + if(status) { + FURI_LOG_E(TAG, "Failed to delete RPC Status characteristic: %d", status); + } // Delete service status = aci_gatt_del_service(serial_svc->svc_handle); if(status) { @@ -242,3 +288,8 @@ bool serial_svc_update_tx(uint8_t* data, uint16_t data_len) { return true; } + +void serial_svc_set_rpc_status(SerialServiceRpcStatus status) { + furi_assert(serial_svc); + serial_svc_update_rpc_char(status); +} diff --git a/firmware/targets/f7/ble_glue/serial_service.h b/firmware/targets/f7/ble_glue/serial_service.h index a1e5bc1cc57..7d38066f47f 100644 --- a/firmware/targets/f7/ble_glue/serial_service.h +++ b/firmware/targets/f7/ble_glue/serial_service.h @@ -10,9 +10,15 @@ extern "C" { #endif +typedef enum { + SerialServiceRpcStatusNotActive = 0UL, + SerialServiceRpcStatusActive = 1UL, +} SerialServiceRpcStatus; + typedef enum { SerialServiceEventTypeDataReceived, SerialServiceEventTypeDataSent, + SerialServiceEventTypesBleResetRequest, } SerialServiceEventType; typedef struct { @@ -34,6 +40,8 @@ void serial_svc_set_callbacks( SerialServiceEventCallback callback, void* context); +void serial_svc_set_rpc_status(SerialServiceRpcStatus status); + void serial_svc_notify_buffer_is_empty(); void serial_svc_stop(); diff --git a/firmware/targets/f7/furi_hal/furi_hal_bt_serial.c b/firmware/targets/f7/furi_hal/furi_hal_bt_serial.c index 9bdad5bf2ad..aa09dde5235 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_bt_serial.c +++ b/firmware/targets/f7/furi_hal/furi_hal_bt_serial.c @@ -31,6 +31,16 @@ void furi_hal_bt_serial_notify_buffer_is_empty() { serial_svc_notify_buffer_is_empty(); } +void furi_hal_bt_serial_set_rpc_status(FuriHalBtSerialRpcStatus status) { + SerialServiceRpcStatus st; + if(status == FuriHalBtSerialRpcStatusActive) { + st = SerialServiceRpcStatusActive; + } else { + st = SerialServiceRpcStatusNotActive; + } + serial_svc_set_rpc_status(st); +} + bool furi_hal_bt_serial_tx(uint8_t* data, uint16_t size) { if(size > FURI_HAL_BT_SERIAL_PACKET_SIZE_MAX) { return false; diff --git a/firmware/targets/furi_hal_include/furi_hal_bt_serial.h b/firmware/targets/furi_hal_include/furi_hal_bt_serial.h index e1b7af224eb..1b6e79ab078 100644 --- a/firmware/targets/furi_hal_include/furi_hal_bt_serial.h +++ b/firmware/targets/furi_hal_include/furi_hal_bt_serial.h @@ -8,6 +8,11 @@ extern "C" { #define FURI_HAL_BT_SERIAL_PACKET_SIZE_MAX SERIAL_SVC_DATA_LEN_MAX +typedef enum { + FuriHalBtSerialRpcStatusNotActive, + FuriHalBtSerialRpcStatusActive, +} FuriHalBtSerialRpcStatus; + /** Serial service callback type */ typedef SerialServiceEventCallback FuriHalBtSerialCallback; @@ -30,6 +35,12 @@ void furi_hal_bt_serial_set_event_callback( FuriHalBtSerialCallback callback, void* context); +/** Set BLE RPC status + * + * @param status FuriHalBtSerialRpcStatus instance + */ +void furi_hal_bt_serial_set_rpc_status(FuriHalBtSerialRpcStatus status); + /** Notify that application buffer is empty */ void furi_hal_bt_serial_notify_buffer_is_empty(); From f81999ea4a0dff606e0a19c6e50f3459e2ec3689 Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Fri, 14 Oct 2022 20:35:53 +0300 Subject: [PATCH 146/824] Fix number of dolphin_apps (#1874) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- applications/services/dolphin/helpers/dolphin_deed.c | 6 ++---- applications/services/dolphin/helpers/dolphin_deed.h | 3 --- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/applications/services/dolphin/helpers/dolphin_deed.c b/applications/services/dolphin/helpers/dolphin_deed.c index d3c40298d7f..ce3e058b564 100644 --- a/applications/services/dolphin/helpers/dolphin_deed.c +++ b/applications/services/dolphin/helpers/dolphin_deed.c @@ -35,9 +35,9 @@ static const DolphinDeedWeight dolphin_deed_weights[] = { {2, DolphinAppIbutton}, // DolphinDeedIbuttonAdd {3, DolphinAppBadusb}, // DolphinDeedBadUsbPlayScript - {3, DolphinAppU2f}, // DolphinDeedU2fAuthorized + {3, DolphinAppPlugin}, // DolphinDeedU2fAuthorized - {1, DolphinAppGpio}, // DolphinDeedGpioUartBridge + {1, DolphinAppPlugin}, // DolphinDeedGpioUartBridge {1, DolphinAppPlugin}, // DolphinDeedPluginStart {1, DolphinAppPlugin}, // DolphinDeedPluginGameStart @@ -51,8 +51,6 @@ static uint8_t dolphin_deed_limits[] = { 20, // DolphinAppIr 20, // DolphinAppIbutton 20, // DolphinAppBadusb - 20, // DolphinAppU2f - 20, // DolphinAppGpio 20, // DolphinAppPlugin }; diff --git a/applications/services/dolphin/helpers/dolphin_deed.h b/applications/services/dolphin/helpers/dolphin_deed.h index 969f0d5ccda..abe027d7909 100644 --- a/applications/services/dolphin/helpers/dolphin_deed.h +++ b/applications/services/dolphin/helpers/dolphin_deed.h @@ -13,8 +13,6 @@ typedef enum { DolphinAppIr, DolphinAppIbutton, DolphinAppBadusb, - DolphinAppU2f, - DolphinAppGpio, DolphinAppPlugin, DolphinAppMAX, } DolphinApp; @@ -55,7 +53,6 @@ typedef enum { DolphinDeedBadUsbPlayScript, DolphinDeedU2fAuthorized, - DolphinDeedGpioUartBridge, DolphinDeedPluginStart, From 865baed0bbcd30fc378f2e92eb00ebc20be8dcdd Mon Sep 17 00:00:00 2001 From: Kevin Kwok Date: Fri, 14 Oct 2022 11:07:23 -0700 Subject: [PATCH 147/824] Fix FileNotFoundError in ./fbt flash_usb (#1876) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix FileNotFoundError in ./fbt flash_usb * scripts: update.py: proper fix for file naming Co-authored-by: あく Co-authored-by: hedger --- scripts/update.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/scripts/update.py b/scripts/update.py index 6be1dce066a..3259c5b09df 100755 --- a/scripts/update.py +++ b/scripts/update.py @@ -78,8 +78,12 @@ def init(self): def generate(self): stage_basename = "updater.bin" # used to be basename(self.args.stage) - dfu_basename = "firmware.dfu" # used to be basename(self.args.dfu) - radiobin_basename = "radio.bin" # used to be basename(self.args.radiobin) + dfu_basename = ( + "firmware.dfu" if self.args.dfu else "" + ) # used to be basename(self.args.dfu) + radiobin_basename = ( + "radio.bin" if self.args.radiobin else "" + ) # used to be basename(self.args.radiobin) resources_basename = "" radio_version = 0 From f06930e4ae98bfad66c38f9bea5bb67601c64001 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Sat, 15 Oct 2022 03:23:06 +0900 Subject: [PATCH 148/824] Desktop: fix fap in settings (#1877) --- applications/main/fap_loader/application.fam | 1 + .../scenes/desktop_settings_scene_favorite.c | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/applications/main/fap_loader/application.fam b/applications/main/fap_loader/application.fam index bd0403e0eb9..784ee9508a3 100644 --- a/applications/main/fap_loader/application.fam +++ b/applications/main/fap_loader/application.fam @@ -3,6 +3,7 @@ App( name="Applications", apptype=FlipperAppType.APP, entry_point="fap_loader_app", + cdefines=["APP_FAP_LOADER"], requires=[ "gui", "storage", diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c index 07ba9925f3e..bdd9589ed75 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c @@ -11,9 +11,16 @@ static bool favorite_fap_selector_item_callback( uint8_t** icon_ptr, FuriString* item_name) { UNUSED(context); +#ifdef APP_FAP_LOADER Storage* storage = furi_record_open(RECORD_STORAGE); bool success = fap_loader_load_name_and_icon(file_path, storage, icon_ptr, item_name); furi_record_close(RECORD_STORAGE); +#else + UNUSED(file_path); + UNUSED(icon_ptr); + UNUSED(item_name); + bool success = false; +#endif return success; } From 8ec5527ae4a486608e4003b51e21b41045daf7fd Mon Sep 17 00:00:00 2001 From: hedger Date: Sun, 16 Oct 2022 21:11:27 +0400 Subject: [PATCH 149/824] fbt: fix for cincludes in app's private library definition (#1882) --- scripts/fbt_tools/fbt_extapps.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/scripts/fbt_tools/fbt_extapps.py b/scripts/fbt_tools/fbt_extapps.py index 34fb942abe1..9f81d4145c0 100644 --- a/scripts/fbt_tools/fbt_extapps.py +++ b/scripts/fbt_tools/fbt_extapps.py @@ -80,10 +80,7 @@ def legacy_app_build_stub(**kw): *lib_def.cflags, ], CPPDEFINES=lib_def.cdefines, - CPPPATH=list( - os.path.join(app._appdir.path, cinclude) - for cinclude in lib_def.cincludes - ), + CPPPATH=list(map(app._appdir.Dir, lib_def.cincludes)), ) lib = private_lib_env.StaticLibrary( From e7aaf3dbb2f86ec6cb51b0be34095411274182ab Mon Sep 17 00:00:00 2001 From: Ivan Podogov Date: Mon, 17 Oct 2022 16:17:04 +0100 Subject: [PATCH 150/824] Enable all `view_` methods in SDK (#1884) --- firmware/targets/f7/api_symbols.csv | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index c9e2371f4ee..33e289948dc 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,2.2,, +Version,+,2.3,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -2511,17 +2511,17 @@ Function,+,view_port_alloc,ViewPort*, Function,+,view_port_draw_callback_set,void,"ViewPort*, ViewPortDrawCallback, void*" Function,+,view_port_enabled_set,void,"ViewPort*, _Bool" Function,+,view_port_free,void,ViewPort* -Function,-,view_port_get_height,uint8_t,ViewPort* +Function,+,view_port_get_height,uint8_t,ViewPort* Function,+,view_port_get_orientation,ViewPortOrientation,const ViewPort* Function,+,view_port_get_width,uint8_t,ViewPort* Function,+,view_port_input_callback_set,void,"ViewPort*, ViewPortInputCallback, void*" Function,+,view_port_is_enabled,_Bool,ViewPort* -Function,-,view_port_set_height,void,"ViewPort*, uint8_t" +Function,+,view_port_set_height,void,"ViewPort*, uint8_t" Function,+,view_port_set_orientation,void,"ViewPort*, ViewPortOrientation" Function,+,view_port_set_width,void,"ViewPort*, uint8_t" Function,+,view_port_update,void,ViewPort* Function,+,view_set_context,void,"View*, void*" -Function,-,view_set_custom_callback,void,"View*, ViewCustomCallback" +Function,+,view_set_custom_callback,void,"View*, ViewCustomCallback" Function,+,view_set_draw_callback,void,"View*, ViewDrawCallback" Function,+,view_set_enter_callback,void,"View*, ViewCallback" Function,+,view_set_exit_callback,void,"View*, ViewCallback" From dfbe21e7203688d33a7c002a4a19fc4714469aa6 Mon Sep 17 00:00:00 2001 From: gornekich Date: Mon, 17 Oct 2022 21:10:41 +0400 Subject: [PATCH 151/824] NFC fixes part 3 (#1885) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * nfc: fix read next key * nfc: verify new line ending in user dictionary file * nfc: fix cache save * nfc: add unit test for dict load * nfc: fix total key count in dictionary Co-authored-by: あく --- applications/debug/unit_tests/nfc/nfc_test.c | 68 ++++++++++++++++++++ lib/nfc/helpers/mf_classic_dict.c | 22 +++++-- lib/nfc/nfc_device.c | 2 +- 3 files changed, 87 insertions(+), 5 deletions(-) diff --git a/applications/debug/unit_tests/nfc/nfc_test.c b/applications/debug/unit_tests/nfc/nfc_test.c index f149508b0ab..8009f6a17be 100644 --- a/applications/debug/unit_tests/nfc/nfc_test.c +++ b/applications/debug/unit_tests/nfc/nfc_test.c @@ -16,6 +16,7 @@ #define NFC_TEST_RESOURCES_DIR EXT_PATH("unit_tests/nfc/") #define NFC_TEST_SIGNAL_SHORT_FILE "nfc_nfca_signal_short.nfc" #define NFC_TEST_SIGNAL_LONG_FILE "nfc_nfca_signal_long.nfc" +#define NFC_TEST_DICT_PATH EXT_PATH("unit_tests/mf_classic_dict.nfc") static const char* nfc_test_file_type = "Flipper NFC test"; static const uint32_t nfc_test_file_version = 1; @@ -220,11 +221,78 @@ MU_TEST(mf_classic_dict_test) { furi_string_free(temp_str); } +MU_TEST(mf_classic_dict_load_test) { + Storage* storage = furi_record_open(RECORD_STORAGE); + mu_assert(storage != NULL, "storage != NULL assert failed\r\n"); + + // Delete unit test dict file if exists + if(storage_file_exists(storage, NFC_TEST_DICT_PATH)) { + mu_assert( + storage_simply_remove(storage, NFC_TEST_DICT_PATH), + "remove == true assert failed\r\n"); + } + + // Create unit test dict file + Stream* file_stream = file_stream_alloc(storage); + mu_assert(file_stream != NULL, "file_stream != NULL assert failed\r\n"); + mu_assert( + file_stream_open(file_stream, NFC_TEST_DICT_PATH, FSAM_WRITE, FSOM_OPEN_ALWAYS), + "file_stream_open == true assert failed\r\n"); + + // Write unit test dict file + char key_str[] = "a0a1a2a3a4a5"; + mu_assert( + stream_write_cstring(file_stream, key_str) == strlen(key_str), + "write == true assert failed\r\n"); + // Close unit test dict file + mu_assert(file_stream_close(file_stream), "file_stream_close == true assert failed\r\n"); + + // Load unit test dict file + MfClassicDict* instance = NULL; + instance = mf_classic_dict_alloc(MfClassicDictTypeUnitTest); + mu_assert(instance != NULL, "mf_classic_dict_alloc\r\n"); + uint32_t total_keys = mf_classic_dict_get_total_keys(instance); + mu_assert(total_keys == 1, "total_keys == 1 assert failed\r\n"); + + // Read key + uint64_t key_ref = 0xa0a1a2a3a4a5; + uint64_t key_dut = 0; + FuriString* temp_str = furi_string_alloc(); + mu_assert( + mf_classic_dict_get_next_key_str(instance, temp_str), + "get_next_key_str == true assert failed\r\n"); + mu_assert(furi_string_cmp_str(temp_str, key_str) == 0, "invalid key loaded\r\n"); + mu_assert(mf_classic_dict_rewind(instance), "mf_classic_dict_rewind == 1 assert failed\r\n"); + mu_assert( + mf_classic_dict_get_next_key(instance, &key_dut), + "get_next_key == true assert failed\r\n"); + mu_assert(key_dut == key_ref, "invalid key loaded\r\n"); + furi_string_free(temp_str); + mf_classic_dict_free(instance); + + // Check that MfClassicDict added new line to the end of the file + mu_assert( + file_stream_open(file_stream, NFC_TEST_DICT_PATH, FSAM_READ, FSOM_OPEN_EXISTING), + "file_stream_open == true assert failed\r\n"); + mu_assert(stream_seek(file_stream, -1, StreamOffsetFromEnd), "seek == true assert failed\r\n"); + uint8_t last_char = 0; + mu_assert(stream_read(file_stream, &last_char, 1) == 1, "read == true assert failed\r\n"); + mu_assert(last_char == '\n', "last_char == '\\n' assert failed\r\n"); + mu_assert(file_stream_close(file_stream), "file_stream_close == true assert failed\r\n"); + + // Delete unit test dict file + mu_assert( + storage_simply_remove(storage, NFC_TEST_DICT_PATH), "remove == true assert failed\r\n"); + stream_free(file_stream); + furi_record_close(RECORD_STORAGE); +} + MU_TEST_SUITE(nfc) { nfc_test_alloc(); MU_RUN_TEST(nfc_digital_signal_test); MU_RUN_TEST(mf_classic_dict_test); + MU_RUN_TEST(mf_classic_dict_load_test); nfc_test_free(); } diff --git a/lib/nfc/helpers/mf_classic_dict.c b/lib/nfc/helpers/mf_classic_dict.c index a842ed921b5..690bba61b31 100644 --- a/lib/nfc/helpers/mf_classic_dict.c +++ b/lib/nfc/helpers/mf_classic_dict.c @@ -44,7 +44,10 @@ MfClassicDict* mf_classic_dict_alloc(MfClassicDictType dict_type) { do { if(dict_type == MfClassicDictTypeFlipper) { if(!buffered_file_stream_open( - dict->stream, MF_CLASSIC_DICT_FLIPPER_PATH, FSAM_READ, FSOM_OPEN_EXISTING)) { + dict->stream, + MF_CLASSIC_DICT_FLIPPER_PATH, + FSAM_READ_WRITE, + FSOM_OPEN_EXISTING)) { buffered_file_stream_close(dict->stream); break; } @@ -59,12 +62,24 @@ MfClassicDict* mf_classic_dict_alloc(MfClassicDictType dict_type) { dict->stream, MF_CLASSIC_DICT_UNIT_TEST_PATH, FSAM_READ_WRITE, - FSOM_CREATE_ALWAYS)) { + FSOM_OPEN_ALWAYS)) { buffered_file_stream_close(dict->stream); break; } } + // Check for new line ending + if(!stream_eof(dict->stream)) { + if(!stream_seek(dict->stream, -1, StreamOffsetFromEnd)) break; + uint8_t last_char = 0; + if(stream_read(dict->stream, &last_char, 1) != 1) break; + if(last_char != '\n') { + FURI_LOG_D(TAG, "Adding new line ending"); + if(stream_write_char(dict->stream, '\n') != 1) break; + } + if(!stream_rewind(dict->stream)) break; + } + // Read total amount of keys FuriString* next_line; next_line = furi_string_alloc(); @@ -73,14 +88,13 @@ MfClassicDict* mf_classic_dict_alloc(MfClassicDictType dict_type) { FURI_LOG_T(TAG, "No keys left in dict"); break; } - furi_string_trim(next_line); FURI_LOG_T( TAG, "Read line: %s, len: %d", furi_string_get_cstr(next_line), furi_string_size(next_line)); if(furi_string_get_char(next_line, 0) == '#') continue; - if(furi_string_size(next_line) != NFC_MF_CLASSIC_KEY_LEN - 1) continue; + if(furi_string_size(next_line) != NFC_MF_CLASSIC_KEY_LEN) continue; dict->total_keys++; } furi_string_free(next_line); diff --git a/lib/nfc/nfc_device.c b/lib/nfc/nfc_device.c index 06d57a0c35e..740cfae5e75 100644 --- a/lib/nfc/nfc_device.c +++ b/lib/nfc/nfc_device.c @@ -921,7 +921,7 @@ static bool nfc_device_save_mifare_classic_keys(NfcDevice* dev) { file, furi_string_get_cstr(temp_str), sec_tr->key_a, 6); } if(!key_save_success) break; - if(FURI_BIT(data->key_a_mask, i)) { + if(FURI_BIT(data->key_b_mask, i)) { furi_string_printf(temp_str, "Key B sector %d", i); key_save_success = flipper_format_write_hex( file, furi_string_get_cstr(temp_str), sec_tr->key_b, 6); From 5e35e51c578d938e2b3fb3dee476d88c405b0105 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Mon, 17 Oct 2022 20:49:00 +0300 Subject: [PATCH 152/824] [FL-2907] Remove the back button from MFC keys list #1878 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- applications/main/nfc/scenes/nfc_scene_mf_classic_keys.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys.c index a2e6ae745fc..54cc18d327b 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys.c @@ -34,8 +34,6 @@ void nfc_scene_mf_classic_keys_on_enter(void* context) { widget_add_string_element(nfc->widget, 0, 32, AlignLeft, AlignTop, FontSecondary, temp_str); widget_add_button_element( nfc->widget, GuiButtonTypeCenter, "Add", nfc_scene_mf_classic_keys_widget_callback, nfc); - widget_add_button_element( - nfc->widget, GuiButtonTypeLeft, "Back", nfc_scene_mf_classic_keys_widget_callback, nfc); widget_add_icon_element(nfc->widget, 87, 13, &I_Keychain_39x36); if(user_dict_keys_total > 0) { widget_add_button_element( @@ -57,9 +55,6 @@ bool nfc_scene_mf_classic_keys_on_event(void* context, SceneManagerEvent event) if(event.event == GuiButtonTypeCenter) { scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicKeysAdd); consumed = true; - } else if(event.event == GuiButtonTypeLeft) { - scene_manager_previous_scene(nfc->scene_manager); - consumed = true; } else if(event.event == GuiButtonTypeRight) { scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicKeysList); consumed = true; From f61a8fda536656ee4900e24ed1b5a029b0de533f Mon Sep 17 00:00:00 2001 From: Travis Montoya <99630881+sqlsquirreltm@users.noreply.github.com> Date: Mon, 17 Oct 2022 12:07:05 -0600 Subject: [PATCH 153/824] Feature/infrared add remote to cli (#1856) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Initial testing of remote using cli * More fixes for cli ir remote * Fixes. Turns off power now * Finished adding other tv remote commands * Changed if-else formatting * Cleaned up unused variables * Updating cli unviersal remote to accept tv, ac and more modular. Listing signals still does not work properly * Using mlib dictionary to get unique signals from files for ir universal list * Fixing progress bar * Added error checking for invalid signal to stop freezing cli * Added error checking for arg length * Api symbols was changed somehow.. changed back and updated the argument check to account for newline * Fixing string compares and argument length issue * Freeing InfraredBruteForce in cli brute force signals Co-authored-by: sqlsquirreltm Co-authored-by: あく --- applications/main/infrared/infrared_cli.c | 178 ++++++++++++++++++++++ 1 file changed, 178 insertions(+) diff --git a/applications/main/infrared/infrared_cli.c b/applications/main/infrared/infrared_cli.c index 5ec57c75745..54e3e25154d 100644 --- a/applications/main/infrared/infrared_cli.c +++ b/applications/main/infrared/infrared_cli.c @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -6,12 +7,24 @@ #include #include "infrared_signal.h" +#include "infrared_brute_force.h" + +#include "m-dict.h" +#include "m-string.h" #define INFRARED_CLI_BUF_SIZE 10 +DICT_DEF2(dict_signals, string_t, STRING_OPLIST, int, M_DEFAULT_OPLIST) + +enum RemoteTypes { TV = 0, AC = 1 }; + static void infrared_cli_start_ir_rx(Cli* cli, FuriString* args); static void infrared_cli_start_ir_tx(Cli* cli, FuriString* args); static void infrared_cli_process_decode(Cli* cli, FuriString* args); +static void infrared_cli_process_universal(Cli* cli, FuriString* args); +static void infrared_cli_list_remote_signals(enum RemoteTypes remote_type); +static void + infrared_cli_brute_force_signals(Cli* cli, enum RemoteTypes remote_type, FuriString* signal); static const struct { const char* cmd; @@ -20,6 +33,7 @@ static const struct { {.cmd = "rx", .process_function = infrared_cli_start_ir_rx}, {.cmd = "tx", .process_function = infrared_cli_start_ir_tx}, {.cmd = "decode", .process_function = infrared_cli_process_decode}, + {.cmd = "universal", .process_function = infrared_cli_process_universal}, }; static void signal_received_callback(void* context, InfraredWorkerSignal* received_signal) { @@ -90,6 +104,8 @@ static void infrared_cli_print_usage(void) { INFRARED_MIN_FREQUENCY, INFRARED_MAX_FREQUENCY); printf("\tir decode []\r\n"); + printf("\tir universal \r\n"); + printf("\tir universal list \r\n"); } static bool infrared_cli_parse_message(const char* str, InfraredSignal* signal) { @@ -328,6 +344,168 @@ static void infrared_cli_process_decode(Cli* cli, FuriString* args) { furi_record_close(RECORD_STORAGE); } +static void infrared_cli_process_universal(Cli* cli, FuriString* args) { + enum RemoteTypes Remote; + + FuriString* command; + FuriString* remote; + FuriString* signal; + command = furi_string_alloc(); + remote = furi_string_alloc(); + signal = furi_string_alloc(); + + do { + if(!args_read_string_and_trim(args, command)) { + infrared_cli_print_usage(); + break; + } + + if(furi_string_cmp_str(command, "list") == 0) { + args_read_string_and_trim(args, remote); + if(furi_string_cmp_str(remote, "tv") == 0) { + Remote = TV; + } else if(furi_string_cmp_str(remote, "ac") == 0) { + Remote = AC; + } else { + printf("Invalid remote type.\r\n"); + break; + } + infrared_cli_list_remote_signals(Remote); + break; + } + + if(furi_string_cmp_str(command, "tv") == 0) { + Remote = TV; + } else if(furi_string_cmp_str(command, "ac") == 0) { + Remote = AC; + } else { + printf("Invalid remote type.\r\n"); + break; + } + + args_read_string_and_trim(args, signal); + if(furi_string_empty(signal)) { + printf("Must supply a valid signal for type of remote selected.\r\n"); + break; + } + + infrared_cli_brute_force_signals(cli, Remote, signal); + break; + + } while(false); + + furi_string_free(command); + furi_string_free(remote); + furi_string_free(signal); +} + +static void infrared_cli_list_remote_signals(enum RemoteTypes remote_type) { + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* ff = flipper_format_buffered_file_alloc(storage); + dict_signals_t signals_dict; + string_t key; + const char* remote_file = NULL; + bool success = false; + int max = 1; + + switch(remote_type) { + case TV: + remote_file = EXT_PATH("infrared/assets/tv.ir"); + break; + case AC: + remote_file = EXT_PATH("infrared/assets/ac.ir"); + break; + default: + break; + } + + dict_signals_init(signals_dict); + string_init(key); + + success = flipper_format_buffered_file_open_existing(ff, remote_file); + if(success) { + FuriString* signal_name; + signal_name = furi_string_alloc(); + printf("Valid signals:\r\n"); + while(flipper_format_read_string(ff, "name", signal_name)) { + string_set_str(key, furi_string_get_cstr(signal_name)); + int* v = dict_signals_get(signals_dict, key); + if(v != NULL) { + (*v)++; + max = M_MAX(*v, max); + } else { + dict_signals_set_at(signals_dict, key, 1); + } + } + dict_signals_it_t it; + for(dict_signals_it(it, signals_dict); !dict_signals_end_p(it); dict_signals_next(it)) { + const struct dict_signals_pair_s* pair = dict_signals_cref(it); + printf("\t%s\r\n", string_get_cstr(pair->key)); + } + furi_string_free(signal_name); + } + + string_clear(key); + dict_signals_clear(signals_dict); + flipper_format_free(ff); + furi_record_close(RECORD_STORAGE); +} + +static void + infrared_cli_brute_force_signals(Cli* cli, enum RemoteTypes remote_type, FuriString* signal) { + InfraredBruteForce* brute_force = infrared_brute_force_alloc(); + const char* remote_file = NULL; + uint32_t i = 0; + bool success = false; + + switch(remote_type) { + case TV: + remote_file = EXT_PATH("infrared/assets/tv.ir"); + break; + case AC: + remote_file = EXT_PATH("infrared/assets/ac.ir"); + break; + default: + break; + } + + infrared_brute_force_set_db_filename(brute_force, remote_file); + infrared_brute_force_add_record(brute_force, i++, furi_string_get_cstr(signal)); + + success = infrared_brute_force_calculate_messages(brute_force); + if(success) { + uint32_t record_count; + uint32_t index = 0; + int records_sent = 0; + bool running = false; + + running = infrared_brute_force_start(brute_force, index, &record_count); + if(record_count <= 0) { + printf("Invalid signal.\n"); + infrared_brute_force_reset(brute_force); + return; + } + + printf("Sending %ld codes to the tv.\r\n", record_count); + printf("Press Ctrl-C to stop.\r\n"); + while(running) { + running = infrared_brute_force_send_next(brute_force); + + if(cli_cmd_interrupt_received(cli)) break; + + printf("\r%d%% complete.", (int)((float)records_sent++ / (float)record_count * 100)); + fflush(stdout); + } + + infrared_brute_force_stop(brute_force); + } else { + printf("Invalid signal.\r\n"); + } + + infrared_brute_force_reset(brute_force); + infrared_brute_force_free(brute_force); +} + static void infrared_cli_start_ir(Cli* cli, FuriString* args, void* context) { UNUSED(context); if(furi_hal_infrared_is_busy()) { From 4942bd2105aa5690d9736dfd0a13efc81ff45aea Mon Sep 17 00:00:00 2001 From: hedger Date: Tue, 18 Oct 2022 16:13:28 +0400 Subject: [PATCH 154/824] scripts: fixed c2 bundle format (#1889) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * scripts: fixed c2 bundle format * scripts: copro.py: small refactoring Co-authored-by: あく --- scripts/flipper/assets/copro.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/scripts/flipper/assets/copro.py b/scripts/flipper/assets/copro.py index d39f3033359..b61ac032964 100644 --- a/scripts/flipper/assets/copro.py +++ b/scripts/flipper/assets/copro.py @@ -3,6 +3,8 @@ from io import BytesIO import tarfile import xml.etree.ElementTree as ET +import posixpath +import os from flipper.utils import * from flipper.assets.coprobin import CoproBinary, get_stack_type @@ -23,6 +25,8 @@ class Copro: + COPRO_TAR_DIR = "core2_firmware" + def __init__(self, mcu): self.mcu = mcu self.version = None @@ -50,9 +54,8 @@ def loadCubeInfo(self, cube_dir, cube_version): raise Exception(f"Unsupported cube version") self.version = cube_version - @staticmethod - def _getFileName(name): - return os.path.join("core2_firmware", name) + def _getFileName(self, name): + return posixpath.join(self.COPRO_TAR_DIR, name) def addFile(self, array, filename, **kwargs): source_file = os.path.join(self.mcu_copro, filename) @@ -61,6 +64,9 @@ def addFile(self, array, filename, **kwargs): def bundle(self, output_file, stack_file_name, stack_type, stack_addr=None): self.output_tar = tarfile.open(output_file, "w:gz", format=tarfile.USTAR_FORMAT) + fw_directory = tarfile.TarInfo(self.COPRO_TAR_DIR) + fw_directory.type = tarfile.DIRTYPE + self.output_tar.addfile(fw_directory) stack_file = os.path.join(self.mcu_copro, stack_file_name) # Form Manifest From 02c27becb0d682c6ab757e9f7fa6774e1e5d783e Mon Sep 17 00:00:00 2001 From: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> Date: Tue, 18 Oct 2022 17:10:21 +0300 Subject: [PATCH 155/824] [FL-2912] Forced RAW receive option for Infrared CLI #1891 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- applications/main/infrared/infrared_cli.c | 47 +++++++++++++++-------- lib/infrared/worker/infrared_worker.c | 10 ++++- lib/infrared/worker/infrared_worker.h | 8 ++++ 3 files changed, 47 insertions(+), 18 deletions(-) diff --git a/applications/main/infrared/infrared_cli.c b/applications/main/infrared/infrared_cli.c index 54e3e25154d..7723dc97376 100644 --- a/applications/main/infrared/infrared_cli.c +++ b/applications/main/infrared/infrared_cli.c @@ -71,25 +71,9 @@ static void signal_received_callback(void* context, InfraredWorkerSignal* receiv } } -static void infrared_cli_start_ir_rx(Cli* cli, FuriString* args) { - UNUSED(cli); - UNUSED(args); - InfraredWorker* worker = infrared_worker_alloc(); - infrared_worker_rx_start(worker); - infrared_worker_rx_set_received_signal_callback(worker, signal_received_callback, cli); - - printf("Receiving INFRARED...\r\nPress Ctrl+C to abort\r\n"); - while(!cli_cmd_interrupt_received(cli)) { - furi_delay_ms(50); - } - - infrared_worker_rx_stop(worker); - infrared_worker_free(worker); -} - static void infrared_cli_print_usage(void) { printf("Usage:\r\n"); - printf("\tir rx\r\n"); + printf("\tir rx [raw]\r\n"); printf("\tir tx

\r\n"); printf("\t and
are hex-formatted\r\n"); printf("\tAvailable protocols:"); @@ -108,6 +92,35 @@ static void infrared_cli_print_usage(void) { printf("\tir universal list \r\n"); } +static void infrared_cli_start_ir_rx(Cli* cli, FuriString* args) { + UNUSED(cli); + + bool enable_decoding = true; + + if(!furi_string_empty(args)) { + if(!furi_string_cmp_str(args, "raw")) { + enable_decoding = false; + } else { + printf("Wrong arguments.\r\n"); + infrared_cli_print_usage(); + return; + } + } + + InfraredWorker* worker = infrared_worker_alloc(); + infrared_worker_rx_enable_signal_decoding(worker, enable_decoding); + infrared_worker_rx_start(worker); + infrared_worker_rx_set_received_signal_callback(worker, signal_received_callback, cli); + + printf("Receiving %s INFRARED...\r\nPress Ctrl+C to abort\r\n", enable_decoding ? "" : "RAW"); + while(!cli_cmd_interrupt_received(cli)) { + furi_delay_ms(50); + } + + infrared_worker_rx_stop(worker); + infrared_worker_free(worker); +} + static bool infrared_cli_parse_message(const char* str, InfraredSignal* signal) { char protocol_name[32]; InfraredMessage message; diff --git a/lib/infrared/worker/infrared_worker.c b/lib/infrared/worker/infrared_worker.c index 86b19114c16..c03f180f66a 100644 --- a/lib/infrared/worker/infrared_worker.c +++ b/lib/infrared/worker/infrared_worker.c @@ -57,6 +57,7 @@ struct InfraredWorker { InfraredDecoderHandler* infrared_decoder; NotificationApp* notification; bool blink_enable; + bool decode_enable; union { struct { @@ -131,7 +132,8 @@ static void infrared_worker_process_timeout(InfraredWorker* instance) { static void infrared_worker_process_timings(InfraredWorker* instance, uint32_t duration, bool level) { const InfraredMessage* message_decoded = - infrared_decode(instance->infrared_decoder, level, duration); + instance->decode_enable ? infrared_decode(instance->infrared_decoder, level, duration) : + NULL; if(message_decoded) { instance->signal.message = *message_decoded; instance->signal.timings_cnt = 0; @@ -233,6 +235,7 @@ InfraredWorker* infrared_worker_alloc() { instance->infrared_decoder = infrared_alloc_decoder(); instance->infrared_encoder = infrared_alloc_encoder(); instance->blink_enable = false; + instance->decode_enable = true; instance->notification = furi_record_open(RECORD_NOTIFICATION); instance->state = InfraredWorkerStateIdle; @@ -316,6 +319,11 @@ void infrared_worker_rx_enable_blink_on_receiving(InfraredWorker* instance, bool instance->blink_enable = enable; } +void infrared_worker_rx_enable_signal_decoding(InfraredWorker* instance, bool enable) { + furi_assert(instance); + instance->decode_enable = enable; +} + void infrared_worker_tx_start(InfraredWorker* instance) { furi_assert(instance); furi_assert(instance->state == InfraredWorkerStateIdle); diff --git a/lib/infrared/worker/infrared_worker.h b/lib/infrared/worker/infrared_worker.h index c6617e501e3..26919c4f59b 100644 --- a/lib/infrared/worker/infrared_worker.h +++ b/lib/infrared/worker/infrared_worker.h @@ -76,6 +76,14 @@ void infrared_worker_rx_set_received_signal_callback( */ void infrared_worker_rx_enable_blink_on_receiving(InfraredWorker* instance, bool enable); +/** Enable decoding of received infrared signals. + * + * @param[in] instance - instance of InfraredWorker + * @param[in] enable - true if you want to enable decoding + * false otherwise + */ +void infrared_worker_rx_enable_signal_decoding(InfraredWorker* instance, bool enable); + /** Clarify is received signal either decoded or raw * * @param[in] signal - received signal From 68009c6230c5898d96409255897c35ead7991bcf Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Tue, 18 Oct 2022 18:24:53 +0400 Subject: [PATCH 156/824] [FL-2919] SubGhz: CAME Wrong number of bits in key (add protocol Airforce) (#1890) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- lib/subghz/protocols/came.c | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/lib/subghz/protocols/came.c b/lib/subghz/protocols/came.c index 7c037bd129c..3fc61bf60ea 100644 --- a/lib/subghz/protocols/came.c +++ b/lib/subghz/protocols/came.c @@ -16,6 +16,8 @@ #define CAME_24_COUNT_BIT 24 #define PRASTEL_COUNT_BIT 25 #define PRASTEL_NAME "Prastel" +#define AIRFORCE_COUNT_BIT 18 +#define AIRFORCE_NAME "Airforce" static const SubGhzBlockConst subghz_protocol_came_const = { .te_short = 320, @@ -86,7 +88,7 @@ void* subghz_protocol_encoder_came_alloc(SubGhzEnvironment* environment) { instance->generic.protocol_name = instance->base.protocol->name; instance->encoder.repeat = 10; - instance->encoder.size_upload = 52; //max 24bit*2 + 2 (start, stop) + instance->encoder.size_upload = 128; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; return instance; @@ -151,10 +153,7 @@ bool subghz_protocol_encoder_came_deserialize(void* context, FlipperFormat* flip FURI_LOG_E(TAG, "Deserialize error"); break; } - if((instance->generic.data_count_bit != - subghz_protocol_came_const.min_count_bit_for_found) && - (instance->generic.data_count_bit != CAME_24_COUNT_BIT) && - (instance->generic.data_count_bit != PRASTEL_COUNT_BIT)) { + if((instance->generic.data_count_bit > PRASTEL_COUNT_BIT)) { FURI_LOG_E(TAG, "Wrong number of bits in key"); break; } @@ -310,10 +309,7 @@ bool subghz_protocol_decoder_came_deserialize(void* context, FlipperFormat* flip if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { break; } - if((instance->generic.data_count_bit != - subghz_protocol_came_const.min_count_bit_for_found) && - (instance->generic.data_count_bit != CAME_24_COUNT_BIT) && - (instance->generic.data_count_bit != PRASTEL_COUNT_BIT)) { + if((instance->generic.data_count_bit > PRASTEL_COUNT_BIT)) { FURI_LOG_E(TAG, "Wrong number of bits in key"); break; } @@ -338,8 +334,11 @@ void subghz_protocol_decoder_came_get_string(void* context, FuriString* output) "%s %dbit\r\n" "Key:0x%08lX\r\n" "Yek:0x%08lX\r\n", - (instance->generic.data_count_bit == PRASTEL_COUNT_BIT ? PRASTEL_NAME : - instance->generic.protocol_name), + (instance->generic.data_count_bit == PRASTEL_COUNT_BIT ? + PRASTEL_NAME : + (instance->generic.data_count_bit == AIRFORCE_COUNT_BIT ? + AIRFORCE_NAME : + instance->generic.protocol_name)), instance->generic.data_count_bit, code_found_lo, code_found_reverse_lo); From 56f760aa07ba5e659f7d8278819f3699afc61504 Mon Sep 17 00:00:00 2001 From: Patrick Cunningham Date: Tue, 18 Oct 2022 09:58:26 -0500 Subject: [PATCH 157/824] Picopass: Read Elite (#1888) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * working elite dict * add csn to display Co-authored-by: あく --- .../picopass/helpers/iclass_elite_dict.c | 151 ++++++++++++++++++ .../picopass/helpers/iclass_elite_dict.h | 28 ++++ .../picopass/lib/loclass/optimized_elite.c | 2 +- .../plugins/picopass/picopass_device.h | 2 + .../plugins/picopass/picopass_worker.c | 103 ++++++++++-- .../plugins/picopass/picopass_worker_i.h | 1 + .../scenes/picopass_scene_read_card_success.c | 29 ++-- 7 files changed, 289 insertions(+), 27 deletions(-) create mode 100644 applications/plugins/picopass/helpers/iclass_elite_dict.c create mode 100644 applications/plugins/picopass/helpers/iclass_elite_dict.h diff --git a/applications/plugins/picopass/helpers/iclass_elite_dict.c b/applications/plugins/picopass/helpers/iclass_elite_dict.c new file mode 100644 index 00000000000..455eb23c170 --- /dev/null +++ b/applications/plugins/picopass/helpers/iclass_elite_dict.c @@ -0,0 +1,151 @@ +#include "iclass_elite_dict.h" + +#include +#include + +#define ICLASS_ELITE_DICT_FLIPPER_PATH EXT_PATH("picopass/assets/iclass_elite_dict.txt") +#define ICLASS_ELITE_DICT_USER_PATH EXT_PATH("picopass/assets/iclass_elite_dict_user.txt") + +#define TAG "IclassEliteDict" + +#define ICLASS_ELITE_KEY_LINE_LEN (17) +#define ICLASS_ELITE_KEY_LEN (8) + +struct IclassEliteDict { + Stream* stream; + uint32_t total_keys; +}; + +bool iclass_elite_dict_check_presence(IclassEliteDictType dict_type) { + Storage* storage = furi_record_open(RECORD_STORAGE); + + bool dict_present = false; + if(dict_type == IclassEliteDictTypeFlipper) { + dict_present = storage_common_stat(storage, ICLASS_ELITE_DICT_FLIPPER_PATH, NULL) == + FSE_OK; + } else if(dict_type == IclassEliteDictTypeUser) { + dict_present = storage_common_stat(storage, ICLASS_ELITE_DICT_USER_PATH, NULL) == FSE_OK; + } + + furi_record_close(RECORD_STORAGE); + + return dict_present; +} + +IclassEliteDict* iclass_elite_dict_alloc(IclassEliteDictType dict_type) { + IclassEliteDict* dict = malloc(sizeof(IclassEliteDict)); + Storage* storage = furi_record_open(RECORD_STORAGE); + dict->stream = buffered_file_stream_alloc(storage); + furi_record_close(RECORD_STORAGE); + FuriString* next_line = furi_string_alloc(); + + bool dict_loaded = false; + do { + if(dict_type == IclassEliteDictTypeFlipper) { + if(!buffered_file_stream_open( + dict->stream, ICLASS_ELITE_DICT_FLIPPER_PATH, FSAM_READ, FSOM_OPEN_EXISTING)) { + buffered_file_stream_close(dict->stream); + break; + } + } else if(dict_type == IclassEliteDictTypeUser) { + if(!buffered_file_stream_open( + dict->stream, ICLASS_ELITE_DICT_USER_PATH, FSAM_READ_WRITE, FSOM_OPEN_ALWAYS)) { + buffered_file_stream_close(dict->stream); + break; + } + } + + // Read total amount of keys + while(true) { + if(!stream_read_line(dict->stream, next_line)) break; + if(furi_string_get_char(next_line, 0) == '#') continue; + if(furi_string_size(next_line) != ICLASS_ELITE_KEY_LINE_LEN) continue; + dict->total_keys++; + } + furi_string_reset(next_line); + stream_rewind(dict->stream); + + dict_loaded = true; + FURI_LOG_I(TAG, "Loaded dictionary with %lu keys", dict->total_keys); + } while(false); + + if(!dict_loaded) { + buffered_file_stream_close(dict->stream); + free(dict); + dict = NULL; + } + + furi_string_free(next_line); + + return dict; +} + +void iclass_elite_dict_free(IclassEliteDict* dict) { + furi_assert(dict); + furi_assert(dict->stream); + + buffered_file_stream_close(dict->stream); + stream_free(dict->stream); + free(dict); +} + +uint32_t iclass_elite_dict_get_total_keys(IclassEliteDict* dict) { + furi_assert(dict); + + return dict->total_keys; +} + +bool iclass_elite_dict_get_next_key(IclassEliteDict* dict, uint8_t* key) { + furi_assert(dict); + furi_assert(dict->stream); + + uint8_t key_byte_tmp = 0; + FuriString* next_line = furi_string_alloc(); + + bool key_read = false; + *key = 0ULL; + while(!key_read) { + if(!stream_read_line(dict->stream, next_line)) break; + if(furi_string_get_char(next_line, 0) == '#') continue; + if(furi_string_size(next_line) != ICLASS_ELITE_KEY_LINE_LEN) continue; + for(uint8_t i = 0; i < ICLASS_ELITE_KEY_LEN * 2; i += 2) { + args_char_to_hex( + furi_string_get_char(next_line, i), + furi_string_get_char(next_line, i + 1), + &key_byte_tmp); + key[i / 2] = key_byte_tmp; + } + key_read = true; + } + + furi_string_free(next_line); + return key_read; +} + +bool iclass_elite_dict_rewind(IclassEliteDict* dict) { + furi_assert(dict); + furi_assert(dict->stream); + + return stream_rewind(dict->stream); +} + +bool iclass_elite_dict_add_key(IclassEliteDict* dict, uint8_t* key) { + furi_assert(dict); + furi_assert(dict->stream); + + FuriString* key_str = furi_string_alloc(); + for(size_t i = 0; i < 6; i++) { + furi_string_cat_printf(key_str, "%02X", key[i]); + } + furi_string_cat_printf(key_str, "\n"); + + bool key_added = false; + do { + if(!stream_seek(dict->stream, 0, StreamOffsetFromEnd)) break; + if(!stream_insert_string(dict->stream, key_str)) break; + key_added = true; + } while(false); + + furi_string_free(key_str); + return key_added; +} \ No newline at end of file diff --git a/applications/plugins/picopass/helpers/iclass_elite_dict.h b/applications/plugins/picopass/helpers/iclass_elite_dict.h new file mode 100644 index 00000000000..e5ec8dfcbea --- /dev/null +++ b/applications/plugins/picopass/helpers/iclass_elite_dict.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include +#include +#include +#include + +typedef enum { + IclassEliteDictTypeUser, + IclassEliteDictTypeFlipper, +} IclassEliteDictType; + +typedef struct IclassEliteDict IclassEliteDict; + +bool iclass_elite_dict_check_presence(IclassEliteDictType dict_type); + +IclassEliteDict* iclass_elite_dict_alloc(IclassEliteDictType dict_type); + +void iclass_elite_dict_free(IclassEliteDict* dict); + +uint32_t iclass_elite_dict_get_total_keys(IclassEliteDict* dict); + +bool iclass_elite_dict_get_next_key(IclassEliteDict* dict, uint8_t* key); + +bool iclass_elite_dict_rewind(IclassEliteDict* dict); + +bool iclass_elite_dict_add_key(IclassEliteDict* dict, uint8_t* key); \ No newline at end of file diff --git a/applications/plugins/picopass/lib/loclass/optimized_elite.c b/applications/plugins/picopass/lib/loclass/optimized_elite.c index c175f3986b5..34e98706026 100644 --- a/applications/plugins/picopass/lib/loclass/optimized_elite.c +++ b/applications/plugins/picopass/lib/loclass/optimized_elite.c @@ -185,7 +185,7 @@ static void loclass_desencrypt_iclass(uint8_t* iclass_key, uint8_t* input, uint8 * @param loclass_hash1 loclass_hash1 * @param key_sel output key_sel=h[loclass_hash1[i]] */ -void hash2(uint8_t* key64, uint8_t* outp_keytable) { +void loclass_hash2(uint8_t* key64, uint8_t* outp_keytable) { /** *Expected: * High Security Key Table diff --git a/applications/plugins/picopass/picopass_device.h b/applications/plugins/picopass/picopass_device.h index ed6bb47819d..26f2159419b 100644 --- a/applications/plugins/picopass/picopass_device.h +++ b/applications/plugins/picopass/picopass_device.h @@ -9,6 +9,7 @@ #include "rfal_picopass.h" #include #include +#include "helpers/iclass_elite_dict.h" #define PICOPASS_DEV_NAME_MAX_LEN 22 #define PICOPASS_READER_DATA_MAX_SIZE 64 @@ -49,6 +50,7 @@ typedef struct { bool se_enabled; bool sio; bool biometrics; + uint8_t key[8]; uint8_t pin_length; PicopassEncryption encryption; uint8_t credential[8]; diff --git a/applications/plugins/picopass/picopass_worker.c b/applications/plugins/picopass/picopass_worker.c index 532effd9ad9..2c9b1a0ceed 100644 --- a/applications/plugins/picopass/picopass_worker.c +++ b/applications/plugins/picopass/picopass_worker.c @@ -1,5 +1,7 @@ #include "picopass_worker_i.h" +#include + #define TAG "PicopassWorker" const uint8_t picopass_iclass_key[] = {0xaf, 0xa7, 0x85, 0xa7, 0xda, 0xb3, 0x33, 0x78}; @@ -176,7 +178,7 @@ ReturnCode picopass_read_preauth(PicopassBlock* AA1) { return ERR_NONE; } -ReturnCode picopass_read_card(PicopassBlock* AA1) { +ReturnCode picopass_auth(PicopassBlock* AA1, PicopassPacs* pacs) { rfalPicoPassReadCheckRes rcRes; rfalPicoPassCheckRes chkRes; @@ -197,11 +199,69 @@ ReturnCode picopass_read_card(PicopassBlock* AA1) { loclass_opt_doReaderMAC(ccnr, div_key, mac); err = rfalPicoPassPollerCheck(mac, &chkRes); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "rfalPicoPassPollerCheck error %d", err); - return err; + if(err == ERR_NONE) { + return ERR_NONE; + } + FURI_LOG_E(TAG, "rfalPicoPassPollerCheck error %d", err); + + FURI_LOG_E(TAG, "Starting dictionary attack"); + + size_t index = 0; + uint8_t key[PICOPASS_BLOCK_LEN] = {0}; + + if(!iclass_elite_dict_check_presence(IclassEliteDictTypeFlipper)) { + FURI_LOG_E(TAG, "Dictionary not found"); + return ERR_PARAM; + } + + IclassEliteDict* dict = iclass_elite_dict_alloc(IclassEliteDictTypeFlipper); + if(!dict) { + FURI_LOG_E(TAG, "Dictionary not allocated"); + return ERR_PARAM; + } + + FURI_LOG_D(TAG, "Loaded %lu keys", iclass_elite_dict_get_total_keys(dict)); + while(iclass_elite_dict_get_next_key(dict, key)) { + FURI_LOG_D( + TAG, + "Try to auth with key %d %02x%02x%02x%02x%02x%02x%02x%02x", + index++, + key[0], + key[1], + key[2], + key[3], + key[4], + key[5], + key[6], + key[7]); + + err = rfalPicoPassPollerReadCheck(&rcRes); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "rfalPicoPassPollerReadCheck error %d", err); + return err; + } + memcpy(ccnr, rcRes.CCNR, sizeof(rcRes.CCNR)); // last 4 bytes left 0 + + loclass_iclass_calc_div_key(AA1[PICOPASS_CSN_BLOCK_INDEX].data, key, div_key, true); + loclass_opt_doReaderMAC(ccnr, div_key, mac); + + err = rfalPicoPassPollerCheck(mac, &chkRes); + if(err == ERR_NONE) { + memcpy(pacs->key, key, PICOPASS_BLOCK_LEN); + break; + } + } + + if(dict) { + iclass_elite_dict_free(dict); } + return err; +} + +ReturnCode picopass_read_card(PicopassBlock* AA1) { + ReturnCode err; + size_t app_limit = AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[0] < PICOPASS_MAX_APP_LIMIT ? AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[0] : PICOPASS_MAX_APP_LIMIT; @@ -352,28 +412,39 @@ void picopass_worker_detect(PicopassWorker* picopass_worker) { pacs->se_enabled = (memcmp(AA1[5].data, "\xff\xff\xff\x00\x06\xff\xff\xff", 8) == 0); if(pacs->se_enabled) { FURI_LOG_D(TAG, "SE enabled"); + nextState = PicopassWorkerEventFail; } - err = picopass_read_card(AA1); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "picopass_read_card error %d", err); - nextState = PicopassWorkerEventFail; + if(nextState == PicopassWorkerEventSuccess) { + err = picopass_auth(AA1, pacs); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "picopass_try_auth error %d", err); + nextState = PicopassWorkerEventFail; + } } if(nextState == PicopassWorkerEventSuccess) { - err = picopass_device_parse_credential(AA1, pacs); + err = picopass_read_card(AA1); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "picopass_read_card error %d", err); + nextState = PicopassWorkerEventFail; + } } - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "picopass_device_parse_credential error %d", err); - nextState = PicopassWorkerEventFail; + + if(nextState == PicopassWorkerEventSuccess) { + err = picopass_device_parse_credential(AA1, pacs); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "picopass_device_parse_credential error %d", err); + nextState = PicopassWorkerEventFail; + } } if(nextState == PicopassWorkerEventSuccess) { err = picopass_device_parse_wiegand(pacs->credential, &pacs->record); - } - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "picopass_device_parse_wiegand error %d", err); - nextState = PicopassWorkerEventFail; + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "picopass_device_parse_wiegand error %d", err); + nextState = PicopassWorkerEventFail; + } } // Notify caller and exit diff --git a/applications/plugins/picopass/picopass_worker_i.h b/applications/plugins/picopass/picopass_worker_i.h index 9b215fb7437..101d4d8f25d 100644 --- a/applications/plugins/picopass/picopass_worker_i.h +++ b/applications/plugins/picopass/picopass_worker_i.h @@ -18,6 +18,7 @@ struct PicopassWorker { FuriThread* thread; Storage* storage; + Stream* dict_stream; PicopassDeviceData* dev_data; PicopassWorkerCallback callback; diff --git a/applications/plugins/picopass/scenes/picopass_scene_read_card_success.c b/applications/plugins/picopass/scenes/picopass_scene_read_card_success.c index 37f1db4f26e..bb170ac4503 100644 --- a/applications/plugins/picopass/scenes/picopass_scene_read_card_success.c +++ b/applications/plugins/picopass/scenes/picopass_scene_read_card_success.c @@ -15,12 +15,10 @@ void picopass_scene_read_card_success_widget_callback( void picopass_scene_read_card_success_on_enter(void* context) { Picopass* picopass = context; - FuriString* credential_str; - FuriString* wiegand_str; - FuriString* sio_str; - credential_str = furi_string_alloc(); - wiegand_str = furi_string_alloc(); - sio_str = furi_string_alloc(); + FuriString* csn_str = furi_string_alloc_set("CSN:"); + FuriString* credential_str = furi_string_alloc(); + FuriString* wiegand_str = furi_string_alloc(); + FuriString* sio_str = furi_string_alloc(); DOLPHIN_DEED(DolphinDeedNfcReadSuccess); @@ -28,10 +26,18 @@ void picopass_scene_read_card_success_on_enter(void* context) { notification_message(picopass->notifications, &sequence_success); // Setup view + PicopassBlock* AA1 = picopass->dev->dev_data.AA1; PicopassPacs* pacs = &picopass->dev->dev_data.pacs; Widget* widget = picopass->widget; - if(pacs->record.bitLength == 0) { + uint8_t csn[PICOPASS_BLOCK_LEN]; + memcpy(csn, &AA1->data[PICOPASS_CSN_BLOCK_INDEX], PICOPASS_BLOCK_LEN); + for(uint8_t i = 0; i < PICOPASS_BLOCK_LEN; i++) { + furi_string_cat_printf(csn_str, " %02X", csn[i]); + } + + // Neither of these are valid. Indicates the block was all 0x00 or all 0xff + if(pacs->record.bitLength == 0 || pacs->record.bitLength == 255) { furi_string_cat_printf(wiegand_str, "Read Failed"); if(pacs->se_enabled) { @@ -79,18 +85,21 @@ void picopass_scene_read_card_success_on_enter(void* context) { } widget_add_string_element( - widget, 64, 12, AlignCenter, AlignCenter, FontPrimary, furi_string_get_cstr(wiegand_str)); + widget, 64, 5, AlignCenter, AlignCenter, FontSecondary, furi_string_get_cstr(csn_str)); + widget_add_string_element( + widget, 64, 20, AlignCenter, AlignCenter, FontPrimary, furi_string_get_cstr(wiegand_str)); widget_add_string_element( widget, 64, - 32, + 36, AlignCenter, AlignCenter, FontSecondary, furi_string_get_cstr(credential_str)); widget_add_string_element( - widget, 64, 42, AlignCenter, AlignCenter, FontSecondary, furi_string_get_cstr(sio_str)); + widget, 64, 46, AlignCenter, AlignCenter, FontSecondary, furi_string_get_cstr(sio_str)); + furi_string_free(csn_str); furi_string_free(credential_str); furi_string_free(wiegand_str); furi_string_free(sio_str); From 72713d6f4e8525d9550285bd6c57c30d789a1dc1 Mon Sep 17 00:00:00 2001 From: Kevin Kwok Date: Tue, 18 Oct 2022 08:06:18 -0700 Subject: [PATCH 158/824] Allow pins 0 and 1 as RTS/DTR for USB UART Bridge (#1864) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Allow pins 0 and 1 as RTS/DTR for USB UART Bridge * add logic to gpio_scene_usb_uart_config, fix flow_pins * fixing count of pins * disable PC0,PC1 RTS/DTR when using LPUART * add logic to ensure flow pins dont overlap with uart lines Co-authored-by: あく --- applications/main/gpio/gpio_app_i.h | 1 + .../gpio/scenes/gpio_scene_usb_uart_config.c | 26 +++++++++++++++++-- applications/main/gpio/usb_uart_bridge.c | 1 + .../services/gui/modules/variable_item_list.c | 4 +++ .../services/gui/modules/variable_item_list.h | 7 +++++ 5 files changed, 37 insertions(+), 2 deletions(-) diff --git a/applications/main/gpio/gpio_app_i.h b/applications/main/gpio/gpio_app_i.h index fa91e8f79db..fff35c95a7c 100644 --- a/applications/main/gpio/gpio_app_i.h +++ b/applications/main/gpio/gpio_app_i.h @@ -24,6 +24,7 @@ struct GpioApp { Widget* widget; VariableItemList* var_item_list; + VariableItem* var_item_flow; GpioTest* gpio_test; GpioUsbUart* gpio_usb_uart; UsbUartBridge* usb_uart_bridge; diff --git a/applications/main/gpio/scenes/gpio_scene_usb_uart_config.c b/applications/main/gpio/scenes/gpio_scene_usb_uart_config.c index 653a306aa56..c114d79a222 100644 --- a/applications/main/gpio/scenes/gpio_scene_usb_uart_config.c +++ b/applications/main/gpio/scenes/gpio_scene_usb_uart_config.c @@ -13,7 +13,7 @@ static UsbUartConfig* cfg_set; static const char* vcp_ch[] = {"0 (CLI)", "1"}; static const char* uart_ch[] = {"13,14", "15,16"}; -static const char* flow_pins[] = {"None", "2,3", "6,7"}; +static const char* flow_pins[] = {"None", "2,3", "6,7", "16,15"}; static const char* baudrate_mode[] = {"Host"}; static const uint32_t baudrate_list[] = { 2400, @@ -33,6 +33,24 @@ bool gpio_scene_usb_uart_cfg_on_event(void* context, SceneManagerEvent event) { return false; } +void line_ensure_flow_invariant(GpioApp* app) { + // GPIO pins PC0, PC1 (16,15) are unavailable for RTS/DTR when LPUART is + // selected. This function enforces that invariant by resetting flow_pins + // to None if it is configured to 16,15 when LPUART is selected. + + uint8_t available_flow_pins = cfg_set->uart_ch == FuriHalUartIdLPUART1 ? 3 : 4; + VariableItem* item = app->var_item_flow; + variable_item_set_values_count(item, available_flow_pins); + + if(cfg_set->flow_pins >= available_flow_pins) { + cfg_set->flow_pins = 0; + usb_uart_set_config(app->usb_uart_bridge, cfg_set); + + variable_item_set_current_value_index(item, cfg_set->flow_pins); + variable_item_set_current_value_text(item, flow_pins[cfg_set->flow_pins]); + } +} + static void line_vcp_cb(VariableItem* item) { GpioApp* app = variable_item_get_context(item); uint8_t index = variable_item_get_current_value_index(item); @@ -54,6 +72,7 @@ static void line_port_cb(VariableItem* item) { else if(index == 1) cfg_set->uart_ch = FuriHalUartIdLPUART1; usb_uart_set_config(app->usb_uart_bridge, cfg_set); + line_ensure_flow_invariant(app); } static void line_flow_cb(VariableItem* item) { @@ -116,9 +135,12 @@ void gpio_scene_usb_uart_cfg_on_enter(void* context) { variable_item_set_current_value_index(item, cfg_set->uart_ch); variable_item_set_current_value_text(item, uart_ch[cfg_set->uart_ch]); - item = variable_item_list_add(var_item_list, "RTS/DTR Pins", 3, line_flow_cb, app); + item = variable_item_list_add( + var_item_list, "RTS/DTR Pins", COUNT_OF(flow_pins), line_flow_cb, app); variable_item_set_current_value_index(item, cfg_set->flow_pins); variable_item_set_current_value_text(item, flow_pins[cfg_set->flow_pins]); + app->var_item_flow = item; + line_ensure_flow_invariant(app); variable_item_list_set_selected_item( var_item_list, scene_manager_get_scene_state(app->scene_manager, GpioAppViewUsbUartCfg)); diff --git a/applications/main/gpio/usb_uart_bridge.c b/applications/main/gpio/usb_uart_bridge.c index 6e0bce736a5..a5caceafa46 100644 --- a/applications/main/gpio/usb_uart_bridge.c +++ b/applications/main/gpio/usb_uart_bridge.c @@ -14,6 +14,7 @@ static const GpioPin* flow_pins[][2] = { {&gpio_ext_pa7, &gpio_ext_pa6}, // 2, 3 {&gpio_ext_pb2, &gpio_ext_pc3}, // 6, 7 + {&gpio_ext_pc0, &gpio_ext_pc1}, // 16, 15 }; typedef enum { diff --git a/applications/services/gui/modules/variable_item_list.c b/applications/services/gui/modules/variable_item_list.c index acd46ee4b15..a9b89d63b52 100644 --- a/applications/services/gui/modules/variable_item_list.c +++ b/applications/services/gui/modules/variable_item_list.c @@ -396,6 +396,10 @@ void variable_item_set_current_value_index(VariableItem* item, uint8_t current_v item->current_value_index = current_value_index; } +void variable_item_set_values_count(VariableItem* item, uint8_t values_count) { + item->values_count = values_count; +} + void variable_item_set_current_value_text(VariableItem* item, const char* current_value_text) { furi_string_set(item->current_value_text, current_value_text); } diff --git a/applications/services/gui/modules/variable_item_list.h b/applications/services/gui/modules/variable_item_list.h index 78c7769c519..db2a58993d4 100644 --- a/applications/services/gui/modules/variable_item_list.h +++ b/applications/services/gui/modules/variable_item_list.h @@ -81,6 +81,13 @@ uint8_t variable_item_list_get_selected_item_index(VariableItemList* variable_it */ void variable_item_set_current_value_index(VariableItem* item, uint8_t current_value_index); +/** Set number of values for item + * + * @param item VariableItem* instance + * @param values_count The new values count + */ +void variable_item_set_values_count(VariableItem* item, uint8_t values_count); + /** Set item current selected text * * @param item VariableItem* instance From 79c3040629cc7390ffad9e081c165dc03846699f Mon Sep 17 00:00:00 2001 From: hedger Date: Wed, 19 Oct 2022 18:28:48 +0400 Subject: [PATCH 159/824] fbt: fixed dependency issues with SDK definition file (#1893) * fbt: fixed dependency issues with SDK definition file * fbt: more path fixes; marked up new symbols --- firmware.scons | 2 +- firmware/SConscript | 4 +++- firmware/targets/f7/api_symbols.csv | 5 ++++- scripts/fbt_tools/fbt_extapps.py | 4 ++-- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/firmware.scons b/firmware.scons index d28309d0e09..5219a2f3baa 100644 --- a/firmware.scons +++ b/firmware.scons @@ -329,7 +329,7 @@ if fwenv["IS_BASE_FIRMWARE"]: # AlwaysBuild(sdk_tree) Alias("sdk_tree", sdk_tree) - sdk_apicheck = fwenv.SDKSymUpdater(fwenv.subst("$SDK_DEFINITION"), "sdk_origin") + sdk_apicheck = fwenv.SDKSymUpdater(fwenv["SDK_DEFINITION"], "sdk_origin") Precious(sdk_apicheck) NoClean(sdk_apicheck) AlwaysBuild(sdk_apicheck) diff --git a/firmware/SConscript b/firmware/SConscript index 5795e242b80..2285a6f2cfb 100644 --- a/firmware/SConscript +++ b/firmware/SConscript @@ -9,8 +9,10 @@ env.Append( File("#/firmware/targets/f7/platform_specific/intrinsic_export.h"), ], ) -env.SetDefault(SDK_DEFINITION=env.File("./targets/f${TARGET_HW}/api_symbols.csv")) +env.SetDefault( + SDK_DEFINITION=env.File("./targets/f${TARGET_HW}/api_symbols.csv").srcnode() +) libenv = env.Clone(FW_LIB_NAME="flipper${TARGET_HW}") libenv.Append( diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 33e289948dc..d3d5a90104d 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,2.3,, +Version,+,2.4,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -929,6 +929,7 @@ Function,+,furi_hal_bt_nvm_sram_sem_release,void, Function,+,furi_hal_bt_reinit,void, Function,+,furi_hal_bt_serial_notify_buffer_is_empty,void, Function,+,furi_hal_bt_serial_set_event_callback,void,"uint16_t, FuriHalBtSerialCallback, void*" +Function,+,furi_hal_bt_serial_set_rpc_status,void,FuriHalBtSerialRpcStatus Function,+,furi_hal_bt_serial_start,void, Function,+,furi_hal_bt_serial_stop,void, Function,+,furi_hal_bt_serial_tx,_Bool,"uint8_t*, uint16_t" @@ -2071,6 +2072,7 @@ Function,-,select,int,"int, fd_set*, fd_set*, fd_set*, timeval*" Function,-,serial_svc_is_started,_Bool, Function,-,serial_svc_notify_buffer_is_empty,void, Function,-,serial_svc_set_callbacks,void,"uint16_t, SerialServiceEventCallback, void*" +Function,-,serial_svc_set_rpc_status,void,SerialServiceRpcStatus Function,-,serial_svc_start,void, Function,-,serial_svc_stop,void, Function,-,serial_svc_update_tx,_Bool,"uint8_t*, uint16_t" @@ -2467,6 +2469,7 @@ Function,+,variable_item_list_set_enter_callback,void,"VariableItemList*, Variab Function,+,variable_item_list_set_selected_item,void,"VariableItemList*, uint8_t" Function,+,variable_item_set_current_value_index,void,"VariableItem*, uint8_t" Function,+,variable_item_set_current_value_text,void,"VariableItem*, const char*" +Function,+,variable_item_set_values_count,void,"VariableItem*, uint8_t" Function,-,vasiprintf,int,"char**, const char*, __gnuc_va_list" Function,-,vasniprintf,char*,"char*, size_t*, const char*, __gnuc_va_list" Function,-,vasnprintf,char*,"char*, size_t*, const char*, __gnuc_va_list" diff --git a/scripts/fbt_tools/fbt_extapps.py b/scripts/fbt_tools/fbt_extapps.py index 9f81d4145c0..e40a4efc251 100644 --- a/scripts/fbt_tools/fbt_extapps.py +++ b/scripts/fbt_tools/fbt_extapps.py @@ -145,7 +145,7 @@ def legacy_app_build_stub(**kw): def prepare_app_metadata(target, source, env): - sdk_cache = SdkCache(env.subst("$SDK_DEFINITION"), load_version_only=True) + sdk_cache = SdkCache(env["SDK_DEFINITION"].path, load_version_only=True) if not sdk_cache.is_buildable(): raise UserError( @@ -166,7 +166,7 @@ def prepare_app_metadata(target, source, env): def validate_app_imports(target, source, env): - sdk_cache = SdkCache(env.subst("$SDK_DEFINITION"), load_version_only=False) + sdk_cache = SdkCache(env["SDK_DEFINITION"].path, load_version_only=False) app_syms = set() with open(target[0].path, "rt") as f: for line in f: From 9a9abd59e9a97e0e215041eca7fd1ee0570bd10b Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Wed, 19 Oct 2022 21:27:26 +0400 Subject: [PATCH 160/824] [FL-2904, FL-2900, FL-2890] WS: add app WeatherStation (#1833) * WeatherStation: start * SubGhz: rename protocol magellen -> magellan * WeatherStation: err Unresolved symbols: {'subghz_protocol_decoder_base_get_string'} * WeatherStation: fix Unresolved symbols: {'subghz_protocol_decoder_base_get_string'} * Subghz: add set protocol_items * WeatherStation: adding your protocols * WS: add Infactory protocol * WS: add history * WS: add setting * WS: add lock * WS: add hopper frequency * WS: fix history * WS fix string_t -> FuriString* * WS: add images * WS: history record update when receiving data from the sensor again * WS: add receiver info, delete extra code * WS: add protocol ThermoPRO_TX4 * [FL-2900] SubGhz: Move icons in Sub-GHz * WS: add Notification * [FL-2890] SubGhz: Rename *_user files in resources to _user.example * WS: add about scene * WS: removing redundant code * WS: add protocol Nexus-TH * WS: add protocol GT_WT03 * WS: fix notification and rename "Weather Station" -> "Read Weather Station" * SubGhz: partial unit tests fix * SubGhz: fix unit_test * SubGhz: remove dead code * SubGhz: rename SubGhzPresetDefinition into SubGhzRadioPreset, cleanup subghz types. Co-authored-by: Aleksandr Kutuzov --- .../debug/unit_tests/subghz/subghz_test.c | 20 +- .../lfrfid/scenes/lfrfid_scene_raw_info.c | 2 +- .../main/subghz/helpers/subghz_types.h | 9 - .../scenes/subghz_scene_receiver_info.c | 4 +- .../subghz/scenes/subghz_scene_set_type.c | 2 +- applications/main/subghz/subghz.c | 5 +- applications/main/subghz/subghz_cli.c | 4 + applications/main/subghz/subghz_history.c | 8 +- applications/main/subghz/subghz_history.h | 8 +- applications/main/subghz/subghz_i.h | 9 +- applications/main/subghz/views/receiver.c | 2 +- .../plugins/weather_station/application.fam | 13 + .../helpers/weather_station_event.h | 14 + .../helpers/weather_station_types.h | 49 ++ .../weather_station/images/Humid_10x15.png | Bin 0 -> 3624 bytes .../weather_station/images/Therm_7x16.png | Bin 0 -> 3611 bytes .../weather_station/images/station_icon.png | Bin 0 -> 3607 bytes .../weather_station/protocols/gt_wt_03.c | 341 ++++++++++++++ .../weather_station/protocols/gt_wt_03.h | 79 ++++ .../weather_station/protocols/infactory.c | 296 ++++++++++++ .../weather_station/protocols/infactory.h | 79 ++++ .../weather_station/protocols/nexus_th.c | 261 +++++++++++ .../weather_station/protocols/nexus_th.h | 79 ++++ .../protocols/protocol_items.c | 12 + .../protocols/protocol_items.h | 9 + .../weather_station/protocols/thermopro_tx4.c | 260 +++++++++++ .../weather_station/protocols/thermopro_tx4.h | 79 ++++ .../weather_station/protocols/ws_generic.c | 198 ++++++++ .../weather_station/protocols/ws_generic.h | 61 +++ .../scenes/weather_station_receiver.c | 207 +++++++++ .../scenes/weather_station_scene.c | 30 ++ .../scenes/weather_station_scene.h | 29 ++ .../scenes/weather_station_scene_about.c | 78 ++++ .../scenes/weather_station_scene_config.h | 5 + .../weather_station_scene_receiver_config.c | 223 +++++++++ .../weather_station_scene_receiver_info.c | 50 ++ .../scenes/weather_station_scene_start.c | 58 +++ .../views/weather_station_receiver.c | 437 ++++++++++++++++++ .../views/weather_station_receiver.h | 36 ++ .../views/weather_station_receiver_info.c | 150 ++++++ .../views/weather_station_receiver_info.h | 16 + .../weather_station/weather_station_10px.png | Bin 0 -> 175 bytes .../weather_station/weather_station_app.c | 178 +++++++ .../weather_station/weather_station_app_i.c | 159 +++++++ .../weather_station/weather_station_app_i.h | 73 +++ .../weather_station/weather_station_history.c | 246 ++++++++++ .../weather_station/weather_station_history.h | 112 +++++ ...codes_user => keeloq_mfcodes_user.example} | 1 + .../{setting_user => setting_user.example} | 1 + .../subghz/{magellen.sub => magellan.sub} | 2 +- .../{magellen_raw.sub => magellan_raw.sub} | 0 firmware/targets/f7/api_symbols.csv | 59 ++- lib/subghz/SConscript | 6 + lib/subghz/blocks/const.h | 8 + lib/subghz/blocks/decoder.h | 8 + lib/subghz/blocks/encoder.h | 8 + lib/subghz/blocks/generic.c | 2 +- lib/subghz/blocks/generic.h | 12 +- lib/subghz/blocks/math.c | 65 +++ lib/subghz/blocks/math.h | 52 +++ lib/subghz/environment.c | 33 ++ lib/subghz/environment.h | 24 + lib/subghz/protocols/base.c | 2 +- lib/subghz/protocols/base.h | 4 +- lib/subghz/protocols/bett.c | 2 +- lib/subghz/protocols/bett.h | 4 +- lib/subghz/protocols/came.c | 2 +- lib/subghz/protocols/came.h | 4 +- lib/subghz/protocols/came_atomo.c | 2 +- lib/subghz/protocols/came_atomo.h | 4 +- lib/subghz/protocols/came_twee.c | 2 +- lib/subghz/protocols/came_twee.h | 4 +- lib/subghz/protocols/chamberlain_code.c | 2 +- lib/subghz/protocols/chamberlain_code.h | 4 +- lib/subghz/protocols/clemsa.c | 2 +- lib/subghz/protocols/clemsa.h | 4 +- lib/subghz/protocols/doitrand.c | 2 +- lib/subghz/protocols/doitrand.h | 4 +- lib/subghz/protocols/faac_slh.c | 2 +- lib/subghz/protocols/faac_slh.h | 4 +- lib/subghz/protocols/gate_tx.c | 2 +- lib/subghz/protocols/gate_tx.h | 4 +- lib/subghz/protocols/holtek.c | 2 +- lib/subghz/protocols/holtek.h | 4 +- lib/subghz/protocols/honeywell_wdb.c | 2 +- lib/subghz/protocols/honeywell_wdb.h | 4 +- lib/subghz/protocols/hormann.c | 2 +- lib/subghz/protocols/hormann.h | 4 +- lib/subghz/protocols/ido.c | 2 +- lib/subghz/protocols/ido.h | 4 +- lib/subghz/protocols/intertechno_v3.c | 2 +- lib/subghz/protocols/intertechno_v3.h | 4 +- lib/subghz/protocols/keeloq.c | 4 +- lib/subghz/protocols/keeloq.h | 8 +- lib/subghz/protocols/kia.c | 2 +- lib/subghz/protocols/kia.h | 4 +- lib/subghz/protocols/linear.c | 2 +- lib/subghz/protocols/linear.h | 4 +- .../protocols/{magellen.c => magellan.c} | 262 +++++------ lib/subghz/protocols/magellan.h | 107 +++++ lib/subghz/protocols/magellen.h | 107 ----- lib/subghz/protocols/marantec.c | 2 +- lib/subghz/protocols/marantec.h | 4 +- lib/subghz/protocols/megacode.c | 2 +- lib/subghz/protocols/megacode.h | 4 +- lib/subghz/protocols/nero_radio.c | 2 +- lib/subghz/protocols/nero_radio.h | 4 +- lib/subghz/protocols/nero_sketch.c | 2 +- lib/subghz/protocols/nero_sketch.h | 4 +- lib/subghz/protocols/nice_flo.c | 2 +- lib/subghz/protocols/nice_flo.h | 4 +- lib/subghz/protocols/nice_flor_s.c | 2 +- lib/subghz/protocols/nice_flor_s.h | 4 +- lib/subghz/protocols/oregon2.c | 2 +- lib/subghz/protocols/phoenix_v2.c | 2 +- lib/subghz/protocols/phoenix_v2.h | 4 +- lib/subghz/protocols/power_smart.c | 2 +- lib/subghz/protocols/power_smart.h | 4 +- lib/subghz/protocols/princeton.c | 2 +- lib/subghz/protocols/princeton.h | 4 +- .../{registry.c => protocol_items.c} | 29 +- .../{registry.h => protocol_items.h} | 25 +- lib/subghz/protocols/raw.c | 2 +- lib/subghz/protocols/raw.h | 4 +- lib/subghz/protocols/scher_khan.c | 2 +- lib/subghz/protocols/scher_khan.h | 4 +- lib/subghz/protocols/secplus_v1.c | 2 +- lib/subghz/protocols/secplus_v1.h | 4 +- lib/subghz/protocols/secplus_v2.c | 4 +- lib/subghz/protocols/secplus_v2.h | 8 +- lib/subghz/protocols/somfy_keytis.c | 2 +- lib/subghz/protocols/somfy_keytis.h | 4 +- lib/subghz/protocols/somfy_telis.c | 2 +- lib/subghz/protocols/somfy_telis.h | 4 +- lib/subghz/protocols/star_line.c | 2 +- lib/subghz/protocols/star_line.h | 4 +- lib/subghz/receiver.c | 11 +- lib/subghz/registry.c | 30 ++ lib/subghz/registry.h | 39 ++ .../main => lib}/subghz/subghz_setting.c | 5 +- .../main => lib}/subghz/subghz_setting.h | 8 + lib/subghz/transmitter.c | 10 +- lib/subghz/types.h | 19 +- 143 files changed, 4680 insertions(+), 461 deletions(-) create mode 100644 applications/plugins/weather_station/application.fam create mode 100644 applications/plugins/weather_station/helpers/weather_station_event.h create mode 100644 applications/plugins/weather_station/helpers/weather_station_types.h create mode 100644 applications/plugins/weather_station/images/Humid_10x15.png create mode 100644 applications/plugins/weather_station/images/Therm_7x16.png create mode 100644 applications/plugins/weather_station/images/station_icon.png create mode 100644 applications/plugins/weather_station/protocols/gt_wt_03.c create mode 100644 applications/plugins/weather_station/protocols/gt_wt_03.h create mode 100644 applications/plugins/weather_station/protocols/infactory.c create mode 100644 applications/plugins/weather_station/protocols/infactory.h create mode 100644 applications/plugins/weather_station/protocols/nexus_th.c create mode 100644 applications/plugins/weather_station/protocols/nexus_th.h create mode 100644 applications/plugins/weather_station/protocols/protocol_items.c create mode 100644 applications/plugins/weather_station/protocols/protocol_items.h create mode 100644 applications/plugins/weather_station/protocols/thermopro_tx4.c create mode 100644 applications/plugins/weather_station/protocols/thermopro_tx4.h create mode 100644 applications/plugins/weather_station/protocols/ws_generic.c create mode 100644 applications/plugins/weather_station/protocols/ws_generic.h create mode 100644 applications/plugins/weather_station/scenes/weather_station_receiver.c create mode 100644 applications/plugins/weather_station/scenes/weather_station_scene.c create mode 100644 applications/plugins/weather_station/scenes/weather_station_scene.h create mode 100644 applications/plugins/weather_station/scenes/weather_station_scene_about.c create mode 100644 applications/plugins/weather_station/scenes/weather_station_scene_config.h create mode 100644 applications/plugins/weather_station/scenes/weather_station_scene_receiver_config.c create mode 100644 applications/plugins/weather_station/scenes/weather_station_scene_receiver_info.c create mode 100644 applications/plugins/weather_station/scenes/weather_station_scene_start.c create mode 100644 applications/plugins/weather_station/views/weather_station_receiver.c create mode 100644 applications/plugins/weather_station/views/weather_station_receiver.h create mode 100644 applications/plugins/weather_station/views/weather_station_receiver_info.c create mode 100644 applications/plugins/weather_station/views/weather_station_receiver_info.h create mode 100644 applications/plugins/weather_station/weather_station_10px.png create mode 100644 applications/plugins/weather_station/weather_station_app.c create mode 100644 applications/plugins/weather_station/weather_station_app_i.c create mode 100644 applications/plugins/weather_station/weather_station_app_i.h create mode 100644 applications/plugins/weather_station/weather_station_history.c create mode 100644 applications/plugins/weather_station/weather_station_history.h rename assets/resources/subghz/assets/{keeloq_mfcodes_user => keeloq_mfcodes_user.example} (80%) rename assets/resources/subghz/assets/{setting_user => setting_user.example} (91%) rename assets/unit_tests/subghz/{magellen.sub => magellan.sub} (88%) rename assets/unit_tests/subghz/{magellen_raw.sub => magellan_raw.sub} (100%) rename lib/subghz/protocols/{magellen.c => magellan.c} (59%) create mode 100644 lib/subghz/protocols/magellan.h delete mode 100644 lib/subghz/protocols/magellen.h rename lib/subghz/protocols/{registry.c => protocol_items.c} (58%) rename lib/subghz/protocols/{registry.h => protocol_items.h} (54%) create mode 100644 lib/subghz/registry.c create mode 100644 lib/subghz/registry.h rename {applications/main => lib}/subghz/subghz_setting.c (99%) rename {applications/main => lib}/subghz/subghz_setting.h (95%) diff --git a/applications/debug/unit_tests/subghz/subghz_test.c b/applications/debug/unit_tests/subghz/subghz_test.c index 210d3770f06..2dbf2eeda1e 100644 --- a/applications/debug/unit_tests/subghz/subghz_test.c +++ b/applications/debug/unit_tests/subghz/subghz_test.c @@ -5,7 +5,7 @@ #include #include #include -#include +#include #include #define TAG "SubGhz TEST" @@ -43,6 +43,8 @@ static void subghz_test_init(void) { environment_handler, CAME_ATOMO_DIR_NAME); subghz_environment_set_nice_flor_s_rainbow_table_file_name( environment_handler, NICE_FLOR_S_DIR_NAME); + subghz_environment_set_protocol_registry( + environment_handler, (void*)&subghz_protocol_registry); receiver_handler = subghz_receiver_alloc_init(environment_handler); subghz_receiver_set_filter(receiver_handler, SubGhzProtocolFlag_Decodable); @@ -413,11 +415,11 @@ MU_TEST(subghz_decoder_honeywell_wdb_test) { "Test decoder " SUBGHZ_PROTOCOL_HONEYWELL_WDB_NAME " error\r\n"); } -MU_TEST(subghz_decoder_magellen_test) { +MU_TEST(subghz_decoder_magellan_test) { mu_assert( subghz_decoder_test( - EXT_PATH("unit_tests/subghz/magellen_raw.sub"), SUBGHZ_PROTOCOL_MAGELLEN_NAME), - "Test decoder " SUBGHZ_PROTOCOL_MAGELLEN_NAME " error\r\n"); + EXT_PATH("unit_tests/subghz/magellan_raw.sub"), SUBGHZ_PROTOCOL_MAGELLAN_NAME), + "Test decoder " SUBGHZ_PROTOCOL_MAGELLAN_NAME " error\r\n"); } MU_TEST(subghz_decoder_intertechno_v3_test) { @@ -545,10 +547,10 @@ MU_TEST(subghz_encoder_honeywell_wdb_test) { "Test encoder " SUBGHZ_PROTOCOL_HONEYWELL_WDB_NAME " error\r\n"); } -MU_TEST(subghz_encoder_magellen_test) { +MU_TEST(subghz_encoder_magellan_test) { mu_assert( - subghz_encoder_test(EXT_PATH("unit_tests/subghz/magellen.sub")), - "Test encoder " SUBGHZ_PROTOCOL_MAGELLEN_NAME " error\r\n"); + subghz_encoder_test(EXT_PATH("unit_tests/subghz/magellan.sub")), + "Test encoder " SUBGHZ_PROTOCOL_MAGELLAN_NAME " error\r\n"); } MU_TEST(subghz_encoder_intertechno_v3_test) { @@ -600,7 +602,7 @@ MU_TEST_SUITE(subghz) { MU_RUN_TEST(subghz_decoder_doitrand_test); MU_RUN_TEST(subghz_decoder_phoenix_v2_test); MU_RUN_TEST(subghz_decoder_honeywell_wdb_test); - MU_RUN_TEST(subghz_decoder_magellen_test); + MU_RUN_TEST(subghz_decoder_magellan_test); MU_RUN_TEST(subghz_decoder_intertechno_v3_test); MU_RUN_TEST(subghz_decoder_clemsa_test); MU_RUN_TEST(subghz_decoder_oregon2_test); @@ -622,7 +624,7 @@ MU_TEST_SUITE(subghz) { MU_RUN_TEST(subghz_encoder_doitrand_test); MU_RUN_TEST(subghz_encoder_phoenix_v2_test); MU_RUN_TEST(subghz_encoder_honeywell_wdb_test); - MU_RUN_TEST(subghz_encoder_magellen_test); + MU_RUN_TEST(subghz_encoder_magellan_test); MU_RUN_TEST(subghz_encoder_intertechno_v3_test); MU_RUN_TEST(subghz_encoder_clemsa_test); diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_raw_info.c b/applications/main/lfrfid/scenes/lfrfid_scene_raw_info.c index e5193521df9..f403c1f38ed 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_raw_info.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_raw_info.c @@ -34,7 +34,7 @@ void lfrfid_scene_raw_info_on_enter(void* context) { } view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewWidget); - //string_clear(tmp_string); + //furi_string_free(tmp_string); } bool lfrfid_scene_raw_info_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/main/subghz/helpers/subghz_types.h b/applications/main/subghz/helpers/subghz_types.h index 023fb4b6f1c..bd6451f248d 100644 --- a/applications/main/subghz/helpers/subghz_types.h +++ b/applications/main/subghz/helpers/subghz_types.h @@ -69,12 +69,3 @@ typedef enum { SubGhzViewIdTestCarrier, SubGhzViewIdTestPacket, } SubGhzViewId; - -struct SubGhzPresetDefinition { - FuriString* name; - uint32_t frequency; - uint8_t* data; - size_t data_size; -}; - -typedef struct SubGhzPresetDefinition SubGhzPresetDefinition; diff --git a/applications/main/subghz/scenes/subghz_scene_receiver_info.c b/applications/main/subghz/scenes/subghz_scene_receiver_info.c index 2e833edd193..03a8ebbcb6e 100644 --- a/applications/main/subghz/scenes/subghz_scene_receiver_info.c +++ b/applications/main/subghz/scenes/subghz_scene_receiver_info.c @@ -28,8 +28,8 @@ static bool subghz_scene_receiver_info_update_parser(void* context) { subghz->txrx->decoder_result, subghz_history_get_raw_data(subghz->txrx->history, subghz->txrx->idx_menu_chosen)); - SubGhzPresetDefinition* preset = - subghz_history_get_preset_def(subghz->txrx->history, subghz->txrx->idx_menu_chosen); + SubGhzRadioPreset* preset = + subghz_history_get_radio_preset(subghz->txrx->history, subghz->txrx->idx_menu_chosen); subghz_preset_init( subghz, furi_string_get_cstr(preset->name), diff --git a/applications/main/subghz/scenes/subghz_scene_set_type.c b/applications/main/subghz/scenes/subghz_scene_set_type.c index 1e7a7474bcd..44fe2fc76d0 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_type.c +++ b/applications/main/subghz/scenes/subghz_scene_set_type.c @@ -6,7 +6,7 @@ #include #include #include -#include +#include #define TAG "SubGhzSetType" diff --git a/applications/main/subghz/subghz.c b/applications/main/subghz/subghz.c index 71c6bc2cd42..ba70f36d1b2 100644 --- a/applications/main/subghz/subghz.c +++ b/applications/main/subghz/subghz.c @@ -3,6 +3,7 @@ #include "subghz/types.h" #include "subghz_i.h" #include +#include bool subghz_custom_event_callback(void* context, uint32_t event) { furi_assert(context); @@ -169,7 +170,7 @@ SubGhz* subghz_alloc() { //init Worker & Protocol & History & KeyBoard subghz->lock = SubGhzLockOff; subghz->txrx = malloc(sizeof(SubGhzTxRx)); - subghz->txrx->preset = malloc(sizeof(SubGhzPresetDefinition)); + subghz->txrx->preset = malloc(sizeof(SubGhzRadioPreset)); subghz->txrx->preset->name = furi_string_alloc(); subghz_preset_init( subghz, "AM650", subghz_setting_get_default_frequency(subghz->setting), NULL, 0); @@ -186,6 +187,8 @@ SubGhz* subghz_alloc() { subghz->txrx->environment, EXT_PATH("subghz/assets/came_atomo")); subghz_environment_set_nice_flor_s_rainbow_table_file_name( subghz->txrx->environment, EXT_PATH("subghz/assets/nice_flor_s")); + subghz_environment_set_protocol_registry( + subghz->txrx->environment, (void*)&subghz_protocol_registry); subghz->txrx->receiver = subghz_receiver_alloc_init(subghz->txrx->environment); subghz_receiver_set_filter(subghz->txrx->receiver, SubGhzProtocolFlag_Decodable); diff --git a/applications/main/subghz/subghz_cli.c b/applications/main/subghz/subghz_cli.c index a1474885eb2..ad5d8afb0b0 100644 --- a/applications/main/subghz/subghz_cli.c +++ b/applications/main/subghz/subghz_cli.c @@ -9,6 +9,7 @@ #include #include #include +#include #include "helpers/subghz_chat.h" @@ -164,6 +165,7 @@ void subghz_cli_command_tx(Cli* cli, FuriString* args, void* context) { stream_write_cstring(stream, furi_string_get_cstr(flipper_format_string)); SubGhzEnvironment* environment = subghz_environment_alloc(); + subghz_environment_set_protocol_registry(environment, (void*)&subghz_protocol_registry); SubGhzTransmitter* transmitter = subghz_transmitter_alloc_init(environment, "Princeton"); subghz_transmitter_deserialize(transmitter, flipper_format); @@ -257,6 +259,7 @@ void subghz_cli_command_rx(Cli* cli, FuriString* args, void* context) { environment, EXT_PATH("subghz/assets/came_atomo")); subghz_environment_set_nice_flor_s_rainbow_table_file_name( environment, EXT_PATH("subghz/assets/nice_flor_s")); + subghz_environment_set_protocol_registry(environment, (void*)&subghz_protocol_registry); SubGhzReceiver* receiver = subghz_receiver_alloc_init(environment); subghz_receiver_set_filter(receiver, SubGhzProtocolFlag_Decodable); @@ -376,6 +379,7 @@ void subghz_cli_command_decode_raw(Cli* cli, FuriString* args, void* context) { environment, EXT_PATH("subghz/assets/came_atomo")); subghz_environment_set_nice_flor_s_rainbow_table_file_name( environment, EXT_PATH("subghz/assets/nice_flor_s")); + subghz_environment_set_protocol_registry(environment, (void*)&subghz_protocol_registry); SubGhzReceiver* receiver = subghz_receiver_alloc_init(environment); subghz_receiver_set_filter(receiver, SubGhzProtocolFlag_Decodable); diff --git a/applications/main/subghz/subghz_history.c b/applications/main/subghz/subghz_history.c index 0f67b4fc9f4..beecc6e8b26 100644 --- a/applications/main/subghz/subghz_history.c +++ b/applications/main/subghz/subghz_history.c @@ -11,7 +11,7 @@ typedef struct { FuriString* item_str; FlipperFormat* flipper_string; uint8_t type; - SubGhzPresetDefinition* preset; + SubGhzRadioPreset* preset; } SubGhzHistoryItem; ARRAY_DEF(SubGhzHistoryItemArray, SubGhzHistoryItem, M_POD_OPLIST) @@ -60,7 +60,7 @@ uint32_t subghz_history_get_frequency(SubGhzHistory* instance, uint16_t idx) { return item->preset->frequency; } -SubGhzPresetDefinition* subghz_history_get_preset_def(SubGhzHistory* instance, uint16_t idx) { +SubGhzRadioPreset* subghz_history_get_radio_preset(SubGhzHistory* instance, uint16_t idx) { furi_assert(instance); SubGhzHistoryItem* item = SubGhzHistoryItemArray_get(instance->history->data, idx); return item->preset; @@ -138,7 +138,7 @@ void subghz_history_get_text_item_menu(SubGhzHistory* instance, FuriString* outp bool subghz_history_add_to_history( SubGhzHistory* instance, void* context, - SubGhzPresetDefinition* preset) { + SubGhzRadioPreset* preset) { furi_assert(instance); furi_assert(context); @@ -158,7 +158,7 @@ bool subghz_history_add_to_history( FuriString* text; text = furi_string_alloc(); SubGhzHistoryItem* item = SubGhzHistoryItemArray_push_raw(instance->history->data); - item->preset = malloc(sizeof(SubGhzPresetDefinition)); + item->preset = malloc(sizeof(SubGhzRadioPreset)); item->type = decoder_base->protocol->type; item->preset->frequency = preset->frequency; item->preset->name = furi_string_alloc(); diff --git a/applications/main/subghz/subghz_history.h b/applications/main/subghz/subghz_history.h index 7bff3df5ef0..5b2b57d3338 100644 --- a/applications/main/subghz/subghz_history.h +++ b/applications/main/subghz/subghz_history.h @@ -5,7 +5,7 @@ #include #include #include -#include "helpers/subghz_types.h" +#include typedef struct SubGhzHistory SubGhzHistory; @@ -35,7 +35,7 @@ void subghz_history_reset(SubGhzHistory* instance); */ uint32_t subghz_history_get_frequency(SubGhzHistory* instance, uint16_t idx); -SubGhzPresetDefinition* subghz_history_get_preset_def(SubGhzHistory* instance, uint16_t idx); +SubGhzRadioPreset* subghz_history_get_radio_preset(SubGhzHistory* instance, uint16_t idx); /** Get preset to history[idx] * @@ -88,13 +88,13 @@ bool subghz_history_get_text_space_left(SubGhzHistory* instance, FuriString* out * * @param instance - SubGhzHistory instance * @param context - SubGhzProtocolCommon context - * @param preset - SubGhzPresetDefinition preset + * @param preset - SubGhzRadioPreset preset * @return bool; */ bool subghz_history_add_to_history( SubGhzHistory* instance, void* context, - SubGhzPresetDefinition* preset); + SubGhzRadioPreset* preset); /** Get SubGhzProtocolCommonLoad to load into the protocol decoder bin data * diff --git a/applications/main/subghz/subghz_i.h b/applications/main/subghz/subghz_i.h index 58d307170fa..284f7ccf532 100644 --- a/applications/main/subghz/subghz_i.h +++ b/applications/main/subghz/subghz_i.h @@ -1,6 +1,7 @@ #pragma once #include "helpers/subghz_types.h" +#include #include "subghz.h" #include "views/receiver.h" #include "views/transmitter.h" @@ -11,8 +12,6 @@ #include "views/subghz_test_carrier.h" #include "views/subghz_test_packet.h" -// #include -// #include #include #include #include @@ -24,14 +23,12 @@ #include #include - #include - +#include #include #include #include "subghz_history.h" -#include "subghz_setting.h" #include #include @@ -49,7 +46,7 @@ struct SubGhzTxRx { SubGhzProtocolDecoderBase* decoder_result; FlipperFormat* fff_data; - SubGhzPresetDefinition* preset; + SubGhzRadioPreset* preset; SubGhzHistory* history; uint16_t idx_menu_chosen; SubGhzTxRxState txrx_state; diff --git a/applications/main/subghz/views/receiver.c b/applications/main/subghz/views/receiver.c index cdebc632037..7aa69922298 100644 --- a/applications/main/subghz/views/receiver.c +++ b/applications/main/subghz/views/receiver.c @@ -192,7 +192,7 @@ void subghz_view_receiver_draw(Canvas* canvas, SubGhzViewReceiverModel* model) { } else { canvas_set_color(canvas, ColorBlack); } - canvas_draw_icon(canvas, 1, 2 + i * FRAME_HEIGHT, ReceiverItemIcons[item_menu->type]); + canvas_draw_icon(canvas, 4, 2 + i * FRAME_HEIGHT, ReceiverItemIcons[item_menu->type]); canvas_draw_str(canvas, 15, 9 + i * FRAME_HEIGHT, furi_string_get_cstr(str_buff)); furi_string_reset(str_buff); } diff --git a/applications/plugins/weather_station/application.fam b/applications/plugins/weather_station/application.fam new file mode 100644 index 00000000000..1074fc040f2 --- /dev/null +++ b/applications/plugins/weather_station/application.fam @@ -0,0 +1,13 @@ +App( + appid="weather_station", + name="Weather Station", + apptype=FlipperAppType.PLUGIN, + entry_point="weather_station_app", + cdefines=["APP_WEATHER_STATION"], + requires=["gui"], + stack_size=4 * 1024, + order=50, + fap_icon="weather_station_10px.png", + fap_category="Tools", + fap_icon_assets="images", +) diff --git a/applications/plugins/weather_station/helpers/weather_station_event.h b/applications/plugins/weather_station/helpers/weather_station_event.h new file mode 100644 index 00000000000..b0486183d9b --- /dev/null +++ b/applications/plugins/weather_station/helpers/weather_station_event.h @@ -0,0 +1,14 @@ +#pragma once + +typedef enum { + //WSCustomEvent + WSCustomEventStartId = 100, + + WSCustomEventSceneSettingLock, + + WSCustomEventViewReceiverOK, + WSCustomEventViewReceiverConfig, + WSCustomEventViewReceiverBack, + WSCustomEventViewReceiverOffDisplay, + WSCustomEventViewReceiverUnlock, +} WSCustomEvent; diff --git a/applications/plugins/weather_station/helpers/weather_station_types.h b/applications/plugins/weather_station/helpers/weather_station_types.h new file mode 100644 index 00000000000..a6905e828d5 --- /dev/null +++ b/applications/plugins/weather_station/helpers/weather_station_types.h @@ -0,0 +1,49 @@ +#pragma once + +#include +#include + +#define WS_VERSION_APP "0.1" +#define WS_DEVELOPED "SkorP" +#define WS_GITHUB "https://github.com/flipperdevices/flipperzero-firmware" + +#define WS_KEY_FILE_VERSION 1 +#define WS_KEY_FILE_TYPE "Flipper Weather Station Key File" + +/** WSRxKeyState state */ +typedef enum { + WSRxKeyStateIDLE, + WSRxKeyStateBack, + WSRxKeyStateStart, + WSRxKeyStateAddKey, +} WSRxKeyState; + +/** WSHopperState state */ +typedef enum { + WSHopperStateOFF, + WSHopperStateRunnig, + WSHopperStatePause, + WSHopperStateRSSITimeOut, +} WSHopperState; + +/** WSLock */ +typedef enum { + WSLockOff, + WSLockOn, +} WSLock; + +typedef enum { + WeatherStationViewVariableItemList, + WeatherStationViewSubmenu, + WeatherStationViewReceiver, + WeatherStationViewReceiverInfo, + WeatherStationViewWidget, +} WeatherStationView; + +/** WeatherStationTxRx state */ +typedef enum { + WSTxRxStateIDLE, + WSTxRxStateRx, + WSTxRxStateTx, + WSTxRxStateSleep, +} WSTxRxState; diff --git a/applications/plugins/weather_station/images/Humid_10x15.png b/applications/plugins/weather_station/images/Humid_10x15.png new file mode 100644 index 0000000000000000000000000000000000000000..34b074e5f75150853ec976d7521e1663f0f30f1c GIT binary patch literal 3624 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 literal 0 HcmV?d00001 diff --git a/applications/plugins/weather_station/images/Therm_7x16.png b/applications/plugins/weather_station/images/Therm_7x16.png new file mode 100644 index 0000000000000000000000000000000000000000..7c55500b7e7ea9112c92ba966643e3684887eb92 GIT binary patch literal 3611 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 literal 0 HcmV?d00001 diff --git a/applications/plugins/weather_station/images/station_icon.png b/applications/plugins/weather_station/images/station_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..b839eeb7aebfcf8aee9b2ba9570a3d9cf1d8af12 GIT binary patch literal 3607 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 PjRq`CY_X+i_pAQ_NTe=6 literal 0 HcmV?d00001 diff --git a/applications/plugins/weather_station/protocols/gt_wt_03.c b/applications/plugins/weather_station/protocols/gt_wt_03.c new file mode 100644 index 00000000000..1492374c9d3 --- /dev/null +++ b/applications/plugins/weather_station/protocols/gt_wt_03.c @@ -0,0 +1,341 @@ +#include "gt_wt_03.h" + +#define TAG "WSProtocolGT_WT03" + +/* + * Help + * https://github.com/merbanan/rtl_433/blob/5f0ff6db624270a4598958ab9dd79bb385ced3ef/src/devices/gt_wt_03.c + * + * + * Globaltronics GT-WT-03 sensor on 433.92MHz. + * The 01-set sensor has 60 ms packet gap with 10 repeats. + * The 02-set sensor has no packet gap with 23 repeats. + * Example: + * {41} 17 cf be fa 6a 80 [ S1 C1 26,1 C 78.9 F 48% Bat-Good Manual-Yes ] + * {41} 17 cf be fa 6a 80 [ S1 C1 26,1 C 78.9 F 48% Bat-Good Manual-Yes Batt-Changed ] + * {41} 17 cf fe fa ea 80 [ S1 C1 26,1 C 78.9 F 48% Bat-Good Manual-No Batt-Changed ] + * {41} 01 cf 6f 11 b2 80 [ S2 C2 23,8 C 74.8 F 48% Bat-LOW Manual-No ] + * {41} 01 c8 d0 2b 76 80 [ S2 C3 -4,4 C 24.1 F 55% Bat-Good Manual-No Batt-Changed ] + * Format string: + * ID:8h HUM:8d B:b M:b C:2d TEMP:12d CHK:8h 1x + * Data layout: + * TYP IIIIIIII HHHHHHHH BMCCTTTT TTTTTTTT XXXXXXXX + * - I: Random Device Code: changes with battery reset + * - H: Humidity: 8 Bit 00-99, Display LL=10%, Display HH=110% (Range 20-95%) + * - B: Battery: 0=OK 1=LOW + * - M: Manual Send Button Pressed: 0=not pressed, 1=pressed + * - C: Channel: 00=CH1, 01=CH2, 10=CH3 + * - T: Temperature: 12 Bit 2's complement, scaled by 10, range-50.0 C (-50.1 shown as Lo) to +70.0 C (+70.1 C is shown as Hi) + * - X: Checksum, xor shifting key per byte + * Humidity: + * - the working range is 20-95 % + * - if "LL" in display view it sends 10 % + * - if "HH" in display view it sends 110% + * Checksum: + * Per byte xor the key for each 1-bit, shift per bit. Key list per bit, starting at MSB: + * - 0x00 [07] + * - 0x80 [06] + * - 0x40 [05] + * - 0x20 [04] + * - 0x10 [03] + * - 0x88 [02] + * - 0xc4 [01] + * - 0x62 [00] + * Note: this can also be seen as lower byte of a Galois/Fibonacci LFSR-16, gen 0x00, init 0x3100 (or 0x62 if reversed) resetting at every byte. + * Battery voltages: + * - U=<2,65V +- ~5% Battery indicator + * - U=>2.10C +- 5% plausible readings + * - U=2,00V +- ~5% Temperature offset -5°C Humidity offset unknown + * - U=<1,95V +- ~5% does not initialize anymore + * - U=1,90V +- 5% temperature offset -15°C + * - U=1,80V +- 5% Display is showing refresh pattern + * - U=1.75V +- ~5% TX causes cut out + * + */ + +static const SubGhzBlockConst ws_protocol_gt_wt_03_const = { + .te_short = 285, + .te_long = 570, + .te_delta = 120, + .min_count_bit_for_found = 41, +}; + +struct WSProtocolDecoderGT_WT03 { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + WSBlockGeneric generic; + + uint16_t header_count; +}; + +struct WSProtocolEncoderGT_WT03 { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + WSBlockGeneric generic; +}; + +typedef enum { + GT_WT03DecoderStepReset = 0, + GT_WT03DecoderStepCheckPreambule, + GT_WT03DecoderStepSaveDuration, + GT_WT03DecoderStepCheckDuration, +} GT_WT03DecoderStep; + +const SubGhzProtocolDecoder ws_protocol_gt_wt_03_decoder = { + .alloc = ws_protocol_decoder_gt_wt_03_alloc, + .free = ws_protocol_decoder_gt_wt_03_free, + + .feed = ws_protocol_decoder_gt_wt_03_feed, + .reset = ws_protocol_decoder_gt_wt_03_reset, + + .get_hash_data = ws_protocol_decoder_gt_wt_03_get_hash_data, + .serialize = ws_protocol_decoder_gt_wt_03_serialize, + .deserialize = ws_protocol_decoder_gt_wt_03_deserialize, + .get_string = ws_protocol_decoder_gt_wt_03_get_string, +}; + +const SubGhzProtocolEncoder ws_protocol_gt_wt_03_encoder = { + .alloc = NULL, + .free = NULL, + + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol ws_protocol_gt_wt_03 = { + .name = WS_PROTOCOL_GT_WT_03_NAME, + .type = SubGhzProtocolWeatherStation, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable, + + .decoder = &ws_protocol_gt_wt_03_decoder, + .encoder = &ws_protocol_gt_wt_03_encoder, +}; + +void* ws_protocol_decoder_gt_wt_03_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + WSProtocolDecoderGT_WT03* instance = malloc(sizeof(WSProtocolDecoderGT_WT03)); + instance->base.protocol = &ws_protocol_gt_wt_03; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void ws_protocol_decoder_gt_wt_03_free(void* context) { + furi_assert(context); + WSProtocolDecoderGT_WT03* instance = context; + free(instance); +} + +void ws_protocol_decoder_gt_wt_03_reset(void* context) { + furi_assert(context); + WSProtocolDecoderGT_WT03* instance = context; + instance->decoder.parser_step = GT_WT03DecoderStepReset; +} + +static bool ws_protocol_gt_wt_03_check_crc(WSProtocolDecoderGT_WT03* instance) { + uint8_t msg[] = { + instance->decoder.decode_data >> 33, + instance->decoder.decode_data >> 25, + instance->decoder.decode_data >> 17, + instance->decoder.decode_data >> 9}; + + uint8_t sum = 0; + for(unsigned k = 0; k < sizeof(msg); ++k) { + uint8_t data = msg[k]; + uint16_t key = 0x3100; + for(int i = 7; i >= 0; --i) { + // XOR key into sum if data bit is set + if((data >> i) & 1) sum ^= key & 0xff; + // roll the key right + key = (key >> 1); + } + } + return ((sum ^ (uint8_t)((instance->decoder.decode_data >> 1) & 0xFF)) == 0x2D); +} + +/** + * Analysis of received data + * @param instance Pointer to a WSBlockGeneric* instance + */ +static void ws_protocol_gt_wt_03_remote_controller(WSBlockGeneric* instance) { + instance->id = instance->data >> 33; + instance->humidity = (instance->data >> 25) & 0xFF; + + if(instance->humidity <= 10) { // actually the sensors sends 10 below working range of 20% + instance->humidity = 0; + } else if(instance->humidity > 95) { // actually the sensors sends 110 above working range of 90% + instance->humidity = 100; + } + + instance->battery_low = (instance->data >> 24) & 1; + instance->btn = (instance->data >> 23) & 1; + instance->channel = ((instance->data >> 21) & 0x03) + 1; + + if(!((instance->data >> 20) & 1)) { + instance->temp = (float)((instance->data >> 9) & 0x07FF) / 10.0f; + } else { + instance->temp = (float)((~(instance->data >> 9) & 0x07FF) + 1) / -10.0f; + } +} + +void ws_protocol_decoder_gt_wt_03_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + WSProtocolDecoderGT_WT03* instance = context; + + switch(instance->decoder.parser_step) { + case GT_WT03DecoderStepReset: + if((level) && (DURATION_DIFF(duration, ws_protocol_gt_wt_03_const.te_short * 3) < + ws_protocol_gt_wt_03_const.te_delta * 2)) { + instance->decoder.parser_step = GT_WT03DecoderStepCheckPreambule; + instance->decoder.te_last = duration; + instance->header_count = 0; + } + break; + + case GT_WT03DecoderStepCheckPreambule: + if(level) { + instance->decoder.te_last = duration; + } else { + if((DURATION_DIFF(instance->decoder.te_last, ws_protocol_gt_wt_03_const.te_short * 3) < + ws_protocol_gt_wt_03_const.te_delta * 2) && + (DURATION_DIFF(duration, ws_protocol_gt_wt_03_const.te_short * 3) < + ws_protocol_gt_wt_03_const.te_delta * 2)) { + //Found preambule + instance->header_count++; + } else if(instance->header_count == 4) { + if((DURATION_DIFF(instance->decoder.te_last, ws_protocol_gt_wt_03_const.te_short) < + ws_protocol_gt_wt_03_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_gt_wt_03_const.te_long) < + ws_protocol_gt_wt_03_const.te_delta)) { + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = GT_WT03DecoderStepSaveDuration; + } else if( + (DURATION_DIFF(instance->decoder.te_last, ws_protocol_gt_wt_03_const.te_long) < + ws_protocol_gt_wt_03_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_gt_wt_03_const.te_short) < + ws_protocol_gt_wt_03_const.te_delta)) { + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = GT_WT03DecoderStepSaveDuration; + } else { + instance->decoder.parser_step = GT_WT03DecoderStepReset; + } + } else { + instance->decoder.parser_step = GT_WT03DecoderStepReset; + } + } + break; + + case GT_WT03DecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = GT_WT03DecoderStepCheckDuration; + } else { + instance->decoder.parser_step = GT_WT03DecoderStepReset; + } + break; + + case GT_WT03DecoderStepCheckDuration: + if(!level) { + if(((DURATION_DIFF(instance->decoder.te_last, ws_protocol_gt_wt_03_const.te_short * 3) < + ws_protocol_gt_wt_03_const.te_delta * 2) && + (DURATION_DIFF(duration, ws_protocol_gt_wt_03_const.te_short * 3) < + ws_protocol_gt_wt_03_const.te_delta * 2))) { + if((instance->decoder.decode_count_bit == + ws_protocol_gt_wt_03_const.min_count_bit_for_found) && + ws_protocol_gt_wt_03_check_crc(instance)) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + ws_protocol_gt_wt_03_remote_controller(&instance->generic); + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->header_count = 1; + instance->decoder.parser_step = GT_WT03DecoderStepCheckPreambule; + break; + } else if( + (DURATION_DIFF(instance->decoder.te_last, ws_protocol_gt_wt_03_const.te_short) < + ws_protocol_gt_wt_03_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_gt_wt_03_const.te_long) < + ws_protocol_gt_wt_03_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = GT_WT03DecoderStepSaveDuration; + } else if( + (DURATION_DIFF(instance->decoder.te_last, ws_protocol_gt_wt_03_const.te_long) < + ws_protocol_gt_wt_03_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_gt_wt_03_const.te_short) < + ws_protocol_gt_wt_03_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = GT_WT03DecoderStepSaveDuration; + } else { + instance->decoder.parser_step = GT_WT03DecoderStepReset; + } + } else { + instance->decoder.parser_step = GT_WT03DecoderStepReset; + } + break; + } +} + +uint8_t ws_protocol_decoder_gt_wt_03_get_hash_data(void* context) { + furi_assert(context); + WSProtocolDecoderGT_WT03* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool ws_protocol_decoder_gt_wt_03_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + WSProtocolDecoderGT_WT03* instance = context; + return ws_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +bool ws_protocol_decoder_gt_wt_03_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + WSProtocolDecoderGT_WT03* instance = context; + bool ret = false; + do { + if(!ws_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if(instance->generic.data_count_bit != + ws_protocol_gt_wt_03_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + ret = true; + } while(false); + return ret; +} + +void ws_protocol_decoder_gt_wt_03_get_string(void* context, FuriString* output) { + furi_assert(context); + WSProtocolDecoderGT_WT03* instance = context; + furi_string_printf( + output, + "%s %dbit\r\n" + "Key:0x%lX%08lX\r\n" + "Sn:0x%lX Ch:%d Bat:%d\r\n" + "Temp:%d.%d C Hum:%d%%", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data >> 32), + (uint32_t)(instance->generic.data), + instance->generic.id, + instance->generic.channel, + instance->generic.battery_low, + (int16_t)instance->generic.temp, + abs(((int16_t)(instance->generic.temp * 10) - (((int16_t)instance->generic.temp) * 10))), + instance->generic.humidity); +} diff --git a/applications/plugins/weather_station/protocols/gt_wt_03.h b/applications/plugins/weather_station/protocols/gt_wt_03.h new file mode 100644 index 00000000000..fd9536e342c --- /dev/null +++ b/applications/plugins/weather_station/protocols/gt_wt_03.h @@ -0,0 +1,79 @@ +#pragma once + +#include + +#include +#include +#include +#include "ws_generic.h" +#include + +#define WS_PROTOCOL_GT_WT_03_NAME "GT-WT03" + +typedef struct WSProtocolDecoderGT_WT03 WSProtocolDecoderGT_WT03; +typedef struct WSProtocolEncoderGT_WT03 WSProtocolEncoderGT_WT03; + +extern const SubGhzProtocolDecoder ws_protocol_gt_wt_03_decoder; +extern const SubGhzProtocolEncoder ws_protocol_gt_wt_03_encoder; +extern const SubGhzProtocol ws_protocol_gt_wt_03; + +/** + * Allocate WSProtocolDecoderGT_WT03. + * @param environment Pointer to a SubGhzEnvironment instance + * @return WSProtocolDecoderGT_WT03* pointer to a WSProtocolDecoderGT_WT03 instance + */ +void* ws_protocol_decoder_gt_wt_03_alloc(SubGhzEnvironment* environment); + +/** + * Free WSProtocolDecoderGT_WT03. + * @param context Pointer to a WSProtocolDecoderGT_WT03 instance + */ +void ws_protocol_decoder_gt_wt_03_free(void* context); + +/** + * Reset decoder WSProtocolDecoderGT_WT03. + * @param context Pointer to a WSProtocolDecoderGT_WT03 instance + */ +void ws_protocol_decoder_gt_wt_03_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a WSProtocolDecoderGT_WT03 instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void ws_protocol_decoder_gt_wt_03_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a WSProtocolDecoderGT_WT03 instance + * @return hash Hash sum + */ +uint8_t ws_protocol_decoder_gt_wt_03_get_hash_data(void* context); + +/** + * Serialize data WSProtocolDecoderGT_WT03. + * @param context Pointer to a WSProtocolDecoderGT_WT03 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool ws_protocol_decoder_gt_wt_03_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data WSProtocolDecoderGT_WT03. + * @param context Pointer to a WSProtocolDecoderGT_WT03 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool ws_protocol_decoder_gt_wt_03_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a WSProtocolDecoderGT_WT03 instance + * @param output Resulting text + */ +void ws_protocol_decoder_gt_wt_03_get_string(void* context, FuriString* output); diff --git a/applications/plugins/weather_station/protocols/infactory.c b/applications/plugins/weather_station/protocols/infactory.c new file mode 100644 index 00000000000..525b55575a1 --- /dev/null +++ b/applications/plugins/weather_station/protocols/infactory.c @@ -0,0 +1,296 @@ +#include "infactory.h" + +#define TAG "WSProtocolInfactory" + +/* + * Help + * https://github.com/merbanan/rtl_433/blob/master/src/devices/infactory.c + * + * Analysis using Genuino (see http://gitlab.com/hp-uno, e.g. uno_log_433): + * Observed On-Off-Key (OOK) data pattern: + * preamble syncPrefix data...(40 bit) syncPostfix + * HHLL HHLL HHLL HHLL HLLLLLLLLLLLLLLLL (HLLLL HLLLLLLLL HLLLL HLLLLLLLL ....) HLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL + * Breakdown: + * - four preamble pairs '1'/'0' each with a length of ca. 1000us + * - syncPre, syncPost, data0, data1 have a '1' start pulse of ca. 500us + * - syncPre pulse before dataPtr has a '0' pulse length of ca. 8000us + * - data0 (0-bits) have then a '0' pulse length of ca. 2000us + * - data1 (1-bits) have then a '0' pulse length of ca. 4000us + * - syncPost after dataPtr has a '0' pulse length of ca. 16000us + * This analysis is the reason for the new r_device definitions below. + * NB: pulse_slicer_ppm does not use .gap_limit if .tolerance is set. + * + * Outdoor sensor, transmits temperature and humidity data + * - inFactory NC-3982-913/NX-5817-902, Pearl (for FWS-686 station) + * - nor-tec 73383 (weather station + sensor), Schou Company AS, Denmark + * - DAY 73365 (weather station + sensor), Schou Company AS, Denmark + * Known brand names: inFactory, nor-tec, GreenBlue, DAY. Manufacturer in China. + * Transmissions includes an id. Every 60 seconds the sensor transmits 6 packets: + * 0000 1111 | 0011 0000 | 0101 1100 | 1110 0111 | 0110 0001 + * iiii iiii | cccc ub?? | tttt tttt | tttt hhhh | hhhh ??nn + * - i: identification; changes on battery switch + * - c: CRC-4; CCITT checksum, see below for computation specifics + * - u: unknown; (sometimes set at power-on, but not always) + * - b: battery low; flag to indicate low battery voltage + * - h: Humidity; BCD-encoded, each nibble is one digit, 'A0' means 100%rH + * - t: Temperature; in °F as binary number with one decimal place + 90 °F offset + * - n: Channel; Channel number 1 - 3 + * + */ + +static const SubGhzBlockConst ws_protocol_infactory_const = { + .te_short = 500, + .te_long = 2000, + .te_delta = 150, + .min_count_bit_for_found = 40, +}; + +struct WSProtocolDecoderInfactory { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + WSBlockGeneric generic; + + uint16_t header_count; +}; + +struct WSProtocolEncoderInfactory { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + WSBlockGeneric generic; +}; + +typedef enum { + InfactoryDecoderStepReset = 0, + InfactoryDecoderStepCheckPreambule, + InfactoryDecoderStepSaveDuration, + InfactoryDecoderStepCheckDuration, +} InfactoryDecoderStep; + +const SubGhzProtocolDecoder ws_protocol_infactory_decoder = { + .alloc = ws_protocol_decoder_infactory_alloc, + .free = ws_protocol_decoder_infactory_free, + + .feed = ws_protocol_decoder_infactory_feed, + .reset = ws_protocol_decoder_infactory_reset, + + .get_hash_data = ws_protocol_decoder_infactory_get_hash_data, + .serialize = ws_protocol_decoder_infactory_serialize, + .deserialize = ws_protocol_decoder_infactory_deserialize, + .get_string = ws_protocol_decoder_infactory_get_string, +}; + +const SubGhzProtocolEncoder ws_protocol_infactory_encoder = { + .alloc = NULL, + .free = NULL, + + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol ws_protocol_infactory = { + .name = WS_PROTOCOL_INFACTORY_NAME, + .type = SubGhzProtocolWeatherStation, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable, + + .decoder = &ws_protocol_infactory_decoder, + .encoder = &ws_protocol_infactory_encoder, +}; + +void* ws_protocol_decoder_infactory_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + WSProtocolDecoderInfactory* instance = malloc(sizeof(WSProtocolDecoderInfactory)); + instance->base.protocol = &ws_protocol_infactory; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void ws_protocol_decoder_infactory_free(void* context) { + furi_assert(context); + WSProtocolDecoderInfactory* instance = context; + free(instance); +} + +void ws_protocol_decoder_infactory_reset(void* context) { + furi_assert(context); + WSProtocolDecoderInfactory* instance = context; + instance->decoder.parser_step = InfactoryDecoderStepReset; +} + +static bool ws_protocol_infactory_check_crc(WSProtocolDecoderInfactory* instance) { + uint8_t msg[] = { + instance->decoder.decode_data >> 32, + (((instance->decoder.decode_data >> 24) & 0x0F) | (instance->decoder.decode_data & 0x0F) + << 4), + instance->decoder.decode_data >> 16, + instance->decoder.decode_data >> 8, + instance->decoder.decode_data}; + + uint8_t crc = + subghz_protocol_blocks_crc4(msg, 4, 0x13, 0); // Koopmann 0x9, CCITT-4; FP-4; ITU-T G.704 + crc ^= msg[4] >> 4; // last nibble is only XORed + return (crc == ((instance->decoder.decode_data >> 28) & 0x0F)); +} + +/** + * Analysis of received data + * @param instance Pointer to a WSBlockGeneric* instance + */ +static void ws_protocol_infactory_remote_controller(WSBlockGeneric* instance) { + instance->id = instance->data >> 32; + instance->battery_low = (instance->data >> 26) & 1; + instance->temp = ws_block_generic_fahrenheit_to_celsius( + ((float)((instance->data >> 12) & 0x0FFF) - 900.0f) / 10.0f); + instance->humidity = + (((instance->data >> 8) & 0x0F) * 10) + ((instance->data >> 4) & 0x0F); // BCD, 'A0'=100%rH + instance->channel = instance->data & 0x03; +} + +void ws_protocol_decoder_infactory_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + WSProtocolDecoderInfactory* instance = context; + + switch(instance->decoder.parser_step) { + case InfactoryDecoderStepReset: + if((level) && (DURATION_DIFF(duration, ws_protocol_infactory_const.te_short * 2) < + ws_protocol_infactory_const.te_delta * 2)) { + instance->decoder.parser_step = InfactoryDecoderStepCheckPreambule; + instance->decoder.te_last = duration; + instance->header_count = 0; + } + break; + + case InfactoryDecoderStepCheckPreambule: + if(level) { + instance->decoder.te_last = duration; + } else { + if((DURATION_DIFF(instance->decoder.te_last, ws_protocol_infactory_const.te_short * 2) < + ws_protocol_infactory_const.te_delta * 2) && + (DURATION_DIFF(duration, ws_protocol_infactory_const.te_short * 2) < + ws_protocol_infactory_const.te_delta * 2)) { + //Found preambule + instance->header_count++; + } else if( + (DURATION_DIFF(instance->decoder.te_last, ws_protocol_infactory_const.te_short) < + ws_protocol_infactory_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_infactory_const.te_short * 16) < + ws_protocol_infactory_const.te_delta * 8)) { + //Found syncPrefix + if(instance->header_count > 3) { + instance->decoder.parser_step = InfactoryDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } + } else { + instance->decoder.parser_step = InfactoryDecoderStepReset; + } + } + break; + + case InfactoryDecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = InfactoryDecoderStepCheckDuration; + } else { + instance->decoder.parser_step = InfactoryDecoderStepReset; + } + break; + + case InfactoryDecoderStepCheckDuration: + if(!level) { + if(duration >= ((uint32_t)ws_protocol_infactory_const.te_short * 30)) { + //Found syncPostfix + if((instance->decoder.decode_count_bit == + ws_protocol_infactory_const.min_count_bit_for_found) && + ws_protocol_infactory_check_crc(instance)) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + ws_protocol_infactory_remote_controller(&instance->generic); + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->decoder.parser_step = InfactoryDecoderStepReset; + break; + } else if( + (DURATION_DIFF(instance->decoder.te_last, ws_protocol_infactory_const.te_short) < + ws_protocol_infactory_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_infactory_const.te_long) < + ws_protocol_infactory_const.te_delta * 2)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = InfactoryDecoderStepSaveDuration; + } else if( + (DURATION_DIFF(instance->decoder.te_last, ws_protocol_infactory_const.te_short) < + ws_protocol_infactory_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_infactory_const.te_long * 2) < + ws_protocol_infactory_const.te_delta * 4)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = InfactoryDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = InfactoryDecoderStepReset; + } + } else { + instance->decoder.parser_step = InfactoryDecoderStepReset; + } + break; + } +} + +uint8_t ws_protocol_decoder_infactory_get_hash_data(void* context) { + furi_assert(context); + WSProtocolDecoderInfactory* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool ws_protocol_decoder_infactory_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + WSProtocolDecoderInfactory* instance = context; + return ws_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +bool ws_protocol_decoder_infactory_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + WSProtocolDecoderInfactory* instance = context; + bool ret = false; + do { + if(!ws_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if(instance->generic.data_count_bit != + ws_protocol_infactory_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + ret = true; + } while(false); + return ret; +} + +void ws_protocol_decoder_infactory_get_string(void* context, FuriString* output) { + furi_assert(context); + WSProtocolDecoderInfactory* instance = context; + furi_string_printf( + output, + "%s %dbit\r\n" + "Key:0x%lX%08lX\r\n" + "Sn:0x%lX Ch:%d Bat:%d\r\n" + "Temp:%d.%d C Hum:%d%%", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data >> 32), + (uint32_t)(instance->generic.data), + instance->generic.id, + instance->generic.channel, + instance->generic.battery_low, + (int16_t)instance->generic.temp, + abs(((int16_t)(instance->generic.temp * 10) - (((int16_t)instance->generic.temp) * 10))), + instance->generic.humidity); +} diff --git a/applications/plugins/weather_station/protocols/infactory.h b/applications/plugins/weather_station/protocols/infactory.h new file mode 100644 index 00000000000..2792ab98733 --- /dev/null +++ b/applications/plugins/weather_station/protocols/infactory.h @@ -0,0 +1,79 @@ +#pragma once + +#include + +#include +#include +#include +#include "ws_generic.h" +#include + +#define WS_PROTOCOL_INFACTORY_NAME "inFactory-TH" + +typedef struct WSProtocolDecoderInfactory WSProtocolDecoderInfactory; +typedef struct WSProtocolEncoderInfactory WSProtocolEncoderInfactory; + +extern const SubGhzProtocolDecoder ws_protocol_infactory_decoder; +extern const SubGhzProtocolEncoder ws_protocol_infactory_encoder; +extern const SubGhzProtocol ws_protocol_infactory; + +/** + * Allocate WSProtocolDecoderInfactory. + * @param environment Pointer to a SubGhzEnvironment instance + * @return WSProtocolDecoderInfactory* pointer to a WSProtocolDecoderInfactory instance + */ +void* ws_protocol_decoder_infactory_alloc(SubGhzEnvironment* environment); + +/** + * Free WSProtocolDecoderInfactory. + * @param context Pointer to a WSProtocolDecoderInfactory instance + */ +void ws_protocol_decoder_infactory_free(void* context); + +/** + * Reset decoder WSProtocolDecoderInfactory. + * @param context Pointer to a WSProtocolDecoderInfactory instance + */ +void ws_protocol_decoder_infactory_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a WSProtocolDecoderInfactory instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void ws_protocol_decoder_infactory_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a WSProtocolDecoderInfactory instance + * @return hash Hash sum + */ +uint8_t ws_protocol_decoder_infactory_get_hash_data(void* context); + +/** + * Serialize data WSProtocolDecoderInfactory. + * @param context Pointer to a WSProtocolDecoderInfactory instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool ws_protocol_decoder_infactory_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data WSProtocolDecoderInfactory. + * @param context Pointer to a WSProtocolDecoderInfactory instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool ws_protocol_decoder_infactory_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a WSProtocolDecoderInfactory instance + * @param output Resulting text + */ +void ws_protocol_decoder_infactory_get_string(void* context, FuriString* output); diff --git a/applications/plugins/weather_station/protocols/nexus_th.c b/applications/plugins/weather_station/protocols/nexus_th.c new file mode 100644 index 00000000000..e40f3348592 --- /dev/null +++ b/applications/plugins/weather_station/protocols/nexus_th.c @@ -0,0 +1,261 @@ +#include "nexus_th.h" + +#define TAG "WSProtocolNexus_TH" + +/* + * Help + * https://github.com/merbanan/rtl_433/blob/ef2d37cf51e3264d11cde9149ef87de2f0a4d37a/src/devices/nexus.c + * + * Nexus sensor protocol with ID, temperature and optional humidity + * also FreeTec (Pearl) NC-7345 sensors for FreeTec Weatherstation NC-7344, + * also infactory/FreeTec (Pearl) NX-3980 sensors for infactory/FreeTec NX-3974 station, + * also Solight TE82S sensors for Solight TE76/TE82/TE83/TE84 stations, + * also TFA 30.3209.02 temperature/humidity sensor. + * The sensor sends 36 bits 12 times, + * the packets are ppm modulated (distance coding) with a pulse of ~500 us + * followed by a short gap of ~1000 us for a 0 bit or a long ~2000 us gap for a + * 1 bit, the sync gap is ~4000 us. + * The data is grouped in 9 nibbles: + * [id0] [id1] [flags] [temp0] [temp1] [temp2] [const] [humi0] [humi1] + * - The 8-bit id changes when the battery is changed in the sensor. + * - flags are 4 bits B 0 C C, where B is the battery status: 1=OK, 0=LOW + * - and CC is the channel: 0=CH1, 1=CH2, 2=CH3 + * - temp is 12 bit signed scaled by 10 + * - const is always 1111 (0x0F) + * - humidity is 8 bits + * The sensors can be bought at Clas Ohlsen (Nexus) and Pearl (infactory/FreeTec). + * + */ + +#define NEXUS_TH_CONST_DATA 0b1111 + +static const SubGhzBlockConst ws_protocol_nexus_th_const = { + .te_short = 500, + .te_long = 2000, + .te_delta = 150, + .min_count_bit_for_found = 36, +}; + +struct WSProtocolDecoderNexus_TH { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + WSBlockGeneric generic; +}; + +struct WSProtocolEncoderNexus_TH { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + WSBlockGeneric generic; +}; + +typedef enum { + Nexus_THDecoderStepReset = 0, + Nexus_THDecoderStepSaveDuration, + Nexus_THDecoderStepCheckDuration, +} Nexus_THDecoderStep; + +const SubGhzProtocolDecoder ws_protocol_nexus_th_decoder = { + .alloc = ws_protocol_decoder_nexus_th_alloc, + .free = ws_protocol_decoder_nexus_th_free, + + .feed = ws_protocol_decoder_nexus_th_feed, + .reset = ws_protocol_decoder_nexus_th_reset, + + .get_hash_data = ws_protocol_decoder_nexus_th_get_hash_data, + .serialize = ws_protocol_decoder_nexus_th_serialize, + .deserialize = ws_protocol_decoder_nexus_th_deserialize, + .get_string = ws_protocol_decoder_nexus_th_get_string, +}; + +const SubGhzProtocolEncoder ws_protocol_nexus_th_encoder = { + .alloc = NULL, + .free = NULL, + + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol ws_protocol_nexus_th = { + .name = WS_PROTOCOL_NEXUS_TH_NAME, + .type = SubGhzProtocolWeatherStation, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable, + + .decoder = &ws_protocol_nexus_th_decoder, + .encoder = &ws_protocol_nexus_th_encoder, +}; + +void* ws_protocol_decoder_nexus_th_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + WSProtocolDecoderNexus_TH* instance = malloc(sizeof(WSProtocolDecoderNexus_TH)); + instance->base.protocol = &ws_protocol_nexus_th; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void ws_protocol_decoder_nexus_th_free(void* context) { + furi_assert(context); + WSProtocolDecoderNexus_TH* instance = context; + free(instance); +} + +void ws_protocol_decoder_nexus_th_reset(void* context) { + furi_assert(context); + WSProtocolDecoderNexus_TH* instance = context; + instance->decoder.parser_step = Nexus_THDecoderStepReset; +} + +static bool ws_protocol_nexus_th_check(WSProtocolDecoderNexus_TH* instance) { + uint8_t type = (instance->decoder.decode_data >> 8) & 0x0F; + + if((type == NEXUS_TH_CONST_DATA) && ((instance->decoder.decode_data >> 4) != 0xffffffff)) { + return true; + } else { + return false; + } + return true; +} + +/** + * Analysis of received data + * @param instance Pointer to a WSBlockGeneric* instance + */ +static void ws_protocol_nexus_th_remote_controller(WSBlockGeneric* instance) { + instance->id = (instance->data >> 28) & 0xFF; + instance->battery_low = !((instance->data >> 27) & 1); + instance->channel = ((instance->data >> 24) & 0x03) + 1; + + if(!((instance->data >> 23) & 1)) { + instance->temp = (float)((instance->data >> 12) & 0x07FF) / 10.0f; + } else { + instance->temp = (float)((~(instance->data >> 12) & 0x07FF) + 1) / -10.0f; + } + + instance->humidity = instance->data & 0xFF; +} + +void ws_protocol_decoder_nexus_th_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + WSProtocolDecoderNexus_TH* instance = context; + + switch(instance->decoder.parser_step) { + case Nexus_THDecoderStepReset: + if((!level) && (DURATION_DIFF(duration, ws_protocol_nexus_th_const.te_short * 8) < + ws_protocol_nexus_th_const.te_delta * 4)) { + //Found sync + instance->decoder.parser_step = Nexus_THDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } + break; + + case Nexus_THDecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = Nexus_THDecoderStepCheckDuration; + } else { + instance->decoder.parser_step = Nexus_THDecoderStepReset; + } + break; + + case Nexus_THDecoderStepCheckDuration: + if(!level) { + if(DURATION_DIFF(duration, ws_protocol_nexus_th_const.te_short * 8) < + ws_protocol_nexus_th_const.te_delta * 4) { + //Found sync + instance->decoder.parser_step = Nexus_THDecoderStepReset; + if((instance->decoder.decode_count_bit == + ws_protocol_nexus_th_const.min_count_bit_for_found) && + ws_protocol_nexus_th_check(instance)) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + ws_protocol_nexus_th_remote_controller(&instance->generic); + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + instance->decoder.parser_step = Nexus_THDecoderStepCheckDuration; + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + + break; + } else if( + (DURATION_DIFF(instance->decoder.te_last, ws_protocol_nexus_th_const.te_short) < + ws_protocol_nexus_th_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_nexus_th_const.te_short * 2) < + ws_protocol_nexus_th_const.te_delta * 2)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = Nexus_THDecoderStepSaveDuration; + } else if( + (DURATION_DIFF(instance->decoder.te_last, ws_protocol_nexus_th_const.te_short) < + ws_protocol_nexus_th_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_nexus_th_const.te_short * 4) < + ws_protocol_nexus_th_const.te_delta * 4)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = Nexus_THDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = Nexus_THDecoderStepReset; + } + } else { + instance->decoder.parser_step = Nexus_THDecoderStepReset; + } + break; + } +} + +uint8_t ws_protocol_decoder_nexus_th_get_hash_data(void* context) { + furi_assert(context); + WSProtocolDecoderNexus_TH* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool ws_protocol_decoder_nexus_th_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + WSProtocolDecoderNexus_TH* instance = context; + return ws_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +bool ws_protocol_decoder_nexus_th_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + WSProtocolDecoderNexus_TH* instance = context; + bool ret = false; + do { + if(!ws_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if(instance->generic.data_count_bit != + ws_protocol_nexus_th_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + ret = true; + } while(false); + return ret; +} + +void ws_protocol_decoder_nexus_th_get_string(void* context, FuriString* output) { + furi_assert(context); + WSProtocolDecoderNexus_TH* instance = context; + furi_string_printf( + output, + "%s %dbit\r\n" + "Key:0x%lX%08lX\r\n" + "Sn:0x%lX Ch:%d Bat:%d\r\n" + "Temp:%d.%d C Hum:%d%%", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data >> 32), + (uint32_t)(instance->generic.data), + instance->generic.id, + instance->generic.channel, + instance->generic.battery_low, + (int16_t)instance->generic.temp, + abs(((int16_t)(instance->generic.temp * 10) - (((int16_t)instance->generic.temp) * 10))), + instance->generic.humidity); +} diff --git a/applications/plugins/weather_station/protocols/nexus_th.h b/applications/plugins/weather_station/protocols/nexus_th.h new file mode 100644 index 00000000000..5450f0040ee --- /dev/null +++ b/applications/plugins/weather_station/protocols/nexus_th.h @@ -0,0 +1,79 @@ +#pragma once + +#include + +#include +#include +#include +#include "ws_generic.h" +#include + +#define WS_PROTOCOL_NEXUS_TH_NAME "Nexus-TH" + +typedef struct WSProtocolDecoderNexus_TH WSProtocolDecoderNexus_TH; +typedef struct WSProtocolEncoderNexus_TH WSProtocolEncoderNexus_TH; + +extern const SubGhzProtocolDecoder ws_protocol_nexus_th_decoder; +extern const SubGhzProtocolEncoder ws_protocol_nexus_th_encoder; +extern const SubGhzProtocol ws_protocol_nexus_th; + +/** + * Allocate WSProtocolDecoderNexus_TH. + * @param environment Pointer to a SubGhzEnvironment instance + * @return WSProtocolDecoderNexus_TH* pointer to a WSProtocolDecoderNexus_TH instance + */ +void* ws_protocol_decoder_nexus_th_alloc(SubGhzEnvironment* environment); + +/** + * Free WSProtocolDecoderNexus_TH. + * @param context Pointer to a WSProtocolDecoderNexus_TH instance + */ +void ws_protocol_decoder_nexus_th_free(void* context); + +/** + * Reset decoder WSProtocolDecoderNexus_TH. + * @param context Pointer to a WSProtocolDecoderNexus_TH instance + */ +void ws_protocol_decoder_nexus_th_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a WSProtocolDecoderNexus_TH instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void ws_protocol_decoder_nexus_th_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a WSProtocolDecoderNexus_TH instance + * @return hash Hash sum + */ +uint8_t ws_protocol_decoder_nexus_th_get_hash_data(void* context); + +/** + * Serialize data WSProtocolDecoderNexus_TH. + * @param context Pointer to a WSProtocolDecoderNexus_TH instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool ws_protocol_decoder_nexus_th_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data WSProtocolDecoderNexus_TH. + * @param context Pointer to a WSProtocolDecoderNexus_TH instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool ws_protocol_decoder_nexus_th_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a WSProtocolDecoderNexus_TH instance + * @param output Resulting text + */ +void ws_protocol_decoder_nexus_th_get_string(void* context, FuriString* output); diff --git a/applications/plugins/weather_station/protocols/protocol_items.c b/applications/plugins/weather_station/protocols/protocol_items.c new file mode 100644 index 00000000000..ea02ec058ae --- /dev/null +++ b/applications/plugins/weather_station/protocols/protocol_items.c @@ -0,0 +1,12 @@ +#include "protocol_items.h" + +const SubGhzProtocol* weather_station_protocol_registry_items[] = { + &ws_protocol_infactory, + &ws_protocol_thermopro_tx4, + &ws_protocol_nexus_th, + &ws_protocol_gt_wt_03, +}; + +const SubGhzProtocolRegistry weather_station_protocol_registry = { + .items = weather_station_protocol_registry_items, + .size = COUNT_OF(weather_station_protocol_registry_items)}; \ No newline at end of file diff --git a/applications/plugins/weather_station/protocols/protocol_items.h b/applications/plugins/weather_station/protocols/protocol_items.h new file mode 100644 index 00000000000..eac28a23af4 --- /dev/null +++ b/applications/plugins/weather_station/protocols/protocol_items.h @@ -0,0 +1,9 @@ +#pragma once +#include "../weather_station_app_i.h" + +#include "infactory.h" +#include "thermopro_tx4.h" +#include "nexus_th.h" +#include "gt_wt_03.h" + +extern const SubGhzProtocolRegistry weather_station_protocol_registry; diff --git a/applications/plugins/weather_station/protocols/thermopro_tx4.c b/applications/plugins/weather_station/protocols/thermopro_tx4.c new file mode 100644 index 00000000000..9a2eacb2fdb --- /dev/null +++ b/applications/plugins/weather_station/protocols/thermopro_tx4.c @@ -0,0 +1,260 @@ +#include "thermopro_tx4.h" + +#define TAG "WSProtocolThermoPRO_TX4" + +/* + * Help + * https://github.com/merbanan/rtl_433/blob/master/src/devices/thermopro_tx2.c + * + * The sensor sends 37 bits 6 times, before the first packet there is a sync pulse. + * The packets are ppm modulated (distance coding) with a pulse of ~500 us + * followed by a short gap of ~2000 us for a 0 bit or a long ~4000 us gap for a + * 1 bit, the sync gap is ~9000 us. + * The data is grouped in 9 nibbles + * [type] [id0] [id1] [flags] [temp0] [temp1] [temp2] [humi0] [humi1] + * - type: 4 bit fixed 1001 (9) or 0110 (5) + * - id: 8 bit a random id that is generated when the sensor starts, could include battery status + * the same batteries often generate the same id + * - flags(3): is 1 when the battery is low, otherwise 0 (ok) + * - flags(2): is 1 when the sensor sends a reading when pressing the button on the sensor + * - flags(1,0): the channel number that can be set by the sensor (1, 2, 3, X) + * - temp: 12 bit signed scaled by 10 + * - humi: 8 bit always 11001100 (0xCC) if no humidity sensor is available + * + */ + +#define THERMO_PRO_TX4_TYPE_1 0b1001 +#define THERMO_PRO_TX4_TYPE_2 0b0110 + +static const SubGhzBlockConst ws_protocol_thermopro_tx4_const = { + .te_short = 500, + .te_long = 2000, + .te_delta = 150, + .min_count_bit_for_found = 37, +}; + +struct WSProtocolDecoderThermoPRO_TX4 { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + WSBlockGeneric generic; +}; + +struct WSProtocolEncoderThermoPRO_TX4 { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + WSBlockGeneric generic; +}; + +typedef enum { + ThermoPRO_TX4DecoderStepReset = 0, + ThermoPRO_TX4DecoderStepSaveDuration, + ThermoPRO_TX4DecoderStepCheckDuration, +} ThermoPRO_TX4DecoderStep; + +const SubGhzProtocolDecoder ws_protocol_thermopro_tx4_decoder = { + .alloc = ws_protocol_decoder_thermopro_tx4_alloc, + .free = ws_protocol_decoder_thermopro_tx4_free, + + .feed = ws_protocol_decoder_thermopro_tx4_feed, + .reset = ws_protocol_decoder_thermopro_tx4_reset, + + .get_hash_data = ws_protocol_decoder_thermopro_tx4_get_hash_data, + .serialize = ws_protocol_decoder_thermopro_tx4_serialize, + .deserialize = ws_protocol_decoder_thermopro_tx4_deserialize, + .get_string = ws_protocol_decoder_thermopro_tx4_get_string, +}; + +const SubGhzProtocolEncoder ws_protocol_thermopro_tx4_encoder = { + .alloc = NULL, + .free = NULL, + + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol ws_protocol_thermopro_tx4 = { + .name = WS_PROTOCOL_THERMOPRO_TX4_NAME, + .type = SubGhzProtocolWeatherStation, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable, + + .decoder = &ws_protocol_thermopro_tx4_decoder, + .encoder = &ws_protocol_thermopro_tx4_encoder, +}; + +void* ws_protocol_decoder_thermopro_tx4_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + WSProtocolDecoderThermoPRO_TX4* instance = malloc(sizeof(WSProtocolDecoderThermoPRO_TX4)); + instance->base.protocol = &ws_protocol_thermopro_tx4; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void ws_protocol_decoder_thermopro_tx4_free(void* context) { + furi_assert(context); + WSProtocolDecoderThermoPRO_TX4* instance = context; + free(instance); +} + +void ws_protocol_decoder_thermopro_tx4_reset(void* context) { + furi_assert(context); + WSProtocolDecoderThermoPRO_TX4* instance = context; + instance->decoder.parser_step = ThermoPRO_TX4DecoderStepReset; +} + +static bool ws_protocol_thermopro_tx4_check(WSProtocolDecoderThermoPRO_TX4* instance) { + uint8_t type = instance->decoder.decode_data >> 33; + + if((type == THERMO_PRO_TX4_TYPE_1) || (type == THERMO_PRO_TX4_TYPE_2)) { + return true; + } else { + return false; + } +} + +/** + * Analysis of received data + * @param instance Pointer to a WSBlockGeneric* instance + */ +static void ws_protocol_thermopro_tx4_remote_controller(WSBlockGeneric* instance) { + instance->id = (instance->data >> 25) & 0xFF; + instance->battery_low = (instance->data >> 24) & 1; + instance->btn = (instance->data >> 23) & 1; + instance->channel = ((instance->data >> 21) & 0x03) + 1; + + if(!((instance->data >> 20) & 1)) { + instance->temp = (float)((instance->data >> 9) & 0x07FF) / 10.0f; + } else { + instance->temp = (float)((~(instance->data >> 9) & 0x07FF) + 1) / -10.0f; + } + + instance->humidity = (instance->data >> 1) & 0xFF; +} + +void ws_protocol_decoder_thermopro_tx4_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + WSProtocolDecoderThermoPRO_TX4* instance = context; + + switch(instance->decoder.parser_step) { + case ThermoPRO_TX4DecoderStepReset: + if((!level) && (DURATION_DIFF(duration, ws_protocol_thermopro_tx4_const.te_short * 18) < + ws_protocol_thermopro_tx4_const.te_delta * 10)) { + //Found sync + instance->decoder.parser_step = ThermoPRO_TX4DecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } + break; + + case ThermoPRO_TX4DecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = ThermoPRO_TX4DecoderStepCheckDuration; + } else { + instance->decoder.parser_step = ThermoPRO_TX4DecoderStepReset; + } + break; + + case ThermoPRO_TX4DecoderStepCheckDuration: + if(!level) { + if(DURATION_DIFF(duration, ws_protocol_thermopro_tx4_const.te_short * 18) < + ws_protocol_thermopro_tx4_const.te_delta * 10) { + //Found sync + instance->decoder.parser_step = ThermoPRO_TX4DecoderStepReset; + if((instance->decoder.decode_count_bit == + ws_protocol_thermopro_tx4_const.min_count_bit_for_found) && + ws_protocol_thermopro_tx4_check(instance)) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + ws_protocol_thermopro_tx4_remote_controller(&instance->generic); + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + instance->decoder.parser_step = ThermoPRO_TX4DecoderStepCheckDuration; + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + + break; + } else if( + (DURATION_DIFF( + instance->decoder.te_last, ws_protocol_thermopro_tx4_const.te_short) < + ws_protocol_thermopro_tx4_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_thermopro_tx4_const.te_long) < + ws_protocol_thermopro_tx4_const.te_delta * 2)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = ThermoPRO_TX4DecoderStepSaveDuration; + } else if( + (DURATION_DIFF( + instance->decoder.te_last, ws_protocol_thermopro_tx4_const.te_short) < + ws_protocol_thermopro_tx4_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_thermopro_tx4_const.te_long * 2) < + ws_protocol_thermopro_tx4_const.te_delta * 4)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = ThermoPRO_TX4DecoderStepSaveDuration; + } else { + instance->decoder.parser_step = ThermoPRO_TX4DecoderStepReset; + } + } else { + instance->decoder.parser_step = ThermoPRO_TX4DecoderStepReset; + } + break; + } +} + +uint8_t ws_protocol_decoder_thermopro_tx4_get_hash_data(void* context) { + furi_assert(context); + WSProtocolDecoderThermoPRO_TX4* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool ws_protocol_decoder_thermopro_tx4_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + WSProtocolDecoderThermoPRO_TX4* instance = context; + return ws_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +bool ws_protocol_decoder_thermopro_tx4_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + WSProtocolDecoderThermoPRO_TX4* instance = context; + bool ret = false; + do { + if(!ws_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if(instance->generic.data_count_bit != + ws_protocol_thermopro_tx4_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + ret = true; + } while(false); + return ret; +} + +void ws_protocol_decoder_thermopro_tx4_get_string(void* context, FuriString* output) { + furi_assert(context); + WSProtocolDecoderThermoPRO_TX4* instance = context; + furi_string_printf( + output, + "%s %dbit\r\n" + "Key:0x%lX%08lX\r\n" + "Sn:0x%lX Ch:%d Bat:%d\r\n" + "Temp:%d.%d C Hum:%d%%", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data >> 32), + (uint32_t)(instance->generic.data), + instance->generic.id, + instance->generic.channel, + instance->generic.battery_low, + (int16_t)instance->generic.temp, + abs(((int16_t)(instance->generic.temp * 10) - (((int16_t)instance->generic.temp) * 10))), + instance->generic.humidity); +} diff --git a/applications/plugins/weather_station/protocols/thermopro_tx4.h b/applications/plugins/weather_station/protocols/thermopro_tx4.h new file mode 100644 index 00000000000..1feae58d301 --- /dev/null +++ b/applications/plugins/weather_station/protocols/thermopro_tx4.h @@ -0,0 +1,79 @@ +#pragma once + +#include + +#include +#include +#include +#include "ws_generic.h" +#include + +#define WS_PROTOCOL_THERMOPRO_TX4_NAME "ThermoPRO-TX4" + +typedef struct WSProtocolDecoderThermoPRO_TX4 WSProtocolDecoderThermoPRO_TX4; +typedef struct WSProtocolEncoderThermoPRO_TX4 WSProtocolEncoderThermoPRO_TX4; + +extern const SubGhzProtocolDecoder ws_protocol_thermopro_tx4_decoder; +extern const SubGhzProtocolEncoder ws_protocol_thermopro_tx4_encoder; +extern const SubGhzProtocol ws_protocol_thermopro_tx4; + +/** + * Allocate WSProtocolDecoderThermoPRO_TX4. + * @param environment Pointer to a SubGhzEnvironment instance + * @return WSProtocolDecoderThermoPRO_TX4* pointer to a WSProtocolDecoderThermoPRO_TX4 instance + */ +void* ws_protocol_decoder_thermopro_tx4_alloc(SubGhzEnvironment* environment); + +/** + * Free WSProtocolDecoderThermoPRO_TX4. + * @param context Pointer to a WSProtocolDecoderThermoPRO_TX4 instance + */ +void ws_protocol_decoder_thermopro_tx4_free(void* context); + +/** + * Reset decoder WSProtocolDecoderThermoPRO_TX4. + * @param context Pointer to a WSProtocolDecoderThermoPRO_TX4 instance + */ +void ws_protocol_decoder_thermopro_tx4_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a WSProtocolDecoderThermoPRO_TX4 instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void ws_protocol_decoder_thermopro_tx4_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a WSProtocolDecoderThermoPRO_TX4 instance + * @return hash Hash sum + */ +uint8_t ws_protocol_decoder_thermopro_tx4_get_hash_data(void* context); + +/** + * Serialize data WSProtocolDecoderThermoPRO_TX4. + * @param context Pointer to a WSProtocolDecoderThermoPRO_TX4 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool ws_protocol_decoder_thermopro_tx4_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data WSProtocolDecoderThermoPRO_TX4. + * @param context Pointer to a WSProtocolDecoderThermoPRO_TX4 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool ws_protocol_decoder_thermopro_tx4_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a WSProtocolDecoderThermoPRO_TX4 instance + * @param output Resulting text + */ +void ws_protocol_decoder_thermopro_tx4_get_string(void* context, FuriString* output); diff --git a/applications/plugins/weather_station/protocols/ws_generic.c b/applications/plugins/weather_station/protocols/ws_generic.c new file mode 100644 index 00000000000..5740750a2e0 --- /dev/null +++ b/applications/plugins/weather_station/protocols/ws_generic.c @@ -0,0 +1,198 @@ +#include "ws_generic.h" +#include +#include +#include "../helpers/weather_station_types.h" + +#define TAG "WSBlockGeneric" + +void ws_block_generic_get_preset_name(const char* preset_name, FuriString* preset_str) { + const char* preset_name_temp; + if(!strcmp(preset_name, "AM270")) { + preset_name_temp = "FuriHalSubGhzPresetOok270Async"; + } else if(!strcmp(preset_name, "AM650")) { + preset_name_temp = "FuriHalSubGhzPresetOok650Async"; + } else if(!strcmp(preset_name, "FM238")) { + preset_name_temp = "FuriHalSubGhzPreset2FSKDev238Async"; + } else if(!strcmp(preset_name, "FM476")) { + preset_name_temp = "FuriHalSubGhzPreset2FSKDev476Async"; + } else { + preset_name_temp = "FuriHalSubGhzPresetCustom"; + } + furi_string_set(preset_str, preset_name_temp); +} + +bool ws_block_generic_serialize( + WSBlockGeneric* instance, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(instance); + bool res = false; + FuriString* temp_str; + temp_str = furi_string_alloc(); + do { + stream_clean(flipper_format_get_raw_stream(flipper_format)); + if(!flipper_format_write_header_cstr( + flipper_format, WS_KEY_FILE_TYPE, WS_KEY_FILE_VERSION)) { + FURI_LOG_E(TAG, "Unable to add header"); + break; + } + + if(!flipper_format_write_uint32(flipper_format, "Frequency", &preset->frequency, 1)) { + FURI_LOG_E(TAG, "Unable to add Frequency"); + break; + } + + ws_block_generic_get_preset_name(furi_string_get_cstr(preset->name), temp_str); + if(!flipper_format_write_string_cstr( + flipper_format, "Preset", furi_string_get_cstr(temp_str))) { + FURI_LOG_E(TAG, "Unable to add Preset"); + break; + } + if(!strcmp(furi_string_get_cstr(temp_str), "FuriHalSubGhzPresetCustom")) { + if(!flipper_format_write_string_cstr( + flipper_format, "Custom_preset_module", "CC1101")) { + FURI_LOG_E(TAG, "Unable to add Custom_preset_module"); + break; + } + if(!flipper_format_write_hex( + flipper_format, "Custom_preset_data", preset->data, preset->data_size)) { + FURI_LOG_E(TAG, "Unable to add Custom_preset_data"); + break; + } + } + if(!flipper_format_write_string_cstr(flipper_format, "Protocol", instance->protocol_name)) { + FURI_LOG_E(TAG, "Unable to add Protocol"); + break; + } + + uint32_t temp_data = instance->id; + if(!flipper_format_write_uint32(flipper_format, "Id", &temp_data, 1)) { + FURI_LOG_E(TAG, "Unable to add Id"); + break; + } + + temp_data = instance->data_count_bit; + if(!flipper_format_write_uint32(flipper_format, "Bit", &temp_data, 1)) { + FURI_LOG_E(TAG, "Unable to add Bit"); + break; + } + + uint8_t key_data[sizeof(uint64_t)] = {0}; + for(size_t i = 0; i < sizeof(uint64_t); i++) { + key_data[sizeof(uint64_t) - i - 1] = (instance->data >> i * 8) & 0xFF; + } + + if(!flipper_format_write_hex(flipper_format, "Data", key_data, sizeof(uint64_t))) { + FURI_LOG_E(TAG, "Unable to add Data"); + break; + } + + temp_data = instance->battery_low; + if(!flipper_format_write_uint32(flipper_format, "Batt", &temp_data, 1)) { + FURI_LOG_E(TAG, "Unable to add Battery_low"); + break; + } + + temp_data = instance->humidity; + if(!flipper_format_write_uint32(flipper_format, "Hum", &temp_data, 1)) { + FURI_LOG_E(TAG, "Unable to add Humidity"); + break; + } + + temp_data = instance->channel; + if(!flipper_format_write_uint32(flipper_format, "Ch", &temp_data, 1)) { + FURI_LOG_E(TAG, "Unable to add Channel"); + break; + } + + // temp_data = instance->btn; + // if(!flipper_format_write_uint32(flipper_format, "Btn", &temp_data, 1)) { + // FURI_LOG_E(TAG, "Unable to add Btn"); + // break; + // } + + float temp = instance->temp; + if(!flipper_format_write_float(flipper_format, "Temp", &temp, 1)) { + FURI_LOG_E(TAG, "Unable to add Temperature"); + break; + } + + res = true; + } while(false); + furi_string_free(temp_str); + return res; +} + +bool ws_block_generic_deserialize(WSBlockGeneric* instance, FlipperFormat* flipper_format) { + furi_assert(instance); + bool res = false; + uint32_t temp_data = 0; + + do { + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + + if(!flipper_format_read_uint32(flipper_format, "Id", (uint32_t*)&temp_data, 1)) { + FURI_LOG_E(TAG, "Missing Id"); + break; + } + instance->id = (uint32_t)temp_data; + + if(!flipper_format_read_uint32(flipper_format, "Bit", (uint32_t*)&temp_data, 1)) { + FURI_LOG_E(TAG, "Missing Bit"); + break; + } + instance->data_count_bit = (uint8_t)temp_data; + + uint8_t key_data[sizeof(uint64_t)] = {0}; + if(!flipper_format_read_hex(flipper_format, "Data", key_data, sizeof(uint64_t))) { + FURI_LOG_E(TAG, "Missing Data"); + break; + } + + for(uint8_t i = 0; i < sizeof(uint64_t); i++) { + instance->data = instance->data << 8 | key_data[i]; + } + + if(!flipper_format_read_uint32(flipper_format, "Batt", (uint32_t*)&temp_data, 1)) { + FURI_LOG_E(TAG, "Missing Battery_low"); + break; + } + instance->battery_low = (uint8_t)temp_data; + + if(!flipper_format_read_uint32(flipper_format, "Hum", (uint32_t*)&temp_data, 1)) { + FURI_LOG_E(TAG, "Missing Humidity"); + break; + } + instance->humidity = (uint8_t)temp_data; + + if(!flipper_format_read_uint32(flipper_format, "Ch", (uint32_t*)&temp_data, 1)) { + FURI_LOG_E(TAG, "Missing Channel"); + break; + } + instance->channel = (uint8_t)temp_data; + + // if(!flipper_format_read_uint32(flipper_format, "Btn", (uint32_t*)&temp_data, 1)) { + // FURI_LOG_E(TAG, "Missing Btn"); + // break; + // } + // instance->btn = (uint8_t)temp_data; + + float temp; + if(!flipper_format_read_float(flipper_format, "Temp", (float*)&temp, 1)) { + FURI_LOG_E(TAG, "Missing Temperature"); + break; + } + instance->temp = temp; + + res = true; + } while(0); + + return res; +} + +float ws_block_generic_fahrenheit_to_celsius(float fahrenheit) { + return (fahrenheit - 32.0f) / 1.8f; +} \ No newline at end of file diff --git a/applications/plugins/weather_station/protocols/ws_generic.h b/applications/plugins/weather_station/protocols/ws_generic.h new file mode 100644 index 00000000000..e0402b8181f --- /dev/null +++ b/applications/plugins/weather_station/protocols/ws_generic.h @@ -0,0 +1,61 @@ +#pragma once + +#include +#include +#include + +#include +#include "furi.h" +#include "furi_hal.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct WSBlockGeneric WSBlockGeneric; + +struct WSBlockGeneric { + const char* protocol_name; + uint64_t data; + uint32_t id; + uint8_t data_count_bit; + uint8_t battery_low; + uint8_t humidity; + uint8_t channel; + uint8_t btn; + float temp; +}; + +/** + * Get name preset. + * @param preset_name name preset + * @param preset_str Output name preset + */ +void ws_block_generic_get_preset_name(const char* preset_name, FuriString* preset_str); + +/** + * Serialize data WSBlockGeneric. + * @param instance Pointer to a WSBlockGeneric instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool ws_block_generic_serialize( + WSBlockGeneric* instance, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data WSBlockGeneric. + * @param instance Pointer to a WSBlockGeneric instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool ws_block_generic_deserialize(WSBlockGeneric* instance, FlipperFormat* flipper_format); + +float ws_block_generic_fahrenheit_to_celsius(float fahrenheit); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/applications/plugins/weather_station/scenes/weather_station_receiver.c b/applications/plugins/weather_station/scenes/weather_station_receiver.c new file mode 100644 index 00000000000..670c8c38615 --- /dev/null +++ b/applications/plugins/weather_station/scenes/weather_station_receiver.c @@ -0,0 +1,207 @@ +#include "../weather_station_app_i.h" +#include "../views/weather_station_receiver.h" + +static const NotificationSequence subghs_sequence_rx = { + &message_green_255, + + &message_vibro_on, + &message_note_c6, + &message_delay_50, + &message_sound_off, + &message_vibro_off, + + &message_delay_50, + NULL, +}; + +static const NotificationSequence subghs_sequence_rx_locked = { + &message_green_255, + + &message_display_backlight_on, + + &message_vibro_on, + &message_note_c6, + &message_delay_50, + &message_sound_off, + &message_vibro_off, + + &message_delay_500, + + &message_display_backlight_off, + NULL, +}; + +static void weather_station_scene_receiver_update_statusbar(void* context) { + WeatherStationApp* app = context; + FuriString* history_stat_str; + history_stat_str = furi_string_alloc(); + if(!ws_history_get_text_space_left(app->txrx->history, history_stat_str)) { + FuriString* frequency_str; + FuriString* modulation_str; + + frequency_str = furi_string_alloc(); + modulation_str = furi_string_alloc(); + + ws_get_frequency_modulation(app, frequency_str, modulation_str); + + ws_view_receiver_add_data_statusbar( + app->ws_receiver, + furi_string_get_cstr(frequency_str), + furi_string_get_cstr(modulation_str), + furi_string_get_cstr(history_stat_str)); + + furi_string_free(frequency_str); + furi_string_free(modulation_str); + } else { + ws_view_receiver_add_data_statusbar( + app->ws_receiver, furi_string_get_cstr(history_stat_str), "", ""); + } + furi_string_free(history_stat_str); +} + +void weather_station_scene_receiver_callback(WSCustomEvent event, void* context) { + furi_assert(context); + WeatherStationApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, event); +} + +static void weather_station_scene_receiver_add_to_history_callback( + SubGhzReceiver* receiver, + SubGhzProtocolDecoderBase* decoder_base, + void* context) { + furi_assert(context); + WeatherStationApp* app = context; + FuriString* str_buff; + str_buff = furi_string_alloc(); + + if(ws_history_add_to_history(app->txrx->history, decoder_base, app->txrx->preset) == + WSHistoryStateAddKeyNewDada) { + furi_string_reset(str_buff); + + ws_history_get_text_item_menu( + app->txrx->history, str_buff, ws_history_get_item(app->txrx->history) - 1); + ws_view_receiver_add_item_to_menu( + app->ws_receiver, + furi_string_get_cstr(str_buff), + ws_history_get_type_protocol( + app->txrx->history, ws_history_get_item(app->txrx->history) - 1)); + + weather_station_scene_receiver_update_statusbar(app); + notification_message(app->notifications, &sequence_blink_green_10); + if(app->lock != WSLockOn) { + notification_message(app->notifications, &subghs_sequence_rx); + } else { + notification_message(app->notifications, &subghs_sequence_rx_locked); + } + } + subghz_receiver_reset(receiver); + furi_string_free(str_buff); + app->txrx->rx_key_state = WSRxKeyStateAddKey; +} + +void weather_station_scene_receiver_on_enter(void* context) { + WeatherStationApp* app = context; + + FuriString* str_buff; + str_buff = furi_string_alloc(); + + if(app->txrx->rx_key_state == WSRxKeyStateIDLE) { + ws_preset_init(app, "AM650", subghz_setting_get_default_frequency(app->setting), NULL, 0); + ws_history_reset(app->txrx->history); + app->txrx->rx_key_state = WSRxKeyStateStart; + } + + ws_view_receiver_set_lock(app->ws_receiver, app->lock); + + //Load history to receiver + ws_view_receiver_exit(app->ws_receiver); + for(uint8_t i = 0; i < ws_history_get_item(app->txrx->history); i++) { + furi_string_reset(str_buff); + ws_history_get_text_item_menu(app->txrx->history, str_buff, i); + ws_view_receiver_add_item_to_menu( + app->ws_receiver, + furi_string_get_cstr(str_buff), + ws_history_get_type_protocol(app->txrx->history, i)); + app->txrx->rx_key_state = WSRxKeyStateAddKey; + } + furi_string_free(str_buff); + weather_station_scene_receiver_update_statusbar(app); + + ws_view_receiver_set_callback(app->ws_receiver, weather_station_scene_receiver_callback, app); + subghz_receiver_set_rx_callback( + app->txrx->receiver, weather_station_scene_receiver_add_to_history_callback, app); + + if(app->txrx->txrx_state == WSTxRxStateRx) { + ws_rx_end(app); + }; + if((app->txrx->txrx_state == WSTxRxStateIDLE) || (app->txrx->txrx_state == WSTxRxStateSleep)) { + ws_begin( + app, + subghz_setting_get_preset_data_by_name( + app->setting, furi_string_get_cstr(app->txrx->preset->name))); + + ws_rx(app, app->txrx->preset->frequency); + } + + ws_view_receiver_set_idx_menu(app->ws_receiver, app->txrx->idx_menu_chosen); + view_dispatcher_switch_to_view(app->view_dispatcher, WeatherStationViewReceiver); +} + +bool weather_station_scene_receiver_on_event(void* context, SceneManagerEvent event) { + WeatherStationApp* app = context; + bool consumed = false; + if(event.type == SceneManagerEventTypeCustom) { + switch(event.event) { + case WSCustomEventViewReceiverBack: + // Stop CC1101 Rx + if(app->txrx->txrx_state == WSTxRxStateRx) { + ws_rx_end(app); + ws_sleep(app); + }; + app->txrx->hopper_state = WSHopperStateOFF; + app->txrx->idx_menu_chosen = 0; + subghz_receiver_set_rx_callback(app->txrx->receiver, NULL, app); + + app->txrx->rx_key_state = WSRxKeyStateIDLE; + ws_preset_init( + app, "AM650", subghz_setting_get_default_frequency(app->setting), NULL, 0); + scene_manager_search_and_switch_to_previous_scene( + app->scene_manager, WeatherStationSceneStart); + consumed = true; + break; + case WSCustomEventViewReceiverOK: + app->txrx->idx_menu_chosen = ws_view_receiver_get_idx_menu(app->ws_receiver); + scene_manager_next_scene(app->scene_manager, WeatherStationSceneReceiverInfo); + consumed = true; + break; + case WSCustomEventViewReceiverConfig: + app->txrx->idx_menu_chosen = ws_view_receiver_get_idx_menu(app->ws_receiver); + scene_manager_next_scene(app->scene_manager, WeatherStationSceneReceiverConfig); + consumed = true; + break; + case WSCustomEventViewReceiverOffDisplay: + notification_message(app->notifications, &sequence_display_backlight_off); + consumed = true; + break; + case WSCustomEventViewReceiverUnlock: + app->lock = WSLockOff; + consumed = true; + break; + default: + break; + } + } else if(event.type == SceneManagerEventTypeTick) { + if(app->txrx->hopper_state != WSHopperStateOFF) { + ws_hopper_update(app); + weather_station_scene_receiver_update_statusbar(app); + } + if(app->txrx->txrx_state == WSTxRxStateRx) { + notification_message(app->notifications, &sequence_blink_cyan_10); + } + } + return consumed; +} + +void weather_station_scene_receiver_on_exit(void* context) { + UNUSED(context); +} diff --git a/applications/plugins/weather_station/scenes/weather_station_scene.c b/applications/plugins/weather_station/scenes/weather_station_scene.c new file mode 100644 index 00000000000..f9306e5f456 --- /dev/null +++ b/applications/plugins/weather_station/scenes/weather_station_scene.c @@ -0,0 +1,30 @@ +#include "../weather_station_app_i.h" + +// Generate scene on_enter handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, +void (*const weather_station_scene_on_enter_handlers[])(void*) = { +#include "weather_station_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 weather_station_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = { +#include "weather_station_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 weather_station_scene_on_exit_handlers[])(void* context) = { +#include "weather_station_scene_config.h" +}; +#undef ADD_SCENE + +// Initialize scene handlers configuration structure +const SceneManagerHandlers weather_station_scene_handlers = { + .on_enter_handlers = weather_station_scene_on_enter_handlers, + .on_event_handlers = weather_station_scene_on_event_handlers, + .on_exit_handlers = weather_station_scene_on_exit_handlers, + .scene_num = WeatherStationSceneNum, +}; diff --git a/applications/plugins/weather_station/scenes/weather_station_scene.h b/applications/plugins/weather_station/scenes/weather_station_scene.h new file mode 100644 index 00000000000..8cee4ee6052 --- /dev/null +++ b/applications/plugins/weather_station/scenes/weather_station_scene.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +// Generate scene id and total number +#define ADD_SCENE(prefix, name, id) WeatherStationScene##id, +typedef enum { +#include "weather_station_scene_config.h" + WeatherStationSceneNum, +} WeatherStationScene; +#undef ADD_SCENE + +extern const SceneManagerHandlers weather_station_scene_handlers; + +// Generate scene on_enter handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); +#include "weather_station_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 "weather_station_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 "weather_station_scene_config.h" +#undef ADD_SCENE diff --git a/applications/plugins/weather_station/scenes/weather_station_scene_about.c b/applications/plugins/weather_station/scenes/weather_station_scene_about.c new file mode 100644 index 00000000000..df784ec96b5 --- /dev/null +++ b/applications/plugins/weather_station/scenes/weather_station_scene_about.c @@ -0,0 +1,78 @@ +#include "../weather_station_app_i.h" +#include "../helpers/weather_station_types.h" + +void weather_station_scene_about_widget_callback( + GuiButtonType result, + InputType type, + void* context) { + WeatherStationApp* app = context; + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(app->view_dispatcher, result); + } +} + +void weather_station_scene_about_on_enter(void* context) { + WeatherStationApp* app = context; + + FuriString* temp_str; + temp_str = furi_string_alloc(); + furi_string_printf(temp_str, "\e#%s\n", "Information"); + + furi_string_cat_printf(temp_str, "Version: %s\n", WS_VERSION_APP); + furi_string_cat_printf(temp_str, "Developed by: %s\n", WS_DEVELOPED); + furi_string_cat_printf(temp_str, "Github: %s\n\n", WS_GITHUB); + + furi_string_cat_printf(temp_str, "\e#%s\n", "Description"); + furi_string_cat_printf( + temp_str, "Reading messages from\nweather station that work\nwith SubGhz sensors\n\n"); + + furi_string_cat_printf(temp_str, "Supported protocols:\n"); + size_t i = 0; + const char* protocol_name = + subghz_environment_get_protocol_name_registry(app->txrx->environment, i++); + do { + furi_string_cat_printf(temp_str, "%s\n", protocol_name); + protocol_name = subghz_environment_get_protocol_name_registry(app->txrx->environment, i++); + } while(protocol_name != NULL); + + widget_add_text_box_element( + app->widget, + 0, + 0, + 128, + 14, + AlignCenter, + AlignBottom, + "\e#\e! \e!\n", + false); + widget_add_text_box_element( + app->widget, + 0, + 2, + 128, + 14, + AlignCenter, + AlignBottom, + "\e#\e! Weather station \e!\n", + false); + widget_add_text_scroll_element(app->widget, 0, 16, 128, 50, furi_string_get_cstr(temp_str)); + furi_string_free(temp_str); + + view_dispatcher_switch_to_view(app->view_dispatcher, WeatherStationViewWidget); +} + +bool weather_station_scene_about_on_event(void* context, SceneManagerEvent event) { + WeatherStationApp* app = context; + bool consumed = false; + UNUSED(app); + UNUSED(event); + + return consumed; +} + +void weather_station_scene_about_on_exit(void* context) { + WeatherStationApp* app = context; + + // Clear views + widget_reset(app->widget); +} diff --git a/applications/plugins/weather_station/scenes/weather_station_scene_config.h b/applications/plugins/weather_station/scenes/weather_station_scene_config.h new file mode 100644 index 00000000000..0ba8ec013dc --- /dev/null +++ b/applications/plugins/weather_station/scenes/weather_station_scene_config.h @@ -0,0 +1,5 @@ +ADD_SCENE(weather_station, start, Start) +ADD_SCENE(weather_station, about, About) +ADD_SCENE(weather_station, receiver, Receiver) +ADD_SCENE(weather_station, receiver_config, ReceiverConfig) +ADD_SCENE(weather_station, receiver_info, ReceiverInfo) diff --git a/applications/plugins/weather_station/scenes/weather_station_scene_receiver_config.c b/applications/plugins/weather_station/scenes/weather_station_scene_receiver_config.c new file mode 100644 index 00000000000..fcd8f6d3e6b --- /dev/null +++ b/applications/plugins/weather_station/scenes/weather_station_scene_receiver_config.c @@ -0,0 +1,223 @@ +#include "../weather_station_app_i.h" + +enum WSSettingIndex { + WSSettingIndexFrequency, + WSSettingIndexHopping, + WSSettingIndexModulation, + WSSettingIndexLock, +}; + +#define HOPPING_COUNT 2 +const char* const hopping_text[HOPPING_COUNT] = { + "OFF", + "ON", +}; +const uint32_t hopping_value[HOPPING_COUNT] = { + WSHopperStateOFF, + WSHopperStateRunnig, +}; + +uint8_t weather_station_scene_receiver_config_next_frequency(const uint32_t value, void* context) { + furi_assert(context); + WeatherStationApp* app = context; + uint8_t index = 0; + for(uint8_t i = 0; i < subghz_setting_get_frequency_count(app->setting); i++) { + if(value == subghz_setting_get_frequency(app->setting, i)) { + index = i; + break; + } else { + index = subghz_setting_get_frequency_default_index(app->setting); + } + } + return index; +} + +uint8_t weather_station_scene_receiver_config_next_preset(const char* preset_name, void* context) { + furi_assert(context); + WeatherStationApp* app = context; + uint8_t index = 0; + for(uint8_t i = 0; i < subghz_setting_get_preset_count(app->setting); i++) { + if(!strcmp(subghz_setting_get_preset_name(app->setting, i), preset_name)) { + index = i; + break; + } else { + // index = subghz_setting_get_frequency_default_index(app ->setting); + } + } + return index; +} + +uint8_t weather_station_scene_receiver_config_hopper_value_index( + const uint32_t value, + const uint32_t values[], + uint8_t values_count, + void* context) { + furi_assert(context); + UNUSED(values_count); + WeatherStationApp* app = context; + + if(value == values[0]) { + return 0; + } else { + variable_item_set_current_value_text( + (VariableItem*)scene_manager_get_scene_state( + app->scene_manager, WeatherStationSceneReceiverConfig), + " -----"); + return 1; + } +} + +static void weather_station_scene_receiver_config_set_frequency(VariableItem* item) { + WeatherStationApp* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + if(app->txrx->hopper_state == WSHopperStateOFF) { + char text_buf[10] = {0}; + snprintf( + text_buf, + sizeof(text_buf), + "%lu.%02lu", + subghz_setting_get_frequency(app->setting, index) / 1000000, + (subghz_setting_get_frequency(app->setting, index) % 1000000) / 10000); + variable_item_set_current_value_text(item, text_buf); + app->txrx->preset->frequency = subghz_setting_get_frequency(app->setting, index); + } else { + variable_item_set_current_value_index( + item, subghz_setting_get_frequency_default_index(app->setting)); + } +} + +static void weather_station_scene_receiver_config_set_preset(VariableItem* item) { + WeatherStationApp* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + variable_item_set_current_value_text( + item, subghz_setting_get_preset_name(app->setting, index)); + ws_preset_init( + app, + subghz_setting_get_preset_name(app->setting, index), + app->txrx->preset->frequency, + subghz_setting_get_preset_data(app->setting, index), + subghz_setting_get_preset_data_size(app->setting, index)); +} + +static void weather_station_scene_receiver_config_set_hopping_running(VariableItem* item) { + WeatherStationApp* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + variable_item_set_current_value_text(item, hopping_text[index]); + if(hopping_value[index] == WSHopperStateOFF) { + char text_buf[10] = {0}; + snprintf( + text_buf, + sizeof(text_buf), + "%lu.%02lu", + subghz_setting_get_default_frequency(app->setting) / 1000000, + (subghz_setting_get_default_frequency(app->setting) % 1000000) / 10000); + variable_item_set_current_value_text( + (VariableItem*)scene_manager_get_scene_state( + app->scene_manager, WeatherStationSceneReceiverConfig), + text_buf); + app->txrx->preset->frequency = subghz_setting_get_default_frequency(app->setting); + variable_item_set_current_value_index( + (VariableItem*)scene_manager_get_scene_state( + app->scene_manager, WeatherStationSceneReceiverConfig), + subghz_setting_get_frequency_default_index(app->setting)); + } else { + variable_item_set_current_value_text( + (VariableItem*)scene_manager_get_scene_state( + app->scene_manager, WeatherStationSceneReceiverConfig), + " -----"); + variable_item_set_current_value_index( + (VariableItem*)scene_manager_get_scene_state( + app->scene_manager, WeatherStationSceneReceiverConfig), + subghz_setting_get_frequency_default_index(app->setting)); + } + + app->txrx->hopper_state = hopping_value[index]; +} + +static void + weather_station_scene_receiver_config_var_list_enter_callback(void* context, uint32_t index) { + furi_assert(context); + WeatherStationApp* app = context; + if(index == WSSettingIndexLock) { + view_dispatcher_send_custom_event(app->view_dispatcher, WSCustomEventSceneSettingLock); + } +} + +void weather_station_scene_receiver_config_on_enter(void* context) { + WeatherStationApp* app = context; + VariableItem* item; + uint8_t value_index; + + item = variable_item_list_add( + app->variable_item_list, + "Frequency:", + subghz_setting_get_frequency_count(app->setting), + weather_station_scene_receiver_config_set_frequency, + app); + value_index = + weather_station_scene_receiver_config_next_frequency(app->txrx->preset->frequency, app); + scene_manager_set_scene_state( + app->scene_manager, WeatherStationSceneReceiverConfig, (uint32_t)item); + variable_item_set_current_value_index(item, value_index); + char text_buf[10] = {0}; + snprintf( + text_buf, + sizeof(text_buf), + "%lu.%02lu", + subghz_setting_get_frequency(app->setting, value_index) / 1000000, + (subghz_setting_get_frequency(app->setting, value_index) % 1000000) / 10000); + variable_item_set_current_value_text(item, text_buf); + + item = variable_item_list_add( + app->variable_item_list, + "Hopping:", + HOPPING_COUNT, + weather_station_scene_receiver_config_set_hopping_running, + app); + value_index = weather_station_scene_receiver_config_hopper_value_index( + app->txrx->hopper_state, hopping_value, HOPPING_COUNT, app); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, hopping_text[value_index]); + + item = variable_item_list_add( + app->variable_item_list, + "Modulation:", + subghz_setting_get_preset_count(app->setting), + weather_station_scene_receiver_config_set_preset, + app); + value_index = weather_station_scene_receiver_config_next_preset( + furi_string_get_cstr(app->txrx->preset->name), app); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text( + item, subghz_setting_get_preset_name(app->setting, value_index)); + + variable_item_list_add(app->variable_item_list, "Lock Keyboard", 1, NULL, NULL); + variable_item_list_set_enter_callback( + app->variable_item_list, + weather_station_scene_receiver_config_var_list_enter_callback, + app); + + view_dispatcher_switch_to_view(app->view_dispatcher, WeatherStationViewVariableItemList); +} + +bool weather_station_scene_receiver_config_on_event(void* context, SceneManagerEvent event) { + WeatherStationApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == WSCustomEventSceneSettingLock) { + app->lock = WSLockOn; + scene_manager_previous_scene(app->scene_manager); + consumed = true; + } + } + return consumed; +} + +void weather_station_scene_receiver_config_on_exit(void* context) { + WeatherStationApp* app = context; + variable_item_list_set_selected_item(app->variable_item_list, 0); + variable_item_list_reset(app->variable_item_list); +} diff --git a/applications/plugins/weather_station/scenes/weather_station_scene_receiver_info.c b/applications/plugins/weather_station/scenes/weather_station_scene_receiver_info.c new file mode 100644 index 00000000000..b26661be385 --- /dev/null +++ b/applications/plugins/weather_station/scenes/weather_station_scene_receiver_info.c @@ -0,0 +1,50 @@ +#include "../weather_station_app_i.h" +#include "../views/weather_station_receiver.h" + +void weather_station_scene_receiver_info_callback(WSCustomEvent event, void* context) { + furi_assert(context); + WeatherStationApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, event); +} + +static void weather_station_scene_receiver_info_add_to_history_callback( + SubGhzReceiver* receiver, + SubGhzProtocolDecoderBase* decoder_base, + void* context) { + furi_assert(context); + WeatherStationApp* app = context; + + if(ws_history_add_to_history(app->txrx->history, decoder_base, app->txrx->preset) == + WSHistoryStateAddKeyUpdateData) { + ws_view_receiver_info_update( + app->ws_receiver_info, + ws_history_get_raw_data(app->txrx->history, app->txrx->idx_menu_chosen)); + subghz_receiver_reset(receiver); + + notification_message(app->notifications, &sequence_blink_green_10); + app->txrx->rx_key_state = WSRxKeyStateAddKey; + } +} + +void weather_station_scene_receiver_info_on_enter(void* context) { + WeatherStationApp* app = context; + + subghz_receiver_set_rx_callback( + app->txrx->receiver, weather_station_scene_receiver_info_add_to_history_callback, app); + ws_view_receiver_info_update( + app->ws_receiver_info, + ws_history_get_raw_data(app->txrx->history, app->txrx->idx_menu_chosen)); + view_dispatcher_switch_to_view(app->view_dispatcher, WeatherStationViewReceiverInfo); +} + +bool weather_station_scene_receiver_info_on_event(void* context, SceneManagerEvent event) { + WeatherStationApp* app = context; + bool consumed = false; + UNUSED(app); + UNUSED(event); + return consumed; +} + +void weather_station_scene_receiver_info_on_exit(void* context) { + UNUSED(context); +} diff --git a/applications/plugins/weather_station/scenes/weather_station_scene_start.c b/applications/plugins/weather_station/scenes/weather_station_scene_start.c new file mode 100644 index 00000000000..56dd6fa86dc --- /dev/null +++ b/applications/plugins/weather_station/scenes/weather_station_scene_start.c @@ -0,0 +1,58 @@ +#include "../weather_station_app_i.h" + +typedef enum { + SubmenuIndexWeatherStationReceiver, + SubmenuIndexWeatherStationAbout, +} SubmenuIndex; + +void weather_station_scene_start_submenu_callback(void* context, uint32_t index) { + WeatherStationApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, index); +} + +void weather_station_scene_start_on_enter(void* context) { + UNUSED(context); + WeatherStationApp* app = context; + Submenu* submenu = app->submenu; + + submenu_add_item( + submenu, + "Read Weather Station", + SubmenuIndexWeatherStationReceiver, + weather_station_scene_start_submenu_callback, + app); + submenu_add_item( + submenu, + "About", + SubmenuIndexWeatherStationAbout, + weather_station_scene_start_submenu_callback, + app); + + submenu_set_selected_item( + submenu, scene_manager_get_scene_state(app->scene_manager, WeatherStationSceneStart)); + + view_dispatcher_switch_to_view(app->view_dispatcher, WeatherStationViewSubmenu); +} + +bool weather_station_scene_start_on_event(void* context, SceneManagerEvent event) { + WeatherStationApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubmenuIndexWeatherStationAbout) { + scene_manager_next_scene(app->scene_manager, WeatherStationSceneAbout); + consumed = true; + } else if(event.event == SubmenuIndexWeatherStationReceiver) { + scene_manager_next_scene(app->scene_manager, WeatherStationSceneReceiver); + consumed = true; + } + scene_manager_set_scene_state(app->scene_manager, WeatherStationSceneStart, event.event); + } + + return consumed; +} + +void weather_station_scene_start_on_exit(void* context) { + WeatherStationApp* app = context; + submenu_reset(app->submenu); +} diff --git a/applications/plugins/weather_station/views/weather_station_receiver.c b/applications/plugins/weather_station/views/weather_station_receiver.c new file mode 100644 index 00000000000..d30b7926b2e --- /dev/null +++ b/applications/plugins/weather_station/views/weather_station_receiver.c @@ -0,0 +1,437 @@ +#include "weather_station_receiver.h" +#include "../weather_station_app_i.h" +#include "weather_station_icons.h" +#include + +#include +#include +#include +#include + +#define FRAME_HEIGHT 12 +#define MAX_LEN_PX 100 +#define MENU_ITEMS 4u +#define UNLOCK_CNT 3 + +typedef struct { + FuriString* item_str; + uint8_t type; +} WSReceiverMenuItem; + +ARRAY_DEF(WSReceiverMenuItemArray, WSReceiverMenuItem, M_POD_OPLIST) + +#define M_OPL_WSReceiverMenuItemArray_t() ARRAY_OPLIST(WSReceiverMenuItemArray, M_POD_OPLIST) + +struct WSReceiverHistory { + WSReceiverMenuItemArray_t data; +}; + +typedef struct WSReceiverHistory WSReceiverHistory; + +static const Icon* ReceiverItemIcons[] = { + [SubGhzProtocolTypeUnknown] = &I_Quest_7x8, + [SubGhzProtocolTypeStatic] = &I_Unlock_7x8, + [SubGhzProtocolTypeDynamic] = &I_Lock_7x8, + [SubGhzProtocolWeatherStation] = &I_station_icon, +}; + +typedef enum { + WSReceiverBarShowDefault, + WSReceiverBarShowLock, + WSReceiverBarShowToUnlockPress, + WSReceiverBarShowUnlock, +} WSReceiverBarShow; + +struct WSReceiver { + WSLock lock; + uint8_t lock_count; + FuriTimer* timer; + View* view; + WSReceiverCallback callback; + void* context; +}; + +typedef struct { + FuriString* frequency_str; + FuriString* preset_str; + FuriString* history_stat_str; + WSReceiverHistory* history; + uint16_t idx; + uint16_t list_offset; + uint16_t history_item; + WSReceiverBarShow bar_show; +} WSReceiverModel; + +void ws_view_receiver_set_lock(WSReceiver* ws_receiver, WSLock lock) { + furi_assert(ws_receiver); + ws_receiver->lock_count = 0; + if(lock == WSLockOn) { + ws_receiver->lock = lock; + with_view_model( + ws_receiver->view, + WSReceiverModel * model, + { model->bar_show = WSReceiverBarShowLock; }, + true); + furi_timer_start(ws_receiver->timer, pdMS_TO_TICKS(1000)); + } else { + with_view_model( + ws_receiver->view, + WSReceiverModel * model, + { model->bar_show = WSReceiverBarShowDefault; }, + true); + } +} + +void ws_view_receiver_set_callback( + WSReceiver* ws_receiver, + WSReceiverCallback callback, + void* context) { + furi_assert(ws_receiver); + furi_assert(callback); + ws_receiver->callback = callback; + ws_receiver->context = context; +} + +static void ws_view_receiver_update_offset(WSReceiver* ws_receiver) { + furi_assert(ws_receiver); + + with_view_model( + ws_receiver->view, + WSReceiverModel * model, + { + size_t history_item = model->history_item; + uint16_t bounds = history_item > 3 ? 2 : history_item; + + if(history_item > 3 && model->idx >= (int16_t)(history_item - 1)) { + model->list_offset = model->idx - 3; + } else if(model->list_offset < model->idx - bounds) { + model->list_offset = + CLAMP(model->list_offset + 1, (int16_t)(history_item - bounds), 0); + } else if(model->list_offset > model->idx - bounds) { + model->list_offset = CLAMP(model->idx - 1, (int16_t)(history_item - bounds), 0); + } + }, + true); +} + +void ws_view_receiver_add_item_to_menu(WSReceiver* ws_receiver, const char* name, uint8_t type) { + furi_assert(ws_receiver); + with_view_model( + ws_receiver->view, + WSReceiverModel * model, + { + WSReceiverMenuItem* item_menu = WSReceiverMenuItemArray_push_raw(model->history->data); + item_menu->item_str = furi_string_alloc_set(name); + item_menu->type = type; + if((model->idx == model->history_item - 1)) { + model->history_item++; + model->idx++; + } else { + model->history_item++; + } + }, + true); + ws_view_receiver_update_offset(ws_receiver); +} + +void ws_view_receiver_add_data_statusbar( + WSReceiver* ws_receiver, + const char* frequency_str, + const char* preset_str, + const char* history_stat_str) { + furi_assert(ws_receiver); + with_view_model( + ws_receiver->view, + WSReceiverModel * model, + { + furi_string_set_str(model->frequency_str, frequency_str); + furi_string_set_str(model->preset_str, preset_str); + furi_string_set_str(model->history_stat_str, history_stat_str); + }, + true); +} + +static void ws_view_receiver_draw_frame(Canvas* canvas, uint16_t idx, bool scrollbar) { + canvas_set_color(canvas, ColorBlack); + canvas_draw_box(canvas, 0, 0 + idx * FRAME_HEIGHT, scrollbar ? 122 : 127, FRAME_HEIGHT); + + canvas_set_color(canvas, ColorWhite); + canvas_draw_dot(canvas, 0, 0 + idx * FRAME_HEIGHT); + canvas_draw_dot(canvas, 1, 0 + idx * FRAME_HEIGHT); + canvas_draw_dot(canvas, 0, (0 + idx * FRAME_HEIGHT) + 1); + + canvas_draw_dot(canvas, 0, (0 + idx * FRAME_HEIGHT) + 11); + canvas_draw_dot(canvas, scrollbar ? 121 : 126, 0 + idx * FRAME_HEIGHT); + canvas_draw_dot(canvas, scrollbar ? 121 : 126, (0 + idx * FRAME_HEIGHT) + 11); +} + +void ws_view_receiver_draw(Canvas* canvas, WSReceiverModel* model) { + canvas_clear(canvas); + canvas_set_color(canvas, ColorBlack); + canvas_set_font(canvas, FontSecondary); + + elements_button_left(canvas, "Config"); + canvas_draw_line(canvas, 46, 51, 125, 51); + + bool scrollbar = model->history_item > 4; + FuriString* str_buff; + str_buff = furi_string_alloc(); + + WSReceiverMenuItem* item_menu; + + for(size_t i = 0; i < MIN(model->history_item, MENU_ITEMS); ++i) { + size_t idx = CLAMP((uint16_t)(i + model->list_offset), model->history_item, 0); + item_menu = WSReceiverMenuItemArray_get(model->history->data, idx); + furi_string_set(str_buff, item_menu->item_str); + elements_string_fit_width(canvas, str_buff, scrollbar ? MAX_LEN_PX - 6 : MAX_LEN_PX); + if(model->idx == idx) { + ws_view_receiver_draw_frame(canvas, i, scrollbar); + } else { + canvas_set_color(canvas, ColorBlack); + } + canvas_draw_icon(canvas, 4, 2 + i * FRAME_HEIGHT, ReceiverItemIcons[item_menu->type]); + canvas_draw_str(canvas, 15, 9 + i * FRAME_HEIGHT, furi_string_get_cstr(str_buff)); + furi_string_reset(str_buff); + } + if(scrollbar) { + elements_scrollbar_pos(canvas, 128, 0, 49, model->idx, model->history_item); + } + furi_string_free(str_buff); + + canvas_set_color(canvas, ColorBlack); + + if(model->history_item == 0) { + canvas_draw_icon(canvas, 0, 0, &I_Scanning_123x52); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 63, 46, "Scanning..."); + canvas_draw_line(canvas, 46, 51, 125, 51); + canvas_set_font(canvas, FontSecondary); + } + + switch(model->bar_show) { + case WSReceiverBarShowLock: + canvas_draw_icon(canvas, 64, 55, &I_Lock_7x8); + canvas_draw_str(canvas, 74, 62, "Locked"); + break; + case WSReceiverBarShowToUnlockPress: + canvas_draw_str(canvas, 44, 62, furi_string_get_cstr(model->frequency_str)); + canvas_draw_str(canvas, 79, 62, furi_string_get_cstr(model->preset_str)); + canvas_draw_str(canvas, 96, 62, furi_string_get_cstr(model->history_stat_str)); + canvas_set_font(canvas, FontSecondary); + elements_bold_rounded_frame(canvas, 14, 8, 99, 48); + elements_multiline_text(canvas, 65, 26, "To unlock\npress:"); + canvas_draw_icon(canvas, 65, 42, &I_Pin_back_arrow_10x8); + canvas_draw_icon(canvas, 80, 42, &I_Pin_back_arrow_10x8); + canvas_draw_icon(canvas, 95, 42, &I_Pin_back_arrow_10x8); + canvas_draw_icon(canvas, 16, 13, &I_WarningDolphin_45x42); + canvas_draw_dot(canvas, 17, 61); + break; + case WSReceiverBarShowUnlock: + canvas_draw_icon(canvas, 64, 55, &I_Unlock_7x8); + canvas_draw_str(canvas, 74, 62, "Unlocked"); + break; + default: + canvas_draw_str(canvas, 44, 62, furi_string_get_cstr(model->frequency_str)); + canvas_draw_str(canvas, 79, 62, furi_string_get_cstr(model->preset_str)); + canvas_draw_str(canvas, 96, 62, furi_string_get_cstr(model->history_stat_str)); + break; + } +} + +static void ws_view_receiver_timer_callback(void* context) { + furi_assert(context); + WSReceiver* ws_receiver = context; + with_view_model( + ws_receiver->view, + WSReceiverModel * model, + { model->bar_show = WSReceiverBarShowDefault; }, + true); + if(ws_receiver->lock_count < UNLOCK_CNT) { + ws_receiver->callback(WSCustomEventViewReceiverOffDisplay, ws_receiver->context); + } else { + ws_receiver->lock = WSLockOff; + ws_receiver->callback(WSCustomEventViewReceiverUnlock, ws_receiver->context); + } + ws_receiver->lock_count = 0; +} + +bool ws_view_receiver_input(InputEvent* event, void* context) { + furi_assert(context); + WSReceiver* ws_receiver = context; + + if(ws_receiver->lock == WSLockOn) { + with_view_model( + ws_receiver->view, + WSReceiverModel * model, + { model->bar_show = WSReceiverBarShowToUnlockPress; }, + true); + if(ws_receiver->lock_count == 0) { + furi_timer_start(ws_receiver->timer, pdMS_TO_TICKS(1000)); + } + if(event->key == InputKeyBack && event->type == InputTypeShort) { + ws_receiver->lock_count++; + } + if(ws_receiver->lock_count >= UNLOCK_CNT) { + ws_receiver->callback(WSCustomEventViewReceiverUnlock, ws_receiver->context); + with_view_model( + ws_receiver->view, + WSReceiverModel * model, + { model->bar_show = WSReceiverBarShowUnlock; }, + true); + ws_receiver->lock = WSLockOff; + furi_timer_start(ws_receiver->timer, pdMS_TO_TICKS(650)); + } + + return true; + } + + if(event->key == InputKeyBack && event->type == InputTypeShort) { + ws_receiver->callback(WSCustomEventViewReceiverBack, ws_receiver->context); + } else if( + event->key == InputKeyUp && + (event->type == InputTypeShort || event->type == InputTypeRepeat)) { + with_view_model( + ws_receiver->view, + WSReceiverModel * model, + { + if(model->idx != 0) model->idx--; + }, + true); + } else if( + event->key == InputKeyDown && + (event->type == InputTypeShort || event->type == InputTypeRepeat)) { + with_view_model( + ws_receiver->view, + WSReceiverModel * model, + { + if(model->idx != model->history_item - 1) model->idx++; + }, + true); + } else if(event->key == InputKeyLeft && event->type == InputTypeShort) { + ws_receiver->callback(WSCustomEventViewReceiverConfig, ws_receiver->context); + } else if(event->key == InputKeyOk && event->type == InputTypeShort) { + with_view_model( + ws_receiver->view, + WSReceiverModel * model, + { + if(model->history_item != 0) { + ws_receiver->callback(WSCustomEventViewReceiverOK, ws_receiver->context); + } + }, + false); + } + + ws_view_receiver_update_offset(ws_receiver); + + return true; +} + +void ws_view_receiver_enter(void* context) { + furi_assert(context); +} + +void ws_view_receiver_exit(void* context) { + furi_assert(context); + WSReceiver* ws_receiver = context; + with_view_model( + ws_receiver->view, + WSReceiverModel * model, + { + furi_string_reset(model->frequency_str); + furi_string_reset(model->preset_str); + furi_string_reset(model->history_stat_str); + for + M_EACH(item_menu, model->history->data, WSReceiverMenuItemArray_t) { + furi_string_free(item_menu->item_str); + item_menu->type = 0; + } + WSReceiverMenuItemArray_reset(model->history->data); + model->idx = 0; + model->list_offset = 0; + model->history_item = 0; + }, + false); + furi_timer_stop(ws_receiver->timer); +} + +WSReceiver* ws_view_receiver_alloc() { + WSReceiver* ws_receiver = malloc(sizeof(WSReceiver)); + + // View allocation and configuration + ws_receiver->view = view_alloc(); + + ws_receiver->lock = WSLockOff; + ws_receiver->lock_count = 0; + view_allocate_model(ws_receiver->view, ViewModelTypeLocking, sizeof(WSReceiverModel)); + view_set_context(ws_receiver->view, ws_receiver); + view_set_draw_callback(ws_receiver->view, (ViewDrawCallback)ws_view_receiver_draw); + view_set_input_callback(ws_receiver->view, ws_view_receiver_input); + view_set_enter_callback(ws_receiver->view, ws_view_receiver_enter); + view_set_exit_callback(ws_receiver->view, ws_view_receiver_exit); + + with_view_model( + ws_receiver->view, + WSReceiverModel * model, + { + model->frequency_str = furi_string_alloc(); + model->preset_str = furi_string_alloc(); + model->history_stat_str = furi_string_alloc(); + model->bar_show = WSReceiverBarShowDefault; + model->history = malloc(sizeof(WSReceiverHistory)); + WSReceiverMenuItemArray_init(model->history->data); + }, + true); + ws_receiver->timer = + furi_timer_alloc(ws_view_receiver_timer_callback, FuriTimerTypeOnce, ws_receiver); + return ws_receiver; +} + +void ws_view_receiver_free(WSReceiver* ws_receiver) { + furi_assert(ws_receiver); + + with_view_model( + ws_receiver->view, + WSReceiverModel * model, + { + furi_string_free(model->frequency_str); + furi_string_free(model->preset_str); + furi_string_free(model->history_stat_str); + for + M_EACH(item_menu, model->history->data, WSReceiverMenuItemArray_t) { + furi_string_free(item_menu->item_str); + item_menu->type = 0; + } + WSReceiverMenuItemArray_clear(model->history->data); + free(model->history); + }, + false); + furi_timer_free(ws_receiver->timer); + view_free(ws_receiver->view); + free(ws_receiver); +} + +View* ws_view_receiver_get_view(WSReceiver* ws_receiver) { + furi_assert(ws_receiver); + return ws_receiver->view; +} + +uint16_t ws_view_receiver_get_idx_menu(WSReceiver* ws_receiver) { + furi_assert(ws_receiver); + uint32_t idx = 0; + with_view_model( + ws_receiver->view, WSReceiverModel * model, { idx = model->idx; }, false); + return idx; +} + +void ws_view_receiver_set_idx_menu(WSReceiver* ws_receiver, uint16_t idx) { + furi_assert(ws_receiver); + with_view_model( + ws_receiver->view, + WSReceiverModel * model, + { + model->idx = idx; + if(model->idx > 2) model->list_offset = idx - 2; + }, + true); + ws_view_receiver_update_offset(ws_receiver); +} diff --git a/applications/plugins/weather_station/views/weather_station_receiver.h b/applications/plugins/weather_station/views/weather_station_receiver.h new file mode 100644 index 00000000000..30c6516d53c --- /dev/null +++ b/applications/plugins/weather_station/views/weather_station_receiver.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include "../helpers/weather_station_types.h" +#include "../helpers/weather_station_event.h" + +typedef struct WSReceiver WSReceiver; + +typedef void (*WSReceiverCallback)(WSCustomEvent event, void* context); + +void ws_view_receiver_set_lock(WSReceiver* ws_receiver, WSLock keyboard); + +void ws_view_receiver_set_callback( + WSReceiver* ws_receiver, + WSReceiverCallback callback, + void* context); + +WSReceiver* ws_view_receiver_alloc(); + +void ws_view_receiver_free(WSReceiver* ws_receiver); + +View* ws_view_receiver_get_view(WSReceiver* ws_receiver); + +void ws_view_receiver_add_data_statusbar( + WSReceiver* ws_receiver, + const char* frequency_str, + const char* preset_str, + const char* history_stat_str); + +void ws_view_receiver_add_item_to_menu(WSReceiver* ws_receiver, const char* name, uint8_t type); + +uint16_t ws_view_receiver_get_idx_menu(WSReceiver* ws_receiver); + +void ws_view_receiver_set_idx_menu(WSReceiver* ws_receiver, uint16_t idx); + +void ws_view_receiver_exit(void* context); diff --git a/applications/plugins/weather_station/views/weather_station_receiver_info.c b/applications/plugins/weather_station/views/weather_station_receiver_info.c new file mode 100644 index 00000000000..3187062767a --- /dev/null +++ b/applications/plugins/weather_station/views/weather_station_receiver_info.c @@ -0,0 +1,150 @@ +#include "weather_station_receiver.h" +#include "../weather_station_app_i.h" +#include "weather_station_icons.h" +#include "../protocols/ws_generic.h" +#include +#include +#include "math.h" + +#define abs(x) ((x) > 0 ? (x) : -(x)) + +struct WSReceiverInfo { + View* view; +}; + +typedef struct { + FuriString* protocol_name; + WSBlockGeneric* generic; +} WSReceiverInfoModel; + +void ws_view_receiver_info_update(WSReceiverInfo* ws_receiver_info, FlipperFormat* fff) { + furi_assert(ws_receiver_info); + furi_assert(fff); + + with_view_model( + ws_receiver_info->view, + WSReceiverInfoModel * model, + { + flipper_format_rewind(fff); + flipper_format_read_string(fff, "Protocol", model->protocol_name); + + ws_block_generic_deserialize(model->generic, fff); + }, + true); +} + +void ws_view_receiver_info_draw(Canvas* canvas, WSReceiverInfoModel* model) { + char buffer[64]; + canvas_clear(canvas); + canvas_set_color(canvas, ColorBlack); + canvas_set_font(canvas, FontSecondary); + + snprintf( + buffer, + sizeof(buffer), + "%s %db", + furi_string_get_cstr(model->protocol_name), + model->generic->data_count_bit); + canvas_draw_str(canvas, 5, 8, buffer); + + snprintf(buffer, sizeof(buffer), "Ch: %01d", model->generic->channel); + canvas_draw_str(canvas, 105, 8, buffer); + + snprintf(buffer, sizeof(buffer), "Sn: 0x%02lX", model->generic->id); + canvas_draw_str(canvas, 5, 20, buffer); + + snprintf(buffer, sizeof(buffer), "Batt: %s", (!model->generic->battery_low ? "ok" : "low")); + canvas_draw_str(canvas, 85, 20, buffer); + + snprintf(buffer, sizeof(buffer), "Data: 0x%llX", model->generic->data); + canvas_draw_str(canvas, 5, 32, buffer); + + elements_bold_rounded_frame(canvas, 2, 37, 123, 25); + canvas_set_font(canvas, FontPrimary); + + canvas_draw_icon(canvas, 13 + 5, 42, &I_Therm_7x16); + snprintf( + buffer, + sizeof(buffer), + "%3.2d.%d C", + (int16_t)model->generic->temp, + abs(((int16_t)(model->generic->temp * 10) - (((int16_t)model->generic->temp) * 10)))); + canvas_draw_str_aligned(canvas, 58 + 5, 46, AlignRight, AlignTop, buffer); + canvas_draw_circle(canvas, 50 + 5, 45, 1); + + canvas_draw_icon(canvas, 70 + 5, 42, &I_Humid_10x15); + snprintf(buffer, sizeof(buffer), "%d%%", model->generic->humidity); + canvas_draw_str(canvas, 86 + 5, 54, buffer); +} + +bool ws_view_receiver_info_input(InputEvent* event, void* context) { + furi_assert(context); + //WSReceiverInfo* ws_receiver_info = context; + + if(event->key == InputKeyBack) { + return false; + } + + return true; +} + +void ws_view_receiver_info_enter(void* context) { + furi_assert(context); +} + +void ws_view_receiver_info_exit(void* context) { + furi_assert(context); + WSReceiverInfo* ws_receiver_info = context; + + with_view_model( + ws_receiver_info->view, + WSReceiverInfoModel * model, + { furi_string_reset(model->protocol_name); }, + false); +} + +WSReceiverInfo* ws_view_receiver_info_alloc() { + WSReceiverInfo* ws_receiver_info = malloc(sizeof(WSReceiverInfo)); + + // View allocation and configuration + ws_receiver_info->view = view_alloc(); + + view_allocate_model(ws_receiver_info->view, ViewModelTypeLocking, sizeof(WSReceiverInfoModel)); + view_set_context(ws_receiver_info->view, ws_receiver_info); + view_set_draw_callback(ws_receiver_info->view, (ViewDrawCallback)ws_view_receiver_info_draw); + view_set_input_callback(ws_receiver_info->view, ws_view_receiver_info_input); + view_set_enter_callback(ws_receiver_info->view, ws_view_receiver_info_enter); + view_set_exit_callback(ws_receiver_info->view, ws_view_receiver_info_exit); + + with_view_model( + ws_receiver_info->view, + WSReceiverInfoModel * model, + { + model->generic = malloc(sizeof(WSBlockGeneric)); + model->protocol_name = furi_string_alloc(); + }, + true); + + return ws_receiver_info; +} + +void ws_view_receiver_info_free(WSReceiverInfo* ws_receiver_info) { + furi_assert(ws_receiver_info); + + with_view_model( + ws_receiver_info->view, + WSReceiverInfoModel * model, + { + furi_string_free(model->protocol_name); + free(model->generic); + }, + false); + + view_free(ws_receiver_info->view); + free(ws_receiver_info); +} + +View* ws_view_receiver_info_get_view(WSReceiverInfo* ws_receiver_info) { + furi_assert(ws_receiver_info); + return ws_receiver_info->view; +} diff --git a/applications/plugins/weather_station/views/weather_station_receiver_info.h b/applications/plugins/weather_station/views/weather_station_receiver_info.h new file mode 100644 index 00000000000..705434a2383 --- /dev/null +++ b/applications/plugins/weather_station/views/weather_station_receiver_info.h @@ -0,0 +1,16 @@ +#pragma once + +#include +#include "../helpers/weather_station_types.h" +#include "../helpers/weather_station_event.h" +#include + +typedef struct WSReceiverInfo WSReceiverInfo; + +void ws_view_receiver_info_update(WSReceiverInfo* ws_receiver_info, FlipperFormat* fff); + +WSReceiverInfo* ws_view_receiver_info_alloc(); + +void ws_view_receiver_info_free(WSReceiverInfo* ws_receiver_info); + +View* ws_view_receiver_info_get_view(WSReceiverInfo* ws_receiver_info); diff --git a/applications/plugins/weather_station/weather_station_10px.png b/applications/plugins/weather_station/weather_station_10px.png new file mode 100644 index 0000000000000000000000000000000000000000..7d5cc318c369f583c531ca5e8846a9a498ec44b6 GIT binary patch literal 175 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V6Od#Ihk44ofy`glX(f`u%tWsIx;Y9 z?C1WI$O`0h7I;J!GcfQS24TkI`72U@f-asejv*Ssy?udP3<@01TYulb=@zZ(cJX(> z9E<0IiRH6?9buO;SoeAj)2m&2)(%T&iEpuE4*KbLE`4W{EU(#4ulkv@OZPmG6Pd23 R{2OQ +#include +#include "protocols/protocol_items.h" + +static bool weather_station_app_custom_event_callback(void* context, uint32_t event) { + furi_assert(context); + WeatherStationApp* app = context; + return scene_manager_handle_custom_event(app->scene_manager, event); +} + +static bool weather_station_app_back_event_callback(void* context) { + furi_assert(context); + WeatherStationApp* app = context; + return scene_manager_handle_back_event(app->scene_manager); +} + +static void weather_station_app_tick_event_callback(void* context) { + furi_assert(context); + WeatherStationApp* app = context; + scene_manager_handle_tick_event(app->scene_manager); +} + +WeatherStationApp* weather_station_app_alloc() { + WeatherStationApp* app = malloc(sizeof(WeatherStationApp)); + + // GUI + app->gui = furi_record_open(RECORD_GUI); + + // View Dispatcher + app->view_dispatcher = view_dispatcher_alloc(); + app->scene_manager = scene_manager_alloc(&weather_station_scene_handlers, app); + view_dispatcher_enable_queue(app->view_dispatcher); + + view_dispatcher_set_event_callback_context(app->view_dispatcher, app); + view_dispatcher_set_custom_event_callback( + app->view_dispatcher, weather_station_app_custom_event_callback); + view_dispatcher_set_navigation_event_callback( + app->view_dispatcher, weather_station_app_back_event_callback); + view_dispatcher_set_tick_event_callback( + app->view_dispatcher, weather_station_app_tick_event_callback, 100); + + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + + // Open Notification record + app->notifications = furi_record_open(RECORD_NOTIFICATION); + + // Variable Item List + app->variable_item_list = variable_item_list_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + WeatherStationViewVariableItemList, + variable_item_list_get_view(app->variable_item_list)); + + // SubMenu + app->submenu = submenu_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, WeatherStationViewSubmenu, submenu_get_view(app->submenu)); + + // Widget + app->widget = widget_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, WeatherStationViewWidget, widget_get_view(app->widget)); + + // Receiver + app->ws_receiver = ws_view_receiver_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + WeatherStationViewReceiver, + ws_view_receiver_get_view(app->ws_receiver)); + + // Receiver Info + app->ws_receiver_info = ws_view_receiver_info_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + WeatherStationViewReceiverInfo, + ws_view_receiver_info_get_view(app->ws_receiver_info)); + + //init setting + app->setting = subghz_setting_alloc(); + + //ToDo FIX file name setting + subghz_setting_load(app->setting, EXT_PATH("subghz/assets/setting_user")); + + //init Worker & Protocol & History + app->lock = WSLockOff; + app->txrx = malloc(sizeof(WeatherStationTxRx)); + app->txrx->preset = malloc(sizeof(SubGhzRadioPreset)); + app->txrx->preset->name = furi_string_alloc(); + ws_preset_init(app, "AM650", subghz_setting_get_default_frequency(app->setting), NULL, 0); + + app->txrx->hopper_state = WSHopperStateOFF; + app->txrx->history = ws_history_alloc(); + app->txrx->worker = subghz_worker_alloc(); + app->txrx->environment = subghz_environment_alloc(); + subghz_environment_set_protocol_registry( + app->txrx->environment, (void*)&weather_station_protocol_registry); + app->txrx->receiver = subghz_receiver_alloc_init(app->txrx->environment); + + subghz_receiver_set_filter(app->txrx->receiver, SubGhzProtocolFlag_Decodable); + subghz_worker_set_overrun_callback( + app->txrx->worker, (SubGhzWorkerOverrunCallback)subghz_receiver_reset); + subghz_worker_set_pair_callback( + app->txrx->worker, (SubGhzWorkerPairCallback)subghz_receiver_decode); + subghz_worker_set_context(app->txrx->worker, app->txrx->receiver); + + furi_hal_power_suppress_charge_enter(); + + scene_manager_next_scene(app->scene_manager, WeatherStationSceneStart); + + return app; +} + +void weather_station_app_free(WeatherStationApp* app) { + furi_assert(app); + + //CC1101 off + ws_sleep(app); + + // Submenu + view_dispatcher_remove_view(app->view_dispatcher, WeatherStationViewSubmenu); + submenu_free(app->submenu); + + // Variable Item List + view_dispatcher_remove_view(app->view_dispatcher, WeatherStationViewVariableItemList); + variable_item_list_free(app->variable_item_list); + + // Widget + view_dispatcher_remove_view(app->view_dispatcher, WeatherStationViewWidget); + widget_free(app->widget); + + // Receiver + view_dispatcher_remove_view(app->view_dispatcher, WeatherStationViewReceiver); + ws_view_receiver_free(app->ws_receiver); + + // Receiver Info + view_dispatcher_remove_view(app->view_dispatcher, WeatherStationViewReceiverInfo); + ws_view_receiver_info_free(app->ws_receiver_info); + + //setting + subghz_setting_free(app->setting); + + //Worker & Protocol & History + subghz_receiver_free(app->txrx->receiver); + subghz_environment_free(app->txrx->environment); + ws_history_free(app->txrx->history); + subghz_worker_free(app->txrx->worker); + furi_string_free(app->txrx->preset->name); + free(app->txrx->preset); + free(app->txrx); + + // View dispatcher + view_dispatcher_free(app->view_dispatcher); + scene_manager_free(app->scene_manager); + + // Notifications + furi_record_close(RECORD_NOTIFICATION); + app->notifications = NULL; + + // Close records + furi_record_close(RECORD_GUI); + + furi_hal_power_suppress_charge_exit(); + + free(app); +} + +int32_t weather_station_app(void* p) { + UNUSED(p); + WeatherStationApp* weather_station_app = weather_station_app_alloc(); + + view_dispatcher_run(weather_station_app->view_dispatcher); + + weather_station_app_free(weather_station_app); + + return 0; +} diff --git a/applications/plugins/weather_station/weather_station_app_i.c b/applications/plugins/weather_station/weather_station_app_i.c new file mode 100644 index 00000000000..052bb853321 --- /dev/null +++ b/applications/plugins/weather_station/weather_station_app_i.c @@ -0,0 +1,159 @@ +#include "weather_station_app_i.h" + +#define TAG "WeatherStation" +#include + +void ws_preset_init( + void* context, + const char* preset_name, + uint32_t frequency, + uint8_t* preset_data, + size_t preset_data_size) { + furi_assert(context); + WeatherStationApp* app = context; + furi_string_set(app->txrx->preset->name, preset_name); + app->txrx->preset->frequency = frequency; + app->txrx->preset->data = preset_data; + app->txrx->preset->data_size = preset_data_size; +} + +bool ws_set_preset(WeatherStationApp* app, const char* preset) { + if(!strcmp(preset, "FuriHalSubGhzPresetOok270Async")) { + furi_string_set(app->txrx->preset->name, "AM270"); + } else if(!strcmp(preset, "FuriHalSubGhzPresetOok650Async")) { + furi_string_set(app->txrx->preset->name, "AM650"); + } else if(!strcmp(preset, "FuriHalSubGhzPreset2FSKDev238Async")) { + furi_string_set(app->txrx->preset->name, "FM238"); + } else if(!strcmp(preset, "FuriHalSubGhzPreset2FSKDev476Async")) { + furi_string_set(app->txrx->preset->name, "FM476"); + } else if(!strcmp(preset, "FuriHalSubGhzPresetCustom")) { + furi_string_set(app->txrx->preset->name, "CUSTOM"); + } else { + FURI_LOG_E(TAG, "Unknown preset"); + return false; + } + return true; +} + +void ws_get_frequency_modulation( + WeatherStationApp* app, + FuriString* frequency, + FuriString* modulation) { + furi_assert(app); + if(frequency != NULL) { + furi_string_printf( + frequency, + "%03ld.%02ld", + app->txrx->preset->frequency / 1000000 % 1000, + app->txrx->preset->frequency / 10000 % 100); + } + if(modulation != NULL) { + furi_string_printf(modulation, "%.2s", furi_string_get_cstr(app->txrx->preset->name)); + } +} + +void ws_begin(WeatherStationApp* app, uint8_t* preset_data) { + furi_assert(app); + UNUSED(preset_data); + furi_hal_subghz_reset(); + furi_hal_subghz_idle(); + furi_hal_subghz_load_custom_preset(preset_data); + furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow); + app->txrx->txrx_state = WSTxRxStateIDLE; +} + +uint32_t ws_rx(WeatherStationApp* app, uint32_t frequency) { + furi_assert(app); + if(!furi_hal_subghz_is_frequency_valid(frequency)) { + furi_crash("WeatherStation: Incorrect RX frequency."); + } + furi_assert( + app->txrx->txrx_state != WSTxRxStateRx && app->txrx->txrx_state != WSTxRxStateSleep); + + furi_hal_subghz_idle(); + uint32_t value = furi_hal_subghz_set_frequency_and_path(frequency); + furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow); + furi_hal_subghz_flush_rx(); + furi_hal_subghz_rx(); + + furi_hal_subghz_start_async_rx(subghz_worker_rx_callback, app->txrx->worker); + subghz_worker_start(app->txrx->worker); + app->txrx->txrx_state = WSTxRxStateRx; + return value; +} + +void ws_idle(WeatherStationApp* app) { + furi_assert(app); + furi_assert(app->txrx->txrx_state != WSTxRxStateSleep); + furi_hal_subghz_idle(); + app->txrx->txrx_state = WSTxRxStateIDLE; +} + +void ws_rx_end(WeatherStationApp* app) { + furi_assert(app); + furi_assert(app->txrx->txrx_state == WSTxRxStateRx); + if(subghz_worker_is_running(app->txrx->worker)) { + subghz_worker_stop(app->txrx->worker); + furi_hal_subghz_stop_async_rx(); + } + furi_hal_subghz_idle(); + app->txrx->txrx_state = WSTxRxStateIDLE; +} + +void ws_sleep(WeatherStationApp* app) { + furi_assert(app); + furi_hal_subghz_sleep(); + app->txrx->txrx_state = WSTxRxStateSleep; +} + +void ws_hopper_update(WeatherStationApp* app) { + furi_assert(app); + + switch(app->txrx->hopper_state) { + case WSHopperStateOFF: + return; + break; + case WSHopperStatePause: + return; + break; + case WSHopperStateRSSITimeOut: + if(app->txrx->hopper_timeout != 0) { + app->txrx->hopper_timeout--; + return; + } + break; + default: + break; + } + float rssi = -127.0f; + if(app->txrx->hopper_state != WSHopperStateRSSITimeOut) { + // See RSSI Calculation timings in CC1101 17.3 RSSI + rssi = furi_hal_subghz_get_rssi(); + + // Stay if RSSI is high enough + if(rssi > -90.0f) { + app->txrx->hopper_timeout = 10; + app->txrx->hopper_state = WSHopperStateRSSITimeOut; + return; + } + } else { + app->txrx->hopper_state = WSHopperStateRunnig; + } + // Select next frequency + if(app->txrx->hopper_idx_frequency < + subghz_setting_get_hopper_frequency_count(app->setting) - 1) { + app->txrx->hopper_idx_frequency++; + } else { + app->txrx->hopper_idx_frequency = 0; + } + + if(app->txrx->txrx_state == WSTxRxStateRx) { + ws_rx_end(app); + }; + if(app->txrx->txrx_state == WSTxRxStateIDLE) { + subghz_receiver_reset(app->txrx->receiver); + app->txrx->preset->frequency = + subghz_setting_get_hopper_frequency(app->setting, app->txrx->hopper_idx_frequency); + ws_rx(app, app->txrx->preset->frequency); + } +} diff --git a/applications/plugins/weather_station/weather_station_app_i.h b/applications/plugins/weather_station/weather_station_app_i.h new file mode 100644 index 00000000000..41e2481120d --- /dev/null +++ b/applications/plugins/weather_station/weather_station_app_i.h @@ -0,0 +1,73 @@ +#pragma once + +#include "helpers/weather_station_types.h" + +#include "scenes/weather_station_scene.h" +#include +#include +#include +#include +#include +#include +#include +#include "views/weather_station_receiver.h" +#include "views/weather_station_receiver_info.h" +#include "weather_station_history.h" + +#include +#include +#include +#include +#include + +typedef struct WeatherStationApp WeatherStationApp; + +struct WeatherStationTxRx { + SubGhzWorker* worker; + + SubGhzEnvironment* environment; + SubGhzReceiver* receiver; + SubGhzRadioPreset* preset; + WSHistory* history; + uint16_t idx_menu_chosen; + WSTxRxState txrx_state; + WSHopperState hopper_state; + uint8_t hopper_timeout; + uint8_t hopper_idx_frequency; + WSRxKeyState rx_key_state; +}; + +typedef struct WeatherStationTxRx WeatherStationTxRx; + +struct WeatherStationApp { + Gui* gui; + ViewDispatcher* view_dispatcher; + WeatherStationTxRx* txrx; + SceneManager* scene_manager; + NotificationApp* notifications; + VariableItemList* variable_item_list; + Submenu* submenu; + Widget* widget; + WSReceiver* ws_receiver; + WSReceiverInfo* ws_receiver_info; + WSLock lock; + SubGhzSetting* setting; +}; + +void ws_preset_init( + void* context, + const char* preset_name, + uint32_t frequency, + uint8_t* preset_data, + size_t preset_data_size); +bool ws_set_preset(WeatherStationApp* app, const char* preset); +void ws_get_frequency_modulation( + WeatherStationApp* app, + FuriString* frequency, + FuriString* modulation); +void ws_begin(WeatherStationApp* app, uint8_t* preset_data); +uint32_t ws_rx(WeatherStationApp* app, uint32_t frequency); +void ws_idle(WeatherStationApp* app); +void ws_rx_end(WeatherStationApp* app); +void ws_sleep(WeatherStationApp* app); +void ws_hopper_update(WeatherStationApp* app); diff --git a/applications/plugins/weather_station/weather_station_history.c b/applications/plugins/weather_station/weather_station_history.c new file mode 100644 index 00000000000..2eddf6cdccc --- /dev/null +++ b/applications/plugins/weather_station/weather_station_history.c @@ -0,0 +1,246 @@ +#include "weather_station_history.h" +#include +#include +#include + +#include + +#define WS_HISTORY_MAX 50 +#define TAG "WSHistory" + +typedef struct { + FuriString* item_str; + FlipperFormat* flipper_string; + uint8_t type; + uint32_t id; + SubGhzRadioPreset* preset; +} WSHistoryItem; + +ARRAY_DEF(WSHistoryItemArray, WSHistoryItem, M_POD_OPLIST) + +#define M_OPL_WSHistoryItemArray_t() ARRAY_OPLIST(WSHistoryItemArray, M_POD_OPLIST) + +typedef struct { + WSHistoryItemArray_t data; +} WSHistoryStruct; + +struct WSHistory { + uint32_t last_update_timestamp; + uint16_t last_index_write; + uint8_t code_last_hash_data; + FuriString* tmp_string; + WSHistoryStruct* history; +}; + +WSHistory* ws_history_alloc(void) { + WSHistory* instance = malloc(sizeof(WSHistory)); + instance->tmp_string = furi_string_alloc(); + instance->history = malloc(sizeof(WSHistoryStruct)); + WSHistoryItemArray_init(instance->history->data); + return instance; +} + +void ws_history_free(WSHistory* instance) { + furi_assert(instance); + furi_string_free(instance->tmp_string); + for + M_EACH(item, instance->history->data, WSHistoryItemArray_t) { + furi_string_free(item->item_str); + furi_string_free(item->preset->name); + free(item->preset); + flipper_format_free(item->flipper_string); + item->type = 0; + } + WSHistoryItemArray_clear(instance->history->data); + free(instance->history); + free(instance); +} + +uint32_t ws_history_get_frequency(WSHistory* instance, uint16_t idx) { + furi_assert(instance); + WSHistoryItem* item = WSHistoryItemArray_get(instance->history->data, idx); + return item->preset->frequency; +} + +SubGhzRadioPreset* ws_history_get_radio_preset(WSHistory* instance, uint16_t idx) { + furi_assert(instance); + WSHistoryItem* item = WSHistoryItemArray_get(instance->history->data, idx); + return item->preset; +} + +const char* ws_history_get_preset(WSHistory* instance, uint16_t idx) { + furi_assert(instance); + WSHistoryItem* item = WSHistoryItemArray_get(instance->history->data, idx); + return furi_string_get_cstr(item->preset->name); +} + +void ws_history_reset(WSHistory* instance) { + furi_assert(instance); + furi_string_reset(instance->tmp_string); + for + M_EACH(item, instance->history->data, WSHistoryItemArray_t) { + furi_string_free(item->item_str); + furi_string_free(item->preset->name); + free(item->preset); + flipper_format_free(item->flipper_string); + item->type = 0; + } + WSHistoryItemArray_reset(instance->history->data); + instance->last_index_write = 0; + instance->code_last_hash_data = 0; +} + +uint16_t ws_history_get_item(WSHistory* instance) { + furi_assert(instance); + return instance->last_index_write; +} + +uint8_t ws_history_get_type_protocol(WSHistory* instance, uint16_t idx) { + furi_assert(instance); + WSHistoryItem* item = WSHistoryItemArray_get(instance->history->data, idx); + return item->type; +} + +const char* ws_history_get_protocol_name(WSHistory* instance, uint16_t idx) { + furi_assert(instance); + WSHistoryItem* item = WSHistoryItemArray_get(instance->history->data, idx); + flipper_format_rewind(item->flipper_string); + if(!flipper_format_read_string(item->flipper_string, "Protocol", instance->tmp_string)) { + FURI_LOG_E(TAG, "Missing Protocol"); + furi_string_reset(instance->tmp_string); + } + return furi_string_get_cstr(instance->tmp_string); +} + +FlipperFormat* ws_history_get_raw_data(WSHistory* instance, uint16_t idx) { + furi_assert(instance); + WSHistoryItem* item = WSHistoryItemArray_get(instance->history->data, idx); + if(item->flipper_string) { + return item->flipper_string; + } else { + return NULL; + } +} +bool ws_history_get_text_space_left(WSHistory* instance, FuriString* output) { + furi_assert(instance); + if(instance->last_index_write == WS_HISTORY_MAX) { + if(output != NULL) furi_string_printf(output, "Memory is FULL"); + return true; + } + if(output != NULL) + furi_string_printf(output, "%02u/%02u", instance->last_index_write, WS_HISTORY_MAX); + return false; +} + +void ws_history_get_text_item_menu(WSHistory* instance, FuriString* output, uint16_t idx) { + WSHistoryItem* item = WSHistoryItemArray_get(instance->history->data, idx); + furi_string_set(output, item->item_str); +} + +WSHistoryStateAddKey + ws_history_add_to_history(WSHistory* instance, void* context, SubGhzRadioPreset* preset) { + furi_assert(instance); + furi_assert(context); + + if(instance->last_index_write >= WS_HISTORY_MAX) return WSHistoryStateAddKeyOverflow; + + SubGhzProtocolDecoderBase* decoder_base = context; + if((instance->code_last_hash_data == + subghz_protocol_decoder_base_get_hash_data(decoder_base)) && + ((furi_get_tick() - instance->last_update_timestamp) < 500)) { + instance->last_update_timestamp = furi_get_tick(); + return WSHistoryStateAddKeyTimeOut; + } + + instance->code_last_hash_data = subghz_protocol_decoder_base_get_hash_data(decoder_base); + instance->last_update_timestamp = furi_get_tick(); + + FlipperFormat* fff = flipper_format_string_alloc(); + uint32_t id = 0; + subghz_protocol_decoder_base_serialize(decoder_base, fff, preset); + + do { + if(!flipper_format_rewind(fff)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + if(!flipper_format_read_uint32(fff, "Id", (uint32_t*)&id, 1)) { + FURI_LOG_E(TAG, "Missing Id"); + break; + } + } while(false); + flipper_format_free(fff); + + //Update record if found + bool sensor_found = false; + for(size_t i = 0; i < WSHistoryItemArray_size(instance->history->data); i++) { + WSHistoryItem* item = WSHistoryItemArray_get(instance->history->data, i); + if(item->id == id) { + sensor_found = true; + Stream* flipper_string_stream = flipper_format_get_raw_stream(item->flipper_string); + stream_clean(flipper_string_stream); + subghz_protocol_decoder_base_serialize(decoder_base, item->flipper_string, preset); + return WSHistoryStateAddKeyUpdateData; + } + } + + // or add new record + if(!sensor_found) { + WSHistoryItem* item = WSHistoryItemArray_push_raw(instance->history->data); + item->preset = malloc(sizeof(SubGhzRadioPreset)); + item->type = decoder_base->protocol->type; + item->preset->frequency = preset->frequency; + item->preset->name = furi_string_alloc(); + furi_string_set(item->preset->name, preset->name); + item->preset->data = preset->data; + item->preset->data_size = preset->data_size; + item->id = id; + + item->item_str = furi_string_alloc(); + item->flipper_string = flipper_format_string_alloc(); + subghz_protocol_decoder_base_serialize(decoder_base, item->flipper_string, preset); + + do { + if(!flipper_format_rewind(item->flipper_string)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + if(!flipper_format_read_string( + item->flipper_string, "Protocol", instance->tmp_string)) { + FURI_LOG_E(TAG, "Missing Protocol"); + break; + } + + if(!flipper_format_rewind(item->flipper_string)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + uint8_t key_data[sizeof(uint64_t)] = {0}; + if(!flipper_format_read_hex(item->flipper_string, "Data", key_data, sizeof(uint64_t))) { + FURI_LOG_E(TAG, "Missing Data"); + break; + } + uint64_t data = 0; + for(uint8_t i = 0; i < sizeof(uint64_t); i++) { + data = (data << 8) | key_data[i]; + } + if(!(uint32_t)(data >> 32)) { + furi_string_printf( + item->item_str, + "%s %lX", + furi_string_get_cstr(instance->tmp_string), + (uint32_t)(data & 0xFFFFFFFF)); + } else { + furi_string_printf( + item->item_str, + "%s %lX%08lX", + furi_string_get_cstr(instance->tmp_string), + (uint32_t)(data >> 32), + (uint32_t)(data & 0xFFFFFFFF)); + } + } while(false); + instance->last_index_write++; + return WSHistoryStateAddKeyNewDada; + } + return WSHistoryStateAddKeyUnknown; +} diff --git a/applications/plugins/weather_station/weather_station_history.h b/applications/plugins/weather_station/weather_station_history.h new file mode 100644 index 00000000000..11601fe79ec --- /dev/null +++ b/applications/plugins/weather_station/weather_station_history.h @@ -0,0 +1,112 @@ + +#pragma once + +#include +#include +#include +#include +#include + +typedef struct WSHistory WSHistory; + +/** History state add key */ +typedef enum { + WSHistoryStateAddKeyUnknown, + WSHistoryStateAddKeyTimeOut, + WSHistoryStateAddKeyNewDada, + WSHistoryStateAddKeyUpdateData, + WSHistoryStateAddKeyOverflow, +} WSHistoryStateAddKey; + +/** Allocate WSHistory + * + * @return WSHistory* + */ +WSHistory* ws_history_alloc(void); + +/** Free WSHistory + * + * @param instance - WSHistory instance + */ +void ws_history_free(WSHistory* instance); + +/** Clear history + * + * @param instance - WSHistory instance + */ +void ws_history_reset(WSHistory* instance); + +/** Get frequency to history[idx] + * + * @param instance - WSHistory instance + * @param idx - record index + * @return frequency - frequency Hz + */ +uint32_t ws_history_get_frequency(WSHistory* instance, uint16_t idx); + +SubGhzRadioPreset* ws_history_get_radio_preset(WSHistory* instance, uint16_t idx); + +/** Get preset to history[idx] + * + * @param instance - WSHistory instance + * @param idx - record index + * @return preset - preset name + */ +const char* ws_history_get_preset(WSHistory* instance, uint16_t idx); + +/** Get history index write + * + * @param instance - WSHistory instance + * @return idx - current record index + */ +uint16_t ws_history_get_item(WSHistory* instance); + +/** Get type protocol to history[idx] + * + * @param instance - WSHistory instance + * @param idx - record index + * @return type - type protocol + */ +uint8_t ws_history_get_type_protocol(WSHistory* instance, uint16_t idx); + +/** Get name protocol to history[idx] + * + * @param instance - WSHistory instance + * @param idx - record index + * @return name - const char* name protocol + */ +const char* ws_history_get_protocol_name(WSHistory* instance, uint16_t idx); + +/** Get string item menu to history[idx] + * + * @param instance - WSHistory instance + * @param output - FuriString* output + * @param idx - record index + */ +void ws_history_get_text_item_menu(WSHistory* instance, FuriString* output, uint16_t idx); + +/** Get string the remaining number of records to history + * + * @param instance - WSHistory instance + * @param output - FuriString* output + * @return bool - is FUUL + */ +bool ws_history_get_text_space_left(WSHistory* instance, FuriString* output); + +/** Add protocol to history + * + * @param instance - WSHistory instance + * @param context - SubGhzProtocolCommon context + * @param preset - SubGhzRadioPreset preset + * @return WSHistoryStateAddKey; + */ +WSHistoryStateAddKey + ws_history_add_to_history(WSHistory* instance, void* context, SubGhzRadioPreset* preset); + +/** Get SubGhzProtocolCommonLoad to load into the protocol decoder bin data + * + * @param instance - WSHistory instance + * @param idx - record index + * @return SubGhzProtocolCommonLoad* + */ +FlipperFormat* ws_history_get_raw_data(WSHistory* instance, uint16_t idx); diff --git a/assets/resources/subghz/assets/keeloq_mfcodes_user b/assets/resources/subghz/assets/keeloq_mfcodes_user.example similarity index 80% rename from assets/resources/subghz/assets/keeloq_mfcodes_user rename to assets/resources/subghz/assets/keeloq_mfcodes_user.example index f013afa2db4..0d43c5591e7 100644 --- a/assets/resources/subghz/assets/keeloq_mfcodes_user +++ b/assets/resources/subghz/assets/keeloq_mfcodes_user.example @@ -1,3 +1,4 @@ +# to use manual settings and prevent them from being deleted on upgrade, rename *_user.example files to *_user # for adding manufacture keys # AABBCCDDEEFFAABB:X:NAME\r\n # AABBCCDDEEFFAABB - man 64 bit diff --git a/assets/resources/subghz/assets/setting_user b/assets/resources/subghz/assets/setting_user.example similarity index 91% rename from assets/resources/subghz/assets/setting_user rename to assets/resources/subghz/assets/setting_user.example index 1f37a2eb5de..a0cbb9a0761 100644 --- a/assets/resources/subghz/assets/setting_user +++ b/assets/resources/subghz/assets/setting_user.example @@ -1,3 +1,4 @@ +# to use manual settings and prevent them from being deleted on upgrade, rename *_user.example files to *_user Filetype: Flipper SubGhz Setting File Version: 1 diff --git a/assets/unit_tests/subghz/magellen.sub b/assets/unit_tests/subghz/magellan.sub similarity index 88% rename from assets/unit_tests/subghz/magellen.sub rename to assets/unit_tests/subghz/magellan.sub index 3317fd4bce9..11684803dff 100644 --- a/assets/unit_tests/subghz/magellen.sub +++ b/assets/unit_tests/subghz/magellan.sub @@ -2,6 +2,6 @@ Filetype: Flipper SubGhz Key File Version: 1 Frequency: 433920000 Preset: FuriHalSubGhzPresetOok650Async -Protocol: Magellen +Protocol: Magellan Bit: 32 Key: 00 00 00 00 37 AE 48 28 diff --git a/assets/unit_tests/subghz/magellen_raw.sub b/assets/unit_tests/subghz/magellan_raw.sub similarity index 100% rename from assets/unit_tests/subghz/magellen_raw.sub rename to assets/unit_tests/subghz/magellan_raw.sub diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index d3d5a90104d..b65de9a946c 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,2.4,, +Version,+,3.3,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -124,9 +124,15 @@ Header,+,lib/one_wire/one_wire_host.h,, Header,+,lib/one_wire/one_wire_host_timing.h,, Header,+,lib/one_wire/one_wire_slave.h,, Header,+,lib/print/wrappers.h,, +Header,+,lib/subghz/blocks/const.h,, +Header,+,lib/subghz/blocks/decoder.h,, +Header,+,lib/subghz/blocks/encoder.h,, +Header,+,lib/subghz/blocks/generic.h,, +Header,+,lib/subghz/blocks/math.h,, Header,+,lib/subghz/environment.h,, Header,+,lib/subghz/protocols/raw.h,, Header,+,lib/subghz/receiver.h,, +Header,+,lib/subghz/subghz_setting.h,, Header,+,lib/subghz/subghz_tx_rx_worker.h,, Header,+,lib/subghz/subghz_worker.h,, Header,+,lib/subghz/transmitter.h,, @@ -2240,14 +2246,20 @@ Function,-,strupr,char*,char* Function,-,strverscmp,int,"const char*, const char*" Function,-,strxfrm,size_t,"char*, const char*, size_t" Function,-,strxfrm_l,size_t,"char*, const char*, size_t, locale_t" +Function,+,subghz_block_generic_deserialize,_Bool,"SubGhzBlockGeneric*, FlipperFormat*" +Function,+,subghz_block_generic_get_preset_name,void,"const char*, FuriString*" +Function,+,subghz_block_generic_serialize,_Bool,"SubGhzBlockGeneric*, FlipperFormat*, SubGhzRadioPreset*" Function,+,subghz_environment_alloc,SubGhzEnvironment*, Function,+,subghz_environment_free,void,SubGhzEnvironment* -Function,-,subghz_environment_get_came_atomo_rainbow_table_file_name,const char*,SubGhzEnvironment* -Function,-,subghz_environment_get_keystore,SubGhzKeystore*,SubGhzEnvironment* -Function,-,subghz_environment_get_nice_flor_s_rainbow_table_file_name,const char*,SubGhzEnvironment* +Function,+,subghz_environment_get_came_atomo_rainbow_table_file_name,const char*,SubGhzEnvironment* +Function,+,subghz_environment_get_keystore,SubGhzKeystore*,SubGhzEnvironment* +Function,+,subghz_environment_get_nice_flor_s_rainbow_table_file_name,const char*,SubGhzEnvironment* +Function,+,subghz_environment_get_protocol_name_registry,const char*,"SubGhzEnvironment*, size_t" +Function,+,subghz_environment_get_protocol_registry,void*,SubGhzEnvironment* Function,+,subghz_environment_load_keystore,_Bool,"SubGhzEnvironment*, const char*" -Function,-,subghz_environment_set_came_atomo_rainbow_table_file_name,void,"SubGhzEnvironment*, const char*" -Function,-,subghz_environment_set_nice_flor_s_rainbow_table_file_name,void,"SubGhzEnvironment*, const char*" +Function,+,subghz_environment_set_came_atomo_rainbow_table_file_name,void,"SubGhzEnvironment*, const char*" +Function,+,subghz_environment_set_nice_flor_s_rainbow_table_file_name,void,"SubGhzEnvironment*, const char*" +Function,+,subghz_environment_set_protocol_registry,void,"SubGhzEnvironment*, void*" Function,-,subghz_keystore_alloc,SubGhzKeystore*, Function,-,subghz_keystore_free,void,SubGhzKeystore* Function,-,subghz_keystore_get_data,SubGhzKeyArray_t*,SubGhzKeystore* @@ -2255,10 +2267,20 @@ Function,-,subghz_keystore_load,_Bool,"SubGhzKeystore*, const char*" Function,-,subghz_keystore_raw_encrypted_save,_Bool,"const char*, const char*, uint8_t*" Function,-,subghz_keystore_raw_get_data,_Bool,"const char*, size_t, uint8_t*, size_t" Function,-,subghz_keystore_save,_Bool,"SubGhzKeystore*, const char*, uint8_t*" +Function,+,subghz_protocol_blocks_add_bit,void,"SubGhzBlockDecoder*, uint8_t" +Function,+,subghz_protocol_blocks_crc4,uint8_t,"const uint8_t[], unsigned, uint8_t, uint8_t" +Function,+,subghz_protocol_blocks_crc7,uint8_t,"const uint8_t[], unsigned, uint8_t, uint8_t" +Function,+,subghz_protocol_blocks_crc8,uint8_t,"const uint8_t[], unsigned, uint8_t, uint8_t" +Function,+,subghz_protocol_blocks_get_bit_array,_Bool,"uint8_t[], size_t" +Function,+,subghz_protocol_blocks_get_hash_data,uint8_t,"SubGhzBlockDecoder*, size_t" +Function,+,subghz_protocol_blocks_get_parity,uint8_t,"uint64_t, uint8_t" +Function,+,subghz_protocol_blocks_get_upload,size_t,"uint8_t[], size_t, LevelDuration*, size_t, uint32_t" +Function,+,subghz_protocol_blocks_reverse_key,uint64_t,"uint64_t, uint8_t" +Function,+,subghz_protocol_blocks_set_bit_array,void,"_Bool, uint8_t[], size_t, size_t" Function,-,subghz_protocol_decoder_base_deserialize,_Bool,"SubGhzProtocolDecoderBase*, FlipperFormat*" -Function,-,subghz_protocol_decoder_base_get_hash_data,uint8_t,SubGhzProtocolDecoderBase* -Function,-,subghz_protocol_decoder_base_get_string,_Bool,"SubGhzProtocolDecoderBase*, FuriString*" -Function,+,subghz_protocol_decoder_base_serialize,_Bool,"SubGhzProtocolDecoderBase*, FlipperFormat*, SubGhzPresetDefinition*" +Function,+,subghz_protocol_decoder_base_get_hash_data,uint8_t,SubGhzProtocolDecoderBase* +Function,+,subghz_protocol_decoder_base_get_string,_Bool,"SubGhzProtocolDecoderBase*, FuriString*" +Function,+,subghz_protocol_decoder_base_serialize,_Bool,"SubGhzProtocolDecoderBase*, FlipperFormat*, SubGhzRadioPreset*" Function,-,subghz_protocol_decoder_base_set_decoder_callback,void,"SubGhzProtocolDecoderBase*, SubGhzProtocolDecoderBaseRxCallback, void*" Function,+,subghz_protocol_decoder_raw_alloc,void*,SubGhzEnvironment* Function,+,subghz_protocol_decoder_raw_deserialize,_Bool,"void*, FlipperFormat*" @@ -2274,7 +2296,7 @@ Function,+,subghz_protocol_encoder_raw_yield,LevelDuration,void* Function,+,subghz_protocol_raw_file_encoder_worker_set_callback_end,void,"SubGhzProtocolEncoderRAW*, SubGhzProtocolEncoderRAWCallbackEnd, void*" Function,+,subghz_protocol_raw_gen_fff_data,void,"FlipperFormat*, const char*" Function,+,subghz_protocol_raw_get_sample_write,size_t,SubGhzProtocolDecoderRAW* -Function,+,subghz_protocol_raw_save_to_file_init,_Bool,"SubGhzProtocolDecoderRAW*, const char*, SubGhzPresetDefinition*" +Function,+,subghz_protocol_raw_save_to_file_init,_Bool,"SubGhzProtocolDecoderRAW*, const char*, SubGhzRadioPreset*" Function,+,subghz_protocol_raw_save_to_file_stop,void,SubGhzProtocolDecoderRAW* Function,+,subghz_receiver_alloc_init,SubGhzReceiver*,SubGhzEnvironment* Function,+,subghz_receiver_decode,void,"SubGhzReceiver*, _Bool, uint32_t" @@ -2283,6 +2305,23 @@ Function,+,subghz_receiver_reset,void,SubGhzReceiver* Function,+,subghz_receiver_search_decoder_base_by_name,SubGhzProtocolDecoderBase*,"SubGhzReceiver*, const char*" Function,+,subghz_receiver_set_filter,void,"SubGhzReceiver*, SubGhzProtocolFlag" Function,+,subghz_receiver_set_rx_callback,void,"SubGhzReceiver*, SubGhzReceiverCallback, void*" +Function,+,subghz_setting_alloc,SubGhzSetting*, +Function,+,subghz_setting_delete_custom_preset,_Bool,"SubGhzSetting*, const char*" +Function,+,subghz_setting_free,void,SubGhzSetting* +Function,+,subghz_setting_get_default_frequency,uint32_t,SubGhzSetting* +Function,+,subghz_setting_get_frequency,uint32_t,"SubGhzSetting*, size_t" +Function,+,subghz_setting_get_frequency_count,size_t,SubGhzSetting* +Function,+,subghz_setting_get_frequency_default_index,uint32_t,SubGhzSetting* +Function,+,subghz_setting_get_hopper_frequency,uint32_t,"SubGhzSetting*, size_t" +Function,+,subghz_setting_get_hopper_frequency_count,size_t,SubGhzSetting* +Function,+,subghz_setting_get_inx_preset_by_name,int,"SubGhzSetting*, const char*" +Function,+,subghz_setting_get_preset_count,size_t,SubGhzSetting* +Function,+,subghz_setting_get_preset_data,uint8_t*,"SubGhzSetting*, size_t" +Function,+,subghz_setting_get_preset_data_by_name,uint8_t*,"SubGhzSetting*, const char*" +Function,+,subghz_setting_get_preset_data_size,size_t,"SubGhzSetting*, size_t" +Function,+,subghz_setting_get_preset_name,const char*,"SubGhzSetting*, size_t" +Function,+,subghz_setting_load,void,"SubGhzSetting*, const char*" +Function,+,subghz_setting_load_custom_preset,_Bool,"SubGhzSetting*, const char*, FlipperFormat*" Function,+,subghz_transmitter_alloc_init,SubGhzTransmitter*,"SubGhzEnvironment*, const char*" Function,+,subghz_transmitter_deserialize,_Bool,"SubGhzTransmitter*, FlipperFormat*" Function,+,subghz_transmitter_free,void,SubGhzTransmitter* diff --git a/lib/subghz/SConscript b/lib/subghz/SConscript index eff2f168eac..e25d122c81f 100644 --- a/lib/subghz/SConscript +++ b/lib/subghz/SConscript @@ -11,6 +11,12 @@ env.Append( File("#/lib/subghz/subghz_tx_rx_worker.h"), File("#/lib/subghz/transmitter.h"), File("#/lib/subghz/protocols/raw.h"), + File("#/lib/subghz/blocks/const.h"), + File("#/lib/subghz/blocks/decoder.h"), + File("#/lib/subghz/blocks/encoder.h"), + File("#/lib/subghz/blocks/generic.h"), + File("#/lib/subghz/blocks/math.h"), + File("#/lib/subghz/subghz_setting.h"), ], ) diff --git a/lib/subghz/blocks/const.h b/lib/subghz/blocks/const.h index 57b47d50349..f32334e2f44 100644 --- a/lib/subghz/blocks/const.h +++ b/lib/subghz/blocks/const.h @@ -4,9 +4,17 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + typedef struct { const uint16_t te_long; const uint16_t te_short; const uint16_t te_delta; const uint8_t min_count_bit_for_found; } SubGhzBlockConst; + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/subghz/blocks/decoder.h b/lib/subghz/blocks/decoder.h index 339e27c15ef..25549fab3c1 100644 --- a/lib/subghz/blocks/decoder.h +++ b/lib/subghz/blocks/decoder.h @@ -4,6 +4,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + typedef struct SubGhzBlockDecoder SubGhzBlockDecoder; struct SubGhzBlockDecoder { @@ -26,3 +30,7 @@ void subghz_protocol_blocks_add_bit(SubGhzBlockDecoder* decoder, uint8_t bit); * @return hash Hash sum */ uint8_t subghz_protocol_blocks_get_hash_data(SubGhzBlockDecoder* decoder, size_t len); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/subghz/blocks/encoder.h b/lib/subghz/blocks/encoder.h index 6ad734cbdd2..1ff0777261d 100644 --- a/lib/subghz/blocks/encoder.h +++ b/lib/subghz/blocks/encoder.h @@ -6,6 +6,10 @@ #include +#ifdef __cplusplus +extern "C" { +#endif + typedef struct { bool is_running; size_t repeat; @@ -50,3 +54,7 @@ size_t subghz_protocol_blocks_get_upload( LevelDuration* upload, size_t max_size_upload, uint32_t duration_bit); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/subghz/blocks/generic.c b/lib/subghz/blocks/generic.c index 7496aea3dcc..1bad5f0a36c 100644 --- a/lib/subghz/blocks/generic.c +++ b/lib/subghz/blocks/generic.c @@ -23,7 +23,7 @@ void subghz_block_generic_get_preset_name(const char* preset_name, FuriString* p bool subghz_block_generic_serialize( SubGhzBlockGeneric* instance, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset) { + SubGhzRadioPreset* preset) { furi_assert(instance); bool res = false; FuriString* temp_str; diff --git a/lib/subghz/blocks/generic.h b/lib/subghz/blocks/generic.h index 300807daadb..1448f0ea622 100644 --- a/lib/subghz/blocks/generic.h +++ b/lib/subghz/blocks/generic.h @@ -9,6 +9,10 @@ #include "furi_hal.h" #include "../types.h" +#ifdef __cplusplus +extern "C" { +#endif + typedef struct SubGhzBlockGeneric SubGhzBlockGeneric; struct SubGhzBlockGeneric { @@ -31,13 +35,13 @@ void subghz_block_generic_get_preset_name(const char* preset_name, FuriString* p * Serialize data SubGhzBlockGeneric. * @param instance Pointer to a SubGhzBlockGeneric instance * @param flipper_format Pointer to a FlipperFormat instance - * @param preset The modulation on which the signal was received, SubGhzPresetDefinition + * @param preset The modulation on which the signal was received, SubGhzRadioPreset * @return true On success */ bool subghz_block_generic_serialize( SubGhzBlockGeneric* instance, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset); + SubGhzRadioPreset* preset); /** * Deserialize data SubGhzBlockGeneric. @@ -46,3 +50,7 @@ bool subghz_block_generic_serialize( * @return true On success */ bool subghz_block_generic_deserialize(SubGhzBlockGeneric* instance, FlipperFormat* flipper_format); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/subghz/blocks/math.c b/lib/subghz/blocks/math.c index fca50c8f8df..782076e3e48 100644 --- a/lib/subghz/blocks/math.c +++ b/lib/subghz/blocks/math.c @@ -14,4 +14,69 @@ uint8_t subghz_protocol_blocks_get_parity(uint64_t key, uint8_t count_bit) { parity += bit_read(key, i); } return parity & 0x01; +} + +uint8_t subghz_protocol_blocks_crc4( + uint8_t const message[], + unsigned nBytes, + uint8_t polynomial, + uint8_t init) { + unsigned remainder = init << 4; // LSBs are unused + unsigned poly = polynomial << 4; + unsigned bit; + + while(nBytes--) { + remainder ^= *message++; + for(bit = 0; bit < 8; bit++) { + if(remainder & 0x80) { + remainder = (remainder << 1) ^ poly; + } else { + remainder = (remainder << 1); + } + } + } + return remainder >> 4 & 0x0f; // discard the LSBs +} + +uint8_t subghz_protocol_blocks_crc7( + uint8_t const message[], + unsigned nBytes, + uint8_t polynomial, + uint8_t init) { + unsigned remainder = init << 1; // LSB is unused + unsigned poly = polynomial << 1; + unsigned byte, bit; + + for(byte = 0; byte < nBytes; ++byte) { + remainder ^= message[byte]; + for(bit = 0; bit < 8; ++bit) { + if(remainder & 0x80) { + remainder = (remainder << 1) ^ poly; + } else { + remainder = (remainder << 1); + } + } + } + return remainder >> 1 & 0x7f; // discard the LSB +} + +uint8_t subghz_protocol_blocks_crc8( + uint8_t const message[], + unsigned nBytes, + uint8_t polynomial, + uint8_t init) { + uint8_t remainder = init; + unsigned byte, bit; + + for(byte = 0; byte < nBytes; ++byte) { + remainder ^= message[byte]; + for(bit = 0; bit < 8; ++bit) { + if(remainder & 0x80) { + remainder = (remainder << 1) ^ polynomial; + } else { + remainder = (remainder << 1); + } + } + } + return remainder; } \ No newline at end of file diff --git a/lib/subghz/blocks/math.h b/lib/subghz/blocks/math.h index 85b146ebc7e..c312b607ae5 100644 --- a/lib/subghz/blocks/math.h +++ b/lib/subghz/blocks/math.h @@ -9,7 +9,11 @@ #define bit_clear(value, bit) ((value) &= ~(1UL << (bit))) #define bit_write(value, bit, bitvalue) (bitvalue ? bit_set(value, bit) : bit_clear(value, bit)) #define DURATION_DIFF(x, y) ((x < y) ? (y - x) : (x - y)) +#define abs(x) ((x) > 0 ? (x) : -(x)) +#ifdef __cplusplus +extern "C" { +#endif /** * Flip the data bitwise. * @param key In data @@ -25,3 +29,51 @@ uint64_t subghz_protocol_blocks_reverse_key(uint64_t key, uint8_t count_bit); * @return parity */ uint8_t subghz_protocol_blocks_get_parity(uint64_t key, uint8_t count_bit); + +/** + * CRC-4. + * @param message array of bytes to check + * @param nBytes number of bytes in message + * @param polynomial CRC polynomial + * @param init starting crc value + * @return CRC value + */ +uint8_t subghz_protocol_blocks_crc4( + uint8_t const message[], + unsigned nBytes, + uint8_t polynomial, + uint8_t init); + +/** + * CRC-7. + * @param message array of bytes to check + * @param nBytes number of bytes in message + * @param polynomial CRC polynomial + * @param init starting crc value + * @return CRC value + */ +uint8_t subghz_protocol_blocks_crc7( + uint8_t const message[], + unsigned nBytes, + uint8_t polynomial, + uint8_t init); + +/** + * Generic Cyclic Redundancy Check CRC-8. + * Example polynomial: 0x31 = x8 + x5 + x4 + 1 (x8 is implicit) + * Example polynomial: 0x80 = x8 + x7 (a normal bit-by-bit parity XOR) + * @param message array of bytes to check + * @param nBytes number of bytes in message + * @param polynomial byte is from x^7 to x^0 (x^8 is implicitly one) + * @param init starting crc value + * @return CRC value + */ +uint8_t subghz_protocol_blocks_crc8( + uint8_t const message[], + unsigned nBytes, + uint8_t polynomial, + uint8_t init); + +#ifdef __cplusplus +} +#endif diff --git a/lib/subghz/environment.c b/lib/subghz/environment.c index 46d0d368c5b..0a4b7b60682 100644 --- a/lib/subghz/environment.c +++ b/lib/subghz/environment.c @@ -1,7 +1,9 @@ #include "environment.h" +#include "registry.h" struct SubGhzEnvironment { SubGhzKeystore* keystore; + const SubGhzProtocolRegistry* protocol_registry; const char* came_atomo_rainbow_table_file_name; const char* nice_flor_s_rainbow_table_file_name; }; @@ -10,6 +12,7 @@ SubGhzEnvironment* subghz_environment_alloc() { SubGhzEnvironment* instance = malloc(sizeof(SubGhzEnvironment)); instance->keystore = subghz_keystore_alloc(); + instance->protocol_registry = NULL; instance->came_atomo_rainbow_table_file_name = NULL; instance->nice_flor_s_rainbow_table_file_name = NULL; @@ -19,6 +22,9 @@ SubGhzEnvironment* subghz_environment_alloc() { void subghz_environment_free(SubGhzEnvironment* instance) { furi_assert(instance); + instance->protocol_registry = NULL; + instance->came_atomo_rainbow_table_file_name = NULL; + instance->nice_flor_s_rainbow_table_file_name = NULL; subghz_keystore_free(instance->keystore); free(instance); @@ -65,3 +71,30 @@ const char* return instance->nice_flor_s_rainbow_table_file_name; } + +void subghz_environment_set_protocol_registry( + SubGhzEnvironment* instance, + void* protocol_registry_items) { + furi_assert(instance); + const SubGhzProtocolRegistry* protocol_registry = protocol_registry_items; + instance->protocol_registry = protocol_registry; +} + +void* subghz_environment_get_protocol_registry(SubGhzEnvironment* instance) { + furi_assert(instance); + furi_assert(instance->protocol_registry); + return (void*)instance->protocol_registry; +} + +const char* + subghz_environment_get_protocol_name_registry(SubGhzEnvironment* instance, size_t idx) { + furi_assert(instance); + furi_assert(instance->protocol_registry); + const SubGhzProtocol* protocol = + subghz_protocol_registry_get_by_index(instance->protocol_registry, idx); + if(protocol != NULL) { + return protocol->name; + } else { + return NULL; + } +} \ No newline at end of file diff --git a/lib/subghz/environment.h b/lib/subghz/environment.h index d4678e41349..5f8fcf1f546 100644 --- a/lib/subghz/environment.h +++ b/lib/subghz/environment.h @@ -69,6 +69,30 @@ void subghz_environment_set_nice_flor_s_rainbow_table_file_name( const char* subghz_environment_get_nice_flor_s_rainbow_table_file_name(SubGhzEnvironment* instance); +/** + * Set list of protocols to work. + * @param instance Pointer to a SubGhzEnvironment instance + * @param protocol_registry_items Pointer to a SubGhzProtocolRegistry + */ +void subghz_environment_set_protocol_registry( + SubGhzEnvironment* instance, + void* protocol_registry_items); + +/** + * Get list of protocols to work. + * @param instance Pointer to a SubGhzEnvironment instance + * @return Pointer to a SubGhzProtocolRegistry + */ +void* subghz_environment_get_protocol_registry(SubGhzEnvironment* instance); + +/** + * Get list of protocols names. + * @param instance Pointer to a SubGhzEnvironment instance + * @param idx index protocols + * @return Pointer to a SubGhzProtocolRegistry + */ +const char* subghz_environment_get_protocol_name_registry(SubGhzEnvironment* instance, size_t idx); + #ifdef __cplusplus } #endif diff --git a/lib/subghz/protocols/base.c b/lib/subghz/protocols/base.c index 4ee7a3f8ac0..36f33b9a5c6 100644 --- a/lib/subghz/protocols/base.c +++ b/lib/subghz/protocols/base.c @@ -26,7 +26,7 @@ bool subghz_protocol_decoder_base_get_string( bool subghz_protocol_decoder_base_serialize( SubGhzProtocolDecoderBase* decoder_base, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset) { + SubGhzRadioPreset* preset) { bool status = false; if(decoder_base->protocol && decoder_base->protocol->decoder && diff --git a/lib/subghz/protocols/base.h b/lib/subghz/protocols/base.h index 47b4e482b45..1f3d3e1bece 100644 --- a/lib/subghz/protocols/base.h +++ b/lib/subghz/protocols/base.h @@ -48,13 +48,13 @@ bool subghz_protocol_decoder_base_get_string( * Serialize data SubGhzProtocolDecoderBase. * @param decoder_base Pointer to a SubGhzProtocolDecoderBase instance * @param flipper_format Pointer to a FlipperFormat instance - * @param preset The modulation on which the signal was received, SubGhzPresetDefinition + * @param preset The modulation on which the signal was received, SubGhzRadioPreset * @return true On success */ bool subghz_protocol_decoder_base_serialize( SubGhzProtocolDecoderBase* decoder_base, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset); + SubGhzRadioPreset* preset); /** * Deserialize data SubGhzProtocolDecoderBase. diff --git a/lib/subghz/protocols/bett.c b/lib/subghz/protocols/bett.c index 605a922c602..2dd39af9e16 100644 --- a/lib/subghz/protocols/bett.c +++ b/lib/subghz/protocols/bett.c @@ -299,7 +299,7 @@ uint8_t subghz_protocol_decoder_bett_get_hash_data(void* context) { bool subghz_protocol_decoder_bett_serialize( void* context, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset) { + SubGhzRadioPreset* preset) { furi_assert(context); SubGhzProtocolDecoderBETT* instance = context; return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); diff --git a/lib/subghz/protocols/bett.h b/lib/subghz/protocols/bett.h index 04bf46b56b8..c0ce0b7f42a 100644 --- a/lib/subghz/protocols/bett.h +++ b/lib/subghz/protocols/bett.h @@ -83,13 +83,13 @@ uint8_t subghz_protocol_decoder_bett_get_hash_data(void* context); * Serialize data SubGhzProtocolDecoderBETT. * @param context Pointer to a SubGhzProtocolDecoderBETT instance * @param flipper_format Pointer to a FlipperFormat instance - * @param preset The modulation on which the signal was received, SubGhzPresetDefinition + * @param preset The modulation on which the signal was received, SubGhzRadioPreset * @return true On success */ bool subghz_protocol_decoder_bett_serialize( void* context, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset); + SubGhzRadioPreset* preset); /** * Deserialize data SubGhzProtocolDecoderBETT. diff --git a/lib/subghz/protocols/came.c b/lib/subghz/protocols/came.c index 3fc61bf60ea..1ac4ec053b3 100644 --- a/lib/subghz/protocols/came.c +++ b/lib/subghz/protocols/came.c @@ -295,7 +295,7 @@ uint8_t subghz_protocol_decoder_came_get_hash_data(void* context) { bool subghz_protocol_decoder_came_serialize( void* context, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset) { + SubGhzRadioPreset* preset) { furi_assert(context); SubGhzProtocolDecoderCame* instance = context; return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); diff --git a/lib/subghz/protocols/came.h b/lib/subghz/protocols/came.h index d1ac7628667..253c93aaea5 100644 --- a/lib/subghz/protocols/came.h +++ b/lib/subghz/protocols/came.h @@ -83,13 +83,13 @@ uint8_t subghz_protocol_decoder_came_get_hash_data(void* context); * Serialize data SubGhzProtocolDecoderCame. * @param context Pointer to a SubGhzProtocolDecoderCame instance * @param flipper_format Pointer to a FlipperFormat instance - * @param preset The modulation on which the signal was received, SubGhzPresetDefinition + * @param preset The modulation on which the signal was received, SubGhzRadioPreset * @return true On success */ bool subghz_protocol_decoder_came_serialize( void* context, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset); + SubGhzRadioPreset* preset); /** * Deserialize data SubGhzProtocolDecoderCame. diff --git a/lib/subghz/protocols/came_atomo.c b/lib/subghz/protocols/came_atomo.c index 8c2a542c2c3..3f6045beaf7 100644 --- a/lib/subghz/protocols/came_atomo.c +++ b/lib/subghz/protocols/came_atomo.c @@ -301,7 +301,7 @@ uint8_t subghz_protocol_decoder_came_atomo_get_hash_data(void* context) { bool subghz_protocol_decoder_came_atomo_serialize( void* context, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset) { + SubGhzRadioPreset* preset) { furi_assert(context); SubGhzProtocolDecoderCameAtomo* instance = context; return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); diff --git a/lib/subghz/protocols/came_atomo.h b/lib/subghz/protocols/came_atomo.h index aa1cffd051a..3781a0d8c23 100644 --- a/lib/subghz/protocols/came_atomo.h +++ b/lib/subghz/protocols/came_atomo.h @@ -48,13 +48,13 @@ uint8_t subghz_protocol_decoder_came_atomo_get_hash_data(void* context); * Serialize data SubGhzProtocolDecoderCameAtomo. * @param context Pointer to a SubGhzProtocolDecoderCameAtomo instance * @param flipper_format Pointer to a FlipperFormat instance - * @param preset The modulation on which the signal was received, SubGhzPresetDefinition + * @param preset The modulation on which the signal was received, SubGhzRadioPreset * @return true On success */ bool subghz_protocol_decoder_came_atomo_serialize( void* context, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset); + SubGhzRadioPreset* preset); /** * Deserialize data SubGhzProtocolDecoderCameAtomo. diff --git a/lib/subghz/protocols/came_twee.c b/lib/subghz/protocols/came_twee.c index 3d5029ec979..e7eb33c4285 100644 --- a/lib/subghz/protocols/came_twee.c +++ b/lib/subghz/protocols/came_twee.c @@ -422,7 +422,7 @@ uint8_t subghz_protocol_decoder_came_twee_get_hash_data(void* context) { bool subghz_protocol_decoder_came_twee_serialize( void* context, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset) { + SubGhzRadioPreset* preset) { furi_assert(context); SubGhzProtocolDecoderCameTwee* instance = context; return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); diff --git a/lib/subghz/protocols/came_twee.h b/lib/subghz/protocols/came_twee.h index aa1f0e0db4e..359b964da7f 100644 --- a/lib/subghz/protocols/came_twee.h +++ b/lib/subghz/protocols/came_twee.h @@ -83,13 +83,13 @@ uint8_t subghz_protocol_decoder_came_twee_get_hash_data(void* context); * Serialize data SubGhzProtocolDecoderCameTwee. * @param context Pointer to a SubGhzProtocolDecoderCameTwee instance * @param flipper_format Pointer to a FlipperFormat instance - * @param preset The modulation on which the signal was received, SubGhzPresetDefinition + * @param preset The modulation on which the signal was received, SubGhzRadioPreset * @return true On success */ bool subghz_protocol_decoder_came_twee_serialize( void* context, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset); + SubGhzRadioPreset* preset); /** * Deserialize data SubGhzProtocolDecoderCameTwee. diff --git a/lib/subghz/protocols/chamberlain_code.c b/lib/subghz/protocols/chamberlain_code.c index d2d19af2546..3650a986731 100644 --- a/lib/subghz/protocols/chamberlain_code.c +++ b/lib/subghz/protocols/chamberlain_code.c @@ -427,7 +427,7 @@ uint8_t subghz_protocol_decoder_chamb_code_get_hash_data(void* context) { bool subghz_protocol_decoder_chamb_code_serialize( void* context, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset) { + SubGhzRadioPreset* preset) { furi_assert(context); SubGhzProtocolDecoderChamb_Code* instance = context; return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); diff --git a/lib/subghz/protocols/chamberlain_code.h b/lib/subghz/protocols/chamberlain_code.h index 923a3151b5d..f87b64d9012 100644 --- a/lib/subghz/protocols/chamberlain_code.h +++ b/lib/subghz/protocols/chamberlain_code.h @@ -83,13 +83,13 @@ 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 preset The modulation on which the signal was received, SubGhzPresetDefinition + * @param preset The modulation on which the signal was received, SubGhzRadioPreset * @return true On success */ bool subghz_protocol_decoder_chamb_code_serialize( void* context, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset); + SubGhzRadioPreset* preset); /** * Deserialize data SubGhzProtocolDecoderChamb_Code. diff --git a/lib/subghz/protocols/clemsa.c b/lib/subghz/protocols/clemsa.c index dbee0ac9404..a2cb7a18bdc 100644 --- a/lib/subghz/protocols/clemsa.c +++ b/lib/subghz/protocols/clemsa.c @@ -319,7 +319,7 @@ uint8_t subghz_protocol_decoder_clemsa_get_hash_data(void* context) { bool subghz_protocol_decoder_clemsa_serialize( void* context, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset) { + SubGhzRadioPreset* preset) { furi_assert(context); SubGhzProtocolDecoderClemsa* instance = context; return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); diff --git a/lib/subghz/protocols/clemsa.h b/lib/subghz/protocols/clemsa.h index 8287af23413..8858c1a3bac 100644 --- a/lib/subghz/protocols/clemsa.h +++ b/lib/subghz/protocols/clemsa.h @@ -83,13 +83,13 @@ uint8_t subghz_protocol_decoder_clemsa_get_hash_data(void* context); * Serialize data SubGhzProtocolDecoderClemsa. * @param context Pointer to a SubGhzProtocolDecoderClemsa instance * @param flipper_format Pointer to a FlipperFormat instance - * @param preset The modulation on which the signal was received, SubGhzPresetDefinition + * @param preset The modulation on which the signal was received, SubGhzRadioPreset * @return true On success */ bool subghz_protocol_decoder_clemsa_serialize( void* context, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset); + SubGhzRadioPreset* preset); /** * Deserialize data SubGhzProtocolDecoderClemsa. diff --git a/lib/subghz/protocols/doitrand.c b/lib/subghz/protocols/doitrand.c index 5c340214448..6b31d4f2716 100644 --- a/lib/subghz/protocols/doitrand.c +++ b/lib/subghz/protocols/doitrand.c @@ -313,7 +313,7 @@ uint8_t subghz_protocol_decoder_doitrand_get_hash_data(void* context) { bool subghz_protocol_decoder_doitrand_serialize( void* context, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset) { + SubGhzRadioPreset* preset) { furi_assert(context); SubGhzProtocolDecoderDoitrand* instance = context; return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); diff --git a/lib/subghz/protocols/doitrand.h b/lib/subghz/protocols/doitrand.h index c96946a13da..30f1fffd0dc 100644 --- a/lib/subghz/protocols/doitrand.h +++ b/lib/subghz/protocols/doitrand.h @@ -83,13 +83,13 @@ uint8_t subghz_protocol_decoder_doitrand_get_hash_data(void* context); * Serialize data SubGhzProtocolDecoderDoitrand. * @param context Pointer to a SubGhzProtocolDecoderDoitrand instance * @param flipper_format Pointer to a FlipperFormat instance - * @param preset The modulation on which the signal was received, SubGhzPresetDefinition + * @param preset The modulation on which the signal was received, SubGhzRadioPreset * @return true On success */ bool subghz_protocol_decoder_doitrand_serialize( void* context, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset); + SubGhzRadioPreset* preset); /** * Deserialize data SubGhzProtocolDecoderDoitrand. diff --git a/lib/subghz/protocols/faac_slh.c b/lib/subghz/protocols/faac_slh.c index b544359025c..ad186738355 100644 --- a/lib/subghz/protocols/faac_slh.c +++ b/lib/subghz/protocols/faac_slh.c @@ -183,7 +183,7 @@ uint8_t subghz_protocol_decoder_faac_slh_get_hash_data(void* context) { bool subghz_protocol_decoder_faac_slh_serialize( void* context, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset) { + SubGhzRadioPreset* preset) { furi_assert(context); SubGhzProtocolDecoderFaacSLH* instance = context; return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); diff --git a/lib/subghz/protocols/faac_slh.h b/lib/subghz/protocols/faac_slh.h index 9b18f5eabfa..52c265d20b8 100644 --- a/lib/subghz/protocols/faac_slh.h +++ b/lib/subghz/protocols/faac_slh.h @@ -49,13 +49,13 @@ uint8_t subghz_protocol_decoder_faac_slh_get_hash_data(void* context); * Serialize data SubGhzProtocolDecoderFaacSLH. * @param context Pointer to a SubGhzProtocolDecoderFaacSLH instance * @param flipper_format Pointer to a FlipperFormat instance - * @param preset The modulation on which the signal was received, SubGhzPresetDefinition + * @param preset The modulation on which the signal was received, SubGhzRadioPreset * @return true On success */ bool subghz_protocol_decoder_faac_slh_serialize( void* context, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset); + SubGhzRadioPreset* preset); /** * Deserialize data SubGhzProtocolDecoderFaacSLH. diff --git a/lib/subghz/protocols/gate_tx.c b/lib/subghz/protocols/gate_tx.c index 5cf3a87141f..4c7c2d48458 100644 --- a/lib/subghz/protocols/gate_tx.c +++ b/lib/subghz/protocols/gate_tx.c @@ -293,7 +293,7 @@ uint8_t subghz_protocol_decoder_gate_tx_get_hash_data(void* context) { bool subghz_protocol_decoder_gate_tx_serialize( void* context, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset) { + SubGhzRadioPreset* preset) { furi_assert(context); SubGhzProtocolDecoderGateTx* instance = context; return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); diff --git a/lib/subghz/protocols/gate_tx.h b/lib/subghz/protocols/gate_tx.h index 36eeb9a99d8..4bfba3597cd 100644 --- a/lib/subghz/protocols/gate_tx.h +++ b/lib/subghz/protocols/gate_tx.h @@ -83,13 +83,13 @@ uint8_t subghz_protocol_decoder_gate_tx_get_hash_data(void* context); * Serialize data SubGhzProtocolDecoderGateTx. * @param context Pointer to a SubGhzProtocolDecoderGateTx instance * @param flipper_format Pointer to a FlipperFormat instance - * @param preset The modulation on which the signal was received, SubGhzPresetDefinition + * @param preset The modulation on which the signal was received, SubGhzRadioPreset * @return true On success */ bool subghz_protocol_decoder_gate_tx_serialize( void* context, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset); + SubGhzRadioPreset* preset); /** * Deserialize data SubGhzProtocolDecoderGateTx. diff --git a/lib/subghz/protocols/holtek.c b/lib/subghz/protocols/holtek.c index 230f4cfef9c..39e27bbf8f2 100644 --- a/lib/subghz/protocols/holtek.c +++ b/lib/subghz/protocols/holtek.c @@ -326,7 +326,7 @@ uint8_t subghz_protocol_decoder_holtek_get_hash_data(void* context) { bool subghz_protocol_decoder_holtek_serialize( void* context, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset) { + SubGhzRadioPreset* preset) { furi_assert(context); SubGhzProtocolDecoderHoltek* instance = context; return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); diff --git a/lib/subghz/protocols/holtek.h b/lib/subghz/protocols/holtek.h index 1dac783b1ba..252a26dc7c4 100644 --- a/lib/subghz/protocols/holtek.h +++ b/lib/subghz/protocols/holtek.h @@ -83,13 +83,13 @@ uint8_t subghz_protocol_decoder_holtek_get_hash_data(void* context); * Serialize data SubGhzProtocolDecoderHoltek. * @param context Pointer to a SubGhzProtocolDecoderHoltek instance * @param flipper_format Pointer to a FlipperFormat instance - * @param preset The modulation on which the signal was received, SubGhzPresetDefinition + * @param preset The modulation on which the signal was received, SubGhzRadioPreset * @return true On success */ bool subghz_protocol_decoder_holtek_serialize( void* context, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset); + SubGhzRadioPreset* preset); /** * Deserialize data SubGhzProtocolDecoderHoltek. diff --git a/lib/subghz/protocols/honeywell_wdb.c b/lib/subghz/protocols/honeywell_wdb.c index 3cd62698dcf..3b940fc6776 100644 --- a/lib/subghz/protocols/honeywell_wdb.c +++ b/lib/subghz/protocols/honeywell_wdb.c @@ -348,7 +348,7 @@ uint8_t subghz_protocol_decoder_honeywell_wdb_get_hash_data(void* context) { bool subghz_protocol_decoder_honeywell_wdb_serialize( void* context, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset) { + SubGhzRadioPreset* preset) { furi_assert(context); SubGhzProtocolDecoderHoneywell_WDB* instance = context; return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); diff --git a/lib/subghz/protocols/honeywell_wdb.h b/lib/subghz/protocols/honeywell_wdb.h index c61aa822fc9..828631837d6 100644 --- a/lib/subghz/protocols/honeywell_wdb.h +++ b/lib/subghz/protocols/honeywell_wdb.h @@ -85,13 +85,13 @@ uint8_t subghz_protocol_decoder_honeywell_wdb_get_hash_data(void* context); * Serialize data SubGhzProtocolDecoderHoneywell_WDB. * @param context Pointer to a SubGhzProtocolDecoderHoneywell_WDB instance * @param flipper_format Pointer to a FlipperFormat instance - * @param preset The modulation on which the signal was received, SubGhzPresetDefinition + * @param preset The modulation on which the signal was received, SubGhzRadioPreset * @return true On success */ bool subghz_protocol_decoder_honeywell_wdb_serialize( void* context, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset); + SubGhzRadioPreset* preset); /** * Deserialize data SubGhzProtocolDecoderHoneywell_WDB. diff --git a/lib/subghz/protocols/hormann.c b/lib/subghz/protocols/hormann.c index d8438604fa9..cb6adaf6232 100644 --- a/lib/subghz/protocols/hormann.c +++ b/lib/subghz/protocols/hormann.c @@ -314,7 +314,7 @@ uint8_t subghz_protocol_decoder_hormann_get_hash_data(void* context) { bool subghz_protocol_decoder_hormann_serialize( void* context, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset) { + SubGhzRadioPreset* preset) { furi_assert(context); SubGhzProtocolDecoderHormann* instance = context; return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); diff --git a/lib/subghz/protocols/hormann.h b/lib/subghz/protocols/hormann.h index 743ad0b384b..857a50041bc 100644 --- a/lib/subghz/protocols/hormann.h +++ b/lib/subghz/protocols/hormann.h @@ -83,13 +83,13 @@ uint8_t subghz_protocol_decoder_hormann_get_hash_data(void* context); * Serialize data SubGhzProtocolDecoderHormann. * @param context Pointer to a SubGhzProtocolDecoderHormann instance * @param flipper_format Pointer to a FlipperFormat instance - * @param preset The modulation on which the signal was received, SubGhzPresetDefinition + * @param preset The modulation on which the signal was received, SubGhzRadioPreset * @return true On success */ bool subghz_protocol_decoder_hormann_serialize( void* context, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset); + SubGhzRadioPreset* preset); /** * Deserialize data SubGhzProtocolDecoderHormann. diff --git a/lib/subghz/protocols/ido.c b/lib/subghz/protocols/ido.c index 6cc60bde823..31ff20e420f 100644 --- a/lib/subghz/protocols/ido.c +++ b/lib/subghz/protocols/ido.c @@ -182,7 +182,7 @@ uint8_t subghz_protocol_decoder_ido_get_hash_data(void* context) { bool subghz_protocol_decoder_ido_serialize( void* context, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset) { + SubGhzRadioPreset* preset) { furi_assert(context); SubGhzProtocolDecoderIDo* instance = context; return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); diff --git a/lib/subghz/protocols/ido.h b/lib/subghz/protocols/ido.h index 67d2b4d8f91..634f6ff89cc 100644 --- a/lib/subghz/protocols/ido.h +++ b/lib/subghz/protocols/ido.h @@ -49,13 +49,13 @@ uint8_t subghz_protocol_decoder_ido_get_hash_data(void* context); * Serialize data SubGhzProtocolDecoderIDo. * @param context Pointer to a SubGhzProtocolDecoderIDo instance * @param flipper_format Pointer to a FlipperFormat instance - * @param preset The modulation on which the signal was received, SubGhzPresetDefinition + * @param preset The modulation on which the signal was received, SubGhzRadioPreset * @return true On success */ bool subghz_protocol_decoder_ido_serialize( void* context, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset); + SubGhzRadioPreset* preset); /** * Deserialize data SubGhzProtocolDecoderIDo. diff --git a/lib/subghz/protocols/intertechno_v3.c b/lib/subghz/protocols/intertechno_v3.c index 14e137cab3d..2c4e514cce3 100644 --- a/lib/subghz/protocols/intertechno_v3.c +++ b/lib/subghz/protocols/intertechno_v3.c @@ -407,7 +407,7 @@ uint8_t subghz_protocol_decoder_intertechno_v3_get_hash_data(void* context) { bool subghz_protocol_decoder_intertechno_v3_serialize( void* context, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset) { + SubGhzRadioPreset* preset) { furi_assert(context); SubGhzProtocolDecoderIntertechno_V3* instance = context; return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); diff --git a/lib/subghz/protocols/intertechno_v3.h b/lib/subghz/protocols/intertechno_v3.h index 078d039782a..ffee14b04f9 100644 --- a/lib/subghz/protocols/intertechno_v3.h +++ b/lib/subghz/protocols/intertechno_v3.h @@ -85,13 +85,13 @@ uint8_t subghz_protocol_decoder_intertechno_v3_get_hash_data(void* context); * Serialize data SubGhzProtocolDecoderIntertechno_V3. * @param context Pointer to a SubGhzProtocolDecoderIntertechno_V3 instance * @param flipper_format Pointer to a FlipperFormat instance - * @param preset The modulation on which the signal was received, SubGhzPresetDefinition + * @param preset The modulation on which the signal was received, SubGhzRadioPreset * @return true On success */ bool subghz_protocol_decoder_intertechno_v3_serialize( void* context, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset); + SubGhzRadioPreset* preset); /** * Deserialize data SubGhzProtocolDecoderIntertechno_V3. diff --git a/lib/subghz/protocols/keeloq.c b/lib/subghz/protocols/keeloq.c index 039d97a6a54..ae6588e7afb 100644 --- a/lib/subghz/protocols/keeloq.c +++ b/lib/subghz/protocols/keeloq.c @@ -173,7 +173,7 @@ bool subghz_protocol_keeloq_create_data( uint8_t btn, uint16_t cnt, const char* manufacture_name, - SubGhzPresetDefinition* preset) { + SubGhzRadioPreset* preset) { furi_assert(context); SubGhzProtocolEncoderKeeloq* instance = context; instance->generic.serial = serial; @@ -646,7 +646,7 @@ uint8_t subghz_protocol_decoder_keeloq_get_hash_data(void* context) { bool subghz_protocol_decoder_keeloq_serialize( void* context, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset) { + SubGhzRadioPreset* preset) { furi_assert(context); SubGhzProtocolDecoderKeeloq* instance = context; subghz_protocol_keeloq_check_remote_controller( diff --git a/lib/subghz/protocols/keeloq.h b/lib/subghz/protocols/keeloq.h index c0575bd96b8..7b1aaee237a 100644 --- a/lib/subghz/protocols/keeloq.h +++ b/lib/subghz/protocols/keeloq.h @@ -32,7 +32,7 @@ void subghz_protocol_encoder_keeloq_free(void* context); * @param btn Button number, 4 bit * @param cnt Container value, 16 bit * @param manufacture_name Name of manufacturer's key - * @param preset Modulation, SubGhzPresetDefinition + * @param preset Modulation, SubGhzRadioPreset * @return true On success */ bool subghz_protocol_keeloq_create_data( @@ -42,7 +42,7 @@ bool subghz_protocol_keeloq_create_data( uint8_t btn, uint16_t cnt, const char* manufacture_name, - SubGhzPresetDefinition* preset); + SubGhzRadioPreset* preset); /** * Deserialize and generating an upload to send. @@ -103,13 +103,13 @@ uint8_t subghz_protocol_decoder_keeloq_get_hash_data(void* context); * Serialize data SubGhzProtocolDecoderKeeloq. * @param context Pointer to a SubGhzProtocolDecoderKeeloq instance * @param flipper_format Pointer to a FlipperFormat instance - * @param preset The modulation on which the signal was received, SubGhzPresetDefinition + * @param preset The modulation on which the signal was received, SubGhzRadioPreset * @return true On success */ bool subghz_protocol_decoder_keeloq_serialize( void* context, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset); + SubGhzRadioPreset* preset); /** * Deserialize data SubGhzProtocolDecoderKeeloq. diff --git a/lib/subghz/protocols/kia.c b/lib/subghz/protocols/kia.c index d46838661b2..997f8e1de58 100644 --- a/lib/subghz/protocols/kia.c +++ b/lib/subghz/protocols/kia.c @@ -233,7 +233,7 @@ uint8_t subghz_protocol_decoder_kia_get_hash_data(void* context) { bool subghz_protocol_decoder_kia_serialize( void* context, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset) { + SubGhzRadioPreset* preset) { furi_assert(context); SubGhzProtocolDecoderKIA* instance = context; return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); diff --git a/lib/subghz/protocols/kia.h b/lib/subghz/protocols/kia.h index cf18692193b..a9afcf14984 100644 --- a/lib/subghz/protocols/kia.h +++ b/lib/subghz/protocols/kia.h @@ -49,13 +49,13 @@ uint8_t subghz_protocol_decoder_kia_get_hash_data(void* context); * Serialize data SubGhzProtocolDecoderKIA. * @param context Pointer to a SubGhzProtocolDecoderKIA instance * @param flipper_format Pointer to a FlipperFormat instance - * @param preset The modulation on which the signal was received, SubGhzPresetDefinition + * @param preset The modulation on which the signal was received, SubGhzRadioPreset * @return true On success */ bool subghz_protocol_decoder_kia_serialize( void* context, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset); + SubGhzRadioPreset* preset); /** * Deserialize data SubGhzProtocolDecoderKIA. diff --git a/lib/subghz/protocols/linear.c b/lib/subghz/protocols/linear.c index 582c4aaf3d3..2fc8b20c872 100644 --- a/lib/subghz/protocols/linear.c +++ b/lib/subghz/protocols/linear.c @@ -303,7 +303,7 @@ uint8_t subghz_protocol_decoder_linear_get_hash_data(void* context) { bool subghz_protocol_decoder_linear_serialize( void* context, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset) { + SubGhzRadioPreset* preset) { furi_assert(context); SubGhzProtocolDecoderLinear* instance = context; return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); diff --git a/lib/subghz/protocols/linear.h b/lib/subghz/protocols/linear.h index 6111ad85cab..923337ac21e 100644 --- a/lib/subghz/protocols/linear.h +++ b/lib/subghz/protocols/linear.h @@ -83,13 +83,13 @@ 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 preset The modulation on which the signal was received, SubGhzPresetDefinition + * @param preset The modulation on which the signal was received, SubGhzRadioPreset * @return true On success */ bool subghz_protocol_decoder_linear_serialize( void* context, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset); + SubGhzRadioPreset* preset); /** * Deserialize data SubGhzProtocolDecoderLinear. diff --git a/lib/subghz/protocols/magellen.c b/lib/subghz/protocols/magellan.c similarity index 59% rename from lib/subghz/protocols/magellen.c rename to lib/subghz/protocols/magellan.c index 160ec4a2cab..71497525423 100644 --- a/lib/subghz/protocols/magellen.c +++ b/lib/subghz/protocols/magellan.c @@ -1,4 +1,4 @@ -#include "magellen.h" +#include "magellan.h" #include "../blocks/const.h" #include "../blocks/decoder.h" @@ -6,16 +6,16 @@ #include "../blocks/generic.h" #include "../blocks/math.h" -#define TAG "SubGhzProtocolMagellen" +#define TAG "SubGhzProtocolMagellan" -static const SubGhzBlockConst subghz_protocol_magellen_const = { +static const SubGhzBlockConst subghz_protocol_magellan_const = { .te_short = 200, .te_long = 400, .te_delta = 100, .min_count_bit_for_found = 32, }; -struct SubGhzProtocolDecoderMagellen { +struct SubGhzProtocolDecoderMagellan { SubGhzProtocolDecoderBase base; SubGhzBlockDecoder decoder; @@ -23,7 +23,7 @@ struct SubGhzProtocolDecoderMagellen { uint16_t header_count; }; -struct SubGhzProtocolEncoderMagellen { +struct SubGhzProtocolEncoderMagellan { SubGhzProtocolEncoderBase base; SubGhzProtocolBlockEncoder encoder; @@ -31,50 +31,50 @@ struct SubGhzProtocolEncoderMagellen { }; typedef enum { - MagellenDecoderStepReset = 0, - MagellenDecoderStepCheckPreambula, - MagellenDecoderStepFoundPreambula, - MagellenDecoderStepSaveDuration, - MagellenDecoderStepCheckDuration, -} MagellenDecoderStep; - -const SubGhzProtocolDecoder subghz_protocol_magellen_decoder = { - .alloc = subghz_protocol_decoder_magellen_alloc, - .free = subghz_protocol_decoder_magellen_free, - - .feed = subghz_protocol_decoder_magellen_feed, - .reset = subghz_protocol_decoder_magellen_reset, - - .get_hash_data = subghz_protocol_decoder_magellen_get_hash_data, - .serialize = subghz_protocol_decoder_magellen_serialize, - .deserialize = subghz_protocol_decoder_magellen_deserialize, - .get_string = subghz_protocol_decoder_magellen_get_string, + MagellanDecoderStepReset = 0, + MagellanDecoderStepCheckPreambula, + MagellanDecoderStepFoundPreambula, + MagellanDecoderStepSaveDuration, + MagellanDecoderStepCheckDuration, +} MagellanDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_magellan_decoder = { + .alloc = subghz_protocol_decoder_magellan_alloc, + .free = subghz_protocol_decoder_magellan_free, + + .feed = subghz_protocol_decoder_magellan_feed, + .reset = subghz_protocol_decoder_magellan_reset, + + .get_hash_data = subghz_protocol_decoder_magellan_get_hash_data, + .serialize = subghz_protocol_decoder_magellan_serialize, + .deserialize = subghz_protocol_decoder_magellan_deserialize, + .get_string = subghz_protocol_decoder_magellan_get_string, }; -const SubGhzProtocolEncoder subghz_protocol_magellen_encoder = { - .alloc = subghz_protocol_encoder_magellen_alloc, - .free = subghz_protocol_encoder_magellen_free, +const SubGhzProtocolEncoder subghz_protocol_magellan_encoder = { + .alloc = subghz_protocol_encoder_magellan_alloc, + .free = subghz_protocol_encoder_magellan_free, - .deserialize = subghz_protocol_encoder_magellen_deserialize, - .stop = subghz_protocol_encoder_magellen_stop, - .yield = subghz_protocol_encoder_magellen_yield, + .deserialize = subghz_protocol_encoder_magellan_deserialize, + .stop = subghz_protocol_encoder_magellan_stop, + .yield = subghz_protocol_encoder_magellan_yield, }; -const SubGhzProtocol subghz_protocol_magellen = { - .name = SUBGHZ_PROTOCOL_MAGELLEN_NAME, +const SubGhzProtocol subghz_protocol_magellan = { + .name = SUBGHZ_PROTOCOL_MAGELLAN_NAME, .type = SubGhzProtocolTypeStatic, .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, - .decoder = &subghz_protocol_magellen_decoder, - .encoder = &subghz_protocol_magellen_encoder, + .decoder = &subghz_protocol_magellan_decoder, + .encoder = &subghz_protocol_magellan_encoder, }; -void* subghz_protocol_encoder_magellen_alloc(SubGhzEnvironment* environment) { +void* subghz_protocol_encoder_magellan_alloc(SubGhzEnvironment* environment) { UNUSED(environment); - SubGhzProtocolEncoderMagellen* instance = malloc(sizeof(SubGhzProtocolEncoderMagellen)); + SubGhzProtocolEncoderMagellan* instance = malloc(sizeof(SubGhzProtocolEncoderMagellan)); - instance->base.protocol = &subghz_protocol_magellen; + instance->base.protocol = &subghz_protocol_magellan; instance->generic.protocol_name = instance->base.protocol->name; instance->encoder.repeat = 10; @@ -84,75 +84,75 @@ void* subghz_protocol_encoder_magellen_alloc(SubGhzEnvironment* environment) { return instance; } -void subghz_protocol_encoder_magellen_free(void* context) { +void subghz_protocol_encoder_magellan_free(void* context) { furi_assert(context); - SubGhzProtocolEncoderMagellen* instance = context; + SubGhzProtocolEncoderMagellan* instance = context; free(instance->encoder.upload); free(instance); } /** * Generating an upload from data. - * @param instance Pointer to a SubGhzProtocolEncoderMagellen instance + * @param instance Pointer to a SubGhzProtocolEncoderMagellan instance * @return true On success */ -static bool subghz_protocol_encoder_magellen_get_upload(SubGhzProtocolEncoderMagellen* instance) { +static bool subghz_protocol_encoder_magellan_get_upload(SubGhzProtocolEncoderMagellan* instance) { furi_assert(instance); size_t index = 0; //Send header instance->encoder.upload[index++] = - level_duration_make(true, (uint32_t)subghz_protocol_magellen_const.te_short * 4); + level_duration_make(true, (uint32_t)subghz_protocol_magellan_const.te_short * 4); instance->encoder.upload[index++] = - level_duration_make(false, (uint32_t)subghz_protocol_magellen_const.te_short); + level_duration_make(false, (uint32_t)subghz_protocol_magellan_const.te_short); for(uint8_t i = 0; i < 12; i++) { instance->encoder.upload[index++] = - level_duration_make(true, (uint32_t)subghz_protocol_magellen_const.te_short); + level_duration_make(true, (uint32_t)subghz_protocol_magellan_const.te_short); instance->encoder.upload[index++] = - level_duration_make(false, (uint32_t)subghz_protocol_magellen_const.te_short); + level_duration_make(false, (uint32_t)subghz_protocol_magellan_const.te_short); } instance->encoder.upload[index++] = - level_duration_make(true, (uint32_t)subghz_protocol_magellen_const.te_short); + level_duration_make(true, (uint32_t)subghz_protocol_magellan_const.te_short); instance->encoder.upload[index++] = - level_duration_make(false, (uint32_t)subghz_protocol_magellen_const.te_long); + level_duration_make(false, (uint32_t)subghz_protocol_magellan_const.te_long); //Send start bit instance->encoder.upload[index++] = - level_duration_make(true, (uint32_t)subghz_protocol_magellen_const.te_long * 3); + level_duration_make(true, (uint32_t)subghz_protocol_magellan_const.te_long * 3); instance->encoder.upload[index++] = - level_duration_make(false, (uint32_t)subghz_protocol_magellen_const.te_long); + level_duration_make(false, (uint32_t)subghz_protocol_magellan_const.te_long); //Send key data for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) { if(bit_read(instance->generic.data, i - 1)) { //send bit 1 instance->encoder.upload[index++] = - level_duration_make(true, (uint32_t)subghz_protocol_magellen_const.te_short); + level_duration_make(true, (uint32_t)subghz_protocol_magellan_const.te_short); instance->encoder.upload[index++] = - level_duration_make(false, (uint32_t)subghz_protocol_magellen_const.te_long); + level_duration_make(false, (uint32_t)subghz_protocol_magellan_const.te_long); } else { //send bit 0 instance->encoder.upload[index++] = - level_duration_make(true, (uint32_t)subghz_protocol_magellen_const.te_long); + level_duration_make(true, (uint32_t)subghz_protocol_magellan_const.te_long); instance->encoder.upload[index++] = - level_duration_make(false, (uint32_t)subghz_protocol_magellen_const.te_short); + level_duration_make(false, (uint32_t)subghz_protocol_magellan_const.te_short); } } //Send stop bit instance->encoder.upload[index++] = - level_duration_make(true, (uint32_t)subghz_protocol_magellen_const.te_short); + level_duration_make(true, (uint32_t)subghz_protocol_magellan_const.te_short); instance->encoder.upload[index++] = - level_duration_make(false, (uint32_t)subghz_protocol_magellen_const.te_long * 100); + level_duration_make(false, (uint32_t)subghz_protocol_magellan_const.te_long * 100); instance->encoder.size_upload = index; return true; } -bool subghz_protocol_encoder_magellen_deserialize(void* context, FlipperFormat* flipper_format) { +bool subghz_protocol_encoder_magellan_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); - SubGhzProtocolEncoderMagellen* instance = context; + SubGhzProtocolEncoderMagellan* instance = context; bool res = false; do { if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { @@ -160,7 +160,7 @@ bool subghz_protocol_encoder_magellen_deserialize(void* context, FlipperFormat* break; } if(instance->generic.data_count_bit != - subghz_protocol_magellen_const.min_count_bit_for_found) { + subghz_protocol_magellan_const.min_count_bit_for_found) { FURI_LOG_E(TAG, "Wrong number of bits in key"); break; } @@ -168,7 +168,7 @@ bool subghz_protocol_encoder_magellen_deserialize(void* context, FlipperFormat* flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - if(!subghz_protocol_encoder_magellen_get_upload(instance)) break; + if(!subghz_protocol_encoder_magellan_get_upload(instance)) break; instance->encoder.is_running = true; res = true; @@ -177,13 +177,13 @@ bool subghz_protocol_encoder_magellen_deserialize(void* context, FlipperFormat* return res; } -void subghz_protocol_encoder_magellen_stop(void* context) { - SubGhzProtocolEncoderMagellen* instance = context; +void subghz_protocol_encoder_magellan_stop(void* context) { + SubGhzProtocolEncoderMagellan* instance = context; instance->encoder.is_running = false; } -LevelDuration subghz_protocol_encoder_magellen_yield(void* context) { - SubGhzProtocolEncoderMagellen* instance = context; +LevelDuration subghz_protocol_encoder_magellan_yield(void* context) { + SubGhzProtocolEncoderMagellan* instance = context; if(instance->encoder.repeat == 0 || !instance->encoder.is_running) { instance->encoder.is_running = false; @@ -200,27 +200,27 @@ LevelDuration subghz_protocol_encoder_magellen_yield(void* context) { return ret; } -void* subghz_protocol_decoder_magellen_alloc(SubGhzEnvironment* environment) { +void* subghz_protocol_decoder_magellan_alloc(SubGhzEnvironment* environment) { UNUSED(environment); - SubGhzProtocolDecoderMagellen* instance = malloc(sizeof(SubGhzProtocolDecoderMagellen)); - instance->base.protocol = &subghz_protocol_magellen; + SubGhzProtocolDecoderMagellan* instance = malloc(sizeof(SubGhzProtocolDecoderMagellan)); + instance->base.protocol = &subghz_protocol_magellan; instance->generic.protocol_name = instance->base.protocol->name; return instance; } -void subghz_protocol_decoder_magellen_free(void* context) { +void subghz_protocol_decoder_magellan_free(void* context) { furi_assert(context); - SubGhzProtocolDecoderMagellen* instance = context; + SubGhzProtocolDecoderMagellan* instance = context; free(instance); } -void subghz_protocol_decoder_magellen_reset(void* context) { +void subghz_protocol_decoder_magellan_reset(void* context) { furi_assert(context); - SubGhzProtocolDecoderMagellen* instance = context; - instance->decoder.parser_step = MagellenDecoderStepReset; + SubGhzProtocolDecoderMagellan* instance = context; + instance->decoder.parser_step = MagellanDecoderStepReset; } -uint8_t subghz_protocol_magellen_crc8(uint8_t* data, size_t len) { +uint8_t subghz_protocol_magellan_crc8(uint8_t* data, size_t len) { uint8_t crc = 0x00; size_t i, j; for(i = 0; i < len; i++) { @@ -235,99 +235,99 @@ uint8_t subghz_protocol_magellen_crc8(uint8_t* data, size_t len) { return crc; } -static bool subghz_protocol_magellen_check_crc(SubGhzProtocolDecoderMagellen* instance) { +static bool subghz_protocol_magellan_check_crc(SubGhzProtocolDecoderMagellan* instance) { uint8_t data[3] = { instance->decoder.decode_data >> 24, instance->decoder.decode_data >> 16, instance->decoder.decode_data >> 8}; return (instance->decoder.decode_data & 0xFF) == - subghz_protocol_magellen_crc8(data, sizeof(data)); + subghz_protocol_magellan_crc8(data, sizeof(data)); } -void subghz_protocol_decoder_magellen_feed(void* context, bool level, uint32_t duration) { +void subghz_protocol_decoder_magellan_feed(void* context, bool level, uint32_t duration) { furi_assert(context); - SubGhzProtocolDecoderMagellen* instance = context; + SubGhzProtocolDecoderMagellan* instance = context; switch(instance->decoder.parser_step) { - case MagellenDecoderStepReset: - if((level) && (DURATION_DIFF(duration, subghz_protocol_magellen_const.te_short) < - subghz_protocol_magellen_const.te_delta)) { - instance->decoder.parser_step = MagellenDecoderStepCheckPreambula; + case MagellanDecoderStepReset: + if((level) && (DURATION_DIFF(duration, subghz_protocol_magellan_const.te_short) < + subghz_protocol_magellan_const.te_delta)) { + instance->decoder.parser_step = MagellanDecoderStepCheckPreambula; instance->decoder.te_last = duration; instance->header_count = 0; } break; - case MagellenDecoderStepCheckPreambula: + case MagellanDecoderStepCheckPreambula: if(level) { instance->decoder.te_last = duration; } else { - if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_magellen_const.te_short) < - subghz_protocol_magellen_const.te_delta) && - (DURATION_DIFF(duration, subghz_protocol_magellen_const.te_short) < - subghz_protocol_magellen_const.te_delta)) { + if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_magellan_const.te_short) < + subghz_protocol_magellan_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_magellan_const.te_short) < + subghz_protocol_magellan_const.te_delta)) { // Found header instance->header_count++; } else if( - (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_magellen_const.te_short) < - subghz_protocol_magellen_const.te_delta) && - (DURATION_DIFF(duration, subghz_protocol_magellen_const.te_long) < - subghz_protocol_magellen_const.te_delta * 2) && + (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_magellan_const.te_short) < + subghz_protocol_magellan_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_magellan_const.te_long) < + subghz_protocol_magellan_const.te_delta * 2) && (instance->header_count > 10)) { - instance->decoder.parser_step = MagellenDecoderStepFoundPreambula; + instance->decoder.parser_step = MagellanDecoderStepFoundPreambula; } else { - instance->decoder.parser_step = MagellenDecoderStepReset; + instance->decoder.parser_step = MagellanDecoderStepReset; } } break; - case MagellenDecoderStepFoundPreambula: + case MagellanDecoderStepFoundPreambula: if(level) { instance->decoder.te_last = duration; } else { if((DURATION_DIFF( - instance->decoder.te_last, subghz_protocol_magellen_const.te_short * 6) < - subghz_protocol_magellen_const.te_delta * 3) && - (DURATION_DIFF(duration, subghz_protocol_magellen_const.te_long) < - subghz_protocol_magellen_const.te_delta * 2)) { - instance->decoder.parser_step = MagellenDecoderStepSaveDuration; + instance->decoder.te_last, subghz_protocol_magellan_const.te_short * 6) < + subghz_protocol_magellan_const.te_delta * 3) && + (DURATION_DIFF(duration, subghz_protocol_magellan_const.te_long) < + subghz_protocol_magellan_const.te_delta * 2)) { + instance->decoder.parser_step = MagellanDecoderStepSaveDuration; instance->decoder.decode_data = 0; instance->decoder.decode_count_bit = 0; } else { - instance->decoder.parser_step = MagellenDecoderStepReset; + instance->decoder.parser_step = MagellanDecoderStepReset; } } break; - case MagellenDecoderStepSaveDuration: + case MagellanDecoderStepSaveDuration: if(level) { instance->decoder.te_last = duration; - instance->decoder.parser_step = MagellenDecoderStepCheckDuration; + instance->decoder.parser_step = MagellanDecoderStepCheckDuration; } else { - instance->decoder.parser_step = MagellenDecoderStepReset; + instance->decoder.parser_step = MagellanDecoderStepReset; } break; - case MagellenDecoderStepCheckDuration: + case MagellanDecoderStepCheckDuration: if(!level) { - if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_magellen_const.te_short) < - subghz_protocol_magellen_const.te_delta) && - (DURATION_DIFF(duration, subghz_protocol_magellen_const.te_long) < - subghz_protocol_magellen_const.te_delta)) { + if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_magellan_const.te_short) < + subghz_protocol_magellan_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_magellan_const.te_long) < + subghz_protocol_magellan_const.te_delta)) { subghz_protocol_blocks_add_bit(&instance->decoder, 1); - instance->decoder.parser_step = MagellenDecoderStepSaveDuration; + instance->decoder.parser_step = MagellanDecoderStepSaveDuration; } else if( - (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_magellen_const.te_long) < - subghz_protocol_magellen_const.te_delta) && - (DURATION_DIFF(duration, subghz_protocol_magellen_const.te_short) < - subghz_protocol_magellen_const.te_delta)) { + (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_magellan_const.te_long) < + subghz_protocol_magellan_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_magellan_const.te_short) < + subghz_protocol_magellan_const.te_delta)) { subghz_protocol_blocks_add_bit(&instance->decoder, 0); - instance->decoder.parser_step = MagellenDecoderStepSaveDuration; - } else if(duration >= (subghz_protocol_magellen_const.te_long * 3)) { + instance->decoder.parser_step = MagellanDecoderStepSaveDuration; + } else if(duration >= (subghz_protocol_magellan_const.te_long * 3)) { //Found stop bit if((instance->decoder.decode_count_bit == - subghz_protocol_magellen_const.min_count_bit_for_found) && - subghz_protocol_magellen_check_crc(instance)) { + subghz_protocol_magellan_const.min_count_bit_for_found) && + subghz_protocol_magellan_check_crc(instance)) { instance->generic.data = instance->decoder.decode_data; instance->generic.data_count_bit = instance->decoder.decode_count_bit; if(instance->base.callback) @@ -335,12 +335,12 @@ void subghz_protocol_decoder_magellen_feed(void* context, bool level, uint32_t d } instance->decoder.decode_data = 0; instance->decoder.decode_count_bit = 0; - instance->decoder.parser_step = MagellenDecoderStepReset; + instance->decoder.parser_step = MagellanDecoderStepReset; } else { - instance->decoder.parser_step = MagellenDecoderStepReset; + instance->decoder.parser_step = MagellanDecoderStepReset; } } else { - instance->decoder.parser_step = MagellenDecoderStepReset; + instance->decoder.parser_step = MagellanDecoderStepReset; } break; } @@ -350,7 +350,7 @@ void subghz_protocol_decoder_magellen_feed(void* context, bool level, uint32_t d * Analysis of received data * @param instance Pointer to a SubGhzBlockGeneric* instance */ -static void subghz_protocol_magellen_check_remote_controller(SubGhzBlockGeneric* instance) { +static void subghz_protocol_magellan_check_remote_controller(SubGhzBlockGeneric* instance) { /* * package 32b data 24b CRC8 * 0x037AE4828 => 001101111010111001001000 00101000 @@ -375,7 +375,7 @@ static void subghz_protocol_magellen_check_remote_controller(SubGhzBlockGeneric* instance->btn = (data_rev >> 16) & 0xFF; } -static void subghz_protocol_magellen_get_event_serialize(uint8_t event, FuriString* output) { +static void subghz_protocol_magellan_get_event_serialize(uint8_t event, FuriString* output) { furi_string_cat_printf( output, "%s%s%s%s%s%s%s%s", @@ -390,32 +390,32 @@ static void subghz_protocol_magellen_get_event_serialize(uint8_t event, FuriStri ((event >> 7) & 0x1 ? ", ?" : "")); } -uint8_t subghz_protocol_decoder_magellen_get_hash_data(void* context) { +uint8_t subghz_protocol_decoder_magellan_get_hash_data(void* context) { furi_assert(context); - SubGhzProtocolDecoderMagellen* instance = context; + SubGhzProtocolDecoderMagellan* instance = context; return subghz_protocol_blocks_get_hash_data( &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } -bool subghz_protocol_decoder_magellen_serialize( +bool subghz_protocol_decoder_magellan_serialize( void* context, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset) { + SubGhzRadioPreset* preset) { furi_assert(context); - SubGhzProtocolDecoderMagellen* instance = context; + SubGhzProtocolDecoderMagellan* instance = context; return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); } -bool subghz_protocol_decoder_magellen_deserialize(void* context, FlipperFormat* flipper_format) { +bool subghz_protocol_decoder_magellan_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); - SubGhzProtocolDecoderMagellen* instance = context; + SubGhzProtocolDecoderMagellan* instance = context; bool ret = false; do { if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { break; } if(instance->generic.data_count_bit != - subghz_protocol_magellen_const.min_count_bit_for_found) { + subghz_protocol_magellan_const.min_count_bit_for_found) { FURI_LOG_E(TAG, "Wrong number of bits in key"); break; } @@ -424,10 +424,10 @@ bool subghz_protocol_decoder_magellen_deserialize(void* context, FlipperFormat* return ret; } -void subghz_protocol_decoder_magellen_get_string(void* context, FuriString* output) { +void subghz_protocol_decoder_magellan_get_string(void* context, FuriString* output) { furi_assert(context); - SubGhzProtocolDecoderMagellen* instance = context; - subghz_protocol_magellen_check_remote_controller(&instance->generic); + SubGhzProtocolDecoderMagellan* instance = context; + subghz_protocol_magellan_check_remote_controller(&instance->generic); furi_string_cat_printf( output, "%s %dbit\r\n" @@ -441,5 +441,5 @@ void subghz_protocol_decoder_magellen_get_string(void* context, FuriString* outp instance->generic.serial & 0xFF, instance->generic.btn); - subghz_protocol_magellen_get_event_serialize(instance->generic.btn, output); + subghz_protocol_magellan_get_event_serialize(instance->generic.btn, output); } diff --git a/lib/subghz/protocols/magellan.h b/lib/subghz/protocols/magellan.h new file mode 100644 index 00000000000..a179c9cb4b7 --- /dev/null +++ b/lib/subghz/protocols/magellan.h @@ -0,0 +1,107 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_MAGELLAN_NAME "Magellan" + +typedef struct SubGhzProtocolDecoderMagellan SubGhzProtocolDecoderMagellan; +typedef struct SubGhzProtocolEncoderMagellan SubGhzProtocolEncoderMagellan; + +extern const SubGhzProtocolDecoder subghz_protocol_magellan_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_magellan_encoder; +extern const SubGhzProtocol subghz_protocol_magellan; + +/** + * Allocate SubGhzProtocolEncoderMagellan. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderMagellan* pointer to a SubGhzProtocolEncoderMagellan instance + */ +void* subghz_protocol_encoder_magellan_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderMagellan. + * @param context Pointer to a SubGhzProtocolEncoderMagellan instance + */ +void subghz_protocol_encoder_magellan_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderMagellan instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_encoder_magellan_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderMagellan instance + */ +void subghz_protocol_encoder_magellan_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderMagellan instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_magellan_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderMagellan. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderMagellan* pointer to a SubGhzProtocolDecoderMagellan instance + */ +void* subghz_protocol_decoder_magellan_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderMagellan. + * @param context Pointer to a SubGhzProtocolDecoderMagellan instance + */ +void subghz_protocol_decoder_magellan_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderMagellan. + * @param context Pointer to a SubGhzProtocolDecoderMagellan instance + */ +void subghz_protocol_decoder_magellan_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderMagellan instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_magellan_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderMagellan instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_magellan_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderMagellan. + * @param context Pointer to a SubGhzProtocolDecoderMagellan instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_decoder_magellan_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderMagellan. + * @param context Pointer to a SubGhzProtocolDecoderMagellan instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_decoder_magellan_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderMagellan instance + * @param output Resulting text + */ +void subghz_protocol_decoder_magellan_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/magellen.h b/lib/subghz/protocols/magellen.h deleted file mode 100644 index 226919c8d9a..00000000000 --- a/lib/subghz/protocols/magellen.h +++ /dev/null @@ -1,107 +0,0 @@ -#pragma once - -#include "base.h" - -#define SUBGHZ_PROTOCOL_MAGELLEN_NAME "Magellen" - -typedef struct SubGhzProtocolDecoderMagellen SubGhzProtocolDecoderMagellen; -typedef struct SubGhzProtocolEncoderMagellen SubGhzProtocolEncoderMagellen; - -extern const SubGhzProtocolDecoder subghz_protocol_magellen_decoder; -extern const SubGhzProtocolEncoder subghz_protocol_magellen_encoder; -extern const SubGhzProtocol subghz_protocol_magellen; - -/** - * Allocate SubGhzProtocolEncoderMagellen. - * @param environment Pointer to a SubGhzEnvironment instance - * @return SubGhzProtocolEncoderMagellen* pointer to a SubGhzProtocolEncoderMagellen instance - */ -void* subghz_protocol_encoder_magellen_alloc(SubGhzEnvironment* environment); - -/** - * Free SubGhzProtocolEncoderMagellen. - * @param context Pointer to a SubGhzProtocolEncoderMagellen instance - */ -void subghz_protocol_encoder_magellen_free(void* context); - -/** - * Deserialize and generating an upload to send. - * @param context Pointer to a SubGhzProtocolEncoderMagellen instance - * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success - */ -bool subghz_protocol_encoder_magellen_deserialize(void* context, FlipperFormat* flipper_format); - -/** - * Forced transmission stop. - * @param context Pointer to a SubGhzProtocolEncoderMagellen instance - */ -void subghz_protocol_encoder_magellen_stop(void* context); - -/** - * Getting the level and duration of the upload to be loaded into DMA. - * @param context Pointer to a SubGhzProtocolEncoderMagellen instance - * @return LevelDuration - */ -LevelDuration subghz_protocol_encoder_magellen_yield(void* context); - -/** - * Allocate SubGhzProtocolDecoderMagellen. - * @param environment Pointer to a SubGhzEnvironment instance - * @return SubGhzProtocolDecoderMagellen* pointer to a SubGhzProtocolDecoderMagellen instance - */ -void* subghz_protocol_decoder_magellen_alloc(SubGhzEnvironment* environment); - -/** - * Free SubGhzProtocolDecoderMagellen. - * @param context Pointer to a SubGhzProtocolDecoderMagellen instance - */ -void subghz_protocol_decoder_magellen_free(void* context); - -/** - * Reset decoder SubGhzProtocolDecoderMagellen. - * @param context Pointer to a SubGhzProtocolDecoderMagellen instance - */ -void subghz_protocol_decoder_magellen_reset(void* context); - -/** - * Parse a raw sequence of levels and durations received from the air. - * @param context Pointer to a SubGhzProtocolDecoderMagellen instance - * @param level Signal level true-high false-low - * @param duration Duration of this level in, us - */ -void subghz_protocol_decoder_magellen_feed(void* context, bool level, uint32_t duration); - -/** - * Getting the hash sum of the last randomly received parcel. - * @param context Pointer to a SubGhzProtocolDecoderMagellen instance - * @return hash Hash sum - */ -uint8_t subghz_protocol_decoder_magellen_get_hash_data(void* context); - -/** - * Serialize data SubGhzProtocolDecoderMagellen. - * @param context Pointer to a SubGhzProtocolDecoderMagellen instance - * @param flipper_format Pointer to a FlipperFormat instance - * @param preset The modulation on which the signal was received, SubGhzPresetDefinition - * @return true On success - */ -bool subghz_protocol_decoder_magellen_serialize( - void* context, - FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset); - -/** - * Deserialize data SubGhzProtocolDecoderMagellen. - * @param context Pointer to a SubGhzProtocolDecoderMagellen instance - * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success - */ -bool subghz_protocol_decoder_magellen_deserialize(void* context, FlipperFormat* flipper_format); - -/** - * Getting a textual representation of the received data. - * @param context Pointer to a SubGhzProtocolDecoderMagellen instance - * @param output Resulting text - */ -void subghz_protocol_decoder_magellen_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/marantec.c b/lib/subghz/protocols/marantec.c index a72238a412b..d557c29b0bb 100644 --- a/lib/subghz/protocols/marantec.c +++ b/lib/subghz/protocols/marantec.c @@ -349,7 +349,7 @@ uint8_t subghz_protocol_decoder_marantec_get_hash_data(void* context) { bool subghz_protocol_decoder_marantec_serialize( void* context, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset) { + SubGhzRadioPreset* preset) { furi_assert(context); SubGhzProtocolDecoderMarantec* instance = context; return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); diff --git a/lib/subghz/protocols/marantec.h b/lib/subghz/protocols/marantec.h index 5fc042e778b..e330ccf16b3 100644 --- a/lib/subghz/protocols/marantec.h +++ b/lib/subghz/protocols/marantec.h @@ -83,13 +83,13 @@ uint8_t subghz_protocol_decoder_marantec_get_hash_data(void* context); * Serialize data SubGhzProtocolDecoderMarantec. * @param context Pointer to a SubGhzProtocolDecoderMarantec instance * @param flipper_format Pointer to a FlipperFormat instance - * @param preset The modulation on which the signal was received, SubGhzPresetDefinition + * @param preset The modulation on which the signal was received, SubGhzRadioPreset * @return true On success */ bool subghz_protocol_decoder_marantec_serialize( void* context, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset); + SubGhzRadioPreset* preset); /** * Deserialize data SubGhzProtocolDecoderMarantec. diff --git a/lib/subghz/protocols/megacode.c b/lib/subghz/protocols/megacode.c index baa8188d33e..1b871a0c6c5 100644 --- a/lib/subghz/protocols/megacode.c +++ b/lib/subghz/protocols/megacode.c @@ -384,7 +384,7 @@ uint8_t subghz_protocol_decoder_megacode_get_hash_data(void* context) { bool subghz_protocol_decoder_megacode_serialize( void* context, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset) { + SubGhzRadioPreset* preset) { furi_assert(context); SubGhzProtocolDecoderMegaCode* instance = context; return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); diff --git a/lib/subghz/protocols/megacode.h b/lib/subghz/protocols/megacode.h index 0fafddb6bb0..e31434fa307 100644 --- a/lib/subghz/protocols/megacode.h +++ b/lib/subghz/protocols/megacode.h @@ -83,13 +83,13 @@ uint8_t subghz_protocol_decoder_megacode_get_hash_data(void* context); * Serialize data SubGhzProtocolDecoderMegaCode. * @param context Pointer to a SubGhzProtocolDecoderMegaCode instance * @param flipper_format Pointer to a FlipperFormat instance - * @param preset The modulation on which the signal was received, SubGhzPresetDefinition + * @param preset The modulation on which the signal was received, SubGhzRadioPreset * @return true On success */ bool subghz_protocol_decoder_megacode_serialize( void* context, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset); + SubGhzRadioPreset* preset); /** * Deserialize data SubGhzProtocolDecoderMegaCode. diff --git a/lib/subghz/protocols/nero_radio.c b/lib/subghz/protocols/nero_radio.c index d6925d48572..5fffaa19d7b 100644 --- a/lib/subghz/protocols/nero_radio.c +++ b/lib/subghz/protocols/nero_radio.c @@ -346,7 +346,7 @@ uint8_t subghz_protocol_decoder_nero_radio_get_hash_data(void* context) { bool subghz_protocol_decoder_nero_radio_serialize( void* context, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset) { + SubGhzRadioPreset* preset) { furi_assert(context); SubGhzProtocolDecoderNeroRadio* instance = context; return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); diff --git a/lib/subghz/protocols/nero_radio.h b/lib/subghz/protocols/nero_radio.h index ef270342c62..361da6173c3 100644 --- a/lib/subghz/protocols/nero_radio.h +++ b/lib/subghz/protocols/nero_radio.h @@ -83,13 +83,13 @@ uint8_t subghz_protocol_decoder_nero_radio_get_hash_data(void* context); * Serialize data SubGhzProtocolDecoderNeroRadio. * @param context Pointer to a SubGhzProtocolDecoderNeroRadio instance * @param flipper_format Pointer to a FlipperFormat instance - * @param preset The modulation on which the signal was received, SubGhzPresetDefinition + * @param preset The modulation on which the signal was received, SubGhzRadioPreset * @return true On success */ bool subghz_protocol_decoder_nero_radio_serialize( void* context, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset); + SubGhzRadioPreset* preset); /** * Deserialize data SubGhzProtocolDecoderNeroRadio. diff --git a/lib/subghz/protocols/nero_sketch.c b/lib/subghz/protocols/nero_sketch.c index 8080bf9e4bd..b124b717bdb 100644 --- a/lib/subghz/protocols/nero_sketch.c +++ b/lib/subghz/protocols/nero_sketch.c @@ -331,7 +331,7 @@ uint8_t subghz_protocol_decoder_nero_sketch_get_hash_data(void* context) { bool subghz_protocol_decoder_nero_sketch_serialize( void* context, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset) { + SubGhzRadioPreset* preset) { furi_assert(context); SubGhzProtocolDecoderNeroSketch* instance = context; return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); diff --git a/lib/subghz/protocols/nero_sketch.h b/lib/subghz/protocols/nero_sketch.h index 4536e704355..ac87fb00a63 100644 --- a/lib/subghz/protocols/nero_sketch.h +++ b/lib/subghz/protocols/nero_sketch.h @@ -83,13 +83,13 @@ uint8_t subghz_protocol_decoder_nero_sketch_get_hash_data(void* context); * Serialize data SubGhzProtocolDecoderNeroSketch. * @param context Pointer to a SubGhzProtocolDecoderNeroSketch instance * @param flipper_format Pointer to a FlipperFormat instance - * @param preset The modulation on which the signal was received, SubGhzPresetDefinition + * @param preset The modulation on which the signal was received, SubGhzRadioPreset * @return true On success */ bool subghz_protocol_decoder_nero_sketch_serialize( void* context, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset); + SubGhzRadioPreset* preset); /** * Deserialize data SubGhzProtocolDecoderNeroSketch. diff --git a/lib/subghz/protocols/nice_flo.c b/lib/subghz/protocols/nice_flo.c index 06538db1080..ecd866a725d 100644 --- a/lib/subghz/protocols/nice_flo.c +++ b/lib/subghz/protocols/nice_flo.c @@ -283,7 +283,7 @@ uint8_t subghz_protocol_decoder_nice_flo_get_hash_data(void* context) { bool subghz_protocol_decoder_nice_flo_serialize( void* context, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset) { + SubGhzRadioPreset* preset) { furi_assert(context); SubGhzProtocolDecoderNiceFlo* instance = context; return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); diff --git a/lib/subghz/protocols/nice_flo.h b/lib/subghz/protocols/nice_flo.h index dd374006b74..e382e614618 100644 --- a/lib/subghz/protocols/nice_flo.h +++ b/lib/subghz/protocols/nice_flo.h @@ -83,13 +83,13 @@ uint8_t subghz_protocol_decoder_nice_flo_get_hash_data(void* context); * Serialize data SubGhzProtocolDecoderNiceFlo. * @param context Pointer to a SubGhzProtocolDecoderNiceFlo instance * @param flipper_format Pointer to a FlipperFormat instance - * @param preset The modulation on which the signal was received, SubGhzPresetDefinition + * @param preset The modulation on which the signal was received, SubGhzRadioPreset * @return true On success */ bool subghz_protocol_decoder_nice_flo_serialize( void* context, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset); + SubGhzRadioPreset* preset); /** * Deserialize data SubGhzProtocolDecoderNiceFlo. diff --git a/lib/subghz/protocols/nice_flor_s.c b/lib/subghz/protocols/nice_flor_s.c index d567ddf2c99..411ceeacfd6 100644 --- a/lib/subghz/protocols/nice_flor_s.c +++ b/lib/subghz/protocols/nice_flor_s.c @@ -330,7 +330,7 @@ uint8_t subghz_protocol_decoder_nice_flor_s_get_hash_data(void* context) { bool subghz_protocol_decoder_nice_flor_s_serialize( void* context, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset) { + SubGhzRadioPreset* preset) { furi_assert(context); SubGhzProtocolDecoderNiceFlorS* instance = context; return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); diff --git a/lib/subghz/protocols/nice_flor_s.h b/lib/subghz/protocols/nice_flor_s.h index 286d03d44dc..593a7915769 100644 --- a/lib/subghz/protocols/nice_flor_s.h +++ b/lib/subghz/protocols/nice_flor_s.h @@ -49,13 +49,13 @@ uint8_t subghz_protocol_decoder_nice_flor_s_get_hash_data(void* context); * Serialize data SubGhzProtocolDecoderNiceFlorS. * @param context Pointer to a SubGhzProtocolDecoderNiceFlorS instance * @param flipper_format Pointer to a FlipperFormat instance - * @param preset The modulation on which the signal was received, SubGhzPresetDefinition + * @param preset The modulation on which the signal was received, SubGhzRadioPreset * @return true On success */ bool subghz_protocol_decoder_nice_flor_s_serialize( void* context, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset); + SubGhzRadioPreset* preset); /** * Deserialize data SubGhzProtocolDecoderNiceFlorS. diff --git a/lib/subghz/protocols/oregon2.c b/lib/subghz/protocols/oregon2.c index 561df819f64..244ac58d834 100644 --- a/lib/subghz/protocols/oregon2.c +++ b/lib/subghz/protocols/oregon2.c @@ -193,7 +193,7 @@ uint8_t subghz_protocol_decoder_oregon2_get_hash_data(void* context) { bool subghz_protocol_decoder_oregon2_serialize( void* context, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset) { + SubGhzRadioPreset* preset) { furi_assert(context); SubGhzProtocolDecoderOregon2* instance = context; if(!subghz_block_generic_serialize(&instance->generic, flipper_format, preset)) return false; diff --git a/lib/subghz/protocols/phoenix_v2.c b/lib/subghz/protocols/phoenix_v2.c index 019c3ec25aa..b3d6f1e98f5 100644 --- a/lib/subghz/protocols/phoenix_v2.c +++ b/lib/subghz/protocols/phoenix_v2.c @@ -296,7 +296,7 @@ uint8_t subghz_protocol_decoder_phoenix_v2_get_hash_data(void* context) { bool subghz_protocol_decoder_phoenix_v2_serialize( void* context, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset) { + SubGhzRadioPreset* preset) { furi_assert(context); SubGhzProtocolDecoderPhoenix_V2* instance = context; return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); diff --git a/lib/subghz/protocols/phoenix_v2.h b/lib/subghz/protocols/phoenix_v2.h index b2f18279665..48487535e78 100644 --- a/lib/subghz/protocols/phoenix_v2.h +++ b/lib/subghz/protocols/phoenix_v2.h @@ -83,13 +83,13 @@ uint8_t subghz_protocol_decoder_phoenix_v2_get_hash_data(void* context); * Serialize data SubGhzProtocolDecoderPhoenix_V2. * @param context Pointer to a SubGhzProtocolDecoderPhoenix_V2 instance * @param flipper_format Pointer to a FlipperFormat instance - * @param preset The modulation on which the signal was received, SubGhzPresetDefinition + * @param preset The modulation on which the signal was received, SubGhzRadioPreset * @return true On success */ bool subghz_protocol_decoder_phoenix_v2_serialize( void* context, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset); + SubGhzRadioPreset* preset); /** * Deserialize data SubGhzProtocolDecoderPhoenix_V2. diff --git a/lib/subghz/protocols/power_smart.c b/lib/subghz/protocols/power_smart.c index 3ec35c74f93..63ca78711e1 100644 --- a/lib/subghz/protocols/power_smart.c +++ b/lib/subghz/protocols/power_smart.c @@ -349,7 +349,7 @@ uint8_t subghz_protocol_decoder_power_smart_get_hash_data(void* context) { bool subghz_protocol_decoder_power_smart_serialize( void* context, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset) { + SubGhzRadioPreset* preset) { furi_assert(context); SubGhzProtocolDecoderPowerSmart* instance = context; return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); diff --git a/lib/subghz/protocols/power_smart.h b/lib/subghz/protocols/power_smart.h index 21e5dcf4abf..806729f8e93 100644 --- a/lib/subghz/protocols/power_smart.h +++ b/lib/subghz/protocols/power_smart.h @@ -83,13 +83,13 @@ uint8_t subghz_protocol_decoder_power_smart_get_hash_data(void* context); * Serialize data SubGhzProtocolDecoderPowerSmart. * @param context Pointer to a SubGhzProtocolDecoderPowerSmart instance * @param flipper_format Pointer to a FlipperFormat instance - * @param preset The modulation on which the signal was received, SubGhzPresetDefinition + * @param preset The modulation on which the signal was received, SubGhzRadioPreset * @return true On success */ bool subghz_protocol_decoder_power_smart_serialize( void* context, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset); + SubGhzRadioPreset* preset); /** * Deserialize data SubGhzProtocolDecoderPowerSmart. diff --git a/lib/subghz/protocols/princeton.c b/lib/subghz/protocols/princeton.c index 370f33fb02e..a4a0921acd6 100644 --- a/lib/subghz/protocols/princeton.c +++ b/lib/subghz/protocols/princeton.c @@ -312,7 +312,7 @@ uint8_t subghz_protocol_decoder_princeton_get_hash_data(void* context) { bool subghz_protocol_decoder_princeton_serialize( void* context, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset) { + SubGhzRadioPreset* preset) { furi_assert(context); SubGhzProtocolDecoderPrinceton* instance = context; bool res = subghz_block_generic_serialize(&instance->generic, flipper_format, preset); diff --git a/lib/subghz/protocols/princeton.h b/lib/subghz/protocols/princeton.h index 4e482f74798..f63004db464 100644 --- a/lib/subghz/protocols/princeton.h +++ b/lib/subghz/protocols/princeton.h @@ -83,13 +83,13 @@ uint8_t subghz_protocol_decoder_princeton_get_hash_data(void* context); * Serialize data SubGhzProtocolDecoderPrinceton. * @param context Pointer to a SubGhzProtocolDecoderPrinceton instance * @param flipper_format Pointer to a FlipperFormat instance - * @param preset The modulation on which the signal was received, SubGhzPresetDefinition + * @param preset The modulation on which the signal was received, SubGhzRadioPreset * @return true On success */ bool subghz_protocol_decoder_princeton_serialize( void* context, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset); + SubGhzRadioPreset* preset); /** * Deserialize data SubGhzProtocolDecoderPrinceton. diff --git a/lib/subghz/protocols/registry.c b/lib/subghz/protocols/protocol_items.c similarity index 58% rename from lib/subghz/protocols/registry.c rename to lib/subghz/protocols/protocol_items.c index 453fa747a0e..65ba0328068 100644 --- a/lib/subghz/protocols/registry.c +++ b/lib/subghz/protocols/protocol_items.c @@ -1,6 +1,6 @@ -#include "registry.h" +#include "protocol_items.h" -const SubGhzProtocol* subghz_protocol_registry[] = { +const SubGhzProtocol* subghz_protocol_registry_items[] = { &subghz_protocol_gate_tx, &subghz_protocol_keeloq, &subghz_protocol_star_line, &subghz_protocol_nice_flo, &subghz_protocol_came, &subghz_protocol_faac_slh, &subghz_protocol_nice_flor_s, &subghz_protocol_came_twee, &subghz_protocol_came_atomo, @@ -11,26 +11,9 @@ const SubGhzProtocol* subghz_protocol_registry[] = { &subghz_protocol_secplus_v1, &subghz_protocol_megacode, &subghz_protocol_holtek, &subghz_protocol_chamb_code, &subghz_protocol_power_smart, &subghz_protocol_marantec, &subghz_protocol_bett, &subghz_protocol_doitrand, &subghz_protocol_phoenix_v2, - &subghz_protocol_honeywell_wdb, &subghz_protocol_magellen, &subghz_protocol_intertechno_v3, + &subghz_protocol_honeywell_wdb, &subghz_protocol_magellan, &subghz_protocol_intertechno_v3, &subghz_protocol_clemsa, &subghz_protocol_oregon2}; -const SubGhzProtocol* subghz_protocol_registry_get_by_name(const char* name) { - for(size_t i = 0; i < subghz_protocol_registry_count(); i++) { - if(strcmp(name, subghz_protocol_registry[i]->name) == 0) { - return subghz_protocol_registry[i]; - } - } - return NULL; -} - -const SubGhzProtocol* subghz_protocol_registry_get_by_index(size_t index) { - if(index < subghz_protocol_registry_count()) { - return subghz_protocol_registry[index]; - } else { - return NULL; - } -} - -size_t subghz_protocol_registry_count() { - return COUNT_OF(subghz_protocol_registry); -} +const SubGhzProtocolRegistry subghz_protocol_registry = { + .items = subghz_protocol_registry_items, + .size = COUNT_OF(subghz_protocol_registry_items)}; \ No newline at end of file diff --git a/lib/subghz/protocols/registry.h b/lib/subghz/protocols/protocol_items.h similarity index 54% rename from lib/subghz/protocols/registry.h rename to lib/subghz/protocols/protocol_items.h index 8eb464ada3a..9c187e795a1 100644 --- a/lib/subghz/protocols/registry.h +++ b/lib/subghz/protocols/protocol_items.h @@ -1,6 +1,5 @@ #pragma once - -#include "../types.h" +#include "../registry.h" #include "princeton.h" #include "keeloq.h" @@ -33,27 +32,9 @@ #include "doitrand.h" #include "phoenix_v2.h" #include "honeywell_wdb.h" -#include "magellen.h" +#include "magellan.h" #include "intertechno_v3.h" #include "clemsa.h" #include "oregon2.h" -/** - * Registration by name SubGhzProtocol. - * @param name Protocol name - * @return SubGhzProtocol* pointer to a SubGhzProtocol instance - */ -const SubGhzProtocol* subghz_protocol_registry_get_by_name(const char* name); - -/** - * Registration protocol by index in array SubGhzProtocol. - * @param index Protocol by index in array - * @return SubGhzProtocol* pointer to a SubGhzProtocol instance - */ -const SubGhzProtocol* subghz_protocol_registry_get_by_index(size_t index); - -/** - * Getting the number of registered protocols. - * @return Number of protocols - */ -size_t subghz_protocol_registry_count(); +extern const SubGhzProtocolRegistry subghz_protocol_registry; diff --git a/lib/subghz/protocols/raw.c b/lib/subghz/protocols/raw.c index fd234502a63..b8e93c3d57a 100644 --- a/lib/subghz/protocols/raw.c +++ b/lib/subghz/protocols/raw.c @@ -84,7 +84,7 @@ const SubGhzProtocol subghz_protocol_raw = { bool subghz_protocol_raw_save_to_file_init( SubGhzProtocolDecoderRAW* instance, const char* dev_name, - SubGhzPresetDefinition* preset) { + SubGhzRadioPreset* preset) { furi_assert(instance); instance->storage = furi_record_open(RECORD_STORAGE); diff --git a/lib/subghz/protocols/raw.h b/lib/subghz/protocols/raw.h index be03dea2747..96c77553a1e 100644 --- a/lib/subghz/protocols/raw.h +++ b/lib/subghz/protocols/raw.h @@ -21,13 +21,13 @@ extern const SubGhzProtocol subghz_protocol_raw; * Open file for writing * @param instance Pointer to a SubGhzProtocolDecoderRAW instance * @param dev_name File name - * @param preset The modulation on which the signal was received, SubGhzPresetDefinition + * @param preset The modulation on which the signal was received, SubGhzRadioPreset * @return true On success */ bool subghz_protocol_raw_save_to_file_init( SubGhzProtocolDecoderRAW* instance, const char* dev_name, - SubGhzPresetDefinition* preset); + SubGhzRadioPreset* preset); /** * Stop writing file to flash diff --git a/lib/subghz/protocols/scher_khan.c b/lib/subghz/protocols/scher_khan.c index 837dcf05839..a9a3078e47f 100644 --- a/lib/subghz/protocols/scher_khan.c +++ b/lib/subghz/protocols/scher_khan.c @@ -251,7 +251,7 @@ uint8_t subghz_protocol_decoder_scher_khan_get_hash_data(void* context) { bool subghz_protocol_decoder_scher_khan_serialize( void* context, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset) { + SubGhzRadioPreset* preset) { furi_assert(context); SubGhzProtocolDecoderScherKhan* instance = context; return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); diff --git a/lib/subghz/protocols/scher_khan.h b/lib/subghz/protocols/scher_khan.h index f0fe595b455..b7e84ea1f35 100644 --- a/lib/subghz/protocols/scher_khan.h +++ b/lib/subghz/protocols/scher_khan.h @@ -49,13 +49,13 @@ uint8_t subghz_protocol_decoder_scher_khan_get_hash_data(void* context); * Serialize data SubGhzProtocolDecoderScherKhan. * @param context Pointer to a SubGhzProtocolDecoderScherKhan instance * @param flipper_format Pointer to a FlipperFormat instance - * @param preset The modulation on which the signal was received, SubGhzPresetDefinition + * @param preset The modulation on which the signal was received, SubGhzRadioPreset * @return true On success */ bool subghz_protocol_decoder_scher_khan_serialize( void* context, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset); + SubGhzRadioPreset* preset); /** * Deserialize data SubGhzProtocolDecoderScherKhan. diff --git a/lib/subghz/protocols/secplus_v1.c b/lib/subghz/protocols/secplus_v1.c index cfe6bdee53a..7bd0b5b791c 100644 --- a/lib/subghz/protocols/secplus_v1.c +++ b/lib/subghz/protocols/secplus_v1.c @@ -519,7 +519,7 @@ uint8_t subghz_protocol_decoder_secplus_v1_get_hash_data(void* context) { bool subghz_protocol_decoder_secplus_v1_serialize( void* context, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset) { + SubGhzRadioPreset* preset) { furi_assert(context); SubGhzProtocolDecoderSecPlus_v1* instance = context; return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); diff --git a/lib/subghz/protocols/secplus_v1.h b/lib/subghz/protocols/secplus_v1.h index ba1762f55de..99480b62b53 100644 --- a/lib/subghz/protocols/secplus_v1.h +++ b/lib/subghz/protocols/secplus_v1.h @@ -82,13 +82,13 @@ uint8_t subghz_protocol_decoder_secplus_v1_get_hash_data(void* context); * Serialize data SubGhzProtocolDecoderSecPlus_v1. * @param context Pointer to a SubGhzProtocolDecoderSecPlus_v1 instance * @param flipper_format Pointer to a FlipperFormat instance - * @param preset The modulation on which the signal was received, SubGhzPresetDefinition + * @param preset The modulation on which the signal was received, SubGhzRadioPreset * @return true On success */ bool subghz_protocol_decoder_secplus_v1_serialize( void* context, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset); + SubGhzRadioPreset* preset); /** * Deserialize data SubGhzProtocolDecoderSecPlus_v1. diff --git a/lib/subghz/protocols/secplus_v2.c b/lib/subghz/protocols/secplus_v2.c index 73bb7802b39..90cc805a339 100644 --- a/lib/subghz/protocols/secplus_v2.c +++ b/lib/subghz/protocols/secplus_v2.c @@ -592,7 +592,7 @@ bool subghz_protocol_secplus_v2_create_data( uint32_t serial, uint8_t btn, uint32_t cnt, - SubGhzPresetDefinition* preset) { + SubGhzRadioPreset* preset) { furi_assert(context); SubGhzProtocolEncoderSecPlus_v2* instance = context; instance->generic.serial = serial; @@ -759,7 +759,7 @@ uint8_t subghz_protocol_decoder_secplus_v2_get_hash_data(void* context) { bool subghz_protocol_decoder_secplus_v2_serialize( void* context, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset) { + SubGhzRadioPreset* preset) { furi_assert(context); SubGhzProtocolDecoderSecPlus_v2* instance = context; bool res = subghz_block_generic_serialize(&instance->generic, flipper_format, preset); diff --git a/lib/subghz/protocols/secplus_v2.h b/lib/subghz/protocols/secplus_v2.h index 007609fc1be..bea8cdb5d92 100644 --- a/lib/subghz/protocols/secplus_v2.h +++ b/lib/subghz/protocols/secplus_v2.h @@ -52,7 +52,7 @@ LevelDuration subghz_protocol_encoder_secplus_v2_yield(void* context); * @param btn Button number, 8 bit * @param cnt Container value, 28 bit * @param manufacture_name Name of manufacturer's key - * @param preset Modulation, SubGhzPresetDefinition + * @param preset Modulation, SubGhzRadioPreset * @return true On success */ bool subghz_protocol_secplus_v2_create_data( @@ -61,7 +61,7 @@ bool subghz_protocol_secplus_v2_create_data( uint32_t serial, uint8_t btn, uint32_t cnt, - SubGhzPresetDefinition* preset); + SubGhzRadioPreset* preset); /** * Allocate SubGhzProtocolDecoderSecPlus_v2. @@ -101,13 +101,13 @@ uint8_t subghz_protocol_decoder_secplus_v2_get_hash_data(void* context); * Serialize data SubGhzProtocolDecoderSecPlus_v2. * @param context Pointer to a SubGhzProtocolDecoderSecPlus_v2 instance * @param flipper_format Pointer to a FlipperFormat instance - * @param preset The modulation on which the signal was received, SubGhzPresetDefinition + * @param preset The modulation on which the signal was received, SubGhzRadioPreset * @return true On success */ bool subghz_protocol_decoder_secplus_v2_serialize( void* context, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset); + SubGhzRadioPreset* preset); /** * Deserialize data SubGhzProtocolDecoderSecPlus_v2. diff --git a/lib/subghz/protocols/somfy_keytis.c b/lib/subghz/protocols/somfy_keytis.c index cf627ef3732..00bb8a8cb1e 100644 --- a/lib/subghz/protocols/somfy_keytis.c +++ b/lib/subghz/protocols/somfy_keytis.c @@ -382,7 +382,7 @@ uint8_t subghz_protocol_decoder_somfy_keytis_get_hash_data(void* context) { bool subghz_protocol_decoder_somfy_keytis_serialize( void* context, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset) { + SubGhzRadioPreset* preset) { furi_assert(context); SubGhzProtocolDecoderSomfyKeytis* instance = context; bool res = subghz_block_generic_serialize(&instance->generic, flipper_format, preset); diff --git a/lib/subghz/protocols/somfy_keytis.h b/lib/subghz/protocols/somfy_keytis.h index af1df91d5f5..3b595061124 100644 --- a/lib/subghz/protocols/somfy_keytis.h +++ b/lib/subghz/protocols/somfy_keytis.h @@ -49,13 +49,13 @@ uint8_t subghz_protocol_decoder_somfy_keytis_get_hash_data(void* context); * Serialize data SubGhzProtocolDecoderSomfyKeytis. * @param context Pointer to a SubGhzProtocolDecoderSomfyKeytis instance * @param flipper_format Pointer to a FlipperFormat instance - * @param preset The modulation on which the signal was received, SubGhzPresetDefinition + * @param preset The modulation on which the signal was received, SubGhzRadioPreset * @return true On success */ bool subghz_protocol_decoder_somfy_keytis_serialize( void* context, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset); + SubGhzRadioPreset* preset); /** * Deserialize data SubGhzProtocolDecoderSomfyKeytis. diff --git a/lib/subghz/protocols/somfy_telis.c b/lib/subghz/protocols/somfy_telis.c index 6e375bb384f..362b1b07c4a 100644 --- a/lib/subghz/protocols/somfy_telis.c +++ b/lib/subghz/protocols/somfy_telis.c @@ -339,7 +339,7 @@ uint8_t subghz_protocol_decoder_somfy_telis_get_hash_data(void* context) { bool subghz_protocol_decoder_somfy_telis_serialize( void* context, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset) { + SubGhzRadioPreset* preset) { furi_assert(context); SubGhzProtocolDecoderSomfyTelis* instance = context; return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); diff --git a/lib/subghz/protocols/somfy_telis.h b/lib/subghz/protocols/somfy_telis.h index b6e58e34c1a..a6a9fa5b241 100644 --- a/lib/subghz/protocols/somfy_telis.h +++ b/lib/subghz/protocols/somfy_telis.h @@ -49,13 +49,13 @@ uint8_t subghz_protocol_decoder_somfy_telis_get_hash_data(void* context); * Serialize data SubGhzProtocolDecoderSomfyTelis. * @param context Pointer to a SubGhzProtocolDecoderSomfyTelis instance * @param flipper_format Pointer to a FlipperFormat instance - * @param preset The modulation on which the signal was received, SubGhzPresetDefinition + * @param preset The modulation on which the signal was received, SubGhzRadioPreset * @return true On success */ bool subghz_protocol_decoder_somfy_telis_serialize( void* context, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset); + SubGhzRadioPreset* preset); /** * Deserialize data SubGhzProtocolDecoderSomfyTelis. diff --git a/lib/subghz/protocols/star_line.c b/lib/subghz/protocols/star_line.c index 4b89bd2bf4a..8e5e07c1f44 100644 --- a/lib/subghz/protocols/star_line.c +++ b/lib/subghz/protocols/star_line.c @@ -319,7 +319,7 @@ uint8_t subghz_protocol_decoder_star_line_get_hash_data(void* context) { bool subghz_protocol_decoder_star_line_serialize( void* context, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset) { + SubGhzRadioPreset* preset) { furi_assert(context); SubGhzProtocolDecoderStarLine* instance = context; subghz_protocol_star_line_check_remote_controller( diff --git a/lib/subghz/protocols/star_line.h b/lib/subghz/protocols/star_line.h index 74c994101e2..34c25150474 100644 --- a/lib/subghz/protocols/star_line.h +++ b/lib/subghz/protocols/star_line.h @@ -49,13 +49,13 @@ uint8_t subghz_protocol_decoder_star_line_get_hash_data(void* context); * Serialize data SubGhzProtocolDecoderStarLine. * @param context Pointer to a SubGhzProtocolDecoderStarLine instance * @param flipper_format Pointer to a FlipperFormat instance - * @param preset The modulation on which the signal was received, SubGhzPresetDefinition + * @param preset The modulation on which the signal was received, SubGhzRadioPreset * @return true On success */ bool subghz_protocol_decoder_star_line_serialize( void* context, FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset); + SubGhzRadioPreset* preset); /** * Deserialize data SubGhzProtocolDecoderStarLine. diff --git a/lib/subghz/receiver.c b/lib/subghz/receiver.c index cf4085df447..fd6f1493e24 100644 --- a/lib/subghz/receiver.c +++ b/lib/subghz/receiver.c @@ -1,6 +1,7 @@ #include "receiver.h" -#include "protocols/registry.h" +#include "registry.h" +#include "protocols/protocol_items.h" #include @@ -22,9 +23,12 @@ struct SubGhzReceiver { SubGhzReceiver* subghz_receiver_alloc_init(SubGhzEnvironment* environment) { SubGhzReceiver* instance = malloc(sizeof(SubGhzReceiver)); SubGhzReceiverSlotArray_init(instance->slots); + const SubGhzProtocolRegistry* protocol_registry_items = + subghz_environment_get_protocol_registry(environment); - for(size_t i = 0; i < subghz_protocol_registry_count(); ++i) { - const SubGhzProtocol* protocol = subghz_protocol_registry_get_by_index(i); + for(size_t i = 0; i < subghz_protocol_registry_count(protocol_registry_items); ++i) { + const SubGhzProtocol* protocol = + subghz_protocol_registry_get_by_index(protocol_registry_items, i); if(protocol->decoder && protocol->decoder->alloc) { SubGhzReceiverSlot* slot = SubGhzReceiverSlotArray_push_new(instance->slots); @@ -34,7 +38,6 @@ SubGhzReceiver* subghz_receiver_alloc_init(SubGhzEnvironment* environment) { instance->callback = NULL; instance->context = NULL; - return instance; } diff --git a/lib/subghz/registry.c b/lib/subghz/registry.c new file mode 100644 index 00000000000..d0c22ea8c4a --- /dev/null +++ b/lib/subghz/registry.c @@ -0,0 +1,30 @@ +#include "registry.h" + +const SubGhzProtocol* subghz_protocol_registry_get_by_name( + const SubGhzProtocolRegistry* protocol_registry, + const char* name) { + furi_assert(protocol_registry); + + for(size_t i = 0; i < subghz_protocol_registry_count(protocol_registry); i++) { + if(strcmp(name, protocol_registry->items[i]->name) == 0) { + return protocol_registry->items[i]; + } + } + return NULL; +} + +const SubGhzProtocol* subghz_protocol_registry_get_by_index( + const SubGhzProtocolRegistry* protocol_registry, + size_t index) { + furi_assert(protocol_registry); + if(index < subghz_protocol_registry_count(protocol_registry)) { + return protocol_registry->items[index]; + } else { + return NULL; + } +} + +size_t subghz_protocol_registry_count(const SubGhzProtocolRegistry* protocol_registry) { + furi_assert(protocol_registry); + return protocol_registry->size; +} diff --git a/lib/subghz/registry.h b/lib/subghz/registry.h new file mode 100644 index 00000000000..062cdf68f87 --- /dev/null +++ b/lib/subghz/registry.h @@ -0,0 +1,39 @@ +#pragma once + +#include "types.h" + +typedef struct SubGhzEnvironment SubGhzEnvironment; + +typedef struct SubGhzProtocolRegistry SubGhzProtocolRegistry; + +struct SubGhzProtocolRegistry { + const SubGhzProtocol** items; + const size_t size; +}; + +/** + * Registration by name SubGhzProtocol. + * @param protocol_registry SubGhzProtocolRegistry + * @param name Protocol name + * @return SubGhzProtocol* pointer to a SubGhzProtocol instance + */ +const SubGhzProtocol* subghz_protocol_registry_get_by_name( + const SubGhzProtocolRegistry* protocol_registry, + const char* name); + +/** + * Registration protocol by index in array SubGhzProtocol. + * @param protocol_registry SubGhzProtocolRegistry + * @param index Protocol by index in array + * @return SubGhzProtocol* pointer to a SubGhzProtocol instance + */ +const SubGhzProtocol* subghz_protocol_registry_get_by_index( + const SubGhzProtocolRegistry* protocol_registry, + size_t index); + +/** + * Getting the number of registered protocols. + * @param protocol_registry SubGhzProtocolRegistry + * @return Number of protocols + */ +size_t subghz_protocol_registry_count(const SubGhzProtocolRegistry* protocol_registry); diff --git a/applications/main/subghz/subghz_setting.c b/lib/subghz/subghz_setting.c similarity index 99% rename from applications/main/subghz/subghz_setting.c rename to lib/subghz/subghz_setting.c index e0322f6a977..c5ec5db75cc 100644 --- a/applications/main/subghz/subghz_setting.c +++ b/lib/subghz/subghz_setting.c @@ -1,5 +1,6 @@ #include "subghz_setting.h" -#include "subghz_i.h" +#include "types.h" +//#include "subghz_i.h" #include #include @@ -324,7 +325,7 @@ void subghz_setting_load(SubGhzSetting* instance, const char* file_path) { if(file_path) { do { if(!flipper_format_file_open_existing(fff_data_file, file_path)) { - FURI_LOG_E(TAG, "Error open file %s", file_path); + FURI_LOG_I(TAG, "File is not used %s", file_path); break; } diff --git a/applications/main/subghz/subghz_setting.h b/lib/subghz/subghz_setting.h similarity index 95% rename from applications/main/subghz/subghz_setting.h rename to lib/subghz/subghz_setting.h index 0590cf499fd..cd82b302b00 100644 --- a/applications/main/subghz/subghz_setting.h +++ b/lib/subghz/subghz_setting.h @@ -6,6 +6,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + #define SUBGHZ_SETTING_DEFAULT_PRESET_COUNT 4 typedef struct SubGhzSetting SubGhzSetting; @@ -46,3 +50,7 @@ uint32_t subghz_setting_get_hopper_frequency(SubGhzSetting* instance, size_t idx uint32_t subghz_setting_get_frequency_default_index(SubGhzSetting* instance); uint32_t subghz_setting_get_default_frequency(SubGhzSetting* instance); + +#ifdef __cplusplus +} +#endif diff --git a/lib/subghz/transmitter.c b/lib/subghz/transmitter.c index 0dc2bacde41..8507ee054b8 100644 --- a/lib/subghz/transmitter.c +++ b/lib/subghz/transmitter.c @@ -1,7 +1,8 @@ #include "transmitter.h" #include "protocols/base.h" -#include "protocols/registry.h" +#include "registry.h" +#include "protocols/protocol_items.h" struct SubGhzTransmitter { const SubGhzProtocol* protocol; @@ -11,14 +12,17 @@ struct SubGhzTransmitter { SubGhzTransmitter* subghz_transmitter_alloc_init(SubGhzEnvironment* environment, const char* protocol_name) { SubGhzTransmitter* instance = NULL; - const SubGhzProtocol* protocol = subghz_protocol_registry_get_by_name(protocol_name); + const SubGhzProtocolRegistry* protocol_registry_items = + subghz_environment_get_protocol_registry(environment); + + const SubGhzProtocol* protocol = + subghz_protocol_registry_get_by_name(protocol_registry_items, protocol_name); if(protocol && protocol->encoder && protocol->encoder->alloc) { instance = malloc(sizeof(SubGhzTransmitter)); instance->protocol = protocol; instance->protocol_instance = instance->protocol->encoder->alloc(environment); } - return instance; } diff --git a/lib/subghz/types.h b/lib/subghz/types.h index dd9cefb8bfa..6c34dc7281d 100644 --- a/lib/subghz/types.h +++ b/lib/subghz/types.h @@ -10,7 +10,6 @@ #include "environment.h" #include #include -#include #define SUBGHZ_APP_FOLDER ANY_PATH("subghz") #define SUBGHZ_RAW_FOLDER EXT_PATH("subghz") @@ -22,19 +21,21 @@ #define SUBGHZ_RAW_FILE_VERSION 1 #define SUBGHZ_RAW_FILE_TYPE "Flipper SubGhz RAW File" -// -// Abstract method types -// +// Radio Preset +typedef struct { + FuriString* name; + uint32_t frequency; + uint8_t* data; + size_t data_size; +} SubGhzRadioPreset; // Allocator and Deallocator typedef void* (*SubGhzAlloc)(SubGhzEnvironment* environment); typedef void (*SubGhzFree)(void* context); // Serialize and Deserialize -typedef bool (*SubGhzSerialize)( - void* context, - FlipperFormat* flipper_format, - SubGhzPresetDefinition* preset); +typedef bool ( + *SubGhzSerialize)(void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset); typedef bool (*SubGhzDeserialize)(void* context, FlipperFormat* flipper_format); // Decoder specific @@ -74,6 +75,8 @@ typedef enum { SubGhzProtocolTypeStatic, SubGhzProtocolTypeDynamic, SubGhzProtocolTypeRAW, + SubGhzProtocolWeatherStation, + SubGhzProtocolCustom, } SubGhzProtocolType; typedef enum { From 42df7aa04a65314e1ae943ecd5c5beea32cda031 Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Thu, 20 Oct 2022 20:58:11 +1000 Subject: [PATCH 161/824] Fix FuriString oplist (init move) (#1894) * FuriString, Infrared: fix oplist and drop string_t. * Elf loader: log size * Comment fix * API: furi_hal_console_init --- applications/main/infrared/infrared_cli.c | 15 +++++------ firmware/targets/f7/api_symbols.csv | 4 +-- furi/core/string.h | 31 +++++++++++++---------- lib/flipper_application/elf/elf_file.c | 10 ++++++++ 4 files changed, 37 insertions(+), 23 deletions(-) diff --git a/applications/main/infrared/infrared_cli.c b/applications/main/infrared/infrared_cli.c index 7723dc97376..5a04f7495b3 100644 --- a/applications/main/infrared/infrared_cli.c +++ b/applications/main/infrared/infrared_cli.c @@ -9,12 +9,11 @@ #include "infrared_signal.h" #include "infrared_brute_force.h" -#include "m-dict.h" -#include "m-string.h" +#include #define INFRARED_CLI_BUF_SIZE 10 -DICT_DEF2(dict_signals, string_t, STRING_OPLIST, int, M_DEFAULT_OPLIST) +DICT_DEF2(dict_signals, FuriString*, FURI_STRING_OPLIST, int, M_DEFAULT_OPLIST) enum RemoteTypes { TV = 0, AC = 1 }; @@ -416,7 +415,7 @@ static void infrared_cli_list_remote_signals(enum RemoteTypes remote_type) { Storage* storage = furi_record_open(RECORD_STORAGE); FlipperFormat* ff = flipper_format_buffered_file_alloc(storage); dict_signals_t signals_dict; - string_t key; + FuriString* key; const char* remote_file = NULL; bool success = false; int max = 1; @@ -433,7 +432,7 @@ static void infrared_cli_list_remote_signals(enum RemoteTypes remote_type) { } dict_signals_init(signals_dict); - string_init(key); + key = furi_string_alloc(); success = flipper_format_buffered_file_open_existing(ff, remote_file); if(success) { @@ -441,7 +440,7 @@ static void infrared_cli_list_remote_signals(enum RemoteTypes remote_type) { signal_name = furi_string_alloc(); printf("Valid signals:\r\n"); while(flipper_format_read_string(ff, "name", signal_name)) { - string_set_str(key, furi_string_get_cstr(signal_name)); + furi_string_set_str(key, furi_string_get_cstr(signal_name)); int* v = dict_signals_get(signals_dict, key); if(v != NULL) { (*v)++; @@ -453,12 +452,12 @@ static void infrared_cli_list_remote_signals(enum RemoteTypes remote_type) { dict_signals_it_t it; for(dict_signals_it(it, signals_dict); !dict_signals_end_p(it); dict_signals_next(it)) { const struct dict_signals_pair_s* pair = dict_signals_cref(it); - printf("\t%s\r\n", string_get_cstr(pair->key)); + printf("\t%s\r\n", furi_string_get_cstr(pair->key)); } furi_string_free(signal_name); } - string_clear(key); + furi_string_free(key); dict_signals_clear(signals_dict); flipper_format_free(ff); furi_record_close(RECORD_STORAGE); diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index b65de9a946c..6aa8591e7d1 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,3.3,, +Version,+,3.4,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -976,7 +976,7 @@ Function,-,furi_hal_compress_icon_decode,void,"const uint8_t*, uint8_t**" Function,-,furi_hal_compress_icon_init,void, Function,+,furi_hal_console_disable,void, Function,+,furi_hal_console_enable,void, -Function,-,furi_hal_console_init,void, +Function,+,furi_hal_console_init,void, Function,+,furi_hal_console_printf,void,"const char[], ..." Function,+,furi_hal_console_puts,void,const char* Function,+,furi_hal_console_set_tx_callback,void,"FuriHalConsoleTxCallback, void*" diff --git a/furi/core/string.h b/furi/core/string.h index 01b26cce107..0523d3ba04e 100644 --- a/furi/core/string.h +++ b/furi/core/string.h @@ -718,22 +718,27 @@ void furi_string_utf8_decode(char c, FuriStringUTF8State* state, FuriStringUnico */ #define F_STR_INIT_SET(a, b) ((a) = furi_string_alloc_set(b)) +/** + * @brief INIT MOVE OPLIST for FuriString. + */ +#define F_STR_INIT_MOVE(a, b) ((a) = furi_string_alloc_move(b)) + /** * @brief OPLIST for FuriString. */ -#define FURI_STRING_OPLIST \ - (INIT(F_STR_INIT), \ - INIT_SET(F_STR_INIT_SET), \ - SET(furi_string_set), \ - INIT_MOVE(furi_string_alloc_move), \ - MOVE(furi_string_move), \ - SWAP(furi_string_swap), \ - RESET(furi_string_reset), \ - EMPTY_P(furi_string_empty), \ - CLEAR(furi_string_free), \ - HASH(furi_string_hash), \ - EQUAL(furi_string_equal), \ - CMP(furi_string_cmp), \ +#define FURI_STRING_OPLIST \ + (INIT(F_STR_INIT), \ + INIT_SET(F_STR_INIT_SET), \ + SET(furi_string_set), \ + INIT_MOVE(F_STR_INIT_MOVE), \ + MOVE(furi_string_move), \ + SWAP(furi_string_swap), \ + RESET(furi_string_reset), \ + EMPTY_P(furi_string_empty), \ + CLEAR(furi_string_free), \ + HASH(furi_string_hash), \ + EQUAL(furi_string_equal), \ + CMP(furi_string_cmp), \ TYPE(FuriString*)) #ifdef __cplusplus diff --git a/lib/flipper_application/elf/elf_file.c b/lib/flipper_application/elf/elf_file.c index 3fe57162fc4..2082e550f96 100644 --- a/lib/flipper_application/elf/elf_file.c +++ b/lib/flipper_application/elf/elf_file.c @@ -786,6 +786,16 @@ ELFFileLoadStatus elf_file_load_sections(ELFFile* elf) { FURI_LOG_D(TAG, "Trampoline cache size: %u", AddressCache_size(elf->trampoline_cache)); AddressCache_clear(elf->relocation_cache); + { + size_t total_size = 0; + for(ELFSectionDict_it(it, elf->sections); !ELFSectionDict_end_p(it); + ELFSectionDict_next(it)) { + ELFSectionDict_itref_t* itref = ELFSectionDict_ref(it); + total_size += itref->value.size; + } + FURI_LOG_I(TAG, "Total size of loaded sections: %u", total_size); + } + return status; } From f56c94922d839f161be9f699b024f2a25d0c41db Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Thu, 20 Oct 2022 21:42:55 +1000 Subject: [PATCH 162/824] CMSIS DAP/DAP Link Debugger (#1897) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Apps: DAP Link * API: furi_hal_console_init Co-authored-by: あく --- .gitmodules | 3 + applications/plugins/dap_link/README.md | 105 ++ applications/plugins/dap_link/application.fam | 24 + applications/plugins/dap_link/dap_config.h | 234 +++++ applications/plugins/dap_link/dap_link.c | 521 +++++++++ applications/plugins/dap_link/dap_link.h | 55 + applications/plugins/dap_link/dap_link.png | Bin 0 -> 143 bytes applications/plugins/dap_link/gui/dap_gui.c | 92 ++ applications/plugins/dap_link/gui/dap_gui.h | 4 + .../dap_link/gui/dap_gui_custom_event.h | 7 + applications/plugins/dap_link/gui/dap_gui_i.h | 34 + .../dap_link/gui/scenes/config/dap_scene.c | 30 + .../dap_link/gui/scenes/config/dap_scene.h | 29 + .../gui/scenes/config/dap_scene_config.h | 4 + .../dap_link/gui/scenes/dap_scene_about.c | 68 ++ .../dap_link/gui/scenes/dap_scene_config.c | 107 ++ .../dap_link/gui/scenes/dap_scene_help.c | 102 ++ .../dap_link/gui/scenes/dap_scene_main.c | 154 +++ .../dap_link/gui/views/dap_main_view.c | 189 ++++ .../dap_link/gui/views/dap_main_view.h | 45 + .../dap_link/icons/ArrowDownEmpty_12x18.png | Bin 0 -> 160 bytes .../dap_link/icons/ArrowDownFilled_12x18.png | Bin 0 -> 168 bytes .../dap_link/icons/ArrowUpEmpty_12x18.png | Bin 0 -> 159 bytes .../dap_link/icons/ArrowUpFilled_12x18.png | Bin 0 -> 173 bytes applications/plugins/dap_link/lib/free-dap | 1 + .../plugins/dap_link/usb/dap_v2_usb.c | 994 ++++++++++++++++++ .../plugins/dap_link/usb/dap_v2_usb.h | 55 + .../plugins/dap_link/usb/usb_winusb.h | 143 +++ 28 files changed, 3000 insertions(+) create mode 100644 applications/plugins/dap_link/README.md create mode 100644 applications/plugins/dap_link/application.fam create mode 100644 applications/plugins/dap_link/dap_config.h create mode 100644 applications/plugins/dap_link/dap_link.c create mode 100644 applications/plugins/dap_link/dap_link.h create mode 100644 applications/plugins/dap_link/dap_link.png create mode 100644 applications/plugins/dap_link/gui/dap_gui.c create mode 100644 applications/plugins/dap_link/gui/dap_gui.h create mode 100644 applications/plugins/dap_link/gui/dap_gui_custom_event.h create mode 100644 applications/plugins/dap_link/gui/dap_gui_i.h create mode 100644 applications/plugins/dap_link/gui/scenes/config/dap_scene.c create mode 100644 applications/plugins/dap_link/gui/scenes/config/dap_scene.h create mode 100644 applications/plugins/dap_link/gui/scenes/config/dap_scene_config.h create mode 100644 applications/plugins/dap_link/gui/scenes/dap_scene_about.c create mode 100644 applications/plugins/dap_link/gui/scenes/dap_scene_config.c create mode 100644 applications/plugins/dap_link/gui/scenes/dap_scene_help.c create mode 100644 applications/plugins/dap_link/gui/scenes/dap_scene_main.c create mode 100644 applications/plugins/dap_link/gui/views/dap_main_view.c create mode 100644 applications/plugins/dap_link/gui/views/dap_main_view.h create mode 100644 applications/plugins/dap_link/icons/ArrowDownEmpty_12x18.png create mode 100644 applications/plugins/dap_link/icons/ArrowDownFilled_12x18.png create mode 100644 applications/plugins/dap_link/icons/ArrowUpEmpty_12x18.png create mode 100644 applications/plugins/dap_link/icons/ArrowUpFilled_12x18.png create mode 160000 applications/plugins/dap_link/lib/free-dap create mode 100644 applications/plugins/dap_link/usb/dap_v2_usb.c create mode 100644 applications/plugins/dap_link/usb/dap_v2_usb.h create mode 100644 applications/plugins/dap_link/usb/usb_winusb.h diff --git a/.gitmodules b/.gitmodules index ba764498120..308d60fdc2a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -31,3 +31,6 @@ [submodule "lib/cxxheaderparser"] path = lib/cxxheaderparser url = https://github.com/robotpy/cxxheaderparser.git +[submodule "applications/plugins/dap_link/lib/free-dap"] + path = applications/plugins/dap_link/lib/free-dap + url = https://github.com/ataradov/free-dap.git diff --git a/applications/plugins/dap_link/README.md b/applications/plugins/dap_link/README.md new file mode 100644 index 00000000000..aead0a60a22 --- /dev/null +++ b/applications/plugins/dap_link/README.md @@ -0,0 +1,105 @@ +# Flipper Zero as CMSIS DAP/DAP Link +Flipper Zero as a [Free-DAP](https://github.com/ataradov/free-dap) based SWD\JTAG debugger. Free-DAP is a free and open source firmware implementation of the [CMSIS-DAP](https://www.keil.com/pack/doc/CMSIS_Dev/DAP/html/index.html) debugger. + +## Protocols +SWD, JTAG , CMSIS-DAP v1 (18 KiB/s), CMSIS-DAP v2 (46 KiB/s), VCP (USB-UART). + +WinUSB for driverless installation for Windows 8 and above. + +## Usage + +### VSCode + Cortex-Debug + Set `"device": "cmsis-dap"` + +
+ BluePill configuration example + + ```json +{ + "name": "Attach (DAP)", + "cwd": "${workspaceFolder}", + "executable": "./build/firmware.elf", + "request": "attach", + "type": "cortex-debug", + "servertype": "openocd", + "device": "cmsis-dap", + "configFiles": [ + "interface/cmsis-dap.cfg", + "target/stm32f1x.cfg", + ], +}, + ``` +
+ +
+ Flipper Zero configuration example + + ```json +{ + "name": "Attach (DAP)", + "cwd": "${workspaceFolder}", + "executable": "./build/latest/firmware.elf", + "request": "attach", + "type": "cortex-debug", + "servertype": "openocd", + "device": "cmsis-dap", + "svdFile": "./debug/STM32WB55_CM4.svd", + "rtos": "FreeRTOS", + "configFiles": [ + "interface/cmsis-dap.cfg", + "./debug/stm32wbx.cfg", + ], + "postAttachCommands": [ + "source debug/flipperapps.py", + ], +}, + ``` +
+ +### OpenOCD +Use `interface/cmsis-dap.cfg`. You will need OpenOCD v0.11.0. + +Additional commands: +* `cmsis_dap_backend hid` for CMSIS-DAP v1 protocol. +* `cmsis_dap_backend usb_bulk` for CMSIS-DAP v2 protocol. +* `cmsis_dap_serial DAP_Oyevoxo` use DAP-Link running on Flipper named `Oyevoxo`. +* `cmsis-dap cmd 81` - reboot connected DAP-Link. + +
+ Flash BluePill + + ``` +openocd -f interface/cmsis-dap.cfg -f target/stm32f1x.cfg -c init -c "program build/firmware.bin reset exit 0x8000000" + ``` +
+ +
+ Flash Flipper Zero using DAP v2 protocol + + ``` +openocd -f interface/cmsis-dap.cfg -c "cmsis_dap_backend usb_bulk" -f debug/stm32wbx.cfg -c init -c "program build/latest/firmware.bin reset exit 0x8000000" + ``` +
+ +
+ Reboot connected DAP-Link on Flipper named Oyevoxo + + ``` +openocd -f interface/cmsis-dap.cfg -c "cmsis_dap_serial DAP_Oyevoxo" -c "transport select swd" -c "adapter speed 4000000" -c init -c "cmsis-dap cmd 81" -c "exit" + ``` +
+ +### PlatformIO +Use `debug_tool = cmsis-dap` and `upload_protocol = cmsis-dap`. [Documentation](https://docs.platformio.org/en/latest/plus/debug-tools/cmsis-dap.html#debugging-tool-cmsis-dap). Remember that Windows 8 and above do not require drivers. + +
+ BluePill platformio.ini example + + ``` +[env:bluepill_f103c8] +platform = ststm32 +board = bluepill_f103c8 +debug_tool = cmsis-dap +upload_protocol = cmsis-dap + ``` +
diff --git a/applications/plugins/dap_link/application.fam b/applications/plugins/dap_link/application.fam new file mode 100644 index 00000000000..3b99d5ef372 --- /dev/null +++ b/applications/plugins/dap_link/application.fam @@ -0,0 +1,24 @@ +App( + appid="dap_link", + name="DAP Link", + apptype=FlipperAppType.PLUGIN, + entry_point="dap_link_app", + requires=[ + "gui", + "dialogs", + ], + stack_size=4 * 1024, + order=20, + fap_icon="dap_link.png", + fap_category="Tools", + fap_private_libs=[ + Lib( + name="free-dap", + cincludes=["."], + sources=[ + "dap.c", + ], + ), + ], + fap_icon_assets="icons", +) diff --git a/applications/plugins/dap_link/dap_config.h b/applications/plugins/dap_link/dap_config.h new file mode 100644 index 00000000000..88b90bd34a9 --- /dev/null +++ b/applications/plugins/dap_link/dap_config.h @@ -0,0 +1,234 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2022, Alex Taradov . All rights reserved. + +#ifndef _DAP_CONFIG_H_ +#define _DAP_CONFIG_H_ + +/*- Includes ----------------------------------------------------------------*/ +#include + +/*- Definitions -------------------------------------------------------------*/ +#define DAP_CONFIG_ENABLE_JTAG + +#define DAP_CONFIG_DEFAULT_PORT DAP_PORT_SWD +#define DAP_CONFIG_DEFAULT_CLOCK 4200000 // Hz + +#define DAP_CONFIG_PACKET_SIZE 64 +#define DAP_CONFIG_PACKET_COUNT 1 + +#define DAP_CONFIG_JTAG_DEV_COUNT 8 + +// DAP_CONFIG_PRODUCT_STR must contain "CMSIS-DAP" to be compatible with the standard +#define DAP_CONFIG_VENDOR_STR "Flipper Zero" +#define DAP_CONFIG_PRODUCT_STR "Generic CMSIS-DAP Adapter" +#define DAP_CONFIG_SER_NUM_STR usb_serial_number +#define DAP_CONFIG_CMSIS_DAP_VER_STR "2.0.0" + +#define DAP_CONFIG_RESET_TARGET_FN dap_app_target_reset +#define DAP_CONFIG_VENDOR_FN dap_app_vendor_cmd + +// Attribute to use for performance-critical functions +#define DAP_CONFIG_PERFORMANCE_ATTR + +// A value at which dap_clock_test() produces 1 kHz output on the SWCLK pin +// #define DAP_CONFIG_DELAY_CONSTANT 19000 +#define DAP_CONFIG_DELAY_CONSTANT 6290 + +// A threshold for switching to fast clock (no added delays) +// This is the frequency produced by dap_clock_test(1) on the SWCLK pin +#define DAP_CONFIG_FAST_CLOCK 2400000 // Hz + +/*- Prototypes --------------------------------------------------------------*/ +extern char usb_serial_number[16]; + +/*- Implementations ---------------------------------------------------------*/ +extern GpioPin flipper_dap_swclk_pin; +extern GpioPin flipper_dap_swdio_pin; +extern GpioPin flipper_dap_reset_pin; +extern GpioPin flipper_dap_tdo_pin; +extern GpioPin flipper_dap_tdi_pin; + +extern void dap_app_vendor_cmd(uint8_t cmd); +extern void dap_app_target_reset(); +extern void dap_app_disconnect(); +extern void dap_app_connect_swd(); +extern void dap_app_connect_jtag(); + +//----------------------------------------------------------------------------- +static inline void DAP_CONFIG_SWCLK_TCK_write(int value) { + furi_hal_gpio_write(&flipper_dap_swclk_pin, value); +} + +//----------------------------------------------------------------------------- +static inline void DAP_CONFIG_SWDIO_TMS_write(int value) { + furi_hal_gpio_write(&flipper_dap_swdio_pin, value); +} + +//----------------------------------------------------------------------------- +static inline void DAP_CONFIG_TDI_write(int value) { +#ifdef DAP_CONFIG_ENABLE_JTAG + furi_hal_gpio_write(&flipper_dap_tdi_pin, value); +#else + (void)value; +#endif +} + +//----------------------------------------------------------------------------- +static inline void DAP_CONFIG_TDO_write(int value) { +#ifdef DAP_CONFIG_ENABLE_JTAG + furi_hal_gpio_write(&flipper_dap_tdo_pin, value); +#else + (void)value; +#endif +} + +//----------------------------------------------------------------------------- +static inline void DAP_CONFIG_nTRST_write(int value) { + (void)value; +} + +//----------------------------------------------------------------------------- +static inline void DAP_CONFIG_nRESET_write(int value) { + furi_hal_gpio_write(&flipper_dap_reset_pin, value); +} + +//----------------------------------------------------------------------------- +static inline int DAP_CONFIG_SWCLK_TCK_read(void) { + return furi_hal_gpio_read(&flipper_dap_swclk_pin); +} + +//----------------------------------------------------------------------------- +static inline int DAP_CONFIG_SWDIO_TMS_read(void) { + return furi_hal_gpio_read(&flipper_dap_swdio_pin); +} + +//----------------------------------------------------------------------------- +static inline int DAP_CONFIG_TDO_read(void) { +#ifdef DAP_CONFIG_ENABLE_JTAG + return furi_hal_gpio_read(&flipper_dap_tdo_pin); +#else + return 0; +#endif +} + +//----------------------------------------------------------------------------- +static inline int DAP_CONFIG_TDI_read(void) { +#ifdef DAP_CONFIG_ENABLE_JTAG + return furi_hal_gpio_read(&flipper_dap_tdi_pin); +#else + return 0; +#endif +} + +//----------------------------------------------------------------------------- +static inline int DAP_CONFIG_nTRST_read(void) { + return 0; +} + +//----------------------------------------------------------------------------- +static inline int DAP_CONFIG_nRESET_read(void) { + return furi_hal_gpio_read(&flipper_dap_reset_pin); +} + +//----------------------------------------------------------------------------- +static inline void DAP_CONFIG_SWCLK_TCK_set(void) { + LL_GPIO_SetOutputPin(flipper_dap_swclk_pin.port, flipper_dap_swclk_pin.pin); +} + +//----------------------------------------------------------------------------- +static inline void DAP_CONFIG_SWCLK_TCK_clr(void) { + LL_GPIO_ResetOutputPin(flipper_dap_swclk_pin.port, flipper_dap_swclk_pin.pin); +} + +//----------------------------------------------------------------------------- +static inline void DAP_CONFIG_SWDIO_TMS_in(void) { + LL_GPIO_SetPinMode(flipper_dap_swdio_pin.port, flipper_dap_swdio_pin.pin, LL_GPIO_MODE_INPUT); +} + +//----------------------------------------------------------------------------- +static inline void DAP_CONFIG_SWDIO_TMS_out(void) { + LL_GPIO_SetPinMode(flipper_dap_swdio_pin.port, flipper_dap_swdio_pin.pin, LL_GPIO_MODE_OUTPUT); +} + +//----------------------------------------------------------------------------- +static inline void DAP_CONFIG_SETUP(void) { + furi_hal_gpio_init(&flipper_dap_swdio_pin, GpioModeInput, GpioPullNo, GpioSpeedVeryHigh); + furi_hal_gpio_init(&flipper_dap_swclk_pin, GpioModeInput, GpioPullNo, GpioSpeedVeryHigh); + furi_hal_gpio_init(&flipper_dap_reset_pin, GpioModeInput, GpioPullNo, GpioSpeedVeryHigh); +#ifdef DAP_CONFIG_ENABLE_JTAG + furi_hal_gpio_init(&flipper_dap_tdo_pin, GpioModeInput, GpioPullNo, GpioSpeedVeryHigh); + furi_hal_gpio_init(&flipper_dap_tdi_pin, GpioModeInput, GpioPullNo, GpioSpeedVeryHigh); +#endif +} + +//----------------------------------------------------------------------------- +static inline void DAP_CONFIG_DISCONNECT(void) { + furi_hal_gpio_init(&flipper_dap_swdio_pin, GpioModeInput, GpioPullNo, GpioSpeedVeryHigh); + furi_hal_gpio_init(&flipper_dap_swclk_pin, GpioModeInput, GpioPullNo, GpioSpeedVeryHigh); + furi_hal_gpio_init(&flipper_dap_reset_pin, GpioModeInput, GpioPullNo, GpioSpeedVeryHigh); +#ifdef DAP_CONFIG_ENABLE_JTAG + furi_hal_gpio_init(&flipper_dap_tdo_pin, GpioModeInput, GpioPullNo, GpioSpeedVeryHigh); + furi_hal_gpio_init(&flipper_dap_tdi_pin, GpioModeInput, GpioPullNo, GpioSpeedVeryHigh); +#endif + dap_app_disconnect(); +} + +//----------------------------------------------------------------------------- +static inline void DAP_CONFIG_CONNECT_SWD(void) { + furi_hal_gpio_init( + &flipper_dap_swdio_pin, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); + furi_hal_gpio_write(&flipper_dap_swdio_pin, true); + + furi_hal_gpio_init( + &flipper_dap_swclk_pin, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); + furi_hal_gpio_write(&flipper_dap_swclk_pin, true); + + furi_hal_gpio_init( + &flipper_dap_reset_pin, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); + furi_hal_gpio_write(&flipper_dap_reset_pin, true); + +#ifdef DAP_CONFIG_ENABLE_JTAG + furi_hal_gpio_init(&flipper_dap_tdo_pin, GpioModeInput, GpioPullNo, GpioSpeedVeryHigh); + furi_hal_gpio_init(&flipper_dap_tdi_pin, GpioModeInput, GpioPullNo, GpioSpeedVeryHigh); +#endif + dap_app_connect_swd(); +} + +//----------------------------------------------------------------------------- +static inline void DAP_CONFIG_CONNECT_JTAG(void) { + furi_hal_gpio_init( + &flipper_dap_swdio_pin, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); + furi_hal_gpio_write(&flipper_dap_swdio_pin, true); + + furi_hal_gpio_init( + &flipper_dap_swclk_pin, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); + furi_hal_gpio_write(&flipper_dap_swclk_pin, true); + + furi_hal_gpio_init( + &flipper_dap_reset_pin, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); + furi_hal_gpio_write(&flipper_dap_reset_pin, true); + +#ifdef DAP_CONFIG_ENABLE_JTAG + furi_hal_gpio_init(&flipper_dap_tdo_pin, GpioModeInput, GpioPullNo, GpioSpeedVeryHigh); + + furi_hal_gpio_init( + &flipper_dap_tdi_pin, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); + furi_hal_gpio_write(&flipper_dap_tdi_pin, true); +#endif + dap_app_connect_jtag(); +} + +//----------------------------------------------------------------------------- +static inline void DAP_CONFIG_LED(int index, int state) { + (void)index; + (void)state; +} + +//----------------------------------------------------------------------------- +__attribute__((always_inline)) static inline void DAP_CONFIG_DELAY(uint32_t cycles) { + asm volatile("1: subs %[cycles], %[cycles], #1 \n" + " bne 1b \n" + : [cycles] "+l"(cycles)); +} + +#endif // _DAP_CONFIG_H_ diff --git a/applications/plugins/dap_link/dap_link.c b/applications/plugins/dap_link/dap_link.c new file mode 100644 index 00000000000..58d032b91c4 --- /dev/null +++ b/applications/plugins/dap_link/dap_link.c @@ -0,0 +1,521 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dap_link.h" +#include "dap_config.h" +#include "gui/dap_gui.h" +#include "usb/dap_v2_usb.h" + +/***************************************************************************/ +/****************************** DAP COMMON *********************************/ +/***************************************************************************/ + +struct DapApp { + FuriThread* dap_thread; + FuriThread* cdc_thread; + FuriThread* gui_thread; + + DapState state; + DapConfig config; +}; + +void dap_app_get_state(DapApp* app, DapState* state) { + *state = app->state; +} + +#define DAP_PROCESS_THREAD_TICK 500 + +typedef enum { + DapThreadEventStop = (1 << 0), +} DapThreadEvent; + +void dap_thread_send_stop(FuriThread* thread) { + furi_thread_flags_set(furi_thread_get_id(thread), DapThreadEventStop); +} + +GpioPin flipper_dap_swclk_pin; +GpioPin flipper_dap_swdio_pin; +GpioPin flipper_dap_reset_pin; +GpioPin flipper_dap_tdo_pin; +GpioPin flipper_dap_tdi_pin; + +/***************************************************************************/ +/****************************** DAP PROCESS ********************************/ +/***************************************************************************/ + +typedef struct { + uint8_t data[DAP_CONFIG_PACKET_SIZE]; + uint8_t size; +} DapPacket; + +typedef enum { + DAPThreadEventStop = DapThreadEventStop, + DAPThreadEventRxV1 = (1 << 1), + DAPThreadEventRxV2 = (1 << 2), + DAPThreadEventUSBConnect = (1 << 3), + DAPThreadEventUSBDisconnect = (1 << 4), + DAPThreadEventApplyConfig = (1 << 5), + DAPThreadEventAll = DAPThreadEventStop | DAPThreadEventRxV1 | DAPThreadEventRxV2 | + DAPThreadEventUSBConnect | DAPThreadEventUSBDisconnect | + DAPThreadEventApplyConfig, +} DAPThreadEvent; + +#define USB_SERIAL_NUMBER_LEN 16 +char usb_serial_number[USB_SERIAL_NUMBER_LEN] = {0}; + +const char* dap_app_get_serial(DapApp* app) { + UNUSED(app); + return usb_serial_number; +} + +static void dap_app_rx1_callback(void* context) { + furi_assert(context); + FuriThreadId thread_id = (FuriThreadId)context; + furi_thread_flags_set(thread_id, DAPThreadEventRxV1); +} + +static void dap_app_rx2_callback(void* context) { + furi_assert(context); + FuriThreadId thread_id = (FuriThreadId)context; + furi_thread_flags_set(thread_id, DAPThreadEventRxV2); +} + +static void dap_app_usb_state_callback(bool state, void* context) { + furi_assert(context); + FuriThreadId thread_id = (FuriThreadId)context; + if(state) { + furi_thread_flags_set(thread_id, DAPThreadEventUSBConnect); + } else { + furi_thread_flags_set(thread_id, DAPThreadEventUSBDisconnect); + } +} + +static void dap_app_process_v1() { + DapPacket tx_packet; + DapPacket rx_packet; + memset(&tx_packet, 0, sizeof(DapPacket)); + rx_packet.size = dap_v1_usb_rx(rx_packet.data, DAP_CONFIG_PACKET_SIZE); + dap_process_request(rx_packet.data, rx_packet.size, tx_packet.data, DAP_CONFIG_PACKET_SIZE); + dap_v1_usb_tx(tx_packet.data, DAP_CONFIG_PACKET_SIZE); +} + +static void dap_app_process_v2() { + DapPacket tx_packet; + DapPacket rx_packet; + memset(&tx_packet, 0, sizeof(DapPacket)); + rx_packet.size = dap_v2_usb_rx(rx_packet.data, DAP_CONFIG_PACKET_SIZE); + size_t len = dap_process_request( + rx_packet.data, rx_packet.size, tx_packet.data, DAP_CONFIG_PACKET_SIZE); + dap_v2_usb_tx(tx_packet.data, len); +} + +void dap_app_vendor_cmd(uint8_t cmd) { + // openocd -c "cmsis-dap cmd 81" + if(cmd == 0x01) { + furi_hal_power_reset(); + } +} + +void dap_app_target_reset() { + FURI_LOG_I("DAP", "Target reset"); +} + +static void dap_init_gpio(DapSwdPins swd_pins) { + switch(swd_pins) { + case DapSwdPinsPA7PA6: + flipper_dap_swclk_pin = gpio_ext_pa7; + flipper_dap_swdio_pin = gpio_ext_pa6; + break; + case DapSwdPinsPA14PA13: + flipper_dap_swclk_pin = (GpioPin){.port = GPIOA, .pin = LL_GPIO_PIN_14}; + flipper_dap_swdio_pin = (GpioPin){.port = GPIOA, .pin = LL_GPIO_PIN_13}; + break; + } + + flipper_dap_reset_pin = gpio_ext_pa4; + flipper_dap_tdo_pin = gpio_ext_pb3; + flipper_dap_tdi_pin = gpio_ext_pb2; +} + +static void dap_deinit_gpio(DapSwdPins swd_pins) { + // setup gpio pins to default state + furi_hal_gpio_init(&flipper_dap_reset_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_init(&flipper_dap_tdo_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_init(&flipper_dap_tdi_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + + if(DapSwdPinsPA14PA13 == swd_pins) { + // PA14 and PA13 are used by SWD + furi_hal_gpio_init_ex( + &flipper_dap_swclk_pin, + GpioModeAltFunctionPushPull, + GpioPullDown, + GpioSpeedLow, + GpioAltFn0JTCK_SWCLK); + furi_hal_gpio_init_ex( + &flipper_dap_swdio_pin, + GpioModeAltFunctionPushPull, + GpioPullUp, + GpioSpeedVeryHigh, + GpioAltFn0JTMS_SWDIO); + } else { + furi_hal_gpio_init(&flipper_dap_swclk_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_init(&flipper_dap_swdio_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + } +} + +static int32_t dap_process(void* p) { + DapApp* app = p; + DapState* dap_state = &(app->state); + + // allocate resources + FuriHalUsbInterface* usb_config_prev; + app->config.swd_pins = DapSwdPinsPA7PA6; + DapSwdPins swd_pins_prev = app->config.swd_pins; + + // init pins + dap_init_gpio(swd_pins_prev); + + // init dap + dap_init(); + + // get name + const char* name = furi_hal_version_get_name_ptr(); + if(!name) { + name = "Flipper"; + } + snprintf(usb_serial_number, USB_SERIAL_NUMBER_LEN, "DAP_%s", name); + + // init usb + usb_config_prev = furi_hal_usb_get_config(); + dap_common_usb_alloc_name(usb_serial_number); + dap_common_usb_set_context(furi_thread_get_id(furi_thread_get_current())); + dap_v1_usb_set_rx_callback(dap_app_rx1_callback); + dap_v2_usb_set_rx_callback(dap_app_rx2_callback); + dap_common_usb_set_state_callback(dap_app_usb_state_callback); + furi_hal_usb_set_config(&dap_v2_usb_hid, NULL); + + // work + uint32_t events; + while(1) { + events = furi_thread_flags_wait(DAPThreadEventAll, FuriFlagWaitAny, FuriWaitForever); + + if(!(events & FuriFlagError)) { + if(events & DAPThreadEventRxV1) { + dap_app_process_v1(); + dap_state->dap_counter++; + dap_state->dap_version = DapVersionV1; + } + + if(events & DAPThreadEventRxV2) { + dap_app_process_v2(); + dap_state->dap_counter++; + dap_state->dap_version = DapVersionV2; + } + + if(events & DAPThreadEventUSBConnect) { + dap_state->usb_connected = true; + } + + if(events & DAPThreadEventUSBDisconnect) { + dap_state->usb_connected = false; + dap_state->dap_version = DapVersionUnknown; + } + + if(events & DAPThreadEventApplyConfig) { + if(swd_pins_prev != app->config.swd_pins) { + dap_deinit_gpio(swd_pins_prev); + swd_pins_prev = app->config.swd_pins; + dap_init_gpio(swd_pins_prev); + } + } + + if(events & DAPThreadEventStop) { + break; + } + } + } + + // deinit usb + furi_hal_usb_set_config(usb_config_prev, NULL); + dap_common_wait_for_deinit(); + dap_common_usb_free_name(); + dap_deinit_gpio(swd_pins_prev); + return 0; +} + +/***************************************************************************/ +/****************************** CDC PROCESS ********************************/ +/***************************************************************************/ + +typedef enum { + CDCThreadEventStop = DapThreadEventStop, + CDCThreadEventUARTRx = (1 << 1), + CDCThreadEventCDCRx = (1 << 2), + CDCThreadEventCDCConfig = (1 << 3), + CDCThreadEventApplyConfig = (1 << 4), + CDCThreadEventAll = CDCThreadEventStop | CDCThreadEventUARTRx | CDCThreadEventCDCRx | + CDCThreadEventCDCConfig | CDCThreadEventApplyConfig, +} CDCThreadEvent; + +typedef struct { + FuriStreamBuffer* rx_stream; + FuriThreadId thread_id; + FuriHalUartId uart_id; + struct usb_cdc_line_coding line_coding; +} CDCProcess; + +static void cdc_uart_irq_cb(UartIrqEvent ev, uint8_t data, void* ctx) { + CDCProcess* app = ctx; + + if(ev == UartIrqEventRXNE) { + furi_stream_buffer_send(app->rx_stream, &data, 1, 0); + furi_thread_flags_set(app->thread_id, CDCThreadEventUARTRx); + } +} + +static void cdc_usb_rx_callback(void* context) { + CDCProcess* app = context; + furi_thread_flags_set(app->thread_id, CDCThreadEventCDCRx); +} + +static void cdc_usb_control_line_callback(uint8_t state, void* context) { + UNUSED(context); + UNUSED(state); +} + +static void cdc_usb_config_callback(struct usb_cdc_line_coding* config, void* context) { + CDCProcess* app = context; + app->line_coding = *config; + furi_thread_flags_set(app->thread_id, CDCThreadEventCDCConfig); +} + +static FuriHalUartId cdc_init_uart( + DapUartType type, + DapUartTXRX swap, + uint32_t baudrate, + void (*cb)(UartIrqEvent ev, uint8_t data, void* ctx), + void* ctx) { + FuriHalUartId uart_id = FuriHalUartIdUSART1; + if(baudrate == 0) baudrate = 115200; + + switch(type) { + case DapUartTypeUSART1: + uart_id = FuriHalUartIdUSART1; + furi_hal_console_disable(); + furi_hal_uart_deinit(uart_id); + if(swap == DapUartTXRXSwap) { + LL_USART_SetTXRXSwap(USART1, LL_USART_TXRX_SWAPPED); + } else { + LL_USART_SetTXRXSwap(USART1, LL_USART_TXRX_STANDARD); + } + furi_hal_uart_init(uart_id, baudrate); + furi_hal_uart_set_irq_cb(uart_id, cb, ctx); + break; + case DapUartTypeLPUART1: + uart_id = FuriHalUartIdLPUART1; + furi_hal_uart_deinit(uart_id); + if(swap == DapUartTXRXSwap) { + LL_LPUART_SetTXRXSwap(LPUART1, LL_LPUART_TXRX_SWAPPED); + } else { + LL_LPUART_SetTXRXSwap(LPUART1, LL_LPUART_TXRX_STANDARD); + } + furi_hal_uart_init(uart_id, baudrate); + furi_hal_uart_set_irq_cb(uart_id, cb, ctx); + break; + } + + return uart_id; +} + +static void cdc_deinit_uart(DapUartType type) { + switch(type) { + case DapUartTypeUSART1: + furi_hal_uart_deinit(FuriHalUartIdUSART1); + LL_USART_SetTXRXSwap(USART1, LL_USART_TXRX_STANDARD); + furi_hal_console_init(); + break; + case DapUartTypeLPUART1: + furi_hal_uart_deinit(FuriHalUartIdLPUART1); + LL_LPUART_SetTXRXSwap(LPUART1, LL_LPUART_TXRX_STANDARD); + break; + } +} + +static int32_t cdc_process(void* p) { + DapApp* dap_app = p; + DapState* dap_state = &(dap_app->state); + + dap_app->config.uart_pins = DapUartTypeLPUART1; + dap_app->config.uart_swap = DapUartTXRXNormal; + + DapUartType uart_pins_prev = dap_app->config.uart_pins; + DapUartTXRX uart_swap_prev = dap_app->config.uart_swap; + + CDCProcess* app = malloc(sizeof(CDCProcess)); + app->thread_id = furi_thread_get_id(furi_thread_get_current()); + app->rx_stream = furi_stream_buffer_alloc(512, 1); + + const uint8_t rx_buffer_size = 64; + uint8_t* rx_buffer = malloc(rx_buffer_size); + + app->uart_id = cdc_init_uart( + uart_pins_prev, uart_swap_prev, dap_state->cdc_baudrate, cdc_uart_irq_cb, app); + + dap_cdc_usb_set_context(app); + dap_cdc_usb_set_rx_callback(cdc_usb_rx_callback); + dap_cdc_usb_set_control_line_callback(cdc_usb_control_line_callback); + dap_cdc_usb_set_config_callback(cdc_usb_config_callback); + + uint32_t events; + while(1) { + events = furi_thread_flags_wait(CDCThreadEventAll, FuriFlagWaitAny, FuriWaitForever); + + if(!(events & FuriFlagError)) { + if(events & CDCThreadEventCDCConfig) { + if(dap_state->cdc_baudrate != app->line_coding.dwDTERate) { + dap_state->cdc_baudrate = app->line_coding.dwDTERate; + if(dap_state->cdc_baudrate > 0) { + furi_hal_uart_set_br(app->uart_id, dap_state->cdc_baudrate); + } + } + } + + if(events & CDCThreadEventUARTRx) { + size_t len = + furi_stream_buffer_receive(app->rx_stream, rx_buffer, rx_buffer_size, 0); + + if(len > 0) { + dap_cdc_usb_tx(rx_buffer, len); + } + dap_state->cdc_rx_counter += len; + } + + if(events & CDCThreadEventCDCRx) { + size_t len = dap_cdc_usb_rx(rx_buffer, rx_buffer_size); + if(len > 0) { + furi_hal_uart_tx(app->uart_id, rx_buffer, len); + } + dap_state->cdc_tx_counter += len; + } + + if(events & CDCThreadEventApplyConfig) { + if(uart_pins_prev != dap_app->config.uart_pins || + uart_swap_prev != dap_app->config.uart_swap) { + cdc_deinit_uart(uart_pins_prev); + uart_pins_prev = dap_app->config.uart_pins; + uart_swap_prev = dap_app->config.uart_swap; + app->uart_id = cdc_init_uart( + uart_pins_prev, + uart_swap_prev, + dap_state->cdc_baudrate, + cdc_uart_irq_cb, + app); + } + } + + if(events & CDCThreadEventStop) { + break; + } + } + } + + cdc_deinit_uart(uart_pins_prev); + free(rx_buffer); + furi_stream_buffer_free(app->rx_stream); + free(app); + + return 0; +} + +/***************************************************************************/ +/******************************* MAIN APP **********************************/ +/***************************************************************************/ + +static FuriThread* furi_thread_alloc_ex( + const char* name, + uint32_t stack_size, + FuriThreadCallback callback, + void* context) { + FuriThread* thread = furi_thread_alloc(); + furi_thread_set_name(thread, name); + furi_thread_set_stack_size(thread, stack_size); + furi_thread_set_callback(thread, callback); + furi_thread_set_context(thread, context); + return thread; +} + +static DapApp* dap_app_alloc() { + DapApp* dap_app = malloc(sizeof(DapApp)); + dap_app->dap_thread = furi_thread_alloc_ex("DAP Process", 1024, dap_process, dap_app); + dap_app->cdc_thread = furi_thread_alloc_ex("DAP CDC", 1024, cdc_process, dap_app); + dap_app->gui_thread = furi_thread_alloc_ex("DAP GUI", 1024, dap_gui_thread, dap_app); + return dap_app; +} + +static void dap_app_free(DapApp* dap_app) { + furi_assert(dap_app); + furi_thread_free(dap_app->dap_thread); + furi_thread_free(dap_app->cdc_thread); + furi_thread_free(dap_app->gui_thread); + free(dap_app); +} + +static DapApp* app_handle = NULL; + +void dap_app_disconnect() { + app_handle->state.dap_mode = DapModeDisconnected; +} + +void dap_app_connect_swd() { + app_handle->state.dap_mode = DapModeSWD; +} + +void dap_app_connect_jtag() { + app_handle->state.dap_mode = DapModeJTAG; +} + +void dap_app_set_config(DapApp* app, DapConfig* config) { + app->config = *config; + furi_thread_flags_set(furi_thread_get_id(app->dap_thread), DAPThreadEventApplyConfig); + furi_thread_flags_set(furi_thread_get_id(app->cdc_thread), CDCThreadEventApplyConfig); +} + +DapConfig* dap_app_get_config(DapApp* app) { + return &app->config; +} + +int32_t dap_link_app(void* p) { + UNUSED(p); + + // alloc app + DapApp* app = dap_app_alloc(); + app_handle = app; + + furi_thread_start(app->dap_thread); + furi_thread_start(app->cdc_thread); + furi_thread_start(app->gui_thread); + + // wait until gui thread is finished + furi_thread_join(app->gui_thread); + + // send stop event to threads + dap_thread_send_stop(app->dap_thread); + dap_thread_send_stop(app->cdc_thread); + + // wait for threads to stop + furi_thread_join(app->dap_thread); + furi_thread_join(app->cdc_thread); + + // free app + dap_app_free(app); + + return 0; +} \ No newline at end of file diff --git a/applications/plugins/dap_link/dap_link.h b/applications/plugins/dap_link/dap_link.h new file mode 100644 index 00000000000..d51726c4587 --- /dev/null +++ b/applications/plugins/dap_link/dap_link.h @@ -0,0 +1,55 @@ +#pragma once +#include + +typedef enum { + DapModeDisconnected, + DapModeSWD, + DapModeJTAG, +} DapMode; + +typedef enum { + DapVersionUnknown, + DapVersionV1, + DapVersionV2, +} DapVersion; + +typedef struct { + bool usb_connected; + DapMode dap_mode; + DapVersion dap_version; + uint32_t dap_counter; + uint32_t cdc_baudrate; + uint32_t cdc_tx_counter; + uint32_t cdc_rx_counter; +} DapState; + +typedef enum { + DapSwdPinsPA7PA6, // Pins 2, 3 + DapSwdPinsPA14PA13, // Pins 10, 12 +} DapSwdPins; + +typedef enum { + DapUartTypeUSART1, // Pins 13, 14 + DapUartTypeLPUART1, // Pins 15, 16 +} DapUartType; + +typedef enum { + DapUartTXRXNormal, + DapUartTXRXSwap, +} DapUartTXRX; + +typedef struct { + DapSwdPins swd_pins; + DapUartType uart_pins; + DapUartTXRX uart_swap; +} DapConfig; + +typedef struct DapApp DapApp; + +void dap_app_get_state(DapApp* app, DapState* state); + +const char* dap_app_get_serial(DapApp* app); + +void dap_app_set_config(DapApp* app, DapConfig* config); + +DapConfig* dap_app_get_config(DapApp* app); \ No newline at end of file diff --git a/applications/plugins/dap_link/dap_link.png b/applications/plugins/dap_link/dap_link.png new file mode 100644 index 0000000000000000000000000000000000000000..2278ce2b61cf97e51720bafb2d07dae48d986560 GIT binary patch literal 143 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V8<6ZZI=>f4F%}28J29*~C-V}>@$__Y43U`H zI>D2R!GMF={pJ7tHnyBWw`O@WO=4vXnQOryxnPsm9sxmyCsV>A?tXRt_DY<&Wq)IO q>DB4a%~XFc{Gk)L^WR4G>Gvdb>|Y;BDpm&?%HZkh=d#Wzp$P!!VJ~6; literal 0 HcmV?d00001 diff --git a/applications/plugins/dap_link/gui/dap_gui.c b/applications/plugins/dap_link/gui/dap_gui.c new file mode 100644 index 00000000000..4dd9861530f --- /dev/null +++ b/applications/plugins/dap_link/gui/dap_gui.c @@ -0,0 +1,92 @@ +#include "dap_gui.h" +#include "dap_gui_i.h" + +#define DAP_GUI_TICK 250 + +static bool dap_gui_custom_event_callback(void* context, uint32_t event) { + furi_assert(context); + DapGuiApp* app = context; + return scene_manager_handle_custom_event(app->scene_manager, event); +} + +static bool dap_gui_back_event_callback(void* context) { + furi_assert(context); + DapGuiApp* app = context; + return scene_manager_handle_back_event(app->scene_manager); +} + +static void dap_gui_tick_event_callback(void* context) { + furi_assert(context); + DapGuiApp* app = context; + scene_manager_handle_tick_event(app->scene_manager); +} + +DapGuiApp* dap_gui_alloc() { + DapGuiApp* app = malloc(sizeof(DapGuiApp)); + app->gui = furi_record_open(RECORD_GUI); + app->view_dispatcher = view_dispatcher_alloc(); + app->scene_manager = scene_manager_alloc(&dap_scene_handlers, app); + view_dispatcher_enable_queue(app->view_dispatcher); + view_dispatcher_set_event_callback_context(app->view_dispatcher, app); + + view_dispatcher_set_custom_event_callback(app->view_dispatcher, dap_gui_custom_event_callback); + view_dispatcher_set_navigation_event_callback( + app->view_dispatcher, dap_gui_back_event_callback); + view_dispatcher_set_tick_event_callback( + app->view_dispatcher, dap_gui_tick_event_callback, DAP_GUI_TICK); + + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + + app->notifications = furi_record_open(RECORD_NOTIFICATION); + + app->var_item_list = variable_item_list_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + DapGuiAppViewVarItemList, + variable_item_list_get_view(app->var_item_list)); + + app->main_view = dap_main_view_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, DapGuiAppViewMainView, dap_main_view_get_view(app->main_view)); + + app->widget = widget_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, DapGuiAppViewWidget, widget_get_view(app->widget)); + + scene_manager_next_scene(app->scene_manager, DapSceneMain); + + return app; +} + +void dap_gui_free(DapGuiApp* app) { + view_dispatcher_remove_view(app->view_dispatcher, DapGuiAppViewVarItemList); + variable_item_list_free(app->var_item_list); + + view_dispatcher_remove_view(app->view_dispatcher, DapGuiAppViewMainView); + dap_main_view_free(app->main_view); + + view_dispatcher_remove_view(app->view_dispatcher, DapGuiAppViewWidget); + widget_free(app->widget); + + // View dispatcher + view_dispatcher_free(app->view_dispatcher); + scene_manager_free(app->scene_manager); + + // Close records + furi_record_close(RECORD_GUI); + furi_record_close(RECORD_NOTIFICATION); + + free(app); +} + +int32_t dap_gui_thread(void* arg) { + DapGuiApp* app = dap_gui_alloc(); + app->dap_app = arg; + + notification_message_block(app->notifications, &sequence_display_backlight_enforce_on); + view_dispatcher_run(app->view_dispatcher); + notification_message_block(app->notifications, &sequence_display_backlight_enforce_auto); + + dap_gui_free(app); + return 0; +} \ No newline at end of file diff --git a/applications/plugins/dap_link/gui/dap_gui.h b/applications/plugins/dap_link/gui/dap_gui.h new file mode 100644 index 00000000000..3d8e6bdf965 --- /dev/null +++ b/applications/plugins/dap_link/gui/dap_gui.h @@ -0,0 +1,4 @@ +#pragma once +#include + +int32_t dap_gui_thread(void* arg); \ No newline at end of file diff --git a/applications/plugins/dap_link/gui/dap_gui_custom_event.h b/applications/plugins/dap_link/gui/dap_gui_custom_event.h new file mode 100644 index 00000000000..8b127c9d4bc --- /dev/null +++ b/applications/plugins/dap_link/gui/dap_gui_custom_event.h @@ -0,0 +1,7 @@ +#pragma once + +typedef enum { + DapAppCustomEventConfig, + DapAppCustomEventHelp, + DapAppCustomEventAbout, +} DapAppCustomEvent; diff --git a/applications/plugins/dap_link/gui/dap_gui_i.h b/applications/plugins/dap_link/gui/dap_gui_i.h new file mode 100644 index 00000000000..59411e78c1a --- /dev/null +++ b/applications/plugins/dap_link/gui/dap_gui_i.h @@ -0,0 +1,34 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include "dap_gui.h" +#include "../dap_link.h" +#include "scenes/config/dap_scene.h" +#include "dap_gui_custom_event.h" +#include "views/dap_main_view.h" + +typedef struct { + DapApp* dap_app; + + Gui* gui; + NotificationApp* notifications; + ViewDispatcher* view_dispatcher; + SceneManager* scene_manager; + + VariableItemList* var_item_list; + DapMainView* main_view; + Widget* widget; +} DapGuiApp; + +typedef enum { + DapGuiAppViewVarItemList, + DapGuiAppViewMainView, + DapGuiAppViewWidget, +} DapGuiAppView; diff --git a/applications/plugins/dap_link/gui/scenes/config/dap_scene.c b/applications/plugins/dap_link/gui/scenes/config/dap_scene.c new file mode 100644 index 00000000000..37e235540db --- /dev/null +++ b/applications/plugins/dap_link/gui/scenes/config/dap_scene.c @@ -0,0 +1,30 @@ +#include "dap_scene.h" + +// Generate scene on_enter handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, +void (*const dap_scene_on_enter_handlers[])(void*) = { +#include "dap_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 dap_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = { +#include "dap_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 dap_scene_on_exit_handlers[])(void* context) = { +#include "dap_scene_config.h" +}; +#undef ADD_SCENE + +// Initialize scene handlers configuration structure +const SceneManagerHandlers dap_scene_handlers = { + .on_enter_handlers = dap_scene_on_enter_handlers, + .on_event_handlers = dap_scene_on_event_handlers, + .on_exit_handlers = dap_scene_on_exit_handlers, + .scene_num = DapSceneNum, +}; diff --git a/applications/plugins/dap_link/gui/scenes/config/dap_scene.h b/applications/plugins/dap_link/gui/scenes/config/dap_scene.h new file mode 100644 index 00000000000..6fb38da4abe --- /dev/null +++ b/applications/plugins/dap_link/gui/scenes/config/dap_scene.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +// Generate scene id and total number +#define ADD_SCENE(prefix, name, id) DapScene##id, +typedef enum { +#include "dap_scene_config.h" + DapSceneNum, +} DapScene; +#undef ADD_SCENE + +extern const SceneManagerHandlers dap_scene_handlers; + +// Generate scene on_enter handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); +#include "dap_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 "dap_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 "dap_scene_config.h" +#undef ADD_SCENE diff --git a/applications/plugins/dap_link/gui/scenes/config/dap_scene_config.h b/applications/plugins/dap_link/gui/scenes/config/dap_scene_config.h new file mode 100644 index 00000000000..8957aca068e --- /dev/null +++ b/applications/plugins/dap_link/gui/scenes/config/dap_scene_config.h @@ -0,0 +1,4 @@ +ADD_SCENE(dap, main, Main) +ADD_SCENE(dap, config, Config) +ADD_SCENE(dap, help, Help) +ADD_SCENE(dap, about, About) \ No newline at end of file diff --git a/applications/plugins/dap_link/gui/scenes/dap_scene_about.c b/applications/plugins/dap_link/gui/scenes/dap_scene_about.c new file mode 100644 index 00000000000..0974e60a7fb --- /dev/null +++ b/applications/plugins/dap_link/gui/scenes/dap_scene_about.c @@ -0,0 +1,68 @@ +#include "../dap_gui_i.h" + +#define DAP_VERSION_APP "0.1.0" +#define DAP_DEVELOPED "Dr_Zlo" +#define DAP_GITHUB "https://github.com/flipperdevices/flipperzero-firmware" + +void dap_scene_about_on_enter(void* context) { + DapGuiApp* app = context; + + FuriString* temp_str; + temp_str = furi_string_alloc(); + furi_string_printf(temp_str, "\e#%s\n", "Information"); + + furi_string_cat_printf(temp_str, "Version: %s\n", DAP_VERSION_APP); + furi_string_cat_printf(temp_str, "Developed by: %s\n", DAP_DEVELOPED); + furi_string_cat_printf(temp_str, "Github: %s\n\n", DAP_GITHUB); + + furi_string_cat_printf(temp_str, "\e#%s\n", "Description"); + furi_string_cat_printf( + temp_str, "CMSIS-DAP debugger\nbased on Free-DAP\nThanks to Alex Taradov\n\n"); + + furi_string_cat_printf( + temp_str, + "Supported protocols:\n" + "SWD, JTAG, UART\n" + "DAP v1 (cmsis_backend hid), DAP v2 (cmsis_backend usb_bulk), VCP\n"); + + widget_add_text_box_element( + app->widget, + 0, + 0, + 128, + 14, + AlignCenter, + AlignBottom, + "\e#\e! \e!\n", + false); + widget_add_text_box_element( + app->widget, + 0, + 2, + 128, + 14, + AlignCenter, + AlignBottom, + "\e#\e! DAP Link \e!\n", + false); + widget_add_text_scroll_element(app->widget, 0, 16, 128, 50, furi_string_get_cstr(temp_str)); + furi_string_free(temp_str); + + view_dispatcher_switch_to_view(app->view_dispatcher, DapGuiAppViewWidget); +} + +bool dap_scene_about_on_event(void* context, SceneManagerEvent event) { + DapGuiApp* app = context; + bool consumed = false; + UNUSED(app); + UNUSED(event); + + return consumed; +} + +void dap_scene_about_on_exit(void* context) { + DapGuiApp* app = context; + + // Clear views + widget_reset(app->widget); +} diff --git a/applications/plugins/dap_link/gui/scenes/dap_scene_config.c b/applications/plugins/dap_link/gui/scenes/dap_scene_config.c new file mode 100644 index 00000000000..56b06411c78 --- /dev/null +++ b/applications/plugins/dap_link/gui/scenes/dap_scene_config.c @@ -0,0 +1,107 @@ +#include "../dap_gui_i.h" + +static const char* swd_pins[] = {[DapSwdPinsPA7PA6] = "2,3", [DapSwdPinsPA14PA13] = "10,12"}; +static const char* uart_pins[] = {[DapUartTypeUSART1] = "13,14", [DapUartTypeLPUART1] = "15,16"}; +static const char* uart_swap[] = {[DapUartTXRXNormal] = "No", [DapUartTXRXSwap] = "Yes"}; + +static void swd_pins_cb(VariableItem* item) { + DapGuiApp* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + variable_item_set_current_value_text(item, swd_pins[index]); + + DapConfig* config = dap_app_get_config(app->dap_app); + config->swd_pins = index; + dap_app_set_config(app->dap_app, config); +} + +static void uart_pins_cb(VariableItem* item) { + DapGuiApp* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + variable_item_set_current_value_text(item, uart_pins[index]); + + DapConfig* config = dap_app_get_config(app->dap_app); + config->uart_pins = index; + dap_app_set_config(app->dap_app, config); +} + +static void uart_swap_cb(VariableItem* item) { + DapGuiApp* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + variable_item_set_current_value_text(item, uart_swap[index]); + + DapConfig* config = dap_app_get_config(app->dap_app); + config->uart_swap = index; + dap_app_set_config(app->dap_app, config); +} + +static void ok_cb(void* context, uint32_t index) { + DapGuiApp* app = context; + switch(index) { + case 3: + view_dispatcher_send_custom_event(app->view_dispatcher, DapAppCustomEventHelp); + break; + case 4: + view_dispatcher_send_custom_event(app->view_dispatcher, DapAppCustomEventAbout); + break; + default: + break; + } +} + +void dap_scene_config_on_enter(void* context) { + DapGuiApp* app = context; + VariableItemList* var_item_list = app->var_item_list; + VariableItem* item; + DapConfig* config = dap_app_get_config(app->dap_app); + + item = variable_item_list_add( + var_item_list, "SWC SWD Pins", COUNT_OF(swd_pins), swd_pins_cb, app); + variable_item_set_current_value_index(item, config->swd_pins); + variable_item_set_current_value_text(item, swd_pins[config->swd_pins]); + + item = + variable_item_list_add(var_item_list, "UART Pins", COUNT_OF(uart_pins), uart_pins_cb, app); + variable_item_set_current_value_index(item, config->uart_pins); + variable_item_set_current_value_text(item, uart_pins[config->uart_pins]); + + item = variable_item_list_add( + var_item_list, "Swap TX RX", COUNT_OF(uart_swap), uart_swap_cb, app); + variable_item_set_current_value_index(item, config->uart_swap); + variable_item_set_current_value_text(item, uart_swap[config->uart_swap]); + + item = variable_item_list_add(var_item_list, "Help and Pinout", 0, NULL, NULL); + item = variable_item_list_add(var_item_list, "About", 0, NULL, NULL); + + variable_item_list_set_selected_item( + var_item_list, scene_manager_get_scene_state(app->scene_manager, DapSceneConfig)); + + variable_item_list_set_enter_callback(var_item_list, ok_cb, app); + + view_dispatcher_switch_to_view(app->view_dispatcher, DapGuiAppViewVarItemList); +} + +bool dap_scene_config_on_event(void* context, SceneManagerEvent event) { + DapGuiApp* app = context; + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == DapAppCustomEventHelp) { + scene_manager_next_scene(app->scene_manager, DapSceneHelp); + return true; + } else if(event.event == DapAppCustomEventAbout) { + scene_manager_next_scene(app->scene_manager, DapSceneAbout); + return true; + } + } + return false; +} + +void dap_scene_config_on_exit(void* context) { + DapGuiApp* app = context; + scene_manager_set_scene_state( + app->scene_manager, + DapSceneConfig, + variable_item_list_get_selected_item_index(app->var_item_list)); + variable_item_list_reset(app->var_item_list); +} \ No newline at end of file diff --git a/applications/plugins/dap_link/gui/scenes/dap_scene_help.c b/applications/plugins/dap_link/gui/scenes/dap_scene_help.c new file mode 100644 index 00000000000..7193f4f4b9c --- /dev/null +++ b/applications/plugins/dap_link/gui/scenes/dap_scene_help.c @@ -0,0 +1,102 @@ +#include "../dap_gui_i.h" + +void dap_scene_help_on_enter(void* context) { + DapGuiApp* app = context; + DapConfig* config = dap_app_get_config(app->dap_app); + FuriString* string = furi_string_alloc(); + + furi_string_cat(string, "CMSIS DAP/DAP Link v2\r\n"); + furi_string_cat_printf(string, "Serial: %s\r\n", dap_app_get_serial(app->dap_app)); + furi_string_cat( + string, + "Pinout:\r\n" + "\e#SWD:\r\n"); + + switch(config->swd_pins) { + case DapSwdPinsPA7PA6: + furi_string_cat( + string, + " SWC: 2 [A7]\r\n" + " SWD: 3 [A6]\r\n"); + break; + case DapSwdPinsPA14PA13: + furi_string_cat( + string, + " SWC: 10 [SWC]\r\n" + " SWD: 12 [SIO]\r\n"); + break; + default: + break; + } + + furi_string_cat(string, "\e#JTAG:\r\n"); + switch(config->swd_pins) { + case DapSwdPinsPA7PA6: + furi_string_cat( + string, + " TCK: 2 [A7]\r\n" + " TMS: 3 [A6]\r\n" + " RST: 4 [A4]\r\n" + " TDO: 5 [B3]\r\n" + " TDI: 6 [B2]\r\n"); + break; + case DapSwdPinsPA14PA13: + furi_string_cat( + string, + " RST: 4 [A4]\r\n" + " TDO: 5 [B3]\r\n" + " TDI: 6 [B2]\r\n" + " TCK: 10 [SWC]\r\n" + " TMS: 12 [SIO]\r\n"); + break; + default: + break; + } + + furi_string_cat(string, "\e#UART:\r\n"); + switch(config->uart_pins) { + case DapUartTypeUSART1: + if(config->uart_swap == DapUartTXRXNormal) { + furi_string_cat( + string, + " TX: 13 [TX]\r\n" + " RX: 14 [RX]\r\n"); + } else { + furi_string_cat( + string, + " RX: 13 [TX]\r\n" + " TX: 14 [RX]\r\n"); + } + break; + case DapUartTypeLPUART1: + if(config->uart_swap == DapUartTXRXNormal) { + furi_string_cat( + string, + " TX: 15 [С1]\r\n" + " RX: 16 [С0]\r\n"); + } else { + furi_string_cat( + string, + " RX: 15 [С1]\r\n" + " TX: 16 [С0]\r\n"); + } + break; + default: + break; + } + + widget_add_text_scroll_element(app->widget, 0, 0, 128, 64, furi_string_get_cstr(string)); + furi_string_free(string); + view_dispatcher_switch_to_view(app->view_dispatcher, DapGuiAppViewWidget); +} + +bool dap_scene_help_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + UNUSED(event); + return false; +} + +void dap_scene_help_on_exit(void* context) { + DapGuiApp* app = context; + widget_reset(app->widget); +} \ No newline at end of file diff --git a/applications/plugins/dap_link/gui/scenes/dap_scene_main.c b/applications/plugins/dap_link/gui/scenes/dap_scene_main.c new file mode 100644 index 00000000000..8c19bd6a5d5 --- /dev/null +++ b/applications/plugins/dap_link/gui/scenes/dap_scene_main.c @@ -0,0 +1,154 @@ +#include "../dap_gui_i.h" +#include "../../dap_link.h" + +typedef struct { + DapState dap_state; + bool dap_active; + bool tx_active; + bool rx_active; +} DapSceneMainState; + +static bool process_dap_state(DapGuiApp* app) { + DapSceneMainState* state = + (DapSceneMainState*)scene_manager_get_scene_state(app->scene_manager, DapSceneMain); + if(state == NULL) return true; + + DapState* prev_state = &state->dap_state; + DapState next_state; + dap_app_get_state(app->dap_app, &next_state); + bool need_to_update = false; + + if(prev_state->dap_mode != next_state.dap_mode) { + switch(next_state.dap_mode) { + case DapModeDisconnected: + dap_main_view_set_mode(app->main_view, DapMainViewModeDisconnected); + notification_message(app->notifications, &sequence_blink_stop); + break; + case DapModeSWD: + dap_main_view_set_mode(app->main_view, DapMainViewModeSWD); + notification_message(app->notifications, &sequence_blink_start_blue); + break; + case DapModeJTAG: + dap_main_view_set_mode(app->main_view, DapMainViewModeJTAG); + notification_message(app->notifications, &sequence_blink_start_magenta); + break; + } + need_to_update = true; + } + + if(prev_state->dap_version != next_state.dap_version) { + switch(next_state.dap_version) { + case DapVersionUnknown: + dap_main_view_set_version(app->main_view, DapMainViewVersionUnknown); + break; + case DapVersionV1: + dap_main_view_set_version(app->main_view, DapMainViewVersionV1); + break; + case DapVersionV2: + dap_main_view_set_version(app->main_view, DapMainViewVersionV2); + break; + } + need_to_update = true; + } + + if(prev_state->usb_connected != next_state.usb_connected) { + dap_main_view_set_usb_connected(app->main_view, next_state.usb_connected); + need_to_update = true; + } + + if(prev_state->dap_counter != next_state.dap_counter) { + if(!state->dap_active) { + state->dap_active = true; + dap_main_view_set_dap(app->main_view, state->dap_active); + need_to_update = true; + } + } else { + if(state->dap_active) { + state->dap_active = false; + dap_main_view_set_dap(app->main_view, state->dap_active); + need_to_update = true; + } + } + + if(prev_state->cdc_baudrate != next_state.cdc_baudrate) { + dap_main_view_set_baudrate(app->main_view, next_state.cdc_baudrate); + need_to_update = true; + } + + if(prev_state->cdc_tx_counter != next_state.cdc_tx_counter) { + if(!state->tx_active) { + state->tx_active = true; + dap_main_view_set_tx(app->main_view, state->tx_active); + need_to_update = true; + notification_message(app->notifications, &sequence_blink_start_red); + } + } else { + if(state->tx_active) { + state->tx_active = false; + dap_main_view_set_tx(app->main_view, state->tx_active); + need_to_update = true; + notification_message(app->notifications, &sequence_blink_stop); + } + } + + if(prev_state->cdc_rx_counter != next_state.cdc_rx_counter) { + if(!state->rx_active) { + state->rx_active = true; + dap_main_view_set_rx(app->main_view, state->rx_active); + need_to_update = true; + notification_message(app->notifications, &sequence_blink_start_green); + } + } else { + if(state->rx_active) { + state->rx_active = false; + dap_main_view_set_rx(app->main_view, state->rx_active); + need_to_update = true; + notification_message(app->notifications, &sequence_blink_stop); + } + } + + if(need_to_update) { + dap_main_view_update(app->main_view); + } + + *prev_state = next_state; + return true; +} + +static void dap_scene_main_on_left(void* context) { + DapGuiApp* app = (DapGuiApp*)context; + view_dispatcher_send_custom_event(app->view_dispatcher, DapAppCustomEventConfig); +} + +void dap_scene_main_on_enter(void* context) { + DapGuiApp* app = context; + DapSceneMainState* state = malloc(sizeof(DapSceneMainState)); + dap_main_view_set_left_callback(app->main_view, dap_scene_main_on_left, app); + view_dispatcher_switch_to_view(app->view_dispatcher, DapGuiAppViewMainView); + scene_manager_set_scene_state(app->scene_manager, DapSceneMain, (uint32_t)state); +} + +bool dap_scene_main_on_event(void* context, SceneManagerEvent event) { + DapGuiApp* app = context; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == DapAppCustomEventConfig) { + scene_manager_next_scene(app->scene_manager, DapSceneConfig); + return true; + } + } else if(event.type == SceneManagerEventTypeTick) { + return process_dap_state(app); + } + + return false; +} + +void dap_scene_main_on_exit(void* context) { + DapGuiApp* app = context; + DapSceneMainState* state = + (DapSceneMainState*)scene_manager_get_scene_state(app->scene_manager, DapSceneMain); + scene_manager_set_scene_state(app->scene_manager, DapSceneMain, (uint32_t)NULL); + FURI_SW_MEMBARRIER(); + free(state); + notification_message(app->notifications, &sequence_blink_stop); +} \ No newline at end of file diff --git a/applications/plugins/dap_link/gui/views/dap_main_view.c b/applications/plugins/dap_link/gui/views/dap_main_view.c new file mode 100644 index 00000000000..c5c8f9dff02 --- /dev/null +++ b/applications/plugins/dap_link/gui/views/dap_main_view.c @@ -0,0 +1,189 @@ +#include "dap_main_view.h" +#include "dap_link_icons.h" +#include + +// extern const Icon I_ArrowDownEmpty_12x18; +// extern const Icon I_ArrowDownFilled_12x18; +// extern const Icon I_ArrowUpEmpty_12x18; +// extern const Icon I_ArrowUpFilled_12x18; + +struct DapMainView { + View* view; + DapMainViewButtonCallback cb_left; + void* cb_context; +}; + +typedef struct { + DapMainViewMode mode; + DapMainViewVersion version; + bool usb_connected; + uint32_t baudrate; + bool dap_active; + bool tx_active; + bool rx_active; +} DapMainViewModel; + +static void dap_main_view_draw_callback(Canvas* canvas, void* _model) { + DapMainViewModel* model = _model; + UNUSED(model); + canvas_clear(canvas); + elements_button_left(canvas, "Config"); + + canvas_set_color(canvas, ColorBlack); + canvas_draw_box(canvas, 0, 0, 127, 11); + canvas_set_color(canvas, ColorWhite); + + const char* header_string; + if(model->usb_connected) { + if(model->version == DapMainViewVersionV1) { + header_string = "DAP Link V1 Connected"; + } else if(model->version == DapMainViewVersionV2) { + header_string = "DAP Link V2 Connected"; + } else { + header_string = "DAP Link Connected"; + } + } else { + header_string = "DAP Link"; + } + + canvas_draw_str_aligned(canvas, 64, 9, AlignCenter, AlignBottom, header_string); + + canvas_set_color(canvas, ColorBlack); + if(model->dap_active) { + canvas_draw_icon(canvas, 14, 16, &I_ArrowUpFilled_12x18); + canvas_draw_icon(canvas, 28, 16, &I_ArrowDownFilled_12x18); + } else { + canvas_draw_icon(canvas, 14, 16, &I_ArrowUpEmpty_12x18); + canvas_draw_icon(canvas, 28, 16, &I_ArrowDownEmpty_12x18); + } + + switch(model->mode) { + case DapMainViewModeDisconnected: + canvas_draw_str_aligned(canvas, 26, 38, AlignCenter, AlignTop, "----"); + break; + case DapMainViewModeSWD: + canvas_draw_str_aligned(canvas, 26, 38, AlignCenter, AlignTop, "SWD"); + break; + case DapMainViewModeJTAG: + canvas_draw_str_aligned(canvas, 26, 38, AlignCenter, AlignTop, "JTAG"); + break; + } + + if(model->tx_active) { + canvas_draw_icon(canvas, 87, 16, &I_ArrowUpFilled_12x18); + } else { + canvas_draw_icon(canvas, 87, 16, &I_ArrowUpEmpty_12x18); + } + + if(model->rx_active) { + canvas_draw_icon(canvas, 101, 16, &I_ArrowDownFilled_12x18); + } else { + canvas_draw_icon(canvas, 101, 16, &I_ArrowDownEmpty_12x18); + } + + canvas_draw_str_aligned(canvas, 100, 38, AlignCenter, AlignTop, "UART"); + + canvas_draw_line(canvas, 44, 52, 123, 52); + if(model->baudrate == 0) { + canvas_draw_str(canvas, 45, 62, "Baud: ????"); + } else { + char baudrate_str[18]; + snprintf(baudrate_str, 18, "Baud: %lu", model->baudrate); + canvas_draw_str(canvas, 45, 62, baudrate_str); + } +} + +static bool dap_main_view_input_callback(InputEvent* event, void* context) { + furi_assert(context); + DapMainView* dap_main_view = context; + bool consumed = false; + + if(event->type == InputTypeShort) { + if(event->key == InputKeyLeft) { + if(dap_main_view->cb_left) { + dap_main_view->cb_left(dap_main_view->cb_context); + } + consumed = true; + } + } + + return consumed; +} + +DapMainView* dap_main_view_alloc() { + DapMainView* dap_main_view = malloc(sizeof(DapMainView)); + + dap_main_view->view = view_alloc(); + view_allocate_model(dap_main_view->view, ViewModelTypeLocking, sizeof(DapMainViewModel)); + view_set_context(dap_main_view->view, dap_main_view); + view_set_draw_callback(dap_main_view->view, dap_main_view_draw_callback); + view_set_input_callback(dap_main_view->view, dap_main_view_input_callback); + return dap_main_view; +} + +void dap_main_view_free(DapMainView* dap_main_view) { + view_free(dap_main_view->view); + free(dap_main_view); +} + +View* dap_main_view_get_view(DapMainView* dap_main_view) { + return dap_main_view->view; +} + +void dap_main_view_set_left_callback( + DapMainView* dap_main_view, + DapMainViewButtonCallback callback, + void* context) { + with_view_model( + dap_main_view->view, + DapMainViewModel * model, + { + UNUSED(model); + dap_main_view->cb_left = callback; + dap_main_view->cb_context = context; + }, + true); +} + +void dap_main_view_set_mode(DapMainView* dap_main_view, DapMainViewMode mode) { + with_view_model( + dap_main_view->view, DapMainViewModel * model, { model->mode = mode; }, false); +} + +void dap_main_view_set_dap(DapMainView* dap_main_view, bool active) { + with_view_model( + dap_main_view->view, DapMainViewModel * model, { model->dap_active = active; }, false); +} + +void dap_main_view_set_tx(DapMainView* dap_main_view, bool active) { + with_view_model( + dap_main_view->view, DapMainViewModel * model, { model->tx_active = active; }, false); +} + +void dap_main_view_set_rx(DapMainView* dap_main_view, bool active) { + with_view_model( + dap_main_view->view, DapMainViewModel * model, { model->rx_active = active; }, false); +} + +void dap_main_view_set_baudrate(DapMainView* dap_main_view, uint32_t baudrate) { + with_view_model( + dap_main_view->view, DapMainViewModel * model, { model->baudrate = baudrate; }, false); +} + +void dap_main_view_update(DapMainView* dap_main_view) { + with_view_model( + dap_main_view->view, DapMainViewModel * model, { UNUSED(model); }, true); +} + +void dap_main_view_set_version(DapMainView* dap_main_view, DapMainViewVersion version) { + with_view_model( + dap_main_view->view, DapMainViewModel * model, { model->version = version; }, false); +} + +void dap_main_view_set_usb_connected(DapMainView* dap_main_view, bool connected) { + with_view_model( + dap_main_view->view, + DapMainViewModel * model, + { model->usb_connected = connected; }, + false); +} \ No newline at end of file diff --git a/applications/plugins/dap_link/gui/views/dap_main_view.h b/applications/plugins/dap_link/gui/views/dap_main_view.h new file mode 100644 index 00000000000..1fd90045279 --- /dev/null +++ b/applications/plugins/dap_link/gui/views/dap_main_view.h @@ -0,0 +1,45 @@ +#pragma once +#include + +typedef struct DapMainView DapMainView; + +typedef void (*DapMainViewButtonCallback)(void* context); + +typedef enum { + DapMainViewVersionUnknown, + DapMainViewVersionV1, + DapMainViewVersionV2, +} DapMainViewVersion; + +typedef enum { + DapMainViewModeDisconnected, + DapMainViewModeSWD, + DapMainViewModeJTAG, +} DapMainViewMode; + +DapMainView* dap_main_view_alloc(); + +void dap_main_view_free(DapMainView* dap_main_view); + +View* dap_main_view_get_view(DapMainView* dap_main_view); + +void dap_main_view_set_left_callback( + DapMainView* dap_main_view, + DapMainViewButtonCallback callback, + void* context); + +void dap_main_view_set_mode(DapMainView* dap_main_view, DapMainViewMode mode); + +void dap_main_view_set_version(DapMainView* dap_main_view, DapMainViewVersion version); + +void dap_main_view_set_dap(DapMainView* dap_main_view, bool active); + +void dap_main_view_set_tx(DapMainView* dap_main_view, bool active); + +void dap_main_view_set_rx(DapMainView* dap_main_view, bool active); + +void dap_main_view_set_usb_connected(DapMainView* dap_main_view, bool connected); + +void dap_main_view_set_baudrate(DapMainView* dap_main_view, uint32_t baudrate); + +void dap_main_view_update(DapMainView* dap_main_view); \ No newline at end of file diff --git a/applications/plugins/dap_link/icons/ArrowDownEmpty_12x18.png b/applications/plugins/dap_link/icons/ArrowDownEmpty_12x18.png new file mode 100644 index 0000000000000000000000000000000000000000..6007f74ab0e89fc751f503a8a5e4e44021c0106c GIT binary patch literal 160 zcmeAS@N?(olHy`uVBq!ia0vp^JU}eO!3HGrSK5O(jKx9jP7LeL$-D$|qC8z3Lo_D7 zo$SbWK!Jlr{PF+&Hnv;TJQ&v7?g&r!Fz9A{(LGbM+u3CI@thML?q)j=*|$VqJ$T$$ ztuvzCPsSl~7YFluKK0CllcIM`vf0BqQzUWaj&{}5`5zhLf9t51+o=lhbp(50(A=anC2ZSBvNHg_RnWm*uWGEIRgogS6BGwsp^g6qa`c R{s7v>;OXk;vd$@?2>@rkKMeo? literal 0 HcmV?d00001 diff --git a/applications/plugins/dap_link/icons/ArrowUpEmpty_12x18.png b/applications/plugins/dap_link/icons/ArrowUpEmpty_12x18.png new file mode 100644 index 0000000000000000000000000000000000000000..c9365a67d4e6e64761392b238965950762577773 GIT binary patch literal 159 zcmeAS@N?(olHy`uVBq!ia0vp^JU}eO!3HGrSK5O(jKx9jP7LeL$-D$|B0XIkLo_D7 zooL9{V8FxtcE$hyn7lHVqBK5*LoCO;xQ;Z~RV&b424_kZ9`tW^A4md67B@CXfelF{r G5}E*=05`}0 literal 0 HcmV?d00001 diff --git a/applications/plugins/dap_link/icons/ArrowUpFilled_12x18.png b/applications/plugins/dap_link/icons/ArrowUpFilled_12x18.png new file mode 100644 index 0000000000000000000000000000000000000000..dc481517ebae2d875880d96c3a6863c9ca985690 GIT binary patch literal 173 zcmeAS@N?(olHy`uVBq!ia0vp^JU}eO!3HGrSK5O(jKx9jP7LeL$-D$|(mh=qLo_Cy zop_M%fC7he^yB~ki^>fomK}Y&?Q6<{^IZXxHgu>k9N2PUPNa~E?*^7bKA+0AH5l)R zS}i4d#BjFRWa(K?7>YJFIonF7y_qz@_V#wWmAe;3-!N4C_Ce~$H&1(?8x60XU0}74 WwRe~lwXYaxC4;A{pUXO@geCw-ib4GV literal 0 HcmV?d00001 diff --git a/applications/plugins/dap_link/lib/free-dap b/applications/plugins/dap_link/lib/free-dap new file mode 160000 index 00000000000..e7752beb5e8 --- /dev/null +++ b/applications/plugins/dap_link/lib/free-dap @@ -0,0 +1 @@ +Subproject commit e7752beb5e8a69119af67b70b9179cb3c90f3ac5 diff --git a/applications/plugins/dap_link/usb/dap_v2_usb.c b/applications/plugins/dap_link/usb/dap_v2_usb.c new file mode 100644 index 00000000000..0c303a3ba41 --- /dev/null +++ b/applications/plugins/dap_link/usb/dap_v2_usb.c @@ -0,0 +1,994 @@ +#include +#include +#include +#include +#include +#include + +#include "dap_v2_usb.h" + +// #define DAP_USB_LOG + +#define HID_EP_IN 0x80 +#define HID_EP_OUT 0x00 + +#define DAP_HID_EP_SEND 1 +#define DAP_HID_EP_RECV 2 +#define DAP_HID_EP_BULK_RECV 3 +#define DAP_HID_EP_BULK_SEND 4 +#define DAP_CDC_EP_COMM 5 +#define DAP_CDC_EP_SEND 6 +#define DAP_CDC_EP_RECV 7 + +#define DAP_HID_EP_IN (HID_EP_IN | DAP_HID_EP_SEND) +#define DAP_HID_EP_OUT (HID_EP_OUT | DAP_HID_EP_RECV) +#define DAP_HID_EP_BULK_IN (HID_EP_IN | DAP_HID_EP_BULK_SEND) +#define DAP_HID_EP_BULK_OUT (HID_EP_OUT | DAP_HID_EP_BULK_RECV) + +#define DAP_HID_EP_SIZE 64 +#define DAP_CDC_COMM_EP_SIZE 8 +#define DAP_CDC_EP_SIZE 64 + +#define DAP_BULK_INTERVAL 0 +#define DAP_HID_INTERVAL 1 +#define DAP_CDC_INTERVAL 0 +#define DAP_CDC_COMM_INTERVAL 1 + +#define DAP_HID_VID 0x0483 +#define DAP_HID_PID 0x5740 + +#define DAP_USB_EP0_SIZE 8 + +#define EP_CFG_DECONFIGURE 0 +#define EP_CFG_CONFIGURE 1 + +enum { + USB_INTF_HID, + USB_INTF_BULK, + USB_INTF_CDC_COMM, + USB_INTF_CDC_DATA, + USB_INTF_COUNT, +}; + +enum { + USB_STR_ZERO, + USB_STR_MANUFACTURER, + USB_STR_PRODUCT, + USB_STR_SERIAL_NUMBER, + USB_STR_CMSIS_DAP_V1, + USB_STR_CMSIS_DAP_V2, + USB_STR_COM_PORT, + USB_STR_COUNT, +}; + +// static const char* usb_str[] = { +// [USB_STR_MANUFACTURER] = "Flipper Devices Inc.", +// [USB_STR_PRODUCT] = "Combined VCP and CMSIS-DAP Adapter", +// [USB_STR_COM_PORT] = "Virtual COM-Port", +// [USB_STR_CMSIS_DAP_V1] = "CMSIS-DAP v1 Adapter", +// [USB_STR_CMSIS_DAP_V2] = "CMSIS-DAP v2 Adapter", +// [USB_STR_SERIAL_NUMBER] = "01234567890ABCDEF", +// }; + +static const struct usb_string_descriptor dev_manuf_descr = + USB_STRING_DESC("Flipper Devices Inc."); + +static const struct usb_string_descriptor dev_prod_descr = + USB_STRING_DESC("Combined VCP and CMSIS-DAP Adapter"); + +static struct usb_string_descriptor* dev_serial_descr = NULL; + +static const struct usb_string_descriptor dev_dap_v1_descr = + USB_STRING_DESC("CMSIS-DAP v1 Adapter"); + +static const struct usb_string_descriptor dev_dap_v2_descr = + USB_STRING_DESC("CMSIS-DAP v2 Adapter"); + +static const struct usb_string_descriptor dev_com_descr = USB_STRING_DESC("Virtual COM-Port"); + +struct HidConfigDescriptor { + struct usb_config_descriptor configuration; + + // CMSIS-DAP v1 + struct usb_interface_descriptor hid_interface; + struct usb_hid_descriptor hid; + struct usb_endpoint_descriptor hid_ep_in; + struct usb_endpoint_descriptor hid_ep_out; + + // CMSIS-DAP v2 + struct usb_interface_descriptor bulk_interface; + struct usb_endpoint_descriptor bulk_ep_out; + struct usb_endpoint_descriptor bulk_ep_in; + + // CDC + struct usb_iad_descriptor iad; + struct usb_interface_descriptor interface_comm; + struct usb_cdc_header_desc cdc_header; + struct usb_cdc_call_mgmt_desc cdc_acm; + struct usb_cdc_acm_desc cdc_call_mgmt; + struct usb_cdc_union_desc cdc_union; + struct usb_endpoint_descriptor ep_comm; + struct usb_interface_descriptor interface_data; + struct usb_endpoint_descriptor ep_in; + struct usb_endpoint_descriptor ep_out; + +} __attribute__((packed)); + +static const struct usb_device_descriptor hid_device_desc = { + .bLength = sizeof(struct usb_device_descriptor), + .bDescriptorType = USB_DTYPE_DEVICE, + .bcdUSB = VERSION_BCD(2, 1, 0), + .bDeviceClass = USB_CLASS_MISC, + .bDeviceSubClass = USB_SUBCLASS_IAD, + .bDeviceProtocol = USB_PROTO_IAD, + .bMaxPacketSize0 = DAP_USB_EP0_SIZE, + .idVendor = DAP_HID_VID, + .idProduct = DAP_HID_PID, + .bcdDevice = VERSION_BCD(1, 0, 0), + .iManufacturer = USB_STR_MANUFACTURER, + .iProduct = USB_STR_PRODUCT, + .iSerialNumber = USB_STR_SERIAL_NUMBER, + .bNumConfigurations = 1, +}; + +static const uint8_t hid_report_desc[] = { + 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) + 0x09, 0x00, // Usage (Undefined) + 0xa1, 0x01, // Collection (Application) + 0x15, 0x00, // Logical Minimum (0) + 0x26, 0xff, 0x00, // Logical Maximum (255) + 0x75, 0x08, // Report Size (8) + 0x95, 0x40, // Report Count (64) + 0x09, 0x00, // Usage (Undefined) + 0x81, 0x82, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) + 0x75, 0x08, // Report Size (8) + 0x95, 0x40, // Report Count (64) + 0x09, 0x00, // Usage (Undefined) + 0x91, 0x82, // Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Volatile) + 0xc0, // End Collection +}; + +static const struct HidConfigDescriptor hid_cfg_desc = { + .configuration = + { + .bLength = sizeof(struct usb_config_descriptor), + .bDescriptorType = USB_DTYPE_CONFIGURATION, + .wTotalLength = sizeof(struct HidConfigDescriptor), + .bNumInterfaces = USB_INTF_COUNT, + .bConfigurationValue = 1, + .iConfiguration = NO_DESCRIPTOR, + .bmAttributes = USB_CFG_ATTR_RESERVED, + .bMaxPower = USB_CFG_POWER_MA(500), + }, + + // CMSIS-DAP v1 + .hid_interface = + { + .bLength = sizeof(struct usb_interface_descriptor), + .bDescriptorType = USB_DTYPE_INTERFACE, + .bInterfaceNumber = USB_INTF_HID, + .bAlternateSetting = 0, + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_HID, + .bInterfaceSubClass = USB_HID_SUBCLASS_NONBOOT, + .bInterfaceProtocol = USB_HID_PROTO_NONBOOT, + .iInterface = USB_STR_CMSIS_DAP_V1, + }, + + .hid = + { + .bLength = sizeof(struct usb_hid_descriptor), + .bDescriptorType = USB_DTYPE_HID, + .bcdHID = VERSION_BCD(1, 1, 1), + .bCountryCode = USB_HID_COUNTRY_NONE, + .bNumDescriptors = 1, + .bDescriptorType0 = USB_DTYPE_HID_REPORT, + .wDescriptorLength0 = sizeof(hid_report_desc), + }, + + .hid_ep_in = + { + .bLength = sizeof(struct usb_endpoint_descriptor), + .bDescriptorType = USB_DTYPE_ENDPOINT, + .bEndpointAddress = DAP_HID_EP_IN, + .bmAttributes = USB_EPTYPE_INTERRUPT, + .wMaxPacketSize = DAP_HID_EP_SIZE, + .bInterval = DAP_HID_INTERVAL, + }, + + .hid_ep_out = + { + .bLength = sizeof(struct usb_endpoint_descriptor), + .bDescriptorType = USB_DTYPE_ENDPOINT, + .bEndpointAddress = DAP_HID_EP_OUT, + .bmAttributes = USB_EPTYPE_INTERRUPT, + .wMaxPacketSize = DAP_HID_EP_SIZE, + .bInterval = DAP_HID_INTERVAL, + }, + + // CMSIS-DAP v2 + .bulk_interface = + { + .bLength = sizeof(struct usb_interface_descriptor), + .bDescriptorType = USB_DTYPE_INTERFACE, + .bInterfaceNumber = USB_INTF_BULK, + .bAlternateSetting = 0, + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_VENDOR, + .bInterfaceSubClass = 0, + .bInterfaceProtocol = 0, + .iInterface = USB_STR_CMSIS_DAP_V2, + }, + + .bulk_ep_out = + { + .bLength = sizeof(struct usb_endpoint_descriptor), + .bDescriptorType = USB_DTYPE_ENDPOINT, + .bEndpointAddress = DAP_HID_EP_BULK_OUT, + .bmAttributes = USB_EPTYPE_BULK, + .wMaxPacketSize = DAP_HID_EP_SIZE, + .bInterval = DAP_BULK_INTERVAL, + }, + + .bulk_ep_in = + { + .bLength = sizeof(struct usb_endpoint_descriptor), + .bDescriptorType = USB_DTYPE_ENDPOINT, + .bEndpointAddress = DAP_HID_EP_BULK_IN, + .bmAttributes = USB_EPTYPE_BULK, + .wMaxPacketSize = DAP_HID_EP_SIZE, + .bInterval = DAP_BULK_INTERVAL, + }, + + // CDC + .iad = + { + .bLength = sizeof(struct usb_iad_descriptor), + .bDescriptorType = USB_DTYPE_INTERFASEASSOC, + .bFirstInterface = USB_INTF_CDC_COMM, + .bInterfaceCount = 2, + .bFunctionClass = USB_CLASS_CDC, + .bFunctionSubClass = USB_CDC_SUBCLASS_ACM, + .bFunctionProtocol = USB_PROTO_NONE, + .iFunction = USB_STR_COM_PORT, + }, + .interface_comm = + { + .bLength = sizeof(struct usb_interface_descriptor), + .bDescriptorType = USB_DTYPE_INTERFACE, + .bInterfaceNumber = USB_INTF_CDC_COMM, + .bAlternateSetting = 0, + .bNumEndpoints = 1, + .bInterfaceClass = USB_CLASS_CDC, + .bInterfaceSubClass = USB_CDC_SUBCLASS_ACM, + .bInterfaceProtocol = USB_PROTO_NONE, + .iInterface = 0, + }, + + .cdc_header = + { + .bFunctionLength = sizeof(struct usb_cdc_header_desc), + .bDescriptorType = USB_DTYPE_CS_INTERFACE, + .bDescriptorSubType = USB_DTYPE_CDC_HEADER, + .bcdCDC = VERSION_BCD(1, 1, 0), + }, + + .cdc_acm = + { + .bFunctionLength = sizeof(struct usb_cdc_call_mgmt_desc), + .bDescriptorType = USB_DTYPE_CS_INTERFACE, + .bDescriptorSubType = USB_DTYPE_CDC_CALL_MANAGEMENT, + // .bmCapabilities = USB_CDC_CAP_LINE | USB_CDC_CAP_BRK, + .bmCapabilities = 0, + }, + + .cdc_call_mgmt = + { + .bFunctionLength = sizeof(struct usb_cdc_acm_desc), + .bDescriptorType = USB_DTYPE_CS_INTERFACE, + .bDescriptorSubType = USB_DTYPE_CDC_ACM, + .bmCapabilities = USB_CDC_CALL_MGMT_CAP_DATA_INTF, + // .bDataInterface = USB_INTF_CDC_DATA, + }, + + .cdc_union = + { + .bFunctionLength = sizeof(struct usb_cdc_union_desc), + .bDescriptorType = USB_DTYPE_CS_INTERFACE, + .bDescriptorSubType = USB_DTYPE_CDC_UNION, + .bMasterInterface0 = USB_INTF_CDC_COMM, + .bSlaveInterface0 = USB_INTF_CDC_DATA, + }, + + .ep_comm = + { + .bLength = sizeof(struct usb_endpoint_descriptor), + .bDescriptorType = USB_DTYPE_ENDPOINT, + .bEndpointAddress = HID_EP_IN | DAP_CDC_EP_COMM, + .bmAttributes = USB_EPTYPE_INTERRUPT, + .wMaxPacketSize = DAP_CDC_COMM_EP_SIZE, + .bInterval = DAP_CDC_COMM_INTERVAL, + }, + + .interface_data = + { + .bLength = sizeof(struct usb_interface_descriptor), + .bDescriptorType = USB_DTYPE_INTERFACE, + .bInterfaceNumber = USB_INTF_CDC_DATA, + .bAlternateSetting = 0, + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_CDC_DATA, + .bInterfaceSubClass = USB_SUBCLASS_NONE, + .bInterfaceProtocol = USB_PROTO_NONE, + .iInterface = NO_DESCRIPTOR, + }, + + .ep_in = + { + .bLength = sizeof(struct usb_endpoint_descriptor), + .bDescriptorType = USB_DTYPE_ENDPOINT, + .bEndpointAddress = HID_EP_IN | DAP_CDC_EP_SEND, + .bmAttributes = USB_EPTYPE_BULK, + .wMaxPacketSize = DAP_CDC_EP_SIZE, + .bInterval = DAP_CDC_INTERVAL, + }, + + .ep_out = + { + .bLength = sizeof(struct usb_endpoint_descriptor), + .bDescriptorType = USB_DTYPE_ENDPOINT, + .bEndpointAddress = HID_EP_OUT | DAP_CDC_EP_RECV, + .bmAttributes = USB_EPTYPE_BULK, + .wMaxPacketSize = DAP_CDC_EP_SIZE, + .bInterval = DAP_CDC_INTERVAL, + }, +}; + +// WinUSB +#include "usb_winusb.h" + +typedef struct USB_PACK { + usb_binary_object_store_descriptor_t bos; + usb_winusb_capability_descriptor_t winusb; +} usb_bos_hierarchy_t; + +typedef struct USB_PACK { + usb_winusb_subset_header_function_t header; + usb_winusb_feature_compatble_id_t comp_id; + usb_winusb_feature_reg_property_guids_t property; +} usb_msos_descriptor_subset_t; + +typedef struct USB_PACK { + usb_winusb_set_header_descriptor_t header; + usb_msos_descriptor_subset_t subset; +} usb_msos_descriptor_set_t; + +#define USB_DTYPE_BINARY_OBJECT_STORE 15 +#define USB_DTYPE_DEVICE_CAPABILITY_DESCRIPTOR 16 +#define USB_DC_TYPE_PLATFORM 5 + +const usb_bos_hierarchy_t usb_bos_hierarchy = { + .bos = + { + .bLength = sizeof(usb_binary_object_store_descriptor_t), + .bDescriptorType = USB_DTYPE_BINARY_OBJECT_STORE, + .wTotalLength = sizeof(usb_bos_hierarchy_t), + .bNumDeviceCaps = 1, + }, + .winusb = + { + .bLength = sizeof(usb_winusb_capability_descriptor_t), + .bDescriptorType = USB_DTYPE_DEVICE_CAPABILITY_DESCRIPTOR, + .bDevCapabilityType = USB_DC_TYPE_PLATFORM, + .bReserved = 0, + .PlatformCapabilityUUID = USB_WINUSB_PLATFORM_CAPABILITY_ID, + .dwWindowsVersion = USB_WINUSB_WINDOWS_VERSION, + .wMSOSDescriptorSetTotalLength = sizeof(usb_msos_descriptor_set_t), + .bMS_VendorCode = USB_WINUSB_VENDOR_CODE, + .bAltEnumCode = 0, + }, +}; + +const usb_msos_descriptor_set_t usb_msos_descriptor_set = { + .header = + { + .wLength = sizeof(usb_winusb_set_header_descriptor_t), + .wDescriptorType = USB_WINUSB_SET_HEADER_DESCRIPTOR, + .dwWindowsVersion = USB_WINUSB_WINDOWS_VERSION, + .wDescriptorSetTotalLength = sizeof(usb_msos_descriptor_set_t), + }, + + .subset = + { + .header = + { + .wLength = sizeof(usb_winusb_subset_header_function_t), + .wDescriptorType = USB_WINUSB_SUBSET_HEADER_FUNCTION, + .bFirstInterface = USB_INTF_BULK, + .bReserved = 0, + .wSubsetLength = sizeof(usb_msos_descriptor_subset_t), + }, + + .comp_id = + { + .wLength = sizeof(usb_winusb_feature_compatble_id_t), + .wDescriptorType = USB_WINUSB_FEATURE_COMPATBLE_ID, + .CompatibleID = "WINUSB\0\0", + .SubCompatibleID = {0}, + }, + + .property = + { + .wLength = sizeof(usb_winusb_feature_reg_property_guids_t), + .wDescriptorType = USB_WINUSB_FEATURE_REG_PROPERTY, + .wPropertyDataType = USB_WINUSB_PROPERTY_DATA_TYPE_MULTI_SZ, + .wPropertyNameLength = + sizeof(usb_msos_descriptor_set.subset.property.PropertyName), + .PropertyName = {'D', 0, 'e', 0, 'v', 0, 'i', 0, 'c', 0, 'e', 0, 'I', 0, + 'n', 0, 't', 0, 'e', 0, 'r', 0, 'f', 0, 'a', 0, 'c', 0, + 'e', 0, 'G', 0, 'U', 0, 'I', 0, 'D', 0, 's', 0, 0, 0}, + .wPropertyDataLength = + sizeof(usb_msos_descriptor_set.subset.property.PropertyData), + .PropertyData = {'{', 0, 'C', 0, 'D', 0, 'B', 0, '3', 0, 'B', 0, '5', 0, + 'A', 0, 'D', 0, '-', 0, '2', 0, '9', 0, '3', 0, 'B', 0, + '-', 0, '4', 0, '6', 0, '6', 0, '3', 0, '-', 0, 'A', 0, + 'A', 0, '3', 0, '6', 0, '-', 0, '1', 0, 'A', 0, 'A', 0, + 'E', 0, '4', 0, '6', 0, '4', 0, '6', 0, '3', 0, '7', 0, + '7', 0, '6', 0, '}', 0, 0, 0, 0, 0}, + }, + }, +}; + +typedef struct { + FuriSemaphore* semaphore_v1; + FuriSemaphore* semaphore_v2; + FuriSemaphore* semaphore_cdc; + bool connected; + usbd_device* usb_dev; + DapStateCallback state_callback; + DapRxCallback rx_callback_v1; + DapRxCallback rx_callback_v2; + DapRxCallback rx_callback_cdc; + DapCDCControlLineCallback control_line_callback_cdc; + DapCDCConfigCallback config_callback_cdc; + void* context; + void* context_cdc; +} DAPState; + +static DAPState dap_state = { + .semaphore_v1 = NULL, + .semaphore_v2 = NULL, + .semaphore_cdc = NULL, + .connected = false, + .usb_dev = NULL, + .state_callback = NULL, + .rx_callback_v1 = NULL, + .rx_callback_v2 = NULL, + .rx_callback_cdc = NULL, + .control_line_callback_cdc = NULL, + .config_callback_cdc = NULL, + .context = NULL, + .context_cdc = NULL, +}; + +static struct usb_cdc_line_coding cdc_config = {0}; +static uint8_t cdc_ctrl_line_state = 0; + +#ifdef DAP_USB_LOG +void furi_console_log_printf(const char* format, ...) _ATTRIBUTE((__format__(__printf__, 1, 2))); + +void furi_console_log_printf(const char* format, ...) { + char buffer[256]; + va_list args; + va_start(args, format); + vsnprintf(buffer, sizeof(buffer), format, args); + va_end(args); + furi_hal_console_puts(buffer); + furi_hal_console_puts("\r\n"); + UNUSED(format); +} +#else +#define furi_console_log_printf(...) +#endif + +int32_t dap_v1_usb_tx(uint8_t* buffer, uint8_t size) { + if((dap_state.semaphore_v1 == NULL) || (dap_state.connected == false)) return 0; + + furi_check(furi_semaphore_acquire(dap_state.semaphore_v1, FuriWaitForever) == FuriStatusOk); + + if(dap_state.connected) { + int32_t len = usbd_ep_write(dap_state.usb_dev, DAP_HID_EP_IN, buffer, size); + furi_console_log_printf("v1 tx %ld", len); + return len; + } else { + return 0; + } +} + +int32_t dap_v2_usb_tx(uint8_t* buffer, uint8_t size) { + if((dap_state.semaphore_v2 == NULL) || (dap_state.connected == false)) return 0; + + furi_check(furi_semaphore_acquire(dap_state.semaphore_v2, FuriWaitForever) == FuriStatusOk); + + if(dap_state.connected) { + int32_t len = usbd_ep_write(dap_state.usb_dev, DAP_HID_EP_BULK_IN, buffer, size); + furi_console_log_printf("v2 tx %ld", len); + return len; + } else { + return 0; + } +} + +int32_t dap_cdc_usb_tx(uint8_t* buffer, uint8_t size) { + if((dap_state.semaphore_cdc == NULL) || (dap_state.connected == false)) return 0; + + furi_check(furi_semaphore_acquire(dap_state.semaphore_cdc, FuriWaitForever) == FuriStatusOk); + + if(dap_state.connected) { + int32_t len = usbd_ep_write(dap_state.usb_dev, HID_EP_IN | DAP_CDC_EP_SEND, buffer, size); + furi_console_log_printf("cdc tx %ld", len); + return len; + } else { + return 0; + } +} + +void dap_v1_usb_set_rx_callback(DapRxCallback callback) { + dap_state.rx_callback_v1 = callback; +} + +void dap_v2_usb_set_rx_callback(DapRxCallback callback) { + dap_state.rx_callback_v2 = callback; +} + +void dap_cdc_usb_set_rx_callback(DapRxCallback callback) { + dap_state.rx_callback_cdc = callback; +} + +void dap_cdc_usb_set_control_line_callback(DapCDCControlLineCallback callback) { + dap_state.control_line_callback_cdc = callback; +} + +void dap_cdc_usb_set_config_callback(DapCDCConfigCallback callback) { + dap_state.config_callback_cdc = callback; +} + +void dap_cdc_usb_set_context(void* context) { + dap_state.context_cdc = context; +} + +void dap_common_usb_set_context(void* context) { + dap_state.context = context; +} + +void dap_common_usb_set_state_callback(DapStateCallback callback) { + dap_state.state_callback = callback; +} + +static void* dap_usb_alloc_string_descr(const char* str) { + furi_assert(str); + + uint8_t len = strlen(str); + uint8_t wlen = (len + 1) * sizeof(uint16_t); + struct usb_string_descriptor* dev_str_desc = malloc(wlen); + dev_str_desc->bLength = wlen; + dev_str_desc->bDescriptorType = USB_DTYPE_STRING; + for(uint8_t i = 0; i < len; i++) { + dev_str_desc->wString[i] = str[i]; + } + + return dev_str_desc; +} + +void dap_common_usb_alloc_name(const char* name) { + dev_serial_descr = dap_usb_alloc_string_descr(name); +} + +void dap_common_usb_free_name() { + free(dev_serial_descr); +} + +static void hid_init(usbd_device* dev, FuriHalUsbInterface* intf, void* ctx); +static void hid_deinit(usbd_device* dev); +static void hid_on_wakeup(usbd_device* dev); +static void hid_on_suspend(usbd_device* dev); + +static usbd_respond hid_ep_config(usbd_device* dev, uint8_t cfg); +static usbd_respond hid_control(usbd_device* dev, usbd_ctlreq* req, usbd_rqc_callback* callback); + +FuriHalUsbInterface dap_v2_usb_hid = { + .init = hid_init, + .deinit = hid_deinit, + .wakeup = hid_on_wakeup, + .suspend = hid_on_suspend, + .dev_descr = (struct usb_device_descriptor*)&hid_device_desc, + .cfg_descr = (void*)&hid_cfg_desc, +}; + +static void hid_init(usbd_device* dev, FuriHalUsbInterface* intf, void* ctx) { + UNUSED(intf); + UNUSED(ctx); + + dap_v2_usb_hid.str_manuf_descr = (void*)&dev_manuf_descr; + dap_v2_usb_hid.str_prod_descr = (void*)&dev_prod_descr; + dap_v2_usb_hid.str_serial_descr = (void*)dev_serial_descr; + + dap_state.usb_dev = dev; + if(dap_state.semaphore_v1 == NULL) dap_state.semaphore_v1 = furi_semaphore_alloc(1, 1); + if(dap_state.semaphore_v2 == NULL) dap_state.semaphore_v2 = furi_semaphore_alloc(1, 1); + if(dap_state.semaphore_cdc == NULL) dap_state.semaphore_cdc = furi_semaphore_alloc(1, 1); + + usb_hid.dev_descr->idVendor = DAP_HID_VID; + usb_hid.dev_descr->idProduct = DAP_HID_PID; + + usbd_reg_config(dev, hid_ep_config); + usbd_reg_control(dev, hid_control); + + usbd_connect(dev, true); +} + +static bool deinit_flag = false; + +void dap_common_wait_for_deinit() { + while(!deinit_flag) { + furi_delay_ms(50); + } +} + +static void hid_deinit(usbd_device* dev) { + dap_state.usb_dev = NULL; + + furi_semaphore_free(dap_state.semaphore_v1); + furi_semaphore_free(dap_state.semaphore_v2); + furi_semaphore_free(dap_state.semaphore_cdc); + dap_state.semaphore_v1 = NULL; + dap_state.semaphore_v2 = NULL; + dap_state.semaphore_cdc = NULL; + + usbd_reg_config(dev, NULL); + usbd_reg_control(dev, NULL); + + free(usb_hid.str_manuf_descr); + free(usb_hid.str_prod_descr); + + FURI_SW_MEMBARRIER(); + deinit_flag = true; +} + +static void hid_on_wakeup(usbd_device* dev) { + UNUSED(dev); + if(!dap_state.connected) { + dap_state.connected = true; + if(dap_state.state_callback != NULL) { + dap_state.state_callback(dap_state.connected, dap_state.context); + } + } +} + +static void hid_on_suspend(usbd_device* dev) { + UNUSED(dev); + if(dap_state.connected) { + dap_state.connected = false; + if(dap_state.state_callback != NULL) { + dap_state.state_callback(dap_state.connected, dap_state.context); + } + } +} + +size_t dap_v1_usb_rx(uint8_t* buffer, size_t size) { + size_t len = 0; + + if(dap_state.connected) { + len = usbd_ep_read(dap_state.usb_dev, DAP_HID_EP_OUT, buffer, size); + } + + return len; +} + +size_t dap_v2_usb_rx(uint8_t* buffer, size_t size) { + size_t len = 0; + + if(dap_state.connected) { + len = usbd_ep_read(dap_state.usb_dev, DAP_HID_EP_BULK_OUT, buffer, size); + } + + return len; +} + +size_t dap_cdc_usb_rx(uint8_t* buffer, size_t size) { + size_t len = 0; + + if(dap_state.connected) { + len = usbd_ep_read(dap_state.usb_dev, HID_EP_OUT | DAP_CDC_EP_RECV, buffer, size); + } + + return len; +} + +static void hid_txrx_ep_callback(usbd_device* dev, uint8_t event, uint8_t ep) { + UNUSED(dev); + UNUSED(ep); + + switch(event) { + case usbd_evt_eptx: + furi_semaphore_release(dap_state.semaphore_v1); + furi_console_log_printf("hid tx complete"); + break; + case usbd_evt_eprx: + if(dap_state.rx_callback_v1 != NULL) { + dap_state.rx_callback_v1(dap_state.context); + } + break; + default: + furi_console_log_printf("hid %d, %d", event, ep); + break; + } +} + +static void hid_txrx_ep_bulk_callback(usbd_device* dev, uint8_t event, uint8_t ep) { + UNUSED(dev); + UNUSED(ep); + + switch(event) { + case usbd_evt_eptx: + furi_semaphore_release(dap_state.semaphore_v2); + furi_console_log_printf("bulk tx complete"); + break; + case usbd_evt_eprx: + if(dap_state.rx_callback_v2 != NULL) { + dap_state.rx_callback_v2(dap_state.context); + } + break; + default: + furi_console_log_printf("bulk %d, %d", event, ep); + break; + } +} + +static void cdc_txrx_ep_callback(usbd_device* dev, uint8_t event, uint8_t ep) { + UNUSED(dev); + UNUSED(ep); + + switch(event) { + case usbd_evt_eptx: + furi_semaphore_release(dap_state.semaphore_cdc); + furi_console_log_printf("cdc tx complete"); + break; + case usbd_evt_eprx: + if(dap_state.rx_callback_cdc != NULL) { + dap_state.rx_callback_cdc(dap_state.context_cdc); + } + break; + default: + furi_console_log_printf("cdc %d, %d", event, ep); + break; + } +} + +static usbd_respond hid_ep_config(usbd_device* dev, uint8_t cfg) { + switch(cfg) { + case EP_CFG_DECONFIGURE: + usbd_ep_deconfig(dev, DAP_HID_EP_OUT); + usbd_ep_deconfig(dev, DAP_HID_EP_IN); + usbd_ep_deconfig(dev, DAP_HID_EP_BULK_IN); + usbd_ep_deconfig(dev, DAP_HID_EP_BULK_OUT); + usbd_ep_deconfig(dev, HID_EP_IN | DAP_CDC_EP_COMM); + usbd_ep_deconfig(dev, HID_EP_IN | DAP_CDC_EP_SEND); + usbd_ep_deconfig(dev, HID_EP_OUT | DAP_CDC_EP_RECV); + usbd_reg_endpoint(dev, DAP_HID_EP_OUT, NULL); + usbd_reg_endpoint(dev, DAP_HID_EP_IN, NULL); + usbd_reg_endpoint(dev, DAP_HID_EP_BULK_IN, NULL); + usbd_reg_endpoint(dev, DAP_HID_EP_BULK_OUT, NULL); + usbd_reg_endpoint(dev, HID_EP_IN | DAP_CDC_EP_SEND, 0); + usbd_reg_endpoint(dev, HID_EP_OUT | DAP_CDC_EP_RECV, 0); + return usbd_ack; + case EP_CFG_CONFIGURE: + usbd_ep_config(dev, DAP_HID_EP_IN, USB_EPTYPE_INTERRUPT, DAP_HID_EP_SIZE); + usbd_ep_config(dev, DAP_HID_EP_OUT, USB_EPTYPE_INTERRUPT, DAP_HID_EP_SIZE); + usbd_ep_config(dev, DAP_HID_EP_BULK_OUT, USB_EPTYPE_BULK, DAP_HID_EP_SIZE); + usbd_ep_config(dev, DAP_HID_EP_BULK_IN, USB_EPTYPE_BULK, DAP_HID_EP_SIZE); + usbd_ep_config(dev, HID_EP_OUT | DAP_CDC_EP_RECV, USB_EPTYPE_BULK, DAP_CDC_EP_SIZE); + usbd_ep_config(dev, HID_EP_IN | DAP_CDC_EP_SEND, USB_EPTYPE_BULK, DAP_CDC_EP_SIZE); + usbd_ep_config(dev, HID_EP_IN | DAP_CDC_EP_COMM, USB_EPTYPE_INTERRUPT, DAP_CDC_EP_SIZE); + usbd_reg_endpoint(dev, DAP_HID_EP_IN, hid_txrx_ep_callback); + usbd_reg_endpoint(dev, DAP_HID_EP_OUT, hid_txrx_ep_callback); + usbd_reg_endpoint(dev, DAP_HID_EP_BULK_OUT, hid_txrx_ep_bulk_callback); + usbd_reg_endpoint(dev, DAP_HID_EP_BULK_IN, hid_txrx_ep_bulk_callback); + usbd_reg_endpoint(dev, HID_EP_OUT | DAP_CDC_EP_RECV, cdc_txrx_ep_callback); + usbd_reg_endpoint(dev, HID_EP_IN | DAP_CDC_EP_SEND, cdc_txrx_ep_callback); + // usbd_ep_write(dev, DAP_HID_EP_IN, NULL, 0); + // usbd_ep_write(dev, DAP_HID_EP_BULK_IN, NULL, 0); + // usbd_ep_write(dev, HID_EP_IN | DAP_CDC_EP_SEND, NULL, 0); + return usbd_ack; + default: + return usbd_fail; + } +} + +#ifdef DAP_USB_LOG +static void dump_request_type(uint8_t type) { + switch(type & USB_REQ_DIRECTION) { + case USB_REQ_HOSTTODEV: + furi_hal_console_puts("host to dev, "); + break; + case USB_REQ_DEVTOHOST: + furi_hal_console_puts("dev to host, "); + break; + } + + switch(type & USB_REQ_TYPE) { + case USB_REQ_STANDARD: + furi_hal_console_puts("standard, "); + break; + case USB_REQ_CLASS: + furi_hal_console_puts("class, "); + break; + case USB_REQ_VENDOR: + furi_hal_console_puts("vendor, "); + break; + } + + switch(type & USB_REQ_RECIPIENT) { + case USB_REQ_DEVICE: + furi_hal_console_puts("device"); + break; + case USB_REQ_INTERFACE: + furi_hal_console_puts("interface"); + break; + case USB_REQ_ENDPOINT: + furi_hal_console_puts("endpoint"); + break; + case USB_REQ_OTHER: + furi_hal_console_puts("other"); + break; + } + + furi_hal_console_puts("\r\n"); +} +#else +#define dump_request_type(...) +#endif + +static usbd_respond hid_control(usbd_device* dev, usbd_ctlreq* req, usbd_rqc_callback* callback) { + UNUSED(callback); + + dump_request_type(req->bmRequestType); + furi_console_log_printf( + "control: RT %02x, R %02x, V %04x, I %04x, L %04x", + req->bmRequestType, + req->bRequest, + req->wValue, + req->wIndex, + req->wLength); + + if(((USB_REQ_RECIPIENT | USB_REQ_TYPE | USB_REQ_DIRECTION) & req->bmRequestType) == + (USB_REQ_STANDARD | USB_REQ_VENDOR | USB_REQ_DEVTOHOST)) { + // vendor request, device to host + furi_console_log_printf("vendor request"); + if(USB_WINUSB_VENDOR_CODE == req->bRequest) { + // WINUSB request + if(USB_WINUSB_DESCRIPTOR_INDEX == req->wIndex) { + furi_console_log_printf("WINUSB descriptor"); + uint16_t length = req->wLength; + if(length > sizeof(usb_msos_descriptor_set_t)) { + length = sizeof(usb_msos_descriptor_set_t); + } + + dev->status.data_ptr = (uint8_t*)&usb_msos_descriptor_set; + dev->status.data_count = length; + return usbd_ack; + } + } + } + + if(((USB_REQ_RECIPIENT | USB_REQ_TYPE) & req->bmRequestType) == + (USB_REQ_STANDARD | USB_REQ_DEVICE)) { + // device request + if(req->bRequest == USB_STD_GET_DESCRIPTOR) { + const uint8_t dtype = req->wValue >> 8; + const uint8_t dnumber = req->wValue & 0xFF; + // get string descriptor + if(USB_DTYPE_STRING == dtype) { + if(dnumber == USB_STR_CMSIS_DAP_V1) { + furi_console_log_printf("str CMSIS-DAP v1"); + dev->status.data_ptr = (uint8_t*)&dev_dap_v1_descr; + dev->status.data_count = dev_dap_v1_descr.bLength; + return usbd_ack; + } else if(dnumber == USB_STR_CMSIS_DAP_V2) { + furi_console_log_printf("str CMSIS-DAP v2"); + dev->status.data_ptr = (uint8_t*)&dev_dap_v2_descr; + dev->status.data_count = dev_dap_v2_descr.bLength; + return usbd_ack; + } else if(dnumber == USB_STR_COM_PORT) { + furi_console_log_printf("str COM port"); + dev->status.data_ptr = (uint8_t*)&dev_com_descr; + dev->status.data_count = dev_com_descr.bLength; + return usbd_ack; + } + } else if(USB_DTYPE_BINARY_OBJECT_STORE == dtype) { + furi_console_log_printf("BOS descriptor"); + uint16_t length = req->wLength; + if(length > sizeof(usb_bos_hierarchy_t)) { + length = sizeof(usb_bos_hierarchy_t); + } + dev->status.data_ptr = (uint8_t*)&usb_bos_hierarchy; + dev->status.data_count = length; + return usbd_ack; + } + } + } + + if(((USB_REQ_RECIPIENT | USB_REQ_TYPE) & req->bmRequestType) == + (USB_REQ_INTERFACE | USB_REQ_CLASS) && + req->wIndex == 0) { + // class request + switch(req->bRequest) { + // get hid descriptor + case USB_HID_GETREPORT: + furi_console_log_printf("get report"); + return usbd_fail; + // set hid idle + case USB_HID_SETIDLE: + furi_console_log_printf("set idle"); + return usbd_ack; + default: + break; + } + } + + if(((USB_REQ_RECIPIENT | USB_REQ_TYPE) & req->bmRequestType) == + (USB_REQ_INTERFACE | USB_REQ_CLASS) && + req->wIndex == 2) { + // class request + switch(req->bRequest) { + // control line state + case USB_CDC_SET_CONTROL_LINE_STATE: + furi_console_log_printf("set control line state"); + cdc_ctrl_line_state = req->wValue; + if(dap_state.control_line_callback_cdc != NULL) { + dap_state.control_line_callback_cdc(cdc_ctrl_line_state, dap_state.context_cdc); + } + return usbd_ack; + // set cdc line coding + case USB_CDC_SET_LINE_CODING: + furi_console_log_printf("set line coding"); + memcpy(&cdc_config, req->data, sizeof(cdc_config)); + if(dap_state.config_callback_cdc != NULL) { + dap_state.config_callback_cdc(&cdc_config, dap_state.context_cdc); + } + return usbd_ack; + // get cdc line coding + case USB_CDC_GET_LINE_CODING: + furi_console_log_printf("get line coding"); + dev->status.data_ptr = &cdc_config; + dev->status.data_count = sizeof(cdc_config); + return usbd_ack; + default: + break; + } + } + + if(((USB_REQ_RECIPIENT | USB_REQ_TYPE) & req->bmRequestType) == + (USB_REQ_INTERFACE | USB_REQ_STANDARD) && + req->wIndex == 0 && req->bRequest == USB_STD_GET_DESCRIPTOR) { + // standard request + switch(req->wValue >> 8) { + // get hid descriptor + case USB_DTYPE_HID: + furi_console_log_printf("get hid descriptor"); + dev->status.data_ptr = (uint8_t*)&(hid_cfg_desc.hid); + dev->status.data_count = sizeof(hid_cfg_desc.hid); + return usbd_ack; + // get hid report descriptor + case USB_DTYPE_HID_REPORT: + furi_console_log_printf("get hid report descriptor"); + dev->status.data_ptr = (uint8_t*)hid_report_desc; + dev->status.data_count = sizeof(hid_report_desc); + return usbd_ack; + default: + break; + } + } + + return usbd_fail; +} \ No newline at end of file diff --git a/applications/plugins/dap_link/usb/dap_v2_usb.h b/applications/plugins/dap_link/usb/dap_v2_usb.h new file mode 100644 index 00000000000..2a0e86056fd --- /dev/null +++ b/applications/plugins/dap_link/usb/dap_v2_usb.h @@ -0,0 +1,55 @@ +#pragma once +#include +#include + +extern FuriHalUsbInterface dap_v2_usb_hid; + +// receive callback type +typedef void (*DapRxCallback)(void* context); + +typedef void (*DapStateCallback)(bool state, void* context); + +/************************************ V1 ***************************************/ + +int32_t dap_v1_usb_tx(uint8_t* buffer, uint8_t size); + +size_t dap_v1_usb_rx(uint8_t* buffer, size_t size); + +void dap_v1_usb_set_rx_callback(DapRxCallback callback); + +/************************************ V2 ***************************************/ + +int32_t dap_v2_usb_tx(uint8_t* buffer, uint8_t size); + +size_t dap_v2_usb_rx(uint8_t* buffer, size_t size); + +void dap_v2_usb_set_rx_callback(DapRxCallback callback); + +/************************************ CDC **************************************/ + +typedef void (*DapCDCControlLineCallback)(uint8_t state, void* context); +typedef void (*DapCDCConfigCallback)(struct usb_cdc_line_coding* config, void* context); + +int32_t dap_cdc_usb_tx(uint8_t* buffer, uint8_t size); + +size_t dap_cdc_usb_rx(uint8_t* buffer, size_t size); + +void dap_cdc_usb_set_rx_callback(DapRxCallback callback); + +void dap_cdc_usb_set_control_line_callback(DapCDCControlLineCallback callback); + +void dap_cdc_usb_set_config_callback(DapCDCConfigCallback callback); + +void dap_cdc_usb_set_context(void* context); + +/*********************************** Common ************************************/ + +void dap_common_usb_set_context(void* context); + +void dap_common_usb_set_state_callback(DapStateCallback callback); + +void dap_common_usb_alloc_name(const char* name); + +void dap_common_usb_free_name(); + +void dap_common_wait_for_deinit(); \ No newline at end of file diff --git a/applications/plugins/dap_link/usb/usb_winusb.h b/applications/plugins/dap_link/usb/usb_winusb.h new file mode 100644 index 00000000000..9c3a172dc20 --- /dev/null +++ b/applications/plugins/dap_link/usb/usb_winusb.h @@ -0,0 +1,143 @@ +#pragma once +#include + +/*- Definitions -------------------------------------------------------------*/ + +#define USB_PACK __attribute__((packed)) + +#define USB_WINUSB_VENDOR_CODE 0x20 + +#define USB_WINUSB_WINDOWS_VERSION 0x06030000 // Windows 8.1 + +#define USB_WINUSB_PLATFORM_CAPABILITY_ID \ + { \ + 0xdf, 0x60, 0xdd, 0xd8, 0x89, 0x45, 0xc7, 0x4c, 0x9c, 0xd2, 0x65, 0x9d, 0x9e, 0x64, 0x8a, \ + 0x9f \ + } + +enum // WinUSB Microsoft OS 2.0 descriptor request codes +{ + USB_WINUSB_DESCRIPTOR_INDEX = 0x07, + USB_WINUSB_SET_ALT_ENUMERATION = 0x08, +}; + +enum // wDescriptorType +{ + USB_WINUSB_SET_HEADER_DESCRIPTOR = 0x00, + USB_WINUSB_SUBSET_HEADER_CONFIGURATION = 0x01, + USB_WINUSB_SUBSET_HEADER_FUNCTION = 0x02, + USB_WINUSB_FEATURE_COMPATBLE_ID = 0x03, + USB_WINUSB_FEATURE_REG_PROPERTY = 0x04, + USB_WINUSB_FEATURE_MIN_RESUME_TIME = 0x05, + USB_WINUSB_FEATURE_MODEL_ID = 0x06, + USB_WINUSB_FEATURE_CCGP_DEVICE = 0x07, + USB_WINUSB_FEATURE_VENDOR_REVISION = 0x08, +}; + +enum // wPropertyDataType +{ + USB_WINUSB_PROPERTY_DATA_TYPE_SZ = 1, + USB_WINUSB_PROPERTY_DATA_TYPE_EXPAND_SZ = 2, + USB_WINUSB_PROPERTY_DATA_TYPE_BINARY = 3, + USB_WINUSB_PROPERTY_DATA_TYPE_DWORD_LITTLE_ENDIAN = 4, + USB_WINUSB_PROPERTY_DATA_TYPE_DWORD_BIG_ENDIAN = 5, + USB_WINUSB_PROPERTY_DATA_TYPE_LINK = 6, + USB_WINUSB_PROPERTY_DATA_TYPE_MULTI_SZ = 7, +}; + +/*- Types BOS -------------------------------------------------------------------*/ + +typedef struct USB_PACK { + uint8_t bLength; + uint8_t bDescriptorType; + uint16_t wTotalLength; + uint8_t bNumDeviceCaps; +} usb_binary_object_store_descriptor_t; + +/*- Types WinUSB -------------------------------------------------------------------*/ + +typedef struct USB_PACK { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDevCapabilityType; + uint8_t bReserved; + uint8_t PlatformCapabilityUUID[16]; + uint32_t dwWindowsVersion; + uint16_t wMSOSDescriptorSetTotalLength; + uint8_t bMS_VendorCode; + uint8_t bAltEnumCode; +} usb_winusb_capability_descriptor_t; + +typedef struct USB_PACK { + uint16_t wLength; + uint16_t wDescriptorType; + uint32_t dwWindowsVersion; + uint16_t wDescriptorSetTotalLength; +} usb_winusb_set_header_descriptor_t; + +typedef struct USB_PACK { + uint16_t wLength; + uint16_t wDescriptorType; + uint8_t bConfigurationValue; + uint8_t bReserved; + uint16_t wTotalLength; +} usb_winusb_subset_header_configuration_t; + +typedef struct USB_PACK { + uint16_t wLength; + uint16_t wDescriptorType; + uint8_t bFirstInterface; + uint8_t bReserved; + uint16_t wSubsetLength; +} usb_winusb_subset_header_function_t; + +typedef struct USB_PACK { + uint16_t wLength; + uint16_t wDescriptorType; + uint8_t CompatibleID[8]; + uint8_t SubCompatibleID[8]; +} usb_winusb_feature_compatble_id_t; + +typedef struct USB_PACK { + uint16_t wLength; + uint16_t wDescriptorType; + uint16_t wPropertyDataType; + //uint16_t wPropertyNameLength; + //uint8_t PropertyName[...]; + //uint16_t wPropertyDataLength + //uint8_t PropertyData[...]; +} usb_winusb_feature_reg_property_t; + +typedef struct USB_PACK { + uint16_t wLength; + uint16_t wDescriptorType; + uint16_t wPropertyDataType; + uint16_t wPropertyNameLength; + uint8_t PropertyName[42]; + uint16_t wPropertyDataLength; + uint8_t PropertyData[80]; +} usb_winusb_feature_reg_property_guids_t; + +typedef struct USB_PACK { + uint16_t wLength; + uint16_t wDescriptorType; + uint8_t bResumeRecoveryTime; + uint8_t bResumeSignalingTime; +} usb_winusb_feature_min_resume_time_t; + +typedef struct USB_PACK { + uint16_t wLength; + uint16_t wDescriptorType; + uint8_t ModelID[16]; +} usb_winusb_feature_model_id_t; + +typedef struct USB_PACK { + uint16_t wLength; + uint16_t wDescriptorType; +} usb_winusb_feature_ccgp_device_t; + +typedef struct USB_PACK { + uint16_t wLength; + uint16_t wDescriptorType; + uint16_t VendorRevision; +} usb_winusb_feature_vendor_revision_t; \ No newline at end of file From 33892ebfb790c46e7f63aa6dd33e83e9159b8946 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Fri, 21 Oct 2022 02:14:46 +0900 Subject: [PATCH 163/824] [FL-2818] FuriHal: add FuriHalCortexTimer, use it for i2c bus timeouts (#1900) * FuriHal: add FuriHalCortexTimer, use it for i2c bus timeouts * Furi: cleanup FuriHalCortexTimer sources and headers --- firmware/targets/f7/api_symbols.csv | 5 +++- .../targets/f7/furi_hal/furi_hal_cortex.c | 21 ++++++++++++-- firmware/targets/f7/furi_hal/furi_hal_i2c.c | 29 ++++++++++--------- .../furi_hal_include/furi_hal_cortex.h | 29 +++++++++++++++++++ 4 files changed, 67 insertions(+), 17 deletions(-) diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 6aa8591e7d1..89f447157d1 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,3.4,, +Version,+,3.5,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -985,6 +985,9 @@ Function,+,furi_hal_console_tx_with_new_line,void,"const uint8_t*, size_t" Function,+,furi_hal_cortex_delay_us,void,uint32_t Function,-,furi_hal_cortex_init_early,void, Function,+,furi_hal_cortex_instructions_per_microsecond,uint32_t, +Function,+,furi_hal_cortex_timer_get,FuriHalCortexTimer,uint32_t +Function,+,furi_hal_cortex_timer_is_expired,_Bool,FuriHalCortexTimer +Function,+,furi_hal_cortex_timer_wait,void,FuriHalCortexTimer Function,+,furi_hal_crypto_decrypt,_Bool,"const uint8_t*, uint8_t*, size_t" Function,+,furi_hal_crypto_encrypt,_Bool,"const uint8_t*, uint8_t*, size_t" Function,-,furi_hal_crypto_init,void, diff --git a/firmware/targets/f7/furi_hal/furi_hal_cortex.c b/firmware/targets/f7/furi_hal/furi_hal_cortex.c index c9c8400a748..c2abd1b85ee 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_cortex.c +++ b/firmware/targets/f7/furi_hal/furi_hal_cortex.c @@ -2,6 +2,8 @@ #include +#define FURI_HAL_CORTEX_INSTRUCTIONS_PER_MICROSECOND (SystemCoreClock / 1000000) + void furi_hal_cortex_init_early() { CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; @@ -13,11 +15,26 @@ void furi_hal_cortex_init_early() { void furi_hal_cortex_delay_us(uint32_t microseconds) { uint32_t start = DWT->CYCCNT; - uint32_t time_ticks = SystemCoreClock / 1000000 * microseconds; + uint32_t time_ticks = FURI_HAL_CORTEX_INSTRUCTIONS_PER_MICROSECOND * microseconds; while((DWT->CYCCNT - start) < time_ticks) { }; } uint32_t furi_hal_cortex_instructions_per_microsecond() { - return SystemCoreClock / 1000000; + return FURI_HAL_CORTEX_INSTRUCTIONS_PER_MICROSECOND; +} + +FuriHalCortexTimer furi_hal_cortex_timer_get(uint32_t timeout_us) { + FuriHalCortexTimer cortex_timer = {0}; + cortex_timer.start = DWT->CYCCNT; + cortex_timer.value = FURI_HAL_CORTEX_INSTRUCTIONS_PER_MICROSECOND * timeout_us; + return cortex_timer; } + +bool furi_hal_cortex_timer_is_expired(FuriHalCortexTimer cortex_timer) { + return !((DWT->CYCCNT - cortex_timer.start) < cortex_timer.value); +} + +void furi_hal_cortex_timer_wait(FuriHalCortexTimer cortex_timer) { + while(!furi_hal_cortex_timer_is_expired(cortex_timer)); +} \ No newline at end of file diff --git a/firmware/targets/f7/furi_hal/furi_hal_i2c.c b/firmware/targets/f7/furi_hal/furi_hal_i2c.c index 36f5230c217..6c17d6ade90 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_i2c.c +++ b/firmware/targets/f7/furi_hal/furi_hal_i2c.c @@ -1,6 +1,7 @@ #include #include #include +#include #include #include @@ -60,11 +61,11 @@ bool furi_hal_i2c_tx( furi_assert(timeout > 0); bool ret = true; - uint32_t timeout_tick = furi_get_tick() + timeout; + FuriHalCortexTimer timer = furi_hal_cortex_timer_get(timeout * 1000); do { while(LL_I2C_IsActiveFlag_BUSY(handle->bus->i2c)) { - if(furi_get_tick() >= timeout_tick) { + if(furi_hal_cortex_timer_is_expired(timer)) { ret = false; break; } @@ -89,7 +90,7 @@ bool furi_hal_i2c_tx( size--; } - if(furi_get_tick() >= timeout_tick) { + if(furi_hal_cortex_timer_is_expired(timer)) { ret = false; break; } @@ -111,11 +112,11 @@ bool furi_hal_i2c_rx( furi_assert(timeout > 0); bool ret = true; - uint32_t timeout_tick = furi_get_tick() + timeout; + FuriHalCortexTimer timer = furi_hal_cortex_timer_get(timeout * 1000); do { while(LL_I2C_IsActiveFlag_BUSY(handle->bus->i2c)) { - if(furi_get_tick() >= timeout_tick) { + if(furi_hal_cortex_timer_is_expired(timer)) { ret = false; break; } @@ -140,7 +141,7 @@ bool furi_hal_i2c_rx( size--; } - if(furi_get_tick() >= timeout_tick) { + if(furi_hal_cortex_timer_is_expired(timer)) { ret = false; break; } @@ -175,11 +176,11 @@ bool furi_hal_i2c_is_device_ready(FuriHalI2cBusHandle* handle, uint8_t i2c_addr, furi_assert(timeout > 0); bool ret = true; - uint32_t timeout_tick = furi_get_tick() + timeout; + FuriHalCortexTimer timer = furi_hal_cortex_timer_get(timeout * 1000); do { while(LL_I2C_IsActiveFlag_BUSY(handle->bus->i2c)) { - if(furi_get_tick() >= timeout_tick) { + if(furi_hal_cortex_timer_is_expired(timer)) { return false; } } @@ -190,14 +191,14 @@ bool furi_hal_i2c_is_device_ready(FuriHalI2cBusHandle* handle, uint8_t i2c_addr, while((!LL_I2C_IsActiveFlag_NACK(handle->bus->i2c)) && (!LL_I2C_IsActiveFlag_STOP(handle->bus->i2c))) { - if(furi_get_tick() >= timeout_tick) { + if(furi_hal_cortex_timer_is_expired(timer)) { return false; } } if(LL_I2C_IsActiveFlag_NACK(handle->bus->i2c)) { while(!LL_I2C_IsActiveFlag_STOP(handle->bus->i2c)) { - if(furi_get_tick() >= timeout_tick) { + if(furi_hal_cortex_timer_is_expired(timer)) { return false; } } @@ -214,7 +215,7 @@ bool furi_hal_i2c_is_device_ready(FuriHalI2cBusHandle* handle, uint8_t i2c_addr, } while(!LL_I2C_IsActiveFlag_STOP(handle->bus->i2c)) { - if(furi_get_tick() >= timeout_tick) { + if(furi_hal_cortex_timer_is_expired(timer)) { return false; } } @@ -308,11 +309,11 @@ bool furi_hal_i2c_write_mem( bool ret = true; uint8_t size = len + 1; - uint32_t timeout_tick = furi_get_tick() + timeout; + FuriHalCortexTimer timer = furi_hal_cortex_timer_get(timeout * 1000); do { while(LL_I2C_IsActiveFlag_BUSY(handle->bus->i2c)) { - if(furi_get_tick() >= timeout_tick) { + if(furi_hal_cortex_timer_is_expired(timer)) { ret = false; break; } @@ -341,7 +342,7 @@ bool furi_hal_i2c_write_mem( size--; } - if(furi_get_tick() >= timeout_tick) { + if(furi_hal_cortex_timer_is_expired(timer)) { ret = false; break; } diff --git a/firmware/targets/furi_hal_include/furi_hal_cortex.h b/firmware/targets/furi_hal_include/furi_hal_cortex.h index 13035161de7..91596ffe3f2 100644 --- a/firmware/targets/furi_hal_include/furi_hal_cortex.h +++ b/firmware/targets/furi_hal_include/furi_hal_cortex.h @@ -6,11 +6,18 @@ #pragma once #include +#include #ifdef __cplusplus extern "C" { #endif +/** Cortex timer provides high precision low level expiring timer */ +typedef struct { + uint32_t start; + uint32_t value; +} FuriHalCortexTimer; + /** Early init stage for cortex */ void furi_hal_cortex_init_early(); @@ -27,6 +34,28 @@ void furi_hal_cortex_delay_us(uint32_t microseconds); */ uint32_t furi_hal_cortex_instructions_per_microsecond(); +/** Get Timer + * + * @param[in] timeout_us The expire timeout in us + * + * @return The FuriHalCortexTimer + */ +FuriHalCortexTimer furi_hal_cortex_timer_get(uint32_t timeout_us); + +/** Check if timer expired + * + * @param[in] cortex_timer The FuriHalCortexTimer + * + * @return true if expired + */ +bool furi_hal_cortex_timer_is_expired(FuriHalCortexTimer cortex_timer); + +/** Wait for timer expire + * + * @param[in] cortex_timer The FuriHalCortexTimer + */ +void furi_hal_cortex_timer_wait(FuriHalCortexTimer cortex_timer); + #ifdef __cplusplus } #endif From c1bb10a6947f56e7cb71912b864b6d2b969f6155 Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Thu, 20 Oct 2022 21:26:28 +0400 Subject: [PATCH 164/824] [FL-2920] WS: add protocol Acurite-606TX, LaCrosse_TX141THBv2 (#1898) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * WS: add protocol Acurite-606TX * WS: history, added display of the channel (if any) in the general list * WS: added display of the button state if it is on the transmitter, and displaying the data that is in the signal * WS: fix batt info * WS: add protocol LaCrosse_TX141THBv2 * WS; fix syntax * Furi: bump api_symbols version Co-authored-by: あく --- .../helpers/weather_station_types.h | 2 +- .../weather_station/protocols/acurite_606tx.c | 252 +++++++++++++++ .../weather_station/protocols/acurite_606tx.h | 79 +++++ .../weather_station/protocols/infactory.c | 1 + .../protocols/lacrosse_tx141thbv2.c | 298 ++++++++++++++++++ .../protocols/lacrosse_tx141thbv2.h | 81 +++++ .../weather_station/protocols/nexus_th.c | 2 +- .../protocols/protocol_items.c | 2 + .../protocols/protocol_items.h | 2 + .../weather_station/protocols/ws_generic.c | 20 +- .../weather_station/protocols/ws_generic.h | 7 + .../views/weather_station_receiver_info.c | 55 ++-- .../weather_station/weather_station_history.c | 25 +- firmware/targets/f7/api_symbols.csv | 8 +- lib/subghz/blocks/math.c | 137 ++++++++ lib/subghz/blocks/math.h | 97 +++++- 16 files changed, 1017 insertions(+), 51 deletions(-) create mode 100644 applications/plugins/weather_station/protocols/acurite_606tx.c create mode 100644 applications/plugins/weather_station/protocols/acurite_606tx.h create mode 100644 applications/plugins/weather_station/protocols/lacrosse_tx141thbv2.c create mode 100644 applications/plugins/weather_station/protocols/lacrosse_tx141thbv2.h diff --git a/applications/plugins/weather_station/helpers/weather_station_types.h b/applications/plugins/weather_station/helpers/weather_station_types.h index a6905e828d5..275d2332937 100644 --- a/applications/plugins/weather_station/helpers/weather_station_types.h +++ b/applications/plugins/weather_station/helpers/weather_station_types.h @@ -3,7 +3,7 @@ #include #include -#define WS_VERSION_APP "0.1" +#define WS_VERSION_APP "0.2" #define WS_DEVELOPED "SkorP" #define WS_GITHUB "https://github.com/flipperdevices/flipperzero-firmware" diff --git a/applications/plugins/weather_station/protocols/acurite_606tx.c b/applications/plugins/weather_station/protocols/acurite_606tx.c new file mode 100644 index 00000000000..a92ec243c7c --- /dev/null +++ b/applications/plugins/weather_station/protocols/acurite_606tx.c @@ -0,0 +1,252 @@ +#include "acurite_606tx.h" + +#define TAG "WSProtocolAcurite_606TX" + +/* + * Help + * https://github.com/merbanan/rtl_433/blob/5bef4e43133ac4c0e2d18d36f87c52b4f9458453/src/devices/acurite.c#L1644 + * + * 0000 1111 | 0011 0000 | 0101 1100 | 1110 0111 + * iiii iiii | buuu tttt | tttt tttt | cccc cccc + * - i: identification; changes on battery switch + * - c: lfsr_digest8; + * - u: unknown; + * - b: battery low; flag to indicate low battery voltage + * - t: Temperature; in °C + * + */ + +static const SubGhzBlockConst ws_protocol_acurite_606tx_const = { + .te_short = 500, + .te_long = 2000, + .te_delta = 150, + .min_count_bit_for_found = 32, +}; + +struct WSProtocolDecoderAcurite_606TX { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + WSBlockGeneric generic; +}; + +struct WSProtocolEncoderAcurite_606TX { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + WSBlockGeneric generic; +}; + +typedef enum { + Acurite_606TXDecoderStepReset = 0, + Acurite_606TXDecoderStepSaveDuration, + Acurite_606TXDecoderStepCheckDuration, +} Acurite_606TXDecoderStep; + +const SubGhzProtocolDecoder ws_protocol_acurite_606tx_decoder = { + .alloc = ws_protocol_decoder_acurite_606tx_alloc, + .free = ws_protocol_decoder_acurite_606tx_free, + + .feed = ws_protocol_decoder_acurite_606tx_feed, + .reset = ws_protocol_decoder_acurite_606tx_reset, + + .get_hash_data = ws_protocol_decoder_acurite_606tx_get_hash_data, + .serialize = ws_protocol_decoder_acurite_606tx_serialize, + .deserialize = ws_protocol_decoder_acurite_606tx_deserialize, + .get_string = ws_protocol_decoder_acurite_606tx_get_string, +}; + +const SubGhzProtocolEncoder ws_protocol_acurite_606tx_encoder = { + .alloc = NULL, + .free = NULL, + + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol ws_protocol_acurite_606tx = { + .name = WS_PROTOCOL_ACURITE_606TX_NAME, + .type = SubGhzProtocolWeatherStation, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable, + + .decoder = &ws_protocol_acurite_606tx_decoder, + .encoder = &ws_protocol_acurite_606tx_encoder, +}; + +void* ws_protocol_decoder_acurite_606tx_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + WSProtocolDecoderAcurite_606TX* instance = malloc(sizeof(WSProtocolDecoderAcurite_606TX)); + instance->base.protocol = &ws_protocol_acurite_606tx; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void ws_protocol_decoder_acurite_606tx_free(void* context) { + furi_assert(context); + WSProtocolDecoderAcurite_606TX* instance = context; + free(instance); +} + +void ws_protocol_decoder_acurite_606tx_reset(void* context) { + furi_assert(context); + WSProtocolDecoderAcurite_606TX* instance = context; + instance->decoder.parser_step = Acurite_606TXDecoderStepReset; +} + +static bool ws_protocol_acurite_606tx_check(WSProtocolDecoderAcurite_606TX* instance) { + if(!instance->decoder.decode_data) return false; + uint8_t msg[] = { + instance->decoder.decode_data >> 24, + instance->decoder.decode_data >> 16, + instance->decoder.decode_data >> 8}; + + uint8_t crc = subghz_protocol_blocks_lfsr_digest8(msg, 3, 0x98, 0xF1); + return (crc == (instance->decoder.decode_data & 0xFF)); +} + +/** + * Analysis of received data + * @param instance Pointer to a WSBlockGeneric* instance + */ +static void ws_protocol_acurite_606tx_remote_controller(WSBlockGeneric* instance) { + instance->id = (instance->data >> 24) & 0xFF; + instance->battery_low = (instance->data >> 23) & 1; + + instance->channel = WS_NO_CHANNEL; + + if(!((instance->data >> 19) & 1)) { + instance->temp = (float)((instance->data >> 8) & 0x07FF) / 10.0f; + } else { + instance->temp = (float)((~(instance->data >> 8) & 0x07FF) + 1) / -10.0f; + } + instance->btn = WS_NO_BTN; + instance->humidity = WS_NO_HUMIDITY; +} + +void ws_protocol_decoder_acurite_606tx_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + WSProtocolDecoderAcurite_606TX* instance = context; + + switch(instance->decoder.parser_step) { + case Acurite_606TXDecoderStepReset: + if((!level) && (DURATION_DIFF(duration, ws_protocol_acurite_606tx_const.te_short * 17) < + ws_protocol_acurite_606tx_const.te_delta * 8)) { + //Found syncPrefix + instance->decoder.parser_step = Acurite_606TXDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } + break; + + case Acurite_606TXDecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = Acurite_606TXDecoderStepCheckDuration; + } else { + instance->decoder.parser_step = Acurite_606TXDecoderStepReset; + } + break; + + case Acurite_606TXDecoderStepCheckDuration: + if(!level) { + if((DURATION_DIFF(instance->decoder.te_last, ws_protocol_acurite_606tx_const.te_short) < + ws_protocol_acurite_606tx_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_acurite_606tx_const.te_short) < + ws_protocol_acurite_606tx_const.te_delta)) { + //Found syncPostfix + instance->decoder.parser_step = Acurite_606TXDecoderStepReset; + if((instance->decoder.decode_count_bit == + ws_protocol_acurite_606tx_const.min_count_bit_for_found) && + ws_protocol_acurite_606tx_check(instance)) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + ws_protocol_acurite_606tx_remote_controller(&instance->generic); + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + + break; + } else if( + (DURATION_DIFF( + instance->decoder.te_last, ws_protocol_acurite_606tx_const.te_short) < + ws_protocol_acurite_606tx_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_acurite_606tx_const.te_long) < + ws_protocol_acurite_606tx_const.te_delta * 2)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = Acurite_606TXDecoderStepSaveDuration; + } else if( + (DURATION_DIFF( + instance->decoder.te_last, ws_protocol_acurite_606tx_const.te_short) < + ws_protocol_acurite_606tx_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_acurite_606tx_const.te_long * 2) < + ws_protocol_acurite_606tx_const.te_delta * 4)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = Acurite_606TXDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = Acurite_606TXDecoderStepReset; + } + } else { + instance->decoder.parser_step = Acurite_606TXDecoderStepReset; + } + break; + } +} + +uint8_t ws_protocol_decoder_acurite_606tx_get_hash_data(void* context) { + furi_assert(context); + WSProtocolDecoderAcurite_606TX* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool ws_protocol_decoder_acurite_606tx_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + WSProtocolDecoderAcurite_606TX* instance = context; + return ws_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +bool ws_protocol_decoder_acurite_606tx_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + WSProtocolDecoderAcurite_606TX* instance = context; + bool ret = false; + do { + if(!ws_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if(instance->generic.data_count_bit != + ws_protocol_acurite_606tx_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + ret = true; + } while(false); + return ret; +} + +void ws_protocol_decoder_acurite_606tx_get_string(void* context, FuriString* output) { + furi_assert(context); + WSProtocolDecoderAcurite_606TX* instance = context; + furi_string_printf( + output, + "%s %dbit\r\n" + "Key:0x%lX%08lX\r\n" + "Sn:0x%lX Ch:%d Bat:%d\r\n" + "Temp:%d.%d C Hum:%d%%", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data >> 32), + (uint32_t)(instance->generic.data), + instance->generic.id, + instance->generic.channel, + instance->generic.battery_low, + (int16_t)instance->generic.temp, + abs(((int16_t)(instance->generic.temp * 10) - (((int16_t)instance->generic.temp) * 10))), + instance->generic.humidity); +} diff --git a/applications/plugins/weather_station/protocols/acurite_606tx.h b/applications/plugins/weather_station/protocols/acurite_606tx.h new file mode 100644 index 00000000000..5bab3bcb794 --- /dev/null +++ b/applications/plugins/weather_station/protocols/acurite_606tx.h @@ -0,0 +1,79 @@ +#pragma once + +#include + +#include +#include +#include +#include "ws_generic.h" +#include + +#define WS_PROTOCOL_ACURITE_606TX_NAME "Acurite-606TX" + +typedef struct WSProtocolDecoderAcurite_606TX WSProtocolDecoderAcurite_606TX; +typedef struct WSProtocolEncoderAcurite_606TX WSProtocolEncoderAcurite_606TX; + +extern const SubGhzProtocolDecoder ws_protocol_acurite_606tx_decoder; +extern const SubGhzProtocolEncoder ws_protocol_acurite_606tx_encoder; +extern const SubGhzProtocol ws_protocol_acurite_606tx; + +/** + * Allocate WSProtocolDecoderAcurite_606TX. + * @param environment Pointer to a SubGhzEnvironment instance + * @return WSProtocolDecoderAcurite_606TX* pointer to a WSProtocolDecoderAcurite_606TX instance + */ +void* ws_protocol_decoder_acurite_606tx_alloc(SubGhzEnvironment* environment); + +/** + * Free WSProtocolDecoderAcurite_606TX. + * @param context Pointer to a WSProtocolDecoderAcurite_606TX instance + */ +void ws_protocol_decoder_acurite_606tx_free(void* context); + +/** + * Reset decoder WSProtocolDecoderAcurite_606TX. + * @param context Pointer to a WSProtocolDecoderAcurite_606TX instance + */ +void ws_protocol_decoder_acurite_606tx_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a WSProtocolDecoderAcurite_606TX instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void ws_protocol_decoder_acurite_606tx_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a WSProtocolDecoderAcurite_606TX instance + * @return hash Hash sum + */ +uint8_t ws_protocol_decoder_acurite_606tx_get_hash_data(void* context); + +/** + * Serialize data WSProtocolDecoderAcurite_606TX. + * @param context Pointer to a WSProtocolDecoderAcurite_606TX instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool ws_protocol_decoder_acurite_606tx_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data WSProtocolDecoderAcurite_606TX. + * @param context Pointer to a WSProtocolDecoderAcurite_606TX instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool ws_protocol_decoder_acurite_606tx_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a WSProtocolDecoderAcurite_606TX instance + * @param output Resulting text + */ +void ws_protocol_decoder_acurite_606tx_get_string(void* context, FuriString* output); diff --git a/applications/plugins/weather_station/protocols/infactory.c b/applications/plugins/weather_station/protocols/infactory.c index 525b55575a1..b08a4e9dbee 100644 --- a/applications/plugins/weather_station/protocols/infactory.c +++ b/applications/plugins/weather_station/protocols/infactory.c @@ -142,6 +142,7 @@ static bool ws_protocol_infactory_check_crc(WSProtocolDecoderInfactory* instance static void ws_protocol_infactory_remote_controller(WSBlockGeneric* instance) { instance->id = instance->data >> 32; instance->battery_low = (instance->data >> 26) & 1; + instance->btn = WS_NO_BTN; instance->temp = ws_block_generic_fahrenheit_to_celsius( ((float)((instance->data >> 12) & 0x0FFF) - 900.0f) / 10.0f); instance->humidity = diff --git a/applications/plugins/weather_station/protocols/lacrosse_tx141thbv2.c b/applications/plugins/weather_station/protocols/lacrosse_tx141thbv2.c new file mode 100644 index 00000000000..828d49be78a --- /dev/null +++ b/applications/plugins/weather_station/protocols/lacrosse_tx141thbv2.c @@ -0,0 +1,298 @@ +#include "lacrosse_tx141thbv2.h" + +#define TAG "WSProtocolLaCrosse_TX141THBv2" + +/* + * Help + * https://github.com/merbanan/rtl_433/blob/7e83cfd27d14247b6c3c81732bfe4a4f9a974d30/src/devices/lacrosse_tx141x.c + * + * iiii iiii | bkcc tttt | tttt tttt | hhhh hhhh | cccc cccc | u + * - i: identification; changes on battery switch + * - c: lfsr_digest8_reflect; + * - u: unknown; + * - b: battery low; flag to indicate low battery voltage + * - h: Humidity; + * - t: Temperature; in °F as binary number with one decimal place + 50 °F offset + * - n: Channel; Channel number 1 - 3 + */ + +static const SubGhzBlockConst ws_protocol_lacrosse_tx141thbv2_const = { + .te_short = 250, + .te_long = 500, + .te_delta = 120, + .min_count_bit_for_found = 41, +}; + +struct WSProtocolDecoderLaCrosse_TX141THBv2 { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + WSBlockGeneric generic; + + uint16_t header_count; +}; + +struct WSProtocolEncoderLaCrosse_TX141THBv2 { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + WSBlockGeneric generic; +}; + +typedef enum { + LaCrosse_TX141THBv2DecoderStepReset = 0, + LaCrosse_TX141THBv2DecoderStepCheckPreambule, + LaCrosse_TX141THBv2DecoderStepSaveDuration, + LaCrosse_TX141THBv2DecoderStepCheckDuration, +} LaCrosse_TX141THBv2DecoderStep; + +const SubGhzProtocolDecoder ws_protocol_lacrosse_tx141thbv2_decoder = { + .alloc = ws_protocol_decoder_lacrosse_tx141thbv2_alloc, + .free = ws_protocol_decoder_lacrosse_tx141thbv2_free, + + .feed = ws_protocol_decoder_lacrosse_tx141thbv2_feed, + .reset = ws_protocol_decoder_lacrosse_tx141thbv2_reset, + + .get_hash_data = ws_protocol_decoder_lacrosse_tx141thbv2_get_hash_data, + .serialize = ws_protocol_decoder_lacrosse_tx141thbv2_serialize, + .deserialize = ws_protocol_decoder_lacrosse_tx141thbv2_deserialize, + .get_string = ws_protocol_decoder_lacrosse_tx141thbv2_get_string, +}; + +const SubGhzProtocolEncoder ws_protocol_lacrosse_tx141thbv2_encoder = { + .alloc = NULL, + .free = NULL, + + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol ws_protocol_lacrosse_tx141thbv2 = { + .name = WS_PROTOCOL_LACROSSE_TX141THBV2_NAME, + .type = SubGhzProtocolWeatherStation, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable, + + .decoder = &ws_protocol_lacrosse_tx141thbv2_decoder, + .encoder = &ws_protocol_lacrosse_tx141thbv2_encoder, +}; + +void* ws_protocol_decoder_lacrosse_tx141thbv2_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + WSProtocolDecoderLaCrosse_TX141THBv2* instance = + malloc(sizeof(WSProtocolDecoderLaCrosse_TX141THBv2)); + instance->base.protocol = &ws_protocol_lacrosse_tx141thbv2; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void ws_protocol_decoder_lacrosse_tx141thbv2_free(void* context) { + furi_assert(context); + WSProtocolDecoderLaCrosse_TX141THBv2* instance = context; + free(instance); +} + +void ws_protocol_decoder_lacrosse_tx141thbv2_reset(void* context) { + furi_assert(context); + WSProtocolDecoderLaCrosse_TX141THBv2* instance = context; + instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepReset; +} + +static bool + ws_protocol_lacrosse_tx141thbv2_check_crc(WSProtocolDecoderLaCrosse_TX141THBv2* instance) { + if(!instance->decoder.decode_data) return false; + uint8_t msg[] = { + instance->decoder.decode_data >> 33, + instance->decoder.decode_data >> 25, + instance->decoder.decode_data >> 17, + instance->decoder.decode_data >> 9}; + + uint8_t crc = subghz_protocol_blocks_lfsr_digest8_reflect(msg, 4, 0x31, 0xF4); + return (crc == ((instance->decoder.decode_data >> 1) & 0xFF)); +} + +/** + * Analysis of received data + * @param instance Pointer to a WSBlockGeneric* instance + */ +static void ws_protocol_lacrosse_tx141thbv2_remote_controller(WSBlockGeneric* instance) { + instance->id = instance->data >> 33; + instance->battery_low = (instance->data >> 32) & 1; + instance->btn = (instance->data >> 31) & 1; + instance->channel = ((instance->data >> 29) & 0x03) + 1; + instance->temp = ((float)((instance->data >> 17) & 0x0FFF) - 500.0f) / 10.0f; + instance->humidity = (instance->data >> 9) & 0xFF; +} + +void ws_protocol_decoder_lacrosse_tx141thbv2_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + WSProtocolDecoderLaCrosse_TX141THBv2* instance = context; + + switch(instance->decoder.parser_step) { + case LaCrosse_TX141THBv2DecoderStepReset: + if((level) && + (DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_short * 3) < + ws_protocol_lacrosse_tx141thbv2_const.te_delta * 2)) { + instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepCheckPreambule; + instance->decoder.te_last = duration; + instance->header_count = 0; + } + break; + + case LaCrosse_TX141THBv2DecoderStepCheckPreambule: + if(level) { + instance->decoder.te_last = duration; + } else { + if((DURATION_DIFF( + instance->decoder.te_last, + ws_protocol_lacrosse_tx141thbv2_const.te_short * 3) < + ws_protocol_lacrosse_tx141thbv2_const.te_delta * 2) && + (DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_short * 3) < + ws_protocol_lacrosse_tx141thbv2_const.te_delta * 2)) { + //Found preambule + instance->header_count++; + } else if(instance->header_count == 4) { + if((DURATION_DIFF( + instance->decoder.te_last, + ws_protocol_lacrosse_tx141thbv2_const.te_short) < + ws_protocol_lacrosse_tx141thbv2_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_long) < + ws_protocol_lacrosse_tx141thbv2_const.te_delta)) { + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepSaveDuration; + } else if( + (DURATION_DIFF( + instance->decoder.te_last, + ws_protocol_lacrosse_tx141thbv2_const.te_long) < + ws_protocol_lacrosse_tx141thbv2_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_short) < + ws_protocol_lacrosse_tx141thbv2_const.te_delta)) { + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepSaveDuration; + } else { + instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepReset; + } + } else { + instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepReset; + } + } + break; + + case LaCrosse_TX141THBv2DecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepCheckDuration; + } else { + instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepReset; + } + break; + + case LaCrosse_TX141THBv2DecoderStepCheckDuration: + if(!level) { + if(((DURATION_DIFF( + instance->decoder.te_last, + ws_protocol_lacrosse_tx141thbv2_const.te_short * 3) < + ws_protocol_lacrosse_tx141thbv2_const.te_delta * 2) && + (DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_short * 3) < + ws_protocol_lacrosse_tx141thbv2_const.te_delta * 2))) { + if((instance->decoder.decode_count_bit == + ws_protocol_lacrosse_tx141thbv2_const.min_count_bit_for_found) && + ws_protocol_lacrosse_tx141thbv2_check_crc(instance)) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + ws_protocol_lacrosse_tx141thbv2_remote_controller(&instance->generic); + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->header_count = 1; + instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepCheckPreambule; + break; + } else if( + (DURATION_DIFF( + instance->decoder.te_last, ws_protocol_lacrosse_tx141thbv2_const.te_short) < + ws_protocol_lacrosse_tx141thbv2_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_long) < + ws_protocol_lacrosse_tx141thbv2_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepSaveDuration; + } else if( + (DURATION_DIFF( + instance->decoder.te_last, ws_protocol_lacrosse_tx141thbv2_const.te_long) < + ws_protocol_lacrosse_tx141thbv2_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_short) < + ws_protocol_lacrosse_tx141thbv2_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepSaveDuration; + } else { + instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepReset; + } + } else { + instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepReset; + } + break; + } +} + +uint8_t ws_protocol_decoder_lacrosse_tx141thbv2_get_hash_data(void* context) { + furi_assert(context); + WSProtocolDecoderLaCrosse_TX141THBv2* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool ws_protocol_decoder_lacrosse_tx141thbv2_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + WSProtocolDecoderLaCrosse_TX141THBv2* instance = context; + return ws_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +bool ws_protocol_decoder_lacrosse_tx141thbv2_deserialize( + void* context, + FlipperFormat* flipper_format) { + furi_assert(context); + WSProtocolDecoderLaCrosse_TX141THBv2* instance = context; + bool ret = false; + do { + if(!ws_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if(instance->generic.data_count_bit != + ws_protocol_lacrosse_tx141thbv2_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + ret = true; + } while(false); + return ret; +} + +void ws_protocol_decoder_lacrosse_tx141thbv2_get_string(void* context, FuriString* output) { + furi_assert(context); + WSProtocolDecoderLaCrosse_TX141THBv2* instance = context; + furi_string_printf( + output, + "%s %dbit\r\n" + "Key:0x%lX%08lX\r\n" + "Sn:0x%lX Ch:%d Bat:%d\r\n" + "Temp:%d.%d C Hum:%d%%", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data >> 32), + (uint32_t)(instance->generic.data), + instance->generic.id, + instance->generic.channel, + instance->generic.battery_low, + (int16_t)instance->generic.temp, + abs(((int16_t)(instance->generic.temp * 10) - (((int16_t)instance->generic.temp) * 10))), + instance->generic.humidity); +} diff --git a/applications/plugins/weather_station/protocols/lacrosse_tx141thbv2.h b/applications/plugins/weather_station/protocols/lacrosse_tx141thbv2.h new file mode 100644 index 00000000000..941b0105814 --- /dev/null +++ b/applications/plugins/weather_station/protocols/lacrosse_tx141thbv2.h @@ -0,0 +1,81 @@ +#pragma once + +#include + +#include +#include +#include +#include "ws_generic.h" +#include + +#define WS_PROTOCOL_LACROSSE_TX141THBV2_NAME "TX141THBv2" + +typedef struct WSProtocolDecoderLaCrosse_TX141THBv2 WSProtocolDecoderLaCrosse_TX141THBv2; +typedef struct WSProtocolEncoderLaCrosse_TX141THBv2 WSProtocolEncoderLaCrosse_TX141THBv2; + +extern const SubGhzProtocolDecoder ws_protocol_lacrosse_tx141thbv2_decoder; +extern const SubGhzProtocolEncoder ws_protocol_lacrosse_tx141thbv2_encoder; +extern const SubGhzProtocol ws_protocol_lacrosse_tx141thbv2; + +/** + * Allocate WSProtocolDecoderLaCrosse_TX141THBv2. + * @param environment Pointer to a SubGhzEnvironment instance + * @return WSProtocolDecoderLaCrosse_TX141THBv2* pointer to a WSProtocolDecoderLaCrosse_TX141THBv2 instance + */ +void* ws_protocol_decoder_lacrosse_tx141thbv2_alloc(SubGhzEnvironment* environment); + +/** + * Free WSProtocolDecoderLaCrosse_TX141THBv2. + * @param context Pointer to a WSProtocolDecoderLaCrosse_TX141THBv2 instance + */ +void ws_protocol_decoder_lacrosse_tx141thbv2_free(void* context); + +/** + * Reset decoder WSProtocolDecoderLaCrosse_TX141THBv2. + * @param context Pointer to a WSProtocolDecoderLaCrosse_TX141THBv2 instance + */ +void ws_protocol_decoder_lacrosse_tx141thbv2_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a WSProtocolDecoderLaCrosse_TX141THBv2 instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void ws_protocol_decoder_lacrosse_tx141thbv2_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a WSProtocolDecoderLaCrosse_TX141THBv2 instance + * @return hash Hash sum + */ +uint8_t ws_protocol_decoder_lacrosse_tx141thbv2_get_hash_data(void* context); + +/** + * Serialize data WSProtocolDecoderLaCrosse_TX141THBv2. + * @param context Pointer to a WSProtocolDecoderLaCrosse_TX141THBv2 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool ws_protocol_decoder_lacrosse_tx141thbv2_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data WSProtocolDecoderLaCrosse_TX141THBv2. + * @param context Pointer to a WSProtocolDecoderLaCrosse_TX141THBv2 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool ws_protocol_decoder_lacrosse_tx141thbv2_deserialize( + void* context, + FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a WSProtocolDecoderLaCrosse_TX141THBv2 instance + * @param output Resulting text + */ +void ws_protocol_decoder_lacrosse_tx141thbv2_get_string(void* context, FuriString* output); diff --git a/applications/plugins/weather_station/protocols/nexus_th.c b/applications/plugins/weather_station/protocols/nexus_th.c index e40f3348592..a2ab0f412b4 100644 --- a/applications/plugins/weather_station/protocols/nexus_th.c +++ b/applications/plugins/weather_station/protocols/nexus_th.c @@ -127,7 +127,7 @@ static void ws_protocol_nexus_th_remote_controller(WSBlockGeneric* instance) { instance->id = (instance->data >> 28) & 0xFF; instance->battery_low = !((instance->data >> 27) & 1); instance->channel = ((instance->data >> 24) & 0x03) + 1; - + instance->btn = WS_NO_BTN; if(!((instance->data >> 23) & 1)) { instance->temp = (float)((instance->data >> 12) & 0x07FF) / 10.0f; } else { diff --git a/applications/plugins/weather_station/protocols/protocol_items.c b/applications/plugins/weather_station/protocols/protocol_items.c index ea02ec058ae..5b753994e95 100644 --- a/applications/plugins/weather_station/protocols/protocol_items.c +++ b/applications/plugins/weather_station/protocols/protocol_items.c @@ -5,6 +5,8 @@ const SubGhzProtocol* weather_station_protocol_registry_items[] = { &ws_protocol_thermopro_tx4, &ws_protocol_nexus_th, &ws_protocol_gt_wt_03, + &ws_protocol_acurite_606tx, + &ws_protocol_lacrosse_tx141thbv2, }; const SubGhzProtocolRegistry weather_station_protocol_registry = { diff --git a/applications/plugins/weather_station/protocols/protocol_items.h b/applications/plugins/weather_station/protocols/protocol_items.h index eac28a23af4..c4f4ecd9a19 100644 --- a/applications/plugins/weather_station/protocols/protocol_items.h +++ b/applications/plugins/weather_station/protocols/protocol_items.h @@ -5,5 +5,7 @@ #include "thermopro_tx4.h" #include "nexus_th.h" #include "gt_wt_03.h" +#include "acurite_606tx.h" +#include "lacrosse_tx141thbv2.h" extern const SubGhzProtocolRegistry weather_station_protocol_registry; diff --git a/applications/plugins/weather_station/protocols/ws_generic.c b/applications/plugins/weather_station/protocols/ws_generic.c index 5740750a2e0..17453109012 100644 --- a/applications/plugins/weather_station/protocols/ws_generic.c +++ b/applications/plugins/weather_station/protocols/ws_generic.c @@ -105,11 +105,11 @@ bool ws_block_generic_serialize( break; } - // temp_data = instance->btn; - // if(!flipper_format_write_uint32(flipper_format, "Btn", &temp_data, 1)) { - // FURI_LOG_E(TAG, "Unable to add Btn"); - // break; - // } + temp_data = instance->btn; + if(!flipper_format_write_uint32(flipper_format, "Btn", &temp_data, 1)) { + FURI_LOG_E(TAG, "Unable to add Btn"); + break; + } float temp = instance->temp; if(!flipper_format_write_float(flipper_format, "Temp", &temp, 1)) { @@ -174,11 +174,11 @@ bool ws_block_generic_deserialize(WSBlockGeneric* instance, FlipperFormat* flipp } instance->channel = (uint8_t)temp_data; - // if(!flipper_format_read_uint32(flipper_format, "Btn", (uint32_t*)&temp_data, 1)) { - // FURI_LOG_E(TAG, "Missing Btn"); - // break; - // } - // instance->btn = (uint8_t)temp_data; + if(!flipper_format_read_uint32(flipper_format, "Btn", (uint32_t*)&temp_data, 1)) { + FURI_LOG_E(TAG, "Missing Btn"); + break; + } + instance->btn = (uint8_t)temp_data; float temp; if(!flipper_format_read_float(flipper_format, "Temp", (float*)&temp, 1)) { diff --git a/applications/plugins/weather_station/protocols/ws_generic.h b/applications/plugins/weather_station/protocols/ws_generic.h index e0402b8181f..b2a84df8e67 100644 --- a/applications/plugins/weather_station/protocols/ws_generic.h +++ b/applications/plugins/weather_station/protocols/ws_generic.h @@ -13,6 +13,13 @@ extern "C" { #endif +#define WS_NO_ID 0xFFFFFFFF +#define WS_NO_BATT 0xFF +#define WS_NO_HUMIDITY 0xFF +#define WS_NO_CHANNEL 0xFF +#define WS_NO_BTN 0xFF +#define WS_NO_TEMPERATURE -273.0f + typedef struct WSBlockGeneric WSBlockGeneric; struct WSBlockGeneric { diff --git a/applications/plugins/weather_station/views/weather_station_receiver_info.c b/applications/plugins/weather_station/views/weather_station_receiver_info.c index 3187062767a..34ec122d135 100644 --- a/applications/plugins/weather_station/views/weather_station_receiver_info.c +++ b/applications/plugins/weather_station/views/weather_station_receiver_info.c @@ -4,7 +4,6 @@ #include "../protocols/ws_generic.h" #include #include -#include "math.h" #define abs(x) ((x) > 0 ? (x) : -(x)) @@ -47,14 +46,26 @@ void ws_view_receiver_info_draw(Canvas* canvas, WSReceiverInfoModel* model) { model->generic->data_count_bit); canvas_draw_str(canvas, 5, 8, buffer); - snprintf(buffer, sizeof(buffer), "Ch: %01d", model->generic->channel); - canvas_draw_str(canvas, 105, 8, buffer); + if(model->generic->channel != WS_NO_CHANNEL) { + snprintf(buffer, sizeof(buffer), "Ch: %01d", model->generic->channel); + canvas_draw_str(canvas, 105, 8, buffer); + } + + if(model->generic->id != WS_NO_ID) { + snprintf(buffer, sizeof(buffer), "Sn: 0x%02lX", model->generic->id); + canvas_draw_str(canvas, 5, 20, buffer); + } - snprintf(buffer, sizeof(buffer), "Sn: 0x%02lX", model->generic->id); - canvas_draw_str(canvas, 5, 20, buffer); + if(model->generic->btn != WS_NO_BTN) { + snprintf(buffer, sizeof(buffer), "Btn: %01d", model->generic->btn); + canvas_draw_str(canvas, 62, 20, buffer); + } - snprintf(buffer, sizeof(buffer), "Batt: %s", (!model->generic->battery_low ? "ok" : "low")); - canvas_draw_str(canvas, 85, 20, buffer); + if(model->generic->battery_low != WS_NO_BATT) { + snprintf( + buffer, sizeof(buffer), "Batt: %s", (!model->generic->battery_low ? "ok" : "low")); + canvas_draw_str(canvas, 90, 20, buffer); + } snprintf(buffer, sizeof(buffer), "Data: 0x%llX", model->generic->data); canvas_draw_str(canvas, 5, 32, buffer); @@ -62,19 +73,23 @@ void ws_view_receiver_info_draw(Canvas* canvas, WSReceiverInfoModel* model) { elements_bold_rounded_frame(canvas, 2, 37, 123, 25); canvas_set_font(canvas, FontPrimary); - canvas_draw_icon(canvas, 13 + 5, 42, &I_Therm_7x16); - snprintf( - buffer, - sizeof(buffer), - "%3.2d.%d C", - (int16_t)model->generic->temp, - abs(((int16_t)(model->generic->temp * 10) - (((int16_t)model->generic->temp) * 10)))); - canvas_draw_str_aligned(canvas, 58 + 5, 46, AlignRight, AlignTop, buffer); - canvas_draw_circle(canvas, 50 + 5, 45, 1); - - canvas_draw_icon(canvas, 70 + 5, 42, &I_Humid_10x15); - snprintf(buffer, sizeof(buffer), "%d%%", model->generic->humidity); - canvas_draw_str(canvas, 86 + 5, 54, buffer); + if(model->generic->temp != WS_NO_TEMPERATURE) { + canvas_draw_icon(canvas, 18, 42, &I_Therm_7x16); + snprintf( + buffer, + sizeof(buffer), + "%3.2d.%d C", + (int16_t)model->generic->temp, + abs(((int16_t)(model->generic->temp * 10) - (((int16_t)model->generic->temp) * 10)))); + canvas_draw_str_aligned(canvas, 63, 46, AlignRight, AlignTop, buffer); + canvas_draw_circle(canvas, 55, 45, 1); + } + + if(model->generic->humidity != WS_NO_HUMIDITY) { + canvas_draw_icon(canvas, 75, 42, &I_Humid_10x15); + snprintf(buffer, sizeof(buffer), "%d%%", model->generic->humidity); + canvas_draw_str(canvas, 91, 54, buffer); + } } bool ws_view_receiver_info_input(InputEvent* event, void* context) { diff --git a/applications/plugins/weather_station/weather_station_history.c b/applications/plugins/weather_station/weather_station_history.c index 2eddf6cdccc..b37009c4658 100644 --- a/applications/plugins/weather_station/weather_station_history.c +++ b/applications/plugins/weather_station/weather_station_history.c @@ -2,6 +2,7 @@ #include #include #include +#include "protocols/ws_generic.h" #include @@ -224,20 +225,18 @@ WSHistoryStateAddKey for(uint8_t i = 0; i < sizeof(uint64_t); i++) { data = (data << 8) | key_data[i]; } - if(!(uint32_t)(data >> 32)) { - furi_string_printf( - item->item_str, - "%s %lX", - furi_string_get_cstr(instance->tmp_string), - (uint32_t)(data & 0xFFFFFFFF)); - } else { - furi_string_printf( - item->item_str, - "%s %lX%08lX", - furi_string_get_cstr(instance->tmp_string), - (uint32_t)(data >> 32), - (uint32_t)(data & 0xFFFFFFFF)); + uint32_t temp_data = 0; + if(!flipper_format_read_uint32(item->flipper_string, "Ch", (uint32_t*)&temp_data, 1)) { + FURI_LOG_E(TAG, "Missing Channel"); + break; + } + if(temp_data != WS_NO_CHANNEL) { + furi_string_cat_printf(instance->tmp_string, " Ch:%X", (uint8_t)temp_data); } + + furi_string_printf( + item->item_str, "%s %llX", furi_string_get_cstr(instance->tmp_string), data); + } while(false); instance->last_index_write++; return WSHistoryStateAddKeyNewDada; diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 89f447157d1..d0dfcbfad76 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,3.5,, +Version,+,3.6,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -2271,13 +2271,19 @@ Function,-,subghz_keystore_raw_encrypted_save,_Bool,"const char*, const char*, u Function,-,subghz_keystore_raw_get_data,_Bool,"const char*, size_t, uint8_t*, size_t" Function,-,subghz_keystore_save,_Bool,"SubGhzKeystore*, const char*, uint8_t*" Function,+,subghz_protocol_blocks_add_bit,void,"SubGhzBlockDecoder*, uint8_t" +Function,+,subghz_protocol_blocks_crc16,uint16_t,"const uint8_t[], unsigned, uint16_t, uint16_t" +Function,+,subghz_protocol_blocks_crc16lsb,uint16_t,"const uint8_t[], unsigned, uint16_t, uint16_t" Function,+,subghz_protocol_blocks_crc4,uint8_t,"const uint8_t[], unsigned, uint8_t, uint8_t" Function,+,subghz_protocol_blocks_crc7,uint8_t,"const uint8_t[], unsigned, uint8_t, uint8_t" Function,+,subghz_protocol_blocks_crc8,uint8_t,"const uint8_t[], unsigned, uint8_t, uint8_t" +Function,+,subghz_protocol_blocks_crc8le,uint8_t,"const uint8_t[], unsigned, uint8_t, uint8_t" Function,+,subghz_protocol_blocks_get_bit_array,_Bool,"uint8_t[], size_t" Function,+,subghz_protocol_blocks_get_hash_data,uint8_t,"SubGhzBlockDecoder*, size_t" Function,+,subghz_protocol_blocks_get_parity,uint8_t,"uint64_t, uint8_t" Function,+,subghz_protocol_blocks_get_upload,size_t,"uint8_t[], size_t, LevelDuration*, size_t, uint32_t" +Function,+,subghz_protocol_blocks_lfsr_digest16,uint16_t,"const uint8_t[], unsigned, uint16_t, uint16_t" +Function,+,subghz_protocol_blocks_lfsr_digest8,uint8_t,"const uint8_t[], unsigned, uint8_t, uint8_t" +Function,+,subghz_protocol_blocks_lfsr_digest8_reflect,uint8_t,"const uint8_t[], int, uint8_t, uint8_t" Function,+,subghz_protocol_blocks_reverse_key,uint64_t,"uint64_t, uint8_t" Function,+,subghz_protocol_blocks_set_bit_array,void,"_Bool, uint8_t[], size_t, size_t" Function,-,subghz_protocol_decoder_base_deserialize,_Bool,"SubGhzProtocolDecoderBase*, FlipperFormat*" diff --git a/lib/subghz/blocks/math.c b/lib/subghz/blocks/math.c index 782076e3e48..588d4effc04 100644 --- a/lib/subghz/blocks/math.c +++ b/lib/subghz/blocks/math.c @@ -79,4 +79,141 @@ uint8_t subghz_protocol_blocks_crc8( } } return remainder; +} + +uint8_t subghz_protocol_blocks_crc8le( + uint8_t const message[], + unsigned nBytes, + uint8_t polynomial, + uint8_t init) { + uint8_t remainder = subghz_protocol_blocks_reverse_key(init, 8); + unsigned byte, bit; + polynomial = subghz_protocol_blocks_reverse_key(polynomial, 8); + + for(byte = 0; byte < nBytes; ++byte) { + remainder ^= message[byte]; + for(bit = 0; bit < 8; ++bit) { + if(remainder & 1) { + remainder = (remainder >> 1) ^ polynomial; + } else { + remainder = (remainder >> 1); + } + } + } + return remainder; +} + +uint16_t subghz_protocol_blocks_crc16lsb( + uint8_t const message[], + unsigned nBytes, + uint16_t polynomial, + uint16_t init) { + uint16_t remainder = init; + unsigned byte, bit; + + for(byte = 0; byte < nBytes; ++byte) { + remainder ^= message[byte]; + for(bit = 0; bit < 8; ++bit) { + if(remainder & 1) { + remainder = (remainder >> 1) ^ polynomial; + } else { + remainder = (remainder >> 1); + } + } + } + return remainder; +} + +uint16_t subghz_protocol_blocks_crc16( + uint8_t const message[], + unsigned nBytes, + uint16_t polynomial, + uint16_t init) { + uint16_t remainder = init; + unsigned byte, bit; + + for(byte = 0; byte < nBytes; ++byte) { + remainder ^= message[byte] << 8; + for(bit = 0; bit < 8; ++bit) { + if(remainder & 0x8000) { + remainder = (remainder << 1) ^ polynomial; + } else { + remainder = (remainder << 1); + } + } + } + return remainder; +} + +uint8_t subghz_protocol_blocks_lfsr_digest8( + uint8_t const message[], + unsigned bytes, + uint8_t gen, + uint8_t key) { + uint8_t sum = 0; + for(unsigned k = 0; k < bytes; ++k) { + uint8_t data = message[k]; + for(int i = 7; i >= 0; --i) { + // XOR key into sum if data bit is set + if((data >> i) & 1) sum ^= key; + + // roll the key right (actually the lsb is dropped here) + // and apply the gen (needs to include the dropped lsb as msb) + if(key & 1) + key = (key >> 1) ^ gen; + else + key = (key >> 1); + } + } + return sum; +} + +uint8_t subghz_protocol_blocks_lfsr_digest8_reflect( + uint8_t const message[], + int bytes, + uint8_t gen, + uint8_t key) { + uint8_t sum = 0; + // Process message from last byte to first byte (reflected) + for(int k = bytes - 1; k >= 0; --k) { + uint8_t data = message[k]; + // Process individual bits of each byte (reflected) + for(int i = 0; i < 8; ++i) { + // XOR key into sum if data bit is set + if((data >> i) & 1) { + sum ^= key; + } + + // roll the key left (actually the lsb is dropped here) + // and apply the gen (needs to include the dropped lsb as msb) + if(key & 0x80) + key = (key << 1) ^ gen; + else + key = (key << 1); + } + } + return sum; +} + +uint16_t subghz_protocol_blocks_lfsr_digest16( + uint8_t const message[], + unsigned bytes, + uint16_t gen, + uint16_t key) { + uint16_t sum = 0; + for(unsigned k = 0; k < bytes; ++k) { + uint8_t data = message[k]; + for(int i = 7; i >= 0; --i) { + // if data bit is set then xor with key + if((data >> i) & 1) sum ^= key; + + // roll the key right (actually the lsb is dropped here) + // and apply the gen (needs to include the dropped lsb as msb) + if(key & 1) + key = (key >> 1) ^ gen; + else + key = (key >> 1); + } + } + return sum; } \ No newline at end of file diff --git a/lib/subghz/blocks/math.h b/lib/subghz/blocks/math.h index c312b607ae5..275889484db 100644 --- a/lib/subghz/blocks/math.h +++ b/lib/subghz/blocks/math.h @@ -19,7 +19,7 @@ extern "C" { * @param key In data * @param count_bit number of data bits * @return Reverse data - */ + **/ uint64_t subghz_protocol_blocks_reverse_key(uint64_t key, uint8_t count_bit); /** @@ -27,7 +27,7 @@ uint64_t subghz_protocol_blocks_reverse_key(uint64_t key, uint8_t count_bit); * @param key In data * @param count_bit number of data bits * @return parity - */ + **/ uint8_t subghz_protocol_blocks_get_parity(uint64_t key, uint8_t count_bit); /** @@ -37,7 +37,7 @@ uint8_t subghz_protocol_blocks_get_parity(uint64_t key, uint8_t count_bit); * @param polynomial CRC polynomial * @param init starting crc value * @return CRC value - */ + **/ uint8_t subghz_protocol_blocks_crc4( uint8_t const message[], unsigned nBytes, @@ -51,7 +51,7 @@ uint8_t subghz_protocol_blocks_crc4( * @param polynomial CRC polynomial * @param init starting crc value * @return CRC value - */ + **/ uint8_t subghz_protocol_blocks_crc7( uint8_t const message[], unsigned nBytes, @@ -67,13 +67,100 @@ uint8_t subghz_protocol_blocks_crc7( * @param polynomial byte is from x^7 to x^0 (x^8 is implicitly one) * @param init starting crc value * @return CRC value - */ + **/ uint8_t subghz_protocol_blocks_crc8( uint8_t const message[], unsigned nBytes, uint8_t polynomial, uint8_t init); +/** + * "Little-endian" Cyclic Redundancy Check CRC-8 LE + * Input and output are reflected, i.e. least significant bit is shifted in first. + * @param message array of bytes to check + * @param nBytes number of bytes in message + * @param polynomial CRC polynomial + * @param init starting crc value + * @return CRC value + **/ +uint8_t subghz_protocol_blocks_crc8le( + uint8_t const message[], + unsigned nBytes, + uint8_t polynomial, + uint8_t init); + +/** + * CRC-16 LSB. + * Input and output are reflected, i.e. least significant bit is shifted in first. + * Note that poly and init already need to be reflected. + * @param message array of bytes to check + * @param nBytes number of bytes in message + * @param polynomial CRC polynomial + * @param init starting crc value + * @return CRC value + **/ +uint16_t subghz_protocol_blocks_crc16lsb( + uint8_t const message[], + unsigned nBytes, + uint16_t polynomial, + uint16_t init); + +/** + * CRC-16. + * @param message array of bytes to check + * @param nBytes number of bytes in message + * @param polynomial CRC polynomial + * @param init starting crc value + * @return CRC value + **/ +uint16_t subghz_protocol_blocks_crc16( + uint8_t const message[], + unsigned nBytes, + uint16_t polynomial, + uint16_t init); + +/** + * Digest-8 by "LFSR-based Toeplitz hash". + * @param message bytes of message data + * @param bytes number of bytes to digest + * @param gen key stream generator, needs to includes the MSB if the LFSR is rolling + * @param key initial key + * @return digest value + **/ +uint8_t subghz_protocol_blocks_lfsr_digest8( + uint8_t const message[], + unsigned bytes, + uint8_t gen, + uint8_t key); + +/** + * Digest-8 by "LFSR-based Toeplitz hash", byte reflect, bit reflect. + * @param message bytes of message data + * @param bytes number of bytes to digest + * @param gen key stream generator, needs to includes the MSB if the LFSR is rolling + * @param key initial key + * @return digest value + **/ +uint8_t subghz_protocol_blocks_lfsr_digest8_reflect( + uint8_t const message[], + int bytes, + uint8_t gen, + uint8_t key); + +/** + * Digest-16 by "LFSR-based Toeplitz hash". + * @param message bytes of message data + * @param bytes number of bytes to digest + * @param gen key stream generator, needs to includes the MSB if the LFSR is rolling + * @param key initial key + * @return digest value + **/ +uint16_t subghz_protocol_blocks_lfsr_digest16( + uint8_t const message[], + unsigned bytes, + uint16_t gen, + uint16_t key); + #ifdef __cplusplus } #endif From f8af0c1509245e1bce3d9651a138db8490d69c33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Sun, 23 Oct 2022 00:21:10 +0900 Subject: [PATCH 165/824] [FL-2892] Gui: update statusbar attention icon and better crash handling (#1908) * Gui: update statusbar attention icon * Furi: snapshot registers on crash and restore in halt * Furi: document check routines --- applications/services/gui/gui.c | 4 +- assets/icons/StatusBar/Alert_9x8.png | Bin 0 -> 3611 bytes assets/icons/StatusBar/Hidden_window_9x8.png | Bin 0 -> 3604 bytes firmware/targets/f7/api_symbols.csv | 9 ++-- furi/core/check.c | 45 ++++++++++++----- furi/core/check.h | 49 ++++++++++++++----- 6 files changed, 78 insertions(+), 29 deletions(-) create mode 100644 assets/icons/StatusBar/Alert_9x8.png create mode 100644 assets/icons/StatusBar/Hidden_window_9x8.png diff --git a/applications/services/gui/gui.c b/applications/services/gui/gui.c index 0535b32b812..8c5ed91a958 100644 --- a/applications/services/gui/gui.c +++ b/applications/services/gui/gui.c @@ -152,7 +152,7 @@ static void gui_redraw_status_bar(Gui* gui, bool need_attention) { } // Extra notification if(need_attention) { - width = icon_get_width(&I_Attention_5x8); + width = icon_get_width(&I_Hidden_window_9x8); // Prepare work area background canvas_frame_set( gui->canvas, @@ -166,7 +166,7 @@ static void gui_redraw_status_bar(Gui* gui, bool need_attention) { // Draw Icon canvas_frame_set( gui->canvas, x, GUI_STATUS_BAR_Y + 2, width, GUI_STATUS_BAR_WORKAREA_HEIGHT); - canvas_draw_icon(gui->canvas, 0, 0, &I_Attention_5x8); + canvas_draw_icon(gui->canvas, 0, 0, &I_Hidden_window_9x8); // Recalculate next position left_used += (width + 2); x += (width + 2); diff --git a/assets/icons/StatusBar/Alert_9x8.png b/assets/icons/StatusBar/Alert_9x8.png new file mode 100644 index 0000000000000000000000000000000000000000..d03f107ef1e957fdc15c896c2f65d52ec691f9b3 GIT binary patch literal 3611 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 T?I80e8v&Rb+u+JDu3`TM+ZHm< literal 0 HcmV?d00001 diff --git a/assets/icons/StatusBar/Hidden_window_9x8.png b/assets/icons/StatusBar/Hidden_window_9x8.png new file mode 100644 index 0000000000000000000000000000000000000000..d6fc2b326d03d5b6a3dbabb1d5ed3a8a0140d35f GIT binary patch literal 3604 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 M%FN!h6ytIA-_)%w8UO$Q literal 0 HcmV?d00001 diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index d0dfcbfad76..188cd74846b 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,3.6,, +Version,+,4.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -260,6 +260,8 @@ Function,-,__eprintf,void,"const char*, const char*, unsigned int, const char*" Function,+,__errno,int*, Function,-,__fpclassifyd,int,double Function,-,__fpclassifyf,int,float +Function,+,__furi_crash,void, +Function,+,__furi_halt,void, Function,-,__getdelim,ssize_t,"char**, size_t*, int, FILE*" Function,-,__getline,ssize_t,"char**, size_t*, FILE*" Function,-,__isinfd,int,double @@ -891,7 +893,6 @@ Function,-,ftello,off_t,FILE* Function,-,ftrylockfile,int,FILE* Function,-,funlockfile,void,FILE* Function,-,funopen,FILE*,"const void*, int (*)(void*, char*, int), int (*)(void*, const char*, int), fpos_t (*)(void*, fpos_t, int), int (*)(void*)" -Function,+,furi_crash,void,const char* Function,+,furi_delay_ms,void,uint32_t Function,+,furi_delay_tick,void,uint32_t Function,+,furi_delay_until_tick,FuriStatus,uint32_t @@ -1308,7 +1309,6 @@ Function,+,furi_hal_version_uid,const uint8_t*, Function,+,furi_hal_version_uid_size,size_t, Function,-,furi_hal_vibro_init,void, Function,+,furi_hal_vibro_on,void,_Bool -Function,+,furi_halt,void,const char* Function,-,furi_init,void, Function,+,furi_kernel_get_tick_frequency,uint32_t, Function,+,furi_kernel_lock,int32_t, @@ -2674,6 +2674,7 @@ Variable,+,A_iButton_14,const Icon, Variable,-,ITM_RxBuffer,volatile int32_t, Variable,+,I_125_10px,const Icon, Variable,+,I_ActiveConnection_50x64,const Icon, +Variable,+,I_Alert_9x8,const Icon, Variable,+,I_ArrowC_1_36x36,const Icon, Variable,+,I_ArrowDownEmpty_14x15,const Icon, Variable,+,I_ArrowDownFilled_14x15,const Icon, @@ -2741,6 +2742,7 @@ Variable,+,I_HeatHi_25x27,const Icon, Variable,+,I_HeatHi_hvr_25x27,const Icon, Variable,+,I_HeatLo_25x27,const Icon, Variable,+,I_HeatLo_hvr_25x27,const Icon, +Variable,+,I_Hidden_window_9x8,const Icon, Variable,+,I_InfraredArrowDown_4x8,const Icon, Variable,+,I_InfraredArrowUp_4x8,const Icon, Variable,+,I_InfraredLearnShort_128x31,const Icon, @@ -2841,6 +2843,7 @@ Variable,+,I_update_10px,const Icon, Variable,-,MSIRangeTable,const uint32_t[16], Variable,-,SmpsPrescalerTable,const uint32_t[4][6], Variable,+,SystemCoreClock,uint32_t, +Variable,+,__furi_check_message,const char*, Variable,+,_ctype_,const char[], Variable,+,_global_impure_ptr,_reent*, Variable,+,_impure_ptr,_reent*, diff --git a/furi/core/check.c b/furi/core/check.c index 613b52f4666..ed38038a53f 100644 --- a/furi/core/check.c +++ b/furi/core/check.c @@ -11,6 +11,9 @@ #include #include +PLACE_IN_SECTION("MB_MEM2") const char* __furi_check_message = NULL; +PLACE_IN_SECTION("MB_MEM2") uint32_t __furi_check_registers[12] = {0}; + extern size_t xPortGetTotalHeapSize(void); extern size_t xPortGetFreeHeapSize(void); extern size_t xPortGetMinimumEverFreeHeapSize(void); @@ -52,8 +55,10 @@ static void __furi_print_name(bool isr) { } } -static FURI_NORETURN void __furi_halt() { +static FURI_NORETURN void __furi_halt_mcu() { + register const void* r12 asm ("r12") = (void*)__furi_check_registers; asm volatile( + "ldm r12, {r0-r11} \n" #ifdef FURI_DEBUG "bkpt 0x00 \n" #endif @@ -61,22 +66,29 @@ static FURI_NORETURN void __furi_halt() { "wfi \n" "b loop%= \n" : - : + : "r" (r12) : "memory"); __builtin_unreachable(); } -FURI_NORETURN void furi_crash(const char* message) { +FURI_NORETURN void __furi_crash() { + register const void* r12 asm ("r12") = (void*)__furi_check_registers; + asm volatile( + "stm r12, {r0-r11} \n" + : + : "r" (r12) + : "memory"); + bool isr = FURI_IS_ISR(); __disable_irq(); - if(message == NULL) { - message = "Fatal Error"; + if(__furi_check_message == NULL) { + __furi_check_message = "Fatal Error"; } furi_hal_console_puts("\r\n\033[0;31m[CRASH]"); __furi_print_name(isr); - furi_hal_console_puts(message); + furi_hal_console_puts(__furi_check_message); if(!isr) { __furi_print_stack_info(); @@ -86,9 +98,9 @@ FURI_NORETURN void furi_crash(const char* message) { #ifdef FURI_DEBUG furi_hal_console_puts("\r\nSystem halted. Connect debugger for more info\r\n"); furi_hal_console_puts("\033[0m\r\n"); - __furi_halt(); + __furi_halt_mcu(); #else - furi_hal_rtc_set_fault_data((uint32_t)message); + furi_hal_rtc_set_fault_data((uint32_t)__furi_check_message); furi_hal_console_puts("\r\nRebooting system.\r\n"); furi_hal_console_puts("\033[0m\r\n"); furi_hal_power_reset(); @@ -96,18 +108,25 @@ FURI_NORETURN void furi_crash(const char* message) { __builtin_unreachable(); } -FURI_NORETURN void furi_halt(const char* message) { +FURI_NORETURN void __furi_halt() { + register const void* r12 asm ("r12") = (void*)__furi_check_registers; + asm volatile( + "stm r12, {r0-r11} \n" + : + : "r" (r12) + : "memory"); + bool isr = FURI_IS_ISR(); __disable_irq(); - if(message == NULL) { - message = "System halt requested."; + if(__furi_check_message == NULL) { + __furi_check_message = "System halt requested."; } furi_hal_console_puts("\r\n\033[0;31m[HALT]"); __furi_print_name(isr); - furi_hal_console_puts(message); + furi_hal_console_puts(__furi_check_message); furi_hal_console_puts("\r\nSystem halted. Bye-bye!\r\n"); furi_hal_console_puts("\033[0m\r\n"); - __furi_halt(); + __furi_halt_mcu(); } diff --git a/furi/core/check.h b/furi/core/check.h index 30efdf01068..e77891f7074 100644 --- a/furi/core/check.h +++ b/furi/core/check.h @@ -8,25 +8,52 @@ extern "C" { #define FURI_NORETURN noreturn #endif +/** Pointer to pass message to __furi_crash and __furi_halt */ +extern const char* __furi_check_message; + +/** Crash system */ +FURI_NORETURN void __furi_crash(); + +/** Halt system */ +FURI_NORETURN void __furi_halt(); + +/** Crash system with message. Show message after reboot. */ +#define furi_crash(message) \ + do { \ + __furi_check_message = message; \ + __furi_crash(); \ + } while(0) + +/** Halt system with message. */ +#define furi_halt(message) \ + do { \ + __furi_check_message = message; \ + __furi_halt(); \ + } while(0) + /** Check condition and crash if check failed */ -#define furi_check(__e) ((__e) ? (void)0 : furi_crash("furi_check failed\r\n")) +#define furi_check(__e) \ + do { \ + if ((__e) == 0) { \ + furi_crash("furi_check failed\r\n"); \ + } \ + } while(0) /** Only in debug build: Assert condition and crash if assert failed */ #ifdef FURI_DEBUG -#define furi_assert(__e) ((__e) ? (void)0 : furi_crash("furi_assert failed\r\n")) +#define furi_assert(__e) \ + do { \ + if ((__e) == 0) { \ + furi_crash("furi_assert failed\r\n"); \ + } \ + } while(0) #else -#define furi_assert(__e) \ - do { \ - ((void)(__e)); \ +#define furi_assert(__e) \ + do { \ + ((void)(__e)); \ } while(0) #endif -/** Crash system */ -FURI_NORETURN void furi_crash(const char* message); - -/** Halt system */ -FURI_NORETURN void furi_halt(const char* message); - #ifdef __cplusplus } #endif From d8fbaba7a0e67ec6c9462aa96a97ac194152da64 Mon Sep 17 00:00:00 2001 From: Max Lapan Date: Sat, 22 Oct 2022 17:50:26 +0200 Subject: [PATCH 166/824] Move Oregon2 to Weather Station FAP (#1910) * Init copy of oregon2 to weather station app * WS decoder * Reuse decoded data * Delete old protocol * Delete oregon2 unit test * Decrement count of random test Co-authored-by: Aleksandr Kutuzov --- .../debug/unit_tests/subghz/subghz_test.c | 10 +- .../weather_station}/protocols/oregon2.c | 165 +++++++++++------- .../weather_station/protocols/oregon2.h | 6 + .../protocols/protocol_items.c | 1 + .../protocols/protocol_items.h | 1 + assets/unit_tests/subghz/oregon2_raw.sub | 20 --- assets/unit_tests/subghz/test_random_raw.sub | 15 -- lib/subghz/protocols/oregon2.h | 5 - lib/subghz/protocols/protocol_items.c | 3 +- lib/subghz/protocols/protocol_items.h | 1 - 10 files changed, 109 insertions(+), 118 deletions(-) rename {lib/subghz => applications/plugins/weather_station}/protocols/oregon2.c (66%) create mode 100644 applications/plugins/weather_station/protocols/oregon2.h delete mode 100644 assets/unit_tests/subghz/oregon2_raw.sub delete mode 100644 lib/subghz/protocols/oregon2.h diff --git a/applications/debug/unit_tests/subghz/subghz_test.c b/applications/debug/unit_tests/subghz/subghz_test.c index 2dbf2eeda1e..dbe03d88c33 100644 --- a/applications/debug/unit_tests/subghz/subghz_test.c +++ b/applications/debug/unit_tests/subghz/subghz_test.c @@ -13,7 +13,7 @@ #define CAME_ATOMO_DIR_NAME EXT_PATH("subghz/assets/came_atomo") #define NICE_FLOR_S_DIR_NAME EXT_PATH("subghz/assets/nice_flor_s") #define TEST_RANDOM_DIR_NAME EXT_PATH("unit_tests/subghz/test_random_raw.sub") -#define TEST_RANDOM_COUNT_PARSE 233 +#define TEST_RANDOM_COUNT_PARSE 232 #define TEST_TIMEOUT 10000 static SubGhzEnvironment* environment_handler; @@ -437,13 +437,6 @@ MU_TEST(subghz_decoder_clemsa_test) { "Test decoder " SUBGHZ_PROTOCOL_CLEMSA_NAME " error\r\n"); } -MU_TEST(subghz_decoder_oregon2_test) { - mu_assert( - subghz_decoder_test( - EXT_PATH("unit_tests/subghz/oregon2_raw.sub"), SUBGHZ_PROTOCOL_OREGON2_NAME), - "Test decoder " SUBGHZ_PROTOCOL_OREGON2_NAME " error\r\n"); -} - //test encoders MU_TEST(subghz_encoder_princeton_test) { mu_assert( @@ -605,7 +598,6 @@ MU_TEST_SUITE(subghz) { MU_RUN_TEST(subghz_decoder_magellan_test); MU_RUN_TEST(subghz_decoder_intertechno_v3_test); MU_RUN_TEST(subghz_decoder_clemsa_test); - MU_RUN_TEST(subghz_decoder_oregon2_test); MU_RUN_TEST(subghz_encoder_princeton_test); MU_RUN_TEST(subghz_encoder_came_test); diff --git a/lib/subghz/protocols/oregon2.c b/applications/plugins/weather_station/protocols/oregon2.c similarity index 66% rename from lib/subghz/protocols/oregon2.c rename to applications/plugins/weather_station/protocols/oregon2.c index 244ac58d834..c4e4471a048 100644 --- a/lib/subghz/protocols/oregon2.c +++ b/applications/plugins/weather_station/protocols/oregon2.c @@ -1,14 +1,17 @@ #include "oregon2.h" -#include "../blocks/const.h" -#include "../blocks/decoder.h" -#include "../blocks/generic.h" -#include "../blocks/math.h" + +#include +#include +#include +#include +#include "ws_generic.h" + #include #include -#define TAG "SubGhzProtocolOregon2" +#define TAG "WSProtocolOregon2" -static const SubGhzBlockConst oregon2_const = { +static const SubGhzBlockConst ws_oregon2_const = { .te_long = 1000, .te_short = 500, .te_delta = 200, @@ -26,11 +29,11 @@ static const SubGhzBlockConst oregon2_const = { // bit indicating the low battery #define OREGON2_FLAG_BAT_LOW 0x4 -struct SubGhzProtocolDecoderOregon2 { +struct WSProtocolDecoderOregon2 { SubGhzProtocolDecoderBase base; SubGhzBlockDecoder decoder; - SubGhzBlockGeneric generic; + WSBlockGeneric generic; ManchesterState manchester_state; bool prev_bit; bool have_bit; @@ -39,7 +42,7 @@ struct SubGhzProtocolDecoderOregon2 { uint32_t var_data; }; -typedef struct SubGhzProtocolDecoderOregon2 SubGhzProtocolDecoderOregon2; +typedef struct WSProtocolDecoderOregon2 WSProtocolDecoderOregon2; typedef enum { Oregon2DecoderStepReset = 0, @@ -47,23 +50,29 @@ typedef enum { Oregon2DecoderStepVarData, } Oregon2DecoderStep; -void* subghz_protocol_decoder_oregon2_alloc(SubGhzEnvironment* environment) { +void* ws_protocol_decoder_oregon2_alloc(SubGhzEnvironment* environment) { UNUSED(environment); - SubGhzProtocolDecoderOregon2* instance = malloc(sizeof(SubGhzProtocolDecoderOregon2)); - instance->base.protocol = &subghz_protocol_oregon2; + WSProtocolDecoderOregon2* instance = malloc(sizeof(WSProtocolDecoderOregon2)); + instance->base.protocol = &ws_protocol_oregon2; instance->generic.protocol_name = instance->base.protocol->name; + instance->generic.humidity = WS_NO_HUMIDITY; + instance->generic.temp = WS_NO_TEMPERATURE; + instance->generic.btn = WS_NO_BTN; + instance->generic.channel = WS_NO_CHANNEL; + instance->generic.battery_low = WS_NO_BATT; + instance->generic.id = WS_NO_ID; return instance; } -void subghz_protocol_decoder_oregon2_free(void* context) { +void ws_protocol_decoder_oregon2_free(void* context) { furi_assert(context); - SubGhzProtocolDecoderOregon2* instance = context; + WSProtocolDecoderOregon2* instance = context; free(instance); } -void subghz_protocol_decoder_oregon2_reset(void* context) { +void ws_protocol_decoder_oregon2_reset(void* context) { furi_assert(context); - SubGhzProtocolDecoderOregon2* instance = context; + WSProtocolDecoderOregon2* instance = context; instance->decoder.parser_step = Oregon2DecoderStepReset; instance->decoder.decode_data = 0UL; instance->decoder.decode_count_bit = 0; @@ -77,9 +86,9 @@ void subghz_protocol_decoder_oregon2_reset(void* context) { static ManchesterEvent level_and_duration_to_event(bool level, uint32_t duration) { bool is_long = false; - if(DURATION_DIFF(duration, oregon2_const.te_long) < oregon2_const.te_delta) { + if(DURATION_DIFF(duration, ws_oregon2_const.te_long) < ws_oregon2_const.te_delta) { is_long = true; - } else if(DURATION_DIFF(duration, oregon2_const.te_short) < oregon2_const.te_delta) { + } else if(DURATION_DIFF(duration, ws_oregon2_const.te_short) < ws_oregon2_const.te_delta) { is_long = false; } else { return ManchesterEventReset; @@ -97,9 +106,36 @@ static uint8_t oregon2_sensor_id_var_bits(uint16_t sensor_id) { return 0; } -void subghz_protocol_decoder_oregon2_feed(void* context, bool level, uint32_t duration) { +static void ws_oregon2_decode_const_data(WSBlockGeneric* ws_block) { + ws_block->id = OREGON2_SENSOR_ID(ws_block->data); + + uint8_t ch_bits = (ws_block->data >> 12) & 0xF; + ws_block->channel = 1; + while(ch_bits > 1) { + ws_block->channel++; + ch_bits >>= 1; + } + + ws_block->battery_low = (ws_block->data & OREGON2_FLAG_BAT_LOW) ? 1 : 0; +} + +static void + ws_oregon2_decode_var_data(WSBlockGeneric* ws_block, uint16_t sensor_id, uint32_t var_data) { + int16_t temp_val; + if(sensor_id == 0xEC40) { + temp_val = ((var_data >> 4) & 0xF) * 10 + ((var_data >> 8) & 0xF); + temp_val *= 10; + temp_val += (var_data >> 12) & 0xF; + if(var_data & 0xF) temp_val = -temp_val; + } else + return; + + ws_block->temp = (float)temp_val / 10.0; +} + +void ws_protocol_decoder_oregon2_feed(void* context, bool level, uint32_t duration) { furi_assert(context); - SubGhzProtocolDecoderOregon2* instance = context; + WSProtocolDecoderOregon2* instance = context; // oregon v2.1 signal is inverted ManchesterEvent event = level_and_duration_to_event(!level, duration); bool data; @@ -118,7 +154,7 @@ void subghz_protocol_decoder_oregon2_feed(void* context, bool level, uint32_t du } else if(instance->prev_bit && !data) { subghz_protocol_blocks_add_bit(&instance->decoder, 0); } else { - subghz_protocol_decoder_oregon2_reset(context); + ws_protocol_decoder_oregon2_reset(context); } instance->have_bit = false; } else { @@ -151,6 +187,7 @@ void subghz_protocol_decoder_oregon2_feed(void* context, bool level, uint32_t du instance->generic.data = (instance->generic.data & 0x33333333) << 2 | (instance->generic.data & 0xCCCCCCCC) >> 2; + ws_oregon2_decode_const_data(&instance->generic); instance->var_bits = oregon2_sensor_id_var_bits(OREGON2_SENSOR_ID(instance->generic.data)); @@ -175,6 +212,11 @@ void subghz_protocol_decoder_oregon2_feed(void* context, bool level, uint32_t du instance->var_data = (instance->var_data & 0x33333333) << 2 | (instance->var_data & 0xCCCCCCCC) >> 2; + ws_oregon2_decode_var_data( + &instance->generic, + OREGON2_SENSOR_ID(instance->generic.data), + instance->var_data >> OREGON2_CHECKSUM_BITS); + instance->decoder.parser_step = Oregon2DecoderStepReset; if(instance->base.callback) instance->base.callback(&instance->base, instance->base.context); @@ -183,20 +225,20 @@ void subghz_protocol_decoder_oregon2_feed(void* context, bool level, uint32_t du } } -uint8_t subghz_protocol_decoder_oregon2_get_hash_data(void* context) { +uint8_t ws_protocol_decoder_oregon2_get_hash_data(void* context) { furi_assert(context); - SubGhzProtocolDecoderOregon2* instance = context; + WSProtocolDecoderOregon2* instance = context; return subghz_protocol_blocks_get_hash_data( &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } -bool subghz_protocol_decoder_oregon2_serialize( +bool ws_protocol_decoder_oregon2_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset) { furi_assert(context); - SubGhzProtocolDecoderOregon2* instance = context; - if(!subghz_block_generic_serialize(&instance->generic, flipper_format, preset)) return false; + WSProtocolDecoderOregon2* instance = context; + if(!ws_block_generic_serialize(&instance->generic, flipper_format, preset)) return false; uint32_t temp = instance->var_bits; if(!flipper_format_write_uint32(flipper_format, "VarBits", &temp, 1)) { FURI_LOG_E(TAG, "Error adding VarBits"); @@ -213,13 +255,13 @@ bool subghz_protocol_decoder_oregon2_serialize( return true; } -bool subghz_protocol_decoder_oregon2_deserialize(void* context, FlipperFormat* flipper_format) { +bool ws_protocol_decoder_oregon2_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); - SubGhzProtocolDecoderOregon2* instance = context; + WSProtocolDecoderOregon2* instance = context; bool ret = false; uint32_t temp_data; do { - if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + if(!ws_block_generic_deserialize(&instance->generic, flipper_format)) { break; } if(!flipper_format_read_uint32(flipper_format, "VarBits", &temp_data, 1)) { @@ -235,7 +277,7 @@ bool subghz_protocol_decoder_oregon2_deserialize(void* context, FlipperFormat* f FURI_LOG_E(TAG, "Missing VarData"); break; } - if(instance->generic.data_count_bit != oregon2_const.min_count_bit_for_found) { + if(instance->generic.data_count_bit != ws_oregon2_const.min_count_bit_for_found) { FURI_LOG_E(TAG, "Wrong number of bits in key: %d", instance->generic.data_count_bit); break; } @@ -244,22 +286,6 @@ bool subghz_protocol_decoder_oregon2_deserialize(void* context, FlipperFormat* f return ret; } -// append string of the variable data -static void - oregon2_var_data_append_string(uint16_t sensor_id, uint32_t var_data, FuriString* output) { - uint32_t val; - - if(sensor_id == 0xEC40) { - val = ((var_data >> 4) & 0xF) * 10 + ((var_data >> 8) & 0xF); - furi_string_cat_printf( - output, - "Temp: %s%ld.%ld C\r\n", - (var_data & 0xF) ? "-" : "+", - val, - (uint32_t)(var_data >> 12) & 0xF); - } -} - static void oregon2_append_check_sum(uint32_t fix_data, uint32_t var_data, FuriString* output) { uint8_t sum = fix_data & 0xF; uint8_t ref_sum = var_data & 0xFF; @@ -279,45 +305,50 @@ static void oregon2_append_check_sum(uint32_t fix_data, uint32_t var_data, FuriS furi_string_cat_printf(output, "Sum err: 0x%hhX vs 0x%hhX", ref_sum, sum); } -void subghz_protocol_decoder_oregon2_get_string(void* context, FuriString* output) { +void ws_protocol_decoder_oregon2_get_string(void* context, FuriString* output) { furi_assert(context); - SubGhzProtocolDecoderOregon2* instance = context; - uint16_t sensor_id = OREGON2_SENSOR_ID(instance->generic.data); + WSProtocolDecoderOregon2* instance = context; furi_string_cat_printf( output, "%s\r\n" - "ID: 0x%04lX, ch: %ld%s, rc: 0x%02lX\r\n", + "ID: 0x%04lX, ch: %d, bat: %d, rc: 0x%02lX\r\n", instance->generic.protocol_name, - (uint32_t)sensor_id, - (uint32_t)(instance->generic.data >> 12) & 0xF, - ((instance->generic.data & OREGON2_FLAG_BAT_LOW) ? ", low bat" : ""), + instance->generic.id, + instance->generic.channel, + instance->generic.battery_low, (uint32_t)(instance->generic.data >> 4) & 0xFF); if(instance->var_bits > 0) { - oregon2_var_data_append_string( - sensor_id, instance->var_data >> OREGON2_CHECKSUM_BITS, output); + furi_string_cat_printf( + output, + "Temp:%d.%d C Hum:%d%%", + (int16_t)instance->generic.temp, + abs( + ((int16_t)(instance->generic.temp * 10) - + (((int16_t)instance->generic.temp) * 10))), + instance->generic.humidity); oregon2_append_check_sum((uint32_t)instance->generic.data, instance->var_data, output); } } -const SubGhzProtocolDecoder subghz_protocol_oregon2_decoder = { - .alloc = subghz_protocol_decoder_oregon2_alloc, - .free = subghz_protocol_decoder_oregon2_free, +const SubGhzProtocolDecoder ws_protocol_oregon2_decoder = { + .alloc = ws_protocol_decoder_oregon2_alloc, + .free = ws_protocol_decoder_oregon2_free, - .feed = subghz_protocol_decoder_oregon2_feed, - .reset = subghz_protocol_decoder_oregon2_reset, + .feed = ws_protocol_decoder_oregon2_feed, + .reset = ws_protocol_decoder_oregon2_reset, - .get_hash_data = subghz_protocol_decoder_oregon2_get_hash_data, - .serialize = subghz_protocol_decoder_oregon2_serialize, - .deserialize = subghz_protocol_decoder_oregon2_deserialize, - .get_string = subghz_protocol_decoder_oregon2_get_string, + .get_hash_data = ws_protocol_decoder_oregon2_get_hash_data, + .serialize = ws_protocol_decoder_oregon2_serialize, + .deserialize = ws_protocol_decoder_oregon2_deserialize, + .get_string = ws_protocol_decoder_oregon2_get_string, }; -const SubGhzProtocol subghz_protocol_oregon2 = { - .name = SUBGHZ_PROTOCOL_OREGON2_NAME, +const SubGhzProtocol ws_protocol_oregon2 = { + .name = WS_PROTOCOL_OREGON2_NAME, .type = SubGhzProtocolTypeStatic, .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save, - .decoder = &subghz_protocol_oregon2_decoder, + .decoder = &ws_protocol_oregon2_decoder, }; diff --git a/applications/plugins/weather_station/protocols/oregon2.h b/applications/plugins/weather_station/protocols/oregon2.h new file mode 100644 index 00000000000..cfe938e6d83 --- /dev/null +++ b/applications/plugins/weather_station/protocols/oregon2.h @@ -0,0 +1,6 @@ +#pragma once + +#include + +#define WS_PROTOCOL_OREGON2_NAME "Oregon2" +extern const SubGhzProtocol ws_protocol_oregon2; diff --git a/applications/plugins/weather_station/protocols/protocol_items.c b/applications/plugins/weather_station/protocols/protocol_items.c index 5b753994e95..0740691a285 100644 --- a/applications/plugins/weather_station/protocols/protocol_items.c +++ b/applications/plugins/weather_station/protocols/protocol_items.c @@ -7,6 +7,7 @@ const SubGhzProtocol* weather_station_protocol_registry_items[] = { &ws_protocol_gt_wt_03, &ws_protocol_acurite_606tx, &ws_protocol_lacrosse_tx141thbv2, + &ws_protocol_oregon2, }; const SubGhzProtocolRegistry weather_station_protocol_registry = { diff --git a/applications/plugins/weather_station/protocols/protocol_items.h b/applications/plugins/weather_station/protocols/protocol_items.h index c4f4ecd9a19..edc078cd62f 100644 --- a/applications/plugins/weather_station/protocols/protocol_items.h +++ b/applications/plugins/weather_station/protocols/protocol_items.h @@ -7,5 +7,6 @@ #include "gt_wt_03.h" #include "acurite_606tx.h" #include "lacrosse_tx141thbv2.h" +#include "oregon2.h" extern const SubGhzProtocolRegistry weather_station_protocol_registry; diff --git a/assets/unit_tests/subghz/oregon2_raw.sub b/assets/unit_tests/subghz/oregon2_raw.sub deleted file mode 100644 index 549a60db348..00000000000 --- a/assets/unit_tests/subghz/oregon2_raw.sub +++ /dev/null @@ -1,20 +0,0 @@ -Filetype: Flipper SubGhz RAW File -Version: 1 -Frequency: 433920000 -Preset: FuriHalSubGhzPresetOok270Async -Protocol: RAW -RAW_Data: 889 -130 325 -64 457 -560 165 -68 199 -170 67 -66 265 -132 133 -666 67 -166 431 -66 201 -98 297 -100 595 -66 199 -134 65 -100 795 -132 99 -168 501 -200 331 -132 265 -102 265 -134 423 -98 521 -226 65 -166 431 -134 99 -100 133 -464 195 -326 623 -100 673 -98 321 -200 65 -136 369 -166 65 -68 97 -166 165 -334 265 -102 231 -166 101 -170 65 -170 265 -136 931 -100 133 -134 563 -66 333 -100 427 -66 163 -390 231 -66 193 -130 461 -166 557 -100 99 -198 263 -100 197 -294 231 -232 299 -134 199 -170 267 -134 631 -98 235 -100 499 -68 463 -100 65 -134 335 -170 273 -134 297 -100 67 -66 197 -166 67 -134 301 -168 537 -470 99 -134 433 -132 199 -192 261 -100 523 -164 459 -132 259 -332 359 -64 227 -96 131 -132 687 -132 363 -136 329 -434 99 -334 133 -100 401 -132 233 -700 233 -170 337 -66 371 -68 233 -202 531 -266 731 -66 465 -100 167 -100 133 -232 335 -166 239 -102 367 -232 231 -100 167 -134 201 -136 301 -168 199 -300 231 -98 237 -134 233 -102 329 -132 261 -134 199 -66 265 -136 99 -170 167 -134 199 -166 167 -136 367 -298 197 -200 99 -166 469 -136 439 -66 303 -134 295 -100 433 -134 899 -266 363 -132 197 -160 555 -324 129 -96 97 -128 257 -132 97 -394 257 -98 195 -166 459 -332 395 -132 633 -134 301 -100 131 -332 169 -168 395 -166 263 -540 783 -100 287 -130 295 -96 225 -296 133 -98 99 -100 461 -164 545 -130 99 -66 301 -68 265 -100 235 -134 235 -70 333 -102 497 -66 233 -364 301 -170 103 -66 165 -336 733 -200 133 -100 263 -102 65 -136 465 -200 1035 -198 165 -170 67 -302 631 -100 429 -332 65 -128 129 -130 159 -128 159 -66 161 -96 325 -164 261 -100 197 -162 65 -96 99 -130 65 -102 333 -100 199 -98 389 -330 129 -128 229 -66 425 -366 229 -64 261 -100 227 -96 227 -526 301 -200 97 -66 699 -334 67 -100 399 -198 787 -98 297 -134 429 -100 3245 -64 527 -98 131 -526 633 -68 133 -302 1459 -164 971 -102 237 -136 1439 -266 1131 -66 599 -200 303 -332 325 -130 389 -166 371 -66 333 -102 65 -100 233 -234 327 -266 233 -166 297 -100 225 -130 163 -336 99 -596 199 -330 131 -66 331 -338 263 -358 197 -168 877 -66 227 -96 63 -130 263 -162 225 -290 197 -198 357 -132 297 -262 165 -456 227 -98 399 -296 95 -132 99 -98 457 -200 199 -168 535 -100 567 -134 327 -130 193 -130 683 -102 101 -132 233 -170 943 -166 827 -66 267 -102 503 -68 1325 -164 -RAW_Data: 1607 -68 233 -166 1167 -70 531 -134 335 -168 131 -66 299 -402 899 -66 461 -66 457 -98 953 -98 165 -66 293 -230 881 -64 393 -166 589 -66 289 -66 1093 -204 333 -98 2745 -132 2019 -170 925 -68 269 -102 1469 -136 2301 -68 1355 -100 527 -66 975 -68 1445 -98 2397 -100 1733 -66 703 -100 995 -100 135 -136 235 -202 167 -134 2071 -166 339 -170 201 -268 129 -66 465 -66 365 -100 197 -164 129 -98 161 -96 423 -66 675 -66 1543 -136 567 -200 767 -202 65 -100 1401 -66 623 -136 567 -234 67 -236 197 -194 97 -66 263 -66 1827 -392 1893 -98 165 -268 133 -132 231 -162 225 -98 695 -198 563 -100 301 -332 267 -102 341 -66 99 -132 1299 -130 525 -68 161 -96 357 -98 353 -100 131 -100 131 -98 163 -132 323 -100 535 -66 1323 -130 133 -66 235 -134 1497 -132 387 -98 129 -162 2623 -134 163 -68 167 -66 959 -232 495 -68 131 -134 867 -134 865 -66 333 -98 305 -134 231 -98 765 -198 397 -432 165 -66 165 -366 265 -102 541 -100 261 -162 331 -134 457 -66 491 -196 97 -266 193 -262 65 -166 231 -266 497 -360 263 -98 587 -164 259 -98 231 -66 359 -100 267 -102 271 -168 97 -262 63 -66 261 -130 227 -130 295 -164 65 -66 265 -200 597 -134 267 -170 603 -100 97 -466 231 -264 97 -168 99 -66 65 -200 199 -100 267 -404 303 -102 201 -204 235 -134 131 -198 335 -298 327 -130 291 -164 63 -162 295 -262 197 -130 95 -130 195 -96 159 -130 161 -66 231 -100 165 -66 199 -134 363 -66 267 -168 165 -168 167 -100 165 -530 363 -432 99 -232 65 -132 395 -328 229 -98 197 -132 161 -96 191 -292 197 -204 133 -100 399 -166 531 -332 235 -168 99 -66 325 -158 553 -132 129 -226 231 -134 99 -462 129 -64 289 -100 193 -66 355 -164 291 -198 131 -298 197 -198 373 -268 335 -234 427 -68 199 -132 267 -232 131 -66 783 -326 63 -162 161 -130 227 -66 259 -562 233 -464 303 -102 201 -334 301 -134 297 -198 229 -66 127 -166 99 -100 197 -198 571 -66 457 -134 361 -424 131 -328 163 -98 63 -100 505 -102 201 -1094 229 -164 65 -230 789 -236 2505 -166 201 -170 163 -64 1139 -66 927 -100 295 -198 723 -100 365 -66 459 -196 3033 -272 199 -66 499 -202 1319 -232 295 -298 131 -362 97 -164 129 -132 65 -98 197 -130 129 -98 261 -130 97 -98 229 -96 425 -66 227 -166 483 -66 163 -326 567 -68 235 -68 67 -66 167 -66 235 -330 425 -164 63 -66 427 -102 167 -66 669 -132 429 -200 65 -102 133 -100 197 -368 -RAW_Data: 65 -134 2481 -228 65 -130 229 -228 763 -136 603 -166 1619 -98 1763 -102 837 -166 321 -66 951 -130 2067 -66 259 -132 1835 -66 437 -102 701 -66 565 -68 363 -70 1113 -66 1989 -164 257 -128 351 -162 1055 -232 265 -170 309 -200 435 -166 833 -102 2467 -132 595 -66 773 -166 1615 -98 131 -96 485 -64 517 -166 197 -68 1231 -68 403 -100 263 -134 233 -100 503 -100 333 -266 729 -66 199 -100 369 -68 1239 -100 197 -68 299 -170 337 -100 825 -132 163 -66 4205 -64 161 -100 635 -66 907 -66 1017 -166 1709 -100 201 -266 657 -68 463 -166 331 -164 293 -64 259 -162 129 -262 597 -134 701 -136 67 -168 235 -136 303 -170 1417 -66 263 -98 857 -100 659 -166 97 -100 2497 -64 2495 -98 719 -128 227 -130 2217 -164 623 -264 719 -134 329 -98 1371 -100 553 -294 165 -66 1163 -100 329 -196 649 -200 1123 -68 263 -100 593 -266 333 -102 1133 -136 131 -132 603 -200 1819 -66 489 -66 563 -266 1113 -230 165 -66 423 -68 335 -100 101 -100 1073 -132 897 -100 101 -100 499 -134 173 -138 763 -238 371 -130 403 -166 203 -102 271 -136 269 -166 99 -168 263 -96 425 -66 331 -234 133 -400 231 -132 453 -66 459 -164 199 -68 237 -132 163 -198 161 -196 265 -132 65 -64 195 -130 357 -164 663 -68 167 -600 131 -98 133 -304 203 -134 433 -98 261 -130 199 -100 237 -100 229 -326 99 -98 331 -132 99 -294 165 -66 303 -134 99 -232 133 -136 99 -68 267 -198 233 -138 67 -166 367 -100 333 -168 267 -200 369 -266 135 -404 1939 -132 231 -160 161 -64 293 -98 331 -132 339 -104 135 -100 197 -430 263 -202 233 -64 195 -162 129 -64 227 -298 265 -68 697 -66 301 -68 231 -300 131 -368 769 -234 265 -98 195 -324 97 -752 229 -126 355 -98 257 -98 287 -64 427 -132 295 -262 197 -170 369 -102 267 -100 169 -68 201 -102 2551 -136 635 -134 639 -134 99 -132 197 -200 371 -66 731 -132 199 -138 733 -304 433 -68 729 -440 197 -68 99 -102 165 -266 261 -164 491 -296 489 -194 257 -164 133 -134 237 -68 335 -98 227 -130 229 -98 295 -98 231 -202 267 -236 233 -136 331 -130 195 -128 261 -430 261 -162 97 -224 99 -130 193 -96 197 -162 229 -396 97 -98 227 -364 267 -100 99 -100 233 -236 697 -164 227 -196 63 -98 327 -230 325 -66 129 -196 95 -98 195 -130 325 -430 131 -194 129 -454 161 -196 235 -68 433 -134 667 -164 355 -236 101 -98 2143 -134 1827 -198 63 -198 65 -64 2859 -64 619 -66 97 -130 3157 -66 679 -194 1491 -98 -RAW_Data: 951 -64 393 -100 955 -132 4715 -100 131 -66 199 -204 1541 -66 929 -130 1347 -166 665 -132 233 -132 67 -102 433 -100 595 -228 997 -66 505 -68 133 -98 231 -68 571 -134 1371 -232 231 -270 135 -102 97 -66 867 -100 269 -68 967 -100 1649 -66 65 -66 951 -68 65 -202 363 -200 779 -102 1449 -294 419 -130 361 -230 1079 -164 163 -260 893 -102 333 -100 533 -166 467 -100 135 -66 135 -202 369 -100 199 -100 269 -134 301 -166 229 -66 101 -134 199 -134 1293 -64 779 -62 831 -66 1243 -68 267 -102 197 -100 395 -98 455 -64 621 -132 877 -98 199 -100 2101 -134 503 -100 2035 -134 735 -236 475 -136 237 -132 133 -134 1229 -100 133 -66 167 -68 2655 -100 1807 -100 1095 -264 825 -98 163 -66 491 -98 161 -128 953 -100 773 -100 131 -66 67 -134 457 -130 63 -64 389 -98 715 -66 425 -300 97 -100 1515 -66 303 -68 99 -98 721 -64 887 -132 65 -132 165 -66 635 -68 2801 -66 1561 -100 751 -98 129 -64 725 -136 201 -100 333 -204 573 -104 1745 -134 99 -66 129 -64 595 -134 167 -102 337 -134 567 -134 1131 -138 1207 -100 269 -68 135 -100 1143 -134 2139 -68 1701 -162 991 -596 431 -66 99 -132 657 -66 391 -320 357 -260 259 -98 429 -66 163 -228 65 -130 227 -66 261 -166 99 -98 131 -366 199 -134 463 -102 201 -98 231 -102 639 -238 301 -568 169 -610 265 -102 841 -198 297 -100 335 -132 263 -266 265 -68 469 -134 267 -68 933 -298 333 -298 729 -168 135 -136 437 -132 1137 -134 199 -68 265 -132 463 -166 129 -130 227 -98 297 -98 65 -132 97 -202 199 -232 305 -66 165 -198 365 -66 99 -98 299 -170 65 -136 301 -232 99 -564 133 -132 233 -170 99 -102 131 -134 65 -204 101 -98 297 -98 167 -762 233 -298 99 -326 395 -66 299 -132 369 -504 333 -98 483 -200 457 -164 63 -164 329 -162 65 -622 231 -268 131 -132 133 -134 131 -134 131 -66 99 -100 231 -66 167 -336 165 -98 197 -100 97 -264 321 -98 521 -132 163 -130 129 -294 297 -134 101 -102 265 -168 497 -68 197 -68 499 -134 269 -398 267 -130 203 -302 65 -498 271 -136 465 -292 131 -294 163 -198 329 -96 129 -98 193 -130 391 -330 165 -134 167 -170 297 -102 133 -136 135 -366 199 -132 423 -132 395 -168 65 -166 401 -98 229 -98 329 -98 99 -130 129 -228 261 -160 127 -426 389 -162 193 -132 131 -100 231 -168 67 -304 201 -68 765 -132 161 -162 193 -64 195 -64 295 -130 787 -98 419 -528 429 -66 363 -134 131 -100 133 -200 331 -98 -RAW_Data: 431 -66 1167 -68 937 -68 1003 -66 99 -132 941 -134 65 -66 365 -274 165 -236 367 -96 557 -134 675 -66 261 -164 127 -96 391 -164 161 -98 391 -292 163 -98 519 -196 165 -98 523 -66 195 -160 3343 -66 661 -100 2589 -136 307 -100 629 -136 639 -100 133 -168 405 -100 267 -66 465 -132 1171 -64 749 -64 165 -98 983 -100 163 -202 537 -66 327 -100 669 -100 401 -236 2885 -164 439 -134 97 -426 1931 -66 1385 -98 715 -98 519 -66 289 -162 97 -360 297 -166 163 -66 289 -66 555 -334 167 -230 429 -102 267 -132 943 -136 401 -68 929 -130 193 -68 467 -198 335 -66 963 -100 597 -132 197 -260 523 -232 1115 -102 1935 -66 1395 -134 305 -100 99 -66 199 -66 1071 -66 2357 -66 367 -498 769 -234 163 -130 191 -64 1211 -200 133 -102 201 -100 561 -366 361 -98 195 -100 537 -64 165 -196 1041 -332 133 -102 441 -230 4217 -66 1033 -66 167 -66 933 -100 565 -66 331 -164 673 -104 441 -66 533 -66 2095 -164 525 -66 297 -170 965 -198 421 -100 663 -832 65 -100 331 -164 231 -166 135 -168 237 -466 761 -134 891 -196 791 -198 257 -160 161 -98 293 -66 1081 -98 229 -130 327 -66 1301 -200 331 -166 101 -66 461 -100 2619 -132 1663 -98 1609 -134 499 -332 165 -370 67 -264 97 -96 259 -98 701 -402 197 -128 527 -236 233 -102 167 -134 303 -134 99 -166 299 -132 165 -200 467 -68 305 -168 207 -102 465 -102 729 -136 101 -374 327 -96 259 -98 467 -202 65 -66 673 -98 335 -404 135 -66 339 -204 99 -366 233 -68 365 -166 133 -102 867 -198 163 -162 163 -294 463 -332 165 -68 269 -268 331 -100 131 -166 299 -132 231 -400 263 -164 131 -266 267 -264 367 -66 371 -134 229 -104 267 -232 67 -466 265 -100 101 -100 165 -200 65 -200 301 -66 199 -168 233 -98 267 -66 67 -134 261 -196 261 -234 427 -294 65 -194 193 -66 259 -132 849 -96 63 -198 167 -294 95 -98 361 -164 261 -196 131 -132 437 -100 597 -262 327 -162 295 -98 295 -164 259 -196 425 -230 321 -66 195 -66 261 -496 99 -200 529 -132 133 -966 133 -132 165 -66 63 -128 491 -402 65 -262 299 -66 299 -202 265 -100 99 -668 97 -134 65 -100 101 -66 65 -266 691 -66 431 -166 167 -134 199 -370 899 -134 99 -100 1093 -166 163 -166 399 -98 327 -100 99 -168 135 -200 133 -202 429 -98 65 -98 197 -556 65 -66 97 -326 331 -166 333 -200 135 -100 235 -234 265 -98 65 -68 135 -66 335 -66 133 -298 99 -66 233 -164 435 -232 97 -132 97 -392 -RAW_Data: 99 -198 819 -66 1235 -98 321 -132 1091 -66 1307 -98 3059 -164 3305 -64 227 -98 591 -98 129 -66 229 -98 2143 -98 939 -68 563 -100 361 -232 945 -164 257 -96 229 -230 387 -64 195 -130 981 -294 587 -162 193 -98 1337 -66 293 -98 2665 -66 297 -98 647 -66 459 -132 491 -164 489 -96 595 -66 899 -66 837 -64 1151 -196 259 -98 357 -164 891 -132 1359 -134 197 -98 97 -98 261 -64 229 -96 461 -136 693 -100 201 -98 865 -66 599 -100 517 -132 709 -66 293 -298 655 -66 197 -130 129 -66 197 -98 4291 -66 673 -66 667 -132 1473 -132 133 -104 99 -66 163 -168 333 -134 1743 -132 1097 -132 99 -68 167 -602 1323 -352 99 -166 753 -98 423 -98 97 -66 1317 -228 1309 -98 1849 -66 1939 -132 601 -100 665 -100 1875 -66 695 -132 425 -66 425 -66 263 -134 165 -134 99 -98 829 -66 601 -166 131 -102 565 -66 301 -100 1099 -100 601 -138 533 -66 667 -234 561 -66 99 -68 2741 -98 199 -100 531 -168 101 -434 1027 -68 431 -66 403 -132 99 -98 565 -132 135 -100 399 -166 271 -236 233 -166 197 -366 99 -66 99 -168 503 -66 199 -170 207 -100 673 -368 99 -66 263 -168 133 -98 397 -268 337 -66 131 -132 231 -132 501 -134 99 -168 567 -138 103 -136 267 -298 231 -134 197 -160 321 -332 231 -98 131 -164 257 -64 163 -328 395 -66 331 -202 65 -168 133 -68 167 -100 233 -102 335 -66 197 -326 1101 -132 589 -100 811 -132 399 -136 269 -102 497 -66 559 -100 129 -98 855 -68 637 -102 65 -200 875 -68 233 -166 167 -66 529 -202 235 -102 231 -66 1237 -66 733 -98 1723 -132 101 -100 297 -66 829 -232 197 -100 367 -134 169 -166 167 -434 633 -100 235 -200 131 -134 233 -100 131 -100 331 -134 495 -432 65 -528 161 -130 295 -132 337 -136 133 -166 165 -100 269 -240 201 -336 133 -166 165 -238 199 -202 431 -434 99 -134 501 -166 231 -96 559 -202 167 -66 717 -98 987 -198 65 -64 163 -64 227 -98 555 -164 199 -64 361 -66 163 -98 129 -162 97 -130 161 -460 197 -230 681 -98 197 -98 329 -100 267 -266 291 -264 65 -100 329 -100 459 -200 363 -98 165 -134 231 -134 301 -134 231 -302 99 -132 101 -134 267 -136 233 -68 393 -422 163 -166 361 -166 99 -134 365 -134 133 -336 401 -66 495 -132 401 -168 133 -402 501 -136 1093 -862 165 -132 293 -300 289 -66 131 -164 391 -134 99 -360 359 -130 323 -200 423 -98 195 -162 295 -132 161 -98 129 -782 131 -426 227 -64 259 -166 63 -160 323 -98 261 -230 -RAW_Data: 231 -66 921 -66 355 -64 1019 -98 227 -258 163 -66 597 -232 1313 -132 163 -404 467 -236 901 -164 483 -98 195 -96 489 -134 103 -238 169 -66 67 -68 299 -100 497 -68 65 -134 1635 -304 1153 -100 539 -168 265 -200 499 -166 535 -100 397 -168 931 -100 131 -66 631 -134 897 -270 1233 -100 65 -132 131 -334 663 -66 163 -66 131 -132 705 -98 571 -200 433 -100 237 -234 229 -132 1627 -66 569 -100 715 -66 1863 -272 265 -68 301 -98 465 -68 97 -134 99 -66 395 -136 1405 -66 529 -132 63 -196 579 -132 413 -260 129 -136 101 -166 1201 -134 833 -134 393 -66 335 -172 201 -68 1027 -96 753 -64 815 -66 97 -64 1341 -132 289 -160 127 -66 99 -228 1083 -96 163 -66 259 -64 159 -98 2409 -168 767 -200 367 -66 1675 -66 1067 -98 3407 -200 99 -66 1403 -166 99 -134 439 -200 329 -136 599 -66 637 -66 835 -66 1099 -98 99 -66 463 -166 165 -100 461 -164 3037 -66 655 -66 97 -98 229 -130 355 -132 1443 -66 527 -98 881 -98 229 -162 127 -96 583 -64 65 -162 489 -166 885 -194 257 -98 1539 -66 293 -166 229 -132 655 -98 757 -49522 271 -758 689 -1264 737 -670 293 -1152 811 -1144 341 -664 773 -678 327 -1118 807 -1144 835 -1146 781 -1126 873 -1096 347 -622 877 -624 321 -1106 843 -1098 871 -1098 843 -1106 379 -610 841 -584 381 -1122 365 -602 845 -1116 837 -610 381 -1056 889 -1078 383 -614 827 -1110 877 -592 353 -1108 845 -1120 839 -1120 347 -602 849 -1110 865 -612 361 -1072 869 -1114 351 -618 861 -618 343 -1090 853 -1106 387 -618 797 -674 347 -1084 389 -574 867 -584 381 -1114 841 -1102 845 -1116 839 -1112 843 -1098 875 -1086 383 -584 865 -588 375 -1100 861 -1112 851 -1084 853 -1108 847 -1106 381 -584 857 -610 383 -1080 357 -602 871 -602 385 -1084 383 -616 823 -610 373 -1086 381 -590 871 -1084 839 -628 353 -1102 875 -1100 349 -9404 875 -1060 871 -1086 887 -1088 879 -1058 863 -1086 855 -1132 845 -1078 871 -1076 857 -1098 881 -1082 861 -1088 843 -1120 853 -1074 879 -1074 879 -1068 889 -614 341 -1090 387 -616 863 -624 345 -1088 391 -590 857 -612 385 -1058 393 -596 843 -1088 889 -1078 879 -578 387 -1082 875 -1076 415 -550 881 -1070 877 -592 391 -1114 821 -1104 373 -620 821 -624 361 -1072 903 -1086 855 -1092 843 -1086 905 -1054 387 -614 863 -618 347 -1088 853 -1114 845 -1090 867 -1070 381 -610 885 -584 385 -1052 407 -578 877 -1052 899 -600 389 -1048 907 -1074 383 -586 877 -1072 877 -594 359 -1076 875 -1082 891 -1088 363 -616 855 -1084 857 -592 381 -1088 883 -1086 385 -572 -RAW_Data: 889 -624 353 -1082 853 -1096 379 -594 853 -624 353 -1092 417 -582 847 -612 385 -1076 847 -1080 883 -1052 913 -1044 907 -1076 849 -1088 383 -602 867 -616 361 -1068 901 -1072 865 -1104 831 -1080 879 -1098 397 -586 855 -626 355 -1084 381 -592 873 -616 351 -1084 385 -624 821 -620 359 -1086 387 -584 883 -1086 877 -592 355 -1106 853 -1086 387 -69570 97 -100 99 -2620 131 -636 333 -102 235 -236 67 -68 363 -66 201 -100 567 -102 267 -164 101 -134 65 -68 197 -68 297 -166 671 -100 469 -336 165 -100 201 -66 169 -230 169 -204 329 -624 67 -98 265 -232 193 -168 299 -100 235 -138 101 -370 165 -294 333 -622 231 -130 129 -130 353 -132 195 -162 359 -164 67 -68 333 -100 133 -688 235 -236 497 -198 293 -98 129 -296 293 -164 229 -128 229 -132 193 -400 165 -66 163 -98 361 -164 355 -196 587 -164 131 -98 263 -554 99 -130 129 -130 191 -464 99 -132 67 -100 167 -604 329 -66 199 -68 133 -102 163 -66 2971 -132 785 -66 329 -96 323 -100 201 -136 301 -66 1959 -166 867 -134 467 -66 297 -100 835 -100 753 -166 165 -64 67 -370 335 -66 559 -232 165 -334 65 -162 129 -354 163 -64 131 -134 265 -300 263 -132 267 -296 327 -198 99 -132 535 -132 469 -866 231 -860 99 -232 503 -134 99 -198 233 -134 267 -200 97 -358 297 -164 259 -98 227 -166 135 -66 323 -100 97 -294 131 -164 129 -98 295 -96 129 -426 299 -100 67 -102 623 -100 163 -194 127 -360 563 -134 199 -428 493 -98 229 -130 257 -64 165 -100 131 -98 163 -692 357 -64 161 -98 321 -64 389 -230 65 -692 227 -130 261 -132 231 -162 287 -298 97 -460 393 -130 301 -168 331 -100 269 -202 101 -134 201 -102 99 -132 199 -204 235 -664 65 -562 133 -328 463 -100 291 -194 159 -162 227 -98 293 -328 165 -128 227 -574 535 -332 197 -168 65 -300 131 -66 389 -1078 131 -64 259 -64 223 -98 257 -164 63 -328 433 -134 65 -602 131 -68 333 -136 369 -66 297 -264 427 -66 97 -130 429 -102 133 -136 203 -240 167 -236 329 -526 67 -132 133 -168 331 -360 65 -66 331 -296 267 -134 469 -132 595 -230 661 -662 299 -100 265 -200 203 -168 801 -100 133 -68 399 -132 99 -100 161 -390 65 -298 65 -98 261 -130 161 -128 257 -66 67 -134 621 -98 227 -328 99 -230 129 -294 193 -96 195 -318 425 -526 129 -196 163 -162 65 -132 293 -130 63 -66 325 -128 63 -130 293 -66 199 -200 269 -206 133 -198 325 -98 163 -100 97 -98 261 -164 67 -98 167 -430 131 -494 131 -164 -RAW_Data: 97 -98 861 -66 1199 -166 231 -100 651 -166 197 -104 439 -98 131 -64 493 -98 883 -96 99 -98 3327 -66 131 -264 733 -134 2133 -166 131 -102 303 -136 535 -134 701 -98 355 -228 131 -202 99 -134 99 -100 791 -166 169 -202 671 -100 741 -100 263 -66 165 -68 935 -132 197 -198 673 -100 605 -66 1457 -98 1195 -166 2347 -134 505 -100 1469 -66 391 -100 229 -100 1171 -98 939 -100 459 -170 369 -134 231 -162 127 -98 95 -66 195 -98 195 -66 299 -100 331 -98 65 -232 369 -132 201 -68 167 -166 1481 -102 501 -160 1257 -66 2307 -64 623 -164 2079 -66 1101 -98 423 -64 659 -68 431 -136 99 -100 435 -130 167 -168 835 -200 135 -104 133 -100 503 -68 1437 -232 821 -132 357 -96 463 -66 263 -64 683 -132 165 -96 655 -166 3939 -100 1169 -132 2443 -98 197 -132 425 -234 233 -162 1043 -66 197 -100 2793 -134 167 -104 675 -100 197 -134 1367 -102 763 -132 265 -230 133 -102 365 -100 167 -66 1069 -66 837 -100 295 -160 97 -64 129 -132 617 -164 197 -100 133 -136 337 -172 133 -66 557 -98 951 -66 263 -130 587 -66 729 -196 335 -166 933 -432 369 -100 199 -296 225 -98 355 -66 129 -64 557 -98 289 -66 355 -128 193 -162 267 -134 299 -98 165 -170 303 -640 1031 -134 99 -66 135 -68 771 -166 171 -104 201 -134 131 -68 635 -428 661 -292 749 -430 1161 -100 905 -98 65 -98 657 -262 2837 -132 67 -66 265 -132 631 -66 1037 -296 97 -98 1703 -302 367 -100 505 -232 497 -362 333 -134 591 -100 755 -232 67 -130 587 -66 231 -168 65 -332 99 -66 267 -232 393 -134 65 -132 131 -428 133 -200 165 -202 199 -168 165 -102 269 -100 333 -852 201 -134 233 -202 65 -200 563 -768 265 -136 169 -102 169 -598 333 -202 267 -134 267 -328 163 -130 625 -500 199 -200 99 -270 65 -134 65 -198 65 -100 99 -596 493 -66 99 -66 331 -232 103 -136 373 -168 831 -170 65 -672 163 -102 133 -136 331 -100 333 -234 101 -100 99 -200 99 -100 201 -302 199 -600 301 -202 135 -134 705 -166 435 -530 97 -198 131 -198 195 -66 163 -392 293 -66 295 -370 229 -198 65 -100 405 -134 165 -134 133 -170 337 -236 205 -274 267 -134 329 -132 195 -132 503 -132 133 -136 133 -334 197 -196 299 -168 101 -100 233 -100 439 -134 301 -332 331 -298 433 -406 433 -68 167 -100 203 -100 101 -102 99 -328 397 -234 205 -168 133 -364 63 -202 397 -198 95 -394 267 -134 569 -66 201 -102 133 -136 101 -102 99 -132 99 -196 197 -498 197 -102 135 -170 -RAW_Data: 331 -164 63 -162 1267 -66 163 -130 129 -66 725 -164 231 -64 853 -66 101 -134 199 -102 99 -68 365 -66 357 -130 815 -64 357 -98 97 -98 97 -66 65 -466 231 -172 3749 -66 849 -130 917 -64 327 -64 1013 -98 555 -332 795 -100 571 -132 769 -132 401 -134 1297 -134 377 -138 435 -100 401 -100 667 -100 1761 -66 667 -66 1533 -236 233 -98 885 -130 457 -66 999 -66 165 -66 833 -134 695 -166 501 -66 499 -200 329 -64 197 -134 441 -100 2099 -98 491 -134 197 -130 2225 -132 65 -100 689 -64 193 -160 159 -96 195 -98 323 -164 259 -98 535 -472 771 -66 665 -270 665 -66 595 -266 2191 -64 643 -98 1287 -98 741 -100 233 -200 569 -194 261 -68 637 -100 97 -66 491 -158 395 -138 1017 -66 627 -262 559 -64 327 -98 263 -134 99 -102 201 -102 337 -66 167 -68 679 -100 471 -134 195 -66 133 -202 693 -96 197 -98 391 -164 99 -98 3883 -194 461 -100 237 -168 1891 -68 301 -68 969 -166 1439 -294 551 -130 389 -98 99 -196 167 -102 505 -66 569 -234 901 -98 407 -136 469 -66 769 -98 769 -166 1263 -266 297 -98 1701 -200 203 -168 329 -232 65 -100 329 -164 803 -100 135 -200 233 -166 135 -272 265 -134 197 -100 133 -134 539 -232 197 -396 165 -366 263 -68 233 -102 365 -132 233 -100 135 -266 199 -234 167 -232 97 -524 127 -128 389 -98 305 -364 261 -130 257 -162 589 -464 361 -66 229 -134 161 -100 203 -432 265 -66 199 -66 199 -366 229 -236 99 -134 99 -100 131 -168 133 -100 131 -236 267 -132 297 -264 291 -132 167 -234 65 -100 199 -66 333 -730 237 -440 365 -102 99 -100 99 -132 99 -100 1429 -134 427 -100 97 -100 131 -164 799 -170 1077 -100 431 -66 133 -168 737 -134 197 -230 65 -102 803 -132 491 -98 429 -198 471 -134 365 -66 299 -236 65 -66 2837 -102 399 -64 585 -64 523 -196 97 -98 295 -196 555 -160 261 -500 299 -396 333 -236 133 -68 327 -100 199 -204 699 -66 701 -100 65 -164 65 -370 195 -196 97 -66 193 -130 129 -360 195 -130 231 -96 291 -64 455 -228 293 -196 291 -162 97 -194 621 -130 847 -66 395 -66 161 -128 193 -130 293 -98 231 -170 67 -134 297 -360 167 -266 263 -526 263 -132 229 -98 191 -160 159 -100 721 -234 101 -100 99 -130 259 -258 265 -632 687 -164 133 -134 631 -100 199 -102 165 -560 299 -200 265 -332 431 -870 99 -266 503 -364 135 -66 269 -68 499 -100 265 -102 263 -102 569 -234 719 -132 99 -196 419 -262 163 -688 95 -66 165 -128 95 -66 -RAW_Data: 295 -98 987 -196 517 -100 489 -66 355 -132 563 -198 867 -134 1413 -134 541 -134 767 -100 193 -98 1799 -102 467 -134 299 -96 323 -66 261 -100 259 -66 229 -96 851 -66 369 -266 469 -66 101 -98 163 -136 267 -432 859 -130 523 -66 197 -134 1027 -132 227 -194 393 -98 807 -166 235 -100 133 -66 165 -102 133 -136 371 -162 1411 -132 865 -200 471 -100 133 -68 299 -66 633 -98 329 -234 401 -98 1505 -132 133 -134 331 -262 163 -66 261 -98 289 -64 201 -68 1055 -96 391 -66 951 -298 265 -202 297 -66 401 -68 131 -100 1733 -98 941 -66 803 -98 847 -64 3701 -100 721 -160 357 -166 1799 -66 329 -100 99 -102 363 -198 167 -136 197 -66 567 -66 199 -236 1247 -166 2455 -68 1107 -200 235 -100 2355 -130 913 -98 877 -98 163 -196 97 -66 427 -100 801 -134 867 -98 263 -68 441 -134 561 -98 1671 -134 865 -68 935 -132 163 -102 975 -66 1343 -132 1339 -134 369 -100 1107 -66 1167 -168 631 -232 835 -66 1027 -132 333 -166 265 -98 1207 -98 223 -98 455 -64 2095 -134 933 -136 233 -68 335 -136 305 -100 1737 -66 427 -100 263 -130 323 -66 227 -66 717 -100 265 -100 65 -128 355 -66 367 -132 95 -230 229 -100 131 -64 493 -132 291 -396 393 -130 259 -196 227 -288 397 -68 229 -430 99 -302 237 -700 65 -66 65 -100 133 -200 101 -336 133 -166 237 -202 67 -302 67 -68 333 -132 263 -102 267 -296 163 -166 233 -168 363 -64 295 -298 537 -166 431 -200 431 -166 63 -258 363 -164 563 -234 199 -68 299 -100 325 -754 295 -196 65 -98 165 -132 301 -134 131 -134 97 -68 405 -68 233 -134 271 -134 67 -168 101 -136 133 -366 99 -132 67 -132 265 -200 233 -100 201 -136 101 -66 263 -132 129 -66 293 -582 263 -132 1103 -134 203 -168 97 -66 197 -264 131 -168 133 -132 65 -134 199 -134 101 -100 131 -436 99 -232 97 -398 231 -362 65 -202 301 -396 297 -98 199 -134 265 -164 101 -168 267 -102 405 -170 99 -102 397 -132 97 -98 295 -98 1179 -100 135 -136 131 -134 765 -134 465 -168 439 -232 403 -100 65 -134 931 -100 169 -136 237 -68 231 -234 199 -68 401 -134 541 -166 429 -166 1607 -368 533 -66 363 -66 133 -134 433 -166 297 -238 201 -100 201 -170 199 -134 273 -136 99 -134 167 -238 133 -66 265 -134 165 -132 165 -132 97 -228 723 -198 415 -64 491 -298 257 -66 231 -192 225 -96 227 -98 193 -96 521 -198 65 -66 231 -166 163 -98 465 -66 133 -132 195 -130 225 -162 521 -130 63 -66 199 -228 -RAW_Data: 817 -162 449 -160 719 -198 469 -68 133 -68 1101 -132 593 -230 1105 -100 131 -134 231 -66 329 -196 685 -96 557 -68 1263 -68 101 -68 397 -100 65 -66 625 -66 97 -132 1099 -66 493 -66 757 -98 1151 -66 303 -134 1901 -66 99 -100 665 -262 991 -98 791 -66 1925 -168 865 -232 835 -98 505 -102 99 -100 535 -100 169 -134 427 -132 863 -68 167 -134 975 -100 133 -268 1339 -100 1453 -66 1445 -162 195 -64 3623 -66 237 -68 1063 -308 1449 -98 1111 -132 167 -102 855 -270 199 -134 297 -134 267 -168 863 -234 637 -66 567 -230 99 -200 3325 -198 845 -66 289 -66 131 -66 815 -130 1093 -100 167 -100 429 -98 1703 -166 195 -64 971 -98 163 -192 195 -168 439 -132 329 -132 67 -134 67 -134 1591 -168 407 -100 867 -68 399 -134 661 -100 663 -66 237 -136 395 -232 131 -66 695 -100 627 -264 913 -66 1083 -98 287 -66 199 -132 335 -100 1031 -68 99 -100 3815 -98 165 -66 129 -98 163 -128 563 -98 779 -96 223 -64 161 -164 2025 -66 1741 -172 101 -136 203 -102 665 -100 475 -64 167 -100 637 -98 997 -170 1207 -136 233 -166 233 -168 635 -132 199 -100 235 -270 199 -98 131 -102 169 -170 293 -98 323 -164 427 -334 233 -168 267 -68 369 -100 263 -368 101 -66 665 -98 265 -100 133 -100 99 -168 133 -66 133 -132 133 -66 269 -134 435 -68 267 -136 271 -500 163 -100 163 -166 355 -132 97 -98 323 -194 63 -688 463 -130 97 -396 65 -100 357 -194 461 -98 161 -130 223 -162 165 -352 461 -300 267 -166 233 -464 329 -100 293 -362 163 -228 289 -66 229 -66 195 -162 325 -66 261 -98 127 -424 299 -302 367 -68 265 -272 429 -98 161 -98 393 -296 65 -130 161 -196 261 -66 473 -234 97 -98 263 -160 323 -98 67 -132 697 -298 99 -134 233 -202 97 -134 301 -200 307 -100 101 -134 865 -166 231 -202 233 -100 301 -170 169 -102 169 -200 65 -98 595 -166 231 -234 661 -66 473 -334 165 -304 365 -266 97 -502 363 -134 133 -236 65 -100 99 -134 99 -170 235 -66 333 -100 195 -100 133 -300 133 -102 301 -304 65 -100 99 -100 131 -202 135 -134 65 -200 363 -66 263 -498 67 -68 295 -194 321 -368 435 -100 97 -664 99 -100 569 -66 133 -66 67 -134 199 -136 101 -68 301 -68 405 -198 133 -132 581 -132 165 -98 159 -98 197 -66 229 -130 131 -294 133 -96 423 -100 427 -300 357 -132 291 -64 95 -194 455 -98 263 -100 359 -196 65 -162 227 -162 157 -96 157 -230 589 -132 325 -134 535 -66 267 -100 135 -302 -RAW_Data: 131 -134 599 -166 393 -98 369 -236 197 -100 401 -232 569 -134 135 -70 337 -134 101 -136 135 -100 1895 -66 401 -170 503 -66 1633 -66 601 -66 355 -96 683 -100 729 -68 133 -132 433 -68 569 -100 133 -68 201 -132 835 -100 465 -68 527 -98 193 -200 1129 -166 535 -100 199 -98 259 -132 227 -64 1597 -98 261 -192 753 -100 911 -66 667 -298 131 -100 263 -66 1051 -230 787 -66 935 -66 233 -98 885 -236 431 -66 197 -162 521 -68 167 -196 263 -96 589 -98 517 -66 1439 -64 777 -66 3219 -132 679 -134 205 -68 507 -198 749 -200 199 -168 167 -100 133 -134 201 -68 731 -66 495 -198 737 -66 237 -68 135 -100 167 -234 1535 -68 873 -66 373 -66 67 -232 297 -68 65 -66 1095 -68 327 -130 63 -132 1715 -66 2261 -100 321 -132 197 -164 457 -232 1291 -132 405 -68 1001 -68 1133 -272 471 -66 99 -134 1403 -68 167 -68 1091 -336 933 -134 1207 -132 265 -68 267 -66 99 -366 265 -66 1469 -258 367 -168 429 -132 129 -66 491 -132 343 -100 65 -100 263 -136 199 -164 273 -204 791 -100 901 -66 167 -98 165 -64 559 -132 619 -132 1087 -128 2283 -398 1467 -164 259 -130 1927 -130 421 -98 1085 -66 705 -68 1843 -168 875 -170 203 -136 341 -640 199 -66 133 -554 161 -196 63 -66 521 -292 163 -160 95 -158 127 -192 197 -100 587 -130 397 -662 261 -66 193 -130 259 -66 361 -64 459 -98 197 -560 655 -130 389 -66 1135 -100 133 -130 131 -98 1011 -100 561 -66 685 -164 457 -132 2469 -200 609 -66 665 -66 67 -132 327 -200 1657 -134 919 -132 651 -100 327 -230 191 -130 263 -358 95 -130 549 -98 99 -68 299 -100 461 -132 99 -472 165 -134 99 -66 99 -132 399 -102 169 -102 697 -166 233 -132 333 -632 197 -164 865 -266 101 -68 533 -166 299 -100 163 -228 259 -66 327 -200 65 -66 229 -100 363 -230 197 -336 165 -102 893 -300 65 -132 231 -370 265 -230 99 -98 229 -518 199 -100 401 -724 225 -98 63 -96 231 -64 291 -292 65 -98 131 -98 159 -158 127 -194 161 -292 65 -98 133 -66 297 -66 303 -168 97 -168 231 -234 269 -532 135 -168 99 -168 301 -528 99 -506 199 -368 399 -132 329 -372 99 -68 133 -264 197 -100 201 -200 67 -134 131 -270 133 -134 133 -198 327 -200 65 -100 331 -262 161 -166 469 -534 167 -738 131 -100 367 -232 101 -100 265 -604 65 -170 99 -166 299 -102 169 -132 99 -398 229 -330 197 -166 335 -366 97 -98 131 -200 269 -100 199 -168 131 -134 537 -98 265 -100 335 -236 99 -366 -RAW_Data: 459 -100 453 -130 419 -130 519 -96 63 -130 2077 -66 767 -64 127 -134 1961 -296 529 -202 637 -134 527 -100 201 -68 633 -66 163 -360 1029 -68 765 -100 867 -66 503 -100 131 -66 841 -98 165 -68 237 -66 509 -100 501 -302 235 -66 99 -164 227 -130 551 -196 327 -66 1571 -132 99 -68 867 -66 163 -96 161 -130 129 -130 549 -130 487 -166 1801 -66 229 -66 197 -232 325 -66 425 -198 131 -64 295 -166 735 -66 533 -98 227 -130 129 -262 425 -100 263 -66 129 -132 97 -168 971 -170 405 -68 199 -134 475 -202 297 -98 1445 -98 395 -196 161 -66 225 -134 1803 -100 473 -102 1499 -66 199 -100 701 -132 165 -68 133 -102 303 -98 735 -102 805 -100 827 -100 235 -100 65 -266 637 -68 693 -66 1383 -228 819 -66 233 -304 435 -198 203 -136 1135 -270 1709 -64 227 -64 581 -134 505 -66 2203 -64 293 -64 753 -66 551 -132 747 -64 1303 -64 463 -66 229 -102 1877 -266 871 -166 1357 -64 819 -66 465 -198 693 -68 165 -64 95 -128 3785 -132 1465 -100 299 -102 329 -164 595 -134 1029 -66 299 -168 1263 -166 331 -68 967 -100 101 -102 603 -260 165 -132 467 -66 233 -66 235 -102 475 -100 135 -68 301 -134 297 -98 131 -102 269 -466 99 -134 237 -166 135 -168 203 -102 265 -68 503 -66 233 -66 637 -134 101 -200 199 -166 293 -554 361 -328 367 -264 533 -238 167 -68 135 -170 99 -300 591 -298 133 -236 299 -66 231 -368 263 -232 435 -136 133 -102 133 -200 133 -134 163 -134 167 -168 299 -66 265 -100 133 -240 135 -132 263 -170 269 -200 501 -396 263 -98 227 -132 129 -292 427 -66 165 -102 627 -602 99 -66 301 -168 199 -100 563 -330 165 -134 233 -136 65 -332 499 -100 131 -232 325 -96 65 -132 195 -98 393 -624 323 -68 133 -98 195 -162 231 -100 263 -132 231 -102 133 -236 99 -236 231 -166 65 -102 133 -268 101 -102 299 -136 267 -164 493 -64 229 -258 291 -326 263 -198 391 -134 167 -202 365 -594 133 -102 201 -134 503 -396 429 -204 169 -400 197 -170 267 -132 403 -466 297 -98 469 -234 395 -132 233 -100 165 -100 165 -66 197 -68 297 -166 501 -134 133 -100 65 -166 631 -68 297 -134 199 -100 165 -68 299 -266 133 -66 165 -100 231 -490 557 -134 371 -164 299 -170 733 -164 239 -334 335 -66 299 -300 199 -170 103 -100 233 -102 641 -168 65 -100 995 -66 265 -160 259 -130 129 -226 425 -100 355 -726 97 -688 99 -66 233 -266 299 -942 167 -102 167 -166 65 -100 367 -136 99 -134 199 -134 267 -164 -RAW_Data: 67 -68 233 -66 899 -66 163 -96 485 -98 355 -130 943 -100 235 -168 499 -104 1367 -98 297 -100 635 -68 1169 -100 67 -134 835 -264 959 -164 129 -98 419 -196 589 -66 421 -66 1717 -100 133 -100 265 -134 227 -356 455 -166 163 -66 1055 -100 1455 -134 463 -98 2191 -132 295 -132 335 -66 709 -64 619 -98 959 -68 835 -170 603 -134 1033 -134 635 -168 759 -232 397 -198 397 -164 1267 -166 257 -198 1295 -100 239 -104 563 -204 335 -198 203 -68 901 -68 1255 -134 1697 -66 793 -66 1691 -68 201 -100 765 -66 165 -132 131 -230 131 -66 917 -66 335 -338 231 -170 827 -98 199 -136 301 -196 65 -98 199 -200 765 -134 403 -98 333 -68 1691 -132 2565 -64 569 -170 1255 -264 65 -132 1243 -132 2527 -66 259 -66 1739 -100 1309 -198 167 -238 337 -66 131 -68 1973 -362 299 -100 1387 -96 129 -164 423 -230 3875 -96 4283 -98 165 -98 515 -134 469 -68 171 -102 1163 -100 65 -298 461 -66 367 -136 205 -168 371 -98 491 -164 161 -262 1093 -100 299 -100 269 -334 1205 -98 63 -98 261 -64 457 -98 diff --git a/assets/unit_tests/subghz/test_random_raw.sub b/assets/unit_tests/subghz/test_random_raw.sub index 7d342bb93ad..928838d3c8f 100644 --- a/assets/unit_tests/subghz/test_random_raw.sub +++ b/assets/unit_tests/subghz/test_random_raw.sub @@ -145,18 +145,3 @@ RAW_Data: -66 133 -66 97 -166 561 -100 895 -132 1323 -66 10873 -3752 99 -722 229 RAW_Data: -5434 65 -298 133 -132 131 -68 231 -200 661 -132 9517 -424 97 -1456 99 -1694 393 -100 131 -560 131 -196 197 -298 65 -428 229 -196 297 -266 131 -166 2435 -66 10161 -11230 65 -1320 131 -298 265 -532 231 -200 1291 -68 631 -66 12645 -4048 133 -66 67 -132 167 -266 163 -66 397 -132 197 -132 299 -98 197 -198 2903 -66 2361 -66 9627 -3588 197 -332 165 -68 331 -68 197 -132 99 -100 663 -66 363 -230 231 -166 131 -100 201 -298 163 -132 133 -202 363 -300 397 -102 263 -100 165 -66 1221 -66 1479 -132 165 -98 229 -12976 263 -66 363 -134 231 -66 629 -132 327 -100 97 -130 99 -164 227 -64 297 -132 397 -164 425 -198 97 -198 99 -66 365 -164 199 -102 97 -66 1817 -13524 231 -134 16907 -4086 233 -630 65 -396 201 -66 165 -198 67 -198 99 -664 2117 -166 12473 -446 2649 -440 2661 -420 2651 -422 2681 -418 2703 -400 365 -2724 387 -2696 2695 -414 357 -2704 2707 -386 389 -2700 2687 -392 405 -2706 2695 -402 363 -21268 2707 -388 377 -2706 2691 -404 2699 -382 2717 -382 2707 -378 2693 -416 2687 -396 363 -2736 355 -2748 2659 -416 365 -2708 2715 -388 377 -2708 2697 -404 363 -2730 2673 -420 355 -21268 2655 -460 319 -2766 2663 -448 2631 -436 2665 -418 2683 -410 2681 -416 2701 -386 383 -2700 375 -2744 2669 -416 353 -2730 2685 -416 357 -2708 2721 -380 369 -2724 2697 -382 385 -21260 2701 -418 353 -2720 2673 -418 2675 -408 2693 -384 2715 -386 2717 -386 2691 -404 363 -2732 387 -2702 2669 -412 359 -2736 2699 -380 381 -2728 2675 -416 381 -2720 2675 -414 347 -21280 2685 -390 377 -2724 2689 -416 2673 -408 2705 -382 2695 -410 2689 -414 2661 -418 385 -2704 369 -2704 2693 -416 375 -2726 2661 -420 355 -2728 2711 -388 375 -2702 2691 -410 363 -21252 2659 -488 287 -2794 2651 -448 2629 -436 2671 -416 2695 -416 2663 -406 2699 -384 383 -2730 367 -2702 2695 -418 385 -2702 2685 -412 349 -2744 2693 -366 389 -2714 2693 -394 381 -21266 2685 -418 363 -2730 2683 -382 2693 -418 2675 -410 2699 -384 2719 -382 2707 -380 359 -2734 387 -2704 2709 -380 361 -2732 2699 -418 357 -2728 2667 -416 383 -2696 2709 -380 391 -21228 2685 -458 307 -2800 2647 -412 2659 -432 2667 -416 2695 -416 2675 -406 2675 -416 383 -2700 361 -2730 2687 -414 375 -2696 2701 -420 353 -2720 2711 -382 367 -2728 2675 -416 385 -21222 2735 -386 355 -2744 2687 -396 2679 -418 2701 -386 2705 -382 2681 -410 2697 -384 385 -2736 365 -2704 2715 -384 377 -2696 2697 -416 349 -2722 2707 -386 379 -2732 2671 -410 361 -21258 2681 -464 297 -2796 2629 -456 2655 -420 2661 -448 2663 -404 2695 -382 2715 -380 371 -2740 355 -2744 2679 -384 391 -2728 2675 -388 379 RAW_Data: -2728 2695 -414 357 -2704 2705 -418 357 -21262 2673 -416 383 -2696 2709 -380 2703 -384 2699 -418 2671 -408 2695 -382 2713 -386 379 -2730 357 -2732 2695 -384 383 -2730 2679 -416 357 -2708 2701 -410 349 -2736 2697 -382 385 -21252 2669 -478 289 -2790 2647 -426 2651 -444 2653 -430 2659 -418 2695 -414 2681 -402 349 -2738 383 -2722 2677 -414 347 -2744 2691 -382 369 -2730 2691 -384 383 -2734 2679 -414 347 -21264 2705 -386 379 -2736 2667 -410 2695 -382 2715 -380 2709 -420 2665 -392 2713 -382 383 -2730 365 -2728 2665 -418 383 -2696 2693 -418 357 -2710 2711 -380 375 -2718 2701 -416 357 -21238 2677 -484 311 -2766 2635 -444 2657 -420 2663 -422 2695 -416 2667 -428 2675 -396 363 -73890 133 -98 131 -132 129 -658 99 -66 853 -100 63 -100 361 -98 1589 -66 1231 -132 65 -100 297 -198 65 -132 265 -66 9857 -4672 165 -1030 97 -1394 65 -200 2687 -68 6873 -8336 99 -1156 97 -66 163 -232 163 -262 197 -132 295 -132 263 -166 953 -100 263 -130 393 -164 295 -64 329 -66 393 -164 823 -130 165 -66 6133 -8436 165 -164 265 -266 65 -362 197 -696 3181 -132 363 -98 65 -166 131 -66 399 -132 663 -396 329 -66 7335 -7578 497 -230 627 -264 99 -366 99 -132 131 -134 265 -498 163 -100 1323 -66 265 -66 1129 -100 399 -132 365 -100 795 -68 397 -98 597 -364 297 -132 361 -132 265 -132 8591 -4740 65 -100 131 -166 199 -1088 97 -296 99 -528 131 -98 661 -66 401 -198 1157 -166 361 -164 495 -100 165 -66 297 -100 1423 -66 3067 -5658 67 -6406 197 -1092 65 -530 659 -68 265 -100 991 -68 231 -230 297 -66 327 -66 131 -132 659 -134 131 -100 1183 -132 263 -98 621 -66 2075 -6976 65 -5138 67 -132 129 -664 67 -132 165 -100 331 -466 231 -68 467 -98 563 -66 231 -100 531 -66 465 -66 1023 -166 297 -134 3409 -12290 67 -164 99 -532 133 -166 263 -66 231 -66 721 -64 131 -68 959 -134 495 -100 299 -98 497 -98 365 -100 397 -232 297 -98 531 -66 3029 -12216 265 -132 99 -364 199 -234 131 -66 431 -166 333 -166 397 -132 327 -100 395 -66 197 -132 395 -66 527 -98 295 -100 97 -98 789 -132 363 -132 297 -200 2815 -4914 65 -6620 65 -462 65 -134 297 -66 497 -264 231 -198 2773 -134 365 -100 831 -166 131 -100 297 -132 861 -132 299 -100 561 -66 1381 -6946 65 -5516 231 -266 97 -1362 1093 -68 1621 -134 165 -332 297 -98 361 -228 97 -132 797 -98 3487 -13224 229 -164 65 -132 913 -66 1123 -98 527 -134 929 -98 723 -100 12259 -270 165 -132 67 -132 165 -1326 99 -98 65 -1194 431 -66 695 -66 733 -134 197 RAW_Data: -134 10801 -166 67 -6130 133 -198 231 -334 365 -98 229 -132 165 -68 231 -166 14501 -524 65 -328 131 -498 129 -1288 65 -494 163 -64 165 -66 527 -132 131 -132 1019 -198 129 -166 393 -198 65 -164 6411 -66 3255 -10642 65 -1320 165 -164 493 -492 559 -264 2555 -66 695 -66 1657 -164 855 -66 4001 -10526 97 -596 133 -298 67 -264 65 -300 65 -100 263 -166 231 -134 99 -100 2703 -68 13643 -4922 297 -100 65 -232 133 -198 331 -300 231 -66 331 -100 12047 -3872 97 -196 65 -494 329 -66 65 -890 97 -98 229 -164 195 -596 797 -66 861 -132 65 -66 231 -100 565 -66 65 -66 1297 -132 265 -66 363 -134 265 -364 297 -164 299 -134 297 -134 495 -98 11309 -3790 131 -1380 65 -758 65 -164 129 -460 65 -360 199 -100 563 -68 497 -198 363 -266 263 -100 165 -66 697 -66 1933 -13594 65 -762 1223 -132 1119 -196 361 -134 131 -100 793 -166 695 -68 231 -68 463 -66 11727 -4204 363 -264 131 -132 133 -1124 97 -100 163 -100 327 -100 331 -198 397 -66 397 -100 395 -100 163 -66 197 -564 1059 -7962 65 -100 65 -198 129 -362 99 -394 197 -296 495 -100 1357 -68 459 -66 593 -66 265 -68 301 -132 465 -66 231 -200 397 -66 397 -232 199 -298 12077 -4350 231 -796 363 -198 133 -264 65 -1132 597 -332 3295 -100 755 -98 231 -164 97 -264 459 -166 759 -164 3265 -12138 99 -232 99 -1228 1025 -100 393 -66 531 -132 693 -132 1063 -66 427 -64 297 -294 229 -98 9723 -5404 67 -466 99 -796 267 -98 201 -100 167 -264 461 -98 1415 -66 861 -66 267 -66 331 -134 1663 -66 2089 -7012 65 -100 101 -4804 431 -728 99 -100 65 -100 995 -134 165 -66 929 -100 65 -66 927 -100 1093 -168 99 -100 497 -66 665 -200 6517 -8312 165 -66 129 -66 559 -166 99 -430 65 -398 67 -66 593 -198 459 -132 261 -132 263 -130 723 -66 459 -100 325 -166 67 -198 559 -66 493 -66 11475 -3896 99 -266 99 -66 197 -1092 129 -198 361 -166 163 -98 263 -196 759 -100 265 -100 365 -630 4635 -12748 65 -1712 461 -100 497 -66 395 -98 265 -98 229 -164 529 -132 297 -66 565 -132 987 -132 8665 -2820 2265 -450 313 -2774 2643 -442 325 -2772 2665 -416 359 -2734 2667 -386 379 -21274 2657 -474 293 -2810 2619 -466 2613 -476 2629 -452 2663 -388 2683 -418 2705 -400 365 -2722 387 -2700 2697 -380 361 -2732 2691 -418 361 -2732 2667 -416 383 -2698 2697 -416 357 -21238 2715 -384 383 -2732 2685 -416 2667 -416 2695 -398 2671 -418 2687 -390 2713 -382 383 -2730 365 -2728 2661 -416 379 -2716 2685 -384 379 -2720 2703 -378 401 -2718 2671 -RAW_Data: 889 -130 325 -64 457 -560 165 -68 199 -170 67 -66 265 -132 133 -666 67 -166 431 -66 201 -98 297 -100 595 -66 199 -134 65 -100 795 -132 99 -168 501 -200 331 -132 265 -102 265 -134 423 -98 521 -226 65 -166 431 -134 99 -100 133 -464 195 -326 623 -100 673 -98 321 -200 65 -136 369 -166 65 -68 97 -166 165 -334 265 -102 231 -166 101 -170 65 -170 265 -136 931 -100 133 -134 563 -66 333 -100 427 -66 163 -390 231 -66 193 -130 461 -166 557 -100 99 -198 263 -100 197 -294 231 -232 299 -134 199 -170 267 -134 631 -98 235 -100 499 -68 463 -100 65 -134 335 -170 273 -134 297 -100 67 -66 197 -166 67 -134 301 -168 537 -470 99 -134 433 -132 199 -192 261 -100 523 -164 459 -132 259 -332 359 -64 227 -96 131 -132 687 -132 363 -136 329 -434 99 -334 133 -100 401 -132 233 -700 233 -170 337 -66 371 -68 233 -202 531 -266 731 -66 465 -100 167 -100 133 -232 335 -166 239 -102 367 -232 231 -100 167 -134 201 -136 301 -168 199 -300 231 -98 237 -134 233 -102 329 -132 261 -134 199 -66 265 -136 99 -170 167 -134 199 -166 167 -136 367 -298 197 -200 99 -166 469 -136 439 -66 303 -134 295 -100 433 -134 899 -266 363 -132 197 -160 555 -324 129 -96 97 -128 257 -132 97 -394 257 -98 195 -166 459 -332 395 -132 633 -134 301 -100 131 -332 169 -168 395 -166 263 -540 783 -100 287 -130 295 -96 225 -296 133 -98 99 -100 461 -164 545 -130 99 -66 301 -68 265 -100 235 -134 235 -70 333 -102 497 -66 233 -364 301 -170 103 -66 165 -336 733 -200 133 -100 263 -102 65 -136 465 -200 1035 -198 165 -170 67 -302 631 -100 429 -332 65 -128 129 -130 159 -128 159 -66 161 -96 325 -164 261 -100 197 -162 65 -96 99 -130 65 -102 333 -100 199 -98 389 -330 129 -128 229 -66 425 -366 229 -64 261 -100 227 -96 227 -526 301 -200 97 -66 699 -334 67 -100 399 -198 787 -98 297 -134 429 -100 3245 -64 527 -98 131 -526 633 -68 133 -302 1459 -164 971 -102 237 -136 1439 -266 1131 -66 599 -200 303 -332 325 -130 389 -166 371 -66 333 -102 65 -100 233 -234 327 -266 233 -166 297 -100 225 -130 163 -336 99 -596 199 -330 131 -66 331 -338 263 -358 197 -168 877 -66 227 -96 63 -130 263 -162 225 -290 197 -198 357 -132 297 -262 165 -456 227 -98 399 -296 95 -132 99 -98 457 -200 199 -168 535 -100 567 -134 327 -130 193 -130 683 -102 101 -132 233 -170 943 -166 827 -66 267 -102 503 -68 1325 -164 -RAW_Data: 1607 -68 233 -166 1167 -70 531 -134 335 -168 131 -66 299 -402 899 -66 461 -66 457 -98 953 -98 165 -66 293 -230 881 -64 393 -166 589 -66 289 -66 1093 -204 333 -98 2745 -132 2019 -170 925 -68 269 -102 1469 -136 2301 -68 1355 -100 527 -66 975 -68 1445 -98 2397 -100 1733 -66 703 -100 995 -100 135 -136 235 -202 167 -134 2071 -166 339 -170 201 -268 129 -66 465 -66 365 -100 197 -164 129 -98 161 -96 423 -66 675 -66 1543 -136 567 -200 767 -202 65 -100 1401 -66 623 -136 567 -234 67 -236 197 -194 97 -66 263 -66 1827 -392 1893 -98 165 -268 133 -132 231 -162 225 -98 695 -198 563 -100 301 -332 267 -102 341 -66 99 -132 1299 -130 525 -68 161 -96 357 -98 353 -100 131 -100 131 -98 163 -132 323 -100 535 -66 1323 -130 133 -66 235 -134 1497 -132 387 -98 129 -162 2623 -134 163 -68 167 -66 959 -232 495 -68 131 -134 867 -134 865 -66 333 -98 305 -134 231 -98 765 -198 397 -432 165 -66 165 -366 265 -102 541 -100 261 -162 331 -134 457 -66 491 -196 97 -266 193 -262 65 -166 231 -266 497 -360 263 -98 587 -164 259 -98 231 -66 359 -100 267 -102 271 -168 97 -262 63 -66 261 -130 227 -130 295 -164 65 -66 265 -200 597 -134 267 -170 603 -100 97 -466 231 -264 97 -168 99 -66 65 -200 199 -100 267 -404 303 -102 201 -204 235 -134 131 -198 335 -298 327 -130 291 -164 63 -162 295 -262 197 -130 95 -130 195 -96 159 -130 161 -66 231 -100 165 -66 199 -134 363 -66 267 -168 165 -168 167 -100 165 -530 363 -432 99 -232 65 -132 395 -328 229 -98 197 -132 161 -96 191 -292 197 -204 133 -100 399 -166 531 -332 235 -168 99 -66 325 -158 553 -132 129 -226 231 -134 99 -462 129 -64 289 -100 193 -66 355 -164 291 -198 131 -298 197 -198 373 -268 335 -234 427 -68 199 -132 267 -232 131 -66 783 -326 63 -162 161 -130 227 -66 259 -562 233 -464 303 -102 201 -334 301 -134 297 -198 229 -66 127 -166 99 -100 197 -198 571 -66 457 -134 361 -424 131 -328 163 -98 63 -100 505 -102 201 -1094 229 -164 65 -230 789 -236 2505 -166 201 -170 163 -64 1139 -66 927 -100 295 -198 723 -100 365 -66 459 -196 3033 -272 199 -66 499 -202 1319 -232 295 -298 131 -362 97 -164 129 -132 65 -98 197 -130 129 -98 261 -130 97 -98 229 -96 425 -66 227 -166 483 -66 163 -326 567 -68 235 -68 67 -66 167 -66 235 -330 425 -164 63 -66 427 -102 167 -66 669 -132 429 -200 65 -102 133 -100 197 -368 -RAW_Data: 65 -134 2481 -228 65 -130 229 -228 763 -136 603 -166 1619 -98 1763 -102 837 -166 321 -66 951 -130 2067 -66 259 -132 1835 -66 437 -102 701 -66 565 -68 363 -70 1113 -66 1989 -164 257 -128 351 -162 1055 -232 265 -170 309 -200 435 -166 833 -102 2467 -132 595 -66 773 -166 1615 -98 131 -96 485 -64 517 -166 197 -68 1231 -68 403 -100 263 -134 233 -100 503 -100 333 -266 729 -66 199 -100 369 -68 1239 -100 197 -68 299 -170 337 -100 825 -132 163 -66 4205 -64 161 -100 635 -66 907 -66 1017 -166 1709 -100 201 -266 657 -68 463 -166 331 -164 293 -64 259 -162 129 -262 597 -134 701 -136 67 -168 235 -136 303 -170 1417 -66 263 -98 857 -100 659 -166 97 -100 2497 -64 2495 -98 719 -128 227 -130 2217 -164 623 -264 719 -134 329 -98 1371 -100 553 -294 165 -66 1163 -100 329 -196 649 -200 1123 -68 263 -100 593 -266 333 -102 1133 -136 131 -132 603 -200 1819 -66 489 -66 563 -266 1113 -230 165 -66 423 -68 335 -100 101 -100 1073 -132 897 -100 101 -100 499 -134 173 -138 763 -238 371 -130 403 -166 203 -102 271 -136 269 -166 99 -168 263 -96 425 -66 331 -234 133 -400 231 -132 453 -66 459 -164 199 -68 237 -132 163 -198 161 -196 265 -132 65 -64 195 -130 357 -164 663 -68 167 -600 131 -98 133 -304 203 -134 433 -98 261 -130 199 -100 237 -100 229 -326 99 -98 331 -132 99 -294 165 -66 303 -134 99 -232 133 -136 99 -68 267 -198 233 -138 67 -166 367 -100 333 -168 267 -200 369 -266 135 -404 1939 -132 231 -160 161 -64 293 -98 331 -132 339 -104 135 -100 197 -430 263 -202 233 -64 195 -162 129 -64 227 -298 265 -68 697 -66 301 -68 231 -300 131 -368 769 -234 265 -98 195 -324 97 -752 229 -126 355 -98 257 -98 287 -64 427 -132 295 -262 197 -170 369 -102 267 -100 169 -68 201 -102 2551 -136 635 -134 639 -134 99 -132 197 -200 371 -66 731 -132 199 -138 733 -304 433 -68 729 -440 197 -68 99 -102 165 -266 261 -164 491 -296 489 -194 257 -164 133 -134 237 -68 335 -98 227 -130 229 -98 295 -98 231 -202 267 -236 233 -136 331 -130 195 -128 261 -430 261 -162 97 -224 99 -130 193 -96 197 -162 229 -396 97 -98 227 -364 267 -100 99 -100 233 -236 697 -164 227 -196 63 -98 327 -230 325 -66 129 -196 95 -98 195 -130 325 -430 131 -194 129 -454 161 -196 235 -68 433 -134 667 -164 355 -236 101 -98 2143 -134 1827 -198 63 -198 65 -64 2859 -64 619 -66 97 -130 3157 -66 679 -194 1491 -98 -RAW_Data: 951 -64 393 -100 955 -132 4715 -100 131 -66 199 -204 1541 -66 929 -130 1347 -166 665 -132 233 -132 67 -102 433 -100 595 -228 997 -66 505 -68 133 -98 231 -68 571 -134 1371 -232 231 -270 135 -102 97 -66 867 -100 269 -68 967 -100 1649 -66 65 -66 951 -68 65 -202 363 -200 779 -102 1449 -294 419 -130 361 -230 1079 -164 163 -260 893 -102 333 -100 533 -166 467 -100 135 -66 135 -202 369 -100 199 -100 269 -134 301 -166 229 -66 101 -134 199 -134 1293 -64 779 -62 831 -66 1243 -68 267 -102 197 -100 395 -98 455 -64 621 -132 877 -98 199 -100 2101 -134 503 -100 2035 -134 735 -236 475 -136 237 -132 133 -134 1229 -100 133 -66 167 -68 2655 -100 1807 -100 1095 -264 825 -98 163 -66 491 -98 161 -128 953 -100 773 -100 131 -66 67 -134 457 -130 63 -64 389 -98 715 -66 425 -300 97 -100 1515 -66 303 -68 99 -98 721 -64 887 -132 65 -132 165 -66 635 -68 2801 -66 1561 -100 751 -98 129 -64 725 -136 201 -100 333 -204 573 -104 1745 -134 99 -66 129 -64 595 -134 167 -102 337 -134 567 -134 1131 -138 1207 -100 269 -68 135 -100 1143 -134 2139 -68 1701 -162 991 -596 431 -66 99 -132 657 -66 391 -320 357 -260 259 -98 429 -66 163 -228 65 -130 227 -66 261 -166 99 -98 131 -366 199 -134 463 -102 201 -98 231 -102 639 -238 301 -568 169 -610 265 -102 841 -198 297 -100 335 -132 263 -266 265 -68 469 -134 267 -68 933 -298 333 -298 729 -168 135 -136 437 -132 1137 -134 199 -68 265 -132 463 -166 129 -130 227 -98 297 -98 65 -132 97 -202 199 -232 305 -66 165 -198 365 -66 99 -98 299 -170 65 -136 301 -232 99 -564 133 -132 233 -170 99 -102 131 -134 65 -204 101 -98 297 -98 167 -762 233 -298 99 -326 395 -66 299 -132 369 -504 333 -98 483 -200 457 -164 63 -164 329 -162 65 -622 231 -268 131 -132 133 -134 131 -134 131 -66 99 -100 231 -66 167 -336 165 -98 197 -100 97 -264 321 -98 521 -132 163 -130 129 -294 297 -134 101 -102 265 -168 497 -68 197 -68 499 -134 269 -398 267 -130 203 -302 65 -498 271 -136 465 -292 131 -294 163 -198 329 -96 129 -98 193 -130 391 -330 165 -134 167 -170 297 -102 133 -136 135 -366 199 -132 423 -132 395 -168 65 -166 401 -98 229 -98 329 -98 99 -130 129 -228 261 -160 127 -426 389 -162 193 -132 131 -100 231 -168 67 -304 201 -68 765 -132 161 -162 193 -64 195 -64 295 -130 787 -98 419 -528 429 -66 363 -134 131 -100 133 -200 331 -98 -RAW_Data: 431 -66 1167 -68 937 -68 1003 -66 99 -132 941 -134 65 -66 365 -274 165 -236 367 -96 557 -134 675 -66 261 -164 127 -96 391 -164 161 -98 391 -292 163 -98 519 -196 165 -98 523 -66 195 -160 3343 -66 661 -100 2589 -136 307 -100 629 -136 639 -100 133 -168 405 -100 267 -66 465 -132 1171 -64 749 -64 165 -98 983 -100 163 -202 537 -66 327 -100 669 -100 401 -236 2885 -164 439 -134 97 -426 1931 -66 1385 -98 715 -98 519 -66 289 -162 97 -360 297 -166 163 -66 289 -66 555 -334 167 -230 429 -102 267 -132 943 -136 401 -68 929 -130 193 -68 467 -198 335 -66 963 -100 597 -132 197 -260 523 -232 1115 -102 1935 -66 1395 -134 305 -100 99 -66 199 -66 1071 -66 2357 -66 367 -498 769 -234 163 -130 191 -64 1211 -200 133 -102 201 -100 561 -366 361 -98 195 -100 537 -64 165 -196 1041 -332 133 -102 441 -230 4217 -66 1033 -66 167 -66 933 -100 565 -66 331 -164 673 -104 441 -66 533 -66 2095 -164 525 -66 297 -170 965 -198 421 -100 663 -832 65 -100 331 -164 231 -166 135 -168 237 -466 761 -134 891 -196 791 -198 257 -160 161 -98 293 -66 1081 -98 229 -130 327 -66 1301 -200 331 -166 101 -66 461 -100 2619 -132 1663 -98 1609 -134 499 -332 165 -370 67 -264 97 -96 259 -98 701 -402 197 -128 527 -236 233 -102 167 -134 303 -134 99 -166 299 -132 165 -200 467 -68 305 -168 207 -102 465 -102 729 -136 101 -374 327 -96 259 -98 467 -202 65 -66 673 -98 335 -404 135 -66 339 -204 99 -366 233 -68 365 -166 133 -102 867 -198 163 -162 163 -294 463 -332 165 -68 269 -268 331 -100 131 -166 299 -132 231 -400 263 -164 131 -266 267 -264 367 -66 371 -134 229 -104 267 -232 67 -466 265 -100 101 -100 165 -200 65 -200 301 -66 199 -168 233 -98 267 -66 67 -134 261 -196 261 -234 427 -294 65 -194 193 -66 259 -132 849 -96 63 -198 167 -294 95 -98 361 -164 261 -196 131 -132 437 -100 597 -262 327 -162 295 -98 295 -164 259 -196 425 -230 321 -66 195 -66 261 -496 99 -200 529 -132 133 -966 133 -132 165 -66 63 -128 491 -402 65 -262 299 -66 299 -202 265 -100 99 -668 97 -134 65 -100 101 -66 65 -266 691 -66 431 -166 167 -134 199 -370 899 -134 99 -100 1093 -166 163 -166 399 -98 327 -100 99 -168 135 -200 133 -202 429 -98 65 -98 197 -556 65 -66 97 -326 331 -166 333 -200 135 -100 235 -234 265 -98 65 -68 135 -66 335 -66 133 -298 99 -66 233 -164 435 -232 97 -132 97 -392 -RAW_Data: 99 -198 819 -66 1235 -98 321 -132 1091 -66 1307 -98 3059 -164 3305 -64 227 -98 591 -98 129 -66 229 -98 2143 -98 939 -68 563 -100 361 -232 945 -164 257 -96 229 -230 387 -64 195 -130 981 -294 587 -162 193 -98 1337 -66 293 -98 2665 -66 297 -98 647 -66 459 -132 491 -164 489 -96 595 -66 899 -66 837 -64 1151 -196 259 -98 357 -164 891 -132 1359 -134 197 -98 97 -98 261 -64 229 -96 461 -136 693 -100 201 -98 865 -66 599 -100 517 -132 709 -66 293 -298 655 -66 197 -130 129 -66 197 -98 4291 -66 673 -66 667 -132 1473 -132 133 -104 99 -66 163 -168 333 -134 1743 -132 1097 -132 99 -68 167 -602 1323 -352 99 -166 753 -98 423 -98 97 -66 1317 -228 1309 -98 1849 -66 1939 -132 601 -100 665 -100 1875 -66 695 -132 425 -66 425 -66 263 -134 165 -134 99 -98 829 -66 601 -166 131 -102 565 -66 301 -100 1099 -100 601 -138 533 -66 667 -234 561 -66 99 -68 2741 -98 199 -100 531 -168 101 -434 1027 -68 431 -66 403 -132 99 -98 565 -132 135 -100 399 -166 271 -236 233 -166 197 -366 99 -66 99 -168 503 -66 199 -170 207 -100 673 -368 99 -66 263 -168 133 -98 397 -268 337 -66 131 -132 231 -132 501 -134 99 -168 567 -138 103 -136 267 -298 231 -134 197 -160 321 -332 231 -98 131 -164 257 -64 163 -328 395 -66 331 -202 65 -168 133 -68 167 -100 233 -102 335 -66 197 -326 1101 -132 589 -100 811 -132 399 -136 269 -102 497 -66 559 -100 129 -98 855 -68 637 -102 65 -200 875 -68 233 -166 167 -66 529 -202 235 -102 231 -66 1237 -66 733 -98 1723 -132 101 -100 297 -66 829 -232 197 -100 367 -134 169 -166 167 -434 633 -100 235 -200 131 -134 233 -100 131 -100 331 -134 495 -432 65 -528 161 -130 295 -132 337 -136 133 -166 165 -100 269 -240 201 -336 133 -166 165 -238 199 -202 431 -434 99 -134 501 -166 231 -96 559 -202 167 -66 717 -98 987 -198 65 -64 163 -64 227 -98 555 -164 199 -64 361 -66 163 -98 129 -162 97 -130 161 -460 197 -230 681 -98 197 -98 329 -100 267 -266 291 -264 65 -100 329 -100 459 -200 363 -98 165 -134 231 -134 301 -134 231 -302 99 -132 101 -134 267 -136 233 -68 393 -422 163 -166 361 -166 99 -134 365 -134 133 -336 401 -66 495 -132 401 -168 133 -402 501 -136 1093 -862 165 -132 293 -300 289 -66 131 -164 391 -134 99 -360 359 -130 323 -200 423 -98 195 -162 295 -132 161 -98 129 -782 131 -426 227 -64 259 -166 63 -160 323 -98 261 -230 -RAW_Data: 231 -66 921 -66 355 -64 1019 -98 227 -258 163 -66 597 -232 1313 -132 163 -404 467 -236 901 -164 483 -98 195 -96 489 -134 103 -238 169 -66 67 -68 299 -100 497 -68 65 -134 1635 -304 1153 -100 539 -168 265 -200 499 -166 535 -100 397 -168 931 -100 131 -66 631 -134 897 -270 1233 -100 65 -132 131 -334 663 -66 163 -66 131 -132 705 -98 571 -200 433 -100 237 -234 229 -132 1627 -66 569 -100 715 -66 1863 -272 265 -68 301 -98 465 -68 97 -134 99 -66 395 -136 1405 -66 529 -132 63 -196 579 -132 413 -260 129 -136 101 -166 1201 -134 833 -134 393 -66 335 -172 201 -68 1027 -96 753 -64 815 -66 97 -64 1341 -132 289 -160 127 -66 99 -228 1083 -96 163 -66 259 -64 159 -98 2409 -168 767 -200 367 -66 1675 -66 1067 -98 3407 -200 99 -66 1403 -166 99 -134 439 -200 329 -136 599 -66 637 -66 835 -66 1099 -98 99 -66 463 -166 165 -100 461 -164 3037 -66 655 -66 97 -98 229 -130 355 -132 1443 -66 527 -98 881 -98 229 -162 127 -96 583 -64 65 -162 489 -166 885 -194 257 -98 1539 -66 293 -166 229 -132 655 -98 757 -49522 271 -758 689 -1264 737 -670 293 -1152 811 -1144 341 -664 773 -678 327 -1118 807 -1144 835 -1146 781 -1126 873 -1096 347 -622 877 -624 321 -1106 843 -1098 871 -1098 843 -1106 379 -610 841 -584 381 -1122 365 -602 845 -1116 837 -610 381 -1056 889 -1078 383 -614 827 -1110 877 -592 353 -1108 845 -1120 839 -1120 347 -602 849 -1110 865 -612 361 -1072 869 -1114 351 -618 861 -618 343 -1090 853 -1106 387 -618 797 -674 347 -1084 389 -574 867 -584 381 -1114 841 -1102 845 -1116 839 -1112 843 -1098 875 -1086 383 -584 865 -588 375 -1100 861 -1112 851 -1084 853 -1108 847 -1106 381 -584 857 -610 383 -1080 357 -602 871 -602 385 -1084 383 -616 823 -610 373 -1086 381 -590 871 -1084 839 -628 353 -1102 875 -1100 349 -9404 875 -1060 871 -1086 887 -1088 879 -1058 863 -1086 855 -1132 845 -1078 871 -1076 857 -1098 881 -1082 861 -1088 843 -1120 853 -1074 879 -1074 879 -1068 889 -614 341 -1090 387 -616 863 -624 345 -1088 391 -590 857 -612 385 -1058 393 -596 843 -1088 889 -1078 879 -578 387 -1082 875 -1076 415 -550 881 -1070 877 -592 391 -1114 821 -1104 373 -620 821 -624 361 -1072 903 -1086 855 -1092 843 -1086 905 -1054 387 -614 863 -618 347 -1088 853 -1114 845 -1090 867 -1070 381 -610 885 -584 385 -1052 407 -578 877 -1052 899 -600 389 -1048 907 -1074 383 -586 877 -1072 877 -594 359 -1076 875 -1082 891 -1088 363 -616 855 -1084 857 -592 381 -1088 883 -1086 385 -572 -RAW_Data: 889 -624 353 -1082 853 -1096 379 -594 853 -624 353 -1092 417 -582 847 -612 385 -1076 847 -1080 883 -1052 913 -1044 907 -1076 849 -1088 383 -602 867 -616 361 -1068 901 -1072 865 -1104 831 -1080 879 -1098 397 -586 855 -626 355 -1084 381 -592 873 -616 351 -1084 385 -624 821 -620 359 -1086 387 -584 883 -1086 877 -592 355 -1106 853 -1086 387 -69570 97 -100 99 -2620 131 -636 333 -102 235 -236 67 -68 363 -66 201 -100 567 -102 267 -164 101 -134 65 -68 197 -68 297 -166 671 -100 469 -336 165 -100 201 -66 169 -230 169 -204 329 -624 67 -98 265 -232 193 -168 299 -100 235 -138 101 -370 165 -294 333 -622 231 -130 129 -130 353 -132 195 -162 359 -164 67 -68 333 -100 133 -688 235 -236 497 -198 293 -98 129 -296 293 -164 229 -128 229 -132 193 -400 165 -66 163 -98 361 -164 355 -196 587 -164 131 -98 263 -554 99 -130 129 -130 191 -464 99 -132 67 -100 167 -604 329 -66 199 -68 133 -102 163 -66 2971 -132 785 -66 329 -96 323 -100 201 -136 301 -66 1959 -166 867 -134 467 -66 297 -100 835 -100 753 -166 165 -64 67 -370 335 -66 559 -232 165 -334 65 -162 129 -354 163 -64 131 -134 265 -300 263 -132 267 -296 327 -198 99 -132 535 -132 469 -866 231 -860 99 -232 503 -134 99 -198 233 -134 267 -200 97 -358 297 -164 259 -98 227 -166 135 -66 323 -100 97 -294 131 -164 129 -98 295 -96 129 -426 299 -100 67 -102 623 -100 163 -194 127 -360 563 -134 199 -428 493 -98 229 -130 257 -64 165 -100 131 -98 163 -692 357 -64 161 -98 321 -64 389 -230 65 -692 227 -130 261 -132 231 -162 287 -298 97 -460 393 -130 301 -168 331 -100 269 -202 101 -134 201 -102 99 -132 199 -204 235 -664 65 -562 133 -328 463 -100 291 -194 159 -162 227 -98 293 -328 165 -128 227 -574 535 -332 197 -168 65 -300 131 -66 389 -1078 131 -64 259 -64 223 -98 257 -164 63 -328 433 -134 65 -602 131 -68 333 -136 369 -66 297 -264 427 -66 97 -130 429 -102 133 -136 203 -240 167 -236 329 -526 67 -132 133 -168 331 -360 65 -66 331 -296 267 -134 469 -132 595 -230 661 -662 299 -100 265 -200 203 -168 801 -100 133 -68 399 -132 99 -100 161 -390 65 -298 65 -98 261 -130 161 -128 257 -66 67 -134 621 -98 227 -328 99 -230 129 -294 193 -96 195 -318 425 -526 129 -196 163 -162 65 -132 293 -130 63 -66 325 -128 63 -130 293 -66 199 -200 269 -206 133 -198 325 -98 163 -100 97 -98 261 -164 67 -98 167 -430 131 -494 131 -164 -RAW_Data: 97 -98 861 -66 1199 -166 231 -100 651 -166 197 -104 439 -98 131 -64 493 -98 883 -96 99 -98 3327 -66 131 -264 733 -134 2133 -166 131 -102 303 -136 535 -134 701 -98 355 -228 131 -202 99 -134 99 -100 791 -166 169 -202 671 -100 741 -100 263 -66 165 -68 935 -132 197 -198 673 -100 605 -66 1457 -98 1195 -166 2347 -134 505 -100 1469 -66 391 -100 229 -100 1171 -98 939 -100 459 -170 369 -134 231 -162 127 -98 95 -66 195 -98 195 -66 299 -100 331 -98 65 -232 369 -132 201 -68 167 -166 1481 -102 501 -160 1257 -66 2307 -64 623 -164 2079 -66 1101 -98 423 -64 659 -68 431 -136 99 -100 435 -130 167 -168 835 -200 135 -104 133 -100 503 -68 1437 -232 821 -132 357 -96 463 -66 263 -64 683 -132 165 -96 655 -166 3939 -100 1169 -132 2443 -98 197 -132 425 -234 233 -162 1043 -66 197 -100 2793 -134 167 -104 675 -100 197 -134 1367 -102 763 -132 265 -230 133 -102 365 -100 167 -66 1069 -66 837 -100 295 -160 97 -64 129 -132 617 -164 197 -100 133 -136 337 -172 133 -66 557 -98 951 -66 263 -130 587 -66 729 -196 335 -166 933 -432 369 -100 199 -296 225 -98 355 -66 129 -64 557 -98 289 -66 355 -128 193 -162 267 -134 299 -98 165 -170 303 -640 1031 -134 99 -66 135 -68 771 -166 171 -104 201 -134 131 -68 635 -428 661 -292 749 -430 1161 -100 905 -98 65 -98 657 -262 2837 -132 67 -66 265 -132 631 -66 1037 -296 97 -98 1703 -302 367 -100 505 -232 497 -362 333 -134 591 -100 755 -232 67 -130 587 -66 231 -168 65 -332 99 -66 267 -232 393 -134 65 -132 131 -428 133 -200 165 -202 199 -168 165 -102 269 -100 333 -852 201 -134 233 -202 65 -200 563 -768 265 -136 169 -102 169 -598 333 -202 267 -134 267 -328 163 -130 625 -500 199 -200 99 -270 65 -134 65 -198 65 -100 99 -596 493 -66 99 -66 331 -232 103 -136 373 -168 831 -170 65 -672 163 -102 133 -136 331 -100 333 -234 101 -100 99 -200 99 -100 201 -302 199 -600 301 -202 135 -134 705 -166 435 -530 97 -198 131 -198 195 -66 163 -392 293 -66 295 -370 229 -198 65 -100 405 -134 165 -134 133 -170 337 -236 205 -274 267 -134 329 -132 195 -132 503 -132 133 -136 133 -334 197 -196 299 -168 101 -100 233 -100 439 -134 301 -332 331 -298 433 -406 433 -68 167 -100 203 -100 101 -102 99 -328 397 -234 205 -168 133 -364 63 -202 397 -198 95 -394 267 -134 569 -66 201 -102 133 -136 101 -102 99 -132 99 -196 197 -498 197 -102 135 -170 -RAW_Data: 331 -164 63 -162 1267 -66 163 -130 129 -66 725 -164 231 -64 853 -66 101 -134 199 -102 99 -68 365 -66 357 -130 815 -64 357 -98 97 -98 97 -66 65 -466 231 -172 3749 -66 849 -130 917 -64 327 -64 1013 -98 555 -332 795 -100 571 -132 769 -132 401 -134 1297 -134 377 -138 435 -100 401 -100 667 -100 1761 -66 667 -66 1533 -236 233 -98 885 -130 457 -66 999 -66 165 -66 833 -134 695 -166 501 -66 499 -200 329 -64 197 -134 441 -100 2099 -98 491 -134 197 -130 2225 -132 65 -100 689 -64 193 -160 159 -96 195 -98 323 -164 259 -98 535 -472 771 -66 665 -270 665 -66 595 -266 2191 -64 643 -98 1287 -98 741 -100 233 -200 569 -194 261 -68 637 -100 97 -66 491 -158 395 -138 1017 -66 627 -262 559 -64 327 -98 263 -134 99 -102 201 -102 337 -66 167 -68 679 -100 471 -134 195 -66 133 -202 693 -96 197 -98 391 -164 99 -98 3883 -194 461 -100 237 -168 1891 -68 301 -68 969 -166 1439 -294 551 -130 389 -98 99 -196 167 -102 505 -66 569 -234 901 -98 407 -136 469 -66 769 -98 769 -166 1263 -266 297 -98 1701 -200 203 -168 329 -232 65 -100 329 -164 803 -100 135 -200 233 -166 135 -272 265 -134 197 -100 133 -134 539 -232 197 -396 165 -366 263 -68 233 -102 365 -132 233 -100 135 -266 199 -234 167 -232 97 -524 127 -128 389 -98 305 -364 261 -130 257 -162 589 -464 361 -66 229 -134 161 -100 203 -432 265 -66 199 -66 199 -366 229 -236 99 -134 99 -100 131 -168 133 -100 131 -236 267 -132 297 -264 291 -132 167 -234 65 -100 199 -66 333 -730 237 -440 365 -102 99 -100 99 -132 99 -100 1429 -134 427 -100 97 -100 131 -164 799 -170 1077 -100 431 -66 133 -168 737 -134 197 -230 65 -102 803 -132 491 -98 429 -198 471 -134 365 -66 299 -236 65 -66 2837 -102 399 -64 585 -64 523 -196 97 -98 295 -196 555 -160 261 -500 299 -396 333 -236 133 -68 327 -100 199 -204 699 -66 701 -100 65 -164 65 -370 195 -196 97 -66 193 -130 129 -360 195 -130 231 -96 291 -64 455 -228 293 -196 291 -162 97 -194 621 -130 847 -66 395 -66 161 -128 193 -130 293 -98 231 -170 67 -134 297 -360 167 -266 263 -526 263 -132 229 -98 191 -160 159 -100 721 -234 101 -100 99 -130 259 -258 265 -632 687 -164 133 -134 631 -100 199 -102 165 -560 299 -200 265 -332 431 -870 99 -266 503 -364 135 -66 269 -68 499 -100 265 -102 263 -102 569 -234 719 -132 99 -196 419 -262 163 -688 95 -66 165 -128 95 -66 -RAW_Data: 295 -98 987 -196 517 -100 489 -66 355 -132 563 -198 867 -134 1413 -134 541 -134 767 -100 193 -98 1799 -102 467 -134 299 -96 323 -66 261 -100 259 -66 229 -96 851 -66 369 -266 469 -66 101 -98 163 -136 267 -432 859 -130 523 -66 197 -134 1027 -132 227 -194 393 -98 807 -166 235 -100 133 -66 165 -102 133 -136 371 -162 1411 -132 865 -200 471 -100 133 -68 299 -66 633 -98 329 -234 401 -98 1505 -132 133 -134 331 -262 163 -66 261 -98 289 -64 201 -68 1055 -96 391 -66 951 -298 265 -202 297 -66 401 -68 131 -100 1733 -98 941 -66 803 -98 847 -64 3701 -100 721 -160 357 -166 1799 -66 329 -100 99 -102 363 -198 167 -136 197 -66 567 -66 199 -236 1247 -166 2455 -68 1107 -200 235 -100 2355 -130 913 -98 877 -98 163 -196 97 -66 427 -100 801 -134 867 -98 263 -68 441 -134 561 -98 1671 -134 865 -68 935 -132 163 -102 975 -66 1343 -132 1339 -134 369 -100 1107 -66 1167 -168 631 -232 835 -66 1027 -132 333 -166 265 -98 1207 -98 223 -98 455 -64 2095 -134 933 -136 233 -68 335 -136 305 -100 1737 -66 427 -100 263 -130 323 -66 227 -66 717 -100 265 -100 65 -128 355 -66 367 -132 95 -230 229 -100 131 -64 493 -132 291 -396 393 -130 259 -196 227 -288 397 -68 229 -430 99 -302 237 -700 65 -66 65 -100 133 -200 101 -336 133 -166 237 -202 67 -302 67 -68 333 -132 263 -102 267 -296 163 -166 233 -168 363 -64 295 -298 537 -166 431 -200 431 -166 63 -258 363 -164 563 -234 199 -68 299 -100 325 -754 295 -196 65 -98 165 -132 301 -134 131 -134 97 -68 405 -68 233 -134 271 -134 67 -168 101 -136 133 -366 99 -132 67 -132 265 -200 233 -100 201 -136 101 -66 263 -132 129 -66 293 -582 263 -132 1103 -134 203 -168 97 -66 197 -264 131 -168 133 -132 65 -134 199 -134 101 -100 131 -436 99 -232 97 -398 231 -362 65 -202 301 -396 297 -98 199 -134 265 -164 101 -168 267 -102 405 -170 99 -102 397 -132 97 -98 295 -98 1179 -100 135 -136 131 -134 765 -134 465 -168 439 -232 403 -100 65 -134 931 -100 169 -136 237 -68 231 -234 199 -68 401 -134 541 -166 429 -166 1607 -368 533 -66 363 -66 133 -134 433 -166 297 -238 201 -100 201 -170 199 -134 273 -136 99 -134 167 -238 133 -66 265 -134 165 -132 165 -132 97 -228 723 -198 415 -64 491 -298 257 -66 231 -192 225 -96 227 -98 193 -96 521 -198 65 -66 231 -166 163 -98 465 -66 133 -132 195 -130 225 -162 521 -130 63 -66 199 -228 -RAW_Data: 817 -162 449 -160 719 -198 469 -68 133 -68 1101 -132 593 -230 1105 -100 131 -134 231 -66 329 -196 685 -96 557 -68 1263 -68 101 -68 397 -100 65 -66 625 -66 97 -132 1099 -66 493 -66 757 -98 1151 -66 303 -134 1901 -66 99 -100 665 -262 991 -98 791 -66 1925 -168 865 -232 835 -98 505 -102 99 -100 535 -100 169 -134 427 -132 863 -68 167 -134 975 -100 133 -268 1339 -100 1453 -66 1445 -162 195 -64 3623 -66 237 -68 1063 -308 1449 -98 1111 -132 167 -102 855 -270 199 -134 297 -134 267 -168 863 -234 637 -66 567 -230 99 -200 3325 -198 845 -66 289 -66 131 -66 815 -130 1093 -100 167 -100 429 -98 1703 -166 195 -64 971 -98 163 -192 195 -168 439 -132 329 -132 67 -134 67 -134 1591 -168 407 -100 867 -68 399 -134 661 -100 663 -66 237 -136 395 -232 131 -66 695 -100 627 -264 913 -66 1083 -98 287 -66 199 -132 335 -100 1031 -68 99 -100 3815 -98 165 -66 129 -98 163 -128 563 -98 779 -96 223 -64 161 -164 2025 -66 1741 -172 101 -136 203 -102 665 -100 475 -64 167 -100 637 -98 997 -170 1207 -136 233 -166 233 -168 635 -132 199 -100 235 -270 199 -98 131 -102 169 -170 293 -98 323 -164 427 -334 233 -168 267 -68 369 -100 263 -368 101 -66 665 -98 265 -100 133 -100 99 -168 133 -66 133 -132 133 -66 269 -134 435 -68 267 -136 271 -500 163 -100 163 -166 355 -132 97 -98 323 -194 63 -688 463 -130 97 -396 65 -100 357 -194 461 -98 161 -130 223 -162 165 -352 461 -300 267 -166 233 -464 329 -100 293 -362 163 -228 289 -66 229 -66 195 -162 325 -66 261 -98 127 -424 299 -302 367 -68 265 -272 429 -98 161 -98 393 -296 65 -130 161 -196 261 -66 473 -234 97 -98 263 -160 323 -98 67 -132 697 -298 99 -134 233 -202 97 -134 301 -200 307 -100 101 -134 865 -166 231 -202 233 -100 301 -170 169 -102 169 -200 65 -98 595 -166 231 -234 661 -66 473 -334 165 -304 365 -266 97 -502 363 -134 133 -236 65 -100 99 -134 99 -170 235 -66 333 -100 195 -100 133 -300 133 -102 301 -304 65 -100 99 -100 131 -202 135 -134 65 -200 363 -66 263 -498 67 -68 295 -194 321 -368 435 -100 97 -664 99 -100 569 -66 133 -66 67 -134 199 -136 101 -68 301 -68 405 -198 133 -132 581 -132 165 -98 159 -98 197 -66 229 -130 131 -294 133 -96 423 -100 427 -300 357 -132 291 -64 95 -194 455 -98 263 -100 359 -196 65 -162 227 -162 157 -96 157 -230 589 -132 325 -134 535 -66 267 -100 135 -302 -RAW_Data: 131 -134 599 -166 393 -98 369 -236 197 -100 401 -232 569 -134 135 -70 337 -134 101 -136 135 -100 1895 -66 401 -170 503 -66 1633 -66 601 -66 355 -96 683 -100 729 -68 133 -132 433 -68 569 -100 133 -68 201 -132 835 -100 465 -68 527 -98 193 -200 1129 -166 535 -100 199 -98 259 -132 227 -64 1597 -98 261 -192 753 -100 911 -66 667 -298 131 -100 263 -66 1051 -230 787 -66 935 -66 233 -98 885 -236 431 -66 197 -162 521 -68 167 -196 263 -96 589 -98 517 -66 1439 -64 777 -66 3219 -132 679 -134 205 -68 507 -198 749 -200 199 -168 167 -100 133 -134 201 -68 731 -66 495 -198 737 -66 237 -68 135 -100 167 -234 1535 -68 873 -66 373 -66 67 -232 297 -68 65 -66 1095 -68 327 -130 63 -132 1715 -66 2261 -100 321 -132 197 -164 457 -232 1291 -132 405 -68 1001 -68 1133 -272 471 -66 99 -134 1403 -68 167 -68 1091 -336 933 -134 1207 -132 265 -68 267 -66 99 -366 265 -66 1469 -258 367 -168 429 -132 129 -66 491 -132 343 -100 65 -100 263 -136 199 -164 273 -204 791 -100 901 -66 167 -98 165 -64 559 -132 619 -132 1087 -128 2283 -398 1467 -164 259 -130 1927 -130 421 -98 1085 -66 705 -68 1843 -168 875 -170 203 -136 341 -640 199 -66 133 -554 161 -196 63 -66 521 -292 163 -160 95 -158 127 -192 197 -100 587 -130 397 -662 261 -66 193 -130 259 -66 361 -64 459 -98 197 -560 655 -130 389 -66 1135 -100 133 -130 131 -98 1011 -100 561 -66 685 -164 457 -132 2469 -200 609 -66 665 -66 67 -132 327 -200 1657 -134 919 -132 651 -100 327 -230 191 -130 263 -358 95 -130 549 -98 99 -68 299 -100 461 -132 99 -472 165 -134 99 -66 99 -132 399 -102 169 -102 697 -166 233 -132 333 -632 197 -164 865 -266 101 -68 533 -166 299 -100 163 -228 259 -66 327 -200 65 -66 229 -100 363 -230 197 -336 165 -102 893 -300 65 -132 231 -370 265 -230 99 -98 229 -518 199 -100 401 -724 225 -98 63 -96 231 -64 291 -292 65 -98 131 -98 159 -158 127 -194 161 -292 65 -98 133 -66 297 -66 303 -168 97 -168 231 -234 269 -532 135 -168 99 -168 301 -528 99 -506 199 -368 399 -132 329 -372 99 -68 133 -264 197 -100 201 -200 67 -134 131 -270 133 -134 133 -198 327 -200 65 -100 331 -262 161 -166 469 -534 167 -738 131 -100 367 -232 101 -100 265 -604 65 -170 99 -166 299 -102 169 -132 99 -398 229 -330 197 -166 335 -366 97 -98 131 -200 269 -100 199 -168 131 -134 537 -98 265 -100 335 -236 99 -366 -RAW_Data: 459 -100 453 -130 419 -130 519 -96 63 -130 2077 -66 767 -64 127 -134 1961 -296 529 -202 637 -134 527 -100 201 -68 633 -66 163 -360 1029 -68 765 -100 867 -66 503 -100 131 -66 841 -98 165 -68 237 -66 509 -100 501 -302 235 -66 99 -164 227 -130 551 -196 327 -66 1571 -132 99 -68 867 -66 163 -96 161 -130 129 -130 549 -130 487 -166 1801 -66 229 -66 197 -232 325 -66 425 -198 131 -64 295 -166 735 -66 533 -98 227 -130 129 -262 425 -100 263 -66 129 -132 97 -168 971 -170 405 -68 199 -134 475 -202 297 -98 1445 -98 395 -196 161 -66 225 -134 1803 -100 473 -102 1499 -66 199 -100 701 -132 165 -68 133 -102 303 -98 735 -102 805 -100 827 -100 235 -100 65 -266 637 -68 693 -66 1383 -228 819 -66 233 -304 435 -198 203 -136 1135 -270 1709 -64 227 -64 581 -134 505 -66 2203 -64 293 -64 753 -66 551 -132 747 -64 1303 -64 463 -66 229 -102 1877 -266 871 -166 1357 -64 819 -66 465 -198 693 -68 165 -64 95 -128 3785 -132 1465 -100 299 -102 329 -164 595 -134 1029 -66 299 -168 1263 -166 331 -68 967 -100 101 -102 603 -260 165 -132 467 -66 233 -66 235 -102 475 -100 135 -68 301 -134 297 -98 131 -102 269 -466 99 -134 237 -166 135 -168 203 -102 265 -68 503 -66 233 -66 637 -134 101 -200 199 -166 293 -554 361 -328 367 -264 533 -238 167 -68 135 -170 99 -300 591 -298 133 -236 299 -66 231 -368 263 -232 435 -136 133 -102 133 -200 133 -134 163 -134 167 -168 299 -66 265 -100 133 -240 135 -132 263 -170 269 -200 501 -396 263 -98 227 -132 129 -292 427 -66 165 -102 627 -602 99 -66 301 -168 199 -100 563 -330 165 -134 233 -136 65 -332 499 -100 131 -232 325 -96 65 -132 195 -98 393 -624 323 -68 133 -98 195 -162 231 -100 263 -132 231 -102 133 -236 99 -236 231 -166 65 -102 133 -268 101 -102 299 -136 267 -164 493 -64 229 -258 291 -326 263 -198 391 -134 167 -202 365 -594 133 -102 201 -134 503 -396 429 -204 169 -400 197 -170 267 -132 403 -466 297 -98 469 -234 395 -132 233 -100 165 -100 165 -66 197 -68 297 -166 501 -134 133 -100 65 -166 631 -68 297 -134 199 -100 165 -68 299 -266 133 -66 165 -100 231 -490 557 -134 371 -164 299 -170 733 -164 239 -334 335 -66 299 -300 199 -170 103 -100 233 -102 641 -168 65 -100 995 -66 265 -160 259 -130 129 -226 425 -100 355 -726 97 -688 99 -66 233 -266 299 -942 167 -102 167 -166 65 -100 367 -136 99 -134 199 -134 267 -164 -RAW_Data: 67 -68 233 -66 899 -66 163 -96 485 -98 355 -130 943 -100 235 -168 499 -104 1367 -98 297 -100 635 -68 1169 -100 67 -134 835 -264 959 -164 129 -98 419 -196 589 -66 421 -66 1717 -100 133 -100 265 -134 227 -356 455 -166 163 -66 1055 -100 1455 -134 463 -98 2191 -132 295 -132 335 -66 709 -64 619 -98 959 -68 835 -170 603 -134 1033 -134 635 -168 759 -232 397 -198 397 -164 1267 -166 257 -198 1295 -100 239 -104 563 -204 335 -198 203 -68 901 -68 1255 -134 1697 -66 793 -66 1691 -68 201 -100 765 -66 165 -132 131 -230 131 -66 917 -66 335 -338 231 -170 827 -98 199 -136 301 -196 65 -98 199 -200 765 -134 403 -98 333 -68 1691 -132 2565 -64 569 -170 1255 -264 65 -132 1243 -132 2527 -66 259 -66 1739 -100 1309 -198 167 -238 337 -66 131 -68 1973 -362 299 -100 1387 -96 129 -164 423 -230 3875 -96 4283 -98 165 -98 515 -134 469 -68 171 -102 1163 -100 65 -298 461 -66 367 -136 205 -168 371 -98 491 -164 161 -262 1093 -100 299 -100 269 -334 1205 -98 63 -98 261 -64 457 -98 diff --git a/lib/subghz/protocols/oregon2.h b/lib/subghz/protocols/oregon2.h deleted file mode 100644 index 981b2599974..00000000000 --- a/lib/subghz/protocols/oregon2.h +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once - -#include "base.h" -#define SUBGHZ_PROTOCOL_OREGON2_NAME "Oregon2" -extern const SubGhzProtocol subghz_protocol_oregon2; diff --git a/lib/subghz/protocols/protocol_items.c b/lib/subghz/protocols/protocol_items.c index 65ba0328068..ebf9b93e422 100644 --- a/lib/subghz/protocols/protocol_items.c +++ b/lib/subghz/protocols/protocol_items.c @@ -12,7 +12,8 @@ const SubGhzProtocol* subghz_protocol_registry_items[] = { &subghz_protocol_chamb_code, &subghz_protocol_power_smart, &subghz_protocol_marantec, &subghz_protocol_bett, &subghz_protocol_doitrand, &subghz_protocol_phoenix_v2, &subghz_protocol_honeywell_wdb, &subghz_protocol_magellan, &subghz_protocol_intertechno_v3, - &subghz_protocol_clemsa, &subghz_protocol_oregon2}; + &subghz_protocol_clemsa, +}; const SubGhzProtocolRegistry subghz_protocol_registry = { .items = subghz_protocol_registry_items, diff --git a/lib/subghz/protocols/protocol_items.h b/lib/subghz/protocols/protocol_items.h index 9c187e795a1..23aa71be034 100644 --- a/lib/subghz/protocols/protocol_items.h +++ b/lib/subghz/protocols/protocol_items.h @@ -35,6 +35,5 @@ #include "magellan.h" #include "intertechno_v3.h" #include "clemsa.h" -#include "oregon2.h" extern const SubGhzProtocolRegistry subghz_protocol_registry; From 984d89c6d072a9c4c1955299b46f793ddf69cdd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Mon, 24 Oct 2022 19:50:34 +0900 Subject: [PATCH 167/824] Furi: smaller crash routine (#1912) * Furi: smaller crash routine * Furi: small fixes * Furi: cleanup check routines, more assembly code, force inline of __furi_halt_mcu * SubGhz: cleanup residual line mess * Documentation * Dap-link: fix help * Furi: replace __furi_halt_mcu with HALT_MCU macros * Furi: disable IRQ earlier in crash handler * Furi: properly handle masked mode when detecting ISR * Ble: allow 0 length feed in rpc_session_feed * Format sources * Furi: better crash logic explanation. * Furi: some grammar in check.h Co-authored-by: SG --- .../dap_link/gui/scenes/dap_scene_help.c | 8 +-- applications/services/rpc/rpc.c | 3 +- firmware/targets/f7/api_symbols.csv | 3 +- furi/core/check.c | 65 +++++++++---------- furi/core/check.h | 60 ++++++++++------- 5 files changed, 74 insertions(+), 65 deletions(-) diff --git a/applications/plugins/dap_link/gui/scenes/dap_scene_help.c b/applications/plugins/dap_link/gui/scenes/dap_scene_help.c index 7193f4f4b9c..d8d70e7ff29 100644 --- a/applications/plugins/dap_link/gui/scenes/dap_scene_help.c +++ b/applications/plugins/dap_link/gui/scenes/dap_scene_help.c @@ -72,13 +72,13 @@ void dap_scene_help_on_enter(void* context) { if(config->uart_swap == DapUartTXRXNormal) { furi_string_cat( string, - " TX: 15 [С1]\r\n" - " RX: 16 [С0]\r\n"); + " TX: 15 [C1]\r\n" + " RX: 16 [C0]\r\n"); } else { furi_string_cat( string, - " RX: 15 [С1]\r\n" - " TX: 16 [С0]\r\n"); + " RX: 15 [C1]\r\n" + " TX: 16 [C0]\r\n"); } break; default: diff --git a/applications/services/rpc/rpc.c b/applications/services/rpc/rpc.c index 06c05173c39..73eaadfb15a 100644 --- a/applications/services/rpc/rpc.c +++ b/applications/services/rpc/rpc.c @@ -148,7 +148,8 @@ size_t rpc_session_feed(RpcSession* session, uint8_t* encoded_bytes, size_t size, TickType_t timeout) { furi_assert(session); furi_assert(encoded_bytes); - furi_assert(size > 0); + + if(!size) return 0; size_t bytes_sent = furi_stream_buffer_send(session->stream, encoded_bytes, size, timeout); diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 188cd74846b..4c48d9abd50 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,4.0,, +Version,+,5.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -2843,7 +2843,6 @@ Variable,+,I_update_10px,const Icon, Variable,-,MSIRangeTable,const uint32_t[16], Variable,-,SmpsPrescalerTable,const uint32_t[4][6], Variable,+,SystemCoreClock,uint32_t, -Variable,+,__furi_check_message,const char*, Variable,+,_ctype_,const char[], Variable,+,_global_impure_ptr,_reent*, Variable,+,_impure_ptr,_reent*, diff --git a/furi/core/check.c b/furi/core/check.c index ed38038a53f..00c20575af0 100644 --- a/furi/core/check.c +++ b/furi/core/check.c @@ -14,6 +14,28 @@ PLACE_IN_SECTION("MB_MEM2") const char* __furi_check_message = NULL; PLACE_IN_SECTION("MB_MEM2") uint32_t __furi_check_registers[12] = {0}; +/** Load r12 value to __furi_check_message and store registers to __furi_check_registers */ +#define GET_MESSAGE_AND_STORE_REGISTERS() \ + asm volatile("ldr r11, =__furi_check_message \n" \ + "str r12, [r11] \n" \ + "ldr r12, =__furi_check_registers \n" \ + "stm r12, {r0-r11} \n" \ + : \ + : \ + : "memory"); + +// Restore registers and halt MCU +#define RESTORE_REGISTERS_AND_HALT_MCU() \ + asm volatile("ldr r12, =__furi_check_registers \n" \ + "ldm r12, {r0-r11} \n" \ + "loop%=: \n" \ + "bkpt 0x00 \n" \ + "wfi \n" \ + "b loop%= \n" \ + : \ + : \ + : "memory"); + extern size_t xPortGetTotalHeapSize(void); extern size_t xPortGetFreeHeapSize(void); extern size_t xPortGetMinimumEverFreeHeapSize(void); @@ -55,32 +77,11 @@ static void __furi_print_name(bool isr) { } } -static FURI_NORETURN void __furi_halt_mcu() { - register const void* r12 asm ("r12") = (void*)__furi_check_registers; - asm volatile( - "ldm r12, {r0-r11} \n" -#ifdef FURI_DEBUG - "bkpt 0x00 \n" -#endif - "loop%=: \n" - "wfi \n" - "b loop%= \n" - : - : "r" (r12) - : "memory"); - __builtin_unreachable(); -} - FURI_NORETURN void __furi_crash() { - register const void* r12 asm ("r12") = (void*)__furi_check_registers; - asm volatile( - "stm r12, {r0-r11} \n" - : - : "r" (r12) - : "memory"); - - bool isr = FURI_IS_ISR(); __disable_irq(); + GET_MESSAGE_AND_STORE_REGISTERS(); + + bool isr = FURI_IS_IRQ_MODE(); if(__furi_check_message == NULL) { __furi_check_message = "Fatal Error"; @@ -98,7 +99,7 @@ FURI_NORETURN void __furi_crash() { #ifdef FURI_DEBUG furi_hal_console_puts("\r\nSystem halted. Connect debugger for more info\r\n"); furi_hal_console_puts("\033[0m\r\n"); - __furi_halt_mcu(); + RESTORE_REGISTERS_AND_HALT_MCU(); #else furi_hal_rtc_set_fault_data((uint32_t)__furi_check_message); furi_hal_console_puts("\r\nRebooting system.\r\n"); @@ -109,15 +110,10 @@ FURI_NORETURN void __furi_crash() { } FURI_NORETURN void __furi_halt() { - register const void* r12 asm ("r12") = (void*)__furi_check_registers; - asm volatile( - "stm r12, {r0-r11} \n" - : - : "r" (r12) - : "memory"); - - bool isr = FURI_IS_ISR(); __disable_irq(); + GET_MESSAGE_AND_STORE_REGISTERS(); + + bool isr = FURI_IS_IRQ_MODE(); if(__furi_check_message == NULL) { __furi_check_message = "System halt requested."; @@ -128,5 +124,6 @@ FURI_NORETURN void __furi_halt() { furi_hal_console_puts(__furi_check_message); furi_hal_console_puts("\r\nSystem halted. Bye-bye!\r\n"); furi_hal_console_puts("\033[0m\r\n"); - __furi_halt_mcu(); + RESTORE_REGISTERS_AND_HALT_MCU(); + __builtin_unreachable(); } diff --git a/furi/core/check.h b/furi/core/check.h index e77891f7074..78efc145190 100644 --- a/furi/core/check.h +++ b/furi/core/check.h @@ -1,3 +1,16 @@ +/** + * @file check.h + * + * Furi crash and assert functions. + * + * The main problem with crashing is that you can't do anything without disturbing registers, + * and if you disturb registers, you won't be able to see the correct register values in the debugger. + * + * Current solution works around it by passing the message through r12 and doing some magic with registers in crash function. + * r0-r10 are stored in the ram2 on crash routine start and restored at the end. + * The only register that is going to be lost is r11. + * + */ #pragma once #ifdef __cplusplus @@ -8,9 +21,6 @@ extern "C" { #define FURI_NORETURN noreturn #endif -/** Pointer to pass message to __furi_crash and __furi_halt */ -extern const char* __furi_check_message; - /** Crash system */ FURI_NORETURN void __furi_crash(); @@ -18,39 +28,41 @@ FURI_NORETURN void __furi_crash(); FURI_NORETURN void __furi_halt(); /** Crash system with message. Show message after reboot. */ -#define furi_crash(message) \ - do { \ - __furi_check_message = message; \ - __furi_crash(); \ +#define furi_crash(message) \ + do { \ + register const void* r12 asm("r12") = (void*)message; \ + asm volatile("sukima%=:" : : "r"(r12)); \ + __furi_crash(); \ } while(0) /** Halt system with message. */ -#define furi_halt(message) \ - do { \ - __furi_check_message = message; \ - __furi_halt(); \ +#define furi_halt(message) \ + do { \ + register const void* r12 asm("r12") = (void*)message; \ + asm volatile("sukima%=:" : : "r"(r12)); \ + __furi_halt(); \ } while(0) /** Check condition and crash if check failed */ -#define furi_check(__e) \ - do { \ - if ((__e) == 0) { \ - furi_crash("furi_check failed\r\n"); \ - } \ +#define furi_check(__e) \ + do { \ + if((__e) == 0) { \ + furi_crash("furi_check failed\r\n"); \ + } \ } while(0) /** Only in debug build: Assert condition and crash if assert failed */ #ifdef FURI_DEBUG -#define furi_assert(__e) \ - do { \ - if ((__e) == 0) { \ - furi_crash("furi_assert failed\r\n"); \ - } \ +#define furi_assert(__e) \ + do { \ + if((__e) == 0) { \ + furi_crash("furi_assert failed\r\n"); \ + } \ } while(0) #else -#define furi_assert(__e) \ - do { \ - ((void)(__e)); \ +#define furi_assert(__e) \ + do { \ + ((void)(__e)); \ } while(0) #endif From 0adad32418decc8d19c39e80ce5c42795d141b1e Mon Sep 17 00:00:00 2001 From: hedger Date: Wed, 26 Oct 2022 02:15:02 +0400 Subject: [PATCH 168/824] fbt fixes pt4 (#1899) * fbt: fixed py scripts for gdb * fbt: removed compiled dolphin assets from tracked files; resolved cached dependency issues by globally disabling deps cache; changed dependency tracking for dolphin assets * fbt: fix for "resources" node lookup * toolchain: bump to v.16 with scons + x64 win binaries * fbt: using scons from toolchain * vscode: fixed paths for 64-bit Windows toolchain * fbt: added colors! * fbt: moved import validator to ansi lib coloring * fbt: moved COMSTR vars to tools * fbt: custom action for fap dist * fbt: added OPENOCD_ADAPTER_SERIAL configuration var for openocd operations * fbt: added get_stlink target * docs: details on libs for faps * vscode: added DAP config for using Flipper as a debugger for a 2nd Flipper * fbt: blind deps fix for sdk_origin * fbt: sdk: moved deployment actions to pure python * Github: disable disableLicenseExpirationCheck option for pvs Co-authored-by: Aleksandr Kutuzov --- .github/workflows/pvs_studio.yml | 1 - .gitmodules | 3 - .vscode/example/c_cpp_properties.json | 2 +- .vscode/example/launch.json | 19 +++ .vscode/example/settings.json | 6 +- SConstruct | 16 ++- assets/.gitignore | 3 +- assets/SConscript | 6 +- .../dolphin/L1_Boxing_128x64/frame_0.bm | Bin 481 -> 0 bytes .../dolphin/L1_Boxing_128x64/frame_1.bm | Bin 461 -> 0 bytes .../dolphin/L1_Boxing_128x64/frame_2.bm | Bin 531 -> 0 bytes .../dolphin/L1_Boxing_128x64/frame_3.bm | Bin 437 -> 0 bytes .../dolphin/L1_Boxing_128x64/frame_4.bm | Bin 459 -> 0 bytes .../dolphin/L1_Boxing_128x64/frame_5.bm | Bin 450 -> 0 bytes .../dolphin/L1_Boxing_128x64/frame_6.bm | Bin 442 -> 0 bytes .../dolphin/L1_Boxing_128x64/meta.txt | 32 ----- .../dolphin/L1_Cry_128x64/frame_0.bm | Bin 889 -> 0 bytes .../dolphin/L1_Cry_128x64/frame_1.bm | Bin 911 -> 0 bytes .../dolphin/L1_Cry_128x64/frame_2.bm | Bin 910 -> 0 bytes .../dolphin/L1_Cry_128x64/frame_3.bm | Bin 923 -> 0 bytes .../dolphin/L1_Cry_128x64/frame_4.bm | Bin 894 -> 0 bytes .../dolphin/L1_Cry_128x64/frame_5.bm | Bin 940 -> 0 bytes .../dolphin/L1_Cry_128x64/frame_6.bm | Bin 915 -> 0 bytes .../dolphin/L1_Cry_128x64/frame_7.bm | Bin 934 -> 0 bytes .../resources/dolphin/L1_Cry_128x64/meta.txt | 41 ------ .../dolphin/L1_Furippa1_128x64/frame_0.bm | Bin 294 -> 0 bytes .../dolphin/L1_Furippa1_128x64/frame_1.bm | Bin 325 -> 0 bytes .../dolphin/L1_Furippa1_128x64/frame_10.bm | Bin 465 -> 0 bytes .../dolphin/L1_Furippa1_128x64/frame_11.bm | Bin 698 -> 0 bytes .../dolphin/L1_Furippa1_128x64/frame_12.bm | Bin 541 -> 0 bytes .../dolphin/L1_Furippa1_128x64/frame_13.bm | Bin 584 -> 0 bytes .../dolphin/L1_Furippa1_128x64/frame_14.bm | Bin 610 -> 0 bytes .../dolphin/L1_Furippa1_128x64/frame_15.bm | Bin 719 -> 0 bytes .../dolphin/L1_Furippa1_128x64/frame_16.bm | Bin 458 -> 0 bytes .../dolphin/L1_Furippa1_128x64/frame_17.bm | Bin 400 -> 0 bytes .../dolphin/L1_Furippa1_128x64/frame_18.bm | Bin 333 -> 0 bytes .../dolphin/L1_Furippa1_128x64/frame_2.bm | Bin 351 -> 0 bytes .../dolphin/L1_Furippa1_128x64/frame_3.bm | Bin 324 -> 0 bytes .../dolphin/L1_Furippa1_128x64/frame_4.bm | Bin 387 -> 0 bytes .../dolphin/L1_Furippa1_128x64/frame_5.bm | Bin 390 -> 0 bytes .../dolphin/L1_Furippa1_128x64/frame_6.bm | Bin 407 -> 0 bytes .../dolphin/L1_Furippa1_128x64/frame_7.bm | Bin 294 -> 0 bytes .../dolphin/L1_Furippa1_128x64/frame_8.bm | Bin 283 -> 0 bytes .../dolphin/L1_Furippa1_128x64/frame_9.bm | Bin 312 -> 0 bytes .../dolphin/L1_Furippa1_128x64/meta.txt | 14 -- .../dolphin/L1_Laptop_128x51/frame_0.bm | Bin 555 -> 0 bytes .../dolphin/L1_Laptop_128x51/frame_1.bm | Bin 557 -> 0 bytes .../dolphin/L1_Laptop_128x51/frame_2.bm | Bin 560 -> 0 bytes .../dolphin/L1_Laptop_128x51/frame_3.bm | Bin 556 -> 0 bytes .../dolphin/L1_Laptop_128x51/frame_4.bm | Bin 560 -> 0 bytes .../dolphin/L1_Laptop_128x51/frame_5.bm | Bin 554 -> 0 bytes .../dolphin/L1_Laptop_128x51/frame_6.bm | Bin 553 -> 0 bytes .../dolphin/L1_Laptop_128x51/frame_7.bm | Bin 560 -> 0 bytes .../dolphin/L1_Laptop_128x51/meta.txt | 32 ----- .../dolphin/L1_Leaving_sad_128x64/frame_0.bm | Bin 514 -> 0 bytes .../dolphin/L1_Leaving_sad_128x64/frame_1.bm | Bin 526 -> 0 bytes .../dolphin/L1_Leaving_sad_128x64/frame_10.bm | Bin 316 -> 0 bytes .../dolphin/L1_Leaving_sad_128x64/frame_11.bm | Bin 294 -> 0 bytes .../dolphin/L1_Leaving_sad_128x64/frame_12.bm | Bin 322 -> 0 bytes .../dolphin/L1_Leaving_sad_128x64/frame_2.bm | Bin 542 -> 0 bytes .../dolphin/L1_Leaving_sad_128x64/frame_3.bm | Bin 557 -> 0 bytes .../dolphin/L1_Leaving_sad_128x64/frame_4.bm | Bin 488 -> 0 bytes .../dolphin/L1_Leaving_sad_128x64/frame_5.bm | Bin 469 -> 0 bytes .../dolphin/L1_Leaving_sad_128x64/frame_6.bm | Bin 499 -> 0 bytes .../dolphin/L1_Leaving_sad_128x64/frame_7.bm | Bin 486 -> 0 bytes .../dolphin/L1_Leaving_sad_128x64/frame_8.bm | Bin 403 -> 0 bytes .../dolphin/L1_Leaving_sad_128x64/frame_9.bm | Bin 317 -> 0 bytes .../dolphin/L1_Leaving_sad_128x64/meta.txt | 32 ----- .../dolphin/L1_Mad_fist_128x64/frame_0.bm | Bin 520 -> 0 bytes .../dolphin/L1_Mad_fist_128x64/frame_1.bm | Bin 540 -> 0 bytes .../dolphin/L1_Mad_fist_128x64/frame_10.bm | Bin 542 -> 0 bytes .../dolphin/L1_Mad_fist_128x64/frame_11.bm | Bin 505 -> 0 bytes .../dolphin/L1_Mad_fist_128x64/frame_12.bm | Bin 501 -> 0 bytes .../dolphin/L1_Mad_fist_128x64/frame_13.bm | Bin 500 -> 0 bytes .../dolphin/L1_Mad_fist_128x64/frame_2.bm | Bin 515 -> 0 bytes .../dolphin/L1_Mad_fist_128x64/frame_3.bm | Bin 538 -> 0 bytes .../dolphin/L1_Mad_fist_128x64/frame_4.bm | Bin 512 -> 0 bytes .../dolphin/L1_Mad_fist_128x64/frame_5.bm | Bin 519 -> 0 bytes .../dolphin/L1_Mad_fist_128x64/frame_6.bm | Bin 524 -> 0 bytes .../dolphin/L1_Mad_fist_128x64/frame_7.bm | Bin 515 -> 0 bytes .../dolphin/L1_Mad_fist_128x64/frame_8.bm | Bin 517 -> 0 bytes .../dolphin/L1_Mad_fist_128x64/frame_9.bm | Bin 526 -> 0 bytes .../dolphin/L1_Mad_fist_128x64/meta.txt | 23 ---- .../dolphin/L1_Painting_128x64/frame_0.bm | Bin 763 -> 0 bytes .../dolphin/L1_Painting_128x64/frame_1.bm | Bin 764 -> 0 bytes .../dolphin/L1_Painting_128x64/frame_10.bm | Bin 772 -> 0 bytes .../dolphin/L1_Painting_128x64/frame_11.bm | Bin 767 -> 0 bytes .../dolphin/L1_Painting_128x64/frame_2.bm | Bin 762 -> 0 bytes .../dolphin/L1_Painting_128x64/frame_3.bm | Bin 759 -> 0 bytes .../dolphin/L1_Painting_128x64/frame_4.bm | Bin 759 -> 0 bytes .../dolphin/L1_Painting_128x64/frame_5.bm | Bin 757 -> 0 bytes .../dolphin/L1_Painting_128x64/frame_6.bm | Bin 785 -> 0 bytes .../dolphin/L1_Painting_128x64/frame_7.bm | Bin 803 -> 0 bytes .../dolphin/L1_Painting_128x64/frame_8.bm | Bin 797 -> 0 bytes .../dolphin/L1_Painting_128x64/frame_9.bm | Bin 777 -> 0 bytes .../dolphin/L1_Painting_128x64/meta.txt | 32 ----- .../dolphin/L1_Read_books_128x64/frame_0.bm | Bin 653 -> 0 bytes .../dolphin/L1_Read_books_128x64/frame_1.bm | Bin 653 -> 0 bytes .../dolphin/L1_Read_books_128x64/frame_2.bm | Bin 650 -> 0 bytes .../dolphin/L1_Read_books_128x64/frame_3.bm | Bin 646 -> 0 bytes .../dolphin/L1_Read_books_128x64/frame_4.bm | Bin 650 -> 0 bytes .../dolphin/L1_Read_books_128x64/frame_5.bm | Bin 652 -> 0 bytes .../dolphin/L1_Read_books_128x64/frame_6.bm | Bin 646 -> 0 bytes .../dolphin/L1_Read_books_128x64/frame_7.bm | Bin 647 -> 0 bytes .../dolphin/L1_Read_books_128x64/frame_8.bm | Bin 643 -> 0 bytes .../dolphin/L1_Read_books_128x64/meta.txt | 23 ---- .../dolphin/L1_Recording_128x51/frame_0.bm | Bin 663 -> 0 bytes .../dolphin/L1_Recording_128x51/frame_1.bm | Bin 657 -> 0 bytes .../dolphin/L1_Recording_128x51/frame_10.bm | Bin 629 -> 0 bytes .../dolphin/L1_Recording_128x51/frame_11.bm | Bin 659 -> 0 bytes .../dolphin/L1_Recording_128x51/frame_2.bm | Bin 628 -> 0 bytes .../dolphin/L1_Recording_128x51/frame_3.bm | Bin 654 -> 0 bytes .../dolphin/L1_Recording_128x51/frame_4.bm | Bin 662 -> 0 bytes .../dolphin/L1_Recording_128x51/frame_5.bm | Bin 622 -> 0 bytes .../dolphin/L1_Recording_128x51/frame_6.bm | Bin 664 -> 0 bytes .../dolphin/L1_Recording_128x51/frame_7.bm | Bin 626 -> 0 bytes .../dolphin/L1_Recording_128x51/frame_8.bm | Bin 663 -> 0 bytes .../dolphin/L1_Recording_128x51/frame_9.bm | Bin 661 -> 0 bytes .../dolphin/L1_Recording_128x51/meta.txt | 14 -- .../dolphin/L1_Sleep_128x64/frame_0.bm | Bin 580 -> 0 bytes .../dolphin/L1_Sleep_128x64/frame_1.bm | Bin 589 -> 0 bytes .../dolphin/L1_Sleep_128x64/frame_2.bm | Bin 582 -> 0 bytes .../dolphin/L1_Sleep_128x64/frame_3.bm | Bin 597 -> 0 bytes .../dolphin/L1_Sleep_128x64/meta.txt | 41 ------ .../dolphin/L1_Waves_128x50/frame_0.bm | Bin 443 -> 0 bytes .../dolphin/L1_Waves_128x50/frame_1.bm | Bin 448 -> 0 bytes .../dolphin/L1_Waves_128x50/frame_2.bm | Bin 463 -> 0 bytes .../dolphin/L1_Waves_128x50/frame_3.bm | Bin 472 -> 0 bytes .../dolphin/L1_Waves_128x50/meta.txt | 50 -------- .../dolphin/L2_Furippa2_128x64/frame_0.bm | Bin 350 -> 0 bytes .../dolphin/L2_Furippa2_128x64/frame_1.bm | Bin 385 -> 0 bytes .../dolphin/L2_Furippa2_128x64/frame_10.bm | Bin 465 -> 0 bytes .../dolphin/L2_Furippa2_128x64/frame_11.bm | Bin 698 -> 0 bytes .../dolphin/L2_Furippa2_128x64/frame_12.bm | Bin 541 -> 0 bytes .../dolphin/L2_Furippa2_128x64/frame_13.bm | Bin 584 -> 0 bytes .../dolphin/L2_Furippa2_128x64/frame_14.bm | Bin 610 -> 0 bytes .../dolphin/L2_Furippa2_128x64/frame_15.bm | Bin 740 -> 0 bytes .../dolphin/L2_Furippa2_128x64/frame_16.bm | Bin 533 -> 0 bytes .../dolphin/L2_Furippa2_128x64/frame_17.bm | Bin 451 -> 0 bytes .../dolphin/L2_Furippa2_128x64/frame_18.bm | Bin 397 -> 0 bytes .../dolphin/L2_Furippa2_128x64/frame_2.bm | Bin 402 -> 0 bytes .../dolphin/L2_Furippa2_128x64/frame_3.bm | Bin 374 -> 0 bytes .../dolphin/L2_Furippa2_128x64/frame_4.bm | Bin 440 -> 0 bytes .../dolphin/L2_Furippa2_128x64/frame_5.bm | Bin 449 -> 0 bytes .../dolphin/L2_Furippa2_128x64/frame_6.bm | Bin 466 -> 0 bytes .../dolphin/L2_Furippa2_128x64/frame_7.bm | Bin 350 -> 0 bytes .../dolphin/L2_Furippa2_128x64/frame_8.bm | Bin 319 -> 0 bytes .../dolphin/L2_Furippa2_128x64/frame_9.bm | Bin 317 -> 0 bytes .../dolphin/L2_Furippa2_128x64/meta.txt | 14 -- .../dolphin/L2_Hacking_pc_128x64/frame_0.bm | Bin 543 -> 0 bytes .../dolphin/L2_Hacking_pc_128x64/frame_1.bm | Bin 545 -> 0 bytes .../dolphin/L2_Hacking_pc_128x64/frame_2.bm | Bin 548 -> 0 bytes .../dolphin/L2_Hacking_pc_128x64/frame_3.bm | Bin 608 -> 0 bytes .../dolphin/L2_Hacking_pc_128x64/frame_4.bm | Bin 609 -> 0 bytes .../dolphin/L2_Hacking_pc_128x64/meta.txt | 32 ----- .../dolphin/L2_Soldering_128x64/frame_0.bm | Bin 699 -> 0 bytes .../dolphin/L2_Soldering_128x64/frame_1.bm | Bin 688 -> 0 bytes .../dolphin/L2_Soldering_128x64/frame_10.bm | Bin 699 -> 0 bytes .../dolphin/L2_Soldering_128x64/frame_2.bm | Bin 689 -> 0 bytes .../dolphin/L2_Soldering_128x64/frame_3.bm | Bin 689 -> 0 bytes .../dolphin/L2_Soldering_128x64/frame_4.bm | Bin 693 -> 0 bytes .../dolphin/L2_Soldering_128x64/frame_5.bm | Bin 696 -> 0 bytes .../dolphin/L2_Soldering_128x64/frame_6.bm | Bin 712 -> 0 bytes .../dolphin/L2_Soldering_128x64/frame_7.bm | Bin 732 -> 0 bytes .../dolphin/L2_Soldering_128x64/frame_8.bm | Bin 705 -> 0 bytes .../dolphin/L2_Soldering_128x64/frame_9.bm | Bin 698 -> 0 bytes .../dolphin/L2_Soldering_128x64/meta.txt | 23 ---- .../dolphin/L3_Furippa3_128x64/frame_0.bm | Bin 398 -> 0 bytes .../dolphin/L3_Furippa3_128x64/frame_1.bm | Bin 438 -> 0 bytes .../dolphin/L3_Furippa3_128x64/frame_10.bm | Bin 559 -> 0 bytes .../dolphin/L3_Furippa3_128x64/frame_11.bm | Bin 728 -> 0 bytes .../dolphin/L3_Furippa3_128x64/frame_12.bm | Bin 541 -> 0 bytes .../dolphin/L3_Furippa3_128x64/frame_13.bm | Bin 584 -> 0 bytes .../dolphin/L3_Furippa3_128x64/frame_14.bm | Bin 610 -> 0 bytes .../dolphin/L3_Furippa3_128x64/frame_15.bm | Bin 741 -> 0 bytes .../dolphin/L3_Furippa3_128x64/frame_16.bm | Bin 559 -> 0 bytes .../dolphin/L3_Furippa3_128x64/frame_17.bm | Bin 492 -> 0 bytes .../dolphin/L3_Furippa3_128x64/frame_18.bm | Bin 445 -> 0 bytes .../dolphin/L3_Furippa3_128x64/frame_2.bm | Bin 463 -> 0 bytes .../dolphin/L3_Furippa3_128x64/frame_3.bm | Bin 424 -> 0 bytes .../dolphin/L3_Furippa3_128x64/frame_4.bm | Bin 499 -> 0 bytes .../dolphin/L3_Furippa3_128x64/frame_5.bm | Bin 504 -> 0 bytes .../dolphin/L3_Furippa3_128x64/frame_6.bm | Bin 521 -> 0 bytes .../dolphin/L3_Furippa3_128x64/frame_7.bm | Bin 398 -> 0 bytes .../dolphin/L3_Furippa3_128x64/frame_8.bm | Bin 419 -> 0 bytes .../dolphin/L3_Furippa3_128x64/frame_9.bm | Bin 435 -> 0 bytes .../dolphin/L3_Furippa3_128x64/meta.txt | 14 -- .../dolphin/L3_Hijack_radio_128x64/frame_0.bm | Bin 524 -> 0 bytes .../dolphin/L3_Hijack_radio_128x64/frame_1.bm | Bin 527 -> 0 bytes .../L3_Hijack_radio_128x64/frame_10.bm | Bin 550 -> 0 bytes .../L3_Hijack_radio_128x64/frame_11.bm | Bin 572 -> 0 bytes .../L3_Hijack_radio_128x64/frame_12.bm | Bin 539 -> 0 bytes .../L3_Hijack_radio_128x64/frame_13.bm | Bin 579 -> 0 bytes .../dolphin/L3_Hijack_radio_128x64/frame_2.bm | Bin 526 -> 0 bytes .../dolphin/L3_Hijack_radio_128x64/frame_3.bm | Bin 529 -> 0 bytes .../dolphin/L3_Hijack_radio_128x64/frame_4.bm | Bin 571 -> 0 bytes .../dolphin/L3_Hijack_radio_128x64/frame_5.bm | Bin 574 -> 0 bytes .../dolphin/L3_Hijack_radio_128x64/frame_6.bm | Bin 524 -> 0 bytes .../dolphin/L3_Hijack_radio_128x64/frame_7.bm | Bin 655 -> 0 bytes .../dolphin/L3_Hijack_radio_128x64/frame_8.bm | Bin 645 -> 0 bytes .../dolphin/L3_Hijack_radio_128x64/frame_9.bm | Bin 611 -> 0 bytes .../dolphin/L3_Hijack_radio_128x64/meta.txt | 14 -- .../dolphin/L3_Lab_research_128x54/frame_0.bm | Bin 611 -> 0 bytes .../dolphin/L3_Lab_research_128x54/frame_1.bm | Bin 614 -> 0 bytes .../L3_Lab_research_128x54/frame_10.bm | Bin 576 -> 0 bytes .../L3_Lab_research_128x54/frame_11.bm | Bin 585 -> 0 bytes .../L3_Lab_research_128x54/frame_12.bm | Bin 571 -> 0 bytes .../L3_Lab_research_128x54/frame_13.bm | Bin 615 -> 0 bytes .../dolphin/L3_Lab_research_128x54/frame_2.bm | Bin 618 -> 0 bytes .../dolphin/L3_Lab_research_128x54/frame_3.bm | Bin 608 -> 0 bytes .../dolphin/L3_Lab_research_128x54/frame_4.bm | Bin 615 -> 0 bytes .../dolphin/L3_Lab_research_128x54/frame_5.bm | Bin 618 -> 0 bytes .../dolphin/L3_Lab_research_128x54/frame_6.bm | Bin 615 -> 0 bytes .../dolphin/L3_Lab_research_128x54/frame_7.bm | Bin 585 -> 0 bytes .../dolphin/L3_Lab_research_128x54/frame_8.bm | Bin 578 -> 0 bytes .../dolphin/L3_Lab_research_128x54/frame_9.bm | Bin 581 -> 0 bytes .../dolphin/L3_Lab_research_128x54/meta.txt | 59 --------- assets/resources/dolphin/manifest.txt | 121 ------------------ documentation/AppManifests.md | 4 +- documentation/fbt.md | 1 + fbt | 3 +- fbt.cmd | 4 +- fbt_options.py | 2 - firmware.scons | 38 +----- lib/scons | 1 - scripts/fbt/sdk.py | 25 ++-- scripts/fbt_tools/fbt_assets.py | 30 +++-- scripts/fbt_tools/fbt_debugopts.py | 35 +++++ scripts/fbt_tools/fbt_extapps.py | 44 ++++++- scripts/fbt_tools/fbt_sdk.py | 72 +++++++---- scripts/fbt_tools/fbt_tweaks.py | 43 +++++++ scripts/fbt_tools/fwbin.py | 8 ++ scripts/fbt_tools/gdb.py | 2 - scripts/fwsize.py | 5 +- scripts/sconsdist.py | 7 +- scripts/toolchain/fbtenv.cmd | 4 +- scripts/toolchain/fbtenv.sh | 2 +- .../toolchain/windows-toolchain-download.ps1 | 12 +- site_scons/commandline.scons | 5 + site_scons/environ.scons | 4 +- site_scons/extapps.scons | 3 + site_scons/fbt_extra/util.py | 5 +- site_scons/site_init.py | 16 +-- 243 files changed, 302 insertions(+), 740 deletions(-) delete mode 100644 assets/resources/dolphin/L1_Boxing_128x64/frame_0.bm delete mode 100644 assets/resources/dolphin/L1_Boxing_128x64/frame_1.bm delete mode 100644 assets/resources/dolphin/L1_Boxing_128x64/frame_2.bm delete mode 100644 assets/resources/dolphin/L1_Boxing_128x64/frame_3.bm delete mode 100644 assets/resources/dolphin/L1_Boxing_128x64/frame_4.bm delete mode 100644 assets/resources/dolphin/L1_Boxing_128x64/frame_5.bm delete mode 100644 assets/resources/dolphin/L1_Boxing_128x64/frame_6.bm delete mode 100644 assets/resources/dolphin/L1_Boxing_128x64/meta.txt delete mode 100644 assets/resources/dolphin/L1_Cry_128x64/frame_0.bm delete mode 100644 assets/resources/dolphin/L1_Cry_128x64/frame_1.bm delete mode 100644 assets/resources/dolphin/L1_Cry_128x64/frame_2.bm delete mode 100644 assets/resources/dolphin/L1_Cry_128x64/frame_3.bm delete mode 100644 assets/resources/dolphin/L1_Cry_128x64/frame_4.bm delete mode 100644 assets/resources/dolphin/L1_Cry_128x64/frame_5.bm delete mode 100644 assets/resources/dolphin/L1_Cry_128x64/frame_6.bm delete mode 100644 assets/resources/dolphin/L1_Cry_128x64/frame_7.bm delete mode 100644 assets/resources/dolphin/L1_Cry_128x64/meta.txt delete mode 100644 assets/resources/dolphin/L1_Furippa1_128x64/frame_0.bm delete mode 100644 assets/resources/dolphin/L1_Furippa1_128x64/frame_1.bm delete mode 100644 assets/resources/dolphin/L1_Furippa1_128x64/frame_10.bm delete mode 100644 assets/resources/dolphin/L1_Furippa1_128x64/frame_11.bm delete mode 100644 assets/resources/dolphin/L1_Furippa1_128x64/frame_12.bm delete mode 100644 assets/resources/dolphin/L1_Furippa1_128x64/frame_13.bm delete mode 100644 assets/resources/dolphin/L1_Furippa1_128x64/frame_14.bm delete mode 100644 assets/resources/dolphin/L1_Furippa1_128x64/frame_15.bm delete mode 100644 assets/resources/dolphin/L1_Furippa1_128x64/frame_16.bm delete mode 100644 assets/resources/dolphin/L1_Furippa1_128x64/frame_17.bm delete mode 100644 assets/resources/dolphin/L1_Furippa1_128x64/frame_18.bm delete mode 100644 assets/resources/dolphin/L1_Furippa1_128x64/frame_2.bm delete mode 100644 assets/resources/dolphin/L1_Furippa1_128x64/frame_3.bm delete mode 100644 assets/resources/dolphin/L1_Furippa1_128x64/frame_4.bm delete mode 100644 assets/resources/dolphin/L1_Furippa1_128x64/frame_5.bm delete mode 100644 assets/resources/dolphin/L1_Furippa1_128x64/frame_6.bm delete mode 100644 assets/resources/dolphin/L1_Furippa1_128x64/frame_7.bm delete mode 100644 assets/resources/dolphin/L1_Furippa1_128x64/frame_8.bm delete mode 100644 assets/resources/dolphin/L1_Furippa1_128x64/frame_9.bm delete mode 100644 assets/resources/dolphin/L1_Furippa1_128x64/meta.txt delete mode 100644 assets/resources/dolphin/L1_Laptop_128x51/frame_0.bm delete mode 100644 assets/resources/dolphin/L1_Laptop_128x51/frame_1.bm delete mode 100644 assets/resources/dolphin/L1_Laptop_128x51/frame_2.bm delete mode 100644 assets/resources/dolphin/L1_Laptop_128x51/frame_3.bm delete mode 100644 assets/resources/dolphin/L1_Laptop_128x51/frame_4.bm delete mode 100644 assets/resources/dolphin/L1_Laptop_128x51/frame_5.bm delete mode 100644 assets/resources/dolphin/L1_Laptop_128x51/frame_6.bm delete mode 100644 assets/resources/dolphin/L1_Laptop_128x51/frame_7.bm delete mode 100644 assets/resources/dolphin/L1_Laptop_128x51/meta.txt delete mode 100644 assets/resources/dolphin/L1_Leaving_sad_128x64/frame_0.bm delete mode 100644 assets/resources/dolphin/L1_Leaving_sad_128x64/frame_1.bm delete mode 100644 assets/resources/dolphin/L1_Leaving_sad_128x64/frame_10.bm delete mode 100644 assets/resources/dolphin/L1_Leaving_sad_128x64/frame_11.bm delete mode 100644 assets/resources/dolphin/L1_Leaving_sad_128x64/frame_12.bm delete mode 100644 assets/resources/dolphin/L1_Leaving_sad_128x64/frame_2.bm delete mode 100644 assets/resources/dolphin/L1_Leaving_sad_128x64/frame_3.bm delete mode 100644 assets/resources/dolphin/L1_Leaving_sad_128x64/frame_4.bm delete mode 100644 assets/resources/dolphin/L1_Leaving_sad_128x64/frame_5.bm delete mode 100644 assets/resources/dolphin/L1_Leaving_sad_128x64/frame_6.bm delete mode 100644 assets/resources/dolphin/L1_Leaving_sad_128x64/frame_7.bm delete mode 100644 assets/resources/dolphin/L1_Leaving_sad_128x64/frame_8.bm delete mode 100644 assets/resources/dolphin/L1_Leaving_sad_128x64/frame_9.bm delete mode 100644 assets/resources/dolphin/L1_Leaving_sad_128x64/meta.txt delete mode 100644 assets/resources/dolphin/L1_Mad_fist_128x64/frame_0.bm delete mode 100644 assets/resources/dolphin/L1_Mad_fist_128x64/frame_1.bm delete mode 100644 assets/resources/dolphin/L1_Mad_fist_128x64/frame_10.bm delete mode 100644 assets/resources/dolphin/L1_Mad_fist_128x64/frame_11.bm delete mode 100644 assets/resources/dolphin/L1_Mad_fist_128x64/frame_12.bm delete mode 100644 assets/resources/dolphin/L1_Mad_fist_128x64/frame_13.bm delete mode 100644 assets/resources/dolphin/L1_Mad_fist_128x64/frame_2.bm delete mode 100644 assets/resources/dolphin/L1_Mad_fist_128x64/frame_3.bm delete mode 100644 assets/resources/dolphin/L1_Mad_fist_128x64/frame_4.bm delete mode 100644 assets/resources/dolphin/L1_Mad_fist_128x64/frame_5.bm delete mode 100644 assets/resources/dolphin/L1_Mad_fist_128x64/frame_6.bm delete mode 100644 assets/resources/dolphin/L1_Mad_fist_128x64/frame_7.bm delete mode 100644 assets/resources/dolphin/L1_Mad_fist_128x64/frame_8.bm delete mode 100644 assets/resources/dolphin/L1_Mad_fist_128x64/frame_9.bm delete mode 100644 assets/resources/dolphin/L1_Mad_fist_128x64/meta.txt delete mode 100644 assets/resources/dolphin/L1_Painting_128x64/frame_0.bm delete mode 100644 assets/resources/dolphin/L1_Painting_128x64/frame_1.bm delete mode 100644 assets/resources/dolphin/L1_Painting_128x64/frame_10.bm delete mode 100644 assets/resources/dolphin/L1_Painting_128x64/frame_11.bm delete mode 100644 assets/resources/dolphin/L1_Painting_128x64/frame_2.bm delete mode 100644 assets/resources/dolphin/L1_Painting_128x64/frame_3.bm delete mode 100644 assets/resources/dolphin/L1_Painting_128x64/frame_4.bm delete mode 100644 assets/resources/dolphin/L1_Painting_128x64/frame_5.bm delete mode 100644 assets/resources/dolphin/L1_Painting_128x64/frame_6.bm delete mode 100644 assets/resources/dolphin/L1_Painting_128x64/frame_7.bm delete mode 100644 assets/resources/dolphin/L1_Painting_128x64/frame_8.bm delete mode 100644 assets/resources/dolphin/L1_Painting_128x64/frame_9.bm delete mode 100644 assets/resources/dolphin/L1_Painting_128x64/meta.txt delete mode 100644 assets/resources/dolphin/L1_Read_books_128x64/frame_0.bm delete mode 100644 assets/resources/dolphin/L1_Read_books_128x64/frame_1.bm delete mode 100644 assets/resources/dolphin/L1_Read_books_128x64/frame_2.bm delete mode 100644 assets/resources/dolphin/L1_Read_books_128x64/frame_3.bm delete mode 100644 assets/resources/dolphin/L1_Read_books_128x64/frame_4.bm delete mode 100644 assets/resources/dolphin/L1_Read_books_128x64/frame_5.bm delete mode 100644 assets/resources/dolphin/L1_Read_books_128x64/frame_6.bm delete mode 100644 assets/resources/dolphin/L1_Read_books_128x64/frame_7.bm delete mode 100644 assets/resources/dolphin/L1_Read_books_128x64/frame_8.bm delete mode 100644 assets/resources/dolphin/L1_Read_books_128x64/meta.txt delete mode 100644 assets/resources/dolphin/L1_Recording_128x51/frame_0.bm delete mode 100644 assets/resources/dolphin/L1_Recording_128x51/frame_1.bm delete mode 100644 assets/resources/dolphin/L1_Recording_128x51/frame_10.bm delete mode 100644 assets/resources/dolphin/L1_Recording_128x51/frame_11.bm delete mode 100644 assets/resources/dolphin/L1_Recording_128x51/frame_2.bm delete mode 100644 assets/resources/dolphin/L1_Recording_128x51/frame_3.bm delete mode 100644 assets/resources/dolphin/L1_Recording_128x51/frame_4.bm delete mode 100644 assets/resources/dolphin/L1_Recording_128x51/frame_5.bm delete mode 100644 assets/resources/dolphin/L1_Recording_128x51/frame_6.bm delete mode 100644 assets/resources/dolphin/L1_Recording_128x51/frame_7.bm delete mode 100644 assets/resources/dolphin/L1_Recording_128x51/frame_8.bm delete mode 100644 assets/resources/dolphin/L1_Recording_128x51/frame_9.bm delete mode 100644 assets/resources/dolphin/L1_Recording_128x51/meta.txt delete mode 100644 assets/resources/dolphin/L1_Sleep_128x64/frame_0.bm delete mode 100644 assets/resources/dolphin/L1_Sleep_128x64/frame_1.bm delete mode 100644 assets/resources/dolphin/L1_Sleep_128x64/frame_2.bm delete mode 100644 assets/resources/dolphin/L1_Sleep_128x64/frame_3.bm delete mode 100644 assets/resources/dolphin/L1_Sleep_128x64/meta.txt delete mode 100644 assets/resources/dolphin/L1_Waves_128x50/frame_0.bm delete mode 100644 assets/resources/dolphin/L1_Waves_128x50/frame_1.bm delete mode 100644 assets/resources/dolphin/L1_Waves_128x50/frame_2.bm delete mode 100644 assets/resources/dolphin/L1_Waves_128x50/frame_3.bm delete mode 100644 assets/resources/dolphin/L1_Waves_128x50/meta.txt delete mode 100644 assets/resources/dolphin/L2_Furippa2_128x64/frame_0.bm delete mode 100644 assets/resources/dolphin/L2_Furippa2_128x64/frame_1.bm delete mode 100644 assets/resources/dolphin/L2_Furippa2_128x64/frame_10.bm delete mode 100644 assets/resources/dolphin/L2_Furippa2_128x64/frame_11.bm delete mode 100644 assets/resources/dolphin/L2_Furippa2_128x64/frame_12.bm delete mode 100644 assets/resources/dolphin/L2_Furippa2_128x64/frame_13.bm delete mode 100644 assets/resources/dolphin/L2_Furippa2_128x64/frame_14.bm delete mode 100644 assets/resources/dolphin/L2_Furippa2_128x64/frame_15.bm delete mode 100644 assets/resources/dolphin/L2_Furippa2_128x64/frame_16.bm delete mode 100644 assets/resources/dolphin/L2_Furippa2_128x64/frame_17.bm delete mode 100644 assets/resources/dolphin/L2_Furippa2_128x64/frame_18.bm delete mode 100644 assets/resources/dolphin/L2_Furippa2_128x64/frame_2.bm delete mode 100644 assets/resources/dolphin/L2_Furippa2_128x64/frame_3.bm delete mode 100644 assets/resources/dolphin/L2_Furippa2_128x64/frame_4.bm delete mode 100644 assets/resources/dolphin/L2_Furippa2_128x64/frame_5.bm delete mode 100644 assets/resources/dolphin/L2_Furippa2_128x64/frame_6.bm delete mode 100644 assets/resources/dolphin/L2_Furippa2_128x64/frame_7.bm delete mode 100644 assets/resources/dolphin/L2_Furippa2_128x64/frame_8.bm delete mode 100644 assets/resources/dolphin/L2_Furippa2_128x64/frame_9.bm delete mode 100644 assets/resources/dolphin/L2_Furippa2_128x64/meta.txt delete mode 100644 assets/resources/dolphin/L2_Hacking_pc_128x64/frame_0.bm delete mode 100644 assets/resources/dolphin/L2_Hacking_pc_128x64/frame_1.bm delete mode 100644 assets/resources/dolphin/L2_Hacking_pc_128x64/frame_2.bm delete mode 100644 assets/resources/dolphin/L2_Hacking_pc_128x64/frame_3.bm delete mode 100644 assets/resources/dolphin/L2_Hacking_pc_128x64/frame_4.bm delete mode 100644 assets/resources/dolphin/L2_Hacking_pc_128x64/meta.txt delete mode 100644 assets/resources/dolphin/L2_Soldering_128x64/frame_0.bm delete mode 100644 assets/resources/dolphin/L2_Soldering_128x64/frame_1.bm delete mode 100644 assets/resources/dolphin/L2_Soldering_128x64/frame_10.bm delete mode 100644 assets/resources/dolphin/L2_Soldering_128x64/frame_2.bm delete mode 100644 assets/resources/dolphin/L2_Soldering_128x64/frame_3.bm delete mode 100644 assets/resources/dolphin/L2_Soldering_128x64/frame_4.bm delete mode 100644 assets/resources/dolphin/L2_Soldering_128x64/frame_5.bm delete mode 100644 assets/resources/dolphin/L2_Soldering_128x64/frame_6.bm delete mode 100644 assets/resources/dolphin/L2_Soldering_128x64/frame_7.bm delete mode 100644 assets/resources/dolphin/L2_Soldering_128x64/frame_8.bm delete mode 100644 assets/resources/dolphin/L2_Soldering_128x64/frame_9.bm delete mode 100644 assets/resources/dolphin/L2_Soldering_128x64/meta.txt delete mode 100644 assets/resources/dolphin/L3_Furippa3_128x64/frame_0.bm delete mode 100644 assets/resources/dolphin/L3_Furippa3_128x64/frame_1.bm delete mode 100644 assets/resources/dolphin/L3_Furippa3_128x64/frame_10.bm delete mode 100644 assets/resources/dolphin/L3_Furippa3_128x64/frame_11.bm delete mode 100644 assets/resources/dolphin/L3_Furippa3_128x64/frame_12.bm delete mode 100644 assets/resources/dolphin/L3_Furippa3_128x64/frame_13.bm delete mode 100644 assets/resources/dolphin/L3_Furippa3_128x64/frame_14.bm delete mode 100644 assets/resources/dolphin/L3_Furippa3_128x64/frame_15.bm delete mode 100644 assets/resources/dolphin/L3_Furippa3_128x64/frame_16.bm delete mode 100644 assets/resources/dolphin/L3_Furippa3_128x64/frame_17.bm delete mode 100644 assets/resources/dolphin/L3_Furippa3_128x64/frame_18.bm delete mode 100644 assets/resources/dolphin/L3_Furippa3_128x64/frame_2.bm delete mode 100644 assets/resources/dolphin/L3_Furippa3_128x64/frame_3.bm delete mode 100644 assets/resources/dolphin/L3_Furippa3_128x64/frame_4.bm delete mode 100644 assets/resources/dolphin/L3_Furippa3_128x64/frame_5.bm delete mode 100644 assets/resources/dolphin/L3_Furippa3_128x64/frame_6.bm delete mode 100644 assets/resources/dolphin/L3_Furippa3_128x64/frame_7.bm delete mode 100644 assets/resources/dolphin/L3_Furippa3_128x64/frame_8.bm delete mode 100644 assets/resources/dolphin/L3_Furippa3_128x64/frame_9.bm delete mode 100644 assets/resources/dolphin/L3_Furippa3_128x64/meta.txt delete mode 100644 assets/resources/dolphin/L3_Hijack_radio_128x64/frame_0.bm delete mode 100644 assets/resources/dolphin/L3_Hijack_radio_128x64/frame_1.bm delete mode 100644 assets/resources/dolphin/L3_Hijack_radio_128x64/frame_10.bm delete mode 100644 assets/resources/dolphin/L3_Hijack_radio_128x64/frame_11.bm delete mode 100644 assets/resources/dolphin/L3_Hijack_radio_128x64/frame_12.bm delete mode 100644 assets/resources/dolphin/L3_Hijack_radio_128x64/frame_13.bm delete mode 100644 assets/resources/dolphin/L3_Hijack_radio_128x64/frame_2.bm delete mode 100644 assets/resources/dolphin/L3_Hijack_radio_128x64/frame_3.bm delete mode 100644 assets/resources/dolphin/L3_Hijack_radio_128x64/frame_4.bm delete mode 100644 assets/resources/dolphin/L3_Hijack_radio_128x64/frame_5.bm delete mode 100644 assets/resources/dolphin/L3_Hijack_radio_128x64/frame_6.bm delete mode 100644 assets/resources/dolphin/L3_Hijack_radio_128x64/frame_7.bm delete mode 100644 assets/resources/dolphin/L3_Hijack_radio_128x64/frame_8.bm delete mode 100644 assets/resources/dolphin/L3_Hijack_radio_128x64/frame_9.bm delete mode 100644 assets/resources/dolphin/L3_Hijack_radio_128x64/meta.txt delete mode 100644 assets/resources/dolphin/L3_Lab_research_128x54/frame_0.bm delete mode 100644 assets/resources/dolphin/L3_Lab_research_128x54/frame_1.bm delete mode 100644 assets/resources/dolphin/L3_Lab_research_128x54/frame_10.bm delete mode 100644 assets/resources/dolphin/L3_Lab_research_128x54/frame_11.bm delete mode 100644 assets/resources/dolphin/L3_Lab_research_128x54/frame_12.bm delete mode 100644 assets/resources/dolphin/L3_Lab_research_128x54/frame_13.bm delete mode 100644 assets/resources/dolphin/L3_Lab_research_128x54/frame_2.bm delete mode 100644 assets/resources/dolphin/L3_Lab_research_128x54/frame_3.bm delete mode 100644 assets/resources/dolphin/L3_Lab_research_128x54/frame_4.bm delete mode 100644 assets/resources/dolphin/L3_Lab_research_128x54/frame_5.bm delete mode 100644 assets/resources/dolphin/L3_Lab_research_128x54/frame_6.bm delete mode 100644 assets/resources/dolphin/L3_Lab_research_128x54/frame_7.bm delete mode 100644 assets/resources/dolphin/L3_Lab_research_128x54/frame_8.bm delete mode 100644 assets/resources/dolphin/L3_Lab_research_128x54/frame_9.bm delete mode 100644 assets/resources/dolphin/L3_Lab_research_128x54/meta.txt delete mode 100644 assets/resources/dolphin/manifest.txt delete mode 160000 lib/scons create mode 100644 scripts/fbt_tools/fbt_tweaks.py diff --git a/.github/workflows/pvs_studio.yml b/.github/workflows/pvs_studio.yml index e3d5fc1321e..f28fad20dc6 100644 --- a/.github/workflows/pvs_studio.yml +++ b/.github/workflows/pvs_studio.yml @@ -65,7 +65,6 @@ jobs: pvs-studio-analyzer credentials ${{ secrets.PVS_STUDIO_CREDENTIALS }} pvs-studio-analyzer analyze \ @.pvsoptions \ - --disableLicenseExpirationCheck \ -j$(grep -c processor /proc/cpuinfo) \ -f build/f7-firmware-DC/compile_commands.json \ -o PVS-Studio.log diff --git a/.gitmodules b/.gitmodules index 308d60fdc2a..a97e0933a0c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -22,9 +22,6 @@ [submodule "lib/microtar"] path = lib/microtar url = https://github.com/amachronic/microtar.git -[submodule "lib/scons"] - path = lib/scons - url = https://github.com/SCons/scons.git [submodule "lib/mbedtls"] path = lib/mbedtls url = https://github.com/Mbed-TLS/mbedtls.git diff --git a/.vscode/example/c_cpp_properties.json b/.vscode/example/c_cpp_properties.json index db08320c9c9..d1cac63e966 100644 --- a/.vscode/example/c_cpp_properties.json +++ b/.vscode/example/c_cpp_properties.json @@ -2,7 +2,7 @@ "configurations": [ { "name": "Win32", - "compilerPath": "${workspaceFolder}/toolchain/i686-windows/bin/arm-none-eabi-gcc.exe", + "compilerPath": "${workspaceFolder}/toolchain/x86_64-windows/bin/arm-none-eabi-gcc.exe", "intelliSenseMode": "gcc-arm", "compileCommands": "${workspaceFolder}/build/latest/compile_commands.json", "configurationProvider": "ms-vscode.cpptools", diff --git a/.vscode/example/launch.json b/.vscode/example/launch.json index f4df9659275..c8b0c601d46 100644 --- a/.vscode/example/launch.json +++ b/.vscode/example/launch.json @@ -79,6 +79,25 @@ ] // "showDevDebugOutput": "raw", }, + { + "name": "Attach FW (DAP)", + "cwd": "${workspaceFolder}", + "executable": "./build/latest/firmware.elf", + "request": "attach", + "type": "cortex-debug", + "servertype": "openocd", + "device": "cmsis-dap", + "svdFile": "./debug/STM32WB55_CM4.svd", + "rtos": "FreeRTOS", + "configFiles": [ + "interface/cmsis-dap.cfg", + "./debug/stm32wbx.cfg", + ], + "postAttachCommands": [ + "source debug/flipperapps.py", + ], + // "showDevDebugOutput": "raw", + }, { "name": "fbt debug", "type": "python", diff --git a/.vscode/example/settings.json b/.vscode/example/settings.json index d84707e071b..19a03b69d98 100644 --- a/.vscode/example/settings.json +++ b/.vscode/example/settings.json @@ -6,13 +6,13 @@ "cortex-debug.enableTelemetry": false, "cortex-debug.variableUseNaturalFormat": true, "cortex-debug.showRTOS": true, - "cortex-debug.armToolchainPath.windows": "${workspaceFolder}/toolchain/i686-windows/bin", + "cortex-debug.armToolchainPath.windows": "${workspaceFolder}/toolchain/x86_64-windows/bin", "cortex-debug.armToolchainPath.linux": "${workspaceFolder}/toolchain/x86_64-linux/bin", "cortex-debug.armToolchainPath.osx": "${workspaceFolder}/toolchain/x86_64-darwin/bin", - "cortex-debug.openocdPath.windows": "${workspaceFolder}/toolchain/i686-windows/openocd/bin/openocd.exe", + "cortex-debug.openocdPath.windows": "${workspaceFolder}/toolchain/x86_64-windows/openocd/bin/openocd.exe", "cortex-debug.openocdPath.linux": "${workspaceFolder}/toolchain/x86_64-linux/openocd/bin/openocd", "cortex-debug.openocdPath.osx": "${workspaceFolder}/toolchain/x86_64-darwin/openocd/bin/openocd", - "cortex-debug.gdbPath.windows": "${workspaceFolder}/toolchain/i686-windows/bin/arm-none-eabi-gdb-py.bat", + "cortex-debug.gdbPath.windows": "${workspaceFolder}/toolchain/x86_64-windows/bin/arm-none-eabi-gdb-py.bat", "cortex-debug.gdbPath.linux": "${workspaceFolder}/toolchain/x86_64-linux/bin/arm-none-eabi-gdb-py", "cortex-debug.gdbPath.osx": "${workspaceFolder}/toolchain/x86_64-darwin/bin/arm-none-eabi-gdb-py", "editor.formatOnSave": true, diff --git a/SConstruct b/SConstruct index 74fa5667b3f..448df97153a 100644 --- a/SConstruct +++ b/SConstruct @@ -156,11 +156,9 @@ Depends(fap_dist, firmware_env["FW_EXTAPPS"]["validators"].values()) Alias("fap_dist", fap_dist) # distenv.Default(fap_dist) -plugin_resources_dist = list( - distenv.Install(f"#/assets/resources/apps/{dist_entry[0]}", dist_entry[1]) - for dist_entry in firmware_env["FW_EXTAPPS"]["dist"].values() +distenv.Depends( + firmware_env["FW_RESOURCES"], firmware_env["FW_EXTAPPS"]["resources_dist"] ) -distenv.Depends(firmware_env["FW_RESOURCES"], plugin_resources_dist) # Target for bundling core2 package for qFlipper @@ -291,6 +289,16 @@ distenv.PhonyTarget( "@echo $( ${BLACKMAGIC_ADDR} $)", ) + +# Find STLink probe ids +distenv.PhonyTarget( + "get_stlink", + distenv.Action( + lambda **kw: distenv.GetDevices(), + None, + ), +) + # Prepare vscode environment vscode_dist = distenv.Install("#.vscode", distenv.Glob("#.vscode/example/*")) distenv.Precious(vscode_dist) diff --git a/assets/.gitignore b/assets/.gitignore index 9bc0bdc0c73..2695770477a 100644 --- a/assets/.gitignore +++ b/assets/.gitignore @@ -1,3 +1,4 @@ /core2_firmware /resources/Manifest -/resources/apps/* \ No newline at end of file +/resources/apps/* +/resources/dolphin/* diff --git a/assets/SConscript b/assets/SConscript index e1bf546cc36..63141829ec3 100644 --- a/assets/SConscript +++ b/assets/SConscript @@ -68,15 +68,17 @@ if assetsenv["IS_BASE_FIRMWARE"]: assetsenv.Dir("#/assets/dolphin"), DOLPHIN_RES_TYPE="external", ) - assetsenv.NoClean(dolphin_external) if assetsenv["FORCE"]: assetsenv.AlwaysBuild(dolphin_external) assetsenv.Alias("dolphin_ext", dolphin_external) + assetsenv.Clean(dolphin_external, assetsenv.Dir("#/assets/resources/dolphin")) # Resources manifest resources = assetsenv.Command( "#/assets/resources/Manifest", - assetsenv.GlobRecursive("*", "resources", exclude="Manifest"), + assetsenv.GlobRecursive( + "*", assetsenv.Dir("resources").srcnode(), exclude="Manifest" + ), action=Action( '${PYTHON3} "${ASSETS_COMPILER}" manifest "${TARGET.dir.posix}" --timestamp=${GIT_UNIX_TIMESTAMP}', "${RESMANIFESTCOMSTR}", diff --git a/assets/resources/dolphin/L1_Boxing_128x64/frame_0.bm b/assets/resources/dolphin/L1_Boxing_128x64/frame_0.bm deleted file mode 100644 index 46079c3728bbc787dac3b6bd912534c3cb26c2dc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 481 zcmV<70UrJV0Nnuq41gct{-6W=AJ_nYgZv-l{|EQLAJWJOKV)zscEkbuK!`yZ+7IZ2 zA`k^H{0tv403h%n5x{%GBY^UdKr8^Whl&a-KGuK#@pCZ$fTREYfXmDOv4I8_7$4|8 zWM9wy{zW2%1H=e2Efj7e5vqyGsa1l5#-&xN^>_NMRwDg?{NgbPBEkVj5nrNFDx-1l zhs9(-K@X&ej6;zJl%Rf5@%aPDK9qp^!U4m790)wJ1NH}yWI^RnAHn>AY(wU59C%i$P0j1>yyZb2eSZ&<<EXw3P3+Y`9bg> zGw`qs{?L2IGCmF}0sTSa4~A5K;qqV!{Q=`2l1RPCF<=n=r}ZkY=Heb41yFw9{7xhI zFDwB4@9UBO1B(J5LHS?bB2PRR{qOIQ2c8r@kbrq&0D=!9D38oEA@{_G&~zj7CzJxB z|4{mWm<1(&sQl034`;q07 diff --git a/assets/resources/dolphin/L1_Boxing_128x64/frame_1.bm b/assets/resources/dolphin/L1_Boxing_128x64/frame_1.bm deleted file mode 100644 index e12d70796299cef4674cbc2e84e90f9f89cdf652..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 461 zcmV;;0W$sp0LcLW41gct{|ER#!Tt~HfIq?gs0Z|t0uSgIKV(oKe#l^e?7&DN0RIQ& z0znW0m%@IofB=C?8Ndg{z#eBYLYzDVKl;Doq5uEy!2|jNkN@@qEZ_g1{{Pp(1NLHZ5`cK(00m#7Q7XS*{roa9 zP>mvRO#zP{B1I8dfDER%u^N0_S{9+ME27rFS^biz)e|IKu$OrfJzlYp@m=A^i z13({@Y<|qu!2#onAFWhAQ3xC$9wqgWREOoTfP>0NKU-)#WTfGU z2i*Rg2mm~v^OfMhED%ZsA%8#U20{r$lt1U=0Kh=#f%SiFb)nte>GVQX8}N{kLpDxphzMk`jYS_5(oPa(oq11 z=rM!(lE5Gm0saHX2qW|VhsXpn@ObC|;s8K;MZx|L9)LMu1M+~tA^`CL5CH-H5Ac5g D_X*BS diff --git a/assets/resources/dolphin/L1_Boxing_128x64/frame_2.bm b/assets/resources/dolphin/L1_Boxing_128x64/frame_2.bm deleted file mode 100644 index b416740f2772d3a057f2fd7817d1b16adc40af47..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 531 zcmV+u0_^<(01pBH41gct{-6W=AJ_nYgZv-l{|EQLAJWJOKV)zscEkbuK!`yZ+7IZ2 zA`k^H^c){C0wF+S0QZDP0pybz04(9)A^*i^+Ry*~UJ(!=fCu&iFE9Sa1Q@u)f57#T ze?Ry66pAD$83+_~QMimosuwDyRuT^yr~rDq{Z^|He!zZl7=w{wfTM`7(I}N)@Bh9Z z7$`hyLG_Ulh;k|-k`Ky0KOlJr#iT$%52PR*H}SxO$b3We2asfNK=G&#;Qm@60K^CI zA1_o5fFt{N6@Y$KvHOF|KwuI3io_mN1^}7x9D%Rn9wc*+m!`v1q^8X)k4#Xu+Y4~RT;0Q1NX)2sad=j0k7 zaDWCve^7ZuGEfLWJcsCiJcrH!4+o6|{`>pHGlCB@`lHGpQCE}$15`htJfxB+0svwd z5&O^ABUOPy0pcG)`Qk?-1cXR1Blo|)MymygNFZzV2b72aGx@y&3n3r1{5(Bg1R{Y6 zU#L8X11Nw*Xb}=HkIJeaNU6AJ8B+R4hv^D{NTeDU`iIgI4;;aiNAlho790vA_Wy=N zK0XWpePmzYc#p;<0FqDuqCY?QJb*(NfJdkTf&dsjCHM#kKP&VI06m}p@_;}v5)gQS VL;#<_c)&s99)L&z_y-VwgV=lG-g*E4 diff --git a/assets/resources/dolphin/L1_Boxing_128x64/frame_3.bm b/assets/resources/dolphin/L1_Boxing_128x64/frame_3.bm deleted file mode 100644 index b7e15ddeec980a2775b4df50901a2996a7991609..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 437 zcmV;m0ZRS>0I>l841gct{|ER#!Tt~HfIq?gs0Z|t0uSgIKV(oKe#l^e?7&DN0RIQ& z0znW0m%=`-fB=C?8DIy+z#ecc0u1NLHZ5`cK(00m#7Q7XSuV1`CY z4;p}dWM9E}$YfC2*hpfPa4=c@OXF0}r_UegN^% z56ZSbf5?6Z=>q-_)qejQ`Mi1+LHxrffbqZ|-TF0H6yP2(0sBV>RDt862aJFoGC}&=L+8+l2i*Q}m=6*VKNtN# zd{AKc(iigvh_l84D1Xf4Vj}Sms{J?+L-`NuDi0_HKz}+YI5=bx0sQ4bu z1P4KoAI(-jfESHKe=;lefF3as{MB|b@OZ!2ev*QC=rM!(lAnlwav*r0gnpm!`2dDq f4<3LVKnM@;dPTwU03VaEVh!?X5CFmc5Ac5gJVnKO diff --git a/assets/resources/dolphin/L1_Boxing_128x64/frame_4.bm b/assets/resources/dolphin/L1_Boxing_128x64/frame_4.bm deleted file mode 100644 index 202ad6e37bf56c5959bb6ca06bfe101de96248f1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 459 zcmV;+0W|&r0LK9U41gct{|ER#!Tt~HfIq?gs0Z|t0uSgIKV(oKe#l^e?7&DN0RIQ& z0znW0m-wj?004;!0}Kbnz#dcx1i%(>@DTs%|B8qI|1SuL5I_U_0hVw7&;Nho;}HS~ zJtSYC8Azf)@d6BsMH`63YQNBU)oQSjz<~y<)!*v1SdHWl*@?s;hzKA7uhA%#U$6fD z85k%$YC-jpe+AokJ_ZaE5rzZL-w|V$E$z-5DKJ! zoPXj14;BGG-Ua9f3$0RibCfPNrA{TK*8!OC5Y B#Yg}E diff --git a/assets/resources/dolphin/L1_Boxing_128x64/frame_5.bm b/assets/resources/dolphin/L1_Boxing_128x64/frame_5.bm deleted file mode 100644 index cdc0a2a343182740de054af8ad6e8a42848e616b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 450 zcmV;z0X_Z!0KNeL41gct{|ER#!Tt~HfIq?gs0Z|t0uSgIKV(oKe#l^e?7&DN0RIQ& z0znX0XaGQi!2g5h;14N;0O*kL5dZ4`iiiLI3xIt9f&d@b46}d!fBXLhfO-M#BK-i# zBvAk$$h1+oj7F>d2aQ&$4;p|FYQ0_lt5t~JK>e7QLr8QOfB{$NluEDH|9=dKJY)g( zk$(l_A<-p>esS^pWnd5*0R6+|3BXV)Gy(gE%ZraLjs~bdnU163^}>(p6OjCW@Zt6! zoEJb3A^FzF>jDRu{(^{EsSrG-1O1RM=zd`FU&wr5PzwNkhw2X% skiXadA0QBe#zDjX0t5hH_RvoO$`6hhIbZ|wE{qWVE+GIYKf(SF;Hdb~#sB~S diff --git a/assets/resources/dolphin/L1_Boxing_128x64/frame_6.bm b/assets/resources/dolphin/L1_Boxing_128x64/frame_6.bm deleted file mode 100644 index e8ea3aa683605683a11be4c74d8946e2c7f720d3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 442 zcmV;r0Y&}+0JZ@D41gct{|ER#!Tt~HfIq?gs0Z|t0uSgIKV(oKe#l^e?7&DN0RIQ& z0znX0XaGQi!2g5h;14N;0O*kL5dZ4`iiiLI3xIt9f&d@b46}d!fBXLhfO-M#BK-i# zBvAk$$h1+oj7F>d2aQ&$4;p|FYQ0_lt5t~JK>e7QLr8QOfB{$NluEDH|9=dKJY)g( zk$(l_A<kmk_ZF^z(e$o2g*laz(8aN z_Oye?{SlyG8UjCRz&v2k{s#g9fFHF~9zU3V0>V%c`&5DB@du6k9x@U7+d<={hrlQ1 z0jVG2QwV@Zpb$J${v@;nc|k+!zd8+o9#D7u)oK9pfUoBhNC%b$e&O<~gNFe_f6RwK z4=LdOvY_&)kLwT*DZu`N=kOoah#oWvfc~nm5O}~B^NEB5&3_^LAEb%}06`1=f8p{0 k2s~sQKky(x1`lln@EoA{;e(a{KO$(s3FH7^e}nuVz*X;M1& diff --git a/assets/resources/dolphin/L1_Boxing_128x64/meta.txt b/assets/resources/dolphin/L1_Boxing_128x64/meta.txt deleted file mode 100644 index c66998e7d85..00000000000 --- a/assets/resources/dolphin/L1_Boxing_128x64/meta.txt +++ /dev/null @@ -1,32 +0,0 @@ -Filetype: Flipper Animation -Version: 1 - -Width: 128 -Height: 64 -Passive frames: 9 -Active frames: 7 -Frames order: 0 1 2 1 3 1 2 3 1 4 5 6 5 6 5 4 -Active cycles: 1 -Frame rate: 2 -Duration: 3600 -Active cooldown: 7 - -Bubble slots: 2 - -Slot: 0 -X: 78 -Y: 16 -Text: F*&K!!! -AlignH: Left -AlignV: Bottom -StartFrame: 2 -EndFrame: 4 - -Slot: 1 -X: 78 -Y: 16 -Text: What ya\nlookin at?! -AlignH: Left -AlignV: Center -StartFrame: 10 -EndFrame: 15 diff --git a/assets/resources/dolphin/L1_Cry_128x64/frame_0.bm b/assets/resources/dolphin/L1_Cry_128x64/frame_0.bm deleted file mode 100644 index 9147714c1e291e52f40294d265c696fc6ae5c708..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 889 zcmV-<1BUzo0CfYDU~v!(1pp2(7#2ba0isYMQizTMiKK)^720#ulnk15?AeiDP5lAF}9xx0=5)*)eL;*vIpiChW z2vinH0iZ(RGDw921%bdwu@VA^!67(^gd$0#2aG2o4-<&cAdtL-LlH72#WvB=6^$ zQ4eDvkYafN;s=b1k(9{N4`V=qLP6;57zC8|VIja=LFk?n*oQ|+K#)c(0rfqO2M~K0 zLcBrgfS|x-GYE)0a1;oDJz%jH=$^&_5eJOHQYR5Y3=H-;3J z;z8oY7x91w7JEuWz-l1((jLbHfPrEcLJv$`nD!u1$iSfx51=%mkPI2@tdRit)S(|j z=))uM2pR0k!v={`0p0qmg=NQEQeV1R?l2Z%kE5OOgnjNpVn!Fb7!WeAc$ z1f!Ci1qa|V88Pf!BLt8DgdQNl02G+^G6UF@MnQu3AJPpFJ)KZ6R{(lZk7QmNq$-fb z5c;0Q7EYlDjAR%wz!1ZaX#~JRBoNTZ*umpY&^ko+GK>x)fGHaQC}0`G*q8?;2qK8& zqX6?gh@e5^EWpA6;sF>G18D?3jFJVBghWB(z(^j_0Z;-Ah@3DOh1zfsJ0dJZgCxkx zBv>4DAMhV5A-G`&u@;PjAq;>|!R5p~i~txiC=7!EIPg0}9$AS2XlM{9j0y$u|HHxM zgo#2j7?=dAD@2h4#s&{F*vT*;@sUtu7a#e&;DGUjA4EDN0kK~j=L9v1ET|GFO%lZK PKzQIK5;%cCMPd(Nc|%A2 diff --git a/assets/resources/dolphin/L1_Cry_128x64/frame_1.bm b/assets/resources/dolphin/L1_Cry_128x64/frame_1.bm deleted file mode 100644 index 789273d9da32277651ffe8631e3a657068af1b5d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 911 zcmV;A191ES0E+{N=4y}%1Og=ih+t@t4n&eg0Fhv70YQRPq!LI-7;z9u1TsYts7N6& zNR z$o4WBCPEN{DF6geBP212?4l5f4nX!&3>F~_FbN^-b_y{ZiR@&UCTm-JP96%9yJ9qKr|GH z6vS{)br8r8V*q^)hY(Aq0kl${A!ui1e~BN=8CK2M3m7kc%A! zN|87@PGy}NWfDhP$O924kJkioWNEfk>LZ!5Ro1U96{xXJ()mZ83-379(Dvl z9?48N5cq?L9xxmLSadvsXyjl+Q2-YLLnMO4G#QEnN+Sih1JV+EAn3s&QYadmV_R1v{}fpA@PAsAdmpy;2~$R%tRnq7ckPkiSeHU z#tT3_o+gt3p-ctm0E5y8r2>IMXcZuOz}4dt*p5b%K*~-7>U$ZKjtdf`VltnAX+t0w zv)W9uSO9pJMj0O$j6(o>7zBeQ0NQ*3-lnVhs$3g!A^0FS%5hBEJ4 l45L(JRvY7d;D@rr(jpWI1Om$MMTnlp5kSylWRMF8JwP+AOc?+G diff --git a/assets/resources/dolphin/L1_Cry_128x64/frame_2.bm b/assets/resources/dolphin/L1_Cry_128x64/frame_2.bm deleted file mode 100644 index a3c87e0a6de7d598766d94945ad530339de2d96f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 910 zcmWO3QA`tO007{-UTg2VlWR{l4p!RhabVLjJ?1UZrRd-G=&p`Qkv=S$$tsjd+zY5# zOlnxK6n0muwjFDVS+F2;WG|z-B|aEaLWKikfIckx))b6;=n~i*F-tb*>-YM>Py$hf z1*6D16uKlZ+#2hY7#aLxoMIV_jt6P(W}6dc<>Hu^O~T6vgj_qoJB^|`!{gT(Hyndk zu*s5}hDPLaKNSO&Z~#Bm2MZ1R4LUI<*f|E27LK91wpMox%#&iA1w6Q{y0@V5O`R8& z;bj;oo*zuc-=`nzpv28JiEd7M`G{R>fK3WhD$^tBuvEQBrG|!s-N1Yypcw5J^aUBT znp&PcazMLt!icV#>h91NNZHjtnVZwZK5N2Ld%@`C4g5Crfg*&U-5lsV@$c37;rHJl zmr~feKIp&K&tHnRT7losYBnNNauj~}iBh{;e~o2Qge6`F@7R=Lh;ry|9}rgsm(W}B zxT}$N9bLzrkE<3AZKfYM&&nR#QB>#Q2|I7_L#d3<1gn;1Ghq+m-!7$9bizL~rdi`k zX1EsS(5w%m52LR2K|I~;^ZGZDC*r|->HaY3sMBbO_u>f&?@Y!JTN(_5zjnm?`n!HS z+hwXE2{jYv`uW}ZLm1RW9;@Q z2Q4h62Pot!f1d1FeDD*zD}zBJ+Qqm*>WuU}745z8#Xd@B*!Y;)%V)E)l}Z{$lTE==&kxd&ed~{#*&6c=+@X{`Af>@ zOGD93Ti-4170XD=v*KJ(Y*>0t=(8kfrf{D!TLrGqUr}$Cp@ePuExPdalNZ`dsD-r8 in?r|oA0o2lG%nSK^SznFf@KnUs?wiQ+WepN-~R)iPDSnj diff --git a/assets/resources/dolphin/L1_Cry_128x64/frame_3.bm b/assets/resources/dolphin/L1_Cry_128x64/frame_3.bm deleted file mode 100644 index ba3012b7e5a476c4d58f100bc9a8ff16e28a31ec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 923 zcmWN~-%lEM008j22iDH-bR?NuJW+c(CdW#W3JB1r=X;5%peN8vT34J+c05U-O*d;@yWx$5HozO?*(cecOtOb9P@p&`EO#ew@na zA3d|`+nnDFC=`LL@xGU#Q-aOc{WA1oLf2WZ0v+KD>BoGBmG*$@B-G^>o+sRlhn%c{ zB^Meq;rJcjFILI+SDWBn^`8+ebdPSo+!hr*%m5LwJ)tE#`=PJXX$c48%ScpWPsb@f zQVOwuJ!D@Cj&!+_o`4ceeu1?yH)k!-89iw`q$E4l-1|DzJP2Nt^&32BeE%E#Oxm$s z2ieoNC7@>%U1)NK2wxW(F;-!UygG|yqe6E=PHvkjw^S!ms{tzvl`b#BH>=s;DvesW z{{MF@+^631wT~~yA0y#YoZ0b*qoA)z2q9qZ0aqRHZuk_+>-$d*SBIEt3sFJQGYJFC zWNhCR8}l*DsiLtQeQtQ{C|7UnYb)}6(q+_1^G3L(P2WWTeSIFt>8UO995IS0!gRcK z11cncz;~e&6BZKJnWu9|^$5=M^u$mWD!a{DofF%g8yP?NIZlzXTkCs6_Ky+biB99| z>Mu_IIu|64`K>-zp*U7DU3^I?_GJVOb{mU~0g5BVauYn)C2d1Sm1#R(F|j!|x)0L} z$d#~l7V9&{xNnK5Me$?o9W7D3_R(tZ;}CfaMXJm6jE^03Ve)Rgh1WLt7(e$Gx#lqr zH9pCYq##jW<02w^PfxVq_dp}hKLDj-3H0=|&O+C^3_&TkO u!B>y&mLZ0pg#?B-%Q>X*d-qc`wX#V$vsWyO$N#~c`oU33;av~mpZ^Cg14kzS diff --git a/assets/resources/dolphin/L1_Cry_128x64/frame_4.bm b/assets/resources/dolphin/L1_Cry_128x64/frame_4.bm deleted file mode 100644 index 1ce28c7adb733388b90d31ce7bc1d5e8a5998ebd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 894 zcmV-^1A+Vj0D1#}QXrB{1tLKaqDU~1NDLxH1(HIc0P%!K!Q(>2$RZR721bbxq)8@` z08rs5fbq~&q!=1RRsjSSh}dBuA);uaPzV$z3jm-|P9nz~L_$Pm91uKUFjy2IL^!V}{~{_Hpxj^)EDQpmbsSgo z|BsPCQDe+Vl!7HlfWf2i4fGlXM37=i0zDXoCjrEPz&;RIhcNa~DO46E(13XeL%^cM zp)^PbAoe2xfMO}W=V}K8gLXqMJ5Il1yu>guJWSJm(@FFL)gveqLix52pBLIaS%2ATcSpn!e6j;Fr zqbQJMECBPsNKs=7J(Vs2kPoH;14w|L%4bqzjL>o?5f6?e_Hrr|l1KSZjQAc|0*Hvk z{xAs?+g=G8r&DRN@aDL`WVW!2lE(9w)Mhf(H@!KcpD8G72RMBpWM;Jt)BP zhEX6=7$iz!A7m)vL4ye*3}hHFz!1Z*IKx;|QifF+lO)Bo63%I7J2t2!!3I0Ryrg$^jCf zz#&webR9w>p|T#v0!0w`(IF=BpNq?gXc$rKau|pt26^C*gUd*kc%7yW>EmNf6W9Z91RfmQV4{nAz{7?M0O{Eq`@%&afrxd UkMj?d2=*|_Op+oi5P6M82x}2S2LJ#7 diff --git a/assets/resources/dolphin/L1_Cry_128x64/frame_5.bm b/assets/resources/dolphin/L1_Cry_128x64/frame_5.bm deleted file mode 100644 index 4cbc4968951c8b4391c7da7a5a90cd8bacf4b245..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 940 zcmV~$T}&DW007|Q?l?|B?+4tlrFdKqi%ys0T07Q2oE;wbWtuP&H<;dtx?j^-axY%(8X+J`L~)QX_U8Z9ud{ z$>+pFOveYBQn$Ug3U1KaRUQgZ(uYiM9tjX|W|ir+Fo9Y)A60z&614IFo~{;ZsB}8G z>lV(_4p<4=ne-g*jG*{J0+%vKmTWQl?=Wl73gna`S8yez4&Y5;6QHw*o+PKQCFA&v z>Pn8iUM(WsXR1McYhBpj5T?Ofj+i(w^$Gb{Ekwr=Is_ETi|tEL&i{bU`kj`qnA@X* zX=_^{16!p&47ymo~9}?nvBU)V5th52&`>CH?B68DV_ue1Z0Y|}sd=m`$>P;`B9+>0-W?$EqiBJTcng%6I^rVw7^)5R$I52k`b+v zsW|0TBE|14D?*WL1Uy4#3HSV72k*TeC^mH(2tjQ@ZSG*QJXb`2{^qI`xVFX0<8b;% zmP)O-#cf^Acbh{U8iCEDUN^R0h|ETfH%SLxgf?)!MN4N%HAqu`sbLpJEQee!=Y(jx z!pn)ifOOH=!N*2@#*^ZFd65lMo;VrOJ-3~%ToWkP`jF1zqr$1gpYtjm$<0xHG;zZM z41JE?rLW?8qYcC^yFGDEsz&U3u`j)=J7EwK?pjGbD?WV#l@A+cr3z)HQeU^<#l+Y? z83vi*2yFRRvr{knUi6z4wRpkx{Bko}zj<9_C`Bl<74CpCUyj%z`yJJ;uFFy3Sx93n z!EI6w?8KE2M@^r|^$bJ!ZueAwv+R3o43$Q@c59@yIB<}S(QgCS`Ah+`4pKN*C3`>G z0X8>G&wvTT=3%QIDWs|SP*p$BhI*1@^CH-6} z8sRvjnX=J@Zeneb(+ySPWJmCLTJss|d8A#+48_@t=YLJ&c;0kzj4P5zIS<^Ydb;0CV@8DJs>ST3f;!oz4Qq#njP8&mm z4|j#EhrP~gN6cL2($%IhK9#u#qYoY{?dYOrGj2g+A}O6SW-+iX4O`;o_kqBt3O?#Z zy{|zO%#%O^lPe|j2@k}h@-3jD)Eb66(Rs>kxy;_2fAg?}U|V56H2x`~b>r`_QP_da$rUj{+@SF>ERH?Ar$xD$G(q!Iu3& z=AhSa?sDtyU3N5nnF?^=HMz#d(oZh_^?y;GZ^^YCx=uUhb z*rJtC_V9yegYZ@iTzB(ns?pDdzih%yBL3cf8-4BqxkJ_++E*{}i9*;dVIpiD1mvdE z7c&Gv=0mgY!jG2}o8T!fF{&x$yNAduRwusRC`s&?xnPMca21^r2;Bl6PuVQ>DsI!# znEg$zz1fjM`hf1?8vwSUP?n3h=8o#pPa$u3W)0G_ZgYb!n{ZWFLE1pI%<$1kyQGe%XP&cIJ^jLqS-rA;12@yr8SeWpszx|iGk7$a@z0+K>dJOmfhpA^% zp`zMER+wYtqd7%o@3dATQi(Z<_=sxZB7EJih4VkW(?%qFfF-MZ1NfD~P3uoAnWu1Q=4FIwVrhUItE9sJ{Sg>VWYLoBNZiP&N0vi{VmU zlo6L93Ggo*x&0_h+v`^S;Zz!yC`rNTB(T^K%_YPMq-) zLHj8>d8J~H_}h~|L5QoC+jNmv$*&xR0`YM68=;J;7$Ad_T{PAmg|^g1?JA<;o3aAwlK%8M1$Hce;#> n8<&_P_`=$s9S@Xh?bL)CyiFcD^mvAzRDm-T{+8Av+L!(ZB@#`e diff --git a/assets/resources/dolphin/L1_Cry_128x64/frame_7.bm b/assets/resources/dolphin/L1_Cry_128x64/frame_7.bm deleted file mode 100644 index da28419fd279dac8ad18145dc9a1385df41ce7c0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 934 zcmV;X16lk50HOnsz-kZ(M1mp+97qUo5J?0QMUkjTDKZEx3I&0PKp`+d(7=O31fa+R zBLalLU{HW~;-FFtKon{cAQ;53p}~MeF;oZ^A}JDzNH~-MaL6i9ff$3v6Cr_7BpBKt z;9!voNZe?YD9})NluS(mB`Si%xCF7F4h91dz|kNai6n@6FalXYDpCm~B=#VYkV6c@ z1rwOWs1giFWN;_4kYJz^oJ@jCN(Kq-AyP~fLQw#qz-U4tg1{pOvVuV(GMJSH2|}bq zFbA=KKygr+Bt|k1875I^FcaA=&O*l;4ImyzvaDb{pZE-lg#gGJ#(^Z2Kuu!+{(tH6 zD1?(B#Suuo#z27>`~v+Vkw_vCi4H*aoQNC);R4V=HWdg16i;X16ORH4C=-~%G(eF( zl)wrw2pp1;B!IMqqBy8Tk}?k{H4qLl3LJuxe-qflLFJ63LM)mXlRQWx37|p2D$)hA@jDQ|FpU6CaMgfJ$u_5tN3?zy_5P3yl5jYkGK>(#mBQ7Dh z1IFYZ7?^x%FbWg`Bn%(KA_3%xz#`Bn5Ugrc2o#0@k1xQeV$eol&@zyK_`xOuAr_1R zO$CWkF#z$ALZCk-aYG;wWw_+90Pz$FFjwXe7zBeQ0NN=9sxtt3z`^ARh{-8f2*5od z$FVFH0*M604#WU->J5l@XG35XnzfWlGC6#_vJpg<@v2vclh5FmMFi(|1VjRFZo z2Z)3~12i>^kU%mF4k8H$;0uTzSc*WHWEltq67M+R1Io!1h|)9=C_n^YnCL&?9&&(q z*su_c6e3V>8TdT7hq1&_Gy(C5$|oKN=!4DxtPqgNA`%~`{vHo3B?>_iqF6G3L6G=A z@GyCz#vz2H90ns0uz$_x1O*HR6F|sHBvmT~@qTba*ubz92!sX&5VJGV2zeP6BY^^- Iq{he(P&v{|ZU6uP diff --git a/assets/resources/dolphin/L1_Cry_128x64/meta.txt b/assets/resources/dolphin/L1_Cry_128x64/meta.txt deleted file mode 100644 index 1b7d13dd88e..00000000000 --- a/assets/resources/dolphin/L1_Cry_128x64/meta.txt +++ /dev/null @@ -1,41 +0,0 @@ -Filetype: Flipper Animation -Version: 1 - -Width: 128 -Height: 64 -Passive frames: 8 -Active frames: 4 -Frames order: 0 1 2 3 4 2 3 4 5 6 7 6 -Active cycles: 2 -Frame rate: 2 -Duration: 3600 -Active cooldown: 7 - -Bubble slots: 2 - -Slot: 0 -X: 22 -Y: 40 -Text: I miss you -AlignH: Right -AlignV: Bottom -StartFrame: 9 -EndFrame: 11 - -Slot: 0 -X: 17 -Y: 40 -Text: my friend... -AlignH: Right -AlignV: Bottom -StartFrame: 12 -EndFrame: 15 - -Slot: 1 -X: 2 -Y: 29 -Text: Why are you\nalways away? -AlignH: Right -AlignV: Bottom -StartFrame: 9 -EndFrame: 15 diff --git a/assets/resources/dolphin/L1_Furippa1_128x64/frame_0.bm b/assets/resources/dolphin/L1_Furippa1_128x64/frame_0.bm deleted file mode 100644 index 8558f0f5bd29c5b76d6c222d7c85d52ab284e154..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 294 zcmZQ%P-0}LU_Kzn@PWO-hVcPEGKca1{|5=LKR6@^KBzs%;QWF8js=SaV=L1IH3#+w zjC=Wa$Lk9CK2LLbCB`HWC7-b4J-I(2#5*^aR#jFO37OA^}lG0 z+|rGb0(J=^42cT^7&dV~QQ$D>eXOc_{ROn zF@fr@3~l#CYqA>JnHt;}--j?xV@OjHWH4R8=5nZ+`OXCc$HW5%-!TYew6i2RaBQex qY<|G*k(2y_nIWe@{^$!y!>R_h^8##l82@^(f12R{$K;eQ(miEr(rzy}T-4@vmuCID3D#ndPOCK}D<0GEJ3z=sR83SEjo^TZX!IK-3> ziNINn5C^BCGGK6e{VeAfmnYNr-w(X}2yp-9oF4V?A^-pJuzS}*>FzTkF3V*x;2tB9J0MLWRxxj&o+Jp}~{F*?nWid2Jyde1>pZ*FDh=qV2UziX+ zED^wYUSK5g2etzS1TFe?zy&~B9{?XR02xcc=YWHh6b4apN=5XjI9KX?h!Y8XA|2#F`05eA_3Di@J)Nb`;Ym%t`9dA(X8 z2xA!h!RrWO7=TX<4>Yid1Rr#W1wpVVVUYvoKo7<-s>ojD@ogA9WF`fO0g{*xBzr)@ zLIoR`JYp*P;9np(2!R2B2Oz(L#EyUig8+xIdw|dZ_vE;P#t1?X1b+8pA;ANu0sP;Y H1;juD{u;iw diff --git a/assets/resources/dolphin/L1_Furippa1_128x64/frame_11.bm b/assets/resources/dolphin/L1_Furippa1_128x64/frame_11.bm deleted file mode 100644 index c91ed2fd23efc317a9fec7f9244f5ecf26bfdaf6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 698 zcmV;r0!94+0JZ}EfFeKmfrIjX59|Np9uLp{LL3k1U_W#*kbcNuF#vwR<^cS;@vIO) zd(Z#CF}ve9B@qX-JRV&5#tfn=$M69PXF`dC-_IZZ0Sk>X2aoI?k-rCw2tNFDh(7RG z1aJsF{OCR-a1Q{$x3&;_+0i069sK5s4--KE3qB7Yqr`7u@&FI;)>rrp1JI!=ED16{ zi|2eOP$&mL2sL^*JbV9r{(!)9X&8`x@E~~0{Q&eFj|1S45O~kx-2i$rMgb51`=XFY z{7@c~_&7X%AB}K5FZveQ1B3uPee_r!fBx`1X5jOjOTpql6S(+u|M*Be_!7x95WXpK zPLKW!4>$%49$ov|$bZxR2|R!U30bH0tOFAfIQ$(2*5D`?RtQVL>}ORfY^cfK@wo4#6MrnKs|tmU<0WTLH;XP z27sI(^o9V%@FvkNl$ajh@M$c>{x_Jwy*v;P#4Z6m2#gaC7YC$sfA@mGI8b;bBL5Hv zl>>p}ulg3lJ^Tm<|BIl5$ALfoSM3lU2l@jagnjXN6H#zK_4KgJKlR@P9)Acv7K8uq zuhJkK&-_qkJ@}dkx*z}k{!f7M-}}L#m&pO;={q_P6AHB35AhFN`%)TJ2HpaDrD0~`qk(=2CEZ z0f)!)C5Sy^VDfDd6n=P6c!CNF4@>zJeV5sIJm5g`(0}*gLcxQ{tS}II8iWXe@eB|E zXaM&oh#-OHDi9%W;4^FixNpGtzyG;6_}9XI3LFReE(ABI%&-txX8^(=_6Q6zF#`F| z%pwXHSUm6OQH)?em-v`H|E9r!(1@c42fM^ULUj;%Vgc))|K1Fy>Kb?a9=!ZL{EAT6 z1w0;~ugGj@5C#axa1?NQe1YPw7#HjRdiZJo@l^SIcmMtl`T!3dm;X)wiUZI8-@skq zAn}sVy5IRje7^$Wz<=Pt@uKiS==&f4;{o%GgT|Nt_kZdD@qLIqwsLTI)$jj*{(v4o zPrv8!!7vCsb@%_@{(}R60SDkA5P1JeqyPQ^#q2GC2cG?kAOw2~a6#jN_us+G|A+7a f<$u2eTmSC}k$89X5PN_N?%5stv_aF0B#1shQS}MQ diff --git a/assets/resources/dolphin/L1_Furippa1_128x64/frame_13.bm b/assets/resources/dolphin/L1_Furippa1_128x64/frame_13.bm deleted file mode 100644 index aa5353e988d02785f45759a024931abe7d580086..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 584 zcmV-O0=NAE07L@+fII=<4*+-rz#ahb2Y@^IANT|F;Rk?x!S}#`z&(Kbz(C*~0DZs# zKZbz<@kP z-_ZGk-W5OxmVe$YUN}5vRseX=FaHn!_#S((c=Kc+^8fh5iJ!HD#(O>ojswBL|KI=9 zyXAl$Ey0L`%fG>Z-}v0Uoqw17zfWUvCftXMrUwwc* zFdOIv=nzr}54)K^iv!95wSa!G^7S~tJCHd1P#$^^c)jswmy;2(?+ml*DY%6Tpaje?1R4~z?+C+R@JB71OJ%^j$i~g#Q}fdP!0e4@$U+bI{^s# z3(Nlv;0TA07lYt1>XG_?f57p%{4ekc#4b7D#lRpxNFUrDAR+mp#tHC`{fFbU28aM| z{|{g!U}Yi1z%rjedF|ox@OZ&E5DPI#e_na`JbB{LzW^w(J#=94pcIrmEe`+y diff --git a/assets/resources/dolphin/L1_Furippa1_128x64/frame_14.bm b/assets/resources/dolphin/L1_Furippa1_128x64/frame_14.bm deleted file mode 100644 index 837c6c71defd8be280776e7881817fc68276b5d0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 610 zcmV-o0-gN<0A2$B|KVUk{Q*P)Fi?2?0rvt2kDwoLFnIg{_W=j^KOjHgdBMT)0R98a zDj@jq1I#KQ_}~M|;Q;Id;{Xp%fIlVx`hWxc9=`B>kL(_GhKz#+o><3>gVG5B{Tt>8 z4;U5;!}CwXJVamY_&z)lfe}FLAMe0Dt_H!Nasoqr`-9r~&_@VJU>l>0Krj6W95O)w zgJlpe;FN#haR>y*&>wsRUrhppDF-nZ{sYsG|A@(;VDX@2Js+@oPGA%6%nc+tE0kSi zuxJH>Lj(tm3qcDYD|B!X2tYh=AoBP#)&UWmL9h@2c-lSS_KA8RbO;895vb?`+9^1J zKqL|g{UbmRX+$ywAO;Ttt3U_0LJkiYg@OTE2b}yKQ3wz`aF!;BgU_B1DFg(5A@ZVV zdEoJzgVn$w5#o2S;PHin%RrqGp+G;L5P3mC<-mYJ58@9VCwyLBV0pmdiSdjD2SN@Q z8NBEa$N2-wgZ=&g0qGDH4O%!v8W;Zm|NDc@SRg@@1ImB)|6{@DKq!m?&N3Yo|NZ%| zgUlq?4N0o0xY&yEj|qzoQt^FED>06iEa;GRJCuV4t{^!eXG#1C2(0)+vGdoW=5 z-AFJnC>}#T4`yhz^{KA$Q(_^4>({vevs<0996^* wNHeH?5Gg=DU|{j6KxP1O`UB1b0N_Ay5Dfv)f$A(Dg8+f^cz{o!766F@+?Nvri2wiq diff --git a/assets/resources/dolphin/L1_Furippa1_128x64/frame_15.bm b/assets/resources/dolphin/L1_Furippa1_128x64/frame_15.bm deleted file mode 100644 index 4cb6e53325ca82bc1ba52fafee1ac42a1860a50b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 719 zcmV;=0x2edpMFaQvP#()oKH^LrM60rl^I503^{{iLsHy|)^ z0P@TO$c~T*6avV`F91BQ2NQrnAQ7N>U;*j-2b3ZK1#_7gbPDB+U69S%_nXL) zC-VRZJnv)jfk2!FPbpLAzn>Mrz?udRAJnuSUypGxMd<{tLA* zrXFq&{(Uec2t8nMZU^Um&;LikL=Pb#gU11WpyB_e!8i^9U?f3-fro8?!+z!9@{kxt z1Au`*;1$q#@xkK^fn+M+^1?tt<^J!N#5gpGLDXAf4;s)scmMbcfMkji;6cIZe>Vs` zWDyAj0>L;3j_JTF@t8;f0&ac3Iqm7QxJHZ1_AZpe1D;lp}-`8@&O7E zf4~2Td|(6-v^gM1u%*MM5rYZhlmIgItVWU zM*v6&Tz~M)@I2@cw)t=ZxC7z zA2bOE00<&62h@N_MMD3>!RtVd0SE*!_y@oqya2gi1HuX-s!%Wq5(kL{%Bg_BK>)`F z0hGWVuplXB7K?0mW9)pqY1KH}Ok%5B<9%eKGis(y(512t1 zV1o5Im^_lA7$AJa0isACd4S9wN1+GifZ8p>2hD&2D1rsA;Y>{dhy%&&jsbR%2w?#C za0n!fqF@vd!9nLh_()r-01$a420;v=I8aJZka|FG^IL|R3GfZ_(Gk1Pbd4kST{ zMsfi11Bg7X2ZO{(1R}5@neYJv=m)TLP6L3zL7)(Ddpk-XLm%(`ia3bCJkmrDD-itP z9{_m_j2T6gp61a(gZ{ZM1zPgjQn6h8mzDgPw@^3 zz}F@lG7%UECQ4EVmjV2M^?I5VRtF3sqyij97+5^uQ9ycvN8SY{0Kl$5kq4*%+69tk z2m(_@KO~R>1LeVjgXn4{VF%kx+rka7e&8S}(IJomV8H&oCqy1_6AdOzA27gr0I%$+ AWB>pF diff --git a/assets/resources/dolphin/L1_Furippa1_128x64/frame_17.bm b/assets/resources/dolphin/L1_Furippa1_128x64/frame_17.bm deleted file mode 100644 index 02af0bcf325201270bdfadedbec283ef88d23ca3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 400 zcmV;B0dM{R0E__u6hI%*fPX>)+z)6lKcGPUz(7I#PwD_arU3pz{4gKTMmK;k_{2d3 zDe-;_GJ(K9|L_=t*8Igk0EA=6#lS<%$2b$10XzUQ!9pRyf*1$@!z2$7S_H`=&>jM< z!s7x5oq?9d6#|P{0)!VvvyBn0Ld9!N0es2xXIKD?hHNkcf>bJ^nHs1)f5le^scc3> z4<_k=4fG(wLZAU;7sx;mVIRmiJ>nnuh!AlQc;G$qANZIMe+_j29wYxF0t`96e+YOy z`5!qud`_ZP5CH#(h7U6`01AE&KlnW>;6u0r4{JaeiG>Chtc7q; zL13H+9^e56h;T+EH$XAiJOEMf2b8WMQ;(nw0EJZm6mSf&0DG5>a9{?&7mP;0180j7 u0|XCSfDp`V06IWm_y=K9vXGzv!6FDmf$)kW1cS%`P~bR%6du3>^KMRlm@CSfA0hhpbq?iBy zfrHw>1U~>!W5JH5eJR~-y#2rfd}y7oJamf1Q>IQc^^4Dd`_ZWV0xQm9$)Z!SHb(hAEy8x69Iam zOBw%{f(jT9T;K?EQ}fZl!RiVqqNK+He{2K-RC^G)$P4e(0vmi)@u~tG3IP}gy-;dn zF%XCWtpGA66d7V;Ljw#DSa2}=fCL&L!5EMNEFJ)0_yN)z82IHt7aRxi1V9;pFHiv4 f;sD!#KdAuzRg>(&`v8E*0s8e2eMA64*bo4KNFID{ diff --git a/assets/resources/dolphin/L1_Furippa1_128x64/frame_2.bm b/assets/resources/dolphin/L1_Furippa1_128x64/frame_2.bm deleted file mode 100644 index 94357802fc84c61ff65389b3e8e592c882f7dbf8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 351 zcmV-l0igZ?09ye7cmu#50PqKZJOSVj0C)qy9suwMfII=<0ssHtVC(QeU=iSb&HzL3 z2i!OWI01zM&?Z0!-~kRW51RlG<^cHxAW;AcDnX(E78q1Oe8BN!R}iTkwCZr_kd6iL=zu`d>--(%?dhyEu35Rdu>4}3@dMx-zKoNNH~<27iUx!LC*lA+s1!%z z06$O$N8q_n<%^kOb)bFk~5wa1*0=zys#M xtsVp)fDe)2|J%Yg5C*>#0W3lQWN;tNQvg>1!5{>@3%Md74DbuO_)Y8hDgXv`km3LU diff --git a/assets/resources/dolphin/L1_Furippa1_128x64/frame_3.bm b/assets/resources/dolphin/L1_Furippa1_128x64/frame_3.bm deleted file mode 100644 index b0d0e691407afc3067f1adb44437407fb75e695b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 324 zcmV-K0lWSI06+l%cmu#50PqKZJOSVj0C)qy9suwMfII=<0ssHtVC(QeU=iSb&HzL3 z2i!OWI01zM&?Z0!-~kRW51RlG<^cHxAW;AcDnX(E78q1Oe8BCSxu9Tz!-0d`03gv02*iL9VDJM6zz&eu$Iu1<*x)~l&@y86 W01ciX4Y&jPkPqb`gZv-h{{T3j6M%aF diff --git a/assets/resources/dolphin/L1_Furippa1_128x64/frame_4.bm b/assets/resources/dolphin/L1_Furippa1_128x64/frame_4.bm deleted file mode 100644 index 3413e507294a89aad208259baae4b7e14c2fed36..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 387 zcmV-}0et=e0Dl1hcmu#50PqKZJOSVj0C)qy9suwMfII=<0ssHtVC(QeU=iSb&HzL3 z2i!OWI01zM&?Z0!-~kRW51RlG<^cHxAW;AcDnX(E78q1O89{;T1P7!C`45Bx)GV+V zJmMe^SP&IBa2WtSlS^`dND1uC5CO)4_5N5S{2t}8859eE4|oLt2{}X@+NBsi_q~snrkNk~DU-LNF0ppx47bE8< zU>E)9K=FoP%)$>&1^fm#3k0eihX3V`WjVd+2l)@g z1Jtar7(C)2rw{?afT_cP$N}t{TakIiA_Yj$KEKNZpM%`CBO-xt0q%z*kc7wK9|ydG z`3MP@;vNi1zzAX=_=^y~kbedK%>c|wLF17B#NYxE|3Ja%IR}m-|07Zt{LVH2c;^d6 z$oa|G1^;>wJYkqKFoV;9e*ulc!77Jg!R7x4mw^0Ji-f8S-5`1&jy*V#LDb?8Q}K8q z@#usO7z!L`{$2zV@Poz&4u%8IH9tm)_?6E9iUx!LC*lA+s1!%z06$O$N8q_n<%^kOb)bFk~5wa1*0=zys#MtsVp)fDe)2|J%Yg k5C+%*{KOyzM*;lRFa>ZN5&%oUyOJUR&j7oxgxI3xgWa$Lk9CK2LLbCB`HWC7-b4J-I(2#5*^aR#jFO37OA^}lG0 z+|rGb0(J=^42cT^7&dV~QQ$D>eXOc_{ROn zF@fr@3~l#CYqA>JnHt;}--j?xV@OjHWH4R8=5nZ+`OXCc$HW5%-!TYew6i2RaBQex qY<|G*k(2y_nIWe@{^$!y!>R_h^8##l82@^(f12R85=KBE9Kzz`}J1UMKzV8BQKC}{+U0724`4BWs}hy&t)1xbKDEC5uH1K<*& zRTR5KMyg4E8C8uffcQXg0QiI$i}?cw%4DP;=sp7je+RS>9*_@-1=%GK{KMc8_mjYZ z>eUyM1JC{s5HN$-3_$!F5IoR=C?I?Pf%_N-od6!d0-8@2g)j%qq7d{bKt5`UGE0a) zbq1q=o7g^JqF)NVgXRh;0{y@RP&u1@&}{^;1L=Bm0DZv)qLLs4#wQR6U_Sg1ba;d9 hyh!N)A-cc^a0l^w00j;Q@jIEc5A$+^{2$%`d;q4lY*+vQ diff --git a/assets/resources/dolphin/L1_Furippa1_128x64/frame_9.bm b/assets/resources/dolphin/L1_Furippa1_128x64/frame_9.bm deleted file mode 100644 index 114b26391f8643130725f03b5209d97ec8e791d7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 312 zcmV-80muFU05kyrcmu#50PqKZJOSVj0C)qy9{}J$VSx>WL;?FSkOOc32Y^r@AJ6zg zZx9F%f(HPHgW~}KfkyzNa2Lb^4S){_RDtggh*$vU0tdhTK^Oy^5FZeDFdX3k_{WZp z;FNgzz<>q#kb~H^GXOFDBxnd6r_zQGf^u~z?r{gO9X^zK;9Vc&1y3g)261O<@i6EY zJS-vXAoYj=mJsj|Ldq8l&;blEKCOi!lqlUm^1wnt34j}{f)QXGEdl`)1Ej(@IC8r1 zAYk=-xgo*uNEd996aa-)W57N~3{_FYl~hqM61xf{{eTGI0o&ki(y)of90xeSK4K_) z2dQviVEsC|Jjdk(up4#=_JONI0s9aqI03v1&Y%PP0L{2Vx5I>AjsawYgnx+xAb><* KfO)#k4i8}640;Rz diff --git a/assets/resources/dolphin/L1_Furippa1_128x64/meta.txt b/assets/resources/dolphin/L1_Furippa1_128x64/meta.txt deleted file mode 100644 index c21027e49cb..00000000000 --- a/assets/resources/dolphin/L1_Furippa1_128x64/meta.txt +++ /dev/null @@ -1,14 +0,0 @@ -Filetype: Flipper Animation -Version: 1 - -Width: 128 -Height: 64 -Passive frames: 8 -Active frames: 11 -Frames order: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 -Active cycles: 1 -Frame rate: 2 -Duration: 3600 -Active cooldown: 7 - -Bubble slots: 0 diff --git a/assets/resources/dolphin/L1_Laptop_128x51/frame_0.bm b/assets/resources/dolphin/L1_Laptop_128x51/frame_0.bm deleted file mode 100644 index 5eb2bdd05d20de1a4abcae2b7f79aa7c3277ec5d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 555 zcmV+`0@VEh04D+fYy=2+2pBoRC4hr~h6E3)J|YABhlAdM1b|v86Q~@(9`Xo81L05S zKsW&Rh(sVCdVIJJB?p8^AQxN2cn%|B5sV&r0RDgf_xrsr92X!E*dY-xvCu9Qh%nrZ zMUV;)&^|Tr_@77~Pzs1WL*fr2Fn|W6EfD&w$R2e-xP#8?NC%Yw9$tVrhtrV&z5~y} zc4QCIKky%jJa9ns%zJJi@$>V6=K+9&$|3vSKz^US^?f2Z3xo>*QY*ZA2lW2mtKzZ1 zdI9G^w`0ZQ{{L5l<@|y0A?fk_Q^DvTAb89a1^1i4E}#GPb_Nd{cE zZ!j1bJbWJ*_&qK_@&9-h1%UUE#2(1;fKQZz{#T9C|NO4}s3`KlNMIa}KRyR^-~NN+ zLS>bM_{kgn-VJo`{ek0p2b5v`@_c_Y z>ix=8J|d8T2jPDQi+q9S7zd>N{($os2n@d+_jt$t0P&3i%YY9*`8-aeU;nLyfM5as ze}&=?C|X8AGkb1r2w8lz+(spoKN5nRZ&C%<-m;OAi(pI1I7&?Bph>K1tKx1 tM}R3V1b?6kAON-q4wL@`ygXn6KZ$@qLm-enL;$`3`V9~P;12+J0DzyW_2~cr diff --git a/assets/resources/dolphin/L1_Laptop_128x51/frame_1.bm b/assets/resources/dolphin/L1_Laptop_128x51/frame_1.bm deleted file mode 100644 index 210f0c918cea285c191659adaf8d55e740f72e12..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 557 zcmV+|0@D2f04V|hYy=2+2pBoRC4hr~h6E3)J|YABhlAdM1b|v86Q~@(9`Xo81L05S zKsW&Rh(sVCdVIJJB?p8^AQxN2cn%|B5sV&r0RDgf_xrsr92X!E*dY-xvCu9Qh%nrZ zMUV;)&^|Tr_@77~Pzs1WL*fr2Fn|W6EfD&w$R2e-xP#8?NC%Yw9$tVrhtrV&z5~y} zc4QCIKky%jJa9ns%zJJi@$>V6=K+9&$|3vSKz^US^?f2Z3xo>*QY*ZA2lW2mtKzZ1 zdI9G^w`0ZQ{{L5l<@|y0A?fk_Q^DvTAb89a1^1i4E}#GPb_Nd{cE zZ!j1bJbWJ*_&qK_@&9-h1%UUE#2(1;fKQZz{#T9C|NO4}s3`KlNMIa}KRyR^-~NN+ zLS>bM_{kgn-VJo`{ek0p2b5v`@_c_Y z>ix=8J|d8T2jPDQi+q9S7zd>N{($os2n@d+w|K|?0P&3i%YY9*={!!OU;nLyfM5as ze?{UCCL3O12hu;lFCYWJ9su|N)o=Bl diff --git a/assets/resources/dolphin/L1_Laptop_128x51/frame_2.bm b/assets/resources/dolphin/L1_Laptop_128x51/frame_2.bm deleted file mode 100644 index ff2851c280846f4470944424c7acfaee9acc2d6f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 560 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 diff --git a/assets/resources/dolphin/L1_Laptop_128x51/meta.txt b/assets/resources/dolphin/L1_Laptop_128x51/meta.txt deleted file mode 100644 index 90cdc5ce9d8..00000000000 --- a/assets/resources/dolphin/L1_Laptop_128x51/meta.txt +++ /dev/null @@ -1,32 +0,0 @@ -Filetype: Flipper Animation -Version: 1 - -Width: 128 -Height: 51 -Passive frames: 6 -Active frames: 2 -Frames order: 0 1 2 3 4 5 6 7 -Active cycles: 4 -Frame rate: 2 -Duration: 3600 -Active cooldown: 7 - -Bubble slots: 1 - -Slot: 0 -X: 60 -Y: 23 -Text: I have to rest -AlignH: Left -AlignV: Bottom -StartFrame: 7 -EndFrame: 10 - -Slot: 0 -X: 60 -Y: 23 -Text: but not today -AlignH: Left -AlignV: Bottom -StartFrame: 11 -EndFrame: 13 diff --git a/assets/resources/dolphin/L1_Leaving_sad_128x64/frame_0.bm b/assets/resources/dolphin/L1_Leaving_sad_128x64/frame_0.bm deleted file mode 100644 index d4cf85bada7670d1b6d1fccf281fcfbea3533e1e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 514 zcmV+d0{#5~0R900HUJ;s{|ER#!Tt~MGXFo|{elPhAK?E7_&#_a|NJ~3bO0E_0r~&_ zLc#fgf+_1S0_hxEt6Wya)gUQR*)_1V+FO z7z#dS009CF48J-Eq+kdzV0?#$7!SetgUT2Wj0DDM1_$24#ZZHT@8NXYz`)}eH^;O4 zln0Ff9!T>Cm&*HI2dBgT|Br*uiEkH{03I;_V1S4oIgOnh9#a7PNI!D&c}#EsF#s4m zXdk)wKRg0)2jqdcKp~j}{2rI#`GeAE4@?L>Fc9#1l1u)|5J30??Qjs2sGv81IbH$f z7z30R03sOR9J>JWrT~WkKM=5p$R2=lpaaN!a14QHU=%PA$N}YmfP>co!TVQZpz$$? zJmMHIIHpPw{5g^&!Q(`-KaPz=2dsk)nViA^!vf9bU>pE(LIL9Kf8&^R9y0LUa6k1=GY_X{KMX`JK=Ot&20jlPVfbvp^#_wQv1j4L E03aFJ-~a#s diff --git a/assets/resources/dolphin/L1_Leaving_sad_128x64/frame_1.bm b/assets/resources/dolphin/L1_Leaving_sad_128x64/frame_1.bm deleted file mode 100644 index f1f0e89f0bf92ab9618b0b7416579212bc000e05..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 526 zcmV+p0`dI;015&CHUJ;s{|ER#!Tt~MGXFo|{elPhAK?E7_&#_a|NJ~3bO0E_0r~&_ zLc#fgffO%jd@DIoS1`k?5Jg5g@jzA#h^#`;X0Srf|yyOuW z1P%chN6f-ufGCIzOYxwJ1N1&T03g@_@;?xMU?ww55FB^__}Eyg7f^ltE}MHq0vrz` zdwV~*Nz?)(fWV{B9$zc%dTI|phyVW{2cHt(x13-k0P%=1v0-uyo>ePBWB90!&h3?!2O!Q0{p7_dCC zpkcTON(=%R2tDHf@`AtwMH2^>WEM2Q5a0*m79IdSg=IhomxF&H2aW?^2pSj&WB~FK z2t9BdAGLNm4-+9s0px5X$wEJX;sgu4ycA0#_~_I?d*){_fOx>OWpW>gJhl=j(JZhs zIS_oPAn~AVIgrQ@WOy9TXNTbjqrf6G4CF=*s2T-=A_*D<3CuAb20zRwDgDCDW&;)c z9#}s>cwiRH#v>4rD*!yO6Tj~B_v4;S=3w&RKp|EP2fGDB;&>?ZN+2|6;Pj2xeUtqL zWB?^WkORgEVDNb)aR_9BL;w?@$HC%4AAww;egp9H90pn$gh3`M(U>2GBG52-z$4)C QwjYMfA8E#S?_px7MM3xQx^3kK`}Tiwm85(F%jJEqWm+YG_<#TL@Okkq`+Gn<;s7xL5Il1m zIy_WAau3|UL_ahkgU*5bpV)!-;1i7eA94kW1N?u6bO-o8K?J8De83Tc1R#9>;NT$- zD*zy&ZL*Z{%g2S5Vf!RJH(Fg^1i1{$C69#S9!3=ifHE?@z?KL|d% zm=7q30jLxR6_aoeD=-0!D4-k$AOOf@fdj^70APEc-T}a0h&<2nK#&8_{@{_1gV6rt O_dmJ4&+Z;K;UD0o(vidf diff --git a/assets/resources/dolphin/L1_Leaving_sad_128x64/frame_11.bm b/assets/resources/dolphin/L1_Leaving_sad_128x64/frame_11.bm deleted file mode 100644 index fe93787f2dbaa45e6546910e547ae553947e3827..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 294 zcmV+>0oncm03rbZHUJ;s{|ER#!Tt~MGXFo|{elPhAK?E7_&#_a|NJ~3bO0E_0r~&_ zLc#fgf2W^ zzlsC&KlAbX1LA1{SLOPl6%XYHZzwnC|36wsz&$_tR*7If0CA6l+TIU1fDAxH4@mfb zwd3Hq#h!Rdepj07Hd7$hhjgMtU=fF3X~en5Nx{D^;}2uMGSqz}6Q z6oL3I-A zU;%;e5dbi}_5kv;02n@jLBL`F3=cTO2p%&q0|VUt@Hjv{Pw)>IKs?Xz!Vr2N;1(eC UKf$bFAA@-x-vpF diff --git a/assets/resources/dolphin/L1_Leaving_sad_128x64/frame_2.bm b/assets/resources/dolphin/L1_Leaving_sad_128x64/frame_2.bm deleted file mode 100644 index 3050ba38f22ac4c8f1ccac17830bfd3455abaa1f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 542 zcmV+(0^$7u02%@SHUJ;s{|ER#!Tt~MGXFo|{elPhAK?E7_&#_a|NJ~3bO0E_0r~&_ zLc#fgfqEK2tVN7p!VtSN3MM3xQx^49f z64)Ex+5O6Pf#{Dgd3>+6?yMXhUl0HOJ`X-6zi()A7Q!F{5D^2%F|(u1K?EO(2ku`Q z510&q1P>Yq?tcyh7GNI!0XUP0A2I+StBHOf`cD)CI1M}S{K4z82c85Tm;~`aJU}5N zktnbL_27Vp;07X5KzIOhzySsw7z30R03r~1zyO0grUi(8FbKdr;vNhIgjQ4ld3Z17 zQuuH994Z(rWB~GjSO8_<&)6`WAGLNm4-*)N#sm`_1pknkC`a(-Q}Ym$hXWkAKaPz= z2a*U&1BZmnApl{4X7eCHm@J9`{)fg1Wr2~%gXB0U2jV{rB8pUboX-!!Za5?fLI|Tk zutZ`s3Ixb-K=SwsO5tX+0Z1N^i9if(nT$pu9#Ft}&p4aU-;Q}RnS;_`1s{JBhs5wv z=#)fI-{HV9dJJyI?4Rf}AOR{5f*u_i_v6Vwh(jb2AOM{QKN~+1+`sTY0r+`^qX(2< g0F@XN!2B@~xkuJylMpDtt%u>W2h<)+(#4;L5FzQ{VgLXD diff --git a/assets/resources/dolphin/L1_Leaving_sad_128x64/frame_3.bm b/assets/resources/dolphin/L1_Leaving_sad_128x64/frame_3.bm deleted file mode 100644 index 0c0c832385caef6fc7bcbcfb485699100bc5df3e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 557 zcmV+|0@D2f04V|hHUJ;s{|ER#!Tt~MGXFo|{elPhAK?E7_&#_a|NJ~3bO0E_0r~&_ zLc#DX932A#1cd|R;s3{j^YQ=BgYt0z&jaQHJ{~{+LJv#8Jg^aQaR3H_4>~|Rs0U$k zhyZEk^9Q&19#!;ti_Sq2fCEG%H}reVCIIn|kcR$WeFRV*Fm#Cn+&*7890R})f%xD) zC?W^G0%J76d|D@A(0t2hK6TgA2?39|xQ;9$pFj8IWHH9v~i2 zM@g=T0Ca$QtxgaRI$@B%fYJw%0N@m0c~Pj8KzWQ3h7T;(XEF&a8l;b7BL|d-03q_> zhmXM`LKi!k%<%jm{xEso1LY8j#9}lG1PkU5Kk%TZ_Iov$3_$bXd&q?>#NraA0PhEm zcm3Xe{Btk>g3Fk$E&sa(L*jTSr|}ew|9~zm3~syZpXd+5e;NdU9)1rbjv)*`njmA~ v^Y|&xz}PCWh|3 diff --git a/assets/resources/dolphin/L1_Leaving_sad_128x64/frame_4.bm b/assets/resources/dolphin/L1_Leaving_sad_128x64/frame_4.bm deleted file mode 100644 index 5e74ea12ae20161d809bc90a813fe9be78929a4a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 488 zcmV<%yeeI8=-kVJ3*^S`6sVK7#T|)H4`lBX0Km`YpD=k) eBhms0%n#(BICwm~5%75159Hf0d4Y-ri$9QvY}0N4 diff --git a/assets/resources/dolphin/L1_Leaving_sad_128x64/frame_5.bm b/assets/resources/dolphin/L1_Leaving_sad_128x64/frame_5.bm deleted file mode 100644 index 5c556cfdfd3f46173b2f6eb279797637000bb792..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 469 zcmV;`0V@6h0MP*eHUJ;s{|ER#!Tt~MGXFo|{elPhAK?E7&*B~r-}nFi5g>enh5-l% z@AeiCt^hn>@SuH&L?4q6{r(6Kvhhd3?Exn!fI;JU2bKaZ4=(^bzu@tt1ImDQAbOj` zA44Jsh`i(x5P8qaM2}g7z@BmN82K!NwL zu~Z_U`}kcp@_>0y3GeLwR;45T*k7|G~gQ4?ejxO?ej~aA`OqCi4>)!L2OvL?oMYhjxF|&aL-?6Ppb`W58`K_LC<{bSXD1+1Da2WmI9#{ytJ%6C_|I$I_K!h9E093Gir^bE) z8~8k=5fFP5NBsu}l)_*SVz~bS=b(xY*a$y}7=BCQ1RsP1e>4gR5Ml`Oz)WV4AbspC zRS2j(EFiH5pTg<4lmpU!Ul4iwKe$!mPh|GQy|NKM%GztU?$+!oVnoUsPU@Z1)F@+QZ zfW%rd0CC0QF$n~L1IA}_h&D!LGns?j@-TpS(SV}>;4j1;d78*Pvq$ovNCD|0@eYbc pg)9Vo9*2KBl!o7_$TtjQ9y%<0*Hd~*c<== diff --git a/assets/resources/dolphin/L1_Leaving_sad_128x64/frame_7.bm b/assets/resources/dolphin/L1_Leaving_sad_128x64/frame_7.bm deleted file mode 100644 index e992d75c7a000c28168c9a683b2cd8a00694f195..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 486 zcmVMe+nUkfkr_Q z54bo%=P;NL-~7T&1W+FkKg4`LkPiSnhwcv>)B}Jo90bN`0tdAy)+X`TSgH|Fef%z) zc|twG=j{IFD@gXFKtbj5zSpv?62N>9gMSa765qG91I{3MC=fie8#+8xKGX2|$Uk!Z z5d6SoA`p2{KXdyKKKuf62h@RLK=zC)#2=7|CIEgya|g5tAF#*{3?3kYgag|h0KrE< zqKpurgV!_(8N37k2LT8@xq=M)6-f(h@=Z)VyHzy^BjPDE}MBlzG09LXZI;uN5DM3 zSK9VfqF4`y|NkEclnaS(+u8x=5CMn?ppXp4&W{xj%o0H${mb-2^FkN^aFQAa?tfwj z%@RT26O8;HGC_E(NFOsme@{P%ax;)U?2$lz(IS9?Q;@3zR|h04abzmBIiZc>&M>KfoDZ!RJH(ClbI9e8>TG15^G3%0vJ^7ruTp zKbSnZfCH9ZJRTo}A6?7`ltch?8H9p?K!I5|0P?c{5&>{BqJVH1fC2J9`1CSBf#Wj( xFg?%e02l`WejxKdwq^l9kOR>EfC2=Jd>)7N01*Ap=l~;opVb0D0)7$wKm>gitj+)c diff --git a/assets/resources/dolphin/L1_Leaving_sad_128x64/frame_9.bm b/assets/resources/dolphin/L1_Leaving_sad_128x64/frame_9.bm deleted file mode 100644 index 6ebc55c16edc741797d5b69ba3482afcaa5896cc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 317 zcmV-D0mA+P0678wfENA-U;~5vAK?E7_&>qz12^~VKfwP7_&>q(KV=WVAA9f^{lWPF zf&Bsbe*OJH{txhfgV+2IoH#tM{(yY#!R5a@5xv3ok1=`o{mA|2^UM2R^@H?3|t=KG1jt$I{^1IinM(At_Me}}*z9sqgH@)V4?JpJG?0T4WF z^v%bpKYjQoLb^XRA%o9<1mI^u_a76F03Utzi39w9hI9w`K0yShAbkJPf&?IZz&`*X z59gkL|NaB+K8_!lKEMUfAJ_oF#^A1P~1p07L#i-~^w8-Y_m%fIZ~^^^^nOkS|#{89)P1Etm*= zm0x{IV1C`3Kw{ zUl05g9z*sAmGglI59B@p9uM9E2LPN0@K^H&_&`JApQtn<4FLW@)AI)6Pr>6456ln9z!-F3K7E?FeBggb ze*yzA52n1nVDe{=z`kMfUz88=7g!G5KH&0zLLd|k#UrhCo{!8Pa7LKyDITrVxIW+k zh6Bg#eV>Qa`YiIyQWir${QLjq^5KJrfdKqd!ROciuf!HB;WvyhKB@AsKpy%7)(4cp9x(y|1Jm3;5P4vawqu603MGK z|6k#NL4jZmA`fqbd>-Io2bch0!1@uK93Gem2tBa^^AIC!Y KBj9xacmu%J8sK#R diff --git a/assets/resources/dolphin/L1_Mad_fist_128x64/frame_1.bm b/assets/resources/dolphin/L1_Mad_fist_128x64/frame_1.bm deleted file mode 100644 index 0ec761cbfd5c88d482ea64236928813bae064c6c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 540 zcmV+%0^|Jw02l&*&>#^A1P~1p07L#i-~^w8-Y_m%fIZ~^^^^nOkS|#{89)P1Etm*= zm0x{IV1C`3Kw{ zUl05g9z*sAmGglI59B@p9uM9E2LPN0@K^H&_&`JApQtn<4FLW@)AI)6Pr>6456ln9z!-F3K7E?FeBggb ze*yzA52n1nVDe{=z`kMfUz88=7g!G5KH&0zLLd|k#UrhCo{!8Pa7LKyDITrVxIW+k zh6Bg#eV>Qa`YiIyQWir${QLjq^5KJrfdKqd!ROciuf!HB;WvyhKB@AsKpy%7)(4cp9x(y|1Jm3;5P4vaHi0=0J@+7-~-X(KkNSj8vub|4I&R`2-so3^#zDL#v`MJgXBhjU_gV*0RacJA{Y-v e_?bi=kq_a4U|?mBlmq~f598r{5%4;IJOSX1q3;R+ diff --git a/assets/resources/dolphin/L1_Mad_fist_128x64/frame_10.bm b/assets/resources/dolphin/L1_Mad_fist_128x64/frame_10.bm deleted file mode 100644 index d4207c95d87c1178737332cfb846c4f3ef65a217..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 542 zcmV+(0^$7u02%^-P#_Tq1QG!O12ljT;Q#!8zzIJGykL5>0DH;->nI1mAYQU?GJpo2 z94H6~e3%FXf&mDCK(G{mddUIt{wOd$EyYNc9`7=z}mRhrgny-@$j3-BLbA^g9e z_$g>Z?hh#c0uB!$`vb>;1As?^_uxW|;6HN!c-RB-Ss3^{cm?@Cj4&QhH2p&$&hZ0)zhG90flh@xLfXo*%pP z1HsS_r)I7nJPi5)5by`rUSBu(m&Jf3^AD2zNrC_v9f1C!@&}j*LFLxE&qwADGWg5^ z3T8KacwU zA0+t`2Luloa6EI@dEMu%4@eXS0SyO~{2o5x_`qWn2owVVFD3}bY5JCa~EC@Xy7wBi=0q_SD{2suugQ`W&Fc|@q zLFh0X3=SL*pa9rlkZ^uPV36>6nFJoth$r~Id;omRAP_7B03I-aVG2YZQGkDc0p_m| gdqg0f2sk{I;tL>pL4jfbI2aWG5da#91Hc{tz=$g6mH+?% diff --git a/assets/resources/dolphin/L1_Mad_fist_128x64/frame_11.bm b/assets/resources/dolphin/L1_Mad_fist_128x64/frame_11.bm deleted file mode 100644 index 35955bc2021298f14e78677f1134681ff57ed248..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 505 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 JKpp__1AyX#(Xjvk diff --git a/assets/resources/dolphin/L1_Mad_fist_128x64/frame_6.bm b/assets/resources/dolphin/L1_Mad_fist_128x64/frame_6.bm deleted file mode 100644 index 887a4b866414750fa353f434e7890755aabf34e8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 524 zcmV+n0`vU=00;tr&>#^A1P~1p07L#i-~^w8-Y_m%fIZ~^^^^nOkS|#{89)P1Etm*= zm0x{Js-G)4;UJLV1F1nd|=7=;6Q&cJAsfx@DDg}JcH%gtB2G4 zKOY1%KAQ6RgGLDeh<}UbA0_zk4Wj}V4lq98^AL;-9zAQU^nPITfPNqtYDcSeE)Tx~ zeh)9T_I@8(%$N z{(tG<^C^n_VUh)K%psTxV%;DJ@q>641suR-f$Jh%ns01ONh O00d);od6yH@B{!JY}+dU diff --git a/assets/resources/dolphin/L1_Mad_fist_128x64/frame_7.bm b/assets/resources/dolphin/L1_Mad_fist_128x64/frame_7.bm deleted file mode 100644 index 8e9a34e971bd15694ea1fbbb480a712b73950009..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 515 zcmV+e0{r~}0RI7i&>#^A1P~1p07L#i-~^w8-Y_m%fIZ~^^^^nOkS|#{89)P1Etm*= zm0x{Js-G)4;UJLV1F1nd|=7=;6Q&cJAsfx@DDg}JcH%gtB2G5 z30Mp?KAQ6RlST;uh!_U0A4T}^5u`30XW%v=^aq$kv5?N?)_OlMdVC-lXgA|^E)Tu} zeh)8&_I@8(;Ko{FBlFX2vacnC(6M9!7GNr z0paF6^PA6D9#a5#lty6z!{@=}?jMLeph^*rU~f6V`$i}bC^P~efq?%9>;H#?#sR^Q@$WSZ*-3{j$2f^%sWabcl!NKfz5P88k5HNc$#L6J@{1=!2FfdF25&`^kpaZ}j F0Dyyp+Bg6J diff --git a/assets/resources/dolphin/L1_Mad_fist_128x64/frame_8.bm b/assets/resources/dolphin/L1_Mad_fist_128x64/frame_8.bm deleted file mode 100644 index a430e480a6fff0f3e6a619747de0e6cb40819722..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 517 zcmV+g0{Z;{009Dk&>#^A1P~1p07L#i-~^w8-Y_m%fIZ~^^^^nOkS|#{89)P1Etm*= zm0x{Js; zhw2Q#90dJ;P&@Pj;qYJ75As4E0)DW6$OI37pPmK>{lGX32k;+$1qS^vfOFtK>bQJ+ zHE{WZ#a=xK3?Eh>EqQ$KGw1|CfG`pH2g!aT!H<{6fq;PP1L_YYaaaN4+`7+4<_|OI zgUi{vmj~Z50GK#O`F)><)_DM;1Iiix|IhzE|M`5la0wIx@&90mJi7n&_>0Da5`acH zG7$PF%E17kLk@|F2ed#QqOkxRowgwLLkIyS z0QCWgJ*FS{2t8Ni089uynj!#6_&w=>GJ+UJKo5E_J-8rp2b~NbxC#h8zu@$Qcni({ zQ;0np10YHi3jntudS`+11`q)9FyQ_EU_tKp5P8o50uN>QnM6KC=Ku^06(9o*3!y+B H0Pq8VP0`#< diff --git a/assets/resources/dolphin/L1_Mad_fist_128x64/frame_9.bm b/assets/resources/dolphin/L1_Mad_fist_128x64/frame_9.bm deleted file mode 100644 index 487f50b354b087e2a65878b92ca3e653f1b244da..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 526 zcmV+p0`dI;015(t&>#^A1P~1p07L#i-~^w8-Y_m%fIZ~^^^^nOkS|#{89)P1Etm*= zm0x{Jss;s6yGFo;0{j~F+A3NQ#jJYazLhy&9B85Rftr0uZ> ztOLv)AOOk%>H{XZ019FMfP>k7AOygJ-M%0LpM&Bs0~jHMWB~Yv1J{BJFd3c5A7*n0 zpZp$&@h><0>>%~LNC1Tbz#)hpU;&SV;Z{h}gM;!3{D1@70Rab}^L`I?_?bi=Mb-cm Q3>6>)4GDPw9suwJ09n4>asU7T diff --git a/assets/resources/dolphin/L1_Mad_fist_128x64/meta.txt b/assets/resources/dolphin/L1_Mad_fist_128x64/meta.txt deleted file mode 100644 index 93e59e49b43..00000000000 --- a/assets/resources/dolphin/L1_Mad_fist_128x64/meta.txt +++ /dev/null @@ -1,23 +0,0 @@ -Filetype: Flipper Animation -Version: 1 - -Width: 128 -Height: 64 -Passive frames: 7 -Active frames: 13 -Frames order: 0 1 0 1 2 3 4 5 6 7 8 9 10 11 12 13 12 13 12 13 -Active cycles: 1 -Frame rate: 2 -Duration: 3600 -Active cooldown: 7 - -Bubble slots: 1 - -Slot: 0 -X: 67 -Y: 24 -Text: Am I a joke\nto you?!?! -AlignH: Left -AlignV: Center -StartFrame: 15 -EndFrame: 19 diff --git a/assets/resources/dolphin/L1_Painting_128x64/frame_0.bm b/assets/resources/dolphin/L1_Painting_128x64/frame_0.bm deleted file mode 100644 index 2694219ef9eefda1c8d52e3ce99cd1284d0d8d31..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 763 zcmVrBnnlSRlM?kSPVi2@upQgaA>f5hQr9 zSqB0O5bPdE05MQ_z-+K%nDOE|fOpudH`hRdR4*Wh)9!NbZ zIh_adpMUy@#sNqNo&#Y~)W$gb{ZIfw<%qwoMxiOILU335r~rTTb_l@W@(qL~OfVgI zeCQ0(z(yk#h%A5}HsJXK%7K8eLA8=uh6f3v53GX$#DLYLaheS-${$vcoX1}goXBPx zfcn5D6RZVhBl(EHZ3Xi-3W>5=-f&+BIKrB^N0FX!kSqp_gX>^~C7P% z5DX>*Z>qx=#&i!H0)Su~h%)?Z6x76{hvh|tRbb%t t!9;K;@{=FN1`k*l`~Wxltd|IW5PF(K20Q@x(4WHt2Oa_t4=MnBbU+(OFu?!- diff --git a/assets/resources/dolphin/L1_Painting_128x64/frame_1.bm b/assets/resources/dolphin/L1_Painting_128x64/frame_1.bm deleted file mode 100644 index 3c9623d4c79a19e74bfe403f8819221e10322d62..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 764 zcmVrBnnlSRlM?kSPVi2@upQgaA>f5hQr9 zSqB0O5bPdE05MQ_z-+K%nDOE|fOpudH`hRdR4*Wh)9!NbZ zIhhCZpMUy@#sNqNo&#Y~l*Tyw{ZIfw<%qwoMxiOILNHhQr~rTTb_l@W@(qM0fI1y` ze8A)2z(yk#h$uX*!SV-{0|8)zYb3Y~1`|XdSq1}%0jo&xGzwjmKCK}+j=m#7kjym! z^?*z#SPINX!x4bm3+8MX2Lcb^*g$j`sD{KoTB}mbpce}S2VsPOSgNW4AdmpFoCpp= zlK{|ap->H1s2+Iy1O_n)fLJQO(D?O1#el=WAajU_e4xSg_)+H&Ys7>;P*?zcKhxSI zGx!D&`XEGHG4|NPF3j?jV2LDx(;Sa(OSRfd<0r8I}kAEZeiLFwq0vN0iUOd2f&LEKuLdZY`8i5i< z8v%oO0pr*_kN{$!@qoc#5PSy~Bd7)KEAWA;h5;di+#nj8@I05|qab+9VBk@$&;A4gii65A0QiH;RX?~G2Yw$Y3?v?W z3f^Wx{O8~PqA`FZL>^KNbw*Pda1O3<3vNf`iK3A0T-E2Y~?9$#579CWt@GV27^@! zWC(ox191n5U`V6=53lfSAn|+%d|<)!;BUj?(;)dlU;*>^LG^3#6A*py6WYC2(gqmt zec&E3_~dXreF&6b*bP+Z9#PN#RslkkP%eS-yj5nP^0+}Ya0+Ci0k98@5I&p;7yJJ+ zm`5rB@qtxms`G%uiK7^ihsp!0mn+Tzb2prd zf#MJ4CW20Z<$#35A@S%p^W2$+0l7vTBA0pp;6LMRac`2Roj0Qq47 z<52KG_y_TUf4}oE`B;O4D2PEY3-N=OM*bfd6h;v+jzS^$500-KFCX}Pagq23LI;E~ z41T|V`~$|r75G5M!vNw(I1n9W_#R8~QII@@g8;5i{saPDhyZQ?^wdx81_9rP$^$6} zrGGOZ{&VmDQ5e7_0Q10VDl(YIAAhO<2t2tH2nB+%DXT&-SNf;`fAV%{i-kTgngHl^ z;q#CvL>@58t@94-g323+F!{kVJJ*7zQdKu@9E2)DJtwR311;1&XSm8m-hGd*Jy}AQ}x+DgYl$ z1zbKzVX#$yq46+?0Dc<1R1HQU@`DH0{vQ}Qpo8NDfCtZkAJ%H2p<)lj0=ZT6j0QX( zZ~*=_|NQSCLPZ!h16Aq=ox}O~xXLIOK=`5ust-7XBjNazz%~K#f(O$94}^aw^El-| zJ}@N^tBwhb@xbHKF!?}rTcCMEpkK&@5(xkglm@Gv7Z?5m3h==2m<_(G3||~@JY(<( z4-^U^@vK#Fw+gNtJT?pj0&))+H2{7zXhy3BFDeEByfFI^{HU;wt^}S`6M*0$0f7&U xnhEL^hyY~V355d@|Nr0WvRoz9R8R#_@F*NdIS0mtItQFUqBt8E7-#|U(EtcKLwx`M diff --git a/assets/resources/dolphin/L1_Painting_128x64/frame_2.bm b/assets/resources/dolphin/L1_Painting_128x64/frame_2.bm deleted file mode 100644 index 13916806fd62d4aed731cb58265eab34adb4b755..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 762 zcmVrBnnlSRlM?kSPVi2@upQgaA>f5hQr9 zSqB0O5bPdE05MQ_z-+K%nDOE|fOpudH`hRdR4*Wh)9!NbZ zIhhCZpMUy@#sNqNo&#Y~l*Tyw{ZIfw<%qwoMxiOILNHhQr~rTTb_l@W@(qM0fI1y` ze8A)2z(yk#h$uX*!SV-{0|8)zYb3Y~1`|XdSq1}%0jo&xGzwjmKCK}+j=m!Skjym! z^?*z#SPIBIKqF``nXq6S2tR{i0f1tn8xZ+wtxGb1Tr2?b!ayulRREAk09noeJW~Up z)k2^euTVVk_yBmqAQlR*^gcaMabPg;2pX6}3?1VOfZOV@#qpg3 z%71{MVLr?8tW|Kg3aoSm5U~%+iwLU0$L9u+&*K;q s`ALuC0|%TV!$ALk|M&W=mk53o`bKaNGbsngg#Hjg6b26fD;RuqKoWI0G5`Po diff --git a/assets/resources/dolphin/L1_Painting_128x64/frame_3.bm b/assets/resources/dolphin/L1_Painting_128x64/frame_3.bm deleted file mode 100644 index 751fbc3efb282916cc38d5999018e804cd4c0a37..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 759 zcmVrBnnlSRlM?kSPVi2@upQgaA>f5hQr9 zSqB0O5bPdE05MQ_z-+K%nDOE|fOpudH`hRdR4*Wh)9!NbZ zIhhCZpMUy@#sNqNo&#Y~l*Tyw{ZIfw<%qwoMxiOILNHhQr~rTTb_l@W@(qM0fI1y` ze8A)2z(yk#h$uX*!SV-{0|8)zYb3Y~1`|XdSq1}%0jo&xGzwjmKCK}+j=m!Skjym! z^?*z#SPIBIKqF``nXq6S2tR{i0f1tn8xZ+wtxGb1Tr2?b!ayulRREAk09noeJW~Up z)k2^euTVVk_yBmqAQlR*^gcaMabPg;2pX6}ciuJ510UA7%7K_Pe8f{#_%5zxU5Qv;P^?P8vyvh z1L6-k1mfXnnnx-D@qq`{!2pj*Ba$%rKu_WiItKznA~lG7pfy{lJfqwRg#v?zz-{$d zV))L1PFMTAvg;PWVy pM*@E-G5la)^`3vg1AnT?aEIXstOyKT0Qk_K!vhDPSm*)q(Ew_YJf;8u diff --git a/assets/resources/dolphin/L1_Painting_128x64/frame_4.bm b/assets/resources/dolphin/L1_Painting_128x64/frame_4.bm deleted file mode 100644 index c1135b467cc5fe2e313984e30a04ded3dd814e7d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 759 zcmVrBnnlSRlM?kSPVi2@upQgaA>f5hQr9 zSqB0O5bPdE05MQ_z-+K%nDOE|fOpudH`hRdR4*Wh)9!NbZ zIhhCZpMUy@#sNqNo&#Y~l*Tyw{ZIfw<%qwoMxiOILNHhQr~rTTb_l@W@(qM0fI1y` ze8A)2z(yk#h$uX*!SV-{0|8)zYb3Y~1`|XdSq1}%0jo&xGzwjmKCK}+j=m!Skjym! z^?*z#SPIBIKqF``nXq6S2tR{i0f1tn8xZ+wtxGb1Tr2?b!ayulRREAk09noi2jNM8 zXf;r%2CLK$JbnTL_=UhM6<_FldZ6OKVc-xyq)a|gVEX(h^N2T!P9G>N06w4T?GhRB z4TyaZA}$#FY;hNeBzQj%2*wJrDB@^!Ve!BR%m6VA6!J+bpj`vwcn^vsRwYE?)1`0? zfP7$q@duoPae(;DW0e5-z=P{xYK{gmBM+1W{vh(cP$mHJf8L-2#t(j-z3jS2iPFmd1^0P>&*$3*xWIm`e6 diff --git a/assets/resources/dolphin/L1_Painting_128x64/frame_5.bm b/assets/resources/dolphin/L1_Painting_128x64/frame_5.bm deleted file mode 100644 index a4681af98e85a780faca27f632565d56a90abee8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 757 zcmVB0PzBVP#_wh1V*4h^Q%=r4;6re#y}sidI#q!KOp_)f#@Ib|JA;~|NVSF zaiA#b1NRjIjbK0F{}1|oq7Vqjg1#Sx_{cBtNru2bFs=pzg&nX(e-Qe|)~{6o3|1Ks zpV(mf2-Q+6sFiCqKp~AmBoql03&9`$N9z+>rBnnlSRlM?kSPVi2@upQgaA>f5hQr9 zSqB0O5bPdE05MQ_z-+K%nDOE|fONp1LOSv&;#X!fI=t` z5Wt_t1^)lc!{uTJj)MUKz%RxQT^sm(U{M%EBB20>U_3g!aJ+xv@y18T<2XbV?!ABf z1IEJ@_(0&F2sk{U`pfV8P)bMOCA7{DnI3j{I&fY?-JF^)d}Q~(fpOcVuBl+~daEB#aeKl(capz;lbCV)B} zcznR(Ef~aNu>}W}xIRGiutBwwTm}OPq7T0k16GmXXcW6BeW3?2*TiTt8HS)f(+SoB zvk~ybU^asJ-@)U+gZMTO9R?~Pu@9E2)UzlB!odO9VIUSNs(?r&04(PM1CXS^G#aQ> z16Aq=o<9Krj6zofRsM&^stzm$9svWKL__5U53jfcU^cr6ZnJ5c|hPO1_=NUlm@GH2bT|nFn~aCm<_(G z3||@0yz$V81RMl4imn#nRgQztV0airGJroCG$U0G2b;Kj5epFfsIZEx93Ef+$V6}_ n@{=FN1`k+A`~Wxltd|IW5PHD?#lR1Z3H&fHdP0HaKo5?HvfnzS diff --git a/assets/resources/dolphin/L1_Painting_128x64/frame_6.bm b/assets/resources/dolphin/L1_Painting_128x64/frame_6.bm deleted file mode 100644 index 36f2d084fe3277d555dda68b3a9675916ce237c3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 785 zcmV+s1Md6*01X3xP#_wh1V*4h^Q%=r4;6re#y}sidI#q!KOp_)f#@Ib|JA;~|NVSF zaiA#b1NRjIjbK0F{}1|oq7Vr#7-N!1m0(_h@t{H>K=F(N$51I5i%Xy{O8~P zqA?GT_#QAVR;r^ZjB)qiVbxF|ANYBQYV}!7S`mW3)j$J(fPY{Z2nMfJ2@DJeULQue zU;^U>2bH)!Kw*qG$AA|ZE&~CC(Fe&^Fo*;~z!w=F27ybmhsbsy@q}yw;}L+!W*UI` z1H^7HYQS7U;sF~#e81rF!(erRae%-vQ4NTEy+HB2U@F0J2aXZ}VydVIst*_k0PsNZ zq(C$ps8j$DK=H4J2(Ct9{)faKRsi_Nuo3uxe4xSfoL9cEIP}6mA12U;&V96I0|$?S0TQeMhJ65_C&nR!3=9In;187I{2w?*5(45cgiJm^&;0;CSWF}W@c@W^ z1Lgkz%){kk#y~K_LH;ks4qY4gd|*a_fIwhSPxE|n!twuy#~B~EKrb*MSii>q_y>)K zEAWBQFdxVy0{$Nx@I05|TN(xf%#jdy)+hf00YyRUfS-rPe{e7k{61712lyT#p|9p- zAI^RM>IfPH1OE@;YxP>HjHWTi-+_lz4JrmYCUU%fg3b0>gwY4f01qf9^08Hs;Aj-PD13q902~~DQL2c* zWHSILht2`PPZd-iAQ7|&%l;1&0Khexs0;%Y5ZH&y)Gr_a)vBQJ!ayulRRHxt0stNc ztUgqT27^@!fNHQH^1Kbi9wu?HRez!J2cGzDAn}L?$_yVK2qb(9@#>YvKp!Y91FOM< zz(37_pWpz(A82F)@qg-r2KfMw2hOWOXO07ZKAe0w9DNCtVAu^+4mcYh1P+Y|z{+SB zK=_(R2ObBF0yrfY46XsN4~!F3%oKS@r``wz$~jOEj0>v+pM%E$BPaxFMjt2+st0Tb zmjVU^64-~z1J!|Y0p&4*cpx}j2H#bNz-P#_wh1V*4h^Q%=r4;6re#y}sidI#q!KOp_)f#@Ib|JA;~|NVSF zaiA#b1NRjIjbK0F{}0FF1|O98#2^spFn(9#Aiu!jf%=7TI}jK^e18!7$JVb^0Ss0d z5cl9e`uNpSE2x!gH9#SaK_nN81P}2Q{zvN*TBTG3F<2)8VbFkJBXCHDp=2NejXI17Q$+fWtn3P!r=2!UhHbV7^4d<2b(u&Jl!wgU5l6hzG~{|DXrU350-N0pkNN zj0^q$nTN{7jDTUFJO?1wAm!1&hsFgFtUw?zDFgxqsK*P({vRAVa1LJ-NlKe|!K)`vDBM5<3C;tKgMM3QZ4~+icU>*2;s5lSsJWYdN z%*a2S`~TENGzbU&9%3C{s-r24argS50E5T-0Kh;h^;u0?5rV(fKm&hco&Y{Dkifuo z;q;6*$AA|YC_Jsf@>Pr?0TA#7#!G;}VKhPV9f&+(7XY}(@H7ftls-W56O0>h7Z{8N zLon0_(ZE%}xP!z3Hh}qm!Q*&5czt6;s6@>TtVZ6fLN-k0qTMV0l+*E z7--To62B0rGm~YybuYLa-bg=7gh&92aW(nPzcnFK2RN04%iPa1Pllz zu@967s{-T$%3}oZKybJXzN-y@^+B=?5`@Ac7$x}DD!5yR*NqVslrCWq;X^0`@jDHw z4I)wu0u?5RSZqV`qQWk)N+Q6a(jNrD&>``YK_^g3MED**LxBE2|NH$`ON5@mG8_n=;>2_ diff --git a/assets/resources/dolphin/L1_Painting_128x64/frame_9.bm b/assets/resources/dolphin/L1_Painting_128x64/frame_9.bm deleted file mode 100644 index 99ed507179a3dc7a64a99f871d18c55796a97cb1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 777 zcmV+k1NQs@00jepP#_wh1V*4h^Q%=r4;6re#y}sidI#q!KOp_)f#@Ib|JA;~|NVSF zaiA#b1NRjIjbK0F{}0djKPmBu1I7ne55oLp7ysl0KTxg)0_;C8#6Ge0tJOe56^2A7 z@;C?jH&lx1C0fl;2xCx51p-X~fkpq3`oz|$RRIiE2rnAoJY$eZhM{C20*ye4BWHj> zZUFJ@9!LN&P$bQ$M&E2Yw$Y3=|%4 ziq2+1{O8~PqA`F$0pkY(4!)x)jB)q+pa6n~0WlJc3acrrLNHhQr~rTA!G}OF7#&@`ss42EH- z53BqiZEy(eDD+a z33bwjuI>)nnlDkWeER*d`kRx77g0 zRTmbF4nZJ-SYVLI1!Aj(xOHM{frdmRAB+SJ5kMb}8WHMI|3CNotd|LO2b2N}C@_p71Q!ta(5F)Xc|fHc0C>Pq H!XF(FZihX= diff --git a/assets/resources/dolphin/L1_Painting_128x64/meta.txt b/assets/resources/dolphin/L1_Painting_128x64/meta.txt deleted file mode 100644 index e5f5fc0a645..00000000000 --- a/assets/resources/dolphin/L1_Painting_128x64/meta.txt +++ /dev/null @@ -1,32 +0,0 @@ -Filetype: Flipper Animation -Version: 1 - -Width: 128 -Height: 64 -Passive frames: 9 -Active frames: 13 -Frames order: 0 1 2 3 4 5 2 3 4 10 6 7 8 7 8 7 8 7 8 9 10 11 -Active cycles: 1 -Frame rate: 2 -Duration: 3600 -Active cooldown: 7 - -Bubble slots: 1 - -Slot: 0 -X: 57 -Y: 24 -Text: No mistakes, -AlignH: Left -AlignV: Center -StartFrame: 11 -EndFrame: 14 - -Slot: 0 -X: 57 -Y: 21 -Text: only happy\n accidents -AlignH: Left -AlignV: Center -StartFrame: 15 -EndFrame: 18 diff --git a/assets/resources/dolphin/L1_Read_books_128x64/frame_0.bm b/assets/resources/dolphin/L1_Read_books_128x64/frame_0.bm deleted file mode 100644 index 1169e42d690a46a34ea8a8e1910843a4d46556f7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 653 zcmV;80&@KU0Eq&sfICzG;QxpGKjHrm_%c+< zAOOz11N86I1Z!1;zySlg*6zML59jDUkQfHDRqy)qd>#DcmaFw26aoQQs>}Vn|91a5 zC)UQbdaF=8b{eU7*OTh_XC#$aZCa}}2aSL}nPB-sheIj{j{rV?41j#^6Xgq`B4-ooVpgRau0{9hgxfsM+m>fXw~f#LNB$6cx5CFg?0rHaBWj;#3R%jnca6D@B-!7-gtkq8g=fEHq zx5|D+_=m=;Kj0WfvJYVSz<+`P)d%Yb{}+u*N)JgmeE-+*c^7!R>aI*NP#nfa zF(wC$mqm)zTV-@8L^=f!%SJo^@q+8H!=d$4Cm0AF!a)K5K>PvoMU6v|e}P~mei{5T n@fo3FwjuGSOfUt3o`2wf3;BI$91Hjmd=v5T$N$Im3ZMYM>|;S8 diff --git a/assets/resources/dolphin/L1_Read_books_128x64/frame_1.bm b/assets/resources/dolphin/L1_Read_books_128x64/frame_1.bm deleted file mode 100644 index 80e2f39bb69255c5c17ab52708c536680e2ad7dd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 653 zcmV;80&@KU0Eq&sfICzG;QxpGKjHrm_%c+< zAOOz11N86I1Z!1;zySlg*6zML59jDUkQfHDRqy)qd>#DcmaFw26aoQQs>}Vn|91a5 zC)UQbdaF=8b{eU7*OTh_XC#$aZCa}}2aSL}nPB-sheIj{j{rV?41j#^6Xgq`B4-ooVpgRau0{9hgxfsM+m>fXw~f#LNB$6cx5CFg?0rHaBWj;#3R%jnca6D@B-!7-gtkq8g=fEHq zx5|D+_=m=;Kj0WfvJYVSz<+`P)d%Yb{}+u*N)JgmeE-+*c^7!R>aI*NP#nfa zF(wC$mqm)zTV-@8L^=f!%SJo^@q+8H!=d$4Cm0AF!a)K5K>PvoMU6v|e}P~mei{5T n@fo3FwjuGSOfUt3o`2wf3;BI$91Hjmd=v5T$N$Im3ZMYMr(i() diff --git a/assets/resources/dolphin/L1_Read_books_128x64/frame_2.bm b/assets/resources/dolphin/L1_Read_books_128x64/frame_2.bm deleted file mode 100644 index 959b02556d64ec11a58378b58d291bb1843394bc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 650 zcmV;50(JcX0EPmpfICzG;QxpGKjHrm_%c+< zAOOz11N86I1Z!1;zySlg*6zML59jDUkQfHDRqy)qd>#DcmaFw26aoQQs>}Vn|91a5 zC)UQbdaF=8b{eU7*OTh_XC#$aZCa}}2aSL}nPB-sheIj{j{rV?41j#^6Xgq`B4-ooVpgRau0{9I8tTOt4|-EOM`51*@hD*gwD)E^yosFHfQ?UsAw z4vf@3HD0M)>au+-zsMgMczi$u0GJ2LOJ$V#D*aiYeIdZ{tIvG8pC+?aJP)4$fLh-v z`4{3J8m#|-VH(IigXaPM2nSRjtRMVdH7zJTB;oV_U%}&~-m{WSA1mxP{|A6sG@$hl zmm3cQk3cyM<&%fUs~@v?Bk&4D9cpq9z zTd5g>ScBRwA1rvhLGeEilzS)mg@eSLU;*HN|70=9Mk3&WjMK2%nMbO*FvUP~85qQv z9x`1PD^+ck(4i3M6hkc-@B_vRuEP$8)l8gVAa@A_2mJ%^2hA2W4n_V2fRXrT@Xy3% kg^JjR#+@+076y6$f&4G!^`LMs;6dfevkzfcae_rL=k*0*)>*ndAk^n*Y(dar-io8a%~B(-0te4sE2rB+|<=li$$$v(C; z8XZ&u0jSkWzPz7T!#O0X!)n3t2aP~JnPB-swVJ6=Jahr`@MHt$c&g1o<3Os*zr2w7 zNeAPE)y9G2zz>g1u%NAwcpEpQt{2SOsDm0KNy*9$Ma116V&C z5`ahV{eV@YqVa$S%pQ8(Fla;L|A6ZM|KIok>b2YrR6aNO3XE8S06k{QgR1R7FQNIb z*k8ggDz2&WEs_G3%T#|NAiasQKmo1R>abJzm&et;m45@n>JN^)RRRy3_RBr;2S#ci z5FnOLt0&US{DJY8hr|FTbJfXgvY#bit27UnRsk>vjb3}@)cG}p3LB^1iZ04*;@hLFykaHXa8a zfN~qlCl8c<&ESu~DG+rj$Ugz{@x~*`0mw-N2QP%x1LFjKjldo(&ipZfTGpwzy^3J*XBuiQR1t92tW1Dpfe zE*~DQI9?^O2ekbDQT(6bUNi!6fCqv9{gB5Y7>9xeGfu;5Wge>J!xaI{WMdOxc*%5F ztyQ*HLWD!0Q4F+Wzz-NMy9_!XRWfmaf!rhzAM_8vA2e9hIT!dA0!QJW!#@$47As;O g8g#<|SQ+R32k^g_)`7skfd|1q9}Iu|e^9Cb3>pbCT>t<8 diff --git a/assets/resources/dolphin/L1_Read_books_128x64/frame_4.bm b/assets/resources/dolphin/L1_Read_books_128x64/frame_4.bm deleted file mode 100644 index 389d2a8ef52e63d603a3d5c6357d14886acd4518..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 650 zcmV;50(JcX0EPmpfICzG;QxpGKjHrm_fevkzfcae_rL=k*0*)>*ndAk^n*Y(dar-io8a%~B(-0te4sE2rB+|<=li$$$v(C; z8XZ&u0jSkWzPz7T!#O0X!)n3t2aP~JnPB-swVJ6=Jahr`@MHt$c&g1o<3Os*zr2w7 zNeAPE)y9G2zz>g1u%NAwcpEpQt{2SOsDm0KNy*9$Ma116V&C z5`ahV{eV@Yun7DFK4A0K>4QQa8Tw_B>gPvZcp`nR&L;COvO@z-jgL~_;7Y_s1W zbY`LP0tsa5vVAPS$R8Pad_V$cJzSQ{De_hNvq1TEU=sj%)#tulPm@`yo(Iw#4;^om z{EP7qmjHlOkb4Ktf53sjH9`3=kMKY>Ehs%C;q$?N#p9&jvyx07E9)d+@CzoC9-;E% zVc>D-2O+$&aQR2<-U$2xkq1(ogYX|O9AZ3>9E6ZSa`;V9J}^)G2H+1FJCr~JDEx=U zRS1$yJ_3=Fhtje?8Lgs8a6k-ze!GDAVJRPv6b?XMPyKv8P-WpHj6=Z#8K+^jGLKbqVTyp}GBJs; zJY>2oR;t@8p+X_hD27@w;0KHsU4|VGs+l;zK<*L<5Bdk-51K4$9Ep%c+< zAOOz11N86I1Z!1;zySlg*6zML59jDUkQfHDRqy)qd>#DcmaFw26aoQQs>}Vn|91a5 zC)UQbdaF=8b{eU7*OTh_XC#$aZCa}}2aSL}nPB-sheIj{j{rV?41j#^6Xgq`B4-ooVpgRau0{9hgxfsM+m>fXw~f#LNB$6cx5CFg?0rHaBWj;#3R%jnca6D@B-!7-gtkq8g=fEHq zx5|D+_=m>7Kj0WfvJYVSXkb6V0P2JB)$m~d;_<0zLFp$CmV^I)2ac0^&PgzQr;zXb z9sy+1gVa7;d@K$<0OU88PG2bfyTKoTQXuP7kbVQ@;*3X>1CWvk4qpkXH;exPxC6#d z1rL;?@*gNPl41N5jFdi=k@;Czb>0qge< zm8y}LC5Szu;qu0d#2*vz`9`vTh*&&H#sD4%|Mo*1gkmlT7|lBksg!!FlMGY`A(4zp zf#W67VzpM;T?!EnfkZOVj{rPiy6iCMeN@TD0taxAK!4Cb0DSdQs)r)~0>DW8Gx%rX mGeX5|L*q`EU<(60|G@qi^7_y?7w{nXC*$Fd|BvbwKmmYdgFQk3 diff --git a/assets/resources/dolphin/L1_Read_books_128x64/frame_6.bm b/assets/resources/dolphin/L1_Read_books_128x64/frame_6.bm deleted file mode 100644 index 451c80a26af36c0bfe1cdbfa307f6e6cbb98b292..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 646 zcmV;10(t!b0D=OlfICzG;QxpGKjHrm_%c+< zAOOz11N86I1Z!1;zySlg*6zML59jDUkQfHDRqy)qd>#DcmaFw26aoQQs>}Vn|91a5 zC)UQbdaF=8b{eU7*OTh_XC#$aZCa}}2aSL}nPB-sheIj{j{rV?41j#^6Xgq`B4-ooVpgRau0{9hgxfsM+m>fXw~f#LNB$6cx5CFg?0rHaBWj;#3R%jnca6D@B-!7-gtkq8g=fEHq zx5|D+_=m=;Kj0WfvJYVSz<+`P)d%Yat{4SNN)JgmeE-lec?1niA#9RT5*kRE6sgsNZ4&fkx|Db*V`Rb)r4n_V2fRXrT@Xy3%g^JjR g#+@+076y6$f&4G!^`LMs;6d%c+< zAOOz11N86I1Z!1;zySlg*6zML59jDUkQfHDRqy)qd>#DcmaFw26aoQQs>}Vn|91a5 zC)UQbdaF=8b{eU7*OTh_XC#$aZCa}}2aSL}nPB-sheIj{j{rV?41j#^6Xgq`B4-ooVpgRaqeZ4{R+MdKdPyqS^%UjA#h*vf#{r`Xtt6jj=d$0~X2CD#S2mtk)E(`czAMpIw>?$FE ze~06$e2Zj20tFqGsto|X5e4iuR>*(~w_B>f%jfFe%D;i(^#{jYswAGSdu5*a1EVz$ zjaRByI;@{dFY*V*ULOzuz$O9mlG$ZGO21ZUA4qUKYV+SNr^&3j(cAjY~=oNjQA}*YJ4hH>~872g>^m|H0rEO(;D><;KIn zc{Nf2>b$(2U?ti@E;pjk})1o4njyEIeaFmTbjZ@@Ed?UWbjb=N5PVO=IkGrcvsyOfgU##zrwF2aK0R ziq%_XbSOkR1rW#)P2^;0Jp2pz&f0slb!0rN$TLy>=hU?hGS{4?%c+< zAOOz11N86I1Z!1;zySlg*6zML59jDUkQfHDRqy)qd>#DcmaFw26aoQQs>}Vn|91a5 zC)UQbdaF=8b{eU7*OTh_XC#$aZCa}}2aSL}nPB-sheIj{j{rV?41j#^6Xgq`B4-ooVpgRaqeZ4{R+MdKdPyqS^%UjA#h*vf#{r`Xtt6ji|i;n@Sz#4)8J!Z>;s*k`w;rXxFTgCn# zj;Zo3kpKu3c1_~G5e4iuR>*(~w_6AKa@I`*nv(c0QLKy%H2rJ62u|N9}1LNONv3}&5%)XF_o$%ZNen8?N?!10pkv0AHa zu7wDPK%yCF$ABI%U3M6BKB{En0Ry;5AV26IfIeuksB$mxECi3kKZbrIG%QxcJ~Ziu d0I)O9{14%OFRcTCe*zDJem)rg`2L|(02rJOIJW=* diff --git a/assets/resources/dolphin/L1_Read_books_128x64/meta.txt b/assets/resources/dolphin/L1_Read_books_128x64/meta.txt deleted file mode 100644 index 7432507ce29..00000000000 --- a/assets/resources/dolphin/L1_Read_books_128x64/meta.txt +++ /dev/null @@ -1,23 +0,0 @@ -Filetype: Flipper Animation -Version: 1 - -Width: 128 -Height: 64 -Passive frames: 13 -Active frames: 2 -Frames order: 0 1 0 2 3 3 4 0 1 5 6 1 1 7 8 -Active cycles: 2 -Frame rate: 2 -Duration: 3600 -Active cooldown: 5 - -Bubble slots: 1 - -Slot: 0 -X: 5 -Y: 28 -Text: Predictable twist -AlignH: Right -AlignV: Bottom -StartFrame: 14 -EndFrame: 16 diff --git a/assets/resources/dolphin/L1_Recording_128x51/frame_0.bm b/assets/resources/dolphin/L1_Recording_128x51/frame_0.bm deleted file mode 100644 index a278e3a9d693124ef13784e913883e49b5c1e994..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 663 zcmV;I0%-jK0FwfMU_apf@PFW7LAU`#0MHM{0uB!zWPn=m3FHDD0I*P`Fc0vUzYy?v z!@tn@2Lgj3z(8Su@q!62UqK0wYO^1xgqQlG|x3FPo#@?i5q4L?O>f=ppCD2)?I zb_bj?!J_&=uR!sIN;H{fDcfQ?PoxI;4;Veb9^d`{(&7&;J;WYggowR_9yJhoQNiP$ zfO#L|{twQ60rGc@3IIQh{2!zbGWfvn!10GpG?9q-e7;Z}2-Iib@sCdd<30zJI|U&0 zkcJ5){zvjr%fLKbV?>a0ary9g&LP0@xOk!Shy@}+lE>5FKv98WVcrkWykHoK1dAUh z2c#Rp1cNXJA19(?A}}0;hlPaWcpy+n1;Pl!~zK*X^uP|KZtldZUK>S3@C`x)A3%I)5G({}5=Ei{mcTwbXka~Ifb;MZ z#xoJXMnUFK5GnuT@R*hY5(zE8k$iMl zV0ptV8ZV>|{0|s(<3*NYowg&S`ao}h@q_#{AoU&((&GdNj~*kS2an+*FJT9bL>^Rd zc;}!5k#&fEbMOz7ykJlP{Ab|(AbFR@1Yamod}9&t`Fx-{8zd0^Q4{#aC&2QDV5AeD z!hC$51o+1<0P%5+5OfU!WpgkJ2< zfSxg!jsh|dGJt_k{~v_Jun>?*Z~^K77%;q1A_gD8JW`|pKzZOl;PHchzzmrr0KrmY z0uQh}?+``^!{iX7b rwNoj#XR&PAKo{o(Rt~#VA7FaQ2ksAPBsjjn_K_#v0SE`41HzwhY5ozm diff --git a/assets/resources/dolphin/L1_Recording_128x51/frame_10.bm b/assets/resources/dolphin/L1_Recording_128x51/frame_10.bm deleted file mode 100644 index 7684384fdfb09f1dc5e2dde7b898fa11630b8d79..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 629 zcmV-*0*d_s0C56nmLGlK12bKciBJ}_K0P+4{@?i5q4L?QbgUn$vDD@Lb zb_bj?!J_;???CZ~N;H{h2f!dU*p8Fo0lq?bPDR|M&0-*KRNgZ&|WYo0RA)Zev}*@Mfi+I{!sV_$^)T4ykdR&2aG!fAWyt< z@dk!C#>pUGymH}ifKDJ3i35G%-UwpEu@VRS!Mp{*U<^J_ME*fwF#rhRINkv9d?6Ez zL+&#AMkjO1OU)L$$zM!`3Ifihz}TagTdp+1$^CSV+fPOF#cm;xmA%K5^#rTJV#(4mS zKsXc`4gvxO7z7$%4;Xj)J|!9)hQR=s0fqy>2ag^Da0CpxLlFN^;1mIP;BXT#v0x{X z7(@pN0FXf<7%T)dY*YxuhY9?Iz+wRL!f+r!@q|z!5*Qc$4+4Y(#}UAQgT^61hDhK> z@duBh7l`m73PC`@$f7^W3=#?OgX9e202&4Y;Ubj&G!RcGg9nobqynF!vOy*=n3P6| zq_Zs!8DP0>@-h%iQ7?0o{KFBCMi{k^o1I8UV(q_Wk})3# zm&*en^pJ)LB>qS8QHl5Z7l4P*q#T@nd>%81c1k&TgF`M53M7LikEg(?5(pM1IdF3c zz;O}@7Cuf7NOyuTfG9+siHwNAauOaE6OG`4FbW?y#6j`!2?2a?Qg0csSVIVqL>{C8 z7zPF{4+8-f^l%}*2sk`JV~qdL;L{8Qd-1^dXW%f&1Oxq#2an<( zfH#i<04o@fJYX2F{r&*~2c&WVAqSQbVmttO42A+w5PA9o<3ND}#Bxv$1q2>j0Ql*l zm+lNhU-HKY)0T1OU)L zc;G+uaQuVF;51Np!=xS$9zP$)&%lA@c#;FgBmemQ`~ClZ{sJjDrUOB)kX)|)zQwzD t06(PzL8{eErrn;!vtw9d@Wb!1W)%J!A#=2f6*=5P*5a6Y#Yx0w4eY diff --git a/assets/resources/dolphin/L1_Recording_128x51/frame_2.bm b/assets/resources/dolphin/L1_Recording_128x51/frame_2.bm deleted file mode 100644 index f5dbbc71c2ddd18c7644ca48385fa4066c1f6daa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 628 zcmV-)0*n0t0B{0;U_apf@PFW7LAU`#0MHM{0uB!zWPn=m3FHDD0I*P`Fc0upzYy?v z!@tn@2Lgj3z(8Su@qnmLGlK12bKciBJ}_K0P+4{@?i5q4L?QbgUn$vDD@Lb zb_bj?!J_;???CZ~N;H{fDcfQ?PlN{f3E@HR0QUdy;1>{hz_^zXc?w1V_7Hf~LFGgT zj(P#)bPxDHIrs<3-Y_Tt{xk4?kUY!r7>|d`oO7;G?R0K??;PvjN@5CD!7jo^VnAQuQk;}HSEKqL@o zh6+vNUz9o#Ac%jca0& z5C|lJra16;i^Lu`0LZuo1P>SnEB}9hKtbX*AMh+-Ao9XJ1yCUIh!@L%Y7ij$1LHt} zU?V`1Ef5s80rAsA0qYD0pMah*n2rK64>Ev(PyZi;#IO*MNpJz;g&+Yzh2n`2FujBl zFff8BgdSK2P@u>8{{SjvkOKu+i;zR=4?DyWVlenegU64@@$>4F4h$Os5&!&t{r>;I zeyKPHG0-~bMau8n>|3{Il)6AP8m&~yZQ1NwHc$omlLuk0)d$!fvVr@9+DQ&Cusx(_ O!62UqK0wYO^1xgqQlG|x3FPo#@?i5q4L?O>f=ppCD2)?I zb_bj?!J_&=uR!sIN;H{h2f)HM*p8Fw0lovq4{!&RG7JwN{{QK52bUh=41ZZ#DDn*fPA1jA0!@r4;b|D9y9Os zJ^>EFNIfJWf=NG-{FHL>28KAsi6G?T^WgEELxJOPYYgH6NRVW)^!N}IU|5)QfZ!O3 z1dAUh2c#GtcnEx+iHwNAauOaE6OG_*i~`^?97G=<0FW2Q1t#%>${h%hL?9FZ2|zG0 zZzKW&Ul4fMAQJ(wf=2-!{6HYn$N)S+V~qdL;L{8N{9p?Lf=C);j|Y$99uFIXK>_0c z#eeVPp$CZAKm`E@mJwnkfd`3TKm))v2oQY%@t{Dk5uiyHhzeT(`01g5^@an_z)u*= zM*$fJoE#o-5Bz=;62L-1CBO%$0AE9ZM2HxF0P#wQ0YT@0|AWR2{{S*%lS2hoj0ir! z^SnVBqYsdHJb3(nA3muy@WwC^AOFYi-|zeP>XSc=AAzorT(14T#k+QCPox7us?|)U o-JZp>WdL8C5m-9yP$qK1KLENcmyCGcn=DFz&rO3ng9R* diff --git a/assets/resources/dolphin/L1_Recording_128x51/frame_4.bm b/assets/resources/dolphin/L1_Recording_128x51/frame_4.bm deleted file mode 100644 index e0db66ffe303381a4cee87351807509ea9dfe0b2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 662 zcmV;H0%`pL0FnZLU_apf@PFW7LAU`#0MHM{0uB!zWPn=m3FHDD0I*P`Fc0upzYy?v z!@tn@2Lgj3z(8Su@q8k$iMl zV0ptV8ZV>|{0|s(<3*N$d<-LPi0M9%8{j-({|yK{VUS>Wj|b^-f&<5o5zqs~08e2D zjYJ+)aCql|=l&1Qd>(Kp0RA)Zf5QYp;$IjMe4$10j7R^Fcn8V@p|U{_iwf&=_tj1%Dx2aUo72%JD0CH`-WBhdrI!<+{Ie2feJ z{}@CQ0f>VG&jAmU!9TGW3_=M&!g0I}(STeALkNK5AQA{9!v!YsgUTHUkVHS!I0Xao z55UE|kO&EULE~V6Oa{UU90Ysu0E15;0PzKmGygw>OfU!WfGi3LAZd;~9wPAvjlrOR z@ql8#_wmq!#B3k}fP>2j^b$aW#vl$Z0jNNO=nsto1%QnLNVGsw*ayc=3<>G{5-|XW!aN>4em{?&RG4sJ*a(mRnmLGlK12bKciBJ}_K0P+4{@?i5q4L?QbgUn$vDD@Lb zb_bj?!J_;???CZ~N;H{fDcfQ?PlN{f3E@HR0QUdy;1>{hz_^zXc?w1V_7Hf~LFGgT zj(P#)bPxDHIrs<3-Y_Tt{xk4?kUY!r7>|d`oO7;G?R0K??;PvjN@5CD!7jo^VnAQuQk;}HSEKqL@o zh6+vNUz9o#Ac%jca0& z5C|lJra16;i^Lu`0LZuo1P>SnEB}9hKtbX*AMh+-Ao9XJ1yCUIh!@L%Y7ij$1LHt} zU?V`1Ef5s80rAsA0qYD0pMah*n2rK64>Ev(PyZi;#IO*MNpJz`02na5Q6dHxuz-Xf zilPUW0n{Op|9}}XNCASR#snW=dEOw55r@b;9z1?OkDpYU_+uCdkN@NM@Av)t^+}(` z55U(*E?0ivV%@tmr_uqS)oP|wZqH)bvVbqn2&^4;s6N2;ln>k<(nxWAf$btsyaEsp IJO_n7;4e4@;Q#;t diff --git a/assets/resources/dolphin/L1_Recording_128x51/frame_6.bm b/assets/resources/dolphin/L1_Recording_128x51/frame_6.bm deleted file mode 100644 index b2676b7ce2fc232e1fcfb5069feb32a5271de2bd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 664 zcmV;J0%!dJ0F(lNU_apf@PFW7LAU`#0MHM{0uB!zWPn=m3FHDD0I*P`Fc0upzYy?v z!@tn@2Lgj3z(8Su@q8k$iMl zV0ptV8ZV>|{0|s(<3*N$d;$Y)i0M9%8{j-({|yK{U^Qt zJZd2Fql3phHb^1-q9^f; zPruN(1Um&FocQ4dLVe1cY+wP zC&<9R@9~5|Fc^q8fVeCHhsofd*o+1t1fXF!-T?7@Arpv12N?j6K_(a}H;f)r_(Xys z{-MAqABcViE#%-p2tp4V1Oi|-7!Lyh9{g}1amW}vL1T>n&*0Mx0sMF$&Kv?6AZd;~ z9wPAvjlmisk%1$^CSV+fPOF#cm;xmA%K5?!V4I9JZF#y zWCMXgkl-L7VSquV0P%-^qvBDa$ZQY^fEZvr0C@4>HvmA(q%jZm4go+Hjt2oV3l;)- zA;ds%kO>4DA%eg|M#VskNNAtPECwI}947(<4;V!PAt8}};P5O&0wIXtKtbaWphF~i zBrw3K9td6|z=SCU0|z4H|KKo5C*lu~Gl&3a7z>1p)Bo@T$N7WFgVF&{(Rv{B7)**i zMABK7hYYZ2zYsgnJYmv}CD^Q?d4LEWVfN3x1_uC9i2ecN?1F>HzZr->`VtQ<3H{>{@OgZ&G6eg^C*SB^ z0v|$2HjY!(p0BoPnw4emdPeg-Y%z$eCh4;usmU?vz30|6HFa3Q`3I6OgPjQ`Kz z(+mWA@xb_J;4sMq1O1N&h`ewH@!&uOV-g390~PjzAq@Vps@BB;erm01K!= z>A=I_0qV&l$bkp&4-w#i8VC;@2mX#9ka-*iiVqlcgTdp+_2g)d$!fqxc7`fWH9u MKfD4E4>*E;0O;od%>V!Z diff --git a/assets/resources/dolphin/L1_Recording_128x51/frame_8.bm b/assets/resources/dolphin/L1_Recording_128x51/frame_8.bm deleted file mode 100644 index 05f98d639bbddc8653ccabef2808aeb876a4b0c3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 663 zcmV;I0%-jK0FwfMU_apf@PFW7LAU`#0MHM{0uB!zWPn=m3FHDD0I*P`Fc0vUzYy?v z!@tn@2Lgj3z(8Su@q!62UqK0wYO^1xgqQlG|x3FPo#@?i5q4L?O>f=ppCD2)?I zb_bj?!J_&=uR!sIN;H{h2f!dU*p8Fw0lovq4{!&R0RlGP{{QK52bUh=4>%+N>>%-| zgUXH%9P^N2F0l{JegW(kj0ylhjQk({1qYFQV0Ykn!>1ZZ#DDxRACw0}%81a6E1o2n6B*NRVW)^!N}IU|5)U zf*7$ZM1n<+lY`O?;4TXQVe)z=G9v-VNO)LIH-J1}2t?x%2gkr91@Xa2ykPR5!Xyz0 z1poq23=CVzz<>~h9ySOBz-%xc1_C|!;6US$FnEH;8ULTbrWgbG@IRb51TsL=9C$o` z5b${15uzy=kUU@*ul@c30SAbPU}zDLgUbl95YU6fus`S@03LpT_|PC&2+$;pLqP(T zz&<)?U_D`g^Y9bKGZDZ>LFWg+flvP*gv78AkV$X>>Hrte;1MDQAI2X54_3(o%K`rf zi10uS1Pqt@iXV`9-Y9_ahe$jgJbpiqpMeHWNDml~|Ks=X_x=0$2t4AL4Ft9+V9Rt5q_ac6%1hlmUJq0fGa^U8)bTJxA~lSpj|l?tgd$ARci9`~W@N5<~z1 diff --git a/assets/resources/dolphin/L1_Recording_128x51/frame_9.bm b/assets/resources/dolphin/L1_Recording_128x51/frame_9.bm deleted file mode 100644 index 65b723203c3cba419975bb816eceab751c8021f4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 661 zcmV;G0&4vM0FeR!d;1$^CSV+fPOF#cm;xmA%K5?!V4I9JZF#y zWCMXgkl-L7VSquV0P%-^qvBDa$ZQY^fEZvr0C@4>HvmA(q%jBu00K}94gzKtECljH zh=b$c5(xD31%QT)ih&rA(J_%23_?Rf!f+r!@q|zTBO=Gi;8=+U3lYG8gT^61hDh>Q z`g{tJAc10h2ttrhFmf(VKR*PLP9fPNIDiI$fVfD#KmGVTf06Kdz$yAKBp-hWi5JI3 zW?CFF!J_&=|G@EwPBd3wdD|j7PoxI;4;VkgLzaMi3?pqG57OfV2ag^jpa+aH4FG!x zJZd2Fql3%d7R?fd}vp5#WFt z2oD?w{*E7zc^n3c4;XZV!Q;o{`1$w{Jg*W!c*KAIAHRRU@87^hCltVFHPQ=}-?!Me vZr}&>plCH(sg&EZ*tTq-5de8X0pqUK2iP8?_y??jzX10?yaEspID&ovpRo_7 diff --git a/assets/resources/dolphin/L1_Recording_128x51/meta.txt b/assets/resources/dolphin/L1_Recording_128x51/meta.txt deleted file mode 100644 index de37d5b2e4e..00000000000 --- a/assets/resources/dolphin/L1_Recording_128x51/meta.txt +++ /dev/null @@ -1,14 +0,0 @@ -Filetype: Flipper Animation -Version: 1 - -Width: 128 -Height: 51 -Passive frames: 6 -Active frames: 6 -Frames order: 0 1 2 3 4 5 6 7 8 9 10 11 -Active cycles: 1 -Frame rate: 2 -Duration: 3600 -Active cooldown: 5 - -Bubble slots: 0 diff --git a/assets/resources/dolphin/L1_Sleep_128x64/frame_0.bm b/assets/resources/dolphin/L1_Sleep_128x64/frame_0.bm deleted file mode 100644 index 9560e1f41551cbb914ece9b331178782165c8e38..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 580 zcmV-K0=xYI06+r&fII=<4*+-rz#ahb2Y@^Q;12+J1Hcat2lW5^46XPGZ~wslgXVt# z6&OALfBrxJ@(+LLMDH*UIX%JY4uEG3?SJ?I4hI0Z5}ZDJ_zVMpTX1>z;1CP}P(TxJ z2aE&k52?63xq$kMgUWEUU;)O#<=~D$3PT?UtTY~24p^N(^}X!1MtZ;9#?Gc=!Q>KmiVO=5A1U+yR3i0S%9w4@g)%OM?KI z9ESlpKPWt7!10I-OUyt>Fg!p77#NmdfRG@1LIflJ|MSzw0|f{Gp^y%Q2Z20K90VSM zL7Wsv!ayzfsDK2-5(vOB-?ecTh_+BD1@MSoV0hQhq971>z~C4_5X?J^;($nClnVp_ zoG?O=&?W%!m*6N6Dg=W74;T`NyeBaSjsgZ3902fOTnq>Q2mNmdC}1)Oz&!95@|c(g zE)EzBVmSUJaGb|6_Xm~)_%P@3KuqNz_dkXKf+h?cz%UOC1fVny!}wqnD*;4XgMf}9 zO98Nd3<8N5@DxN8DB(I4hyMi!j1u6P5Miwgh5nAfF%U`f45$UsdKMG=H-Ka?aQ5L) z8{`!m$L)SH5Af($WMU<-<^JyRpm+fz4g!e)s2Ax6+kXMTOhMu?69|w8;Q^p~FYs_- zZ~%f25&*-)VvrF~!h-@2I|t#WT@U>30X=|!!Qz1tAweJ;AOF4q>i?KX)-&P&v_{4M S-@)pYUZ^~NANU9~LW4vded_Z7 diff --git a/assets/resources/dolphin/L1_Sleep_128x64/frame_1.bm b/assets/resources/dolphin/L1_Sleep_128x64/frame_1.bm deleted file mode 100644 index 238b50a2e7b89ec4d08249e43b1fc9ed16fff178..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 589 zcmV-T0fII=<4*+-rz#ahb2Y@^Q;12+J1Hcat244IGGym{Dqu?RG{{#9D zf7qm80;31N$N$Iw{z3E~8_WaF2O@fZLMIM@XAST86dVo#a4Wcc_wX180M~u^1Oos> z5Cq%7;{f{u>TVA%U_PSY@|*+eHV-cZasW~lp#TmV4=e|d|HB5a-v0&%lmdJK6Ps_~ z;PZ|^`~o3AED;dI3AF& zc$WqNFgXqaa(+;F$ARMz7?+rUkYIR#3NSG&!2uvZ^n?gR)I9KUU*KS&02DF-(17qK ziQ|BS&`2|ai1IQh5E5L4gOIfNm`@=zr&M3G4&@4-^QA3JCz%|NZa}SO5HB b6IjoP0?`{6|9=OnReGTD_TVA% zU_PSY@|-Og0CBhk`T^jMKng=22A|*m4S-Ne1LObj!|UU}!LS$sfQW{Cb6>vx4?ut* zMmYoU2!sGjLGQsR5x{u-07D*XExaC_0g(O%1|J!?JqBXbADOv9;y)<>JZya6dP3lZ z^Ozim0XYYxHVA(x1VCb5V~Rn6;(QncU}9N<0ziS{04P9&OGD2%oD38o0){|35FP~a zsBjQ^2?lUc9|-`r;-UZ)7=lm?H|!og^3fK`1pvMg3(OB1`E*1A4;UN+2m%?0a2_`J z1cY`r1qne4LqM1V#$SM-3dTWz2aE|sUK5yu#{mP7Oco^q;9x)aKkIlwLjjCN0q20f zl*GU>aB#q2k;m~PgyuPqxIC~W!G}MJ0%s`)x%@B;5U^n80f2a5B>|vzAHx8lSPCRt z7~&ML8wc>fD3K2Y1RhA?Iu(ci1qX~G;F%C%tqX={O$ogfPcZ_fe|4=AR8b5z5(k0m`K($;sCTp U#sA;I>XlxoJboYe2sA>2M3T4c#sB~S diff --git a/assets/resources/dolphin/L1_Sleep_128x64/frame_3.bm b/assets/resources/dolphin/L1_Sleep_128x64/frame_3.bm deleted file mode 100644 index 72fedc5e13cf89e0b10e68e27ddbb31f86f17de5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 597 zcmV-b0;>H108s+}fII=<4*+-rz#ahb2Y@^Q;12+J1Hcat244IGGym{Dqu?RG{{#9D zf7qm80;31N$N$Iw{z32k51r-#=LHcx0ptJ}bOShVYyZFqa5x3PuHp0Fz+fB$TZ7NP z0Dxc!f&iO%JYXMSeNDmT%m>t59#e%Q01h_*pFlhj$N@-Lga9~f0)kKA)Ee;9#?GdJM&=KQnTJ z#C}o$c-Z;C^o79-=P)@A0&;#>A^e~b0f~8t2?hs=@L&;viDn210tbiyp#l*#4?G-K z_!uZa1q^_6AUp};c;F!P5)9y?J`w?M#Y6xoF$AC(Z`!zvL|Z5n0{BEPFg$DJ(GUnc zU~mi|2xc9?c-!C-5!l!iB?KuA0$>jregc3i83q6zFeMRqPGS!n1P(zkSdFa>1~C{1o&x?;69C1*!vTgzAHtXS|eir@8I=HuT&mC5BvlgAwi-5Z;a;$ diff --git a/assets/resources/dolphin/L1_Sleep_128x64/meta.txt b/assets/resources/dolphin/L1_Sleep_128x64/meta.txt deleted file mode 100644 index ffd845e8ce4..00000000000 --- a/assets/resources/dolphin/L1_Sleep_128x64/meta.txt +++ /dev/null @@ -1,41 +0,0 @@ -Filetype: Flipper Animation -Version: 1 - -Width: 128 -Height: 64 -Passive frames: 2 -Active frames: 4 -Frames order: 0 1 2 3 2 3 -Active cycles: 2 -Frame rate: 2 -Duration: 3600 -Active cooldown: 5 - -Bubble slots: 2 - -Slot: 0 -X: 53 -Y: 20 -Text: In a lucid dream,\nI could walk... -AlignH: Left -AlignV: Bottom -StartFrame: 3 -EndFrame: 9 - -Slot: 1 -X: 53 -Y: 20 -Text: OH MY GOD! -AlignH: Left -AlignV: Bottom -StartFrame: 3 -EndFrame: 5 - -Slot: 1 -X: 53 -Y: 31 -Text: Just a dream... -AlignH: Left -AlignV: Bottom -StartFrame: 6 -EndFrame: 9 diff --git a/assets/resources/dolphin/L1_Waves_128x50/frame_0.bm b/assets/resources/dolphin/L1_Waves_128x50/frame_0.bm deleted file mode 100644 index aa7454666240b57190099b0e7bee4a1c218982cc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 443 zcmV;s0Yv@*0Ji}EKZDN$0uSIHI1l_hC_yp+Kl(TXAbno|W$=T8=l=m8m;8S@@YzQ{ z;|Rb;N#%r(j3a>=gaHbAFNy?V2y=iTh|nIN3xE)^1%dz&JnkF-$%E8XcZ>i(fI>Vd z5Iw(>@OfT==l=hI7xD}qsDNN!`ltZ@uzv!$wNvmY>VOIQ1`hro;Hv-Y;lrv`YALe5 zeQW-p>Eg9gpdi1?-~|!V01Nw9XO{x6RUN1Rf7eYZ_2fsd!ydOzcpaw8M zz&!VwudVz(omdZnYYP17oUI#WkCQB3=l9tQ9sTB z_zw-&fPEOiJl|;86}W>&zzHHE01m(n8~{AW<_|#0-wKKkNCXH7Kj4_H2mqb}@t+C| z4`RT=0)!nHa6O3wFai)fVSx4=4FDcUK?j$D{tq~DfWhe;z(>L593>i%&_u;71nvM( zAbH#ah5Uiu0K!lrc+Lr{{DYtcwm1k-J=-Y*pv6RnfN>y)$a-J}C5!@q9}s+;1b%-2 le1E_Khb)7A|KLM6j6fQf$OQZjhS3ME06J$wkB{VRvG5NIyQu&G diff --git a/assets/resources/dolphin/L1_Waves_128x50/frame_1.bm b/assets/resources/dolphin/L1_Waves_128x50/frame_1.bm deleted file mode 100644 index a23d250b969fd3914a84d4ad0b4e0594ed714ae5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 448 zcmV;x0YCl$0K5SJKZDN$0uSIHI1l_hC_yp+Kl(TXAbno|W$=T8=l=m8m;8S@@YzQ{ z;|Rb;N#%r(j3a>=gaHbAFNy?V2y=iTh|nIN3xE)^1%dz&JnkF-$%E8XcZ>i&|Iz!v z7)ONy2d@MF{|A-m9)I9afC>lj{euUpAQ%_^ssKCdjDh2-pZGuepaMsMgT9COD!=;p zaO#y>ioCBMTK}jzc&${c{i`gOgIB5@AOOGpcF}Mu^-NYQ0RcN*?e&e_#Fnt92cSJnt0{@k4=QueBotUVi}i(R!tVJP0&c$6z0m1cU?c zK8!F9UIF`e qCxBGs1LOJs|C)vY2p>QFFjxrqJ-!eh;n9Qb{#z^p|A-&J*>B)^bG`)t diff --git a/assets/resources/dolphin/L1_Waves_128x50/frame_2.bm b/assets/resources/dolphin/L1_Waves_128x50/frame_2.bm deleted file mode 100644 index cd39b17ec8969063183447fd5463a0ae27cf0fc4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 463 zcmV;=0Wkgn0LuXYKZDN$0uSIHI1l_hC_yp+Kl(TXAbno|W$=T8=l=m8m;8S@@YzQ{ z;|Rb;N#%r(j3a>=gaHbAFNy?V2y=iTh|nIN3xE)^1%dz&JnkF-$%E8XcZ>i&|B(B@ z7)ONy2de~c;PSl#&;9=Z91r371`kv~FfaX70C(3J1IJZA@PG9{0s;(p7(3{Hf~)_p zhYzX4sjJHI^{@Jar;62jpc>-IcmYB>Kmj_%v|IoF&)nla2hZT(ZsAHG0OJS|F_1J; zh7Uq0g9qRZ5PQr90ty}gKCBu9An*V~1_!l+0YQh8L>^oMIRFUIIB4)Z;gg5~;6Mau z7{d^H0P-5700b~U1^NM?CX8_waDRXhVaL&cR1QK9Fo2ST5MK-qf&_;ZgWLc^eyBlU z1mOTTfCfSG2gd=DQ~>z?0q%Ps0rCOvUJwBY#t;sF@r;Th4`h(Kh-h#y`yfmJf5=Gw F2F$Z&xby%3 diff --git a/assets/resources/dolphin/L1_Waves_128x50/frame_3.bm b/assets/resources/dolphin/L1_Waves_128x50/frame_3.bm deleted file mode 100644 index 2d5452d7c931fbcc2cb933335456f4f647e40ed1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 472 zcmV;}0Vn=gaHbAFNy?V2y=iTh|nHi$bZ7%2`qtNfCU&xjsD@l446GH(}dvs zFE{{vaOZb}7~jC+JSY*k;4|fUUw}h;2cP@?m>s}Yx~e#<`w ztE$Z+sDKB0%MeL$Dzl)ehzbsX7zFqpAaQmb@~Ryl@OVN)fU1LxEGW@{e*xqIAJ{zr z2l!u)zyvQs2e{;1MAR?={4f6ic>sj^AO-9Ygj6^He8M1l2>3kVfcKtI0prjIzxw|O z;AqjKpeBrP{E!>~aDRy*s2{{0lfV$p5@mtVK%fzTNnj3zU<(!cp#=tsOomDXAo+d< zq5x2_&;#T6S2+xT2gnD%fM95H2f0we_W*|j_u>Bl2*`o&{%(o@fsFSbfb|Gv1PCYK OI$#3+K!3#e8!QB;G|RjDQco7?di^51Rl;u)Gj_%m726q2vr=0C>O%Y7!X&24R8n zh)|%w_<{$@5kvxz0fQxIgkl7w0B8|NsF;XAJ_#fLl;I)@34o*qEd+qX!Sf*hMj;#& zDFfXWF*roRK%kmL0!a{t1L{e9q$h?!{DXtuDgUUHhd~E0#!LT`0uSM?UKtQA-7L0R1hYVN712hFV6ap|Gd#F&xVj&O%T7YCsC@?+hkO&~K){|ERc09Xc%RjDQco7?di^51Rl;u)Gj_%m726q2vr=0C>O%Y7!X&24R8n zh)|%w_<{$@5kvxz0fQxIgkl7w0B8|NsF;XAJ_#fLl;I)@34o*qEd+qX!Sf*hMj;#- zhvFU&bXdgU6A1)8g%pSck|7N6Kvv`cStat2o)xIj0q+$5)Jj5s8V7>@4_uf3CUraT zJ@H@t8^mB33H#*02g>>B-i6?ggY0}i1Nhtl?f=u@2gCe>zz{4CAjyA%LmmgftZ*Ol z2&`Kd|CfOWfNUQCgg^&?$xq9oP8t32KJijWfCur9ql23PCV+uU{r_LbGzf9{;63me zpdoND@G$&cKxN>#t6Ts!pc!5v&W~N`v`S)hAOpq;!UQk`SUd+J7>EKe0pf8G f13UojemDXL)Ive`i@Ey%2ET*(n*auOz(3#s%S5EX diff --git a/assets/resources/dolphin/L2_Furippa2_128x64/frame_10.bm b/assets/resources/dolphin/L2_Furippa2_128x64/frame_10.bm deleted file mode 100644 index c5312e5e5a820aa4379d24bfda9c4e3a6c296f8b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 465 zcmV;?0WSUl0L=ma|KJ!1JOBb92k?KvV|ai<1QYz9hz1J~2hIcX0~p1CgIV|t0%5=@ zSun5w`-VUukO3HgE-(-8gboCi*f7LHlA#oMki-Dc9wdEcFgU`3ATxj=M-Bu3hlAJe z1_Bj>;eQ(miEr(rzy}T-4@vmuCID3D#ndPOCK}D<0GEJ3z=sR83SEjo^TZX!IK-3> ziNINn5C^BCGGK6e{VeAfmnYNr-w(X}2yp-9oF4V?A^-pJuzS}*>FzTkF3V*x;2tB9J0MLWRxxj&o+Jp}~{F*?nWid2Jyde1>pZ*FDh=qV2UziX+ zED^wYUSK5g2etzS1TFe?zy&~B9{?XR02xcc=YWHh6b4apN=5XjI9KX?h!Y8XA|2#F`05eA_3Di@J)Nb`;Ym%t`9dA(X8 z2xA!h!RrWO7=TX<4>Yid1Rr#W1wpVVVUYvoKo7<-s>ojD@ogA9WF`fO0g{*xBzr)@ zLIoR`JYp*P;9np(2!R2B2Oz(L#EyUig8+xIdw|dZ_vE;P#t1?X1b+8pA;ANu0sP;Y H1;juD{u;iw diff --git a/assets/resources/dolphin/L2_Furippa2_128x64/frame_11.bm b/assets/resources/dolphin/L2_Furippa2_128x64/frame_11.bm deleted file mode 100644 index c91ed2fd23efc317a9fec7f9244f5ecf26bfdaf6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 698 zcmV;r0!94+0JZ}EfFeKmfrIjX59|Np9uLp{LL3k1U_W#*kbcNuF#vwR<^cS;@vIO) zd(Z#CF}ve9B@qX-JRV&5#tfn=$M69PXF`dC-_IZZ0Sk>X2aoI?k-rCw2tNFDh(7RG z1aJsF{OCR-a1Q{$x3&;_+0i069sK5s4--KE3qB7Yqr`7u@&FI;)>rrp1JI!=ED16{ zi|2eOP$&mL2sL^*JbV9r{(!)9X&8`x@E~~0{Q&eFj|1S45O~kx-2i$rMgb51`=XFY z{7@c~_&7X%AB}K5FZveQ1B3uPee_r!fBx`1X5jOjOTpql6S(+u|M*Be_!7x95WXpK zPLKW!4>$%49$ov|$bZxR2|R!U30bH0tOFAfIQ$(2*5D`?RtQVL>}ORfY^cfK@wo4#6MrnKs|tmU<0WTLH;XP z27sI(^o9V%@FvkNl$ajh@M$c>{x_Jwy*v;P#4Z6m2#gaC7YC$sfA@mGI8b;bBL5Hv zl>>p}ulg3lJ^Tm<|BIl5$ALfoSM3lU2l@jagnjXN6H#zK_4KgJKlR@P9)Acv7K8uq zuhJkK&-_qkJ@}dkx*z}k{!f7M-}}L#m&pO;={q_P6AHB35AhFN`%)TJ2HpaDrD0~`qk(=2CEZ z0f)!)C5Sy^VDfDd6n=P6c!CNF4@>zJeV5sIJm5g`(0}*gLcxQ{tS}II8iWXe@eB|E zXaM&oh#-OHDi9%W;4^FixNpGtzyG;6_}9XI3LFReE(ABI%&-txX8^(=_6Q6zF#`F| z%pwXHSUm6OQH)?em-v`H|E9r!(1@c42fM^ULUj;%Vgc))|K1Fy>Kb?a9=!ZL{EAT6 z1w0;~ugGj@5C#axa1?NQe1YPw7#HjRdiZJo@l^SIcmMtl`T!3dm;X)wiUZI8-@skq zAn}sVy5IRje7^$Wz<=Pt@uKiS==&f4;{o%GgT|Nt_kZdD@qLIqwsLTI)$jj*{(v4o zPrv8!!7vCsb@%_@{(}R60SDkA5P1JeqyPQ^#q2GC2cG?kAOw2~a6#jN_us+G|A+7a f<$u2eTmSC}k$89X5PN_N?%5stv_aF0B#1shQS}MQ diff --git a/assets/resources/dolphin/L2_Furippa2_128x64/frame_13.bm b/assets/resources/dolphin/L2_Furippa2_128x64/frame_13.bm deleted file mode 100644 index aa5353e988d02785f45759a024931abe7d580086..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 584 zcmV-O0=NAE07L@+fII=<4*+-rz#ahb2Y@^IANT|F;Rk?x!S}#`z&(Kbz(C*~0DZs# zKZbz<@kP z-_ZGk-W5OxmVe$YUN}5vRseX=FaHn!_#S((c=Kc+^8fh5iJ!HD#(O>ojswBL|KI=9 zyXAl$Ey0L`%fG>Z-}v0Uoqw17zfWUvCftXMrUwwc* zFdOIv=nzr}54)K^iv!95wSa!G^7S~tJCHd1P#$^^c)jswmy;2(?+ml*DY%6Tpaje?1R4~z?+C+R@JB71OJ%^j$i~g#Q}fdP!0e4@$U+bI{^s# z3(Nlv;0TA07lYt1>XG_?f57p%{4ekc#4b7D#lRpxNFUrDAR+mp#tHC`{fFbU28aM| z{|{g!U}Yi1z%rjedF|ox@OZ&E5DPI#e_na`JbB{LzW^w(J#=94pcIrmEe`+y diff --git a/assets/resources/dolphin/L2_Furippa2_128x64/frame_14.bm b/assets/resources/dolphin/L2_Furippa2_128x64/frame_14.bm deleted file mode 100644 index 837c6c71defd8be280776e7881817fc68276b5d0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 610 zcmV-o0-gN<0A2$B|KVUk{Q*P)Fi?2?0rvt2kDwoLFnIg{_W=j^KOjHgdBMT)0R98a zDj@jq1I#KQ_}~M|;Q;Id;{Xp%fIlVx`hWxc9=`B>kL(_GhKz#+o><3>gVG5B{Tt>8 z4;U5;!}CwXJVamY_&z)lfe}FLAMe0Dt_H!Nasoqr`-9r~&_@VJU>l>0Krj6W95O)w zgJlpe;FN#haR>y*&>wsRUrhppDF-nZ{sYsG|A@(;VDX@2Js+@oPGA%6%nc+tE0kSi zuxJH>Lj(tm3qcDYD|B!X2tYh=AoBP#)&UWmL9h@2c-lSS_KA8RbO;895vb?`+9^1J zKqL|g{UbmRX+$ywAO;Ttt3U_0LJkiYg@OTE2b}yKQ3wz`aF!;BgU_B1DFg(5A@ZVV zdEoJzgVn$w5#o2S;PHin%RrqGp+G;L5P3mC<-mYJ58@9VCwyLBV0pmdiSdjD2SN@Q z8NBEa$N2-wgZ=&g0qGDH4O%!v8W;Zm|NDc@SRg@@1ImB)|6{@DKq!m?&N3Yo|NZ%| zgUlq?4N0o0xY&yEj|qzoQt^FED>06iEa;GRJCuV4t{^!eXG#1C2(0)+vGdoW=5 z-AFJnC>}#T4`yhz^{KA$Q(_^4>({vevs<0996^* wNHeH?5Gg=DU|{j6KxP1O`UB1b0N_Ay5Dfv)f$A(Dg8+f^cz{o!766F@+?Nvri2wiq diff --git a/assets/resources/dolphin/L2_Furippa2_128x64/frame_15.bm b/assets/resources/dolphin/L2_Furippa2_128x64/frame_15.bm deleted file mode 100644 index 0000a886313b8c265743efe166ccfd156492d830..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 740 zcmV2edpMFaQvP#()oKH^LrM60rl^I503^{{iLsHy|)^ z0P@TO$c~T*6avV`F91BQ2NQrnAQ7N>U;*j-2b3ZK1#_7gbPDB+UsoNaWG5dPb%aa%Ma{d z|DEDsz@&x(4~zyiF!OMK^XY*gSOo$Hj1CRJ{O`H{==f-1o&bS^#{qtz;s2(wI1VBV zR2T#eE<0=n8}}~2CKq!|)qA>9R0!Kjw;7H&PBItkNnc#WQA#nkC@B-!W2amiSaeM(VZUFM|55zur z3vdiz5rDaP0mKg|26UD%2yi^UB;p606xKKhI6S}qaC+Xa&=0^Es2%ZpVYol}I2f=O W5qn`E9|>4I?g03JC zA2bOE00<&62h@N_MMD3>!RtVd0SE*!_y@oqya2gi1HuI$kb}V0E5g61P?e66wYH9giI33j52{(AdrB`gVf+cal;Nwlp<4z0LlXagupw1_IjTN zArTdbFoEVNECQfnMiC(pJ>mfYBMcB;p<UWSb^&U9gP8?HUvQ}Q-S!Aj0Rwr5P2)idN68af)NZK2tD!;9ESo92@?RI zh6)clN5Vq^MF;@_GKB&s2`okA)ew0}AXG>YEJP^P6xl@}IEWEAK;n-a1iZRX z2$W|b1X>6jLFI4qM!wUzT z3Mdau03?0jVqgpk5>2tL|o-Vkkr_W=P; Xi41@f1_$-wIw13am}x#>`Gy0~KbO0F diff --git a/assets/resources/dolphin/L2_Furippa2_128x64/frame_17.bm b/assets/resources/dolphin/L2_Furippa2_128x64/frame_17.bm deleted file mode 100644 index edccc7396ec68d5dd2a861017a681b2763c1a4c1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 451 zcmV;!0X+Tz0KWkM6hI%*fPX>)+z)6lKcGPUz(7I#PwD_arU3pz{4gKTMmK;k_{2d3 zDe-;_GJ(K9|L_=t*8Igk0EA=6#lS<%$2b#|96X)?8DOCw>-+=J90#Cef#ZOI;2`rr zNu*W-zzdWr%n2f(z=7vr0gnR~(2t^UO zA(FI0F%QWy6vBy!gahH8Qk*0~K(B;I3|a{RiG$`r{)|F6C{hQyEMjnpgn_!C1EfGC zkqBTsfDa_TQWL`<{z1X-6#vvpL!g70V9{3ED{Pb`z z{6$QLp%MGwB+w8k2!*2@&>_PW@qo<%P6Ysrd;zFN8>kq@Vj&QFT7bk%C@{HXFbbps z2rLtU1Ka>0(GCd2hUf-62Y?DM5-X@w>^KMRlm@CSfA0hhpbq?iBy zfrHw>1U~>!W5l;g-C!L zl0W%Q5+I;30>DxO7J@)xVEK@LqY#b?6oKxG7@Q(uAZRcEsSpVyLKqJ)14%EGgz(5e zkZ^m&KlKuj=pg18$$#=-LHsq$0JyLIj0iCIxqugy^V7gQ1=t6u@PYIH)8HS)UkBB| z3OoV&8U!AM(Ju(SP^F9i%fST<2d;7ffXPqKM*|0_D59#990~of7zs24E{z!Gfesk2 zj0R{5a3};|KKD?ejKo492DJdlm{4VjjSi^*f(s2~KnDhha7H8miwA%hFA@Mo0zNrV r1;+vWK@bLD3)BEMcz`zG59&Zam1IO9e?TBI0DiqhA5j1hwgdnmDfpDF diff --git a/assets/resources/dolphin/L2_Furippa2_128x64/frame_2.bm b/assets/resources/dolphin/L2_Furippa2_128x64/frame_2.bm deleted file mode 100644 index ade67d1016ab61cb52596c9cc607f61b30ea100a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 402 zcmV;D0d4*P0FD6wcmu#50PqKZJOSVj0C)qy9suwMfII=<0ssHtVC(QeU=iSb&HzL3 zgM;h+e}H-;fa$RjDQco7?di^51Rl;u)Gj_%m726q2vr=0C>O%Y7!X&24R8n zh)|%w_<{$@5k!K2A>j55m7)=dhC$9x0x1;}5y*hZ0qjX1{HF;JzCZ!*0Z0s52?33R z=0X09LO3Q-pM%{NF*roR1;7Wq0+9fcL@|&bgnSO2M@{~-Pg z`;~HE{G0+20RKS2?~4EE)P?_Wjes7!ub!O9I1l(BLGqFw1^f^<3jjTT`f_mi55+jR z1Km0&ija90cgzFaY_m wD@TC`afymcg5DYcK17NS<{sClcm>^jCiVOk07yZoxc~qF diff --git a/assets/resources/dolphin/L2_Furippa2_128x64/frame_3.bm b/assets/resources/dolphin/L2_Furippa2_128x64/frame_3.bm deleted file mode 100644 index d05e8ae2b4715a5e6ec362ef63a96ac6b1ce0895..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 374 zcmV-+0g3(r0CE8Ucmu#50PqKZJOSVj0C)qy9suwMfII=<0ssHtVC(QeU=iSb&HzL3 zgM;h+e}H-;fa$RjDQco7?di^51Rl;u)Gj_%m726q2vr=0C>O%Y7!X&24R8n zh)|%w_<{$@5kvxz0fQxIgkl7w0B8|NsF;XAJ_#fLl;I)@34o*qEd+qX!Sf*hMj;#& zDFfXWF*roRK%kmL0!a{t1L{e9q$h?!{DXtuDgUUHhd~F9crWf%$$#=-LHr>E9{8{R zj0iCIxmW?~%K7Qw9sn@V0rs9Cc-#a+4^RG|0RAfc0&#E$xFEOz!H-S>?oAT#&^!?M z@I5hq`FIqefboIDfyaQ!PuYh9Cx`)}s*_NOaYN-MfPqLvEg0s34j2(2`(}Wr0)R#X z?{x|o%tRspYfub{g$4(`QUL@O8pwbS4G`drNC6fP05D!80E`5E0ALM{1NglICNEF` U+2R1(fIq1K{-6W=AK?E4z^3n-*Z=?k diff --git a/assets/resources/dolphin/L2_Furippa2_128x64/frame_4.bm b/assets/resources/dolphin/L2_Furippa2_128x64/frame_4.bm deleted file mode 100644 index 5aef127622c6a77c35d1a33e111aca2f49d2cd7a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 440 zcmV;p0Z0A;0JH%Bcmu#50PqKZJOSVj0C)qy9suwMfII=<0ssHtVC(QeU=iSb&HzL3 zgM;h+e}H-;fa$RjDQco7?di^51Rl;u)Gj_%m726q2vr=0C>O%Y7!X&24R8n zh)|%wQ2`JD;DPdlQ6Qhld>|g7Vzfdr5Xd~@AP-m(NT`^OL^FU;NF#G0!h2Z~y z1R(cN8C*Xf{P;cXKr)^X2ObZ4qyh;5PLIO|QJBX8IyZ~}K5Pon;6dDCVv^vuhJX)| iB7^yeKn#up`Kn+F;5Z-vw}E#gL;;=wcV7v;e+58OX1XQ- diff --git a/assets/resources/dolphin/L2_Furippa2_128x64/frame_5.bm b/assets/resources/dolphin/L2_Furippa2_128x64/frame_5.bm deleted file mode 100644 index 3be1790d74ca3b2fce974f633aaa7e01de7b81dc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 449 zcmV;y0Y3f#0KEYKcmu#50PqKZJOSVj0C)qy9suwMfII=<0ssHtVC(QeU=iSb&HzL3 zgM;h+e}H-;fa$RjDQco7?di^2q-W;Yyl#|@IeI_2duyZItm^@#xMy0JwnA$ zkjM}vKnJWaK2Zt~@UX!3f(ObGM1p@I_<(wbO3?_!Lm=~rfee5J0x1;}5y*hZ0qjX1 z{HF;Jz89VW0Z0s52?33R=0X09LO3Q-pM%{NF*roR1;7Wq0+9fcL@|&bgnSA&^yK02ABu5sl|h@N4$1{oLzFaIwB3HU+d1BXKa<$%dg$HIOkbHE~@p#TZ^fDM3?0*L%z2j+mt z{9*Ua0Sm$Z00=?upfb3AKl$)`+JI#|APzhp^+*H~0G%I(45KlQ0(5T}0DRaLqrii> r#Kk4SZw&w+B1H%D5P%sR2lG_G6~J&n0dE5CNQeVG0`9&Odj1N48I-`J diff --git a/assets/resources/dolphin/L2_Furippa2_128x64/frame_6.bm b/assets/resources/dolphin/L2_Furippa2_128x64/frame_6.bm deleted file mode 100644 index c457e78771463e34c9357d1c8dd27ddc2984d33c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 466 zcmV;@0WJOk0L}pbcmu#50PqKZJOSVj0C)qy9suwMfII=<0ssHtVC(QeU=iRsfe-*w zfDrs3;Bv47+x>rldLw{xzzM_vcnBT>4>S>w0AN7$;0#I?W&{)(2oFKAa4@_OK}G@V zFaZvNhmakVf)Ec#zzAv*83F{T0QH6k${|970Y?oG2cXod5Q-!d`47Yc)G}6xMj{yp zoJ1lZ^g5KniHPJtWB~RgkN#7Hh+hlBu4oECV$etoY#%ZY^kNagGK~Bl=&^~yCJ-(F zJ>V3G1d<_)fczui_mW>J5(*px@ec+gprW7pj{%53$UlPqrmA>d!Z<8ZJ_q1bSFfAr+x@E?kC zaFs!uqz@$MpN>5^kU`GLIxQa1~30F0txs*;{%670p)fI+{{RRR diff --git a/assets/resources/dolphin/L2_Furippa2_128x64/frame_7.bm b/assets/resources/dolphin/L2_Furippa2_128x64/frame_7.bm deleted file mode 100644 index 7e83e14a5ae80fed0dfe4163d65bc5bb431613e6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 350 zcmV-k0ipf@09pY6cmu#50PqKZJOSVj0C)qy9suwMfII=<0ssHtVC(QeU=iSb&HzL3 zgM;h+e}H-;fa$RjDQco7?di^51Rl;u)Gj_%m726q2vr=0C>O%Y7!X&24R8n zh)|%w_<{$@5kvxz0fQxIgkl7w0B8|NsF;XAJ_#fLl;I)@34o*qEd+qX!Sf*hMj;#& zDFfXWF*roRK%kmL0!a{t1L{e9q$h?!{DXtuDgUUHhd~E0#!LT`0uSM?UKtQA-7L0R1hYVN712hFV6ap|Gd#F&xVj&O%T7YCsC@?+hkO&~K){|ERc09Xc%0uQ)60v!(r zR{%IO3BW$C0V41N>M#Q=83LhzGJ-(C^9BKwAP8Uv4IDx~4vYZk^aVmUI1ua{fP7E@ zhakcO;=ltb^g{%2i2(?>Fo=jgCIcd#L*yt3hzN^^2B5%|)sXl>LGuU!4I}vrfdk=u zQjhdr1%S2iy=^F@Sx>ClCl=KKu}Lc!TT!I0STn5Zzz{xC8jTfC7gD R_?^t!2l=@{{txc}J^=C5d%XYv diff --git a/assets/resources/dolphin/L2_Furippa2_128x64/frame_9.bm b/assets/resources/dolphin/L2_Furippa2_128x64/frame_9.bm deleted file mode 100644 index 269e5b1d860d851c52cc46fe88b260d4ba0f9a9b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 317 zcmV-D0mA+P0675wcmu#50PqKZJOSVj0C)qy9{}J$VSx>WL;?FSkOOc32Y^r@AJ6zg zZx9F%f(HPHgW~}KfkyzNa2Lb^4S){_RDtggh*$vU1_!_XK^QuqJ|N+vyhMTVj~yS< zRu5?Tz<>q%l!Mr|GXOFDX=n(91Jb4sgGzO3?r{gO9X`5w;sv+>RPy2AV{`xuwRpaA zI)DZs1xQicLF*6$EGgh1g_J=6$94!I?6c8+M zaHrubuL1@SQ}U7|9|VDR$ss@pRaQI$8=1_lq)tCP%rP)h-~V2@}Tv@jpB0&{>Hz`W`JKd=njggbmVMfl(rNH|CM PkTM7aMhFL+tl;4G_Y8T; diff --git a/assets/resources/dolphin/L2_Furippa2_128x64/meta.txt b/assets/resources/dolphin/L2_Furippa2_128x64/meta.txt deleted file mode 100644 index c21027e49cb..00000000000 --- a/assets/resources/dolphin/L2_Furippa2_128x64/meta.txt +++ /dev/null @@ -1,14 +0,0 @@ -Filetype: Flipper Animation -Version: 1 - -Width: 128 -Height: 64 -Passive frames: 8 -Active frames: 11 -Frames order: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 -Active cycles: 1 -Frame rate: 2 -Duration: 3600 -Active cooldown: 7 - -Bubble slots: 0 diff --git a/assets/resources/dolphin/L2_Hacking_pc_128x64/frame_0.bm b/assets/resources/dolphin/L2_Hacking_pc_128x64/frame_0.bm deleted file mode 100644 index 3ff70a91699daba73b1e15ad953597404f21fd73..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 543 zcmV+)0^t1t02>1TfII=<4*+-rz#ahb2Y@^Q;12+J0B`Jbzys%g6hLdw|K0cqe5e5T z@Be+^{ICCh;P->@0qgMt6&wa%nfN_FVDqCCi~Yd<1JYxS;(zF%kh%paaHZP5cASF948q4a5Hr#4N}<5C6Ua`VWl;A`dt!&`zW8hy%nv!0~`!f5G&n zWP#@b2aE++H9-6W$_4okq)X%vEBp)|gZKxW6XYLBC-@&f|JZ%E;Prrm#$y!ZgW3_| z2bg_v;Pdkb*T@eqyaXQM_5Fj?o!c9`#aq|Apyq2d!Ju$TE2Y z(jhqNLO%fV_)uUL0C{jf^9sN`RpSeXBSap5&;5e&4ERJ~5FvPj&j3dr2R;wr4~z_6 zzW{iR4gwb-0E5U}Ao2K5@DLxxRKm7gw!T;ge!Qv$-0kBDfIJLkx z_$%1iFc1$N{sDOZ4TcAPFBp8ImR@_1@VA>6y~llIsgt14`B1)1DF5+ diff --git a/assets/resources/dolphin/L2_Hacking_pc_128x64/frame_1.bm b/assets/resources/dolphin/L2_Hacking_pc_128x64/frame_1.bm deleted file mode 100644 index ed11583f8f8149118f44b8da3aa791c579bffa72..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 545 zcmV++0^a=r038DVfII=<4*+-rz#ahb2Y@^Q;12+J0B`Jbzys%g6hLdw|K0cqe5e5T z@Be+^{ICCh;P->@0qgMt6&wa%nfN_Fa~y9I|3)bn`+@uipnT|{AI4rM3b1N{@`-$b?E?IV=Kli+p#0>Y;Cn$nLH!4p|Mnjkrz9R*@Or>O;vZ0X!aP9p z508)@SnzrIgY)-*gWNtpka>Q=>Q5m0&q3u#2Z_9g=S2sIIA9u%H@{W-{fEwt|BnO8 zQOom(#2$k2jRVv69}>a9A0rk5lw+0afPK945PDOA>D$$MzhLo|KnLSK2b4ppyMTJV zV)ZYWJcHxgClhpk_ptl!|MU0<`#=A?gU_)$fcmdt^WTHss!t#Ay)EGNt9n@mPat|k zCml#f;2vKJ3250rBO1N>HKun6_R8bk9J3=_fg1CN3p4_|}^4G7=@#fE+W jd6S9=SST1i2jn~unFHUzUl<4D{~rgExRvGrM(KmhGyew= diff --git a/assets/resources/dolphin/L2_Hacking_pc_128x64/frame_2.bm b/assets/resources/dolphin/L2_Hacking_pc_128x64/frame_2.bm deleted file mode 100644 index 41850505b54db86c1a371b051d9d4d1a5eee03da..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 548 zcmV+<0^9uo03ZVYfII=<4*+-rz#ahb2Y@^Q;12+J0B`Jbzys%g6hLdw|K0cqe5e5T z@Be+^{ICCh;P->@0qgMt6&wa%nfN_FVDqCCi~Yd<1JYwn{D0`6kh%paaHZL5PFSF948q4a5Hr#4N}<5C6Ua`VWmOBo8<$&`zW8hy%nv!0~`!f5G&L ze1Ycz2aE++H9-6W$_4okq?7y)EBp)|gZKxW6XYLArz9Ug|JZ%E;Prrm#6CVidqO-w z^AD~(9)4i@{z2vUfP>sVzp#3f$bPfXc~Sx5Zy@>6LE;V=2BVGd)qcNw5P4C*@!)w% zIeu{X{>SSY2dC^lC4+!IMl1y=$1B$W`+4Rd^rr*Ux2pAi!Q(1`55{~CD2G#b0QGvs z>R&K<2gkNfCg}g~VfWqt=kO2qfB$y}pJI0b^09*q9fII=<4*+-rz#ahb2Y@^Q;12+J0B`Jbzys%g6hLdw|K0cqe5e5T z@Be+^{ICCh;Q#o6ijF!s2lyU;uzAsdWyk-5NFGxhZxjDT2bAUk&Ih0LUhr?^{AM%& zc+6?PfO+5WFg^kBf8qGw&+%42{=M-4c!%2G5C@OYK9sDGJo@mE zc=|o{K>R}C!13q@(k1c-oEH%f2ax_`w+Vy;Kp#mW+(G3Mh*81i|NV#C2NLAw0p$S) zjK(R+2bK3FD9}BG9%1#tVEmF82t5Pi9${8@q z2*e&H@(-OCe-PjU%K`7ze!pPzqi@@zuiwBh9&q@B(4I6H2k;(Wu=xGpEO9&y{{sNv zSHa`!fPK945PARLL1B}F$G58We!=4^fKi*n0)R(>Kwd=sMpFN#Ee^9sN`RpSeXQ-Omt0T1*4V7x;<5f}m}XaK=BksNp&_&&aA;IDQrw8%>hrtZOCMWe~5Kj4f5%sw&91P}4qAq=a*2S$ke#lr;fe8!`HkEck! ua2PZrfe#7~;P5^Gd6S6{KljlC++%42{=M-4c!%2G5C@OYK9sDGJo@mE zc=|o{K>R}C!13q@(k1c-oEH%f2ax_`w+Vy;Kp#mW+(G3Mh*81i|NV#C2NLAw0p$S) zjK(R+2bK3FD9}BG9%1#tVEmF82t5Pi|pA|Cm+*;;$H7I-Cp{;0S-8`vu||@QA=5Lh%L(w20%t=fV5| z@qvrR;14f=JV<50dWnFs;zAb)Jbn}W1PAd|tJPtUFnFE{0S*rj|3Clyf8p>$Fo}u) z1rWH-HGU0LVlXyL1Ovx^fL=erp^x|@0P_!wa{&YVc1S}i@Ild{KQVB@JRdQr-{a}h vFPsJq2;f7)gZMlTfF5MxL=XLTK=&V%0zC)^&w#!n!JmW8TuSo*9{?WUP=735 diff --git a/assets/resources/dolphin/L2_Hacking_pc_128x64/meta.txt b/assets/resources/dolphin/L2_Hacking_pc_128x64/meta.txt deleted file mode 100644 index 8ad8d42a345..00000000000 --- a/assets/resources/dolphin/L2_Hacking_pc_128x64/meta.txt +++ /dev/null @@ -1,32 +0,0 @@ -Filetype: Flipper Animation -Version: 1 - -Width: 128 -Height: 64 -Passive frames: 3 -Active frames: 2 -Frames order: 0 1 2 3 4 -Active cycles: 4 -Frame rate: 2 -Duration: 3600 -Active cooldown: 7 - -Bubble slots: 1 - -Slot: 0 -X: 22 -Y: 25 -Text: Mess with\nthe best, -AlignH: Right -AlignV: Center -StartFrame: 4 -EndFrame: 7 - -Slot: 0 -X: 31 -Y: 25 -Text: die like\nthe rest. -AlignH: Right -AlignV: Center -StartFrame: 8 -EndFrame: 10 diff --git a/assets/resources/dolphin/L2_Soldering_128x64/frame_0.bm b/assets/resources/dolphin/L2_Soldering_128x64/frame_0.bm deleted file mode 100644 index 3fc3644065c7118acc02181bf59501908732b125..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 699 zcmV;s0z~}*0Jj4FfCfGf$p7FUupbCNU>qQN{0AfeDf|P74_<&hL4%kFv=0CeUw}U$ z;QxSl0sBA)zwdqz*j|DT1s}vdgXE6`-|ikC&OYPf7r=cdfcGbeJmc}D`*b3CAG{16 zU9fxV^-+412Js*Nhw|~C|NKYyIQ@ghI{@Hc+wuSXugYP76pcVikN|*`h5-zKLxb^u z2b2l{6bGRIV1M}j;2tm-1dtw{{rZrHD$M^G($N}S! z2Z?aK!1Dk7!RHWwhui=$_=f@>42~UJqSM`&-CydE|Od3pbU@dk(pe)D*Gq;Medn)&zO^^3>E140LmD=|NxemDa1 zh0p8|37|YdBfpq3igEt3^j~*Kuh<)aUfs$XY5Gs&7yaCY%odWRyQSg+4w=1yg|qkID<<)vNDd9{|YR;1AKy{v_k^4~~`r2*5zqBLet&|I!k9 z(O?iy_x^nVfW$YDaCp!G<3@l$;Lzy50s#>GK>R=Z_~>-x(YAXca{ze!ATtx0KqHa( h24lnIhl9ivFpznG2ski+AcM*d4`jHA#Oh?hfB?*oKqQN{0AfeDf|P74_<&hL4%kFv=0CeUw}U$ z;QxSl0sBA)zwdqz*j|DT1s}vdgXE6`-|ikC&OYPf7r=cdfcGbeJmc}D`*b3CAG{16 zU9fxV^-+412Js*Nhw|~C|NKYyIQ@ghI{@Hc+wuSXugYP76pcVikN|*`h5-zKLxb^u z2b2l{6bGRIV1M}j;2tm-1dtw{{rZrHD$M^G($N}S! z2Z?aK!1Dk7!RHWwhui=$_=f@>42~UJqS^Z)ja(BB|=SRLi({sY7sAR+tD#AATKgT`y;--Fga9uN%(9yqMT{(bo13(6lp zus|k&@dS?keQ@9k$K?;d1I8HuN0fSavJiPf|K0$3bOGa^2b5;V@`C?7FbR-6U^oc) zJhZt_loRpefN+812U3sVh9L9!ao{j0A3{I)v&_rlFB_B!G0cF#VT<#hm&_h8d|*6g z4)c%~&Sm0&XCQDI0DS@C1R1zUW!UwIhvH}g8ZG*9fd`*}Iw14FQXm>g1x5fbBKY@Y z`$z=?;8YNJ@F21IL410(edw6yVbP!G{}OTdhsR3*h+rUb>;wbh=l@7a=S6@*Ki~L! zh+-SaI6P&}?)rI1eAh55xbzj)zVo8)x(90|$@712H+20yvc%#pVbH7=Q?2 WAoBnez=R<5gM--MG4LS#6Ndmx6F-&! diff --git a/assets/resources/dolphin/L2_Soldering_128x64/frame_10.bm b/assets/resources/dolphin/L2_Soldering_128x64/frame_10.bm deleted file mode 100644 index f808193580805a0d405d7316ec33cc3708428222..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 699 zcmV;s0z~}*0Jj4FfCfGf$p7FUupbCNU>qQN{0AfeDf|P74_<&hL4%kFv=0CeUw}U$ z;QxSl0sBA)zwdqz*j|DT1s}vdgXE6`-|ikC&OYPf7r=cdfcGbeJmc}D`Fbr z;9&=kY#vwm;5@Z@sJ%)9c#r?V6@05Tu}|7a%==MeNb|NeYDLMlT4_&@L(06a;9 zz~}F(?t#L8)58DL5rOJGRqKzH{e7q3%Ty&^-wM;?FZLh`erCEF2UbFva=L z%ja^jU?BkUm^;l1Ao7_K1`-M$0DT=L|C_*Q5#ZTWL+>;PjN#%iDv&(90nrDXg5dz< zKx&Rb7m0j(vVEiiA>e>{AQeso7C$I2k5;d}M~Gx@@CWGU{}OTdhsR3*f?yzOk%4?X z|LF-l=%Athf9KE$3`2Pb2aNz8G;9O~+A($j#2CZj|KG<$r!I}N!4q%{L*W6KoXPqQN{0AfeDf|P74_<&hL4%kFv=0CeUw}U$ z;QxSl0sBA)zwdqz*j|DT1s}vdgXE6`-|ikC&OYPf7r=cdfcGbeJmc}D`*b3CAG{16 zU9fxV^-+412Js*Nhw|~C|NKYyIQ@ghI{@Hc+wuSXugYP76pcVikN|*`h5-zKLxb^u z2b2l{6bGRIV1M}j;2tm-1dtw{{rZrHD$M^G($N}S! z2Z?aK!1Dk7!RHWwhui=$_=f@>42~UJqS^Z)ja(BC0=z$^~(^Zxje?GWy2V?S!{{iC+fFsI0C=hwX|DF(ebOGa^2b5;V@`V08a0!q+U^oc) zJhZt_lq2EifN+812U3sVh9L5ScscMG6c3>v{8{E@@fVHC2{`OPU@*n`&&%g>fkCqr z7!1MQasv6xxKJI%4g)|Bpgcf>Hwg_hp0N=8O#nlR|4uL<^Y8~m9+@8ok^xbG3&_4b z**?%#J`0V=9y|yveo$W?tzUXXIoNb(`TxY66e01_k&15~z(5qE{*aT-ivWm!zwq}E z#5a&|c+dgkMs5rnc?*sM$MFO3|L^0W)4*VM&&SRN4qQN{0AfeDf|P74_<&hL4%kFv=0CeUw}U$ z;QxSl0sBA)zwdqz*j|DT1s}vdgXE6`-|ikC&OYPf7r=cdfcGbeJmc}D`*b3CAG{16 zU9fxV^-+412Js*Nhw|~C|NKYyIQ@ghI{@Hc+wuSXugYP76pcVikN|*`h5-zKLxb^u z2b2l{6bGRIV1M}j;2tm-1dtw{{rZrHD$M^G($N}S! z2Z?aK!1Dk7!RHWwhui=$_=f@>42~UJqS^Z)ja(BB|=SRLi({sY7sAR+tD#AATKgT`y;--Fga9)Ar89yqMT{(bo13(6lp zutFw)@dS?keQ@9k$K?;dSRs(e0z9MB#gK!_7yj@A%b*V&_&lRFKa>~w;f73s;{m`& z!R4jOe4w9?95aLu7&?@H1ThDn!;c+-K>88?#hzwg5qR97QI2GG0}NlB{JvoEgX1qS z8H2p!1@oDBpc%^?27n(xc!36P5*c^BVj=jN0EUZxoM1uc;0}m9@Dzy#k^xbG3&_4b z**?+%K=>5|9y|yveo$W?tzUX3IoNb(`TxY6ej)MFyGH>7k6<7l4?p@sPdY3D5&r+f z>;X~ULBZod2aOs527_avalm-~AbubH{B$~T7~4OeI2b&B5E+Tgpb^BV<}WZnIK%)$ X2?v+}rUW4eq#Pc{0gr(P;G8%BxBEH0 diff --git a/assets/resources/dolphin/L2_Soldering_128x64/frame_4.bm b/assets/resources/dolphin/L2_Soldering_128x64/frame_4.bm deleted file mode 100644 index a961f4c0a17681a1c74e2d84e79a148aad724c00..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 693 zcmV;m0!sY>0I>r9fCfGf$p7FUupbCNU>qQN{0AfeDf|P74_<&hL4%kFv=0CeUw}U$ z;QxSl0sBA)zwdqz*j|DT1s}vdgXE6`-|ikC&OYPf7r=cdfcGbeJmc}D`*b3CAG{16 zU9fxV^-+412Js*Nhw|~C|NKYyIQ@ghI{@Hc+wuSXugYP76pcVikN|*`h5-zKLxb^u z2b2l{6bGRIV1M}j;2tm-1dtw{{rZrHD$M^G($N}S! z2Z?aK!1Dk7!RHWwhui=$_=f@>42~UJqS2Y|N1+?Jbr-k z{z(Djpa0MQ+B-vhh2sFQJIl}f2Z%I4L-)VHpdSnfJZ8Rq_&sOy$OPhq4;)rve?I(h z1?4ZkSRoTYc!EcNKDclPWAcmtED%U!0UlA|K!eUF{P2Uzpbs4QJfk*0lqd1yf=q$q z0l-JW<)zAep&t)CBZLnaKY}0lA&5Mno(_Bl1q0|u{}y?fd`07Of=)XS7z{CfbMpDz zpipeUJZ28_kQdHn!hr4|a2f!80pbK1xJYT3_yzz!6F?B+uhWbOJp2LC2c|{=q<~al z0`f19c2BewkAeeo2af^^ACwozt5@EU4t5HgXpn2anqQN{0AfeDf|P74_<&hL4%kFv=0CeUw}U$ z;QxSl0sBA)zwdqz*j|DT1s}vdgXE6`-|ikC&OYPf7r=cdfcGbeJmc}D`*b3CAG{16 zU9fxV^-+412Js*Nhw|~C|NKYyIQ@ghI{@Hc+wuSXugYP76pcVikN|*`h5-zKLxb^u z2b2nd6bGRIV1M}j;2tm=1dtw{{rZrHP-5s;UI*I%px2mpVO z`b791w0H%49$)BuqF)3Dk01ZfhloW;KoQhW0pv^`2S0sRfWR<-@$>+M9*gn^j}!j` z^iSsi@oz8YPvr-l0s{YU&0HQZ77*Z5=pSegf*%P7uaEFQA1C}uIC(%k@BjTB;2u9f zc}FCG@z4L~|Lq;2zCiM@JIl}f2Z%I4L-(JE#{q!{jMvY<2dsWPAQ})naaoD{`|-dR zlsy+^C*#Ke;RD7Fr60izLFe$}z+g~5gn#j8nU};~Hz*WinE`;q7w11Om^@(k zzvVEiif$)HNAQeso7C$I2k5;d}6CK_F{T%<|PCpR%>0l8I1PxL!FNdG~At#*{ z0SNzp=gBM7f{zT>g@%TVyCo+IX5~G;> e`61x(1q>t}U;+*dAP6AxgM--MG4LS#6Ndn7H99^3 diff --git a/assets/resources/dolphin/L2_Soldering_128x64/frame_6.bm b/assets/resources/dolphin/L2_Soldering_128x64/frame_6.bm deleted file mode 100644 index 2f030833a27a95c21247e158074d5fe721ef4e51..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 712 zcmV;(0yq5u0K@|SfCfGf$p7FUupbCNU>qQN{0AfeDf|P74_<&hL4%kFv=0CeUw}U$ z;QxSl0sBA)zwdqz*j|DT1s}vdgXE6`-|ikC&OYPf50nsIf%KjO&KOTXdB@{R^9P(y zBlm%W%eD_Vz&y2jsJ%)9c#r?V=Qt0&<3IoSgUaAPdB^M?G1vzJ{@;)P?S4}afTU^& zRDc8oq$T0FLxb^u2b2oo6bGRIV1M}j;2tm;gpeMd{rZrHD$M^G($RH4mfILfu?gy9u?hhygLINLf0LS7S2zWe@LZBde$RZGk5IpJl586z} zqb~=pzgPqi0RJKMiGV$5@Cx`mztH(af(Q>DKmVT(5Q>n1Bj^X2FnApO^0En z0)um)Fc@O|=jHRcc%V4oc+4H=ATONA1UYCN2Y??yc!36P4;dajHZ>6Y%?$%2zg!?y zAbEHLq7OO+;sMBj)gS`$FOPOlv=a(^e2@yK0t+9M7ssnt-oQQqk-NYjqo4dq$KoFy zECLaLfvQFY@bmwqB=e%cAfNC2`T+rmZy@0DpaaH@0D-}w(S8I1A^3s#fA{gw>B*yP u_C)3Y@%TVyCo+IXBk&Bzhsh5Ih$vwo^8gTVVE{n~lpG$(aS@5s$%6o#P)TY4 diff --git a/assets/resources/dolphin/L2_Soldering_128x64/frame_7.bm b/assets/resources/dolphin/L2_Soldering_128x64/frame_7.bm deleted file mode 100644 index 4519819ea5c7e9d0127fbaf538f5aa276c881d4c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 732 zcmV<20weta0N4WmfCfGV7(hN_|A2W$0rLUygZ2T!2b6#WKLN=A3V#6N1J|GrP+;Z( z<=H`&OaarjzAtI!uJEq|Mv%+LINLf0LS7S z2zWe@LZBde$RZGk5IpJl586!O83}kjb^5>{fCu>xq)Y+pM}Sws<^G4tCGbFa@&Ekz zc!X4h0Ubo}9z?<5bNAJCP~idN=m7{l7vv8fC;kWMpUwf|-e1h0$`3jO1^(ZfxIADi zA;725KF}WoJ`xXKAK-pIPxzE@@_>2Y|N1+?Jbsz*c*i7w@z4L~|Lq;2zFzQn*d68P z{sY7sAR+tB;p&mVgT`y;--FgK9}o=)9yqMT{(bo13(6Nius|k&@dS?keQ@9t$K?aR zgT@&EN0fSavJiPe|K0$3bOGa^2b5;V@`C?7FbR-6U^oc)JhZt_loRpefN+812U3sV zh9L5RPB_pQ6c3>v{8{E@@fVHC1qSCpU@*n`&&%g>@j!9F@t8Z#KwmkK2y)Ok4*))Z z@d6Co9x^<5Y-%C*ni>X4ez-uYK=SYhL>_bt!~>B5sz3$gUmonAXeJc+`5+Ze1QtIi zFOOEQy?}fJBX@v5M?d(JkHkJYSOg;h15}I);phKIN#{j?K|kO5^a2AA-a*0RKnIN) z0Rw|WqWlO1L-7Of|L^0W)00No?1{_)Pl0{-8R|LuNL3;?8R z0#twm1f(zsWC9!?i}*aCPzazs2m=HE$M*p7fWRbx^!M-7gg+br*l7Utm+#?!{1`lF z0P(~J*d9N>oPIzL9DqDah3*HJ|LzYsgakg|0guEu5b${*g+M^{kVGL6AbHd9AGDdm zG7|85>-B&^01xsXNSFiGj{vWO%l!|OOW=U<G{Jc)zA=kKcOp~3^l z&;k&8FUTG|Py7$jKb!-_yuX=0lpb^l3;n+}aCpF2LxE4AeV{%Fd?X&eKfwHapYbT+ zMw89FhaaKmVWqw04I13&sIpcbA{|4-ja8hwp!ZDn|kj8Lyvy4_W;1 zU^F0j;e*`fHloP?vfWV-A2>;^GGcSm|Zcs_b zVgmt&FV22nJCq6ym3igEt8cGaefnh<)aOA;n*=5Gs&7 zyaCY%rbYqCfYl%Z@-L5ePqc22f&=7$RX7k>{Gh%)TE6s%cX$KzbN`7rC`03=BNQD{ zFeNDeq$KmAz#<>-{Q3a_h;Ja^@t_07jNBMBaxcINkKza6|KG<$r+~oipNO2m9zO^S n#O6>4=lTJd`1v8=@dXSd9$*3v3?K*~@`HofE-~;R{1b-&_Jm6b diff --git a/assets/resources/dolphin/L2_Soldering_128x64/frame_9.bm b/assets/resources/dolphin/L2_Soldering_128x64/frame_9.bm deleted file mode 100644 index 1339c607e63f0d52de7d04b4076e94a820e1a774..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 698 zcmV;r0!94+0JZ}EfCfGf$p7FUupbCNU>qQN{0AfeDf|P74_<&hL4%kFv=0CeUw}U$ z;QxSl0sBA)zwdqz*j|DT1s}vdgXE6`-|ikC&OYPf7r=cdfcGbeJmc}D`Fbr z;9&=kY#vwm;5@Z@sJ%)9c#r?V6@05Tu}|7a%==MeNb|NeYDLMlT4_&@L(06a;9 zz~}F(?t#L8)58D(8>|NlpL2anUflp_fAGynPj`$uSRm%Pj-aR-l|_zw_hfQRq*hrm7pVdzbK`|x`8 z*MguBc;d4Y`S;_1FDzXBgdp++j{bdcG>ik~2Tl#*1dllM@bWl001;PbV2bX|4AoGx1ARLGd zQHUb(FOPOlw17lB5Dz2*slbBA0l5{1PxL!FNdG~ zAt#*_6hH6$`T+rmZy@0DpaaH@fPlfE5q1E?7{lTJ-^W9zE{(In6L1Ve;Q^SO$^jgQ gz%w2nAP`~zp;M3n1A@UIc|pPKml*gEehI^X01SRc?f?J) diff --git a/assets/resources/dolphin/L2_Soldering_128x64/meta.txt b/assets/resources/dolphin/L2_Soldering_128x64/meta.txt deleted file mode 100644 index b705bf62361..00000000000 --- a/assets/resources/dolphin/L2_Soldering_128x64/meta.txt +++ /dev/null @@ -1,23 +0,0 @@ -Filetype: Flipper Animation -Version: 1 - -Width: 128 -Height: 64 -Passive frames: 9 -Active frames: 5 -Frames order: 0 1 2 3 4 5 6 7 8 9 10 9 10 9 -Active cycles: 1 -Frame rate: 2 -Duration: 3600 -Active cooldown: 7 - -Bubble slots: 1 - -Slot: 0 -X: 71 -Y: 28 -Text: I am busy rn -AlignH: Left -AlignV: Center -StartFrame: 10 -EndFrame: 13 diff --git a/assets/resources/dolphin/L3_Furippa3_128x64/frame_0.bm b/assets/resources/dolphin/L3_Furippa3_128x64/frame_0.bm deleted file mode 100644 index 07a63d6424edfe232cc55faf2756cca9485384c3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 398 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 K0l^!}zz-0n&QtRM diff --git a/assets/resources/dolphin/L3_Furippa3_128x64/frame_12.bm b/assets/resources/dolphin/L3_Furippa3_128x64/frame_12.bm deleted file mode 100644 index 392905a559e718552e9db176de5b7cc96aee167a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 541 zcmV+&0^2HpaDrD0~`qk(=2CEZ z0f)!)C5Sy^VDfDd6n=P6c!CNF4@>zJeV5sIJm5g`(0}*gLcxQ{tS}II8iWXe@eB|E zXaM&oh#-OHDi9%W;4^FixNpGtzyG;6_}9XI3LFReE(ABI%&-txX8^(=_6Q6zF#`F| z%pwXHSUm6OQH)?em-v`H|E9r!(1@c42fM^ULUj;%Vgc))|K1Fy>Kb?a9=!ZL{EAT6 z1w0;~ugGj@5C#axa1?NQe1YPw7#HjRdiZJo@l^SIcmMtl`T!3dm;X)wiUZI8-@skq zAn}sVy5IRje7^$Wz<=Pt@uKiS==&f4;{o%GgT|Nt_kZdD@qLIqwsLTI)$jj*{(v4o zPrv8!!7vCsb@%_@{(}R60SDkA5P1JeqyPQ^#q2GC2cG?kAOw2~a6#jN_us+G|A+7a f<$u2eTmSC}k$89X5PN_N?%5stv_aF0B#1shQS}MQ diff --git a/assets/resources/dolphin/L3_Furippa3_128x64/frame_13.bm b/assets/resources/dolphin/L3_Furippa3_128x64/frame_13.bm deleted file mode 100644 index aa5353e988d02785f45759a024931abe7d580086..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 584 zcmV-O0=NAE07L@+fII=<4*+-rz#ahb2Y@^IANT|F;Rk?x!S}#`z&(Kbz(C*~0DZs# zKZbz<@kP z-_ZGk-W5OxmVe$YUN}5vRseX=FaHn!_#S((c=Kc+^8fh5iJ!HD#(O>ojswBL|KI=9 zyXAl$Ey0L`%fG>Z-}v0Uoqw17zfWUvCftXMrUwwc* zFdOIv=nzr}54)K^iv!95wSa!G^7S~tJCHd1P#$^^c)jswmy;2(?+ml*DY%6Tpaje?1R4~z?+C+R@JB71OJ%^j$i~g#Q}fdP!0e4@$U+bI{^s# z3(Nlv;0TA07lYt1>XG_?f57p%{4ekc#4b7D#lRpxNFUrDAR+mp#tHC`{fFbU28aM| z{|{g!U}Yi1z%rjedF|ox@OZ&E5DPI#e_na`JbB{LzW^w(J#=94pcIrmEe`+y diff --git a/assets/resources/dolphin/L3_Furippa3_128x64/frame_14.bm b/assets/resources/dolphin/L3_Furippa3_128x64/frame_14.bm deleted file mode 100644 index 837c6c71defd8be280776e7881817fc68276b5d0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 610 zcmV-o0-gN<0A2$B|KVUk{Q*P)Fi?2?0rvt2kDwoLFnIg{_W=j^KOjHgdBMT)0R98a zDj@jq1I#KQ_}~M|;Q;Id;{Xp%fIlVx`hWxc9=`B>kL(_GhKz#+o><3>gVG5B{Tt>8 z4;U5;!}CwXJVamY_&z)lfe}FLAMe0Dt_H!Nasoqr`-9r~&_@VJU>l>0Krj6W95O)w zgJlpe;FN#haR>y*&>wsRUrhppDF-nZ{sYsG|A@(;VDX@2Js+@oPGA%6%nc+tE0kSi zuxJH>Lj(tm3qcDYD|B!X2tYh=AoBP#)&UWmL9h@2c-lSS_KA8RbO;895vb?`+9^1J zKqL|g{UbmRX+$ywAO;Ttt3U_0LJkiYg@OTE2b}yKQ3wz`aF!;BgU_B1DFg(5A@ZVV zdEoJzgVn$w5#o2S;PHin%RrqGp+G;L5P3mC<-mYJ58@9VCwyLBV0pmdiSdjD2SN@Q z8NBEa$N2-wgZ=&g0qGDH4O%!v8W;Zm|NDc@SRg@@1ImB)|6{@DKq!m?&N3Yo|NZ%| zgUlq?4N0o0xY&yEj|qzoQt^FED>06iEa;GRJCuV4t{^!eXG#1C2(0)+vGdoW=5 z-AFJnC>}#T4`yhz^{KA$Q(_^4>({vevs<0996^* wNHeH?5Gg=DU|{j6KxP1O`UB1b0N_Ay5Dfv)f$A(Dg8+f^cz{o!766F@+?Nvri2wiq diff --git a/assets/resources/dolphin/L3_Furippa3_128x64/frame_15.bm b/assets/resources/dolphin/L3_Furippa3_128x64/frame_15.bm deleted file mode 100644 index 9ff56a5b6bd83d39c6585dafb084113387bf309d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 741 zcmV2edpMFaQvP#()oKH^LrM60rl^I503^{{iLsHy|)^ z0P@TO$c~T*6avV`F91BQ2NQrnAQ7N>U;*j-2b3ZK1#_7gbPDB+U69Vy z2d})|M3D~)2*KxjACw9NtPlv3%2fI<=fqYpC`JMNAOZbLLFM@O69xpR5TL;;kZ&wM zv48$|iGu=y7zjQv7}Uef!T-;u1c9OhgeEXZ92#R14<0x?VUR4PTpn0R2t2>t^7yL9kti^WY(e8% z2af;$0dNeKDo`E-93GeRaKs{72$D!_6M%T`oC3cYgo1+sguF|DVj0x`W)B_(0t2Li zLmEV1AFl)B{S1u`0c1CcR7V2<{{Q|V@qlR{6sUUupb^dk^nd z`{45EK&FvLi4;m6ARtKSAiN120pwi|{4+ccIs`5tFCG9~z98}UgU&C2CJn$IUIF-r z&jD@$i~=whF90}!d04o9jcmu#50PqKZJOMC(e@*~H;Gcu)Kp-FI{sIrE!~!3HfrIK{fDVi> zA2bOE00<&6fJcGxAQBVFfe%V z2?Po(1P}^v0C>zG2xA9~2ufh`6Nn_h0*o^N!C(S$6qr3?0P+rtY{w!sFnNW<3O&Gk zC9wT8f5ItaOfd_~*OhgYk2s}k& z0iYO!9@H2I{!R!IaNrw+4_FxNU{(lyfXFOW;C>`v@t9B|0P2CSOq13hXmkeVx$Jlghm1hk|1&b^56rE9-u*b zk`z`43?iih97Y&eJm674dC)Kbk@t^KHL{N x0tncHk_Vv>zzCfd@UEO1VEw>AQ=&RRdj|v`*MLBS&H_(>K!T0}e_#N^0q7Yl$LIh6 diff --git a/assets/resources/dolphin/L3_Furippa3_128x64/frame_17.bm b/assets/resources/dolphin/L3_Furippa3_128x64/frame_17.bm deleted file mode 100644 index 80cb06fd3cbd753e5bf6f00f85a67bee5a0590ad..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 492 zcmV)+z)6lKcGPUz(7I#PwD_arU3pz{4gKTMmK;S{sc4Q z5e5Umrk^9=vnU*SF-SfRq7N02#{ta&$w0Dd;runk9{8*#h#oL? zpn`|t4+otp@uYxyFYqOpfCv0UAb8RN;~c<20p|dpgVzBAfI@5F^SA(p1TP>la);yq zKzQLakl--Rh3F4FUyEM>0}MDG_)L^!Tu=xYJ>Zd5NxcMq_(?FdlcdpjPc#Z}#e8CO zMO1)BJ^*vTP$E}QF^t4QAoY-1fW%BFFu7zj3ZaNaf^Z;vQUGEN5a5hRZjc-Rw7dYp z@GDp(AySW^3;=~uQ?zglu?Pg@5T)Z>8J(~N;}N1AS>nV%!5)YJPa`p_olv4@E&zNWkwRctC_R7$4g-w>^KMRlm@CSfA05|Yd-vDL6 zrh}m*mk*cyGWGx=$00Bbd_3^slEc9uABn*Va1<7}%{%~tpfJ~gKoN{-w5JrATcm{vup{D1Q;0w-&W_W-d nPOl>XE1giJZ<8y6l#+*4fXD!a1qaL2M1)X4c|-t0*bo4Ks86b< diff --git a/assets/resources/dolphin/L3_Furippa3_128x64/frame_2.bm b/assets/resources/dolphin/L3_Furippa3_128x64/frame_2.bm deleted file mode 100644 index c014858502e8d153d61a39604e4128198513b5df..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 463 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 diff --git a/assets/resources/dolphin/L3_Furippa3_128x64/frame_6.bm b/assets/resources/dolphin/L3_Furippa3_128x64/frame_6.bm deleted file mode 100644 index f46aabadf6b1ae21e3d61fe50b3691c7d6c063cd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 521 zcmV+k0`~m@00ja7cmu#50PqKZJOSVj0C)qy9suwMfCl~!-UT!tw-1;80uQ+4CIOF! zfyz7&Y&;SH_?!XCzz$pm1_c0`cmW1AUIhRSF`!xn!~k{xc|%|jFhIeE;s7}SJa7s*c2Y|^a2<$=eApaMJ1^j@| z!R>*_Kw@C=fpCHE0HnYqkPIjP=^qEYn)yh8Q6L}Kcrhml0>RW^6G#X858%Jqpc#ot zXmU_2n)tuaFnUfwJ=1rhkb4GHFf2>f6M#O8p7;QxRG9?>hP z5UwAO{(K(N3s4ZJgaOBc+JRIt3;{Yn3>ZDB03nQU6Qg)(hyg(5U{;R;55Nb=@PF;$ zpby**&oKZjYfqw_UW%0@E diff --git a/assets/resources/dolphin/L3_Furippa3_128x64/frame_7.bm b/assets/resources/dolphin/L3_Furippa3_128x64/frame_7.bm deleted file mode 100644 index 07a63d6424edfe232cc55faf2756cca9485384c3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 398 zcmV;90df8T0Ez(scmu#50PqKZJOSVj0C)qy9suwMfCl~!-UT!tw-1;80uQ+4CIOF! zgX<3jfPNA+B6Pzk4iom2tn#;^n#umj2)0D*(zKo1-Q8UO(h1Iz*AKn{saKo1xP zj)D&Wh99IJJP5=B5D$o;JZusHD1+iHh=9~65D0)iBuBB7U;*_bz<=hjC=9?K6dnWu zJph1^L?03l@i1UO`M~5LF))3=DKH5lctQ0hzET0@{85TQ@O2P)tcExaXbwsRlUEPn zt|0ftVKhMTgRKM}^sB~_0qDQLmSPWjKs;lZ2p~T25I6)Tz7J>+ysiLY@6g zO6mkG0L(-p09gg72t>kz1KNR9F$l2MLI7Go2tlG85s3g704*;7Fnj>!2c7~xfHC`l smN*Z2$Qet{2y6j)(Cp6;1F6;IUcmu#50PqKZJOSVj0C)qy9suwMfCmBpkAw5U0GI>vco7%~KIHHT z1UwyF2?#uA0Q$H{0pdas1jYg55Woy789)b&0yYnrFg&mmpnSl=;|QQxQ?mgFj3+_D zH%LT7<$*}RbV22y8i+mb03m?St`HayJ>`HQ!0=-bC@?+bz#*YeA@E2bJ>)?_LF)kn zj#5D@pd-aX!1M3}$K?YDxMXsX30DFi3Q$SHKg+Wz(67Z8-P9y0uM6tm<%3p;CwCweq`t<5GWL?94Aeas{f2wnmn4S)O| z0YKn1MNcxw0z(|^5sxL?E_DWKtbc>!Jr~=4-}B)a6tGph%)1~B0Iz@!4=gh~K|3LmQUL&XqM4gg-Ku?-MBWDtu45FU!Lnhgj>GYI4e z{Gs!Z0Y`xY2dQ*Ny* dI7Rs27DoVFD1bfW41$AzguwvwjseIfKm^${pj`j} diff --git a/assets/resources/dolphin/L3_Furippa3_128x64/meta.txt b/assets/resources/dolphin/L3_Furippa3_128x64/meta.txt deleted file mode 100644 index c21027e49cb..00000000000 --- a/assets/resources/dolphin/L3_Furippa3_128x64/meta.txt +++ /dev/null @@ -1,14 +0,0 @@ -Filetype: Flipper Animation -Version: 1 - -Width: 128 -Height: 64 -Passive frames: 8 -Active frames: 11 -Frames order: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 -Active cycles: 1 -Frame rate: 2 -Duration: 3600 -Active cooldown: 7 - -Bubble slots: 0 diff --git a/assets/resources/dolphin/L3_Hijack_radio_128x64/frame_0.bm b/assets/resources/dolphin/L3_Hijack_radio_128x64/frame_0.bm deleted file mode 100644 index cf2120ff470ed140a0d47f8adcfedd64a8d20cd8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 524 zcmV+n0`vU=00;vAfII=<4*+-rz#ahb2Y@^Q;12+J1Hc^RUH~ZK7yu!Ef~mg&l>gs< z0+a|ongB_2=NJzFKIZ&r9DHANybK@1{0G+80S$qCz@u&L?SMnp2bF384}p6D^MCHa z;m|ICK6moyY2a$|_{Be1B z_1@fzk3(N>7S?zlxT`9#A0ogVTq9g@ew41d(XP=VSg~;Hj|y3A{t&2LqOX zBrqU(%pwvm*nHFC4;V-A;4y&t56?soFT@N!Fvf^Kd8K*sdIr~590(U zs62`y4-ou9;1~gjhQ)p~0s7tW2a<#X=NDfII=<4*+-rz#ahb2Y@^Q;12+J1Hc^RUH~ZK7yu!Ef~mg&l>gs< z0+a|ongB_2=NJzFKIZ&r9DHANybK@1{0G+80S$qCz@u&L?SMnp2bF384}p6D^MCHa z;m|ICK6moydAvUe z4CbAH(1Y5=_NBlv@yzD~LFxymuRHCPz&0?<#{z&4SzY_JCBSe2<6sB2t&TES06g#j z_w~xK7yu)fFdrfL=z-<Ggk>LU;sg2?ePEr diff --git a/assets/resources/dolphin/L3_Hijack_radio_128x64/frame_10.bm b/assets/resources/dolphin/L3_Hijack_radio_128x64/frame_10.bm deleted file mode 100644 index 1354c78f2ce9d20f8e2b08bad75be024c8b4018e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 550 zcmV+>0@?im03rhafIt8H0hjbC;12+J1Hc{t@CSfA0pJe+cmu!?uZ39z`kHnw)XbGA?pLmwE%~} zy@2_@_h9ho7eF67`E+st0xO6;`d`8WiKJjOAoj6;sc;N`6C=RTdV%Td&i`d_4UCf|z@P)xSAP9Ta2yHddcY5D zU;pbRfN%rjV88?4*DAnZ2jegY$6OvT0{Fay9`gBW6i?!y5PR3_um}%|P7f3M=-eY# z4~*V0FA#gz^x&a{#3T>}`sy9SPT;0k$7k=NpgZVC3@91cn6=9t;9u5Rrbv=ARIF!Y9FT7>Rtoa!<%S{|EAhPy^%- zU-Ud~P-+4FLFENYoC^Sd7$H4DLu*4rY^Z^6M{0Si0fBpab3Vf�Q4{f5&!@I diff --git a/assets/resources/dolphin/L3_Hijack_radio_128x64/frame_11.bm b/assets/resources/dolphin/L3_Hijack_radio_128x64/frame_11.bm deleted file mode 100644 index c15289b5ec4e1bf2622a9d4e63dabf35ff48e539..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 572 zcmV-C0>k|Q05}5wfII=<4*+-rz#ahb2Y@^Q;12+J1Hc^RUH~ZK7yu!Ef~mg&l>gs< z0+a|ongB_2=NJzFKIZ&r9DHANybK@1{0G+80S$qCz@u&L?SMnp2bF384}p6D^MCHa z;m|ICK6moyM;G#;+THpei>dh@^8Tmxey$#5tZuD#Z~-@3~0->EJGfjrMx z0ps8L-+TXkU;pbRfN%rjV89E+9y5$^JbiMk1^|9D0C@Gmn5fFIDpmcnf zp7Y|Lf8wRFCXWIH9}s$Q|M0MQ#tGvDk!Z!|WBz~Osj-6yNIo!fd1wMd0*DU=0WgS2 zzhU!Fh&*8v;JA!LzF#>fgs< z0+a|ongB_2=NJzFKIZ&r9DHANybK@1{0G+80S$qCz@u&L?SMnp2bF384}p6D^MCHa z;m|ICK6moyM;G#;ROdh@^8Tmxey$#5tD^_AbhQd|cDd7iKX+gJbk$zU7+ z_}DN2_w~xK7yDIo{EcjsC^iG+9t zG5_(!<>TT1B|@gs< z0+a|ongB_2=NJzFKIZ&r9DHANybK@1{0G+80S$qCz@u&L?SMnp2bF384}p6D^MCHa z;m|ICK6moyM;G#;+k_jh%>`t!fpTmxey$#5tZuYH%D-P_9V->EJGfjrMx z0poe!-}is*U;pbRfN%rjV8D8}`g!-iRu3Oss{w!?jKCf~ukD||x(AP39xwv#QwTB2-kz-2Z#&A9`*hBC}S|m1Oa}! zhj8DxG5-V(!2oZpv;9-P&)-ErfIrv4{kCslF9AsiJ>$PR(fmQ=V;}zDAj zh=ayO`{U>Qm!A~-{}nBWM0gM&`2){~|Am9Z`ZNe2e1+v>{(s=9v4%hm;vX0}9JB!; zfkZG6F$lyYP(C7k0pke21;#KRDn5EoNznt#@P8;}0!8BoxB4D8C^Z27qVj^J&IN!U zi9LbjVGwwS;uf9}h`|@-K*>)zAo9E54gs< z0+a|ongB_2=NJzFKIZ&r9DHANybK@1{0G+80S$qCz@u&L?SMnp2bF384}p6D^MCHa z;m|ICK6moydAvUe z3?`9)(1Y5={-wY%@JxpTLFxymuRHCPz&0TApaa%de*H;s8~}LO0qv_}jFtcoJODkj z{<&5I01p{}J@5Tsc)$zd@)3K><)~3Vihx1yTdu$$De(u1{d8^-uLs5t5EqC&>HDxy z#$l2O0{wLk;lFTW{s`lo%LzKVeWf3Ji4Y~H|L0+JAW$9{FA_=CvCKmItp zynH?6s8%5JkwEDADJ3VxKL5o_Vh<<~d_n2M|H8rNK!QlLV)L>8Kk!u8fCSzl@q>ZO zKoS@bJmwJz7wkT1@du0}_;47&e23?v2bbao9~fl-J|Oo0L&fC=pdZv;P*l0Vum|yi z6Vx6>5eJBVA#e-;#6x1g8UX!n_yfs80rQUm34lT8z!$CjJ|q-(p*+9)`S=`IVh^MN Q1IPRcAXxy-TqLo801PJV%>V!Z diff --git a/assets/resources/dolphin/L3_Hijack_radio_128x64/frame_3.bm b/assets/resources/dolphin/L3_Hijack_radio_128x64/frame_3.bm deleted file mode 100644 index dc0fb9b79cc78fac3cda74455bf92526b8504378..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 529 zcmV+s0`C0*01X2FfII=<4*+-rz#ahb2Y@^Q;12+J1Hc^RUH~ZK7yu!Ef~mg&l>gs< z0+a|ongB_2=NJzFKIZ&r9DHANybK@1{0G+80S$qCz@u&L?SMnp2bF384}p6D^MCHa z;m|ICK6moyx?2gVN&7l=LU z`|wc4VUh>}{dErEzi?yz2pxg|-&tq+r+uHkih%%suY>z+-oRc0k`Q~xes!bxgUH4| z{y4n6d_Ux zgMrIH5*QFX<`D@O>^^Dn2aF^5a2UXRhv%XPm*NH=7-axHAol-5#pMQ|AJkq@RJp*g z2l0Xv)E-3<2Z(+la0~#%Lt?)g0R3+G1Ia=G^N#@ufI;WL7p?q0Boud{Jiq(-_#9Yb T52OJD$NUK(SpdykB(Z=1M>Oxj diff --git a/assets/resources/dolphin/L3_Hijack_radio_128x64/frame_4.bm b/assets/resources/dolphin/L3_Hijack_radio_128x64/frame_4.bm deleted file mode 100644 index 025477a7a943d7eac253ddddafe16f07fea0a0a4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 571 zcmV-B0>u3R05<~vfII=<4*+-rz#ahb2Y@^Q;12+J1Hc^RUH~ZK7yu!Ef~mg&l>gs< z0+a|ongB_2=NJzFKIZ&r9DHANybK@1{0G+80S$qCz@u&L?SMnp2bF384}p6D^MCHa z;m|ICK6moy#QwTB2-kz-2Z#&A9`*hBC}S|m1Oa}!hj8Dx zG5-V(!2oZpv;9-P&)-ErfIrv4{kCslF9AsiJ>$PR(fmQ=V;}zDAjdC0$f zeE*X3;-7!wrLhN;2tGjb;s4=a^PoWk9JtssDFT@N!Fv JGgk>LU;r>`30MFC diff --git a/assets/resources/dolphin/L3_Hijack_radio_128x64/frame_5.bm b/assets/resources/dolphin/L3_Hijack_radio_128x64/frame_5.bm deleted file mode 100644 index 89a4cd6acf85afc12c608940bc379d5b1464e61c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 574 zcmV-E0>S+O06GHyfII=<4*+-rz#ahb2Y@^Q;12+J1Hc^RUH~ZK7yu!Ef~mg&l>gs< z0+a|ongB_2=NJzFKIZ&r9DHANybK@1{0G+80S$qCz@u&L?SMnp2bF384}p6D^MCHa z;m|ICK6moyEJGfDao0 zJbV7%r~kLB|NUgJ0D0goL&5v*|KRcU%CHy#_{;(0wcGc8b?|ug!Q%igi^zRn|F`}> z_Xmu=T7?t%s0Y>D?$P#p(0JGDum}o#LE?X18-#1Y@q@$#;tzWM{1h>mWP$*{T|>BU z+!+6Y2Vj6V)>;0k-)HZlpg@lx1>$^;)8I6U}&_*gvX5J?w|UUom{{tBBA0Gq@?gHR9Z4=5^J;8+P4?4H2#D2O~m z@e6=p1|tMtl>;T`r09X;cfcM>5D%Pq2uuPGJ^;OM;qf4&y$R+2-_O9}!w`KS2p&J+ MNdm|QYT+e}07|gs< z0+a|ongB_2=NJzFKIZ&r9DHANybK@1{0G+80S$qCz@u&L?SMnp2bF384}p6D^MCHa z;m|ICK6moygT??~7m$PAUoAq3{8R!Ddi{0*0Z)iLPwS&_jd(sVc!0b??_b}7 zhBFM1Ko{$%cMbc4AMilz5C;0oKh-TT1B|@ZO zKoS@bJmwJz6c31>KzPDGhXITS$bNbtd43>a@rF<&UNC!qq2ls`P!H-aC@NgwSONHx z*d9d@2Z(+la0~#%V2kpg57zI1Jd_|GIPegd1Ri_=df&t1K}UKM%m2ThfyIU(`alpo Of54IjkPOwrOBeu5#pKBV diff --git a/assets/resources/dolphin/L3_Hijack_radio_128x64/frame_7.bm b/assets/resources/dolphin/L3_Hijack_radio_128x64/frame_7.bm deleted file mode 100644 index fb6d9bd292c739951e00a03612b86833b5222191..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 655 zcmV;A0&x8S0E+_ufIt852lspgKhS;iz(e>4-y8%#zgs<0+a|ong+SfFdhJW`Tz5N zG!8y5l&I41Fni(o&u{_Gwd0_X$Y z;r`#tqmT#@TtV!i9sF8g4;lbH%EMoU*a72!{{z^>Ao1V>%lHpousn6}Tm%I;Keha8 z!Gqy}z&`8v2b5|V0QG~`KqeZz1_1_$1IIKkPhxmM<>HaRIxwL6N<3fuygmU@qTo6p zfdCwjJl-FK2XjuqXhH2_|5D%>|E6Dop!IgYySuI5*PZ^#;2RlcYk@$ud+faK?%r2^ z{Yh{f38q@W4;#+@|GWQc|NmJm1AreJ1_RZ<)6c#7xnKX+D!^a|<1h!0tNUl~?xlau zxIAD5@p%ucy{-Q-23Po(%TS_!6#)9KcYfWnpR4>=>#zt4d_m%WT^w(JUJr~OATJPm zcdN6rHKZgN~2g{H1l72zw_<@JU89)z^J%7;gc|oWL^#_y{ zE^sUX{9uIj2a!ZU;vZQboc=Hj0K`LLzZwR5d3lj=zxR9rof1`a$*CIJVZ0A6_y p{rn^(6nCV8VdMYr=iqV&z2mSC6afRr{4F2=@9*sa->Za{FaVQOM?U}n diff --git a/assets/resources/dolphin/L3_Hijack_radio_128x64/frame_8.bm b/assets/resources/dolphin/L3_Hijack_radio_128x64/frame_8.bm deleted file mode 100644 index a0377f635fe25d6ae38494e2e28ec168af6f1f3d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 645 zcmV;00($)c0D%Jk|NZ~_|M&_Z3G;);_z%CBJbnQF`UCIo0uXceiGk!D-3L^J| z&NuYD3?IUn5PMnjAN{TZ8v=R)3!o2ykjtZx z2oan?^A?x`#()o+{4T%`90&LxTmX500q^tI!Eg{1;QrU~uLci>2LSu4*dQSCjYA+F zuzJV@!&iX7AkhGL=7s6({)5lOBY<>aLG+Y(zxjB40;5O3bU^|DIUaleOZY%_G`t3c z9@a1QE&-43Wq2A7R`StyE@tgPm*Gn$0|I`PJ1Jg=*53Be4-PiB> zxcs78g&U0(KsK)bH`m+QhsJ?LfIw5?4-@+6|M(h^d|>ebc!S=rxCIPm86bc!*HG>o z_Xa=Uf!H7o^_G9CciH>ss1OJG_&>JI>;>Q{AqTv7=UP9AJd9)i1Ro%I@c;0zc%Mdr1P_qBtbfn^6*e%)0lY)w2P2k)zyEwE%~}y@2@7+&i#%bPJ#liT>hU9DqQI;tz5V#iju9pabT=3$O#n z0saTq03Kh!d~4vi2nuk2XZY6x2g3t^ebw==1dM7K0QG~`KqeZz1_1_$1IIKkPiOQV zekmLSqY4kCqs9Nr!@+|^z;rScHu4_9LwW0+&T zdEe}=0kM{5xD*RlUh7@&-DP+0)RzIko@cB8@$da_z5l+i|Milhm&hJ9dh7xMpAdMT*GK=s z(}Uv&hzrCX^l|X`zcBd(0e-rNaNIaC_yj`10B@|b{Zqcr-$g)xKi9$iwr^lB0Z9lw zuABcR@;tv=__%1Pk`El}cen>q32l9qc zBw8?gf1%@YgHR9Z4=5^J;8+P4?4G{xc^E_jf@01)tm2VkEkh=8RH9=ZSk diff --git a/assets/resources/dolphin/L3_Hijack_radio_128x64/meta.txt b/assets/resources/dolphin/L3_Hijack_radio_128x64/meta.txt deleted file mode 100644 index 1d415b4b85e..00000000000 --- a/assets/resources/dolphin/L3_Hijack_radio_128x64/meta.txt +++ /dev/null @@ -1,14 +0,0 @@ -Filetype: Flipper Animation -Version: 1 - -Width: 128 -Height: 64 -Passive frames: 8 -Active frames: 8 -Frames order: 0 1 2 3 4 5 4 6 7 8 9 10 11 12 11 13 -Active cycles: 1 -Frame rate: 2 -Duration: 3600 -Active cooldown: 7 - -Bubble slots: 0 diff --git a/assets/resources/dolphin/L3_Lab_research_128x54/frame_0.bm b/assets/resources/dolphin/L3_Lab_research_128x54/frame_0.bm deleted file mode 100644 index db283e81fbb46514ec3a63dedb87bba98daeb086..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 611 zcmV-p0-XH;0AB(C9v}oX06F|W|M~y;20#3`JpT|Kgajs8?>{Feln0y|4Y0(-C|H1b ze#7Yjgbl!hQGMmP^FfkZ{(4=*JIk|}|Q#BCT6`h2W`agbO8 zUe7-Od}#U4@H~1P4h3P}!192K27NKCgT@U8C!TZU4=M~g;13_bAXDh;6iC5=_{D9tfb60|+AxcxrGc1{wqGfrH#I zFh$UV$NuRUAk1~a@(CBh2dw^-#2!@PD1**04;T;f05=#8I0hgCg9FQe4>)8L9C!=} z{9rZ!dP0r`0JvZvKI!m1W`WXx!-Zq+;mlAa0)}!0#r}Z!AaVFSz#fwWuz&~_F!*F( z&`?Nc0vHcJ<_;Ga2dn}n4>*6q3viYd#bPmN1h5=j4j=vtFyjpbZXoffD~B~v1BMS0!Vg3qc>D(j1F-*q3?7~ge*j`T1L%MXMh`v)KESLHh=bRFH!b|) xaRBTC(n22@;1qe0==1{c4`bMf2Qi0(z%7CJ5D(+ROd0S!garY-UjV~52cXVC@<9Lq diff --git a/assets/resources/dolphin/L3_Lab_research_128x54/frame_1.bm b/assets/resources/dolphin/L3_Lab_research_128x54/frame_1.bm deleted file mode 100644 index c600743700f179ba45f3713af0d427024c0b683c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 614 zcmV-s0-5~*0Ad0F9v}oX06F|W|M~y;20#3`JpT|Kgajs8FFofaln0y|4Y0(-C|H1b zeuL=&gblVi*Vs{7vFNSUds*4@7v6$R3bLgaSUV zF#4v(5Rn;sJp2Lj&VPa9(BN<@4)+I)1W+^Sjbt7$XfQo8K=Po&t^o1;0t9}2Cs^b# z5MX%#2x1@s2bDts1^HjW=ji{x1RkOF03G-r)({Yz#6QwFEXW@hfDq_6kWYm?3^Cvk z;1_}qJp>E*UmSqstPzL>unH*g!9eZ-fnbXWT4E0>2l(zh5kV*h5JnpC)ZkDIGzZxO z2e@Ehi=hXP{n9W&nCpY&5-)@gS^X)9JgLG_2b^FYFdyUqZZI5h3_u442bTaIaL6b) z@E8&Jz-$2Yg&Yb2aKJ!))8Kl|1Em3n3dh~Un4n4p4CD)o{Q>YmT9()Xafmk6C2d@Bb zTlvJ|0oVtmgg!ICDDxxH=mp>&$FUF&V-E*_TLbVQAIF54GvIp&3Ilk)0fuf5K-I|j AUjP6A diff --git a/assets/resources/dolphin/L3_Lab_research_128x54/frame_10.bm b/assets/resources/dolphin/L3_Lab_research_128x54/frame_10.bm deleted file mode 100644 index 694302a9de7918f0009e738f417aee84929cc94a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 576 zcmV-G0>AwM06YQ!9v}oX06F|W|M~y;20#3`JpT|Kgajs8FFofaln0y|4Y0(-C|H1a z{(s=~fWiji4`F%WAQ1rjUtk&Vc?bqT4?=ju_#g0jNIn4(1Ld6mKx2@~WCOWRcd$IXkvtp&@n2DjkU{6tfMm3Sv7B52LevrY zgU1zU6^t^#@(=)wL;xW1;?zOo0|%$0|NozZ#!FIw90UR&2ERZjwde=N0uPG_eBrP! z@P0?-|HA*l?pz#HpjbYjc}IXlfP;g}B_y0cKUeTrjz$4sj6g3C1o(wV{Xc*(NrWvi ztUxmYvj0ch#Gn{Mlud(R1c^Xl;P>4E2^v4~ki;MmC;`4`{>`4n793gXT2=jDH}1^mv2X86<#s1EcsD2sA<>MHmz^P);C_ z@!(+hKM_1<_3cxEV0<=bAA8&^YMpFP7%pt(_jsnU6)DW1% zh3SCDumk|1^+<{YKnn{Z68!)T3&0ix064&W*c=DE2gD~q4FZ>7A20}z6te*NkV^oi O7ze{pY5-D3m;pc_5AB-( diff --git a/assets/resources/dolphin/L3_Lab_research_128x54/frame_11.bm b/assets/resources/dolphin/L3_Lab_research_128x54/frame_11.bm deleted file mode 100644 index 246b955cf93c8b6abccaef6f44dd1b8fcf01e296..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 585 zcmV-P0=E4D07U`-9v}oX06F|W|M~y;20#3`JpT|Kgajs8?Qy>+`d;`bH6T!eM0P%w<1Ri}T21`kR2cO}nAoays z1!4~%0SrU{Ao1eVLF4$pgU`|b|IfkWC8+{O@m~j+0PoNV?RY*h%fLPCA@jh%>;d^7 zmBs*~5Cazn6)3=ef#)6p4gwAjER>Uh0fGMkVmTNEf-wLj3VeT`_-76P!4P=FBngp# zWDVEGEIQ0!9He2gc|SKmmpW#vuTKKp=xL2Z$g*0)HTTR-hpA4v0Ki zg9Hea2Rs810L%a+4`{>`4p8U;;6d*i1d2RL0uOj(kXAFnh{1uR5fHe*p^}1VqsIZm zfrH%qa1Q_^jv}!rAU$s%2pB!Z;s7b|2t*?wR*1|)>42l~90EraAeP1)4?l(-AV2|! zgvuN*KlUF0V-Ny|&w>9BkK_ITSXmIDAIxBNfCCG_76bqw`mi_;cn^q9f*J)bz&>CR XASq@6^B|T1N&FxOY7IaNNYel)t_A1i diff --git a/assets/resources/dolphin/L3_Lab_research_128x54/frame_12.bm b/assets/resources/dolphin/L3_Lab_research_128x54/frame_12.bm deleted file mode 100644 index b6fb1130bb895889494bb5ade77a02bde7d64d3f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 571 zcmV-B0>u3R05<{v9v}oX06F|W|M~y;20#3`JpT|Kgajs8?>{Feln0y|4Y0(-C|H1b zf5GVigbl{$PW`(gUcXM5qN{j$Y?L&5Mnlr2>m`&KzT+3=VWyFD^hl;o$Q4Kxh#Hc>DwBo`b-F<2Zqz;2w~8Fg*W?^nruNfdGyx%sy0bd3hl7 z2n4a7egOE<^PlJpav4m3cPZ}m2cMEBgMfZ3>M>FYJhPuD21`gQ8O6XPEkRGD0~Kf$ zj55IT5CDus03h+=)Is9|2dAU||DS`#OHzOw1Ogxizd$Fo=m*9E4~qzV;jl08en;j1 z!vDeUTpU!OSU#Y6M}R|sgM-T@B%DA$SMXSlMgd@qKravk_=QLPKW_?DSVGep!~-xZ zFZ6x9N&$p9MA$X}NR$R14}H)ek)!_^3_<|{fI$Xg4-i1WJ|FlPJ?l^qd4E6RvIdAm zpgG_ekBFo|_KZP5KREVHN0I;$lFVFzcya8Z91B?gFfxvsfd_r^(&?$BS^8kqfOE3?a1h5KGfP6Iu JpamppfE4a4{Feln0y|4Y0(-C|H1b zf5GVigblZNCreNFnGtz;#ePmJcH%p#C)WX z@VLR?5QvR~01sUxE&&9G1Ir*$5qN{k$Y&TQ9>x3~evkkDfAWB6JP|>8fE)Y|7=%=KG%$PcLE|5b#6J}9{zM^_zb^tK2MT9Le2aW^z$AG~B z;`StforZh`8I0jlDDeliXbLdlK-vT25P(3S5J8ycfTRx?z&;~TP&onw9xTBNh$LV! z=+Hdia{znBB#A=cKmqeuI0yhC~6P5ffOzqZ9xjyjs2bT_F4@nFI7?mIP z{sA9@mq;F$2y_GS!!3+4h#>XC3JV8-g~kEz0H-gD;RU!A$R3bNfD~Zg$SlGP4?xk! B`X>MY diff --git a/assets/resources/dolphin/L3_Lab_research_128x54/frame_2.bm b/assets/resources/dolphin/L3_Lab_research_128x54/frame_2.bm deleted file mode 100644 index 1025137e462b44c2341e193471c8b457d4451c1e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 618 zcmV-w0+sy%0A>OJ9v}oX06F|W|M~y;20#3`JpT|Kgajs8?>u;K^nyrYW-I1e0-Lm*#YJs^FyKMwk_VLr9dHMa;1D45={mx!Dc}Cya0zmyn=iw;9-vd zhXDr%rh*0hFOL9nRtUrb*aZ}L;GlO12ZI0!7=(ZxbPw^|cp&kBs0I*58t~NMPz*E& zVFL%dXkd$>2ao;IFhQ8>gX9t~gb#84DTq9&!chmDU>-1URk2L^%fAlv|X z!yusJz+hlI$PIuVkfVV>E*J<7+I|mNpmd-x;aKgt^AKo+Mj6N#7y1L>f!yPh0D4Rg z!T=yx&gKz=K|vv$2w*(_m^o`OLFoXAgU%oDi|{3daafF60W1d>1Bd^Ma5%$38;Cq= z3gdudj6(*8h6o-aQh;wT6fk&}5PBf<$KWU`;s1aP9-a(;0^tMTA4C9BFnRDX_5~QI zgdU@F-_AS&K?kISJ~O~5^CQ=Y12{qIdl3NUAo1rIhu}azj0_$DI0vwRpf`_!a7M%c EAd|oRBLDyZ diff --git a/assets/resources/dolphin/L3_Lab_research_128x54/frame_3.bm b/assets/resources/dolphin/L3_Lab_research_128x54/frame_3.bm deleted file mode 100644 index e623a1c0fa5cba36a8a2f364e19c96d49a644e13..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 608 zcmV-m0-yZ>09*n99v}oX06F|W|M~y;20#3`JpT|Kgajs8?>{Feln0y|4Y0(-C|H1b ze#7Yjgbl!hQGMmP^FfkZ{(4=*JIk|}|Q#BCT6`h2W`agbO8 zUe7-Od}#U4@H~1P4h3P}!192K27NKCgT@U8C!TZU4=M~g;13_bAXDh;6iC5=tAQr$VqsIjUxHpA@EFo!#Jg6VzxbR>SMhK(?j5Xn@z@Qjt54r{qaKOPA zLje*Cq+o+F*9XWXUkDuRra#0%CzE6f=-6FZ2h%Ul;_y9+LyGfCv^pvlyBR z2@K#v0q6X|@!{b0fJDLP4@eEpSXUK@#h?7QS@!$>1e>g3O uJ!B#Ao&iUh9*jUOfc8CzfO8OdbOOvDfdKw6Fn9#m9>M~E-aG}tnS;Vi*Vs{7vFNSUds*4@7v6$R3bLgaSUV zF#4v(5Rn;sJp2Lj&VPa9(BN<@4)+I)1W+^Sjbt7$XfQo8K=Po&t^o1;0t9}2Cs^b# z5MX%#2x1@s2bDts1^HjW=ji{x1RkOF03G-r)({Yz#6QwFEXW@hfDq_6kWYm?3^Cvk z;1_}qJp>E*UmO7BtPzL>unH*g!9eZ>QUr`arXcd5e~#n9fKC`9kPsM1NMJPVh<{CltJehf$JbY$N=16IN%t74h#=206gK4 zP;uZeBk_RP0qF`j6awLZfZM0Q^_mAt0}d6ByMZx4lnNQh7Z>^i;DP(#a{ziw4#EH+ zSjXV;gF!(doCsh%|Cl&@Fdnc7m^|VC2wmb>R~3lGpc24wa5#VXEW?a65x9fKpsqLu zI36%)cwm9zB`5~-0mCl|VF#iQJbnX%1K5AS1`kgLKY{Ro><^*OJ9v}oX06F|W|M~y;20#3`JpT|Kgajs8?>u;K^nyrYW-I1e0-Lm*#YJs^FyKMwk_VLr9dHMa;1D45={mEOrk1|zUOhybKu^WbCb3c(17 zJ$M6h-_9ox4!}JmA@QC8N0}avKraCHJ&1sF7{Feln0y|4Y0(-C|H1b zf5GVigblZNCreNFnGtz;#ePmJcH%p#C)WX z@VLR?5QvR~01sUxE&&9G1Ir*$5qN{k$Y&TQ9>x3~evkkDfAWB6JP|>8fE)Y|7=%=KG%$PcLE|5b#6J}9{zM^_zb^tK2MT9Le2aW^z$AG~B z;`StforZh`8I0jlDDeliXbLdlK-vT25P(3S5J8ycfTRx?z&;~TP&onw9xTBNh$LV! z=+Hdia{znBB#A=cKmqeuI0yhC~6P5ffOzqZ9xjyjs2bT_F4@nFI7?mIP z{sA9@mq;F$2y_GS!!3+4h#>XC3JV8-g~kEz0H-gD;RU!A$R3bNfD~Zg$SlGP4?xk! B`X>MY diff --git a/assets/resources/dolphin/L3_Lab_research_128x54/frame_7.bm b/assets/resources/dolphin/L3_Lab_research_128x54/frame_7.bm deleted file mode 100644 index c9b99a0144864d530c76fb9864ad23ebd5351253..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 585 zcmV-P0=E4D07U`-9v}oX06F|W|M~y;20#3`JpT|Kgajs8FFofaln0y|4Y0(-C|H1a z{(s=~fWiji4`F%WAQ1rjUtk&Vc?bqT4?=ju_#g0jNIn4(1Ld6mKx2@~WCFPFaCvzmcsK=M9x!DfgU_V^$!P^*4==+|N9GS4 zRiIWN@&FLTKmrdQEkqtai}*bKAOHOP9x_^xB!3m~d4LZ60G`)_;~cyL-ohU|3=Y5_ zk@;L;3LyY7aB)(M2lyUw;1J*-;PS~yI1m^g@D?MHfLJ3C0zjw7`TvG+;0zLB3ruSe z49ETl0O^aw6e02zP;f$bQAfyx~KJP1AGK#@m@Ktb;ek_yInF&Hp3f+80f6f#gv6nNk` za4>tHjsf5Vk;GOd1P86-;R6S_yg&s$0SJU-3eg#eeJ~V$1As{4gc8`pf#>kUqzC{o z@R>t}=l;Xs3}OIK`S3sC@%(?l3kxC?1Nn>&umE9r0>FR-A65qe?*Z`%&_h6_*ayr4 XBn2$MK4cQWDL;e&?LnvkNg7}UlQy?A6d%eNqrfDR ze?Q`~28cwUIp7$Nh@?RFj6p!-;A;pzV^9dk@(2G%h&`c_NC$vAKY@UQL?R?nfkPz( z;t3BP1`l)b6UKhh5m>|)B?JeeM4({z7mxs~0{Feln0y|4Y0(-C|H1b zf5GVigbl{$PW`(gUcXM5qN{j$Y?L&5Mnlr2>m`&KzT+3=VWyFD^hl;o$Q4Kxh#Hc>DwBo`b-F<2Zqz;2w~8Fg*W?^nruNfdGyx%sy0bd3hl7 z2n4a7egOE<^PlJpav4m3R~_sRKO|2F0IUPX45SiyXFgC2mXKB;^87UgK9CGmpjIIA z01(7L0uLT7L>@nj_&og||NQ(OGFp%%e--d~fDZitp4Wrp9J~YG!XG>g4!|Fg`CMQM zApkLOaZ-#2_#ScK5a1x-^2td!5Eviu79){>SR)VuK&Qv~|Auhe!j%>fw8pUj%zxl; z4w$?_LZBEzSWSaq1dIY`4~@_ufCCH%j6wkdfI$Xg4-i0r1pYwwtw2HK9T0f41_%)- z4tNG20hj9HGzyz=Pg22^4sg1Rn6oAgpJD5rYFrA|Y{sLnQ>!M~(xB0|&YI z;2r=;97SSKKziOj5HNd-!~j#^5Qs)Vtr3`q(*Z}|I0TL;K`o3p9)AovK!5`e36wZq zf9yU0#vla`p9B6MAIJOvu(BaQKbXMl00tL;EC>KW^ None: if self._have_pending_entries(): self.new_entries.add(self.version) print( - f"API version is still WIP: {self.version}. Review the changes and re-run command." + fg.red( + f"API version is still WIP: {self.version}. Review the changes and re-run command." + ) ) - print(f"Entries to review:") + print(f"CSV file entries to mark up:") print( - "\n".join( - map( - str, - filter( - lambda e: not isinstance(e, SdkVersion), self.new_entries - ), + fg.yellow( + "\n".join( + map( + str, + filter( + lambda e: not isinstance(e, SdkVersion), + self.new_entries, + ), + ) ) ) ) else: - print(f"API version {self.version} is up to date") + print(fg.green(f"API version {self.version} is up to date")) regenerate_csv = ( self.loaded_dirty_version diff --git a/scripts/fbt_tools/fbt_assets.py b/scripts/fbt_tools/fbt_assets.py index f058d15f918..4fa5353d0a7 100644 --- a/scripts/fbt_tools/fbt_assets.py +++ b/scripts/fbt_tools/fbt_assets.py @@ -30,22 +30,25 @@ def dolphin_emitter(target, source, env): res_root_dir = source[0].Dir(env["DOLPHIN_RES_TYPE"]) source = [res_root_dir] source.extend( - env.GlobRecursive("*.*", res_root_dir), + env.GlobRecursive("*.*", res_root_dir.srcnode()), ) target_base_dir = target[0] env.Replace(_DOLPHIN_OUT_DIR=target[0]) if env["DOLPHIN_RES_TYPE"] == "external": - target = [] - target.extend( - map( - lambda node: target_base_dir.File( - res_root_dir.rel_path(node).replace(".png", ".bm") - ), - filter(lambda node: isinstance(node, SCons.Node.FS.File), source), - ) - ) + target = [target_base_dir.File("manifest.txt")] + ## A detailed list of files to be generated + ## works better if we just leave target the folder + # target = [] + # target.extend( + # map( + # lambda node: target_base_dir.File( + # res_root_dir.rel_path(node).replace(".png", ".bm") + # ), + # filter(lambda node: isinstance(node, SCons.Node.FS.File), source), + # ) + # ) else: asset_basename = f"assets_dolphin_{env['DOLPHIN_RES_TYPE']}" target = [ @@ -53,6 +56,13 @@ def dolphin_emitter(target, source, env): target_base_dir.File(asset_basename + ".h"), ] + # Debug output + # print( + # f"Dolphin res type: {env['DOLPHIN_RES_TYPE']},\ntarget files:", + # list(f.path for f in target), + # f"\nsource files:", + # list(f.path for f in source), + # ) return target, source diff --git a/scripts/fbt_tools/fbt_debugopts.py b/scripts/fbt_tools/fbt_debugopts.py index 3e7b0701014..0c0ce3d32a0 100644 --- a/scripts/fbt_tools/fbt_debugopts.py +++ b/scripts/fbt_tools/fbt_debugopts.py @@ -1,4 +1,39 @@ +from re import search + +from SCons.Errors import UserError +from fbt_options import OPENOCD_OPTS + + +def _get_device_serials(search_str="STLink"): + import serial.tools.list_ports as list_ports + + return set([device.serial_number for device in list_ports.grep(search_str)]) + + +def GetDevices(env): + serials = _get_device_serials() + if len(serials) == 0: + raise UserError("No devices found") + + print("\n".join(serials)) + + def generate(env, **kw): + env.AddMethod(GetDevices) + + if (adapter_serial := env.subst("$OPENOCD_ADAPTER_SERIAL")) != "auto": + env.Append( + OPENOCD_OPTS=[ + "-c", + f"adapter serial {adapter_serial}", + ] + ) + + # Final command is "init", always explicitly added + env.Append( + OPENOCD_OPTS=["-c", "init"], + ) + env.SetDefault( OPENOCD_GDB_PIPE=[ "|openocd -c 'gdb_port pipe; log_output debug/openocd.log' ${[SINGLEQUOTEFUNC(OPENOCD_OPTS)]}" diff --git a/scripts/fbt_tools/fbt_extapps.py b/scripts/fbt_tools/fbt_extapps.py index e40a4efc251..5a5dab57284 100644 --- a/scripts/fbt_tools/fbt_extapps.py +++ b/scripts/fbt_tools/fbt_extapps.py @@ -1,3 +1,4 @@ +import shutil from SCons.Builder import Builder from SCons.Action import Action from SCons.Errors import UserError @@ -9,6 +10,7 @@ from fbt.appmanifest import FlipperApplication, FlipperManifestException from fbt.sdk import SdkCache import itertools +from ansi.color import fg def BuildAppElf(env, app): @@ -175,7 +177,8 @@ def validate_app_imports(target, source, env): if unresolved_syms: SCons.Warnings.warn( SCons.Warnings.LinkWarning, - f"\033[93m{source[0].path}: app won't run. Unresolved symbols: \033[95m{unresolved_syms}\033[0m", + fg.brightyellow(f"{source[0].path}: app won't run. Unresolved symbols: ") + + fg.brightmagenta(f"{unresolved_syms}"), ) @@ -209,14 +212,51 @@ def GetExtAppFromPath(env, app_dir): return (app, app_elf[0], app_validator[0]) +def fap_dist_emitter(target, source, env): + target_dir = target[0] + + target = [] + for dist_entry in env["_extapps"]["dist"].values(): + target.append(target_dir.Dir(dist_entry[0]).File(dist_entry[1][0].name)) + + for compact_entry in env["_extapps"]["compact"].values(): + source.extend(compact_entry) + + return (target, source) + + +def fap_dist_action(target, source, env): + # FIXME + target_dir = env.Dir("#/assets/resources/apps") + + shutil.rmtree(target_dir.path, ignore_errors=True) + for src, target in zip(source, target): + os.makedirs(os.path.dirname(target.path), exist_ok=True) + shutil.copy(src.path, target.path) + + def generate(env, **kw): env.SetDefault(EXT_APPS_WORK_DIR=kw.get("EXT_APPS_WORK_DIR")) - # env.VariantDir(env.subst("$EXT_APPS_WORK_DIR"), env.Dir("#"), duplicate=False) + + if not env["VERBOSE"]: + env.SetDefault( + FAPDISTCOMSTR="\tFAPDIST\t${TARGET}", + APPMETA_COMSTR="\tAPPMETA\t${TARGET}", + APPMETAEMBED_COMSTR="\tFAP\t${TARGET}", + APPCHECK_COMSTR="\tAPPCHK\t${SOURCE}", + ) env.AddMethod(BuildAppElf) env.AddMethod(GetExtAppFromPath) env.Append( BUILDERS={ + "FapDist": Builder( + action=Action( + fap_dist_action, + "$FAPDISTCOMSTR", + ), + emitter=fap_dist_emitter, + ), "EmbedAppMetadata": Builder( action=[ Action(prepare_app_metadata, "$APPMETA_COMSTR"), diff --git a/scripts/fbt_tools/fbt_sdk.py b/scripts/fbt_tools/fbt_sdk.py index ed0abdff674..0b6e22de5b0 100644 --- a/scripts/fbt_tools/fbt_sdk.py +++ b/scripts/fbt_tools/fbt_sdk.py @@ -1,3 +1,4 @@ +import shutil from SCons.Builder import Builder from SCons.Action import Action from SCons.Errors import UserError @@ -117,42 +118,30 @@ def emitter(self, target, source, env): target = [target_folder.File("sdk.opts")] return target, source - def _create_deploy_commands(self): + def _run_deploy_commands(self): dirs_to_create = set( - self.sdk_deploy_dir.Dir(dirpath) for dirpath in self.header_dirs + self.sdk_deploy_dir.Dir(dirpath).path for dirpath in self.header_dirs ) - actions = [ - Delete(self.sdk_deploy_dir), - Mkdir(self.sdk_deploy_dir), - Copy( - self.sdk_root_dir, - self.env["SDK_DEFINITION"], - ), - ] - actions += [Mkdir(d) for d in dirs_to_create] - actions += [ - Action( - Copy(self.sdk_deploy_dir.File(h).path, h), - # f"Copy {h} to {self.sdk_deploy_dir}", - ) - for h in self.header_depends - ] - return actions + shutil.rmtree(self.sdk_root_dir.path, ignore_errors=False) - def generate_actions(self): - self._parse_sdk_depends() - self._generate_sdk_meta() + for sdkdir in dirs_to_create: + os.makedirs(sdkdir, exist_ok=True) - return self._create_deploy_commands() + shutil.copy2(self.env["SDK_DEFINITION"].path, self.sdk_root_dir.path) + for header in self.header_depends: + shutil.copy2(header, self.sdk_deploy_dir.File(header).path) -def deploy_sdk_tree(target, source, env, for_signature): - if for_signature: - return [] + def deploy_action(self): + self._parse_sdk_depends() + self._run_deploy_commands() + self._generate_sdk_meta() + +def deploy_sdk_tree_action(target, source, env): sdk_tree = SdkTreeBuilder(env, target, source) - return sdk_tree.generate_actions() + return sdk_tree.deploy_action() def deploy_sdk_tree_emitter(target, source, env): @@ -217,6 +206,30 @@ def generate_sdk_symbols(source, target, env): def generate(env, **kw): + if not env["VERBOSE"]: + env.SetDefault( + SDK_PREGEN_COMSTR="\tPREGEN\t${TARGET}", + SDK_COMSTR="\tSDKSRC\t${TARGET}", + SDKSYM_UPDATER_COMSTR="\tSDKCHK\t${TARGET}", + SDKSYM_GENERATOR_COMSTR="\tSDKSYM\t${TARGET}", + SDKDEPLOY_COMSTR="\tSDKTREE\t${TARGET}", + ) + + # Filtering out things cxxheaderparser cannot handle + env.SetDefault( + SDK_PP_FLAGS=[ + '-D"_Static_assert(x,y)="', + '-D"__asm__(x)="', + '-D"__attribute__(x)="', + "-Drestrict=", + "-D_Noreturn=", + "-D__restrict=", + "-D__extension__=", + "-D__inline=inline", + "-D__inline__=inline", + ] + ) + env.AddMethod(ProcessSdkDepends) env.Append( BUILDERS={ @@ -235,7 +248,10 @@ def generate(env, **kw): suffix=".i", ), "SDKTree": Builder( - generator=deploy_sdk_tree, + action=Action( + deploy_sdk_tree_action, + "$SDKDEPLOY_COMSTR", + ), emitter=deploy_sdk_tree_emitter, src_suffix=".d", ), diff --git a/scripts/fbt_tools/fbt_tweaks.py b/scripts/fbt_tools/fbt_tweaks.py new file mode 100644 index 00000000000..a903d4033b5 --- /dev/null +++ b/scripts/fbt_tools/fbt_tweaks.py @@ -0,0 +1,43 @@ +import SCons.Warnings as Warnings + +# from SCons.Script.Main import find_deepest_user_frame + +from ansi.color import fg, bg, fx + +import traceback +import sys +import os + + +def find_deepest_user_frame(tb): + tb.reverse() + + # find the deepest traceback frame that is not part + # of SCons: + for frame in tb: + filename = frame[0] + if filename.find("fbt_tweaks") != -1: + continue + if filename.find(os.sep + "SCons" + os.sep) == -1: + return frame + return tb[0] + + +def fbt_warning(e): + filename, lineno, routine, dummy = find_deepest_user_frame( + traceback.extract_stack() + ) + fbt_line = "\nfbt: warning: %s\n" % e.args[0] + sys.stderr.write(fg.boldmagenta(fbt_line)) + fbt_line = ( + fg.yellow("%s, line %d, " % (routine, lineno)) + 'in file "%s"\n' % filename + ) + sys.stderr.write(fg.yellow(fbt_line)) + + +def generate(env): + Warnings._warningOut = fbt_warning + + +def exists(): + return True diff --git a/scripts/fbt_tools/fwbin.py b/scripts/fbt_tools/fwbin.py index 678b0499822..67e0d6450ec 100644 --- a/scripts/fbt_tools/fwbin.py +++ b/scripts/fbt_tools/fwbin.py @@ -12,6 +12,14 @@ def generate(env): OBJCOPY=__OBJCOPY_ARM_BIN, # FIXME NM=__NM_ARM_BIN, # FIXME ) + + if not env["VERBOSE"]: + env.SetDefault( + HEXCOMSTR="\tHEX\t${TARGET}", + BINCOMSTR="\tBIN\t${TARGET}", + DFUCOMSTR="\tDFU\t${TARGET}", + ) + env.Append( BUILDERS={ "HEXBuilder": Builder( diff --git a/scripts/fbt_tools/gdb.py b/scripts/fbt_tools/gdb.py index 94ea9fbe9be..38256a0f81d 100644 --- a/scripts/fbt_tools/gdb.py +++ b/scripts/fbt_tools/gdb.py @@ -6,8 +6,6 @@ def generate(env): env.SetDefault( GDB="gdb", GDBPY="gdb-py", - GDBOPTS="", - GDBPYOPTS="", GDBCOM="$GDB $GDBOPTS $SOURCES", # no $TARGET GDBPYCOM="$GDBPY $GDBOPTS $GDBPYOPTS $SOURCES", # no $TARGET ) diff --git a/scripts/fwsize.py b/scripts/fwsize.py index b381f6e9a57..445c290498f 100644 --- a/scripts/fwsize.py +++ b/scripts/fwsize.py @@ -4,6 +4,7 @@ import subprocess import os import math +from ansi.color import fg class Main(App): @@ -43,7 +44,9 @@ def process_bin(self): pages = math.ceil(binsize / PAGE_SIZE) last_page_state = (binsize % PAGE_SIZE) * 100 / PAGE_SIZE print( - f"{os.path.basename(self.args.binname):<11}: {pages:>4} flash pages (last page {last_page_state:.02f}% full)" + fg.yellow( + f"{os.path.basename(self.args.binname):<11}: {pages:>4} flash pages (last page {last_page_state:.02f}% full)" + ) ) return 0 diff --git a/scripts/sconsdist.py b/scripts/sconsdist.py index cb4f8f5a5e3..4c0427894ec 100644 --- a/scripts/sconsdist.py +++ b/scripts/sconsdist.py @@ -7,6 +7,7 @@ import shutil import zipfile import tarfile +from ansi.color import fg class ProjectDir: @@ -126,7 +127,7 @@ def copy(self): self.copy_single_project(project) self.logger.info( - f"Firmware binaries can be found at:\n\t{self.output_dir_path}" + fg.green(f"Firmware binaries can be found at:\n\t{self.output_dir_path}") ) if self.args.version: @@ -162,7 +163,9 @@ def copy(self): if (bundle_result := UpdateMain(no_exit=True)(bundle_args)) == 0: self.logger.info( - f"Use this directory to self-update your Flipper:\n\t{bundle_dir}" + fg.green( + f"Use this directory to self-update your Flipper:\n\t{bundle_dir}" + ) ) # Create tgz archive diff --git a/scripts/toolchain/fbtenv.cmd b/scripts/toolchain/fbtenv.cmd index 5eaf16f9443..a11a3ccd547 100644 --- a/scripts/toolchain/fbtenv.cmd +++ b/scripts/toolchain/fbtenv.cmd @@ -13,8 +13,8 @@ if not [%FBT_NOENV%] == [] ( exit /b 0 ) -set "FLIPPER_TOOLCHAIN_VERSION=15" -set "FBT_TOOLCHAIN_ROOT=%FBT_ROOT%\toolchain\i686-windows" +set "FLIPPER_TOOLCHAIN_VERSION=16" +set "FBT_TOOLCHAIN_ROOT=%FBT_ROOT%\toolchain\x86_64-windows" if not exist "%FBT_TOOLCHAIN_ROOT%" ( diff --git a/scripts/toolchain/fbtenv.sh b/scripts/toolchain/fbtenv.sh index 7c501803fbf..15f29e4dc67 100755 --- a/scripts/toolchain/fbtenv.sh +++ b/scripts/toolchain/fbtenv.sh @@ -5,7 +5,7 @@ # public variables DEFAULT_SCRIPT_PATH="$(pwd -P)"; SCRIPT_PATH="${SCRIPT_PATH:-$DEFAULT_SCRIPT_PATH}"; -FBT_TOOLCHAIN_VERSION="${FBT_TOOLCHAIN_VERSION:-"15"}"; +FBT_TOOLCHAIN_VERSION="${FBT_TOOLCHAIN_VERSION:-"16"}"; FBT_TOOLCHAIN_PATH="${FBT_TOOLCHAIN_PATH:-$SCRIPT_PATH}"; fbtenv_show_usage() diff --git a/scripts/toolchain/windows-toolchain-download.ps1 b/scripts/toolchain/windows-toolchain-download.ps1 index bcb8f998f25..370f1a14a9c 100644 --- a/scripts/toolchain/windows-toolchain-download.ps1 +++ b/scripts/toolchain/windows-toolchain-download.ps1 @@ -3,13 +3,13 @@ $ErrorActionPreference = "Stop" [Net.ServicePointManager]::SecurityProtocol = "tls12, tls11, tls" $repo_root = (Get-Item "$PSScriptRoot\..\..").FullName $toolchain_version = $args[0] -$toolchain_url = "https://update.flipperzero.one/builds/toolchain/gcc-arm-none-eabi-10.3-i686-windows-flipper-$toolchain_version.zip" -$toolchain_zip = "gcc-arm-none-eabi-10.3-i686-windows-flipper-$toolchain_version.zip" -$toolchain_dir = "gcc-arm-none-eabi-10.3-i686-windows-flipper" +$toolchain_url = "https://update.flipperzero.one/builds/toolchain/gcc-arm-none-eabi-10.3-x86_64-windows-flipper-$toolchain_version.zip" +$toolchain_zip = "gcc-arm-none-eabi-10.3-x86_64-windows-flipper-$toolchain_version.zip" +$toolchain_dir = "gcc-arm-none-eabi-10.3-x86_64-windows-flipper" -if (Test-Path -LiteralPath "$repo_root\toolchain\i686-windows") { +if (Test-Path -LiteralPath "$repo_root\toolchain\x86_64-windows") { Write-Host -NoNewline "Removing old Windows toolchain.." - Remove-Item -LiteralPath "$repo_root\toolchain\i686-windows" -Force -Recurse + Remove-Item -LiteralPath "$repo_root\toolchain\x86_64-windows" -Force -Recurse Write-Host "done!" } if (!(Test-Path -Path "$repo_root\$toolchain_zip" -PathType Leaf)) { @@ -26,7 +26,7 @@ if (!(Test-Path -LiteralPath "$repo_root\toolchain")) { Write-Host -NoNewline "Unziping Windows toolchain.." Add-Type -Assembly "System.IO.Compression.Filesystem" [System.IO.Compression.ZipFile]::ExtractToDirectory("$toolchain_zip", "$repo_root\") -Move-Item -Path "$repo_root\$toolchain_dir" -Destination "$repo_root\toolchain\i686-windows" +Move-Item -Path "$repo_root\$toolchain_dir" -Destination "$repo_root\toolchain\x86_64-windows" Write-Host "done!" Write-Host -NoNewline "Clearing temporary files.." diff --git a/site_scons/commandline.scons b/site_scons/commandline.scons index 324dfe33253..cadb417f858 100644 --- a/site_scons/commandline.scons +++ b/site_scons/commandline.scons @@ -171,6 +171,11 @@ vars.AddVariables( "Blackmagic probe location", "auto", ), + ( + "OPENOCD_ADAPTER_SERIAL", + "OpenOCD adapter serial number", + "auto", + ), ( "UPDATE_SPLASH", "Directory name with slideshow frames to render after installing update package", diff --git a/site_scons/environ.scons b/site_scons/environ.scons index 2d082838a2f..94705dada41 100644 --- a/site_scons/environ.scons +++ b/site_scons/environ.scons @@ -35,6 +35,7 @@ for env_value_name in variables_to_forward: coreenv = VAR_ENV.Clone( tools=[ + "fbt_tweaks", ( "crosscc", { @@ -80,8 +81,9 @@ if not coreenv["VERBOSE"]: # Default value for commandline options SetOption("num_jobs", multiprocessing.cpu_count()) +## NB - disabled both caches since they seem to do more harm then good in our case # Avoiding re-scan of all sources on every startup -SetOption("implicit_cache", True) +# SetOption("implicit_cache", True) # SetOption("implicit_deps_unchanged", True) # More aggressive caching SetOption("max_drift", 1) diff --git a/site_scons/extapps.scons b/site_scons/extapps.scons index 9edc6b5c17a..ee317be3bae 100644 --- a/site_scons/extapps.scons +++ b/site_scons/extapps.scons @@ -65,6 +65,7 @@ extapps = appenv["_extapps"] = { "debug": {}, "validators": {}, "dist": {}, + "resources_dist": None, } @@ -108,6 +109,8 @@ appenv.PhonyTarget("firmware_extapps", appenv.Action(legacy_app_build_stub, None Alias("faps", extapps["compact"].values()) Alias("faps", extapps["validators"].values()) +extapps["resources_dist"] = appenv.FapDist(appenv.Dir("#/assets/resources/apps"), []) + if appsrc := appenv.subst("$APPSRC"): app_manifest, fap_file, app_validator = appenv.GetExtAppFromPath(appsrc) appenv.PhonyTarget( diff --git a/site_scons/fbt_extra/util.py b/site_scons/fbt_extra/util.py index aa3d50b648a..c670c01d4d0 100644 --- a/site_scons/fbt_extra/util.py +++ b/site_scons/fbt_extra/util.py @@ -1,10 +1,11 @@ from fbt.util import link_dir +from ansi.color import fg def link_elf_dir_as_latest(env, elf_node): elf_dir = elf_node.Dir(".") latest_dir = env.Dir("#build/latest") - print(f"Setting {elf_dir} as latest built dir (./build/latest/)") + print(fg.green(f"Linking {elf_dir} as latest built dir (./build/latest/)")) return link_dir(latest_dir.abspath, elf_dir.abspath, env["PLATFORM"] == "win32") @@ -12,7 +13,7 @@ def should_gen_cdb_and_link_dir(env, requested_targets): explicitly_building_updater = False # Hacky way to check if updater-related targets were requested for build_target in requested_targets: - if "updater" in str(build_target): + if "updater" in str(build_target) and "package" not in str(build_target): explicitly_building_updater = True is_updater = not env["IS_BASE_FIRMWARE"] diff --git a/site_scons/site_init.py b/site_scons/site_init.py index 2d83d8816c8..b763a3a7c05 100644 --- a/site_scons/site_init.py +++ b/site_scons/site_init.py @@ -3,6 +3,7 @@ import sys import os import atexit +from ansi.color import fg, fx sys.path.insert(0, os.path.join(os.getcwd(), "scripts")) sys.path.insert(0, os.path.join(os.getcwd(), "lib/cxxheaderparser")) @@ -16,12 +17,12 @@ def bf_to_str(bf): if bf is None: # unknown targets product None in list return "(unknown tgt)" elif isinstance(bf, SCons.Errors.StopError): - return str(bf) + return fg.yellow(str(bf)) elif bf.node: - return str(bf.node) + ": " + bf.errstr + return fg.yellow(str(bf.node)) + ": " + bf.errstr elif bf.filename: - return bf.filename + ": " + bf.errstr - return "unknown failure: " + bf.errstr + return fg.yellow(bf.filename) + ": " + bf.errstr + return fg.yellow("unknown failure: ") + bf.errstr def display_build_status(): @@ -31,10 +32,9 @@ def display_build_status(): if bf: # bf is normally a list of build failures; if an element is None, # it's because of a target that scons doesn't know anything about. - failures_message = "\n".join( - ["Failed building %s" % bf_to_str(x) for x in bf if x is not None] - ) - print("*" * 10, "ERRORS", "*" * 10) + failures_message = "\n".join([bf_to_str(x) for x in bf if x is not None]) + print() + print(fg.brightred(fx.bold("*" * 10 + " FBT ERRORS " + "*" * 10))) print(failures_message) From 30f10dce80300a2a308e323e70cb1961f86b60cc Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Wed, 26 Oct 2022 01:44:27 +0300 Subject: [PATCH 169/824] Fix a typo in the factory reset screen "setting" -> "settings" #1917 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- .../scenes/storage_settings_scene_factory_reset.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/settings/storage_settings/scenes/storage_settings_scene_factory_reset.c b/applications/settings/storage_settings/scenes/storage_settings_scene_factory_reset.c index 64d2b96b18b..865ee48d4b5 100644 --- a/applications/settings/storage_settings/scenes/storage_settings_scene_factory_reset.c +++ b/applications/settings/storage_settings/scenes/storage_settings_scene_factory_reset.c @@ -24,7 +24,7 @@ void storage_settings_scene_factory_reset_on_enter(void* context) { dialog_ex_set_header(dialog_ex, "Confirm Factory Reset", 64, 10, AlignCenter, AlignCenter); dialog_ex_set_text( dialog_ex, - "Internal storage will be erased\r\nData and setting will be lost!", + "Internal storage will be erased\r\nData and settings will be lost!", 64, 32, AlignCenter, From d530238faeae6cf0b9ac2229ed7bead090ee1d09 Mon Sep 17 00:00:00 2001 From: Anna Prosvetova Date: Wed, 26 Oct 2022 00:48:33 +0200 Subject: [PATCH 170/824] CI: Update web updater domain (#1919) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7d616295480..ed5ee6bd7a0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -127,7 +127,7 @@ jobs: **Compiled firmware for commit `${{steps.names.outputs.commit_sha}}`:** - [📦 Update package](https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.branch_name}}/flipper-z-${{steps.names.outputs.default_target}}-update-${{steps.names.outputs.suffix}}.tgz) - [📥 DFU file](https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.branch_name}}/flipper-z-${{steps.names.outputs.default_target}}-full-${{steps.names.outputs.suffix}}.dfu) - - [☁️ Web/App updater](https://my.flipp.dev/?url=https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.branch_name}}/flipper-z-${{steps.names.outputs.default_target}}-update-${{steps.names.outputs.suffix}}.tgz&channel=${{steps.names.outputs.branch_name}}&version=${{steps.names.outputs.commit_sha}}) + - [☁️ Web/App updater](https://lab.flipper.net/?url=https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.branch_name}}/flipper-z-${{steps.names.outputs.default_target}}-update-${{steps.names.outputs.suffix}}.tgz&channel=${{steps.names.outputs.branch_name}}&version=${{steps.names.outputs.commit_sha}}) edit-mode: replace compact: From 7d2d2b3dd9e04ca61a88781a5dd229974f1de58d Mon Sep 17 00:00:00 2001 From: gornekich Date: Wed, 26 Oct 2022 02:57:06 +0400 Subject: [PATCH 171/824] [FL-2932] TikTok: reset cursor after enter and reconnect #1921 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- .../plugins/bt_hid_app/views/bt_hid_tiktok.c | 179 ++++++++++-------- 1 file changed, 101 insertions(+), 78 deletions(-) diff --git a/applications/plugins/bt_hid_app/views/bt_hid_tiktok.c b/applications/plugins/bt_hid_app/views/bt_hid_tiktok.c index 9af00157dd5..fe1005b2d69 100644 --- a/applications/plugins/bt_hid_app/views/bt_hid_tiktok.c +++ b/applications/plugins/bt_hid_app/views/bt_hid_tiktok.c @@ -17,6 +17,7 @@ typedef struct { bool down_pressed; bool ok_pressed; bool connected; + bool is_cursor_set; } BtHidTikTokModel; static void bt_hid_tiktok_draw_callback(Canvas* canvas, void* context) { @@ -88,48 +89,48 @@ static void bt_hid_tiktok_draw_callback(Canvas* canvas, void* context) { elements_multiline_text_aligned(canvas, 13, 62, AlignLeft, AlignBottom, "Hold to exit"); } -static void bt_hid_tiktok_process_press(BtHidTikTok* bt_hid_tiktok, InputEvent* event) { - with_view_model( - bt_hid_tiktok->view, - BtHidTikTokModel * model, - { - if(event->key == InputKeyUp) { - model->up_pressed = true; - } else if(event->key == InputKeyDown) { - model->down_pressed = true; - } else if(event->key == InputKeyLeft) { - model->left_pressed = true; - furi_hal_bt_hid_consumer_key_press(HID_CONSUMER_VOLUME_DECREMENT); - } else if(event->key == InputKeyRight) { - model->right_pressed = true; - furi_hal_bt_hid_consumer_key_press(HID_CONSUMER_VOLUME_INCREMENT); - } else if(event->key == InputKeyOk) { - model->ok_pressed = true; - } - }, - true); +static void bt_hid_tiktok_reset_cursor() { + // Set cursor to the phone's left up corner + // Delays to guarantee one packet per connection interval + for(size_t i = 0; i < 8; i++) { + furi_hal_bt_hid_mouse_move(-127, -127); + furi_delay_ms(50); + } + // Move cursor from the corner + furi_hal_bt_hid_mouse_move(20, 120); + furi_delay_ms(50); } -static void bt_hid_tiktok_process_release(BtHidTikTok* bt_hid_tiktok, InputEvent* event) { - with_view_model( - bt_hid_tiktok->view, - BtHidTikTokModel * model, - { - if(event->key == InputKeyUp) { - model->up_pressed = false; - } else if(event->key == InputKeyDown) { - model->down_pressed = false; - } else if(event->key == InputKeyLeft) { - model->left_pressed = false; - furi_hal_bt_hid_consumer_key_release(HID_CONSUMER_VOLUME_DECREMENT); - } else if(event->key == InputKeyRight) { - model->right_pressed = false; - furi_hal_bt_hid_consumer_key_release(HID_CONSUMER_VOLUME_INCREMENT); - } else if(event->key == InputKeyOk) { - model->ok_pressed = false; - } - }, - true); +static void bt_hid_tiktok_process_press(BtHidTikTokModel* model, InputEvent* event) { + if(event->key == InputKeyUp) { + model->up_pressed = true; + } else if(event->key == InputKeyDown) { + model->down_pressed = true; + } else if(event->key == InputKeyLeft) { + model->left_pressed = true; + furi_hal_bt_hid_consumer_key_press(HID_CONSUMER_VOLUME_DECREMENT); + } else if(event->key == InputKeyRight) { + model->right_pressed = true; + furi_hal_bt_hid_consumer_key_press(HID_CONSUMER_VOLUME_INCREMENT); + } else if(event->key == InputKeyOk) { + model->ok_pressed = true; + } +} + +static void bt_hid_tiktok_process_release(BtHidTikTokModel* model, InputEvent* event) { + if(event->key == InputKeyUp) { + model->up_pressed = false; + } else if(event->key == InputKeyDown) { + model->down_pressed = false; + } else if(event->key == InputKeyLeft) { + model->left_pressed = false; + furi_hal_bt_hid_consumer_key_release(HID_CONSUMER_VOLUME_DECREMENT); + } else if(event->key == InputKeyRight) { + model->right_pressed = false; + furi_hal_bt_hid_consumer_key_release(HID_CONSUMER_VOLUME_INCREMENT); + } else if(event->key == InputKeyOk) { + model->ok_pressed = false; + } } static bool bt_hid_tiktok_input_callback(InputEvent* event, void* context) { @@ -137,43 +138,59 @@ static bool bt_hid_tiktok_input_callback(InputEvent* event, void* context) { BtHidTikTok* bt_hid_tiktok = context; bool consumed = false; - if(event->type == InputTypePress) { - bt_hid_tiktok_process_press(bt_hid_tiktok, event); - consumed = true; - } else if(event->type == InputTypeRelease) { - bt_hid_tiktok_process_release(bt_hid_tiktok, event); - consumed = true; - } else if(event->type == InputTypeShort) { - if(event->key == InputKeyOk) { - furi_hal_bt_hid_mouse_press(HID_MOUSE_BTN_LEFT); - furi_delay_ms(50); - furi_hal_bt_hid_mouse_release(HID_MOUSE_BTN_LEFT); - furi_delay_ms(50); - furi_hal_bt_hid_mouse_press(HID_MOUSE_BTN_LEFT); - furi_delay_ms(50); - furi_hal_bt_hid_mouse_release(HID_MOUSE_BTN_LEFT); - consumed = true; - } else if(event->key == InputKeyUp) { - // Emulate up swipe - furi_hal_bt_hid_mouse_scroll(-6); - furi_hal_bt_hid_mouse_scroll(-12); - furi_hal_bt_hid_mouse_scroll(-19); - furi_hal_bt_hid_mouse_scroll(-12); - furi_hal_bt_hid_mouse_scroll(-6); - consumed = true; - } else if(event->key == InputKeyDown) { - // Emulate down swipe - furi_hal_bt_hid_mouse_scroll(6); - furi_hal_bt_hid_mouse_scroll(12); - furi_hal_bt_hid_mouse_scroll(19); - furi_hal_bt_hid_mouse_scroll(12); - furi_hal_bt_hid_mouse_scroll(6); - consumed = true; - } else if(event->key == InputKeyBack) { - furi_hal_bt_hid_consumer_key_release_all(); - consumed = true; - } - } + with_view_model( + bt_hid_tiktok->view, + BtHidTikTokModel * model, + { + if(event->type == InputTypePress) { + bt_hid_tiktok_process_press(model, event); + if(model->connected && !model->is_cursor_set) { + bt_hid_tiktok_reset_cursor(); + model->is_cursor_set = true; + } + consumed = true; + } else if(event->type == InputTypeRelease) { + bt_hid_tiktok_process_release(model, event); + consumed = true; + } else if(event->type == InputTypeShort) { + if(event->key == InputKeyOk) { + furi_hal_bt_hid_mouse_press(HID_MOUSE_BTN_LEFT); + furi_delay_ms(50); + furi_hal_bt_hid_mouse_release(HID_MOUSE_BTN_LEFT); + furi_delay_ms(50); + furi_hal_bt_hid_mouse_press(HID_MOUSE_BTN_LEFT); + furi_delay_ms(50); + furi_hal_bt_hid_mouse_release(HID_MOUSE_BTN_LEFT); + consumed = true; + } else if(event->key == InputKeyUp) { + // Emulate up swipe + furi_hal_bt_hid_mouse_scroll(-6); + furi_hal_bt_hid_mouse_scroll(-12); + furi_hal_bt_hid_mouse_scroll(-19); + furi_hal_bt_hid_mouse_scroll(-12); + furi_hal_bt_hid_mouse_scroll(-6); + consumed = true; + } else if(event->key == InputKeyDown) { + // Emulate down swipe + furi_hal_bt_hid_mouse_scroll(6); + furi_hal_bt_hid_mouse_scroll(12); + furi_hal_bt_hid_mouse_scroll(19); + furi_hal_bt_hid_mouse_scroll(12); + furi_hal_bt_hid_mouse_scroll(6); + consumed = true; + } else if(event->key == InputKeyBack) { + furi_hal_bt_hid_consumer_key_release_all(); + consumed = true; + } + } else if(event->type == InputTypeLong) { + if(event->key == InputKeyBack) { + furi_hal_bt_hid_consumer_key_release_all(); + model->is_cursor_set = false; + consumed = false; + } + } + }, + true); return consumed; } @@ -203,5 +220,11 @@ View* bt_hid_tiktok_get_view(BtHidTikTok* bt_hid_tiktok) { void bt_hid_tiktok_set_connected_status(BtHidTikTok* bt_hid_tiktok, bool connected) { furi_assert(bt_hid_tiktok); with_view_model( - bt_hid_tiktok->view, BtHidTikTokModel * model, { model->connected = connected; }, true); + bt_hid_tiktok->view, + BtHidTikTokModel * model, + { + model->connected = connected; + model->is_cursor_set = false; + }, + true); } From 406d830fb652bb0a8791efd28357bb50e1f9640a Mon Sep 17 00:00:00 2001 From: Max Lapan Date: Wed, 26 Oct 2022 15:37:58 +0200 Subject: [PATCH 172/824] Oregon2 extra (#1924) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add support for temp-humid sensor 0x1D20 * Fix protocol type and flags, humidity decoding Co-authored-by: あく --- .../weather_station/protocols/oregon2.c | 44 ++++++++++++------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/applications/plugins/weather_station/protocols/oregon2.c b/applications/plugins/weather_station/protocols/oregon2.c index c4e4471a048..76bc3f0a169 100644 --- a/applications/plugins/weather_station/protocols/oregon2.c +++ b/applications/plugins/weather_station/protocols/oregon2.c @@ -19,7 +19,7 @@ static const SubGhzBlockConst ws_oregon2_const = { }; #define OREGON2_PREAMBLE_BITS 19 -#define OREGON2_PREAMBLE_MASK ((1 << (OREGON2_PREAMBLE_BITS + 1)) - 1) +#define OREGON2_PREAMBLE_MASK 0b1111111111111111111 #define OREGON2_SENSOR_ID(d) (((d) >> 16) & 0xFFFF) #define OREGON2_CHECKSUM_BITS 8 @@ -103,6 +103,7 @@ static ManchesterEvent level_and_duration_to_event(bool level, uint32_t duration // From sensor id code return amount of bits in variable section static uint8_t oregon2_sensor_id_var_bits(uint16_t sensor_id) { if(sensor_id == 0xEC40) return 16; + if(sensor_id == 0x1D20) return 24; return 0; } @@ -119,18 +120,30 @@ static void ws_oregon2_decode_const_data(WSBlockGeneric* ws_block) { ws_block->battery_low = (ws_block->data & OREGON2_FLAG_BAT_LOW) ? 1 : 0; } -static void - ws_oregon2_decode_var_data(WSBlockGeneric* ws_block, uint16_t sensor_id, uint32_t var_data) { - int16_t temp_val; - if(sensor_id == 0xEC40) { - temp_val = ((var_data >> 4) & 0xF) * 10 + ((var_data >> 8) & 0xF); - temp_val *= 10; - temp_val += (var_data >> 12) & 0xF; - if(var_data & 0xF) temp_val = -temp_val; - } else - return; - - ws_block->temp = (float)temp_val / 10.0; +uint16_t bcd_decode_short(uint32_t data) { + return (data & 0xF) * 10 + ((data >> 4) & 0xF); +} + +static float ws_oregon2_decode_temp(uint32_t data) { + int32_t temp_val; + temp_val = bcd_decode_short(data >> 4); + temp_val *= 10; + temp_val += (data >> 12) & 0xF; + if(data & 0xF) temp_val = -temp_val; + return (float)temp_val / 10.0; +} + +static void ws_oregon2_decode_var_data(WSBlockGeneric* ws_b, uint16_t sensor_id, uint32_t data) { + switch(sensor_id) { + case 0xEC40: + ws_b->temp = ws_oregon2_decode_temp(data); + ws_b->humidity = WS_NO_HUMIDITY; + break; + case 0x1D20: + ws_b->humidity = bcd_decode_short(data); + ws_b->temp = ws_oregon2_decode_temp(data >> 8); + break; + } } void ws_protocol_decoder_oregon2_feed(void* context, bool level, uint32_t duration) { @@ -346,9 +359,8 @@ const SubGhzProtocolDecoder ws_protocol_oregon2_decoder = { const SubGhzProtocol ws_protocol_oregon2 = { .name = WS_PROTOCOL_OREGON2_NAME, - .type = SubGhzProtocolTypeStatic, - .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | - SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save, + .type = SubGhzProtocolWeatherStation, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable, .decoder = &ws_protocol_oregon2_decoder, }; From 378bf050684b9e56d71c84fd2e44a57caa507ee2 Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Wed, 26 Oct 2022 18:56:54 +0400 Subject: [PATCH 173/824] [FL-2934] WS: add protocol Acurite 592TXR (#1916) * WS: add protocol Acurite 592TXR * WS: fix decoder Acurite_592TXR * SubGhz: proper types in math Co-authored-by: Aleksandr Kutuzov --- .../helpers/weather_station_types.h | 2 +- .../protocols/acurite_592txr.c | 307 ++++++++++++++++++ .../protocols/acurite_592txr.h | 79 +++++ .../protocols/protocol_items.c | 1 + .../protocols/protocol_items.h | 1 + firmware/targets/f7/api_symbols.csv | 6 +- lib/subghz/blocks/math.c | 30 ++ lib/subghz/blocks/math.h | 31 ++ 8 files changed, 455 insertions(+), 2 deletions(-) create mode 100644 applications/plugins/weather_station/protocols/acurite_592txr.c create mode 100644 applications/plugins/weather_station/protocols/acurite_592txr.h diff --git a/applications/plugins/weather_station/helpers/weather_station_types.h b/applications/plugins/weather_station/helpers/weather_station_types.h index 275d2332937..479638b98ff 100644 --- a/applications/plugins/weather_station/helpers/weather_station_types.h +++ b/applications/plugins/weather_station/helpers/weather_station_types.h @@ -3,7 +3,7 @@ #include #include -#define WS_VERSION_APP "0.2" +#define WS_VERSION_APP "0.3" #define WS_DEVELOPED "SkorP" #define WS_GITHUB "https://github.com/flipperdevices/flipperzero-firmware" diff --git a/applications/plugins/weather_station/protocols/acurite_592txr.c b/applications/plugins/weather_station/protocols/acurite_592txr.c new file mode 100644 index 00000000000..db05af09555 --- /dev/null +++ b/applications/plugins/weather_station/protocols/acurite_592txr.c @@ -0,0 +1,307 @@ +#include "acurite_592txr.h" + +#define TAG "WSProtocolAcurite_592TXR" + +/* + * Help + * https://github.com/merbanan/rtl_433/blob/5bef4e43133ac4c0e2d18d36f87c52b4f9458453/src/devices/acurite.c + * + * Acurite 592TXR Temperature Humidity sensor decoder + * Message Type 0x04, 7 bytes + * | Byte 0 | Byte 1 | Byte 2 | Byte 3 | Byte 4 | Byte 5 | Byte 6 | + * | --------- | --------- | --------- | --------- | --------- | --------- | --------- | + * | CCII IIII | IIII IIII | pB00 0100 | pHHH HHHH | p??T TTTT | pTTT TTTT | KKKK KKKK | + * - C: Channel 00: C, 10: B, 11: A, (01 is invalid) + * - I: Device ID (14 bits) + * - B: Battery, 1 is battery OK, 0 is battery low + * - M: Message type (6 bits), 0x04 + * - T: Temperature Celsius (11 - 14 bits?), + 1000 * 10 + * - H: Relative Humidity (%) (7 bits) + * - K: Checksum (8 bits) + * - p: Parity bit + * Notes: + * - Temperature + * - Encoded as Celsius + 1000 * 10 + * - only 11 bits needed for specified range -40 C to 70 C (-40 F - 158 F) + * - However 14 bits available for temperature, giving possible range of -100 C to 1538.4 C + * - @todo - check if high 3 bits ever used for anything else + * + */ + +static const SubGhzBlockConst ws_protocol_acurite_592txr_const = { + .te_short = 200, + .te_long = 400, + .te_delta = 90, + .min_count_bit_for_found = 56, +}; + +struct WSProtocolDecoderAcurite_592TXR { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + WSBlockGeneric generic; + + uint16_t header_count; +}; + +struct WSProtocolEncoderAcurite_592TXR { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + WSBlockGeneric generic; +}; + +typedef enum { + Acurite_592TXRDecoderStepReset = 0, + Acurite_592TXRDecoderStepCheckPreambule, + Acurite_592TXRDecoderStepSaveDuration, + Acurite_592TXRDecoderStepCheckDuration, +} Acurite_592TXRDecoderStep; + +const SubGhzProtocolDecoder ws_protocol_acurite_592txr_decoder = { + .alloc = ws_protocol_decoder_acurite_592txr_alloc, + .free = ws_protocol_decoder_acurite_592txr_free, + + .feed = ws_protocol_decoder_acurite_592txr_feed, + .reset = ws_protocol_decoder_acurite_592txr_reset, + + .get_hash_data = ws_protocol_decoder_acurite_592txr_get_hash_data, + .serialize = ws_protocol_decoder_acurite_592txr_serialize, + .deserialize = ws_protocol_decoder_acurite_592txr_deserialize, + .get_string = ws_protocol_decoder_acurite_592txr_get_string, +}; + +const SubGhzProtocolEncoder ws_protocol_acurite_592txr_encoder = { + .alloc = NULL, + .free = NULL, + + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol ws_protocol_acurite_592txr = { + .name = WS_PROTOCOL_ACURITE_592TXR_NAME, + .type = SubGhzProtocolWeatherStation, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable, + + .decoder = &ws_protocol_acurite_592txr_decoder, + .encoder = &ws_protocol_acurite_592txr_encoder, +}; + +void* ws_protocol_decoder_acurite_592txr_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + WSProtocolDecoderAcurite_592TXR* instance = malloc(sizeof(WSProtocolDecoderAcurite_592TXR)); + instance->base.protocol = &ws_protocol_acurite_592txr; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void ws_protocol_decoder_acurite_592txr_free(void* context) { + furi_assert(context); + WSProtocolDecoderAcurite_592TXR* instance = context; + free(instance); +} + +void ws_protocol_decoder_acurite_592txr_reset(void* context) { + furi_assert(context); + WSProtocolDecoderAcurite_592TXR* instance = context; + instance->decoder.parser_step = Acurite_592TXRDecoderStepReset; +} + +static bool ws_protocol_acurite_592txr_check_crc(WSProtocolDecoderAcurite_592TXR* instance) { + uint8_t msg[] = { + instance->decoder.decode_data >> 48, + instance->decoder.decode_data >> 40, + instance->decoder.decode_data >> 32, + instance->decoder.decode_data >> 24, + instance->decoder.decode_data >> 16, + instance->decoder.decode_data >> 8}; + + if((subghz_protocol_blocks_add_bytes(msg, 6) == + (uint8_t)(instance->decoder.decode_data & 0xFF)) && + (!subghz_protocol_blocks_parity_bytes(&msg[2], 4))) { + return true; + } else { + return false; + } +} + +/** + * Analysis of received data + * @param instance Pointer to a WSBlockGeneric* instance + */ +static void ws_protocol_acurite_592txr_remote_controller(WSBlockGeneric* instance) { + uint8_t channel[] = {3, 0, 2, 1}; + uint8_t channel_raw = ((instance->data >> 54) & 0x03); + instance->channel = channel[channel_raw]; + instance->id = (instance->data >> 40) & 0x3FFF; + instance->battery_low = !((instance->data >> 38) & 1); + instance->humidity = (instance->data >> 24) & 0x7F; + + uint16_t temp_raw = ((instance->data >> 9) & 0xF80) | ((instance->data >> 8) & 0x7F); + instance->temp = ((float)(temp_raw)-1000) / 10.0f; + + instance->btn = WS_NO_BTN; +} + +void ws_protocol_decoder_acurite_592txr_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + WSProtocolDecoderAcurite_592TXR* instance = context; + + switch(instance->decoder.parser_step) { + case Acurite_592TXRDecoderStepReset: + if((level) && (DURATION_DIFF(duration, ws_protocol_acurite_592txr_const.te_short * 3) < + ws_protocol_acurite_592txr_const.te_delta * 2)) { + instance->decoder.parser_step = Acurite_592TXRDecoderStepCheckPreambule; + instance->decoder.te_last = duration; + instance->header_count = 0; + } + break; + + case Acurite_592TXRDecoderStepCheckPreambule: + if(level) { + instance->decoder.te_last = duration; + } else { + if((DURATION_DIFF( + instance->decoder.te_last, ws_protocol_acurite_592txr_const.te_short * 3) < + ws_protocol_acurite_592txr_const.te_delta * 2) && + (DURATION_DIFF(duration, ws_protocol_acurite_592txr_const.te_short * 3) < + ws_protocol_acurite_592txr_const.te_delta * 2)) { + //Found preambule + instance->header_count++; + } else if((instance->header_count > 2) && (instance->header_count < 5)) { + if((DURATION_DIFF( + instance->decoder.te_last, ws_protocol_acurite_592txr_const.te_short) < + ws_protocol_acurite_592txr_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_acurite_592txr_const.te_long) < + ws_protocol_acurite_592txr_const.te_delta)) { + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = Acurite_592TXRDecoderStepSaveDuration; + } else if( + (DURATION_DIFF( + instance->decoder.te_last, ws_protocol_acurite_592txr_const.te_long) < + ws_protocol_acurite_592txr_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_acurite_592txr_const.te_short) < + ws_protocol_acurite_592txr_const.te_delta)) { + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = Acurite_592TXRDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = Acurite_592TXRDecoderStepReset; + } + } else { + instance->decoder.parser_step = Acurite_592TXRDecoderStepReset; + } + } + break; + + case Acurite_592TXRDecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = Acurite_592TXRDecoderStepCheckDuration; + } else { + instance->decoder.parser_step = Acurite_592TXRDecoderStepReset; + } + break; + + case Acurite_592TXRDecoderStepCheckDuration: + if(!level) { + if(duration >= ((uint32_t)ws_protocol_acurite_592txr_const.te_short * 5)) { + if((instance->decoder.decode_count_bit == + ws_protocol_acurite_592txr_const.min_count_bit_for_found) && + ws_protocol_acurite_592txr_check_crc(instance)) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + ws_protocol_acurite_592txr_remote_controller(&instance->generic); + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->decoder.parser_step = Acurite_592TXRDecoderStepReset; + break; + } else if( + (DURATION_DIFF( + instance->decoder.te_last, ws_protocol_acurite_592txr_const.te_short) < + ws_protocol_acurite_592txr_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_acurite_592txr_const.te_long) < + ws_protocol_acurite_592txr_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = Acurite_592TXRDecoderStepSaveDuration; + } else if( + (DURATION_DIFF( + instance->decoder.te_last, ws_protocol_acurite_592txr_const.te_long) < + ws_protocol_acurite_592txr_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_acurite_592txr_const.te_short) < + ws_protocol_acurite_592txr_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = Acurite_592TXRDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = Acurite_592TXRDecoderStepReset; + } + } else { + instance->decoder.parser_step = Acurite_592TXRDecoderStepReset; + } + break; + } +} + +uint8_t ws_protocol_decoder_acurite_592txr_get_hash_data(void* context) { + furi_assert(context); + WSProtocolDecoderAcurite_592TXR* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool ws_protocol_decoder_acurite_592txr_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + WSProtocolDecoderAcurite_592TXR* instance = context; + return ws_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +bool ws_protocol_decoder_acurite_592txr_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + WSProtocolDecoderAcurite_592TXR* instance = context; + bool ret = false; + do { + if(!ws_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if(instance->generic.data_count_bit != + ws_protocol_acurite_592txr_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + ret = true; + } while(false); + return ret; +} + +void ws_protocol_decoder_acurite_592txr_get_string(void* context, FuriString* output) { + furi_assert(context); + WSProtocolDecoderAcurite_592TXR* instance = context; + furi_string_printf( + output, + "%s %dbit\r\n" + "Key:0x%lX%08lX\r\n" + "Sn:0x%lX Ch:%d Bat:%d\r\n" + "Temp:%d.%d C Hum:%d%%", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data >> 32), + (uint32_t)(instance->generic.data), + instance->generic.id, + instance->generic.channel, + instance->generic.battery_low, + (int16_t)instance->generic.temp, + abs(((int16_t)(instance->generic.temp * 10) - (((int16_t)instance->generic.temp) * 10))), + instance->generic.humidity); +} diff --git a/applications/plugins/weather_station/protocols/acurite_592txr.h b/applications/plugins/weather_station/protocols/acurite_592txr.h new file mode 100644 index 00000000000..ac0371d89e4 --- /dev/null +++ b/applications/plugins/weather_station/protocols/acurite_592txr.h @@ -0,0 +1,79 @@ +#pragma once + +#include + +#include +#include +#include +#include "ws_generic.h" +#include + +#define WS_PROTOCOL_ACURITE_592TXR_NAME "Acurite 592TXR" + +typedef struct WSProtocolDecoderAcurite_592TXR WSProtocolDecoderAcurite_592TXR; +typedef struct WSProtocolEncoderAcurite_592TXR WSProtocolEncoderAcurite_592TXR; + +extern const SubGhzProtocolDecoder ws_protocol_acurite_592txr_decoder; +extern const SubGhzProtocolEncoder ws_protocol_acurite_592txr_encoder; +extern const SubGhzProtocol ws_protocol_acurite_592txr; + +/** + * Allocate WSProtocolDecoderAcurite_592TXR. + * @param environment Pointer to a SubGhzEnvironment instance + * @return WSProtocolDecoderAcurite_592TXR* pointer to a WSProtocolDecoderAcurite_592TXR instance + */ +void* ws_protocol_decoder_acurite_592txr_alloc(SubGhzEnvironment* environment); + +/** + * Free WSProtocolDecoderAcurite_592TXR. + * @param context Pointer to a WSProtocolDecoderAcurite_592TXR instance + */ +void ws_protocol_decoder_acurite_592txr_free(void* context); + +/** + * Reset decoder WSProtocolDecoderAcurite_592TXR. + * @param context Pointer to a WSProtocolDecoderAcurite_592TXR instance + */ +void ws_protocol_decoder_acurite_592txr_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a WSProtocolDecoderAcurite_592TXR instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void ws_protocol_decoder_acurite_592txr_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a WSProtocolDecoderAcurite_592TXR instance + * @return hash Hash sum + */ +uint8_t ws_protocol_decoder_acurite_592txr_get_hash_data(void* context); + +/** + * Serialize data WSProtocolDecoderAcurite_592TXR. + * @param context Pointer to a WSProtocolDecoderAcurite_592TXR instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool ws_protocol_decoder_acurite_592txr_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data WSProtocolDecoderAcurite_592TXR. + * @param context Pointer to a WSProtocolDecoderAcurite_592TXR instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool ws_protocol_decoder_acurite_592txr_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a WSProtocolDecoderAcurite_592TXR instance + * @param output Resulting text + */ +void ws_protocol_decoder_acurite_592txr_get_string(void* context, FuriString* output); diff --git a/applications/plugins/weather_station/protocols/protocol_items.c b/applications/plugins/weather_station/protocols/protocol_items.c index 0740691a285..3ec9e995a4a 100644 --- a/applications/plugins/weather_station/protocols/protocol_items.c +++ b/applications/plugins/weather_station/protocols/protocol_items.c @@ -8,6 +8,7 @@ const SubGhzProtocol* weather_station_protocol_registry_items[] = { &ws_protocol_acurite_606tx, &ws_protocol_lacrosse_tx141thbv2, &ws_protocol_oregon2, + &ws_protocol_acurite_592txr, }; const SubGhzProtocolRegistry weather_station_protocol_registry = { diff --git a/applications/plugins/weather_station/protocols/protocol_items.h b/applications/plugins/weather_station/protocols/protocol_items.h index edc078cd62f..8f3eb53d789 100644 --- a/applications/plugins/weather_station/protocols/protocol_items.h +++ b/applications/plugins/weather_station/protocols/protocol_items.h @@ -8,5 +8,6 @@ #include "acurite_606tx.h" #include "lacrosse_tx141thbv2.h" #include "oregon2.h" +#include "acurite_592txr.h" extern const SubGhzProtocolRegistry weather_station_protocol_registry; diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 4c48d9abd50..17a8a675a10 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,5.0,, +Version,+,5.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -2271,6 +2271,7 @@ Function,-,subghz_keystore_raw_encrypted_save,_Bool,"const char*, const char*, u Function,-,subghz_keystore_raw_get_data,_Bool,"const char*, size_t, uint8_t*, size_t" Function,-,subghz_keystore_save,_Bool,"SubGhzKeystore*, const char*, uint8_t*" Function,+,subghz_protocol_blocks_add_bit,void,"SubGhzBlockDecoder*, uint8_t" +Function,+,subghz_protocol_blocks_add_bytes,uint8_t,"const uint8_t[], size_t" Function,+,subghz_protocol_blocks_crc16,uint16_t,"const uint8_t[], unsigned, uint16_t, uint16_t" Function,+,subghz_protocol_blocks_crc16lsb,uint16_t,"const uint8_t[], unsigned, uint16_t, uint16_t" Function,+,subghz_protocol_blocks_crc4,uint8_t,"const uint8_t[], unsigned, uint8_t, uint8_t" @@ -2284,8 +2285,11 @@ Function,+,subghz_protocol_blocks_get_upload,size_t,"uint8_t[], size_t, LevelDur Function,+,subghz_protocol_blocks_lfsr_digest16,uint16_t,"const uint8_t[], unsigned, uint16_t, uint16_t" Function,+,subghz_protocol_blocks_lfsr_digest8,uint8_t,"const uint8_t[], unsigned, uint8_t, uint8_t" Function,+,subghz_protocol_blocks_lfsr_digest8_reflect,uint8_t,"const uint8_t[], int, uint8_t, uint8_t" +Function,+,subghz_protocol_blocks_parity8,int,uint8_t +Function,+,subghz_protocol_blocks_parity_bytes,int,"const uint8_t[], size_t" Function,+,subghz_protocol_blocks_reverse_key,uint64_t,"uint64_t, uint8_t" Function,+,subghz_protocol_blocks_set_bit_array,void,"_Bool, uint8_t[], size_t, size_t" +Function,+,subghz_protocol_blocks_xor_bytes,uint8_t,"const uint8_t[], size_t" Function,-,subghz_protocol_decoder_base_deserialize,_Bool,"SubGhzProtocolDecoderBase*, FlipperFormat*" Function,+,subghz_protocol_decoder_base_get_hash_data,uint8_t,SubGhzProtocolDecoderBase* Function,+,subghz_protocol_decoder_base_get_string,_Bool,"SubGhzProtocolDecoderBase*, FuriString*" diff --git a/lib/subghz/blocks/math.c b/lib/subghz/blocks/math.c index 588d4effc04..d2b8e3d1145 100644 --- a/lib/subghz/blocks/math.c +++ b/lib/subghz/blocks/math.c @@ -216,4 +216,34 @@ uint16_t subghz_protocol_blocks_lfsr_digest16( } } return sum; +} + +uint8_t subghz_protocol_blocks_add_bytes(uint8_t const message[], size_t num_bytes) { + int result = 0; + for(size_t i = 0; i < num_bytes; ++i) { + result += message[i]; + } + return (uint8_t)result; +} + +int subghz_protocol_blocks_parity8(uint8_t byte) { + byte ^= byte >> 4; + byte &= 0xf; + return (0x6996 >> byte) & 1; +} + +int subghz_protocol_blocks_parity_bytes(uint8_t const message[], size_t num_bytes) { + int result = 0; + for(size_t i = 0; i < num_bytes; ++i) { + result ^= subghz_protocol_blocks_parity8(message[i]); + } + return result; +} + +uint8_t subghz_protocol_blocks_xor_bytes(uint8_t const message[], size_t num_bytes) { + uint8_t result = 0; + for(size_t i = 0; i < num_bytes; ++i) { + result ^= message[i]; + } + return result; } \ No newline at end of file diff --git a/lib/subghz/blocks/math.h b/lib/subghz/blocks/math.h index 275889484db..8cddf4c0bce 100644 --- a/lib/subghz/blocks/math.h +++ b/lib/subghz/blocks/math.h @@ -161,6 +161,37 @@ uint16_t subghz_protocol_blocks_lfsr_digest16( uint16_t gen, uint16_t key); +/** + * Compute Addition of a number of bytes. + * @param message bytes of message data + * @param num_bytes number of bytes to sum + * @return summation value + **/ +uint8_t subghz_protocol_blocks_add_bytes(uint8_t const message[], size_t num_bytes); + +/** + * Compute bit parity of a single byte (8 bits). + * @param byte single byte to check + * @return 1 odd parity, 0 even parity + **/ +int subghz_protocol_blocks_parity8(uint8_t byte); + +/** + * Compute bit parity of a number of bytes. + * @param message bytes of message data + * @param num_bytes number of bytes to sum + * @return 1 odd parity, 0 even parity + **/ +int subghz_protocol_blocks_parity_bytes(uint8_t const message[], size_t num_bytes); + +/** + * Compute XOR (byte-wide parity) of a number of bytes. + * @param message bytes of message data + * @param num_bytes number of bytes to sum + * @return summation value, per bit-position 1 odd parity, 0 even parity + **/ +uint8_t subghz_protocol_blocks_xor_bytes(uint8_t const message[], size_t num_bytes); + #ifdef __cplusplus } #endif From 5c8df66b7c2d10c4e17c831ebf9852d5690205fc Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Wed, 26 Oct 2022 19:13:00 +0400 Subject: [PATCH 174/824] [FL-2927] SubGhz: add RAW Read threshold rssi (#1911) * SubGhz: add RAW Read threshold rssi * SubGhz: update indicator RSSI * SubGhz: fix record file Co-authored-by: Aleksandr Kutuzov --- .../subghz/scenes/subghz_scene_read_raw.c | 50 ++++++++++-- .../scenes/subghz_scene_receiver_config.c | 52 +++++++++++++ applications/main/subghz/subghz.c | 1 + applications/main/subghz/subghz_i.h | 3 + .../main/subghz/views/subghz_read_raw.c | 77 ++++++++++++++++--- .../main/subghz/views/subghz_read_raw.h | 7 +- firmware/targets/f7/api_symbols.csv | 1 + lib/subghz/protocols/raw.c | 12 ++- lib/subghz/protocols/raw.h | 7 ++ 9 files changed, 192 insertions(+), 18 deletions(-) diff --git a/applications/main/subghz/scenes/subghz_scene_read_raw.c b/applications/main/subghz/scenes/subghz_scene_read_raw.c index 5dacefd6600..8ac9bf5ba9c 100644 --- a/applications/main/subghz/scenes/subghz_scene_read_raw.c +++ b/applications/main/subghz/scenes/subghz_scene_read_raw.c @@ -6,6 +6,7 @@ #define RAW_FILE_NAME "Raw_signal_" #define TAG "SubGhzSceneReadRAW" +#define RAW_THRESHOLD_RSSI_LOW_COUNT 10 bool subghz_scene_read_raw_update_filename(SubGhz* subghz) { bool ret = false; @@ -72,24 +73,33 @@ void subghz_scene_read_raw_on_enter(void* context) { switch(subghz->txrx->rx_key_state) { case SubGhzRxKeyStateBack: - subghz_read_raw_set_status(subghz->subghz_read_raw, SubGhzReadRAWStatusIDLE, ""); + subghz_read_raw_set_status( + subghz->subghz_read_raw, SubGhzReadRAWStatusIDLE, "", subghz->txrx->raw_threshold_rssi); break; case SubGhzRxKeyStateRAWLoad: path_extract_filename(subghz->file_path, file_name, true); subghz_read_raw_set_status( subghz->subghz_read_raw, SubGhzReadRAWStatusLoadKeyTX, - furi_string_get_cstr(file_name)); + furi_string_get_cstr(file_name), + subghz->txrx->raw_threshold_rssi); subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE; break; case SubGhzRxKeyStateRAWSave: path_extract_filename(subghz->file_path, file_name, true); subghz_read_raw_set_status( - subghz->subghz_read_raw, SubGhzReadRAWStatusSaveKey, furi_string_get_cstr(file_name)); + subghz->subghz_read_raw, + SubGhzReadRAWStatusSaveKey, + furi_string_get_cstr(file_name), + subghz->txrx->raw_threshold_rssi); subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE; break; default: - subghz_read_raw_set_status(subghz->subghz_read_raw, SubGhzReadRAWStatusStart, ""); + subghz_read_raw_set_status( + subghz->subghz_read_raw, + SubGhzReadRAWStatusStart, + "", + subghz->txrx->raw_threshold_rssi); subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE; break; } @@ -273,7 +283,7 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { if(subghz->txrx->rx_key_state != SubGhzRxKeyStateIDLE) { scene_manager_next_scene(subghz->scene_manager, SubGhzSceneNeedSaving); } else { - //subghz_get_preset_name(subghz, subghz->error_str); + subghz->txrx->raw_threshold_rssi_low_count = RAW_THRESHOLD_RSSI_LOW_COUNT; if(subghz_protocol_raw_save_to_file_init( (SubGhzProtocolDecoderRAW*)subghz->txrx->decoder_result, RAW_FILE_NAME, @@ -319,7 +329,35 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { subghz->subghz_read_raw, subghz_protocol_raw_get_sample_write( (SubGhzProtocolDecoderRAW*)subghz->txrx->decoder_result)); - subghz_read_raw_add_data_rssi(subghz->subghz_read_raw, furi_hal_subghz_get_rssi()); + + float rssi = furi_hal_subghz_get_rssi(); + + if(subghz->txrx->raw_threshold_rssi == SUBGHZ_RAW_TRESHOLD_MIN) { + subghz_read_raw_add_data_rssi(subghz->subghz_read_raw, rssi, true); + subghz_protocol_raw_save_to_file_pause( + (SubGhzProtocolDecoderRAW*)subghz->txrx->decoder_result, false); + } else { + if(rssi < subghz->txrx->raw_threshold_rssi) { + subghz->txrx->raw_threshold_rssi_low_count++; + if(subghz->txrx->raw_threshold_rssi_low_count > RAW_THRESHOLD_RSSI_LOW_COUNT) { + subghz->txrx->raw_threshold_rssi_low_count = RAW_THRESHOLD_RSSI_LOW_COUNT; + } + subghz_read_raw_add_data_rssi(subghz->subghz_read_raw, rssi, false); + } else { + subghz->txrx->raw_threshold_rssi_low_count = 0; + } + + if(subghz->txrx->raw_threshold_rssi_low_count == RAW_THRESHOLD_RSSI_LOW_COUNT) { + subghz_read_raw_add_data_rssi(subghz->subghz_read_raw, rssi, false); + subghz_protocol_raw_save_to_file_pause( + (SubGhzProtocolDecoderRAW*)subghz->txrx->decoder_result, true); + } else { + subghz_read_raw_add_data_rssi(subghz->subghz_read_raw, rssi, true); + subghz_protocol_raw_save_to_file_pause( + (SubGhzProtocolDecoderRAW*)subghz->txrx->decoder_result, false); + } + } + break; case SubGhzNotificationStateTx: notification_message(subghz->notifications, &sequence_blink_magenta_10); diff --git a/applications/main/subghz/scenes/subghz_scene_receiver_config.c b/applications/main/subghz/scenes/subghz_scene_receiver_config.c index 5f022d6e153..fd42829b5a0 100644 --- a/applications/main/subghz/scenes/subghz_scene_receiver_config.c +++ b/applications/main/subghz/scenes/subghz_scene_receiver_config.c @@ -1,10 +1,41 @@ #include "../subghz_i.h" +#include enum SubGhzSettingIndex { SubGhzSettingIndexFrequency, SubGhzSettingIndexHopping, SubGhzSettingIndexModulation, SubGhzSettingIndexLock, + SubGhzSettingIndexRAWThesholdRSSI, +}; + +#define RAW_THRESHOLD_RSSI_COUNT 11 +const char* const raw_theshold_rssi_text[RAW_THRESHOLD_RSSI_COUNT] = { + "-----", + "-85.0", + "-80.0", + "-75.0", + "-70.0", + "-65.0", + "-60.0", + "-55.0", + "-50.0", + "-45.0", + "-40.0", + +}; +const float raw_theshold_rssi_value[RAW_THRESHOLD_RSSI_COUNT] = { + -90.0f, + -85.0f, + -80.0f, + -75.0f, + -70.0f, + -65.0f, + -60.0f, + -55.0f, + -50.0f, + -45.0f, + -40.0f, }; #define HOPPING_COUNT 2 @@ -136,6 +167,14 @@ static void subghz_scene_receiver_config_set_hopping_running(VariableItem* item) subghz->txrx->hopper_state = hopping_value[index]; } +static void subghz_scene_receiver_config_set_raw_threshold_rssi(VariableItem* item) { + SubGhz* subghz = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + variable_item_set_current_value_text(item, raw_theshold_rssi_text[index]); + subghz->txrx->raw_threshold_rssi = raw_theshold_rssi_value[index]; +} + static void subghz_scene_receiver_config_var_list_enter_callback(void* context, uint32_t index) { furi_assert(context); SubGhz* subghz = context; @@ -204,6 +243,19 @@ void subghz_scene_receiver_config_on_enter(void* context) { subghz_scene_receiver_config_var_list_enter_callback, subghz); } + if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAW) == + SubGhzCustomEventManagerSet) { + item = variable_item_list_add( + subghz->variable_item_list, + "RSSI Threshold:", + RAW_THRESHOLD_RSSI_COUNT, + subghz_scene_receiver_config_set_raw_threshold_rssi, + subghz); + value_index = value_index_float( + subghz->txrx->raw_threshold_rssi, raw_theshold_rssi_value, RAW_THRESHOLD_RSSI_COUNT); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, raw_theshold_rssi_text[value_index]); + } view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdVariableItemList); } diff --git a/applications/main/subghz/subghz.c b/applications/main/subghz/subghz.c index ba70f36d1b2..df5a76525d7 100644 --- a/applications/main/subghz/subghz.c +++ b/applications/main/subghz/subghz.c @@ -178,6 +178,7 @@ SubGhz* subghz_alloc() { subghz->txrx->txrx_state = SubGhzTxRxStateSleep; subghz->txrx->hopper_state = SubGhzHopperStateOFF; subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE; + subghz->txrx->raw_threshold_rssi = SUBGHZ_RAW_TRESHOLD_MIN; subghz->txrx->history = subghz_history_alloc(); subghz->txrx->worker = subghz_worker_alloc(); subghz->txrx->fff_data = flipper_format_string_alloc(); diff --git a/applications/main/subghz/subghz_i.h b/applications/main/subghz/subghz_i.h index 284f7ccf532..09830ba0567 100644 --- a/applications/main/subghz/subghz_i.h +++ b/applications/main/subghz/subghz_i.h @@ -54,6 +54,9 @@ struct SubGhzTxRx { uint8_t hopper_timeout; uint8_t hopper_idx_frequency; SubGhzRxKeyState rx_key_state; + + float raw_threshold_rssi; + uint8_t raw_threshold_rssi_low_count; }; typedef struct SubGhzTxRx SubGhzTxRx; diff --git a/applications/main/subghz/views/subghz_read_raw.c b/applications/main/subghz/views/subghz_read_raw.c index 2d951b11afd..6120a210b51 100644 --- a/applications/main/subghz/views/subghz_read_raw.c +++ b/applications/main/subghz/views/subghz_read_raw.c @@ -23,10 +23,12 @@ typedef struct { FuriString* sample_write; FuriString* file_name; uint8_t* rssi_history; + uint8_t rssi_curret; bool rssi_history_end; uint8_t ind_write; uint8_t ind_sin; SubGhzReadRAWStatus status; + float raw_threshold_rssi; } SubGhzReadRAWModel; void subghz_read_raw_set_callback( @@ -54,21 +56,27 @@ void subghz_read_raw_add_data_statusbar( true); } -void subghz_read_raw_add_data_rssi(SubGhzReadRAW* instance, float rssi) { +void subghz_read_raw_add_data_rssi(SubGhzReadRAW* instance, float rssi, bool trace) { furi_assert(instance); uint8_t u_rssi = 0; - if(rssi < -90) { + if(rssi < SUBGHZ_RAW_TRESHOLD_MIN) { u_rssi = 0; } else { - u_rssi = (uint8_t)((rssi + 90) / 2.7); + u_rssi = (uint8_t)((rssi - SUBGHZ_RAW_TRESHOLD_MIN) / 2.7); } with_view_model( instance->view, SubGhzReadRAWModel * model, { - model->rssi_history[model->ind_write++] = u_rssi; + model->rssi_curret = u_rssi; + if(trace) { + model->rssi_history[model->ind_write++] = u_rssi; + } else { + model->rssi_history[model->ind_write] = u_rssi; + } + if(model->ind_write > SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE) { model->rssi_history_end = true; model->ind_write = 0; @@ -187,24 +195,53 @@ void subghz_read_raw_draw_scale(Canvas* canvas, SubGhzReadRAWModel* model) { void subghz_read_raw_draw_rssi(Canvas* canvas, SubGhzReadRAWModel* model) { int ind = 0; int base = 0; + uint8_t width = 2; if(model->rssi_history_end == false) { for(int i = model->ind_write; i >= 0; i--) { canvas_draw_line(canvas, i, 47, i, 47 - model->rssi_history[i]); } + canvas_draw_line( + canvas, model->ind_write + 1, 47, model->ind_write + 1, 47 - model->rssi_curret); if(model->ind_write > 3) { - canvas_draw_line(canvas, model->ind_write, 47, model->ind_write, 13); + canvas_draw_line( + canvas, model->ind_write - 1, 47, model->ind_write - 1, 47 - model->rssi_curret); + + for(uint8_t i = 13; i < 47; i += width * 2) { + canvas_draw_line(canvas, model->ind_write, i, model->ind_write, i + width); + } canvas_draw_line(canvas, model->ind_write - 2, 12, model->ind_write + 2, 12); canvas_draw_line(canvas, model->ind_write - 1, 13, model->ind_write + 1, 13); } } else { + int i = 0; base = SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE - model->ind_write; - for(int i = SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE; i >= 0; i--) { + for(i = SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE; i > 0; i--) { ind = i - base; if(ind < 0) ind += SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE; canvas_draw_line(canvas, i, 47, i, 47 - model->rssi_history[ind]); } + canvas_draw_line( - canvas, SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE, 47, SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE, 13); + canvas, + SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE - 1, + 47, + SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE - 1, + 47 - model->rssi_curret); + canvas_draw_line( + canvas, + SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE + 1, + 47, + SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE + 1, + 47 - model->rssi_curret); + + for(uint8_t i = 13; i < 47; i += width * 2) { + canvas_draw_line( + canvas, + SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE, + i, + SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE, + i + width); + } canvas_draw_line( canvas, SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE - 2, @@ -220,6 +257,24 @@ void subghz_read_raw_draw_rssi(Canvas* canvas, SubGhzReadRAWModel* model) { } } +void subghz_read_raw_draw_threshold_rssi(Canvas* canvas, SubGhzReadRAWModel* model) { + uint8_t x = 118; + uint8_t y = 48; + + if(model->raw_threshold_rssi > SUBGHZ_RAW_TRESHOLD_MIN) { + uint8_t x = 118; + y -= (uint8_t)((model->raw_threshold_rssi - SUBGHZ_RAW_TRESHOLD_MIN) / 2.7); + + uint8_t width = 3; + for(uint8_t i = 0; i < x; i += width * 2) { + canvas_draw_line(canvas, i, y, i + width, y); + } + } + canvas_draw_line(canvas, x, y - 2, x, y + 2); + canvas_draw_line(canvas, x - 1, y - 1, x - 1, y + 1); + canvas_draw_dot(canvas, x - 2, y); +} + void subghz_read_raw_draw(Canvas* canvas, SubGhzReadRAWModel* model) { uint8_t graphics_mode = 1; canvas_set_color(canvas, ColorBlack); @@ -278,8 +333,9 @@ void subghz_read_raw_draw(Canvas* canvas, SubGhzReadRAWModel* model) { } else { subghz_read_raw_draw_rssi(canvas, model); subghz_read_raw_draw_scale(canvas, model); + subghz_read_raw_draw_threshold_rssi(canvas, model); canvas_set_font_direction(canvas, CanvasDirectionBottomToTop); - canvas_draw_str(canvas, 126, 40, "RSSI"); + canvas_draw_str(canvas, 128, 40, "RSSI"); canvas_set_font_direction(canvas, CanvasDirectionLeftToRight); } } @@ -433,7 +489,8 @@ bool subghz_read_raw_input(InputEvent* event, void* context) { void subghz_read_raw_set_status( SubGhzReadRAW* instance, SubGhzReadRAWStatus status, - const char* file_name) { + const char* file_name, + float raw_threshold_rssi) { furi_assert(instance); switch(status) { @@ -447,6 +504,7 @@ void subghz_read_raw_set_status( model->ind_write = 0; furi_string_reset(model->file_name); furi_string_set(model->sample_write, "0 spl."); + model->raw_threshold_rssi = raw_threshold_rssi; }, true); break; @@ -536,6 +594,7 @@ SubGhzReadRAW* subghz_read_raw_alloc() { model->sample_write = furi_string_alloc(); model->file_name = furi_string_alloc(); model->rssi_history = malloc(SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE * sizeof(uint8_t)); + model->raw_threshold_rssi = -127.0f; }, true); diff --git a/applications/main/subghz/views/subghz_read_raw.h b/applications/main/subghz/views/subghz_read_raw.h index 1d4bb7dc0c9..bc871192395 100644 --- a/applications/main/subghz/views/subghz_read_raw.h +++ b/applications/main/subghz/views/subghz_read_raw.h @@ -3,6 +3,8 @@ #include #include "../helpers/subghz_custom_event.h" +#define SUBGHZ_RAW_TRESHOLD_MIN -90.0f + typedef struct SubGhzReadRAW SubGhzReadRAW; typedef void (*SubGhzReadRAWCallback)(SubGhzCustomEvent event, void* context); @@ -40,11 +42,12 @@ void subghz_read_raw_stop_send(SubGhzReadRAW* instance); void subghz_read_raw_update_sin(SubGhzReadRAW* instance); -void subghz_read_raw_add_data_rssi(SubGhzReadRAW* instance, float rssi); +void subghz_read_raw_add_data_rssi(SubGhzReadRAW* instance, float rssi, bool trace); void subghz_read_raw_set_status( SubGhzReadRAW* instance, SubGhzReadRAWStatus status, - const char* file_name); + const char* file_name, + float raw_threshold_rssi); View* subghz_read_raw_get_view(SubGhzReadRAW* subghz_static); diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 17a8a675a10..8bad0b83023 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -2310,6 +2310,7 @@ Function,+,subghz_protocol_raw_file_encoder_worker_set_callback_end,void,"SubGhz Function,+,subghz_protocol_raw_gen_fff_data,void,"FlipperFormat*, const char*" Function,+,subghz_protocol_raw_get_sample_write,size_t,SubGhzProtocolDecoderRAW* Function,+,subghz_protocol_raw_save_to_file_init,_Bool,"SubGhzProtocolDecoderRAW*, const char*, SubGhzRadioPreset*" +Function,+,subghz_protocol_raw_save_to_file_pause,void,"SubGhzProtocolDecoderRAW*, _Bool" Function,+,subghz_protocol_raw_save_to_file_stop,void,SubGhzProtocolDecoderRAW* Function,+,subghz_receiver_alloc_init,SubGhzReceiver*,SubGhzEnvironment* Function,+,subghz_receiver_decode,void,"SubGhzReceiver*, _Bool, uint32_t" diff --git a/lib/subghz/protocols/raw.c b/lib/subghz/protocols/raw.c index b8e93c3d57a..b639c93b924 100644 --- a/lib/subghz/protocols/raw.c +++ b/lib/subghz/protocols/raw.c @@ -32,6 +32,7 @@ struct SubGhzProtocolDecoderRAW { FuriString* file_name; size_t sample_write; bool last_level; + bool pause; }; struct SubGhzProtocolEncoderRAW { @@ -158,6 +159,7 @@ bool subghz_protocol_raw_save_to_file_init( instance->upload_raw = malloc(SUBGHZ_DOWNLOAD_MAX_SIZE * sizeof(int32_t)); instance->file_is_open = RAWFileIsOpenWrite; instance->sample_write = 0; + instance->pause = false; init = true; } while(0); @@ -199,6 +201,14 @@ void subghz_protocol_raw_save_to_file_stop(SubGhzProtocolDecoderRAW* instance) { instance->file_is_open = RAWFileIsOpenClose; } +void subghz_protocol_raw_save_to_file_pause(SubGhzProtocolDecoderRAW* instance, bool pause) { + furi_assert(instance); + + if(instance->pause != pause) { + instance->pause = pause; + } +} + size_t subghz_protocol_raw_get_sample_write(SubGhzProtocolDecoderRAW* instance) { return instance->sample_write + instance->ind_write; } @@ -234,7 +244,7 @@ void subghz_protocol_decoder_raw_feed(void* context, bool level, uint32_t durati furi_assert(context); SubGhzProtocolDecoderRAW* instance = context; - if(instance->upload_raw != NULL) { + if(!instance->pause && (instance->upload_raw != NULL)) { if(duration > subghz_protocol_raw_const.te_short) { if(instance->last_level != level) { instance->last_level = (level ? true : false); diff --git a/lib/subghz/protocols/raw.h b/lib/subghz/protocols/raw.h index 96c77553a1e..44c7faec5ff 100644 --- a/lib/subghz/protocols/raw.h +++ b/lib/subghz/protocols/raw.h @@ -103,6 +103,13 @@ void subghz_protocol_encoder_raw_free(void* context); */ void subghz_protocol_encoder_raw_stop(void* context); +/** + * pause writing to flash. + * @param context Pointer to a SubGhzProtocolEncoderRAW instance + * @param pause pause writing + */ +void subghz_protocol_raw_save_to_file_pause(SubGhzProtocolDecoderRAW* instance, bool pause); + /** * Set callback on completion of file transfer. * @param instance Pointer to a SubGhzProtocolEncoderRAW instance From a8edb41eae75c7796e4f16f31b0fa8c5d6440e01 Mon Sep 17 00:00:00 2001 From: hedger Date: Wed, 26 Oct 2022 19:18:06 +0400 Subject: [PATCH 175/824] fbt, docs: typo fixes; vscode: fixed deprecated target names (#1926) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- .vscode/example/tasks.json | 4 ++-- documentation/fbt.md | 2 +- firmware.scons | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.vscode/example/tasks.json b/.vscode/example/tasks.json index 3946d05ccbe..9baaf97b44a 100644 --- a/.vscode/example/tasks.json +++ b/.vscode/example/tasks.json @@ -109,13 +109,13 @@ "label": "[Debug] Build FAPs", "group": "build", "type": "shell", - "command": "./fbt plugin_dist" + "command": "./fbt fap_dist" }, { "label": "[Release] Build FAPs", "group": "build", "type": "shell", - "command": "./fbt COMPACT=1 DEBUG=0 plugin_dist" + "command": "./fbt COMPACT=1 DEBUG=0 fap_dist" }, { "label": "[Debug] Launch App on Flipper", diff --git a/documentation/fbt.md b/documentation/fbt.md index 659170519d4..2bf9ea28e05 100644 --- a/documentation/fbt.md +++ b/documentation/fbt.md @@ -54,7 +54,7 @@ To run cleanup (think of `make clean`) for specified targets, add `-c` option. - `blackmagic` - debug firmware with Blackmagic probe (WiFi dev board) - `openocd` - just start OpenOCD - `get_blackmagic` - output blackmagic address in gdb remote format. Useful for IDE integration -- `get_stlink` - output serial numbers for attached STLink probes. Ued for `OPENOCD_ADAPTER_SERIAL=...`. +- `get_stlink` - output serial numbers for attached STLink probes. Used for specifying an adapter with `OPENOCD_ADAPTER_SERIAL=...`. - `lint`, `format` - run clang-format on C source code to check and reformat it according to `.clang-format` specs - `lint_py`, `format_py` - run [black](https://black.readthedocs.io/en/stable/index.html) on Python source code, build system files & application manifests - `cli` - start Flipper CLI session over USB diff --git a/firmware.scons b/firmware.scons index d0b89a763fb..d501996b334 100644 --- a/firmware.scons +++ b/firmware.scons @@ -292,9 +292,10 @@ Alias(fwenv["FIRMWARE_BUILD_CFG"] + "_all", fw_artifacts) if fwenv["IS_BASE_FIRMWARE"]: sdk_source = fwenv.SDKPrebuilder( "sdk_origin", + # Deps on root SDK headers and generated files (fwenv["SDK_HEADERS"], fwenv["FW_ASSETS_HEADERS"]), ) - # Extra deps for root headers and generated files + # Extra deps on headers included in deeper levels Depends(sdk_source, fwenv.ProcessSdkDepends("sdk_origin.d")) fwenv["SDK_DIR"] = fwenv.Dir("sdk") From ebaa84b0c27548dd4df1a9584fe22ee797a68331 Mon Sep 17 00:00:00 2001 From: "Vasyl \"vk\" Kaigorodov" Date: Wed, 26 Oct 2022 20:35:49 +0200 Subject: [PATCH 176/824] Support for setting all screen orientations (#1928) * Support for setting all screen orientations * Gui: add flipped orientation to view * Gui: correct assert conditions in gui_add_view_port Co-authored-by: Aleksandr Kutuzov --- applications/services/gui/canvas.c | 35 ++++++++-- applications/services/gui/canvas.h | 2 + applications/services/gui/gui.c | 4 +- applications/services/gui/view.h | 2 + applications/services/gui/view_dispatcher.c | 10 ++- applications/services/gui/view_port.c | 72 +++++++++++++++++++-- applications/services/gui/view_port.h | 2 + 7 files changed, 111 insertions(+), 16 deletions(-) diff --git a/applications/services/gui/canvas.c b/applications/services/gui/canvas.c index 1e275aafb43..a2979d56b13 100644 --- a/applications/services/gui/canvas.c +++ b/applications/services/gui/canvas.c @@ -371,16 +371,39 @@ void canvas_set_bitmap_mode(Canvas* canvas, bool alpha) { void canvas_set_orientation(Canvas* canvas, CanvasOrientation orientation) { furi_assert(canvas); if(canvas->orientation != orientation) { - canvas->orientation = orientation; - if(canvas->orientation == CanvasOrientationHorizontal) { - FURI_SWAP(canvas->width, canvas->height); + switch(orientation) { + case CanvasOrientationHorizontal: + if(canvas->orientation == CanvasOrientationVertical || + canvas->orientation == CanvasOrientationVerticalFlip) { + FURI_SWAP(canvas->width, canvas->height); + } u8g2_SetDisplayRotation(&canvas->fb, U8G2_R0); - } else if(canvas->orientation == CanvasOrientationVertical) { - FURI_SWAP(canvas->width, canvas->height); + break; + case CanvasOrientationHorizontalFlip: + if(canvas->orientation == CanvasOrientationVertical || + canvas->orientation == CanvasOrientationVerticalFlip) { + FURI_SWAP(canvas->width, canvas->height); + } + u8g2_SetDisplayRotation(&canvas->fb, U8G2_R2); + break; + case CanvasOrientationVertical: + if(canvas->orientation == CanvasOrientationHorizontal || + canvas->orientation == CanvasOrientationHorizontalFlip) { + FURI_SWAP(canvas->width, canvas->height); + }; u8g2_SetDisplayRotation(&canvas->fb, U8G2_R3); - } else { + break; + case CanvasOrientationVerticalFlip: + if(canvas->orientation == CanvasOrientationHorizontal || + canvas->orientation == CanvasOrientationHorizontalFlip) { + FURI_SWAP(canvas->width, canvas->height); + } + u8g2_SetDisplayRotation(&canvas->fb, U8G2_R1); + break; + default: furi_assert(0); } + canvas->orientation = orientation; } } diff --git a/applications/services/gui/canvas.h b/applications/services/gui/canvas.h index 49bbd7d6875..a67e58494a5 100644 --- a/applications/services/gui/canvas.h +++ b/applications/services/gui/canvas.h @@ -42,7 +42,9 @@ typedef enum { /** Canvas Orientation */ typedef enum { CanvasOrientationHorizontal, + CanvasOrientationHorizontalFlip, CanvasOrientationVertical, + CanvasOrientationVerticalFlip, } CanvasOrientation; /** Font Direction */ diff --git a/applications/services/gui/gui.c b/applications/services/gui/gui.c index 8c5ed91a958..42712ed9065 100644 --- a/applications/services/gui/gui.c +++ b/applications/services/gui/gui.c @@ -322,7 +322,9 @@ void gui_add_view_port(Gui* gui, ViewPort* view_port, GuiLayer layer) { furi_check(layer < GuiLayerMAX); // Only fullscreen supports Vertical orientation for now furi_assert( - (layer == GuiLayerFullscreen) || (view_port->orientation != ViewPortOrientationVertical)); + (layer == GuiLayerFullscreen) || + ((view_port->orientation != ViewPortOrientationVertical) && + (view_port->orientation != ViewPortOrientationVerticalFlip))); gui_lock(gui); // Verify that view port is not yet added diff --git a/applications/services/gui/view.h b/applications/services/gui/view.h index b40f8ded5a2..7a2003a63bc 100644 --- a/applications/services/gui/view.h +++ b/applications/services/gui/view.h @@ -25,7 +25,9 @@ extern "C" { typedef enum { ViewOrientationHorizontal, + ViewOrientationHorizontalFlip, ViewOrientationVertical, + ViewOrientationVerticalFlip, } ViewOrientation; /** View, anonymous type */ diff --git a/applications/services/gui/view_dispatcher.c b/applications/services/gui/view_dispatcher.c index 6e4ce836011..1736558cbbf 100644 --- a/applications/services/gui/view_dispatcher.c +++ b/applications/services/gui/view_dispatcher.c @@ -331,10 +331,16 @@ void view_dispatcher_set_current_view(ViewDispatcher* view_dispatcher, View* vie view_dispatcher->current_view = view; // Dispatch view enter event if(view_dispatcher->current_view) { - if(view->orientation == ViewOrientationVertical) + if(view->orientation == ViewOrientationVertical) { view_port_set_orientation(view_dispatcher->view_port, ViewPortOrientationVertical); - else if(view->orientation == ViewOrientationHorizontal) + } else if(view->orientation == ViewOrientationVerticalFlip) { + view_port_set_orientation(view_dispatcher->view_port, ViewPortOrientationVerticalFlip); + } else if(view->orientation == ViewOrientationHorizontal) { view_port_set_orientation(view_dispatcher->view_port, ViewPortOrientationHorizontal); + } else if(view->orientation == ViewOrientationHorizontalFlip) { + view_port_set_orientation( + view_dispatcher->view_port, ViewPortOrientationHorizontalFlip); + } view_enter(view_dispatcher->current_view); view_port_enabled_set(view_dispatcher->view_port, true); view_port_update(view_dispatcher->view_port); diff --git a/applications/services/gui/view_port.c b/applications/services/gui/view_port.c index 8069a02e5a2..baa8f7bd2d4 100644 --- a/applications/services/gui/view_port.c +++ b/applications/services/gui/view_port.c @@ -7,7 +7,7 @@ // TODO add mutex to view_port ops -static void view_port_rotate_buttons(InputEvent* event) { +static void view_port_remap_buttons_vertical(InputEvent* event) { switch(event->key) { case InputKeyUp: event->key = InputKeyRight; @@ -26,12 +26,59 @@ static void view_port_rotate_buttons(InputEvent* event) { } } +static void view_port_remap_buttons_vertical_flip(InputEvent* event) { + switch(event->key) { + case InputKeyUp: + event->key = InputKeyLeft; + break; + case InputKeyDown: + event->key = InputKeyRight; + break; + case InputKeyRight: + event->key = InputKeyUp; + break; + case InputKeyLeft: + event->key = InputKeyDown; + break; + default: + break; + } +} + +static void view_port_remap_buttons_horizontal_flip(InputEvent* event) { + switch(event->key) { + case InputKeyUp: + event->key = InputKeyDown; + break; + case InputKeyDown: + event->key = InputKeyUp; + break; + case InputKeyRight: + event->key = InputKeyLeft; + break; + case InputKeyLeft: + event->key = InputKeyRight; + break; + default: + break; + } +} + static void view_port_setup_canvas_orientation(const ViewPort* view_port, Canvas* canvas) { - if(view_port->orientation == ViewPortOrientationHorizontal) { - canvas_set_orientation(canvas, CanvasOrientationHorizontal); - } else if(view_port->orientation == ViewPortOrientationVertical) { + switch(view_port->orientation) { + case ViewPortOrientationHorizontalFlip: + canvas_set_orientation(canvas, CanvasOrientationHorizontalFlip); + break; + case ViewPortOrientationVertical: canvas_set_orientation(canvas, CanvasOrientationVertical); - } + break; + case ViewPortOrientationVerticalFlip: + canvas_set_orientation(canvas, CanvasOrientationVerticalFlip); + break; + default: + canvas_set_orientation(canvas, CanvasOrientationHorizontal); + break; + }; } ViewPort* view_port_alloc() { @@ -122,8 +169,19 @@ void view_port_input(ViewPort* view_port, InputEvent* event) { furi_check(view_port->gui); if(view_port->input_callback) { - if(view_port_get_orientation(view_port) == ViewPortOrientationVertical) { - view_port_rotate_buttons(event); + ViewPortOrientation orientation = view_port_get_orientation(view_port); + switch(orientation) { + case ViewPortOrientationHorizontalFlip: + view_port_remap_buttons_horizontal_flip(event); + break; + case ViewPortOrientationVertical: + view_port_remap_buttons_vertical(event); + break; + case ViewPortOrientationVerticalFlip: + view_port_remap_buttons_vertical_flip(event); + break; + default: + break; } view_port->input_callback(event, view_port->input_callback_context); } diff --git a/applications/services/gui/view_port.h b/applications/services/gui/view_port.h index 96f2798e21b..169681ac005 100644 --- a/applications/services/gui/view_port.h +++ b/applications/services/gui/view_port.h @@ -16,7 +16,9 @@ typedef struct ViewPort ViewPort; typedef enum { ViewPortOrientationHorizontal, + ViewPortOrientationHorizontalFlip, ViewPortOrientationVertical, + ViewPortOrientationVerticalFlip, } ViewPortOrientation; /** ViewPort Draw callback From aff99a72e84aac45b7bc6281d3072218b83f5498 Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Wed, 26 Oct 2022 23:40:13 +0400 Subject: [PATCH 177/824] SubGhz: fix variable types and CC1101 GPIO initialization optimization (#1931) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * SubGhz: fix variable types * SubGhz: CC1101 GPIO initialization optimization * SubGhz: return back gpio init * SubGhz: cleanup grammar in math and format doxygen comments Co-authored-by: あく --- applications/main/subghz/subghz_i.c | 2 +- firmware/targets/f7/api_symbols.csv | 25 +-- lib/subghz/blocks/decoder.c | 10 + lib/subghz/blocks/decoder.h | 11 ++ lib/subghz/blocks/math.c | 115 ++++++------ lib/subghz/blocks/math.h | 282 +++++++++++++++------------- 6 files changed, 240 insertions(+), 205 deletions(-) diff --git a/applications/main/subghz/subghz_i.c b/applications/main/subghz/subghz_i.c index ac1036d0551..beefd802433 100644 --- a/applications/main/subghz/subghz_i.c +++ b/applications/main/subghz/subghz_i.c @@ -102,8 +102,8 @@ static bool subghz_tx(SubGhz* subghz, uint32_t frequency) { furi_assert(subghz->txrx->txrx_state != SubGhzTxRxStateSleep); furi_hal_subghz_idle(); furi_hal_subghz_set_frequency_and_path(frequency); + furi_hal_gpio_write(&gpio_cc1101_g0, false); furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); - furi_hal_gpio_write(&gpio_cc1101_g0, true); bool ret = furi_hal_subghz_tx(); subghz->txrx->txrx_state = SubGhzTxRxStateTx; return ret; diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 8bad0b83023..fd11dffe0e3 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,5.1,, +Version,+,6.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -2272,21 +2272,22 @@ Function,-,subghz_keystore_raw_get_data,_Bool,"const char*, size_t, uint8_t*, si Function,-,subghz_keystore_save,_Bool,"SubGhzKeystore*, const char*, uint8_t*" Function,+,subghz_protocol_blocks_add_bit,void,"SubGhzBlockDecoder*, uint8_t" Function,+,subghz_protocol_blocks_add_bytes,uint8_t,"const uint8_t[], size_t" -Function,+,subghz_protocol_blocks_crc16,uint16_t,"const uint8_t[], unsigned, uint16_t, uint16_t" -Function,+,subghz_protocol_blocks_crc16lsb,uint16_t,"const uint8_t[], unsigned, uint16_t, uint16_t" -Function,+,subghz_protocol_blocks_crc4,uint8_t,"const uint8_t[], unsigned, uint8_t, uint8_t" -Function,+,subghz_protocol_blocks_crc7,uint8_t,"const uint8_t[], unsigned, uint8_t, uint8_t" -Function,+,subghz_protocol_blocks_crc8,uint8_t,"const uint8_t[], unsigned, uint8_t, uint8_t" -Function,+,subghz_protocol_blocks_crc8le,uint8_t,"const uint8_t[], unsigned, uint8_t, uint8_t" +Function,+,subghz_protocol_blocks_add_to_128_bit,void,"SubGhzBlockDecoder*, uint8_t, uint64_t*" +Function,+,subghz_protocol_blocks_crc16,uint16_t,"const uint8_t[], size_t, uint16_t, uint16_t" +Function,+,subghz_protocol_blocks_crc16lsb,uint16_t,"const uint8_t[], size_t, uint16_t, uint16_t" +Function,+,subghz_protocol_blocks_crc4,uint8_t,"const uint8_t[], size_t, uint8_t, uint8_t" +Function,+,subghz_protocol_blocks_crc7,uint8_t,"const uint8_t[], size_t, uint8_t, uint8_t" +Function,+,subghz_protocol_blocks_crc8,uint8_t,"const uint8_t[], size_t, uint8_t, uint8_t" +Function,+,subghz_protocol_blocks_crc8le,uint8_t,"const uint8_t[], size_t, uint8_t, uint8_t" Function,+,subghz_protocol_blocks_get_bit_array,_Bool,"uint8_t[], size_t" Function,+,subghz_protocol_blocks_get_hash_data,uint8_t,"SubGhzBlockDecoder*, size_t" Function,+,subghz_protocol_blocks_get_parity,uint8_t,"uint64_t, uint8_t" Function,+,subghz_protocol_blocks_get_upload,size_t,"uint8_t[], size_t, LevelDuration*, size_t, uint32_t" -Function,+,subghz_protocol_blocks_lfsr_digest16,uint16_t,"const uint8_t[], unsigned, uint16_t, uint16_t" -Function,+,subghz_protocol_blocks_lfsr_digest8,uint8_t,"const uint8_t[], unsigned, uint8_t, uint8_t" -Function,+,subghz_protocol_blocks_lfsr_digest8_reflect,uint8_t,"const uint8_t[], int, uint8_t, uint8_t" -Function,+,subghz_protocol_blocks_parity8,int,uint8_t -Function,+,subghz_protocol_blocks_parity_bytes,int,"const uint8_t[], size_t" +Function,+,subghz_protocol_blocks_lfsr_digest16,uint16_t,"const uint8_t[], size_t, uint16_t, uint16_t" +Function,+,subghz_protocol_blocks_lfsr_digest8,uint8_t,"const uint8_t[], size_t, uint8_t, uint8_t" +Function,+,subghz_protocol_blocks_lfsr_digest8_reflect,uint8_t,"const uint8_t[], size_t, uint8_t, uint8_t" +Function,+,subghz_protocol_blocks_parity8,uint8_t,uint8_t +Function,+,subghz_protocol_blocks_parity_bytes,uint8_t,"const uint8_t[], size_t" Function,+,subghz_protocol_blocks_reverse_key,uint64_t,"uint64_t, uint8_t" Function,+,subghz_protocol_blocks_set_bit_array,void,"_Bool, uint8_t[], size_t, size_t" Function,+,subghz_protocol_blocks_xor_bytes,uint8_t,"const uint8_t[], size_t" diff --git a/lib/subghz/blocks/decoder.c b/lib/subghz/blocks/decoder.c index d2237f6c4ef..f491c87bfb5 100644 --- a/lib/subghz/blocks/decoder.c +++ b/lib/subghz/blocks/decoder.c @@ -7,6 +7,16 @@ void subghz_protocol_blocks_add_bit(SubGhzBlockDecoder* decoder, uint8_t bit) { decoder->decode_count_bit++; } +void subghz_protocol_blocks_add_to_128_bit( + SubGhzBlockDecoder* decoder, + uint8_t bit, + uint64_t* head_64_bit) { + if(++decoder->decode_count_bit > 64) { + (*head_64_bit) = ((*head_64_bit) << 1) | (decoder->decode_data >> 63); + } + decoder->decode_data = decoder->decode_data << 1 | bit; +} + uint8_t subghz_protocol_blocks_get_hash_data(SubGhzBlockDecoder* decoder, size_t len) { uint8_t hash = 0; uint8_t* p = (uint8_t*)&decoder->decode_data; diff --git a/lib/subghz/blocks/decoder.h b/lib/subghz/blocks/decoder.h index 25549fab3c1..a5e561e3517 100644 --- a/lib/subghz/blocks/decoder.h +++ b/lib/subghz/blocks/decoder.h @@ -24,6 +24,17 @@ struct SubGhzBlockDecoder { */ void subghz_protocol_blocks_add_bit(SubGhzBlockDecoder* decoder, uint8_t bit); +/** + * Add data to_128 bit when decoding. + * @param decoder Pointer to a SubGhzBlockDecoder instance + * @param head_64_bit Pointer to a head_64_bit + * @param bit data, 1bit + */ +void subghz_protocol_blocks_add_to_128_bit( + SubGhzBlockDecoder* decoder, + uint8_t bit, + uint64_t* head_64_bit); + /** * Getting the hash sum of the last randomly received parcel. * @param decoder Pointer to a SubGhzBlockDecoder instance diff --git a/lib/subghz/blocks/math.c b/lib/subghz/blocks/math.c index d2b8e3d1145..24202ad1c62 100644 --- a/lib/subghz/blocks/math.c +++ b/lib/subghz/blocks/math.c @@ -1,16 +1,16 @@ #include "math.h" -uint64_t subghz_protocol_blocks_reverse_key(uint64_t key, uint8_t count_bit) { - uint64_t key_reverse = 0; - for(uint8_t i = 0; i < count_bit; i++) { - key_reverse = key_reverse << 1 | bit_read(key, i); +uint64_t subghz_protocol_blocks_reverse_key(uint64_t key, uint8_t bit_count) { + uint64_t reverse_key = 0; + for(uint8_t i = 0; i < bit_count; i++) { + reverse_key = reverse_key << 1 | bit_read(key, i); } - return key_reverse; + return reverse_key; } -uint8_t subghz_protocol_blocks_get_parity(uint64_t key, uint8_t count_bit) { +uint8_t subghz_protocol_blocks_get_parity(uint64_t key, uint8_t bit_count) { uint8_t parity = 0; - for(uint8_t i = 0; i < count_bit; i++) { + for(uint8_t i = 0; i < bit_count; i++) { parity += bit_read(key, i); } return parity & 0x01; @@ -18,14 +18,14 @@ uint8_t subghz_protocol_blocks_get_parity(uint64_t key, uint8_t count_bit) { uint8_t subghz_protocol_blocks_crc4( uint8_t const message[], - unsigned nBytes, + size_t size, uint8_t polynomial, uint8_t init) { - unsigned remainder = init << 4; // LSBs are unused - unsigned poly = polynomial << 4; - unsigned bit; + uint8_t remainder = init << 4; // LSBs are unused + uint8_t poly = polynomial << 4; + uint8_t bit; - while(nBytes--) { + while(size--) { remainder ^= *message++; for(bit = 0; bit < 8; bit++) { if(remainder & 0x80) { @@ -40,16 +40,15 @@ uint8_t subghz_protocol_blocks_crc4( uint8_t subghz_protocol_blocks_crc7( uint8_t const message[], - unsigned nBytes, + size_t size, uint8_t polynomial, uint8_t init) { - unsigned remainder = init << 1; // LSB is unused - unsigned poly = polynomial << 1; - unsigned byte, bit; + uint8_t remainder = init << 1; // LSB is unused + uint8_t poly = polynomial << 1; - for(byte = 0; byte < nBytes; ++byte) { + for(size_t byte = 0; byte < size; ++byte) { remainder ^= message[byte]; - for(bit = 0; bit < 8; ++bit) { + for(uint8_t bit = 0; bit < 8; ++bit) { if(remainder & 0x80) { remainder = (remainder << 1) ^ poly; } else { @@ -62,15 +61,14 @@ uint8_t subghz_protocol_blocks_crc7( uint8_t subghz_protocol_blocks_crc8( uint8_t const message[], - unsigned nBytes, + size_t size, uint8_t polynomial, uint8_t init) { uint8_t remainder = init; - unsigned byte, bit; - for(byte = 0; byte < nBytes; ++byte) { + for(size_t byte = 0; byte < size; ++byte) { remainder ^= message[byte]; - for(bit = 0; bit < 8; ++bit) { + for(uint8_t bit = 0; bit < 8; ++bit) { if(remainder & 0x80) { remainder = (remainder << 1) ^ polynomial; } else { @@ -83,16 +81,15 @@ uint8_t subghz_protocol_blocks_crc8( uint8_t subghz_protocol_blocks_crc8le( uint8_t const message[], - unsigned nBytes, + size_t size, uint8_t polynomial, uint8_t init) { uint8_t remainder = subghz_protocol_blocks_reverse_key(init, 8); - unsigned byte, bit; polynomial = subghz_protocol_blocks_reverse_key(polynomial, 8); - for(byte = 0; byte < nBytes; ++byte) { + for(size_t byte = 0; byte < size; ++byte) { remainder ^= message[byte]; - for(bit = 0; bit < 8; ++bit) { + for(uint8_t bit = 0; bit < 8; ++bit) { if(remainder & 1) { remainder = (remainder >> 1) ^ polynomial; } else { @@ -105,15 +102,14 @@ uint8_t subghz_protocol_blocks_crc8le( uint16_t subghz_protocol_blocks_crc16lsb( uint8_t const message[], - unsigned nBytes, + size_t size, uint16_t polynomial, uint16_t init) { uint16_t remainder = init; - unsigned byte, bit; - for(byte = 0; byte < nBytes; ++byte) { + for(size_t byte = 0; byte < size; ++byte) { remainder ^= message[byte]; - for(bit = 0; bit < 8; ++bit) { + for(uint8_t bit = 0; bit < 8; ++bit) { if(remainder & 1) { remainder = (remainder >> 1) ^ polynomial; } else { @@ -126,15 +122,14 @@ uint16_t subghz_protocol_blocks_crc16lsb( uint16_t subghz_protocol_blocks_crc16( uint8_t const message[], - unsigned nBytes, + size_t size, uint16_t polynomial, uint16_t init) { uint16_t remainder = init; - unsigned byte, bit; - for(byte = 0; byte < nBytes; ++byte) { + for(size_t byte = 0; byte < size; ++byte) { remainder ^= message[byte] << 8; - for(bit = 0; bit < 8; ++bit) { + for(uint8_t bit = 0; bit < 8; ++bit) { if(remainder & 0x8000) { remainder = (remainder << 1) ^ polynomial; } else { @@ -147,18 +142,18 @@ uint16_t subghz_protocol_blocks_crc16( uint8_t subghz_protocol_blocks_lfsr_digest8( uint8_t const message[], - unsigned bytes, + size_t size, uint8_t gen, uint8_t key) { uint8_t sum = 0; - for(unsigned k = 0; k < bytes; ++k) { - uint8_t data = message[k]; + for(size_t byte = 0; byte < size; ++byte) { + uint8_t data = message[byte]; for(int i = 7; i >= 0; --i) { // XOR key into sum if data bit is set if((data >> i) & 1) sum ^= key; - // roll the key right (actually the lsb is dropped here) - // and apply the gen (needs to include the dropped lsb as msb) + // roll the key right (actually the LSB is dropped here) + // and apply the gen (needs to include the dropped LSB as MSB) if(key & 1) key = (key >> 1) ^ gen; else @@ -170,22 +165,22 @@ uint8_t subghz_protocol_blocks_lfsr_digest8( uint8_t subghz_protocol_blocks_lfsr_digest8_reflect( uint8_t const message[], - int bytes, + size_t size, uint8_t gen, uint8_t key) { uint8_t sum = 0; // Process message from last byte to first byte (reflected) - for(int k = bytes - 1; k >= 0; --k) { - uint8_t data = message[k]; + for(int byte = size - 1; byte >= 0; --byte) { + uint8_t data = message[byte]; // Process individual bits of each byte (reflected) - for(int i = 0; i < 8; ++i) { + for(uint8_t i = 0; i < 8; ++i) { // XOR key into sum if data bit is set if((data >> i) & 1) { sum ^= key; } - // roll the key left (actually the lsb is dropped here) - // and apply the gen (needs to include the dropped lsb as msb) + // roll the key left (actually the LSB is dropped here) + // and apply the gen (needs to include the dropped lsb as MSB) if(key & 0x80) key = (key << 1) ^ gen; else @@ -197,18 +192,18 @@ uint8_t subghz_protocol_blocks_lfsr_digest8_reflect( uint16_t subghz_protocol_blocks_lfsr_digest16( uint8_t const message[], - unsigned bytes, + size_t size, uint16_t gen, uint16_t key) { uint16_t sum = 0; - for(unsigned k = 0; k < bytes; ++k) { - uint8_t data = message[k]; - for(int i = 7; i >= 0; --i) { + for(size_t byte = 0; byte < size; ++byte) { + uint8_t data = message[byte]; + for(int8_t i = 7; i >= 0; --i) { // if data bit is set then xor with key if((data >> i) & 1) sum ^= key; - // roll the key right (actually the lsb is dropped here) - // and apply the gen (needs to include the dropped lsb as msb) + // roll the key right (actually the LSB is dropped here) + // and apply the gen (needs to include the dropped LSB as MSB) if(key & 1) key = (key >> 1) ^ gen; else @@ -218,31 +213,31 @@ uint16_t subghz_protocol_blocks_lfsr_digest16( return sum; } -uint8_t subghz_protocol_blocks_add_bytes(uint8_t const message[], size_t num_bytes) { - int result = 0; - for(size_t i = 0; i < num_bytes; ++i) { +uint8_t subghz_protocol_blocks_add_bytes(uint8_t const message[], size_t size) { + uint32_t result = 0; + for(size_t i = 0; i < size; ++i) { result += message[i]; } return (uint8_t)result; } -int subghz_protocol_blocks_parity8(uint8_t byte) { +uint8_t subghz_protocol_blocks_parity8(uint8_t byte) { byte ^= byte >> 4; byte &= 0xf; return (0x6996 >> byte) & 1; } -int subghz_protocol_blocks_parity_bytes(uint8_t const message[], size_t num_bytes) { - int result = 0; - for(size_t i = 0; i < num_bytes; ++i) { +uint8_t subghz_protocol_blocks_parity_bytes(uint8_t const message[], size_t size) { + uint8_t result = 0; + for(size_t i = 0; i < size; ++i) { result ^= subghz_protocol_blocks_parity8(message[i]); } return result; } -uint8_t subghz_protocol_blocks_xor_bytes(uint8_t const message[], size_t num_bytes) { +uint8_t subghz_protocol_blocks_xor_bytes(uint8_t const message[], size_t size) { uint8_t result = 0; - for(size_t i = 0; i < num_bytes; ++i) { + for(size_t i = 0; i < size; ++i) { result ^= message[i]; } return result; diff --git a/lib/subghz/blocks/math.h b/lib/subghz/blocks/math.h index 8cddf4c0bce..a4f04271a62 100644 --- a/lib/subghz/blocks/math.h +++ b/lib/subghz/blocks/math.h @@ -14,183 +14,201 @@ #ifdef __cplusplus extern "C" { #endif -/** - * Flip the data bitwise. - * @param key In data - * @param count_bit number of data bits - * @return Reverse data - **/ -uint64_t subghz_protocol_blocks_reverse_key(uint64_t key, uint8_t count_bit); - -/** - * Get parity the data bitwise. - * @param key In data - * @param count_bit number of data bits - * @return parity - **/ -uint8_t subghz_protocol_blocks_get_parity(uint64_t key, uint8_t count_bit); - -/** - * CRC-4. - * @param message array of bytes to check - * @param nBytes number of bytes in message - * @param polynomial CRC polynomial - * @param init starting crc value - * @return CRC value - **/ + +/** Flip the data bitwise + * + * @param key In data + * @param bit_count number of data bits + * + * @return Reverse data + */ +uint64_t subghz_protocol_blocks_reverse_key(uint64_t key, uint8_t bit_count); + +/** Get parity the data bitwise + * + * @param key In data + * @param bit_count number of data bits + * + * @return parity + */ +uint8_t subghz_protocol_blocks_get_parity(uint64_t key, uint8_t bit_count); + +/** CRC-4 + * + * @param message array of bytes to check + * @param size number of bytes in message + * @param polynomial CRC polynomial + * @param init starting crc value + * + * @return CRC value + */ uint8_t subghz_protocol_blocks_crc4( uint8_t const message[], - unsigned nBytes, + size_t size, uint8_t polynomial, uint8_t init); -/** - * CRC-7. - * @param message array of bytes to check - * @param nBytes number of bytes in message - * @param polynomial CRC polynomial - * @param init starting crc value - * @return CRC value - **/ +/** CRC-7 + * + * @param message array of bytes to check + * @param size number of bytes in message + * @param polynomial CRC polynomial + * @param init starting crc value + * + * @return CRC value + */ uint8_t subghz_protocol_blocks_crc7( uint8_t const message[], - unsigned nBytes, + size_t size, uint8_t polynomial, uint8_t init); -/** - * Generic Cyclic Redundancy Check CRC-8. - * Example polynomial: 0x31 = x8 + x5 + x4 + 1 (x8 is implicit) - * Example polynomial: 0x80 = x8 + x7 (a normal bit-by-bit parity XOR) - * @param message array of bytes to check - * @param nBytes number of bytes in message - * @param polynomial byte is from x^7 to x^0 (x^8 is implicitly one) - * @param init starting crc value - * @return CRC value - **/ +/** Generic Cyclic Redundancy Check CRC-8. Example polynomial: 0x31 = x8 + x5 + + * x4 + 1 (x8 is implicit) Example polynomial: 0x80 = x8 + x7 (a normal + * bit-by-bit parity XOR) + * + * @param message array of bytes to check + * @param size number of bytes in message + * @param polynomial byte is from x^7 to x^0 (x^8 is implicitly one) + * @param init starting crc value + * + * @return CRC value + */ uint8_t subghz_protocol_blocks_crc8( uint8_t const message[], - unsigned nBytes, + size_t size, uint8_t polynomial, uint8_t init); -/** - * "Little-endian" Cyclic Redundancy Check CRC-8 LE - * Input and output are reflected, i.e. least significant bit is shifted in first. - * @param message array of bytes to check - * @param nBytes number of bytes in message - * @param polynomial CRC polynomial - * @param init starting crc value - * @return CRC value - **/ +/** "Little-endian" Cyclic Redundancy Check CRC-8 LE Input and output are + * reflected, i.e. least significant bit is shifted in first + * + * @param message array of bytes to check + * @param size number of bytes in message + * @param polynomial CRC polynomial + * @param init starting crc value + * + * @return CRC value + */ uint8_t subghz_protocol_blocks_crc8le( uint8_t const message[], - unsigned nBytes, + size_t size, uint8_t polynomial, uint8_t init); -/** - * CRC-16 LSB. - * Input and output are reflected, i.e. least significant bit is shifted in first. - * Note that poly and init already need to be reflected. - * @param message array of bytes to check - * @param nBytes number of bytes in message - * @param polynomial CRC polynomial - * @param init starting crc value - * @return CRC value - **/ +/** CRC-16 LSB. Input and output are reflected, i.e. least significant bit is + * shifted in first. Note that poly and init already need to be reflected + * + * @param message array of bytes to check + * @param size number of bytes in message + * @param polynomial CRC polynomial + * @param init starting crc value + * + * @return CRC value + */ uint16_t subghz_protocol_blocks_crc16lsb( uint8_t const message[], - unsigned nBytes, + size_t size, uint16_t polynomial, uint16_t init); -/** - * CRC-16. - * @param message array of bytes to check - * @param nBytes number of bytes in message - * @param polynomial CRC polynomial - * @param init starting crc value - * @return CRC value - **/ +/** CRC-16 + * + * @param message array of bytes to check + * @param size number of bytes in message + * @param polynomial CRC polynomial + * @param init starting crc value + * + * @return CRC value + */ uint16_t subghz_protocol_blocks_crc16( uint8_t const message[], - unsigned nBytes, + size_t size, uint16_t polynomial, uint16_t init); -/** - * Digest-8 by "LFSR-based Toeplitz hash". - * @param message bytes of message data - * @param bytes number of bytes to digest - * @param gen key stream generator, needs to includes the MSB if the LFSR is rolling - * @param key initial key - * @return digest value - **/ +/** Digest-8 by "LFSR-based Toeplitz hash" + * + * @param message bytes of message data + * @param size number of bytes to digest + * @param gen key stream generator, needs to includes the MSB if the + * LFSR is rolling + * @param key initial key + * + * @return digest value + */ uint8_t subghz_protocol_blocks_lfsr_digest8( uint8_t const message[], - unsigned bytes, + size_t size, uint8_t gen, uint8_t key); -/** - * Digest-8 by "LFSR-based Toeplitz hash", byte reflect, bit reflect. - * @param message bytes of message data - * @param bytes number of bytes to digest - * @param gen key stream generator, needs to includes the MSB if the LFSR is rolling - * @param key initial key - * @return digest value - **/ +/** Digest-8 by "LFSR-based Toeplitz hash", byte reflect, bit reflect + * + * @param message bytes of message data + * @param size number of bytes to digest + * @param gen key stream generator, needs to includes the MSB if the + * LFSR is rolling + * @param key initial key + * + * @return digest value + */ uint8_t subghz_protocol_blocks_lfsr_digest8_reflect( uint8_t const message[], - int bytes, + size_t size, uint8_t gen, uint8_t key); -/** - * Digest-16 by "LFSR-based Toeplitz hash". - * @param message bytes of message data - * @param bytes number of bytes to digest - * @param gen key stream generator, needs to includes the MSB if the LFSR is rolling - * @param key initial key - * @return digest value - **/ +/** Digest-16 by "LFSR-based Toeplitz hash" + * + * @param message bytes of message data + * @param size number of bytes to digest + * @param gen key stream generator, needs to includes the MSB if the + * LFSR is rolling + * @param key initial key + * + * @return digest value + */ uint16_t subghz_protocol_blocks_lfsr_digest16( uint8_t const message[], - unsigned bytes, + size_t size, uint16_t gen, uint16_t key); -/** - * Compute Addition of a number of bytes. - * @param message bytes of message data - * @param num_bytes number of bytes to sum - * @return summation value - **/ -uint8_t subghz_protocol_blocks_add_bytes(uint8_t const message[], size_t num_bytes); - -/** - * Compute bit parity of a single byte (8 bits). - * @param byte single byte to check - * @return 1 odd parity, 0 even parity - **/ -int subghz_protocol_blocks_parity8(uint8_t byte); - -/** - * Compute bit parity of a number of bytes. - * @param message bytes of message data - * @param num_bytes number of bytes to sum - * @return 1 odd parity, 0 even parity - **/ -int subghz_protocol_blocks_parity_bytes(uint8_t const message[], size_t num_bytes); - -/** - * Compute XOR (byte-wide parity) of a number of bytes. - * @param message bytes of message data - * @param num_bytes number of bytes to sum - * @return summation value, per bit-position 1 odd parity, 0 even parity - **/ -uint8_t subghz_protocol_blocks_xor_bytes(uint8_t const message[], size_t num_bytes); +/** Compute Addition of a number of bytes + * + * @param message bytes of message data + * @param size number of bytes to sum + * + * @return summation value + */ +uint8_t subghz_protocol_blocks_add_bytes(uint8_t const message[], size_t size); + +/** Compute bit parity of a single byte (8 bits) + * + * @param byte single byte to check + * + * @return 1 odd parity, 0 even parity + */ +uint8_t subghz_protocol_blocks_parity8(uint8_t byte); + +/** Compute bit parity of a number of bytes + * + * @param message bytes of message data + * @param size number of bytes to sum + * + * @return 1 odd parity, 0 even parity + */ +uint8_t subghz_protocol_blocks_parity_bytes(uint8_t const message[], size_t size); + +/** Compute XOR (byte-wide parity) of a number of bytes + * + * @param message bytes of message data + * @param size number of bytes to sum + * + * @return summation value, per bit-position 1 odd parity, 0 even parity + */ +uint8_t subghz_protocol_blocks_xor_bytes(uint8_t const message[], size_t size); #ifdef __cplusplus } From 8b7a52b97b6b537b02608517ad3d12c6c170ece5 Mon Sep 17 00:00:00 2001 From: hedger Date: Thu, 27 Oct 2022 00:25:31 +0400 Subject: [PATCH 178/824] fbt: fixed linter paths (#1930) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fbt: fixed linter paths * lint: changed file permissions Co-authored-by: あく --- .../targets/f7/ble_glue/dev_info_service.c | 0 .../targets/f7/furi_hal/furi_hal_cortex.c | 3 +- .../targets/furi_hal_include/furi_hal_nfc.h | 125 +++++++++++------- furi/SConscript | 7 +- furi/core/timer.c | 2 +- furi/flipper.c | 0 lib/lfrfid/SConscript | 2 +- lib/lfrfid/protocols/protocol_awid.c | 2 +- 8 files changed, 85 insertions(+), 56 deletions(-) mode change 100755 => 100644 firmware/targets/f7/ble_glue/dev_info_service.c mode change 100755 => 100644 furi/flipper.c diff --git a/firmware/targets/f7/ble_glue/dev_info_service.c b/firmware/targets/f7/ble_glue/dev_info_service.c old mode 100755 new mode 100644 diff --git a/firmware/targets/f7/furi_hal/furi_hal_cortex.c b/firmware/targets/f7/furi_hal/furi_hal_cortex.c index c2abd1b85ee..192b83ee452 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_cortex.c +++ b/firmware/targets/f7/furi_hal/furi_hal_cortex.c @@ -36,5 +36,6 @@ bool furi_hal_cortex_timer_is_expired(FuriHalCortexTimer cortex_timer) { } void furi_hal_cortex_timer_wait(FuriHalCortexTimer cortex_timer) { - while(!furi_hal_cortex_timer_is_expired(cortex_timer)); + while(!furi_hal_cortex_timer_is_expired(cortex_timer)) + ; } \ No newline at end of file diff --git a/firmware/targets/furi_hal_include/furi_hal_nfc.h b/firmware/targets/furi_hal_include/furi_hal_nfc.h index 537e0abf020..90d968fea99 100644 --- a/firmware/targets/furi_hal_include/furi_hal_nfc.h +++ b/firmware/targets/furi_hal_include/furi_hal_nfc.h @@ -111,7 +111,7 @@ bool furi_hal_nfc_is_busy(); * * @return true if initialized */ - bool furi_hal_nfc_is_init(); +bool furi_hal_nfc_is_init(); /** NFC field on */ @@ -234,7 +234,6 @@ void furi_hal_nfc_sleep(); void furi_hal_nfc_stop(); - /* Low level transport API, use it to implement your own transport layers */ #define furi_hal_nfc_ll_ms2fc rfalConvMsTo1fc @@ -250,7 +249,8 @@ typedef enum { FuriHalNfcReturnBusy = 2, /*!< device or resource busy */ FuriHalNfcReturnIo = 3, /*!< generic IO error */ FuriHalNfcReturnTimeout = 4, /*!< error due to timeout */ - FuriHalNfcReturnRequest = 5, /*!< invalid request or requested function can't be executed at the moment */ + FuriHalNfcReturnRequest = + 5, /*!< invalid request or requested function can't be executed at the moment */ FuriHalNfcReturnNomsg = 6, /*!< No message of desired type */ FuriHalNfcReturnParam = 7, /*!< Parameter error */ FuriHalNfcReturnSystem = 8, /*!< System error */ @@ -261,20 +261,23 @@ typedef enum { FuriHalNfcReturnAgain = 13, /*!< Call again */ FuriHalNfcReturnMemCorrupt = 14, /*!< memory corruption */ FuriHalNfcReturnNotImplemented = 15, /*!< not implemented */ - FuriHalNfcReturnPcCorrupt = 16, /*!< Program Counter has been manipulated or spike/noise trigger illegal operation */ + FuriHalNfcReturnPcCorrupt = + 16, /*!< Program Counter has been manipulated or spike/noise trigger illegal operation */ FuriHalNfcReturnSend = 17, /*!< error sending*/ FuriHalNfcReturnIgnore = 18, /*!< indicates error detected but to be ignored */ FuriHalNfcReturnSemantic = 19, /*!< indicates error in state machine (unexpected cmd) */ FuriHalNfcReturnSyntax = 20, /*!< indicates error in state machine (unknown cmd) */ FuriHalNfcReturnCrc = 21, /*!< crc error */ FuriHalNfcReturnNotfound = 22, /*!< transponder not found */ - FuriHalNfcReturnNotunique = 23, /*!< transponder not unique - more than one transponder in field */ + FuriHalNfcReturnNotunique = + 23, /*!< transponder not unique - more than one transponder in field */ FuriHalNfcReturnNotsupp = 24, /*!< requested operation not supported */ FuriHalNfcReturnWrite = 25, /*!< write error */ FuriHalNfcReturnFifo = 26, /*!< fifo over or underflow error */ FuriHalNfcReturnPar = 27, /*!< parity error */ FuriHalNfcReturnDone = 28, /*!< transfer has already finished */ - FuriHalNfcReturnRfCollision = 29, /*!< collision error (Bit Collision or during RF Collision avoidance ) */ + FuriHalNfcReturnRfCollision = + 29, /*!< collision error (Bit Collision or during RF Collision avoidance ) */ FuriHalNfcReturnHwOverrun = 30, /*!< lost one or more received bytes */ FuriHalNfcReturnReleaseReq = 31, /*!< device requested release */ FuriHalNfcReturnSleepReq = 32, /*!< device requested sleep */ @@ -282,7 +285,8 @@ typedef enum { FuriHalNfcReturnMaxReruns = 34, /*!< blocking procedure reached maximum runs */ FuriHalNfcReturnDisabled = 35, /*!< operation aborted due to disabled configuration */ FuriHalNfcReturnHwMismatch = 36, /*!< expected hw do not match */ - FuriHalNfcReturnLinkLoss = 37, /*!< Other device's field didn't behave as expected: turned off by Initiator in Passive mode, or AP2P did not turn on field */ + FuriHalNfcReturnLinkLoss = + 37, /*!< Other device's field didn't behave as expected: turned off by Initiator in Passive mode, or AP2P did not turn on field */ FuriHalNfcReturnInvalidHandle = 38, /*!< invalid or not initalized device handle */ FuriHalNfcReturnIncompleteByte = 40, /*!< Incomplete byte rcvd */ FuriHalNfcReturnIncompleteByte01 = 41, /*!< Incomplete byte rcvd - 1 bit */ @@ -295,38 +299,40 @@ typedef enum { } FuriHalNfcReturn; typedef enum { - FuriHalNfcModeNone = 0, /*!< No mode selected/defined */ - FuriHalNfcModePollNfca = 1, /*!< Mode to perform as NFCA (ISO14443A) Poller (PCD) */ - FuriHalNfcModePollNfcaT1t = 2, /*!< Mode to perform as NFCA T1T (Topaz) Poller (PCD) */ - FuriHalNfcModePollNfcb = 3, /*!< Mode to perform as NFCB (ISO14443B) Poller (PCD) */ - FuriHalNfcModePollBPrime = 4, /*!< Mode to perform as B' Calypso (Innovatron) (PCD) */ - FuriHalNfcModePollBCts = 5, /*!< Mode to perform as CTS Poller (PCD) */ - FuriHalNfcModePollNfcf = 6, /*!< Mode to perform as NFCF (FeliCa) Poller (PCD) */ - FuriHalNfcModePollNfcv = 7, /*!< Mode to perform as NFCV (ISO15963) Poller (PCD) */ - FuriHalNfcModePollPicopass = 8, /*!< Mode to perform as PicoPass / iClass Poller (PCD) */ - FuriHalNfcModePollActiveP2p = 9, /*!< Mode to perform as Active P2P (ISO18092) Initiator */ - FuriHalNfcModeListenNfca = 10, /*!< Mode to perform as NFCA (ISO14443A) Listener (PICC) */ - FuriHalNfcModeListenNfcb = 11, /*!< Mode to perform as NFCA (ISO14443B) Listener (PICC) */ - FuriHalNfcModeListenNfcf = 12, /*!< Mode to perform as NFCA (ISO15963) Listener (PICC) */ - FuriHalNfcModeListenActiveP2p = 13 /*!< Mode to perform as Active P2P (ISO18092) Target */ + FuriHalNfcModeNone = 0, /*!< No mode selected/defined */ + FuriHalNfcModePollNfca = 1, /*!< Mode to perform as NFCA (ISO14443A) Poller (PCD) */ + FuriHalNfcModePollNfcaT1t = 2, /*!< Mode to perform as NFCA T1T (Topaz) Poller (PCD) */ + FuriHalNfcModePollNfcb = 3, /*!< Mode to perform as NFCB (ISO14443B) Poller (PCD) */ + FuriHalNfcModePollBPrime = 4, /*!< Mode to perform as B' Calypso (Innovatron) (PCD) */ + FuriHalNfcModePollBCts = 5, /*!< Mode to perform as CTS Poller (PCD) */ + FuriHalNfcModePollNfcf = 6, /*!< Mode to perform as NFCF (FeliCa) Poller (PCD) */ + FuriHalNfcModePollNfcv = 7, /*!< Mode to perform as NFCV (ISO15963) Poller (PCD) */ + FuriHalNfcModePollPicopass = 8, /*!< Mode to perform as PicoPass / iClass Poller (PCD) */ + FuriHalNfcModePollActiveP2p = 9, /*!< Mode to perform as Active P2P (ISO18092) Initiator */ + FuriHalNfcModeListenNfca = 10, /*!< Mode to perform as NFCA (ISO14443A) Listener (PICC) */ + FuriHalNfcModeListenNfcb = 11, /*!< Mode to perform as NFCA (ISO14443B) Listener (PICC) */ + FuriHalNfcModeListenNfcf = 12, /*!< Mode to perform as NFCA (ISO15963) Listener (PICC) */ + FuriHalNfcModeListenActiveP2p = 13 /*!< Mode to perform as Active P2P (ISO18092) Target */ } FuriHalNfcMode; typedef enum { - FuriHalNfcBitrate106 = 0, /*!< Bit Rate 106 kbit/s (fc/128) */ - FuriHalNfcBitrate212 = 1, /*!< Bit Rate 212 kbit/s (fc/64) */ - FuriHalNfcBitrate424 = 2, /*!< Bit Rate 424 kbit/s (fc/32) */ - FuriHalNfcBitrate848 = 3, /*!< Bit Rate 848 kbit/s (fc/16) */ - FuriHalNfcBitrate1695 = 4, /*!< Bit Rate 1695 kbit/s (fc/8) */ - FuriHalNfcBitrate3390 = 5, /*!< Bit Rate 3390 kbit/s (fc/4) */ - FuriHalNfcBitrate6780 = 6, /*!< Bit Rate 6780 kbit/s (fc/2) */ - FuriHalNfcBitrate13560 = 7, /*!< Bit Rate 13560 kbit/s (fc) */ - FuriHalNfcBitrate52p97 = 0xEB, /*!< Bit Rate 52.97 kbit/s (fc/256) Fast Mode VICC->VCD */ - FuriHalNfcBitrate26p48 = 0xEC, /*!< Bit Rate 26,48 kbit/s (fc/512) NFCV VICC->VCD & VCD->VICC 1of4 */ - FuriHalNfcBitrate1p66 = 0xED, /*!< Bit Rate 1,66 kbit/s (fc/8192) NFCV VCD->VICC 1of256 */ - FuriHalNfcBitrateKeep = 0xFF /*!< Value indicating to keep the same previous bit rate */ + FuriHalNfcBitrate106 = 0, /*!< Bit Rate 106 kbit/s (fc/128) */ + FuriHalNfcBitrate212 = 1, /*!< Bit Rate 212 kbit/s (fc/64) */ + FuriHalNfcBitrate424 = 2, /*!< Bit Rate 424 kbit/s (fc/32) */ + FuriHalNfcBitrate848 = 3, /*!< Bit Rate 848 kbit/s (fc/16) */ + FuriHalNfcBitrate1695 = 4, /*!< Bit Rate 1695 kbit/s (fc/8) */ + FuriHalNfcBitrate3390 = 5, /*!< Bit Rate 3390 kbit/s (fc/4) */ + FuriHalNfcBitrate6780 = 6, /*!< Bit Rate 6780 kbit/s (fc/2) */ + FuriHalNfcBitrate13560 = 7, /*!< Bit Rate 13560 kbit/s (fc) */ + FuriHalNfcBitrate52p97 = 0xEB, /*!< Bit Rate 52.97 kbit/s (fc/256) Fast Mode VICC->VCD */ + FuriHalNfcBitrate26p48 = + 0xEC, /*!< Bit Rate 26,48 kbit/s (fc/512) NFCV VICC->VCD & VCD->VICC 1of4 */ + FuriHalNfcBitrate1p66 = 0xED, /*!< Bit Rate 1,66 kbit/s (fc/8192) NFCV VCD->VICC 1of256 */ + FuriHalNfcBitrateKeep = 0xFF /*!< Value indicating to keep the same previous bit rate */ } FuriHalNfcBitrate; -FuriHalNfcReturn furi_hal_nfc_ll_set_mode(FuriHalNfcMode mode, FuriHalNfcBitrate txBR, FuriHalNfcBitrate rxBR); +FuriHalNfcReturn + furi_hal_nfc_ll_set_mode(FuriHalNfcMode mode, FuriHalNfcBitrate txBR, FuriHalNfcBitrate rxBR); #define FURI_HAL_NFC_LL_GT_NFCA furi_hal_nfc_ll_ms2fc(5U) /*!< GTA Digital 2.0 6.10.4.1 & B.2 */ #define FURI_HAL_NFC_LL_GT_NFCB furi_hal_nfc_ll_ms2fc(5U) /*!< GTB Digital 2.0 7.9.4.1 & B.3 */ @@ -334,40 +340,57 @@ FuriHalNfcReturn furi_hal_nfc_ll_set_mode(FuriHalNfcMode mode, FuriHalNfcBitrate #define FURI_HAL_NFC_LL_GT_NFCV furi_hal_nfc_ll_ms2fc(5U) /*!< GTV Digital 2.0 9.7.5.1 & B.5 */ #define FURI_HAL_NFC_LL_GT_PICOPASS furi_hal_nfc_ll_ms2fc(1U) /*!< GT Picopass */ #define FURI_HAL_NFC_LL_GT_AP2P furi_hal_nfc_ll_ms2fc(5U) /*!< TIRFG Ecma 340 11.1.1 */ -#define FURI_HAL_NFC_LL_GT_AP2P_ADJUSTED furi_hal_nfc_ll_ms2fc(5U + 25U) /*!< Adjusted GT for greater interoperability (Sony XPERIA P, Nokia N9, Huawei P2) */ +#define FURI_HAL_NFC_LL_GT_AP2P_ADJUSTED \ + furi_hal_nfc_ll_ms2fc( \ + 5U + \ + 25U) /*!< Adjusted GT for greater interoperability (Sony XPERIA P, Nokia N9, Huawei P2) */ void furi_hal_nfc_ll_set_guard_time(uint32_t cycles); typedef enum { - FuriHalNfcErrorHandlingNone = 0, /*!< No special error handling will be performed */ - FuriHalNfcErrorHandlingNfc = 1, /*!< Error handling set to perform as NFC compliant device */ - FuriHalNfcErrorHandlingEmvco = 2 /*!< Error handling set to perform as EMVCo compliant device */ + FuriHalNfcErrorHandlingNone = 0, /*!< No special error handling will be performed */ + FuriHalNfcErrorHandlingNfc = 1, /*!< Error handling set to perform as NFC compliant device */ + FuriHalNfcErrorHandlingEmvco = + 2 /*!< Error handling set to perform as EMVCo compliant device */ } FuriHalNfcErrorHandling; void furi_hal_nfc_ll_set_error_handling(FuriHalNfcErrorHandling eHandling); /* RFAL Frame Delay Time (FDT) Listen default values */ -#define FURI_HAL_NFC_LL_FDT_LISTEN_NFCA_POLLER 1172U /*!< FDTA,LISTEN,MIN (n=9) Last bit: Logic "1" - tnn,min/2 Digital 1.1 6.10 ; EMV CCP Spec Book D v2.01 4.8.1.3 */ -#define FURI_HAL_NFC_LL_FDT_LISTEN_NFCB_POLLER 1008U /*!< TR0B,MIN Digital 1.1 7.1.3 & A.3 ; EMV CCP Spec Book D v2.01 4.8.1.3 & Table A.5 */ -#define FURI_HAL_NFC_LL_FDT_LISTEN_NFCF_POLLER 2672U /*!< TR0F,LISTEN,MIN Digital 1.1 8.7.1.1 & A.4 */ -#define FURI_HAL_NFC_LL_FDT_LISTEN_NFCV_POLLER 4310U /*!< FDTV,LISTEN,MIN t1 min Digital 2.1 B.5 ; ISO15693-3 2009 9.1 */ -#define FURI_HAL_NFC_LL_FDT_LISTEN_PICOPASS_POLLER 3400U /*!< ISO15693 t1 min - observed adjustment */ -#define FURI_HAL_NFC_LL_FDT_LISTEN_AP2P_POLLER 64U /*!< FDT AP2P No actual FDTListen is required as fields switch and collision avoidance */ +#define FURI_HAL_NFC_LL_FDT_LISTEN_NFCA_POLLER \ + 1172U /*!< FDTA,LISTEN,MIN (n=9) Last bit: Logic "1" - tnn,min/2 Digital 1.1 6.10 ; EMV CCP Spec Book D v2.01 4.8.1.3 */ +#define FURI_HAL_NFC_LL_FDT_LISTEN_NFCB_POLLER \ + 1008U /*!< TR0B,MIN Digital 1.1 7.1.3 & A.3 ; EMV CCP Spec Book D v2.01 4.8.1.3 & Table A.5 */ +#define FURI_HAL_NFC_LL_FDT_LISTEN_NFCF_POLLER \ + 2672U /*!< TR0F,LISTEN,MIN Digital 1.1 8.7.1.1 & A.4 */ +#define FURI_HAL_NFC_LL_FDT_LISTEN_NFCV_POLLER \ + 4310U /*!< FDTV,LISTEN,MIN t1 min Digital 2.1 B.5 ; ISO15693-3 2009 9.1 */ +#define FURI_HAL_NFC_LL_FDT_LISTEN_PICOPASS_POLLER \ + 3400U /*!< ISO15693 t1 min - observed adjustment */ +#define FURI_HAL_NFC_LL_FDT_LISTEN_AP2P_POLLER \ + 64U /*!< FDT AP2P No actual FDTListen is required as fields switch and collision avoidance */ #define FURI_HAL_NFC_LL_FDT_LISTEN_NFCA_LISTENER 1172U /*!< FDTA,LISTEN,MIN Digital 1.1 6.10 */ -#define FURI_HAL_NFC_LL_FDT_LISTEN_NFCB_LISTENER 1024U /*!< TR0B,MIN Digital 1.1 7.1.3 & A.3 ; EMV CCP Spec Book D v2.01 4.8.1.3 & Table A.5 */ -#define FURI_HAL_NFC_LL_FDT_LISTEN_NFCF_LISTENER 2688U /*!< TR0F,LISTEN,MIN Digital 2.1 8.7.1.1 & B.4 */ -#define FURI_HAL_NFC_LL_FDT_LISTEN_AP2P_LISTENER 64U /*!< FDT AP2P No actual FDTListen exists as fields switch and collision avoidance */ +#define FURI_HAL_NFC_LL_FDT_LISTEN_NFCB_LISTENER \ + 1024U /*!< TR0B,MIN Digital 1.1 7.1.3 & A.3 ; EMV CCP Spec Book D v2.01 4.8.1.3 & Table A.5 */ +#define FURI_HAL_NFC_LL_FDT_LISTEN_NFCF_LISTENER \ + 2688U /*!< TR0F,LISTEN,MIN Digital 2.1 8.7.1.1 & B.4 */ +#define FURI_HAL_NFC_LL_FDT_LISTEN_AP2P_LISTENER \ + 64U /*!< FDT AP2P No actual FDTListen exists as fields switch and collision avoidance */ void furi_hal_nfc_ll_set_fdt_listen(uint32_t cycles); /* RFAL Frame Delay Time (FDT) Poll default values */ -#define FURI_HAL_NFC_LL_FDT_POLL_NFCA_POLLER 6780U /*!< FDTA,POLL,MIN Digital 1.1 6.10.3.1 & A.2 */ -#define FURI_HAL_NFC_LL_FDT_POLL_NFCA_T1T_POLLER 384U /*!< RRDDT1T,MIN,B1 Digital 1.1 10.7.1 & A.5 */ -#define FURI_HAL_NFC_LL_FDT_POLL_NFCB_POLLER 6780U /*!< FDTB,POLL,MIN = TR2B,MIN,DEFAULT Digital 1.1 7.9.3 & A.3 ; EMVCo 3.0 FDTB,PCD,MIN Table A.5 */ +#define FURI_HAL_NFC_LL_FDT_POLL_NFCA_POLLER \ + 6780U /*!< FDTA,POLL,MIN Digital 1.1 6.10.3.1 & A.2 */ +#define FURI_HAL_NFC_LL_FDT_POLL_NFCA_T1T_POLLER \ + 384U /*!< RRDDT1T,MIN,B1 Digital 1.1 10.7.1 & A.5 */ +#define FURI_HAL_NFC_LL_FDT_POLL_NFCB_POLLER \ + 6780U /*!< FDTB,POLL,MIN = TR2B,MIN,DEFAULT Digital 1.1 7.9.3 & A.3 ; EMVCo 3.0 FDTB,PCD,MIN Table A.5 */ #define FURI_HAL_NFC_LL_FDT_POLL_NFCF_POLLER 6800U /*!< FDTF,POLL,MIN Digital 2.1 8.7.3 & B.4 */ #define FURI_HAL_NFC_LL_FDT_POLL_NFCV_POLLER 4192U /*!< FDTV,POLL Digital 2.1 9.7.3.1 & B.5 */ #define FURI_HAL_NFC_LL_FDT_POLL_PICOPASS_POLLER 1790U /*!< FDT Max */ -#define FURI_HAL_NFC_LL_FDT_POLL_AP2P_POLLER 0U /*!< FDT AP2P No actual FDTPoll exists as fields switch and collision avoidance */ +#define FURI_HAL_NFC_LL_FDT_POLL_AP2P_POLLER \ + 0U /*!< FDT AP2P No actual FDTPoll exists as fields switch and collision avoidance */ void furi_hal_nfc_ll_set_fdt_poll(uint32_t FDTPoll); diff --git a/furi/SConscript b/furi/SConscript index a751eb6e1bf..f95ef13f91b 100644 --- a/furi/SConscript +++ b/furi/SConscript @@ -1,6 +1,11 @@ Import("env") -env.Append(LINT_SOURCES=["furi"]) +env.Append( + LINT_SOURCES=[ + "furi", + "furi/core", + ] +) libenv = env.Clone(FW_LIB_NAME="furi") diff --git a/furi/core/timer.c b/furi/core/timer.c index 462a2e89ea0..c42b0c2acbb 100644 --- a/furi/core/timer.c +++ b/furi/core/timer.c @@ -86,7 +86,7 @@ void furi_timer_free(FuriTimer* instance) { furi_check(xTimerDelete(hTimer, portMAX_DELAY) == pdPASS); - while (furi_timer_is_running(instance)) furi_delay_tick(2); + while(furi_timer_is_running(instance)) furi_delay_tick(2); if((uint32_t)callb & 1U) { /* Callback memory was allocated from dynamic pool, clear flag */ diff --git a/furi/flipper.c b/furi/flipper.c old mode 100755 new mode 100644 diff --git a/lib/lfrfid/SConscript b/lib/lfrfid/SConscript index fd29ca2ef54..6177a9a508b 100644 --- a/lib/lfrfid/SConscript +++ b/lib/lfrfid/SConscript @@ -2,7 +2,7 @@ Import("env") env.Append( LINT_SOURCES=[ - "#/lib/lfrfid", + "lib/lfrfid", ], CPPPATH=[ "#/lib/lfrfid", diff --git a/lib/lfrfid/protocols/protocol_awid.c b/lib/lfrfid/protocols/protocol_awid.c index 38c7793b853..9396277236e 100644 --- a/lib/lfrfid/protocols/protocol_awid.c +++ b/lib/lfrfid/protocols/protocol_awid.c @@ -207,7 +207,7 @@ bool protocol_awid_write_data(ProtocolAwid* protocol, void* data) { // Fix incorrect length byte if(protocol->data[0] != 26 && protocol->data[0] != 50 && protocol->data[0] != 37 && - protocol->data[0] != 34 && protocol->data[0] != 36 ) { + protocol->data[0] != 34 && protocol->data[0] != 36) { protocol->data[0] = 26; } From f11df494683ecadd20fe760ec27d2e77115aee90 Mon Sep 17 00:00:00 2001 From: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> Date: Fri, 28 Oct 2022 10:18:41 +0300 Subject: [PATCH 179/824] [FL-2828] Dolphin score update take 2 (#1929) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Move DolphinDeedNfcRead * Move DolphinDeedNfcReadSuccess * Move DolphinDeedNfcSave * Move DolphinDeedNfcDetectReader * Move DolphinDeedNfcEmulate * Count DolphinDeedNfcEmulate when launched from file browser * Implement most of the score accounting for NFC * Fully update Nfc icounter handling * Move DolphinDeedSubGhzFrequencyAnalyzer * Update the rest of icounter in SubGHz * Adjust SubGHz icounter handling * Adjust LFRFID icounter handling * Adjust Infrared icounter handling * Don't count renaming RFID tags as saving * Don't count renaming SubGHz signals as saving * Don't count renaming NFC tags as saving * Adjust iButton icounter handling * Minor code refactoring * Correct formatting * Account for emulating iButton keys from file manager/rpc Co-authored-by: あく --- applications/main/ibutton/ibutton.c | 3 +++ .../main/ibutton/scenes/ibutton_scene_add_value.c | 3 --- .../main/ibutton/scenes/ibutton_scene_emulate.c | 3 --- .../main/ibutton/scenes/ibutton_scene_read.c | 3 +-- .../ibutton/scenes/ibutton_scene_read_key_menu.c | 2 ++ .../main/ibutton/scenes/ibutton_scene_save_name.c | 10 ++++++++++ .../main/ibutton/scenes/ibutton_scene_save_success.c | 2 -- .../ibutton/scenes/ibutton_scene_saved_key_menu.c | 2 ++ .../main/ibutton/scenes/ibutton_scene_start.c | 2 ++ .../main/infrared/scenes/infrared_scene_learn.c | 2 ++ .../main/infrared/scenes/infrared_scene_learn_done.c | 3 --- .../scenes/infrared_scene_learn_enter_name.c | 2 ++ .../infrared/scenes/infrared_scene_learn_success.c | 3 --- applications/main/lfrfid/lfrfid.c | 5 ++++- .../main/lfrfid/scenes/lfrfid_scene_emulate.c | 3 --- .../main/lfrfid/scenes/lfrfid_scene_extra_actions.c | 3 +++ applications/main/lfrfid/scenes/lfrfid_scene_read.c | 3 +-- .../main/lfrfid/scenes/lfrfid_scene_read_key_menu.c | 2 ++ .../main/lfrfid/scenes/lfrfid_scene_save_data.c | 2 -- .../main/lfrfid/scenes/lfrfid_scene_save_name.c | 8 ++++++++ .../main/lfrfid/scenes/lfrfid_scene_save_success.c | 2 -- .../main/lfrfid/scenes/lfrfid_scene_saved_key_menu.c | 2 ++ applications/main/lfrfid/scenes/lfrfid_scene_start.c | 2 ++ applications/main/nfc/nfc.c | 4 ++++ .../main/nfc/scenes/nfc_scene_detect_reader.c | 2 -- applications/main/nfc/scenes/nfc_scene_emulate_uid.c | 2 -- .../main/nfc/scenes/nfc_scene_emv_read_success.c | 2 -- .../nfc/scenes/nfc_scene_mf_classic_dict_attack.c | 4 ++++ .../main/nfc/scenes/nfc_scene_mf_classic_emulate.c | 2 -- .../main/nfc/scenes/nfc_scene_mf_classic_keys_add.c | 2 ++ .../main/nfc/scenes/nfc_scene_mf_classic_menu.c | 7 +++++-- .../nfc/scenes/nfc_scene_mf_classic_read_success.c | 3 --- .../main/nfc/scenes/nfc_scene_mf_desfire_menu.c | 6 ++++++ .../nfc/scenes/nfc_scene_mf_ultralight_emulate.c | 2 -- .../main/nfc/scenes/nfc_scene_mf_ultralight_menu.c | 6 ++++++ .../nfc/scenes/nfc_scene_mf_ultralight_read_auth.c | 2 -- .../nfc_scene_mf_ultralight_read_auth_result.c | 4 ---- .../scenes/nfc_scene_mf_ultralight_read_success.c | 2 -- .../nfc/scenes/nfc_scene_mf_ultralight_unlock_warn.c | 2 ++ applications/main/nfc/scenes/nfc_scene_nfca_menu.c | 6 ++++++ .../main/nfc/scenes/nfc_scene_nfca_read_success.c | 3 --- applications/main/nfc/scenes/nfc_scene_read.c | 7 ++++++- .../main/nfc/scenes/nfc_scene_read_card_success.c | 2 -- applications/main/nfc/scenes/nfc_scene_save_name.c | 8 ++++++++ .../main/nfc/scenes/nfc_scene_save_success.c | 2 -- applications/main/nfc/scenes/nfc_scene_saved_menu.c | 2 ++ applications/main/nfc/scenes/nfc_scene_set_uid.c | 2 -- applications/main/nfc/scenes/nfc_scene_start.c | 3 +++ .../subghz/scenes/subghz_scene_frequency_analyzer.c | 2 -- .../main/subghz/scenes/subghz_scene_read_raw.c | 7 ++++++- .../main/subghz/scenes/subghz_scene_receiver.c | 2 ++ .../main/subghz/scenes/subghz_scene_receiver_info.c | 2 -- .../main/subghz/scenes/subghz_scene_save_name.c | 12 ++++++++++++ .../main/subghz/scenes/subghz_scene_save_success.c | 3 --- .../main/subghz/scenes/subghz_scene_set_type.c | 2 -- applications/main/subghz/scenes/subghz_scene_start.c | 2 ++ .../main/subghz/scenes/subghz_scene_transmitter.c | 2 +- applications/services/dolphin/helpers/dolphin_deed.c | 2 +- applications/services/dolphin/helpers/dolphin_deed.h | 2 +- 59 files changed, 125 insertions(+), 72 deletions(-) diff --git a/applications/main/ibutton/ibutton.c b/applications/main/ibutton/ibutton.c index 887fb3e75f4..b6d8361b353 100644 --- a/applications/main/ibutton/ibutton.c +++ b/applications/main/ibutton/ibutton.c @@ -5,6 +5,7 @@ #include #include #include +#include #define TAG "iButtonApp" @@ -337,11 +338,13 @@ int32_t ibutton_app(void* p) { view_dispatcher_attach_to_gui( ibutton->view_dispatcher, ibutton->gui, ViewDispatcherTypeDesktop); scene_manager_next_scene(ibutton->scene_manager, iButtonSceneRpc); + DOLPHIN_DEED(DolphinDeedIbuttonEmulate); } else { view_dispatcher_attach_to_gui( ibutton->view_dispatcher, ibutton->gui, ViewDispatcherTypeFullscreen); if(key_loaded) { scene_manager_next_scene(ibutton->scene_manager, iButtonSceneEmulate); + DOLPHIN_DEED(DolphinDeedIbuttonEmulate); } else { scene_manager_next_scene(ibutton->scene_manager, iButtonSceneStart); } diff --git a/applications/main/ibutton/scenes/ibutton_scene_add_value.c b/applications/main/ibutton/scenes/ibutton_scene_add_value.c index b3ec11a502a..ccac7612102 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_add_value.c +++ b/applications/main/ibutton/scenes/ibutton_scene_add_value.c @@ -1,7 +1,5 @@ #include "../ibutton_i.h" -#include - void ibutton_scene_add_type_byte_input_callback(void* context) { iButton* ibutton = context; view_dispatcher_send_custom_event(ibutton->view_dispatcher, iButtonCustomEventByteEditResult); @@ -38,7 +36,6 @@ bool ibutton_scene_add_value_on_event(void* context, SceneManagerEvent event) { consumed = true; if(event.event == iButtonCustomEventByteEditResult) { ibutton_key_set_data(ibutton->key, new_key_data, IBUTTON_KEY_DATA_SIZE); - DOLPHIN_DEED(DolphinDeedIbuttonAdd); scene_manager_next_scene(ibutton->scene_manager, iButtonSceneSaveName); } } diff --git a/applications/main/ibutton/scenes/ibutton_scene_emulate.c b/applications/main/ibutton/scenes/ibutton_scene_emulate.c index b3bc38ead2c..6f6ffcf574c 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_emulate.c +++ b/applications/main/ibutton/scenes/ibutton_scene_emulate.c @@ -1,6 +1,5 @@ #include "../ibutton_i.h" #include -#include #include #define EMULATE_TIMEOUT_TICKS 10 @@ -26,8 +25,6 @@ void ibutton_scene_emulate_on_enter(void* context) { path_extract_filename(ibutton->file_path, key_name, true); } - DOLPHIN_DEED(DolphinDeedIbuttonEmulate); - // check that stored key has name if(!furi_string_empty(key_name)) { ibutton_text_store_set(ibutton, "%s", furi_string_get_cstr(key_name)); diff --git a/applications/main/ibutton/scenes/ibutton_scene_read.c b/applications/main/ibutton/scenes/ibutton_scene_read.c index 05920a0adbd..1fe75e45ae5 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_read.c +++ b/applications/main/ibutton/scenes/ibutton_scene_read.c @@ -11,7 +11,6 @@ void ibutton_scene_read_on_enter(void* context) { Popup* popup = ibutton->popup; iButtonKey* key = ibutton->key; iButtonWorker* worker = ibutton->key_worker; - DOLPHIN_DEED(DolphinDeedIbuttonRead); popup_set_header(popup, "iButton", 95, 26, AlignCenter, AlignBottom); popup_set_text(popup, "Waiting\nfor key ...", 95, 30, AlignCenter, AlignTop); @@ -54,8 +53,8 @@ bool ibutton_scene_read_on_event(void* context, SceneManagerEvent event) { if(success) { ibutton_notification_message(ibutton, iButtonNotificationMessageSuccess); ibutton_notification_message(ibutton, iButtonNotificationMessageGreenOn); - DOLPHIN_DEED(DolphinDeedIbuttonReadSuccess); scene_manager_next_scene(scene_manager, iButtonSceneReadSuccess); + DOLPHIN_DEED(DolphinDeedIbuttonReadSuccess); } } } diff --git a/applications/main/ibutton/scenes/ibutton_scene_read_key_menu.c b/applications/main/ibutton/scenes/ibutton_scene_read_key_menu.c index 921b24fc18b..0a8ecfa55f7 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_read_key_menu.c +++ b/applications/main/ibutton/scenes/ibutton_scene_read_key_menu.c @@ -1,4 +1,5 @@ #include "../ibutton_i.h" +#include typedef enum { SubmenuIndexSave, @@ -49,6 +50,7 @@ bool ibutton_scene_read_key_menu_on_event(void* context, SceneManagerEvent event scene_manager_next_scene(ibutton->scene_manager, iButtonSceneSaveName); } else if(event.event == SubmenuIndexEmulate) { scene_manager_next_scene(ibutton->scene_manager, iButtonSceneEmulate); + DOLPHIN_DEED(DolphinDeedIbuttonEmulate); } else if(event.event == SubmenuIndexWrite) { scene_manager_next_scene(ibutton->scene_manager, iButtonSceneWrite); } diff --git a/applications/main/ibutton/scenes/ibutton_scene_save_name.c b/applications/main/ibutton/scenes/ibutton_scene_save_name.c index 773b93e0c8a..5f25a0002ff 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_save_name.c +++ b/applications/main/ibutton/scenes/ibutton_scene_save_name.c @@ -1,6 +1,7 @@ #include "../ibutton_i.h" #include #include +#include static void ibutton_scene_save_name_text_input_callback(void* context) { iButton* ibutton = context; @@ -57,6 +58,15 @@ bool ibutton_scene_save_name_on_event(void* context, SceneManagerEvent event) { if(event.event == iButtonCustomEventTextEditResult) { if(ibutton_save_key(ibutton, ibutton->text_store)) { scene_manager_next_scene(ibutton->scene_manager, iButtonSceneSaveSuccess); + if(scene_manager_has_previous_scene( + ibutton->scene_manager, iButtonSceneSavedKeyMenu)) { + // Nothing, do not count editing as saving + } else if(scene_manager_has_previous_scene( + ibutton->scene_manager, iButtonSceneAddType)) { + DOLPHIN_DEED(DolphinDeedIbuttonAdd); + } else { + DOLPHIN_DEED(DolphinDeedIbuttonSave); + } } else { const uint32_t possible_scenes[] = { iButtonSceneReadKeyMenu, iButtonSceneSavedKeyMenu, iButtonSceneAddType}; diff --git a/applications/main/ibutton/scenes/ibutton_scene_save_success.c b/applications/main/ibutton/scenes/ibutton_scene_save_success.c index 43237f4292d..e0b9b3c47fb 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_save_success.c +++ b/applications/main/ibutton/scenes/ibutton_scene_save_success.c @@ -1,5 +1,4 @@ #include "../ibutton_i.h" -#include static void ibutton_scene_save_success_popup_callback(void* context) { iButton* ibutton = context; @@ -9,7 +8,6 @@ static void ibutton_scene_save_success_popup_callback(void* context) { void ibutton_scene_save_success_on_enter(void* context) { iButton* ibutton = context; Popup* popup = ibutton->popup; - DOLPHIN_DEED(DolphinDeedIbuttonSave); popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); popup_set_header(popup, "Saved!", 5, 7, AlignLeft, AlignTop); diff --git a/applications/main/ibutton/scenes/ibutton_scene_saved_key_menu.c b/applications/main/ibutton/scenes/ibutton_scene_saved_key_menu.c index 3d588dd02a6..e4c9c350ae2 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_saved_key_menu.c +++ b/applications/main/ibutton/scenes/ibutton_scene_saved_key_menu.c @@ -1,4 +1,5 @@ #include "../ibutton_i.h" +#include enum SubmenuIndex { SubmenuIndexEmulate, @@ -58,6 +59,7 @@ bool ibutton_scene_saved_key_menu_on_event(void* context, SceneManagerEvent even consumed = true; if(event.event == SubmenuIndexEmulate) { scene_manager_next_scene(ibutton->scene_manager, iButtonSceneEmulate); + DOLPHIN_DEED(DolphinDeedIbuttonEmulate); } else if(event.event == SubmenuIndexWrite) { scene_manager_next_scene(ibutton->scene_manager, iButtonSceneWrite); } else if(event.event == SubmenuIndexEdit) { diff --git a/applications/main/ibutton/scenes/ibutton_scene_start.c b/applications/main/ibutton/scenes/ibutton_scene_start.c index dde224e157c..b8f6b07d6ca 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_start.c +++ b/applications/main/ibutton/scenes/ibutton_scene_start.c @@ -1,5 +1,6 @@ #include "../ibutton_i.h" #include "ibutton/scenes/ibutton_scene.h" +#include enum SubmenuIndex { SubmenuIndexRead, @@ -38,6 +39,7 @@ bool ibutton_scene_start_on_event(void* context, SceneManagerEvent event) { consumed = true; if(event.event == SubmenuIndexRead) { scene_manager_next_scene(ibutton->scene_manager, iButtonSceneRead); + DOLPHIN_DEED(DolphinDeedIbuttonRead); } else if(event.event == SubmenuIndexSaved) { furi_string_set(ibutton->file_path, IBUTTON_APP_FOLDER); scene_manager_next_scene(ibutton->scene_manager, iButtonSceneSelectKey); diff --git a/applications/main/infrared/scenes/infrared_scene_learn.c b/applications/main/infrared/scenes/infrared_scene_learn.c index 37f9b3e0548..48699a71fc9 100644 --- a/applications/main/infrared/scenes/infrared_scene_learn.c +++ b/applications/main/infrared/scenes/infrared_scene_learn.c @@ -1,4 +1,5 @@ #include "../infrared_i.h" +#include void infrared_scene_learn_on_enter(void* context) { Infrared* infrared = context; @@ -27,6 +28,7 @@ bool infrared_scene_learn_on_event(void* context, SceneManagerEvent event) { if(event.event == InfraredCustomEventTypeSignalReceived) { infrared_play_notification_message(infrared, InfraredNotificationMessageSuccess); scene_manager_next_scene(infrared->scene_manager, InfraredSceneLearnSuccess); + DOLPHIN_DEED(DolphinDeedIrLearnSuccess); consumed = true; } } diff --git a/applications/main/infrared/scenes/infrared_scene_learn_done.c b/applications/main/infrared/scenes/infrared_scene_learn_done.c index 7d35717155e..54b7da7247b 100644 --- a/applications/main/infrared/scenes/infrared_scene_learn_done.c +++ b/applications/main/infrared/scenes/infrared_scene_learn_done.c @@ -1,13 +1,10 @@ #include "../infrared_i.h" -#include - void infrared_scene_learn_done_on_enter(void* context) { Infrared* infrared = context; Popup* popup = infrared->popup; popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); - DOLPHIN_DEED(DolphinDeedIrSave); if(infrared->app_state.is_learning_new_remote) { popup_set_header(popup, "New remote\ncreated!", 0, 0, AlignLeft, AlignTop); diff --git a/applications/main/infrared/scenes/infrared_scene_learn_enter_name.c b/applications/main/infrared/scenes/infrared_scene_learn_enter_name.c index b6a7eac0d87..a8772a985cc 100644 --- a/applications/main/infrared/scenes/infrared_scene_learn_enter_name.c +++ b/applications/main/infrared/scenes/infrared_scene_learn_enter_name.c @@ -1,4 +1,5 @@ #include "../infrared_i.h" +#include void infrared_scene_learn_enter_name_on_enter(void* context) { Infrared* infrared = context; @@ -49,6 +50,7 @@ bool infrared_scene_learn_enter_name_on_event(void* context, SceneManagerEvent e if(success) { scene_manager_next_scene(scene_manager, InfraredSceneLearnDone); + DOLPHIN_DEED(DolphinDeedIrSave); } else { dialog_message_show_storage_error(infrared->dialogs, "Failed to save file"); const uint32_t possible_scenes[] = {InfraredSceneRemoteList, InfraredSceneStart}; diff --git a/applications/main/infrared/scenes/infrared_scene_learn_success.c b/applications/main/infrared/scenes/infrared_scene_learn_success.c index 466627144a0..469d4de9e46 100644 --- a/applications/main/infrared/scenes/infrared_scene_learn_success.c +++ b/applications/main/infrared/scenes/infrared_scene_learn_success.c @@ -1,7 +1,5 @@ #include "../infrared_i.h" -#include - static void infrared_scene_learn_success_dialog_result_callback(DialogExResult result, void* context) { Infrared* infrared = context; @@ -13,7 +11,6 @@ void infrared_scene_learn_success_on_enter(void* context) { DialogEx* dialog_ex = infrared->dialog_ex; InfraredSignal* signal = infrared->received_signal; - DOLPHIN_DEED(DolphinDeedIrLearnSuccess); infrared_play_notification_message(infrared, InfraredNotificationMessageGreenOn); if(infrared_signal_is_raw(signal)) { diff --git a/applications/main/lfrfid/lfrfid.c b/applications/main/lfrfid/lfrfid.c index b0f98937497..5132273064f 100644 --- a/applications/main/lfrfid/lfrfid.c +++ b/applications/main/lfrfid/lfrfid.c @@ -1,4 +1,5 @@ #include "lfrfid_i.h" +#include static bool lfrfid_debug_custom_event_callback(void* context, uint32_t event) { furi_assert(context); @@ -182,12 +183,14 @@ int32_t lfrfid_app(void* p) { view_dispatcher_attach_to_gui( app->view_dispatcher, app->gui, ViewDispatcherTypeDesktop); scene_manager_next_scene(app->scene_manager, LfRfidSceneRpc); + DOLPHIN_DEED(DolphinDeedRfidEmulate); } else { furi_string_set(app->file_path, args); lfrfid_load_key_data(app, app->file_path, true); view_dispatcher_attach_to_gui( app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); scene_manager_next_scene(app->scene_manager, LfRfidSceneEmulate); + DOLPHIN_DEED(DolphinDeedRfidEmulate); } } else { @@ -311,4 +314,4 @@ void lfrfid_widget_callback(GuiButtonType result, InputType type, void* context) void lfrfid_text_input_callback(void* context) { LfRfid* app = context; view_dispatcher_send_custom_event(app->view_dispatcher, LfRfidEventNext); -} \ No newline at end of file +} diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_emulate.c b/applications/main/lfrfid/scenes/lfrfid_scene_emulate.c index 2725982f0ef..dc39189942f 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_emulate.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_emulate.c @@ -1,12 +1,9 @@ #include "../lfrfid_i.h" -#include void lfrfid_scene_emulate_on_enter(void* context) { LfRfid* app = context; Popup* popup = app->popup; - DOLPHIN_DEED(DolphinDeedRfidEmulate); - popup_set_header(popup, "Emulating", 89, 30, AlignCenter, AlignTop); if(!furi_string_empty(app->file_name)) { popup_set_text(popup, furi_string_get_cstr(app->file_name), 89, 43, AlignCenter, AlignTop); diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_extra_actions.c b/applications/main/lfrfid/scenes/lfrfid_scene_extra_actions.c index d7fd93e19de..fac2ebcec54 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_extra_actions.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_extra_actions.c @@ -1,4 +1,5 @@ #include "../lfrfid_i.h" +#include typedef enum { SubmenuIndexASK, @@ -57,10 +58,12 @@ bool lfrfid_scene_extra_actions_on_event(void* context, SceneManagerEvent event) if(event.event == SubmenuIndexASK) { app->read_type = LFRFIDWorkerReadTypeASKOnly; scene_manager_next_scene(app->scene_manager, LfRfidSceneRead); + DOLPHIN_DEED(DolphinDeedRfidRead); consumed = true; } else if(event.event == SubmenuIndexPSK) { app->read_type = LFRFIDWorkerReadTypePSKOnly; scene_manager_next_scene(app->scene_manager, LfRfidSceneRead); + DOLPHIN_DEED(DolphinDeedRfidRead); consumed = true; } else if(event.event == SubmenuIndexRAW) { scene_manager_next_scene(app->scene_manager, LfRfidSceneRawName); diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_read.c b/applications/main/lfrfid/scenes/lfrfid_scene_read.c index 4bdb215d165..5f19597282a 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_read.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_read.c @@ -46,7 +46,6 @@ static void void lfrfid_scene_read_on_enter(void* context) { LfRfid* app = context; - DOLPHIN_DEED(DolphinDeedRfidRead); if(app->read_type == LFRFIDWorkerReadTypePSKOnly) { lfrfid_view_read_set_read_mode(app->read_view, LfRfidReadPskOnly); } else if(app->read_type == LFRFIDWorkerReadTypeASKOnly) { @@ -79,10 +78,10 @@ bool lfrfid_scene_read_on_event(void* context, SceneManagerEvent event) { consumed = true; } else if(event.event == LfRfidEventReadDone) { app->protocol_id = app->protocol_id_next; - DOLPHIN_DEED(DolphinDeedRfidReadSuccess); notification_message(app->notifications, &sequence_success); furi_string_reset(app->file_name); scene_manager_next_scene(app->scene_manager, LfRfidSceneReadSuccess); + DOLPHIN_DEED(DolphinDeedRfidReadSuccess); consumed = true; } else if(event.event == LfRfidEventReadStartPSK) { if(app->read_type == LFRFIDWorkerReadTypeAuto) { diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_read_key_menu.c b/applications/main/lfrfid/scenes/lfrfid_scene_read_key_menu.c index 7480304b6d4..081c479123f 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_read_key_menu.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_read_key_menu.c @@ -1,4 +1,5 @@ #include "../lfrfid_i.h" +#include typedef enum { SubmenuIndexSave, @@ -43,6 +44,7 @@ bool lfrfid_scene_read_key_menu_on_event(void* context, SceneManagerEvent event) consumed = true; } else if(event.event == SubmenuIndexEmulate) { scene_manager_next_scene(app->scene_manager, LfRfidSceneEmulate); + DOLPHIN_DEED(DolphinDeedRfidEmulate); consumed = true; } scene_manager_set_scene_state(app->scene_manager, LfRfidSceneReadKeyMenu, event.event); diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_save_data.c b/applications/main/lfrfid/scenes/lfrfid_scene_save_data.c index 6c5ea2f2df0..11a687bdd4d 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_save_data.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_save_data.c @@ -1,5 +1,4 @@ #include "../lfrfid_i.h" -#include void lfrfid_scene_save_data_on_enter(void* context) { LfRfid* app = context; @@ -32,7 +31,6 @@ bool lfrfid_scene_save_data_on_event(void* context, SceneManagerEvent event) { consumed = true; size_t size = protocol_dict_get_data_size(app->dict, app->protocol_id); protocol_dict_set_data(app->dict, app->protocol_id, app->new_key_data, size); - DOLPHIN_DEED(DolphinDeedRfidAdd); scene_manager_next_scene(scene_manager, LfRfidSceneSaveName); scene_manager_set_scene_state(scene_manager, LfRfidSceneSaveData, 1); } diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_save_name.c b/applications/main/lfrfid/scenes/lfrfid_scene_save_name.c index ca9a52de0ad..87e110f185b 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_save_name.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_save_name.c @@ -1,5 +1,6 @@ #include #include "../lfrfid_i.h" +#include void lfrfid_scene_save_name_on_enter(void* context) { LfRfid* app = context; @@ -55,6 +56,13 @@ bool lfrfid_scene_save_name_on_event(void* context, SceneManagerEvent event) { if(lfrfid_save_key(app)) { scene_manager_next_scene(scene_manager, LfRfidSceneSaveSuccess); + if(scene_manager_has_previous_scene(scene_manager, LfRfidSceneSavedKeyMenu)) { + // Nothing, do not count editing as saving + } else if(scene_manager_has_previous_scene(scene_manager, LfRfidSceneSaveType)) { + DOLPHIN_DEED(DolphinDeedRfidAdd); + } else { + DOLPHIN_DEED(DolphinDeedRfidSave); + } } else { scene_manager_search_and_switch_to_previous_scene( scene_manager, LfRfidSceneReadKeyMenu); diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_save_success.c b/applications/main/lfrfid/scenes/lfrfid_scene_save_success.c index e91ad04e36e..52aefa84899 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_save_success.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_save_success.c @@ -1,5 +1,4 @@ #include "../lfrfid_i.h" -#include void lfrfid_scene_save_success_on_enter(void* context) { LfRfid* app = context; @@ -8,7 +7,6 @@ void lfrfid_scene_save_success_on_enter(void* context) { // Clear state of data enter scene scene_manager_set_scene_state(app->scene_manager, LfRfidSceneSaveData, 0); - DOLPHIN_DEED(DolphinDeedRfidSave); popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); popup_set_header(popup, "Saved!", 5, 7, AlignLeft, AlignTop); popup_set_context(popup, app); diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_saved_key_menu.c b/applications/main/lfrfid/scenes/lfrfid_scene_saved_key_menu.c index 040b31f10b8..d3c3d389a8d 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_saved_key_menu.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_saved_key_menu.c @@ -1,4 +1,5 @@ #include "../lfrfid_i.h" +#include typedef enum { SubmenuIndexEmulate, @@ -42,6 +43,7 @@ bool lfrfid_scene_saved_key_menu_on_event(void* context, SceneManagerEvent event if(event.type == SceneManagerEventTypeCustom) { if(event.event == SubmenuIndexEmulate) { scene_manager_next_scene(app->scene_manager, LfRfidSceneEmulate); + DOLPHIN_DEED(DolphinDeedRfidEmulate); consumed = true; } else if(event.event == SubmenuIndexWrite) { scene_manager_next_scene(app->scene_manager, LfRfidSceneWrite); diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_start.c b/applications/main/lfrfid/scenes/lfrfid_scene_start.c index d6d87f441ed..8e1c92dbb29 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_start.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_start.c @@ -1,4 +1,5 @@ #include "../lfrfid_i.h" +#include typedef enum { SubmenuIndexRead, @@ -47,6 +48,7 @@ bool lfrfid_scene_start_on_event(void* context, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { if(event.event == SubmenuIndexRead) { scene_manager_next_scene(app->scene_manager, LfRfidSceneRead); + DOLPHIN_DEED(DolphinDeedRfidRead); consumed = true; } else if(event.event == SubmenuIndexSaved) { furi_string_set(app->file_path, LFRFID_APP_FOLDER); diff --git a/applications/main/nfc/nfc.c b/applications/main/nfc/nfc.c index 0b685f54574..55c68a450fa 100644 --- a/applications/main/nfc/nfc.c +++ b/applications/main/nfc/nfc.c @@ -1,5 +1,6 @@ #include "nfc_i.h" #include "furi_hal_nfc.h" +#include bool nfc_custom_event_callback(void* context, uint32_t event) { furi_assert(context); @@ -275,12 +276,15 @@ int32_t nfc_app(void* p) { if(nfc_device_load(nfc->dev, p, true)) { if(nfc->dev->format == NfcDeviceSaveFormatMifareUl) { scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightEmulate); + DOLPHIN_DEED(DolphinDeedNfcEmulate); } else if(nfc->dev->format == NfcDeviceSaveFormatMifareClassic) { scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicEmulate); + DOLPHIN_DEED(DolphinDeedNfcEmulate); } else if(nfc->dev->format == NfcDeviceSaveFormatBankCard) { scene_manager_next_scene(nfc->scene_manager, NfcSceneDeviceInfo); } else { scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateUid); + DOLPHIN_DEED(DolphinDeedNfcEmulate); } } else { // Exit app diff --git a/applications/main/nfc/scenes/nfc_scene_detect_reader.c b/applications/main/nfc/scenes/nfc_scene_detect_reader.c index f0177f9c11e..abf1437d2ce 100644 --- a/applications/main/nfc/scenes/nfc_scene_detect_reader.c +++ b/applications/main/nfc/scenes/nfc_scene_detect_reader.c @@ -1,5 +1,4 @@ #include "../nfc_i.h" -#include #define NFC_SCENE_DETECT_READER_PAIR_NONCES_MAX (10U) @@ -26,7 +25,6 @@ void nfc_scene_detect_reader_callback(void* context) { void nfc_scene_detect_reader_on_enter(void* context) { Nfc* nfc = context; - DOLPHIN_DEED(DolphinDeedNfcDetectReader); detect_reader_set_callback(nfc->detect_reader, nfc_scene_detect_reader_callback, nfc); detect_reader_set_nonces_max(nfc->detect_reader, NFC_SCENE_DETECT_READER_PAIR_NONCES_MAX); diff --git a/applications/main/nfc/scenes/nfc_scene_emulate_uid.c b/applications/main/nfc/scenes/nfc_scene_emulate_uid.c index 8bb20796005..5ddb60992f0 100644 --- a/applications/main/nfc/scenes/nfc_scene_emulate_uid.c +++ b/applications/main/nfc/scenes/nfc_scene_emulate_uid.c @@ -1,5 +1,4 @@ #include "../nfc_i.h" -#include #define NFC_SCENE_EMULATE_UID_LOG_SIZE_MAX (200) @@ -59,7 +58,6 @@ static void nfc_scene_emulate_uid_widget_config(Nfc* nfc, bool data_received) { void nfc_scene_emulate_uid_on_enter(void* context) { Nfc* nfc = context; - DOLPHIN_DEED(DolphinDeedNfcEmulate); // Setup Widget nfc_scene_emulate_uid_widget_config(nfc, false); diff --git a/applications/main/nfc/scenes/nfc_scene_emv_read_success.c b/applications/main/nfc/scenes/nfc_scene_emv_read_success.c index 6a0b32fadc6..005b76cb211 100644 --- a/applications/main/nfc/scenes/nfc_scene_emv_read_success.c +++ b/applications/main/nfc/scenes/nfc_scene_emv_read_success.c @@ -1,6 +1,5 @@ #include "../nfc_i.h" #include "../helpers/nfc_emv_parser.h" -#include void nfc_scene_emv_read_success_widget_callback( GuiButtonType result, @@ -15,7 +14,6 @@ void nfc_scene_emv_read_success_widget_callback( void nfc_scene_emv_read_success_on_enter(void* context) { Nfc* nfc = context; EmvData* emv_data = &nfc->dev->dev_data.emv_data; - DOLPHIN_DEED(DolphinDeedNfcReadSuccess); // Setup Custom Widget view widget_add_button_element( diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c index 813546905ed..acb5b783c3d 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c @@ -1,4 +1,5 @@ #include "../nfc_i.h" +#include #define TAG "NfcMfClassicDictAttack" @@ -110,6 +111,7 @@ bool nfc_scene_mf_classic_dict_attack_on_event(void* context, SceneManagerEvent } else { notification_message(nfc->notifications, &sequence_success); scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicReadSuccess); + DOLPHIN_DEED(DolphinDeedNfcReadSuccess); consumed = true; } } else if(event.event == NfcWorkerEventAborted) { @@ -119,6 +121,8 @@ bool nfc_scene_mf_classic_dict_attack_on_event(void* context, SceneManagerEvent } else { notification_message(nfc->notifications, &sequence_success); scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicReadSuccess); + // Counting failed attempts too + DOLPHIN_DEED(DolphinDeedNfcReadSuccess); consumed = true; } } else if(event.event == NfcWorkerEventCardDetected) { diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_emulate.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_emulate.c index 65639b2b40c..e514fa7280c 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_emulate.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_emulate.c @@ -1,5 +1,4 @@ #include "../nfc_i.h" -#include #define NFC_MF_CLASSIC_DATA_NOT_CHANGED (0UL) #define NFC_MF_CLASSIC_DATA_CHANGED (1UL) @@ -15,7 +14,6 @@ bool nfc_mf_classic_emulate_worker_callback(NfcWorkerEvent event, void* context) void nfc_scene_mf_classic_emulate_on_enter(void* context) { Nfc* nfc = context; - DOLPHIN_DEED(DolphinDeedNfcEmulate); // Setup view Popup* popup = nfc->popup; diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_add.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_add.c index 2921d21c96e..b122aa225b9 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_add.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_add.c @@ -1,4 +1,5 @@ #include "../nfc_i.h" +#include void nfc_scene_mf_classic_keys_add_byte_input_callback(void* context) { Nfc* nfc = context; @@ -36,6 +37,7 @@ bool nfc_scene_mf_classic_keys_add_on_event(void* context, SceneManagerEvent eve nfc->scene_manager, NfcSceneMfClassicKeysWarnDuplicate); } else if(mf_classic_dict_add_key(dict, nfc->byte_input_store)) { scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveSuccess); + DOLPHIN_DEED(DolphinDeedNfcMfcAdd); } else { scene_manager_next_scene(nfc->scene_manager, NfcSceneDictNotFound); } diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_menu.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_menu.c index 2cba0433753..a5bb10ddf86 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_menu.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_menu.c @@ -36,8 +36,6 @@ bool nfc_scene_mf_classic_menu_on_event(void* context, SceneManagerEvent event) if(event.type == SceneManagerEventTypeCustom) { if(event.event == SubmenuIndexSave) { - DOLPHIN_DEED(DolphinDeedNfcMfcAdd); - scene_manager_set_scene_state( nfc->scene_manager, NfcSceneMfClassicMenu, SubmenuIndexSave); nfc->dev->format = NfcDeviceSaveFormatMifareClassic; @@ -49,6 +47,11 @@ bool nfc_scene_mf_classic_menu_on_event(void* context, SceneManagerEvent event) scene_manager_set_scene_state( nfc->scene_manager, NfcSceneMfClassicMenu, SubmenuIndexEmulate); scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicEmulate); + if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSetType)) { + DOLPHIN_DEED(DolphinDeedNfcAddEmulate); + } else { + DOLPHIN_DEED(DolphinDeedNfcEmulate); + } consumed = true; } else if(event.event == SubmenuIndexInfo) { scene_manager_set_scene_state( diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_read_success.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_read_success.c index 0cdb86464bc..ae31e92cccb 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_read_success.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_read_success.c @@ -1,5 +1,4 @@ #include "../nfc_i.h" -#include void nfc_scene_mf_classic_read_success_widget_callback( GuiButtonType result, @@ -18,8 +17,6 @@ void nfc_scene_mf_classic_read_success_on_enter(void* context) { NfcDeviceData* dev_data = &nfc->dev->dev_data; MfClassicData* mf_data = &dev_data->mf_classic_data; - DOLPHIN_DEED(DolphinDeedNfcReadSuccess); - // Setup view Widget* widget = nfc->widget; widget_add_button_element( diff --git a/applications/main/nfc/scenes/nfc_scene_mf_desfire_menu.c b/applications/main/nfc/scenes/nfc_scene_mf_desfire_menu.c index 1e2f2d2f29d..bee63d775bd 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_desfire_menu.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_desfire_menu.c @@ -1,4 +1,5 @@ #include "../nfc_i.h" +#include enum SubmenuIndex { SubmenuIndexSave, @@ -48,6 +49,11 @@ bool nfc_scene_mf_desfire_menu_on_event(void* context, SceneManagerEvent event) consumed = true; } else if(event.event == SubmenuIndexEmulateUid) { scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateUid); + if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSetType)) { + DOLPHIN_DEED(DolphinDeedNfcAddEmulate); + } else { + DOLPHIN_DEED(DolphinDeedNfcEmulate); + } consumed = true; } else if(event.event == SubmenuIndexInfo) { scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcDataInfo); diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_emulate.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_emulate.c index 712ddc077d9..e84fb392767 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_emulate.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_emulate.c @@ -1,5 +1,4 @@ #include "../nfc_i.h" -#include #define NFC_MF_UL_DATA_NOT_CHANGED (0UL) #define NFC_MF_UL_DATA_CHANGED (1UL) @@ -15,7 +14,6 @@ bool nfc_mf_ultralight_emulate_worker_callback(NfcWorkerEvent event, void* conte void nfc_scene_mf_ultralight_emulate_on_enter(void* context) { Nfc* nfc = context; - DOLPHIN_DEED(DolphinDeedNfcEmulate); // Setup view Popup* popup = nfc->popup; diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_menu.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_menu.c index ba9f2233887..ab4d37b09f8 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_menu.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_menu.c @@ -1,4 +1,5 @@ #include "../nfc_i.h" +#include enum SubmenuIndex { SubmenuIndexUnlock, @@ -56,6 +57,11 @@ bool nfc_scene_mf_ultralight_menu_on_event(void* context, SceneManagerEvent even consumed = true; } else if(event.event == SubmenuIndexEmulate) { scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightEmulate); + if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSetType)) { + DOLPHIN_DEED(DolphinDeedNfcAddEmulate); + } else { + DOLPHIN_DEED(DolphinDeedNfcEmulate); + } consumed = true; } else if(event.event == SubmenuIndexUnlock) { scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightUnlockMenu); diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_auth.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_auth.c index 25008004bc4..5dbb0c18a88 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_auth.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_auth.c @@ -1,5 +1,4 @@ #include "../nfc_i.h" -#include typedef enum { NfcSceneMfUlReadStateIdle, @@ -51,7 +50,6 @@ void nfc_scene_mf_ultralight_read_auth_set_state(Nfc* nfc, NfcSceneMfUlReadState void nfc_scene_mf_ultralight_read_auth_on_enter(void* context) { Nfc* nfc = context; - DOLPHIN_DEED(DolphinDeedNfcRead); nfc_device_clear(nfc->dev); // Setup view diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_auth_result.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_auth_result.c index 5a690a2135e..178d03351b4 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_auth_result.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_auth_result.c @@ -1,5 +1,4 @@ #include "../nfc_i.h" -#include void nfc_scene_mf_ultralight_read_auth_result_widget_callback( GuiButtonType result, @@ -37,7 +36,6 @@ void nfc_scene_mf_ultralight_read_auth_result_on_enter(void* context) { widget_add_string_element( widget, 0, 17, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(temp_str)); if(mf_ul_data->auth_success) { - DOLPHIN_DEED(DolphinDeedNfcReadSuccess); furi_string_printf( temp_str, "Password: %02X %02X %02X %02X", @@ -54,8 +52,6 @@ void nfc_scene_mf_ultralight_read_auth_result_on_enter(void* context) { config_pages->auth_data.pack.raw[1]); widget_add_string_element( widget, 0, 39, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(temp_str)); - } else { - DOLPHIN_DEED(DolphinDeedNfcMfulError); } furi_string_printf( temp_str, "Pages Read: %d/%d", mf_ul_data->data_read / 4, mf_ul_data->data_size / 4); diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_success.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_success.c index 77034ea8061..63bffbf3666 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_success.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_success.c @@ -1,5 +1,4 @@ #include "../nfc_i.h" -#include void nfc_scene_mf_ultralight_read_success_widget_callback( GuiButtonType result, @@ -14,7 +13,6 @@ void nfc_scene_mf_ultralight_read_success_widget_callback( void nfc_scene_mf_ultralight_read_success_on_enter(void* context) { Nfc* nfc = context; - DOLPHIN_DEED(DolphinDeedNfcReadSuccess); // Setup widget view FuriHalNfcDevData* data = &nfc->dev->dev_data.nfc_data; diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_warn.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_warn.c index 58e081db94b..514cd4e9891 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_warn.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_warn.c @@ -1,4 +1,5 @@ #include "../nfc_i.h" +#include void nfc_scene_mf_ultralight_unlock_warn_dialog_callback(DialogExResult result, void* context) { Nfc* nfc = context; @@ -30,6 +31,7 @@ bool nfc_scene_mf_ultralight_unlock_warn_on_event(void* context, SceneManagerEve if(event.type == SceneManagerEventTypeCustom) { if(event.event == DialogExResultCenter) { scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightReadAuth); + DOLPHIN_DEED(DolphinDeedNfcRead); consumed = true; } } diff --git a/applications/main/nfc/scenes/nfc_scene_nfca_menu.c b/applications/main/nfc/scenes/nfc_scene_nfca_menu.c index 00d0d943dc0..30f63945c62 100644 --- a/applications/main/nfc/scenes/nfc_scene_nfca_menu.c +++ b/applications/main/nfc/scenes/nfc_scene_nfca_menu.c @@ -1,4 +1,5 @@ #include "../nfc_i.h" +#include enum SubmenuIndex { SubmenuIndexSaveUid, @@ -41,6 +42,11 @@ bool nfc_scene_nfca_menu_on_event(void* context, SceneManagerEvent event) { consumed = true; } else if(event.event == SubmenuIndexEmulateUid) { scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateUid); + if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSetType)) { + DOLPHIN_DEED(DolphinDeedNfcAddEmulate); + } else { + DOLPHIN_DEED(DolphinDeedNfcEmulate); + } consumed = true; } else if(event.event == SubmenuIndexInfo) { scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcDataInfo); diff --git a/applications/main/nfc/scenes/nfc_scene_nfca_read_success.c b/applications/main/nfc/scenes/nfc_scene_nfca_read_success.c index 2ea7c99217a..a38f31a9813 100644 --- a/applications/main/nfc/scenes/nfc_scene_nfca_read_success.c +++ b/applications/main/nfc/scenes/nfc_scene_nfca_read_success.c @@ -1,5 +1,4 @@ #include "../nfc_i.h" -#include void nfc_scene_nfca_read_success_widget_callback( GuiButtonType result, @@ -16,8 +15,6 @@ void nfc_scene_nfca_read_success_widget_callback( void nfc_scene_nfca_read_success_on_enter(void* context) { Nfc* nfc = context; - DOLPHIN_DEED(DolphinDeedNfcReadSuccess); - // Setup view FuriHalNfcDevData* data = &nfc->dev->dev_data.nfc_data; Widget* widget = nfc->widget; diff --git a/applications/main/nfc/scenes/nfc_scene_read.c b/applications/main/nfc/scenes/nfc_scene_read.c index e6df476f0dc..1f82aef0809 100644 --- a/applications/main/nfc/scenes/nfc_scene_read.c +++ b/applications/main/nfc/scenes/nfc_scene_read.c @@ -39,7 +39,6 @@ void nfc_scene_read_set_state(Nfc* nfc, NfcSceneReadState state) { void nfc_scene_read_on_enter(void* context) { Nfc* nfc = context; - DOLPHIN_DEED(DolphinDeedNfcRead); nfc_device_clear(nfc->dev); // Setup view @@ -62,26 +61,32 @@ bool nfc_scene_read_on_event(void* context, SceneManagerEvent event) { (event.event == NfcWorkerEventReadUidNfcV)) { notification_message(nfc->notifications, &sequence_success); scene_manager_next_scene(nfc->scene_manager, NfcSceneReadCardSuccess); + DOLPHIN_DEED(DolphinDeedNfcReadSuccess); consumed = true; } else if(event.event == NfcWorkerEventReadUidNfcA) { notification_message(nfc->notifications, &sequence_success); scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcaReadSuccess); + DOLPHIN_DEED(DolphinDeedNfcReadSuccess); consumed = true; } else if(event.event == NfcWorkerEventReadMfUltralight) { notification_message(nfc->notifications, &sequence_success); scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightReadSuccess); + DOLPHIN_DEED(DolphinDeedNfcReadSuccess); consumed = true; } else if(event.event == NfcWorkerEventReadMfClassicDone) { notification_message(nfc->notifications, &sequence_success); scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicReadSuccess); + DOLPHIN_DEED(DolphinDeedNfcReadSuccess); consumed = true; } else if(event.event == NfcWorkerEventReadMfDesfire) { notification_message(nfc->notifications, &sequence_success); scene_manager_next_scene(nfc->scene_manager, NfcSceneMfDesfireReadSuccess); + DOLPHIN_DEED(DolphinDeedNfcReadSuccess); consumed = true; } else if(event.event == NfcWorkerEventReadBankCard) { notification_message(nfc->notifications, &sequence_success); scene_manager_next_scene(nfc->scene_manager, NfcSceneEmvReadSuccess); + DOLPHIN_DEED(DolphinDeedNfcReadSuccess); consumed = true; } else if(event.event == NfcWorkerEventReadMfClassicDictAttackRequired) { if(mf_classic_dict_check_presence(MfClassicDictTypeFlipper)) { diff --git a/applications/main/nfc/scenes/nfc_scene_read_card_success.c b/applications/main/nfc/scenes/nfc_scene_read_card_success.c index 352cb4a7e52..9b2a2188e4a 100644 --- a/applications/main/nfc/scenes/nfc_scene_read_card_success.c +++ b/applications/main/nfc/scenes/nfc_scene_read_card_success.c @@ -1,5 +1,4 @@ #include "../nfc_i.h" -#include void nfc_scene_read_card_success_widget_callback( GuiButtonType result, @@ -18,7 +17,6 @@ void nfc_scene_read_card_success_on_enter(void* context) { FuriString* temp_str; temp_str = furi_string_alloc(); - DOLPHIN_DEED(DolphinDeedNfcReadSuccess); // Setup view FuriHalNfcDevData* data = &nfc->dev->dev_data.nfc_data; diff --git a/applications/main/nfc/scenes/nfc_scene_save_name.c b/applications/main/nfc/scenes/nfc_scene_save_name.c index 736eab7de50..791f8d99b70 100644 --- a/applications/main/nfc/scenes/nfc_scene_save_name.c +++ b/applications/main/nfc/scenes/nfc_scene_save_name.c @@ -2,6 +2,7 @@ #include #include #include +#include void nfc_scene_save_name_text_input_callback(void* context) { Nfc* nfc = context; @@ -63,6 +64,13 @@ bool nfc_scene_save_name_on_event(void* context, SceneManagerEvent event) { strlcpy(nfc->dev->dev_name, nfc->text_store, strlen(nfc->text_store) + 1); if(nfc_device_save(nfc->dev, nfc->text_store)) { scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveSuccess); + if(!scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSavedMenu)) { + // Nothing, do not count editing as saving + } else if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSetType)) { + DOLPHIN_DEED(DolphinDeedNfcAddSave); + } else { + DOLPHIN_DEED(DolphinDeedNfcSave); + } consumed = true; } else { consumed = scene_manager_search_and_switch_to_previous_scene( diff --git a/applications/main/nfc/scenes/nfc_scene_save_success.c b/applications/main/nfc/scenes/nfc_scene_save_success.c index dcd2519f1eb..34919cbd863 100644 --- a/applications/main/nfc/scenes/nfc_scene_save_success.c +++ b/applications/main/nfc/scenes/nfc_scene_save_success.c @@ -1,5 +1,4 @@ #include "../nfc_i.h" -#include void nfc_scene_save_success_popup_callback(void* context) { Nfc* nfc = context; @@ -8,7 +7,6 @@ void nfc_scene_save_success_popup_callback(void* context) { void nfc_scene_save_success_on_enter(void* context) { Nfc* nfc = context; - DOLPHIN_DEED(DolphinDeedNfcSave); // Setup view Popup* popup = nfc->popup; diff --git a/applications/main/nfc/scenes/nfc_scene_saved_menu.c b/applications/main/nfc/scenes/nfc_scene_saved_menu.c index fe65b5b8ad9..09d2c2d61b3 100644 --- a/applications/main/nfc/scenes/nfc_scene_saved_menu.c +++ b/applications/main/nfc/scenes/nfc_scene_saved_menu.c @@ -1,4 +1,5 @@ #include "../nfc_i.h" +#include enum SubmenuIndex { SubmenuIndexEmulate, @@ -76,6 +77,7 @@ bool nfc_scene_saved_menu_on_event(void* context, SceneManagerEvent event) { } else { scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateUid); } + DOLPHIN_DEED(DolphinDeedNfcEmulate); consumed = true; } else if(event.event == SubmenuIndexRename) { scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveName); diff --git a/applications/main/nfc/scenes/nfc_scene_set_uid.c b/applications/main/nfc/scenes/nfc_scene_set_uid.c index 9622ba2130f..1d3fb5eb999 100644 --- a/applications/main/nfc/scenes/nfc_scene_set_uid.c +++ b/applications/main/nfc/scenes/nfc_scene_set_uid.c @@ -1,5 +1,4 @@ #include "../nfc_i.h" -#include void nfc_scene_set_uid_byte_input_callback(void* context) { Nfc* nfc = context; @@ -30,7 +29,6 @@ bool nfc_scene_set_uid_on_event(void* context, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { if(event.event == NfcCustomEventByteInputDone) { - DOLPHIN_DEED(DolphinDeedNfcAddSave); if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSavedMenu)) { nfc->dev->dev_data.nfc_data = nfc->dev_edit_data; if(nfc_device_save(nfc->dev, nfc->dev->dev_name)) { diff --git a/applications/main/nfc/scenes/nfc_scene_start.c b/applications/main/nfc/scenes/nfc_scene_start.c index 1a9051dfd6d..0c4ec1cf902 100644 --- a/applications/main/nfc/scenes/nfc_scene_start.c +++ b/applications/main/nfc/scenes/nfc_scene_start.c @@ -1,4 +1,5 @@ #include "../nfc_i.h" +#include enum SubmenuIndex { SubmenuIndexRead, @@ -47,11 +48,13 @@ bool nfc_scene_start_on_event(void* context, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { if(event.event == SubmenuIndexRead) { scene_manager_next_scene(nfc->scene_manager, NfcSceneRead); + DOLPHIN_DEED(DolphinDeedNfcRead); consumed = true; } else if(event.event == SubmenuIndexDetectReader) { bool sd_exist = storage_sd_status(nfc->dev->storage) == FSE_OK; if(sd_exist) { scene_manager_next_scene(nfc->scene_manager, NfcSceneDetectReader); + DOLPHIN_DEED(DolphinDeedNfcDetectReader); } else { scene_manager_next_scene(nfc->scene_manager, NfcSceneDictNotFound); } diff --git a/applications/main/subghz/scenes/subghz_scene_frequency_analyzer.c b/applications/main/subghz/scenes/subghz_scene_frequency_analyzer.c index 9595c61bef4..b48a821da23 100644 --- a/applications/main/subghz/scenes/subghz_scene_frequency_analyzer.c +++ b/applications/main/subghz/scenes/subghz_scene_frequency_analyzer.c @@ -1,6 +1,5 @@ #include "../subghz_i.h" #include "../views/subghz_frequency_analyzer.h" -#include void subghz_scene_frequency_analyzer_callback(SubGhzCustomEvent event, void* context) { furi_assert(context); @@ -10,7 +9,6 @@ void subghz_scene_frequency_analyzer_callback(SubGhzCustomEvent event, void* con void subghz_scene_frequency_analyzer_on_enter(void* context) { SubGhz* subghz = context; - DOLPHIN_DEED(DolphinDeedSubGhzFrequencyAnalyzer); subghz_frequency_analyzer_set_callback( subghz->subghz_frequency_analyzer, subghz_scene_frequency_analyzer_callback, subghz); view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdFrequencyAnalyzer); diff --git a/applications/main/subghz/scenes/subghz_scene_read_raw.c b/applications/main/subghz/scenes/subghz_scene_read_raw.c index 8ac9bf5ba9c..ba9ce803bf5 100644 --- a/applications/main/subghz/scenes/subghz_scene_read_raw.c +++ b/applications/main/subghz/scenes/subghz_scene_read_raw.c @@ -223,7 +223,12 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { subghz->txrx->rx_key_state = SubGhzRxKeyStateBack; scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowOnlyRx); } else { - DOLPHIN_DEED(DolphinDeedSubGhzSend); + if(scene_manager_has_previous_scene( + subghz->scene_manager, SubGhzSceneSaved) || + !scene_manager_has_previous_scene( + subghz->scene_manager, SubGhzSceneStart)) { + DOLPHIN_DEED(DolphinDeedSubGhzSend); + } // set callback end tx subghz_protocol_raw_file_encoder_worker_set_callback_end( (SubGhzProtocolEncoderRAW*)subghz_transmitter_get_protocol_instance( diff --git a/applications/main/subghz/scenes/subghz_scene_receiver.c b/applications/main/subghz/scenes/subghz_scene_receiver.c index 77a92145714..2b01e29752f 100644 --- a/applications/main/subghz/scenes/subghz_scene_receiver.c +++ b/applications/main/subghz/scenes/subghz_scene_receiver.c @@ -1,5 +1,6 @@ #include "../subghz_i.h" #include "../views/receiver.h" +#include static const NotificationSequence subghs_sequence_rx = { &message_green_255, @@ -181,6 +182,7 @@ bool subghz_scene_receiver_on_event(void* context, SceneManagerEvent event) { subghz->txrx->idx_menu_chosen = subghz_view_receiver_get_idx_menu(subghz->subghz_receiver); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReceiverInfo); + DOLPHIN_DEED(DolphinDeedSubGhzReceiverInfo); consumed = true; break; case SubGhzCustomEventViewReceiverConfig: diff --git a/applications/main/subghz/scenes/subghz_scene_receiver_info.c b/applications/main/subghz/scenes/subghz_scene_receiver_info.c index 03a8ebbcb6e..bc6c8f112eb 100644 --- a/applications/main/subghz/scenes/subghz_scene_receiver_info.c +++ b/applications/main/subghz/scenes/subghz_scene_receiver_info.c @@ -1,6 +1,5 @@ #include "../subghz_i.h" #include "../helpers/subghz_custom_event.h" -#include void subghz_scene_receiver_info_callback(GuiButtonType result, InputType type, void* context) { furi_assert(context); @@ -45,7 +44,6 @@ static bool subghz_scene_receiver_info_update_parser(void* context) { void subghz_scene_receiver_info_on_enter(void* context) { SubGhz* subghz = context; - DOLPHIN_DEED(DolphinDeedSubGhzReceiverInfo); if(subghz_scene_receiver_info_update_parser(subghz)) { FuriString* frequency_str; FuriString* modulation_str; diff --git a/applications/main/subghz/scenes/subghz_scene_save_name.c b/applications/main/subghz/scenes/subghz_scene_save_name.c index dfcb65865ba..33846c283fe 100644 --- a/applications/main/subghz/scenes/subghz_scene_save_name.c +++ b/applications/main/subghz/scenes/subghz_scene_save_name.c @@ -4,6 +4,7 @@ #include "../helpers/subghz_custom_event.h" #include #include +#include #define MAX_TEXT_INPUT_LEN 22 @@ -131,6 +132,17 @@ bool subghz_scene_save_name_on_event(void* context, SceneManagerEvent event) { } scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveSuccess); + if(scene_manager_has_previous_scene(subghz->scene_manager, SubGhzSceneSavedMenu)) { + // Nothing, do not count editing as saving + } else if(scene_manager_has_previous_scene( + subghz->scene_manager, SubGhzSceneMoreRAW)) { + // Ditto, for RAW signals + } else if(scene_manager_has_previous_scene( + subghz->scene_manager, SubGhzSceneSetType)) { + DOLPHIN_DEED(DolphinDeedSubGhzAddManually); + } else { + DOLPHIN_DEED(DolphinDeedSubGhzSave); + } return true; } else { furi_string_set(subghz->error_str, "No name file"); diff --git a/applications/main/subghz/scenes/subghz_scene_save_success.c b/applications/main/subghz/scenes/subghz_scene_save_success.c index 3d5c16ca3db..d32c9271a24 100644 --- a/applications/main/subghz/scenes/subghz_scene_save_success.c +++ b/applications/main/subghz/scenes/subghz_scene_save_success.c @@ -1,7 +1,5 @@ #include "../subghz_i.h" #include "../helpers/subghz_custom_event.h" -#include -#include void subghz_scene_save_success_popup_callback(void* context) { SubGhz* subghz = context; @@ -10,7 +8,6 @@ void subghz_scene_save_success_popup_callback(void* context) { void subghz_scene_save_success_on_enter(void* context) { SubGhz* subghz = context; - DOLPHIN_DEED(DolphinDeedSubGhzSave); // Setup view Popup* popup = subghz->popup; diff --git a/applications/main/subghz/scenes/subghz_scene_set_type.c b/applications/main/subghz/scenes/subghz_scene_set_type.c index 44fe2fc76d0..2ed537193a8 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_type.c +++ b/applications/main/subghz/scenes/subghz_scene_set_type.c @@ -3,7 +3,6 @@ #include #include #include -#include #include #include #include @@ -381,7 +380,6 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { if(generated_protocol) { subghz_file_name_clear(subghz); - DOLPHIN_DEED(DolphinDeedSubGhzAddManually); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveName); return true; } diff --git a/applications/main/subghz/scenes/subghz_scene_start.c b/applications/main/subghz/scenes/subghz_scene_start.c index f37ccae9e42..0b1c3c159fd 100644 --- a/applications/main/subghz/scenes/subghz_scene_start.c +++ b/applications/main/subghz/scenes/subghz_scene_start.c @@ -1,4 +1,5 @@ #include "../subghz_i.h" +#include enum SubmenuIndex { SubmenuIndexRead = 10, @@ -84,6 +85,7 @@ bool subghz_scene_start_on_event(void* context, SceneManagerEvent event) { scene_manager_set_scene_state( subghz->scene_manager, SubGhzSceneStart, SubmenuIndexFrequencyAnalyzer); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneFrequencyAnalyzer); + DOLPHIN_DEED(DolphinDeedSubGhzFrequencyAnalyzer); return true; } else if(event.event == SubmenuIndexTest) { scene_manager_set_scene_state( diff --git a/applications/main/subghz/scenes/subghz_scene_transmitter.c b/applications/main/subghz/scenes/subghz_scene_transmitter.c index 5da6f4300e4..fbe954f0c35 100644 --- a/applications/main/subghz/scenes/subghz_scene_transmitter.c +++ b/applications/main/subghz/scenes/subghz_scene_transmitter.c @@ -50,7 +50,6 @@ bool subghz_scene_transmitter_update_data_show(void* context) { void subghz_scene_transmitter_on_enter(void* context) { SubGhz* subghz = context; - DOLPHIN_DEED(DolphinDeedSubGhzSend); if(!subghz_scene_transmitter_update_data_show(subghz)) { view_dispatcher_send_custom_event( subghz->view_dispatcher, SubGhzCustomEventViewTransmitterError); @@ -78,6 +77,7 @@ bool subghz_scene_transmitter_on_event(void* context, SceneManagerEvent event) { } else { subghz->state_notifications = SubGhzNotificationStateTx; subghz_scene_transmitter_update_data_show(subghz); + DOLPHIN_DEED(DolphinDeedSubGhzSend); } } return true; diff --git a/applications/services/dolphin/helpers/dolphin_deed.c b/applications/services/dolphin/helpers/dolphin_deed.c index ce3e058b564..51db56fdf68 100644 --- a/applications/services/dolphin/helpers/dolphin_deed.c +++ b/applications/services/dolphin/helpers/dolphin_deed.c @@ -21,8 +21,8 @@ static const DolphinDeedWeight dolphin_deed_weights[] = { {1, DolphinAppNfc}, // DolphinDeedNfcDetectReader {2, DolphinAppNfc}, // DolphinDeedNfcEmulate {2, DolphinAppNfc}, // DolphinDeedNfcMfcAdd - {1, DolphinAppNfc}, // DolphinDeedNfcMfulError {1, DolphinAppNfc}, // DolphinDeedNfcAddSave + {1, DolphinAppNfc}, // DolphinDeedNfcAddEmulate {1, DolphinAppIr}, // DolphinDeedIrSend {3, DolphinAppIr}, // DolphinDeedIrLearnSuccess diff --git a/applications/services/dolphin/helpers/dolphin_deed.h b/applications/services/dolphin/helpers/dolphin_deed.h index abe027d7909..c9cd18f316f 100644 --- a/applications/services/dolphin/helpers/dolphin_deed.h +++ b/applications/services/dolphin/helpers/dolphin_deed.h @@ -37,8 +37,8 @@ typedef enum { DolphinDeedNfcDetectReader, DolphinDeedNfcEmulate, DolphinDeedNfcMfcAdd, - DolphinDeedNfcMfulError, DolphinDeedNfcAddSave, + DolphinDeedNfcAddEmulate, DolphinDeedIrSend, DolphinDeedIrLearnSuccess, From 26f852839a93f82a3d863fc6a6e64c9ebd7e6f0a Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Fri, 28 Oct 2022 15:16:54 +0400 Subject: [PATCH 180/824] WS: fix Acurite-606TX protocol (#1938) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * WS: fix acurite_606tx protocol * WS: update version * WeatherStation: remove break from invalid place Co-authored-by: あく --- .../helpers/weather_station_types.h | 2 +- .../weather_station/protocols/acurite_606tx.c | 64 +++++++++---------- 2 files changed, 31 insertions(+), 35 deletions(-) diff --git a/applications/plugins/weather_station/helpers/weather_station_types.h b/applications/plugins/weather_station/helpers/weather_station_types.h index 479638b98ff..2976cbce8f6 100644 --- a/applications/plugins/weather_station/helpers/weather_station_types.h +++ b/applications/plugins/weather_station/helpers/weather_station_types.h @@ -3,7 +3,7 @@ #include #include -#define WS_VERSION_APP "0.3" +#define WS_VERSION_APP "0.3.1" #define WS_DEVELOPED "SkorP" #define WS_GITHUB "https://github.com/flipperdevices/flipperzero-firmware" diff --git a/applications/plugins/weather_station/protocols/acurite_606tx.c b/applications/plugins/weather_station/protocols/acurite_606tx.c index a92ec243c7c..3c914405751 100644 --- a/applications/plugins/weather_station/protocols/acurite_606tx.c +++ b/applications/plugins/weather_station/protocols/acurite_606tx.c @@ -151,41 +151,37 @@ void ws_protocol_decoder_acurite_606tx_feed(void* context, bool level, uint32_t case Acurite_606TXDecoderStepCheckDuration: if(!level) { - if((DURATION_DIFF(instance->decoder.te_last, ws_protocol_acurite_606tx_const.te_short) < - ws_protocol_acurite_606tx_const.te_delta) && - (DURATION_DIFF(duration, ws_protocol_acurite_606tx_const.te_short) < - ws_protocol_acurite_606tx_const.te_delta)) { - //Found syncPostfix - instance->decoder.parser_step = Acurite_606TXDecoderStepReset; - if((instance->decoder.decode_count_bit == - ws_protocol_acurite_606tx_const.min_count_bit_for_found) && - ws_protocol_acurite_606tx_check(instance)) { - instance->generic.data = instance->decoder.decode_data; - instance->generic.data_count_bit = instance->decoder.decode_count_bit; - ws_protocol_acurite_606tx_remote_controller(&instance->generic); - if(instance->base.callback) - instance->base.callback(&instance->base, instance->base.context); + if(DURATION_DIFF(instance->decoder.te_last, ws_protocol_acurite_606tx_const.te_short) < + ws_protocol_acurite_606tx_const.te_delta) { + if((DURATION_DIFF(duration, ws_protocol_acurite_606tx_const.te_short) < + ws_protocol_acurite_606tx_const.te_delta) || + (duration > ws_protocol_acurite_606tx_const.te_long * 3)) { + //Found syncPostfix + instance->decoder.parser_step = Acurite_606TXDecoderStepReset; + if((instance->decoder.decode_count_bit == + ws_protocol_acurite_606tx_const.min_count_bit_for_found) && + ws_protocol_acurite_606tx_check(instance)) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + ws_protocol_acurite_606tx_remote_controller(&instance->generic); + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } else if( + DURATION_DIFF(duration, ws_protocol_acurite_606tx_const.te_long) < + ws_protocol_acurite_606tx_const.te_delta * 2) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = Acurite_606TXDecoderStepSaveDuration; + } else if( + DURATION_DIFF(duration, ws_protocol_acurite_606tx_const.te_long * 2) < + ws_protocol_acurite_606tx_const.te_delta * 4) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = Acurite_606TXDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = Acurite_606TXDecoderStepReset; } - instance->decoder.decode_data = 0; - instance->decoder.decode_count_bit = 0; - - break; - } else if( - (DURATION_DIFF( - instance->decoder.te_last, ws_protocol_acurite_606tx_const.te_short) < - ws_protocol_acurite_606tx_const.te_delta) && - (DURATION_DIFF(duration, ws_protocol_acurite_606tx_const.te_long) < - ws_protocol_acurite_606tx_const.te_delta * 2)) { - subghz_protocol_blocks_add_bit(&instance->decoder, 0); - instance->decoder.parser_step = Acurite_606TXDecoderStepSaveDuration; - } else if( - (DURATION_DIFF( - instance->decoder.te_last, ws_protocol_acurite_606tx_const.te_short) < - ws_protocol_acurite_606tx_const.te_delta) && - (DURATION_DIFF(duration, ws_protocol_acurite_606tx_const.te_long * 2) < - ws_protocol_acurite_606tx_const.te_delta * 4)) { - subghz_protocol_blocks_add_bit(&instance->decoder, 1); - instance->decoder.parser_step = Acurite_606TXDecoderStepSaveDuration; } else { instance->decoder.parser_step = Acurite_606TXDecoderStepReset; } From be3ee9f2fe7a06fc2cd8e959c3dcb3e7fde374a7 Mon Sep 17 00:00:00 2001 From: Oleg Moiseenko <807634+merlokk@users.noreply.github.com> Date: Fri, 28 Oct 2022 16:42:59 +0300 Subject: [PATCH 181/824] Oregon2 additional sensors defines (#1933) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * added a list of sensors and added several additional temperature and temperature/humidity sensor id's * now here are only sensors that have test data Co-authored-by: あく --- .../weather_station/protocols/oregon2.c | 52 ++++++++++++++++--- 1 file changed, 45 insertions(+), 7 deletions(-) diff --git a/applications/plugins/weather_station/protocols/oregon2.c b/applications/plugins/weather_station/protocols/oregon2.c index 76bc3f0a169..d294548e62b 100644 --- a/applications/plugins/weather_station/protocols/oregon2.c +++ b/applications/plugins/weather_station/protocols/oregon2.c @@ -29,6 +29,40 @@ static const SubGhzBlockConst ws_oregon2_const = { // bit indicating the low battery #define OREGON2_FLAG_BAT_LOW 0x4 +/// Documentation for Oregon Scientific protocols can be found here: +/// http://wmrx00.sourceforge.net/Arduino/OregonScientific-RF-Protocols.pdf +// Sensors ID +#define ID_THGR122N 0x1d20 +#define ID_THGR968 0x1d30 +#define ID_BTHR918 0x5d50 +#define ID_BHTR968 0x5d60 +#define ID_RGR968 0x2d10 +#define ID_THR228N 0xec40 +#define ID_THN132N 0xec40 // same as THR228N but different packet size +#define ID_RTGN318 0x0cc3 // warning: id is from 0x0cc3 and 0xfcc3 +#define ID_RTGN129 0x0cc3 // same as RTGN318 but different packet size +#define ID_THGR810 0xf824 // This might be ID_THGR81, but what's true is lost in (git) history +#define ID_THGR810a 0xf8b4 // unconfirmed version +#define ID_THN802 0xc844 +#define ID_PCR800 0x2914 +#define ID_PCR800a 0x2d14 // Different PCR800 ID - AU version I think +#define ID_WGR800 0x1984 +#define ID_WGR800a 0x1994 // unconfirmed version +#define ID_WGR968 0x3d00 +#define ID_UV800 0xd874 +#define ID_THN129 0xcc43 // THN129 Temp only +#define ID_RTHN129 0x0cd3 // RTHN129 Temp, clock sensors +#define ID_BTHGN129 0x5d53 // Baro, Temp, Hygro sensor +#define ID_UVR128 0xec70 +#define ID_THGR328N 0xcc23 // Temp & Hygro sensor similar to THR228N with 5 channel instead of 3 +#define ID_RTGR328N_1 0xdcc3 // RTGR328N_[1-5] RFclock(date &time)&Temp&Hygro sensor +#define ID_RTGR328N_2 0xccc3 +#define ID_RTGR328N_3 0xbcc3 +#define ID_RTGR328N_4 0xacc3 +#define ID_RTGR328N_5 0x9cc3 +#define ID_RTGR328N_6 0x8ce3 // RTGR328N_6&7 RFclock(date &time)&Temp&Hygro sensor like THGR328N +#define ID_RTGR328N_7 0x8ae3 + struct WSProtocolDecoderOregon2 { SubGhzProtocolDecoderBase base; @@ -101,9 +135,12 @@ static ManchesterEvent level_and_duration_to_event(bool level, uint32_t duration } // From sensor id code return amount of bits in variable section +// https://temofeev.ru/info/articles/o-dekodirovanii-protokola-pogodnykh-datchikov-oregon-scientific static uint8_t oregon2_sensor_id_var_bits(uint16_t sensor_id) { - if(sensor_id == 0xEC40) return 16; - if(sensor_id == 0x1D20) return 24; + if(sensor_id == ID_THR228N) return 16; + + if(sensor_id == ID_THGR122N) return 24; + return 0; } @@ -134,15 +171,16 @@ static float ws_oregon2_decode_temp(uint32_t data) { } static void ws_oregon2_decode_var_data(WSBlockGeneric* ws_b, uint16_t sensor_id, uint32_t data) { - switch(sensor_id) { - case 0xEC40: + if(sensor_id == ID_THR228N) { ws_b->temp = ws_oregon2_decode_temp(data); ws_b->humidity = WS_NO_HUMIDITY; - break; - case 0x1D20: + return; + } + + if(sensor_id == ID_THGR122N) { ws_b->humidity = bcd_decode_short(data); ws_b->temp = ws_oregon2_decode_temp(data >> 8); - break; + return; } } From 492f147568a6f04896dfea6fba06a9494dfaa65d Mon Sep 17 00:00:00 2001 From: Konstantin Volkov <72250702+doomwastaken@users.noreply.github.com> Date: Fri, 28 Oct 2022 16:59:09 +0300 Subject: [PATCH 182/824] [FL-2887] actions unit tests runner (#1920) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Konstantin Volkov Co-authored-by: あく --- .github/workflows/unit_tests.yml | 56 ++++++++++++++++++++++ scripts/flipper/storage.py | 13 ++++++ scripts/storage.py | 16 +++++++ scripts/testing/await_flipper.py | 48 +++++++++++++++++++ scripts/testing/units.py | 79 ++++++++++++++++++++++++++++++++ 5 files changed, 212 insertions(+) create mode 100644 .github/workflows/unit_tests.yml create mode 100644 scripts/testing/await_flipper.py create mode 100644 scripts/testing/units.py diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml new file mode 100644 index 00000000000..8c5bac2a282 --- /dev/null +++ b/.github/workflows/unit_tests.yml @@ -0,0 +1,56 @@ +name: 'Unit tests' + +on: + pull_request: + +env: + TARGETS: f7 + DEFAULT_TARGET: f7 + +jobs: + main: + runs-on: [self-hosted, FlipperZeroTest] + steps: + - name: Checkout code + uses: actions/checkout@v2 + with: + fetch-depth: 0 + ref: ${{ github.event.pull_request.head.sha }} + + - name: 'Get flipper from device manager (mock)' + id: device + run: | + echo "flipper=/dev/ttyACM0" >> $GITHUB_OUTPUT + + - name: 'Compile unit tests firmware' + id: compile + continue-on-error: true + run: | + FBT_TOOLCHAIN_PATH=/opt ./fbt flash OPENOCD_ADAPTER_SERIAL=2A0906016415303030303032 FIRMWARE_APP_SET=unit_tests FORCE=1 + + - name: 'Wait for flipper to finish updating' + id: connect + if: steps.compile.outcome == 'success' + continue-on-error: true + run: | + python3 ./scripts/testing/await_flipper.py ${{steps.device.outputs.flipper}} + + - name: 'Format flipper SD card' + id: format + if: steps.connect.outcome == 'success' + continue-on-error: true + run: | + ./scripts/storage.py -p ${{steps.device.outputs.flipper}} format_ext + + - name: 'Copy unit tests to flipper' + id: copy + if: steps.format.outcome == 'success' + continue-on-error: true + run: | + ./scripts/storage.py -p ${{steps.device.outputs.flipper}} send assets/unit_tests /ext/unit_tests + + - name: 'Run units and validate results' + if: steps.copy.outcome == 'success' + continue-on-error: true + run: | + python3 ./scripts/testing/units.py ${{steps.device.outputs.flipper}} diff --git a/scripts/flipper/storage.py b/scripts/flipper/storage.py index 5fa8a2c8101..6b53f7477e4 100644 --- a/scripts/flipper/storage.py +++ b/scripts/flipper/storage.py @@ -340,6 +340,19 @@ def mkdir(self, path): else: return True + def format_ext(self): + """Create a directory on Flipper""" + self.send_and_wait_eol("storage format /ext\r") + self.send_and_wait_eol("y\r") + answer = self.read.until(self.CLI_EOL) + self.read.until(self.CLI_PROMPT) + + if self.has_error(answer): + self.last_error = self.get_error(answer) + return False + else: + return True + def remove(self, path): """Remove file or directory on Flipper""" self.send_and_wait_eol('storage remove "' + path + '"\r') diff --git a/scripts/storage.py b/scripts/storage.py index 167ba88eb10..ee5dabd439d 100755 --- a/scripts/storage.py +++ b/scripts/storage.py @@ -21,6 +21,11 @@ def init(self): self.parser_mkdir.add_argument("flipper_path", help="Flipper path") self.parser_mkdir.set_defaults(func=self.mkdir) + self.parser_format = self.subparsers.add_parser( + "format_ext", help="Format flash card" + ) + self.parser_format.set_defaults(func=self.format_ext) + self.parser_remove = self.subparsers.add_parser( "remove", help="Remove file/directory" ) @@ -275,6 +280,17 @@ def list(self): storage.stop() return 0 + def format_ext(self): + if not (storage := self._get_storage()): + return 1 + + self.logger.debug("Formatting /ext SD card") + + if not storage.format_ext(): + self.logger.error(f"Error: {storage.last_error}") + storage.stop() + return 0 + def stress(self): self.logger.error("This test is wearing out flash memory.") self.logger.error("Never use it with internal storage(/int)") diff --git a/scripts/testing/await_flipper.py b/scripts/testing/await_flipper.py new file mode 100644 index 00000000000..1f0d161949f --- /dev/null +++ b/scripts/testing/await_flipper.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 + +import sys, os, time + + +def flp_serial_by_name(flp_name): + if sys.platform == "darwin": # MacOS + flp_serial = "/dev/cu.usbmodemflip_" + flp_name + "1" + elif sys.platform == "linux": # Linux + flp_serial = ( + "/dev/serial/by-id/usb-Flipper_Devices_Inc._Flipper_" + + flp_name + + "_flip_" + + flp_name + + "-if00" + ) + + if os.path.exists(flp_serial): + return flp_serial + else: + if os.path.exists(flp_name): + return flp_name + else: + return "" + + +UPDATE_TIMEOUT = 30 + + +def main(): + flipper_name = sys.argv[1] + elapsed = 0 + flipper = flp_serial_by_name(flipper_name) + + while flipper == "" and elapsed < UPDATE_TIMEOUT: + elapsed += 1 + time.sleep(1) + flipper = flp_serial_by_name(flipper_name) + + if flipper == "": + print(f"Cannot find {flipper_name} flipper. Guess your flipper swam away") + sys.exit(1) + + sys.exit(0) + + +if __name__ == "__main__": + main() diff --git a/scripts/testing/units.py b/scripts/testing/units.py new file mode 100644 index 00000000000..83b07899a9a --- /dev/null +++ b/scripts/testing/units.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 + +import sys, os +import serial +import re + +from await_flipper import flp_serial_by_name + + +LEAK_THRESHOLD = 3000 # added until units are fixed + + +def main(): + flp_serial = flp_serial_by_name(sys.argv[1]) + + if flp_serial == "": + print("Name or serial port is invalid") + sys.exit(1) + + with serial.Serial(flp_serial, timeout=1) as flipper: + flipper.baudrate = 230400 + flipper.flushOutput() + flipper.flushInput() + + flipper.timeout = 300 + + flipper.read_until(b">: ").decode("utf-8") + flipper.write(b"unit_tests\r") + data = flipper.read_until(b">: ").decode("utf-8") + + lines = data.split("\r\n") + + tests_re = r"Failed tests: \d{0,}" + time_re = r"Consumed: \d{0,}" + leak_re = r"Leaked: \d{0,}" + status_re = r"Status: \w{3,}" + + tests_pattern = re.compile(tests_re) + time_pattern = re.compile(time_re) + leak_pattern = re.compile(leak_re) + status_pattern = re.compile(status_re) + + tests, time, leak, status = None, None, None, None + + for line in lines: + print(line) + if not tests: + tests = re.match(tests_pattern, line) + if not time: + time = re.match(time_pattern, line) + if not leak: + leak = re.match(leak_pattern, line) + if not status: + status = re.match(status_pattern, line) + + if leak is None or time is None or leak is None or status is None: + print("Failed to get data. Or output is corrupt") + sys.exit(1) + + leak = int(re.findall(r"[- ]\d+", leak.group(0))[0]) + status = re.findall(r"\w+", status.group(0))[1] + tests = int(re.findall(r"\d+", tests.group(0))[0]) + time = int(re.findall(r"\d+", time.group(0))[0]) + + if tests > 0 or leak > LEAK_THRESHOLD or status != "PASSED": + print(f"Got {tests} failed tests.") + print(f"Leaked {leak} bytes.") + print(f"Status by flipper: {status}") + print(f"Time elapsed {time/1000} seconds.") + sys.exit(1) + + print( + f"Tests ran successfully! Time elapsed {time/1000} seconds. Passed {tests} tests." + ) + sys.exit(0) + + +if __name__ == "__main__": + main() From 3434305630b4e76f20d703e119c9f3ad1df9cf4b Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Sat, 29 Oct 2022 00:08:50 +1000 Subject: [PATCH 183/824] [FL-2937] Remove resources from API to prevent frequent API version increase (#1935) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Remove all icons from API * Music player: icons * Signal generator: icons * Bt hid: icons * Weather station: icons * Picopass: icons * File browser test: icons * Example images: documentation * Remove global assets header * Fix picopass Co-authored-by: あく --- .../debug/file_browser_test/application.fam | 1 + .../file_browser_test/file_browser_app.c | 2 +- .../file_browser_test/icons/badusb_10px.png | Bin 0 -> 576 bytes .../examples/example_images/ReadMe.md | 24 +++ applications/main/bad_usb/bad_usb_app_i.h | 1 + .../main/bad_usb/views/bad_usb_view.c | 1 + applications/main/fap_loader/fap_loader_app.c | 1 + applications/main/gpio/gpio_app_i.h | 1 + applications/main/ibutton/ibutton_i.h | 1 + applications/main/infrared/infrared_i.h | 1 + applications/main/lfrfid/lfrfid_i.h | 1 + .../main/lfrfid/views/lfrfid_view_read.c | 1 + applications/main/nfc/nfc_i.h | 1 + applications/main/nfc/views/detect_reader.c | 2 +- applications/main/subghz/subghz_i.h | 1 + applications/main/u2f/u2f_app_i.h | 1 + applications/main/u2f/views/u2f_view.c | 1 + .../bt_hid_app/assets/ButtonDown_7x4.png | Bin 0 -> 102 bytes .../bt_hid_app/assets/ButtonLeft_4x7.png | Bin 0 -> 1415 bytes .../bt_hid_app/assets/ButtonRight_4x7.png | Bin 0 -> 1839 bytes .../bt_hid_app/assets/ButtonUp_7x4.png | Bin 0 -> 102 bytes .../plugins/bt_hid_app/assets/Ok_btn_9x9.png | Bin 0 -> 3605 bytes .../bt_hid_app/assets/Pin_arrow_down_7x9.png | Bin 0 -> 3607 bytes .../bt_hid_app/assets/Pin_arrow_left_9x7.png | Bin 0 -> 3603 bytes .../bt_hid_app/assets/Pin_arrow_right_9x7.png | Bin 0 -> 3602 bytes .../bt_hid_app/assets/Pin_arrow_up_7x9.png | Bin 0 -> 3603 bytes .../bt_hid_app/assets/Pin_back_arrow_10x8.png | Bin 0 -> 3606 bytes .../plugins/music_player/application.fam | 3 +- .../plugins/music_player/icons/music_10px.png | Bin 0 -> 142 bytes .../plugins/music_player/music_player.c | 2 +- applications/plugins/picopass/application.fam | 1 + .../picopass/icons/DolphinMafia_115x62.png | Bin 0 -> 2504 bytes .../picopass/icons/DolphinNice_96x59.png | Bin 0 -> 2459 bytes .../plugins/picopass/icons/Nfc_10px.png | Bin 0 -> 304 bytes .../icons/RFIDDolphinReceive_97x61.png | Bin 0 -> 1421 bytes .../picopass/icons/RFIDDolphinSend_97x61.png | Bin 0 -> 1418 bytes .../plugins/picopass/picopass_device.c | 1 + applications/plugins/picopass/picopass_i.h | 1 + .../plugins/signal_generator/application.fam | 1 + .../icons/SmallArrowDown_3x5.png | Bin 0 -> 3592 bytes .../icons/SmallArrowUp_3x5.png | Bin 0 -> 7976 bytes .../signal_generator/views/signal_gen_pwm.c | 1 + .../weather_station/images/Lock_7x8.png | Bin 0 -> 3597 bytes .../images/Pin_back_arrow_10x8.png | Bin 0 -> 3606 bytes .../weather_station/images/Quest_7x8.png | Bin 0 -> 3675 bytes .../images/Scanning_123x52.png | Bin 0 -> 1690 bytes .../weather_station/images/Unlock_7x8.png | Bin 0 -> 3598 bytes .../images/WarningDolphin_45x42.png | Bin 0 -> 1139 bytes .../views/weather_station_receiver.c | 3 +- applications/services/bt/bt_service/bt.c | 1 + .../desktop/views/desktop_view_lock_menu.c | 1 + .../desktop/views/desktop_view_locked.c | 1 + .../desktop/views/desktop_view_pin_input.c | 1 + applications/services/dialogs/dialogs_api.c | 1 + applications/services/gui/canvas.h | 2 +- applications/services/gui/gui.c | 1 + applications/services/gui/icon_animation.h | 2 +- .../services/gui/modules/button_menu.c | 1 + .../services/gui/modules/byte_input.c | 5 +- applications/services/gui/modules/menu.c | 1 + .../services/gui/modules/text_input.c | 1 + .../services/power/power_service/power_i.h | 1 + .../power/power_service/views/power_off.c | 1 + .../power_service/views/power_unplug_usb.c | 1 + applications/services/storage/storage.c | 1 + applications/settings/about/about.c | 1 + .../bt_settings_app/bt_settings_app.h | 1 + .../desktop_settings/desktop_settings_app.h | 1 + .../power_settings_app/power_settings_app.h | 1 + .../power_settings_app/views/battery_info.c | 1 + .../storage_settings/storage_settings.h | 1 + .../system/updater/views/updater_main.c | 1 + firmware/targets/f7/api_symbols.csv | 186 +----------------- 73 files changed, 74 insertions(+), 195 deletions(-) create mode 100644 applications/debug/file_browser_test/icons/badusb_10px.png create mode 100644 applications/examples/example_images/ReadMe.md create mode 100644 applications/plugins/bt_hid_app/assets/ButtonDown_7x4.png create mode 100644 applications/plugins/bt_hid_app/assets/ButtonLeft_4x7.png create mode 100644 applications/plugins/bt_hid_app/assets/ButtonRight_4x7.png create mode 100644 applications/plugins/bt_hid_app/assets/ButtonUp_7x4.png create mode 100644 applications/plugins/bt_hid_app/assets/Ok_btn_9x9.png create mode 100644 applications/plugins/bt_hid_app/assets/Pin_arrow_down_7x9.png create mode 100644 applications/plugins/bt_hid_app/assets/Pin_arrow_left_9x7.png create mode 100644 applications/plugins/bt_hid_app/assets/Pin_arrow_right_9x7.png create mode 100644 applications/plugins/bt_hid_app/assets/Pin_arrow_up_7x9.png create mode 100644 applications/plugins/bt_hid_app/assets/Pin_back_arrow_10x8.png create mode 100644 applications/plugins/music_player/icons/music_10px.png create mode 100644 applications/plugins/picopass/icons/DolphinMafia_115x62.png create mode 100644 applications/plugins/picopass/icons/DolphinNice_96x59.png create mode 100644 applications/plugins/picopass/icons/Nfc_10px.png create mode 100644 applications/plugins/picopass/icons/RFIDDolphinReceive_97x61.png create mode 100644 applications/plugins/picopass/icons/RFIDDolphinSend_97x61.png create mode 100644 applications/plugins/signal_generator/icons/SmallArrowDown_3x5.png create mode 100644 applications/plugins/signal_generator/icons/SmallArrowUp_3x5.png create mode 100644 applications/plugins/weather_station/images/Lock_7x8.png create mode 100644 applications/plugins/weather_station/images/Pin_back_arrow_10x8.png create mode 100644 applications/plugins/weather_station/images/Quest_7x8.png create mode 100644 applications/plugins/weather_station/images/Scanning_123x52.png create mode 100644 applications/plugins/weather_station/images/Unlock_7x8.png create mode 100644 applications/plugins/weather_station/images/WarningDolphin_45x42.png diff --git a/applications/debug/file_browser_test/application.fam b/applications/debug/file_browser_test/application.fam index 5e4c7f467b0..4a401a649d1 100644 --- a/applications/debug/file_browser_test/application.fam +++ b/applications/debug/file_browser_test/application.fam @@ -8,4 +8,5 @@ App( stack_size=2 * 1024, order=150, fap_category="Debug", + fap_icon_assets="icons", ) diff --git a/applications/debug/file_browser_test/file_browser_app.c b/applications/debug/file_browser_test/file_browser_app.c index 5c7b93bcbaf..6cb50d38543 100644 --- a/applications/debug/file_browser_test/file_browser_app.c +++ b/applications/debug/file_browser_test/file_browser_app.c @@ -1,4 +1,4 @@ -#include "assets_icons.h" +#include #include "file_browser_app_i.h" #include "gui/modules/file_browser.h" #include diff --git a/applications/debug/file_browser_test/icons/badusb_10px.png b/applications/debug/file_browser_test/icons/badusb_10px.png new file mode 100644 index 0000000000000000000000000000000000000000..037474aa3bc9c2e1aca79a68483e69980432bcf5 GIT binary patch literal 576 zcmV-G0>AxEX>4Tx04R}tkv&MmKpe$i(`rSk4t5Z6$WWau6cusQDionYs1;guFuC*#nlvOW zE{=k0!NHHks)LKOt`4q(Aou~|=;Wm6A|?JWDYS_3;J6>}?mh0_0Yan9G%FATG`(u3 z5^*t;T@{0`5D-8=V(6BcWz0!Z5}xDh9zMR_MR}I@xj#prnzI<-6NzV;VOEJZh^IHJ z2Iqa^Fe}O`@j3ChNf#u3C`-Nm{=@yu+qV-Xlle$#1U1~DPPFA zta9Gstd(o5bx;1nP)=W2<~q$0B(R7jND!f*h7!uCB1)@HiiH&I$36VRj$a~|Laq`R zITlcX2HEk0|H1EWt^DMKn-q!zT`#u%F$x5Cfo9#dzmILZc>?&Kfh)c3uQY&}Ptxmc zEph}5Yy%h9ZB5w&E_Z;TCqp)6NAlAY@_FF>jJ_!g4Bi60Yi@6?eVjf3Y3eF@0~{Oz zV+G1y_jq?tXK(+WY4!I5C=YUpXXIhH00006VoOIv0RI600RN!9r;`8x010qNS#tmY z3ljhU3ljkVnw%H_000McNliru<^lu`HWXkp{t5s906j@WK~xyijZi@f05Awj>HlAL zr$MwDdI>{Qf+U53tOUR#xOeyy)jcQo#JNRv)7r6DVVK|+*(cmT+R+EbO(O#X#REG4 O0000 +#include #include #include #include diff --git a/applications/main/bad_usb/views/bad_usb_view.c b/applications/main/bad_usb/views/bad_usb_view.c index 6c6a15847b0..e5c5d92a368 100644 --- a/applications/main/bad_usb/views/bad_usb_view.c +++ b/applications/main/bad_usb/views/bad_usb_view.c @@ -1,6 +1,7 @@ #include "bad_usb_view.h" #include "../bad_usb_script.h" #include +#include #define MAX_NAME_LEN 64 diff --git a/applications/main/fap_loader/fap_loader_app.c b/applications/main/fap_loader/fap_loader_app.c index faf8eefc833..9b4c9233596 100644 --- a/applications/main/fap_loader/fap_loader_app.c +++ b/applications/main/fap_loader/fap_loader_app.c @@ -1,5 +1,6 @@ #include #include +#include #include #include #include diff --git a/applications/main/gpio/gpio_app_i.h b/applications/main/gpio/gpio_app_i.h index fff35c95a7c..85c5c332e40 100644 --- a/applications/main/gpio/gpio_app_i.h +++ b/applications/main/gpio/gpio_app_i.h @@ -15,6 +15,7 @@ #include #include "views/gpio_test.h" #include "views/gpio_usb_uart.h" +#include struct GpioApp { Gui* gui; diff --git a/applications/main/ibutton/ibutton_i.h b/applications/main/ibutton/ibutton_i.h index a9515195e9d..0a80993517b 100644 --- a/applications/main/ibutton/ibutton_i.h +++ b/applications/main/ibutton/ibutton_i.h @@ -4,6 +4,7 @@ #include #include +#include #include #include #include diff --git a/applications/main/infrared/infrared_i.h b/applications/main/infrared/infrared_i.h index 6d25d160947..5b555e4bb73 100644 --- a/applications/main/infrared/infrared_i.h +++ b/applications/main/infrared/infrared_i.h @@ -2,6 +2,7 @@ #include #include +#include #include #include #include diff --git a/applications/main/lfrfid/lfrfid_i.h b/applications/main/lfrfid/lfrfid_i.h index 71917c0c2cc..72b0619304f 100644 --- a/applications/main/lfrfid/lfrfid_i.h +++ b/applications/main/lfrfid/lfrfid_i.h @@ -5,6 +5,7 @@ #include #include +#include #include #include #include diff --git a/applications/main/lfrfid/views/lfrfid_view_read.c b/applications/main/lfrfid/views/lfrfid_view_read.c index 0d4db61788c..094afb61758 100644 --- a/applications/main/lfrfid/views/lfrfid_view_read.c +++ b/applications/main/lfrfid/views/lfrfid_view_read.c @@ -1,5 +1,6 @@ #include "lfrfid_view_read.h" #include +#include #define TEMP_STR_LEN 128 diff --git a/applications/main/nfc/nfc_i.h b/applications/main/nfc/nfc_i.h index fa5b54edc57..e9b36a3e990 100644 --- a/applications/main/nfc/nfc_i.h +++ b/applications/main/nfc/nfc_i.h @@ -7,6 +7,7 @@ #include #include +#include #include #include #include diff --git a/applications/main/nfc/views/detect_reader.c b/applications/main/nfc/views/detect_reader.c index 2dbb4338b92..91537868b8b 100644 --- a/applications/main/nfc/views/detect_reader.c +++ b/applications/main/nfc/views/detect_reader.c @@ -1,5 +1,5 @@ #include "detect_reader.h" - +#include #include struct DetectReader { diff --git a/applications/main/subghz/subghz_i.h b/applications/main/subghz/subghz_i.h index 09830ba0567..46768cf02bb 100644 --- a/applications/main/subghz/subghz_i.h +++ b/applications/main/subghz/subghz_i.h @@ -13,6 +13,7 @@ #include "views/subghz_test_packet.h" #include +#include #include #include #include diff --git a/applications/main/u2f/u2f_app_i.h b/applications/main/u2f/u2f_app_i.h index 53647859ab8..2896684c373 100644 --- a/applications/main/u2f/u2f_app_i.h +++ b/applications/main/u2f/u2f_app_i.h @@ -4,6 +4,7 @@ #include "scenes/u2f_scene.h" #include +#include #include #include #include diff --git a/applications/main/u2f/views/u2f_view.c b/applications/main/u2f/views/u2f_view.c index fa3d6cc24a6..181c495d00a 100644 --- a/applications/main/u2f/views/u2f_view.c +++ b/applications/main/u2f/views/u2f_view.c @@ -1,5 +1,6 @@ #include "u2f_view.h" #include +#include struct U2fView { View* view; diff --git a/applications/plugins/bt_hid_app/assets/ButtonDown_7x4.png b/applications/plugins/bt_hid_app/assets/ButtonDown_7x4.png new file mode 100644 index 0000000000000000000000000000000000000000..2954bb6a67d1c23c0bb5d765e8d2aa04b9b5adec GIT binary patch literal 102 zcmeAS@N?(olHy`uVBq!ia0vp^>_E)I!3HFqj;YoHDIHH2#}J9|(o>FH3<^BV2haYO z-y5_sM4;GPjq%Ck6>60csmUj6EiNa>ORduPH4*)h!w|e3sE@(Z)z4*}Q$iC10Gods AV*mgE literal 0 HcmV?d00001 diff --git a/applications/plugins/bt_hid_app/assets/ButtonLeft_4x7.png b/applications/plugins/bt_hid_app/assets/ButtonLeft_4x7.png new file mode 100644 index 0000000000000000000000000000000000000000..0b4655d43247083aa705620e9836ac415b42ca46 GIT binary patch literal 1415 zcmbVM+iKK67*5rq)>aU2M7$VM1Vxif;vTv~W2u`S7ED{V3s&&L*<`XiG|9wd+THd> z5CnY!sdyuJtrvQyAo>KpiLcV|{Tkc)riAbluXfwSZCApL`ztB&p zx6LGKvks4K_4~)qD&oGa-YdJlW)hAKMNJd7<=t?6c^RI1>c$ifyjaM>^|&8!ey zB4!nh9u>5uen6Ve@<H5rru6h<2Ef#GQdQ*CmZOlQi~N!?9H`Rp;C% zU}CB21#?;r`&0|6C0}b-=jODa5|nEJ#ntxQ&{~jpgtwDta4hftr~G=#p@V36e4Zjh zq%J~{y26Jjn=1Nw-l*3%QW5YFE*v4z3gt0$&(*xf2en34c?JpH8+FYldo+Alvg8af-pG4(=!fyUi-Wsg z`g#n9VUcf(DFr{poMSNzw-lz>w+HV+n1ELr&SLA#LHUb0p(xWQ(1*vJ-i+1!`swxZ Z!O7;c$;lT_->m1Ovaz)0yuI`A$q$F8u*d)a literal 0 HcmV?d00001 diff --git a/applications/plugins/bt_hid_app/assets/ButtonRight_4x7.png b/applications/plugins/bt_hid_app/assets/ButtonRight_4x7.png new file mode 100644 index 0000000000000000000000000000000000000000..8e1c74c1c0038ea55172f19ac875003fc80c2d06 GIT binary patch literal 1839 zcmcIlO>f*p7#Yw)M6zw!O+@VZ{?d|D~WYi~8rHRY?X-&T}Yen`g$^+EJ;z+|RV zE@PoDvZ9%#+_}3bC_5Cj8jDGq541mi{7F+&KF}W65sr$Xn5H|YrMQ2(J7%Yc%;(zO z57ax000=TsQ+1Ke@+w#iw3au3cGGQWY740k2ijH>P(6tD)S)be>gX6Tj7`<`b>di- zgWp$8Y+?i31~CzF0&E4uRlA=C(Mp~K`{74jEchB|)4DDK!ZVhSwdFyw0YIZ1cDh0S{OvfO-U_~ zvmRF*m9sWDXNH)GOyqS1Skhxbr6}s*7t&@~kFM(NW5}qh?Lu@lJ}HE;FDiLdGO>LO z5pS*%E2grR)l^;|?O5b_?u0me&c1U}%jrk8*%=Wk%i)8yp2P|kuxmKg<=(u_`oQRI_0 zS`-DNysBx=#3&qSkgA@hJP>~D+ZM(s5jI6Owp`?yE=3e`YGUqkVOp#Cp=3wR3O4hX zX6BLsN3UBzV(vI5;|SZHgOb=HD0VFjpTyfFW}GnQuh>2*Q`k>*cAmA#iUT7EXSpo# zkPm5~#I-o^cpgfe#P$=4-Pi*SpT!-@nJgp8L347xe>5EKl`=_ZFc8XGy+_j=_R_7! z@vZZMowS1GJ?Zw)eetks%~G{BTR>T}9|jt0j3Btyb*C3-`C?fwY3EY`q*oYZ39DpM z&uJ;PCZPLs4QO1Jd_|A1PF)azZJ)RZ`^-VMWr6e#XUOA%3eLG_Ch@BDOHzMk*MF0G zCo7xMd?Mg*HMIXw%nNz?%60fZiZPlqb?GqUpXO`F&Yi!okZl(n>P@r1P2i)yk3DgRwbHeNn6e|;J^SK4TM LH~i+q&mR8;k>NTA literal 0 HcmV?d00001 diff --git a/applications/plugins/bt_hid_app/assets/ButtonUp_7x4.png b/applications/plugins/bt_hid_app/assets/ButtonUp_7x4.png new file mode 100644 index 0000000000000000000000000000000000000000..1be79328b40a93297a5609756328406565c437c0 GIT binary patch literal 102 zcmeAS@N?(olHy`uVBq!ia0vp^>_E)I!3HFqj;YoHDIHH2#}J8d-yTOk1_O>mFaFD) zeWb+ZHz{mGZZ1QpXe09^4tcYT#4oe=UbmGC^A-KE*|F&zP#=S*tDnm{r-UX30HgpM AM*si- literal 0 HcmV?d00001 diff --git a/applications/plugins/bt_hid_app/assets/Ok_btn_9x9.png b/applications/plugins/bt_hid_app/assets/Ok_btn_9x9.png new file mode 100644 index 0000000000000000000000000000000000000000..9a1539da2049f12f7b25f96b11a9c40cd8227302 GIT binary patch literal 3605 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 QX&eA7&1_6djNPvM5BL~PlmGw# literal 0 HcmV?d00001 diff --git a/applications/plugins/bt_hid_app/assets/Pin_arrow_left_9x7.png b/applications/plugins/bt_hid_app/assets/Pin_arrow_left_9x7.png new file mode 100644 index 0000000000000000000000000000000000000000..fb4ded78fde8d1bf4f053ba0b353e7acf14f031a GIT binary patch literal 3603 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 literal 0 HcmV?d00001 diff --git a/applications/plugins/bt_hid_app/assets/Pin_arrow_up_7x9.png b/applications/plugins/bt_hid_app/assets/Pin_arrow_up_7x9.png new file mode 100644 index 0000000000000000000000000000000000000000..a91a6fd5e99a72112e28865cd8a004c7d1933fff GIT binary patch literal 3603 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 TFC+3}9cE|kU{!4CefEC<&8td2 literal 0 HcmV?d00001 diff --git a/applications/plugins/music_player/application.fam b/applications/plugins/music_player/application.fam index 76787e09719..a3698898332 100644 --- a/applications/plugins/music_player/application.fam +++ b/applications/plugins/music_player/application.fam @@ -11,8 +11,9 @@ App( provides=["music_player_start"], stack_size=2 * 1024, order=20, - fap_icon="../../../assets/icons/Archive/music_10px.png", + fap_icon="icons/music_10px.png", fap_category="Misc", + fap_icon_assets="icons", ) App( diff --git a/applications/plugins/music_player/icons/music_10px.png b/applications/plugins/music_player/icons/music_10px.png new file mode 100644 index 0000000000000000000000000000000000000000..d41eb0db8c822c60be6c097393b3682680b81a6c GIT binary patch literal 142 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2xGmzZ=C-xtZVhivIasA%~WHIdey2}7aaTa() z7Bet#3xhBt!>l_x9Asc&(0a_=e)W&n8K5!-Pgg&ebxsLQ0Ao%f>i_@% literal 0 HcmV?d00001 diff --git a/applications/plugins/music_player/music_player.c b/applications/plugins/music_player/music_player.c index 192500c2e6c..28872284bda 100644 --- a/applications/plugins/music_player/music_player.c +++ b/applications/plugins/music_player/music_player.c @@ -3,7 +3,7 @@ #include #include -#include +#include #include #include #include diff --git a/applications/plugins/picopass/application.fam b/applications/plugins/picopass/application.fam index bbe37e0649a..88460b4ff80 100644 --- a/applications/plugins/picopass/application.fam +++ b/applications/plugins/picopass/application.fam @@ -17,4 +17,5 @@ App( name="loclass", ), ], + fap_icon_assets="icons", ) diff --git a/applications/plugins/picopass/icons/DolphinMafia_115x62.png b/applications/plugins/picopass/icons/DolphinMafia_115x62.png new file mode 100644 index 0000000000000000000000000000000000000000..66fdb40ff2651916faed4a2ae1d564cafdbf7bcb GIT binary patch literal 2504 zcmbVO3se(V8ji5Kg5ZmV3I#ewZHbsZB8b0=g#+k z_y7La$vcsnwa$(njyxXES*27&ad(Ehf@a%szx(??vFC0MMrAy=Img9%&ES885R5X2P@K{dB9p<$p?SQ()g~i~r4cNkC3JdH&i}sg6d%yza(--p8dMv@ zh!njtmnNcfH8EIj8V2M1)j>d@3E>C~1d9SDLpsSICOLnC7va{{Z80C1fUs$Deu(uz zAWj_#gi$mBNJXF!13?Io!6J#&-(L!@03Z+o#bAI~0tqEj1oTHFGGOY%=T4*XWF$)Q z`>C_ICpkZbWsQhfoSmI5%Jvgcv`#F6VOR`8Vh9p)2qBY0vZzT&GE1fz6a<6OdLyf+ zNWjX7YNwhuAF&nut zlTM%T7{|m!I$L;81?0JCCML&7h@%LG%A_%3 zO%`|J5~~^`5=Ij!OVKeDl|G%Q$Z2^1BoRS?PpqEAscc5@GXp|_vV@$^WlbUkA?_Ok zK?n#V5acTX5fGe&s<}GAQ5OB*z!a`e&iO^CEx1S+l}^!W3g`Ur;{!N`BvZ5jW@%VH?>OF2Tk@O zPGOv@&rGEfX|lv0Cxk2gKu)ie6Af#Vr9x}>!CI+Aiv@szVry$~6u{(al2-hTBEgTzn_D^}jklllIvu1V{Q`ig6OgP|0jI zN)sVEE|=@hm?j7H6PqgYzU5==|fB0<6@J90B?N8); z?B48M`Q6&q<>QYftD|a*tJ$!0YduA;TS}(23t@i9jJ}9E&d>+O-{j}lDtd6mP7wiU?pLh0* zla-TQ!!6f>9b(>jct-Z*@vzVmEjaUp9adYyRH)W#u&{1)0G7#K8z}OOe9Z4J`?k~5 z;u#n4^?R%GdBZDjly!H8xtVMF9ud_Q|CsUp%X4BI?jMd19&&9{QqgG_a)Rz9J*BH| z$zM9cbZYA6R(n(=QYD(cO(#Aoy6CQh;hG<}_gRz&>ZIovmNuT&Z9VwM8m5pu&$kG$ zvTJ!+pA|E6E-UBtJJrv;*XaRo7|Z#x4L(qON`UQa?6`jZqnkg3XliTEuJKo%PCa~M z@WlnE3u1ZRT?c;b@m&$07PGImr1km-TQZ8*DS|rZudw{x4R!5F9=$VOt{XWj(Y>BT zd-yG`a(KJ-o0Dfs8h&U=J*C(_ z=8hNq6aC?^r7wqGy5!v`zvX@KNEDDEpXqBVXiB`Z=eNZRgGG2tG`F;x~xDn9)G1Y@4Fl28Px*E!|ivy@~-8Lx%@`DyQ}?V z4f!BGF*jl}N~1D%!=YeZY6W)9lyDw_Uq#NDJx^=CJZDD2|CF# zA7Ixt{Z7BT8@4fZgFkI{D9fJxang<$JS``+d(*81cbB@prG*c!rZ)8U4y-<__Pt)Z zZ3lJfK;Y5eZHd?A3O-!mWX3$UChhmy)r@4iKkvyz(mdTtF7?TWn4`7t4=} zZ`OLe!fHzEo3eUH7jwVD-n?Xnx$AC<-H6`;RB2iYH9UO}ROfZkPOl32mRZ%`xW#FL zD@GqK${E&#=gzidc(qkxLZ^tk7u}u0Uu|;00}}A@rq4$9xE75>Hwj!4$Nk!`)YmDg{{4HeKCy?7Z85xPzg%Peucca}QJ6#D*z!+`G0ZOj literal 0 HcmV?d00001 diff --git a/applications/plugins/picopass/icons/DolphinNice_96x59.png b/applications/plugins/picopass/icons/DolphinNice_96x59.png new file mode 100644 index 0000000000000000000000000000000000000000..a299d3630239b4486e249cc501872bed5996df3b GIT binary patch literal 2459 zcmbVO3s4i+8V(M(gEFORwSrA`4O0uPn|M|5y* zB*aMDxC&7(gP9JN;POOi-9khrC>Z9YJs2U!LnVcQEEC0fDtKo&ILlzb30%M}3J^;~ zv7RzcsilOs4Mq@tD*&R;!LMSk2A~{(`HK9|hQBqEX)3sQr9Je6SZU*F-^fD-p+~Hs; zHLkO%v?>ZoxEv+F#whudr%615FkA0DYR0tMEo}3OOY#xecLWe>xV?u5KtSmC^ z7)Fmj6gjfKstiEV-*Cxbbb+&rRWuI_rBJ)ybs_f1Rn&f2>q3pYwI^|J(hdn{j{0EZIm_F zpIyIWLsRUgOItR-dUbVd|6Zo=_BU_Tj4|{{jxO#=JH4o8er(5{!nZD_j4}MH&zh~9 zVLC~y(0-D6GO0ghZD8BYzP?o{>22~lT6^d@X{SwQ8vrNY-PPIMajIwC)`s14Ep72@ zeq7YOzM`?U{+W)ocXBr`eSOcpk?Rxc=ou5&)fWW|pD};-Z0mvk9}=&`Rb&y<77W~a z(>6YM;6Y5aIU~JKZ}mQZynKHiSTQ#Bczn@&jTiN^?vPJ(jhm7cXLx0oum5P$`TceG zU+wR;OO^)8CVlnM)5p$CO&e94KJt>HccCaHGusmW_b`T6m| z-R6V6Db1pErTot?^d22ojm+2>_)FbD`_+WbDGMx9f@hO27maS2`csiV(D&Fs`PS2& zvrq18du_&zXID(!KIxsU$)iuTYuZ?zmYiP&n&i@Be{IdbS-jA2c0QAlu5NXQv_0K< z3Hvs4eeu6B7yD&CNT~gIkMV&UkRU=V!iQ(+_(O&u^ah$+s{_yn(yBYeD40HeU{xGsIT6W Zfq!wOp!Q<>&pI=m5)b(dHL6nbwD9yPZ!4!j_b)k${QZ;XFmLn zjqNsD+YPq1J8W%_*aXBie!OR3*tC!PwU_7Q9H4U564!{5l*E!$tK_0oAjM#0U}UIk zV5)0q5@Kj%Wo%$&Y@uynU}a#izM}XGiiX_$l+3hBs0L%8o)7~QD^p9LQiz6svOM}g O4Gf;HelF{r5}E+GUQp8j literal 0 HcmV?d00001 diff --git a/applications/plugins/picopass/icons/RFIDDolphinReceive_97x61.png b/applications/plugins/picopass/icons/RFIDDolphinReceive_97x61.png new file mode 100644 index 0000000000000000000000000000000000000000..e1f5f9f8017b49ab1ea78a9394ed8ea7684e3083 GIT binary patch literal 1421 zcmaJ>eQXnD7{Agv#{x2p_yIvKhl^r%z3;AfTbW%yMuEcUiZn{X?&IxtS=&4BZnPU= zlx>i)WI>$aQvw7<5XB`bIuRj@1WD9@Lr2D(5=YPwGc*zsSTf&kEAj{7lDqeL-}m`F zpTFm}Rj;U;Sva>4L6DijCB86RMfkc4?C{(9_MQ1~dCu}jtr{(6r9=ZD9z~M?8cc|F zAPhvM>5U7Z96{_EH4?R=q2+?CB^+W_$B|Cx5RD+^6=_|R8-RsMpiWJ?vC&g!FjQ6C z*cvWGhIB8eSC=#!pr(06L~d@7c?GLjjFzVbXdnSB5ltuJNmEF>u?f2Zl(WYKhEAwh z4Q^~QsA#Af^=bw{oemP0Nz#dy@(x9mL|KwbP@1GEf@BGb#Ys|Nc!6cnsRx7Z3?(Ln zeSs-waOcMAElU>&B9%%xQj9}0>IjPGd4i+~n#Q39ZZ;(?F^wn9g*gj8V9JK7TdI~s zvlc~3YqZ=L40SSxgdPgrH=H!5Dg|psq(z;e93+uQWD}dvHmxxDKa7WJn~^3R5Mf|y zjfM;x5?h!9!{R;KQC1N~Bdj!3*cCDE)8xhkNLoRk8-q6vMO6faWjLq8D>(0>TsY@s zOL2+gT{ut5lFP+&G;q>6I}gJ}uK`3$Ga{N6&(WZ|Ub8f_Uei&p7kz1snpCuuxhUJA$%K8tP}c(` zU}y<+qQrvwF!u}>V`HR<(=n36VBt1B^`_fx&WPzU_AMY;oo7=&mIg2tu^u1f5 z)@y#lGg2HF{icooYxXeey6HJl+%===Q-Yg*f$J(< z+gbGCvVprluc__jmS6m=F>l7JjJ;Cb^sMdho~B4w{1|(u#k_H5R;4;`zs)u0gC*%S zI_>C5rsHbY>U}-r=8b&^Mh7zat>Eaqs$E;p%^t}^&M*C`d_!V*2g<#^ZLQq9;N6x= zv^)OzpYh#+OwHKfQ+kHHZreNi()*6Nw&PX5?kxF@U2EB*+}LH?toC1`{oRjksXb78 zx8u;V!Qv~6!ySjp4u16f-y8F;3}d=*b!=ao^)Gw)nS({6qa!CbyuwrWMvi?_zz4rL rb-KI#{JuTj%qEZPotyLfwj*}ruaRky;O7Gyvp>k7e}(TvWo_$!Vg&g_ literal 0 HcmV?d00001 diff --git a/applications/plugins/picopass/icons/RFIDDolphinSend_97x61.png b/applications/plugins/picopass/icons/RFIDDolphinSend_97x61.png new file mode 100644 index 0000000000000000000000000000000000000000..380a970d9004cba5520560fd9aa24aa42924e2a1 GIT binary patch literal 1418 zcmaJ>eQXnD7{6`EHeeAZ8ii;s2g4C}y=!}S>mBQswzrLLl$EYRfoyOe?`_T2-g$Sk z9pb2CPQ(a0XM$+dKhO~O0nx;1L}kUOGdh8Ve-^?LG)CD7lOfXp&bQl&{IPJ!-TS=n z`~05I-*YefH&^B@S+xW~kUZ~3J^)t%zRsL1_&wM?{Wx46Gs{C}t*V$YK?jISRz-k% zBSHfR06}hjW(brZNLC^o44EO{CQec#79pi$iAOYuMv#)SxF$$Vz(hsR5RN*rYhQeg zp<&sHZKHjpPxFAr@WwqlsNJ(UDD7#ISQ#rTMN8rwG!Ox%fW{-uQG<&+v01wulvBq9 zhR&*(O-^hssF2T(dQ=^tjD^G{l4Q_g)*=g{AcBJ%MzrGu-R~^fg7z+Q;6eHV@=uu4-82U zYi3xDqA81lsJ56+42C+FLqzlW?i!97^Ob@%BjSQaSS=(GiKG&n)i%rk_&3wK+`#f1_%uMx&~s9uHc$EgY5An6W<9p}B;4 zpogCYa)qu&(Ag4m;RW0?c3PnnQowBrN#lw@*>TY-L0(ZbMKQG9>QDeSkC*Q$-5f{Z z2~0stN5WYZfssX-#AS)L;{r{IxG2~K90(F6+7!i3SxJn5ArdLp+{2>u5u|2HygL+d zb9byj6wZ} zqrIB@aESUiV~B&zwY0sUci%;mf;cmkA+7cD0^$ih9{f{w;v_DJ`sY;R`f3( z?7BXf_vMbW zuU1_w753GAG_~{axB58aI?KM!#N|b)zyZV)ZU9QaOj9KuN$fX{&>fy=f`f8Io+CbZIMpovDCx1HL z?$&C^=R1DyispWLc%|FSKGs*ccUMOLz=7=zt7r7(!|y7;X08;c-@aJ>V5pwIR`S;) wTk7+73`}?J{<7dJ@~ literal 0 HcmV?d00001 diff --git a/applications/plugins/picopass/picopass_device.c b/applications/plugins/picopass/picopass_device.c index b6e69cc21bd..199b79e97ac 100644 --- a/applications/plugins/picopass/picopass_device.c +++ b/applications/plugins/picopass/picopass_device.c @@ -2,6 +2,7 @@ #include #include +#include #define TAG "PicopassDevice" diff --git a/applications/plugins/picopass/picopass_i.h b/applications/plugins/picopass/picopass_i.h index 8e011f22249..469a672b7e7 100644 --- a/applications/plugins/picopass/picopass_i.h +++ b/applications/plugins/picopass/picopass_i.h @@ -24,6 +24,7 @@ #include #include +#include #define PICOPASS_TEXT_STORE_SIZE 128 diff --git a/applications/plugins/signal_generator/application.fam b/applications/plugins/signal_generator/application.fam index 7794ee492c6..de915733c49 100644 --- a/applications/plugins/signal_generator/application.fam +++ b/applications/plugins/signal_generator/application.fam @@ -9,4 +9,5 @@ App( order=50, fap_icon="signal_gen_10px.png", fap_category="Tools", + fap_icon_assets="icons", ) diff --git a/applications/plugins/signal_generator/icons/SmallArrowDown_3x5.png b/applications/plugins/signal_generator/icons/SmallArrowDown_3x5.png new file mode 100644 index 0000000000000000000000000000000000000000..1912e5d246268d75a20984bdc8b996d503f3d166 GIT binary patch literal 3592 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 BDRuw= literal 0 HcmV?d00001 diff --git a/applications/plugins/signal_generator/icons/SmallArrowUp_3x5.png b/applications/plugins/signal_generator/icons/SmallArrowUp_3x5.png new file mode 100644 index 0000000000000000000000000000000000000000..9c6242078d3bcc9a93eb55b6b5f358720ee6662c GIT binary patch literal 7976 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 +#include typedef enum { LineIndexChannel, diff --git a/applications/plugins/weather_station/images/Lock_7x8.png b/applications/plugins/weather_station/images/Lock_7x8.png new file mode 100644 index 0000000000000000000000000000000000000000..f7c9ca2c702f1b93d7d06bd12ae708655c79d7c8 GIT binary patch literal 3597 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 a9|QyVQ~@!E@+ey_4zMz}H7hmoyzn0`d`!~- literal 0 HcmV?d00001 diff --git a/applications/plugins/weather_station/images/Pin_back_arrow_10x8.png b/applications/plugins/weather_station/images/Pin_back_arrow_10x8.png new file mode 100644 index 0000000000000000000000000000000000000000..3bafabd144864b575144c75b592e5eaf53974566 GIT binary patch literal 3606 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 TFC+3}9cE|kU{!4CefEC<&8td2 literal 0 HcmV?d00001 diff --git a/applications/plugins/weather_station/images/Quest_7x8.png b/applications/plugins/weather_station/images/Quest_7x8.png new file mode 100644 index 0000000000000000000000000000000000000000..6825247fbeaf98b4d0d9f8aa15d4f2c5f45dcf3c GIT binary patch literal 3675 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 N!3~Mzlm%~0{|8*@s)qmo literal 0 HcmV?d00001 diff --git a/applications/plugins/weather_station/images/Unlock_7x8.png b/applications/plugins/weather_station/images/Unlock_7x8.png new file mode 100644 index 0000000000000000000000000000000000000000..9d82b4daf3c722a5244f537f18b147833c2fb4ee GIT binary patch literal 3598 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 MrJY5ondim-0h+K&wEzGB literal 0 HcmV?d00001 diff --git a/applications/plugins/weather_station/images/WarningDolphin_45x42.png b/applications/plugins/weather_station/images/WarningDolphin_45x42.png new file mode 100644 index 0000000000000000000000000000000000000000..d766ffbb444db1739f2ccd030e506e8bada11ee8 GIT binary patch literal 1139 zcmaJ=TWAz#6rQ4%BpO*okt7zz3Bkm=bK9L=XV}qhW_HceY#KHzP4Z$UGyf(-b}pUy z<8ESW=_MtCk6tj|`k)VrCbo(SMH0n`eW+KIU`wG5O`$IeMg)Vzf7adDhv+af=lqBB zo%5Z`zqhqzdu2s+1%_dji6%LPq#u2o%9fzN?;7Dlq6)^^VVjkKImH23RI|DPo-mXi zkOGP}@Wrnnf?-SQ^>jOIPc{pxWsr*JL*@+|p)oA7EpIDoAAoo_=+RA)c=F3Qf$N$` ze9k55q%DD7y=l+^ZG$aob+Aw6HDcRVJdzhs00Te;&l_3O74jlch$|r7GgAa!aDjay z@rG1;vK5ys2jF3n@vAgV<6)izn!k6Qhd>D(EhD7l zcrhJ1i9|1iwm?z2T#n2INXzM=7@p@Tnx$CQk39VDfC-hn-*jtB5oF-1j&4KUGI1}W z(rxuakw9eMRAJc3%8 zlBq3$QTyJX$a6$&1ldyi4Pe5AEE32Sg@vDzY~7qR?1u@oXh zd9(fBtV<@eK%Tm=yy&p7{=h^#@1W)WFFSe!U5pP~o71uR`FW)7xc*=d5_b}EG@XBZ z@<7MRp-;+|o}SzJvMyfx>37Y4etBizf%0H*zaIF?2ObZt?$D;{o|esJ z*PgAOIO^*8tL%z2)|}k$DP5kN5}8qo*wNa8y?R2=%wO{gCD(`sHt}TzWQuK zMDIJap(nb2=7*ns*oDcRBbC23yy`kvKYV`ovU`a=EmxNv-90;;5r6+l8hQTp^u`Hn XX6*+%8gHC`h)Tl}u@-r>vFqE{F~5F& literal 0 HcmV?d00001 diff --git a/applications/plugins/weather_station/views/weather_station_receiver.c b/applications/plugins/weather_station/views/weather_station_receiver.c index d30b7926b2e..61b1526029c 100644 --- a/applications/plugins/weather_station/views/weather_station_receiver.c +++ b/applications/plugins/weather_station/views/weather_station_receiver.c @@ -1,11 +1,10 @@ #include "weather_station_receiver.h" #include "../weather_station_app_i.h" -#include "weather_station_icons.h" +#include #include #include #include -#include #include #define FRAME_HEIGHT 12 diff --git a/applications/services/bt/bt_service/bt.c b/applications/services/bt/bt_service/bt.c index c003013e412..62b5ab109ec 100644 --- a/applications/services/bt/bt_service/bt.c +++ b/applications/services/bt/bt_service/bt.c @@ -4,6 +4,7 @@ #include #include +#include #define TAG "BtSrv" diff --git a/applications/services/desktop/views/desktop_view_lock_menu.c b/applications/services/desktop/views/desktop_view_lock_menu.c index 8cb8a7a12f9..486be23b5e8 100644 --- a/applications/services/desktop/views/desktop_view_lock_menu.c +++ b/applications/services/desktop/views/desktop_view_lock_menu.c @@ -1,5 +1,6 @@ #include #include +#include #include "../desktop_i.h" #include "desktop_view_lock_menu.h" diff --git a/applications/services/desktop/views/desktop_view_locked.c b/applications/services/desktop/views/desktop_view_locked.c index 915b26103c6..d18ed6c98c0 100644 --- a/applications/services/desktop/views/desktop_view_locked.c +++ b/applications/services/desktop/views/desktop_view_locked.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include diff --git a/applications/services/desktop/views/desktop_view_pin_input.c b/applications/services/desktop/views/desktop_view_pin_input.c index bf05f06b9c1..b86bf2929bf 100644 --- a/applications/services/desktop/views/desktop_view_pin_input.c +++ b/applications/services/desktop/views/desktop_view_pin_input.c @@ -2,6 +2,7 @@ #include #include #include +#include #include #include diff --git a/applications/services/dialogs/dialogs_api.c b/applications/services/dialogs/dialogs_api.c index 6fd51782de8..ee959a33c41 100644 --- a/applications/services/dialogs/dialogs_api.c +++ b/applications/services/dialogs/dialogs_api.c @@ -1,6 +1,7 @@ #include "dialogs/dialogs_message.h" #include "dialogs_i.h" #include "dialogs_api_lock.h" +#include /****************** File browser ******************/ diff --git a/applications/services/gui/canvas.h b/applications/services/gui/canvas.h index a67e58494a5..a3df5adc7f9 100644 --- a/applications/services/gui/canvas.h +++ b/applications/services/gui/canvas.h @@ -7,7 +7,7 @@ #include #include -#include +#include #ifdef __cplusplus extern "C" { diff --git a/applications/services/gui/gui.c b/applications/services/gui/gui.c index 42712ed9065..2d06d70c7c9 100644 --- a/applications/services/gui/gui.c +++ b/applications/services/gui/gui.c @@ -1,5 +1,6 @@ #include "gui/canvas.h" #include "gui_i.h" +#include #define TAG "GuiSrv" diff --git a/applications/services/gui/icon_animation.h b/applications/services/gui/icon_animation.h index dab9d996dae..684790353a8 100644 --- a/applications/services/gui/icon_animation.h +++ b/applications/services/gui/icon_animation.h @@ -7,7 +7,7 @@ #include #include -#include +#include #ifdef __cplusplus extern "C" { diff --git a/applications/services/gui/modules/button_menu.c b/applications/services/gui/modules/button_menu.c index 37a04326ae0..ff12a93117b 100644 --- a/applications/services/gui/modules/button_menu.c +++ b/applications/services/gui/modules/button_menu.c @@ -5,6 +5,7 @@ #include #include #include +#include #define ITEM_FIRST_OFFSET 17 #define ITEM_NEXT_OFFSET 4 diff --git a/applications/services/gui/modules/byte_input.c b/applications/services/gui/modules/byte_input.c index 8d7e7fd4f91..bc19f0eeeff 100644 --- a/applications/services/gui/modules/byte_input.c +++ b/applications/services/gui/modules/byte_input.c @@ -1,6 +1,7 @@ -#include "byte_input.h" -#include #include +#include +#include +#include "byte_input.h" struct ByteInput { View* view; diff --git a/applications/services/gui/modules/menu.c b/applications/services/gui/modules/menu.c index db0717f7760..6983e0108b1 100644 --- a/applications/services/gui/modules/menu.c +++ b/applications/services/gui/modules/menu.c @@ -2,6 +2,7 @@ #include #include +#include #include struct Menu { diff --git a/applications/services/gui/modules/text_input.c b/applications/services/gui/modules/text_input.c index 79fa8772870..540e4b7c48b 100644 --- a/applications/services/gui/modules/text_input.c +++ b/applications/services/gui/modules/text_input.c @@ -1,5 +1,6 @@ #include "text_input.h" #include +#include #include struct TextInput { diff --git a/applications/services/power/power_service/power_i.h b/applications/services/power/power_service/power_i.h index 66ced885b16..8cb5140d77d 100644 --- a/applications/services/power/power_service/power_i.h +++ b/applications/services/power/power_service/power_i.h @@ -5,6 +5,7 @@ #include #include #include +#include #include #include "views/power_off.h" diff --git a/applications/services/power/power_service/views/power_off.c b/applications/services/power/power_service/views/power_off.c index b0046325c5b..f14a18d7ee8 100644 --- a/applications/services/power/power_service/views/power_off.c +++ b/applications/services/power/power_service/views/power_off.c @@ -1,6 +1,7 @@ #include "power_off.h" #include #include +#include struct PowerOff { View* view; diff --git a/applications/services/power/power_service/views/power_unplug_usb.c b/applications/services/power/power_service/views/power_unplug_usb.c index 5632cd8b05b..c2d61139e4b 100644 --- a/applications/services/power/power_service/views/power_unplug_usb.c +++ b/applications/services/power/power_service/views/power_unplug_usb.c @@ -1,6 +1,7 @@ #include "power_unplug_usb.h" #include #include +#include struct PowerUnplugUsb { View* view; diff --git a/applications/services/storage/storage.c b/applications/services/storage/storage.c index 9079a95eda3..700408c9d07 100644 --- a/applications/services/storage/storage.c +++ b/applications/services/storage/storage.c @@ -5,6 +5,7 @@ #include "storage/storage_glue.h" #include "storages/storage_int.h" #include "storages/storage_ext.h" +#include #define STORAGE_TICK 1000 diff --git a/applications/settings/about/about.c b/applications/settings/about/about.c index a42969b2bfe..1719e188d5c 100644 --- a/applications/settings/about/about.c +++ b/applications/settings/about/about.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include diff --git a/applications/settings/bt_settings_app/bt_settings_app.h b/applications/settings/bt_settings_app/bt_settings_app.h index c45ff3db0fb..b79e3695115 100644 --- a/applications/settings/bt_settings_app/bt_settings_app.h +++ b/applications/settings/bt_settings_app/bt_settings_app.h @@ -6,6 +6,7 @@ #include #include #include +#include #include #include diff --git a/applications/settings/desktop_settings/desktop_settings_app.h b/applications/settings/desktop_settings/desktop_settings_app.h index fc56c3253ac..6f97564c94d 100644 --- a/applications/settings/desktop_settings/desktop_settings_app.h +++ b/applications/settings/desktop_settings/desktop_settings_app.h @@ -7,6 +7,7 @@ #include #include #include +#include #include #include diff --git a/applications/settings/power_settings_app/power_settings_app.h b/applications/settings/power_settings_app/power_settings_app.h index 8429b54b4bb..cd05846c055 100644 --- a/applications/settings/power_settings_app/power_settings_app.h +++ b/applications/settings/power_settings_app/power_settings_app.h @@ -6,6 +6,7 @@ #include #include #include +#include #include "views/battery_info.h" #include diff --git a/applications/settings/power_settings_app/views/battery_info.c b/applications/settings/power_settings_app/views/battery_info.c index 1a8bc71ec05..e1b7adb4bd8 100644 --- a/applications/settings/power_settings_app/views/battery_info.c +++ b/applications/settings/power_settings_app/views/battery_info.c @@ -1,6 +1,7 @@ #include "battery_info.h" #include #include +#include struct BatteryInfo { View* view; diff --git a/applications/settings/storage_settings/storage_settings.h b/applications/settings/storage_settings/storage_settings.h index 4cf185e0c90..664e74c848d 100644 --- a/applications/settings/storage_settings/storage_settings.h +++ b/applications/settings/storage_settings/storage_settings.h @@ -4,6 +4,7 @@ #include #include #include +#include #include #include diff --git a/applications/system/updater/views/updater_main.c b/applications/system/updater/views/updater_main.c index 5ed3c70aa9d..1199cc882aa 100644 --- a/applications/system/updater/views/updater_main.c +++ b/applications/system/updater/views/updater_main.c @@ -2,6 +2,7 @@ #include #include #include +#include #include #include diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index fd11dffe0e3..00ba30c24c1 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,6.0,, +Version,+,7.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -2661,191 +2661,7 @@ Function,-,yn,double,"int, double" Function,-,ynf,float,"int, float" Variable,-,AHBPrescTable,const uint32_t[16], Variable,-,APBPrescTable,const uint32_t[8], -Variable,+,A_125khz_14,const Icon, -Variable,+,A_BadUsb_14,const Icon, -Variable,+,A_Debug_14,const Icon, -Variable,+,A_FileManager_14,const Icon, -Variable,+,A_GPIO_14,const Icon, -Variable,+,A_Infrared_14,const Icon, -Variable,+,A_Levelup1_128x64,const Icon, -Variable,+,A_Levelup2_128x64,const Icon, -Variable,+,A_Loading_24,const Icon, -Variable,+,A_NFC_14,const Icon, -Variable,+,A_Plugins_14,const Icon, -Variable,+,A_Round_loader_8x8,const Icon, -Variable,+,A_Settings_14,const Icon, -Variable,+,A_Sub1ghz_14,const Icon, -Variable,+,A_U2F_14,const Icon, -Variable,+,A_iButton_14,const Icon, Variable,-,ITM_RxBuffer,volatile int32_t, -Variable,+,I_125_10px,const Icon, -Variable,+,I_ActiveConnection_50x64,const Icon, -Variable,+,I_Alert_9x8,const Icon, -Variable,+,I_ArrowC_1_36x36,const Icon, -Variable,+,I_ArrowDownEmpty_14x15,const Icon, -Variable,+,I_ArrowDownFilled_14x15,const Icon, -Variable,+,I_ArrowUpEmpty_14x15,const Icon, -Variable,+,I_ArrowUpFilled_14x15,const Icon, -Variable,+,I_Attention_5x8,const Icon, -Variable,+,I_Auth_62x31,const Icon, -Variable,+,I_BLE_Pairing_128x64,const Icon, -Variable,+,I_Background_128x11,const Icon, -Variable,+,I_BatteryBody_52x28,const Icon, -Variable,+,I_Battery_16x16,const Icon, -Variable,+,I_Battery_26x8,const Icon, -Variable,+,I_Ble_connected_15x15,const Icon, -Variable,+,I_Ble_disconnected_15x15,const Icon, -Variable,+,I_Bluetooth_Connected_16x8,const Icon, -Variable,+,I_Bluetooth_Idle_5x8,const Icon, -Variable,+,I_ButtonCenter_7x7,const Icon, -Variable,+,I_ButtonDown_7x4,const Icon, -Variable,+,I_ButtonLeftSmall_3x5,const Icon, -Variable,+,I_ButtonLeft_4x7,const Icon, -Variable,+,I_ButtonRightSmall_3x5,const Icon, -Variable,+,I_ButtonRight_4x7,const Icon, -Variable,+,I_ButtonUp_7x4,const Icon, -Variable,+,I_Button_18x18,const Icon, -Variable,+,I_Certification1_103x56,const Icon, -Variable,+,I_Certification2_98x33,const Icon, -Variable,+,I_Charging_lightning_9x10,const Icon, -Variable,+,I_Charging_lightning_mask_9x10,const Icon, -Variable,+,I_Circles_47x47,const Icon, -Variable,+,I_Clock_18x18,const Icon, -Variable,+,I_Connect_me_62x31,const Icon, -Variable,+,I_Connected_62x31,const Icon, -Variable,+,I_CoolHi_25x27,const Icon, -Variable,+,I_CoolHi_hvr_25x27,const Icon, -Variable,+,I_CoolLo_25x27,const Icon, -Variable,+,I_CoolLo_hvr_25x27,const Icon, -Variable,+,I_Cry_dolph_55x52,const Icon, -Variable,+,I_DFU_128x50,const Icon, -Variable,+,I_Dehumidify_25x27,const Icon, -Variable,+,I_Dehumidify_hvr_25x27,const Icon, -Variable,+,I_Detailed_chip_17x13,const Icon, -Variable,+,I_DolphinCommon_56x48,const Icon, -Variable,+,I_DolphinMafia_115x62,const Icon, -Variable,+,I_DolphinNice_96x59,const Icon, -Variable,+,I_DolphinReadingSuccess_59x63,const Icon, -Variable,+,I_DolphinWait_61x59,const Icon, -Variable,+,I_DoorLeft_70x55,const Icon, -Variable,+,I_DoorRight_70x55,const Icon, -Variable,+,I_Down_25x27,const Icon, -Variable,+,I_Down_hvr_25x27,const Icon, -Variable,+,I_Drive_112x35,const Icon, -Variable,+,I_Error_18x18,const Icon, -Variable,+,I_Error_62x31,const Icon, -Variable,+,I_EviSmile1_18x21,const Icon, -Variable,+,I_EviSmile2_18x21,const Icon, -Variable,+,I_EviWaiting1_18x21,const Icon, -Variable,+,I_EviWaiting2_18x21,const Icon, -Variable,+,I_FaceCharging_29x14,const Icon, -Variable,+,I_FaceConfused_29x14,const Icon, -Variable,+,I_FaceNopower_29x14,const Icon, -Variable,+,I_FaceNormal_29x14,const Icon, -Variable,+,I_GameMode_11x8,const Icon, -Variable,+,I_Health_16x16,const Icon, -Variable,+,I_HeatHi_25x27,const Icon, -Variable,+,I_HeatHi_hvr_25x27,const Icon, -Variable,+,I_HeatLo_25x27,const Icon, -Variable,+,I_HeatLo_hvr_25x27,const Icon, -Variable,+,I_Hidden_window_9x8,const Icon, -Variable,+,I_InfraredArrowDown_4x8,const Icon, -Variable,+,I_InfraredArrowUp_4x8,const Icon, -Variable,+,I_InfraredLearnShort_128x31,const Icon, -Variable,+,I_KeyBackspaceSelected_16x9,const Icon, -Variable,+,I_KeyBackspace_16x9,const Icon, -Variable,+,I_KeySaveSelected_24x11,const Icon, -Variable,+,I_KeySave_24x11,const Icon, -Variable,+,I_Keychain_39x36,const Icon, -Variable,+,I_Left_mouse_icon_9x9,const Icon, -Variable,+,I_Lock_7x8,const Icon, -Variable,+,I_Lock_8x8,const Icon, -Variable,+,I_MHz_25x11,const Icon, -Variable,+,I_Medium_chip_22x21,const Icon, -Variable,+,I_Modern_reader_18x34,const Icon, -Variable,+,I_Move_flipper_26x39,const Icon, -Variable,+,I_Mute_25x27,const Icon, -Variable,+,I_Mute_hvr_25x27,const Icon, -Variable,+,I_NFC_manual_60x50,const Icon, -Variable,+,I_Nfc_10px,const Icon, -Variable,+,I_Off_25x27,const Icon, -Variable,+,I_Off_hvr_25x27,const Icon, -Variable,+,I_Ok_btn_9x9,const Icon, -Variable,+,I_Ok_btn_pressed_13x13,const Icon, -Variable,+,I_Percent_10x14,const Icon, -Variable,+,I_Pin_arrow_down_7x9,const Icon, -Variable,+,I_Pin_arrow_left_9x7,const Icon, -Variable,+,I_Pin_arrow_right_9x7,const Icon, -Variable,+,I_Pin_arrow_up_7x9,const Icon, -Variable,+,I_Pin_attention_dpad_29x29,const Icon, -Variable,+,I_Pin_back_arrow_10x8,const Icon, -Variable,+,I_Pin_back_full_40x8,const Icon, -Variable,+,I_Pin_pointer_5x3,const Icon, -Variable,+,I_Pin_star_7x7,const Icon, -Variable,+,I_Power_25x27,const Icon, -Variable,+,I_Power_hvr_25x27,const Icon, -Variable,+,I_Pressed_Button_13x13,const Icon, -Variable,+,I_Quest_7x8,const Icon, -Variable,+,I_RFIDBigChip_37x36,const Icon, -Variable,+,I_RFIDDolphinReceive_97x61,const Icon, -Variable,+,I_RFIDDolphinSend_97x61,const Icon, -Variable,+,I_RFIDDolphinSuccess_108x57,const Icon, -Variable,+,I_Reader_detect_43x40,const Icon, -Variable,+,I_Release_arrow_18x15,const Icon, -Variable,+,I_Restoring_38x32,const Icon, -Variable,+,I_Right_mouse_icon_9x9,const Icon, -Variable,+,I_SDQuestion_35x43,const Icon, -Variable,+,I_SDcardFail_11x8,const Icon, -Variable,+,I_SDcardMounted_11x8,const Icon, -Variable,+,I_Scanning_123x52,const Icon, -Variable,+,I_SmallArrowDown_3x5,const Icon, -Variable,+,I_SmallArrowDown_4x7,const Icon, -Variable,+,I_SmallArrowUp_3x5,const Icon, -Variable,+,I_SmallArrowUp_4x7,const Icon, -Variable,+,I_Smile_18x18,const Icon, -Variable,+,I_Space_65x18,const Icon, -Variable,+,I_Tap_reader_36x38,const Icon, -Variable,+,I_Temperature_16x16,const Icon, -Variable,+,I_Unlock_7x8,const Icon, -Variable,+,I_Unplug_bg_bottom_128x10,const Icon, -Variable,+,I_Unplug_bg_top_128x14,const Icon, -Variable,+,I_Up_25x27,const Icon, -Variable,+,I_Up_hvr_25x27,const Icon, -Variable,+,I_Updating_32x40,const Icon, -Variable,+,I_UsbTree_48x22,const Icon, -Variable,+,I_Vol_down_25x27,const Icon, -Variable,+,I_Vol_down_hvr_25x27,const Icon, -Variable,+,I_Vol_up_25x27,const Icon, -Variable,+,I_Vol_up_hvr_25x27,const Icon, -Variable,+,I_Voldwn_6x6,const Icon, -Variable,+,I_Voltage_16x16,const Icon, -Variable,+,I_Volup_8x6,const Icon, -Variable,+,I_WarningDolphin_45x42,const Icon, -Variable,+,I_Warning_30x23,const Icon, -Variable,+,I_back_10px,const Icon, -Variable,+,I_badusb_10px,const Icon, -Variable,+,I_dir_10px,const Icon, -Variable,+,I_iButtonDolphinVerySuccess_108x52,const Icon, -Variable,+,I_iButtonKey_49x44,const Icon, -Variable,+,I_ibutt_10px,const Icon, -Variable,+,I_ir_10px,const Icon, -Variable,+,I_loading_10px,const Icon, -Variable,+,I_music_10px,const Icon, -Variable,+,I_passport_bad1_46x49,const Icon, -Variable,+,I_passport_bad2_46x49,const Icon, -Variable,+,I_passport_bad3_46x49,const Icon, -Variable,+,I_passport_bottom_128x18,const Icon, -Variable,+,I_passport_happy1_46x49,const Icon, -Variable,+,I_passport_happy2_46x49,const Icon, -Variable,+,I_passport_happy3_46x49,const Icon, -Variable,+,I_passport_left_6x46,const Icon, -Variable,+,I_passport_okay1_46x49,const Icon, -Variable,+,I_passport_okay2_46x49,const Icon, -Variable,+,I_passport_okay3_46x49,const Icon, -Variable,+,I_sub1_10px,const Icon, -Variable,+,I_u2f_10px,const Icon, -Variable,+,I_unknown_10px,const Icon, -Variable,+,I_update_10px,const Icon, Variable,-,MSIRangeTable,const uint32_t[16], Variable,-,SmpsPrescalerTable,const uint32_t[4][6], Variable,+,SystemCoreClock,uint32_t, From 9cd0592aafb826ea08d180c857d399acdf607706 Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Fri, 28 Oct 2022 18:31:41 +0400 Subject: [PATCH 184/824] SubGhz: add keeloq potocol JCM_Tech (#1939) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * SubGhz: add keeloq potocol JCM_Tech * SubGhz: add new metod decoder Co-authored-by: あく --- assets/resources/subghz/assets/keeloq_mfcodes | 99 ++++++++++--------- lib/subghz/protocols/keeloq.c | 18 ++++ lib/subghz/protocols/keeloq_common.c | 30 +++++- lib/subghz/protocols/keeloq_common.h | 18 ++++ 4 files changed, 117 insertions(+), 48 deletions(-) diff --git a/assets/resources/subghz/assets/keeloq_mfcodes b/assets/resources/subghz/assets/keeloq_mfcodes index b8fc36903f2..1b27bfb01fb 100644 --- a/assets/resources/subghz/assets/keeloq_mfcodes +++ b/assets/resources/subghz/assets/keeloq_mfcodes @@ -1,50 +1,55 @@ Filetype: Flipper SubGhz Keystore File Version: 0 Encryption: 1 -IV: 2A 34 F1 5A AF 6F F5 1A 83 A6 1E DA DE B7 3D F1 -06B63DF24AE073A2F2B19C55CA9E8364FBECD26E49C551990153F6513BDE5267 -6139C78C74C341EB7474085CF1D047BD6FB005F80A72AF3EF3F89D58EF5DF500 -D85F11689020ECA47FBE9C2B67EE41A81E1F06DE2A35AF958965E3ECE29EA701 -1AE9073A42FE0E439544FE6945F6B33CF15A7A4A279020B5E0B3BE33FD189A7E -E161F007854BB33E0056FA09A2E2DEE66789B5C87C8D6D3DE2C8C1BD2B48983EB9D1C5697CA6E95996918F7C47B761B0 -59AE4644DCB3D720C38B5115F230DA58E7BE0A697907F6174BB05AB7886ACDB1 -634DF0BCC185C4C1F7E1B1594B4438D051ABAE092433078963063B51D961D08C -1EBEBCB49E498B9BE977D53EC21B9A546155B627737BD0AA832D496035729346 -4DFA93E639197772D57E8ACE04512CEFC045B8CC965C175A25ED525B630CBB63 -C2D5235D1014A319B249EAE8A5EE350F18D5AB8A498EF222704BD4EB1435F388 -F66D1937160E1392197F463A52E87FCE938A92070892113443C348D7553327A5715CF615CE2F2C96284F47759E043419 -841D29E7CBE040188E2283BFBA9F26EF2F65CCB085B56C3515E8C46C3F20BD75BAA963550869435FDAF509CEEE66A2C4 -7D87E24487D307635E7A17B989B8547EE11F3BF3468D055F0B44633B631BA42C -B4916043973501B95A82B329196D6EBA69FBBC3AF8FD914583104E0E18CE82F6 -E4649F9C2A5465D2EA6F3E9724DD06CD6962FE2BAEB14F1453C14D1559232AE1 -96E15D890DF7FD348441F5E429A875754C6BF0520A787F8E9D8C5415674783CC -CB52005EDED47B57F795BC92FB0522EAB18D23EE028B8D10ED57828C250EB285BFEC6E4A4BE8DABCE0D57ECAA20D90C3 -8E5A50C7D5C374445E88752301D20F0B3D6E4988B61D90FD63779B0EDEF9C60D -49D6CB276A0E5FF134A38062503F01351F44CD6455708B50B5F07D03FC477C33 -CB45B56613DF208E79E4E10A6510F07DC1AA49210C7B94E8BBAECD2C35EC6ABC99FB10FD7C96DD6BB6A6685E9FAD93FB -0743F3CC51200F763C242F1956B4D775C092ADF1A5C19ACAE96EB60C2990CF214F8FEA8FC6749286F6BDAB67657C479A -E5608B28A058787D64A145F0362DEFD98CAE0B5A0F22C6DA7C6D278C7B5F95E3 -D4C113D43E7FB6D2EFA9E87471AA76A61B26872607B4AF5B87F9D72113835CE6 -2DC502800BFD21B76126390CA64A08C5432A2254E822F214CDE1EA11430084C5 -CA22C73010B0F1CB8009601BE2AF0B3674D83D5880E4A26C2A3FF0EA0A098CEA -E53B2B102FDB000E9BB747F957156976E5A0C0E3898AA844C13AE8A9CEE7013B -95CF1A46FFC252BE92919531C92BF6A3AA1B16C170DF4461EC54BE07A55C2387 -2EC7E24090F6DFFF6F2F2D8874D2F36AA769995F31F29FBE3B0EA6A16C3EE833 -C1145B1D9AC70761EA902B86455C1BE1BB1153552A1F7327411DECABE538827B -18D596CADD2EE544200A58716C7A4690B658E58CC2B97334740F70894A6C90FA -6A2F8859DFF01E13AC6C5300AD4A2218810FC91A6FB64A560E99FE6C99226AD2 -48D2EB5A08E35AF89A3B7A1CFDEE829FC0C2DDD2E965F4E3D043B0B14CB7825E -91039325D53CDD0236D1CD13047973A013C14B45A32DE0784A73BFABCEAFBCD1 -51B4EAC87C4DC49B007F40D38B8166C388A1AF25E8D2FF6598E8EDE8726E6E14AD88443114D2A0F5E7721E304F3870DA -3A179DDF65B9868CD84C7C04931F40D5D204C97B20DCBF1A70C241E59BFD7F14 -AF538FD16104DCAF03F4DDF05026D6741898DFC247E48A8F72E652DDF2DFD289 -E67F16AEC9D84B6C06F77B806CA6FBC7618BFBECD0D7A04EC3AE1D1DD06BEC5B -FA4D9F8920EBF2F4293C6D4E99083AA4A71A9DDFFDB07EEBDC552DACEC4DA24A -5BF23E630AC81E2CD533803E225BCB3C481B8D650A9858CF2B5219BAE1CDA01A -17B57E8C1032481E69247EA9A0C9EA41F6C0EA9B3F11170CA69C0842423F0455 -96EA848B8527A647DC9DACDB16C5D92B0081EB1CD77B99B47F56C2E249190BD3BE4306333F37487133DD3AD8E57F3092 -B0E9411274D799BE5989D52E74E00DE310CCA2BD47D7A8FA554D66BB04CD787A -D0D28476E3D8832975653D93F545C35278EC1F0B7AD70CA2F36EB476CC207937 -933195E37014619F997B73F5CF4C0110865A822CA8CB0ED1D977D49A1B06A37F -E790CAC2A26452BF941A9E1BABF0A85598EA1CC8F8CFED637C9B40D5E027B518 -49C1F179ABA5BD4F2C45257A33701730E9CC4728677EFF07808ABE31D3CE6FD5C805F43EA5ABB7261B220C82F0794092 +IV: AA FF DE 54 A1 BB F1 21 83 46 FE 2A 1E B7 3D 33 +95B8CD65BBAC95EACE67CA94F679B82877A921396D461ECB479722F8A369454A +61065C41297B9FF8F8168814F49A03D1FE7B4CB79DFFCBBF0402AAA6A2211E84 +A1557AC139188FF105D1081A4B688C5CA440FB5DA7F40901B541120AD08A544F +AF0A6056D7F0D97DAD6C16C4E63204E4B3B1C5A20AC82B983B516F4F718EE29F +6861BFAE46A1AADB1DB2D6DFAA7E39D21D5B3E46A41BD50F4F2828879EB328EF0A406F2B9C79A031AB361257E6D69756 +0DDB3DAC53678541981CC46C22CED245CBA314C9BBE1BA9383B8505B75AC5E40 +99AB5D9404934F2D257ED04D9F8CCEE06D00F38157B121AFD63101E4E5C08268 +5114A6C42B342C7D933A76F9052FF963C2047E85EA524497C21B4C35C38EF6E7 +88CA2A1907D94B972FF93DBB9B88CB576F3E1BB0FE8F85A5B2CCA7D44B00374D +349C4153FE7CA8AE044E9F75F77D9694304474CE3F127CF968662B5F78A7F421 +62AA02E20CA7E691EFC0B55CA41C9BDF889FB23868289284241CD31AA1A0E499AE2A770B6B5AB3170CDCCDB8A246D36C +97901B5EB76228ADF8E5073F1BAB1502878DEFF1C4EBF12A43D105556CB7E80F947A8BD7831666BD838C57CDF64A6F3F +B05959D210B500943A93BDFAF783D9DB215FC84503B152EAFBCFB5B6237E3888 +B393DE4489BCAFD5DB80592A12E329E18913E185D2042580048029A8C4C3A257 +B4B30492A5F0C3C763E2F43C02D1451A5B9CFB468CFE62BE85B1F56FF49DAB9A +CE5D57C0EE3D717FC717EB725970A9F25D211546EE7AC5C237950CEA323D85D4 +4E9028944813FD40A17AF6DF5A97E76179B48EE79265BBD38B07E3A270587A813DADB51B3367479AC5644F754B5613F8 +3B3C3000B9D1361711ECE3DB77C90A059576F738CB167679DA36DD3D128B27A1 +997023148148DE7B9CBA47D3FD48DEF73AA1715FF4BC1E7A1DBA6D52A0DCB2C0 +C8428D18E69FB92486434FCE470F1FF37D40507F27D824679C132A70D516530367277F02DDB5C464D03450FF6B425A24 +3701200DF5DA7235971FD95844056E74C7D61A8EB12A8772E04F52037C63D50B6229A7F905F3E6F84C565FCC7632870C +BB392A464CDC0D5D923AA9EF8ECC3C6F020D0AD82165462DF0DE7C5025AAAAAC +999C82209B30638506E5D708471676D2CBB4A432E5AF86ABD61179111EDAE636 +FDE2A452A6B47261338117EC20FC57731DA492562ECD21BBC61F098A5442CF20 +D923BABB5C4DFB48E3F763898B2796C7830D3EE9A91DF904AC2223A0F4736507 +0987DDAC695DD5E4607048DF1D4EF96599E17ED52F41785E676AA048AB7213FE +26CB3E6CFA10338A8DDD99BFF6957C53DEF435CB0FF977B71B5164ADFE11292A +097908FD07A0A093CA80E6FF59524707C1A11169D0CB6F8E4967D8DAA725FE7A +8C629E70A5CC6FCB039DFA1A6AC58CB7B7E92C85BDA66266AB49E6B1285FC7A6 +39A2052350CD446EDC1B9AD0C2DD51C78B2E5F3A76AAD0EC200F74B40ACD4AC5 +A1685CF8C4A5401F2CA0C8172CB5B4B5726C61CE68A72AE834B0A472CEB2F3DE +1F5ED5793DB381D1B501BA8A4DF3E74FB11FC1A922DDC8AE62E5BA8934C37EA8 +D80EF661BF36E2F6C179E253CE5BC3732684ACBC7C65E526A628442A2EBF8FAA +7785BF721F21E19A8CFBFBBB56BD76B96A4E8EF9F8A2344009B14AB385909598F834A5533B648DA7D62BD6D4314A43A5 +C8F6F943DE615B5827569B283577344C0455B3279C73634FC4E0E9A8088DF633 +FB4F4C786FC51BBDA679A212B4A05EF120AC62F7EBFFE8263BD50A4D9BC9C6E0 +16EEC35CF69BA86DB3BE999CDF9B39F5736F3727B2AA2C5AB9141A48F176D831 +AD1AD6DE813E7710DA3AF546D4F9EA085831E6B3FA17B64F1B8765F48134EA54 +345D743BC35B4A8614632ADD11E809C0D1E6C78F9469256B9A738DA0B648B2B8 +7C876CECAC839EBB4609C3996966C3EC454F51C8ABCC51097E405370C4B6F086 +0F857C031FD3047607647148C534F969567F207FF1691D8D06DCDF4C2514695D +EC0630EDC82241C1952F49B6B1B0C1A954A7DDD6BDB1326ACC54AD449D1BF985 +286EF9F7FD0D09F2604CCE867C52144CD0C4773A3D8183066C61B8BF9860AE7C +EA55424097A08722A66966E3177E09DE91AC65175E5C68CB47B6153E6585DF85 +D54FCDF9EA4BD1FE4F316DB6D5CE4A2675F2D0144772865EDC781FBA7DFD23E4 +7A2F5C5CA9F97FE9527BAA760E64B930C407A27DE036476737E6BDD9422F4056A5F1F414F12F0982109FD7C30E8CC1CB +06BAD9B4EEEEB1BCF8C97672D271534FE84D772282EE9642698788D3842D7641 +101C1B2DBD963E23777294C22E553D145D5B40838F91355CA86D571A0CEFF68F +1B148C2B502B3E0A5BD40858E019C513DD4CCAF2A114CBB29C59BFB018079285 +8DF4D07EC20FF873EA989ACEF4AF96E9787FE6E0F71965858B4186C3AF302A31 +2317DC8C098CD60F3467B3644A19CCE887339708820CD37F6F5277D6648F837512F70CE90E23D7339CDDE002BD8D83DB diff --git a/lib/subghz/protocols/keeloq.c b/lib/subghz/protocols/keeloq.c index ae6588e7afb..eef1d09371c 100644 --- a/lib/subghz/protocols/keeloq.c +++ b/lib/subghz/protocols/keeloq.c @@ -529,6 +529,24 @@ static uint8_t subghz_protocol_keeloq_check_remote_controller_selector( return 1; } break; + case KEELOQ_LEARNING_MAGIC_SERIAL_TYPE_2: + man = subghz_protocol_keeloq_common_magic_serial_type2_learning( + fix, manufacture_code->key); + decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); + if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { + *manufacture_name = furi_string_get_cstr(manufacture_code->name); + return 1; + } + break; + case KEELOQ_LEARNING_MAGIC_SERIAL_TYPE_3: + man = subghz_protocol_keeloq_common_magic_serial_type3_learning( + fix, manufacture_code->key); + decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); + if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { + *manufacture_name = furi_string_get_cstr(manufacture_code->name); + return 1; + } + break; case KEELOQ_LEARNING_UNKNOWN: // Simple Learning decrypt = subghz_protocol_keeloq_common_decrypt(hop, manufacture_code->key); diff --git a/lib/subghz/protocols/keeloq_common.c b/lib/subghz/protocols/keeloq_common.c index 6c9bc461e3f..ddbf1c9174c 100644 --- a/lib/subghz/protocols/keeloq_common.c +++ b/lib/subghz/protocols/keeloq_common.c @@ -94,6 +94,34 @@ inline uint64_t inline uint64_t subghz_protocol_keeloq_common_magic_serial_type1_learning(uint32_t data, uint64_t man) { - return man | ((uint64_t)data << 40) | + return (man & 0xFFFFFFFF) | ((uint64_t)data << 40) | ((uint64_t)(((data & 0xff) + ((data >> 8) & 0xFF)) & 0xFF) << 32); } + +/** Magic_serial_type2 Learning + * @param data - btn+serial number (32bit) + * @param man - magic man (64bit) + * @return manufacture for this serial number (64bit) + */ + +inline uint64_t + subghz_protocol_keeloq_common_magic_serial_type2_learning(uint32_t data, uint64_t man) { + uint8_t* p = (uint8_t*)&data; + uint8_t* m = (uint8_t*)&man; + m[7] = p[0]; + m[6] = p[1]; + m[5] = p[2]; + m[4] = p[3]; + return man; +} + +/** Magic_serial_type3 Learning + * @param data - serial number (24bit) + * @param man - magic man (64bit) + * @return manufacture for this serial number (64bit) + */ + +inline uint64_t + subghz_protocol_keeloq_common_magic_serial_type3_learning(uint32_t data, uint64_t man) { + return (man & 0xFFFFFFFFFF000000) | (data & 0xFFFFFF); +} diff --git a/lib/subghz/protocols/keeloq_common.h b/lib/subghz/protocols/keeloq_common.h index 448388f0a62..df3d0dbf367 100644 --- a/lib/subghz/protocols/keeloq_common.h +++ b/lib/subghz/protocols/keeloq_common.h @@ -22,6 +22,8 @@ #define KEELOQ_LEARNING_SECURE 3u #define KEELOQ_LEARNING_MAGIC_XOR_TYPE_1 4u #define KEELOQ_LEARNING_MAGIC_SERIAL_TYPE_1 5u +#define KEELOQ_LEARNING_MAGIC_SERIAL_TYPE_2 6u +#define KEELOQ_LEARNING_MAGIC_SERIAL_TYPE_3 7u /** * Simple Learning Encrypt @@ -72,3 +74,19 @@ uint64_t subghz_protocol_keeloq_common_magic_xor_type1_learning(uint32_t data, u */ uint64_t subghz_protocol_keeloq_common_magic_serial_type1_learning(uint32_t data, uint64_t man); + +/** Magic_serial_type2 Learning + * @param data - btn+serial number (32bit) + * @param man - magic man (64bit) + * @return manufacture for this serial number (64bit) + */ + +uint64_t subghz_protocol_keeloq_common_magic_serial_type2_learning(uint32_t data, uint64_t man); + +/** Magic_serial_type3 Learning + * @param data - btn+serial number (32bit) + * @param man - magic man (64bit) + * @return manufacture for this serial number (64bit) + */ + +uint64_t subghz_protocol_keeloq_common_magic_serial_type3_learning(uint32_t data, uint64_t man); From 4b921803cbad19829d0b08e92bab89f29080ffd7 Mon Sep 17 00:00:00 2001 From: hedger Date: Fri, 28 Oct 2022 19:32:06 +0400 Subject: [PATCH 185/824] fbt: fixes for ufbt compat (#1940) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fbt: split sdk management code * scripts: fixed import handling * fbt: sdk: reformatted paths * scrips: dist: bundling libs as a build artifact * fbt: sdk: better path management * typo fix * fbt: sdk: minor path handling fixes * toolchain: fixed windows toolchain download Co-authored-by: あく --- firmware.scons | 17 -- scripts/fbt/sdk/__init__.py | 44 +++ scripts/fbt/{sdk.py => sdk/cache.py} | 280 +----------------- scripts/fbt/sdk/collector.py | 238 +++++++++++++++ scripts/fbt_tools/fbt_extapps.py | 2 +- scripts/fbt_tools/fbt_sdk.py | 49 ++- scripts/sconsdist.py | 68 +++-- .../toolchain/windows-toolchain-download.ps1 | 6 +- site_scons/extapps.scons | 2 +- site_scons/firmwareopts.scons | 20 +- 10 files changed, 384 insertions(+), 342 deletions(-) create mode 100644 scripts/fbt/sdk/__init__.py rename scripts/fbt/{sdk.py => sdk/cache.py} (52%) create mode 100644 scripts/fbt/sdk/collector.py diff --git a/firmware.scons b/firmware.scons index d501996b334..63a1aa3f716 100644 --- a/firmware.scons +++ b/firmware.scons @@ -178,23 +178,6 @@ sources.extend( ) ) - -fwenv.AppendUnique( - LINKFLAGS=[ - "-specs=nano.specs", - "-specs=nosys.specs", - "-Wl,--gc-sections", - "-Wl,--undefined=uxTopUsedPriority", - "-Wl,--wrap,_malloc_r", - "-Wl,--wrap,_free_r", - "-Wl,--wrap,_calloc_r", - "-Wl,--wrap,_realloc_r", - "-n", - "-Xlinker", - "-Map=${TARGET}.map", - ], -) - # Debug # print(fwenv.Dump()) diff --git a/scripts/fbt/sdk/__init__.py b/scripts/fbt/sdk/__init__.py new file mode 100644 index 00000000000..27da5f7c842 --- /dev/null +++ b/scripts/fbt/sdk/__init__.py @@ -0,0 +1,44 @@ +from typing import Set, ClassVar +from dataclasses import dataclass, field + + +@dataclass(frozen=True) +class ApiEntryFunction: + name: str + returns: str + params: str + + csv_type: ClassVar[str] = "Function" + + def dictify(self): + return dict(name=self.name, type=self.returns, params=self.params) + + +@dataclass(frozen=True) +class ApiEntryVariable: + name: str + var_type: str + + csv_type: ClassVar[str] = "Variable" + + def dictify(self): + return dict(name=self.name, type=self.var_type, params=None) + + +@dataclass(frozen=True) +class ApiHeader: + name: str + + csv_type: ClassVar[str] = "Header" + + def dictify(self): + return dict(name=self.name, type=None, params=None) + + +@dataclass +class ApiEntries: + # These are sets, to avoid creating duplicates when we have multiple + # declarations with same signature + functions: Set[ApiEntryFunction] = field(default_factory=set) + variables: Set[ApiEntryVariable] = field(default_factory=set) + headers: Set[ApiHeader] = field(default_factory=set) diff --git a/scripts/fbt/sdk.py b/scripts/fbt/sdk/cache.py similarity index 52% rename from scripts/fbt/sdk.py rename to scripts/fbt/sdk/cache.py index 48f935de3b4..62d42798c98 100644 --- a/scripts/fbt/sdk.py +++ b/scripts/fbt/sdk/cache.py @@ -4,284 +4,18 @@ import operator from enum import Enum, auto -from typing import List, Set, ClassVar, Any -from dataclasses import dataclass, field +from typing import Set, ClassVar, Any +from dataclasses import dataclass from ansi.color import fg -from cxxheaderparser.parser import CxxParser - - -# 'Fixing' complaints about typedefs -CxxParser._fundamentals.discard("wchar_t") - -from cxxheaderparser.types import ( - EnumDecl, - Field, - ForwardDecl, - FriendDecl, - Function, - Method, - Typedef, - UsingAlias, - UsingDecl, - Variable, - Pointer, - Type, - PQName, - NameSpecifier, - FundamentalSpecifier, - Parameter, - Array, - Value, - Token, - FunctionType, +from . import ( + ApiEntries, + ApiEntryFunction, + ApiEntryVariable, + ApiHeader, ) -from cxxheaderparser.parserstate import ( - State, - EmptyBlockState, - ClassBlockState, - ExternBlockState, - NamespaceBlockState, -) - - -@dataclass(frozen=True) -class ApiEntryFunction: - name: str - returns: str - params: str - - csv_type: ClassVar[str] = "Function" - - def dictify(self): - return dict(name=self.name, type=self.returns, params=self.params) - - -@dataclass(frozen=True) -class ApiEntryVariable: - name: str - var_type: str - - csv_type: ClassVar[str] = "Variable" - - def dictify(self): - return dict(name=self.name, type=self.var_type, params=None) - - -@dataclass(frozen=True) -class ApiHeader: - name: str - - csv_type: ClassVar[str] = "Header" - - def dictify(self): - return dict(name=self.name, type=None, params=None) - - -@dataclass -class ApiEntries: - # These are sets, to avoid creating duplicates when we have multiple - # declarations with same signature - functions: Set[ApiEntryFunction] = field(default_factory=set) - variables: Set[ApiEntryVariable] = field(default_factory=set) - headers: Set[ApiHeader] = field(default_factory=set) - - -class SymbolManager: - def __init__(self): - self.api = ApiEntries() - self.name_hashes = set() - - # Calculate hash of name and raise exception if it already is in the set - def _name_check(self, name: str): - name_hash = gnu_sym_hash(name) - if name_hash in self.name_hashes: - raise Exception(f"Hash collision on {name}") - self.name_hashes.add(name_hash) - - def add_function(self, function_def: ApiEntryFunction): - if function_def in self.api.functions: - return - self._name_check(function_def.name) - self.api.functions.add(function_def) - - def add_variable(self, variable_def: ApiEntryVariable): - if variable_def in self.api.variables: - return - self._name_check(variable_def.name) - self.api.variables.add(variable_def) - - def add_header(self, header: str): - self.api.headers.add(ApiHeader(header)) - - -def gnu_sym_hash(name: str): - h = 0x1505 - for c in name: - h = (h << 5) + h + ord(c) - return str(hex(h))[-8:] - - -class SdkCollector: - def __init__(self): - self.symbol_manager = SymbolManager() - - def add_header_to_sdk(self, header: str): - self.symbol_manager.add_header(header) - - def process_source_file_for_sdk(self, file_path: str): - visitor = SdkCxxVisitor(self.symbol_manager) - with open(file_path, "rt") as f: - content = f.read() - parser = CxxParser(file_path, content, visitor, None) - parser.parse() - - def get_api(self): - return self.symbol_manager.api - - -def stringify_array_dimension(size_descr): - if not size_descr: - return "" - return stringify_descr(size_descr) - - -def stringify_array_descr(type_descr): - assert isinstance(type_descr, Array) - return ( - stringify_descr(type_descr.array_of), - stringify_array_dimension(type_descr.size), - ) - - -def stringify_descr(type_descr): - if isinstance(type_descr, (NameSpecifier, FundamentalSpecifier)): - return type_descr.name - elif isinstance(type_descr, PQName): - return "::".join(map(stringify_descr, type_descr.segments)) - elif isinstance(type_descr, Pointer): - # Hack - if isinstance(type_descr.ptr_to, FunctionType): - return stringify_descr(type_descr.ptr_to) - return f"{stringify_descr(type_descr.ptr_to)}*" - elif isinstance(type_descr, Type): - return ( - f"{'const ' if type_descr.const else ''}" - f"{'volatile ' if type_descr.volatile else ''}" - f"{stringify_descr(type_descr.typename)}" - ) - elif isinstance(type_descr, Parameter): - return stringify_descr(type_descr.type) - elif isinstance(type_descr, Array): - # Hack for 2d arrays - if isinstance(type_descr.array_of, Array): - argtype, dimension = stringify_array_descr(type_descr.array_of) - return ( - f"{argtype}[{stringify_array_dimension(type_descr.size)}][{dimension}]" - ) - return f"{stringify_descr(type_descr.array_of)}[{stringify_array_dimension(type_descr.size)}]" - elif isinstance(type_descr, Value): - return " ".join(map(stringify_descr, type_descr.tokens)) - elif isinstance(type_descr, FunctionType): - return f"{stringify_descr(type_descr.return_type)} (*)({', '.join(map(stringify_descr, type_descr.parameters))})" - elif isinstance(type_descr, Token): - return type_descr.value - elif type_descr is None: - return "" - else: - raise Exception("unsupported type_descr: %s" % type_descr) - - -class SdkCxxVisitor: - def __init__(self, symbol_manager: SymbolManager): - self.api = symbol_manager - - def on_variable(self, state: State, v: Variable) -> None: - if not v.extern: - return - - self.api.add_variable( - ApiEntryVariable( - stringify_descr(v.name), - stringify_descr(v.type), - ) - ) - - def on_function(self, state: State, fn: Function) -> None: - if fn.inline or fn.has_body: - return - - self.api.add_function( - ApiEntryFunction( - stringify_descr(fn.name), - stringify_descr(fn.return_type), - ", ".join(map(stringify_descr, fn.parameters)) - + (", ..." if fn.vararg else ""), - ) - ) - - def on_define(self, state: State, content: str) -> None: - pass - - def on_pragma(self, state: State, content: str) -> None: - pass - - def on_include(self, state: State, filename: str) -> None: - pass - - def on_empty_block_start(self, state: EmptyBlockState) -> None: - pass - - def on_empty_block_end(self, state: EmptyBlockState) -> None: - pass - - def on_extern_block_start(self, state: ExternBlockState) -> None: - pass - - def on_extern_block_end(self, state: ExternBlockState) -> None: - pass - - def on_namespace_start(self, state: NamespaceBlockState) -> None: - pass - - def on_namespace_end(self, state: NamespaceBlockState) -> None: - pass - - def on_forward_decl(self, state: State, fdecl: ForwardDecl) -> None: - pass - - def on_typedef(self, state: State, typedef: Typedef) -> None: - pass - - def on_using_namespace(self, state: State, namespace: List[str]) -> None: - pass - - def on_using_alias(self, state: State, using: UsingAlias) -> None: - pass - - def on_using_declaration(self, state: State, using: UsingDecl) -> None: - pass - - def on_enum(self, state: State, enum: EnumDecl) -> None: - pass - - def on_class_start(self, state: ClassBlockState) -> None: - pass - - def on_class_field(self, state: State, f: Field) -> None: - pass - - def on_class_method(self, state: ClassBlockState, method: Method) -> None: - pass - - def on_class_friend(self, state: ClassBlockState, friend: FriendDecl) -> None: - pass - - def on_class_end(self, state: ClassBlockState) -> None: - pass - @dataclass(frozen=True) class SdkVersion: diff --git a/scripts/fbt/sdk/collector.py b/scripts/fbt/sdk/collector.py new file mode 100644 index 00000000000..578a8c7a62b --- /dev/null +++ b/scripts/fbt/sdk/collector.py @@ -0,0 +1,238 @@ +from typing import List + +from cxxheaderparser.parser import CxxParser +from . import ( + ApiEntries, + ApiEntryFunction, + ApiEntryVariable, + ApiHeader, +) + + +# 'Fixing' complaints about typedefs +CxxParser._fundamentals.discard("wchar_t") + +from cxxheaderparser.types import ( + EnumDecl, + Field, + ForwardDecl, + FriendDecl, + Function, + Method, + Typedef, + UsingAlias, + UsingDecl, + Variable, + Pointer, + Type, + PQName, + NameSpecifier, + FundamentalSpecifier, + Parameter, + Array, + Value, + Token, + FunctionType, +) + +from cxxheaderparser.parserstate import ( + State, + EmptyBlockState, + ClassBlockState, + ExternBlockState, + NamespaceBlockState, +) + + +class SymbolManager: + def __init__(self): + self.api = ApiEntries() + self.name_hashes = set() + + # Calculate hash of name and raise exception if it already is in the set + def _name_check(self, name: str): + name_hash = gnu_sym_hash(name) + if name_hash in self.name_hashes: + raise Exception(f"Hash collision on {name}") + self.name_hashes.add(name_hash) + + def add_function(self, function_def: ApiEntryFunction): + if function_def in self.api.functions: + return + self._name_check(function_def.name) + self.api.functions.add(function_def) + + def add_variable(self, variable_def: ApiEntryVariable): + if variable_def in self.api.variables: + return + self._name_check(variable_def.name) + self.api.variables.add(variable_def) + + def add_header(self, header: str): + self.api.headers.add(ApiHeader(header)) + + +def gnu_sym_hash(name: str): + h = 0x1505 + for c in name: + h = (h << 5) + h + ord(c) + return str(hex(h))[-8:] + + +class SdkCollector: + def __init__(self): + self.symbol_manager = SymbolManager() + + def add_header_to_sdk(self, header: str): + self.symbol_manager.add_header(header) + + def process_source_file_for_sdk(self, file_path: str): + visitor = SdkCxxVisitor(self.symbol_manager) + with open(file_path, "rt") as f: + content = f.read() + parser = CxxParser(file_path, content, visitor, None) + parser.parse() + + def get_api(self): + return self.symbol_manager.api + + +def stringify_array_dimension(size_descr): + if not size_descr: + return "" + return stringify_descr(size_descr) + + +def stringify_array_descr(type_descr): + assert isinstance(type_descr, Array) + return ( + stringify_descr(type_descr.array_of), + stringify_array_dimension(type_descr.size), + ) + + +def stringify_descr(type_descr): + if isinstance(type_descr, (NameSpecifier, FundamentalSpecifier)): + return type_descr.name + elif isinstance(type_descr, PQName): + return "::".join(map(stringify_descr, type_descr.segments)) + elif isinstance(type_descr, Pointer): + # Hack + if isinstance(type_descr.ptr_to, FunctionType): + return stringify_descr(type_descr.ptr_to) + return f"{stringify_descr(type_descr.ptr_to)}*" + elif isinstance(type_descr, Type): + return ( + f"{'const ' if type_descr.const else ''}" + f"{'volatile ' if type_descr.volatile else ''}" + f"{stringify_descr(type_descr.typename)}" + ) + elif isinstance(type_descr, Parameter): + return stringify_descr(type_descr.type) + elif isinstance(type_descr, Array): + # Hack for 2d arrays + if isinstance(type_descr.array_of, Array): + argtype, dimension = stringify_array_descr(type_descr.array_of) + return ( + f"{argtype}[{stringify_array_dimension(type_descr.size)}][{dimension}]" + ) + return f"{stringify_descr(type_descr.array_of)}[{stringify_array_dimension(type_descr.size)}]" + elif isinstance(type_descr, Value): + return " ".join(map(stringify_descr, type_descr.tokens)) + elif isinstance(type_descr, FunctionType): + return f"{stringify_descr(type_descr.return_type)} (*)({', '.join(map(stringify_descr, type_descr.parameters))})" + elif isinstance(type_descr, Token): + return type_descr.value + elif type_descr is None: + return "" + else: + raise Exception("unsupported type_descr: %s" % type_descr) + + +class SdkCxxVisitor: + def __init__(self, symbol_manager: SymbolManager): + self.api = symbol_manager + + def on_variable(self, state: State, v: Variable) -> None: + if not v.extern: + return + + self.api.add_variable( + ApiEntryVariable( + stringify_descr(v.name), + stringify_descr(v.type), + ) + ) + + def on_function(self, state: State, fn: Function) -> None: + if fn.inline or fn.has_body: + return + + self.api.add_function( + ApiEntryFunction( + stringify_descr(fn.name), + stringify_descr(fn.return_type), + ", ".join(map(stringify_descr, fn.parameters)) + + (", ..." if fn.vararg else ""), + ) + ) + + def on_define(self, state: State, content: str) -> None: + pass + + def on_pragma(self, state: State, content: str) -> None: + pass + + def on_include(self, state: State, filename: str) -> None: + pass + + def on_empty_block_start(self, state: EmptyBlockState) -> None: + pass + + def on_empty_block_end(self, state: EmptyBlockState) -> None: + pass + + def on_extern_block_start(self, state: ExternBlockState) -> None: + pass + + def on_extern_block_end(self, state: ExternBlockState) -> None: + pass + + def on_namespace_start(self, state: NamespaceBlockState) -> None: + pass + + def on_namespace_end(self, state: NamespaceBlockState) -> None: + pass + + def on_forward_decl(self, state: State, fdecl: ForwardDecl) -> None: + pass + + def on_typedef(self, state: State, typedef: Typedef) -> None: + pass + + def on_using_namespace(self, state: State, namespace: List[str]) -> None: + pass + + def on_using_alias(self, state: State, using: UsingAlias) -> None: + pass + + def on_using_declaration(self, state: State, using: UsingDecl) -> None: + pass + + def on_enum(self, state: State, enum: EnumDecl) -> None: + pass + + def on_class_start(self, state: ClassBlockState) -> None: + pass + + def on_class_field(self, state: State, f: Field) -> None: + pass + + def on_class_method(self, state: ClassBlockState, method: Method) -> None: + pass + + def on_class_friend(self, state: ClassBlockState, friend: FriendDecl) -> None: + pass + + def on_class_end(self, state: ClassBlockState) -> None: + pass diff --git a/scripts/fbt_tools/fbt_extapps.py b/scripts/fbt_tools/fbt_extapps.py index 5a5dab57284..38c943cc57a 100644 --- a/scripts/fbt_tools/fbt_extapps.py +++ b/scripts/fbt_tools/fbt_extapps.py @@ -8,7 +8,7 @@ import pathlib from fbt.elfmanifest import assemble_manifest_data from fbt.appmanifest import FlipperApplication, FlipperManifestException -from fbt.sdk import SdkCache +from fbt.sdk.cache import SdkCache import itertools from ansi.color import fg diff --git a/scripts/fbt_tools/fbt_sdk.py b/scripts/fbt_tools/fbt_sdk.py index 0b6e22de5b0..f1f55bdb819 100644 --- a/scripts/fbt_tools/fbt_sdk.py +++ b/scripts/fbt_tools/fbt_sdk.py @@ -4,7 +4,7 @@ from SCons.Errors import UserError # from SCons.Scanner import C -from SCons.Script import Mkdir, Copy, Delete, Entry +from SCons.Script import Entry from SCons.Util import LogicalLines import os.path @@ -12,7 +12,8 @@ import pathlib import json -from fbt.sdk import SdkCollector, SdkCache +from fbt.sdk.collector import SdkCollector +from fbt.sdk.cache import SdkCache def ProcessSdkDepends(env, filename): @@ -49,15 +50,19 @@ def prebuild_sdk_create_origin_file(target, source, env): class SdkMeta: - def __init__(self, env): + def __init__(self, env, tree_builder: "SdkTreeBuilder"): self.env = env + self.treebuilder = tree_builder def save_to(self, json_manifest_path: str): meta_contents = { - "sdk_symbols": self.env["SDK_DEFINITION"].name, + "sdk_symbols": self.treebuilder.build_sdk_file_path( + self.env["SDK_DEFINITION"].path + ), "cc_args": self._wrap_scons_vars("$CCFLAGS $_CCCOMCOM"), "cpp_args": self._wrap_scons_vars("$CXXFLAGS $CCFLAGS $_CCCOMCOM"), "linker_args": self._wrap_scons_vars("$LINKFLAGS"), + "linker_script": self.env.subst("${LINKER_SCRIPT_PATH}"), } with open(json_manifest_path, "wt") as f: json.dump(meta_contents, f, indent=4) @@ -68,6 +73,8 @@ def _wrap_scons_vars(self, vars: str): class SdkTreeBuilder: + SDK_DIR_SUBST = "SDK_ROOT_DIR" + def __init__(self, env, target, source) -> None: self.env = env self.target = target @@ -88,6 +95,8 @@ def _parse_sdk_depends(self): self.header_depends = list( filter(lambda fname: fname.endswith(".h"), depends.split()), ) + self.header_depends.append(self.env.subst("${LINKER_SCRIPT_PATH}")) + self.header_depends.append(self.env.subst("${SDK_DEFINITION}")) self.header_dirs = sorted( set(map(os.path.normpath, map(os.path.dirname, self.header_depends))) ) @@ -102,17 +111,33 @@ def _generate_sdk_meta(self): ) sdk_dirs = ", ".join(f"'{dir}'" for dir in self.header_dirs) - for dir in full_fw_paths: - if dir in sdk_dirs: - filtered_paths.append( - posixpath.normpath(posixpath.join(self.target_sdk_dir_name, dir)) - ) + filtered_paths.extend( + map( + self.build_sdk_file_path, + filter(lambda path: path in sdk_dirs, full_fw_paths), + ) + ) sdk_env = self.env.Clone() - sdk_env.Replace(CPPPATH=filtered_paths) - meta = SdkMeta(sdk_env) + sdk_env.Replace( + CPPPATH=filtered_paths, + LINKER_SCRIPT=self.env.subst("${APP_LINKER_SCRIPT}"), + ORIG_LINKER_SCRIPT_PATH=self.env["LINKER_SCRIPT_PATH"], + LINKER_SCRIPT_PATH=self.build_sdk_file_path("${ORIG_LINKER_SCRIPT_PATH}"), + ) + + meta = SdkMeta(sdk_env, self) meta.save_to(self.target[0].path) + def build_sdk_file_path(self, orig_path: str) -> str: + return posixpath.normpath( + posixpath.join( + self.SDK_DIR_SUBST, + self.target_sdk_dir_name, + orig_path, + ) + ).replace("\\", "/") + def emitter(self, target, source, env): target_folder = target[0] target = [target_folder.File("sdk.opts")] @@ -128,8 +153,6 @@ def _run_deploy_commands(self): for sdkdir in dirs_to_create: os.makedirs(sdkdir, exist_ok=True) - shutil.copy2(self.env["SDK_DEFINITION"].path, self.sdk_root_dir.path) - for header in self.header_depends: shutil.copy2(header, self.sdk_deploy_dir.File(header).path) diff --git a/scripts/sconsdist.py b/scripts/sconsdist.py index 4c0427894ec..7636c87bb11 100644 --- a/scripts/sconsdist.py +++ b/scripts/sconsdist.py @@ -48,48 +48,52 @@ def init(self): ) self.parser_copy.set_defaults(func=self.copy) - def get_project_filename(self, project, filetype): + def get_project_file_name(self, project: ProjectDir, filetype: str) -> str: # Temporary fix project_name = project.project - if project_name == "firmware": - if filetype == "zip": - project_name = "sdk" - elif filetype != "elf": - project_name = "full" + if project_name == "firmware" and filetype != "elf": + project_name = "full" - return f"{self.DIST_FILE_PREFIX}{self.target}-{project_name}-{self.args.suffix}.{filetype}" + return self.get_dist_file_name(project_name, filetype) - def get_dist_filepath(self, filename): + def get_dist_file_name(self, dist_artifact_type: str, filetype: str) -> str: + return f"{self.DIST_FILE_PREFIX}{self.target}-{dist_artifact_type}-{self.args.suffix}.{filetype}" + + def get_dist_file_path(self, filename: str) -> str: return join(self.output_dir_path, filename) - def copy_single_project(self, project): + def copy_single_project(self, project: ProjectDir) -> None: obj_directory = join("build", project.dir) for filetype in ("elf", "bin", "dfu", "json"): if exists(src_file := join(obj_directory, f"{project.project}.{filetype}")): shutil.copyfile( src_file, - self.get_dist_filepath( - self.get_project_filename(project, filetype) + self.get_dist_file_path( + self.get_project_file_name(project, filetype) ), ) - if exists(sdk_folder := join(obj_directory, "sdk")): - with zipfile.ZipFile( - self.get_dist_filepath(self.get_project_filename(project, "zip")), - "w", - zipfile.ZIP_DEFLATED, - ) as zf: - for root, dirs, files in walk(sdk_folder): - for file in files: - zf.write( - join(root, file), - relpath( - join(root, file), - sdk_folder, - ), - ) - - def copy(self): + for foldertype in ("sdk", "lib"): + if exists(sdk_folder := join(obj_directory, foldertype)): + self.package_zip(foldertype, sdk_folder) + + def package_zip(self, foldertype, sdk_folder): + with zipfile.ZipFile( + self.get_dist_file_path(self.get_dist_file_name(foldertype, "zip")), + "w", + zipfile.ZIP_DEFLATED, + ) as zf: + for root, _, files in walk(sdk_folder): + for file in files: + zf.write( + join(root, file), + relpath( + join(root, file), + sdk_folder, + ), + ) + + def copy(self) -> int: self.projects = dict( map( lambda pd: (pd.project, pd), @@ -144,12 +148,12 @@ def copy(self): "-t", self.target, "--dfu", - self.get_dist_filepath( - self.get_project_filename(self.projects["firmware"], "dfu") + self.get_dist_file_path( + self.get_project_file_name(self.projects["firmware"], "dfu") ), "--stage", - self.get_dist_filepath( - self.get_project_filename(self.projects["updater"], "bin") + self.get_dist_file_path( + self.get_project_file_name(self.projects["updater"], "bin") ), ] if self.args.resources: diff --git a/scripts/toolchain/windows-toolchain-download.ps1 b/scripts/toolchain/windows-toolchain-download.ps1 index 370f1a14a9c..aaed89856cc 100644 --- a/scripts/toolchain/windows-toolchain-download.ps1 +++ b/scripts/toolchain/windows-toolchain-download.ps1 @@ -23,12 +23,12 @@ if (!(Test-Path -LiteralPath "$repo_root\toolchain")) { New-Item "$repo_root\toolchain" -ItemType Directory } -Write-Host -NoNewline "Unziping Windows toolchain.." +Write-Host -NoNewline "Extracting Windows toolchain.." Add-Type -Assembly "System.IO.Compression.Filesystem" -[System.IO.Compression.ZipFile]::ExtractToDirectory("$toolchain_zip", "$repo_root\") +[System.IO.Compression.ZipFile]::ExtractToDirectory("$repo_root\$toolchain_zip", "$repo_root\") Move-Item -Path "$repo_root\$toolchain_dir" -Destination "$repo_root\toolchain\x86_64-windows" Write-Host "done!" -Write-Host -NoNewline "Clearing temporary files.." +Write-Host -NoNewline "Cleaning up temporary files.." Remove-Item -LiteralPath "$repo_root\$toolchain_zip" -Force Write-Host "done!" diff --git a/site_scons/extapps.scons b/site_scons/extapps.scons index ee317be3bae..90d228e585c 100644 --- a/site_scons/extapps.scons +++ b/site_scons/extapps.scons @@ -21,7 +21,7 @@ appenv = ENV.Clone( ) appenv.Replace( - LINKER_SCRIPT="application_ext", + LINKER_SCRIPT=appenv.subst("$APP_LINKER_SCRIPT"), ) appenv.AppendUnique( diff --git a/site_scons/firmwareopts.scons b/site_scons/firmwareopts.scons index f04b55cdd20..9f707b4d8f0 100644 --- a/site_scons/firmwareopts.scons +++ b/site_scons/firmwareopts.scons @@ -32,12 +32,27 @@ else: ], ) -ENV.Append( +ENV.AppendUnique( LINKFLAGS=[ - "-Tfirmware/targets/f${TARGET_HW}/${LINKER_SCRIPT}.ld", + "-specs=nano.specs", + "-specs=nosys.specs", + "-Wl,--gc-sections", + "-Wl,--undefined=uxTopUsedPriority", + "-Wl,--wrap,_malloc_r", + "-Wl,--wrap,_free_r", + "-Wl,--wrap,_calloc_r", + "-Wl,--wrap,_realloc_r", + "-n", + "-Xlinker", + "-Map=${TARGET}.map", + "-T${LINKER_SCRIPT_PATH}", ], ) +ENV.SetDefault( + LINKER_SCRIPT_PATH="firmware/targets/f${TARGET_HW}/${LINKER_SCRIPT}.ld", +) + if ENV["FIRMWARE_BUILD_CFG"] == "updater": ENV.Append( IMAGE_BASE_ADDRESS="0x20000000", @@ -47,4 +62,5 @@ else: ENV.Append( IMAGE_BASE_ADDRESS="0x8000000", LINKER_SCRIPT="stm32wb55xx_flash", + APP_LINKER_SCRIPT="application_ext", ) From 09b622d4ae01ef6d3ffcfd0eefabfa5cf890ffdd Mon Sep 17 00:00:00 2001 From: Konstantin Volkov <72250702+doomwastaken@users.noreply.github.com> Date: Fri, 28 Oct 2022 18:45:22 +0300 Subject: [PATCH 186/824] UnitTests: removed all continue-on-error lines (#1946) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * removed all continue-on-error lines * Github: add assets deployment after format Co-authored-by: Konstantin Volkov Co-authored-by: あく --- .github/workflows/unit_tests.yml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 8c5bac2a282..b5bf1000497 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -24,33 +24,29 @@ jobs: - name: 'Compile unit tests firmware' id: compile - continue-on-error: true run: | FBT_TOOLCHAIN_PATH=/opt ./fbt flash OPENOCD_ADAPTER_SERIAL=2A0906016415303030303032 FIRMWARE_APP_SET=unit_tests FORCE=1 - name: 'Wait for flipper to finish updating' id: connect if: steps.compile.outcome == 'success' - continue-on-error: true run: | python3 ./scripts/testing/await_flipper.py ${{steps.device.outputs.flipper}} - name: 'Format flipper SD card' id: format if: steps.connect.outcome == 'success' - continue-on-error: true run: | ./scripts/storage.py -p ${{steps.device.outputs.flipper}} format_ext - - name: 'Copy unit tests to flipper' + - name: 'Copy assets and unit tests data to flipper' id: copy if: steps.format.outcome == 'success' - continue-on-error: true run: | + ./scripts/storage.py -p ${{steps.device.outputs.flipper}} send assets/resources /ext ./scripts/storage.py -p ${{steps.device.outputs.flipper}} send assets/unit_tests /ext/unit_tests - name: 'Run units and validate results' if: steps.copy.outcome == 'success' - continue-on-error: true run: | python3 ./scripts/testing/units.py ${{steps.device.outputs.flipper}} From 93a6e17ce57222fa7b92a930dd4f9aaee3a146bf Mon Sep 17 00:00:00 2001 From: gornekich Date: Fri, 28 Oct 2022 20:10:16 +0400 Subject: [PATCH 187/824] [FL-2933] Mf Classic initial write, update, detect reader (#1941) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * nfc: introduce nfc write * nfc: add write logic * nfc worker: add write state * nfc: add mfc update logic * nfc: add update success logic * nfc: add custom card for detect reader * nfc: update write logic * nfc: add halt command, add notifications * nfc: add write fail scene * nfc: fixes and clean up * nfc: fix navigation ad notifications * nfc: fix detect reader nfc data setter Co-authored-by: あく --- .../main/nfc/scenes/nfc_scene_config.h | 6 + .../main/nfc/scenes/nfc_scene_detect_reader.c | 5 + .../nfc/scenes/nfc_scene_mf_classic_update.c | 98 +++++++ .../nfc_scene_mf_classic_update_success.c | 44 +++ .../nfc/scenes/nfc_scene_mf_classic_write.c | 92 ++++++ .../scenes/nfc_scene_mf_classic_write_fail.c | 58 ++++ .../nfc_scene_mf_classic_write_success.c | 44 +++ .../scenes/nfc_scene_mf_classic_wrong_card.c | 53 ++++ .../main/nfc/scenes/nfc_scene_saved_menu.c | 34 +++ .../main/nfc/scenes/nfc_scene_start.c | 1 + applications/main/nfc/views/detect_reader.c | 36 +++ applications/main/nfc/views/detect_reader.h | 2 + firmware/targets/f7/furi_hal/furi_hal_nfc.c | 10 +- lib/nfc/helpers/reader_analyzer.c | 9 +- lib/nfc/helpers/reader_analyzer.h | 2 + lib/nfc/nfc_device.c | 7 + lib/nfc/nfc_worker.c | 150 +++++++++- lib/nfc/nfc_worker.h | 7 +- lib/nfc/nfc_worker_i.h | 4 + lib/nfc/protocols/crypto1.c | 52 ++++ lib/nfc/protocols/crypto1.h | 14 + lib/nfc/protocols/mifare_classic.c | 271 ++++++++++++------ lib/nfc/protocols/mifare_classic.h | 43 +++ 23 files changed, 949 insertions(+), 93 deletions(-) create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_classic_update.c create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_classic_update_success.c create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_classic_write.c create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_classic_write_fail.c create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_classic_write_success.c create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_classic_wrong_card.c diff --git a/applications/main/nfc/scenes/nfc_scene_config.h b/applications/main/nfc/scenes/nfc_scene_config.h index a25850c8456..9b922add2f4 100644 --- a/applications/main/nfc/scenes/nfc_scene_config.h +++ b/applications/main/nfc/scenes/nfc_scene_config.h @@ -36,6 +36,12 @@ ADD_SCENE(nfc, mf_classic_keys_list, MfClassicKeysList) ADD_SCENE(nfc, mf_classic_keys_delete, MfClassicKeysDelete) ADD_SCENE(nfc, mf_classic_keys_warn_duplicate, MfClassicKeysWarnDuplicate) ADD_SCENE(nfc, mf_classic_dict_attack, MfClassicDictAttack) +ADD_SCENE(nfc, mf_classic_write, MfClassicWrite) +ADD_SCENE(nfc, mf_classic_write_success, MfClassicWriteSuccess) +ADD_SCENE(nfc, mf_classic_write_fail, MfClassicWriteFail) +ADD_SCENE(nfc, mf_classic_update, MfClassicUpdate) +ADD_SCENE(nfc, mf_classic_update_success, MfClassicUpdateSuccess) +ADD_SCENE(nfc, mf_classic_wrong_card, MfClassicWrongCard) ADD_SCENE(nfc, emv_read_success, EmvReadSuccess) ADD_SCENE(nfc, emv_menu, EmvMenu) ADD_SCENE(nfc, emulate_apdu_sequence, EmulateApduSequence) diff --git a/applications/main/nfc/scenes/nfc_scene_detect_reader.c b/applications/main/nfc/scenes/nfc_scene_detect_reader.c index abf1437d2ce..745946157b0 100644 --- a/applications/main/nfc/scenes/nfc_scene_detect_reader.c +++ b/applications/main/nfc/scenes/nfc_scene_detect_reader.c @@ -28,6 +28,11 @@ void nfc_scene_detect_reader_on_enter(void* context) { detect_reader_set_callback(nfc->detect_reader, nfc_scene_detect_reader_callback, nfc); detect_reader_set_nonces_max(nfc->detect_reader, NFC_SCENE_DETECT_READER_PAIR_NONCES_MAX); + NfcDeviceData* dev_data = &nfc->dev->dev_data; + if(dev_data->nfc_data.uid_len) { + detect_reader_set_uid( + nfc->detect_reader, dev_data->nfc_data.uid, dev_data->nfc_data.uid_len); + } // Store number of collected nonces in scene state scene_manager_set_scene_state(nfc->scene_manager, NfcSceneDetectReader, 0); diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_update.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_update.c new file mode 100644 index 00000000000..dd3a6f7d597 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_update.c @@ -0,0 +1,98 @@ +#include "../nfc_i.h" +#include + +enum { + NfcSceneMfClassicUpdateStateCardSearch, + NfcSceneMfClassicUpdateStateCardFound, +}; + +bool nfc_mf_classic_update_worker_callback(NfcWorkerEvent event, void* context) { + furi_assert(context); + + Nfc* nfc = context; + view_dispatcher_send_custom_event(nfc->view_dispatcher, event); + + return true; +} + +static void nfc_scene_mf_classic_update_setup_view(Nfc* nfc) { + Popup* popup = nfc->popup; + popup_reset(popup); + uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfClassicUpdate); + + if(state == NfcSceneMfClassicUpdateStateCardSearch) { + popup_set_text( + nfc->popup, "Apply the initial\ncard only", 128, 32, AlignRight, AlignCenter); + popup_set_icon(nfc->popup, 0, 8, &I_NFC_manual_60x50); + } else { + popup_set_header(popup, "Updating\nDon't move...", 52, 32, AlignLeft, AlignCenter); + popup_set_icon(popup, 12, 23, &A_Loading_24); + } + + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); +} + +void nfc_scene_mf_classic_update_on_enter(void* context) { + Nfc* nfc = context; + DOLPHIN_DEED(DolphinDeedNfcEmulate); + + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneMfClassicUpdate, NfcSceneMfClassicUpdateStateCardSearch); + nfc_scene_mf_classic_update_setup_view(nfc); + + // Setup and start worker + nfc_worker_start( + nfc->worker, + NfcWorkerStateMfClassicUpdate, + &nfc->dev->dev_data, + nfc_mf_classic_update_worker_callback, + nfc); + nfc_blink_emulate_start(nfc); +} + +bool nfc_scene_mf_classic_update_on_event(void* context, SceneManagerEvent event) { + Nfc* nfc = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcWorkerEventSuccess) { + nfc_worker_stop(nfc->worker); + if(nfc_device_save_shadow(nfc->dev, nfc->dev->dev_name)) { + scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicUpdateSuccess); + } else { + scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicWrongCard); + } + consumed = true; + } else if(event.event == NfcWorkerEventWrongCard) { + nfc_worker_stop(nfc->worker); + scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicWrongCard); + consumed = true; + } else if(event.event == NfcWorkerEventCardDetected) { + scene_manager_set_scene_state( + nfc->scene_manager, + NfcSceneMfClassicUpdate, + NfcSceneMfClassicUpdateStateCardFound); + nfc_scene_mf_classic_update_setup_view(nfc); + consumed = true; + } else if(event.event == NfcWorkerEventNoCardDetected) { + scene_manager_set_scene_state( + nfc->scene_manager, + NfcSceneMfClassicUpdate, + NfcSceneMfClassicUpdateStateCardSearch); + nfc_scene_mf_classic_update_setup_view(nfc); + consumed = true; + } + } + return consumed; +} + +void nfc_scene_mf_classic_update_on_exit(void* context) { + Nfc* nfc = context; + nfc_worker_stop(nfc->worker); + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneMfClassicUpdate, NfcSceneMfClassicUpdateStateCardSearch); + // Clear view + popup_reset(nfc->popup); + + nfc_blink_stop(nfc); +} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_update_success.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_update_success.c new file mode 100644 index 00000000000..fef8fd5e932 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_update_success.c @@ -0,0 +1,44 @@ +#include "../nfc_i.h" +#include + +void nfc_scene_mf_classic_update_success_popup_callback(void* context) { + Nfc* nfc = context; + view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit); +} + +void nfc_scene_mf_classic_update_success_on_enter(void* context) { + Nfc* nfc = context; + DOLPHIN_DEED(DolphinDeedNfcSave); + + notification_message(nfc->notifications, &sequence_success); + + Popup* popup = nfc->popup; + popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); + popup_set_header(popup, "Updated!", 11, 20, AlignLeft, AlignBottom); + popup_set_timeout(popup, 1500); + popup_set_context(popup, nfc); + popup_set_callback(popup, nfc_scene_mf_classic_update_success_popup_callback); + popup_enable_timeout(popup); + + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); +} + +bool nfc_scene_mf_classic_update_success_on_event(void* context, SceneManagerEvent event) { + Nfc* nfc = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcCustomEventViewExit) { + consumed = scene_manager_search_and_switch_to_previous_scene( + nfc->scene_manager, NfcSceneFileSelect); + } + } + return consumed; +} + +void nfc_scene_mf_classic_update_success_on_exit(void* context) { + Nfc* nfc = context; + + // Clear view + popup_reset(nfc->popup); +} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_write.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_write.c new file mode 100644 index 00000000000..3543cbc5889 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_write.c @@ -0,0 +1,92 @@ +#include "../nfc_i.h" +#include + +enum { + NfcSceneMfClassicWriteStateCardSearch, + NfcSceneMfClassicWriteStateCardFound, +}; + +bool nfc_mf_classic_write_worker_callback(NfcWorkerEvent event, void* context) { + furi_assert(context); + + Nfc* nfc = context; + view_dispatcher_send_custom_event(nfc->view_dispatcher, event); + + return true; +} + +static void nfc_scene_mf_classic_write_setup_view(Nfc* nfc) { + Popup* popup = nfc->popup; + popup_reset(popup); + uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfClassicWrite); + + if(state == NfcSceneMfClassicWriteStateCardSearch) { + popup_set_text( + nfc->popup, "Apply the initial\ncard only", 128, 32, AlignRight, AlignCenter); + popup_set_icon(nfc->popup, 0, 8, &I_NFC_manual_60x50); + } else { + popup_set_header(popup, "Writing\nDon't move...", 52, 32, AlignLeft, AlignCenter); + popup_set_icon(popup, 12, 23, &A_Loading_24); + } + + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); +} + +void nfc_scene_mf_classic_write_on_enter(void* context) { + Nfc* nfc = context; + DOLPHIN_DEED(DolphinDeedNfcEmulate); + + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneMfClassicWrite, NfcSceneMfClassicWriteStateCardSearch); + nfc_scene_mf_classic_write_setup_view(nfc); + + // Setup and start worker + nfc_worker_start( + nfc->worker, + NfcWorkerStateMfClassicWrite, + &nfc->dev->dev_data, + nfc_mf_classic_write_worker_callback, + nfc); + nfc_blink_emulate_start(nfc); +} + +bool nfc_scene_mf_classic_write_on_event(void* context, SceneManagerEvent event) { + Nfc* nfc = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcWorkerEventSuccess) { + scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicWriteSuccess); + consumed = true; + } else if(event.event == NfcWorkerEventFail) { + scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicWriteFail); + consumed = true; + } else if(event.event == NfcWorkerEventWrongCard) { + scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicWrongCard); + consumed = true; + } else if(event.event == NfcWorkerEventCardDetected) { + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneMfClassicWrite, NfcSceneMfClassicWriteStateCardFound); + nfc_scene_mf_classic_write_setup_view(nfc); + consumed = true; + } else if(event.event == NfcWorkerEventNoCardDetected) { + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneMfClassicWrite, NfcSceneMfClassicWriteStateCardSearch); + nfc_scene_mf_classic_write_setup_view(nfc); + consumed = true; + } + } + return consumed; +} + +void nfc_scene_mf_classic_write_on_exit(void* context) { + Nfc* nfc = context; + + nfc_worker_stop(nfc->worker); + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneMfClassicWrite, NfcSceneMfClassicWriteStateCardSearch); + // Clear view + popup_reset(nfc->popup); + + nfc_blink_stop(nfc); +} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_write_fail.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_write_fail.c new file mode 100644 index 00000000000..aeea6eef069 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_write_fail.c @@ -0,0 +1,58 @@ +#include "../nfc_i.h" + +void nfc_scene_mf_classic_write_fail_widget_callback( + GuiButtonType result, + InputType type, + void* context) { + Nfc* nfc = context; + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(nfc->view_dispatcher, result); + } +} + +void nfc_scene_mf_classic_write_fail_on_enter(void* context) { + Nfc* nfc = context; + Widget* widget = nfc->widget; + + notification_message(nfc->notifications, &sequence_error); + + widget_add_icon_element(widget, 72, 17, &I_DolphinCommon_56x48); + widget_add_string_element( + widget, 7, 4, AlignLeft, AlignTop, FontPrimary, "Writing gone wrong!"); + widget_add_string_multiline_element( + widget, + 7, + 17, + AlignLeft, + AlignTop, + FontSecondary, + "Not all sectors\nwere written\ncorrectly."); + + widget_add_button_element( + widget, GuiButtonTypeLeft, "Finish", nfc_scene_mf_classic_write_fail_widget_callback, nfc); + + // Setup and start worker + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); +} + +bool nfc_scene_mf_classic_write_fail_on_event(void* context, SceneManagerEvent event) { + Nfc* nfc = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == GuiButtonTypeLeft) { + consumed = scene_manager_search_and_switch_to_previous_scene( + nfc->scene_manager, NfcSceneFileSelect); + } + } else if(event.type == SceneManagerEventTypeBack) { + consumed = scene_manager_search_and_switch_to_previous_scene( + nfc->scene_manager, NfcSceneSavedMenu); + } + return consumed; +} + +void nfc_scene_mf_classic_write_fail_on_exit(void* context) { + Nfc* nfc = context; + + widget_reset(nfc->widget); +} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_write_success.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_write_success.c new file mode 100644 index 00000000000..2f2a3beb119 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_write_success.c @@ -0,0 +1,44 @@ +#include "../nfc_i.h" +#include + +void nfc_scene_mf_classic_write_success_popup_callback(void* context) { + Nfc* nfc = context; + view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit); +} + +void nfc_scene_mf_classic_write_success_on_enter(void* context) { + Nfc* nfc = context; + DOLPHIN_DEED(DolphinDeedNfcSave); + + notification_message(nfc->notifications, &sequence_success); + + Popup* popup = nfc->popup; + popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); + popup_set_header(popup, "Successfully\nwritten", 13, 22, AlignLeft, AlignBottom); + popup_set_timeout(popup, 1500); + popup_set_context(popup, nfc); + popup_set_callback(popup, nfc_scene_mf_classic_write_success_popup_callback); + popup_enable_timeout(popup); + + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); +} + +bool nfc_scene_mf_classic_write_success_on_event(void* context, SceneManagerEvent event) { + Nfc* nfc = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcCustomEventViewExit) { + consumed = scene_manager_search_and_switch_to_previous_scene( + nfc->scene_manager, NfcSceneFileSelect); + } + } + return consumed; +} + +void nfc_scene_mf_classic_write_success_on_exit(void* context) { + Nfc* nfc = context; + + // Clear view + popup_reset(nfc->popup); +} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_wrong_card.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_wrong_card.c new file mode 100644 index 00000000000..2c56270e36d --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_wrong_card.c @@ -0,0 +1,53 @@ +#include "../nfc_i.h" + +void nfc_scene_mf_classic_wrong_card_widget_callback( + GuiButtonType result, + InputType type, + void* context) { + Nfc* nfc = context; + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(nfc->view_dispatcher, result); + } +} + +void nfc_scene_mf_classic_wrong_card_on_enter(void* context) { + Nfc* nfc = context; + Widget* widget = nfc->widget; + + notification_message(nfc->notifications, &sequence_error); + + widget_add_icon_element(widget, 73, 17, &I_DolphinCommon_56x48); + widget_add_string_element( + widget, 3, 4, AlignLeft, AlignTop, FontPrimary, "This is wrong card"); + widget_add_string_multiline_element( + widget, + 4, + 17, + AlignLeft, + AlignTop, + FontSecondary, + "Data management\nis only possible\nwith initial card"); + widget_add_button_element( + widget, GuiButtonTypeLeft, "Retry", nfc_scene_mf_classic_wrong_card_widget_callback, nfc); + + // Setup and start worker + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); +} + +bool nfc_scene_mf_classic_wrong_card_on_event(void* context, SceneManagerEvent event) { + Nfc* nfc = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == GuiButtonTypeLeft) { + consumed = scene_manager_previous_scene(nfc->scene_manager); + } + } + return consumed; +} + +void nfc_scene_mf_classic_wrong_card_on_exit(void* context) { + Nfc* nfc = context; + + widget_reset(nfc->widget); +} \ No newline at end of file diff --git a/applications/main/nfc/scenes/nfc_scene_saved_menu.c b/applications/main/nfc/scenes/nfc_scene_saved_menu.c index 09d2c2d61b3..231f12089be 100644 --- a/applications/main/nfc/scenes/nfc_scene_saved_menu.c +++ b/applications/main/nfc/scenes/nfc_scene_saved_menu.c @@ -4,6 +4,9 @@ enum SubmenuIndex { SubmenuIndexEmulate, SubmenuIndexEditUid, + SubmenuIndexDetectReader, + SubmenuIndexWrite, + SubmenuIndexUpdate, SubmenuIndexRename, SubmenuIndexDelete, SubmenuIndexInfo, @@ -42,6 +45,28 @@ void nfc_scene_saved_menu_on_enter(void* context) { submenu_add_item( submenu, "Emulate", SubmenuIndexEmulate, nfc_scene_saved_menu_submenu_callback, nfc); } + if(nfc->dev->format == NfcDeviceSaveFormatMifareClassic) { + if(!mf_classic_is_card_read(&nfc->dev->dev_data.mf_classic_data)) { + submenu_add_item( + submenu, + "Detect reader", + SubmenuIndexDetectReader, + nfc_scene_saved_menu_submenu_callback, + nfc); + } + submenu_add_item( + submenu, + "Write To Initial Card", + SubmenuIndexWrite, + nfc_scene_saved_menu_submenu_callback, + nfc); + submenu_add_item( + submenu, + "Update From Initial Card", + SubmenuIndexUpdate, + nfc_scene_saved_menu_submenu_callback, + nfc); + } submenu_add_item( submenu, "Info", SubmenuIndexInfo, nfc_scene_saved_menu_submenu_callback, nfc); if(nfc->dev->shadow_file_exist) { @@ -79,6 +104,15 @@ bool nfc_scene_saved_menu_on_event(void* context, SceneManagerEvent event) { } DOLPHIN_DEED(DolphinDeedNfcEmulate); consumed = true; + } else if(event.event == SubmenuIndexDetectReader) { + scene_manager_next_scene(nfc->scene_manager, NfcSceneDetectReader); + consumed = true; + } else if(event.event == SubmenuIndexWrite) { + scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicWrite); + consumed = true; + } else if(event.event == SubmenuIndexUpdate) { + scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicUpdate); + consumed = true; } else if(event.event == SubmenuIndexRename) { scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveName); consumed = true; diff --git a/applications/main/nfc/scenes/nfc_scene_start.c b/applications/main/nfc/scenes/nfc_scene_start.c index 0c4ec1cf902..028f85ae033 100644 --- a/applications/main/nfc/scenes/nfc_scene_start.c +++ b/applications/main/nfc/scenes/nfc_scene_start.c @@ -53,6 +53,7 @@ bool nfc_scene_start_on_event(void* context, SceneManagerEvent event) { } else if(event.event == SubmenuIndexDetectReader) { bool sd_exist = storage_sd_status(nfc->dev->storage) == FSE_OK; if(sd_exist) { + nfc_device_data_clear(&nfc->dev->dev_data); scene_manager_next_scene(nfc->scene_manager, NfcSceneDetectReader); DOLPHIN_DEED(DolphinDeedNfcDetectReader); } else { diff --git a/applications/main/nfc/views/detect_reader.c b/applications/main/nfc/views/detect_reader.c index 91537868b8b..e5951beb267 100644 --- a/applications/main/nfc/views/detect_reader.c +++ b/applications/main/nfc/views/detect_reader.c @@ -2,6 +2,8 @@ #include #include +#define DETECT_READER_UID_MAX_LEN (10) + struct DetectReader { View* view; DetectReaderDoneCallback callback; @@ -12,6 +14,7 @@ typedef struct { uint16_t nonces; uint16_t nonces_max; DetectReaderState state; + FuriString* uid_str; } DetectReaderViewModel; static void detect_reader_draw_callback(Canvas* canvas, void* model) { @@ -23,6 +26,10 @@ static void detect_reader_draw_callback(Canvas* canvas, void* model) { if(m->state == DetectReaderStateStart) { snprintf(text, sizeof(text), "Touch the reader"); canvas_draw_icon(canvas, 21, 13, &I_Move_flipper_26x39); + if(furi_string_size(m->uid_str)) { + elements_multiline_text_aligned( + canvas, 64, 64, AlignCenter, AlignBottom, furi_string_get_cstr(m->uid_str)); + } } else if(m->state == DetectReaderStateReaderDetected) { snprintf(text, sizeof(text), "Move the Flipper away"); canvas_draw_icon(canvas, 24, 25, &I_Release_arrow_18x15); @@ -86,12 +93,24 @@ DetectReader* detect_reader_alloc() { view_set_input_callback(detect_reader->view, detect_reader_input_callback); view_set_context(detect_reader->view, detect_reader); + with_view_model( + detect_reader->view, + DetectReaderViewModel * model, + { model->uid_str = furi_string_alloc(); }, + false); + return detect_reader; } void detect_reader_free(DetectReader* detect_reader) { furi_assert(detect_reader); + with_view_model( + detect_reader->view, + DetectReaderViewModel * model, + { furi_string_free(model->uid_str); }, + false); + view_free(detect_reader->view); free(detect_reader); } @@ -106,6 +125,7 @@ void detect_reader_reset(DetectReader* detect_reader) { model->nonces = 0; model->nonces_max = 0; model->state = DetectReaderStateStart; + furi_string_reset(model->uid_str); }, false); } @@ -152,3 +172,19 @@ void detect_reader_set_state(DetectReader* detect_reader, DetectReaderState stat with_view_model( detect_reader->view, DetectReaderViewModel * model, { model->state = state; }, true); } + +void detect_reader_set_uid(DetectReader* detect_reader, uint8_t* uid, uint8_t uid_len) { + furi_assert(detect_reader); + furi_assert(uid); + furi_assert(uid_len < DETECT_READER_UID_MAX_LEN); + with_view_model( + detect_reader->view, + DetectReaderViewModel * model, + { + furi_string_set_str(model->uid_str, "UID:"); + for(size_t i = 0; i < uid_len; i++) { + furi_string_cat_printf(model->uid_str, " %02X", uid[i]); + } + }, + true); +} diff --git a/applications/main/nfc/views/detect_reader.h b/applications/main/nfc/views/detect_reader.h index aabdd7c87db..6481216b4cf 100644 --- a/applications/main/nfc/views/detect_reader.h +++ b/applications/main/nfc/views/detect_reader.h @@ -32,3 +32,5 @@ void detect_reader_set_nonces_max(DetectReader* detect_reader, uint16_t nonces_m void detect_reader_set_nonces_collected(DetectReader* detect_reader, uint16_t nonces_collected); void detect_reader_set_state(DetectReader* detect_reader, DetectReaderState state); + +void detect_reader_set_uid(DetectReader* detect_reader, uint8_t* uid, uint8_t uid_len); diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc.c b/firmware/targets/f7/furi_hal/furi_hal_nfc.c index 069ac4ea41c..3ebf4f82b79 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_nfc.c +++ b/firmware/targets/f7/furi_hal/furi_hal_nfc.c @@ -620,6 +620,10 @@ uint16_t furi_hal_nfc_bitstream_to_data_and_parity( uint16_t in_buff_bits, uint8_t* out_data, uint8_t* out_parity) { + if(in_buff_bits < 8) { + out_data[0] = in_buff[0]; + return in_buff_bits; + } if(in_buff_bits % 9 != 0) { return 0; } @@ -635,7 +639,7 @@ uint16_t furi_hal_nfc_bitstream_to_data_and_parity( bit_processed += 9; curr_byte++; } - return curr_byte; + return curr_byte * 8; } bool furi_hal_nfc_tx_rx(FuriHalNfcTxRxContext* tx_rx, uint16_t timeout_ms) { @@ -692,8 +696,8 @@ bool furi_hal_nfc_tx_rx(FuriHalNfcTxRxContext* tx_rx, uint16_t timeout_ms) { 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); + tx_rx->rx_bits = furi_hal_nfc_bitstream_to_data_and_parity( + temp_rx_buff, *temp_rx_bits, tx_rx->rx_data, tx_rx->rx_parity); } else { memcpy(tx_rx->rx_data, temp_rx_buff, MIN(*temp_rx_bits / 8, FURI_HAL_NFC_DATA_BUFF_SIZE)); tx_rx->rx_bits = *temp_rx_bits; diff --git a/lib/nfc/helpers/reader_analyzer.c b/lib/nfc/helpers/reader_analyzer.c index 0ba657a2eab..7fed9c6f68a 100644 --- a/lib/nfc/helpers/reader_analyzer.c +++ b/lib/nfc/helpers/reader_analyzer.c @@ -201,10 +201,17 @@ NfcProtocol FuriHalNfcDevData* reader_analyzer_get_nfc_data(ReaderAnalyzer* instance) { furi_assert(instance); - + instance->nfc_data = reader_analyzer_nfc_data[ReaderAnalyzerNfcDataMfClassic]; return &instance->nfc_data; } +void reader_analyzer_set_nfc_data(ReaderAnalyzer* instance, FuriHalNfcDevData* nfc_data) { + furi_assert(instance); + furi_assert(nfc_data); + + memcpy(&instance->nfc_data, nfc_data, sizeof(FuriHalNfcDevData)); +} + static void reader_analyzer_write( ReaderAnalyzer* instance, uint8_t* data, diff --git a/lib/nfc/helpers/reader_analyzer.h b/lib/nfc/helpers/reader_analyzer.h index cc501f5a63b..13bf4d77ccf 100644 --- a/lib/nfc/helpers/reader_analyzer.h +++ b/lib/nfc/helpers/reader_analyzer.h @@ -35,6 +35,8 @@ NfcProtocol FuriHalNfcDevData* reader_analyzer_get_nfc_data(ReaderAnalyzer* instance); +void reader_analyzer_set_nfc_data(ReaderAnalyzer* instance, FuriHalNfcDevData* nfc_data); + void reader_analyzer_prepare_tx_rx( ReaderAnalyzer* instance, FuriHalNfcTxRxContext* tx_rx, diff --git a/lib/nfc/nfc_device.c b/lib/nfc/nfc_device.c index 740cfae5e75..a5e3fc14f20 100644 --- a/lib/nfc/nfc_device.c +++ b/lib/nfc/nfc_device.c @@ -1122,6 +1122,13 @@ static bool nfc_device_load_data(NfcDevice* dev, FuriString* path, bool show_dia if(!flipper_format_read_hex(file, "UID", data->uid, data->uid_len)) break; if(!flipper_format_read_hex(file, "ATQA", data->atqa, 2)) break; if(!flipper_format_read_hex(file, "SAK", &data->sak, 1)) break; + // Load CUID + uint8_t* cuid_start = data->uid; + if(data->uid_len == 7) { + cuid_start = &data->uid[3]; + } + data->cuid = (cuid_start[0] << 24) | (cuid_start[1] << 16) | (cuid_start[2] << 8) | + (cuid_start[3]); // Parse other data if(dev->format == NfcDeviceSaveFormatMifareUl) { if(!nfc_device_load_mifare_ul_data(file, dev)) break; diff --git a/lib/nfc/nfc_worker.c b/lib/nfc/nfc_worker.c index ebe203905fd..e1e379a0667 100644 --- a/lib/nfc/nfc_worker.c +++ b/lib/nfc/nfc_worker.c @@ -99,6 +99,10 @@ int32_t nfc_worker_task(void* context) { nfc_worker_emulate_mf_ultralight(nfc_worker); } else if(nfc_worker->state == NfcWorkerStateMfClassicEmulate) { nfc_worker_emulate_mf_classic(nfc_worker); + } else if(nfc_worker->state == NfcWorkerStateMfClassicWrite) { + nfc_worker_write_mf_classic(nfc_worker); + } else if(nfc_worker->state == NfcWorkerStateMfClassicUpdate) { + nfc_worker_update_mf_classic(nfc_worker); } else if(nfc_worker->state == NfcWorkerStateReadMfUltralightReadAuth) { nfc_worker_mf_ultralight_read_auth(nfc_worker); } else if(nfc_worker->state == NfcWorkerStateMfClassicDictAttack) { @@ -666,6 +670,144 @@ void nfc_worker_emulate_mf_classic(NfcWorker* nfc_worker) { rfal_platform_spi_release(); } +void nfc_worker_write_mf_classic(NfcWorker* nfc_worker) { + FuriHalNfcTxRxContext tx_rx = {}; + bool card_found_notified = false; + FuriHalNfcDevData nfc_data = {}; + MfClassicData* src_data = &nfc_worker->dev_data->mf_classic_data; + MfClassicData dest_data = *src_data; + + while(nfc_worker->state == NfcWorkerStateMfClassicWrite) { + if(furi_hal_nfc_detect(&nfc_data, 200)) { + if(!card_found_notified) { + nfc_worker->callback(NfcWorkerEventCardDetected, nfc_worker->context); + card_found_notified = true; + } + furi_hal_nfc_sleep(); + + FURI_LOG_I(TAG, "Check low level nfc data"); + if(memcmp(&nfc_data, &nfc_worker->dev_data->nfc_data, sizeof(FuriHalNfcDevData))) { + FURI_LOG_E(TAG, "Wrong card"); + nfc_worker->callback(NfcWorkerEventWrongCard, nfc_worker->context); + break; + } + + FURI_LOG_I(TAG, "Check mf classic type"); + MfClassicType type = + mf_classic_get_classic_type(nfc_data.atqa[0], nfc_data.atqa[1], nfc_data.sak); + if(type != nfc_worker->dev_data->mf_classic_data.type) { + FURI_LOG_E(TAG, "Wrong mf classic type"); + nfc_worker->callback(NfcWorkerEventWrongCard, nfc_worker->context); + break; + } + + // Set blocks not read + mf_classic_set_sector_data_not_read(&dest_data); + FURI_LOG_I(TAG, "Updating card sectors"); + uint8_t total_sectors = mf_classic_get_total_sectors_num(type); + bool write_success = true; + for(uint8_t i = 0; i < total_sectors; i++) { + FURI_LOG_I(TAG, "Reading sector %d", i); + mf_classic_read_sector(&tx_rx, &dest_data, i); + bool old_data_read = mf_classic_is_sector_data_read(src_data, i); + bool new_data_read = mf_classic_is_sector_data_read(&dest_data, i); + if(old_data_read != new_data_read) { + FURI_LOG_E(TAG, "Failed to update sector %d", i); + write_success = false; + break; + } + if(nfc_worker->state != NfcWorkerStateMfClassicWrite) break; + if(!mf_classic_write_sector(&tx_rx, &dest_data, src_data, i)) { + FURI_LOG_E(TAG, "Failed to write %d sector", i); + write_success = false; + break; + } + } + if(nfc_worker->state != NfcWorkerStateMfClassicWrite) break; + if(write_success) { + nfc_worker->callback(NfcWorkerEventSuccess, nfc_worker->context); + break; + } else { + nfc_worker->callback(NfcWorkerEventFail, nfc_worker->context); + break; + } + + } else { + if(card_found_notified) { + nfc_worker->callback(NfcWorkerEventNoCardDetected, nfc_worker->context); + card_found_notified = false; + } + } + furi_delay_ms(300); + } +} + +void nfc_worker_update_mf_classic(NfcWorker* nfc_worker) { + FuriHalNfcTxRxContext tx_rx = {}; + bool card_found_notified = false; + FuriHalNfcDevData nfc_data = {}; + MfClassicData* old_data = &nfc_worker->dev_data->mf_classic_data; + MfClassicData new_data = *old_data; + + while(nfc_worker->state == NfcWorkerStateMfClassicUpdate) { + if(furi_hal_nfc_detect(&nfc_data, 200)) { + if(!card_found_notified) { + nfc_worker->callback(NfcWorkerEventCardDetected, nfc_worker->context); + card_found_notified = true; + } + furi_hal_nfc_sleep(); + + FURI_LOG_I(TAG, "Check low level nfc data"); + if(memcmp(&nfc_data, &nfc_worker->dev_data->nfc_data, sizeof(FuriHalNfcDevData))) { + FURI_LOG_E(TAG, "Low level nfc data mismatch"); + nfc_worker->callback(NfcWorkerEventWrongCard, nfc_worker->context); + break; + } + + FURI_LOG_I(TAG, "Check MF classic type"); + MfClassicType type = + mf_classic_get_classic_type(nfc_data.atqa[0], nfc_data.atqa[1], nfc_data.sak); + if(type != nfc_worker->dev_data->mf_classic_data.type) { + FURI_LOG_E(TAG, "MF classic type mismatch"); + nfc_worker->callback(NfcWorkerEventWrongCard, nfc_worker->context); + break; + } + + // Set blocks not read + mf_classic_set_sector_data_not_read(&new_data); + FURI_LOG_I(TAG, "Updating card sectors"); + uint8_t total_sectors = mf_classic_get_total_sectors_num(type); + bool update_success = true; + for(uint8_t i = 0; i < total_sectors; i++) { + FURI_LOG_I(TAG, "Reading sector %d", i); + mf_classic_read_sector(&tx_rx, &new_data, i); + bool old_data_read = mf_classic_is_sector_data_read(old_data, i); + bool new_data_read = mf_classic_is_sector_data_read(&new_data, i); + if(old_data_read != new_data_read) { + FURI_LOG_E(TAG, "Failed to update sector %d", i); + update_success = false; + break; + } + if(nfc_worker->state != NfcWorkerStateMfClassicUpdate) break; + } + if(nfc_worker->state != NfcWorkerStateMfClassicUpdate) break; + + // Check updated data + if(update_success) { + *old_data = new_data; + nfc_worker->callback(NfcWorkerEventSuccess, nfc_worker->context); + break; + } + } else { + if(card_found_notified) { + nfc_worker->callback(NfcWorkerEventNoCardDetected, nfc_worker->context); + card_found_notified = false; + } + } + furi_delay_ms(300); + } +} + void nfc_worker_mf_ultralight_read_auth(NfcWorker* nfc_worker) { furi_assert(nfc_worker); furi_assert(nfc_worker->callback); @@ -758,7 +900,13 @@ void nfc_worker_analyze_reader(NfcWorker* nfc_worker) { FuriHalNfcTxRxContext tx_rx = {}; ReaderAnalyzer* reader_analyzer = nfc_worker->reader_analyzer; - FuriHalNfcDevData* nfc_data = reader_analyzer_get_nfc_data(reader_analyzer); + FuriHalNfcDevData* nfc_data = NULL; + if(nfc_worker->dev_data->protocol == NfcDeviceProtocolMifareClassic) { + nfc_data = &nfc_worker->dev_data->nfc_data; + reader_analyzer_set_nfc_data(reader_analyzer, nfc_data); + } else { + nfc_data = reader_analyzer_get_nfc_data(reader_analyzer); + } MfClassicEmulator emulator = { .cuid = nfc_util_bytes2num(&nfc_data->uid[nfc_data->uid_len - 4], 4), .data = nfc_worker->dev_data->mf_classic_data, diff --git a/lib/nfc/nfc_worker.h b/lib/nfc/nfc_worker.h index 84615f5d8b8..ce3a1824178 100644 --- a/lib/nfc/nfc_worker.h +++ b/lib/nfc/nfc_worker.h @@ -14,6 +14,8 @@ typedef enum { NfcWorkerStateUidEmulate, NfcWorkerStateMfUltralightEmulate, NfcWorkerStateMfClassicEmulate, + NfcWorkerStateMfClassicWrite, + NfcWorkerStateMfClassicUpdate, NfcWorkerStateReadMfUltralightReadAuth, NfcWorkerStateMfClassicDictAttack, NfcWorkerStateAnalyzeReader, @@ -48,13 +50,16 @@ typedef enum { NfcWorkerEventNoCardDetected, NfcWorkerEventWrongCardDetected, - // Mifare Classic events + // Read Mifare Classic events NfcWorkerEventNoDictFound, NfcWorkerEventNewSector, NfcWorkerEventNewDictKeyBatch, NfcWorkerEventFoundKeyA, NfcWorkerEventFoundKeyB, + // Write Mifare Classic events + NfcWorkerEventWrongCard, + // Detect Reader events NfcWorkerEventDetectReaderDetected, NfcWorkerEventDetectReaderLost, diff --git a/lib/nfc/nfc_worker_i.h b/lib/nfc/nfc_worker_i.h index 526182f9a99..b9f69e620cf 100644 --- a/lib/nfc/nfc_worker_i.h +++ b/lib/nfc/nfc_worker_i.h @@ -41,6 +41,10 @@ void nfc_worker_emulate_mf_ultralight(NfcWorker* nfc_worker); void nfc_worker_emulate_mf_classic(NfcWorker* nfc_worker); +void nfc_worker_write_mf_classic(NfcWorker* nfc_worker); + +void nfc_worker_update_mf_classic(NfcWorker* nfc_worker); + void nfc_worker_mf_classic_dict_attack(NfcWorker* nfc_worker); void nfc_worker_mf_ultralight_read_auth(NfcWorker* nfc_worker); diff --git a/lib/nfc/protocols/crypto1.c b/lib/nfc/protocols/crypto1.c index f08164ba9f2..2ac0ff0812f 100644 --- a/lib/nfc/protocols/crypto1.c +++ b/lib/nfc/protocols/crypto1.c @@ -73,3 +73,55 @@ uint32_t prng_successor(uint32_t x, uint32_t n) { return SWAPENDIAN(x); } + +void crypto1_decrypt( + Crypto1* crypto, + uint8_t* encrypted_data, + uint16_t encrypted_data_bits, + uint8_t* decrypted_data) { + furi_assert(crypto); + furi_assert(encrypted_data); + furi_assert(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 crypto1_encrypt( + Crypto1* crypto, + uint8_t* keystream, + uint8_t* plain_data, + uint16_t plain_data_bits, + uint8_t* encrypted_data, + uint8_t* encrypted_parity) { + furi_assert(crypto); + furi_assert(plain_data); + furi_assert(encrypted_data); + furi_assert(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))); + } + } +} diff --git a/lib/nfc/protocols/crypto1.h b/lib/nfc/protocols/crypto1.h index 07b39c22ce7..450d1534e32 100644 --- a/lib/nfc/protocols/crypto1.h +++ b/lib/nfc/protocols/crypto1.h @@ -21,3 +21,17 @@ uint32_t crypto1_word(Crypto1* crypto1, uint32_t in, int is_encrypted); uint32_t crypto1_filter(uint32_t in); uint32_t prng_successor(uint32_t x, uint32_t n); + +void crypto1_decrypt( + Crypto1* crypto, + uint8_t* encrypted_data, + uint16_t encrypted_data_bits, + uint8_t* decrypted_data); + +void crypto1_encrypt( + Crypto1* crypto, + uint8_t* keystream, + uint8_t* plain_data, + uint16_t plain_data_bits, + uint8_t* encrypted_data, + uint8_t* encrypted_parity); diff --git a/lib/nfc/protocols/mifare_classic.c b/lib/nfc/protocols/mifare_classic.c index e879ff4ef41..7b0e17975bd 100644 --- a/lib/nfc/protocols/mifare_classic.c +++ b/lib/nfc/protocols/mifare_classic.c @@ -9,21 +9,8 @@ #define MF_CLASSIC_AUTH_KEY_A_CMD (0x60U) #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; +#define MF_CLASSIC_READ_BLOCK_CMD (0x30) +#define MF_CLASSIC_WRITE_BLOCK_CMD (0xA0) const char* mf_classic_get_type_str(MfClassicType type) { if(type == MfClassicType1k) { @@ -122,6 +109,24 @@ void mf_classic_set_block_read(MfClassicData* data, uint8_t block_num, MfClassic FURI_BIT_SET(data->block_read_mask[block_num / 32], block_num % 32); } +bool mf_classic_is_sector_data_read(MfClassicData* data, uint8_t sector_num) { + furi_assert(data); + + uint8_t first_block = mf_classic_get_first_block_num_of_sector(sector_num); + uint8_t total_blocks = mf_classic_get_blocks_num_in_sector(sector_num); + bool data_read = true; + for(size_t i = first_block; i < first_block + total_blocks; i++) { + data_read &= mf_classic_is_block_read(data, i); + } + + return data_read; +} + +void mf_classic_set_sector_data_not_read(MfClassicData* data) { + furi_assert(data); + memset(data->block_read_mask, 0, sizeof(data->block_read_mask)); +} + bool mf_classic_is_key_found(MfClassicData* data, uint8_t sector_num, MfClassicKey key_type) { furi_assert(data); @@ -190,6 +195,9 @@ void mf_classic_get_read_sectors_and_keys( uint8_t* sectors_read, uint8_t* keys_found) { furi_assert(data); + furi_assert(sectors_read); + furi_assert(keys_found); + *sectors_read = 0; *keys_found = 0; uint8_t sectors_total = mf_classic_get_total_sectors_num(data->type); @@ -225,12 +233,12 @@ bool mf_classic_is_card_read(MfClassicData* data) { return card_read; } -static bool mf_classic_is_allowed_access_sector_trailer( - MfClassicEmulator* emulator, +bool mf_classic_is_allowed_access_sector_trailer( + MfClassicData* data, uint8_t block_num, MfClassicKey key, MfClassicAction action) { - uint8_t* sector_trailer = emulator->data.block[block_num].value; + uint8_t* sector_trailer = 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) { @@ -266,13 +274,13 @@ static bool mf_classic_is_allowed_access_sector_trailer( return true; } -static bool mf_classic_is_allowed_access_data_block( - MfClassicEmulator* emulator, +bool mf_classic_is_allowed_access_data_block( + MfClassicData* data, uint8_t block_num, MfClassicKey key, MfClassicAction action) { uint8_t* sector_trailer = - emulator->data.block[mf_classic_get_sector_trailer_num_by_block(block_num)].value; + data->block[mf_classic_get_sector_trailer_num_by_block(block_num)].value; uint8_t sector_block; if(block_num <= 128) { @@ -336,9 +344,10 @@ static bool mf_classic_is_allowed_access( 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); + return mf_classic_is_allowed_access_sector_trailer( + &emulator->data, block_num, key, action); } else { - return mf_classic_is_allowed_access_data_block(emulator, block_num, key, action); + return mf_classic_is_allowed_access_data_block(&emulator->data, block_num, key, action); } } @@ -514,25 +523,17 @@ bool mf_classic_read_block( furi_assert(block); bool read_block_success = false; - uint8_t plain_cmd[4] = {MF_CLASSIC_READ_SECT_CMD, block_num, 0x00, 0x00}; + uint8_t plain_cmd[4] = {MF_CLASSIC_READ_BLOCK_CMD, block_num, 0x00, 0x00}; nfca_append_crc16(plain_cmd, 2); - memset(tx_rx->tx_data, 0, sizeof(tx_rx->tx_data)); - memset(tx_rx->tx_parity, 0, sizeof(tx_rx->tx_parity)); - for(uint8_t i = 0; i < 4; i++) { - tx_rx->tx_data[i] = crypto1_byte(crypto, 0x00, 0) ^ plain_cmd[i]; - tx_rx->tx_parity[0] |= - ((crypto1_filter(crypto->odd) ^ nfc_util_odd_parity8(plain_cmd[i])) & 0x01) << (7 - i); - } + crypto1_encrypt(crypto, NULL, plain_cmd, 4 * 8, tx_rx->tx_data, tx_rx->tx_parity); tx_rx->tx_bits = 4 * 9; tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRaw; if(furi_hal_nfc_tx_rx(tx_rx, 50)) { if(tx_rx->rx_bits == 8 * (MF_CLASSIC_BLOCK_SIZE + 2)) { uint8_t block_received[MF_CLASSIC_BLOCK_SIZE + 2]; - for(uint8_t i = 0; i < MF_CLASSIC_BLOCK_SIZE + 2; i++) { - block_received[i] = crypto1_byte(crypto, 0, 0) ^ tx_rx->rx_data[i]; - } + crypto1_decrypt(crypto, tx_rx->rx_data, tx_rx->rx_bits, block_received); uint16_t crc_calc = nfca_get_crc16(block_received, MF_CLASSIC_BLOCK_SIZE); uint16_t crc_received = (block_received[MF_CLASSIC_BLOCK_SIZE + 1] << 8) | block_received[MF_CLASSIC_BLOCK_SIZE]; @@ -754,49 +755,6 @@ uint8_t mf_classic_update_card(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data 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); @@ -819,7 +777,7 @@ bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_ tx_rx->rx_bits); break; } - mf_crypto1_decrypt(&emulator->crypto, tx_rx->rx_data, tx_rx->rx_bits, plain_data); + crypto1_decrypt(&emulator->crypto, tx_rx->rx_data, tx_rx->rx_bits, plain_data); } if(plain_data[0] == 0x50 && plain_data[1] == 0x00) { @@ -857,7 +815,7 @@ bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_ tx_rx->tx_bits = sizeof(nt) * 8; tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; } else { - mf_crypto1_encrypt( + crypto1_encrypt( &emulator->crypto, nt_keystream, nt, @@ -904,7 +862,7 @@ bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_ uint32_t ans = prng_successor(nonce, 96); uint8_t responce[4] = {}; nfc_util_num2bytes(ans, 4, responce); - mf_crypto1_encrypt( + crypto1_encrypt( &emulator->crypto, NULL, responce, @@ -938,7 +896,7 @@ bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_ // Send NACK uint8_t nack = 0x04; if(is_encrypted) { - mf_crypto1_encrypt( + crypto1_encrypt( &emulator->crypto, NULL, &nack, 4, tx_rx->tx_data, tx_rx->tx_parity); } else { tx_rx->tx_data[0] = nack; @@ -951,7 +909,7 @@ bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_ } nfca_append_crc16(block_data, 16); - mf_crypto1_encrypt( + crypto1_encrypt( &emulator->crypto, NULL, block_data, @@ -967,14 +925,14 @@ bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_ } // Send ACK uint8_t ack = 0x0A; - mf_crypto1_encrypt(&emulator->crypto, NULL, &ack, 4, tx_rx->tx_data, tx_rx->tx_parity); + 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); + 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)) { @@ -1002,7 +960,7 @@ bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_ } // Send ACK ack = 0x0A; - mf_crypto1_encrypt(&emulator->crypto, NULL, &ack, 4, tx_rx->tx_data, tx_rx->tx_parity); + 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 { @@ -1015,8 +973,7 @@ bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_ // 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); + crypto1_encrypt(&emulator->crypto, NULL, &nack, 4, tx_rx->tx_data, tx_rx->tx_parity); } else { tx_rx->tx_data[0] = nack; } @@ -1027,3 +984,143 @@ bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_ return true; } + +bool mf_classic_write_block( + FuriHalNfcTxRxContext* tx_rx, + MfClassicBlock* src_block, + uint8_t block_num, + MfClassicKey key_type, + uint64_t key) { + furi_assert(tx_rx); + furi_assert(src_block); + + Crypto1 crypto = {}; + uint8_t plain_data[18] = {}; + uint8_t resp = 0; + bool write_success = false; + + do { + furi_hal_nfc_sleep(); + if(!mf_classic_auth(tx_rx, block_num, key, key_type, &crypto)) { + FURI_LOG_D(TAG, "Auth fail"); + break; + } + // Send write command + plain_data[0] = MF_CLASSIC_WRITE_BLOCK_CMD; + plain_data[1] = block_num; + nfca_append_crc16(plain_data, 2); + crypto1_encrypt(&crypto, NULL, plain_data, 4 * 8, tx_rx->tx_data, tx_rx->tx_parity); + tx_rx->tx_bits = 4 * 8; + tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRaw; + + if(furi_hal_nfc_tx_rx(tx_rx, 50)) { + if(tx_rx->rx_bits == 4) { + crypto1_decrypt(&crypto, tx_rx->rx_data, 4, &resp); + if(resp != 0x0A) { + FURI_LOG_D(TAG, "NACK received on write cmd: %02X", resp); + break; + } + } else { + FURI_LOG_D(TAG, "Not ACK received"); + break; + } + } else { + FURI_LOG_D(TAG, "Failed to send write cmd"); + break; + } + + // Send data + memcpy(plain_data, src_block->value, MF_CLASSIC_BLOCK_SIZE); + nfca_append_crc16(plain_data, MF_CLASSIC_BLOCK_SIZE); + crypto1_encrypt( + &crypto, + NULL, + plain_data, + (MF_CLASSIC_BLOCK_SIZE + 2) * 8, + tx_rx->tx_data, + tx_rx->tx_parity); + tx_rx->tx_bits = (MF_CLASSIC_BLOCK_SIZE + 2) * 8; + tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRaw; + if(furi_hal_nfc_tx_rx(tx_rx, 50)) { + if(tx_rx->rx_bits == 4) { + crypto1_decrypt(&crypto, tx_rx->rx_data, 4, &resp); + if(resp != 0x0A) { + FURI_LOG_D(TAG, "NACK received on sending data"); + break; + } + } else { + FURI_LOG_D(TAG, "Not ACK received"); + break; + } + } else { + FURI_LOG_D(TAG, "Failed to send data"); + break; + } + write_success = true; + + // Send Halt + plain_data[0] = 0x50; + plain_data[1] = 0x00; + nfca_append_crc16(plain_data, 2); + crypto1_encrypt(&crypto, NULL, plain_data, 2 * 8, tx_rx->tx_data, tx_rx->tx_parity); + tx_rx->tx_bits = 2 * 8; + tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRaw; + // No response is expected + furi_hal_nfc_tx_rx(tx_rx, 50); + } while(false); + + return write_success; +} + +bool mf_classic_write_sector( + FuriHalNfcTxRxContext* tx_rx, + MfClassicData* dest_data, + MfClassicData* src_data, + uint8_t sec_num) { + furi_assert(tx_rx); + furi_assert(dest_data); + furi_assert(src_data); + + uint8_t first_block = mf_classic_get_first_block_num_of_sector(sec_num); + uint8_t total_blocks = mf_classic_get_blocks_num_in_sector(sec_num); + MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(dest_data, sec_num); + bool key_a_found = mf_classic_is_key_found(dest_data, sec_num, MfClassicKeyA); + bool key_b_found = mf_classic_is_key_found(dest_data, sec_num, MfClassicKeyB); + + bool write_success = true; + for(size_t i = first_block; i < first_block + total_blocks; i++) { + // Compare blocks + if(memcmp(dest_data->block[i].value, src_data->block[i].value, MF_CLASSIC_BLOCK_SIZE)) { + bool key_a_write_allowed = mf_classic_is_allowed_access_data_block( + dest_data, i, MfClassicKeyA, MfClassicActionDataWrite); + bool key_b_write_allowed = mf_classic_is_allowed_access_data_block( + dest_data, i, MfClassicKeyB, MfClassicActionDataWrite); + + if(key_a_found && key_a_write_allowed) { + FURI_LOG_I(TAG, "Writing block %d with key A", i); + uint64_t key = nfc_util_bytes2num(sec_tr->key_a, 6); + if(!mf_classic_write_block(tx_rx, &src_data->block[i], i, MfClassicKeyA, key)) { + FURI_LOG_E(TAG, "Failed to write block %d", i); + write_success = false; + break; + } + } else if(key_b_found && key_b_write_allowed) { + FURI_LOG_I(TAG, "Writing block %d with key A", i); + uint64_t key = nfc_util_bytes2num(sec_tr->key_b, 6); + if(!mf_classic_write_block(tx_rx, &src_data->block[i], i, MfClassicKeyB, key)) { + FURI_LOG_E(TAG, "Failed to write block %d", i); + write_success = false; + break; + } + } else { + FURI_LOG_E(TAG, "Failed to find key with write access"); + write_success = false; + break; + } + } else { + FURI_LOG_D(TAG, "Blocks %d are equal", i); + } + } + + return write_success; +} diff --git a/lib/nfc/protocols/mifare_classic.h b/lib/nfc/protocols/mifare_classic.h index ead846e4267..d5467b100a5 100644 --- a/lib/nfc/protocols/mifare_classic.h +++ b/lib/nfc/protocols/mifare_classic.h @@ -27,6 +27,20 @@ typedef enum { MfClassicKeyB, } MfClassicKey; +typedef enum { + MfClassicActionDataRead, + MfClassicActionDataWrite, + MfClassicActionDataInc, + MfClassicActionDataDec, + + MfClassicActionKeyARead, + MfClassicActionKeyAWrite, + MfClassicActionKeyBRead, + MfClassicActionKeyBWrite, + MfClassicActionACRead, + MfClassicActionACWrite, +} MfClassicAction; + typedef struct { uint8_t value[MF_CLASSIC_BLOCK_SIZE]; } MfClassicBlock; @@ -90,6 +104,18 @@ bool mf_classic_is_sector_trailer(uint8_t block); uint8_t mf_classic_get_sector_by_block(uint8_t block); +bool mf_classic_is_allowed_access_sector_trailer( + MfClassicData* data, + uint8_t block_num, + MfClassicKey key, + MfClassicAction action); + +bool mf_classic_is_allowed_access_data_block( + MfClassicData* data, + uint8_t block_num, + MfClassicKey key, + MfClassicAction action); + bool mf_classic_is_key_found(MfClassicData* data, uint8_t sector_num, MfClassicKey key_type); void mf_classic_set_key_found( @@ -104,6 +130,10 @@ bool mf_classic_is_block_read(MfClassicData* data, uint8_t block_num); void mf_classic_set_block_read(MfClassicData* data, uint8_t block_num, MfClassicBlock* block_data); +bool mf_classic_is_sector_data_read(MfClassicData* data, uint8_t sector_num); + +void mf_classic_set_sector_data_not_read(MfClassicData* data); + bool mf_classic_is_sector_read(MfClassicData* data, uint8_t sector_num); bool mf_classic_is_card_read(MfClassicData* data); @@ -145,3 +175,16 @@ uint8_t mf_classic_read_card( uint8_t mf_classic_update_card(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data); bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_rx); + +bool mf_classic_write_block( + FuriHalNfcTxRxContext* tx_rx, + MfClassicBlock* src_block, + uint8_t block_num, + MfClassicKey key_type, + uint64_t key); + +bool mf_classic_write_sector( + FuriHalNfcTxRxContext* tx_rx, + MfClassicData* dest_data, + MfClassicData* src_data, + uint8_t sec_num); From d5f791b1fa8ee34428b92f6c0c2ddac9d6fcbe0a Mon Sep 17 00:00:00 2001 From: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> Date: Fri, 28 Oct 2022 19:43:54 +0300 Subject: [PATCH 188/824] [FL-2911] IR Universal Audio Remote (#1942) * Add Audio universal remote * Add signal library for Audio Universal Remote * Update UniversalRemotes.md * Added IR profile for Samsung K450 soundbar (#1892) * Add symbols to API file * Rearrange Audio remote buttons * Add new icons, remove old ones * Remove old signals, add new ones * Add universal audio remote to CLI, refactor code * Improve help text * Correct formatting * Update UniversalRemotes.md * Furi: restore correct api_symbols.csv version Co-authored-by: Alexei Humeniy Co-authored-by: Aleksandr Kutuzov --- applications/main/infrared/infrared_cli.c | 193 ++++++-------- .../infrared/scenes/infrared_scene_config.h | 1 + .../scenes/infrared_scene_universal.c | 8 +- .../scenes/infrared_scene_universal_audio.c | 133 ++++++++++ assets/icons/Infrared/Pause_25x27.png | Bin 0 -> 3634 bytes assets/icons/Infrared/Pause_hvr_25x27.png | Bin 0 -> 3623 bytes assets/icons/Infrared/Play_25x27.png | Bin 0 -> 3653 bytes assets/icons/Infrared/Play_hvr_25x27.png | Bin 0 -> 3643 bytes assets/icons/Infrared/TrackNext_25x27.png | Bin 0 -> 3651 bytes assets/icons/Infrared/TrackNext_hvr_25x27.png | Bin 0 -> 3639 bytes assets/icons/Infrared/TrackPrev_25x27.png | Bin 0 -> 3657 bytes assets/icons/Infrared/TrackPrev_hvr_25x27.png | Bin 0 -> 3644 bytes assets/resources/infrared/assets/audio.ir | 244 ++++++++++++++++++ documentation/UniversalRemotes.md | 16 +- 14 files changed, 477 insertions(+), 118 deletions(-) create mode 100644 applications/main/infrared/scenes/infrared_scene_universal_audio.c create mode 100644 assets/icons/Infrared/Pause_25x27.png create mode 100644 assets/icons/Infrared/Pause_hvr_25x27.png create mode 100644 assets/icons/Infrared/Play_25x27.png create mode 100644 assets/icons/Infrared/Play_hvr_25x27.png create mode 100644 assets/icons/Infrared/TrackNext_25x27.png create mode 100644 assets/icons/Infrared/TrackNext_hvr_25x27.png create mode 100644 assets/icons/Infrared/TrackPrev_25x27.png create mode 100644 assets/icons/Infrared/TrackPrev_hvr_25x27.png create mode 100644 assets/resources/infrared/assets/audio.ir diff --git a/applications/main/infrared/infrared_cli.c b/applications/main/infrared/infrared_cli.c index 5a04f7495b3..8f35a8fd150 100644 --- a/applications/main/infrared/infrared_cli.c +++ b/applications/main/infrared/infrared_cli.c @@ -5,25 +5,21 @@ #include #include #include +#include #include "infrared_signal.h" #include "infrared_brute_force.h" -#include - #define INFRARED_CLI_BUF_SIZE 10 +#define INFRARED_ASSETS_FOLDER "infrared/assets" +#define INFRARED_BRUTE_FORCE_DUMMY_INDEX 0 DICT_DEF2(dict_signals, FuriString*, FURI_STRING_OPLIST, int, M_DEFAULT_OPLIST) -enum RemoteTypes { TV = 0, AC = 1 }; - static void infrared_cli_start_ir_rx(Cli* cli, FuriString* args); static void infrared_cli_start_ir_tx(Cli* cli, FuriString* args); static void infrared_cli_process_decode(Cli* cli, FuriString* args); static void infrared_cli_process_universal(Cli* cli, FuriString* args); -static void infrared_cli_list_remote_signals(enum RemoteTypes remote_type); -static void - infrared_cli_brute_force_signals(Cli* cli, enum RemoteTypes remote_type, FuriString* signal); static const struct { const char* cmd; @@ -87,8 +83,10 @@ static void infrared_cli_print_usage(void) { INFRARED_MIN_FREQUENCY, INFRARED_MAX_FREQUENCY); printf("\tir decode []\r\n"); - printf("\tir universal \r\n"); - printf("\tir universal list \r\n"); + printf("\tir universal \r\n"); + printf("\tir universal list \r\n"); + // TODO: Do not hardcode universal remote names + printf("\tAvailable universal remotes: tv audio ac\r\n"); } static void infrared_cli_start_ir_rx(Cli* cli, FuriString* args) { @@ -356,89 +354,31 @@ static void infrared_cli_process_decode(Cli* cli, FuriString* args) { furi_record_close(RECORD_STORAGE); } -static void infrared_cli_process_universal(Cli* cli, FuriString* args) { - enum RemoteTypes Remote; +static void infrared_cli_list_remote_signals(FuriString* remote_name) { + if(furi_string_empty(remote_name)) { + printf("Missing remote name.\r\n"); + return; + } - FuriString* command; - FuriString* remote; - FuriString* signal; - command = furi_string_alloc(); - remote = furi_string_alloc(); - signal = furi_string_alloc(); + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* ff = flipper_format_buffered_file_alloc(storage); + FuriString* remote_path = furi_string_alloc_printf( + "%s/%s.ir", EXT_PATH(INFRARED_ASSETS_FOLDER), furi_string_get_cstr(remote_name)); do { - if(!args_read_string_and_trim(args, command)) { - infrared_cli_print_usage(); + if(!flipper_format_buffered_file_open_existing(ff, furi_string_get_cstr(remote_path))) { + printf("Invalid remote name.\r\n"); break; } - if(furi_string_cmp_str(command, "list") == 0) { - args_read_string_and_trim(args, remote); - if(furi_string_cmp_str(remote, "tv") == 0) { - Remote = TV; - } else if(furi_string_cmp_str(remote, "ac") == 0) { - Remote = AC; - } else { - printf("Invalid remote type.\r\n"); - break; - } - infrared_cli_list_remote_signals(Remote); - break; - } - - if(furi_string_cmp_str(command, "tv") == 0) { - Remote = TV; - } else if(furi_string_cmp_str(command, "ac") == 0) { - Remote = AC; - } else { - printf("Invalid remote type.\r\n"); - break; - } - - args_read_string_and_trim(args, signal); - if(furi_string_empty(signal)) { - printf("Must supply a valid signal for type of remote selected.\r\n"); - break; - } - - infrared_cli_brute_force_signals(cli, Remote, signal); - break; - - } while(false); - - furi_string_free(command); - furi_string_free(remote); - furi_string_free(signal); -} - -static void infrared_cli_list_remote_signals(enum RemoteTypes remote_type) { - Storage* storage = furi_record_open(RECORD_STORAGE); - FlipperFormat* ff = flipper_format_buffered_file_alloc(storage); - dict_signals_t signals_dict; - FuriString* key; - const char* remote_file = NULL; - bool success = false; - int max = 1; - - switch(remote_type) { - case TV: - remote_file = EXT_PATH("infrared/assets/tv.ir"); - break; - case AC: - remote_file = EXT_PATH("infrared/assets/ac.ir"); - break; - default: - break; - } + dict_signals_t signals_dict; + dict_signals_init(signals_dict); - dict_signals_init(signals_dict); - key = furi_string_alloc(); + FuriString* key = furi_string_alloc(); + FuriString* signal_name = furi_string_alloc(); - success = flipper_format_buffered_file_open_existing(ff, remote_file); - if(success) { - FuriString* signal_name; - signal_name = furi_string_alloc(); printf("Valid signals:\r\n"); + int max = 1; while(flipper_format_read_string(ff, "name", signal_name)) { furi_string_set_str(key, furi_string_get_cstr(signal_name)); int* v = dict_signals_get(signals_dict, key); @@ -449,57 +389,57 @@ static void infrared_cli_list_remote_signals(enum RemoteTypes remote_type) { dict_signals_set_at(signals_dict, key, 1); } } + dict_signals_it_t it; for(dict_signals_it(it, signals_dict); !dict_signals_end_p(it); dict_signals_next(it)) { const struct dict_signals_pair_s* pair = dict_signals_cref(it); printf("\t%s\r\n", furi_string_get_cstr(pair->key)); } + + furi_string_free(key); furi_string_free(signal_name); - } + dict_signals_clear(signals_dict); + + } while(false); - furi_string_free(key); - dict_signals_clear(signals_dict); flipper_format_free(ff); + furi_string_free(remote_path); furi_record_close(RECORD_STORAGE); } static void - infrared_cli_brute_force_signals(Cli* cli, enum RemoteTypes remote_type, FuriString* signal) { + infrared_cli_brute_force_signals(Cli* cli, FuriString* remote_name, FuriString* signal_name) { InfraredBruteForce* brute_force = infrared_brute_force_alloc(); - const char* remote_file = NULL; - uint32_t i = 0; - bool success = false; - - switch(remote_type) { - case TV: - remote_file = EXT_PATH("infrared/assets/tv.ir"); - break; - case AC: - remote_file = EXT_PATH("infrared/assets/ac.ir"); - break; - default: - break; - } + FuriString* remote_path = furi_string_alloc_printf( + "%s/%s.ir", EXT_PATH(INFRARED_ASSETS_FOLDER), furi_string_get_cstr(remote_name)); - infrared_brute_force_set_db_filename(brute_force, remote_file); - infrared_brute_force_add_record(brute_force, i++, furi_string_get_cstr(signal)); + infrared_brute_force_set_db_filename(brute_force, furi_string_get_cstr(remote_path)); + infrared_brute_force_add_record( + brute_force, INFRARED_BRUTE_FORCE_DUMMY_INDEX, furi_string_get_cstr(signal_name)); + + do { + if(furi_string_empty(signal_name)) { + printf("Missing signal name.\r\n"); + break; + } + if(!infrared_brute_force_calculate_messages(brute_force)) { + printf("Invalid remote name.\r\n"); + break; + } - success = infrared_brute_force_calculate_messages(brute_force); - if(success) { uint32_t record_count; - uint32_t index = 0; - int records_sent = 0; - bool running = false; + bool running = infrared_brute_force_start( + brute_force, INFRARED_BRUTE_FORCE_DUMMY_INDEX, &record_count); - running = infrared_brute_force_start(brute_force, index, &record_count); if(record_count <= 0) { - printf("Invalid signal.\n"); - infrared_brute_force_reset(brute_force); - return; + printf("Invalid signal name.\r\n"); + break; } - printf("Sending %ld codes to the tv.\r\n", record_count); + printf("Sending %ld signal(s)...\r\n", record_count); printf("Press Ctrl-C to stop.\r\n"); + + int records_sent = 0; while(running) { running = infrared_brute_force_send_next(brute_force); @@ -510,14 +450,35 @@ static void } infrared_brute_force_stop(brute_force); - } else { - printf("Invalid signal.\r\n"); - } + } while(false); + furi_string_free(remote_path); infrared_brute_force_reset(brute_force); infrared_brute_force_free(brute_force); } +static void infrared_cli_process_universal(Cli* cli, FuriString* args) { + FuriString* arg1 = furi_string_alloc(); + FuriString* arg2 = furi_string_alloc(); + + do { + if(!args_read_string_and_trim(args, arg1)) break; + if(!args_read_string_and_trim(args, arg2)) break; + } while(false); + + if(furi_string_empty(arg1)) { + printf("Wrong arguments.\r\n"); + infrared_cli_print_usage(); + } else if(furi_string_equal_str(arg1, "list")) { + infrared_cli_list_remote_signals(arg2); + } else { + infrared_cli_brute_force_signals(cli, arg1, arg2); + } + + furi_string_free(arg1); + furi_string_free(arg2); +} + static void infrared_cli_start_ir(Cli* cli, FuriString* args, void* context) { UNUSED(context); if(furi_hal_infrared_is_busy()) { diff --git a/applications/main/infrared/scenes/infrared_scene_config.h b/applications/main/infrared/scenes/infrared_scene_config.h index 22125fb795e..111fd2d31b2 100644 --- a/applications/main/infrared/scenes/infrared_scene_config.h +++ b/applications/main/infrared/scenes/infrared_scene_config.h @@ -16,6 +16,7 @@ ADD_SCENE(infrared, remote_list, RemoteList) ADD_SCENE(infrared, universal, Universal) ADD_SCENE(infrared, universal_tv, UniversalTV) ADD_SCENE(infrared, universal_ac, UniversalAC) +ADD_SCENE(infrared, universal_audio, UniversalAudio) ADD_SCENE(infrared, debug, Debug) ADD_SCENE(infrared, error_databases, ErrorDatabases) ADD_SCENE(infrared, rpc, Rpc) diff --git a/applications/main/infrared/scenes/infrared_scene_universal.c b/applications/main/infrared/scenes/infrared_scene_universal.c index 2bd7082c451..914360d78cf 100644 --- a/applications/main/infrared/scenes/infrared_scene_universal.c +++ b/applications/main/infrared/scenes/infrared_scene_universal.c @@ -21,6 +21,12 @@ void infrared_scene_universal_on_enter(void* context) { SubmenuIndexUniversalTV, infrared_scene_universal_submenu_callback, context); + submenu_add_item( + submenu, + "Audio Players", + SubmenuIndexUniversalAudio, + infrared_scene_universal_submenu_callback, + context); submenu_add_item( submenu, "Air Conditioners", @@ -45,7 +51,7 @@ bool infrared_scene_universal_on_event(void* context, SceneManagerEvent event) { scene_manager_next_scene(scene_manager, InfraredSceneUniversalAC); consumed = true; } else if(event.event == SubmenuIndexUniversalAudio) { - //TODO Implement Audio universal remote + scene_manager_next_scene(scene_manager, InfraredSceneUniversalAudio); consumed = true; } } diff --git a/applications/main/infrared/scenes/infrared_scene_universal_audio.c b/applications/main/infrared/scenes/infrared_scene_universal_audio.c new file mode 100644 index 00000000000..00c86fff4a6 --- /dev/null +++ b/applications/main/infrared/scenes/infrared_scene_universal_audio.c @@ -0,0 +1,133 @@ +#include "../infrared_i.h" + +#include "common/infrared_scene_universal_common.h" + +void infrared_scene_universal_audio_on_enter(void* context) { + infrared_scene_universal_common_on_enter(context); + + Infrared* infrared = context; + ButtonPanel* button_panel = infrared->button_panel; + InfraredBruteForce* brute_force = infrared->brute_force; + + infrared_brute_force_set_db_filename(brute_force, EXT_PATH("infrared/assets/audio.ir")); + + button_panel_reserve(button_panel, 2, 4); + uint32_t i = 0; + button_panel_add_item( + button_panel, + i, + 0, + 0, + 3, + 11, + &I_Power_25x27, + &I_Power_hvr_25x27, + infrared_scene_universal_common_item_callback, + context); + infrared_brute_force_add_record(brute_force, i++, "Power"); + button_panel_add_item( + button_panel, + i, + 1, + 0, + 36, + 11, + &I_Mute_25x27, + &I_Mute_hvr_25x27, + infrared_scene_universal_common_item_callback, + context); + infrared_brute_force_add_record(brute_force, i++, "Mute"); + button_panel_add_item( + button_panel, + i, + 0, + 1, + 3, + 41, + &I_Play_25x27, + &I_Play_hvr_25x27, + infrared_scene_universal_common_item_callback, + context); + infrared_brute_force_add_record(brute_force, i++, "Play"); + button_panel_add_item( + button_panel, + i, + 1, + 1, + 36, + 41, + &I_Pause_25x27, + &I_Pause_hvr_25x27, + infrared_scene_universal_common_item_callback, + context); + infrared_brute_force_add_record(brute_force, i++, "Pause"); + button_panel_add_item( + button_panel, + i, + 0, + 2, + 3, + 71, + &I_TrackPrev_25x27, + &I_TrackPrev_hvr_25x27, + infrared_scene_universal_common_item_callback, + context); + infrared_brute_force_add_record(brute_force, i++, "Prev"); + button_panel_add_item( + button_panel, + i, + 1, + 2, + 36, + 71, + &I_TrackNext_25x27, + &I_TrackNext_hvr_25x27, + infrared_scene_universal_common_item_callback, + context); + infrared_brute_force_add_record(brute_force, i++, "Next"); + button_panel_add_item( + button_panel, + i, + 0, + 3, + 3, + 101, + &I_Vol_down_25x27, + &I_Vol_down_hvr_25x27, + infrared_scene_universal_common_item_callback, + context); + infrared_brute_force_add_record(brute_force, i++, "Vol_dn"); + button_panel_add_item( + button_panel, + i, + 1, + 3, + 36, + 101, + &I_Vol_up_25x27, + &I_Vol_up_hvr_25x27, + infrared_scene_universal_common_item_callback, + context); + infrared_brute_force_add_record(brute_force, i++, "Vol_up"); + + button_panel_add_label(button_panel, 1, 8, FontPrimary, "Mus. remote"); + + view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical); + view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack); + + infrared_show_loading_popup(infrared, true); + bool success = infrared_brute_force_calculate_messages(brute_force); + infrared_show_loading_popup(infrared, false); + + if(!success) { + scene_manager_next_scene(infrared->scene_manager, InfraredSceneErrorDatabases); + } +} + +bool infrared_scene_universal_audio_on_event(void* context, SceneManagerEvent event) { + return infrared_scene_universal_common_on_event(context, event); +} + +void infrared_scene_universal_audio_on_exit(void* context) { + infrared_scene_universal_common_on_exit(context); +} diff --git a/assets/icons/Infrared/Pause_25x27.png b/assets/icons/Infrared/Pause_25x27.png new file mode 100644 index 0000000000000000000000000000000000000000..a371ba81718e864efe908e61f34bc842079cc20d GIT binary patch literal 3634 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 literal 0 HcmV?d00001 diff --git a/assets/icons/Infrared/Pause_hvr_25x27.png b/assets/icons/Infrared/Pause_hvr_25x27.png new file mode 100644 index 0000000000000000000000000000000000000000..472d583db0ff9adc5ef810675c8b915c6e5a899c GIT binary patch literal 3623 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 HpN{x9W3^Ap literal 0 HcmV?d00001 diff --git a/assets/icons/Infrared/Play_hvr_25x27.png b/assets/icons/Infrared/Play_hvr_25x27.png new file mode 100644 index 0000000000000000000000000000000000000000..6708dcdbf2c7ac0c656bbb1c2441fb0f4d6fc9c5 GIT binary patch literal 3643 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 SxG~3f0odCdwl1|i74;vAA8iN# literal 0 HcmV?d00001 diff --git a/assets/icons/Infrared/TrackNext_hvr_25x27.png b/assets/icons/Infrared/TrackNext_hvr_25x27.png new file mode 100644 index 0000000000000000000000000000000000000000..a4de4fc3cbe582349b8c79bbb7ec6e931e8bc792 GIT binary patch literal 3639 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 W@<`O{ko&lf02|BW7A0mL7ybo9V{1nM literal 0 HcmV?d00001 diff --git a/assets/icons/Infrared/TrackPrev_hvr_25x27.png b/assets/icons/Infrared/TrackPrev_hvr_25x27.png new file mode 100644 index 0000000000000000000000000000000000000000..838055341e568fed60b3e28a280fe839d2c9db9e GIT binary patch literal 3644 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 in order to keep the library organised. + +When done, open a pull request containing the changed file. + ## Air Conditioners ### Recording signals Air conditioners differ from most other infrared-controlled devices because their state is tracked by the remote. @@ -31,7 +45,7 @@ Finally, record the `Off` signal: 4. Save the resulting signal under the name `Off`. The resulting remote file should now contain 6 signals. Any of them can be omitted, but that will mean that this functionality will not be used. -Test the file against the actual device. Every signal must do what it's supposed to. +Test the file against the actual device. Make sure that every signal does what it's supposed to. If everything checks out, append these signals **to the end** of the [A/C universal remote file](/assets/resources/infrared/assets/ac.ir). From 85d341104f32b509c70ed8808655697d25963df3 Mon Sep 17 00:00:00 2001 From: Vladimir <74153654+touchscan@users.noreply.github.com> Date: Fri, 28 Oct 2022 19:50:07 +0300 Subject: [PATCH 189/824] Update ac.ir (#1945) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added conditioner Saturn CS-TL09CHR ir signals (Dh, Cool_hi, Cool_lo, Heat_hi, Heat_lo, Off) Co-authored-by: あく --- assets/resources/infrared/assets/ac.ir | 37 ++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/assets/resources/infrared/assets/ac.ir b/assets/resources/infrared/assets/ac.ir index 97c38459167..7ef953059a3 100644 --- a/assets/resources/infrared/assets/ac.ir +++ b/assets/resources/infrared/assets/ac.ir @@ -111,4 +111,41 @@ type: raw frequency: 38000 duty_cycle: 0.330000 data: 9106 4398 731 499 706 500 705 502 702 504 701 505 701 505 701 1606 701 505 701 1607 701 505 701 506 700 1607 700 506 700 506 700 505 700 505 701 506 700 506 700 506 699 506 700 506 700 1607 700 506 700 506 700 506 700 505 701 506 700 506 700 1608 699 506 700 1608 699 506 700 506 700 1608 700 506 700 19941 701 1606 700 505 701 505 701 506 700 505 700 506 700 506 700 506 700 506 700 506 700 506 700 506 700 506 701 506 700 506 700 506 700 506 700 506 700 506 700 506 700 506 700 506 700 506 700 506 700 506 700 506 699 506 700 506 700 1608 700 1607 700 506 700 506 700 +# +# Model: Saturn CS-TL09CHR +name: Dh +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3014 1708 488 1059 464 1085 461 362 461 363 460 387 436 1085 462 362 461 402 436 1084 463 1083 463 364 459 1083 463 363 460 386 437 1082 518 1044 464 386 437 1108 439 1108 438 386 464 360 463 1082 464 360 490 352 459 1084 462 362 460 363 460 363 460 364 459 364 459 364 459 380 459 364 459 364 459 364 460 364 459 364 459 364 459 364 459 380 459 364 459 365 458 1088 459 364 459 365 458 1088 458 365 458 380 459 365 458 1088 459 365 458 364 459 365 459 365 458 365 458 380 458 1088 459 1088 459 1088 458 365 458 365 458 365 458 365 458 381 458 365 458 365 458 365 458 365 458 365 459 365 458 365 458 381 458 366 457 366 457 366 457 366 457 366 458 365 458 366 458 381 457 366 457 366 457 366 457 366 457 366 457 366 457 366 458 382 457 366 457 366 457 367 456 367 457 367 456 367 456 390 433 406 433 367 456 367 456 390 433 391 433 390 433 390 433 390 433 406 433 391 432 1114 432 391 432 391 432 391 432 391 432 1114 433 396 433 +# +name: Cool_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3011 1710 462 1084 462 1085 486 356 467 356 444 364 484 1060 462 364 483 357 458 1085 461 1085 461 388 435 1085 461 388 435 388 435 1084 462 1099 463 387 436 1084 462 1085 461 388 461 362 461 1084 462 362 461 378 460 1087 459 365 458 365 458 366 457 366 457 366 457 366 457 382 457 366 457 366 457 366 457 367 456 367 456 367 456 367 456 382 457 367 456 367 456 1090 457 367 456 367 456 1090 457 367 456 382 457 1090 456 1090 457 367 456 367 456 367 456 367 456 367 456 382 457 1091 456 1091 456 1091 456 1090 456 367 456 367 456 367 457 382 457 367 456 367 456 367 456 367 456 368 456 367 456 368 455 383 456 367 456 367 456 368 455 368 455 368 455 368 456 368 455 383 456 368 455 368 455 368 455 368 455 368 455 368 455 368 455 383 456 368 455 368 455 368 455 368 455 368 455 368 455 368 455 384 455 368 455 368 455 368 455 368 455 368 455 368 455 368 455 383 456 1091 456 1091 456 368 456 1091 455 368 455 368 455 1091 456 373 456 +# +name: Cool_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3012 1709 462 1083 463 1084 463 361 462 362 484 355 469 1059 463 387 436 402 437 1083 464 1083 464 362 462 1080 520 354 469 354 469 1026 521 1068 518 354 390 1108 439 1108 438 386 463 360 464 1081 465 359 464 375 463 1083 463 361 462 362 461 363 460 363 460 364 459 364 459 380 459 364 459 364 459 365 458 365 458 365 458 365 458 365 458 380 459 365 458 365 458 1089 458 365 459 365 458 1089 458 366 457 382 456 1090 457 1113 433 390 433 390 433 390 433 390 433 390 433 405 434 390 433 390 433 390 433 1113 434 390 433 390 433 390 433 405 434 390 433 390 433 390 434 390 433 390 433 390 433 390 433 405 434 390 433 390 433 390 433 390 433 390 433 390 433 390 434 405 433 390 433 390 433 390 433 390 433 390 433 390 433 390 433 406 433 390 433 390 433 390 433 390 433 390 433 391 432 391 432 406 433 390 433 391 432 391 432 391 433 390 433 390 433 391 432 406 433 391 432 391 432 1114 433 391 432 391 432 391 432 1114 433 396 433 +# +name: Heat_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3011 1712 461 1087 459 1088 459 364 459 389 434 366 458 1086 461 388 435 404 435 1086 460 1085 461 388 435 1085 461 365 458 388 435 1084 462 1098 464 387 436 1084 462 1084 462 388 461 362 461 1084 462 362 461 378 460 1086 460 364 459 365 458 365 458 365 458 366 457 365 458 381 458 366 457 366 458 366 457 366 457 366 457 366 457 365 458 381 458 366 458 366 457 1089 458 366 457 366 457 1089 458 366 457 381 458 1089 458 366 457 366 457 366 457 366 458 366 457 366 457 381 458 366 457 366 457 366 457 366 457 366 457 366 457 366 457 381 458 366 457 366 457 366 458 366 457 366 457 366 457 366 457 382 457 366 457 366 457 366 457 366 457 366 457 366 457 366 457 382 457 366 457 366 457 367 456 367 456 367 457 366 457 366 457 382 457 367 457 366 457 367 457 366 457 367 457 366 457 366 457 382 457 367 456 367 456 367 456 367 456 367 456 367 456 367 456 382 457 367 456 1090 457 367 456 1091 456 1090 457 1090 456 367 456 373 456 +# +name: Heat_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3045 1677 493 1023 524 1024 522 354 469 354 469 354 469 1026 576 353 470 350 487 1001 492 1054 492 354 469 1054 492 355 468 354 469 1054 492 1070 491 354 468 1056 438 1109 438 386 437 385 438 1107 439 385 438 400 464 1081 465 359 464 360 463 361 462 362 461 388 435 388 435 404 434 389 434 389 434 389 434 389 434 389 434 389 434 389 434 405 434 389 434 389 434 1112 434 389 434 389 434 1113 434 389 434 405 434 1112 435 389 434 366 458 365 458 365 458 365 458 365 458 381 458 365 458 366 457 365 458 1089 457 366 457 366 457 366 457 382 456 367 433 390 433 391 432 391 432 391 432 391 432 391 432 406 433 391 432 391 432 390 433 391 432 391 432 391 432 391 432 406 433 391 432 391 433 391 432 391 432 391 432 391 432 391 432 407 432 391 432 391 432 391 432 392 431 391 433 391 432 391 432 430 409 393 430 392 431 392 431 392 432 391 457 367 432 392 431 408 431 392 431 1138 433 391 408 392 431 392 456 367 456 1113 408 421 433 +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3013 1709 463 1085 462 1084 463 387 461 355 469 355 444 1084 463 387 461 378 436 1084 462 1084 487 355 469 1059 463 387 460 355 444 1083 463 1098 464 386 437 1083 463 1083 489 361 463 360 463 1082 464 361 462 376 462 1085 461 363 460 364 459 364 459 365 458 365 459 364 459 380 459 365 458 365 458 365 458 365 458 365 458 365 458 365 459 380 458 365 459 365 458 365 459 365 458 365 458 1089 458 365 458 380 459 1088 459 365 458 365 458 365 458 365 459 365 458 365 458 381 458 365 458 365 458 365 458 1089 458 365 458 365 458 365 458 381 458 365 458 365 458 365 458 365 458 365 458 365 458 365 458 381 457 366 457 366 457 366 457 366 457 366 458 365 458 366 457 381 458 366 458 366 457 366 457 366 457 366 457 366 457 366 457 381 458 366 457 366 457 366 457 366 457 366 457 366 457 366 457 382 457 366 457 366 457 366 457 366 457 366 457 367 457 366 457 382 457 367 456 1090 457 1090 456 1090 457 1090 457 1090 456 367 457 372 457 From 104a1998a5e2f77cea99c31311541864fb98d4c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Tue, 1 Nov 2022 19:27:25 +0900 Subject: [PATCH 190/824] Furi: raise bkpt only if debug session initiated, add debug support for release builds (#1957) * Fix hard crash on some custom firmwares in RELEASE mode * Furi: anya wa erai, anya wa eleganto, raise bkpt only if debug session initiated, add debug support for release builds Co-authored-by: DerSkythe --- furi/core/check.c | 59 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 41 insertions(+), 18 deletions(-) diff --git a/furi/core/check.c b/furi/core/check.c index 00c20575af0..1c2a005f386 100644 --- a/furi/core/check.c +++ b/furi/core/check.c @@ -1,6 +1,7 @@ #include "check.h" #include "common_defines.h" +#include #include #include #include @@ -24,16 +25,30 @@ PLACE_IN_SECTION("MB_MEM2") uint32_t __furi_check_registers[12] = {0}; : \ : "memory"); -// Restore registers and halt MCU -#define RESTORE_REGISTERS_AND_HALT_MCU() \ - asm volatile("ldr r12, =__furi_check_registers \n" \ - "ldm r12, {r0-r11} \n" \ +/** Restore registers and halt MCU + * + * - Always use it with GET_MESSAGE_AND_STORE_REGISTERS + * - If debugger is(was) connected this routine will raise bkpt + * - If debugger is not connected then endless loop + * + */ +#define RESTORE_REGISTERS_AND_HALT_MCU(debug) \ + register const bool r0 asm("r0") = debug; \ + asm volatile("cbnz r0, with_debugger%= \n" \ + "ldr r12, =__furi_check_registers\n" \ + "ldm r12, {r0-r11} \n" \ "loop%=: \n" \ - "bkpt 0x00 \n" \ "wfi \n" \ - "b loop%= \n" \ - : \ + "b loop%= \n" \ + "with_debugger%=: \n" \ + "ldr r12, =__furi_check_registers\n" \ + "ldm r12, {r0-r11} \n" \ + "debug_loop%=: \n" \ + "bkpt 0x00 \n" \ + "wfi \n" \ + "b debug_loop%= \n" \ : \ + : "r"(r0) \ : "memory"); extern size_t xPortGetTotalHeapSize(void); @@ -96,16 +111,19 @@ FURI_NORETURN void __furi_crash() { } __furi_print_heap_info(); -#ifdef FURI_DEBUG - furi_hal_console_puts("\r\nSystem halted. Connect debugger for more info\r\n"); - furi_hal_console_puts("\033[0m\r\n"); - RESTORE_REGISTERS_AND_HALT_MCU(); -#else - furi_hal_rtc_set_fault_data((uint32_t)__furi_check_message); - furi_hal_console_puts("\r\nRebooting system.\r\n"); - furi_hal_console_puts("\033[0m\r\n"); - furi_hal_power_reset(); -#endif + // Check if debug enabled by DAP + // https://developer.arm.com/documentation/ddi0403/d/Debug-Architecture/ARMv7-M-Debug/Debug-register-support-in-the-SCS/Debug-Halting-Control-and-Status-Register--DHCSR?lang=en + bool debug = CoreDebug->DHCSR & CoreDebug_DHCSR_C_DEBUGEN_Msk; + if(debug) { + furi_hal_console_puts("\r\nSystem halted. Connect debugger for more info\r\n"); + furi_hal_console_puts("\033[0m\r\n"); + RESTORE_REGISTERS_AND_HALT_MCU(debug); + } else { + furi_hal_rtc_set_fault_data((uint32_t)__furi_check_message); + furi_hal_console_puts("\r\nRebooting system.\r\n"); + furi_hal_console_puts("\033[0m\r\n"); + furi_hal_power_reset(); + } __builtin_unreachable(); } @@ -124,6 +142,11 @@ FURI_NORETURN void __furi_halt() { furi_hal_console_puts(__furi_check_message); furi_hal_console_puts("\r\nSystem halted. Bye-bye!\r\n"); furi_hal_console_puts("\033[0m\r\n"); - RESTORE_REGISTERS_AND_HALT_MCU(); + + // Check if debug enabled by DAP + // https://developer.arm.com/documentation/ddi0403/d/Debug-Architecture/ARMv7-M-Debug/Debug-register-support-in-the-SCS/Debug-Halting-Control-and-Status-Register--DHCSR?lang=en + bool debug = CoreDebug->DHCSR & CoreDebug_DHCSR_C_DEBUGEN_Msk; + RESTORE_REGISTERS_AND_HALT_MCU(debug); + __builtin_unreachable(); } From abfa804ae0bf8c126b5dd78a92cc59c9cec14103 Mon Sep 17 00:00:00 2001 From: "Vasyl \"vk\" Kaigorodov" Date: Wed, 2 Nov 2022 15:36:17 +0100 Subject: [PATCH 191/824] Gui: refactor buttons remapping (#1949) * Gui: refactor buttons remapping Instead of calling 3 separate functions with a ton of switch/case statements, use a 2-dimensional lookup table to remap buttons based on the orientation. * Gui: cleanup input mapping and fix incorrect asserts * SnakeGame: handle input special case Co-authored-by: Aleksandr Kutuzov --- applications/plugins/snake_game/snake_game.c | 2 + applications/services/gui/view_port.c | 114 +++++++----------- applications/services/gui/view_port.h | 1 + applications/services/input/input.c | 3 +- applications/services/input/input.h | 1 + .../targets/f7/furi_hal/furi_hal_resources.h | 1 + 6 files changed, 53 insertions(+), 69 deletions(-) diff --git a/applications/plugins/snake_game/snake_game.c b/applications/plugins/snake_game/snake_game.c index 283d017ed6f..f9309c28070 100644 --- a/applications/plugins/snake_game/snake_game.c +++ b/applications/plugins/snake_game/snake_game.c @@ -380,6 +380,8 @@ int32_t snake_game_app(void* p) { case InputKeyBack: processing = false; break; + default: + break; } } } else if(event.type == EventTypeTick) { diff --git a/applications/services/gui/view_port.c b/applications/services/gui/view_port.c index baa8f7bd2d4..ffd01450b27 100644 --- a/applications/services/gui/view_port.c +++ b/applications/services/gui/view_port.c @@ -7,61 +7,51 @@ // TODO add mutex to view_port ops -static void view_port_remap_buttons_vertical(InputEvent* event) { - switch(event->key) { - case InputKeyUp: - event->key = InputKeyRight; - break; - case InputKeyDown: - event->key = InputKeyLeft; - break; - case InputKeyRight: - event->key = InputKeyDown; - break; - case InputKeyLeft: - event->key = InputKeyUp; - break; - default: - break; - } -} - -static void view_port_remap_buttons_vertical_flip(InputEvent* event) { - switch(event->key) { - case InputKeyUp: - event->key = InputKeyLeft; - break; - case InputKeyDown: - event->key = InputKeyRight; - break; - case InputKeyRight: - event->key = InputKeyUp; - break; - case InputKeyLeft: - event->key = InputKeyDown; - break; - default: - break; - } -} - -static void view_port_remap_buttons_horizontal_flip(InputEvent* event) { - switch(event->key) { - case InputKeyUp: - event->key = InputKeyDown; - break; - case InputKeyDown: - event->key = InputKeyUp; - break; - case InputKeyRight: - event->key = InputKeyLeft; - break; - case InputKeyLeft: - event->key = InputKeyRight; - break; - default: - break; - } +_Static_assert(ViewPortOrientationMAX == 4, "Incorrect ViewPortOrientation count"); +_Static_assert( + (ViewPortOrientationHorizontal == 0 && ViewPortOrientationHorizontalFlip == 1 && + ViewPortOrientationVertical == 2 && ViewPortOrientationVerticalFlip == 3), + "Incorrect ViewPortOrientation order"); +_Static_assert(InputKeyMAX == 6, "Incorrect InputKey count"); +_Static_assert( + (InputKeyUp == 0 && InputKeyDown == 1 && InputKeyRight == 2 && InputKeyLeft == 3 && + InputKeyOk == 4 && InputKeyBack == 5), + "Incorrect InputKey order"); + +/** InputKey directional keys mappings for different screen orientations +* +*/ +static const InputKey view_port_input_mapping[ViewPortOrientationMAX][InputKeyMAX] = { + {InputKeyUp, + InputKeyDown, + InputKeyRight, + InputKeyLeft, + InputKeyOk, + InputKeyBack}, //ViewPortOrientationHorizontal + {InputKeyDown, + InputKeyUp, + InputKeyLeft, + InputKeyRight, + InputKeyOk, + InputKeyBack}, //ViewPortOrientationHorizontalFlip + {InputKeyRight, + InputKeyLeft, + InputKeyDown, + InputKeyUp, + InputKeyOk, + InputKeyBack}, //ViewPortOrientationVertical + {InputKeyLeft, + InputKeyRight, + InputKeyUp, + InputKeyDown, + InputKeyOk, + InputKeyBack}, //ViewPortOrientationVerticalFlip +}; + +// Remaps directional pad buttons on Flipper based on ViewPort orientation +static void view_port_map_input(InputEvent* event, ViewPortOrientation orientation) { + furi_assert(orientation < ViewPortOrientationMAX && event->key < InputKeyMAX); + event->key = view_port_input_mapping[orientation][event->key]; } static void view_port_setup_canvas_orientation(const ViewPort* view_port, Canvas* canvas) { @@ -170,19 +160,7 @@ void view_port_input(ViewPort* view_port, InputEvent* event) { if(view_port->input_callback) { ViewPortOrientation orientation = view_port_get_orientation(view_port); - switch(orientation) { - case ViewPortOrientationHorizontalFlip: - view_port_remap_buttons_horizontal_flip(event); - break; - case ViewPortOrientationVertical: - view_port_remap_buttons_vertical(event); - break; - case ViewPortOrientationVerticalFlip: - view_port_remap_buttons_vertical_flip(event); - break; - default: - break; - } + view_port_map_input(event, orientation); view_port->input_callback(event, view_port->input_callback_context); } } diff --git a/applications/services/gui/view_port.h b/applications/services/gui/view_port.h index 169681ac005..703e99248e8 100644 --- a/applications/services/gui/view_port.h +++ b/applications/services/gui/view_port.h @@ -19,6 +19,7 @@ typedef enum { ViewPortOrientationHorizontalFlip, ViewPortOrientationVertical, ViewPortOrientationVerticalFlip, + ViewPortOrientationMAX, /**< Special value, don't use it */ } ViewPortOrientation; /** ViewPort Draw callback diff --git a/applications/services/input/input.c b/applications/services/input/input.c index 7b8433aeffe..d1aef9e8ac0 100644 --- a/applications/services/input/input.c +++ b/applications/services/input/input.c @@ -60,8 +60,9 @@ const char* input_get_type_name(InputType type) { return "Long"; case InputTypeRepeat: return "Repeat"; + default: + return "Unknown"; } - return "Unknown"; } int32_t input_srv(void* p) { diff --git a/applications/services/input/input.h b/applications/services/input/input.h index bd0ba390201..172b16361fd 100644 --- a/applications/services/input/input.h +++ b/applications/services/input/input.h @@ -22,6 +22,7 @@ typedef enum { InputTypeShort, /**< Short event, emitted after InputTypeRelease done withing INPUT_LONG_PRESS interval */ InputTypeLong, /**< Long event, emmited after INPUT_LONG_PRESS interval, asynchronouse to InputTypeRelease */ InputTypeRepeat, /**< Repeat event, emmited with INPUT_REPEATE_PRESS period after InputTypeLong event */ + InputTypeMAX, /**< Special value for exceptional */ } InputType; /** Input Event, dispatches with FuriPubSub */ diff --git a/firmware/targets/f7/furi_hal/furi_hal_resources.h b/firmware/targets/f7/furi_hal/furi_hal_resources.h index d16c567e628..8d53ec48ae0 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_resources.h +++ b/firmware/targets/f7/furi_hal/furi_hal_resources.h @@ -20,6 +20,7 @@ typedef enum { InputKeyLeft, InputKeyOk, InputKeyBack, + InputKeyMAX, /**< Special value */ } InputKey; /* Light */ From ebc2b66372091d811278bcb6180bfa16b62cd46f Mon Sep 17 00:00:00 2001 From: hedger Date: Wed, 2 Nov 2022 19:15:40 +0400 Subject: [PATCH 192/824] fbt fixes for mfbt pt2 (#1951) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fbt: split sdk management code * scripts: fixed import handling * fbt: sdk: reformatted paths * scrips: dist: bundling libs as a build artifact * fbt: sdk: better path management * typo fix * fbt: sdk: minor path handling fixes * toolchain: fixed windows toolchain download * fbt: minor refactorin * fbt: moved sdk management code to extapps.scons * fbt: fixed sdk symbols header path; disabled -fstack-usage * fbt: changed pathing for .py scripts * fbt: changed SDK_HEADERS pathing; added libusb to SDK; added icon_i.h to SDK; added hw target to SDK meta * fbt: added libusb headers to SDK * picopass: include cleanup; api: added subghz/registry.h; api: added mbedtls to exported headers * picopass: fixed formatting * fbt: fixed COPRO_ASSETS_SCRIPT * sdk: added basic infrared apis * toolchain: added ufbt to list of legal fbtenv callers; updated error messages * fbt: changed manifest collection & icon processing code * fbt: simpler srcdir lookup * toolchain: path management fixes; fbt: fixes for fap private libs paths * scripts: toolchain: reworked download on Windows * toolchain: v17 * scripts: added colorlog for logging * Github: fix unit tests Co-authored-by: あく --- .github/workflows/unit_tests.yml | 8 +- SConstruct | 10 +- applications/plugins/picopass/125_10px.png | Bin 0 -> 308 bytes applications/plugins/picopass/application.fam | 2 +- .../plugins/picopass/picopass_worker_i.h | 2 - applications/plugins/picopass/rfal_picopass.c | 7 +- applications/services/gui/application.fam | 1 + firmware.scons | 66 ++++------ firmware/SConscript | 7 +- firmware/targets/f7/api_symbols.csv | 124 +++++++++++++++++- lib/SConscript | 12 +- lib/STM32CubeWB.scons | 2 +- lib/flipper_application/SConscript | 2 +- lib/flipper_format/SConscript | 4 +- lib/infrared/SConscript | 5 + lib/lfrfid/SConscript | 12 +- lib/libusb_stm32.scons | 4 + lib/mbedtls.scons | 4 + lib/misc.scons | 2 +- lib/print/SConscript | 2 +- lib/subghz/SConscript | 25 ++-- lib/subghz/registry.h | 8 ++ lib/toolbox/SConscript | 34 ++--- scripts/fbt/util.py | 15 ++- scripts/fbt_tools/crosscc.py | 15 +++ scripts/fbt_tools/fbt_apps.py | 38 +++--- scripts/fbt_tools/fbt_assets.py | 20 ++- scripts/fbt_tools/fbt_dist.py | 9 +- scripts/fbt_tools/fbt_extapps.py | 24 +++- scripts/fbt_tools/fbt_sdk.py | 46 ++++--- scripts/fbt_tools/fbt_version.py | 5 +- scripts/fbt_tools/fwbin.py | 3 +- scripts/flipper/app.py | 16 ++- scripts/sconsdist.py | 6 +- scripts/testing/await_flipper.py | 0 scripts/testing/units.py | 0 scripts/toolchain/fbtenv.cmd | 13 +- scripts/toolchain/fbtenv.sh | 24 ++-- .../toolchain/windows-toolchain-download.ps1 | 36 +++-- site_scons/cc.scons | 5 +- site_scons/environ.scons | 36 ++--- site_scons/extapps.scons | 38 +++++- 42 files changed, 458 insertions(+), 234 deletions(-) create mode 100644 applications/plugins/picopass/125_10px.png mode change 100644 => 100755 scripts/testing/await_flipper.py mode change 100644 => 100755 scripts/testing/units.py diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index b5bf1000497..a7671f0f9b2 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -31,22 +31,26 @@ jobs: id: connect if: steps.compile.outcome == 'success' run: | - python3 ./scripts/testing/await_flipper.py ${{steps.device.outputs.flipper}} + . scripts/toolchain/fbtenv.sh + ./scripts/testing/await_flipper.py ${{steps.device.outputs.flipper}} - name: 'Format flipper SD card' id: format if: steps.connect.outcome == 'success' run: | + . scripts/toolchain/fbtenv.sh ./scripts/storage.py -p ${{steps.device.outputs.flipper}} format_ext - name: 'Copy assets and unit tests data to flipper' id: copy if: steps.format.outcome == 'success' run: | + . scripts/toolchain/fbtenv.sh ./scripts/storage.py -p ${{steps.device.outputs.flipper}} send assets/resources /ext ./scripts/storage.py -p ${{steps.device.outputs.flipper}} send assets/unit_tests /ext/unit_tests - name: 'Run units and validate results' if: steps.copy.outcome == 'success' run: | - python3 ./scripts/testing/units.py ${{steps.device.outputs.flipper}} + . scripts/toolchain/fbtenv.sh + ./scripts/testing/units.py ${{steps.device.outputs.flipper}} diff --git a/SConstruct b/SConstruct index 448df97153a..13a698c81fb 100644 --- a/SConstruct +++ b/SConstruct @@ -33,10 +33,6 @@ coreenv = SConscript( ) SConscript("site_scons/cc.scons", exports={"ENV": coreenv}) -# Store root dir in environment for certain tools -coreenv["ROOT_DIR"] = Dir(".") - - # Create a separate "dist" environment and add construction envs to it distenv = coreenv.Clone( tools=[ @@ -233,13 +229,13 @@ distenv.PhonyTarget( # Linter distenv.PhonyTarget( "lint", - "${PYTHON3} scripts/lint.py check ${LINT_SOURCES}", + "${PYTHON3} ${FBT_SCRIPT_DIR}/lint.py check ${LINT_SOURCES}", LINT_SOURCES=firmware_env["LINT_SOURCES"], ) distenv.PhonyTarget( "format", - "${PYTHON3} scripts/lint.py format ${LINT_SOURCES}", + "${PYTHON3} ${FBT_SCRIPT_DIR}/lint.py format ${LINT_SOURCES}", LINT_SOURCES=firmware_env["LINT_SOURCES"], ) @@ -280,7 +276,7 @@ distenv.PhonyTarget( ) # Start Flipper CLI via PySerial's miniterm -distenv.PhonyTarget("cli", "${PYTHON3} scripts/serial_cli.py") +distenv.PhonyTarget("cli", "${PYTHON3} ${FBT_SCRIPT_DIR}/serial_cli.py") # Find blackmagic probe diff --git a/applications/plugins/picopass/125_10px.png b/applications/plugins/picopass/125_10px.png new file mode 100644 index 0000000000000000000000000000000000000000..ce01284a2c1f3eb413f581b84f1fb3f3a2a7223b GIT binary patch literal 308 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2xkYHHq`AGmsv7|ftIx;Y9?C1WI$O_~uBzpw; zGB8xBF)%c=FfjZA3N^f7U???UV0e|lz+g3lfkC`r&aOZkpafHrx4R1i<>&pI=m5)bWZjP>yH&963)5S4_<9hOs!iI #include -#include -#include #include #include diff --git a/applications/plugins/picopass/rfal_picopass.c b/applications/plugins/picopass/rfal_picopass.c index 50cd4e95de7..ac66cb92d97 100644 --- a/applications/plugins/picopass/rfal_picopass.c +++ b/applications/plugins/picopass/rfal_picopass.c @@ -1,5 +1,4 @@ #include "rfal_picopass.h" -#include "utils.h" #define RFAL_PICOPASS_TXRX_FLAGS \ (FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_TX_MANUAL | FURI_HAL_NFC_LL_TXRX_FLAGS_AGC_ON | \ @@ -97,7 +96,7 @@ FuriHalNfcReturn rfalPicoPassPollerSelect(uint8_t* csn, rfalPicoPassSelectRes* s rfalPicoPassSelectReq selReq; selReq.CMD = RFAL_PICOPASS_CMD_SELECT; - ST_MEMCPY(selReq.CSN, csn, RFAL_PICOPASS_UID_LEN); + memcpy(selReq.CSN, csn, RFAL_PICOPASS_UID_LEN); uint16_t recvLen = 0; uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS; uint32_t fwt = furi_hal_nfc_ll_ms2fc(20); @@ -146,8 +145,8 @@ FuriHalNfcReturn rfalPicoPassPollerCheck(uint8_t* mac, rfalPicoPassCheckRes* chk FuriHalNfcReturn ret; rfalPicoPassCheckReq chkReq; chkReq.CMD = RFAL_PICOPASS_CMD_CHECK; - ST_MEMCPY(chkReq.mac, mac, 4); - ST_MEMSET(chkReq.null, 0, 4); + memcpy(chkReq.mac, mac, 4); + memset(chkReq.null, 0, 4); uint16_t recvLen = 0; uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS; uint32_t fwt = furi_hal_nfc_ll_ms2fc(20); diff --git a/applications/services/gui/application.fam b/applications/services/gui/application.fam index 7fad7b4ea85..869d964dd01 100644 --- a/applications/services/gui/application.fam +++ b/applications/services/gui/application.fam @@ -12,6 +12,7 @@ App( order=70, sdk_headers=[ "gui.h", + "icon_i.h", "elements.h", "view_dispatcher.h", "view_stack.h", diff --git a/firmware.scons b/firmware.scons index 63a1aa3f716..a0c1ab33943 100644 --- a/firmware.scons +++ b/firmware.scons @@ -1,6 +1,7 @@ Import("ENV", "fw_build_meta") from SCons.Errors import UserError +from SCons.Node import FS import itertools from fbt_extra.util import ( @@ -14,7 +15,6 @@ env = ENV.Clone( ("compilation_db", {"COMPILATIONDB_COMSTR": "\tCDB\t${TARGET}"}), "fwbin", "fbt_apps", - "fbt_sdk", ], COMPILATIONDB_USE_ABSPATH=False, BUILD_DIR=fw_build_meta["build_dir"], @@ -112,7 +112,9 @@ lib_targets = env.BuildModules( # Now, env is fully set up with everything to build apps -fwenv = env.Clone() +fwenv = env.Clone(FW_ARTIFACTS=[]) + +fw_artifacts = fwenv["FW_ARTIFACTS"] # Set up additional app-specific build flags SConscript("site_scons/firmwareopts.scons", exports={"ENV": fwenv}) @@ -130,7 +132,14 @@ if extra_int_apps := GetOption("extra_int_apps"): if fwenv["FAP_EXAMPLES"]: fwenv.Append(APPDIRS=[("applications/examples", False)]) -fwenv.LoadApplicationManifests() +for app_dir, _ in env["APPDIRS"]: + app_dir_node = env.Dir("#").Dir(app_dir) + + for entry in app_dir_node.glob("*"): + if isinstance(entry, FS.Dir) and not str(entry).startswith("."): + fwenv.LoadAppManifest(entry) + + fwenv.PrepareApplicationsBuild() # Build external apps @@ -138,6 +147,7 @@ if env["IS_BASE_FIRMWARE"]: extapps = fwenv["FW_EXTAPPS"] = SConscript( "site_scons/extapps.scons", exports={"ENV": fwenv} ) + fw_artifacts.append(extapps["sdk_tree"]) # Add preprocessor definitions for current set of apps @@ -220,7 +230,10 @@ Depends(fwelf, lib_targets) AddPostAction(fwelf, fwenv["APPBUILD_DUMP"]) AddPostAction( fwelf, - Action('${PYTHON3} "${ROOT_DIR}/scripts/fwsize.py" elf ${TARGET}', "Firmware size"), + Action( + '${PYTHON3} "${BIN_SIZE_SCRIPT}" elf ${TARGET}', + "Firmware size", + ), ) # Produce extra firmware files @@ -228,7 +241,7 @@ fwhex = fwenv["FW_HEX"] = fwenv.HEXBuilder("${FIRMWARE_BUILD_CFG}") fwbin = fwenv["FW_BIN"] = fwenv.BINBuilder("${FIRMWARE_BUILD_CFG}") AddPostAction( fwbin, - Action('@${PYTHON3} "${ROOT_DIR}/scripts/fwsize.py" bin ${TARGET}'), + Action('@${PYTHON3} "${BIN_SIZE_SCRIPT}" bin ${TARGET}'), ) fwdfu = fwenv["FW_DFU"] = fwenv.DFUBuilder("${FIRMWARE_BUILD_CFG}") @@ -238,12 +251,14 @@ fwdump = fwenv.ObjDump("${FIRMWARE_BUILD_CFG}") Alias(fwenv["FIRMWARE_BUILD_CFG"] + "_list", fwdump) -fw_artifacts = fwenv["FW_ARTIFACTS"] = [ - fwhex, - fwbin, - fwdfu, - fwenv["FW_VERSION_JSON"], -] +fw_artifacts.extend( + [ + fwhex, + fwbin, + fwdfu, + fwenv["FW_VERSION_JSON"], + ] +) fwcdb = fwenv.CompilationDatabase() @@ -272,34 +287,5 @@ if should_gen_cdb_and_link_dir(fwenv, BUILD_TARGETS): Alias(fwenv["FIRMWARE_BUILD_CFG"] + "_all", fw_artifacts) -if fwenv["IS_BASE_FIRMWARE"]: - sdk_source = fwenv.SDKPrebuilder( - "sdk_origin", - # Deps on root SDK headers and generated files - (fwenv["SDK_HEADERS"], fwenv["FW_ASSETS_HEADERS"]), - ) - # Extra deps on headers included in deeper levels - Depends(sdk_source, fwenv.ProcessSdkDepends("sdk_origin.d")) - - fwenv["SDK_DIR"] = fwenv.Dir("sdk") - sdk_tree = fwenv.SDKTree(fwenv["SDK_DIR"], "sdk_origin") - fw_artifacts.append(sdk_tree) - # AlwaysBuild(sdk_tree) - Alias("sdk_tree", sdk_tree) - - sdk_apicheck = fwenv.SDKSymUpdater(fwenv["SDK_DEFINITION"], "sdk_origin") - Precious(sdk_apicheck) - NoClean(sdk_apicheck) - AlwaysBuild(sdk_apicheck) - Alias("sdk_check", sdk_apicheck) - - sdk_apisyms = fwenv.SDKSymGenerator( - "assets/compiled/symbols.h", fwenv["SDK_DEFINITION"] - ) - Alias("api_syms", sdk_apisyms) - - if fwenv["FORCE"]: - fwenv.AlwaysBuild(sdk_source, sdk_tree, sdk_apicheck, sdk_apisyms) - Return("fwenv") diff --git a/firmware/SConscript b/firmware/SConscript index 2285a6f2cfb..19dde2e4cd6 100644 --- a/firmware/SConscript +++ b/firmware/SConscript @@ -2,11 +2,10 @@ Import("env") env.Append( LINT_SOURCES=["firmware"], - # SDK_HEADERS=[env.File("#/firmware/targets/furi_hal_include/furi_hal.h")], SDK_HEADERS=[ - *env.GlobRecursive("*.h", "#/firmware/targets/furi_hal_include", "*_i.h"), - *env.GlobRecursive("*.h", "#/firmware/targets/f${TARGET_HW}/furi_hal", "*_i.h"), - File("#/firmware/targets/f7/platform_specific/intrinsic_export.h"), + *env.GlobRecursive("*.h", "targets/furi_hal_include", "*_i.h"), + *env.GlobRecursive("*.h", "targets/f${TARGET_HW}/furi_hal", "*_i.h"), + File("targets/f7/platform_specific/intrinsic_export.h"), ], ) diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 00ba30c24c1..0d9a5b56128 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,7.0,, +Version,+,7.2,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -7,6 +7,7 @@ Header,+,applications/services/dialogs/dialogs.h,, Header,+,applications/services/dolphin/dolphin.h,, Header,+,applications/services/gui/elements.h,, Header,+,applications/services/gui/gui.h,, +Header,+,applications/services/gui/icon_i.h,, Header,+,applications/services/gui/modules/button_menu.h,, Header,+,applications/services/gui/modules/button_panel.h,, Header,+,applications/services/gui/modules/byte_input.h,, @@ -110,12 +111,42 @@ Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_wwdg.h,, Header,+,lib/flipper_application/flipper_application.h,, Header,+,lib/flipper_format/flipper_format.h,, Header,+,lib/flipper_format/flipper_format_i.h,, +Header,+,lib/infrared/encoder_decoder/infrared.h,, +Header,+,lib/infrared/worker/infrared_transmit.h,, +Header,+,lib/infrared/worker/infrared_worker.h,, Header,+,lib/lfrfid/lfrfid_dict_file.h,, Header,+,lib/lfrfid/lfrfid_raw_file.h,, Header,+,lib/lfrfid/lfrfid_raw_worker.h,, Header,+,lib/lfrfid/lfrfid_worker.h,, Header,+,lib/lfrfid/protocols/lfrfid_protocols.h,, Header,+,lib/lfrfid/tools/bit_lib.h,, +Header,+,lib/libusb_stm32/inc/hid_usage_button.h,, +Header,+,lib/libusb_stm32/inc/hid_usage_consumer.h,, +Header,+,lib/libusb_stm32/inc/hid_usage_desktop.h,, +Header,+,lib/libusb_stm32/inc/hid_usage_device.h,, +Header,+,lib/libusb_stm32/inc/hid_usage_game.h,, +Header,+,lib/libusb_stm32/inc/hid_usage_keyboard.h,, +Header,+,lib/libusb_stm32/inc/hid_usage_led.h,, +Header,+,lib/libusb_stm32/inc/hid_usage_ordinal.h,, +Header,+,lib/libusb_stm32/inc/hid_usage_power.h,, +Header,+,lib/libusb_stm32/inc/hid_usage_simulation.h,, +Header,+,lib/libusb_stm32/inc/hid_usage_sport.h,, +Header,+,lib/libusb_stm32/inc/hid_usage_telephony.h,, +Header,+,lib/libusb_stm32/inc/hid_usage_vr.h,, +Header,+,lib/libusb_stm32/inc/usb.h,, +Header,+,lib/libusb_stm32/inc/usb_cdc.h,, +Header,+,lib/libusb_stm32/inc/usb_cdca.h,, +Header,+,lib/libusb_stm32/inc/usb_cdce.h,, +Header,+,lib/libusb_stm32/inc/usb_cdci.h,, +Header,+,lib/libusb_stm32/inc/usb_cdcp.h,, +Header,+,lib/libusb_stm32/inc/usb_cdcw.h,, +Header,+,lib/libusb_stm32/inc/usb_dfu.h,, +Header,+,lib/libusb_stm32/inc/usb_hid.h,, +Header,+,lib/libusb_stm32/inc/usb_std.h,, +Header,+,lib/libusb_stm32/inc/usb_tmc.h,, +Header,+,lib/libusb_stm32/inc/usbd_core.h,, +Header,+,lib/mbedtls/include/mbedtls/des.h,, +Header,+,lib/mbedtls/include/mbedtls/sha1.h,, Header,+,lib/micro-ecc/uECC.h,, Header,+,lib/one_wire/ibutton/ibutton_worker.h,, Header,+,lib/one_wire/maxim_crc.h,, @@ -132,6 +163,7 @@ Header,+,lib/subghz/blocks/math.h,, Header,+,lib/subghz/environment.h,, Header,+,lib/subghz/protocols/raw.h,, Header,+,lib/subghz/receiver.h,, +Header,+,lib/subghz/registry.h,, Header,+,lib/subghz/subghz_setting.h,, Header,+,lib/subghz/subghz_tx_rx_worker.h,, Header,+,lib/subghz/subghz_worker.h,, @@ -407,6 +439,7 @@ Function,-,_system_r,int,"_reent*, const char*" Function,-,_tempnam_r,char*,"_reent*, const char*, const char*" Function,-,_tmpfile_r,FILE*,_reent* Function,-,_tmpnam_r,char*,"_reent*, char*" +Function,-,_tzset_r,void,_reent* Function,-,_ungetc_r,int,"_reent*, int, FILE*" Function,-,_unsetenv_r,int,"_reent*, const char*" Function,-,_vasiprintf_r,int,"_reent*, char**, const char*, __gnuc_va_list" @@ -454,6 +487,8 @@ Function,+,args_read_hex_bytes,_Bool,"FuriString*, uint8_t*, size_t" Function,+,args_read_int_and_trim,_Bool,"FuriString*, int*" Function,+,args_read_probably_quoted_string_and_trim,_Bool,"FuriString*, FuriString*" Function,+,args_read_string_and_trim,_Bool,"FuriString*, FuriString*" +Function,-,asctime,char*,const tm* +Function,-,asctime_r,char*,"const tm*, char*" Function,-,asin,double,double Function,-,asinf,float,float Function,-,asinh,double,double @@ -621,6 +656,7 @@ Function,+,cli_read_timeout,size_t,"Cli*, uint8_t*, size_t, uint32_t" Function,+,cli_session_close,void,Cli* Function,+,cli_session_open,void,"Cli*, void*" Function,+,cli_write,void,"Cli*, const uint8_t*, size_t" +Function,-,clock,clock_t, Function,-,copysign,double,"double, double" Function,-,copysignf,float,"float, float" Function,-,copysignl,long double,"long double, long double" @@ -633,6 +669,8 @@ Function,-,cosl,long double,long double Function,+,crc32_calc_buffer,uint32_t,"uint32_t, const void*, size_t" Function,+,crc32_calc_file,uint32_t,"File*, const FileCrcProgressCb, void*" Function,-,ctermid,char*,char* +Function,-,ctime,char*,const time_t* +Function,-,ctime_r,char*,"const time_t*, char*" Function,-,cuserid,char*,char* Function,+,delete_mutex,_Bool,ValueMutex* Function,+,dialog_ex_alloc,DialogEx*, @@ -659,6 +697,7 @@ Function,+,dialog_message_set_icon,void,"DialogMessage*, const Icon*, uint8_t, u Function,+,dialog_message_set_text,void,"DialogMessage*, const char*, uint8_t, uint8_t, Align, Align" Function,+,dialog_message_show,DialogMessageButton,"DialogsApp*, const DialogMessage*" Function,+,dialog_message_show_storage_error,void,"DialogsApp*, const char*" +Function,-,difftime,double,"time_t, time_t" Function,-,digital_signal_alloc,DigitalSignal*,uint32_t Function,-,digital_signal_append,_Bool,"DigitalSignal*, DigitalSignal*" Function,-,digital_signal_free,void,DigitalSignal* @@ -1475,6 +1514,8 @@ Function,-,getenv,char*,const char* Function,-,gets,char*,char* Function,-,getsubopt,int,"char**, char**, char**" Function,-,getw,int,FILE* +Function,-,gmtime,tm*,const time_t* +Function,-,gmtime_r,tm*,"const time_t*, tm*" Function,+,gui_add_framebuffer_callback,void,"Gui*, GuiCanvasCommitCallback, void*" Function,+,gui_add_view_port,void,"Gui*, ViewPort*, GuiLayer" Function,+,gui_get_framebuffer_size,size_t,Gui* @@ -1535,6 +1576,42 @@ Function,-,ilogbl,int,long double Function,-,index,char*,"const char*, int" Function,-,infinity,double, Function,-,infinityf,float, +Function,+,infrared_alloc_decoder,InfraredDecoderHandler*, +Function,+,infrared_alloc_encoder,InfraredEncoderHandler*, +Function,+,infrared_check_decoder_ready,const InfraredMessage*,InfraredDecoderHandler* +Function,+,infrared_decode,const InfraredMessage*,"InfraredDecoderHandler*, _Bool, uint32_t" +Function,+,infrared_encode,InfraredStatus,"InfraredEncoderHandler*, uint32_t*, _Bool*" +Function,+,infrared_free_decoder,void,InfraredDecoderHandler* +Function,+,infrared_free_encoder,void,InfraredEncoderHandler* +Function,+,infrared_get_protocol_address_length,uint8_t,InfraredProtocol +Function,+,infrared_get_protocol_by_name,InfraredProtocol,const char* +Function,+,infrared_get_protocol_command_length,uint8_t,InfraredProtocol +Function,+,infrared_get_protocol_duty_cycle,float,InfraredProtocol +Function,+,infrared_get_protocol_frequency,uint32_t,InfraredProtocol +Function,+,infrared_get_protocol_name,const char*,InfraredProtocol +Function,+,infrared_is_protocol_valid,_Bool,InfraredProtocol +Function,+,infrared_reset_decoder,void,InfraredDecoderHandler* +Function,+,infrared_reset_encoder,void,"InfraredEncoderHandler*, const InfraredMessage*" +Function,+,infrared_send,void,"const InfraredMessage*, int" +Function,+,infrared_send_raw,void,"const uint32_t[], uint32_t, _Bool" +Function,+,infrared_send_raw_ext,void,"const uint32_t[], uint32_t, _Bool, uint32_t, float" +Function,+,infrared_worker_alloc,InfraredWorker*, +Function,+,infrared_worker_free,void,InfraredWorker* +Function,+,infrared_worker_get_decoded_signal,const InfraredMessage*,const InfraredWorkerSignal* +Function,+,infrared_worker_get_raw_signal,void,"const InfraredWorkerSignal*, const uint32_t**, size_t*" +Function,+,infrared_worker_rx_enable_blink_on_receiving,void,"InfraredWorker*, _Bool" +Function,+,infrared_worker_rx_enable_signal_decoding,void,"InfraredWorker*, _Bool" +Function,+,infrared_worker_rx_set_received_signal_callback,void,"InfraredWorker*, InfraredWorkerReceivedSignalCallback, void*" +Function,+,infrared_worker_rx_start,void,InfraredWorker* +Function,+,infrared_worker_rx_stop,void,InfraredWorker* +Function,+,infrared_worker_set_decoded_signal,void,"InfraredWorker*, const InfraredMessage*" +Function,+,infrared_worker_set_raw_signal,void,"InfraredWorker*, const uint32_t*, size_t" +Function,+,infrared_worker_signal_is_decoded,_Bool,const InfraredWorkerSignal* +Function,+,infrared_worker_tx_get_signal_steady_callback,InfraredWorkerGetSignalResponse,"void*, InfraredWorker*" +Function,+,infrared_worker_tx_set_get_signal_callback,void,"InfraredWorker*, InfraredWorkerGetSignalCallback, void*" +Function,+,infrared_worker_tx_set_signal_sent_callback,void,"InfraredWorker*, InfraredWorkerMessageSentCallback, void*" +Function,+,infrared_worker_tx_start,void,InfraredWorker* +Function,+,infrared_worker_tx_stop,void,InfraredWorker* Function,+,init_mutex,_Bool,"ValueMutex*, void*, size_t" Function,-,initstate,char*,"unsigned, char*, size_t" Function,+,input_get_key_name,const char*,InputKey @@ -1634,6 +1711,8 @@ Function,+,loader_update_menu,void, Function,+,loading_alloc,Loading*, Function,+,loading_free,void,Loading* Function,+,loading_get_view,View*,Loading* +Function,-,localtime,tm*,const time_t* +Function,-,localtime_r,tm*,"const time_t*, tm*" Function,-,log,double,double Function,-,log10,double,double Function,-,log10f,float,float @@ -1662,6 +1741,36 @@ Function,+,manchester_encoder_advance,_Bool,"ManchesterEncoderState*, const _Boo Function,+,manchester_encoder_finish,ManchesterEncoderResult,ManchesterEncoderState* Function,+,manchester_encoder_reset,void,ManchesterEncoderState* Function,+,maxim_crc8,uint8_t,"const uint8_t*, const uint8_t, const uint8_t" +Function,-,mbedtls_des3_crypt_cbc,int,"mbedtls_des3_context*, int, size_t, unsigned char[8], const unsigned char*, unsigned char*" +Function,-,mbedtls_des3_crypt_ecb,int,"mbedtls_des3_context*, const unsigned char[8], unsigned char[8]" +Function,-,mbedtls_des3_free,void,mbedtls_des3_context* +Function,-,mbedtls_des3_init,void,mbedtls_des3_context* +Function,-,mbedtls_des3_set2key_dec,int,"mbedtls_des3_context*, const unsigned char[8 * 2]" +Function,-,mbedtls_des3_set2key_enc,int,"mbedtls_des3_context*, const unsigned char[8 * 2]" +Function,-,mbedtls_des3_set3key_dec,int,"mbedtls_des3_context*, const unsigned char[8 * 3]" +Function,-,mbedtls_des3_set3key_enc,int,"mbedtls_des3_context*, const unsigned char[8 * 3]" +Function,-,mbedtls_des_crypt_cbc,int,"mbedtls_des_context*, int, size_t, unsigned char[8], const unsigned char*, unsigned char*" +Function,-,mbedtls_des_crypt_ecb,int,"mbedtls_des_context*, const unsigned char[8], unsigned char[8]" +Function,-,mbedtls_des_free,void,mbedtls_des_context* +Function,-,mbedtls_des_init,void,mbedtls_des_context* +Function,-,mbedtls_des_key_check_key_parity,int,const unsigned char[8] +Function,-,mbedtls_des_key_check_weak,int,const unsigned char[8] +Function,-,mbedtls_des_key_set_parity,void,unsigned char[8] +Function,-,mbedtls_des_self_test,int,int +Function,-,mbedtls_des_setkey,void,"uint32_t[32], const unsigned char[8]" +Function,-,mbedtls_des_setkey_dec,int,"mbedtls_des_context*, const unsigned char[8]" +Function,-,mbedtls_des_setkey_enc,int,"mbedtls_des_context*, const unsigned char[8]" +Function,-,mbedtls_internal_sha1_process,int,"mbedtls_sha1_context*, const unsigned char[64]" +Function,-,mbedtls_platform_gmtime_r,tm*,"const mbedtls_time_t*, tm*" +Function,-,mbedtls_platform_zeroize,void,"void*, size_t" +Function,-,mbedtls_sha1,int,"const unsigned char*, size_t, unsigned char[20]" +Function,-,mbedtls_sha1_clone,void,"mbedtls_sha1_context*, const mbedtls_sha1_context*" +Function,-,mbedtls_sha1_finish,int,"mbedtls_sha1_context*, unsigned char[20]" +Function,-,mbedtls_sha1_free,void,mbedtls_sha1_context* +Function,-,mbedtls_sha1_init,void,mbedtls_sha1_context* +Function,-,mbedtls_sha1_self_test,int,int +Function,-,mbedtls_sha1_starts,int,mbedtls_sha1_context* +Function,-,mbedtls_sha1_update,int,"mbedtls_sha1_context*, const unsigned char*, size_t" Function,-,mblen,int,"const char*, size_t" Function,-,mbstowcs,size_t,"wchar_t*, const char*, size_t" Function,-,mbtowc,int,"wchar_t*, const char*, size_t" @@ -1702,6 +1811,7 @@ Function,-,mkostemps,int,"char*, int, int" Function,-,mkstemp,int,char* Function,-,mkstemps,int,"char*, int" Function,-,mktemp,char*,char* +Function,-,mktime,time_t,tm* Function,-,modf,double,"double, double*" Function,-,modff,float,"float, float*" Function,-,modfl,long double,"long double, long double*" @@ -2210,6 +2320,8 @@ Function,+,stream_write_vaformat,size_t,"Stream*, const char*, va_list" Function,-,strerror,char*,int Function,-,strerror_l,char*,"int, locale_t" Function,-,strerror_r,char*,"int, char*, size_t" +Function,-,strftime,size_t,"char*, size_t, const char*, const tm*" +Function,-,strftime_l,size_t,"char*, size_t, const char*, const tm*, locale_t" Function,+,string_stream_alloc,Stream*, Function,-,strlcat,size_t,"char*, const char*, size_t" Function,+,strlcpy,size_t,"char*, const char*, size_t" @@ -2224,6 +2336,8 @@ Function,-,strndup,char*,"const char*, size_t" Function,-,strnlen,size_t,"const char*, size_t" Function,-,strnstr,char*,"const char*, const char*, size_t" Function,-,strpbrk,char*,"const char*, const char*" +Function,-,strptime,char*,"const char*, const char*, tm*" +Function,-,strptime_l,char*,"const char*, const char*, tm*, locale_t" Function,+,strrchr,char*,"const char*, int" Function,-,strsep,char*,"char**, const char*" Function,-,strsignal,char*,int @@ -2313,6 +2427,9 @@ Function,+,subghz_protocol_raw_get_sample_write,size_t,SubGhzProtocolDecoderRAW* Function,+,subghz_protocol_raw_save_to_file_init,_Bool,"SubGhzProtocolDecoderRAW*, const char*, SubGhzRadioPreset*" Function,+,subghz_protocol_raw_save_to_file_pause,void,"SubGhzProtocolDecoderRAW*, _Bool" Function,+,subghz_protocol_raw_save_to_file_stop,void,SubGhzProtocolDecoderRAW* +Function,+,subghz_protocol_registry_count,size_t,const SubGhzProtocolRegistry* +Function,+,subghz_protocol_registry_get_by_index,const SubGhzProtocol*,"const SubGhzProtocolRegistry*, size_t" +Function,+,subghz_protocol_registry_get_by_name,const SubGhzProtocol*,"const SubGhzProtocolRegistry*, const char*" Function,+,subghz_receiver_alloc_init,SubGhzReceiver*,SubGhzEnvironment* Function,+,subghz_receiver_decode,void,"SubGhzReceiver*, _Bool, uint32_t" Function,+,subghz_receiver_free,void,SubGhzReceiver* @@ -2411,6 +2528,7 @@ Function,+,text_input_set_validator,void,"TextInput*, TextInputValidatorCallback Function,-,tgamma,double,double Function,-,tgammaf,float,float Function,-,tgammal,long double,long double +Function,-,time,time_t,time_t* Function,+,timerCalculateTimer,uint32_t,uint16_t Function,-,timerDelay,void,uint16_t Function,+,timerIsExpired,_Bool,uint32_t @@ -2429,6 +2547,7 @@ Function,-,toupper_l,int,"int, locale_t" Function,-,trunc,double,double Function,-,truncf,float,float Function,-,truncl,long double,long double +Function,-,tzset,void, Function,-,uECC_compress,void,"const uint8_t*, uint8_t*, uECC_Curve" Function,+,uECC_compute_public_key,int,"const uint8_t*, uint8_t*, uECC_Curve" Function,-,uECC_curve_private_key_size,int,uECC_Curve @@ -2666,10 +2785,13 @@ Variable,-,MSIRangeTable,const uint32_t[16], Variable,-,SmpsPrescalerTable,const uint32_t[4][6], Variable,+,SystemCoreClock,uint32_t, Variable,+,_ctype_,const char[], +Variable,-,_daylight,int, Variable,+,_global_impure_ptr,_reent*, Variable,+,_impure_ptr,_reent*, Variable,-,_sys_errlist,const char*[], Variable,-,_sys_nerr,int, +Variable,-,_timezone,long, +Variable,-,_tzname,char*[2], Variable,+,cli_vcp,CliSession, Variable,+,furi_hal_i2c_bus_external,FuriHalI2cBus, Variable,+,furi_hal_i2c_bus_power,FuriHalI2cBus, diff --git a/lib/SConscript b/lib/SConscript index 2822684bca7..60ffabfa932 100644 --- a/lib/SConscript +++ b/lib/SConscript @@ -17,12 +17,12 @@ env.Append( "lib/print", ], SDK_HEADERS=[ - File("#/lib/one_wire/one_wire_host_timing.h"), - File("#/lib/one_wire/one_wire_host.h"), - File("#/lib/one_wire/one_wire_slave.h"), - File("#/lib/one_wire/one_wire_device.h"), - File("#/lib/one_wire/ibutton/ibutton_worker.h"), - File("#/lib/one_wire/maxim_crc.h"), + File("one_wire/one_wire_host_timing.h"), + File("one_wire/one_wire_host.h"), + File("one_wire/one_wire_slave.h"), + File("one_wire/one_wire_device.h"), + File("one_wire/ibutton/ibutton_worker.h"), + File("one_wire/maxim_crc.h"), ], ) diff --git a/lib/STM32CubeWB.scons b/lib/STM32CubeWB.scons index e8350ea99ae..b0e55f82eda 100644 --- a/lib/STM32CubeWB.scons +++ b/lib/STM32CubeWB.scons @@ -15,7 +15,7 @@ env.Append( ], SDK_HEADERS=env.GlobRecursive( "*_ll_*.h", - "#/lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/", + Dir("STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/"), exclude="*usb.h", ), ) diff --git a/lib/flipper_application/SConscript b/lib/flipper_application/SConscript index 3a5e7f4db74..9fbbf95d144 100644 --- a/lib/flipper_application/SConscript +++ b/lib/flipper_application/SConscript @@ -5,7 +5,7 @@ env.Append( "#/lib/flipper_application", ], SDK_HEADERS=[ - File("#/lib/flipper_application/flipper_application.h"), + File("flipper_application.h"), ], ) diff --git a/lib/flipper_format/SConscript b/lib/flipper_format/SConscript index 5e185678b8a..353da803570 100644 --- a/lib/flipper_format/SConscript +++ b/lib/flipper_format/SConscript @@ -5,8 +5,8 @@ env.Append( "#/lib/flipper_format", ], SDK_HEADERS=[ - File("#/lib/flipper_format/flipper_format.h"), - File("#/lib/flipper_format/flipper_format_i.h"), + File("flipper_format.h"), + File("flipper_format_i.h"), ], ) diff --git a/lib/infrared/SConscript b/lib/infrared/SConscript index 35db75f879d..9a1543f00fa 100644 --- a/lib/infrared/SConscript +++ b/lib/infrared/SConscript @@ -5,6 +5,11 @@ env.Append( "#/lib/infrared/encoder_decoder", "#/lib/infrared/worker", ], + SDK_HEADERS=[ + File("encoder_decoder/infrared.h"), + File("worker/infrared_worker.h"), + File("worker/infrared_transmit.h"), + ], ) diff --git a/lib/lfrfid/SConscript b/lib/lfrfid/SConscript index 6177a9a508b..69ea9d3c162 100644 --- a/lib/lfrfid/SConscript +++ b/lib/lfrfid/SConscript @@ -8,12 +8,12 @@ env.Append( "#/lib/lfrfid", ], SDK_HEADERS=[ - File("#/lib/lfrfid/lfrfid_worker.h"), - File("#/lib/lfrfid/lfrfid_raw_worker.h"), - File("#/lib/lfrfid/lfrfid_raw_file.h"), - File("#/lib/lfrfid/lfrfid_dict_file.h"), - File("#/lib/lfrfid/tools/bit_lib.h"), - File("#/lib/lfrfid/protocols/lfrfid_protocols.h"), + File("lfrfid_worker.h"), + File("lfrfid_raw_worker.h"), + File("lfrfid_raw_file.h"), + File("lfrfid_dict_file.h"), + File("tools/bit_lib.h"), + File("protocols/lfrfid_protocols.h"), ], ) diff --git a/lib/libusb_stm32.scons b/lib/libusb_stm32.scons index cb867fdbc9c..4838b7c509a 100644 --- a/lib/libusb_stm32.scons +++ b/lib/libusb_stm32.scons @@ -7,6 +7,10 @@ env.Append( CPPDEFINES=[ ("USB_PMASIZE", "0x400"), ], + SDK_HEADERS=env.GlobRecursive( + "*.h", + Dir("libusb_stm32/inc"), + ), ) diff --git a/lib/mbedtls.scons b/lib/mbedtls.scons index b57221a498e..79a4a25203f 100644 --- a/lib/mbedtls.scons +++ b/lib/mbedtls.scons @@ -5,6 +5,10 @@ env.Append( "#/lib/mbedtls", "#/lib/mbedtls/include", ], + SDK_HEADERS=[ + File("mbedtls/include/mbedtls/des.h"), + File("mbedtls/include/mbedtls/sha1.h"), + ], ) diff --git a/lib/misc.scons b/lib/misc.scons index b7d8554b590..91ad276a0b2 100644 --- a/lib/misc.scons +++ b/lib/misc.scons @@ -13,7 +13,7 @@ env.Append( "PB_ENABLE_MALLOC", ], SDK_HEADERS=[ - File("#/lib/micro-ecc/uECC.h"), + File("micro-ecc/uECC.h"), ], ) diff --git a/lib/print/SConscript b/lib/print/SConscript index d4a55ab8471..f34c8152fab 100644 --- a/lib/print/SConscript +++ b/lib/print/SConscript @@ -98,7 +98,7 @@ for wrapped_fn in wrapped_fn_list: env.Append( SDK_HEADERS=[ - File("#/lib/print/wrappers.h"), + File("wrappers.h"), ], ) diff --git a/lib/subghz/SConscript b/lib/subghz/SConscript index e25d122c81f..6d9c0cd0638 100644 --- a/lib/subghz/SConscript +++ b/lib/subghz/SConscript @@ -5,18 +5,19 @@ env.Append( "#/lib/subghz", ], SDK_HEADERS=[ - File("#/lib/subghz/environment.h"), - File("#/lib/subghz/receiver.h"), - File("#/lib/subghz/subghz_worker.h"), - File("#/lib/subghz/subghz_tx_rx_worker.h"), - File("#/lib/subghz/transmitter.h"), - File("#/lib/subghz/protocols/raw.h"), - File("#/lib/subghz/blocks/const.h"), - File("#/lib/subghz/blocks/decoder.h"), - File("#/lib/subghz/blocks/encoder.h"), - File("#/lib/subghz/blocks/generic.h"), - File("#/lib/subghz/blocks/math.h"), - File("#/lib/subghz/subghz_setting.h"), + File("environment.h"), + File("receiver.h"), + File("registry.h"), + File("subghz_worker.h"), + File("subghz_tx_rx_worker.h"), + File("transmitter.h"), + File("protocols/raw.h"), + File("blocks/const.h"), + File("blocks/decoder.h"), + File("blocks/encoder.h"), + File("blocks/generic.h"), + File("blocks/math.h"), + File("subghz_setting.h"), ], ) diff --git a/lib/subghz/registry.h b/lib/subghz/registry.h index 062cdf68f87..91027807e89 100644 --- a/lib/subghz/registry.h +++ b/lib/subghz/registry.h @@ -2,6 +2,10 @@ #include "types.h" +#ifdef __cplusplus +extern "C" { +#endif + typedef struct SubGhzEnvironment SubGhzEnvironment; typedef struct SubGhzProtocolRegistry SubGhzProtocolRegistry; @@ -37,3 +41,7 @@ const SubGhzProtocol* subghz_protocol_registry_get_by_index( * @return Number of protocols */ size_t subghz_protocol_registry_count(const SubGhzProtocolRegistry* protocol_registry); + +#ifdef __cplusplus +} +#endif diff --git a/lib/toolbox/SConscript b/lib/toolbox/SConscript index d631431ee8c..015a8ed189a 100644 --- a/lib/toolbox/SConscript +++ b/lib/toolbox/SConscript @@ -8,23 +8,23 @@ env.Append( "#/lib/toolbox", ], SDK_HEADERS=[ - File("#/lib/toolbox/manchester_decoder.h"), - File("#/lib/toolbox/manchester_encoder.h"), - File("#/lib/toolbox/path.h"), - File("#/lib/toolbox/random_name.h"), - File("#/lib/toolbox/hmac_sha256.h"), - File("#/lib/toolbox/crc32_calc.h"), - File("#/lib/toolbox/dir_walk.h"), - File("#/lib/toolbox/md5.h"), - File("#/lib/toolbox/args.h"), - File("#/lib/toolbox/saved_struct.h"), - File("#/lib/toolbox/version.h"), - File("#/lib/toolbox/tar/tar_archive.h"), - File("#/lib/toolbox/stream/stream.h"), - File("#/lib/toolbox/stream/file_stream.h"), - File("#/lib/toolbox/stream/string_stream.h"), - File("#/lib/toolbox/stream/buffered_file_stream.h"), - File("#/lib/toolbox/protocols/protocol_dict.h"), + File("manchester_decoder.h"), + File("manchester_encoder.h"), + File("path.h"), + File("random_name.h"), + File("hmac_sha256.h"), + File("crc32_calc.h"), + File("dir_walk.h"), + File("md5.h"), + File("args.h"), + File("saved_struct.h"), + File("version.h"), + File("tar/tar_archive.h"), + File("stream/stream.h"), + File("stream/file_stream.h"), + File("stream/string_stream.h"), + File("stream/buffered_file_stream.h"), + File("protocols/protocol_dict.h"), ], ) diff --git a/scripts/fbt/util.py b/scripts/fbt/util.py index baa4ddfee04..f5404458ec5 100644 --- a/scripts/fbt/util.py +++ b/scripts/fbt/util.py @@ -1,11 +1,11 @@ import SCons from SCons.Subst import quote_spaces from SCons.Errors import StopError +from SCons.Node.FS import _my_normcase import re import os -import random -import string + WINPATHSEP_RE = re.compile(r"\\([^\"'\\]|$)") @@ -41,3 +41,14 @@ def link_dir(target_path, source_path, is_windows): def single_quote(arg_list): return " ".join(f"'{arg}'" if " " in arg else str(arg) for arg in arg_list) + + +def extract_abs_dir_path(node): + if isinstance(node, SCons.Node.FS.EntryProxy): + node = node.get() + + for repo_dir in node.get_all_rdirs(): + if os.path.exists(repo_dir.abspath): + return repo_dir.abspath + + raise StopError(f"Can't find absolute path for {node.name}") diff --git a/scripts/fbt_tools/crosscc.py b/scripts/fbt_tools/crosscc.py index aacda58c6b3..dd5cd531961 100644 --- a/scripts/fbt_tools/crosscc.py +++ b/scripts/fbt_tools/crosscc.py @@ -37,6 +37,21 @@ def _get_tool_version(env, tool): def generate(env, **kw): + if not env.get("VERBOSE", False): + env.SetDefault( + CCCOMSTR="\tCC\t${SOURCE}", + CXXCOMSTR="\tCPP\t${SOURCE}", + ASCOMSTR="\tASM\t${SOURCE}", + ARCOMSTR="\tAR\t${TARGET}", + RANLIBCOMSTR="\tRANLIB\t${TARGET}", + LINKCOMSTR="\tLINK\t${TARGET}", + INSTALLSTR="\tINSTALL\t${TARGET}", + APPSCOMSTR="\tAPPS\t${TARGET}", + VERSIONCOMSTR="\tVERSION\t${TARGET}", + STRIPCOMSTR="\tSTRIP\t${TARGET}", + OBJDUMPCOMSTR="\tOBJDUMP\t${TARGET}", + ) + for orig_tool in (asm, gcc, gxx, ar, gnulink, strip, gdb, objdump): orig_tool.generate(env) env.SetDefault( diff --git a/scripts/fbt_tools/fbt_apps.py b/scripts/fbt_tools/fbt_apps.py index ef5e9b9d934..55e282017fe 100644 --- a/scripts/fbt_tools/fbt_apps.py +++ b/scripts/fbt_tools/fbt_apps.py @@ -2,7 +2,7 @@ from SCons.Action import Action from SCons.Warnings import warn, WarningOnByDefault import SCons -import os.path +from ansi.color import fg from fbt.appmanifest import ( FlipperAppType, @@ -16,21 +16,20 @@ # AppBuildset env["APPBUILD"] - contains subset of apps, filtered for current config -def LoadApplicationManifests(env): - appmgr = env["APPMGR"] = AppManager() - for app_dir, _ in env["APPDIRS"]: - app_dir_node = env.Dir("#").Dir(app_dir) +def LoadAppManifest(env, entry): + try: + APP_MANIFEST_NAME = "application.fam" + manifest_glob = entry.glob(APP_MANIFEST_NAME) + if len(manifest_glob) == 0: + raise FlipperManifestException( + f"Folder {entry}: manifest {APP_MANIFEST_NAME} is missing" + ) - for entry in app_dir_node.glob("*", ondisk=True, source=True): - if isinstance(entry, SCons.Node.FS.Dir) and not str(entry).startswith("."): - try: - app_manifest_file_path = os.path.join( - entry.abspath, "application.fam" - ) - appmgr.load_manifest(app_manifest_file_path, entry) - env.Append(PY_LINT_SOURCES=[app_manifest_file_path]) - except FlipperManifestException as e: - warn(WarningOnByDefault, str(e)) + app_manifest_file_path = manifest_glob[0].rfile().abspath + env["APPMGR"].load_manifest(app_manifest_file_path, entry) + env.Append(PY_LINT_SOURCES=[app_manifest_file_path]) + except FlipperManifestException as e: + warn(WarningOnByDefault, str(e)) def PrepareApplicationsBuild(env): @@ -46,12 +45,12 @@ def PrepareApplicationsBuild(env): def DumpApplicationConfig(target, source, env): print(f"Loaded {len(env['APPMGR'].known_apps)} app definitions.") - print("Firmware modules configuration:") + print(fg.boldgreen("Firmware modules configuration:")) for apptype in FlipperAppType: app_sublist = env["APPBUILD"].get_apps_of_type(apptype) if app_sublist: print( - f"{apptype.value}:\n\t", + fg.green(f"{apptype.value}:\n\t"), ", ".join(app.appid for app in app_sublist), ) @@ -65,8 +64,11 @@ def build_apps_c(target, source, env): def generate(env): - env.AddMethod(LoadApplicationManifests) + env.AddMethod(LoadAppManifest) env.AddMethod(PrepareApplicationsBuild) + env.SetDefault( + APPMGR=AppManager(), + ) env.Append( BUILDERS={ diff --git a/scripts/fbt_tools/fbt_assets.py b/scripts/fbt_tools/fbt_assets.py index 4fa5353d0a7..521c37e904d 100644 --- a/scripts/fbt_tools/fbt_assets.py +++ b/scripts/fbt_tools/fbt_assets.py @@ -1,11 +1,10 @@ -import SCons - from SCons.Builder import Builder from SCons.Action import Action -from SCons.Node.FS import File +from SCons.Errors import SConsEnvironmentError import os import subprocess +from ansi.color import fg def icons_emitter(target, source, env): @@ -13,7 +12,6 @@ def icons_emitter(target, source, env): target[0].File(env.subst("${ICON_FILE_NAME}.c")), target[0].File(env.subst("${ICON_FILE_NAME}.h")), ] - source = env.GlobRecursive("*.*", env["ICON_SRC_DIR"]) return target, source @@ -86,7 +84,7 @@ def proto_ver_generator(target, source, env): ) except (subprocess.CalledProcessError, EnvironmentError) as e: # Not great, not terrible - print("Git: fetch failed") + print(fg.boldred("Git: fetch failed")) try: git_describe = _invoke_git( @@ -94,10 +92,8 @@ def proto_ver_generator(target, source, env): source_dir=src_dir, ) except (subprocess.CalledProcessError, EnvironmentError) as e: - print("Git: describe failed") - Exit("git error") + raise SConsEnvironmentError("Git: describe failed") - # print("describe=", git_describe) git_major, git_minor = git_describe.split(".") version_file_data = ( "#pragma once", @@ -116,7 +112,7 @@ def CompileIcons(env, target_dir, source_dir, *, icon_bundle_name="assets_icons" icons = env.IconBuilder( target_dir, - ICON_SRC_DIR=source_dir, + source_dir, ICON_FILE_NAME=icon_bundle_name, ) env.Depends(icons, icons_src) @@ -125,8 +121,8 @@ def CompileIcons(env, target_dir, source_dir, *, icon_bundle_name="assets_icons" def generate(env): env.SetDefault( - ASSETS_COMPILER="${ROOT_DIR.abspath}/scripts/assets.py", - NANOPB_COMPILER="${ROOT_DIR.abspath}/lib/nanopb/generator/nanopb_generator.py", + ASSETS_COMPILER="${FBT_SCRIPT_DIR}/assets.py", + NANOPB_COMPILER="${ROOT_DIR}/lib/nanopb/generator/nanopb_generator.py", ) env.AddMethod(CompileIcons) @@ -143,7 +139,7 @@ def generate(env): BUILDERS={ "IconBuilder": Builder( action=Action( - '${PYTHON3} "${ASSETS_COMPILER}" icons ${ICON_SRC_DIR} ${TARGET.dir} --filename ${ICON_FILE_NAME}', + '${PYTHON3} "${ASSETS_COMPILER}" icons ${ABSPATHGETTERFUNC(SOURCE)} ${TARGET.dir} --filename ${ICON_FILE_NAME}', "${ICONSCOMSTR}", ), emitter=icons_emitter, diff --git a/scripts/fbt_tools/fbt_dist.py b/scripts/fbt_tools/fbt_dist.py index 853013e9f30..fb59e5b954e 100644 --- a/scripts/fbt_tools/fbt_dist.py +++ b/scripts/fbt_tools/fbt_dist.py @@ -103,7 +103,7 @@ def DistCommand(env, name, source, **kw): command = env.Command( target, source, - '@${PYTHON3} "${ROOT_DIR.abspath}/scripts/sconsdist.py" copy -p ${DIST_PROJECTS} -s "${DIST_SUFFIX}" ${DIST_EXTRA}', + '@${PYTHON3} "${DIST_SCRIPT}" copy -p ${DIST_PROJECTS} -s "${DIST_SUFFIX}" ${DIST_EXTRA}', **kw, ) env.Pseudo(target) @@ -121,6 +121,9 @@ def generate(env): env.SetDefault( COPRO_MCU_FAMILY="STM32WB5x", + SELFUPDATE_SCRIPT="${FBT_SCRIPT_DIR}/selfupdate.py", + DIST_SCRIPT="${FBT_SCRIPT_DIR}/sconsdist.py", + COPRO_ASSETS_SCRIPT="${FBT_SCRIPT_DIR}/assets.py", ) env.Append( @@ -128,7 +131,7 @@ def generate(env): "UsbInstall": Builder( action=[ Action( - '${PYTHON3} "${ROOT_DIR.abspath}/scripts/selfupdate.py" dist/${DIST_DIR}/f${TARGET_HW}-update-${DIST_SUFFIX}/update.fuf' + '${PYTHON3} "${SELFUPDATE_SCRIPT}" dist/${DIST_DIR}/f${TARGET_HW}-update-${DIST_SUFFIX}/update.fuf' ), Touch("${TARGET}"), ] @@ -136,7 +139,7 @@ def generate(env): "CoproBuilder": Builder( action=Action( [ - '${PYTHON3} "${ROOT_DIR.abspath}/scripts/assets.py" ' + '${PYTHON3} "${COPRO_ASSETS_SCRIPT}" ' "copro ${COPRO_CUBE_DIR} " "${TARGET} ${COPRO_MCU_FAMILY} " "--cube_ver=${COPRO_CUBE_VERSION} " diff --git a/scripts/fbt_tools/fbt_extapps.py b/scripts/fbt_tools/fbt_extapps.py index 38c943cc57a..fb4dc2f1634 100644 --- a/scripts/fbt_tools/fbt_extapps.py +++ b/scripts/fbt_tools/fbt_extapps.py @@ -1,15 +1,18 @@ -import shutil from SCons.Builder import Builder from SCons.Action import Action from SCons.Errors import UserError import SCons.Warnings -import os -import pathlib from fbt.elfmanifest import assemble_manifest_data from fbt.appmanifest import FlipperApplication, FlipperManifestException from fbt.sdk.cache import SdkCache +from fbt.util import extract_abs_dir_path + +import os +import pathlib import itertools +import shutil + from ansi.color import fg @@ -62,7 +65,7 @@ def legacy_app_build_stub(**kw): lib_src_root_path = os.path.join(app_work_dir, "lib", lib_def.name) app_env.AppendUnique( CPPPATH=list( - app_env.Dir(lib_src_root_path).Dir(incpath).srcnode() + app_env.Dir(lib_src_root_path).Dir(incpath).srcnode().rfile().abspath for incpath in lib_def.fap_include_paths ), ) @@ -82,7 +85,12 @@ def legacy_app_build_stub(**kw): *lib_def.cflags, ], CPPDEFINES=lib_def.cdefines, - CPPPATH=list(map(app._appdir.Dir, lib_def.cincludes)), + CPPPATH=list( + map( + lambda cpath: extract_abs_dir_path(app._appdir.Dir(cpath)), + lib_def.cincludes, + ) + ), ) lib = private_lib_env.StaticLibrary( @@ -157,7 +165,6 @@ def prepare_app_metadata(target, source, env): app = env["APP"] meta_file_name = source[0].path + ".meta" with open(meta_file_name, "wb") as f: - # f.write(f"hello this is {app}") f.write( assemble_manifest_data( app_manifest=app, @@ -236,7 +243,10 @@ def fap_dist_action(target, source, env): def generate(env, **kw): - env.SetDefault(EXT_APPS_WORK_DIR=kw.get("EXT_APPS_WORK_DIR")) + env.SetDefault( + EXT_APPS_WORK_DIR=kw.get("EXT_APPS_WORK_DIR"), + APP_RUN_SCRIPT="${FBT_SCRIPT_DIR}/runfap.py", + ) if not env["VERBOSE"]: env.SetDefault( diff --git a/scripts/fbt_tools/fbt_sdk.py b/scripts/fbt_tools/fbt_sdk.py index f1f55bdb819..c46346b650f 100644 --- a/scripts/fbt_tools/fbt_sdk.py +++ b/scripts/fbt_tools/fbt_sdk.py @@ -46,7 +46,9 @@ def prebuild_sdk_emitter(target, source, env): def prebuild_sdk_create_origin_file(target, source, env): mega_file = env.subst("${TARGET}.c", target=target[0]) with open(mega_file, "wt") as sdk_c: - sdk_c.write("\n".join(f"#include <{h.path}>" for h in env["SDK_HEADERS"])) + sdk_c.write( + "\n".join(f"#include <{h.srcnode().path}>" for h in env["SDK_HEADERS"]) + ) class SdkMeta: @@ -62,18 +64,25 @@ def save_to(self, json_manifest_path: str): "cc_args": self._wrap_scons_vars("$CCFLAGS $_CCCOMCOM"), "cpp_args": self._wrap_scons_vars("$CXXFLAGS $CCFLAGS $_CCCOMCOM"), "linker_args": self._wrap_scons_vars("$LINKFLAGS"), - "linker_script": self.env.subst("${LINKER_SCRIPT_PATH}"), + "linker_libs": self.env.subst("${LIBS}"), + "app_ep_subst": self.env.subst("${APP_ENTRY}"), + "sdk_path_subst": self.env.subst("${SDK_DIR_SUBST}"), + "hardware": self.env.subst("${TARGET_HW}"), } with open(json_manifest_path, "wt") as f: json.dump(meta_contents, f, indent=4) def _wrap_scons_vars(self, vars: str): - expanded_vars = self.env.subst(vars, target=Entry("dummy")) + expanded_vars = self.env.subst( + vars, + target=Entry("dummy"), + ) return expanded_vars.replace("\\", "/") class SdkTreeBuilder: SDK_DIR_SUBST = "SDK_ROOT_DIR" + SDK_APP_EP_SUBST = "SDK_APP_EP_SUBST" def __init__(self, env, target, source) -> None: self.env = env @@ -87,6 +96,11 @@ def __init__(self, env, target, source) -> None: self.sdk_root_dir = target[0].Dir(".") self.sdk_deploy_dir = self.sdk_root_dir.Dir(self.target_sdk_dir_name) + self.sdk_env = self.env.Clone( + APP_ENTRY=self.SDK_APP_EP_SUBST, + SDK_DIR_SUBST=self.SDK_DIR_SUBST, + ) + def _parse_sdk_depends(self): deps_file = self.source[0] with open(deps_file.path, "rt") as deps_f: @@ -95,38 +109,36 @@ def _parse_sdk_depends(self): self.header_depends = list( filter(lambda fname: fname.endswith(".h"), depends.split()), ) - self.header_depends.append(self.env.subst("${LINKER_SCRIPT_PATH}")) - self.header_depends.append(self.env.subst("${SDK_DEFINITION}")) + self.header_depends.append(self.sdk_env.subst("${LINKER_SCRIPT_PATH}")) + self.header_depends.append(self.sdk_env.subst("${SDK_DEFINITION}")) self.header_dirs = sorted( set(map(os.path.normpath, map(os.path.dirname, self.header_depends))) ) def _generate_sdk_meta(self): - filtered_paths = [self.target_sdk_dir_name] + filtered_paths = ["."] full_fw_paths = list( map( os.path.normpath, - (self.env.Dir(inc_dir).relpath for inc_dir in self.env["CPPPATH"]), + ( + self.sdk_env.Dir(inc_dir).relpath + for inc_dir in self.sdk_env["CPPPATH"] + ), ) ) sdk_dirs = ", ".join(f"'{dir}'" for dir in self.header_dirs) filtered_paths.extend( - map( - self.build_sdk_file_path, - filter(lambda path: path in sdk_dirs, full_fw_paths), - ) + filter(lambda path: path in sdk_dirs, full_fw_paths), ) + filtered_paths = list(map(self.build_sdk_file_path, filtered_paths)) - sdk_env = self.env.Clone() - sdk_env.Replace( + self.sdk_env.Replace( CPPPATH=filtered_paths, - LINKER_SCRIPT=self.env.subst("${APP_LINKER_SCRIPT}"), ORIG_LINKER_SCRIPT_PATH=self.env["LINKER_SCRIPT_PATH"], LINKER_SCRIPT_PATH=self.build_sdk_file_path("${ORIG_LINKER_SCRIPT_PATH}"), ) - - meta = SdkMeta(sdk_env, self) + meta = SdkMeta(self.sdk_env, self) meta.save_to(self.target[0].path) def build_sdk_file_path(self, orig_path: str) -> str: @@ -211,7 +223,7 @@ def validate_sdk_cache(source, target, env): current_sdk = SdkCollector() current_sdk.process_source_file_for_sdk(source[0].path) for h in env["SDK_HEADERS"]: - current_sdk.add_header_to_sdk(pathlib.Path(h.path).as_posix()) + current_sdk.add_header_to_sdk(pathlib.Path(h.srcnode().path).as_posix()) sdk_cache = SdkCache(target[0].path) sdk_cache.validate_api(current_sdk.get_api()) diff --git a/scripts/fbt_tools/fbt_version.py b/scripts/fbt_tools/fbt_version.py index 909eea4f342..87497ca5f72 100644 --- a/scripts/fbt_tools/fbt_version.py +++ b/scripts/fbt_tools/fbt_version.py @@ -12,11 +12,14 @@ def version_emitter(target, source, env): def generate(env): + env.SetDefault( + VERSION_SCRIPT="${FBT_SCRIPT_DIR}/version.py", + ) env.Append( BUILDERS={ "VersionBuilder": Builder( action=Action( - '${PYTHON3} "${ROOT_DIR.abspath}/scripts/version.py" generate -t ${TARGET_HW} -o ${TARGET.dir.posix} --dir "${ROOT_DIR}"', + '${PYTHON3} "${VERSION_SCRIPT}" generate -t ${TARGET_HW} -o ${TARGET.dir.posix} --dir "${ROOT_DIR}"', "${VERSIONCOMSTR}", ), emitter=version_emitter, diff --git a/scripts/fbt_tools/fwbin.py b/scripts/fbt_tools/fwbin.py index 67e0d6450ec..f510c2a60c8 100644 --- a/scripts/fbt_tools/fwbin.py +++ b/scripts/fbt_tools/fwbin.py @@ -8,7 +8,8 @@ def generate(env): env.SetDefault( - BIN2DFU="${ROOT_DIR.abspath}/scripts/bin2dfu.py", + BIN2DFU="${FBT_SCRIPT_DIR}/bin2dfu.py", + BIN_SIZE_SCRIPT="${FBT_SCRIPT_DIR}/fwsize.py", OBJCOPY=__OBJCOPY_ARM_BIN, # FIXME NM=__NM_ARM_BIN, # FIXME ) diff --git a/scripts/flipper/app.py b/scripts/flipper/app.py index 9583560212c..30630a5f9f6 100644 --- a/scripts/flipper/app.py +++ b/scripts/flipper/app.py @@ -1,6 +1,7 @@ import logging import argparse import sys +import colorlog class App: @@ -10,7 +11,7 @@ def __init__(self, no_exit=False): self.parser = argparse.ArgumentParser() self.parser.add_argument("-d", "--debug", action="store_true", help="Debug") # Logging - self.logger = logging.getLogger() + self.logger = colorlog.getLogger() # Application specific initialization self.init() @@ -21,10 +22,17 @@ def __call__(self, args=None, skip_logger_init=False): self.log_level = logging.DEBUG if self.args.debug else logging.INFO self.logger.setLevel(self.log_level) if not self.logger.hasHandlers(): - self.handler = logging.StreamHandler(sys.stdout) + self.handler = colorlog.StreamHandler(sys.stdout) self.handler.setLevel(self.log_level) - self.formatter = logging.Formatter( - "%(asctime)s [%(levelname)s] %(message)s" + self.formatter = colorlog.ColoredFormatter( + "%(log_color)s%(asctime)s [%(levelname)s] %(message)s", + log_colors={ + "DEBUG": "cyan", + # "INFO": "white", + "WARNING": "yellow", + "ERROR": "red", + "CRITICAL": "red,bg_white", + }, ) self.handler.setFormatter(self.formatter) self.logger.addHandler(self.handler) diff --git a/scripts/sconsdist.py b/scripts/sconsdist.py index 7636c87bb11..b8f1d72b260 100644 --- a/scripts/sconsdist.py +++ b/scripts/sconsdist.py @@ -131,7 +131,9 @@ def copy(self) -> int: self.copy_single_project(project) self.logger.info( - fg.green(f"Firmware binaries can be found at:\n\t{self.output_dir_path}") + fg.boldgreen( + f"Firmware binaries can be found at:\n\t{self.output_dir_path}" + ) ) if self.args.version: @@ -167,7 +169,7 @@ def copy(self) -> int: if (bundle_result := UpdateMain(no_exit=True)(bundle_args)) == 0: self.logger.info( - fg.green( + fg.boldgreen( f"Use this directory to self-update your Flipper:\n\t{bundle_dir}" ) ) diff --git a/scripts/testing/await_flipper.py b/scripts/testing/await_flipper.py old mode 100644 new mode 100755 diff --git a/scripts/testing/units.py b/scripts/testing/units.py old mode 100644 new mode 100755 diff --git a/scripts/toolchain/fbtenv.cmd b/scripts/toolchain/fbtenv.cmd index a11a3ccd547..6e87bf95a50 100644 --- a/scripts/toolchain/fbtenv.cmd +++ b/scripts/toolchain/fbtenv.cmd @@ -13,19 +13,22 @@ if not [%FBT_NOENV%] == [] ( exit /b 0 ) -set "FLIPPER_TOOLCHAIN_VERSION=16" -set "FBT_TOOLCHAIN_ROOT=%FBT_ROOT%\toolchain\x86_64-windows" +set "FLIPPER_TOOLCHAIN_VERSION=17" +if [%FBT_TOOLCHAIN_ROOT%] == [] ( + set "FBT_TOOLCHAIN_ROOT=%FBT_ROOT%\toolchain\x86_64-windows" +) if not exist "%FBT_TOOLCHAIN_ROOT%" ( - powershell -ExecutionPolicy Bypass -File "%FBT_ROOT%\scripts\toolchain\windows-toolchain-download.ps1" "%flipper_toolchain_version%" + powershell -ExecutionPolicy Bypass -File "%FBT_ROOT%\scripts\toolchain\windows-toolchain-download.ps1" "%flipper_toolchain_version%" "%FBT_TOOLCHAIN_ROOT%" ) if not exist "%FBT_TOOLCHAIN_ROOT%\VERSION" ( - powershell -ExecutionPolicy Bypass -File "%FBT_ROOT%\scripts\toolchain\windows-toolchain-download.ps1" "%flipper_toolchain_version%" + powershell -ExecutionPolicy Bypass -File "%FBT_ROOT%\scripts\toolchain\windows-toolchain-download.ps1" "%flipper_toolchain_version%" "%FBT_TOOLCHAIN_ROOT%" ) + set /p REAL_TOOLCHAIN_VERSION=<"%FBT_TOOLCHAIN_ROOT%\VERSION" if not "%REAL_TOOLCHAIN_VERSION%" == "%FLIPPER_TOOLCHAIN_VERSION%" ( - powershell -ExecutionPolicy Bypass -File "%FBT_ROOT%\scripts\toolchain\windows-toolchain-download.ps1" "%flipper_toolchain_version%" + powershell -ExecutionPolicy Bypass -File "%FBT_ROOT%\scripts\toolchain\windows-toolchain-download.ps1" "%flipper_toolchain_version%" "%FBT_TOOLCHAIN_ROOT%" ) diff --git a/scripts/toolchain/fbtenv.sh b/scripts/toolchain/fbtenv.sh index 15f29e4dc67..d3fdb8ceaf6 100755 --- a/scripts/toolchain/fbtenv.sh +++ b/scripts/toolchain/fbtenv.sh @@ -5,7 +5,7 @@ # public variables DEFAULT_SCRIPT_PATH="$(pwd -P)"; SCRIPT_PATH="${SCRIPT_PATH:-$DEFAULT_SCRIPT_PATH}"; -FBT_TOOLCHAIN_VERSION="${FBT_TOOLCHAIN_VERSION:-"16"}"; +FBT_TOOLCHAIN_VERSION="${FBT_TOOLCHAIN_VERSION:-"17"}"; FBT_TOOLCHAIN_PATH="${FBT_TOOLCHAIN_PATH:-$SCRIPT_PATH}"; fbtenv_show_usage() @@ -62,7 +62,7 @@ fbtenv_check_sourced() fbtenv_show_usage; return 1; fi - case ${0##*/} in dash|-dash|bash|-bash|ksh|-ksh|sh|-sh|*.sh|fbt) + case ${0##*/} in dash|-dash|bash|-bash|ksh|-ksh|sh|-sh|*.sh|fbt|ufbt) return 0;; esac fbtenv_show_usage; @@ -76,8 +76,8 @@ fbtenv_chck_many_source() return 0; fi fi - echo "Warning! FBT environment script sourced more than once!"; - echo "This may signal that you are making mistakes, please open a new shell!"; + echo "Warning! FBT environment script was sourced more than once!"; + echo "You might be doing things wrong, please open a new shell!"; return 1; } @@ -93,8 +93,8 @@ fbtenv_set_shell_prompt() fbtenv_check_script_path() { - if [ ! -x "$SCRIPT_PATH/fbt" ]; then - echo "Please source this script being into flipperzero-firmware root directory, or specify 'SCRIPT_PATH' manually"; + if [ ! -x "$SCRIPT_PATH/fbt" ] && [ ! -x "$SCRIPT_PATH/ufbt" ] ; then + echo "Please source this script from [u]fbt root directory, or specify 'SCRIPT_PATH' variable manually"; echo "Example:"; printf "\tSCRIPT_PATH=lang/c/flipperzero-firmware source lang/c/flipperzero-firmware/scripts/fbtenv.sh\n"; echo "If current directory is right, type 'unset SCRIPT_PATH' and try again" @@ -108,7 +108,7 @@ fbtenv_get_kernel_type() SYS_TYPE="$(uname -s)"; ARCH_TYPE="$(uname -m)"; if [ "$ARCH_TYPE" != "x86_64" ] && [ "$SYS_TYPE" != "Darwin" ]; then - echo "Now we provide toolchain only for x86_64 arhitecture, sorry.."; + echo "We only provide toolchain for x86_64 CPUs, sorry.."; return 1; fi if [ "$SYS_TYPE" = "Darwin" ]; then @@ -119,10 +119,10 @@ fbtenv_get_kernel_type() TOOLCHAIN_ARCH_DIR="$FBT_TOOLCHAIN_PATH/toolchain/x86_64-linux"; TOOLCHAIN_URL="https://update.flipperzero.one/builds/toolchain/gcc-arm-none-eabi-10.3-x86_64-linux-flipper-$FBT_TOOLCHAIN_VERSION.tar.gz"; elif echo "$SYS_TYPE" | grep -q "MINGW"; then - echo "In MinGW shell use \"fbt.cmd\" instead of \"fbt\""; + echo "In MinGW shell use \"[u]fbt.cmd\" instead of \"[u]fbt\""; return 1; else - echo "Your system is not recognized. Sorry.. Please report us your configuration."; + echo "Your system configuration is not supported. Sorry.. Please report us your configuration."; return 1; fi return 0; @@ -142,7 +142,7 @@ fbtenv_check_rosetta() fbtenv_check_tar() { - printf "Checking tar.."; + printf "Checking for tar.."; if ! tar --version > /dev/null 2>&1; then echo "no"; return 1; @@ -153,7 +153,7 @@ fbtenv_check_tar() fbtenv_check_downloaded_toolchain() { - printf "Checking downloaded toolchain tgz.."; + printf "Checking if downloaded toolchain tgz exists.."; if [ ! -f "$FBT_TOOLCHAIN_PATH/toolchain/$TOOLCHAIN_TAR" ]; then echo "no"; return 1; @@ -204,7 +204,7 @@ fbtenv_unpack_toolchain() fbtenv_clearing() { - printf "Clearing.."; + printf "Cleaning up.."; if [ -n "${FBT_TOOLCHAIN_PATH:-""}" ]; then rm -rf "${FBT_TOOLCHAIN_PATH:?}/toolchain/"*.tar.gz; rm -rf "${FBT_TOOLCHAIN_PATH:?}/toolchain/"*.part; diff --git a/scripts/toolchain/windows-toolchain-download.ps1 b/scripts/toolchain/windows-toolchain-download.ps1 index aaed89856cc..c96bb119c88 100644 --- a/scripts/toolchain/windows-toolchain-download.ps1 +++ b/scripts/toolchain/windows-toolchain-download.ps1 @@ -1,34 +1,46 @@ Set-StrictMode -Version 2.0 $ErrorActionPreference = "Stop" [Net.ServicePointManager]::SecurityProtocol = "tls12, tls11, tls" -$repo_root = (Get-Item "$PSScriptRoot\..\..").FullName +# TODO: fix +$download_dir = (Get-Item "$PSScriptRoot\..\..").FullName $toolchain_version = $args[0] +$toolchain_target_path = $args[1] + $toolchain_url = "https://update.flipperzero.one/builds/toolchain/gcc-arm-none-eabi-10.3-x86_64-windows-flipper-$toolchain_version.zip" -$toolchain_zip = "gcc-arm-none-eabi-10.3-x86_64-windows-flipper-$toolchain_version.zip" -$toolchain_dir = "gcc-arm-none-eabi-10.3-x86_64-windows-flipper" +$toolchain_dist_folder = "gcc-arm-none-eabi-10.3-x86_64-windows-flipper" +$toolchain_zip = "$toolchain_dist_folder-$toolchain_version.zip" + +$toolchain_zip_temp_path = "$download_dir\$toolchain_zip" +$toolchain_dist_temp_path = "$download_dir\$toolchain_dist_folder" -if (Test-Path -LiteralPath "$repo_root\toolchain\x86_64-windows") { +if (Test-Path -LiteralPath "$toolchain_target_path") { Write-Host -NoNewline "Removing old Windows toolchain.." - Remove-Item -LiteralPath "$repo_root\toolchain\x86_64-windows" -Force -Recurse + Remove-Item -LiteralPath "$toolchain_target_path" -Force -Recurse Write-Host "done!" } -if (!(Test-Path -Path "$repo_root\$toolchain_zip" -PathType Leaf)) { +if (!(Test-Path -Path "$toolchain_zip_temp_path" -PathType Leaf)) { Write-Host -NoNewline "Downloading Windows toolchain.." $wc = New-Object net.webclient - $wc.Downloadfile("$toolchain_url", "$repo_root\$toolchain_zip") + $wc.Downloadfile("$toolchain_url", "$toolchain_zip_temp_path") Write-Host "done!" } -if (!(Test-Path -LiteralPath "$repo_root\toolchain")) { - New-Item "$repo_root\toolchain" -ItemType Directory +if (!(Test-Path -LiteralPath "$toolchain_target_path\..")) { + New-Item "$toolchain_target_path\.." -ItemType Directory -Force } Write-Host -NoNewline "Extracting Windows toolchain.." +# This is faster than Expand-Archive Add-Type -Assembly "System.IO.Compression.Filesystem" -[System.IO.Compression.ZipFile]::ExtractToDirectory("$repo_root\$toolchain_zip", "$repo_root\") -Move-Item -Path "$repo_root\$toolchain_dir" -Destination "$repo_root\toolchain\x86_64-windows" +[System.IO.Compression.ZipFile]::ExtractToDirectory("$toolchain_zip_temp_path", "$download_dir") +# Expand-Archive -LiteralPath "$toolchain_zip_temp_path" -DestinationPath "$download_dir" + +Write-Host -NoNewline "moving.." +Move-Item -LiteralPath "$toolchain_dist_temp_path" -Destination "$toolchain_target_path" Write-Host "done!" Write-Host -NoNewline "Cleaning up temporary files.." -Remove-Item -LiteralPath "$repo_root\$toolchain_zip" -Force +Remove-Item -LiteralPath "$toolchain_zip_temp_path" -Force Write-Host "done!" + +# dasdasd \ No newline at end of file diff --git a/site_scons/cc.scons b/site_scons/cc.scons index c923b387226..1eb6a33768e 100644 --- a/site_scons/cc.scons +++ b/site_scons/cc.scons @@ -30,10 +30,9 @@ ENV.AppendUnique( "-ffunction-sections", "-fsingle-precision-constant", "-fno-math-errno", - "-fstack-usage", + # Generates .su files with stack usage information + # "-fstack-usage", "-g", - # "-Wno-stringop-overread", - # "-Wno-stringop-overflow", ], CPPDEFINES=[ "_GNU_SOURCE", diff --git a/site_scons/environ.scons b/site_scons/environ.scons index 94705dada41..96424caadca 100644 --- a/site_scons/environ.scons +++ b/site_scons/environ.scons @@ -1,5 +1,10 @@ from SCons.Platform import TempFileMunge -from fbt.util import tempfile_arg_esc_func, single_quote, wrap_tempfile +from fbt.util import ( + tempfile_arg_esc_func, + single_quote, + wrap_tempfile, + extract_abs_dir_path, +) import os import multiprocessing @@ -52,6 +57,12 @@ coreenv = VAR_ENV.Clone( MAXLINELENGTH=2048, PROGSUFFIX=".elf", ENV=forward_os_env, + SINGLEQUOTEFUNC=single_quote, + ABSPATHGETTERFUNC=extract_abs_dir_path, + # Setting up temp file parameters - to overcome command line length limits + TEMPFILEARGESCFUNC=tempfile_arg_esc_func, + FBT_SCRIPT_DIR=Dir("#/scripts"), + ROOT_DIR=Dir("#"), ) # If DIST_SUFFIX is set in environment, is has precedence (set by CI) @@ -60,24 +71,6 @@ if os_suffix := os.environ.get("DIST_SUFFIX", None): DIST_SUFFIX=os_suffix, ) -# print(coreenv.Dump()) -if not coreenv["VERBOSE"]: - coreenv.SetDefault( - CCCOMSTR="\tCC\t${SOURCE}", - CXXCOMSTR="\tCPP\t${SOURCE}", - ASCOMSTR="\tASM\t${SOURCE}", - ARCOMSTR="\tAR\t${TARGET}", - RANLIBCOMSTR="\tRANLIB\t${TARGET}", - LINKCOMSTR="\tLINK\t${TARGET}", - INSTALLSTR="\tINSTALL\t${TARGET}", - APPSCOMSTR="\tAPPS\t${TARGET}", - VERSIONCOMSTR="\tVERSION\t${TARGET}", - STRIPCOMSTR="\tSTRIP\t${TARGET}", - OBJDUMPCOMSTR="\tOBJDUMP\t${TARGET}", - # GDBCOMSTR="\tGDB\t${SOURCE}", - # GDBPYCOMSTR="\tGDB-PY\t${SOURCE}", - ) - # Default value for commandline options SetOption("num_jobs", multiprocessing.cpu_count()) @@ -90,12 +83,7 @@ SetOption("max_drift", 1) # Random task queue - to discover isses with build logic faster # SetOption("random", 1) - -# Setting up temp file parameters - to overcome command line length limits -coreenv["TEMPFILEARGESCFUNC"] = tempfile_arg_esc_func wrap_tempfile(coreenv, "LINKCOM") wrap_tempfile(coreenv, "ARCOM") -coreenv["SINGLEQUOTEFUNC"] = single_quote - Return("coreenv") diff --git a/site_scons/extapps.scons b/site_scons/extapps.scons index 90d228e585c..bb1d65ffc52 100644 --- a/site_scons/extapps.scons +++ b/site_scons/extapps.scons @@ -3,10 +3,9 @@ from SCons.Errors import UserError Import("ENV") - from fbt.appmanifest import FlipperAppType -appenv = ENV.Clone( +appenv = ENV["APPENV"] = ENV.Clone( tools=[ ( "fbt_extapps", @@ -17,6 +16,7 @@ appenv = ENV.Clone( }, ), "fbt_assets", + "fbt_sdk", ] ) @@ -66,6 +66,7 @@ extapps = appenv["_extapps"] = { "validators": {}, "dist": {}, "resources_dist": None, + "sdk_tree": None, } @@ -115,10 +116,41 @@ if appsrc := appenv.subst("$APPSRC"): app_manifest, fap_file, app_validator = appenv.GetExtAppFromPath(appsrc) appenv.PhonyTarget( "launch_app", - '${PYTHON3} scripts/runfap.py ${SOURCE} --fap_dst_dir "/ext/apps/${FAP_CATEGORY}"', + '${PYTHON3} "${APP_RUN_SCRIPT}" ${SOURCE} --fap_dst_dir "/ext/apps/${FAP_CATEGORY}"', source=fap_file, FAP_CATEGORY=app_manifest.fap_category, ) appenv.Alias("launch_app", app_validator) +# SDK management + +sdk_origin_path = "${BUILD_DIR}/sdk_origin" +sdk_source = appenv.SDKPrebuilder( + sdk_origin_path, + # Deps on root SDK headers and generated files + (appenv["SDK_HEADERS"], appenv["FW_ASSETS_HEADERS"]), +) +# Extra deps on headers included in deeper levels +Depends(sdk_source, appenv.ProcessSdkDepends(f"{sdk_origin_path}.d")) + +appenv["SDK_DIR"] = appenv.Dir("${BUILD_DIR}/sdk") +sdk_tree = extapps["sdk_tree"] = appenv.SDKTree(appenv["SDK_DIR"], sdk_origin_path) +# AlwaysBuild(sdk_tree) +Alias("sdk_tree", sdk_tree) + +sdk_apicheck = appenv.SDKSymUpdater(appenv["SDK_DEFINITION"], sdk_origin_path) +Precious(sdk_apicheck) +NoClean(sdk_apicheck) +AlwaysBuild(sdk_apicheck) +Alias("sdk_check", sdk_apicheck) + +sdk_apisyms = appenv.SDKSymGenerator( + "${BUILD_DIR}/assets/compiled/symbols.h", appenv["SDK_DEFINITION"] +) +Alias("api_syms", sdk_apisyms) + +if appenv["FORCE"]: + appenv.AlwaysBuild(sdk_source, sdk_tree, sdk_apicheck, sdk_apisyms) + + Return("extapps") From a09d0a8bd42d727fe4b686b761cb8012dc99703a Mon Sep 17 00:00:00 2001 From: Konstantin Volkov <72250702+doomwastaken@users.noreply.github.com> Date: Wed, 2 Nov 2022 18:21:43 +0300 Subject: [PATCH 193/824] fixed job name, renamed compile step id (#1952) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- .github/workflows/unit_tests.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index a7671f0f9b2..1ca4a9c03f9 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -8,7 +8,7 @@ env: DEFAULT_TARGET: f7 jobs: - main: + run_units_on_test_bench: runs-on: [self-hosted, FlipperZeroTest] steps: - name: Checkout code @@ -22,14 +22,14 @@ jobs: run: | echo "flipper=/dev/ttyACM0" >> $GITHUB_OUTPUT - - name: 'Compile unit tests firmware' - id: compile + - name: 'Flash unit tests firmware' + id: flashing run: | FBT_TOOLCHAIN_PATH=/opt ./fbt flash OPENOCD_ADAPTER_SERIAL=2A0906016415303030303032 FIRMWARE_APP_SET=unit_tests FORCE=1 - name: 'Wait for flipper to finish updating' id: connect - if: steps.compile.outcome == 'success' + if: steps.flashing.outcome == 'success' run: | . scripts/toolchain/fbtenv.sh ./scripts/testing/await_flipper.py ${{steps.device.outputs.flipper}} From c417d467f72b63b25f7dce0eee6b3554fd2d8d0d Mon Sep 17 00:00:00 2001 From: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> Date: Wed, 2 Nov 2022 19:13:06 +0300 Subject: [PATCH 194/824] Handle storage full error (#1958) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- applications/services/rpc/rpc_storage.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/applications/services/rpc/rpc_storage.c b/applications/services/rpc/rpc_storage.c index 1b545b41417..e28998e135c 100644 --- a/applications/services/rpc/rpc_storage.c +++ b/applications/services/rpc/rpc_storage.c @@ -405,6 +405,10 @@ static void rpc_system_storage_write_process(const PB_Main* request, void* conte if(!fs_operation_success) { send_response = true; command_status = rpc_system_storage_get_file_error(file); + if(command_status == PB_CommandStatus_OK) { + // Report errors not handled by underlying APIs + command_status = PB_CommandStatus_ERROR_STORAGE_INTERNAL; + } } if(send_response) { From 0652830c51cee5fced1d4651c538d24160599dfa Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Wed, 2 Nov 2022 20:24:07 +0400 Subject: [PATCH 195/824] [FL-2940] WS: add protocol Ambient_Weather (#1960) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * WS: add protocol Ambient_Weather * WS: fix link * WS: removing unused code Co-authored-by: あく --- .../helpers/weather_station_types.h | 2 +- .../protocols/acurite_592txr.c | 2 +- .../protocols/ambient_weather.c | 278 ++++++++++++++++++ .../protocols/ambient_weather.h | 79 +++++ .../weather_station/protocols/gt_wt_03.c | 2 +- .../protocols/lacrosse_tx141thbv2.c | 2 +- .../weather_station/protocols/nexus_th.c | 2 +- .../protocols/protocol_items.c | 1 + .../protocols/protocol_items.h | 1 + 9 files changed, 364 insertions(+), 5 deletions(-) create mode 100644 applications/plugins/weather_station/protocols/ambient_weather.c create mode 100644 applications/plugins/weather_station/protocols/ambient_weather.h diff --git a/applications/plugins/weather_station/helpers/weather_station_types.h b/applications/plugins/weather_station/helpers/weather_station_types.h index 2976cbce8f6..5a66dd0ce40 100644 --- a/applications/plugins/weather_station/helpers/weather_station_types.h +++ b/applications/plugins/weather_station/helpers/weather_station_types.h @@ -3,7 +3,7 @@ #include #include -#define WS_VERSION_APP "0.3.1" +#define WS_VERSION_APP "0.4" #define WS_DEVELOPED "SkorP" #define WS_GITHUB "https://github.com/flipperdevices/flipperzero-firmware" diff --git a/applications/plugins/weather_station/protocols/acurite_592txr.c b/applications/plugins/weather_station/protocols/acurite_592txr.c index db05af09555..4d7f5954496 100644 --- a/applications/plugins/weather_station/protocols/acurite_592txr.c +++ b/applications/plugins/weather_station/protocols/acurite_592txr.c @@ -4,7 +4,7 @@ /* * Help - * https://github.com/merbanan/rtl_433/blob/5bef4e43133ac4c0e2d18d36f87c52b4f9458453/src/devices/acurite.c + * https://github.com/merbanan/rtl_433/blob/master/src/devices/acurite.c * * Acurite 592TXR Temperature Humidity sensor decoder * Message Type 0x04, 7 bytes diff --git a/applications/plugins/weather_station/protocols/ambient_weather.c b/applications/plugins/weather_station/protocols/ambient_weather.c new file mode 100644 index 00000000000..07f5330fcae --- /dev/null +++ b/applications/plugins/weather_station/protocols/ambient_weather.c @@ -0,0 +1,278 @@ +#include "ambient_weather.h" +#include + +#define TAG "WSProtocolAmbient_Weather" + +/* + * Help + * https://github.com/merbanan/rtl_433/blob/master/src/devices/ambient_weather.c + * + * Decode Ambient Weather F007TH, F012TH, TF 30.3208.02, SwitchDoc F016TH. + * Devices supported: + * - Ambient Weather F007TH Thermo-Hygrometer. + * - Ambient Weather F012TH Indoor/Display Thermo-Hygrometer. + * - TFA senders 30.3208.02 from the TFA "Klima-Monitor" 30.3054, + * - SwitchDoc Labs F016TH. + * This decoder handles the 433mhz/868mhz thermo-hygrometers. + * The 915mhz (WH*) family of devices use different modulation/encoding. + * Byte 0 Byte 1 Byte 2 Byte 3 Byte 4 Byte 5 + * xxxxMMMM IIIIIIII BCCCTTTT TTTTTTTT HHHHHHHH MMMMMMMM + * - x: Unknown 0x04 on F007TH/F012TH + * - M: Model Number?, 0x05 on F007TH/F012TH/SwitchDocLabs F016TH + * - I: ID byte (8 bits), volatie, changes at power up, + * - B: Battery Low + * - C: Channel (3 bits 1-8) - F007TH set by Dip switch, F012TH soft setting + * - T: Temperature 12 bits - Fahrenheit * 10 + 400 + * - H: Humidity (8 bits) + * - M: Message integrity check LFSR Digest-8, gen 0x98, key 0x3e, init 0x64 + * + * three repeats without gap + * full preamble is 0x00145 (the last bits might not be fixed, e.g. 0x00146) + * and on decoding also 0xffd45 + */ + +#define AMBIENT_WEATHER_PACKET_HEADER_1 0xFFD440000000000 //0xffd45 .. 0xffd46 +#define AMBIENT_WEATHER_PACKET_HEADER_2 0x001440000000000 //0x00145 .. 0x00146 +#define AMBIENT_WEATHER_PACKET_HEADER_MASK 0xFFFFC0000000000 + +static const SubGhzBlockConst ws_protocol_ambient_weather_const = { + .te_short = 500, + .te_long = 1000, + .te_delta = 120, + .min_count_bit_for_found = 48, +}; + +struct WSProtocolDecoderAmbient_Weather { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + WSBlockGeneric generic; + ManchesterState manchester_saved_state; + uint16_t header_count; +}; + +struct WSProtocolEncoderAmbient_Weather { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + WSBlockGeneric generic; +}; + +const SubGhzProtocolDecoder ws_protocol_ambient_weather_decoder = { + .alloc = ws_protocol_decoder_ambient_weather_alloc, + .free = ws_protocol_decoder_ambient_weather_free, + + .feed = ws_protocol_decoder_ambient_weather_feed, + .reset = ws_protocol_decoder_ambient_weather_reset, + + .get_hash_data = ws_protocol_decoder_ambient_weather_get_hash_data, + .serialize = ws_protocol_decoder_ambient_weather_serialize, + .deserialize = ws_protocol_decoder_ambient_weather_deserialize, + .get_string = ws_protocol_decoder_ambient_weather_get_string, +}; + +const SubGhzProtocolEncoder ws_protocol_ambient_weather_encoder = { + .alloc = NULL, + .free = NULL, + + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol ws_protocol_ambient_weather = { + .name = WS_PROTOCOL_AMBIENT_WEATHER_NAME, + .type = SubGhzProtocolWeatherStation, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable, + + .decoder = &ws_protocol_ambient_weather_decoder, + .encoder = &ws_protocol_ambient_weather_encoder, +}; + +void* ws_protocol_decoder_ambient_weather_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + WSProtocolDecoderAmbient_Weather* instance = malloc(sizeof(WSProtocolDecoderAmbient_Weather)); + instance->base.protocol = &ws_protocol_ambient_weather; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void ws_protocol_decoder_ambient_weather_free(void* context) { + furi_assert(context); + WSProtocolDecoderAmbient_Weather* instance = context; + free(instance); +} + +void ws_protocol_decoder_ambient_weather_reset(void* context) { + furi_assert(context); + WSProtocolDecoderAmbient_Weather* instance = context; + manchester_advance( + instance->manchester_saved_state, + ManchesterEventReset, + &instance->manchester_saved_state, + NULL); +} + +static bool ws_protocol_ambient_weather_check_crc(WSProtocolDecoderAmbient_Weather* instance) { + uint8_t msg[] = { + instance->decoder.decode_data >> 40, + instance->decoder.decode_data >> 32, + instance->decoder.decode_data >> 24, + instance->decoder.decode_data >> 16, + instance->decoder.decode_data >> 8}; + + uint8_t crc = subghz_protocol_blocks_lfsr_digest8(msg, 5, 0x98, 0x3e) ^ 0x64; + return (crc == (uint8_t)(instance->decoder.decode_data & 0xFF)); +} + +/** + * Analysis of received data + * @param instance Pointer to a WSBlockGeneric* instance + */ +static void ws_protocol_ambient_weather_remote_controller(WSBlockGeneric* instance) { + instance->id = (instance->data >> 32) & 0xFF; + instance->battery_low = (instance->data >> 31) & 1; + instance->channel = ((instance->data >> 28) & 0x07) + 1; + instance->temp = ws_block_generic_fahrenheit_to_celsius( + ((float)((instance->data >> 16) & 0x0FFF) - 400.0f) / 10.0f); + instance->humidity = (instance->data >> 8) & 0xFF; + instance->btn = WS_NO_BTN; + + // ToDo maybe it won't be needed + /* + Sanity checks to reduce false positives and other bad data + Packets with Bad data often pass the MIC check. + - humidity > 100 (such as 255) and + - temperatures > 140 F (such as 369.5 F and 348.8 F + Specs in the F007TH and F012TH manuals state the range is: + - Temperature: -40 to 140 F + - Humidity: 10 to 99% + @todo - sanity check b[0] "model number" + - 0x45 - F007TH and F012TH + - 0x?5 - SwitchDocLabs F016TH temperature sensor (based on comment b[0] & 0x0f == 5) + - ? - TFA 30.3208.02 + if (instance->humidity < 0 || instance->humidity > 100) { + ERROR; + } + + if (instance->temp < -40.0 || instance->temp > 140.0) { + ERROR; + } + */ +} + +void ws_protocol_decoder_ambient_weather_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + WSProtocolDecoderAmbient_Weather* instance = context; + + ManchesterEvent event = ManchesterEventReset; + if(!level) { + if(DURATION_DIFF(duration, ws_protocol_ambient_weather_const.te_short) < + ws_protocol_ambient_weather_const.te_delta) { + event = ManchesterEventShortLow; + } else if( + DURATION_DIFF(duration, ws_protocol_ambient_weather_const.te_long) < + ws_protocol_ambient_weather_const.te_delta * 2) { + event = ManchesterEventLongLow; + } + } else { + if(DURATION_DIFF(duration, ws_protocol_ambient_weather_const.te_short) < + ws_protocol_ambient_weather_const.te_delta) { + event = ManchesterEventShortHigh; + } else if( + DURATION_DIFF(duration, ws_protocol_ambient_weather_const.te_long) < + ws_protocol_ambient_weather_const.te_delta * 2) { + event = ManchesterEventLongHigh; + } + } + if(event != ManchesterEventReset) { + bool data; + bool data_ok = manchester_advance( + instance->manchester_saved_state, event, &instance->manchester_saved_state, &data); + + if(data_ok) { + instance->decoder.decode_data = (instance->decoder.decode_data << 1) | !data; + } + + if(((instance->decoder.decode_data & AMBIENT_WEATHER_PACKET_HEADER_MASK) == + AMBIENT_WEATHER_PACKET_HEADER_1) || + ((instance->decoder.decode_data & AMBIENT_WEATHER_PACKET_HEADER_MASK) == + AMBIENT_WEATHER_PACKET_HEADER_2)) { + if(ws_protocol_ambient_weather_check_crc(instance)) { + instance->decoder.decode_data = instance->decoder.decode_data; + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = + ws_protocol_ambient_weather_const.min_count_bit_for_found; + ws_protocol_ambient_weather_remote_controller(&instance->generic); + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } + } + } else { + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + manchester_advance( + instance->manchester_saved_state, + ManchesterEventReset, + &instance->manchester_saved_state, + NULL); + } +} + +uint8_t ws_protocol_decoder_ambient_weather_get_hash_data(void* context) { + furi_assert(context); + WSProtocolDecoderAmbient_Weather* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool ws_protocol_decoder_ambient_weather_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + WSProtocolDecoderAmbient_Weather* instance = context; + return ws_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +bool ws_protocol_decoder_ambient_weather_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + WSProtocolDecoderAmbient_Weather* instance = context; + bool ret = false; + do { + if(!ws_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if(instance->generic.data_count_bit != + ws_protocol_ambient_weather_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + ret = true; + } while(false); + return ret; +} + +void ws_protocol_decoder_ambient_weather_get_string(void* context, FuriString* output) { + furi_assert(context); + WSProtocolDecoderAmbient_Weather* instance = context; + furi_string_printf( + output, + "%s %dbit\r\n" + "Key:0x%lX%08lX\r\n" + "Sn:0x%lX Ch:%d Bat:%d\r\n" + "Temp:%d.%d C Hum:%d%%", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data >> 32), + (uint32_t)(instance->generic.data), + instance->generic.id, + instance->generic.channel, + instance->generic.battery_low, + (int16_t)instance->generic.temp, + abs(((int16_t)(instance->generic.temp * 10) - (((int16_t)instance->generic.temp) * 10))), + instance->generic.humidity); +} diff --git a/applications/plugins/weather_station/protocols/ambient_weather.h b/applications/plugins/weather_station/protocols/ambient_weather.h new file mode 100644 index 00000000000..04cc5819c27 --- /dev/null +++ b/applications/plugins/weather_station/protocols/ambient_weather.h @@ -0,0 +1,79 @@ +#pragma once + +#include + +#include +#include +#include +#include "ws_generic.h" +#include + +#define WS_PROTOCOL_AMBIENT_WEATHER_NAME "Ambient_Weather" + +typedef struct WSProtocolDecoderAmbient_Weather WSProtocolDecoderAmbient_Weather; +typedef struct WSProtocolEncoderAmbient_Weather WSProtocolEncoderAmbient_Weather; + +extern const SubGhzProtocolDecoder ws_protocol_ambient_weather_decoder; +extern const SubGhzProtocolEncoder ws_protocol_ambient_weather_encoder; +extern const SubGhzProtocol ws_protocol_ambient_weather; + +/** + * Allocate WSProtocolDecoderAmbient_Weather. + * @param environment Pointer to a SubGhzEnvironment instance + * @return WSProtocolDecoderAmbient_Weather* pointer to a WSProtocolDecoderAmbient_Weather instance + */ +void* ws_protocol_decoder_ambient_weather_alloc(SubGhzEnvironment* environment); + +/** + * Free WSProtocolDecoderAmbient_Weather. + * @param context Pointer to a WSProtocolDecoderAmbient_Weather instance + */ +void ws_protocol_decoder_ambient_weather_free(void* context); + +/** + * Reset decoder WSProtocolDecoderAmbient_Weather. + * @param context Pointer to a WSProtocolDecoderAmbient_Weather instance + */ +void ws_protocol_decoder_ambient_weather_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a WSProtocolDecoderAmbient_Weather instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void ws_protocol_decoder_ambient_weather_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a WSProtocolDecoderAmbient_Weather instance + * @return hash Hash sum + */ +uint8_t ws_protocol_decoder_ambient_weather_get_hash_data(void* context); + +/** + * Serialize data WSProtocolDecoderAmbient_Weather. + * @param context Pointer to a WSProtocolDecoderAmbient_Weather instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool ws_protocol_decoder_ambient_weather_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data WSProtocolDecoderAmbient_Weather. + * @param context Pointer to a WSProtocolDecoderAmbient_Weather instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool ws_protocol_decoder_ambient_weather_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a WSProtocolDecoderAmbient_Weather instance + * @param output Resulting text + */ +void ws_protocol_decoder_ambient_weather_get_string(void* context, FuriString* output); diff --git a/applications/plugins/weather_station/protocols/gt_wt_03.c b/applications/plugins/weather_station/protocols/gt_wt_03.c index 1492374c9d3..04bca9ac17e 100644 --- a/applications/plugins/weather_station/protocols/gt_wt_03.c +++ b/applications/plugins/weather_station/protocols/gt_wt_03.c @@ -4,7 +4,7 @@ /* * Help - * https://github.com/merbanan/rtl_433/blob/5f0ff6db624270a4598958ab9dd79bb385ced3ef/src/devices/gt_wt_03.c + * https://github.com/merbanan/rtl_433/blob/master/src/devices/gt_wt_03.c * * * Globaltronics GT-WT-03 sensor on 433.92MHz. diff --git a/applications/plugins/weather_station/protocols/lacrosse_tx141thbv2.c b/applications/plugins/weather_station/protocols/lacrosse_tx141thbv2.c index 828d49be78a..d4b89be874f 100644 --- a/applications/plugins/weather_station/protocols/lacrosse_tx141thbv2.c +++ b/applications/plugins/weather_station/protocols/lacrosse_tx141thbv2.c @@ -4,7 +4,7 @@ /* * Help - * https://github.com/merbanan/rtl_433/blob/7e83cfd27d14247b6c3c81732bfe4a4f9a974d30/src/devices/lacrosse_tx141x.c + * https://github.com/merbanan/rtl_433/blob/master/src/devices/lacrosse_tx141x.c * * iiii iiii | bkcc tttt | tttt tttt | hhhh hhhh | cccc cccc | u * - i: identification; changes on battery switch diff --git a/applications/plugins/weather_station/protocols/nexus_th.c b/applications/plugins/weather_station/protocols/nexus_th.c index a2ab0f412b4..c3d823edaec 100644 --- a/applications/plugins/weather_station/protocols/nexus_th.c +++ b/applications/plugins/weather_station/protocols/nexus_th.c @@ -4,7 +4,7 @@ /* * Help - * https://github.com/merbanan/rtl_433/blob/ef2d37cf51e3264d11cde9149ef87de2f0a4d37a/src/devices/nexus.c + * https://github.com/merbanan/rtl_433/blob/master/src/devices/nexus.c * * Nexus sensor protocol with ID, temperature and optional humidity * also FreeTec (Pearl) NC-7345 sensors for FreeTec Weatherstation NC-7344, diff --git a/applications/plugins/weather_station/protocols/protocol_items.c b/applications/plugins/weather_station/protocols/protocol_items.c index 3ec9e995a4a..d7f6458abea 100644 --- a/applications/plugins/weather_station/protocols/protocol_items.c +++ b/applications/plugins/weather_station/protocols/protocol_items.c @@ -9,6 +9,7 @@ const SubGhzProtocol* weather_station_protocol_registry_items[] = { &ws_protocol_lacrosse_tx141thbv2, &ws_protocol_oregon2, &ws_protocol_acurite_592txr, + &ws_protocol_ambient_weather, }; const SubGhzProtocolRegistry weather_station_protocol_registry = { diff --git a/applications/plugins/weather_station/protocols/protocol_items.h b/applications/plugins/weather_station/protocols/protocol_items.h index 8f3eb53d789..76c085ab471 100644 --- a/applications/plugins/weather_station/protocols/protocol_items.h +++ b/applications/plugins/weather_station/protocols/protocol_items.h @@ -9,5 +9,6 @@ #include "lacrosse_tx141thbv2.h" #include "oregon2.h" #include "acurite_592txr.h" +#include "ambient_weather.h" extern const SubGhzProtocolRegistry weather_station_protocol_registry; From 95182b266cfd6fc1d81ec1ff4019bfcca930e1cb Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Thu, 3 Nov 2022 07:42:54 +0300 Subject: [PATCH 196/824] BadUSB scrolllock typo fix (#1968) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- applications/main/bad_usb/bad_usb_script.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/main/bad_usb/bad_usb_script.c b/applications/main/bad_usb/bad_usb_script.c index 8ff38ef66e7..b90218f8d2f 100644 --- a/applications/main/bad_usb/bad_usb_script.c +++ b/applications/main/bad_usb/bad_usb_script.c @@ -82,7 +82,7 @@ static const DuckyKey ducky_keys[] = { {"PAGEUP", HID_KEYBOARD_PAGE_UP}, {"PAGEDOWN", HID_KEYBOARD_PAGE_DOWN}, {"PRINTSCREEN", HID_KEYBOARD_PRINT_SCREEN}, - {"SCROLLOCK", HID_KEYBOARD_SCROLL_LOCK}, + {"SCROLLLOCK", HID_KEYBOARD_SCROLL_LOCK}, {"SPACE", HID_KEYBOARD_SPACEBAR}, {"TAB", HID_KEYBOARD_TAB}, {"MENU", HID_KEYBOARD_APPLICATION}, From eee90c6c406fef114be9b2250936c5e36f0a46cd Mon Sep 17 00:00:00 2001 From: head47 <63517545+head47@users.noreply.github.com> Date: Thu, 3 Nov 2022 08:21:44 +0300 Subject: [PATCH 197/824] Run Bad USB immediately after connection (#1955) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- applications/main/bad_usb/bad_usb_script.c | 31 ++++++++++++++++++- applications/main/bad_usb/bad_usb_script.h | 1 + .../main/bad_usb/views/bad_usb_view.c | 10 +++++- 3 files changed, 40 insertions(+), 2 deletions(-) diff --git a/applications/main/bad_usb/bad_usb_script.c b/applications/main/bad_usb/bad_usb_script.c index b90218f8d2f..33b3f50303a 100644 --- a/applications/main/bad_usb/bad_usb_script.c +++ b/applications/main/bad_usb/bad_usb_script.c @@ -524,12 +524,16 @@ static int32_t bad_usb_worker(void* context) { } else if(worker_state == BadUsbStateNotConnected) { // State: USB not connected uint32_t flags = furi_thread_flags_wait( - WorkerEvtEnd | WorkerEvtConnect, FuriFlagWaitAny, FuriWaitForever); + WorkerEvtEnd | WorkerEvtConnect | WorkerEvtToggle, + FuriFlagWaitAny, + FuriWaitForever); furi_check((flags & FuriFlagError) == 0); if(flags & WorkerEvtEnd) { break; } else if(flags & WorkerEvtConnect) { worker_state = BadUsbStateIdle; // Ready to run + } else if(flags & WorkerEvtToggle) { + worker_state = BadUsbStateWillRun; // Will run when USB is connected } bad_usb->st.state = worker_state; @@ -556,6 +560,31 @@ static int32_t bad_usb_worker(void* context) { } bad_usb->st.state = worker_state; + } else if(worker_state == BadUsbStateWillRun) { // State: start on connection + uint32_t flags = furi_thread_flags_wait( + WorkerEvtEnd | WorkerEvtConnect | WorkerEvtToggle, + FuriFlagWaitAny, + FuriWaitForever); + furi_check((flags & FuriFlagError) == 0); + if(flags & WorkerEvtEnd) { + break; + } else if(flags & WorkerEvtConnect) { // Start executing script + DOLPHIN_DEED(DolphinDeedBadUsbPlayScript); + delay_val = 0; + bad_usb->buf_len = 0; + bad_usb->st.line_cur = 0; + bad_usb->defdelay = 0; + bad_usb->repeat_cnt = 0; + bad_usb->file_end = false; + storage_file_seek(script_file, 0, true); + // extra time for PC to recognize Flipper as keyboard + furi_thread_flags_wait(0, FuriFlagWaitAny, 1500); + worker_state = BadUsbStateRunning; + } else if(flags & WorkerEvtToggle) { // Cancel scheduled execution + worker_state = BadUsbStateNotConnected; + } + bad_usb->st.state = worker_state; + } else if(worker_state == BadUsbStateRunning) { // State: running uint16_t delay_cur = (delay_val > 1000) ? (1000) : (delay_val); uint32_t flags = furi_thread_flags_wait( diff --git a/applications/main/bad_usb/bad_usb_script.h b/applications/main/bad_usb/bad_usb_script.h index f24372fab6b..188142db850 100644 --- a/applications/main/bad_usb/bad_usb_script.h +++ b/applications/main/bad_usb/bad_usb_script.h @@ -12,6 +12,7 @@ typedef enum { BadUsbStateInit, BadUsbStateNotConnected, BadUsbStateIdle, + BadUsbStateWillRun, BadUsbStateRunning, BadUsbStateDelay, BadUsbStateDone, diff --git a/applications/main/bad_usb/views/bad_usb_view.c b/applications/main/bad_usb/views/bad_usb_view.c index e5c5d92a368..b3eb9bb5636 100644 --- a/applications/main/bad_usb/views/bad_usb_view.c +++ b/applications/main/bad_usb/views/bad_usb_view.c @@ -29,10 +29,13 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) { canvas_draw_icon(canvas, 22, 20, &I_UsbTree_48x22); - if((model->state.state == BadUsbStateIdle) || (model->state.state == BadUsbStateDone)) { + if((model->state.state == BadUsbStateIdle) || (model->state.state == BadUsbStateDone) || + (model->state.state == BadUsbStateNotConnected)) { elements_button_center(canvas, "Run"); } else if((model->state.state == BadUsbStateRunning) || (model->state.state == BadUsbStateDelay)) { elements_button_center(canvas, "Stop"); + } else if(model->state.state == BadUsbStateWillRun) { + elements_button_center(canvas, "Cancel"); } if(model->state.state == BadUsbStateNotConnected) { @@ -40,6 +43,11 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) { canvas_set_font(canvas, FontPrimary); canvas_draw_str_aligned(canvas, 127, 27, AlignRight, AlignBottom, "Connect"); canvas_draw_str_aligned(canvas, 127, 39, AlignRight, AlignBottom, "to USB"); + } else if(model->state.state == BadUsbStateWillRun) { + canvas_draw_icon(canvas, 4, 22, &I_Clock_18x18); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 127, 27, AlignRight, AlignBottom, "Will run"); + canvas_draw_str_aligned(canvas, 127, 39, AlignRight, AlignBottom, "on connect"); } else if(model->state.state == BadUsbStateFileError) { canvas_draw_icon(canvas, 4, 22, &I_Error_18x18); canvas_set_font(canvas, FontPrimary); From 60d125e72a832d300a487b3668187f7a0fe6e996 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexandre=20D=C3=ADaz?= Date: Thu, 3 Nov 2022 08:57:56 +0100 Subject: [PATCH 198/824] subghz: add analyzer frequency logs (#1914) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * subghz: add analyzer frequency logs * SubGhz: switch to change on short press * SubGhz: use full RSSI bar for history view Co-authored-by: あく --- ...subghz_frequency_analyzer_log_item_array.c | 27 ++ ...subghz_frequency_analyzer_log_item_array.h | 73 +++++ .../subghz/views/subghz_frequency_analyzer.c | 280 ++++++++++++++++-- 3 files changed, 355 insertions(+), 25 deletions(-) create mode 100644 applications/main/subghz/helpers/subghz_frequency_analyzer_log_item_array.c create mode 100644 applications/main/subghz/helpers/subghz_frequency_analyzer_log_item_array.h diff --git a/applications/main/subghz/helpers/subghz_frequency_analyzer_log_item_array.c b/applications/main/subghz/helpers/subghz_frequency_analyzer_log_item_array.c new file mode 100644 index 00000000000..9b33e92d186 --- /dev/null +++ b/applications/main/subghz/helpers/subghz_frequency_analyzer_log_item_array.c @@ -0,0 +1,27 @@ +#include "subghz_frequency_analyzer_log_item_array.h" + +const char* + subghz_frequency_analyzer_log_get_order_name(SubGhzFrequencyAnalyzerLogOrderBy order_by) { + if(order_by == SubGhzFrequencyAnalyzerLogOrderBySeqAsc) { + return "Seq. A"; + } + if(order_by == SubGhzFrequencyAnalyzerLogOrderByCountDesc) { + return "Count D"; + } + if(order_by == SubGhzFrequencyAnalyzerLogOrderByCountAsc) { + return "Count A"; + } + if(order_by == SubGhzFrequencyAnalyzerLogOrderByRSSIDesc) { + return "RSSI D"; + } + if(order_by == SubGhzFrequencyAnalyzerLogOrderByRSSIAsc) { + return "RSSI A"; + } + if(order_by == SubGhzFrequencyAnalyzerLogOrderByFrequencyDesc) { + return "Freq. D"; + } + if(order_by == SubGhzFrequencyAnalyzerLogOrderByFrequencyAsc) { + return "Freq. A"; + } + return "Seq. D"; +} diff --git a/applications/main/subghz/helpers/subghz_frequency_analyzer_log_item_array.h b/applications/main/subghz/helpers/subghz_frequency_analyzer_log_item_array.h new file mode 100644 index 00000000000..eaf53b6639b --- /dev/null +++ b/applications/main/subghz/helpers/subghz_frequency_analyzer_log_item_array.h @@ -0,0 +1,73 @@ +#pragma once + +#include +#include +#include +#include + +typedef enum { + SubGhzFrequencyAnalyzerLogOrderBySeqDesc, + SubGhzFrequencyAnalyzerLogOrderBySeqAsc, + SubGhzFrequencyAnalyzerLogOrderByCountDesc, + SubGhzFrequencyAnalyzerLogOrderByCountAsc, + SubGhzFrequencyAnalyzerLogOrderByRSSIDesc, + SubGhzFrequencyAnalyzerLogOrderByRSSIAsc, + SubGhzFrequencyAnalyzerLogOrderByFrequencyDesc, + SubGhzFrequencyAnalyzerLogOrderByFrequencyAsc, +} SubGhzFrequencyAnalyzerLogOrderBy; + +const char* + subghz_frequency_analyzer_log_get_order_name(SubGhzFrequencyAnalyzerLogOrderBy order_by); + +TUPLE_DEF2( + SubGhzFrequencyAnalyzerLogItem, + (seq, uint8_t), + (frequency, uint32_t), + (count, uint8_t), + (rssi_max, uint8_t)) +/* Register globaly the oplist */ +#define M_OPL_SubGhzFrequencyAnalyzerLogItem_t() \ + TUPLE_OPLIST(SubGhzFrequencyAnalyzerLogItem, M_POD_OPLIST, M_DEFAULT_OPLIST, M_DEFAULT_OPLIST) + +/* Define the array, register the oplist and define further algorithms on it */ +ARRAY_DEF(SubGhzFrequencyAnalyzerLogItemArray, SubGhzFrequencyAnalyzerLogItem_t) +#define M_OPL_SubGhzFrequencyAnalyzerLogItemArray_t() \ + ARRAY_OPLIST(SubGhzFrequencyAnalyzerLogItemArray, M_OPL_SubGhzFrequencyAnalyzerLogItem_t()) +ALGO_DEF(SubGhzFrequencyAnalyzerLogItemArray, SubGhzFrequencyAnalyzerLogItemArray_t) + +FUNC_OBJ_INS_DEF( + SubGhzFrequencyAnalyzerLogItemArray_compare_by /* name of the instance */, + SubGhzFrequencyAnalyzerLogItemArray_cmp_obj /* name of the interface */, + (a, + b) /* name of the input parameters of the function like object. The type are inherited from the interface. */ + , + { + /* code of the function object */ + if(self->order_by == SubGhzFrequencyAnalyzerLogOrderByFrequencyAsc) { + return a->frequency < b->frequency ? -1 : a->frequency > b->frequency; + } + if(self->order_by == SubGhzFrequencyAnalyzerLogOrderByFrequencyDesc) { + return a->frequency > b->frequency ? -1 : a->frequency < b->frequency; + } + if(self->order_by == SubGhzFrequencyAnalyzerLogOrderByRSSIAsc) { + return a->rssi_max < b->rssi_max ? -1 : a->rssi_max > b->rssi_max; + } + if(self->order_by == SubGhzFrequencyAnalyzerLogOrderByRSSIDesc) { + return a->rssi_max > b->rssi_max ? -1 : a->rssi_max < b->rssi_max; + } + if(self->order_by == SubGhzFrequencyAnalyzerLogOrderByCountAsc) { + return a->count < b->count ? -1 : a->count > b->count; + } + if(self->order_by == SubGhzFrequencyAnalyzerLogOrderByCountDesc) { + return a->count > b->count ? -1 : a->count < b->count; + } + if(self->order_by == SubGhzFrequencyAnalyzerLogOrderBySeqAsc) { + return a->seq < b->seq ? -1 : a->seq > b->seq; + } + + return a->seq > b->seq ? -1 : a->seq < b->seq; + }, + /* Additional fields stored in the function object */ + (order_by, SubGhzFrequencyAnalyzerLogOrderBy)) +#define M_OPL_SubGhzFrequencyAnalyzerLogItemArray_compare_by_t() \ + FUNC_OBJ_INS_OPLIST(SubGhzFrequencyAnalyzerLogItemArray_compare_by, M_DEFAULT_OPLIST) diff --git a/applications/main/subghz/views/subghz_frequency_analyzer.c b/applications/main/subghz/views/subghz_frequency_analyzer.c index c169f3611e3..e980bd97056 100644 --- a/applications/main/subghz/views/subghz_frequency_analyzer.c +++ b/applications/main/subghz/views/subghz_frequency_analyzer.c @@ -5,30 +5,53 @@ #include #include #include +#include #include #include "../helpers/subghz_frequency_analyzer_worker.h" +#include "../helpers/subghz_frequency_analyzer_log_item_array.h" #include +#define LOG_FREQUENCY_MAX_ITEMS 60 // uint8_t (limited by 'seq' of SubGhzFrequencyAnalyzerLogItem) +#define RSSI_OFFSET 74 +#define RSSI_MAX 53 // 127 - RSSI_OFFSET + +#define SNPRINTF_FREQUENCY(buff, freq) \ + snprintf(buff, sizeof(buff), "%03ld.%03ld", freq / 1000000 % 1000, freq / 1000 % 1000); + typedef enum { SubGhzFrequencyAnalyzerStatusIDLE, } SubGhzFrequencyAnalyzerStatus; +typedef enum { + SubGhzFrequencyAnalyzerFragmentBottomTypeMain, + SubGhzFrequencyAnalyzerFragmentBottomTypeLog, +} SubGhzFrequencyAnalyzerFragmentBottomType; + struct SubGhzFrequencyAnalyzer { View* view; SubGhzFrequencyAnalyzerWorker* worker; SubGhzFrequencyAnalyzerCallback callback; void* context; bool locked; + uint32_t last_frequency; }; typedef struct { uint32_t frequency; - float rssi; + uint8_t rssi; uint32_t history_frequency[3]; bool signal; + SubGhzFrequencyAnalyzerLogItemArray_t log_frequency; + SubGhzFrequencyAnalyzerFragmentBottomType fragment_bottom_type; + SubGhzFrequencyAnalyzerLogOrderBy log_frequency_order_by; + uint8_t log_frequency_scroll_offset; } SubGhzFrequencyAnalyzerModel; +static inline uint8_t rssi_sanitize(float rssi) { + return (rssi * -1.0f) - RSSI_OFFSET; +} + void subghz_frequency_analyzer_set_callback( SubGhzFrequencyAnalyzer* subghz_frequency_analyzer, SubGhzFrequencyAnalyzerCallback callback, @@ -39,13 +62,11 @@ void subghz_frequency_analyzer_set_callback( subghz_frequency_analyzer->context = context; } -void subghz_frequency_analyzer_draw_rssi(Canvas* canvas, float rssi) { - uint8_t x = 20; - uint8_t y = 64; +void subghz_frequency_analyzer_draw_rssi(Canvas* canvas, uint8_t rssi, uint8_t x, uint8_t y) { uint8_t column_number = 0; if(rssi) { - rssi = (rssi + 90) / 3; - for(size_t i = 1; i < (uint8_t)rssi; i++) { + rssi = rssi / 3; + for(uint8_t i = 1; i < rssi; i++) { if(i > 20) break; if(i % 4) { column_number++; @@ -55,6 +76,54 @@ void subghz_frequency_analyzer_draw_rssi(Canvas* canvas, float rssi) { } } +static void subghz_frequency_analyzer_log_frequency_draw( + Canvas* canvas, + SubGhzFrequencyAnalyzerModel* model) { + char buffer[64]; + const uint8_t offset_x = 0; + const uint8_t offset_y = 43; + canvas_set_font(canvas, FontKeyboard); + + const size_t items_count = SubGhzFrequencyAnalyzerLogItemArray_size(model->log_frequency); + if(items_count == 0) { + canvas_draw_rframe(canvas, offset_x + 27u, offset_y - 3u, 73u, 16u, 5u); + canvas_draw_str_aligned( + canvas, offset_x + 64u, offset_y + 8u, AlignCenter, AlignBottom, "No records"); + return; + } else if(items_count > 3) { + elements_scrollbar_pos( + canvas, + offset_x + 127, + offset_y - 8, + 29, + model->log_frequency_scroll_offset, + items_count - 2); + } + + SubGhzFrequencyAnalyzerLogItem_t* log_frequency_item; + for(uint8_t i = 0; i < 3; ++i) { + const uint8_t item_pos = model->log_frequency_scroll_offset + i; + if(item_pos >= items_count) { + break; + } + log_frequency_item = + SubGhzFrequencyAnalyzerLogItemArray_get(model->log_frequency, item_pos); + // Frequency + SNPRINTF_FREQUENCY(buffer, (*log_frequency_item)->frequency) + canvas_draw_str(canvas, offset_x, offset_y + i * 10, buffer); + + // Count + snprintf(buffer, sizeof(buffer), "%3d", (*log_frequency_item)->count); + canvas_draw_str(canvas, offset_x + 48, offset_y + i * 10, buffer); + + // Max RSSI + subghz_frequency_analyzer_draw_rssi( + canvas, (*log_frequency_item)->rssi_max, offset_x + 69, (offset_y + i * 10)); + } + + canvas_set_font(canvas, FontSecondary); +} + static void subghz_frequency_analyzer_history_frequency_draw( Canvas* canvas, SubGhzFrequencyAnalyzerModel* model) { @@ -65,12 +134,7 @@ static void subghz_frequency_analyzer_history_frequency_draw( canvas_set_font(canvas, FontKeyboard); for(uint8_t i = 0; i < 3; i++) { if(model->history_frequency[i]) { - snprintf( - buffer, - sizeof(buffer), - "%03ld.%03ld", - model->history_frequency[i] / 1000000 % 1000, - model->history_frequency[i] / 1000 % 1000); + SNPRINTF_FREQUENCY(buffer, model->history_frequency[i]) canvas_draw_str(canvas, x, y + i * 10, buffer); } else { canvas_draw_str(canvas, x, y + i * 10, "---.---"); @@ -81,18 +145,34 @@ static void subghz_frequency_analyzer_history_frequency_draw( } void subghz_frequency_analyzer_draw(Canvas* canvas, SubGhzFrequencyAnalyzerModel* model) { + furi_assert(canvas); + furi_assert(model); char buffer[64]; canvas_set_color(canvas, ColorBlack); canvas_set_font(canvas, FontSecondary); - canvas_draw_str(canvas, 20, 8, "Frequency Analyzer"); - canvas_draw_str(canvas, 0, 64, "RSSI"); - subghz_frequency_analyzer_draw_rssi(canvas, model->rssi); + if(model->fragment_bottom_type == SubGhzFrequencyAnalyzerFragmentBottomTypeLog) { + const size_t items_count = SubGhzFrequencyAnalyzerLogItemArray_size(model->log_frequency); + const char* log_order_by_name = + subghz_frequency_analyzer_log_get_order_name(model->log_frequency_order_by); + if(items_count < LOG_FREQUENCY_MAX_ITEMS) { + snprintf(buffer, sizeof(buffer), "Frequency Analyzer [%s]", log_order_by_name); + canvas_draw_str_aligned(canvas, 64, 8, AlignCenter, AlignBottom, buffer); + } else { + snprintf(buffer, sizeof(buffer), "The log is full! [%s]", log_order_by_name); + canvas_draw_str(canvas, 2, 8, buffer); + } + subghz_frequency_analyzer_log_frequency_draw(canvas, model); + } else { + canvas_draw_str(canvas, 20, 8, "Frequency Analyzer"); + canvas_draw_str(canvas, 0, 64, "RSSI"); + subghz_frequency_analyzer_draw_rssi(canvas, model->rssi, 20u, 64u); - subghz_frequency_analyzer_history_frequency_draw(canvas, model); + subghz_frequency_analyzer_history_frequency_draw(canvas, model); + } - //Frequency + // Frequency canvas_set_font(canvas, FontBigNumbers); snprintf( buffer, @@ -103,23 +183,151 @@ void subghz_frequency_analyzer_draw(Canvas* canvas, SubGhzFrequencyAnalyzerModel if(model->signal) { canvas_draw_box(canvas, 4, 12, 121, 22); canvas_set_color(canvas, ColorWhite); - } else { } - canvas_draw_str(canvas, 8, 30, buffer); canvas_draw_icon(canvas, 96, 19, &I_MHz_25x11); } +static void subghz_frequency_analyzer_log_frequency_sort(SubGhzFrequencyAnalyzerModel* model) { + furi_assert(model); + M_LET((cmp, model->log_frequency_order_by), SubGhzFrequencyAnalyzerLogItemArray_compare_by_t) + SubGhzFrequencyAnalyzerLogItemArray_sort_fo( + model->log_frequency, SubGhzFrequencyAnalyzerLogItemArray_compare_by_as_interface(cmp)); +} + bool subghz_frequency_analyzer_input(InputEvent* event, void* context) { furi_assert(context); + SubGhzFrequencyAnalyzer* instance = context; if(event->key == InputKeyBack) { return false; } + if((event->type == InputTypeShort) && + ((event->key == InputKeyLeft) || (event->key == InputKeyRight))) { + with_view_model( + instance->view, + SubGhzFrequencyAnalyzerModel * model, + { + if(event->key == InputKeyLeft) { + if(model->fragment_bottom_type == 0) { + model->fragment_bottom_type = SubGhzFrequencyAnalyzerFragmentBottomTypeLog; + } else { + --model->fragment_bottom_type; + } + } else if(event->key == InputKeyRight) { + if(model->fragment_bottom_type == + SubGhzFrequencyAnalyzerFragmentBottomTypeLog) { + model->fragment_bottom_type = 0; + } else { + ++model->fragment_bottom_type; + } + } + }, + true); + } else if((event->type == InputTypeShort) && (event->key == InputKeyOk)) { + with_view_model( + instance->view, + SubGhzFrequencyAnalyzerModel * model, + { + if(model->fragment_bottom_type == SubGhzFrequencyAnalyzerFragmentBottomTypeLog) { + ++model->log_frequency_order_by; + if(model->log_frequency_order_by > + SubGhzFrequencyAnalyzerLogOrderByFrequencyAsc) { + model->log_frequency_order_by = 0; + } + subghz_frequency_analyzer_log_frequency_sort(model); + } + }, + true); + } else if((event->type == InputTypeShort) || (event->type == InputTypeRepeat)) { + with_view_model( + instance->view, + SubGhzFrequencyAnalyzerModel * model, + { + if(model->fragment_bottom_type == SubGhzFrequencyAnalyzerFragmentBottomTypeLog) { + if(event->key == InputKeyUp) { + if(model->log_frequency_scroll_offset > 0) { + --model->log_frequency_scroll_offset; + } + } else if(event->key == InputKeyDown) { + const size_t items_count = + SubGhzFrequencyAnalyzerLogItemArray_size(model->log_frequency); + if((model->log_frequency_scroll_offset + 3u) < items_count) { + ++model->log_frequency_scroll_offset; + } + } + } + }, + true); + } + return true; } +static void subghz_frequency_analyzer_log_frequency_search_it( + SubGhzFrequencyAnalyzerLogItemArray_it_t* itref, + SubGhzFrequencyAnalyzerLogItemArray_t* log_frequency, + uint32_t frequency) { + furi_assert(log_frequency); + + SubGhzFrequencyAnalyzerLogItemArray_it(*itref, *log_frequency); + SubGhzFrequencyAnalyzerLogItem_t* item; + while(!SubGhzFrequencyAnalyzerLogItemArray_end_p(*itref)) { + item = SubGhzFrequencyAnalyzerLogItemArray_ref(*itref); + if((*item)->frequency == frequency) { + break; + } + SubGhzFrequencyAnalyzerLogItemArray_next(*itref); + } +} + +static bool subghz_frequency_analyzer_log_frequency_insert(SubGhzFrequencyAnalyzerModel* model) { + furi_assert(model); + const size_t items_count = SubGhzFrequencyAnalyzerLogItemArray_size(model->log_frequency); + if(items_count < LOG_FREQUENCY_MAX_ITEMS) { + SubGhzFrequencyAnalyzerLogItem_t* item = + SubGhzFrequencyAnalyzerLogItemArray_push_new(model->log_frequency); + if(item == NULL) { + return false; + } + (*item)->frequency = model->frequency; + (*item)->count = 1u; + (*item)->rssi_max = model->rssi; + (*item)->seq = items_count; + return true; + } + return false; +} + +static void subghz_frequency_analyzer_log_frequency_update( + SubGhzFrequencyAnalyzerModel* model, + bool need_insert) { + furi_assert(model); + if(!model->frequency) { + return; + } + + SubGhzFrequencyAnalyzerLogItemArray_it_t it; + subghz_frequency_analyzer_log_frequency_search_it( + &it, &model->log_frequency, model->frequency); + if(!SubGhzFrequencyAnalyzerLogItemArray_end_p(it)) { + SubGhzFrequencyAnalyzerLogItem_t* item = SubGhzFrequencyAnalyzerLogItemArray_ref(it); + if((*item)->rssi_max < model->rssi) { + (*item)->rssi_max = model->rssi; + } + + if(need_insert && (*item)->count < UINT8_MAX) { + ++(*item)->count; + subghz_frequency_analyzer_log_frequency_sort(model); + } + } else if(need_insert) { + if(subghz_frequency_analyzer_log_frequency_insert(model)) { + subghz_frequency_analyzer_log_frequency_sort(model); + } + } +} + void subghz_frequency_analyzer_pair_callback( void* context, uint32_t frequency, @@ -130,6 +338,7 @@ void subghz_frequency_analyzer_pair_callback( if(instance->callback) { instance->callback(SubGhzCustomEventSceneAnalyzerUnlock, instance->context); } + instance->last_frequency = 0; //update history with_view_model( instance->view, @@ -151,9 +360,14 @@ void subghz_frequency_analyzer_pair_callback( instance->view, SubGhzFrequencyAnalyzerModel * model, { - model->rssi = rssi; + model->rssi = rssi_sanitize(rssi); model->frequency = frequency; model->signal = signal; + if(frequency) { + subghz_frequency_analyzer_log_frequency_update( + model, frequency != instance->last_frequency); + instance->last_frequency = frequency; + } }, true); } @@ -176,11 +390,14 @@ void subghz_frequency_analyzer_enter(void* context) { instance->view, SubGhzFrequencyAnalyzerModel * model, { - model->rssi = 0; + model->rssi = 0u; model->frequency = 0; - model->history_frequency[2] = 0; - model->history_frequency[1] = 0; - model->history_frequency[0] = 0; + model->fragment_bottom_type = SubGhzFrequencyAnalyzerFragmentBottomTypeMain; + model->log_frequency_order_by = SubGhzFrequencyAnalyzerLogOrderBySeqDesc; + model->log_frequency_scroll_offset = 0u; + model->history_frequency[0] = model->history_frequency[1] = + model->history_frequency[2] = 0u; + SubGhzFrequencyAnalyzerLogItemArray_init(model->log_frequency); }, true); } @@ -196,13 +413,26 @@ void subghz_frequency_analyzer_exit(void* context) { subghz_frequency_analyzer_worker_free(instance->worker); with_view_model( - instance->view, SubGhzFrequencyAnalyzerModel * model, { model->rssi = 0; }, true); + instance->view, + SubGhzFrequencyAnalyzerModel * model, + { + model->rssi = 0u; + model->frequency = 0; + model->fragment_bottom_type = SubGhzFrequencyAnalyzerFragmentBottomTypeMain; + model->log_frequency_order_by = SubGhzFrequencyAnalyzerLogOrderBySeqDesc; + model->log_frequency_scroll_offset = 0u; + model->history_frequency[0] = model->history_frequency[1] = + model->history_frequency[2] = 0u; + SubGhzFrequencyAnalyzerLogItemArray_clear(model->log_frequency); + }, + true); } SubGhzFrequencyAnalyzer* subghz_frequency_analyzer_alloc() { SubGhzFrequencyAnalyzer* instance = malloc(sizeof(SubGhzFrequencyAnalyzer)); // View allocation and configuration + instance->last_frequency = 0; instance->view = view_alloc(); view_allocate_model( instance->view, ViewModelTypeLocking, sizeof(SubGhzFrequencyAnalyzerModel)); From e3ea5bca7652d2e1b7161c833fb30cdfa93dbf34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Fri, 4 Nov 2022 13:44:28 +0900 Subject: [PATCH 199/824] Dolphin: add L1_Mods_128x64 animation (#1973) --- .../dolphin/external/L1_Mods_128x64/frame_0.png | Bin 0 -> 4344 bytes .../dolphin/external/L1_Mods_128x64/frame_1.png | Bin 0 -> 4351 bytes .../dolphin/external/L1_Mods_128x64/frame_10.png | Bin 0 -> 4370 bytes .../dolphin/external/L1_Mods_128x64/frame_11.png | Bin 0 -> 4342 bytes .../dolphin/external/L1_Mods_128x64/frame_12.png | Bin 0 -> 4327 bytes .../dolphin/external/L1_Mods_128x64/frame_13.png | Bin 0 -> 4363 bytes .../dolphin/external/L1_Mods_128x64/frame_14.png | Bin 0 -> 4306 bytes .../dolphin/external/L1_Mods_128x64/frame_15.png | Bin 0 -> 4325 bytes .../dolphin/external/L1_Mods_128x64/frame_16.png | Bin 0 -> 4346 bytes .../dolphin/external/L1_Mods_128x64/frame_17.png | Bin 0 -> 4338 bytes .../dolphin/external/L1_Mods_128x64/frame_18.png | Bin 0 -> 4346 bytes .../dolphin/external/L1_Mods_128x64/frame_19.png | Bin 0 -> 4339 bytes .../dolphin/external/L1_Mods_128x64/frame_2.png | Bin 0 -> 4344 bytes .../dolphin/external/L1_Mods_128x64/frame_20.png | Bin 0 -> 4314 bytes .../dolphin/external/L1_Mods_128x64/frame_21.png | Bin 0 -> 4357 bytes .../dolphin/external/L1_Mods_128x64/frame_22.png | Bin 0 -> 4320 bytes .../dolphin/external/L1_Mods_128x64/frame_23.png | Bin 0 -> 4332 bytes .../dolphin/external/L1_Mods_128x64/frame_24.png | Bin 0 -> 4284 bytes .../dolphin/external/L1_Mods_128x64/frame_25.png | Bin 0 -> 4149 bytes .../dolphin/external/L1_Mods_128x64/frame_26.png | Bin 0 -> 4260 bytes .../dolphin/external/L1_Mods_128x64/frame_27.png | Bin 0 -> 4376 bytes .../dolphin/external/L1_Mods_128x64/frame_28.png | Bin 0 -> 4393 bytes .../dolphin/external/L1_Mods_128x64/frame_29.png | Bin 0 -> 4380 bytes .../dolphin/external/L1_Mods_128x64/frame_3.png | Bin 0 -> 4341 bytes .../dolphin/external/L1_Mods_128x64/frame_30.png | Bin 0 -> 4390 bytes .../dolphin/external/L1_Mods_128x64/frame_31.png | Bin 0 -> 4383 bytes .../dolphin/external/L1_Mods_128x64/frame_32.png | Bin 0 -> 4402 bytes .../dolphin/external/L1_Mods_128x64/frame_33.png | Bin 0 -> 4340 bytes .../dolphin/external/L1_Mods_128x64/frame_34.png | Bin 0 -> 4253 bytes .../dolphin/external/L1_Mods_128x64/frame_35.png | Bin 0 -> 4342 bytes .../dolphin/external/L1_Mods_128x64/frame_36.png | Bin 0 -> 4315 bytes .../dolphin/external/L1_Mods_128x64/frame_37.png | Bin 0 -> 4267 bytes .../dolphin/external/L1_Mods_128x64/frame_38.png | Bin 0 -> 4301 bytes .../dolphin/external/L1_Mods_128x64/frame_39.png | Bin 0 -> 4326 bytes .../dolphin/external/L1_Mods_128x64/frame_4.png | Bin 0 -> 4327 bytes .../dolphin/external/L1_Mods_128x64/frame_40.png | Bin 0 -> 4313 bytes .../dolphin/external/L1_Mods_128x64/frame_5.png | Bin 0 -> 4357 bytes .../dolphin/external/L1_Mods_128x64/frame_6.png | Bin 0 -> 4334 bytes .../dolphin/external/L1_Mods_128x64/frame_7.png | Bin 0 -> 4331 bytes .../dolphin/external/L1_Mods_128x64/frame_8.png | Bin 0 -> 4352 bytes .../dolphin/external/L1_Mods_128x64/frame_9.png | Bin 0 -> 4360 bytes assets/dolphin/external/L1_Mods_128x64/meta.txt | 14 ++++++++++++++ assets/dolphin/external/manifest.txt | 9 ++++++++- 43 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 assets/dolphin/external/L1_Mods_128x64/frame_0.png create mode 100644 assets/dolphin/external/L1_Mods_128x64/frame_1.png create mode 100644 assets/dolphin/external/L1_Mods_128x64/frame_10.png create mode 100644 assets/dolphin/external/L1_Mods_128x64/frame_11.png create mode 100644 assets/dolphin/external/L1_Mods_128x64/frame_12.png create mode 100644 assets/dolphin/external/L1_Mods_128x64/frame_13.png create mode 100644 assets/dolphin/external/L1_Mods_128x64/frame_14.png create mode 100644 assets/dolphin/external/L1_Mods_128x64/frame_15.png create mode 100644 assets/dolphin/external/L1_Mods_128x64/frame_16.png create mode 100644 assets/dolphin/external/L1_Mods_128x64/frame_17.png create mode 100644 assets/dolphin/external/L1_Mods_128x64/frame_18.png create mode 100644 assets/dolphin/external/L1_Mods_128x64/frame_19.png create mode 100644 assets/dolphin/external/L1_Mods_128x64/frame_2.png create mode 100644 assets/dolphin/external/L1_Mods_128x64/frame_20.png create mode 100644 assets/dolphin/external/L1_Mods_128x64/frame_21.png create mode 100644 assets/dolphin/external/L1_Mods_128x64/frame_22.png create mode 100644 assets/dolphin/external/L1_Mods_128x64/frame_23.png create mode 100644 assets/dolphin/external/L1_Mods_128x64/frame_24.png create mode 100644 assets/dolphin/external/L1_Mods_128x64/frame_25.png create mode 100644 assets/dolphin/external/L1_Mods_128x64/frame_26.png create mode 100644 assets/dolphin/external/L1_Mods_128x64/frame_27.png create mode 100644 assets/dolphin/external/L1_Mods_128x64/frame_28.png create mode 100644 assets/dolphin/external/L1_Mods_128x64/frame_29.png create mode 100644 assets/dolphin/external/L1_Mods_128x64/frame_3.png create mode 100644 assets/dolphin/external/L1_Mods_128x64/frame_30.png create mode 100644 assets/dolphin/external/L1_Mods_128x64/frame_31.png create mode 100644 assets/dolphin/external/L1_Mods_128x64/frame_32.png create mode 100644 assets/dolphin/external/L1_Mods_128x64/frame_33.png create mode 100644 assets/dolphin/external/L1_Mods_128x64/frame_34.png create mode 100644 assets/dolphin/external/L1_Mods_128x64/frame_35.png create mode 100644 assets/dolphin/external/L1_Mods_128x64/frame_36.png create mode 100644 assets/dolphin/external/L1_Mods_128x64/frame_37.png create mode 100644 assets/dolphin/external/L1_Mods_128x64/frame_38.png create mode 100644 assets/dolphin/external/L1_Mods_128x64/frame_39.png create mode 100644 assets/dolphin/external/L1_Mods_128x64/frame_4.png create mode 100644 assets/dolphin/external/L1_Mods_128x64/frame_40.png create mode 100644 assets/dolphin/external/L1_Mods_128x64/frame_5.png create mode 100644 assets/dolphin/external/L1_Mods_128x64/frame_6.png create mode 100644 assets/dolphin/external/L1_Mods_128x64/frame_7.png create mode 100644 assets/dolphin/external/L1_Mods_128x64/frame_8.png create mode 100644 assets/dolphin/external/L1_Mods_128x64/frame_9.png create mode 100644 assets/dolphin/external/L1_Mods_128x64/meta.txt diff --git a/assets/dolphin/external/L1_Mods_128x64/frame_0.png b/assets/dolphin/external/L1_Mods_128x64/frame_0.png new file mode 100644 index 0000000000000000000000000000000000000000..220908495ad987548598b3c9d253b5c0898a5e76 GIT binary patch literal 4344 zcmbVOc|4Ts+kb3@kYovAj5sZr#n`6oduQxxNsKWx%+?spC`(Byd$z0zB^ja=$ug0U zU7;-5NoYt&ws$(`bl&%m-{<$o`+T0|zOVcHy|(+hKF@vA#>z~PUy2_906`0LV>|XY zoc-wYazGP1ESG6In)BySuZ3jnmiZ2KUWq;+xK(RJ*8C&Ld*Pxv-~z9uZ9-xPU+-$_*LK$f}V>#KeI=GiwUa*tN|7~HyXHrnx=)>paV z!e=4Dti{F6fz6uPtl6zjde{E&10qU^GkLy%wq4{YP2atZ$>LaytOh;slIj4|Z^YP+Ymt-ukg3 zcXDKvIS0UOOX*1avsX>DDy3U0=-blRg#1JUzT6NkaZK2oZwj21mZ@;ie3lENa0ekRZUgZq2lMzV}%ho*DX;gELylr8f}J4_#4-k=mCC z2>T%5^mPD8s^5s$KfwWXq!mm8K+Wev=N=|Ypx*ETfN}1X<7EbW1zw3Vm_k{vWNMfa zoajBq`T|For-TjnOobojzjr9rK+=JU{J{OJj7uu_K*qRm#VeSW;Ov;lxhwNzi9H^N zavBcRT<7C9XyHAGmPg(=;2HM;+{r7E9>)zvn0`K{2!VUPV2?=H{C>NuLiVF%ABGtUkd=b}R4S#@>9$Hr@28(RGrghHJ<0rtQ0wg}D}H&!1h~aM3qSKAbCm-70^MF=Toy`cK7r8sX^Qtr zI0KZHSedXt!TR=QYvWX3h+RRl$Z2(~-Zc09yKuww+()(@MLF+zAKgIS zJ!?V84YYySM-?4J1Y{_7WQrTL6{zI*b!XswaO)Q!bJS|SkqB9nwC=QCzt4N;X6GWl z2MGvX7mR2&_dF`;CwWP-@KB{BBpvmD=UrNQivx53E}iakXa60Nqs#HNgb0~J)UwqyvmzW3tAeMK^kFN-uvJMDt(;%&NY z-Sg|*x3B)h2tK(UX_|Z8Jl_FR`9?BUUi=LlyX<1vPjTX!SN6MO>FN4pV*wS&vnd@2 zxrDMe@_*#b@J{s3p1Ih;A@rtvY)SiL*6B|9PQlJgDqJdhD&_sYCEg{WC7Of5gLQ+R ziDu_i18oW+0J`(=1n_f)*2wzsz^4>LY6S{LH9`?CCU z&~+bchPGlO@0(;bosQOsZcZIcz1+r=YMHvuxZ|YjM0Z+s8emxTpPN^nC-ghxZKRzu z1u~JDZOpiw=PaK3TY%TCFWGw7i)LuicDXtOLx@6i9i^%k! zq@KJ?p$4=E;>k7h1>=auqSr-@bIv}}KKJ~ySJWjQ{F%@uk`|aIVNWQ$RY<%W+?Fye z^|iY9*}K*J%$J$NB_9dzN7{9oLW(ynRWqeq-5PaR2H%&~T_!HJPi7Zp7B;LT%tFow zoKa|ii^xAc-w(XJ!QdbB2vvZD1_l+*+-Qc@Xbr0&mqdIj8q5Yxh zp?kM-Hc$R++m8QHKTiK9zHG(g!SRY?nxpoL)Rm`Peq3_g>D>4Cs`JG1yxap3&=oTj z;1fI}M@b?vCB8{-VAO_vlrwt7-tuOPot6FxZC66c_8*A5-o-mA97=kyftqW7&U<6b z41c3a4kwu|u@NGA>^S_9>KEGzd+J5%x^h2b>ddB3ZQOEvQOt{zL%z=`fj$ghyd561 zkXaR8^{DC!E$WBKa+(PB;1RyXn^UhpywCZWf&rr*n%5X@fVoIln^Ik~I%yqWq z1f4pgekS3vR^%O#v?qGg5g7=N)>r)CF{wqUb_()VweuZ7BjgUAB^6&)B^^hyA%DmS6?0dCQ z@0~9j3j3~#U#?9kv#Bb3`k{(;W_b);Z}L)HA=+LdNEPO0SM5c^E%k=9U1)ofx@|Mz zV(0kHmp=S0&Mu<&{(YkpSDg_lMaivg#M19)Utq-0DPE2&-?Tngr?BG^Deeq1<6-HK zGSjM=31=(!zRRn0!r8ouE1U)tZE?5_`X19f_Cnl&-Fy2_3023$kv?iNAFJ(%v-_*1Rv3gdcufU;l)U zV5bo)YmOvOXNHpoGZ!<0o{`TU7iut=e)Y%bgUgpDZ9d=YY#eUv7)=kWT;-iNr=RFW z)(0?D@(H;*_i9<6WAd|Q&dZq9RqlqeV3U}DX7zaIoZRfAI?EeVSCb|Sy$W-(t@L!> zrw`NDQCsPLZnkcE1Jmh&fjSntAq`ZD|c2Yw8wy4|JMvfeC6R}z&*4p%8wul@*JTa*am z|B4!R3y!Bf-l`ki{<8Py{x4w_JLOm=VeE$o)1#~L%zl;-UsTRc^6tB+@m{lbvpxk= zh1mFPIXP)TsoHN|ok`<`PYMrqy$yBRrO*2p&yH?SZ2sBhvh`tU-$ry-eJQ zdW>vVXqKz#r0Xm{*edB93Sm{Tsz;k^<F?OA^!&s$7j|2(>&{-@*@CQJTiFcP zRy@EyRZ3D#T&VUWZ)y;lf&~n`NS;`b1p)1YwZo#l=oeeDrvQM9hI4SCx>%x+7!pAh z{R^W?Baqo_063*bBcm~XSSrX9>w_cefLSl=!62NM4%k`U5^6~{!d}3c2UD>2!B!5K zU_Xqu7g$dhbc%*#D35#mgIMXKeb9 zIrdBke1S?OBO#EWpdi&CHB}PD2LjX9)`mdg5I9_gjZpEY6RBvL3ejKTw+3UZKZb%M zQ*k6B=$A&cCncN7G&rRBehiT?lOik&qG4NZo?RG|<8;a6yXq5Y|L*#C9o zKcf8|=wvL!4(m?}pkUZr;;rzT%ue5bH}nh0_6BK9VILrfXuL5A6F|TcsTRgMVD^ft z7tRZ*rG-GNslhZ<5O8&b3IeO$gf)YKeh8)#4gBR z!+)EBJ^8ntutatbQP|C5Sj%n1ZZrWKl!M9c?k;<{v$M0ky`5gneI5WfA6pn3IM4JWYB7uDmZvw|>gU7G^UnL!?@mgGL^9XCKMDh}9 zJO(mY_%QSE{WS)?=3S%JwW{U?YI^sJ1J1=|#y2%Qj}PqO^Bf_B9k3SHFSg}PSqP0g z0w#9l6@&Y{q^@an4@y0fvl8_r z@TVk~s9Z#uLeo(l(c2IE!{-W6JY@J6hg#{QiFoj8{6feB$!7tv+Rb>tBiixxjsj$_ z*9`|CZT0gz)x3R@9S>$++&&@XbmVMI;+KUui4cX`pRiL}8sguXnQloMCj=s1) z*s@Nzfubbtd_0xKn!C2g0-1B zJS~MK`!@=^hQ|v`pNeN!cGVAS3Y-f;1sfNCt-nCqTk`UA*j_+>8xGlQUUl!#6&!mE aZ~XjcCIx_F)+NC|hISsxbz`Y>mN;BrPPeWlNTX5}`;;_Ou{d z6bWU?lF*PX*}my{o}Taf<2~L#zWcbB>pHLB@7%8QJnrkdt+lzZpsXMO0Kyh%6Ab4Y z&UsM$Je>Cgf4mg{2pSWNjcqNAjX_iz#hXCH0{~+n%P!bCX;o5xcoo0f(WrmEERA>x z04o&pw}`srirxgUS4G88uOgxZ9mOT~WTG8jUhWk{XGM+W9AWtwMn}2EIJj$nmmey4 zdLe{8H@E&_y>=>dYNM0cwL5%|m{Q_ot{m7D-jA}|6Lo7%;dfB{-m?OViw7HQzI z-4icCAQ=FN%upjRP$Ue@E?MaB0Qw7)hNOW0+w6W>u?F9%-z@o zgVB!_?kD9mu|G+oHFBpWv=tbBdmK>!4p^^taVUDI+mqE1cD> z!H{|2+-+2PNn_rX&2w0CP$?#Es*TqOMqZs#*lLcrxEXh}drZB|U0EV}!TCDN7Hrkb ze1=IhIoO&qCN_N$z965vDUcK_3=~J&G+I~geA-5A4ExT-9V6o069k~b7FBM@?n(qi zebnL1RRBn8Sc^v;=K?y?^1lE;?N@2{2gy>FulWJMBD#=a5J&-?T-k8IAK+oPft&tclaQzK&Tmu9F^d}pPz zo1|+a1$Yfx`1fKDBBJ(q#`S|c`K8k1c)?h-`pxis&~%jeDe$WnT0VE7kzb+*TH%KZ zOhb3PsKgIK&I}r8kfxK+=>zsgYy83R9@YG!YZrO?h$neVC zg6@-F1L?qp=r~+>H%!F)#z(CCjccZ7uRF^sHA~_qoHZQdHE{c#wSpw%8ndx%{zKd` z;d^KKPhS;nB3@2NFn6|eHh!Qut56|U&F>^|;6cR%xWz6DfAk<)CEqSzzekjiX{j`mKx+Lw&R-s0 z3`$F^O4yxXb924*#dwuC;#Jg`r9kc>On$Q12@Sl#1n<2&aHI5`qEj7(+3)#_q7Zjn zEl4>*wh+5(g?rTlZ!30WNE)~0tK{{*y-n~TtolNx>D26Z6*yh}@Ov4?(u%cc9=+I@@S;CyW5Sg1te8?h8b ziepMx%2biPLx6)pFSBTu{X>V{4^KN@xNqk0#P&;}z+=J2AnSc-yX1 zXY%ULY+n9_6E2U8Fw2QV=h@?`UdzNDlza`xFE|_Z(HwbZl>Kj6dAgOa%~}TSSeFZe zoVTbq z=V)`1b29_%Kx!5$OF3&9K|-ts3RG)WpQ?_i9t`aIrvDB%!<-gaezPnx{i)l0nY5I* zEcK1u$L^Nbf~xatCdd$B+PL}l1l(8uTLf13{MFG1d)j=f6JPq~bz9^eW+{EY=JQ2^ zA*JtKOxfpIto|84ds}nJrG0mw1siAEC$yWY4pJMAR@JPCVy1@Md8cB7t>BYaz1whvKBR@?w?F(6H5z9ld>ZfL>G|ngtVnh z$bPTxdHQZSFQX=7sN^H*{ik;1s|yd;tyDAQT0LGM*@jE=tIlKR+P`EKWE30vuJve$KgwQa`#Y#3$!kX*3dahB^j*92GHCD}_)c>H2 zBqSj0B2P=AG)n!DTf?ah`6%D+mUzRTCE+Uf3)-$^d7y7kTx1vjuxJ?N{+i`=~iKO%;R zo6TT_vx-=k7}tK9E~JUk_Z||MyFUK1|0VMJmjLc5;%&9e@!R&PbX@7gQ0_CS$?49v z>|i|?4VQ#a?TA}qY2^kJS3pL#5NVr<0uqDyw3h(7QRA<(RUf z{Ym$N@h{&<8?(UIY63Qxj0a|sW z%EtTn@g(7K<|OHjT++2StK$l50gHnY#0bmoyb4UIt%#&0Oe z-ukwtuveOycuXqgOfj|iLFv!O zW~|9EC+jo4q03Ctz`*AdRd42I&@=fngTu4dj!NY+JoQd&ZAWAr3uiXebvT zVKie8pb^xGjBv_8#@y}Tr&QNtB29)9&kqmZ4?X|I_Uqlw7eg;PhSM)rE%VQyna6t& z4S~ffd88cV-8%Ny8+lp#PwzLcui6e{!@l4Gn>FH{vU9SIAQ#rgFDHE|@G8j8vNk}z zPak5gT5hEKdz|v9^D*wqHjtu!x2!s|+Pe68D&AwhmbwI9^&C{JI$44&MVg0EH%HNr z+$b~n)wVe&y;Ay;)r@X8``yJ~evQ2WAAWvN^|-^VW51r2t0F6X+Q(9?Uj7-hGA9)* z_}y~IBP5>jXrq2$^V`l}yT4tm*eb&}l16@>Z9Loc!euvGMBrNXR`T|{Yok5p?dH7- zW(u+KS@QC7!m@QgygHLc3(5=jcD)I6+-A-MJe(Td99uux<-E~9ziTa~tD%%xxJY9^ zEPRA$R%n*5?PMYs?r)TI4qjlh*ww?$b@J&)Hc(r(i`~C)%>{35R`sVsw>BWFR~OgA zbrkn-PL(orQ)jvz#hV_CrQrcXFN!A~WI@9E;4yft7xP>zUJn3x7zBG~y0euf0!JaK zVt->)86+x)4FGxu3@R4qkEeq?@je7H63nh?0D}l#NU)QJ71WAqjQ1s=Luh!r5Nmr} zh(Aup3v8ed(qkYv3P^Z57Q`SC$pHuk68x851ZVtP3;~1wg3$eu;J=-6wz35oQ)qaQ zmMUBY2ZcgGS~{vQPfZQ1mX4>oG6)WZ!y!;j2uw`{4o5&?oE!Ad1?Cu}d3hr+CT9Pb z~8cgs3I2wUU zCs4?s-x{%=lt4NX%yIVbC`eQ*tA7=f1OCYsCu5Q`VHiGgRr4-4iIE4(S(8vB;m<)3lk)mv!d!n z@Iq*7t7FyFV45oGa1C`8b-bFliVjpwQw6R8*HKf`)`H>i+JE%?Cq7i$1g2?ZW~u>) z8pB|wCfX(@Ff%h4%uEexsAX)X`G;#k4xnSnIQ$>m1di>0xF-LVi!i3)v2+T}o4R{bg8Z(P|5H1EM4W>B zJ^Z&BIFoSO(H&LeWQ=8TBJlS?V!EUuBJ$KgM5LbWDT;gP5B*dHfOtc7u-w7n%C z;Q%5J%a^6UErz;VE1@O<90sh#E>FW1!rux9-@O0Ypzt*DdUFV*c9Qk8#ZtdDB87HP z7n;c})^B}hpUb(Hv1VPJS(7NYcIz~O-CARQ_|zkPW|k8nhTLw!gJ=5fcIUi4auppk z+)Qs0M^!Ff-?KuNWOj@2Ah~+Q9`13K$XypRo721N2nc~w<*=C@p3Ga>2MQEdGKZ7z zz3Y;0tQBsG6VDsBMd!K--A{x|G+GqeEs=9fT=yQm=*89|b?cYHrfvxX#IopH7sp4# zg%SlvT#>(?mia2=J&R$gjz>p0h{lF86z;%aE}BE4PgJSr6Az0xFKB83O93;R6YlNh z{YJg(ups_d-L`#(AJ(TocSk<{h6=+T1fM_ck?GEp<8yRK<`p`(=M4|5wV<%Z*Q<$A za6Ha@=QmWOq#oa8rJt*2w-|zZfnJmQDY}Fc6sEC$Ui&g}g!D3RcNVO=JKj zBP)JOpve8*8?Flw)b~pL@jHD1*Q{eJUEfpg7z`1FtNQO}-}^qSUVEj_W2u2Bd?f7S lQMCuLAvt&W=AFX1faFU8hJxon*x&yU3sY;8Qlqn%{|B1zvo!z! literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Mods_128x64/frame_10.png b/assets/dolphin/external/L1_Mods_128x64/frame_10.png new file mode 100644 index 0000000000000000000000000000000000000000..e90ad5e90d9d22f6dff7d583ff77c79483616922 GIT binary patch literal 4370 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Mods_128x64/frame_11.png b/assets/dolphin/external/L1_Mods_128x64/frame_11.png new file mode 100644 index 0000000000000000000000000000000000000000..031c0ad81a1ce3f4f77b62f74dc865fabc2103ce GIT binary patch literal 4342 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 Y^oL+X!q1V(-@guPi=*awrf0(c2Rp-`h5!Hn literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Mods_128x64/frame_12.png b/assets/dolphin/external/L1_Mods_128x64/frame_12.png new file mode 100644 index 0000000000000000000000000000000000000000..856e068fd002a75f04ef4fd9a20649584763ee01 GIT binary patch literal 4327 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 GasLPY-HN^d literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Mods_128x64/frame_13.png b/assets/dolphin/external/L1_Mods_128x64/frame_13.png new file mode 100644 index 0000000000000000000000000000000000000000..a0366b2cfb4478ee1e6a3261351ad2608a960caa GIT binary patch literal 4363 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Mods_128x64/frame_14.png b/assets/dolphin/external/L1_Mods_128x64/frame_14.png new file mode 100644 index 0000000000000000000000000000000000000000..24fd557abd9732bc5c44221f0d2de36aac1276c5 GIT binary patch literal 4306 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Mods_128x64/frame_15.png b/assets/dolphin/external/L1_Mods_128x64/frame_15.png new file mode 100644 index 0000000000000000000000000000000000000000..3bf1d3ed2b4aab9c4801cdb145d64886751a96ea GIT binary patch literal 4325 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Mods_128x64/frame_16.png b/assets/dolphin/external/L1_Mods_128x64/frame_16.png new file mode 100644 index 0000000000000000000000000000000000000000..f0b44898fb76099f26cfd61c57c2c252e843faba GIT binary patch literal 4346 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Mods_128x64/frame_17.png b/assets/dolphin/external/L1_Mods_128x64/frame_17.png new file mode 100644 index 0000000000000000000000000000000000000000..c98c70c91261ccdfda7bb489908991c192199121 GIT binary patch literal 4338 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 TR|WGY1^}2Eoi;4jzZmsD!#|dj literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Mods_128x64/frame_18.png b/assets/dolphin/external/L1_Mods_128x64/frame_18.png new file mode 100644 index 0000000000000000000000000000000000000000..4f7b7ae82eb0a11725af52a2ece0ab0f85703825 GIT binary patch literal 4346 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Mods_128x64/frame_19.png b/assets/dolphin/external/L1_Mods_128x64/frame_19.png new file mode 100644 index 0000000000000000000000000000000000000000..b3ad6700cec06ed37af344e2373eb2bbd808edd4 GIT binary patch literal 4339 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Mods_128x64/frame_20.png b/assets/dolphin/external/L1_Mods_128x64/frame_20.png new file mode 100644 index 0000000000000000000000000000000000000000..ea2eae4d7cb6d87b5f75327cc668f38c0ed0d383 GIT binary patch literal 4314 zcmbVOc{r49+rKSkDSP&0jA+rAg|STbb(D21jj}YxjD{JbF_w{C+GNX?T_~v`N||I! z)`%z)$`VOvs3h6G>3N=>@B8CD-ao$kxR>iXuix+7uJb(Z>$1JArI4VUAOHYDR%kN} z=Nrv=jQDvt?-~AN8vqbAC7GJqTbY`I0%%ksiA(^1(2;z{5Nz72l)?BaLBz#mc&R#z z90P!rnf#riuEnC)0Ni;|aig~QOM)(YB*pU3&aJV7g6RB9Q$+_^KE_urolJ0c)A^z} zR`xI=jJ>$H{${;?Hg9&Lm(eE@Ehes-`mxv-(8a{-p@tQc?a-}V4bj|^=m}ns=*R9B zpB-%gu*Aa%jigr@ZgAbB0^C5rp!$CCj>+w~I(yX<04Noh0E^G=;!42)o$5}Vdw@kjt^+;VP(9O3;DVtZ?PHI4&ddz)1axp!1vUAV zp8`f)fHUK3?i!;4MJEMchOH$qbT*9k72L4z)Kl0ixNFy*oOpzZH-ClL_*{b z&g#}^*b;Cy$0)m^xg>7$EY3f;5|cFB&1(YpU!7In>WGipOghv*rCIHEMDofq_A<*J zY}3Jbj7c?9>dKfBpNm2)D`svAq=g6p%nNqSwvYH8c9WYUzi@FU2xA9=0i(!okgIaL zQUOsPO$1{V0MeS)l8w~3fEQV%9{`~KlZ@N#bZP5$egH5liaA_m%(wgL9%i#}-cyD8 zW@&ERPBWw3`@GJ1SlQSM56QD8q^Q^Y+wv)Hd+u4bQp z^rPak{^Os5=s?7kBz$x~T$p(EEza%gMf1~_v2v;%QurAx(j^&*SHNlqOUgGF;Mn~8 zxf7x#7Wh5Ri?)zsQ&KFk)>zZqDhtXr;&uEd1me!aTMmHJZrjzUgo&@UhI*l@Q?M3H z)h|l>lnPWl4hTzlrlI!>cU_yd6;x75L0Ijw@W-L>_uXVbNNP;Hl{tObhqV@lJ-k0A+$GrTto5hmG1cN|`n7rf@ba|u;= z(uz_PY!7w3ST3O%l%w(@SIV@z6jCzyGKb_tT0H}uqt^+%i`!?O^)hR~?-SqL>|EUU zkljKTgyK5TUI%6UWY5W#$<)e1v#oFKc$Jmi=>&U&kk9tHE^?jfj6MA0aJXdZbMXvR zhD%0d#_Sy@=Rjw}LB^e3PIsL}?s~XH+_G?fVE>_9;I-7yX6!G#(ESVX7DX4(B~JL-cG*NFsdfZm8EY~`bK#jk;&(u?V1?%7)>+-?S zbJmWgfp>^ZVk$BJEXOch|$v4(zfZwLod?(a)@>PHLvJBJ)K9!n8VDjg(Tgv&4Hcvf(hv?HbLN|}FQSa-&Z z+?Tq6hp$#ja-ZamRlKFVe%GVl7IAmoMm<-)%l(-?+xYv^Dt78@&xic7+_IK$DYHWO(NW(=v|t~mFBwC| zFXXbKS$9}5p%;IeFK3C3pZq>j1A&`5PoTHI9o#T{A$5+mb6+f2#IM>@< z5TfUbbWI7@iN7wMb>DC%4rF2vm9d*C^S4ou_8{yKBP&LXH?L%XD_E`Cwjf>gPduHF;vu`1~>3QjSB^V)6W{uh#Sna`D zJEEFen$R@kBjve#+QsLq)5>ds-$o_L@z(t%HJD0!VJU0qZMD!-GN(dW=mzx3*Bax* zt}wJxZs6ukQ?=L=ni=KkUEThb-%mcqOX4!To!P!wgQr}g4ohcvG6R^mD}Pp5 zus%+mu=N}aUtv&2M&2K*eZDx4o-dvEXRf+7-8G=XF-O1N&?@;6mLKKb?Xp>Mx3=-E>c~*jyO49=gyR$>kG^N8 zFVsI&3q9f2U>>pJKky~?U9y~)d|lJW$agHu5WBX5D;>Htj>1cUeKct)c$w z>@miw^+vXzyMud!k7-|lp)~!Ab*<-W*SGhx$?i+_0pG!^UZX0t$1C(J^(`XZZAuvZslk2k45Pa z!7tWh?qSKH_cj_wHb3+I68RievsF!KrcC@i-F&+5nX3p}Sm0v8R{HjkpoWJSUG5HJLsH{)yx0=(n!q5Ew?6m;(gtP|U`jYcLWj`^j$yV= z_%J`bt~c1w0Hha+;wYdH=r~X)h3p@Q3e^Yyr5D8+{}w~RpuZq=KYj4uPGN2AL8eq1 z0i>;tfZ$;;7)V=J9qy%t#A)k#X&wO~UhrUiv-KoAHN49>Yh|6E{>L7F!ag)y`E z#~f#-4?aVu2cV$PkdP4d5Dj%I%?AqC)zyW<5KsgH!a+a+8UA!!D8xTd`Hu!OLLi<- z3ZRpy{-EC)abDCQx;~iW?B7vP0&HylRqP-5Pp&vwgNEV)pm22%u|VH#Qx1QMaEp`oJ<#}jn^==o243v(n=*A!`v zM8HhoaC0*qGc&k_1sran0W;P%wb1&*wek<7$C8UR9Qs3SoKZE$#!|8L2m-@Ej8&}IZ0DVX4GL8DSYf2A2k z`VTH>A$7F0v~dUsCsYDN6Gza5=xFQUA-Y7Ox3;&277`BE0RO}H{{N;A%5e(%yH@^B z?femO3i9{x-)7)U{%t3MKc|OioMtJFJ!{EnwB7dBPUhR&+nnLn*4F0cX7*iP4*=j! zwK6ky3LRO%#`v2m@lV%nZ_lb!IPBr zm*Vu}WTVwcoiNlsEElWw>5uh$=li@)m!aPPMYv(3`de)RX;85-o>0oS@Vae_?Q;^I z=mlVsNcPwpZ5yAZkj|wkH}d4Q5w_DD$frSw4rryRAvHyjiJBPWi6oMt2*~SP^Eeu3ht?NZ5vA~lI8C$)SbiZhg_|jOkyH<9tgZ7r9A=86*?Nr=*m}G#ISSj zlCE>fh@S*RNA$K6ZCryh4;agE!{YB?SgNz839Ku|HX>ev#B3sA)S|4|+lKBvcqkfO zoM1w)j*^g%Yo3XmAd6FcM9glJY3~hIhPoYE*aES$&wUPR-RwLNHXSd$t8j^lfCL;q zpAc0AyqCZ(m0vkCZeqSz+iwL}7>XZ>3!E=u_3 zXSS`Uq6ksvIp3)s!>PQZ;L^msui8azcc(rEalr@`kb?WUzJfsAu8ABIc~SKkEYQMx zqi>IlM>ucnVLnv*HO=#^Ix-yRo8ye^P9r(99_w|5Fc;;42(?KISE*+B_6N}zt3;t?i5@|5wVx$s zBj^%=O9ziHqbmHqk8?$%n=-N{D@9!|M#?qc19*T)9|6!iYDL8F--DI8ty!hX>Dd1P DJeHGU literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Mods_128x64/frame_21.png b/assets/dolphin/external/L1_Mods_128x64/frame_21.png new file mode 100644 index 0000000000000000000000000000000000000000..900cc7d12099f0141c1e0c27cd29025fb1645f3b GIT binary patch literal 4357 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 JY2tnHe*iH9mO=mk literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Mods_128x64/frame_23.png b/assets/dolphin/external/L1_Mods_128x64/frame_23.png new file mode 100644 index 0000000000000000000000000000000000000000..4f82f63bc9e11628f6e221ffab62fd6a0059d2f0 GIT binary patch literal 4332 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 Un~;#LUq1j-!_x+pdhVD05Bny3KmY&$ literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Mods_128x64/frame_25.png b/assets/dolphin/external/L1_Mods_128x64/frame_25.png new file mode 100644 index 0000000000000000000000000000000000000000..768030b3c5614c681e3e2af785c662695bc9296f GIT binary patch literal 4149 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Mods_128x64/frame_26.png b/assets/dolphin/external/L1_Mods_128x64/frame_26.png new file mode 100644 index 0000000000000000000000000000000000000000..12f22abdb996b1a1e301e114c3d1f60c641e2ee5 GIT binary patch literal 4260 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Mods_128x64/frame_3.png b/assets/dolphin/external/L1_Mods_128x64/frame_3.png new file mode 100644 index 0000000000000000000000000000000000000000..1b0e77426c54323d46644d443ce364301dca1962 GIT binary patch literal 4341 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 bLx2O|;^IXc4Q(KPe>p5ntxd{|+%EnP;{}-{ literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Mods_128x64/frame_30.png b/assets/dolphin/external/L1_Mods_128x64/frame_30.png new file mode 100644 index 0000000000000000000000000000000000000000..13caae7ca2e39adf8dd0e33d7a94b1fc326b9090 GIT binary patch literal 4390 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 T<^(JL{>NCEA2Ta9@i_NCw3V}x literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Mods_128x64/frame_32.png b/assets/dolphin/external/L1_Mods_128x64/frame_32.png new file mode 100644 index 0000000000000000000000000000000000000000..acf000827ffb7177e5b7dbdc7935afc06a35d147 GIT binary patch literal 4402 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Mods_128x64/frame_33.png b/assets/dolphin/external/L1_Mods_128x64/frame_33.png new file mode 100644 index 0000000000000000000000000000000000000000..b6c6fbb19168b4e5b01f36c2a17bbe00336a4a7d GIT binary patch literal 4340 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 b7eWHHnZlOuxnixqe;n2pcIM@#p6C7teSDMt literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Mods_128x64/frame_34.png b/assets/dolphin/external/L1_Mods_128x64/frame_34.png new file mode 100644 index 0000000000000000000000000000000000000000..7d2dcda5d98ac939ae8263cb7db583cef6574fb7 GIT binary patch literal 4253 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 z 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
`cx7 zHpiOjL42rGG71I@4h~igR#PQWykQ6(9UT}P2}2^GEDdM?ok+#dpu_-$-wdX>04xPh zrs7FN@GnM;2Pu%M2Vpt;R}=)Ywe>&5#DKqZ#mX9th9Sccs&E*A@GG=GwF9Vjxc_(K zU$p}q=wuws4i`WQq+nTF;-&B#%u3&XH}p%9 z!i^CKGgEC-Q-rxW0%5KOH_|dO*ZhsOBnD71L@e&NZ9L2N-&nQ(iba`Fa2P6y;y@z# z|1Jbu9}<-m;6oyVk!q?M;KSA!ES~sFa^%-8{S~w+j)D)ud74v51n{3}M&bX(1x*cY zO-(Hf63Pk{2UW-5)S=p1+E}QLmzSrOr<$e)0-*-^8}IqQrVqw)3ihj3{-bt&i&zEu zYxqwyuqOYs6OPF0AquNmB%`IcSdGSSYvo|}^XE_25Cj4V3JMPC+wTAXdx@o~kppdD zJ`F^94dypnS=wO^<-g78<-ry%Sj{S4_sg7K96YBN1MTP#PH(-_g4B44Z9KFq~^=mqGNP{EgxF<0W`#-`{uwB z@N;dc_cQ%VfS^&IQ0=#_a+KW#Gj78lO4L@>mq`MRK>QqI&geA%ZDLi}SuRG>?ct{r z5)9Q7E^^KMqw3KVpq!WIxRhqCDu?hZiF`^R6VC>q4{jZQhKH7q@@8F>21?oU&kVv# zxIm+%+uq}btHHt}0tGvTY;c4AOTB!aeUXQEEn0le0Tt_4ng>*h>?`QH0^mv~J#-oc z%tv;DwR0eE33u~&UQH|l;hOc%Y(7D0o`t%+3hE#w9 Z;93Nk^X$3K`tbw+OS6-vrN-`;{|~Y=oI3yj literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Mods_128x64/frame_36.png b/assets/dolphin/external/L1_Mods_128x64/frame_36.png new file mode 100644 index 0000000000000000000000000000000000000000..b018a94c19c4cdd239a3043b1ffe49e93cb0f471 GIT binary patch literal 4315 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 Bcas1B literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Mods_128x64/frame_38.png b/assets/dolphin/external/L1_Mods_128x64/frame_38.png new file mode 100644 index 0000000000000000000000000000000000000000..ed38122f52a73a977c2ed044d22e8c113ae6c3f9 GIT binary patch literal 4301 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Mods_128x64/frame_39.png b/assets/dolphin/external/L1_Mods_128x64/frame_39.png new file mode 100644 index 0000000000000000000000000000000000000000..38610bb4b4a9aa68f97442f901f3318941a2305a GIT binary patch literal 4326 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 Lomr`g$L0S4vAUb6 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Mods_128x64/frame_4.png b/assets/dolphin/external/L1_Mods_128x64/frame_4.png new file mode 100644 index 0000000000000000000000000000000000000000..45e47de12adccf1b4deaa8bf4be3207357078bf2 GIT binary patch literal 4327 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Mods_128x64/frame_5.png b/assets/dolphin/external/L1_Mods_128x64/frame_5.png new file mode 100644 index 0000000000000000000000000000000000000000..7c293b4859a18b634ef661c0b206a41cc03ab31d GIT binary patch literal 4357 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 T11a*?C&S+Qh*g=zsSE!Hfg_nh literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Mods_128x64/frame_7.png b/assets/dolphin/external/L1_Mods_128x64/frame_7.png new file mode 100644 index 0000000000000000000000000000000000000000..5c840d6f642fdb36967d4646a3d3695ec2a12da5 GIT binary patch literal 4331 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Mods_128x64/meta.txt b/assets/dolphin/external/L1_Mods_128x64/meta.txt new file mode 100644 index 00000000000..0225c7e5593 --- /dev/null +++ b/assets/dolphin/external/L1_Mods_128x64/meta.txt @@ -0,0 +1,14 @@ +Filetype: Flipper Animation +Version: 1 + +Width: 128 +Height: 64 +Passive frames: 23 +Active frames: 18 +Frames order: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 +Active cycles: 1 +Frame rate: 2 +Duration: 3600 +Active cooldown: 7 + +Bubble slots: 0 \ No newline at end of file diff --git a/assets/dolphin/external/manifest.txt b/assets/dolphin/external/manifest.txt index 6bf6957c38b..a6c7ca694d9 100644 --- a/assets/dolphin/external/manifest.txt +++ b/assets/dolphin/external/manifest.txt @@ -85,12 +85,19 @@ Min level: 1 Max level: 3 Weight: 3 +Name: L1_Mods_128x64 +Min butthurt: 0 +Max butthurt: 9 +Min level: 1 +Max level: 3 +Weight: 5 + Name: L1_Painting_128x64 Min butthurt: 0 Max butthurt: 7 Min level: 1 Max level: 3 -Weight: 6 +Weight: 4 Name: L3_Hijack_radio_128x64 Min butthurt: 0 From d68ac50efdbc99039cc8a25963fc0ab5c7ee556e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Fri, 4 Nov 2022 14:26:04 +0900 Subject: [PATCH 200/824] Storage: tree timestamps (#1971) * Storage: tree timestamps * Rpc: add storage timestamp * Storage: correct timestamp owner * Storage: update timestamp at sd mount Co-authored-by: SG --- applications/services/rpc/rpc_storage.c | 38 +++++++++++++++++++ applications/services/storage/storage.c | 1 + applications/services/storage/storage.h | 10 +++++ applications/services/storage/storage_cli.c | 22 +++++++++++ .../services/storage/storage_external_api.c | 10 +++++ applications/services/storage/storage_glue.c | 8 ++++ applications/services/storage/storage_glue.h | 3 ++ applications/services/storage/storage_i.h | 1 + .../services/storage/storage_message.h | 7 ++++ .../services/storage/storage_processing.c | 29 ++++++++++++++ .../services/storage/storages/storage_ext.c | 1 + assets/protobuf | 2 +- firmware/targets/f7/api_symbols.csv | 4 +- firmware/targets/f7/furi_hal/furi_hal_rtc.c | 6 +++ .../targets/furi_hal_include/furi_hal_rtc.h | 2 + 15 files changed, 142 insertions(+), 2 deletions(-) diff --git a/applications/services/rpc/rpc_storage.c b/applications/services/rpc/rpc_storage.c index e28998e135c..16e343fcef5 100644 --- a/applications/services/rpc/rpc_storage.c +++ b/applications/services/rpc/rpc_storage.c @@ -138,6 +138,41 @@ static void rpc_system_storage_info_process(const PB_Main* request, void* contex furi_record_close(RECORD_STORAGE); } +static void rpc_system_storage_timestamp_process(const PB_Main* request, void* context) { + furi_assert(request); + furi_assert(context); + furi_assert(request->which_content == PB_Main_storage_timestamp_request_tag); + + FURI_LOG_D(TAG, "Timestamp"); + + RpcStorageSystem* rpc_storage = context; + RpcSession* session = rpc_storage->session; + furi_assert(session); + + rpc_system_storage_reset_state(rpc_storage, session, true); + + PB_Main* response = malloc(sizeof(PB_Main)); + response->command_id = request->command_id; + + Storage* fs_api = furi_record_open(RECORD_STORAGE); + + const char* path = request->content.storage_timestamp_request.path; + uint32_t timestamp = 0; + FS_Error error = storage_common_timestamp(fs_api, path, ×tamp); + + response->command_status = rpc_system_storage_get_error(error); + response->which_content = PB_Main_empty_tag; + + if(error == FSE_OK) { + response->which_content = PB_Main_storage_timestamp_response_tag; + response->content.storage_timestamp_response.timestamp = timestamp; + } + + rpc_send_and_release(session, response); + free(response); + furi_record_close(RECORD_STORAGE); +} + static void rpc_system_storage_stat_process(const PB_Main* request, void* context) { furi_assert(request); furi_assert(context); @@ -672,6 +707,9 @@ void* rpc_system_storage_alloc(RpcSession* session) { rpc_handler.message_handler = rpc_system_storage_info_process; rpc_add_handler(session, PB_Main_storage_info_request_tag, &rpc_handler); + rpc_handler.message_handler = rpc_system_storage_timestamp_process; + rpc_add_handler(session, PB_Main_storage_timestamp_request_tag, &rpc_handler); + rpc_handler.message_handler = rpc_system_storage_stat_process; rpc_add_handler(session, PB_Main_storage_stat_request_tag, &rpc_handler); diff --git a/applications/services/storage/storage.c b/applications/services/storage/storage.c index 700408c9d07..1816bf921cc 100644 --- a/applications/services/storage/storage.c +++ b/applications/services/storage/storage.c @@ -39,6 +39,7 @@ Storage* storage_app_alloc() { for(uint8_t i = 0; i < STORAGE_COUNT; i++) { storage_data_init(&app->storage[i]); + storage_data_timestamp(&app->storage[i]); } #ifndef FURI_RAM_EXEC diff --git a/applications/services/storage/storage.h b/applications/services/storage/storage.h index 968b69048e2..9c133e9be19 100644 --- a/applications/services/storage/storage.h +++ b/applications/services/storage/storage.h @@ -177,6 +177,16 @@ bool storage_dir_rewind(File* file); /******************* Common Functions *******************/ +/** Retrieves unix timestamp of last access + * + * @param storage The storage instance + * @param path path to file/directory + * @param timestamp the timestamp pointer + * + * @return FS_Error operation result + */ +FS_Error storage_common_timestamp(Storage* storage, const char* path, uint32_t* timestamp); + /** Retrieves information about a file/directory * @param app pointer to the api * @param path path to file/directory diff --git a/applications/services/storage/storage_cli.c b/applications/services/storage/storage_cli.c index 880fb9700e4..0efcb5e2c2b 100644 --- a/applications/services/storage/storage_cli.c +++ b/applications/services/storage/storage_cli.c @@ -32,6 +32,7 @@ static void storage_cli_print_usage() { printf("\tmkdir\t - creates a new directory\r\n"); printf("\tmd5\t - md5 hash of the file\r\n"); printf("\tstat\t - info about file or dir\r\n"); + printf("\ttimestamp\t - last modification timestamp\r\n"); }; static void storage_cli_print_error(FS_Error error) { @@ -386,6 +387,22 @@ static void storage_cli_stat(Cli* cli, FuriString* path) { furi_record_close(RECORD_STORAGE); } +static void storage_cli_timestamp(Cli* cli, FuriString* path) { + UNUSED(cli); + Storage* api = furi_record_open(RECORD_STORAGE); + + uint32_t timestamp = 0; + FS_Error error = storage_common_timestamp(api, furi_string_get_cstr(path), ×tamp); + + if(error != FSE_OK) { + printf("Invalid arguments\r\n"); + } else { + printf("Timestamp %lu\r\n", timestamp); + } + + furi_record_close(RECORD_STORAGE); +} + static void storage_cli_copy(Cli* cli, FuriString* old_path, FuriString* args) { UNUSED(cli); Storage* api = furi_record_open(RECORD_STORAGE); @@ -578,6 +595,11 @@ void storage_cli(Cli* cli, FuriString* args, void* context) { break; } + if(furi_string_cmp_str(cmd, "timestamp") == 0) { + storage_cli_timestamp(cli, path); + break; + } + storage_cli_print_usage(); } while(false); diff --git a/applications/services/storage/storage_external_api.c b/applications/services/storage/storage_external_api.c index c0c730fb77b..6854ef7f32c 100644 --- a/applications/services/storage/storage_external_api.c +++ b/applications/services/storage/storage_external_api.c @@ -354,6 +354,16 @@ bool storage_dir_rewind(File* file) { /****************** COMMON ******************/ +FS_Error storage_common_timestamp(Storage* storage, const char* path, uint32_t* timestamp) { + S_API_PROLOGUE; + + SAData data = {.ctimestamp = {.path = path, .timestamp = timestamp}}; + + S_API_MESSAGE(StorageCommandCommonTimestamp); + S_API_EPILOGUE; + return S_RETURN_ERROR; +} + FS_Error storage_common_stat(Storage* storage, const char* path, FileInfo* fileinfo) { S_API_PROLOGUE; diff --git a/applications/services/storage/storage_glue.c b/applications/services/storage/storage_glue.c index c5682f67bf7..c6ff08bdc83 100644 --- a/applications/services/storage/storage_glue.c +++ b/applications/services/storage/storage_glue.c @@ -82,6 +82,14 @@ const char* storage_data_status_text(StorageData* storage) { return result; } +void storage_data_timestamp(StorageData* storage) { + storage->timestamp = furi_hal_rtc_get_timestamp(); +} + +uint32_t storage_data_get_timestamp(StorageData* storage) { + return storage->timestamp; +} + /****************** storage glue ******************/ bool storage_has_file(const File* file, StorageData* storage_data) { diff --git a/applications/services/storage/storage_glue.h b/applications/services/storage/storage_glue.h index 53fa0de1909..6fdc700997c 100644 --- a/applications/services/storage/storage_glue.h +++ b/applications/services/storage/storage_glue.h @@ -42,6 +42,8 @@ bool storage_data_lock(StorageData* storage); bool storage_data_unlock(StorageData* storage); StorageStatus storage_data_status(StorageData* storage); const char* storage_data_status_text(StorageData* storage); +void storage_data_timestamp(StorageData* storage); +uint32_t storage_data_get_timestamp(StorageData* storage); LIST_DEF( StorageFileList, @@ -58,6 +60,7 @@ struct StorageData { FuriMutex* mutex; StorageStatus status; StorageFileList_t files; + uint32_t timestamp; }; bool storage_has_file(const File* file, StorageData* storage_data); diff --git a/applications/services/storage/storage_i.h b/applications/services/storage/storage_i.h index 5c836ccd295..406fc921ef9 100644 --- a/applications/services/storage/storage_i.h +++ b/applications/services/storage/storage_i.h @@ -1,5 +1,6 @@ #pragma once #include +#include #include #include "storage_glue.h" #include "storage_sd_api.h" diff --git a/applications/services/storage/storage_message.h b/applications/services/storage/storage_message.h index 78cd1e03cc5..98726801792 100644 --- a/applications/services/storage/storage_message.h +++ b/applications/services/storage/storage_message.h @@ -42,6 +42,11 @@ typedef struct { uint16_t name_length; } SADataDRead; +typedef struct { + const char* path; + uint32_t* timestamp; +} SADataCTimestamp; + typedef struct { const char* path; FileInfo* fileinfo; @@ -78,6 +83,7 @@ typedef union { SADataDOpen dopen; SADataDRead dread; + SADataCTimestamp ctimestamp; SADataCStat cstat; SADataCFSInfo cfsinfo; @@ -112,6 +118,7 @@ typedef enum { StorageCommandDirClose, StorageCommandDirRead, StorageCommandDirRewind, + StorageCommandCommonTimestamp, StorageCommandCommonStat, StorageCommandCommonRemove, StorageCommandCommonMkDir, diff --git a/applications/services/storage/storage_processing.c b/applications/services/storage/storage_processing.c index 8643e974e67..795a5d11c8e 100644 --- a/applications/services/storage/storage_processing.c +++ b/applications/services/storage/storage_processing.c @@ -114,6 +114,9 @@ bool storage_process_file_open( if(storage_path_already_open(real_path, storage->files)) { file->error_id = FSE_ALREADY_OPEN; } else { + if(access_mode & FSAM_WRITE) { + storage_data_timestamp(storage); + } storage_push_storage_file(file, real_path, type, storage); FS_CALL(storage, file.open(storage, file, remove_vfs(path), access_mode, open_mode)); } @@ -166,6 +169,7 @@ static uint16_t storage_process_file_write( if(storage == NULL) { file->error_id = FSE_INVALID_PARAMETER; } else { + storage_data_timestamp(storage); FS_CALL(storage, file.write(storage, file, buff, bytes_to_write)); } @@ -209,6 +213,7 @@ static bool storage_process_file_truncate(Storage* app, File* file) { if(storage == NULL) { file->error_id = FSE_INVALID_PARAMETER; } else { + storage_data_timestamp(storage); FS_CALL(storage, file.truncate(storage, file)); } @@ -222,6 +227,7 @@ static bool storage_process_file_sync(Storage* app, File* file) { if(storage == NULL) { file->error_id = FSE_INVALID_PARAMETER; } else { + storage_data_timestamp(storage); FS_CALL(storage, file.sync(storage, file)); } @@ -332,6 +338,21 @@ bool storage_process_dir_rewind(Storage* app, File* file) { /******************* Common FS Functions *******************/ +static FS_Error + storage_process_common_timestamp(Storage* app, const char* path, uint32_t* timestamp) { + FS_Error ret = FSE_OK; + StorageType type = storage_get_type_by_path(app, path); + + if(storage_type_is_not_valid(type)) { + ret = FSE_INVALID_NAME; + } else { + StorageData* storage = storage_get_storage_by_type(app, type); + *timestamp = storage_data_get_timestamp(storage); + } + + return ret; +} + static FS_Error storage_process_common_stat(Storage* app, const char* path, FileInfo* fileinfo) { FS_Error ret = FSE_OK; StorageType type = storage_get_type_by_path(app, path); @@ -366,6 +387,7 @@ static FS_Error storage_process_common_remove(Storage* app, const char* path) { break; } + storage_data_timestamp(storage); FS_CALL(storage, common.remove(storage, remove_vfs(path))); } while(false); @@ -382,6 +404,7 @@ static FS_Error storage_process_common_mkdir(Storage* app, const char* path) { ret = FSE_INVALID_NAME; } else { StorageData* storage = storage_get_storage_by_type(app, type); + storage_data_timestamp(storage); FS_CALL(storage, common.mkdir(storage, remove_vfs(path))); } @@ -417,6 +440,7 @@ static FS_Error storage_process_sd_format(Storage* app) { ret = FSE_NOT_READY; } else { ret = sd_format_card(&app->storage[ST_EXT]); + storage_data_timestamp(&app->storage[ST_EXT]); } return ret; @@ -429,6 +453,7 @@ static FS_Error storage_process_sd_unmount(Storage* app) { ret = FSE_NOT_READY; } else { sd_unmount_card(&app->storage[ST_EXT]); + storage_data_timestamp(&app->storage[ST_EXT]); } return ret; @@ -541,6 +566,10 @@ void storage_process_message_internal(Storage* app, StorageMessage* message) { message->return_data->bool_value = storage_process_dir_rewind(app, message->data->file.file); break; + case StorageCommandCommonTimestamp: + message->return_data->error_value = storage_process_common_timestamp( + app, message->data->ctimestamp.path, message->data->ctimestamp.timestamp); + break; case StorageCommandCommonStat: message->return_data->error_value = storage_process_common_stat( app, message->data->cstat.path, message->data->cstat.fileinfo); diff --git a/applications/services/storage/storages/storage_ext.c b/applications/services/storage/storages/storage_ext.c index 7341a6ec86e..0c81a0006c2 100644 --- a/applications/services/storage/storages/storage_ext.c +++ b/applications/services/storage/storages/storage_ext.c @@ -90,6 +90,7 @@ static bool sd_mount_card(StorageData* storage, bool notify) { } } + storage_data_timestamp(storage); storage_data_unlock(storage); return result; diff --git a/assets/protobuf b/assets/protobuf index 6727eaf287d..e5af96e08fe 160000 --- a/assets/protobuf +++ b/assets/protobuf @@ -1 +1 @@ -Subproject commit 6727eaf287db077dcd28719cd764f5804712223e +Subproject commit e5af96e08fea8351898f7b8c6d1e34ce5fd6cdef diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 0d9a5b56128..65e6f48577e 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,7.2,, +Version,+,7.3,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -1251,6 +1251,7 @@ Function,+,furi_hal_rtc_get_fault_data,uint32_t, Function,+,furi_hal_rtc_get_log_level,uint8_t, Function,+,furi_hal_rtc_get_pin_fails,uint32_t, Function,+,furi_hal_rtc_get_register,uint32_t,FuriHalRtcRegister +Function,+,furi_hal_rtc_get_timestamp,uint32_t, Function,-,furi_hal_rtc_init,void, Function,-,furi_hal_rtc_init_early,void, Function,+,furi_hal_rtc_is_flag_set,_Bool,FuriHalRtcFlag @@ -2235,6 +2236,7 @@ Function,+,storage_common_mkdir,FS_Error,"Storage*, const char*" Function,+,storage_common_remove,FS_Error,"Storage*, const char*" Function,+,storage_common_rename,FS_Error,"Storage*, const char*, const char*" Function,+,storage_common_stat,FS_Error,"Storage*, const char*, FileInfo*" +Function,+,storage_common_timestamp,FS_Error,"Storage*, const char*, uint32_t*" Function,+,storage_dir_close,_Bool,File* Function,+,storage_dir_open,_Bool,"File*, const char*" Function,+,storage_dir_read,_Bool,"File*, FileInfo*, char*, uint16_t" diff --git a/firmware/targets/f7/furi_hal/furi_hal_rtc.c b/firmware/targets/f7/furi_hal/furi_hal_rtc.c index 24dad38fa02..14ece9466dc 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_rtc.c +++ b/firmware/targets/f7/furi_hal/furi_hal_rtc.c @@ -318,6 +318,12 @@ uint32_t furi_hal_rtc_get_pin_fails() { return furi_hal_rtc_get_register(FuriHalRtcRegisterPinFails); } +uint32_t furi_hal_rtc_get_timestamp() { + FuriHalRtcDateTime datetime = {0}; + furi_hal_rtc_get_datetime(&datetime); + return furi_hal_rtc_datetime_to_timestamp(&datetime); +} + uint32_t furi_hal_rtc_datetime_to_timestamp(FuriHalRtcDateTime* datetime) { uint32_t timestamp = 0; uint8_t years = 0; diff --git a/firmware/targets/furi_hal_include/furi_hal_rtc.h b/firmware/targets/furi_hal_include/furi_hal_rtc.h index bdae3b93100..361225fb240 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_get_timestamp(); + uint32_t furi_hal_rtc_datetime_to_timestamp(FuriHalRtcDateTime* datetime); #ifdef __cplusplus From 3bd74b7f0108690f451b8a1e83fc9f8d121aca61 Mon Sep 17 00:00:00 2001 From: Sergey Monchenko <14358100+msvsergey@users.noreply.github.com> Date: Fri, 4 Nov 2022 09:08:51 +0300 Subject: [PATCH 201/824] SubGhz: fix incorrect response in rpc mode. Code cleanup. (#1964) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Code cleanup * SubGhz: correct logic in RPC * SubGhz: do not blink on app rpc fail Co-authored-by: あく --- .../scenes/subghz_scene_receiver_info.c | 4 +--- .../main/subghz/scenes/subghz_scene_rpc.c | 3 +-- .../dap_link/gui/scenes/dap_scene_config.c | 4 ++-- applications/services/cli/cli_command_gpio.c | 4 ++-- .../desktop/views/desktop_view_locked.c | 2 +- applications/services/gui/view_dispatcher.c | 3 +-- .../services/notification/notification_app.c | 2 +- .../power_settings_app/views/battery_info.c | 21 ++++++++++++++----- 8 files changed, 25 insertions(+), 18 deletions(-) diff --git a/applications/main/subghz/scenes/subghz_scene_receiver_info.c b/applications/main/subghz/scenes/subghz_scene_receiver_info.c index bc6c8f112eb..c0f90157849 100644 --- a/applications/main/subghz/scenes/subghz_scene_receiver_info.c +++ b/applications/main/subghz/scenes/subghz_scene_receiver_info.c @@ -155,9 +155,7 @@ bool subghz_scene_receiver_info_on_event(void* context, SceneManagerEvent event) } else if(event.event == SubGhzCustomEventSceneReceiverInfoSave) { //CC1101 Stop RX -> Save subghz->state_notifications = SubGhzNotificationStateIDLE; - if(subghz->txrx->hopper_state != SubGhzHopperStateOFF) { - subghz->txrx->hopper_state = SubGhzHopperStateOFF; - } + subghz->txrx->hopper_state = SubGhzHopperStateOFF; if(subghz->txrx->txrx_state == SubGhzTxRxStateRx) { subghz_rx_end(subghz); subghz_sleep(subghz); diff --git a/applications/main/subghz/scenes/subghz_scene_rpc.c b/applications/main/subghz/scenes/subghz_scene_rpc.c index 1f06f4f9e41..79295aaa6a1 100644 --- a/applications/main/subghz/scenes/subghz_scene_rpc.c +++ b/applications/main/subghz/scenes/subghz_scene_rpc.c @@ -40,9 +40,8 @@ bool subghz_scene_rpc_on_event(void* context, SceneManagerEvent event) { bool result = false; if((subghz->txrx->txrx_state == SubGhzTxRxStateSleep) && (state == SubGhzRpcStateLoaded)) { - subghz_blink_start(subghz); result = subghz_tx_start(subghz, subghz->txrx->fff_data); - result = true; + if(result) subghz_blink_start(subghz); } rpc_system_app_confirm(subghz->rpc_ctx, RpcAppEventButtonPress, result); } else if(event.event == SubGhzCustomEventSceneRpcButtonRelease) { diff --git a/applications/plugins/dap_link/gui/scenes/dap_scene_config.c b/applications/plugins/dap_link/gui/scenes/dap_scene_config.c index 56b06411c78..48d5fedcd08 100644 --- a/applications/plugins/dap_link/gui/scenes/dap_scene_config.c +++ b/applications/plugins/dap_link/gui/scenes/dap_scene_config.c @@ -72,8 +72,8 @@ void dap_scene_config_on_enter(void* context) { variable_item_set_current_value_index(item, config->uart_swap); variable_item_set_current_value_text(item, uart_swap[config->uart_swap]); - item = variable_item_list_add(var_item_list, "Help and Pinout", 0, NULL, NULL); - item = variable_item_list_add(var_item_list, "About", 0, NULL, NULL); + variable_item_list_add(var_item_list, "Help and Pinout", 0, NULL, NULL); + variable_item_list_add(var_item_list, "About", 0, NULL, NULL); variable_item_list_set_selected_item( var_item_list, scene_manager_get_scene_state(app->scene_manager, DapSceneConfig)); diff --git a/applications/services/cli/cli_command_gpio.c b/applications/services/cli/cli_command_gpio.c index 54671eda439..d072ce00c98 100644 --- a/applications/services/cli/cli_command_gpio.c +++ b/applications/services/cli/cli_command_gpio.c @@ -40,7 +40,7 @@ static bool pin_name_to_int(FuriString* pin_name, size_t* result) { bool debug = furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug); for(size_t i = 0; i < COUNT_OF(cli_command_gpio_pins); i++) { if(!furi_string_cmp(pin_name, cli_command_gpio_pins[i].name)) { - if(!cli_command_gpio_pins[i].debug || (cli_command_gpio_pins[i].debug && debug)) { + if(!cli_command_gpio_pins[i].debug || debug) { *result = i; found = true; break; @@ -55,7 +55,7 @@ static void gpio_print_pins(void) { printf("Wrong pin name. Available pins: "); bool debug = furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug); for(size_t i = 0; i < COUNT_OF(cli_command_gpio_pins); i++) { - if(!cli_command_gpio_pins[i].debug || (cli_command_gpio_pins[i].debug && debug)) { + if(!cli_command_gpio_pins[i].debug || debug) { printf("%s ", cli_command_gpio_pins[i].name); } } diff --git a/applications/services/desktop/views/desktop_view_locked.c b/applications/services/desktop/views/desktop_view_locked.c index d18ed6c98c0..0bf7570361e 100644 --- a/applications/services/desktop/views/desktop_view_locked.c +++ b/applications/services/desktop/views/desktop_view_locked.c @@ -160,7 +160,7 @@ static bool desktop_view_locked_input(InputEvent* event, void* context) { view_commit_model(locked_view->view, is_changed); if(view_state == DesktopViewLockedStateUnlocked) { - return view_state != DesktopViewLockedStateUnlocked; + return false; } else if(view_state == DesktopViewLockedStateLocked && pin_locked) { locked_view->callback(DesktopLockedEventShowPinInput, locked_view->context); } else if( diff --git a/applications/services/gui/view_dispatcher.c b/applications/services/gui/view_dispatcher.c index 1736558cbbf..8de452d205d 100644 --- a/applications/services/gui/view_dispatcher.c +++ b/applications/services/gui/view_dispatcher.c @@ -304,8 +304,7 @@ void view_dispatcher_handle_custom_event(ViewDispatcher* view_dispatcher, uint32 } // If custom event is not consumed in View, call callback if(!is_consumed && view_dispatcher->custom_event_callback) { - is_consumed = - view_dispatcher->custom_event_callback(view_dispatcher->event_context, event); + view_dispatcher->custom_event_callback(view_dispatcher->event_context, event); } } diff --git a/applications/services/notification/notification_app.c b/applications/services/notification/notification_app.c index 640bd7d71fe..6091f0aa7f2 100644 --- a/applications/services/notification/notification_app.c +++ b/applications/services/notification/notification_app.c @@ -22,7 +22,7 @@ static const uint8_t reset_blink_mask = 1 << 6; void notification_vibro_on(); void notification_vibro_off(); -void notification_sound_on(float pwm, float freq); +void notification_sound_on(float freq, float volume); void notification_sound_off(); uint8_t notification_settings_get_display_brightness(NotificationApp* app, uint8_t value); diff --git a/applications/settings/power_settings_app/views/battery_info.c b/applications/settings/power_settings_app/views/battery_info.c index e1b7adb4bd8..bbb0acb9a61 100644 --- a/applications/settings/power_settings_app/views/battery_info.c +++ b/applications/settings/power_settings_app/views/battery_info.c @@ -3,6 +3,9 @@ #include #include +#define LOW_CHARGE_THRESHOLD 10 +#define HIGH_DRAIN_CURRENT_THRESHOLD 100 + struct BatteryInfo { View* view; }; @@ -28,9 +31,9 @@ static void draw_battery(Canvas* canvas, BatteryInfoModel* data, int x, int y) { canvas_draw_icon(canvas, x, y, &I_BatteryBody_52x28); if(charge_current > 0) { canvas_draw_icon(canvas, x + 16, y + 7, &I_FaceCharging_29x14); - } else if(drain_current > 100) { + } else if(drain_current > HIGH_DRAIN_CURRENT_THRESHOLD) { canvas_draw_icon(canvas, x + 16, y + 7, &I_FaceConfused_29x14); - } else if(data->charge < 10) { + } else if(data->charge < LOW_CHARGE_THRESHOLD) { canvas_draw_icon(canvas, x + 16, y + 7, &I_FaceNopower_29x14); } else { canvas_draw_icon(canvas, x + 16, y + 7, &I_FaceNormal_29x14); @@ -51,11 +54,19 @@ static void draw_battery(Canvas* canvas, BatteryInfoModel* data, int x, int y) { (uint32_t)(data->vbus_voltage * 10) % 10, charge_current); } else if(drain_current > 0) { - snprintf(emote, sizeof(emote), "%s", drain_current > 100 ? "Oh no!" : "Om-nom-nom!"); + snprintf( + emote, + sizeof(emote), + "%s", + drain_current > HIGH_DRAIN_CURRENT_THRESHOLD ? "Oh no!" : "Om-nom-nom!"); snprintf(header, sizeof(header), "%s", "Consumption is"); snprintf( - value, sizeof(value), "%ld %s", drain_current, drain_current > 100 ? "mA!" : "mA"); - } else if(charge_current != 0 || drain_current != 0) { + value, + sizeof(value), + "%ld %s", + drain_current, + drain_current > HIGH_DRAIN_CURRENT_THRESHOLD ? "mA!" : "mA"); + } else if(drain_current != 0) { snprintf(header, 20, "..."); } else { snprintf(header, sizeof(header), "Charged!"); From bf8fd71c00be90015811b9e6b13b88d0e7b1d5ee Mon Sep 17 00:00:00 2001 From: gornekich Date: Fri, 4 Nov 2022 11:01:44 +0400 Subject: [PATCH 202/824] NFC magic cards support (#1966) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * nfc magic: introduce nfc app to work with magic cards * nfc: add nfc device functions to API * nfc magic: add bacis scenes * nfc magic: add wrong card and write confirm scenes * nfc magic: introduce magic lib * nfc magic: write magic lib * nfc magic: add write commands to magic lib * nfc magic: work on worker * furi_hal_nfc: add bits data exchage method to API * nfc magic: rework with new API * nfc magic: add check and wipe scenes * nfc magic: add icons, gui fixes * nfc: format python src Co-authored-by: あく --- .../plugins/nfc_magic/application.fam | 20 ++ .../nfc_magic/assets/DolphinCommon_56x48.png | Bin 0 -> 1416 bytes .../nfc_magic/assets/DolphinNice_96x59.png | Bin 0 -> 2459 bytes .../plugins/nfc_magic/assets/Loading_24.png | Bin 0 -> 3649 bytes .../nfc_magic/assets/NFC_manual_60x50.png | Bin 0 -> 3804 bytes .../plugins/nfc_magic/lib/magic/magic.c | 214 ++++++++++++++++++ .../plugins/nfc_magic/lib/magic/magic.h | 15 ++ applications/plugins/nfc_magic/nfc_magic.c | 169 ++++++++++++++ applications/plugins/nfc_magic/nfc_magic.h | 3 + applications/plugins/nfc_magic/nfc_magic_i.h | 77 +++++++ .../plugins/nfc_magic/nfc_magic_worker.c | 174 ++++++++++++++ .../plugins/nfc_magic/nfc_magic_worker.h | 38 ++++ .../plugins/nfc_magic/nfc_magic_worker_i.h | 24 ++ .../nfc_magic/scenes/nfc_magic_scene.c | 30 +++ .../nfc_magic/scenes/nfc_magic_scene.h | 29 +++ .../nfc_magic/scenes/nfc_magic_scene_check.c | 87 +++++++ .../nfc_magic/scenes/nfc_magic_scene_config.h | 12 + .../scenes/nfc_magic_scene_file_select.c | 34 +++ .../scenes/nfc_magic_scene_magic_info.c | 45 ++++ .../scenes/nfc_magic_scene_not_magic.c | 44 ++++ .../nfc_magic/scenes/nfc_magic_scene_start.c | 61 +++++ .../scenes/nfc_magic_scene_success.c | 42 ++++ .../nfc_magic/scenes/nfc_magic_scene_wipe.c | 90 ++++++++ .../scenes/nfc_magic_scene_wipe_fail.c | 41 ++++ .../nfc_magic/scenes/nfc_magic_scene_write.c | 90 ++++++++ .../scenes/nfc_magic_scene_write_confirm.c | 64 ++++++ .../scenes/nfc_magic_scene_write_fail.c | 58 +++++ .../scenes/nfc_magic_scene_wrong_card.c | 53 +++++ firmware/targets/f7/api_symbols.csv | 121 ++++++++++ firmware/targets/f7/furi_hal/furi_hal_nfc.c | 11 + .../targets/furi_hal_include/furi_hal_nfc.h | 10 + lib/ST25RFAL002/include/rfal_rf.h | 9 + .../source/st25r3916/rfal_rfst25r3916.c | 17 ++ lib/nfc/SConscript | 3 + lib/nfc/nfc_device.h | 8 + 35 files changed, 1693 insertions(+) create mode 100644 applications/plugins/nfc_magic/application.fam create mode 100644 applications/plugins/nfc_magic/assets/DolphinCommon_56x48.png create mode 100644 applications/plugins/nfc_magic/assets/DolphinNice_96x59.png create mode 100644 applications/plugins/nfc_magic/assets/Loading_24.png create mode 100644 applications/plugins/nfc_magic/assets/NFC_manual_60x50.png create mode 100644 applications/plugins/nfc_magic/lib/magic/magic.c create mode 100644 applications/plugins/nfc_magic/lib/magic/magic.h create mode 100644 applications/plugins/nfc_magic/nfc_magic.c create mode 100644 applications/plugins/nfc_magic/nfc_magic.h create mode 100644 applications/plugins/nfc_magic/nfc_magic_i.h create mode 100644 applications/plugins/nfc_magic/nfc_magic_worker.c create mode 100644 applications/plugins/nfc_magic/nfc_magic_worker.h create mode 100644 applications/plugins/nfc_magic/nfc_magic_worker_i.h create mode 100644 applications/plugins/nfc_magic/scenes/nfc_magic_scene.c create mode 100644 applications/plugins/nfc_magic/scenes/nfc_magic_scene.h create mode 100644 applications/plugins/nfc_magic/scenes/nfc_magic_scene_check.c create mode 100644 applications/plugins/nfc_magic/scenes/nfc_magic_scene_config.h create mode 100644 applications/plugins/nfc_magic/scenes/nfc_magic_scene_file_select.c create mode 100644 applications/plugins/nfc_magic/scenes/nfc_magic_scene_magic_info.c create mode 100644 applications/plugins/nfc_magic/scenes/nfc_magic_scene_not_magic.c create mode 100644 applications/plugins/nfc_magic/scenes/nfc_magic_scene_start.c create mode 100644 applications/plugins/nfc_magic/scenes/nfc_magic_scene_success.c create mode 100644 applications/plugins/nfc_magic/scenes/nfc_magic_scene_wipe.c create mode 100644 applications/plugins/nfc_magic/scenes/nfc_magic_scene_wipe_fail.c create mode 100644 applications/plugins/nfc_magic/scenes/nfc_magic_scene_write.c create mode 100644 applications/plugins/nfc_magic/scenes/nfc_magic_scene_write_confirm.c create mode 100644 applications/plugins/nfc_magic/scenes/nfc_magic_scene_write_fail.c create mode 100644 applications/plugins/nfc_magic/scenes/nfc_magic_scene_wrong_card.c diff --git a/applications/plugins/nfc_magic/application.fam b/applications/plugins/nfc_magic/application.fam new file mode 100644 index 00000000000..f09d65c90cc --- /dev/null +++ b/applications/plugins/nfc_magic/application.fam @@ -0,0 +1,20 @@ +App( + appid="nfc_magic", + name="Nfc Magic", + apptype=FlipperAppType.EXTERNAL, + entry_point="nfc_magic_app", + requires=[ + "storage", + "gui", + ], + stack_size=4 * 1024, + order=30, + fap_icon="../../../assets/icons/Archive/125_10px.png", + fap_category="Tools", + fap_private_libs=[ + Lib( + name="magic", + ), + ], + fap_icon_assets="assets", +) diff --git a/applications/plugins/nfc_magic/assets/DolphinCommon_56x48.png b/applications/plugins/nfc_magic/assets/DolphinCommon_56x48.png new file mode 100644 index 0000000000000000000000000000000000000000..089aaed83507431993a76ca25d32fdd9664c1c84 GIT binary patch literal 1416 zcmaJ>eNYr-7(dh;KXS5&nWVIBjS_NizYg|x=Pr^vz*7zxJO|P-dw2IeZq?gec9-rD zoPZchQ_6}yP{Slc4I!!28K==nodOJ_nsCY-(wOq2uZbLx!rlYU{KIi)_Wj!D_j`WN z^FGgREXdEDF)ewT&1Re7Tj(uBvlG44lnH3;I%IzsO|z`*Vr!`uv?9QOwgs{#Ld+Ki zC9n_zxxBOkx@@+IwMwAaD)#3Ik`}gun2kLe))Crfb7e+#AgzHGCc+X$b>qJuIf`S7 z?8b}I{ghw#z>uiaLknQh@LJUrqHcVYS3v97F^OZN zCe|7^J|?QzUx0Zu17e(=CM1fYFpjtLk|a4~$g}e?hGH0!VoBOT&<=s(1ct%J9~?O} z$)jW_dkX9yTX~%W*i_IM%0{ z7EmP^_pKn`<5>E(SixgJU};7`)7Hidp&+DLnizsebUk}_-GfgbN^il9b`v)f+ z{o5Zry)d<7`fHQ^uw_;+x>mcPw0&8iW69x{k92O{Q}`yFdH=5d$pbf49w1&NS)G+vhr6y}5TMsofQirRDUmKilk5=(KGouJ{H9hW=$X zgi;)vI!jl!_4H3jD(?Jz=8By|i47I&tKA1y9{nfp;_|FxKBDNWp{hN9hJ1nU?z%J6 z?>UxyzWvO}Pgc~rCZ#5%Eq+_hNS~bBdiGlT&f%%e`hHjSySR2=JuK2^+%;$R3#Wz~ z=e_mfqW23bPa0fhe)HdE5+GelU&!jS3ckUZOQ)CC5?mo zo=tzG_4|RuvPUO|mhCwA>y)1c%SWC%a4?a-x|J*?ch~+n=R7o@>p6J2dE=$stKZmK z-xoTRwET2^Wu)&1U7!Ebw!!D?x`xwQX3pMnrRwCT?`4GHt4&?|cIiI{_^XYp-np>6 xE^lPSXzOYCC4X`6tl@OB1M5_S7jml-Y~(TPp{aTIejNKZ`m*!Atyxdk{0EAy49frj literal 0 HcmV?d00001 diff --git a/applications/plugins/nfc_magic/assets/DolphinNice_96x59.png b/applications/plugins/nfc_magic/assets/DolphinNice_96x59.png new file mode 100644 index 0000000000000000000000000000000000000000..a299d3630239b4486e249cc501872bed5996df3b GIT binary patch literal 2459 zcmbVO3s4i+8V(M(gEFORwSrA`4O0uPn|M|5y* zB*aMDxC&7(gP9JN;POOi-9khrC>Z9YJs2U!LnVcQEEC0fDtKo&ILlzb30%M}3J^;~ zv7RzcsilOs4Mq@tD*&R;!LMSk2A~{(`HK9|hQBqEX)3sQr9Je6SZU*F-^fD-p+~Hs; zHLkO%v?>ZoxEv+F#whudr%615FkA0DYR0tMEo}3OOY#xecLWe>xV?u5KtSmC^ z7)Fmj6gjfKstiEV-*Cxbbb+&rRWuI_rBJ)ybs_f1Rn&f2>q3pYwI^|J(hdn{j{0EZIm_F zpIyIWLsRUgOItR-dUbVd|6Zo=_BU_Tj4|{{jxO#=JH4o8er(5{!nZD_j4}MH&zh~9 zVLC~y(0-D6GO0ghZD8BYzP?o{>22~lT6^d@X{SwQ8vrNY-PPIMajIwC)`s14Ep72@ zeq7YOzM`?U{+W)ocXBr`eSOcpk?Rxc=ou5&)fWW|pD};-Z0mvk9}=&`Rb&y<77W~a z(>6YM;6Y5aIU~JKZ}mQZynKHiSTQ#Bczn@&jTiN^?vPJ(jhm7cXLx0oum5P$`TceG zU+wR;OO^)8CVlnM)5p$CO&e94KJt>HccCaHGusmW_b`T6m| z-R6V6Db1pErTot?^d22ojm+2>_)FbD`_+WbDGMx9f@hO27maS2`csiV(D&Fs`PS2& zvrq18du_&zXID(!KIxsU$)iuTYuZ?zmYiP&n&i@Be{IdbS-jA2c0QAlu5NXQv_0K< z3Hvs4eeu6B7yD&CNT~gIkMV&UkRU=V!iQ(+_(O&u^ah$+s{_yn(yBYeD40HeU{xGsIT6W Zfq!wOp!QR4Q+~K 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 LTALIbyM_M?u*zuP literal 0 HcmV?d00001 diff --git a/applications/plugins/nfc_magic/assets/NFC_manual_60x50.png b/applications/plugins/nfc_magic/assets/NFC_manual_60x50.png new file mode 100644 index 0000000000000000000000000000000000000000..787c0bcfe01755f4dcadcdce004a1a0fcfb06f41 GIT binary patch literal 3804 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 IrsqTc1+$%)k^lez literal 0 HcmV?d00001 diff --git a/applications/plugins/nfc_magic/lib/magic/magic.c b/applications/plugins/nfc_magic/lib/magic/magic.c new file mode 100644 index 00000000000..3cfca748b4f --- /dev/null +++ b/applications/plugins/nfc_magic/lib/magic/magic.c @@ -0,0 +1,214 @@ +#include "magic.h" + +#include + +#define TAG "Magic" + +#define MAGIC_CMD_WUPA (0x40) +#define MAGIC_CMD_WIPE (0x41) +#define MAGIC_CMD_READ (0x43) +#define MAGIC_CMD_WRITE (0x43) + +#define MAGIC_MIFARE_READ_CMD (0x30) +#define MAGIC_MIFARE_WRITE_CMD (0xA0) + +#define MAGIC_ACK (0x0A) + +#define MAGIC_BUFFER_SIZE (32) + +bool magic_wupa() { + bool magic_activated = false; + uint8_t tx_data[MAGIC_BUFFER_SIZE] = {}; + uint8_t rx_data[MAGIC_BUFFER_SIZE] = {}; + uint16_t rx_len = 0; + FuriHalNfcReturn ret = 0; + + do { + // Setup nfc poller + furi_hal_nfc_exit_sleep(); + furi_hal_nfc_ll_txrx_on(); + furi_hal_nfc_ll_poll(); + ret = furi_hal_nfc_ll_set_mode( + FuriHalNfcModePollNfca, FuriHalNfcBitrate106, FuriHalNfcBitrate106); + if(ret != FuriHalNfcReturnOk) break; + + furi_hal_nfc_ll_set_fdt_listen(FURI_HAL_NFC_LL_FDT_LISTEN_NFCA_POLLER); + furi_hal_nfc_ll_set_fdt_poll(FURI_HAL_NFC_LL_FDT_POLL_NFCA_POLLER); + furi_hal_nfc_ll_set_error_handling(FuriHalNfcErrorHandlingNfc); + furi_hal_nfc_ll_set_guard_time(FURI_HAL_NFC_LL_GT_NFCA); + + // Start communication + tx_data[0] = MAGIC_CMD_WUPA; + ret = furi_hal_nfc_ll_txrx_bits( + tx_data, + 7, + rx_data, + sizeof(rx_data), + &rx_len, + FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_TX_MANUAL | FURI_HAL_NFC_LL_TXRX_FLAGS_AGC_ON | + FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_RX_KEEP, + furi_hal_nfc_ll_ms2fc(20)); + if(ret != FuriHalNfcReturnIncompleteByte) break; + if(rx_len != 4) break; + if(rx_data[0] != MAGIC_ACK) break; + magic_activated = true; + } while(false); + + if(!magic_activated) { + furi_hal_nfc_ll_txrx_off(); + furi_hal_nfc_start_sleep(); + } + + return magic_activated; +} + +bool magic_data_access_cmd() { + bool write_cmd_success = false; + uint8_t tx_data[MAGIC_BUFFER_SIZE] = {}; + uint8_t rx_data[MAGIC_BUFFER_SIZE] = {}; + uint16_t rx_len = 0; + FuriHalNfcReturn ret = 0; + + do { + tx_data[0] = MAGIC_CMD_WRITE; + ret = furi_hal_nfc_ll_txrx_bits( + tx_data, + 8, + rx_data, + sizeof(rx_data), + &rx_len, + FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_TX_MANUAL | FURI_HAL_NFC_LL_TXRX_FLAGS_AGC_ON | + FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_RX_KEEP, + furi_hal_nfc_ll_ms2fc(20)); + if(ret != FuriHalNfcReturnIncompleteByte) break; + if(rx_len != 4) break; + if(rx_data[0] != MAGIC_ACK) break; + + write_cmd_success = true; + } while(false); + + if(!write_cmd_success) { + furi_hal_nfc_ll_txrx_off(); + furi_hal_nfc_start_sleep(); + } + + return write_cmd_success; +} + +bool magic_read_block(uint8_t block_num, MfClassicBlock* data) { + furi_assert(data); + + bool read_success = false; + + uint8_t tx_data[MAGIC_BUFFER_SIZE] = {}; + uint8_t rx_data[MAGIC_BUFFER_SIZE] = {}; + uint16_t rx_len = 0; + FuriHalNfcReturn ret = 0; + + do { + tx_data[0] = MAGIC_MIFARE_READ_CMD; + tx_data[1] = block_num; + ret = furi_hal_nfc_ll_txrx_bits( + tx_data, + 2 * 8, + rx_data, + sizeof(rx_data), + &rx_len, + FURI_HAL_NFC_LL_TXRX_FLAGS_AGC_ON, + furi_hal_nfc_ll_ms2fc(20)); + + if(ret != FuriHalNfcReturnOk) break; + if(rx_len != 16 * 8) break; + memcpy(data->value, rx_data, sizeof(data->value)); + read_success = true; + } while(false); + + if(!read_success) { + furi_hal_nfc_ll_txrx_off(); + furi_hal_nfc_start_sleep(); + } + + return read_success; +} + +bool magic_write_blk(uint8_t block_num, MfClassicBlock* data) { + furi_assert(data); + + bool write_success = false; + uint8_t tx_data[MAGIC_BUFFER_SIZE] = {}; + uint8_t rx_data[MAGIC_BUFFER_SIZE] = {}; + uint16_t rx_len = 0; + FuriHalNfcReturn ret = 0; + + do { + tx_data[0] = MAGIC_MIFARE_WRITE_CMD; + tx_data[1] = block_num; + ret = furi_hal_nfc_ll_txrx_bits( + tx_data, + 2 * 8, + rx_data, + sizeof(rx_data), + &rx_len, + FURI_HAL_NFC_LL_TXRX_FLAGS_AGC_ON | FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_RX_KEEP, + furi_hal_nfc_ll_ms2fc(20)); + if(ret != FuriHalNfcReturnIncompleteByte) break; + if(rx_len != 4) break; + if(rx_data[0] != MAGIC_ACK) break; + + memcpy(tx_data, data->value, sizeof(data->value)); + ret = furi_hal_nfc_ll_txrx_bits( + tx_data, + 16 * 8, + rx_data, + sizeof(rx_data), + &rx_len, + FURI_HAL_NFC_LL_TXRX_FLAGS_AGC_ON | FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_RX_KEEP, + furi_hal_nfc_ll_ms2fc(20)); + if(ret != FuriHalNfcReturnIncompleteByte) break; + if(rx_len != 4) break; + if(rx_data[0] != MAGIC_ACK) break; + + write_success = true; + } while(false); + + if(!write_success) { + furi_hal_nfc_ll_txrx_off(); + furi_hal_nfc_start_sleep(); + } + + return write_success; +} + +bool magic_wipe() { + bool wipe_success = false; + uint8_t tx_data[MAGIC_BUFFER_SIZE] = {}; + uint8_t rx_data[MAGIC_BUFFER_SIZE] = {}; + uint16_t rx_len = 0; + FuriHalNfcReturn ret = 0; + + do { + tx_data[0] = MAGIC_CMD_WIPE; + ret = furi_hal_nfc_ll_txrx_bits( + tx_data, + 8, + rx_data, + sizeof(rx_data), + &rx_len, + FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_TX_MANUAL | FURI_HAL_NFC_LL_TXRX_FLAGS_AGC_ON | + FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_RX_KEEP, + furi_hal_nfc_ll_ms2fc(2000)); + + if(ret != FuriHalNfcReturnIncompleteByte) break; + if(rx_len != 4) break; + if(rx_data[0] != MAGIC_ACK) break; + + wipe_success = true; + } while(false); + + return wipe_success; +} + +void magic_deactivate() { + furi_hal_nfc_ll_txrx_off(); + furi_hal_nfc_start_sleep(); +} diff --git a/applications/plugins/nfc_magic/lib/magic/magic.h b/applications/plugins/nfc_magic/lib/magic/magic.h new file mode 100644 index 00000000000..64c60a0a705 --- /dev/null +++ b/applications/plugins/nfc_magic/lib/magic/magic.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +bool magic_wupa(); + +bool magic_read_block(uint8_t block_num, MfClassicBlock* data); + +bool magic_data_access_cmd(); + +bool magic_write_blk(uint8_t block_num, MfClassicBlock* data); + +bool magic_wipe(); + +void magic_deactivate(); diff --git a/applications/plugins/nfc_magic/nfc_magic.c b/applications/plugins/nfc_magic/nfc_magic.c new file mode 100644 index 00000000000..38eecba6a82 --- /dev/null +++ b/applications/plugins/nfc_magic/nfc_magic.c @@ -0,0 +1,169 @@ +#include "nfc_magic_i.h" + +bool nfc_magic_custom_event_callback(void* context, uint32_t event) { + furi_assert(context); + NfcMagic* nfc_magic = context; + return scene_manager_handle_custom_event(nfc_magic->scene_manager, event); +} + +bool nfc_magic_back_event_callback(void* context) { + furi_assert(context); + NfcMagic* nfc_magic = context; + return scene_manager_handle_back_event(nfc_magic->scene_manager); +} + +void nfc_magic_tick_event_callback(void* context) { + furi_assert(context); + NfcMagic* nfc_magic = context; + scene_manager_handle_tick_event(nfc_magic->scene_manager); +} + +void nfc_magic_show_loading_popup(void* context, bool show) { + NfcMagic* nfc_magic = context; + TaskHandle_t timer_task = xTaskGetHandle(configTIMER_SERVICE_TASK_NAME); + + if(show) { + // Raise timer priority so that animations can play + vTaskPrioritySet(timer_task, configMAX_PRIORITIES - 1); + view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewLoading); + } else { + // Restore default timer priority + vTaskPrioritySet(timer_task, configTIMER_TASK_PRIORITY); + } +} + +NfcMagic* nfc_magic_alloc() { + NfcMagic* nfc_magic = malloc(sizeof(NfcMagic)); + + nfc_magic->worker = nfc_magic_worker_alloc(); + nfc_magic->view_dispatcher = view_dispatcher_alloc(); + nfc_magic->scene_manager = scene_manager_alloc(&nfc_magic_scene_handlers, nfc_magic); + view_dispatcher_enable_queue(nfc_magic->view_dispatcher); + view_dispatcher_set_event_callback_context(nfc_magic->view_dispatcher, nfc_magic); + view_dispatcher_set_custom_event_callback( + nfc_magic->view_dispatcher, nfc_magic_custom_event_callback); + view_dispatcher_set_navigation_event_callback( + nfc_magic->view_dispatcher, nfc_magic_back_event_callback); + view_dispatcher_set_tick_event_callback( + nfc_magic->view_dispatcher, nfc_magic_tick_event_callback, 100); + + // Nfc device + nfc_magic->nfc_dev = nfc_device_alloc(); + + // Open GUI record + nfc_magic->gui = furi_record_open(RECORD_GUI); + view_dispatcher_attach_to_gui( + nfc_magic->view_dispatcher, nfc_magic->gui, ViewDispatcherTypeFullscreen); + + // Open Notification record + nfc_magic->notifications = furi_record_open(RECORD_NOTIFICATION); + + // Submenu + nfc_magic->submenu = submenu_alloc(); + view_dispatcher_add_view( + nfc_magic->view_dispatcher, NfcMagicViewMenu, submenu_get_view(nfc_magic->submenu)); + + // Popup + nfc_magic->popup = popup_alloc(); + view_dispatcher_add_view( + nfc_magic->view_dispatcher, NfcMagicViewPopup, popup_get_view(nfc_magic->popup)); + + // Loading + nfc_magic->loading = loading_alloc(); + view_dispatcher_add_view( + nfc_magic->view_dispatcher, NfcMagicViewLoading, loading_get_view(nfc_magic->loading)); + + // Text Input + nfc_magic->text_input = text_input_alloc(); + view_dispatcher_add_view( + nfc_magic->view_dispatcher, + NfcMagicViewTextInput, + text_input_get_view(nfc_magic->text_input)); + + // Custom Widget + nfc_magic->widget = widget_alloc(); + view_dispatcher_add_view( + nfc_magic->view_dispatcher, NfcMagicViewWidget, widget_get_view(nfc_magic->widget)); + + return nfc_magic; +} + +void nfc_magic_free(NfcMagic* nfc_magic) { + furi_assert(nfc_magic); + + // Nfc device + nfc_device_free(nfc_magic->nfc_dev); + + // Submenu + view_dispatcher_remove_view(nfc_magic->view_dispatcher, NfcMagicViewMenu); + submenu_free(nfc_magic->submenu); + + // Popup + view_dispatcher_remove_view(nfc_magic->view_dispatcher, NfcMagicViewPopup); + popup_free(nfc_magic->popup); + + // Loading + view_dispatcher_remove_view(nfc_magic->view_dispatcher, NfcMagicViewLoading); + loading_free(nfc_magic->loading); + + // TextInput + view_dispatcher_remove_view(nfc_magic->view_dispatcher, NfcMagicViewTextInput); + text_input_free(nfc_magic->text_input); + + // Custom Widget + view_dispatcher_remove_view(nfc_magic->view_dispatcher, NfcMagicViewWidget); + widget_free(nfc_magic->widget); + + // Worker + nfc_magic_worker_stop(nfc_magic->worker); + nfc_magic_worker_free(nfc_magic->worker); + + // View Dispatcher + view_dispatcher_free(nfc_magic->view_dispatcher); + + // Scene Manager + scene_manager_free(nfc_magic->scene_manager); + + // GUI + furi_record_close(RECORD_GUI); + nfc_magic->gui = NULL; + + // Notifications + furi_record_close(RECORD_NOTIFICATION); + nfc_magic->notifications = NULL; + + free(nfc_magic); +} + +static const NotificationSequence nfc_magic_sequence_blink_start_blue = { + &message_blink_start_10, + &message_blink_set_color_blue, + &message_do_not_reset, + NULL, +}; + +static const NotificationSequence nfc_magic_sequence_blink_stop = { + &message_blink_stop, + NULL, +}; + +void nfc_magic_blink_start(NfcMagic* nfc_magic) { + notification_message(nfc_magic->notifications, &nfc_magic_sequence_blink_start_blue); +} + +void nfc_magic_blink_stop(NfcMagic* nfc_magic) { + notification_message(nfc_magic->notifications, &nfc_magic_sequence_blink_stop); +} + +int32_t nfc_magic_app(void* p) { + UNUSED(p); + NfcMagic* nfc_magic = nfc_magic_alloc(); + + scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneStart); + + view_dispatcher_run(nfc_magic->view_dispatcher); + + nfc_magic_free(nfc_magic); + + return 0; +} diff --git a/applications/plugins/nfc_magic/nfc_magic.h b/applications/plugins/nfc_magic/nfc_magic.h new file mode 100644 index 00000000000..1abf1371ed0 --- /dev/null +++ b/applications/plugins/nfc_magic/nfc_magic.h @@ -0,0 +1,3 @@ +#pragma once + +typedef struct NfcMagic NfcMagic; diff --git a/applications/plugins/nfc_magic/nfc_magic_i.h b/applications/plugins/nfc_magic/nfc_magic_i.h new file mode 100644 index 00000000000..01b30082663 --- /dev/null +++ b/applications/plugins/nfc_magic/nfc_magic_i.h @@ -0,0 +1,77 @@ +#pragma once + +#include "nfc_magic.h" +#include "nfc_magic_worker.h" + +#include "lib/magic/magic.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include "scenes/nfc_magic_scene.h" + +#include +#include + +#include +#include "nfc_magic_icons.h" + +enum NfcMagicCustomEvent { + // Reserve first 100 events for button types and indexes, starting from 0 + NfcMagicCustomEventReserved = 100, + + NfcMagicCustomEventViewExit, + NfcMagicCustomEventWorkerExit, + NfcMagicCustomEventByteInputDone, + NfcMagicCustomEventTextInputDone, +}; + +struct NfcMagic { + NfcMagicWorker* worker; + ViewDispatcher* view_dispatcher; + Gui* gui; + NotificationApp* notifications; + SceneManager* scene_manager; + // NfcMagicDevice* dev; + NfcDevice* nfc_dev; + + FuriString* text_box_store; + + // Common Views + Submenu* submenu; + Popup* popup; + Loading* loading; + TextInput* text_input; + Widget* widget; +}; + +typedef enum { + NfcMagicViewMenu, + NfcMagicViewPopup, + NfcMagicViewLoading, + NfcMagicViewTextInput, + NfcMagicViewWidget, +} NfcMagicView; + +NfcMagic* nfc_magic_alloc(); + +void nfc_magic_text_store_set(NfcMagic* nfc_magic, const char* text, ...); + +void nfc_magic_text_store_clear(NfcMagic* nfc_magic); + +void nfc_magic_blink_start(NfcMagic* nfc_magic); + +void nfc_magic_blink_stop(NfcMagic* nfc_magic); + +void nfc_magic_show_loading_popup(void* context, bool show); diff --git a/applications/plugins/nfc_magic/nfc_magic_worker.c b/applications/plugins/nfc_magic/nfc_magic_worker.c new file mode 100644 index 00000000000..0623211e289 --- /dev/null +++ b/applications/plugins/nfc_magic/nfc_magic_worker.c @@ -0,0 +1,174 @@ +#include "nfc_magic_worker_i.h" + +#include "lib/magic/magic.h" + +#define TAG "NfcMagicWorker" + +static void + nfc_magic_worker_change_state(NfcMagicWorker* nfc_magic_worker, NfcMagicWorkerState state) { + furi_assert(nfc_magic_worker); + + nfc_magic_worker->state = state; +} + +NfcMagicWorker* nfc_magic_worker_alloc() { + NfcMagicWorker* nfc_magic_worker = malloc(sizeof(NfcMagicWorker)); + + // Worker thread attributes + nfc_magic_worker->thread = furi_thread_alloc(); + furi_thread_set_name(nfc_magic_worker->thread, "NfcMagicWorker"); + furi_thread_set_stack_size(nfc_magic_worker->thread, 8192); + furi_thread_set_callback(nfc_magic_worker->thread, nfc_magic_worker_task); + furi_thread_set_context(nfc_magic_worker->thread, nfc_magic_worker); + + nfc_magic_worker->callback = NULL; + nfc_magic_worker->context = NULL; + + nfc_magic_worker_change_state(nfc_magic_worker, NfcMagicWorkerStateReady); + + return nfc_magic_worker; +} + +void nfc_magic_worker_free(NfcMagicWorker* nfc_magic_worker) { + furi_assert(nfc_magic_worker); + + furi_thread_free(nfc_magic_worker->thread); + free(nfc_magic_worker); +} + +void nfc_magic_worker_stop(NfcMagicWorker* nfc_magic_worker) { + furi_assert(nfc_magic_worker); + + nfc_magic_worker_change_state(nfc_magic_worker, NfcMagicWorkerStateStop); + furi_thread_join(nfc_magic_worker->thread); +} + +void nfc_magic_worker_start( + NfcMagicWorker* nfc_magic_worker, + NfcMagicWorkerState state, + NfcDeviceData* dev_data, + NfcMagicWorkerCallback callback, + void* context) { + furi_assert(nfc_magic_worker); + furi_assert(dev_data); + + nfc_magic_worker->callback = callback; + nfc_magic_worker->context = context; + nfc_magic_worker->dev_data = dev_data; + nfc_magic_worker_change_state(nfc_magic_worker, state); + furi_thread_start(nfc_magic_worker->thread); +} + +int32_t nfc_magic_worker_task(void* context) { + NfcMagicWorker* nfc_magic_worker = context; + + if(nfc_magic_worker->state == NfcMagicWorkerStateCheck) { + nfc_magic_worker_check(nfc_magic_worker); + } else if(nfc_magic_worker->state == NfcMagicWorkerStateWrite) { + nfc_magic_worker_write(nfc_magic_worker); + } else if(nfc_magic_worker->state == NfcMagicWorkerStateWipe) { + nfc_magic_worker_wipe(nfc_magic_worker); + } + + nfc_magic_worker_change_state(nfc_magic_worker, NfcMagicWorkerStateReady); + + return 0; +} + +void nfc_magic_worker_write(NfcMagicWorker* nfc_magic_worker) { + bool card_found_notified = false; + FuriHalNfcDevData nfc_data = {}; + MfClassicData* src_data = &nfc_magic_worker->dev_data->mf_classic_data; + + while(nfc_magic_worker->state == NfcMagicWorkerStateWrite) { + if(furi_hal_nfc_detect(&nfc_data, 200)) { + if(!card_found_notified) { + nfc_magic_worker->callback( + NfcMagicWorkerEventCardDetected, nfc_magic_worker->context); + card_found_notified = true; + } + furi_hal_nfc_sleep(); + + if(!magic_wupa()) { + FURI_LOG_E(TAG, "Not Magic card"); + nfc_magic_worker->callback( + NfcMagicWorkerEventWrongCard, nfc_magic_worker->context); + break; + } + if(!magic_data_access_cmd()) { + FURI_LOG_E(TAG, "Not Magic card"); + nfc_magic_worker->callback( + NfcMagicWorkerEventWrongCard, nfc_magic_worker->context); + break; + } + for(size_t i = 0; i < 64; i++) { + FURI_LOG_D(TAG, "Writing block %d", i); + if(!magic_write_blk(i, &src_data->block[i])) { + FURI_LOG_E(TAG, "Failed to write %d block", i); + nfc_magic_worker->callback(NfcMagicWorkerEventFail, nfc_magic_worker->context); + break; + } + } + nfc_magic_worker->callback(NfcMagicWorkerEventSuccess, nfc_magic_worker->context); + break; + } else { + if(card_found_notified) { + nfc_magic_worker->callback( + NfcMagicWorkerEventNoCardDetected, nfc_magic_worker->context); + card_found_notified = false; + } + } + furi_delay_ms(300); + } + magic_deactivate(); +} + +void nfc_magic_worker_check(NfcMagicWorker* nfc_magic_worker) { + bool card_found_notified = false; + + while(nfc_magic_worker->state == NfcMagicWorkerStateCheck) { + if(magic_wupa()) { + if(!card_found_notified) { + nfc_magic_worker->callback( + NfcMagicWorkerEventCardDetected, nfc_magic_worker->context); + card_found_notified = true; + } + + nfc_magic_worker->callback(NfcMagicWorkerEventSuccess, nfc_magic_worker->context); + break; + } else { + if(card_found_notified) { + nfc_magic_worker->callback( + NfcMagicWorkerEventNoCardDetected, nfc_magic_worker->context); + card_found_notified = false; + } + } + furi_delay_ms(300); + } + magic_deactivate(); +} + +void nfc_magic_worker_wipe(NfcMagicWorker* nfc_magic_worker) { + MfClassicBlock block; + memset(&block, 0, sizeof(MfClassicBlock)); + block.value[0] = 0x01; + block.value[1] = 0x02; + block.value[2] = 0x03; + block.value[3] = 0x04; + block.value[4] = 0x04; + block.value[5] = 0x08; + block.value[6] = 0x04; + + while(nfc_magic_worker->state == NfcMagicWorkerStateWipe) { + magic_deactivate(); + furi_delay_ms(300); + if(!magic_wupa()) continue; + if(!magic_wipe()) continue; + if(!magic_data_access_cmd()) continue; + if(!magic_write_blk(0, &block)) continue; + nfc_magic_worker->callback(NfcMagicWorkerEventSuccess, nfc_magic_worker->context); + magic_deactivate(); + break; + } + magic_deactivate(); +} diff --git a/applications/plugins/nfc_magic/nfc_magic_worker.h b/applications/plugins/nfc_magic/nfc_magic_worker.h new file mode 100644 index 00000000000..9d29bb3a8be --- /dev/null +++ b/applications/plugins/nfc_magic/nfc_magic_worker.h @@ -0,0 +1,38 @@ +#pragma once + +#include + +typedef struct NfcMagicWorker NfcMagicWorker; + +typedef enum { + NfcMagicWorkerStateReady, + + NfcMagicWorkerStateCheck, + NfcMagicWorkerStateWrite, + NfcMagicWorkerStateWipe, + + NfcMagicWorkerStateStop, +} NfcMagicWorkerState; + +typedef enum { + NfcMagicWorkerEventSuccess, + NfcMagicWorkerEventFail, + NfcMagicWorkerEventCardDetected, + NfcMagicWorkerEventNoCardDetected, + NfcMagicWorkerEventWrongCard, +} NfcMagicWorkerEvent; + +typedef bool (*NfcMagicWorkerCallback)(NfcMagicWorkerEvent event, void* context); + +NfcMagicWorker* nfc_magic_worker_alloc(); + +void nfc_magic_worker_free(NfcMagicWorker* nfc_magic_worker); + +void nfc_magic_worker_stop(NfcMagicWorker* nfc_magic_worker); + +void nfc_magic_worker_start( + NfcMagicWorker* nfc_magic_worker, + NfcMagicWorkerState state, + NfcDeviceData* dev_data, + NfcMagicWorkerCallback callback, + void* context); diff --git a/applications/plugins/nfc_magic/nfc_magic_worker_i.h b/applications/plugins/nfc_magic/nfc_magic_worker_i.h new file mode 100644 index 00000000000..0cde2e7125b --- /dev/null +++ b/applications/plugins/nfc_magic/nfc_magic_worker_i.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +#include "nfc_magic_worker.h" + +struct NfcMagicWorker { + FuriThread* thread; + + NfcDeviceData* dev_data; + + NfcMagicWorkerCallback callback; + void* context; + + NfcMagicWorkerState state; +}; + +int32_t nfc_magic_worker_task(void* context); + +void nfc_magic_worker_check(NfcMagicWorker* nfc_magic_worker); + +void nfc_magic_worker_write(NfcMagicWorker* nfc_magic_worker); + +void nfc_magic_worker_wipe(NfcMagicWorker* nfc_magic_worker); diff --git a/applications/plugins/nfc_magic/scenes/nfc_magic_scene.c b/applications/plugins/nfc_magic/scenes/nfc_magic_scene.c new file mode 100644 index 00000000000..520ef2a9dea --- /dev/null +++ b/applications/plugins/nfc_magic/scenes/nfc_magic_scene.c @@ -0,0 +1,30 @@ +#include "nfc_magic_scene.h" + +// Generate scene on_enter handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, +void (*const nfc_magic_on_enter_handlers[])(void*) = { +#include "nfc_magic_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 nfc_magic_on_event_handlers[])(void* context, SceneManagerEvent event) = { +#include "nfc_magic_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 nfc_magic_on_exit_handlers[])(void* context) = { +#include "nfc_magic_scene_config.h" +}; +#undef ADD_SCENE + +// Initialize scene handlers configuration structure +const SceneManagerHandlers nfc_magic_scene_handlers = { + .on_enter_handlers = nfc_magic_on_enter_handlers, + .on_event_handlers = nfc_magic_on_event_handlers, + .on_exit_handlers = nfc_magic_on_exit_handlers, + .scene_num = NfcMagicSceneNum, +}; diff --git a/applications/plugins/nfc_magic/scenes/nfc_magic_scene.h b/applications/plugins/nfc_magic/scenes/nfc_magic_scene.h new file mode 100644 index 00000000000..f1e9f715dd4 --- /dev/null +++ b/applications/plugins/nfc_magic/scenes/nfc_magic_scene.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +// Generate scene id and total number +#define ADD_SCENE(prefix, name, id) NfcMagicScene##id, +typedef enum { +#include "nfc_magic_scene_config.h" + NfcMagicSceneNum, +} NfcMagicScene; +#undef ADD_SCENE + +extern const SceneManagerHandlers nfc_magic_scene_handlers; + +// Generate scene on_enter handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); +#include "nfc_magic_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 "nfc_magic_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 "nfc_magic_scene_config.h" +#undef ADD_SCENE diff --git a/applications/plugins/nfc_magic/scenes/nfc_magic_scene_check.c b/applications/plugins/nfc_magic/scenes/nfc_magic_scene_check.c new file mode 100644 index 00000000000..d5179724283 --- /dev/null +++ b/applications/plugins/nfc_magic/scenes/nfc_magic_scene_check.c @@ -0,0 +1,87 @@ +#include "../nfc_magic_i.h" + +enum { + NfcMagicSceneCheckStateCardSearch, + NfcMagicSceneCheckStateCardFound, +}; + +bool nfc_magic_check_worker_callback(NfcMagicWorkerEvent event, void* context) { + furi_assert(context); + + NfcMagic* nfc_magic = context; + view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, event); + + return true; +} + +static void nfc_magic_scene_check_setup_view(NfcMagic* nfc_magic) { + Popup* popup = nfc_magic->popup; + popup_reset(popup); + uint32_t state = scene_manager_get_scene_state(nfc_magic->scene_manager, NfcMagicSceneCheck); + + if(state == NfcMagicSceneCheckStateCardSearch) { + popup_set_icon(nfc_magic->popup, 0, 8, &I_NFC_manual_60x50); + popup_set_text( + nfc_magic->popup, "Apply card to\nthe back", 128, 32, AlignRight, AlignCenter); + } else { + popup_set_icon(popup, 12, 23, &I_Loading_24); + popup_set_header(popup, "Checking\nDon't move...", 52, 32, AlignLeft, AlignCenter); + } + + view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewPopup); +} + +void nfc_magic_scene_check_on_enter(void* context) { + NfcMagic* nfc_magic = context; + + scene_manager_set_scene_state( + nfc_magic->scene_manager, NfcMagicSceneCheck, NfcMagicSceneCheckStateCardSearch); + nfc_magic_scene_check_setup_view(nfc_magic); + + // Setup and start worker + nfc_magic_worker_start( + nfc_magic->worker, + NfcMagicWorkerStateCheck, + &nfc_magic->nfc_dev->dev_data, + nfc_magic_check_worker_callback, + nfc_magic); + nfc_magic_blink_start(nfc_magic); +} + +bool nfc_magic_scene_check_on_event(void* context, SceneManagerEvent event) { + NfcMagic* nfc_magic = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcMagicWorkerEventSuccess) { + scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneMagicInfo); + consumed = true; + } else if(event.event == NfcMagicWorkerEventWrongCard) { + scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneNotMagic); + consumed = true; + } else if(event.event == NfcMagicWorkerEventCardDetected) { + scene_manager_set_scene_state( + nfc_magic->scene_manager, NfcMagicSceneCheck, NfcMagicSceneCheckStateCardFound); + nfc_magic_scene_check_setup_view(nfc_magic); + consumed = true; + } else if(event.event == NfcMagicWorkerEventNoCardDetected) { + scene_manager_set_scene_state( + nfc_magic->scene_manager, NfcMagicSceneCheck, NfcMagicSceneCheckStateCardSearch); + nfc_magic_scene_check_setup_view(nfc_magic); + consumed = true; + } + } + return consumed; +} + +void nfc_magic_scene_check_on_exit(void* context) { + NfcMagic* nfc_magic = context; + + nfc_magic_worker_stop(nfc_magic->worker); + scene_manager_set_scene_state( + nfc_magic->scene_manager, NfcMagicSceneCheck, NfcMagicSceneCheckStateCardSearch); + // Clear view + popup_reset(nfc_magic->popup); + + nfc_magic_blink_stop(nfc_magic); +} diff --git a/applications/plugins/nfc_magic/scenes/nfc_magic_scene_config.h b/applications/plugins/nfc_magic/scenes/nfc_magic_scene_config.h new file mode 100644 index 00000000000..557e26914e6 --- /dev/null +++ b/applications/plugins/nfc_magic/scenes/nfc_magic_scene_config.h @@ -0,0 +1,12 @@ +ADD_SCENE(nfc_magic, start, Start) +ADD_SCENE(nfc_magic, file_select, FileSelect) +ADD_SCENE(nfc_magic, write_confirm, WriteConfirm) +ADD_SCENE(nfc_magic, wrong_card, WrongCard) +ADD_SCENE(nfc_magic, write, Write) +ADD_SCENE(nfc_magic, write_fail, WriteFail) +ADD_SCENE(nfc_magic, success, Success) +ADD_SCENE(nfc_magic, check, Check) +ADD_SCENE(nfc_magic, not_magic, NotMagic) +ADD_SCENE(nfc_magic, magic_info, MagicInfo) +ADD_SCENE(nfc_magic, wipe, Wipe) +ADD_SCENE(nfc_magic, wipe_fail, WipeFail) diff --git a/applications/plugins/nfc_magic/scenes/nfc_magic_scene_file_select.c b/applications/plugins/nfc_magic/scenes/nfc_magic_scene_file_select.c new file mode 100644 index 00000000000..a19237ed47b --- /dev/null +++ b/applications/plugins/nfc_magic/scenes/nfc_magic_scene_file_select.c @@ -0,0 +1,34 @@ +#include "../nfc_magic_i.h" + +static bool nfc_magic_scene_file_select_is_file_suitable(NfcDevice* nfc_dev) { + return (nfc_dev->format == NfcDeviceSaveFormatMifareClassic) && + (nfc_dev->dev_data.mf_classic_data.type == MfClassicType1k) && + (nfc_dev->dev_data.nfc_data.uid_len == 4); +} + +void nfc_magic_scene_file_select_on_enter(void* context) { + NfcMagic* nfc_magic = context; + // Process file_select return + nfc_device_set_loading_callback(nfc_magic->nfc_dev, nfc_magic_show_loading_popup, nfc_magic); + + if(nfc_file_select(nfc_magic->nfc_dev)) { + if(nfc_magic_scene_file_select_is_file_suitable(nfc_magic->nfc_dev)) { + scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneWriteConfirm); + } else { + scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneWrongCard); + } + } else { + scene_manager_previous_scene(nfc_magic->scene_manager); + } +} + +bool nfc_magic_scene_file_select_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + UNUSED(event); + return false; +} + +void nfc_magic_scene_file_select_on_exit(void* context) { + NfcMagic* nfc_magic = context; + nfc_device_set_loading_callback(nfc_magic->nfc_dev, NULL, nfc_magic); +} diff --git a/applications/plugins/nfc_magic/scenes/nfc_magic_scene_magic_info.c b/applications/plugins/nfc_magic/scenes/nfc_magic_scene_magic_info.c new file mode 100644 index 00000000000..e9b226b3abb --- /dev/null +++ b/applications/plugins/nfc_magic/scenes/nfc_magic_scene_magic_info.c @@ -0,0 +1,45 @@ +#include "../nfc_magic_i.h" + +void nfc_magic_scene_magic_info_widget_callback( + GuiButtonType result, + InputType type, + void* context) { + NfcMagic* nfc_magic = context; + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, result); + } +} + +void nfc_magic_scene_magic_info_on_enter(void* context) { + NfcMagic* nfc_magic = context; + Widget* widget = nfc_magic->widget; + + notification_message(nfc_magic->notifications, &sequence_success); + + widget_add_icon_element(widget, 73, 17, &I_DolphinCommon_56x48); + widget_add_string_element( + widget, 3, 4, AlignLeft, AlignTop, FontPrimary, "Magic card detected"); + widget_add_button_element( + widget, GuiButtonTypeLeft, "Retry", nfc_magic_scene_magic_info_widget_callback, nfc_magic); + + // Setup and start worker + view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewWidget); +} + +bool nfc_magic_scene_magic_info_on_event(void* context, SceneManagerEvent event) { + NfcMagic* nfc_magic = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == GuiButtonTypeLeft) { + consumed = scene_manager_previous_scene(nfc_magic->scene_manager); + } + } + return consumed; +} + +void nfc_magic_scene_magic_info_on_exit(void* context) { + NfcMagic* nfc_magic = context; + + widget_reset(nfc_magic->widget); +} diff --git a/applications/plugins/nfc_magic/scenes/nfc_magic_scene_not_magic.c b/applications/plugins/nfc_magic/scenes/nfc_magic_scene_not_magic.c new file mode 100644 index 00000000000..b87f7f383e8 --- /dev/null +++ b/applications/plugins/nfc_magic/scenes/nfc_magic_scene_not_magic.c @@ -0,0 +1,44 @@ +#include "../nfc_magic_i.h" + +void nfc_magic_scene_not_magic_widget_callback(GuiButtonType result, InputType type, void* context) { + NfcMagic* nfc_magic = context; + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, result); + } +} + +void nfc_magic_scene_not_magic_on_enter(void* context) { + NfcMagic* nfc_magic = context; + Widget* widget = nfc_magic->widget; + + notification_message(nfc_magic->notifications, &sequence_error); + + // widget_add_icon_element(widget, 73, 17, &I_DolphinCommon_56x48); + widget_add_string_element( + widget, 3, 4, AlignLeft, AlignTop, FontPrimary, "This is wrong card"); + widget_add_string_multiline_element( + widget, 4, 17, AlignLeft, AlignTop, FontSecondary, "Not a magic\ncard"); + widget_add_button_element( + widget, GuiButtonTypeLeft, "Retry", nfc_magic_scene_not_magic_widget_callback, nfc_magic); + + // Setup and start worker + view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewWidget); +} + +bool nfc_magic_scene_not_magic_on_event(void* context, SceneManagerEvent event) { + NfcMagic* nfc_magic = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == GuiButtonTypeLeft) { + consumed = scene_manager_previous_scene(nfc_magic->scene_manager); + } + } + return consumed; +} + +void nfc_magic_scene_not_magic_on_exit(void* context) { + NfcMagic* nfc_magic = context; + + widget_reset(nfc_magic->widget); +} diff --git a/applications/plugins/nfc_magic/scenes/nfc_magic_scene_start.c b/applications/plugins/nfc_magic/scenes/nfc_magic_scene_start.c new file mode 100644 index 00000000000..f2984443fb9 --- /dev/null +++ b/applications/plugins/nfc_magic/scenes/nfc_magic_scene_start.c @@ -0,0 +1,61 @@ +#include "../nfc_magic_i.h" +enum SubmenuIndex { + SubmenuIndexCheck, + SubmenuIndexWriteGen1A, + SubmenuIndexWipe, +}; + +void nfc_magic_scene_start_submenu_callback(void* context, uint32_t index) { + NfcMagic* nfc_magic = context; + view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, index); +} + +void nfc_magic_scene_start_on_enter(void* context) { + NfcMagic* nfc_magic = context; + + Submenu* submenu = nfc_magic->submenu; + submenu_add_item( + submenu, + "Check Magic Tag", + SubmenuIndexCheck, + nfc_magic_scene_start_submenu_callback, + nfc_magic); + submenu_add_item( + submenu, + "Write Gen1A", + SubmenuIndexWriteGen1A, + nfc_magic_scene_start_submenu_callback, + nfc_magic); + submenu_add_item( + submenu, "Wipe", SubmenuIndexWipe, nfc_magic_scene_start_submenu_callback, nfc_magic); + + submenu_set_selected_item( + submenu, scene_manager_get_scene_state(nfc_magic->scene_manager, NfcMagicSceneStart)); + view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewMenu); +} + +bool nfc_magic_scene_start_on_event(void* context, SceneManagerEvent event) { + NfcMagic* nfc_magic = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubmenuIndexCheck) { + scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneCheck); + consumed = true; + } else if(event.event == SubmenuIndexWriteGen1A) { + scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneFileSelect); + consumed = true; + } else if(event.event == SubmenuIndexWipe) { + scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneWipe); + consumed = true; + } + scene_manager_set_scene_state(nfc_magic->scene_manager, NfcMagicSceneStart, event.event); + } + + return consumed; +} + +void nfc_magic_scene_start_on_exit(void* context) { + NfcMagic* nfc_magic = context; + submenu_reset(nfc_magic->submenu); +} diff --git a/applications/plugins/nfc_magic/scenes/nfc_magic_scene_success.c b/applications/plugins/nfc_magic/scenes/nfc_magic_scene_success.c new file mode 100644 index 00000000000..37441e80e8a --- /dev/null +++ b/applications/plugins/nfc_magic/scenes/nfc_magic_scene_success.c @@ -0,0 +1,42 @@ +#include "../nfc_magic_i.h" + +void nfc_magic_scene_success_popup_callback(void* context) { + NfcMagic* nfc_magic = context; + view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, NfcMagicCustomEventViewExit); +} + +void nfc_magic_scene_success_on_enter(void* context) { + NfcMagic* nfc_magic = context; + + notification_message(nfc_magic->notifications, &sequence_success); + + Popup* popup = nfc_magic->popup; + popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); + popup_set_header(popup, "Success!", 10, 20, AlignLeft, AlignBottom); + popup_set_timeout(popup, 1500); + popup_set_context(popup, nfc_magic); + popup_set_callback(popup, nfc_magic_scene_success_popup_callback); + popup_enable_timeout(popup); + + view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewPopup); +} + +bool nfc_magic_scene_success_on_event(void* context, SceneManagerEvent event) { + NfcMagic* nfc_magic = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcMagicCustomEventViewExit) { + consumed = scene_manager_search_and_switch_to_previous_scene( + nfc_magic->scene_manager, NfcMagicSceneStart); + } + } + return consumed; +} + +void nfc_magic_scene_success_on_exit(void* context) { + NfcMagic* nfc_magic = context; + + // Clear view + popup_reset(nfc_magic->popup); +} diff --git a/applications/plugins/nfc_magic/scenes/nfc_magic_scene_wipe.c b/applications/plugins/nfc_magic/scenes/nfc_magic_scene_wipe.c new file mode 100644 index 00000000000..1ca194286ad --- /dev/null +++ b/applications/plugins/nfc_magic/scenes/nfc_magic_scene_wipe.c @@ -0,0 +1,90 @@ +#include "../nfc_magic_i.h" + +enum { + NfcMagicSceneWipeStateCardSearch, + NfcMagicSceneWipeStateCardFound, +}; + +bool nfc_magic_wipe_worker_callback(NfcMagicWorkerEvent event, void* context) { + furi_assert(context); + + NfcMagic* nfc_magic = context; + view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, event); + + return true; +} + +static void nfc_magic_scene_wipe_setup_view(NfcMagic* nfc_magic) { + Popup* popup = nfc_magic->popup; + popup_reset(popup); + uint32_t state = scene_manager_get_scene_state(nfc_magic->scene_manager, NfcMagicSceneWipe); + + if(state == NfcMagicSceneWipeStateCardSearch) { + popup_set_icon(nfc_magic->popup, 0, 8, &I_NFC_manual_60x50); + popup_set_text( + nfc_magic->popup, "Apply card to\nthe back", 128, 32, AlignRight, AlignCenter); + } else { + popup_set_icon(popup, 12, 23, &I_Loading_24); + popup_set_header(popup, "Wiping\nDon't move...", 52, 32, AlignLeft, AlignCenter); + } + + view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewPopup); +} + +void nfc_magic_scene_wipe_on_enter(void* context) { + NfcMagic* nfc_magic = context; + + scene_manager_set_scene_state( + nfc_magic->scene_manager, NfcMagicSceneWipe, NfcMagicSceneWipeStateCardSearch); + nfc_magic_scene_wipe_setup_view(nfc_magic); + + // Setup and start worker + nfc_magic_worker_start( + nfc_magic->worker, + NfcMagicWorkerStateWipe, + &nfc_magic->nfc_dev->dev_data, + nfc_magic_wipe_worker_callback, + nfc_magic); + nfc_magic_blink_start(nfc_magic); +} + +bool nfc_magic_scene_wipe_on_event(void* context, SceneManagerEvent event) { + NfcMagic* nfc_magic = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcMagicWorkerEventSuccess) { + scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneSuccess); + consumed = true; + } else if(event.event == NfcMagicWorkerEventFail) { + scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneWipeFail); + consumed = true; + } else if(event.event == NfcMagicWorkerEventWrongCard) { + scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneNotMagic); + consumed = true; + } else if(event.event == NfcMagicWorkerEventCardDetected) { + scene_manager_set_scene_state( + nfc_magic->scene_manager, NfcMagicSceneWipe, NfcMagicSceneWipeStateCardFound); + nfc_magic_scene_wipe_setup_view(nfc_magic); + consumed = true; + } else if(event.event == NfcMagicWorkerEventNoCardDetected) { + scene_manager_set_scene_state( + nfc_magic->scene_manager, NfcMagicSceneWipe, NfcMagicSceneWipeStateCardSearch); + nfc_magic_scene_wipe_setup_view(nfc_magic); + consumed = true; + } + } + return consumed; +} + +void nfc_magic_scene_wipe_on_exit(void* context) { + NfcMagic* nfc_magic = context; + + nfc_magic_worker_stop(nfc_magic->worker); + scene_manager_set_scene_state( + nfc_magic->scene_manager, NfcMagicSceneWipe, NfcMagicSceneWipeStateCardSearch); + // Clear view + popup_reset(nfc_magic->popup); + + nfc_magic_blink_stop(nfc_magic); +} diff --git a/applications/plugins/nfc_magic/scenes/nfc_magic_scene_wipe_fail.c b/applications/plugins/nfc_magic/scenes/nfc_magic_scene_wipe_fail.c new file mode 100644 index 00000000000..828b65e6c58 --- /dev/null +++ b/applications/plugins/nfc_magic/scenes/nfc_magic_scene_wipe_fail.c @@ -0,0 +1,41 @@ +#include "../nfc_magic_i.h" + +void nfc_magic_scene_wipe_fail_widget_callback(GuiButtonType result, InputType type, void* context) { + NfcMagic* nfc_magic = context; + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, result); + } +} + +void nfc_magic_scene_wipe_fail_on_enter(void* context) { + NfcMagic* nfc_magic = context; + Widget* widget = nfc_magic->widget; + + notification_message(nfc_magic->notifications, &sequence_error); + + widget_add_icon_element(widget, 73, 17, &I_DolphinCommon_56x48); + widget_add_string_element(widget, 3, 4, AlignLeft, AlignTop, FontPrimary, "Wipe failed"); + widget_add_button_element( + widget, GuiButtonTypeLeft, "Retry", nfc_magic_scene_wipe_fail_widget_callback, nfc_magic); + + // Setup and start worker + view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewWidget); +} + +bool nfc_magic_scene_wipe_fail_on_event(void* context, SceneManagerEvent event) { + NfcMagic* nfc_magic = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == GuiButtonTypeLeft) { + consumed = scene_manager_previous_scene(nfc_magic->scene_manager); + } + } + return consumed; +} + +void nfc_magic_scene_wipe_fail_on_exit(void* context) { + NfcMagic* nfc_magic = context; + + widget_reset(nfc_magic->widget); +} diff --git a/applications/plugins/nfc_magic/scenes/nfc_magic_scene_write.c b/applications/plugins/nfc_magic/scenes/nfc_magic_scene_write.c new file mode 100644 index 00000000000..c3e6f962a34 --- /dev/null +++ b/applications/plugins/nfc_magic/scenes/nfc_magic_scene_write.c @@ -0,0 +1,90 @@ +#include "../nfc_magic_i.h" + +enum { + NfcMagicSceneWriteStateCardSearch, + NfcMagicSceneWriteStateCardFound, +}; + +bool nfc_magic_write_worker_callback(NfcMagicWorkerEvent event, void* context) { + furi_assert(context); + + NfcMagic* nfc_magic = context; + view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, event); + + return true; +} + +static void nfc_magic_scene_write_setup_view(NfcMagic* nfc_magic) { + Popup* popup = nfc_magic->popup; + popup_reset(popup); + uint32_t state = scene_manager_get_scene_state(nfc_magic->scene_manager, NfcMagicSceneWrite); + + if(state == NfcMagicSceneWriteStateCardSearch) { + popup_set_text( + nfc_magic->popup, "Apply card to\nthe back", 128, 32, AlignRight, AlignCenter); + popup_set_icon(nfc_magic->popup, 0, 8, &I_NFC_manual_60x50); + } else { + popup_set_icon(popup, 12, 23, &I_Loading_24); + popup_set_header(popup, "Writing\nDon't move...", 52, 32, AlignLeft, AlignCenter); + } + + view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewPopup); +} + +void nfc_magic_scene_write_on_enter(void* context) { + NfcMagic* nfc_magic = context; + + scene_manager_set_scene_state( + nfc_magic->scene_manager, NfcMagicSceneWrite, NfcMagicSceneWriteStateCardSearch); + nfc_magic_scene_write_setup_view(nfc_magic); + + // Setup and start worker + nfc_magic_worker_start( + nfc_magic->worker, + NfcMagicWorkerStateWrite, + &nfc_magic->nfc_dev->dev_data, + nfc_magic_write_worker_callback, + nfc_magic); + nfc_magic_blink_start(nfc_magic); +} + +bool nfc_magic_scene_write_on_event(void* context, SceneManagerEvent event) { + NfcMagic* nfc_magic = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcMagicWorkerEventSuccess) { + scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneSuccess); + consumed = true; + } else if(event.event == NfcMagicWorkerEventFail) { + scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneWriteFail); + consumed = true; + } else if(event.event == NfcMagicWorkerEventWrongCard) { + scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneNotMagic); + consumed = true; + } else if(event.event == NfcMagicWorkerEventCardDetected) { + scene_manager_set_scene_state( + nfc_magic->scene_manager, NfcMagicSceneWrite, NfcMagicSceneWriteStateCardFound); + nfc_magic_scene_write_setup_view(nfc_magic); + consumed = true; + } else if(event.event == NfcMagicWorkerEventNoCardDetected) { + scene_manager_set_scene_state( + nfc_magic->scene_manager, NfcMagicSceneWrite, NfcMagicSceneWriteStateCardSearch); + nfc_magic_scene_write_setup_view(nfc_magic); + consumed = true; + } + } + return consumed; +} + +void nfc_magic_scene_write_on_exit(void* context) { + NfcMagic* nfc_magic = context; + + nfc_magic_worker_stop(nfc_magic->worker); + scene_manager_set_scene_state( + nfc_magic->scene_manager, NfcMagicSceneWrite, NfcMagicSceneWriteStateCardSearch); + // Clear view + popup_reset(nfc_magic->popup); + + nfc_magic_blink_stop(nfc_magic); +} diff --git a/applications/plugins/nfc_magic/scenes/nfc_magic_scene_write_confirm.c b/applications/plugins/nfc_magic/scenes/nfc_magic_scene_write_confirm.c new file mode 100644 index 00000000000..d31c1c194d0 --- /dev/null +++ b/applications/plugins/nfc_magic/scenes/nfc_magic_scene_write_confirm.c @@ -0,0 +1,64 @@ +#include "../nfc_magic_i.h" + +void nfc_magic_scene_write_confirm_widget_callback( + GuiButtonType result, + InputType type, + void* context) { + NfcMagic* nfc_magic = context; + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, result); + } +} + +void nfc_magic_scene_write_confirm_on_enter(void* context) { + NfcMagic* nfc_magic = context; + Widget* widget = nfc_magic->widget; + + widget_add_string_element(widget, 3, 0, AlignLeft, AlignTop, FontPrimary, "Risky operation"); + widget_add_text_box_element( + widget, + 0, + 13, + 128, + 54, + AlignLeft, + AlignTop, + "Writing to this card will change manufacturer block. On some cards it may not be rewritten", + false); + widget_add_button_element( + widget, + GuiButtonTypeCenter, + "Continue", + nfc_magic_scene_write_confirm_widget_callback, + nfc_magic); + widget_add_button_element( + widget, + GuiButtonTypeLeft, + "Back", + nfc_magic_scene_write_confirm_widget_callback, + nfc_magic); + + // Setup and start worker + view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewWidget); +} + +bool nfc_magic_scene_write_confirm_on_event(void* context, SceneManagerEvent event) { + NfcMagic* nfc_magic = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == GuiButtonTypeLeft) { + consumed = scene_manager_previous_scene(nfc_magic->scene_manager); + } else if(event.event == GuiButtonTypeCenter) { + scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneWrite); + consumed = true; + } + } + return consumed; +} + +void nfc_magic_scene_write_confirm_on_exit(void* context) { + NfcMagic* nfc_magic = context; + + widget_reset(nfc_magic->widget); +} diff --git a/applications/plugins/nfc_magic/scenes/nfc_magic_scene_write_fail.c b/applications/plugins/nfc_magic/scenes/nfc_magic_scene_write_fail.c new file mode 100644 index 00000000000..8a465bf61e7 --- /dev/null +++ b/applications/plugins/nfc_magic/scenes/nfc_magic_scene_write_fail.c @@ -0,0 +1,58 @@ +#include "../nfc_magic_i.h" + +void nfc_magic_scene_write_fail_widget_callback( + GuiButtonType result, + InputType type, + void* context) { + NfcMagic* nfc_magic = context; + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, result); + } +} + +void nfc_magic_scene_write_fail_on_enter(void* context) { + NfcMagic* nfc_magic = context; + Widget* widget = nfc_magic->widget; + + notification_message(nfc_magic->notifications, &sequence_error); + + widget_add_icon_element(widget, 72, 17, &I_DolphinCommon_56x48); + widget_add_string_element( + widget, 7, 4, AlignLeft, AlignTop, FontPrimary, "Writing gone wrong!"); + widget_add_string_multiline_element( + widget, + 7, + 17, + AlignLeft, + AlignTop, + FontSecondary, + "Not all sectors\nwere written\ncorrectly."); + + widget_add_button_element( + widget, GuiButtonTypeLeft, "Finish", nfc_magic_scene_write_fail_widget_callback, nfc_magic); + + // Setup and start worker + view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewWidget); +} + +bool nfc_magic_scene_write_fail_on_event(void* context, SceneManagerEvent event) { + NfcMagic* nfc_magic = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == GuiButtonTypeLeft) { + consumed = scene_manager_search_and_switch_to_previous_scene( + nfc_magic->scene_manager, NfcMagicSceneStart); + } + } else if(event.type == SceneManagerEventTypeBack) { + consumed = scene_manager_search_and_switch_to_previous_scene( + nfc_magic->scene_manager, NfcMagicSceneStart); + } + return consumed; +} + +void nfc_magic_scene_write_fail_on_exit(void* context) { + NfcMagic* nfc_magic = context; + + widget_reset(nfc_magic->widget); +} diff --git a/applications/plugins/nfc_magic/scenes/nfc_magic_scene_wrong_card.c b/applications/plugins/nfc_magic/scenes/nfc_magic_scene_wrong_card.c new file mode 100644 index 00000000000..69bf9eb5026 --- /dev/null +++ b/applications/plugins/nfc_magic/scenes/nfc_magic_scene_wrong_card.c @@ -0,0 +1,53 @@ +#include "../nfc_magic_i.h" + +void nfc_magic_scene_wrong_card_widget_callback( + GuiButtonType result, + InputType type, + void* context) { + NfcMagic* nfc_magic = context; + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, result); + } +} + +void nfc_magic_scene_wrong_card_on_enter(void* context) { + NfcMagic* nfc_magic = context; + Widget* widget = nfc_magic->widget; + + notification_message(nfc_magic->notifications, &sequence_error); + + widget_add_icon_element(widget, 73, 17, &I_DolphinCommon_56x48); + widget_add_string_element( + widget, 1, 4, AlignLeft, AlignTop, FontPrimary, "This is wrong card"); + widget_add_string_multiline_element( + widget, + 1, + 17, + AlignLeft, + AlignTop, + FontSecondary, + "Writing is supported\nonly for 4 bytes UID\nMifare CLassic 1k"); + widget_add_button_element( + widget, GuiButtonTypeLeft, "Retry", nfc_magic_scene_wrong_card_widget_callback, nfc_magic); + + // Setup and start worker + view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewWidget); +} + +bool nfc_magic_scene_wrong_card_on_event(void* context, SceneManagerEvent event) { + NfcMagic* nfc_magic = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == GuiButtonTypeLeft) { + consumed = scene_manager_previous_scene(nfc_magic->scene_manager); + } + } + return consumed; +} + +void nfc_magic_scene_wrong_card_on_exit(void* context) { + NfcMagic* nfc_magic = context; + + widget_reset(nfc_magic->widget); +} diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 65e6f48577e..0c10258f741 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -148,6 +148,7 @@ Header,+,lib/libusb_stm32/inc/usbd_core.h,, Header,+,lib/mbedtls/include/mbedtls/des.h,, Header,+,lib/mbedtls/include/mbedtls/sha1.h,, Header,+,lib/micro-ecc/uECC.h,, +Header,+,lib/nfc/nfc_device.h,, Header,+,lib/one_wire/ibutton/ibutton_worker.h,, Header,+,lib/one_wire/maxim_crc.h,, Header,+,lib/one_wire/one_wire_device.h,, @@ -668,6 +669,14 @@ Function,-,coshl,long double,long double Function,-,cosl,long double,long double Function,+,crc32_calc_buffer,uint32_t,"uint32_t, const void*, size_t" Function,+,crc32_calc_file,uint32_t,"File*, const FileCrcProgressCb, void*" +Function,-,crypto1_bit,uint8_t,"Crypto1*, uint8_t, int" +Function,-,crypto1_byte,uint8_t,"Crypto1*, uint8_t, int" +Function,-,crypto1_decrypt,void,"Crypto1*, uint8_t*, uint16_t, uint8_t*" +Function,-,crypto1_encrypt,void,"Crypto1*, uint8_t*, uint8_t*, uint16_t, uint8_t*, uint8_t*" +Function,-,crypto1_filter,uint32_t,uint32_t +Function,-,crypto1_init,void,"Crypto1*, uint64_t" +Function,-,crypto1_reset,void,Crypto1* +Function,-,crypto1_word,uint32_t,"Crypto1*, uint32_t, int" Function,-,ctermid,char*,char* Function,-,ctime,char*,const time_t* Function,-,ctime_r,char*,"const time_t*, char*" @@ -750,6 +759,8 @@ Function,+,elements_text_box,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, Function,+,empty_screen_alloc,EmptyScreen*, Function,+,empty_screen_free,void,EmptyScreen* Function,+,empty_screen_get_view,View*,EmptyScreen* +Function,-,emv_card_emulation,_Bool,FuriHalNfcTxRxContext* +Function,-,emv_read_bank_card,_Bool,"FuriHalNfcTxRxContext*, EmvApplication*" Function,-,erand48,double,unsigned short[3] Function,-,erf,double,double Function,-,erfc,double,double @@ -1161,6 +1172,7 @@ Function,+,furi_hal_nfc_ll_set_fdt_poll,void,uint32_t Function,+,furi_hal_nfc_ll_set_guard_time,void,uint32_t Function,+,furi_hal_nfc_ll_set_mode,FuriHalNfcReturn,"FuriHalNfcMode, FuriHalNfcBitrate, FuriHalNfcBitrate" Function,+,furi_hal_nfc_ll_txrx,FuriHalNfcReturn,"uint8_t*, uint16_t, uint8_t*, uint16_t, uint16_t*, uint32_t, uint32_t" +Function,+,furi_hal_nfc_ll_txrx_bits,FuriHalNfcReturn,"uint8_t*, uint16_t, uint8_t*, uint16_t, uint16_t*, uint32_t, uint32_t" Function,+,furi_hal_nfc_ll_txrx_off,void, Function,+,furi_hal_nfc_ll_txrx_on,void, Function,+,furi_hal_nfc_sleep,void, @@ -1806,6 +1818,100 @@ Function,+,menu_free,void,Menu* Function,+,menu_get_view,View*,Menu* Function,+,menu_reset,void,Menu* Function,+,menu_set_selected_item,void,"Menu*, uint32_t" +Function,-,mf_classic_auth_attempt,_Bool,"FuriHalNfcTxRxContext*, MfClassicAuthContext*, uint64_t" +Function,-,mf_classic_auth_init_context,void,"MfClassicAuthContext*, uint8_t" +Function,-,mf_classic_authenticate,_Bool,"FuriHalNfcTxRxContext*, uint8_t, uint64_t, MfClassicKey" +Function,-,mf_classic_check_card_type,_Bool,"uint8_t, uint8_t, uint8_t" +Function,-,mf_classic_dict_add_key,_Bool,"MfClassicDict*, uint8_t*" +Function,-,mf_classic_dict_add_key_str,_Bool,"MfClassicDict*, FuriString*" +Function,-,mf_classic_dict_alloc,MfClassicDict*,MfClassicDictType +Function,-,mf_classic_dict_check_presence,_Bool,MfClassicDictType +Function,-,mf_classic_dict_delete_index,_Bool,"MfClassicDict*, uint32_t" +Function,-,mf_classic_dict_find_index,_Bool,"MfClassicDict*, uint8_t*, uint32_t*" +Function,-,mf_classic_dict_find_index_str,_Bool,"MfClassicDict*, FuriString*, uint32_t*" +Function,-,mf_classic_dict_free,void,MfClassicDict* +Function,-,mf_classic_dict_get_key_at_index,_Bool,"MfClassicDict*, uint64_t*, uint32_t" +Function,-,mf_classic_dict_get_key_at_index_str,_Bool,"MfClassicDict*, FuriString*, uint32_t" +Function,-,mf_classic_dict_get_next_key,_Bool,"MfClassicDict*, uint64_t*" +Function,-,mf_classic_dict_get_next_key_str,_Bool,"MfClassicDict*, FuriString*" +Function,-,mf_classic_dict_get_total_keys,uint32_t,MfClassicDict* +Function,-,mf_classic_dict_is_key_present,_Bool,"MfClassicDict*, uint8_t*" +Function,-,mf_classic_dict_is_key_present_str,_Bool,"MfClassicDict*, FuriString*" +Function,-,mf_classic_dict_rewind,_Bool,MfClassicDict* +Function,-,mf_classic_emulator,_Bool,"MfClassicEmulator*, FuriHalNfcTxRxContext*" +Function,-,mf_classic_get_classic_type,MfClassicType,"int8_t, uint8_t, uint8_t" +Function,-,mf_classic_get_read_sectors_and_keys,void,"MfClassicData*, uint8_t*, uint8_t*" +Function,-,mf_classic_get_sector_by_block,uint8_t,uint8_t +Function,-,mf_classic_get_sector_trailer_block_num_by_sector,uint8_t,uint8_t +Function,-,mf_classic_get_sector_trailer_by_sector,MfClassicSectorTrailer*,"MfClassicData*, uint8_t" +Function,-,mf_classic_get_total_sectors_num,uint8_t,MfClassicType +Function,-,mf_classic_get_type_str,const char*,MfClassicType +Function,-,mf_classic_is_allowed_access_data_block,_Bool,"MfClassicData*, uint8_t, MfClassicKey, MfClassicAction" +Function,-,mf_classic_is_allowed_access_sector_trailer,_Bool,"MfClassicData*, uint8_t, MfClassicKey, MfClassicAction" +Function,-,mf_classic_is_block_read,_Bool,"MfClassicData*, uint8_t" +Function,-,mf_classic_is_card_read,_Bool,MfClassicData* +Function,-,mf_classic_is_key_found,_Bool,"MfClassicData*, uint8_t, MfClassicKey" +Function,-,mf_classic_is_sector_data_read,_Bool,"MfClassicData*, uint8_t" +Function,-,mf_classic_is_sector_read,_Bool,"MfClassicData*, uint8_t" +Function,-,mf_classic_is_sector_trailer,_Bool,uint8_t +Function,-,mf_classic_read_card,uint8_t,"FuriHalNfcTxRxContext*, MfClassicReader*, MfClassicData*" +Function,-,mf_classic_read_sector,void,"FuriHalNfcTxRxContext*, MfClassicData*, uint8_t" +Function,-,mf_classic_reader_add_sector,void,"MfClassicReader*, uint8_t, uint64_t, uint64_t" +Function,-,mf_classic_set_block_read,void,"MfClassicData*, uint8_t, MfClassicBlock*" +Function,-,mf_classic_set_key_found,void,"MfClassicData*, uint8_t, MfClassicKey, uint64_t" +Function,-,mf_classic_set_key_not_found,void,"MfClassicData*, uint8_t, MfClassicKey" +Function,-,mf_classic_set_sector_data_not_read,void,MfClassicData* +Function,-,mf_classic_update_card,uint8_t,"FuriHalNfcTxRxContext*, MfClassicData*" +Function,-,mf_classic_write_block,_Bool,"FuriHalNfcTxRxContext*, MfClassicBlock*, uint8_t, MfClassicKey, uint64_t" +Function,-,mf_classic_write_sector,_Bool,"FuriHalNfcTxRxContext*, MfClassicData*, MfClassicData*, uint8_t" +Function,-,mf_df_cat_application,void,"MifareDesfireApplication*, FuriString*" +Function,-,mf_df_cat_application_info,void,"MifareDesfireApplication*, FuriString*" +Function,-,mf_df_cat_card_info,void,"MifareDesfireData*, FuriString*" +Function,-,mf_df_cat_data,void,"MifareDesfireData*, FuriString*" +Function,-,mf_df_cat_file,void,"MifareDesfireFile*, FuriString*" +Function,-,mf_df_cat_free_mem,void,"MifareDesfireFreeMemory*, FuriString*" +Function,-,mf_df_cat_key_settings,void,"MifareDesfireKeySettings*, FuriString*" +Function,-,mf_df_cat_version,void,"MifareDesfireVersion*, FuriString*" +Function,-,mf_df_check_card_type,_Bool,"uint8_t, uint8_t, uint8_t" +Function,-,mf_df_clear,void,MifareDesfireData* +Function,-,mf_df_parse_get_application_ids_response,_Bool,"uint8_t*, uint16_t, MifareDesfireApplication**" +Function,-,mf_df_parse_get_file_ids_response,_Bool,"uint8_t*, uint16_t, MifareDesfireFile**" +Function,-,mf_df_parse_get_file_settings_response,_Bool,"uint8_t*, uint16_t, MifareDesfireFile*" +Function,-,mf_df_parse_get_free_memory_response,_Bool,"uint8_t*, uint16_t, MifareDesfireFreeMemory*" +Function,-,mf_df_parse_get_key_settings_response,_Bool,"uint8_t*, uint16_t, MifareDesfireKeySettings*" +Function,-,mf_df_parse_get_key_version_response,_Bool,"uint8_t*, uint16_t, MifareDesfireKeyVersion*" +Function,-,mf_df_parse_get_version_response,_Bool,"uint8_t*, uint16_t, MifareDesfireVersion*" +Function,-,mf_df_parse_read_data_response,_Bool,"uint8_t*, uint16_t, MifareDesfireFile*" +Function,-,mf_df_parse_select_application_response,_Bool,"uint8_t*, uint16_t" +Function,-,mf_df_prepare_get_application_ids,uint16_t,uint8_t* +Function,-,mf_df_prepare_get_file_ids,uint16_t,uint8_t* +Function,-,mf_df_prepare_get_file_settings,uint16_t,"uint8_t*, uint8_t" +Function,-,mf_df_prepare_get_free_memory,uint16_t,uint8_t* +Function,-,mf_df_prepare_get_key_settings,uint16_t,uint8_t* +Function,-,mf_df_prepare_get_key_version,uint16_t,"uint8_t*, uint8_t" +Function,-,mf_df_prepare_get_value,uint16_t,"uint8_t*, uint8_t" +Function,-,mf_df_prepare_get_version,uint16_t,uint8_t* +Function,-,mf_df_prepare_read_data,uint16_t,"uint8_t*, uint8_t, uint32_t, uint32_t" +Function,-,mf_df_prepare_read_records,uint16_t,"uint8_t*, uint8_t, uint32_t, uint32_t" +Function,-,mf_df_prepare_select_application,uint16_t,"uint8_t*, uint8_t[3]" +Function,-,mf_df_read_card,_Bool,"FuriHalNfcTxRxContext*, MifareDesfireData*" +Function,-,mf_ul_check_card_type,_Bool,"uint8_t, uint8_t, uint8_t" +Function,-,mf_ul_prepare_emulation,void,"MfUltralightEmulator*, MfUltralightData*" +Function,-,mf_ul_prepare_emulation_response,_Bool,"uint8_t*, uint16_t, uint8_t*, uint16_t*, uint32_t*, void*" +Function,-,mf_ul_pwdgen_amiibo,uint32_t,FuriHalNfcDevData* +Function,-,mf_ul_pwdgen_xiaomi,uint32_t,FuriHalNfcDevData* +Function,-,mf_ul_read_card,_Bool,"FuriHalNfcTxRxContext*, MfUltralightReader*, MfUltralightData*" +Function,-,mf_ul_reset,void,MfUltralightData* +Function,-,mf_ul_reset_emulation,void,"MfUltralightEmulator*, _Bool" +Function,-,mf_ultralight_authenticate,_Bool,"FuriHalNfcTxRxContext*, uint32_t, uint16_t*" +Function,-,mf_ultralight_fast_read_pages,_Bool,"FuriHalNfcTxRxContext*, MfUltralightReader*, MfUltralightData*" +Function,-,mf_ultralight_get_config_pages,MfUltralightConfigPages*,MfUltralightData* +Function,-,mf_ultralight_read_counters,_Bool,"FuriHalNfcTxRxContext*, MfUltralightData*" +Function,-,mf_ultralight_read_pages,_Bool,"FuriHalNfcTxRxContext*, MfUltralightReader*, MfUltralightData*" +Function,-,mf_ultralight_read_pages_direct,_Bool,"FuriHalNfcTxRxContext*, uint8_t, uint8_t*" +Function,-,mf_ultralight_read_signature,_Bool,"FuriHalNfcTxRxContext*, MfUltralightData*" +Function,-,mf_ultralight_read_tearing_flags,_Bool,"FuriHalNfcTxRxContext*, MfUltralightData*" +Function,-,mf_ultralight_read_version,_Bool,"FuriHalNfcTxRxContext*, MfUltralightReader*, MfUltralightData*" Function,-,mkdtemp,char*,char* Function,-,mkostemp,int,"char*, int" Function,-,mkostemps,int,"char*, int, int" @@ -1829,6 +1935,19 @@ Function,-,nextafterl,long double,"long double, long double" Function,-,nexttoward,double,"double, long double" Function,-,nexttowardf,float,"float, long double" Function,-,nexttowardl,long double,"long double, long double" +Function,+,nfc_device_alloc,NfcDevice*, +Function,+,nfc_device_clear,void,NfcDevice* +Function,+,nfc_device_data_clear,void,NfcDeviceData* +Function,+,nfc_device_delete,_Bool,"NfcDevice*, _Bool" +Function,+,nfc_device_free,void,NfcDevice* +Function,+,nfc_device_load,_Bool,"NfcDevice*, const char*, _Bool" +Function,+,nfc_device_load_key_cache,_Bool,NfcDevice* +Function,+,nfc_device_restore,_Bool,"NfcDevice*, _Bool" +Function,+,nfc_device_save,_Bool,"NfcDevice*, const char*" +Function,+,nfc_device_save_shadow,_Bool,"NfcDevice*, const char*" +Function,+,nfc_device_set_loading_callback,void,"NfcDevice*, NfcLoadingCallback, void*" +Function,+,nfc_device_set_name,void,"NfcDevice*, const char*" +Function,+,nfc_file_select,_Bool,NfcDevice* Function,-,nfca_append_crc16,void,"uint8_t*, uint16_t" Function,-,nfca_emulation_handler,_Bool,"uint8_t*, uint16_t, uint8_t*, uint16_t*" Function,-,nfca_get_crc16,uint16_t,"uint8_t*, uint16_t" @@ -1913,6 +2032,7 @@ Function,+,power_reboot,void,PowerBootMode Function,+,powf,float,"float, float" Function,-,powl,long double,"long double, long double" Function,-,printf,int,"const char*, ..." +Function,-,prng_successor,uint32_t,"uint32_t, uint32_t" Function,+,protocol_dict_alloc,ProtocolDict*,"const ProtocolBase**, size_t" Function,+,protocol_dict_decoders_feed,ProtocolId,"ProtocolDict*, _Bool, uint32_t" Function,+,protocol_dict_decoders_feed_by_feature,ProtocolId,"ProtocolDict*, uint32_t, _Bool, uint32_t" @@ -2129,6 +2249,7 @@ Function,-,rfalT1TPollerRall,ReturnCode,"const uint8_t*, uint8_t*, uint16_t, uin Function,-,rfalT1TPollerRid,ReturnCode,rfalT1TRidRes* Function,-,rfalT1TPollerWrite,ReturnCode,"const uint8_t*, uint8_t, uint8_t" Function,-,rfalTransceiveBitsBlockingTx,ReturnCode,"uint8_t*, uint16_t, uint8_t*, uint16_t, uint16_t*, uint32_t, uint32_t" +Function,-,rfalTransceiveBitsBlockingTxRx,ReturnCode,"uint8_t*, uint16_t, uint8_t*, uint16_t, uint16_t*, uint32_t, uint32_t" Function,-,rfalTransceiveBlockingRx,ReturnCode, Function,-,rfalTransceiveBlockingTx,ReturnCode,"uint8_t*, uint16_t, uint8_t*, uint16_t, uint16_t*, uint32_t, uint32_t" Function,-,rfalTransceiveBlockingTxRx,ReturnCode,"uint8_t*, uint16_t, uint8_t*, uint16_t, uint16_t*, uint32_t, uint32_t" diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc.c b/firmware/targets/f7/furi_hal/furi_hal_nfc.c index 3ebf4f82b79..2d27313ae5e 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_nfc.c +++ b/firmware/targets/f7/furi_hal/furi_hal_nfc.c @@ -786,6 +786,17 @@ FuriHalNfcReturn furi_hal_nfc_ll_txrx( return rfalTransceiveBlockingTxRx(txBuf, txBufLen, rxBuf, rxBufLen, actLen, flags, fwt); } +FuriHalNfcReturn furi_hal_nfc_ll_txrx_bits( + uint8_t* txBuf, + uint16_t txBufLen, + uint8_t* rxBuf, + uint16_t rxBufLen, + uint16_t* actLen, + uint32_t flags, + uint32_t fwt) { + return rfalTransceiveBitsBlockingTxRx(txBuf, txBufLen, rxBuf, rxBufLen, actLen, flags, fwt); +} + void furi_hal_nfc_ll_poll() { rfalWorker(); } \ No newline at end of file diff --git a/firmware/targets/furi_hal_include/furi_hal_nfc.h b/firmware/targets/furi_hal_include/furi_hal_nfc.h index 90d968fea99..d3f6de60284 100644 --- a/firmware/targets/furi_hal_include/furi_hal_nfc.h +++ b/firmware/targets/furi_hal_include/furi_hal_nfc.h @@ -398,6 +398,7 @@ void furi_hal_nfc_ll_txrx_on(); void furi_hal_nfc_ll_txrx_off(); +// TODO rework all pollers with furi_hal_nfc_ll_txrx_bits FuriHalNfcReturn furi_hal_nfc_ll_txrx( uint8_t* txBuf, uint16_t txBufLen, @@ -407,6 +408,15 @@ FuriHalNfcReturn furi_hal_nfc_ll_txrx( uint32_t flags, uint32_t fwt); +FuriHalNfcReturn furi_hal_nfc_ll_txrx_bits( + uint8_t* txBuf, + uint16_t txBufLen, + uint8_t* rxBuf, + uint16_t rxBufLen, + uint16_t* actLen, + uint32_t flags, + uint32_t fwt); + void furi_hal_nfc_ll_poll(); #ifdef __cplusplus diff --git a/lib/ST25RFAL002/include/rfal_rf.h b/lib/ST25RFAL002/include/rfal_rf.h index e1b864830b2..35e9a445411 100644 --- a/lib/ST25RFAL002/include/rfal_rf.h +++ b/lib/ST25RFAL002/include/rfal_rf.h @@ -1496,6 +1496,15 @@ ReturnCode rfalTransceiveBlockingTxRx( uint32_t flags, uint32_t fwt); +ReturnCode rfalTransceiveBitsBlockingTxRx( + uint8_t* txBuf, + uint16_t txBufLen, + uint8_t* rxBuf, + uint16_t rxBufLen, + uint16_t* actLen, + uint32_t flags, + uint32_t fwt); + ReturnCode rfalTransceiveBitsBlockingTx( uint8_t* txBuf, uint16_t txBufLen, diff --git a/lib/ST25RFAL002/source/st25r3916/rfal_rfst25r3916.c b/lib/ST25RFAL002/source/st25r3916/rfal_rfst25r3916.c index 9ad35bcb6bb..0bad67a6dba 100644 --- a/lib/ST25RFAL002/source/st25r3916/rfal_rfst25r3916.c +++ b/lib/ST25RFAL002/source/st25r3916/rfal_rfst25r3916.c @@ -1607,6 +1607,23 @@ ReturnCode rfalTransceiveBlockingTxRx( return ret; } +ReturnCode rfalTransceiveBitsBlockingTxRx( + uint8_t* txBuf, + uint16_t txBufLen, + uint8_t* rxBuf, + uint16_t rxBufLen, + uint16_t* actLen, + uint32_t flags, + uint32_t fwt) { + ReturnCode ret; + + EXIT_ON_ERR( + ret, rfalTransceiveBitsBlockingTx(txBuf, txBufLen, rxBuf, rxBufLen, actLen, flags, fwt)); + ret = rfalTransceiveBlockingRx(); + + return ret; +} + /*******************************************************************************/ static ReturnCode rfalRunTransceiveWorker(void) { if(gRFAL.state == RFAL_STATE_TXRX) { diff --git a/lib/nfc/SConscript b/lib/nfc/SConscript index 657f3a9e5b2..c6b70a67790 100644 --- a/lib/nfc/SConscript +++ b/lib/nfc/SConscript @@ -4,6 +4,9 @@ env.Append( CPPPATH=[ "#/lib/nfc", ], + SDK_HEADERS=[ + File("#/lib/nfc/nfc_device.h"), + ], ) libenv = env.Clone(FW_LIB_NAME="nfc") diff --git a/lib/nfc/nfc_device.h b/lib/nfc/nfc_device.h index 6cac72c6b74..c8e8517ae6d 100644 --- a/lib/nfc/nfc_device.h +++ b/lib/nfc/nfc_device.h @@ -12,6 +12,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + #define NFC_DEV_NAME_MAX_LEN 22 #define NFC_READER_DATA_MAX_SIZE 64 #define NFC_DICT_KEY_BATCH_SIZE 50 @@ -101,3 +105,7 @@ bool nfc_device_delete(NfcDevice* dev, bool use_load_path); bool nfc_device_restore(NfcDevice* dev, bool use_load_path); void nfc_device_set_loading_callback(NfcDevice* dev, NfcLoadingCallback callback, void* context); + +#ifdef __cplusplus +} +#endif From 04e50c9f893db9ea1fd4517a0a2c81c5efcd2be8 Mon Sep 17 00:00:00 2001 From: hedger Date: Sat, 5 Nov 2022 15:47:59 +0400 Subject: [PATCH 203/824] fbt: fixes for ufbt pt3 (#1970) * fbt: replaced debug dir paths with FBT_DEBUG_DIR * scripts: updated requirements.txt * fbt: fixed wrong import * fbt: removed delayed import for file2image * fbt: added UPDATE_BUNDLE_DIR internal var * fbt: cleaner internal management of extapps * applications: added fap_libs for core apps to link with resources when building with --extra-ext-apps * fbt: removed deprecation stub for faps * fbt: added quotation for icons build cmd * fbt: reworked BUILD_DIR & fap work dir handling; fap debug: using debug elf path from fbt * fbt: explicit LIB_DIST_DIR --- SConstruct | 30 ++++--- applications/main/bad_usb/application.fam | 1 + applications/main/gpio/application.fam | 1 + applications/main/ibutton/application.fam | 1 + applications/main/infrared/application.fam | 1 + applications/main/lfrfid/application.fam | 1 + applications/main/u2f/application.fam | 1 + debug/flipperapps.py | 46 ++++++++--- fbt_options.py | 4 +- firmware.scons | 13 ++-- scripts/fbt/elfmanifest.py | 3 +- scripts/fbt/sdk/cache.py | 3 + scripts/fbt_tools/fbt_assets.py | 2 +- scripts/fbt_tools/fbt_debugopts.py | 16 ++-- scripts/fbt_tools/fbt_dist.py | 4 +- scripts/fbt_tools/fbt_extapps.py | 91 +++++++++++++--------- scripts/requirements.txt | 10 ++- site_scons/commandline.scons | 2 +- site_scons/environ.scons | 2 +- site_scons/extapps.scons | 73 +++++++---------- 20 files changed, 179 insertions(+), 126 deletions(-) diff --git a/SConstruct b/SConstruct index 13a698c81fb..67eac382523 100644 --- a/SConstruct +++ b/SConstruct @@ -43,6 +43,7 @@ distenv = coreenv.Clone( "jflash", ], ENV=os.environ, + UPDATE_BUNDLE_DIR="dist/${DIST_DIR}/f${TARGET_HW}-update-${DIST_SUFFIX}", ) firmware_env = distenv.AddFwProject( @@ -140,21 +141,28 @@ distenv.Default(basic_dist) dist_dir = distenv.GetProjetDirName() fap_dist = [ distenv.Install( - f"#/dist/{dist_dir}/apps/debug_elf", - firmware_env["FW_EXTAPPS"]["debug"].values(), + distenv.Dir(f"#/dist/{dist_dir}/apps/debug_elf"), + list( + app_artifact.debug + for app_artifact in firmware_env["FW_EXTAPPS"].applications.values() + ), ), - *( - distenv.Install(f"#/dist/{dist_dir}/apps/{dist_entry[0]}", dist_entry[1]) - for dist_entry in firmware_env["FW_EXTAPPS"]["dist"].values() + distenv.Install( + f"#/dist/{dist_dir}/apps", + "#/assets/resources/apps", ), ] -Depends(fap_dist, firmware_env["FW_EXTAPPS"]["validators"].values()) +Depends( + fap_dist, + list( + app_artifact.validator + for app_artifact in firmware_env["FW_EXTAPPS"].applications.values() + ), +) Alias("fap_dist", fap_dist) # distenv.Default(fap_dist) -distenv.Depends( - firmware_env["FW_RESOURCES"], firmware_env["FW_EXTAPPS"]["resources_dist"] -) +distenv.Depends(firmware_env["FW_RESOURCES"], firmware_env["FW_EXTAPPS"].resources_dist) # Target for bundling core2 package for qFlipper @@ -192,6 +200,7 @@ firmware_debug = distenv.PhonyTarget( source=firmware_env["FW_ELF"], GDBOPTS="${GDBOPTS_BASE}", GDBREMOTE="${OPENOCD_GDB_PIPE}", + FBT_FAP_DEBUG_ELF_ROOT=firmware_env.subst("$FBT_FAP_DEBUG_ELF_ROOT"), ) distenv.Depends(firmware_debug, firmware_flash) @@ -201,6 +210,7 @@ distenv.PhonyTarget( source=firmware_env["FW_ELF"], GDBOPTS="${GDBOPTS_BASE} ${GDBOPTS_BLACKMAGIC}", GDBREMOTE="${BLACKMAGIC_ADDR}", + FBT_FAP_DEBUG_ELF_ROOT=firmware_env.subst("$FBT_FAP_DEBUG_ELF_ROOT"), ) # Debug alien elf @@ -209,7 +219,7 @@ distenv.PhonyTarget( "${GDBPYCOM}", GDBOPTS="${GDBOPTS_BASE}", GDBREMOTE="${OPENOCD_GDB_PIPE}", - GDBPYOPTS='-ex "source debug/PyCortexMDebug/PyCortexMDebug.py" ', + GDBPYOPTS='-ex "source ${FBT_DEBUG_DIR}/PyCortexMDebug/PyCortexMDebug.py" ', ) distenv.PhonyTarget( diff --git a/applications/main/bad_usb/application.fam b/applications/main/bad_usb/application.fam index 4da34f0de1b..2442dd3aa0f 100644 --- a/applications/main/bad_usb/application.fam +++ b/applications/main/bad_usb/application.fam @@ -11,4 +11,5 @@ App( stack_size=2 * 1024, icon="A_BadUsb_14", order=70, + fap_libs=["assets"], ) diff --git a/applications/main/gpio/application.fam b/applications/main/gpio/application.fam index 64f8db5b098..efeb8b6fe8b 100644 --- a/applications/main/gpio/application.fam +++ b/applications/main/gpio/application.fam @@ -8,4 +8,5 @@ App( stack_size=1 * 1024, icon="A_GPIO_14", order=50, + fap_libs=["assets"], ) diff --git a/applications/main/ibutton/application.fam b/applications/main/ibutton/application.fam index 0bc6f8a9b65..77bb9a33c63 100644 --- a/applications/main/ibutton/application.fam +++ b/applications/main/ibutton/application.fam @@ -12,6 +12,7 @@ App( icon="A_iButton_14", stack_size=2 * 1024, order=60, + fap_libs=["assets"], ) App( diff --git a/applications/main/infrared/application.fam b/applications/main/infrared/application.fam index 6f76ed429a6..9c5eaf392c4 100644 --- a/applications/main/infrared/application.fam +++ b/applications/main/infrared/application.fam @@ -12,6 +12,7 @@ App( icon="A_Infrared_14", stack_size=3 * 1024, order=40, + fap_libs=["assets"], ) App( diff --git a/applications/main/lfrfid/application.fam b/applications/main/lfrfid/application.fam index 4a1498181c5..150a6f3dbb2 100644 --- a/applications/main/lfrfid/application.fam +++ b/applications/main/lfrfid/application.fam @@ -14,6 +14,7 @@ App( icon="A_125khz_14", stack_size=2 * 1024, order=20, + fap_libs=["assets"], ) App( diff --git a/applications/main/u2f/application.fam b/applications/main/u2f/application.fam index 6b32e022530..82010ffb439 100644 --- a/applications/main/u2f/application.fam +++ b/applications/main/u2f/application.fam @@ -11,4 +11,5 @@ App( stack_size=2 * 1024, icon="A_U2F_14", order=80, + fap_libs=["assets"], ) diff --git a/debug/flipperapps.py b/debug/flipperapps.py index 8e1aa2daf90..e815e40b17b 100644 --- a/debug/flipperapps.py +++ b/debug/flipperapps.py @@ -1,5 +1,5 @@ from dataclasses import dataclass -from typing import Tuple, Dict +from typing import Optional, Tuple, Dict, ClassVar import struct import posixpath import os @@ -22,14 +22,18 @@ class AppState: debug_link_elf: str = "" debug_link_crc: int = 0 + DEBUG_ELF_ROOT: ClassVar[Optional[str]] = None + def __post_init__(self): if self.other_sections is None: self.other_sections = {} - def get_original_elf_path(self, elf_path="build/latest/.extapps") -> str: + def get_original_elf_path(self) -> str: + if self.DEBUG_ELF_ROOT is None: + raise ValueError("DEBUG_ELF_ROOT not set; call fap-set-debug-elf-root") return ( - posixpath.join(elf_path, self.debug_link_elf) - if elf_path + posixpath.join(self.DEBUG_ELF_ROOT, self.debug_link_elf) + if self.DEBUG_ELF_ROOT else self.debug_link_elf ) @@ -84,7 +88,9 @@ def from_gdb(gdb_app: "AppState") -> "AppState": if debug_link_size := int(app_state["debug_link_info"]["debug_link_size"]): debug_link_data = ( gdb.selected_inferior() - .read_memory(int(app_state["debug_link_info"]["debug_link"]), debug_link_size) + .read_memory( + int(app_state["debug_link_info"]["debug_link"]), debug_link_size + ) .tobytes() ) state.debug_link_elf, state.debug_link_crc = AppState.parse_debug_link_data( @@ -103,6 +109,29 @@ def from_gdb(gdb_app: "AppState") -> "AppState": return state +class SetFapDebugElfRoot(gdb.Command): + """Set path to original ELF files for debug info""" + + def __init__(self): + super().__init__( + "fap-set-debug-elf-root", gdb.COMMAND_FILES, gdb.COMPLETE_FILENAME + ) + self.dont_repeat() + + def invoke(self, arg, from_tty): + AppState.DEBUG_ELF_ROOT = arg + try: + global helper + print(f"Set '{arg}' as debug info lookup path for Flipper external apps") + helper.attach_fw() + gdb.events.stop.connect(helper.handle_stop) + except gdb.error as e: + print(f"Support for Flipper external apps debug is not available: {e}") + + +SetFapDebugElfRoot() + + class FlipperAppDebugHelper: def __init__(self): self.app_ptr = None @@ -149,9 +178,4 @@ def handle_stop(self, event) -> None: helper = FlipperAppDebugHelper() -try: - helper.attach_fw() - print("Support for Flipper external apps debug is enabled") - gdb.events.stop.connect(helper.handle_stop) -except gdb.error as e: - print(f"Support for Flipper external apps debug is not available: {e}") +print("Support for Flipper external apps debug is loaded") diff --git a/fbt_options.py b/fbt_options.py index 6ef9759e39c..11124b93698 100644 --- a/fbt_options.py +++ b/fbt_options.py @@ -49,12 +49,12 @@ "-c", "transport select hla_swd", "-f", - "debug/stm32wbx.cfg", + "${FBT_DEBUG_DIR}/stm32wbx.cfg", "-c", "stm32wbx.cpu configure -rtos auto", ] -SVD_FILE = "debug/STM32WB55_CM4.svd" +SVD_FILE = "${FBT_DEBUG_DIR}/STM32WB55_CM4.svd" # Look for blackmagic probe on serial ports and local network BLACKMAGIC = "auto" diff --git a/firmware.scons b/firmware.scons index a0c1ab33943..6feb73ac339 100644 --- a/firmware.scons +++ b/firmware.scons @@ -20,8 +20,7 @@ env = ENV.Clone( BUILD_DIR=fw_build_meta["build_dir"], IS_BASE_FIRMWARE=fw_build_meta["type"] == "firmware", FW_FLAVOR=fw_build_meta["flavor"], - PLUGIN_ELF_DIR="${BUILD_DIR}", - LIB_DIST_DIR="${BUILD_DIR}/lib", + LIB_DIST_DIR=fw_build_meta["build_dir"].Dir("lib"), LINT_SOURCES=[ "applications", ], @@ -142,12 +141,14 @@ for app_dir, _ in env["APPDIRS"]: fwenv.PrepareApplicationsBuild() -# Build external apps +# Build external apps + configure SDK if env["IS_BASE_FIRMWARE"]: - extapps = fwenv["FW_EXTAPPS"] = SConscript( - "site_scons/extapps.scons", exports={"ENV": fwenv} + fwenv.SetDefault(FBT_FAP_DEBUG_ELF_ROOT="${BUILD_DIR}/.extapps") + fwenv["FW_EXTAPPS"] = SConscript( + "site_scons/extapps.scons", + exports={"ENV": fwenv}, ) - fw_artifacts.append(extapps["sdk_tree"]) + fw_artifacts.append(fwenv["FW_EXTAPPS"].sdk_tree) # Add preprocessor definitions for current set of apps diff --git a/scripts/fbt/elfmanifest.py b/scripts/fbt/elfmanifest.py index 313a64c092e..17bceddf4e3 100644 --- a/scripts/fbt/elfmanifest.py +++ b/scripts/fbt/elfmanifest.py @@ -5,6 +5,7 @@ from dataclasses import dataclass, field from .appmanifest import FlipperApplication +from flipper.assets.icon import file2image _MANIFEST_MAGIC = 0x52474448 @@ -53,8 +54,6 @@ def assemble_manifest_data( ): image_data = b"" if app_manifest.fap_icon: - from flipper.assets.icon import file2image - image = file2image(os.path.join(app_manifest._apppath, app_manifest.fap_icon)) if (image.width, image.height) != (10, 10): raise ValueError( diff --git a/scripts/fbt/sdk/cache.py b/scripts/fbt/sdk/cache.py index 62d42798c98..756c3782755 100644 --- a/scripts/fbt/sdk/cache.py +++ b/scripts/fbt/sdk/cache.py @@ -89,6 +89,9 @@ def get_valid_names(self): syms.update(map(lambda e: e.name, self.get_variables())) return syms + def get_disabled_names(self): + return set(map(lambda e: e.name, self.disabled_entries)) + def get_functions(self): return self._filter_enabled(self.sdk.functions) diff --git a/scripts/fbt_tools/fbt_assets.py b/scripts/fbt_tools/fbt_assets.py index 521c37e904d..e1748735874 100644 --- a/scripts/fbt_tools/fbt_assets.py +++ b/scripts/fbt_tools/fbt_assets.py @@ -139,7 +139,7 @@ def generate(env): BUILDERS={ "IconBuilder": Builder( action=Action( - '${PYTHON3} "${ASSETS_COMPILER}" icons ${ABSPATHGETTERFUNC(SOURCE)} ${TARGET.dir} --filename ${ICON_FILE_NAME}', + '${PYTHON3} "${ASSETS_COMPILER}" icons "${ABSPATHGETTERFUNC(SOURCE)}" "${TARGET.dir}" --filename ${ICON_FILE_NAME}', "${ICONSCOMSTR}", ), emitter=icons_emitter, diff --git a/scripts/fbt_tools/fbt_debugopts.py b/scripts/fbt_tools/fbt_debugopts.py index 0c0ce3d32a0..c3be5ca477a 100644 --- a/scripts/fbt_tools/fbt_debugopts.py +++ b/scripts/fbt_tools/fbt_debugopts.py @@ -1,7 +1,6 @@ from re import search from SCons.Errors import UserError -from fbt_options import OPENOCD_OPTS def _get_device_serials(search_str="STLink"): @@ -20,6 +19,9 @@ def GetDevices(env): def generate(env, **kw): env.AddMethod(GetDevices) + env.SetDefault( + FBT_DEBUG_DIR="${ROOT_DIR}/debug", + ) if (adapter_serial := env.subst("$OPENOCD_ADAPTER_SERIAL")) != "auto": env.Append( @@ -36,7 +38,7 @@ def generate(env, **kw): env.SetDefault( OPENOCD_GDB_PIPE=[ - "|openocd -c 'gdb_port pipe; log_output debug/openocd.log' ${[SINGLEQUOTEFUNC(OPENOCD_OPTS)]}" + "|openocd -c 'gdb_port pipe; log_output ${FBT_DEBUG_DIR}/openocd.log' ${[SINGLEQUOTEFUNC(OPENOCD_OPTS)]}" ], GDBOPTS_BASE=[ "-ex", @@ -58,17 +60,19 @@ def generate(env, **kw): ], GDBPYOPTS=[ "-ex", - "source debug/FreeRTOS/FreeRTOS.py", + "source ${FBT_DEBUG_DIR}/FreeRTOS/FreeRTOS.py", + "-ex", + "source ${FBT_DEBUG_DIR}/flipperapps.py", "-ex", - "source debug/flipperapps.py", + "fap-set-debug-elf-root ${FBT_FAP_DEBUG_ELF_ROOT}", "-ex", - "source debug/PyCortexMDebug/PyCortexMDebug.py", + "source ${FBT_DEBUG_DIR}/PyCortexMDebug/PyCortexMDebug.py", "-ex", "svd_load ${SVD_FILE}", "-ex", "compare-sections", ], - JFLASHPROJECT="${ROOT_DIR.abspath}/debug/fw.jflash", + JFLASHPROJECT="${FBT_DEBUG_DIR}/fw.jflash", ) diff --git a/scripts/fbt_tools/fbt_dist.py b/scripts/fbt_tools/fbt_dist.py index fb59e5b954e..f0b4434864a 100644 --- a/scripts/fbt_tools/fbt_dist.py +++ b/scripts/fbt_tools/fbt_dist.py @@ -22,7 +22,7 @@ def GetProjetDirName(env, project=None): def create_fw_build_targets(env, configuration_name): flavor = GetProjetDirName(env, configuration_name) - build_dir = env.Dir("build").Dir(flavor).abspath + build_dir = env.Dir("build").Dir(flavor) return env.SConscript( "firmware.scons", variant_dir=build_dir, @@ -131,7 +131,7 @@ def generate(env): "UsbInstall": Builder( action=[ Action( - '${PYTHON3} "${SELFUPDATE_SCRIPT}" dist/${DIST_DIR}/f${TARGET_HW}-update-${DIST_SUFFIX}/update.fuf' + '${PYTHON3} "${SELFUPDATE_SCRIPT}" ${UPDATE_BUNDLE_DIR}/update.fuf' ), Touch("${TARGET}"), ] diff --git a/scripts/fbt_tools/fbt_extapps.py b/scripts/fbt_tools/fbt_extapps.py index fb4dc2f1634..f1906191b9c 100644 --- a/scripts/fbt_tools/fbt_extapps.py +++ b/scripts/fbt_tools/fbt_extapps.py @@ -1,6 +1,9 @@ +from dataclasses import dataclass, field +from typing import Optional from SCons.Builder import Builder from SCons.Action import Action from SCons.Errors import UserError +from SCons.Node import NodeList import SCons.Warnings from fbt.elfmanifest import assemble_manifest_data @@ -16,6 +19,15 @@ from ansi.color import fg +@dataclass +class FlipperExternalAppInfo: + app: FlipperApplication + compact: NodeList = field(default_factory=NodeList) + debug: NodeList = field(default_factory=NodeList) + validator: NodeList = field(default_factory=NodeList) + installer: NodeList = field(default_factory=NodeList) + + def BuildAppElf(env, app): ext_apps_work_dir = env.subst("$EXT_APPS_WORK_DIR") app_work_dir = os.path.join(ext_apps_work_dir, app.appid) @@ -26,15 +38,7 @@ def BuildAppElf(env, app): app_alias = f"fap_{app.appid}" - # Deprecation stub - legacy_app_taget_name = f"{app_env['FIRMWARE_BUILD_CFG']}_{app.appid}" - - def legacy_app_build_stub(**kw): - raise UserError( - f"Target name '{legacy_app_taget_name}' is deprecated, use '{app_alias}' instead" - ) - - app_env.PhonyTarget(legacy_app_taget_name, Action(legacy_app_build_stub, None)) + app_artifacts = FlipperExternalAppInfo(app) externally_built_files = [] if app.fap_extbuild: @@ -115,20 +119,22 @@ def legacy_app_build_stub(**kw): CPPPATH=env.Dir(app_work_dir), ) - app_elf_raw = app_env.Program( + app_artifacts.debug = app_env.Program( os.path.join(ext_apps_work_dir, f"{app.appid}_d"), app_sources, APP_ENTRY=app.entry_point, ) - app_env.Clean(app_elf_raw, [*externally_built_files, app_env.Dir(app_work_dir)]) + app_env.Clean( + app_artifacts.debug, [*externally_built_files, app_env.Dir(app_work_dir)] + ) - app_elf_dump = app_env.ObjDump(app_elf_raw) + app_elf_dump = app_env.ObjDump(app_artifacts.debug) app_env.Alias(f"{app_alias}_list", app_elf_dump) - app_elf_augmented = app_env.EmbedAppMetadata( + app_artifacts.compact = app_env.EmbedAppMetadata( os.path.join(ext_apps_work_dir, app.appid), - app_elf_raw, + app_artifacts.debug, APP=app, ) @@ -139,19 +145,21 @@ def legacy_app_build_stub(**kw): } app_env.Depends( - app_elf_augmented, + app_artifacts.compact, [app_env["SDK_DEFINITION"], app_env.Value(manifest_vals)], ) if app.fap_icon: app_env.Depends( - app_elf_augmented, + app_artifacts.compact, app_env.File(f"{app._apppath}/{app.fap_icon}"), ) - app_elf_import_validator = app_env.ValidateAppImports(app_elf_augmented) - app_env.AlwaysBuild(app_elf_import_validator) - app_env.Alias(app_alias, app_elf_import_validator) - return (app_elf_augmented, app_elf_raw, app_elf_import_validator) + app_artifacts.validator = app_env.ValidateAppImports(app_artifacts.compact) + app_env.AlwaysBuild(app_artifacts.validator) + app_env.Alias(app_alias, app_artifacts.validator) + + env["EXT_APPS"][app.appid] = app_artifacts + return app_artifacts def prepare_app_metadata(target, source, env): @@ -182,11 +190,17 @@ def validate_app_imports(target, source, env): app_syms.add(line.split()[0]) unresolved_syms = app_syms - sdk_cache.get_valid_names() if unresolved_syms: - SCons.Warnings.warn( - SCons.Warnings.LinkWarning, - fg.brightyellow(f"{source[0].path}: app won't run. Unresolved symbols: ") - + fg.brightmagenta(f"{unresolved_syms}"), - ) + warning_msg = fg.brightyellow( + f"{source[0].path}: app won't run. Unresolved symbols: " + ) + fg.brightmagenta(f"{unresolved_syms}") + disabled_api_syms = unresolved_syms.intersection(sdk_cache.get_disabled_names()) + if disabled_api_syms: + warning_msg += ( + fg.brightyellow(" (in API, but disabled: ") + + fg.brightmagenta(f"{disabled_api_syms}") + + fg.brightyellow(")") + ) + SCons.Warnings.warn(SCons.Warnings.LinkWarning, warning_msg), def GetExtAppFromPath(env, app_dir): @@ -208,26 +222,26 @@ def GetExtAppFromPath(env, app_dir): if not app: raise UserError(f"Failed to resolve application for given APPSRC={app_dir}") - app_elf = env["_extapps"]["compact"].get(app.appid, None) - if not app_elf: + app_artifacts = env["EXT_APPS"].get(app.appid, None) + if not app_artifacts: raise UserError( f"Application {app.appid} is not configured for building as external" ) - app_validator = env["_extapps"]["validators"].get(app.appid, None) - - return (app, app_elf[0], app_validator[0]) + return app_artifacts def fap_dist_emitter(target, source, env): target_dir = target[0] target = [] - for dist_entry in env["_extapps"]["dist"].values(): - target.append(target_dir.Dir(dist_entry[0]).File(dist_entry[1][0].name)) - - for compact_entry in env["_extapps"]["compact"].values(): - source.extend(compact_entry) + for _, app_artifacts in env["EXT_APPS"].items(): + source.extend(app_artifacts.compact) + target.append( + target_dir.Dir(app_artifacts.app.fap_category).File( + app_artifacts.compact[0].name + ) + ) return (target, source) @@ -244,10 +258,9 @@ def fap_dist_action(target, source, env): def generate(env, **kw): env.SetDefault( - EXT_APPS_WORK_DIR=kw.get("EXT_APPS_WORK_DIR"), + EXT_APPS_WORK_DIR="${FBT_FAP_DEBUG_ELF_ROOT}", APP_RUN_SCRIPT="${FBT_SCRIPT_DIR}/runfap.py", ) - if not env["VERBOSE"]: env.SetDefault( FAPDISTCOMSTR="\tFAPDIST\t${TARGET}", @@ -256,6 +269,10 @@ def generate(env, **kw): APPCHECK_COMSTR="\tAPPCHK\t${SOURCE}", ) + env.SetDefault( + EXT_APPS={}, # appid -> FlipperExternalAppInfo + ) + env.AddMethod(BuildAppElf) env.AddMethod(GetExtAppFromPath) env.Append( diff --git a/scripts/requirements.txt b/scripts/requirements.txt index 35cac774211..5b6fac5f7da 100644 --- a/scripts/requirements.txt +++ b/scripts/requirements.txt @@ -1,7 +1,9 @@ -pyserial==3.5 +ansi==0.3.6 +black==22.6.0 +colorlog==6.7.0 heatshrink2==0.11.0 Pillow==9.1.1 -grpcio==1.47.0 -grpcio-tools==1.47.0 -protobuf==3.20.2 +protobuf==3.20.1 +pyserial==3.5 python3-protobuf==2.5.0 +SCons==4.4.0 diff --git a/site_scons/commandline.scons b/site_scons/commandline.scons index cadb417f858..044de6b3039 100644 --- a/site_scons/commandline.scons +++ b/site_scons/commandline.scons @@ -147,7 +147,7 @@ vars.AddVariables( PathVariable( "SVD_FILE", help="Path to SVD file", - validator=PathVariable.PathIsFile, + validator=PathVariable.PathAccept, default="", ), PathVariable( diff --git a/site_scons/environ.scons b/site_scons/environ.scons index 96424caadca..acdc83e2ad7 100644 --- a/site_scons/environ.scons +++ b/site_scons/environ.scons @@ -61,8 +61,8 @@ coreenv = VAR_ENV.Clone( ABSPATHGETTERFUNC=extract_abs_dir_path, # Setting up temp file parameters - to overcome command line length limits TEMPFILEARGESCFUNC=tempfile_arg_esc_func, - FBT_SCRIPT_DIR=Dir("#/scripts"), ROOT_DIR=Dir("#"), + FBT_SCRIPT_DIR="${ROOT_DIR}/scripts", ) # If DIST_SUFFIX is set in environment, is has precedence (set by CI) diff --git a/site_scons/extapps.scons b/site_scons/extapps.scons index bb1d65ffc52..3d2a98788bf 100644 --- a/site_scons/extapps.scons +++ b/site_scons/extapps.scons @@ -1,4 +1,6 @@ +from dataclasses import dataclass, field from SCons.Errors import UserError +from SCons.Node import NodeList Import("ENV") @@ -7,14 +9,7 @@ from fbt.appmanifest import FlipperAppType appenv = ENV["APPENV"] = ENV.Clone( tools=[ - ( - "fbt_extapps", - { - "EXT_APPS_WORK_DIR": ENV.subst( - "${BUILD_DIR}/.extapps", - ) - }, - ), + "fbt_extapps", "fbt_assets", "fbt_sdk", ] @@ -60,22 +55,11 @@ appenv.AppendUnique( ) -extapps = appenv["_extapps"] = { - "compact": {}, - "debug": {}, - "validators": {}, - "dist": {}, - "resources_dist": None, - "sdk_tree": None, -} - - -def build_app_as_external(env, appdef): - compact_elf, debug_elf, validator = env.BuildAppElf(appdef) - extapps["compact"][appdef.appid] = compact_elf - extapps["debug"][appdef.appid] = debug_elf - extapps["validators"][appdef.appid] = validator - extapps["dist"][appdef.appid] = (appdef.fap_category, compact_elf) +@dataclass +class FlipperExtAppBuildArtifacts: + applications: dict = field(default_factory=dict) + resources_dist: NodeList = field(default_factory=NodeList) + sdk_tree: NodeList = field(default_factory=NodeList) apps_to_build_as_faps = [ @@ -85,38 +69,39 @@ apps_to_build_as_faps = [ if appenv["DEBUG_TOOLS"]: apps_to_build_as_faps.append(FlipperAppType.DEBUG) -for apptype in apps_to_build_as_faps: - for app in appenv["APPBUILD"].get_apps_of_type(apptype, True): - build_app_as_external(appenv, app) +known_extapps = [ + app + for apptype in apps_to_build_as_faps + for app in appenv["APPBUILD"].get_apps_of_type(apptype, True) +] # Ugly access to global option if extra_app_list := GetOption("extra_ext_apps"): - for extra_app in extra_app_list.split(","): - build_app_as_external(appenv, appenv["APPMGR"].get(extra_app)) - - -if appenv["FORCE"]: - appenv.AlwaysBuild(extapps["compact"].values()) - + known_extapps.extend(map(appenv["APPMGR"].get, extra_app_list.split(","))) -# Deprecation stub -def legacy_app_build_stub(**kw): - raise UserError(f"Target name 'firmware_extapps' is deprecated, use 'faps' instead") +for app in known_extapps: + appenv.BuildAppElf(app) -appenv.PhonyTarget("firmware_extapps", appenv.Action(legacy_app_build_stub, None)) +if appenv["FORCE"]: + appenv.AlwaysBuild( + list(app_artifact.compact for app_artifact in appenv["EXT_APPS"].values()) + ) -Alias("faps", extapps["compact"].values()) -Alias("faps", extapps["validators"].values()) +Alias( + "faps", list(app_artifact.validator for app_artifact in appenv["EXT_APPS"].values()) +) -extapps["resources_dist"] = appenv.FapDist(appenv.Dir("#/assets/resources/apps"), []) +extapps = FlipperExtAppBuildArtifacts() +extapps.applications = appenv["EXT_APPS"] +extapps.resources_dist = appenv.FapDist(appenv.Dir("#/assets/resources/apps"), []) if appsrc := appenv.subst("$APPSRC"): app_manifest, fap_file, app_validator = appenv.GetExtAppFromPath(appsrc) appenv.PhonyTarget( "launch_app", - '${PYTHON3} "${APP_RUN_SCRIPT}" ${SOURCE} --fap_dst_dir "/ext/apps/${FAP_CATEGORY}"', + '${PYTHON3} "${APP_RUN_SCRIPT}" "${SOURCE}" --fap_dst_dir "/ext/apps/${FAP_CATEGORY}"', source=fap_file, FAP_CATEGORY=app_manifest.fap_category, ) @@ -131,12 +116,14 @@ sdk_source = appenv.SDKPrebuilder( (appenv["SDK_HEADERS"], appenv["FW_ASSETS_HEADERS"]), ) # Extra deps on headers included in deeper levels +# Available on second and subsequent builds Depends(sdk_source, appenv.ProcessSdkDepends(f"{sdk_origin_path}.d")) appenv["SDK_DIR"] = appenv.Dir("${BUILD_DIR}/sdk") -sdk_tree = extapps["sdk_tree"] = appenv.SDKTree(appenv["SDK_DIR"], sdk_origin_path) +sdk_tree = appenv.SDKTree(appenv["SDK_DIR"], sdk_origin_path) # AlwaysBuild(sdk_tree) Alias("sdk_tree", sdk_tree) +extapps.sdk_tree = sdk_tree sdk_apicheck = appenv.SDKSymUpdater(appenv["SDK_DEFINITION"], sdk_origin_path) Precious(sdk_apicheck) From e8913f2e33d8fbbbee0e3d0ebcf77f1e65bc6ece Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Sun, 6 Nov 2022 00:07:24 +0900 Subject: [PATCH 204/824] Code cleanup: srand, PVS warnings (#1974) * Remove srand invocation * PVS High priority fixes * PVS High errors part 2 * Furi: heap tracing inheritance * Furi add __builtin_unreachable to furi_thread_catch --- applications/main/bad_usb/bad_usb_script.c | 6 +--- applications/main/fap_loader/fap_loader_app.c | 2 +- applications/main/gpio/usb_uart_bridge.c | 4 +-- applications/main/lfrfid/lfrfid.c | 2 +- .../nfc_scene_mf_classic_read_success.c | 2 +- .../nfc_scene_mf_ultralight_read_success.c | 2 +- applications/main/subghz/subghz_i.c | 6 ---- applications/main/u2f/u2f_hid.c | 2 +- applications/plugins/snake_game/snake_game.c | 1 - .../protocols/ambient_weather.c | 1 - applications/services/cli/cli_command_gpio.c | 35 +++++++++++-------- applications/services/cli/cli_vcp.c | 4 +-- applications/services/crypto/crypto_cli.c | 4 +-- applications/services/gui/gui.c | 2 +- .../gui/modules/file_browser_worker.c | 2 +- .../widget_elements/widget_element_button.c | 2 +- applications/services/gui/view_dispatcher.c | 4 +-- applications/services/loader/loader.c | 4 +-- applications/services/storage/storage_cli.c | 4 +-- .../services/storage/storage_external_api.c | 4 +-- firmware/targets/f7/ble_glue/gap.c | 3 +- .../targets/f7/furi_hal/furi_hal_crypto.c | 2 +- furi/core/check.h | 4 +-- furi/core/event_flag.c | 6 ++-- furi/core/memmgr_heap.c | 6 ++-- furi/core/mutex.c | 6 ++-- furi/core/semaphore.c | 6 ++-- furi/core/stream_buffer.c | 4 +-- furi/core/string.c | 4 +-- furi/core/thread.c | 7 ++++ lib/flipper_format/flipper_format_stream.c | 2 +- .../common/infrared_common_decoder.c | 6 ++-- lib/lfrfid/lfrfid_worker.c | 3 +- lib/print/printf_tiny.c | 2 +- lib/subghz/protocols/power_smart.c | 1 - lib/subghz/subghz_keystore.c | 2 +- lib/toolbox/random_name.c | 6 ---- 37 files changed, 77 insertions(+), 86 deletions(-) diff --git a/applications/main/bad_usb/bad_usb_script.c b/applications/main/bad_usb/bad_usb_script.c index 33b3f50303a..ae618114957 100644 --- a/applications/main/bad_usb/bad_usb_script.c +++ b/applications/main/bad_usb/bad_usb_script.c @@ -338,10 +338,6 @@ static int32_t furi_hal_hid_kb_release(key); return (0); } - if(error != NULL) { - strncpy(error, "Unknown error", error_len); - } - return SCRIPT_STATE_ERROR; } static bool ducky_set_usb_id(BadUsbScript* bad_usb, const char* line) { @@ -656,7 +652,7 @@ static int32_t bad_usb_worker(void* context) { BadUsbScript* bad_usb_script_open(FuriString* file_path) { furi_assert(file_path); - BadUsbScript* bad_usb = malloc(sizeof(BadUsbScript)); + BadUsbScript* bad_usb = malloc(sizeof(BadUsbScript)); //-V773 bad_usb->file_path = furi_string_alloc(); furi_string_set(bad_usb->file_path, file_path); diff --git a/applications/main/fap_loader/fap_loader_app.c b/applications/main/fap_loader/fap_loader_app.c index 9b4c9233596..10cec086a60 100644 --- a/applications/main/fap_loader/fap_loader_app.c +++ b/applications/main/fap_loader/fap_loader_app.c @@ -155,7 +155,7 @@ static bool fap_loader_select_app(FapLoader* loader) { } static FapLoader* fap_loader_alloc(const char* path) { - FapLoader* loader = malloc(sizeof(FapLoader)); + FapLoader* loader = malloc(sizeof(FapLoader)); //-V773 loader->fap_path = furi_string_alloc_set(path); loader->storage = furi_record_open(RECORD_STORAGE); loader->dialogs = furi_record_open(RECORD_DIALOGS); diff --git a/applications/main/gpio/usb_uart_bridge.c b/applications/main/gpio/usb_uart_bridge.c index a5caceafa46..a1ab40329df 100644 --- a/applications/main/gpio/usb_uart_bridge.c +++ b/applications/main/gpio/usb_uart_bridge.c @@ -184,7 +184,7 @@ static int32_t usb_uart_worker(void* context) { while(1) { uint32_t events = furi_thread_flags_wait(WORKER_ALL_RX_EVENTS, FuriFlagWaitAny, FuriWaitForever); - furi_check((events & FuriFlagError) == 0); + furi_check(!(events & FuriFlagError)); if(events & WorkerEvtStop) break; if(events & WorkerEvtRxDone) { size_t len = furi_stream_buffer_receive( @@ -288,7 +288,7 @@ static int32_t usb_uart_tx_thread(void* context) { while(1) { uint32_t events = furi_thread_flags_wait(WORKER_ALL_TX_EVENTS, FuriFlagWaitAny, FuriWaitForever); - furi_check((events & FuriFlagError) == 0); + furi_check(!(events & FuriFlagError)); if(events & WorkerEvtTxStop) break; if(events & WorkerEvtCdcRx) { furi_check(furi_mutex_acquire(usb_uart->usb_mutex, FuriWaitForever) == FuriStatusOk); diff --git a/applications/main/lfrfid/lfrfid.c b/applications/main/lfrfid/lfrfid.c index 5132273064f..d391c5e8992 100644 --- a/applications/main/lfrfid/lfrfid.c +++ b/applications/main/lfrfid/lfrfid.c @@ -32,7 +32,7 @@ static void rpc_command_callback(RpcAppSystemEvent rpc_event, void* context) { } static LfRfid* lfrfid_alloc() { - LfRfid* lfrfid = malloc(sizeof(LfRfid)); + LfRfid* lfrfid = malloc(sizeof(LfRfid)); //-V773 lfrfid->storage = furi_record_open(RECORD_STORAGE); lfrfid->dialogs = furi_record_open(RECORD_DIALOGS); diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_read_success.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_read_success.c index ae31e92cccb..444c9a540e7 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_read_success.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_read_success.c @@ -24,7 +24,7 @@ void nfc_scene_mf_classic_read_success_on_enter(void* context) { widget_add_button_element( widget, GuiButtonTypeRight, "More", nfc_scene_mf_classic_read_success_widget_callback, nfc); - FuriString* temp_str; + FuriString* temp_str = NULL; if(furi_string_size(nfc->dev->dev_data.parsed_data)) { temp_str = furi_string_alloc_set(nfc->dev->dev_data.parsed_data); } else { diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_success.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_success.c index 63bffbf3666..cb5ccd6e826 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_success.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_success.c @@ -31,7 +31,7 @@ void nfc_scene_mf_ultralight_read_success_on_enter(void* context) { nfc_scene_mf_ultralight_read_success_widget_callback, nfc); - FuriString* temp_str; + FuriString* temp_str = NULL; if(furi_string_size(nfc->dev->dev_data.parsed_data)) { temp_str = furi_string_alloc_set(nfc->dev->dev_data.parsed_data); } else { diff --git a/applications/main/subghz/subghz_i.c b/applications/main/subghz/subghz_i.c index beefd802433..a887b6543bf 100644 --- a/applications/main/subghz/subghz_i.c +++ b/applications/main/subghz/subghz_i.c @@ -513,12 +513,6 @@ bool subghz_path_is_file(FuriString* path) { } uint32_t subghz_random_serial(void) { - static bool rand_generator_inited = false; - - if(!rand_generator_inited) { - srand(DWT->CYCCNT); - rand_generator_inited = true; - } return (uint32_t)rand(); } diff --git a/applications/main/u2f/u2f_hid.c b/applications/main/u2f/u2f_hid.c index 4922d6a5af7..b6e86384d99 100644 --- a/applications/main/u2f/u2f_hid.c +++ b/applications/main/u2f/u2f_hid.c @@ -203,7 +203,7 @@ static int32_t u2f_hid_worker(void* context) { WorkerEvtStop | WorkerEvtConnect | WorkerEvtDisconnect | WorkerEvtRequest, FuriFlagWaitAny, FuriWaitForever); - furi_check((flags & FuriFlagError) == 0); + furi_check(!(flags & FuriFlagError)); if(flags & WorkerEvtStop) break; if(flags & WorkerEvtConnect) { u2f_set_state(u2f_hid->u2f_instance, 1); diff --git a/applications/plugins/snake_game/snake_game.c b/applications/plugins/snake_game/snake_game.c index f9309c28070..ef4ae2ee854 100644 --- a/applications/plugins/snake_game/snake_game.c +++ b/applications/plugins/snake_game/snake_game.c @@ -318,7 +318,6 @@ static void int32_t snake_game_app(void* p) { UNUSED(p); - srand(DWT->CYCCNT); FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(SnakeEvent)); diff --git a/applications/plugins/weather_station/protocols/ambient_weather.c b/applications/plugins/weather_station/protocols/ambient_weather.c index 07f5330fcae..5fede684ea6 100644 --- a/applications/plugins/weather_station/protocols/ambient_weather.c +++ b/applications/plugins/weather_station/protocols/ambient_weather.c @@ -200,7 +200,6 @@ void ws_protocol_decoder_ambient_weather_feed(void* context, bool level, uint32_ ((instance->decoder.decode_data & AMBIENT_WEATHER_PACKET_HEADER_MASK) == AMBIENT_WEATHER_PACKET_HEADER_2)) { if(ws_protocol_ambient_weather_check_crc(instance)) { - instance->decoder.decode_data = instance->decoder.decode_data; instance->generic.data = instance->decoder.decode_data; instance->generic.data_count_bit = ws_protocol_ambient_weather_const.min_count_bit_for_found; diff --git a/applications/services/cli/cli_command_gpio.c b/applications/services/cli/cli_command_gpio.c index d072ce00c98..3e7301cdc49 100644 --- a/applications/services/cli/cli_command_gpio.c +++ b/applications/services/cli/cli_command_gpio.c @@ -61,15 +61,20 @@ static void gpio_print_pins(void) { } } -typedef enum { OK, ERR_CMD_SYNTAX, ERR_PIN, ERR_VALUE } GpioParseError; - -static GpioParseError gpio_command_parse(FuriString* args, size_t* pin_num, uint8_t* value) { +typedef enum { + GpioParseReturnOk, + GpioParseReturnCmdSyntaxError, + GpioParseReturnPinError, + GpioParseReturnValueError +} GpioParseReturn; + +static GpioParseReturn gpio_command_parse(FuriString* args, size_t* pin_num, uint8_t* value) { FuriString* pin_name; pin_name = furi_string_alloc(); size_t ws = furi_string_search_char(args, ' '); if(ws == FURI_STRING_FAILURE) { - return ERR_CMD_SYNTAX; + return GpioParseReturnCmdSyntaxError; } furi_string_set_n(pin_name, args, 0, ws); @@ -78,7 +83,7 @@ static GpioParseError gpio_command_parse(FuriString* args, size_t* pin_num, uint if(!pin_name_to_int(pin_name, pin_num)) { furi_string_free(pin_name); - return ERR_PIN; + return GpioParseReturnPinError; } furi_string_free(pin_name); @@ -88,10 +93,10 @@ static GpioParseError gpio_command_parse(FuriString* args, size_t* pin_num, uint } else if(!furi_string_cmp(args, "1")) { *value = 1; } else { - return ERR_VALUE; + return GpioParseReturnValueError; } - return OK; + return GpioParseReturnOk; } void cli_command_gpio_mode(Cli* cli, FuriString* args, void* context) { @@ -101,15 +106,15 @@ void cli_command_gpio_mode(Cli* cli, FuriString* args, void* context) { size_t num = 0; uint8_t value = 255; - GpioParseError err = gpio_command_parse(args, &num, &value); + GpioParseReturn err = gpio_command_parse(args, &num, &value); - if(ERR_CMD_SYNTAX == err) { + if(err == GpioParseReturnCmdSyntaxError) { cli_print_usage("gpio mode", " <0|1>", furi_string_get_cstr(args)); return; - } else if(ERR_PIN == err) { + } else if(err == GpioParseReturnPinError) { gpio_print_pins(); return; - } else if(ERR_VALUE == err) { + } else if(err == GpioParseReturnValueError) { printf("Value is invalid. Enter 1 for input or 0 for output"); return; } @@ -161,15 +166,15 @@ void cli_command_gpio_set(Cli* cli, FuriString* args, void* context) { size_t num = 0; uint8_t value = 0; - GpioParseError err = gpio_command_parse(args, &num, &value); + GpioParseReturn err = gpio_command_parse(args, &num, &value); - if(ERR_CMD_SYNTAX == err) { + if(err == GpioParseReturnCmdSyntaxError) { cli_print_usage("gpio set", " <0|1>", furi_string_get_cstr(args)); return; - } else if(ERR_PIN == err) { + } else if(err == GpioParseReturnPinError) { gpio_print_pins(); return; - } else if(ERR_VALUE == err) { + } else if(err == GpioParseReturnValueError) { printf("Value is invalid. Enter 1 for high or 0 for low"); return; } diff --git a/applications/services/cli/cli_vcp.c b/applications/services/cli/cli_vcp.c index 1e27e185b65..94b82950d9c 100644 --- a/applications/services/cli/cli_vcp.c +++ b/applications/services/cli/cli_vcp.c @@ -103,7 +103,7 @@ static int32_t vcp_worker(void* context) { while(1) { uint32_t flags = furi_thread_flags_wait(VCP_THREAD_FLAG_ALL, FuriFlagWaitAny, FuriWaitForever); - furi_assert((flags & FuriFlagError) == 0); + furi_assert(!(flags & FuriFlagError)); // VCP session opened if(flags & VcpEvtConnect) { @@ -303,7 +303,7 @@ static void vcp_on_cdc_control_line(void* context, uint8_t state) { static void vcp_on_cdc_rx(void* context) { UNUSED(context); uint32_t ret = furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtRx); - furi_check((ret & FuriFlagError) == 0); + furi_check(!(ret & FuriFlagError)); } static void vcp_on_cdc_tx_complete(void* context) { diff --git a/applications/services/crypto/crypto_cli.c b/applications/services/crypto/crypto_cli.c index a64a3ad0b04..1b26ba9fb33 100644 --- a/applications/services/crypto/crypto_cli.c +++ b/applications/services/crypto/crypto_cli.c @@ -167,7 +167,7 @@ void crypto_cli_decrypt(Cli* cli, FuriString* args) { void crypto_cli_has_key(Cli* cli, FuriString* args) { UNUSED(cli); int key_slot = 0; - uint8_t iv[16]; + uint8_t iv[16] = {0}; do { if(!args_read_int_and_trim(args, &key_slot) || !(key_slot > 0 && key_slot <= 100)) { @@ -249,7 +249,7 @@ void crypto_cli_store_key(Cli* cli, FuriString* args) { } if(key_slot > 0) { - uint8_t iv[16]; + uint8_t iv[16] = {0}; if(key_slot > 1) { if(!furi_hal_crypto_store_load_key(key_slot - 1, iv)) { printf( diff --git a/applications/services/gui/gui.c b/applications/services/gui/gui.c index 2d06d70c7c9..9f6ebcd76f2 100644 --- a/applications/services/gui/gui.c +++ b/applications/services/gui/gui.c @@ -436,7 +436,7 @@ void gui_add_framebuffer_callback(Gui* gui, GuiCanvasCommitCallback callback, vo const CanvasCallbackPair p = {callback, context}; gui_lock(gui); - furi_assert(CanvasCallbackPairArray_count(gui->canvas_callback_pair, p) == 0); + furi_assert(!CanvasCallbackPairArray_count(gui->canvas_callback_pair, p)); CanvasCallbackPairArray_push_back(gui->canvas_callback_pair, p); gui_unlock(gui); diff --git a/applications/services/gui/modules/file_browser_worker.c b/applications/services/gui/modules/file_browser_worker.c index fdaf8273fb4..b9b2b2d8fe3 100644 --- a/applications/services/gui/modules/file_browser_worker.c +++ b/applications/services/gui/modules/file_browser_worker.c @@ -359,7 +359,7 @@ static int32_t browser_worker(void* context) { BrowserWorker* file_browser_worker_alloc(FuriString* path, const char* filter_ext, bool skip_assets) { - BrowserWorker* browser = malloc(sizeof(BrowserWorker)); + BrowserWorker* browser = malloc(sizeof(BrowserWorker)); //-V773 idx_last_array_init(browser->idx_last); diff --git a/applications/services/gui/modules/widget_elements/widget_element_button.c b/applications/services/gui/modules/widget_elements/widget_element_button.c index be33b1897ea..e3267058e48 100644 --- a/applications/services/gui/modules/widget_elements/widget_element_button.c +++ b/applications/services/gui/modules/widget_elements/widget_element_button.c @@ -60,7 +60,7 @@ WidgetElement* widget_element_button_create( ButtonCallback callback, void* context) { // Allocate and init model - GuiButtonModel* model = malloc(sizeof(GuiButtonModel)); + GuiButtonModel* model = malloc(sizeof(GuiButtonModel)); //-V773 model->button_type = button_type; model->callback = callback; model->context = context; diff --git a/applications/services/gui/view_dispatcher.c b/applications/services/gui/view_dispatcher.c index 8de452d205d..4034cc0b40b 100644 --- a/applications/services/gui/view_dispatcher.c +++ b/applications/services/gui/view_dispatcher.c @@ -23,7 +23,7 @@ void view_dispatcher_free(ViewDispatcher* view_dispatcher) { gui_remove_view_port(view_dispatcher->gui, view_dispatcher->view_port); } // Crash if not all views were freed - furi_assert(ViewDict_size(view_dispatcher->views) == 0); + furi_assert(!ViewDict_size(view_dispatcher->views)); ViewDict_clear(view_dispatcher->views); // Free ViewPort @@ -157,7 +157,7 @@ void view_dispatcher_remove_view(ViewDispatcher* view_dispatcher, uint32_t view_ view_dispatcher->ongoing_input_view = NULL; } // Remove view - ViewDict_erase(view_dispatcher->views, view_id); + furi_check(ViewDict_erase(view_dispatcher->views, view_id)); view_set_update_callback(view, NULL); view_set_update_callback_context(view, NULL); diff --git a/applications/services/loader/loader.c b/applications/services/loader/loader.c index bc456536c9c..62dbad95fc1 100644 --- a/applications/services/loader/loader.c +++ b/applications/services/loader/loader.c @@ -269,7 +269,7 @@ static void loader_thread_state_callback(FuriThreadState thread_state, void* con event.type = LoaderEventTypeApplicationStarted; furi_pubsub_publish(loader_instance->pubsub, &event); - if(!loader_instance->application->flags & FlipperApplicationFlagInsomniaSafe) { + if(!(loader_instance->application->flags & FlipperApplicationFlagInsomniaSafe)) { furi_hal_power_insomnia_enter(); } } else if(thread_state == FuriThreadStateStopped) { @@ -284,7 +284,7 @@ static void loader_thread_state_callback(FuriThreadState thread_state, void* con loader_instance->application_arguments = NULL; } - if(!loader_instance->application->flags & FlipperApplicationFlagInsomniaSafe) { + if(!(loader_instance->application->flags & FlipperApplicationFlagInsomniaSafe)) { furi_hal_power_insomnia_exit(); } loader_unlock(instance); diff --git a/applications/services/storage/storage_cli.c b/applications/services/storage/storage_cli.c index 0efcb5e2c2b..c83f164997a 100644 --- a/applications/services/storage/storage_cli.c +++ b/applications/services/storage/storage_cli.c @@ -275,7 +275,7 @@ static void storage_cli_read_chunks(Cli* cli, FuriString* path, FuriString* args uint32_t buffer_size; int parsed_count = sscanf(furi_string_get_cstr(args), "%lu", &buffer_size); - if(parsed_count == EOF || parsed_count != 1) { + if(parsed_count != 1) { storage_cli_print_usage(); } else if(storage_file_open(file, furi_string_get_cstr(path), FSAM_READ, FSOM_OPEN_EXISTING)) { uint64_t file_size = storage_file_size(file); @@ -315,7 +315,7 @@ static void storage_cli_write_chunk(Cli* cli, FuriString* path, FuriString* args uint32_t buffer_size; int parsed_count = sscanf(furi_string_get_cstr(args), "%lu", &buffer_size); - if(parsed_count == EOF || parsed_count != 1) { + if(parsed_count != 1) { storage_cli_print_usage(); } else { if(storage_file_open(file, furi_string_get_cstr(path), FSAM_WRITE, FSOM_OPEN_APPEND)) { diff --git a/applications/services/storage/storage_external_api.c b/applications/services/storage/storage_external_api.c index 6854ef7f32c..2c3a7bfc9bc 100644 --- a/applications/services/storage/storage_external_api.c +++ b/applications/services/storage/storage_external_api.c @@ -545,8 +545,8 @@ static FS_Error FS_Error storage_common_merge(Storage* storage, const char* old_path, const char* new_path) { FS_Error error; - const char* new_path_tmp; - FuriString* new_path_next; + const char* new_path_tmp = NULL; + FuriString* new_path_next = NULL; new_path_next = furi_string_alloc(); FileInfo fileinfo; diff --git a/firmware/targets/f7/ble_glue/gap.c b/firmware/targets/f7/ble_glue/gap.c index aa8cd2c90f1..cf27df3a370 100644 --- a/firmware/targets/f7/ble_glue/gap.c +++ b/firmware/targets/f7/ble_glue/gap.c @@ -179,7 +179,7 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification(void* pckt) { case EVT_BLUE_GAP_PASS_KEY_REQUEST: { // Generate random PIN code - uint32_t pin = rand() % 999999; + uint32_t pin = rand() % 999999; //-V1064 aci_gap_pass_key_resp(gap->service.connection_handle, pin); if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagLock)) { FURI_LOG_I(TAG, "Pass key request event. Pin: ******"); @@ -478,7 +478,6 @@ bool gap_init(GapConfig* config, GapEventCallback on_event_cb, void* context) { gap = malloc(sizeof(Gap)); gap->config = config; - srand(DWT->CYCCNT); // Create advertising timer gap->advertise_timer = furi_timer_alloc(gap_advetise_timer_callback, FuriTimerTypeOnce, NULL); // Initialization of GATT & GAP layer diff --git a/firmware/targets/f7/furi_hal/furi_hal_crypto.c b/firmware/targets/f7/furi_hal/furi_hal_crypto.c index dbd8c58c2a0..e0ed3ab9be2 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_crypto.c +++ b/firmware/targets/f7/furi_hal/furi_hal_crypto.c @@ -91,7 +91,7 @@ bool furi_hal_crypto_verify_key(uint8_t key_slot) { uint8_t keys_nb = 0; uint8_t valid_keys_nb = 0; uint8_t last_valid_slot = ENCLAVE_FACTORY_KEY_SLOTS; - uint8_t empty_iv[16]; + uint8_t empty_iv[16] = {0}; furi_hal_crypto_verify_enclave(&keys_nb, &valid_keys_nb); if(key_slot <= ENCLAVE_FACTORY_KEY_SLOTS) { // It's a factory key if(key_slot > keys_nb) return false; diff --git a/furi/core/check.h b/furi/core/check.h index 78efc145190..192c5260e8e 100644 --- a/furi/core/check.h +++ b/furi/core/check.h @@ -46,7 +46,7 @@ FURI_NORETURN void __furi_halt(); /** Check condition and crash if check failed */ #define furi_check(__e) \ do { \ - if((__e) == 0) { \ + if(!(__e)) { \ furi_crash("furi_check failed\r\n"); \ } \ } while(0) @@ -55,7 +55,7 @@ FURI_NORETURN void __furi_halt(); #ifdef FURI_DEBUG #define furi_assert(__e) \ do { \ - if((__e) == 0) { \ + if(!(__e)) { \ furi_crash("furi_assert failed\r\n"); \ } \ } while(0) diff --git a/furi/core/event_flag.c b/furi/core/event_flag.c index 5d2a49910d4..07dd30a1693 100644 --- a/furi/core/event_flag.c +++ b/furi/core/event_flag.c @@ -25,7 +25,7 @@ uint32_t furi_event_flag_set(FuriEventFlag* instance, uint32_t flags) { uint32_t rflags; BaseType_t yield; - if(FURI_IS_IRQ_MODE() != 0U) { + if(FURI_IS_IRQ_MODE()) { yield = pdFALSE; if(xEventGroupSetBitsFromISR(hEventGroup, (EventBits_t)flags, &yield) == pdFAIL) { rflags = (uint32_t)FuriStatusErrorResource; @@ -48,7 +48,7 @@ uint32_t furi_event_flag_clear(FuriEventFlag* instance, uint32_t flags) { EventGroupHandle_t hEventGroup = (EventGroupHandle_t)instance; uint32_t rflags; - if(FURI_IS_IRQ_MODE() != 0U) { + if(FURI_IS_IRQ_MODE()) { rflags = xEventGroupGetBitsFromISR(hEventGroup); if(xEventGroupClearBitsFromISR(hEventGroup, (EventBits_t)flags) == pdFAIL) { @@ -73,7 +73,7 @@ uint32_t furi_event_flag_get(FuriEventFlag* instance) { EventGroupHandle_t hEventGroup = (EventGroupHandle_t)instance; uint32_t rflags; - if(FURI_IS_IRQ_MODE() != 0U) { + if(FURI_IS_IRQ_MODE()) { rflags = xEventGroupGetBitsFromISR(hEventGroup); } else { rflags = xEventGroupGetBits(hEventGroup); diff --git a/furi/core/memmgr_heap.c b/furi/core/memmgr_heap.c index 32b2875cc40..ac51b4a204b 100644 --- a/furi/core/memmgr_heap.c +++ b/furi/core/memmgr_heap.c @@ -150,8 +150,7 @@ void memmgr_heap_disable_thread_trace(FuriThreadId thread_id) { vTaskSuspendAll(); { memmgr_heap_thread_trace_depth++; - furi_check(MemmgrHeapThreadDict_get(memmgr_heap_thread_dict, (uint32_t)thread_id) != NULL); - MemmgrHeapThreadDict_erase(memmgr_heap_thread_dict, (uint32_t)thread_id); + furi_check(MemmgrHeapThreadDict_erase(memmgr_heap_thread_dict, (uint32_t)thread_id)); memmgr_heap_thread_trace_depth--; } (void)xTaskResumeAll(); @@ -212,7 +211,8 @@ static inline void traceFREE(void* pointer, size_t size) { MemmgrHeapAllocDict_t* alloc_dict = MemmgrHeapThreadDict_get(memmgr_heap_thread_dict, (uint32_t)thread_id); if(alloc_dict) { - MemmgrHeapAllocDict_erase(*alloc_dict, (uint32_t)pointer); + // In some cases thread may want to release memory that was not allocated by it + (void)MemmgrHeapAllocDict_erase(*alloc_dict, (uint32_t)pointer); } memmgr_heap_thread_trace_depth--; } diff --git a/furi/core/mutex.c b/furi/core/mutex.c index 78ea0519692..ab66b0f18af 100644 --- a/furi/core/mutex.c +++ b/furi/core/mutex.c @@ -45,7 +45,7 @@ FuriStatus furi_mutex_acquire(FuriMutex* instance, uint32_t timeout) { stat = FuriStatusOk; - if(FURI_IS_IRQ_MODE() != 0U) { + if(FURI_IS_IRQ_MODE()) { stat = FuriStatusErrorISR; } else if(hMutex == NULL) { stat = FuriStatusErrorParameter; @@ -85,7 +85,7 @@ FuriStatus furi_mutex_release(FuriMutex* instance) { stat = FuriStatusOk; - if(FURI_IS_IRQ_MODE() != 0U) { + if(FURI_IS_IRQ_MODE()) { stat = FuriStatusErrorISR; } else if(hMutex == NULL) { stat = FuriStatusErrorParameter; @@ -111,7 +111,7 @@ FuriThreadId furi_mutex_get_owner(FuriMutex* instance) { hMutex = (SemaphoreHandle_t)((uint32_t)instance & ~1U); - if((FURI_IS_IRQ_MODE() != 0U) || (hMutex == NULL)) { + if((FURI_IS_IRQ_MODE()) || (hMutex == NULL)) { owner = 0; } else { owner = (FuriThreadId)xSemaphoreGetMutexHolder(hMutex); diff --git a/furi/core/semaphore.c b/furi/core/semaphore.c index a204cbe6eab..8c99bfc541f 100644 --- a/furi/core/semaphore.c +++ b/furi/core/semaphore.c @@ -45,7 +45,7 @@ FuriStatus furi_semaphore_acquire(FuriSemaphore* instance, uint32_t timeout) { stat = FuriStatusOk; - if(FURI_IS_IRQ_MODE() != 0U) { + if(FURI_IS_IRQ_MODE()) { if(timeout != 0U) { stat = FuriStatusErrorParameter; } else { @@ -80,7 +80,7 @@ FuriStatus furi_semaphore_release(FuriSemaphore* instance) { stat = FuriStatusOk; - if(FURI_IS_IRQ_MODE() != 0U) { + if(FURI_IS_IRQ_MODE()) { yield = pdFALSE; if(xSemaphoreGiveFromISR(hSemaphore, &yield) != pdTRUE) { @@ -104,7 +104,7 @@ uint32_t furi_semaphore_get_count(FuriSemaphore* instance) { SemaphoreHandle_t hSemaphore = (SemaphoreHandle_t)instance; uint32_t count; - if(FURI_IS_IRQ_MODE() != 0U) { + if(FURI_IS_IRQ_MODE()) { count = (uint32_t)uxSemaphoreGetCountFromISR(hSemaphore); } else { count = (uint32_t)uxSemaphoreGetCount(hSemaphore); diff --git a/furi/core/stream_buffer.c b/furi/core/stream_buffer.c index b9d0629fe03..2df84fa5bb3 100644 --- a/furi/core/stream_buffer.c +++ b/furi/core/stream_buffer.c @@ -23,7 +23,7 @@ size_t furi_stream_buffer_send( uint32_t timeout) { size_t ret; - if(FURI_IS_IRQ_MODE() != 0U) { + if(FURI_IS_IRQ_MODE()) { BaseType_t yield; ret = xStreamBufferSendFromISR(stream_buffer, data, length, &yield); portYIELD_FROM_ISR(yield); @@ -41,7 +41,7 @@ size_t furi_stream_buffer_receive( uint32_t timeout) { size_t ret; - if(FURI_IS_IRQ_MODE() != 0U) { + if(FURI_IS_IRQ_MODE()) { BaseType_t yield; ret = xStreamBufferReceiveFromISR(stream_buffer, data, length, &yield); portYIELD_FROM_ISR(yield); diff --git a/furi/core/string.c b/furi/core/string.c index 099f70c11fc..901b1f625d4 100644 --- a/furi/core/string.c +++ b/furi/core/string.c @@ -29,13 +29,13 @@ FuriString* furi_string_alloc() { } FuriString* furi_string_alloc_set(const FuriString* s) { - FuriString* string = malloc(sizeof(FuriString)); + FuriString* string = malloc(sizeof(FuriString)); //-V773 string_init_set(string->string, s->string); return string; } FuriString* furi_string_alloc_set_str(const char cstr[]) { - FuriString* string = malloc(sizeof(FuriString)); + FuriString* string = malloc(sizeof(FuriString)); //-V773 string_init_set(string->string, cstr); return string; } diff --git a/furi/core/thread.c b/furi/core/thread.c index 508146f6312..157e022e974 100644 --- a/furi/core/thread.c +++ b/furi/core/thread.c @@ -50,6 +50,7 @@ static int32_t __furi_thread_stdout_flush(FuriThread* thread); __attribute__((__noreturn__)) void furi_thread_catch() { asm volatile("nop"); // extra magic furi_crash("You are doing it wrong"); + __builtin_unreachable(); } static void furi_thread_set_state(FuriThread* thread, FuriThreadState state) { @@ -112,6 +113,12 @@ FuriThread* furi_thread_alloc() { FuriThread* thread = malloc(sizeof(FuriThread)); thread->output.buffer = furi_string_alloc(); thread->is_service = false; + + if(furi_thread_get_current_id()) { + FuriThread* parent = pvTaskGetThreadLocalStoragePointer(NULL, 0); + if(parent) thread->heap_trace_enabled = parent->heap_trace_enabled; + } + return thread; } diff --git a/lib/flipper_format/flipper_format_stream.c b/lib/flipper_format/flipper_format_stream.c index 41934a3b157..9cce95d47e1 100644 --- a/lib/flipper_format/flipper_format_stream.c +++ b/lib/flipper_format/flipper_format_stream.c @@ -313,7 +313,7 @@ bool flipper_format_stream_write_value_line(Stream* stream, FlipperStreamWriteDa furi_crash("Unknown FF type"); } - if((size_t)(i + 1) < write_data->data_size) { + if(((size_t)i + 1) < write_data->data_size) { furi_string_cat(value, " "); } diff --git a/lib/infrared/encoder_decoder/common/infrared_common_decoder.c b/lib/infrared/encoder_decoder/common/infrared_common_decoder.c index bff4c73db75..7f1c3a4fda9 100644 --- a/lib/infrared/encoder_decoder/common/infrared_common_decoder.c +++ b/lib/infrared/encoder_decoder/common/infrared_common_decoder.c @@ -85,8 +85,8 @@ static InfraredStatus infrared_common_decode_bits(InfraredCommonDecoder* decoder if(timings->min_split_time && !level) { if(timing > timings->min_split_time) { /* long low timing - check if we're ready for any of protocol modification */ - for(size_t i = 0; decoder->protocol->databit_len[i] && - (i < COUNT_OF(decoder->protocol->databit_len)); + for(size_t i = 0; i < COUNT_OF(decoder->protocol->databit_len) && + decoder->protocol->databit_len[i]; ++i) { if(decoder->protocol->databit_len[i] == decoder->databit_cnt) { return InfraredStatusReady; @@ -199,7 +199,7 @@ InfraredMessage* infrared_common_decoder_check_ready(InfraredCommonDecoder* deco bool found_length = false; for(size_t i = 0; - decoder->protocol->databit_len[i] && (i < COUNT_OF(decoder->protocol->databit_len)); + i < COUNT_OF(decoder->protocol->databit_len) && decoder->protocol->databit_len[i]; ++i) { if(decoder->protocol->databit_len[i] == decoder->databit_cnt) { found_length = true; diff --git a/lib/lfrfid/lfrfid_worker.c b/lib/lfrfid/lfrfid_worker.c index 8b4f8b6a985..f3e37fa3c9a 100644 --- a/lib/lfrfid/lfrfid_worker.c +++ b/lib/lfrfid/lfrfid_worker.c @@ -140,9 +140,8 @@ size_t lfrfid_worker_dict_get_data_size(LFRFIDWorker* worker, LFRFIDProtocol pro static int32_t lfrfid_worker_thread(void* thread_context) { LFRFIDWorker* worker = thread_context; - bool running = true; - while(running) { + while(true) { uint32_t flags = furi_thread_flags_wait(LFRFIDEventAll, FuriFlagWaitAny, FuriWaitForever); if(flags != FuriFlagErrorTimeout) { // stop thread diff --git a/lib/print/printf_tiny.c b/lib/print/printf_tiny.c index 0db11922d71..6e47f65289a 100644 --- a/lib/print/printf_tiny.c +++ b/lib/print/printf_tiny.c @@ -541,7 +541,7 @@ static size_t _etoa( exp2 = (int)(expval * 3.321928094887362 + 0.5); const double z = expval * 2.302585092994046 - exp2 * 0.6931471805599453; const double z2 = z * z; - conv.U = (uint64_t)(exp2 + 1023) << 52U; + conv.U = ((uint64_t)exp2 + 1023) << 52U; // compute exp(z) using continued fractions, see https://en.wikipedia.org/wiki/Exponential_function#Continued_fractions_for_ex conv.F *= 1 + 2 * z / (2 - z + (z2 / (6 + (z2 / (10 + z2 / 14))))); // correct for rounding errors diff --git a/lib/subghz/protocols/power_smart.c b/lib/subghz/protocols/power_smart.c index 63ca78711e1..1e8d10e952e 100644 --- a/lib/subghz/protocols/power_smart.c +++ b/lib/subghz/protocols/power_smart.c @@ -312,7 +312,6 @@ void subghz_protocol_decoder_power_smart_feed( if((instance->decoder.decode_data & POWER_SMART_PACKET_HEADER_MASK) == POWER_SMART_PACKET_HEADER) { if(subghz_protocol_power_smart_chek_valid(instance->decoder.decode_data)) { - instance->decoder.decode_data = instance->decoder.decode_data; instance->generic.data = instance->decoder.decode_data; instance->generic.data_count_bit = subghz_protocol_power_smart_const.min_count_bit_for_found; diff --git a/lib/subghz/subghz_keystore.c b/lib/subghz/subghz_keystore.c index 1b4b3b716be..e06bd9796df 100644 --- a/lib/subghz/subghz_keystore.c +++ b/lib/subghz/subghz_keystore.c @@ -464,7 +464,7 @@ bool subghz_keystore_raw_encrypted_save( } stream_write_cstring(output_stream, encrypted_line); - } while(ret > 0 && result); + } while(result); flipper_format_free(output_flipper_format); diff --git a/lib/toolbox/random_name.c b/lib/toolbox/random_name.c index 5a53743983f..64e712c7ca0 100644 --- a/lib/toolbox/random_name.c +++ b/lib/toolbox/random_name.c @@ -5,12 +5,6 @@ #include void set_random_name(char* name, uint8_t max_name_size) { - static bool rand_generator_inited = false; - - if(!rand_generator_inited) { - srand(DWT->CYCCNT); - rand_generator_inited = true; - } const char* prefix[] = { "ancient", "hollow", "strange", "disappeared", "unknown", "unthinkable", "unnamable", "nameless", "my", "concealed", From 0a86ad43ca2ccf00f3f71e288bb7552b48e14dce Mon Sep 17 00:00:00 2001 From: hedger Date: Sun, 6 Nov 2022 11:39:50 +0400 Subject: [PATCH 205/824] fbt: fix for launch_app (#1978) --- site_scons/extapps.scons | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/site_scons/extapps.scons b/site_scons/extapps.scons index 3d2a98788bf..670d71fd0e9 100644 --- a/site_scons/extapps.scons +++ b/site_scons/extapps.scons @@ -98,14 +98,14 @@ extapps.applications = appenv["EXT_APPS"] extapps.resources_dist = appenv.FapDist(appenv.Dir("#/assets/resources/apps"), []) if appsrc := appenv.subst("$APPSRC"): - app_manifest, fap_file, app_validator = appenv.GetExtAppFromPath(appsrc) + app_artifacts = appenv.GetExtAppFromPath(appsrc) appenv.PhonyTarget( "launch_app", '${PYTHON3} "${APP_RUN_SCRIPT}" "${SOURCE}" --fap_dst_dir "/ext/apps/${FAP_CATEGORY}"', - source=fap_file, - FAP_CATEGORY=app_manifest.fap_category, + source=app_artifacts.compact, + FAP_CATEGORY=app_artifacts.app.fap_category, ) - appenv.Alias("launch_app", app_validator) + appenv.Alias("launch_app", app_artifacts.validator) # SDK management From 65005e71d2524e2c82d9bb6631f655b3c442d2e5 Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Sun, 6 Nov 2022 21:30:02 +0400 Subject: [PATCH 206/824] WS: fix show negative temperature (#1980) --- .../plugins/weather_station/protocols/acurite_592txr.c | 5 ++--- .../plugins/weather_station/protocols/acurite_606tx.c | 5 ++--- .../plugins/weather_station/protocols/ambient_weather.c | 5 ++--- applications/plugins/weather_station/protocols/gt_wt_03.c | 5 ++--- applications/plugins/weather_station/protocols/infactory.c | 5 ++--- .../weather_station/protocols/lacrosse_tx141thbv2.c | 5 ++--- applications/plugins/weather_station/protocols/nexus_th.c | 5 ++--- .../plugins/weather_station/protocols/thermopro_tx4.c | 5 ++--- .../weather_station/views/weather_station_receiver_info.c | 7 +------ 9 files changed, 17 insertions(+), 30 deletions(-) diff --git a/applications/plugins/weather_station/protocols/acurite_592txr.c b/applications/plugins/weather_station/protocols/acurite_592txr.c index 4d7f5954496..5384a3c9190 100644 --- a/applications/plugins/weather_station/protocols/acurite_592txr.c +++ b/applications/plugins/weather_station/protocols/acurite_592txr.c @@ -293,7 +293,7 @@ void ws_protocol_decoder_acurite_592txr_get_string(void* context, FuriString* ou "%s %dbit\r\n" "Key:0x%lX%08lX\r\n" "Sn:0x%lX Ch:%d Bat:%d\r\n" - "Temp:%d.%d C Hum:%d%%", + "Temp:%3.1f C Hum:%d%%", instance->generic.protocol_name, instance->generic.data_count_bit, (uint32_t)(instance->generic.data >> 32), @@ -301,7 +301,6 @@ void ws_protocol_decoder_acurite_592txr_get_string(void* context, FuriString* ou instance->generic.id, instance->generic.channel, instance->generic.battery_low, - (int16_t)instance->generic.temp, - abs(((int16_t)(instance->generic.temp * 10) - (((int16_t)instance->generic.temp) * 10))), + (double)instance->generic.temp, instance->generic.humidity); } diff --git a/applications/plugins/weather_station/protocols/acurite_606tx.c b/applications/plugins/weather_station/protocols/acurite_606tx.c index 3c914405751..4cb5d18b8f9 100644 --- a/applications/plugins/weather_station/protocols/acurite_606tx.c +++ b/applications/plugins/weather_station/protocols/acurite_606tx.c @@ -234,7 +234,7 @@ void ws_protocol_decoder_acurite_606tx_get_string(void* context, FuriString* out "%s %dbit\r\n" "Key:0x%lX%08lX\r\n" "Sn:0x%lX Ch:%d Bat:%d\r\n" - "Temp:%d.%d C Hum:%d%%", + "Temp:%3.1f C Hum:%d%%", instance->generic.protocol_name, instance->generic.data_count_bit, (uint32_t)(instance->generic.data >> 32), @@ -242,7 +242,6 @@ void ws_protocol_decoder_acurite_606tx_get_string(void* context, FuriString* out instance->generic.id, instance->generic.channel, instance->generic.battery_low, - (int16_t)instance->generic.temp, - abs(((int16_t)(instance->generic.temp * 10) - (((int16_t)instance->generic.temp) * 10))), + (double)instance->generic.temp, instance->generic.humidity); } diff --git a/applications/plugins/weather_station/protocols/ambient_weather.c b/applications/plugins/weather_station/protocols/ambient_weather.c index 5fede684ea6..5ae22b790b2 100644 --- a/applications/plugins/weather_station/protocols/ambient_weather.c +++ b/applications/plugins/weather_station/protocols/ambient_weather.c @@ -263,7 +263,7 @@ void ws_protocol_decoder_ambient_weather_get_string(void* context, FuriString* o "%s %dbit\r\n" "Key:0x%lX%08lX\r\n" "Sn:0x%lX Ch:%d Bat:%d\r\n" - "Temp:%d.%d C Hum:%d%%", + "Temp:%3.1f C Hum:%d%%", instance->generic.protocol_name, instance->generic.data_count_bit, (uint32_t)(instance->generic.data >> 32), @@ -271,7 +271,6 @@ void ws_protocol_decoder_ambient_weather_get_string(void* context, FuriString* o instance->generic.id, instance->generic.channel, instance->generic.battery_low, - (int16_t)instance->generic.temp, - abs(((int16_t)(instance->generic.temp * 10) - (((int16_t)instance->generic.temp) * 10))), + (double)instance->generic.temp, instance->generic.humidity); } diff --git a/applications/plugins/weather_station/protocols/gt_wt_03.c b/applications/plugins/weather_station/protocols/gt_wt_03.c index 04bca9ac17e..7831cf06950 100644 --- a/applications/plugins/weather_station/protocols/gt_wt_03.c +++ b/applications/plugins/weather_station/protocols/gt_wt_03.c @@ -327,7 +327,7 @@ void ws_protocol_decoder_gt_wt_03_get_string(void* context, FuriString* output) "%s %dbit\r\n" "Key:0x%lX%08lX\r\n" "Sn:0x%lX Ch:%d Bat:%d\r\n" - "Temp:%d.%d C Hum:%d%%", + "Temp:%3.1f C Hum:%d%%", instance->generic.protocol_name, instance->generic.data_count_bit, (uint32_t)(instance->generic.data >> 32), @@ -335,7 +335,6 @@ void ws_protocol_decoder_gt_wt_03_get_string(void* context, FuriString* output) instance->generic.id, instance->generic.channel, instance->generic.battery_low, - (int16_t)instance->generic.temp, - abs(((int16_t)(instance->generic.temp * 10) - (((int16_t)instance->generic.temp) * 10))), + (double)instance->generic.temp, instance->generic.humidity); } diff --git a/applications/plugins/weather_station/protocols/infactory.c b/applications/plugins/weather_station/protocols/infactory.c index b08a4e9dbee..2d444d9814f 100644 --- a/applications/plugins/weather_station/protocols/infactory.c +++ b/applications/plugins/weather_station/protocols/infactory.c @@ -283,7 +283,7 @@ void ws_protocol_decoder_infactory_get_string(void* context, FuriString* output) "%s %dbit\r\n" "Key:0x%lX%08lX\r\n" "Sn:0x%lX Ch:%d Bat:%d\r\n" - "Temp:%d.%d C Hum:%d%%", + "Temp:%3.1f C Hum:%d%%", instance->generic.protocol_name, instance->generic.data_count_bit, (uint32_t)(instance->generic.data >> 32), @@ -291,7 +291,6 @@ void ws_protocol_decoder_infactory_get_string(void* context, FuriString* output) instance->generic.id, instance->generic.channel, instance->generic.battery_low, - (int16_t)instance->generic.temp, - abs(((int16_t)(instance->generic.temp * 10) - (((int16_t)instance->generic.temp) * 10))), + (double)instance->generic.temp, instance->generic.humidity); } diff --git a/applications/plugins/weather_station/protocols/lacrosse_tx141thbv2.c b/applications/plugins/weather_station/protocols/lacrosse_tx141thbv2.c index d4b89be874f..e4b61225014 100644 --- a/applications/plugins/weather_station/protocols/lacrosse_tx141thbv2.c +++ b/applications/plugins/weather_station/protocols/lacrosse_tx141thbv2.c @@ -284,7 +284,7 @@ void ws_protocol_decoder_lacrosse_tx141thbv2_get_string(void* context, FuriStrin "%s %dbit\r\n" "Key:0x%lX%08lX\r\n" "Sn:0x%lX Ch:%d Bat:%d\r\n" - "Temp:%d.%d C Hum:%d%%", + "Temp:%3.1f C Hum:%d%%", instance->generic.protocol_name, instance->generic.data_count_bit, (uint32_t)(instance->generic.data >> 32), @@ -292,7 +292,6 @@ void ws_protocol_decoder_lacrosse_tx141thbv2_get_string(void* context, FuriStrin instance->generic.id, instance->generic.channel, instance->generic.battery_low, - (int16_t)instance->generic.temp, - abs(((int16_t)(instance->generic.temp * 10) - (((int16_t)instance->generic.temp) * 10))), + (double)instance->generic.temp, instance->generic.humidity); } diff --git a/applications/plugins/weather_station/protocols/nexus_th.c b/applications/plugins/weather_station/protocols/nexus_th.c index c3d823edaec..7d4a77aea00 100644 --- a/applications/plugins/weather_station/protocols/nexus_th.c +++ b/applications/plugins/weather_station/protocols/nexus_th.c @@ -247,7 +247,7 @@ void ws_protocol_decoder_nexus_th_get_string(void* context, FuriString* output) "%s %dbit\r\n" "Key:0x%lX%08lX\r\n" "Sn:0x%lX Ch:%d Bat:%d\r\n" - "Temp:%d.%d C Hum:%d%%", + "Temp:%3.1f C Hum:%d%%", instance->generic.protocol_name, instance->generic.data_count_bit, (uint32_t)(instance->generic.data >> 32), @@ -255,7 +255,6 @@ void ws_protocol_decoder_nexus_th_get_string(void* context, FuriString* output) instance->generic.id, instance->generic.channel, instance->generic.battery_low, - (int16_t)instance->generic.temp, - abs(((int16_t)(instance->generic.temp * 10) - (((int16_t)instance->generic.temp) * 10))), + (double)instance->generic.temp, instance->generic.humidity); } diff --git a/applications/plugins/weather_station/protocols/thermopro_tx4.c b/applications/plugins/weather_station/protocols/thermopro_tx4.c index 9a2eacb2fdb..0882bc33d74 100644 --- a/applications/plugins/weather_station/protocols/thermopro_tx4.c +++ b/applications/plugins/weather_station/protocols/thermopro_tx4.c @@ -246,7 +246,7 @@ void ws_protocol_decoder_thermopro_tx4_get_string(void* context, FuriString* out "%s %dbit\r\n" "Key:0x%lX%08lX\r\n" "Sn:0x%lX Ch:%d Bat:%d\r\n" - "Temp:%d.%d C Hum:%d%%", + "Temp:%3.1f C Hum:%d%%", instance->generic.protocol_name, instance->generic.data_count_bit, (uint32_t)(instance->generic.data >> 32), @@ -254,7 +254,6 @@ void ws_protocol_decoder_thermopro_tx4_get_string(void* context, FuriString* out instance->generic.id, instance->generic.channel, instance->generic.battery_low, - (int16_t)instance->generic.temp, - abs(((int16_t)(instance->generic.temp * 10) - (((int16_t)instance->generic.temp) * 10))), + (double)instance->generic.temp, instance->generic.humidity); } diff --git a/applications/plugins/weather_station/views/weather_station_receiver_info.c b/applications/plugins/weather_station/views/weather_station_receiver_info.c index 34ec122d135..49b447f10dc 100644 --- a/applications/plugins/weather_station/views/weather_station_receiver_info.c +++ b/applications/plugins/weather_station/views/weather_station_receiver_info.c @@ -75,12 +75,7 @@ void ws_view_receiver_info_draw(Canvas* canvas, WSReceiverInfoModel* model) { if(model->generic->temp != WS_NO_TEMPERATURE) { canvas_draw_icon(canvas, 18, 42, &I_Therm_7x16); - snprintf( - buffer, - sizeof(buffer), - "%3.2d.%d C", - (int16_t)model->generic->temp, - abs(((int16_t)(model->generic->temp * 10) - (((int16_t)model->generic->temp) * 10)))); + snprintf(buffer, sizeof(buffer), "%3.1f C", (double)model->generic->temp); canvas_draw_str_aligned(canvas, 63, 46, AlignRight, AlignTop, buffer); canvas_draw_circle(canvas, 55, 45, 1); } From aa2ecbe80f77d6b7132cd4fdb83eb49fba297b31 Mon Sep 17 00:00:00 2001 From: Samuel Stauffer Date: Mon, 7 Nov 2022 06:38:04 -0800 Subject: [PATCH 207/824] infrared: add Kaseikyo IR protocol (#1965) * infrared: add Kaseikyo IR protocol Add Kaseikyo IR protocol support. This protocol is also called the Panasonic protocol and is used by a number of manufacturers including Denon. The protocol includes a vendor field and a number of fields that are vendor specific. To support the format of address+command used by flipper the vendor+genre1+genre2+id fields are encoded into the address while the data is used for the command. There are older versions of the protocol that used a reverse bit order that are not supported. Protocol information: - https://github.com/Arduino-IRremote/Arduino-IRremote/blob/master/src/ir_Kaseikyo.hpp - http://www.hifi-remote.com/johnsfine/DecodeIR.html#Kaseikyo - https://www.denon.com/-/media/files/documentmaster/denonna/avr-x3700h_avc-x3700h_ir_code_v01_04062020.doc * Format and add unit test to Kaseikyo IR codec. Co-authored-by: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> --- .../debug/unit_tests/infrared/infrared_test.c | 12 ++ .../unit_tests/infrared/test_kaseikyo.irtest | 105 ++++++++++++++++++ .../common/infrared_common_protocol_defs.c | 23 ++++ lib/infrared/encoder_decoder/infrared.c | 14 +++ lib/infrared/encoder_decoder/infrared.h | 1 + .../infrared_protocol_defs_i.h | 51 +++++++++ .../kaseikyo/infrared_decoder_kaseikyo.c | 54 +++++++++ .../kaseikyo/infrared_encoder_kaseikyo.c | 45 ++++++++ .../kaseikyo/infrared_kaseikyo_spec.c | 17 +++ 9 files changed, 322 insertions(+) create mode 100644 assets/unit_tests/infrared/test_kaseikyo.irtest create mode 100644 lib/infrared/encoder_decoder/kaseikyo/infrared_decoder_kaseikyo.c create mode 100644 lib/infrared/encoder_decoder/kaseikyo/infrared_encoder_kaseikyo.c create mode 100644 lib/infrared/encoder_decoder/kaseikyo/infrared_kaseikyo_spec.c diff --git a/applications/debug/unit_tests/infrared/infrared_test.c b/applications/debug/unit_tests/infrared/infrared_test.c index 8879c8fc883..2bcb95da8e5 100644 --- a/applications/debug/unit_tests/infrared/infrared_test.c +++ b/applications/debug/unit_tests/infrared/infrared_test.c @@ -424,6 +424,7 @@ MU_TEST(infrared_test_decoder_mixed) { infrared_test_run_decoder(InfraredProtocolRC5, 5); infrared_test_run_decoder(InfraredProtocolSamsung32, 1); infrared_test_run_decoder(InfraredProtocolSIRC, 3); + infrared_test_run_decoder(InfraredProtocolKaseikyo, 1); } MU_TEST(infrared_test_decoder_nec) { @@ -489,6 +490,15 @@ MU_TEST(infrared_test_encoder_rc6) { infrared_test_run_encoder(InfraredProtocolRC6, 1); } +MU_TEST(infrared_test_decoder_kaseikyo) { + infrared_test_run_decoder(InfraredProtocolKaseikyo, 1); + infrared_test_run_decoder(InfraredProtocolKaseikyo, 2); + infrared_test_run_decoder(InfraredProtocolKaseikyo, 3); + infrared_test_run_decoder(InfraredProtocolKaseikyo, 4); + infrared_test_run_decoder(InfraredProtocolKaseikyo, 5); + infrared_test_run_decoder(InfraredProtocolKaseikyo, 6); +} + MU_TEST(infrared_test_encoder_decoder_all) { infrared_test_run_encoder_decoder(InfraredProtocolNEC, 1); infrared_test_run_encoder_decoder(InfraredProtocolNECext, 1); @@ -498,6 +508,7 @@ MU_TEST(infrared_test_encoder_decoder_all) { infrared_test_run_encoder_decoder(InfraredProtocolRC6, 1); infrared_test_run_encoder_decoder(InfraredProtocolRC5, 1); infrared_test_run_encoder_decoder(InfraredProtocolSIRC, 1); + infrared_test_run_encoder_decoder(InfraredProtocolKaseikyo, 1); } MU_TEST_SUITE(infrared_test) { @@ -515,6 +526,7 @@ MU_TEST_SUITE(infrared_test) { MU_RUN_TEST(infrared_test_decoder_nec); MU_RUN_TEST(infrared_test_decoder_samsung32); MU_RUN_TEST(infrared_test_decoder_necext1); + MU_RUN_TEST(infrared_test_decoder_kaseikyo); MU_RUN_TEST(infrared_test_decoder_mixed); MU_RUN_TEST(infrared_test_encoder_decoder_all); } diff --git a/assets/unit_tests/infrared/test_kaseikyo.irtest b/assets/unit_tests/infrared/test_kaseikyo.irtest new file mode 100644 index 00000000000..d0142fecdc3 --- /dev/null +++ b/assets/unit_tests/infrared/test_kaseikyo.irtest @@ -0,0 +1,105 @@ +Filetype: IR tests file +Version: 1 +# +name: decoder_input1 +type: raw +data: 1000000 3363 1685 407 436 411 432 415 1240 434 410 437 1245 439 404 433 1249 435 408 439 431 406 1249 435 435 412 405 442 1241 433 1249 435 408 439 405 442 428 409 434 413 430 407 411 436 433 414 429 408 1248 436 407 440 1243 441 428 409 434 413 431 406 1249 435 1248 436 406 441 1242 442 1240 434 409 438 431 416 428 409 408 439 430 407 411 436 407 440 429 408 436 411 432 415 402 435 1247 437 1245 439 1243 441 1238 436 +# +name: decoder_expected1 +type: parsed_array +count: 1 +# +protocol: Kaseikyo +address: 41 54 32 00 +command: 1B 00 00 00 +repeat: false +# +name: decoder_input2 +type: raw +data: 1000000 3365 1683 409 434 413 431 406 1276 408 435 412 1270 414 429 408 1248 436 434 413 430 407 1275 409 434 413 431 406 1276 408 1248 436 433 414 430 407 437 410 433 414 429 408 436 411 432 415 428 409 1246 438 432 415 1267 407 437 410 433 414 429 408 436 411 432 415 1266 408 1250 434 1248 436 432 415 429 408 435 412 432 415 428 409 434 413 430 407 437 410 433 414 429 408 436 411 432 415 428 409 435 412 1240 434 +# +name: decoder_expected2 +type: parsed_array +count: 1 +# +protocol: Kaseikyo +address: 41 54 32 00 +command: 1C 00 00 00 +repeat: false +# +name: decoder_input3 +type: raw +data: 1000000 3361 1661 442 427 410 434 413 1243 441 428 409 1247 437 432 415 1241 433 410 437 407 440 1242 432 437 410 407 440 1242 442 1241 433 436 411 407 440 430 407 436 411 406 441 402 435 435 412 431 416 1240 434 410 437 1245 439 404 433 411 436 407 440 403 434 436 411 432 415 429 408 1249 435 1247 437 1245 439 430 407 1250 434 434 413 404 433 438 409 434 413 1243 441 1241 433 410 437 1245 439 430 407 1250 434 432 415 +# +name: decoder_expected3 +type: parsed_array +count: 1 +# +protocol: Kaseikyo +address: 41 54 32 00 +command: 70 01 00 00 +repeat: false +# +name: decoder_input4 +type: raw +data: 1000000 3365 1656 436 406 441 402 435 1248 436 406 441 1242 432 410 437 1246 438 404 433 410 437 1246 438 404 433 437 410 1245 491 1190 442 401 436 435 412 431 416 427 410 433 414 429 408 435 412 431 416 1240 434 435 412 1244 440 1241 433 436 411 433 414 402 435 409 438 405 442 402 435 1247 437 1244 440 1241 433 437 410 1245 439 430 407 410 437 406 441 402 435 409 438 1243 441 402 435 1247 437 406 441 1240 434 433 414 +# +name: decoder_expected4 +type: parsed_array +count: 1 +# +protocol: Kaseikyo +address: 43 54 32 00 +command: 70 01 00 00 +repeat: false +# +name: decoder_input5 +type: raw +data: 1000000 3357 1665 438 431 416 428 409 1247 437 432 415 1241 433 436 411 1245 439 430 407 436 411 1245 439 430 407 437 410 1246 438 1243 441 428 409 436 411 432 415 428 409 435 412 431 416 427 410 434 413 1243 441 427 410 1247 437 1245 439 430 407 437 410 1246 438 1244 440 429 408 1250 434 1248 488 355 440 429 408 436 411 432 415 428 408 435 412 431 416 428 409 1247 437 432 415 428 409 1248 436 1246 490 1191 441 1240 434 +# +name: decoder_expected5 +type: parsed_array +count: 1 +# +protocol: Kaseikyo +address: 43 54 32 00 +command: 1B 00 00 00 +repeat: false +# +name: decoder_input6 +type: raw +data: 1000000 3358 1664 439 430 407 437 410 1245 439 430 407 1250 434 434 413 1243 441 428 409 435 412 1244 440 428 409 435 412 1244 440 1242 432 437 410 434 413 430 407 436 411 432 415 428 409 435 412 431 416 1240 434 435 412 1244 440 1242 442 427 410 434 413 1243 441 427 409 1247 437 433 414 429 408 436 411 432 415 428 409 435 412 431 416 427 410 434 413 1243 441 1240 486 357 438 432 415 1240 434 436 411 432 415 425 412 +# +name: decoder_expected6 +type: parsed_array +count: 1 +# +protocol: Kaseikyo +address: 43 54 32 00 +command: 05 00 00 00 +repeat: false +# +name: encoder_decoder_input1 +type: parsed_array +count: 4 +# +protocol: Kaseikyo +address: 41 54 32 00 +command: 1B 00 00 00 +repeat: false +# +protocol: Kaseikyo +address: 41 54 32 00 +command: 70 01 00 00 +repeat: false +# +protocol: Kaseikyo +address: 43 54 32 00 +command: 05 00 00 00 +repeat: false +# +protocol: Kaseikyo +address: 43 54 32 00 +command: 1B 00 00 00 +repeat: false +# diff --git a/lib/infrared/encoder_decoder/common/infrared_common_protocol_defs.c b/lib/infrared/encoder_decoder/common/infrared_common_protocol_defs.c index e8f7664a7cd..3dd26e9d87b 100644 --- a/lib/infrared/encoder_decoder/common/infrared_common_protocol_defs.c +++ b/lib/infrared/encoder_decoder/common/infrared_common_protocol_defs.c @@ -115,3 +115,26 @@ const InfraredCommonProtocolSpec protocol_sirc = { .decode_repeat = NULL, .encode_repeat = infrared_encoder_sirc_encode_repeat, }; + +const InfraredCommonProtocolSpec protocol_kaseikyo = { + .timings = + { + .preamble_mark = INFRARED_KASEIKYO_PREAMBLE_MARK, + .preamble_space = INFRARED_KASEIKYO_PREAMBLE_SPACE, + .bit1_mark = INFRARED_KASEIKYO_BIT1_MARK, + .bit1_space = INFRARED_KASEIKYO_BIT1_SPACE, + .bit0_mark = INFRARED_KASEIKYO_BIT0_MARK, + .bit0_space = INFRARED_KASEIKYO_BIT0_SPACE, + .preamble_tolerance = INFRARED_KASEIKYO_PREAMBLE_TOLERANCE, + .bit_tolerance = INFRARED_KASEIKYO_BIT_TOLERANCE, + .silence_time = INFRARED_KASEIKYO_SILENCE, + .min_split_time = INFRARED_KASEIKYO_MIN_SPLIT_TIME, + }, + .databit_len[0] = 48, + .no_stop_bit = false, + .decode = infrared_common_decode_pdwm, + .encode = infrared_common_encode_pdwm, + .interpret = infrared_decoder_kaseikyo_interpret, + .decode_repeat = NULL, + .encode_repeat = NULL, +}; diff --git a/lib/infrared/encoder_decoder/infrared.c b/lib/infrared/encoder_decoder/infrared.c index 71bd6bb6d17..2c5ef0fffd2 100644 --- a/lib/infrared/encoder_decoder/infrared.c +++ b/lib/infrared/encoder_decoder/infrared.c @@ -110,6 +110,20 @@ static const InfraredEncoderDecoder infrared_encoder_decoder[] = { .free = infrared_encoder_sirc_free}, .get_protocol_spec = infrared_sirc_get_spec, }, + { + .decoder = + {.alloc = infrared_decoder_kaseikyo_alloc, + .decode = infrared_decoder_kaseikyo_decode, + .reset = infrared_decoder_kaseikyo_reset, + .check_ready = infrared_decoder_kaseikyo_check_ready, + .free = infrared_decoder_kaseikyo_free}, + .encoder = + {.alloc = infrared_encoder_kaseikyo_alloc, + .encode = infrared_encoder_kaseikyo_encode, + .reset = infrared_encoder_kaseikyo_reset, + .free = infrared_encoder_kaseikyo_free}, + .get_protocol_spec = infrared_kaseikyo_get_spec, + }, }; static int infrared_find_index_by_protocol(InfraredProtocol protocol); diff --git a/lib/infrared/encoder_decoder/infrared.h b/lib/infrared/encoder_decoder/infrared.h index 945913000be..086950f1ef8 100644 --- a/lib/infrared/encoder_decoder/infrared.h +++ b/lib/infrared/encoder_decoder/infrared.h @@ -31,6 +31,7 @@ typedef enum { InfraredProtocolSIRC, InfraredProtocolSIRC15, InfraredProtocolSIRC20, + InfraredProtocolKaseikyo, InfraredProtocolMAX, } InfraredProtocol; diff --git a/lib/infrared/encoder_decoder/infrared_protocol_defs_i.h b/lib/infrared/encoder_decoder/infrared_protocol_defs_i.h index 575961f1357..6146f7b4e2f 100644 --- a/lib/infrared/encoder_decoder/infrared_protocol_defs_i.h +++ b/lib/infrared/encoder_decoder/infrared_protocol_defs_i.h @@ -267,3 +267,54 @@ InfraredStatus infrared_encoder_sirc_encode_repeat( bool* level); extern const InfraredCommonProtocolSpec protocol_sirc; + +/*************************************************************************************************** +* Kaseikyo protocol description +* https://github.com/Arduino-IRremote/Arduino-IRremote/blob/master/src/ir_Kaseikyo.hpp +**************************************************************************************************** +* Preamble Preamble Pulse Distance/Width Pause Preamble Preamble +* mark space Modulation up to period repeat repeat +* mark space +* +* 3360 1665 48 bit ...130000 3456 1728 +* __________ _ _ _ _ _ _ _ _ _ _ _ _ _ ___________ +* ____ __________ _ _ _ __ __ __ _ _ __ __ _ _ ________________ ___________ +* +***************************************************************************************************/ + +#define INFRARED_KASEIKYO_UNIT 432 +#define INFRARED_KASEIKYO_PREAMBLE_MARK (8 * INFRARED_KASEIKYO_UNIT) +#define INFRARED_KASEIKYO_PREAMBLE_SPACE (4 * INFRARED_KASEIKYO_UNIT) +#define INFRARED_KASEIKYO_BIT1_MARK INFRARED_KASEIKYO_UNIT +#define INFRARED_KASEIKYO_BIT1_SPACE (3 * INFRARED_KASEIKYO_UNIT) +#define INFRARED_KASEIKYO_BIT0_MARK INFRARED_KASEIKYO_UNIT +#define INFRARED_KASEIKYO_BIT0_SPACE INFRARED_KASEIKYO_UNIT +#define INFRARED_KASEIKYO_REPEAT_PERIOD 130000 +#define INFRARED_KASEIKYO_SILENCE INFRARED_KASEIKYO_REPEAT_PERIOD +#define INFRARED_KASEIKYO_MIN_SPLIT_TIME INFRARED_KASEIKYO_REPEAT_PAUSE_MIN +#define INFRARED_KASEIKYO_REPEAT_PAUSE_MIN 4000 +#define INFRARED_KASEIKYO_REPEAT_PAUSE_MAX 150000 +#define INFRARED_KASEIKYO_REPEAT_MARK INFRARED_KASEIKYO_PREAMBLE_MARK +#define INFRARED_KASEIKYO_REPEAT_SPACE (INFRARED_KASEIKYO_REPEAT_PERIOD - 56000) +#define INFRARED_KASEIKYO_PREAMBLE_TOLERANCE 200 // us +#define INFRARED_KASEIKYO_BIT_TOLERANCE 120 // us + +void* infrared_decoder_kaseikyo_alloc(void); +void infrared_decoder_kaseikyo_reset(void* decoder); +void infrared_decoder_kaseikyo_free(void* decoder); +InfraredMessage* infrared_decoder_kaseikyo_check_ready(void* decoder); +InfraredMessage* infrared_decoder_kaseikyo_decode(void* decoder, bool level, uint32_t duration); +void* infrared_encoder_kaseikyo_alloc(void); +InfraredStatus + infrared_encoder_kaseikyo_encode(void* encoder_ptr, uint32_t* duration, bool* level); +void infrared_encoder_kaseikyo_reset(void* encoder_ptr, const InfraredMessage* message); +void infrared_encoder_kaseikyo_free(void* encoder_ptr); +bool infrared_decoder_kaseikyo_interpret(InfraredCommonDecoder* decoder); +InfraredStatus infrared_decoder_kaseikyo_decode_repeat(InfraredCommonDecoder* decoder); +InfraredStatus infrared_encoder_kaseikyo_encode_repeat( + InfraredCommonEncoder* encoder, + uint32_t* duration, + bool* level); +const InfraredProtocolSpecification* infrared_kaseikyo_get_spec(InfraredProtocol protocol); + +extern const InfraredCommonProtocolSpec protocol_kaseikyo; diff --git a/lib/infrared/encoder_decoder/kaseikyo/infrared_decoder_kaseikyo.c b/lib/infrared/encoder_decoder/kaseikyo/infrared_decoder_kaseikyo.c new file mode 100644 index 00000000000..b8db81d7ef8 --- /dev/null +++ b/lib/infrared/encoder_decoder/kaseikyo/infrared_decoder_kaseikyo.c @@ -0,0 +1,54 @@ +#include "infrared.h" +#include "infrared_protocol_defs_i.h" +#include +#include +#include +#include "../infrared_i.h" + +InfraredMessage* infrared_decoder_kaseikyo_check_ready(void* ctx) { + return infrared_common_decoder_check_ready(ctx); +} + +bool infrared_decoder_kaseikyo_interpret(InfraredCommonDecoder* decoder) { + furi_assert(decoder); + + bool result = false; + uint16_t vendor_id = ((uint16_t)(decoder->data[1]) << 8) | (uint16_t)decoder->data[0]; + uint8_t vendor_parity = decoder->data[2] & 0x0f; + uint8_t genre1 = decoder->data[2] >> 4; + uint8_t genre2 = decoder->data[3] & 0x0f; + uint16_t data = (uint16_t)(decoder->data[3] >> 4) | ((uint16_t)(decoder->data[4] & 0x3f) << 4); + uint8_t id = decoder->data[4] >> 6; + uint8_t parity = decoder->data[5]; + + uint8_t vendor_parity_check = decoder->data[0] ^ decoder->data[1]; + vendor_parity_check = (vendor_parity_check & 0xf) ^ (vendor_parity_check >> 4); + uint8_t parity_check = decoder->data[2] ^ decoder->data[3] ^ decoder->data[4]; + + if(vendor_parity == vendor_parity_check && parity == parity_check) { + decoder->message.command = (uint32_t)data; + decoder->message.address = ((uint32_t)id << 24) | ((uint32_t)vendor_id << 8) | + ((uint32_t)genre1 << 4) | (uint32_t)genre2; + decoder->message.protocol = InfraredProtocolKaseikyo; + decoder->message.repeat = false; + result = true; + } + + return result; +} + +void* infrared_decoder_kaseikyo_alloc(void) { + return infrared_common_decoder_alloc(&protocol_kaseikyo); +} + +InfraredMessage* infrared_decoder_kaseikyo_decode(void* decoder, bool level, uint32_t duration) { + return infrared_common_decode(decoder, level, duration); +} + +void infrared_decoder_kaseikyo_free(void* decoder) { + infrared_common_decoder_free(decoder); +} + +void infrared_decoder_kaseikyo_reset(void* decoder) { + infrared_common_decoder_reset(decoder); +} diff --git a/lib/infrared/encoder_decoder/kaseikyo/infrared_encoder_kaseikyo.c b/lib/infrared/encoder_decoder/kaseikyo/infrared_encoder_kaseikyo.c new file mode 100644 index 00000000000..5814c725577 --- /dev/null +++ b/lib/infrared/encoder_decoder/kaseikyo/infrared_encoder_kaseikyo.c @@ -0,0 +1,45 @@ +#include +#include "common/infrared_common_i.h" +#include +#include "../infrared_i.h" +#include "infrared_protocol_defs_i.h" +#include + +void infrared_encoder_kaseikyo_reset(void* encoder_ptr, const InfraredMessage* message) { + furi_assert(encoder_ptr); + + InfraredCommonEncoder* encoder = encoder_ptr; + infrared_common_encoder_reset(encoder); + + uint32_t address = message->address; + uint16_t command = message->command; + + uint8_t id = (address >> 24) & 3; + uint16_t vendor_id = (address >> 8) & 0xffff; + uint8_t genre1 = (address >> 4) & 0xf; + uint8_t genre2 = address & 0xf; + + encoder->data[0] = (uint8_t)(vendor_id & 0xff); + encoder->data[1] = (uint8_t)(vendor_id >> 8); + uint8_t vendor_parity = encoder->data[0] ^ encoder->data[1]; + vendor_parity = (vendor_parity & 0xf) ^ (vendor_parity >> 4); + encoder->data[2] = (vendor_parity & 0xf) | (genre1 << 4); + encoder->data[3] = (genre2 & 0xf) | ((uint8_t)(command & 0xf) << 4); + encoder->data[4] = (id << 6) | (uint8_t)(command >> 4); + encoder->data[5] = encoder->data[2] ^ encoder->data[3] ^ encoder->data[4]; + + encoder->bits_to_encode = encoder->protocol->databit_len[0]; +} + +void* infrared_encoder_kaseikyo_alloc(void) { + return infrared_common_encoder_alloc(&protocol_kaseikyo); +} + +void infrared_encoder_kaseikyo_free(void* encoder_ptr) { + infrared_common_encoder_free(encoder_ptr); +} + +InfraredStatus + infrared_encoder_kaseikyo_encode(void* encoder_ptr, uint32_t* duration, bool* level) { + return infrared_common_encode(encoder_ptr, duration, level); +} diff --git a/lib/infrared/encoder_decoder/kaseikyo/infrared_kaseikyo_spec.c b/lib/infrared/encoder_decoder/kaseikyo/infrared_kaseikyo_spec.c new file mode 100644 index 00000000000..87c86c7b3d1 --- /dev/null +++ b/lib/infrared/encoder_decoder/kaseikyo/infrared_kaseikyo_spec.c @@ -0,0 +1,17 @@ +#include "../infrared_i.h" +#include "infrared_protocol_defs_i.h" + +static const InfraredProtocolSpecification infrared_kaseikyo_protocol_specification = { + .name = "Kaseikyo", + .address_length = 26, + .command_length = 10, + .frequency = INFRARED_COMMON_CARRIER_FREQUENCY, + .duty_cycle = INFRARED_COMMON_DUTY_CYCLE, +}; + +const InfraredProtocolSpecification* infrared_kaseikyo_get_spec(InfraredProtocol protocol) { + if(protocol == InfraredProtocolKaseikyo) + return &infrared_kaseikyo_protocol_specification; + else + return NULL; +} From 2d6c2886ae429b634736fe9da6f48caef155d2e3 Mon Sep 17 00:00:00 2001 From: hedger Date: Mon, 7 Nov 2022 18:54:41 +0400 Subject: [PATCH 208/824] fbt: compile_db fixes (#1981) * fbt: forked compilation_db tool * fbt: fixes for static analysis * pvs-studio: ignoring more generic warnings * fbt: util: added extract_abs_dir * vscode: added fap-set-debug-elf-root for debug configurations --- .github/workflows/pvs_studio.yml | 2 +- .gitignore | 2 + .pvsconfig | 1 + .vscode/example/launch.json | 4 + SConstruct | 8 +- firmware.scons | 5 +- scripts/fbt/util.py | 12 +- scripts/fbt_tools/compilation_db.py | 278 ++++++++++++++++++++++++++++ scripts/fbt_tools/fbt_extapps.py | 3 +- 9 files changed, 307 insertions(+), 8 deletions(-) create mode 100644 scripts/fbt_tools/compilation_db.py diff --git a/.github/workflows/pvs_studio.yml b/.github/workflows/pvs_studio.yml index f28fad20dc6..9de493a44d7 100644 --- a/.github/workflows/pvs_studio.yml +++ b/.github/workflows/pvs_studio.yml @@ -57,7 +57,7 @@ jobs: - name: 'Generate compile_comands.json' run: | - FBT_TOOLCHAIN_PATH=/runner/_work ./fbt COMPACT=1 version_json proto_ver icons firmware_cdb dolphin_internal dolphin_blocking + FBT_TOOLCHAIN_PATH=/runner/_work ./fbt COMPACT=1 version_json proto_ver icons firmware_cdb dolphin_internal dolphin_blocking _fap_icons - name: 'Static code analysis' run: | diff --git a/.gitignore b/.gitignore index 38a31bf01bd..61c594ed106 100644 --- a/.gitignore +++ b/.gitignore @@ -54,3 +54,5 @@ openocd.log # PVS Studio temporary files .PVS-Studio/ PVS-Studio.log + +.gdbinit diff --git a/.pvsconfig b/.pvsconfig index d17eaa5a031..5f1ffb7cb5b 100644 --- a/.pvsconfig +++ b/.pvsconfig @@ -5,6 +5,7 @@ //-V:BPTREE_DEF2:779,1086,557,773,512 //-V:DICT_DEF2:779,524,776,760,1044,1001,729,590,568,747,685 //-V:ALGO_DEF:1048,747,1044 +//-V:TUPLE_DEF2:524,590,1001,760 # Non-severe malloc/null pointer deref warnings //-V::522:2,3 diff --git a/.vscode/example/launch.json b/.vscode/example/launch.json index c8b0c601d46..5c46d397919 100644 --- a/.vscode/example/launch.json +++ b/.vscode/example/launch.json @@ -38,6 +38,7 @@ "postAttachCommands": [ // "compare-sections", "source debug/flipperapps.py", + "fap-set-debug-elf-root build/latest/.extapps", // "source debug/FreeRTOS/FreeRTOS.py", // "svd_load debug/STM32WB55_CM4.svd" ] @@ -59,6 +60,7 @@ "set confirm off", "set mem inaccessible-by-default off", "source debug/flipperapps.py", + "fap-set-debug-elf-root build/latest/.extapps", // "compare-sections", ] // "showDevDebugOutput": "raw", @@ -76,6 +78,7 @@ "rtos": "FreeRTOS", "postAttachCommands": [ "source debug/flipperapps.py", + "fap-set-debug-elf-root build/latest/.extapps", ] // "showDevDebugOutput": "raw", }, @@ -95,6 +98,7 @@ ], "postAttachCommands": [ "source debug/flipperapps.py", + "fap-set-debug-elf-root build/latest/.extapps", ], // "showDevDebugOutput": "raw", }, diff --git a/SConstruct b/SConstruct index 67eac382523..3ee89f646c0 100644 --- a/SConstruct +++ b/SConstruct @@ -200,7 +200,9 @@ firmware_debug = distenv.PhonyTarget( source=firmware_env["FW_ELF"], GDBOPTS="${GDBOPTS_BASE}", GDBREMOTE="${OPENOCD_GDB_PIPE}", - FBT_FAP_DEBUG_ELF_ROOT=firmware_env.subst("$FBT_FAP_DEBUG_ELF_ROOT"), + FBT_FAP_DEBUG_ELF_ROOT=firmware_env.subst("$FBT_FAP_DEBUG_ELF_ROOT").replace( + "\\", "/" + ), ) distenv.Depends(firmware_debug, firmware_flash) @@ -210,7 +212,9 @@ distenv.PhonyTarget( source=firmware_env["FW_ELF"], GDBOPTS="${GDBOPTS_BASE} ${GDBOPTS_BLACKMAGIC}", GDBREMOTE="${BLACKMAGIC_ADDR}", - FBT_FAP_DEBUG_ELF_ROOT=firmware_env.subst("$FBT_FAP_DEBUG_ELF_ROOT"), + FBT_FAP_DEBUG_ELF_ROOT=firmware_env.subst("$FBT_FAP_DEBUG_ELF_ROOT").replace( + "\\", "/" + ), ) # Debug alien elf diff --git a/firmware.scons b/firmware.scons index 6feb73ac339..6a92374778e 100644 --- a/firmware.scons +++ b/firmware.scons @@ -264,7 +264,10 @@ fw_artifacts.extend( fwcdb = fwenv.CompilationDatabase() # without filtering, both updater & firmware commands would be generated in same file -fwenv.Replace(COMPILATIONDB_PATH_FILTER=fwenv.subst("*${FW_FLAVOR}*")) +fwenv.Replace( + COMPILATIONDB_PATH_FILTER=fwenv.subst("*${FW_FLAVOR}*"), + COMPILATIONDB_SRCPATH_FILTER="*.c*", +) AlwaysBuild(fwcdb) Precious(fwcdb) NoClean(fwcdb) diff --git a/scripts/fbt/util.py b/scripts/fbt/util.py index f5404458ec5..b8e9c5928f4 100644 --- a/scripts/fbt/util.py +++ b/scripts/fbt/util.py @@ -43,12 +43,18 @@ def single_quote(arg_list): return " ".join(f"'{arg}'" if " " in arg else str(arg) for arg in arg_list) -def extract_abs_dir_path(node): +def extract_abs_dir(node): if isinstance(node, SCons.Node.FS.EntryProxy): node = node.get() for repo_dir in node.get_all_rdirs(): if os.path.exists(repo_dir.abspath): - return repo_dir.abspath + return repo_dir + + +def extract_abs_dir_path(node): + abs_dir_node = extract_abs_dir(node) + if abs_dir_node is None: + raise StopError(f"Can't find absolute path for {node.name}") - raise StopError(f"Can't find absolute path for {node.name}") + return abs_dir_node.abspath diff --git a/scripts/fbt_tools/compilation_db.py b/scripts/fbt_tools/compilation_db.py new file mode 100644 index 00000000000..17ff6aaa3fa --- /dev/null +++ b/scripts/fbt_tools/compilation_db.py @@ -0,0 +1,278 @@ +""" +Implements the ability for SCons to emit a compilation database for the MongoDB project. See +http://clang.llvm.org/docs/JSONCompilationDatabase.html for details on what a compilation +database is, and why you might want one. The only user visible entry point here is +'env.CompilationDatabase'. This method takes an optional 'target' to name the file that +should hold the compilation database, otherwise, the file defaults to compile_commands.json, +which is the name that most clang tools search for by default. +""" + +# Copyright 2020 MongoDB Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +import json +import itertools +import fnmatch +import SCons + +from SCons.Tool.cxx import CXXSuffixes +from SCons.Tool.cc import CSuffixes +from SCons.Tool.asm import ASSuffixes, ASPPSuffixes + +# TODO: Is there a better way to do this than this global? Right now this exists so that the +# emitter we add can record all of the things it emits, so that the scanner for the top level +# compilation database can access the complete list, and also so that the writer has easy +# access to write all of the files. But it seems clunky. How can the emitter and the scanner +# communicate more gracefully? +__COMPILATION_DB_ENTRIES = [] + + +# We make no effort to avoid rebuilding the entries. Someday, perhaps we could and even +# integrate with the cache, but there doesn't seem to be much call for it. +class __CompilationDbNode(SCons.Node.Python.Value): + def __init__(self, value): + SCons.Node.Python.Value.__init__(self, value) + self.Decider(changed_since_last_build_node) + + +def changed_since_last_build_node(child, target, prev_ni, node): + """Dummy decider to force always building""" + return True + + +def make_emit_compilation_DB_entry(comstr): + """ + Effectively this creates a lambda function to capture: + * command line + * source + * target + :param comstr: unevaluated command line + :return: an emitter which has captured the above + """ + user_action = SCons.Action.Action(comstr) + + def emit_compilation_db_entry(target, source, env): + """ + This emitter will be added to each c/c++ object build to capture the info needed + for clang tools + :param target: target node(s) + :param source: source node(s) + :param env: Environment for use building this node + :return: target(s), source(s) + """ + + dbtarget = __CompilationDbNode(source) + + entry = env.__COMPILATIONDB_Entry( + target=dbtarget, + source=[], + __COMPILATIONDB_UOUTPUT=target, + __COMPILATIONDB_USOURCE=source, + __COMPILATIONDB_UACTION=user_action, + __COMPILATIONDB_ENV=env, + ) + + # TODO: Technically, these next two lines should not be required: it should be fine to + # cache the entries. However, they don't seem to update properly. Since they are quick + # to re-generate disable caching and sidestep this problem. + env.AlwaysBuild(entry) + env.NoCache(entry) + + __COMPILATION_DB_ENTRIES.append(dbtarget) + + return target, source + + return emit_compilation_db_entry + + +def compilation_db_entry_action(target, source, env, **kw): + """ + Create a dictionary with evaluated command line, target, source + and store that info as an attribute on the target + (Which has been stored in __COMPILATION_DB_ENTRIES array + :param target: target node(s) + :param source: source node(s) + :param env: Environment for use building this node + :param kw: + :return: None + """ + + command = env["__COMPILATIONDB_UACTION"].strfunction( + target=env["__COMPILATIONDB_UOUTPUT"], + source=env["__COMPILATIONDB_USOURCE"], + env=env["__COMPILATIONDB_ENV"], + ) + + entry = { + "directory": env.Dir("#").abspath, + "command": command, + "file": env["__COMPILATIONDB_USOURCE"][0], + "output": env["__COMPILATIONDB_UOUTPUT"][0], + } + + target[0].write(entry) + + +def write_compilation_db(target, source, env): + entries = [] + + use_abspath = env["COMPILATIONDB_USE_ABSPATH"] in [True, 1, "True", "true"] + use_path_filter = env.subst("$COMPILATIONDB_PATH_FILTER") + use_srcpath_filter = env.subst("$COMPILATIONDB_SRCPATH_FILTER") + + for s in __COMPILATION_DB_ENTRIES: + entry = s.read() + source_file = entry["file"] + output_file = entry["output"] + + if source_file.rfile().srcnode().exists(): + source_file = source_file.rfile().srcnode() + + if use_abspath: + source_file = source_file.abspath + output_file = output_file.abspath + else: + source_file = source_file.path + output_file = output_file.path + + # print("output_file, path_filter", output_file, use_path_filter) + if use_path_filter and not fnmatch.fnmatch(output_file, use_path_filter): + continue + + if use_srcpath_filter and not fnmatch.fnmatch(source_file, use_srcpath_filter): + continue + + path_entry = { + "directory": entry["directory"], + "command": entry["command"], + "file": source_file, + "output": output_file, + } + + entries.append(path_entry) + + with open(target[0].path, "w") as output_file: + json.dump( + entries, output_file, sort_keys=True, indent=4, separators=(",", ": ") + ) + + +def scan_compilation_db(node, env, path): + return __COMPILATION_DB_ENTRIES + + +def compilation_db_emitter(target, source, env): + """fix up the source/targets""" + + # Someone called env.CompilationDatabase('my_targetname.json') + if not target and len(source) == 1: + target = source + + # Default target name is compilation_db.json + if not target: + target = [ + "compile_commands.json", + ] + + # No source should have been passed. Drop it. + if source: + source = [] + + return target, source + + +def generate(env, **kwargs): + static_obj, shared_obj = SCons.Tool.createObjBuilders(env) + + env.SetDefault( + COMPILATIONDB_COMSTR=kwargs.get( + "COMPILATIONDB_COMSTR", "Building compilation database $TARGET" + ), + COMPILATIONDB_USE_ABSPATH=False, + COMPILATIONDB_PATH_FILTER="", + COMPILATIONDB_SRCPATH_FILTER="", + ) + + components_by_suffix = itertools.chain( + itertools.product( + CSuffixes, + [ + (static_obj, SCons.Defaults.StaticObjectEmitter, "$CCCOM"), + (shared_obj, SCons.Defaults.SharedObjectEmitter, "$SHCCCOM"), + ], + ), + itertools.product( + CXXSuffixes, + [ + (static_obj, SCons.Defaults.StaticObjectEmitter, "$CXXCOM"), + (shared_obj, SCons.Defaults.SharedObjectEmitter, "$SHCXXCOM"), + ], + ), + itertools.product( + ASSuffixes, + [(static_obj, SCons.Defaults.StaticObjectEmitter, "$ASCOM")], + [(shared_obj, SCons.Defaults.SharedObjectEmitter, "$ASCOM")], + ), + itertools.product( + ASPPSuffixes, + [(static_obj, SCons.Defaults.StaticObjectEmitter, "$ASPPCOM")], + [(shared_obj, SCons.Defaults.SharedObjectEmitter, "$ASPPCOM")], + ), + ) + + for entry in components_by_suffix: + suffix = entry[0] + builder, base_emitter, command = entry[1] + + # Assumes a dictionary emitter + emitter = builder.emitter.get(suffix, False) + if emitter: + # We may not have tools installed which initialize all or any of + # cxx, cc, or assembly. If not skip resetting the respective emitter. + builder.emitter[suffix] = SCons.Builder.ListEmitter( + [ + emitter, + make_emit_compilation_DB_entry(command), + ] + ) + + env.Append( + BUILDERS={ + "__COMPILATIONDB_Entry": SCons.Builder.Builder( + action=SCons.Action.Action(compilation_db_entry_action, None), + ), + "CompilationDatabase": SCons.Builder.Builder( + action=SCons.Action.Action( + write_compilation_db, "$COMPILATIONDB_COMSTR" + ), + target_scanner=SCons.Scanner.Scanner( + function=scan_compilation_db, node_class=None + ), + emitter=compilation_db_emitter, + suffix="json", + ), + } + ) + + +def exists(env): + return True diff --git a/scripts/fbt_tools/fbt_extapps.py b/scripts/fbt_tools/fbt_extapps.py index f1906191b9c..a4116e51329 100644 --- a/scripts/fbt_tools/fbt_extapps.py +++ b/scripts/fbt_tools/fbt_extapps.py @@ -57,11 +57,12 @@ def BuildAppElf(env, app): ) if app.fap_icon_assets: - app_env.CompileIcons( + fap_icons = app_env.CompileIcons( app_env.Dir(app_work_dir), app._appdir.Dir(app.fap_icon_assets), icon_bundle_name=f"{app.appid}_icons", ) + app_env.Alias("_fap_icons", fap_icons) private_libs = [] From 4d11213494be5bb0614f6cae50b7c57c42314a31 Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Tue, 8 Nov 2022 02:15:58 +1000 Subject: [PATCH 209/824] DAP-Link: show error if usb is locked (#1982) --- applications/plugins/dap_link/dap_link.c | 20 ++++++++++++++++++ .../dap_link/icons/ActiveConnection_50x64.png | Bin 0 -> 3842 bytes 2 files changed, 20 insertions(+) create mode 100644 applications/plugins/dap_link/icons/ActiveConnection_50x64.png diff --git a/applications/plugins/dap_link/dap_link.c b/applications/plugins/dap_link/dap_link.c index 58d032b91c4..443d77c5eed 100644 --- a/applications/plugins/dap_link/dap_link.c +++ b/applications/plugins/dap_link/dap_link.c @@ -13,6 +13,8 @@ #include "dap_config.h" #include "gui/dap_gui.h" #include "usb/dap_v2_usb.h" +#include +#include "dap_link_icons.h" /***************************************************************************/ /****************************** DAP COMMON *********************************/ @@ -495,6 +497,24 @@ DapConfig* dap_app_get_config(DapApp* app) { int32_t dap_link_app(void* p) { UNUSED(p); + if(furi_hal_usb_is_locked()) { + DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); + DialogMessage* message = dialog_message_alloc(); + dialog_message_set_header(message, "Connection\nis active!", 3, 2, AlignLeft, AlignTop); + dialog_message_set_text( + message, + "Disconnect from\nPC or phone to\nuse this function.", + 3, + 30, + AlignLeft, + AlignTop); + dialog_message_set_icon(message, &I_ActiveConnection_50x64, 78, 0); + dialog_message_show(dialogs, message); + dialog_message_free(message); + furi_record_close(RECORD_DIALOGS); + return -1; + } + // alloc app DapApp* app = dap_app_alloc(); app_handle = app; diff --git a/applications/plugins/dap_link/icons/ActiveConnection_50x64.png b/applications/plugins/dap_link/icons/ActiveConnection_50x64.png new file mode 100644 index 0000000000000000000000000000000000000000..1d7686dddf8a33b724c7528ed36435514b7518b2 GIT binary patch literal 3842 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 Date: Tue, 8 Nov 2022 19:56:49 +0300 Subject: [PATCH 210/824] Update toolchain to version 19. Update codeowners. Fix amap analyze. (#1986) * Up toolchain to 19 * Fix amap_analyse.yml * Github: update codeowners Co-authored-by: Aleksandr Kutuzov --- .github/CODEOWNERS | 9 +++++++-- .github/workflows/amap_analyse.yml | 2 +- scripts/toolchain/fbtenv.cmd | 2 +- scripts/toolchain/fbtenv.sh | 2 +- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index c9b8ff3f593..6b77482c6c8 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -18,7 +18,7 @@ /applications/main/gpio/ @skotopes @DrZlo13 @hedger @nminaylov /applications/main/ibutton/ @skotopes @DrZlo13 @hedger @gsurkov /applications/main/infrared/ @skotopes @DrZlo13 @hedger @gsurkov -/applications/main/nfc/ @skotopes @DrZlo13 @hedger @gornekich +/applications/main/nfc/ @skotopes @DrZlo13 @hedger @gornekich @Astrrra /applications/main/subghz/ @skotopes @DrZlo13 @hedger @Skorpionm /applications/main/u2f/ @skotopes @DrZlo13 @hedger @nminaylov @@ -40,6 +40,8 @@ /applications/system/storage_move_to_sd/ @skotopes @DrZlo13 @hedger @nminaylov +/applications/debug/unit_tests/ @skotopes @DrZlo13 @hedger @nminaylov @gornekich @Astrrra @gsurkov @Skorpionm + # Documentation /documentation/ @skotopes @DrZlo13 @hedger @drunkbatya /scripts/toolchain/ @skotopes @DrZlo13 @hedger @drunkbatya @@ -54,6 +56,9 @@ /lib/mbedtls/ @skotopes @DrZlo13 @hedger @nminaylov /lib/micro-ecc/ @skotopes @DrZlo13 @hedger @nminaylov /lib/nanopb/ @skotopes @DrZlo13 @hedger @nminaylov -/lib/nfc/ @skotopes @DrZlo13 @hedger @gornekich +/lib/nfc/ @skotopes @DrZlo13 @hedger @gornekich @Astrrra /lib/one_wire/ @skotopes @DrZlo13 @hedger @gsurkov /lib/subghz/ @skotopes @DrZlo13 @hedger @Skorpionm + +# CI/CD +/.github/workflows/ @skotopes @DrZlo13 @hedger @drunkbatya diff --git a/.github/workflows/amap_analyse.yml b/.github/workflows/amap_analyse.yml index a50c5436f47..cfb1eab14ee 100644 --- a/.github/workflows/amap_analyse.yml +++ b/.github/workflows/amap_analyse.yml @@ -91,7 +91,7 @@ jobs: export RODATA_SIZE="$(get_size ".rodata")" export DATA_SIZE="$(get_size ".data")" export FREE_FLASH_SIZE="$(get_size ".free_flash")" - python3 -m pip install mariadb + python3 -m pip install mariadb==1.1.4 python3 scripts/amap_mariadb_insert.py \ ${{ secrets.AMAP_MARIADB_USER }} \ ${{ secrets.AMAP_MARIADB_PASSWORD }} \ diff --git a/scripts/toolchain/fbtenv.cmd b/scripts/toolchain/fbtenv.cmd index 6e87bf95a50..44a2551f772 100644 --- a/scripts/toolchain/fbtenv.cmd +++ b/scripts/toolchain/fbtenv.cmd @@ -13,7 +13,7 @@ if not [%FBT_NOENV%] == [] ( exit /b 0 ) -set "FLIPPER_TOOLCHAIN_VERSION=17" +set "FLIPPER_TOOLCHAIN_VERSION=19" if [%FBT_TOOLCHAIN_ROOT%] == [] ( set "FBT_TOOLCHAIN_ROOT=%FBT_ROOT%\toolchain\x86_64-windows" diff --git a/scripts/toolchain/fbtenv.sh b/scripts/toolchain/fbtenv.sh index d3fdb8ceaf6..852e00394a1 100755 --- a/scripts/toolchain/fbtenv.sh +++ b/scripts/toolchain/fbtenv.sh @@ -5,7 +5,7 @@ # public variables DEFAULT_SCRIPT_PATH="$(pwd -P)"; SCRIPT_PATH="${SCRIPT_PATH:-$DEFAULT_SCRIPT_PATH}"; -FBT_TOOLCHAIN_VERSION="${FBT_TOOLCHAIN_VERSION:-"17"}"; +FBT_TOOLCHAIN_VERSION="${FBT_TOOLCHAIN_VERSION:-"19"}"; FBT_TOOLCHAIN_PATH="${FBT_TOOLCHAIN_PATH:-$SCRIPT_PATH}"; fbtenv_show_usage() From 328d049b6a56ed3ff9e92ad47a3778381b8db64e Mon Sep 17 00:00:00 2001 From: Samuel Stauffer Date: Tue, 8 Nov 2022 09:07:55 -0800 Subject: [PATCH 211/824] Add Acurite 609TXC protocol to weather station (#1987) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- .../protocols/acurite_609txc.c | 247 ++++++++++++++++++ .../protocols/acurite_609txc.h | 79 ++++++ .../protocols/protocol_items.c | 1 + .../protocols/protocol_items.h | 1 + 4 files changed, 328 insertions(+) create mode 100644 applications/plugins/weather_station/protocols/acurite_609txc.c create mode 100644 applications/plugins/weather_station/protocols/acurite_609txc.h diff --git a/applications/plugins/weather_station/protocols/acurite_609txc.c b/applications/plugins/weather_station/protocols/acurite_609txc.c new file mode 100644 index 00000000000..aeb0785eb50 --- /dev/null +++ b/applications/plugins/weather_station/protocols/acurite_609txc.c @@ -0,0 +1,247 @@ +#include "acurite_609txc.h" + +#define TAG "WSProtocolAcurite_609TXC" + +/* + * Help + * https://github.com/merbanan/rtl_433/blob/5bef4e43133ac4c0e2d18d36f87c52b4f9458453/src/devices/acurite.c#L216 + * + * 0000 1111 | 0011 0000 | 0101 1100 | 0000 0000 | 1110 0111 + * iiii iiii | buuu tttt | tttt tttt | hhhh hhhh | cccc cccc + * - i: identification; changes on battery switch + * - c: checksum (sum of previous by bytes) + * - u: unknown + * - b: battery low; flag to indicate low battery voltage + * - t: temperature; in °C * 10, 12 bit with complement + * - h: humidity + * + */ + +static const SubGhzBlockConst ws_protocol_acurite_609txc_const = { + .te_short = 500, + .te_long = 1000, + .te_delta = 150, + .min_count_bit_for_found = 40, +}; + +struct WSProtocolDecoderAcurite_609TXC { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + WSBlockGeneric generic; +}; + +struct WSProtocolEncoderAcurite_609TXC { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + WSBlockGeneric generic; +}; + +typedef enum { + Acurite_609TXCDecoderStepReset = 0, + Acurite_609TXCDecoderStepSaveDuration, + Acurite_609TXCDecoderStepCheckDuration, +} Acurite_609TXCDecoderStep; + +const SubGhzProtocolDecoder ws_protocol_acurite_609txc_decoder = { + .alloc = ws_protocol_decoder_acurite_609txc_alloc, + .free = ws_protocol_decoder_acurite_609txc_free, + + .feed = ws_protocol_decoder_acurite_609txc_feed, + .reset = ws_protocol_decoder_acurite_609txc_reset, + + .get_hash_data = ws_protocol_decoder_acurite_609txc_get_hash_data, + .serialize = ws_protocol_decoder_acurite_609txc_serialize, + .deserialize = ws_protocol_decoder_acurite_609txc_deserialize, + .get_string = ws_protocol_decoder_acurite_609txc_get_string, +}; + +const SubGhzProtocolEncoder ws_protocol_acurite_609txc_encoder = { + .alloc = NULL, + .free = NULL, + + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol ws_protocol_acurite_609txc = { + .name = WS_PROTOCOL_ACURITE_609TXC_NAME, + .type = SubGhzProtocolWeatherStation, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable, + + .decoder = &ws_protocol_acurite_609txc_decoder, + .encoder = &ws_protocol_acurite_609txc_encoder, +}; + +void* ws_protocol_decoder_acurite_609txc_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + WSProtocolDecoderAcurite_609TXC* instance = malloc(sizeof(WSProtocolDecoderAcurite_609TXC)); + instance->base.protocol = &ws_protocol_acurite_609txc; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void ws_protocol_decoder_acurite_609txc_free(void* context) { + furi_assert(context); + WSProtocolDecoderAcurite_609TXC* instance = context; + free(instance); +} + +void ws_protocol_decoder_acurite_609txc_reset(void* context) { + furi_assert(context); + WSProtocolDecoderAcurite_609TXC* instance = context; + instance->decoder.parser_step = Acurite_609TXCDecoderStepReset; +} + +static bool ws_protocol_acurite_609txc_check(WSProtocolDecoderAcurite_609TXC* instance) { + if(!instance->decoder.decode_data) return false; + uint8_t crc = (uint8_t)(instance->decoder.decode_data >> 32) + + (uint8_t)(instance->decoder.decode_data >> 24) + + (uint8_t)(instance->decoder.decode_data >> 16) + + (uint8_t)(instance->decoder.decode_data >> 8); + return (crc == (instance->decoder.decode_data & 0xFF)); +} + +/** + * Analysis of received data + * @param instance Pointer to a WSBlockGeneric* instance + */ +static void ws_protocol_acurite_609txc_remote_controller(WSBlockGeneric* instance) { + instance->id = (instance->data >> 32) & 0xFF; + instance->battery_low = (instance->data >> 31) & 1; + + instance->channel = WS_NO_CHANNEL; + + // Temperature in Celsius is encoded as a 12 bit integer value + // multiplied by 10 using the 4th - 6th nybbles (bytes 1 & 2) + // negative values are recovered by sign extend from int16_t. + int16_t temp_raw = + (int16_t)(((instance->data >> 12) & 0xf000) | ((instance->data >> 16) << 4)); + instance->temp = (temp_raw >> 4) * 0.1f; + instance->humidity = (instance->data >> 8) & 0xff; + instance->btn = WS_NO_BTN; +} + +void ws_protocol_decoder_acurite_609txc_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + WSProtocolDecoderAcurite_609TXC* instance = context; + + switch(instance->decoder.parser_step) { + case Acurite_609TXCDecoderStepReset: + if((!level) && (DURATION_DIFF(duration, ws_protocol_acurite_609txc_const.te_short * 17) < + ws_protocol_acurite_609txc_const.te_delta * 8)) { + //Found syncPrefix + instance->decoder.parser_step = Acurite_609TXCDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } + break; + + case Acurite_609TXCDecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = Acurite_609TXCDecoderStepCheckDuration; + } else { + instance->decoder.parser_step = Acurite_609TXCDecoderStepReset; + } + break; + + case Acurite_609TXCDecoderStepCheckDuration: + if(!level) { + if(DURATION_DIFF(instance->decoder.te_last, ws_protocol_acurite_609txc_const.te_short) < + ws_protocol_acurite_609txc_const.te_delta) { + if((DURATION_DIFF(duration, ws_protocol_acurite_609txc_const.te_short) < + ws_protocol_acurite_609txc_const.te_delta) || + (duration > ws_protocol_acurite_609txc_const.te_long * 3)) { + //Found syncPostfix + instance->decoder.parser_step = Acurite_609TXCDecoderStepReset; + if((instance->decoder.decode_count_bit == + ws_protocol_acurite_609txc_const.min_count_bit_for_found) && + ws_protocol_acurite_609txc_check(instance)) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + ws_protocol_acurite_609txc_remote_controller(&instance->generic); + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } else if( + DURATION_DIFF(duration, ws_protocol_acurite_609txc_const.te_long) < + ws_protocol_acurite_609txc_const.te_delta * 2) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = Acurite_609TXCDecoderStepSaveDuration; + } else if( + DURATION_DIFF(duration, ws_protocol_acurite_609txc_const.te_long * 2) < + ws_protocol_acurite_609txc_const.te_delta * 4) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = Acurite_609TXCDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = Acurite_609TXCDecoderStepReset; + } + } else { + instance->decoder.parser_step = Acurite_609TXCDecoderStepReset; + } + } else { + instance->decoder.parser_step = Acurite_609TXCDecoderStepReset; + } + break; + } +} + +uint8_t ws_protocol_decoder_acurite_609txc_get_hash_data(void* context) { + furi_assert(context); + WSProtocolDecoderAcurite_609TXC* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool ws_protocol_decoder_acurite_609txc_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + WSProtocolDecoderAcurite_609TXC* instance = context; + return ws_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +bool ws_protocol_decoder_acurite_609txc_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + WSProtocolDecoderAcurite_609TXC* instance = context; + bool ret = false; + do { + if(!ws_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if(instance->generic.data_count_bit != + ws_protocol_acurite_609txc_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + ret = true; + } while(false); + return ret; +} + +void ws_protocol_decoder_acurite_609txc_get_string(void* context, FuriString* output) { + furi_assert(context); + WSProtocolDecoderAcurite_609TXC* instance = context; + furi_string_printf( + output, + "%s %dbit\r\n" + "Key:0x%lX%08lX\r\n" + "Sn:0x%lX Ch:%d Bat:%d\r\n" + "Temp:%3.1f C Hum:%d%%", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data >> 40), + (uint32_t)(instance->generic.data), + instance->generic.id, + instance->generic.channel, + instance->generic.battery_low, + (double)instance->generic.temp, + instance->generic.humidity); +} diff --git a/applications/plugins/weather_station/protocols/acurite_609txc.h b/applications/plugins/weather_station/protocols/acurite_609txc.h new file mode 100644 index 00000000000..f87c20e9b70 --- /dev/null +++ b/applications/plugins/weather_station/protocols/acurite_609txc.h @@ -0,0 +1,79 @@ +#pragma once + +#include + +#include +#include +#include +#include "ws_generic.h" +#include + +#define WS_PROTOCOL_ACURITE_609TXC_NAME "Acurite-609TXC" + +typedef struct WSProtocolDecoderAcurite_609TXC WSProtocolDecoderAcurite_609TXC; +typedef struct WSProtocolEncoderAcurite_609TXC WSProtocolEncoderAcurite_609TXC; + +extern const SubGhzProtocolDecoder ws_protocol_acurite_609txc_decoder; +extern const SubGhzProtocolEncoder ws_protocol_acurite_609txc_encoder; +extern const SubGhzProtocol ws_protocol_acurite_609txc; + +/** + * Allocate WSProtocolDecoderAcurite_609TXC. + * @param environment Pointer to a SubGhzEnvironment instance + * @return WSProtocolDecoderAcurite_609TXC* pointer to a WSProtocolDecoderAcurite_609TXC instance + */ +void* ws_protocol_decoder_acurite_609txc_alloc(SubGhzEnvironment* environment); + +/** + * Free WSProtocolDecoderAcurite_609TXC. + * @param context Pointer to a WSProtocolDecoderAcurite_609TXC instance + */ +void ws_protocol_decoder_acurite_609txc_free(void* context); + +/** + * Reset decoder WSProtocolDecoderAcurite_609TXC. + * @param context Pointer to a WSProtocolDecoderAcurite_609TXC instance + */ +void ws_protocol_decoder_acurite_609txc_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a WSProtocolDecoderAcurite_609TXC instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void ws_protocol_decoder_acurite_609txc_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a WSProtocolDecoderAcurite_609TXC instance + * @return hash Hash sum + */ +uint8_t ws_protocol_decoder_acurite_609txc_get_hash_data(void* context); + +/** + * Serialize data WSProtocolDecoderAcurite_609TXC. + * @param context Pointer to a WSProtocolDecoderAcurite_609TXC instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool ws_protocol_decoder_acurite_609txc_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data WSProtocolDecoderAcurite_609TXC. + * @param context Pointer to a WSProtocolDecoderAcurite_609TXC instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool ws_protocol_decoder_acurite_609txc_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a WSProtocolDecoderAcurite_609TXC instance + * @param output Resulting text + */ +void ws_protocol_decoder_acurite_609txc_get_string(void* context, FuriString* output); diff --git a/applications/plugins/weather_station/protocols/protocol_items.c b/applications/plugins/weather_station/protocols/protocol_items.c index d7f6458abea..d2b20e51a72 100644 --- a/applications/plugins/weather_station/protocols/protocol_items.c +++ b/applications/plugins/weather_station/protocols/protocol_items.c @@ -6,6 +6,7 @@ const SubGhzProtocol* weather_station_protocol_registry_items[] = { &ws_protocol_nexus_th, &ws_protocol_gt_wt_03, &ws_protocol_acurite_606tx, + &ws_protocol_acurite_609txc, &ws_protocol_lacrosse_tx141thbv2, &ws_protocol_oregon2, &ws_protocol_acurite_592txr, diff --git a/applications/plugins/weather_station/protocols/protocol_items.h b/applications/plugins/weather_station/protocols/protocol_items.h index 76c085ab471..45b297e10a6 100644 --- a/applications/plugins/weather_station/protocols/protocol_items.h +++ b/applications/plugins/weather_station/protocols/protocol_items.h @@ -6,6 +6,7 @@ #include "nexus_th.h" #include "gt_wt_03.h" #include "acurite_606tx.h" +#include "acurite_609txc.h" #include "lacrosse_tx141thbv2.h" #include "oregon2.h" #include "acurite_592txr.h" From 9f0aef330efc48028d74009f00e81ec16d4656d2 Mon Sep 17 00:00:00 2001 From: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> Date: Tue, 8 Nov 2022 20:38:28 +0300 Subject: [PATCH 212/824] [FL-2956] Initial unit test docs (#1984) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- documentation/UnitTests.md | 49 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 documentation/UnitTests.md diff --git a/documentation/UnitTests.md b/documentation/UnitTests.md new file mode 100644 index 00000000000..3f56a9a9003 --- /dev/null +++ b/documentation/UnitTests.md @@ -0,0 +1,49 @@ +# Unit tests +## Intro +Unit tests are special pieces of code that apply known inputs to the feature code and check the results to see if they were correct. +They are crucial for writing robust, bug-free code. + +Flipper Zero firmware includes a separate application called [unit_tests](/applications/debug/unit_tests). +It is run directly on the Flipper Zero in order to employ its hardware features and to rule out any platform-related differences. + +When contributing code to the Flipper Zero firmware, it is highly desirable to supply unit tests along with the proposed features. +Running existing unit tests is useful to ensure that the new code doesn't introduce any regressions. + +## Running unit tests +In order to run the unit tests, follow these steps: +1. Compile the firmware with the tests enabled: `./fbt FIRMWARE_APP_SET=unit_tests`. +2. Flash the firmware using your preferred method. +3. Copy the [assets/unit_tests](assets/unit_tests) folder to the root your Flipper Zero's SD card. +4. Launch the CLI session and run the `unit_tests` command. + +**NOTE:** To run a particular test (and skip all others), specify its name as the command argument. +See [test_index.c](applications/debug/unit_tests/test_index.c) for the complete list of test names. + +## Adding unit tests +### General +#### Entry point +The common entry point for all tests it the [unit_tests](applications/debug/unit_tests) application. Test-specific code is placed into an arbitrarily named subdirectory and is then called from the [test_index.c](applications/debug/unit_tests/test_index.c) source file. +#### Test assets +Some unit tests require external data in order to function. These files (commonly called assets) reside in the [assets/unit_tests](/assets/unit_tests) directory in their respective subdirectories. Asset files can be of any type (plain text, FlipperFormat(FFF), binary etc). +### Application-specific +#### Infrared +Each infrared protocol has a corresponding set of unit tests, so it makes sense to implement one when adding support for a new protocol. +In order to add unit tests for your protocol, follow these steps: +1. Create a file named `test_.irtest` in the [assets](assets/unit_tests/infrared) directory. +2. Fill it with the test data (more on it below). +3. Add the test code to [infrared_test.c](applications/debug/unit_tests/infrared/infrared_test.c). +4. Update the [assets](assets/unit_tests/infrared) on your Flipper Zero and run the tests to see if they pass. + +##### Test data format +Each unit test has 3 sections: +1. `decoder` - takes in raw signal and outputs decoded messages. +2. `encoder` - takes in decoded messages and outputs raw signal. +3. `encoder_decoder` - takes in decoded messages, turns them into raw signal and then decodes again. + +Infrared test asset files have an `.irtest` extension and are regular `.ir` files with a few additions. +Decoder input data has signal names `decoder_input_N`, where N is a test sequence number. Expected data goes under the name `decoder_expected_N`. When testing the encoder these two are switched. + +Decoded data is represented in arrays (since a single raw signal may decode to several messages). If there is only one signal, then it has to be an array of size 1. Use the existing files as syntax examples. + +##### Getting raw signals +Recording raw IR signals is possible using Flipper Zero. Launch the CLI session, run `ir rx raw`, then point the remote towards Flipper's receiver and send the signals. The raw signal data will be printed to the console in a convenient format. From c89e5e11a43589649278050b94fd7c2ef5f3b824 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Thu, 10 Nov 2022 02:33:09 +0900 Subject: [PATCH 213/824] Furi: show thread allocation balance for child threads (#1992) --- applications/services/loader/loader.c | 6 +----- furi/core/thread.c | 12 ++++++++++-- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/applications/services/loader/loader.c b/applications/services/loader/loader.c index 62dbad95fc1..712576e14a2 100644 --- a/applications/services/loader/loader.c +++ b/applications/services/loader/loader.c @@ -273,11 +273,7 @@ static void loader_thread_state_callback(FuriThreadState thread_state, void* con furi_hal_power_insomnia_enter(); } } else if(thread_state == FuriThreadStateStopped) { - FURI_LOG_I( - TAG, - "Application thread stopped. Free heap: %d. Thread allocation balance: %d.", - memmgr_get_free_heap(), - furi_thread_get_heap_size(instance->application_thread)); + FURI_LOG_I(TAG, "Application stopped. Free heap: %d", memmgr_get_free_heap()); if(loader_instance->application_arguments) { free(loader_instance->application_arguments); diff --git a/furi/core/thread.c b/furi/core/thread.c index 157e022e974..8320a47e830 100644 --- a/furi/core/thread.c +++ b/furi/core/thread.c @@ -12,6 +12,8 @@ #include #include +#define TAG "FuriThread" + #define THREAD_NOTIFY_INDEX 1 // Index 0 is used for stream buffers typedef struct FuriThreadStdout FuriThreadStdout; @@ -82,6 +84,12 @@ static void furi_thread_body(void* context) { if(thread->heap_trace_enabled == true) { furi_delay_ms(33); thread->heap_size = memmgr_heap_get_thread_memory((FuriThreadId)task_handle); + furi_log_print_format( + thread->heap_size ? FuriLogLevelError : FuriLogLevelInfo, + TAG, + "%s allocation balance: %d", + thread->name ? thread->name : "Thread", + thread->heap_size); memmgr_heap_disable_thread_trace((FuriThreadId)task_handle); } @@ -89,8 +97,8 @@ static void furi_thread_body(void* context) { if(thread->is_service) { FURI_LOG_E( - "Service", - "%s thread exited. Thread memory cannot be reclaimed.", + TAG, + "%s service thread exited. Thread memory cannot be reclaimed.", thread->name ? thread->name : ""); } From 3985b456c39c644314282ad426cb757c770236b8 Mon Sep 17 00:00:00 2001 From: gornekich Date: Wed, 9 Nov 2022 22:12:55 +0400 Subject: [PATCH 214/824] NFC: fix crash on MFC read (#1993) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * nfc: fix nfc_worker_stop logic * nfc: fix stop order Co-authored-by: あく --- lib/nfc/nfc_worker.c | 10 +++++----- lib/nfc/nfc_worker.h | 1 - 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/nfc/nfc_worker.c b/lib/nfc/nfc_worker.c index e1e379a0667..5ce543636d5 100644 --- a/lib/nfc/nfc_worker.c +++ b/lib/nfc/nfc_worker.c @@ -70,12 +70,12 @@ void nfc_worker_start( void nfc_worker_stop(NfcWorker* nfc_worker) { furi_assert(nfc_worker); - if(nfc_worker->state == NfcWorkerStateBroken || nfc_worker->state == NfcWorkerStateReady) { - return; + furi_assert(nfc_worker->thread); + if(furi_thread_get_state(nfc_worker->thread) != FuriThreadStateStopped) { + furi_hal_nfc_stop(); + nfc_worker_change_state(nfc_worker, NfcWorkerStateStop); + furi_thread_join(nfc_worker->thread); } - furi_hal_nfc_stop(); - nfc_worker_change_state(nfc_worker, NfcWorkerStateStop); - furi_thread_join(nfc_worker->thread); } void nfc_worker_change_state(NfcWorker* nfc_worker, NfcWorkerState state) { diff --git a/lib/nfc/nfc_worker.h b/lib/nfc/nfc_worker.h index ce3a1824178..ddee34c95c7 100644 --- a/lib/nfc/nfc_worker.h +++ b/lib/nfc/nfc_worker.h @@ -7,7 +7,6 @@ typedef struct NfcWorker NfcWorker; typedef enum { // Init states NfcWorkerStateNone, - NfcWorkerStateBroken, NfcWorkerStateReady, // Main worker states NfcWorkerStateRead, From a959fa32bc65620460d2bcfd593a12ddcf55def3 Mon Sep 17 00:00:00 2001 From: hedger Date: Thu, 10 Nov 2022 15:55:11 +0400 Subject: [PATCH 215/824] fbt: 'target' field for apps; lib debugging support (#1995) * fbt: added 'target' field to application manifest * fbt: earlier pagination setup for gdb * fbt: added LIB_DEBUG flag * fbt: sdk: added SDK_MAP_FILE_SUBST --- SConstruct | 9 +++------ documentation/AppManifests.md | 1 + firmware.scons | 4 ++-- scripts/fbt/appmanifest.py | 20 +++++++++++++++++--- scripts/fbt/util.py | 7 ++++++- scripts/fbt_tools/fbt_apps.py | 13 +++++++------ scripts/fbt_tools/fbt_debugopts.py | 4 ++-- scripts/fbt_tools/fbt_sdk.py | 22 ++++++++++++++-------- site_scons/commandline.scons | 5 +++++ site_scons/extapps.scons | 10 +++++++++- 10 files changed, 66 insertions(+), 29 deletions(-) diff --git a/SConstruct b/SConstruct index 3ee89f646c0..34ff80bc96a 100644 --- a/SConstruct +++ b/SConstruct @@ -7,6 +7,7 @@ # construction of certain targets behind command-line options. import os +from fbt.util import path_as_posix DefaultEnvironment(tools=[]) @@ -200,9 +201,7 @@ firmware_debug = distenv.PhonyTarget( source=firmware_env["FW_ELF"], GDBOPTS="${GDBOPTS_BASE}", GDBREMOTE="${OPENOCD_GDB_PIPE}", - FBT_FAP_DEBUG_ELF_ROOT=firmware_env.subst("$FBT_FAP_DEBUG_ELF_ROOT").replace( - "\\", "/" - ), + FBT_FAP_DEBUG_ELF_ROOT=path_as_posix(firmware_env.subst("$FBT_FAP_DEBUG_ELF_ROOT")), ) distenv.Depends(firmware_debug, firmware_flash) @@ -212,9 +211,7 @@ distenv.PhonyTarget( source=firmware_env["FW_ELF"], GDBOPTS="${GDBOPTS_BASE} ${GDBOPTS_BLACKMAGIC}", GDBREMOTE="${BLACKMAGIC_ADDR}", - FBT_FAP_DEBUG_ELF_ROOT=firmware_env.subst("$FBT_FAP_DEBUG_ELF_ROOT").replace( - "\\", "/" - ), + FBT_FAP_DEBUG_ELF_ROOT=path_as_posix(firmware_env.subst("$FBT_FAP_DEBUG_ELF_ROOT")), ) # Debug alien elf diff --git a/documentation/AppManifests.md b/documentation/AppManifests.md index f4814ee5daf..d70a12f9c9e 100644 --- a/documentation/AppManifests.md +++ b/documentation/AppManifests.md @@ -40,6 +40,7 @@ Only 2 parameters are mandatory: ***appid*** and ***apptype***, others are optio * **icon**: Animated icon name from built-in assets to be used when building app as a part of firmware. * **order**: Order of an application within its group when sorting entries in it. The lower the order is, the closer to the start of the list the item is placed. *Used for ordering startup hooks and menu entries.* * **sdk_headers**: List of C header files from this app's code to include in API definitions for external applications. +* **targets**: list of strings, target names, which this application is compatible with. If not specified, application is built for all targets. Default value is `["all"]`. #### Parameters for external applications diff --git a/firmware.scons b/firmware.scons index 6a92374778e..da5caba5391 100644 --- a/firmware.scons +++ b/firmware.scons @@ -40,11 +40,11 @@ env = ENV.Clone( FW_LIB_OPTS={ "Default": { "CCFLAGS": [ - "-Os", + "-Og" if ENV["LIB_DEBUG"] else "-Os", ], "CPPDEFINES": [ "NDEBUG", - "FURI_NDEBUG", + "FURI_DEBUG" if ENV["LIB_DEBUG"] else "FURI_NDEBUG", ], # You can add other entries named after libraries # If they are present, they have precedence over Default diff --git a/scripts/fbt/appmanifest.py b/scripts/fbt/appmanifest.py index de7c6b68205..908a64a6fee 100644 --- a/scripts/fbt/appmanifest.py +++ b/scripts/fbt/appmanifest.py @@ -52,6 +52,8 @@ class Library: icon: Optional[str] = None order: int = 0 sdk_headers: List[str] = field(default_factory=list) + targets: List[str] = field(default_factory=lambda: ["all"]) + # .fap-specific sources: List[str] = field(default_factory=lambda: ["*.c*"]) fap_version: Tuple[int] = field(default_factory=lambda: (0, 1)) @@ -135,8 +137,8 @@ def _add_known_app(self, app: FlipperApplication): raise FlipperManifestException(f"Duplicate app declaration: {app.appid}") self.known_apps[app.appid] = app - def filter_apps(self, applist: List[str]): - return AppBuildset(self, applist) + def filter_apps(self, applist: List[str], hw_target: str): + return AppBuildset(self, applist, hw_target) class AppBuilderException(Exception): @@ -155,11 +157,13 @@ class AppBuildset: FlipperAppType.STARTUP, ) - def __init__(self, appmgr: AppManager, appnames: List[str]): + def __init__(self, appmgr: AppManager, appnames: List[str], hw_target: str): self.appmgr = appmgr self.appnames = set(appnames) + self.hw_target = hw_target self._orig_appnames = appnames self._process_deps() + self._filter_by_target() self._check_conflicts() self._check_unsatisfied() # unneeded? self.apps = sorted( @@ -170,6 +174,16 @@ def __init__(self, appmgr: AppManager, appnames: List[str]): def _is_missing_dep(self, dep_name: str): return dep_name not in self.appnames + def _filter_by_target(self): + for appname in self.appnames.copy(): + app = self.appmgr.get(appname) + # if app.apptype not in self.BUILTIN_APP_TYPES: + if not any(map(lambda t: t in app.targets, ["all", self.hw_target])): + print( + f"Removing {appname} due to target mismatch (building for {self.hw_target}, app supports {app.targets}" + ) + self.appnames.remove(appname) + def _process_deps(self): while True: provided = [] diff --git a/scripts/fbt/util.py b/scripts/fbt/util.py index b8e9c5928f4..ee756205882 100644 --- a/scripts/fbt/util.py +++ b/scripts/fbt/util.py @@ -1,7 +1,6 @@ import SCons from SCons.Subst import quote_spaces from SCons.Errors import StopError -from SCons.Node.FS import _my_normcase import re import os @@ -58,3 +57,9 @@ def extract_abs_dir_path(node): raise StopError(f"Can't find absolute path for {node.name}") return abs_dir_node.abspath + + +def path_as_posix(path): + if SCons.Platform.platform_default() == "win32": + return path.replace(os.path.sep, os.path.altsep) + return path diff --git a/scripts/fbt_tools/fbt_apps.py b/scripts/fbt_tools/fbt_apps.py index 55e282017fe..96528f4e57b 100644 --- a/scripts/fbt_tools/fbt_apps.py +++ b/scripts/fbt_tools/fbt_apps.py @@ -1,7 +1,6 @@ from SCons.Builder import Builder from SCons.Action import Action from SCons.Warnings import warn, WarningOnByDefault -import SCons from ansi.color import fg from fbt.appmanifest import ( @@ -33,14 +32,12 @@ def LoadAppManifest(env, entry): def PrepareApplicationsBuild(env): - appbuild = env["APPBUILD"] = env["APPMGR"].filter_apps(env["APPS"]) + appbuild = env["APPBUILD"] = env["APPMGR"].filter_apps( + env["APPS"], env.subst("f${TARGET_HW}") + ) env.Append( SDK_HEADERS=appbuild.get_sdk_headers(), ) - env["APPBUILD_DUMP"] = env.Action( - DumpApplicationConfig, - "\tINFO\t", - ) def DumpApplicationConfig(target, source, env): @@ -68,6 +65,10 @@ def generate(env): env.AddMethod(PrepareApplicationsBuild) env.SetDefault( APPMGR=AppManager(), + APPBUILD_DUMP=env.Action( + DumpApplicationConfig, + "\tINFO\t", + ), ) env.Append( diff --git a/scripts/fbt_tools/fbt_debugopts.py b/scripts/fbt_tools/fbt_debugopts.py index c3be5ca477a..9ff05cb735d 100644 --- a/scripts/fbt_tools/fbt_debugopts.py +++ b/scripts/fbt_tools/fbt_debugopts.py @@ -41,12 +41,12 @@ def generate(env, **kw): "|openocd -c 'gdb_port pipe; log_output ${FBT_DEBUG_DIR}/openocd.log' ${[SINGLEQUOTEFUNC(OPENOCD_OPTS)]}" ], GDBOPTS_BASE=[ + "-ex", + "set pagination off", "-ex", "target extended-remote ${GDBREMOTE}", "-ex", "set confirm off", - "-ex", - "set pagination off", ], GDBOPTS_BLACKMAGIC=[ "-ex", diff --git a/scripts/fbt_tools/fbt_sdk.py b/scripts/fbt_tools/fbt_sdk.py index c46346b650f..3a37eacc972 100644 --- a/scripts/fbt_tools/fbt_sdk.py +++ b/scripts/fbt_tools/fbt_sdk.py @@ -14,6 +14,7 @@ from fbt.sdk.collector import SdkCollector from fbt.sdk.cache import SdkCache +from fbt.util import path_as_posix def ProcessSdkDepends(env, filename): @@ -52,6 +53,8 @@ def prebuild_sdk_create_origin_file(target, source, env): class SdkMeta: + MAP_FILE_SUBST = "SDK_MAP_FILE_SUBST" + def __init__(self, env, tree_builder: "SdkTreeBuilder"): self.env = env self.treebuilder = tree_builder @@ -67,6 +70,7 @@ def save_to(self, json_manifest_path: str): "linker_libs": self.env.subst("${LIBS}"), "app_ep_subst": self.env.subst("${APP_ENTRY}"), "sdk_path_subst": self.env.subst("${SDK_DIR_SUBST}"), + "map_file_subst": self.MAP_FILE_SUBST, "hardware": self.env.subst("${TARGET_HW}"), } with open(json_manifest_path, "wt") as f: @@ -75,9 +79,9 @@ def save_to(self, json_manifest_path: str): def _wrap_scons_vars(self, vars: str): expanded_vars = self.env.subst( vars, - target=Entry("dummy"), + target=Entry(self.MAP_FILE_SUBST), ) - return expanded_vars.replace("\\", "/") + return path_as_posix(expanded_vars) class SdkTreeBuilder: @@ -142,13 +146,15 @@ def _generate_sdk_meta(self): meta.save_to(self.target[0].path) def build_sdk_file_path(self, orig_path: str) -> str: - return posixpath.normpath( - posixpath.join( - self.SDK_DIR_SUBST, - self.target_sdk_dir_name, - orig_path, + return path_as_posix( + posixpath.normpath( + posixpath.join( + self.SDK_DIR_SUBST, + self.target_sdk_dir_name, + orig_path, + ) ) - ).replace("\\", "/") + ) def emitter(self, target, source, env): target_folder = target[0] diff --git a/site_scons/commandline.scons b/site_scons/commandline.scons index 044de6b3039..6087c1c7add 100644 --- a/site_scons/commandline.scons +++ b/site_scons/commandline.scons @@ -63,6 +63,11 @@ vars.AddVariables( help="Enable debug build", default=True, ), + BoolVariable( + "LIB_DEBUG", + help="Enable debug build for libraries", + default=False, + ), BoolVariable( "COMPACT", help="Optimize for size", diff --git a/site_scons/extapps.scons b/site_scons/extapps.scons index 670d71fd0e9..b8f210563cd 100644 --- a/site_scons/extapps.scons +++ b/site_scons/extapps.scons @@ -1,6 +1,6 @@ from dataclasses import dataclass, field -from SCons.Errors import UserError from SCons.Node import NodeList +from SCons.Warnings import warn, WarningOnByDefault Import("ENV") @@ -80,6 +80,14 @@ if extra_app_list := GetOption("extra_ext_apps"): known_extapps.extend(map(appenv["APPMGR"].get, extra_app_list.split(","))) for app in known_extapps: + if not any(map(lambda t: t in app.targets, ["all", appenv.subst("f${TARGET_HW}")])): + warn( + WarningOnByDefault, + f"Can't build '{app.name}' (id '{app.appid}'): target mismatch" + f" (building for {appenv.subst('f${TARGET_HW}')}, app supports {app.targets}", + ) + continue + appenv.BuildAppElf(app) From f94e8f4ac8978118489f1086fa2c979995f71964 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Thu, 10 Nov 2022 22:56:08 +0900 Subject: [PATCH 216/824] Rpc: increase stack size, fix stack overflow (#1997) --- applications/services/rpc/rpc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/services/rpc/rpc.c b/applications/services/rpc/rpc.c index 73eaadfb15a..f1e0cbd6687 100644 --- a/applications/services/rpc/rpc.c +++ b/applications/services/rpc/rpc.c @@ -372,7 +372,7 @@ RpcSession* rpc_session_open(Rpc* rpc) { session->thread = furi_thread_alloc(); furi_thread_set_name(session->thread, "RpcSessionWorker"); - furi_thread_set_stack_size(session->thread, 2048); + furi_thread_set_stack_size(session->thread, 3072); furi_thread_set_context(session->thread, session); furi_thread_set_callback(session->thread, rpc_session_worker); From a66e8d9ac98d1845714f28d60e3c63f2584ef0b9 Mon Sep 17 00:00:00 2001 From: Rom1 Date: Thu, 10 Nov 2022 16:21:28 +0100 Subject: [PATCH 217/824] corr: bad path for furi core (#1975) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * corr: bad path for furi core * Documentation: exclude submodules * Documentation: wider folder list Co-authored-by: Sergey Gavrilov Co-authored-by: あく --- documentation/.gitignore | 2 ++ documentation/Doxyfile | 22 +++++++++++++++------- 2 files changed, 17 insertions(+), 7 deletions(-) create mode 100644 documentation/.gitignore diff --git a/documentation/.gitignore b/documentation/.gitignore new file mode 100644 index 00000000000..c18ff03bbb8 --- /dev/null +++ b/documentation/.gitignore @@ -0,0 +1,2 @@ +/html +/latex \ No newline at end of file diff --git a/documentation/Doxyfile b/documentation/Doxyfile index 6d6bb8aa8cf..1824e5a5258 100644 --- a/documentation/Doxyfile +++ b/documentation/Doxyfile @@ -872,12 +872,9 @@ WARN_LOGFILE = # Note: If this tag is empty the current directory is searched. INPUT = applications \ - core \ - lib/infrared \ - lib/subghz \ - lib/toolbox \ - lib/onewire \ - firmware/targets/furi_hal_include + lib \ + firmware \ + furi # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses @@ -930,7 +927,18 @@ RECURSIVE = YES # Note that relative paths are relative to the directory from which doxygen is # run. -EXCLUDE = +EXCLUDE = \ + lib/mlib \ + lib/STM32CubeWB \ + lib/littlefs \ + lib/nanopb \ + assets/protobuf \ + lib/libusb_stm32 \ + lib/FreeRTOS-Kernel \ + lib/microtar \ + lib/mbedtls \ + lib/cxxheaderparser \ + applications/plugins/dap_link/lib/free-dap # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded From 820afd2aec377629c3e514ceb57ecdd0f9163b6c Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Thu, 10 Nov 2022 18:20:35 +0200 Subject: [PATCH 218/824] NFC Unit tests part 1.1 (#1927) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Mifare Classic 1/4K, 4/7b uid, NFC-A: NFC-A is not complete yet, as there are no 4b uid tests. Also, Mifare Classic tests don't cover the key cache yet. * NFC unit tests require access to the NFC app * Made nfc_device_save accept full path as an argument * Move from cstrs to furi strings and fix logic * nfc tests: fix memory leak * nfc: add mf_classic_get_total_blocks() to API * nfc tests: simplify nfc tests * nfc: fix memory leak in shadow file saving * nfc: fix set uid scene * nfc: fix saving files * nfc: fix preload nfc file path * nfc: remove comments Co-authored-by: Sergey Gavrilov Co-authored-by: gornekich Co-authored-by: あく --- applications/debug/unit_tests/nfc/nfc_test.c | 197 ++++++++++++++++++ .../main/nfc/helpers/nfc_generators.c | 7 +- .../main/nfc/helpers/nfc_generators.h | 2 + applications/main/nfc/nfc.c | 11 +- applications/main/nfc/nfc_i.h | 2 + .../main/nfc/scenes/nfc_scene_file_select.c | 3 + .../nfc/scenes/nfc_scene_mf_classic_emulate.c | 5 +- .../nfc/scenes/nfc_scene_mf_classic_update.c | 2 +- .../scenes/nfc_scene_mf_ultralight_emulate.c | 5 +- .../main/nfc/scenes/nfc_scene_save_name.c | 2 +- .../main/nfc/scenes/nfc_scene_set_uid.c | 3 +- fbt_options.py | 1 + firmware/targets/f7/api_symbols.csv | 3 +- lib/nfc/nfc_device.c | 44 ++-- lib/nfc/protocols/mifare_classic.c | 2 +- lib/nfc/protocols/mifare_classic.h | 2 + 16 files changed, 256 insertions(+), 35 deletions(-) diff --git a/applications/debug/unit_tests/nfc/nfc_test.c b/applications/debug/unit_tests/nfc/nfc_test.c index 8009f6a17be..454c11c0f62 100644 --- a/applications/debug/unit_tests/nfc/nfc_test.c +++ b/applications/debug/unit_tests/nfc/nfc_test.c @@ -5,6 +5,8 @@ #include #include #include +#include +#include #include #include @@ -17,6 +19,7 @@ #define NFC_TEST_SIGNAL_SHORT_FILE "nfc_nfca_signal_short.nfc" #define NFC_TEST_SIGNAL_LONG_FILE "nfc_nfca_signal_long.nfc" #define NFC_TEST_DICT_PATH EXT_PATH("unit_tests/mf_classic_dict.nfc") +#define NFC_TEST_NFC_DEV_PATH EXT_PATH("unit_tests/nfc/nfc_dev_test.nfc") static const char* nfc_test_file_type = "Flipper NFC test"; static const uint32_t nfc_test_file_version = 1; @@ -287,9 +290,203 @@ MU_TEST(mf_classic_dict_load_test) { furi_record_close(RECORD_STORAGE); } +MU_TEST(nfca_file_test) { + NfcDevice* nfc = nfc_device_alloc(); + mu_assert(nfc != NULL, "nfc_device_data != NULL assert failed\r\n"); + nfc->format = NfcDeviceSaveFormatUid; + + // Fill the UID, sak, ATQA and type + uint8_t uid[7] = {0x04, 0x01, 0x23, 0x45, 0x67, 0x89, 0x00}; + memcpy(nfc->dev_data.nfc_data.uid, uid, 7); + nfc->dev_data.nfc_data.uid_len = 7; + + nfc->dev_data.nfc_data.sak = 0x08; + nfc->dev_data.nfc_data.atqa[0] = 0x00; + nfc->dev_data.nfc_data.atqa[1] = 0x04; + nfc->dev_data.nfc_data.type = FuriHalNfcTypeA; + + // Save the NFC device data to the file + mu_assert( + nfc_device_save(nfc, NFC_TEST_NFC_DEV_PATH), "nfc_device_save == true assert failed\r\n"); + nfc_device_free(nfc); + + // Load the NFC device data from the file + NfcDevice* nfc_validate = nfc_device_alloc(); + mu_assert( + nfc_device_load(nfc_validate, NFC_TEST_NFC_DEV_PATH, true), + "nfc_device_load == true assert failed\r\n"); + + // Check the UID, sak, ATQA and type + mu_assert(memcmp(nfc_validate->dev_data.nfc_data.uid, uid, 7) == 0, "uid assert failed\r\n"); + mu_assert(nfc_validate->dev_data.nfc_data.sak == 0x08, "sak == 0x08 assert failed\r\n"); + mu_assert( + nfc_validate->dev_data.nfc_data.atqa[0] == 0x00, "atqa[0] == 0x00 assert failed\r\n"); + mu_assert( + nfc_validate->dev_data.nfc_data.atqa[1] == 0x04, "atqa[1] == 0x04 assert failed\r\n"); + mu_assert( + nfc_validate->dev_data.nfc_data.type == FuriHalNfcTypeA, + "type == FuriHalNfcTypeA assert failed\r\n"); + nfc_device_free(nfc_validate); +} + +static void mf_classic_generator_test(uint8_t uid_len, MfClassicType type) { + NfcDevice* nfc_dev = nfc_device_alloc(); + mu_assert(nfc_dev != NULL, "nfc_device_data != NULL assert failed\r\n"); + nfc_dev->format = NfcDeviceSaveFormatMifareClassic; + + // Create a test file + nfc_generate_mf_classic(&nfc_dev->dev_data, uid_len, type); + + // Get the uid from generated MFC + uint8_t uid[7] = {0}; + memcpy(uid, nfc_dev->dev_data.nfc_data.uid, uid_len); + uint8_t sak = nfc_dev->dev_data.nfc_data.sak; + uint8_t atqa[2] = {}; + memcpy(atqa, nfc_dev->dev_data.nfc_data.atqa, 2); + + MfClassicData* mf_data = &nfc_dev->dev_data.mf_classic_data; + // Check the manufacturer block (should be uid[uid_len] + 0xFF[rest]) + uint8_t manufacturer_block[16] = {0}; + memcpy(manufacturer_block, nfc_dev->dev_data.mf_classic_data.block[0].value, 16); + mu_assert( + memcmp(manufacturer_block, uid, uid_len) == 0, + "manufacturer_block uid doesn't match the file\r\n"); + for(uint8_t i = uid_len; i < 16; i++) { + mu_assert( + manufacturer_block[i] == 0xFF, "manufacturer_block[i] == 0xFF assert failed\r\n"); + } + + // Reference sector trailers (should be 0xFF[6] + 0xFF + 0x07 + 0x80 + 0x69 + 0xFF[6]) + uint8_t sector_trailer[16] = { + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0x07, + 0x80, + 0x69, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF}; + // Reference block data + uint8_t block_data[16] = {}; + memset(block_data, 0xff, sizeof(block_data)); + uint16_t total_blocks = mf_classic_get_total_block_num(type); + for(size_t i = 1; i < total_blocks; i++) { + if(mf_classic_is_sector_trailer(i)) { + mu_assert( + memcmp(mf_data->block[i].value, sector_trailer, 16) == 0, + "Failed sector trailer compare"); + } else { + mu_assert(memcmp(mf_data->block[i].value, block_data, 16) == 0, "Failed data compare"); + } + } + // Save the NFC device data to the file + mu_assert( + nfc_device_save(nfc_dev, NFC_TEST_NFC_DEV_PATH), + "nfc_device_save == true assert failed\r\n"); + // Verify that key cache is saved + FuriString* key_cache_name = furi_string_alloc(); + furi_string_set_str(key_cache_name, "/ext/nfc/cache/"); + for(size_t i = 0; i < uid_len; i++) { + furi_string_cat_printf(key_cache_name, "%02X", uid[i]); + } + furi_string_cat_printf(key_cache_name, ".keys"); + mu_assert( + storage_common_stat(nfc_dev->storage, furi_string_get_cstr(key_cache_name), NULL) == + FSE_OK, + "Key cache file save failed"); + nfc_device_free(nfc_dev); + + // Load the NFC device data from the file + NfcDevice* nfc_validate = nfc_device_alloc(); + mu_assert(nfc_validate, "Nfc device alloc assert"); + mu_assert( + nfc_device_load(nfc_validate, NFC_TEST_NFC_DEV_PATH, false), + "nfc_device_load == true assert failed\r\n"); + + // Check the UID, sak, ATQA and type + mu_assert( + memcmp(nfc_validate->dev_data.nfc_data.uid, uid, uid_len) == 0, + "uid compare assert failed\r\n"); + mu_assert(nfc_validate->dev_data.nfc_data.sak == sak, "sak compare assert failed\r\n"); + mu_assert( + memcmp(nfc_validate->dev_data.nfc_data.atqa, atqa, 2) == 0, + "atqa compare assert failed\r\n"); + mu_assert( + nfc_validate->dev_data.nfc_data.type == FuriHalNfcTypeA, + "type == FuriHalNfcTypeA assert failed\r\n"); + + // Check the manufacturer block + mu_assert( + memcmp(nfc_validate->dev_data.mf_classic_data.block[0].value, manufacturer_block, 16) == 0, + "manufacturer_block assert failed\r\n"); + // Check other blocks + for(size_t i = 1; i < total_blocks; i++) { + if(mf_classic_is_sector_trailer(i)) { + mu_assert( + memcmp(mf_data->block[i].value, sector_trailer, 16) == 0, + "Failed sector trailer compare"); + } else { + mu_assert(memcmp(mf_data->block[i].value, block_data, 16) == 0, "Failed data compare"); + } + } + nfc_device_free(nfc_validate); + + // Check saved key cache + NfcDevice* nfc_keys = nfc_device_alloc(); + mu_assert(nfc_validate, "Nfc device alloc assert"); + nfc_keys->dev_data.nfc_data.uid_len = uid_len; + memcpy(nfc_keys->dev_data.nfc_data.uid, uid, uid_len); + mu_assert(nfc_device_load_key_cache(nfc_keys), "Failed to load key cache"); + uint8_t total_sec = mf_classic_get_total_sectors_num(type); + uint8_t default_key[6] = {}; + memset(default_key, 0xff, 6); + for(size_t i = 0; i < total_sec; i++) { + MfClassicSectorTrailer* sec_tr = + mf_classic_get_sector_trailer_by_sector(&nfc_keys->dev_data.mf_classic_data, i); + mu_assert(memcmp(sec_tr->key_a, default_key, 6) == 0, "Failed key compare"); + mu_assert(memcmp(sec_tr->key_b, default_key, 6) == 0, "Failed key compare"); + } + + // Delete key cache file + mu_assert( + storage_common_remove(nfc_keys->storage, furi_string_get_cstr(key_cache_name)) == FSE_OK, + "Failed to remove key cache file"); + furi_string_free(key_cache_name); + nfc_device_free(nfc_keys); +} + +MU_TEST(mf_classic_1k_4b_file_test) { + mf_classic_generator_test(4, MfClassicType1k); +} + +MU_TEST(mf_classic_4k_4b_file_test) { + mf_classic_generator_test(4, MfClassicType4k); +} + +MU_TEST(mf_classic_1k_7b_file_test) { + mf_classic_generator_test(7, MfClassicType1k); +} + +MU_TEST(mf_classic_4k_7b_file_test) { + mf_classic_generator_test(7, MfClassicType4k); +} + MU_TEST_SUITE(nfc) { nfc_test_alloc(); + MU_RUN_TEST(nfca_file_test); + MU_RUN_TEST(mf_classic_1k_4b_file_test); + MU_RUN_TEST(mf_classic_4k_4b_file_test); + MU_RUN_TEST(mf_classic_1k_7b_file_test); + MU_RUN_TEST(mf_classic_4k_7b_file_test); MU_RUN_TEST(nfc_digital_signal_test); MU_RUN_TEST(mf_classic_dict_test); MU_RUN_TEST(mf_classic_dict_load_test); diff --git a/applications/main/nfc/helpers/nfc_generators.c b/applications/main/nfc/helpers/nfc_generators.c index 11083b9f0ae..5f0527c6a50 100644 --- a/applications/main/nfc/helpers/nfc_generators.c +++ b/applications/main/nfc/helpers/nfc_generators.c @@ -314,7 +314,7 @@ static void nfc_generate_ntag_i2c_plus_2k(NfcDeviceData* data) { mful->version.storage_size = 0x15; } -static void nfc_generate_mf_classic(NfcDeviceData* data, uint8_t uid_len, MfClassicType type) { +void nfc_generate_mf_classic(NfcDeviceData* data, uint8_t uid_len, MfClassicType type) { nfc_generate_common_start(data); nfc_generate_mf_classic_common(data, uid_len, type); @@ -337,6 +337,9 @@ static void nfc_generate_mf_classic(NfcDeviceData* data, uint8_t uid_len, MfClas } mf_classic_set_block_read(mfc, i, &mfc->block[i]); } + // Set SAK to 18 + data->nfc_data.sak = 0x18; + } else if(type == MfClassicType1k) { // Set every block to 0xFF for(uint16_t i = 1; i < MF_CLASSIC_1K_TOTAL_SECTORS_NUM * 4; i += 1) { @@ -347,6 +350,8 @@ static void nfc_generate_mf_classic(NfcDeviceData* data, uint8_t uid_len, MfClas } mf_classic_set_block_read(mfc, i, &mfc->block[i]); } + // Set SAK to 08 + data->nfc_data.sak = 0x08; } mfc->type = type; diff --git a/applications/main/nfc/helpers/nfc_generators.h b/applications/main/nfc/helpers/nfc_generators.h index 10a05591b1b..362a19b1ee8 100644 --- a/applications/main/nfc/helpers/nfc_generators.h +++ b/applications/main/nfc/helpers/nfc_generators.h @@ -11,3 +11,5 @@ struct NfcGenerator { }; extern const NfcGenerator* const nfc_generators[]; + +void nfc_generate_mf_classic(NfcDeviceData* data, uint8_t uid_len, MfClassicType type); diff --git a/applications/main/nfc/nfc.c b/applications/main/nfc/nfc.c index 55c68a450fa..0dd071bc48e 100644 --- a/applications/main/nfc/nfc.c +++ b/applications/main/nfc/nfc.c @@ -116,7 +116,9 @@ void nfc_free(Nfc* nfc) { // Stop worker nfc_worker_stop(nfc->worker); // Save data in shadow file - nfc_device_save_shadow(nfc->dev, nfc->dev->dev_name); + if(furi_string_size(nfc->dev->load_path)) { + nfc_device_save_shadow(nfc->dev, furi_string_get_cstr(nfc->dev->load_path)); + } } if(nfc->rpc_ctx) { rpc_system_app_send_exited(nfc->rpc_ctx); @@ -218,6 +220,13 @@ void nfc_blink_stop(Nfc* nfc) { notification_message(nfc->notifications, &sequence_blink_stop); } +bool nfc_save_file(Nfc* nfc) { + furi_string_printf( + nfc->dev->load_path, "%s/%s%s", NFC_APP_FOLDER, nfc->dev->dev_name, NFC_APP_EXTENSION); + bool file_saved = nfc_device_save(nfc->dev, furi_string_get_cstr(nfc->dev->load_path)); + return file_saved; +} + void nfc_show_loading_popup(void* context, bool show) { Nfc* nfc = context; TaskHandle_t timer_task = xTaskGetHandle(configTIMER_SERVICE_TASK_NAME); diff --git a/applications/main/nfc/nfc_i.h b/applications/main/nfc/nfc_i.h index e9b36a3e990..57eefbf676a 100644 --- a/applications/main/nfc/nfc_i.h +++ b/applications/main/nfc/nfc_i.h @@ -114,4 +114,6 @@ void nfc_blink_detect_start(Nfc* nfc); void nfc_blink_stop(Nfc* nfc); +bool nfc_save_file(Nfc* nfc); + void nfc_show_loading_popup(void* context, bool show); diff --git a/applications/main/nfc/scenes/nfc_scene_file_select.c b/applications/main/nfc/scenes/nfc_scene_file_select.c index 693fdec2052..374a933d1c7 100644 --- a/applications/main/nfc/scenes/nfc_scene_file_select.c +++ b/applications/main/nfc/scenes/nfc_scene_file_select.c @@ -5,6 +5,9 @@ void nfc_scene_file_select_on_enter(void* context) { Nfc* nfc = context; // Process file_select return nfc_device_set_loading_callback(nfc->dev, nfc_show_loading_popup, nfc); + if(!furi_string_size(nfc->dev->load_path)) { + furi_string_set_str(nfc->dev->load_path, NFC_APP_FOLDER); + } if(nfc_file_select(nfc->dev)) { scene_manager_set_scene_state(nfc->scene_manager, NfcSceneSavedMenu, 0); scene_manager_next_scene(nfc->scene_manager, NfcSceneSavedMenu); diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_emulate.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_emulate.c index e514fa7280c..68eda70406c 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_emulate.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_emulate.c @@ -48,7 +48,10 @@ bool nfc_scene_mf_classic_emulate_on_event(void* context, SceneManagerEvent even NFC_MF_CLASSIC_DATA_CHANGED) { scene_manager_set_scene_state( nfc->scene_manager, NfcSceneMfClassicEmulate, NFC_MF_CLASSIC_DATA_NOT_CHANGED); - nfc_device_save_shadow(nfc->dev, nfc->dev->dev_name); + // Save shadow file + if(furi_string_size(nfc->dev->load_path)) { + nfc_device_save_shadow(nfc->dev, furi_string_get_cstr(nfc->dev->load_path)); + } } consumed = false; } diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_update.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_update.c index dd3a6f7d597..aacf77f773f 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_update.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_update.c @@ -57,7 +57,7 @@ bool nfc_scene_mf_classic_update_on_event(void* context, SceneManagerEvent event if(event.type == SceneManagerEventTypeCustom) { if(event.event == NfcWorkerEventSuccess) { nfc_worker_stop(nfc->worker); - if(nfc_device_save_shadow(nfc->dev, nfc->dev->dev_name)) { + if(nfc_device_save_shadow(nfc->dev, furi_string_get_cstr(nfc->dev->load_path))) { scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicUpdateSuccess); } else { scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicWrongCard); diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_emulate.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_emulate.c index e84fb392767..adcadaaf26f 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_emulate.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_emulate.c @@ -48,7 +48,10 @@ bool nfc_scene_mf_ultralight_emulate_on_event(void* context, SceneManagerEvent e NFC_MF_UL_DATA_CHANGED) { scene_manager_set_scene_state( nfc->scene_manager, NfcSceneMfUltralightEmulate, NFC_MF_UL_DATA_NOT_CHANGED); - nfc_device_save_shadow(nfc->dev, nfc->dev->dev_name); + // Save shadow file + if(furi_string_size(nfc->dev->load_path)) { + nfc_device_save_shadow(nfc->dev, furi_string_get_cstr(nfc->dev->load_path)); + } } consumed = false; } diff --git a/applications/main/nfc/scenes/nfc_scene_save_name.c b/applications/main/nfc/scenes/nfc_scene_save_name.c index 791f8d99b70..ca4b1a350f9 100644 --- a/applications/main/nfc/scenes/nfc_scene_save_name.c +++ b/applications/main/nfc/scenes/nfc_scene_save_name.c @@ -62,7 +62,7 @@ bool nfc_scene_save_name_on_event(void* context, SceneManagerEvent event) { nfc->dev->dev_data.nfc_data = nfc->dev_edit_data; } strlcpy(nfc->dev->dev_name, nfc->text_store, strlen(nfc->text_store) + 1); - if(nfc_device_save(nfc->dev, nfc->text_store)) { + if(nfc_save_file(nfc)) { scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveSuccess); if(!scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSavedMenu)) { // Nothing, do not count editing as saving diff --git a/applications/main/nfc/scenes/nfc_scene_set_uid.c b/applications/main/nfc/scenes/nfc_scene_set_uid.c index 1d3fb5eb999..5f0f52f6ef4 100644 --- a/applications/main/nfc/scenes/nfc_scene_set_uid.c +++ b/applications/main/nfc/scenes/nfc_scene_set_uid.c @@ -31,7 +31,7 @@ bool nfc_scene_set_uid_on_event(void* context, SceneManagerEvent event) { if(event.event == NfcCustomEventByteInputDone) { if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSavedMenu)) { nfc->dev->dev_data.nfc_data = nfc->dev_edit_data; - if(nfc_device_save(nfc->dev, nfc->dev->dev_name)) { + if(nfc_save_file(nfc)) { scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveSuccess); consumed = true; } @@ -41,6 +41,7 @@ bool nfc_scene_set_uid_on_event(void* context, SceneManagerEvent event) { } } } + return consumed; } diff --git a/fbt_options.py b/fbt_options.py index 11124b93698..a00f7c1b680 100644 --- a/fbt_options.py +++ b/fbt_options.py @@ -81,6 +81,7 @@ "basic_services", "updater_app", "unit_tests", + "nfc", ], } diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 0c10258f741..d6e522a2b44 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,7.3,, +Version,+,7.4,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -1844,6 +1844,7 @@ Function,-,mf_classic_get_read_sectors_and_keys,void,"MfClassicData*, uint8_t*, Function,-,mf_classic_get_sector_by_block,uint8_t,uint8_t Function,-,mf_classic_get_sector_trailer_block_num_by_sector,uint8_t,uint8_t Function,-,mf_classic_get_sector_trailer_by_sector,MfClassicSectorTrailer*,"MfClassicData*, uint8_t" +Function,-,mf_classic_get_total_block_num,uint16_t,MfClassicType Function,-,mf_classic_get_total_sectors_num,uint8_t,MfClassicType Function,-,mf_classic_get_type_str,const char*,MfClassicType Function,-,mf_classic_is_allowed_access_data_block,_Bool,"MfClassicData*, uint8_t, MfClassicKey, MfClassicAction" diff --git a/lib/nfc/nfc_device.c b/lib/nfc/nfc_device.c index a5e3fc14f20..9f17aed3ab6 100644 --- a/lib/nfc/nfc_device.c +++ b/lib/nfc/nfc_device.c @@ -1006,12 +1006,7 @@ static void nfc_device_get_shadow_path(FuriString* orig_path, FuriString* shadow furi_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, - bool use_load_path) { +bool nfc_device_save(NfcDevice* dev, const char* dev_name) { furi_assert(dev); bool saved = false; @@ -1021,19 +1016,10 @@ static bool nfc_device_save_file( temp_str = furi_string_alloc(); do { - if(use_load_path && !furi_string_empty(dev->load_path)) { - // Get directory name - path_extract_dirname(furi_string_get_cstr(dev->load_path), temp_str); - // Create nfc directory if necessary - if(!storage_simply_mkdir(dev->storage, furi_string_get_cstr(temp_str))) break; - // Make path to file to save - furi_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 - furi_string_printf(temp_str, "%s/%s%s", folder, dev_name, extension); - } + // Create nfc directory if necessary + if(!storage_simply_mkdir(dev->storage, NFC_APP_FOLDER)) break; + // First remove nfc device file if it was saved + furi_string_printf(temp_str, "%s", dev_name); // Open file if(!flipper_format_file_open_always(file, furi_string_get_cstr(temp_str))) break; // Write header @@ -1072,13 +1058,19 @@ static bool nfc_device_save_file( return saved; } -bool nfc_device_save(NfcDevice* dev, const char* dev_name) { - 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) { +bool nfc_device_save_shadow(NfcDevice* dev, const char* path) { dev->shadow_file_exist = true; - return nfc_device_save_file(dev, dev_name, NFC_APP_FOLDER, NFC_APP_SHADOW_EXTENSION, true); + // Replace extension from .nfc to .shd if necessary + FuriString* orig_path = furi_string_alloc(); + furi_string_set_str(orig_path, path); + FuriString* shadow_path = furi_string_alloc(); + nfc_device_get_shadow_path(orig_path, shadow_path); + + bool file_saved = nfc_device_save(dev, furi_string_get_cstr(shadow_path)); + furi_string_free(orig_path); + furi_string_free(shadow_path); + + return file_saved; } static bool nfc_device_load_data(NfcDevice* dev, FuriString* path, bool show_dialog) { @@ -1195,7 +1187,7 @@ bool nfc_file_select(NfcDevice* dev) { }; bool res = - dialog_file_browser_show(dev->dialogs, dev->load_path, nfc_app_folder, &browser_options); + dialog_file_browser_show(dev->dialogs, dev->load_path, dev->load_path, &browser_options); furi_string_free(nfc_app_folder); if(res) { diff --git a/lib/nfc/protocols/mifare_classic.c b/lib/nfc/protocols/mifare_classic.c index 7b0e17975bd..b7a52bc01ee 100644 --- a/lib/nfc/protocols/mifare_classic.c +++ b/lib/nfc/protocols/mifare_classic.c @@ -82,7 +82,7 @@ uint8_t mf_classic_get_total_sectors_num(MfClassicType type) { } } -static uint16_t mf_classic_get_total_block_num(MfClassicType type) { +uint16_t mf_classic_get_total_block_num(MfClassicType type) { if(type == MfClassicType1k) { return 64; } else if(type == MfClassicType4k) { diff --git a/lib/nfc/protocols/mifare_classic.h b/lib/nfc/protocols/mifare_classic.h index d5467b100a5..9a0bb579040 100644 --- a/lib/nfc/protocols/mifare_classic.h +++ b/lib/nfc/protocols/mifare_classic.h @@ -98,6 +98,8 @@ MfClassicType mf_classic_get_classic_type(int8_t ATQA0, uint8_t ATQA1, uint8_t S uint8_t mf_classic_get_total_sectors_num(MfClassicType type); +uint16_t mf_classic_get_total_block_num(MfClassicType type); + uint8_t mf_classic_get_sector_trailer_block_num_by_sector(uint8_t sector); bool mf_classic_is_sector_trailer(uint8_t block); From e7c4b40dbe14f71275639ac9ba9a712c17a5e566 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Thu, 10 Nov 2022 18:29:57 +0200 Subject: [PATCH 219/824] Force card types in extra actions (#1961) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Mifare Classic forced read * Add all the needed card types * nfc: remove unused scene * nfc: remove unused worker state * nfc: fix read card type scene state usage * nfc: move NfcReadMode to NfcDevData struct * nfc: fix bank card reading and scene navigation * nfc magic: fix magic deactifate function Co-authored-by: gornekich Co-authored-by: あく --- .../main/nfc/scenes/nfc_scene_config.h | 1 + .../main/nfc/scenes/nfc_scene_exit_confirm.c | 9 +- .../main/nfc/scenes/nfc_scene_extra_actions.c | 13 +++ .../nfc/scenes/nfc_scene_read_card_type.c | 97 +++++++++++++++++++ .../main/nfc/scenes/nfc_scene_start.c | 2 + .../plugins/nfc_magic/lib/magic/magic.c | 2 +- .../plugins/nfc_magic/nfc_magic_worker.c | 1 - lib/nfc/nfc_device.h | 10 ++ lib/nfc/nfc_worker.c | 81 +++++++++++++++- lib/nfc/nfc_worker_i.h | 2 + 10 files changed, 213 insertions(+), 5 deletions(-) create mode 100644 applications/main/nfc/scenes/nfc_scene_read_card_type.c diff --git a/applications/main/nfc/scenes/nfc_scene_config.h b/applications/main/nfc/scenes/nfc_scene_config.h index 9b922add2f4..49c8412c5a1 100644 --- a/applications/main/nfc/scenes/nfc_scene_config.h +++ b/applications/main/nfc/scenes/nfc_scene_config.h @@ -60,3 +60,4 @@ ADD_SCENE(nfc, detect_reader, DetectReader) ADD_SCENE(nfc, mfkey_nonces_info, MfkeyNoncesInfo) ADD_SCENE(nfc, mfkey_complete, MfkeyComplete) ADD_SCENE(nfc, nfc_data_info, NfcDataInfo) +ADD_SCENE(nfc, read_card_type, ReadCardType) diff --git a/applications/main/nfc/scenes/nfc_scene_exit_confirm.c b/applications/main/nfc/scenes/nfc_scene_exit_confirm.c index b22dd42a13a..3ce4f6de839 100644 --- a/applications/main/nfc/scenes/nfc_scene_exit_confirm.c +++ b/applications/main/nfc/scenes/nfc_scene_exit_confirm.c @@ -29,8 +29,13 @@ bool nfc_scene_exit_confirm_on_event(void* context, SceneManagerEvent event) { if(event.event == DialogExResultRight) { consumed = scene_manager_previous_scene(nfc->scene_manager); } else if(event.event == DialogExResultLeft) { - consumed = scene_manager_search_and_switch_to_previous_scene( - nfc->scene_manager, NfcSceneStart); + if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneReadCardType)) { + consumed = scene_manager_search_and_switch_to_previous_scene( + nfc->scene_manager, NfcSceneReadCardType); + } else { + consumed = scene_manager_search_and_switch_to_previous_scene( + nfc->scene_manager, NfcSceneStart); + } } } else if(event.type == SceneManagerEventTypeBack) { consumed = true; diff --git a/applications/main/nfc/scenes/nfc_scene_extra_actions.c b/applications/main/nfc/scenes/nfc_scene_extra_actions.c index e888e9d357c..fc6021d7392 100644 --- a/applications/main/nfc/scenes/nfc_scene_extra_actions.c +++ b/applications/main/nfc/scenes/nfc_scene_extra_actions.c @@ -1,6 +1,7 @@ #include "../nfc_i.h" enum SubmenuIndex { + SubmenuIndexReadCardType, SubmenuIndexMfClassicKeys, SubmenuIndexMfUltralightUnlock, }; @@ -15,6 +16,12 @@ void nfc_scene_extra_actions_on_enter(void* context) { Nfc* nfc = context; Submenu* submenu = nfc->submenu; + submenu_add_item( + submenu, + "Read Specific Card Type", + SubmenuIndexReadCardType, + nfc_scene_extra_actions_submenu_callback, + nfc); submenu_add_item( submenu, "Mifare Classic Keys", @@ -44,9 +51,15 @@ bool nfc_scene_extra_actions_on_event(void* context, SceneManagerEvent event) { consumed = true; } else if(event.event == SubmenuIndexMfUltralightUnlock) { scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightUnlockMenu); + consumed = true; + } else if(event.event == SubmenuIndexReadCardType) { + scene_manager_set_scene_state(nfc->scene_manager, NfcSceneReadCardType, 0); + scene_manager_next_scene(nfc->scene_manager, NfcSceneReadCardType); + consumed = true; } scene_manager_set_scene_state(nfc->scene_manager, NfcSceneExtraActions, event.event); } + return consumed; } diff --git a/applications/main/nfc/scenes/nfc_scene_read_card_type.c b/applications/main/nfc/scenes/nfc_scene_read_card_type.c new file mode 100644 index 00000000000..94262aa1e04 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_read_card_type.c @@ -0,0 +1,97 @@ +#include "../nfc_i.h" +#include "nfc_worker_i.h" + +enum SubmenuIndex { + SubmenuIndexReadMifareClassic, + SubmenuIndexReadMifareDesfire, + SubmenuIndexReadMfUltralight, + SubmenuIndexReadEMV, + SubmenuIndexReadNFCA, +}; + +void nfc_scene_read_card_type_submenu_callback(void* context, uint32_t index) { + Nfc* nfc = context; + + view_dispatcher_send_custom_event(nfc->view_dispatcher, index); +} + +void nfc_scene_read_card_type_on_enter(void* context) { + Nfc* nfc = context; + Submenu* submenu = nfc->submenu; + + submenu_add_item( + submenu, + "Read Mifare Classic", + SubmenuIndexReadMifareClassic, + nfc_scene_read_card_type_submenu_callback, + nfc); + submenu_add_item( + submenu, + "Read Mifare DESFire", + SubmenuIndexReadMifareDesfire, + nfc_scene_read_card_type_submenu_callback, + nfc); + submenu_add_item( + submenu, + "Read NTAG/Ultralight", + SubmenuIndexReadMfUltralight, + nfc_scene_read_card_type_submenu_callback, + nfc); + submenu_add_item( + submenu, + "Read EMV card", + SubmenuIndexReadEMV, + nfc_scene_read_card_type_submenu_callback, + nfc); + submenu_add_item( + submenu, + "Read NFC-A data", + SubmenuIndexReadNFCA, + nfc_scene_read_card_type_submenu_callback, + nfc); + uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneReadCardType); + submenu_set_selected_item(submenu, state); + + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); +} + +bool nfc_scene_read_card_type_on_event(void* context, SceneManagerEvent event) { + Nfc* nfc = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubmenuIndexReadMifareClassic) { + nfc->dev->dev_data.read_mode = NfcReadModeMfClassic; + scene_manager_next_scene(nfc->scene_manager, NfcSceneRead); + consumed = true; + } + if(event.event == SubmenuIndexReadMifareDesfire) { + nfc->dev->dev_data.read_mode = NfcReadModeMfDesfire; + scene_manager_next_scene(nfc->scene_manager, NfcSceneRead); + consumed = true; + } + if(event.event == SubmenuIndexReadMfUltralight) { + nfc->dev->dev_data.read_mode = NfcReadModeMfUltralight; + scene_manager_next_scene(nfc->scene_manager, NfcSceneRead); + consumed = true; + } + if(event.event == SubmenuIndexReadEMV) { + nfc->dev->dev_data.read_mode = NfcReadModeEMV; + scene_manager_next_scene(nfc->scene_manager, NfcSceneRead); + consumed = true; + } + if(event.event == SubmenuIndexReadNFCA) { + nfc->dev->dev_data.read_mode = NfcReadModeNFCA; + scene_manager_next_scene(nfc->scene_manager, NfcSceneRead); + consumed = true; + } + scene_manager_set_scene_state(nfc->scene_manager, NfcSceneReadCardType, event.event); + } + return consumed; +} + +void nfc_scene_read_card_type_on_exit(void* context) { + Nfc* nfc = context; + + submenu_reset(nfc->submenu); +} diff --git a/applications/main/nfc/scenes/nfc_scene_start.c b/applications/main/nfc/scenes/nfc_scene_start.c index 028f85ae033..f8b9f52c630 100644 --- a/applications/main/nfc/scenes/nfc_scene_start.c +++ b/applications/main/nfc/scenes/nfc_scene_start.c @@ -1,4 +1,5 @@ #include "../nfc_i.h" +#include "nfc_worker_i.h" #include enum SubmenuIndex { @@ -47,6 +48,7 @@ bool nfc_scene_start_on_event(void* context, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { if(event.event == SubmenuIndexRead) { + nfc->dev->dev_data.read_mode = NfcReadModeAuto; scene_manager_next_scene(nfc->scene_manager, NfcSceneRead); DOLPHIN_DEED(DolphinDeedNfcRead); consumed = true; diff --git a/applications/plugins/nfc_magic/lib/magic/magic.c b/applications/plugins/nfc_magic/lib/magic/magic.c index 3cfca748b4f..a922bc7a8f6 100644 --- a/applications/plugins/nfc_magic/lib/magic/magic.c +++ b/applications/plugins/nfc_magic/lib/magic/magic.c @@ -210,5 +210,5 @@ bool magic_wipe() { void magic_deactivate() { furi_hal_nfc_ll_txrx_off(); - furi_hal_nfc_start_sleep(); + furi_hal_nfc_sleep(); } diff --git a/applications/plugins/nfc_magic/nfc_magic_worker.c b/applications/plugins/nfc_magic/nfc_magic_worker.c index 0623211e289..0e1f6cea401 100644 --- a/applications/plugins/nfc_magic/nfc_magic_worker.c +++ b/applications/plugins/nfc_magic/nfc_magic_worker.c @@ -167,7 +167,6 @@ void nfc_magic_worker_wipe(NfcMagicWorker* nfc_magic_worker) { if(!magic_data_access_cmd()) continue; if(!magic_write_blk(0, &block)) continue; nfc_magic_worker->callback(NfcMagicWorkerEventSuccess, nfc_magic_worker->context); - magic_deactivate(); break; } magic_deactivate(); diff --git a/lib/nfc/nfc_device.h b/lib/nfc/nfc_device.h index c8e8517ae6d..3d302c18b70 100644 --- a/lib/nfc/nfc_device.h +++ b/lib/nfc/nfc_device.h @@ -51,9 +51,19 @@ typedef struct { MfClassicDict* dict; } NfcMfClassicDictAttackData; +typedef enum { + NfcReadModeAuto, + NfcReadModeMfClassic, + NfcReadModeMfUltralight, + NfcReadModeMfDesfire, + NfcReadModeEMV, + NfcReadModeNFCA, +} NfcReadMode; + typedef struct { FuriHalNfcDevData nfc_data; NfcProtocol protocol; + NfcReadMode read_mode; union { NfcReaderRequestData reader_data; NfcMfClassicDictAttackData mf_classic_dict_attack_data; diff --git a/lib/nfc/nfc_worker.c b/lib/nfc/nfc_worker.c index 5ce543636d5..5ad37c6e3cc 100644 --- a/lib/nfc/nfc_worker.c +++ b/lib/nfc/nfc_worker.c @@ -90,7 +90,11 @@ int32_t nfc_worker_task(void* context) { furi_hal_nfc_exit_sleep(); if(nfc_worker->state == NfcWorkerStateRead) { - nfc_worker_read(nfc_worker); + if(nfc_worker->dev_data->read_mode == NfcReadModeAuto) { + nfc_worker_read(nfc_worker); + } else { + nfc_worker_read_type(nfc_worker); + } } else if(nfc_worker->state == NfcWorkerStateUidEmulate) { nfc_worker_emulate_uid(nfc_worker); } else if(nfc_worker->state == NfcWorkerStateEmulateApdu) { @@ -394,6 +398,81 @@ void nfc_worker_read(NfcWorker* nfc_worker) { } } +void nfc_worker_read_type(NfcWorker* nfc_worker) { + furi_assert(nfc_worker); + furi_assert(nfc_worker->callback); + + NfcReadMode read_mode = nfc_worker->dev_data->read_mode; + nfc_device_data_clear(nfc_worker->dev_data); + NfcDeviceData* dev_data = nfc_worker->dev_data; + FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data; + FuriHalNfcTxRxContext tx_rx = {}; + NfcWorkerEvent event = 0; + bool card_not_detected_notified = false; + + while(nfc_worker->state == NfcWorkerStateRead) { + if(furi_hal_nfc_detect(nfc_data, 300)) { + FURI_LOG_D(TAG, "Card detected"); + furi_hal_nfc_sleep(); + // Process first found device + nfc_worker->callback(NfcWorkerEventCardDetected, nfc_worker->context); + card_not_detected_notified = false; + if(nfc_data->type == FuriHalNfcTypeA) { + if(read_mode == NfcReadModeMfClassic) { + nfc_worker->dev_data->protocol = NfcDeviceProtocolMifareClassic; + nfc_worker->dev_data->mf_classic_data.type = mf_classic_get_classic_type( + nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak); + if(nfc_worker_read_mf_classic(nfc_worker, &tx_rx)) { + FURI_LOG_D(TAG, "Card read"); + dev_data->protocol = NfcDeviceProtocolMifareClassic; + event = NfcWorkerEventReadMfClassicDone; + break; + } else { + FURI_LOG_D(TAG, "Card read failed"); + dev_data->protocol = NfcDeviceProtocolMifareClassic; + event = NfcWorkerEventReadMfClassicDictAttackRequired; + break; + } + } else if(read_mode == NfcReadModeMfUltralight) { + FURI_LOG_I(TAG, "Mifare Ultralight / NTAG"); + nfc_worker->dev_data->protocol = NfcDeviceProtocolMifareUl; + if(nfc_worker_read_mf_ultralight(nfc_worker, &tx_rx)) { + event = NfcWorkerEventReadMfUltralight; + break; + } + } else if(read_mode == NfcReadModeMfDesfire) { + nfc_worker->dev_data->protocol = NfcDeviceProtocolMifareDesfire; + if(nfc_worker_read_mf_desfire(nfc_worker, &tx_rx)) { + event = NfcWorkerEventReadMfDesfire; + break; + } + } else if(read_mode == NfcReadModeEMV) { + nfc_worker->dev_data->protocol = NfcDeviceProtocolEMV; + if(nfc_worker_read_bank_card(nfc_worker, &tx_rx)) { + event = NfcWorkerEventReadBankCard; + break; + } + } else if(read_mode == NfcReadModeNFCA) { + nfc_worker->dev_data->protocol = NfcDeviceProtocolUnknown; + event = NfcWorkerEventReadUidNfcA; + break; + } + } else { + if(!card_not_detected_notified) { + nfc_worker->callback(NfcWorkerEventNoCardDetected, nfc_worker->context); + card_not_detected_notified = true; + } + } + } + furi_hal_nfc_sleep(); + furi_delay_ms(100); + } + // Notify caller and exit + if(event > NfcWorkerEventReserved) { + nfc_worker->callback(event, nfc_worker->context); + } +} + void nfc_worker_emulate_uid(NfcWorker* nfc_worker) { FuriHalNfcTxRxContext tx_rx = {}; FuriHalNfcDevData* data = &nfc_worker->dev_data->nfc_data; diff --git a/lib/nfc/nfc_worker_i.h b/lib/nfc/nfc_worker_i.h index b9f69e620cf..9733426abf2 100644 --- a/lib/nfc/nfc_worker_i.h +++ b/lib/nfc/nfc_worker_i.h @@ -35,6 +35,8 @@ int32_t nfc_worker_task(void* context); void nfc_worker_read(NfcWorker* nfc_worker); +void nfc_worker_read_type(NfcWorker* nfc_worker); + void nfc_worker_emulate_uid(NfcWorker* nfc_worker); void nfc_worker_emulate_mf_ultralight(NfcWorker* nfc_worker); From aec36e7041a24af68d04d57b024726d909d29c82 Mon Sep 17 00:00:00 2001 From: lauaall <48836917+skelq@users.noreply.github.com> Date: Thu, 10 Nov 2022 18:48:58 +0200 Subject: [PATCH 220/824] Fixed typos (#1999) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sergey Gavrilov Co-authored-by: あく --- documentation/KeyCombo.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/documentation/KeyCombo.md b/documentation/KeyCombo.md index 359fd5b9bd3..e6f55dc5295 100644 --- a/documentation/KeyCombo.md +++ b/documentation/KeyCombo.md @@ -1,7 +1,7 @@ # Key Combos -There are times when your flipper feels blue and don't respond to your commands. -In that case you may find this guide useful. +There are times when your flipper feels blue and doesn't respond to your commands. +In that case, you may find this guide useful. ## Basic Combos @@ -9,7 +9,7 @@ In that case you may find this guide useful. ### Hardware Reset -- Press `LEFT` and `BACK` and hold for couple seconds +- Press `LEFT` and `BACK` and hold for a couple of seconds - Release `LEFT` and `BACK` This combo performs hardware reset by pulling MCU reset line down. @@ -29,7 +29,7 @@ There is 1 case when it's not working: - If you have not disconnected USB, then disconnect USB and repeat previous step - Release `BACK` key -This combo performs reset by switching SYS power line off and then on. +This combo performs a reset by switching SYS power line off and then on. Main components involved: Keys -> DD6(bq25896, charger) There is 1 case when it's not working: @@ -60,13 +60,13 @@ There is 1 case when it's not working: ### Hardware Reset + Software DFU -- Press `LEFT` and `BACK` and hold for couple seconds +- Press `LEFT` and `BACK` and hold for a couple of seconds - Release `BACK` - Device will enter DFU with indication (Blue LED + DFU Screen) - Release `LEFT` This combo performs hardware reset by pulling MCU reset line down. -Then `LEFT` key indicates to boot-loader that DFU mode requested. +Then `LEFT` key indicates to boot-loader that DFU mode is requested. There are 2 cases when it's not working: @@ -76,7 +76,7 @@ There are 2 cases when it's not working: ### Hardware Reset + Hardware DFU -- Press `LEFT` and `BACK` and `OK` and hold for couple seconds +- Press `LEFT` and `BACK` and `OK` and hold for a couple of seconds - Release `BACK` and `LEFT` - Device will enter DFU without indication @@ -127,8 +127,8 @@ There are 2 cases when it's not working: If none of the described methods were useful: -- Ensure battery charged -- Disconnect battery and connect again (Requires disassembly) -- Try to Flash device with ST-Link or other programmer that support SWD +- Ensure the battery charged +- Disconnect the battery and connect again (Requires disassembly) +- Try to Flash device with ST-Link or other programmer that supports SWD -If you still here and your device is not working: it's not software issue. +If you still here and your device is not working: it's not a software issue. From 721ab717d784859f7076175d22991b6d950c361e Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Thu, 10 Nov 2022 21:14:44 +0400 Subject: [PATCH 221/824] [FL-2961] SubGhz: properly handle storage loss (#1990) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- .../subghz/scenes/subghz_scene_more_raw.c | 38 +++++++++++++------ .../subghz/scenes/subghz_scene_read_raw.c | 38 ++++++++++++++----- applications/main/subghz/subghz_i.c | 17 +++++++++ applications/main/subghz/subghz_i.h | 1 + 4 files changed, 74 insertions(+), 20 deletions(-) diff --git a/applications/main/subghz/scenes/subghz_scene_more_raw.c b/applications/main/subghz/scenes/subghz_scene_more_raw.c index d75ab13c74b..864be1ed1ad 100644 --- a/applications/main/subghz/scenes/subghz_scene_more_raw.c +++ b/applications/main/subghz/scenes/subghz_scene_more_raw.c @@ -38,18 +38,34 @@ bool subghz_scene_more_raw_on_event(void* context, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { if(event.event == SubmenuIndexDelete) { - scene_manager_set_scene_state( - subghz->scene_manager, SubGhzSceneReadRAW, SubGhzCustomEventManagerNoSet); - scene_manager_set_scene_state( - subghz->scene_manager, SubGhzSceneMoreRAW, SubmenuIndexDelete); - scene_manager_next_scene(subghz->scene_manager, SubGhzSceneDeleteRAW); - return true; + if(subghz_file_available(subghz)) { + scene_manager_set_scene_state( + subghz->scene_manager, SubGhzSceneReadRAW, SubGhzCustomEventManagerNoSet); + scene_manager_set_scene_state( + subghz->scene_manager, SubGhzSceneMoreRAW, SubmenuIndexDelete); + scene_manager_next_scene(subghz->scene_manager, SubGhzSceneDeleteRAW); + return true; + } else { + if(!scene_manager_search_and_switch_to_previous_scene( + subghz->scene_manager, SubGhzSceneStart)) { + scene_manager_stop(subghz->scene_manager); + view_dispatcher_stop(subghz->view_dispatcher); + } + } } else if(event.event == SubmenuIndexEdit) { - furi_string_reset(subghz->file_path_tmp); - scene_manager_set_scene_state( - subghz->scene_manager, SubGhzSceneMoreRAW, SubmenuIndexEdit); - scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveName); - return true; + if(subghz_file_available(subghz)) { + furi_string_reset(subghz->file_path_tmp); + scene_manager_set_scene_state( + subghz->scene_manager, SubGhzSceneMoreRAW, SubmenuIndexEdit); + scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveName); + return true; + } else { + if(!scene_manager_search_and_switch_to_previous_scene( + subghz->scene_manager, SubGhzSceneStart)) { + scene_manager_stop(subghz->scene_manager); + view_dispatcher_stop(subghz->view_dispatcher); + } + } } } return false; diff --git a/applications/main/subghz/scenes/subghz_scene_read_raw.c b/applications/main/subghz/scenes/subghz_scene_read_raw.c index ba9ce803bf5..2e5ba09667d 100644 --- a/applications/main/subghz/scenes/subghz_scene_read_raw.c +++ b/applications/main/subghz/scenes/subghz_scene_read_raw.c @@ -198,20 +198,28 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { break; case SubGhzCustomEventViewReadRAWMore: - if(subghz_scene_read_raw_update_filename(subghz)) { - scene_manager_set_scene_state( - subghz->scene_manager, SubGhzSceneReadRAW, SubGhzCustomEventManagerSet); - subghz->txrx->rx_key_state = SubGhzRxKeyStateRAWLoad; - scene_manager_next_scene(subghz->scene_manager, SubGhzSceneMoreRAW); - consumed = true; + if(subghz_file_available(subghz)) { + if(subghz_scene_read_raw_update_filename(subghz)) { + scene_manager_set_scene_state( + subghz->scene_manager, SubGhzSceneReadRAW, SubGhzCustomEventManagerSet); + subghz->txrx->rx_key_state = SubGhzRxKeyStateRAWLoad; + scene_manager_next_scene(subghz->scene_manager, SubGhzSceneMoreRAW); + consumed = true; + } else { + furi_crash("SubGhz: RAW file name update error."); + } } else { - furi_crash("SubGhz: RAW file name update error."); + if(!scene_manager_search_and_switch_to_previous_scene( + subghz->scene_manager, SubGhzSceneStart)) { + scene_manager_stop(subghz->scene_manager); + view_dispatcher_stop(subghz->view_dispatcher); + } } break; case SubGhzCustomEventViewReadRAWSendStart: - if(subghz_scene_read_raw_update_filename(subghz)) { + if(subghz_file_available(subghz) && subghz_scene_read_raw_update_filename(subghz)) { //start send subghz->state_notifications = SubGhzNotificationStateIDLE; if(subghz->txrx->txrx_state == SubGhzTxRxStateRx) { @@ -238,6 +246,12 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { subghz->state_notifications = SubGhzNotificationStateTx; } } + } else { + if(!scene_manager_search_and_switch_to_previous_scene( + subghz->scene_manager, SubGhzSceneStart)) { + scene_manager_stop(subghz->scene_manager); + view_dispatcher_stop(subghz->view_dispatcher); + } } consumed = true; break; @@ -314,11 +328,17 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { break; case SubGhzCustomEventViewReadRAWSave: - if(subghz_scene_read_raw_update_filename(subghz)) { + if(subghz_file_available(subghz) && subghz_scene_read_raw_update_filename(subghz)) { scene_manager_set_scene_state( subghz->scene_manager, SubGhzSceneReadRAW, SubGhzCustomEventManagerSetRAW); subghz->txrx->rx_key_state = SubGhzRxKeyStateBack; scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveName); + } else { + if(!scene_manager_search_and_switch_to_previous_scene( + subghz->scene_manager, SubGhzSceneStart)) { + scene_manager_stop(subghz->scene_manager); + view_dispatcher_stop(subghz->view_dispatcher); + } } consumed = true; break; diff --git a/applications/main/subghz/subghz_i.c b/applications/main/subghz/subghz_i.c index a887b6543bf..d9070ba8cc0 100644 --- a/applications/main/subghz/subghz_i.c +++ b/applications/main/subghz/subghz_i.c @@ -490,6 +490,23 @@ bool subghz_rename_file(SubGhz* subghz) { return ret; } +bool subghz_file_available(SubGhz* subghz) { + furi_assert(subghz); + bool ret = true; + Storage* storage = furi_record_open(RECORD_STORAGE); + + FS_Error fs_result = + storage_common_stat(storage, furi_string_get_cstr(subghz->file_path), NULL); + + if(fs_result != FSE_OK) { + dialog_message_show_storage_error(subghz->dialogs, "File not available\n file/directory"); + ret = false; + } + + furi_record_close(RECORD_STORAGE); + return ret; +} + bool subghz_delete_file(SubGhz* subghz) { furi_assert(subghz); diff --git a/applications/main/subghz/subghz_i.h b/applications/main/subghz/subghz_i.h index 46768cf02bb..0a40196bb1d 100644 --- a/applications/main/subghz/subghz_i.h +++ b/applications/main/subghz/subghz_i.h @@ -124,6 +124,7 @@ bool subghz_save_protocol_to_file( const char* dev_file_name); bool subghz_load_protocol_from_file(SubGhz* subghz); bool subghz_rename_file(SubGhz* subghz); +bool subghz_file_available(SubGhz* subghz); bool subghz_delete_file(SubGhz* subghz); void subghz_file_name_clear(SubGhz* subghz); bool subghz_path_is_file(FuriString* path); From 90cefe7c7198d09c2bcaf43eff0e8ded54c761c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Sat, 12 Nov 2022 17:46:04 +0900 Subject: [PATCH 222/824] [FL-2975] Bug fixes and improvements: Furi, Input, Cli (#2004) * Furi: configurable heap allocation tracking * Furi: relax restriction in thread heap setter asserts, apply heap tracking setting on app start instead of thread allocation * Furi: hide dangerous heap tracking levels in release build * Input: fix non-working debounce --- applications/services/cli/cli_commands.c | 96 +++++++++++++++---- .../desktop/scenes/desktop_scene_main.c | 7 ++ applications/services/input/input.c | 20 +++- applications/services/loader/loader.c | 9 +- .../settings/system/system_settings.c | 37 +++++++ firmware/targets/f7/api_symbols.csv | 4 +- .../targets/f7/furi_hal/furi_hal_resources.h | 2 +- firmware/targets/f7/furi_hal/furi_hal_rtc.c | 16 +++- .../targets/furi_hal_include/furi_hal_rtc.h | 11 +++ furi/core/thread.c | 9 +- 10 files changed, 186 insertions(+), 25 deletions(-) diff --git a/applications/services/cli/cli_commands.c b/applications/services/cli/cli_commands.c index 239434b7a0e..64ff8ef71b3 100644 --- a/applications/services/cli/cli_commands.c +++ b/applications/services/cli/cli_commands.c @@ -7,6 +7,7 @@ #include #include #include +#include // Close to ISO, `date +'%Y-%m-%d %H:%M:%S %u'` #define CLI_DATE_FORMAT "%.4d-%.2d-%.2d %.2d:%.2d:%.2d %d" @@ -192,35 +193,96 @@ void cli_command_log(Cli* cli, FuriString* args, void* context) { furi_stream_buffer_free(ring); } -void cli_command_vibro(Cli* cli, FuriString* args, void* context) { +void cli_command_sysctl_debug(Cli* cli, FuriString* args, void* context) { UNUSED(cli); UNUSED(context); if(!furi_string_cmp(args, "0")) { - NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); - notification_message_block(notification, &sequence_reset_vibro); - furi_record_close(RECORD_NOTIFICATION); + furi_hal_rtc_reset_flag(FuriHalRtcFlagDebug); + loader_update_menu(); + printf("Debug disabled."); } else if(!furi_string_cmp(args, "1")) { - NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); - notification_message_block(notification, &sequence_set_vibro_on); - furi_record_close(RECORD_NOTIFICATION); + furi_hal_rtc_set_flag(FuriHalRtcFlagDebug); + loader_update_menu(); + printf("Debug enabled."); } else { - cli_print_usage("vibro", "<1|0>", furi_string_get_cstr(args)); + cli_print_usage("sysctl debug", "<1|0>", furi_string_get_cstr(args)); } } -void cli_command_debug(Cli* cli, FuriString* args, void* context) { +void cli_command_sysctl_heap_track(Cli* cli, FuriString* args, void* context) { + UNUSED(cli); + UNUSED(context); + if(!furi_string_cmp(args, "none")) { + furi_hal_rtc_set_heap_track_mode(FuriHalRtcHeapTrackModeNone); + printf("Heap tracking disabled"); + } else if(!furi_string_cmp(args, "main")) { + furi_hal_rtc_set_heap_track_mode(FuriHalRtcHeapTrackModeMain); + printf("Heap tracking enabled for application main thread"); +#if FURI_DEBUG + } else if(!furi_string_cmp(args, "tree")) { + furi_hal_rtc_set_heap_track_mode(FuriHalRtcHeapTrackModeTree); + printf("Heap tracking enabled for application main and child threads"); + } else if(!furi_string_cmp(args, "all")) { + furi_hal_rtc_set_heap_track_mode(FuriHalRtcHeapTrackModeAll); + printf("Heap tracking enabled for all threads"); +#endif + } else { + cli_print_usage("sysctl heap_track", "", furi_string_get_cstr(args)); + } +} + +void cli_command_sysctl_print_usage() { + printf("Usage:\r\n"); + printf("sysctl \r\n"); + printf("Cmd list:\r\n"); + + printf("\tdebug <0|1>\t - Enable or disable system debug\r\n"); +#if FURI_DEBUG + printf("\theap_track \t - Set heap allocation tracking mode\r\n"); +#else + printf("\theap_track \t - Set heap allocation tracking mode\r\n"); +#endif +} + +void cli_command_sysctl(Cli* cli, FuriString* args, void* context) { + FuriString* cmd; + cmd = furi_string_alloc(); + + do { + if(!args_read_string_and_trim(args, cmd)) { + cli_command_sysctl_print_usage(); + break; + } + + if(furi_string_cmp_str(cmd, "debug") == 0) { + cli_command_sysctl_debug(cli, args, context); + break; + } + + if(furi_string_cmp_str(cmd, "heap_track") == 0) { + cli_command_sysctl_heap_track(cli, args, context); + break; + } + + cli_command_sysctl_print_usage(); + } while(false); + + furi_string_free(cmd); +} + +void cli_command_vibro(Cli* cli, FuriString* args, void* context) { UNUSED(cli); UNUSED(context); if(!furi_string_cmp(args, "0")) { - furi_hal_rtc_reset_flag(FuriHalRtcFlagDebug); - loader_update_menu(); - printf("Debug disabled."); + NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); + notification_message_block(notification, &sequence_reset_vibro); + furi_record_close(RECORD_NOTIFICATION); } else if(!furi_string_cmp(args, "1")) { - furi_hal_rtc_set_flag(FuriHalRtcFlagDebug); - loader_update_menu(); - printf("Debug enabled."); + NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); + notification_message_block(notification, &sequence_set_vibro_on); + furi_record_close(RECORD_NOTIFICATION); } else { - cli_print_usage("debug", "<1|0>", furi_string_get_cstr(args)); + cli_print_usage("vibro", "<1|0>", furi_string_get_cstr(args)); } } @@ -356,7 +418,7 @@ void cli_commands_init(Cli* cli) { cli_add_command(cli, "date", CliCommandFlagParallelSafe, cli_command_date, NULL); cli_add_command(cli, "log", CliCommandFlagParallelSafe, cli_command_log, NULL); - cli_add_command(cli, "debug", CliCommandFlagDefault, cli_command_debug, NULL); + cli_add_command(cli, "sysctl", CliCommandFlagDefault, cli_command_sysctl, NULL); cli_add_command(cli, "ps", CliCommandFlagParallelSafe, cli_command_ps, NULL); cli_add_command(cli, "free", CliCommandFlagParallelSafe, cli_command_free, NULL); cli_add_command(cli, "free_blocks", CliCommandFlagParallelSafe, cli_command_free_blocks, NULL); diff --git a/applications/services/desktop/scenes/desktop_scene_main.c b/applications/services/desktop/scenes/desktop_scene_main.c index 654de44d578..4f01ad5beed 100644 --- a/applications/services/desktop/scenes/desktop_scene_main.c +++ b/applications/services/desktop/scenes/desktop_scene_main.c @@ -45,6 +45,13 @@ static void desktop_switch_to_app(Desktop* desktop, const FlipperApplication* fl return; } + FuriHalRtcHeapTrackMode mode = furi_hal_rtc_get_heap_track_mode(); + if(mode > FuriHalRtcHeapTrackModeNone) { + furi_thread_enable_heap_trace(desktop->scene_thread); + } else { + furi_thread_disable_heap_trace(desktop->scene_thread); + } + furi_thread_set_name(desktop->scene_thread, flipper_app->name); furi_thread_set_stack_size(desktop->scene_thread, flipper_app->stack_size); furi_thread_set_callback(desktop->scene_thread, flipper_app->app); diff --git a/applications/services/input/input.c b/applications/services/input/input.c index d1aef9e8ac0..1d02df1e519 100644 --- a/applications/services/input/input.c +++ b/applications/services/input/input.c @@ -1,5 +1,7 @@ #include "input_i.h" +// #define INPUT_DEBUG + #define GPIO_Read(input_pin) (furi_hal_gpio_read(input_pin.pin->gpio) ^ (input_pin.pin->inverted)) static Input* input = NULL; @@ -72,6 +74,10 @@ int32_t input_srv(void* p) { input->event_pubsub = furi_pubsub_alloc(); furi_record_create(RECORD_INPUT_EVENTS, input->event_pubsub); +#if INPUT_DEBUG + furi_hal_gpio_init_simple(&gpio_ext_pa4, GpioModeOutputPushPull); +#endif + #ifdef SRV_CLI input->cli = furi_record_open(RECORD_CLI); if(input->cli) { @@ -95,10 +101,16 @@ int32_t input_srv(void* p) { bool is_changing = false; for(size_t i = 0; i < input_pins_count; i++) { bool state = GPIO_Read(input->pin_states[i]); + if(state) { + if(input->pin_states[i].debounce < INPUT_DEBOUNCE_TICKS) + input->pin_states[i].debounce += 1; + } else { + if(input->pin_states[i].debounce > 0) input->pin_states[i].debounce -= 1; + } + if(input->pin_states[i].debounce > 0 && input->pin_states[i].debounce < INPUT_DEBOUNCE_TICKS) { is_changing = true; - input->pin_states[i].debounce += (state ? 1 : -1); } else if(input->pin_states[i].state != state) { input->pin_states[i].state = state; @@ -129,8 +141,14 @@ int32_t input_srv(void* p) { } if(is_changing) { +#if INPUT_DEBUG + furi_hal_gpio_write(&gpio_ext_pa4, 1); +#endif furi_delay_tick(1); } else { +#if INPUT_DEBUG + furi_hal_gpio_write(&gpio_ext_pa4, 0); +#endif furi_thread_flags_wait(INPUT_THREAD_FLAG_ISR, FuriFlagWaitAny, FuriWaitForever); } } diff --git a/applications/services/loader/loader.c b/applications/services/loader/loader.c index 712576e14a2..931719723a5 100644 --- a/applications/services/loader/loader.c +++ b/applications/services/loader/loader.c @@ -21,6 +21,13 @@ static bool FURI_LOG_I(TAG, "Starting: %s", loader_instance->application->name); + FuriHalRtcHeapTrackMode mode = furi_hal_rtc_get_heap_track_mode(); + if(mode > FuriHalRtcHeapTrackModeNone) { + furi_thread_enable_heap_trace(loader_instance->application_thread); + } else { + furi_thread_disable_heap_trace(loader_instance->application_thread); + } + furi_thread_set_name(loader_instance->application_thread, loader_instance->application->name); furi_thread_set_stack_size( loader_instance->application_thread, loader_instance->application->stack_size); @@ -306,7 +313,7 @@ static Loader* loader_alloc() { Loader* instance = malloc(sizeof(Loader)); instance->application_thread = furi_thread_alloc(); - furi_thread_enable_heap_trace(instance->application_thread); + furi_thread_set_state_context(instance->application_thread, instance); furi_thread_set_state_callback(instance->application_thread, loader_thread_state_callback); diff --git a/applications/settings/system/system_settings.c b/applications/settings/system/system_settings.c index 7661413d714..dfce11a2207 100644 --- a/applications/settings/system/system_settings.c +++ b/applications/settings/system/system_settings.c @@ -45,6 +45,31 @@ static void debug_changed(VariableItem* item) { loader_update_menu(); } +const char* const heap_trace_mode_text[] = { + "None", + "Main", +#if FURI_DEBUG + "Tree", + "All", +#endif +}; + +const uint32_t heap_trace_mode_value[] = { + FuriHalRtcHeapTrackModeNone, + FuriHalRtcHeapTrackModeMain, +#if FURI_DEBUG + FuriHalRtcHeapTrackModeTree, + FuriHalRtcHeapTrackModeAll, +#endif +}; + +static void heap_trace_mode_changed(VariableItem* item) { + // SystemSettings* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + variable_item_set_current_value_text(item, heap_trace_mode_text[index]); + furi_hal_rtc_set_heap_track_mode(heap_trace_mode_value[index]); +} + static uint32_t system_settings_exit(void* context) { UNUSED(context); return VIEW_NONE; @@ -79,6 +104,18 @@ SystemSettings* system_settings_alloc() { variable_item_set_current_value_index(item, value_index); variable_item_set_current_value_text(item, debug_text[value_index]); + item = variable_item_list_add( + app->var_item_list, + "Heap Trace", + COUNT_OF(heap_trace_mode_text), + heap_trace_mode_changed, + app); + value_index = value_index_uint32( + furi_hal_rtc_get_heap_track_mode(), heap_trace_mode_value, COUNT_OF(heap_trace_mode_text)); + furi_hal_rtc_set_heap_track_mode(heap_trace_mode_value[value_index]); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, heap_trace_mode_text[value_index]); + view_set_previous_callback( variable_item_list_get_view(app->var_item_list), system_settings_exit); view_dispatcher_add_view( diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index d6e522a2b44..1ad5efa7277 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,7.4,, +Version,+,7.5,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -1260,6 +1260,7 @@ Function,+,furi_hal_rtc_deinit_early,void, Function,+,furi_hal_rtc_get_boot_mode,FuriHalRtcBootMode, Function,+,furi_hal_rtc_get_datetime,void,FuriHalRtcDateTime* Function,+,furi_hal_rtc_get_fault_data,uint32_t, +Function,+,furi_hal_rtc_get_heap_track_mode,FuriHalRtcHeapTrackMode, Function,+,furi_hal_rtc_get_log_level,uint8_t, Function,+,furi_hal_rtc_get_pin_fails,uint32_t, Function,+,furi_hal_rtc_get_register,uint32_t,FuriHalRtcRegister @@ -1272,6 +1273,7 @@ Function,+,furi_hal_rtc_set_boot_mode,void,FuriHalRtcBootMode Function,+,furi_hal_rtc_set_datetime,void,FuriHalRtcDateTime* Function,+,furi_hal_rtc_set_fault_data,void,uint32_t Function,+,furi_hal_rtc_set_flag,void,FuriHalRtcFlag +Function,+,furi_hal_rtc_set_heap_track_mode,void,FuriHalRtcHeapTrackMode Function,+,furi_hal_rtc_set_log_level,void,uint8_t Function,+,furi_hal_rtc_set_pin_fails,void,uint32_t Function,+,furi_hal_rtc_set_register,void,"FuriHalRtcRegister, uint32_t" diff --git a/firmware/targets/f7/furi_hal/furi_hal_resources.h b/firmware/targets/f7/furi_hal/furi_hal_resources.h index 8d53ec48ae0..64e5e19f900 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_resources.h +++ b/firmware/targets/f7/furi_hal/furi_hal_resources.h @@ -10,7 +10,7 @@ extern "C" { #endif /* Input Related Constants */ -#define INPUT_DEBOUNCE_TICKS 30 +#define INPUT_DEBOUNCE_TICKS 4 /* Input Keys */ typedef enum { diff --git a/firmware/targets/f7/furi_hal/furi_hal_rtc.c b/firmware/targets/f7/furi_hal/furi_hal_rtc.c index 14ece9466dc..c38cbfec5b3 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_rtc.c +++ b/firmware/targets/f7/furi_hal/furi_hal_rtc.c @@ -30,7 +30,8 @@ typedef struct { uint8_t log_reserved : 4; uint8_t flags; uint8_t boot_mode : 4; - uint16_t reserved : 12; + uint8_t heap_track_mode : 2; + uint16_t reserved : 10; } DeveloperReg; _Static_assert(sizeof(DeveloperReg) == 4, "DeveloperReg size mismatch"); @@ -224,6 +225,19 @@ FuriHalRtcBootMode furi_hal_rtc_get_boot_mode() { return (FuriHalRtcBootMode)data->boot_mode; } +void furi_hal_rtc_set_heap_track_mode(FuriHalRtcHeapTrackMode mode) { + uint32_t data_reg = furi_hal_rtc_get_register(FuriHalRtcRegisterSystem); + DeveloperReg* data = (DeveloperReg*)&data_reg; + data->heap_track_mode = mode; + furi_hal_rtc_set_register(FuriHalRtcRegisterSystem, data_reg); +} + +FuriHalRtcHeapTrackMode furi_hal_rtc_get_heap_track_mode() { + uint32_t data_reg = furi_hal_rtc_get_register(FuriHalRtcRegisterSystem); + DeveloperReg* data = (DeveloperReg*)&data_reg; + return (FuriHalRtcHeapTrackMode)data->heap_track_mode; +} + void furi_hal_rtc_set_datetime(FuriHalRtcDateTime* datetime) { furi_assert(datetime); diff --git a/firmware/targets/furi_hal_include/furi_hal_rtc.h b/firmware/targets/furi_hal_include/furi_hal_rtc.h index 361225fb240..5ce122271d6 100644 --- a/firmware/targets/furi_hal_include/furi_hal_rtc.h +++ b/firmware/targets/furi_hal_include/furi_hal_rtc.h @@ -39,6 +39,13 @@ typedef enum { FuriHalRtcBootModePostUpdate, /**< Boot to Update, post update */ } FuriHalRtcBootMode; +typedef enum { + FuriHalRtcHeapTrackModeNone = 0, /**< Disable allocation tracking */ + FuriHalRtcHeapTrackModeMain, /**< Enable allocation tracking for main application thread */ + FuriHalRtcHeapTrackModeTree, /**< Enable allocation tracking for main and children application threads */ + FuriHalRtcHeapTrackModeAll, /**< Enable allocation tracking for all threads */ +} FuriHalRtcHeapTrackMode; + typedef enum { FuriHalRtcRegisterHeader, /**< RTC structure header */ FuriHalRtcRegisterSystem, /**< Various system bits */ @@ -79,6 +86,10 @@ void furi_hal_rtc_set_boot_mode(FuriHalRtcBootMode mode); FuriHalRtcBootMode furi_hal_rtc_get_boot_mode(); +void furi_hal_rtc_set_heap_track_mode(FuriHalRtcHeapTrackMode mode); + +FuriHalRtcHeapTrackMode furi_hal_rtc_get_heap_track_mode(); + void furi_hal_rtc_set_datetime(FuriHalRtcDateTime* datetime); void furi_hal_rtc_get_datetime(FuriHalRtcDateTime* datetime); diff --git a/furi/core/thread.c b/furi/core/thread.c index 8320a47e830..201b47fe78c 100644 --- a/furi/core/thread.c +++ b/furi/core/thread.c @@ -122,9 +122,14 @@ FuriThread* furi_thread_alloc() { thread->output.buffer = furi_string_alloc(); thread->is_service = false; - if(furi_thread_get_current_id()) { + FuriHalRtcHeapTrackMode mode = furi_hal_rtc_get_heap_track_mode(); + if(mode == FuriHalRtcHeapTrackModeAll) { + thread->heap_trace_enabled = true; + } else if(mode == FuriHalRtcHeapTrackModeTree && furi_thread_get_current_id()) { FuriThread* parent = pvTaskGetThreadLocalStoragePointer(NULL, 0); if(parent) thread->heap_trace_enabled = parent->heap_trace_enabled; + } else { + thread->heap_trace_enabled = false; } return thread; @@ -243,14 +248,12 @@ FuriThreadId furi_thread_get_id(FuriThread* thread) { void furi_thread_enable_heap_trace(FuriThread* thread) { furi_assert(thread); furi_assert(thread->state == FuriThreadStateStopped); - furi_assert(thread->heap_trace_enabled == false); thread->heap_trace_enabled = true; } void furi_thread_disable_heap_trace(FuriThread* thread) { furi_assert(thread); furi_assert(thread->state == FuriThreadStateStopped); - furi_assert(thread->heap_trace_enabled == true); thread->heap_trace_enabled = false; } From 3c7a4eeaed1f8c636771adb6974a47e3934b9149 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sat, 12 Nov 2022 12:45:19 +0300 Subject: [PATCH 223/824] iButton: Fix header "Saved!" message stays on other screens (#2003) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * iButton: Fix header "Saved!" message stays on other screens * SubGhz,iButton: proper popup reset Co-authored-by: あく --- .../ibutton/scenes/ibutton_scene_delete_success.c | 7 +------ .../main/ibutton/scenes/ibutton_scene_save_success.c | 7 +------ .../ibutton/scenes/ibutton_scene_write_success.c | 7 +------ .../main/subghz/scenes/subghz_scene_delete_success.c | 11 ++--------- .../main/subghz/scenes/subghz_scene_save_success.c | 11 ++--------- .../main/subghz/scenes/subghz_scene_show_error_sub.c | 12 +++--------- .../main/subghz/scenes/subghz_scene_show_only_rx.c | 11 ++--------- 7 files changed, 12 insertions(+), 54 deletions(-) diff --git a/applications/main/ibutton/scenes/ibutton_scene_delete_success.c b/applications/main/ibutton/scenes/ibutton_scene_delete_success.c index 8b7d9dfc02f..9ff165e4a31 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_delete_success.c +++ b/applications/main/ibutton/scenes/ibutton_scene_delete_success.c @@ -39,10 +39,5 @@ void ibutton_scene_delete_success_on_exit(void* context) { iButton* ibutton = context; Popup* popup = ibutton->popup; - popup_set_text(popup, NULL, 0, 0, AlignCenter, AlignTop); - popup_set_icon(popup, 0, 0, NULL); - - popup_disable_timeout(popup); - popup_set_context(popup, NULL); - popup_set_callback(popup, NULL); + popup_reset(popup); } diff --git a/applications/main/ibutton/scenes/ibutton_scene_save_success.c b/applications/main/ibutton/scenes/ibutton_scene_save_success.c index e0b9b3c47fb..8b16d2929a5 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_save_success.c +++ b/applications/main/ibutton/scenes/ibutton_scene_save_success.c @@ -39,10 +39,5 @@ void ibutton_scene_save_success_on_exit(void* context) { iButton* ibutton = context; Popup* popup = ibutton->popup; - popup_set_text(popup, NULL, 0, 0, AlignCenter, AlignTop); - popup_set_icon(popup, 0, 0, NULL); - - popup_disable_timeout(popup); - popup_set_context(popup, NULL); - popup_set_callback(popup, NULL); + popup_reset(popup); } diff --git a/applications/main/ibutton/scenes/ibutton_scene_write_success.c b/applications/main/ibutton/scenes/ibutton_scene_write_success.c index 3acb1dea04c..17cd53d08d6 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_write_success.c +++ b/applications/main/ibutton/scenes/ibutton_scene_write_success.c @@ -43,10 +43,5 @@ void ibutton_scene_write_success_on_exit(void* context) { iButton* ibutton = context; Popup* popup = ibutton->popup; - popup_set_text(popup, NULL, 0, 0, AlignCenter, AlignTop); - popup_set_icon(popup, 0, 0, NULL); - - popup_disable_timeout(popup); - popup_set_context(popup, NULL); - popup_set_callback(popup, NULL); + popup_reset(popup); } diff --git a/applications/main/subghz/scenes/subghz_scene_delete_success.c b/applications/main/subghz/scenes/subghz_scene_delete_success.c index d6e1f8dd42a..4f98b6a394f 100644 --- a/applications/main/subghz/scenes/subghz_scene_delete_success.c +++ b/applications/main/subghz/scenes/subghz_scene_delete_success.c @@ -44,14 +44,7 @@ bool subghz_scene_delete_success_on_event(void* context, SceneManagerEvent event void subghz_scene_delete_success_on_exit(void* context) { SubGhz* subghz = context; - - // Clear view Popup* popup = subghz->popup; - popup_set_header(popup, NULL, 0, 0, AlignCenter, AlignBottom); - popup_set_text(popup, NULL, 0, 0, AlignCenter, AlignTop); - popup_set_icon(popup, 0, 0, NULL); - popup_set_callback(popup, NULL); - popup_set_context(popup, NULL); - popup_set_timeout(popup, 0); - popup_disable_timeout(popup); + + popup_reset(popup); } diff --git a/applications/main/subghz/scenes/subghz_scene_save_success.c b/applications/main/subghz/scenes/subghz_scene_save_success.c index d32c9271a24..2977975f7ff 100644 --- a/applications/main/subghz/scenes/subghz_scene_save_success.c +++ b/applications/main/subghz/scenes/subghz_scene_save_success.c @@ -44,14 +44,7 @@ bool subghz_scene_save_success_on_event(void* context, SceneManagerEvent event) void subghz_scene_save_success_on_exit(void* context) { SubGhz* subghz = context; - - // Clear view Popup* popup = subghz->popup; - popup_set_header(popup, NULL, 0, 0, AlignCenter, AlignBottom); - popup_set_text(popup, NULL, 0, 0, AlignCenter, AlignTop); - popup_set_icon(popup, 0, 0, NULL); - popup_set_callback(popup, NULL); - popup_set_context(popup, NULL); - popup_set_timeout(popup, 0); - popup_disable_timeout(popup); + + popup_reset(popup); } diff --git a/applications/main/subghz/scenes/subghz_scene_show_error_sub.c b/applications/main/subghz/scenes/subghz_scene_show_error_sub.c index 2720b2b94db..113e7ae7463 100644 --- a/applications/main/subghz/scenes/subghz_scene_show_error_sub.c +++ b/applications/main/subghz/scenes/subghz_scene_show_error_sub.c @@ -36,16 +36,10 @@ bool subghz_scene_show_error_sub_on_event(void* context, SceneManagerEvent event void subghz_scene_show_error_sub_on_exit(void* context) { SubGhz* subghz = context; - - // Clear view Popup* popup = subghz->popup; - popup_set_header(popup, NULL, 0, 0, AlignCenter, AlignBottom); - popup_set_text(popup, NULL, 0, 0, AlignCenter, AlignTop); - popup_set_icon(popup, 0, 0, NULL); - popup_set_callback(popup, NULL); - popup_set_context(popup, NULL); - popup_set_timeout(popup, 0); - popup_disable_timeout(popup); + + popup_reset(popup); + furi_string_reset(subghz->error_str); notification_message(subghz->notifications, &sequence_reset_rgb); diff --git a/applications/main/subghz/scenes/subghz_scene_show_only_rx.c b/applications/main/subghz/scenes/subghz_scene_show_only_rx.c index 3bc08e5b4da..1907c41926c 100644 --- a/applications/main/subghz/scenes/subghz_scene_show_only_rx.c +++ b/applications/main/subghz/scenes/subghz_scene_show_only_rx.c @@ -43,14 +43,7 @@ bool subghz_scene_show_only_rx_on_event(void* context, SceneManagerEvent event) void subghz_scene_show_only_rx_on_exit(void* context) { SubGhz* subghz = context; - - // Clear view Popup* popup = subghz->popup; - popup_set_header(popup, NULL, 0, 0, AlignCenter, AlignBottom); - popup_set_text(popup, NULL, 0, 0, AlignCenter, AlignTop); - popup_set_icon(popup, 0, 0, NULL); - popup_set_callback(popup, NULL); - popup_set_context(popup, NULL); - popup_set_timeout(popup, 0); - popup_disable_timeout(popup); + + popup_reset(popup); } From f9730bcafeafec8afbe82fa676133b413a6bbae0 Mon Sep 17 00:00:00 2001 From: hedger Date: Sat, 12 Nov 2022 14:03:22 +0400 Subject: [PATCH 224/824] fbt: lint fixes (#2008) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * lint: exclude "lib" subfolder from naming checks; fbt: moved LINT_SOURCES from literal strings to Dir() nodes * lint: also exclude hidden directories Co-authored-by: あく --- SConstruct | 4 ++-- firmware.scons | 2 +- firmware/SConscript | 2 +- furi/SConscript | 3 +-- lib/SConscript | 26 +++++++++++++------------- lib/lfrfid/SConscript | 2 +- lib/nfc/SConscript | 2 +- scripts/lint.py | 16 +++++++++++++--- 8 files changed, 33 insertions(+), 24 deletions(-) diff --git a/SConstruct b/SConstruct index 34ff80bc96a..474175c1421 100644 --- a/SConstruct +++ b/SConstruct @@ -241,13 +241,13 @@ distenv.PhonyTarget( distenv.PhonyTarget( "lint", "${PYTHON3} ${FBT_SCRIPT_DIR}/lint.py check ${LINT_SOURCES}", - LINT_SOURCES=firmware_env["LINT_SOURCES"], + LINT_SOURCES=[n.srcnode() for n in firmware_env["LINT_SOURCES"]], ) distenv.PhonyTarget( "format", "${PYTHON3} ${FBT_SCRIPT_DIR}/lint.py format ${LINT_SOURCES}", - LINT_SOURCES=firmware_env["LINT_SOURCES"], + LINT_SOURCES=[n.srcnode() for n in firmware_env["LINT_SOURCES"]], ) # PY_LINT_SOURCES contains recursively-built modules' SConscript files + application manifests diff --git a/firmware.scons b/firmware.scons index da5caba5391..d674bf160ea 100644 --- a/firmware.scons +++ b/firmware.scons @@ -22,7 +22,7 @@ env = ENV.Clone( FW_FLAVOR=fw_build_meta["flavor"], LIB_DIST_DIR=fw_build_meta["build_dir"].Dir("lib"), LINT_SOURCES=[ - "applications", + Dir("applications"), ], LIBPATH=[ "${LIB_DIST_DIR}", diff --git a/firmware/SConscript b/firmware/SConscript index 19dde2e4cd6..a16f14e65a3 100644 --- a/firmware/SConscript +++ b/firmware/SConscript @@ -1,7 +1,7 @@ Import("env") env.Append( - LINT_SOURCES=["firmware"], + LINT_SOURCES=[Dir(".")], SDK_HEADERS=[ *env.GlobRecursive("*.h", "targets/furi_hal_include", "*_i.h"), *env.GlobRecursive("*.h", "targets/f${TARGET_HW}/furi_hal", "*_i.h"), diff --git a/furi/SConscript b/furi/SConscript index f95ef13f91b..8f8caeb8771 100644 --- a/furi/SConscript +++ b/furi/SConscript @@ -2,8 +2,7 @@ Import("env") env.Append( LINT_SOURCES=[ - "furi", - "furi/core", + Dir("."), ] ) diff --git a/lib/SConscript b/lib/SConscript index 60ffabfa932..abede5f3324 100644 --- a/lib/SConscript +++ b/lib/SConscript @@ -2,19 +2,19 @@ Import("env") env.Append( LINT_SOURCES=[ - "lib/app-scened-template", - "lib/digital_signal", - "lib/drivers", - "lib/flipper_format", - "lib/infrared", - "lib/nfc", - "lib/one_wire", - "lib/ST25RFAL002", - "lib/subghz", - "lib/toolbox", - "lib/u8g2", - "lib/update_util", - "lib/print", + Dir("app-scened-template"), + Dir("digital_signal"), + Dir("drivers"), + Dir("flipper_format"), + Dir("infrared"), + Dir("nfc"), + Dir("one_wire"), + Dir("ST25RFAL002"), + Dir("subghz"), + Dir("toolbox"), + Dir("u8g2"), + Dir("update_util"), + Dir("print"), ], SDK_HEADERS=[ File("one_wire/one_wire_host_timing.h"), diff --git a/lib/lfrfid/SConscript b/lib/lfrfid/SConscript index 69ea9d3c162..f9431ca75f2 100644 --- a/lib/lfrfid/SConscript +++ b/lib/lfrfid/SConscript @@ -2,7 +2,7 @@ Import("env") env.Append( LINT_SOURCES=[ - "lib/lfrfid", + Dir("."), ], CPPPATH=[ "#/lib/lfrfid", diff --git a/lib/nfc/SConscript b/lib/nfc/SConscript index c6b70a67790..b086298de2a 100644 --- a/lib/nfc/SConscript +++ b/lib/nfc/SConscript @@ -5,7 +5,7 @@ env.Append( "#/lib/nfc", ], SDK_HEADERS=[ - File("#/lib/nfc/nfc_device.h"), + File("nfc_device.h"), ], ) diff --git a/scripts/lint.py b/scripts/lint.py index c178c8763a8..58f2d69f559 100755 --- a/scripts/lint.py +++ b/scripts/lint.py @@ -35,11 +35,23 @@ def init(self): ) self.parser_format.set_defaults(func=self.format) + @staticmethod + def _filter_lint_directories(dirnames: list[str]): + # Skipping 3rd-party code - usually resides in subfolder "lib" + if "lib" in dirnames: + dirnames.remove("lib") + # Skipping hidden folders + for dirname in dirnames.copy(): + if dirname.startswith("."): + dirnames.remove(dirname) + def _check_folders(self, folders: list): show_message = False pattern = re.compile(SOURCE_CODE_DIR_PATTERN) for folder in folders: for dirpath, dirnames, filenames in os.walk(folder): + self._filter_lint_directories(dirnames) + for dirname in dirnames: if not pattern.match(dirname): to_fix = os.path.join(dirpath, dirname) @@ -54,9 +66,7 @@ def _find_sources(self, folders: list): output = [] for folder in folders: for dirpath, dirnames, filenames in os.walk(folder): - # Skipping 3rd-party code - usually resides in subfolder "lib" - if "lib" in dirnames: - dirnames.remove("lib") + self._filter_lint_directories(dirnames) for filename in filenames: ext = os.path.splitext(filename.lower())[1] From 73441af9c65228030652230ff65ea850b8969485 Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Sat, 12 Nov 2022 14:55:42 +0300 Subject: [PATCH 225/824] BadUSB and Archive fixes (#2005) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * BsdUsb: fix empty lines handling * Archive: folders and unknown files rename fix Co-authored-by: あく --- .../main/archive/scenes/archive_scene_browser.c | 2 +- .../main/archive/scenes/archive_scene_rename.c | 4 +++- .../main/archive/views/archive_browser_view.c | 1 - applications/main/bad_usb/bad_usb_script.c | 14 ++++++-------- lib/toolbox/path.c | 2 +- 5 files changed, 11 insertions(+), 12 deletions(-) diff --git a/applications/main/archive/scenes/archive_scene_browser.c b/applications/main/archive/scenes/archive_scene_browser.c index 9dc671617ed..04f4dcc3a5c 100644 --- a/applications/main/archive/scenes/archive_scene_browser.c +++ b/applications/main/archive/scenes/archive_scene_browser.c @@ -133,7 +133,7 @@ bool archive_scene_browser_on_event(void* context, SceneManagerEvent event) { case ArchiveBrowserEventFileMenuRename: if(favorites) { browser->callback(ArchiveBrowserEventEnterFavMove, browser->context); - } else if((archive_is_known_app(selected->type)) && (selected->is_app == false)) { + } else if(selected->is_app == false) { archive_show_file_menu(browser, false); scene_manager_set_scene_state( archive->scene_manager, ArchiveAppSceneBrowser, SCENE_STATE_NEED_REFRESH); diff --git a/applications/main/archive/scenes/archive_scene_rename.c b/applications/main/archive/scenes/archive_scene_rename.c index 1451428bdf9..37f860a9a58 100644 --- a/applications/main/archive/scenes/archive_scene_rename.c +++ b/applications/main/archive/scenes/archive_scene_rename.c @@ -57,9 +57,11 @@ bool archive_scene_rename_on_event(void* context, SceneManagerEvent event) { ArchiveFile_t* file = archive_get_current_file(archive->browser); FuriString* path_dst; + path_dst = furi_string_alloc(); path_extract_dirname(path_src, path_dst); - furi_string_cat_printf(path_dst, "/%s%s", archive->text_store, known_ext[file->type]); + furi_string_cat_printf( + path_dst, "/%s%s", archive->text_store, archive->file_extension); storage_common_rename(fs_api, path_src, furi_string_get_cstr(path_dst)); furi_record_close(RECORD_STORAGE); diff --git a/applications/main/archive/views/archive_browser_view.c b/applications/main/archive/views/archive_browser_view.c index a2e219b9530..65be421357c 100644 --- a/applications/main/archive/views/archive_browser_view.c +++ b/applications/main/archive/views/archive_browser_view.c @@ -65,7 +65,6 @@ static void render_item_menu(Canvas* canvas, ArchiveBrowserViewModel* model) { if(!archive_is_known_app(selected->type)) { furi_string_set(menu[0], "---"); furi_string_set(menu[1], "---"); - furi_string_set(menu[2], "---"); } else { if(model->tab_idx == ArchiveTabFavorites) { furi_string_set(menu[2], "Move"); diff --git a/applications/main/bad_usb/bad_usb_script.c b/applications/main/bad_usb/bad_usb_script.c index ae618114957..295cc1c3e38 100644 --- a/applications/main/bad_usb/bad_usb_script.c +++ b/applications/main/bad_usb/bad_usb_script.c @@ -237,12 +237,8 @@ static int32_t const char* line_tmp = furi_string_get_cstr(line); bool state = false; - for(uint32_t i = 0; i < line_len; i++) { - if((line_tmp[i] != ' ') && (line_tmp[i] != '\t') && (line_tmp[i] != '\n')) { - line_tmp = &line_tmp[i]; - break; // Skip spaces and tabs - } - if(i == line_len - 1) return SCRIPT_STATE_NEXT_LINE; // Skip empty lines + if(line_len == 0) { + return SCRIPT_STATE_NEXT_LINE; // Skip empty lines } FURI_LOG_D(WORKER_TAG, "line:%s", line_tmp); @@ -450,10 +446,12 @@ static int32_t ducky_script_execute_next(BadUsbScript* bad_usb, File* script_fil bad_usb->st.line_cur++; bad_usb->buf_len = bad_usb->buf_len + bad_usb->buf_start - (i + 1); bad_usb->buf_start = i + 1; + furi_string_trim(bad_usb->line); delay_val = ducky_parse_line( bad_usb, bad_usb->line, bad_usb->st.error, sizeof(bad_usb->st.error)); - - if(delay_val < 0) { + if(delay_val == SCRIPT_STATE_NEXT_LINE) { // Empty line + return 0; + } else if(delay_val < 0) { bad_usb->st.error_line = bad_usb->st.line_cur; FURI_LOG_E(WORKER_TAG, "Unknown command at line %u", bad_usb->st.line_cur); return SCRIPT_STATE_ERROR; diff --git a/lib/toolbox/path.c b/lib/toolbox/path.c index 53e9fc0923c..ce65aca4fd3 100644 --- a/lib/toolbox/path.c +++ b/lib/toolbox/path.c @@ -38,7 +38,7 @@ void path_extract_extension(FuriString* path, char* ext, size_t ext_len_max) { size_t dot = furi_string_search_rchar(path, '.'); size_t filename_start = furi_string_search_rchar(path, '/'); - if((dot > 0) && (filename_start < dot)) { + if((dot != FURI_STRING_FAILURE) && (filename_start < dot)) { strlcpy(ext, &(furi_string_get_cstr(path))[dot], ext_len_max); } } From b56fed477a2cbad5f8cfbce8af63adcb7da8bd39 Mon Sep 17 00:00:00 2001 From: hedger Date: Sat, 12 Nov 2022 21:22:40 +0400 Subject: [PATCH 226/824] Path handling fixes in toolchain download #2010 --- scripts/toolchain/fbtenv.cmd | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/scripts/toolchain/fbtenv.cmd b/scripts/toolchain/fbtenv.cmd index 44a2551f772..2305e891f75 100644 --- a/scripts/toolchain/fbtenv.cmd +++ b/scripts/toolchain/fbtenv.cmd @@ -5,7 +5,7 @@ if not [%FBT_ROOT%] == [] ( ) set "FBT_ROOT=%~dp0\..\..\" -pushd %FBT_ROOT% +pushd "%FBT_ROOT%" set "FBT_ROOT=%cd%" popd @@ -15,23 +15,25 @@ if not [%FBT_NOENV%] == [] ( set "FLIPPER_TOOLCHAIN_VERSION=19" -if [%FBT_TOOLCHAIN_ROOT%] == [] ( +if ["%FBT_TOOLCHAIN_ROOT%"] == [""] ( set "FBT_TOOLCHAIN_ROOT=%FBT_ROOT%\toolchain\x86_64-windows" ) +set "FBT_TOOLCHAIN_VERSION_FILE=%FBT_TOOLCHAIN_ROOT%\VERSION" + if not exist "%FBT_TOOLCHAIN_ROOT%" ( - powershell -ExecutionPolicy Bypass -File "%FBT_ROOT%\scripts\toolchain\windows-toolchain-download.ps1" "%flipper_toolchain_version%" "%FBT_TOOLCHAIN_ROOT%" + powershell -ExecutionPolicy Bypass -File "%FBT_ROOT%\scripts\toolchain\windows-toolchain-download.ps1" %flipper_toolchain_version% "%FBT_TOOLCHAIN_ROOT%" ) -if not exist "%FBT_TOOLCHAIN_ROOT%\VERSION" ( - powershell -ExecutionPolicy Bypass -File "%FBT_ROOT%\scripts\toolchain\windows-toolchain-download.ps1" "%flipper_toolchain_version%" "%FBT_TOOLCHAIN_ROOT%" + +if not exist "%FBT_TOOLCHAIN_VERSION_FILE%" ( + powershell -ExecutionPolicy Bypass -File "%FBT_ROOT%\scripts\toolchain\windows-toolchain-download.ps1" %flipper_toolchain_version% "%FBT_TOOLCHAIN_ROOT%" ) -set /p REAL_TOOLCHAIN_VERSION=<"%FBT_TOOLCHAIN_ROOT%\VERSION" +set /p REAL_TOOLCHAIN_VERSION=<"%FBT_TOOLCHAIN_VERSION_FILE%" if not "%REAL_TOOLCHAIN_VERSION%" == "%FLIPPER_TOOLCHAIN_VERSION%" ( - powershell -ExecutionPolicy Bypass -File "%FBT_ROOT%\scripts\toolchain\windows-toolchain-download.ps1" "%flipper_toolchain_version%" "%FBT_TOOLCHAIN_ROOT%" + powershell -ExecutionPolicy Bypass -File "%FBT_ROOT%\scripts\toolchain\windows-toolchain-download.ps1" %flipper_toolchain_version% "%FBT_TOOLCHAIN_ROOT%" ) - set "HOME=%USERPROFILE%" set "PYTHONHOME=%FBT_TOOLCHAIN_ROOT%\python" set "PYTHONPATH=" From 41de5f3c5221aa8bc485aab7bb472a7152003f54 Mon Sep 17 00:00:00 2001 From: hedger Date: Sat, 12 Nov 2022 22:28:29 +0400 Subject: [PATCH 227/824] fbt: more fixes for windows environment #2011 --- scripts/toolchain/fbtenv.cmd | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/toolchain/fbtenv.cmd b/scripts/toolchain/fbtenv.cmd index 2305e891f75..9fbd8fd9b60 100644 --- a/scripts/toolchain/fbtenv.cmd +++ b/scripts/toolchain/fbtenv.cmd @@ -1,6 +1,6 @@ @echo off -if not [%FBT_ROOT%] == [] ( +if not ["%FBT_ROOT%"] == [""] ( goto already_set ) @@ -9,7 +9,7 @@ pushd "%FBT_ROOT%" set "FBT_ROOT=%cd%" popd -if not [%FBT_NOENV%] == [] ( +if not ["%FBT_NOENV%"] == [""] ( exit /b 0 ) From cf5b87f82e9d120bd33f5a285fc691d0e9747b30 Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Wed, 23 Nov 2022 12:28:44 +0400 Subject: [PATCH 228/824] SubGhz: add protocol Nice_Flo 20bit (#1983) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- lib/subghz/protocols/nice_flo.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/subghz/protocols/nice_flo.c b/lib/subghz/protocols/nice_flo.c index ecd866a725d..a57d5f4da54 100644 --- a/lib/subghz/protocols/nice_flo.c +++ b/lib/subghz/protocols/nice_flo.c @@ -138,9 +138,9 @@ bool subghz_protocol_encoder_nice_flo_deserialize(void* context, FlipperFormat* FURI_LOG_E(TAG, "Deserialize error"); break; } - if((instance->generic.data_count_bit != - subghz_protocol_nice_flo_const.min_count_bit_for_found) && - (instance->generic.data_count_bit != + if((instance->generic.data_count_bit < + subghz_protocol_nice_flo_const.min_count_bit_for_found) || + (instance->generic.data_count_bit > 2 * subghz_protocol_nice_flo_const.min_count_bit_for_found)) { FURI_LOG_E(TAG, "Wrong number of bits in key"); break; @@ -297,9 +297,9 @@ bool subghz_protocol_decoder_nice_flo_deserialize(void* context, FlipperFormat* if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { break; } - if((instance->generic.data_count_bit != - subghz_protocol_nice_flo_const.min_count_bit_for_found) && - (instance->generic.data_count_bit != + if((instance->generic.data_count_bit < + subghz_protocol_nice_flo_const.min_count_bit_for_found) || + (instance->generic.data_count_bit > 2 * subghz_protocol_nice_flo_const.min_count_bit_for_found)) { FURI_LOG_E(TAG, "Wrong number of bits in key"); break; From 00fcd9cfcd9eae7ea97cbe94504ac852cc63a519 Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Wed, 23 Nov 2022 13:29:30 +0400 Subject: [PATCH 229/824] [FL-2976] SubGhz: add protocol "Ansonic" (#2000) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * SubGhz: add protocol "Ansonic" * SubGhz: fix encoder "Ansonic" * SubGhz: add unit_test "Ansonic" * SubGhz: fix flag * SubGhz: protocol "Ansonic" fix timing Co-authored-by: あく --- .../debug/unit_tests/subghz/subghz_test.c | 17 +- assets/unit_tests/subghz/ansonic.sub | 7 + assets/unit_tests/subghz/ansonic_raw.sub | 11 + assets/unit_tests/subghz/test_random_raw.sub | 21 ++ lib/subghz/protocols/ansonic.c | 346 ++++++++++++++++++ lib/subghz/protocols/ansonic.h | 107 ++++++ lib/subghz/protocols/protocol_items.c | 2 +- lib/subghz/protocols/protocol_items.h | 1 + 8 files changed, 510 insertions(+), 2 deletions(-) create mode 100644 assets/unit_tests/subghz/ansonic.sub create mode 100644 assets/unit_tests/subghz/ansonic_raw.sub create mode 100644 lib/subghz/protocols/ansonic.c create mode 100644 lib/subghz/protocols/ansonic.h diff --git a/applications/debug/unit_tests/subghz/subghz_test.c b/applications/debug/unit_tests/subghz/subghz_test.c index dbe03d88c33..3052448b758 100644 --- a/applications/debug/unit_tests/subghz/subghz_test.c +++ b/applications/debug/unit_tests/subghz/subghz_test.c @@ -13,7 +13,7 @@ #define CAME_ATOMO_DIR_NAME EXT_PATH("subghz/assets/came_atomo") #define NICE_FLOR_S_DIR_NAME EXT_PATH("subghz/assets/nice_flor_s") #define TEST_RANDOM_DIR_NAME EXT_PATH("unit_tests/subghz/test_random_raw.sub") -#define TEST_RANDOM_COUNT_PARSE 232 +#define TEST_RANDOM_COUNT_PARSE 244 #define TEST_TIMEOUT 10000 static SubGhzEnvironment* environment_handler; @@ -437,6 +437,13 @@ MU_TEST(subghz_decoder_clemsa_test) { "Test decoder " SUBGHZ_PROTOCOL_CLEMSA_NAME " error\r\n"); } +MU_TEST(subghz_decoder_ansonic_test) { + mu_assert( + subghz_decoder_test( + EXT_PATH("unit_tests/subghz/ansonic_raw.sub"), SUBGHZ_PROTOCOL_ANSONIC_NAME), + "Test decoder " SUBGHZ_PROTOCOL_ANSONIC_NAME " error\r\n"); +} + //test encoders MU_TEST(subghz_encoder_princeton_test) { mu_assert( @@ -558,6 +565,12 @@ MU_TEST(subghz_encoder_clemsa_test) { "Test encoder " SUBGHZ_PROTOCOL_CLEMSA_NAME " error\r\n"); } +MU_TEST(subghz_encoder_ansonic_test) { + mu_assert( + subghz_encoder_test(EXT_PATH("unit_tests/subghz/ansonic.sub")), + "Test encoder " SUBGHZ_PROTOCOL_ANSONIC_NAME " error\r\n"); +} + MU_TEST(subghz_random_test) { mu_assert(subghz_decode_random_test(TEST_RANDOM_DIR_NAME), "Random test error\r\n"); } @@ -598,6 +611,7 @@ MU_TEST_SUITE(subghz) { MU_RUN_TEST(subghz_decoder_magellan_test); MU_RUN_TEST(subghz_decoder_intertechno_v3_test); MU_RUN_TEST(subghz_decoder_clemsa_test); + MU_RUN_TEST(subghz_decoder_ansonic_test); MU_RUN_TEST(subghz_encoder_princeton_test); MU_RUN_TEST(subghz_encoder_came_test); @@ -619,6 +633,7 @@ MU_TEST_SUITE(subghz) { MU_RUN_TEST(subghz_encoder_magellan_test); MU_RUN_TEST(subghz_encoder_intertechno_v3_test); MU_RUN_TEST(subghz_encoder_clemsa_test); + MU_RUN_TEST(subghz_encoder_ansonic_test); MU_RUN_TEST(subghz_random_test); subghz_test_deinit(); diff --git a/assets/unit_tests/subghz/ansonic.sub b/assets/unit_tests/subghz/ansonic.sub new file mode 100644 index 00000000000..a7219b12ce9 --- /dev/null +++ b/assets/unit_tests/subghz/ansonic.sub @@ -0,0 +1,7 @@ +Filetype: Flipper SubGhz Key File +Version: 1 +Frequency: 433920000 +Preset: FuriHalSubGhzPresetOok650Async +Protocol: Ansonic +Bit: 12 +Key: 00 00 00 00 00 00 05 5A diff --git a/assets/unit_tests/subghz/ansonic_raw.sub b/assets/unit_tests/subghz/ansonic_raw.sub new file mode 100644 index 00000000000..6d4c78ebe69 --- /dev/null +++ b/assets/unit_tests/subghz/ansonic_raw.sub @@ -0,0 +1,11 @@ +Filetype: Flipper SubGhz RAW File +Version: 1 +Frequency: 433920000 +Preset: FuriHalSubGhzPresetOok650Async +Protocol: RAW +RAW_Data: 57 -144 401 -86 173 -202 143 -258 133 -88 257 -144 287 -58 402 -56 259 -230 259 -86 85 -96 95 -174 286 -162 57 -230 253 -400 229 -88 536 -58 85 -72 167 -110 263 -72 229 -58 85 -86 87 -262 119 -288 163 -210 321 -320 186 -140 261 -96 143 -456 117 -216 143 -246 239 -102 121 -72 71 -191 167 -263 191 -96 239 -80 57 -116 143 -118 167 -168 215 -288 191 -106 287 -114 517 -88 113 -394 173 -70 215 -100 661 -86 201 -114 259 -58 287 -86 57 -202 399 -200 57 -288 229 -144 115 -1425 83 -142 173 -86 459 -112 223 -144 201 -116 143 -114 196 -17422 287 -614 1075 -1132 533 -560 1125 -1112 561 -530 1133 -1088 585 -512 1129 -1104 563 -1102 571 -1072 591 -524 1137 -538 1115 -19392 589 -522 1139 -1090 569 -520 1137 -1100 587 -520 1135 -1092 569 -514 1155 -1076 599 -1062 585 -1086 585 -532 1125 -528 1145 -19396 581 -504 1155 -1064 619 -506 1145 -1080 595 -508 1149 -1062 587 -536 1129 -1088 585 -1090 589 -1062 587 -510 1177 -506 1149 -19394 587 -524 1147 -1076 597 -512 1129 -1072 617 -498 1159 -1066 599 -514 1147 -1076 595 -1060 613 -1060 589 -506 1179 -510 1155 -19378 605 -514 1129 -1098 601 -488 1177 -1062 583 -510 1161 -1072 587 -516 1169 -1058 587 -1082 589 -1076 593 -532 1121 -536 1127 -19444 549 -542 1129 -1092 601 -510 1147 -1098 581 -506 1123 -1100 585 -510 1147 -1106 591 -1060 597 -1060 595 -510 1151 -534 1157 -19390 593 -512 1163 -1064 597 -510 1173 -1042 617 -510 1159 -1056 579 -556 1133 -1054 609 -1060 607 -1078 585 -528 1147 -516 1135 -19444 559 -534 1165 -1052 605 -510 1149 -1062 599 -520 1173 -1054 591 -540 1111 -1116 573 -1088 593 -1042 615 -534 1117 -536 1129 -19436 569 -530 1157 -1052 605 -504 1177 -1062 591 -536 1113 -1114 589 -514 1125 -1112 563 -1084 587 -1080 589 -536 1123 -532 1165 -19392 599 -524 1143 -1080 595 -540 1125 -1090 563 -534 1149 -1084 567 -534 1147 -1092 587 -1086 585 -1064 565 -558 1123 -532 1143 -19450 549 -556 1121 -1086 585 -534 1137 -1094 583 -516 1133 -1114 563 -536 1123 -1108 573 -1082 597 -1078 565 -532 1143 -524 1135 -19464 561 -528 1149 -1066 613 -508 1151 -1076 587 -532 1125 -1076 609 -506 1175 -1076 563 -1106 563 -1084 591 -534 1157 -484 1179 -19414 589 -528 1131 -1096 587 -520 1163 -1080 563 -550 1121 -1098 555 -562 1117 -1082 575 -1112 563 -1104 559 -558 1121 -530 1149 -19442 591 -498 1175 -1066 585 -534 1121 -1114 565 -540 1115 -1096 585 -538 1123 -1110 559 -1086 585 -1084 589 -530 1125 -558 1121 -19454 563 -542 1163 -1044 585 -558 1131 -1092 571 -536 1133 -1088 587 -518 1149 -1086 601 -1058 587 -1080 611 -510 +RAW_Data: 1125 -536 1145 -19444 567 -542 1151 -1086 581 -534 1133 -1084 583 -530 1141 -1090 587 -516 1121 -1114 591 -1062 595 -1092 567 -538 1131 -536 1145 -19448 589 -516 1143 -1076 591 -540 1115 -1090 591 -538 1115 -1096 597 -512 1155 -1078 581 -1082 585 -1104 559 -530 1153 -516 1147 -19438 589 -526 1157 -1056 609 -534 1137 -1078 585 -532 1149 -1076 585 -536 1123 -1104 587 -1062 597 -1082 587 -536 1121 -554 1121 -19464 555 -560 1123 -1104 563 -542 1131 -1096 565 -536 1143 -1060 621 -492 1175 -1078 571 -1100 577 -1092 591 -494 1167 -538 1117 -19452 593 -538 1133 -1074 603 -510 1157 -1062 595 -548 1115 -1102 573 -538 1139 -1094 567 -1094 583 -1092 567 -536 1127 -560 1113 -19478 563 -534 1151 -1084 569 -534 1135 -1116 565 -534 1135 -1084 583 -532 1155 -1060 587 -1084 589 -1084 581 -536 1125 -546 1141 -19460 577 -536 1123 -1102 565 -554 1119 -1096 593 -520 1133 -1108 563 -546 1131 -1088 569 -1094 573 -1100 583 -526 1147 -540 1123 -19466 587 -530 1129 -1100 587 -510 1143 -1084 609 -510 1131 -1110 567 -542 1121 -1102 567 -1110 563 -1106 567 -532 1149 -518 1145 -19458 573 -534 1147 -1082 575 -556 1149 -1048 589 -532 1157 -1088 565 -536 1129 -1088 589 -1106 563 -1086 587 -536 1129 -548 1147 -4298 103 -56 87 -344 87 -258 85 -278 215 -70 77 -344 279 -56 87 -56 197 -198 143 -288 362 -86 605 -144 57 -268 95 -114 143 -144 173 -144 143 -402 57 -202 259 -391 652 -340 427 -230 173 -356 97 -144 111 -246 219 -96 191 -114 173 -288 115 -56 109 -106 199 -106 73 -130 57 -172 85 -260 373 -56 629 -200 690 -230 273 -120 85 -460 85 -314 77 -78 111 -88 401 -116 171 -312 71 -500 81 -224 229 -88 257 -370 181 -172 200 -116 535 -174 113 -294 213 -359 445 -144 258 -114 115 -202 675 -509 239 -432 373 -538 85 -58 113 -86 761 -104 113 -318 443 -70 143 -144 647 -204 111 -334 87 -114 115 -144 113 -188 177 -144 199 -260 143 -86 87 -622 57 -116 171 -58 139 -222 55 -346 315 -76 345 -114 139 -171 195 -52 53 -98 119 -144 143 -244 95 -72 95 -96 167 -302 253 -186 307 -444 287 -449 115 -172 57 -172 316 -202 85 -370 697 -116 57 -144 171 -202 259 -114 85 -144 87 -315 85 -58 201 -116 171 -272 121 -358 171 -403 113 -86 115 -202 489 -229 115 -392 95 -116 171 -140 93 -102 143 -543 245 -358 215 -120 387 -288 171 -202 221 -202 115 -748 57 -316 143 -260 143 -288 115 -316 115 -58 85 -288 143 -460 485 -96 71 -104 199 -96 199 -202 143 -86 201 -116 85 -230 211 -288 115 -605 365 -126 53 -172 +RAW_Data: 317 -144 57 -486 53 -282 115 -585 97 -72 229 -174 257 -440 225 -86 173 -518 243 -167 95 -259 137 -96 694 -58 227 -80 279 -287 71 -72 301 -72 121 -106 51 -84 57 -58 199 -260 143 -288 219 -174 113 -681 115 -172 403 -58 113 -116 113 -432 171 -202 55 -108 95 -212 113 -72 527 -166 95 -212 195 -108 603 -142 239 -296 173 -346 373 -287 53 -80 79 -72 95 -238 95 -312 167 -618 143 -288 95 -72 95 -72 141 -210 55 -258 143 -328 305 -58 87 -86 315 -116 195 -218 85 -290 285 -220 215 -189 201 -58 57 -645 119 -96 71 -144 119 -406 143 -72 191 -72 631 -268 344 -56 115 -260 315 -140 455 -518 57 -58 171 -144 488 -86 219 -232 257 -144 85 -174 171 -260 115 -56 87 -166 197 -58 83 -56 85 -288 113 -410 115 -172 163 -202 113 -58 201 -144 201 -86 143 -264 167 -212 113 -116 139 -72 181 -287 343 -430 201 -260 201 -462 143 -192 301 -230 191 -454 187 -144 315 -164 143 -477 165 -58 201 -114 143 -490 115 -86 201 -58 113 -88 85 -58 203 -198 375 -86 171 -346 95 -88 257 -170 81 -56 143 -172 335 -230 173 -202 133 -471 187 -264 215 -86 115 -198 159 -72 179 -112 195 -116 449 -216 93 -96 167 -216 71 -216 71 -166 235 -86 447 -102 101 -226 195 -213 71 -144 215 -144 215 -261 241 -136 269 -142 263 -311 215 -172 201 -144 265 -168 71 -404 259 -86 85 -230 115 -650 143 -202 749 -512 248 -316 201 -154 71 -96 95 -360 105 -56 57 -432 95 -288 95 -286 95 -96 166 -144 93 -144 167 -150 904 -162 95 -526 287 -244 95 -240 383 -120 167 -394 430 -854 95 -72 143 -194 227 -120 167 -264 405 -144 143 -72 143 -72 141 -120 187 -86 143 -164 170 -96 143 -58 143 -86 402 -166 153 -120 95 -96 69 -96 71 -359 404 -338 71 -225 93 -74 97 -54 161 -114 319 -288 113 -116 459 -202 115 -114 115 -116 143 -86 57 -56 87 -114 85 -375 113 -58 311 -240 203 -288 95 -72 119 -383 213 -384 115 -86 171 -58 53 -104 401 -58 115 -86 373 -116 143 -144 161 -216 406 -72 263 -96 215 -72 95 -94 167 -96 191 -240 95 -94 214 -120 403 -116 200 -114 57 -172 220 -120 137 -364 334 -392 115 -260 199 -116 373 -188 95 -110 143 -172 87 -114 172 -230 57 -316 201 -56 249 -485 171 -202 87 -86 85 -144 345 -86 171 -58 259 -58 295 -120 95 -120 71 -192 635 -118 167 -96 375 -72 119 -120 261 -144 167 -96 95 -96 923 -215 71 -433 71 -477 +RAW_Data: 191 -240 85 -72 637 -408 213 -510 261 -168 143 -126 79 -106 167 -72 117 -218 251 -168 119 -96 215 -182 191 -238 517 -116 201 -144 255 -154 97 -94 215 -72 95 -120 71 -288 261 -106 434 -96 606 -232 229 -432 85 -174 343 -58 329 -156 55 -116 259 -144 488 -56 307 -339 115 -202 334 -88 113 -86 57 -174 143 -144 401 -376 85 -240 267 -82 95 -216 137 -158 85 -144 143 -58 221 -308 295 -114 87 -114 301 -120 358 -517 71 -262 191 -144 57 -140 165 -407 53 -262 217 -120 238 -358 119 -357 71 -72 119 -96 428 -72 95 -72 167 -72 93 -240 335 -96 357 -240 173 -230 143 -114 87 -200 143 -232 287 -150 97 -288 71 -72 93 -288 115 -58 143 -230 109 -264 71 -72 119 -72 238 -242 97 -78 163 -86 115 -518 79 -560 205 -449 969 -144 507 -86 231 -114 345 -58 979 -110 85 -288 287 -404 229 -202 57 -274 233 -86 115 -202 632 -230 85 -312 369 -392 460 -450 75 -280 85 -202 201 -86 229 -174 143 -144 233 -528 115 -212 127 -202 287 -172 403 -172 139 -128 165 -138 261 -392 143 -480 142 -189 291 -80 53 -283 167 -140 113 -1008 191 -144 119 -120 71 -193 241 -462 201 -58 143 -344 539 -316 113 -174 85 -116 113 -250 239 -168 405 -168 239 -158 85 -144 115 -86 57 -86 341 -144 171 -202 85 -202 115 -114 719 -88 55 -318 257 -56 254 -86 171 -116 459 -174 171 -329 95 -134 85 -314 431 -306 77 -316 401 -86 173 -404 281 -1073 488 -94 217 -78 101 -98 214 -120 215 -340 403 -535 143 -564 115 -116 199 -58 85 -174 315 -58 335 -136 55 -260 143 -144 229 -460 143 -58 143 -144 171 -202 115 -374 291 -130 339 -82 143 -58 171 -58 201 -86 85 -174 1022 -56 85 -82 255 -240 103 -202 431 -278 95 -216 119 -72 71 -96 71 -559 57 -144 171 -88 113 -86 231 -414 131 -192 237 -360 95 -168 145 -168 213 -120 167 -96 143 -110 57 -86 259 -56 87 -777 295 -96 57 -86 173 -86 171 -404 143 -172 231 -200 57 -441 55 -58 173 -56 87 -86 171 -72 287 -72 119 -262 119 -144 71 -72 121 -310 71 -302 113 -54 193 -80 307 -58 257 -232 143 -56 143 -116 219 -72 695 -70 71 -460 85 -232 719 -363 57 -402 604 -230 287 -138 83 -172 259 -58 171 -174 55 -88 489 -114 143 -116 171 -116 143 -58 199 -144 145 -343 374 -186 235 -140 77 -86 143 -202 143 -144 113 -144 143 -58 732 -96 263 -264 71 -206 95 -168 215 -144 271 -80 139 -88 85 -414 75 -100 +RAW_Data: 285 -96 627 -362 53 -84 201 -374 113 -202 115 -202 421 -316 85 -58 139 -224 87 -86 229 -58 243 -178 267 -288 95 -336 171 -96 213 -288 71 -405 95 -96 95 -384 95 -72 213 -72 95 -96 95 -272 87 -1083 85 -58 113 -88 257 -116 143 -292 175 -318 95 -120 95 -144 95 -72 71 -216 368 -116 373 -172 115 -58 85 -116 143 -86 85 -144 201 -86 201 -202 257 -144 201 -174 113 -144 115 -144 257 -202 585 -364 173 -138 287 -422 431 -86 85 -96 869 -186 95 -52 115 -86 115 -58 55 -276 365 -86 85 -489 171 -140 577 -106 718 -144 391 -232 195 -82 143 -172 109 -120 167 -96 280 -216 145 -240 215 -186 163 -96 141 -172 159 -603 257 -108 629 -192 119 -80 87 -172 57 -144 286 -86 57 -230 344 -58 113 -537 75 -96 537 -86 403 -196 167 -264 119 -238 119 -120 167 -96 95 -478 95 -120 167 -216 1085 -96 358 -72 263 -72 69 -120 143 -96 71 -96 191 -362 55 -144 57 -260 113 -58 85 -174 55 -88 257 -86 231 -194 55 -58 115 -56 55 -339 55 -58 374 -172 139 -82 419 -98 119 -261 71 -72 71 -240 713 -86 143 -218 295 -72 53 -56 431 -58 317 -144 161 -144 373 -144 173 -144 57 -114 85 -116 195 -72 708 -172 115 -86 191 -96 506 -120 71 -174 85 -58 363 -114 317 -230 316 -200 87 -114 57 -230 115 -315 173 -280 694 -212 453 -256 143 -202 113 -540 352 -116 257 -116 457 -56 109 -58 143 -230 259 -144 259 -525 119 -408 247 -112 389 -72 431 -96 137 -236 97 -474 201 -298 71 -82 55 -116 55 -112 199 -174 191 -86 143 -144 115 -114 317 -86 85 -230 87 -114 259 -84 107 -130 143 -94 153 -86 135 -94 215 -72 239 -94 435 -96 263 -142 166 -334 87 -194 179 -96 115 -284 135 -56 57 -144 463 -204 143 -316 201 -58 403 -86 141 -288 85 -202 139 -397 171 -174 305 -202 85 -144 373 -253 161 -492 181 -191 95 -216 315 -191 71 -166 97 -126 337 -96 71 -96 189 -168 295 -84 197 -86 259 -345 137 -144 167 -796 115 -344 455 -72 119 -96 119 -550 209 -88 85 -86 143 -340 167 -260 143 -537 85 -226 51 -537 57 -260 315 -461 51 -84 199 -358 383 -96 143 -257 115 -86 173 -86 201 -144 143 -316 85 -86 479 -88 85 -72 71 -104 115 -116 267 -72 137 -144 143 -116 85 -86 373 -288 115 -200 87 -114 259 -114 259 -462 143 -144 171 -86 57 -58 137 -144 57 -634 343 -72 205 -86 143 -258 57 -232 113 -230 461 -58 185 -74 537 -86 +RAW_Data: 535 -142 57 -58 55 -116 115 -432 85 -172 259 -192 167 -120 117 -72 119 -240 334 -72 71 -267 285 -144 119 -374 85 -88 85 -114 143 -202 229 -58 143 -202 115 -202 171 -86 71 -144 87 -56 173 -373 143 -116 113 -462 169 -80 215 -148 115 -336 85 -230 163 -432 85 -374 639 -174 85 -58 57 -82 295 -352 269 -532 414 -322 95 -287 263 -268 115 -56 259 -76 85 -282 401 -305 516 -114 115 -202 171 -86 451 -110 85 -346 201 -274 149 -202 85 -364 366 -258 57 -114 259 -172 142 -144 85 -116 85 -480 171 -144 57 -352 115 -116 535 -404 315 -202 163 -158 517 -316 215 -98 85 -346 85 -144 87 -86 257 -82 167 -58 85 -116 113 -894 233 -186 77 -266 147 -72 71 -82 57 -86 171 -58 57 -86 201 -364 143 -202 115 -114 85 -88 113 -86 87 -230 57 -76 613 -72 85 -96 209 -346 458 -58 547 -490 201 -315 315 -116 75 -168 359 -335 95 -384 93 -120 71 -312 251 -366 233 -96 189 -240 263 -192 271 -58 115 -58 229 -346 459 -174 113 -144 173 -144 218 -224 57 -116 215 -72 103 -202 513 -210 433 -116 113 -174 650 -273 147 -450 375 -86 115 -172 536 -84 85 -230 85 -58 195 -468 287 -110 551 -214 167 -311 213 -250 85 -58 85 -355 113 -230 115 -144 117 -288 195 -202 57 -376 123 -144 236 -168 553 -284 119 -72 143 -188 161 -120 93 -312 335 -58 55 -260 105 -244 143 -120 381 -268 173 -268 635 -168 453 -318 71 -167 71 -406 191 -172 215 -408 119 -144 93 -120 97 -130 143 -192 308 -122 147 -550 313 -96 139 -162 167 -96 431 -80 83 -112 201 -86 287 -86 229 -116 57 -288 113 -174 143 -116 113 -144 115 -518 57 -230 57 -172 231 -86 113 -314 183 -144 119 -72 165 -446 81 -86 135 -190 143 -96 71 -72 411 -96 143 -120 69 -216 349 -72 95 -96 517 -646 163 -86 113 -116 171 -116 143 -116 113 -287 259 -114 517 -168 141 -116 105 -72 95 -96 311 -118 159 -310 191 -54 143 -258 115 -450 219 -54 339 -372 239 -72 167 -174 113 -58 57 -144 259 -172 143 -336 113 -174 85 -230 83 -668 85 -202 113 -144 57 -116 373 -316 719 -288 115 -58 75 -120 139 -144 229 -144 57 -144 171 -192 391 -202 403 -58 315 -188 259 -56 115 -144 85 -404 57 -58 105 -102 429 -406 81 -172 57 -144 287 -230 287 -220 317 -458 283 -58 113 -86 269 -72 281 -58 85 -202 113 -52 421 -58 229 -480 259 -58 143 -660 155 -638 123 -86 57 -86 143 -346 143 -144 57 -144 \ No newline at end of file diff --git a/assets/unit_tests/subghz/test_random_raw.sub b/assets/unit_tests/subghz/test_random_raw.sub index 928838d3c8f..a6a3c9866b0 100644 --- a/assets/unit_tests/subghz/test_random_raw.sub +++ b/assets/unit_tests/subghz/test_random_raw.sub @@ -145,3 +145,24 @@ RAW_Data: -66 133 -66 97 -166 561 -100 895 -132 1323 -66 10873 -3752 99 -722 229 RAW_Data: -5434 65 -298 133 -132 131 -68 231 -200 661 -132 9517 -424 97 -1456 99 -1694 393 -100 131 -560 131 -196 197 -298 65 -428 229 -196 297 -266 131 -166 2435 -66 10161 -11230 65 -1320 131 -298 265 -532 231 -200 1291 -68 631 -66 12645 -4048 133 -66 67 -132 167 -266 163 -66 397 -132 197 -132 299 -98 197 -198 2903 -66 2361 -66 9627 -3588 197 -332 165 -68 331 -68 197 -132 99 -100 663 -66 363 -230 231 -166 131 -100 201 -298 163 -132 133 -202 363 -300 397 -102 263 -100 165 -66 1221 -66 1479 -132 165 -98 229 -12976 263 -66 363 -134 231 -66 629 -132 327 -100 97 -130 99 -164 227 -64 297 -132 397 -164 425 -198 97 -198 99 -66 365 -164 199 -102 97 -66 1817 -13524 231 -134 16907 -4086 233 -630 65 -396 201 -66 165 -198 67 -198 99 -664 2117 -166 12473 -446 2649 -440 2661 -420 2651 -422 2681 -418 2703 -400 365 -2724 387 -2696 2695 -414 357 -2704 2707 -386 389 -2700 2687 -392 405 -2706 2695 -402 363 -21268 2707 -388 377 -2706 2691 -404 2699 -382 2717 -382 2707 -378 2693 -416 2687 -396 363 -2736 355 -2748 2659 -416 365 -2708 2715 -388 377 -2708 2697 -404 363 -2730 2673 -420 355 -21268 2655 -460 319 -2766 2663 -448 2631 -436 2665 -418 2683 -410 2681 -416 2701 -386 383 -2700 375 -2744 2669 -416 353 -2730 2685 -416 357 -2708 2721 -380 369 -2724 2697 -382 385 -21260 2701 -418 353 -2720 2673 -418 2675 -408 2693 -384 2715 -386 2717 -386 2691 -404 363 -2732 387 -2702 2669 -412 359 -2736 2699 -380 381 -2728 2675 -416 381 -2720 2675 -414 347 -21280 2685 -390 377 -2724 2689 -416 2673 -408 2705 -382 2695 -410 2689 -414 2661 -418 385 -2704 369 -2704 2693 -416 375 -2726 2661 -420 355 -2728 2711 -388 375 -2702 2691 -410 363 -21252 2659 -488 287 -2794 2651 -448 2629 -436 2671 -416 2695 -416 2663 -406 2699 -384 383 -2730 367 -2702 2695 -418 385 -2702 2685 -412 349 -2744 2693 -366 389 -2714 2693 -394 381 -21266 2685 -418 363 -2730 2683 -382 2693 -418 2675 -410 2699 -384 2719 -382 2707 -380 359 -2734 387 -2704 2709 -380 361 -2732 2699 -418 357 -2728 2667 -416 383 -2696 2709 -380 391 -21228 2685 -458 307 -2800 2647 -412 2659 -432 2667 -416 2695 -416 2675 -406 2675 -416 383 -2700 361 -2730 2687 -414 375 -2696 2701 -420 353 -2720 2711 -382 367 -2728 2675 -416 385 -21222 2735 -386 355 -2744 2687 -396 2679 -418 2701 -386 2705 -382 2681 -410 2697 -384 385 -2736 365 -2704 2715 -384 377 -2696 2697 -416 349 -2722 2707 -386 379 -2732 2671 -410 361 -21258 2681 -464 297 -2796 2629 -456 2655 -420 2661 -448 2663 -404 2695 -382 2715 -380 371 -2740 355 -2744 2679 -384 391 -2728 2675 -388 379 RAW_Data: -2728 2695 -414 357 -2704 2705 -418 357 -21262 2673 -416 383 -2696 2709 -380 2703 -384 2699 -418 2671 -408 2695 -382 2713 -386 379 -2730 357 -2732 2695 -384 383 -2730 2679 -416 357 -2708 2701 -410 349 -2736 2697 -382 385 -21252 2669 -478 289 -2790 2647 -426 2651 -444 2653 -430 2659 -418 2695 -414 2681 -402 349 -2738 383 -2722 2677 -414 347 -2744 2691 -382 369 -2730 2691 -384 383 -2734 2679 -414 347 -21264 2705 -386 379 -2736 2667 -410 2695 -382 2715 -380 2709 -420 2665 -392 2713 -382 383 -2730 365 -2728 2665 -418 383 -2696 2693 -418 357 -2710 2711 -380 375 -2718 2701 -416 357 -21238 2677 -484 311 -2766 2635 -444 2657 -420 2663 -422 2695 -416 2667 -428 2675 -396 363 -73890 133 -98 131 -132 129 -658 99 -66 853 -100 63 -100 361 -98 1589 -66 1231 -132 65 -100 297 -198 65 -132 265 -66 9857 -4672 165 -1030 97 -1394 65 -200 2687 -68 6873 -8336 99 -1156 97 -66 163 -232 163 -262 197 -132 295 -132 263 -166 953 -100 263 -130 393 -164 295 -64 329 -66 393 -164 823 -130 165 -66 6133 -8436 165 -164 265 -266 65 -362 197 -696 3181 -132 363 -98 65 -166 131 -66 399 -132 663 -396 329 -66 7335 -7578 497 -230 627 -264 99 -366 99 -132 131 -134 265 -498 163 -100 1323 -66 265 -66 1129 -100 399 -132 365 -100 795 -68 397 -98 597 -364 297 -132 361 -132 265 -132 8591 -4740 65 -100 131 -166 199 -1088 97 -296 99 -528 131 -98 661 -66 401 -198 1157 -166 361 -164 495 -100 165 -66 297 -100 1423 -66 3067 -5658 67 -6406 197 -1092 65 -530 659 -68 265 -100 991 -68 231 -230 297 -66 327 -66 131 -132 659 -134 131 -100 1183 -132 263 -98 621 -66 2075 -6976 65 -5138 67 -132 129 -664 67 -132 165 -100 331 -466 231 -68 467 -98 563 -66 231 -100 531 -66 465 -66 1023 -166 297 -134 3409 -12290 67 -164 99 -532 133 -166 263 -66 231 -66 721 -64 131 -68 959 -134 495 -100 299 -98 497 -98 365 -100 397 -232 297 -98 531 -66 3029 -12216 265 -132 99 -364 199 -234 131 -66 431 -166 333 -166 397 -132 327 -100 395 -66 197 -132 395 -66 527 -98 295 -100 97 -98 789 -132 363 -132 297 -200 2815 -4914 65 -6620 65 -462 65 -134 297 -66 497 -264 231 -198 2773 -134 365 -100 831 -166 131 -100 297 -132 861 -132 299 -100 561 -66 1381 -6946 65 -5516 231 -266 97 -1362 1093 -68 1621 -134 165 -332 297 -98 361 -228 97 -132 797 -98 3487 -13224 229 -164 65 -132 913 -66 1123 -98 527 -134 929 -98 723 -100 12259 -270 165 -132 67 -132 165 -1326 99 -98 65 -1194 431 -66 695 -66 733 -134 197 RAW_Data: -134 10801 -166 67 -6130 133 -198 231 -334 365 -98 229 -132 165 -68 231 -166 14501 -524 65 -328 131 -498 129 -1288 65 -494 163 -64 165 -66 527 -132 131 -132 1019 -198 129 -166 393 -198 65 -164 6411 -66 3255 -10642 65 -1320 165 -164 493 -492 559 -264 2555 -66 695 -66 1657 -164 855 -66 4001 -10526 97 -596 133 -298 67 -264 65 -300 65 -100 263 -166 231 -134 99 -100 2703 -68 13643 -4922 297 -100 65 -232 133 -198 331 -300 231 -66 331 -100 12047 -3872 97 -196 65 -494 329 -66 65 -890 97 -98 229 -164 195 -596 797 -66 861 -132 65 -66 231 -100 565 -66 65 -66 1297 -132 265 -66 363 -134 265 -364 297 -164 299 -134 297 -134 495 -98 11309 -3790 131 -1380 65 -758 65 -164 129 -460 65 -360 199 -100 563 -68 497 -198 363 -266 263 -100 165 -66 697 -66 1933 -13594 65 -762 1223 -132 1119 -196 361 -134 131 -100 793 -166 695 -68 231 -68 463 -66 11727 -4204 363 -264 131 -132 133 -1124 97 -100 163 -100 327 -100 331 -198 397 -66 397 -100 395 -100 163 -66 197 -564 1059 -7962 65 -100 65 -198 129 -362 99 -394 197 -296 495 -100 1357 -68 459 -66 593 -66 265 -68 301 -132 465 -66 231 -200 397 -66 397 -232 199 -298 12077 -4350 231 -796 363 -198 133 -264 65 -1132 597 -332 3295 -100 755 -98 231 -164 97 -264 459 -166 759 -164 3265 -12138 99 -232 99 -1228 1025 -100 393 -66 531 -132 693 -132 1063 -66 427 -64 297 -294 229 -98 9723 -5404 67 -466 99 -796 267 -98 201 -100 167 -264 461 -98 1415 -66 861 -66 267 -66 331 -134 1663 -66 2089 -7012 65 -100 101 -4804 431 -728 99 -100 65 -100 995 -134 165 -66 929 -100 65 -66 927 -100 1093 -168 99 -100 497 -66 665 -200 6517 -8312 165 -66 129 -66 559 -166 99 -430 65 -398 67 -66 593 -198 459 -132 261 -132 263 -130 723 -66 459 -100 325 -166 67 -198 559 -66 493 -66 11475 -3896 99 -266 99 -66 197 -1092 129 -198 361 -166 163 -98 263 -196 759 -100 265 -100 365 -630 4635 -12748 65 -1712 461 -100 497 -66 395 -98 265 -98 229 -164 529 -132 297 -66 565 -132 987 -132 8665 -2820 2265 -450 313 -2774 2643 -442 325 -2772 2665 -416 359 -2734 2667 -386 379 -21274 2657 -474 293 -2810 2619 -466 2613 -476 2629 -452 2663 -388 2683 -418 2705 -400 365 -2722 387 -2700 2697 -380 361 -2732 2691 -418 361 -2732 2667 -416 383 -2698 2697 -416 357 -21238 2715 -384 383 -2732 2685 -416 2667 -416 2695 -398 2671 -418 2687 -390 2713 -382 383 -2730 365 -2728 2661 -416 379 -2716 2685 -384 379 -2720 2703 -378 401 -2718 2671 +RAW_Data: 889 -130 325 -64 457 -560 165 -68 199 -170 67 -66 265 -132 133 -666 67 -166 431 -66 201 -98 297 -100 595 -66 199 -134 65 -100 795 -132 99 -168 501 -200 331 -132 265 -102 265 -134 423 -98 521 -226 65 -166 431 -134 99 -100 133 -464 195 -326 623 -100 673 -98 321 -200 65 -136 369 -166 65 -68 97 -166 165 -334 265 -102 231 -166 101 -170 65 -170 265 -136 931 -100 133 -134 563 -66 333 -100 427 -66 163 -390 231 -66 193 -130 461 -166 557 -100 99 -198 263 -100 197 -294 231 -232 299 -134 199 -170 267 -134 631 -98 235 -100 499 -68 463 -100 65 -134 335 -170 273 -134 297 -100 67 -66 197 -166 67 -134 301 -168 537 -470 99 -134 433 -132 199 -192 261 -100 523 -164 459 -132 259 -332 359 -64 227 -96 131 -132 687 -132 363 -136 329 -434 99 -334 133 -100 401 -132 233 -700 233 -170 337 -66 371 -68 233 -202 531 -266 731 -66 465 -100 167 -100 133 -232 335 -166 239 -102 367 -232 231 -100 167 -134 201 -136 301 -168 199 -300 231 -98 237 -134 233 -102 329 -132 261 -134 199 -66 265 -136 99 -170 167 -134 199 -166 167 -136 367 -298 197 -200 99 -166 469 -136 439 -66 303 -134 295 -100 433 -134 899 -266 363 -132 197 -160 555 -324 129 -96 97 -128 257 -132 97 -394 257 -98 195 -166 459 -332 395 -132 633 -134 301 -100 131 -332 169 -168 395 -166 263 -540 783 -100 287 -130 295 -96 225 -296 133 -98 99 -100 461 -164 545 -130 99 -66 301 -68 265 -100 235 -134 235 -70 333 -102 497 -66 233 -364 301 -170 103 -66 165 -336 733 -200 133 -100 263 -102 65 -136 465 -200 1035 -198 165 -170 67 -302 631 -100 429 -332 65 -128 129 -130 159 -128 159 -66 161 -96 325 -164 261 -100 197 -162 65 -96 99 -130 65 -102 333 -100 199 -98 389 -330 129 -128 229 -66 425 -366 229 -64 261 -100 227 -96 227 -526 301 -200 97 -66 699 -334 67 -100 399 -198 787 -98 297 -134 429 -100 3245 -64 527 -98 131 -526 633 -68 133 -302 1459 -164 971 -102 237 -136 1439 -266 1131 -66 599 -200 303 -332 325 -130 389 -166 371 -66 333 -102 65 -100 233 -234 327 -266 233 -166 297 -100 225 -130 163 -336 99 -596 199 -330 131 -66 331 -338 263 -358 197 -168 877 -66 227 -96 63 -130 263 -162 225 -290 197 -198 357 -132 297 -262 165 -456 227 -98 399 -296 95 -132 99 -98 457 -200 199 -168 535 -100 567 -134 327 -130 193 -130 683 -102 101 -132 233 -170 943 -166 827 -66 267 -102 503 -68 1325 -164 +RAW_Data: 1607 -68 233 -166 1167 -70 531 -134 335 -168 131 -66 299 -402 899 -66 461 -66 457 -98 953 -98 165 -66 293 -230 881 -64 393 -166 589 -66 289 -66 1093 -204 333 -98 2745 -132 2019 -170 925 -68 269 -102 1469 -136 2301 -68 1355 -100 527 -66 975 -68 1445 -98 2397 -100 1733 -66 703 -100 995 -100 135 -136 235 -202 167 -134 2071 -166 339 -170 201 -268 129 -66 465 -66 365 -100 197 -164 129 -98 161 -96 423 -66 675 -66 1543 -136 567 -200 767 -202 65 -100 1401 -66 623 -136 567 -234 67 -236 197 -194 97 -66 263 -66 1827 -392 1893 -98 165 -268 133 -132 231 -162 225 -98 695 -198 563 -100 301 -332 267 -102 341 -66 99 -132 1299 -130 525 -68 161 -96 357 -98 353 -100 131 -100 131 -98 163 -132 323 -100 535 -66 1323 -130 133 -66 235 -134 1497 -132 387 -98 129 -162 2623 -134 163 -68 167 -66 959 -232 495 -68 131 -134 867 -134 865 -66 333 -98 305 -134 231 -98 765 -198 397 -432 165 -66 165 -366 265 -102 541 -100 261 -162 331 -134 457 -66 491 -196 97 -266 193 -262 65 -166 231 -266 497 -360 263 -98 587 -164 259 -98 231 -66 359 -100 267 -102 271 -168 97 -262 63 -66 261 -130 227 -130 295 -164 65 -66 265 -200 597 -134 267 -170 603 -100 97 -466 231 -264 97 -168 99 -66 65 -200 199 -100 267 -404 303 -102 201 -204 235 -134 131 -198 335 -298 327 -130 291 -164 63 -162 295 -262 197 -130 95 -130 195 -96 159 -130 161 -66 231 -100 165 -66 199 -134 363 -66 267 -168 165 -168 167 -100 165 -530 363 -432 99 -232 65 -132 395 -328 229 -98 197 -132 161 -96 191 -292 197 -204 133 -100 399 -166 531 -332 235 -168 99 -66 325 -158 553 -132 129 -226 231 -134 99 -462 129 -64 289 -100 193 -66 355 -164 291 -198 131 -298 197 -198 373 -268 335 -234 427 -68 199 -132 267 -232 131 -66 783 -326 63 -162 161 -130 227 -66 259 -562 233 -464 303 -102 201 -334 301 -134 297 -198 229 -66 127 -166 99 -100 197 -198 571 -66 457 -134 361 -424 131 -328 163 -98 63 -100 505 -102 201 -1094 229 -164 65 -230 789 -236 2505 -166 201 -170 163 -64 1139 -66 927 -100 295 -198 723 -100 365 -66 459 -196 3033 -272 199 -66 499 -202 1319 -232 295 -298 131 -362 97 -164 129 -132 65 -98 197 -130 129 -98 261 -130 97 -98 229 -96 425 -66 227 -166 483 -66 163 -326 567 -68 235 -68 67 -66 167 -66 235 -330 425 -164 63 -66 427 -102 167 -66 669 -132 429 -200 65 -102 133 -100 197 -368 +RAW_Data: 65 -134 2481 -228 65 -130 229 -228 763 -136 603 -166 1619 -98 1763 -102 837 -166 321 -66 951 -130 2067 -66 259 -132 1835 -66 437 -102 701 -66 565 -68 363 -70 1113 -66 1989 -164 257 -128 351 -162 1055 -232 265 -170 309 -200 435 -166 833 -102 2467 -132 595 -66 773 -166 1615 -98 131 -96 485 -64 517 -166 197 -68 1231 -68 403 -100 263 -134 233 -100 503 -100 333 -266 729 -66 199 -100 369 -68 1239 -100 197 -68 299 -170 337 -100 825 -132 163 -66 4205 -64 161 -100 635 -66 907 -66 1017 -166 1709 -100 201 -266 657 -68 463 -166 331 -164 293 -64 259 -162 129 -262 597 -134 701 -136 67 -168 235 -136 303 -170 1417 -66 263 -98 857 -100 659 -166 97 -100 2497 -64 2495 -98 719 -128 227 -130 2217 -164 623 -264 719 -134 329 -98 1371 -100 553 -294 165 -66 1163 -100 329 -196 649 -200 1123 -68 263 -100 593 -266 333 -102 1133 -136 131 -132 603 -200 1819 -66 489 -66 563 -266 1113 -230 165 -66 423 -68 335 -100 101 -100 1073 -132 897 -100 101 -100 499 -134 173 -138 763 -238 371 -130 403 -166 203 -102 271 -136 269 -166 99 -168 263 -96 425 -66 331 -234 133 -400 231 -132 453 -66 459 -164 199 -68 237 -132 163 -198 161 -196 265 -132 65 -64 195 -130 357 -164 663 -68 167 -600 131 -98 133 -304 203 -134 433 -98 261 -130 199 -100 237 -100 229 -326 99 -98 331 -132 99 -294 165 -66 303 -134 99 -232 133 -136 99 -68 267 -198 233 -138 67 -166 367 -100 333 -168 267 -200 369 -266 135 -404 1939 -132 231 -160 161 -64 293 -98 331 -132 339 -104 135 -100 197 -430 263 -202 233 -64 195 -162 129 -64 227 -298 265 -68 697 -66 301 -68 231 -300 131 -368 769 -234 265 -98 195 -324 97 -752 229 -126 355 -98 257 -98 287 -64 427 -132 295 -262 197 -170 369 -102 267 -100 169 -68 201 -102 2551 -136 635 -134 639 -134 99 -132 197 -200 371 -66 731 -132 199 -138 733 -304 433 -68 729 -440 197 -68 99 -102 165 -266 261 -164 491 -296 489 -194 257 -164 133 -134 237 -68 335 -98 227 -130 229 -98 295 -98 231 -202 267 -236 233 -136 331 -130 195 -128 261 -430 261 -162 97 -224 99 -130 193 -96 197 -162 229 -396 97 -98 227 -364 267 -100 99 -100 233 -236 697 -164 227 -196 63 -98 327 -230 325 -66 129 -196 95 -98 195 -130 325 -430 131 -194 129 -454 161 -196 235 -68 433 -134 667 -164 355 -236 101 -98 2143 -134 1827 -198 63 -198 65 -64 2859 -64 619 -66 97 -130 3157 -66 679 -194 1491 -98 +RAW_Data: 951 -64 393 -100 955 -132 4715 -100 131 -66 199 -204 1541 -66 929 -130 1347 -166 665 -132 233 -132 67 -102 433 -100 595 -228 997 -66 505 -68 133 -98 231 -68 571 -134 1371 -232 231 -270 135 -102 97 -66 867 -100 269 -68 967 -100 1649 -66 65 -66 951 -68 65 -202 363 -200 779 -102 1449 -294 419 -130 361 -230 1079 -164 163 -260 893 -102 333 -100 533 -166 467 -100 135 -66 135 -202 369 -100 199 -100 269 -134 301 -166 229 -66 101 -134 199 -134 1293 -64 779 -62 831 -66 1243 -68 267 -102 197 -100 395 -98 455 -64 621 -132 877 -98 199 -100 2101 -134 503 -100 2035 -134 735 -236 475 -136 237 -132 133 -134 1229 -100 133 -66 167 -68 2655 -100 1807 -100 1095 -264 825 -98 163 -66 491 -98 161 -128 953 -100 773 -100 131 -66 67 -134 457 -130 63 -64 389 -98 715 -66 425 -300 97 -100 1515 -66 303 -68 99 -98 721 -64 887 -132 65 -132 165 -66 635 -68 2801 -66 1561 -100 751 -98 129 -64 725 -136 201 -100 333 -204 573 -104 1745 -134 99 -66 129 -64 595 -134 167 -102 337 -134 567 -134 1131 -138 1207 -100 269 -68 135 -100 1143 -134 2139 -68 1701 -162 991 -596 431 -66 99 -132 657 -66 391 -320 357 -260 259 -98 429 -66 163 -228 65 -130 227 -66 261 -166 99 -98 131 -366 199 -134 463 -102 201 -98 231 -102 639 -238 301 -568 169 -610 265 -102 841 -198 297 -100 335 -132 263 -266 265 -68 469 -134 267 -68 933 -298 333 -298 729 -168 135 -136 437 -132 1137 -134 199 -68 265 -132 463 -166 129 -130 227 -98 297 -98 65 -132 97 -202 199 -232 305 -66 165 -198 365 -66 99 -98 299 -170 65 -136 301 -232 99 -564 133 -132 233 -170 99 -102 131 -134 65 -204 101 -98 297 -98 167 -762 233 -298 99 -326 395 -66 299 -132 369 -504 333 -98 483 -200 457 -164 63 -164 329 -162 65 -622 231 -268 131 -132 133 -134 131 -134 131 -66 99 -100 231 -66 167 -336 165 -98 197 -100 97 -264 321 -98 521 -132 163 -130 129 -294 297 -134 101 -102 265 -168 497 -68 197 -68 499 -134 269 -398 267 -130 203 -302 65 -498 271 -136 465 -292 131 -294 163 -198 329 -96 129 -98 193 -130 391 -330 165 -134 167 -170 297 -102 133 -136 135 -366 199 -132 423 -132 395 -168 65 -166 401 -98 229 -98 329 -98 99 -130 129 -228 261 -160 127 -426 389 -162 193 -132 131 -100 231 -168 67 -304 201 -68 765 -132 161 -162 193 -64 195 -64 295 -130 787 -98 419 -528 429 -66 363 -134 131 -100 133 -200 331 -98 +RAW_Data: 431 -66 1167 -68 937 -68 1003 -66 99 -132 941 -134 65 -66 365 -274 165 -236 367 -96 557 -134 675 -66 261 -164 127 -96 391 -164 161 -98 391 -292 163 -98 519 -196 165 -98 523 -66 195 -160 3343 -66 661 -100 2589 -136 307 -100 629 -136 639 -100 133 -168 405 -100 267 -66 465 -132 1171 -64 749 -64 165 -98 983 -100 163 -202 537 -66 327 -100 669 -100 401 -236 2885 -164 439 -134 97 -426 1931 -66 1385 -98 715 -98 519 -66 289 -162 97 -360 297 -166 163 -66 289 -66 555 -334 167 -230 429 -102 267 -132 943 -136 401 -68 929 -130 193 -68 467 -198 335 -66 963 -100 597 -132 197 -260 523 -232 1115 -102 1935 -66 1395 -134 305 -100 99 -66 199 -66 1071 -66 2357 -66 367 -498 769 -234 163 -130 191 -64 1211 -200 133 -102 201 -100 561 -366 361 -98 195 -100 537 -64 165 -196 1041 -332 133 -102 441 -230 4217 -66 1033 -66 167 -66 933 -100 565 -66 331 -164 673 -104 441 -66 533 -66 2095 -164 525 -66 297 -170 965 -198 421 -100 663 -832 65 -100 331 -164 231 -166 135 -168 237 -466 761 -134 891 -196 791 -198 257 -160 161 -98 293 -66 1081 -98 229 -130 327 -66 1301 -200 331 -166 101 -66 461 -100 2619 -132 1663 -98 1609 -134 499 -332 165 -370 67 -264 97 -96 259 -98 701 -402 197 -128 527 -236 233 -102 167 -134 303 -134 99 -166 299 -132 165 -200 467 -68 305 -168 207 -102 465 -102 729 -136 101 -374 327 -96 259 -98 467 -202 65 -66 673 -98 335 -404 135 -66 339 -204 99 -366 233 -68 365 -166 133 -102 867 -198 163 -162 163 -294 463 -332 165 -68 269 -268 331 -100 131 -166 299 -132 231 -400 263 -164 131 -266 267 -264 367 -66 371 -134 229 -104 267 -232 67 -466 265 -100 101 -100 165 -200 65 -200 301 -66 199 -168 233 -98 267 -66 67 -134 261 -196 261 -234 427 -294 65 -194 193 -66 259 -132 849 -96 63 -198 167 -294 95 -98 361 -164 261 -196 131 -132 437 -100 597 -262 327 -162 295 -98 295 -164 259 -196 425 -230 321 -66 195 -66 261 -496 99 -200 529 -132 133 -966 133 -132 165 -66 63 -128 491 -402 65 -262 299 -66 299 -202 265 -100 99 -668 97 -134 65 -100 101 -66 65 -266 691 -66 431 -166 167 -134 199 -370 899 -134 99 -100 1093 -166 163 -166 399 -98 327 -100 99 -168 135 -200 133 -202 429 -98 65 -98 197 -556 65 -66 97 -326 331 -166 333 -200 135 -100 235 -234 265 -98 65 -68 135 -66 335 -66 133 -298 99 -66 233 -164 435 -232 97 -132 97 -392 +RAW_Data: 99 -198 819 -66 1235 -98 321 -132 1091 -66 1307 -98 3059 -164 3305 -64 227 -98 591 -98 129 -66 229 -98 2143 -98 939 -68 563 -100 361 -232 945 -164 257 -96 229 -230 387 -64 195 -130 981 -294 587 -162 193 -98 1337 -66 293 -98 2665 -66 297 -98 647 -66 459 -132 491 -164 489 -96 595 -66 899 -66 837 -64 1151 -196 259 -98 357 -164 891 -132 1359 -134 197 -98 97 -98 261 -64 229 -96 461 -136 693 -100 201 -98 865 -66 599 -100 517 -132 709 -66 293 -298 655 -66 197 -130 129 -66 197 -98 4291 -66 673 -66 667 -132 1473 -132 133 -104 99 -66 163 -168 333 -134 1743 -132 1097 -132 99 -68 167 -602 1323 -352 99 -166 753 -98 423 -98 97 -66 1317 -228 1309 -98 1849 -66 1939 -132 601 -100 665 -100 1875 -66 695 -132 425 -66 425 -66 263 -134 165 -134 99 -98 829 -66 601 -166 131 -102 565 -66 301 -100 1099 -100 601 -138 533 -66 667 -234 561 -66 99 -68 2741 -98 199 -100 531 -168 101 -434 1027 -68 431 -66 403 -132 99 -98 565 -132 135 -100 399 -166 271 -236 233 -166 197 -366 99 -66 99 -168 503 -66 199 -170 207 -100 673 -368 99 -66 263 -168 133 -98 397 -268 337 -66 131 -132 231 -132 501 -134 99 -168 567 -138 103 -136 267 -298 231 -134 197 -160 321 -332 231 -98 131 -164 257 -64 163 -328 395 -66 331 -202 65 -168 133 -68 167 -100 233 -102 335 -66 197 -326 1101 -132 589 -100 811 -132 399 -136 269 -102 497 -66 559 -100 129 -98 855 -68 637 -102 65 -200 875 -68 233 -166 167 -66 529 -202 235 -102 231 -66 1237 -66 733 -98 1723 -132 101 -100 297 -66 829 -232 197 -100 367 -134 169 -166 167 -434 633 -100 235 -200 131 -134 233 -100 131 -100 331 -134 495 -432 65 -528 161 -130 295 -132 337 -136 133 -166 165 -100 269 -240 201 -336 133 -166 165 -238 199 -202 431 -434 99 -134 501 -166 231 -96 559 -202 167 -66 717 -98 987 -198 65 -64 163 -64 227 -98 555 -164 199 -64 361 -66 163 -98 129 -162 97 -130 161 -460 197 -230 681 -98 197 -98 329 -100 267 -266 291 -264 65 -100 329 -100 459 -200 363 -98 165 -134 231 -134 301 -134 231 -302 99 -132 101 -134 267 -136 233 -68 393 -422 163 -166 361 -166 99 -134 365 -134 133 -336 401 -66 495 -132 401 -168 133 -402 501 -136 1093 -862 165 -132 293 -300 289 -66 131 -164 391 -134 99 -360 359 -130 323 -200 423 -98 195 -162 295 -132 161 -98 129 -782 131 -426 227 -64 259 -166 63 -160 323 -98 261 -230 +RAW_Data: 231 -66 921 -66 355 -64 1019 -98 227 -258 163 -66 597 -232 1313 -132 163 -404 467 -236 901 -164 483 -98 195 -96 489 -134 103 -238 169 -66 67 -68 299 -100 497 -68 65 -134 1635 -304 1153 -100 539 -168 265 -200 499 -166 535 -100 397 -168 931 -100 131 -66 631 -134 897 -270 1233 -100 65 -132 131 -334 663 -66 163 -66 131 -132 705 -98 571 -200 433 -100 237 -234 229 -132 1627 -66 569 -100 715 -66 1863 -272 265 -68 301 -98 465 -68 97 -134 99 -66 395 -136 1405 -66 529 -132 63 -196 579 -132 413 -260 129 -136 101 -166 1201 -134 833 -134 393 -66 335 -172 201 -68 1027 -96 753 -64 815 -66 97 -64 1341 -132 289 -160 127 -66 99 -228 1083 -96 163 -66 259 -64 159 -98 2409 -168 767 -200 367 -66 1675 -66 1067 -98 3407 -200 99 -66 1403 -166 99 -134 439 -200 329 -136 599 -66 637 -66 835 -66 1099 -98 99 -66 463 -166 165 -100 461 -164 3037 -66 655 -66 97 -98 229 -130 355 -132 1443 -66 527 -98 881 -98 229 -162 127 -96 583 -64 65 -162 489 -166 885 -194 257 -98 1539 -66 293 -166 229 -132 655 -98 757 -49522 271 -758 689 -1264 737 -670 293 -1152 811 -1144 341 -664 773 -678 327 -1118 807 -1144 835 -1146 781 -1126 873 -1096 347 -622 877 -624 321 -1106 843 -1098 871 -1098 843 -1106 379 -610 841 -584 381 -1122 365 -602 845 -1116 837 -610 381 -1056 889 -1078 383 -614 827 -1110 877 -592 353 -1108 845 -1120 839 -1120 347 -602 849 -1110 865 -612 361 -1072 869 -1114 351 -618 861 -618 343 -1090 853 -1106 387 -618 797 -674 347 -1084 389 -574 867 -584 381 -1114 841 -1102 845 -1116 839 -1112 843 -1098 875 -1086 383 -584 865 -588 375 -1100 861 -1112 851 -1084 853 -1108 847 -1106 381 -584 857 -610 383 -1080 357 -602 871 -602 385 -1084 383 -616 823 -610 373 -1086 381 -590 871 -1084 839 -628 353 -1102 875 -1100 349 -9404 875 -1060 871 -1086 887 -1088 879 -1058 863 -1086 855 -1132 845 -1078 871 -1076 857 -1098 881 -1082 861 -1088 843 -1120 853 -1074 879 -1074 879 -1068 889 -614 341 -1090 387 -616 863 -624 345 -1088 391 -590 857 -612 385 -1058 393 -596 843 -1088 889 -1078 879 -578 387 -1082 875 -1076 415 -550 881 -1070 877 -592 391 -1114 821 -1104 373 -620 821 -624 361 -1072 903 -1086 855 -1092 843 -1086 905 -1054 387 -614 863 -618 347 -1088 853 -1114 845 -1090 867 -1070 381 -610 885 -584 385 -1052 407 -578 877 -1052 899 -600 389 -1048 907 -1074 383 -586 877 -1072 877 -594 359 -1076 875 -1082 891 -1088 363 -616 855 -1084 857 -592 381 -1088 883 -1086 385 -572 +RAW_Data: 889 -624 353 -1082 853 -1096 379 -594 853 -624 353 -1092 417 -582 847 -612 385 -1076 847 -1080 883 -1052 913 -1044 907 -1076 849 -1088 383 -602 867 -616 361 -1068 901 -1072 865 -1104 831 -1080 879 -1098 397 -586 855 -626 355 -1084 381 -592 873 -616 351 -1084 385 -624 821 -620 359 -1086 387 -584 883 -1086 877 -592 355 -1106 853 -1086 387 -69570 97 -100 99 -2620 131 -636 333 -102 235 -236 67 -68 363 -66 201 -100 567 -102 267 -164 101 -134 65 -68 197 -68 297 -166 671 -100 469 -336 165 -100 201 -66 169 -230 169 -204 329 -624 67 -98 265 -232 193 -168 299 -100 235 -138 101 -370 165 -294 333 -622 231 -130 129 -130 353 -132 195 -162 359 -164 67 -68 333 -100 133 -688 235 -236 497 -198 293 -98 129 -296 293 -164 229 -128 229 -132 193 -400 165 -66 163 -98 361 -164 355 -196 587 -164 131 -98 263 -554 99 -130 129 -130 191 -464 99 -132 67 -100 167 -604 329 -66 199 -68 133 -102 163 -66 2971 -132 785 -66 329 -96 323 -100 201 -136 301 -66 1959 -166 867 -134 467 -66 297 -100 835 -100 753 -166 165 -64 67 -370 335 -66 559 -232 165 -334 65 -162 129 -354 163 -64 131 -134 265 -300 263 -132 267 -296 327 -198 99 -132 535 -132 469 -866 231 -860 99 -232 503 -134 99 -198 233 -134 267 -200 97 -358 297 -164 259 -98 227 -166 135 -66 323 -100 97 -294 131 -164 129 -98 295 -96 129 -426 299 -100 67 -102 623 -100 163 -194 127 -360 563 -134 199 -428 493 -98 229 -130 257 -64 165 -100 131 -98 163 -692 357 -64 161 -98 321 -64 389 -230 65 -692 227 -130 261 -132 231 -162 287 -298 97 -460 393 -130 301 -168 331 -100 269 -202 101 -134 201 -102 99 -132 199 -204 235 -664 65 -562 133 -328 463 -100 291 -194 159 -162 227 -98 293 -328 165 -128 227 -574 535 -332 197 -168 65 -300 131 -66 389 -1078 131 -64 259 -64 223 -98 257 -164 63 -328 433 -134 65 -602 131 -68 333 -136 369 -66 297 -264 427 -66 97 -130 429 -102 133 -136 203 -240 167 -236 329 -526 67 -132 133 -168 331 -360 65 -66 331 -296 267 -134 469 -132 595 -230 661 -662 299 -100 265 -200 203 -168 801 -100 133 -68 399 -132 99 -100 161 -390 65 -298 65 -98 261 -130 161 -128 257 -66 67 -134 621 -98 227 -328 99 -230 129 -294 193 -96 195 -318 425 -526 129 -196 163 -162 65 -132 293 -130 63 -66 325 -128 63 -130 293 -66 199 -200 269 -206 133 -198 325 -98 163 -100 97 -98 261 -164 67 -98 167 -430 131 -494 131 -164 +RAW_Data: 97 -98 861 -66 1199 -166 231 -100 651 -166 197 -104 439 -98 131 -64 493 -98 883 -96 99 -98 3327 -66 131 -264 733 -134 2133 -166 131 -102 303 -136 535 -134 701 -98 355 -228 131 -202 99 -134 99 -100 791 -166 169 -202 671 -100 741 -100 263 -66 165 -68 935 -132 197 -198 673 -100 605 -66 1457 -98 1195 -166 2347 -134 505 -100 1469 -66 391 -100 229 -100 1171 -98 939 -100 459 -170 369 -134 231 -162 127 -98 95 -66 195 -98 195 -66 299 -100 331 -98 65 -232 369 -132 201 -68 167 -166 1481 -102 501 -160 1257 -66 2307 -64 623 -164 2079 -66 1101 -98 423 -64 659 -68 431 -136 99 -100 435 -130 167 -168 835 -200 135 -104 133 -100 503 -68 1437 -232 821 -132 357 -96 463 -66 263 -64 683 -132 165 -96 655 -166 3939 -100 1169 -132 2443 -98 197 -132 425 -234 233 -162 1043 -66 197 -100 2793 -134 167 -104 675 -100 197 -134 1367 -102 763 -132 265 -230 133 -102 365 -100 167 -66 1069 -66 837 -100 295 -160 97 -64 129 -132 617 -164 197 -100 133 -136 337 -172 133 -66 557 -98 951 -66 263 -130 587 -66 729 -196 335 -166 933 -432 369 -100 199 -296 225 -98 355 -66 129 -64 557 -98 289 -66 355 -128 193 -162 267 -134 299 -98 165 -170 303 -640 1031 -134 99 -66 135 -68 771 -166 171 -104 201 -134 131 -68 635 -428 661 -292 749 -430 1161 -100 905 -98 65 -98 657 -262 2837 -132 67 -66 265 -132 631 -66 1037 -296 97 -98 1703 -302 367 -100 505 -232 497 -362 333 -134 591 -100 755 -232 67 -130 587 -66 231 -168 65 -332 99 -66 267 -232 393 -134 65 -132 131 -428 133 -200 165 -202 199 -168 165 -102 269 -100 333 -852 201 -134 233 -202 65 -200 563 -768 265 -136 169 -102 169 -598 333 -202 267 -134 267 -328 163 -130 625 -500 199 -200 99 -270 65 -134 65 -198 65 -100 99 -596 493 -66 99 -66 331 -232 103 -136 373 -168 831 -170 65 -672 163 -102 133 -136 331 -100 333 -234 101 -100 99 -200 99 -100 201 -302 199 -600 301 -202 135 -134 705 -166 435 -530 97 -198 131 -198 195 -66 163 -392 293 -66 295 -370 229 -198 65 -100 405 -134 165 -134 133 -170 337 -236 205 -274 267 -134 329 -132 195 -132 503 -132 133 -136 133 -334 197 -196 299 -168 101 -100 233 -100 439 -134 301 -332 331 -298 433 -406 433 -68 167 -100 203 -100 101 -102 99 -328 397 -234 205 -168 133 -364 63 -202 397 -198 95 -394 267 -134 569 -66 201 -102 133 -136 101 -102 99 -132 99 -196 197 -498 197 -102 135 -170 +RAW_Data: 331 -164 63 -162 1267 -66 163 -130 129 -66 725 -164 231 -64 853 -66 101 -134 199 -102 99 -68 365 -66 357 -130 815 -64 357 -98 97 -98 97 -66 65 -466 231 -172 3749 -66 849 -130 917 -64 327 -64 1013 -98 555 -332 795 -100 571 -132 769 -132 401 -134 1297 -134 377 -138 435 -100 401 -100 667 -100 1761 -66 667 -66 1533 -236 233 -98 885 -130 457 -66 999 -66 165 -66 833 -134 695 -166 501 -66 499 -200 329 -64 197 -134 441 -100 2099 -98 491 -134 197 -130 2225 -132 65 -100 689 -64 193 -160 159 -96 195 -98 323 -164 259 -98 535 -472 771 -66 665 -270 665 -66 595 -266 2191 -64 643 -98 1287 -98 741 -100 233 -200 569 -194 261 -68 637 -100 97 -66 491 -158 395 -138 1017 -66 627 -262 559 -64 327 -98 263 -134 99 -102 201 -102 337 -66 167 -68 679 -100 471 -134 195 -66 133 -202 693 -96 197 -98 391 -164 99 -98 3883 -194 461 -100 237 -168 1891 -68 301 -68 969 -166 1439 -294 551 -130 389 -98 99 -196 167 -102 505 -66 569 -234 901 -98 407 -136 469 -66 769 -98 769 -166 1263 -266 297 -98 1701 -200 203 -168 329 -232 65 -100 329 -164 803 -100 135 -200 233 -166 135 -272 265 -134 197 -100 133 -134 539 -232 197 -396 165 -366 263 -68 233 -102 365 -132 233 -100 135 -266 199 -234 167 -232 97 -524 127 -128 389 -98 305 -364 261 -130 257 -162 589 -464 361 -66 229 -134 161 -100 203 -432 265 -66 199 -66 199 -366 229 -236 99 -134 99 -100 131 -168 133 -100 131 -236 267 -132 297 -264 291 -132 167 -234 65 -100 199 -66 333 -730 237 -440 365 -102 99 -100 99 -132 99 -100 1429 -134 427 -100 97 -100 131 -164 799 -170 1077 -100 431 -66 133 -168 737 -134 197 -230 65 -102 803 -132 491 -98 429 -198 471 -134 365 -66 299 -236 65 -66 2837 -102 399 -64 585 -64 523 -196 97 -98 295 -196 555 -160 261 -500 299 -396 333 -236 133 -68 327 -100 199 -204 699 -66 701 -100 65 -164 65 -370 195 -196 97 -66 193 -130 129 -360 195 -130 231 -96 291 -64 455 -228 293 -196 291 -162 97 -194 621 -130 847 -66 395 -66 161 -128 193 -130 293 -98 231 -170 67 -134 297 -360 167 -266 263 -526 263 -132 229 -98 191 -160 159 -100 721 -234 101 -100 99 -130 259 -258 265 -632 687 -164 133 -134 631 -100 199 -102 165 -560 299 -200 265 -332 431 -870 99 -266 503 -364 135 -66 269 -68 499 -100 265 -102 263 -102 569 -234 719 -132 99 -196 419 -262 163 -688 95 -66 165 -128 95 -66 +RAW_Data: 295 -98 987 -196 517 -100 489 -66 355 -132 563 -198 867 -134 1413 -134 541 -134 767 -100 193 -98 1799 -102 467 -134 299 -96 323 -66 261 -100 259 -66 229 -96 851 -66 369 -266 469 -66 101 -98 163 -136 267 -432 859 -130 523 -66 197 -134 1027 -132 227 -194 393 -98 807 -166 235 -100 133 -66 165 -102 133 -136 371 -162 1411 -132 865 -200 471 -100 133 -68 299 -66 633 -98 329 -234 401 -98 1505 -132 133 -134 331 -262 163 -66 261 -98 289 -64 201 -68 1055 -96 391 -66 951 -298 265 -202 297 -66 401 -68 131 -100 1733 -98 941 -66 803 -98 847 -64 3701 -100 721 -160 357 -166 1799 -66 329 -100 99 -102 363 -198 167 -136 197 -66 567 -66 199 -236 1247 -166 2455 -68 1107 -200 235 -100 2355 -130 913 -98 877 -98 163 -196 97 -66 427 -100 801 -134 867 -98 263 -68 441 -134 561 -98 1671 -134 865 -68 935 -132 163 -102 975 -66 1343 -132 1339 -134 369 -100 1107 -66 1167 -168 631 -232 835 -66 1027 -132 333 -166 265 -98 1207 -98 223 -98 455 -64 2095 -134 933 -136 233 -68 335 -136 305 -100 1737 -66 427 -100 263 -130 323 -66 227 -66 717 -100 265 -100 65 -128 355 -66 367 -132 95 -230 229 -100 131 -64 493 -132 291 -396 393 -130 259 -196 227 -288 397 -68 229 -430 99 -302 237 -700 65 -66 65 -100 133 -200 101 -336 133 -166 237 -202 67 -302 67 -68 333 -132 263 -102 267 -296 163 -166 233 -168 363 -64 295 -298 537 -166 431 -200 431 -166 63 -258 363 -164 563 -234 199 -68 299 -100 325 -754 295 -196 65 -98 165 -132 301 -134 131 -134 97 -68 405 -68 233 -134 271 -134 67 -168 101 -136 133 -366 99 -132 67 -132 265 -200 233 -100 201 -136 101 -66 263 -132 129 -66 293 -582 263 -132 1103 -134 203 -168 97 -66 197 -264 131 -168 133 -132 65 -134 199 -134 101 -100 131 -436 99 -232 97 -398 231 -362 65 -202 301 -396 297 -98 199 -134 265 -164 101 -168 267 -102 405 -170 99 -102 397 -132 97 -98 295 -98 1179 -100 135 -136 131 -134 765 -134 465 -168 439 -232 403 -100 65 -134 931 -100 169 -136 237 -68 231 -234 199 -68 401 -134 541 -166 429 -166 1607 -368 533 -66 363 -66 133 -134 433 -166 297 -238 201 -100 201 -170 199 -134 273 -136 99 -134 167 -238 133 -66 265 -134 165 -132 165 -132 97 -228 723 -198 415 -64 491 -298 257 -66 231 -192 225 -96 227 -98 193 -96 521 -198 65 -66 231 -166 163 -98 465 -66 133 -132 195 -130 225 -162 521 -130 63 -66 199 -228 +RAW_Data: 817 -162 449 -160 719 -198 469 -68 133 -68 1101 -132 593 -230 1105 -100 131 -134 231 -66 329 -196 685 -96 557 -68 1263 -68 101 -68 397 -100 65 -66 625 -66 97 -132 1099 -66 493 -66 757 -98 1151 -66 303 -134 1901 -66 99 -100 665 -262 991 -98 791 -66 1925 -168 865 -232 835 -98 505 -102 99 -100 535 -100 169 -134 427 -132 863 -68 167 -134 975 -100 133 -268 1339 -100 1453 -66 1445 -162 195 -64 3623 -66 237 -68 1063 -308 1449 -98 1111 -132 167 -102 855 -270 199 -134 297 -134 267 -168 863 -234 637 -66 567 -230 99 -200 3325 -198 845 -66 289 -66 131 -66 815 -130 1093 -100 167 -100 429 -98 1703 -166 195 -64 971 -98 163 -192 195 -168 439 -132 329 -132 67 -134 67 -134 1591 -168 407 -100 867 -68 399 -134 661 -100 663 -66 237 -136 395 -232 131 -66 695 -100 627 -264 913 -66 1083 -98 287 -66 199 -132 335 -100 1031 -68 99 -100 3815 -98 165 -66 129 -98 163 -128 563 -98 779 -96 223 -64 161 -164 2025 -66 1741 -172 101 -136 203 -102 665 -100 475 -64 167 -100 637 -98 997 -170 1207 -136 233 -166 233 -168 635 -132 199 -100 235 -270 199 -98 131 -102 169 -170 293 -98 323 -164 427 -334 233 -168 267 -68 369 -100 263 -368 101 -66 665 -98 265 -100 133 -100 99 -168 133 -66 133 -132 133 -66 269 -134 435 -68 267 -136 271 -500 163 -100 163 -166 355 -132 97 -98 323 -194 63 -688 463 -130 97 -396 65 -100 357 -194 461 -98 161 -130 223 -162 165 -352 461 -300 267 -166 233 -464 329 -100 293 -362 163 -228 289 -66 229 -66 195 -162 325 -66 261 -98 127 -424 299 -302 367 -68 265 -272 429 -98 161 -98 393 -296 65 -130 161 -196 261 -66 473 -234 97 -98 263 -160 323 -98 67 -132 697 -298 99 -134 233 -202 97 -134 301 -200 307 -100 101 -134 865 -166 231 -202 233 -100 301 -170 169 -102 169 -200 65 -98 595 -166 231 -234 661 -66 473 -334 165 -304 365 -266 97 -502 363 -134 133 -236 65 -100 99 -134 99 -170 235 -66 333 -100 195 -100 133 -300 133 -102 301 -304 65 -100 99 -100 131 -202 135 -134 65 -200 363 -66 263 -498 67 -68 295 -194 321 -368 435 -100 97 -664 99 -100 569 -66 133 -66 67 -134 199 -136 101 -68 301 -68 405 -198 133 -132 581 -132 165 -98 159 -98 197 -66 229 -130 131 -294 133 -96 423 -100 427 -300 357 -132 291 -64 95 -194 455 -98 263 -100 359 -196 65 -162 227 -162 157 -96 157 -230 589 -132 325 -134 535 -66 267 -100 135 -302 +RAW_Data: 131 -134 599 -166 393 -98 369 -236 197 -100 401 -232 569 -134 135 -70 337 -134 101 -136 135 -100 1895 -66 401 -170 503 -66 1633 -66 601 -66 355 -96 683 -100 729 -68 133 -132 433 -68 569 -100 133 -68 201 -132 835 -100 465 -68 527 -98 193 -200 1129 -166 535 -100 199 -98 259 -132 227 -64 1597 -98 261 -192 753 -100 911 -66 667 -298 131 -100 263 -66 1051 -230 787 -66 935 -66 233 -98 885 -236 431 -66 197 -162 521 -68 167 -196 263 -96 589 -98 517 -66 1439 -64 777 -66 3219 -132 679 -134 205 -68 507 -198 749 -200 199 -168 167 -100 133 -134 201 -68 731 -66 495 -198 737 -66 237 -68 135 -100 167 -234 1535 -68 873 -66 373 -66 67 -232 297 -68 65 -66 1095 -68 327 -130 63 -132 1715 -66 2261 -100 321 -132 197 -164 457 -232 1291 -132 405 -68 1001 -68 1133 -272 471 -66 99 -134 1403 -68 167 -68 1091 -336 933 -134 1207 -132 265 -68 267 -66 99 -366 265 -66 1469 -258 367 -168 429 -132 129 -66 491 -132 343 -100 65 -100 263 -136 199 -164 273 -204 791 -100 901 -66 167 -98 165 -64 559 -132 619 -132 1087 -128 2283 -398 1467 -164 259 -130 1927 -130 421 -98 1085 -66 705 -68 1843 -168 875 -170 203 -136 341 -640 199 -66 133 -554 161 -196 63 -66 521 -292 163 -160 95 -158 127 -192 197 -100 587 -130 397 -662 261 -66 193 -130 259 -66 361 -64 459 -98 197 -560 655 -130 389 -66 1135 -100 133 -130 131 -98 1011 -100 561 -66 685 -164 457 -132 2469 -200 609 -66 665 -66 67 -132 327 -200 1657 -134 919 -132 651 -100 327 -230 191 -130 263 -358 95 -130 549 -98 99 -68 299 -100 461 -132 99 -472 165 -134 99 -66 99 -132 399 -102 169 -102 697 -166 233 -132 333 -632 197 -164 865 -266 101 -68 533 -166 299 -100 163 -228 259 -66 327 -200 65 -66 229 -100 363 -230 197 -336 165 -102 893 -300 65 -132 231 -370 265 -230 99 -98 229 -518 199 -100 401 -724 225 -98 63 -96 231 -64 291 -292 65 -98 131 -98 159 -158 127 -194 161 -292 65 -98 133 -66 297 -66 303 -168 97 -168 231 -234 269 -532 135 -168 99 -168 301 -528 99 -506 199 -368 399 -132 329 -372 99 -68 133 -264 197 -100 201 -200 67 -134 131 -270 133 -134 133 -198 327 -200 65 -100 331 -262 161 -166 469 -534 167 -738 131 -100 367 -232 101 -100 265 -604 65 -170 99 -166 299 -102 169 -132 99 -398 229 -330 197 -166 335 -366 97 -98 131 -200 269 -100 199 -168 131 -134 537 -98 265 -100 335 -236 99 -366 +RAW_Data: 459 -100 453 -130 419 -130 519 -96 63 -130 2077 -66 767 -64 127 -134 1961 -296 529 -202 637 -134 527 -100 201 -68 633 -66 163 -360 1029 -68 765 -100 867 -66 503 -100 131 -66 841 -98 165 -68 237 -66 509 -100 501 -302 235 -66 99 -164 227 -130 551 -196 327 -66 1571 -132 99 -68 867 -66 163 -96 161 -130 129 -130 549 -130 487 -166 1801 -66 229 -66 197 -232 325 -66 425 -198 131 -64 295 -166 735 -66 533 -98 227 -130 129 -262 425 -100 263 -66 129 -132 97 -168 971 -170 405 -68 199 -134 475 -202 297 -98 1445 -98 395 -196 161 -66 225 -134 1803 -100 473 -102 1499 -66 199 -100 701 -132 165 -68 133 -102 303 -98 735 -102 805 -100 827 -100 235 -100 65 -266 637 -68 693 -66 1383 -228 819 -66 233 -304 435 -198 203 -136 1135 -270 1709 -64 227 -64 581 -134 505 -66 2203 -64 293 -64 753 -66 551 -132 747 -64 1303 -64 463 -66 229 -102 1877 -266 871 -166 1357 -64 819 -66 465 -198 693 -68 165 -64 95 -128 3785 -132 1465 -100 299 -102 329 -164 595 -134 1029 -66 299 -168 1263 -166 331 -68 967 -100 101 -102 603 -260 165 -132 467 -66 233 -66 235 -102 475 -100 135 -68 301 -134 297 -98 131 -102 269 -466 99 -134 237 -166 135 -168 203 -102 265 -68 503 -66 233 -66 637 -134 101 -200 199 -166 293 -554 361 -328 367 -264 533 -238 167 -68 135 -170 99 -300 591 -298 133 -236 299 -66 231 -368 263 -232 435 -136 133 -102 133 -200 133 -134 163 -134 167 -168 299 -66 265 -100 133 -240 135 -132 263 -170 269 -200 501 -396 263 -98 227 -132 129 -292 427 -66 165 -102 627 -602 99 -66 301 -168 199 -100 563 -330 165 -134 233 -136 65 -332 499 -100 131 -232 325 -96 65 -132 195 -98 393 -624 323 -68 133 -98 195 -162 231 -100 263 -132 231 -102 133 -236 99 -236 231 -166 65 -102 133 -268 101 -102 299 -136 267 -164 493 -64 229 -258 291 -326 263 -198 391 -134 167 -202 365 -594 133 -102 201 -134 503 -396 429 -204 169 -400 197 -170 267 -132 403 -466 297 -98 469 -234 395 -132 233 -100 165 -100 165 -66 197 -68 297 -166 501 -134 133 -100 65 -166 631 -68 297 -134 199 -100 165 -68 299 -266 133 -66 165 -100 231 -490 557 -134 371 -164 299 -170 733 -164 239 -334 335 -66 299 -300 199 -170 103 -100 233 -102 641 -168 65 -100 995 -66 265 -160 259 -130 129 -226 425 -100 355 -726 97 -688 99 -66 233 -266 299 -942 167 -102 167 -166 65 -100 367 -136 99 -134 199 -134 267 -164 +RAW_Data: 67 -68 233 -66 899 -66 163 -96 485 -98 355 -130 943 -100 235 -168 499 -104 1367 -98 297 -100 635 -68 1169 -100 67 -134 835 -264 959 -164 129 -98 419 -196 589 -66 421 -66 1717 -100 133 -100 265 -134 227 -356 455 -166 163 -66 1055 -100 1455 -134 463 -98 2191 -132 295 -132 335 -66 709 -64 619 -98 959 -68 835 -170 603 -134 1033 -134 635 -168 759 -232 397 -198 397 -164 1267 -166 257 -198 1295 -100 239 -104 563 -204 335 -198 203 -68 901 -68 1255 -134 1697 -66 793 -66 1691 -68 201 -100 765 -66 165 -132 131 -230 131 -66 917 -66 335 -338 231 -170 827 -98 199 -136 301 -196 65 -98 199 -200 765 -134 403 -98 333 -68 1691 -132 2565 -64 569 -170 1255 -264 65 -132 1243 -132 2527 -66 259 -66 1739 -100 1309 -198 167 -238 337 -66 131 -68 1973 -362 299 -100 1387 -96 129 -164 423 -230 3875 -96 4283 -98 165 -98 515 -134 469 -68 171 -102 1163 -100 65 -298 461 -66 367 -136 205 -168 371 -98 491 -164 161 -262 1093 -100 299 -100 269 -334 1205 -98 63 -98 261 -64 457 -98 +RAW_Data: 57 -144 401 -86 173 -202 143 -258 133 -88 257 -144 287 -58 402 -56 259 -230 259 -86 85 -96 95 -174 286 -162 57 -230 253 -400 229 -88 536 -58 85 -72 167 -110 263 -72 229 -58 85 -86 87 -262 119 -288 163 -210 321 -320 186 -140 261 -96 143 -456 117 -216 143 -246 239 -102 121 -72 71 -191 167 -263 191 -96 239 -80 57 -116 143 -118 167 -168 215 -288 191 -106 287 -114 517 -88 113 -394 173 -70 215 -100 661 -86 201 -114 259 -58 287 -86 57 -202 399 -200 57 -288 229 -144 115 -1425 83 -142 173 -86 459 -112 223 -144 201 -116 143 -114 196 -17422 287 -614 1075 -1132 533 -560 1125 -1112 561 -530 1133 -1088 585 -512 1129 -1104 563 -1102 571 -1072 591 -524 1137 -538 1115 -19392 589 -522 1139 -1090 569 -520 1137 -1100 587 -520 1135 -1092 569 -514 1155 -1076 599 -1062 585 -1086 585 -532 1125 -528 1145 -19396 581 -504 1155 -1064 619 -506 1145 -1080 595 -508 1149 -1062 587 -536 1129 -1088 585 -1090 589 -1062 587 -510 1177 -506 1149 -19394 587 -524 1147 -1076 597 -512 1129 -1072 617 -498 1159 -1066 599 -514 1147 -1076 595 -1060 613 -1060 589 -506 1179 -510 1155 -19378 605 -514 1129 -1098 601 -488 1177 -1062 583 -510 1161 -1072 587 -516 1169 -1058 587 -1082 589 -1076 593 -532 1121 -536 1127 -19444 549 -542 1129 -1092 601 -510 1147 -1098 581 -506 1123 -1100 585 -510 1147 -1106 591 -1060 597 -1060 595 -510 1151 -534 1157 -19390 593 -512 1163 -1064 597 -510 1173 -1042 617 -510 1159 -1056 579 -556 1133 -1054 609 -1060 607 -1078 585 -528 1147 -516 1135 -19444 559 -534 1165 -1052 605 -510 1149 -1062 599 -520 1173 -1054 591 -540 1111 -1116 573 -1088 593 -1042 615 -534 1117 -536 1129 -19436 569 -530 1157 -1052 605 -504 1177 -1062 591 -536 1113 -1114 589 -514 1125 -1112 563 -1084 587 -1080 589 -536 1123 -532 1165 -19392 599 -524 1143 -1080 595 -540 1125 -1090 563 -534 1149 -1084 567 -534 1147 -1092 587 -1086 585 -1064 565 -558 1123 -532 1143 -19450 549 -556 1121 -1086 585 -534 1137 -1094 583 -516 1133 -1114 563 -536 1123 -1108 573 -1082 597 -1078 565 -532 1143 -524 1135 -19464 561 -528 1149 -1066 613 -508 1151 -1076 587 -532 1125 -1076 609 -506 1175 -1076 563 -1106 563 -1084 591 -534 1157 -484 1179 -19414 589 -528 1131 -1096 587 -520 1163 -1080 563 -550 1121 -1098 555 -562 1117 -1082 575 -1112 563 -1104 559 -558 1121 -530 1149 -19442 591 -498 1175 -1066 585 -534 1121 -1114 565 -540 1115 -1096 585 -538 1123 -1110 559 -1086 585 -1084 589 -530 1125 -558 1121 -19454 563 -542 1163 -1044 585 -558 1131 -1092 571 -536 1133 -1088 587 -518 1149 -1086 601 -1058 587 -1080 611 -510 +RAW_Data: 1125 -536 1145 -19444 567 -542 1151 -1086 581 -534 1133 -1084 583 -530 1141 -1090 587 -516 1121 -1114 591 -1062 595 -1092 567 -538 1131 -536 1145 -19448 589 -516 1143 -1076 591 -540 1115 -1090 591 -538 1115 -1096 597 -512 1155 -1078 581 -1082 585 -1104 559 -530 1153 -516 1147 -19438 589 -526 1157 -1056 609 -534 1137 -1078 585 -532 1149 -1076 585 -536 1123 -1104 587 -1062 597 -1082 587 -536 1121 -554 1121 -19464 555 -560 1123 -1104 563 -542 1131 -1096 565 -536 1143 -1060 621 -492 1175 -1078 571 -1100 577 -1092 591 -494 1167 -538 1117 -19452 593 -538 1133 -1074 603 -510 1157 -1062 595 -548 1115 -1102 573 -538 1139 -1094 567 -1094 583 -1092 567 -536 1127 -560 1113 -19478 563 -534 1151 -1084 569 -534 1135 -1116 565 -534 1135 -1084 583 -532 1155 -1060 587 -1084 589 -1084 581 -536 1125 -546 1141 -19460 577 -536 1123 -1102 565 -554 1119 -1096 593 -520 1133 -1108 563 -546 1131 -1088 569 -1094 573 -1100 583 -526 1147 -540 1123 -19466 587 -530 1129 -1100 587 -510 1143 -1084 609 -510 1131 -1110 567 -542 1121 -1102 567 -1110 563 -1106 567 -532 1149 -518 1145 -19458 573 -534 1147 -1082 575 -556 1149 -1048 589 -532 1157 -1088 565 -536 1129 -1088 589 -1106 563 -1086 587 -536 1129 -548 1147 -4298 103 -56 87 -344 87 -258 85 -278 215 -70 77 -344 279 -56 87 -56 197 -198 143 -288 362 -86 605 -144 57 -268 95 -114 143 -144 173 -144 143 -402 57 -202 259 -391 652 -340 427 -230 173 -356 97 -144 111 -246 219 -96 191 -114 173 -288 115 -56 109 -106 199 -106 73 -130 57 -172 85 -260 373 -56 629 -200 690 -230 273 -120 85 -460 85 -314 77 -78 111 -88 401 -116 171 -312 71 -500 81 -224 229 -88 257 -370 181 -172 200 -116 535 -174 113 -294 213 -359 445 -144 258 -114 115 -202 675 -509 239 -432 373 -538 85 -58 113 -86 761 -104 113 -318 443 -70 143 -144 647 -204 111 -334 87 -114 115 -144 113 -188 177 -144 199 -260 143 -86 87 -622 57 -116 171 -58 139 -222 55 -346 315 -76 345 -114 139 -171 195 -52 53 -98 119 -144 143 -244 95 -72 95 -96 167 -302 253 -186 307 -444 287 -449 115 -172 57 -172 316 -202 85 -370 697 -116 57 -144 171 -202 259 -114 85 -144 87 -315 85 -58 201 -116 171 -272 121 -358 171 -403 113 -86 115 -202 489 -229 115 -392 95 -116 171 -140 93 -102 143 -543 245 -358 215 -120 387 -288 171 -202 221 -202 115 -748 57 -316 143 -260 143 -288 115 -316 115 -58 85 -288 143 -460 485 -96 71 -104 199 -96 199 -202 143 -86 201 -116 85 -230 211 -288 115 -605 365 -126 53 -172 +RAW_Data: 317 -144 57 -486 53 -282 115 -585 97 -72 229 -174 257 -440 225 -86 173 -518 243 -167 95 -259 137 -96 694 -58 227 -80 279 -287 71 -72 301 -72 121 -106 51 -84 57 -58 199 -260 143 -288 219 -174 113 -681 115 -172 403 -58 113 -116 113 -432 171 -202 55 -108 95 -212 113 -72 527 -166 95 -212 195 -108 603 -142 239 -296 173 -346 373 -287 53 -80 79 -72 95 -238 95 -312 167 -618 143 -288 95 -72 95 -72 141 -210 55 -258 143 -328 305 -58 87 -86 315 -116 195 -218 85 -290 285 -220 215 -189 201 -58 57 -645 119 -96 71 -144 119 -406 143 -72 191 -72 631 -268 344 -56 115 -260 315 -140 455 -518 57 -58 171 -144 488 -86 219 -232 257 -144 85 -174 171 -260 115 -56 87 -166 197 -58 83 -56 85 -288 113 -410 115 -172 163 -202 113 -58 201 -144 201 -86 143 -264 167 -212 113 -116 139 -72 181 -287 343 -430 201 -260 201 -462 143 -192 301 -230 191 -454 187 -144 315 -164 143 -477 165 -58 201 -114 143 -490 115 -86 201 -58 113 -88 85 -58 203 -198 375 -86 171 -346 95 -88 257 -170 81 -56 143 -172 335 -230 173 -202 133 -471 187 -264 215 -86 115 -198 159 -72 179 -112 195 -116 449 -216 93 -96 167 -216 71 -216 71 -166 235 -86 447 -102 101 -226 195 -213 71 -144 215 -144 215 -261 241 -136 269 -142 263 -311 215 -172 201 -144 265 -168 71 -404 259 -86 85 -230 115 -650 143 -202 749 -512 248 -316 201 -154 71 -96 95 -360 105 -56 57 -432 95 -288 95 -286 95 -96 166 -144 93 -144 167 -150 904 -162 95 -526 287 -244 95 -240 383 -120 167 -394 430 -854 95 -72 143 -194 227 -120 167 -264 405 -144 143 -72 143 -72 141 -120 187 -86 143 -164 170 -96 143 -58 143 -86 402 -166 153 -120 95 -96 69 -96 71 -359 404 -338 71 -225 93 -74 97 -54 161 -114 319 -288 113 -116 459 -202 115 -114 115 -116 143 -86 57 -56 87 -114 85 -375 113 -58 311 -240 203 -288 95 -72 119 -383 213 -384 115 -86 171 -58 53 -104 401 -58 115 -86 373 -116 143 -144 161 -216 406 -72 263 -96 215 -72 95 -94 167 -96 191 -240 95 -94 214 -120 403 -116 200 -114 57 -172 220 -120 137 -364 334 -392 115 -260 199 -116 373 -188 95 -110 143 -172 87 -114 172 -230 57 -316 201 -56 249 -485 171 -202 87 -86 85 -144 345 -86 171 -58 259 -58 295 -120 95 -120 71 -192 635 -118 167 -96 375 -72 119 -120 261 -144 167 -96 95 -96 923 -215 71 -433 71 -477 +RAW_Data: 191 -240 85 -72 637 -408 213 -510 261 -168 143 -126 79 -106 167 -72 117 -218 251 -168 119 -96 215 -182 191 -238 517 -116 201 -144 255 -154 97 -94 215 -72 95 -120 71 -288 261 -106 434 -96 606 -232 229 -432 85 -174 343 -58 329 -156 55 -116 259 -144 488 -56 307 -339 115 -202 334 -88 113 -86 57 -174 143 -144 401 -376 85 -240 267 -82 95 -216 137 -158 85 -144 143 -58 221 -308 295 -114 87 -114 301 -120 358 -517 71 -262 191 -144 57 -140 165 -407 53 -262 217 -120 238 -358 119 -357 71 -72 119 -96 428 -72 95 -72 167 -72 93 -240 335 -96 357 -240 173 -230 143 -114 87 -200 143 -232 287 -150 97 -288 71 -72 93 -288 115 -58 143 -230 109 -264 71 -72 119 -72 238 -242 97 -78 163 -86 115 -518 79 -560 205 -449 969 -144 507 -86 231 -114 345 -58 979 -110 85 -288 287 -404 229 -202 57 -274 233 -86 115 -202 632 -230 85 -312 369 -392 460 -450 75 -280 85 -202 201 -86 229 -174 143 -144 233 -528 115 -212 127 -202 287 -172 403 -172 139 -128 165 -138 261 -392 143 -480 142 -189 291 -80 53 -283 167 -140 113 -1008 191 -144 119 -120 71 -193 241 -462 201 -58 143 -344 539 -316 113 -174 85 -116 113 -250 239 -168 405 -168 239 -158 85 -144 115 -86 57 -86 341 -144 171 -202 85 -202 115 -114 719 -88 55 -318 257 -56 254 -86 171 -116 459 -174 171 -329 95 -134 85 -314 431 -306 77 -316 401 -86 173 -404 281 -1073 488 -94 217 -78 101 -98 214 -120 215 -340 403 -535 143 -564 115 -116 199 -58 85 -174 315 -58 335 -136 55 -260 143 -144 229 -460 143 -58 143 -144 171 -202 115 -374 291 -130 339 -82 143 -58 171 -58 201 -86 85 -174 1022 -56 85 -82 255 -240 103 -202 431 -278 95 -216 119 -72 71 -96 71 -559 57 -144 171 -88 113 -86 231 -414 131 -192 237 -360 95 -168 145 -168 213 -120 167 -96 143 -110 57 -86 259 -56 87 -777 295 -96 57 -86 173 -86 171 -404 143 -172 231 -200 57 -441 55 -58 173 -56 87 -86 171 -72 287 -72 119 -262 119 -144 71 -72 121 -310 71 -302 113 -54 193 -80 307 -58 257 -232 143 -56 143 -116 219 -72 695 -70 71 -460 85 -232 719 -363 57 -402 604 -230 287 -138 83 -172 259 -58 171 -174 55 -88 489 -114 143 -116 171 -116 143 -58 199 -144 145 -343 374 -186 235 -140 77 -86 143 -202 143 -144 113 -144 143 -58 732 -96 263 -264 71 -206 95 -168 215 -144 271 -80 139 -88 85 -414 75 -100 +RAW_Data: 285 -96 627 -362 53 -84 201 -374 113 -202 115 -202 421 -316 85 -58 139 -224 87 -86 229 -58 243 -178 267 -288 95 -336 171 -96 213 -288 71 -405 95 -96 95 -384 95 -72 213 -72 95 -96 95 -272 87 -1083 85 -58 113 -88 257 -116 143 -292 175 -318 95 -120 95 -144 95 -72 71 -216 368 -116 373 -172 115 -58 85 -116 143 -86 85 -144 201 -86 201 -202 257 -144 201 -174 113 -144 115 -144 257 -202 585 -364 173 -138 287 -422 431 -86 85 -96 869 -186 95 -52 115 -86 115 -58 55 -276 365 -86 85 -489 171 -140 577 -106 718 -144 391 -232 195 -82 143 -172 109 -120 167 -96 280 -216 145 -240 215 -186 163 -96 141 -172 159 -603 257 -108 629 -192 119 -80 87 -172 57 -144 286 -86 57 -230 344 -58 113 -537 75 -96 537 -86 403 -196 167 -264 119 -238 119 -120 167 -96 95 -478 95 -120 167 -216 1085 -96 358 -72 263 -72 69 -120 143 -96 71 -96 191 -362 55 -144 57 -260 113 -58 85 -174 55 -88 257 -86 231 -194 55 -58 115 -56 55 -339 55 -58 374 -172 139 -82 419 -98 119 -261 71 -72 71 -240 713 -86 143 -218 295 -72 53 -56 431 -58 317 -144 161 -144 373 -144 173 -144 57 -114 85 -116 195 -72 708 -172 115 -86 191 -96 506 -120 71 -174 85 -58 363 -114 317 -230 316 -200 87 -114 57 -230 115 -315 173 -280 694 -212 453 -256 143 -202 113 -540 352 -116 257 -116 457 -56 109 -58 143 -230 259 -144 259 -525 119 -408 247 -112 389 -72 431 -96 137 -236 97 -474 201 -298 71 -82 55 -116 55 -112 199 -174 191 -86 143 -144 115 -114 317 -86 85 -230 87 -114 259 -84 107 -130 143 -94 153 -86 135 -94 215 -72 239 -94 435 -96 263 -142 166 -334 87 -194 179 -96 115 -284 135 -56 57 -144 463 -204 143 -316 201 -58 403 -86 141 -288 85 -202 139 -397 171 -174 305 -202 85 -144 373 -253 161 -492 181 -191 95 -216 315 -191 71 -166 97 -126 337 -96 71 -96 189 -168 295 -84 197 -86 259 -345 137 -144 167 -796 115 -344 455 -72 119 -96 119 -550 209 -88 85 -86 143 -340 167 -260 143 -537 85 -226 51 -537 57 -260 315 -461 51 -84 199 -358 383 -96 143 -257 115 -86 173 -86 201 -144 143 -316 85 -86 479 -88 85 -72 71 -104 115 -116 267 -72 137 -144 143 -116 85 -86 373 -288 115 -200 87 -114 259 -114 259 -462 143 -144 171 -86 57 -58 137 -144 57 -634 343 -72 205 -86 143 -258 57 -232 113 -230 461 -58 185 -74 537 -86 +RAW_Data: 535 -142 57 -58 55 -116 115 -432 85 -172 259 -192 167 -120 117 -72 119 -240 334 -72 71 -267 285 -144 119 -374 85 -88 85 -114 143 -202 229 -58 143 -202 115 -202 171 -86 71 -144 87 -56 173 -373 143 -116 113 -462 169 -80 215 -148 115 -336 85 -230 163 -432 85 -374 639 -174 85 -58 57 -82 295 -352 269 -532 414 -322 95 -287 263 -268 115 -56 259 -76 85 -282 401 -305 516 -114 115 -202 171 -86 451 -110 85 -346 201 -274 149 -202 85 -364 366 -258 57 -114 259 -172 142 -144 85 -116 85 -480 171 -144 57 -352 115 -116 535 -404 315 -202 163 -158 517 -316 215 -98 85 -346 85 -144 87 -86 257 -82 167 -58 85 -116 113 -894 233 -186 77 -266 147 -72 71 -82 57 -86 171 -58 57 -86 201 -364 143 -202 115 -114 85 -88 113 -86 87 -230 57 -76 613 -72 85 -96 209 -346 458 -58 547 -490 201 -315 315 -116 75 -168 359 -335 95 -384 93 -120 71 -312 251 -366 233 -96 189 -240 263 -192 271 -58 115 -58 229 -346 459 -174 113 -144 173 -144 218 -224 57 -116 215 -72 103 -202 513 -210 433 -116 113 -174 650 -273 147 -450 375 -86 115 -172 536 -84 85 -230 85 -58 195 -468 287 -110 551 -214 167 -311 213 -250 85 -58 85 -355 113 -230 115 -144 117 -288 195 -202 57 -376 123 -144 236 -168 553 -284 119 -72 143 -188 161 -120 93 -312 335 -58 55 -260 105 -244 143 -120 381 -268 173 -268 635 -168 453 -318 71 -167 71 -406 191 -172 215 -408 119 -144 93 -120 97 -130 143 -192 308 -122 147 -550 313 -96 139 -162 167 -96 431 -80 83 -112 201 -86 287 -86 229 -116 57 -288 113 -174 143 -116 113 -144 115 -518 57 -230 57 -172 231 -86 113 -314 183 -144 119 -72 165 -446 81 -86 135 -190 143 -96 71 -72 411 -96 143 -120 69 -216 349 -72 95 -96 517 -646 163 -86 113 -116 171 -116 143 -116 113 -287 259 -114 517 -168 141 -116 105 -72 95 -96 311 -118 159 -310 191 -54 143 -258 115 -450 219 -54 339 -372 239 -72 167 -174 113 -58 57 -144 259 -172 143 -336 113 -174 85 -230 83 -668 85 -202 113 -144 57 -116 373 -316 719 -288 115 -58 75 -120 139 -144 229 -144 57 -144 171 -192 391 -202 403 -58 315 -188 259 -56 115 -144 85 -404 57 -58 105 -102 429 -406 81 -172 57 -144 287 -230 287 -220 317 -458 283 -58 113 -86 269 -72 281 -58 85 -202 113 -52 421 -58 229 -480 259 -58 143 -660 155 -638 123 -86 57 -86 143 -346 143 -144 57 -144 \ No newline at end of file diff --git a/lib/subghz/protocols/ansonic.c b/lib/subghz/protocols/ansonic.c new file mode 100644 index 00000000000..81b196e369c --- /dev/null +++ b/lib/subghz/protocols/ansonic.c @@ -0,0 +1,346 @@ +#include "ansonic.h" +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#define TAG "SubGhzProtocolAnsonic" + +#define DIP_PATTERN "%c%c%c%c%c%c%c%c%c%c" +#define CNT_TO_DIP(dip) \ + (dip & 0x0800 ? '1' : '0'), (dip & 0x0400 ? '1' : '0'), (dip & 0x0200 ? '1' : '0'), \ + (dip & 0x0100 ? '1' : '0'), (dip & 0x0080 ? '1' : '0'), (dip & 0x0040 ? '1' : '0'), \ + (dip & 0x0020 ? '1' : '0'), (dip & 0x0010 ? '1' : '0'), (dip & 0x0001 ? '1' : '0'), \ + (dip & 0x0008 ? '1' : '0') + +static const SubGhzBlockConst subghz_protocol_ansonic_const = { + .te_short = 555, + .te_long = 1111, + .te_delta = 120, + .min_count_bit_for_found = 12, +}; + +struct SubGhzProtocolDecoderAnsonic { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; +}; + +struct SubGhzProtocolEncoderAnsonic { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + AnsonicDecoderStepReset = 0, + AnsonicDecoderStepFoundStartBit, + AnsonicDecoderStepSaveDuration, + AnsonicDecoderStepCheckDuration, +} AnsonicDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_ansonic_decoder = { + .alloc = subghz_protocol_decoder_ansonic_alloc, + .free = subghz_protocol_decoder_ansonic_free, + + .feed = subghz_protocol_decoder_ansonic_feed, + .reset = subghz_protocol_decoder_ansonic_reset, + + .get_hash_data = subghz_protocol_decoder_ansonic_get_hash_data, + .serialize = subghz_protocol_decoder_ansonic_serialize, + .deserialize = subghz_protocol_decoder_ansonic_deserialize, + .get_string = subghz_protocol_decoder_ansonic_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_ansonic_encoder = { + .alloc = subghz_protocol_encoder_ansonic_alloc, + .free = subghz_protocol_encoder_ansonic_free, + + .deserialize = subghz_protocol_encoder_ansonic_deserialize, + .stop = subghz_protocol_encoder_ansonic_stop, + .yield = subghz_protocol_encoder_ansonic_yield, +}; + +const SubGhzProtocol subghz_protocol_ansonic = { + .name = SUBGHZ_PROTOCOL_ANSONIC_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_FM | + SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | + SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_ansonic_decoder, + .encoder = &subghz_protocol_ansonic_encoder, +}; + +void* subghz_protocol_encoder_ansonic_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderAnsonic* instance = malloc(sizeof(SubGhzProtocolEncoderAnsonic)); + + instance->base.protocol = &subghz_protocol_ansonic; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->encoder.repeat = 10; + instance->encoder.size_upload = 52; + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_running = false; + return instance; +} + +void subghz_protocol_encoder_ansonic_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderAnsonic* instance = context; + free(instance->encoder.upload); + free(instance); +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderAnsonic instance + * @return true On success + */ +static bool subghz_protocol_encoder_ansonic_get_upload(SubGhzProtocolEncoderAnsonic* instance) { + furi_assert(instance); + size_t index = 0; + size_t size_upload = (instance->generic.data_count_bit * 2) + 2; + if(size_upload > instance->encoder.size_upload) { + FURI_LOG_E(TAG, "Size upload exceeds allocated encoder buffer."); + return false; + } else { + instance->encoder.size_upload = size_upload; + } + //Send header + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_ansonic_const.te_short * 35); + //Send start bit + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_ansonic_const.te_short); + //Send key data + for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) { + if(bit_read(instance->generic.data, i - 1)) { + //send bit 1 + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_ansonic_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_ansonic_const.te_long); + } else { + //send bit 0 + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_ansonic_const.te_long); + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_ansonic_const.te_short); + } + } + return true; +} + +bool subghz_protocol_encoder_ansonic_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderAnsonic* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_ansonic_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + if(!subghz_protocol_encoder_ansonic_get_upload(instance)) break; + instance->encoder.is_running = true; + + res = true; + } while(false); + + return res; +} + +void subghz_protocol_encoder_ansonic_stop(void* context) { + SubGhzProtocolEncoderAnsonic* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_ansonic_yield(void* context) { + SubGhzProtocolEncoderAnsonic* instance = context; + + if(instance->encoder.repeat == 0 || !instance->encoder.is_running) { + instance->encoder.is_running = 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_ansonic_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderAnsonic* instance = malloc(sizeof(SubGhzProtocolDecoderAnsonic)); + instance->base.protocol = &subghz_protocol_ansonic; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_ansonic_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderAnsonic* instance = context; + free(instance); +} + +void subghz_protocol_decoder_ansonic_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderAnsonic* instance = context; + instance->decoder.parser_step = AnsonicDecoderStepReset; +} + +void subghz_protocol_decoder_ansonic_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderAnsonic* instance = context; + + switch(instance->decoder.parser_step) { + case AnsonicDecoderStepReset: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_ansonic_const.te_short * 35) < + subghz_protocol_ansonic_const.te_delta * 35)) { + //Found header Ansonic + instance->decoder.parser_step = AnsonicDecoderStepFoundStartBit; + } + break; + case AnsonicDecoderStepFoundStartBit: + if(!level) { + break; + } else if( + DURATION_DIFF(duration, subghz_protocol_ansonic_const.te_short) < + subghz_protocol_ansonic_const.te_delta) { + //Found start bit Ansonic + instance->decoder.parser_step = AnsonicDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } else { + instance->decoder.parser_step = AnsonicDecoderStepReset; + } + break; + case AnsonicDecoderStepSaveDuration: + if(!level) { //save interval + if(duration >= (subghz_protocol_ansonic_const.te_short * 4)) { + instance->decoder.parser_step = AnsonicDecoderStepFoundStartBit; + if(instance->decoder.decode_count_bit >= + subghz_protocol_ansonic_const.min_count_bit_for_found) { + instance->generic.serial = 0x0; + instance->generic.btn = 0x0; + + 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); + } + break; + } + instance->decoder.te_last = duration; + instance->decoder.parser_step = AnsonicDecoderStepCheckDuration; + } else { + instance->decoder.parser_step = AnsonicDecoderStepReset; + } + break; + case AnsonicDecoderStepCheckDuration: + if(level) { + if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_ansonic_const.te_short) < + subghz_protocol_ansonic_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_ansonic_const.te_long) < + subghz_protocol_ansonic_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = AnsonicDecoderStepSaveDuration; + } else if( + (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_ansonic_const.te_long) < + subghz_protocol_ansonic_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_ansonic_const.te_short) < + subghz_protocol_ansonic_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = AnsonicDecoderStepSaveDuration; + } else + instance->decoder.parser_step = AnsonicDecoderStepReset; + } else { + instance->decoder.parser_step = AnsonicDecoderStepReset; + } + break; + } +} + +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + */ +static void subghz_protocol_ansonic_check_remote_controller(SubGhzBlockGeneric* instance) { + /* + * 12345678(10) k 9 + * AAA => 10101010 1 01 0 + * + * 1...10 - DIP + * k- KEY + */ + instance->cnt = instance->data & 0xFFF; + instance->btn = ((instance->data >> 1) & 0x3); +} + +uint8_t subghz_protocol_decoder_ansonic_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderAnsonic* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_ansonic_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderAnsonic* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +bool subghz_protocol_decoder_ansonic_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderAnsonic* instance = context; + bool ret = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_ansonic_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + ret = true; + } while(false); + return ret; +} + +void subghz_protocol_decoder_ansonic_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderAnsonic* instance = context; + subghz_protocol_ansonic_check_remote_controller(&instance->generic); + furi_string_cat_printf( + output, + "%s %dbit\r\n" + "Key:%03lX\r\n" + "Btn:%X\r\n" + "DIP:" DIP_PATTERN "\r\n", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data & 0xFFFFFFFF), + instance->generic.btn, + CNT_TO_DIP(instance->generic.cnt)); +} diff --git a/lib/subghz/protocols/ansonic.h b/lib/subghz/protocols/ansonic.h new file mode 100644 index 00000000000..0170a604897 --- /dev/null +++ b/lib/subghz/protocols/ansonic.h @@ -0,0 +1,107 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_ANSONIC_NAME "Ansonic" + +typedef struct SubGhzProtocolDecoderAnsonic SubGhzProtocolDecoderAnsonic; +typedef struct SubGhzProtocolEncoderAnsonic SubGhzProtocolEncoderAnsonic; + +extern const SubGhzProtocolDecoder subghz_protocol_ansonic_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_ansonic_encoder; +extern const SubGhzProtocol subghz_protocol_ansonic; + +/** + * Allocate SubGhzProtocolEncoderAnsonic. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderAnsonic* pointer to a SubGhzProtocolEncoderAnsonic instance + */ +void* subghz_protocol_encoder_ansonic_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderAnsonic. + * @param context Pointer to a SubGhzProtocolEncoderAnsonic instance + */ +void subghz_protocol_encoder_ansonic_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderAnsonic instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_encoder_ansonic_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderAnsonic instance + */ +void subghz_protocol_encoder_ansonic_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderAnsonic instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_ansonic_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderAnsonic. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderAnsonic* pointer to a SubGhzProtocolDecoderAnsonic instance + */ +void* subghz_protocol_decoder_ansonic_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderAnsonic. + * @param context Pointer to a SubGhzProtocolDecoderAnsonic instance + */ +void subghz_protocol_decoder_ansonic_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderAnsonic. + * @param context Pointer to a SubGhzProtocolDecoderAnsonic instance + */ +void subghz_protocol_decoder_ansonic_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderAnsonic instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_ansonic_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderAnsonic instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_ansonic_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderAnsonic. + * @param context Pointer to a SubGhzProtocolDecoderAnsonic instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_decoder_ansonic_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderAnsonic. + * @param context Pointer to a SubGhzProtocolDecoderAnsonic instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_decoder_ansonic_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderAnsonic instance + * @param output Resulting text + */ +void subghz_protocol_decoder_ansonic_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/protocol_items.c b/lib/subghz/protocols/protocol_items.c index ebf9b93e422..24aaae8dfb9 100644 --- a/lib/subghz/protocols/protocol_items.c +++ b/lib/subghz/protocols/protocol_items.c @@ -12,7 +12,7 @@ const SubGhzProtocol* subghz_protocol_registry_items[] = { &subghz_protocol_chamb_code, &subghz_protocol_power_smart, &subghz_protocol_marantec, &subghz_protocol_bett, &subghz_protocol_doitrand, &subghz_protocol_phoenix_v2, &subghz_protocol_honeywell_wdb, &subghz_protocol_magellan, &subghz_protocol_intertechno_v3, - &subghz_protocol_clemsa, + &subghz_protocol_clemsa, &subghz_protocol_ansonic, }; const SubGhzProtocolRegistry subghz_protocol_registry = { diff --git a/lib/subghz/protocols/protocol_items.h b/lib/subghz/protocols/protocol_items.h index 23aa71be034..114dc504677 100644 --- a/lib/subghz/protocols/protocol_items.h +++ b/lib/subghz/protocols/protocol_items.h @@ -35,5 +35,6 @@ #include "magellan.h" #include "intertechno_v3.h" #include "clemsa.h" +#include "ansonic.h" extern const SubGhzProtocolRegistry subghz_protocol_registry; From b9c483fbf8c0361518c0eb59812aa0edc3dab0bd Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Wed, 23 Nov 2022 13:44:49 +0400 Subject: [PATCH 230/824] [FL-2975] WS: add protocol GT-WT02 (#2001) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * WS: add protocol GT-WT02 * WS: fix text "About" scene Co-authored-by: あく --- .../helpers/weather_station_types.h | 2 +- .../weather_station/protocols/gt_wt_02.c | 265 ++++++++++++++++++ .../weather_station/protocols/gt_wt_02.h | 79 ++++++ .../protocols/protocol_items.c | 1 + .../protocols/protocol_items.h | 1 + .../scenes/weather_station_scene_about.c | 2 +- 6 files changed, 348 insertions(+), 2 deletions(-) create mode 100644 applications/plugins/weather_station/protocols/gt_wt_02.c create mode 100644 applications/plugins/weather_station/protocols/gt_wt_02.h diff --git a/applications/plugins/weather_station/helpers/weather_station_types.h b/applications/plugins/weather_station/helpers/weather_station_types.h index 5a66dd0ce40..a23540e3d0f 100644 --- a/applications/plugins/weather_station/helpers/weather_station_types.h +++ b/applications/plugins/weather_station/helpers/weather_station_types.h @@ -3,7 +3,7 @@ #include #include -#define WS_VERSION_APP "0.4" +#define WS_VERSION_APP "0.5" #define WS_DEVELOPED "SkorP" #define WS_GITHUB "https://github.com/flipperdevices/flipperzero-firmware" diff --git a/applications/plugins/weather_station/protocols/gt_wt_02.c b/applications/plugins/weather_station/protocols/gt_wt_02.c new file mode 100644 index 00000000000..cbe119192ac --- /dev/null +++ b/applications/plugins/weather_station/protocols/gt_wt_02.c @@ -0,0 +1,265 @@ +#include "gt_wt_02.h" + +#define TAG "WSProtocolGT_WT02" + +/* + * Help + * https://github.com/merbanan/rtl_433/blob/master/src/devices/gt_wt_02.c + * + * GT-WT-02 sensor on 433.92MHz. + * Example and frame description provided by https://github.com/ludwich66 + * [01] {37} 34 00 ed 47 60 : 00110100 00000000 11101101 01000111 01100000 + * code, BatOK,not-man-send, Channel1, +23,7C, 35% + * [01] {37} 34 8f 87 15 90 : 00110100 10001111 10000111 00010101 10010000 + * code, BatOK,not-man-send, Channel1,-12,1C, 10% + * Humidity: + * - the working range is 20-90 % + * - if "LL" in display view it sends 10 % + * - if "HH" in display view it sends 110% + * SENSOR: GT-WT-02 (ALDI Globaltronics..) + * TYP IIIIIIII BMCCTTTT TTTTTTTT HHHHHHHX XXXXX + * TYPE Description: + * - I = Random Device Code, changes with battery reset + * - B = Battery 0=OK 1=LOW + * - M = Manual Send Button Pressed 0=not pressed 1=pressed + * - C = Channel 00=CH1, 01=CH2, 10=CH3 + * - T = Temperature, 12 Bit 2's complement, scaled by 10 + * - H = Humidity = 7 Bit bin2dez 00-99, Display LL=10%, Display HH=110% (Range 20-90%) + * - X = Checksum, sum modulo 64 + * A Lidl AURIO (from 12/2018) with PCB marking YJ-T12 V02 has two extra bits in front. + * +*/ + +static const SubGhzBlockConst ws_protocol_gt_wt_02_const = { + .te_short = 500, + .te_long = 2000, + .te_delta = 150, + .min_count_bit_for_found = 37, +}; + +struct WSProtocolDecoderGT_WT02 { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + WSBlockGeneric generic; +}; + +struct WSProtocolEncoderGT_WT02 { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + WSBlockGeneric generic; +}; + +typedef enum { + GT_WT02DecoderStepReset = 0, + GT_WT02DecoderStepSaveDuration, + GT_WT02DecoderStepCheckDuration, +} GT_WT02DecoderStep; + +const SubGhzProtocolDecoder ws_protocol_gt_wt_02_decoder = { + .alloc = ws_protocol_decoder_gt_wt_02_alloc, + .free = ws_protocol_decoder_gt_wt_02_free, + + .feed = ws_protocol_decoder_gt_wt_02_feed, + .reset = ws_protocol_decoder_gt_wt_02_reset, + + .get_hash_data = ws_protocol_decoder_gt_wt_02_get_hash_data, + .serialize = ws_protocol_decoder_gt_wt_02_serialize, + .deserialize = ws_protocol_decoder_gt_wt_02_deserialize, + .get_string = ws_protocol_decoder_gt_wt_02_get_string, +}; + +const SubGhzProtocolEncoder ws_protocol_gt_wt_02_encoder = { + .alloc = NULL, + .free = NULL, + + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol ws_protocol_gt_wt_02 = { + .name = WS_PROTOCOL_GT_WT_02_NAME, + .type = SubGhzProtocolWeatherStation, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable, + + .decoder = &ws_protocol_gt_wt_02_decoder, + .encoder = &ws_protocol_gt_wt_02_encoder, +}; + +void* ws_protocol_decoder_gt_wt_02_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + WSProtocolDecoderGT_WT02* instance = malloc(sizeof(WSProtocolDecoderGT_WT02)); + instance->base.protocol = &ws_protocol_gt_wt_02; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void ws_protocol_decoder_gt_wt_02_free(void* context) { + furi_assert(context); + WSProtocolDecoderGT_WT02* instance = context; + free(instance); +} + +void ws_protocol_decoder_gt_wt_02_reset(void* context) { + furi_assert(context); + WSProtocolDecoderGT_WT02* instance = context; + instance->decoder.parser_step = GT_WT02DecoderStepReset; +} + +static bool ws_protocol_gt_wt_02_check(WSProtocolDecoderGT_WT02* instance) { + if(!instance->decoder.decode_data) return false; + uint8_t sum = (instance->decoder.decode_data >> 5) & 0xe; + uint64_t temp_data = instance->decoder.decode_data >> 9; + for(uint8_t i = 0; i < 7; i++) { + sum += (temp_data >> (i * 4)) & 0xF; + } + return ((uint8_t)(instance->decoder.decode_data & 0x3F) == (sum & 0x3F)); +} + +/** + * Analysis of received data + * @param instance Pointer to a WSBlockGeneric* instance + */ +static void ws_protocol_gt_wt_02_remote_controller(WSBlockGeneric* instance) { + instance->id = (instance->data >> 29) & 0xFF; + instance->battery_low = (instance->data >> 28) & 1; + instance->btn = (instance->data >> 27) & 1; + instance->channel = ((instance->data >> 25) & 0x3) + 1; + + if(!((instance->data >> 24) & 1)) { + instance->temp = (float)((instance->data >> 13) & 0x07FF) / 10.0f; + } else { + instance->temp = (float)((~(instance->data >> 13) & 0x07FF) + 1) / -10.0f; + } + + instance->humidity = (instance->data >> 6) & 0x7F; + if(instance->humidity <= 10) // actually the sensors sends 10 below working range of 20% + instance->humidity = 0; + else if(instance->humidity > 90) // actually the sensors sends 110 above working range of 90% + instance->humidity = 100; +} + +void ws_protocol_decoder_gt_wt_02_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + WSProtocolDecoderGT_WT02* instance = context; + + switch(instance->decoder.parser_step) { + case GT_WT02DecoderStepReset: + if((!level) && (DURATION_DIFF(duration, ws_protocol_gt_wt_02_const.te_short * 18) < + ws_protocol_gt_wt_02_const.te_delta * 8)) { + //Found syncPrefix + instance->decoder.parser_step = GT_WT02DecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } + break; + + case GT_WT02DecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = GT_WT02DecoderStepCheckDuration; + } else { + instance->decoder.parser_step = GT_WT02DecoderStepReset; + } + break; + + case GT_WT02DecoderStepCheckDuration: + if(!level) { + if(DURATION_DIFF(instance->decoder.te_last, ws_protocol_gt_wt_02_const.te_short) < + ws_protocol_gt_wt_02_const.te_delta) { + if(DURATION_DIFF(duration, ws_protocol_gt_wt_02_const.te_short * 18) < + ws_protocol_gt_wt_02_const.te_delta * 8) { + //Found syncPostfix + instance->decoder.parser_step = GT_WT02DecoderStepReset; + if((instance->decoder.decode_count_bit == + ws_protocol_gt_wt_02_const.min_count_bit_for_found) && + ws_protocol_gt_wt_02_check(instance)) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + ws_protocol_gt_wt_02_remote_controller(&instance->generic); + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } else if(instance->decoder.decode_count_bit == 1) { + instance->decoder.parser_step = GT_WT02DecoderStepSaveDuration; + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } else if( + DURATION_DIFF(duration, ws_protocol_gt_wt_02_const.te_long) < + ws_protocol_gt_wt_02_const.te_delta * 2) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = GT_WT02DecoderStepSaveDuration; + } else if( + DURATION_DIFF(duration, ws_protocol_gt_wt_02_const.te_long * 2) < + ws_protocol_gt_wt_02_const.te_delta * 4) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = GT_WT02DecoderStepSaveDuration; + } else { + instance->decoder.parser_step = GT_WT02DecoderStepReset; + } + } else { + instance->decoder.parser_step = GT_WT02DecoderStepReset; + } + } else { + instance->decoder.parser_step = GT_WT02DecoderStepReset; + } + break; + } +} + +uint8_t ws_protocol_decoder_gt_wt_02_get_hash_data(void* context) { + furi_assert(context); + WSProtocolDecoderGT_WT02* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool ws_protocol_decoder_gt_wt_02_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + WSProtocolDecoderGT_WT02* instance = context; + return ws_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +bool ws_protocol_decoder_gt_wt_02_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + WSProtocolDecoderGT_WT02* instance = context; + bool ret = false; + do { + if(!ws_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if(instance->generic.data_count_bit != + ws_protocol_gt_wt_02_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + ret = true; + } while(false); + return ret; +} + +void ws_protocol_decoder_gt_wt_02_get_string(void* context, FuriString* output) { + furi_assert(context); + WSProtocolDecoderGT_WT02* instance = context; + furi_string_printf( + output, + "%s %dbit\r\n" + "Key:0x%lX%08lX\r\n" + "Sn:0x%lX Ch:%d Bat:%d\r\n" + "Temp:%3.1f C Hum:%d%%", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data >> 32), + (uint32_t)(instance->generic.data), + instance->generic.id, + instance->generic.channel, + instance->generic.battery_low, + (double)instance->generic.temp, + instance->generic.humidity); +} diff --git a/applications/plugins/weather_station/protocols/gt_wt_02.h b/applications/plugins/weather_station/protocols/gt_wt_02.h new file mode 100644 index 00000000000..f17d5baa07d --- /dev/null +++ b/applications/plugins/weather_station/protocols/gt_wt_02.h @@ -0,0 +1,79 @@ +#pragma once + +#include + +#include +#include +#include +#include "ws_generic.h" +#include + +#define WS_PROTOCOL_GT_WT_02_NAME "GT-WT02" + +typedef struct WSProtocolDecoderGT_WT02 WSProtocolDecoderGT_WT02; +typedef struct WSProtocolEncoderGT_WT02 WSProtocolEncoderGT_WT02; + +extern const SubGhzProtocolDecoder ws_protocol_gt_wt_02_decoder; +extern const SubGhzProtocolEncoder ws_protocol_gt_wt_02_encoder; +extern const SubGhzProtocol ws_protocol_gt_wt_02; + +/** + * Allocate WSProtocolDecoderGT_WT02. + * @param environment Pointer to a SubGhzEnvironment instance + * @return WSProtocolDecoderGT_WT02* pointer to a WSProtocolDecoderGT_WT02 instance + */ +void* ws_protocol_decoder_gt_wt_02_alloc(SubGhzEnvironment* environment); + +/** + * Free WSProtocolDecoderGT_WT02. + * @param context Pointer to a WSProtocolDecoderGT_WT02 instance + */ +void ws_protocol_decoder_gt_wt_02_free(void* context); + +/** + * Reset decoder WSProtocolDecoderGT_WT02. + * @param context Pointer to a WSProtocolDecoderGT_WT02 instance + */ +void ws_protocol_decoder_gt_wt_02_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a WSProtocolDecoderGT_WT02 instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void ws_protocol_decoder_gt_wt_02_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a WSProtocolDecoderGT_WT02 instance + * @return hash Hash sum + */ +uint8_t ws_protocol_decoder_gt_wt_02_get_hash_data(void* context); + +/** + * Serialize data WSProtocolDecoderGT_WT02. + * @param context Pointer to a WSProtocolDecoderGT_WT02 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool ws_protocol_decoder_gt_wt_02_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data WSProtocolDecoderGT_WT02. + * @param context Pointer to a WSProtocolDecoderGT_WT02 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool ws_protocol_decoder_gt_wt_02_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a WSProtocolDecoderGT_WT02 instance + * @param output Resulting text + */ +void ws_protocol_decoder_gt_wt_02_get_string(void* context, FuriString* output); diff --git a/applications/plugins/weather_station/protocols/protocol_items.c b/applications/plugins/weather_station/protocols/protocol_items.c index d2b20e51a72..21768e0426e 100644 --- a/applications/plugins/weather_station/protocols/protocol_items.c +++ b/applications/plugins/weather_station/protocols/protocol_items.c @@ -4,6 +4,7 @@ const SubGhzProtocol* weather_station_protocol_registry_items[] = { &ws_protocol_infactory, &ws_protocol_thermopro_tx4, &ws_protocol_nexus_th, + &ws_protocol_gt_wt_02, &ws_protocol_gt_wt_03, &ws_protocol_acurite_606tx, &ws_protocol_acurite_609txc, diff --git a/applications/plugins/weather_station/protocols/protocol_items.h b/applications/plugins/weather_station/protocols/protocol_items.h index 45b297e10a6..aa064f0447e 100644 --- a/applications/plugins/weather_station/protocols/protocol_items.h +++ b/applications/plugins/weather_station/protocols/protocol_items.h @@ -4,6 +4,7 @@ #include "infactory.h" #include "thermopro_tx4.h" #include "nexus_th.h" +#include "gt_wt_02.h" #include "gt_wt_03.h" #include "acurite_606tx.h" #include "acurite_609txc.h" diff --git a/applications/plugins/weather_station/scenes/weather_station_scene_about.c b/applications/plugins/weather_station/scenes/weather_station_scene_about.c index df784ec96b5..d916dc76fe3 100644 --- a/applications/plugins/weather_station/scenes/weather_station_scene_about.c +++ b/applications/plugins/weather_station/scenes/weather_station_scene_about.c @@ -24,7 +24,7 @@ void weather_station_scene_about_on_enter(void* context) { furi_string_cat_printf(temp_str, "\e#%s\n", "Description"); furi_string_cat_printf( - temp_str, "Reading messages from\nweather station that work\nwith SubGhz sensors\n\n"); + temp_str, "Reading messages from\nweather stations that work\nwith SubGhz sensors\n\n"); furi_string_cat_printf(temp_str, "Supported protocols:\n"); size_t i = 0; From c511c67e71be4a9f3cc3b3be3f503032038f2b03 Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Wed, 23 Nov 2022 22:49:17 +1000 Subject: [PATCH 231/824] Core: thread allocation shortcut (#2007) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Core: thread alloc+set shortcut * Apps: use thread allocation shortcut * Mark some service threads as services * Init BT as soon as possible Co-authored-by: あく --- applications/debug/uart_echo/uart_echo.c | 6 +----- .../debug/unit_tests/storage/storage_test.c | 14 ++++---------- applications/main/bad_usb/bad_usb_script.c | 7 +------ applications/main/gpio/usb_uart_bridge.c | 13 +++---------- applications/main/subghz/helpers/subghz_chat.c | 7 ++----- .../helpers/subghz_frequency_analyzer_worker.c | 8 ++------ applications/main/u2f/u2f_hid.c | 6 +----- applications/plugins/dap_link/dap_link.c | 13 ------------- .../plugins/music_player/music_player_worker.c | 7 ++----- .../plugins/nfc_magic/nfc_magic_worker.c | 7 ++----- applications/plugins/picopass/picopass_worker.c | 7 ++----- applications/services/cli/cli_vcp.c | 5 +---- .../services/gui/modules/file_browser_worker.c | 6 +----- applications/services/rpc/rpc.c | 6 +----- applications/services/rpc/rpc_gui.c | 8 ++------ applications/system/updater/cli/updater_cli.c | 7 ++----- applications/system/updater/util/update_task.c | 7 ++----- firmware/targets/f7/Src/main.c | 5 +---- firmware/targets/f7/api_symbols.csv | 1 + firmware/targets/f7/ble_glue/ble_app.c | 6 +----- firmware/targets/f7/ble_glue/ble_glue.c | 6 +----- firmware/targets/f7/ble_glue/gap.c | 6 +----- firmware/targets/f7/furi_hal/furi_hal.c | 17 ++++++++--------- firmware/targets/f7/furi_hal/furi_hal_usb.c | 6 ++---- furi/core/thread.c | 13 +++++++++++++ furi/core/thread.h | 14 ++++++++++++++ furi/flipper.c | 10 +++++----- lib/ST25RFAL002/platform.c | 8 +++----- lib/flipper_application/flipper_application.c | 7 ++----- lib/infrared/worker/infrared_worker.c | 5 +---- lib/lfrfid/lfrfid_raw_worker.c | 5 +---- lib/lfrfid/lfrfid_worker.c | 6 +----- lib/nfc/helpers/reader_analyzer.c | 7 ++----- lib/nfc/nfc_worker.c | 6 +----- lib/one_wire/ibutton/ibutton_worker.c | 6 +----- lib/subghz/subghz_file_encoder_worker.c | 7 ++----- lib/subghz/subghz_tx_rx_worker.c | 7 ++----- lib/subghz/subghz_worker.c | 7 ++----- 38 files changed, 94 insertions(+), 195 deletions(-) diff --git a/applications/debug/uart_echo/uart_echo.c b/applications/debug/uart_echo/uart_echo.c index 701e5325a3f..16996ba8cd2 100644 --- a/applications/debug/uart_echo/uart_echo.c +++ b/applications/debug/uart_echo/uart_echo.c @@ -220,11 +220,7 @@ static UartEchoApp* uart_echo_app_alloc() { furi_hal_uart_set_br(FuriHalUartIdUSART1, 115200); furi_hal_uart_set_irq_cb(FuriHalUartIdUSART1, uart_echo_on_irq_cb, app); - app->worker_thread = furi_thread_alloc(); - furi_thread_set_name(app->worker_thread, "UsbUartWorker"); - furi_thread_set_stack_size(app->worker_thread, 1024); - furi_thread_set_context(app->worker_thread, app); - furi_thread_set_callback(app->worker_thread, uart_echo_worker); + app->worker_thread = furi_thread_alloc_ex("UsbUartWorker", 1024, uart_echo_worker, app); furi_thread_start(app->worker_thread); return app; diff --git a/applications/debug/unit_tests/storage/storage_test.c b/applications/debug/unit_tests/storage/storage_test.c index 7c1c669ff4a..115009701c4 100644 --- a/applications/debug/unit_tests/storage/storage_test.c +++ b/applications/debug/unit_tests/storage/storage_test.c @@ -43,11 +43,8 @@ MU_TEST(storage_file_open_lock) { File* file = storage_file_alloc(storage); // file_locker thread start - FuriThread* locker_thread = furi_thread_alloc(); - furi_thread_set_name(locker_thread, "StorageFileLocker"); - furi_thread_set_stack_size(locker_thread, 2048); - furi_thread_set_context(locker_thread, semaphore); - furi_thread_set_callback(locker_thread, storage_file_locker); + FuriThread* locker_thread = + furi_thread_alloc_ex("StorageFileLocker", 2048, storage_file_locker, semaphore); furi_thread_start(locker_thread); // wait for file lock @@ -133,11 +130,8 @@ MU_TEST(storage_dir_open_lock) { File* file = storage_file_alloc(storage); // file_locker thread start - FuriThread* locker_thread = furi_thread_alloc(); - furi_thread_set_name(locker_thread, "StorageDirLocker"); - furi_thread_set_stack_size(locker_thread, 2048); - furi_thread_set_context(locker_thread, semaphore); - furi_thread_set_callback(locker_thread, storage_dir_locker); + FuriThread* locker_thread = + furi_thread_alloc_ex("StorageDirLocker", 2048, storage_dir_locker, semaphore); furi_thread_start(locker_thread); // wait for dir lock diff --git a/applications/main/bad_usb/bad_usb_script.c b/applications/main/bad_usb/bad_usb_script.c index 295cc1c3e38..aa465351e20 100644 --- a/applications/main/bad_usb/bad_usb_script.c +++ b/applications/main/bad_usb/bad_usb_script.c @@ -657,12 +657,7 @@ BadUsbScript* bad_usb_script_open(FuriString* file_path) { bad_usb->st.state = BadUsbStateInit; bad_usb->st.error[0] = '\0'; - bad_usb->thread = furi_thread_alloc(); - furi_thread_set_name(bad_usb->thread, "BadUsbWorker"); - furi_thread_set_stack_size(bad_usb->thread, 2048); - furi_thread_set_context(bad_usb->thread, bad_usb); - furi_thread_set_callback(bad_usb->thread, bad_usb_worker); - + bad_usb->thread = furi_thread_alloc_ex("BadUsbWorker", 2048, bad_usb_worker, bad_usb); furi_thread_start(bad_usb->thread); return bad_usb; } diff --git a/applications/main/gpio/usb_uart_bridge.c b/applications/main/gpio/usb_uart_bridge.c index a1ab40329df..927eedb07dd 100644 --- a/applications/main/gpio/usb_uart_bridge.c +++ b/applications/main/gpio/usb_uart_bridge.c @@ -159,11 +159,8 @@ static int32_t usb_uart_worker(void* context) { usb_uart->tx_sem = furi_semaphore_alloc(1, 1); usb_uart->usb_mutex = furi_mutex_alloc(FuriMutexTypeNormal); - usb_uart->tx_thread = furi_thread_alloc(); - furi_thread_set_name(usb_uart->tx_thread, "UsbUartTxWorker"); - furi_thread_set_stack_size(usb_uart->tx_thread, 512); - furi_thread_set_context(usb_uart->tx_thread, usb_uart); - furi_thread_set_callback(usb_uart->tx_thread, usb_uart_tx_thread); + usb_uart->tx_thread = + furi_thread_alloc_ex("UsbUartTxWorker", 512, usb_uart_tx_thread, usb_uart); usb_uart_vcp_init(usb_uart, usb_uart->cfg.vcp_ch); usb_uart_serial_init(usb_uart, usb_uart->cfg.uart_ch); @@ -338,11 +335,7 @@ UsbUartBridge* usb_uart_enable(UsbUartConfig* cfg) { memcpy(&(usb_uart->cfg_new), cfg, sizeof(UsbUartConfig)); - usb_uart->thread = furi_thread_alloc(); - furi_thread_set_name(usb_uart->thread, "UsbUartWorker"); - furi_thread_set_stack_size(usb_uart->thread, 1024); - furi_thread_set_context(usb_uart->thread, usb_uart); - furi_thread_set_callback(usb_uart->thread, usb_uart_worker); + usb_uart->thread = furi_thread_alloc_ex("UsbUartWorker", 1024, usb_uart_worker, usb_uart); furi_thread_start(usb_uart->thread); return usb_uart; diff --git a/applications/main/subghz/helpers/subghz_chat.c b/applications/main/subghz/helpers/subghz_chat.c index f821feaa9b2..dbf34c97051 100644 --- a/applications/main/subghz/helpers/subghz_chat.c +++ b/applications/main/subghz/helpers/subghz_chat.c @@ -59,11 +59,8 @@ SubGhzChatWorker* subghz_chat_worker_alloc(Cli* cli) { instance->cli = cli; - instance->thread = furi_thread_alloc(); - furi_thread_set_name(instance->thread, "SubGhzChat"); - furi_thread_set_stack_size(instance->thread, 2048); - furi_thread_set_context(instance->thread, instance); - furi_thread_set_callback(instance->thread, subghz_chat_worker_thread); + instance->thread = + furi_thread_alloc_ex("SubGhzChat", 2048, subghz_chat_worker_thread, instance); instance->subghz_txrx = subghz_tx_rx_worker_alloc(); instance->event_queue = furi_message_queue_alloc(80, sizeof(SubGhzChatEvent)); return instance; diff --git a/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.c b/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.c index 0341990d8d4..7826e2ae787 100644 --- a/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.c +++ b/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.c @@ -265,12 +265,8 @@ SubGhzFrequencyAnalyzerWorker* subghz_frequency_analyzer_worker_alloc(void* cont furi_assert(context); SubGhzFrequencyAnalyzerWorker* instance = malloc(sizeof(SubGhzFrequencyAnalyzerWorker)); - instance->thread = furi_thread_alloc(); - furi_thread_set_name(instance->thread, "SubGhzFAWorker"); - furi_thread_set_stack_size(instance->thread, 2048); - furi_thread_set_context(instance->thread, instance); - furi_thread_set_callback(instance->thread, subghz_frequency_analyzer_worker_thread); - + instance->thread = furi_thread_alloc_ex( + "SubGhzFAWorker", 2048, subghz_frequency_analyzer_worker_thread, instance); SubGhz* subghz = context; instance->setting = subghz->setting; return instance; diff --git a/applications/main/u2f/u2f_hid.c b/applications/main/u2f/u2f_hid.c index b6e86384d99..916400e91e2 100644 --- a/applications/main/u2f/u2f_hid.c +++ b/applications/main/u2f/u2f_hid.c @@ -282,11 +282,7 @@ U2fHid* u2f_hid_start(U2fData* u2f_inst) { u2f_hid->u2f_instance = u2f_inst; - u2f_hid->thread = furi_thread_alloc(); - furi_thread_set_name(u2f_hid->thread, "U2fHidWorker"); - furi_thread_set_stack_size(u2f_hid->thread, 2048); - furi_thread_set_context(u2f_hid->thread, u2f_hid); - furi_thread_set_callback(u2f_hid->thread, u2f_hid_worker); + u2f_hid->thread = furi_thread_alloc_ex("U2fHidWorker", 2048, u2f_hid_worker, u2f_hid); furi_thread_start(u2f_hid->thread); return u2f_hid; } diff --git a/applications/plugins/dap_link/dap_link.c b/applications/plugins/dap_link/dap_link.c index 443d77c5eed..5c30e85c5ba 100644 --- a/applications/plugins/dap_link/dap_link.c +++ b/applications/plugins/dap_link/dap_link.c @@ -441,19 +441,6 @@ static int32_t cdc_process(void* p) { /******************************* MAIN APP **********************************/ /***************************************************************************/ -static FuriThread* furi_thread_alloc_ex( - const char* name, - uint32_t stack_size, - FuriThreadCallback callback, - void* context) { - FuriThread* thread = furi_thread_alloc(); - furi_thread_set_name(thread, name); - furi_thread_set_stack_size(thread, stack_size); - furi_thread_set_callback(thread, callback); - furi_thread_set_context(thread, context); - return thread; -} - static DapApp* dap_app_alloc() { DapApp* dap_app = malloc(sizeof(DapApp)); dap_app->dap_thread = furi_thread_alloc_ex("DAP Process", 1024, dap_process, dap_app); diff --git a/applications/plugins/music_player/music_player_worker.c b/applications/plugins/music_player/music_player_worker.c index 3f1ac62f7aa..0d683f4a6a6 100644 --- a/applications/plugins/music_player/music_player_worker.c +++ b/applications/plugins/music_player/music_player_worker.c @@ -97,11 +97,8 @@ MusicPlayerWorker* music_player_worker_alloc() { NoteBlockArray_init(instance->notes); - instance->thread = furi_thread_alloc(); - furi_thread_set_name(instance->thread, "MusicPlayerWorker"); - furi_thread_set_stack_size(instance->thread, 1024); - furi_thread_set_context(instance->thread, instance); - furi_thread_set_callback(instance->thread, music_player_worker_thread_callback); + instance->thread = furi_thread_alloc_ex( + "MusicPlayerWorker", 1024, music_player_worker_thread_callback, instance); instance->volume = 1.0f; diff --git a/applications/plugins/nfc_magic/nfc_magic_worker.c b/applications/plugins/nfc_magic/nfc_magic_worker.c index 0e1f6cea401..523c794f7dd 100644 --- a/applications/plugins/nfc_magic/nfc_magic_worker.c +++ b/applications/plugins/nfc_magic/nfc_magic_worker.c @@ -15,11 +15,8 @@ NfcMagicWorker* nfc_magic_worker_alloc() { NfcMagicWorker* nfc_magic_worker = malloc(sizeof(NfcMagicWorker)); // Worker thread attributes - nfc_magic_worker->thread = furi_thread_alloc(); - furi_thread_set_name(nfc_magic_worker->thread, "NfcMagicWorker"); - furi_thread_set_stack_size(nfc_magic_worker->thread, 8192); - furi_thread_set_callback(nfc_magic_worker->thread, nfc_magic_worker_task); - furi_thread_set_context(nfc_magic_worker->thread, nfc_magic_worker); + nfc_magic_worker->thread = + furi_thread_alloc_ex("NfcMagicWorker", 8192, nfc_magic_worker_task, nfc_magic_worker); nfc_magic_worker->callback = NULL; nfc_magic_worker->context = NULL; diff --git a/applications/plugins/picopass/picopass_worker.c b/applications/plugins/picopass/picopass_worker.c index 2c9b1a0ceed..c2a32cb6ae8 100644 --- a/applications/plugins/picopass/picopass_worker.c +++ b/applications/plugins/picopass/picopass_worker.c @@ -25,11 +25,8 @@ PicopassWorker* picopass_worker_alloc() { PicopassWorker* picopass_worker = malloc(sizeof(PicopassWorker)); // Worker thread attributes - picopass_worker->thread = furi_thread_alloc(); - furi_thread_set_name(picopass_worker->thread, "PicopassWorker"); - furi_thread_set_stack_size(picopass_worker->thread, 8192); - furi_thread_set_callback(picopass_worker->thread, picopass_worker_task); - furi_thread_set_context(picopass_worker->thread, picopass_worker); + picopass_worker->thread = + furi_thread_alloc_ex("PicopassWorker", 8192, picopass_worker_task, picopass_worker); picopass_worker->callback = NULL; picopass_worker->context = NULL; diff --git a/applications/services/cli/cli_vcp.c b/applications/services/cli/cli_vcp.c index 94b82950d9c..ad26dca47cd 100644 --- a/applications/services/cli/cli_vcp.c +++ b/applications/services/cli/cli_vcp.c @@ -68,10 +68,7 @@ static void cli_vcp_init() { vcp->connected = false; - vcp->thread = furi_thread_alloc(); - furi_thread_set_name(vcp->thread, "CliVcpWorker"); - furi_thread_set_stack_size(vcp->thread, 1024); - furi_thread_set_callback(vcp->thread, vcp_worker); + vcp->thread = furi_thread_alloc_ex("CliVcpWorker", 1024, vcp_worker, NULL); furi_thread_start(vcp->thread); FURI_LOG_I(TAG, "Init OK"); diff --git a/applications/services/gui/modules/file_browser_worker.c b/applications/services/gui/modules/file_browser_worker.c index b9b2b2d8fe3..34d83032d16 100644 --- a/applications/services/gui/modules/file_browser_worker.c +++ b/applications/services/gui/modules/file_browser_worker.c @@ -367,11 +367,7 @@ BrowserWorker* browser->skip_assets = skip_assets; browser->path_next = furi_string_alloc_set(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); + browser->thread = furi_thread_alloc_ex("BrowserWorker", 2048, browser_worker, browser); furi_thread_start(browser->thread); return browser; diff --git a/applications/services/rpc/rpc.c b/applications/services/rpc/rpc.c index f1e0cbd6687..ebf01572295 100644 --- a/applications/services/rpc/rpc.c +++ b/applications/services/rpc/rpc.c @@ -370,11 +370,7 @@ RpcSession* rpc_session_open(Rpc* rpc) { }; rpc_add_handler(session, PB_Main_stop_session_tag, &rpc_handler); - session->thread = furi_thread_alloc(); - furi_thread_set_name(session->thread, "RpcSessionWorker"); - furi_thread_set_stack_size(session->thread, 3072); - furi_thread_set_context(session->thread, session); - furi_thread_set_callback(session->thread, rpc_session_worker); + session->thread = furi_thread_alloc_ex("RpcSessionWorker", 3072, rpc_session_worker, session); furi_thread_set_state_context(session->thread, session); furi_thread_set_state_callback(session->thread, rpc_session_free_callback); diff --git a/applications/services/rpc/rpc_gui.c b/applications/services/rpc/rpc_gui.c index 029ed010645..e66553d5133 100644 --- a/applications/services/rpc/rpc_gui.c +++ b/applications/services/rpc/rpc_gui.c @@ -88,12 +88,8 @@ static void rpc_system_gui_start_screen_stream_process(const PB_Main* request, v malloc(PB_BYTES_ARRAY_T_ALLOCSIZE(framebuffer_size)); rpc_gui->transmit_frame->content.gui_screen_frame.data->size = framebuffer_size; // Transmission thread for async TX - rpc_gui->transmit_thread = furi_thread_alloc(); - furi_thread_set_name(rpc_gui->transmit_thread, "GuiRpcWorker"); - furi_thread_set_callback( - rpc_gui->transmit_thread, rpc_system_gui_screen_stream_frame_transmit_thread); - furi_thread_set_context(rpc_gui->transmit_thread, rpc_gui); - furi_thread_set_stack_size(rpc_gui->transmit_thread, 1024); + rpc_gui->transmit_thread = furi_thread_alloc_ex( + "GuiRpcWorker", 1024, rpc_system_gui_screen_stream_frame_transmit_thread, rpc_gui); furi_thread_start(rpc_gui->transmit_thread); // GUI framebuffer callback gui_add_framebuffer_callback( diff --git a/applications/system/updater/cli/updater_cli.c b/applications/system/updater/cli/updater_cli.c index c3cdbb5f799..f8e21ca3ee4 100644 --- a/applications/system/updater/cli/updater_cli.c +++ b/applications/system/updater/cli/updater_cli.c @@ -110,11 +110,8 @@ static void updater_start_app() { * inside loader process, at startup. * So, accessing its record would cause a deadlock */ - FuriThread* thread = furi_thread_alloc(); - - furi_thread_set_name(thread, "UpdateAppSpawner"); - furi_thread_set_stack_size(thread, 768); - furi_thread_set_callback(thread, updater_spawner_thread_worker); + FuriThread* thread = + furi_thread_alloc_ex("UpdateAppSpawner", 768, updater_spawner_thread_worker, NULL); furi_thread_set_state_callback(thread, updater_spawner_thread_cleanup); furi_thread_set_state_context(thread, thread); furi_thread_start(thread); diff --git a/applications/system/updater/util/update_task.c b/applications/system/updater/util/update_task.c index de172dd4944..b43a6df167d 100644 --- a/applications/system/updater/util/update_task.c +++ b/applications/system/updater/util/update_task.c @@ -216,11 +216,8 @@ UpdateTask* update_task_alloc() { update_task->boot_mode = furi_hal_rtc_get_boot_mode(); update_task->update_path = furi_string_alloc(); - FuriThread* thread = update_task->thread = furi_thread_alloc(); - - furi_thread_set_name(thread, "UpdateWorker"); - furi_thread_set_stack_size(thread, 5120); - furi_thread_set_context(thread, update_task); + FuriThread* thread = update_task->thread = + furi_thread_alloc_ex("UpdateWorker", 5120, NULL, update_task); furi_thread_set_state_callback(thread, update_task_worker_thread_cb); furi_thread_set_state_context(thread, update_task); diff --git a/firmware/targets/f7/Src/main.c b/firmware/targets/f7/Src/main.c index 5f33569ae0f..d9a2221a21f 100644 --- a/firmware/targets/f7/Src/main.c +++ b/firmware/targets/f7/Src/main.c @@ -26,10 +26,7 @@ int main() { // Flipper critical FURI HAL furi_hal_init_early(); - FuriThread* main_thread = furi_thread_alloc(); - furi_thread_set_name(main_thread, "Init"); - furi_thread_set_stack_size(main_thread, 4096); - furi_thread_set_callback(main_thread, init_task); + FuriThread* main_thread = furi_thread_alloc_ex("Init", 4096, init_task, NULL); #ifdef FURI_RAM_EXEC furi_thread_start(main_thread); diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 1ad5efa7277..5ccb9d4339b 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1471,6 +1471,7 @@ Function,+,furi_string_utf8_length,size_t,FuriString* Function,+,furi_string_utf8_push,void,"FuriString*, FuriStringUnicodeValue" Function,+,furi_string_vprintf,int,"FuriString*, const char[], va_list" Function,+,furi_thread_alloc,FuriThread*, +Function,+,furi_thread_alloc_ex,FuriThread*,"const char*, uint32_t, FuriThreadCallback, void*" Function,+,furi_thread_catch,void, Function,-,furi_thread_disable_heap_trace,void,FuriThread* Function,+,furi_thread_enable_heap_trace,void,FuriThread* diff --git a/firmware/targets/f7/ble_glue/ble_app.c b/firmware/targets/f7/ble_glue/ble_app.c index 3cf02009fc8..30be3c7ceca 100644 --- a/firmware/targets/f7/ble_glue/ble_app.c +++ b/firmware/targets/f7/ble_glue/ble_app.c @@ -40,11 +40,7 @@ bool ble_app_init() { ble_app->hci_mtx = furi_mutex_alloc(FuriMutexTypeNormal); ble_app->hci_sem = furi_semaphore_alloc(1, 0); // HCI transport layer thread to handle user asynch events - ble_app->thread = furi_thread_alloc(); - furi_thread_set_name(ble_app->thread, "BleHciDriver"); - furi_thread_set_stack_size(ble_app->thread, 1024); - furi_thread_set_context(ble_app->thread, ble_app); - furi_thread_set_callback(ble_app->thread, ble_app_hci_thread); + ble_app->thread = furi_thread_alloc_ex("BleHciDriver", 1024, ble_app_hci_thread, ble_app); furi_thread_start(ble_app->thread); // Initialize Ble Transport Layer diff --git a/firmware/targets/f7/ble_glue/ble_glue.c b/firmware/targets/f7/ble_glue/ble_glue.c index 87af5f2a892..b3752f17f2c 100644 --- a/firmware/targets/f7/ble_glue/ble_glue.c +++ b/firmware/targets/f7/ble_glue/ble_glue.c @@ -78,11 +78,7 @@ void ble_glue_init() { ble_glue->shci_sem = furi_semaphore_alloc(1, 0); // FreeRTOS system task creation - ble_glue->thread = furi_thread_alloc(); - furi_thread_set_name(ble_glue->thread, "BleShciDriver"); - furi_thread_set_stack_size(ble_glue->thread, 1024); - furi_thread_set_context(ble_glue->thread, ble_glue); - furi_thread_set_callback(ble_glue->thread, ble_glue_shci_thread); + ble_glue->thread = furi_thread_alloc_ex("BleShciDriver", 1024, ble_glue_shci_thread, ble_glue); furi_thread_start(ble_glue->thread); // System channel initialization diff --git a/firmware/targets/f7/ble_glue/gap.c b/firmware/targets/f7/ble_glue/gap.c index cf27df3a370..3e29527ecd3 100644 --- a/firmware/targets/f7/ble_glue/gap.c +++ b/firmware/targets/f7/ble_glue/gap.c @@ -492,11 +492,7 @@ bool gap_init(GapConfig* config, GapEventCallback on_event_cb, void* context) { gap->enable_adv = true; // Thread configuration - gap->thread = furi_thread_alloc(); - furi_thread_set_name(gap->thread, "BleGapDriver"); - furi_thread_set_stack_size(gap->thread, 1024); - furi_thread_set_context(gap->thread, gap); - furi_thread_set_callback(gap->thread, gap_app); + gap->thread = furi_thread_alloc_ex("BleGapDriver", 1024, gap_app, gap); furi_thread_start(gap->thread); // Command queue allocation diff --git a/firmware/targets/f7/furi_hal/furi_hal.c b/firmware/targets/f7/furi_hal/furi_hal.c index 141efdb6036..ec82c377aa9 100644 --- a/firmware/targets/f7/furi_hal/furi_hal.c +++ b/firmware/targets/f7/furi_hal/furi_hal.c @@ -61,26 +61,25 @@ void furi_hal_init() { furi_hal_crypto_init(); - // USB -#ifndef FURI_RAM_EXEC - furi_hal_usb_init(); - FURI_LOG_I(TAG, "USB OK"); -#endif - furi_hal_i2c_init(); // High Level furi_hal_power_init(); furi_hal_light_init(); + + furi_hal_bt_init(); + furi_hal_memory_init(); + furi_hal_compress_icon_init(); + #ifndef FURI_RAM_EXEC + // USB + furi_hal_usb_init(); + FURI_LOG_I(TAG, "USB OK"); furi_hal_vibro_init(); furi_hal_subghz_init(); furi_hal_nfc_init(); furi_hal_rfid_init(); #endif - furi_hal_bt_init(); - furi_hal_memory_init(); - furi_hal_compress_icon_init(); // FatFS driver initialization MX_FATFS_Init(); diff --git a/firmware/targets/f7/furi_hal/furi_hal_usb.c b/firmware/targets/f7/furi_hal/furi_hal_usb.c index 86b10c79cfa..1eaeffd6ab0 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_usb.c +++ b/firmware/targets/f7/furi_hal/furi_hal_usb.c @@ -80,10 +80,8 @@ void furi_hal_usb_init(void) { NVIC_EnableIRQ(USB_LP_IRQn); NVIC_EnableIRQ(USB_HP_IRQn); - usb.thread = furi_thread_alloc(); - furi_thread_set_name(usb.thread, "UsbDriver"); - furi_thread_set_stack_size(usb.thread, 1024); - furi_thread_set_callback(usb.thread, furi_hal_usb_thread); + usb.thread = furi_thread_alloc_ex("UsbDriver", 1024, furi_hal_usb_thread, NULL); + furi_thread_mark_as_service(usb.thread); furi_thread_start(usb.thread); FURI_LOG_I(TAG, "Init OK"); diff --git a/furi/core/thread.c b/furi/core/thread.c index 201b47fe78c..34cc7d987a9 100644 --- a/furi/core/thread.c +++ b/furi/core/thread.c @@ -135,6 +135,19 @@ FuriThread* furi_thread_alloc() { return thread; } +FuriThread* furi_thread_alloc_ex( + const char* name, + uint32_t stack_size, + FuriThreadCallback callback, + void* context) { + FuriThread* thread = furi_thread_alloc(); + furi_thread_set_name(thread, name); + furi_thread_set_stack_size(thread, stack_size); + furi_thread_set_callback(thread, callback); + furi_thread_set_context(thread, context); + return thread; +} + void furi_thread_free(FuriThread* thread) { furi_assert(thread); furi_assert(thread->state == FuriThreadStateStopped); diff --git a/furi/core/thread.h b/furi/core/thread.h index fda81bb3a8f..6ec8ebfec40 100644 --- a/furi/core/thread.h +++ b/furi/core/thread.h @@ -60,6 +60,20 @@ typedef void (*FuriThreadStateCallback)(FuriThreadState state, void* context); */ FuriThread* furi_thread_alloc(); +/** Allocate FuriThread, shortcut version + * + * @param name + * @param stack_size + * @param callback + * @param context + * @return FuriThread* + */ +FuriThread* furi_thread_alloc_ex( + const char* name, + uint32_t stack_size, + FuriThreadCallback callback, + void* context); + /** Release FuriThread * * @param thread FuriThread instance diff --git a/furi/flipper.c b/furi/flipper.c index 2acfea0151d..73899e58bf6 100644 --- a/furi/flipper.c +++ b/furi/flipper.c @@ -34,11 +34,11 @@ void flipper_init() { for(size_t i = 0; i < FLIPPER_SERVICES_COUNT; i++) { FURI_LOG_I(TAG, "starting service %s", FLIPPER_SERVICES[i].name); - FuriThread* thread = furi_thread_alloc(); - - furi_thread_set_name(thread, FLIPPER_SERVICES[i].name); - furi_thread_set_stack_size(thread, FLIPPER_SERVICES[i].stack_size); - furi_thread_set_callback(thread, FLIPPER_SERVICES[i].app); + FuriThread* thread = furi_thread_alloc_ex( + FLIPPER_SERVICES[i].name, + FLIPPER_SERVICES[i].stack_size, + FLIPPER_SERVICES[i].app, + NULL); furi_thread_mark_as_service(thread); furi_thread_start(thread); diff --git a/lib/ST25RFAL002/platform.c b/lib/ST25RFAL002/platform.c index af9dc6ff736..754e2565013 100644 --- a/lib/ST25RFAL002/platform.c +++ b/lib/ST25RFAL002/platform.c @@ -45,11 +45,9 @@ void platformDisableIrqCallback() { void platformSetIrqCallback(PlatformIrqCallback callback) { rfal_platform.callback = callback; - rfal_platform.thread = furi_thread_alloc(); - - furi_thread_set_name(rfal_platform.thread, "RfalIrqDriver"); - furi_thread_set_callback(rfal_platform.thread, rfal_platform_irq_thread); - furi_thread_set_stack_size(rfal_platform.thread, 1024); + rfal_platform.thread = + furi_thread_alloc_ex("RfalIrqDriver", 1024, rfal_platform_irq_thread, NULL); + furi_thread_mark_as_service(rfal_platform.thread); furi_thread_set_priority(rfal_platform.thread, FuriThreadPriorityIsr); furi_thread_start(rfal_platform.thread); diff --git a/lib/flipper_application/flipper_application.c b/lib/flipper_application/flipper_application.c index cf44eebb2ab..618a3623113 100644 --- a/lib/flipper_application/flipper_application.c +++ b/lib/flipper_application/flipper_application.c @@ -104,11 +104,8 @@ FuriThread* flipper_application_spawn(FlipperApplication* app, void* args) { const FlipperApplicationManifest* manifest = flipper_application_get_manifest(app); furi_check(manifest->stack_size > 0); - app->thread = furi_thread_alloc(); - furi_thread_set_stack_size(app->thread, manifest->stack_size); - furi_thread_set_name(app->thread, manifest->name); - furi_thread_set_callback(app->thread, flipper_application_thread); - furi_thread_set_context(app->thread, args); + app->thread = furi_thread_alloc_ex( + manifest->name, manifest->stack_size, flipper_application_thread, args); return app->thread; } diff --git a/lib/infrared/worker/infrared_worker.c b/lib/infrared/worker/infrared_worker.c index c03f180f66a..57288f3c3a9 100644 --- a/lib/infrared/worker/infrared_worker.c +++ b/lib/infrared/worker/infrared_worker.c @@ -223,10 +223,7 @@ void infrared_worker_rx_set_received_signal_callback( InfraredWorker* infrared_worker_alloc() { InfraredWorker* instance = malloc(sizeof(InfraredWorker)); - instance->thread = furi_thread_alloc(); - furi_thread_set_name(instance->thread, "InfraredWorker"); - furi_thread_set_stack_size(instance->thread, 2048); - furi_thread_set_context(instance->thread, instance); + instance->thread = furi_thread_alloc_ex("InfraredWorker", 2048, NULL, instance); size_t buffer_size = MAX(sizeof(InfraredWorkerTiming) * (MAX_TIMINGS_AMOUNT + 1), diff --git a/lib/lfrfid/lfrfid_raw_worker.c b/lib/lfrfid/lfrfid_raw_worker.c index 1547d20f958..8c69acedba8 100644 --- a/lib/lfrfid/lfrfid_raw_worker.c +++ b/lib/lfrfid/lfrfid_raw_worker.c @@ -61,10 +61,7 @@ static int32_t lfrfid_raw_emulate_worker_thread(void* thread_context); LFRFIDRawWorker* lfrfid_raw_worker_alloc() { LFRFIDRawWorker* worker = malloc(sizeof(LFRFIDRawWorker)); - worker->thread = furi_thread_alloc(); - furi_thread_set_name(worker->thread, "lfrfid_raw_worker"); - furi_thread_set_context(worker->thread, worker); - furi_thread_set_stack_size(worker->thread, 2048); + worker->thread = furi_thread_alloc_ex("LfrfidRawWorker", 2048, NULL, worker); worker->events = furi_event_flag_alloc(NULL); diff --git a/lib/lfrfid/lfrfid_worker.c b/lib/lfrfid/lfrfid_worker.c index f3e37fa3c9a..f33c1aed632 100644 --- a/lib/lfrfid/lfrfid_worker.c +++ b/lib/lfrfid/lfrfid_worker.c @@ -29,11 +29,7 @@ LFRFIDWorker* lfrfid_worker_alloc(ProtocolDict* dict) { worker->raw_filename = NULL; worker->mode_storage = NULL; - worker->thread = furi_thread_alloc(); - furi_thread_set_name(worker->thread, "lfrfid_worker"); - furi_thread_set_callback(worker->thread, lfrfid_worker_thread); - furi_thread_set_context(worker->thread, worker); - furi_thread_set_stack_size(worker->thread, 2048); + worker->thread = furi_thread_alloc_ex("LfrfidWorker", 2048, lfrfid_worker_thread, worker); worker->protocols = dict; diff --git a/lib/nfc/helpers/reader_analyzer.c b/lib/nfc/helpers/reader_analyzer.c index 7fed9c6f68a..73b4b125e6d 100644 --- a/lib/nfc/helpers/reader_analyzer.c +++ b/lib/nfc/helpers/reader_analyzer.c @@ -104,11 +104,8 @@ ReaderAnalyzer* reader_analyzer_alloc() { instance->stream = furi_stream_buffer_alloc(READER_ANALYZER_MAX_BUFF_SIZE, sizeof(ReaderAnalyzerHeader)); - instance->thread = furi_thread_alloc(); - furi_thread_set_name(instance->thread, "ReaderAnalyzerWorker"); - furi_thread_set_stack_size(instance->thread, 2048); - furi_thread_set_callback(instance->thread, reader_analyzer_thread); - furi_thread_set_context(instance->thread, instance); + instance->thread = + furi_thread_alloc_ex("ReaderAnalyzerWorker", 2048, reader_analyzer_thread, instance); furi_thread_set_priority(instance->thread, FuriThreadPriorityLow); return instance; diff --git a/lib/nfc/nfc_worker.c b/lib/nfc/nfc_worker.c index 5ad37c6e3cc..c701132c321 100644 --- a/lib/nfc/nfc_worker.c +++ b/lib/nfc/nfc_worker.c @@ -12,11 +12,7 @@ NfcWorker* nfc_worker_alloc() { NfcWorker* nfc_worker = malloc(sizeof(NfcWorker)); // Worker thread attributes - nfc_worker->thread = furi_thread_alloc(); - furi_thread_set_name(nfc_worker->thread, "NfcWorker"); - furi_thread_set_stack_size(nfc_worker->thread, 8192); - furi_thread_set_callback(nfc_worker->thread, nfc_worker_task); - furi_thread_set_context(nfc_worker->thread, nfc_worker); + nfc_worker->thread = furi_thread_alloc_ex("NfcWorker", 8192, nfc_worker_task, nfc_worker); nfc_worker->callback = NULL; nfc_worker->context = NULL; diff --git a/lib/one_wire/ibutton/ibutton_worker.c b/lib/one_wire/ibutton/ibutton_worker.c index 26982bcb678..29126d84535 100644 --- a/lib/one_wire/ibutton/ibutton_worker.c +++ b/lib/one_wire/ibutton/ibutton_worker.c @@ -37,11 +37,7 @@ iButtonWorker* ibutton_worker_alloc() { worker->emulate_cb = NULL; worker->cb_ctx = NULL; - worker->thread = furi_thread_alloc(); - furi_thread_set_name(worker->thread, "ibutton_worker"); - furi_thread_set_callback(worker->thread, ibutton_worker_thread); - furi_thread_set_context(worker->thread, worker); - furi_thread_set_stack_size(worker->thread, 2048); + worker->thread = furi_thread_alloc_ex("iButtonWorker", 2048, ibutton_worker_thread, worker); worker->protocols = protocol_dict_alloc(ibutton_protocols, iButtonProtocolMax); diff --git a/lib/subghz/subghz_file_encoder_worker.c b/lib/subghz/subghz_file_encoder_worker.c index 29834b4122d..f987430d6d9 100644 --- a/lib/subghz/subghz_file_encoder_worker.c +++ b/lib/subghz/subghz_file_encoder_worker.c @@ -174,11 +174,8 @@ static int32_t subghz_file_encoder_worker_thread(void* context) { SubGhzFileEncoderWorker* subghz_file_encoder_worker_alloc() { SubGhzFileEncoderWorker* instance = malloc(sizeof(SubGhzFileEncoderWorker)); - instance->thread = furi_thread_alloc(); - furi_thread_set_name(instance->thread, "SubGhzFEWorker"); - furi_thread_set_stack_size(instance->thread, 2048); - furi_thread_set_context(instance->thread, instance); - furi_thread_set_callback(instance->thread, subghz_file_encoder_worker_thread); + instance->thread = + furi_thread_alloc_ex("SubGhzFEWorker", 2048, subghz_file_encoder_worker_thread, instance); instance->stream = furi_stream_buffer_alloc(sizeof(int32_t) * 2048, sizeof(int32_t)); instance->storage = furi_record_open(RECORD_STORAGE); diff --git a/lib/subghz/subghz_tx_rx_worker.c b/lib/subghz/subghz_tx_rx_worker.c index 37c0bfc5e96..42124bebc1c 100644 --- a/lib/subghz/subghz_tx_rx_worker.c +++ b/lib/subghz/subghz_tx_rx_worker.c @@ -201,11 +201,8 @@ static int32_t subghz_tx_rx_worker_thread(void* context) { SubGhzTxRxWorker* subghz_tx_rx_worker_alloc() { SubGhzTxRxWorker* instance = malloc(sizeof(SubGhzTxRxWorker)); - instance->thread = furi_thread_alloc(); - furi_thread_set_name(instance->thread, "SubGhzTxRxWorker"); - furi_thread_set_stack_size(instance->thread, 2048); - furi_thread_set_context(instance->thread, instance); - furi_thread_set_callback(instance->thread, subghz_tx_rx_worker_thread); + instance->thread = + furi_thread_alloc_ex("SubGhzTxRxWorker", 2048, subghz_tx_rx_worker_thread, instance); instance->stream_tx = furi_stream_buffer_alloc(sizeof(uint8_t) * SUBGHZ_TXRX_WORKER_BUF_SIZE, sizeof(uint8_t)); instance->stream_rx = diff --git a/lib/subghz/subghz_worker.c b/lib/subghz/subghz_worker.c index 61146c168f5..35e39988585 100644 --- a/lib/subghz/subghz_worker.c +++ b/lib/subghz/subghz_worker.c @@ -88,11 +88,8 @@ static int32_t subghz_worker_thread_callback(void* context) { SubGhzWorker* subghz_worker_alloc() { SubGhzWorker* instance = malloc(sizeof(SubGhzWorker)); - instance->thread = furi_thread_alloc(); - furi_thread_set_name(instance->thread, "SubGhzWorker"); - furi_thread_set_stack_size(instance->thread, 2048); - furi_thread_set_context(instance->thread, instance); - furi_thread_set_callback(instance->thread, subghz_worker_thread_callback); + instance->thread = + furi_thread_alloc_ex("SubGhzWorker", 2048, subghz_worker_thread_callback, instance); instance->stream = furi_stream_buffer_alloc(sizeof(LevelDuration) * 4096, sizeof(LevelDuration)); From 4f3ef83c77490f344ab89222d4800f705e0aa061 Mon Sep 17 00:00:00 2001 From: lauaall <48836917+skelq@users.noreply.github.com> Date: Wed, 23 Nov 2022 14:59:55 +0200 Subject: [PATCH 232/824] Docs: fix typos (#2016) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixed typos in UnitTests.md Co-authored-by: あく --- documentation/UnitTests.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/documentation/UnitTests.md b/documentation/UnitTests.md index 3f56a9a9003..896426567dd 100644 --- a/documentation/UnitTests.md +++ b/documentation/UnitTests.md @@ -13,7 +13,7 @@ Running existing unit tests is useful to ensure that the new code doesn't introd In order to run the unit tests, follow these steps: 1. Compile the firmware with the tests enabled: `./fbt FIRMWARE_APP_SET=unit_tests`. 2. Flash the firmware using your preferred method. -3. Copy the [assets/unit_tests](assets/unit_tests) folder to the root your Flipper Zero's SD card. +3. Copy the [assets/unit_tests](assets/unit_tests) folder to the root of your Flipper Zero's SD card. 4. Launch the CLI session and run the `unit_tests` command. **NOTE:** To run a particular test (and skip all others), specify its name as the command argument. @@ -22,9 +22,9 @@ See [test_index.c](applications/debug/unit_tests/test_index.c) for the complete ## Adding unit tests ### General #### Entry point -The common entry point for all tests it the [unit_tests](applications/debug/unit_tests) application. Test-specific code is placed into an arbitrarily named subdirectory and is then called from the [test_index.c](applications/debug/unit_tests/test_index.c) source file. +The common entry point for all tests is the [unit_tests](applications/debug/unit_tests) application. Test-specific code is placed into an arbitrarily named subdirectory and is then called from the [test_index.c](applications/debug/unit_tests/test_index.c) source file. #### Test assets -Some unit tests require external data in order to function. These files (commonly called assets) reside in the [assets/unit_tests](/assets/unit_tests) directory in their respective subdirectories. Asset files can be of any type (plain text, FlipperFormat(FFF), binary etc). +Some unit tests require external data in order to function. These files (commonly called assets) reside in the [assets/unit_tests](/assets/unit_tests) directory in their respective subdirectories. Asset files can be of any type (plain text, FlipperFormat(FFF), binary, etc). ### Application-specific #### Infrared Each infrared protocol has a corresponding set of unit tests, so it makes sense to implement one when adding support for a new protocol. @@ -37,8 +37,8 @@ In order to add unit tests for your protocol, follow these steps: ##### Test data format Each unit test has 3 sections: 1. `decoder` - takes in raw signal and outputs decoded messages. -2. `encoder` - takes in decoded messages and outputs raw signal. -3. `encoder_decoder` - takes in decoded messages, turns them into raw signal and then decodes again. +2. `encoder` - takes in decoded messages and outputs a raw signal. +3. `encoder_decoder` - takes in decoded messages, turns them into a raw signal, and then decodes again. Infrared test asset files have an `.irtest` extension and are regular `.ir` files with a few additions. Decoder input data has signal names `decoder_input_N`, where N is a test sequence number. Expected data goes under the name `decoder_expected_N`. When testing the encoder these two are switched. @@ -46,4 +46,4 @@ Decoder input data has signal names `decoder_input_N`, where N is a test sequenc Decoded data is represented in arrays (since a single raw signal may decode to several messages). If there is only one signal, then it has to be an array of size 1. Use the existing files as syntax examples. ##### Getting raw signals -Recording raw IR signals is possible using Flipper Zero. Launch the CLI session, run `ir rx raw`, then point the remote towards Flipper's receiver and send the signals. The raw signal data will be printed to the console in a convenient format. +Recording raw IR signals are possible using the Flipper Zero. Launch the CLI session, run `ir rx raw`, then point the remote towards Flipper's receiver and send the signals. The raw signal data will be printed to the console in a convenient format. From 669822cdd2d676150a78c7d3336d2af3bc7da529 Mon Sep 17 00:00:00 2001 From: MangoTornado <31556509+MangoTornado@users.noreply.github.com> Date: Wed, 23 Nov 2022 08:15:08 -0500 Subject: [PATCH 233/824] Fix typos in various outputs (#2032) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix typos in various outputs * Update u2f_view.c Co-authored-by: あく --- applications/main/u2f/views/u2f_view.c | 4 ++-- applications/services/cli/cli_command_gpio.c | 2 +- assets/dolphin/ReadMe.md | 2 +- scripts/toolchain/fbtenv.sh | 4 ++-- site_scons/commandline.scons | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/applications/main/u2f/views/u2f_view.c b/applications/main/u2f/views/u2f_view.c index 181c495d00a..bf220ca2c3a 100644 --- a/applications/main/u2f/views/u2f_view.c +++ b/applications/main/u2f/views/u2f_view.c @@ -37,10 +37,10 @@ static void u2f_view_draw_callback(Canvas* canvas, void* _model) { } else if(model->display_msg == U2fMsgSuccess) { canvas_draw_icon(canvas, 22, 15, &I_Connected_62x31); canvas_draw_str_aligned( - canvas, 128 / 2, 3, AlignCenter, AlignTop, "Authentication successfull!"); + canvas, 128 / 2, 3, AlignCenter, AlignTop, "Authentication successful!"); } else if(model->display_msg == U2fMsgError) { canvas_draw_icon(canvas, 22, 15, &I_Error_62x31); - canvas_draw_str_aligned(canvas, 128 / 2, 3, AlignCenter, AlignTop, "Ceritficate error"); + canvas_draw_str_aligned(canvas, 128 / 2, 3, AlignCenter, AlignTop, "Certificate error"); } } diff --git a/applications/services/cli/cli_command_gpio.c b/applications/services/cli/cli_command_gpio.c index 3e7301cdc49..0b29f4853e4 100644 --- a/applications/services/cli/cli_command_gpio.c +++ b/applications/services/cli/cli_command_gpio.c @@ -121,7 +121,7 @@ void cli_command_gpio_mode(Cli* cli, FuriString* args, void* context) { if(cli_command_gpio_pins[num].debug) { printf( - "Changeing this pin mode may damage hardware. Are you sure you want to continue? (y/n)?\r\n"); + "Changing this pin mode may damage hardware. Are you sure you want to continue? (y/n)?\r\n"); char c = cli_getc(cli); if(c != 'y' && c != 'Y') { printf("Cancelled.\r\n"); diff --git a/assets/dolphin/ReadMe.md b/assets/dolphin/ReadMe.md index 6b59d231f0b..643086c26f7 100644 --- a/assets/dolphin/ReadMe.md +++ b/assets/dolphin/ReadMe.md @@ -52,7 +52,7 @@ Version: 1 - `Active cooldown` - amount of seconds (after passive mode) to pass before entering next active mode. - `Bubble slots` - amount of bubble sequences. -- Any bubble sequence plays whole sequence during active mode. There can be many bubble sequences and bubbles inside it. Bubbles in 1 bubble sequence have to reside in 1 slot. Bubbles order in 1 bubble sequence is determined by occurance in file. As soon as frame index goes out of EndFrame index of bubble - next animation bubble is choosen. There can also be free of bubbles frames between 2 bubbles. +- Any bubble sequence plays whole sequence during active mode. There can be many bubble sequences and bubbles inside it. Bubbles in 1 bubble sequence have to reside in 1 slot. Bubbles order in 1 bubble sequence is determined by occurrence in file. As soon as frame index goes out of EndFrame index of bubble - next animation bubble is chosen. There can also be free of bubbles frames between 2 bubbles. - `Slot` - number to unite bubbles for same sequence. - `X`, `Y` - are coordinates of left top corner of bubble. diff --git a/scripts/toolchain/fbtenv.sh b/scripts/toolchain/fbtenv.sh index 852e00394a1..f3e4cb1fa3f 100755 --- a/scripts/toolchain/fbtenv.sh +++ b/scripts/toolchain/fbtenv.sh @@ -13,7 +13,7 @@ fbtenv_show_usage() echo "Running this script manually is wrong, please source it"; echo "Example:"; printf "\tsource scripts/toolchain/fbtenv.sh\n"; - echo "To restore your enviroment source fbtenv.sh with '--restore'." + echo "To restore your environment source fbtenv.sh with '--restore'." echo "Example:"; printf "\tsource scripts/toolchain/fbtenv.sh --restore\n"; } @@ -227,7 +227,7 @@ fbtenv_curl_wget_check() echo; echo "$TOOLCHAIN_URL"; echo; - echo "And place in $FBT_TOOLCHAIN_PATH/toolchain/ dir mannualy"; + echo "And place in $FBT_TOOLCHAIN_PATH/toolchain/ dir manually"; return 1; fi echo "yes" diff --git a/site_scons/commandline.scons b/site_scons/commandline.scons index 6087c1c7add..fc2534ed523 100644 --- a/site_scons/commandline.scons +++ b/site_scons/commandline.scons @@ -15,7 +15,7 @@ AddOption( nargs=1, action="store", default="fbt_options.py", - help="Enviroment option file", + help="Environment option file", ) AddOption( @@ -23,7 +23,7 @@ AddOption( action="store", dest="extra_int_apps", default="", - help="List of applications to add to firmare's built-ins. Also see FIRMWARE_APP_SET and FIRMWARE_APPS", + help="List of applications to add to firmware's built-ins. Also see FIRMWARE_APP_SET and FIRMWARE_APPS", ) AddOption( From 1c8451fad49e62a482aa81342631954856742b47 Mon Sep 17 00:00:00 2001 From: Kyhwana Pardus Date: Thu, 24 Nov 2022 02:23:11 +1300 Subject: [PATCH 234/824] Fix typos in subghz_cli.c (#2030) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix up the "receive" typos Co-authored-by: あく --- applications/main/subghz/subghz_cli.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/applications/main/subghz/subghz_cli.c b/applications/main/subghz/subghz_cli.c index ad5d8afb0b0..b6471b33c92 100644 --- a/applications/main/subghz/subghz_cli.c +++ b/applications/main/subghz/subghz_cli.c @@ -300,7 +300,7 @@ void subghz_cli_command_rx(Cli* cli, FuriString* args, void* context) { furi_hal_power_suppress_charge_exit(); - printf("\r\nPackets recieved %u\r\n", instance->packet_count); + printf("\r\nPackets received %u\r\n", instance->packet_count); // Cleanup subghz_receiver_free(receiver); @@ -408,7 +408,7 @@ void subghz_cli_command_decode_raw(Cli* cli, FuriString* args, void* context) { } } - printf("\r\nPackets recieved \033[0;32m%u\033[0m\r\n", instance->packet_count); + printf("\r\nPackets received \033[0;32m%u\033[0m\r\n", instance->packet_count); // Cleanup subghz_receiver_free(receiver); @@ -438,7 +438,7 @@ static void subghz_cli_command_print_usage() { printf("\r\n"); printf(" debug cmd:\r\n"); printf("\ttx_carrier \t - Transmit carrier\r\n"); - printf("\trx_carrier \t - Receiv carrier\r\n"); + printf("\trx_carrier \t - Receive carrier\r\n"); printf( "\tencrypt_keeloq \t - Encrypt keeloq manufacture keys\r\n"); printf( From 9bb0dbaa3e7cc9f7ded52d6419ed156e318b1c66 Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Wed, 23 Nov 2022 17:31:39 +0400 Subject: [PATCH 235/824] SubGhz: frequency analyzer. 2dbi desensitization, rssi averaging to reduce jitter, new rssi indicators in log_mode, GUI fix (#2020) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- .../subghz_frequency_analyzer_worker.c | 24 +++----- .../subghz_frequency_analyzer_worker.h | 2 + .../subghz/views/subghz_frequency_analyzer.c | 56 ++++++++++--------- 3 files changed, 41 insertions(+), 41 deletions(-) diff --git a/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.c b/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.c index 7826e2ae787..7463cfaf927 100644 --- a/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.c +++ b/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.c @@ -5,8 +5,6 @@ #define TAG "SubghzFrequencyAnalyzerWorker" -#define SUBGHZ_FREQUENCY_ANALYZER_THRESHOLD -95.0f - static const uint8_t subghz_preset_ook_58khz[][2] = { {CC1101_MDMCFG4, 0b11110111}, // Rx BW filter is 58.035714kHz /* End */ @@ -71,7 +69,7 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) { .frequency_coarse = 0, .rssi_coarse = 0, .frequency_fine = 0, .rssi_fine = 0}; float rssi = 0; uint32_t frequency = 0; - float rssi_temp = 0; + float rssi_temp = -127.0f; uint32_t frequency_temp = 0; CC1101Status status; @@ -196,7 +194,7 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) { TAG, "=:%lu:%f", frequency_rssi.frequency_fine, (double)frequency_rssi.rssi_fine); instance->sample_hold_counter = 20; - rssi_temp = frequency_rssi.rssi_fine; + rssi_temp = (rssi_temp + frequency_rssi.rssi_fine) / 2; frequency_temp = frequency_rssi.frequency_fine; if(instance->filVal) { @@ -207,10 +205,7 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) { // Deliver callback if(instance->pair_callback) { instance->pair_callback( - instance->context, - frequency_rssi.frequency_fine, - frequency_rssi.rssi_fine, - true); + instance->context, frequency_rssi.frequency_fine, rssi_temp, true); } } else if( // Deliver results coarse (frequency_rssi.rssi_coarse > SUBGHZ_FREQUENCY_ANALYZER_THRESHOLD) && @@ -222,7 +217,7 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) { (double)frequency_rssi.rssi_coarse); instance->sample_hold_counter = 20; - rssi_temp = frequency_rssi.rssi_coarse; + rssi_temp = (rssi_temp + frequency_rssi.rssi_coarse) / 2; frequency_temp = frequency_rssi.frequency_coarse; if(instance->filVal) { frequency_rssi.frequency_coarse = @@ -232,15 +227,12 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) { // Deliver callback if(instance->pair_callback) { instance->pair_callback( - instance->context, - frequency_rssi.frequency_coarse, - frequency_rssi.rssi_coarse, - true); + instance->context, frequency_rssi.frequency_coarse, rssi_temp, true); } } else { if(instance->sample_hold_counter > 0) { instance->sample_hold_counter--; - if(instance->sample_hold_counter == 18) { + if(instance->sample_hold_counter == 15) { if(instance->pair_callback) { instance->pair_callback( instance->context, frequency_temp, rssi_temp, false); @@ -248,8 +240,8 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) { } } else { instance->filVal = 0; - if(instance->pair_callback) - instance->pair_callback(instance->context, 0, 0, false); + rssi_temp = -127.0f; + instance->pair_callback(instance->context, 0, 0, false); } } } diff --git a/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.h b/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.h index 8bd1addc41e..ed5bd9644fe 100644 --- a/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.h +++ b/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.h @@ -3,6 +3,8 @@ #include #include "../subghz_i.h" +#define SUBGHZ_FREQUENCY_ANALYZER_THRESHOLD -93.0f + typedef struct SubGhzFrequencyAnalyzerWorker SubGhzFrequencyAnalyzerWorker; typedef void (*SubGhzFrequencyAnalyzerWorkerPairCallback)( diff --git a/applications/main/subghz/views/subghz_frequency_analyzer.c b/applications/main/subghz/views/subghz_frequency_analyzer.c index e980bd97056..12989840048 100644 --- a/applications/main/subghz/views/subghz_frequency_analyzer.c +++ b/applications/main/subghz/views/subghz_frequency_analyzer.c @@ -13,8 +13,6 @@ #include #define LOG_FREQUENCY_MAX_ITEMS 60 // uint8_t (limited by 'seq' of SubGhzFrequencyAnalyzerLogItem) -#define RSSI_OFFSET 74 -#define RSSI_MAX 53 // 127 - RSSI_OFFSET #define SNPRINTF_FREQUENCY(buff, freq) \ snprintf(buff, sizeof(buff), "%03ld.%03ld", freq / 1000000 % 1000, freq / 1000 % 1000); @@ -49,7 +47,7 @@ typedef struct { } SubGhzFrequencyAnalyzerModel; static inline uint8_t rssi_sanitize(float rssi) { - return (rssi * -1.0f) - RSSI_OFFSET; + return (rssi ? (uint8_t)(rssi - SUBGHZ_FREQUENCY_ANALYZER_THRESHOLD) : 0); } void subghz_frequency_analyzer_set_callback( @@ -65,12 +63,25 @@ void subghz_frequency_analyzer_set_callback( void subghz_frequency_analyzer_draw_rssi(Canvas* canvas, uint8_t rssi, uint8_t x, uint8_t y) { uint8_t column_number = 0; if(rssi) { - rssi = rssi / 3; + rssi = rssi / 3 + 2; + if(rssi > 20) rssi = 20; for(uint8_t i = 1; i < rssi; i++) { - if(i > 20) break; if(i % 4) { column_number++; - canvas_draw_box(canvas, x + 2 * i, y - column_number, 2, 4 + column_number); + canvas_draw_box(canvas, x + 2 * i, y - column_number, 2, column_number); + } + } + } +} + +void subghz_frequency_analyzer_draw_log_rssi(Canvas* canvas, uint8_t rssi, uint8_t x, uint8_t y) { + uint8_t column_height = 6; + if(rssi) { + //rssi = rssi + if(rssi > 54) rssi = 54; + for(uint8_t i = 1; i < rssi; i++) { + if(i % 5) { + canvas_draw_box(canvas, x + i, y - column_height, 1, column_height); } } } @@ -86,9 +97,9 @@ static void subghz_frequency_analyzer_log_frequency_draw( const size_t items_count = SubGhzFrequencyAnalyzerLogItemArray_size(model->log_frequency); if(items_count == 0) { - canvas_draw_rframe(canvas, offset_x + 27u, offset_y - 3u, 73u, 16u, 5u); + canvas_draw_rframe(canvas, offset_x + 27, offset_y - 3, 73, 16, 5); canvas_draw_str_aligned( - canvas, offset_x + 64u, offset_y + 8u, AlignCenter, AlignBottom, "No records"); + canvas, offset_x + 64, offset_y + 8, AlignCenter, AlignBottom, "No records"); return; } else if(items_count > 3) { elements_scrollbar_pos( @@ -117,7 +128,7 @@ static void subghz_frequency_analyzer_log_frequency_draw( canvas_draw_str(canvas, offset_x + 48, offset_y + i * 10, buffer); // Max RSSI - subghz_frequency_analyzer_draw_rssi( + subghz_frequency_analyzer_draw_log_rssi( canvas, (*log_frequency_item)->rssi_max, offset_x + 69, (offset_y + i * 10)); } @@ -167,25 +178,20 @@ void subghz_frequency_analyzer_draw(Canvas* canvas, SubGhzFrequencyAnalyzerModel } else { canvas_draw_str(canvas, 20, 8, "Frequency Analyzer"); canvas_draw_str(canvas, 0, 64, "RSSI"); - subghz_frequency_analyzer_draw_rssi(canvas, model->rssi, 20u, 64u); + subghz_frequency_analyzer_draw_rssi(canvas, model->rssi, 20, 64); subghz_frequency_analyzer_history_frequency_draw(canvas, model); } // Frequency canvas_set_font(canvas, FontBigNumbers); - snprintf( - buffer, - sizeof(buffer), - "%03ld.%03ld", - model->frequency / 1000000 % 1000, - model->frequency / 1000 % 1000); + SNPRINTF_FREQUENCY(buffer, model->frequency); if(model->signal) { - canvas_draw_box(canvas, 4, 12, 121, 22); + canvas_draw_box(canvas, 4, 11, 121, 22); canvas_set_color(canvas, ColorWhite); } - canvas_draw_str(canvas, 8, 30, buffer); - canvas_draw_icon(canvas, 96, 19, &I_MHz_25x11); + canvas_draw_str(canvas, 8, 29, buffer); + canvas_draw_icon(canvas, 96, 18, &I_MHz_25x11); } static void subghz_frequency_analyzer_log_frequency_sort(SubGhzFrequencyAnalyzerModel* model) { @@ -292,7 +298,7 @@ static bool subghz_frequency_analyzer_log_frequency_insert(SubGhzFrequencyAnalyz return false; } (*item)->frequency = model->frequency; - (*item)->count = 1u; + (*item)->count = 1; (*item)->rssi_max = model->rssi; (*item)->seq = items_count; return true; @@ -394,9 +400,9 @@ void subghz_frequency_analyzer_enter(void* context) { model->frequency = 0; model->fragment_bottom_type = SubGhzFrequencyAnalyzerFragmentBottomTypeMain; model->log_frequency_order_by = SubGhzFrequencyAnalyzerLogOrderBySeqDesc; - model->log_frequency_scroll_offset = 0u; + model->log_frequency_scroll_offset = 0; model->history_frequency[0] = model->history_frequency[1] = - model->history_frequency[2] = 0u; + model->history_frequency[2] = 0; SubGhzFrequencyAnalyzerLogItemArray_init(model->log_frequency); }, true); @@ -416,13 +422,13 @@ void subghz_frequency_analyzer_exit(void* context) { instance->view, SubGhzFrequencyAnalyzerModel * model, { - model->rssi = 0u; + model->rssi = 0; model->frequency = 0; model->fragment_bottom_type = SubGhzFrequencyAnalyzerFragmentBottomTypeMain; model->log_frequency_order_by = SubGhzFrequencyAnalyzerLogOrderBySeqDesc; - model->log_frequency_scroll_offset = 0u; + model->log_frequency_scroll_offset = 0; model->history_frequency[0] = model->history_frequency[1] = - model->history_frequency[2] = 0u; + model->history_frequency[2] = 0; SubGhzFrequencyAnalyzerLogItemArray_clear(model->log_frequency); }, true); From 51d478489a0d948b8fc148d394c2724166564e2c Mon Sep 17 00:00:00 2001 From: Anna Prosvetova Date: Wed, 23 Nov 2022 14:43:48 +0100 Subject: [PATCH 236/824] Infrared: Add Olimpia Splendid AC (#2038) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- assets/resources/infrared/assets/ac.ir | 37 ++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/assets/resources/infrared/assets/ac.ir b/assets/resources/infrared/assets/ac.ir index 7ef953059a3..af776706d8f 100644 --- a/assets/resources/infrared/assets/ac.ir +++ b/assets/resources/infrared/assets/ac.ir @@ -148,4 +148,41 @@ type: raw frequency: 38000 duty_cycle: 0.330000 data: 3013 1709 463 1085 462 1084 463 387 461 355 469 355 444 1084 463 387 461 378 436 1084 462 1084 487 355 469 1059 463 387 460 355 444 1083 463 1098 464 386 437 1083 463 1083 489 361 463 360 463 1082 464 361 462 376 462 1085 461 363 460 364 459 364 459 365 458 365 459 364 459 380 459 365 458 365 458 365 458 365 458 365 458 365 458 365 459 380 458 365 459 365 458 365 459 365 458 365 458 1089 458 365 458 380 459 1088 459 365 458 365 458 365 458 365 459 365 458 365 458 381 458 365 458 365 458 365 458 1089 458 365 458 365 458 365 458 381 458 365 458 365 458 365 458 365 458 365 458 365 458 365 458 381 457 366 457 366 457 366 457 366 457 366 458 365 458 366 457 381 458 366 458 366 457 366 457 366 457 366 457 366 457 366 457 381 458 366 457 366 457 366 457 366 457 366 457 366 457 366 457 382 457 366 457 366 457 366 457 366 457 366 457 367 457 366 457 382 457 367 456 1090 457 1090 456 1090 457 1090 457 1090 456 367 457 372 457 +# +# Model: Olimpia Splendid OS-SEAMH09EI +name: Dh +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4402 4336 561 1641 538 554 535 1616 563 1640 539 554 535 557 532 1645 534 560 539 551 538 1639 540 553 536 556 533 1644 535 1642 537 556 533 1620 558 558 531 561 538 554 535 1641 538 1639 540 1638 530 1620 559 1647 532 1643 536 1641 538 1613 566 553 536 557 532 560 539 553 536 558 531 560 529 1647 532 535 564 1613 555 537 563 1614 565 554 535 559 530 1645 534 559 530 1647 532 560 539 1612 557 562 537 1640 539 1611 557 5189 4398 4344 564 1638 530 563 537 1640 539 1639 529 563 537 530 559 1644 535 560 529 535 564 1639 529 537 563 556 533 1644 535 1643 536 557 532 1647 532 559 530 536 564 555 534 1643 536 1616 563 1640 539 1613 555 1624 565 1610 558 1619 560 1617 562 557 532 560 539 554 535 557 532 562 537 553 536 1641 538 555 534 1643 536 530 559 1644 535 558 531 564 536 1639 539 553 536 1641 537 555 534 1617 562 557 532 1645 534 1645 534 +# +name: Cool_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4404 4345 563 1613 566 553 536 1641 538 1614 565 528 561 557 532 1645 534 560 539 551 538 1639 529 563 536 556 533 1618 561 1642 537 556 533 1645 534 557 532 561 538 1638 530 1646 533 1645 554 1623 556 1621 558 1596 562 1613 586 1591 588 531 537 555 534 559 530 562 537 555 534 561 538 552 537 555 534 559 530 562 537 555 534 559 530 562 537 557 532 1643 536 1615 564 1639 560 1591 588 1616 563 1588 580 1623 556 1621 537 5183 4404 4339 579 1597 581 538 530 1646 533 1619 580 539 529 563 536 1615 564 557 532 532 557 1646 533 560 539 553 536 1641 538 1639 539 554 535 1643 536 555 534 559 530 1647 532 1646 533 1644 535 1617 582 1620 559 1621 537 1612 587 1590 589 530 538 554 535 558 531 561 538 554 535 560 539 551 538 554 535 558 531 561 538 554 535 557 532 561 538 556 533 1642 537 1640 538 1613 565 1638 561 1590 589 1588 580 1623 556 1598 560 +# +name: Cool_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4400 4357 561 1616 563 530 559 1619 559 1644 535 557 532 535 564 1613 555 565 534 556 533 1645 534 533 556 536 563 1614 565 1639 529 537 563 1643 536 529 560 558 531 1647 532 1645 533 1618 561 1643 536 1642 537 1642 537 1613 565 1638 530 537 563 556 533 559 530 563 536 556 533 562 538 527 562 1615 563 555 534 1643 536 531 558 561 538 554 535 560 539 1610 558 560 539 1612 556 563 537 1640 538 1639 539 1612 556 1620 558 5189 4398 4345 562 1614 564 529 560 1643 536 1616 563 531 558 534 565 1639 529 565 534 556 533 1619 559 559 530 563 537 1641 538 1640 538 554 535 1645 534 531 558 560 539 1639 529 1622 556 1621 558 1646 532 1619 559 1646 533 1617 562 1642 537 557 532 560 539 554 535 557 532 561 538 556 533 558 531 1620 559 560 539 1638 530 563 536 556 533 560 539 555 534 1642 537 556 533 1619 559 559 530 1622 557 1621 558 1620 558 1647 532 +# +name: Heat_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4402 4346 561 1615 564 555 534 1644 535 1643 536 531 558 561 538 1639 539 555 534 531 558 1645 533 560 529 563 536 1641 537 1640 538 528 561 1645 533 531 558 561 538 1639 539 1638 530 1622 557 1647 532 1646 564 1590 557 1619 559 1618 561 558 531 562 537 555 534 559 530 563 536 558 531 1645 533 559 530 1648 530 1647 531 1646 532 1646 532 560 539 556 533 558 531 1621 557 561 538 555 534 558 531 562 537 1641 537 1639 539 5183 4405 4339 558 1645 533 559 530 1648 530 1647 532 561 538 555 534 1644 535 560 539 551 538 1614 564 555 534 559 530 1647 532 1620 558 561 538 1615 563 553 536 531 558 1620 558 1644 534 1643 536 1642 537 1641 537 1642 536 1613 565 1638 530 563 536 557 532 534 565 554 535 557 532 563 536 1613 565 554 535 1642 537 1641 537 1640 538 1640 538 554 535 560 539 551 538 1614 564 528 561 558 531 562 537 555 534 1644 534 1645 533 +# +name: Heat_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4404 4354 564 1614 564 528 561 1617 562 1642 537 530 559 534 565 1613 555 539 560 556 533 1619 559 534 555 563 536 1642 537 1641 537 555 534 1646 533 532 557 562 537 1640 538 1613 565 1613 555 1648 531 1647 532 1622 557 1619 559 1618 560 559 530 563 536 556 533 560 539 554 535 559 530 561 538 1639 539 554 535 1616 563 1615 564 1615 563 555 534 561 538 1612 556 563 536 1641 538 529 560 533 556 563 537 1641 538 1614 564 5183 4404 4342 555 1622 557 562 537 1641 537 1640 538 529 560 533 556 1648 531 538 562 555 534 1618 560 559 530 562 537 1615 563 1640 538 529 560 1646 532 532 557 562 537 1640 538 1639 539 1639 539 1638 530 1648 530 1649 540 1611 557 1646 532 561 538 554 535 558 531 562 537 555 534 561 538 552 537 1641 537 555 534 1644 534 1643 536 1617 562 557 532 563 536 1639 539 553 536 1642 537 556 533 560 539 554 535 1642 537 1643 535 +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4403 4333 563 1615 563 528 561 1618 560 1618 560 531 558 561 538 1614 564 556 533 558 541 1611 557 562 537 555 534 1619 559 1618 560 558 541 1613 565 551 538 1614 564 1614 564 1614 564 1613 565 528 561 1617 561 1619 559 1617 561 557 532 535 564 528 561 533 566 1611 557 562 537 558 531 1619 559 1619 559 1619 559 559 540 553 536 557 532 561 538 557 532 559 540 553 536 557 532 1620 558 1620 558 1620 558 1620 558 1618 560 5188 4398 4346 561 1618 560 558 531 1621 557 1621 557 561 538 555 534 1619 559 561 538 553 536 1616 562 556 533 560 539 1614 564 1613 565 554 535 1619 559 557 532 1621 557 1620 558 1620 558 1620 558 560 539 1613 565 1615 563 1613 565 553 536 557 532 535 564 554 535 1618 560 558 531 564 535 1615 563 1615 563 1614 564 555 534 559 540 552 537 530 559 536 563 528 561 532 557 562 537 1615 563 1615 563 1614 564 1614 564 1616 562 From ffa6249b640c0676cd37b6d24e845e2e6adff802 Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Wed, 23 Nov 2022 21:12:53 +0300 Subject: [PATCH 237/824] [FL-2985] Fix U2F HID vulnerability #2042 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- applications/main/u2f/u2f_hid.c | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/applications/main/u2f/u2f_hid.c b/applications/main/u2f/u2f_hid.c index 916400e91e2..6e1a51f331b 100644 --- a/applications/main/u2f/u2f_hid.c +++ b/applications/main/u2f/u2f_hid.c @@ -58,13 +58,13 @@ struct U2fHid_packet { struct U2fHid { FuriThread* thread; FuriTimer* lock_timer; - struct U2fHid_packet packet; uint8_t seq_id_last; uint16_t req_buf_ptr; uint32_t req_len_left; uint32_t lock_cid; bool lock; U2fData* u2f_instance; + struct U2fHid_packet packet; }; static void u2f_hid_event_callback(HidU2fEvent ev, void* context) { @@ -215,10 +215,21 @@ static int32_t u2f_hid_worker(void* context) { } if(flags & WorkerEvtRequest) { uint32_t len_cur = furi_hal_hid_u2f_get_request(packet_buf); - if(len_cur > 0) { + do { + if(len_cur == 0) { + break; + } if((packet_buf[4] & U2F_HID_TYPE_MASK) == U2F_HID_TYPE_INIT) { + if(len_cur < 7) { + u2f_hid->req_len_left = 0; + break; // Wrong chunk len + } // Init packet u2f_hid->packet.len = (packet_buf[5] << 8) | (packet_buf[6]); + if(u2f_hid->packet.len > U2F_HID_MAX_PAYLOAD_LEN) { + u2f_hid->req_len_left = 0; + break; // Wrong packet len + } if(u2f_hid->packet.len > (len_cur - 7)) { u2f_hid->req_len_left = u2f_hid->packet.len - (len_cur - 7); len_cur = len_cur - 7; @@ -232,6 +243,10 @@ static int32_t u2f_hid_worker(void* context) { u2f_hid->req_buf_ptr = len_cur; if(len_cur > 0) memcpy(u2f_hid->packet.payload, &packet_buf[7], len_cur); } else { + if(len_cur < 5) { + u2f_hid->req_len_left = 0; + break; // Wrong chunk len + } // Continuation packet if(u2f_hid->req_len_left > 0) { uint32_t cid_temp = 0; @@ -260,7 +275,7 @@ static int32_t u2f_hid_worker(void* context) { u2f_hid_send_error(u2f_hid, U2F_HID_ERR_INVALID_CMD); } } - } + } while(0); } if(flags & WorkerEvtUnlock) { u2f_hid->lock = false; From 03140e434916e206ac3553e0d1e5dca71f0038cb Mon Sep 17 00:00:00 2001 From: Smooklu <37220586+Smooklu@users.noreply.github.com> Date: Mon, 28 Nov 2022 10:27:16 -0600 Subject: [PATCH 238/824] Bluetooth Remote to HID Remote (#2039) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * WIP BT + USB Hid * Refactoring Submenus/Views * Changed to bool instead of enum * Revamp finished * Removed usb_keyboard * Renaming device_types that can do USB+BT * Removed error view * Renaming folder structure and file names * Fixed views.h * Fixed hid.h * Fixed hid_mouse.c * Fixed a accidetnal renaming * Apps: add missing view remove call in hid app destructor * Hid app: hal abstraction, split into bluetooth and usb remotes. Fbt: customizable icon symbol name. * Hid app: update usb remote icon * Hid: single status change routine * HID App: final touches * HID App: rename BtHidTikTok to HidTikTok, format sources * HID App: fix comma in keyboard Co-authored-by: あく --- .../plugins/bt_hid_app/application.fam | 10 - applications/plugins/bt_hid_app/bt_hid.c | 216 ----------- applications/plugins/bt_hid_app/bt_hid.h | 41 -- .../bt_hid_app/views/bt_hid_keyboard.h | 13 - .../plugins/bt_hid_app/views/bt_hid_keynote.h | 13 - .../plugins/bt_hid_app/views/bt_hid_media.h | 13 - .../plugins/bt_hid_app/views/bt_hid_mouse.h | 13 - .../plugins/bt_hid_app/views/bt_hid_tiktok.h | 13 - applications/plugins/hid_app/application.fam | 24 ++ .../assets/Arr_dwn_7x9.png | Bin .../assets/Arr_up_7x9.png | Bin .../assets/Ble_connected_15x15.png | Bin .../assets/Ble_disconnected_15x15.png | Bin .../assets/ButtonDown_7x4.png | Bin .../assets/ButtonLeft_4x7.png | Bin .../assets/ButtonRight_4x7.png | Bin .../assets/ButtonUp_7x4.png | Bin .../assets/Button_18x18.png | Bin .../assets/Circles_47x47.png | Bin .../assets/Left_mouse_icon_9x9.png | Bin .../assets/Like_def_11x9.png | Bin .../assets/Like_pressed_17x17.png | Bin .../assets/Ok_btn_9x9.png | Bin .../assets/Ok_btn_pressed_13x13.png | Bin .../assets/Pin_arrow_down_7x9.png | Bin .../assets/Pin_arrow_left_9x7.png | Bin .../assets/Pin_arrow_right_9x7.png | Bin .../assets/Pin_arrow_up_7x9.png | Bin .../assets/Pin_back_arrow_10x8.png | Bin .../assets/Pressed_Button_13x13.png | Bin .../assets/Right_mouse_icon_9x9.png | Bin .../assets/Space_65x18.png | Bin .../assets/Voldwn_6x6.png | Bin .../assets/Volup_8x6.png | Bin applications/plugins/hid_app/hid.c | 365 ++++++++++++++++++ applications/plugins/hid_app/hid.h | 60 +++ .../hid_ble_10px.png} | Bin applications/plugins/hid_app/hid_usb_10px.png | Bin 0 -> 969 bytes applications/plugins/hid_app/views.h | 9 + .../views/hid_keyboard.c} | 123 +++--- .../plugins/hid_app/views/hid_keyboard.h | 14 + .../views/hid_keynote.c} | 108 +++--- .../plugins/hid_app/views/hid_keynote.h | 14 + .../views/hid_media.c} | 107 ++--- .../plugins/hid_app/views/hid_media.h | 13 + .../views/hid_mouse.c} | 100 ++--- .../plugins/hid_app/views/hid_mouse.h | 17 + .../views/hid_tiktok.c} | 124 +++--- .../plugins/hid_app/views/hid_tiktok.h | 14 + scripts/fbt/appmanifest.py | 1 + scripts/fbt_tools/fbt_extapps.py | 2 +- 51 files changed, 820 insertions(+), 607 deletions(-) delete mode 100644 applications/plugins/bt_hid_app/application.fam delete mode 100644 applications/plugins/bt_hid_app/bt_hid.c delete mode 100644 applications/plugins/bt_hid_app/bt_hid.h delete mode 100644 applications/plugins/bt_hid_app/views/bt_hid_keyboard.h delete mode 100644 applications/plugins/bt_hid_app/views/bt_hid_keynote.h delete mode 100644 applications/plugins/bt_hid_app/views/bt_hid_media.h delete mode 100644 applications/plugins/bt_hid_app/views/bt_hid_mouse.h delete mode 100644 applications/plugins/bt_hid_app/views/bt_hid_tiktok.h create mode 100644 applications/plugins/hid_app/application.fam rename applications/plugins/{bt_hid_app => hid_app}/assets/Arr_dwn_7x9.png (100%) rename applications/plugins/{bt_hid_app => hid_app}/assets/Arr_up_7x9.png (100%) rename applications/plugins/{bt_hid_app => hid_app}/assets/Ble_connected_15x15.png (100%) rename applications/plugins/{bt_hid_app => hid_app}/assets/Ble_disconnected_15x15.png (100%) rename applications/plugins/{bt_hid_app => hid_app}/assets/ButtonDown_7x4.png (100%) rename applications/plugins/{bt_hid_app => hid_app}/assets/ButtonLeft_4x7.png (100%) rename applications/plugins/{bt_hid_app => hid_app}/assets/ButtonRight_4x7.png (100%) rename applications/plugins/{bt_hid_app => hid_app}/assets/ButtonUp_7x4.png (100%) rename applications/plugins/{bt_hid_app => hid_app}/assets/Button_18x18.png (100%) rename applications/plugins/{bt_hid_app => hid_app}/assets/Circles_47x47.png (100%) rename applications/plugins/{bt_hid_app => hid_app}/assets/Left_mouse_icon_9x9.png (100%) rename applications/plugins/{bt_hid_app => hid_app}/assets/Like_def_11x9.png (100%) rename applications/plugins/{bt_hid_app => hid_app}/assets/Like_pressed_17x17.png (100%) rename applications/plugins/{bt_hid_app => hid_app}/assets/Ok_btn_9x9.png (100%) rename applications/plugins/{bt_hid_app => hid_app}/assets/Ok_btn_pressed_13x13.png (100%) rename applications/plugins/{bt_hid_app => hid_app}/assets/Pin_arrow_down_7x9.png (100%) rename applications/plugins/{bt_hid_app => hid_app}/assets/Pin_arrow_left_9x7.png (100%) rename applications/plugins/{bt_hid_app => hid_app}/assets/Pin_arrow_right_9x7.png (100%) rename applications/plugins/{bt_hid_app => hid_app}/assets/Pin_arrow_up_7x9.png (100%) rename applications/plugins/{bt_hid_app => hid_app}/assets/Pin_back_arrow_10x8.png (100%) rename applications/plugins/{bt_hid_app => hid_app}/assets/Pressed_Button_13x13.png (100%) rename applications/plugins/{bt_hid_app => hid_app}/assets/Right_mouse_icon_9x9.png (100%) rename applications/plugins/{bt_hid_app => hid_app}/assets/Space_65x18.png (100%) rename applications/plugins/{bt_hid_app => hid_app}/assets/Voldwn_6x6.png (100%) rename applications/plugins/{bt_hid_app => hid_app}/assets/Volup_8x6.png (100%) create mode 100644 applications/plugins/hid_app/hid.c create mode 100644 applications/plugins/hid_app/hid.h rename applications/plugins/{bt_hid_app/bt_remote_10px.png => hid_app/hid_ble_10px.png} (100%) create mode 100644 applications/plugins/hid_app/hid_usb_10px.png create mode 100644 applications/plugins/hid_app/views.h rename applications/plugins/{bt_hid_app/views/bt_hid_keyboard.c => hid_app/views/hid_keyboard.c} (80%) create mode 100644 applications/plugins/hid_app/views/hid_keyboard.h rename applications/plugins/{bt_hid_app/views/bt_hid_keynote.c => hid_app/views/hid_keynote.c} (60%) create mode 100644 applications/plugins/hid_app/views/hid_keynote.h rename applications/plugins/{bt_hid_app/views/bt_hid_media.c => hid_app/views/hid_media.c} (59%) create mode 100644 applications/plugins/hid_app/views/hid_media.h rename applications/plugins/{bt_hid_app/views/bt_hid_mouse.c => hid_app/views/hid_mouse.c} (68%) create mode 100644 applications/plugins/hid_app/views/hid_mouse.h rename applications/plugins/{bt_hid_app/views/bt_hid_tiktok.c => hid_app/views/hid_tiktok.c} (59%) create mode 100644 applications/plugins/hid_app/views/hid_tiktok.h diff --git a/applications/plugins/bt_hid_app/application.fam b/applications/plugins/bt_hid_app/application.fam deleted file mode 100644 index 2712fded721..00000000000 --- a/applications/plugins/bt_hid_app/application.fam +++ /dev/null @@ -1,10 +0,0 @@ -App( - appid="bt_hid", - name="Bluetooth Remote", - apptype=FlipperAppType.EXTERNAL, - entry_point="bt_hid_app", - stack_size=1 * 1024, - fap_category="Tools", - fap_icon="bt_remote_10px.png", - fap_icon_assets="assets", -) diff --git a/applications/plugins/bt_hid_app/bt_hid.c b/applications/plugins/bt_hid_app/bt_hid.c deleted file mode 100644 index 4a77a249096..00000000000 --- a/applications/plugins/bt_hid_app/bt_hid.c +++ /dev/null @@ -1,216 +0,0 @@ -#include "bt_hid.h" -#include -#include -#include - -#define TAG "BtHidApp" - -enum BtDebugSubmenuIndex { - BtHidSubmenuIndexKeynote, - BtHidSubmenuIndexKeyboard, - BtHidSubmenuIndexMedia, - BtHidSubmenuIndexTikTok, - BtHidSubmenuIndexMouse, -}; - -void bt_hid_submenu_callback(void* context, uint32_t index) { - furi_assert(context); - BtHid* app = context; - if(index == BtHidSubmenuIndexKeynote) { - app->view_id = BtHidViewKeynote; - view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewKeynote); - } else if(index == BtHidSubmenuIndexKeyboard) { - app->view_id = BtHidViewKeyboard; - view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewKeyboard); - } else if(index == BtHidSubmenuIndexMedia) { - app->view_id = BtHidViewMedia; - view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewMedia); - } else if(index == BtHidSubmenuIndexMouse) { - app->view_id = BtHidViewMouse; - view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewMouse); - } else if(index == BtHidSubmenuIndexTikTok) { - app->view_id = BtHidViewTikTok; - view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewTikTok); - } -} - -void bt_hid_dialog_callback(DialogExResult result, void* context) { - furi_assert(context); - BtHid* app = context; - if(result == DialogExResultLeft) { - view_dispatcher_stop(app->view_dispatcher); - } else if(result == DialogExResultRight) { - view_dispatcher_switch_to_view(app->view_dispatcher, app->view_id); // Show last view - } else if(result == DialogExResultCenter) { - view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewSubmenu); - } -} - -uint32_t bt_hid_exit_confirm_view(void* context) { - UNUSED(context); - return BtHidViewExitConfirm; -} - -uint32_t bt_hid_exit(void* context) { - UNUSED(context); - return VIEW_NONE; -} - -void bt_hid_connection_status_changed_callback(BtStatus status, void* context) { - furi_assert(context); - BtHid* bt_hid = context; - bool connected = (status == BtStatusConnected); - if(connected) { - notification_internal_message(bt_hid->notifications, &sequence_set_blue_255); - } else { - notification_internal_message(bt_hid->notifications, &sequence_reset_blue); - } - bt_hid_keynote_set_connected_status(bt_hid->bt_hid_keynote, connected); - bt_hid_keyboard_set_connected_status(bt_hid->bt_hid_keyboard, connected); - bt_hid_media_set_connected_status(bt_hid->bt_hid_media, connected); - bt_hid_mouse_set_connected_status(bt_hid->bt_hid_mouse, connected); - bt_hid_tiktok_set_connected_status(bt_hid->bt_hid_tiktok, connected); -} - -BtHid* bt_hid_app_alloc() { - BtHid* app = malloc(sizeof(BtHid)); - - // Gui - app->gui = furi_record_open(RECORD_GUI); - - // Bt - app->bt = furi_record_open(RECORD_BT); - - // Notifications - app->notifications = furi_record_open(RECORD_NOTIFICATION); - - // View dispatcher - app->view_dispatcher = view_dispatcher_alloc(); - view_dispatcher_enable_queue(app->view_dispatcher); - view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); - - // Submenu view - app->submenu = submenu_alloc(); - submenu_add_item( - app->submenu, "Keynote", BtHidSubmenuIndexKeynote, bt_hid_submenu_callback, app); - submenu_add_item( - app->submenu, "Keyboard", BtHidSubmenuIndexKeyboard, bt_hid_submenu_callback, app); - submenu_add_item(app->submenu, "Media", BtHidSubmenuIndexMedia, bt_hid_submenu_callback, app); - submenu_add_item( - app->submenu, "TikTok Controller", BtHidSubmenuIndexTikTok, bt_hid_submenu_callback, app); - submenu_add_item(app->submenu, "Mouse", BtHidSubmenuIndexMouse, bt_hid_submenu_callback, app); - view_set_previous_callback(submenu_get_view(app->submenu), bt_hid_exit); - view_dispatcher_add_view( - app->view_dispatcher, BtHidViewSubmenu, submenu_get_view(app->submenu)); - - // Dialog view - app->dialog = dialog_ex_alloc(); - dialog_ex_set_result_callback(app->dialog, bt_hid_dialog_callback); - dialog_ex_set_context(app->dialog, app); - dialog_ex_set_left_button_text(app->dialog, "Exit"); - dialog_ex_set_right_button_text(app->dialog, "Stay"); - dialog_ex_set_center_button_text(app->dialog, "Menu"); - dialog_ex_set_header(app->dialog, "Close Current App?", 16, 12, AlignLeft, AlignTop); - view_dispatcher_add_view( - app->view_dispatcher, BtHidViewExitConfirm, dialog_ex_get_view(app->dialog)); - - // Keynote view - app->bt_hid_keynote = bt_hid_keynote_alloc(); - view_set_previous_callback( - bt_hid_keynote_get_view(app->bt_hid_keynote), bt_hid_exit_confirm_view); - view_dispatcher_add_view( - app->view_dispatcher, BtHidViewKeynote, bt_hid_keynote_get_view(app->bt_hid_keynote)); - - // Keyboard view - app->bt_hid_keyboard = bt_hid_keyboard_alloc(); - view_set_previous_callback( - bt_hid_keyboard_get_view(app->bt_hid_keyboard), bt_hid_exit_confirm_view); - view_dispatcher_add_view( - app->view_dispatcher, BtHidViewKeyboard, bt_hid_keyboard_get_view(app->bt_hid_keyboard)); - - // Media view - app->bt_hid_media = bt_hid_media_alloc(); - view_set_previous_callback(bt_hid_media_get_view(app->bt_hid_media), bt_hid_exit_confirm_view); - view_dispatcher_add_view( - app->view_dispatcher, BtHidViewMedia, bt_hid_media_get_view(app->bt_hid_media)); - - // TikTok view - app->bt_hid_tiktok = bt_hid_tiktok_alloc(); - view_set_previous_callback( - bt_hid_tiktok_get_view(app->bt_hid_tiktok), bt_hid_exit_confirm_view); - view_dispatcher_add_view( - app->view_dispatcher, BtHidViewTikTok, bt_hid_tiktok_get_view(app->bt_hid_tiktok)); - - // Mouse view - app->bt_hid_mouse = bt_hid_mouse_alloc(); - view_set_previous_callback(bt_hid_mouse_get_view(app->bt_hid_mouse), bt_hid_exit_confirm_view); - view_dispatcher_add_view( - app->view_dispatcher, BtHidViewMouse, bt_hid_mouse_get_view(app->bt_hid_mouse)); - - // TODO switch to menu after Media is done - app->view_id = BtHidViewSubmenu; - view_dispatcher_switch_to_view(app->view_dispatcher, app->view_id); - - return app; -} - -void bt_hid_app_free(BtHid* app) { - furi_assert(app); - - // Reset notification - notification_internal_message(app->notifications, &sequence_reset_blue); - - // Free views - view_dispatcher_remove_view(app->view_dispatcher, BtHidViewSubmenu); - submenu_free(app->submenu); - view_dispatcher_remove_view(app->view_dispatcher, BtHidViewExitConfirm); - dialog_ex_free(app->dialog); - view_dispatcher_remove_view(app->view_dispatcher, BtHidViewKeynote); - bt_hid_keynote_free(app->bt_hid_keynote); - view_dispatcher_remove_view(app->view_dispatcher, BtHidViewKeyboard); - bt_hid_keyboard_free(app->bt_hid_keyboard); - view_dispatcher_remove_view(app->view_dispatcher, BtHidViewMedia); - bt_hid_media_free(app->bt_hid_media); - view_dispatcher_remove_view(app->view_dispatcher, BtHidViewMouse); - bt_hid_mouse_free(app->bt_hid_mouse); - view_dispatcher_remove_view(app->view_dispatcher, BtHidViewTikTok); - bt_hid_tiktok_free(app->bt_hid_tiktok); - view_dispatcher_free(app->view_dispatcher); - - // Close records - furi_record_close(RECORD_GUI); - app->gui = NULL; - furi_record_close(RECORD_NOTIFICATION); - app->notifications = NULL; - furi_record_close(RECORD_BT); - app->bt = NULL; - - // Free rest - free(app); -} - -int32_t bt_hid_app(void* p) { - UNUSED(p); - // Switch profile to Hid - BtHid* app = bt_hid_app_alloc(); - bt_set_status_changed_callback(app->bt, bt_hid_connection_status_changed_callback, app); - // Change profile - if(!bt_set_profile(app->bt, BtProfileHidKeyboard)) { - FURI_LOG_E(TAG, "Failed to switch profile"); - bt_hid_app_free(app); - return -1; - } - furi_hal_bt_start_advertising(); - - DOLPHIN_DEED(DolphinDeedPluginStart); - - view_dispatcher_run(app->view_dispatcher); - - bt_set_status_changed_callback(app->bt, NULL, NULL); - // Change back profile to Serial - bt_set_profile(app->bt, BtProfileSerial); - - bt_hid_app_free(app); - - return 0; -} diff --git a/applications/plugins/bt_hid_app/bt_hid.h b/applications/plugins/bt_hid_app/bt_hid.h deleted file mode 100644 index 89e8807fec5..00000000000 --- a/applications/plugins/bt_hid_app/bt_hid.h +++ /dev/null @@ -1,41 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include - -#include -#include -#include "views/bt_hid_keynote.h" -#include "views/bt_hid_keyboard.h" -#include "views/bt_hid_media.h" -#include "views/bt_hid_mouse.h" -#include "views/bt_hid_tiktok.h" - -typedef struct { - Bt* bt; - Gui* gui; - NotificationApp* notifications; - ViewDispatcher* view_dispatcher; - Submenu* submenu; - DialogEx* dialog; - BtHidKeynote* bt_hid_keynote; - BtHidKeyboard* bt_hid_keyboard; - BtHidMedia* bt_hid_media; - BtHidMouse* bt_hid_mouse; - BtHidTikTok* bt_hid_tiktok; - uint32_t view_id; -} BtHid; - -typedef enum { - BtHidViewSubmenu, - BtHidViewKeynote, - BtHidViewKeyboard, - BtHidViewMedia, - BtHidViewMouse, - BtHidViewTikTok, - BtHidViewExitConfirm, -} BtHidView; diff --git a/applications/plugins/bt_hid_app/views/bt_hid_keyboard.h b/applications/plugins/bt_hid_app/views/bt_hid_keyboard.h deleted file mode 100644 index b2cc928e2a9..00000000000 --- a/applications/plugins/bt_hid_app/views/bt_hid_keyboard.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -#include - -typedef struct BtHidKeyboard BtHidKeyboard; - -BtHidKeyboard* bt_hid_keyboard_alloc(); - -void bt_hid_keyboard_free(BtHidKeyboard* bt_hid_keyboard); - -View* bt_hid_keyboard_get_view(BtHidKeyboard* bt_hid_keyboard); - -void bt_hid_keyboard_set_connected_status(BtHidKeyboard* bt_hid_keyboard, bool connected); diff --git a/applications/plugins/bt_hid_app/views/bt_hid_keynote.h b/applications/plugins/bt_hid_app/views/bt_hid_keynote.h deleted file mode 100644 index 05b121457db..00000000000 --- a/applications/plugins/bt_hid_app/views/bt_hid_keynote.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -#include - -typedef struct BtHidKeynote BtHidKeynote; - -BtHidKeynote* bt_hid_keynote_alloc(); - -void bt_hid_keynote_free(BtHidKeynote* bt_hid_keynote); - -View* bt_hid_keynote_get_view(BtHidKeynote* bt_hid_keynote); - -void bt_hid_keynote_set_connected_status(BtHidKeynote* bt_hid_keynote, bool connected); diff --git a/applications/plugins/bt_hid_app/views/bt_hid_media.h b/applications/plugins/bt_hid_app/views/bt_hid_media.h deleted file mode 100644 index 804239dce9b..00000000000 --- a/applications/plugins/bt_hid_app/views/bt_hid_media.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -#include - -typedef struct BtHidMedia BtHidMedia; - -BtHidMedia* bt_hid_media_alloc(); - -void bt_hid_media_free(BtHidMedia* bt_hid_media); - -View* bt_hid_media_get_view(BtHidMedia* bt_hid_media); - -void bt_hid_media_set_connected_status(BtHidMedia* bt_hid_media, bool connected); diff --git a/applications/plugins/bt_hid_app/views/bt_hid_mouse.h b/applications/plugins/bt_hid_app/views/bt_hid_mouse.h deleted file mode 100644 index a82971d7689..00000000000 --- a/applications/plugins/bt_hid_app/views/bt_hid_mouse.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -#include - -typedef struct BtHidMouse BtHidMouse; - -BtHidMouse* bt_hid_mouse_alloc(); - -void bt_hid_mouse_free(BtHidMouse* bt_hid_mouse); - -View* bt_hid_mouse_get_view(BtHidMouse* bt_hid_mouse); - -void bt_hid_mouse_set_connected_status(BtHidMouse* bt_hid_mouse, bool connected); diff --git a/applications/plugins/bt_hid_app/views/bt_hid_tiktok.h b/applications/plugins/bt_hid_app/views/bt_hid_tiktok.h deleted file mode 100644 index 03c9afecac0..00000000000 --- a/applications/plugins/bt_hid_app/views/bt_hid_tiktok.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -#include - -typedef struct BtHidTikTok BtHidTikTok; - -BtHidTikTok* bt_hid_tiktok_alloc(); - -void bt_hid_tiktok_free(BtHidTikTok* bt_hid_tiktok); - -View* bt_hid_tiktok_get_view(BtHidTikTok* bt_hid_tiktok); - -void bt_hid_tiktok_set_connected_status(BtHidTikTok* bt_hid_tiktok, bool connected); diff --git a/applications/plugins/hid_app/application.fam b/applications/plugins/hid_app/application.fam new file mode 100644 index 00000000000..b8c13e35389 --- /dev/null +++ b/applications/plugins/hid_app/application.fam @@ -0,0 +1,24 @@ +App( + appid="hid_usb", + name="USB Remote", + apptype=FlipperAppType.PLUGIN, + entry_point="hid_usb_app", + stack_size=1 * 1024, + fap_category="Tools", + fap_icon="hid_usb_10px.png", + fap_icon_assets="assets", + fap_icon_assets_symbol="hid", +) + + +App( + appid="hid_ble", + name="Bluetooth Remote", + apptype=FlipperAppType.PLUGIN, + entry_point="hid_ble_app", + stack_size=1 * 1024, + fap_category="Tools", + fap_icon="hid_ble_10px.png", + fap_icon_assets="assets", + fap_icon_assets_symbol="hid", +) diff --git a/applications/plugins/bt_hid_app/assets/Arr_dwn_7x9.png b/applications/plugins/hid_app/assets/Arr_dwn_7x9.png similarity index 100% rename from applications/plugins/bt_hid_app/assets/Arr_dwn_7x9.png rename to applications/plugins/hid_app/assets/Arr_dwn_7x9.png diff --git a/applications/plugins/bt_hid_app/assets/Arr_up_7x9.png b/applications/plugins/hid_app/assets/Arr_up_7x9.png similarity index 100% rename from applications/plugins/bt_hid_app/assets/Arr_up_7x9.png rename to applications/plugins/hid_app/assets/Arr_up_7x9.png diff --git a/applications/plugins/bt_hid_app/assets/Ble_connected_15x15.png b/applications/plugins/hid_app/assets/Ble_connected_15x15.png similarity index 100% rename from applications/plugins/bt_hid_app/assets/Ble_connected_15x15.png rename to applications/plugins/hid_app/assets/Ble_connected_15x15.png diff --git a/applications/plugins/bt_hid_app/assets/Ble_disconnected_15x15.png b/applications/plugins/hid_app/assets/Ble_disconnected_15x15.png similarity index 100% rename from applications/plugins/bt_hid_app/assets/Ble_disconnected_15x15.png rename to applications/plugins/hid_app/assets/Ble_disconnected_15x15.png diff --git a/applications/plugins/bt_hid_app/assets/ButtonDown_7x4.png b/applications/plugins/hid_app/assets/ButtonDown_7x4.png similarity index 100% rename from applications/plugins/bt_hid_app/assets/ButtonDown_7x4.png rename to applications/plugins/hid_app/assets/ButtonDown_7x4.png diff --git a/applications/plugins/bt_hid_app/assets/ButtonLeft_4x7.png b/applications/plugins/hid_app/assets/ButtonLeft_4x7.png similarity index 100% rename from applications/plugins/bt_hid_app/assets/ButtonLeft_4x7.png rename to applications/plugins/hid_app/assets/ButtonLeft_4x7.png diff --git a/applications/plugins/bt_hid_app/assets/ButtonRight_4x7.png b/applications/plugins/hid_app/assets/ButtonRight_4x7.png similarity index 100% rename from applications/plugins/bt_hid_app/assets/ButtonRight_4x7.png rename to applications/plugins/hid_app/assets/ButtonRight_4x7.png diff --git a/applications/plugins/bt_hid_app/assets/ButtonUp_7x4.png b/applications/plugins/hid_app/assets/ButtonUp_7x4.png similarity index 100% rename from applications/plugins/bt_hid_app/assets/ButtonUp_7x4.png rename to applications/plugins/hid_app/assets/ButtonUp_7x4.png diff --git a/applications/plugins/bt_hid_app/assets/Button_18x18.png b/applications/plugins/hid_app/assets/Button_18x18.png similarity index 100% rename from applications/plugins/bt_hid_app/assets/Button_18x18.png rename to applications/plugins/hid_app/assets/Button_18x18.png diff --git a/applications/plugins/bt_hid_app/assets/Circles_47x47.png b/applications/plugins/hid_app/assets/Circles_47x47.png similarity index 100% rename from applications/plugins/bt_hid_app/assets/Circles_47x47.png rename to applications/plugins/hid_app/assets/Circles_47x47.png diff --git a/applications/plugins/bt_hid_app/assets/Left_mouse_icon_9x9.png b/applications/plugins/hid_app/assets/Left_mouse_icon_9x9.png similarity index 100% rename from applications/plugins/bt_hid_app/assets/Left_mouse_icon_9x9.png rename to applications/plugins/hid_app/assets/Left_mouse_icon_9x9.png diff --git a/applications/plugins/bt_hid_app/assets/Like_def_11x9.png b/applications/plugins/hid_app/assets/Like_def_11x9.png similarity index 100% rename from applications/plugins/bt_hid_app/assets/Like_def_11x9.png rename to applications/plugins/hid_app/assets/Like_def_11x9.png diff --git a/applications/plugins/bt_hid_app/assets/Like_pressed_17x17.png b/applications/plugins/hid_app/assets/Like_pressed_17x17.png similarity index 100% rename from applications/plugins/bt_hid_app/assets/Like_pressed_17x17.png rename to applications/plugins/hid_app/assets/Like_pressed_17x17.png diff --git a/applications/plugins/bt_hid_app/assets/Ok_btn_9x9.png b/applications/plugins/hid_app/assets/Ok_btn_9x9.png similarity index 100% rename from applications/plugins/bt_hid_app/assets/Ok_btn_9x9.png rename to applications/plugins/hid_app/assets/Ok_btn_9x9.png diff --git a/applications/plugins/bt_hid_app/assets/Ok_btn_pressed_13x13.png b/applications/plugins/hid_app/assets/Ok_btn_pressed_13x13.png similarity index 100% rename from applications/plugins/bt_hid_app/assets/Ok_btn_pressed_13x13.png rename to applications/plugins/hid_app/assets/Ok_btn_pressed_13x13.png diff --git a/applications/plugins/bt_hid_app/assets/Pin_arrow_down_7x9.png b/applications/plugins/hid_app/assets/Pin_arrow_down_7x9.png similarity index 100% rename from applications/plugins/bt_hid_app/assets/Pin_arrow_down_7x9.png rename to applications/plugins/hid_app/assets/Pin_arrow_down_7x9.png diff --git a/applications/plugins/bt_hid_app/assets/Pin_arrow_left_9x7.png b/applications/plugins/hid_app/assets/Pin_arrow_left_9x7.png similarity index 100% rename from applications/plugins/bt_hid_app/assets/Pin_arrow_left_9x7.png rename to applications/plugins/hid_app/assets/Pin_arrow_left_9x7.png diff --git a/applications/plugins/bt_hid_app/assets/Pin_arrow_right_9x7.png b/applications/plugins/hid_app/assets/Pin_arrow_right_9x7.png similarity index 100% rename from applications/plugins/bt_hid_app/assets/Pin_arrow_right_9x7.png rename to applications/plugins/hid_app/assets/Pin_arrow_right_9x7.png diff --git a/applications/plugins/bt_hid_app/assets/Pin_arrow_up_7x9.png b/applications/plugins/hid_app/assets/Pin_arrow_up_7x9.png similarity index 100% rename from applications/plugins/bt_hid_app/assets/Pin_arrow_up_7x9.png rename to applications/plugins/hid_app/assets/Pin_arrow_up_7x9.png diff --git a/applications/plugins/bt_hid_app/assets/Pin_back_arrow_10x8.png b/applications/plugins/hid_app/assets/Pin_back_arrow_10x8.png similarity index 100% rename from applications/plugins/bt_hid_app/assets/Pin_back_arrow_10x8.png rename to applications/plugins/hid_app/assets/Pin_back_arrow_10x8.png diff --git a/applications/plugins/bt_hid_app/assets/Pressed_Button_13x13.png b/applications/plugins/hid_app/assets/Pressed_Button_13x13.png similarity index 100% rename from applications/plugins/bt_hid_app/assets/Pressed_Button_13x13.png rename to applications/plugins/hid_app/assets/Pressed_Button_13x13.png diff --git a/applications/plugins/bt_hid_app/assets/Right_mouse_icon_9x9.png b/applications/plugins/hid_app/assets/Right_mouse_icon_9x9.png similarity index 100% rename from applications/plugins/bt_hid_app/assets/Right_mouse_icon_9x9.png rename to applications/plugins/hid_app/assets/Right_mouse_icon_9x9.png diff --git a/applications/plugins/bt_hid_app/assets/Space_65x18.png b/applications/plugins/hid_app/assets/Space_65x18.png similarity index 100% rename from applications/plugins/bt_hid_app/assets/Space_65x18.png rename to applications/plugins/hid_app/assets/Space_65x18.png diff --git a/applications/plugins/bt_hid_app/assets/Voldwn_6x6.png b/applications/plugins/hid_app/assets/Voldwn_6x6.png similarity index 100% rename from applications/plugins/bt_hid_app/assets/Voldwn_6x6.png rename to applications/plugins/hid_app/assets/Voldwn_6x6.png diff --git a/applications/plugins/bt_hid_app/assets/Volup_8x6.png b/applications/plugins/hid_app/assets/Volup_8x6.png similarity index 100% rename from applications/plugins/bt_hid_app/assets/Volup_8x6.png rename to applications/plugins/hid_app/assets/Volup_8x6.png diff --git a/applications/plugins/hid_app/hid.c b/applications/plugins/hid_app/hid.c new file mode 100644 index 00000000000..2a617fdeae1 --- /dev/null +++ b/applications/plugins/hid_app/hid.c @@ -0,0 +1,365 @@ +#include "hid.h" +#include "views.h" +#include +#include + +#define TAG "HidApp" + +enum HidDebugSubmenuIndex { + HidSubmenuIndexKeynote, + HidSubmenuIndexKeyboard, + HidSubmenuIndexMedia, + BtHidSubmenuIndexTikTok, + HidSubmenuIndexMouse, +}; +typedef enum { ConnTypeSubmenuIndexBluetooth, ConnTypeSubmenuIndexUsb } ConnTypeDebugSubmenuIndex; + +static void hid_submenu_callback(void* context, uint32_t index) { + furi_assert(context); + Hid* app = context; + if(index == HidSubmenuIndexKeynote) { + app->view_id = HidViewKeynote; + view_dispatcher_switch_to_view(app->view_dispatcher, HidViewKeynote); + } else if(index == HidSubmenuIndexKeyboard) { + app->view_id = HidViewKeyboard; + view_dispatcher_switch_to_view(app->view_dispatcher, HidViewKeyboard); + } else if(index == HidSubmenuIndexMedia) { + app->view_id = HidViewMedia; + view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMedia); + } else if(index == HidSubmenuIndexMouse) { + app->view_id = HidViewMouse; + view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMouse); + } else if(index == BtHidSubmenuIndexTikTok) { + app->view_id = BtHidViewTikTok; + view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewTikTok); + } +} + +static void bt_hid_connection_status_changed_callback(BtStatus status, void* context) { + furi_assert(context); + Hid* hid = context; + bool connected = (status == BtStatusConnected); + if(connected) { + notification_internal_message(hid->notifications, &sequence_set_blue_255); + } else { + notification_internal_message(hid->notifications, &sequence_reset_blue); + } + hid_keynote_set_connected_status(hid->hid_keynote, connected); + hid_keyboard_set_connected_status(hid->hid_keyboard, connected); + hid_media_set_connected_status(hid->hid_media, connected); + hid_mouse_set_connected_status(hid->hid_mouse, connected); + hid_tiktok_set_connected_status(hid->hid_tiktok, connected); +} + +static void hid_dialog_callback(DialogExResult result, void* context) { + furi_assert(context); + Hid* app = context; + if(result == DialogExResultLeft) { + view_dispatcher_stop(app->view_dispatcher); + } else if(result == DialogExResultRight) { + view_dispatcher_switch_to_view(app->view_dispatcher, app->view_id); // Show last view + } else if(result == DialogExResultCenter) { + view_dispatcher_switch_to_view(app->view_dispatcher, HidViewSubmenu); + } +} + +static uint32_t hid_exit_confirm_view(void* context) { + UNUSED(context); + return HidViewExitConfirm; +} + +static uint32_t hid_exit(void* context) { + UNUSED(context); + return VIEW_NONE; +} + +Hid* hid_alloc(HidTransport transport) { + Hid* app = malloc(sizeof(Hid)); + app->transport = transport; + + // Gui + app->gui = furi_record_open(RECORD_GUI); + + // Bt + app->bt = furi_record_open(RECORD_BT); + + // Notifications + app->notifications = furi_record_open(RECORD_NOTIFICATION); + + // View dispatcher + app->view_dispatcher = view_dispatcher_alloc(); + view_dispatcher_enable_queue(app->view_dispatcher); + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + // Device Type Submenu view + app->device_type_submenu = submenu_alloc(); + submenu_add_item( + app->device_type_submenu, "Keynote", HidSubmenuIndexKeynote, hid_submenu_callback, app); + submenu_add_item( + app->device_type_submenu, "Keyboard", HidSubmenuIndexKeyboard, hid_submenu_callback, app); + submenu_add_item( + app->device_type_submenu, "Media", HidSubmenuIndexMedia, hid_submenu_callback, app); + submenu_add_item( + app->device_type_submenu, "Mouse", HidSubmenuIndexMouse, hid_submenu_callback, app); + if(app->transport == HidTransportBle) { + submenu_add_item( + app->device_type_submenu, + "TikTok Controller", + BtHidSubmenuIndexTikTok, + hid_submenu_callback, + app); + } + view_set_previous_callback(submenu_get_view(app->device_type_submenu), hid_exit); + view_dispatcher_add_view( + app->view_dispatcher, HidViewSubmenu, submenu_get_view(app->device_type_submenu)); + app->view_id = HidViewSubmenu; + view_dispatcher_switch_to_view(app->view_dispatcher, app->view_id); + return app; +} + +Hid* hid_app_alloc_view(void* context) { + furi_assert(context); + Hid* app = context; + // Dialog view + app->dialog = dialog_ex_alloc(); + dialog_ex_set_result_callback(app->dialog, hid_dialog_callback); + dialog_ex_set_context(app->dialog, app); + dialog_ex_set_left_button_text(app->dialog, "Exit"); + dialog_ex_set_right_button_text(app->dialog, "Stay"); + dialog_ex_set_center_button_text(app->dialog, "Menu"); + dialog_ex_set_header(app->dialog, "Close Current App?", 16, 12, AlignLeft, AlignTop); + view_dispatcher_add_view( + app->view_dispatcher, HidViewExitConfirm, dialog_ex_get_view(app->dialog)); + + // Keynote view + app->hid_keynote = hid_keynote_alloc(app); + view_set_previous_callback(hid_keynote_get_view(app->hid_keynote), hid_exit_confirm_view); + view_dispatcher_add_view( + app->view_dispatcher, HidViewKeynote, hid_keynote_get_view(app->hid_keynote)); + + // Keyboard view + app->hid_keyboard = hid_keyboard_alloc(app); + view_set_previous_callback(hid_keyboard_get_view(app->hid_keyboard), hid_exit_confirm_view); + view_dispatcher_add_view( + app->view_dispatcher, HidViewKeyboard, hid_keyboard_get_view(app->hid_keyboard)); + + // Media view + app->hid_media = hid_media_alloc(app); + view_set_previous_callback(hid_media_get_view(app->hid_media), hid_exit_confirm_view); + view_dispatcher_add_view( + app->view_dispatcher, HidViewMedia, hid_media_get_view(app->hid_media)); + + // TikTok view + app->hid_tiktok = hid_tiktok_alloc(app); + view_set_previous_callback(hid_tiktok_get_view(app->hid_tiktok), hid_exit_confirm_view); + view_dispatcher_add_view( + app->view_dispatcher, BtHidViewTikTok, hid_tiktok_get_view(app->hid_tiktok)); + + // Mouse view + app->hid_mouse = hid_mouse_alloc(app); + view_set_previous_callback(hid_mouse_get_view(app->hid_mouse), hid_exit_confirm_view); + view_dispatcher_add_view( + app->view_dispatcher, HidViewMouse, hid_mouse_get_view(app->hid_mouse)); + + return app; +} + +void hid_free(Hid* app) { + furi_assert(app); + + // Reset notification + notification_internal_message(app->notifications, &sequence_reset_blue); + + // Free views + view_dispatcher_remove_view(app->view_dispatcher, HidViewSubmenu); + submenu_free(app->device_type_submenu); + view_dispatcher_remove_view(app->view_dispatcher, HidViewExitConfirm); + dialog_ex_free(app->dialog); + view_dispatcher_remove_view(app->view_dispatcher, HidViewKeynote); + hid_keynote_free(app->hid_keynote); + view_dispatcher_remove_view(app->view_dispatcher, HidViewKeyboard); + hid_keyboard_free(app->hid_keyboard); + view_dispatcher_remove_view(app->view_dispatcher, HidViewMedia); + hid_media_free(app->hid_media); + view_dispatcher_remove_view(app->view_dispatcher, HidViewMouse); + hid_mouse_free(app->hid_mouse); + view_dispatcher_remove_view(app->view_dispatcher, BtHidViewTikTok); + hid_tiktok_free(app->hid_tiktok); + view_dispatcher_free(app->view_dispatcher); + + // Close records + furi_record_close(RECORD_GUI); + app->gui = NULL; + furi_record_close(RECORD_NOTIFICATION); + app->notifications = NULL; + furi_record_close(RECORD_BT); + app->bt = NULL; + + // Free rest + free(app); +} + +void hid_hal_keyboard_press(Hid* instance, uint16_t event) { + furi_assert(instance); + if(instance->transport == HidTransportBle) { + furi_hal_bt_hid_kb_press(event); + } else if(instance->transport == HidTransportUsb) { + furi_hal_hid_kb_press(event); + } else { + furi_crash(NULL); + } +} + +void hid_hal_keyboard_release(Hid* instance, uint16_t event) { + furi_assert(instance); + if(instance->transport == HidTransportBle) { + furi_hal_bt_hid_kb_release(event); + } else if(instance->transport == HidTransportUsb) { + furi_hal_hid_kb_release(event); + } else { + furi_crash(NULL); + } +} + +void hid_hal_keyboard_release_all(Hid* instance) { + furi_assert(instance); + if(instance->transport == HidTransportBle) { + furi_hal_bt_hid_kb_release_all(); + } else if(instance->transport == HidTransportUsb) { + furi_hal_hid_kb_release_all(); + } else { + furi_crash(NULL); + } +} + +void hid_hal_consumer_key_press(Hid* instance, uint16_t event) { + furi_assert(instance); + if(instance->transport == HidTransportBle) { + furi_hal_bt_hid_consumer_key_press(event); + } else if(instance->transport == HidTransportUsb) { + furi_hal_hid_consumer_key_press(event); + } else { + furi_crash(NULL); + } +} + +void hid_hal_consumer_key_release(Hid* instance, uint16_t event) { + furi_assert(instance); + if(instance->transport == HidTransportBle) { + furi_hal_bt_hid_consumer_key_release(event); + } else if(instance->transport == HidTransportUsb) { + furi_hal_hid_consumer_key_release(event); + } else { + furi_crash(NULL); + } +} + +void hid_hal_consumer_key_release_all(Hid* instance) { + furi_assert(instance); + if(instance->transport == HidTransportBle) { + furi_hal_bt_hid_consumer_key_release_all(); + } else if(instance->transport == HidTransportUsb) { + furi_hal_hid_kb_release_all(); + } else { + furi_crash(NULL); + } +} + +void hid_hal_mouse_move(Hid* instance, int8_t dx, int8_t dy) { + furi_assert(instance); + if(instance->transport == HidTransportBle) { + furi_hal_bt_hid_mouse_move(dx, dy); + } else if(instance->transport == HidTransportUsb) { + furi_hal_hid_mouse_move(dx, dy); + } else { + furi_crash(NULL); + } +} + +void hid_hal_mouse_scroll(Hid* instance, int8_t delta) { + furi_assert(instance); + if(instance->transport == HidTransportBle) { + furi_hal_bt_hid_mouse_scroll(delta); + } else if(instance->transport == HidTransportUsb) { + furi_hal_hid_mouse_scroll(delta); + } else { + furi_crash(NULL); + } +} + +void hid_hal_mouse_press(Hid* instance, uint16_t event) { + furi_assert(instance); + if(instance->transport == HidTransportBle) { + furi_hal_bt_hid_mouse_press(event); + } else if(instance->transport == HidTransportUsb) { + furi_hal_hid_mouse_press(event); + } else { + furi_crash(NULL); + } +} + +void hid_hal_mouse_release(Hid* instance, uint16_t event) { + furi_assert(instance); + if(instance->transport == HidTransportBle) { + furi_hal_bt_hid_mouse_release(event); + } else if(instance->transport == HidTransportUsb) { + furi_hal_hid_mouse_release(event); + } else { + furi_crash(NULL); + } +} + +void hid_hal_mouse_release_all(Hid* instance) { + furi_assert(instance); + if(instance->transport == HidTransportBle) { + furi_hal_bt_hid_mouse_release_all(); + } else if(instance->transport == HidTransportUsb) { + furi_hal_hid_mouse_release(HID_MOUSE_BTN_LEFT); + furi_hal_hid_mouse_release(HID_MOUSE_BTN_RIGHT); + } else { + furi_crash(NULL); + } +} + +int32_t hid_usb_app(void* p) { + UNUSED(p); + Hid* app = hid_alloc(HidTransportUsb); + app = hid_app_alloc_view(app); + FuriHalUsbInterface* usb_mode_prev = furi_hal_usb_get_config(); + furi_hal_usb_unlock(); + furi_check(furi_hal_usb_set_config(&usb_hid, NULL) == true); + + bt_hid_connection_status_changed_callback(BtStatusConnected, app); + + DOLPHIN_DEED(DolphinDeedPluginStart); + + view_dispatcher_run(app->view_dispatcher); + + furi_hal_usb_set_config(usb_mode_prev, NULL); + + hid_free(app); + + return 0; +} + +int32_t hid_ble_app(void* p) { + UNUSED(p); + Hid* app = hid_alloc(HidTransportBle); + app = hid_app_alloc_view(app); + + if(!bt_set_profile(app->bt, BtProfileHidKeyboard)) { + FURI_LOG_E(TAG, "Failed to switch profile"); + } + furi_hal_bt_start_advertising(); + bt_set_status_changed_callback(app->bt, bt_hid_connection_status_changed_callback, app); + + DOLPHIN_DEED(DolphinDeedPluginStart); + + view_dispatcher_run(app->view_dispatcher); + + bt_set_status_changed_callback(app->bt, NULL, NULL); + bt_set_profile(app->bt, BtProfileSerial); + + hid_free(app); + + return 0; +} diff --git a/applications/plugins/hid_app/hid.h b/applications/plugins/hid_app/hid.h new file mode 100644 index 00000000000..81ebcf5669c --- /dev/null +++ b/applications/plugins/hid_app/hid.h @@ -0,0 +1,60 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include "views/hid_keynote.h" +#include "views/hid_keyboard.h" +#include "views/hid_media.h" +#include "views/hid_mouse.h" +#include "views/hid_tiktok.h" + +typedef enum { + HidTransportUsb, + HidTransportBle, +} HidTransport; + +typedef struct Hid Hid; + +struct Hid { + Bt* bt; + Gui* gui; + NotificationApp* notifications; + ViewDispatcher* view_dispatcher; + Submenu* device_type_submenu; + DialogEx* dialog; + HidKeynote* hid_keynote; + HidKeyboard* hid_keyboard; + HidMedia* hid_media; + HidMouse* hid_mouse; + HidTikTok* hid_tiktok; + + HidTransport transport; + uint32_t view_id; +}; + +void hid_hal_keyboard_press(Hid* instance, uint16_t event); +void hid_hal_keyboard_release(Hid* instance, uint16_t event); +void hid_hal_keyboard_release_all(Hid* instance); + +void hid_hal_consumer_key_press(Hid* instance, uint16_t event); +void hid_hal_consumer_key_release(Hid* instance, uint16_t event); +void hid_hal_consumer_key_release_all(Hid* instance); + +void hid_hal_mouse_move(Hid* instance, int8_t dx, int8_t dy); +void hid_hal_mouse_scroll(Hid* instance, int8_t delta); +void hid_hal_mouse_press(Hid* instance, uint16_t event); +void hid_hal_mouse_release(Hid* instance, uint16_t event); +void hid_hal_mouse_release_all(Hid* instance); \ No newline at end of file diff --git a/applications/plugins/bt_hid_app/bt_remote_10px.png b/applications/plugins/hid_app/hid_ble_10px.png similarity index 100% rename from applications/plugins/bt_hid_app/bt_remote_10px.png rename to applications/plugins/hid_app/hid_ble_10px.png diff --git a/applications/plugins/hid_app/hid_usb_10px.png b/applications/plugins/hid_app/hid_usb_10px.png new file mode 100644 index 0000000000000000000000000000000000000000..415de7d2304fe982c025b2b9a942abbf0a2b6dd0 GIT binary patch literal 969 zcmaJ=J#W)M7(OY0prTdS3e(9IQYsOjefe;0)l|h!Xha;MG(g5)>`P;{_8I$1oJb(V z#>Co{9{@i91|(QuX5%*?v9pwOnxqT_55D(az0dQ0J@>lZy1%+|YXtzX+Ss!@;>_%o zt2y!i@N?&lIBxPQduOaNrjU+e?;YX%)UR2L%LyN@}>atRF6-9xXE~}dAVr@YBcOX_U zM#>gat3`~BQpG5%aP~Eh*gj89{x|#<%&i_M$ zU=f}04!x-NpTtRb98uJv2|I~hvAe-WmMSu=m=ez7E@Q{@LAHmCvt-C3h|9793l4Gp zF!O9qA&z4-!i1C1r48GZ1c~hXo`JDuS-aJ0wOrd$)taqWN;ON>tZH4WV;fs@tj*k$ zfQEdI^)9g5QfwxOAQG8v8vD3;Z_1q-{ zl$i_hipxU&G!&YTg}8q|eDGX6j4SPCw{~`RCd@~lzrPU2?S{SEO@H(cJnv<$o(G-l ph0_~rZ>7^_`EovpzT_W+OY7j;8rXcd{ -#include -#include #include #include +#include "../hid.h" +#include "hid_icons.h" -#include "bt_hid_icons.h" +#define TAG "HidKeyboard" -struct BtHidKeyboard { +struct HidKeyboard { View* view; + Hid* hid; }; typedef struct { @@ -24,7 +25,7 @@ typedef struct { bool back_pressed; bool connected; char key_string[5]; -} BtHidKeyboardModel; +} HidKeyboardModel; typedef struct { uint8_t width; @@ -32,13 +33,12 @@ typedef struct { const Icon* icon; char* shift_key; uint8_t value; -} BtHidKeyboardKey; +} HidKeyboardKey; typedef struct { int8_t x; int8_t y; -} BtHidKeyboardPoint; - +} HidKeyboardPoint; // 4 BY 12 #define MARGIN_TOP 0 #define MARGIN_LEFT 4 @@ -49,7 +49,7 @@ typedef struct { #define COLUMN_COUNT 12 // 0 width items are not drawn, but there value is used -const BtHidKeyboardKey bt_hid_keyboard_keyset[ROW_COUNT][COLUMN_COUNT] = { +const HidKeyboardKey hid_keyboard_keyset[ROW_COUNT][COLUMN_COUNT] = { { {.width = 1, .icon = NULL, .key = "1", .shift_key = "!", .value = HID_KEYBOARD_1}, {.width = 1, .icon = NULL, .key = "2", .shift_key = "@", .value = HID_KEYBOARD_2}, @@ -112,7 +112,7 @@ const BtHidKeyboardKey bt_hid_keyboard_keyset[ROW_COUNT][COLUMN_COUNT] = { }, { {.width = 1, .icon = &I_Pin_arrow_up_7x9, .value = HID_KEYBOARD_L_SHIFT}, - {.width = 1, .icon = NULL, .key = ",", .shift_key = "<", .value = HID_KEYPAD_COMMA}, + {.width = 1, .icon = NULL, .key = ",", .shift_key = "<", .value = HID_KEYBOARD_COMMA}, {.width = 1, .icon = NULL, .key = ".", .shift_key = ">", .value = HID_KEYBOARD_DOT}, {.width = 4, .icon = NULL, .key = " ", .value = HID_KEYBOARD_SPACEBAR}, {.width = 0, .value = HID_KEYBOARD_SPACEBAR}, @@ -140,19 +140,19 @@ const BtHidKeyboardKey bt_hid_keyboard_keyset[ROW_COUNT][COLUMN_COUNT] = { }, }; -static void bt_hid_keyboard_to_upper(char* str) { +static void hid_keyboard_to_upper(char* str) { while(*str) { *str = toupper((unsigned char)*str); str++; } } -static void bt_hid_keyboard_draw_key( +static void hid_keyboard_draw_key( Canvas* canvas, - BtHidKeyboardModel* model, + HidKeyboardModel* model, uint8_t x, uint8_t y, - BtHidKeyboardKey key, + HidKeyboardKey key, bool selected) { if(!key.width) return; @@ -190,7 +190,7 @@ static void bt_hid_keyboard_draw_key( if((model->ctrl && key.value == HID_KEYBOARD_L_CTRL) || (model->alt && key.value == HID_KEYBOARD_L_ALT) || (model->gui && key.value == HID_KEYBOARD_L_GUI)) { - bt_hid_keyboard_to_upper(model->key_string); + hid_keyboard_to_upper(model->key_string); } canvas_draw_str_aligned( canvas, @@ -202,9 +202,9 @@ static void bt_hid_keyboard_draw_key( } } -static void bt_hid_keyboard_draw_callback(Canvas* canvas, void* context) { +static void hid_keyboard_draw_callback(Canvas* canvas, void* context) { furi_assert(context); - BtHidKeyboardModel* model = context; + HidKeyboardModel* model = context; // Header if(!model->connected) { @@ -225,17 +225,17 @@ static void bt_hid_keyboard_draw_callback(Canvas* canvas, void* context) { // Start shifting the all keys up if on the next row (Scrolling) uint8_t initY = model->y - 4 > 0 ? model->y - 4 : 0; for(uint8_t y = initY; y < ROW_COUNT; y++) { - const BtHidKeyboardKey* keyboardKeyRow = bt_hid_keyboard_keyset[y]; + const HidKeyboardKey* keyboardKeyRow = hid_keyboard_keyset[y]; uint8_t x = 0; for(uint8_t i = 0; i < COLUMN_COUNT; i++) { - BtHidKeyboardKey key = keyboardKeyRow[i]; + HidKeyboardKey key = keyboardKeyRow[i]; // Select when the button is hovered // Select if the button is hovered within its width // Select if back is clicked and its the backspace key // Deselect when the button clicked or not hovered bool keySelected = (x <= model->x && model->x < (x + key.width)) && y == model->y; bool backSelected = model->back_pressed && key.value == HID_KEYBOARD_DELETE; - bt_hid_keyboard_draw_key( + hid_keyboard_draw_key( canvas, model, x, @@ -247,8 +247,8 @@ static void bt_hid_keyboard_draw_callback(Canvas* canvas, void* context) { } } -static uint8_t bt_hid_keyboard_get_selected_key(BtHidKeyboardModel* model) { - BtHidKeyboardKey key = bt_hid_keyboard_keyset[model->y][model->x]; +static uint8_t hid_keyboard_get_selected_key(HidKeyboardModel* model) { + HidKeyboardKey key = hid_keyboard_keyset[model->y][model->x]; // Use upper case if shift is toggled bool useUppercase = model->shift; // Check if the key has an upper case version @@ -259,34 +259,34 @@ static uint8_t bt_hid_keyboard_get_selected_key(BtHidKeyboardModel* model) { return key.value; } -static void bt_hid_keyboard_get_select_key(BtHidKeyboardModel* model, BtHidKeyboardPoint delta) { +static void hid_keyboard_get_select_key(HidKeyboardModel* model, HidKeyboardPoint delta) { // Keep going until a valid spot is found, this allows for nulls and zero width keys in the map do { if(((int8_t)model->y) + delta.y < 0) model->y = ROW_COUNT - 1; else model->y = (model->y + delta.y) % ROW_COUNT; - } while(delta.y != 0 && bt_hid_keyboard_keyset[model->y][model->x].value == 0); + } while(delta.y != 0 && hid_keyboard_keyset[model->y][model->x].value == 0); do { if(((int8_t)model->x) + delta.x < 0) model->x = COLUMN_COUNT - 1; else model->x = (model->x + delta.x) % COLUMN_COUNT; - } while(delta.x != 0 && bt_hid_keyboard_keyset[model->y][model->x].width == + } while(delta.x != 0 && hid_keyboard_keyset[model->y][model->x].width == 0); // Skip zero width keys, pretend they are one key } -static void bt_hid_keyboard_process(BtHidKeyboard* bt_hid_keyboard, InputEvent* event) { +static void hid_keyboard_process(HidKeyboard* hid_keyboard, InputEvent* event) { with_view_model( - bt_hid_keyboard->view, - BtHidKeyboardModel * model, + hid_keyboard->view, + HidKeyboardModel * model, { if(event->key == InputKeyOk) { if(event->type == InputTypePress) { model->ok_pressed = true; } else if(event->type == InputTypeLong || event->type == InputTypeShort) { - model->last_key_code = bt_hid_keyboard_get_selected_key(model); + model->last_key_code = hid_keyboard_get_selected_key(model); // Toggle the modifier key when clicked, and click the key if(model->last_key_code == HID_KEYBOARD_L_SHIFT) { @@ -314,10 +314,12 @@ static void bt_hid_keyboard_process(BtHidKeyboard* bt_hid_keyboard, InputEvent* else model->modifier_code &= ~KEY_MOD_LEFT_GUI; } - furi_hal_bt_hid_kb_press(model->modifier_code | model->last_key_code); + hid_hal_keyboard_press( + hid_keyboard->hid, model->modifier_code | model->last_key_code); } else if(event->type == InputTypeRelease) { // Release happens after short and long presses - furi_hal_bt_hid_kb_release(model->modifier_code | model->last_key_code); + hid_hal_keyboard_release( + hid_keyboard->hid, model->modifier_code | model->last_key_code); model->ok_pressed = false; } } else if(event->key == InputKeyBack) { @@ -325,66 +327,67 @@ static void bt_hid_keyboard_process(BtHidKeyboard* bt_hid_keyboard, InputEvent* if(event->type == InputTypePress) { model->back_pressed = true; } else if(event->type == InputTypeShort) { - furi_hal_bt_hid_kb_press(HID_KEYBOARD_DELETE); - furi_hal_bt_hid_kb_release(HID_KEYBOARD_DELETE); + hid_hal_keyboard_press(hid_keyboard->hid, HID_KEYBOARD_DELETE); + hid_hal_keyboard_release(hid_keyboard->hid, HID_KEYBOARD_DELETE); } else if(event->type == InputTypeRelease) { model->back_pressed = false; } } else if(event->type == InputTypePress || event->type == InputTypeRepeat) { // Cycle the selected keys if(event->key == InputKeyUp) { - bt_hid_keyboard_get_select_key(model, (BtHidKeyboardPoint){.x = 0, .y = -1}); + hid_keyboard_get_select_key(model, (HidKeyboardPoint){.x = 0, .y = -1}); } else if(event->key == InputKeyDown) { - bt_hid_keyboard_get_select_key(model, (BtHidKeyboardPoint){.x = 0, .y = 1}); + hid_keyboard_get_select_key(model, (HidKeyboardPoint){.x = 0, .y = 1}); } else if(event->key == InputKeyLeft) { - bt_hid_keyboard_get_select_key(model, (BtHidKeyboardPoint){.x = -1, .y = 0}); + hid_keyboard_get_select_key(model, (HidKeyboardPoint){.x = -1, .y = 0}); } else if(event->key == InputKeyRight) { - bt_hid_keyboard_get_select_key(model, (BtHidKeyboardPoint){.x = 1, .y = 0}); + hid_keyboard_get_select_key(model, (HidKeyboardPoint){.x = 1, .y = 0}); } } }, true); } -static bool bt_hid_keyboard_input_callback(InputEvent* event, void* context) { +static bool hid_keyboard_input_callback(InputEvent* event, void* context) { furi_assert(context); - BtHidKeyboard* bt_hid_keyboard = context; + HidKeyboard* hid_keyboard = context; bool consumed = false; if(event->type == InputTypeLong && event->key == InputKeyBack) { - furi_hal_bt_hid_kb_release_all(); + hid_hal_keyboard_release_all(hid_keyboard->hid); } else { - bt_hid_keyboard_process(bt_hid_keyboard, event); + hid_keyboard_process(hid_keyboard, event); consumed = true; } return consumed; } -BtHidKeyboard* bt_hid_keyboard_alloc() { - BtHidKeyboard* bt_hid_keyboard = malloc(sizeof(BtHidKeyboard)); - bt_hid_keyboard->view = view_alloc(); - view_set_context(bt_hid_keyboard->view, bt_hid_keyboard); - view_allocate_model(bt_hid_keyboard->view, ViewModelTypeLocking, sizeof(BtHidKeyboardModel)); - view_set_draw_callback(bt_hid_keyboard->view, bt_hid_keyboard_draw_callback); - view_set_input_callback(bt_hid_keyboard->view, bt_hid_keyboard_input_callback); +HidKeyboard* hid_keyboard_alloc(Hid* bt_hid) { + HidKeyboard* hid_keyboard = malloc(sizeof(HidKeyboard)); + hid_keyboard->view = view_alloc(); + hid_keyboard->hid = bt_hid; + view_set_context(hid_keyboard->view, hid_keyboard); + view_allocate_model(hid_keyboard->view, ViewModelTypeLocking, sizeof(HidKeyboardModel)); + view_set_draw_callback(hid_keyboard->view, hid_keyboard_draw_callback); + view_set_input_callback(hid_keyboard->view, hid_keyboard_input_callback); - return bt_hid_keyboard; + return hid_keyboard; } -void bt_hid_keyboard_free(BtHidKeyboard* bt_hid_keyboard) { - furi_assert(bt_hid_keyboard); - view_free(bt_hid_keyboard->view); - free(bt_hid_keyboard); +void hid_keyboard_free(HidKeyboard* hid_keyboard) { + furi_assert(hid_keyboard); + view_free(hid_keyboard->view); + free(hid_keyboard); } -View* bt_hid_keyboard_get_view(BtHidKeyboard* bt_hid_keyboard) { - furi_assert(bt_hid_keyboard); - return bt_hid_keyboard->view; +View* hid_keyboard_get_view(HidKeyboard* hid_keyboard) { + furi_assert(hid_keyboard); + return hid_keyboard->view; } -void bt_hid_keyboard_set_connected_status(BtHidKeyboard* bt_hid_keyboard, bool connected) { - furi_assert(bt_hid_keyboard); +void hid_keyboard_set_connected_status(HidKeyboard* hid_keyboard, bool connected) { + furi_assert(hid_keyboard); with_view_model( - bt_hid_keyboard->view, BtHidKeyboardModel * model, { model->connected = connected; }, true); + hid_keyboard->view, HidKeyboardModel * model, { model->connected = connected; }, true); } diff --git a/applications/plugins/hid_app/views/hid_keyboard.h b/applications/plugins/hid_app/views/hid_keyboard.h new file mode 100644 index 00000000000..7127713643b --- /dev/null +++ b/applications/plugins/hid_app/views/hid_keyboard.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +typedef struct Hid Hid; +typedef struct HidKeyboard HidKeyboard; + +HidKeyboard* hid_keyboard_alloc(Hid* bt_hid); + +void hid_keyboard_free(HidKeyboard* hid_keyboard); + +View* hid_keyboard_get_view(HidKeyboard* hid_keyboard); + +void hid_keyboard_set_connected_status(HidKeyboard* hid_keyboard, bool connected); diff --git a/applications/plugins/bt_hid_app/views/bt_hid_keynote.c b/applications/plugins/hid_app/views/hid_keynote.c similarity index 60% rename from applications/plugins/bt_hid_app/views/bt_hid_keynote.c rename to applications/plugins/hid_app/views/hid_keynote.c index 0e81c5fa09d..c95f427803d 100644 --- a/applications/plugins/bt_hid_app/views/bt_hid_keynote.c +++ b/applications/plugins/hid_app/views/hid_keynote.c @@ -1,13 +1,14 @@ -#include "bt_hid_keynote.h" -#include -#include -#include +#include "hid_keynote.h" #include +#include "../hid.h" -#include "bt_hid_icons.h" +#include "hid_icons.h" -struct BtHidKeynote { +#define TAG "HidKeynote" + +struct HidKeynote { View* view; + Hid* hid; }; typedef struct { @@ -18,9 +19,9 @@ typedef struct { bool ok_pressed; bool back_pressed; bool connected; -} BtHidKeynoteModel; +} HidKeynoteModel; -static void bt_hid_keynote_draw_arrow(Canvas* canvas, uint8_t x, uint8_t y, CanvasDirection dir) { +static void hid_keynote_draw_arrow(Canvas* canvas, uint8_t x, uint8_t y, CanvasDirection dir) { canvas_draw_triangle(canvas, x, y, 5, 3, dir); if(dir == CanvasDirectionBottomToTop) { canvas_draw_line(canvas, x, y + 6, x, y - 1); @@ -33,9 +34,9 @@ static void bt_hid_keynote_draw_arrow(Canvas* canvas, uint8_t x, uint8_t y, Canv } } -static void bt_hid_keynote_draw_callback(Canvas* canvas, void* context) { +static void hid_keynote_draw_callback(Canvas* canvas, void* context) { furi_assert(context); - BtHidKeynoteModel* model = context; + HidKeynoteModel* model = context; // Header if(model->connected) { @@ -56,7 +57,7 @@ static void bt_hid_keynote_draw_callback(Canvas* canvas, void* context) { elements_slightly_rounded_box(canvas, 24, 26, 13, 13); canvas_set_color(canvas, ColorWhite); } - bt_hid_keynote_draw_arrow(canvas, 30, 30, CanvasDirectionBottomToTop); + hid_keynote_draw_arrow(canvas, 30, 30, CanvasDirectionBottomToTop); canvas_set_color(canvas, ColorBlack); // Down @@ -65,7 +66,7 @@ static void bt_hid_keynote_draw_callback(Canvas* canvas, void* context) { elements_slightly_rounded_box(canvas, 24, 47, 13, 13); canvas_set_color(canvas, ColorWhite); } - bt_hid_keynote_draw_arrow(canvas, 30, 55, CanvasDirectionTopToBottom); + hid_keynote_draw_arrow(canvas, 30, 55, CanvasDirectionTopToBottom); canvas_set_color(canvas, ColorBlack); // Left @@ -74,7 +75,7 @@ static void bt_hid_keynote_draw_callback(Canvas* canvas, void* context) { elements_slightly_rounded_box(canvas, 3, 47, 13, 13); canvas_set_color(canvas, ColorWhite); } - bt_hid_keynote_draw_arrow(canvas, 7, 53, CanvasDirectionRightToLeft); + hid_keynote_draw_arrow(canvas, 7, 53, CanvasDirectionRightToLeft); canvas_set_color(canvas, ColorBlack); // Right @@ -83,7 +84,7 @@ static void bt_hid_keynote_draw_callback(Canvas* canvas, void* context) { elements_slightly_rounded_box(canvas, 45, 47, 13, 13); canvas_set_color(canvas, ColorWhite); } - bt_hid_keynote_draw_arrow(canvas, 53, 53, CanvasDirectionLeftToRight); + hid_keynote_draw_arrow(canvas, 53, 53, CanvasDirectionLeftToRight); canvas_set_color(canvas, ColorBlack); // Ok @@ -106,100 +107,101 @@ static void bt_hid_keynote_draw_callback(Canvas* canvas, void* context) { elements_multiline_text_aligned(canvas, 91, 57, AlignLeft, AlignBottom, "Back"); } -static void bt_hid_keynote_process(BtHidKeynote* bt_hid_keynote, InputEvent* event) { +static void hid_keynote_process(HidKeynote* hid_keynote, InputEvent* event) { with_view_model( - bt_hid_keynote->view, - BtHidKeynoteModel * model, + hid_keynote->view, + HidKeynoteModel * model, { if(event->type == InputTypePress) { if(event->key == InputKeyUp) { model->up_pressed = true; - furi_hal_bt_hid_kb_press(HID_KEYBOARD_UP_ARROW); + hid_hal_keyboard_press(hid_keynote->hid, HID_KEYBOARD_UP_ARROW); } else if(event->key == InputKeyDown) { model->down_pressed = true; - furi_hal_bt_hid_kb_press(HID_KEYBOARD_DOWN_ARROW); + hid_hal_keyboard_press(hid_keynote->hid, HID_KEYBOARD_DOWN_ARROW); } else if(event->key == InputKeyLeft) { model->left_pressed = true; - furi_hal_bt_hid_kb_press(HID_KEYBOARD_LEFT_ARROW); + hid_hal_keyboard_press(hid_keynote->hid, HID_KEYBOARD_LEFT_ARROW); } else if(event->key == InputKeyRight) { model->right_pressed = true; - furi_hal_bt_hid_kb_press(HID_KEYBOARD_RIGHT_ARROW); + hid_hal_keyboard_press(hid_keynote->hid, HID_KEYBOARD_RIGHT_ARROW); } else if(event->key == InputKeyOk) { model->ok_pressed = true; - furi_hal_bt_hid_kb_press(HID_KEYBOARD_SPACEBAR); + hid_hal_keyboard_press(hid_keynote->hid, HID_KEYBOARD_SPACEBAR); } else if(event->key == InputKeyBack) { model->back_pressed = true; } } else if(event->type == InputTypeRelease) { if(event->key == InputKeyUp) { model->up_pressed = false; - furi_hal_bt_hid_kb_release(HID_KEYBOARD_UP_ARROW); + hid_hal_keyboard_release(hid_keynote->hid, HID_KEYBOARD_UP_ARROW); } else if(event->key == InputKeyDown) { model->down_pressed = false; - furi_hal_bt_hid_kb_release(HID_KEYBOARD_DOWN_ARROW); + hid_hal_keyboard_release(hid_keynote->hid, HID_KEYBOARD_DOWN_ARROW); } else if(event->key == InputKeyLeft) { model->left_pressed = false; - furi_hal_bt_hid_kb_release(HID_KEYBOARD_LEFT_ARROW); + hid_hal_keyboard_release(hid_keynote->hid, HID_KEYBOARD_LEFT_ARROW); } else if(event->key == InputKeyRight) { model->right_pressed = false; - furi_hal_bt_hid_kb_release(HID_KEYBOARD_RIGHT_ARROW); + hid_hal_keyboard_release(hid_keynote->hid, HID_KEYBOARD_RIGHT_ARROW); } else if(event->key == InputKeyOk) { model->ok_pressed = false; - furi_hal_bt_hid_kb_release(HID_KEYBOARD_SPACEBAR); + hid_hal_keyboard_release(hid_keynote->hid, HID_KEYBOARD_SPACEBAR); } else if(event->key == InputKeyBack) { model->back_pressed = false; } } else if(event->type == InputTypeShort) { if(event->key == InputKeyBack) { - furi_hal_bt_hid_kb_press(HID_KEYBOARD_DELETE); - furi_hal_bt_hid_kb_release(HID_KEYBOARD_DELETE); - furi_hal_bt_hid_consumer_key_press(HID_CONSUMER_AC_BACK); - furi_hal_bt_hid_consumer_key_release(HID_CONSUMER_AC_BACK); + hid_hal_keyboard_press(hid_keynote->hid, HID_KEYBOARD_DELETE); + hid_hal_keyboard_release(hid_keynote->hid, HID_KEYBOARD_DELETE); + hid_hal_consumer_key_press(hid_keynote->hid, HID_CONSUMER_AC_BACK); + hid_hal_consumer_key_release(hid_keynote->hid, HID_CONSUMER_AC_BACK); } } }, true); } -static bool bt_hid_keynote_input_callback(InputEvent* event, void* context) { +static bool hid_keynote_input_callback(InputEvent* event, void* context) { furi_assert(context); - BtHidKeynote* bt_hid_keynote = context; + HidKeynote* hid_keynote = context; bool consumed = false; if(event->type == InputTypeLong && event->key == InputKeyBack) { - furi_hal_bt_hid_kb_release_all(); + hid_hal_keyboard_release_all(hid_keynote->hid); } else { - bt_hid_keynote_process(bt_hid_keynote, event); + hid_keynote_process(hid_keynote, event); consumed = true; } return consumed; } -BtHidKeynote* bt_hid_keynote_alloc() { - BtHidKeynote* bt_hid_keynote = malloc(sizeof(BtHidKeynote)); - bt_hid_keynote->view = view_alloc(); - view_set_context(bt_hid_keynote->view, bt_hid_keynote); - view_allocate_model(bt_hid_keynote->view, ViewModelTypeLocking, sizeof(BtHidKeynoteModel)); - view_set_draw_callback(bt_hid_keynote->view, bt_hid_keynote_draw_callback); - view_set_input_callback(bt_hid_keynote->view, bt_hid_keynote_input_callback); +HidKeynote* hid_keynote_alloc(Hid* hid) { + HidKeynote* hid_keynote = malloc(sizeof(HidKeynote)); + hid_keynote->view = view_alloc(); + hid_keynote->hid = hid; + view_set_context(hid_keynote->view, hid_keynote); + view_allocate_model(hid_keynote->view, ViewModelTypeLocking, sizeof(HidKeynoteModel)); + view_set_draw_callback(hid_keynote->view, hid_keynote_draw_callback); + view_set_input_callback(hid_keynote->view, hid_keynote_input_callback); - return bt_hid_keynote; + return hid_keynote; } -void bt_hid_keynote_free(BtHidKeynote* bt_hid_keynote) { - furi_assert(bt_hid_keynote); - view_free(bt_hid_keynote->view); - free(bt_hid_keynote); +void hid_keynote_free(HidKeynote* hid_keynote) { + furi_assert(hid_keynote); + view_free(hid_keynote->view); + free(hid_keynote); } -View* bt_hid_keynote_get_view(BtHidKeynote* bt_hid_keynote) { - furi_assert(bt_hid_keynote); - return bt_hid_keynote->view; +View* hid_keynote_get_view(HidKeynote* hid_keynote) { + furi_assert(hid_keynote); + return hid_keynote->view; } -void bt_hid_keynote_set_connected_status(BtHidKeynote* bt_hid_keynote, bool connected) { - furi_assert(bt_hid_keynote); +void hid_keynote_set_connected_status(HidKeynote* hid_keynote, bool connected) { + furi_assert(hid_keynote); with_view_model( - bt_hid_keynote->view, BtHidKeynoteModel * model, { model->connected = connected; }, true); + hid_keynote->view, HidKeynoteModel * model, { model->connected = connected; }, true); } diff --git a/applications/plugins/hid_app/views/hid_keynote.h b/applications/plugins/hid_app/views/hid_keynote.h new file mode 100644 index 00000000000..4d4a0a9b1a0 --- /dev/null +++ b/applications/plugins/hid_app/views/hid_keynote.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +typedef struct Hid Hid; +typedef struct HidKeynote HidKeynote; + +HidKeynote* hid_keynote_alloc(Hid* bt_hid); + +void hid_keynote_free(HidKeynote* hid_keynote); + +View* hid_keynote_get_view(HidKeynote* hid_keynote); + +void hid_keynote_set_connected_status(HidKeynote* hid_keynote, bool connected); diff --git a/applications/plugins/bt_hid_app/views/bt_hid_media.c b/applications/plugins/hid_app/views/hid_media.c similarity index 59% rename from applications/plugins/bt_hid_app/views/bt_hid_media.c rename to applications/plugins/hid_app/views/hid_media.c index df7349a97bf..5d764f73ac0 100644 --- a/applications/plugins/bt_hid_app/views/bt_hid_media.c +++ b/applications/plugins/hid_app/views/hid_media.c @@ -1,13 +1,17 @@ -#include "bt_hid_media.h" +#include "hid_media.h" #include #include #include #include +#include "../hid.h" -#include "bt_hid_icons.h" +#include "hid_icons.h" -struct BtHidMedia { +#define TAG "HidMedia" + +struct HidMedia { View* view; + Hid* hid; }; typedef struct { @@ -17,9 +21,9 @@ typedef struct { bool down_pressed; bool ok_pressed; bool connected; -} BtHidMediaModel; +} HidMediaModel; -static void bt_hid_media_draw_arrow(Canvas* canvas, uint8_t x, uint8_t y, CanvasDirection dir) { +static void hid_media_draw_arrow(Canvas* canvas, uint8_t x, uint8_t y, CanvasDirection dir) { canvas_draw_triangle(canvas, x, y, 5, 3, dir); if(dir == CanvasDirectionBottomToTop) { canvas_draw_dot(canvas, x, y - 1); @@ -32,9 +36,9 @@ static void bt_hid_media_draw_arrow(Canvas* canvas, uint8_t x, uint8_t y, Canvas } } -static void bt_hid_media_draw_callback(Canvas* canvas, void* context) { +static void hid_media_draw_callback(Canvas* canvas, void* context) { furi_assert(context); - BtHidMediaModel* model = context; + HidMediaModel* model = context; // Header if(model->connected) { @@ -76,8 +80,8 @@ static void bt_hid_media_draw_callback(Canvas* canvas, void* context) { canvas_set_bitmap_mode(canvas, 0); canvas_set_color(canvas, ColorWhite); } - bt_hid_media_draw_arrow(canvas, 82, 31, CanvasDirectionRightToLeft); - bt_hid_media_draw_arrow(canvas, 86, 31, CanvasDirectionRightToLeft); + hid_media_draw_arrow(canvas, 82, 31, CanvasDirectionRightToLeft); + hid_media_draw_arrow(canvas, 86, 31, CanvasDirectionRightToLeft); canvas_set_color(canvas, ColorBlack); // Right @@ -87,8 +91,8 @@ static void bt_hid_media_draw_callback(Canvas* canvas, void* context) { canvas_set_bitmap_mode(canvas, 0); canvas_set_color(canvas, ColorWhite); } - bt_hid_media_draw_arrow(canvas, 112, 31, CanvasDirectionLeftToRight); - bt_hid_media_draw_arrow(canvas, 116, 31, CanvasDirectionLeftToRight); + hid_media_draw_arrow(canvas, 112, 31, CanvasDirectionLeftToRight); + hid_media_draw_arrow(canvas, 116, 31, CanvasDirectionLeftToRight); canvas_set_color(canvas, ColorBlack); // Ok @@ -96,7 +100,7 @@ static void bt_hid_media_draw_callback(Canvas* canvas, void* context) { canvas_draw_icon(canvas, 93, 25, &I_Pressed_Button_13x13); canvas_set_color(canvas, ColorWhite); } - bt_hid_media_draw_arrow(canvas, 96, 31, CanvasDirectionLeftToRight); + hid_media_draw_arrow(canvas, 96, 31, CanvasDirectionLeftToRight); canvas_draw_line(canvas, 100, 29, 100, 33); canvas_draw_line(canvas, 102, 29, 102, 33); canvas_set_color(canvas, ColorBlack); @@ -107,100 +111,101 @@ static void bt_hid_media_draw_callback(Canvas* canvas, void* context) { elements_multiline_text_aligned(canvas, 13, 62, AlignLeft, AlignBottom, "Hold to exit"); } -static void bt_hid_media_process_press(BtHidMedia* bt_hid_media, InputEvent* event) { +static void hid_media_process_press(HidMedia* hid_media, InputEvent* event) { with_view_model( - bt_hid_media->view, - BtHidMediaModel * model, + hid_media->view, + HidMediaModel * model, { if(event->key == InputKeyUp) { model->up_pressed = true; - furi_hal_bt_hid_consumer_key_press(HID_CONSUMER_VOLUME_INCREMENT); + hid_hal_consumer_key_press(hid_media->hid, HID_CONSUMER_VOLUME_INCREMENT); } else if(event->key == InputKeyDown) { model->down_pressed = true; - furi_hal_bt_hid_consumer_key_press(HID_CONSUMER_VOLUME_DECREMENT); + hid_hal_consumer_key_press(hid_media->hid, HID_CONSUMER_VOLUME_DECREMENT); } else if(event->key == InputKeyLeft) { model->left_pressed = true; - furi_hal_bt_hid_consumer_key_press(HID_CONSUMER_SCAN_PREVIOUS_TRACK); + hid_hal_consumer_key_press(hid_media->hid, HID_CONSUMER_SCAN_PREVIOUS_TRACK); } else if(event->key == InputKeyRight) { model->right_pressed = true; - furi_hal_bt_hid_consumer_key_press(HID_CONSUMER_SCAN_NEXT_TRACK); + hid_hal_consumer_key_press(hid_media->hid, HID_CONSUMER_SCAN_NEXT_TRACK); } else if(event->key == InputKeyOk) { model->ok_pressed = true; - furi_hal_bt_hid_consumer_key_press(HID_CONSUMER_PLAY_PAUSE); + hid_hal_consumer_key_press(hid_media->hid, HID_CONSUMER_PLAY_PAUSE); } }, true); } -static void bt_hid_media_process_release(BtHidMedia* bt_hid_media, InputEvent* event) { +static void hid_media_process_release(HidMedia* hid_media, InputEvent* event) { with_view_model( - bt_hid_media->view, - BtHidMediaModel * model, + hid_media->view, + HidMediaModel * model, { if(event->key == InputKeyUp) { model->up_pressed = false; - furi_hal_bt_hid_consumer_key_release(HID_CONSUMER_VOLUME_INCREMENT); + hid_hal_consumer_key_release(hid_media->hid, HID_CONSUMER_VOLUME_INCREMENT); } else if(event->key == InputKeyDown) { model->down_pressed = false; - furi_hal_bt_hid_consumer_key_release(HID_CONSUMER_VOLUME_DECREMENT); + hid_hal_consumer_key_release(hid_media->hid, HID_CONSUMER_VOLUME_DECREMENT); } else if(event->key == InputKeyLeft) { model->left_pressed = false; - furi_hal_bt_hid_consumer_key_release(HID_CONSUMER_SCAN_PREVIOUS_TRACK); + hid_hal_consumer_key_release(hid_media->hid, HID_CONSUMER_SCAN_PREVIOUS_TRACK); } else if(event->key == InputKeyRight) { model->right_pressed = false; - furi_hal_bt_hid_consumer_key_release(HID_CONSUMER_SCAN_NEXT_TRACK); + hid_hal_consumer_key_release(hid_media->hid, HID_CONSUMER_SCAN_NEXT_TRACK); } else if(event->key == InputKeyOk) { model->ok_pressed = false; - furi_hal_bt_hid_consumer_key_release(HID_CONSUMER_PLAY_PAUSE); + hid_hal_consumer_key_release(hid_media->hid, HID_CONSUMER_PLAY_PAUSE); } }, true); } -static bool bt_hid_media_input_callback(InputEvent* event, void* context) { +static bool hid_media_input_callback(InputEvent* event, void* context) { furi_assert(context); - BtHidMedia* bt_hid_media = context; + HidMedia* hid_media = context; bool consumed = false; if(event->type == InputTypePress) { - bt_hid_media_process_press(bt_hid_media, event); + hid_media_process_press(hid_media, event); consumed = true; } else if(event->type == InputTypeRelease) { - bt_hid_media_process_release(bt_hid_media, event); + hid_media_process_release(hid_media, event); consumed = true; } else if(event->type == InputTypeShort) { if(event->key == InputKeyBack) { - furi_hal_bt_hid_consumer_key_release_all(); + hid_hal_consumer_key_release_all(hid_media->hid); } } return consumed; } -BtHidMedia* bt_hid_media_alloc() { - BtHidMedia* bt_hid_media = malloc(sizeof(BtHidMedia)); - bt_hid_media->view = view_alloc(); - view_set_context(bt_hid_media->view, bt_hid_media); - view_allocate_model(bt_hid_media->view, ViewModelTypeLocking, sizeof(BtHidMediaModel)); - view_set_draw_callback(bt_hid_media->view, bt_hid_media_draw_callback); - view_set_input_callback(bt_hid_media->view, bt_hid_media_input_callback); +HidMedia* hid_media_alloc(Hid* hid) { + HidMedia* hid_media = malloc(sizeof(HidMedia)); + hid_media->view = view_alloc(); + hid_media->hid = hid; + view_set_context(hid_media->view, hid_media); + view_allocate_model(hid_media->view, ViewModelTypeLocking, sizeof(HidMediaModel)); + view_set_draw_callback(hid_media->view, hid_media_draw_callback); + view_set_input_callback(hid_media->view, hid_media_input_callback); - return bt_hid_media; + return hid_media; } -void bt_hid_media_free(BtHidMedia* bt_hid_media) { - furi_assert(bt_hid_media); - view_free(bt_hid_media->view); - free(bt_hid_media); +void hid_media_free(HidMedia* hid_media) { + furi_assert(hid_media); + view_free(hid_media->view); + free(hid_media); } -View* bt_hid_media_get_view(BtHidMedia* bt_hid_media) { - furi_assert(bt_hid_media); - return bt_hid_media->view; +View* hid_media_get_view(HidMedia* hid_media) { + furi_assert(hid_media); + return hid_media->view; } -void bt_hid_media_set_connected_status(BtHidMedia* bt_hid_media, bool connected) { - furi_assert(bt_hid_media); +void hid_media_set_connected_status(HidMedia* hid_media, bool connected) { + furi_assert(hid_media); with_view_model( - bt_hid_media->view, BtHidMediaModel * model, { model->connected = connected; }, true); + hid_media->view, HidMediaModel * model, { model->connected = connected; }, true); } diff --git a/applications/plugins/hid_app/views/hid_media.h b/applications/plugins/hid_app/views/hid_media.h new file mode 100644 index 00000000000..4aa51dc173b --- /dev/null +++ b/applications/plugins/hid_app/views/hid_media.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +typedef struct HidMedia HidMedia; + +HidMedia* hid_media_alloc(); + +void hid_media_free(HidMedia* hid_media); + +View* hid_media_get_view(HidMedia* hid_media); + +void hid_media_set_connected_status(HidMedia* hid_media, bool connected); diff --git a/applications/plugins/bt_hid_app/views/bt_hid_mouse.c b/applications/plugins/hid_app/views/hid_mouse.c similarity index 68% rename from applications/plugins/bt_hid_app/views/bt_hid_mouse.c rename to applications/plugins/hid_app/views/hid_mouse.c index bd48bab1638..d1d76c15ab4 100644 --- a/applications/plugins/bt_hid_app/views/bt_hid_mouse.c +++ b/applications/plugins/hid_app/views/hid_mouse.c @@ -1,16 +1,15 @@ -#include "bt_hid_mouse.h" -#include -#include -#include +#include "hid_mouse.h" #include +#include "../hid.h" -#include "bt_hid_icons.h" +#include "hid_icons.h" -struct BtHidMouse { +#define TAG "HidMouse" + +struct HidMouse { View* view; + Hid* hid; }; -#define MOUSE_MOVE_SHORT 5 -#define MOUSE_MOVE_LONG 20 typedef struct { bool left_pressed; @@ -21,11 +20,11 @@ typedef struct { bool left_mouse_held; bool right_mouse_pressed; bool connected; -} BtHidMouseModel; +} HidMouseModel; -static void bt_hid_mouse_draw_callback(Canvas* canvas, void* context) { +static void hid_mouse_draw_callback(Canvas* canvas, void* context) { furi_assert(context); - BtHidMouseModel* model = context; + HidMouseModel* model = context; // Header if(model->connected) { @@ -103,15 +102,15 @@ static void bt_hid_mouse_draw_callback(Canvas* canvas, void* context) { } } -static void bt_hid_mouse_process(BtHidMouse* bt_hid_mouse, InputEvent* event) { +static void hid_mouse_process(HidMouse* hid_mouse, InputEvent* event) { with_view_model( - bt_hid_mouse->view, - BtHidMouseModel * model, + hid_mouse->view, + HidMouseModel * model, { if(event->key == InputKeyBack) { if(event->type == InputTypeShort) { - furi_hal_bt_hid_mouse_press(HID_MOUSE_BTN_RIGHT); - furi_hal_bt_hid_mouse_release(HID_MOUSE_BTN_RIGHT); + hid_hal_mouse_press(hid_mouse->hid, HID_MOUSE_BTN_RIGHT); + hid_hal_mouse_release(hid_mouse->hid, HID_MOUSE_BTN_RIGHT); } else if(event->type == InputTypePress) { model->right_mouse_pressed = true; } else if(event->type == InputTypeRelease) { @@ -120,11 +119,12 @@ static void bt_hid_mouse_process(BtHidMouse* bt_hid_mouse, InputEvent* event) { } else if(event->key == InputKeyOk) { if(event->type == InputTypeShort) { // Just release if it was being held before - if(!model->left_mouse_held) furi_hal_bt_hid_mouse_press(HID_MOUSE_BTN_LEFT); - furi_hal_bt_hid_mouse_release(HID_MOUSE_BTN_LEFT); + if(!model->left_mouse_held) + hid_hal_mouse_press(hid_mouse->hid, HID_MOUSE_BTN_LEFT); + hid_hal_mouse_release(hid_mouse->hid, HID_MOUSE_BTN_LEFT); model->left_mouse_held = false; } else if(event->type == InputTypeLong) { - furi_hal_bt_hid_mouse_press(HID_MOUSE_BTN_LEFT); + hid_hal_mouse_press(hid_mouse->hid, HID_MOUSE_BTN_LEFT); model->left_mouse_held = true; model->left_mouse_pressed = true; } else if(event->type == InputTypePress) { @@ -133,40 +133,39 @@ static void bt_hid_mouse_process(BtHidMouse* bt_hid_mouse, InputEvent* event) { // Only release if it wasn't a long press if(!model->left_mouse_held) model->left_mouse_pressed = false; } - } else if(event->key == InputKeyRight) { if(event->type == InputTypePress) { model->right_pressed = true; - furi_hal_bt_hid_mouse_move(MOUSE_MOVE_SHORT, 0); + hid_hal_mouse_move(hid_mouse->hid, MOUSE_MOVE_SHORT, 0); } else if(event->type == InputTypeRepeat) { - furi_hal_bt_hid_mouse_move(MOUSE_MOVE_LONG, 0); + hid_hal_mouse_move(hid_mouse->hid, MOUSE_MOVE_LONG, 0); } else if(event->type == InputTypeRelease) { model->right_pressed = false; } } else if(event->key == InputKeyLeft) { if(event->type == InputTypePress) { model->left_pressed = true; - furi_hal_bt_hid_mouse_move(-MOUSE_MOVE_SHORT, 0); + hid_hal_mouse_move(hid_mouse->hid, -MOUSE_MOVE_SHORT, 0); } else if(event->type == InputTypeRepeat) { - furi_hal_bt_hid_mouse_move(-MOUSE_MOVE_LONG, 0); + hid_hal_mouse_move(hid_mouse->hid, -MOUSE_MOVE_LONG, 0); } else if(event->type == InputTypeRelease) { model->left_pressed = false; } } else if(event->key == InputKeyDown) { if(event->type == InputTypePress) { model->down_pressed = true; - furi_hal_bt_hid_mouse_move(0, MOUSE_MOVE_SHORT); + hid_hal_mouse_move(hid_mouse->hid, 0, MOUSE_MOVE_SHORT); } else if(event->type == InputTypeRepeat) { - furi_hal_bt_hid_mouse_move(0, MOUSE_MOVE_LONG); + hid_hal_mouse_move(hid_mouse->hid, 0, MOUSE_MOVE_LONG); } else if(event->type == InputTypeRelease) { model->down_pressed = false; } } else if(event->key == InputKeyUp) { if(event->type == InputTypePress) { model->up_pressed = true; - furi_hal_bt_hid_mouse_move(0, -MOUSE_MOVE_SHORT); + hid_hal_mouse_move(hid_mouse->hid, 0, -MOUSE_MOVE_SHORT); } else if(event->type == InputTypeRepeat) { - furi_hal_bt_hid_mouse_move(0, -MOUSE_MOVE_LONG); + hid_hal_mouse_move(hid_mouse->hid, 0, -MOUSE_MOVE_LONG); } else if(event->type == InputTypeRelease) { model->up_pressed = false; } @@ -175,45 +174,46 @@ static void bt_hid_mouse_process(BtHidMouse* bt_hid_mouse, InputEvent* event) { true); } -static bool bt_hid_mouse_input_callback(InputEvent* event, void* context) { +static bool hid_mouse_input_callback(InputEvent* event, void* context) { furi_assert(context); - BtHidMouse* bt_hid_mouse = context; + HidMouse* hid_mouse = context; bool consumed = false; if(event->type == InputTypeLong && event->key == InputKeyBack) { - furi_hal_bt_hid_mouse_release_all(); + hid_hal_mouse_release_all(hid_mouse->hid); } else { - bt_hid_mouse_process(bt_hid_mouse, event); + hid_mouse_process(hid_mouse, event); consumed = true; } return consumed; } -BtHidMouse* bt_hid_mouse_alloc() { - BtHidMouse* bt_hid_mouse = malloc(sizeof(BtHidMouse)); - bt_hid_mouse->view = view_alloc(); - view_set_context(bt_hid_mouse->view, bt_hid_mouse); - view_allocate_model(bt_hid_mouse->view, ViewModelTypeLocking, sizeof(BtHidMouseModel)); - view_set_draw_callback(bt_hid_mouse->view, bt_hid_mouse_draw_callback); - view_set_input_callback(bt_hid_mouse->view, bt_hid_mouse_input_callback); +HidMouse* hid_mouse_alloc(Hid* hid) { + HidMouse* hid_mouse = malloc(sizeof(HidMouse)); + hid_mouse->view = view_alloc(); + hid_mouse->hid = hid; + view_set_context(hid_mouse->view, hid_mouse); + view_allocate_model(hid_mouse->view, ViewModelTypeLocking, sizeof(HidMouseModel)); + view_set_draw_callback(hid_mouse->view, hid_mouse_draw_callback); + view_set_input_callback(hid_mouse->view, hid_mouse_input_callback); - return bt_hid_mouse; + return hid_mouse; } -void bt_hid_mouse_free(BtHidMouse* bt_hid_mouse) { - furi_assert(bt_hid_mouse); - view_free(bt_hid_mouse->view); - free(bt_hid_mouse); +void hid_mouse_free(HidMouse* hid_mouse) { + furi_assert(hid_mouse); + view_free(hid_mouse->view); + free(hid_mouse); } -View* bt_hid_mouse_get_view(BtHidMouse* bt_hid_mouse) { - furi_assert(bt_hid_mouse); - return bt_hid_mouse->view; +View* hid_mouse_get_view(HidMouse* hid_mouse) { + furi_assert(hid_mouse); + return hid_mouse->view; } -void bt_hid_mouse_set_connected_status(BtHidMouse* bt_hid_mouse, bool connected) { - furi_assert(bt_hid_mouse); +void hid_mouse_set_connected_status(HidMouse* hid_mouse, bool connected) { + furi_assert(hid_mouse); with_view_model( - bt_hid_mouse->view, BtHidMouseModel * model, { model->connected = connected; }, true); + hid_mouse->view, HidMouseModel * model, { model->connected = connected; }, true); } diff --git a/applications/plugins/hid_app/views/hid_mouse.h b/applications/plugins/hid_app/views/hid_mouse.h new file mode 100644 index 00000000000..d9fb2fd88a7 --- /dev/null +++ b/applications/plugins/hid_app/views/hid_mouse.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +#define MOUSE_MOVE_SHORT 5 +#define MOUSE_MOVE_LONG 20 + +typedef struct Hid Hid; +typedef struct HidMouse HidMouse; + +HidMouse* hid_mouse_alloc(Hid* bt_hid); + +void hid_mouse_free(HidMouse* hid_mouse); + +View* hid_mouse_get_view(HidMouse* hid_mouse); + +void hid_mouse_set_connected_status(HidMouse* hid_mouse, bool connected); diff --git a/applications/plugins/bt_hid_app/views/bt_hid_tiktok.c b/applications/plugins/hid_app/views/hid_tiktok.c similarity index 59% rename from applications/plugins/bt_hid_app/views/bt_hid_tiktok.c rename to applications/plugins/hid_app/views/hid_tiktok.c index fe1005b2d69..42abf41489b 100644 --- a/applications/plugins/bt_hid_app/views/bt_hid_tiktok.c +++ b/applications/plugins/hid_app/views/hid_tiktok.c @@ -1,13 +1,14 @@ -#include "bt_hid_tiktok.h" -#include -#include -#include +#include "hid_tiktok.h" +#include "../hid.h" #include -#include "bt_hid_icons.h" +#include "hid_icons.h" -struct BtHidTikTok { +#define TAG "HidTikTok" + +struct HidTikTok { View* view; + Hid* hid; }; typedef struct { @@ -18,11 +19,11 @@ typedef struct { bool ok_pressed; bool connected; bool is_cursor_set; -} BtHidTikTokModel; +} HidTikTokModel; -static void bt_hid_tiktok_draw_callback(Canvas* canvas, void* context) { +static void hid_tiktok_draw_callback(Canvas* canvas, void* context) { furi_assert(context); - BtHidTikTokModel* model = context; + HidTikTokModel* model = context; // Header if(model->connected) { @@ -89,102 +90,104 @@ static void bt_hid_tiktok_draw_callback(Canvas* canvas, void* context) { elements_multiline_text_aligned(canvas, 13, 62, AlignLeft, AlignBottom, "Hold to exit"); } -static void bt_hid_tiktok_reset_cursor() { +static void hid_tiktok_reset_cursor(HidTikTok* hid_tiktok) { // Set cursor to the phone's left up corner // Delays to guarantee one packet per connection interval for(size_t i = 0; i < 8; i++) { - furi_hal_bt_hid_mouse_move(-127, -127); + hid_hal_mouse_move(hid_tiktok->hid, -127, -127); furi_delay_ms(50); } // Move cursor from the corner - furi_hal_bt_hid_mouse_move(20, 120); + hid_hal_mouse_move(hid_tiktok->hid, 20, 120); furi_delay_ms(50); } -static void bt_hid_tiktok_process_press(BtHidTikTokModel* model, InputEvent* event) { +static void + hid_tiktok_process_press(HidTikTok* hid_tiktok, HidTikTokModel* model, InputEvent* event) { if(event->key == InputKeyUp) { model->up_pressed = true; } else if(event->key == InputKeyDown) { model->down_pressed = true; } else if(event->key == InputKeyLeft) { model->left_pressed = true; - furi_hal_bt_hid_consumer_key_press(HID_CONSUMER_VOLUME_DECREMENT); + hid_hal_consumer_key_press(hid_tiktok->hid, HID_CONSUMER_VOLUME_DECREMENT); } else if(event->key == InputKeyRight) { model->right_pressed = true; - furi_hal_bt_hid_consumer_key_press(HID_CONSUMER_VOLUME_INCREMENT); + hid_hal_consumer_key_press(hid_tiktok->hid, HID_CONSUMER_VOLUME_INCREMENT); } else if(event->key == InputKeyOk) { model->ok_pressed = true; } } -static void bt_hid_tiktok_process_release(BtHidTikTokModel* model, InputEvent* event) { +static void + hid_tiktok_process_release(HidTikTok* hid_tiktok, HidTikTokModel* model, InputEvent* event) { if(event->key == InputKeyUp) { model->up_pressed = false; } else if(event->key == InputKeyDown) { model->down_pressed = false; } else if(event->key == InputKeyLeft) { model->left_pressed = false; - furi_hal_bt_hid_consumer_key_release(HID_CONSUMER_VOLUME_DECREMENT); + hid_hal_consumer_key_release(hid_tiktok->hid, HID_CONSUMER_VOLUME_DECREMENT); } else if(event->key == InputKeyRight) { model->right_pressed = false; - furi_hal_bt_hid_consumer_key_release(HID_CONSUMER_VOLUME_INCREMENT); + hid_hal_consumer_key_release(hid_tiktok->hid, HID_CONSUMER_VOLUME_INCREMENT); } else if(event->key == InputKeyOk) { model->ok_pressed = false; } } -static bool bt_hid_tiktok_input_callback(InputEvent* event, void* context) { +static bool hid_tiktok_input_callback(InputEvent* event, void* context) { furi_assert(context); - BtHidTikTok* bt_hid_tiktok = context; + HidTikTok* hid_tiktok = context; bool consumed = false; with_view_model( - bt_hid_tiktok->view, - BtHidTikTokModel * model, + hid_tiktok->view, + HidTikTokModel * model, { if(event->type == InputTypePress) { - bt_hid_tiktok_process_press(model, event); + hid_tiktok_process_press(hid_tiktok, model, event); if(model->connected && !model->is_cursor_set) { - bt_hid_tiktok_reset_cursor(); + hid_tiktok_reset_cursor(hid_tiktok); model->is_cursor_set = true; } consumed = true; } else if(event->type == InputTypeRelease) { - bt_hid_tiktok_process_release(model, event); + hid_tiktok_process_release(hid_tiktok, model, event); consumed = true; } else if(event->type == InputTypeShort) { if(event->key == InputKeyOk) { - furi_hal_bt_hid_mouse_press(HID_MOUSE_BTN_LEFT); + hid_hal_mouse_press(hid_tiktok->hid, HID_MOUSE_BTN_LEFT); furi_delay_ms(50); - furi_hal_bt_hid_mouse_release(HID_MOUSE_BTN_LEFT); + hid_hal_mouse_release(hid_tiktok->hid, HID_MOUSE_BTN_LEFT); furi_delay_ms(50); - furi_hal_bt_hid_mouse_press(HID_MOUSE_BTN_LEFT); + hid_hal_mouse_press(hid_tiktok->hid, HID_MOUSE_BTN_LEFT); furi_delay_ms(50); - furi_hal_bt_hid_mouse_release(HID_MOUSE_BTN_LEFT); + hid_hal_mouse_release(hid_tiktok->hid, HID_MOUSE_BTN_LEFT); consumed = true; } else if(event->key == InputKeyUp) { // Emulate up swipe - furi_hal_bt_hid_mouse_scroll(-6); - furi_hal_bt_hid_mouse_scroll(-12); - furi_hal_bt_hid_mouse_scroll(-19); - furi_hal_bt_hid_mouse_scroll(-12); - furi_hal_bt_hid_mouse_scroll(-6); + hid_hal_mouse_scroll(hid_tiktok->hid, -6); + hid_hal_mouse_scroll(hid_tiktok->hid, -12); + hid_hal_mouse_scroll(hid_tiktok->hid, -19); + hid_hal_mouse_scroll(hid_tiktok->hid, -12); + hid_hal_mouse_scroll(hid_tiktok->hid, -6); consumed = true; } else if(event->key == InputKeyDown) { // Emulate down swipe - furi_hal_bt_hid_mouse_scroll(6); - furi_hal_bt_hid_mouse_scroll(12); - furi_hal_bt_hid_mouse_scroll(19); - furi_hal_bt_hid_mouse_scroll(12); - furi_hal_bt_hid_mouse_scroll(6); + hid_hal_mouse_scroll(hid_tiktok->hid, 6); + hid_hal_mouse_scroll(hid_tiktok->hid, 12); + hid_hal_mouse_scroll(hid_tiktok->hid, 19); + hid_hal_mouse_scroll(hid_tiktok->hid, 12); + hid_hal_mouse_scroll(hid_tiktok->hid, 6); consumed = true; } else if(event->key == InputKeyBack) { - furi_hal_bt_hid_consumer_key_release_all(); + hid_hal_consumer_key_release_all(hid_tiktok->hid); consumed = true; } } else if(event->type == InputTypeLong) { if(event->key == InputKeyBack) { - furi_hal_bt_hid_consumer_key_release_all(); + hid_hal_consumer_key_release_all(hid_tiktok->hid); model->is_cursor_set = false; consumed = false; } @@ -195,33 +198,34 @@ static bool bt_hid_tiktok_input_callback(InputEvent* event, void* context) { return consumed; } -BtHidTikTok* bt_hid_tiktok_alloc() { - BtHidTikTok* bt_hid_tiktok = malloc(sizeof(BtHidTikTok)); - bt_hid_tiktok->view = view_alloc(); - view_set_context(bt_hid_tiktok->view, bt_hid_tiktok); - view_allocate_model(bt_hid_tiktok->view, ViewModelTypeLocking, sizeof(BtHidTikTokModel)); - view_set_draw_callback(bt_hid_tiktok->view, bt_hid_tiktok_draw_callback); - view_set_input_callback(bt_hid_tiktok->view, bt_hid_tiktok_input_callback); +HidTikTok* hid_tiktok_alloc(Hid* bt_hid) { + HidTikTok* hid_tiktok = malloc(sizeof(HidTikTok)); + hid_tiktok->hid = bt_hid; + hid_tiktok->view = view_alloc(); + view_set_context(hid_tiktok->view, hid_tiktok); + view_allocate_model(hid_tiktok->view, ViewModelTypeLocking, sizeof(HidTikTokModel)); + view_set_draw_callback(hid_tiktok->view, hid_tiktok_draw_callback); + view_set_input_callback(hid_tiktok->view, hid_tiktok_input_callback); - return bt_hid_tiktok; + return hid_tiktok; } -void bt_hid_tiktok_free(BtHidTikTok* bt_hid_tiktok) { - furi_assert(bt_hid_tiktok); - view_free(bt_hid_tiktok->view); - free(bt_hid_tiktok); +void hid_tiktok_free(HidTikTok* hid_tiktok) { + furi_assert(hid_tiktok); + view_free(hid_tiktok->view); + free(hid_tiktok); } -View* bt_hid_tiktok_get_view(BtHidTikTok* bt_hid_tiktok) { - furi_assert(bt_hid_tiktok); - return bt_hid_tiktok->view; +View* hid_tiktok_get_view(HidTikTok* hid_tiktok) { + furi_assert(hid_tiktok); + return hid_tiktok->view; } -void bt_hid_tiktok_set_connected_status(BtHidTikTok* bt_hid_tiktok, bool connected) { - furi_assert(bt_hid_tiktok); +void hid_tiktok_set_connected_status(HidTikTok* hid_tiktok, bool connected) { + furi_assert(hid_tiktok); with_view_model( - bt_hid_tiktok->view, - BtHidTikTokModel * model, + hid_tiktok->view, + HidTikTokModel * model, { model->connected = connected; model->is_cursor_set = false; diff --git a/applications/plugins/hid_app/views/hid_tiktok.h b/applications/plugins/hid_app/views/hid_tiktok.h new file mode 100644 index 00000000000..b2efc3692d3 --- /dev/null +++ b/applications/plugins/hid_app/views/hid_tiktok.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +typedef struct Hid Hid; +typedef struct HidTikTok HidTikTok; + +HidTikTok* hid_tiktok_alloc(Hid* bt_hid); + +void hid_tiktok_free(HidTikTok* hid_tiktok); + +View* hid_tiktok_get_view(HidTikTok* hid_tiktok); + +void hid_tiktok_set_connected_status(HidTikTok* hid_tiktok, bool connected); diff --git a/scripts/fbt/appmanifest.py b/scripts/fbt/appmanifest.py index 908a64a6fee..a7460d889df 100644 --- a/scripts/fbt/appmanifest.py +++ b/scripts/fbt/appmanifest.py @@ -64,6 +64,7 @@ class Library: fap_author: str = "" fap_weburl: str = "" fap_icon_assets: Optional[str] = None + fap_icon_assets_symbol: Optional[str] = None fap_extbuild: List[ExternallyBuiltFile] = field(default_factory=list) fap_private_libs: List[Library] = field(default_factory=list) # Internally used by fbt diff --git a/scripts/fbt_tools/fbt_extapps.py b/scripts/fbt_tools/fbt_extapps.py index a4116e51329..f0015cf2559 100644 --- a/scripts/fbt_tools/fbt_extapps.py +++ b/scripts/fbt_tools/fbt_extapps.py @@ -60,7 +60,7 @@ def BuildAppElf(env, app): fap_icons = app_env.CompileIcons( app_env.Dir(app_work_dir), app._appdir.Dir(app.fap_icon_assets), - icon_bundle_name=f"{app.appid}_icons", + icon_bundle_name=f"{app.fap_icon_assets_symbol if app.fap_icon_assets_symbol else app.appid }_icons", ) app_env.Alias("_fap_icons", fap_icons) From e121e6a2877857ec19a027ed361579eaaf2911bd Mon Sep 17 00:00:00 2001 From: Maksim Derbasov Date: Tue, 29 Nov 2022 01:51:51 +0900 Subject: [PATCH 239/824] Fix for spelling (#2051) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix for spelling * Review iteration Co-authored-by: あく --- CONTRIBUTING.md | 6 +++--- applications/debug/accessor/helpers/wiegand.cpp | 2 +- applications/debug/bt_debug_app/bt_debug_app.c | 2 +- applications/debug/display_test/display_test.c | 2 +- applications/debug/unit_tests/furi/furi_memmgr_test.c | 4 ++-- applications/debug/unit_tests/nfc/nfc_test.c | 2 +- applications/debug/unit_tests/stream/stream_test.c | 6 +++--- applications/main/archive/helpers/archive_browser.h | 2 +- applications/main/fap_loader/elf_cpp/elf_hashtable.cpp | 2 +- .../helpers/subghz_frequency_analyzer_log_item_array.h | 2 +- applications/services/applications.h | 2 +- .../services/desktop/animations/animation_manager.h | 6 +++--- applications/services/gui/elements.h | 2 +- applications/services/gui/modules/dialog_ex.h | 8 ++++---- applications/services/gui/modules/popup.h | 4 ++-- applications/services/gui/modules/text_box.c | 2 +- applications/services/gui/modules/text_input.h | 2 +- applications/services/gui/view_dispatcher.c | 2 +- applications/services/input/input.h | 4 ++-- applications/services/power/power_service/power.h | 2 +- applications/services/rpc/rpc.h | 2 +- applications/services/rpc/rpc_storage.c | 2 +- applications/services/storage/filesystem_api_defines.h | 4 ++-- applications/services/storage/filesystem_api_internal.h | 2 +- applications/services/storage/storage.h | 6 +++--- applications/settings/storage_settings/storage_settings.h | 2 +- .../system/storage_move_to_sd/storage_move_to_sd.h | 2 +- documentation/fbt.md | 2 +- fbt_options.py | 2 +- furi/core/log.c | 8 ++++---- furi/core/memmgr.h | 2 +- furi/core/memmgr_heap.h | 2 +- furi/core/thread.h | 4 ++-- furi/core/valuemutex.c | 2 +- furi/core/valuemutex.h | 4 ++-- furi/furi.c | 6 +++--- 36 files changed, 58 insertions(+), 58 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 00a0191e5ed..dc1d41e27d1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,7 @@ Thank you for investing your time in contributing to our project! -Read our [Code of Coduct](CODE_OF_CONDUCT.md) to keep our community approachable and respectable. +Read our [Code of Conduct](CODE_OF_CONDUCT.md) to keep our community approachable and respectable. In this guide you will get an overview of the contribution workflow from opening an issue, creating a PR, reviewing, and merging the PR. @@ -17,12 +17,12 @@ See the [ReadMe](ReadMe.md) to get an overview of the project. Here are some hel ## Getting started -Before writing code and creating PR make sure that it aligns with our mission and guidlines: +Before writing code and creating PR make sure that it aligns with our mission and guidelines: - All our devices are intended for research and education. - PR that contains code intended to commit crimes is not going to be accepted. - Your PR must comply with our [Coding Style](CODING_STYLE.md) -- Your PR must contain code compatiable with project [LICENSE](LICENSE). +- Your PR must contain code compatible with project [LICENSE](LICENSE). - PR will only be merged if it pass CI/CD. - PR will only be merged if it pass review by code owner. diff --git a/applications/debug/accessor/helpers/wiegand.cpp b/applications/debug/accessor/helpers/wiegand.cpp index 79c9f723b2c..bb288554991 100644 --- a/applications/debug/accessor/helpers/wiegand.cpp +++ b/applications/debug/accessor/helpers/wiegand.cpp @@ -71,7 +71,7 @@ void WIEGAND::end() { } void WIEGAND::ReadD0() { - _bitCount++; // Increament bit count for Interrupt connected to D0 + _bitCount++; // Increment bit count for Interrupt connected to D0 if(_bitCount > 31) // If bit count more than 31, process high bits { _cardTempHigh |= ((0x80000000 & _cardTemp) >> 31); // shift value to high bits diff --git a/applications/debug/bt_debug_app/bt_debug_app.c b/applications/debug/bt_debug_app/bt_debug_app.c index ac442de0a29..405051a4aca 100644 --- a/applications/debug/bt_debug_app/bt_debug_app.c +++ b/applications/debug/bt_debug_app/bt_debug_app.c @@ -98,7 +98,7 @@ void bt_debug_app_free(BtDebugApp* app) { int32_t bt_debug_app(void* p) { UNUSED(p); if(!furi_hal_bt_is_testing_supported()) { - FURI_LOG_E(TAG, "Incorrect radio stack: radio testing fetures are absent."); + FURI_LOG_E(TAG, "Incorrect radio stack: radio testing features are absent."); DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); dialog_message_show_storage_error(dialogs, "Incorrect\nRadioStack"); return 255; diff --git a/applications/debug/display_test/display_test.c b/applications/debug/display_test/display_test.c index e7f366cbb3a..5b46d2b4110 100644 --- a/applications/debug/display_test/display_test.c +++ b/applications/debug/display_test/display_test.c @@ -145,7 +145,7 @@ DisplayTest* display_test_alloc() { view_set_previous_callback(view, display_test_previous_callback); view_dispatcher_add_view(instance->view_dispatcher, DisplayTestViewConfigure, view); - // Configurtion items + // Configuration items VariableItem* item; instance->config_bias = false; instance->config_contrast = 32; diff --git a/applications/debug/unit_tests/furi/furi_memmgr_test.c b/applications/debug/unit_tests/furi/furi_memmgr_test.c index b0fd060cf74..cf3848747a8 100644 --- a/applications/debug/unit_tests/furi/furi_memmgr_test.c +++ b/applications/debug/unit_tests/furi/furi_memmgr_test.c @@ -11,7 +11,7 @@ extern size_t memmgr_get_free_heap(void); extern size_t memmgr_get_minimum_free_heap(void); -// current heap managment realization consume: +// current heap management realization consume: // X bytes after allocate and 0 bytes after allocate and free, // where X = sizeof(void*) + sizeof(size_t), look to BlockLink_t const size_t heap_overhead_max_size = sizeof(void*) + sizeof(size_t); @@ -21,7 +21,7 @@ bool heap_equal(size_t heap_size, size_t heap_size_old) { const size_t heap_low = heap_size_old - heap_overhead_max_size; const size_t heap_high = heap_size_old + heap_overhead_max_size; - // not extact, so we must test it against bigger numbers than "overhead size" + // not exact, so we must test it against bigger numbers than "overhead size" const bool result = ((heap_size >= heap_low) && (heap_size <= heap_high)); // debug allocation info diff --git a/applications/debug/unit_tests/nfc/nfc_test.c b/applications/debug/unit_tests/nfc/nfc_test.c index 454c11c0f62..07ec73a0317 100644 --- a/applications/debug/unit_tests/nfc/nfc_test.c +++ b/applications/debug/unit_tests/nfc/nfc_test.c @@ -136,7 +136,7 @@ static bool nfc_test_digital_signal_test_encode( ref_timings_sum += ref[i]; if(timings_diff > timing_tolerance) { FURI_LOG_E( - TAG, "Too big differece in %d timings. Ref: %ld, DUT: %ld", i, ref[i], dut[i]); + TAG, "Too big difference in %d timings. Ref: %ld, DUT: %ld", i, ref[i], dut[i]); timing_check_success = false; break; } diff --git a/applications/debug/unit_tests/stream/stream_test.c b/applications/debug/unit_tests/stream/stream_test.c index c04e119c626..802e340259b 100644 --- a/applications/debug/unit_tests/stream/stream_test.c +++ b/applications/debug/unit_tests/stream/stream_test.c @@ -219,21 +219,21 @@ MU_TEST_1(stream_composite_subtest, Stream* stream) { mu_check(stream_eof(stream)); mu_assert_int_eq(0, stream_tell(stream)); - // insert formated string at the end of stream + // insert formatted string at the end of stream // "" -> "dio666" mu_check(stream_insert_format(stream, "%s%d", "dio", 666)); mu_assert_int_eq(6, stream_size(stream)); mu_check(stream_eof(stream)); mu_assert_int_eq(6, stream_tell(stream)); - // insert formated string at the end of stream + // insert formatted string at the end of stream // "dio666" -> "dio666zlo555" mu_check(stream_insert_format(stream, "%s%d", "zlo", 555)); mu_assert_int_eq(12, stream_size(stream)); mu_check(stream_eof(stream)); mu_assert_int_eq(12, stream_tell(stream)); - // insert formated string at the 6 pos + // insert formatted string at the 6 pos // "dio666" -> "dio666baba13zlo555" mu_check(stream_seek(stream, 6, StreamOffsetFromStart)); mu_check(stream_insert_format(stream, "%s%d", "baba", 13)); diff --git a/applications/main/archive/helpers/archive_browser.h b/applications/main/archive/helpers/archive_browser.h index 519a34a2d0f..1a7e01f5ab3 100644 --- a/applications/main/archive/helpers/archive_browser.h +++ b/applications/main/archive/helpers/archive_browser.h @@ -3,7 +3,7 @@ #include "../archive_i.h" #include -#define TAB_RIGHT InputKeyRight // Default tab swith direction +#define TAB_RIGHT InputKeyRight // Default tab switch direction #define TAB_DEFAULT ArchiveTabFavorites // Start tab #define FILE_LIST_BUF_LEN 100 diff --git a/applications/main/fap_loader/elf_cpp/elf_hashtable.cpp b/applications/main/fap_loader/elf_cpp/elf_hashtable.cpp index f8adbf9d8d8..e845ed008d6 100644 --- a/applications/main/fap_loader/elf_cpp/elf_hashtable.cpp +++ b/applications/main/fap_loader/elf_cpp/elf_hashtable.cpp @@ -31,7 +31,7 @@ bool elf_resolve_from_hashtable(const char* name, Elf32_Addr* address) { auto find_res = std::lower_bound(elf_api_table.cbegin(), elf_api_table.cend(), key); if((find_res == elf_api_table.cend() || (find_res->hash != gnu_sym_hash))) { - FURI_LOG_W(TAG, "Cant find symbol '%s' (hash %lx)!", name, gnu_sym_hash); + FURI_LOG_W(TAG, "Can't find symbol '%s' (hash %lx)!", name, gnu_sym_hash); result = false; } else { result = true; diff --git a/applications/main/subghz/helpers/subghz_frequency_analyzer_log_item_array.h b/applications/main/subghz/helpers/subghz_frequency_analyzer_log_item_array.h index eaf53b6639b..b94ebe3809d 100644 --- a/applications/main/subghz/helpers/subghz_frequency_analyzer_log_item_array.h +++ b/applications/main/subghz/helpers/subghz_frequency_analyzer_log_item_array.h @@ -25,7 +25,7 @@ TUPLE_DEF2( (frequency, uint32_t), (count, uint8_t), (rssi_max, uint8_t)) -/* Register globaly the oplist */ +/* Register globally the oplist */ #define M_OPL_SubGhzFrequencyAnalyzerLogItem_t() \ TUPLE_OPLIST(SubGhzFrequencyAnalyzerLogItem, M_POD_OPLIST, M_DEFAULT_OPLIST, M_DEFAULT_OPLIST) diff --git a/applications/services/applications.h b/applications/services/applications.h index 012e80ddb67..acbfea31252 100644 --- a/applications/services/applications.h +++ b/applications/services/applications.h @@ -56,7 +56,7 @@ extern const size_t FLIPPER_DEBUG_APPS_COUNT; extern const FlipperApplication FLIPPER_SYSTEM_APPS[]; extern const size_t FLIPPER_SYSTEM_APPS_COUNT; -/* Seperate scene app holder +/* Separate scene app holder * Spawned by loader */ extern const FlipperApplication FLIPPER_SCENE; diff --git a/applications/services/desktop/animations/animation_manager.h b/applications/services/desktop/animations/animation_manager.h index 9802c4f1f62..234d20de038 100644 --- a/applications/services/desktop/animations/animation_manager.h +++ b/applications/services/desktop/animations/animation_manager.h @@ -69,7 +69,7 @@ View* animation_manager_get_animation_view(AnimationManager* animation_manager); void animation_manager_set_context(AnimationManager* animation_manager, void* context); /** - * Set callback for Animation Manager for defered calls + * Set callback for Animation Manager for deferred calls * for animation_manager_new_idle_process(). * Animation Manager doesn't have it's own thread, so main thread gives * callbacks to A.M. to call when it should perform some inner manipulations. @@ -96,7 +96,7 @@ void animation_manager_set_new_idle_callback( void animation_manager_new_idle_process(AnimationManager* animation_manager); /** - * Set callback for Animation Manager for defered calls + * Set callback for Animation Manager for deferred calls * for animation_manager_check_blocking_process(). * * @animation_manager instance @@ -115,7 +115,7 @@ void animation_manager_set_check_callback( void animation_manager_check_blocking_process(AnimationManager* animation_manager); /** - * Set callback for Animation Manager for defered calls + * Set callback for Animation Manager for deferred calls * for animation_manager_interact_process(). * * @animation_manager instance diff --git a/applications/services/gui/elements.h b/applications/services/gui/elements.h index b2d204de7e2..9f155402b7f 100644 --- a/applications/services/gui/elements.h +++ b/applications/services/gui/elements.h @@ -90,7 +90,7 @@ void elements_button_center(Canvas* canvas, const char* str); * * @param canvas Canvas instance * @param x, y coordinates based on align param - * @param horizontal, vertical aligment of multiline text + * @param horizontal, vertical alignment of multiline text * @param text string (possible multiline) */ void elements_multiline_text_aligned( diff --git a/applications/services/gui/modules/dialog_ex.h b/applications/services/gui/modules/dialog_ex.h index 4c6094239ea..26a46535450 100644 --- a/applications/services/gui/modules/dialog_ex.h +++ b/applications/services/gui/modules/dialog_ex.h @@ -76,8 +76,8 @@ void dialog_ex_set_context(DialogEx* dialog_ex, void* context); * @param text text to be shown, can be multiline * @param x x position * @param y y position - * @param horizontal horizontal text aligment - * @param vertical vertical text aligment + * @param horizontal horizontal text alignment + * @param vertical vertical text alignment */ void dialog_ex_set_header( DialogEx* dialog_ex, @@ -95,8 +95,8 @@ void dialog_ex_set_header( * @param text text to be shown, can be multiline * @param x x position * @param y y position - * @param horizontal horizontal text aligment - * @param vertical vertical text aligment + * @param horizontal horizontal text alignment + * @param vertical vertical text alignment */ void dialog_ex_set_text( DialogEx* dialog_ex, diff --git a/applications/services/gui/modules/popup.h b/applications/services/gui/modules/popup.h index 94f49a2ba67..13371a05d4a 100644 --- a/applications/services/gui/modules/popup.h +++ b/applications/services/gui/modules/popup.h @@ -64,7 +64,7 @@ void popup_set_context(Popup* popup, void* context); * @param x x position * @param y y position * @param horizontal horizontal alignment - * @param vertical vertical aligment + * @param vertical vertical alignment */ void popup_set_header( Popup* popup, @@ -83,7 +83,7 @@ void popup_set_header( * @param x x position * @param y y position * @param horizontal horizontal alignment - * @param vertical vertical aligment + * @param vertical vertical alignment */ void popup_set_text( Popup* popup, diff --git a/applications/services/gui/modules/text_box.c b/applications/services/gui/modules/text_box.c index 99d7d04f1b9..079a1294d83 100644 --- a/applications/services/gui/modules/text_box.c +++ b/applications/services/gui/modules/text_box.c @@ -43,7 +43,7 @@ static void text_box_process_up(TextBox* text_box) { model->scroll_pos--; // Reach last symbol of previous line model->text_pos--; - // Search prevous line start + // Search previous line start while((model->text_pos != model->text) && (*(--model->text_pos) != '\n')) ; if(*model->text_pos == '\n') { diff --git a/applications/services/gui/modules/text_input.h b/applications/services/gui/modules/text_input.h index 893fbd5330a..218df3141df 100644 --- a/applications/services/gui/modules/text_input.h +++ b/applications/services/gui/modules/text_input.h @@ -1,6 +1,6 @@ /** * @file text_input.h - * GUI: TextInput keybord view module API + * GUI: TextInput keyboard view module API */ #pragma once diff --git a/applications/services/gui/view_dispatcher.c b/applications/services/gui/view_dispatcher.c index 4034cc0b40b..046958749cc 100644 --- a/applications/services/gui/view_dispatcher.c +++ b/applications/services/gui/view_dispatcher.c @@ -152,7 +152,7 @@ void view_dispatcher_remove_view(ViewDispatcher* view_dispatcher, uint32_t view_ if(view_dispatcher->current_view == view) { view_dispatcher_set_current_view(view_dispatcher, NULL); } - // Check if view is recieving input + // Check if view is receiving input if(view_dispatcher->ongoing_input_view == view) { view_dispatcher->ongoing_input_view = NULL; } diff --git a/applications/services/input/input.h b/applications/services/input/input.h index 172b16361fd..ec3d09711ed 100644 --- a/applications/services/input/input.h +++ b/applications/services/input/input.h @@ -20,8 +20,8 @@ typedef enum { InputTypePress, /**< Press event, emitted after debounce */ InputTypeRelease, /**< Release event, emitted after debounce */ InputTypeShort, /**< Short event, emitted after InputTypeRelease done withing INPUT_LONG_PRESS interval */ - InputTypeLong, /**< Long event, emmited after INPUT_LONG_PRESS interval, asynchronouse to InputTypeRelease */ - InputTypeRepeat, /**< Repeat event, emmited with INPUT_REPEATE_PRESS period after InputTypeLong event */ + InputTypeLong, /**< Long event, emitted after INPUT_LONG_PRESS_COUNTS interval, asynchronous to InputTypeRelease */ + InputTypeRepeat, /**< Repeat event, emitted with INPUT_LONG_PRESS_COUNTS period after InputTypeLong event */ InputTypeMAX, /**< Special value for exceptional */ } InputType; diff --git a/applications/services/power/power_service/power.h b/applications/services/power/power_service/power.h index bdf5fa52974..32433ebcaee 100644 --- a/applications/services/power/power_service/power.h +++ b/applications/services/power/power_service/power.h @@ -86,7 +86,7 @@ FuriPubSub* power_get_pubsub(Power* power); */ bool power_is_battery_healthy(Power* power); -/** Enable or disable battery low level notification mesage +/** Enable or disable battery low level notification message * * @param power Power instance * @param enable true - enable, false - disable diff --git a/applications/services/rpc/rpc.h b/applications/services/rpc/rpc.h index 40493949daf..21836d9a472 100644 --- a/applications/services/rpc/rpc.h +++ b/applications/services/rpc/rpc.h @@ -25,7 +25,7 @@ typedef void (*RpcSendBytesCallback)(void* context, uint8_t* bytes, size_t bytes typedef void (*RpcBufferIsEmptyCallback)(void* context); /** Callback to notify transport layer that close_session command * is received. Any other actions lays on transport layer. - * No destruction or session close preformed. */ + * No destruction or session close performed. */ typedef void (*RpcSessionClosedCallback)(void* context); /** Callback to notify transport layer that session was closed * and all operations were finished */ diff --git a/applications/services/rpc/rpc_storage.c b/applications/services/rpc/rpc_storage.c index 16e343fcef5..3c6ff7f94d5 100644 --- a/applications/services/rpc/rpc_storage.c +++ b/applications/services/rpc/rpc_storage.c @@ -330,7 +330,7 @@ static void rpc_system_storage_read_process(const PB_Main* request, void* contex rpc_system_storage_reset_state(rpc_storage, session, true); - /* use same message memory to send reponse */ + /* use same message memory to send response */ PB_Main* response = malloc(sizeof(PB_Main)); const char* path = request->content.storage_read_request.path; Storage* fs_api = furi_record_open(RECORD_STORAGE); diff --git a/applications/services/storage/filesystem_api_defines.h b/applications/services/storage/filesystem_api_defines.h index b6f1d8f1306..b73e6eb33dc 100644 --- a/applications/services/storage/filesystem_api_defines.h +++ b/applications/services/storage/filesystem_api_defines.h @@ -25,13 +25,13 @@ typedef enum { typedef enum { FSE_OK, /**< No error */ FSE_NOT_READY, /**< FS not ready */ - FSE_EXIST, /**< File/Dir alrady exist */ + FSE_EXIST, /**< File/Dir already exist */ FSE_NOT_EXIST, /**< File/Dir does not exist */ FSE_INVALID_PARAMETER, /**< Invalid API parameter */ FSE_DENIED, /**< Access denied */ FSE_INVALID_NAME, /**< Invalid name/path */ FSE_INTERNAL, /**< Internal error */ - FSE_NOT_IMPLEMENTED, /**< Functon not implemented */ + FSE_NOT_IMPLEMENTED, /**< Function not implemented */ FSE_ALREADY_OPEN, /**< File/Dir already opened */ } FS_Error; diff --git a/applications/services/storage/filesystem_api_internal.h b/applications/services/storage/filesystem_api_internal.h index bd4bcf823a6..967d3bb41c1 100644 --- a/applications/services/storage/filesystem_api_internal.h +++ b/applications/services/storage/filesystem_api_internal.h @@ -17,7 +17,7 @@ typedef enum { struct File { uint32_t file_id; /**< File ID for internal references */ FileType type; - FS_Error error_id; /**< Standart API error from FS_Error enum */ + FS_Error error_id; /**< Standard API error from FS_Error enum */ int32_t internal_error_id; /**< Internal API error value */ void* storage; }; diff --git a/applications/services/storage/storage.h b/applications/services/storage/storage.h index 9c133e9be19..e093cbe0f46 100644 --- a/applications/services/storage/storage.h +++ b/applications/services/storage/storage.h @@ -255,19 +255,19 @@ FS_Error storage_common_fs_info( const char* storage_error_get_desc(FS_Error error_id); /** Retrieves the error id from the file object - * @param file pointer to file object. Pointer must not point to NULL. YOU CANNOT RETREIVE THE ERROR ID IF THE FILE HAS BEEN CLOSED + * @param file pointer to file object. Pointer must not point to NULL. YOU CANNOT RETRIEVE THE ERROR ID IF THE FILE HAS BEEN CLOSED * @return FS_Error error id */ FS_Error storage_file_get_error(File* file); /** Retrieves the internal (storage-specific) error id from the file object - * @param file pointer to file object. Pointer must not point to NULL. YOU CANNOT RETREIVE THE INTERNAL ERROR ID IF THE FILE HAS BEEN CLOSED + * @param file pointer to file object. Pointer must not point to NULL. YOU CANNOT RETRIEVE THE INTERNAL ERROR ID IF THE FILE HAS BEEN CLOSED * @return FS_Error error id */ int32_t storage_file_get_internal_error(File* file); /** Retrieves the error text from the file object - * @param file pointer to file object. Pointer must not point to NULL. YOU CANNOT RETREIVE THE ERROR TEXT IF THE FILE HAS BEEN CLOSED + * @param file pointer to file object. Pointer must not point to NULL. YOU CANNOT RETRIEVE THE ERROR TEXT IF THE FILE HAS BEEN CLOSED * @return const char* error text */ const char* storage_file_get_error_desc(File* file); diff --git a/applications/settings/storage_settings/storage_settings.h b/applications/settings/storage_settings/storage_settings.h index 664e74c848d..fd841623e68 100644 --- a/applications/settings/storage_settings/storage_settings.h +++ b/applications/settings/storage_settings/storage_settings.h @@ -26,7 +26,7 @@ typedef struct { NotificationApp* notification; Storage* fs_api; - // view managment + // view management SceneManager* scene_manager; ViewDispatcher* view_dispatcher; diff --git a/applications/system/storage_move_to_sd/storage_move_to_sd.h b/applications/system/storage_move_to_sd/storage_move_to_sd.h index dc1d669b519..a62d87c1f22 100644 --- a/applications/system/storage_move_to_sd/storage_move_to_sd.h +++ b/applications/system/storage_move_to_sd/storage_move_to_sd.h @@ -30,7 +30,7 @@ typedef struct { Widget* widget; NotificationApp* notifications; - // view managment + // view management SceneManager* scene_manager; ViewDispatcher* view_dispatcher; diff --git a/documentation/fbt.md b/documentation/fbt.md index 2bf9ea28e05..4726268d076 100644 --- a/documentation/fbt.md +++ b/documentation/fbt.md @@ -44,7 +44,7 @@ To run cleanup (think of `make clean`) for specified targets, add `-c` option. - `fw_dist` - build & publish firmware to `dist` folder. This is a default target, when no other are specified - `fap_dist` - build external plugins & publish to `dist` folder -- `updater_package`, `updater_minpackage` - build self-update package. Minimal version only inclues firmware's DFU file; full version also includes radio stack & resources for SD card +- `updater_package`, `updater_minpackage` - build self-update package. Minimal version only includes firmware's DFU file; full version also includes radio stack & resources for SD card - `copro_dist` - bundle Core2 FUS+stack binaries for qFlipper - `flash` - flash attached device with OpenOCD over ST-Link - `flash_usb`, `flash_usb_full` - build, upload and install update package to device over USB. See details on `updater_package`, `updater_minpackage` diff --git a/fbt_options.py b/fbt_options.py index a00f7c1b680..7a805c996ab 100644 --- a/fbt_options.py +++ b/fbt_options.py @@ -32,7 +32,7 @@ # Leave 0 to let scripts automatically calculate it COPRO_STACK_ADDR = "0x0" -# If you override COPRO_CUBE_DIR on commandline, override this aswell +# If you override COPRO_CUBE_DIR on commandline, override this as well COPRO_STACK_BIN_DIR = posixpath.join( COPRO_CUBE_DIR, "Projects", diff --git a/furi/core/log.c b/furi/core/log.c index bb163c9566a..a3967ed9282 100644 --- a/furi/core/log.c +++ b/furi/core/log.c @@ -8,7 +8,7 @@ typedef struct { FuriLogLevel log_level; FuriLogPuts puts; - FuriLogTimestamp timetamp; + FuriLogTimestamp timestamp; FuriMutex* mutex; } FuriLogParams; @@ -18,7 +18,7 @@ void furi_log_init() { // Set default logging parameters furi_log.log_level = FURI_LOG_LEVEL_DEFAULT; furi_log.puts = furi_hal_console_puts; - furi_log.timetamp = furi_get_tick; + furi_log.timestamp = furi_get_tick; furi_log.mutex = furi_mutex_alloc(FuriMutexTypeNormal); } @@ -59,7 +59,7 @@ void furi_log_print_format(FuriLogLevel level, const char* tag, const char* form furi_string_printf( string, "%lu %s[%s][%s] " FURI_LOG_CLR_RESET, - furi_log.timetamp(), + furi_log.timestamp(), color, log_letter, tag); @@ -98,5 +98,5 @@ void furi_log_set_puts(FuriLogPuts puts) { void furi_log_set_timestamp(FuriLogTimestamp timestamp) { furi_assert(timestamp); - furi_log.timetamp = timestamp; + furi_log.timestamp = timestamp; } diff --git a/furi/core/memmgr.h b/furi/core/memmgr.h index d3ad0c87c95..bc0c35faa71 100644 --- a/furi/core/memmgr.h +++ b/furi/core/memmgr.h @@ -1,6 +1,6 @@ /** * @file memmgr.h - * Furi: memory managment API and glue + * Furi: memory management API and glue */ #pragma once diff --git a/furi/core/memmgr_heap.h b/furi/core/memmgr_heap.h index 1521fce425e..9aacba1ca79 100644 --- a/furi/core/memmgr_heap.h +++ b/furi/core/memmgr_heap.h @@ -1,6 +1,6 @@ /** * @file memmgr_heap.h - * Furi: heap memory managment API and allocator + * Furi: heap memory management API and allocator */ #pragma once diff --git a/furi/core/thread.h b/furi/core/thread.h index 6ec8ebfec40..c2f5a91303b 100644 --- a/furi/core/thread.h +++ b/furi/core/thread.h @@ -48,7 +48,7 @@ typedef int32_t (*FuriThreadCallback)(void* context); */ typedef void (*FuriThreadStdoutWriteCallback)(const char* data, size_t size); -/** FuriThread state change calback called upon thread state change +/** FuriThread state change callback called upon thread state change * @param state new thread state * @param context callback context */ @@ -194,7 +194,7 @@ size_t furi_thread_get_heap_size(FuriThread* thread); */ int32_t furi_thread_get_return_code(FuriThread* thread); -/** Thread releated methods that doesn't involve FuriThread directly */ +/** Thread related methods that doesn't involve FuriThread directly */ /** Get FreeRTOS FuriThreadId for current thread * diff --git a/furi/core/valuemutex.c b/furi/core/valuemutex.c index af2a0755c24..bf4e6130bd5 100644 --- a/furi/core/valuemutex.c +++ b/furi/core/valuemutex.c @@ -4,7 +4,7 @@ bool init_mutex(ValueMutex* valuemutex, void* value, size_t size) { // mutex without name, - // no attributes (unfortunatly robust mutex is not supported by FreeRTOS), + // no attributes (unfortunately robust mutex is not supported by FreeRTOS), // with dynamic memory allocation valuemutex->mutex = furi_mutex_alloc(FuriMutexTypeNormal); if(valuemutex->mutex == NULL) return false; diff --git a/furi/core/valuemutex.h b/furi/core/valuemutex.h index 41762fdd392..0d867a1df21 100644 --- a/furi/core/valuemutex.h +++ b/furi/core/valuemutex.h @@ -39,7 +39,7 @@ bool delete_mutex(ValueMutex* valuemutex); void* acquire_mutex(ValueMutex* valuemutex, uint32_t timeout); /** - * Helper: infinitly wait for mutex + * Helper: infinitely wait for mutex */ static inline void* acquire_mutex_block(ValueMutex* valuemutex) { return acquire_mutex(valuemutex, FuriWaitForever); @@ -135,7 +135,7 @@ void consumer_app(void* _p) { flapp_exit(NULL); } - // continously read value every 1s + // continuously read value every 1s uint32_t counter; while(1) { if(read_mutex(counter_mutex, &counter, sizeof(counter), OsWaitForever)) { diff --git a/furi/furi.c b/furi/furi.c index 76aed024fa3..a616bce63c1 100644 --- a/furi/furi.c +++ b/furi/furi.c @@ -15,9 +15,9 @@ void furi_run() { furi_assert(xTaskGetSchedulerState() == taskSCHEDULER_NOT_STARTED); #if(__ARM_ARCH_7A__ == 0U) - /* Service Call interrupt might be configured before kernel start */ - /* and when its priority is lower or equal to BASEPRI, svc intruction */ - /* causes a Hard Fault. */ + /* Service Call interrupt might be configured before kernel start */ + /* and when its priority is lower or equal to BASEPRI, svc instruction */ + /* causes a Hard Fault. */ NVIC_SetPriority(SVCall_IRQn, 0U); #endif From a82c3ccc2258271deb2cd9f7a0c2b44eded3035c Mon Sep 17 00:00:00 2001 From: Stephen Kent Date: Mon, 28 Nov 2022 09:05:24 -0800 Subject: [PATCH 240/824] NFC: Fix MIFARE DESfire info action to open app menu (#2058) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When examining a MIFARE DESfire card, selecting "More >" currently leads to the file menu for a single application even if the scanned card contains multiple applications. On examining the source code, a MIFARE DESfire application selection menu is present as `NfcSceneMfDesfireData`. This change updates the MIFARE DESfire Info "More >" action to open the application selection menu. That menu may then be used to open the file selection menu for any application in the read MIFARE DESfire card data. Tested interactively with https://github.com/flipperdevices/flipperzero-sd-card-examples/blob/c4cbdcd94706836fe0e3cda42587814237b1cfde/nfc/Desfire.nfc Co-authored-by: あく --- applications/main/nfc/scenes/nfc_scene_nfc_data_info.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/main/nfc/scenes/nfc_scene_nfc_data_info.c b/applications/main/nfc/scenes/nfc_scene_nfc_data_info.c index 8f33972e079..bd6f5e2ebe0 100644 --- a/applications/main/nfc/scenes/nfc_scene_nfc_data_info.c +++ b/applications/main/nfc/scenes/nfc_scene_nfc_data_info.c @@ -115,7 +115,7 @@ bool nfc_scene_nfc_data_info_on_event(void* context, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { if(event.event == GuiButtonTypeRight) { if(protocol == NfcDeviceProtocolMifareDesfire) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfDesfireApp); + scene_manager_next_scene(nfc->scene_manager, NfcSceneMfDesfireData); consumed = true; } else if(protocol == NfcDeviceProtocolMifareUl) { scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightData); From 1b3156521cb0aaef696b58673f932b2b88747865 Mon Sep 17 00:00:00 2001 From: Michael Huebler Date: Mon, 28 Nov 2022 18:14:13 +0100 Subject: [PATCH 241/824] NFC: Accept non-parsed apps in Mifare DESFire. (#2041) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * NFC: Accept non-parsed apps in Mifare DESFire. Fixes #2040 Co-authored-by: gornekich Co-authored-by: あく --- lib/nfc/nfc_device.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/nfc/nfc_device.c b/lib/nfc/nfc_device.c index 9f17aed3ab6..5d86da0c18d 100644 --- a/lib/nfc/nfc_device.c +++ b/lib/nfc/nfc_device.c @@ -7,6 +7,7 @@ #include #include +#define TAG "NfcDevice" #define NFC_DEVICE_KEYS_FOLDER EXT_PATH("nfc/cache") #define NFC_DEVICE_KEYS_EXTENSION ".keys" @@ -627,7 +628,10 @@ bool nfc_device_load_mifare_df_data(FlipperFormat* file, NfcDevice* dev) { *app_head = app; app_head = &app->next; } - if(!parsed_apps) break; + if(!parsed_apps) { + // accept non-parsed apps, just log a warning: + FURI_LOG_W(TAG, "Non-parsed apps found!"); + } } parsed = true; } while(false); From 6b47bc1af42f2925606f5e201cb8db65eef29f25 Mon Sep 17 00:00:00 2001 From: Yukai Li Date: Mon, 28 Nov 2022 11:16:22 -0700 Subject: [PATCH 242/824] Nfc: NTAG password auto capture (and other password-related changes) (#1843) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * nfc: MFUL minor cleanup * nfc: Add mechanism to pass event data * nfc: Add NTAG authentication event to emulation * nfc: Rename enum member to align with existing convention * nfc: Add function to determine whether MFUL is fully captured * nfc: Fix emulation of incompletely-read password-protected MFUL * nfc: Add reader password capture scene * nfc: Set default MFUL password input to 0xFFFFFFFF * nfc: Fix MFUL auth counter loading * nfc: Be explicit about using manual auth method when using auto unlock * nfc: Fill in MFUL has_auth when loading file * nfc: Fix MFUL auth success usage, remove unused variable * nfc: Display PWD and PACK in MFUL info if available * nfc: Remove unnecessary include * nfc: Add unlock options to loaded MFUL menu * nfc: Move set default MFUL password. This way it can be edited if needed instead of reentered * nfc: Fix unlock menu not maintaining selection index * nfc: Move captured MFUL auth data from worker to device data * nfc: Attempt to authenticate with default PWD when possible when reading NTAG * nfc: Don't try to auth NTAG on read if we already authed * nfc: Add title for all pages read but failed auth for NTAG auth * nfc: Add faster auth callback patch * lib: Remove scons submodule from index * nfc: Revise MFUL unlock UI flow * nfc: Disallow MFUL unlock with reader if card not read yet. Trying to read first results in either needing to make a new scene or badly jury rigging other scenes, so let's just not do that * f7: Bump API symbols * Format code Co-authored-by: gornekich Co-authored-by: あく --- .../main/nfc/scenes/nfc_scene_config.h | 1 + .../nfc/scenes/nfc_scene_mf_ultralight_menu.c | 4 +- .../nfc_scene_mf_ultralight_read_auth.c | 25 +++++-- ...nfc_scene_mf_ultralight_read_auth_result.c | 32 +++++++-- .../nfc_scene_mf_ultralight_unlock_auto.c | 64 +++++++++++++++++ .../nfc_scene_mf_ultralight_unlock_menu.c | 29 +++++--- .../nfc_scene_mf_ultralight_unlock_warn.c | 71 ++++++++++++++++--- .../main/nfc/scenes/nfc_scene_nfc_data_info.c | 14 ++++ applications/main/nfc/scenes/nfc_scene_read.c | 2 + .../main/nfc/scenes/nfc_scene_saved_menu.c | 23 ++++++ firmware/targets/f7/api_symbols.csv | 3 +- lib/nfc/nfc_device.c | 3 + lib/nfc/nfc_device.h | 1 + lib/nfc/nfc_worker.c | 18 ++++- lib/nfc/nfc_worker.h | 4 +- lib/nfc/protocols/mifare_ultralight.c | 57 ++++++++++++++- lib/nfc/protocols/mifare_ultralight.h | 15 +++- 17 files changed, 325 insertions(+), 41 deletions(-) create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_auto.c diff --git a/applications/main/nfc/scenes/nfc_scene_config.h b/applications/main/nfc/scenes/nfc_scene_config.h index 49c8412c5a1..ce51d000d39 100644 --- a/applications/main/nfc/scenes/nfc_scene_config.h +++ b/applications/main/nfc/scenes/nfc_scene_config.h @@ -21,6 +21,7 @@ ADD_SCENE(nfc, mf_ultralight_emulate, MfUltralightEmulate) ADD_SCENE(nfc, mf_ultralight_read_auth, MfUltralightReadAuth) ADD_SCENE(nfc, mf_ultralight_read_auth_result, MfUltralightReadAuthResult) ADD_SCENE(nfc, mf_ultralight_key_input, MfUltralightKeyInput) +ADD_SCENE(nfc, mf_ultralight_unlock_auto, MfUltralightUnlockAuto) ADD_SCENE(nfc, mf_ultralight_unlock_menu, MfUltralightUnlockMenu) ADD_SCENE(nfc, mf_ultralight_unlock_warn, MfUltralightUnlockWarn) ADD_SCENE(nfc, mf_desfire_read_success, MfDesfireReadSuccess) diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_menu.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_menu.c index ab4d37b09f8..c511e9dcbfe 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_menu.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_menu.c @@ -19,10 +19,10 @@ void nfc_scene_mf_ultralight_menu_on_enter(void* context) { Submenu* submenu = nfc->submenu; MfUltralightData* data = &nfc->dev->dev_data.mf_ul_data; - if(data->data_read != data->data_size) { + if(!mf_ul_is_full_capture(data)) { submenu_add_item( submenu, - "Unlock With Password", + "Unlock", SubmenuIndexUnlock, nfc_scene_mf_ultralight_menu_submenu_callback, nfc); diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_auth.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_auth.c index 5dbb0c18a88..2ab5e3f3f44 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_auth.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_auth.c @@ -24,25 +24,29 @@ void nfc_scene_mf_ultralight_read_auth_set_state(Nfc* nfc, NfcSceneMfUlReadState if(curr_state != state) { if(state == NfcSceneMfUlReadStateDetecting) { popup_reset(nfc->popup); - popup_set_text( - nfc->popup, "Apply card to\nFlipper's back", 97, 24, AlignCenter, AlignTop); + popup_set_text(nfc->popup, "Apply the\ntarget card", 97, 24, AlignCenter, AlignTop); popup_set_icon(nfc->popup, 0, 8, &I_NFC_manual_60x50); + nfc_blink_read_start(nfc); } else if(state == NfcSceneMfUlReadStateReading) { popup_reset(nfc->popup); popup_set_header( nfc->popup, "Reading card\nDon't move...", 85, 24, AlignCenter, AlignTop); popup_set_icon(nfc->popup, 12, 23, &A_Loading_24); + nfc_blink_detect_start(nfc); } else if(state == NfcSceneMfUlReadStateNotSupportedCard) { popup_reset(nfc->popup); popup_set_header(nfc->popup, "Wrong type of card!", 64, 3, AlignCenter, AlignTop); popup_set_text( nfc->popup, - "Only MIFARE\nUltralight & NTAG\n are supported", + "Only MIFARE\nUltralight & NTAG\nare supported", 4, 22, AlignLeft, AlignTop); popup_set_icon(nfc->popup, 73, 20, &I_DolphinCommon_56x48); + nfc_blink_stop(nfc); + notification_message(nfc->notifications, &sequence_error); + notification_message(nfc->notifications, &sequence_set_red_255); } scene_manager_set_scene_state(nfc->scene_manager, NfcSceneMfUltralightReadAuth, state); } @@ -62,8 +66,6 @@ void nfc_scene_mf_ultralight_read_auth_on_enter(void* context) { &nfc->dev->dev_data, nfc_scene_mf_ultralight_read_auth_worker_callback, nfc); - - nfc_blink_read_start(nfc); } bool nfc_scene_mf_ultralight_read_auth_on_event(void* context, SceneManagerEvent event) { @@ -86,8 +88,17 @@ bool nfc_scene_mf_ultralight_read_auth_on_event(void* context, SceneManagerEvent nfc, NfcSceneMfUlReadStateNotSupportedCard); } } else if(event.type == SceneManagerEventTypeBack) { - consumed = scene_manager_search_and_switch_to_previous_scene( - nfc->scene_manager, NfcSceneMfUltralightUnlockMenu); + MfUltralightData* mf_ul_data = &nfc->dev->dev_data.mf_ul_data; + NfcScene next_scene; + if(mf_ul_data->auth_method == MfUltralightAuthMethodManual) { + next_scene = NfcSceneMfUltralightKeyInput; + } else if(mf_ul_data->auth_method == MfUltralightAuthMethodAuto) { + next_scene = NfcSceneMfUltralightUnlockAuto; + } else { + next_scene = NfcSceneMfUltralightUnlockMenu; + } + consumed = + scene_manager_search_and_switch_to_previous_scene(nfc->scene_manager, next_scene); } return consumed; } diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_auth_result.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_auth_result.c index 178d03351b4..b125e999127 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_auth_result.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_auth_result.c @@ -19,16 +19,20 @@ void nfc_scene_mf_ultralight_read_auth_result_on_enter(void* context) { MfUltralightData* mf_ul_data = &nfc->dev->dev_data.mf_ul_data; MfUltralightConfigPages* config_pages = mf_ultralight_get_config_pages(mf_ul_data); Widget* widget = nfc->widget; + const char* title; FuriString* temp_str; temp_str = furi_string_alloc(); if((mf_ul_data->data_read == mf_ul_data->data_size) && (mf_ul_data->data_read > 0)) { - widget_add_string_element( - widget, 64, 0, AlignCenter, AlignTop, FontPrimary, "All pages are unlocked!"); + if(mf_ul_data->auth_success) { + title = "All pages are unlocked!"; + } else { + title = "All unlocked but failed auth!"; + } } else { - widget_add_string_element( - widget, 64, 0, AlignCenter, AlignTop, FontPrimary, "Not all pages unlocked!"); + title = "Not all pages unlocked!"; } + widget_add_string_element(widget, 64, 0, AlignCenter, AlignTop, FontPrimary, title); furi_string_set(temp_str, "UID:"); for(size_t i = 0; i < nfc_data->uid_len; i++) { furi_string_cat_printf(temp_str, " %02X", nfc_data->uid[i]); @@ -65,6 +69,7 @@ void nfc_scene_mf_ultralight_read_auth_result_on_enter(void* context) { nfc); furi_string_free(temp_str); + notification_message(nfc->notifications, &sequence_set_green_255); view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); } @@ -81,8 +86,21 @@ bool nfc_scene_mf_ultralight_read_auth_result_on_event(void* context, SceneManag consumed = true; } } else if(event.type == SceneManagerEventTypeBack) { - consumed = scene_manager_search_and_switch_to_previous_scene( - nfc->scene_manager, NfcSceneMfUltralightUnlockMenu); + MfUltralightData* mf_ul_data = &nfc->dev->dev_data.mf_ul_data; + if(mf_ul_data->auth_method == MfUltralightAuthMethodManual || + mf_ul_data->auth_method == MfUltralightAuthMethodAuto) { + consumed = scene_manager_previous_scene(nfc->scene_manager); + } else { + NfcScene next_scene; + if((mf_ul_data->data_read == mf_ul_data->data_size) && (mf_ul_data->data_read > 0)) { + next_scene = NfcSceneMfUltralightMenu; + } else { + next_scene = NfcSceneMfUltralightUnlockMenu; + } + + consumed = + scene_manager_search_and_switch_to_previous_scene(nfc->scene_manager, next_scene); + } } return consumed; @@ -93,4 +111,6 @@ void nfc_scene_mf_ultralight_read_auth_result_on_exit(void* context) { // Clean views widget_reset(nfc->widget); + + notification_message_block(nfc->notifications, &sequence_reset_green); } diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_auto.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_auto.c new file mode 100644 index 00000000000..c59fe3a7d09 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_auto.c @@ -0,0 +1,64 @@ +#include "../nfc_i.h" + +bool nfc_scene_mf_ultralight_unlock_auto_worker_callback(NfcWorkerEvent event, void* context) { + Nfc* nfc = context; + + view_dispatcher_send_custom_event(nfc->view_dispatcher, event); + return true; +} + +void nfc_scene_mf_ultralight_unlock_auto_on_enter(void* context) { + Nfc* nfc = context; + + // Setup view + widget_add_string_multiline_element( + nfc->widget, + 54, + 30, + AlignLeft, + AlignCenter, + FontPrimary, + "Touch the\nreader to get\npassword..."); + widget_add_icon_element(nfc->widget, 0, 15, &I_Modern_reader_18x34); + widget_add_icon_element(nfc->widget, 20, 12, &I_Move_flipper_26x39); + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); + + // Start worker + nfc_worker_start( + nfc->worker, + NfcWorkerStateMfUltralightEmulate, + &nfc->dev->dev_data, + nfc_scene_mf_ultralight_unlock_auto_worker_callback, + nfc); + + nfc_blink_read_start(nfc); +} + +bool nfc_scene_mf_ultralight_unlock_auto_on_event(void* context, SceneManagerEvent event) { + Nfc* nfc = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if((event.event == NfcWorkerEventMfUltralightPwdAuth)) { + MfUltralightAuth* auth = &nfc->dev->dev_data.mf_ul_auth; + memcpy(nfc->byte_input_store, auth->pwd.raw, sizeof(auth->pwd.raw)); + nfc->dev->dev_data.mf_ul_data.auth_method = MfUltralightAuthMethodAuto; + nfc_worker_stop(nfc->worker); + notification_message(nfc->notifications, &sequence_success); + scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightUnlockWarn); + consumed = true; + } + } + return consumed; +} + +void nfc_scene_mf_ultralight_unlock_auto_on_exit(void* context) { + Nfc* nfc = context; + + // Stop worker + nfc_worker_stop(nfc->worker); + // Clear view + widget_reset(nfc->widget); + + nfc_blink_stop(nfc); +} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_menu.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_menu.c index 648aa31dcd7..484629b0bbe 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_menu.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_menu.c @@ -1,9 +1,10 @@ #include "../nfc_i.h" enum SubmenuIndex { - SubmenuIndexMfUlUnlockMenuManual, + SubmenuIndexMfUlUnlockMenuAuto, SubmenuIndexMfUlUnlockMenuAmeebo, SubmenuIndexMfUlUnlockMenuXiaomi, + SubmenuIndexMfUlUnlockMenuManual, }; void nfc_scene_mf_ultralight_unlock_menu_submenu_callback(void* context, uint32_t index) { @@ -18,22 +19,30 @@ void nfc_scene_mf_ultralight_unlock_menu_on_enter(void* context) { uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfUltralightUnlockMenu); + if(nfc->dev->dev_data.protocol == NfcDeviceProtocolMifareUl) { + submenu_add_item( + submenu, + "Unlock With Reader", + SubmenuIndexMfUlUnlockMenuAuto, + nfc_scene_mf_ultralight_unlock_menu_submenu_callback, + nfc); + } submenu_add_item( submenu, - "Enter Password Manually", - SubmenuIndexMfUlUnlockMenuManual, + "Auth As Ameebo", + SubmenuIndexMfUlUnlockMenuAmeebo, nfc_scene_mf_ultralight_unlock_menu_submenu_callback, nfc); submenu_add_item( submenu, - "Auth As Ameebo", - SubmenuIndexMfUlUnlockMenuAmeebo, + "Auth As Xiaomi Air Purifier", + SubmenuIndexMfUlUnlockMenuXiaomi, nfc_scene_mf_ultralight_unlock_menu_submenu_callback, nfc); submenu_add_item( submenu, - "Auth As Xiaomi", - SubmenuIndexMfUlUnlockMenuXiaomi, + "Enter Password Manually", + SubmenuIndexMfUlUnlockMenuManual, nfc_scene_mf_ultralight_unlock_menu_submenu_callback, nfc); submenu_set_selected_item(submenu, state); @@ -57,8 +66,12 @@ bool nfc_scene_mf_ultralight_unlock_menu_on_event(void* context, SceneManagerEve nfc->dev->dev_data.mf_ul_data.auth_method = MfUltralightAuthMethodXiaomi; scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightUnlockWarn); consumed = true; + } else if(event.event == SubmenuIndexMfUlUnlockMenuAuto) { + scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightUnlockAuto); + consumed = true; } - scene_manager_set_scene_state(nfc->scene_manager, NfcSceneExtraActions, event.event); + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneMfUltralightUnlockMenu, event.event); } return consumed; } diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_warn.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_warn.c index 514cd4e9891..16efae9deae 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_warn.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_warn.c @@ -10,15 +10,43 @@ void nfc_scene_mf_ultralight_unlock_warn_dialog_callback(DialogExResult result, void nfc_scene_mf_ultralight_unlock_warn_on_enter(void* context) { Nfc* nfc = context; DialogEx* dialog_ex = nfc->dialog_ex; + MfUltralightAuthMethod auth_method = nfc->dev->dev_data.mf_ul_data.auth_method; dialog_ex_set_context(dialog_ex, nfc); dialog_ex_set_result_callback(dialog_ex, nfc_scene_mf_ultralight_unlock_warn_dialog_callback); - dialog_ex_set_header(dialog_ex, "Risky function!", 64, 4, AlignCenter, AlignTop); - dialog_ex_set_text( - dialog_ex, "Wrong password\ncan block your\ncard.", 4, 18, AlignLeft, AlignTop); - dialog_ex_set_icon(dialog_ex, 73, 20, &I_DolphinCommon_56x48); - dialog_ex_set_center_button_text(dialog_ex, "OK"); + if(auth_method == MfUltralightAuthMethodManual || auth_method == MfUltralightAuthMethodAuto) { + // Build dialog text + MfUltralightAuth* auth = &nfc->dev->dev_data.mf_ul_auth; + FuriString* password_str = + furi_string_alloc_set_str("Try to unlock the card with\npassword: "); + for(size_t i = 0; i < sizeof(auth->pwd.raw); ++i) { + furi_string_cat_printf(password_str, "%02X ", nfc->byte_input_store[i]); + } + furi_string_cat_str(password_str, "?\nCaution, a wrong password\ncan block the card!"); + nfc_text_store_set(nfc, furi_string_get_cstr(password_str)); + furi_string_free(password_str); + + dialog_ex_set_header( + dialog_ex, + auth_method == MfUltralightAuthMethodAuto ? "Password captured!" : "Risky function!", + 64, + 0, + AlignCenter, + AlignTop); + dialog_ex_set_text(dialog_ex, nfc->text_store, 64, 12, AlignCenter, AlignTop); + dialog_ex_set_left_button_text(dialog_ex, "Cancel"); + dialog_ex_set_right_button_text(dialog_ex, "Continue"); + + if(auth_method == MfUltralightAuthMethodAuto) + notification_message(nfc->notifications, &sequence_set_green_255); + } else { + dialog_ex_set_header(dialog_ex, "Risky function!", 64, 4, AlignCenter, AlignTop); + dialog_ex_set_text( + dialog_ex, "Wrong password\ncan block your\ncard.", 4, 18, AlignLeft, AlignTop); + dialog_ex_set_icon(dialog_ex, 73, 20, &I_DolphinCommon_56x48); + dialog_ex_set_center_button_text(dialog_ex, "OK"); + } view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewDialogEx); } @@ -28,12 +56,33 @@ bool nfc_scene_mf_ultralight_unlock_warn_on_event(void* context, SceneManagerEve bool consumed = false; - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == DialogExResultCenter) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightReadAuth); - DOLPHIN_DEED(DolphinDeedNfcRead); + MfUltralightAuthMethod auth_method = nfc->dev->dev_data.mf_ul_data.auth_method; + if(auth_method == MfUltralightAuthMethodManual || auth_method == MfUltralightAuthMethodAuto) { + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == DialogExResultRight) { + scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightReadAuth); + DOLPHIN_DEED(DolphinDeedNfcRead); + consumed = true; + } else if(event.event == DialogExResultLeft) { + if(auth_method == MfUltralightAuthMethodAuto) { + consumed = scene_manager_search_and_switch_to_previous_scene( + nfc->scene_manager, NfcSceneMfUltralightUnlockMenu); + } else { + consumed = scene_manager_previous_scene(nfc->scene_manager); + } + } + } else if(event.type == SceneManagerEventTypeBack) { + // Cannot press back consumed = true; } + } else { + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == DialogExResultCenter) { + scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightReadAuth); + DOLPHIN_DEED(DolphinDeedNfcRead); + consumed = true; + } + } } return consumed; @@ -43,5 +92,7 @@ void nfc_scene_mf_ultralight_unlock_warn_on_exit(void* context) { Nfc* nfc = context; dialog_ex_reset(nfc->dialog_ex); - submenu_reset(nfc->submenu); + nfc_text_store_clear(nfc); + + notification_message_block(nfc->notifications, &sequence_reset_green); } diff --git a/applications/main/nfc/scenes/nfc_scene_nfc_data_info.c b/applications/main/nfc/scenes/nfc_scene_nfc_data_info.c index bd6f5e2ebe0..7318fd009d4 100644 --- a/applications/main/nfc/scenes/nfc_scene_nfc_data_info.c +++ b/applications/main/nfc/scenes/nfc_scene_nfc_data_info.c @@ -87,6 +87,20 @@ void nfc_scene_nfc_data_info_on_enter(void* context) { temp_str, "\nPages Read %d/%d", data->data_read / 4, data->data_size / 4); if(data->data_size > data->data_read) { furi_string_cat_printf(temp_str, "\nPassword-protected"); + } else if(data->auth_success) { + MfUltralightConfigPages* config_pages = mf_ultralight_get_config_pages(data); + furi_string_cat_printf( + temp_str, + "\nPassword: %02X %02X %02X %02X", + config_pages->auth_data.pwd.raw[0], + config_pages->auth_data.pwd.raw[1], + config_pages->auth_data.pwd.raw[2], + config_pages->auth_data.pwd.raw[3]); + furi_string_cat_printf( + temp_str, + "\nPACK: %02X %02X", + config_pages->auth_data.pack.raw[0], + config_pages->auth_data.pack.raw[1]); } } else if(protocol == NfcDeviceProtocolMifareClassic) { MfClassicData* data = &dev_data->mf_classic_data; diff --git a/applications/main/nfc/scenes/nfc_scene_read.c b/applications/main/nfc/scenes/nfc_scene_read.c index 1f82aef0809..a64d4d00d91 100644 --- a/applications/main/nfc/scenes/nfc_scene_read.c +++ b/applications/main/nfc/scenes/nfc_scene_read.c @@ -70,6 +70,8 @@ bool nfc_scene_read_on_event(void* context, SceneManagerEvent event) { consumed = true; } else if(event.event == NfcWorkerEventReadMfUltralight) { notification_message(nfc->notifications, &sequence_success); + // Set unlock password input to 0xFFFFFFFF only on fresh read + memset(nfc->byte_input_store, 0xFF, 4); scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightReadSuccess); DOLPHIN_DEED(DolphinDeedNfcReadSuccess); consumed = true; diff --git a/applications/main/nfc/scenes/nfc_scene_saved_menu.c b/applications/main/nfc/scenes/nfc_scene_saved_menu.c index 231f12089be..e0839d66eec 100644 --- a/applications/main/nfc/scenes/nfc_scene_saved_menu.c +++ b/applications/main/nfc/scenes/nfc_scene_saved_menu.c @@ -11,6 +11,8 @@ enum SubmenuIndex { SubmenuIndexDelete, SubmenuIndexInfo, SubmenuIndexRestoreOriginal, + SubmenuIndexMfUlUnlockByReader, + SubmenuIndexMfUlUnlockByPassword, }; void nfc_scene_saved_menu_submenu_callback(void* context, uint32_t index) { @@ -69,6 +71,21 @@ void nfc_scene_saved_menu_on_enter(void* context) { } submenu_add_item( submenu, "Info", SubmenuIndexInfo, nfc_scene_saved_menu_submenu_callback, nfc); + if(nfc->dev->format == NfcDeviceSaveFormatMifareUl && + !mf_ul_is_full_capture(&nfc->dev->dev_data.mf_ul_data)) { + submenu_add_item( + submenu, + "Unlock With Reader", + SubmenuIndexMfUlUnlockByReader, + nfc_scene_saved_menu_submenu_callback, + nfc); + submenu_add_item( + submenu, + "Unlock With Password", + SubmenuIndexMfUlUnlockByPassword, + nfc_scene_saved_menu_submenu_callback, + nfc); + } if(nfc->dev->shadow_file_exist) { submenu_add_item( submenu, @@ -141,6 +158,12 @@ bool nfc_scene_saved_menu_on_event(void* context, SceneManagerEvent event) { } else if(event.event == SubmenuIndexRestoreOriginal) { scene_manager_next_scene(nfc->scene_manager, NfcSceneRestoreOriginalConfirm); consumed = true; + } else if(event.event == SubmenuIndexMfUlUnlockByReader) { + scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightUnlockAuto); + consumed = true; + } else if(event.event == SubmenuIndexMfUlUnlockByPassword) { + scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightUnlockMenu); + consumed = true; } } diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 5ccb9d4339b..f3ed374bb92 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,7.5,, +Version,+,7.6,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -1900,6 +1900,7 @@ Function,-,mf_df_prepare_read_records,uint16_t,"uint8_t*, uint8_t, uint32_t, uin Function,-,mf_df_prepare_select_application,uint16_t,"uint8_t*, uint8_t[3]" Function,-,mf_df_read_card,_Bool,"FuriHalNfcTxRxContext*, MifareDesfireData*" Function,-,mf_ul_check_card_type,_Bool,"uint8_t, uint8_t, uint8_t" +Function,-,mf_ul_is_full_capture,_Bool,MfUltralightData* Function,-,mf_ul_prepare_emulation,void,"MfUltralightEmulator*, MfUltralightData*" Function,-,mf_ul_prepare_emulation_response,_Bool,"uint8_t*, uint16_t, uint8_t*, uint16_t*, uint32_t*, void*" Function,-,mf_ul_pwdgen_amiibo,uint32_t,FuriHalNfcDevData* diff --git a/lib/nfc/nfc_device.c b/lib/nfc/nfc_device.c index 5d86da0c18d..dc1faa34cec 100644 --- a/lib/nfc/nfc_device.c +++ b/lib/nfc/nfc_device.c @@ -214,6 +214,9 @@ bool nfc_device_load_mifare_ul_data(FlipperFormat* file, NfcDevice* dev) { uint32_t auth_counter; if(!flipper_format_read_uint32(file, "Failed authentication attempts", &auth_counter, 1)) auth_counter = 0; + data->curr_authlim = auth_counter; + + data->auth_success = mf_ul_is_full_capture(data); parsed = true; } while(false); diff --git a/lib/nfc/nfc_device.h b/lib/nfc/nfc_device.h index 3d302c18b70..4be07f0166d 100644 --- a/lib/nfc/nfc_device.h +++ b/lib/nfc/nfc_device.h @@ -67,6 +67,7 @@ typedef struct { union { NfcReaderRequestData reader_data; NfcMfClassicDictAttackData mf_classic_dict_attack_data; + MfUltralightAuth mf_ul_auth; }; union { EmvData emv_data; diff --git a/lib/nfc/nfc_worker.c b/lib/nfc/nfc_worker.c index c701132c321..450428a18a1 100644 --- a/lib/nfc/nfc_worker.c +++ b/lib/nfc/nfc_worker.c @@ -527,10 +527,25 @@ void nfc_worker_emulate_apdu(NfcWorker* nfc_worker) { } } +void nfc_worker_mf_ultralight_auth_received_callback(MfUltralightAuth auth, void* context) { + furi_assert(context); + + NfcWorker* nfc_worker = context; + nfc_worker->dev_data->mf_ul_auth = auth; + if(nfc_worker->callback) { + nfc_worker->callback(NfcWorkerEventMfUltralightPwdAuth, nfc_worker->context); + } +} + void nfc_worker_emulate_mf_ultralight(NfcWorker* nfc_worker) { FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data; MfUltralightEmulator emulator = {}; mf_ul_prepare_emulation(&emulator, &nfc_worker->dev_data->mf_ul_data); + + // TODO rework with reader analyzer + emulator.auth_received_callback = nfc_worker_mf_ultralight_auth_received_callback; + emulator.context = nfc_worker; + while(nfc_worker->state == NfcWorkerStateMfUltralightEmulate) { mf_ul_reset_emulation(&emulator, true); furi_hal_nfc_emulate_nfca( @@ -905,7 +920,8 @@ void nfc_worker_mf_ultralight_read_auth(NfcWorker* nfc_worker) { if(furi_hal_nfc_detect(nfc_data, 300) && nfc_data->type == FuriHalNfcTypeA) { if(mf_ul_check_card_type(nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak)) { nfc_worker->callback(NfcWorkerEventCardDetected, nfc_worker->context); - if(data->auth_method == MfUltralightAuthMethodManual) { + if(data->auth_method == MfUltralightAuthMethodManual || + data->auth_method == MfUltralightAuthMethodAuto) { nfc_worker->callback(NfcWorkerEventMfUltralightPassKey, nfc_worker->context); key = nfc_util_bytes2num(data->auth_key, 4); } else if(data->auth_method == MfUltralightAuthMethodAmeebo) { diff --git a/lib/nfc/nfc_worker.h b/lib/nfc/nfc_worker.h index ddee34c95c7..fdcaa72fdf4 100644 --- a/lib/nfc/nfc_worker.h +++ b/lib/nfc/nfc_worker.h @@ -65,8 +65,8 @@ typedef enum { NfcWorkerEventDetectReaderMfkeyCollected, // Mifare Ultralight events - NfcWorkerEventMfUltralightPassKey, - + NfcWorkerEventMfUltralightPassKey, // NFC worker requesting manual key + NfcWorkerEventMfUltralightPwdAuth, // Reader sent auth command } NfcWorkerEvent; typedef bool (*NfcWorkerCallback)(NfcWorkerEvent event, void* context); diff --git a/lib/nfc/protocols/mifare_ultralight.c b/lib/nfc/protocols/mifare_ultralight.c index a8d1f5548e4..85e234bd92b 100644 --- a/lib/nfc/protocols/mifare_ultralight.c +++ b/lib/nfc/protocols/mifare_ultralight.c @@ -51,7 +51,7 @@ void mf_ul_reset(MfUltralightData* data) { data->data_size = 0; data->data_read = 0; data->curr_authlim = 0; - data->has_auth = false; + data->auth_success = false; } static MfUltralightFeatures mf_ul_get_features(MfUltralightType type) { @@ -756,6 +756,34 @@ bool mf_ul_read_card( mf_ultralight_read_tearing_flags(tx_rx, data); } data->curr_authlim = 0; + + if(reader->pages_read == reader->pages_to_read && + reader->supported_features & MfUltralightSupportAuth && !data->auth_success) { + MfUltralightConfigPages* config = mf_ultralight_get_config_pages(data); + if(config->access.authlim == 0) { + // Attempt to auth with default PWD + uint16_t pack; + data->auth_success = mf_ultralight_authenticate(tx_rx, MF_UL_DEFAULT_PWD, &pack); + if(data->auth_success) { + config->auth_data.pwd.value = MF_UL_DEFAULT_PWD; + config->auth_data.pack.value = pack; + } else { + furi_hal_nfc_sleep(); + furi_hal_nfc_activate_nfca(300, NULL); + } + } + } + } + + if(reader->pages_read != reader->pages_to_read) { + if(reader->supported_features & MfUltralightSupportAuth) { + // Probably password protected, fix AUTH0 and PROT so before AUTH0 + // can be written and since AUTH0 won't be readable, like on the + // original card + MfUltralightConfigPages* config = mf_ultralight_get_config_pages(data); + config->auth0 = reader->pages_read; + config->access.prot = true; + } } return card_read; @@ -1201,6 +1229,8 @@ static void mf_ul_emulate_write( } void mf_ul_reset_emulation(MfUltralightEmulator* emulator, bool is_power_cycle) { + emulator->comp_write_cmd_started = false; + emulator->sector_select_cmd_started = false; emulator->curr_sector = 0; emulator->ntag_i2c_plus_sector3_lockout = false; emulator->auth_success = false; @@ -1244,8 +1274,7 @@ void mf_ul_prepare_emulation(MfUltralightEmulator* emulator, MfUltralightData* d emulator->config = mf_ultralight_get_config_pages(&emulator->data); emulator->page_num = emulator->data.data_size / 4; emulator->data_changed = false; - emulator->comp_write_cmd_started = false; - emulator->sector_select_cmd_started = false; + memset(&emulator->auth_attempt, 0, sizeof(MfUltralightAuth)); mf_ul_reset_emulation(emulator, true); } @@ -1706,6 +1735,17 @@ bool mf_ul_prepare_emulation_response( } else if(cmd == MF_UL_AUTH) { if(emulator->supported_features & MfUltralightSupportAuth) { if(buff_rx_len == (1 + 4) * 8) { + // Record password sent by PCD + memcpy( + emulator->auth_attempt.pwd.raw, + &buff_rx[1], + sizeof(emulator->auth_attempt.pwd.raw)); + emulator->auth_attempted = true; + if(emulator->auth_received_callback) { + emulator->auth_received_callback( + emulator->auth_attempt, emulator->context); + } + uint16_t scaled_authlim = mf_ultralight_calc_auth_count(&emulator->data); if(scaled_authlim != 0 && emulator->data.curr_authlim >= scaled_authlim) { if(emulator->data.curr_authlim != UINT16_MAX) { @@ -1863,3 +1903,14 @@ bool mf_ul_prepare_emulation_response( return tx_bits > 0; } + +bool mf_ul_is_full_capture(MfUltralightData* data) { + if(data->data_read != data->data_size) return false; + + // Having read all the pages doesn't mean that we've got everything. + // By default PWD is 0xFFFFFFFF, but if read back it is always 0x00000000, + // so a default read on an auth-supported NTAG is never complete. + if(!(mf_ul_get_features(data->type) & MfUltralightSupportAuth)) return true; + MfUltralightConfigPages* config = mf_ultralight_get_config_pages(data); + return config->auth_data.pwd.value != 0 || config->auth_data.pack.value != 0; +} diff --git a/lib/nfc/protocols/mifare_ultralight.h b/lib/nfc/protocols/mifare_ultralight.h index 9642824f702..4ab22e89cb8 100644 --- a/lib/nfc/protocols/mifare_ultralight.h +++ b/lib/nfc/protocols/mifare_ultralight.h @@ -28,10 +28,13 @@ #define MF_UL_NTAG203_COUNTER_PAGE (41) +#define MF_UL_DEFAULT_PWD (0xFFFFFFFF) + typedef enum { MfUltralightAuthMethodManual, MfUltralightAuthMethodAmeebo, MfUltralightAuthMethodXiaomi, + MfUltralightAuthMethodAuto, } MfUltralightAuthMethod; // Important: order matters; some features are based on positioning in this enum @@ -110,7 +113,6 @@ typedef struct { uint8_t signature[32]; uint32_t counter[3]; uint8_t tearing[3]; - bool has_auth; MfUltralightAuthMethod auth_method; uint8_t auth_key[4]; bool auth_success; @@ -169,6 +171,9 @@ typedef struct { MfUltralightFeatures supported_features; } MfUltralightReader; +// TODO rework with reader analyzer +typedef void (*MfUltralightAuthReceivedCallback)(MfUltralightAuth auth, void* context); + typedef struct { MfUltralightData data; MfUltralightConfigPages* config; @@ -185,6 +190,12 @@ typedef struct { bool sector_select_cmd_started; bool ntag_i2c_plus_sector3_lockout; bool read_counter_incremented; + bool auth_attempted; + MfUltralightAuth auth_attempt; + + // TODO rework with reader analyzer + MfUltralightAuthReceivedCallback auth_received_callback; + void* context; } MfUltralightEmulator; void mf_ul_reset(MfUltralightData* data); @@ -241,3 +252,5 @@ bool mf_ul_prepare_emulation_response( uint32_t mf_ul_pwdgen_amiibo(FuriHalNfcDevData* data); uint32_t mf_ul_pwdgen_xiaomi(FuriHalNfcDevData* data); + +bool mf_ul_is_full_capture(MfUltralightData* data); From 769c53b6da48a59d0e0d43a2ddc3f6221f9ccfe7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Tue, 29 Nov 2022 03:59:24 +0900 Subject: [PATCH 243/824] [FL-2977] Gui: better navigation in file browser dialog (#2014) * Gui: proper navigation in file browser dialog * Trim file name from start path * File list loading fix * File list offset fix Co-authored-by: nminaylov Co-authored-by: Sergey Gavrilov --- .../main/archive/helpers/archive_browser.c | 3 +-- .../main/archive/helpers/archive_browser.h | 2 +- .../services/gui/modules/file_browser.c | 22 ++++++++++++++++--- .../gui/modules/file_browser_worker.c | 17 ++++++++++++++ .../gui/modules/file_browser_worker.h | 2 ++ firmware/targets/f7/api_symbols.csv | 1 + 6 files changed, 41 insertions(+), 6 deletions(-) diff --git a/applications/main/archive/helpers/archive_browser.c b/applications/main/archive/helpers/archive_browser.c index 9689454ba5a..149b3908987 100644 --- a/applications/main/archive/helpers/archive_browser.c +++ b/applications/main/archive/helpers/archive_browser.c @@ -265,8 +265,7 @@ void archive_file_array_load(ArchiveBrowserView* browser, int8_t dir) { offset_new = model->item_idx - FILE_LIST_BUF_LEN / 4 * 1; } if(offset_new > 0) { - offset_new = - CLAMP(offset_new, (int32_t)model->item_cnt - FILE_LIST_BUF_LEN, 0); + offset_new = CLAMP(offset_new, (int32_t)model->item_cnt, 0); } else { offset_new = 0; } diff --git a/applications/main/archive/helpers/archive_browser.h b/applications/main/archive/helpers/archive_browser.h index 1a7e01f5ab3..09ffea1f9c8 100644 --- a/applications/main/archive/helpers/archive_browser.h +++ b/applications/main/archive/helpers/archive_browser.h @@ -5,7 +5,7 @@ #define TAB_RIGHT InputKeyRight // Default tab switch direction #define TAB_DEFAULT ArchiveTabFavorites // Start tab -#define FILE_LIST_BUF_LEN 100 +#define FILE_LIST_BUF_LEN 50 static const char* tab_default_paths[] = { [ArchiveTabFavorites] = "/app:favorites", diff --git a/applications/services/gui/modules/file_browser.c b/applications/services/gui/modules/file_browser.c index 60e78b01c30..203be23ed6f 100644 --- a/applications/services/gui/modules/file_browser.c +++ b/applications/services/gui/modules/file_browser.c @@ -232,7 +232,10 @@ static bool browser_is_item_in_array(FileBrowserModel* model, uint32_t idx) { 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((array_size > 0) && (!model->is_root) && (model->array_offset == 0)) { + array_size--; + } + uint32_t item_cnt = (model->is_root) ? (model->item_cnt) : (model->item_cnt - 1); if((model->list_loading) || (array_size >= item_cnt)) { return false; @@ -524,7 +527,7 @@ static bool file_browser_view_input_callback(InputEvent* event, void* context) { 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, + (int32_t)model->item_cnt, 0); file_browser_worker_load( browser->worker, load_offset, ITEM_LIST_LEN_MAX); @@ -535,7 +538,7 @@ static bool file_browser_view_input_callback(InputEvent* event, void* context) { 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, + (int32_t)model->item_cnt, 0); file_browser_worker_load( browser->worker, load_offset, ITEM_LIST_LEN_MAX); @@ -590,6 +593,19 @@ static bool file_browser_view_input_callback(InputEvent* event, void* context) { } consumed = true; } + } else if(event->key == InputKeyBack) { + if(event->type == InputTypeShort) { + bool is_root = false; + with_view_model( + browser->view, FileBrowserModel * model, { is_root = model->is_root; }, false); + + if(!is_root && !file_browser_worker_is_in_start_folder(browser->worker)) { + consumed = true; + if(!is_root) { + file_browser_worker_folder_exit(browser->worker); + } + } + } } return consumed; diff --git a/applications/services/gui/modules/file_browser_worker.c b/applications/services/gui/modules/file_browser_worker.c index 34d83032d16..a85a14b75f0 100644 --- a/applications/services/gui/modules/file_browser_worker.c +++ b/applications/services/gui/modules/file_browser_worker.c @@ -35,6 +35,8 @@ struct BrowserWorker { FuriThread* thread; FuriString* filter_extension; + FuriString* path_start; + FuriString* path_current; FuriString* path_next; int32_t item_sel_idx; uint32_t load_offset; @@ -289,6 +291,7 @@ static int32_t browser_worker(void* context) { int32_t file_idx = 0; browser_folder_init(browser, path, filename, &items_cnt, &file_idx); + furi_string_set(browser->path_current, path); FURI_LOG_D( TAG, "Enter folder: %s items: %lu idx: %ld", @@ -311,6 +314,7 @@ static int32_t browser_worker(void* context) { // Pop previous selected item index from history array idx_last_array_pop_back(&file_idx, browser->idx_last); } + furi_string_set(browser->path_current, path); FURI_LOG_D( TAG, "Exit to: %s items: %lu idx: %ld", @@ -365,8 +369,14 @@ BrowserWorker* browser->filter_extension = furi_string_alloc_set(filter_ext); browser->skip_assets = skip_assets; + browser->path_start = furi_string_alloc_set(path); + browser->path_current = furi_string_alloc_set(path); browser->path_next = furi_string_alloc_set(path); + if(browser_path_is_file(browser->path_start)) { + browser_path_trim(browser->path_start); + } + browser->thread = furi_thread_alloc_ex("BrowserWorker", 2048, browser_worker, browser); furi_thread_start(browser->thread); @@ -382,6 +392,8 @@ void file_browser_worker_free(BrowserWorker* browser) { furi_string_free(browser->filter_extension); furi_string_free(browser->path_next); + furi_string_free(browser->path_current); + furi_string_free(browser->path_start); idx_last_array_clear(browser->idx_last); @@ -440,6 +452,11 @@ void file_browser_worker_folder_enter(BrowserWorker* browser, FuriString* path, furi_thread_flags_set(furi_thread_get_id(browser->thread), WorkerEvtFolderEnter); } +bool file_browser_worker_is_in_start_folder(BrowserWorker* browser) { + furi_assert(browser); + return (furi_string_cmp(browser->path_start, browser->path_current) == 0); +} + void file_browser_worker_folder_exit(BrowserWorker* browser) { furi_assert(browser); furi_thread_flags_set(furi_thread_get_id(browser->thread), WorkerEvtFolderExit); diff --git a/applications/services/gui/modules/file_browser_worker.h b/applications/services/gui/modules/file_browser_worker.h index 230bb5b45b4..2f815540173 100644 --- a/applications/services/gui/modules/file_browser_worker.h +++ b/applications/services/gui/modules/file_browser_worker.h @@ -52,6 +52,8 @@ void file_browser_worker_set_config( void file_browser_worker_folder_enter(BrowserWorker* browser, FuriString* path, int32_t item_idx); +bool file_browser_worker_is_in_start_folder(BrowserWorker* browser); + void file_browser_worker_folder_exit(BrowserWorker* browser); void file_browser_worker_folder_refresh(BrowserWorker* browser, int32_t item_idx); diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index f3ed374bb92..17b36a2ae31 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -817,6 +817,7 @@ Function,+,file_browser_worker_folder_enter,void,"BrowserWorker*, FuriString*, i Function,+,file_browser_worker_folder_exit,void,BrowserWorker* Function,+,file_browser_worker_folder_refresh,void,"BrowserWorker*, int32_t" Function,+,file_browser_worker_free,void,BrowserWorker* +Function,+,file_browser_worker_is_in_start_folder,_Bool,BrowserWorker* Function,+,file_browser_worker_load,void,"BrowserWorker*, uint32_t, uint32_t" Function,+,file_browser_worker_set_callback_context,void,"BrowserWorker*, void*" Function,+,file_browser_worker_set_config,void,"BrowserWorker*, FuriString*, const char*, _Bool" From 97e8da7a7bf781ef1670e38b52f984c551253d3f Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 28 Nov 2022 22:08:28 +0300 Subject: [PATCH 244/824] Weather Station: Add protocol - Auriol HG0601A (#2056) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add protocol Auriol hg0601a: Made by @LY2NEO * Improve readability of code: fix protocol name Co-authored-by: あく --- .../protocols/auriol_hg0601a.c | 258 ++++++++++++++++++ .../protocols/auriol_hg0601a.h | 79 ++++++ .../protocols/protocol_items.c | 1 + .../protocols/protocol_items.h | 1 + 4 files changed, 339 insertions(+) create mode 100644 applications/plugins/weather_station/protocols/auriol_hg0601a.c create mode 100644 applications/plugins/weather_station/protocols/auriol_hg0601a.h diff --git a/applications/plugins/weather_station/protocols/auriol_hg0601a.c b/applications/plugins/weather_station/protocols/auriol_hg0601a.c new file mode 100644 index 00000000000..d5f89fc8bd5 --- /dev/null +++ b/applications/plugins/weather_station/protocols/auriol_hg0601a.c @@ -0,0 +1,258 @@ +#include "auriol_hg0601a.h" + +#define TAG "WSProtocolAuriol_TH" + +/* + * +Auriol HG06061A-DCF-TX sensor. + +Data layout: + DDDDDDDD-B0-NN-TT-TTTTTTTTTT-CCCC-HHHHHHHH +Exmpl.: 11110100-10-01-00-0001001100-1111-01011101 + +- D: id, 8 bit +- B: where B is the battery status: 1=OK, 0=LOW, 1 bit +- 0: just zero :) +- N: NN is the channel: 00=CH1, 01=CH2, 11=CH3, 2bit +- T: temperature, 12 bit: 2's complement, scaled by 10 +- C: 4 bit: seems to be 0xf constantly, a separator between temp and humidity +- H: humidity sensor, humidity is 8 bits + + * The sensor sends 37 bits 10 times, + * the packets are ppm modulated (distance coding) with a pulse of ~500 us + * followed by a short gap of ~1000 us for a 0 bit or a long ~2000 us gap for a + * 1 bit, the sync gap is ~4000 us. + * + */ + +#define AURIOL_TH_CONST_DATA 0b1110 + +static const SubGhzBlockConst ws_protocol_auriol_th_const = { + .te_short = 500, + .te_long = 2000, + .te_delta = 150, + .min_count_bit_for_found = 37, +}; + +struct WSProtocolDecoderAuriol_TH { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + WSBlockGeneric generic; +}; + +struct WSProtocolEncoderAuriol_TH { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + WSBlockGeneric generic; +}; + +typedef enum { + auriol_THDecoderStepReset = 0, + auriol_THDecoderStepSaveDuration, + auriol_THDecoderStepCheckDuration, +} auriol_THDecoderStep; + +const SubGhzProtocolDecoder ws_protocol_auriol_th_decoder = { + .alloc = ws_protocol_decoder_auriol_th_alloc, + .free = ws_protocol_decoder_auriol_th_free, + + .feed = ws_protocol_decoder_auriol_th_feed, + .reset = ws_protocol_decoder_auriol_th_reset, + + .get_hash_data = ws_protocol_decoder_auriol_th_get_hash_data, + .serialize = ws_protocol_decoder_auriol_th_serialize, + .deserialize = ws_protocol_decoder_auriol_th_deserialize, + .get_string = ws_protocol_decoder_auriol_th_get_string, +}; + +const SubGhzProtocolEncoder ws_protocol_auriol_th_encoder = { + .alloc = NULL, + .free = NULL, + + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol ws_protocol_auriol_th = { + .name = WS_PROTOCOL_AURIOL_TH_NAME, + .type = SubGhzProtocolWeatherStation, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable, + + .decoder = &ws_protocol_auriol_th_decoder, + .encoder = &ws_protocol_auriol_th_encoder, +}; + +void* ws_protocol_decoder_auriol_th_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + WSProtocolDecoderAuriol_TH* instance = malloc(sizeof(WSProtocolDecoderAuriol_TH)); + instance->base.protocol = &ws_protocol_auriol_th; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void ws_protocol_decoder_auriol_th_free(void* context) { + furi_assert(context); + WSProtocolDecoderAuriol_TH* instance = context; + free(instance); +} + +void ws_protocol_decoder_auriol_th_reset(void* context) { + furi_assert(context); + WSProtocolDecoderAuriol_TH* instance = context; + instance->decoder.parser_step = auriol_THDecoderStepReset; +} + +static bool ws_protocol_auriol_th_check(WSProtocolDecoderAuriol_TH* instance) { + uint8_t type = (instance->decoder.decode_data >> 8) & 0x0F; + + if((type == AURIOL_TH_CONST_DATA) && ((instance->decoder.decode_data >> 4) != 0xffffffff)) { + return true; + } else { + return false; + } + return true; +} + +/** + * Analysis of received data + * @param instance Pointer to a WSBlockGeneric* instance + */ +static void ws_protocol_auriol_th_remote_controller(WSBlockGeneric* instance) { + instance->id = (instance->data >> 31) & 0xFF; + instance->battery_low = ((instance->data >> 30) & 1); + instance->channel = ((instance->data >> 25) & 0x03) + 1; + instance->btn = WS_NO_BTN; + if(!((instance->data >> 23) & 1)) { + instance->temp = (float)((instance->data >> 13) & 0x07FF) / 10.0f; + } else { + instance->temp = (float)((~(instance->data >> 13) & 0x07FF) + 1) / -10.0f; + } + + instance->humidity = (instance->data >> 1) & 0x7F; +} + +void ws_protocol_decoder_auriol_th_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + WSProtocolDecoderAuriol_TH* instance = context; + + switch(instance->decoder.parser_step) { + case auriol_THDecoderStepReset: + if((!level) && (DURATION_DIFF(duration, ws_protocol_auriol_th_const.te_short * 8) < + ws_protocol_auriol_th_const.te_delta)) { + //Found sync + instance->decoder.parser_step = auriol_THDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } + break; + + case auriol_THDecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = auriol_THDecoderStepCheckDuration; + } else { + instance->decoder.parser_step = auriol_THDecoderStepReset; + } + break; + + case auriol_THDecoderStepCheckDuration: + if(!level) { + if(DURATION_DIFF(duration, ws_protocol_auriol_th_const.te_short * 8) < + ws_protocol_auriol_th_const.te_delta) { + //Found sync + instance->decoder.parser_step = auriol_THDecoderStepReset; + if((instance->decoder.decode_count_bit == + ws_protocol_auriol_th_const.min_count_bit_for_found) && + ws_protocol_auriol_th_check(instance)) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + ws_protocol_auriol_th_remote_controller(&instance->generic); + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + instance->decoder.parser_step = auriol_THDecoderStepCheckDuration; + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + + break; + } else if( + (DURATION_DIFF(instance->decoder.te_last, ws_protocol_auriol_th_const.te_short) < + ws_protocol_auriol_th_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_auriol_th_const.te_short * 2) < + ws_protocol_auriol_th_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = auriol_THDecoderStepSaveDuration; + } else if( + (DURATION_DIFF(instance->decoder.te_last, ws_protocol_auriol_th_const.te_short) < + ws_protocol_auriol_th_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_auriol_th_const.te_short * 4) < + ws_protocol_auriol_th_const.te_delta * 2)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = auriol_THDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = auriol_THDecoderStepReset; + } + } else { + instance->decoder.parser_step = auriol_THDecoderStepReset; + } + break; + } +} + +uint8_t ws_protocol_decoder_auriol_th_get_hash_data(void* context) { + furi_assert(context); + WSProtocolDecoderAuriol_TH* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool ws_protocol_decoder_auriol_th_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + WSProtocolDecoderAuriol_TH* instance = context; + return ws_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +bool ws_protocol_decoder_auriol_th_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + WSProtocolDecoderAuriol_TH* instance = context; + bool ret = false; + do { + if(!ws_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if(instance->generic.data_count_bit != + ws_protocol_auriol_th_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + ret = true; + } while(false); + return ret; +} + +void ws_protocol_decoder_auriol_th_get_string(void* context, FuriString* output) { + furi_assert(context); + WSProtocolDecoderAuriol_TH* instance = context; + furi_string_printf( + output, + "%s %dbit\r\n" + "Key:0x%lX%08lX\r\n" + "Sn:0x%lX Ch:%d Bat:%d\r\n" + "Temp:%3.1f C Hum:%d%%", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data >> 32), + (uint32_t)(instance->generic.data), + instance->generic.id, + instance->generic.channel, + instance->generic.battery_low, + (double)instance->generic.temp, + instance->generic.humidity); +} diff --git a/applications/plugins/weather_station/protocols/auriol_hg0601a.h b/applications/plugins/weather_station/protocols/auriol_hg0601a.h new file mode 100644 index 00000000000..c23007c1a70 --- /dev/null +++ b/applications/plugins/weather_station/protocols/auriol_hg0601a.h @@ -0,0 +1,79 @@ +#pragma once + +#include + +#include +#include +#include +#include "ws_generic.h" +#include + +#define WS_PROTOCOL_AURIOL_TH_NAME "Auriol HG06061" //HG06061A-DCF-TX + +typedef struct WSProtocolDecoderAuriol_TH WSProtocolDecoderAuriol_TH; +typedef struct WSProtocolEncoderAuriol_TH WSProtocolEncoderAuriol_TH; + +extern const SubGhzProtocolDecoder ws_protocol_auriol_th_decoder; +extern const SubGhzProtocolEncoder ws_protocol_auriol_th_encoder; +extern const SubGhzProtocol ws_protocol_auriol_th; + +/** + * Allocate WSProtocolDecoderAuriol_TH. + * @param environment Pointer to a SubGhzEnvironment instance + * @return WSProtocolDecoderAuriol_TH* pointer to a WSProtocolDecoderAuriol_TH instance + */ +void* ws_protocol_decoder_auriol_th_alloc(SubGhzEnvironment* environment); + +/** + * Free WSProtocolDecoderAuriol_TH. + * @param context Pointer to a WSProtocolDecoderAuriol_TH instance + */ +void ws_protocol_decoder_auriol_th_free(void* context); + +/** + * Reset decoder WSProtocolDecoderAuriol_TH. + * @param context Pointer to a WSProtocolDecoderAuriol_TH instance + */ +void ws_protocol_decoder_auriol_th_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a WSProtocolDecoderAuriol_TH instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void ws_protocol_decoder_auriol_th_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a WSProtocolDecoderAuriol_TH instance + * @return hash Hash sum + */ +uint8_t ws_protocol_decoder_auriol_th_get_hash_data(void* context); + +/** + * Serialize data WSProtocolDecoderAuriol_TH. + * @param context Pointer to a WSProtocolDecoderAuriol_TH instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool ws_protocol_decoder_auriol_th_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data WSProtocolDecoderAuriol_TH. + * @param context Pointer to a WSProtocolDecoderAuriol_TH instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool ws_protocol_decoder_auriol_th_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a WSProtocolDecoderAuriol_TH instance + * @param output Resulting text + */ +void ws_protocol_decoder_auriol_th_get_string(void* context, FuriString* output); diff --git a/applications/plugins/weather_station/protocols/protocol_items.c b/applications/plugins/weather_station/protocols/protocol_items.c index 21768e0426e..9ad8ce1acfb 100644 --- a/applications/plugins/weather_station/protocols/protocol_items.c +++ b/applications/plugins/weather_station/protocols/protocol_items.c @@ -12,6 +12,7 @@ const SubGhzProtocol* weather_station_protocol_registry_items[] = { &ws_protocol_oregon2, &ws_protocol_acurite_592txr, &ws_protocol_ambient_weather, + &ws_protocol_auriol_th, }; const SubGhzProtocolRegistry weather_station_protocol_registry = { diff --git a/applications/plugins/weather_station/protocols/protocol_items.h b/applications/plugins/weather_station/protocols/protocol_items.h index aa064f0447e..4fef89442fb 100644 --- a/applications/plugins/weather_station/protocols/protocol_items.h +++ b/applications/plugins/weather_station/protocols/protocol_items.h @@ -12,5 +12,6 @@ #include "oregon2.h" #include "acurite_592txr.h" #include "ambient_weather.h" +#include "auriol_hg0601a.h" extern const SubGhzProtocolRegistry weather_station_protocol_registry; From 84f9af3e7e872499c035285e48cd3e087a0f9c18 Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Mon, 28 Nov 2022 23:17:57 +0400 Subject: [PATCH 245/824] SubGhz: fix duration pricenton protocol (#2054) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- lib/subghz/protocols/princeton.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/subghz/protocols/princeton.c b/lib/subghz/protocols/princeton.c index a4a0921acd6..ab1c58765df 100644 --- a/lib/subghz/protocols/princeton.c +++ b/lib/subghz/protocols/princeton.c @@ -15,8 +15,8 @@ #define TAG "SubGhzProtocolPrinceton" static const SubGhzBlockConst subghz_protocol_princeton_const = { - .te_short = 400, - .te_long = 1200, + .te_short = 390, + .te_long = 1170, .te_delta = 300, .min_count_bit_for_found = 24, }; @@ -245,8 +245,7 @@ void subghz_protocol_decoder_princeton_feed(void* context, bool level, uint32_t break; case PrincetonDecoderStepCheckDuration: if(!level) { - if(duration >= ((uint32_t)subghz_protocol_princeton_const.te_short * 10 + - subghz_protocol_princeton_const.te_delta)) { + if(duration >= ((uint32_t)subghz_protocol_princeton_const.te_long * 2)) { instance->decoder.parser_step = PrincetonDecoderStepSaveDuration; if(instance->decoder.decode_count_bit == subghz_protocol_princeton_const.min_count_bit_for_found) { From c535ce9b768cb1a02a2eab77b37567d185bd83b5 Mon Sep 17 00:00:00 2001 From: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> Date: Mon, 28 Nov 2022 22:28:51 +0300 Subject: [PATCH 246/824] [FL-2997] Improve file name filtering #2047 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- lib/toolbox/path.c | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/lib/toolbox/path.c b/lib/toolbox/path.c index ce65aca4fd3..3d161a19630 100644 --- a/lib/toolbox/path.c +++ b/lib/toolbox/path.c @@ -95,22 +95,17 @@ bool path_contains_only_ascii(const char* path) { name_pos++; } - while(*name_pos != '\0') { - if((*name_pos >= '0') && (*name_pos <= '9')) { - name_pos++; - continue; - } else if((*name_pos >= 'A') && (*name_pos <= 'Z')) { - name_pos++; - continue; - } else if((*name_pos >= 'a') && (*name_pos <= 'z')) { - name_pos++; - continue; - } else if(strchr(" .!#\\$%&'()-@^_`{}~", *name_pos) != NULL) { - name_pos++; - continue; - } + for(; *name_pos; ++name_pos) { + const char c = *name_pos; - return false; + // Regular ASCII characters from 0x20 to 0x7e + const bool is_out_of_range = (c < ' ') || (c > '~'); + // Cross-platform forbidden character set + const bool is_forbidden = strchr("\\<>*|\":?", c); + + if(is_out_of_range || is_forbidden) { + return false; + } } return true; From 849afc8798b4a2594090a2abff9ca32bc44909bc Mon Sep 17 00:00:00 2001 From: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> Date: Mon, 28 Nov 2022 22:49:51 +0300 Subject: [PATCH 247/824] [FL-2998] IR TV Universal Remote refactor and docs (#2052) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Rename signals for IR TV universal remote * Update UniversalRemotes.md Co-authored-by: あく --- .../scenes/infrared_scene_universal_tv.c | 12 +- assets/resources/infrared/assets/tv.ir | 540 +++++++++--------- documentation/UniversalRemotes.md | 22 +- 3 files changed, 291 insertions(+), 283 deletions(-) diff --git a/applications/main/infrared/scenes/infrared_scene_universal_tv.c b/applications/main/infrared/scenes/infrared_scene_universal_tv.c index 583f21fa34a..e21bf8f9062 100644 --- a/applications/main/infrared/scenes/infrared_scene_universal_tv.c +++ b/applications/main/infrared/scenes/infrared_scene_universal_tv.c @@ -24,7 +24,7 @@ void infrared_scene_universal_tv_on_enter(void* context) { &I_Power_hvr_25x27, infrared_scene_universal_common_item_callback, context); - infrared_brute_force_add_record(brute_force, i++, "POWER"); + infrared_brute_force_add_record(brute_force, i++, "Power"); button_panel_add_item( button_panel, i, @@ -36,7 +36,7 @@ void infrared_scene_universal_tv_on_enter(void* context) { &I_Mute_hvr_25x27, infrared_scene_universal_common_item_callback, context); - infrared_brute_force_add_record(brute_force, i++, "MUTE"); + infrared_brute_force_add_record(brute_force, i++, "Mute"); button_panel_add_item( button_panel, i, @@ -48,7 +48,7 @@ void infrared_scene_universal_tv_on_enter(void* context) { &I_Vol_up_hvr_25x27, infrared_scene_universal_common_item_callback, context); - infrared_brute_force_add_record(brute_force, i++, "VOL+"); + infrared_brute_force_add_record(brute_force, i++, "Vol_up"); button_panel_add_item( button_panel, i, @@ -60,7 +60,7 @@ void infrared_scene_universal_tv_on_enter(void* context) { &I_Up_hvr_25x27, infrared_scene_universal_common_item_callback, context); - infrared_brute_force_add_record(brute_force, i++, "CH+"); + infrared_brute_force_add_record(brute_force, i++, "Ch_next"); button_panel_add_item( button_panel, i, @@ -72,7 +72,7 @@ void infrared_scene_universal_tv_on_enter(void* context) { &I_Vol_down_hvr_25x27, infrared_scene_universal_common_item_callback, context); - infrared_brute_force_add_record(brute_force, i++, "VOL-"); + infrared_brute_force_add_record(brute_force, i++, "Vol_dn"); button_panel_add_item( button_panel, i, @@ -84,7 +84,7 @@ void infrared_scene_universal_tv_on_enter(void* context) { &I_Down_hvr_25x27, infrared_scene_universal_common_item_callback, context); - infrared_brute_force_add_record(brute_force, i++, "CH-"); + infrared_brute_force_add_record(brute_force, i++, "Ch_prev"); button_panel_add_label(button_panel, 6, 11, FontPrimary, "TV remote"); button_panel_add_label(button_panel, 9, 64, FontSecondary, "Vol"); diff --git a/assets/resources/infrared/assets/tv.ir b/assets/resources/infrared/assets/tv.ir index 79478e7eb0a..8945465c7c6 100755 --- a/assets/resources/infrared/assets/tv.ir +++ b/assets/resources/infrared/assets/tv.ir @@ -1,1621 +1,1621 @@ Filetype: IR library file Version: 1 # -name: POWER +name: Power type: parsed protocol: SIRC address: 01 00 00 00 command: 15 00 00 00 # -name: POWER +name: Power type: parsed protocol: SIRC address: 10 00 00 00 command: 15 00 00 00 # -name: POWER +name: Power type: parsed protocol: NEC address: 08 00 00 00 command: 05 00 00 00 # -name: VOL+ +name: Vol_up type: parsed protocol: NEC address: 08 00 00 00 command: 00 00 00 00 # -name: VOL- +name: Vol_dn type: parsed protocol: NEC address: 08 00 00 00 command: 01 00 00 00 # -name: CH+ +name: Ch_next type: parsed protocol: NEC address: 08 00 00 00 command: 02 00 00 00 # -name: CH- +name: Ch_prev type: parsed protocol: NEC address: 08 00 00 00 command: 03 00 00 00 # -name: MUTE +name: Mute type: parsed protocol: NEC address: 08 00 00 00 command: 0b 00 00 00 # -name: POWER +name: Power type: parsed protocol: NECext address: 00 df 00 00 command: 1c 00 00 00 # -name: VOL+ +name: Vol_up type: parsed protocol: NECext address: 00 df 00 00 command: 4b 00 00 00 # -name: VOL- +name: Vol_dn type: parsed protocol: NECext address: 00 df 00 00 command: 4f 00 00 00 # -name: CH+ +name: Ch_next type: parsed protocol: NECext address: 00 df 00 00 command: 09 00 00 00 # -name: CH- +name: Ch_prev type: parsed protocol: NECext address: 00 df 00 00 command: 05 00 00 00 # -name: MUTE +name: Mute type: parsed protocol: NECext address: 00 df 00 00 command: 08 00 00 00 # -name: POWER +name: Power type: parsed protocol: Samsung32 address: 0e 00 00 00 command: 0c 00 00 00 # -name: MUTE +name: Mute type: parsed protocol: Samsung32 address: 0e 00 00 00 command: 0d 00 00 00 # -name: VOL+ +name: Vol_up type: parsed protocol: Samsung32 address: 0e 00 00 00 command: 14 00 00 00 # -name: VOL- +name: Vol_dn type: parsed protocol: Samsung32 address: 0e 00 00 00 command: 15 00 00 00 # -name: CH+ +name: Ch_next type: parsed protocol: Samsung32 address: 0e 00 00 00 command: 12 00 00 00 # -name: CH- +name: Ch_prev type: parsed protocol: Samsung32 address: 0e 00 00 00 command: 13 00 00 00 # -name: POWER +name: Power type: parsed protocol: RC6 address: 00 00 00 00 command: 0c 00 00 00 # -name: POWER +name: Power type: parsed protocol: Samsung32 address: 07 00 00 00 command: 02 00 00 00 # -name: POWER +name: Power type: parsed protocol: Samsung32 address: 07 00 00 00 command: E6 00 00 00 # -name: VOL+ +name: Vol_up type: parsed protocol: Samsung32 address: 07 00 00 00 command: 07 00 00 00 # -name: VOL- +name: Vol_dn type: parsed protocol: Samsung32 address: 07 00 00 00 command: 0B 00 00 00 # -name: CH+ +name: Ch_next type: parsed protocol: Samsung32 address: 07 00 00 00 command: 12 00 00 00 # -name: CH- +name: Ch_prev type: parsed protocol: Samsung32 address: 07 00 00 00 command: 10 00 00 00 # -name: MUTE +name: Mute type: parsed protocol: Samsung32 address: 07 00 00 00 command: 0F 00 00 00 # -name: POWER +name: Power type: parsed protocol: NEC address: 50 00 00 00 command: 17 00 00 00 # -name: POWER +name: Power type: parsed protocol: NEC address: 40 00 00 00 command: 12 00 00 00 # -name: POWER +name: Power type: parsed protocol: NECext address: 31 49 00 00 command: 63 00 00 00 # -name: POWER +name: Power type: parsed protocol: NEC address: aa 00 00 00 command: 1c 00 00 00 # -name: POWER +name: Power type: parsed protocol: NEC address: 38 00 00 00 command: 1c 00 00 00 # -name: POWER +name: Power type: parsed protocol: NECext address: 83 7a 00 00 command: 08 00 00 00 # -name: POWER +name: Power type: parsed protocol: NEC address: 53 00 00 00 command: 17 00 00 00 # -name: POWER +name: Power type: parsed protocol: NECext address: 18 18 00 00 command: c0 00 00 00 # -name: POWER +name: Power type: parsed protocol: NEC address: 38 00 00 00 command: 10 00 00 00 # -name: POWER +name: Power type: parsed protocol: NEC address: aa 00 00 00 command: c5 00 00 00 # -name: POWER +name: Power type: parsed protocol: NEC address: 04 00 00 00 command: 08 00 00 00 # -name: POWER +name: Power type: parsed protocol: NEC address: 18 00 00 00 command: 08 00 00 00 # -name: POWER +name: Power type: parsed protocol: NEC address: 71 00 00 00 command: 08 00 00 00 # -name: POWER +name: Power type: parsed protocol: NECext address: 80 6f 00 00 command: 0a 00 00 00 # -name: POWER +name: Power type: parsed protocol: NEC address: 48 00 00 00 command: 00 00 00 00 # -name: POWER +name: Power type: parsed protocol: NECext address: 80 7b 00 00 command: 13 00 00 00 # -name: POWER +name: Power type: parsed protocol: Samsung32 address: 0e 00 00 00 command: 14 00 00 00 # -name: POWER +name: Power type: parsed protocol: NECext address: 80 7e 00 00 command: 18 00 00 00 # -name: POWER +name: Power type: parsed protocol: NEC address: 50 00 00 00 command: 08 00 00 00 # -name: POWER +name: Power type: parsed protocol: NECext address: 80 75 00 00 command: 0a 00 00 00 # -name: POWER +name: Power type: parsed protocol: NECext address: 80 57 00 00 command: 0a 00 00 00 # -name: POWER +name: Power type: parsed protocol: Samsung32 address: 0b 00 00 00 command: 0a 00 00 00 # -name: POWER +name: Power type: parsed protocol: NEC address: aa 00 00 00 command: 1b 00 00 00 # -name: POWER +name: Power type: parsed protocol: NECext address: 85 46 00 00 command: 12 00 00 00 # -name: POWER +name: Power type: parsed protocol: Samsung32 address: 05 00 00 00 command: 02 00 00 00 # -name: POWER +name: Power type: parsed protocol: Samsung32 address: 08 00 00 00 command: 0f 00 00 00 # -name: POWER +name: Power type: parsed protocol: NEC address: 00 00 00 00 command: 01 00 00 00 # -name: POWER +name: Power type: parsed protocol: NEC address: 00 00 00 00 command: 01 00 00 00 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 634 2571 505 519 479 519 479 518 480 518 480 518 480 518 480 517 481 547 481 517 481 20040 590 2555 501 1007 999 997 510 548 480 486 512 486 512 486 542 485 513 516 482 116758 593 2552 504 1004 992 1004 514 514 514 483 515 513 485 483 545 482 516 482 516 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 525 1955 449 1999 476 4545 446 4544 478 2032 443 2006 469 2011 444 4577 445 4545 447 4574 448 2002 473 4547 444 34913 447 2032 443 2007 478 4542 449 4541 471 2039 446 2004 471 2008 447 4574 448 4543 448 4572 450 2030 445 4545 446 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 2445 582 1221 603 548 602 1191 609 572 602 1191 609 542 607 544 631 1172 603 568 606 545 605 566 608 543 26263 2414 611 1192 607 544 606 1197 602 569 606 1197 602 539 611 540 635 1168 606 565 610 541 608 563 587 564 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 3461 1802 439 452 444 1283 469 448 438 453 443 447 439 452 444 419 497 448 438 425 471 419 467 451 445 445 441 450 446 1281 471 420 466 425 471 446 440 424 472 445 461 456 440 451 445 445 441 450 446 1281 471 447 439 451 445 419 467 423 473 445 441 422 494 450 436 428 468 1259 493 425 471 1256 496 1259 472 1281 471 1285 467 423 473 417 469 1286 466 452 444 1283 469 1285 467 1261 491 1263 468 423 493 1260 471 74142 3578 1713 467 423 473 1281 471 420 466 452 444 419 467 424 472 418 488 429 467 451 445 445 441 450 446 444 442 449 437 1290 472 446 440 423 473 445 441 449 467 396 490 428 468 449 447 444 442 448 438 1289 473 445 441 450 446 444 442 449 437 426 470 421 495 422 464 426 470 1257 495 450 446 1254 488 1267 464 1290 472 1282 470 421 465 453 443 1284 468 450 446 1281 471 1283 469 1259 493 1261 470 448 468 1258 473 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 389 1737 280 796 253 744 295 754 274 775 274 776 273 1827 271 1828 270 805 254 1820 278 796 253 797 252 745 294 1806 302 773 245 48942 305 1821 277 798 251 746 303 747 271 778 271 1829 279 796 253 796 253 1821 277 798 251 1823 275 1824 274 1825 273 802 247 1827 271 42824 381 1745 272 804 245 752 297 753 275 773 276 774 275 1825 273 1826 272 803 246 1828 270 805 254 795 244 753 296 1804 294 781 247 48939 379 1746 271 804 245 779 270 753 275 774 275 1825 273 802 247 802 247 1827 271 804 245 1829 279 1820 278 1821 277 798 251 1823 275 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 562 1721 561 594 557 597 564 617 513 615 536 618 543 1715 566 1716 566 1692 559 594 567 588 563 618 543 611 540 615 536 618 543 1715 556 623 538 617 534 621 530 624 516 638 513 642 509 1722 560 620 541 1717 565 1692 559 1724 568 1715 556 1701 560 1723 559 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 8436 4189 538 1563 566 1559 539 510 559 543 516 507 542 560 509 540 509 567 512 1586 512 1562 567 1559 539 536 533 1566 542 507 562 513 536 540 509 22102 647 1478 559 1568 540 508 541 535 534 515 534 568 511 538 511 539 540 1585 513 1560 559 1567 541 534 535 1564 534 515 534 568 511 538 511 22125 644 1482 565 1561 537 511 538 564 515 508 541 535 534 541 508 516 563 1588 510 1563 556 1570 538 510 559 1567 541 534 515 535 534 541 508 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 929 825 1711 934 797 930 791 909 822 932 789 938 793 934 797 930 791 1719 899 856 1711 907 824 90848 923 830 1706 912 819 908 823 931 790 910 822 933 788 912 819 935 796 1714 904 850 1707 939 792 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 448 2031 444 2005 480 4540 452 4539 473 2037 438 2011 474 2006 449 4571 451 4539 453 4568 444 2036 449 4541 451 34906 527 1953 451 1998 477 4543 449 4542 480 2030 445 2004 471 2009 446 4575 447 4543 449 4572 450 1999 476 4545 446 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 9021 4496 567 1664 567 1690 561 567 533 1698 563 1667 564 1693 568 1663 568 586 534 567 563 565 535 594 536 591 539 589 531 571 539 563 567 1690 541 560 560 594 536 592 538 564 536 1695 566 1664 567 1690 561 1670 561 1696 555 1675 566 562 558 596 514 562 568 559 561 594 536 565 535 593 537 591 539 1665 566 1692 559 1671 560 1697 564 1666 565 1666 565 1693 558 1672 569 23181 9013 4504 569 1689 542 1689 562 565 535 1697 564 1666 646 1610 560 1672 559 595 535 593 537 590 510 566 564 590 540 588 532 596 514 588 542 1689 542 560 560 594 536 592 538 590 510 1694 567 1664 567 1690 561 1669 562 1695 556 1675 566 561 559 596 514 588 542 585 535 593 537 591 509 593 537 591 539 1665 566 1692 559 1671 560 1697 564 1667 564 1667 564 1693 558 1672 569 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 8041 3979 513 536 482 515 513 1559 509 515 514 1560 508 515 514 509 509 514 484 4000 533 1566 512 537 481 1566 512 537 481 1566 512 537 481 516 513 537 481 24150 8044 3977 505 518 510 539 479 1567 511 512 506 1568 510 539 479 543 485 538 480 3977 536 1564 514 534 484 1563 515 508 510 1563 515 508 510 540 489 534 484 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 383 2027 295 2112 271 2138 266 890 271 887 294 926 235 2114 300 887 274 915 236 2145 269 887 274 884 297 923 238 920 241 917 264 895 266 26573 384 2026 296 2111 273 2136 268 889 272 886 295 924 237 2113 301 886 275 914 237 2144 270 886 275 914 267 921 240 919 242 916 265 924 237 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 177 8474 175 5510 174 8476 173 8477 177 8504 171 5515 175 8476 178 8472 177 8473 176 5541 174 8476 173 45583 171 8481 178 5507 177 8473 176 8474 175 8506 173 5512 172 8478 176 8475 174 8476 178 5538 177 8474 175 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 8044 3976 506 517 511 1563 505 517 511 538 480 517 511 538 460 563 455 568 460 3993 530 545 483 1564 514 1559 509 1564 514 509 509 540 488 535 483 513 505 24150 8043 3978 514 509 509 1564 514 509 509 540 478 519 509 540 458 565 464 559 459 3994 529 546 482 1565 513 1560 508 1565 513 510 508 541 487 536 482 541 477 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 558 2942 450 10021 482 2989 484 10015 447 3024 449 10021 472 3100 485 2986 477 2994 500 2999 475 2996 477 2994 479 2992 482 3018 476 2995 479 6464 484 36270 477 3023 450 10020 473 2999 485 10014 448 3022 452 10019 474 3098 478 2994 480 2991 503 2996 477 2994 480 2992 482 2990 484 3015 479 2992 481 6462 485 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 587 2407 476 1062 965 547 482 547 451 577 452 546 452 22108 590 2404 479 1060 967 1028 510 519 509 548 480 487 511 120791 645 2411 472 1066 961 1065 483 515 514 514 504 493 515 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 172 7439 171 7441 169 7443 177 7434 176 7462 178 4887 176 4916 177 4914 169 7469 171 4920 174 4918 175 55174 176 7436 174 7437 173 7439 171 7440 175 7463 172 4894 174 4917 171 4921 172 7465 175 4916 178 4914 169 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 589 2556 500 524 474 554 454 544 454 543 455 543 455 543 455 543 445 583 446 552 446 20046 584 2561 505 1033 485 514 963 518 480 1032 516 512 476 522 506 491 507 522 476 116758 586 2560 506 1033 484 513 964 548 450 1031 507 522 476 521 507 490 508 521 477 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 586 2407 476 1063 964 578 450 548 450 547 481 517 481 577 451 546 452 546 472 556 452 545 453 575 453 514 484 544 484 543 455 14954 510 2483 481 1058 480 548 959 552 446 1067 481 516 512 546 482 515 992 1003 535 493 515 543 486 513 475 522 506 552 446 111671 589 2405 478 1061 477 551 967 514 484 1059 479 549 479 548 480 517 990 1036 512 516 482 546 483 515 503 525 483 544 454 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 8444 4180 537 1564 565 1560 538 1561 557 1568 540 1559 539 536 533 542 517 559 510 1563 535 1564 565 1561 537 512 567 1558 540 535 534 1566 542 1557 562 23122 564 1562 557 1569 539 1560 538 1587 542 1558 540 534 535 515 534 541 538 1587 511 1563 566 1560 538 536 533 1567 541 534 515 1585 533 1566 542 23166 561 1565 564 1561 537 1563 535 1590 539 1561 537 538 541 534 515 535 534 1564 534 1566 563 1563 535 540 539 1560 538 511 538 1588 541 1559 539 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 527 1923 481 1998 477 4543 449 4542 470 2040 445 2004 471 2009 446 4575 447 4543 449 4572 450 1999 476 2034 441 34899 524 1956 448 2001 474 4546 446 4545 477 2033 442 2007 478 2002 443 4578 444 4546 445 4575 447 2003 472 2037 448 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 533 1356 437 3474 427 3483 429 3455 436 1454 430 1459 405 28168 510 1379 434 3477 434 3476 425 3459 432 1457 427 1462 402 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 921 833 1714 932 789 938 793 934 797 903 818 909 822 905 816 938 793 1716 902 853 1714 905 816 90856 922 805 1742 931 790 937 794 933 788 939 792 935 796 930 791 937 794 1715 903 825 1742 904 817 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 179 7433 177 4915 178 7434 175 7436 174 7464 176 7435 175 4916 177 4915 173 4918 170 4922 171 4920 173 55174 175 7437 173 4919 174 7437 173 7439 171 7467 173 7438 172 4920 173 4919 174 4917 176 4915 178 4914 169 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 169 6731 176 6748 169 6730 177 6748 169 6755 172 4427 177 4447 178 6721 175 6749 178 4446 168 4456 169 54704 176 6723 174 6750 177 6723 173 6750 177 6747 170 4429 175 4449 176 6723 174 6751 176 4448 177 4447 178 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 3506 3494 876 830 840 2576 847 2568 844 862 819 2570 842 864 816 863 818 2570 842 836 844 2572 840 866 815 865 815 2573 839 867 813 866 814 2573 850 857 813 2575 847 2568 844 834 847 2569 843 835 845 2571 872 2571 842 32654 3512 3488 872 834 847 2570 842 2573 839 867 814 2574 849 858 822 857 813 2575 848 859 821 2566 846 832 848 860 821 2567 845 833 848 860 820 2568 844 834 847 2569 843 2572 840 838 843 2574 849 829 841 2575 868 2575 837 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 560 2939 453 10018 475 2996 477 10022 450 3021 452 10018 475 3097 478 6464 483 6460 477 6466 502 6469 479 2993 480 2990 484 36274 564 2936 456 10015 478 2993 481 10020 534 2936 456 10014 479 3093 482 6461 476 6466 482 6461 476 6495 473 2999 485 2986 477 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 10726 41047 10727 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 1617 4604 1559 1537 1560 1537 1560 4661 1533 33422 1613 4607 1566 1530 1556 1540 1536 4685 1539 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 174 4972 177 4910 173 4944 170 6988 174 6984 177 6951 175 6983 174 14269 177 4969 175 4912 176 4941 178 6979 172 6986 175 6953 178 6980 171 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 174 7067 176 10544 1055 719 1053 739 1054 738 953 822 1052 2566 169 3435 171 3431 175 3446 170 3432 174 3446 170 3432 174 3428 178 3442 174 3429 177 3425 171 39320 2323 4900 178 10543 1056 719 1053 739 1054 738 953 821 1053 2566 169 3435 171 3431 175 3445 171 3432 174 3446 170 3432 174 3428 178 3442 174 3428 178 3425 171 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 3506 3492 868 839 841 2575 848 858 822 2566 846 832 848 859 821 858 812 2576 847 860 820 2567 845 833 847 860 821 2568 844 862 818 2570 842 864 817 2571 841 2574 849 2567 845 861 820 2568 845 834 847 2570 873 2570 842 34395 3503 3496 874 833 847 2568 845 834 847 2570 842 864 816 835 846 862 819 2569 843 835 846 2571 841 865 816 864 816 2571 841 837 844 2573 850 857 813 2575 848 2567 845 2570 842 864 816 2572 840 838 842 2574 869 2574 838 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 170 8479 170 5516 178 8472 177 8474 175 8506 173 5513 171 8479 175 8476 178 8472 177 5540 175 8475 174 45584 177 8473 176 5509 175 8476 173 8477 176 8504 170 5516 178 8472 177 8474 175 8476 173 5543 172 8479 170 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 178 4969 170 6958 173 4944 175 6983 173 6956 174 6984 177 6980 171 16308 180 4966 173 6955 176 4941 172 6985 176 6982 169 6960 176 6982 174 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 585 2409 474 550 478 550 448 1033 994 1063 485 512 506 79916 585 2410 473 551 477 550 448 1034 993 1033 515 543 475 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 1192 1012 6649 26844 1192 1013 6648 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 3134 6105 6263 82963 3134 6105 6263 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 588 1511 567 1533 565 589 531 597 513 589 541 587 533 1565 533 569 541 1533 565 562 568 1531 537 1537 561 593 537 591 539 1507 561 1539 569 22152 586 1513 565 1535 563 590 540 562 538 564 566 588 542 1557 531 597 513 1534 564 590 540 1533 535 1539 559 568 562 592 538 1509 569 1531 567 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 923 831 1715 903 818 936 795 932 789 938 793 934 797 1713 1740 932 789 911 821 934 798 930 791 90853 928 799 1737 935 796 931 790 937 795 932 789 938 793 1717 1736 936 796 932 789 911 820 934 798 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 689 1461 566 1534 564 589 531 597 513 1534 564 564 566 1533 535 620 510 1537 561 592 538 1535 543 1531 567 587 533 595 535 1511 567 1533 565 22161 588 1512 566 1534 564 564 556 598 512 1535 563 565 565 1534 534 594 536 1538 560 593 537 1536 542 1532 566 587 543 585 535 1511 567 1534 564 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 588 2558 498 526 482 515 483 546 452 545 453 545 453 545 453 545 453 544 474 554 444 20047 583 2562 504 519 479 519 479 1003 515 483 1024 548 450 1001 995 1001 506 116771 593 2552 504 520 478 551 447 1004 513 514 993 549 449 1002 994 1002 516 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 587 2559 507 517 481 517 481 547 451 516 482 546 452 546 452 546 452 545 473 525 483 20038 592 2553 503 521 477 552 446 521 477 1004 514 515 992 1034 483 514 484 513 485 116769 593 2552 504 520 478 550 448 520 478 1003 515 514 993 1032 486 513 475 522 476 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 4558 4576 558 596 565 97881 4554 4579 565 589 562 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 1039 7202 958 4713 981 4713 961 7255 956 4739 955 16077 1038 7204 956 4714 980 4715 959 7256 954 4741 954 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 506 2638 510 516 482 516 482 546 452 546 452 545 453 545 453 545 453 575 443 554 444 20048 592 2554 502 1036 481 517 481 517 511 516 482 516 961 1035 513 515 483 514 484 116757 594 2552 504 1034 484 514 484 514 504 524 484 513 964 1032 506 522 476 492 506 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 170 7441 169 4924 174 7437 193 7444 171 7441 174 4918 170 7441 174 4917 171 7440 195 7443 172 7439 171 55187 174 7437 173 4919 175 7437 173 7438 172 7466 175 4891 172 7439 171 4921 172 7439 171 7441 174 7464 171 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 179 4967 172 4915 178 6980 171 4945 169 4948 176 4912 176 4941 178 16307 176 4969 175 4912 176 6982 174 4942 171 4945 169 4919 174 4943 176 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 1042 793 898 883 869 858 924 884 878 877 875 879 873 855 928 1717 901 854 1794 878 894 887 875 1716 902 89114 985 798 903 878 874 880 902 852 900 855 897 857 905 876 896 1722 906 849 1789 883 899 855 897 1721 897 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 170 8480 169 8481 178 8472 177 8474 175 8476 173 5513 176 8474 175 8476 173 8507 178 5509 175 8505 174 45558 175 8476 173 8476 173 8477 172 8478 171 8480 169 5517 177 8473 176 8474 175 8506 174 5512 172 8509 171 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 628 2577 499 528 480 545 453 544 454 544 454 544 454 544 454 543 455 543 475 553 445 20047 593 2553 503 521 477 551 447 1004 514 515 992 519 479 1003 515 513 485 543 455 116768 585 2561 505 519 479 519 479 1002 516 513 994 548 450 1001 506 522 476 521 477 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 8987 4505 568 586 534 594 536 591 509 567 563 591 539 563 567 587 513 1692 559 1672 559 1698 563 590 510 592 538 590 540 1664 567 1690 561 593 507 1699 562 1668 563 1694 567 1663 568 586 534 568 562 592 508 595 535 1695 536 592 538 1693 558 595 515 1690 561 593 517 585 535 567 563 39551 8994 4497 566 589 541 560 560 594 516 586 534 594 536 592 538 590 510 1695 566 1664 567 1690 561 593 507 595 535 566 564 1667 564 1693 558 596 534 1670 561 1670 561 1696 565 1666 565 589 541 587 533 595 515 587 533 1697 534 568 562 1695 556 572 538 1693 568 559 541 588 542 585 535 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 8987 4504 569 559 561 593 537 565 535 567 563 591 539 588 532 597 513 1691 560 568 562 592 508 1697 564 589 511 565 565 1692 559 1672 559 569 561 1669 562 566 564 564 566 1665 566 588 542 586 534 1670 561 567 563 565 535 593 537 591 539 1692 539 589 541 586 534 594 536 592 508 40679 8987 4504 569 585 535 593 537 591 509 566 564 591 539 588 532 596 514 1691 560 594 536 592 508 1697 564 589 511 591 539 1692 559 1671 560 594 536 1669 562 592 538 590 540 1664 567 587 543 585 535 1670 561 593 537 565 535 593 537 591 539 1691 540 588 542 586 534 594 536 592 508 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 298 1828 270 804 245 1829 279 1820 278 797 252 771 278 1822 276 1824 274 800 249 1825 273 802 247 776 252 771 278 1822 276 799 250 48931 388 1737 280 796 253 1821 277 1822 276 798 251 1823 275 800 249 774 275 1825 273 776 273 1801 297 1828 270 1829 279 796 253 1821 277 42813 301 1825 273 801 248 1826 272 1827 271 804 245 805 244 1830 278 1821 277 798 251 1823 275 799 250 774 244 779 280 1820 278 796 253 48926 382 1744 354 696 271 1828 270 1829 279 796 253 1821 277 797 252 746 303 1823 275 774 275 1799 299 1826 272 1827 271 804 245 1829 279 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 177 5508 176 5539 176 8475 174 5542 173 8478 171 5545 170 8481 168 8482 177 8473 176 5541 174 8476 173 45573 169 5517 177 5538 177 8473 176 5541 174 8476 173 5544 171 8479 170 8481 178 8472 177 5540 175 8475 174 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 175 8474 175 8475 174 8477 172 8478 171 8480 169 8481 178 5538 177 5510 174 5542 173 5543 172 5544 171 45575 177 8472 177 8474 175 8476 173 8477 172 8478 171 8481 178 5507 177 5539 176 5540 175 5542 173 5543 172 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 8050 3971 511 1562 516 1558 510 539 490 1557 511 1563 515 533 485 538 490 533 485 3983 530 1569 509 1564 514 1559 509 1565 513 1560 508 515 513 536 482 541 488 24152 8042 3979 514 1560 508 1565 513 510 508 1565 513 1560 508 515 513 536 482 541 488 3980 533 1567 511 1562 516 1557 511 1563 515 1558 510 539 489 534 484 539 490 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 175 8475 174 5512 177 8473 176 8475 174 8506 179 5508 176 8474 175 8475 174 8477 172 5544 171 8480 169 45587 176 8475 174 5511 173 8477 177 8473 176 8504 170 5516 178 8472 177 8473 176 8475 174 5542 173 8478 171 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 176 7436 174 7438 172 7439 171 7441 174 7463 172 7440 170 4921 172 4919 174 4917 176 4916 177 4914 174 55176 175 7437 173 7439 191 7446 174 7438 177 7434 176 7435 175 4917 176 4915 179 4914 174 4917 171 4946 178 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 176 7435 175 4917 177 7435 175 7436 189 7449 176 7435 175 7437 173 4918 175 7436 174 4918 175 4916 178 55171 175 7436 174 4918 170 7441 174 7438 177 7460 170 7441 174 7438 177 4914 174 7437 178 4914 169 4922 171 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 8049 3973 509 1564 514 509 509 1564 514 535 483 1564 514 535 483 540 489 535 483 3980 533 1566 512 537 481 1566 512 511 507 1566 512 537 481 542 486 511 507 24149 8045 3976 516 1558 510 512 516 1557 511 512 516 1558 510 512 516 507 511 512 506 3984 539 1560 508 515 513 1560 508 541 488 1560 508 541 488 536 482 514 504 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 366 202 867 527 170 130516 343 227 863 529 168 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 176 5992 176 1372 176 1320 177 1345 172 1349 179 1370 178 1317 175 4444 176 1346 171 1351 177 1345 172 1349 179 1344 174 5963 175 65574 172 5995 178 1344 174 1348 175 1347 175 1347 170 1378 170 1325 172 4447 178 1344 173 1349 169 1353 175 1347 170 1351 177 5961 177 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 497 1478 488 511 467 1482 494 531 447 1477 499 501 466 533 445 530 468 507 471 529 438 12857 488 1485 491 509 469 1481 495 529 448 1476 490 510 468 532 445 529 469 480 498 502 465 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 982 6313 961 2662 903 2718 929 2719 907 6363 900 2722 904 6365 909 6361 903 6368 906 2742 905 46364 989 6307 906 2716 911 2712 925 2723 903 6367 907 2715 901 6369 905 6365 909 6361 903 2745 902 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 599 257 975 317 177 130649 308 228 974 319 175 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 176 4969 170 6958 173 6985 177 6981 171 6958 178 6980 177 6981 170 16308 180 4966 173 6955 176 6982 169 6988 174 6956 175 6983 173 6984 172 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 178 5990 173 1349 174 1348 175 1348 174 4444 176 1346 171 1351 177 4416 178 1344 173 1348 169 1353 175 1347 170 1351 177 9048 177 65573 173 5995 173 1348 175 1348 174 1347 176 4444 171 1351 177 1345 173 4421 173 1348 169 1352 176 1347 170 1351 177 1345 172 9053 177 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 174 4972 177 4910 173 6985 177 4940 174 6984 178 6951 175 6983 174 14269 177 4968 176 4912 176 6981 175 4941 173 6985 177 6953 178 6979 172 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 1318 225 177 130305 1609 231 171 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 585 2410 473 1065 962 581 447 519 479 580 448 549 449 579 449 518 480 548 480 517 481 517 481 577 451 516 482 576 452 545 453 14955 510 2483 481 1058 480 549 969 543 445 1037 511 547 481 546 482 516 482 545 484 545 483 514 484 544 963 1063 485 543 445 111612 626 2427 557 954 513 512 995 547 451 1031 507 521 508 551 477 520 478 550 478 549 479 488 510 548 969 1057 481 517 481 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 203 272 1215 302 171 130229 1423 275 178 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 176 7436 174 7438 192 7446 174 7437 178 7434 176 7435 175 4917 176 4915 178 4913 175 4917 197 7440 175 55130 286 7377 177 7435 175 7437 173 7438 172 7466 174 7411 178 4913 170 4921 172 4919 174 4918 175 7436 174 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 175 7437 173 4918 170 7441 174 7437 178 7460 170 7441 179 7433 177 4915 178 4913 170 4922 171 4920 173 55124 291 7372 172 4921 172 7439 171 7441 174 7463 172 7440 170 7442 178 4913 170 4922 171 4920 173 4918 175 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 172 8477 172 8478 171 8480 169 5516 179 8502 178 5509 175 8475 174 8476 173 8479 170 5545 170 8480 169 45588 176 8473 176 8474 175 8476 173 5513 177 8504 171 5515 174 8476 178 8472 177 8473 176 5541 174 8476 173 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 177 7436 174 4918 175 7436 174 4918 175 7462 178 4887 176 4916 177 4914 174 4917 171 4921 172 4919 175 55184 175 7435 175 4918 175 7436 174 4918 175 7462 178 4914 174 4917 171 4920 173 4919 174 4917 176 4915 178 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 508 2484 480 1060 967 544 485 544 454 574 454 543 455 573 445 553 445 522 506 552 446 552 446 551 477 551 447 581 447 520 478 550 478 15908 626 2427 476 1062 476 522 995 547 451 1061 477 520 508 550 478 519 479 519 999 1058 959 1037 990 582 446 1035 483 111666 590 2404 479 1059 479 549 968 543 455 1027 511 548 480 517 511 486 512 546 961 1065 962 1034 993 579 449 1032 486 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 175 8475 174 8475 174 8477 172 8478 171 8480 169 5517 178 8473 175 8475 179 8501 173 5513 176 8505 169 45562 179 8472 177 8473 176 8474 175 8476 173 8478 171 5515 174 8476 178 8473 176 8505 174 5512 172 8508 171 120769 178 93377 175 7437 173 4919 174 7437 173 7439 171 7467 173 7439 171 7441 174 4918 170 4921 172 7439 171 7467 173 55167 171 7440 170 4922 171 7440 195 7443 172 7439 171 7441 174 7438 177 4915 173 4918 195 7442 173 7439 170 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 295 1805 273 776 242 1808 270 754 244 1806 272 777 241 758 270 754 264 760 268 756 272 14149 297 1802 266 784 244 1805 263 762 246 1803 265 785 244 780 248 751 267 758 271 753 265 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 535 1723 569 585 566 615 536 619 542 586 565 616 535 1722 539 615 536 1721 561 620 541 587 564 617 534 621 540 588 563 618 543 1714 537 617 534 621 540 615 536 618 543 612 539 615 536 1722 560 594 567 1717 534 1723 569 1714 557 1700 643 1639 643 1641 559 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 502 2521 504 521 997 545 453 575 453 545 453 575 454 22097 591 2433 511 513 994 1062 476 552 476 522 476 552 476 120839 628 2455 509 515 992 1064 484 513 505 493 515 543 475 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 591 2404 479 1060 967 1029 509 519 509 549 479 518 480 79926 585 2408 556 956 989 1033 515 544 484 543 475 522 476 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 586 2560 506 518 480 548 450 548 450 517 481 517 481 517 481 547 451 546 482 516 482 20040 590 2556 500 1038 480 518 969 543 455 543 475 1006 511 517 481 517 511 486 481 116778 584 2561 505 1033 485 513 964 548 450 548 480 1001 506 522 476 522 506 521 446 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 917 206 175 186 170 21561 170 2280 175 2274 502 1929 174 2276 169 5178 170 2261 173 3724 498 1932 171 2279 176 2273 172 3709 172 2277 178 3720 171 17223 177 7619 174 2275 170 2279 176 2256 168 2280 175 5172 176 2256 168 3729 173 2276 179 2253 171 2278 177 3703 178 2271 174 3724 177 17251 170 7627 177 2272 173 2276 169 2263 171 2277 178 5169 169 2262 172 3726 175 2256 168 2280 175 2274 171 3710 171 2278 177 3720 171 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 565 233 653 313 170 130328 752 235 233 107 229 398 177 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 586 2439 505 549 968 1027 511 517 511 517 481 547 481 21583 584 2439 505 520 997 1059 479 549 479 518 480 548 480 120894 593 2432 501 522 995 1061 477 521 507 520 478 550 478 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 558 8032 474 8115 503 8116 482 8108 480 8141 477 5239 476 8114 504 8115 483 8107 481 5236 509 8111 477 45290 554 8036 481 8108 510 8110 478 8112 476 8145 473 5243 482 8107 511 8109 479 8111 477 5240 505 8115 473 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 173 7438 172 4920 173 7438 172 4920 173 7465 175 4890 173 7439 171 4920 173 4919 174 4917 176 4915 178 55179 170 7441 169 4924 174 7437 178 4913 175 7463 172 4893 170 7441 174 4918 170 4922 171 4920 173 4918 175 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 225 745 222 774 193 778 200 797 175 769 193 777 201 771 196 774 224 747 220 776 202 769 198 248 220 252 196 250 218 253 225 746 221 250 198 248 220 252 226 246 222 223 225 248 220 252 216 229 219 253 225 247 221 277 176 243 220 279 189 230 228 244 224 248 220 252 196 250 218 253 215 257 201 770 197 799 189 257 201 271 197 37716 222 749 218 778 200 771 196 801 172 772 200 771 196 774 193 777 221 750 217 780 197 773 194 251 217 255 193 279 199 273 195 749 218 254 194 252 226 245 223 275 178 242 221 277 201 245 193 253 225 247 221 250 218 254 194 252 226 272 196 223 225 248 220 251 217 255 193 253 225 247 221 251 197 773 194 803 174 297 176 244 219 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 3503 2655 197 642 876 2568 844 834 846 833 848 860 821 831 839 2576 847 2569 843 2572 841 2574 849 858 823 857 813 866 815 865 815 2572 841 2575 848 2568 844 2570 842 836 844 863 818 834 847 861 820 2568 845 2571 872 2571 842 32651 3505 3495 875 2567 845 861 820 832 849 859 811 840 840 2576 847 2568 844 2571 842 2574 849 830 840 867 814 838 842 865 815 2572 840 2575 848 2568 845 2571 841 865 815 864 817 834 846 861 819 2569 843 2572 871 2572 840 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 347 677 219 252 196 276 202 742 225 798 174 770 192 778 200 771 196 775 223 748 219 777 200 770 197 249 219 253 195 251 227 271 197 747 220 252 216 229 219 253 225 273 195 277 176 244 219 253 215 230 228 244 224 248 220 252 196 250 218 280 198 247 201 245 223 249 219 253 195 251 227 245 223 248 200 797 175 795 198 248 200 246 222 37666 344 678 228 245 223 222 226 771 196 800 198 747 220 750 217 753 224 773 194 776 202 769 198 799 173 246 227 245 223 249 199 247 221 749 218 280 198 247 201 245 223 249 219 253 195 251 227 244 224 248 200 272 196 250 218 254 194 252 226 245 223 249 199 274 194 251 227 245 193 253 225 273 195 251 217 753 225 746 221 251 197 275 193 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 174 7437 173 7440 174 7437 178 7433 177 7461 169 7442 178 4914 174 4917 171 4921 172 4919 174 7463 177 55163 177 7435 174 7437 173 7438 172 7440 175 7463 172 7413 176 4915 178 4913 175 4917 171 4920 174 7438 172 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 8042 3979 513 510 508 541 487 1559 509 515 513 1560 508 515 513 510 508 541 477 3981 532 1568 510 1563 515 1558 510 1564 514 1559 509 514 514 535 483 540 488 24151 8042 3979 513 536 482 541 487 1560 508 541 488 1560 508 541 487 536 482 541 477 3980 533 1566 512 1561 507 1566 512 1562 516 1557 511 538 491 533 485 538 490 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 8989 4501 562 1696 565 1665 566 562 568 586 514 588 542 586 534 594 536 1667 564 591 539 1692 539 563 567 1690 561 1669 562 1669 562 1696 565 562 538 564 566 588 542 586 534 1670 561 568 562 592 538 590 510 592 538 590 540 561 539 563 567 561 569 585 535 593 507 595 535 593 537 39547 8987 4504 569 1689 562 1668 563 591 539 589 511 591 539 589 541 561 559 1671 560 568 562 1695 536 592 538 1693 558 1673 568 1663 568 1689 562 592 508 594 536 592 538 590 540 1664 567 561 569 559 561 567 543 585 535 593 537 591 509 593 537 591 539 589 541 587 513 589 541 587 533 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 8988 4504 640 487 562 592 538 590 510 592 538 590 540 588 532 596 514 614 516 1689 562 1668 563 1695 536 1695 566 1664 567 1690 612 1618 643 1430 801 1613 648 481 558 570 540 589 541 587 533 595 535 592 508 594 536 592 538 1667 564 1693 558 1672 569 1688 563 1668 644 1586 563 1694 567 40630 8994 2265 557 96833 8987 2273 538 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 173 7439 171 4922 172 7439 171 4921 172 7466 174 4917 176 4915 173 4918 170 7441 174 7438 192 7445 175 55181 174 7437 173 4918 175 7437 173 4919 175 7463 177 4888 175 4917 176 4915 178 7460 170 7440 175 7437 178 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 1636 4610 1563 1533 1584 7760 1561 28769 1641 4606 1557 1539 1588 7757 1564 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 590 2404 479 1059 968 575 454 544 454 574 454 543 455 543 475 522 476 552 476 552 446 551 447 581 448 520 478 581 447 519 479 549 479 15967 587 2407 476 1062 476 522 995 547 451 1030 508 520 509 550 478 519 479 519 998 1028 999 1057 481 547 960 1066 482 111641 587 2407 476 1063 485 513 994 547 451 1031 507 551 477 551 477 520 478 550 968 1059 968 1027 511 548 959 1036 512 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 588 2406 477 1061 966 577 451 516 482 545 483 545 453 575 443 524 484 514 504 554 454 543 455 543 475 553 445 583 445 521 477 582 446 15969 585 2409 474 1065 483 514 993 549 449 1033 515 543 475 553 475 522 476 552 965 1030 997 576 452 1029 998 1028 479 111668 587 2407 475 1063 485 543 964 517 481 1031 507 552 476 551 477 520 478 550 968 1028 999 574 454 1027 990 1036 481 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 174 7439 196 7441 174 7437 173 7439 171 7441 174 7438 177 7460 170 7442 178 7433 177 4915 173 4918 170 55186 174 7438 172 7440 170 7441 174 7438 177 7461 169 7416 173 7464 176 7435 175 7437 173 4918 175 4917 176 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 173 7438 172 7441 174 7438 177 7434 176 7462 168 7443 177 7435 175 4917 176 4915 173 7438 177 4941 173 55166 174 7438 197 7441 174 7437 173 7439 171 7441 174 7438 177 7460 170 4922 171 4893 195 7443 172 4920 173 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 175 7436 179 4913 175 4917 171 4920 173 4945 169 4896 177 7435 175 7436 174 7464 176 4916 177 4914 169 55180 170 7441 169 4924 169 4922 171 4920 173 4919 174 4917 176 7435 175 7463 177 7434 176 4916 177 4914 174 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 927 827 1709 936 796 932 789 938 793 1717 1736 936 795 932 789 1721 927 827 1709 909 822 90851 898 829 1738 934 798 930 791 936 796 1715 1738 934 797 930 791 1719 899 828 1739 934 797 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 176 5991 177 1372 176 1320 177 1344 173 1349 174 1374 169 1327 170 4449 176 1346 171 1351 177 1345 172 1349 168 1353 175 5963 175 65573 173 5995 178 1344 174 1348 175 1348 174 1347 170 1378 170 1325 172 4447 178 1344 174 1349 169 1353 175 1347 170 1352 176 5961 177 # -name: POWER +name: Power type: parsed protocol: NEC address: 71 00 00 00 command: 4a 00 00 00 # -name: POWER +name: Power type: parsed protocol: NEC address: 60 00 00 00 command: 03 00 00 00 # -name: POWER +name: Power type: parsed protocol: NEC address: 60 00 00 00 command: 00 00 00 00 # -name: POWER +name: Power type: parsed protocol: NEC address: 42 00 00 00 command: 01 00 00 00 # -name: POWER +name: Power type: parsed protocol: NECext address: 50 ad 00 00 command: 00 00 00 00 # -name: POWER +name: Power type: parsed protocol: NECext address: 50 ad 00 00 command: 02 00 00 00 # -name: POWER +name: Power type: parsed protocol: NEC address: 50 00 00 00 command: 3f 00 00 00 # -name: POWER +name: Power type: parsed protocol: Samsung32 address: 06 00 00 00 command: 0f 00 00 00 # -name: POWER +name: Power type: parsed protocol: NEC address: 08 00 00 00 command: 12 00 00 00 # -name: POWER +name: Power type: parsed protocol: Samsung32 address: 08 00 00 00 command: 0b 00 00 00 # -name: POWER +name: Power type: parsed protocol: NECext address: 83 55 00 00 command: c2 00 00 00 # -name: POWER +name: Power type: parsed protocol: NEC address: 00 00 00 00 command: 51 00 00 00 # -name: POWER +name: Power type: parsed protocol: NECext address: 00 bd 00 00 command: 01 00 00 00 # -name: POWER +name: Power type: parsed protocol: Samsung32 address: 00 00 00 00 command: 0f 00 00 00 # -name: POWER +name: Power type: parsed protocol: Samsung32 address: 16 00 00 00 command: 0f 00 00 00 # -name: POWER +name: Power type: parsed protocol: NEC address: 01 00 00 00 command: 01 00 00 00 # -name: POWER +name: Power type: parsed protocol: NECext address: 80 68 00 00 command: 49 00 00 00 # -name: POWER +name: Power type: parsed protocol: NECext address: 86 02 00 00 command: 49 00 00 00 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 2383 609 1214 600 612 580 1213 608 614 583 1210 605 607 586 616 611 1192 599 613 608 614 579 613 615 607 26670 2387 601 1212 600 612 589 1214 603 609 586 1217 595 607 594 618 606 1187 602 610 609 613 588 614 610 612 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 178 7761 176 11308 546 957 540 1958 538 970 537 1955 541 962 545 1953 543 964 543 962 545 957 540 970 548 960 547 1945 541 1950 546 965 542 1953 543 962 545 1945 540 970 537 1958 538 7881 172 7744 172 11318 536 971 536 1956 540 963 534 1964 542 966 541 1951 535 968 539 971 536 971 536 969 538 964 533 1965 541 1954 542 963 534 1956 540 971 536 1959 537 968 539 1951 535 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 3444 1767 413 486 420 1343 419 478 418 485 411 490 416 479 417 489 417 482 414 485 421 482 414 483 413 490 416 485 411 1343 419 487 419 480 416 483 413 490 416 482 414 488 418 483 413 483 413 492 414 1345 417 482 414 489 417 480 416 487 419 482 414 481 415 491 415 484 412 1347 415 488 418 1338 414 1348 414 1347 415 1340 412 494 412 487 419 1340 412 491 415 1341 421 1341 421 1340 412 1343 419 487 419 1339 413 74311 3445 1753 437 461 445 1316 446 456 440 455 441 465 441 458 438 461 445 458 438 460 436 466 440 461 445 451 445 460 446 1313 439 460 446 457 439 459 437 465 441 460 446 450 436 469 437 462 444 456 440 1322 440 457 439 464 442 459 437 459 437 468 438 461 445 455 441 462 444 1312 440 463 443 1317 445 1310 442 1323 439 1320 442 457 439 464 442 1315 437 466 440 1320 442 1313 439 1326 446 1313 439 460 446 1317 445 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 278 1845 274 808 271 806 273 812 278 805 275 805 274 1840 279 1844 275 809 281 1836 272 806 274 812 278 805 274 1842 277 802 277 44956 279 1842 277 804 275 802 277 808 271 811 279 1838 281 798 271 814 276 1844 275 806 273 1841 278 1845 274 1846 273 808 271 1843 276 44959 275 1845 274 807 272 805 275 811 279 804 275 805 274 1839 280 1844 275 808 271 1845 274 805 274 811 279 804 275 1841 278 801 278 44955 280 1841 278 802 277 801 278 807 272 810 280 1837 271 807 272 813 277 1843 276 805 274 1839 280 1843 276 1845 274 807 272 1842 277 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 881 909 1750 928 875 927 876 921 872 929 874 927 876 918 875 930 873 1815 874 924 1745 937 876 88694 880 922 1747 933 880 914 879 926 877 922 871 927 876 927 876 920 873 1818 871 929 1740 934 879 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 509 1717 504 629 512 631 510 627 504 633 508 633 508 1713 508 1719 512 1713 508 625 505 637 504 633 508 629 512 629 512 623 508 1719 512 626 505 628 513 631 510 627 514 623 507 632 509 1713 508 632 509 1716 505 1715 506 1724 507 1716 505 1719 512 1715 506 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 506 493 505 4059 505 5051 501 506 502 4065 499 511 497 4063 501 5058 504 504 504 4060 504 5052 500 507 501 4066 498 5056 506 5042 500 515 503 119614 504 505 503 4065 499 5051 501 511 497 4069 505 499 499 4072 502 5050 502 506 502 4066 498 5053 499 512 506 4060 504 5044 498 5061 501 508 500 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 8887 4470 532 1738 513 1708 533 1708 533 577 533 576 534 569 531 582 538 1705 536 571 539 1707 534 571 539 571 539 570 540 1699 532 581 539 568 532 575 535 576 534 571 539 571 539 569 541 1698 533 1717 534 1708 533 1710 531 1716 535 1706 535 1711 540 1705 536 567 533 580 540 567 533 39042 8915 2231 530 94849 8917 2256 535 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 8313 4161 515 1574 514 1571 507 569 510 562 507 563 506 562 507 568 511 562 507 1580 508 1577 511 1582 506 567 513 1575 513 554 505 571 509 564 505 22604 513 1573 505 1589 509 563 506 564 505 562 507 569 511 562 507 563 506 1579 509 1584 514 1576 512 558 511 1574 514 562 507 565 515 556 513 22593 514 1581 507 1583 505 564 505 563 506 570 510 563 506 564 505 562 507 1586 512 1578 510 1577 511 557 512 1581 507 566 514 556 513 555 514 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 8735 4383 558 573 557 550 560 568 562 544 566 1722 560 540 560 1732 560 544 566 1719 563 1701 560 1723 559 1704 557 574 556 1699 562 574 556 1703 559 1727 565 1698 563 1721 561 546 564 1723 559 541 559 577 564 541 559 571 560 548 562 565 566 1697 565 567 564 1693 558 1733 559 1701 560 39926 8754 2247 565 92341 8758 2243 589 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 3298 3336 821 2506 825 881 820 2505 826 2529 823 856 825 2524 817 866 825 2528 813 2513 818 888 813 862 819 887 814 865 826 2522 820 864 827 2526 815 861 820 887 814 2510 821 885 816 863 818 2531 821 2512 819 2559 793 32401 3298 3349 818 2507 824 882 819 2509 822 2527 814 868 823 2530 821 855 826 2531 821 2504 827 879 822 857 824 875 816 867 824 2529 823 854 827 2530 821 853 817 889 822 2506 825 873 818 866 825 2527 814 2513 818 2564 788 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 8840 4439 532 566 534 566 534 1668 532 1674 536 1669 531 562 538 566 534 563 537 1667 533 567 533 563 537 563 537 562 538 1662 538 1671 529 568 532 565 535 566 534 1668 532 1674 536 1669 531 562 538 1672 528 1675 536 1668 532 1675 535 559 531 1676 534 565 535 558 532 1678 532 565 535 562 538 563 537 1665 535 565 535 1670 530 1669 531 572 538 1666 534 1669 531 1676 534 22777 8858 4447 535 92577 8858 4413 538 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 289 2112 261 2109 295 2101 262 918 294 912 259 915 297 2098 265 916 296 909 262 2107 297 905 266 913 289 918 263 910 292 909 262 918 294 24789 263 2106 298 2098 265 2110 294 913 258 916 296 905 266 2108 296 911 260 914 288 2107 266 914 298 908 263 911 291 910 261 919 293 913 258 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 8898 4453 569 578 573 577 574 571 570 1745 567 1747 565 1743 569 583 568 579 572 1740 572 1744 568 1742 570 579 572 576 575 568 573 1746 566 1746 566 580 571 1745 567 577 574 576 575 1739 573 569 572 581 570 577 574 1738 574 576 575 1735 567 1748 574 574 567 1742 570 1748 574 1737 575 41005 8876 2261 571 93850 8898 2264 568 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 3215 1637 410 430 405 439 406 1242 408 435 410 1242 408 428 407 440 405 435 410 1240 410 1243 407 431 404 440 405 437 408 1238 402 1254 406 434 401 439 406 438 407 431 404 440 405 437 408 428 407 439 406 434 401 439 406 438 407 1241 409 434 401 441 404 432 403 444 401 1249 401 439 406 438 407 1241 409 434 401 441 404 433 402 444 401 1249 401 439 406 1248 402 436 409 434 401 441 404 433 402 444 401 439 406 45471 3239 1614 403 435 400 444 401 1250 400 437 408 1248 402 438 407 433 402 442 403 1245 405 1248 423 420 405 431 404 443 402 1248 402 1248 422 421 404 435 400 443 402 440 405 431 404 443 402 438 407 433 402 442 403 435 400 443 402 1250 400 436 399 447 408 432 403 438 407 1246 404 434 401 443 402 1250 400 436 399 447 408 432 403 437 408 1246 404 434 401 1253 407 434 401 436 399 447 408 432 403 437 408 436 399 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 7928 3948 504 515 503 518 500 1583 505 516 502 1584 504 510 508 516 502 516 502 3952 510 1579 509 507 501 1587 501 519 510 1571 507 518 500 517 501 517 501 23073 7931 3943 509 514 504 515 503 1578 500 524 505 1580 508 510 508 514 504 511 507 3951 511 1576 502 513 505 1586 502 515 503 1582 506 515 503 513 505 516 502 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 8837 4464 538 610 531 619 532 613 538 1748 534 1750 532 611 540 613 538 609 532 615 536 614 537 608 533 1753 539 1745 537 606 535 618 533 614 537 609 532 619 532 613 538 611 540 609 532 611 540 1748 534 1749 533 1750 532 1754 538 1743 539 1747 535 1750 532 1747 535 618 533 613 538 44105 8864 2224 537 93399 8912 2202 538 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 492 4975 496 4993 498 498 500 4056 498 504 494 4055 499 4963 497 532 496 4031 492 4996 495 502 496 4060 494 4972 499 4990 491 506 492 4063 491 511 497 4052 492 4970 490 539 500 4027 496 103358 500 4961 500 4995 496 505 493 4056 498 499 499 4057 497 4969 491 533 496 4026 497 4997 494 508 490 4059 495 4967 494 5001 490 511 497 4053 491 505 493 4063 491 4975 496 528 490 4032 491 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 2357 610 1183 592 1191 614 1179 595 1188 618 584 613 1180 593 588 613 1190 612 590 605 587 613 589 605 587 25398 2355 623 1180 591 1181 627 1186 589 1183 618 584 616 1187 587 584 614 1189 615 587 605 587 615 587 609 593 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 3425 3482 848 2607 846 2639 845 2608 846 2639 845 2612 852 2624 850 2613 851 911 851 885 847 919 843 891 851 915 847 890 852 907 845 897 845 917 845 891 851 915 847 887 845 2639 845 2612 852 2625 849 2613 851 2630 844 34282 3455 3478 852 2601 852 2633 851 2605 848 2629 845 2617 847 2633 851 2604 849 917 845 890 852 913 849 889 843 915 847 896 846 916 846 890 852 914 848 885 847 919 843 895 847 2630 844 2617 847 2634 850 2605 848 2636 848 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 500 500 498 4066 498 5058 504 502 506 4061 503 508 500 4060 504 5055 497 5055 497 511 497 4071 503 5047 505 507 501 4065 499 5049 503 512 496 124314 501 508 500 4067 507 5043 499 513 505 4060 504 501 497 4073 501 5052 500 5052 500 512 496 4066 498 5058 504 506 502 4058 506 5053 499 509 499 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 176 7763 174 8817 169 10842 544 1955 541 967 540 1952 544 959 538 972 546 962 545 1947 538 1952 544 967 540 1955 541 964 543 1947 539 972 546 1949 547 7873 170 7746 170 10350 175 11811 543 960 537 1961 535 972 535 970 537 965 542 1956 540 1956 540 965 542 1947 539 973 534 1960 536 969 538 1952 534 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 2570 2682 1189 1208 1186 2665 1186 1217 1187 2668 1183 1215 1179 2671 1190 2695 1186 1187 1187 2692 1179 2671 1190 1213 1181 1193 1191 1207 1187 2663 1188 1215 1179 2676 1185 46941 2563 2685 1186 1191 1183 2698 1184 1188 1186 2691 1180 1197 1187 2694 1187 2666 1185 1210 1184 2674 1187 2695 1186 1185 1189 1206 1188 1189 1185 2696 1186 1186 1187 2689 1182 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 873 916 1773 906 877 925 878 919 874 928 875 925 878 1806 1770 914 879 1809 870 928 1772 88684 870 926 1774 908 875 926 877 917 876 929 874 925 878 1809 1777 905 877 1808 871 930 1770 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 874 906 877 912 1767 904 879 908 874 918 875 915 878 907 875 920 873 1795 874 914 1775 897 875 88704 879 914 868 922 1767 897 875 919 874 915 878 911 872 920 873 914 879 1792 877 914 1775 889 873 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 3325 1560 406 444 401 453 402 1226 404 449 406 1226 404 443 402 454 401 449 406 1224 406 1228 402 446 409 444 401 451 404 1223 407 1230 410 440 405 445 410 443 402 446 409 444 401 451 404 442 403 453 402 448 407 443 402 451 404 1224 406 448 407 444 401 445 410 446 409 1221 409 442 403 1231 409 439 406 1227 403 450 405 441 404 452 403 1227 403 448 407 446 409 439 406 447 408 444 401 445 400 456 410 440 405 52348 3320 1553 403 445 400 454 401 1230 400 447 408 1228 402 449 406 444 401 452 403 1225 405 1229 431 421 404 442 403 454 401 1229 401 1229 431 423 402 446 399 455 400 451 404 442 403 453 402 448 407 443 402 451 404 444 401 452 403 1229 401 445 400 457 398 451 404 446 399 1235 405 443 402 1232 408 444 401 1225 405 452 403 447 398 452 403 1231 399 449 406 447 408 443 402 444 401 456 399 450 405 445 400 453 402 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 990 915 980 909 986 924 981 2829 981 934 981 2823 987 923 982 2828 982 933 982 906 989 33895 989 907 988 927 988 900 985 2841 979 915 980 2851 980 909 986 2840 980 914 981 934 981 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 821 5754 848 2490 841 2492 819 2524 817 5726 845 2492 839 5727 844 5757 845 5727 854 2483 848 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 5092 1621 406 2599 406 2598 407 2605 1653 1616 411 2619 1609 1606 1654 1618 409 2623 382 2600 405 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 8879 4446 566 1747 565 584 567 1744 568 581 570 1744 568 574 567 586 565 582 569 578 563 1753 570 575 566 1749 563 585 566 1743 569 1749 563 1748 564 582 569 1747 565 580 571 578 573 1741 571 572 569 584 567 580 571 1741 571 578 573 1738 564 1751 572 577 564 1744 568 1750 572 1740 572 41007 8906 2257 565 93855 8872 2265 567 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 1144 1010 6795 26754 1151 997 6798 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 1144 1009 1120 1006 1143 1991 1116 26758 1146 1006 1123 1003 1146 1988 1119 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 170 46238 169 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 8906 4165 572 1672 569 1678 563 640 572 637 565 643 569 633 569 643 569 1674 567 639 563 1684 567 637 565 1681 570 1675 566 1673 568 1681 570 636 566 640 572 637 565 639 563 1684 567 640 572 630 572 640 572 634 568 1675 566 1681 570 1670 571 638 564 1681 570 1669 572 1678 563 1680 571 40485 8898 2252 570 85621 8955 2194 567 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 446 1191 449 1194 446 1195 445 1204 446 1200 1316 459 447 1193 447 1202 448 1197 443 1201 449 1191 449 34491 443 1204 446 1197 443 1198 442 1207 443 1202 1314 436 440 1201 449 1199 441 1205 445 1198 442 1199 441 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 4268 4327 522 1593 516 1603 516 1597 522 519 520 520 519 515 514 531 518 520 519 519 520 521 518 519 520 1597 522 1595 514 1597 522 1599 520 1595 514 524 515 1604 515 521 518 523 516 524 515 519 520 525 514 524 515 1599 520 522 517 1596 513 1605 514 1602 517 1595 514 1607 522 1592 516 40481 8748 2187 523 93986 8721 2189 521 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 247 3006 244 153 178 1007 251 117 637 597 381 678 218 156 175 529 382 679 217 157 174 24963 247 3038 252 117 219 994 249 119 217 483 250 117 173 531 278 779 173 167 169 569 383 679 217 156 175 126538 246 3039 251 118 218 995 247 120 195 504 249 118 172 532 277 780 172 168 178 560 382 680 216 157 174 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 8872 4454 568 579 572 578 573 572 569 581 570 1744 568 575 566 587 564 582 569 1743 569 1747 565 1745 567 1748 564 584 567 1741 571 1747 565 1747 565 1747 565 1751 571 1739 563 1752 570 578 563 1745 567 1751 572 1741 571 575 566 585 566 578 573 577 564 1750 573 571 570 583 568 579 572 41007 8905 2258 574 93846 8871 2266 566 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 4278 4318 521 517 522 520 519 517 522 520 519 521 518 516 513 531 518 520 519 1595 514 1605 514 1599 520 1598 521 1595 513 1598 521 1600 519 1596 513 1602 517 1601 518 1595 514 1604 515 525 514 520 519 526 513 525 514 524 515 527 522 513 516 526 513 1603 516 1595 514 1607 522 1593 516 40481 8749 2186 524 93985 8722 2189 521 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 2746 8423 2744 19607 2746 19601 2742 8431 2745 8424 2742 19608 2745 8419 2747 19608 2745 19607 2746 8422 2744 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 170 6683 174 4889 175 10382 372 849 821 2498 176 10378 264 2479 842 2492 839 2475 846 2483 838 2481 840 2495 836 2477 844 845 846 37009 172 6681 176 4888 175 10381 373 848 924 2395 844 2490 174 10383 248 2492 172 10386 245 2495 169 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 8898 4453 569 578 573 577 574 1737 565 584 567 582 569 574 567 1751 572 1741 571 1741 571 1744 568 576 575 1741 571 1743 569 1739 573 579 572 575 566 581 570 580 571 574 567 1748 575 1739 573 570 571 582 569 578 573 1739 573 1742 570 1740 572 577 574 575 566 1742 570 1749 574 1738 574 41006 8875 2262 570 93850 8907 2256 566 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 8872 4454 568 1745 567 1749 563 1746 566 584 567 1747 565 1743 569 583 568 579 572 575 566 584 567 578 563 1752 571 578 563 580 571 1747 565 1747 565 581 570 1746 566 578 573 577 564 1751 572 571 570 583 568 579 572 1740 572 578 563 1747 565 1750 573 576 565 1743 569 1749 563 1749 563 41017 8906 2257 565 93855 8872 2265 567 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 179 2644 178 8174 170 2646 176 8181 173 2648 174 8177 177 2640 172 5419 174 5413 170 2649 173 2644 178 2646 176 2646 176 5409 174 28831 174 2651 171 8183 171 2648 174 8174 170 2655 177 8177 177 2642 170 5412 171 5420 173 2649 173 2646 176 2640 172 2653 169 5419 174 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 174 2648 174 8178 176 2640 171 8185 169 2653 169 8182 172 2645 177 2647 557 2264 558 5027 174 5410 173 5418 175 5413 170 28837 168 2649 173 8184 170 2652 170 8181 173 2643 169 8188 176 2646 176 2643 168 2648 174 5417 176 5411 172 5413 170 5413 170 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 178 706 266 139 268 137 173 173 198 234 178 126768 175 299 169 86 275 130 267 138 172 230 177 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 554 1922 584 3809 582 3782 578 3821 580 1920 555 1941 544 1922 574 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 476 1470 465 3479 474 3469 474 3477 475 1467 468 1471 474 27473 472 1474 472 3475 467 3478 475 3468 474 1471 475 1468 467 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 301 2188 236 2169 235 2205 230 1009 234 1070 183 1064 179 2198 206 1046 207 1069 173 2207 207 1042 211 1062 201 1043 210 1032 231 1005 237 1039 234 1006 236 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 8872 4453 570 1744 568 582 569 1741 571 1744 568 580 571 1738 564 1754 569 578 563 584 567 1749 563 581 570 580 571 1743 569 573 568 585 566 1746 566 580 571 580 571 1739 563 586 565 1749 563 579 572 582 569 577 564 1748 564 1752 571 574 567 1748 564 584 567 1742 570 1748 564 1747 565 41015 8898 2265 567 93853 8875 2262 570 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 8904 4446 576 572 569 581 570 575 566 584 567 582 569 1739 573 579 572 575 566 1746 566 1750 572 1738 574 1741 571 1742 570 573 568 1750 572 1740 572 1740 572 578 573 572 569 581 570 1744 568 574 567 586 575 572 569 578 573 1742 570 1740 572 1743 569 579 572 1737 565 1753 570 1743 569 41010 8881 2257 565 93855 8903 2259 573 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 8878 4448 564 584 567 583 568 577 564 586 565 584 567 1741 571 582 569 1743 569 1743 569 1746 566 1744 568 1747 565 1749 563 579 572 1747 565 581 570 1743 569 1746 566 578 573 1743 569 579 572 571 570 583 568 579 572 575 566 584 567 1743 569 581 570 1744 568 1740 572 1746 566 1746 566 41013 8898 2264 568 93853 8872 2265 567 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 8900 4450 572 575 566 585 566 578 573 1743 569 579 572 1736 566 587 574 572 569 1743 569 1747 565 1745 567 582 569 1745 567 575 566 1753 570 1742 570 1742 570 1746 566 578 573 1742 570 578 573 570 571 582 569 578 573 574 567 583 568 1742 570 579 572 1742 570 1738 574 1744 568 1744 568 41012 8871 2266 566 93855 8903 2258 574 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 8872 4454 568 1745 567 1749 563 1747 565 1750 562 1752 571 1737 565 1754 568 1743 569 578 563 587 564 581 570 580 571 577 564 579 572 581 570 576 565 1748 564 1751 572 1739 563 1752 571 1743 569 1739 563 590 571 575 566 581 570 580 571 574 567 583 568 580 571 572 569 1750 562 1749 563 41017 8905 2257 575 93846 8871 2266 566 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 176 7764 173 11361 544 1951 545 1948 176 8815 171 2319 177 2322 174 2321 175 2318 178 2313 173 18281 170 7746 170 11369 536 1954 542 1957 539 969 538 1954 542 1949 536 1962 534 1960 174 2320 176 2315 170 2328 178 2317 168 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 172 23035 176 2267 178 2285 170 2270 175 2288 177 11602 222 2218 216 2246 173 183 173 935 221 218 174 864 221 1250 171 1310 223 2219 216 2247 218 1244 223 216 176 869 170 1296 217 2239 216 1254 223 216 176 866 219 9127 169 7642 173 2284 171 2273 172 2290 175 2263 171 10143 178 10623 222 1245 222 217 175 1843 220 2226 219 1264 223 1237 174 183 178 952 219 2222 223 216 176 866 219 1249 172 9190 173 7637 178 2266 169 2286 169 2280 175 2284 171 10125 175 10622 224 215 177 868 216 2228 217 2238 171 1299 224 215 177 865 174 183 173 934 222 216 176 1848 215 1247 220 1265 222 762 170 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 7410 1482 382 2742 375 2747 380 2751 1630 1535 400 2724 1657 1529 1629 1538 407 2720 407 2716 401 2722 4125 1549 376 2751 376 2748 379 2744 1626 1541 405 2723 1658 1530 1628 1531 404 2726 401 2726 401 2723 4124 1542 383 2747 380 2747 380 2745 1626 1534 401 2729 1652 1539 1629 1532 403 2719 408 2722 405 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 4277 4319 520 518 521 521 518 518 521 1597 522 1594 515 1597 522 1599 520 1594 515 1600 519 1600 519 1594 515 526 513 527 522 512 517 528 521 517 522 516 513 1605 514 522 517 525 514 525 514 521 518 526 513 525 514 1600 519 523 516 1597 522 1596 513 1603 516 1595 514 1607 522 1593 516 40481 8749 2186 524 93986 8721 2188 522 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 1325 431 445 1199 1317 455 441 1207 443 1202 1294 457 449 1190 440 1209 441 1205 445 1198 1318 454 442 93237 1320 434 442 1201 1325 448 448 1200 440 1205 1291 460 446 1193 447 1202 448 1198 442 1201 1325 447 449 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 8876 4450 572 575 566 585 566 578 573 577 564 585 566 577 564 589 572 574 567 1745 567 1749 563 1747 565 1750 562 1751 571 1737 565 1754 569 1743 569 1743 569 1747 565 579 572 1743 569 579 572 571 570 583 568 579 572 575 566 584 567 1743 569 581 570 1744 568 1740 572 1746 566 1746 566 41013 8898 2265 567 93853 8873 2264 568 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 300 1791 297 744 295 743 296 751 298 745 294 747 302 736 293 1837 271 745 294 747 302 1793 295 752 297 1802 296 1801 297 742 297 1806 302 31592 296 1801 297 742 297 749 300 744 295 745 294 745 294 752 297 1829 269 746 293 745 294 1809 300 744 295 1802 296 1799 299 748 301 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 178 2003 177 2899 177 1996 174 2908 168 2910 177 2000 170 2004 176 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 8898 4173 564 642 570 1677 564 1677 564 645 567 640 572 631 571 641 571 635 567 639 563 1683 568 1673 568 641 571 636 566 637 565 647 565 641 571 634 568 642 570 1671 570 639 563 1682 569 632 570 643 569 636 566 1677 564 1683 568 636 566 1680 571 636 566 1674 567 1682 569 1674 567 40489 8904 2246 566 85626 8902 2248 564 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 8897 4454 569 578 573 1743 569 576 565 1750 572 576 575 1733 569 584 567 1745 567 1745 567 583 568 1742 570 579 572 1742 570 573 568 1750 573 574 567 580 571 580 571 573 568 582 569 580 571 572 569 584 567 1744 568 1744 568 1748 575 1735 567 1749 574 1740 572 1736 566 1752 571 576 575 41005 8878 2259 563 93858 8901 2260 572 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 8880 4446 566 1747 565 585 566 578 573 577 564 1750 573 571 570 1748 564 582 569 578 573 1743 569 1741 571 1744 568 580 571 1737 565 588 563 1749 563 583 568 582 569 576 565 1750 573 576 565 578 573 580 571 576 565 1747 565 1751 571 1738 564 586 565 1749 563 1745 567 1751 572 1741 571 41008 8905 2258 574 93846 8871 2266 566 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 3892 3856 525 978 529 977 530 969 528 977 530 974 523 975 532 1924 531 971 526 1924 531 975 532 1916 529 976 531 1921 524 1947 508 1925 530 1920 525 1926 529 1925 530 970 527 1927 528 976 531 1915 530 978 529 1921 1033 9201 3871 3867 524 977 530 975 522 981 526 972 525 983 524 978 529 1921 524 982 525 1923 532 973 524 1928 527 971 526 1931 524 1950 505 1922 523 1931 524 1924 531 1923 532 972 525 1922 523 985 533 1918 527 975 532 1922 1032 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 8900 4451 571 576 575 1742 570 1739 573 1742 570 578 573 1736 566 1752 570 576 575 1737 575 575 566 579 572 578 573 1741 571 571 570 583 568 1745 567 579 572 578 573 1738 574 575 566 1748 575 568 573 581 570 576 575 1737 565 1751 572 573 568 1747 576 573 568 1741 571 1747 565 1747 565 41014 8878 2260 562 93858 8901 2261 571 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 875 904 1745 924 879 913 880 1786 873 919 874 917 1742 922 871 1802 877 912 1747 921 872 88714 880 907 1742 930 873 917 876 1788 871 924 879 910 1749 919 874 1798 871 916 1743 928 875 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 4758 1543 403 2731 407 2726 402 2739 1601 1514 401 2760 1580 1530 1577 1540 406 2758 400 2708 1602 1535 400 2740 408 2729 409 2726 401 2731 1599 1520 405 2758 1572 1540 1578 1532 403 2737 431 2706 1604 1535 411 2722 405 2734 403 2734 404 2731 1599 1512 403 2764 1576 1539 1578 1533 402 2730 428 2712 1608 1534 402 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 8873 4453 570 578 563 1753 570 575 566 1749 563 585 566 1742 570 583 568 1744 568 1744 568 582 569 1741 571 578 573 1741 571 572 569 1749 563 583 568 1745 567 1748 564 1746 566 583 568 581 570 1738 564 589 572 1740 572 574 567 584 567 577 564 1752 571 1743 569 573 568 1751 572 575 566 41014 8900 2263 569 93851 8878 2259 563 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 171 313 176 798 337 133 600 232 170 126777 176 65 169 496 176 200 609 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 500 527 491 4033 500 4986 495 510 498 4054 500 499 499 4048 496 4974 497 530 499 4026 497 4989 492 513 495 4057 497 4967 493 4992 499 506 492 4060 494 505 493 4054 500 98563 497 529 500 4025 498 4988 493 512 496 4056 498 501 497 4050 493 4976 495 532 497 4028 495 4991 500 505 493 4059 495 4969 491 4994 497 508 490 4062 492 507 491 4056 498 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 879 901 871 1796 1770 903 869 917 876 916 877 913 880 906 877 918 875 914 879 1788 1768 1784 875 87826 871 921 872 1797 1769 897 875 919 874 915 878 910 873 920 873 914 879 913 870 1800 1776 1767 871 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 874 905 1774 894 878 914 879 908 875 917 876 915 878 907 876 919 874 1793 876 913 1777 895 878 88703 872 920 1769 901 872 913 870 925 878 910 873 916 877 916 877 910 873 1798 871 919 1770 894 878 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 3339 1714 415 445 420 1283 418 440 415 448 418 444 422 435 420 446 420 440 415 445 421 443 412 445 421 443 412 449 416 1279 412 455 421 439 416 443 412 452 414 444 411 452 414 1287 414 442 413 453 413 1287 414 446 419 444 422 437 418 445 421 441 414 442 413 452 414 447 419 1281 420 443 412 1285 416 1287 414 1287 414 1282 419 447 419 441 414 1286 415 448 418 1280 421 1282 419 443 412 1283 418 448 418 1283 418 73871 3336 1694 414 444 411 1291 410 452 414 442 413 453 413 448 418 442 413 450 416 443 412 450 416 447 419 437 418 448 418 1282 419 441 414 449 417 442 413 449 417 446 420 436 419 1287 414 446 420 440 415 1288 413 445 410 453 413 449 417 439 416 450 416 445 421 439 416 447 419 1279 412 451 415 1287 414 1282 419 1287 414 1285 416 444 411 453 413 1285 416 447 419 1283 418 1278 413 453 413 1287 414 446 419 1284 417 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 4411 4332 558 1660 561 596 565 612 559 597 564 617 565 585 566 620 561 591 560 620 561 595 566 611 560 597 564 1653 558 592 559 627 565 589 562 617 564 1630 560 617 564 592 559 22591 4439 4322 558 1639 561 618 563 590 561 622 560 592 559 624 557 597 564 612 559 600 561 618 563 590 561 622 559 1628 562 621 561 594 567 609 562 597 564 1652 559 595 566 617 564 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 3444 1767 413 487 419 1274 417 481 415 1277 414 488 418 1267 414 492 414 1276 415 484 412 1282 419 478 418 1275 416 1276 415 480 416 1280 421 479 417 1272 419 1275 416 1272 419 1274 417 484 412 484 412 493 413 1277 414 485 421 1273 418 479 417 486 420 1271 420 476 420 486 420 479 417 482 414 1280 411 1276 415 488 418 1273 418 478 418 488 418 481 415 1275 416 487 419 478 418 485 411 1280 421 475 421 1275 416 1273 418 69071 3439 1759 441 456 440 1253 438 463 443 1243 438 468 438 1251 440 460 446 1247 444 454 442 1251 440 461 445 1241 440 1256 445 455 441 1248 443 461 445 1242 439 1254 447 1245 446 1240 441 465 441 458 438 462 444 1249 442 456 440 1252 439 463 443 452 444 1252 439 461 445 454 442 461 445 452 444 1249 442 1250 441 454 442 1254 437 463 443 456 440 463 443 1245 446 456 440 462 444 451 445 1251 440 460 436 1253 438 1256 445 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 8705 4317 583 682 581 688 585 678 585 1721 581 1722 580 681 582 690 583 682 581 1721 581 1725 587 1713 579 689 584 683 580 1718 584 1724 588 1714 588 677 586 683 580 683 580 1726 586 680 583 678 585 687 586 679 584 1718 584 1721 581 1719 583 685 588 1716 586 1713 579 1729 583 1719 583 41145 8706 2217 585 94686 8705 2217 584 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 8888 4470 532 576 534 576 534 571 539 572 538 1706 535 568 532 581 529 578 532 1711 530 581 529 1711 530 1717 534 574 536 1703 538 575 535 572 538 1705 536 1711 530 1711 530 1716 535 1709 532 571 529 585 535 571 529 579 531 579 531 574 536 574 536 573 537 1701 530 1720 531 1712 529 39045 8912 2261 540 94838 8911 2235 536 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 874 915 1774 904 879 923 870 1816 1770 1800 1776 904 879 1805 874 931 1769 909 874 88698 876 927 1773 904 879 922 871 1819 1767 1796 1770 914 879 1809 870 928 1772 910 873 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 178 7753 174 2308 178 2284 171 2316 170 2298 177 10228 174 10724 223 1256 221 217 175 1868 169 2293 172 1327 216 1263 178 1316 217 2245 220 218 174 887 218 1261 170 10707 174 7752 175 2296 169 2314 171 2293 172 2307 179 10216 176 6265 174 10729 219 1258 219 1272 215 1268 173 2310 221 1255 222 217 175 877 172 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 532 1692 559 561 529 574 567 559 531 566 564 555 535 1694 557 568 532 1691 560 560 530 573 557 568 532 565 565 555 535 567 563 1688 533 564 567 554 536 567 563 562 538 558 562 558 532 1697 565 561 539 1683 558 1689 532 1697 564 1687 534 1689 562 1684 537 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 874 915 1774 904 879 923 870 927 876 1814 876 925 1775 900 872 1821 879 920 1769 909 874 88702 871 926 1774 907 876 925 878 917 876 1817 873 927 1773 905 878 1813 876 921 1769 912 871 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 8884 4474 538 569 531 1716 535 570 530 580 530 1715 536 567 533 580 530 577 533 1710 531 1715 536 1705 536 1710 531 1714 537 1702 529 1720 531 1712 529 578 532 1715 536 1705 536 1710 531 577 533 570 530 584 536 571 529 1714 537 573 537 568 532 578 532 1713 538 1701 530 1719 532 1711 530 39044 8913 2260 531 94848 8907 2239 532 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 446 1694 455 1706 453 1705 424 628 452 597 452 622 458 1673 456 625 455 594 455 1706 454 590 459 621 459 591 458 616 453 591 458 622 539 22750 459 1675 454 1733 427 1712 427 622 458 588 451 622 458 1681 458 618 451 595 454 1705 455 598 451 625 454 592 457 616 453 598 451 626 535 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 3429 3445 875 2555 868 875 867 871 871 2560 873 868 874 2551 872 874 868 871 871 869 873 870 872 865 867 2565 868 873 869 2556 867 2567 866 874 868 2560 873 870 872 2555 868 2564 869 2586 847 2577 846 2589 844 870 872 32983 3470 3430 869 2559 874 868 874 867 875 2550 873 873 869 2559 874 865 867 877 875 862 870 873 869 872 870 2555 868 877 875 2554 869 2559 874 869 873 2554 869 873 869 2562 871 2553 870 2590 843 2586 847 2581 842 876 866 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 8873 4452 571 1743 569 580 571 574 567 583 568 1746 566 1742 570 1748 564 582 569 578 563 1753 570 1740 572 1743 569 579 572 571 570 583 568 1744 568 579 572 578 573 572 569 1746 566 582 569 574 567 586 565 582 569 1743 569 1746 566 1744 568 581 570 1745 567 1741 571 1747 565 1746 566 41014 8898 2264 568 93853 8874 2263 569 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 8902 4448 574 1739 573 1742 570 575 566 584 567 581 570 573 568 585 566 1746 566 580 571 580 571 1739 573 1742 570 1743 569 1739 573 1745 567 579 572 1741 571 1744 568 1742 570 1745 567 1747 565 1743 569 1749 573 1739 573 573 568 583 568 576 575 575 566 583 568 575 566 587 574 572 569 41011 8871 2266 566 93854 8902 2260 572 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 4275 4320 519 520 519 523 516 520 519 1599 520 520 519 515 524 521 518 520 519 1595 524 1595 524 1588 520 521 518 1599 520 1591 517 1603 516 1599 520 1595 524 1594 525 1588 521 1597 522 518 521 513 526 519 520 518 521 517 522 520 519 517 522 519 520 1597 522 1589 519 1601 518 1597 522 40475 8724 2186 514 93995 8752 2183 516 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 10380 4892 599 620 592 605 597 620 592 604 598 623 589 2081 598 627 595 2080 599 2101 598 2105 574 2099 590 2088 591 2111 599 591 590 2116 594 599 593 626 596 2082 597 619 593 2086 593 626 596 594 598 627 595 598 594 2106 593 604 598 2100 589 607 595 2107 593 2079 590 2116 594 2082 750 41149 8722 2109 590 94682 8727 2104 596 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 8879 4446 566 1747 565 1751 571 573 568 582 569 580 571 571 570 584 567 1744 568 579 572 578 573 1737 565 1751 572 1742 570 1738 564 1754 568 578 573 574 567 584 567 577 564 1752 571 577 564 580 571 582 569 577 564 1748 564 1752 571 1739 563 587 564 1750 572 1736 566 1752 571 1742 570 41009 8903 2260 572 93848 8880 2257 565 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 data: 4271 4324 515 1600 519 1600 519 1594 515 1603 516 1601 518 1593 516 1605 514 1601 518 520 519 522 517 520 519 522 517 523 516 518 521 523 516 522 517 1598 521 1597 522 1591 518 1600 519 521 518 516 523 522 517 521 518 520 519 523 516 520 519 522 517 1599 520 1591 518 1603 516 1599 520 40477 8753 2183 516 93993 8724 2185 515 # -name: POWER +name: Power type: raw frequency: 38000 duty_cycle: 0.33 diff --git a/documentation/UniversalRemotes.md b/documentation/UniversalRemotes.md index 0a833727e23..186a0e65a1f 100644 --- a/documentation/UniversalRemotes.md +++ b/documentation/UniversalRemotes.md @@ -1,7 +1,19 @@ # Universal Remotes +## Televisions +Adding your TV set to the universal remote is quite straightforward. Up to 6 signals can be recorded: `Power`, `Mute`, `Vol_up`, `Vol_dn`, `Ch_next`, `Ch_prev`. Any of them can be omitted if not supported by the TV. + +Each signal is recorded using the following algorithm: +1. Get the remote and point it to Flipper's IR receiver. +2. Start learning a new remote if it's the first button or press `+` to add a new button otherwise. +3. Press a remote button and save it under a corresponding name. +4. Repeat steps 2-3 until all required signals are saved. + +The signal names are self-explanatory. Don't forget to make sure that every recorded signal does what it's supposed to. + +If everything checks out, append these signals **to the end** of the [TV universal remote file](/assets/resources/infrared/assets/tv.ir). + ## Audio Players -### Recording signals -Adding your audio player to the universal remote is quite straightforward. 8 signals can be recorded: `Power`, `Play`, `Pause`, `Vol_up`, `Vol_dn`, `Next`, `Prev`, `Mute`. Any of them can be omitted if it is not supported by the device. +Adding your audio player to the universal remote is done in the same manner as described above. Up to 8 signals can be recorded: `Power`, `Play`, `Pause`, `Vol_up`, `Vol_dn`, `Next`, `Prev`, `Mute`. Any of them can be omitted if not supported by the player. The signal names are self-explanatory. On many remotes, the `Play` button doubles as `Pause`. In this case record it as `Play` omitting the `Pause`. @@ -9,12 +21,7 @@ Make sure that every signal does what it's supposed to. If everything checks out, append these signals **to the end** of the [Audio players universal remote file](/assets/resources/infrared/assets/audio.ir). -The order of signals is not important, but they must be preceded by a following comment: `# Model: ` in order to keep the library organised. - -When done, open a pull request containing the changed file. - ## Air Conditioners -### Recording signals Air conditioners differ from most other infrared-controlled devices because their state is tracked by the remote. The majority of A/C remotes have a small display which shows current mode, temperature and other settings. When the user presses a button, a whole set of parameters is transmitted to the device, which must be recorded and used as a whole. @@ -49,6 +56,7 @@ Test the file against the actual device. Make sure that every signal does what i If everything checks out, append these signals **to the end** of the [A/C universal remote file](/assets/resources/infrared/assets/ac.ir). +## Final steps The order of signals is not important, but they must be preceded by a following comment: `# Model: ` in order to keep the library organised. When done, open a pull request containing the changed file. From 0261dc30759d56a54e29e57d3a5edfa54f1913c3 Mon Sep 17 00:00:00 2001 From: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> Date: Tue, 29 Nov 2022 12:08:08 +0300 Subject: [PATCH 248/824] [FL-2957] Unified Info API, App Error, Data Xchange (#1998) * Update protobuf definitions * Add Property subsystem entry point function * Key-based system info and power info function stubs * Remove unneeded functions * Working power info * Working system info * Replace #defines with string literals * Remove unneeded field * Simplify system info formatting * Refactor output callback handling * Handle the last info element correctly * Optimise power info, rename methods * Add comments * Add power debug * Remove unneeded definitions * Rename some files and functions * Update protobuf definitions * Implement App GetError and DataExchange APIs * Send GetErrorReply with correct command_id * Add RPC debug app stub * Add more scenes * Add warning, increase stack size * Add receive data exchange scene * Improve data exchange * Add notifications * Update application requirements * Bump format version for property-based infos * Correctly reset error text * RCP: sync protobuf repo to latest release tag Co-authored-by: Aleksandr Kutuzov --- .../debug/rpc_debug_app/application.fam | 10 + .../debug/rpc_debug_app/rpc_debug_app.c | 138 ++++++++ .../debug/rpc_debug_app/rpc_debug_app.h | 54 ++++ .../scenes/rpc_debug_app_scene.c | 30 ++ .../scenes/rpc_debug_app_scene.h | 29 ++ .../scenes/rpc_debug_app_scene_config.h | 8 + .../rpc_debug_app_scene_input_data_exchange.c | 40 +++ .../rpc_debug_app_scene_input_error_code.c | 60 ++++ .../rpc_debug_app_scene_input_error_text.c | 40 +++ ...pc_debug_app_scene_receive_data_exchange.c | 70 ++++ .../scenes/rpc_debug_app_scene_start.c | 57 ++++ .../scenes/rpc_debug_app_scene_start_dummy.c | 30 ++ .../rpc_debug_app_scene_test_app_error.c | 57 ++++ .../rpc_debug_app_scene_test_data_exchange.c | 58 ++++ applications/services/cli/cli_commands.c | 2 +- applications/services/power/power_cli.c | 6 +- applications/services/rpc/rpc.c | 7 +- applications/services/rpc/rpc_app.c | 120 +++++++ applications/services/rpc/rpc_app.h | 13 + applications/services/rpc/rpc_i.h | 1 + applications/services/rpc/rpc_property.c | 107 +++++++ applications/services/rpc/rpc_system.c | 4 +- assets/protobuf | 2 +- firmware/targets/f7/api_symbols.csv | 13 +- firmware/targets/f7/furi_hal/furi_hal_info.c | 299 +++++++++++++----- firmware/targets/f7/furi_hal/furi_hal_power.c | 298 +++++++++++------ .../targets/furi_hal_include/furi_hal_info.h | 15 +- .../targets/furi_hal_include/furi_hal_power.h | 22 +- lib/toolbox/property.c | 33 ++ lib/toolbox/property.h | 39 +++ 30 files changed, 1446 insertions(+), 216 deletions(-) create mode 100644 applications/debug/rpc_debug_app/application.fam create mode 100644 applications/debug/rpc_debug_app/rpc_debug_app.c create mode 100644 applications/debug/rpc_debug_app/rpc_debug_app.h create mode 100644 applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene.c create mode 100644 applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene.h create mode 100644 applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_config.h create mode 100644 applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_input_data_exchange.c create mode 100644 applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_input_error_code.c create mode 100644 applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_input_error_text.c create mode 100644 applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_receive_data_exchange.c create mode 100644 applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_start.c create mode 100644 applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_start_dummy.c create mode 100644 applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_test_app_error.c create mode 100644 applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_test_data_exchange.c create mode 100644 applications/services/rpc/rpc_property.c create mode 100644 lib/toolbox/property.c create mode 100644 lib/toolbox/property.h diff --git a/applications/debug/rpc_debug_app/application.fam b/applications/debug/rpc_debug_app/application.fam new file mode 100644 index 00000000000..d71065afae4 --- /dev/null +++ b/applications/debug/rpc_debug_app/application.fam @@ -0,0 +1,10 @@ +App( + appid="rpc_debug", + name="RPC Debug", + apptype=FlipperAppType.DEBUG, + entry_point="rpc_debug_app", + requires=["gui", "rpc_start", "notification"], + stack_size=2 * 1024, + order=10, + fap_category="Debug", +) diff --git a/applications/debug/rpc_debug_app/rpc_debug_app.c b/applications/debug/rpc_debug_app/rpc_debug_app.c new file mode 100644 index 00000000000..314be5e7225 --- /dev/null +++ b/applications/debug/rpc_debug_app/rpc_debug_app.c @@ -0,0 +1,138 @@ +#include "rpc_debug_app.h" +#include + +#include + +static bool rpc_debug_app_custom_event_callback(void* context, uint32_t event) { + furi_assert(context); + RpcDebugApp* app = context; + return scene_manager_handle_custom_event(app->scene_manager, event); +} + +static bool rpc_debug_app_back_event_callback(void* context) { + furi_assert(context); + RpcDebugApp* app = context; + return scene_manager_handle_back_event(app->scene_manager); +} + +static void rpc_debug_app_tick_event_callback(void* context) { + furi_assert(context); + RpcDebugApp* app = context; + scene_manager_handle_tick_event(app->scene_manager); +} + +static void rpc_debug_app_rpc_command_callback(RpcAppSystemEvent event, void* context) { + furi_assert(context); + RpcDebugApp* app = context; + furi_assert(app->rpc); + + if(event == RpcAppEventSessionClose) { + scene_manager_stop(app->scene_manager); + view_dispatcher_stop(app->view_dispatcher); + rpc_system_app_set_callback(app->rpc, NULL, NULL); + app->rpc = NULL; + } else if(event == RpcAppEventAppExit) { + scene_manager_stop(app->scene_manager); + view_dispatcher_stop(app->view_dispatcher); + rpc_system_app_confirm(app->rpc, RpcAppEventAppExit, true); + } else { + rpc_system_app_confirm(app->rpc, event, false); + } +} + +static bool rpc_debug_app_rpc_init_rpc(RpcDebugApp* app, const char* args) { + bool ret = false; + if(args && strlen(args)) { + uint32_t rpc = 0; + if(sscanf(args, "RPC %lX", &rpc) == 1) { + app->rpc = (RpcAppSystem*)rpc; + rpc_system_app_set_callback(app->rpc, rpc_debug_app_rpc_command_callback, app); + rpc_system_app_send_started(app->rpc); + ret = true; + } + } + return ret; +} + +static RpcDebugApp* rpc_debug_app_alloc() { + RpcDebugApp* app = malloc(sizeof(RpcDebugApp)); + + app->gui = furi_record_open(RECORD_GUI); + app->notifications = furi_record_open(RECORD_NOTIFICATION); + app->scene_manager = scene_manager_alloc(&rpc_debug_app_scene_handlers, app); + app->view_dispatcher = view_dispatcher_alloc(); + + view_dispatcher_set_event_callback_context(app->view_dispatcher, app); + view_dispatcher_set_custom_event_callback( + app->view_dispatcher, rpc_debug_app_custom_event_callback); + view_dispatcher_set_navigation_event_callback( + app->view_dispatcher, rpc_debug_app_back_event_callback); + view_dispatcher_set_tick_event_callback( + app->view_dispatcher, rpc_debug_app_tick_event_callback, 100); + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + view_dispatcher_enable_queue(app->view_dispatcher); + + app->widget = widget_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, RpcDebugAppViewWidget, widget_get_view(app->widget)); + app->submenu = submenu_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, RpcDebugAppViewSubmenu, submenu_get_view(app->submenu)); + app->text_box = text_box_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, RpcDebugAppViewTextBox, text_box_get_view(app->text_box)); + app->text_input = text_input_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, RpcDebugAppViewTextInput, text_input_get_view(app->text_input)); + app->byte_input = byte_input_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, RpcDebugAppViewByteInput, byte_input_get_view(app->byte_input)); + + return app; +} + +static void rpc_debug_app_free(RpcDebugApp* app) { + view_dispatcher_remove_view(app->view_dispatcher, RpcDebugAppViewByteInput); + view_dispatcher_remove_view(app->view_dispatcher, RpcDebugAppViewTextInput); + view_dispatcher_remove_view(app->view_dispatcher, RpcDebugAppViewTextBox); + view_dispatcher_remove_view(app->view_dispatcher, RpcDebugAppViewSubmenu); + view_dispatcher_remove_view(app->view_dispatcher, RpcDebugAppViewWidget); + + free(app->byte_input); + free(app->text_input); + free(app->text_box); + free(app->submenu); + free(app->widget); + + free(app->scene_manager); + free(app->view_dispatcher); + + furi_record_close(RECORD_NOTIFICATION); + app->notifications = NULL; + furi_record_close(RECORD_GUI); + app->gui = NULL; + + if(app->rpc) { + rpc_system_app_set_callback(app->rpc, NULL, NULL); + rpc_system_app_send_exited(app->rpc); + app->rpc = NULL; + } + + free(app); +} + +int32_t rpc_debug_app(void* args) { + RpcDebugApp* app = rpc_debug_app_alloc(); + + if(rpc_debug_app_rpc_init_rpc(app, args)) { + notification_message(app->notifications, &sequence_display_backlight_on); + scene_manager_next_scene(app->scene_manager, RpcDebugAppSceneStart); + } else { + scene_manager_next_scene(app->scene_manager, RpcDebugAppSceneStartDummy); + } + + view_dispatcher_run(app->view_dispatcher); + + rpc_debug_app_free(app); + return 0; +} diff --git a/applications/debug/rpc_debug_app/rpc_debug_app.h b/applications/debug/rpc_debug_app/rpc_debug_app.h new file mode 100644 index 00000000000..100fc124cf7 --- /dev/null +++ b/applications/debug/rpc_debug_app/rpc_debug_app.h @@ -0,0 +1,54 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include "scenes/rpc_debug_app_scene.h" + +#define DATA_STORE_SIZE 64U +#define TEXT_STORE_SIZE 64U + +typedef struct { + Gui* gui; + RpcAppSystem* rpc; + SceneManager* scene_manager; + ViewDispatcher* view_dispatcher; + NotificationApp* notifications; + + Widget* widget; + Submenu* submenu; + TextBox* text_box; + TextInput* text_input; + ByteInput* byte_input; + + char text_store[TEXT_STORE_SIZE]; + uint8_t data_store[DATA_STORE_SIZE]; +} RpcDebugApp; + +typedef enum { + RpcDebugAppViewWidget, + RpcDebugAppViewSubmenu, + RpcDebugAppViewTextBox, + RpcDebugAppViewTextInput, + RpcDebugAppViewByteInput, +} RpcDebugAppView; + +typedef enum { + // Reserve first 100 events for button types and indexes, starting from 0 + RpcDebugAppCustomEventInputErrorCode = 100, + RpcDebugAppCustomEventInputErrorText, + RpcDebugAppCustomEventInputDataExchange, + RpcDebugAppCustomEventRpcDataExchange, +} RpcDebugAppCustomEvent; diff --git a/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene.c b/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene.c new file mode 100644 index 00000000000..2c593cdebcb --- /dev/null +++ b/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene.c @@ -0,0 +1,30 @@ +#include "rpc_debug_app_scene.h" + +// Generate scene on_enter handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, +void (*const rpc_debug_app_on_enter_handlers[])(void*) = { +#include "rpc_debug_app_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 rpc_debug_app_on_event_handlers[])(void* context, SceneManagerEvent event) = { +#include "rpc_debug_app_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 rpc_debug_app_on_exit_handlers[])(void* context) = { +#include "rpc_debug_app_scene_config.h" +}; +#undef ADD_SCENE + +// Initialize scene handlers configuration structure +const SceneManagerHandlers rpc_debug_app_scene_handlers = { + .on_enter_handlers = rpc_debug_app_on_enter_handlers, + .on_event_handlers = rpc_debug_app_on_event_handlers, + .on_exit_handlers = rpc_debug_app_on_exit_handlers, + .scene_num = RpcDebugAppSceneNum, +}; diff --git a/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene.h b/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene.h new file mode 100644 index 00000000000..eb3424fb1e7 --- /dev/null +++ b/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +// Generate scene id and total number +#define ADD_SCENE(prefix, name, id) RpcDebugAppScene##id, +typedef enum { +#include "rpc_debug_app_scene_config.h" + RpcDebugAppSceneNum, +} RpcDebugAppScene; +#undef ADD_SCENE + +extern const SceneManagerHandlers rpc_debug_app_scene_handlers; + +// Generate scene on_enter handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); +#include "rpc_debug_app_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 "rpc_debug_app_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 "rpc_debug_app_scene_config.h" +#undef ADD_SCENE diff --git a/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_config.h b/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_config.h new file mode 100644 index 00000000000..8f163253dcd --- /dev/null +++ b/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_config.h @@ -0,0 +1,8 @@ +ADD_SCENE(rpc_debug_app, start, Start) +ADD_SCENE(rpc_debug_app, start_dummy, StartDummy) +ADD_SCENE(rpc_debug_app, test_app_error, TestAppError) +ADD_SCENE(rpc_debug_app, test_data_exchange, TestDataExchange) +ADD_SCENE(rpc_debug_app, input_error_code, InputErrorCode) +ADD_SCENE(rpc_debug_app, input_error_text, InputErrorText) +ADD_SCENE(rpc_debug_app, input_data_exchange, InputDataExchange) +ADD_SCENE(rpc_debug_app, receive_data_exchange, ReceiveDataExchange) diff --git a/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_input_data_exchange.c b/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_input_data_exchange.c new file mode 100644 index 00000000000..3ae2ba44d2c --- /dev/null +++ b/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_input_data_exchange.c @@ -0,0 +1,40 @@ +#include "../rpc_debug_app.h" + +static void rpc_debug_app_scene_input_data_exchange_result_callback(void* context) { + RpcDebugApp* app = context; + view_dispatcher_send_custom_event( + app->view_dispatcher, RpcDebugAppCustomEventInputDataExchange); +} + +void rpc_debug_app_scene_input_data_exchange_on_enter(void* context) { + RpcDebugApp* app = context; + byte_input_set_header_text(app->byte_input, "Enter data to exchange"); + byte_input_set_result_callback( + app->byte_input, + rpc_debug_app_scene_input_data_exchange_result_callback, + NULL, + app, + app->data_store, + DATA_STORE_SIZE); + view_dispatcher_switch_to_view(app->view_dispatcher, RpcDebugAppViewByteInput); +} + +bool rpc_debug_app_scene_input_data_exchange_on_event(void* context, SceneManagerEvent event) { + RpcDebugApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == RpcDebugAppCustomEventInputDataExchange) { + rpc_system_app_exchange_data(app->rpc, app->data_store, DATA_STORE_SIZE); + scene_manager_previous_scene(app->scene_manager); + consumed = true; + } + } + + return consumed; +} + +void rpc_debug_app_scene_input_data_exchange_on_exit(void* context) { + RpcDebugApp* app = context; + UNUSED(app); +} diff --git a/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_input_error_code.c b/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_input_error_code.c new file mode 100644 index 00000000000..eae12e6eeca --- /dev/null +++ b/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_input_error_code.c @@ -0,0 +1,60 @@ +#include "../rpc_debug_app.h" + +static bool rpc_debug_app_scene_input_error_code_validator_callback( + const char* text, + FuriString* error, + void* context) { + UNUSED(context); + + for(; *text; ++text) { + const char c = *text; + if(c < '0' || c > '9') { + furi_string_printf(error, "%s", "Please enter\na number!"); + return false; + } + } + + return true; +} + +static void rpc_debug_app_scene_input_error_code_result_callback(void* context) { + RpcDebugApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, RpcDebugAppCustomEventInputErrorCode); +} + +void rpc_debug_app_scene_input_error_code_on_enter(void* context) { + RpcDebugApp* app = context; + strncpy(app->text_store, "666", TEXT_STORE_SIZE); + text_input_set_header_text(app->text_input, "Enter error code"); + text_input_set_validator( + app->text_input, rpc_debug_app_scene_input_error_code_validator_callback, NULL); + text_input_set_result_callback( + app->text_input, + rpc_debug_app_scene_input_error_code_result_callback, + app, + app->text_store, + TEXT_STORE_SIZE, + true); + view_dispatcher_switch_to_view(app->view_dispatcher, RpcDebugAppViewTextInput); +} + +bool rpc_debug_app_scene_input_error_code_on_event(void* context, SceneManagerEvent event) { + RpcDebugApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == RpcDebugAppCustomEventInputErrorCode) { + rpc_system_app_set_error_code(app->rpc, (uint32_t)atol(app->text_store)); + scene_manager_previous_scene(app->scene_manager); + consumed = true; + } + } + + return consumed; +} + +void rpc_debug_app_scene_input_error_code_on_exit(void* context) { + RpcDebugApp* app = context; + text_input_reset(app->text_input); + text_input_set_validator(app->text_input, NULL, NULL); +} diff --git a/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_input_error_text.c b/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_input_error_text.c new file mode 100644 index 00000000000..b07f8f4af27 --- /dev/null +++ b/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_input_error_text.c @@ -0,0 +1,40 @@ +#include "../rpc_debug_app.h" + +static void rpc_debug_app_scene_input_error_text_result_callback(void* context) { + RpcDebugApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, RpcDebugAppCustomEventInputErrorText); +} + +void rpc_debug_app_scene_input_error_text_on_enter(void* context) { + RpcDebugApp* app = context; + strncpy(app->text_store, "I'm a scary error message!", TEXT_STORE_SIZE); + text_input_set_header_text(app->text_input, "Enter error text"); + text_input_set_result_callback( + app->text_input, + rpc_debug_app_scene_input_error_text_result_callback, + app, + app->text_store, + TEXT_STORE_SIZE, + true); + view_dispatcher_switch_to_view(app->view_dispatcher, RpcDebugAppViewTextInput); +} + +bool rpc_debug_app_scene_input_error_text_on_event(void* context, SceneManagerEvent event) { + RpcDebugApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == RpcDebugAppCustomEventInputErrorText) { + rpc_system_app_set_error_text(app->rpc, app->text_store); + scene_manager_previous_scene(app->scene_manager); + consumed = true; + } + } + + return consumed; +} + +void rpc_debug_app_scene_input_error_text_on_exit(void* context) { + RpcDebugApp* app = context; + text_input_reset(app->text_input); +} diff --git a/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_receive_data_exchange.c b/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_receive_data_exchange.c new file mode 100644 index 00000000000..98bafff8a7b --- /dev/null +++ b/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_receive_data_exchange.c @@ -0,0 +1,70 @@ +#include "../rpc_debug_app.h" + +static void rpc_debug_app_scene_start_format_hex( + const uint8_t* data, + size_t data_size, + char* buf, + size_t buf_size) { + furi_assert(data); + furi_assert(buf); + + const size_t byte_width = 3; + const size_t line_width = 7; + + data_size = MIN(data_size, buf_size / (byte_width + 1)); + + for(size_t i = 0; i < data_size; ++i) { + char* p = buf + (i * byte_width); + char sep = !((i + 1) % line_width) ? '\n' : ' '; + snprintf(p, byte_width + 1, "%02X%c", data[i], sep); + } + + buf[buf_size - 1] = '\0'; +} + +static void rpc_debug_app_scene_receive_data_exchange_callback( + const uint8_t* data, + size_t data_size, + void* context) { + RpcDebugApp* app = context; + if(data) { + rpc_debug_app_scene_start_format_hex(data, data_size, app->text_store, TEXT_STORE_SIZE); + } else { + strncpy(app->text_store, "", TEXT_STORE_SIZE); + } + view_dispatcher_send_custom_event(app->view_dispatcher, RpcDebugAppCustomEventRpcDataExchange); +} + +void rpc_debug_app_scene_receive_data_exchange_on_enter(void* context) { + RpcDebugApp* app = context; + strncpy(app->text_store, "Received data will appear here...", TEXT_STORE_SIZE); + + text_box_set_text(app->text_box, app->text_store); + text_box_set_font(app->text_box, TextBoxFontHex); + + rpc_system_app_set_data_exchange_callback( + app->rpc, rpc_debug_app_scene_receive_data_exchange_callback, app); + view_dispatcher_switch_to_view(app->view_dispatcher, RpcDebugAppViewTextBox); +} + +bool rpc_debug_app_scene_receive_data_exchange_on_event(void* context, SceneManagerEvent event) { + RpcDebugApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == RpcDebugAppCustomEventRpcDataExchange) { + notification_message(app->notifications, &sequence_blink_cyan_100); + notification_message(app->notifications, &sequence_display_backlight_on); + text_box_set_text(app->text_box, app->text_store); + consumed = true; + } + } + + return consumed; +} + +void rpc_debug_app_scene_receive_data_exchange_on_exit(void* context) { + RpcDebugApp* app = context; + text_box_reset(app->text_box); + rpc_system_app_set_data_exchange_callback(app->rpc, NULL, NULL); +} diff --git a/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_start.c b/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_start.c new file mode 100644 index 00000000000..9461649ba8a --- /dev/null +++ b/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_start.c @@ -0,0 +1,57 @@ +#include "../rpc_debug_app.h" + +enum SubmenuIndex { + SubmenuIndexTestAppError, + SubmenuIndexTestDataExchange, +}; + +static void rpc_debug_app_scene_start_submenu_callback(void* context, uint32_t index) { + RpcDebugApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, index); +} + +void rpc_debug_app_scene_start_on_enter(void* context) { + RpcDebugApp* app = context; + Submenu* submenu = app->submenu; + + submenu_add_item( + submenu, + "Test App Error", + SubmenuIndexTestAppError, + rpc_debug_app_scene_start_submenu_callback, + app); + submenu_add_item( + submenu, + "Test Data Exchange", + SubmenuIndexTestDataExchange, + rpc_debug_app_scene_start_submenu_callback, + app); + + submenu_set_selected_item(submenu, SubmenuIndexTestAppError); + view_dispatcher_switch_to_view(app->view_dispatcher, RpcDebugAppViewSubmenu); +} + +bool rpc_debug_app_scene_start_on_event(void* context, SceneManagerEvent event) { + RpcDebugApp* app = context; + SceneManager* scene_manager = app->scene_manager; + + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + const uint32_t submenu_index = event.event; + if(submenu_index == SubmenuIndexTestAppError) { + scene_manager_next_scene(scene_manager, RpcDebugAppSceneTestAppError); + consumed = true; + } else if(submenu_index == SubmenuIndexTestDataExchange) { + scene_manager_next_scene(scene_manager, RpcDebugAppSceneTestDataExchange); + consumed = true; + } + } + + return consumed; +} + +void rpc_debug_app_scene_start_on_exit(void* context) { + RpcDebugApp* app = context; + submenu_reset(app->submenu); +} diff --git a/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_start_dummy.c b/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_start_dummy.c new file mode 100644 index 00000000000..5a30a079a3d --- /dev/null +++ b/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_start_dummy.c @@ -0,0 +1,30 @@ +#include "../rpc_debug_app.h" + +void rpc_debug_app_scene_start_dummy_on_enter(void* context) { + RpcDebugApp* app = context; + widget_add_text_box_element( + app->widget, + 0, + 0, + 128, + 64, + AlignCenter, + AlignCenter, + "This application\nis meant to be run\nin \e#RPC\e# mode.", + false); + view_dispatcher_switch_to_view(app->view_dispatcher, RpcDebugAppViewWidget); +} + +bool rpc_debug_app_scene_start_dummy_on_event(void* context, SceneManagerEvent event) { + RpcDebugApp* app = context; + UNUSED(app); + UNUSED(event); + + bool consumed = false; + return consumed; +} + +void rpc_debug_app_scene_start_dummy_on_exit(void* context) { + RpcDebugApp* app = context; + widget_reset(app->widget); +} diff --git a/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_test_app_error.c b/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_test_app_error.c new file mode 100644 index 00000000000..865d475ae83 --- /dev/null +++ b/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_test_app_error.c @@ -0,0 +1,57 @@ +#include "../rpc_debug_app.h" + +typedef enum { + SubmenuIndexSetErrorCode, + SubmenuIndexSetErrorText, +} SubmenuIndex; + +static void rpc_debug_app_scene_test_app_error_submenu_callback(void* context, uint32_t index) { + RpcDebugApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, index); +} + +void rpc_debug_app_scene_test_app_error_on_enter(void* context) { + RpcDebugApp* app = context; + Submenu* submenu = app->submenu; + + submenu_add_item( + submenu, + "Set Error Code", + SubmenuIndexSetErrorCode, + rpc_debug_app_scene_test_app_error_submenu_callback, + app); + submenu_add_item( + submenu, + "Set Error Text", + SubmenuIndexSetErrorText, + rpc_debug_app_scene_test_app_error_submenu_callback, + app); + + submenu_set_selected_item(submenu, SubmenuIndexSetErrorCode); + view_dispatcher_switch_to_view(app->view_dispatcher, RpcDebugAppViewSubmenu); +} + +bool rpc_debug_app_scene_test_app_error_on_event(void* context, SceneManagerEvent event) { + RpcDebugApp* app = context; + SceneManager* scene_manager = app->scene_manager; + + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + const uint32_t submenu_index = event.event; + if(submenu_index == SubmenuIndexSetErrorCode) { + scene_manager_next_scene(scene_manager, RpcDebugAppSceneInputErrorCode); + consumed = true; + } else if(submenu_index == SubmenuIndexSetErrorText) { + scene_manager_next_scene(scene_manager, RpcDebugAppSceneInputErrorText); + consumed = true; + } + } + + return consumed; +} + +void rpc_debug_app_scene_test_app_error_on_exit(void* context) { + RpcDebugApp* app = context; + submenu_reset(app->submenu); +} diff --git a/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_test_data_exchange.c b/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_test_data_exchange.c new file mode 100644 index 00000000000..f90f480c2a9 --- /dev/null +++ b/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_test_data_exchange.c @@ -0,0 +1,58 @@ +#include "../rpc_debug_app.h" + +typedef enum { + SubmenuIndexSendData, + SubmenuIndexReceiveData, +} SubmenuIndex; + +static void + rpc_debug_app_scene_test_data_exchange_submenu_callback(void* context, uint32_t index) { + RpcDebugApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, index); +} + +void rpc_debug_app_scene_test_data_exchange_on_enter(void* context) { + RpcDebugApp* app = context; + Submenu* submenu = app->submenu; + + submenu_add_item( + submenu, + "Send Data", + SubmenuIndexSendData, + rpc_debug_app_scene_test_data_exchange_submenu_callback, + app); + submenu_add_item( + submenu, + "Receive Data", + SubmenuIndexReceiveData, + rpc_debug_app_scene_test_data_exchange_submenu_callback, + app); + + submenu_set_selected_item(submenu, SubmenuIndexSendData); + view_dispatcher_switch_to_view(app->view_dispatcher, RpcDebugAppViewSubmenu); +} + +bool rpc_debug_app_scene_test_data_exchange_on_event(void* context, SceneManagerEvent event) { + RpcDebugApp* app = context; + SceneManager* scene_manager = app->scene_manager; + + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + const uint32_t submenu_index = event.event; + if(submenu_index == SubmenuIndexSendData) { + scene_manager_next_scene(scene_manager, RpcDebugAppSceneInputDataExchange); + consumed = true; + } else if(submenu_index == SubmenuIndexReceiveData) { + scene_manager_next_scene(scene_manager, RpcDebugAppSceneReceiveDataExchange); + consumed = true; + } + } + + return consumed; +} + +void rpc_debug_app_scene_test_data_exchange_on_exit(void* context) { + RpcDebugApp* app = context; + submenu_reset(app->submenu); +} diff --git a/applications/services/cli/cli_commands.c b/applications/services/cli/cli_commands.c index 64ff8ef71b3..3534a5418ae 100644 --- a/applications/services/cli/cli_commands.c +++ b/applications/services/cli/cli_commands.c @@ -25,7 +25,7 @@ void cli_command_device_info_callback(const char* key, const char* value, bool l void cli_command_device_info(Cli* cli, FuriString* args, void* context) { UNUSED(cli); UNUSED(args); - furi_hal_info_get(cli_command_device_info_callback, context); + furi_hal_info_get(cli_command_device_info_callback, '_', context); } void cli_command_help(Cli* cli, FuriString* args, void* context) { diff --git a/applications/services/power/power_cli.c b/applications/services/power/power_cli.c index 5c28137e9b6..f4a10f0a98d 100644 --- a/applications/services/power/power_cli.c +++ b/applications/services/power/power_cli.c @@ -26,7 +26,7 @@ void power_cli_reboot2dfu(Cli* cli, FuriString* args) { power_reboot(PowerBootModeDfu); } -static void power_cli_info_callback(const char* key, const char* value, bool last, void* context) { +static void power_cli_callback(const char* key, const char* value, bool last, void* context) { UNUSED(last); UNUSED(context); printf("%-24s: %s\r\n", key, value); @@ -35,13 +35,13 @@ static void power_cli_info_callback(const char* key, const char* value, bool las void power_cli_info(Cli* cli, FuriString* args) { UNUSED(cli); UNUSED(args); - furi_hal_power_info_get(power_cli_info_callback, NULL); + furi_hal_power_info_get(power_cli_callback, '_', NULL); } void power_cli_debug(Cli* cli, FuriString* args) { UNUSED(cli); UNUSED(args); - furi_hal_power_dump_state(); + furi_hal_power_debug_get(power_cli_callback, NULL); } void power_cli_5v(Cli* cli, FuriString* args) { diff --git a/applications/services/rpc/rpc.c b/applications/services/rpc/rpc.c index ebf01572295..63a71ad06d6 100644 --- a/applications/services/rpc/rpc.c +++ b/applications/services/rpc/rpc.c @@ -52,7 +52,12 @@ static RpcSystemCallbacks rpc_systems[] = { { .alloc = rpc_system_gpio_alloc, .free = NULL, - }}; + }, + { + .alloc = rpc_system_property_alloc, + .free = NULL, + }, +}; struct RpcSession { Rpc* rpc; diff --git a/applications/services/rpc/rpc_app.c b/applications/services/rpc/rpc_app.c index 3010555708f..4e1af1fb108 100644 --- a/applications/services/rpc/rpc_app.c +++ b/applications/services/rpc/rpc_app.c @@ -9,9 +9,15 @@ struct RpcAppSystem { RpcSession* session; + RpcAppSystemCallback app_callback; void* app_context; + + RpcAppSystemDataExchangeCallback data_exchange_callback; + void* data_exchange_context; + PB_Main* state_msg; + PB_Main* error_msg; uint32_t last_id; char* last_data; @@ -195,6 +201,50 @@ static void rpc_system_app_button_release(const PB_Main* request, void* context) } } +static void rpc_system_app_get_error_process(const PB_Main* request, void* context) { + furi_assert(request); + furi_assert(request->which_content == PB_Main_app_get_error_request_tag); + furi_assert(context); + + RpcAppSystem* rpc_app = context; + RpcSession* session = rpc_app->session; + furi_assert(session); + + rpc_app->error_msg->command_id = request->command_id; + + FURI_LOG_D(TAG, "GetError"); + rpc_send(session, rpc_app->error_msg); +} + +static void rpc_system_app_data_exchange_process(const PB_Main* request, void* context) { + furi_assert(request); + furi_assert(request->which_content == PB_Main_app_data_exchange_request_tag); + furi_assert(context); + + RpcAppSystem* rpc_app = context; + RpcSession* session = rpc_app->session; + furi_assert(session); + + PB_CommandStatus command_status; + pb_bytes_array_t* data = request->content.app_data_exchange_request.data; + + if(rpc_app->data_exchange_callback) { + uint8_t* data_bytes = NULL; + size_t data_size = 0; + if(data) { + data_bytes = data->bytes; + data_size = data->size; + } + rpc_app->data_exchange_callback(data_bytes, data_size, rpc_app->data_exchange_context); + command_status = PB_CommandStatus_OK; + } else { + command_status = PB_CommandStatus_ERROR_APP_CMD_ERROR; + } + + FURI_LOG_D(TAG, "DataExchange"); + rpc_send_and_release_empty(session, request->command_id, command_status); +} + void rpc_system_app_send_started(RpcAppSystem* rpc_app) { furi_assert(rpc_app); RpcSession* session = rpc_app->session; @@ -259,6 +309,58 @@ void rpc_system_app_set_callback(RpcAppSystem* rpc_app, RpcAppSystemCallback cal rpc_app->app_context = ctx; } +void rpc_system_app_set_error_code(RpcAppSystem* rpc_app, uint32_t error_code) { + furi_assert(rpc_app); + PB_App_GetErrorResponse* content = &rpc_app->error_msg->content.app_get_error_response; + content->code = error_code; +} + +void rpc_system_app_set_error_text(RpcAppSystem* rpc_app, const char* error_text) { + furi_assert(rpc_app); + PB_App_GetErrorResponse* content = &rpc_app->error_msg->content.app_get_error_response; + + if(content->text) { + free(content->text); + } + + content->text = error_text ? strdup(error_text) : NULL; +} + +void rpc_system_app_set_data_exchange_callback( + RpcAppSystem* rpc_app, + RpcAppSystemDataExchangeCallback callback, + void* ctx) { + furi_assert(rpc_app); + + rpc_app->data_exchange_callback = callback; + rpc_app->data_exchange_context = ctx; +} + +void rpc_system_app_exchange_data(RpcAppSystem* rpc_app, const uint8_t* data, size_t data_size) { + furi_assert(rpc_app); + RpcSession* session = rpc_app->session; + furi_assert(session); + + PB_Main message = { + .command_id = 0, + .command_status = PB_CommandStatus_OK, + .has_next = false, + .which_content = PB_Main_app_data_exchange_request_tag, + }; + + PB_App_DataExchangeRequest* content = &message.content.app_data_exchange_request; + + if(data && data_size) { + content->data = malloc(PB_BYTES_ARRAY_T_ALLOCSIZE(data_size)); + content->data->size = data_size; + memcpy(content->data->bytes, data, data_size); + } else { + content->data = NULL; + } + + rpc_send_and_release(session, &message); +} + void* rpc_system_app_alloc(RpcSession* session) { furi_assert(session); @@ -270,6 +372,13 @@ void* rpc_system_app_alloc(RpcSession* session) { rpc_app->state_msg->which_content = PB_Main_app_state_response_tag; rpc_app->state_msg->command_status = PB_CommandStatus_OK; + // App error message + rpc_app->error_msg = malloc(sizeof(PB_Main)); + rpc_app->error_msg->which_content = PB_Main_app_get_error_response_tag; + rpc_app->error_msg->command_status = PB_CommandStatus_OK; + rpc_app->error_msg->content.app_get_error_response.code = 0; + rpc_app->error_msg->content.app_get_error_response.text = NULL; + RpcHandler rpc_handler = { .message_handler = NULL, .decode_submessage = NULL, @@ -294,6 +403,12 @@ void* rpc_system_app_alloc(RpcSession* session) { rpc_handler.message_handler = rpc_system_app_button_release; rpc_add_handler(session, PB_Main_app_button_release_request_tag, &rpc_handler); + rpc_handler.message_handler = rpc_system_app_get_error_process; + rpc_add_handler(session, PB_Main_app_get_error_request_tag, &rpc_handler); + + rpc_handler.message_handler = rpc_system_app_data_exchange_process; + rpc_add_handler(session, PB_Main_app_data_exchange_request_tag, &rpc_handler); + return rpc_app; } @@ -311,8 +426,13 @@ void rpc_system_app_free(void* context) { furi_delay_tick(1); } + furi_assert(!rpc_app->data_exchange_callback); + if(rpc_app->last_data) free(rpc_app->last_data); + pb_release(&PB_Main_msg, rpc_app->error_msg); + + free(rpc_app->error_msg); free(rpc_app->state_msg); free(rpc_app); } diff --git a/applications/services/rpc/rpc_app.h b/applications/services/rpc/rpc_app.h index 635c9f8c648..70febe4911e 100644 --- a/applications/services/rpc/rpc_app.h +++ b/applications/services/rpc/rpc_app.h @@ -14,6 +14,8 @@ typedef enum { } RpcAppSystemEvent; typedef void (*RpcAppSystemCallback)(RpcAppSystemEvent event, void* context); +typedef void ( + *RpcAppSystemDataExchangeCallback)(const uint8_t* data, size_t data_size, void* context); typedef struct RpcAppSystem RpcAppSystem; @@ -27,6 +29,17 @@ const char* rpc_system_app_get_data(RpcAppSystem* rpc_app); void rpc_system_app_confirm(RpcAppSystem* rpc_app, RpcAppSystemEvent event, bool result); +void rpc_system_app_set_error_code(RpcAppSystem* rpc_app, uint32_t error_code); + +void rpc_system_app_set_error_text(RpcAppSystem* rpc_app, const char* error_text); + +void rpc_system_app_set_data_exchange_callback( + RpcAppSystem* rpc_app, + RpcAppSystemDataExchangeCallback callback, + void* ctx); + +void rpc_system_app_exchange_data(RpcAppSystem* rpc_app, const uint8_t* data, size_t data_size); + #ifdef __cplusplus } #endif diff --git a/applications/services/rpc/rpc_i.h b/applications/services/rpc/rpc_i.h index af9033f0a34..91a176da87b 100644 --- a/applications/services/rpc/rpc_i.h +++ b/applications/services/rpc/rpc_i.h @@ -34,6 +34,7 @@ void* rpc_system_gui_alloc(RpcSession* session); void rpc_system_gui_free(void* ctx); void* rpc_system_gpio_alloc(RpcSession* session); void rpc_system_gpio_free(void* ctx); +void* rpc_system_property_alloc(RpcSession* session); void rpc_debug_print_message(const PB_Main* message); void rpc_debug_print_data(const char* prefix, uint8_t* buffer, size_t size); diff --git a/applications/services/rpc/rpc_property.c b/applications/services/rpc/rpc_property.c new file mode 100644 index 00000000000..ad15051ebca --- /dev/null +++ b/applications/services/rpc/rpc_property.c @@ -0,0 +1,107 @@ +#include +#include +#include +#include +#include + +#include "rpc_i.h" + +#define TAG "RpcProperty" + +#define PROPERTY_CATEGORY_DEVICE_INFO "devinfo" +#define PROPERTY_CATEGORY_POWER_INFO "pwrinfo" +#define PROPERTY_CATEGORY_POWER_DEBUG "pwrdebug" + +typedef struct { + RpcSession* session; + PB_Main* response; + FuriString* subkey; +} RpcPropertyContext; + +static void + rpc_system_property_get_callback(const char* key, const char* value, bool last, void* context) { + furi_assert(key); + furi_assert(value); + furi_assert(context); + furi_assert(key); + furi_assert(value); + + RpcPropertyContext* ctx = context; + RpcSession* session = ctx->session; + PB_Main* response = ctx->response; + + if(!strncmp(key, furi_string_get_cstr(ctx->subkey), furi_string_size(ctx->subkey))) { + response->content.system_device_info_response.key = strdup(key); + response->content.system_device_info_response.value = strdup(value); + rpc_send_and_release(session, response); + } + + if(last) { + rpc_send_and_release_empty(session, response->command_id, PB_CommandStatus_OK); + } +} + +static void rpc_system_property_get_process(const PB_Main* request, void* context) { + furi_assert(request); + furi_assert(request->which_content == PB_Main_property_get_request_tag); + + FURI_LOG_D(TAG, "GetProperty"); + + RpcSession* session = (RpcSession*)context; + furi_assert(session); + + FuriString* topkey = furi_string_alloc(); + FuriString* subkey = furi_string_alloc_set_str(request->content.property_get_request.key); + + const size_t sep_idx = furi_string_search_char(subkey, '.'); + + if(sep_idx == FURI_STRING_FAILURE) { + furi_string_swap(topkey, subkey); + } else { + furi_string_set_n(topkey, subkey, 0, sep_idx); + furi_string_right(subkey, sep_idx + 1); + } + + PB_Main* response = malloc(sizeof(PB_Main)); + + response->command_id = request->command_id; + response->command_status = PB_CommandStatus_OK; + response->has_next = true; + response->which_content = PB_Main_property_get_response_tag; + + RpcPropertyContext property_context = { + .session = session, + .response = response, + .subkey = subkey, + }; + + if(!furi_string_cmp(topkey, PROPERTY_CATEGORY_DEVICE_INFO)) { + furi_hal_info_get(rpc_system_property_get_callback, '.', &property_context); + } else if(!furi_string_cmp(topkey, PROPERTY_CATEGORY_POWER_INFO)) { + furi_hal_power_info_get(rpc_system_property_get_callback, '.', &property_context); + } else if(!furi_string_cmp(topkey, PROPERTY_CATEGORY_POWER_DEBUG)) { + furi_hal_power_debug_get(rpc_system_property_get_callback, &property_context); + } else { + rpc_send_and_release_empty( + session, request->command_id, PB_CommandStatus_ERROR_INVALID_PARAMETERS); + } + + furi_string_free(subkey); + furi_string_free(topkey); + + free(response); +} + +void* rpc_system_property_alloc(RpcSession* session) { + furi_assert(session); + + RpcHandler rpc_handler = { + .message_handler = NULL, + .decode_submessage = NULL, + .context = session, + }; + + rpc_handler.message_handler = rpc_system_property_get_process; + rpc_add_handler(session, PB_Main_property_get_request_tag, &rpc_handler); + return NULL; +} diff --git a/applications/services/rpc/rpc_system.c b/applications/services/rpc/rpc_system.c index 1681bb17e8f..a17be7d2d78 100644 --- a/applications/services/rpc/rpc_system.c +++ b/applications/services/rpc/rpc_system.c @@ -109,7 +109,7 @@ static void rpc_system_system_device_info_process(const PB_Main* request, void* .session = session, .response = response, }; - furi_hal_info_get(rpc_system_system_device_info_callback, &device_info_context); + furi_hal_info_get(rpc_system_system_device_info_callback, '_', &device_info_context); free(response); } @@ -266,7 +266,7 @@ static void rpc_system_system_get_power_info_process(const PB_Main* request, voi .session = session, .response = response, }; - furi_hal_power_info_get(rpc_system_system_power_info_callback, &power_info_context); + furi_hal_power_info_get(rpc_system_system_power_info_callback, '_', &power_info_context); free(response); } diff --git a/assets/protobuf b/assets/protobuf index e5af96e08fe..64606602370 160000 --- a/assets/protobuf +++ b/assets/protobuf @@ -1 +1 @@ -Subproject commit e5af96e08fea8351898f7b8c6d1e34ce5fd6cdef +Subproject commit 6460660237005d02d5c223835659b40e373bade9 diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 17b36a2ae31..afc7c4e9337 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,7.6,, +Version,+,8.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -1120,7 +1120,7 @@ Function,+,furi_hal_ibutton_start_drive_in_isr,void, Function,+,furi_hal_ibutton_start_interrupt,void, Function,+,furi_hal_ibutton_start_interrupt_in_isr,void, Function,+,furi_hal_ibutton_stop,void, -Function,+,furi_hal_info_get,void,"FuriHalInfoValueCallback, void*" +Function,+,furi_hal_info_get,void,"PropertyValueCallback, char, void*" Function,+,furi_hal_infrared_async_rx_set_capture_isr_callback,void,"FuriHalInfraredRxCaptureCallback, void*" Function,+,furi_hal_infrared_async_rx_set_timeout,void,uint32_t Function,+,furi_hal_infrared_async_rx_set_timeout_isr_callback,void,"FuriHalInfraredRxTimeoutCallback, void*" @@ -1185,10 +1185,10 @@ Function,+,furi_hal_nfc_tx_rx_full,_Bool,FuriHalNfcTxRxContext* Function,-,furi_hal_os_init,void, Function,+,furi_hal_os_tick,void, Function,+,furi_hal_power_check_otg_status,void, +Function,+,furi_hal_power_debug_get,void,"PropertyValueCallback, void*" Function,+,furi_hal_power_deep_sleep_available,_Bool, Function,+,furi_hal_power_disable_external_3_3v,void, Function,+,furi_hal_power_disable_otg,void, -Function,+,furi_hal_power_dump_state,void, Function,+,furi_hal_power_enable_external_3_3v,void, Function,+,furi_hal_power_enable_otg,void, Function,+,furi_hal_power_gauge_is_ok,_Bool, @@ -1201,7 +1201,7 @@ Function,+,furi_hal_power_get_battery_temperature,float,FuriHalPowerIC Function,+,furi_hal_power_get_battery_voltage,float,FuriHalPowerIC Function,+,furi_hal_power_get_pct,uint8_t, Function,+,furi_hal_power_get_usb_voltage,float, -Function,+,furi_hal_power_info_get,void,"FuriHalPowerInfoCallback, void*" +Function,+,furi_hal_power_info_get,void,"PropertyValueCallback, char, void*" Function,-,furi_hal_power_init,void, Function,+,furi_hal_power_insomnia_enter,void, Function,+,furi_hal_power_insomnia_exit,void, @@ -2039,6 +2039,7 @@ Function,+,powf,float,"float, float" Function,-,powl,long double,"long double, long double" Function,-,printf,int,"const char*, ..." Function,-,prng_successor,uint32_t,"uint32_t, uint32_t" +Function,+,property_value_out,void,"PropertyValueContext*, const char*, unsigned int, ..." Function,+,protocol_dict_alloc,ProtocolDict*,"const ProtocolBase**, size_t" Function,+,protocol_dict_decoders_feed,ProtocolId,"ProtocolDict*, _Bool, uint32_t" Function,+,protocol_dict_decoders_feed_by_feature,ProtocolId,"ProtocolDict*, uint32_t, _Bool, uint32_t" @@ -2284,10 +2285,14 @@ Function,+,rpc_session_set_context,void,"RpcSession*, void*" Function,+,rpc_session_set_send_bytes_callback,void,"RpcSession*, RpcSendBytesCallback" Function,+,rpc_session_set_terminated_callback,void,"RpcSession*, RpcSessionTerminatedCallback" Function,+,rpc_system_app_confirm,void,"RpcAppSystem*, RpcAppSystemEvent, _Bool" +Function,+,rpc_system_app_exchange_data,void,"RpcAppSystem*, const uint8_t*, size_t" Function,+,rpc_system_app_get_data,const char*,RpcAppSystem* Function,+,rpc_system_app_send_exited,void,RpcAppSystem* Function,+,rpc_system_app_send_started,void,RpcAppSystem* Function,+,rpc_system_app_set_callback,void,"RpcAppSystem*, RpcAppSystemCallback, void*" +Function,+,rpc_system_app_set_data_exchange_callback,void,"RpcAppSystem*, RpcAppSystemDataExchangeCallback, void*" +Function,+,rpc_system_app_set_error_code,void,"RpcAppSystem*, uint32_t" +Function,+,rpc_system_app_set_error_text,void,"RpcAppSystem*, const char*" Function,-,rpmatch,int,const char* Function,+,saved_struct_load,_Bool,"const char*, void*, size_t, uint8_t, uint8_t" Function,+,saved_struct_save,_Bool,"const char*, void*, size_t, uint8_t, uint8_t" diff --git a/firmware/targets/f7/furi_hal/furi_hal_info.c b/firmware/targets/f7/furi_hal/furi_hal_info.c index 15ce41a207c..c984ef4d560 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_info.c +++ b/firmware/targets/f7/furi_hal/furi_hal_info.c @@ -8,16 +8,25 @@ #include #include -void furi_hal_info_get(FuriHalInfoValueCallback out, void* context) { - FuriString* value; - value = furi_string_alloc(); +void furi_hal_info_get(PropertyValueCallback out, char sep, void* context) { + FuriString* key = furi_string_alloc(); + FuriString* value = furi_string_alloc(); + + PropertyValueContext property_context = { + .key = key, .value = value, .out = out, .sep = sep, .last = false, .context = context}; // Device Info version - out("device_info_major", "2", false, context); - out("device_info_minor", "0", false, context); + if(sep == '.') { + property_value_out(&property_context, NULL, 2, "format", "major", "3"); + property_value_out(&property_context, NULL, 2, "format", "minor", "0"); + } else { + property_value_out(&property_context, NULL, 3, "device", "info", "major", "2"); + property_value_out(&property_context, NULL, 3, "device", "info", "minor", "0"); + } // Model name - out("hardware_model", furi_hal_version_get_model_name(), false, context); + property_value_out( + &property_context, NULL, 2, "hardware", "model", furi_hal_version_get_model_name()); // Unique ID furi_string_reset(value); @@ -25,93 +34,211 @@ void furi_hal_info_get(FuriHalInfoValueCallback out, void* context) { for(size_t i = 0; i < furi_hal_version_uid_size(); i++) { furi_string_cat_printf(value, "%02X", uid[i]); } - out("hardware_uid", furi_string_get_cstr(value), false, context); + property_value_out(&property_context, NULL, 2, "hardware", "uid", furi_string_get_cstr(value)); // OTP Revision - furi_string_printf(value, "%d", furi_hal_version_get_otp_version()); - out("hardware_otp_ver", furi_string_get_cstr(value), false, context); - furi_string_printf(value, "%lu", furi_hal_version_get_hw_timestamp()); - out("hardware_timestamp", furi_string_get_cstr(value), false, context); + property_value_out( + &property_context, "%d", 3, "hardware", "otp", "ver", furi_hal_version_get_otp_version()); + property_value_out( + &property_context, "%lu", 2, "hardware", "timestamp", furi_hal_version_get_hw_timestamp()); // Board Revision - furi_string_printf(value, "%d", furi_hal_version_get_hw_version()); - out("hardware_ver", furi_string_get_cstr(value), false, context); - furi_string_printf(value, "%d", furi_hal_version_get_hw_target()); - out("hardware_target", furi_string_get_cstr(value), false, context); - furi_string_printf(value, "%d", furi_hal_version_get_hw_body()); - out("hardware_body", furi_string_get_cstr(value), false, context); - furi_string_printf(value, "%d", furi_hal_version_get_hw_connect()); - out("hardware_connect", furi_string_get_cstr(value), false, context); - furi_string_printf(value, "%d", furi_hal_version_get_hw_display()); - out("hardware_display", furi_string_get_cstr(value), false, context); + property_value_out( + &property_context, "%d", 2, "hardware", "ver", furi_hal_version_get_hw_version()); + property_value_out( + &property_context, "%d", 2, "hardware", "target", furi_hal_version_get_hw_target()); + property_value_out( + &property_context, "%d", 2, "hardware", "body", furi_hal_version_get_hw_body()); + property_value_out( + &property_context, "%d", 2, "hardware", "connect", furi_hal_version_get_hw_connect()); + property_value_out( + &property_context, "%d", 2, "hardware", "display", furi_hal_version_get_hw_display()); // Board Personification - furi_string_printf(value, "%d", furi_hal_version_get_hw_color()); - out("hardware_color", furi_string_get_cstr(value), false, context); - furi_string_printf(value, "%d", furi_hal_version_get_hw_region()); - out("hardware_region", furi_string_get_cstr(value), false, context); - out("hardware_region_provisioned", furi_hal_region_get_name(), false, context); + property_value_out( + &property_context, "%d", 2, "hardware", "color", furi_hal_version_get_hw_color()); + + if(sep == '.') { + property_value_out( + &property_context, + "%d", + 3, + "hardware", + "region", + "builtin", + furi_hal_version_get_hw_region()); + } else { + property_value_out( + &property_context, "%d", 2, "hardware", "region", furi_hal_version_get_hw_region()); + } + + property_value_out( + &property_context, + NULL, + 3, + "hardware", + "region", + "provisioned", + furi_hal_region_get_name()); + const char* name = furi_hal_version_get_name_ptr(); if(name) { - out("hardware_name", name, false, context); + property_value_out(&property_context, NULL, 2, "hardware", "name", name); } // Firmware version const Version* firmware_version = furi_hal_version_get_firmware_version(); if(firmware_version) { - out("firmware_commit", version_get_githash(firmware_version), false, context); - out("firmware_commit_dirty", - version_get_dirty_flag(firmware_version) ? "true" : "false", - false, - context); - out("firmware_branch", version_get_gitbranch(firmware_version), false, context); - out("firmware_branch_num", version_get_gitbranchnum(firmware_version), false, context); - out("firmware_version", version_get_version(firmware_version), false, context); - out("firmware_build_date", version_get_builddate(firmware_version), false, context); - furi_string_printf(value, "%d", version_get_target(firmware_version)); - out("firmware_target", furi_string_get_cstr(value), false, context); + if(sep == '.') { + property_value_out( + &property_context, + NULL, + 3, + "firmware", + "commit", + "hash", + version_get_githash(firmware_version)); + } else { + property_value_out( + &property_context, + NULL, + 2, + "firmware", + "commit", + version_get_githash(firmware_version)); + } + + property_value_out( + &property_context, + NULL, + 3, + "firmware", + "commit", + "dirty", + version_get_dirty_flag(firmware_version) ? "true" : "false"); + + if(sep == '.') { + property_value_out( + &property_context, + NULL, + 3, + "firmware", + "branch", + "name", + version_get_gitbranch(firmware_version)); + } else { + property_value_out( + &property_context, + NULL, + 2, + "firmware", + "branch", + version_get_gitbranch(firmware_version)); + } + + property_value_out( + &property_context, + NULL, + 3, + "firmware", + "branch", + "num", + version_get_gitbranchnum(firmware_version)); + property_value_out( + &property_context, + NULL, + 2, + "firmware", + "version", + version_get_version(firmware_version)); + property_value_out( + &property_context, + NULL, + 3, + "firmware", + "build", + "date", + version_get_builddate(firmware_version)); + property_value_out( + &property_context, "%d", 2, "firmware", "target", version_get_target(firmware_version)); } if(furi_hal_bt_is_alive()) { const BleGlueC2Info* ble_c2_info = ble_glue_get_c2_info(); - out("radio_alive", "true", false, context); - out("radio_mode", ble_c2_info->mode == BleGlueC2ModeFUS ? "FUS" : "Stack", false, context); + property_value_out(&property_context, NULL, 2, "radio", "alive", "true"); + property_value_out( + &property_context, + NULL, + 2, + "radio", + "mode", + ble_c2_info->mode == BleGlueC2ModeFUS ? "FUS" : "Stack"); // FUS Info - furi_string_printf(value, "%d", ble_c2_info->FusVersionMajor); - out("radio_fus_major", furi_string_get_cstr(value), false, context); - furi_string_printf(value, "%d", ble_c2_info->FusVersionMinor); - out("radio_fus_minor", furi_string_get_cstr(value), false, context); - furi_string_printf(value, "%d", ble_c2_info->FusVersionSub); - out("radio_fus_sub", furi_string_get_cstr(value), false, context); - furi_string_printf(value, "%dK", ble_c2_info->FusMemorySizeSram2B); - out("radio_fus_sram2b", furi_string_get_cstr(value), false, context); - furi_string_printf(value, "%dK", ble_c2_info->FusMemorySizeSram2A); - out("radio_fus_sram2a", furi_string_get_cstr(value), false, context); - furi_string_printf(value, "%dK", ble_c2_info->FusMemorySizeFlash * 4); - out("radio_fus_flash", furi_string_get_cstr(value), false, context); + property_value_out( + &property_context, "%d", 3, "radio", "fus", "major", ble_c2_info->FusVersionMajor); + property_value_out( + &property_context, "%d", 3, "radio", "fus", "minor", ble_c2_info->FusVersionMinor); + property_value_out( + &property_context, "%d", 3, "radio", "fus", "sub", ble_c2_info->FusVersionSub); + property_value_out( + &property_context, + "%dK", + 3, + "radio", + "fus", + "sram2b", + ble_c2_info->FusMemorySizeSram2B); + property_value_out( + &property_context, + "%dK", + 3, + "radio", + "fus", + "sram2a", + ble_c2_info->FusMemorySizeSram2A); + property_value_out( + &property_context, + "%dK", + 3, + "radio", + "fus", + "flash", + ble_c2_info->FusMemorySizeFlash * 4); // Stack Info - furi_string_printf(value, "%d", ble_c2_info->StackType); - out("radio_stack_type", furi_string_get_cstr(value), false, context); - furi_string_printf(value, "%d", ble_c2_info->VersionMajor); - out("radio_stack_major", furi_string_get_cstr(value), false, context); - furi_string_printf(value, "%d", ble_c2_info->VersionMinor); - out("radio_stack_minor", furi_string_get_cstr(value), false, context); - furi_string_printf(value, "%d", ble_c2_info->VersionSub); - out("radio_stack_sub", furi_string_get_cstr(value), false, context); - furi_string_printf(value, "%d", ble_c2_info->VersionBranch); - out("radio_stack_branch", furi_string_get_cstr(value), false, context); - furi_string_printf(value, "%d", ble_c2_info->VersionReleaseType); - out("radio_stack_release", furi_string_get_cstr(value), false, context); - furi_string_printf(value, "%dK", ble_c2_info->MemorySizeSram2B); - out("radio_stack_sram2b", furi_string_get_cstr(value), false, context); - furi_string_printf(value, "%dK", ble_c2_info->MemorySizeSram2A); - out("radio_stack_sram2a", furi_string_get_cstr(value), false, context); - furi_string_printf(value, "%dK", ble_c2_info->MemorySizeSram1); - out("radio_stack_sram1", furi_string_get_cstr(value), false, context); - furi_string_printf(value, "%dK", ble_c2_info->MemorySizeFlash * 4); - out("radio_stack_flash", furi_string_get_cstr(value), false, context); + property_value_out( + &property_context, "%d", 3, "radio", "stack", "type", ble_c2_info->StackType); + property_value_out( + &property_context, "%d", 3, "radio", "stack", "major", ble_c2_info->VersionMajor); + property_value_out( + &property_context, "%d", 3, "radio", "stack", "minor", ble_c2_info->VersionMinor); + property_value_out( + &property_context, "%d", 3, "radio", "stack", "sub", ble_c2_info->VersionSub); + property_value_out( + &property_context, "%d", 3, "radio", "stack", "branch", ble_c2_info->VersionBranch); + property_value_out( + &property_context, + "%d", + 3, + "radio", + "stack", + "release", + ble_c2_info->VersionReleaseType); + property_value_out( + &property_context, "%dK", 3, "radio", "stack", "sram2b", ble_c2_info->MemorySizeSram2B); + property_value_out( + &property_context, "%dK", 3, "radio", "stack", "sram2a", ble_c2_info->MemorySizeSram2A); + property_value_out( + &property_context, "%dK", 3, "radio", "stack", "sram1", ble_c2_info->MemorySizeSram1); + property_value_out( + &property_context, + "%dK", + 3, + "radio", + "stack", + "flash", + ble_c2_info->MemorySizeFlash * 4); // Mac address furi_string_reset(value); @@ -119,23 +246,33 @@ void furi_hal_info_get(FuriHalInfoValueCallback out, void* context) { for(size_t i = 0; i < 6; i++) { furi_string_cat_printf(value, "%02X", ble_mac[i]); } - out("radio_ble_mac", furi_string_get_cstr(value), false, context); + property_value_out( + &property_context, NULL, 3, "radio", "ble", "mac", furi_string_get_cstr(value)); // Signature verification uint8_t enclave_keys = 0; uint8_t enclave_valid_keys = 0; bool enclave_valid = furi_hal_crypto_verify_enclave(&enclave_keys, &enclave_valid_keys); - furi_string_printf(value, "%d", enclave_valid_keys); - out("enclave_valid_keys", furi_string_get_cstr(value), false, context); - out("enclave_valid", enclave_valid ? "true" : "false", false, context); + if(sep == '.') { + property_value_out( + &property_context, "%d", 3, "enclave", "keys", "valid", enclave_valid_keys); + } else { + property_value_out( + &property_context, "%d", 3, "enclave", "valid", "keys", enclave_valid_keys); + } + + property_value_out( + &property_context, NULL, 2, "enclave", "valid", enclave_valid ? "true" : "false"); } else { - out("radio_alive", "false", false, context); + property_value_out(&property_context, NULL, 2, "radio", "alive", "false"); } - furi_string_printf(value, "%u", PROTOBUF_MAJOR_VERSION); - out("protobuf_version_major", furi_string_get_cstr(value), false, context); - furi_string_printf(value, "%u", PROTOBUF_MINOR_VERSION); - out("protobuf_version_minor", furi_string_get_cstr(value), true, context); + property_value_out( + &property_context, "%u", 3, "protobuf", "version", "major", PROTOBUF_MAJOR_VERSION); + property_context.last = true; + property_value_out( + &property_context, "%u", 3, "protobuf", "version", "minor", PROTOBUF_MINOR_VERSION); + furi_string_free(key); furi_string_free(value); } diff --git a/firmware/targets/f7/furi_hal/furi_hal_power.c b/firmware/targets/f7/furi_hal/furi_hal_power.c index 86505c573ed..88e191e9e5b 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_power.c +++ b/firmware/targets/f7/furi_hal/furi_hal_power.c @@ -422,76 +422,6 @@ float furi_hal_power_get_usb_voltage() { return ret; } -void furi_hal_power_dump_state() { - BatteryStatus battery_status; - OperationStatus operation_status; - - furi_hal_i2c_acquire(&furi_hal_i2c_handle_power); - - if(bq27220_get_battery_status(&furi_hal_i2c_handle_power, &battery_status) == BQ27220_ERROR || - bq27220_get_operation_status(&furi_hal_i2c_handle_power, &operation_status) == - BQ27220_ERROR) { - printf("Failed to get bq27220 status. Communication error.\r\n"); - } else { - // Operation status register - printf( - "bq27220: CALMD: %d, SEC: %d, EDV2: %d, VDQ: %d, INITCOMP: %d, SMTH: %d, BTPINT: %d, CFGUPDATE: %d\r\n", - operation_status.CALMD, - operation_status.SEC, - operation_status.EDV2, - operation_status.VDQ, - operation_status.INITCOMP, - operation_status.SMTH, - operation_status.BTPINT, - operation_status.CFGUPDATE); - // Battery status register, part 1 - printf( - "bq27220: CHGINH: %d, FC: %d, OTD: %d, OTC: %d, SLEEP: %d, OCVFAIL: %d, OCVCOMP: %d, FD: %d\r\n", - battery_status.CHGINH, - battery_status.FC, - battery_status.OTD, - battery_status.OTC, - battery_status.SLEEP, - battery_status.OCVFAIL, - battery_status.OCVCOMP, - battery_status.FD); - // Battery status register, part 2 - printf( - "bq27220: DSG: %d, SYSDWN: %d, TDA: %d, BATTPRES: %d, AUTH_GD: %d, OCVGD: %d, TCA: %d, RSVD: %d\r\n", - battery_status.DSG, - battery_status.SYSDWN, - battery_status.TDA, - battery_status.BATTPRES, - battery_status.AUTH_GD, - battery_status.OCVGD, - battery_status.TCA, - battery_status.RSVD); - // Voltage and current info - printf( - "bq27220: Full capacity: %dmAh, Design capacity: %dmAh, Remaining capacity: %dmAh, State of Charge: %d%%, State of health: %d%%\r\n", - bq27220_get_full_charge_capacity(&furi_hal_i2c_handle_power), - bq27220_get_design_capacity(&furi_hal_i2c_handle_power), - bq27220_get_remaining_capacity(&furi_hal_i2c_handle_power), - bq27220_get_state_of_charge(&furi_hal_i2c_handle_power), - bq27220_get_state_of_health(&furi_hal_i2c_handle_power)); - printf( - "bq27220: Voltage: %dmV, Current: %dmA, Temperature: %dC\r\n", - bq27220_get_voltage(&furi_hal_i2c_handle_power), - bq27220_get_current(&furi_hal_i2c_handle_power), - (int)furi_hal_power_get_battery_temperature_internal(FuriHalPowerICFuelGauge)); - } - - printf( - "bq25896: VBUS: %d, VSYS: %d, VBAT: %d, Current: %d, NTC: %ldm%%\r\n", - bq25896_get_vbus_voltage(&furi_hal_i2c_handle_power), - bq25896_get_vsys_voltage(&furi_hal_i2c_handle_power), - bq25896_get_vbat_voltage(&furi_hal_i2c_handle_power), - bq25896_get_vbat_current(&furi_hal_i2c_handle_power), - bq25896_get_ntc_mpct(&furi_hal_i2c_handle_power)); - - furi_hal_i2c_release(&furi_hal_i2c_handle_power); -} - void furi_hal_power_enable_external_3_3v() { furi_hal_gpio_write(&periph_power, 1); } @@ -526,57 +456,227 @@ void furi_hal_power_suppress_charge_exit() { } } -void furi_hal_power_info_get(FuriHalPowerInfoCallback out, void* context) { +void furi_hal_power_info_get(PropertyValueCallback out, char sep, void* context) { furi_assert(out); - FuriString* value; - value = furi_string_alloc(); + FuriString* value = furi_string_alloc(); + FuriString* key = furi_string_alloc(); - // Power Info version - out("power_info_major", "1", false, context); - out("power_info_minor", "0", false, context); + PropertyValueContext property_context = { + .key = key, .value = value, .out = out, .sep = sep, .last = false, .context = context}; - uint8_t charge = furi_hal_power_get_pct(); + if(sep == '.') { + property_value_out(&property_context, NULL, 2, "format", "major", "2"); + property_value_out(&property_context, NULL, 2, "format", "minor", "0"); + } else { + property_value_out(&property_context, NULL, 3, "power", "info", "major", "1"); + property_value_out(&property_context, NULL, 3, "power", "info", "minor", "0"); + } - furi_string_printf(value, "%u", charge); - out("charge_level", furi_string_get_cstr(value), false, context); + uint8_t charge = furi_hal_power_get_pct(); + property_value_out(&property_context, "%u", 2, "charge", "level", charge); + const char* charge_state; if(furi_hal_power_is_charging()) { if(charge < 100) { - furi_string_printf(value, "charging"); + charge_state = "charging"; } else { - furi_string_printf(value, "charged"); + charge_state = "charged"; } } else { - furi_string_printf(value, "discharging"); + charge_state = "discharging"; } - out("charge_state", furi_string_get_cstr(value), false, context); + property_value_out(&property_context, NULL, 2, "charge", "state", charge_state); uint16_t voltage = (uint16_t)(furi_hal_power_get_battery_voltage(FuriHalPowerICFuelGauge) * 1000.f); - furi_string_printf(value, "%u", voltage); - out("battery_voltage", furi_string_get_cstr(value), false, context); - + property_value_out(&property_context, "%u", 2, "battery", "voltage", voltage); int16_t current = (int16_t)(furi_hal_power_get_battery_current(FuriHalPowerICFuelGauge) * 1000.f); - furi_string_printf(value, "%d", current); - out("battery_current", furi_string_get_cstr(value), false, context); - + property_value_out(&property_context, "%d", 2, "battery", "current", current); int16_t temperature = (int16_t)furi_hal_power_get_battery_temperature(FuriHalPowerICFuelGauge); - furi_string_printf(value, "%d", temperature); - out("gauge_temp", furi_string_get_cstr(value), false, context); + property_value_out(&property_context, "%d", 2, "battery", "temp", temperature); + property_value_out( + &property_context, "%u", 2, "battery", "health", furi_hal_power_get_bat_health_pct()); + property_value_out( + &property_context, + "%lu", + 2, + "capacity", + "remain", + furi_hal_power_get_battery_remaining_capacity()); + property_value_out( + &property_context, + "%lu", + 2, + "capacity", + "full", + furi_hal_power_get_battery_full_capacity()); + property_context.last = true; + property_value_out( + &property_context, + "%lu", + 2, + "capacity", + "design", + furi_hal_power_get_battery_design_capacity()); + + furi_string_free(key); + furi_string_free(value); +} + +void furi_hal_power_debug_get(PropertyValueCallback out, void* context) { + furi_assert(out); + + FuriString* value = furi_string_alloc(); + FuriString* key = furi_string_alloc(); + + PropertyValueContext property_context = { + .key = key, .value = value, .out = out, .sep = '.', .last = false, .context = context}; + + BatteryStatus battery_status; + OperationStatus operation_status; - furi_string_printf(value, "%u", furi_hal_power_get_bat_health_pct()); - out("battery_health", furi_string_get_cstr(value), false, context); + furi_hal_i2c_acquire(&furi_hal_i2c_handle_power); - furi_string_printf(value, "%lu", furi_hal_power_get_battery_remaining_capacity()); - out("capacity_remain", furi_string_get_cstr(value), false, context); + // Power Debug version + property_value_out(&property_context, NULL, 2, "format", "major", "1"); + property_value_out(&property_context, NULL, 2, "format", "minor", "0"); + + property_value_out( + &property_context, + "%d", + 2, + "charger", + "vbus", + bq25896_get_vbus_voltage(&furi_hal_i2c_handle_power)); + property_value_out( + &property_context, + "%d", + 2, + "charger", + "vsys", + bq25896_get_vsys_voltage(&furi_hal_i2c_handle_power)); + property_value_out( + &property_context, + "%d", + 2, + "charger", + "vbat", + bq25896_get_vbat_voltage(&furi_hal_i2c_handle_power)); + property_value_out( + &property_context, + "%d", + 2, + "charger", + "current", + bq25896_get_vbat_current(&furi_hal_i2c_handle_power)); + + const uint32_t ntc_mpct = bq25896_get_ntc_mpct(&furi_hal_i2c_handle_power); + + if(bq27220_get_battery_status(&furi_hal_i2c_handle_power, &battery_status) != BQ27220_ERROR && + bq27220_get_operation_status(&furi_hal_i2c_handle_power, &operation_status) != + BQ27220_ERROR) { + property_value_out(&property_context, "%lu", 2, "charger", "ntc", ntc_mpct); + property_value_out(&property_context, "%d", 2, "gauge", "calmd", operation_status.CALMD); + property_value_out(&property_context, "%d", 2, "gauge", "sec", operation_status.SEC); + property_value_out(&property_context, "%d", 2, "gauge", "edv2", operation_status.EDV2); + property_value_out(&property_context, "%d", 2, "gauge", "vdq", operation_status.VDQ); + property_value_out( + &property_context, "%d", 2, "gauge", "initcomp", operation_status.INITCOMP); + property_value_out(&property_context, "%d", 2, "gauge", "smth", operation_status.SMTH); + property_value_out(&property_context, "%d", 2, "gauge", "btpint", operation_status.BTPINT); + property_value_out( + &property_context, "%d", 2, "gauge", "cfgupdate", operation_status.CFGUPDATE); - furi_string_printf(value, "%lu", furi_hal_power_get_battery_full_capacity()); - out("capacity_full", furi_string_get_cstr(value), false, context); + // Battery status register, part 1 + property_value_out(&property_context, "%d", 2, "gauge", "chginh", battery_status.CHGINH); + property_value_out(&property_context, "%d", 2, "gauge", "fc", battery_status.FC); + property_value_out(&property_context, "%d", 2, "gauge", "otd", battery_status.OTD); + property_value_out(&property_context, "%d", 2, "gauge", "otc", battery_status.OTC); + property_value_out(&property_context, "%d", 2, "gauge", "sleep", battery_status.SLEEP); + property_value_out(&property_context, "%d", 2, "gauge", "ocvfail", battery_status.OCVFAIL); + property_value_out(&property_context, "%d", 2, "gauge", "ocvcomp", battery_status.OCVCOMP); + property_value_out(&property_context, "%d", 2, "gauge", "fd", battery_status.FD); - furi_string_printf(value, "%lu", furi_hal_power_get_battery_design_capacity()); - out("capacity_design", furi_string_get_cstr(value), true, context); + // Battery status register, part 2 + property_value_out(&property_context, "%d", 2, "gauge", "dsg", battery_status.DSG); + property_value_out(&property_context, "%d", 2, "gauge", "sysdwn", battery_status.SYSDWN); + property_value_out(&property_context, "%d", 2, "gauge", "tda", battery_status.TDA); + property_value_out( + &property_context, "%d", 2, "gauge", "battpres", battery_status.BATTPRES); + property_value_out(&property_context, "%d", 2, "gauge", "authgd", battery_status.AUTH_GD); + property_value_out(&property_context, "%d", 2, "gauge", "ocvgd", battery_status.OCVGD); + property_value_out(&property_context, "%d", 2, "gauge", "tca", battery_status.TCA); + property_value_out(&property_context, "%d", 2, "gauge", "rsvd", battery_status.RSVD); + // Voltage and current info + property_value_out( + &property_context, + "%d", + 3, + "gauge", + "capacity", + "full", + bq27220_get_full_charge_capacity(&furi_hal_i2c_handle_power)); + property_value_out( + &property_context, + "%d", + 3, + "gauge", + "capacity", + "design", + bq27220_get_design_capacity(&furi_hal_i2c_handle_power)); + property_value_out( + &property_context, + "%d", + 3, + "gauge", + "capacity", + "remain", + bq27220_get_remaining_capacity(&furi_hal_i2c_handle_power)); + property_value_out( + &property_context, + "%d", + 3, + "gauge", + "state", + "charge", + bq27220_get_state_of_charge(&furi_hal_i2c_handle_power)); + property_value_out( + &property_context, + "%d", + 3, + "gauge", + "state", + "health", + bq27220_get_state_of_health(&furi_hal_i2c_handle_power)); + property_value_out( + &property_context, + "%d", + 2, + "gauge", + "voltage", + bq27220_get_voltage(&furi_hal_i2c_handle_power)); + property_value_out( + &property_context, + "%d", + 2, + "gauge", + "current", + bq27220_get_current(&furi_hal_i2c_handle_power)); + + property_context.last = true; + const int battery_temp = + (int)furi_hal_power_get_battery_temperature_internal(FuriHalPowerICFuelGauge); + property_value_out(&property_context, "%d", 2, "gauge", "temperature", battery_temp); + } else { + property_context.last = true; + property_value_out(&property_context, "%lu", 2, "charger", "ntc", ntc_mpct); + } + + furi_string_free(key); furi_string_free(value); + + furi_hal_i2c_release(&furi_hal_i2c_handle_power); } diff --git a/firmware/targets/furi_hal_include/furi_hal_info.h b/firmware/targets/furi_hal_include/furi_hal_info.h index 4a335f2afc2..fa3267f5d47 100644 --- a/firmware/targets/furi_hal_include/furi_hal_info.h +++ b/firmware/targets/furi_hal_include/furi_hal_info.h @@ -7,27 +7,20 @@ #include #include +#include +#include #ifdef __cplusplus extern "C" { #endif -/** Callback type called every time another key-value pair of device information is ready - * - * @param key[in] device information type identifier - * @param value[in] device information value - * @param last[in] whether the passed key-value pair is the last one - * @param context[in] to pass to callback - */ -typedef void ( - *FuriHalInfoValueCallback)(const char* key, const char* value, bool last, void* context); - /** Get device information * * @param[in] callback callback to provide with new data + * @param[in] sep category separator character * @param[in] context context to pass to callback */ -void furi_hal_info_get(FuriHalInfoValueCallback callback, void* context); +void furi_hal_info_get(PropertyValueCallback callback, char sep, void* context); #ifdef __cplusplus } diff --git a/firmware/targets/furi_hal_include/furi_hal_power.h b/firmware/targets/furi_hal_include/furi_hal_power.h index e94877afcb1..a78d09fe4b8 100644 --- a/firmware/targets/furi_hal_include/furi_hal_power.h +++ b/firmware/targets/furi_hal_include/furi_hal_power.h @@ -7,6 +7,8 @@ #include #include +#include +#include #ifdef __cplusplus extern "C" { @@ -167,10 +169,6 @@ float furi_hal_power_get_battery_temperature(FuriHalPowerIC ic); */ float furi_hal_power_get_usb_voltage(); -/** Get power system component state - */ -void furi_hal_power_dump_state(); - /** Enable 3.3v on external gpio and sd card */ void furi_hal_power_enable_external_3_3v(); @@ -189,22 +187,20 @@ void furi_hal_power_suppress_charge_enter(); */ void furi_hal_power_suppress_charge_exit(); -/** Callback type called by furi_hal_power_info_get every time another key-value pair of information is ready +/** Get power information * - * @param key[in] power information type identifier - * @param value[in] power information value - * @param last[in] whether the passed key-value pair is the last one - * @param context[in] to pass to callback + * @param[in] callback callback to provide with new data + * @param[in] sep category separator character + * @param[in] context context to pass to callback */ -typedef void ( - *FuriHalPowerInfoCallback)(const char* key, const char* value, bool last, void* context); +void furi_hal_power_info_get(PropertyValueCallback callback, char sep, void* context); -/** Get power information +/** Get power debug information * * @param[in] callback callback to provide with new data * @param[in] context context to pass to callback */ -void furi_hal_power_info_get(FuriHalPowerInfoCallback callback, void* context); +void furi_hal_power_debug_get(PropertyValueCallback callback, void* context); #ifdef __cplusplus } diff --git a/lib/toolbox/property.c b/lib/toolbox/property.c new file mode 100644 index 00000000000..c258cdd6a0a --- /dev/null +++ b/lib/toolbox/property.c @@ -0,0 +1,33 @@ +#include "property.h" + +#include + +void property_value_out(PropertyValueContext* ctx, const char* fmt, unsigned int nparts, ...) { + furi_assert(ctx); + furi_string_reset(ctx->key); + + va_list args; + va_start(args, nparts); + + for(size_t i = 0; i < nparts; ++i) { + const char* keypart = va_arg(args, const char*); + furi_string_cat(ctx->key, keypart); + if(i < nparts - 1) { + furi_string_push_back(ctx->key, ctx->sep); + } + } + + const char* value_str; + + if(fmt) { + furi_string_vprintf(ctx->value, fmt, args); + value_str = furi_string_get_cstr(ctx->value); + } else { + // C string passthrough (no formatting) + value_str = va_arg(args, const char*); + } + + va_end(args); + + ctx->out(furi_string_get_cstr(ctx->key), value_str, ctx->last, ctx->context); +} diff --git a/lib/toolbox/property.h b/lib/toolbox/property.h new file mode 100644 index 00000000000..524d8ff1605 --- /dev/null +++ b/lib/toolbox/property.h @@ -0,0 +1,39 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +/** Callback type called every time another key-value pair of device information is ready + * + * @param key[in] device information type identifier + * @param value[in] device information value + * @param last[in] whether the passed key-value pair is the last one + * @param context[in] to pass to callback + */ +typedef void (*PropertyValueCallback)(const char* key, const char* value, bool last, void* context); + +typedef struct { + FuriString* key; /**< key string buffer, must be initialised before use */ + FuriString* value; /**< value string buffer, must be initialised before use */ + PropertyValueCallback out; /**< output callback function */ + char sep; /**< separator character between key parts */ + bool last; /**< flag to indicate last element */ + void* context; /**< user-defined context, passed through to out callback */ +} PropertyValueContext; + +/** Builds key and value strings and outputs them via a callback function + * + * @param ctx[in] local property context + * @param fmt[in] value format, set to NULL to bypass formatting + * @param nparts[in] number of key parts (separated by character) + * @param ...[in] list of key parts followed by value + */ +void property_value_out(PropertyValueContext* ctx, const char* fmt, unsigned int nparts, ...); + +#ifdef __cplusplus +} +#endif From 297f185ef4f8060f581be4307946ebf198f411dc Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Tue, 29 Nov 2022 22:50:55 +1000 Subject: [PATCH 249/824] Blocking USB driver API (#2009) * invalidate memmgt thread dict * Core: rollback memmgt thread dict invalidation * Dialogs: move api lock to toolbox * HAL: blocking usb API * HAL usb: fix api return data * HAL usb: api optimization * api lock: test results * Fix build errors * DAP Link: fix imports * Crash when malloc in ISR * Fix dap-link copypaste error * Moar memory management crashes. * Crash when malloc in IRQ, not ISR * USB-UART: Blocking VCP mode switch Co-authored-by: nminaylov Co-authored-by: Aleksandr Kutuzov --- applications/main/gpio/gpio_app_i.h | 1 + applications/main/gpio/gpio_custom_event.h | 1 + .../gpio/scenes/gpio_scene_usb_uart_config.c | 78 ++-- applications/main/gpio/usb_uart_bridge.c | 6 + applications/plugins/dap_link/dap_link.c | 1 - .../plugins/dap_link/usb/dap_v2_usb.c | 17 - .../plugins/dap_link/usb/dap_v2_usb.h | 2 - applications/services/dialogs/dialogs.c | 4 +- applications/services/dialogs/dialogs_api.c | 10 +- .../services/dialogs/dialogs_api_lock.h | 18 - .../services/dialogs/dialogs_message.h | 2 +- .../dialogs/dialogs_module_file_browser.c | 12 +- .../services/dialogs/dialogs_module_message.c | 12 +- .../targets/f7/furi_hal/furi_hal_memory.c | 4 + firmware/targets/f7/furi_hal/furi_hal_usb.c | 427 ++++++++++++------ furi/core/memmgr_heap.c | 8 + lib/toolbox/api_lock.h | 43 ++ 17 files changed, 418 insertions(+), 228 deletions(-) delete mode 100644 applications/services/dialogs/dialogs_api_lock.h create mode 100644 lib/toolbox/api_lock.h diff --git a/applications/main/gpio/gpio_app_i.h b/applications/main/gpio/gpio_app_i.h index 85c5c332e40..8f805891bbc 100644 --- a/applications/main/gpio/gpio_app_i.h +++ b/applications/main/gpio/gpio_app_i.h @@ -29,6 +29,7 @@ struct GpioApp { GpioTest* gpio_test; GpioUsbUart* gpio_usb_uart; UsbUartBridge* usb_uart_bridge; + UsbUartConfig* usb_uart_cfg; }; typedef enum { diff --git a/applications/main/gpio/gpio_custom_event.h b/applications/main/gpio/gpio_custom_event.h index 2bf3e5a8b37..72b8feccd00 100644 --- a/applications/main/gpio/gpio_custom_event.h +++ b/applications/main/gpio/gpio_custom_event.h @@ -9,4 +9,5 @@ typedef enum { GpioCustomEventErrorBack, GpioUsbUartEventConfig, + GpioUsbUartEventConfigSet, } GpioCustomEvent; diff --git a/applications/main/gpio/scenes/gpio_scene_usb_uart_config.c b/applications/main/gpio/scenes/gpio_scene_usb_uart_config.c index c114d79a222..55b04ed67fb 100644 --- a/applications/main/gpio/scenes/gpio_scene_usb_uart_config.c +++ b/applications/main/gpio/scenes/gpio_scene_usb_uart_config.c @@ -9,8 +9,6 @@ typedef enum { UsbUartLineIndexFlow, } LineIndex; -static UsbUartConfig* cfg_set; - static const char* vcp_ch[] = {"0 (CLI)", "1"}; static const char* uart_ch[] = {"13,14", "15,16"}; static const char* flow_pins[] = {"None", "2,3", "6,7", "16,15"}; @@ -28,8 +26,14 @@ static const uint32_t baudrate_list[] = { }; bool gpio_scene_usb_uart_cfg_on_event(void* context, SceneManagerEvent event) { - UNUSED(context); - UNUSED(event); + GpioApp* app = context; + furi_assert(app); + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == GpioUsbUartEventConfigSet) { + usb_uart_set_config(app->usb_uart_bridge, app->usb_uart_cfg); + return true; + } + } return false; } @@ -38,55 +42,59 @@ void line_ensure_flow_invariant(GpioApp* app) { // selected. This function enforces that invariant by resetting flow_pins // to None if it is configured to 16,15 when LPUART is selected. - uint8_t available_flow_pins = cfg_set->uart_ch == FuriHalUartIdLPUART1 ? 3 : 4; + uint8_t available_flow_pins = app->usb_uart_cfg->uart_ch == FuriHalUartIdLPUART1 ? 3 : 4; VariableItem* item = app->var_item_flow; variable_item_set_values_count(item, available_flow_pins); - if(cfg_set->flow_pins >= available_flow_pins) { - cfg_set->flow_pins = 0; - usb_uart_set_config(app->usb_uart_bridge, cfg_set); + if(app->usb_uart_cfg->flow_pins >= available_flow_pins) { + app->usb_uart_cfg->flow_pins = 0; - variable_item_set_current_value_index(item, cfg_set->flow_pins); - variable_item_set_current_value_text(item, flow_pins[cfg_set->flow_pins]); + variable_item_set_current_value_index(item, app->usb_uart_cfg->flow_pins); + variable_item_set_current_value_text(item, flow_pins[app->usb_uart_cfg->flow_pins]); } } static void line_vcp_cb(VariableItem* item) { GpioApp* app = variable_item_get_context(item); + furi_assert(app); uint8_t index = variable_item_get_current_value_index(item); variable_item_set_current_value_text(item, vcp_ch[index]); - cfg_set->vcp_ch = index; - usb_uart_set_config(app->usb_uart_bridge, cfg_set); + app->usb_uart_cfg->vcp_ch = index; + view_dispatcher_send_custom_event(app->view_dispatcher, GpioUsbUartEventConfigSet); } static void line_port_cb(VariableItem* item) { GpioApp* app = variable_item_get_context(item); + furi_assert(app); uint8_t index = variable_item_get_current_value_index(item); variable_item_set_current_value_text(item, uart_ch[index]); if(index == 0) - cfg_set->uart_ch = FuriHalUartIdUSART1; + app->usb_uart_cfg->uart_ch = FuriHalUartIdUSART1; else if(index == 1) - cfg_set->uart_ch = FuriHalUartIdLPUART1; - usb_uart_set_config(app->usb_uart_bridge, cfg_set); + app->usb_uart_cfg->uart_ch = FuriHalUartIdLPUART1; + line_ensure_flow_invariant(app); + view_dispatcher_send_custom_event(app->view_dispatcher, GpioUsbUartEventConfigSet); } static void line_flow_cb(VariableItem* item) { GpioApp* app = variable_item_get_context(item); + furi_assert(app); uint8_t index = variable_item_get_current_value_index(item); variable_item_set_current_value_text(item, flow_pins[index]); - cfg_set->flow_pins = index; - usb_uart_set_config(app->usb_uart_bridge, cfg_set); + app->usb_uart_cfg->flow_pins = index; + view_dispatcher_send_custom_event(app->view_dispatcher, GpioUsbUartEventConfigSet); } static void line_baudrate_cb(VariableItem* item) { GpioApp* app = variable_item_get_context(item); + furi_assert(app); uint8_t index = variable_item_get_current_value_index(item); char br_text[8]; @@ -94,28 +102,29 @@ static void line_baudrate_cb(VariableItem* item) { if(index > 0) { snprintf(br_text, 7, "%lu", baudrate_list[index - 1]); variable_item_set_current_value_text(item, br_text); - cfg_set->baudrate = baudrate_list[index - 1]; + app->usb_uart_cfg->baudrate = baudrate_list[index - 1]; } else { variable_item_set_current_value_text(item, baudrate_mode[index]); - cfg_set->baudrate = 0; + app->usb_uart_cfg->baudrate = 0; } - cfg_set->baudrate_mode = index; - usb_uart_set_config(app->usb_uart_bridge, cfg_set); + app->usb_uart_cfg->baudrate_mode = index; + view_dispatcher_send_custom_event(app->view_dispatcher, GpioUsbUartEventConfigSet); } void gpio_scene_usb_uart_cfg_on_enter(void* context) { GpioApp* app = context; + furi_assert(app); VariableItemList* var_item_list = app->var_item_list; - cfg_set = malloc(sizeof(UsbUartConfig)); - usb_uart_get_config(app->usb_uart_bridge, cfg_set); + app->usb_uart_cfg = malloc(sizeof(UsbUartConfig)); + usb_uart_get_config(app->usb_uart_bridge, app->usb_uart_cfg); VariableItem* item; char br_text[8]; item = variable_item_list_add(var_item_list, "USB Channel", 2, line_vcp_cb, app); - variable_item_set_current_value_index(item, cfg_set->vcp_ch); - variable_item_set_current_value_text(item, vcp_ch[cfg_set->vcp_ch]); + variable_item_set_current_value_index(item, app->usb_uart_cfg->vcp_ch); + variable_item_set_current_value_text(item, vcp_ch[app->usb_uart_cfg->vcp_ch]); item = variable_item_list_add( var_item_list, @@ -123,22 +132,23 @@ void gpio_scene_usb_uart_cfg_on_enter(void* context) { sizeof(baudrate_list) / sizeof(baudrate_list[0]) + 1, line_baudrate_cb, app); - variable_item_set_current_value_index(item, cfg_set->baudrate_mode); - if(cfg_set->baudrate_mode > 0) { - snprintf(br_text, 7, "%lu", baudrate_list[cfg_set->baudrate_mode - 1]); + variable_item_set_current_value_index(item, app->usb_uart_cfg->baudrate_mode); + if(app->usb_uart_cfg->baudrate_mode > 0) { + snprintf(br_text, 7, "%lu", baudrate_list[app->usb_uart_cfg->baudrate_mode - 1]); variable_item_set_current_value_text(item, br_text); } else { - variable_item_set_current_value_text(item, baudrate_mode[cfg_set->baudrate_mode]); + variable_item_set_current_value_text( + item, baudrate_mode[app->usb_uart_cfg->baudrate_mode]); } item = variable_item_list_add(var_item_list, "UART Pins", 2, line_port_cb, app); - variable_item_set_current_value_index(item, cfg_set->uart_ch); - variable_item_set_current_value_text(item, uart_ch[cfg_set->uart_ch]); + variable_item_set_current_value_index(item, app->usb_uart_cfg->uart_ch); + variable_item_set_current_value_text(item, uart_ch[app->usb_uart_cfg->uart_ch]); item = variable_item_list_add( var_item_list, "RTS/DTR Pins", COUNT_OF(flow_pins), line_flow_cb, app); - variable_item_set_current_value_index(item, cfg_set->flow_pins); - variable_item_set_current_value_text(item, flow_pins[cfg_set->flow_pins]); + variable_item_set_current_value_index(item, app->usb_uart_cfg->flow_pins); + variable_item_set_current_value_text(item, flow_pins[app->usb_uart_cfg->flow_pins]); app->var_item_flow = item; line_ensure_flow_invariant(app); @@ -155,5 +165,5 @@ void gpio_scene_usb_uart_cfg_on_exit(void* context) { GpioAppViewUsbUartCfg, variable_item_list_get_selected_item_index(app->var_item_list)); variable_item_list_reset(app->var_item_list); - free(cfg_set); + free(app->usb_uart_cfg); } diff --git a/applications/main/gpio/usb_uart_bridge.c b/applications/main/gpio/usb_uart_bridge.c index 927eedb07dd..1a82dbdc2e5 100644 --- a/applications/main/gpio/usb_uart_bridge.c +++ b/applications/main/gpio/usb_uart_bridge.c @@ -3,6 +3,7 @@ #include #include "usb_cdc.h" #include "cli/cli_vcp.h" +#include #include "cli/cli.h" #define USB_CDC_PKT_LEN CDC_DATA_SZ @@ -51,6 +52,8 @@ struct UsbUartBridge { UsbUartState st; + FuriApiLock cfg_lock; + uint8_t rx_buf[USB_CDC_PKT_LEN]; }; @@ -244,6 +247,7 @@ static int32_t usb_uart_worker(void* context) { usb_uart->cfg.flow_pins = usb_uart->cfg_new.flow_pins; events |= WorkerEvtCtrlLineSet; } + api_lock_unlock(usb_uart->cfg_lock); } if(events & WorkerEvtLineCfgSet) { if(usb_uart->cfg.baudrate == 0) @@ -352,8 +356,10 @@ void usb_uart_disable(UsbUartBridge* usb_uart) { void usb_uart_set_config(UsbUartBridge* usb_uart, UsbUartConfig* cfg) { furi_assert(usb_uart); furi_assert(cfg); + usb_uart->cfg_lock = api_lock_alloc_locked(); memcpy(&(usb_uart->cfg_new), cfg, sizeof(UsbUartConfig)); furi_thread_flags_set(furi_thread_get_id(usb_uart->thread), WorkerEvtCfgChange); + api_lock_wait_unlock_and_free(usb_uart->cfg_lock); } void usb_uart_get_config(UsbUartBridge* usb_uart, UsbUartConfig* cfg) { diff --git a/applications/plugins/dap_link/dap_link.c b/applications/plugins/dap_link/dap_link.c index 5c30e85c5ba..eafb435e7b3 100644 --- a/applications/plugins/dap_link/dap_link.c +++ b/applications/plugins/dap_link/dap_link.c @@ -247,7 +247,6 @@ static int32_t dap_process(void* p) { // deinit usb furi_hal_usb_set_config(usb_config_prev, NULL); - dap_common_wait_for_deinit(); dap_common_usb_free_name(); dap_deinit_gpio(swd_pins_prev); return 0; diff --git a/applications/plugins/dap_link/usb/dap_v2_usb.c b/applications/plugins/dap_link/usb/dap_v2_usb.c index 0c303a3ba41..b42df2836b6 100644 --- a/applications/plugins/dap_link/usb/dap_v2_usb.c +++ b/applications/plugins/dap_link/usb/dap_v2_usb.c @@ -618,23 +618,12 @@ static void hid_init(usbd_device* dev, FuriHalUsbInterface* intf, void* ctx) { if(dap_state.semaphore_v2 == NULL) dap_state.semaphore_v2 = furi_semaphore_alloc(1, 1); if(dap_state.semaphore_cdc == NULL) dap_state.semaphore_cdc = furi_semaphore_alloc(1, 1); - usb_hid.dev_descr->idVendor = DAP_HID_VID; - usb_hid.dev_descr->idProduct = DAP_HID_PID; - usbd_reg_config(dev, hid_ep_config); usbd_reg_control(dev, hid_control); usbd_connect(dev, true); } -static bool deinit_flag = false; - -void dap_common_wait_for_deinit() { - while(!deinit_flag) { - furi_delay_ms(50); - } -} - static void hid_deinit(usbd_device* dev) { dap_state.usb_dev = NULL; @@ -647,12 +636,6 @@ static void hid_deinit(usbd_device* dev) { usbd_reg_config(dev, NULL); usbd_reg_control(dev, NULL); - - free(usb_hid.str_manuf_descr); - free(usb_hid.str_prod_descr); - - FURI_SW_MEMBARRIER(); - deinit_flag = true; } static void hid_on_wakeup(usbd_device* dev) { diff --git a/applications/plugins/dap_link/usb/dap_v2_usb.h b/applications/plugins/dap_link/usb/dap_v2_usb.h index 2a0e86056fd..3f1534ffd56 100644 --- a/applications/plugins/dap_link/usb/dap_v2_usb.h +++ b/applications/plugins/dap_link/usb/dap_v2_usb.h @@ -51,5 +51,3 @@ void dap_common_usb_set_state_callback(DapStateCallback callback); void dap_common_usb_alloc_name(const char* name); void dap_common_usb_free_name(); - -void dap_common_wait_for_deinit(); \ No newline at end of file diff --git a/applications/services/dialogs/dialogs.c b/applications/services/dialogs/dialogs.c index e2db4a0f834..3c658888965 100644 --- a/applications/services/dialogs/dialogs.c +++ b/applications/services/dialogs/dialogs.c @@ -1,6 +1,6 @@ #include "dialogs/dialogs_message.h" #include "dialogs_i.h" -#include "dialogs_api_lock.h" +#include #include "dialogs_module_file_browser.h" #include "dialogs_module_message.h" @@ -35,7 +35,7 @@ static void dialogs_app_process_message(DialogsApp* app, DialogsAppMessage* mess dialogs_app_process_module_message(&message->data->dialog); break; } - API_LOCK_UNLOCK(message->lock); + api_lock_unlock(message->lock); } int32_t dialogs_srv(void* p) { diff --git a/applications/services/dialogs/dialogs_api.c b/applications/services/dialogs/dialogs_api.c index ee959a33c41..c1de599930d 100644 --- a/applications/services/dialogs/dialogs_api.c +++ b/applications/services/dialogs/dialogs_api.c @@ -1,6 +1,6 @@ #include "dialogs/dialogs_message.h" #include "dialogs_i.h" -#include "dialogs_api_lock.h" +#include #include /****************** File browser ******************/ @@ -10,7 +10,7 @@ bool dialog_file_browser_show( FuriString* result_path, FuriString* path, const DialogsFileBrowserOptions* options) { - FuriApiLock lock = API_LOCK_INIT_LOCKED(); + FuriApiLock lock = api_lock_alloc_locked(); furi_check(lock != NULL); DialogsAppData data = { @@ -35,7 +35,7 @@ bool dialog_file_browser_show( furi_check( furi_message_queue_put(context->message_queue, &message, FuriWaitForever) == FuriStatusOk); - API_LOCK_WAIT_UNTIL_UNLOCK_AND_FREE(lock); + api_lock_wait_unlock_and_free(lock); return return_data.bool_value; } @@ -43,7 +43,7 @@ bool dialog_file_browser_show( /****************** Message ******************/ DialogMessageButton dialog_message_show(DialogsApp* context, const DialogMessage* dialog_message) { - FuriApiLock lock = API_LOCK_INIT_LOCKED(); + FuriApiLock lock = api_lock_alloc_locked(); furi_check(lock != NULL); DialogsAppData data = { @@ -61,7 +61,7 @@ DialogMessageButton dialog_message_show(DialogsApp* context, const DialogMessage furi_check( furi_message_queue_put(context->message_queue, &message, FuriWaitForever) == FuriStatusOk); - API_LOCK_WAIT_UNTIL_UNLOCK_AND_FREE(lock); + api_lock_wait_unlock_and_free(lock); return return_data.dialog_value; } diff --git a/applications/services/dialogs/dialogs_api_lock.h b/applications/services/dialogs/dialogs_api_lock.h deleted file mode 100644 index a165803f423..00000000000 --- a/applications/services/dialogs/dialogs_api_lock.h +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once - -typedef FuriEventFlag* FuriApiLock; - -#define API_LOCK_EVENT (1U << 0) - -#define API_LOCK_INIT_LOCKED() furi_event_flag_alloc(); - -#define API_LOCK_WAIT_UNTIL_UNLOCK(_lock) \ - furi_event_flag_wait(_lock, API_LOCK_EVENT, FuriFlagWaitAny, FuriWaitForever); - -#define API_LOCK_FREE(_lock) furi_event_flag_free(_lock); - -#define API_LOCK_UNLOCK(_lock) furi_event_flag_set(_lock, API_LOCK_EVENT); - -#define API_LOCK_WAIT_UNTIL_UNLOCK_AND_FREE(_lock) \ - API_LOCK_WAIT_UNTIL_UNLOCK(_lock); \ - API_LOCK_FREE(_lock); diff --git a/applications/services/dialogs/dialogs_message.h b/applications/services/dialogs/dialogs_message.h index 91e040ce43d..d01c5aa9c1d 100644 --- a/applications/services/dialogs/dialogs_message.h +++ b/applications/services/dialogs/dialogs_message.h @@ -1,7 +1,7 @@ #pragma once #include #include "dialogs_i.h" -#include "dialogs_api_lock.h" +#include #ifdef __cplusplus extern "C" { diff --git a/applications/services/dialogs/dialogs_module_file_browser.c b/applications/services/dialogs/dialogs_module_file_browser.c index 166fd111333..7e67d6c6006 100644 --- a/applications/services/dialogs/dialogs_module_file_browser.c +++ b/applications/services/dialogs/dialogs_module_file_browser.c @@ -1,5 +1,5 @@ #include "dialogs_i.h" -#include "dialogs_api_lock.h" +#include #include "gui/modules/file_browser.h" typedef struct { @@ -11,14 +11,14 @@ 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); + 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); + api_lock_unlock(file_browser_context->lock); } bool dialogs_app_process_module_file_browser(const DialogsAppMessageDataFileBrowser* data) { @@ -27,7 +27,7 @@ bool dialogs_app_process_module_file_browser(const DialogsAppMessageDataFileBrow DialogsAppFileBrowserContext* file_browser_context = malloc(sizeof(DialogsAppFileBrowserContext)); - file_browser_context->lock = API_LOCK_INIT_LOCKED(); + file_browser_context->lock = api_lock_alloc_locked(); ViewHolder* view_holder = view_holder_alloc(); view_holder_attach_to_gui(view_holder, gui); @@ -44,7 +44,7 @@ bool dialogs_app_process_module_file_browser(const DialogsAppMessageDataFileBrow 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); + api_lock_wait_unlock(file_browser_context->lock); ret = file_browser_context->result; @@ -52,7 +52,7 @@ bool dialogs_app_process_module_file_browser(const DialogsAppMessageDataFileBrow view_holder_free(view_holder); file_browser_stop(file_browser); file_browser_free(file_browser); - API_LOCK_FREE(file_browser_context->lock); + api_lock_free(file_browser_context->lock); free(file_browser_context); furi_record_close(RECORD_GUI); diff --git a/applications/services/dialogs/dialogs_module_message.c b/applications/services/dialogs/dialogs_module_message.c index 8d1c3ba7504..879ba9baab5 100644 --- a/applications/services/dialogs/dialogs_module_message.c +++ b/applications/services/dialogs/dialogs_module_message.c @@ -1,5 +1,5 @@ #include "dialogs_i.h" -#include "dialogs_api_lock.h" +#include #include typedef struct { @@ -30,7 +30,7 @@ static void dialogs_app_message_back_callback(void* context) { furi_assert(context); DialogsAppMessageContext* message_context = context; message_context->result = DialogMessageButtonBack; - API_LOCK_UNLOCK(message_context->lock); + api_lock_unlock(message_context->lock); } static void dialogs_app_message_callback(DialogExResult result, void* context) { @@ -49,7 +49,7 @@ static void dialogs_app_message_callback(DialogExResult result, void* context) { default: break; } - API_LOCK_UNLOCK(message_context->lock); + api_lock_unlock(message_context->lock); } DialogMessageButton dialogs_app_process_module_message(const DialogsAppMessageDataDialog* data) { @@ -57,7 +57,7 @@ DialogMessageButton dialogs_app_process_module_message(const DialogsAppMessageDa Gui* gui = furi_record_open(RECORD_GUI); const DialogMessage* message = data->message; DialogsAppMessageContext* message_context = malloc(sizeof(DialogsAppMessageContext)); - message_context->lock = API_LOCK_INIT_LOCKED(); + message_context->lock = api_lock_alloc_locked(); ViewHolder* view_holder = view_holder_alloc(); view_holder_attach_to_gui(view_holder, gui); @@ -87,14 +87,14 @@ DialogMessageButton dialogs_app_process_module_message(const DialogsAppMessageDa view_holder_set_view(view_holder, dialog_ex_get_view(dialog_ex)); view_holder_start(view_holder); - API_LOCK_WAIT_UNTIL_UNLOCK(message_context->lock); + api_lock_wait_unlock(message_context->lock); ret = message_context->result; view_holder_stop(view_holder); view_holder_free(view_holder); dialog_ex_free(dialog_ex); - API_LOCK_FREE(message_context->lock); + api_lock_free(message_context->lock); free(message_context); furi_record_close(RECORD_GUI); diff --git a/firmware/targets/f7/furi_hal/furi_hal_memory.c b/firmware/targets/f7/furi_hal/furi_hal_memory.c index 9cf2c3120d3..ec71e666024 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_memory.c +++ b/firmware/targets/f7/furi_hal/furi_hal_memory.c @@ -81,6 +81,10 @@ void furi_hal_memory_init() { } void* furi_hal_memory_alloc(size_t size) { + if(FURI_IS_IRQ_MODE()) { + furi_crash("memmgt in ISR"); + } + if(furi_hal_memory == NULL) { return NULL; } diff --git a/firmware/targets/f7/furi_hal/furi_hal_usb.c b/firmware/targets/f7/furi_hal/furi_hal_usb.c index 1eaeffd6ab0..e740155f56c 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_usb.c +++ b/firmware/targets/f7/furi_hal/furi_hal_usb.c @@ -4,6 +4,7 @@ #include #include #include +#include #include "usb.h" @@ -11,35 +12,67 @@ #define USB_RECONNECT_DELAY 500 +typedef enum { + UsbApiEventTypeSetConfig, + UsbApiEventTypeGetConfig, + UsbApiEventTypeLock, + UsbApiEventTypeUnlock, + UsbApiEventTypeIsLocked, + UsbApiEventTypeEnable, + UsbApiEventTypeDisable, + UsbApiEventTypeReinit, + UsbApiEventTypeSetStateCallback, +} UsbApiEventType; + +typedef struct { + FuriHalUsbStateCallback callback; + void* context; +} UsbApiEventDataStateCallback; + +typedef struct { + FuriHalUsbInterface* interface; + void* context; +} UsbApiEventDataInterface; + +typedef union { + UsbApiEventDataStateCallback state_callback; + UsbApiEventDataInterface interface; +} UsbApiEventData; + +typedef union { + bool bool_value; + void* void_value; +} UsbApiEventReturnData; + +typedef struct { + FuriApiLock lock; + UsbApiEventType type; + UsbApiEventData data; + UsbApiEventReturnData* return_data; +} UsbApiEventMessage; + typedef struct { FuriThread* thread; + FuriMessageQueue* queue; bool enabled; bool connected; bool mode_lock; - FuriHalUsbInterface* if_cur; - FuriHalUsbInterface* if_next; - void* if_ctx; + bool request_pending; + FuriHalUsbInterface* interface; + void* interface_context; FuriHalUsbStateCallback callback; - void* cb_ctx; + void* callback_context; } UsbSrv; typedef enum { - EventModeChange = (1 << 0), - EventEnable = (1 << 1), - EventDisable = (1 << 2), - EventReinit = (1 << 3), - - EventReset = (1 << 4), - EventRequest = (1 << 5), - - EventModeChangeStart = (1 << 6), + UsbEventReset = (1 << 0), + UsbEventRequest = (1 << 1), + UsbEventMessage = (1 << 2), } UsbEvent; -#define USB_SRV_ALL_EVENTS \ - (EventModeChange | EventEnable | EventDisable | EventReinit | EventReset | EventRequest | \ - EventModeChangeStart) +#define USB_SRV_ALL_EVENTS (UsbEventReset | UsbEventRequest | UsbEventMessage) -static UsbSrv usb; +PLACE_IN_SECTION("MB_MEM2") static UsbSrv usb = {0}; static const struct usb_string_descriptor dev_lang_desc = USB_ARRAY_DESC(USB_LANGID_ENG_US); @@ -74,12 +107,13 @@ void furi_hal_usb_init(void) { // Reset callback will be enabled after first mode change to avoid getting false reset events usb.enabled = false; - usb.if_cur = NULL; + usb.interface = NULL; NVIC_SetPriority(USB_LP_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 5, 0)); NVIC_SetPriority(USB_HP_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 15, 0)); NVIC_EnableIRQ(USB_LP_IRQn); NVIC_EnableIRQ(USB_HP_IRQn); + usb.queue = furi_message_queue_alloc(1, sizeof(UsbApiEventMessage)); usb.thread = furi_thread_alloc_ex("UsbDriver", 1024, furi_hal_usb_thread, NULL); furi_thread_mark_as_service(usb.thread); furi_thread_start(usb.thread); @@ -87,53 +121,119 @@ void furi_hal_usb_init(void) { FURI_LOG_I(TAG, "Init OK"); } -bool furi_hal_usb_set_config(FuriHalUsbInterface* new_if, void* ctx) { - if(usb.mode_lock) { - return false; - } +static void furi_hal_usb_send_message(UsbApiEventMessage* message) { + furi_message_queue_put(usb.queue, message, FuriWaitForever); + furi_thread_flags_set(furi_thread_get_id(usb.thread), UsbEventMessage); + api_lock_wait_unlock_and_free(message->lock); +} - usb.if_next = new_if; - usb.if_ctx = ctx; - if(usb.thread == NULL) { - // Service thread hasn't started yet, so just save interface mode - return true; - } - furi_assert(usb.thread); - furi_thread_flags_set(furi_thread_get_id(usb.thread), EventModeChange); - return true; +bool furi_hal_usb_set_config(FuriHalUsbInterface* new_if, void* ctx) { + UsbApiEventReturnData return_data = { + .bool_value = false, + }; + + UsbApiEventMessage msg = { + .lock = api_lock_alloc_locked(), + .type = UsbApiEventTypeSetConfig, + .data.interface = + { + .interface = new_if, + .context = ctx, + }, + .return_data = &return_data, + }; + + furi_hal_usb_send_message(&msg); + return return_data.bool_value; } FuriHalUsbInterface* furi_hal_usb_get_config() { - return usb.if_cur; + UsbApiEventReturnData return_data = { + .void_value = NULL, + }; + + UsbApiEventMessage msg = { + .lock = api_lock_alloc_locked(), + .type = UsbApiEventTypeGetConfig, + .return_data = &return_data, + }; + + furi_hal_usb_send_message(&msg); + return return_data.void_value; } void furi_hal_usb_lock() { - FURI_LOG_I(TAG, "Mode lock"); - usb.mode_lock = true; + UsbApiEventMessage msg = { + .lock = api_lock_alloc_locked(), + .type = UsbApiEventTypeLock, + }; + + furi_hal_usb_send_message(&msg); } void furi_hal_usb_unlock() { - FURI_LOG_I(TAG, "Mode unlock"); - usb.mode_lock = false; + UsbApiEventMessage msg = { + .lock = api_lock_alloc_locked(), + .type = UsbApiEventTypeUnlock, + }; + + furi_hal_usb_send_message(&msg); } bool furi_hal_usb_is_locked() { - return usb.mode_lock; + UsbApiEventReturnData return_data = { + .bool_value = false, + }; + + UsbApiEventMessage msg = { + .lock = api_lock_alloc_locked(), + .type = UsbApiEventTypeIsLocked, + .return_data = &return_data, + }; + + furi_hal_usb_send_message(&msg); + return return_data.bool_value; } void furi_hal_usb_disable() { - furi_assert(usb.thread); - furi_thread_flags_set(furi_thread_get_id(usb.thread), EventDisable); + UsbApiEventMessage msg = { + .lock = api_lock_alloc_locked(), + .type = UsbApiEventTypeDisable, + }; + + furi_hal_usb_send_message(&msg); } void furi_hal_usb_enable() { - furi_assert(usb.thread); - furi_thread_flags_set(furi_thread_get_id(usb.thread), EventEnable); + UsbApiEventMessage msg = { + .lock = api_lock_alloc_locked(), + .type = UsbApiEventTypeEnable, + }; + + furi_hal_usb_send_message(&msg); } void furi_hal_usb_reinit() { - furi_assert(usb.thread); - furi_thread_flags_set(furi_thread_get_id(usb.thread), EventReinit); + UsbApiEventMessage msg = { + .lock = api_lock_alloc_locked(), + .type = UsbApiEventTypeReinit, + }; + + furi_hal_usb_send_message(&msg); +} + +void furi_hal_usb_set_state_callback(FuriHalUsbStateCallback cb, void* ctx) { + UsbApiEventMessage msg = { + .lock = api_lock_alloc_locked(), + .type = UsbApiEventTypeSetStateCallback, + .data.state_callback = + { + .callback = cb, + .context = ctx, + }, + }; + + furi_hal_usb_send_message(&msg); } /* Get device / configuration descriptors */ @@ -142,29 +242,29 @@ static usbd_respond usb_descriptor_get(usbd_ctlreq* req, void** address, uint16_ const uint8_t dnumber = req->wValue & 0xFF; const void* desc; uint16_t len = 0; - if(usb.if_cur == NULL) return usbd_fail; + if(usb.interface == NULL) return usbd_fail; switch(dtype) { case USB_DTYPE_DEVICE: - furi_thread_flags_set(furi_thread_get_id(usb.thread), EventRequest); + furi_thread_flags_set(furi_thread_get_id(usb.thread), UsbEventRequest); if(usb.callback != NULL) { - usb.callback(FuriHalUsbStateEventDescriptorRequest, usb.cb_ctx); + usb.callback(FuriHalUsbStateEventDescriptorRequest, usb.callback_context); } - desc = usb.if_cur->dev_descr; + desc = usb.interface->dev_descr; break; case USB_DTYPE_CONFIGURATION: - desc = usb.if_cur->cfg_descr; - len = ((struct usb_string_descriptor*)(usb.if_cur->cfg_descr))->wString[0]; + desc = usb.interface->cfg_descr; + len = ((struct usb_string_descriptor*)(usb.interface->cfg_descr))->wString[0]; break; case USB_DTYPE_STRING: if(dnumber == UsbDevLang) { desc = &dev_lang_desc; - } else if((dnumber == UsbDevManuf) && (usb.if_cur->str_manuf_descr != NULL)) { - desc = usb.if_cur->str_manuf_descr; - } else if((dnumber == UsbDevProduct) && (usb.if_cur->str_prod_descr != NULL)) { - desc = usb.if_cur->str_prod_descr; - } else if((dnumber == UsbDevSerial) && (usb.if_cur->str_serial_descr != NULL)) { - desc = usb.if_cur->str_serial_descr; + } else if((dnumber == UsbDevManuf) && (usb.interface->str_manuf_descr != NULL)) { + desc = usb.interface->str_manuf_descr; + } else if((dnumber == UsbDevProduct) && (usb.interface->str_prod_descr != NULL)) { + desc = usb.interface->str_prod_descr; + } else if((dnumber == UsbDevSerial) && (usb.interface->str_serial_descr != NULL)) { + desc = usb.interface->str_serial_descr; } else return usbd_fail; break; @@ -181,18 +281,13 @@ static usbd_respond usb_descriptor_get(usbd_ctlreq* req, void** address, uint16_ return usbd_ack; } -void furi_hal_usb_set_state_callback(FuriHalUsbStateCallback cb, void* ctx) { - usb.callback = cb; - usb.cb_ctx = ctx; -} - static void reset_evt(usbd_device* dev, uint8_t event, uint8_t ep) { UNUSED(dev); UNUSED(event); UNUSED(ep); - furi_thread_flags_set(furi_thread_get_id(usb.thread), EventReset); + furi_thread_flags_set(furi_thread_get_id(usb.thread), UsbEventReset); if(usb.callback != NULL) { - usb.callback(FuriHalUsbStateEventReset, usb.cb_ctx); + usb.callback(FuriHalUsbStateEventReset, usb.callback_context); } } @@ -200,14 +295,14 @@ static void susp_evt(usbd_device* dev, uint8_t event, uint8_t ep) { UNUSED(dev); UNUSED(event); UNUSED(ep); - if((usb.if_cur != NULL) && (usb.connected == true)) { + if((usb.interface != NULL) && (usb.connected == true)) { usb.connected = false; - usb.if_cur->suspend(&udev); + usb.interface->suspend(&udev); furi_hal_power_insomnia_exit(); } if(usb.callback != NULL) { - usb.callback(FuriHalUsbStateEventSuspend, usb.cb_ctx); + usb.callback(FuriHalUsbStateEventSuspend, usb.callback_context); } } @@ -215,101 +310,161 @@ static void wkup_evt(usbd_device* dev, uint8_t event, uint8_t ep) { UNUSED(dev); UNUSED(event); UNUSED(ep); - if((usb.if_cur != NULL) && (usb.connected == false)) { + if((usb.interface != NULL) && (usb.connected == false)) { usb.connected = true; - usb.if_cur->wakeup(&udev); + usb.interface->wakeup(&udev); furi_hal_power_insomnia_enter(); } if(usb.callback != NULL) { - usb.callback(FuriHalUsbStateEventWakeup, usb.cb_ctx); + usb.callback(FuriHalUsbStateEventWakeup, usb.callback_context); + } +} + +static void usb_process_mode_start(FuriHalUsbInterface* interface, void* context) { + if(usb.interface != NULL) { + usb.interface->deinit(&udev); + } + + __disable_irq(); + usb.interface = interface; + usb.interface_context = context; + __enable_irq(); + + if(interface != NULL) { + interface->init(&udev, interface, context); + usbd_reg_event(&udev, usbd_evt_reset, reset_evt); + FURI_LOG_I(TAG, "USB Mode change done"); + usb.enabled = true; } } +static void usb_process_mode_change(FuriHalUsbInterface* interface, void* context) { + if(interface != usb.interface) { + if(usb.enabled) { + // Disable current interface + susp_evt(&udev, 0, 0); + usbd_connect(&udev, false); + usb.enabled = false; + furi_delay_ms(USB_RECONNECT_DELAY); + } + usb_process_mode_start(interface, context); + } +} + +static void usb_process_mode_reinit() { + // Temporary disable callback to avoid getting false reset events + usbd_reg_event(&udev, usbd_evt_reset, NULL); + FURI_LOG_I(TAG, "USB Reinit"); + susp_evt(&udev, 0, 0); + usbd_connect(&udev, false); + usb.enabled = false; + + usbd_enable(&udev, false); + usbd_enable(&udev, true); + + furi_delay_ms(USB_RECONNECT_DELAY); + usb_process_mode_start(usb.interface, usb.interface_context); +} + +static bool usb_process_set_config(FuriHalUsbInterface* interface, void* context) { + if(usb.mode_lock) { + return false; + } else { + usb_process_mode_change(interface, context); + return true; + } +} + +static void usb_process_enable(bool enable) { + if(enable) { + if((!usb.enabled) && (usb.interface != NULL)) { + usbd_connect(&udev, true); + usb.enabled = true; + FURI_LOG_I(TAG, "USB Enable"); + } + } else { + if(usb.enabled) { + susp_evt(&udev, 0, 0); + usbd_connect(&udev, false); + usb.enabled = false; + usb.request_pending = false; + FURI_LOG_I(TAG, "USB Disable"); + } + } +} + +static void usb_process_message(UsbApiEventMessage* message) { + switch(message->type) { + case UsbApiEventTypeSetConfig: + message->return_data->bool_value = usb_process_set_config( + message->data.interface.interface, message->data.interface.context); + break; + case UsbApiEventTypeGetConfig: + message->return_data->void_value = usb.interface; + break; + case UsbApiEventTypeLock: + FURI_LOG_I(TAG, "Mode lock"); + usb.mode_lock = true; + break; + case UsbApiEventTypeUnlock: + FURI_LOG_I(TAG, "Mode unlock"); + usb.mode_lock = false; + break; + case UsbApiEventTypeIsLocked: + message->return_data->bool_value = usb.mode_lock; + break; + case UsbApiEventTypeDisable: + usb_process_enable(false); + break; + case UsbApiEventTypeEnable: + usb_process_enable(true); + break; + case UsbApiEventTypeReinit: + usb_process_mode_reinit(); + break; + case UsbApiEventTypeSetStateCallback: + usb.callback = message->data.state_callback.callback; + usb.callback_context = message->data.state_callback.context; + break; + } + + api_lock_unlock(message->lock); +} + static int32_t furi_hal_usb_thread(void* context) { UNUSED(context); - bool usb_request_pending = false; uint8_t usb_wait_time = 0; - FuriHalUsbInterface* if_new = NULL; - FuriHalUsbInterface* if_ctx_new = NULL; - if(usb.if_next != NULL) { - furi_thread_flags_set(furi_thread_get_id(usb.thread), EventModeChange); + if(furi_message_queue_get_count(usb.queue) > 0) { + furi_thread_flags_set(furi_thread_get_id(usb.thread), UsbEventMessage); } while(true) { uint32_t flags = furi_thread_flags_wait(USB_SRV_ALL_EVENTS, FuriFlagWaitAny, 500); - if((flags & FuriFlagError) == 0) { - if(flags & EventModeChange) { - if(usb.if_next != usb.if_cur) { - if_new = usb.if_next; - if_ctx_new = usb.if_ctx; - if(usb.enabled) { // Disable current interface - susp_evt(&udev, 0, 0); - usbd_connect(&udev, false); - usb.enabled = false; - furi_delay_ms(USB_RECONNECT_DELAY); - } - flags |= EventModeChangeStart; - } - } - if(flags & EventReinit) { - // Temporary disable callback to avoid getting false reset events - usbd_reg_event(&udev, usbd_evt_reset, NULL); - FURI_LOG_I(TAG, "USB Reinit"); - susp_evt(&udev, 0, 0); - usbd_connect(&udev, false); - usb.enabled = false; - - usbd_enable(&udev, false); - usbd_enable(&udev, true); - - if_new = usb.if_cur; - furi_delay_ms(USB_RECONNECT_DELAY); - flags |= EventModeChangeStart; - } - if(flags & EventModeChangeStart) { // Second stage of mode change process - if(usb.if_cur != NULL) { - usb.if_cur->deinit(&udev); - } - if(if_new != NULL) { - if_new->init(&udev, if_new, if_ctx_new); - usbd_reg_event(&udev, usbd_evt_reset, reset_evt); - FURI_LOG_I(TAG, "USB Mode change done"); - usb.enabled = true; - } - usb.if_cur = if_new; - } - if(flags & EventEnable) { - if((!usb.enabled) && (usb.if_cur != NULL)) { - usbd_connect(&udev, true); - usb.enabled = true; - FURI_LOG_I(TAG, "USB Enable"); - } - } - if(flags & EventDisable) { - if(usb.enabled) { - susp_evt(&udev, 0, 0); - usbd_connect(&udev, false); - usb.enabled = false; - usb_request_pending = false; - FURI_LOG_I(TAG, "USB Disable"); - } + + { + UsbApiEventMessage message; + if(furi_message_queue_get(usb.queue, &message, 0) == FuriStatusOk) { + usb_process_message(&message); } - if(flags & EventReset) { + } + + if((flags & FuriFlagError) == 0) { + if(flags & UsbEventReset) { if(usb.enabled) { - usb_request_pending = true; + usb.request_pending = true; usb_wait_time = 0; } } - if(flags & EventRequest) { - usb_request_pending = false; + if(flags & UsbEventRequest) { + usb.request_pending = false; } - } else if(usb_request_pending) { + } else if(usb.request_pending) { usb_wait_time++; if(usb_wait_time > 4) { - furi_hal_usb_reinit(); - usb_request_pending = false; + usb_process_mode_reinit(); + usb.request_pending = false; } } } diff --git a/furi/core/memmgr_heap.c b/furi/core/memmgr_heap.c index ac51b4a204b..01153fe5718 100644 --- a/furi/core/memmgr_heap.c +++ b/furi/core/memmgr_heap.c @@ -340,6 +340,10 @@ void* pvPortMalloc(size_t xWantedSize) { void* pvReturn = NULL; size_t to_wipe = xWantedSize; + if(FURI_IS_IRQ_MODE()) { + furi_crash("memmgt in ISR"); + } + #ifdef HEAP_PRINT_DEBUG BlockLink_t* print_heap_block = NULL; #endif @@ -486,6 +490,10 @@ void vPortFree(void* pv) { uint8_t* puc = (uint8_t*)pv; BlockLink_t* pxLink; + if(FURI_IS_IRQ_MODE()) { + furi_crash("memmgt in ISR"); + } + if(pv != NULL) { /* The memory being freed will have an BlockLink_t structure immediately before it. */ diff --git a/lib/toolbox/api_lock.h b/lib/toolbox/api_lock.h new file mode 100644 index 00000000000..5902a4922ab --- /dev/null +++ b/lib/toolbox/api_lock.h @@ -0,0 +1,43 @@ +#pragma once +#include + +/* +Testing 10000 api calls + +No lock + Time diff: 445269.218750 us + Time per call: 44.526920 us + +furi_thread_flags + Time diff: 430279.875000 us // lol wtf? smaller than no lock? + Time per call: 43.027988 us // I tested it many times, it's always smaller + +FuriEventFlag + Time diff: 831523.625000 us + Time per call: 83.152359 us + +FuriSemaphore + Time diff: 999807.125000 us + Time per call: 99.980713 us + +FuriMutex + Time diff: 1071417.500000 us + Time per call: 107.141747 us +*/ + +typedef FuriEventFlag* FuriApiLock; + +#define API_LOCK_EVENT (1U << 0) + +#define api_lock_alloc_locked() furi_event_flag_alloc() + +#define api_lock_wait_unlock(_lock) \ + furi_event_flag_wait(_lock, API_LOCK_EVENT, FuriFlagWaitAny, FuriWaitForever) + +#define api_lock_free(_lock) furi_event_flag_free(_lock) + +#define api_lock_unlock(_lock) furi_event_flag_set(_lock, API_LOCK_EVENT) + +#define api_lock_wait_unlock_and_free(_lock) \ + api_lock_wait_unlock(_lock); \ + api_lock_free(_lock); From b70395eba9161c26807494c6db8f1ca22cf77ee9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0=20=D0=A2=D0=B8=D0=BC?= =?UTF-8?q?=D0=BE=D1=84=D0=B5=D0=B5=D0=B2?= Date: Tue, 29 Nov 2022 16:10:02 +0300 Subject: [PATCH 250/824] Allow "Detect reader" for unsaved card (#2045) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Allow "Detect reader" for unsaved card * Add missed detect reader deed Co-authored-by: あく --- .../nfc/scenes/nfc_scene_mf_classic_menu.c | 20 +++++++++++++------ .../main/nfc/scenes/nfc_scene_saved_menu.c | 1 + 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_menu.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_menu.c index a5bb10ddf86..5fbdabe3021 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_menu.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_menu.c @@ -4,6 +4,7 @@ enum SubmenuIndex { SubmenuIndexSave, SubmenuIndexEmulate, + SubmenuIndexDetectReader, SubmenuIndexInfo, }; @@ -21,6 +22,14 @@ void nfc_scene_mf_classic_menu_on_enter(void* context) { submenu, "Save", SubmenuIndexSave, nfc_scene_mf_classic_menu_submenu_callback, nfc); submenu_add_item( submenu, "Emulate", SubmenuIndexEmulate, nfc_scene_mf_classic_menu_submenu_callback, nfc); + if(!mf_classic_is_card_read(&nfc->dev->dev_data.mf_classic_data)) { + submenu_add_item( + submenu, + "Detect reader", + SubmenuIndexDetectReader, + nfc_scene_mf_classic_menu_submenu_callback, + nfc); + } submenu_add_item( submenu, "Info", SubmenuIndexInfo, nfc_scene_mf_classic_menu_submenu_callback, nfc); @@ -35,17 +44,14 @@ bool nfc_scene_mf_classic_menu_on_event(void* context, SceneManagerEvent event) bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { + scene_manager_set_scene_state(nfc->scene_manager, NfcSceneMfClassicMenu, event.event); if(event.event == SubmenuIndexSave) { - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneMfClassicMenu, 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, NfcSceneMfClassicMenu, SubmenuIndexEmulate); scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicEmulate); if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSetType)) { DOLPHIN_DEED(DolphinDeedNfcAddEmulate); @@ -53,9 +59,11 @@ bool nfc_scene_mf_classic_menu_on_event(void* context, SceneManagerEvent event) DOLPHIN_DEED(DolphinDeedNfcEmulate); } consumed = true; + } else if(event.event == SubmenuIndexDetectReader) { + scene_manager_next_scene(nfc->scene_manager, NfcSceneDetectReader); + DOLPHIN_DEED(DolphinDeedNfcDetectReader); + consumed = true; } else if(event.event == SubmenuIndexInfo) { - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneMfClassicMenu, SubmenuIndexInfo); scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcDataInfo); consumed = true; } diff --git a/applications/main/nfc/scenes/nfc_scene_saved_menu.c b/applications/main/nfc/scenes/nfc_scene_saved_menu.c index e0839d66eec..04c686fbe85 100644 --- a/applications/main/nfc/scenes/nfc_scene_saved_menu.c +++ b/applications/main/nfc/scenes/nfc_scene_saved_menu.c @@ -123,6 +123,7 @@ bool nfc_scene_saved_menu_on_event(void* context, SceneManagerEvent event) { consumed = true; } else if(event.event == SubmenuIndexDetectReader) { scene_manager_next_scene(nfc->scene_manager, NfcSceneDetectReader); + DOLPHIN_DEED(DolphinDeedNfcDetectReader); consumed = true; } else if(event.event == SubmenuIndexWrite) { scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicWrite); From 0ab14c37c96083d7818640d2a4c24a02be4fc0cf Mon Sep 17 00:00:00 2001 From: Danil Kalashnikov Date: Tue, 29 Nov 2022 20:31:32 +0700 Subject: [PATCH 251/824] fbt: add missing Force flag when moving toolchain from temp path (#2044) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- scripts/toolchain/windows-toolchain-download.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/toolchain/windows-toolchain-download.ps1 b/scripts/toolchain/windows-toolchain-download.ps1 index c96bb119c88..05ea4be2cf3 100644 --- a/scripts/toolchain/windows-toolchain-download.ps1 +++ b/scripts/toolchain/windows-toolchain-download.ps1 @@ -36,11 +36,11 @@ Add-Type -Assembly "System.IO.Compression.Filesystem" # Expand-Archive -LiteralPath "$toolchain_zip_temp_path" -DestinationPath "$download_dir" Write-Host -NoNewline "moving.." -Move-Item -LiteralPath "$toolchain_dist_temp_path" -Destination "$toolchain_target_path" +Move-Item -LiteralPath "$toolchain_dist_temp_path" -Destination "$toolchain_target_path" -Force Write-Host "done!" Write-Host -NoNewline "Cleaning up temporary files.." Remove-Item -LiteralPath "$toolchain_zip_temp_path" -Force Write-Host "done!" -# dasdasd \ No newline at end of file +# dasdasd From 2a6a3a1bf7ba1ecb42b8cbfc1b1856a54f2878b7 Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Wed, 30 Nov 2022 15:41:23 +0400 Subject: [PATCH 252/824] [FL-2955], [FL-2953] SubGhz: fix RAW "Send never ends" (#1979) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * SubGhz: fix RAW "Send never ends" * SubGhz: delete comments * SubGhz: RAW file parsing speed increase * SubGhz: fix level_duration_is_wait * SubGhz: modification furi_hal_subghz_async_tx_refill * SubGhz: furi_hal_subghz_stop_async_rx * SubGhz: hal unit test and better async tx yield handling * FuriHal: proper async tx end in subghz, vibro on power off * FuriHal: variable naming in subghz * SubGhz,FuriHal: extreme timings in subghz hal unit tests, remove memset in async tx buffer fill routine * FuriHal: small refinements in subghz Co-authored-by: あく --- .../debug/unit_tests/subghz/subghz_test.c | 145 ++++++++++++++++++ firmware/targets/f7/furi_hal/furi_hal_power.c | 5 +- .../targets/f7/furi_hal/furi_hal_subghz.c | 77 +++++----- .../furi_hal_include/furi_hal_subghz.h | 5 + lib/subghz/subghz_file_encoder_worker.c | 16 +- 5 files changed, 204 insertions(+), 44 deletions(-) diff --git a/applications/debug/unit_tests/subghz/subghz_test.c b/applications/debug/unit_tests/subghz/subghz_test.c index 3052448b758..fe834c606cc 100644 --- a/applications/debug/unit_tests/subghz/subghz_test.c +++ b/applications/debug/unit_tests/subghz/subghz_test.c @@ -209,6 +209,149 @@ MU_TEST(subghz_keystore_test) { "Test keystore error"); } +typedef enum { + SubGhzHalAsyncTxTestTypeNormal, + SubGhzHalAsyncTxTestTypeInvalidStart, + SubGhzHalAsyncTxTestTypeInvalidMid, + SubGhzHalAsyncTxTestTypeInvalidEnd, + SubGhzHalAsyncTxTestTypeResetStart, + SubGhzHalAsyncTxTestTypeResetMid, + SubGhzHalAsyncTxTestTypeResetEnd, +} SubGhzHalAsyncTxTestType; + +typedef struct { + SubGhzHalAsyncTxTestType type; + size_t pos; +} SubGhzHalAsyncTxTest; + +#define SUBGHZ_HAL_TEST_DURATION 1 + +static LevelDuration subghz_hal_async_tx_test_yield(void* context) { + SubGhzHalAsyncTxTest* test = context; + bool is_odd = test->pos % 2; + + if(test->type == SubGhzHalAsyncTxTestTypeNormal) { + if(test->pos < API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) { + test->pos++; + return level_duration_make(is_odd, SUBGHZ_HAL_TEST_DURATION); + } else if(test->pos == API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) { + test->pos++; + return level_duration_reset(); + } else { + furi_crash("Yield after reset"); + } + } else if(test->type == SubGhzHalAsyncTxTestTypeInvalidStart) { + if(test->pos == 0) { + test->pos++; + return level_duration_make(!is_odd, SUBGHZ_HAL_TEST_DURATION); + } else if(test->pos < API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) { + test->pos++; + return level_duration_make(is_odd, SUBGHZ_HAL_TEST_DURATION); + } else if(test->pos == API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) { + test->pos++; + return level_duration_reset(); + } else { + furi_crash("Yield after reset"); + } + } else if(test->type == SubGhzHalAsyncTxTestTypeInvalidMid) { + if(test->pos == API_HAL_SUBGHZ_ASYNC_TX_BUFFER_HALF / 2) { + test->pos++; + return level_duration_make(!is_odd, SUBGHZ_HAL_TEST_DURATION); + } else if(test->pos < API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) { + test->pos++; + return level_duration_make(is_odd, SUBGHZ_HAL_TEST_DURATION); + } else if(test->pos == API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) { + test->pos++; + return level_duration_reset(); + } else { + furi_crash("Yield after reset"); + } + } else if(test->type == SubGhzHalAsyncTxTestTypeInvalidEnd) { + if(test->pos == API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL - 1) { + test->pos++; + return level_duration_make(!is_odd, SUBGHZ_HAL_TEST_DURATION); + } else if(test->pos < API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) { + test->pos++; + return level_duration_make(is_odd, SUBGHZ_HAL_TEST_DURATION); + } else if(test->pos == API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) { + test->pos++; + return level_duration_reset(); + } else { + furi_crash("Yield after reset"); + } + } else if(test->type == SubGhzHalAsyncTxTestTypeResetStart) { + if(test->pos == 0) { + test->pos++; + return level_duration_reset(); + } else { + furi_crash("Yield after reset"); + } + } else if(test->type == SubGhzHalAsyncTxTestTypeResetMid) { + if(test->pos < API_HAL_SUBGHZ_ASYNC_TX_BUFFER_HALF / 2) { + test->pos++; + return level_duration_make(is_odd, SUBGHZ_HAL_TEST_DURATION); + } else if(test->pos == API_HAL_SUBGHZ_ASYNC_TX_BUFFER_HALF / 2) { + test->pos++; + return level_duration_reset(); + } else { + furi_crash("Yield after reset"); + } + } else if(test->type == SubGhzHalAsyncTxTestTypeResetEnd) { + if(test->pos < API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL - 1) { + test->pos++; + return level_duration_make(is_odd, SUBGHZ_HAL_TEST_DURATION); + } else if(test->pos == API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL - 1) { + test->pos++; + return level_duration_reset(); + } else { + furi_crash("Yield after reset"); + } + } else { + furi_crash("Programming error"); + } +} + +bool subghz_hal_async_tx_test_run(SubGhzHalAsyncTxTestType type) { + SubGhzHalAsyncTxTest test = {0}; + test.type = type; + furi_hal_subghz_reset(); + furi_hal_subghz_load_preset(FuriHalSubGhzPresetOok650Async); + furi_hal_subghz_set_frequency_and_path(433920000); + + furi_hal_subghz_start_async_tx(subghz_hal_async_tx_test_yield, &test); + while(!furi_hal_subghz_is_async_tx_complete()) { + furi_delay_ms(10); + } + furi_hal_subghz_stop_async_tx(); + furi_hal_subghz_sleep(); + + return true; +} + +MU_TEST(subghz_hal_async_tx_test) { + mu_assert( + subghz_hal_async_tx_test_run(SubGhzHalAsyncTxTestTypeNormal), + "Test furi_hal_async_tx normal"); + mu_assert( + subghz_hal_async_tx_test_run(SubGhzHalAsyncTxTestTypeInvalidStart), + "Test furi_hal_async_tx invalid start"); + mu_assert( + subghz_hal_async_tx_test_run(SubGhzHalAsyncTxTestTypeInvalidMid), + "Test furi_hal_async_tx invalid mid"); + mu_assert( + subghz_hal_async_tx_test_run(SubGhzHalAsyncTxTestTypeInvalidEnd), + "Test furi_hal_async_tx invalid end"); + mu_assert( + subghz_hal_async_tx_test_run(SubGhzHalAsyncTxTestTypeResetStart), + "Test furi_hal_async_tx reset start"); + mu_assert( + subghz_hal_async_tx_test_run(SubGhzHalAsyncTxTestTypeResetMid), + "Test furi_hal_async_tx reset mid"); + mu_assert( + subghz_hal_async_tx_test_run(SubGhzHalAsyncTxTestTypeResetEnd), + "Test furi_hal_async_tx reset end"); +} + //test decoders MU_TEST(subghz_decoder_came_atomo_test) { mu_assert( @@ -579,6 +722,8 @@ MU_TEST_SUITE(subghz) { subghz_test_init(); MU_RUN_TEST(subghz_keystore_test); + MU_RUN_TEST(subghz_hal_async_tx_test); + MU_RUN_TEST(subghz_decoder_came_atomo_test); MU_RUN_TEST(subghz_decoder_came_test); MU_RUN_TEST(subghz_decoder_came_twee_test); diff --git a/firmware/targets/f7/furi_hal/furi_hal_power.c b/firmware/targets/f7/furi_hal/furi_hal_power.c index 88e191e9e5b..ddb056906ba 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_power.c +++ b/firmware/targets/f7/furi_hal/furi_hal_power.c @@ -1,6 +1,7 @@ #include #include #include +#include #include #include @@ -308,11 +309,13 @@ void furi_hal_power_shutdown() { void furi_hal_power_off() { // Crutch: shutting down with ext 3V3 off is causing LSE to stop furi_hal_power_enable_external_3_3v(); - furi_delay_us(1000); + furi_hal_vibro_on(true); + furi_delay_us(50000); // Send poweroff to charger furi_hal_i2c_acquire(&furi_hal_i2c_handle_power); bq25896_poweroff(&furi_hal_i2c_handle_power); furi_hal_i2c_release(&furi_hal_i2c_handle_power); + furi_hal_vibro_on(false); } void furi_hal_power_reset() { diff --git a/firmware/targets/f7/furi_hal/furi_hal_subghz.c b/firmware/targets/f7/furi_hal/furi_hal_subghz.c index 5eeb4e9a571..726b2d7fa74 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_subghz.c +++ b/firmware/targets/f7/furi_hal/furi_hal_subghz.c @@ -488,13 +488,9 @@ void furi_hal_subghz_stop_async_rx() { furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeAnalog, GpioPullNo, GpioSpeedLow); } -#define API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL (256) -#define API_HAL_SUBGHZ_ASYNC_TX_BUFFER_HALF (API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL / 2) -#define API_HAL_SUBGHZ_ASYNC_TX_GUARD_TIME 333 - typedef struct { uint32_t* buffer; - bool flip_flop; + LevelDuration carry_ld; FuriHalSubGhzAsyncTxCallback callback; void* callback_context; uint64_t duty_high; @@ -504,37 +500,48 @@ typedef struct { static FuriHalSubGhzAsyncTx furi_hal_subghz_async_tx = {0}; static void furi_hal_subghz_async_tx_refill(uint32_t* buffer, size_t samples) { + furi_assert(furi_hal_subghz.state == SubGhzStateAsyncTx); while(samples > 0) { bool is_odd = samples % 2; - LevelDuration ld = - furi_hal_subghz_async_tx.callback(furi_hal_subghz_async_tx.callback_context); + LevelDuration ld; + if(level_duration_is_reset(furi_hal_subghz_async_tx.carry_ld)) { + ld = furi_hal_subghz_async_tx.callback(furi_hal_subghz_async_tx.callback_context); + } else { + ld = furi_hal_subghz_async_tx.carry_ld; + furi_hal_subghz_async_tx.carry_ld = level_duration_reset(); + } if(level_duration_is_wait(ld)) { - return; + *buffer = API_HAL_SUBGHZ_ASYNC_TX_GUARD_TIME; + buffer++; + samples--; } else if(level_duration_is_reset(ld)) { - // One more even sample required to end at low level - if(is_odd) { - *buffer = API_HAL_SUBGHZ_ASYNC_TX_GUARD_TIME; - buffer++; - samples--; - furi_hal_subghz_async_tx.duty_low += API_HAL_SUBGHZ_ASYNC_TX_GUARD_TIME; - } + *buffer = 0; + buffer++; + samples--; + LL_DMA_DisableIT_HT(DMA1, LL_DMA_CHANNEL_1); + LL_DMA_DisableIT_TC(DMA1, LL_DMA_CHANNEL_1); + LL_TIM_EnableIT_UPDATE(TIM2); break; } else { - // Inject guard time if level is incorrect bool level = level_duration_get_level(ld); - if(is_odd == level) { + + // Inject guard time if level is incorrect + if(is_odd != level) { *buffer = API_HAL_SUBGHZ_ASYNC_TX_GUARD_TIME; buffer++; samples--; - if(!level) { + if(is_odd) { furi_hal_subghz_async_tx.duty_high += API_HAL_SUBGHZ_ASYNC_TX_GUARD_TIME; } else { furi_hal_subghz_async_tx.duty_low += API_HAL_SUBGHZ_ASYNC_TX_GUARD_TIME; } - // This code must be invoked only once: when encoder starts with low level. - // Otherwise whole thing will crash. - furi_check(samples > 0); + + // Special case: prevent buffer overflow if sample is last + if(samples == 0) { + furi_hal_subghz_async_tx.carry_ld = ld; + break; + } } uint32_t duration = level_duration_get_duration(ld); @@ -543,22 +550,17 @@ static void furi_hal_subghz_async_tx_refill(uint32_t* buffer, size_t samples) { buffer++; samples--; - if(level) { + if(is_odd) { furi_hal_subghz_async_tx.duty_high += duration; } else { furi_hal_subghz_async_tx.duty_low += duration; } } } - - memset(buffer, 0, samples * sizeof(uint32_t)); } static void furi_hal_subghz_async_tx_dma_isr() { - furi_assert( - furi_hal_subghz.state == SubGhzStateAsyncTx || - furi_hal_subghz.state == SubGhzStateAsyncTxEnd || - furi_hal_subghz.state == SubGhzStateAsyncTxLast); + furi_assert(furi_hal_subghz.state == SubGhzStateAsyncTx); if(LL_DMA_IsActiveFlag_HT1(DMA1)) { LL_DMA_ClearFlag_HT1(DMA1); furi_hal_subghz_async_tx_refill( @@ -578,11 +580,14 @@ static void furi_hal_subghz_async_tx_timer_isr() { if(LL_TIM_GetAutoReload(TIM2) == 0) { if(furi_hal_subghz.state == SubGhzStateAsyncTx) { furi_hal_subghz.state = SubGhzStateAsyncTxLast; + LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_1); + } else if(furi_hal_subghz.state == SubGhzStateAsyncTxLast) { + furi_hal_subghz.state = SubGhzStateAsyncTxEnd; //forcibly pulls the pin to the ground so that there is no carrier furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullDown, GpioSpeedLow); - } else { - furi_hal_subghz.state = SubGhzStateAsyncTxEnd; LL_TIM_DisableCounter(TIM2); + } else { + furi_crash(NULL); } } } @@ -605,8 +610,6 @@ bool furi_hal_subghz_start_async_tx(FuriHalSubGhzAsyncTxCallback callback, void* furi_hal_subghz_async_tx.buffer = malloc(API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * sizeof(uint32_t)); - furi_hal_subghz_async_tx_refill( - furi_hal_subghz_async_tx.buffer, API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL); // Connect CC1101_GD0 to TIM2 as output furi_hal_gpio_init_ex( @@ -647,14 +650,16 @@ bool furi_hal_subghz_start_async_tx(FuriHalSubGhzAsyncTxCallback callback, void* TIM_OC_InitStruct.OCState = LL_TIM_OCSTATE_DISABLE; TIM_OC_InitStruct.OCNState = LL_TIM_OCSTATE_DISABLE; TIM_OC_InitStruct.CompareValue = 0; - TIM_OC_InitStruct.OCPolarity = LL_TIM_OCPOLARITY_HIGH; + TIM_OC_InitStruct.OCPolarity = LL_TIM_OCPOLARITY_LOW; LL_TIM_OC_Init(TIM2, LL_TIM_CHANNEL_CH2, &TIM_OC_InitStruct); LL_TIM_OC_DisableFast(TIM2, LL_TIM_CHANNEL_CH2); LL_TIM_DisableMasterSlaveMode(TIM2); furi_hal_interrupt_set_isr(FuriHalInterruptIdTIM2, furi_hal_subghz_async_tx_timer_isr, NULL); - LL_TIM_EnableIT_UPDATE(TIM2); + furi_hal_subghz_async_tx_refill( + furi_hal_subghz_async_tx.buffer, API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL); + LL_TIM_EnableDMAReq_UPDATE(TIM2); LL_TIM_CC_EnableChannel(TIM2, LL_TIM_CHANNEL_CH2); @@ -673,8 +678,8 @@ bool furi_hal_subghz_start_async_tx(FuriHalSubGhzAsyncTxCallback callback, void* &SUBGHZ_DEBUG_CC1101_PIN, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); const GpioPin* gpio = &SUBGHZ_DEBUG_CC1101_PIN; - subghz_debug_gpio_buff[0] = gpio->pin; - subghz_debug_gpio_buff[1] = (uint32_t)gpio->pin << GPIO_NUMBER; + subghz_debug_gpio_buff[0] = (uint32_t)gpio->pin << GPIO_NUMBER; + subghz_debug_gpio_buff[1] = gpio->pin; dma_config.MemoryOrM2MDstAddress = (uint32_t)subghz_debug_gpio_buff; dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (gpio->port->BSRR); diff --git a/firmware/targets/furi_hal_include/furi_hal_subghz.h b/firmware/targets/furi_hal_include/furi_hal_subghz.h index d610b01b7a7..1f99386c0c0 100644 --- a/firmware/targets/furi_hal_include/furi_hal_subghz.h +++ b/firmware/targets/furi_hal_include/furi_hal_subghz.h @@ -14,6 +14,11 @@ extern "C" { #endif +/** Low level buffer dimensions and guard times */ +#define API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL (256) +#define API_HAL_SUBGHZ_ASYNC_TX_BUFFER_HALF (API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL / 2) +#define API_HAL_SUBGHZ_ASYNC_TX_GUARD_TIME 999 + /** Radio Presets */ typedef enum { FuriHalSubGhzPresetIDLE, /**< default configuration */ diff --git a/lib/subghz/subghz_file_encoder_worker.c b/lib/subghz/subghz_file_encoder_worker.c index f987430d6d9..abc33188fff 100644 --- a/lib/subghz/subghz_file_encoder_worker.c +++ b/lib/subghz/subghz_file_encoder_worker.c @@ -18,6 +18,7 @@ struct SubGhzFileEncoderWorker { volatile bool worker_running; volatile bool worker_stoping; bool level; + bool is_storage_slow; FuriString* str_data; FuriString* file_path; @@ -86,7 +87,7 @@ LevelDuration subghz_file_encoder_worker_get_level_duration(void* context) { if(ret == sizeof(int32_t)) { LevelDuration level_duration = {.level = LEVEL_DURATION_RESET}; if(duration < 0) { - level_duration = level_duration_make(false, duration * -1); + level_duration = level_duration_make(false, -duration); } else if(duration > 0) { level_duration = level_duration_make(true, duration); } else if(duration == 0) { @@ -96,7 +97,7 @@ LevelDuration subghz_file_encoder_worker_get_level_duration(void* context) { } return level_duration; } else { - FURI_LOG_E(TAG, "Slow flash read"); + instance->is_storage_slow = true; return level_duration_wait(); } } @@ -110,6 +111,7 @@ static int32_t subghz_file_encoder_worker_thread(void* context) { SubGhzFileEncoderWorker* instance = context; FURI_LOG_I(TAG, "Worker start"); bool res = false; + instance->is_storage_slow = false; Stream* stream = flipper_format_get_raw_stream(instance->flipper_format); do { if(!flipper_format_file_open_existing( @@ -139,21 +141,21 @@ static int32_t subghz_file_encoder_worker_thread(void* context) { furi_string_trim(instance->str_data); if(!subghz_file_encoder_worker_data_parse( instance, furi_string_get_cstr(instance->str_data))) { - //to stop DMA correctly subghz_file_encoder_worker_add_level_duration(instance, LEVEL_DURATION_RESET); - subghz_file_encoder_worker_add_level_duration(instance, LEVEL_DURATION_RESET); - break; } } else { - subghz_file_encoder_worker_add_level_duration(instance, LEVEL_DURATION_RESET); subghz_file_encoder_worker_add_level_duration(instance, LEVEL_DURATION_RESET); break; } + } else { + furi_delay_ms(1); } - furi_delay_ms(5); } //waiting for the end of the transfer + if(instance->is_storage_slow) { + FURI_LOG_E(TAG, "Storage is slow"); + } FURI_LOG_I(TAG, "End read file"); while(!furi_hal_subghz_is_async_tx_complete() && instance->worker_running) { furi_delay_ms(5); From eb3a8734fb11ed557ce7e4dafc7b02b69e98508b Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Fri, 2 Dec 2022 04:28:46 +1000 Subject: [PATCH 253/824] Fix crash in iButton notifications routine (#2074) * iButton: send notifications less strictly * iButton: set notification callback earlier --- lib/one_wire/ibutton/ibutton_worker.c | 4 +++- lib/one_wire/ibutton/ibutton_worker_modes.c | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/one_wire/ibutton/ibutton_worker.c b/lib/one_wire/ibutton/ibutton_worker.c index 29126d84535..9d5ea38970d 100644 --- a/lib/one_wire/ibutton/ibutton_worker.c +++ b/lib/one_wire/ibutton/ibutton_worker.c @@ -131,7 +131,9 @@ void ibutton_worker_switch_mode(iButtonWorker* worker, iButtonWorkerMode mode) { void ibutton_worker_notify_emulate(iButtonWorker* worker) { iButtonMessage message = {.type = iButtonMessageNotifyEmulate}; - furi_check(furi_message_queue_put(worker->messages, &message, 0) == FuriStatusOk); + // we're running in an interrupt context, so we can't wait + // and we can drop message if queue is full, that's ok for that message + furi_message_queue_put(worker->messages, &message, 0); } void ibutton_worker_set_key_p(iButtonWorker* worker, iButtonKey* key) { diff --git a/lib/one_wire/ibutton/ibutton_worker_modes.c b/lib/one_wire/ibutton/ibutton_worker_modes.c index 691aea9ee17..b1e5904ca35 100644 --- a/lib/one_wire/ibutton/ibutton_worker_modes.c +++ b/lib/one_wire/ibutton/ibutton_worker_modes.c @@ -222,8 +222,8 @@ void ibutton_worker_emulate_dallas_start(iButtonWorker* worker) { memcpy(device_id, key_id, key_size); onewire_slave_attach(worker->slave, worker->device); - onewire_slave_start(worker->slave); onewire_slave_set_result_callback(worker->slave, onewire_slave_callback, worker); + onewire_slave_start(worker->slave); } void ibutton_worker_emulate_dallas_stop(iButtonWorker* worker) { From 7f67445c85814c5abafcfadd021fa76572968397 Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Thu, 1 Dec 2022 21:40:49 +0300 Subject: [PATCH 254/824] U2F HID descriptor fix (#2073) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- firmware/targets/f7/furi_hal/furi_hal_usb_u2f.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/firmware/targets/f7/furi_hal/furi_hal_usb_u2f.c b/firmware/targets/f7/furi_hal/furi_hal_usb_u2f.c index 60068f3b43c..c099aec8a7c 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_usb_u2f.c +++ b/firmware/targets/f7/furi_hal/furi_hal_usb_u2f.c @@ -34,13 +34,13 @@ static const uint8_t hid_u2f_report_desc[] = { HID_COLLECTION(HID_APPLICATION_COLLECTION), HID_USAGE(HID_FIDO_INPUT), HID_LOGICAL_MINIMUM(0x00), - HID_LOGICAL_MAXIMUM(0xFF), + HID_RI_LOGICAL_MAXIMUM(16, 0xFF), HID_REPORT_SIZE(8), HID_REPORT_COUNT(HID_U2F_PACKET_LEN), HID_INPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), HID_USAGE(HID_FIDO_OUTPUT), HID_LOGICAL_MINIMUM(0x00), - HID_LOGICAL_MAXIMUM(0xFF), + HID_RI_LOGICAL_MAXIMUM(16, 0xFF), HID_REPORT_SIZE(8), HID_REPORT_COUNT(HID_U2F_PACKET_LEN), HID_OUTPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), From 4070eeb1c95f65d70563a89a685499584eaca9d0 Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Thu, 1 Dec 2022 21:56:27 +0300 Subject: [PATCH 255/824] USB HID: fix key name #2065 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- firmware/targets/f7/api_symbols.csv | 1 + firmware/targets/furi_hal_include/furi_hal_usb_hid.h | 2 -- lib/libusb_stm32 | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index afc7c4e9337..0f00ca908fe 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -133,6 +133,7 @@ Header,+,lib/libusb_stm32/inc/hid_usage_simulation.h,, Header,+,lib/libusb_stm32/inc/hid_usage_sport.h,, Header,+,lib/libusb_stm32/inc/hid_usage_telephony.h,, Header,+,lib/libusb_stm32/inc/hid_usage_vr.h,, +Header,-,lib/libusb_stm32/inc/stm32_compat.h,, Header,+,lib/libusb_stm32/inc/usb.h,, Header,+,lib/libusb_stm32/inc/usb_cdc.h,, Header,+,lib/libusb_stm32/inc/usb_cdca.h,, diff --git a/firmware/targets/furi_hal_include/furi_hal_usb_hid.h b/firmware/targets/furi_hal_include/furi_hal_usb_hid.h index f4b40c89d5e..a9f094814c1 100644 --- a/firmware/targets/furi_hal_include/furi_hal_usb_hid.h +++ b/firmware/targets/furi_hal_include/furi_hal_usb_hid.h @@ -10,8 +10,6 @@ extern "C" { #endif #define HID_KEYBOARD_NONE 0x00 -// Remapping the colon key which is shift + ; to comma -#define HID_KEYBOARD_COMMA HID_KEYBOARD_COLON /** HID keyboard modifier keys */ enum HidKeyboardMods { diff --git a/lib/libusb_stm32 b/lib/libusb_stm32 index 6a88ec4d770..9168e2a31db 160000 --- a/lib/libusb_stm32 +++ b/lib/libusb_stm32 @@ -1 +1 @@ -Subproject commit 6a88ec4d7709ca8605b5ec3e609057c330ca2a70 +Subproject commit 9168e2a31db946326fb84016a74ea2ab5bf87f54 From 79fbaf2620aa38926d72b956dd390c1f6c044ba9 Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Thu, 1 Dec 2022 23:07:16 +0400 Subject: [PATCH 256/824] SubGhz: unit_test modification of text files to one frequency and modulation (#2066) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * SubGhz: unit_test modification of text files to one frequency and modulation * SubGhz: unit_tests raw file Co-authored-by: あく --- assets/unit_tests/subghz/holtek.sub | 2 +- assets/unit_tests/subghz/holtek_raw.sub | 2 +- assets/unit_tests/subghz/honeywell_wdb.sub | 4 ++-- assets/unit_tests/subghz/honeywell_wdb_raw.sub | 4 ++-- assets/unit_tests/subghz/hormann_hsm_raw.sub | 2 +- assets/unit_tests/subghz/kia_seed_raw.sub | 2 +- assets/unit_tests/subghz/linear.sub | 2 +- assets/unit_tests/subghz/linear_raw.sub | 2 +- assets/unit_tests/subghz/megacode.sub | 2 +- assets/unit_tests/subghz/megacode_raw.sub | 2 +- assets/unit_tests/subghz/nero_radio_raw.sub | 2 +- assets/unit_tests/subghz/nero_sketch_raw.sub | 2 +- assets/unit_tests/subghz/power_smart.sub | 2 +- assets/unit_tests/subghz/power_smart_raw.sub | 4 ++-- assets/unit_tests/subghz/scher_khan_magic_code.sub | 2 +- assets/unit_tests/subghz/security_pls_1_0.sub | 2 +- assets/unit_tests/subghz/security_pls_1_0_raw.sub | 2 +- assets/unit_tests/subghz/security_pls_2_0.sub | 2 +- assets/unit_tests/subghz/security_pls_2_0_raw.sub | 2 +- assets/unit_tests/subghz/somfy_keytis_raw.sub | 2 +- 20 files changed, 23 insertions(+), 23 deletions(-) diff --git a/assets/unit_tests/subghz/holtek.sub b/assets/unit_tests/subghz/holtek.sub index b77a759d2ee..8dbd3ea203a 100644 --- a/assets/unit_tests/subghz/holtek.sub +++ b/assets/unit_tests/subghz/holtek.sub @@ -1,6 +1,6 @@ Filetype: Flipper SubGhz Key File Version: 1 -Frequency: 418000000 +Frequency: 433920000 Preset: FuriHalSubGhzPresetOok650Async Protocol: Holtek Bit: 40 diff --git a/assets/unit_tests/subghz/holtek_raw.sub b/assets/unit_tests/subghz/holtek_raw.sub index 8eac0a398d1..d63c4a00a9f 100644 --- a/assets/unit_tests/subghz/holtek_raw.sub +++ b/assets/unit_tests/subghz/holtek_raw.sub @@ -1,6 +1,6 @@ Filetype: Flipper SubGhz RAW File Version: 1 -Frequency: 418000000 +Frequency: 433920000 Preset: FuriHalSubGhzPresetOok650Async Protocol: RAW RAW_Data: 2243 -98 331 -100 1129 -66 761 -100 1393 -100 165 -66 2883 -64 357 -66 4703 -68 927 -98 233 -134 461 -66 3855 -134 165 -98 1281 -100 2053 -66 3061 -98 331 -98 8981 -66 365 -66 631 -100 1027 -100 4521 -134 597 -66 3187 -66 2619 -100 3011 -98 1151 -66 953 -100 1423 -66 1755 -166 333 -98 1557 -66 761 -66 865 -66 4837 -132 357 -132 2419 -100 1023 -66 65 -66 2507 -66 131 -66 761 -66 997 -66 333 -100 2259 -68 431 -100 2523 -66 987 -100 363 -66 363 -66 1197 -68 1589 -164 951 -96 5351 -66 697 -100 163 -100 4683 -66 2265 -68 2051 -64 457 -64 3005 -132 1057 -66 2221 -100 1661 -98 695 -100 99 -66 861 -66 1957 -100 731 -132 1857 -100 3177 -98 1807 -98 463 -66 499 -134 1129 -100 3737 -100 1889 -66 263 -98 623 -66 2103 -98 3165 -66 131 -100 195 -66 691 -66 67 -132 531 -66 1857 -100 199 -68 97 -68 197 -68 697 -68 233 -100 3749 -134 1691 -68 3289 -66 3751 -68 65 -100 853 -66 531 -132 1299 -66 1585 -98 65 -98 1577 -66 785 -98 1151 -66 165 -68 397 -100 4255 -100 857 -100 1017 -66 1575 -130 1255 -234 1923 -66 199 -102 301 -66 231 -66 691 -64 227 -64 195 -66 1257 -100 2353 -100 235 -100 1163 -66 5423 -66 2049 -66 1807 -66 523 -198 693 -100 367 -100 597 -100 4013 -100 233 -166 365 -66 1827 -100 1491 -100 785 -64 885 -66 599 -134 2847 -100 667 -100 4943 -98 3319 -98 6729 -98 361 -96 391 -66 723 -132 503 -66 1583 -166 297 -234 2045 -66 1185 -134 661 -66 195 -66 291 -164 523 -98 1679 -134 233 -132 761 -394 855 -100 2003 -164 261 -66 229 -96 953 -66 3889 -66 929 -66 993 -68 3099 -132 1673 -66 1833 -100 563 -100 1131 -100 3219 -232 4411 -100 1095 -100 5315 -100 631 -198 461 -198 1907 -100 1743 -68 863 -132 4013 -64 295 -66 3883 -100 2707 -198 923 -100 2539 -166 629 -100 563 -100 3783 -68 893 -66 2987 -98 2357 -98 1665 -66 599 -66 1259 -232 165 -66 1361 -66 1645 -166 1543 -66 565 -66 401 -134 465 -100 831 -98 2405 -100 1055 -66 2109 -100 1161 -68 431 -100 265 -68 235 -66 463 -66 3453 -100 433 -66 2693 -132 263 -166 729 -134 763 -134 1327 -100 397 -234 795 -68 563 -66 1625 -98 267 -66 4835 -66 197 -66 589 -66 7575 -100 1959 -100 131 -68 297 -134 261 -98 433 -66 1427 -66 2421 -100 2925 -166 1921 -134 1645 -66 97 -132 5423 -100 2423 -98 1065 -66 1715 -132 963 -66 2403 -66 1117 -328 1981 -66 527 -100 427 -164 865 -66 2129 -232 165 -68 165 -66 131 -366 131 -100 2613 -450 diff --git a/assets/unit_tests/subghz/honeywell_wdb.sub b/assets/unit_tests/subghz/honeywell_wdb.sub index bce11cd0d63..0f2316d5e54 100644 --- a/assets/unit_tests/subghz/honeywell_wdb.sub +++ b/assets/unit_tests/subghz/honeywell_wdb.sub @@ -1,7 +1,7 @@ Filetype: Flipper SubGhz Key File Version: 1 -Frequency: 868350000 -Preset: FuriHalSubGhzPreset2FSKDev476Async +Frequency: 433920000 +Preset: FuriHalSubGhzPresetOok650Async Protocol: Honeywell Bit: 48 Key: 00 00 0E DB 70 20 00 01 diff --git a/assets/unit_tests/subghz/honeywell_wdb_raw.sub b/assets/unit_tests/subghz/honeywell_wdb_raw.sub index 0fbd6d23e43..a8bbaa8fb91 100644 --- a/assets/unit_tests/subghz/honeywell_wdb_raw.sub +++ b/assets/unit_tests/subghz/honeywell_wdb_raw.sub @@ -1,7 +1,7 @@ Filetype: Flipper SubGhz RAW File Version: 1 -Frequency: 868250000 -Preset: FuriHalSubGhzPreset2FSKDev476Async +Frequency: 433920000 +Preset: FuriHalSubGhzPresetOok650Async Protocol: RAW RAW_Data: 414 -1374 51 -108 79 -216 189 -584 53 -214 53 -400 215 -264 129 -322 77 -288 79 -54 107 -184 51 -446 53 -160 161 -305 263 -104 264 -134 53 -806 267 -80 187 -314 53 -158 83 -1172 243 -833 79 -188 345 -208 77 -182 225 -100 107 -106 187 -190 105 -432 176 -174 119 -328 79 -432 79 -102 99 -514 127 -218 79 -184 77 -52 101 -356 53 -615 243 -266 181 -102 212 -138 81 -486 205 -574 77 -256 155 -152 217 -78 75 -1010 393 -346 79 -488 107 -284 121 -72 215 -174 183 -54 257 -332 179 -52 79 -238 53 -636 161 -80 227 -950 346 -160 109 -564 295 -160 299 -160 435 -487 312 -52 105 -292 77 -246 137 -498 366 -104 255 -362 97 -210 53 -555 374 -206 129 -96 303 -280 179 -172 75 -102 99 -76 353 -228 131 -252 147 -130 79 -304 103 -82 139 -52 103 -183 77 -284 75 -454 55 -188 77 -222 161 -128 107 -136 187 -270 53 -371 184 -364 103 -284 129 -52 103 -321 81 -554 261 -1048 107 -106 243 -280 103 -478 215 -530 53 -108 53 -236 203 -180 51 -78 77 -338 81 -82 53 -231 75 -124 256 -232 227 -448 131 -340 131 -266 107 -346 51 -254 75 -134 210 -182 103 -280 127 -122 305 -310 255 -528 77 -513 79 -214 209 -102 53 -80 133 -1727 237 -78 79 -242 53 -296 133 -532 53 -513 53 -54 131 -190 53 -661 129 -218 107 -394 103 -554 157 -112 343 -314 103 -283 79 -304 135 -56 111 -272 189 -370 271 -270 105 -956 79 -184 77 -868 307 -156 129 -731 51 -200 161 -84 81 -326 51 -54 157 -168 369 -152 101 -188 81 -398 239 -132 215 -54 105 -182 317 -206 99 -198 97 -274 157 -271 121 -268 200 -330 499 -184 55 -434 133 -646 283 -152 255 -428 101 -350 199 -124 145 -304 308 -102 75 -386 107 -186 129 -534 101 -180 175 -100 151 -98 53 -78 133 -794 183 -150 83 -220 185 -280 83 -537 107 -308 55 -138 79 -714 79 -538 77 -106 133 -242 263 -236 75 -884 215 -136 81 -270 133 -624 105 -132 79 -848 291 -373 79 -244 55 -619 203 -202 77 -156 103 -176 99 -408 107 -318 77 -316 79 -500 55 -691 51 -340 129 -266 53 -486 103 -376 103 -185 51 -156 81 -106 77 -104 79 -502 208 -292 133 -432 105 -52 105 -80 51 -158 317 -372 181 -844 51 -270 107 -312 79 -302 83 -444 53 -640 77 -268 183 -138 85 -192 79 -158 131 -132 155 -220 81 -245 127 -386 185 -296 53 -608 77 -308 51 -822 105 -832 51 -850 155 -242 105 -422 79 -270 155 -76 77 -162 55 -414 RAW_Data: 109 -630 77 -190 53 -52 131 -860 53 -1300 83 -1122 99 -432 53 -724 81 -390 159 -106 81 -2607 79 -474 75 -694 151 -146 73 -104 53 -380 77 -278 77 -128 73 -148 73 -248 99 -314 75 -154 97 -2225 51 -952 183 -970 77 -1210 51 -326 103 -368 151 -252 121 -784 51 -108 187 -256 227 -242 105 -104 150 -128 77 -1287 173 -2513 77 -1144 105 -1268 111 -3964 101 -1348 81 -2718 81 -302 79 -1999 55 -1268 57 -7269 53 -424 83 -3736 105 -1552 51 -8460 57 -5224 101 -244 121 -4124 103 -10775 133 -1858 73 -20679 51 -5532 97 -1718 103 -3764 51 -6496 79 -792 53 -13269 81 -18787 57 -9140 83 -3302 53 -11039 55 -26162 79 -1713 103 -5860 53 -9589 55 -8389 81 -254 85 -16287 73 -8336 53 -6237 75 -828 55 -19389 73 -8028 79 -6284 83 -3460 77 -13504 75 -3198 75 -1280 51 -1948 51 -758 79 -22076 75 -1681 73 -21693 57 -8106 53 -838 51 -10074 51 -4760 55 -492 79 -6558 79 -22996 75 -13904 53 -5564 55 -16578 83 -25603 77 -410 75 -16694 81 -606 53 -2987 53 -3898 107 -3248 79 -5168 55 -754 81 -20662 53 -11066 51 -8624 79 -26384 79 -2214 81 -7442 79 -12488 53 -1656 71 -4508 55 -15680 57 -6669 51 -5410 71 -6411 53 -6082 79 -13772 53 -5945 55 -574 73 -11921 51 -3472 55 -2323 55 -10414 51 -16069 81 -2678 77 -10775 53 -2106 110 -11794 55 -17082 51 -6184 71 -8376 79 -7152 77 -2691 53 -6332 53 -2074 57 -6804 51 -166 53 -2254 53 -452 51 -6771 53 -42749 53 -6658 73 -2034 111 -3440 109 -626 53 -7291 85 -2546 53 -5287 51 -9902 53 -10040 53 -366 79 -13848 51 -3739 77 -47536 51 -354 77 -228 77 -712 53 -530 53 -162 55 -12640 71 -1708 51 -1034 77 -12891 79 -3334 51 -7644 79 -12676 81 -3036 53 -2038 53 -180 57 -818 55 -21459 71 -3009 51 -6572 53 -4015 105 -642 77 -23618 53 -4921 83 -3026 73 -3672 53 -12654 81 -8632 51 -9419 171 -19195 105 -13041 55 -21910 51 -1051 77 -10292 51 -12884 51 -6589 53 -8718 51 -2510 103 -15406 55 -6014 99 -966 73 -2725 53 -12715 51 -4228 55 -3192 57 -8672 51 -14740 51 -17032 75 -11111 1761 -182 399 -106 215 -270 355 -132 329 -150 177 -312 315 -170 169 -290 331 -162 173 -286 187 -312 163 -300 329 -164 311 -162 171 -316 289 -176 297 -194 311 -164 143 -324 167 -314 163 -310 161 -312 163 -300 175 -300 331 -162 173 -312 161 -312 163 -314 161 -298 189 -300 165 -312 161 -312 189 -276 173 -316 193 -310 163 -312 161 -310 163 -300 175 -296 193 -284 191 -284 187 -310 163 -300 175 -300 169 -320 177 -314 485 -464 177 -292 353 -134 diff --git a/assets/unit_tests/subghz/hormann_hsm_raw.sub b/assets/unit_tests/subghz/hormann_hsm_raw.sub index bf82d46cfe1..ac14f5819b5 100644 --- a/assets/unit_tests/subghz/hormann_hsm_raw.sub +++ b/assets/unit_tests/subghz/hormann_hsm_raw.sub @@ -1,6 +1,6 @@ Filetype: Flipper SubGhz RAW File Version: 1 -Frequency: 868350000 +Frequency: 433920000 Preset: FuriHalSubGhzPresetOok650Async Protocol: RAW RAW_Data: 1718, -32700, 32700, -32700, 11939, -494, 1047, -496, 1015, -516, 1041, -476, 1037, -510, 1035, -512, 1035, -484, 1047, -492, 1031, -504, 513, -1042, 499, -1020, 1041, -512, 511, -1004, 1035, -512, 507, -1042, 481, -1050, 1017, -522, 489, -1040, 1013, -514, 513, -1010, 543, -1008, 513, -1012, 1045, -522, 1003, -512, 1029, -518, 1009, -514, 1039, -476, 541, -1004, 509, -1038, 1035, -482, 539, -1008, 1033, -490, 539, -1018, 1009, -508, 1059, -482, 1041, -512, 1005, -508, 541, -1006, 513, -1008, 535, -1000, 1035, -522, 525, -1016, 515, -1006, 1021, -514, 1045, -478, 12441, -482, 1045, -512, 1005, -512, 1035, -514, 1037, -482, 1035, -520, 1031, -504, 1035, -486, 1041, -510, 501, -1018, 513, -1042, 1007, -512, 509, -1042, 1007, -506, 541, -1010, 507, -1016, 1031, -520, 513, -1010, 1021, -516, 515, -1046, 477, -1042, 513, -1012, 1051, -484, 1043, -512, 1027, -484, 1043, -514, 1007, -512, 509, -1040, 507, -1036, 1011, -526, 493, -1032, 1033, -486, 509, -1056, 1011, -514, 1007, -512, 1037, -514, 1001, -516, 507, -1034, 517, -1036, 493, -1018, 1041, -502, 525, -1016, 513, -1040, 1005, -512, 1033, -506, 12423, -478, 1039, -514, 1009, -516, 1033, -522, 1001, -522, 1039, -510, 1027, -484, 1041, -512, 1007, -510, 509, -1038, 507, -1034, 1037, -482, 507, -1042, 1037, -520, 487, -1014, 527, -1022, 1007, -512, 509, -1038, 1037, -512, 509, -1008, 507, -1044, 507, -1018, 1027, -514, 1005, -528, 1017, -514, 1041, -512, 1003, -512, 509, -1042, 507, -1010, 1035, -520, 507, -1018, 1033, -508, 511, -1038, 991, -516, 1041, -512, 1005, -512, 1035, -514, 507, -1040, 507, -1014, 519, -1014, 1041, -510, 499, -1018, 515, -1046, 1005, -514, 1011, -520, 12425, -480, 1035, -516, 1035, -492, 1031, -522, 1009, -508, 1021, -518, 1041, -512, 1007, -512, 1037, -512, 509, -1008, 519, -1016, 1029, -522, 513, -1008, 1025, -516, 515, -1044, 479, -1038, 1037, -482, 541, -1008, 1035, -520, 507, -1010, 503, -1026, 515, -1042, 1005, -512, 1037, -512, 1005, -520, 1015, -520, 1039, -510, 499, -1018, 515, -1042, 1007, -512, 507, -1040, 1011, -520, 507, -1016, 1031, -520, 1009, -540, 989, -518, 1043, -512, 511, -1006, 507, -1038, 505, -1036, 1001, -516, 509, -1050, 491, -1052, 1009, -500, 1021, -516, 12409, -524, 1015, -516, 1009, -510, 1037, -512, 1033, -506, 1039, -480, 1035, -524, 1019, -522, 1015, -500, 525, -1014, 515, -1040, 1005, -512, 509, -1042, 1011, -524, 493, -1018, 517, -1036, 1017, -514, 515, -1008, 1035, -514, 507, -1040, 505, -1034, 507, -1010, 1035, -492, 1031, -520, 1009, -540, 991, -516, 1043, -514, 511, -1010, 513, -1044, 1015, -492, 531, -1020, 1009, -528, 521, -1012, 1009, -510, 1037, -512, 1037, -482, 1033, -518, 505, -1032, 507, -1016, 537, -1000, 1025, -520, 513, -1034, 519, -1010, 1007, -512, 1039, -514, 12395, -510, 1037, -512, 1037, -482, 1045, -490, 1057, -488, 1045, -496, 1017, -516, 1041, -514, 1005, -512, 507, -1046, 481, -1044, 1035, -494, 505, -1048, 1011, -502, 527, -1018, 513, -1042, 1005, -516, 513, -1010, 1035, -520, 505, -1032, 507, -1022, 503, -1034, 1013, -504, 1023, -516, 1041, -512, 1005, -512, 1037, -514, 507, -1010, 519, -1050 diff --git a/assets/unit_tests/subghz/kia_seed_raw.sub b/assets/unit_tests/subghz/kia_seed_raw.sub index b3453536b84..c78e6d34aa0 100644 --- a/assets/unit_tests/subghz/kia_seed_raw.sub +++ b/assets/unit_tests/subghz/kia_seed_raw.sub @@ -1,7 +1,7 @@ Filetype: Flipper SubGhz RAW File Version: 1 Frequency: 433920000 -Preset: FuriHalSubGhzPreset2FSKDev476Async +Preset: FuriHalSubGhzPresetOok650Async Protocol: RAW RAW_Data: 51 -5136 1113 -104 127 -606 179 -126 325 -102 147 -200 205 -80 107 -190 81 -54 135 -378 181 -204 199 -104 121 -146 51 -230 75 -76 227 -102 415 -236 159 -54 159 -212 77 -312 73 -74 75 -128 155 -102 125 -358 125 -198 173 -608 75 -248 203 -558 53 -186 367 -180 151 -132 133 -138 135 -424 51 -240 53 -132 703 -80 79 -80 129 -294 77 -102 193 -270 77 -52 131 -180 51 -76 77 -76 51 -666 77 -104 101 -54 77 -164 107 -622 615 -294 133 -138 217 -180 75 -104 185 -80 107 -134 159 -398 181 -74 97 -198 73 -76 99 -102 127 -248 71 -128 211 -106 109 -104 101 -384 311 -398 51 -240 111 -84 81 -110 81 -668 105 -54 77 -158 79 -342 493 -78 153 -152 179 -122 223 -696 139 -590 81 -212 83 -322 129 -132 307 -346 129 -78 77 -232 77 -104 503 -52 187 -290 159 -108 159 -108 51 -136 53 -132 103 -566 177 -326 129 -176 177 -52 355 -308 129 -176 73 -104 151 -262 193 -168 111 -162 247 -52 131 -54 107 -84 83 -218 135 -402 107 -300 105 -220 81 -82 137 -82 163 -238 51 -192 293 -184 319 -454 81 -132 131 -132 79 -866 131 -212 163 -112 133 -106 211 -110 189 -214 51 -410 107 -454 131 -562 157 -52 367 -378 165 -110 81 -134 81 -82 75 -104 217 -638 81 -84 109 -160 81 -160 135 -54 53 -478 239 -78 209 -54 107 -52 155 -238 53 -184 105 -318 105 -370 233 -54 109 -246 217 -110 245 -110 209 -80 129 -514 79 -54 79 -314 155 -156 109 -292 79 -504 51 -644 157 -132 53 -208 53 -76 103 -76 77 -260 105 -82 133 -212 163 -136 53 -380 313 -178 193 -172 207 -82 79 -186 107 -136 53 -136 159 -156 97 -98 99 -254 179 -106 135 -214 307 -310 77 -76 123 -242 129 -52 103 -584 103 -178 203 -324 101 -572 253 -78 125 -126 129 -408 131 -52 129 -76 51 -132 79 -248 79 -414 51 -338 717 -322 79 -180 105 -294 109 -254 241 -76 251 -52 101 -152 125 -260 155 -80 77 -292 51 -226 209 -338 147 -100 51 -366 53 -348 133 -268 79 -366 55 -224 111 -248 81 -218 137 -168 79 -288 291 -132 51 -238 79 -78 103 -102 329 -102 123 -178 53 -202 51 -130 79 -138 51 -126 151 -180 99 -388 75 -126 51 -636 329 -150 99 -152 73 -122 177 -124 151 -386 297 -72 103 -130 51 -256 207 -354 75 -124 71 -148 351 -262 51 -540 77 -200 101 -132 77 -362 77 -254 103 -106 79 -296 51 -130 361 -240 51 -358 51 -284 201 -158 185 -288 183 -424 77 -130 RAW_Data: 121 -146 99 -124 327 -74 99 -148 99 -252 183 -112 161 -216 263 -232 77 -102 103 -416 77 -382 345 -160 131 -110 167 -138 79 -158 133 -54 163 -84 107 -80 107 -108 107 -136 225 -82 321 -108 79 -106 107 -106 55 -162 103 -160 133 -192 53 -84 269 -106 77 -162 53 -136 161 -82 135 -346 77 -864 107 -140 55 -250 189 -128 201 -502 157 -564 105 -52 107 -1150 53 -218 237 -76 159 -1010 129 -196 139 -244 101 -270 77 -106 133 -110 109 -254 501 -496 237 -692 107 -54 159 -134 347 -210 51 -190 55 -276 103 -184 229 -98 149 -252 123 -156 75 -150 537 -236 103 -204 101 -100 73 -368 283 -102 77 -652 229 -206 99 -150 197 -206 53 -80 131 -274 101 -186 159 -396 51 -188 81 -188 83 -350 105 -428 129 -128 79 -78 53 -104 351 -284 235 -130 79 -242 51 -186 77 -134 135 -434 131 -552 53 -136 107 -140 141 -252 53 -54 53 -82 51 -770 79 -130 103 -432 127 -208 153 -226 131 -248 101 -902 125 -206 129 -102 77 -154 77 -188 105 -322 105 -166 133 -474 77 -728 161 -190 107 -54 133 -80 79 -190 53 -162 53 -404 77 -334 105 -52 153 -728 423 -700 133 -476 53 -162 53 -542 159 -168 189 -134 163 -134 81 -106 133 -54 267 -108 163 -242 165 -56 167 -294 81 -136 185 -660 51 -304 157 -54 109 -378 133 -272 159 -304 135 -110 53 -402 105 -630 185 -78 257 -422 173 -252 247 -100 123 -330 107 -166 109 -502 127 -156 219 -76 101 -348 71 -586 149 -154 73 -74 77 -358 131 -270 81 -108 159 -516 295 -346 53 -240 157 -240 55 -562 133 -110 213 -320 103 -560 79 -286 51 -134 55 -452 79 -136 79 -80 77 -52 185 -504 79 -216 79 -242 135 -262 75 -206 125 -472 111 -564 79 -322 79 -432 217 -456 167 -272 103 -664 53 -302 127 -138 241 -184 53 -80 81 -192 395 -604 133 -136 53 -1044 105 -166 215 -426 133 -1222 101 -348 127 -124 123 -248 153 -492 177 -102 103 -180 77 -204 151 -282 101 -74 103 -76 101 -170 73 -506 101 -368 51 -124 193 -236 53 -310 77 -130 155 -366 229 -526 129 -428 83 -922 51 -106 131 -82 219 -706 79 -80 81 -162 133 -276 195 -220 51 -266 81 -54 51 -238 53 -686 77 -354 229 -164 195 -164 81 -294 81 -132 197 -238 73 -202 181 -180 149 -128 207 -192 111 -56 187 -808 77 -608 79 -898 77 -208 97 -200 73 -184 75 -78 203 -664 105 -384 103 -102 129 -238 131 -1346 137 -526 105 -390 129 -138 81 -484 103 -222 109 -268 51 -240 diff --git a/assets/unit_tests/subghz/linear.sub b/assets/unit_tests/subghz/linear.sub index 19ab5d72213..d5ca8ff9b86 100644 --- a/assets/unit_tests/subghz/linear.sub +++ b/assets/unit_tests/subghz/linear.sub @@ -1,6 +1,6 @@ Filetype: Flipper SubGhz Key File Version: 1 -Frequency: 300000000 +Frequency: 433920000 Preset: FuriHalSubGhzPresetOok650Async Protocol: Linear Bit: 10 diff --git a/assets/unit_tests/subghz/linear_raw.sub b/assets/unit_tests/subghz/linear_raw.sub index c143fa028a9..5bc6782f5f7 100644 --- a/assets/unit_tests/subghz/linear_raw.sub +++ b/assets/unit_tests/subghz/linear_raw.sub @@ -1,6 +1,6 @@ Filetype: Flipper SubGhz RAW File Version: 1 -Frequency: 300000000 +Frequency: 433920000 Preset: FuriHalSubGhzPresetOok650Async Protocol: RAW RAW_Data: 361 -4326 2555 -28090 65 -302 65 -1890 131 -2062 2547 -95068 2565 -48020 67 -47036 2551 -30016 2561 -47778 67 -14884 2481 -95118 2537 -95132 2549 -48496 65 -21526 2571 -16342 97 -4142 65 -1938 2543 -95008 2551 -44030 1579 -548 1469 -588 433 -1568 469 -1540 479 -1550 1489 -538 473 -1574 1467 -538 1493 -566 443 -22130 693 -3742 321 -1702 341 -1690 1353 -628 391 -1666 1401 -584 1445 -596 391 -22192 1487 -554 1471 -550 447 -1584 453 -1568 445 -1578 1469 -550 459 -1598 1465 -540 1461 -576 445 -22164 1479 -546 1501 -544 453 -1568 447 -1580 445 -1584 1451 -560 443 -1594 1473 -548 1479 -556 441 -22160 1475 -548 1487 -554 449 -1578 451 -1586 451 -1572 1469 -552 449 -1578 1473 -558 1469 -552 449 -22142 1507 -546 1479 -548 457 -1574 447 -1580 447 -1584 1475 -554 441 -1592 1465 -546 1473 -546 483 -22132 1157 -1082 67 -166 65 -198 65 -1150 257 -1706 339 -1666 365 -1642 1409 -610 407 -1626 1417 -620 1435 -580 409 -22184 1495 -572 1463 -552 447 -1584 453 -1574 447 -1580 1473 -548 459 -1568 1501 -520 1501 -552 463 -22124 535 -7674 339 -1680 1345 -678 361 -1664 1395 -618 1411 -646 373 -22194 1511 -526 1477 -550 483 -1542 479 -1562 471 -1566 1483 -546 445 -1574 1471 -534 1497 -548 483 -169938 2539 -89172 167 -5768 2551 -93014 967 -252 1571 -5844 2503 -87590 65 -5948 2523 -64008 1605 -520 1473 -554 449 -1584 449 -1576 447 -1546 1505 -546 451 -1574 1469 -546 1475 -548 459 -22160 1525 -506 1501 -516 479 -1550 469 -1570 469 -1570 1471 -538 471 -1568 1489 -516 1501 -554 443 -22152 1507 -522 1511 -518 481 -1580 451 -1542 483 -1548 1505 -546 451 -1570 1471 -552 1473 -548 455 -22180 1505 -522 1501 -544 451 -1560 475 -1548 481 -1556 1477 -552 475 -1546 1505 -516 1517 -516 473 -22148 1515 -518 1499 -546 485 -1570 443 -1580 447 -1582 1475 -526 483 -1548 1507 -546 1479 -558 449 -22138 1495 -586 1437 -616 403 -1594 429 -1608 417 -1606 1465 -556 433 -1606 1465 -536 1499 -544 445 -22170 1401 -792 1245 -770 271 -1716 325 -1680 359 -1664 1397 -620 393 -1644 1407 -614 1433 -586 429 -22202 1477 -572 1469 -548 449 -1576 457 -1578 479 -1544 1473 -590 429 -1570 1491 -540 1509 -554 429 -22196 1481 -556 1467 -580 439 -1592 431 -1608 415 -1608 1441 -586 463 -1568 1467 -552 1489 -534 473 -22166 1485 -574 1469 -580 453 -1572 443 -1578 447 -1578 1477 -554 447 -1580 1479 -550 1481 -550 475 -22160 1525 -542 1467 -540 483 -1574 447 -1582 451 -1578 1481 -558 447 -1560 1493 -550 1505 -544 449 -22182 1435 -646 1391 -664 369 -1642 385 -1652 395 -1600 1463 -584 417 -1606 1467 -576 1443 -586 429 -148588 275 -106 847 -128 877 -100 281 -97826 2541 -92318 2505 -12474 2563 -73674 2555 -41600 2543 -76554 1601 -516 1471 -554 diff --git a/assets/unit_tests/subghz/megacode.sub b/assets/unit_tests/subghz/megacode.sub index e968fd9bd1d..787e84b0dfd 100644 --- a/assets/unit_tests/subghz/megacode.sub +++ b/assets/unit_tests/subghz/megacode.sub @@ -1,6 +1,6 @@ Filetype: Flipper SubGhz Key File Version: 1 -Frequency: 318000000 +Frequency: 433920000 Preset: FuriHalSubGhzPresetOok650Async Protocol: MegaCode Bit: 24 diff --git a/assets/unit_tests/subghz/megacode_raw.sub b/assets/unit_tests/subghz/megacode_raw.sub index c2235d8c52f..8a061cdfd8e 100644 --- a/assets/unit_tests/subghz/megacode_raw.sub +++ b/assets/unit_tests/subghz/megacode_raw.sub @@ -1,6 +1,6 @@ Filetype: Flipper SubGhz RAW File Version: 1 -Frequency: 318000000 +Frequency: 433920000 Preset: FuriHalSubGhzPresetOok650Async Protocol: RAW RAW_Data: -754 361 -17246 131 -8734 65 -71908 65 -27774 65 -1230 65 -13826 99 -800 65 -634 67 -796 99 -47716 65 -18338 67 -18176 131 -7986 65 -1084 131 -2090 65 -48694 163 -40926 65 -4538 65 -10224 65 -9874 20955 -1970 1023 -4928 1007 -4946 1011 -7910 981 -1992 1021 -7898 1013 -1962 1007 -7896 1019 -4930 995 -4962 987 -1974 1029 -4924 1019 -4920 1025 -7896 1005 -1964 1027 -7894 1007 -4932 1033 -1948 1009 -7916 1009 -1982 1001 -4942 1011 -7880 1011 -2000 1003 -13820 1011 -1990 985 -4944 1021 -4932 995 -7916 1025 -1944 1025 -7892 1015 -1970 1025 -7892 1001 -4934 1031 -4926 1015 -1970 989 -4938 1007 -4954 1019 -7874 1013 -2000 985 -7896 1011 -4950 1001 -1968 1023 -7900 999 -1968 1007 -4968 973 -7936 985 -1970 1009 -13856 1013 -1968 981 -4962 1009 -4926 1005 -7924 1003 -1968 1013 -7892 1003 -1992 985 -7904 1011 -4936 1023 -4948 997 -1960 1015 -4950 987 -4970 993 -7896 1005 -1970 1009 -7928 1005 -4914 1023 -1984 1001 -7894 1011 -1970 999 -4962 1003 -7890 1007 -1992 1013 -13810 1011 -1994 987 -4938 1005 -4964 999 -7904 1013 -1974 987 -7910 1021 -1964 1015 -7894 1021 -4912 1019 -4948 1007 -1970 1017 -4916 1021 -4946 985 -7914 1003 -1972 1009 -7898 1041 -4912 1013 -1994 985 -7892 1027 -1964 1013 -4938 1003 -7904 1023 -1966 1017 -13824 1025 -1968 983 -4950 1007 -4944 999 -7900 1029 -1964 1015 -7914 981 -2000 981 -7932 979 -4936 1021 -4926 1013 -1998 979 -4946 1035 -4908 1031 -7896 999 -1964 1025 -7902 1021 -4914 1023 -1966 1015 -7908 1007 -1970 1003 -4952 981 -7910 1001 -1974 1003 -13832 1015 -1976 999 -4958 1019 -4938 985 -7900 1025 -1966 1011 -7914 1015 -1964 1009 -7906 1017 -4908 1027 -4916 1011 -2000 981 -4954 1007 -4926 1017 -7920 1011 -1958 1009 -7906 1019 -4912 1025 -1980 1009 -7900 1013 -1948 1027 -4916 1011 -7918 1005 -1976 1003 -13846 1013 -1970 1005 -4914 1041 -4916 1007 -7918 987 -2000 987 -7902 1013 -1970 1015 -7894 1007 -4936 1019 -4944 1009 -1970 1013 -4918 1019 -4954 977 -7914 1019 -1964 1007 -7906 1003 -4956 983 -1982 1027 -7896 1005 -1962 1027 -4930 1007 -7906 999 -1972 1009 -13850 1009 -1952 1027 -4944 983 -4954 1023 -7894 1007 -1952 1031 -7882 1035 -1970 1011 -7894 1007 -4936 1025 -4920 1023 -1968 1011 -4928 1009 -4920 1015 -7902 1015 -1966 1021 -7894 1011 -4948 997 -1970 1009 -7902 1027 -1978 1011 -4930 999 -7918 1007 -1966 1013 -13824 1015 -1966 1027 -4918 1027 -4912 1021 -7888 1009 -1998 989 -7918 1019 -1960 989 -7930 983 -4946 1025 -4920 1013 -1970 1015 -4950 1009 -4926 1005 -7892 1037 -1942 1011 -7926 1005 -4918 1007 -1996 985 -7928 1011 -1964 993 -4946 1025 -7880 1003 -1972 1009 -13854 1011 -1948 1019 -4956 983 -4946 1025 -7898 1003 -1966 1025 -7900 1019 -1964 1011 -7890 1021 -4912 1017 -4930 1013 -1968 1015 -4952 1009 -4932 1007 -7920 1009 -1972 979 -7924 1009 -4944 1003 -1970 1015 -7894 1017 diff --git a/assets/unit_tests/subghz/nero_radio_raw.sub b/assets/unit_tests/subghz/nero_radio_raw.sub index 265ed20ad6f..b1da069d173 100644 --- a/assets/unit_tests/subghz/nero_radio_raw.sub +++ b/assets/unit_tests/subghz/nero_radio_raw.sub @@ -1,6 +1,6 @@ Filetype: Flipper SubGhz RAW File Version: 1 -Frequency: 434420000 +Frequency: 433920000 Preset: FuriHalSubGhzPresetOok650Async Protocol: RAW RAW_Data: 1487 -32700 235 -202 229 -218 217 -176 197 -220 217 -210 191 -218 217 -210 189 -218 217 -210 187 -212 181 -230 203 -194 211 -212 213 -226 197 -188 211 -212 213 -226 197 -186 211 -212 213 -228 195 -186 211 -212 239 -172 235 -218 181 -212 193 -218 219 -208 191 -212 211 -200 205 -224 181 -212 213 -228 197 -188 211 -212 213 -226 197 -186 211 -212 213 -226 197 -186 211 -212 213 -226 197 -186 211 -212 213 -226 197 -186 211 -212 237 -174 235 -218 789 -244 205 -384 415 -230 185 -418 419 -226 179 -436 419 -190 435 -198 415 -210 215 -406 187 -436 421 -190 213 -438 193 -404 413 -224 185 -430 411 -238 181 -432 201 -428 199 -404 207 -420 205 -426 385 -244 213 -400 411 -192 221 -400 239 -412 205 -426 205 -394 203 -412 243 -390 203 -426 417 -214 215 -402 211 -428 203 -392 205 -406 231 -396 411 -232 203 -408 207 -424 393 -232 203 -408 209 -422 395 -232 397 -244 391 -202 219 -430 389 -238 405 -208 425 -206 217 -394 389 -238 207 -388 243 -382 411 -244 383 -1234 219 -406 225 -198 255 -182 175 -204 211 -212 201 -208 227 -182 211 -214 227 -198 187 -212 211 -214 225 -198 185 -212 211 -240 171 -230 181 -212 213 -226 199 -188 211 -210 213 -228 195 -188 211 -212 213 -226 197 -186 211 -212 213 -228 195 -186 211 -212 213 -228 195 -188 209 -212 239 -172 227 -182 211 -212 227 -198 189 -212 211 -214 225 -198 187 -212 241 -182 227 -198 185 -212 211 -214 227 -196 185 -212 211 -240 173 -234 219 -182 209 -194 833 -210 213 -410 409 -232 199 -406 415 -204 215 -428 387 -244 391 -204 425 -206 215 -428 205 -382 419 -226 181 -434 195 -440 413 -192 219 -400 447 -202 217 -396 203 -418 189 -438 183 -436 195 -408 443 -194 217 -400 445 -204 217 -396 201 -430 199 -404 209 -422 205 -426 205 -412 193 -420 417 -202 223 -402 239 -408 207 -392 237 -382 223 -412 409 -226 191 -430 209 -416 395 -232 197 -404 209 -422 395 -232 397 -242 393 -202 217 -432 389 -238 407 -206 393 -238 217 -396 387 -238 209 -386 243 -388 411 -240 407 -4594 195 -408 245 -214 173 -218 211 -182 231 -204 223 -180 211 -214 227 -198 187 -212 211 -214 225 -198 185 -212 211 -214 227 -196 185 -212 211 -214 227 -196 185 -212 211 -240 173 -234 217 -182 211 -194 219 -218 207 -192 211 -212 199 -206 223 -182 211 -214 227 -198 187 -212 211 -240 171 -236 217 -182 211 -194 217 -220 207 -192 211 -212 201 -206 193 -212 241 -182 227 -200 187 -212 209 -240 173 -228 diff --git a/assets/unit_tests/subghz/nero_sketch_raw.sub b/assets/unit_tests/subghz/nero_sketch_raw.sub index 6660b5c4208..4ac213366d7 100644 --- a/assets/unit_tests/subghz/nero_sketch_raw.sub +++ b/assets/unit_tests/subghz/nero_sketch_raw.sub @@ -1,6 +1,6 @@ Filetype: Flipper SubGhz RAW File Version: 1 -Frequency: 315000000 +Frequency: 433920000 Preset: FuriHalSubGhzPresetOok650Async Protocol: RAW RAW_Data: -2704 519 -368 279 -698 297 -712 259 -716 309 -688 647 -356 289 -710 615 -384 275 -692 345 -654 321 -696 619 -376 317 -658 335 -652 325 -684 655 -336 317 -698 295 -682 325 -678 639 -376 319 -662 331 -652 677 -352 631 -326 677 -326 315 -682 323 -680 317 -670 315 -702 637 -362 315 -654 679 -352 315 -658 315 -690 321 -676 647 -346 317 -660 337 -688 289 -686 655 -338 1007 -334 317 -350 301 -344 343 -350 315 -334 325 -352 313 -334 313 -346 349 -316 333 -326 351 -314 333 -348 315 -350 301 -344 343 -352 315 -302 353 -352 313 -334 313 -346 315 -350 301 -358 351 -314 333 -316 345 -350 303 -344 343 -352 313 -334 323 -350 315 -334 313 -344 349 -304 343 -340 317 -350 301 -356 315 -350 301 -346 345 -350 301 -344 343 -352 315 -302 353 -352 313 -334 313 -346 315 -350 333 -326 353 -314 333 -314 347 -350 1315 -320 681 -346 297 -680 317 -672 315 -700 311 -672 649 -354 323 -684 643 -352 313 -658 347 -654 323 -664 651 -376 317 -660 333 -652 323 -684 655 -338 317 -688 321 -676 293 -704 639 -338 353 -662 299 -684 647 -350 667 -326 677 -326 313 -676 323 -680 317 -670 315 -700 639 -362 315 -688 647 -352 313 -658 351 -662 297 -682 647 -350 349 -656 311 -688 321 -660 653 -342 1007 -334 317 -350 303 -344 343 -350 317 -332 325 -352 315 -336 311 -346 349 -316 333 -328 351 -316 303 -344 345 -350 303 -342 343 -316 351 -300 355 -350 315 -334 313 -344 315 -352 331 -328 351 -314 333 -316 345 -350 301 -344 341 -352 315 -332 323 -352 315 -334 313 -344 349 -316 333 -328 351 -316 333 -314 347 -350 301 -344 341 -352 315 -334 323 -352 315 -336 311 -346 313 -352 331 -328 351 -316 303 -344 345 -350 303 -342 343 -352 1281 -352 645 -346 333 -678 315 -672 313 -702 311 -672 643 -354 325 -676 645 -352 313 -690 315 -654 323 -704 649 -344 317 -660 335 -652 323 -684 671 -346 317 -660 335 -684 291 -682 671 -346 317 -660 335 -652 677 -352 627 -364 645 -324 351 -648 323 -682 317 -674 347 -656 643 -360 313 -676 649 -350 315 -692 307 -688 287 -690 653 -338 353 -664 297 -678 319 -684 655 -338 983 -362 315 -350 303 -342 341 -352 315 -334 323 -352 313 -334 313 -346 313 -352 333 -326 351 -316 303 -344 345 -350 303 -342 343 -316 351 -300 355 -352 313 -334 313 -346 313 -352 301 -358 351 -314 305 -344 345 -350 301 -342 343 -352 315 -302 353 -352 315 -334 313 -344 349 -304 341 -342 317 -350 301 -354 317 -350 301 -344 345 -350 315 diff --git a/assets/unit_tests/subghz/power_smart.sub b/assets/unit_tests/subghz/power_smart.sub index 445458b8c00..5dd922bdafd 100644 --- a/assets/unit_tests/subghz/power_smart.sub +++ b/assets/unit_tests/subghz/power_smart.sub @@ -1,6 +1,6 @@ Filetype: Flipper SubGhz Key File Version: 1 -Frequency: 433420000 +Frequency: 433920000 Preset: FuriHalSubGhzPresetOok650Async Protocol: Power Smart Bit: 64 diff --git a/assets/unit_tests/subghz/power_smart_raw.sub b/assets/unit_tests/subghz/power_smart_raw.sub index 0aafc87cab8..c52d30d61cc 100644 --- a/assets/unit_tests/subghz/power_smart_raw.sub +++ b/assets/unit_tests/subghz/power_smart_raw.sub @@ -1,7 +1,7 @@ Filetype: Flipper SubGhz RAW File Version: 1 -Frequency: 433420000 -Preset: FuriHalSubGhzPresetOok270Async +Frequency: 433920000 +Preset: FuriHalSubGhzPresetOok650Async Protocol: RAW RAW_Data: -68 521674 -200 213 -258 217 -218 217 -246 193 -242 211 -466 451 -450 225 -212 211 -242 211 -238 243 -200 223 -254 419 -226 221 -432 251 -212 467 -240 203 -450 449 -246 205 -238 217 -218 217 -444 439 -478 431 -230 211 -462 247 -214 435 -454 449 -456 441 -442 471 -448 449 -246 205 -238 215 -220 217 -244 201 -254 217 -430 235 -220 461 -198 223 -442 275 -212 429 -458 245 -212 203 -228 253 -218 431 -448 451 -442 235 -218 463 -426 485 -214 209 -234 221 -254 219 -214 207 -228 253 -428 459 -418 245 -216 243 -194 241 -212 241 -212 211 -244 441 -228 215 -460 217 -244 435 -236 235 -440 439 -246 213 -208 229 -256 217 -430 445 -450 445 -236 253 -420 225 -232 429 -450 465 -448 439 -448 449 -454 453 -230 225 -214 241 -212 211 -242 211 -244 237 -442 237 -218 429 -230 221 -442 241 -246 425 -458 245 -212 203 -228 255 -218 427 -448 451 -446 235 -256 425 -462 417 -244 251 -208 201 -256 217 -218 209 -232 255 -420 451 -448 217 -242 211 -238 209 -234 221 -256 217 -218 437 -238 217 -464 203 -256 419 -226 223 -464 441 -244 213 -240 197 -254 219 -430 445 -452 479 -204 253 -420 225 -222 463 -452 449 -450 449 -452 447 -450 451 -238 215 -220 217 -244 199 -256 217 -218 207 -440 241 -254 423 -238 217 -462 197 -224 471 -448 225 -220 213 -242 211 -212 465 -446 449 -456 245 -214 437 -456 449 -232 233 -216 219 -254 207 -204 253 -218 219 -440 451 -456 237 -216 217 -254 209 -202 253 -218 219 -210 437 -244 253 -422 237 -218 423 -226 247 -430 451 -242 203 -228 253 -220 217 -442 449 -454 449 -240 217 -430 237 -218 461 -426 453 -450 465 -448 451 -440 443 -246 215 -250 201 -224 255 -218 215 -208 227 -442 239 -208 457 -238 219 -464 201 -256 425 -464 201 -256 217 -210 241 -216 451 -426 445 -486 207 -236 441 -430 479 -194 223 -256 217 -218 209 -234 253 -218 217 -438 449 -454 239 -216 217 -256 207 -202 255 -218 217 -210 439 -244 253 -424 235 -218 457 -224 229 -436 449 -230 221 -214 211 -242 211 -468 447 -450 449 -250 207 -454 239 -218 431 -446 451 -448 443 -486 447 -448 431 -240 207 -232 223 -254 219 -216 209 -230 253 -426 233 -234 433 -244 213 -432 237 -218 463 -446 221 -212 211 -242 239 -208 457 -456 451 -456 223 -212 467 -450 453 -204 253 -218 211 -240 215 -218 255 -208 201 -442 479 -452 203 -254 217 -212 241 -216 217 -256 207 -202 439 -240 245 -430 237 -218 463 -240 217 -428 465 -202 253 -218 213 -234 219 RAW_Data: -444 443 -452 455 -244 213 -440 237 -254 427 -450 429 -480 417 -454 477 -430 445 -244 217 -250 203 -224 253 -220 215 -206 221 -462 217 -212 467 -234 231 -440 239 -220 427 -446 219 -242 211 -244 237 -206 451 -458 451 -458 203 -254 427 -450 441 -246 213 -242 193 -244 211 -242 211 -212 241 -442 451 -454 245 -214 241 -198 255 -218 217 -210 225 -212 461 -246 213 -440 225 -242 435 -240 209 -456 457 -246 211 -204 229 -254 219 -428 445 -452 445 -238 253 -426 231 -232 435 -448 455 -448 439 -446 449 -456 451 -230 223 -214 241 -212 211 -242 211 -244 237 -442 237 -218 427 -232 221 -442 239 -246 427 -458 245 -212 205 -228 253 -220 427 -446 471 -442 237 -216 451 -462 411 -244 251 -216 205 -222 211 -242 211 -242 211 -480 453 -418 245 -252 207 -202 253 -218 219 -210 229 -256 425 -232 221 -430 251 -212 469 -204 235 -446 441 -246 213 -242 193 -244 211 -464 451 -456 441 -248 215 -450 201 -256 427 -452 457 -450 455 -450 439 -446 451 -238 217 -256 207 -202 255 -218 217 -210 231 -442 237 -244 423 -240 217 -464 203 -254 419 -454 227 -210 243 -212 241 -212 477 -418 453 -484 199 -226 431 -267004 97 -422 97 -164 65 -490 97 -2222 559 -66 163 -64 295 -100 497 -100 263 -98 361 -460 793 -66 1803 -100 339 -68 1733 diff --git a/assets/unit_tests/subghz/scher_khan_magic_code.sub b/assets/unit_tests/subghz/scher_khan_magic_code.sub index 81467b0a74f..19d6c816016 100644 --- a/assets/unit_tests/subghz/scher_khan_magic_code.sub +++ b/assets/unit_tests/subghz/scher_khan_magic_code.sub @@ -1,7 +1,7 @@ Filetype: Flipper SubGhz RAW File Version: 1 Frequency: 433920000 -Preset: FuriHalSubGhzPreset2FSKDev238Async +Preset: FuriHalSubGhzPresetOok650Async Protocol: RAW RAW_Data: 95 -240 191 -412 187 -152 95 -120 263 -220 373 -116 199 -88 239 -334 503 -142 167 -174 85 -172 143 -86 85 -96 119 -196 259 -96 167 -148 201 -96 481 -120 237 -264 143 -144 105 -790 257 -88 243 -144 143 -86 553 -216 103 -174 143 -142 119 -144 119 -224 307 -144 239 -120 127 -390 189 -168 119 -312 191 -142 215 -96 103 -86 307 -274 191 -104 143 -96 167 -120 357 -390 651 -218 197 -170 143 -164 85 -210 143 -116 143 -86 255 -160 95 -114 199 -88 545 -288 173 -114 115 -202 85 -316 145 -144 113 -562 105 -254 85 -144 287 -116 115 -202 87 -346 287 -244 103 -370 143 -230 143 -144 85 -88 113 -230 231 -984 119 -144 171 -144 201 -174 115 -288 113 -192 277 -114 345 -202 249 -316 83 -228 119 -146 151 -116 85 -202 85 -144 437 -272 115 -144 85 -86 173 -142 163 -260 205 -280 339 -88 229 -194 363 -144 585 -394 599 -144 85 -100 151 -574 171 -174 85 -114 239 -288 211 -410 115 -144 489 -116 115 -116 201 -336 95 -96 93 -120 95 -128 115 -116 257 -174 141 -292 113 -162 165 -192 287 -192 161 -192 1125 -202 85 -222 461 -144 113 -116 115 -114 201 -288 259 -288 401 -174 85 -106 119 -96 431 -130 85 -114 375 -494 95 -96 167 -288 281 -104 411 -214 335 -144 119 -534 85 -144 143 -144 229 -246 97 -82 85 -260 201 -304 403 -116 391 -172 229 -448 81 -82 95 -116 201 -86 521 -144 119 -336 119 -142 95 -144 119 -96 267 -170 283 -288 95 -190 167 -288 199 -116 247 -144 87 -144 85 -114 215 -260 115 -116 113 -338 103 -240 167 -96 119 -120 95 -96 405 -188 85 -116 191 -334 143 -164 85 -230 115 -154 119 -166 143 -288 195 -144 87 -86 343 -232 113 -202 115 -86 201 -278 201 -86 85 -86 303 -286 95 -120 95 -168 167 -118 129 -94 95 -432 151 -218 119 -186 235 -230 167 -632 143 -144 231 -156 153 -282 147 -96 143 -592 261 -120 191 -96 167 -576 333 -342 171 -462 173 -514 157 -432 87 -312 397 -378 287 -374 403 -230 117 -264 257 -170 189 -346 137 -528 117 -240 805 -454 539 -288 171 -86 173 -286 153 -288 115 -172 173 -158 399 -114 85 -116 201 -116 85 -216 215 -432 113 -100 113 -338 123 -146 289 -86 143 -114 343 -94 95 -96 311 -110 171 -86 143 -88 969 -312 119 -316 401 -134 255 -202 323 -86 87 -114 253 -228 95 -172 85 -170 165 -86 343 -192 119 -144 213 -112 191 -334 287 -96 359 -500 137 -120 261 -242 119 -118 283 -210 167 -178 287 -174 343 -268 RAW_Data: 185 -96 119 -978 215 -120 213 -96 265 -172 261 -168 225 -462 307 -312 87 -172 115 -288 423 -84 87 -230 143 -404 109 -114 233 -216 165 -240 95 -288 95 -192 215 -238 335 -116 199 -170 135 -144 109 -408 285 -130 97 -98 115 -86 85 -86 87 -120 167 -144 119 -264 171 -316 231 -202 257 -274 115 -114 421 -86 85 -202 143 -144 201 -86 229 -86 143 -116 115 -86 115 -114 85 -202 143 -288 163 -186 483 -202 167 -212 373 -288 115 -86 171 -116 113 -230 231 -230 201 -172 171 -318 113 -390 101 -200 317 -404 287 -220 171 -232 199 -490 171 -144 173 -168 223 -202 173 -200 115 -172 119 -288 209 -220 85 -86 85 -480 423 -450 201 -364 411 -216 143 -120 219 -538 513 -330 199 -120 119 -96 127 -138 141 -140 117 -312 429 -182 259 -422 1027 -280 303 -210 131 -168 85 -86 201 -230 547 -174 113 -88 259 -202 109 -96 215 -144 291 -168 151 -316 85 -258 287 -260 279 -302 171 -284 189 -168 95 -96 309 -240 167 -384 165 -96 169 -226 95 -216 271 -122 195 -102 113 -116 339 -168 85 -192 583 -114 297 -406 167 -326 209 -212 119 -262 143 -86 87 -114 125 -172 115 -172 173 -576 219 -192 335 -120 85 -116 171 -202 711 -116 85 -172 373 -232 381 -144 95 -244 173 -210 119 -198 85 -86 143 -312 211 -96 115 -86 113 -528 103 -150 191 -144 119 -98 333 -172 215 -114 87 -86 529 -490 85 -356 239 -374 143 -260 457 -312 157 -394 131 -116 353 -172 229 -490 171 -144 189 -166 305 -144 85 -288 201 -356 147 -106 167 -740 143 -312 95 -242 137 -88 465 -126 239 -358 167 -120 1385 -130 385 -192 95 -120 817 -218 805 -140 171 -394 143 -86 371 -430 143 -216 187 -84 143 -316 173 -86 115 -202 85 -114 215 -96 167 -316 107 -368 313 -120 95 -382 197 -216 289 -388 119 -264 109 -86 113 -202 963 -172 111 -104 113 -346 115 -266 255 -88 229 -202 109 -288 357 -348 365 -226 381 -144 455 -144 901 -532 531 -108 103 -142 225 -144 143 -230 85 -144 141 -142 645 -82 167 -116 143 -172 403 -230 85 -144 87 -288 229 -144 115 -144 371 -82 107 -306 333 -116 113 -88 143 -86 229 -174 617 -96 677 -286 95 -240 95 -168 141 -216 95 -212 209 -120 95 -144 95 -240 165 -116 765 -230 229 -88 141 -192 97 -86 111 -114 231 -172 143 -144 365 -96 291 -144 129 -144 87 -168 141 -120 215 -144 143 -238 215 -192 95 -96 95 -144 403 -216 163 -102 155 -120 95 -120 263 -120 403 -374 431 -230 303 -134 791 -288 diff --git a/assets/unit_tests/subghz/security_pls_1_0.sub b/assets/unit_tests/subghz/security_pls_1_0.sub index f0909a77671..42fe7900b2e 100644 --- a/assets/unit_tests/subghz/security_pls_1_0.sub +++ b/assets/unit_tests/subghz/security_pls_1_0.sub @@ -1,6 +1,6 @@ Filetype: Flipper SubGhz Key File Version: 1 -Frequency: 310000000 +Frequency: 433920000 Preset: FuriHalSubGhzPresetOok650Async Protocol: Security+ 1.0 Bit: 42 diff --git a/assets/unit_tests/subghz/security_pls_1_0_raw.sub b/assets/unit_tests/subghz/security_pls_1_0_raw.sub index 5542663d67a..ceecad35bda 100644 --- a/assets/unit_tests/subghz/security_pls_1_0_raw.sub +++ b/assets/unit_tests/subghz/security_pls_1_0_raw.sub @@ -1,6 +1,6 @@ Filetype: Flipper SubGhz RAW File Version: 1 -Frequency: 390000000 +Frequency: 433920000 Preset: FuriHalSubGhzPresetOok650Async Protocol: RAW RAW_Data: 297 -1592 167 -1594 231 -366 65 -598 65 -600 197 -98 199 -1098 99 -98 65 -862 133 -628 165 -597 361 -6828 65 -1668 231 -632 65 -792 163 -1284 163 -5554 199 -1588 165 -5300 97 -992 65 -432 197 -2566 99 -2340 63 -5932 65 -1462 65 -1062 131 -2454 65 -1446 133 -1682 65 -1260 65 -368 131 -1482 65 -134 131 -1512 197 -1710 131 -824 99 -66 133 -464 131 -866 65 -698 65 -200 65 -894 99 -1692 233 -1130 97 -2160 265 -1392 99 -132 131 -166 65 -1318 327 -1548 163 -6980 165 -994 65 -698 131 -1580 129 -132 63 -758 97 -466 99 -1590 395 -1024 97 -600 99 -732 63 -228 129 -98 165 -292 99 -696 231 -232 197 -166 133 -132 131 -430 165 -664 199 -1596 197 -530 65 -1054 63 -3474 165 -4504 65 -2980 99 -268 133 -2356 131 -798 99 -132 99 -796 97 -1692 97 -3804 199 -166 67 -66 97 -132 99 -5058 129 -1512 163 -1290 65 -298 199 -398 65 -1034 65 -1332 167 -3448 65 -1750 65 -1186 131 -462 65 -920 65 -166 97 -362 131 -1704 131 -1748 65 -2124 65 -1064 133 -1694 197 -5148 99 -566 131 -1696 99 -598 65 -698 231 -692 267 -2490 99 -1618 165 -1760 197 -2152 165 -1226 195 -100 99 -66 131 -100 99 -400 65 -456 131 -198 165 -368 261 -826 365 -858 99 -132 163 -460 229 -264 65 -664 165 -234 231 -98 165 -100 99 -1986 99 -2526 65 -832 167 -1762 231 -728 67 -530 133 -2082 97 -960 67 -498 199 -1658 133 -1488 99 -2652 99 -1258 165 -5284 99 -1660 65 -1790 65 -432 65 -3804 197 -2170 131 -196 129 -66 97 -832 99 -2466 97 -1622 99 -1624 163 -394 163 -1018 133 -1856 99 -430 67 -826 197 -3156 65 -166 97 -558 65 -1690 131 -1498 99 -66 65 -2716 161 -658 99 -1054 65 -230 65 -2074 97 -2042 65 -3074 263 -1698 165 -492 65 -400 131 -196 131 -368 165 -134 203 -168 201 -134 165 -398 301 -628 99 -1032 99 -830 65 -66 65 -432 229 -230 161 -394 197 -228 197 -330 327 -66 197 -822 231 -392 131 -892 393 -1456 131 -64 163 -1418 65 -166 165 -1428 133 -1526 163 -954 65 -888 99 -1956 133 -1194 131 -3514 65 -1728 97 -662 67 -1590 167 -2714 65 -930 65 -166 65 -728 131 -2752 65 -434 133 -1828 99 -3704 129 -1912 133 -1544 227 -494 133 -202 97 -466 65 -200 131 -1580 263 -1708 65 -626 65 -626 199 -166 133 -3446 99 -3420 99 -398 131 -1164 131 -7018 131 -3468 63 -1658 199 -956 101 -598 99 -1790 97 -1192 65 -362 133 -630 65 -100 429 -794 65 -696 231 -1130 65 -268 395 -730 131 -466 431 -66 233 -168 197 -998 97 -264 65 -796 163 -236 diff --git a/assets/unit_tests/subghz/security_pls_2_0.sub b/assets/unit_tests/subghz/security_pls_2_0.sub index a13ab0fc81d..85abdb3e190 100644 --- a/assets/unit_tests/subghz/security_pls_2_0.sub +++ b/assets/unit_tests/subghz/security_pls_2_0.sub @@ -1,6 +1,6 @@ Filetype: Flipper SubGhz Key File Version: 1 -Frequency: 315000000 +Frequency: 433920000 Preset: FuriHalSubGhzPresetOok650Async Protocol: Security+ 2.0 Bit: 62 diff --git a/assets/unit_tests/subghz/security_pls_2_0_raw.sub b/assets/unit_tests/subghz/security_pls_2_0_raw.sub index 4cd9822c49d..f3c0b4992f4 100644 --- a/assets/unit_tests/subghz/security_pls_2_0_raw.sub +++ b/assets/unit_tests/subghz/security_pls_2_0_raw.sub @@ -1,6 +1,6 @@ Filetype: Flipper SubGhz RAW File Version: 1 -Frequency: 390000000 +Frequency: 433920000 Preset: FuriHalSubGhzPresetOok650Async Protocol: RAW RAW_Data: -130 1115 -68 55471 -1962 167 -1664 99 -364 427 -232 759 -132 199 -100 267 -134 527 -132 99 -1292 99 -266 65 -68 131 -762 327 -98 393 -298 37611 -730 131 -428 1487 -3910 327 -100 295 -98 491 -492 65 -98 197 -860 97 -98 131 -856 131 -134 231 -792 229 -66 919 -198 7527 -7444 131 -722 97 -230 65 -98 527 -100 267 -68 229 -262 361 -528 195 -624 131 -164 495 -66 1697 -66 1791 -132 3715 -8320 65 -394 165 -166 461 -1094 297 -532 131 -764 197 -98 261 -230 165 -100 6235 -12144 265 -600 131 -134 65 -298 233 -958 163 -196 97 -756 263 -98 465 -134 97 -164 3051 -16552 431 -68 131 -166 131 -264 331 -298 561 -166 689 -66 557 -264 3173 -8316 65 -166 531 -298 197 -234 233 -134 233 -892 163 -296 65 -890 161 -232 331 -100 397 -15072 199 -100 99 -394 165 -164 129 -132 197 -132 199 -798 99 -1032 67 -398 427 -100 391 -98 363 -166 297 -66 2869 -66 2299 -12300 97 -660 99 -662 263 -594 131 -662 133 -66 199 -332 97 -134 99 -132 65 -134 131 -100 431 -100 2143 -66 429 -8840 97 -6976 97 -66 397 -66 65 -166 131 -230 131 -294 99 -228 97 -230 129 -756 133 -1298 1293 -132 529 -100 2567 -15654 229 -328 97 -198 131 -66 195 -1492 131 -398 99 -134 99 -366 265 -168 99 -100 235 -100 131 -396 299 -200 1029 -66 6461 -6804 99 -1062 65 -960 65 -232 131 -98 195 -1708 63 -196 261 -164 331 -66 261 -12094 65 -298 265 -198 163 -592 131 -2402 63 -66 297 -198 97 -66 393 -264 4003 -15878 231 -98 261 -496 229 -264 65 -858 131 -994 133 -66 331 -66 429 -100 165 -132 297 -15004 99 -1466 97 -266 165 -198 463 -796 231 -66 131 -298 99 -100 133 -134 167 -430 99 -66 365 -100 297 -134 265 -132 563 -98 1217 -66 6399 -8742 99 -592 99 -426 397 -2338 199 -66 995 -134 229 -132 65 -164 3989 -66 3675 -6962 165 -466 65 -564 399 -66 199 -134 263 -396 97 -132 97 -100 97 -428 393 -624 131 -988 229 -66 363 -230 791 -164 7883 -8286 165 -134 99 -66 197 -100 99 -68 131 -164 163 -398 197 -2162 2005 -66 97 -100 4365 -98 1255 -12012 99 -132 165 -462 65 -166 97 -564 65 -100 331 -794 199 -364 261 -496 331 -132 823 -66 6233 -10976 165 -764 165 -200 195 -296 97 -19176 195 -230 129 -658 131 -132 293 -66 133 -860 65 -858 131 -64 229 -66 227 -66 161 -66 9051 -7316 65 -1494 131 -98 165 -198 65 -134 365 -398 297 -100 3969 -14874 99 -998 65 -564 67 -364 263 -132 163 -528 197 -132 65 -264 65 -264 431 -100 301 -66 297 diff --git a/assets/unit_tests/subghz/somfy_keytis_raw.sub b/assets/unit_tests/subghz/somfy_keytis_raw.sub index 21145c064ab..2bed74fe111 100644 --- a/assets/unit_tests/subghz/somfy_keytis_raw.sub +++ b/assets/unit_tests/subghz/somfy_keytis_raw.sub @@ -1,6 +1,6 @@ Filetype: Flipper SubGhz RAW File Version: 1 -Frequency: 433420000 +Frequency: 433920000 Preset: FuriHalSubGhzPresetOok650Async Protocol: RAW RAW_Data: 1753 -32700 2581 -2598 2513 -2604 2535 -2568 2529 -2602 2519 -2608 2523 -2574 2529 -2598 2517 -2604 2537 -2596 2521 -2574 2523 -2600 2515 -2530 4829 -1304 1305 -1308 1309 -678 669 -1280 1305 -680 639 -678 653 -1308 1273 -1334 645 -686 645 -684 611 -716 645 -684 1273 -1340 1269 -1338 641 -684 643 -684 643 -684 609 -714 1273 -682 643 -680 643 -1340 643 -684 1271 -1310 645 -684 1303 -682 643 -1308 1301 -1312 645 -682 1275 -1350 649 -644 649 -702 647 -678 679 -654 645 -646 681 -644 683 -642 1309 -672 647 -690 645 -1314 635 -680 1297 -688 651 -1316 647 -682 617 -686 649 -682 1307 -660 639 -704 639 -1304 1293 -688 653 -672 621 -696 653 -664 653 -668 651 -670 647 -704 613 -704 617 -704 613 -708 613 -708 613 -710 613 -1336 665 -652 1285 -680 655 -1340 583 -1734 2523 -2570 2543 -2572 2529 -2564 2533 -2570 2531 -2596 2519 -2478 4805 -1310 1299 -1314 1313 -680 637 -1328 1289 -684 623 -690 653 -1322 1289 -1308 651 -668 651 -688 615 -688 679 -654 1305 -1304 1305 -1304 645 -686 645 -688 643 -652 643 -712 1273 -680 653 -680 651 -1312 647 -682 1273 -1342 639 -680 1271 -718 613 -1348 1281 -1310 639 -680 1295 -1314 653 -678 645 -682 645 -678 675 -648 651 -678 651 -678 647 -678 1281 -698 613 -706 613 -1336 669 -658 1293 -684 639 -1330 629 -686 653 -686 653 -654 1309 -678 621 -1340 1273 -712 603 -712 639 -672 653 -664 651 -666 651 -670 651 -702 617 -672 649 -704 613 -706 615 -706 615 -708 613 -1334 1293 -688 621 -1316 659 -686 1281 -2338 2501 -2600 2511 -2606 2491 -2624 2481 -2610 2521 -2560 2527 -2506 4797 -1308 1299 -1312 1317 -678 635 -1294 1323 -682 619 -692 635 -1334 1291 -1312 653 -658 679 -656 647 -688 643 -654 1303 -1336 1301 -1304 643 -690 641 -654 675 -654 643 -682 1303 -678 655 -680 645 -1308 677 -654 1309 -1310 645 -682 1275 -686 647 -1320 1281 -1318 679 -642 1317 -1300 667 -636 677 -660 673 -656 705 -612 671 -670 643 -670 681 -646 1309 -660 669 -674 637 -1310 673 -658 1293 -690 653 -1290 657 -688 649 -656 679 -658 1303 -676 635 -1314 657 -658 1307 -678 669 -670 621 -690 635 -672 649 -674 647 -674 647 -674 647 -708 613 -710 645 -676 647 -676 613 -1336 631 -688 1285 -680 655 -1306 647 -688 611 -1706 2535 -2592 2529 -2570 2533 -2566 2543 -2570 2529 -2572 2533 -2474 4807 -1294 1325 -1314 1279 -680 665 -1290 1323 -648 659 -688 653 -1290 1317 -1310 657 -656 679 -658 641 -688 643 -656 1303 -1334 1299 -1302 675 -646 675 -644 677 -658 643 -688 1303 -676 641 -676 633 -1314 691 -654 1277 -1336 649 -656 1305 -674 667 -1284 1315 -1308 653 -656 1305 -1306 679 -658 645 -690 From e40376bc630baf5119c3633546f995637451dd84 Mon Sep 17 00:00:00 2001 From: gornekich Date: Fri, 2 Dec 2022 16:41:34 +0400 Subject: [PATCH 257/824] NFC: fix NTAG203 info scene #2078 --- .../main/nfc/scenes/nfc_scene_nfc_data_info.c | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/applications/main/nfc/scenes/nfc_scene_nfc_data_info.c b/applications/main/nfc/scenes/nfc_scene_nfc_data_info.c index 7318fd009d4..d1767a45800 100644 --- a/applications/main/nfc/scenes/nfc_scene_nfc_data_info.c +++ b/applications/main/nfc/scenes/nfc_scene_nfc_data_info.c @@ -89,18 +89,20 @@ void nfc_scene_nfc_data_info_on_enter(void* context) { furi_string_cat_printf(temp_str, "\nPassword-protected"); } else if(data->auth_success) { MfUltralightConfigPages* config_pages = mf_ultralight_get_config_pages(data); - furi_string_cat_printf( - temp_str, - "\nPassword: %02X %02X %02X %02X", - config_pages->auth_data.pwd.raw[0], - config_pages->auth_data.pwd.raw[1], - config_pages->auth_data.pwd.raw[2], - config_pages->auth_data.pwd.raw[3]); - furi_string_cat_printf( - temp_str, - "\nPACK: %02X %02X", - config_pages->auth_data.pack.raw[0], - config_pages->auth_data.pack.raw[1]); + if(config_pages) { + furi_string_cat_printf( + temp_str, + "\nPassword: %02X %02X %02X %02X", + config_pages->auth_data.pwd.raw[0], + config_pages->auth_data.pwd.raw[1], + config_pages->auth_data.pwd.raw[2], + config_pages->auth_data.pwd.raw[3]); + furi_string_cat_printf( + temp_str, + "\nPACK: %02X %02X", + config_pages->auth_data.pack.raw[0], + config_pages->auth_data.pack.raw[1]); + } } } else if(protocol == NfcDeviceProtocolMifareClassic) { MfClassicData* data = &dev_data->mf_classic_data; From 2b06b41ffd962d1df3192e59019fd48a2e6c760c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Wed, 7 Dec 2022 18:34:36 +0900 Subject: [PATCH 258/824] Dolphin: new animation L2_Wake_up_128x64 (#2099) --- .../external/L2_Wake_up_128x64/frame_0.png | Bin 0 -> 4779 bytes .../external/L2_Wake_up_128x64/frame_1.png | Bin 0 -> 4782 bytes .../external/L2_Wake_up_128x64/frame_10.png | Bin 0 -> 4835 bytes .../external/L2_Wake_up_128x64/frame_11.png | Bin 0 -> 4838 bytes .../external/L2_Wake_up_128x64/frame_12.png | Bin 0 -> 4775 bytes .../external/L2_Wake_up_128x64/frame_13.png | Bin 0 -> 4783 bytes .../external/L2_Wake_up_128x64/frame_14.png | Bin 0 -> 4722 bytes .../external/L2_Wake_up_128x64/frame_15.png | Bin 0 -> 4742 bytes .../external/L2_Wake_up_128x64/frame_16.png | Bin 0 -> 4639 bytes .../external/L2_Wake_up_128x64/frame_17.png | Bin 0 -> 3796 bytes .../external/L2_Wake_up_128x64/frame_18.png | Bin 0 -> 3834 bytes .../external/L2_Wake_up_128x64/frame_19.png | Bin 0 -> 4664 bytes .../external/L2_Wake_up_128x64/frame_2.png | Bin 0 -> 4785 bytes .../external/L2_Wake_up_128x64/frame_20.png | Bin 0 -> 4779 bytes .../external/L2_Wake_up_128x64/frame_3.png | Bin 0 -> 4793 bytes .../external/L2_Wake_up_128x64/frame_4.png | Bin 0 -> 4815 bytes .../external/L2_Wake_up_128x64/frame_5.png | Bin 0 -> 1743 bytes .../external/L2_Wake_up_128x64/frame_6.png | Bin 0 -> 1630 bytes .../external/L2_Wake_up_128x64/frame_7.png | Bin 0 -> 4722 bytes .../external/L2_Wake_up_128x64/frame_8.png | Bin 0 -> 4731 bytes .../external/L2_Wake_up_128x64/frame_9.png | Bin 0 -> 4656 bytes .../external/L2_Wake_up_128x64/meta.txt | 14 ++++ assets/dolphin/external/manifest.txt | 69 ++++++++++-------- 23 files changed, 52 insertions(+), 31 deletions(-) create mode 100644 assets/dolphin/external/L2_Wake_up_128x64/frame_0.png create mode 100644 assets/dolphin/external/L2_Wake_up_128x64/frame_1.png create mode 100644 assets/dolphin/external/L2_Wake_up_128x64/frame_10.png create mode 100644 assets/dolphin/external/L2_Wake_up_128x64/frame_11.png create mode 100644 assets/dolphin/external/L2_Wake_up_128x64/frame_12.png create mode 100644 assets/dolphin/external/L2_Wake_up_128x64/frame_13.png create mode 100644 assets/dolphin/external/L2_Wake_up_128x64/frame_14.png create mode 100644 assets/dolphin/external/L2_Wake_up_128x64/frame_15.png create mode 100644 assets/dolphin/external/L2_Wake_up_128x64/frame_16.png create mode 100644 assets/dolphin/external/L2_Wake_up_128x64/frame_17.png create mode 100644 assets/dolphin/external/L2_Wake_up_128x64/frame_18.png create mode 100644 assets/dolphin/external/L2_Wake_up_128x64/frame_19.png create mode 100644 assets/dolphin/external/L2_Wake_up_128x64/frame_2.png create mode 100644 assets/dolphin/external/L2_Wake_up_128x64/frame_20.png create mode 100644 assets/dolphin/external/L2_Wake_up_128x64/frame_3.png create mode 100644 assets/dolphin/external/L2_Wake_up_128x64/frame_4.png create mode 100644 assets/dolphin/external/L2_Wake_up_128x64/frame_5.png create mode 100644 assets/dolphin/external/L2_Wake_up_128x64/frame_6.png create mode 100644 assets/dolphin/external/L2_Wake_up_128x64/frame_7.png create mode 100644 assets/dolphin/external/L2_Wake_up_128x64/frame_8.png create mode 100644 assets/dolphin/external/L2_Wake_up_128x64/frame_9.png create mode 100644 assets/dolphin/external/L2_Wake_up_128x64/meta.txt diff --git a/assets/dolphin/external/L2_Wake_up_128x64/frame_0.png b/assets/dolphin/external/L2_Wake_up_128x64/frame_0.png new file mode 100644 index 0000000000000000000000000000000000000000..91209003253eb30c6c3ad6945a5dc1515bbe858a GIT binary patch literal 4779 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�?vbazMY@}04#AnHVzaAb2BWSL{!54 zmQe~L1~Rk(Ktnq$5Qq08P$2FEFP{KSDE(yv6yoEd3AM+VBg_N!3En;?;bg*@a0?rJ zxF25K1FEeB(Fnsb3=jzv93+hB9}t8M(}ezI7t2`xCc~kSzeFg0n$W*NIhb2P^hsm_ zL{$j|!y^y~h^o30(p?3EQ&o3ID?(5R6bg<|fg_b+C=?cfWW11n9w@^h*~1fSZD90| zJH}2E>P?{pV&U-6&`_mNWhD~X3yxG*SBE1|a1;v0kbnhI11PvKSU`~U9}5PAAUxS8 zkm5rMfc&XtO@@Q zO&@T#6F7>wEgs4cLp>BmPZ^0ps_FjGH4O-&-~#Z3KfZk!zW>oR{GYm5eKG+@A(3rJ zB>z8!VC7Askb=BPfe@6k5(Xk~j>G!|{H7@UKBd2dHXx9FLI@s4WD*hbSDLXt{{;e? zfKgW`AaO9f8V&(Nt0FyM>Sz=OhV)d%s}pbtPrND$`j5WHf28m4N@Y+OVFdm`gT<&2 z)KySuJj_##fP|@H(2Qi_acG#QI!+b!n}bnNgTfgQ;lC^AKPu@DiBX=vm;bf~WAkrw z5ds(;MP{^)oW-F8M%y8*%xnx9^RHjOc6N5Qx3}5Z*%{llwKc}Lva+(VvBAvD%)-K= zqN1|9yE`>CwZFfwaVS+609X;G2D&z3Lko`J#9_pNkM9jXzx?%Uar&_g53eKGN|4Rh zO*OtYhGUV`j7=FxN7)fxQD+?f{!nP>+ZOO}Se@%piKatSp1)iX8DKBuqkH(S1av)7$w zIU~UZ`rsBst`~;1RntRp_(#~O(E8$a@Jl^n%5fk79wVsdHp$EB79z37ast@wE8ckh zTEQ1cUdfiz0vE9s2b8*ftpsWd+i6${NJ}SmC6&ozkWb$@jkn-nX*7?#vD@ce320vb zl8)E`;SZ3bU7$}2z*BeHfZ{26JjaWdx8%ngqt`)+!ws$ua?*NZvHmZY!ha%f5MOf^@#XrS6ovESv96cPu$5$Bs++0tQ`=+s5KVMGT+9=wQpHpj!pMB6N|du`Z%mId*?o!ydZ zd2>QL?{U?HteCv~Y%I+}VXJjjB{skGNhFh7v^JB5U78Jtz!eZv4OA!7$z-~(htQqE zV;;{TbiY2q^lP|ypC!*h@eTb%7U)LLQezig+}Se5W>jWnrV%5$9BcZ9GJ%kL+|)BN z65NPQI*LLAu`S;h&S{=s_LpLdmfjrUjqa5NjKD3Q>n&cu!qGNuJcahrcdv(Xcc(CU zm@LUvX${#bycYmxWvj8zPPe^FAe4)7`paDSSc5Mjw*-9;k~x1g{f5q`OyLVbY_-Wx zawE}6M6+>c3?;o_=XkC3vvBz~vK)<8hYB55{9%`Q$h$GWl@Iiwuc^V8xg?)Xzt zwL<9m;Oma3iUXg4;=I%1MWY=Kl+TXgd#7_{8|=QU2ia|egg99WK^pSDZ%fAB)835cW6Efdbl#|l z){F3N{1K2VV!v{AfUmHOV_Ytu=B&sZS<3&y_N~BowN%mf_jl9ZKDFyld4KJ~tBK%~ zuI<%5UR`8$Dc!!X!HQ{FAom=_>*O7blZ1|UoC=_0zslKUQf&VtO;Da6;9|{6jEsf= z*CMozI0eyJ(H~(PZ7`B#d&cy&t3^}nSPt;n#I@5?%yPN$=TAxtn8zd+L3-E{aB7y3 z;~GspF1J=R7u*%NV0c$9<`Dx(uhHfnWT~ZjTmqOf@D>tbXY&J0<>N|@+HpE;=5e<_ z$F@i9d*?AX$U)fdiwSzWb9QCki>W)_P)GwAYAV>8tDOQ5rrj$S_LuV1x40zsK?g9<&j=>G$&h-8TX literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Wake_up_128x64/frame_1.png b/assets/dolphin/external/L2_Wake_up_128x64/frame_1.png new file mode 100644 index 0000000000000000000000000000000000000000..0b99a32ff9c04baa54b66b8e0504ec39ff75ace7 GIT binary patch literal 4782 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Wake_up_128x64/frame_11.png b/assets/dolphin/external/L2_Wake_up_128x64/frame_11.png new file mode 100644 index 0000000000000000000000000000000000000000..8d54da6c61b0b1ba2b876bef8fe1d023ec958909 GIT binary patch literal 4838 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 VFrQLE34ShsqrHn=g^k~-{{y`aaz_9F literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Wake_up_128x64/frame_12.png b/assets/dolphin/external/L2_Wake_up_128x64/frame_12.png new file mode 100644 index 0000000000000000000000000000000000000000..84046a46c8fd13e533bd2181f088fb726946fa98 GIT binary patch literal 4775 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 U`qrQ19t(hl=`oX1Bag`c1AMVsMF0Q* literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Wake_up_128x64/frame_14.png b/assets/dolphin/external/L2_Wake_up_128x64/frame_14.png new file mode 100644 index 0000000000000000000000000000000000000000..f4f6ccd661ebca010fcfe407427402ee65958efc GIT binary patch literal 4722 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Wake_up_128x64/frame_15.png b/assets/dolphin/external/L2_Wake_up_128x64/frame_15.png new file mode 100644 index 0000000000000000000000000000000000000000..5dc1a652563722b09ff28efbf1ba62efb97e79a8 GIT binary patch literal 4742 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Wake_up_128x64/frame_16.png b/assets/dolphin/external/L2_Wake_up_128x64/frame_16.png new file mode 100644 index 0000000000000000000000000000000000000000..bec472921c88739c9ca5d1290680fe4b1d0f1cf6 GIT binary patch literal 4639 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Wake_up_128x64/frame_17.png b/assets/dolphin/external/L2_Wake_up_128x64/frame_17.png new file mode 100644 index 0000000000000000000000000000000000000000..82e5176c89130ca2f5def41a7b5888fcfe921d8d GIT binary patch literal 3796 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 YnPB0-mm!6Y0yuz;)gj9wbI)`C0T05Vk^lez literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Wake_up_128x64/frame_18.png b/assets/dolphin/external/L2_Wake_up_128x64/frame_18.png new file mode 100644 index 0000000000000000000000000000000000000000..3b5e60dfdb933468706d6c1749f1cc2e862b544a GIT binary patch literal 3834 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 U-yOBsn5_<&7@apP)^`d2KOwkMcK`qY literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Wake_up_128x64/frame_2.png b/assets/dolphin/external/L2_Wake_up_128x64/frame_2.png new file mode 100644 index 0000000000000000000000000000000000000000..84d6aaf3533941c61a9f82147f1bd62f5b17b52b GIT binary patch literal 4785 zcmaJ@c|26__rKQc$u=UH#u71RVK5{6HbmAeMT{{c%NUKZjIFU&WXl#JQK5!5WfD;- zWJ^Ut*-4h6lC1rv&*#(U_x=Q-S-s$aseQS7GnYdZVCen->nRIfWhjV5fCs~zOR}ONap|wRV)=bjUEHu$KoB; zIa{6qWeg*T26sy>2i^T4(vp+j!~=w9xMl4&@&@RIChA=PSeFwp-7ntF1-9WTgTHAW ztM&>>W@0&jmd?EHf+KG5i^p*6hsG_zZD2i1qzN`A*<68Del?WptMKefwd#eFB1li%LFMo8Xga2ER1N$zy48 zKiI1~!%@q?=^~@TnwE;h?bEp6@LI==*-mZ~MDW_I!cJTAne7bKz6q_n9_o^*D{kkR z_7K}P+9StovjZJ@6Jm2`kSp@}+x$5Z!T=-1uI1>%T@N~gTH=;CIMYPj-h=~2ao?a9 zWCXJTQ9mstZ4Cf&n%6Up)Hs0df@_li(D+%}qbe6<^O6q$%*x^q-7(%J^i+b;B69hu zTw@D}6US?2B&67KSk#1fioQ?as&u~bUgs9fAoqhiTry>QipE9jo+5OGXUD`m;^#v^ zyx!8K&!ihu__>YS`NVMtFz5F8WDG)j_&|jj+z=dA>mvOcc~h~7!`<3&A`+95F*42A8;NQ&YF9lcau?WlfqBCp+3|d;UIO#t+=LB(xuWB>Trq1D!El=U`W2)}VcAWG?&{Kad^gWw zt{k@_m4(~GoX%E@YlRglbr(ySc3y*4^uI3h_w!#n1)HNb@PAC)Z(i`a;7#D?U30T@ ziQglHgj0kQ+ps<=djt1I@2!-s-wP|Wxxw?cps?K;{vIh?=yz%NCGs)1Lq871NM^qh z%fsZk!;93vbv z?Ry=(DxP?4Cv4z_@1-PLl%-%RobmN9_og3^dWj^gxS0%4T)5`d125V7oVd5XU=z-> zDH{%pwsA5Iy-8#cvxy}$r@J{sUfvyBMt`_$-E*KvxF;IQ1vP};9SEo)*2LB53`Gq+ z8S+`AEJ`iT53#~RN{mX>OI9%?%vuMLvq7#Snn5uxA=M59RKR8Rgt-meU_`F z?-i?{FRTGppS%uSi%&aiZxBKkzuc9D{~UP9pV_yZG+wo*^Hf82>#2%9tBQk6<)yQJ zlc-3L0g*x8=flwYL>LJ?jPvnF4qt1%_G0RZYIkm65wW4K?k%;et6hDBG05mx$UygB z4lKnz`OrALm7aXv{PGLyH0`vu{Gt4qPM&<*{58fU7XufX%c{$JhSh+_y!t$8z%|HT z*0oru7*pK&u&SoGrZv2nG%7J6k*WGdb=4_&2(hrWAh1xhz`Kb4f%~EKgY=_Uu4|u| z+<+ccFNQ)5>k1DFX`H_{uJydSwfgy-tDmgj)zFe}DA0`~S)F19;RPTkQe|po@Rg{} zylI)GhBpu1u2vL3E*`1*KzjGFOaDdetxa2vV%ZMQ=lU$;@5^g$6Q{c-ODc;ipMA@k zg`EsP>3(vvX}{h5%gUt#a8mnl>xr?Z&S-Cpfj(iQXO7F@r2*%as4Him+;{>R)qkQ8 zHxO4Cw`;3(Q*WblJM(ArIPI&{%26I~j;9>c98K{u@%Om`x#YPExv%d+@uc%S=7k9v zNSX-o3%koxa>y;9ud?fS%@IHKqCUx2d?k{{WjEkm$~OB4_GF~=@{Nkdk#DTq%ym8F zJ2z$-bdD+Szqb&y9xI`G2zgWEi$k3g^)z)&eSk6LzUkMLv65MR;gQ~Oz(Y#7A0r^h zF$lj<%%n4KGUFr9{xn}H5TlAK@-LpBY8`CVKR+4DIU7`@d3mbHIiHHJogOKF1e%%a z=`4*n?2dBJiqTEJBvx?Ga5@oeVh@wHo30GDl@tG(RGe&Q{-iv;+~ICL)B5nmnG%=0 zAG#m)Vof(rrPx`7z0~Blb5x^c@4fg);hU|C*}x}8?}yVLpy=GoxtA*(h5hsE)ZWAE zRqE}CYHB&cQ?wbawcAo0Im%nX4 z;pTYkYXEKJYldUuo9ow2)e>B_@~U$?I)iJ!AAf|G#O3)OV+9oSpKv*I2$bi=2w_y! z{=8$soSASv>eU~!N+S&oeX_29wK$KRzcwGtSaWZ_ZQT@L-2XadS-3`aC4E}fi)8(Y zdNnFwHKV((`&Bm=3Wr)e(e;oq{Pn7K#gC|xGoGC;+cmf9pL|dr8fg9)5&cahQBG3t z6DxP2@qt>TYhaUk>}v3vrG$@}GCr~m%`ien)r@nLPKOxMz~5*7WG^{IrM$x7eK(vqWw z`tJ%yXlpiGg@K+9o=twHy`_dA>XJ>p*ILK7PqUex%Z(x5A!|OvO7%x-^lSAk<3hH_ zvA0i<=Lu__i>`-jso!nq_4-(E&uj#=^oPFx`Ch~G3ayFtazVB}Soz~Vrc%S|&+s3M zpa_8_n-R~b%*fkYPlmR??AqA<O)*nws6}JQ^F~KY!rpy z6m`@Y9~Fp4`$7y2z=tC-Yy%_$6$g$a1qFvJ_%9V|pg!bpQf{{P zU{f-M0M^k!Lh*1o9IS)ZK=^2*a5`uoEp;#wjzq%X+AxGB6p6&Z5$qfM&jn#Sr1%ms zj%F7BxMP3mLrzhtAs84eA|gT~LQ{iG@q;1IXfzCtgdveowgxnm7EHxOLW4sU{#Y<0 zgyJdwAyj{IF!;AcoDVsSst;k4{W}U$h^_6vhJ!=@$rU?mut;18450yskx0Ko`%62N z>PYy18vj*0)R`7SfH@LE$zc>cdrOE4f57bY{clIV71?Ysb`_!515@K7y04hPjDX!=5NzCOB8I1z!<#p@8YQ7GL%cK#dQMAzIL zVTwR%>zX1E2y-(NG+N8V+ysd-(L|sSy2gL7R>7fETri&S$G1P*_di&~|HNWUDFhsq zOmQZYgZ>nP{V6h)9D0fz0!C_Tpuj4&IJ|%GZ;9&fUHUs{GXljwoZxFgA(Oy=r5WS@ zUnpo1P-rv(frH|8ad4=X4#F3T)r0?%aWlPv$g#2NH zL1`1v+DI)tl&DKUKy^@B>}29`T2LYyr-S^xg3{K7z}OUFzbofID(R1iU7o+E|F#DE zm<>ci2{{8#f8vBe7uvn~} zogFSNuC1*tZEfw9l@+#ddV1Q}OW+{@aBEtb89PS~Ex7XskHCfAwd=pm+5Qz+?0IBj z`C=1CYPnIm;{57?j1-->Q!x2OL;4wMU}j~OqlKS1mpUGcJ+4HIi#;(gN(Y_ssp9bd z)WT}}h&z*=7byQyw1J*VM1FTytM@F8@0quZLSo8Ldx7wXXxAKMKEWoItL@(Ck z=TY^;OIus;B{;b1>C99V%qXjuWrqvAUH-N5WKwK`YZZyIt7Vap?IW7CWzKQxeV<{- zF8a3hmskqi3<|`0=CmFh&jqrF553kC^0s#5ZJh%U#4iQCQbsAl2&&CGIQ@=*a@jtP z`|Bt(lYu1@Ss_qBmbK=yH3^_bx(r3+CoI61G%Rw*=})7$+lH@r4Js=HiHWL+JXJLP zc@V;-d|}AG@6h8+MP9j|Nz7c(X!y#xtO6l7Oqt*=&N0qlqpY?`K}n_(c;bS_0|&~` zrIsZLMM)k-Lj|yMF=ly$cPtUaT^|U~Y4QDK(hXQvw6~z>*YD`_*e=<71BW%JXWkuR z8hLPmKL;gBe?6K#f7|$J-K3JNiVA%-p|UHU7;_=*AfI?@t@A^Uvay5oxL4)VkNNwz z`JVwyh*6Of!F9E?^Fq#}+i^GR^?+?>=gKK)Ti|oTh0MO5c zla(V z(`KndM@f*7>J|<~9?6LF4?v*gq$AIJB!?ppxWqdgFTKFD=(mnheRQ^AhCfxPB~m#2 zc}o7q%kSpmZwulYR6RhPQZ?t9UJW=0UZ)yHwBD8Z5sezx$ETXbZoOsLD8sNa{3l|2 z^V=VE^Kv{ml%U3a#P`UH#36+JzKMXYRUVK0Var$Oj;d4#T*$s%Lu(^h$qGT>3({huhjMPk(Kmx zc?x#si{j>*rJ&?b6o;zHBjb3*8?PJJ(d`k_1rpLG4tCb&Vm*$WOP##~f)7RSU$u?b znJP#LP?cVcuI5bN*oZO6JhG-yAhG<#9Fy{`6*Tp}R@x#gBSHJdk=f@(X`!4*Gf+lZ j;-}j3ve1X4oI(HrtuZ3d&ez8NkpNcaN6l(Yyc7NpVorDp literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Wake_up_128x64/frame_20.png b/assets/dolphin/external/L2_Wake_up_128x64/frame_20.png new file mode 100644 index 0000000000000000000000000000000000000000..2f8394fd5185736cbc37b773ae64a7b76fef5f1e GIT binary patch literal 4779 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Wake_up_128x64/frame_5.png b/assets/dolphin/external/L2_Wake_up_128x64/frame_5.png new file mode 100644 index 0000000000000000000000000000000000000000..7f980f57f8a6c7e10c63406770e7abfad0f3d1a7 GIT binary patch literal 1743 zcmV;=1~B=FP)-v7)F}{?eze9D8=ovX3bk|_{w`TxGp{WR*FqD)rsaGWH4hPeCya!kZQrWzWN-1YD zm7nKnu2IDkMvkyG1MJwWm!9vc-?CgSAZtq}(hg-mc2j24wIze63d z(|<(#dVO!d(m6VMFn~)yRXOWW9WJ8A-R;G8|n96n78t!EZubgxvpXJ}$ z18Cz!hW8@YGyal+7&xP0YCGcuS((sIhsa0PWw6x&^kGkz$zjUthW z{z&g^&z7!J**3#A2hep9RW74LYCCH%mY*pr?7r`e{^K}~FUH%DK0v3vswY>kh22JK zN?uh5)^W22G{Y;Zf6Vnp^Gq7T(#MmnOF;#QjA>^>*7r(er*S-w;0GaIT}1D)9py5I zv<{|nTj=m-4lsGG^oCI#<6qYmJAlgsW`<5JMjNT*%W))RGgO8(LzK znZ$XX`vyUi=@BVYNUDs0%q22CrhAV}5B*uvqK>gO19%FeyplhQ^lsgckU5#YE?T?{ zJbB$Ak%esz(BWb0T{rqQ(CD*hI!4pyG*Ra>&?SJW>yy!lPVy!rkdZT00;JJ*8h&Bx z%OLH|%!Z<=93c zgyT)=n#pOv!{oARdQpwN9m^;)}Nm8Xs^syt~1KwhF~500)qQOcYeF=u*cy6E{ov-9Gl zF55Q}{ZkFH#QB$oAJwu^AEjsAv3oOqWE8CVP@TGRqp}v=ESMWgq-T}>VB&?`l zHtA)8DB2>uy!1K&c32ZdGn(<({-{F#{rPZS2BEb{^RS+1DTb5@_I^K2XQl5wR58u({W;qoJ4^A_i}Z{5&PzO<^02I4M-jE;u?}L% z(b&nCWr9c!(@a|WcKF;9kO`?=S-fV@YXZG%rsUDzThBZ?mLBN`vOPq`u3l+dd{^Z9 zMR*aQpIh`xF-?a$&aNoWbj-4qmk~A`KZP&@R8o`X&(i&pA4PW7gk`iIJ@U+wix^td zYI^ydhiYVN9V3_y&^7#$5vA~N1%fE3mIF%{Z!IJ>BeJ{<5ba6p;H}rSL$t=1CSD76 zJWnbuH(X73Sx39p^z_ksc+Vpp{Er7abDMT?KHfkv49>(_0Y2Q8Ci%Eb!nLWjM(icTmz+$&Y>~U z@N~C~0M&WI36}h>6O{gXxUTb)kTt8qy92F3%O;(c{C0nE5E+0?J#6$xb)HG%li7li z$LK$m1+;vYkNSrqcY5@s;ih!#>J&At)2L=n>L+0gZFj@F59}^X40Cq!wSE#-RU#Xr zbg-*8nSh>&L=ii&4@8Dp0(Lfe8mRGET_!TnR7twC6i6UV;y(`XRINwWyUPhGd~)(> z@q1Iqr+b4RlfcE$1)Bb?`F;qVx{{YB1Bc{%Al|KLg002ovPDHLkV1j9fOVt1X literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Wake_up_128x64/frame_6.png b/assets/dolphin/external/L2_Wake_up_128x64/frame_6.png new file mode 100644 index 0000000000000000000000000000000000000000..497136000858840374438aa4481a4f3fd1a9a368 GIT binary patch literal 1630 zcmV-k2BG@qkp5sX{Bpbv;FsfZ_X2h>#%KC+p6Ao|DoDELd6x6I6EEih5G9T= zjxolQGUWFPXv&|we+pm&*x9r)@NWoGGG<91IkOHa~X=gQC3M!?QL z58^H$=&&p=^Uc#!3EJNGbyBh+m@&~PD(ZyvId1nYf zu)8nK=+T54<>MxSNb(ZRXG<&w*%ESx2Rk>Lh$2ZRQnhIG*D-BXK-cwb#PcV>YIaR% z{RBA{HH8$rqd(DLrC(_g`}LkU&g)=IQ{0Jg1}xou>5BEM%l zqgk-6^D3kb09`C{8Z(LII@cPPX)IaqDKNa{_*Xpn6PI{^-6V&74*+>yaZgco7g2E) zoW-MB5Z*ZLE)Q=AXV3Qtcu~AN-U1C_4zay}!WHW2Xa*VYv%M+q}S1{g8j(@C+ zL1B%Llw#dwFD+hQg12f6aIu9`d%i57-()x`B8}77u zo`+)A#~v_do+fKK?~y%mtYEZ-$$4_o7M)CA04-I$XPMGfDnZ*@l>BDaVm*`d+z=qK z>z=)N>vPQrBBMapZ9PX%l%(mobJRPrhG4BFGtcbzl!4xY zydHpeN?1=2&54>QUS5HCFLZD0`nvHH0yF9!kE%HVdI`njoF-E82*D)Gc*NM=y=tMw` z05M8yU(vb{U6dNa0aO7V5hg>Hh~m9Ao>^N4i~<;1Ojttqxf>RaBl$u|g#}7_$ceoo zQL5yD?kfwltn@&=7S5K1jO)*!$^g6hSxK=4MlNjGCqezA$V@+=6BRBH#je42uQS^_ z2tz=%yMLn>Y*+|8f!R~fX8t?4;&fHSakRlSpYQNN*Q^@A8tc1pXBX@$R2n-2q^$j7 z0Nt^^TH)Wtc(irC!XZ;%3(p8O)3K{GhAvOiK^LXT^hgkB^B;3TyFd~Tw9A)8wX&*D zCv#6C$5gLp^8Hh(d=%9orX$8??*=SdUoK&a6KfXJ=q~?v@!AQf#BL6gt*4_kUD>lh zah_++05V-CP4OMc;Mx^q1uo}y80Wh0)yuGJt&(556+FMMK>PV}Ojjn4dV`Kr9|7fM za3?;8iI#v zL+vb0Egq~fKp{X16lGFb!!nQpVM4&wjU^dUnOjzQ7&D##z6_EKSDZk>2q|Mm cvDpsmUs4|T-E1YKJOBUy07*qoM6N<$g3kE=fdBvi literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Wake_up_128x64/frame_7.png b/assets/dolphin/external/L2_Wake_up_128x64/frame_7.png new file mode 100644 index 0000000000000000000000000000000000000000..03d67134aacbae8d562009acb4482172150a1019 GIT binary patch literal 4722 zcmaJ@c|4R|`@hE^`<^u!V<|FbF=LtROO|X|N0P>vVPa;)SVjnGqilt&31tbTjbs^> zkS&S|*_UKblC|ITJWtQ>{p0<-=X2lpIp~JYLK1Pwf`+Gn=uBg^4gjLf2AJ#@?F2)t!TE^Z#?eyG zfD1JiAb{DP)|qj%S3|5O?X7g^x8>1s`SBP2RmOM;6(JJ$Bs3=@TS3%f25jBM2LRF= zJx#mX2FWwilVj7Q8ULm4%>}!D%QUFI{L08~-BmVrK*+XxX~1)8svjKC0(ubK=vV3v z47dQkBO4wXgWUPYdESO^BrvqM4fp5Wwr|yy-N(bryP)KCyB;x-p#Mu_j5eAO^@GLT z9SmOv&SV*8Rxk_C@0`I?Ln<8-XWH3~k<|4Wh2545=XMfRddAUJC)D=Fui&oK*h6hv z7|$G1Oy%3s#zkh&p;qM5cet;I@&n};?U=St_dIF$XGSf8*b)SAy&-^M)GF+%G;azZ zzER3 zcurG8K1HUkkTK_E^gf>3lIccLPE71a_9qX(()q$!V?vLcklOq+qar6_<^m)*Pf6yz zkgU7N&2H4nC5o5FUKaLB{0QyhlE_SChvF^JsnJD{*~?!N87IDH94&#!9A;{viLiWlq^YBYtifjqd zqVrrQErh(I zqe)O1shpHT%AG#b2@-f+HM*?xDaWcyzKg%>EDQ`YfK~OMt{_!JRcH-_4>SyTEd(xz zFU$=r1qb9B=BnkcVJX=4VD4JY+GDjBY6pY6zv{mu%rR!U*WRoN%zo}MU!#04T$A{^ z)W6gtr-eXsX(mhgBee<39m#|*emBWAJ_0K5Hb?1q^|zOC&F0I- zX16~rsmQK)6_QOE5gQjvQt4G$bEFO+=eOs1=CkHG7j%B$e<=N+{OFeL*e4>}f0%wa zB9I>35#k?EH&--4=De1-9 z-Y4(Y3bUVQ4^@1ky#L&x*Bp6o%SJt0rp@D}-jdPx<#pWnnU0Cv;_TuVtI0F)lOZSF zPHxrjw|kVMoY#+_v<|*r%tr2Iz=hgF;k)RA$tk z?YynSo9#PEKO4sw-^5pJIZlC^KvSUl80nZtU_Y=NdnWsxJs6H_9M3u7eENHh`MCMr zTn<}Q-$clc8%f4V|)=+S*;8<04jg@ZdbgoO<5ADx~ zBTY7aFWMameyzc6=WvLTB9;0~Vb#XPw12p~`_q|EFr6DYH*yLc_{r&y4}CyXAFQ?` z9XfQKf08jxc_VZE(wp^3g^i%q!M*+$ta}O{J5<^Wh+D%;4u!c(x`)+R)LR^XuQ5W} z{koyR8!H}BpZvhS=E0+nHDPWmqtHgP=i&+pjuN5jNDqfv?=bRmZ)Ce~`@{4d`*ECu z^S9HCp>K%}=X>wmF*y|LicTxN(bi6_{C@lyVJ|+-+j;49MxVROIaP@?&+>rslFFYC zj?_$#yV`p8MXWI>0|UcW)o&K&Ear;lsO9T!jrXnUPaE~Uy|~O@A+vIAO2(67HB7%9 zetIpj^Ks{!PA~?KS#a-oT0Z#gwr1gv@Z57A?JheN_o^E{DG&5FehxjmDsWzQ@8RL4 z8}oHf4u!e;)tg1GQF|9-KPO3h$<#JZM}4j_54CH~OFDb!?4BQHOLM2`H2QayR1ag#DM>T`z}Tc8+A8t6t-pvtS(R z#Wn_)!wM<+dbjJBzFaNLl|3nI-cbE3Y6&?(2yVe7x#s2P9@Jaen2fzXQS4oumuqXF z_dauov2MMc>F06Gqu$4)JI_FZzGz+Tx!$%qJd@>iy6!coRDHBUuTsxEDqv^K z;=Vg=j=0{w;Hq0m|86sPxM%6zxy{qezMv03Kd5`$V$?6ao|mbnDu3Qrqg1>0GvvpD zL@3Xq^^iw+QrP|NhJl^0do~5Xo_oAoMPyP&f1YBV>VD}axFo=RDR1}2uXmTmdd)k` z`xK5STuaK8lat|>uK(uUb$zV(VX(4~&Zo}8( zR=1*cl!RHgN-4S-j_yb!(L?cpM8L?K=0${9Qt&=R2O{2^ai)!^3jpAYWG5UQXJd^e z&?xHo-!kf9lmM1C0O%To1>gyOL^{Na=tHLJL6@F4LLp>tJ*X?j24NFmLi8nDga;BG z!)=`i;eG@iZ>WJjL^lk}GC(2H@sKczKQ#y&rU(7YE|#_aO@>1ue~Hlj^q_x(!r9nE zOlW~bh?Y7EMnE7C5G@^bq?aZJuchOKR)e4rC=?u_2}f$cP$(<{$$B9F3@FQCpf?HY zV0z>qcdVTr)R#^Vz{267p`q%b8tSw_A2?D+M+c5T!BHp}O9B?epwjVSFlvy(9}A|$ zAVMHHfKH}SA-^r+y=cL7Jtzz8-%(HkY;68DObz-cSFEhT!|(xcq&fmlq5KZ*FXOol?b#Jma7JA7V0+#qp1yrvmnBMSI&P_(jOA5Jby3$Z4K7u z-{vAxSsfL~YMp0)kk(QR0lau4*S4SS%SC8DU}Jjg1XnUf%Wfb#``k zv!HNER_nj9G&OPx8<=hXWv!VAg|^%n$I$7iMZEaO-54!0K@ma%I^ZVZR#D*@E%ul+?r$=kW# z8hu~h0TxwD2Kx{A09;=~8X|a@f?(i<7(Wp5JW97B#Jnd{)odye&wYX>BUTgOSlcQ?S_$bJ!@SzJ4Fhp zQ3<%PwjwV#%qV%gYINS&T|{5Xu2oGryFUFPM2P9u(bXhL^lIgPRYC~yIFQ#f7QW)# zZjPD;#ma4kL&rO;X1jS!)ByEcU+A*D1^cqY-P>$LUc71VZoB9~{rP-wZBaI%Wo!58 zH;5WvX9SQHHgfm=t@SOA7v~n&pOeQjxmIITR1UOtr`XQ-Z#bbXCk~LU!Rd&eP0Ac#vf(|u>)%Spt;pld!~XSir=3E z4*q47Xo~1TxI}t_rsK{m`rVm0iUq;bBuVkT`?IbrpMX|n-ZEW0j_MfJ_;UGOEPJ}3 zW$Jxja#t1nUbOR@5|vx;LCt8~;Q8bv{)Ld#v(%5toTy{(jfB%L(u>DPT&ho0@^5QL zO$G)K0Q)K1&xfxpxh?Vn+p*}Fy0any$_;`2mDB?64WgrHV`?ee0g({!mt}`1u}|gE z{L1?^fEwjA8AXzC^x)1UCJtxb%2|yOx1W!nKB`eRg|9T1yVF#wJSta~2Af-NmOADl zCdePvRc5u4E;%P%96&RS zV0RkPZ8O$2B{p19g$F+oV#@6v%JUqKmYFJin)sn+Dld6oc)WT~MJxYTbvR9;zPCn= zgJ`GC&O>s|z%_X4ygy0tSAO|f^u?0xy^y2>p20~G8!7x^FvX?WC%Y#$vv<=BE`XMW z_~d~+Mf7kgdytbKn=CAAoMohNBb`KzXFp}6IMWvw0UkRt^uXCKN}hCW5}lCBFmUq! zkSZT_)U3*?WLJ3HmTZ#+D%?zz2wN=`KFaq5rZ|y<14ewEfS1>V{)_rq-?R*)LZ-L7aflWcG9|UpSTZhX5?i LY)vbTPsRQpgF-@k literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Wake_up_128x64/frame_8.png b/assets/dolphin/external/L2_Wake_up_128x64/frame_8.png new file mode 100644 index 0000000000000000000000000000000000000000..9f523caceed6287320d581b76312d2036b6ad27e GIT binary patch literal 4731 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 JNdM~1{{w%hGqC^w literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Wake_up_128x64/meta.txt b/assets/dolphin/external/L2_Wake_up_128x64/meta.txt new file mode 100644 index 00000000000..06c710f0390 --- /dev/null +++ b/assets/dolphin/external/L2_Wake_up_128x64/meta.txt @@ -0,0 +1,14 @@ +Filetype: Flipper Animation +Version: 1 + +Width: 128 +Height: 64 +Passive frames: 10 +Active frames: 18 +Frames order: 0 1 0 1 0 1 0 2 3 4 0 5 6 7 8 9 10 11 10 12 13 14 15 16 17 18 19 20 +Active cycles: 1 +Frame rate: 2 +Duration: 3600 +Active cooldown: 7 + +Bubble slots: 0 \ No newline at end of file diff --git a/assets/dolphin/external/manifest.txt b/assets/dolphin/external/manifest.txt index a6c7ca694d9..7f03d659530 100644 --- a/assets/dolphin/external/manifest.txt +++ b/assets/dolphin/external/manifest.txt @@ -36,19 +36,12 @@ Min level: 1 Max level: 1 Weight: 3 -Name: L2_Furippa2_128x64 +Name: L2_Wake_up_128x64 Min butthurt: 0 -Max butthurt: 6 +Max butthurt: 12 Min level: 2 -Max level: 2 -Weight: 3 - -Name: L3_Furippa3_128x64 -Min butthurt: 0 -Max butthurt: 6 -Min level: 3 Max level: 3 -Weight: 3 +Weight: 4 Name: L1_Read_books_128x64 Min butthurt: 0 @@ -57,13 +50,6 @@ Min level: 1 Max level: 1 Weight: 3 -Name: L2_Hacking_pc_128x64 -Min butthurt: 0 -Max butthurt: 8 -Min level: 2 -Max level: 2 -Weight: 3 - Name: L1_Cry_128x64 Min butthurt: 8 Max butthurt: 13 @@ -90,27 +76,34 @@ Min butthurt: 0 Max butthurt: 9 Min level: 1 Max level: 3 -Weight: 5 +Weight: 4 Name: L1_Painting_128x64 Min butthurt: 0 Max butthurt: 7 Min level: 1 Max level: 3 -Weight: 4 +Weight: 3 -Name: L3_Hijack_radio_128x64 -Min butthurt: 0 -Max butthurt: 8 -Min level: 3 +Name: L1_Leaving_sad_128x64 +Min butthurt: 14 +Max butthurt: 14 +Min level: 1 Max level: 3 Weight: 3 -Name: L3_Lab_research_128x54 +Name: L2_Furippa2_128x64 Min butthurt: 0 -Max butthurt: 10 -Min level: 3 -Max level: 3 +Max butthurt: 6 +Min level: 2 +Max level: 2 +Weight: 3 + +Name: L2_Hacking_pc_128x64 +Min butthurt: 0 +Max butthurt: 8 +Min level: 2 +Max level: 2 Weight: 3 Name: L2_Soldering_128x64 @@ -120,9 +113,23 @@ Min level: 2 Max level: 2 Weight: 3 -Name: L1_Leaving_sad_128x64 -Min butthurt: 14 -Max butthurt: 14 -Min level: 1 +Name: L3_Furippa3_128x64 +Min butthurt: 0 +Max butthurt: 6 +Min level: 3 +Max level: 3 +Weight: 3 + +Name: L3_Hijack_radio_128x64 +Min butthurt: 0 +Max butthurt: 8 +Min level: 3 +Max level: 3 +Weight: 3 + +Name: L3_Lab_research_128x54 +Min butthurt: 0 +Max butthurt: 10 +Min level: 3 Max level: 3 Weight: 3 From cbc5e3fa92b6efcf1dcae643a2640bcacad9c7fe Mon Sep 17 00:00:00 2001 From: Dmitry Zinin Date: Wed, 7 Dec 2022 12:50:44 +0300 Subject: [PATCH 259/824] IR TV Universal: Toshiba TV (#2084) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From Toshiba TV 32W1534DG (C) Co-authored-by: あく --- assets/resources/infrared/assets/tv.ir | 36 ++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/assets/resources/infrared/assets/tv.ir b/assets/resources/infrared/assets/tv.ir index 8945465c7c6..3965013da5b 100755 --- a/assets/resources/infrared/assets/tv.ir +++ b/assets/resources/infrared/assets/tv.ir @@ -1620,3 +1620,39 @@ type: raw frequency: 38000 duty_cycle: 0.33 data: 7847 3931 470 1448 467 495 472 1443 472 490 467 1451 464 491 466 499 468 491 466 4413 467 1455 470 486 471 1449 466 495 472 1441 464 501 466 492 465 494 463 22093 7851 3934 467 1454 471 490 467 1446 469 496 471 1446 469 489 468 494 473 484 473 4410 470 1449 466 489 468 1455 470 489 468 1449 466 496 471 486 471 490 467 +# +name: Power +type: parsed +protocol: RC5 +address: 01 00 00 00 +command: 0C 00 00 00 +# +name: Mute +type: parsed +protocol: RC5 +address: 01 00 00 00 +command: 0D 00 00 00 +# +name: Vol_up +type: parsed +protocol: RC5 +address: 01 00 00 00 +command: 10 00 00 00 +# +name: Vol_dn +type: parsed +protocol: RC5 +address: 01 00 00 00 +command: 11 00 00 00 +# +name: Ch_next +type: parsed +protocol: RC5 +address: 01 00 00 00 +command: 20 00 00 00 +# +name: Ch_prev +type: parsed +protocol: RC5 +address: 01 00 00 00 +command: 21 00 00 00 From c43ec414bb8943d84582a2c5e396bff3071019f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Wed, 7 Dec 2022 19:43:40 +0900 Subject: [PATCH 260/824] FuriHal: add i2c unit tests (#2089) * FuriHal: add i2c unit tests * FuriHal: restructure unit tests Co-authored-by: Sergey Gavrilov --- .../unit_tests/furi_hal/furi_hal_tests.c | 116 ++++++++++++++++++ applications/debug/unit_tests/test_index.c | 2 + 2 files changed, 118 insertions(+) create mode 100644 applications/debug/unit_tests/furi_hal/furi_hal_tests.c diff --git a/applications/debug/unit_tests/furi_hal/furi_hal_tests.c b/applications/debug/unit_tests/furi_hal/furi_hal_tests.c new file mode 100644 index 00000000000..e942c5933b0 --- /dev/null +++ b/applications/debug/unit_tests/furi_hal/furi_hal_tests.c @@ -0,0 +1,116 @@ +#include +#include +#include +#include +#include "../minunit.h" + +#define DATA_SIZE 4 + +static void furi_hal_i2c_int_setup() { + furi_hal_i2c_acquire(&furi_hal_i2c_handle_power); +} + +static void furi_hal_i2c_int_teardown() { + furi_hal_i2c_release(&furi_hal_i2c_handle_power); +} + +MU_TEST(furi_hal_i2c_int_1b) { + bool ret = false; + uint8_t data_one = 0; + + // 1 byte: read, write, read + ret = furi_hal_i2c_read_reg_8( + &furi_hal_i2c_handle_power, + LP5562_ADDRESS, + LP5562_CHANNEL_BLUE_CURRENT_REGISTER, + &data_one, + LP5562_I2C_TIMEOUT); + mu_assert(ret, "0 read_reg_8 failed"); + mu_assert(data_one != 0, "0 invalid data"); + ret = furi_hal_i2c_write_reg_8( + &furi_hal_i2c_handle_power, + LP5562_ADDRESS, + LP5562_CHANNEL_BLUE_CURRENT_REGISTER, + data_one, + LP5562_I2C_TIMEOUT); + mu_assert(ret, "1 write_reg_8 failed"); + ret = furi_hal_i2c_read_reg_8( + &furi_hal_i2c_handle_power, + LP5562_ADDRESS, + LP5562_CHANNEL_BLUE_CURRENT_REGISTER, + &data_one, + LP5562_I2C_TIMEOUT); + mu_assert(ret, "2 read_reg_8 failed"); + mu_assert(data_one != 0, "2 invalid data"); +} + +MU_TEST(furi_hal_i2c_int_3b) { + bool ret = false; + uint8_t data_many[DATA_SIZE] = {0}; + + // 3 byte: read, write, read + data_many[0] = LP5562_CHANNEL_BLUE_CURRENT_REGISTER; + ret = furi_hal_i2c_tx( + &furi_hal_i2c_handle_power, LP5562_ADDRESS, data_many, 1, LP5562_I2C_TIMEOUT); + mu_assert(ret, "3 tx failed"); + ret = furi_hal_i2c_rx( + &furi_hal_i2c_handle_power, + LP5562_ADDRESS, + data_many + 1, + DATA_SIZE - 1, + LP5562_I2C_TIMEOUT); + mu_assert(ret, "4 rx failed"); + for(size_t i = 0; i < DATA_SIZE; i++) mu_assert(data_many[i] != 0, "4 invalid data_many"); + + ret = furi_hal_i2c_tx( + &furi_hal_i2c_handle_power, LP5562_ADDRESS, data_many, DATA_SIZE, LP5562_I2C_TIMEOUT); + mu_assert(ret, "5 tx failed"); + + ret = furi_hal_i2c_tx( + &furi_hal_i2c_handle_power, LP5562_ADDRESS, data_many, 1, LP5562_I2C_TIMEOUT); + mu_assert(ret, "6 tx failed"); + ret = furi_hal_i2c_rx( + &furi_hal_i2c_handle_power, + LP5562_ADDRESS, + data_many + 1, + DATA_SIZE - 1, + LP5562_I2C_TIMEOUT); + mu_assert(ret, "7 rx failed"); + for(size_t i = 0; i < DATA_SIZE; i++) mu_assert(data_many[i] != 0, "7 invalid data_many"); +} + +MU_TEST(furi_hal_i2c_int_1b_fail) { + bool ret = false; + uint8_t data_one = 0; + + // 1 byte: fail, read, fail, write, fail, read + data_one = 0; + ret = furi_hal_i2c_read_reg_8( + &furi_hal_i2c_handle_power, + LP5562_ADDRESS + 0x10, + LP5562_CHANNEL_BLUE_CURRENT_REGISTER, + &data_one, + LP5562_I2C_TIMEOUT); + mu_assert(!ret, "8 read_reg_8 failed"); + mu_assert(data_one == 0, "8 invalid data"); + ret = furi_hal_i2c_read_reg_8( + &furi_hal_i2c_handle_power, + LP5562_ADDRESS, + LP5562_CHANNEL_BLUE_CURRENT_REGISTER, + &data_one, + LP5562_I2C_TIMEOUT); + mu_assert(ret, "9 read_reg_8 failed"); + mu_assert(data_one != 0, "9 invalid data"); +} + +MU_TEST_SUITE(furi_hal_i2c_int_suite) { + MU_SUITE_CONFIGURE(&furi_hal_i2c_int_setup, &furi_hal_i2c_int_teardown); + MU_RUN_TEST(furi_hal_i2c_int_1b); + MU_RUN_TEST(furi_hal_i2c_int_3b); + MU_RUN_TEST(furi_hal_i2c_int_1b_fail); +} + +int run_minunit_test_furi_hal() { + MU_RUN_SUITE(furi_hal_i2c_int_suite); + return MU_EXIT_CODE; +} diff --git a/applications/debug/unit_tests/test_index.c b/applications/debug/unit_tests/test_index.c index 2009d4a5bad..b8760b1f08e 100644 --- a/applications/debug/unit_tests/test_index.c +++ b/applications/debug/unit_tests/test_index.c @@ -9,6 +9,7 @@ #define TAG "UnitTests" int run_minunit_test_furi(); +int run_minunit_test_furi_hal(); int run_minunit_test_furi_string(); int run_minunit_test_infrared(); int run_minunit_test_rpc(); @@ -32,6 +33,7 @@ typedef struct { const UnitTest unit_tests[] = { {.name = "furi", .entry = run_minunit_test_furi}, + {.name = "furi_hal", .entry = run_minunit_test_furi_hal}, {.name = "furi_string", .entry = run_minunit_test_furi_string}, {.name = "storage", .entry = run_minunit_test_storage}, {.name = "stream", .entry = run_minunit_test_stream}, From 9a21dae29c8d28799cc31748bf825622f0b762bc Mon Sep 17 00:00:00 2001 From: gornekich Date: Wed, 7 Dec 2022 14:52:44 +0400 Subject: [PATCH 261/824] [FL-3008], [FL-2734], [FL-2766], [FL-2898] NFC bug fixes (#2098) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * nfc: rework mf classic update * nfc: rename cache folder to .cache * nfc: fix ATQA order bytes in nfc files * file browser: add hide dot files option * nfc: fix iso-14443-4 uid cards emulation * nfc: fix unit tests Co-authored-by: あく --- applications/debug/unit_tests/nfc/nfc_test.c | 2 +- .../main/archive/helpers/archive_browser.c | 10 +++-- applications/services/dialogs/dialogs.h | 2 + applications/services/dialogs/dialogs_api.c | 1 + .../services/dialogs/dialogs_message.h | 1 + .../dialogs/dialogs_module_file_browser.c | 7 +++- .../services/gui/modules/file_browser.c | 6 ++- .../services/gui/modules/file_browser.h | 1 + .../gui/modules/file_browser_worker.c | 20 +++++++-- .../gui/modules/file_browser_worker.h | 10 +++-- firmware/targets/f7/api_symbols.csv | 8 ++-- firmware/targets/f7/furi_hal/furi_hal_nfc.c | 3 ++ lib/nfc/nfc_device.c | 35 ++++++++++++---- lib/nfc/protocols/mifare_classic.c | 42 +++---------------- 14 files changed, 88 insertions(+), 60 deletions(-) diff --git a/applications/debug/unit_tests/nfc/nfc_test.c b/applications/debug/unit_tests/nfc/nfc_test.c index 07ec73a0317..4218482c7af 100644 --- a/applications/debug/unit_tests/nfc/nfc_test.c +++ b/applications/debug/unit_tests/nfc/nfc_test.c @@ -393,7 +393,7 @@ static void mf_classic_generator_test(uint8_t uid_len, MfClassicType type) { "nfc_device_save == true assert failed\r\n"); // Verify that key cache is saved FuriString* key_cache_name = furi_string_alloc(); - furi_string_set_str(key_cache_name, "/ext/nfc/cache/"); + furi_string_set_str(key_cache_name, "/ext/nfc/.cache/"); for(size_t i = 0; i < uid_len; i++) { furi_string_cat_printf(key_cache_name, "%02X", uid[i]); } diff --git a/applications/main/archive/helpers/archive_browser.c b/applications/main/archive/helpers/archive_browser.c index 149b3908987..fa705189f5e 100644 --- a/applications/main/archive/helpers/archive_browser.c +++ b/applications/main/archive/helpers/archive_browser.c @@ -80,10 +80,11 @@ static void archive_file_browser_set_path( ArchiveBrowserView* browser, FuriString* path, const char* filter_ext, - bool skip_assets) { + bool skip_assets, + bool hide_dot_files) { furi_assert(browser); if(!browser->worker_running) { - browser->worker = file_browser_worker_alloc(path, filter_ext, skip_assets); + browser->worker = file_browser_worker_alloc(path, filter_ext, skip_assets, hide_dot_files); file_browser_worker_set_callback_context(browser->worker, browser); file_browser_worker_set_folder_callback(browser->worker, archive_folder_open_cb); file_browser_worker_set_list_callback(browser->worker, archive_list_load_cb); @@ -92,7 +93,8 @@ static void archive_file_browser_set_path( browser->worker_running = true; } else { furi_assert(browser->worker); - file_browser_worker_set_config(browser->worker, path, filter_ext, skip_assets); + file_browser_worker_set_config( + browser->worker, path, filter_ext, skip_assets, hide_dot_files); } } @@ -473,7 +475,7 @@ void archive_switch_tab(ArchiveBrowserView* browser, InputKey key) { if(archive_is_dir_exists(browser->path)) { bool skip_assets = (strcmp(archive_get_tab_ext(tab), "*") == 0) ? false : true; archive_file_browser_set_path( - browser, browser->path, archive_get_tab_ext(tab), skip_assets); + browser, browser->path, archive_get_tab_ext(tab), skip_assets, false); tab_empty = false; // Empty check will be performed later } } diff --git a/applications/services/dialogs/dialogs.h b/applications/services/dialogs/dialogs.h index 4e836e3655f..97783eb0d6a 100644 --- a/applications/services/dialogs/dialogs.h +++ b/applications/services/dialogs/dialogs.h @@ -19,6 +19,7 @@ typedef struct DialogsApp DialogsApp; * File browser dialog extra options * @param extension file extension to be offered for selection * @param skip_assets true - do not show assets folders + * @param hide_dot_files true - hide dot files * @param icon file icon pointer, NULL for default icon * @param hide_ext true - hide extensions for files * @param item_loader_callback callback function for providing custom icon & entry name @@ -27,6 +28,7 @@ typedef struct DialogsApp DialogsApp; typedef struct { const char* extension; bool skip_assets; + bool hide_dot_files; const Icon* icon; bool hide_ext; FileBrowserLoadItemCallback item_loader_callback; diff --git a/applications/services/dialogs/dialogs_api.c b/applications/services/dialogs/dialogs_api.c index c1de599930d..91b3a1d04fe 100644 --- a/applications/services/dialogs/dialogs_api.c +++ b/applications/services/dialogs/dialogs_api.c @@ -20,6 +20,7 @@ bool dialog_file_browser_show( .file_icon = options ? options->icon : NULL, .hide_ext = options ? options->hide_ext : true, .skip_assets = options ? options->skip_assets : true, + .hide_dot_files = options ? options->hide_dot_files : true, .preselected_filename = path, .item_callback = options ? options->item_loader_callback : NULL, .item_callback_context = options ? options->item_loader_context : NULL, diff --git a/applications/services/dialogs/dialogs_message.h b/applications/services/dialogs/dialogs_message.h index d01c5aa9c1d..45d36ba171f 100644 --- a/applications/services/dialogs/dialogs_message.h +++ b/applications/services/dialogs/dialogs_message.h @@ -11,6 +11,7 @@ typedef struct { const char* extension; bool skip_assets; bool hide_ext; + bool hide_dot_files; const Icon* file_icon; FuriString* result_path; FuriString* preselected_filename; diff --git a/applications/services/dialogs/dialogs_module_file_browser.c b/applications/services/dialogs/dialogs_module_file_browser.c index 7e67d6c6006..e03434cf5cb 100644 --- a/applications/services/dialogs/dialogs_module_file_browser.c +++ b/applications/services/dialogs/dialogs_module_file_browser.c @@ -38,7 +38,12 @@ bool dialogs_app_process_module_file_browser(const DialogsAppMessageDataFileBrow 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, + data->extension, + data->skip_assets, + data->hide_dot_files, + data->file_icon, + data->hide_ext); file_browser_set_item_callback(file_browser, data->item_callback, data->item_callback_context); file_browser_start(file_browser, data->preselected_filename); diff --git a/applications/services/gui/modules/file_browser.c b/applications/services/gui/modules/file_browser.c index 203be23ed6f..f20f950fac1 100644 --- a/applications/services/gui/modules/file_browser.c +++ b/applications/services/gui/modules/file_browser.c @@ -84,6 +84,7 @@ struct FileBrowser { BrowserWorker* worker; const char* ext_filter; bool skip_assets; + bool hide_dot_files; bool hide_ext; FileBrowserCallback callback; @@ -163,6 +164,7 @@ void file_browser_configure( FileBrowser* browser, const char* extension, bool skip_assets, + bool hide_dot_files, const Icon* file_icon, bool hide_ext) { furi_assert(browser); @@ -170,6 +172,7 @@ void file_browser_configure( browser->ext_filter = extension; browser->skip_assets = skip_assets; browser->hide_ext = hide_ext; + browser->hide_dot_files = hide_dot_files; with_view_model( browser->view, @@ -183,7 +186,8 @@ void file_browser_configure( void file_browser_start(FileBrowser* browser, FuriString* path) { furi_assert(browser); - browser->worker = file_browser_worker_alloc(path, browser->ext_filter, browser->skip_assets); + browser->worker = file_browser_worker_alloc( + path, browser->ext_filter, browser->skip_assets, browser->hide_dot_files); 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); diff --git a/applications/services/gui/modules/file_browser.h b/applications/services/gui/modules/file_browser.h index c9fdddb55d7..377d4d9bc75 100644 --- a/applications/services/gui/modules/file_browser.h +++ b/applications/services/gui/modules/file_browser.h @@ -30,6 +30,7 @@ void file_browser_configure( FileBrowser* browser, const char* extension, bool skip_assets, + bool hide_dot_files, const Icon* file_icon, bool hide_ext); diff --git a/applications/services/gui/modules/file_browser_worker.c b/applications/services/gui/modules/file_browser_worker.c index a85a14b75f0..fb45a384e01 100644 --- a/applications/services/gui/modules/file_browser_worker.c +++ b/applications/services/gui/modules/file_browser_worker.c @@ -42,6 +42,7 @@ struct BrowserWorker { uint32_t load_offset; uint32_t load_count; bool skip_assets; + bool hide_dot_files; idx_last_array_t idx_last; void* cb_ctx; @@ -76,6 +77,13 @@ static bool browser_path_trim(FuriString* path) { } static bool browser_filter_by_name(BrowserWorker* browser, FuriString* name, bool is_folder) { + // Skip dot files if enabled + if(browser->hide_dot_files) { + if(furi_string_start_with_str(name, ".")) { + return false; + } + } + if(is_folder) { // Skip assets folders (if enabled) if(browser->skip_assets) { @@ -361,14 +369,18 @@ static int32_t browser_worker(void* context) { return 0; } -BrowserWorker* - file_browser_worker_alloc(FuriString* path, const char* filter_ext, bool skip_assets) { +BrowserWorker* file_browser_worker_alloc( + FuriString* path, + const char* filter_ext, + bool skip_assets, + bool hide_dot_files) { BrowserWorker* browser = malloc(sizeof(BrowserWorker)); //-V773 idx_last_array_init(browser->idx_last); browser->filter_extension = furi_string_alloc_set(filter_ext); browser->skip_assets = skip_assets; + browser->hide_dot_files = hide_dot_files; browser->path_start = furi_string_alloc_set(path); browser->path_current = furi_string_alloc_set(path); browser->path_next = furi_string_alloc_set(path); @@ -437,11 +449,13 @@ void file_browser_worker_set_config( BrowserWorker* browser, FuriString* path, const char* filter_ext, - bool skip_assets) { + bool skip_assets, + bool hide_dot_files) { furi_assert(browser); furi_string_set(browser->path_next, path); furi_string_set(browser->filter_extension, filter_ext); browser->skip_assets = skip_assets; + browser->hide_dot_files = hide_dot_files; furi_thread_flags_set(furi_thread_get_id(browser->thread), WorkerEvtConfigChange); } diff --git a/applications/services/gui/modules/file_browser_worker.h b/applications/services/gui/modules/file_browser_worker.h index 2f815540173..ca143d66092 100644 --- a/applications/services/gui/modules/file_browser_worker.h +++ b/applications/services/gui/modules/file_browser_worker.h @@ -21,8 +21,11 @@ typedef void (*BrowserWorkerListItemCallback)( bool is_last); typedef void (*BrowserWorkerLongLoadCallback)(void* context); -BrowserWorker* - file_browser_worker_alloc(FuriString* path, const char* filter_ext, bool skip_assets); +BrowserWorker* file_browser_worker_alloc( + FuriString* path, + const char* filter_ext, + bool skip_assets, + bool hide_dot_files); void file_browser_worker_free(BrowserWorker* browser); @@ -48,7 +51,8 @@ void file_browser_worker_set_config( BrowserWorker* browser, FuriString* path, const char* filter_ext, - bool skip_assets); + bool skip_assets, + bool hide_dot_files); void file_browser_worker_folder_enter(BrowserWorker* browser, FuriString* path, int32_t item_idx); diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 0f00ca908fe..38f21902d21 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,8.1,, +Version,+,9.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -806,14 +806,14 @@ Function,-,fgetpos,int,"FILE*, fpos_t*" Function,-,fgets,char*,"char*, int, FILE*" Function,-,fgets_unlocked,char*,"char*, int, FILE*" Function,+,file_browser_alloc,FileBrowser*,FuriString* -Function,+,file_browser_configure,void,"FileBrowser*, const char*, _Bool, const Icon*, _Bool" +Function,+,file_browser_configure,void,"FileBrowser*, const char*, _Bool, _Bool, const Icon*, _Bool" Function,+,file_browser_free,void,FileBrowser* Function,+,file_browser_get_view,View*,FileBrowser* Function,+,file_browser_set_callback,void,"FileBrowser*, FileBrowserCallback, void*" Function,+,file_browser_set_item_callback,void,"FileBrowser*, FileBrowserLoadItemCallback, void*" Function,+,file_browser_start,void,"FileBrowser*, FuriString*" Function,+,file_browser_stop,void,FileBrowser* -Function,+,file_browser_worker_alloc,BrowserWorker*,"FuriString*, const char*, _Bool" +Function,+,file_browser_worker_alloc,BrowserWorker*,"FuriString*, const char*, _Bool, _Bool" Function,+,file_browser_worker_folder_enter,void,"BrowserWorker*, FuriString*, int32_t" Function,+,file_browser_worker_folder_exit,void,BrowserWorker* Function,+,file_browser_worker_folder_refresh,void,"BrowserWorker*, int32_t" @@ -821,7 +821,7 @@ Function,+,file_browser_worker_free,void,BrowserWorker* Function,+,file_browser_worker_is_in_start_folder,_Bool,BrowserWorker* Function,+,file_browser_worker_load,void,"BrowserWorker*, uint32_t, uint32_t" Function,+,file_browser_worker_set_callback_context,void,"BrowserWorker*, void*" -Function,+,file_browser_worker_set_config,void,"BrowserWorker*, FuriString*, const char*, _Bool" +Function,+,file_browser_worker_set_config,void,"BrowserWorker*, FuriString*, const char*, _Bool, _Bool" Function,+,file_browser_worker_set_folder_callback,void,"BrowserWorker*, BrowserWorkerFolderOpenCallback" Function,+,file_browser_worker_set_item_callback,void,"BrowserWorker*, BrowserWorkerListItemCallback" Function,+,file_browser_worker_set_list_callback,void,"BrowserWorker*, BrowserWorkerListLoadCallback" diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc.c b/firmware/targets/f7/furi_hal/furi_hal_nfc.c index 2d27313ae5e..75c695afb9e 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_nfc.c +++ b/firmware/targets/f7/furi_hal/furi_hal_nfc.c @@ -244,6 +244,9 @@ bool furi_hal_nfc_listen( params.lmConfigPA.SEL_RES = sak; rfalNfcDiscover(¶ms); + // Disable EMD suppression. + st25r3916ModifyRegister(ST25R3916_REG_EMD_SUP_CONF, ST25R3916_REG_EMD_SUP_CONF_emd_emv, 0); + uint32_t start = DWT->CYCCNT; while(state != RFAL_NFC_STATE_ACTIVATED) { rfalNfcWorker(); diff --git a/lib/nfc/nfc_device.c b/lib/nfc/nfc_device.c index dc1faa34cec..e18f094590d 100644 --- a/lib/nfc/nfc_device.c +++ b/lib/nfc/nfc_device.c @@ -8,11 +8,11 @@ #include #define TAG "NfcDevice" -#define NFC_DEVICE_KEYS_FOLDER EXT_PATH("nfc/cache") +#define NFC_DEVICE_KEYS_FOLDER EXT_PATH("nfc/.cache") #define NFC_DEVICE_KEYS_EXTENSION ".keys" static const char* nfc_file_header = "Flipper NFC device"; -static const uint32_t nfc_file_version = 2; +static const uint32_t nfc_file_version = 3; static const char* nfc_keys_file_header = "Flipper NFC keys"; static const uint32_t nfc_keys_file_version = 1; @@ -27,6 +27,11 @@ NfcDevice* nfc_device_alloc() { nfc_dev->dialogs = furi_record_open(RECORD_DIALOGS); nfc_dev->load_path = furi_string_alloc(); nfc_dev->dev_data.parsed_data = furi_string_alloc(); + + // Rename cache folder name for backward compatibility + if(storage_common_stat(nfc_dev->storage, "/ext/nfc/cache", NULL) == FSE_OK) { + storage_common_rename(nfc_dev->storage, "/ext/nfc/cache", NFC_DEVICE_KEYS_FOLDER); + } return nfc_dev; } @@ -1041,7 +1046,9 @@ bool nfc_device_save(NfcDevice* dev, const char* dev_name) { if(!flipper_format_write_comment_cstr(file, "UID, ATQA and SAK are common for all formats")) break; if(!flipper_format_write_hex(file, "UID", data->uid, data->uid_len)) break; - if(!flipper_format_write_hex(file, "ATQA", data->atqa, 2)) break; + // Save ATQA in MSB order for correct companion apps display + uint8_t atqa[2] = {data->atqa[1], data->atqa[0]}; + if(!flipper_format_write_hex(file, "ATQA", atqa, 2)) break; if(!flipper_format_write_hex(file, "SAK", &data->sak, 1)) break; // Save more data if necessary if(dev->format == NfcDeviceSaveFormatMifareUl) { @@ -1089,6 +1096,9 @@ static bool nfc_device_load_data(NfcDevice* dev, FuriString* path, bool show_dia temp_str = furi_string_alloc(); bool deprecated_version = false; + // Version 2 of file format had ATQA bytes swapped + uint32_t version_with_lsb_atqa = 2; + if(dev->loading_cb) { dev->loading_cb(dev->loading_cb_ctx, true); } @@ -1107,9 +1117,12 @@ static bool nfc_device_load_data(NfcDevice* dev, FuriString* path, bool show_dia // Read and verify file header uint32_t version = 0; if(!flipper_format_read_header(file, temp_str, &version)) break; - if(furi_string_cmp_str(temp_str, nfc_file_header) || (version != nfc_file_version)) { - deprecated_version = true; - break; + if(furi_string_cmp_str(temp_str, nfc_file_header)) break; + if(version != nfc_file_version) { + if(version < version_with_lsb_atqa) { + deprecated_version = true; + break; + } } // Read Nfc device type if(!flipper_format_read_string(file, "Device type", temp_str)) break; @@ -1119,7 +1132,14 @@ static bool nfc_device_load_data(NfcDevice* dev, FuriString* path, bool show_dia if(!(data_cnt == 4 || data_cnt == 7)) break; data->uid_len = data_cnt; if(!flipper_format_read_hex(file, "UID", data->uid, data->uid_len)) break; - if(!flipper_format_read_hex(file, "ATQA", data->atqa, 2)) break; + if(version == version_with_lsb_atqa) { + if(!flipper_format_read_hex(file, "ATQA", data->atqa, 2)) break; + } else { + uint8_t atqa[2] = {}; + if(!flipper_format_read_hex(file, "ATQA", atqa, 2)) break; + data->atqa[0] = atqa[1]; + data->atqa[1] = atqa[0]; + } if(!flipper_format_read_hex(file, "SAK", &data->sak, 1)) break; // Load CUID uint8_t* cuid_start = data->uid; @@ -1187,6 +1207,7 @@ bool nfc_file_select(NfcDevice* dev) { const DialogsFileBrowserOptions browser_options = { .extension = NFC_APP_EXTENSION, .skip_assets = true, + .hide_dot_files = true, .icon = &I_Nfc_10px, .hide_ext = true, .item_loader_callback = NULL, diff --git a/lib/nfc/protocols/mifare_classic.c b/lib/nfc/protocols/mifare_classic.c index b7a52bc01ee..f4c7353a86e 100644 --- a/lib/nfc/protocols/mifare_classic.c +++ b/lib/nfc/protocols/mifare_classic.c @@ -712,46 +712,16 @@ uint8_t mf_classic_update_card(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data furi_assert(tx_rx); furi_assert(data); - uint8_t sectors_read = 0; - Crypto1 crypto = {}; uint8_t total_sectors = mf_classic_get_total_sectors_num(data->type); - uint64_t key_a = 0; - uint64_t key_b = 0; - MfClassicSectorReader sec_reader = {}; - MfClassicSector temp_sector = {}; for(size_t i = 0; i < total_sectors; i++) { - MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, i); - // Load key A - if(mf_classic_is_key_found(data, i, MfClassicKeyA)) { - sec_reader.key_a = nfc_util_bytes2num(sec_tr->key_a, 6); - } else { - sec_reader.key_a = MF_CLASSIC_NO_KEY; - } - // Load key B - if(mf_classic_is_key_found(data, i, MfClassicKeyB)) { - sec_reader.key_b = nfc_util_bytes2num(sec_tr->key_b, 6); - } else { - sec_reader.key_b = MF_CLASSIC_NO_KEY; - } - if((key_a != MF_CLASSIC_NO_KEY) || (key_b != MF_CLASSIC_NO_KEY)) { - sec_reader.sector_num = i; - if(mf_classic_read_sector_with_reader(tx_rx, &crypto, &sec_reader, &temp_sector)) { - uint8_t first_block = mf_classic_get_first_block_num_of_sector(i); - for(uint8_t j = 0; j < temp_sector.total_blocks; j++) { - mf_classic_set_block_read(data, first_block + j, &temp_sector.block[j]); - } - sectors_read++; - } else { - // Invalid key, set it to not found - if(key_a != MF_CLASSIC_NO_KEY) { - mf_classic_set_key_not_found(data, i, MfClassicKeyA); - } else { - mf_classic_set_key_not_found(data, i, MfClassicKeyB); - } - } - } + mf_classic_read_sector(tx_rx, data, i); } + uint8_t sectors_read = 0; + uint8_t keys_found = 0; + mf_classic_get_read_sectors_and_keys(data, §ors_read, &keys_found); + FURI_LOG_D(TAG, "Read %d sectors and %d keys", sectors_read, keys_found); + return sectors_read; } From e42dda7cfb43a561b66bd60f9798ec864cc3f6fc Mon Sep 17 00:00:00 2001 From: 0xchocolate <109879152+0xchocolate@users.noreply.github.com> Date: Wed, 7 Dec 2022 03:46:50 -0800 Subject: [PATCH 262/824] UART echo: fix race conditions causing null pointer dereference (#2092) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 0xchocolate <0xchocolate@users.noreply.github.com> Co-authored-by: あく --- applications/debug/uart_echo/uart_echo.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/applications/debug/uart_echo/uart_echo.c b/applications/debug/uart_echo/uart_echo.c index 16996ba8cd2..dc132752921 100644 --- a/applications/debug/uart_echo/uart_echo.c +++ b/applications/debug/uart_echo/uart_echo.c @@ -215,26 +215,26 @@ static UartEchoApp* uart_echo_app_alloc() { view_dispatcher_add_view(app->view_dispatcher, 0, app->view); view_dispatcher_switch_to_view(app->view_dispatcher, 0); + app->worker_thread = furi_thread_alloc_ex("UsbUartWorker", 1024, uart_echo_worker, app); + furi_thread_start(app->worker_thread); + // Enable uart listener furi_hal_console_disable(); furi_hal_uart_set_br(FuriHalUartIdUSART1, 115200); furi_hal_uart_set_irq_cb(FuriHalUartIdUSART1, uart_echo_on_irq_cb, app); - app->worker_thread = furi_thread_alloc_ex("UsbUartWorker", 1024, uart_echo_worker, app); - furi_thread_start(app->worker_thread); - return app; } static void uart_echo_app_free(UartEchoApp* app) { furi_assert(app); + furi_hal_console_enable(); // this will also clear IRQ callback so thread is no longer referenced + furi_thread_flags_set(furi_thread_get_id(app->worker_thread), WorkerEventStop); furi_thread_join(app->worker_thread); furi_thread_free(app->worker_thread); - furi_hal_console_enable(); - // Free views view_dispatcher_remove_view(app->view_dispatcher, 0); From 741ad34b2c6c7ad08056e1d4f83026a21c4b8fa2 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 7 Dec 2022 16:28:18 +0300 Subject: [PATCH 263/824] WS: Oregon2 - add support for temp sensor RTHN129 (#2088) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- .../weather_station/protocols/oregon2.c | 37 ++++++++++++++----- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/applications/plugins/weather_station/protocols/oregon2.c b/applications/plugins/weather_station/protocols/oregon2.c index d294548e62b..8779e9596e1 100644 --- a/applications/plugins/weather_station/protocols/oregon2.c +++ b/applications/plugins/weather_station/protocols/oregon2.c @@ -52,6 +52,11 @@ static const SubGhzBlockConst ws_oregon2_const = { #define ID_UV800 0xd874 #define ID_THN129 0xcc43 // THN129 Temp only #define ID_RTHN129 0x0cd3 // RTHN129 Temp, clock sensors +#define ID_RTHN129_1 0x9cd3 +#define ID_RTHN129_2 0xacd3 +#define ID_RTHN129_3 0xbcd3 +#define ID_RTHN129_4 0xccd3 +#define ID_RTHN129_5 0xdcd3 #define ID_BTHGN129 0x5d53 // Baro, Temp, Hygro sensor #define ID_UVR128 0xec70 #define ID_THGR328N 0xcc23 // Temp & Hygro sensor similar to THR228N with 5 channel instead of 3 @@ -137,11 +142,19 @@ static ManchesterEvent level_and_duration_to_event(bool level, uint32_t duration // From sensor id code return amount of bits in variable section // https://temofeev.ru/info/articles/o-dekodirovanii-protokola-pogodnykh-datchikov-oregon-scientific static uint8_t oregon2_sensor_id_var_bits(uint16_t sensor_id) { - if(sensor_id == ID_THR228N) return 16; - - if(sensor_id == ID_THGR122N) return 24; - - return 0; + switch(sensor_id) { + case ID_THR228N: + case ID_RTHN129_1: + case ID_RTHN129_2: + case ID_RTHN129_3: + case ID_RTHN129_4: + case ID_RTHN129_5: + return 16; + case ID_THGR122N: + return 24; + default: + return 0; + } } static void ws_oregon2_decode_const_data(WSBlockGeneric* ws_block) { @@ -171,16 +184,22 @@ static float ws_oregon2_decode_temp(uint32_t data) { } static void ws_oregon2_decode_var_data(WSBlockGeneric* ws_b, uint16_t sensor_id, uint32_t data) { - if(sensor_id == ID_THR228N) { + switch(sensor_id) { + case ID_THR228N: + case ID_RTHN129_1: + case ID_RTHN129_2: + case ID_RTHN129_3: + case ID_RTHN129_4: + case ID_RTHN129_5: ws_b->temp = ws_oregon2_decode_temp(data); ws_b->humidity = WS_NO_HUMIDITY; return; - } - - if(sensor_id == ID_THGR122N) { + case ID_THGR122N: ws_b->humidity = bcd_decode_short(data); ws_b->temp = ws_oregon2_decode_temp(data >> 8); return; + default: + break; } } From 2daf39018bcbdc62d4b34ecffd75d22d365a3334 Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Wed, 7 Dec 2022 17:17:41 +0300 Subject: [PATCH 264/824] [FL-3001] File browser base folder (#2091) * File browser base folder * Format sources * FuriHal: bump api version Co-authored-by: Aleksandr Kutuzov --- applications/debug/file_browser_test/file_browser_app.c | 2 +- applications/main/archive/helpers/archive_browser.c | 3 ++- .../main/bad_usb/scenes/bad_usb_scene_file_select.c | 2 ++ applications/main/fap_loader/fap_loader_app.c | 1 + applications/main/ibutton/ibutton.c | 1 + .../main/infrared/scenes/infrared_scene_remote_list.c | 1 + applications/main/lfrfid/lfrfid.c | 1 + applications/main/subghz/subghz_i.c | 1 + applications/plugins/music_player/music_player.c | 1 + applications/plugins/picopass/picopass_device.c | 1 + applications/services/dialogs/dialogs.c | 1 + applications/services/dialogs/dialogs.h | 2 ++ applications/services/dialogs/dialogs_api.c | 1 + applications/services/dialogs/dialogs_message.h | 1 + .../services/dialogs/dialogs_module_file_browser.c | 1 + applications/services/gui/modules/file_browser.c | 9 ++++++++- applications/services/gui/modules/file_browser.h | 1 + applications/services/gui/modules/file_browser_worker.c | 8 +++++--- applications/services/gui/modules/file_browser_worker.h | 1 + .../scenes/desktop_settings_scene_favorite.c | 1 + firmware/targets/f7/api_symbols.csv | 6 +++--- lib/nfc/nfc_device.c | 1 + 22 files changed, 38 insertions(+), 9 deletions(-) diff --git a/applications/debug/file_browser_test/file_browser_app.c b/applications/debug/file_browser_test/file_browser_app.c index 6cb50d38543..996cb2bd247 100644 --- a/applications/debug/file_browser_test/file_browser_app.c +++ b/applications/debug/file_browser_test/file_browser_app.c @@ -48,7 +48,7 @@ FileBrowserApp* file_browser_app_alloc(char* arg) { app->file_path = furi_string_alloc(); app->file_browser = file_browser_alloc(app->file_path); - file_browser_configure(app->file_browser, "*", true, &I_badusb_10px, true); + file_browser_configure(app->file_browser, "*", NULL, true, &I_badusb_10px, true); view_dispatcher_add_view( app->view_dispatcher, FileBrowserAppViewStart, widget_get_view(app->widget)); diff --git a/applications/main/archive/helpers/archive_browser.c b/applications/main/archive/helpers/archive_browser.c index fa705189f5e..a216705b7c0 100644 --- a/applications/main/archive/helpers/archive_browser.c +++ b/applications/main/archive/helpers/archive_browser.c @@ -84,7 +84,8 @@ static void archive_file_browser_set_path( bool hide_dot_files) { furi_assert(browser); if(!browser->worker_running) { - browser->worker = file_browser_worker_alloc(path, filter_ext, skip_assets, hide_dot_files); + browser->worker = + file_browser_worker_alloc(path, NULL, filter_ext, skip_assets, hide_dot_files); file_browser_worker_set_callback_context(browser->worker, browser); file_browser_worker_set_folder_callback(browser->worker, archive_folder_open_cb); file_browser_worker_set_list_callback(browser->worker, archive_list_load_cb); diff --git a/applications/main/bad_usb/scenes/bad_usb_scene_file_select.c b/applications/main/bad_usb/scenes/bad_usb_scene_file_select.c index c562fc2de51..9264eb97686 100644 --- a/applications/main/bad_usb/scenes/bad_usb_scene_file_select.c +++ b/applications/main/bad_usb/scenes/bad_usb_scene_file_select.c @@ -1,12 +1,14 @@ #include "../bad_usb_app_i.h" #include "furi_hal_power.h" #include "furi_hal_usb.h" +#include static bool bad_usb_file_select(BadUsbApp* bad_usb) { furi_assert(bad_usb); DialogsFileBrowserOptions browser_options; dialog_file_browser_set_basic_options(&browser_options, BAD_USB_APP_EXTENSION, &I_badusb_10px); + browser_options.base_path = BAD_USB_APP_PATH_FOLDER; // Input events and views are managed by file_browser bool res = dialog_file_browser_show( diff --git a/applications/main/fap_loader/fap_loader_app.c b/applications/main/fap_loader/fap_loader_app.c index 10cec086a60..90186674614 100644 --- a/applications/main/fap_loader/fap_loader_app.c +++ b/applications/main/fap_loader/fap_loader_app.c @@ -148,6 +148,7 @@ static bool fap_loader_select_app(FapLoader* loader) { .hide_ext = true, .item_loader_callback = fap_loader_item_callback, .item_loader_context = loader, + .base_path = EXT_PATH("apps"), }; return dialog_file_browser_show( diff --git a/applications/main/ibutton/ibutton.c b/applications/main/ibutton/ibutton.c index b6d8361b353..b7c8223b097 100644 --- a/applications/main/ibutton/ibutton.c +++ b/applications/main/ibutton/ibutton.c @@ -218,6 +218,7 @@ void ibutton_free(iButton* ibutton) { bool ibutton_file_select(iButton* ibutton) { DialogsFileBrowserOptions browser_options; dialog_file_browser_set_basic_options(&browser_options, IBUTTON_APP_EXTENSION, &I_ibutt_10px); + browser_options.base_path = IBUTTON_APP_FOLDER; bool success = dialog_file_browser_show( ibutton->dialogs, ibutton->file_path, ibutton->file_path, &browser_options); diff --git a/applications/main/infrared/scenes/infrared_scene_remote_list.c b/applications/main/infrared/scenes/infrared_scene_remote_list.c index 1667352d156..55f14416bf2 100644 --- a/applications/main/infrared/scenes/infrared_scene_remote_list.c +++ b/applications/main/infrared/scenes/infrared_scene_remote_list.c @@ -7,6 +7,7 @@ void infrared_scene_remote_list_on_enter(void* context) { DialogsFileBrowserOptions browser_options; dialog_file_browser_set_basic_options(&browser_options, INFRARED_APP_EXTENSION, &I_ir_10px); + browser_options.base_path = INFRARED_APP_FOLDER; bool success = dialog_file_browser_show( infrared->dialogs, infrared->file_path, infrared->file_path, &browser_options); diff --git a/applications/main/lfrfid/lfrfid.c b/applications/main/lfrfid/lfrfid.c index d391c5e8992..2207e7e0797 100644 --- a/applications/main/lfrfid/lfrfid.c +++ b/applications/main/lfrfid/lfrfid.c @@ -230,6 +230,7 @@ bool lfrfid_load_key_from_file_select(LfRfid* app) { DialogsFileBrowserOptions browser_options; dialog_file_browser_set_basic_options(&browser_options, LFRFID_APP_EXTENSION, &I_125_10px); + browser_options.base_path = LFRFID_APP_FOLDER; // Input events and views are managed by file_browser bool result = diff --git a/applications/main/subghz/subghz_i.c b/applications/main/subghz/subghz_i.c index d9070ba8cc0..736bcf360ab 100644 --- a/applications/main/subghz/subghz_i.c +++ b/applications/main/subghz/subghz_i.c @@ -454,6 +454,7 @@ bool subghz_load_protocol_from_file(SubGhz* subghz) { DialogsFileBrowserOptions browser_options; dialog_file_browser_set_basic_options(&browser_options, SUBGHZ_APP_EXTENSION, &I_sub1_10px); + browser_options.base_path = SUBGHZ_APP_FOLDER; // Input events and views are managed by file_select bool res = dialog_file_browser_show( diff --git a/applications/plugins/music_player/music_player.c b/applications/plugins/music_player/music_player.c index 28872284bda..07d4e2df49c 100644 --- a/applications/plugins/music_player/music_player.c +++ b/applications/plugins/music_player/music_player.c @@ -313,6 +313,7 @@ int32_t music_player_app(void* p) { dialog_file_browser_set_basic_options( &browser_options, MUSIC_PLAYER_APP_EXTENSION, &I_music_10px); browser_options.hide_ext = false; + browser_options.base_path = MUSIC_PLAYER_APP_PATH_FOLDER; DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); bool res = dialog_file_browser_show(dialogs, file_path, file_path, &browser_options); diff --git a/applications/plugins/picopass/picopass_device.c b/applications/plugins/picopass/picopass_device.c index 199b79e97ac..fd8ddbfbd27 100644 --- a/applications/plugins/picopass/picopass_device.c +++ b/applications/plugins/picopass/picopass_device.c @@ -231,6 +231,7 @@ bool picopass_file_select(PicopassDevice* dev) { DialogsFileBrowserOptions browser_options; dialog_file_browser_set_basic_options(&browser_options, PICOPASS_APP_EXTENSION, &I_Nfc_10px); + browser_options.base_path = PICOPASS_APP_FOLDER; bool res = dialog_file_browser_show( dev->dialogs, dev->load_path, picopass_app_folder, &browser_options); diff --git a/applications/services/dialogs/dialogs.c b/applications/services/dialogs/dialogs.c index 3c658888965..3908ca31b58 100644 --- a/applications/services/dialogs/dialogs.c +++ b/applications/services/dialogs/dialogs.c @@ -14,6 +14,7 @@ void dialog_file_browser_set_basic_options( options->hide_ext = true; options->item_loader_callback = NULL; options->item_loader_context = NULL; + options->base_path = NULL; } static DialogsApp* dialogs_app_alloc() { diff --git a/applications/services/dialogs/dialogs.h b/applications/services/dialogs/dialogs.h index 97783eb0d6a..4c1b675a644 100644 --- a/applications/services/dialogs/dialogs.h +++ b/applications/services/dialogs/dialogs.h @@ -18,6 +18,7 @@ typedef struct DialogsApp DialogsApp; /** * File browser dialog extra options * @param extension file extension to be offered for selection + * @param base_path root folder path for navigation with back key * @param skip_assets true - do not show assets folders * @param hide_dot_files true - hide dot files * @param icon file icon pointer, NULL for default icon @@ -27,6 +28,7 @@ typedef struct DialogsApp DialogsApp; */ typedef struct { const char* extension; + const char* base_path; bool skip_assets; bool hide_dot_files; const Icon* icon; diff --git a/applications/services/dialogs/dialogs_api.c b/applications/services/dialogs/dialogs_api.c index 91b3a1d04fe..5e2b0683ee3 100644 --- a/applications/services/dialogs/dialogs_api.c +++ b/applications/services/dialogs/dialogs_api.c @@ -24,6 +24,7 @@ bool dialog_file_browser_show( .preselected_filename = path, .item_callback = options ? options->item_loader_callback : NULL, .item_callback_context = options ? options->item_loader_context : NULL, + .base_path = options ? options->base_path : NULL, }}; DialogsAppReturn return_data; diff --git a/applications/services/dialogs/dialogs_message.h b/applications/services/dialogs/dialogs_message.h index 45d36ba171f..1c8c4fb50aa 100644 --- a/applications/services/dialogs/dialogs_message.h +++ b/applications/services/dialogs/dialogs_message.h @@ -17,6 +17,7 @@ typedef struct { FuriString* preselected_filename; FileBrowserLoadItemCallback item_callback; void* item_callback_context; + const char* base_path; } DialogsAppMessageDataFileBrowser; typedef struct { diff --git a/applications/services/dialogs/dialogs_module_file_browser.c b/applications/services/dialogs/dialogs_module_file_browser.c index e03434cf5cb..8d486dbaf4e 100644 --- a/applications/services/dialogs/dialogs_module_file_browser.c +++ b/applications/services/dialogs/dialogs_module_file_browser.c @@ -40,6 +40,7 @@ bool dialogs_app_process_module_file_browser(const DialogsAppMessageDataFileBrow file_browser_configure( file_browser, data->extension, + data->base_path, data->skip_assets, data->hide_dot_files, data->file_icon, diff --git a/applications/services/gui/modules/file_browser.c b/applications/services/gui/modules/file_browser.c index f20f950fac1..a5daa91ec27 100644 --- a/applications/services/gui/modules/file_browser.c +++ b/applications/services/gui/modules/file_browser.c @@ -83,6 +83,7 @@ struct FileBrowser { View* view; BrowserWorker* worker; const char* ext_filter; + const char* base_path; bool skip_assets; bool hide_dot_files; bool hide_ext; @@ -163,6 +164,7 @@ View* file_browser_get_view(FileBrowser* browser) { void file_browser_configure( FileBrowser* browser, const char* extension, + const char* base_path, bool skip_assets, bool hide_dot_files, const Icon* file_icon, @@ -172,6 +174,7 @@ void file_browser_configure( browser->ext_filter = extension; browser->skip_assets = skip_assets; browser->hide_ext = hide_ext; + browser->base_path = base_path; browser->hide_dot_files = hide_dot_files; with_view_model( @@ -187,7 +190,11 @@ void file_browser_configure( void file_browser_start(FileBrowser* browser, FuriString* path) { furi_assert(browser); browser->worker = file_browser_worker_alloc( - path, browser->ext_filter, browser->skip_assets, browser->hide_dot_files); + path, + browser->base_path, + browser->ext_filter, + browser->skip_assets, + browser->hide_dot_files); 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); diff --git a/applications/services/gui/modules/file_browser.h b/applications/services/gui/modules/file_browser.h index 377d4d9bc75..879d62c4e21 100644 --- a/applications/services/gui/modules/file_browser.h +++ b/applications/services/gui/modules/file_browser.h @@ -29,6 +29,7 @@ View* file_browser_get_view(FileBrowser* browser); void file_browser_configure( FileBrowser* browser, const char* extension, + const char* base_path, bool skip_assets, bool hide_dot_files, const Icon* file_icon, diff --git a/applications/services/gui/modules/file_browser_worker.c b/applications/services/gui/modules/file_browser_worker.c index fb45a384e01..d8b515d0373 100644 --- a/applications/services/gui/modules/file_browser_worker.c +++ b/applications/services/gui/modules/file_browser_worker.c @@ -371,6 +371,7 @@ static int32_t browser_worker(void* context) { BrowserWorker* file_browser_worker_alloc( FuriString* path, + const char* base_path, const char* filter_ext, bool skip_assets, bool hide_dot_files) { @@ -381,12 +382,13 @@ BrowserWorker* file_browser_worker_alloc( browser->filter_extension = furi_string_alloc_set(filter_ext); browser->skip_assets = skip_assets; browser->hide_dot_files = hide_dot_files; - browser->path_start = furi_string_alloc_set(path); + browser->path_current = furi_string_alloc_set(path); browser->path_next = furi_string_alloc_set(path); - if(browser_path_is_file(browser->path_start)) { - browser_path_trim(browser->path_start); + browser->path_start = furi_string_alloc(); + if(base_path) { + furi_string_set_str(browser->path_start, base_path); } browser->thread = furi_thread_alloc_ex("BrowserWorker", 2048, browser_worker, browser); diff --git a/applications/services/gui/modules/file_browser_worker.h b/applications/services/gui/modules/file_browser_worker.h index ca143d66092..3b4be6aa77b 100644 --- a/applications/services/gui/modules/file_browser_worker.h +++ b/applications/services/gui/modules/file_browser_worker.h @@ -23,6 +23,7 @@ typedef void (*BrowserWorkerLongLoadCallback)(void* context); BrowserWorker* file_browser_worker_alloc( FuriString* path, + const char* base_path, const char* filter_ext, bool skip_assets, bool hide_dot_files); diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c index bdd9589ed75..cf474c546c7 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c @@ -106,6 +106,7 @@ bool desktop_settings_scene_favorite_on_event(void* context, SceneManagerEvent e .hide_ext = true, .item_loader_callback = favorite_fap_selector_item_callback, .item_loader_context = app, + .base_path = EXT_PATH("apps"), }; if(primary_favorite) { // Select favorite fap in file browser diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 38f21902d21..1ce768450d9 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,9.0,, +Version,+,10.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -806,14 +806,14 @@ Function,-,fgetpos,int,"FILE*, fpos_t*" Function,-,fgets,char*,"char*, int, FILE*" Function,-,fgets_unlocked,char*,"char*, int, FILE*" Function,+,file_browser_alloc,FileBrowser*,FuriString* -Function,+,file_browser_configure,void,"FileBrowser*, const char*, _Bool, _Bool, const Icon*, _Bool" +Function,+,file_browser_configure,void,"FileBrowser*, const char*, const char*, _Bool, _Bool, const Icon*, _Bool" Function,+,file_browser_free,void,FileBrowser* Function,+,file_browser_get_view,View*,FileBrowser* Function,+,file_browser_set_callback,void,"FileBrowser*, FileBrowserCallback, void*" Function,+,file_browser_set_item_callback,void,"FileBrowser*, FileBrowserLoadItemCallback, void*" Function,+,file_browser_start,void,"FileBrowser*, FuriString*" Function,+,file_browser_stop,void,FileBrowser* -Function,+,file_browser_worker_alloc,BrowserWorker*,"FuriString*, const char*, _Bool, _Bool" +Function,+,file_browser_worker_alloc,BrowserWorker*,"FuriString*, const char*, const char*, _Bool, _Bool" Function,+,file_browser_worker_folder_enter,void,"BrowserWorker*, FuriString*, int32_t" Function,+,file_browser_worker_folder_exit,void,BrowserWorker* Function,+,file_browser_worker_folder_refresh,void,"BrowserWorker*, int32_t" diff --git a/lib/nfc/nfc_device.c b/lib/nfc/nfc_device.c index e18f094590d..3ab10a4f68f 100644 --- a/lib/nfc/nfc_device.c +++ b/lib/nfc/nfc_device.c @@ -1212,6 +1212,7 @@ bool nfc_file_select(NfcDevice* dev) { .hide_ext = true, .item_loader_callback = NULL, .item_loader_context = NULL, + .base_path = NFC_APP_FOLDER, }; bool res = From c535b8f4ceef1cae1f30a6754a0b59a36055e1e5 Mon Sep 17 00:00:00 2001 From: gornekich Date: Wed, 7 Dec 2022 20:47:55 +0400 Subject: [PATCH 265/824] [FL-3017], [FL-3018] Change NFC emulation screens (#2102) * nfc: fix emulate uid view * archive: hide dot files in apps * nfc: fix other emulation scenes view --- .../main/archive/helpers/archive_browser.c | 4 +++- .../main/nfc/scenes/nfc_scene_emulate_uid.c | 6 +++--- .../nfc/scenes/nfc_scene_mf_classic_emulate.c | 9 +++++---- .../nfc/scenes/nfc_scene_mf_ultralight_emulate.c | 14 ++++++++++---- applications/main/nfc/scenes/nfc_scene_rpc.c | 2 +- assets/icons/NFC/NFC_dolphin_emulation_47x61.png | Bin 0 -> 1541 bytes 6 files changed, 22 insertions(+), 13 deletions(-) create mode 100644 assets/icons/NFC/NFC_dolphin_emulation_47x61.png diff --git a/applications/main/archive/helpers/archive_browser.c b/applications/main/archive/helpers/archive_browser.c index a216705b7c0..6c8c9633a82 100644 --- a/applications/main/archive/helpers/archive_browser.c +++ b/applications/main/archive/helpers/archive_browser.c @@ -475,8 +475,10 @@ void archive_switch_tab(ArchiveBrowserView* browser, InputKey key) { tab = archive_get_tab(browser); if(archive_is_dir_exists(browser->path)) { bool skip_assets = (strcmp(archive_get_tab_ext(tab), "*") == 0) ? false : true; + // Hide dot files everywhere except Browser + bool hide_dot_files = (strcmp(archive_get_tab_ext(tab), "*") == 0) ? false : true; archive_file_browser_set_path( - browser, browser->path, archive_get_tab_ext(tab), skip_assets, false); + browser, browser->path, archive_get_tab_ext(tab), skip_assets, hide_dot_files); tab_empty = false; // Empty check will be performed later } } diff --git a/applications/main/nfc/scenes/nfc_scene_emulate_uid.c b/applications/main/nfc/scenes/nfc_scene_emulate_uid.c index 5ddb60992f0..f901976798d 100644 --- a/applications/main/nfc/scenes/nfc_scene_emulate_uid.c +++ b/applications/main/nfc/scenes/nfc_scene_emulate_uid.c @@ -37,8 +37,8 @@ static void nfc_scene_emulate_uid_widget_config(Nfc* nfc, bool data_received) { FuriString* info_str; info_str = furi_string_alloc(); - widget_add_icon_element(widget, 0, 3, &I_RFIDDolphinSend_97x61); - widget_add_string_element(widget, 89, 32, AlignCenter, AlignTop, FontPrimary, "Emulating UID"); + widget_add_icon_element(widget, 0, 3, &I_NFC_dolphin_emulation_47x61); + widget_add_string_element(widget, 57, 13, AlignLeft, AlignTop, FontPrimary, "Emulating UID"); if(strcmp(nfc->dev->dev_name, "")) { furi_string_printf(info_str, "%s", nfc->dev->dev_name); } else { @@ -48,7 +48,7 @@ static void nfc_scene_emulate_uid_widget_config(Nfc* nfc, bool data_received) { } furi_string_trim(info_str); widget_add_text_box_element( - widget, 56, 43, 70, 21, AlignCenter, AlignTop, furi_string_get_cstr(info_str), true); + widget, 57, 28, 67, 25, AlignCenter, AlignTop, furi_string_get_cstr(info_str), true); furi_string_free(info_str); if(data_received) { widget_add_button_element( diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_emulate.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_emulate.c index 68eda70406c..1bd9a85a859 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_emulate.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_emulate.c @@ -17,13 +17,14 @@ void nfc_scene_mf_classic_emulate_on_enter(void* context) { // Setup view Popup* popup = nfc->popup; + popup_set_header(popup, "Emulating", 67, 13, AlignLeft, AlignTop); if(strcmp(nfc->dev->dev_name, "")) { - nfc_text_store_set(nfc, "Emulating\n%s", nfc->dev->dev_name); + nfc_text_store_set(nfc, "%s", nfc->dev->dev_name); } else { - nfc_text_store_set(nfc, "Emulating\nMf Classic", nfc->dev->dev_name); + nfc_text_store_set(nfc, "MIFARE\nClassic"); } - popup_set_icon(popup, 0, 3, &I_RFIDDolphinSend_97x61); - popup_set_header(popup, nfc->text_store, 56, 31, AlignLeft, AlignTop); + popup_set_icon(popup, 0, 3, &I_NFC_dolphin_emulation_47x61); + popup_set_text(popup, nfc->text_store, 90, 28, AlignCenter, AlignTop); // Setup and start worker view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_emulate.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_emulate.c index adcadaaf26f..c9c617cbef6 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_emulate.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_emulate.c @@ -16,14 +16,20 @@ void nfc_scene_mf_ultralight_emulate_on_enter(void* context) { Nfc* nfc = context; // Setup view + MfUltralightType type = nfc->dev->dev_data.mf_ul_data.type; + bool is_ultralight = (type == MfUltralightTypeUL11) || (type == MfUltralightTypeUL21) || + (type == MfUltralightTypeUnknown); Popup* popup = nfc->popup; + popup_set_header(popup, "Emulating", 67, 13, AlignLeft, AlignTop); if(strcmp(nfc->dev->dev_name, "")) { - nfc_text_store_set(nfc, "Emulating\n%s", nfc->dev->dev_name); + nfc_text_store_set(nfc, "%s", nfc->dev->dev_name); + } else if(is_ultralight) { + nfc_text_store_set(nfc, "MIFARE\nUltralight"); } else { - nfc_text_store_set(nfc, "Emulating\nMf Ultralight", nfc->dev->dev_name); + nfc_text_store_set(nfc, "MIFARE\nNTAG"); } - popup_set_icon(popup, 0, 3, &I_RFIDDolphinSend_97x61); - popup_set_header(popup, nfc->text_store, 56, 31, AlignLeft, AlignTop); + popup_set_icon(popup, 0, 3, &I_NFC_dolphin_emulation_47x61); + popup_set_text(popup, nfc->text_store, 90, 28, AlignCenter, AlignTop); // Setup and start worker view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); diff --git a/applications/main/nfc/scenes/nfc_scene_rpc.c b/applications/main/nfc/scenes/nfc_scene_rpc.c index e5128a52fbe..60d01a30da6 100644 --- a/applications/main/nfc/scenes/nfc_scene_rpc.c +++ b/applications/main/nfc/scenes/nfc_scene_rpc.c @@ -7,7 +7,7 @@ void nfc_scene_rpc_on_enter(void* context) { popup_set_header(popup, "NFC", 89, 42, AlignCenter, AlignBottom); popup_set_text(popup, "RPC mode", 89, 44, AlignCenter, AlignTop); - popup_set_icon(popup, 0, 12, &I_RFIDDolphinSend_97x61); + popup_set_icon(popup, 0, 12, &I_NFC_dolphin_emulation_47x61); view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); diff --git a/assets/icons/NFC/NFC_dolphin_emulation_47x61.png b/assets/icons/NFC/NFC_dolphin_emulation_47x61.png new file mode 100644 index 0000000000000000000000000000000000000000..1783531285bed514517fc0821501e718359dd765 GIT binary patch literal 1541 zcmaJ>eM}Q)7{3ZaLqs>76Ex1V5{uLJ`hl&zQLyxiR4J5Flue0VuU9zXKDZvVFu$in zjZPV|k*WB>lBo=wab{s;0v4vrHgq6yL6|@s=rj(*ZALe`w+OgD#xD2qyib0=-}CW4 z_wKW%tO^gC8wNp8xH$>4fiD6cy*LQG!v-3sLy!oej7F>3XoNYQby0aF1bI7aN-wPT zSzjw@m}hD^wN~8M!%5Suc^yp$% z;LAJvZZ<7U;$}9n&swkday_9{j$6k|(n<`UWz^qKx;6aE!&&NN(Msv}w)zef+FQ1w zPdX)Tbt_@>_j_ z1g4yN$-3nhg@rScIaE?HPo@{A*oop?Lg$pk$HB2)6bR6yfWuxok8z`3y<}7u1$MxV zNZ?V3kgJ!xNGj7}g^esv!dkgfMko{tSgVthPF&?syKrI|tWv0yh!WgdnNo|Y)TmMi zk6jWFkaxOJ8D8>lEl2;>9^cDOUul8V6b|{|}|<3A9_V zkuUT8Phq#ch$9gj>1GRf0_>e@Q6LnD8hH{ISl-UEdE_v6wo9ijB}kYxrRx(fq|eo5E&zRs*rRh@+=LHR*h1V=c1idZ;b1lJeL)dauJXW z64={+?e(||3{b$F7+$cL7=MxhGtYzJp6B{075o?>)?~ZM@Am^U<4XHBa7ryUV+Omo z^NSB}*9I*Vo3i}=cBw9McHOY`cJjO|@#%;8vhG*t42yEl!M7sbnHFlhs_u%cu{BLw zhaGH~)>1i`aAdfxzb-yDYp8axDf-aUrDb=dji35@J+L(5s-gM3W`<^_YQ(SZsXOZ| z+MW^|z2V5B)@@rgjw-Cq(dw%_ar$zq^bB!0SN!Wu`o1~8yPWDctj)SpoN(-94Cmev zzx{^qT>j%Hhc_onj|mkQqEx+SO=({jC(e}(-Mu#TV*2AZM;fP!-l&6{t3IlprLTNH zu7%|!`|gA}!?~;;lRx5Kde1<}Db&?0+p;8mU68o-2TkKA6-h@we_Yd@SR^HGPk zTw<~x)L!ghDi?=TUtd5z8164@t6%c>#|vtn#`%LIpusI7pJ(MpaIQm;*_49SCT!aE E4{AO|uK)l5 literal 0 HcmV?d00001 From 6a470a464ed4dc0f88fb80624f72671e2b0fdb3d Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Thu, 8 Dec 2022 09:49:54 +0400 Subject: [PATCH 266/824] [FL-3002] SubGhz: add RPC error (#2097) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [FL-3002] SubGhz: add RPC error * RPC_APP: rpc_system_app_error_reset, automatic error reset when a new event is executed * SubGhz: fix text error * SubGhz: fix text error 2 * SubGhz: add error description * Format sources Co-authored-by: あく --- .../main/subghz/helpers/subghz_error_type.h | 13 +++++++++++++ applications/main/subghz/scenes/subghz_scene_rpc.c | 9 +++++++++ applications/main/subghz/subghz_i.h | 1 + applications/services/rpc/rpc_app.c | 14 ++++++++++++++ applications/services/rpc/rpc_app.h | 2 ++ firmware/targets/f7/api_symbols.csv | 3 ++- 6 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 applications/main/subghz/helpers/subghz_error_type.h diff --git a/applications/main/subghz/helpers/subghz_error_type.h b/applications/main/subghz/helpers/subghz_error_type.h new file mode 100644 index 00000000000..e481aa4be84 --- /dev/null +++ b/applications/main/subghz/helpers/subghz_error_type.h @@ -0,0 +1,13 @@ +#pragma once + +#include +#include + +/** SubGhzErrorType */ +typedef enum { + SubGhzErrorTypeNoError = 0, /** There are no errors */ + SubGhzErrorTypeParseFile = + 1, /** File parsing error, or wrong file structure, or missing required parameters. more accurate data can be obtained through the debug port */ + SubGhzErrorTypeOnlyRX = + 2, /** Transmission on this frequency is blocked by regional settings */ +} SubGhzErrorType; diff --git a/applications/main/subghz/scenes/subghz_scene_rpc.c b/applications/main/subghz/scenes/subghz_scene_rpc.c index 79295aaa6a1..a1c0e41fd65 100644 --- a/applications/main/subghz/scenes/subghz_scene_rpc.c +++ b/applications/main/subghz/scenes/subghz_scene_rpc.c @@ -43,6 +43,12 @@ bool subghz_scene_rpc_on_event(void* context, SceneManagerEvent event) { result = subghz_tx_start(subghz, subghz->txrx->fff_data); if(result) subghz_blink_start(subghz); } + if(!result) { + rpc_system_app_set_error_code(subghz->rpc_ctx, SubGhzErrorTypeOnlyRX); + rpc_system_app_set_error_text( + subghz->rpc_ctx, + "Transmission on this frequency is restricted in your region"); + } rpc_system_app_confirm(subghz->rpc_ctx, RpcAppEventButtonPress, result); } else if(event.event == SubGhzCustomEventSceneRpcButtonRelease) { bool result = false; @@ -74,6 +80,9 @@ bool subghz_scene_rpc_on_event(void* context, SceneManagerEvent event) { popup_set_text(popup, subghz->file_name_tmp, 89, 44, AlignCenter, AlignTop); furi_string_free(file_name); + } else { + rpc_system_app_set_error_code(subghz->rpc_ctx, SubGhzErrorTypeParseFile); + rpc_system_app_set_error_text(subghz->rpc_ctx, "Cannot parse file"); } } rpc_system_app_confirm(subghz->rpc_ctx, RpcAppEventLoadFile, result); diff --git a/applications/main/subghz/subghz_i.h b/applications/main/subghz/subghz_i.h index 0a40196bb1d..234365155c2 100644 --- a/applications/main/subghz/subghz_i.h +++ b/applications/main/subghz/subghz_i.h @@ -1,6 +1,7 @@ #pragma once #include "helpers/subghz_types.h" +#include "helpers/subghz_error_type.h" #include #include "subghz.h" #include "views/receiver.h" diff --git a/applications/services/rpc/rpc_app.c b/applications/services/rpc/rpc_app.c index 4e1af1fb108..b96f043ac7e 100644 --- a/applications/services/rpc/rpc_app.c +++ b/applications/services/rpc/rpc_app.c @@ -32,6 +32,7 @@ static void rpc_system_app_start_process(const PB_Main* request, void* context) furi_assert(request->which_content == PB_Main_app_start_request_tag); RpcAppSystem* rpc_app = context; RpcSession* session = rpc_app->session; + rpc_system_app_error_reset(rpc_app); furi_assert(session); char args_temp[RPC_SYSTEM_APP_TEMP_ARGS_SIZE]; @@ -79,6 +80,7 @@ static void rpc_system_app_lock_status_process(const PB_Main* request, void* con furi_assert(request->which_content == PB_Main_app_lock_status_request_tag); RpcAppSystem* rpc_app = context; + rpc_system_app_error_reset(rpc_app); RpcSession* session = rpc_app->session; furi_assert(session); @@ -108,6 +110,7 @@ static void rpc_system_app_exit_request(const PB_Main* request, void* context) { furi_assert(request->which_content == PB_Main_app_exit_request_tag); RpcAppSystem* rpc_app = context; + rpc_system_app_error_reset(rpc_app); RpcSession* session = rpc_app->session; furi_assert(session); @@ -133,6 +136,7 @@ static void rpc_system_app_load_file(const PB_Main* request, void* context) { furi_assert(request->which_content == PB_Main_app_load_file_request_tag); RpcAppSystem* rpc_app = context; + rpc_system_app_error_reset(rpc_app); RpcSession* session = rpc_app->session; furi_assert(session); @@ -158,6 +162,7 @@ static void rpc_system_app_button_press(const PB_Main* request, void* context) { furi_assert(request->which_content == PB_Main_app_button_press_request_tag); RpcAppSystem* rpc_app = context; + rpc_system_app_error_reset(rpc_app); RpcSession* session = rpc_app->session; furi_assert(session); @@ -183,6 +188,7 @@ static void rpc_system_app_button_release(const PB_Main* request, void* context) furi_assert(context); RpcAppSystem* rpc_app = context; + rpc_system_app_error_reset(rpc_app); RpcSession* session = rpc_app->session; furi_assert(session); @@ -222,6 +228,7 @@ static void rpc_system_app_data_exchange_process(const PB_Main* request, void* c furi_assert(context); RpcAppSystem* rpc_app = context; + rpc_system_app_error_reset(rpc_app); RpcSession* session = rpc_app->session; furi_assert(session); @@ -326,6 +333,13 @@ void rpc_system_app_set_error_text(RpcAppSystem* rpc_app, const char* error_text content->text = error_text ? strdup(error_text) : NULL; } +void rpc_system_app_error_reset(RpcAppSystem* rpc_app) { + furi_assert(rpc_app); + + rpc_system_app_set_error_code(rpc_app, 0); + rpc_system_app_set_error_text(rpc_app, NULL); +} + void rpc_system_app_set_data_exchange_callback( RpcAppSystem* rpc_app, RpcAppSystemDataExchangeCallback callback, diff --git a/applications/services/rpc/rpc_app.h b/applications/services/rpc/rpc_app.h index 70febe4911e..d5c6fee9629 100644 --- a/applications/services/rpc/rpc_app.h +++ b/applications/services/rpc/rpc_app.h @@ -33,6 +33,8 @@ void rpc_system_app_set_error_code(RpcAppSystem* rpc_app, uint32_t error_code); void rpc_system_app_set_error_text(RpcAppSystem* rpc_app, const char* error_text); +void rpc_system_app_error_reset(RpcAppSystem* rpc_app); + void rpc_system_app_set_data_exchange_callback( RpcAppSystem* rpc_app, RpcAppSystemDataExchangeCallback callback, diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 1ce768450d9..7b07bb1880e 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,10.0,, +Version,+,10.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -2286,6 +2286,7 @@ Function,+,rpc_session_set_context,void,"RpcSession*, void*" Function,+,rpc_session_set_send_bytes_callback,void,"RpcSession*, RpcSendBytesCallback" Function,+,rpc_session_set_terminated_callback,void,"RpcSession*, RpcSessionTerminatedCallback" Function,+,rpc_system_app_confirm,void,"RpcAppSystem*, RpcAppSystemEvent, _Bool" +Function,+,rpc_system_app_error_reset,void,RpcAppSystem* Function,+,rpc_system_app_exchange_data,void,"RpcAppSystem*, const uint8_t*, size_t" Function,+,rpc_system_app_get_data,const char*,RpcAppSystem* Function,+,rpc_system_app_send_exited,void,RpcAppSystem* From 5c3a5cd8f71f386abf2d3676d87d52a39ceb1975 Mon Sep 17 00:00:00 2001 From: Shane Synan Date: Thu, 8 Dec 2022 01:57:49 -0500 Subject: [PATCH 267/824] FuriHal, Power, UnitTests: battery charging voltage limit API (#2063) --- applications/debug/unit_tests/minunit.h | 2 +- .../debug/unit_tests/power/power_test.c | 62 +++++++++++++++++++ applications/debug/unit_tests/test_index.c | 2 + .../services/power/power_service/power.c | 15 +++++ .../services/power/power_service/power.h | 1 + .../power_settings_scene_battery_info.c | 1 + .../power_settings_app/views/battery_info.c | 10 +++ .../power_settings_app/views/battery_info.h | 1 + firmware/targets/f7/api_symbols.csv | 2 + firmware/targets/f7/furi_hal/furi_hal_power.c | 29 ++++++++- .../targets/furi_hal_include/furi_hal_power.h | 16 +++++ lib/drivers/bq25896.c | 27 ++++++++ lib/drivers/bq25896.h | 9 +++ 13 files changed, 173 insertions(+), 4 deletions(-) create mode 100644 applications/debug/unit_tests/power/power_test.c diff --git a/applications/debug/unit_tests/minunit.h b/applications/debug/unit_tests/minunit.h index 17eb7b3f137..69bfba6d92e 100644 --- a/applications/debug/unit_tests/minunit.h +++ b/applications/debug/unit_tests/minunit.h @@ -316,7 +316,7 @@ void minunit_print_fail(const char* error); MU__SAFE_BLOCK( \ double minunit_tmp_e; double minunit_tmp_r; minunit_assert++; minunit_tmp_e = (expected); \ minunit_tmp_r = (result); \ - if(fabs(minunit_tmp_e - minunit_tmp_r) > MINUNIT_EPSILON) { \ + if(fabs(minunit_tmp_e - minunit_tmp_r) > (double)MINUNIT_EPSILON) { \ int minunit_significant_figures = 1 - log10(MINUNIT_EPSILON); \ snprintf( \ minunit_last_message, \ diff --git a/applications/debug/unit_tests/power/power_test.c b/applications/debug/unit_tests/power/power_test.c new file mode 100644 index 00000000000..ce2c7aad729 --- /dev/null +++ b/applications/debug/unit_tests/power/power_test.c @@ -0,0 +1,62 @@ +#include +#include +#include "../minunit.h" + +static void power_test_deinit(void) { + // Try to reset to default charging voltage + furi_hal_power_set_battery_charging_voltage(4.208f); +} + +MU_TEST(test_power_charge_voltage_exact) { + // Power of 16mV charge voltages get applied exactly + // (bq25896 charge controller works in 16mV increments) + // + // This test may need adapted if other charge controllers are used in the future. + for(uint16_t charge_mv = 3840; charge_mv <= 4208; charge_mv += 16) { + float charge_volt = (float)charge_mv / 1000.0f; + furi_hal_power_set_battery_charging_voltage(charge_volt); + mu_assert_double_eq(charge_volt, furi_hal_power_get_battery_charging_voltage()); + } +} + +MU_TEST(test_power_charge_voltage_floating_imprecision) { + // 4.016f should act as 4.016 V, even with floating point imprecision + furi_hal_power_set_battery_charging_voltage(4.016f); + mu_assert_double_eq(4.016f, furi_hal_power_get_battery_charging_voltage()); +} + +MU_TEST(test_power_charge_voltage_inexact) { + // Charge voltages that are not power of 16mV get truncated down + furi_hal_power_set_battery_charging_voltage(3.841f); + mu_assert_double_eq(3.840, furi_hal_power_get_battery_charging_voltage()); + + furi_hal_power_set_battery_charging_voltage(3.900f); + mu_assert_double_eq(3.888, furi_hal_power_get_battery_charging_voltage()); + + furi_hal_power_set_battery_charging_voltage(4.200f); + mu_assert_double_eq(4.192, furi_hal_power_get_battery_charging_voltage()); +} + +MU_TEST(test_power_charge_voltage_invalid_clamped) { + // Out-of-range charge voltages get clamped to 3.840 V and 4.208 V + furi_hal_power_set_battery_charging_voltage(3.808f); + mu_assert_double_eq(3.840, furi_hal_power_get_battery_charging_voltage()); + + // NOTE: Intentionally picking a small increment above 4.208 V to reduce the risk of an + // unhappy battery if this fails. + furi_hal_power_set_battery_charging_voltage(4.240f); + mu_assert_double_eq(4.208, furi_hal_power_get_battery_charging_voltage()); +} + +MU_TEST_SUITE(test_power_suite) { + MU_RUN_TEST(test_power_charge_voltage_exact); + MU_RUN_TEST(test_power_charge_voltage_floating_imprecision); + MU_RUN_TEST(test_power_charge_voltage_inexact); + MU_RUN_TEST(test_power_charge_voltage_invalid_clamped); + power_test_deinit(); +} + +int run_minunit_test_power() { + MU_RUN_SUITE(test_power_suite); + return MU_EXIT_CODE; +} diff --git a/applications/debug/unit_tests/test_index.c b/applications/debug/unit_tests/test_index.c index b8760b1f08e..65fa23c014d 100644 --- a/applications/debug/unit_tests/test_index.c +++ b/applications/debug/unit_tests/test_index.c @@ -19,6 +19,7 @@ int run_minunit_test_stream(); int run_minunit_test_storage(); int run_minunit_test_subghz(); int run_minunit_test_dirwalk(); +int run_minunit_test_power(); int run_minunit_test_protocol_dict(); int run_minunit_test_lfrfid_protocols(); int run_minunit_test_nfc(); @@ -44,6 +45,7 @@ const UnitTest unit_tests[] = { {.name = "subghz", .entry = run_minunit_test_subghz}, {.name = "infrared", .entry = run_minunit_test_infrared}, {.name = "nfc", .entry = run_minunit_test_nfc}, + {.name = "power", .entry = run_minunit_test_power}, {.name = "protocol_dict", .entry = run_minunit_test_protocol_dict}, {.name = "lfrfid", .entry = run_minunit_test_lfrfid_protocols}, {.name = "bit_lib", .entry = run_minunit_test_bit_lib}, diff --git a/applications/services/power/power_service/power.c b/applications/services/power/power_service/power.c index 89886b0f85e..5df611a7469 100644 --- a/applications/services/power/power_service/power.c +++ b/applications/services/power/power_service/power.c @@ -12,6 +12,20 @@ void power_draw_battery_callback(Canvas* canvas, void* context) { if(power->info.gauge_is_ok) { canvas_draw_box(canvas, 2, 2, (power->info.charge + 4) / 5, 4); + if(power->info.voltage_battery_charging < 4.2) { + // Battery charging voltage is modified, indicate with cross pattern + canvas_invert_color(canvas); + uint8_t battery_bar_width = (power->info.charge + 4) / 5; + bool cross_odd = false; + // Start 1 further in from the battery bar's x position + for(uint8_t x = 3; x <= battery_bar_width; x++) { + // Cross pattern is from the center of the battery bar + // y = 2 + 1 (inset) + 1 (for every other) + canvas_draw_dot(canvas, x, 3 + (uint8_t)cross_odd); + cross_odd = !cross_odd; + } + canvas_invert_color(canvas); + } if(power->state == PowerStateCharging) { canvas_set_bitmap_mode(canvas, 1); canvas_set_color(canvas, ColorWhite); @@ -132,6 +146,7 @@ static bool power_update_info(Power* power) { info.capacity_full = furi_hal_power_get_battery_full_capacity(); info.current_charger = furi_hal_power_get_battery_current(FuriHalPowerICCharger); info.current_gauge = furi_hal_power_get_battery_current(FuriHalPowerICFuelGauge); + info.voltage_battery_charging = furi_hal_power_get_battery_charging_voltage(); info.voltage_charger = furi_hal_power_get_battery_voltage(FuriHalPowerICCharger); info.voltage_gauge = furi_hal_power_get_battery_voltage(FuriHalPowerICFuelGauge); info.voltage_vbus = furi_hal_power_get_usb_voltage(); diff --git a/applications/services/power/power_service/power.h b/applications/services/power/power_service/power.h index 32433ebcaee..8b9019c4244 100644 --- a/applications/services/power/power_service/power.h +++ b/applications/services/power/power_service/power.h @@ -41,6 +41,7 @@ typedef struct { float current_charger; float current_gauge; + float voltage_battery_charging; float voltage_charger; float voltage_gauge; float voltage_vbus; diff --git a/applications/settings/power_settings_app/scenes/power_settings_scene_battery_info.c b/applications/settings/power_settings_app/scenes/power_settings_scene_battery_info.c index 0085c31dce2..5fa38df7299 100644 --- a/applications/settings/power_settings_app/scenes/power_settings_scene_battery_info.c +++ b/applications/settings/power_settings_app/scenes/power_settings_scene_battery_info.c @@ -7,6 +7,7 @@ static void power_settings_scene_battery_info_update_model(PowerSettingsApp* app .gauge_voltage = app->info.voltage_gauge, .gauge_current = app->info.current_gauge, .gauge_temperature = app->info.temperature_gauge, + .charging_voltage = app->info.voltage_battery_charging, .charge = app->info.charge, .health = app->info.health, }; diff --git a/applications/settings/power_settings_app/views/battery_info.c b/applications/settings/power_settings_app/views/battery_info.c index bbb0acb9a61..d760164b989 100644 --- a/applications/settings/power_settings_app/views/battery_info.c +++ b/applications/settings/power_settings_app/views/battery_info.c @@ -68,6 +68,16 @@ static void draw_battery(Canvas* canvas, BatteryInfoModel* data, int x, int y) { drain_current > HIGH_DRAIN_CURRENT_THRESHOLD ? "mA!" : "mA"); } else if(drain_current != 0) { snprintf(header, 20, "..."); + } else if(data->charging_voltage < 4.2) { + // Non-default battery charging limit, mention it + snprintf(emote, sizeof(emote), "Charged!"); + snprintf(header, sizeof(header), "Limited to"); + snprintf( + value, + sizeof(value), + "%ld.%ldV", + (uint32_t)(data->charging_voltage), + (uint32_t)(data->charging_voltage * 10) % 10); } else { snprintf(header, sizeof(header), "Charged!"); } diff --git a/applications/settings/power_settings_app/views/battery_info.h b/applications/settings/power_settings_app/views/battery_info.h index e62aa44fabe..7bfacf69e27 100644 --- a/applications/settings/power_settings_app/views/battery_info.h +++ b/applications/settings/power_settings_app/views/battery_info.h @@ -9,6 +9,7 @@ typedef struct { float gauge_voltage; float gauge_current; float gauge_temperature; + float charging_voltage; uint8_t charge; uint8_t health; } BatteryInfoModel; diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 7b07bb1880e..80aaf7fa682 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1194,6 +1194,7 @@ Function,+,furi_hal_power_enable_external_3_3v,void, Function,+,furi_hal_power_enable_otg,void, Function,+,furi_hal_power_gauge_is_ok,_Bool, Function,+,furi_hal_power_get_bat_health_pct,uint8_t, +Function,+,furi_hal_power_get_battery_charging_voltage,float, Function,+,furi_hal_power_get_battery_current,float,FuriHalPowerIC Function,+,furi_hal_power_get_battery_design_capacity,uint32_t, Function,+,furi_hal_power_get_battery_full_capacity,uint32_t, @@ -1212,6 +1213,7 @@ Function,+,furi_hal_power_is_charging_done,_Bool, Function,+,furi_hal_power_is_otg_enabled,_Bool, Function,+,furi_hal_power_off,void, Function,+,furi_hal_power_reset,void, +Function,+,furi_hal_power_set_battery_charging_voltage,void,float Function,+,furi_hal_power_shutdown,void, Function,+,furi_hal_power_sleep,void, Function,+,furi_hal_power_sleep_available,_Bool, diff --git a/firmware/targets/f7/furi_hal/furi_hal_power.c b/firmware/targets/f7/furi_hal/furi_hal_power.c index ddb056906ba..2d709620d56 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_power.c +++ b/firmware/targets/f7/furi_hal/furi_hal_power.c @@ -341,6 +341,20 @@ bool furi_hal_power_is_otg_enabled() { return ret; } +float furi_hal_power_get_battery_charging_voltage() { + furi_hal_i2c_acquire(&furi_hal_i2c_handle_power); + float ret = (float)bq25896_get_vreg_voltage(&furi_hal_i2c_handle_power) / 1000.0f; + furi_hal_i2c_release(&furi_hal_i2c_handle_power); + return ret; +} + +void furi_hal_power_set_battery_charging_voltage(float voltage) { + furi_hal_i2c_acquire(&furi_hal_i2c_handle_power); + // Adding 0.0005 is necessary because 4.016f is 4.015999794000, which gets truncated + bq25896_set_vreg_voltage(&furi_hal_i2c_handle_power, (uint16_t)(voltage * 1000.0f + 0.0005f)); + furi_hal_i2c_release(&furi_hal_i2c_handle_power); +} + void furi_hal_power_check_otg_status() { furi_hal_i2c_acquire(&furi_hal_i2c_handle_power); if(bq25896_check_otg_fault(&furi_hal_i2c_handle_power)) @@ -470,10 +484,10 @@ void furi_hal_power_info_get(PropertyValueCallback out, char sep, void* context) if(sep == '.') { property_value_out(&property_context, NULL, 2, "format", "major", "2"); - property_value_out(&property_context, NULL, 2, "format", "minor", "0"); + property_value_out(&property_context, NULL, 2, "format", "minor", "1"); } else { property_value_out(&property_context, NULL, 3, "power", "info", "major", "1"); - property_value_out(&property_context, NULL, 3, "power", "info", "minor", "0"); + property_value_out(&property_context, NULL, 3, "power", "info", "minor", "1"); } uint8_t charge = furi_hal_power_get_pct(); @@ -481,7 +495,7 @@ void furi_hal_power_info_get(PropertyValueCallback out, char sep, void* context) const char* charge_state; if(furi_hal_power_is_charging()) { - if(charge < 100) { + if((charge < 100) && (!furi_hal_power_is_charging_done())) { charge_state = "charging"; } else { charge_state = "charged"; @@ -491,6 +505,8 @@ void furi_hal_power_info_get(PropertyValueCallback out, char sep, void* context) } property_value_out(&property_context, NULL, 2, "charge", "state", charge_state); + uint16_t charge_voltage = (uint16_t)(furi_hal_power_get_battery_charging_voltage() * 1000.f); + property_value_out(&property_context, "%u", 2, "charge", "voltage", charge_voltage); uint16_t voltage = (uint16_t)(furi_hal_power_get_battery_voltage(FuriHalPowerICFuelGauge) * 1000.f); property_value_out(&property_context, "%u", 2, "battery", "voltage", voltage); @@ -567,6 +583,13 @@ void furi_hal_power_debug_get(PropertyValueCallback out, void* context) { "charger", "vbat", bq25896_get_vbat_voltage(&furi_hal_i2c_handle_power)); + property_value_out( + &property_context, + "%d", + 2, + "charger", + "vreg", + bq25896_get_vreg_voltage(&furi_hal_i2c_handle_power)); property_value_out( &property_context, "%d", diff --git a/firmware/targets/furi_hal_include/furi_hal_power.h b/firmware/targets/furi_hal_include/furi_hal_power.h index a78d09fe4b8..39a11e99f7a 100644 --- a/firmware/targets/furi_hal_include/furi_hal_power.h +++ b/firmware/targets/furi_hal_include/furi_hal_power.h @@ -121,6 +121,22 @@ void furi_hal_power_check_otg_status(); */ bool furi_hal_power_is_otg_enabled(); +/** Get battery charging voltage in V + * + * @return voltage in V + */ +float furi_hal_power_get_battery_charging_voltage(); + +/** Set battery charging voltage in V + * + * Invalid values will be clamped to the nearest valid value. + * + * @param voltage[in] voltage in V + * + * @return voltage in V + */ +void furi_hal_power_set_battery_charging_voltage(float voltage); + /** Get remaining battery battery capacity in mAh * * @return capacity in mAh diff --git a/lib/drivers/bq25896.c b/lib/drivers/bq25896.c index 1fb9d53e71e..7e3008d62df 100644 --- a/lib/drivers/bq25896.c +++ b/lib/drivers/bq25896.c @@ -132,6 +132,33 @@ bool bq25896_is_otg_enabled(FuriHalI2cBusHandle* handle) { return bq25896_regs.r03.OTG_CONFIG; } +uint16_t bq25896_get_vreg_voltage(FuriHalI2cBusHandle* handle) { + furi_hal_i2c_read_reg_8( + handle, BQ25896_ADDRESS, 0x06, (uint8_t*)&bq25896_regs.r06, BQ25896_I2C_TIMEOUT); + return (uint16_t)bq25896_regs.r06.VREG * 16 + 3840; +} + +void bq25896_set_vreg_voltage(FuriHalI2cBusHandle* handle, uint16_t vreg_voltage) { + if(vreg_voltage < 3840) { + // Minimum value is 3840 mV + bq25896_regs.r06.VREG = 0; + } else { + // Find the nearest voltage value (subtract offset, divide into sections) + // Values are truncated downward as needed (e.g. 4200mV -> 4192 mV) + bq25896_regs.r06.VREG = (uint8_t)((vreg_voltage - 3840) / 16); + } + + // Do not allow values above 23 (0x17, 4208mV) + // Exceeding 4.2v will overcharge the battery! + if(bq25896_regs.r06.VREG > 23) { + bq25896_regs.r06.VREG = 23; + } + + // Apply changes + furi_hal_i2c_write_reg_8( + handle, BQ25896_ADDRESS, 0x06, *(uint8_t*)&bq25896_regs.r06, BQ25896_I2C_TIMEOUT); +} + bool bq25896_check_otg_fault(FuriHalI2cBusHandle* handle) { furi_hal_i2c_read_reg_8( handle, BQ25896_ADDRESS, 0x0C, (uint8_t*)&bq25896_regs.r0C, BQ25896_I2C_TIMEOUT); diff --git a/lib/drivers/bq25896.h b/lib/drivers/bq25896.h index c8da0a0643d..c8a8526a163 100644 --- a/lib/drivers/bq25896.h +++ b/lib/drivers/bq25896.h @@ -36,6 +36,15 @@ void bq25896_disable_otg(FuriHalI2cBusHandle* handle); /** Is otg enabled */ bool bq25896_is_otg_enabled(FuriHalI2cBusHandle* handle); +/** Get VREG (charging) voltage in mV */ +uint16_t bq25896_get_vreg_voltage(FuriHalI2cBusHandle* handle); + +/** Set VREG (charging) voltage in mV + * + * Valid range: 3840mV - 4208mV, in steps of 16mV + */ +void bq25896_set_vreg_voltage(FuriHalI2cBusHandle* handle, uint16_t vreg_voltage); + /** Check OTG BOOST Fault status */ bool bq25896_check_otg_fault(FuriHalI2cBusHandle* handle); From df808be8d741b8920d09a92671ddce7712b4f519 Mon Sep 17 00:00:00 2001 From: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> Date: Thu, 8 Dec 2022 12:31:22 +0300 Subject: [PATCH 268/824] [FL-3003] Fix logical error in storage script (#2105) * Fix logical error in storage script * Fix formatting --- scripts/flipper/storage.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/flipper/storage.py b/scripts/flipper/storage.py index 6b53f7477e4..9c9f529582c 100644 --- a/scripts/flipper/storage.py +++ b/scripts/flipper/storage.py @@ -238,9 +238,9 @@ def read_file(self, filename): while read_size < size: self.read.until("Ready?" + self.CLI_EOL) self.send("y") - read_size = min(size - read_size, buffer_size) - filedata.extend(self.port.read(read_size)) - read_size = read_size + read_size + chunk_size = min(size - read_size, buffer_size) + filedata.extend(self.port.read(chunk_size)) + read_size = read_size + chunk_size percent = str(math.ceil(read_size / size * 100)) total_chunks = str(math.ceil(size / buffer_size)) From b85f533a20c8eb5a6ababf20710263002a9096bd Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Fri, 9 Dec 2022 21:03:19 +0300 Subject: [PATCH 269/824] VCP session close fix (#2108) * VCP thread check before flag set * VCP running flag check * Cli vcp: more clear CLI_VCP_DEBUG logs * Cli: move tag to debug log macro Co-authored-by: SG --- applications/services/cli/cli_vcp.c | 71 +++++++++++++---------------- 1 file changed, 32 insertions(+), 39 deletions(-) diff --git a/applications/services/cli/cli_vcp.c b/applications/services/cli/cli_vcp.c index ad26dca47cd..454c99d7a9c 100644 --- a/applications/services/cli/cli_vcp.c +++ b/applications/services/cli/cli_vcp.c @@ -11,6 +11,12 @@ #define VCP_IF_NUM 0 +#ifdef CLI_VCP_DEBUG +#define VCP_DEBUG(...) FURI_LOG_D(TAG, __VA_ARGS__) +#else +#define VCP_DEBUG(...) +#endif + typedef enum { VcpEvtStop = (1 << 0), VcpEvtConnect = (1 << 1), @@ -104,9 +110,8 @@ static int32_t vcp_worker(void* context) { // VCP session opened if(flags & VcpEvtConnect) { -#ifdef CLI_VCP_DEBUG - FURI_LOG_D(TAG, "Connect"); -#endif + VCP_DEBUG("Connect"); + if(vcp->connected == false) { vcp->connected = true; furi_stream_buffer_send(vcp->rx_stream, &ascii_soh, 1, FuriWaitForever); @@ -115,9 +120,8 @@ static int32_t vcp_worker(void* context) { // VCP session closed if(flags & VcpEvtDisconnect) { -#ifdef CLI_VCP_DEBUG - FURI_LOG_D(TAG, "Disconnect"); -#endif + VCP_DEBUG("Disconnect"); + if(vcp->connected == true) { vcp->connected = false; furi_stream_buffer_receive(vcp->tx_stream, vcp->data_buffer, USB_CDC_PKT_LEN, 0); @@ -127,9 +131,8 @@ static int32_t vcp_worker(void* context) { // Rx buffer was read, maybe there is enough space for new data? if((flags & VcpEvtStreamRx) && (missed_rx > 0)) { -#ifdef CLI_VCP_DEBUG - FURI_LOG_D(TAG, "StreamRx"); -#endif + VCP_DEBUG("StreamRx"); + if(furi_stream_buffer_spaces_available(vcp->rx_stream) >= USB_CDC_PKT_LEN) { flags |= VcpEvtRx; missed_rx--; @@ -140,9 +143,8 @@ static int32_t vcp_worker(void* context) { if(flags & VcpEvtRx) { if(furi_stream_buffer_spaces_available(vcp->rx_stream) >= USB_CDC_PKT_LEN) { int32_t len = furi_hal_cdc_receive(VCP_IF_NUM, vcp->data_buffer, USB_CDC_PKT_LEN); -#ifdef CLI_VCP_DEBUG - FURI_LOG_D(TAG, "Rx %d", len); -#endif + VCP_DEBUG("Rx %ld", len); + if(len > 0) { furi_check( furi_stream_buffer_send( @@ -150,18 +152,15 @@ static int32_t vcp_worker(void* context) { (size_t)len); } } else { -#ifdef CLI_VCP_DEBUG - FURI_LOG_D(TAG, "Rx missed"); -#endif + VCP_DEBUG("Rx missed"); missed_rx++; } } // New data in Tx buffer if(flags & VcpEvtStreamTx) { -#ifdef CLI_VCP_DEBUG - FURI_LOG_D(TAG, "StreamTx"); -#endif + VCP_DEBUG("StreamTx"); + if(tx_idle) { flags |= VcpEvtTx; } @@ -171,9 +170,9 @@ static int32_t vcp_worker(void* context) { if(flags & VcpEvtTx) { size_t len = furi_stream_buffer_receive(vcp->tx_stream, vcp->data_buffer, USB_CDC_PKT_LEN, 0); -#ifdef CLI_VCP_DEBUG - FURI_LOG_D(TAG, "Tx %d", len); -#endif + + VCP_DEBUG("Tx %d", len); + if(len > 0) { // Some data left in Tx buffer. Sending it now tx_idle = false; furi_hal_cdc_send(VCP_IF_NUM, vcp->data_buffer, len); @@ -216,9 +215,7 @@ static size_t cli_vcp_rx(uint8_t* buffer, size_t size, uint32_t timeout) { return 0; } -#ifdef CLI_VCP_DEBUG - FURI_LOG_D(TAG, "rx %u start", size); -#endif + VCP_DEBUG("rx %u start", size); size_t rx_cnt = 0; @@ -227,19 +224,21 @@ static size_t cli_vcp_rx(uint8_t* buffer, size_t size, uint32_t timeout) { if(batch_size > VCP_RX_BUF_SIZE) batch_size = VCP_RX_BUF_SIZE; size_t len = furi_stream_buffer_receive(vcp->rx_stream, buffer, batch_size, timeout); -#ifdef CLI_VCP_DEBUG - FURI_LOG_D(TAG, "rx %u ", batch_size); -#endif + VCP_DEBUG("rx %u ", batch_size); + if(len == 0) break; + if(vcp->running == false) { + // EOT command is received after VCP session close + rx_cnt += len; + break; + } furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtStreamRx); size -= len; buffer += len; rx_cnt += len; } -#ifdef CLI_VCP_DEBUG - FURI_LOG_D(TAG, "rx %u end", size); -#endif + VCP_DEBUG("rx %u end", size); return rx_cnt; } @@ -251,9 +250,7 @@ static void cli_vcp_tx(const uint8_t* buffer, size_t size) { return; } -#ifdef CLI_VCP_DEBUG - FURI_LOG_D(TAG, "tx %u start", size); -#endif + VCP_DEBUG("tx %u start", size); while(size > 0 && vcp->connected) { size_t batch_size = size; @@ -261,17 +258,13 @@ static void cli_vcp_tx(const uint8_t* buffer, size_t size) { furi_stream_buffer_send(vcp->tx_stream, buffer, batch_size, FuriWaitForever); furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtStreamTx); -#ifdef CLI_VCP_DEBUG - FURI_LOG_D(TAG, "tx %u", batch_size); -#endif + VCP_DEBUG("tx %u", batch_size); size -= batch_size; buffer += batch_size; } -#ifdef CLI_VCP_DEBUG - FURI_LOG_D(TAG, "tx %u end", size); -#endif + VCP_DEBUG("tx %u end", size); } static void cli_vcp_tx_stdout(const char* data, size_t size) { From 7fb1af07b8014b843cb6abda9ce2be93d14bda99 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Sat, 10 Dec 2022 15:02:34 +0200 Subject: [PATCH 270/824] [FL- 3014] Untangle NFC from Unit Tests (#2106) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Untangle NFC from Unit Tests * nfc tests: add log error Co-authored-by: Sergey Gavrilov Co-authored-by: gornekich Co-authored-by: あく --- applications/debug/unit_tests/nfc/nfc_test.c | 7 +++++-- applications/main/nfc/nfc_i.h | 4 +--- .../main/nfc/scenes/nfc_scene_generate_info.c | 9 +++++++-- .../main/nfc/scenes/nfc_scene_set_type.c | 2 +- fbt_options.py | 1 - .../main => lib}/nfc/helpers/nfc_generators.c | 17 ----------------- .../main => lib}/nfc/helpers/nfc_generators.h | 7 +++---- 7 files changed, 17 insertions(+), 30 deletions(-) rename {applications/main => lib}/nfc/helpers/nfc_generators.c (95%) rename {applications/main => lib}/nfc/helpers/nfc_generators.h (79%) diff --git a/applications/debug/unit_tests/nfc/nfc_test.c b/applications/debug/unit_tests/nfc/nfc_test.c index 4218482c7af..e9e7b35f642 100644 --- a/applications/debug/unit_tests/nfc/nfc_test.c +++ b/applications/debug/unit_tests/nfc/nfc_test.c @@ -6,7 +6,7 @@ #include #include #include -#include +#include #include #include @@ -102,7 +102,10 @@ static bool nfc_test_digital_signal_test_encode( do { // Read test data - if(!nfc_test_read_signal_from_file(file_name)) break; + if(!nfc_test_read_signal_from_file(file_name)) { + FURI_LOG_E(TAG, "Failed to read signal from file"); + break; + } // Encode signal FURI_CRITICAL_ENTER(); diff --git a/applications/main/nfc/nfc_i.h b/applications/main/nfc/nfc_i.h index 57eefbf676a..d49b8476649 100644 --- a/applications/main/nfc/nfc_i.h +++ b/applications/main/nfc/nfc_i.h @@ -27,6 +27,7 @@ #include #include #include +#include #include "views/dict_attack.h" #include "views/detect_reader.h" @@ -50,9 +51,6 @@ typedef enum { NfcRpcStateEmulated, } NfcRpcState; -// Forward declaration due to circular dependency -typedef struct NfcGenerator NfcGenerator; - struct Nfc { NfcWorker* worker; ViewDispatcher* view_dispatcher; diff --git a/applications/main/nfc/scenes/nfc_scene_generate_info.c b/applications/main/nfc/scenes/nfc_scene_generate_info.c index 66900767e54..7b84ae43b11 100644 --- a/applications/main/nfc/scenes/nfc_scene_generate_info.c +++ b/applications/main/nfc/scenes/nfc_scene_generate_info.c @@ -1,5 +1,5 @@ #include "../nfc_i.h" -#include "../helpers/nfc_generators.h" +#include "lib/nfc/helpers/nfc_generators.h" void nfc_scene_generate_info_dialog_callback(DialogExResult result, void* context) { Nfc* nfc = context; @@ -39,7 +39,12 @@ bool nfc_scene_generate_info_on_event(void* context, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { if(event.event == DialogExResultRight) { - scene_manager_next_scene(nfc->scene_manager, nfc->generator->next_scene); + // Switch either to NfcSceneMfClassicMenu or NfcSceneMfUltralightMenu + if(nfc->dev->dev_data.protocol == NfcDeviceProtocolMifareClassic) { + scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicMenu); + } else if(nfc->dev->dev_data.protocol == NfcDeviceProtocolMifareUl) { + scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightMenu); + } consumed = true; } } diff --git a/applications/main/nfc/scenes/nfc_scene_set_type.c b/applications/main/nfc/scenes/nfc_scene_set_type.c index 3e08aeb3fe1..cadf2eb69ef 100644 --- a/applications/main/nfc/scenes/nfc_scene_set_type.c +++ b/applications/main/nfc/scenes/nfc_scene_set_type.c @@ -1,5 +1,5 @@ #include "../nfc_i.h" -#include "../helpers/nfc_generators.h" +#include "lib/nfc/helpers/nfc_generators.h" enum SubmenuIndex { SubmenuIndexNFCA4, diff --git a/fbt_options.py b/fbt_options.py index 7a805c996ab..4850389ad54 100644 --- a/fbt_options.py +++ b/fbt_options.py @@ -81,7 +81,6 @@ "basic_services", "updater_app", "unit_tests", - "nfc", ], } diff --git a/applications/main/nfc/helpers/nfc_generators.c b/lib/nfc/helpers/nfc_generators.c similarity index 95% rename from applications/main/nfc/helpers/nfc_generators.c rename to lib/nfc/helpers/nfc_generators.c index 5f0527c6a50..769b9c7b6bb 100644 --- a/applications/main/nfc/helpers/nfc_generators.c +++ b/lib/nfc/helpers/nfc_generators.c @@ -376,103 +376,86 @@ static void nfc_generate_mf_classic_4k_7b_uid(NfcDeviceData* data) { static const NfcGenerator mf_ul_generator = { .name = "Mifare Ultralight", .generator_func = nfc_generate_mf_ul_orig, - .next_scene = NfcSceneMfUltralightMenu, }; static const NfcGenerator mf_ul_11_generator = { .name = "Mifare Ultralight EV1 11", .generator_func = nfc_generate_mf_ul_11, - .next_scene = NfcSceneMfUltralightMenu, }; static const NfcGenerator mf_ul_h11_generator = { .name = "Mifare Ultralight EV1 H11", .generator_func = nfc_generate_mf_ul_h11, - .next_scene = NfcSceneMfUltralightMenu, }; static const NfcGenerator mf_ul_21_generator = { .name = "Mifare Ultralight EV1 21", .generator_func = nfc_generate_mf_ul_21, - .next_scene = NfcSceneMfUltralightMenu, }; static const NfcGenerator mf_ul_h21_generator = { .name = "Mifare Ultralight EV1 H21", .generator_func = nfc_generate_mf_ul_h21, - .next_scene = NfcSceneMfUltralightMenu, }; static const NfcGenerator ntag203_generator = { .name = "NTAG203", .generator_func = nfc_generate_mf_ul_ntag203, - .next_scene = NfcSceneMfUltralightMenu, }; static const NfcGenerator ntag213_generator = { .name = "NTAG213", .generator_func = nfc_generate_ntag213, - .next_scene = NfcSceneMfUltralightMenu, }; static const NfcGenerator ntag215_generator = { .name = "NTAG215", .generator_func = nfc_generate_ntag215, - .next_scene = NfcSceneMfUltralightMenu, }; static const NfcGenerator ntag216_generator = { .name = "NTAG216", .generator_func = nfc_generate_ntag216, - .next_scene = NfcSceneMfUltralightMenu, }; static const NfcGenerator ntag_i2c_1k_generator = { .name = "NTAG I2C 1k", .generator_func = nfc_generate_ntag_i2c_1k, - .next_scene = NfcSceneMfUltralightMenu, }; static const NfcGenerator ntag_i2c_2k_generator = { .name = "NTAG I2C 2k", .generator_func = nfc_generate_ntag_i2c_2k, - .next_scene = NfcSceneMfUltralightMenu, }; static const NfcGenerator ntag_i2c_plus_1k_generator = { .name = "NTAG I2C Plus 1k", .generator_func = nfc_generate_ntag_i2c_plus_1k, - .next_scene = NfcSceneMfUltralightMenu, }; static const NfcGenerator ntag_i2c_plus_2k_generator = { .name = "NTAG I2C Plus 2k", .generator_func = nfc_generate_ntag_i2c_plus_2k, - .next_scene = NfcSceneMfUltralightMenu, }; static const NfcGenerator mifare_classic_1k_4b_uid_generator = { .name = "Mifare Classic 1k 4byte UID", .generator_func = nfc_generate_mf_classic_1k_4b_uid, - .next_scene = NfcSceneMfClassicMenu, }; static const NfcGenerator mifare_classic_1k_7b_uid_generator = { .name = "Mifare Classic 1k 7byte UID", .generator_func = nfc_generate_mf_classic_1k_7b_uid, - .next_scene = NfcSceneMfClassicMenu, }; static const NfcGenerator mifare_classic_4k_4b_uid_generator = { .name = "Mifare Classic 4k 4byte UID", .generator_func = nfc_generate_mf_classic_4k_4b_uid, - .next_scene = NfcSceneMfClassicMenu, }; static const NfcGenerator mifare_classic_4k_7b_uid_generator = { .name = "Mifare Classic 4k 7byte UID", .generator_func = nfc_generate_mf_classic_4k_7b_uid, - .next_scene = NfcSceneMfClassicMenu, }; const NfcGenerator* const nfc_generators[] = { diff --git a/applications/main/nfc/helpers/nfc_generators.h b/lib/nfc/helpers/nfc_generators.h similarity index 79% rename from applications/main/nfc/helpers/nfc_generators.h rename to lib/nfc/helpers/nfc_generators.h index 362a19b1ee8..8cee67067ca 100644 --- a/applications/main/nfc/helpers/nfc_generators.h +++ b/lib/nfc/helpers/nfc_generators.h @@ -1,14 +1,13 @@ #pragma once -#include "../nfc_i.h" +#include "../nfc_device.h" typedef void (*NfcGeneratorFunc)(NfcDeviceData* data); -struct NfcGenerator { +typedef struct { const char* name; NfcGeneratorFunc generator_func; - NfcScene next_scene; -}; +} NfcGenerator; extern const NfcGenerator* const nfc_generators[]; From 9d728a1c65935d9473389fa98c7f1946935e4976 Mon Sep 17 00:00:00 2001 From: Max Andreev Date: Sat, 10 Dec 2022 16:10:51 +0300 Subject: [PATCH 271/824] Check FL ticket in PR name after merge (#2076) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add base * Add base again * Test reporting * Fix reporting * Fix secrets * Fix arguments in report * Remove depricated actions * Disable reporting on PR Co-authored-by: あく --- .github/workflows/amap_analyse.yml | 2 +- .github/workflows/build.yml | 13 ++----- .github/workflows/check_submodules.yml | 6 +-- .github/workflows/lint_c.yml | 2 +- .github/workflows/lint_python.yml | 2 +- .github/workflows/merge_report.yml | 41 ++++++++++++++++++++ .github/workflows/pvs_studio.yml | 12 +----- .github/workflows/unit_tests.yml | 2 +- scripts/get_env.py | 38 +++++++++++------- scripts/merge_report_qa.py | 53 ++++++++++++++++++++++++++ 10 files changed, 130 insertions(+), 41 deletions(-) create mode 100644 .github/workflows/merge_report.yml create mode 100755 scripts/merge_report_qa.py diff --git a/.github/workflows/amap_analyse.yml b/.github/workflows/amap_analyse.yml index cfb1eab14ee..6231c58867d 100644 --- a/.github/workflows/amap_analyse.yml +++ b/.github/workflows/amap_analyse.yml @@ -39,7 +39,7 @@ jobs: fi - name: 'Checkout code' - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: fetch-depth: 0 ref: ${{ github.event.pull_request.head.sha }} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ed5ee6bd7a0..99c272e609f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,7 +24,7 @@ jobs: fi - name: 'Checkout code' - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: fetch-depth: 0 ref: ${{ github.event.pull_request.head.sha }} @@ -35,6 +35,7 @@ jobs: mkdir artifacts - name: 'Get commit details' + id: names run: | if [[ ${{ github.event_name }} == 'pull_request' ]]; then TYPE="pull" @@ -45,14 +46,6 @@ jobs: fi python3 scripts/get_env.py "--event_file=${{ github.event_path }}" "--type=$TYPE" - - name: 'Generate suffixes for comment' - id: names - run: | - echo "::set-output name=branch_name::${BRANCH_NAME}" - echo "::set-output name=commit_sha::${COMMIT_SHA}" - echo "::set-output name=default_target::${DEFAULT_TARGET}" - echo "::set-output name=suffix::${SUFFIX}" - - name: 'Bundle scripts' if: ${{ !github.event.pull_request.head.repo.fork }} run: | @@ -143,7 +136,7 @@ jobs: fi - name: 'Checkout code' - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: fetch-depth: 0 submodules: true diff --git a/.github/workflows/check_submodules.yml b/.github/workflows/check_submodules.yml index eba4affc3d4..2eb2027c925 100644 --- a/.github/workflows/check_submodules.yml +++ b/.github/workflows/check_submodules.yml @@ -20,7 +20,7 @@ jobs: fi - name: 'Checkout code' - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: fetch-depth: 0 ref: ${{ github.event.pull_request.head.sha }} @@ -36,12 +36,12 @@ jobs: BRANCHES=$(git branch -r --contains "$SUBMODULE_HASH"); COMMITS_IN_BRANCH="$(git rev-list --count dev)"; if [ $COMMITS_IN_BRANCH -lt $SUB_COMMITS_MIN ]; then - echo "::set-output name=fails::error"; + echo "name=fails::error" >> $GITHUB_OUTPUT echo "::error::Error: Too low commits in $SUB_BRANCH of submodule $SUB_PATH: $COMMITS_IN_BRANCH(expected $SUB_COMMITS_MIN+)"; exit 1; fi if ! grep -q "/$SUB_BRANCH" <<< "$BRANCHES"; then - echo "::set-output name=fails::error"; + echo "name=fails::error" >> $GITHUB_OUTPUT echo "::error::Error: Submodule $SUB_PATH is not on branch $SUB_BRANCH"; exit 1; fi diff --git a/.github/workflows/lint_c.yml b/.github/workflows/lint_c.yml index 23dc6c699aa..71ec24ff659 100644 --- a/.github/workflows/lint_c.yml +++ b/.github/workflows/lint_c.yml @@ -23,7 +23,7 @@ jobs: fi - name: 'Checkout code' - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: fetch-depth: 0 ref: ${{ github.event.pull_request.head.sha }} diff --git a/.github/workflows/lint_python.yml b/.github/workflows/lint_python.yml index c2f09211003..44f233db840 100644 --- a/.github/workflows/lint_python.yml +++ b/.github/workflows/lint_python.yml @@ -20,7 +20,7 @@ jobs: fi - name: 'Checkout code' - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: fetch-depth: 0 ref: ${{ github.event.pull_request.head.sha }} diff --git a/.github/workflows/merge_report.yml b/.github/workflows/merge_report.yml new file mode 100644 index 00000000000..b6c08905607 --- /dev/null +++ b/.github/workflows/merge_report.yml @@ -0,0 +1,41 @@ +name: 'Check FL ticket in PR name' + +on: + push: + branches: + - dev +jobs: + merge_report: + runs-on: [self-hosted,FlipperZeroShell] + steps: + - name: 'Decontaminate previous build leftovers' + run: | + if [ -d .git ]; then + git submodule status || git checkout "$(git rev-list --max-parents=0 HEAD | tail -n 1)" + fi + + - name: 'Checkout code' + uses: actions/checkout@v3 + with: + fetch-depth: 0 + ref: ${{ github.event.pull_request.head.sha }} + + - name: 'Get commit details' + run: | + if [[ ${{ github.event_name }} == 'pull_request' ]]; then + TYPE="pull" + elif [[ "${{ github.ref }}" == "refs/tags/"* ]]; then + TYPE="tag" + else + TYPE="other" + fi + python3 scripts/get_env.py "--event_file=${{ github.event_path }}" "--type=$TYPE" + + - name: 'Check ticket and report' + run: | + FBT_TOOLCHAIN_PATH=/runner/_work source scripts/toolchain/fbtenv.sh + python3 -m pip install slack_sdk + python3 scripts/merge_report_qa.py \ + ${{ secrets.QA_REPORT_SLACK_TOKEN }} \ + ${{ secrets.QA_REPORT_SLACK_CHANNEL }} + diff --git a/.github/workflows/pvs_studio.yml b/.github/workflows/pvs_studio.yml index 9de493a44d7..5473f19f067 100644 --- a/.github/workflows/pvs_studio.yml +++ b/.github/workflows/pvs_studio.yml @@ -25,12 +25,13 @@ jobs: fi - name: 'Checkout code' - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: fetch-depth: 0 ref: ${{ github.event.pull_request.head.sha }} - name: 'Get commit details' + id: names run: | if [[ ${{ github.event_name }} == 'pull_request' ]]; then TYPE="pull" @@ -41,15 +42,6 @@ jobs: fi python3 scripts/get_env.py "--event_file=${{ github.event_path }}" "--type=$TYPE" - - name: 'Generate suffixes for comment' - if: ${{ !github.event.pull_request.head.repo.fork && github.event.pull_request }} - id: names - run: | - echo "::set-output name=branch_name::${BRANCH_NAME}" - echo "::set-output name=commit_sha::${COMMIT_SHA}" - echo "::set-output name=default_target::${DEFAULT_TARGET}" - echo "::set-output name=suffix::${SUFFIX}" - - name: 'Make reports directory' run: | rm -rf reports/ diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 1ca4a9c03f9..4eab21c8778 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -12,7 +12,7 @@ jobs: runs-on: [self-hosted, FlipperZeroTest] steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: fetch-depth: 0 ref: ${{ github.event.pull_request.head.sha }} diff --git a/scripts/get_env.py b/scripts/get_env.py index d0527309496..e2da6eda5c7 100644 --- a/scripts/get_env.py +++ b/scripts/get_env.py @@ -77,28 +77,38 @@ def add_env(name, value, file): print(f"{delimeter}", file=file) -def add_envs(data, env_file, args): - add_env("COMMIT_MSG", data["commit_comment"], env_file) - add_env("COMMIT_HASH", data["commit_hash"], env_file) - add_env("COMMIT_SHA", data["commit_sha"], env_file) - add_env("SUFFIX", data["suffix"], env_file) - add_env("BRANCH_NAME", data["branch_name"], env_file) - add_env("DIST_SUFFIX", data["suffix"], env_file) - add_env("WORKFLOW_BRANCH_OR_TAG", data["branch_name"], env_file) +def add_set_output_var(name, value, file): + print(f"{name}={value}", file=file) + + +def add_envs(data, gh_env_file, gh_out_file, args): + add_env("COMMIT_MSG", data["commit_comment"], gh_env_file) + add_env("COMMIT_HASH", data["commit_hash"], gh_env_file) + add_env("COMMIT_SHA", data["commit_sha"], gh_env_file) + add_env("SUFFIX", data["suffix"], gh_env_file) + add_env("BRANCH_NAME", data["branch_name"], gh_env_file) + add_env("DIST_SUFFIX", data["suffix"], gh_env_file) + add_env("WORKFLOW_BRANCH_OR_TAG", data["branch_name"], gh_env_file) + add_set_output_var("branch_name", data["branch_name"], gh_out_file) + add_set_output_var("commit_sha", data["commit_sha"], gh_out_file) + add_set_output_var("default_target", os.getenv("DEFAULT_TARGET"), gh_out_file) + add_set_output_var("suffix", data["suffix"], gh_out_file) if args.type == "pull": - add_env("PULL_ID", data["pull_id"], env_file) - add_env("PULL_NAME", data["pull_name"], env_file) + add_env("PULL_ID", data["pull_id"], gh_env_file) + add_env("PULL_NAME", data["pull_name"], gh_env_file) def main(): args = parse_args() - event_file = open(args.event_file) + event_file = open(args.event_file, "r") event = json.load(event_file) - env_file = open(os.environ["GITHUB_ENV"], "a") + gh_env_file = open(os.environ["GITHUB_ENV"], "a") + gh_out_file = open(os.environ["GITHUB_OUTPUT"], "a") data = get_details(event, args) - add_envs(data, env_file, args) + add_envs(data, gh_env_file, gh_out_file, args) event_file.close() - env_file.close() + gh_env_file.close() + gh_out_file.close() if __name__ == "__main__": diff --git a/scripts/merge_report_qa.py b/scripts/merge_report_qa.py new file mode 100755 index 00000000000..c0707848eed --- /dev/null +++ b/scripts/merge_report_qa.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 + +import os +import re +import sys +import argparse +from slack_sdk import WebClient +from slack_sdk.errors import SlackApiError + + +def parse_args(): + parser = argparse.ArgumentParser() + parser.add_argument("slack_token") + parser.add_argument("slack_channel") + args = parser.parse_args() + return args + + +def checkCommitMessage(msg): + regex = re.compile(r"^'?\[FL-\d+\]") + if regex.match(msg): + return True + return False + + +def reportSlack(commit_hash, slack_token, slack_channel, message): + client = WebClient(token=slack_token) + try: + client.chat_postMessage(channel="#" + slack_channel, text=message) + except SlackApiError as e: + print(e) + sys.exit(1) + + +def main(): + args = parse_args() + commit_msg = os.getenv("COMMIT_MSG") + commit_hash = os.getenv("COMMIT_HASH") + commit_sha = os.getenv("COMMIT_SHA") + commit_link = ( + "" + ) + message = "Commit " + commit_link + " merged to dev without 'FL' ticket!" + if not checkCommitMessage(commit_msg): + reportSlack(commit_hash, args.slack_token, args.slack_channel, message) + + +if __name__ == "__main__": + main() From 01e24f683793f6ea88ab344c1b8b5fcab90429cb Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sat, 10 Dec 2022 18:30:03 +0300 Subject: [PATCH 272/824] WS: Show received signal age (#2087) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Feat: Show received signal age: by @LY2NEO with some fixes from me * WS: Signal age display GUI fixes and improvements * WeatherStation: refactor variable names * WS: GUI fixes and improvements: add icons by @Karator and apply UI changes * Weather Station: proper event flow for view redraw. Co-authored-by: あく --- .../weather_station/images/Humid_8x13.png | Bin 0 -> 3618 bytes .../weather_station/images/Timer_11x11.png | Bin 0 -> 3616 bytes .../weather_station/protocols/ws_generic.c | 17 ++++ .../weather_station/protocols/ws_generic.h | 1 + .../views/weather_station_receiver.c | 4 +- .../views/weather_station_receiver_info.c | 88 +++++++++++++++--- 6 files changed, 94 insertions(+), 16 deletions(-) create mode 100644 applications/plugins/weather_station/images/Humid_8x13.png create mode 100644 applications/plugins/weather_station/images/Timer_11x11.png diff --git a/applications/plugins/weather_station/images/Humid_8x13.png b/applications/plugins/weather_station/images/Humid_8x13.png new file mode 100644 index 0000000000000000000000000000000000000000..6d8c71b000699e148e9480f7ddf9795b33e2fe7f GIT binary patch literal 3618 zcmaJ@c|25m|35C-w`57u9YeO5&DKoDGBdVpVPvGmFk?&_Gse_dBC=PPq^uFizEl(m zWy>0(>|3@Z7tgiCO?bwAZuj~9@#{IS^E%(p_j^9?_h);b*XzWbvN018JR}GJfQW@T z&YrXK@7es^oM-mo3C;^a6Dk&a$^wf8F_?4@>LoG&_zkB!Q1A}((&&xxHH>9+$X!di zy%ayl9&~Pes*cVL7S+0<9t~yryaZCOXNx&!| z7LyAYnR11sCo4MunLL1Nhr8P}a7q(!Rk`-*JrI(wH%KnKXtIKcA+ zP~3g`h6zA`0g@h;O-Nu+6M$Jbd6)xFDuKE#aiKDRUl@SdMMtOsJb{2~tD>SG5S{`^ znyxtM|8cBTd`_LysgyGPDkY>zs0+WQ51*40RSNFjF;k6ySnYyC0g3mr5jrzdO`EcYu;V3o7?oxY zI}eX8@pzsW%DlXB)1yqx=sA!%KkT&1*z1i+*6pgHq1l<4!IMoG7h=0p&<>^HLY>q0 zr9Xr9zi+I6d^M#MiZ~Z)#pW@8ER|@TZmwyj#vT&;+s7p@U zN%+L#Qg5vya=FF&$)AdwNw!&u zUjIRrpF6}eY_glZyKJ~^mU$Ei@vyk#0|4i7N)UW|xnT=OYPif$^(V%1YxM^;>Ua;= z?;EWb`tGV5j!|lAz=&f6Ng;=su4={CF{+WBPvq5Ip&yLowd?FWBNG^+kOs#WqG*QL zHzI#Vy=qOU0FQAi{{f=Ha5R_O4T54Uzf4NRrb4|rkHk$SP+PR59oRBn#~f~d0}paE zmtR3Me?dl_HGLU>q7^_~{~lRm2EQ9xW{3VD{2W`AuXiZi^r6r@5(}OhC!Lx0j`{2m z`j&3i+`A%AvEeuaYzwUJ^FcnXrb{qLb0g;IaSee4_l~FFV&S6ZLr+c@b63Z#yLUfj z^GJl6)CuVFurVOw5o2?L6~SiEJRfveNqhgWfSv$%xLtz^I3eHinexm1e>NR-L%^d5 z<{FCq5^)Eh;(^iFCOsvI7%W1i>h>=dPaolXC3;PJz3mm}H44(S%?~Liv<;KI%J`6X zH9*H&BWBWP8fUa-w#I!vkBw_iLdJ1ah`JBnVVP6%@ZS4Fo-&>r)W@G$FZYk#J7Sac&Z)O!-t2SI zXYMt&ut=m-SW7fTRW|J)-$9Bj`{3hbt6bUlH)UJ!Fg^G}@?45o3f+;QUZH+fD!yIt z-pPB)_vF-}_=3XR!tp{O$5qD;d|bhKhoDkZM=gix0)Y>SMUI8(rxqOK94G}R@}mkV z`E}SoxCky zeG^?+kcGr*oz!wFw_m;MVaPX~?6Y~FWg{@BnwPX1d}Ca4S#3&9E?3*C3Qj)jRhXER zNGLKdvMVxMsMRf9%uCO$HK}&q3KcbOIjM41#f%cywJ&|nVaQ=DPcTo~8jV^ng%o<_ z$YoXI*ss0wmXb4Goe#;dqUVkK*Uo)A90c9QZ_~czt(yrGc*}*Act?c04(h+r@uBO> zLt94vu*05fG{WW(?-7$G!{e)Z^t1a+e=`-kMQuJitu#$*rZs0P^C~MSTUvjyUP`sM zuF6%*Jz;gis-^R7=flqa6rD6Qd;l?*HkUS#Hc{z%#_x&^QmWPoL^-^8$ORpxrFRn&SrB4Y>2g)QvThB54v$`7A zBJ!jQAQBp=L?f$co8x!?Wh}0qFMaFi$^rJ#SV8{=`34FY+N0YOJ%~N4e#B&!`Tl^OaG^P9Cp2W7?64MH$CB7vGk* zkKER~zx-f#QKCU&@=irgq@|OlJmFJq@kL~rzK{Qi;I!1fW09wMi}hdJs8FZ%*%mE2 zC6xx(DhF75g`Tf(zh3{G%WFZ%QE)aQXkm0<@tiFI>OAqB_$@MB&Oj>WMyce8Op?^K zLDf;eS-B{B`|Fg^yUz-WnyN_M9=#s(pT;#aTtpKKlRhPhdW#GVKNFca{cLgltH}s7 zsZ({NI;;X)mHk@(MGZNxt*i5dA^s754gU?VyVN`OoH(%Q-LoVYSo2l;_r4LAnvHFP zwpSyLT#nX#9)093i>>kv!_t_-`OU;F+PM-Nn$KbjcQ5xgpQ32RK-Gsn`Cc^MKCb`R zf|+Q`udjB}m)V*kx+0Fh-EW>!WZ?W~<~IZ;Hjap(hOgWTES}_h|LYZbiahipCUqs% zG|eG(%f-#*rR`gTp8hZ60pHC=eigf~t?%rAauwf39iG4bK7q2*eJlN5dQdRr&r#Qr zhZTWy?p+fX#puf~#aWZRCc8K1PSl*}I=k|MwNf@Rd%)?1Q|e>X1=<(Z7yX@t_qHw7 z_p4J&tIm2=Ed|s*5A@iWm&?%W8e6ON|3iAWzb^xc9;;mqpl`g{Sf7v{3udZpcXd<` zu~n8zYHVvRtQjpD4`Iim`V3umMhBNiuU)KTXRh{)nr-k#gmv%4ug8gD_r;~ebwr9p zE@T`xKq99MncMT<^RV5dZsiP_orgOer83gc;LW~;fv%q9o~)#mq=eVBt2x_W>K0@l zk2E(lA9>a0rv*R1c6w{Eo;}KzU(TKovz@sLx~978`RCJhhj)2f39<2a8Q)k^y59-Hi;gpb;r#doq#a@6$%s2LNtWDxSb1SX-go=`;v& z&j;d1V{p&_pl|5MAi8^zSs*tuh3bt4FIT??gQz4l*h$A4X3fBoJ*nmaOtM3O4cU4KTU66#UBhfvadUn%3x9H z-k?23q8t4(3k~KZ`=2UkjDKjoegEzhr)N+NO z`~MRA;{6$9s6E-2ewpdcnVpB?UML0%%On$7bS9oozx1P#r#$H_y00gl0YYd&;2>3N zqC3@l??mk{h_yA!!rPZc^mZp(;LuuZ>-B#FK}s^x literal 0 HcmV?d00001 diff --git a/applications/plugins/weather_station/images/Timer_11x11.png b/applications/plugins/weather_station/images/Timer_11x11.png new file mode 100644 index 0000000000000000000000000000000000000000..21ad47f4b2bf69d81929187ca63d7a3d1b111ea4 GIT binary patch literal 3616 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 Y4M;=-T9{t95channel; if(!flipper_format_write_uint32(flipper_format, "Ch", &temp_data, 1)) { FURI_LOG_E(TAG, "Unable to add Channel"); @@ -168,6 +179,12 @@ bool ws_block_generic_deserialize(WSBlockGeneric* instance, FlipperFormat* flipp } instance->humidity = (uint8_t)temp_data; + if(!flipper_format_read_uint32(flipper_format, "Ts", (uint32_t*)&temp_data, 1)) { + FURI_LOG_E(TAG, "Missing timestamp"); + break; + } + instance->timestamp = (uint32_t)temp_data; + if(!flipper_format_read_uint32(flipper_format, "Ch", (uint32_t*)&temp_data, 1)) { FURI_LOG_E(TAG, "Missing Channel"); break; diff --git a/applications/plugins/weather_station/protocols/ws_generic.h b/applications/plugins/weather_station/protocols/ws_generic.h index b2a84df8e67..657f8a1fc96 100644 --- a/applications/plugins/weather_station/protocols/ws_generic.h +++ b/applications/plugins/weather_station/protocols/ws_generic.h @@ -29,6 +29,7 @@ struct WSBlockGeneric { uint8_t data_count_bit; uint8_t battery_low; uint8_t humidity; + uint32_t timestamp; uint8_t channel; uint8_t btn; float temp; diff --git a/applications/plugins/weather_station/views/weather_station_receiver.c b/applications/plugins/weather_station/views/weather_station_receiver.c index 61b1526029c..124065e3294 100644 --- a/applications/plugins/weather_station/views/weather_station_receiver.c +++ b/applications/plugins/weather_station/views/weather_station_receiver.c @@ -8,7 +8,7 @@ #include #define FRAME_HEIGHT 12 -#define MAX_LEN_PX 100 +#define MAX_LEN_PX 112 #define MENU_ITEMS 4u #define UNLOCK_CNT 3 @@ -189,7 +189,7 @@ void ws_view_receiver_draw(Canvas* canvas, WSReceiverModel* model) { canvas_set_color(canvas, ColorBlack); } canvas_draw_icon(canvas, 4, 2 + i * FRAME_HEIGHT, ReceiverItemIcons[item_menu->type]); - canvas_draw_str(canvas, 15, 9 + i * FRAME_HEIGHT, furi_string_get_cstr(str_buff)); + canvas_draw_str(canvas, 14, 9 + i * FRAME_HEIGHT, furi_string_get_cstr(str_buff)); furi_string_reset(str_buff); } if(scrollbar) { diff --git a/applications/plugins/weather_station/views/weather_station_receiver_info.c b/applications/plugins/weather_station/views/weather_station_receiver_info.c index 49b447f10dc..c2fa0064112 100644 --- a/applications/plugins/weather_station/views/weather_station_receiver_info.c +++ b/applications/plugins/weather_station/views/weather_station_receiver_info.c @@ -9,9 +9,11 @@ struct WSReceiverInfo { View* view; + FuriTimer* timer; }; typedef struct { + uint32_t curr_ts; FuriString* protocol_name; WSBlockGeneric* generic; } WSReceiverInfoModel; @@ -28,6 +30,10 @@ void ws_view_receiver_info_update(WSReceiverInfo* ws_receiver_info, FlipperForma flipper_format_read_string(fff, "Protocol", model->protocol_name); ws_block_generic_deserialize(model->generic, fff); + + FuriHalRtcDateTime curr_dt; + furi_hal_rtc_get_datetime(&curr_dt); + model->curr_ts = furi_hal_rtc_datetime_to_timestamp(&curr_dt); }, true); } @@ -44,46 +50,76 @@ void ws_view_receiver_info_draw(Canvas* canvas, WSReceiverInfoModel* model) { "%s %db", furi_string_get_cstr(model->protocol_name), model->generic->data_count_bit); - canvas_draw_str(canvas, 5, 8, buffer); + canvas_draw_str(canvas, 0, 8, buffer); if(model->generic->channel != WS_NO_CHANNEL) { snprintf(buffer, sizeof(buffer), "Ch: %01d", model->generic->channel); - canvas_draw_str(canvas, 105, 8, buffer); + canvas_draw_str(canvas, 106, 8, buffer); } if(model->generic->id != WS_NO_ID) { snprintf(buffer, sizeof(buffer), "Sn: 0x%02lX", model->generic->id); - canvas_draw_str(canvas, 5, 20, buffer); + canvas_draw_str(canvas, 0, 20, buffer); } if(model->generic->btn != WS_NO_BTN) { snprintf(buffer, sizeof(buffer), "Btn: %01d", model->generic->btn); - canvas_draw_str(canvas, 62, 20, buffer); + canvas_draw_str(canvas, 57, 20, buffer); } if(model->generic->battery_low != WS_NO_BATT) { snprintf( buffer, sizeof(buffer), "Batt: %s", (!model->generic->battery_low ? "ok" : "low")); - canvas_draw_str(canvas, 90, 20, buffer); + canvas_draw_str_aligned(canvas, 126, 17, AlignRight, AlignCenter, buffer); } snprintf(buffer, sizeof(buffer), "Data: 0x%llX", model->generic->data); - canvas_draw_str(canvas, 5, 32, buffer); + canvas_draw_str(canvas, 0, 32, buffer); - elements_bold_rounded_frame(canvas, 2, 37, 123, 25); + elements_bold_rounded_frame(canvas, 0, 38, 127, 25); canvas_set_font(canvas, FontPrimary); if(model->generic->temp != WS_NO_TEMPERATURE) { - canvas_draw_icon(canvas, 18, 42, &I_Therm_7x16); + canvas_draw_icon(canvas, 6, 43, &I_Therm_7x16); snprintf(buffer, sizeof(buffer), "%3.1f C", (double)model->generic->temp); - canvas_draw_str_aligned(canvas, 63, 46, AlignRight, AlignTop, buffer); - canvas_draw_circle(canvas, 55, 45, 1); + canvas_draw_str_aligned(canvas, 47, 47, AlignRight, AlignTop, buffer); + canvas_draw_circle(canvas, 38, 46, 1); } if(model->generic->humidity != WS_NO_HUMIDITY) { - canvas_draw_icon(canvas, 75, 42, &I_Humid_10x15); + canvas_draw_icon(canvas, 53, 44, &I_Humid_8x13); snprintf(buffer, sizeof(buffer), "%d%%", model->generic->humidity); - canvas_draw_str(canvas, 91, 54, buffer); + canvas_draw_str(canvas, 64, 55, buffer); + } + + if((int)model->generic->timestamp > 0 && model->curr_ts) { + int ts_diff = (int)model->curr_ts - (int)model->generic->timestamp; + + canvas_draw_icon(canvas, 91, 46, &I_Timer_11x11); + + if(ts_diff > 60) { + int tmp_sec = ts_diff; + int cnt_min = 1; + for(int i = 1; tmp_sec > 60; i++) { + tmp_sec = tmp_sec - 60; + cnt_min = i; + } + + if(model->curr_ts % 2 == 0) { + canvas_draw_str_aligned(canvas, 105, 51, AlignLeft, AlignCenter, "Old"); + } else { + if(cnt_min >= 59) { + canvas_draw_str_aligned(canvas, 105, 51, AlignLeft, AlignCenter, "Old"); + } else { + snprintf(buffer, sizeof(buffer), "%dm", cnt_min); + canvas_draw_str_aligned(canvas, 114, 51, AlignCenter, AlignCenter, buffer); + } + } + + } else { + snprintf(buffer, sizeof(buffer), "%d", ts_diff); + canvas_draw_str_aligned(canvas, 112, 51, AlignCenter, AlignCenter, buffer); + } } } @@ -98,14 +134,19 @@ bool ws_view_receiver_info_input(InputEvent* event, void* context) { return true; } -void ws_view_receiver_info_enter(void* context) { +static void ws_view_receiver_info_enter(void* context) { furi_assert(context); + WSReceiverInfo* ws_receiver_info = context; + + furi_timer_start(ws_receiver_info->timer, 1000); } -void ws_view_receiver_info_exit(void* context) { +static void ws_view_receiver_info_exit(void* context) { furi_assert(context); WSReceiverInfo* ws_receiver_info = context; + furi_timer_stop(ws_receiver_info->timer); + with_view_model( ws_receiver_info->view, WSReceiverInfoModel * model, @@ -113,6 +154,20 @@ void ws_view_receiver_info_exit(void* context) { false); } +static void ws_view_receiver_info_timer(void* context) { + WSReceiverInfo* ws_receiver_info = context; + // Force redraw + with_view_model( + ws_receiver_info->view, + WSReceiverInfoModel * model, + { + FuriHalRtcDateTime curr_dt; + furi_hal_rtc_get_datetime(&curr_dt); + model->curr_ts = furi_hal_rtc_datetime_to_timestamp(&curr_dt); + }, + true); +} + WSReceiverInfo* ws_view_receiver_info_alloc() { WSReceiverInfo* ws_receiver_info = malloc(sizeof(WSReceiverInfo)); @@ -135,12 +190,17 @@ WSReceiverInfo* ws_view_receiver_info_alloc() { }, true); + ws_receiver_info->timer = + furi_timer_alloc(ws_view_receiver_info_timer, FuriTimerTypePeriodic, ws_receiver_info); + return ws_receiver_info; } void ws_view_receiver_info_free(WSReceiverInfo* ws_receiver_info) { furi_assert(ws_receiver_info); + furi_timer_free(ws_receiver_info->timer); + with_view_model( ws_receiver_info->view, WSReceiverInfoModel * model, From 31a9a3f5f3dfe073857f5137e391eb3fc56e4e8d Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sat, 10 Dec 2022 20:31:07 +0300 Subject: [PATCH 273/824] SubGHz: Improve signal text visibility in history (#2111) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- applications/main/subghz/views/receiver.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/applications/main/subghz/views/receiver.c b/applications/main/subghz/views/receiver.c index 7aa69922298..999eef6a56b 100644 --- a/applications/main/subghz/views/receiver.c +++ b/applications/main/subghz/views/receiver.c @@ -8,7 +8,7 @@ #include #define FRAME_HEIGHT 12 -#define MAX_LEN_PX 100 +#define MAX_LEN_PX 111 #define MENU_ITEMS 4u #define UNLOCK_CNT 3 @@ -186,7 +186,7 @@ void subghz_view_receiver_draw(Canvas* canvas, SubGhzViewReceiverModel* model) { size_t idx = CLAMP((uint16_t)(i + model->list_offset), model->history_item, 0); item_menu = SubGhzReceiverMenuItemArray_get(model->history->data, idx); furi_string_set(str_buff, item_menu->item_str); - elements_string_fit_width(canvas, str_buff, scrollbar ? MAX_LEN_PX - 6 : MAX_LEN_PX); + elements_string_fit_width(canvas, str_buff, scrollbar ? MAX_LEN_PX - 7 : MAX_LEN_PX); if(model->idx == idx) { subghz_view_receiver_draw_frame(canvas, i, scrollbar); } else { From 2954ec6d97dbaa90974728a818008653d9b4ec88 Mon Sep 17 00:00:00 2001 From: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> Date: Sat, 10 Dec 2022 21:05:52 +0300 Subject: [PATCH 274/824] [FL-3025] IR button overflow fix (#2115) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Allow for more than 255 button_menu items * Allow more than 255 items in submenu * Fix button_menu_reset Co-authored-by: あく --- .../services/gui/modules/button_menu.c | 19 +++--- applications/services/gui/modules/submenu.c | 58 ++++++++++--------- 2 files changed, 40 insertions(+), 37 deletions(-) diff --git a/applications/services/gui/modules/button_menu.c b/applications/services/gui/modules/button_menu.c index ff12a93117b..60bd160c5ba 100644 --- a/applications/services/gui/modules/button_menu.c +++ b/applications/services/gui/modules/button_menu.c @@ -30,7 +30,7 @@ struct ButtonMenu { typedef struct { ButtonMenuItemArray_t items; - uint8_t position; + size_t position; const char* header; } ButtonMenuModel; @@ -102,11 +102,9 @@ static void button_menu_view_draw_callback(Canvas* canvas, void* _model) { ButtonMenuModel* model = (ButtonMenuModel*)_model; canvas_set_font(canvas, FontSecondary); - uint8_t item_position = 0; - int8_t active_screen = model->position / BUTTONS_PER_SCREEN; - size_t items_size = ButtonMenuItemArray_size(model->items); - int8_t max_screen = ((int16_t)items_size - 1) / BUTTONS_PER_SCREEN; - ButtonMenuItemArray_it_t it; + const size_t active_screen = model->position / BUTTONS_PER_SCREEN; + const size_t items_size = ButtonMenuItemArray_size(model->items); + const size_t max_screen = items_size ? (items_size - 1) / BUTTONS_PER_SCREEN : 0; if(active_screen > 0) { canvas_draw_icon(canvas, 28, 1, &I_InfraredArrowUp_4x8); @@ -125,6 +123,9 @@ static void button_menu_view_draw_callback(Canvas* canvas, void* _model) { furi_string_free(disp_str); } + size_t item_position = 0; + ButtonMenuItemArray_it_t it; + for(ButtonMenuItemArray_it(it, model->items); !ButtonMenuItemArray_end_p(it); ButtonMenuItemArray_next(it), ++item_position) { if(active_screen == (item_position / BUTTONS_PER_SCREEN)) { @@ -195,14 +196,14 @@ static void button_menu_process_ok(ButtonMenu* button_menu, InputType type) { if(item) { if(item->type == ButtonMenuItemTypeControl) { if(type == InputTypeShort) { - if(item && item->callback) { + if(item->callback) { item->callback(item->callback_context, item->index, type); } } } if(item->type == ButtonMenuItemTypeCommon) { if((type == InputTypePress) || (type == InputTypeRelease)) { - if(item && item->callback) { + if(item->callback) { item->callback(item->callback_context, item->index, type); } } @@ -341,7 +342,7 @@ void button_menu_set_selected_item(ButtonMenu* button_menu, uint32_t index) { button_menu->view, ButtonMenuModel * model, { - uint8_t item_position = 0; + size_t item_position = 0; ButtonMenuItemArray_it_t it; for(ButtonMenuItemArray_it(it, model->items); !ButtonMenuItemArray_end_p(it); ButtonMenuItemArray_next(it), ++item_position) { diff --git a/applications/services/gui/modules/submenu.c b/applications/services/gui/modules/submenu.c index 949598772c2..b7152a3d263 100644 --- a/applications/services/gui/modules/submenu.c +++ b/applications/services/gui/modules/submenu.c @@ -20,8 +20,8 @@ ARRAY_DEF(SubmenuItemArray, SubmenuItem, M_POD_OPLIST); typedef struct { SubmenuItemArray_t items; const char* header; - uint8_t position; - uint8_t window_position; + size_t position; + size_t window_position; } SubmenuModel; static void submenu_process_up(Submenu* submenu); @@ -36,19 +36,19 @@ static void submenu_view_draw_callback(Canvas* canvas, void* _model) { canvas_clear(canvas); - uint8_t position = 0; - SubmenuItemArray_it_t it; - if(model->header) { canvas_set_font(canvas, FontPrimary); canvas_draw_str(canvas, 4, 11, model->header); } canvas_set_font(canvas, FontSecondary); + + size_t position = 0; + SubmenuItemArray_it_t it; for(SubmenuItemArray_it(it, model->items); !SubmenuItemArray_end_p(it); SubmenuItemArray_next(it)) { - uint8_t item_position = position - model->window_position; - uint8_t items_on_screen = model->header ? 3 : 4; + const size_t item_position = position - model->window_position; + const size_t items_on_screen = model->header ? 3 : 4; uint8_t y_offset = model->header ? 16 : 0; if(item_position < items_on_screen) { @@ -198,7 +198,7 @@ void submenu_set_selected_item(Submenu* submenu, uint32_t index) { submenu->view, SubmenuModel * model, { - uint32_t position = 0; + size_t position = 0; SubmenuItemArray_it_t it; for(SubmenuItemArray_it(it, model->items); !SubmenuItemArray_end_p(it); SubmenuItemArray_next(it)) { @@ -208,7 +208,9 @@ void submenu_set_selected_item(Submenu* submenu, uint32_t index) { position++; } - if(position >= SubmenuItemArray_size(model->items)) { + const size_t items_size = SubmenuItemArray_size(model->items); + + if(position >= items_size) { position = 0; } @@ -219,16 +221,12 @@ void submenu_set_selected_item(Submenu* submenu, uint32_t index) { model->window_position -= 1; } - uint8_t items_on_screen = model->header ? 3 : 4; + const size_t items_on_screen = model->header ? 3 : 4; - if(SubmenuItemArray_size(model->items) <= items_on_screen) { + if(items_size <= items_on_screen) { model->window_position = 0; - } else { - if(model->window_position >= - (SubmenuItemArray_size(model->items) - items_on_screen)) { - model->window_position = - (SubmenuItemArray_size(model->items) - items_on_screen); - } + } else if(model->window_position >= items_size - items_on_screen) { + model->window_position = items_size - items_on_screen; } }, true); @@ -239,16 +237,18 @@ void submenu_process_up(Submenu* submenu) { submenu->view, SubmenuModel * model, { - uint8_t items_on_screen = model->header ? 3 : 4; + const size_t items_on_screen = model->header ? 3 : 4; + const size_t items_size = SubmenuItemArray_size(model->items); + if(model->position > 0) { model->position--; - if(((model->position - model->window_position) < 1) && - model->window_position > 0) { + if((model->position - model->window_position < 1) && + (model->window_position > 0)) { model->window_position--; } } else { - model->position = SubmenuItemArray_size(model->items) - 1; - if(model->position > (items_on_screen - 1)) { + model->position = items_size - 1; + if(model->position > items_on_screen - 1) { model->window_position = model->position - (items_on_screen - 1); } } @@ -261,12 +261,13 @@ void submenu_process_down(Submenu* submenu) { submenu->view, SubmenuModel * model, { - uint8_t items_on_screen = model->header ? 3 : 4; - if(model->position < (SubmenuItemArray_size(model->items) - 1)) { + const size_t items_on_screen = model->header ? 3 : 4; + const size_t items_size = SubmenuItemArray_size(model->items); + + if(model->position < items_size - 1) { model->position++; - if((model->position - model->window_position) > (items_on_screen - 2) && - model->window_position < - (SubmenuItemArray_size(model->items) - items_on_screen)) { + if((model->position - model->window_position > items_on_screen - 2) && + (model->window_position < items_size - items_on_screen)) { model->window_position++; } } else { @@ -284,7 +285,8 @@ void submenu_process_ok(Submenu* submenu) { submenu->view, SubmenuModel * model, { - if(model->position < (SubmenuItemArray_size(model->items))) { + const size_t items_size = SubmenuItemArray_size(model->items); + if(model->position < items_size) { item = SubmenuItemArray_get(model->items, model->position); } }, From 27921e42ff65e07e94f7c7cad08d0c2699602e2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Sun, 11 Dec 2022 05:34:12 +0900 Subject: [PATCH 275/824] Github: fix unit tests workflow (#2117) --- .github/workflows/unit_tests.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 4eab21c8778..308ec5929d7 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -11,6 +11,12 @@ jobs: run_units_on_test_bench: runs-on: [self-hosted, FlipperZeroTest] steps: + - name: 'Decontaminate previous build leftovers' + run: | + if [ -d .git ]; then + git submodule status || git checkout "$(git rev-list --max-parents=0 HEAD | tail -n 1)" + fi + - name: Checkout code uses: actions/checkout@v3 with: From 6ff437ad1824cc1b74477a32ae8b98a071c883fb Mon Sep 17 00:00:00 2001 From: Eric Betts Date: Sun, 11 Dec 2022 09:32:25 -0800 Subject: [PATCH 276/824] Dictionary stuff: iClass keys (#2118) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * MFC comments * iClass keys from proxmark3 repo Co-authored-by: あく --- .../resources/nfc/assets/mf_classic_dict.nfc | 5 +- .../picopass/assets/iclass_elite_dict.txt | 47 +++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 assets/resources/picopass/assets/iclass_elite_dict.txt diff --git a/assets/resources/nfc/assets/mf_classic_dict.nfc b/assets/resources/nfc/assets/mf_classic_dict.nfc index f4cdf595207..0925888f226 100644 --- a/assets/resources/nfc/assets/mf_classic_dict.nfc +++ b/assets/resources/nfc/assets/mf_classic_dict.nfc @@ -244,6 +244,7 @@ FEE470A4CB58 1F1A0A111B5B 1F1FFE000000 2031D1E57A3B +# HID Key B 204752454154 21A600056CB0 22729A9BD40F @@ -292,6 +293,7 @@ FEE470A4CB58 45635EF66EF3 476242304C53 484558414354 +# HID Key A 484944204953 484A57696F4A 48734389EDC3 @@ -1226,6 +1228,7 @@ C41514DEFC07 ABFEDC124578 046154274C11 5429D67E1F57 +# SMARTair Key B E7316853E731 CD7FFFF81C4A F253C30568C4 @@ -1308,4 +1311,4 @@ CE99FBC8BD26 # PIK Comfort Moscow keys (ISBC Mifare Plus SE 1K) 009FB42D98ED -002E626E2820 \ No newline at end of file +002E626E2820 diff --git a/assets/resources/picopass/assets/iclass_elite_dict.txt b/assets/resources/picopass/assets/iclass_elite_dict.txt new file mode 100644 index 00000000000..46808ef602e --- /dev/null +++ b/assets/resources/picopass/assets/iclass_elite_dict.txt @@ -0,0 +1,47 @@ + +## From https://github.com/RfidResearchGroup/proxmark3/blob/master/client/dictionaries/iclass_default_keys.dic + +# AA1 +AEA684A6DAB23278 +# key1/Kc from PicoPass 2k documentation +7665544332211000 +# SAGEM +0123456789ABCDEF +# from loclass demo file. +5b7c62c491c11b39 +# Kd from PicoPass 2k documentation +F0E1D2C3B4A59687 +# PicoPass Default Exchange Key +5CBCF1DA45D5FB4F +# From HID multiclassSE reader +31ad7ebd2f282168 +# From pastebin: https://pastebin.com/uHqpjiuU +6EFD46EFCBB3C875 +E033CA419AEE43F9 + +# iCopy-x DRM keys +# iCL tags +2020666666668888 +# iCS tags reversed from the SOs +6666202066668888 + +# default picopass KD / Page 0 / Book 1 +FDCB5A52EA8F3090 +237FF9079863DF44 +5ADC25FB27181D32 +83B881F2936B2E49 +43644E61EE866BA5 +897034143D016080 +82D17B44C0122963 +4895CA7DE65E2025 +DADAD4C57BE271B7 +E41E9EDEF5719ABF +293D275EC3AF9C7F +C3C169251B8A70FB +F41DAF58B20C8B91 +28877A609EC0DD2B +66584C91EE80D5E5 +C1B74D7478053AE2 + +# default iCLASS RFIDeas +6B65797374726B72 From 87fb852bcfafe5f2113348827159acb3fa0bc243 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 11 Dec 2022 22:29:58 +0300 Subject: [PATCH 277/824] Weather Station: Fix display of temps lower than -9 (#2120) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- .../views/weather_station_receiver_info.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/applications/plugins/weather_station/views/weather_station_receiver_info.c b/applications/plugins/weather_station/views/weather_station_receiver_info.c index c2fa0064112..05809921708 100644 --- a/applications/plugins/weather_station/views/weather_station_receiver_info.c +++ b/applications/plugins/weather_station/views/weather_station_receiver_info.c @@ -82,8 +82,14 @@ void ws_view_receiver_info_draw(Canvas* canvas, WSReceiverInfoModel* model) { if(model->generic->temp != WS_NO_TEMPERATURE) { canvas_draw_icon(canvas, 6, 43, &I_Therm_7x16); snprintf(buffer, sizeof(buffer), "%3.1f C", (double)model->generic->temp); - canvas_draw_str_aligned(canvas, 47, 47, AlignRight, AlignTop, buffer); - canvas_draw_circle(canvas, 38, 46, 1); + uint8_t temp_x1 = 47; + uint8_t temp_x2 = 38; + if(model->generic->temp < -9.0) { + temp_x1 = 49; + temp_x2 = 40; + } + canvas_draw_str_aligned(canvas, temp_x1, 47, AlignRight, AlignTop, buffer); + canvas_draw_circle(canvas, temp_x2, 46, 1); } if(model->generic->humidity != WS_NO_HUMIDITY) { From d541f142c8af39313b525aa28fc7aa7923c0c3f6 Mon Sep 17 00:00:00 2001 From: Kassim Date: Mon, 12 Dec 2022 09:47:22 +0000 Subject: [PATCH 278/824] Add Mouse Jiggler to HID Remote (#2113) * feat: add Mouse Jiggler to HID Remote * move processing to use furi_timer instead of draw loop * HidApp: refine mouse jiggler, move timer work into enter/exit callbacks Co-authored-by: Aleksandr Kutuzov --- applications/plugins/hid_app/hid.c | 29 +++- applications/plugins/hid_app/hid.h | 2 + applications/plugins/hid_app/views.h | 1 + .../plugins/hid_app/views/hid_mouse_jiggler.c | 149 ++++++++++++++++++ .../plugins/hid_app/views/hid_mouse_jiggler.h | 17 ++ 5 files changed, 194 insertions(+), 4 deletions(-) create mode 100644 applications/plugins/hid_app/views/hid_mouse_jiggler.c create mode 100644 applications/plugins/hid_app/views/hid_mouse_jiggler.h diff --git a/applications/plugins/hid_app/hid.c b/applications/plugins/hid_app/hid.c index 2a617fdeae1..d205d96eba3 100644 --- a/applications/plugins/hid_app/hid.c +++ b/applications/plugins/hid_app/hid.c @@ -9,10 +9,10 @@ enum HidDebugSubmenuIndex { HidSubmenuIndexKeynote, HidSubmenuIndexKeyboard, HidSubmenuIndexMedia, - BtHidSubmenuIndexTikTok, + HidSubmenuIndexTikTok, HidSubmenuIndexMouse, + HidSubmenuIndexMouseJiggler, }; -typedef enum { ConnTypeSubmenuIndexBluetooth, ConnTypeSubmenuIndexUsb } ConnTypeDebugSubmenuIndex; static void hid_submenu_callback(void* context, uint32_t index) { furi_assert(context); @@ -29,9 +29,12 @@ static void hid_submenu_callback(void* context, uint32_t index) { } else if(index == HidSubmenuIndexMouse) { app->view_id = HidViewMouse; view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMouse); - } else if(index == BtHidSubmenuIndexTikTok) { + } else if(index == HidSubmenuIndexTikTok) { app->view_id = BtHidViewTikTok; view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewTikTok); + } else if(index == HidSubmenuIndexMouseJiggler) { + app->view_id = HidViewMouseJiggler; + view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMouseJiggler); } } @@ -48,6 +51,7 @@ static void bt_hid_connection_status_changed_callback(BtStatus status, void* con hid_keyboard_set_connected_status(hid->hid_keyboard, connected); hid_media_set_connected_status(hid->hid_media, connected); hid_mouse_set_connected_status(hid->hid_mouse, connected); + hid_mouse_jiggler_set_connected_status(hid->hid_mouse_jiggler, connected); hid_tiktok_set_connected_status(hid->hid_tiktok, connected); } @@ -104,10 +108,16 @@ Hid* hid_alloc(HidTransport transport) { submenu_add_item( app->device_type_submenu, "TikTok Controller", - BtHidSubmenuIndexTikTok, + HidSubmenuIndexTikTok, hid_submenu_callback, app); } + submenu_add_item( + app->device_type_submenu, + "Mouse Jiggler", + HidSubmenuIndexMouseJiggler, + hid_submenu_callback, + app); view_set_previous_callback(submenu_get_view(app->device_type_submenu), hid_exit); view_dispatcher_add_view( app->view_dispatcher, HidViewSubmenu, submenu_get_view(app->device_type_submenu)); @@ -160,6 +170,15 @@ Hid* hid_app_alloc_view(void* context) { view_dispatcher_add_view( app->view_dispatcher, HidViewMouse, hid_mouse_get_view(app->hid_mouse)); + // Mouse jiggler view + app->hid_mouse_jiggler = hid_mouse_jiggler_alloc(app); + view_set_previous_callback( + hid_mouse_jiggler_get_view(app->hid_mouse_jiggler), hid_exit_confirm_view); + view_dispatcher_add_view( + app->view_dispatcher, + HidViewMouseJiggler, + hid_mouse_jiggler_get_view(app->hid_mouse_jiggler)); + return app; } @@ -182,6 +201,8 @@ void hid_free(Hid* app) { hid_media_free(app->hid_media); view_dispatcher_remove_view(app->view_dispatcher, HidViewMouse); hid_mouse_free(app->hid_mouse); + view_dispatcher_remove_view(app->view_dispatcher, HidViewMouseJiggler); + hid_mouse_jiggler_free(app->hid_mouse_jiggler); view_dispatcher_remove_view(app->view_dispatcher, BtHidViewTikTok); hid_tiktok_free(app->hid_tiktok); view_dispatcher_free(app->view_dispatcher); diff --git a/applications/plugins/hid_app/hid.h b/applications/plugins/hid_app/hid.h index 81ebcf5669c..375a89706de 100644 --- a/applications/plugins/hid_app/hid.h +++ b/applications/plugins/hid_app/hid.h @@ -19,6 +19,7 @@ #include "views/hid_keyboard.h" #include "views/hid_media.h" #include "views/hid_mouse.h" +#include "views/hid_mouse_jiggler.h" #include "views/hid_tiktok.h" typedef enum { @@ -39,6 +40,7 @@ struct Hid { HidKeyboard* hid_keyboard; HidMedia* hid_media; HidMouse* hid_mouse; + HidMouseJiggler* hid_mouse_jiggler; HidTikTok* hid_tiktok; HidTransport transport; diff --git a/applications/plugins/hid_app/views.h b/applications/plugins/hid_app/views.h index 68a827ad616..2a44832e12e 100644 --- a/applications/plugins/hid_app/views.h +++ b/applications/plugins/hid_app/views.h @@ -4,6 +4,7 @@ typedef enum { HidViewKeyboard, HidViewMedia, HidViewMouse, + HidViewMouseJiggler, BtHidViewTikTok, HidViewExitConfirm, } HidView; \ No newline at end of file diff --git a/applications/plugins/hid_app/views/hid_mouse_jiggler.c b/applications/plugins/hid_app/views/hid_mouse_jiggler.c new file mode 100644 index 00000000000..a2b07c7a18a --- /dev/null +++ b/applications/plugins/hid_app/views/hid_mouse_jiggler.c @@ -0,0 +1,149 @@ +#include "hid_mouse_jiggler.h" +#include +#include "../hid.h" + +#include "hid_icons.h" + +#define TAG "HidMouseJiggler" + +struct HidMouseJiggler { + View* view; + Hid* hid; + FuriTimer* timer; +}; + +typedef struct { + bool connected; + bool running; + uint8_t counter; +} HidMouseJigglerModel; + +static void hid_mouse_jiggler_draw_callback(Canvas* canvas, void* context) { + furi_assert(context); + HidMouseJigglerModel* model = context; + + // Header + if(model->connected) { + canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); + } else { + canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); + } + canvas_set_font(canvas, FontPrimary); + elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Mouse Jiggler"); + + canvas_set_font(canvas, FontPrimary); + elements_multiline_text(canvas, AlignLeft, 35, "Press Start\nto jiggle"); + canvas_set_font(canvas, FontSecondary); + + // Ok + canvas_draw_icon(canvas, 63, 25, &I_Space_65x18); + if(model->running) { + elements_slightly_rounded_box(canvas, 66, 27, 60, 13); + canvas_set_color(canvas, ColorWhite); + } + canvas_draw_icon(canvas, 74, 29, &I_Ok_btn_9x9); + if(model->running) { + elements_multiline_text_aligned(canvas, 91, 36, AlignLeft, AlignBottom, "Stop"); + } else { + elements_multiline_text_aligned(canvas, 91, 36, AlignLeft, AlignBottom, "Start"); + } + canvas_set_color(canvas, ColorBlack); + + // Back + canvas_draw_icon(canvas, 74, 49, &I_Pin_back_arrow_10x8); + elements_multiline_text_aligned(canvas, 91, 57, AlignLeft, AlignBottom, "Quit"); +} + +static void hid_mouse_jiggler_timer_callback(void* context) { + furi_assert(context); + HidMouseJiggler* hid_mouse_jiggler = context; + with_view_model( + hid_mouse_jiggler->view, + HidMouseJigglerModel * model, + { + if(model->running) { + model->counter++; + hid_hal_mouse_move( + hid_mouse_jiggler->hid, + (model->counter % 2 == 0) ? MOUSE_MOVE_SHORT : -MOUSE_MOVE_SHORT, + 0); + } + }, + false); +} + +static void hid_mouse_jiggler_enter_callback(void* context) { + furi_assert(context); + HidMouseJiggler* hid_mouse_jiggler = context; + + furi_timer_start(hid_mouse_jiggler->timer, 500); +} + +static void hid_mouse_jiggler_exit_callback(void* context) { + furi_assert(context); + HidMouseJiggler* hid_mouse_jiggler = context; + furi_timer_stop(hid_mouse_jiggler->timer); +} + +static bool hid_mouse_jiggler_input_callback(InputEvent* event, void* context) { + furi_assert(context); + HidMouseJiggler* hid_mouse_jiggler = context; + + bool consumed = false; + + if(event->key == InputKeyOk) { + with_view_model( + hid_mouse_jiggler->view, + HidMouseJigglerModel * model, + { model->running = !model->running; }, + true); + consumed = true; + } + + return consumed; +} + +HidMouseJiggler* hid_mouse_jiggler_alloc(Hid* hid) { + HidMouseJiggler* hid_mouse_jiggler = malloc(sizeof(HidMouseJiggler)); + + hid_mouse_jiggler->view = view_alloc(); + view_set_context(hid_mouse_jiggler->view, hid_mouse_jiggler); + view_allocate_model( + hid_mouse_jiggler->view, ViewModelTypeLocking, sizeof(HidMouseJigglerModel)); + view_set_draw_callback(hid_mouse_jiggler->view, hid_mouse_jiggler_draw_callback); + view_set_input_callback(hid_mouse_jiggler->view, hid_mouse_jiggler_input_callback); + view_set_enter_callback(hid_mouse_jiggler->view, hid_mouse_jiggler_enter_callback); + view_set_exit_callback(hid_mouse_jiggler->view, hid_mouse_jiggler_exit_callback); + + hid_mouse_jiggler->hid = hid; + + hid_mouse_jiggler->timer = furi_timer_alloc( + hid_mouse_jiggler_timer_callback, FuriTimerTypePeriodic, hid_mouse_jiggler); + + return hid_mouse_jiggler; +} + +void hid_mouse_jiggler_free(HidMouseJiggler* hid_mouse_jiggler) { + furi_assert(hid_mouse_jiggler); + + furi_timer_stop(hid_mouse_jiggler->timer); + furi_timer_free(hid_mouse_jiggler->timer); + + view_free(hid_mouse_jiggler->view); + + free(hid_mouse_jiggler); +} + +View* hid_mouse_jiggler_get_view(HidMouseJiggler* hid_mouse_jiggler) { + furi_assert(hid_mouse_jiggler); + return hid_mouse_jiggler->view; +} + +void hid_mouse_jiggler_set_connected_status(HidMouseJiggler* hid_mouse_jiggler, bool connected) { + furi_assert(hid_mouse_jiggler); + with_view_model( + hid_mouse_jiggler->view, + HidMouseJigglerModel * model, + { model->connected = connected; }, + true); +} diff --git a/applications/plugins/hid_app/views/hid_mouse_jiggler.h b/applications/plugins/hid_app/views/hid_mouse_jiggler.h new file mode 100644 index 00000000000..0813b4351e1 --- /dev/null +++ b/applications/plugins/hid_app/views/hid_mouse_jiggler.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +#define MOUSE_MOVE_SHORT 5 +#define MOUSE_MOVE_LONG 20 + +typedef struct Hid Hid; +typedef struct HidMouseJiggler HidMouseJiggler; + +HidMouseJiggler* hid_mouse_jiggler_alloc(Hid* bt_hid); + +void hid_mouse_jiggler_free(HidMouseJiggler* hid_mouse_jiggler); + +View* hid_mouse_jiggler_get_view(HidMouseJiggler* hid_mouse_jiggler); + +void hid_mouse_jiggler_set_connected_status(HidMouseJiggler* hid_mouse_jiggler, bool connected); From 1c12613863d31bfd778764318742ea28039948c9 Mon Sep 17 00:00:00 2001 From: usiegl00 <50933431+usiegl00@users.noreply.github.com> Date: Mon, 12 Dec 2022 21:46:41 +0900 Subject: [PATCH 279/824] Prevent hacking related backgrounds from being displayed in dummy mode. (#2107) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Prevent hacking related backgrounds from being displayed in dummy mode. * Add function call to animation manager to set dummy mode. * Reboot retains dummy mode background. Co-authored-by: あく --- .../services/desktop/animations/animation_manager.c | 11 ++++++++++- .../services/desktop/animations/animation_manager.h | 8 ++++++++ applications/services/desktop/desktop.c | 3 +++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/applications/services/desktop/animations/animation_manager.c b/applications/services/desktop/animations/animation_manager.c index 36c5b3975d3..9c22d131407 100644 --- a/applications/services/desktop/animations/animation_manager.c +++ b/applications/services/desktop/animations/animation_manager.c @@ -52,6 +52,7 @@ struct AnimationManager { FuriString* freezed_animation_name; int32_t freezed_animation_time_left; ViewStack* view_stack; + bool dummy_mode; }; static StorageAnimation* @@ -93,6 +94,12 @@ void animation_manager_set_interact_callback( animation_manager->interact_callback = callback; } +void animation_manager_set_dummy_mode_state(AnimationManager* animation_manager, bool enabled) { + furi_assert(animation_manager); + animation_manager->dummy_mode = enabled; + animation_manager_start_new_idle(animation_manager); +} + static void animation_manager_check_blocking_callback(const void* message, void* context) { const StorageEvent* storage_event = message; @@ -363,7 +370,9 @@ static bool animation_manager_is_valid_idle_animation( static StorageAnimation* animation_manager_select_idle_animation(AnimationManager* animation_manager) { - UNUSED(animation_manager); + if(animation_manager->dummy_mode) { + return animation_storage_find_animation(HARDCODED_ANIMATION_NAME); + } StorageAnimationList_t animation_list; StorageAnimationList_init(animation_list); animation_storage_fill_animation_list(&animation_list); diff --git a/applications/services/desktop/animations/animation_manager.h b/applications/services/desktop/animations/animation_manager.h index 234d20de038..a3dcc882794 100644 --- a/applications/services/desktop/animations/animation_manager.h +++ b/applications/services/desktop/animations/animation_manager.h @@ -157,3 +157,11 @@ void animation_manager_unload_and_stall_animation(AnimationManager* animation_ma * @animation_manager instance */ void animation_manager_load_and_continue_animation(AnimationManager* animation_manager); + +/** + * Enable or disable dummy mode backgrounds of animation manager. + * + * @animation_manager instance + * @enabled bool + */ +void animation_manager_set_dummy_mode_state(AnimationManager* animation_manager, bool enabled); diff --git a/applications/services/desktop/desktop.c b/applications/services/desktop/desktop.c index b45a9d62186..848f5cb636b 100644 --- a/applications/services/desktop/desktop.c +++ b/applications/services/desktop/desktop.c @@ -144,6 +144,7 @@ void desktop_unlock(Desktop* desktop) { void desktop_set_dummy_mode_state(Desktop* desktop, bool enabled) { view_port_enabled_set(desktop->dummy_mode_icon_viewport, enabled); desktop_main_set_dummy_mode_state(desktop->main_view, enabled); + animation_manager_set_dummy_mode_state(desktop->animation_manager, enabled); desktop->settings.dummy_mode = enabled; DESKTOP_SETTINGS_SAVE(&desktop->settings); } @@ -330,6 +331,8 @@ int32_t desktop_srv(void* p) { view_port_enabled_set(desktop->dummy_mode_icon_viewport, desktop->settings.dummy_mode); desktop_main_set_dummy_mode_state(desktop->main_view, desktop->settings.dummy_mode); + animation_manager_set_dummy_mode_state( + desktop->animation_manager, desktop->settings.dummy_mode); scene_manager_next_scene(desktop->scene_manager, DesktopSceneMain); From 1fa4c646e69d3d91f3cea9d323a71dbfe1e18c42 Mon Sep 17 00:00:00 2001 From: Der Skythe <31771569+derskythe@users.noreply.github.com> Date: Wed, 14 Dec 2022 11:42:13 +0400 Subject: [PATCH 280/824] VSCode: add task 'Serial console' and group task with sequence calling (#2121) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add task 'Serial console' and group task with sequence calling * PR fixes Co-authored-by: あく --- .vscode/example/tasks.json | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/.vscode/example/tasks.json b/.vscode/example/tasks.json index 9baaf97b44a..c16c3ab4f5b 100644 --- a/.vscode/example/tasks.json +++ b/.vscode/example/tasks.json @@ -128,6 +128,38 @@ "group": "build", "type": "shell", "command": "./fbt COMPACT=1 DEBUG=0 launch_app APPSRC=${relativeFileDirname}" + }, + { + "label": "[Debug] Launch App on Flipper with Serial Console", + "dependsOrder": "sequence", + "group": "build", + "dependsOn": [ + "[Debug] Launch App on Flipper", + "Serial Console" + ] + }, + { + // Press Ctrl+] to quit + "label": "Serial Console", + "type": "shell", + "command": "./fbt cli", + "group": "none", + "isBackground": true, + "options": { + "env": { + "FBT_NO_SYNC": "0" + } + }, + "presentation": { + "reveal": "always", + "revealProblems": "never", + "showReuseMessage": false, + "panel": "dedicated", + "focus": true, + "echo": true, + "close": true, + "group": "Logger" + } } ] -} \ No newline at end of file +} From 327df4a8130dd389ab39186e8a7c1640d51be705 Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Wed, 14 Dec 2022 12:27:55 +0400 Subject: [PATCH 281/824] [FL-3034] WS: fix protocol and add new (#2116) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * WS: fix Nexus-TH potocol * WS: add Oregon_v1 protocol * WS: add AmbientWeather-TX8300 protocol Co-authored-by: あく --- .../helpers/weather_station_types.h | 2 +- .../weather_station/protocols/nexus_th.c | 4 + .../weather_station/protocols/oregon_v1.c | 331 ++++++++++++++++++ .../weather_station/protocols/oregon_v1.h | 79 +++++ .../protocols/protocol_items.c | 2 + .../protocols/protocol_items.h | 2 + .../weather_station/protocols/tx_8300.c | 293 ++++++++++++++++ .../weather_station/protocols/tx_8300.h | 79 +++++ 8 files changed, 791 insertions(+), 1 deletion(-) create mode 100644 applications/plugins/weather_station/protocols/oregon_v1.c create mode 100644 applications/plugins/weather_station/protocols/oregon_v1.h create mode 100644 applications/plugins/weather_station/protocols/tx_8300.c create mode 100644 applications/plugins/weather_station/protocols/tx_8300.h diff --git a/applications/plugins/weather_station/helpers/weather_station_types.h b/applications/plugins/weather_station/helpers/weather_station_types.h index a23540e3d0f..16c195fa175 100644 --- a/applications/plugins/weather_station/helpers/weather_station_types.h +++ b/applications/plugins/weather_station/helpers/weather_station_types.h @@ -3,7 +3,7 @@ #include #include -#define WS_VERSION_APP "0.5" +#define WS_VERSION_APP "0.6" #define WS_DEVELOPED "SkorP" #define WS_GITHUB "https://github.com/flipperdevices/flipperzero-firmware" diff --git a/applications/plugins/weather_station/protocols/nexus_th.c b/applications/plugins/weather_station/protocols/nexus_th.c index 7d4a77aea00..38f2fe895cb 100644 --- a/applications/plugins/weather_station/protocols/nexus_th.c +++ b/applications/plugins/weather_station/protocols/nexus_th.c @@ -135,6 +135,10 @@ static void ws_protocol_nexus_th_remote_controller(WSBlockGeneric* instance) { } instance->humidity = instance->data & 0xFF; + if(instance->humidity > 95) + instance->humidity = 95; + else if(instance->humidity < 20) + instance->humidity = 20; } void ws_protocol_decoder_nexus_th_feed(void* context, bool level, uint32_t duration) { diff --git a/applications/plugins/weather_station/protocols/oregon_v1.c b/applications/plugins/weather_station/protocols/oregon_v1.c new file mode 100644 index 00000000000..d1cc4c7a7be --- /dev/null +++ b/applications/plugins/weather_station/protocols/oregon_v1.c @@ -0,0 +1,331 @@ +#include "oregon_v1.h" +#include + +#define TAG "WSProtocolOregon_V1" + +/* + * Help + * https://github.dev/merbanan/rtl_433/blob/bb1be7f186ac0fdb7dc5d77693847d96fb95281e/src/devices/oregon_scientific_v1.c + * + * OSv1 protocol. + * + * MC with nominal bit width of 2930 us. + * Pulses are somewhat longer than nominal half-bit width, 1748 us / 3216 us, + * Gaps are somewhat shorter than nominal half-bit width, 1176 us / 2640 us. + * After 12 preamble bits there is 4200 us gap, 5780 us pulse, 5200 us gap. + * And next 32 bit data + * + * Care must be taken with the gap after the sync pulse since it + * is outside of the normal clocking. Because of this a data stream + * beginning with a 0 will have data in this gap. + * + * + * Data is in reverse order of bits + * RevBit(data32bit)=> tib23atad + * + * tib23atad => xxxxxxxx | busuTTTT | ttttzzzz | ccuuiiii + * + * - i: ID + * - x: CRC; + * - u: unknown; + * - b: battery low; flag to indicate low battery voltage + * - s: temperature sign + * - T: BCD, Temperature; in C * 10 + * - t: BCD, Temperature; in C * 1 + * - z: BCD, Temperature; in C * 0.1 + * - c: Channel 00=CH1, 01=CH2, 10=CH3 + * + */ + +#define OREGON_V1_HEADER_OK 0xFF + +static const SubGhzBlockConst ws_protocol_oregon_v1_const = { + .te_short = 1465, + .te_long = 2930, + .te_delta = 350, + .min_count_bit_for_found = 32, +}; + +struct WSProtocolDecoderOregon_V1 { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + WSBlockGeneric generic; + ManchesterState manchester_state; + uint16_t header_count; + uint8_t first_bit; +}; + +struct WSProtocolEncoderOregon_V1 { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + WSBlockGeneric generic; +}; + +typedef enum { + Oregon_V1DecoderStepReset = 0, + Oregon_V1DecoderStepFoundPreamble, + Oregon_V1DecoderStepParse, +} Oregon_V1DecoderStep; + +const SubGhzProtocolDecoder ws_protocol_oregon_v1_decoder = { + .alloc = ws_protocol_decoder_oregon_v1_alloc, + .free = ws_protocol_decoder_oregon_v1_free, + + .feed = ws_protocol_decoder_oregon_v1_feed, + .reset = ws_protocol_decoder_oregon_v1_reset, + + .get_hash_data = ws_protocol_decoder_oregon_v1_get_hash_data, + .serialize = ws_protocol_decoder_oregon_v1_serialize, + .deserialize = ws_protocol_decoder_oregon_v1_deserialize, + .get_string = ws_protocol_decoder_oregon_v1_get_string, +}; + +const SubGhzProtocolEncoder ws_protocol_oregon_v1_encoder = { + .alloc = NULL, + .free = NULL, + + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol ws_protocol_oregon_v1 = { + .name = WS_PROTOCOL_OREGON_V1_NAME, + .type = SubGhzProtocolWeatherStation, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable, + + .decoder = &ws_protocol_oregon_v1_decoder, + .encoder = &ws_protocol_oregon_v1_encoder, +}; + +void* ws_protocol_decoder_oregon_v1_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + WSProtocolDecoderOregon_V1* instance = malloc(sizeof(WSProtocolDecoderOregon_V1)); + instance->base.protocol = &ws_protocol_oregon_v1; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void ws_protocol_decoder_oregon_v1_free(void* context) { + furi_assert(context); + WSProtocolDecoderOregon_V1* instance = context; + free(instance); +} + +void ws_protocol_decoder_oregon_v1_reset(void* context) { + furi_assert(context); + WSProtocolDecoderOregon_V1* instance = context; + instance->decoder.parser_step = Oregon_V1DecoderStepReset; +} + +static bool ws_protocol_oregon_v1_check(WSProtocolDecoderOregon_V1* instance) { + if(!instance->decoder.decode_data) return false; + uint64_t data = subghz_protocol_blocks_reverse_key(instance->decoder.decode_data, 32); + uint16_t crc = (data & 0xff) + ((data >> 8) & 0xff) + ((data >> 16) & 0xff); + crc = (crc & 0xff) + ((crc >> 8) & 0xff); + return (crc == ((data >> 24) & 0xFF)); +} + +/** + * Analysis of received data + * @param instance Pointer to a WSBlockGeneric* instance + */ +static void ws_protocol_oregon_v1_remote_controller(WSBlockGeneric* instance) { + uint64_t data = subghz_protocol_blocks_reverse_key(instance->data, 32); + + instance->id = data & 0xFF; + instance->channel = ((data >> 6) & 0x03) + 1; + + float temp_raw = + ((data >> 8) & 0x0F) * 0.1f + ((data >> 12) & 0x0F) + ((data >> 16) & 0x0F) * 10.0f; + if(!((data >> 21) & 1)) { + instance->temp = temp_raw; + } else { + instance->temp = -temp_raw; + } + + instance->battery_low = !(instance->data >> 23) & 1; + + instance->btn = WS_NO_BTN; + instance->humidity = WS_NO_HUMIDITY; +} + +void ws_protocol_decoder_oregon_v1_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + WSProtocolDecoderOregon_V1* instance = context; + ManchesterEvent event = ManchesterEventReset; + switch(instance->decoder.parser_step) { + case Oregon_V1DecoderStepReset: + if((level) && (DURATION_DIFF(duration, ws_protocol_oregon_v1_const.te_short) < + ws_protocol_oregon_v1_const.te_delta)) { + instance->decoder.parser_step = Oregon_V1DecoderStepFoundPreamble; + instance->decoder.te_last = duration; + instance->header_count = 0; + } + break; + case Oregon_V1DecoderStepFoundPreamble: + if(level) { + //keep high levels, if they suit our durations + if((DURATION_DIFF(duration, ws_protocol_oregon_v1_const.te_short) < + ws_protocol_oregon_v1_const.te_delta) || + (DURATION_DIFF(duration, ws_protocol_oregon_v1_const.te_short * 4) < + ws_protocol_oregon_v1_const.te_delta)) { + instance->decoder.te_last = duration; + } else { + instance->decoder.parser_step = Oregon_V1DecoderStepReset; + } + } else if( + //checking low levels + (DURATION_DIFF(duration, ws_protocol_oregon_v1_const.te_short) < + ws_protocol_oregon_v1_const.te_delta) && + (DURATION_DIFF(instance->decoder.te_last, ws_protocol_oregon_v1_const.te_short) < + ws_protocol_oregon_v1_const.te_delta)) { + // Found header + instance->header_count++; + } else if( + (DURATION_DIFF(duration, ws_protocol_oregon_v1_const.te_short * 3) < + ws_protocol_oregon_v1_const.te_delta) && + (DURATION_DIFF(instance->decoder.te_last, ws_protocol_oregon_v1_const.te_short) < + ws_protocol_oregon_v1_const.te_delta)) { + // check header + if(instance->header_count > 7) { + instance->header_count = OREGON_V1_HEADER_OK; + } + } else if( + (instance->header_count == OREGON_V1_HEADER_OK) && + (DURATION_DIFF(instance->decoder.te_last, ws_protocol_oregon_v1_const.te_short * 4) < + ws_protocol_oregon_v1_const.te_delta)) { + //found all the necessary patterns + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 1; + manchester_advance( + instance->manchester_state, + ManchesterEventReset, + &instance->manchester_state, + NULL); + instance->decoder.parser_step = Oregon_V1DecoderStepParse; + if(duration < ws_protocol_oregon_v1_const.te_short * 4) { + instance->first_bit = 1; + } else { + instance->first_bit = 0; + } + } else { + instance->decoder.parser_step = Oregon_V1DecoderStepReset; + } + break; + case Oregon_V1DecoderStepParse: + if(level) { + if(DURATION_DIFF(duration, ws_protocol_oregon_v1_const.te_short) < + ws_protocol_oregon_v1_const.te_delta) { + event = ManchesterEventShortHigh; + } else if( + DURATION_DIFF(duration, ws_protocol_oregon_v1_const.te_long) < + ws_protocol_oregon_v1_const.te_delta) { + event = ManchesterEventLongHigh; + } else { + instance->decoder.parser_step = Oregon_V1DecoderStepReset; + } + } else { + if(DURATION_DIFF(duration, ws_protocol_oregon_v1_const.te_short) < + ws_protocol_oregon_v1_const.te_delta) { + event = ManchesterEventShortLow; + } else if( + DURATION_DIFF(duration, ws_protocol_oregon_v1_const.te_long) < + ws_protocol_oregon_v1_const.te_delta) { + event = ManchesterEventLongLow; + } else if(duration >= ((uint32_t)ws_protocol_oregon_v1_const.te_long * 2)) { + if(instance->decoder.decode_count_bit == + ws_protocol_oregon_v1_const.min_count_bit_for_found) { + if(instance->first_bit) { + instance->decoder.decode_data = ~instance->decoder.decode_data | (1 << 31); + } + if(ws_protocol_oregon_v1_check(instance)) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + ws_protocol_oregon_v1_remote_controller(&instance->generic); + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + manchester_advance( + instance->manchester_state, + ManchesterEventReset, + &instance->manchester_state, + NULL); + } else { + instance->decoder.parser_step = Oregon_V1DecoderStepReset; + } + } + if(event != ManchesterEventReset) { + bool data; + bool data_ok = manchester_advance( + instance->manchester_state, event, &instance->manchester_state, &data); + + if(data_ok) { + instance->decoder.decode_data = (instance->decoder.decode_data << 1) | !data; + instance->decoder.decode_count_bit++; + } + } + + break; + } +} + +uint8_t ws_protocol_decoder_oregon_v1_get_hash_data(void* context) { + furi_assert(context); + WSProtocolDecoderOregon_V1* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool ws_protocol_decoder_oregon_v1_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + WSProtocolDecoderOregon_V1* instance = context; + return ws_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +bool ws_protocol_decoder_oregon_v1_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + WSProtocolDecoderOregon_V1* instance = context; + bool ret = false; + do { + if(!ws_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if(instance->generic.data_count_bit != + ws_protocol_oregon_v1_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + ret = true; + } while(false); + return ret; +} + +void ws_protocol_decoder_oregon_v1_get_string(void* context, FuriString* output) { + furi_assert(context); + WSProtocolDecoderOregon_V1* instance = context; + furi_string_printf( + output, + "%s %dbit\r\n" + "Key:0x%lX%08lX\r\n" + "Sn:0x%lX Ch:%d Bat:%d\r\n" + "Temp:%3.1f C Hum:%d%%", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data >> 32), + (uint32_t)(instance->generic.data), + instance->generic.id, + instance->generic.channel, + instance->generic.battery_low, + (double)instance->generic.temp, + instance->generic.humidity); +} diff --git a/applications/plugins/weather_station/protocols/oregon_v1.h b/applications/plugins/weather_station/protocols/oregon_v1.h new file mode 100644 index 00000000000..c9aa5af48a9 --- /dev/null +++ b/applications/plugins/weather_station/protocols/oregon_v1.h @@ -0,0 +1,79 @@ +#pragma once + +#include + +#include +#include +#include +#include "ws_generic.h" +#include + +#define WS_PROTOCOL_OREGON_V1_NAME "Oregon-v1" + +typedef struct WSProtocolDecoderOregon_V1 WSProtocolDecoderOregon_V1; +typedef struct WSProtocolEncoderOregon_V1 WSProtocolEncoderOregon_V1; + +extern const SubGhzProtocolDecoder ws_protocol_oregon_v1_decoder; +extern const SubGhzProtocolEncoder ws_protocol_oregon_v1_encoder; +extern const SubGhzProtocol ws_protocol_oregon_v1; + +/** + * Allocate WSProtocolDecoderOregon_V1. + * @param environment Pointer to a SubGhzEnvironment instance + * @return WSProtocolDecoderOregon_V1* pointer to a WSProtocolDecoderOregon_V1 instance + */ +void* ws_protocol_decoder_oregon_v1_alloc(SubGhzEnvironment* environment); + +/** + * Free WSProtocolDecoderOregon_V1. + * @param context Pointer to a WSProtocolDecoderOregon_V1 instance + */ +void ws_protocol_decoder_oregon_v1_free(void* context); + +/** + * Reset decoder WSProtocolDecoderOregon_V1. + * @param context Pointer to a WSProtocolDecoderOregon_V1 instance + */ +void ws_protocol_decoder_oregon_v1_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a WSProtocolDecoderOregon_V1 instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void ws_protocol_decoder_oregon_v1_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a WSProtocolDecoderOregon_V1 instance + * @return hash Hash sum + */ +uint8_t ws_protocol_decoder_oregon_v1_get_hash_data(void* context); + +/** + * Serialize data WSProtocolDecoderOregon_V1. + * @param context Pointer to a WSProtocolDecoderOregon_V1 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool ws_protocol_decoder_oregon_v1_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data WSProtocolDecoderOregon_V1. + * @param context Pointer to a WSProtocolDecoderOregon_V1 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool ws_protocol_decoder_oregon_v1_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a WSProtocolDecoderOregon_V1 instance + * @param output Resulting text + */ +void ws_protocol_decoder_oregon_v1_get_string(void* context, FuriString* output); diff --git a/applications/plugins/weather_station/protocols/protocol_items.c b/applications/plugins/weather_station/protocols/protocol_items.c index 9ad8ce1acfb..99c8344f491 100644 --- a/applications/plugins/weather_station/protocols/protocol_items.c +++ b/applications/plugins/weather_station/protocols/protocol_items.c @@ -13,6 +13,8 @@ const SubGhzProtocol* weather_station_protocol_registry_items[] = { &ws_protocol_acurite_592txr, &ws_protocol_ambient_weather, &ws_protocol_auriol_th, + &ws_protocol_oregon_v1, + &ws_protocol_tx_8300, }; const SubGhzProtocolRegistry weather_station_protocol_registry = { diff --git a/applications/plugins/weather_station/protocols/protocol_items.h b/applications/plugins/weather_station/protocols/protocol_items.h index 4fef89442fb..9d5d096f8f7 100644 --- a/applications/plugins/weather_station/protocols/protocol_items.h +++ b/applications/plugins/weather_station/protocols/protocol_items.h @@ -13,5 +13,7 @@ #include "acurite_592txr.h" #include "ambient_weather.h" #include "auriol_hg0601a.h" +#include "oregon_v1.h" +#include "tx_8300.h" extern const SubGhzProtocolRegistry weather_station_protocol_registry; diff --git a/applications/plugins/weather_station/protocols/tx_8300.c b/applications/plugins/weather_station/protocols/tx_8300.c new file mode 100644 index 00000000000..ee0412ba9cc --- /dev/null +++ b/applications/plugins/weather_station/protocols/tx_8300.c @@ -0,0 +1,293 @@ +#include "tx_8300.h" + +#define TAG "WSProtocolTX_8300" + +/* + * Help + * https://github.com/merbanan/rtl_433/blob/master/src/devices/ambientweather_tx8300.c + * + * Ambient Weather TX-8300 (also sold as TFA 30.3211.02). + * 1970us pulse with variable gap (third pulse 3920 us). + * Above 79% humidity, gap after third pulse is 5848 us. + * - Bit 1 : 1970us pulse with 3888 us gap + * - Bit 0 : 1970us pulse with 1936 us gap + * 74 bit (2 bit preamble and 72 bit data => 9 bytes => 18 nibbles) + * The preamble seems to be a repeat counter (00, and 01 seen), + * the first 4 bytes are data, + * the second 4 bytes the same data inverted, + * the last byte is a checksum. + * Preamble format (2 bits): + * [1 bit (0)] [1 bit rolling count] + * Payload format (32 bits): + * HHHHhhhh ??CCNIII IIIITTTT ttttuuuu + * - H = First BCD digit humidity (the MSB might be distorted by the demod) + * - h = Second BCD digit humidity, invalid humidity seems to be 0x0e + * - ? = Likely battery flag, 2 bits + * - C = Channel, 2 bits + * - N = Negative temperature sign bit + * - I = ID, 7-bit + * - T = First BCD digit temperature + * - t = Second BCD digit temperature + * - u = Third BCD digit temperature + * The Checksum seems to covers the 4 data bytes and is something like Fletcher-8. + **/ + +#define TX_8300_PACKAGE_SIZE 32 + +static const SubGhzBlockConst ws_protocol_tx_8300_const = { + .te_short = 1940, + .te_long = 3880, + .te_delta = 250, + .min_count_bit_for_found = 72, +}; + +struct WSProtocolDecoderTX_8300 { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + WSBlockGeneric generic; + uint32_t package_1; + uint32_t package_2; +}; + +struct WSProtocolEncoderTX_8300 { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + WSBlockGeneric generic; +}; + +typedef enum { + TX_8300DecoderStepReset = 0, + TX_8300DecoderStepCheckPreambule, + TX_8300DecoderStepSaveDuration, + TX_8300DecoderStepCheckDuration, +} TX_8300DecoderStep; + +const SubGhzProtocolDecoder ws_protocol_tx_8300_decoder = { + .alloc = ws_protocol_decoder_tx_8300_alloc, + .free = ws_protocol_decoder_tx_8300_free, + + .feed = ws_protocol_decoder_tx_8300_feed, + .reset = ws_protocol_decoder_tx_8300_reset, + + .get_hash_data = ws_protocol_decoder_tx_8300_get_hash_data, + .serialize = ws_protocol_decoder_tx_8300_serialize, + .deserialize = ws_protocol_decoder_tx_8300_deserialize, + .get_string = ws_protocol_decoder_tx_8300_get_string, +}; + +const SubGhzProtocolEncoder ws_protocol_tx_8300_encoder = { + .alloc = NULL, + .free = NULL, + + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol ws_protocol_tx_8300 = { + .name = WS_PROTOCOL_TX_8300_NAME, + .type = SubGhzProtocolWeatherStation, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable, + + .decoder = &ws_protocol_tx_8300_decoder, + .encoder = &ws_protocol_tx_8300_encoder, +}; + +void* ws_protocol_decoder_tx_8300_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + WSProtocolDecoderTX_8300* instance = malloc(sizeof(WSProtocolDecoderTX_8300)); + instance->base.protocol = &ws_protocol_tx_8300; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void ws_protocol_decoder_tx_8300_free(void* context) { + furi_assert(context); + WSProtocolDecoderTX_8300* instance = context; + free(instance); +} + +void ws_protocol_decoder_tx_8300_reset(void* context) { + furi_assert(context); + WSProtocolDecoderTX_8300* instance = context; + instance->decoder.parser_step = TX_8300DecoderStepReset; +} + +static bool ws_protocol_tx_8300_check_crc(WSProtocolDecoderTX_8300* instance) { + if(!instance->package_2) return false; + if(instance->package_1 != ~instance->package_2) return false; + + uint16_t x = 0; + uint16_t y = 0; + for(int i = 0; i < 32; i += 4) { + x += (instance->package_1 >> i) & 0x0F; + y += (instance->package_1 >> i) & 0x05; + } + uint8_t crc = (~x & 0xF) << 4 | (~y & 0xF); + return (crc == ((instance->decoder.decode_data) & 0xFF)); +} + +/** + * Analysis of received data + * @param instance Pointer to a WSBlockGeneric* instance + */ +static void ws_protocol_tx_8300_remote_controller(WSBlockGeneric* instance) { + instance->humidity = (((instance->data >> 28) & 0x0F) * 10) + ((instance->data >> 24) & 0x0F); + instance->btn = WS_NO_BTN; + if(!((instance->data >> 22) & 0x03)) + instance->battery_low = 0; + else + instance->battery_low = 1; + instance->channel = (instance->data >> 20) & 0x03; + instance->id = (instance->data >> 12) & 0x7F; + + float temp_raw = ((instance->data >> 8) & 0x0F) * 10.0f + ((instance->data >> 4) & 0x0F) + + (instance->data & 0x0F) * 0.1f; + if(!((instance->data >> 19) & 1)) { + instance->temp = temp_raw; + } else { + instance->temp = -temp_raw; + } +} + +void ws_protocol_decoder_tx_8300_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + WSProtocolDecoderTX_8300* instance = context; + + switch(instance->decoder.parser_step) { + case TX_8300DecoderStepReset: + if((level) && (DURATION_DIFF(duration, ws_protocol_tx_8300_const.te_short * 2) < + ws_protocol_tx_8300_const.te_delta)) { + instance->decoder.parser_step = TX_8300DecoderStepCheckPreambule; + } + break; + + case TX_8300DecoderStepCheckPreambule: + if((!level) && ((DURATION_DIFF(duration, ws_protocol_tx_8300_const.te_short * 2) < + ws_protocol_tx_8300_const.te_delta) || + (DURATION_DIFF(duration, ws_protocol_tx_8300_const.te_short * 3) < + ws_protocol_tx_8300_const.te_delta))) { + instance->decoder.parser_step = TX_8300DecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 1; + instance->package_1 = 0; + instance->package_2 = 0; + } else { + instance->decoder.parser_step = TX_8300DecoderStepReset; + } + break; + + case TX_8300DecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = TX_8300DecoderStepCheckDuration; + } else { + instance->decoder.parser_step = TX_8300DecoderStepReset; + } + break; + + case TX_8300DecoderStepCheckDuration: + if(!level) { + if(duration >= ((uint32_t)ws_protocol_tx_8300_const.te_short * 5)) { + //Found syncPostfix + if((instance->decoder.decode_count_bit == + ws_protocol_tx_8300_const.min_count_bit_for_found) && + ws_protocol_tx_8300_check_crc(instance)) { + instance->generic.data = instance->package_1; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + ws_protocol_tx_8300_remote_controller(&instance->generic); + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 1; + instance->decoder.parser_step = TX_8300DecoderStepReset; + break; + } else if( + (DURATION_DIFF(instance->decoder.te_last, ws_protocol_tx_8300_const.te_short) < + ws_protocol_tx_8300_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_tx_8300_const.te_long) < + ws_protocol_tx_8300_const.te_delta * 2)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = TX_8300DecoderStepSaveDuration; + } else if( + (DURATION_DIFF(instance->decoder.te_last, ws_protocol_tx_8300_const.te_short) < + ws_protocol_tx_8300_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_tx_8300_const.te_short) < + ws_protocol_tx_8300_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = TX_8300DecoderStepSaveDuration; + } else { + instance->decoder.parser_step = TX_8300DecoderStepReset; + } + + if(instance->decoder.decode_count_bit == TX_8300_PACKAGE_SIZE) { + instance->package_1 = instance->decoder.decode_data; + instance->decoder.decode_data = 0; + } else if(instance->decoder.decode_count_bit == TX_8300_PACKAGE_SIZE * 2) { + instance->package_2 = instance->decoder.decode_data; + instance->decoder.decode_data = 0; + } + + } else { + instance->decoder.parser_step = TX_8300DecoderStepReset; + } + break; + } +} + +uint8_t ws_protocol_decoder_tx_8300_get_hash_data(void* context) { + furi_assert(context); + WSProtocolDecoderTX_8300* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool ws_protocol_decoder_tx_8300_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + WSProtocolDecoderTX_8300* instance = context; + return ws_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +bool ws_protocol_decoder_tx_8300_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + WSProtocolDecoderTX_8300* instance = context; + bool ret = false; + do { + if(!ws_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if(instance->generic.data_count_bit != ws_protocol_tx_8300_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + ret = true; + } while(false); + return ret; +} + +void ws_protocol_decoder_tx_8300_get_string(void* context, FuriString* output) { + furi_assert(context); + WSProtocolDecoderTX_8300* instance = context; + furi_string_printf( + output, + "%s %dbit\r\n" + "Key:0x%lX%08lX\r\n" + "Sn:0x%lX Ch:%d Bat:%d\r\n" + "Temp:%3.1f C Hum:%d%%", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data >> 32), + (uint32_t)(instance->generic.data), + instance->generic.id, + instance->generic.channel, + instance->generic.battery_low, + (double)instance->generic.temp, + instance->generic.humidity); +} diff --git a/applications/plugins/weather_station/protocols/tx_8300.h b/applications/plugins/weather_station/protocols/tx_8300.h new file mode 100644 index 00000000000..ec198e80fa9 --- /dev/null +++ b/applications/plugins/weather_station/protocols/tx_8300.h @@ -0,0 +1,79 @@ +#pragma once + +#include + +#include +#include +#include +#include "ws_generic.h" +#include + +#define WS_PROTOCOL_TX_8300_NAME "TX8300" + +typedef struct WSProtocolDecoderTX_8300 WSProtocolDecoderTX_8300; +typedef struct WSProtocolEncoderTX_8300 WSProtocolEncoderTX_8300; + +extern const SubGhzProtocolDecoder ws_protocol_tx_8300_decoder; +extern const SubGhzProtocolEncoder ws_protocol_tx_8300_encoder; +extern const SubGhzProtocol ws_protocol_tx_8300; + +/** + * Allocate WSProtocolDecoderTX_8300. + * @param environment Pointer to a SubGhzEnvironment instance + * @return WSProtocolDecoderTX_8300* pointer to a WSProtocolDecoderTX_8300 instance + */ +void* ws_protocol_decoder_tx_8300_alloc(SubGhzEnvironment* environment); + +/** + * Free WSProtocolDecoderTX_8300. + * @param context Pointer to a WSProtocolDecoderTX_8300 instance + */ +void ws_protocol_decoder_tx_8300_free(void* context); + +/** + * Reset decoder WSProtocolDecoderTX_8300. + * @param context Pointer to a WSProtocolDecoderTX_8300 instance + */ +void ws_protocol_decoder_tx_8300_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a WSProtocolDecoderTX_8300 instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void ws_protocol_decoder_tx_8300_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a WSProtocolDecoderTX_8300 instance + * @return hash Hash sum + */ +uint8_t ws_protocol_decoder_tx_8300_get_hash_data(void* context); + +/** + * Serialize data WSProtocolDecoderTX_8300. + * @param context Pointer to a WSProtocolDecoderTX_8300 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool ws_protocol_decoder_tx_8300_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data WSProtocolDecoderTX_8300. + * @param context Pointer to a WSProtocolDecoderTX_8300 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool ws_protocol_decoder_tx_8300_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a WSProtocolDecoderTX_8300 instance + * @param output Resulting text + */ +void ws_protocol_decoder_tx_8300_get_string(void* context, FuriString* output); From 1dc79fddf0eaf4b3f774b0189a8e00150f420f44 Mon Sep 17 00:00:00 2001 From: Adam Boeglin Date: Thu, 15 Dec 2022 12:02:43 -0800 Subject: [PATCH 282/824] Added support for IDTECK cards (#2134) --- lib/lfrfid/protocols/lfrfid_protocols.c | 2 + lib/lfrfid/protocols/lfrfid_protocols.h | 1 + lib/lfrfid/protocols/protocol_idteck.c | 269 ++++++++++++++++++++++++ lib/lfrfid/protocols/protocol_idteck.h | 4 + 4 files changed, 276 insertions(+) create mode 100644 lib/lfrfid/protocols/protocol_idteck.c create mode 100644 lib/lfrfid/protocols/protocol_idteck.h diff --git a/lib/lfrfid/protocols/lfrfid_protocols.c b/lib/lfrfid/protocols/lfrfid_protocols.c index bd29bd8e09e..2c1f0ad97cd 100644 --- a/lib/lfrfid/protocols/lfrfid_protocols.c +++ b/lib/lfrfid/protocols/lfrfid_protocols.c @@ -1,6 +1,7 @@ #include "lfrfid_protocols.h" #include "protocol_em4100.h" #include "protocol_h10301.h" +#include "protocol_idteck.h" #include "protocol_indala26.h" #include "protocol_io_prox_xsf.h" #include "protocol_awid.h" @@ -19,6 +20,7 @@ const ProtocolBase* lfrfid_protocols[] = { [LFRFIDProtocolEM4100] = &protocol_em4100, [LFRFIDProtocolH10301] = &protocol_h10301, + [LFRFIDProtocolIdteck] = &protocol_idteck, [LFRFIDProtocolIndala26] = &protocol_indala26, [LFRFIDProtocolIOProxXSF] = &protocol_io_prox_xsf, [LFRFIDProtocolAwid] = &protocol_awid, diff --git a/lib/lfrfid/protocols/lfrfid_protocols.h b/lib/lfrfid/protocols/lfrfid_protocols.h index 26065c9aa69..848f003a31e 100644 --- a/lib/lfrfid/protocols/lfrfid_protocols.h +++ b/lib/lfrfid/protocols/lfrfid_protocols.h @@ -10,6 +10,7 @@ typedef enum { typedef enum { LFRFIDProtocolEM4100, LFRFIDProtocolH10301, + LFRFIDProtocolIdteck, LFRFIDProtocolIndala26, LFRFIDProtocolIOProxXSF, LFRFIDProtocolAwid, diff --git a/lib/lfrfid/protocols/protocol_idteck.c b/lib/lfrfid/protocols/protocol_idteck.c new file mode 100644 index 00000000000..033fcd28c09 --- /dev/null +++ b/lib/lfrfid/protocols/protocol_idteck.c @@ -0,0 +1,269 @@ +#include +#include +#include +#include "lfrfid_protocols.h" + +// Example: 4944544B 351FBE4B +// 01001001 01000100 01010100 01001011 00110101 00011111 10111110 01001011 +// 4 9 4 4 5 4 4 B 3 5 1 F B E 4 B +// 0100 1001 0100 0100 0101 0100 0100 1011 0011 0101 0001 1111 1011 1110 0100 1011 + +#define IDTECK_PREAMBLE_BIT_SIZE (32) +#define IDTECK_PREAMBLE_DATA_SIZE (8) + +#define IDTECK_ENCODED_BIT_SIZE (64) +#define IDTECK_ENCODED_DATA_SIZE (((IDTECK_ENCODED_BIT_SIZE) / 8) + IDTECK_PREAMBLE_DATA_SIZE) +#define IDTECK_ENCODED_DATA_LAST ((IDTECK_ENCODED_BIT_SIZE) / 8) + +#define IDTECK_DECODED_BIT_SIZE (64) +#define IDTECK_DECODED_DATA_SIZE (8) + +#define IDTECK_US_PER_BIT (255) +#define IDTECK_ENCODER_PULSES_PER_BIT (16) + +typedef struct { + uint8_t data_index; + uint8_t bit_clock_index; + bool last_bit; + bool current_polarity; + bool pulse_phase; +} ProtocolIdteckEncoder; + +typedef struct { + uint8_t encoded_data[IDTECK_ENCODED_DATA_SIZE]; + uint8_t negative_encoded_data[IDTECK_ENCODED_DATA_SIZE]; + uint8_t corrupted_encoded_data[IDTECK_ENCODED_DATA_SIZE]; + uint8_t corrupted_negative_encoded_data[IDTECK_ENCODED_DATA_SIZE]; + + uint8_t data[IDTECK_DECODED_DATA_SIZE]; + ProtocolIdteckEncoder encoder; +} ProtocolIdteck; + +ProtocolIdteck* protocol_idteck_alloc(void) { + ProtocolIdteck* protocol = malloc(sizeof(ProtocolIdteck)); + return protocol; +}; + +void protocol_idteck_free(ProtocolIdteck* protocol) { + free(protocol); +}; + +uint8_t* protocol_idteck_get_data(ProtocolIdteck* protocol) { + return protocol->data; +}; + +void protocol_idteck_decoder_start(ProtocolIdteck* protocol) { + memset(protocol->encoded_data, 0, IDTECK_ENCODED_DATA_SIZE); + memset(protocol->negative_encoded_data, 0, IDTECK_ENCODED_DATA_SIZE); + memset(protocol->corrupted_encoded_data, 0, IDTECK_ENCODED_DATA_SIZE); + memset(protocol->corrupted_negative_encoded_data, 0, IDTECK_ENCODED_DATA_SIZE); +}; + +static bool protocol_idteck_check_preamble(uint8_t* data, size_t bit_index) { + // Preamble 01001001 01000100 01010100 01001011 + if(*(uint32_t*)&data[bit_index / 8] != 0b01001011010101000100010001001001) return false; + return true; +} + +static bool protocol_idteck_can_be_decoded(uint8_t* data) { + if(!protocol_idteck_check_preamble(data, 0)) return false; + return true; +} + +static bool protocol_idteck_decoder_feed_internal(bool polarity, uint32_t time, uint8_t* data) { + time += (IDTECK_US_PER_BIT / 2); + + size_t bit_count = (time / IDTECK_US_PER_BIT); + bool result = false; + + if(bit_count < IDTECK_ENCODED_BIT_SIZE) { + for(size_t i = 0; i < bit_count; i++) { + bit_lib_push_bit(data, IDTECK_ENCODED_DATA_SIZE, polarity); + if(protocol_idteck_can_be_decoded(data)) { + result = true; + break; + } + } + } + + return result; +} + +static void protocol_idteck_decoder_save(uint8_t* data_to, const uint8_t* data_from) { + bit_lib_copy_bits(data_to, 0, 64, data_from, 0); +} + +bool protocol_idteck_decoder_feed(ProtocolIdteck* protocol, bool level, uint32_t duration) { + bool result = false; + + if(duration > (IDTECK_US_PER_BIT / 2)) { + if(protocol_idteck_decoder_feed_internal(level, duration, protocol->encoded_data)) { + protocol_idteck_decoder_save(protocol->data, protocol->encoded_data); + FURI_LOG_D("Idteck", "Positive"); + result = true; + return result; + } + + if(protocol_idteck_decoder_feed_internal( + !level, duration, protocol->negative_encoded_data)) { + protocol_idteck_decoder_save(protocol->data, protocol->negative_encoded_data); + FURI_LOG_D("Idteck", "Negative"); + result = true; + return result; + } + } + + if(duration > (IDTECK_US_PER_BIT / 4)) { + // Try to decode wrong phase synced data + if(level) { + duration += 120; + } else { + if(duration > 120) { + duration -= 120; + } + } + + if(protocol_idteck_decoder_feed_internal( + level, duration, protocol->corrupted_encoded_data)) { + protocol_idteck_decoder_save(protocol->data, protocol->corrupted_encoded_data); + FURI_LOG_D("Idteck", "Positive Corrupted"); + + result = true; + return result; + } + + if(protocol_idteck_decoder_feed_internal( + !level, duration, protocol->corrupted_negative_encoded_data)) { + protocol_idteck_decoder_save( + protocol->data, protocol->corrupted_negative_encoded_data); + FURI_LOG_D("Idteck", "Negative Corrupted"); + + result = true; + return result; + } + } + + return result; +}; + +bool protocol_idteck_encoder_start(ProtocolIdteck* protocol) { + memset(protocol->encoded_data, 0, IDTECK_ENCODED_DATA_SIZE); + *(uint32_t*)&protocol->encoded_data[0] = 0b01001011010101000100010001001001; + bit_lib_copy_bits(protocol->encoded_data, 32, 32, protocol->data, 32); + + protocol->encoder.last_bit = + bit_lib_get_bit(protocol->encoded_data, IDTECK_ENCODED_BIT_SIZE - 1); + protocol->encoder.data_index = 0; + protocol->encoder.current_polarity = true; + protocol->encoder.pulse_phase = true; + protocol->encoder.bit_clock_index = 0; + + return true; +}; + +LevelDuration protocol_idteck_encoder_yield(ProtocolIdteck* protocol) { + LevelDuration level_duration; + ProtocolIdteckEncoder* encoder = &protocol->encoder; + + if(encoder->pulse_phase) { + level_duration = level_duration_make(encoder->current_polarity, 1); + encoder->pulse_phase = false; + } else { + level_duration = level_duration_make(!encoder->current_polarity, 1); + encoder->pulse_phase = true; + + encoder->bit_clock_index++; + if(encoder->bit_clock_index >= IDTECK_ENCODER_PULSES_PER_BIT) { + encoder->bit_clock_index = 0; + + bool current_bit = bit_lib_get_bit(protocol->encoded_data, encoder->data_index); + + if(current_bit != encoder->last_bit) { + encoder->current_polarity = !encoder->current_polarity; + } + + encoder->last_bit = current_bit; + + bit_lib_increment_index(encoder->data_index, IDTECK_ENCODED_BIT_SIZE); + } + } + + return level_duration; +}; + +// factory code +static uint32_t get_fc(const uint8_t* data) { + uint32_t fc = 0; + fc = bit_lib_get_bits_32(data, 0, 32); + return fc; +} + +// card number +static uint32_t get_card(const uint8_t* data) { + uint32_t cn = 0; + cn = bit_lib_get_bits_32(data, 32, 32); + return cn; +} + +void protocol_idteck_render_data_internal(ProtocolIdteck* protocol, FuriString* result, bool brief) { + const uint32_t fc = get_fc(protocol->data); + const uint32_t card = get_card(protocol->data); + + if(brief) { + furi_string_printf(result, "FC: %08lX\r\nCard: %08lX", fc, card); + } else { + furi_string_printf( + result, + "FC: %08lX\r\n" + "Card: %08lX\r\n", + fc, + card); + } +} +void protocol_idteck_render_data(ProtocolIdteck* protocol, FuriString* result) { + protocol_idteck_render_data_internal(protocol, result, false); +} +void protocol_idteck_render_brief_data(ProtocolIdteck* protocol, FuriString* result) { + protocol_idteck_render_data_internal(protocol, result, true); +} + +bool protocol_idteck_write_data(ProtocolIdteck* protocol, void* data) { + LFRFIDWriteRequest* request = (LFRFIDWriteRequest*)data; + bool result = false; + + protocol_idteck_encoder_start(protocol); + + if(request->write_type == LFRFIDWriteTypeT5577) { + request->t5577.block[0] = LFRFID_T5577_BITRATE_RF_32 | LFRFID_T5577_MODULATION_PSK1 | + (2 << LFRFID_T5577_MAXBLOCK_SHIFT); + request->t5577.block[1] = bit_lib_get_bits_32(protocol->encoded_data, 0, 32); + request->t5577.block[2] = bit_lib_get_bits_32(protocol->encoded_data, 32, 32); + request->t5577.blocks_to_write = 3; + result = true; + } + return result; +}; + +const ProtocolBase protocol_idteck = { + .name = "Idteck", + .manufacturer = "IDTECK", + .data_size = IDTECK_DECODED_DATA_SIZE, + .features = LFRFIDFeaturePSK, + .validate_count = 6, + .alloc = (ProtocolAlloc)protocol_idteck_alloc, + .free = (ProtocolFree)protocol_idteck_free, + .get_data = (ProtocolGetData)protocol_idteck_get_data, + .decoder = + { + .start = (ProtocolDecoderStart)protocol_idteck_decoder_start, + .feed = (ProtocolDecoderFeed)protocol_idteck_decoder_feed, + }, + .encoder = + { + .start = (ProtocolEncoderStart)protocol_idteck_encoder_start, + .yield = (ProtocolEncoderYield)protocol_idteck_encoder_yield, + }, + .render_data = (ProtocolRenderData)protocol_idteck_render_data, + .render_brief_data = (ProtocolRenderData)protocol_idteck_render_brief_data, + .write_data = (ProtocolWriteData)protocol_idteck_write_data, +}; \ No newline at end of file diff --git a/lib/lfrfid/protocols/protocol_idteck.h b/lib/lfrfid/protocols/protocol_idteck.h new file mode 100644 index 00000000000..b7a5ade4722 --- /dev/null +++ b/lib/lfrfid/protocols/protocol_idteck.h @@ -0,0 +1,4 @@ +#pragma once +#include + +extern const ProtocolBase protocol_idteck; From b5e7bb3334d7dd61c3c0e0bc2d7c2d36bf4deeaa Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Fri, 16 Dec 2022 00:25:43 +0400 Subject: [PATCH 283/824] [FL-3043] SubGhz: add SMC5326, UNILARM protocol (#2138) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * SubGhz: add SMC5326 protocol * SubGhz: add unit_test smc5326 protocol Co-authored-by: あく --- .../debug/unit_tests/subghz/subghz_test.c | 17 +- assets/unit_tests/subghz/smc5326.sub | 8 + assets/unit_tests/subghz/smc5326_raw.sub | 7 + assets/unit_tests/subghz/test_random_raw.sub | 4 +- lib/subghz/protocols/protocol_items.c | 2 +- lib/subghz/protocols/protocol_items.h | 1 + lib/subghz/protocols/smc5326.c | 387 ++++++++++++++++++ lib/subghz/protocols/smc5326.h | 107 +++++ 8 files changed, 530 insertions(+), 3 deletions(-) create mode 100644 assets/unit_tests/subghz/smc5326.sub create mode 100644 assets/unit_tests/subghz/smc5326_raw.sub create mode 100644 lib/subghz/protocols/smc5326.c create mode 100644 lib/subghz/protocols/smc5326.h diff --git a/applications/debug/unit_tests/subghz/subghz_test.c b/applications/debug/unit_tests/subghz/subghz_test.c index fe834c606cc..d954ddd943e 100644 --- a/applications/debug/unit_tests/subghz/subghz_test.c +++ b/applications/debug/unit_tests/subghz/subghz_test.c @@ -13,7 +13,7 @@ #define CAME_ATOMO_DIR_NAME EXT_PATH("subghz/assets/came_atomo") #define NICE_FLOR_S_DIR_NAME EXT_PATH("subghz/assets/nice_flor_s") #define TEST_RANDOM_DIR_NAME EXT_PATH("unit_tests/subghz/test_random_raw.sub") -#define TEST_RANDOM_COUNT_PARSE 244 +#define TEST_RANDOM_COUNT_PARSE 253 #define TEST_TIMEOUT 10000 static SubGhzEnvironment* environment_handler; @@ -587,6 +587,13 @@ MU_TEST(subghz_decoder_ansonic_test) { "Test decoder " SUBGHZ_PROTOCOL_ANSONIC_NAME " error\r\n"); } +MU_TEST(subghz_decoder_smc5326_test) { + mu_assert( + subghz_decoder_test( + EXT_PATH("unit_tests/subghz/smc5326_raw.sub"), SUBGHZ_PROTOCOL_SMC5326_NAME), + "Test decoder " SUBGHZ_PROTOCOL_SMC5326_NAME " error\r\n"); +} + //test encoders MU_TEST(subghz_encoder_princeton_test) { mu_assert( @@ -714,6 +721,12 @@ MU_TEST(subghz_encoder_ansonic_test) { "Test encoder " SUBGHZ_PROTOCOL_ANSONIC_NAME " error\r\n"); } +MU_TEST(subghz_encoder_smc5326_test) { + mu_assert( + subghz_encoder_test(EXT_PATH("unit_tests/subghz/smc5326.sub")), + "Test encoder " SUBGHZ_PROTOCOL_SMC5326_NAME " error\r\n"); +} + MU_TEST(subghz_random_test) { mu_assert(subghz_decode_random_test(TEST_RANDOM_DIR_NAME), "Random test error\r\n"); } @@ -757,6 +770,7 @@ MU_TEST_SUITE(subghz) { MU_RUN_TEST(subghz_decoder_intertechno_v3_test); MU_RUN_TEST(subghz_decoder_clemsa_test); MU_RUN_TEST(subghz_decoder_ansonic_test); + MU_RUN_TEST(subghz_decoder_smc5326_test); MU_RUN_TEST(subghz_encoder_princeton_test); MU_RUN_TEST(subghz_encoder_came_test); @@ -779,6 +793,7 @@ MU_TEST_SUITE(subghz) { MU_RUN_TEST(subghz_encoder_intertechno_v3_test); MU_RUN_TEST(subghz_encoder_clemsa_test); MU_RUN_TEST(subghz_encoder_ansonic_test); + MU_RUN_TEST(subghz_encoder_smc5326_test); MU_RUN_TEST(subghz_random_test); subghz_test_deinit(); diff --git a/assets/unit_tests/subghz/smc5326.sub b/assets/unit_tests/subghz/smc5326.sub new file mode 100644 index 00000000000..eab7aac99b8 --- /dev/null +++ b/assets/unit_tests/subghz/smc5326.sub @@ -0,0 +1,8 @@ +Filetype: Flipper SubGhz Key File +Version: 1 +Frequency: 433920000 +Preset: FuriHalSubGhzPresetOok650Async +Protocol: SMC5326 +Bit: 25 +Key: 00 00 00 00 01 7D 55 80 +TE: 210 diff --git a/assets/unit_tests/subghz/smc5326_raw.sub b/assets/unit_tests/subghz/smc5326_raw.sub new file mode 100644 index 00000000000..f2e3edbf0ec --- /dev/null +++ b/assets/unit_tests/subghz/smc5326_raw.sub @@ -0,0 +1,7 @@ +Filetype: Flipper SubGhz RAW File +Version: 1 +Frequency: 433920000 +Preset: FuriHalSubGhzPresetOok650Async +Protocol: RAW +RAW_Data: 2442 -312 275 -972 949 -310 941 -322 923 -342 921 -352 923 -334 281 -954 945 -350 279 -958 907 -354 289 -980 909 -352 281 -962 907 -330 311 -964 913 -350 317 -930 933 -344 921 -352 893 -330 311 -954 943 -318 315 -958 909 -324 947 -7854 953 -322 289 -948 939 -354 927 -332 911 -324 943 -344 917 -318 317 -964 905 -344 303 -942 947 -312 319 -960 913 -348 281 -958 941 -322 295 -978 905 -350 279 -962 931 -328 947 -324 939 -346 267 -964 935 -348 283 -938 953 -318 931 -7868 935 -346 269 -968 953 -310 941 -322 921 -330 935 -342 931 -318 311 -962 939 -290 337 -950 909 -352 317 -924 943 -324 313 -938 941 -318 317 -932 939 -344 301 -938 933 -350 921 -322 959 -310 301 -942 933 -352 317 -926 957 -314 919 -7868 943 -314 317 -958 909 -322 951 -344 919 -352 921 -324 937 -326 281 -964 941 -318 317 -930 939 -344 301 -938 933 -352 281 -962 953 -314 317 -922 933 -330 315 -954 943 -318 921 -342 943 -320 291 -980 909 -354 281 -962 943 -296 967 -7836 943 -332 309 -950 935 -318 929 -340 943 -320 921 -344 921 -354 283 -960 943 -296 309 -964 945 -318 279 -964 941 -322 333 -944 939 -314 279 -992 903 -342 319 -932 933 -330 931 -340 929 -348 281 -964 935 -334 281 -970 927 -346 921 -7862 951 -314 319 -922 953 -320 923 -346 921 -320 965 -298 943 -324 313 -942 941 -320 317 -930 941 -344 303 -940 945 -312 321 -940 953 -314 303 -960 933 -348 287 -962 911 -352 917 -350 905 -324 333 -918 971 -322 317 -924 945 -324 937 -7872 919 -324 317 -942 941 -318 933 -330 943 -324 943 -310 951 -318 317 -930 939 -344 301 -938 933 -352 317 -926 953 -314 319 -924 939 -324 331 -950 907 -354 315 -926 945 -324 939 -312 953 -318 317 -930 937 -344 301 -940 947 -348 909 -7864 949 -310 319 -956 915 -350 919 -348 905 -322 963 -296 935 -348 317 -922 951 -322 295 -976 939 -314 281 -996 915 -326 307 -940 959 -310 301 -966 935 -346 285 -958 915 -348 921 -348 903 -354 303 -948 911 -350 315 -926 945 -324 941 -7874 943 -290 319 -942 973 -318 929 -314 937 -328 941 -324 939 -310 303 -962 933 -352 285 -962 949 -314 319 -924 951 -320 293 -948 941 -354 283 -962 943 -294 309 -966 943 -320 931 -328 943 -326 311 -940 939 -320 309 -958 933 -338 943 -7840 933 -352 277 -964 941 -322 923 -344 923 -350 931 -310 955 -320 291 -974 907 -350 281 -958 963 -298 313 -956 945 -314 311 -960 937 -312 311 -966 909 -324 319 -944 941 -354 929 -298 945 -324 315 -940 +RAW_Data: 943 -354 281 -964 905 -330 933 -7868 951 -324 315 -938 943 -354 893 -330 943 -324 943 -344 919 -318 317 -962 903 -344 301 -974 903 -350 317 -932 931 -342 269 -972 949 -346 285 -938 955 -310 301 -964 935 -348 921 -320 921 -344 301 -940 935 -350 317 -930 929 -318 937 -7872 939 -344 301 -940 947 -346 917 -322 921 -344 923 -352 927 -334 281 -970 925 -334 277 -982 943 -318 317 -932 931 -344 301 -936 935 -350 281 -960 957 -312 303 -960 935 -346 907 -322 929 -344 301 -942 935 -350 317 -924 955 -312 951 -7858 919 -342 309 -940 949 -348 909 -322 923 -344 923 -352 923 -336 317 -924 945 -312 311 -966 921 -340 317 -924 947 -350 281 -958 941 -322 291 -976 905 -350 279 -960 935 -342 943 -320 919 -330 311 -958 943 -320 315 -932 935 -344 919 -7866 957 -312 303 -964 917 -342 945 -320 923 -344 923 -354 929 -298 315 -956 941 -318 315 -960 911 -324 317 -942 939 -354 281 -964 941 -294 311 -968 943 -318 317 -932 937 -330 931 -350 919 -348 283 -960 917 -350 317 -922 939 -322 965 -7864 921 -324 329 -950 909 -354 923 -336 913 -322 947 -344 919 -354 281 -962 941 -294 311 -960 935 -354 281 -962 939 -294 311 -964 937 -354 281 -964 941 -296 309 -964 939 -318 931 -330 945 -324 315 -940 939 -354 281 -964 909 -344 921 -7862 963 -304 307 -976 933 -320 929 -328 941 -324 939 -348 915 -320 317 -930 939 -344 301 -940 965 -320 319 -926 953 -312 303 -960 933 -312 321 -960 913 -348 319 -924 943 -320 959 -310 921 -354 319 -924 943 -324 311 -938 941 -318 957 -7862 943 -318 317 -932 933 -344 925 -352 897 -332 943 -324 943 -346 267 -966 951 -310 321 -960 911 -350 281 -958 949 -320 291 -978 937 -316 279 -964 949 -326 309 -944 943 -314 959 -318 933 -336 317 -934 933 -344 267 -964 937 -350 905 -7896 943 -318 319 -926 955 -314 919 -350 935 -324 941 -294 967 -312 303 -962 933 -348 285 -960 917 -348 317 -922 941 -322 329 -950 907 -354 315 -926 943 -326 313 -940 941 -352 893 -332 949 -324 315 -938 941 -352 283 -962 943 -310 925 -7890 931 -344 269 -968 949 -310 943 -320 923 -350 937 -310 955 -318 317 -930 935 -344 301 -942 947 -346 285 -958 915 -346 317 -924 951 -322 295 -982 905 -352 317 -924 945 -324 941 -346 917 -318 317 -962 905 -330 311 -956 937 -352 897 -7878 939 -354 283 -960 941 -294 965 -312 953 -318 385 -201512 165 -198 265 -526 229 -298 755 -164 61687 -17310 131 -1056 99 -296 195 -296 65 -66 1617 diff --git a/assets/unit_tests/subghz/test_random_raw.sub b/assets/unit_tests/subghz/test_random_raw.sub index a6a3c9866b0..900b26207a2 100644 --- a/assets/unit_tests/subghz/test_random_raw.sub +++ b/assets/unit_tests/subghz/test_random_raw.sub @@ -165,4 +165,6 @@ RAW_Data: 1125 -536 1145 -19444 567 -542 1151 -1086 581 -534 1133 -1084 583 -530 RAW_Data: 317 -144 57 -486 53 -282 115 -585 97 -72 229 -174 257 -440 225 -86 173 -518 243 -167 95 -259 137 -96 694 -58 227 -80 279 -287 71 -72 301 -72 121 -106 51 -84 57 -58 199 -260 143 -288 219 -174 113 -681 115 -172 403 -58 113 -116 113 -432 171 -202 55 -108 95 -212 113 -72 527 -166 95 -212 195 -108 603 -142 239 -296 173 -346 373 -287 53 -80 79 -72 95 -238 95 -312 167 -618 143 -288 95 -72 95 -72 141 -210 55 -258 143 -328 305 -58 87 -86 315 -116 195 -218 85 -290 285 -220 215 -189 201 -58 57 -645 119 -96 71 -144 119 -406 143 -72 191 -72 631 -268 344 -56 115 -260 315 -140 455 -518 57 -58 171 -144 488 -86 219 -232 257 -144 85 -174 171 -260 115 -56 87 -166 197 -58 83 -56 85 -288 113 -410 115 -172 163 -202 113 -58 201 -144 201 -86 143 -264 167 -212 113 -116 139 -72 181 -287 343 -430 201 -260 201 -462 143 -192 301 -230 191 -454 187 -144 315 -164 143 -477 165 -58 201 -114 143 -490 115 -86 201 -58 113 -88 85 -58 203 -198 375 -86 171 -346 95 -88 257 -170 81 -56 143 -172 335 -230 173 -202 133 -471 187 -264 215 -86 115 -198 159 -72 179 -112 195 -116 449 -216 93 -96 167 -216 71 -216 71 -166 235 -86 447 -102 101 -226 195 -213 71 -144 215 -144 215 -261 241 -136 269 -142 263 -311 215 -172 201 -144 265 -168 71 -404 259 -86 85 -230 115 -650 143 -202 749 -512 248 -316 201 -154 71 -96 95 -360 105 -56 57 -432 95 -288 95 -286 95 -96 166 -144 93 -144 167 -150 904 -162 95 -526 287 -244 95 -240 383 -120 167 -394 430 -854 95 -72 143 -194 227 -120 167 -264 405 -144 143 -72 143 -72 141 -120 187 -86 143 -164 170 -96 143 -58 143 -86 402 -166 153 -120 95 -96 69 -96 71 -359 404 -338 71 -225 93 -74 97 -54 161 -114 319 -288 113 -116 459 -202 115 -114 115 -116 143 -86 57 -56 87 -114 85 -375 113 -58 311 -240 203 -288 95 -72 119 -383 213 -384 115 -86 171 -58 53 -104 401 -58 115 -86 373 -116 143 -144 161 -216 406 -72 263 -96 215 -72 95 -94 167 -96 191 -240 95 -94 214 -120 403 -116 200 -114 57 -172 220 -120 137 -364 334 -392 115 -260 199 -116 373 -188 95 -110 143 -172 87 -114 172 -230 57 -316 201 -56 249 -485 171 -202 87 -86 85 -144 345 -86 171 -58 259 -58 295 -120 95 -120 71 -192 635 -118 167 -96 375 -72 119 -120 261 -144 167 -96 95 -96 923 -215 71 -433 71 -477 RAW_Data: 191 -240 85 -72 637 -408 213 -510 261 -168 143 -126 79 -106 167 -72 117 -218 251 -168 119 -96 215 -182 191 -238 517 -116 201 -144 255 -154 97 -94 215 -72 95 -120 71 -288 261 -106 434 -96 606 -232 229 -432 85 -174 343 -58 329 -156 55 -116 259 -144 488 -56 307 -339 115 -202 334 -88 113 -86 57 -174 143 -144 401 -376 85 -240 267 -82 95 -216 137 -158 85 -144 143 -58 221 -308 295 -114 87 -114 301 -120 358 -517 71 -262 191 -144 57 -140 165 -407 53 -262 217 -120 238 -358 119 -357 71 -72 119 -96 428 -72 95 -72 167 -72 93 -240 335 -96 357 -240 173 -230 143 -114 87 -200 143 -232 287 -150 97 -288 71 -72 93 -288 115 -58 143 -230 109 -264 71 -72 119 -72 238 -242 97 -78 163 -86 115 -518 79 -560 205 -449 969 -144 507 -86 231 -114 345 -58 979 -110 85 -288 287 -404 229 -202 57 -274 233 -86 115 -202 632 -230 85 -312 369 -392 460 -450 75 -280 85 -202 201 -86 229 -174 143 -144 233 -528 115 -212 127 -202 287 -172 403 -172 139 -128 165 -138 261 -392 143 -480 142 -189 291 -80 53 -283 167 -140 113 -1008 191 -144 119 -120 71 -193 241 -462 201 -58 143 -344 539 -316 113 -174 85 -116 113 -250 239 -168 405 -168 239 -158 85 -144 115 -86 57 -86 341 -144 171 -202 85 -202 115 -114 719 -88 55 -318 257 -56 254 -86 171 -116 459 -174 171 -329 95 -134 85 -314 431 -306 77 -316 401 -86 173 -404 281 -1073 488 -94 217 -78 101 -98 214 -120 215 -340 403 -535 143 -564 115 -116 199 -58 85 -174 315 -58 335 -136 55 -260 143 -144 229 -460 143 -58 143 -144 171 -202 115 -374 291 -130 339 -82 143 -58 171 -58 201 -86 85 -174 1022 -56 85 -82 255 -240 103 -202 431 -278 95 -216 119 -72 71 -96 71 -559 57 -144 171 -88 113 -86 231 -414 131 -192 237 -360 95 -168 145 -168 213 -120 167 -96 143 -110 57 -86 259 -56 87 -777 295 -96 57 -86 173 -86 171 -404 143 -172 231 -200 57 -441 55 -58 173 -56 87 -86 171 -72 287 -72 119 -262 119 -144 71 -72 121 -310 71 -302 113 -54 193 -80 307 -58 257 -232 143 -56 143 -116 219 -72 695 -70 71 -460 85 -232 719 -363 57 -402 604 -230 287 -138 83 -172 259 -58 171 -174 55 -88 489 -114 143 -116 171 -116 143 -58 199 -144 145 -343 374 -186 235 -140 77 -86 143 -202 143 -144 113 -144 143 -58 732 -96 263 -264 71 -206 95 -168 215 -144 271 -80 139 -88 85 -414 75 -100 RAW_Data: 285 -96 627 -362 53 -84 201 -374 113 -202 115 -202 421 -316 85 -58 139 -224 87 -86 229 -58 243 -178 267 -288 95 -336 171 -96 213 -288 71 -405 95 -96 95 -384 95 -72 213 -72 95 -96 95 -272 87 -1083 85 -58 113 -88 257 -116 143 -292 175 -318 95 -120 95 -144 95 -72 71 -216 368 -116 373 -172 115 -58 85 -116 143 -86 85 -144 201 -86 201 -202 257 -144 201 -174 113 -144 115 -144 257 -202 585 -364 173 -138 287 -422 431 -86 85 -96 869 -186 95 -52 115 -86 115 -58 55 -276 365 -86 85 -489 171 -140 577 -106 718 -144 391 -232 195 -82 143 -172 109 -120 167 -96 280 -216 145 -240 215 -186 163 -96 141 -172 159 -603 257 -108 629 -192 119 -80 87 -172 57 -144 286 -86 57 -230 344 -58 113 -537 75 -96 537 -86 403 -196 167 -264 119 -238 119 -120 167 -96 95 -478 95 -120 167 -216 1085 -96 358 -72 263 -72 69 -120 143 -96 71 -96 191 -362 55 -144 57 -260 113 -58 85 -174 55 -88 257 -86 231 -194 55 -58 115 -56 55 -339 55 -58 374 -172 139 -82 419 -98 119 -261 71 -72 71 -240 713 -86 143 -218 295 -72 53 -56 431 -58 317 -144 161 -144 373 -144 173 -144 57 -114 85 -116 195 -72 708 -172 115 -86 191 -96 506 -120 71 -174 85 -58 363 -114 317 -230 316 -200 87 -114 57 -230 115 -315 173 -280 694 -212 453 -256 143 -202 113 -540 352 -116 257 -116 457 -56 109 -58 143 -230 259 -144 259 -525 119 -408 247 -112 389 -72 431 -96 137 -236 97 -474 201 -298 71 -82 55 -116 55 -112 199 -174 191 -86 143 -144 115 -114 317 -86 85 -230 87 -114 259 -84 107 -130 143 -94 153 -86 135 -94 215 -72 239 -94 435 -96 263 -142 166 -334 87 -194 179 -96 115 -284 135 -56 57 -144 463 -204 143 -316 201 -58 403 -86 141 -288 85 -202 139 -397 171 -174 305 -202 85 -144 373 -253 161 -492 181 -191 95 -216 315 -191 71 -166 97 -126 337 -96 71 -96 189 -168 295 -84 197 -86 259 -345 137 -144 167 -796 115 -344 455 -72 119 -96 119 -550 209 -88 85 -86 143 -340 167 -260 143 -537 85 -226 51 -537 57 -260 315 -461 51 -84 199 -358 383 -96 143 -257 115 -86 173 -86 201 -144 143 -316 85 -86 479 -88 85 -72 71 -104 115 -116 267 -72 137 -144 143 -116 85 -86 373 -288 115 -200 87 -114 259 -114 259 -462 143 -144 171 -86 57 -58 137 -144 57 -634 343 -72 205 -86 143 -258 57 -232 113 -230 461 -58 185 -74 537 -86 -RAW_Data: 535 -142 57 -58 55 -116 115 -432 85 -172 259 -192 167 -120 117 -72 119 -240 334 -72 71 -267 285 -144 119 -374 85 -88 85 -114 143 -202 229 -58 143 -202 115 -202 171 -86 71 -144 87 -56 173 -373 143 -116 113 -462 169 -80 215 -148 115 -336 85 -230 163 -432 85 -374 639 -174 85 -58 57 -82 295 -352 269 -532 414 -322 95 -287 263 -268 115 -56 259 -76 85 -282 401 -305 516 -114 115 -202 171 -86 451 -110 85 -346 201 -274 149 -202 85 -364 366 -258 57 -114 259 -172 142 -144 85 -116 85 -480 171 -144 57 -352 115 -116 535 -404 315 -202 163 -158 517 -316 215 -98 85 -346 85 -144 87 -86 257 -82 167 -58 85 -116 113 -894 233 -186 77 -266 147 -72 71 -82 57 -86 171 -58 57 -86 201 -364 143 -202 115 -114 85 -88 113 -86 87 -230 57 -76 613 -72 85 -96 209 -346 458 -58 547 -490 201 -315 315 -116 75 -168 359 -335 95 -384 93 -120 71 -312 251 -366 233 -96 189 -240 263 -192 271 -58 115 -58 229 -346 459 -174 113 -144 173 -144 218 -224 57 -116 215 -72 103 -202 513 -210 433 -116 113 -174 650 -273 147 -450 375 -86 115 -172 536 -84 85 -230 85 -58 195 -468 287 -110 551 -214 167 -311 213 -250 85 -58 85 -355 113 -230 115 -144 117 -288 195 -202 57 -376 123 -144 236 -168 553 -284 119 -72 143 -188 161 -120 93 -312 335 -58 55 -260 105 -244 143 -120 381 -268 173 -268 635 -168 453 -318 71 -167 71 -406 191 -172 215 -408 119 -144 93 -120 97 -130 143 -192 308 -122 147 -550 313 -96 139 -162 167 -96 431 -80 83 -112 201 -86 287 -86 229 -116 57 -288 113 -174 143 -116 113 -144 115 -518 57 -230 57 -172 231 -86 113 -314 183 -144 119 -72 165 -446 81 -86 135 -190 143 -96 71 -72 411 -96 143 -120 69 -216 349 -72 95 -96 517 -646 163 -86 113 -116 171 -116 143 -116 113 -287 259 -114 517 -168 141 -116 105 -72 95 -96 311 -118 159 -310 191 -54 143 -258 115 -450 219 -54 339 -372 239 -72 167 -174 113 -58 57 -144 259 -172 143 -336 113 -174 85 -230 83 -668 85 -202 113 -144 57 -116 373 -316 719 -288 115 -58 75 -120 139 -144 229 -144 57 -144 171 -192 391 -202 403 -58 315 -188 259 -56 115 -144 85 -404 57 -58 105 -102 429 -406 81 -172 57 -144 287 -230 287 -220 317 -458 283 -58 113 -86 269 -72 281 -58 85 -202 113 -52 421 -58 229 -480 259 -58 143 -660 155 -638 123 -86 57 -86 143 -346 143 -144 57 -144 \ No newline at end of file +RAW_Data: 535 -142 57 -58 55 -116 115 -432 85 -172 259 -192 167 -120 117 -72 119 -240 334 -72 71 -267 285 -144 119 -374 85 -88 85 -114 143 -202 229 -58 143 -202 115 -202 171 -86 71 -144 87 -56 173 -373 143 -116 113 -462 169 -80 215 -148 115 -336 85 -230 163 -432 85 -374 639 -174 85 -58 57 -82 295 -352 269 -532 414 -322 95 -287 263 -268 115 -56 259 -76 85 -282 401 -305 516 -114 115 -202 171 -86 451 -110 85 -346 201 -274 149 -202 85 -364 366 -258 57 -114 259 -172 142 -144 85 -116 85 -480 171 -144 57 -352 115 -116 535 -404 315 -202 163 -158 517 -316 215 -98 85 -346 85 -144 87 -86 257 -82 167 -58 85 -116 113 -894 233 -186 77 -266 147 -72 71 -82 57 -86 171 -58 57 -86 201 -364 143 -202 115 -114 85 -88 113 -86 87 -230 57 -76 613 -72 85 -96 209 -346 458 -58 547 -490 201 -315 315 -116 75 -168 359 -335 95 -384 93 -120 71 -312 251 -366 233 -96 189 -240 263 -192 271 -58 115 -58 229 -346 459 -174 113 -144 173 -144 218 -224 57 -116 215 -72 103 -202 513 -210 433 -116 113 -174 650 -273 147 -450 375 -86 115 -172 536 -84 85 -230 85 -58 195 -468 287 -110 551 -214 167 -311 213 -250 85 -58 85 -355 113 -230 115 -144 117 -288 195 -202 57 -376 123 -144 236 -168 553 -284 119 -72 143 -188 161 -120 93 -312 335 -58 55 -260 105 -244 143 -120 381 -268 173 -268 635 -168 453 -318 71 -167 71 -406 191 -172 215 -408 119 -144 93 -120 97 -130 143 -192 308 -122 147 -550 313 -96 139 -162 167 -96 431 -80 83 -112 201 -86 287 -86 229 -116 57 -288 113 -174 143 -116 113 -144 115 -518 57 -230 57 -172 231 -86 113 -314 183 -144 119 -72 165 -446 81 -86 135 -190 143 -96 71 -72 411 -96 143 -120 69 -216 349 -72 95 -96 517 -646 163 -86 113 -116 171 -116 143 -116 113 -287 259 -114 517 -168 141 -116 105 -72 95 -96 311 -118 159 -310 191 -54 143 -258 115 -450 219 -54 339 -372 239 -72 167 -174 113 -58 57 -144 259 -172 143 -336 113 -174 85 -230 83 -668 85 -202 113 -144 57 -116 373 -316 719 -288 115 -58 75 -120 139 -144 229 -144 57 -144 171 -192 391 -202 403 -58 315 -188 259 -56 115 -144 85 -404 57 -58 105 -102 429 -406 81 -172 57 -144 287 -230 287 -220 317 -458 283 -58 113 -86 269 -72 281 -58 85 -202 113 -52 421 -58 229 -480 259 -58 143 -660 155 -638 123 -86 57 -86 143 -346 143 -144 57 -144 +RAW_Data: 2442 -312 275 -972 949 -310 941 -322 923 -342 921 -352 923 -334 281 -954 945 -350 279 -958 907 -354 289 -980 909 -352 281 -962 907 -330 311 -964 913 -350 317 -930 933 -344 921 -352 893 -330 311 -954 943 -318 315 -958 909 -324 947 -7854 953 -322 289 -948 939 -354 927 -332 911 -324 943 -344 917 -318 317 -964 905 -344 303 -942 947 -312 319 -960 913 -348 281 -958 941 -322 295 -978 905 -350 279 -962 931 -328 947 -324 939 -346 267 -964 935 -348 283 -938 953 -318 931 -7868 935 -346 269 -968 953 -310 941 -322 921 -330 935 -342 931 -318 311 -962 939 -290 337 -950 909 -352 317 -924 943 -324 313 -938 941 -318 317 -932 939 -344 301 -938 933 -350 921 -322 959 -310 301 -942 933 -352 317 -926 957 -314 919 -7868 943 -314 317 -958 909 -322 951 -344 919 -352 921 -324 937 -326 281 -964 941 -318 317 -930 939 -344 301 -938 933 -352 281 -962 953 -314 317 -922 933 -330 315 -954 943 -318 921 -342 943 -320 291 -980 909 -354 281 -962 943 -296 967 -7836 943 -332 309 -950 935 -318 929 -340 943 -320 921 -344 921 -354 283 -960 943 -296 309 -964 945 -318 279 -964 941 -322 333 -944 939 -314 279 -992 903 -342 319 -932 933 -330 931 -340 929 -348 281 -964 935 -334 281 -970 927 -346 921 -7862 951 -314 319 -922 953 -320 923 -346 921 -320 965 -298 943 -324 313 -942 941 -320 317 -930 941 -344 303 -940 945 -312 321 -940 953 -314 303 -960 933 -348 287 -962 911 -352 917 -350 905 -324 333 -918 971 -322 317 -924 945 -324 937 -7872 919 -324 317 -942 941 -318 933 -330 943 -324 943 -310 951 -318 317 -930 939 -344 301 -938 933 -352 317 -926 953 -314 319 -924 939 -324 331 -950 907 -354 315 -926 945 -324 939 -312 953 -318 317 -930 937 -344 301 -940 947 -348 909 -7864 949 -310 319 -956 915 -350 919 -348 905 -322 963 -296 935 -348 317 -922 951 -322 295 -976 939 -314 281 -996 915 -326 307 -940 959 -310 301 -966 935 -346 285 -958 915 -348 921 -348 903 -354 303 -948 911 -350 315 -926 945 -324 941 -7874 943 -290 319 -942 973 -318 929 -314 937 -328 941 -324 939 -310 303 -962 933 -352 285 -962 949 -314 319 -924 951 -320 293 -948 941 -354 283 -962 943 -294 309 -966 943 -320 931 -328 943 -326 311 -940 939 -320 309 -958 933 -338 943 -7840 933 -352 277 -964 941 -322 923 -344 923 -350 931 -310 955 -320 291 -974 907 -350 281 -958 963 -298 313 -956 945 -314 311 -960 937 -312 311 -966 909 -324 319 -944 941 -354 929 -298 945 -324 315 -940 +RAW_Data: 943 -354 281 -964 905 -330 933 -7868 951 -324 315 -938 943 -354 893 -330 943 -324 943 -344 919 -318 317 -962 903 -344 301 -974 903 -350 317 -932 931 -342 269 -972 949 -346 285 -938 955 -310 301 -964 935 -348 921 -320 921 -344 301 -940 935 -350 317 -930 929 -318 937 -7872 939 -344 301 -940 947 -346 917 -322 921 -344 923 -352 927 -334 281 -970 925 -334 277 -982 943 -318 317 -932 931 -344 301 -936 935 -350 281 -960 957 -312 303 -960 935 -346 907 -322 929 -344 301 -942 935 -350 317 -924 955 -312 951 -7858 919 -342 309 -940 949 -348 909 -322 923 -344 923 -352 923 -336 317 -924 945 -312 311 -966 921 -340 317 -924 947 -350 281 -958 941 -322 291 -976 905 -350 279 -960 935 -342 943 -320 919 -330 311 -958 943 -320 315 -932 935 -344 919 -7866 957 -312 303 -964 917 -342 945 -320 923 -344 923 -354 929 -298 315 -956 941 -318 315 -960 911 -324 317 -942 939 -354 281 -964 941 -294 311 -968 943 -318 317 -932 937 -330 931 -350 919 -348 283 -960 917 -350 317 -922 939 -322 965 -7864 921 -324 329 -950 909 -354 923 -336 913 -322 947 -344 919 -354 281 -962 941 -294 311 -960 935 -354 281 -962 939 -294 311 -964 937 -354 281 -964 941 -296 309 -964 939 -318 931 -330 945 -324 315 -940 939 -354 281 -964 909 -344 921 -7862 963 -304 307 -976 933 -320 929 -328 941 -324 939 -348 915 -320 317 -930 939 -344 301 -940 965 -320 319 -926 953 -312 303 -960 933 -312 321 -960 913 -348 319 -924 943 -320 959 -310 921 -354 319 -924 943 -324 311 -938 941 -318 957 -7862 943 -318 317 -932 933 -344 925 -352 897 -332 943 -324 943 -346 267 -966 951 -310 321 -960 911 -350 281 -958 949 -320 291 -978 937 -316 279 -964 949 -326 309 -944 943 -314 959 -318 933 -336 317 -934 933 -344 267 -964 937 -350 905 -7896 943 -318 319 -926 955 -314 919 -350 935 -324 941 -294 967 -312 303 -962 933 -348 285 -960 917 -348 317 -922 941 -322 329 -950 907 -354 315 -926 943 -326 313 -940 941 -352 893 -332 949 -324 315 -938 941 -352 283 -962 943 -310 925 -7890 931 -344 269 -968 949 -310 943 -320 923 -350 937 -310 955 -318 317 -930 935 -344 301 -942 947 -346 285 -958 915 -346 317 -924 951 -322 295 -982 905 -352 317 -924 945 -324 941 -346 917 -318 317 -962 905 -330 311 -956 937 -352 897 -7878 939 -354 283 -960 941 -294 965 -312 953 -318 385 -201512 165 -198 265 -526 229 -298 755 -164 61687 -17310 131 -1056 99 -296 195 -296 65 -66 1617 diff --git a/lib/subghz/protocols/protocol_items.c b/lib/subghz/protocols/protocol_items.c index 24aaae8dfb9..ed46f5c9787 100644 --- a/lib/subghz/protocols/protocol_items.c +++ b/lib/subghz/protocols/protocol_items.c @@ -12,7 +12,7 @@ const SubGhzProtocol* subghz_protocol_registry_items[] = { &subghz_protocol_chamb_code, &subghz_protocol_power_smart, &subghz_protocol_marantec, &subghz_protocol_bett, &subghz_protocol_doitrand, &subghz_protocol_phoenix_v2, &subghz_protocol_honeywell_wdb, &subghz_protocol_magellan, &subghz_protocol_intertechno_v3, - &subghz_protocol_clemsa, &subghz_protocol_ansonic, + &subghz_protocol_clemsa, &subghz_protocol_ansonic, &subghz_protocol_smc5326, }; const SubGhzProtocolRegistry subghz_protocol_registry = { diff --git a/lib/subghz/protocols/protocol_items.h b/lib/subghz/protocols/protocol_items.h index 114dc504677..9f446739486 100644 --- a/lib/subghz/protocols/protocol_items.h +++ b/lib/subghz/protocols/protocol_items.h @@ -36,5 +36,6 @@ #include "intertechno_v3.h" #include "clemsa.h" #include "ansonic.h" +#include "smc5326.h" extern const SubGhzProtocolRegistry subghz_protocol_registry; diff --git a/lib/subghz/protocols/smc5326.c b/lib/subghz/protocols/smc5326.c new file mode 100644 index 00000000000..889e39f05da --- /dev/null +++ b/lib/subghz/protocols/smc5326.c @@ -0,0 +1,387 @@ +#include "smc5326.h" + +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +/* + * Help + * https://datasheetspdf.com/pdf-file/532079/Aslic/AX5326-4/1 + * + */ + +#define TAG "SubGhzProtocolSMC5326" + +#define DIP_P 0b11 //(+) +#define DIP_O 0b10 //(0) +#define DIP_N 0b00 //(-) + +#define DIP_PATTERN "%c%c%c%c%c%c%c%c" +#define SHOW_DIP_P(dip, check_dip) \ + ((((dip >> 0xE) & 0x3) == check_dip) ? '*' : '_'), \ + ((((dip >> 0xC) & 0x3) == check_dip) ? '*' : '_'), \ + ((((dip >> 0xA) & 0x3) == check_dip) ? '*' : '_'), \ + ((((dip >> 0x8) & 0x3) == check_dip) ? '*' : '_'), \ + ((((dip >> 0x6) & 0x3) == check_dip) ? '*' : '_'), \ + ((((dip >> 0x4) & 0x3) == check_dip) ? '*' : '_'), \ + ((((dip >> 0x2) & 0x3) == check_dip) ? '*' : '_'), \ + ((((dip >> 0x0) & 0x3) == check_dip) ? '*' : '_') + +static const SubGhzBlockConst subghz_protocol_smc5326_const = { + .te_short = 300, + .te_long = 900, + .te_delta = 200, + .min_count_bit_for_found = 25, +}; + +struct SubGhzProtocolDecoderSMC5326 { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; + + uint32_t te; + uint32_t last_data; +}; + +struct SubGhzProtocolEncoderSMC5326 { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; + + uint32_t te; +}; + +typedef enum { + SMC5326DecoderStepReset = 0, + SMC5326DecoderStepSaveDuration, + SMC5326DecoderStepCheckDuration, +} SMC5326DecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_smc5326_decoder = { + .alloc = subghz_protocol_decoder_smc5326_alloc, + .free = subghz_protocol_decoder_smc5326_free, + + .feed = subghz_protocol_decoder_smc5326_feed, + .reset = subghz_protocol_decoder_smc5326_reset, + + .get_hash_data = subghz_protocol_decoder_smc5326_get_hash_data, + .serialize = subghz_protocol_decoder_smc5326_serialize, + .deserialize = subghz_protocol_decoder_smc5326_deserialize, + .get_string = subghz_protocol_decoder_smc5326_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_smc5326_encoder = { + .alloc = subghz_protocol_encoder_smc5326_alloc, + .free = subghz_protocol_encoder_smc5326_free, + + .deserialize = subghz_protocol_encoder_smc5326_deserialize, + .stop = subghz_protocol_encoder_smc5326_stop, + .yield = subghz_protocol_encoder_smc5326_yield, +}; + +const SubGhzProtocol subghz_protocol_smc5326 = { + .name = SUBGHZ_PROTOCOL_SMC5326_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_868 | SubGhzProtocolFlag_315 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | + SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_smc5326_decoder, + .encoder = &subghz_protocol_smc5326_encoder, +}; + +void* subghz_protocol_encoder_smc5326_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderSMC5326* instance = malloc(sizeof(SubGhzProtocolEncoderSMC5326)); + + instance->base.protocol = &subghz_protocol_smc5326; + 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_running = false; + return instance; +} + +void subghz_protocol_encoder_smc5326_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderSMC5326* instance = context; + free(instance->encoder.upload); + free(instance); +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderSMC5326 instance + * @return true On success + */ +static bool subghz_protocol_encoder_smc5326_get_upload(SubGhzProtocolEncoderSMC5326* instance) { + furi_assert(instance); + + size_t index = 0; + size_t size_upload = (instance->generic.data_count_bit * 2) + 2; + if(size_upload > instance->encoder.size_upload) { + FURI_LOG_E(TAG, "Size upload exceeds allocated encoder buffer."); + return false; + } else { + instance->encoder.size_upload = size_upload; + } + + //Send key data + for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) { + if(bit_read(instance->generic.data, i - 1)) { + //send bit 1 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)instance->te * 3); + instance->encoder.upload[index++] = level_duration_make(false, (uint32_t)instance->te); + } else { + //send bit 0 + instance->encoder.upload[index++] = level_duration_make(true, (uint32_t)instance->te); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)instance->te * 3); + } + } + + //Send Stop bit + instance->encoder.upload[index++] = level_duration_make(true, (uint32_t)instance->te); + //Send PT_GUARD + instance->encoder.upload[index++] = level_duration_make(false, (uint32_t)instance->te * 25); + + return true; +} + +bool subghz_protocol_encoder_smc5326_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderSMC5326* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + if(!flipper_format_read_uint32(flipper_format, "TE", (uint32_t*)&instance->te, 1)) { + FURI_LOG_E(TAG, "Missing TE"); + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_smc5326_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + if(!subghz_protocol_encoder_smc5326_get_upload(instance)) break; + instance->encoder.is_running = true; + + res = true; + } while(false); + + return res; +} + +void subghz_protocol_encoder_smc5326_stop(void* context) { + SubGhzProtocolEncoderSMC5326* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_smc5326_yield(void* context) { + SubGhzProtocolEncoderSMC5326* instance = context; + + if(instance->encoder.repeat == 0 || !instance->encoder.is_running) { + instance->encoder.is_running = 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_smc5326_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderSMC5326* instance = malloc(sizeof(SubGhzProtocolDecoderSMC5326)); + instance->base.protocol = &subghz_protocol_smc5326; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_smc5326_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderSMC5326* instance = context; + free(instance); +} + +void subghz_protocol_decoder_smc5326_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderSMC5326* instance = context; + instance->decoder.parser_step = SMC5326DecoderStepReset; + instance->last_data = 0; +} + +void subghz_protocol_decoder_smc5326_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderSMC5326* instance = context; + + switch(instance->decoder.parser_step) { + case SMC5326DecoderStepReset: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_smc5326_const.te_short * 24) < + subghz_protocol_smc5326_const.te_delta * 12)) { + //Found Preambula + instance->decoder.parser_step = SMC5326DecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->te = 0; + } + break; + case SMC5326DecoderStepSaveDuration: + //save duration + if(level) { + instance->decoder.te_last = duration; + instance->te += duration; + instance->decoder.parser_step = SMC5326DecoderStepCheckDuration; + } + break; + case SMC5326DecoderStepCheckDuration: + if(!level) { + if(duration >= ((uint32_t)subghz_protocol_smc5326_const.te_long * 2)) { + instance->decoder.parser_step = SMC5326DecoderStepSaveDuration; + if(instance->decoder.decode_count_bit == + subghz_protocol_smc5326_const.min_count_bit_for_found) { + if((instance->last_data == instance->decoder.decode_data) && + instance->last_data) { + instance->te /= (instance->decoder.decode_count_bit * 4 + 1); + + 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->last_data = instance->decoder.decode_data; + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->te = 0; + break; + } + + instance->te += duration; + + if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_smc5326_const.te_short) < + subghz_protocol_smc5326_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_smc5326_const.te_long) < + subghz_protocol_smc5326_const.te_delta * 3)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = SMC5326DecoderStepSaveDuration; + } else if( + (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_smc5326_const.te_long) < + subghz_protocol_smc5326_const.te_delta * 3) && + (DURATION_DIFF(duration, subghz_protocol_smc5326_const.te_short) < + subghz_protocol_smc5326_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = SMC5326DecoderStepSaveDuration; + } else { + instance->decoder.parser_step = SMC5326DecoderStepReset; + } + } else { + instance->decoder.parser_step = SMC5326DecoderStepReset; + } + break; + } +} + +uint8_t subghz_protocol_decoder_smc5326_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderSMC5326* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_smc5326_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderSMC5326* instance = context; + bool res = subghz_block_generic_serialize(&instance->generic, flipper_format, preset); + if(res && !flipper_format_write_uint32(flipper_format, "TE", &instance->te, 1)) { + FURI_LOG_E(TAG, "Unable to add TE"); + res = false; + } + return res; +} + +bool subghz_protocol_decoder_smc5326_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderSMC5326* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_smc5326_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + if(!flipper_format_read_uint32(flipper_format, "TE", (uint32_t*)&instance->te, 1)) { + FURI_LOG_E(TAG, "Missing TE"); + break; + } + res = true; + } while(false); + + return res; +} + +static void subghz_protocol_smc5326_get_event_serialize(uint8_t event, FuriString* output) { + furi_string_cat_printf( + output, + "%s%s%s%s\r\n", + (((event >> 6) & 0x3) == 0x3 ? "B1 " : ""), + (((event >> 4) & 0x3) == 0x3 ? "B2 " : ""), + (((event >> 2) & 0x3) == 0x3 ? "B3 " : ""), + (((event >> 0) & 0x3) == 0x3 ? "B4 " : "")); +} + +void subghz_protocol_decoder_smc5326_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderSMC5326* instance = context; + uint32_t data = (uint32_t)((instance->generic.data >> 9) & 0xFFFF); + + furi_string_cat_printf( + output, + "%s %dbit\r\n" + "Key:%07lX Te:%ldus\r\n" + " +: " DIP_PATTERN "\r\n" + " o: " DIP_PATTERN " ", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data & 0x1FFFFFF), + instance->te, + SHOW_DIP_P(data, DIP_P), + SHOW_DIP_P(data, DIP_O)); + subghz_protocol_smc5326_get_event_serialize(instance->generic.data >> 1, output); + furi_string_cat_printf(output, " -: " DIP_PATTERN "\r\n", SHOW_DIP_P(data, DIP_N)); +} diff --git a/lib/subghz/protocols/smc5326.h b/lib/subghz/protocols/smc5326.h new file mode 100644 index 00000000000..ddc954bd57b --- /dev/null +++ b/lib/subghz/protocols/smc5326.h @@ -0,0 +1,107 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_SMC5326_NAME "SMC5326" + +typedef struct SubGhzProtocolDecoderSMC5326 SubGhzProtocolDecoderSMC5326; +typedef struct SubGhzProtocolEncoderSMC5326 SubGhzProtocolEncoderSMC5326; + +extern const SubGhzProtocolDecoder subghz_protocol_smc5326_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_smc5326_encoder; +extern const SubGhzProtocol subghz_protocol_smc5326; + +/** + * Allocate SubGhzProtocolEncoderSMC5326. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderSMC5326* pointer to a SubGhzProtocolEncoderSMC5326 instance + */ +void* subghz_protocol_encoder_smc5326_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderSMC5326. + * @param context Pointer to a SubGhzProtocolEncoderSMC5326 instance + */ +void subghz_protocol_encoder_smc5326_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderSMC5326 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_encoder_smc5326_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderSMC5326 instance + */ +void subghz_protocol_encoder_smc5326_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderSMC5326 instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_smc5326_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderSMC5326. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderSMC5326* pointer to a SubGhzProtocolDecoderSMC5326 instance + */ +void* subghz_protocol_decoder_smc5326_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderSMC5326. + * @param context Pointer to a SubGhzProtocolDecoderSMC5326 instance + */ +void subghz_protocol_decoder_smc5326_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderSMC5326. + * @param context Pointer to a SubGhzProtocolDecoderSMC5326 instance + */ +void subghz_protocol_decoder_smc5326_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderSMC5326 instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_smc5326_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderSMC5326 instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_smc5326_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderSMC5326. + * @param context Pointer to a SubGhzProtocolDecoderSMC5326 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_decoder_smc5326_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderSMC5326. + * @param context Pointer to a SubGhzProtocolDecoderSMC5326 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_decoder_smc5326_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderSMC5326 instance + * @param output Resulting text + */ +void subghz_protocol_decoder_smc5326_get_string(void* context, FuriString* output); From 3681a5478c864ffd1cae664af7c05e62631c9a3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Fri, 16 Dec 2022 08:04:15 +0900 Subject: [PATCH 284/824] [FL-3044] Dolphin: add new animation --- .../L1_Happy_holidays_128x64/frame_0.png | Bin 0 -> 1787 bytes .../L1_Happy_holidays_128x64/frame_1.png | Bin 0 -> 1795 bytes .../L1_Happy_holidays_128x64/frame_10.png | Bin 0 -> 1791 bytes .../L1_Happy_holidays_128x64/frame_11.png | Bin 0 -> 1814 bytes .../L1_Happy_holidays_128x64/frame_12.png | Bin 0 -> 1779 bytes .../L1_Happy_holidays_128x64/frame_2.png | Bin 0 -> 1774 bytes .../L1_Happy_holidays_128x64/frame_3.png | Bin 0 -> 1801 bytes .../L1_Happy_holidays_128x64/frame_4.png | Bin 0 -> 1799 bytes .../L1_Happy_holidays_128x64/frame_5.png | Bin 0 -> 1797 bytes .../L1_Happy_holidays_128x64/frame_6.png | Bin 0 -> 1789 bytes .../L1_Happy_holidays_128x64/frame_7.png | Bin 0 -> 1804 bytes .../L1_Happy_holidays_128x64/frame_8.png | Bin 0 -> 1809 bytes .../L1_Happy_holidays_128x64/frame_9.png | Bin 0 -> 1816 bytes .../L1_Happy_holidays_128x64/meta.txt | 23 ++++++++++++++++++ assets/dolphin/external/manifest.txt | 13 +++++++--- 15 files changed, 33 insertions(+), 3 deletions(-) create mode 100644 assets/dolphin/external/L1_Happy_holidays_128x64/frame_0.png create mode 100644 assets/dolphin/external/L1_Happy_holidays_128x64/frame_1.png create mode 100644 assets/dolphin/external/L1_Happy_holidays_128x64/frame_10.png create mode 100644 assets/dolphin/external/L1_Happy_holidays_128x64/frame_11.png create mode 100644 assets/dolphin/external/L1_Happy_holidays_128x64/frame_12.png create mode 100644 assets/dolphin/external/L1_Happy_holidays_128x64/frame_2.png create mode 100644 assets/dolphin/external/L1_Happy_holidays_128x64/frame_3.png create mode 100644 assets/dolphin/external/L1_Happy_holidays_128x64/frame_4.png create mode 100644 assets/dolphin/external/L1_Happy_holidays_128x64/frame_5.png create mode 100644 assets/dolphin/external/L1_Happy_holidays_128x64/frame_6.png create mode 100644 assets/dolphin/external/L1_Happy_holidays_128x64/frame_7.png create mode 100644 assets/dolphin/external/L1_Happy_holidays_128x64/frame_8.png create mode 100644 assets/dolphin/external/L1_Happy_holidays_128x64/frame_9.png create mode 100644 assets/dolphin/external/L1_Happy_holidays_128x64/meta.txt diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_0.png b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_0.png new file mode 100644 index 0000000000000000000000000000000000000000..909cc330392b999e38b0326b5381344c952380b8 GIT binary patch literal 1787 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_10.png b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_10.png new file mode 100644 index 0000000000000000000000000000000000000000..3a43c6b844541f9be3cadc2aa78864e9147919c6 GIT binary patch literal 1791 zcmVnvyf3eBg-+&c%%cGpOsw&ZJ*b5*$z;l?5Yi0jy7XFZ9XH9ar)E7M=0e0a+Ci( z*H%Q2(@-5?W+Nw@07V4tJf0gn{`g#T0?YV)Y^APuj|?se z%wo3_XlwL;BVyKdU19uBz)K3K-u#Yqz>+ZSnT!yhp3gOSd--#KNP{F|$U6{2D@90T zb^8>D(e!+21hHjLCR{RkI08$a|KbQkCoyWq)1?u(OR=_A+r`sG&SQhH~mDq%3O>Yy?=Ilw#UdV2kEcJ z|1^18l+nhqE#sYidTXyypvp&LSs93c8Z^vU`HW33+N~ewYdpf5TLtFEvk_5(A@*Ax zU_8V~)uEMY?0V=K(+ynN%V6bMPFsO;2k?kg#tA&e?aA;C%St~hk#!~+rDh$W+ySiH zYE=)O6KKYX8jLwCuVs9<&k3p=A@pfrl$6Nv5u(+J%or`?YfqjRDY7JM7L}<)b~B?$ zeHJ9wP4(ivowkk6{ul2ybEWQm(Or>;k2p2`8(5al&q zytyY}m73^%IlsCRkUB)(mtfk{BVHna)kHx+<38a7k_;lwi!K9dUyt)MH=B)0bQLg` zaoT%*(X1jTfeaL#!yxl{N{01^8bNILE7L8zqZ4GKlSUrLTWVDvRdxGJ`8Vp=Uq41+ z+5(<12}`l46L=jtZ;n?9>~Kir5S5NFBkN%u#F%%ydYkQ4drt6)oWaZQ5>Y`OJ`Mn=e{w+ykX)}gf)|3=W?M5VVMTKHz1U>5SSNYaeXh&e;lruO8> z=c8_E2VfQV=CjZ?V9BvI>!mD|v$e6Bj;LCaGFU`pF-}nC02N@!COiK0SjGV~c?eAU z_2y_}`?HsC1@C|t$}BD+390pgk2TifDM+q2R@(#8Ym~#nBj6sa>Z#8^@`Vt2h+em> zk74AW9y_9HUr2srUG40*{y+pGji|xl9g;V;-(1PMQD{YLL=Yu+Rbchq{tF>qpP)7F zv*HjVa-%#ZvsTp}Wo1JBT>#tYVaG8F&eXP++>RA;vz=2-hQ>WsaWt0$j7p)- zpj3WFX|{gW=2>9s=#1P5G$+u~*NmeIijt zavZC>sNqDDO@q{vasVcj&xCt#E;7Enxd>zAis&|j*;JSXb7rlzc0C8pR@J1IBHEhr zJx%FM5b(1ifW2JSLpy=7s*me8ViMIt;}IDYy9Zk$huWcpK9WX%1ZTX z;c|jB{%oZ1Zi6I56$Z4{V-MT96fALLQ`GKM@#X-Uf!W&~!OGHyG4Kq5Wudj7EnQBL z#3KfsGTv0Ro-b4V~63n$)d(UL0@Fx)i~~L9UKs%_C951OJANj` zX^kRFmgYPwdM)05-u^BCEk2_NLItn51ES8XMTtoH>|x{~*7-iP2?)ru**u4{!{KzqK5I3i?oWoFz;UKB+%t#*AMY#DSS0<6A? zOam&Sd!zwX|23?V1N7HYc9otUqD~w!tn95s)`_fzWdBtGmREX)l8cPv zR&oHOr}r$P6JqKXSi{JQDiRr@%`R)s%MV0U8mb~cqEgc13Dq9FFv!TPESbD9cQJg&%8T&wSwN;2(4m!TUKxmF hpSXqJX8hJ#`vpJ+v^_(~quc-h002ovPDHLkV1n0aQh)#e literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_11.png b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_11.png new file mode 100644 index 0000000000000000000000000000000000000000..20ea12eb06d4e6d10d9dcea9f1d2c69277765e1a GIT binary patch literal 1814 zcmV+x2kH2UP)F$kQo{QqB`eacqE2AWGUy4zKj8gn6mK!ct= z=W!g@@y~Ew_Lk4{+$72Uc-$?(Zd}*(uN3UzJpPdfm~Z}ho~9?*X(XOU>cYI|vA-uX zkQMNbn`J50Q?l&C^tWWN#%Jg50#g2QI0jG^c;$wXu~n3Z#xwIVMt`^REwplgPu=7y zba;=up&CGTGtvd`fVPfC_%vnxRdT4g|KlJzKos-A@31*0d|A~mV}`;=^?&7o+EgB`89yZh9k=Sgo9)aF>9pX zVleO`9HJ1!=FQPf%Ujj~8Sk~W(Jt3UGO_z&Gk~gbFh=PWv@`;E90?Mbjg@0u=WRhe zBSP?yK`?DQ5VzC%6O}}z4_2Qch0!k)!!a6GFXOe zfsuWy0n7?O!Z8&+?*P0o!;nO2oU1qhJ9pG*BltCdt_dx7DsMA;yCRHx%)27n_=F0f zG}5X}?HoWOAzJppq@Ck=eyz1>pR;R7*}9FX3mL+P5Hf)1VisAOf!gKaqt{w~n@Lr7 zD!DrquJDw{m?VPeVx4IpyPkaZvWai0=4r?p!5az8RKi$f*(?i`DOgo7OFm|;IbUB@ z@yoJ)b{xzCsz$(A<4%v@fhi`ff~T;kLK(6=)}jkftY>kaOPr6BaaYSS1qE@Bs-~kLObpTj=yRPeE z+_<+ntu$-(`72vk>H2Y-U5> zsALN)$@7X=-7>KAzx^U;Oy~{-xpoA9EgV=i2n~_5tAvFp8=etV>JGd6A_HJc?Y5u zMkFDX&t6{{gPD1~zGx4~qs)&?K_}8~0eQ1qeh?xL(IZl$4>SMWeP?JjkuhgSK}l=( zLe#)k4`9VctH9wMl-GAI|7tOvkv#$?<=t75){y#Da0Omv@i@4zkLNOU=OJe1^*Dis zNLZ-sQS-Lfb#Wnpy*1g}HNzrMvY%Yjn-|Gx^<(DuhiBGgFn<&(4?`m`=;n7C0Yc@FDz`C<$a(Bc#VHb~ zvGxk7Vt|aGa_cS3u|G0zl!JT%@tZ;akti$wt=x2Wtd`cWeIxUFDd|{St1d>@-Ff4s zoKHpoEACf~fN_n86v~6IDxK-ON>Sb6Ml)(;tWVh~GO285Zxiw)=nyoav$rmb&f<+W z_exlDKP_cw?GlmCxYK05mJ_c7qX8SDlwxV}){Lri3!5yP^KW5X5-r*Odc9?tbU(=y2s0 zBB|u+eS6u;n*mrJz^Vjh1)JAEuhIMH{7BwPDD%iN!&6og%Q63~FHM#Qa#IMM9AFkw zYmdfQ2}VX)++s0v6HPwvhF6Tw3L{#!q-Fu5qsQF4J-{sF>S`i`4x>h4)5?0}xNJSf z0@B+o(?d2`i=)>as488%$-7xM%AT~xal9(t^Q@JNXN9DAHZYBGB5X1Dya~Z-=lcSuA?DySWym8!P?~1d2T=P8;!m}nhrs@GKcm`v&q4C~2S{{wS?p0<~k#mZiCzS?vD+n1x zWe(ZqrJ+_^cApXBVO%r%obd*ox^g{07noZku|k$;_{g~Cxj$10^u~6b0}7wZJOEle z^4SXFXQi!3o{lrAdK>v#o0I8-u*CyZvw)T8;0fKxcqxYeAiVvNsEn&-1Vm$5QiaB3 zz^wCW`TT0Ud>7zt-1;0<4ZSqbnmg;#D!5bmcU4M%0Cc+*>zlWu!2kdN07*qoM6N<$ Ef-Tf;h5!Hn literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_12.png b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_12.png new file mode 100644 index 0000000000000000000000000000000000000000..a05abd80f1bbcf8c4c31985df242787246776d05 GIT binary patch literal 1779 zcmV0r&z%C3U6&)&Q402Ruv+OXzmGsctVBk~ZZKW%&qr8NNGen&b$i!kXm9wELxpG)xe@^*j-K@u_K6AD8uMQ~(w z`xb|h^t=&4Xxozsmq;FtK$FL706JcC#tfH$b^uxD@SG8qsp)wmf+7lLbEQ-hMA!D@ zY587rTFD_iW3Pnfyxv#|sJ^Kpu*zty>9-JB3oSGy<0ChHSGvkv22WR1_n+Ol%d5z( zjr87cUy?j2%1Gl-%6M1jGP_Pz#*udeJtyE}sllcBk`o{|nvRnk&C_ibD^g2T-ZCCQ z0^M+=>yTPCbUo`egbiH9WoQvkL#9qcQ0@R;A(e3gui~C9h@!@rMaGe=E=rPzo^81U zXt!1C9y~=Q<3tUHL=TTLu5ylS6tP;^?Af*!bbx3W=HsnG3-xfRde`K6UBYfssxpO0 zyhjG9A~3qBR8*}55+QaJc{?!ML3}vC3Obn1S?LV)FzELctsGBv-kx@mNl}O?09Do_ zUc9*{prs7!IiLSTLuPSvfK1Edktp99S}jDJz@yHq+Q2MD4;O(_QAM9=N`8YAX!=EJ zi0qotf_k8WIqaQ$C{#17#=4>7>0(bp_5h{g(Q3ss8JRcJBt0?&(Jkd&*L6wk7+)Yt zz#uH!Z-I420}>Hd0Vy)0Z+i9K+e0+HN<4a%%e0<<8%{ela;BCIO&-AWY|cg@ z2K+`NTX;fxn>o|)CxYG+CqvrlL#sw44Xi>m4cv;rC}#6n^yYXQ^2YMqhsgED@^RK# zaCS0!*(9Lczh$ebH-oBLk;obr&FhV|YL$#!1e(Dk9fS2O`^M6HzwLht*p-{nTPAfO z<9B|mk+GT%mRxTv)!AETC#;%{N3kOx2;quYksuN>pD>ord1ArK_1gN8Fe?{nH=qBk z$+gCgc6NMuW4$#_0!=^YxUCVOHH;@fl(%wr1u}Uac~(_7MdeUED0>P0T>wh-pyMn^ zxxE5b1z#Z-rN(Dc12tR~+!UY{Sk|~%<4J`bI!^;n0abFNb7+)9qg`t`$3dc`nF9D> zX=I0%w^#54*&K~Jl~s#Gp`Agdtz1=u^vjTp^v&AyF=mz((+C{8uGRKP@Wy(qUepOI zoGF5cLqSEL70%EFrMa0_4AE=0fb-JaSle?>KDMGpiU6s5AO}EhYejKmJ?G#Dk9vJ1 z@1k-M@RYVX>`Q4!bat8oR<%?dmS^&3(hbW*9!V}b-%CMUR}IniGC(hrngX+$EBgJg}b$+bY+FHgTLP7zJw$HJ}fQPH1!1>`tv-hF8EqlmDK)|y<;mGf8O z!vQoekAyk|)eM;?YefMY1&tu9jc#2~>GdNKfF3?V&P3O%$hB2YPK(qi`7^t=_4@!+ z1JI(WDgs!!|5RS3Iw3kBtf)74W%x(-@bRHgWK*l0fWD6CWXVMJo7s=l&1ATN6X{^! zXAB(QwOmzQ15hQc-z#KB##il)UYICL>dB3y5pRv3Arx6y`nduptKdp#T!k0qP=oSf z($fJ+Gar8&=o!Oe(@-bLKJ#>{0xHkrG*Ds)Bo}K zv5Udv$mlwtkxSC$sxZDb|HT(T3o|;;>Kd%YpNiEEWf@c|AZIRu=RE2sg190cKOnun zqtfNbtc4nU6%8z*au1ecywEo2LBG^ zCriHd9ChMIAzHr>)FmU?^ z5tW9L9AZWWf6Z${t=DE#64KrwYe+3MGPlZBy>~wXRKfG`N<+=)p^cB|fjVo?8It25(No$3 zV$Y9%5|s(n9=uzC$gIkF$e1S(SCs{)h1+`p87^ReGgn?Gz&Ez?7NU5H@dtK9z<;d< Vp()s7p}_zE002ovPDHLkV1hccKn4H+ literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_2.png b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_2.png new file mode 100644 index 0000000000000000000000000000000000000000..fc7e0364afbfa0c0750a59c1d546dc9211c42161 GIT binary patch literal 1774 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_4.png b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_4.png new file mode 100644 index 0000000000000000000000000000000000000000..bcc7f28a9eb40eda4f9d472409a93fab7f0d98ef GIT binary patch literal 1799 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_6.png b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_6.png new file mode 100644 index 0000000000000000000000000000000000000000..c0b1a4fe24af639bddda8ca7f69d2bee8aed8689 GIT binary patch literal 1789 zcmVgl@z3GiqHRtsLHW&}w z$K$%L*Y(Trdf_EM9*<3yoX5{;19s!}di^Q~M|fPnCh;4e0%<>@HZ&zpfwvW({7sU@O>WoM*ky$YX;3wDS>KIlx^P zxe6WG<22j=s4hmQ;2rSvc!bY$*8fTlz4U)wBnQYsZv2ju;RW66^)iFNbN-myY46=L zgBbuF2Ja36OV9Wk5wl_I3g>?Vmi7U5Tn8)(v&M9W`1bqULbR5D0gzd6#+i56NH!3w zLHaELV;+P{HGIlE|m$~vI$dv9!vYu%Pj?7G+rU?w=&fQ%Yi9s#_r0tu|%mE*n6 z(_-|tR@u7@`cz`i(JB91`l0F&M==j*xADc%8jKr(wg=1We)+PtI; zM9`;pjA?kO+fDt!$)p!L!f1Q)dEumY`;ANHu~yl9cI^&0Gn)QFWr%r9@?J zC&8TQ16Y1|N?{!ad4^?!05MwT#G?KHl2Nhk1enGcgJ;x!WfgR!q&sqk2$hn!yAOc6 zuaQpJ2#TVtauUAL$q0A2-ezq0_=%t)FnrOdwNr|3kVZQZ^&OCt*mgMj9*gza)A4o{ zUc~^=3M;6VHCk`9SmYceH=1kd^qdvd{A>_tRLXJ~0J~Fk6a+JUhda#G?{yv+ritXu zA9J?<51jNN+9Y{liRgMpRJj{HD~-bA!9nR@rc+&hw5 zZ$GWQ-Jzd&Jy}{l*tuvBq*@@<{b5Wlpsz{Iy#B(x0dl?3RjB-SRydL?i z!EC^F&DHCTGfPvME2;f+U8mE9C6pS|-l&YvY{hyY7lES{vtyKL&h2MI*wi~d|i z^u8u6GtHeb-y%R6=nQ1pCJkX)m*}v;;zOA}g7t5;4aYKo)`Q68Z2IfIgR^AW%Md$YU4PQX zAAJfVw%> z_YZpL3GZG=2hoW>-vOsfKeL3ruJ~sHbqREG5lDYEOR?8m<~h>8q6x%wZ6(H>k1GHX zFnt|Fcb`6u*0thYKa3s^m5t4E0FR?Ea>hHiX#~j=BiVj*@=-6myL4QMQ`ZL52qNYlB>4-2RpLIZ{Qet5>g0gF=l+D8@Pp?L`0z!}JlkSaI>)>d^XAwDU z#^x*{#*S=^o}*_UB(MWI16T%1!?gAO?GHq(Dod)e&AJs_3+z~RogL*{HI?hRUA_u4 zKo^)(qp`x4?DestX1Cy-((bIOqnfmwyPBhEjejXrNIC}1-DpUp{*(>it f52u`evP=30f`%HTZ&_~;00000NkvXXu0mjfZU|{H literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_7.png b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_7.png new file mode 100644 index 0000000000000000000000000000000000000000..19c8181d0369a8edf52ef2124d403f5472c8e280 GIT binary patch literal 1804 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_9.png b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_9.png new file mode 100644 index 0000000000000000000000000000000000000000..929a207ef58a63e946d60e4413e5ad96a75d7c0a GIT binary patch literal 1816 zcmV+z2j}>SP)fcfU1=V^L^okrq$q%O>R9{YPT z16cv@xLKA`JtfONOn*xTYkYR@E+FL}hhqR$fmd!A8CykpXgo6?WAt|`-$E+~_|#3V zLWlRb8>#_hHzQr}4ruFWgilk}UnPf{`#%nn14J<&{EnNU8QpbV&H>04*8V+c=`X^PI)Dc$9Ae?JuZ*{v+nW!~bLWhkUH$1HBZFng z78u#L8o;aoBpg%G^A5lZGYm*!ARZFPr$5YMzFy5xkMWOeKs(md&z2nSxaXv*csen)CHl z6~8R&XUD-TplSq+HSY8X9++a%DtHQuDwHA1GagJ41ufw9Lw!F1l>$o!)@5KSep#~D zUI>!u+i07AXT8DQuoQ&eR}H&DQX?{i3PBo4id4xeAz?SNO4O>99Xx%CDJnrPb%#ID>}>*x=e)Jj(o&DdH)0SXcaiTgYx>$o*Ya@V0pjMUO7(-t^v$KS(QPp zMxKGRQct#dT(z&hR2(4Ca?Y51-BVb%3B8Bqct4e43u2NKYxY3Ln8S7JaicBio+1rFX2|5H#X!h251rZjV z#T#wzm9XSKujz@EetE2z^#b~_J?aurViZW`j3P2# zo3rvPwU5f!q|riW{SC zJQHK1huA>~E%`hn%$}haMzm~6ljGMPhoXa*M6BClwc=6~Rbtr9Ydiy!=`JJxID7U| zNIB5Bhb5$39KG&9mqBF|s*TAOwd8y2Qq5k%^Q@JNXBp#LA&5zrG>c`8B>Rj(CPY~- z907&PsB*N-$}5OfA@}tb0xSY1!+{mU<#R2pXyJ!hN5c+g9d*SsEQQYAAWFJ3MXFZ_ zkVb&gG^;*<+E=DptFYR5SX2xwg4c&ZQkl{vc@;v`-kmyn$gaa0roCFh>c@7GttJVkicB*#=efCbNBtTr^>TZgUiY3pip&s-~VPLcDZ(!g#7 zA%m#QA=|t()N0G_Gh#e!CD7tkU7^W>>jApJ+!~1$vP8p2#x>9VnM$BHw(A^F_*~`z z(BhG2D~O+!wjy~t&ZO#XSL4@b0p7-~=csDvrGeJmS(jG9oyxzfQu+f7>@m9|qN_##0000 Date: Sat, 17 Dec 2022 02:20:10 +0400 Subject: [PATCH 285/824] [FL-3040] Audio support for SubGhz (#2131) * Furi_hal_speaker: multiple resource usage * Furi_hal_speaker: fix multiple resource usage * Furi_hal_speaker: fix music_player_worker * Furi_hal_speaker: fix mutex release queue handling * SubGhz: add furi_hal_subghz_set_debug_pin * SubGhz: add sound SubGhz Read, SubGhz Read RAW * furi_hal_speaker: add __attribute__((warn_unused_result)) for furi_hal_speaker_acquire() * Furi_hal_speaker: fix review comments * SubGhz: cleanup naming and locking timings * SubGhz,FuriHal: fix speaker deinit logic and subghz speaker release sequence * FuriHal: crash on speaker acquire/release from IRQ * Furi, FuriHal: FURI_WARN_UNUSED and documentation update * Bump api symbols version: fix broken speaker Co-authored-by: Aleksandr Kutuzov --- .../main/subghz/helpers/subghz_types.h | 7 + .../subghz/scenes/subghz_scene_read_raw.c | 3 + .../scenes/subghz_scene_receiver_config.c | 29 ++++ applications/main/subghz/subghz.c | 1 + applications/main/subghz/subghz_i.c | 42 ++++++ applications/main/subghz/subghz_i.h | 5 + .../music_player/music_player_worker.c | 80 +++++------ .../services/notification/notification_app.c | 9 +- firmware/targets/f7/api_symbols.csv | 7 +- .../targets/f7/furi_hal/furi_hal_speaker.c | 51 ++++++- .../targets/f7/furi_hal/furi_hal_subghz.c | 127 +++++++++--------- .../furi_hal_include/furi_hal_speaker.h | 47 +++++++ .../furi_hal_include/furi_hal_subghz.h | 32 +++-- furi/core/common_defines.h | 4 + 14 files changed, 328 insertions(+), 116 deletions(-) diff --git a/applications/main/subghz/helpers/subghz_types.h b/applications/main/subghz/helpers/subghz_types.h index bd6451f248d..abf7053937d 100644 --- a/applications/main/subghz/helpers/subghz_types.h +++ b/applications/main/subghz/helpers/subghz_types.h @@ -28,6 +28,13 @@ typedef enum { SubGhzHopperStateRSSITimeOut, } SubGhzHopperState; +/** SubGhzSpeakerState state */ +typedef enum { + SubGhzSpeakerStateDisable, + SubGhzSpeakerStateShutdown, + SubGhzSpeakerStateEnable, +} SubGhzSpeakerState; + /** SubGhzRxKeyState state */ typedef enum { SubGhzRxKeyStateIDLE, diff --git a/applications/main/subghz/scenes/subghz_scene_read_raw.c b/applications/main/subghz/scenes/subghz_scene_read_raw.c index 2e5ba09667d..b270dd4821c 100644 --- a/applications/main/subghz/scenes/subghz_scene_read_raw.c +++ b/applications/main/subghz/scenes/subghz_scene_read_raw.c @@ -259,6 +259,7 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { case SubGhzCustomEventViewReadRAWSendStop: subghz->state_notifications = SubGhzNotificationStateIDLE; if(subghz->txrx->txrx_state == SubGhzTxRxStateTx) { + subghz_speaker_unmute(subghz); subghz_tx_stop(subghz); subghz_sleep(subghz); } @@ -376,10 +377,12 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { subghz_read_raw_add_data_rssi(subghz->subghz_read_raw, rssi, false); subghz_protocol_raw_save_to_file_pause( (SubGhzProtocolDecoderRAW*)subghz->txrx->decoder_result, true); + subghz_speaker_mute(subghz); } else { subghz_read_raw_add_data_rssi(subghz->subghz_read_raw, rssi, true); subghz_protocol_raw_save_to_file_pause( (SubGhzProtocolDecoderRAW*)subghz->txrx->decoder_result, false); + subghz_speaker_unmute(subghz); } } diff --git a/applications/main/subghz/scenes/subghz_scene_receiver_config.c b/applications/main/subghz/scenes/subghz_scene_receiver_config.c index fd42829b5a0..b49aac92292 100644 --- a/applications/main/subghz/scenes/subghz_scene_receiver_config.c +++ b/applications/main/subghz/scenes/subghz_scene_receiver_config.c @@ -5,6 +5,7 @@ enum SubGhzSettingIndex { SubGhzSettingIndexFrequency, SubGhzSettingIndexHopping, SubGhzSettingIndexModulation, + SubGhzSettingIndexSound, SubGhzSettingIndexLock, SubGhzSettingIndexRAWThesholdRSSI, }; @@ -48,6 +49,16 @@ const uint32_t hopping_value[HOPPING_COUNT] = { SubGhzHopperStateRunnig, }; +#define SPEAKER_COUNT 2 +const char* const speaker_text[SPEAKER_COUNT] = { + "OFF", + "ON", +}; +const uint32_t speaker_value[SPEAKER_COUNT] = { + SubGhzSpeakerStateShutdown, + SubGhzSpeakerStateEnable, +}; + uint8_t subghz_scene_receiver_config_next_frequency(const uint32_t value, void* context) { furi_assert(context); SubGhz* subghz = context; @@ -167,6 +178,14 @@ static void subghz_scene_receiver_config_set_hopping_running(VariableItem* item) subghz->txrx->hopper_state = hopping_value[index]; } +static void subghz_scene_receiver_config_set_speaker(VariableItem* item) { + SubGhz* subghz = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + variable_item_set_current_value_text(item, speaker_text[index]); + subghz->txrx->speaker_state = speaker_value[index]; +} + static void subghz_scene_receiver_config_set_raw_threshold_rssi(VariableItem* item) { SubGhz* subghz = variable_item_get_context(item); uint8_t index = variable_item_get_current_value_index(item); @@ -235,6 +254,16 @@ void subghz_scene_receiver_config_on_enter(void* context) { variable_item_set_current_value_text( item, subghz_setting_get_preset_name(subghz->setting, value_index)); + item = variable_item_list_add( + subghz->variable_item_list, + "Sound:", + SPEAKER_COUNT, + subghz_scene_receiver_config_set_speaker, + subghz); + value_index = value_index_uint32(subghz->txrx->speaker_state, speaker_value, SPEAKER_COUNT); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, speaker_text[value_index]); + if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAW) != SubGhzCustomEventManagerSet) { variable_item_list_add(subghz->variable_item_list, "Lock Keyboard", 1, NULL, NULL); diff --git a/applications/main/subghz/subghz.c b/applications/main/subghz/subghz.c index df5a76525d7..b7564ab57e6 100644 --- a/applications/main/subghz/subghz.c +++ b/applications/main/subghz/subghz.c @@ -177,6 +177,7 @@ SubGhz* subghz_alloc() { subghz->txrx->txrx_state = SubGhzTxRxStateSleep; subghz->txrx->hopper_state = SubGhzHopperStateOFF; + subghz->txrx->speaker_state = SubGhzSpeakerStateDisable; subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE; subghz->txrx->raw_threshold_rssi = SUBGHZ_RAW_TRESHOLD_MIN; subghz->txrx->history = subghz_history_alloc(); diff --git a/applications/main/subghz/subghz_i.c b/applications/main/subghz/subghz_i.c index 736bcf360ab..0bcd700615a 100644 --- a/applications/main/subghz/subghz_i.c +++ b/applications/main/subghz/subghz_i.c @@ -86,6 +86,7 @@ uint32_t subghz_rx(SubGhz* subghz, uint32_t frequency) { uint32_t value = furi_hal_subghz_set_frequency_and_path(frequency); furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow); furi_hal_subghz_flush_rx(); + subghz_speaker_on(subghz); furi_hal_subghz_rx(); furi_hal_subghz_start_async_rx(subghz_worker_rx_callback, subghz->txrx->worker); @@ -104,6 +105,7 @@ static bool subghz_tx(SubGhz* subghz, uint32_t frequency) { furi_hal_subghz_set_frequency_and_path(frequency); furi_hal_gpio_write(&gpio_cc1101_g0, false); furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); + subghz_speaker_on(subghz); bool ret = furi_hal_subghz_tx(); subghz->txrx->txrx_state = SubGhzTxRxStateTx; return ret; @@ -119,11 +121,13 @@ void subghz_idle(SubGhz* subghz) { void subghz_rx_end(SubGhz* subghz) { furi_assert(subghz); furi_assert(subghz->txrx->txrx_state == SubGhzTxRxStateRx); + if(subghz_worker_is_running(subghz->txrx->worker)) { subghz_worker_stop(subghz->txrx->worker); furi_hal_subghz_stop_async_rx(); } furi_hal_subghz_idle(); + subghz_speaker_off(subghz); subghz->txrx->txrx_state = SubGhzTxRxStateIDLE; } @@ -212,6 +216,7 @@ void subghz_tx_stop(SubGhz* subghz) { subghz, subghz->txrx->fff_data, furi_string_get_cstr(subghz->file_path)); } subghz_idle(subghz); + subghz_speaker_off(subghz); notification_message(subghz->notifications, &sequence_reset_red); } @@ -585,3 +590,40 @@ void subghz_hopper_update(SubGhz* subghz) { subghz_rx(subghz, subghz->txrx->preset->frequency); } } + +void subghz_speaker_on(SubGhz* subghz) { + if(subghz->txrx->speaker_state == SubGhzSpeakerStateEnable) { + if(furi_hal_speaker_acquire(30)) { + furi_hal_subghz_set_async_mirror_pin(&gpio_speaker); + } else { + subghz->txrx->speaker_state = SubGhzSpeakerStateDisable; + } + } +} + +void subghz_speaker_off(SubGhz* subghz) { + if(subghz->txrx->speaker_state != SubGhzSpeakerStateDisable) { + if(furi_hal_speaker_is_mine()) { + furi_hal_subghz_set_async_mirror_pin(NULL); + furi_hal_speaker_release(); + if(subghz->txrx->speaker_state == SubGhzSpeakerStateShutdown) + subghz->txrx->speaker_state = SubGhzSpeakerStateDisable; + } + } +} + +void subghz_speaker_mute(SubGhz* subghz) { + if(subghz->txrx->speaker_state == SubGhzSpeakerStateEnable) { + if(furi_hal_speaker_is_mine()) { + furi_hal_subghz_set_async_mirror_pin(NULL); + } + } +} + +void subghz_speaker_unmute(SubGhz* subghz) { + if(subghz->txrx->speaker_state == SubGhzSpeakerStateEnable) { + if(furi_hal_speaker_is_mine()) { + furi_hal_subghz_set_async_mirror_pin(&gpio_speaker); + } + } +} diff --git a/applications/main/subghz/subghz_i.h b/applications/main/subghz/subghz_i.h index 234365155c2..cd33da447a6 100644 --- a/applications/main/subghz/subghz_i.h +++ b/applications/main/subghz/subghz_i.h @@ -53,6 +53,7 @@ struct SubGhzTxRx { uint16_t idx_menu_chosen; SubGhzTxRxState txrx_state; SubGhzHopperState hopper_state; + SubGhzSpeakerState speaker_state; uint8_t hopper_timeout; uint8_t hopper_idx_frequency; SubGhzRxKeyState rx_key_state; @@ -131,3 +132,7 @@ void subghz_file_name_clear(SubGhz* subghz); bool subghz_path_is_file(FuriString* path); uint32_t subghz_random_serial(void); void subghz_hopper_update(SubGhz* subghz); +void subghz_speaker_on(SubGhz* subghz); +void subghz_speaker_off(SubGhz* subghz); +void subghz_speaker_mute(SubGhz* subghz); +void subghz_speaker_unmute(SubGhz* subghz); diff --git a/applications/plugins/music_player/music_player_worker.c b/applications/plugins/music_player/music_player_worker.c index 0d683f4a6a6..60fd33a1753 100644 --- a/applications/plugins/music_player/music_player_worker.c +++ b/applications/plugins/music_player/music_player_worker.c @@ -47,47 +47,51 @@ static int32_t music_player_worker_thread_callback(void* context) { NoteBlockArray_it_t it; NoteBlockArray_it(it, instance->notes); - - while(instance->should_work) { - if(NoteBlockArray_end_p(it)) { - NoteBlockArray_it(it, instance->notes); - furi_delay_ms(10); - } else { - NoteBlock* note_block = NoteBlockArray_ref(it); - - float note_from_a4 = (float)note_block->semitone - NOTE_C4_SEMITONE; - float frequency = NOTE_C4 * powf(TWO_POW_TWELTH_ROOT, note_from_a4); - float duration = - 60.0 * furi_kernel_get_tick_frequency() * 4 / instance->bpm / note_block->duration; - uint32_t dots = note_block->dots; - while(dots > 0) { - duration += duration / 2; - dots--; - } - uint32_t next_tick = furi_get_tick() + duration; - float volume = instance->volume; - - if(instance->callback) { - instance->callback( - note_block->semitone, - note_block->dots, - note_block->duration, - 0.0, - instance->callback_context); - } - - furi_hal_speaker_stop(); - furi_hal_speaker_start(frequency, volume); - while(instance->should_work && furi_get_tick() < next_tick) { - volume *= 0.9945679; - furi_hal_speaker_set_volume(volume); - furi_delay_ms(2); + if(furi_hal_speaker_acquire(1000)) { + while(instance->should_work) { + if(NoteBlockArray_end_p(it)) { + NoteBlockArray_it(it, instance->notes); + furi_delay_ms(10); + } else { + NoteBlock* note_block = NoteBlockArray_ref(it); + + float note_from_a4 = (float)note_block->semitone - NOTE_C4_SEMITONE; + float frequency = NOTE_C4 * powf(TWO_POW_TWELTH_ROOT, note_from_a4); + float duration = 60.0 * furi_kernel_get_tick_frequency() * 4 / instance->bpm / + note_block->duration; + uint32_t dots = note_block->dots; + while(dots > 0) { + duration += duration / 2; + dots--; + } + uint32_t next_tick = furi_get_tick() + duration; + float volume = instance->volume; + + if(instance->callback) { + instance->callback( + note_block->semitone, + note_block->dots, + note_block->duration, + 0.0, + instance->callback_context); + } + + furi_hal_speaker_stop(); + furi_hal_speaker_start(frequency, volume); + while(instance->should_work && furi_get_tick() < next_tick) { + volume *= 0.9945679; + furi_hal_speaker_set_volume(volume); + furi_delay_ms(2); + } + NoteBlockArray_next(it); } - NoteBlockArray_next(it); } - } - furi_hal_speaker_stop(); + furi_hal_speaker_stop(); + furi_hal_speaker_release(); + } else { + FURI_LOG_E(TAG, "Speaker system is busy with another process."); + } return 0; } diff --git a/applications/services/notification/notification_app.c b/applications/services/notification/notification_app.c index 6091f0aa7f2..4f6d42aa0e5 100644 --- a/applications/services/notification/notification_app.c +++ b/applications/services/notification/notification_app.c @@ -150,11 +150,16 @@ void notification_vibro_off() { } void notification_sound_on(float freq, float volume) { - furi_hal_speaker_start(freq, volume); + if(furi_hal_speaker_acquire(30)) { + furi_hal_speaker_start(freq, volume); + } } void notification_sound_off() { - furi_hal_speaker_stop(); + if(furi_hal_speaker_is_mine()) { + furi_hal_speaker_stop(); + furi_hal_speaker_release(); + } } // display timer diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 80aaf7fa682..b3afb84271a 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,10.1,, +Version,+,11.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -1282,7 +1282,11 @@ Function,+,furi_hal_rtc_set_log_level,void,uint8_t Function,+,furi_hal_rtc_set_pin_fails,void,uint32_t Function,+,furi_hal_rtc_set_register,void,"FuriHalRtcRegister, uint32_t" Function,+,furi_hal_rtc_validate_datetime,_Bool,FuriHalRtcDateTime* +Function,+,furi_hal_speaker_acquire,_Bool,uint32_t +Function,-,furi_hal_speaker_deinit,void, Function,-,furi_hal_speaker_init,void, +Function,+,furi_hal_speaker_is_mine,_Bool, +Function,+,furi_hal_speaker_release,void, Function,+,furi_hal_speaker_set_volume,void,float Function,+,furi_hal_speaker_start,void,"float, float" Function,+,furi_hal_speaker_stop,void, @@ -1316,6 +1320,7 @@ Function,+,furi_hal_subghz_read_packet,void,"uint8_t*, uint8_t*" Function,+,furi_hal_subghz_reset,void, Function,+,furi_hal_subghz_rx,void, Function,+,furi_hal_subghz_rx_pipe_not_empty,_Bool, +Function,+,furi_hal_subghz_set_async_mirror_pin,void,const GpioPin* Function,+,furi_hal_subghz_set_frequency,uint32_t,uint32_t Function,+,furi_hal_subghz_set_frequency_and_path,uint32_t,uint32_t Function,+,furi_hal_subghz_set_path,void,FuriHalSubGhzPath diff --git a/firmware/targets/f7/furi_hal/furi_hal_speaker.c b/firmware/targets/f7/furi_hal/furi_hal_speaker.c index 03a7f094bba..c4a0bdd1e8e 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_speaker.c +++ b/firmware/targets/f7/furi_hal/furi_hal_speaker.c @@ -1,23 +1,66 @@ #include #include #include +#include #include +#include + +#define TAG "FuriHalSpeaker" #define FURI_HAL_SPEAKER_TIMER TIM16 #define FURI_HAL_SPEAKER_CHANNEL LL_TIM_CHANNEL_CH1 #define FURI_HAL_SPEAKER_PRESCALER 500 #define FURI_HAL_SPEAKER_MAX_VOLUME 60 +static FuriMutex* furi_hal_speaker_mutex = NULL; + // #define FURI_HAL_SPEAKER_NEW_VOLUME void furi_hal_speaker_init() { + furi_assert(furi_hal_speaker_mutex == NULL); + furi_hal_speaker_mutex = furi_mutex_alloc(FuriMutexTypeNormal); FURI_CRITICAL_ENTER(); LL_TIM_DeInit(FURI_HAL_SPEAKER_TIMER); FURI_CRITICAL_EXIT(); + FURI_LOG_I(TAG, "Init OK"); +} + +void furi_hal_speaker_deinit() { + furi_check(furi_hal_speaker_mutex != NULL); + LL_TIM_DeInit(FURI_HAL_SPEAKER_TIMER); + furi_hal_gpio_init(&gpio_speaker, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + furi_mutex_free(furi_hal_speaker_mutex); + furi_hal_speaker_mutex = NULL; +} - furi_hal_gpio_init_ex( - &gpio_speaker, GpioModeAltFunctionPushPull, GpioPullNo, GpioSpeedLow, GpioAltFn14TIM16); +bool furi_hal_speaker_acquire(uint32_t timeout) { + furi_check(!FURI_IS_IRQ_MODE()); + + if(furi_mutex_acquire(furi_hal_speaker_mutex, timeout) == FuriStatusOk) { + furi_hal_power_insomnia_enter(); + furi_hal_gpio_init_ex( + &gpio_speaker, GpioModeAltFunctionPushPull, GpioPullNo, GpioSpeedLow, GpioAltFn14TIM16); + return true; + } else { + return false; + } +} + +void furi_hal_speaker_release() { + furi_check(!FURI_IS_IRQ_MODE()); + furi_check(furi_hal_speaker_is_mine()); + + furi_hal_speaker_stop(); + furi_hal_gpio_init(&gpio_speaker, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + furi_hal_power_insomnia_exit(); + + furi_check(furi_mutex_release(furi_hal_speaker_mutex) == FuriStatusOk); +} + +bool furi_hal_speaker_is_mine() { + return (FURI_IS_IRQ_MODE()) || + (furi_mutex_get_owner(furi_hal_speaker_mutex) == furi_thread_get_current_id()); } static inline uint32_t furi_hal_speaker_calculate_autoreload(float frequency) { @@ -54,6 +97,8 @@ static inline uint32_t furi_hal_speaker_calculate_compare(float volume) { } void furi_hal_speaker_start(float frequency, float volume) { + furi_check(furi_hal_speaker_is_mine()); + if(volume <= 0) { furi_hal_speaker_stop(); return; @@ -75,6 +120,7 @@ void furi_hal_speaker_start(float frequency, float volume) { } void furi_hal_speaker_set_volume(float volume) { + furi_check(furi_hal_speaker_is_mine()); if(volume <= 0) { furi_hal_speaker_stop(); return; @@ -88,6 +134,7 @@ void furi_hal_speaker_set_volume(float volume) { } void furi_hal_speaker_stop() { + furi_check(furi_hal_speaker_is_mine()); LL_TIM_DisableAllOutputs(FURI_HAL_SPEAKER_TIMER); LL_TIM_DisableCounter(FURI_HAL_SPEAKER_TIMER); } diff --git a/firmware/targets/f7/furi_hal/furi_hal_subghz.c b/firmware/targets/f7/furi_hal/furi_hal_subghz.c index 726b2d7fa74..3441fd965a5 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_subghz.c +++ b/firmware/targets/f7/furi_hal/furi_hal_subghz.c @@ -4,7 +4,6 @@ #include #include #include -#include #include #include #include @@ -17,39 +16,26 @@ #define TAG "FuriHalSubGhz" -/* - * Uncomment define to enable duplication of - * IO GO0 CC1101 to an external comb. - * Debug pin can be assigned - * gpio_ext_pc0 - * gpio_ext_pc1 - * gpio_ext_pc3 - * gpio_ext_pb2 - * gpio_ext_pb3 - * gpio_ext_pa4 - * gpio_ext_pa6 - * gpio_ext_pa7 - * Attention this setting switches pin to output. - * Make sure it is not connected directly to power or ground - */ - -//#define SUBGHZ_DEBUG_CC1101_PIN gpio_ext_pa7 -#ifdef SUBGHZ_DEBUG_CC1101_PIN -uint32_t subghz_debug_gpio_buff[2]; -#endif +static uint32_t furi_hal_subghz_debug_gpio_buff[2]; typedef struct { volatile SubGhzState state; volatile SubGhzRegulation regulation; volatile FuriHalSubGhzPreset preset; + const GpioPin* async_mirror_pin; } FuriHalSubGhz; volatile FuriHalSubGhz furi_hal_subghz = { .state = SubGhzStateInit, .regulation = SubGhzRegulationTxRx, .preset = FuriHalSubGhzPresetIDLE, + .async_mirror_pin = NULL, }; +void furi_hal_subghz_set_async_mirror_pin(const GpioPin* pin) { + furi_hal_subghz.async_mirror_pin = pin; +} + void furi_hal_subghz_init() { furi_assert(furi_hal_subghz.state == SubGhzStateInit); furi_hal_subghz.state = SubGhzStateIdle; @@ -372,6 +358,29 @@ void furi_hal_subghz_set_path(FuriHalSubGhzPath path) { furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); } +static bool furi_hal_subghz_start_debug() { + bool ret = false; + if(furi_hal_subghz.async_mirror_pin != NULL) { + furi_hal_gpio_init( + furi_hal_subghz.async_mirror_pin, + GpioModeOutputPushPull, + GpioPullNo, + GpioSpeedVeryHigh); + ret = true; + } + return ret; +} + +static bool furi_hal_subghz_stop_debug() { + bool ret = false; + if(furi_hal_subghz.async_mirror_pin != NULL) { + furi_hal_gpio_init( + furi_hal_subghz.async_mirror_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + ret = true; + } + return ret; +} + volatile uint32_t furi_hal_subghz_capture_delta_duration = 0; volatile FuriHalSubGhzCaptureCallback furi_hal_subghz_capture_callback = NULL; volatile void* furi_hal_subghz_capture_callback_context = NULL; @@ -382,9 +391,9 @@ static void furi_hal_subghz_capture_ISR() { LL_TIM_ClearFlag_CC1(TIM2); furi_hal_subghz_capture_delta_duration = LL_TIM_IC_GetCaptureCH1(TIM2); if(furi_hal_subghz_capture_callback) { -#ifdef SUBGHZ_DEBUG_CC1101_PIN - furi_hal_gpio_write(&SUBGHZ_DEBUG_CC1101_PIN, false); -#endif + if(furi_hal_subghz.async_mirror_pin != NULL) + furi_hal_gpio_write(furi_hal_subghz.async_mirror_pin, false); + furi_hal_subghz_capture_callback( true, furi_hal_subghz_capture_delta_duration, @@ -395,9 +404,9 @@ static void furi_hal_subghz_capture_ISR() { if(LL_TIM_IsActiveFlag_CC2(TIM2)) { LL_TIM_ClearFlag_CC2(TIM2); if(furi_hal_subghz_capture_callback) { -#ifdef SUBGHZ_DEBUG_CC1101_PIN - furi_hal_gpio_write(&SUBGHZ_DEBUG_CC1101_PIN, true); -#endif + if(furi_hal_subghz.async_mirror_pin != NULL) + furi_hal_gpio_write(furi_hal_subghz.async_mirror_pin, true); + furi_hal_subghz_capture_callback( false, LL_TIM_IC_GetCaptureCH2(TIM2) - furi_hal_subghz_capture_delta_duration, @@ -459,10 +468,8 @@ void furi_hal_subghz_start_async_rx(FuriHalSubGhzCaptureCallback callback, void* LL_TIM_SetCounter(TIM2, 0); LL_TIM_EnableCounter(TIM2); -#ifdef SUBGHZ_DEBUG_CC1101_PIN - furi_hal_gpio_init( - &SUBGHZ_DEBUG_CC1101_PIN, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); -#endif + // Start debug + furi_hal_subghz_start_debug(); // Switch to RX furi_hal_subghz_rx(); @@ -478,9 +485,8 @@ void furi_hal_subghz_stop_async_rx() { FURI_CRITICAL_ENTER(); LL_TIM_DeInit(TIM2); -#ifdef SUBGHZ_DEBUG_CC1101_PIN - furi_hal_gpio_init(&SUBGHZ_DEBUG_CC1101_PIN, GpioModeAnalog, GpioPullNo, GpioSpeedLow); -#endif + // Stop debug + furi_hal_subghz_stop_debug(); FURI_CRITICAL_EXIT(); furi_hal_interrupt_set_isr(FuriHalInterruptIdTIM2, NULL, NULL); @@ -673,30 +679,27 @@ bool furi_hal_subghz_start_async_tx(FuriHalSubGhzAsyncTxCallback callback, void* LL_TIM_SetCounter(TIM2, 0); LL_TIM_EnableCounter(TIM2); -#ifdef SUBGHZ_DEBUG_CC1101_PIN - furi_hal_gpio_init( - &SUBGHZ_DEBUG_CC1101_PIN, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); - - const GpioPin* gpio = &SUBGHZ_DEBUG_CC1101_PIN; - subghz_debug_gpio_buff[0] = (uint32_t)gpio->pin << GPIO_NUMBER; - subghz_debug_gpio_buff[1] = gpio->pin; - - dma_config.MemoryOrM2MDstAddress = (uint32_t)subghz_debug_gpio_buff; - dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (gpio->port->BSRR); - 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_WORD; - dma_config.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_WORD; - 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_2, &dma_config); - LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_2, 2); - LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_2); - -#endif + // Start debug + if(furi_hal_subghz_start_debug()) { + const GpioPin* gpio = furi_hal_subghz.async_mirror_pin; + furi_hal_subghz_debug_gpio_buff[0] = (uint32_t)gpio->pin << GPIO_NUMBER; + furi_hal_subghz_debug_gpio_buff[1] = gpio->pin; + + dma_config.MemoryOrM2MDstAddress = (uint32_t)furi_hal_subghz_debug_gpio_buff; + dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (gpio->port->BSRR); + 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_WORD; + dma_config.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_WORD; + 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_2, &dma_config); + LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_2, 2); + LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_2); + } return true; } @@ -730,10 +733,10 @@ void furi_hal_subghz_stop_async_tx() { // Deinitialize GPIO furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeAnalog, GpioPullNo, GpioSpeedLow); -#ifdef SUBGHZ_DEBUG_CC1101_PIN - LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_2); - furi_hal_gpio_init(&SUBGHZ_DEBUG_CC1101_PIN, GpioModeAnalog, GpioPullNo, GpioSpeedLow); -#endif + // Stop debug + if(furi_hal_subghz_stop_debug()) { + LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_2); + } FURI_CRITICAL_EXIT(); diff --git a/firmware/targets/furi_hal_include/furi_hal_speaker.h b/firmware/targets/furi_hal_include/furi_hal_speaker.h index 67de41d9227..0b33d923274 100644 --- a/firmware/targets/furi_hal_include/furi_hal_speaker.h +++ b/firmware/targets/furi_hal_include/furi_hal_speaker.h @@ -4,16 +4,63 @@ */ #pragma once +#include + #ifdef __cplusplus extern "C" { #endif +/** Init speaker */ void furi_hal_speaker_init(); +/** Deinit speaker */ +void furi_hal_speaker_deinit(); + +/** Acquire speaker ownership + * + * @warning You must acquire speaker ownership before use + * + * @param timeout Timeout during which speaker ownership must be acquired + * + * @return bool returns true on success + */ +FURI_WARN_UNUSED bool furi_hal_speaker_acquire(uint32_t timeout); + +/** Release speaker ownership + * + * @warning You must release speaker ownership after use + */ +void furi_hal_speaker_release(); + +/** Check current process speaker ownership + * + * @warning always returns true if called from ISR + * + * @return bool returns true if process owns speaker + */ +bool furi_hal_speaker_is_mine(); + +/** Play a note + * + * @warning no ownership check if called from ISR + * + * @param frequency The frequency + * @param volume The volume + */ void furi_hal_speaker_start(float frequency, float volume); +/** Set volume + * + * @warning no ownership check if called from ISR + * + * @param volume The volume + */ void furi_hal_speaker_set_volume(float volume); +/** Stop playback + * + * @warning no ownership check if called from ISR + */ void furi_hal_speaker_stop(); #ifdef __cplusplus diff --git a/firmware/targets/furi_hal_include/furi_hal_subghz.h b/firmware/targets/furi_hal_include/furi_hal_subghz.h index 1f99386c0c0..102981dbeb5 100644 --- a/firmware/targets/furi_hal_include/furi_hal_subghz.h +++ b/firmware/targets/furi_hal_include/furi_hal_subghz.h @@ -9,6 +9,7 @@ #include #include #include +#include #ifdef __cplusplus extern "C" { @@ -34,9 +35,9 @@ typedef enum { /** Switchable Radio Paths */ typedef enum { FuriHalSubGhzPathIsolate, /**< Isolate Radio from antenna */ - FuriHalSubGhzPath433, /**< Center Frquency: 433MHz. Path 1: SW1RF1-SW2RF2, LCLCL */ - FuriHalSubGhzPath315, /**< Center Frquency: 315MHz. Path 2: SW1RF2-SW2RF1, LCLCLCL */ - FuriHalSubGhzPath868, /**< Center Frquency: 868MHz. Path 3: SW1RF3-SW2RF3, LCLC */ + FuriHalSubGhzPath433, /**< Center Frequency: 433MHz. Path 1: SW1RF1-SW2RF2, LCLCL */ + FuriHalSubGhzPath315, /**< Center Frequency: 315MHz. Path 2: SW1RF2-SW2RF1, LCLCLCL */ + FuriHalSubGhzPath868, /**< Center Frequency: 868MHz. Path 3: SW1RF3-SW2RF3, LCLC */ } FuriHalSubGhzPath; /** SubGhz state */ @@ -60,8 +61,17 @@ typedef enum { SubGhzRegulationTxRx, /**TxRx*/ } SubGhzRegulation; +/* Mirror RX/TX async modulation signal to specified pin + * + * @warning Configures pin to output mode. Make sure it is not connected + * directly to power or ground. + * + * @param[in] pin pointer to the gpio pin structure or NULL to disable + */ +void furi_hal_subghz_set_async_mirror_pin(const GpioPin* pin); + /** Initialize and switch to power save mode Used by internal API-HAL - * initalization routine Can be used to reinitialize device to safe state and + * initialization routine Can be used to reinitialize device to safe state and * send it to sleep */ void furi_hal_subghz_init(); @@ -105,13 +115,13 @@ void furi_hal_subghz_load_patable(const uint8_t data[8]); */ void furi_hal_subghz_write_packet(const uint8_t* data, uint8_t size); -/** Check if recieve pipe is not empty +/** Check if receive pipe is not empty * * @return true if not empty */ bool furi_hal_subghz_rx_pipe_not_empty(); -/** Check if recieved data crc is valid +/** Check if received data crc is valid * * @return true if valid */ @@ -132,7 +142,7 @@ void furi_hal_subghz_flush_rx(); */ void furi_hal_subghz_flush_tx(); -/** Shutdown Issue spwd command +/** Shutdown Issue SPWD command * @warning registers content will be lost */ void furi_hal_subghz_shutdown(); @@ -146,7 +156,7 @@ void furi_hal_subghz_reset(); */ void furi_hal_subghz_idle(); -/** Switch to Recieve +/** Switch to Receive */ void furi_hal_subghz_rx(); @@ -172,7 +182,7 @@ uint8_t furi_hal_subghz_get_lqi(); * * @param value frequency in Hz * - * @return true if frequncy is valid, otherwise false + * @return true if frequency is valid, otherwise false */ bool furi_hal_subghz_is_frequency_valid(uint32_t value); @@ -181,7 +191,7 @@ bool furi_hal_subghz_is_frequency_valid(uint32_t value); * * @param value frequency in Hz * - * @return real frequency in herz + * @return real frequency in Hz */ uint32_t furi_hal_subghz_set_frequency_and_path(uint32_t value); @@ -189,7 +199,7 @@ uint32_t furi_hal_subghz_set_frequency_and_path(uint32_t value); * * @param value frequency in Hz * - * @return real frequency in herz + * @return real frequency in Hz */ uint32_t furi_hal_subghz_set_frequency(uint32_t value); diff --git a/furi/core/common_defines.h b/furi/core/common_defines.h index 31be7fff074..c7acf95b438 100644 --- a/furi/core/common_defines.h +++ b/furi/core/common_defines.h @@ -11,6 +11,10 @@ extern "C" { #include +#ifndef FURI_WARN_UNUSED +#define FURI_WARN_UNUSED __attribute__((warn_unused_result)) +#endif + #ifndef FURI_IS_IRQ_MASKED #define FURI_IS_IRQ_MASKED() (__get_PRIMASK() != 0U) #endif From f10e82c64dc8f42ce2ee66c92aefdaf574abeea8 Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Mon, 19 Dec 2022 12:38:20 +0300 Subject: [PATCH 286/824] [FL-3024] Locale settings (#2137) * Locale settings * Time/date format fix * Locale: add docs, enums for HAL, cleanup. Co-authored-by: Aleksandr Kutuzov --- .../debug/locale_test/application.fam | 11 ++ applications/debug/locale_test/locale_test.c | 102 +++++++++++++ applications/services/locale/application.fam | 9 ++ applications/services/locale/locale.c | 109 +++++++++++++ applications/services/locale/locale.h | 107 +++++++++++++ applications/settings/system/application.fam | 2 +- .../settings/system/system_settings.c | 79 ++++++++++ firmware/targets/f7/api_symbols.csv | 19 ++- firmware/targets/f7/furi_hal/furi_hal_rtc.c | 74 +++++++-- .../targets/furi_hal_include/furi_hal_rtc.h | 143 +++++++++++++++++- 10 files changed, 636 insertions(+), 19 deletions(-) create mode 100644 applications/debug/locale_test/application.fam create mode 100644 applications/debug/locale_test/locale_test.c create mode 100644 applications/services/locale/application.fam create mode 100644 applications/services/locale/locale.c create mode 100644 applications/services/locale/locale.h diff --git a/applications/debug/locale_test/application.fam b/applications/debug/locale_test/application.fam new file mode 100644 index 00000000000..e46eeff51c7 --- /dev/null +++ b/applications/debug/locale_test/application.fam @@ -0,0 +1,11 @@ +App( + appid="locale_test", + name="Locale Test", + apptype=FlipperAppType.DEBUG, + entry_point="locale_test_app", + cdefines=["APP_LOCALE"], + requires=["gui", "locale"], + stack_size=2 * 1024, + order=70, + fap_category="Debug", +) diff --git a/applications/debug/locale_test/locale_test.c b/applications/debug/locale_test/locale_test.c new file mode 100644 index 00000000000..003df55dca4 --- /dev/null +++ b/applications/debug/locale_test/locale_test.c @@ -0,0 +1,102 @@ +#include +#include +#include +#include +#include +#include + +typedef struct { + Gui* gui; + ViewDispatcher* view_dispatcher; + View* view; +} LocaleTestApp; + +static void locale_test_view_draw_callback(Canvas* canvas, void* _model) { + UNUSED(_model); + + // Prepare canvas + canvas_set_color(canvas, ColorBlack); + canvas_set_font(canvas, FontSecondary); + + FuriString* tmp_string = furi_string_alloc(); + + float temp = 25.3f; + LocaleMeasurementUnits units = locale_get_measurement_unit(); + if(units == LocaleMeasurementUnitsMetric) { + furi_string_printf(tmp_string, "Temp: %5.1fC", (double)temp); + } else { + temp = locale_celsius_to_fahrenheit(temp); + furi_string_printf(tmp_string, "Temp: %5.1fF", (double)temp); + } + canvas_draw_str(canvas, 0, 10, furi_string_get_cstr(tmp_string)); + + FuriHalRtcDateTime datetime; + furi_hal_rtc_get_datetime(&datetime); + + locale_format_time(tmp_string, &datetime, locale_get_time_format(), false); + canvas_draw_str(canvas, 0, 25, furi_string_get_cstr(tmp_string)); + + locale_format_date(tmp_string, &datetime, locale_get_date_format(), "/"); + canvas_draw_str(canvas, 0, 40, furi_string_get_cstr(tmp_string)); + + furi_string_free(tmp_string); +} + +static bool locale_test_view_input_callback(InputEvent* event, void* context) { + UNUSED(event); + UNUSED(context); + return false; +} + +static uint32_t locale_test_exit(void* context) { + UNUSED(context); + return VIEW_NONE; +} + +static LocaleTestApp* locale_test_alloc() { + LocaleTestApp* app = malloc(sizeof(LocaleTestApp)); + + // Gui + app->gui = furi_record_open(RECORD_GUI); + + // View dispatcher + app->view_dispatcher = view_dispatcher_alloc(); + view_dispatcher_enable_queue(app->view_dispatcher); + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + + // Views + app->view = view_alloc(); + view_set_draw_callback(app->view, locale_test_view_draw_callback); + view_set_input_callback(app->view, locale_test_view_input_callback); + + view_set_previous_callback(app->view, locale_test_exit); + view_dispatcher_add_view(app->view_dispatcher, 0, app->view); + view_dispatcher_switch_to_view(app->view_dispatcher, 0); + + return app; +} + +static void locale_test_free(LocaleTestApp* app) { + furi_assert(app); + + // Free views + view_dispatcher_remove_view(app->view_dispatcher, 0); + + view_free(app->view); + view_dispatcher_free(app->view_dispatcher); + + // Close gui record + furi_record_close(RECORD_GUI); + app->gui = NULL; + + // Free rest + free(app); +} + +int32_t locale_test_app(void* p) { + UNUSED(p); + LocaleTestApp* app = locale_test_alloc(); + view_dispatcher_run(app->view_dispatcher); + locale_test_free(app); + return 0; +} diff --git a/applications/services/locale/application.fam b/applications/services/locale/application.fam new file mode 100644 index 00000000000..c762d02d67b --- /dev/null +++ b/applications/services/locale/application.fam @@ -0,0 +1,9 @@ +App( + appid="locale", + name="LocaleSrv", + apptype=FlipperAppType.STARTUP, + entry_point="locale_on_system_start", + cdefines=["SRV_LOCALE"], + order=90, + sdk_headers=["locale.h"], +) diff --git a/applications/services/locale/locale.c b/applications/services/locale/locale.c new file mode 100644 index 00000000000..07f56b1b01a --- /dev/null +++ b/applications/services/locale/locale.c @@ -0,0 +1,109 @@ +#include "locale.h" + +#define TAG "LocaleSrv" + +LocaleMeasurementUnits locale_get_measurement_unit(void) { + return (LocaleMeasurementUnits)furi_hal_rtc_get_locale_units(); +} + +void locale_set_measurement_unit(LocaleMeasurementUnits format) { + furi_hal_rtc_set_locale_units((FuriHalRtcLocaleUnits)format); +} + +LocaleTimeFormat locale_get_time_format(void) { + return (LocaleTimeFormat)furi_hal_rtc_get_locale_timeformat(); +} + +void locale_set_time_format(LocaleTimeFormat format) { + furi_hal_rtc_set_locale_timeformat((FuriHalRtcLocaleTimeFormat)format); +} + +LocaleDateFormat locale_get_date_format(void) { + return (LocaleDateFormat)furi_hal_rtc_get_locale_dateformat(); +} + +void locale_set_date_format(LocaleDateFormat format) { + furi_hal_rtc_set_locale_dateformat((FuriHalRtcLocaleDateFormat)format); +} + +float locale_fahrenheit_to_celsius(float temp_f) { + return (temp_f - 32.f) / 1.8f; +} + +float locale_celsius_to_fahrenheit(float temp_c) { + return (temp_c * 1.8f + 32.f); +} + +void locale_format_time( + FuriString* out_str, + const FuriHalRtcDateTime* datetime, + const LocaleTimeFormat format, + const bool show_seconds) { + furi_assert(out_str); + furi_assert(datetime); + + uint8_t hours = datetime->hour; + uint8_t am_pm = 0; + if(format == LocaleTimeFormat12h) { + if(hours > 12) { + hours -= 12; + am_pm = 2; + } else { + am_pm = 1; + } + } + + if(show_seconds) { + furi_string_printf(out_str, "%02u:%02u:%02u", hours, datetime->minute, datetime->second); + } else { + furi_string_printf(out_str, "%02u:%02u", hours, datetime->minute); + } + + if(am_pm > 0) { + furi_string_cat_printf(out_str, " %s", (am_pm == 1) ? ("AM") : ("PM")); + } +} + +void locale_format_date( + FuriString* out_str, + const FuriHalRtcDateTime* datetime, + const LocaleDateFormat format, + const char* separator) { + furi_assert(out_str); + furi_assert(datetime); + furi_assert(separator); + + if(format == LocaleDateFormatDMY) { + furi_string_printf( + out_str, + "%02u%s%02u%s%04u", + datetime->day, + separator, + datetime->month, + separator, + datetime->year); + } else if(format == LocaleDateFormatMDY) { + furi_string_printf( + out_str, + "%02u%s%02u%s%04u", + datetime->month, + separator, + datetime->day, + separator, + datetime->year); + } else { + furi_string_printf( + out_str, + "%04u%s%02u%s%02u", + datetime->year, + separator, + datetime->month, + separator, + datetime->day); + } +} + +int32_t locale_on_system_start(void* p) { + UNUSED(p); + return 0; +} diff --git a/applications/services/locale/locale.h b/applications/services/locale/locale.h new file mode 100644 index 00000000000..5f2a4d87f22 --- /dev/null +++ b/applications/services/locale/locale.h @@ -0,0 +1,107 @@ +#pragma once + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + LocaleMeasurementUnitsMetric = 0, /**< Metric measurement units */ + LocaleMeasurementUnitsImperial = 1, /**< Imperial measurement units */ +} LocaleMeasurementUnits; + +typedef enum { + LocaleTimeFormat24h = 0, /**< 24-hour format */ + LocaleTimeFormat12h = 1, /**< 12-hour format */ +} LocaleTimeFormat; + +typedef enum { + LocaleDateFormatDMY = 0, /**< Day/Month/Year */ + LocaleDateFormatMDY = 1, /**< Month/Day/Year */ + LocaleDateFormatYMD = 2, /**< Year/Month/Day */ +} LocaleDateFormat; + +/** Get Locale measurement units + * + * @return The locale measurement units. + */ +LocaleMeasurementUnits locale_get_measurement_unit(); + +/** Set locale measurement units + * + * @param[in] format The locale measurements units + */ +void locale_set_measurement_unit(LocaleMeasurementUnits format); + +/** Convert Fahrenheit to Celsius + * + * @param[in] temp_f The Temperature in Fahrenheit + * + * @return The Temperature in Celsius + */ +float locale_fahrenheit_to_celsius(float temp_f); + +/** Convert Celsius to Fahrenheit + * + * @param[in] temp_c The Temperature in Celsius + * + * @return The Temperature in Fahrenheit + */ +float locale_celsius_to_fahrenheit(float temp_c); + +/** Get Locale time format + * + * @return The locale time format. + */ +LocaleTimeFormat locale_get_time_format(); + +/** Set Locale Time Format + * + * @param[in] format The Locale Time Format + */ +void locale_set_time_format(LocaleTimeFormat format); + +/** Format time to furi string + * + * @param[out] out_str The FuriString to store formatted time + * @param[in] datetime Pointer to the datetime + * @param[in] format The Locale Time Format + * @param[in] show_seconds The show seconds flag + */ +void locale_format_time( + FuriString* out_str, + const FuriHalRtcDateTime* datetime, + const LocaleTimeFormat format, + const bool show_seconds); + +/** Get Locale DateFormat + * + * @return The Locale DateFormat. + */ +LocaleDateFormat locale_get_date_format(); + +/** Set Locale DateFormat + * + * @param[in] format The Locale DateFormat + */ +void locale_set_date_format(LocaleDateFormat format); + +/** Format date to furi string + * + * @param[out] out_str The FuriString to store formatted date + * @param[in] datetime Pointer to the datetime + * @param[in] format The format + * @param[in] separator The separator + */ +void locale_format_date( + FuriString* out_str, + const FuriHalRtcDateTime* datetime, + const LocaleDateFormat format, + const char* separator); + +#ifdef __cplusplus +} +#endif diff --git a/applications/settings/system/application.fam b/applications/settings/system/application.fam index 0fc456b2f88..69a8f1239f9 100644 --- a/applications/settings/system/application.fam +++ b/applications/settings/system/application.fam @@ -3,7 +3,7 @@ App( name="System", apptype=FlipperAppType.SETTINGS, entry_point="system_settings_app", - requires=["gui"], + requires=["gui", "locale"], stack_size=1 * 1024, order=70, ) diff --git a/applications/settings/system/system_settings.c b/applications/settings/system/system_settings.c index dfce11a2207..5eade21159b 100644 --- a/applications/settings/system/system_settings.c +++ b/applications/settings/system/system_settings.c @@ -1,6 +1,7 @@ #include "system_settings.h" #include #include +#include const char* const log_level_text[] = { "Default", @@ -70,6 +71,59 @@ static void heap_trace_mode_changed(VariableItem* item) { furi_hal_rtc_set_heap_track_mode(heap_trace_mode_value[index]); } +const char* const mesurement_units_text[] = { + "Metric", + "Imperial", +}; + +const uint32_t mesurement_units_value[] = { + LocaleMeasurementUnitsMetric, + LocaleMeasurementUnitsImperial, +}; + +static void mesurement_units_changed(VariableItem* item) { + // SystemSettings* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + variable_item_set_current_value_text(item, mesurement_units_text[index]); + locale_set_measurement_unit(mesurement_units_value[index]); +} + +const char* const time_format_text[] = { + "24h", + "12h", +}; + +const uint32_t time_format_value[] = { + LocaleTimeFormat24h, + LocaleTimeFormat12h, +}; + +static void time_format_changed(VariableItem* item) { + // SystemSettings* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + variable_item_set_current_value_text(item, time_format_text[index]); + locale_set_time_format(time_format_value[index]); +} + +const char* const date_format_text[] = { + "D/M/Y", + "M/D/Y", + "Y/M/D", +}; + +const uint32_t date_format_value[] = { + LocaleDateFormatDMY, + LocaleDateFormatMDY, + LocaleDateFormatYMD, +}; + +static void date_format_changed(VariableItem* item) { + // SystemSettings* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + variable_item_set_current_value_text(item, date_format_text[index]); + locale_set_date_format(date_format_value[index]); +} + static uint32_t system_settings_exit(void* context) { UNUSED(context); return VIEW_NONE; @@ -91,6 +145,31 @@ SystemSettings* system_settings_alloc() { uint8_t value_index; app->var_item_list = variable_item_list_alloc(); + item = variable_item_list_add( + app->var_item_list, + "Units", + COUNT_OF(mesurement_units_text), + mesurement_units_changed, + app); + value_index = value_index_uint32( + locale_get_measurement_unit(), mesurement_units_value, COUNT_OF(mesurement_units_value)); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, mesurement_units_text[value_index]); + + item = variable_item_list_add( + app->var_item_list, "Time Format", COUNT_OF(time_format_text), time_format_changed, app); + value_index = value_index_uint32( + locale_get_time_format(), time_format_value, COUNT_OF(time_format_value)); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, time_format_text[value_index]); + + item = variable_item_list_add( + app->var_item_list, "Date Format", COUNT_OF(date_format_text), date_format_changed, app); + value_index = value_index_uint32( + locale_get_date_format(), date_format_value, COUNT_OF(date_format_value)); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, date_format_text[value_index]); + item = variable_item_list_add( app->var_item_list, "Log Level", COUNT_OF(log_level_text), log_level_changed, app); value_index = value_index_uint32( diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index b3afb84271a..d8db33a38d1 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,11.0,, +Version,+,11.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -29,6 +29,7 @@ Header,+,applications/services/gui/view_dispatcher.h,, Header,+,applications/services/gui/view_stack.h,, Header,+,applications/services/input/input.h,, Header,+,applications/services/loader/loader.h,, +Header,+,applications/services/locale/locale.h,, Header,+,applications/services/notification/notification.h,, Header,+,applications/services/notification/notification_messages.h,, Header,+,applications/services/power/power_service/power.h,, @@ -1265,6 +1266,9 @@ Function,+,furi_hal_rtc_get_boot_mode,FuriHalRtcBootMode, Function,+,furi_hal_rtc_get_datetime,void,FuriHalRtcDateTime* Function,+,furi_hal_rtc_get_fault_data,uint32_t, Function,+,furi_hal_rtc_get_heap_track_mode,FuriHalRtcHeapTrackMode, +Function,+,furi_hal_rtc_get_locale_dateformat,FuriHalRtcLocaleDateFormat, +Function,+,furi_hal_rtc_get_locale_timeformat,FuriHalRtcLocaleTimeFormat, +Function,+,furi_hal_rtc_get_locale_units,FuriHalRtcLocaleUnits, Function,+,furi_hal_rtc_get_log_level,uint8_t, Function,+,furi_hal_rtc_get_pin_fails,uint32_t, Function,+,furi_hal_rtc_get_register,uint32_t,FuriHalRtcRegister @@ -1278,6 +1282,9 @@ Function,+,furi_hal_rtc_set_datetime,void,FuriHalRtcDateTime* Function,+,furi_hal_rtc_set_fault_data,void,uint32_t Function,+,furi_hal_rtc_set_flag,void,FuriHalRtcFlag Function,+,furi_hal_rtc_set_heap_track_mode,void,FuriHalRtcHeapTrackMode +Function,+,furi_hal_rtc_set_locale_dateformat,void,FuriHalRtcLocaleDateFormat +Function,+,furi_hal_rtc_set_locale_timeformat,void,FuriHalRtcLocaleTimeFormat +Function,+,furi_hal_rtc_set_locale_units,void,FuriHalRtcLocaleUnits Function,+,furi_hal_rtc_set_log_level,void,uint8_t Function,+,furi_hal_rtc_set_pin_fails,void,uint32_t Function,+,furi_hal_rtc_set_register,void,"FuriHalRtcRegister, uint32_t" @@ -1736,6 +1743,16 @@ Function,+,loader_update_menu,void, Function,+,loading_alloc,Loading*, Function,+,loading_free,void,Loading* Function,+,loading_get_view,View*,Loading* +Function,+,locale_celsius_to_fahrenheit,float,float +Function,+,locale_fahrenheit_to_celsius,float,float +Function,+,locale_format_date,void,"FuriString*, const FuriHalRtcDateTime*, const LocaleDateFormat, const char*" +Function,+,locale_format_time,void,"FuriString*, const FuriHalRtcDateTime*, const LocaleTimeFormat, const _Bool" +Function,+,locale_get_date_format,LocaleDateFormat, +Function,+,locale_get_measurement_unit,LocaleMeasurementUnits, +Function,+,locale_get_time_format,LocaleTimeFormat, +Function,+,locale_set_date_format,void,LocaleDateFormat +Function,+,locale_set_measurement_unit,void,LocaleMeasurementUnits +Function,+,locale_set_time_format,void,LocaleTimeFormat Function,-,localtime,tm*,const time_t* Function,-,localtime_r,tm*,"const time_t*, tm*" Function,-,log,double,double diff --git a/firmware/targets/f7/furi_hal/furi_hal_rtc.c b/firmware/targets/f7/furi_hal/furi_hal_rtc.c index c38cbfec5b3..e5fa8c767f1 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_rtc.c +++ b/firmware/targets/f7/furi_hal/furi_hal_rtc.c @@ -29,12 +29,15 @@ typedef struct { uint8_t log_level : 4; uint8_t log_reserved : 4; uint8_t flags; - uint8_t boot_mode : 4; - uint8_t heap_track_mode : 2; - uint16_t reserved : 10; -} DeveloperReg; + FuriHalRtcBootMode boot_mode : 4; + FuriHalRtcHeapTrackMode heap_track_mode : 2; + FuriHalRtcLocaleUnits locale_units : 1; + FuriHalRtcLocaleTimeFormat locale_timeformat : 1; + FuriHalRtcLocaleDateFormat locale_dateformat : 2; + uint8_t reserved : 6; +} SystemReg; -_Static_assert(sizeof(DeveloperReg) == 4, "DeveloperReg size mismatch"); +_Static_assert(sizeof(SystemReg) == 4, "SystemReg size mismatch"); #define FURI_HAL_RTC_SECONDS_PER_MINUTE 60 #define FURI_HAL_RTC_SECONDS_PER_HOUR (FURI_HAL_RTC_SECONDS_PER_MINUTE * 60) @@ -172,7 +175,7 @@ void furi_hal_rtc_set_register(FuriHalRtcRegister reg, uint32_t value) { void furi_hal_rtc_set_log_level(uint8_t level) { uint32_t data_reg = furi_hal_rtc_get_register(FuriHalRtcRegisterSystem); - DeveloperReg* data = (DeveloperReg*)&data_reg; + SystemReg* data = (SystemReg*)&data_reg; data->log_level = level; furi_hal_rtc_set_register(FuriHalRtcRegisterSystem, data_reg); furi_log_set_level(level); @@ -180,13 +183,13 @@ void furi_hal_rtc_set_log_level(uint8_t level) { uint8_t furi_hal_rtc_get_log_level() { uint32_t data_reg = furi_hal_rtc_get_register(FuriHalRtcRegisterSystem); - DeveloperReg* data = (DeveloperReg*)&data_reg; + SystemReg* data = (SystemReg*)&data_reg; return data->log_level; } void furi_hal_rtc_set_flag(FuriHalRtcFlag flag) { uint32_t data_reg = furi_hal_rtc_get_register(FuriHalRtcRegisterSystem); - DeveloperReg* data = (DeveloperReg*)&data_reg; + SystemReg* data = (SystemReg*)&data_reg; data->flags |= flag; furi_hal_rtc_set_register(FuriHalRtcRegisterSystem, data_reg); @@ -197,7 +200,7 @@ void furi_hal_rtc_set_flag(FuriHalRtcFlag flag) { void furi_hal_rtc_reset_flag(FuriHalRtcFlag flag) { uint32_t data_reg = furi_hal_rtc_get_register(FuriHalRtcRegisterSystem); - DeveloperReg* data = (DeveloperReg*)&data_reg; + SystemReg* data = (SystemReg*)&data_reg; data->flags &= ~flag; furi_hal_rtc_set_register(FuriHalRtcRegisterSystem, data_reg); @@ -208,34 +211,73 @@ void furi_hal_rtc_reset_flag(FuriHalRtcFlag flag) { bool furi_hal_rtc_is_flag_set(FuriHalRtcFlag flag) { uint32_t data_reg = furi_hal_rtc_get_register(FuriHalRtcRegisterSystem); - DeveloperReg* data = (DeveloperReg*)&data_reg; + SystemReg* data = (SystemReg*)&data_reg; return data->flags & flag; } void furi_hal_rtc_set_boot_mode(FuriHalRtcBootMode mode) { uint32_t data_reg = furi_hal_rtc_get_register(FuriHalRtcRegisterSystem); - DeveloperReg* data = (DeveloperReg*)&data_reg; + SystemReg* data = (SystemReg*)&data_reg; data->boot_mode = mode; furi_hal_rtc_set_register(FuriHalRtcRegisterSystem, data_reg); } FuriHalRtcBootMode furi_hal_rtc_get_boot_mode() { uint32_t data_reg = furi_hal_rtc_get_register(FuriHalRtcRegisterSystem); - DeveloperReg* data = (DeveloperReg*)&data_reg; - return (FuriHalRtcBootMode)data->boot_mode; + SystemReg* data = (SystemReg*)&data_reg; + return data->boot_mode; } void furi_hal_rtc_set_heap_track_mode(FuriHalRtcHeapTrackMode mode) { uint32_t data_reg = furi_hal_rtc_get_register(FuriHalRtcRegisterSystem); - DeveloperReg* data = (DeveloperReg*)&data_reg; + SystemReg* data = (SystemReg*)&data_reg; data->heap_track_mode = mode; furi_hal_rtc_set_register(FuriHalRtcRegisterSystem, data_reg); } FuriHalRtcHeapTrackMode furi_hal_rtc_get_heap_track_mode() { uint32_t data_reg = furi_hal_rtc_get_register(FuriHalRtcRegisterSystem); - DeveloperReg* data = (DeveloperReg*)&data_reg; - return (FuriHalRtcHeapTrackMode)data->heap_track_mode; + SystemReg* data = (SystemReg*)&data_reg; + return data->heap_track_mode; +} + +void furi_hal_rtc_set_locale_units(FuriHalRtcLocaleUnits value) { + uint32_t data_reg = furi_hal_rtc_get_register(FuriHalRtcRegisterSystem); + SystemReg* data = (SystemReg*)&data_reg; + data->locale_units = value; + furi_hal_rtc_set_register(FuriHalRtcRegisterSystem, data_reg); +} + +FuriHalRtcLocaleUnits furi_hal_rtc_get_locale_units() { + uint32_t data_reg = furi_hal_rtc_get_register(FuriHalRtcRegisterSystem); + SystemReg* data = (SystemReg*)&data_reg; + return data->locale_units; +} + +void furi_hal_rtc_set_locale_timeformat(FuriHalRtcLocaleTimeFormat value) { + uint32_t data_reg = furi_hal_rtc_get_register(FuriHalRtcRegisterSystem); + SystemReg* data = (SystemReg*)&data_reg; + data->locale_timeformat = value; + furi_hal_rtc_set_register(FuriHalRtcRegisterSystem, data_reg); +} + +FuriHalRtcLocaleTimeFormat furi_hal_rtc_get_locale_timeformat() { + uint32_t data_reg = furi_hal_rtc_get_register(FuriHalRtcRegisterSystem); + SystemReg* data = (SystemReg*)&data_reg; + return data->locale_timeformat; +} + +void furi_hal_rtc_set_locale_dateformat(FuriHalRtcLocaleDateFormat value) { + uint32_t data_reg = furi_hal_rtc_get_register(FuriHalRtcRegisterSystem); + SystemReg* data = (SystemReg*)&data_reg; + data->locale_dateformat = value; + furi_hal_rtc_set_register(FuriHalRtcRegisterSystem, data_reg); +} + +FuriHalRtcLocaleDateFormat furi_hal_rtc_get_locale_dateformat() { + uint32_t data_reg = furi_hal_rtc_get_register(FuriHalRtcRegisterSystem); + SystemReg* data = (SystemReg*)&data_reg; + return data->locale_dateformat; } void furi_hal_rtc_set_datetime(FuriHalRtcDateTime* datetime) { diff --git a/firmware/targets/furi_hal_include/furi_hal_rtc.h b/firmware/targets/furi_hal_include/furi_hal_rtc.h index 5ce122271d6..fe095e74980 100644 --- a/firmware/targets/furi_hal_include/furi_hal_rtc.h +++ b/firmware/targets/furi_hal_include/furi_hal_rtc.h @@ -59,53 +59,194 @@ typedef enum { FuriHalRtcRegisterMAX, /**< Service value, do not use */ } FuriHalRtcRegister; +typedef enum { + FuriHalRtcLocaleUnitsMetric = 0, /**< Metric measurement units */ + FuriHalRtcLocaleUnitsImperial = 1, /**< Imperial measurement units */ +} FuriHalRtcLocaleUnits; + +typedef enum { + FuriHalRtcLocaleTimeFormat24h = 0, /**< 24-hour format */ + FuriHalRtcLocaleTimeFormat12h = 1, /**< 12-hour format */ +} FuriHalRtcLocaleTimeFormat; + +typedef enum { + FuriHalRtcLocaleDateFormatDMY = 0, /**< Day/Month/Year */ + FuriHalRtcLocaleDateFormatMDY = 1, /**< Month/Day/Year */ + FuriHalRtcLocaleDateFormatYMD = 2, /**< Year/Month/Day */ +} FuriHalRtcLocaleDateFormat; + /** Early initialization */ void furi_hal_rtc_init_early(); -/** Early deinitialization */ +/** Early de-initialization */ void furi_hal_rtc_deinit_early(); /** Initialize RTC subsystem */ void furi_hal_rtc_init(); +/** Get RTC register content + * + * @param[in] reg The register identifier + * + * @return content of the register + */ uint32_t furi_hal_rtc_get_register(FuriHalRtcRegister reg); +/** Set register content + * + * @param[in] reg The register identifier + * @param[in] value The value to store into register + */ void furi_hal_rtc_set_register(FuriHalRtcRegister reg, uint32_t value); +/** Set Log Level value + * + * @param[in] level The level to store + */ void furi_hal_rtc_set_log_level(uint8_t level); +/** Get Log Level value + * + * @return The Log Level value + */ uint8_t furi_hal_rtc_get_log_level(); +/** Set RTC Flag + * + * @param[in] flag The flag to set + */ void furi_hal_rtc_set_flag(FuriHalRtcFlag flag); +/** Reset RTC Flag + * + * @param[in] flag The flag to reset + */ void furi_hal_rtc_reset_flag(FuriHalRtcFlag flag); +/** Check if RTC Flag is set + * + * @param[in] flag The flag to check + * + * @return true if set + */ bool furi_hal_rtc_is_flag_set(FuriHalRtcFlag flag); +/** Set RTC boot mode + * + * @param[in] mode The mode to set + */ void furi_hal_rtc_set_boot_mode(FuriHalRtcBootMode mode); +/** Get RTC boot mode + * + * @return The RTC boot mode. + */ FuriHalRtcBootMode furi_hal_rtc_get_boot_mode(); +/** Set Heap Track mode + * + * @param[in] mode The mode to set + */ void furi_hal_rtc_set_heap_track_mode(FuriHalRtcHeapTrackMode mode); +/** Get RTC Heap Track mode + * + * @return The RTC heap track mode. + */ FuriHalRtcHeapTrackMode furi_hal_rtc_get_heap_track_mode(); +/** Set locale units + * + * @param[in] mode The RTC Locale Units + */ +void furi_hal_rtc_set_locale_units(FuriHalRtcLocaleUnits value); + +/** Get RTC Locale Units + * + * @return The RTC Locale Units. + */ +FuriHalRtcLocaleUnits furi_hal_rtc_get_locale_units(); + +/** Set RTC Locale Time Format + * + * @param[in] value The RTC Locale Time Format + */ +void furi_hal_rtc_set_locale_timeformat(FuriHalRtcLocaleTimeFormat value); + +/** Get RTC Locale Time Format + * + * @return The RTC Locale Time Format. + */ +FuriHalRtcLocaleTimeFormat furi_hal_rtc_get_locale_timeformat(); + +/** Set RTC Locale Date Format + * + * @param[in] value The RTC Locale Date Format + */ +void furi_hal_rtc_set_locale_dateformat(FuriHalRtcLocaleDateFormat value); + +/** Get RTC Locale Date Format + * + * @return The RTC Locale Date Format + */ +FuriHalRtcLocaleDateFormat furi_hal_rtc_get_locale_dateformat(); + +/** Set RTC Date Time + * + * @param datetime The date time to set + */ void furi_hal_rtc_set_datetime(FuriHalRtcDateTime* datetime); +/** Get RTC Date Time + * + * @param datetime The datetime + */ void furi_hal_rtc_get_datetime(FuriHalRtcDateTime* datetime); +/** Validate Date Time + * + * @param datetime The datetime to validate + * + * @return { description_of_the_return_value } + */ bool furi_hal_rtc_validate_datetime(FuriHalRtcDateTime* datetime); +/** Set RTC Fault Data + * + * @param[in] value The value + */ void furi_hal_rtc_set_fault_data(uint32_t value); +/** Get RTC Fault Data + * + * @return RTC Fault Data value + */ uint32_t furi_hal_rtc_get_fault_data(); +/** Set Pin Fails count + * + * @param[in] value The Pin Fails count + */ void furi_hal_rtc_set_pin_fails(uint32_t value); +/** Get Pin Fails count + * + * @return Pin Fails Count + */ uint32_t furi_hal_rtc_get_pin_fails(); +/** Get UNIX Timestamp + * + * @return Unix Timestamp in seconds from UNIX epoch start + */ uint32_t furi_hal_rtc_get_timestamp(); +/** Convert DateTime to UNIX timestamp + * + * @param datetime The datetime + * + * @return UNIX Timestamp in seconds from UNIX epoch start + */ uint32_t furi_hal_rtc_datetime_to_timestamp(FuriHalRtcDateTime* datetime); #ifdef __cplusplus From 94453d91003c38e7ebc946738056cbaefc414f2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Mon, 19 Dec 2022 20:57:44 +0900 Subject: [PATCH 287/824] [FL-3046] Notification: fix recursive speaker acquire #2147 --- applications/services/notification/notification_app.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/services/notification/notification_app.c b/applications/services/notification/notification_app.c index 4f6d42aa0e5..b6579f54769 100644 --- a/applications/services/notification/notification_app.c +++ b/applications/services/notification/notification_app.c @@ -150,7 +150,7 @@ void notification_vibro_off() { } void notification_sound_on(float freq, float volume) { - if(furi_hal_speaker_acquire(30)) { + if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(30)) { furi_hal_speaker_start(freq, volume); } } From 36e15a1352a6aa7068698647b71b7ccd56b8216f Mon Sep 17 00:00:00 2001 From: Konstantin Volkov <72250702+doomwastaken@users.noreply.github.com> Date: Mon, 19 Dec 2022 16:07:23 +0300 Subject: [PATCH 288/824] Doom/Unit_tests flashing proper firmware (#2133) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * doom: should fix all issues, needs review * fixed flash call and added port * increased timeout, full flash wasn't completing * turned serial back * added unit formatting and force flag for overwriting files * testing crash * fixed step names, added release flashing, removed unit_tests from updater tests * changed checkout method, added step validations * removed duplicated tag * fixed styling, stopped relying on shebang lines, removed debug output * moved format to the end, flash_usb_full copies resourses already * awaiting flipper after flashing and step status for file move Co-authored-by: Konstantin Volkov Co-authored-by: あく --- .github/workflows/amap_analyse.yml | 3 +- .github/workflows/build.yml | 5 +- .github/workflows/lint_c.yml | 4 +- .github/workflows/lint_python.yml | 6 ++- .github/workflows/merge_report.yml | 6 ++- .github/workflows/pvs_studio.yml | 5 +- .github/workflows/unit_tests.yml | 79 ++++++++++++++++++++++++------ scripts/testing/await_flipper.py | 2 +- 8 files changed, 85 insertions(+), 25 deletions(-) diff --git a/.github/workflows/amap_analyse.yml b/.github/workflows/amap_analyse.yml index 6231c58867d..1340e4cde91 100644 --- a/.github/workflows/amap_analyse.yml +++ b/.github/workflows/amap_analyse.yml @@ -11,6 +11,7 @@ on: env: TARGETS: f7 + FBT_TOOLCHAIN_PATH: /opt jobs: amap_analyse: @@ -78,7 +79,7 @@ jobs: - name: 'Upload report to DB' run: | - FBT_TOOLCHAIN_PATH=/opt source scripts/toolchain/fbtenv.sh + source scripts/toolchain/fbtenv.sh get_size() { SECTION="$1"; diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 99c272e609f..2de0e57c325 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,6 +12,7 @@ on: env: TARGETS: f7 DEFAULT_TARGET: f7 + FBT_TOOLCHAIN_PATH: /runner/_work jobs: main: @@ -55,7 +56,7 @@ jobs: run: | set -e for TARGET in ${TARGETS}; do - FBT_TOOLCHAIN_PATH=/runner/_work ./fbt TARGET_HW="$(echo "${TARGET}" | sed 's/f//')" \ + ./fbt TARGET_HW="$(echo "${TARGET}" | sed 's/f//')" \ copro_dist updater_package ${{ startsWith(github.ref, 'refs/tags') && 'DEBUG=0 COMPACT=1' || '' }} done @@ -157,6 +158,6 @@ jobs: run: | set -e for TARGET in ${TARGETS}; do - FBT_TOOLCHAIN_PATH=/runner/_work ./fbt TARGET_HW="$(echo "${TARGET}" | sed 's/f//')" \ + ./fbt TARGET_HW="$(echo "${TARGET}" | sed 's/f//')" \ updater_package DEBUG=0 COMPACT=1 done diff --git a/.github/workflows/lint_c.yml b/.github/workflows/lint_c.yml index 71ec24ff659..a6fd5127cda 100644 --- a/.github/workflows/lint_c.yml +++ b/.github/workflows/lint_c.yml @@ -11,6 +11,8 @@ on: env: TARGETS: f7 + FBT_TOOLCHAIN_PATH: /runner/_work + SET_GH_OUTPUT: 1 jobs: lint_c_cpp: @@ -30,7 +32,7 @@ jobs: - name: 'Check code formatting' id: syntax_check - run: SET_GH_OUTPUT=1 FBT_TOOLCHAIN_PATH=/runner/_work ./fbt lint + run: ./fbt lint - name: Report code formatting errors if: failure() && steps.syntax_check.outputs.errors && github.event.pull_request diff --git a/.github/workflows/lint_python.yml b/.github/workflows/lint_python.yml index 44f233db840..66c36064c64 100644 --- a/.github/workflows/lint_python.yml +++ b/.github/workflows/lint_python.yml @@ -9,6 +9,10 @@ on: - '*' pull_request: +env: + FBT_TOOLCHAIN_PATH: /runner/_work + SET_GH_OUTPUT: 1 + jobs: lint_python: runs-on: [self-hosted,FlipperZeroShell] @@ -26,4 +30,4 @@ jobs: ref: ${{ github.event.pull_request.head.sha }} - name: 'Check code formatting' - run: SET_GH_OUTPUT=1 FBT_TOOLCHAIN_PATH=/runner/_work ./fbt lint_py + run: ./fbt lint_py diff --git a/.github/workflows/merge_report.yml b/.github/workflows/merge_report.yml index b6c08905607..13fab0948a7 100644 --- a/.github/workflows/merge_report.yml +++ b/.github/workflows/merge_report.yml @@ -4,6 +4,10 @@ on: push: branches: - dev + +env: + FBT_TOOLCHAIN_PATH: /runner/_work + jobs: merge_report: runs-on: [self-hosted,FlipperZeroShell] @@ -33,7 +37,7 @@ jobs: - name: 'Check ticket and report' run: | - FBT_TOOLCHAIN_PATH=/runner/_work source scripts/toolchain/fbtenv.sh + source scripts/toolchain/fbtenv.sh python3 -m pip install slack_sdk python3 scripts/merge_report_qa.py \ ${{ secrets.QA_REPORT_SLACK_TOKEN }} \ diff --git a/.github/workflows/pvs_studio.yml b/.github/workflows/pvs_studio.yml index 5473f19f067..5bb04afcbc5 100644 --- a/.github/workflows/pvs_studio.yml +++ b/.github/workflows/pvs_studio.yml @@ -12,6 +12,7 @@ on: env: TARGETS: f7 DEFAULT_TARGET: f7 + FBT_TOOLCHAIN_PATH: /runner/_work jobs: analyse_c_cpp: @@ -49,11 +50,11 @@ jobs: - name: 'Generate compile_comands.json' run: | - FBT_TOOLCHAIN_PATH=/runner/_work ./fbt COMPACT=1 version_json proto_ver icons firmware_cdb dolphin_internal dolphin_blocking _fap_icons + ./fbt COMPACT=1 version_json proto_ver icons firmware_cdb dolphin_internal dolphin_blocking _fap_icons - name: 'Static code analysis' run: | - FBT_TOOLCHAIN_PATH=/runner/_work source scripts/toolchain/fbtenv.sh + source scripts/toolchain/fbtenv.sh pvs-studio-analyzer credentials ${{ secrets.PVS_STUDIO_CREDENTIALS }} pvs-studio-analyzer analyze \ @.pvsoptions \ diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 308ec5929d7..361b647f765 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -6,6 +6,7 @@ on: env: TARGETS: f7 DEFAULT_TARGET: f7 + FBT_TOOLCHAIN_PATH: /opt jobs: run_units_on_test_bench: @@ -28,35 +29,81 @@ jobs: run: | echo "flipper=/dev/ttyACM0" >> $GITHUB_OUTPUT + - name: 'Flashing target firmware' + id: first_full_flash + run: | + ./fbt flash_usb_full PORT=${{steps.device.outputs.flipper}} FORCE=1 + source scripts/toolchain/fbtenv.sh + python3 scripts/testing/await_flipper.py ${{steps.device.outputs.flipper}} + + - name: 'Validating updater' + id: second_full_flash + if: success() + run: | + ./fbt flash_usb_full PORT=${{steps.device.outputs.flipper}} FORCE=1 + source scripts/toolchain/fbtenv.sh + python3 scripts/testing/await_flipper.py ${{steps.device.outputs.flipper}} + - name: 'Flash unit tests firmware' id: flashing + if: success() run: | - FBT_TOOLCHAIN_PATH=/opt ./fbt flash OPENOCD_ADAPTER_SERIAL=2A0906016415303030303032 FIRMWARE_APP_SET=unit_tests FORCE=1 + ./fbt flash OPENOCD_ADAPTER_SERIAL=2A0906016415303030303032 FIRMWARE_APP_SET=unit_tests FORCE=1 - name: 'Wait for flipper to finish updating' id: connect if: steps.flashing.outcome == 'success' run: | - . scripts/toolchain/fbtenv.sh - ./scripts/testing/await_flipper.py ${{steps.device.outputs.flipper}} - - - name: 'Format flipper SD card' - id: format - if: steps.connect.outcome == 'success' - run: | - . scripts/toolchain/fbtenv.sh - ./scripts/storage.py -p ${{steps.device.outputs.flipper}} format_ext + source scripts/toolchain/fbtenv.sh + python3 scripts/testing/await_flipper.py ${{steps.device.outputs.flipper}} - name: 'Copy assets and unit tests data to flipper' id: copy - if: steps.format.outcome == 'success' + if: steps.connect.outcome == 'success' run: | - . scripts/toolchain/fbtenv.sh - ./scripts/storage.py -p ${{steps.device.outputs.flipper}} send assets/resources /ext - ./scripts/storage.py -p ${{steps.device.outputs.flipper}} send assets/unit_tests /ext/unit_tests + source scripts/toolchain/fbtenv.sh + python3 scripts/storage.py -p ${{steps.device.outputs.flipper}} -f send assets/unit_tests /ext/unit_tests - name: 'Run units and validate results' if: steps.copy.outcome == 'success' run: | - . scripts/toolchain/fbtenv.sh - ./scripts/testing/units.py ${{steps.device.outputs.flipper}} + source scripts/toolchain/fbtenv.sh + python3 scripts/testing/units.py ${{steps.device.outputs.flipper}} + + - name: 'Get last release tag' + id: release_tag + if: success() + run: | + echo "tag=$(git tag -l --sort=-version:refname | grep -v "rc\|RC" | head -1)" >> $GITHUB_OUTPUT + + - name: 'Decontaminate previous build leftovers' + if: success() + run: | + if [ -d .git ]; then + git submodule status || git checkout "$(git rev-list --max-parents=0 HEAD | tail -n 1)" + fi + + - name: 'Checkout latest release' + uses: actions/checkout@v3 + if: success() + with: + fetch-depth: 0 + ref: ${{ steps.release_tag.outputs.tag }} + + - name: 'Flash last release' + if: success() + run: | + ./fbt flash OPENOCD_ADAPTER_SERIAL=2A0906016415303030303032 FIRMWARE_APP_SET=unit_tests FORCE=1 + + - name: 'Wait for flipper to finish updating' + if: success() + run: | + source scripts/toolchain/fbtenv.sh + python3 scripts/testing/await_flipper.py ${{steps.device.outputs.flipper}} + + - name: 'Format flipper SD card' + id: format + if: success() + run: | + source scripts/toolchain/fbtenv.sh + python3 scripts/storage.py -p ${{steps.device.outputs.flipper}} format_ext diff --git a/scripts/testing/await_flipper.py b/scripts/testing/await_flipper.py index 1f0d161949f..efae6765d07 100755 --- a/scripts/testing/await_flipper.py +++ b/scripts/testing/await_flipper.py @@ -24,7 +24,7 @@ def flp_serial_by_name(flp_name): return "" -UPDATE_TIMEOUT = 30 +UPDATE_TIMEOUT = 60 def main(): From 5d18b189ec8c3f6dfbbb0224634110ef4c4baf93 Mon Sep 17 00:00:00 2001 From: Max Andreev Date: Mon, 19 Dec 2022 18:28:53 +0300 Subject: [PATCH 289/824] Run map file analyser through repository dispatch (#2148) * Add ripository dispatch * Fix mistakes --- .github/workflows/amap_analyse.yml | 40 +++--------------------------- .github/workflows/build.yml | 8 ++++++ 2 files changed, 12 insertions(+), 36 deletions(-) diff --git a/.github/workflows/amap_analyse.yml b/.github/workflows/amap_analyse.yml index 1340e4cde91..12155e71af8 100644 --- a/.github/workflows/amap_analyse.yml +++ b/.github/workflows/amap_analyse.yml @@ -1,13 +1,8 @@ name: 'Analyze .map file with Amap' on: - push: - branches: - - dev - - "release*" - tags: - - '*' - pull_request: + repository_dispatch: + types: [make_map_analyse] env: TARGETS: f7 @@ -15,24 +10,9 @@ env: jobs: amap_analyse: - if: ${{ !github.event.pull_request.head.repo.fork }} runs-on: [self-hosted,FlipperZeroMacShell] - timeout-minutes: 15 + timeout-minutes: 5 steps: - - name: 'Wait Build workflow' - uses: fountainhead/action-wait-for-check@v1.0.0 - id: wait-for-build - with: - token: ${{ secrets.GITHUB_TOKEN }} - checkName: 'main' - ref: ${{ github.event.pull_request.head.sha || github.sha }} - intervalSeconds: 20 - - - name: 'Check Build workflow status' - if: steps.wait-for-build.outputs.conclusion == 'failure' - run: | - exit 1 - - name: 'Decontaminate previous build leftovers' run: | if [ -d .git ]; then @@ -43,18 +23,6 @@ jobs: uses: actions/checkout@v3 with: fetch-depth: 0 - ref: ${{ github.event.pull_request.head.sha }} - - - name: 'Get commit details' - run: | - if [[ ${{ github.event_name }} == 'pull_request' ]]; then - TYPE="pull" - elif [[ "${{ github.ref }}" == "refs/tags/"* ]]; then - TYPE="tag" - else - TYPE="other" - fi - python3 scripts/get_env.py "--event_file=${{ github.event_path }}" "--type=$TYPE" - name: 'Make artifacts directory' run: | @@ -69,7 +37,7 @@ jobs: chmod 600 ./deploy_key; rsync -avzP \ -e 'ssh -p ${{ secrets.RSYNC_DEPLOY_PORT }} -i ./deploy_key' \ - ${{ secrets.RSYNC_DEPLOY_USER }}@${{ secrets.RSYNC_DEPLOY_HOST }}:"${{ secrets.RSYNC_DEPLOY_BASE_PATH }}${BRANCH_NAME}/" artifacts/; + ${{ secrets.RSYNC_DEPLOY_USER }}@${{ secrets.RSYNC_DEPLOY_HOST }}:"${{ secrets.RSYNC_DEPLOY_BASE_PATH }}${{ github.event.client_payload.branch_name }}/" artifacts/; rm ./deploy_key; - name: 'Make .map file analyze' diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2de0e57c325..e81d325e914 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -98,6 +98,14 @@ jobs: artifacts/ ${{ secrets.RSYNC_DEPLOY_USER }}@${{ secrets.RSYNC_DEPLOY_HOST }}:"${{ secrets.RSYNC_DEPLOY_BASE_PATH }}${BRANCH_NAME}/"; rm ./deploy_key; + - name: 'Trigger map file analyser' + if: ${{ !github.event.pull_request.head.repo.fork }} + uses: peter-evans/repository-dispatch@v2 + with: + token: ${{ secrets.GITHUB_TOKEN }} + event-type: make_map_analyse + client-payload: '{"branch_name": "${{steps.names.outputs.branch_name}}"}' + - name: 'Trigger update server reindex' if: ${{ !github.event.pull_request.head.repo.fork }} run: curl -X POST -F 'key=${{ secrets.REINDEX_KEY }}' ${{ secrets.REINDEX_URL }} From ef7052fbad5e776c09ca6df40517d013e02476d6 Mon Sep 17 00:00:00 2001 From: Max Andreev Date: Mon, 19 Dec 2022 19:42:06 +0300 Subject: [PATCH 290/824] Fix new amap workflow (#2151) * Fix amap workflow * get_env.py * Fix amap_analyse.yml --- .github/workflows/amap_analyse.yml | 14 ++++++++++++-- .github/workflows/build.yml | 14 ++++++++++++-- scripts/get_env.py | 9 +++++++-- 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/.github/workflows/amap_analyse.yml b/.github/workflows/amap_analyse.yml index 12155e71af8..7e78dc9801c 100644 --- a/.github/workflows/amap_analyse.yml +++ b/.github/workflows/amap_analyse.yml @@ -24,6 +24,16 @@ jobs: with: fetch-depth: 0 + - name: 'Get commit details' + run: | + export COMMIT_HASH=${{ github.event.client_payload.commit_hash }} + export COMMIT_MSG=${{ github.event.client_payload.commit_msg }} + export BRANCH_NAME=${{ github.event.client_payload.branch_name }} + if [[ ${{ github.event.client_payload.event_type }} == "pr" ]]; then + export PULL_ID=${{ github.event.client_payload.pull_id }} + export PULL_NAME=${{ github.event.client_payload.pull_name }} + fi + - name: 'Make artifacts directory' run: | rm -rf artifacts @@ -43,7 +53,7 @@ jobs: - name: 'Make .map file analyze' run: | cd artifacts/ - /Applications/amap/Contents/MacOS/amap -f "flipper-z-f7-firmware-${SUFFIX}.elf.map" + /Applications/amap/Contents/MacOS/amap -f flipper-z-f7-firmware-${{ github.event.client_payload.suffix }}.elf.map - name: 'Upload report to DB' run: | @@ -67,5 +77,5 @@ jobs: ${{ secrets.AMAP_MARIADB_HOST }} \ ${{ secrets.AMAP_MARIADB_PORT }} \ ${{ secrets.AMAP_MARIADB_DATABASE }} \ - artifacts/flipper-z-f7-firmware-$SUFFIX.elf.map.all + artifacts/flipper-z-f7-firmware-${{ github.event.client_payload.suffix }}.elf.map.all diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e81d325e914..52d4626d8a4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -99,12 +99,22 @@ jobs: rm ./deploy_key; - name: 'Trigger map file analyser' - if: ${{ !github.event.pull_request.head.repo.fork }} + if: ${{ (github.event_name != 'pull_request') + && !github.event.pull_request.head.repo.fork }} + uses: peter-evans/repository-dispatch@v2 + with: + token: ${{ secrets.GITHUB_TOKEN }} + event-type: make_map_analyse + client-payload: '{"branch_name": "${{steps.names.outputs.branch_name}}", "commit_hash": "${{steps.names.outputs.commit_hash}}", "commit_msg": "${{steps.names.outputs.commit_msg}}", "event_type": "push"}' + + - name: 'Trigger map file analyser' + if: ${{ (github.event_name == 'pull_request') + && !github.event.pull_request.head.repo.fork }} uses: peter-evans/repository-dispatch@v2 with: token: ${{ secrets.GITHUB_TOKEN }} event-type: make_map_analyse - client-payload: '{"branch_name": "${{steps.names.outputs.branch_name}}"}' + client-payload: '{"branch_name": "${{steps.names.outputs.branch_name}}", "commit_hash": "${{steps.names.outputs.commit_hash}}", "commit_msg": "${{steps.names.outputs.commit_msg}}", "pull_id": "${{steps.names.outputs.pull_id}}", "pull_name": "${{steps.names.outputs.pull_name}}", "event_type": "pr"}' - name: 'Trigger update server reindex' if: ${{ !github.event.pull_request.head.repo.fork }} diff --git a/scripts/get_env.py b/scripts/get_env.py index e2da6eda5c7..c87de47a311 100644 --- a/scripts/get_env.py +++ b/scripts/get_env.py @@ -89,13 +89,18 @@ def add_envs(data, gh_env_file, gh_out_file, args): add_env("BRANCH_NAME", data["branch_name"], gh_env_file) add_env("DIST_SUFFIX", data["suffix"], gh_env_file) add_env("WORKFLOW_BRANCH_OR_TAG", data["branch_name"], gh_env_file) - add_set_output_var("branch_name", data["branch_name"], gh_out_file) + add_set_output_var("commit_msg", data["commit_comment"], gh_out_file) + add_set_output_var("commit_hash", data["commit_hash"], gh_out_file) add_set_output_var("commit_sha", data["commit_sha"], gh_out_file) - add_set_output_var("default_target", os.getenv("DEFAULT_TARGET"), gh_out_file) add_set_output_var("suffix", data["suffix"], gh_out_file) + add_set_output_var("branch_name", data["branch_name"], gh_out_file) + add_set_output_var("dist_suffix", data["suffix"], gh_out_file) + add_set_output_var("default_target", os.getenv("DEFAULT_TARGET"), gh_out_file) if args.type == "pull": add_env("PULL_ID", data["pull_id"], gh_env_file) add_env("PULL_NAME", data["pull_name"], gh_env_file) + add_set_output_var("pull_id", data["pull_id"], gh_out_file) + add_set_output_var("pull_name", data["pull_name"], gh_out_file) def main(): From 361ca8b7501b27e29403a0811bf287edaeee5792 Mon Sep 17 00:00:00 2001 From: Max Andreev Date: Mon, 19 Dec 2022 20:09:19 +0300 Subject: [PATCH 291/824] Rollback new amap workflow (#2152) --- .github/workflows/amap_analyse.yml | 46 ++++++++++++++++++++++-------- .github/workflows/build.yml | 18 ------------ scripts/get_env.py | 9 ++---- 3 files changed, 36 insertions(+), 37 deletions(-) diff --git a/.github/workflows/amap_analyse.yml b/.github/workflows/amap_analyse.yml index 7e78dc9801c..1340e4cde91 100644 --- a/.github/workflows/amap_analyse.yml +++ b/.github/workflows/amap_analyse.yml @@ -1,8 +1,13 @@ name: 'Analyze .map file with Amap' on: - repository_dispatch: - types: [make_map_analyse] + push: + branches: + - dev + - "release*" + tags: + - '*' + pull_request: env: TARGETS: f7 @@ -10,9 +15,24 @@ env: jobs: amap_analyse: + if: ${{ !github.event.pull_request.head.repo.fork }} runs-on: [self-hosted,FlipperZeroMacShell] - timeout-minutes: 5 + timeout-minutes: 15 steps: + - name: 'Wait Build workflow' + uses: fountainhead/action-wait-for-check@v1.0.0 + id: wait-for-build + with: + token: ${{ secrets.GITHUB_TOKEN }} + checkName: 'main' + ref: ${{ github.event.pull_request.head.sha || github.sha }} + intervalSeconds: 20 + + - name: 'Check Build workflow status' + if: steps.wait-for-build.outputs.conclusion == 'failure' + run: | + exit 1 + - name: 'Decontaminate previous build leftovers' run: | if [ -d .git ]; then @@ -23,16 +43,18 @@ jobs: uses: actions/checkout@v3 with: fetch-depth: 0 + ref: ${{ github.event.pull_request.head.sha }} - name: 'Get commit details' run: | - export COMMIT_HASH=${{ github.event.client_payload.commit_hash }} - export COMMIT_MSG=${{ github.event.client_payload.commit_msg }} - export BRANCH_NAME=${{ github.event.client_payload.branch_name }} - if [[ ${{ github.event.client_payload.event_type }} == "pr" ]]; then - export PULL_ID=${{ github.event.client_payload.pull_id }} - export PULL_NAME=${{ github.event.client_payload.pull_name }} + if [[ ${{ github.event_name }} == 'pull_request' ]]; then + TYPE="pull" + elif [[ "${{ github.ref }}" == "refs/tags/"* ]]; then + TYPE="tag" + else + TYPE="other" fi + python3 scripts/get_env.py "--event_file=${{ github.event_path }}" "--type=$TYPE" - name: 'Make artifacts directory' run: | @@ -47,13 +69,13 @@ jobs: chmod 600 ./deploy_key; rsync -avzP \ -e 'ssh -p ${{ secrets.RSYNC_DEPLOY_PORT }} -i ./deploy_key' \ - ${{ secrets.RSYNC_DEPLOY_USER }}@${{ secrets.RSYNC_DEPLOY_HOST }}:"${{ secrets.RSYNC_DEPLOY_BASE_PATH }}${{ github.event.client_payload.branch_name }}/" artifacts/; + ${{ secrets.RSYNC_DEPLOY_USER }}@${{ secrets.RSYNC_DEPLOY_HOST }}:"${{ secrets.RSYNC_DEPLOY_BASE_PATH }}${BRANCH_NAME}/" artifacts/; rm ./deploy_key; - name: 'Make .map file analyze' run: | cd artifacts/ - /Applications/amap/Contents/MacOS/amap -f flipper-z-f7-firmware-${{ github.event.client_payload.suffix }}.elf.map + /Applications/amap/Contents/MacOS/amap -f "flipper-z-f7-firmware-${SUFFIX}.elf.map" - name: 'Upload report to DB' run: | @@ -77,5 +99,5 @@ jobs: ${{ secrets.AMAP_MARIADB_HOST }} \ ${{ secrets.AMAP_MARIADB_PORT }} \ ${{ secrets.AMAP_MARIADB_DATABASE }} \ - artifacts/flipper-z-f7-firmware-${{ github.event.client_payload.suffix }}.elf.map.all + artifacts/flipper-z-f7-firmware-$SUFFIX.elf.map.all diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 52d4626d8a4..2de0e57c325 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -98,24 +98,6 @@ jobs: artifacts/ ${{ secrets.RSYNC_DEPLOY_USER }}@${{ secrets.RSYNC_DEPLOY_HOST }}:"${{ secrets.RSYNC_DEPLOY_BASE_PATH }}${BRANCH_NAME}/"; rm ./deploy_key; - - name: 'Trigger map file analyser' - if: ${{ (github.event_name != 'pull_request') - && !github.event.pull_request.head.repo.fork }} - uses: peter-evans/repository-dispatch@v2 - with: - token: ${{ secrets.GITHUB_TOKEN }} - event-type: make_map_analyse - client-payload: '{"branch_name": "${{steps.names.outputs.branch_name}}", "commit_hash": "${{steps.names.outputs.commit_hash}}", "commit_msg": "${{steps.names.outputs.commit_msg}}", "event_type": "push"}' - - - name: 'Trigger map file analyser' - if: ${{ (github.event_name == 'pull_request') - && !github.event.pull_request.head.repo.fork }} - uses: peter-evans/repository-dispatch@v2 - with: - token: ${{ secrets.GITHUB_TOKEN }} - event-type: make_map_analyse - client-payload: '{"branch_name": "${{steps.names.outputs.branch_name}}", "commit_hash": "${{steps.names.outputs.commit_hash}}", "commit_msg": "${{steps.names.outputs.commit_msg}}", "pull_id": "${{steps.names.outputs.pull_id}}", "pull_name": "${{steps.names.outputs.pull_name}}", "event_type": "pr"}' - - name: 'Trigger update server reindex' if: ${{ !github.event.pull_request.head.repo.fork }} run: curl -X POST -F 'key=${{ secrets.REINDEX_KEY }}' ${{ secrets.REINDEX_URL }} diff --git a/scripts/get_env.py b/scripts/get_env.py index c87de47a311..e2da6eda5c7 100644 --- a/scripts/get_env.py +++ b/scripts/get_env.py @@ -89,18 +89,13 @@ def add_envs(data, gh_env_file, gh_out_file, args): add_env("BRANCH_NAME", data["branch_name"], gh_env_file) add_env("DIST_SUFFIX", data["suffix"], gh_env_file) add_env("WORKFLOW_BRANCH_OR_TAG", data["branch_name"], gh_env_file) - add_set_output_var("commit_msg", data["commit_comment"], gh_out_file) - add_set_output_var("commit_hash", data["commit_hash"], gh_out_file) - add_set_output_var("commit_sha", data["commit_sha"], gh_out_file) - add_set_output_var("suffix", data["suffix"], gh_out_file) add_set_output_var("branch_name", data["branch_name"], gh_out_file) - add_set_output_var("dist_suffix", data["suffix"], gh_out_file) + add_set_output_var("commit_sha", data["commit_sha"], gh_out_file) add_set_output_var("default_target", os.getenv("DEFAULT_TARGET"), gh_out_file) + add_set_output_var("suffix", data["suffix"], gh_out_file) if args.type == "pull": add_env("PULL_ID", data["pull_id"], gh_env_file) add_env("PULL_NAME", data["pull_name"], gh_env_file) - add_set_output_var("pull_id", data["pull_id"], gh_out_file) - add_set_output_var("pull_name", data["pull_name"], gh_out_file) def main(): From 84ba2690a51866ee203fa725ca143c843e5bd760 Mon Sep 17 00:00:00 2001 From: Konstantin Volkov <72250702+doomwastaken@users.noreply.github.com> Date: Mon, 19 Dec 2022 21:14:44 +0300 Subject: [PATCH 292/824] GitHub: update unit_tests workflow steps to always re-flash device (#2150) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Konstantin Volkov Co-authored-by: あく --- .github/workflows/unit_tests.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 361b647f765..eb687b6c921 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -72,12 +72,12 @@ jobs: - name: 'Get last release tag' id: release_tag - if: success() + if: always() run: | echo "tag=$(git tag -l --sort=-version:refname | grep -v "rc\|RC" | head -1)" >> $GITHUB_OUTPUT - name: 'Decontaminate previous build leftovers' - if: success() + if: always() run: | if [ -d .git ]; then git submodule status || git checkout "$(git rev-list --max-parents=0 HEAD | tail -n 1)" @@ -85,25 +85,25 @@ jobs: - name: 'Checkout latest release' uses: actions/checkout@v3 - if: success() + if: always() with: fetch-depth: 0 ref: ${{ steps.release_tag.outputs.tag }} - name: 'Flash last release' - if: success() + if: always() run: | ./fbt flash OPENOCD_ADAPTER_SERIAL=2A0906016415303030303032 FIRMWARE_APP_SET=unit_tests FORCE=1 - name: 'Wait for flipper to finish updating' - if: success() + if: always() run: | source scripts/toolchain/fbtenv.sh python3 scripts/testing/await_flipper.py ${{steps.device.outputs.flipper}} - name: 'Format flipper SD card' id: format - if: success() + if: always() run: | source scripts/toolchain/fbtenv.sh python3 scripts/storage.py -p ${{steps.device.outputs.flipper}} format_ext From a81a5ca57c03cedcc03c29389540a45eeee84926 Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Mon, 19 Dec 2022 23:22:57 +0400 Subject: [PATCH 293/824] [FL-3052] WS: add choice fahrenheit/celsius (#2149) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * WS: add choice fahrenheit/celsius * WS: fix syntax Co-authored-by: あく --- .../helpers/weather_station_types.h | 2 +- .../protocols/ambient_weather.c | 4 +-- .../weather_station/protocols/infactory.c | 4 +-- .../weather_station/protocols/ws_generic.c | 4 --- .../weather_station/protocols/ws_generic.h | 3 +- .../views/weather_station_receiver_info.c | 32 +++++++++++++++---- 6 files changed, 32 insertions(+), 17 deletions(-) diff --git a/applications/plugins/weather_station/helpers/weather_station_types.h b/applications/plugins/weather_station/helpers/weather_station_types.h index 16c195fa175..d512251f172 100644 --- a/applications/plugins/weather_station/helpers/weather_station_types.h +++ b/applications/plugins/weather_station/helpers/weather_station_types.h @@ -3,7 +3,7 @@ #include #include -#define WS_VERSION_APP "0.6" +#define WS_VERSION_APP "0.6.1" #define WS_DEVELOPED "SkorP" #define WS_GITHUB "https://github.com/flipperdevices/flipperzero-firmware" diff --git a/applications/plugins/weather_station/protocols/ambient_weather.c b/applications/plugins/weather_station/protocols/ambient_weather.c index 5ae22b790b2..e3c85f40ba4 100644 --- a/applications/plugins/weather_station/protocols/ambient_weather.c +++ b/applications/plugins/weather_station/protocols/ambient_weather.c @@ -134,8 +134,8 @@ static void ws_protocol_ambient_weather_remote_controller(WSBlockGeneric* instan instance->id = (instance->data >> 32) & 0xFF; instance->battery_low = (instance->data >> 31) & 1; instance->channel = ((instance->data >> 28) & 0x07) + 1; - instance->temp = ws_block_generic_fahrenheit_to_celsius( - ((float)((instance->data >> 16) & 0x0FFF) - 400.0f) / 10.0f); + instance->temp = + locale_fahrenheit_to_celsius(((float)((instance->data >> 16) & 0x0FFF) - 400.0f) / 10.0f); instance->humidity = (instance->data >> 8) & 0xFF; instance->btn = WS_NO_BTN; diff --git a/applications/plugins/weather_station/protocols/infactory.c b/applications/plugins/weather_station/protocols/infactory.c index 2d444d9814f..53a656d738f 100644 --- a/applications/plugins/weather_station/protocols/infactory.c +++ b/applications/plugins/weather_station/protocols/infactory.c @@ -143,8 +143,8 @@ static void ws_protocol_infactory_remote_controller(WSBlockGeneric* instance) { instance->id = instance->data >> 32; instance->battery_low = (instance->data >> 26) & 1; instance->btn = WS_NO_BTN; - instance->temp = ws_block_generic_fahrenheit_to_celsius( - ((float)((instance->data >> 12) & 0x0FFF) - 900.0f) / 10.0f); + instance->temp = + locale_fahrenheit_to_celsius(((float)((instance->data >> 12) & 0x0FFF) - 900.0f) / 10.0f); instance->humidity = (((instance->data >> 8) & 0x0F) * 10) + ((instance->data >> 4) & 0x0F); // BCD, 'A0'=100%rH instance->channel = instance->data & 0x03; diff --git a/applications/plugins/weather_station/protocols/ws_generic.c b/applications/plugins/weather_station/protocols/ws_generic.c index cd5bf65570b..dcacda2e438 100644 --- a/applications/plugins/weather_station/protocols/ws_generic.c +++ b/applications/plugins/weather_station/protocols/ws_generic.c @@ -209,7 +209,3 @@ bool ws_block_generic_deserialize(WSBlockGeneric* instance, FlipperFormat* flipp return res; } - -float ws_block_generic_fahrenheit_to_celsius(float fahrenheit) { - return (fahrenheit - 32.0f) / 1.8f; -} \ No newline at end of file diff --git a/applications/plugins/weather_station/protocols/ws_generic.h b/applications/plugins/weather_station/protocols/ws_generic.h index 657f8a1fc96..8e6e061adbe 100644 --- a/applications/plugins/weather_station/protocols/ws_generic.h +++ b/applications/plugins/weather_station/protocols/ws_generic.h @@ -8,6 +8,7 @@ #include "furi.h" #include "furi_hal.h" #include +#include #ifdef __cplusplus extern "C" { @@ -62,8 +63,6 @@ bool ws_block_generic_serialize( */ bool ws_block_generic_deserialize(WSBlockGeneric* instance, FlipperFormat* flipper_format); -float ws_block_generic_fahrenheit_to_celsius(float fahrenheit); - #ifdef __cplusplus } #endif \ No newline at end of file diff --git a/applications/plugins/weather_station/views/weather_station_receiver_info.c b/applications/plugins/weather_station/views/weather_station_receiver_info.c index 05809921708..55d239aad22 100644 --- a/applications/plugins/weather_station/views/weather_station_receiver_info.c +++ b/applications/plugins/weather_station/views/weather_station_receiver_info.c @@ -81,13 +81,33 @@ void ws_view_receiver_info_draw(Canvas* canvas, WSReceiverInfoModel* model) { if(model->generic->temp != WS_NO_TEMPERATURE) { canvas_draw_icon(canvas, 6, 43, &I_Therm_7x16); - snprintf(buffer, sizeof(buffer), "%3.1f C", (double)model->generic->temp); - uint8_t temp_x1 = 47; - uint8_t temp_x2 = 38; - if(model->generic->temp < -9.0) { - temp_x1 = 49; - temp_x2 = 40; + + uint8_t temp_x1 = 0; + uint8_t temp_x2 = 0; + if(furi_hal_rtc_get_locale_units() == FuriHalRtcLocaleUnitsMetric) { + snprintf(buffer, sizeof(buffer), "%3.1f C", (double)model->generic->temp); + if(model->generic->temp < -9.0f) { + temp_x1 = 49; + temp_x2 = 40; + } else { + temp_x1 = 47; + temp_x2 = 38; + } + } else { + snprintf( + buffer, + sizeof(buffer), + "%3.1f F", + (double)locale_celsius_to_fahrenheit(model->generic->temp)); + if((model->generic->temp < -27.77f) || (model->generic->temp > 37.77f)) { + temp_x1 = 50; + temp_x2 = 42; + } else { + temp_x1 = 48; + temp_x2 = 40; + } } + canvas_draw_str_aligned(canvas, temp_x1, 47, AlignRight, AlignTop, buffer); canvas_draw_circle(canvas, temp_x2, 46, 1); } From fa87216a1e98a9a6a7ad3c25908ede321fe2af1c Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Mon, 19 Dec 2022 21:57:48 +0200 Subject: [PATCH 294/824] [FL-2970] Untangle NFC_APP_FOLDER from nfc_device (#2124) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Untangle NFC_APP_FOLDER from nfc_device * Make the folder an internal part of the nfc device struct * Move the NFC folder definition to the nfc allocator * Move the NFC folder definition to the allocator in the nfc magic FAP * Replace furi_string_printf with furi_string_set for folder allocation and handle the cases when folder has no slashes * Build and allocation fixes Co-authored-by: あく --- applications/main/nfc/nfc.c | 1 + applications/main/nfc/nfc_i.h | 1 + applications/plugins/nfc_magic/nfc_magic.c | 1 + applications/plugins/nfc_magic/nfc_magic_i.h | 2 + lib/nfc/nfc_device.c | 58 ++++++++++++++++---- lib/nfc/nfc_device.h | 2 +- 6 files changed, 53 insertions(+), 12 deletions(-) diff --git a/applications/main/nfc/nfc.c b/applications/main/nfc/nfc.c index 0dd071bc48e..6e6dc4dcce9 100644 --- a/applications/main/nfc/nfc.c +++ b/applications/main/nfc/nfc.c @@ -46,6 +46,7 @@ Nfc* nfc_alloc() { // Nfc device nfc->dev = nfc_device_alloc(); + furi_string_set(nfc->dev->folder, NFC_APP_FOLDER); // Open GUI record nfc->gui = furi_record_open(RECORD_GUI); diff --git a/applications/main/nfc/nfc_i.h b/applications/main/nfc/nfc_i.h index d49b8476649..f7e48990292 100644 --- a/applications/main/nfc/nfc_i.h +++ b/applications/main/nfc/nfc_i.h @@ -44,6 +44,7 @@ ARRAY_DEF(MfClassicUserKeys, char*, M_PTR_OPLIST); #define NFC_TEXT_STORE_SIZE 128 +#define NFC_APP_FOLDER ANY_PATH("nfc") typedef enum { NfcRpcStateIdle, diff --git a/applications/plugins/nfc_magic/nfc_magic.c b/applications/plugins/nfc_magic/nfc_magic.c index 38eecba6a82..e4e0ffde08a 100644 --- a/applications/plugins/nfc_magic/nfc_magic.c +++ b/applications/plugins/nfc_magic/nfc_magic.c @@ -49,6 +49,7 @@ NfcMagic* nfc_magic_alloc() { // Nfc device nfc_magic->nfc_dev = nfc_device_alloc(); + furi_string_set(nfc_magic->nfc_dev->folder, NFC_APP_FOLDER); // Open GUI record nfc_magic->gui = furi_record_open(RECORD_GUI); diff --git a/applications/plugins/nfc_magic/nfc_magic_i.h b/applications/plugins/nfc_magic/nfc_magic_i.h index 01b30082663..378912e5b09 100644 --- a/applications/plugins/nfc_magic/nfc_magic_i.h +++ b/applications/plugins/nfc_magic/nfc_magic_i.h @@ -27,6 +27,8 @@ #include #include "nfc_magic_icons.h" +#define NFC_APP_FOLDER ANY_PATH("nfc") + enum NfcMagicCustomEvent { // Reserve first 100 events for button types and indexes, starting from 0 NfcMagicCustomEventReserved = 100, diff --git a/lib/nfc/nfc_device.c b/lib/nfc/nfc_device.c index 3ab10a4f68f..49eebc37ddc 100644 --- a/lib/nfc/nfc_device.c +++ b/lib/nfc/nfc_device.c @@ -27,6 +27,7 @@ NfcDevice* nfc_device_alloc() { nfc_dev->dialogs = furi_record_open(RECORD_DIALOGS); nfc_dev->load_path = furi_string_alloc(); nfc_dev->dev_data.parsed_data = furi_string_alloc(); + nfc_dev->folder = furi_string_alloc(); // Rename cache folder name for backward compatibility if(storage_common_stat(nfc_dev->storage, "/ext/nfc/cache", NULL) == FSE_OK) { @@ -42,6 +43,7 @@ void nfc_device_free(NfcDevice* nfc_dev) { furi_record_close(RECORD_DIALOGS); furi_string_free(nfc_dev->load_path); furi_string_free(nfc_dev->dev_data.parsed_data); + furi_string_free(nfc_dev->folder); free(nfc_dev); } @@ -1018,6 +1020,16 @@ static void nfc_device_get_shadow_path(FuriString* orig_path, FuriString* shadow furi_string_cat_printf(shadow_path, "%s", NFC_APP_SHADOW_EXTENSION); } +static void nfc_device_get_folder_from_path(FuriString* path, FuriString* folder) { + size_t last_slash = furi_string_search_rchar(path, '/'); + if(last_slash == FURI_STRING_FAILURE) { + // No slashes in the path, treat the whole path as a folder + furi_string_set(folder, path); + } else { + furi_string_set_n(folder, path, 0, last_slash); + } +} + bool nfc_device_save(NfcDevice* dev, const char* dev_name) { furi_assert(dev); @@ -1028,10 +1040,19 @@ bool nfc_device_save(NfcDevice* dev, const char* dev_name) { temp_str = furi_string_alloc(); do { - // Create nfc directory if necessary - if(!storage_simply_mkdir(dev->storage, NFC_APP_FOLDER)) break; + // Create directory if necessary + FuriString* folder = furi_string_alloc(); + // Get folder from filename (filename is in the form of "folder/filename.nfc", so the folder is "folder/") + furi_string_set(temp_str, dev_name); + // Get folder from filename + nfc_device_get_folder_from_path(temp_str, folder); + FURI_LOG_I("Nfc", "Saving to folder %s", furi_string_get_cstr(folder)); + if(!storage_simply_mkdir(dev->storage, furi_string_get_cstr(folder))) { + FURI_LOG_E("Nfc", "Failed to create folder %s", furi_string_get_cstr(folder)); + break; + } + furi_string_free(folder); // First remove nfc device file if it was saved - furi_string_printf(temp_str, "%s", dev_name); // Open file if(!flipper_format_file_open_always(file, furi_string_get_cstr(temp_str))) break; // Write header @@ -1199,10 +1220,9 @@ bool nfc_device_load(NfcDevice* dev, const char* file_path, bool show_dialog) { bool nfc_file_select(NfcDevice* dev) { furi_assert(dev); + const char* folder = furi_string_get_cstr(dev->folder); // Input events and views are managed by file_browser - FuriString* nfc_app_folder; - nfc_app_folder = furi_string_alloc_set(NFC_APP_FOLDER); const DialogsFileBrowserOptions browser_options = { .extension = NFC_APP_EXTENSION, @@ -1212,13 +1232,12 @@ bool nfc_file_select(NfcDevice* dev) { .hide_ext = true, .item_loader_callback = NULL, .item_loader_context = NULL, - .base_path = NFC_APP_FOLDER, + .base_path = folder, }; bool res = dialog_file_browser_show(dev->dialogs, dev->load_path, dev->load_path, &browser_options); - furi_string_free(nfc_app_folder); if(res) { FuriString* filename; filename = furi_string_alloc(); @@ -1271,7 +1290,11 @@ bool nfc_device_delete(NfcDevice* dev, bool use_load_path) { furi_string_set(file_path, dev->load_path); } else { furi_string_printf( - file_path, "%s/%s%s", NFC_APP_FOLDER, dev->dev_name, NFC_APP_EXTENSION); + file_path, + "%s/%s%s", + furi_string_get_cstr(dev->folder), + dev->dev_name, + NFC_APP_EXTENSION); } if(!storage_simply_remove(dev->storage, furi_string_get_cstr(file_path))) break; // Delete shadow file if it exists @@ -1280,7 +1303,11 @@ bool nfc_device_delete(NfcDevice* dev, bool use_load_path) { nfc_device_get_shadow_path(dev->load_path, file_path); } else { furi_string_printf( - file_path, "%s/%s%s", NFC_APP_FOLDER, dev->dev_name, NFC_APP_SHADOW_EXTENSION); + file_path, + "%s/%s%s", + furi_string_get_cstr(dev->folder), + dev->dev_name, + NFC_APP_SHADOW_EXTENSION); } if(!storage_simply_remove(dev->storage, furi_string_get_cstr(file_path))) break; } @@ -1309,14 +1336,23 @@ bool nfc_device_restore(NfcDevice* dev, bool use_load_path) { nfc_device_get_shadow_path(dev->load_path, path); } else { furi_string_printf( - path, "%s/%s%s", NFC_APP_FOLDER, dev->dev_name, NFC_APP_SHADOW_EXTENSION); + path, + "%s/%s%s", + furi_string_get_cstr(dev->folder), + dev->dev_name, + NFC_APP_SHADOW_EXTENSION); } if(!storage_simply_remove(dev->storage, furi_string_get_cstr(path))) break; dev->shadow_file_exist = false; if(use_load_path && !furi_string_empty(dev->load_path)) { furi_string_set(path, dev->load_path); } else { - furi_string_printf(path, "%s/%s%s", NFC_APP_FOLDER, dev->dev_name, NFC_APP_EXTENSION); + furi_string_printf( + path, + "%s/%s%s", + furi_string_get_cstr(dev->folder), + dev->dev_name, + NFC_APP_EXTENSION); } if(!nfc_device_load_data(dev, path, true)) break; restored = true; diff --git a/lib/nfc/nfc_device.h b/lib/nfc/nfc_device.h index 4be07f0166d..55ee4ac4c85 100644 --- a/lib/nfc/nfc_device.h +++ b/lib/nfc/nfc_device.h @@ -20,7 +20,6 @@ extern "C" { #define NFC_READER_DATA_MAX_SIZE 64 #define NFC_DICT_KEY_BATCH_SIZE 50 -#define NFC_APP_FOLDER ANY_PATH("nfc") #define NFC_APP_EXTENSION ".nfc" #define NFC_APP_SHADOW_EXTENSION ".shd" @@ -84,6 +83,7 @@ typedef struct { NfcDeviceData dev_data; char dev_name[NFC_DEV_NAME_MAX_LEN + 1]; FuriString* load_path; + FuriString* folder; NfcDeviceSaveFormat format; bool shadow_file_exist; From 2bdc34274f98d5dddb0e79e514940d6fe1d016d9 Mon Sep 17 00:00:00 2001 From: Kowalski Dragon Date: Mon, 19 Dec 2022 22:43:32 +0100 Subject: [PATCH 295/824] Clock v1 (#1750) --- applications/plugins/clock/application.fam | 10 ++ applications/plugins/clock/clock.png | Bin 0 -> 1896 bytes applications/plugins/clock/clock_app.c | 136 +++++++++++++++++++++ applications/services/locale/locale.c | 3 + 4 files changed, 149 insertions(+) create mode 100644 applications/plugins/clock/application.fam create mode 100644 applications/plugins/clock/clock.png create mode 100644 applications/plugins/clock/clock_app.c diff --git a/applications/plugins/clock/application.fam b/applications/plugins/clock/application.fam new file mode 100644 index 00000000000..590f5dfe09d --- /dev/null +++ b/applications/plugins/clock/application.fam @@ -0,0 +1,10 @@ +App( + appid="clock", + name="Clock", + apptype=FlipperAppType.PLUGIN, + entry_point="clock_app", + requires=["gui"], + stack_size=2 * 1024, + fap_icon="clock.png", + fap_category="Tools", +) diff --git a/applications/plugins/clock/clock.png b/applications/plugins/clock/clock.png new file mode 100644 index 0000000000000000000000000000000000000000..0d96df1020317ef6df0652bc6a0b0b5c1190fa59 GIT binary patch literal 1896 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V6Od#IhXQ2>tmIipR1RclAn~S zSCLx)lxJYDv9BmdOwLX%QAkQn&&;z`dcS+Wl0s&Rtx~wDuYqrYb81GWM^#a3aFt(3 za#eP+Wr~u$9hXgo70`g()RIJnirk#MVyg;UC9t_xKsHENUr7P1q$Jx`DZ)2E!8yMu zRl!uxRL?-kj!VI&C?(A*$i)q+8OXC$$|xx*u+rBrFE7_CH`dE9O4m2Ew6xSWFw!?N z(gmu}Ew0QfNvzP#D^>;>0WrfRwK%ybv!En1KTiQQ?NYItfzCc^Z* zVyO3l0ih3)(KpmH&_`BYkda@KU!0L&0Cy3J9=J4y#*)l59QJ@@Fq8v>54#N&i3Qjc z`}*Qno|}u}jp7p5GGIVJ0~N&!Fbj%9DhpEegHnt0ON)|IUCUDQN|eDN0SXr@=lq=f zqF`XsNVQcmLXVw6UXlT~93c^&nSw43@?cIW zD20UPWdei52y8D{O9VpBR>|B*AIX|XtWv;8v+@O|?v%umM3=-8pi7MmfT`2aNY}_9 z#K6?b#K_9fRNKJP$^a57VDum{2EA0%6xpH@#=h%wQUZYC^la6WZH#5$!TUq(|>phQ6 zq1(EIr1y*cVa=JkHOR$l+9owKpYxT*!OAm>t-sg2`+l!@*Y}tWuA95EuKaX7`}C`a z#gln+XP2ArNjTowEw?MVYay?{uW!IryZ0+MK3aQzRp{S4`>)OMkm9;x!o{ySYo55A z@Vtn#*<$ZGc2DuqQLIsWbM=Vq<%rjUAd-T>6dHXx3uMISzG$D99VU67I;J!Gca%qgD@k*tT_@u zK^IRK#}J9Bt^J;S35`&tSQ2 tcwMJ+VNB(c2WNkm|NQ;lbiu<-j5A!_Z&kb&`3|ZeJzf1=);T3K0RUC#hDiVb literal 0 HcmV?d00001 diff --git a/applications/plugins/clock/clock_app.c b/applications/plugins/clock/clock_app.c new file mode 100644 index 00000000000..e196f0d29e2 --- /dev/null +++ b/applications/plugins/clock/clock_app.c @@ -0,0 +1,136 @@ +#include +#include + +#include +#include + +typedef enum { + ClockEventTypeTick, + ClockEventTypeKey, +} ClockEventType; + +typedef struct { + ClockEventType type; + InputEvent input; +} ClockEvent; + +typedef struct { + FuriString* buffer; + FuriHalRtcDateTime datetime; + LocaleTimeFormat timeformat; + LocaleDateFormat dateformat; +} ClockData; + +typedef struct { + FuriMutex* mutex; + FuriMessageQueue* queue; + ClockData* data; +} Clock; + +static void clock_input_callback(InputEvent* input_event, FuriMessageQueue* queue) { + furi_assert(queue); + ClockEvent event = {.type = ClockEventTypeKey, .input = *input_event}; + furi_message_queue_put(queue, &event, FuriWaitForever); +} + +static void clock_render_callback(Canvas* canvas, void* ctx) { + Clock* clock = ctx; + if(furi_mutex_acquire(clock->mutex, 200) != FuriStatusOk) { + return; + } + + ClockData* data = clock->data; + + canvas_set_font(canvas, FontBigNumbers); + locale_format_time(data->buffer, &data->datetime, data->timeformat, true); + canvas_draw_str_aligned( + canvas, 64, 28, AlignCenter, AlignCenter, furi_string_get_cstr(data->buffer)); + + // Special case to cover missing glyphs in FontBigNumbers + if(data->timeformat == LocaleTimeFormat12h) { + size_t time_width = canvas_string_width(canvas, furi_string_get_cstr(data->buffer)); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned( + canvas, + 64 + (time_width / 2) - 10, + 31, + AlignLeft, + AlignCenter, + (data->datetime.hour > 12) ? "AM" : "PM"); + } + + canvas_set_font(canvas, FontSecondary); + locale_format_date(data->buffer, &data->datetime, data->dateformat, "/"); + canvas_draw_str_aligned( + canvas, 64, 42, AlignCenter, AlignTop, furi_string_get_cstr(data->buffer)); + + furi_mutex_release(clock->mutex); +} + +static void clock_tick(void* ctx) { + furi_assert(ctx); + FuriMessageQueue* queue = ctx; + ClockEvent event = {.type = ClockEventTypeTick}; + // It's OK to loose this event if system overloaded + furi_message_queue_put(queue, &event, 0); +} + +int32_t clock_app(void* p) { + UNUSED(p); + Clock* clock = malloc(sizeof(Clock)); + clock->data = malloc(sizeof(ClockData)); + clock->data->buffer = furi_string_alloc(); + + clock->queue = furi_message_queue_alloc(8, sizeof(ClockEvent)); + clock->mutex = furi_mutex_alloc(FuriMutexTypeNormal); + + furi_hal_rtc_get_datetime(&clock->data->datetime); + clock->data->timeformat = locale_get_time_format(); + clock->data->dateformat = locale_get_date_format(); + + // Set ViewPort callbacks + ViewPort* view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, clock_render_callback, clock); + view_port_input_callback_set(view_port, clock_input_callback, clock->queue); + + FuriTimer* timer = furi_timer_alloc(clock_tick, FuriTimerTypePeriodic, clock->queue); + + // Open GUI and register view_port + Gui* gui = furi_record_open(RECORD_GUI); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); + + furi_timer_start(timer, 100); + + // Main loop + ClockEvent event; + for(bool processing = true; processing;) { + furi_check(furi_message_queue_get(clock->queue, &event, FuriWaitForever) == FuriStatusOk); + furi_mutex_acquire(clock->mutex, FuriWaitForever); + if(event.type == ClockEventTypeKey) { + if(event.input.type == InputTypeShort && event.input.key == InputKeyBack) { + processing = false; + } + } else if(event.type == ClockEventTypeTick) { + furi_hal_rtc_get_datetime(&clock->data->datetime); + } + + furi_mutex_release(clock->mutex); + view_port_update(view_port); + } + + furi_timer_free(timer); + view_port_enabled_set(view_port, false); + gui_remove_view_port(gui, view_port); + view_port_free(view_port); + furi_record_close(RECORD_GUI); + + furi_message_queue_free(clock->queue); + furi_mutex_free(clock->mutex); + + furi_string_free(clock->data->buffer); + + free(clock->data); + free(clock); + + return 0; +} \ No newline at end of file diff --git a/applications/services/locale/locale.c b/applications/services/locale/locale.c index 07f56b1b01a..e8b6a9fc533 100644 --- a/applications/services/locale/locale.c +++ b/applications/services/locale/locale.c @@ -51,6 +51,9 @@ void locale_format_time( } else { am_pm = 1; } + if(hours == 0) { + hours = 12; + } } if(show_seconds) { From 02866361836bb4d3f6d636d66fcf73e881b5a1f4 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Tue, 20 Dec 2022 09:19:26 +0200 Subject: [PATCH 296/824] [FL-3036] Add NFC file format documentation (#2156) --- documentation/file_formats/NfcFileFormats.md | 255 +++++++++++++++++++ 1 file changed, 255 insertions(+) create mode 100644 documentation/file_formats/NfcFileFormats.md diff --git a/documentation/file_formats/NfcFileFormats.md b/documentation/file_formats/NfcFileFormats.md new file mode 100644 index 00000000000..90dcaea1c33 --- /dev/null +++ b/documentation/file_formats/NfcFileFormats.md @@ -0,0 +1,255 @@ +# NFC Flipper File Formats + +## NFC-A (UID) + Header + +### Example + + Filetype: Flipper NFC device + Version: 3 + # Nfc device type can be UID, Mifare Ultralight, Mifare Classic, Bank card + Device type: UID + # UID, ATQA and SAK are common for all formats + UID: 04 85 92 8A A0 61 81 + ATQA: 00 44 + SAK: 00 + +### Description + +This file format is used to store the UID, SAK and ATQA of a NFC-A device. It does not store any internal data, so it can be used for multiple different card types. Also used as a header for other formats. + +Version differences: + + 1. Initial version, deprecated + 2. LSB ATQA (e.g. 4400 instead of 0044) + 3. MSB ATQA (current version) + +UID can be either 4 or 7 bytes long. ATQA is 2 bytes long. SAK is 1 byte long. + +## Mifare Ultralight/NTAG + +### Example + + Filetype: Flipper NFC device + Version: 3 + # Nfc device type can be UID, Mifare Ultralight, Mifare Classic + Device type: NTAG216 + # UID, ATQA and SAK are common for all formats + UID: 04 85 90 54 12 98 23 + ATQA: 00 44 + SAK: 00 + # Mifare Ultralight specific data + Data format version: 1 + Signature: 1B 84 EB 70 BD 4C BD 1B 1D E4 98 0B 18 58 BD 7C 72 85 B4 E4 7B 38 8E 96 CF 88 6B EE A3 43 AD 90 + Mifare version: 00 04 04 02 01 00 13 03 + Counter 0: 0 + Tearing 0: 00 + Counter 1: 0 + Tearing 1: 00 + Counter 2: 0 + Tearing 2: 00 + Pages total: 231 + Pages read: 231 + Page 0: 04 85 92 9B + Page 1: 8A A0 61 81 + Page 2: CA 48 0F 00 + ... + Page 224: 00 00 00 00 + Page 225: 00 00 00 00 + Page 226: 00 00 7F BD + Page 227: 04 00 00 E2 + Page 228: 00 05 00 00 + Page 229: 00 00 00 00 + Page 230: 00 00 00 00 + Failed authentication attempts: 0 + +### Description + +This file format is used to store the UID, SAK and ATQA of a Mifare Ultralight/NTAG device. It also stores the internal data of the card, the signature, the version, and the counters. The data is stored in pages, just like on the card itself. + +The "Signature" field contains the reply of the tag to the READ_SIG command. More on that can be found here: (page 31) + +The "Mifare version" field is not related to the file format version, but to the Mifare Ultralight version. It contains the responce of the tag to the GET_VERSION command. More on that can be found here: (page 21) + +Other fields are the direct representation of the card's internal state, more on them can be found in the same datasheet. + +Version differences: + + 1. Current version + +## Mifare Classic + +### Example + + Filetype: Flipper NFC device + Version: 3 + # Nfc device type can be UID, Mifare Ultralight, Mifare Classic + Device type: Mifare Classic + # UID, ATQA and SAK are common for all formats + UID: BA E2 7C 9D + ATQA: 00 02 + SAK: 18 + # Mifare Classic specific data + Mifare Classic type: 4K + Data format version: 2 + # Mifare Classic blocks, '??' means unknown data + Block 0: BA E2 7C 9D B9 18 02 00 46 44 53 37 30 56 30 31 + Block 1: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + Block 2: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + Block 3: FF FF FF FF FF FF FF 07 80 69 FF FF FF FF FF FF + Block 4: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + Block 5: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + Block 6: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + Block 7: FF FF FF FF FF FF FF 07 80 69 FF FF FF FF FF FF + ... + Block 238: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + Block 239: FF FF FF FF FF FF FF 07 80 69 FF FF FF FF FF FF + Block 240: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + Block 241: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + Block 242: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + Block 243: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + Block 244: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + Block 245: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + Block 246: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + Block 247: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + Block 248: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + Block 249: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + Block 250: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + Block 251: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + Block 252: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + Block 253: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + Block 254: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + Block 255: FF FF FF FF FF FF FF 07 80 69 FF FF FF FF FF FF + +### Description + +This file format is used to store the NFC-A and Mifare Classic specific data of a Mifare Classic card. Aside from the NFC-A data, it stores the card type (1K/4K) and the internal data of the card. The data is stored in blocks, there is no sector grouping. If the block's data is unknown, it is represented by '??'. Otherwise, the data is represented as a hex string. + +Version differences: + + 1. Initial version, has Key A and Key B masks instead of marking unknown data with '??'. + +Example: + + ... + Data format version: 1 + # Key map is the bit mask indicating valid key in each sector + Key A map: 000000000000FFFF + Key B map: 000000000000FFFF + # Mifare Classic blocks + ... + + 2. Current version + +## Mifare DESFire + +### Example + + Filetype: Flipper NFC device + Version: 3 + # Nfc device type can be UID, Mifare Ultralight, Mifare Classic + Device type: Mifare DESFire + # UID, ATQA and SAK are common for all formats + UID: 04 2F 19 0A CD 66 80 + ATQA: 03 44 + SAK: 20 + # Mifare DESFire specific data + PICC Version: 04 01 01 12 00 1A 05 04 01 01 02 01 1A 05 04 2F 19 0A CD 66 80 CE ED D4 51 80 31 19 + PICC Free Memory: 7520 + PICC Change Key ID: 00 + PICC Config Changeable: true + PICC Free Create Delete: true + PICC Free Directory List: true + PICC Key Changeable: true + PICC Max Keys: 01 + PICC Key 0 Version: 00 + Application Count: 1 + Application IDs: 56 34 12 + Application 563412 Change Key ID: 00 + Application 563412 Config Changeable: true + Application 563412 Free Create Delete: true + Application 563412 Free Directory List: true + Application 563412 Key Changeable: true + Application 563412 Max Keys: 0E + Application 563412 Key 0 Version: 00 + Application 563412 Key 1 Version: 00 + Application 563412 Key 2 Version: 00 + Application 563412 Key 3 Version: 00 + Application 563412 Key 4 Version: 00 + Application 563412 Key 5 Version: 00 + Application 563412 Key 6 Version: 00 + Application 563412 Key 7 Version: 00 + Application 563412 Key 8 Version: 00 + Application 563412 Key 9 Version: 00 + Application 563412 Key 10 Version: 00 + Application 563412 Key 11 Version: 00 + Application 563412 Key 12 Version: 00 + Application 563412 Key 13 Version: 00 + Application 563412 File IDs: 01 + Application 563412 File 1 Type: 00 + Application 563412 File 1 Communication Settings: 00 + Application 563412 File 1 Access Rights: EE EE + Application 563412 File 1 Size: 256 + Application 563412 File 1: 13 37 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + +### Description + +This file format is used to store the NFC-A and Mifare DESFire specific data of a Mifare DESFire card. Aside from the NFC-A data, it stores the card type (DESFire) and the internal data of the card. The data is stored per-application, and per-file. Here, the card was written using those pm3 commands: + + hf mfdes createapp --aid 123456 --fid 2345 --dfname astra + hf mfdes createfile --aid 123456 --fid 01 --isofid 0001 --size 000100 + hf mfdes write --aid 123456 --fid 01 -d 1337 + +Version differences: + None, there are no versions yet. + +## Mifare Classic Dictionary + +### Example + + # Key dictionary from https://github.com/ikarus23/MifareClassicTool.git + + # More well known keys! + # Standard keys + FFFFFFFFFFFF + A0A1A2A3A4A5 + D3F7D3F7D3F7 + 000000000000 + + # Keys from mfoc + B0B1B2B3B4B5 + 4D3A99C351DD + 1A982C7E459A + AABBCCDDEEFF + 714C5C886E97 + 587EE5F9350F + A0478CC39091 + 533CB6C723F6 + 8FD0A4F256E9 + ... + +### Description + +This file contains a list of Mifare Classic keys. Each key is represented as a hex string. Lines starting with '#' are ignored as comments. Blank lines are ignored as well. + +## EMV resources + +### Example + + Filetype: Flipper EMV resources + Version: 1 + # EMV currency code: currency name + 0997: USN + 0994: XSU + 0990: CLF + 0986: BRL + 0985: PLN + 0984: BOV + ... + +### Description + +This file stores a list of EMV currency codes, country codes, or AIDs and their names. Each line contains a hex value and a name separated by a colon and a space. + +Version differences: + + 1. Initial version From e7107e39f76570aa25de7be7c8584eb1cbe6fff5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Tue, 20 Dec 2022 20:11:52 +0900 Subject: [PATCH 297/824] Gui: scrollable long file names in FileBrowser and Archive Browser (#2159) * Gui: scrollable long file names in FileBrowser * Archive: scroll long file names * Gui: elements code cleanup --- .../main/archive/views/archive_browser_view.c | 52 ++++++++++++++++-- .../main/archive/views/archive_browser_view.h | 2 + applications/services/dialogs/view_holder.c | 8 +++ applications/services/gui/elements.c | 46 ++++++++++++++++ applications/services/gui/elements.h | 19 +++++++ .../services/gui/modules/file_browser.c | 55 +++++++++++++++++-- firmware/targets/f7/api_symbols.csv | 1 + 7 files changed, 174 insertions(+), 9 deletions(-) diff --git a/applications/main/archive/views/archive_browser_view.c b/applications/main/archive/views/archive_browser_view.c index 65be421357c..2aca3c02ba4 100644 --- a/applications/main/archive/views/archive_browser_view.c +++ b/applications/main/archive/views/archive_browser_view.c @@ -5,6 +5,9 @@ #include "archive_browser_view.h" #include "../helpers/archive_browser.h" +#define SCROLL_INTERVAL (333) +#define SCROLL_DELAY (2) + static const char* ArchiveTabNames[] = { [ArchiveTabFavorites] = "Favorites", [ArchiveTabIButton] = "iButton", @@ -146,13 +149,18 @@ static void draw_list(Canvas* canvas, ArchiveBrowserViewModel* model) { furi_string_set(str_buf, "---"); } - elements_string_fit_width( - canvas, str_buf, (scrollbar ? MAX_LEN_PX - 6 : MAX_LEN_PX) - x_offset); + size_t scroll_counter = model->scroll_counter; if(model->item_idx == idx) { archive_draw_frame(canvas, i, scrollbar, model->move_fav); + if(scroll_counter < SCROLL_DELAY) { + scroll_counter = 0; + } else { + scroll_counter -= SCROLL_DELAY; + } } else { canvas_set_color(canvas, ColorBlack); + scroll_counter = 0; } if(custom_icon_data) { @@ -162,8 +170,15 @@ static void draw_list(Canvas* canvas, ArchiveBrowserViewModel* model) { canvas_draw_icon( canvas, 2 + x_offset, 16 + i * FRAME_HEIGHT, ArchiveItemIcons[file_type]); } - canvas_draw_str( - canvas, 15 + x_offset, 24 + i * FRAME_HEIGHT, furi_string_get_cstr(str_buf)); + + elements_scrollable_text_line( + canvas, + 15 + x_offset, + 24 + i * FRAME_HEIGHT, + ((scrollbar ? MAX_LEN_PX - 6 : MAX_LEN_PX) - x_offset), + str_buf, + scroll_counter, + (model->item_idx != idx)); furi_string_free(str_buf); } @@ -329,6 +344,7 @@ static bool archive_view_input(InputEvent* event, void* context) { if(move_fav_mode) { browser->callback(ArchiveBrowserEventFavMoveUp, browser->context); } + model->scroll_counter = 0; } else if(event->key == InputKeyDown) { model->item_idx = (model->item_idx + 1) % model->item_cnt; if(is_file_list_load_required(model)) { @@ -338,6 +354,7 @@ static bool archive_view_input(InputEvent* event, void* context) { if(move_fav_mode) { browser->callback(ArchiveBrowserEventFavMoveDown, browser->context); } + model->scroll_counter = 0; } }, true); @@ -377,6 +394,27 @@ static bool archive_view_input(InputEvent* event, void* context) { return true; } +static void browser_scroll_timer(void* context) { + furi_assert(context); + ArchiveBrowserView* browser = context; + with_view_model( + browser->view, ArchiveBrowserViewModel * model, { model->scroll_counter++; }, true); +} + +static void browser_view_enter(void* context) { + furi_assert(context); + ArchiveBrowserView* browser = context; + with_view_model( + browser->view, ArchiveBrowserViewModel * model, { model->scroll_counter = 0; }, true); + furi_timer_start(browser->scroll_timer, SCROLL_INTERVAL); +} + +static void browser_view_exit(void* context) { + furi_assert(context); + ArchiveBrowserView* browser = context; + furi_timer_stop(browser->scroll_timer); +} + ArchiveBrowserView* browser_alloc() { ArchiveBrowserView* browser = malloc(sizeof(ArchiveBrowserView)); browser->view = view_alloc(); @@ -384,6 +422,10 @@ ArchiveBrowserView* browser_alloc() { view_set_context(browser->view, browser); view_set_draw_callback(browser->view, archive_view_render); view_set_input_callback(browser->view, archive_view_input); + view_set_enter_callback(browser->view, browser_view_enter); + view_set_exit_callback(browser->view, browser_view_exit); + + browser->scroll_timer = furi_timer_alloc(browser_scroll_timer, FuriTimerTypePeriodic, browser); browser->path = furi_string_alloc_set(archive_get_default_path(TAB_DEFAULT)); @@ -402,6 +444,8 @@ ArchiveBrowserView* browser_alloc() { void browser_free(ArchiveBrowserView* browser) { furi_assert(browser); + furi_timer_free(browser->scroll_timer); + if(browser->worker_running) { file_browser_worker_free(browser->worker); } diff --git a/applications/main/archive/views/archive_browser_view.h b/applications/main/archive/views/archive_browser_view.h index 308af4e4d9d..915b5307a70 100644 --- a/applications/main/archive/views/archive_browser_view.h +++ b/applications/main/archive/views/archive_browser_view.h @@ -81,6 +81,7 @@ struct ArchiveBrowserView { FuriString* path; InputKey last_tab_switch_dir; bool is_root; + FuriTimer* scroll_timer; }; typedef struct { @@ -97,6 +98,7 @@ typedef struct { int32_t item_idx; int32_t array_offset; int32_t list_offset; + size_t scroll_counter; } ArchiveBrowserViewModel; void archive_browser_set_callback( diff --git a/applications/services/dialogs/view_holder.c b/applications/services/dialogs/view_holder.c index d2ae7750373..7ab0a8e1a5f 100644 --- a/applications/services/dialogs/view_holder.c +++ b/applications/services/dialogs/view_holder.c @@ -50,6 +50,10 @@ void view_holder_free(ViewHolder* view_holder) { void view_holder_set_view(ViewHolder* view_holder, View* view) { furi_assert(view_holder); if(view_holder->view) { + if(view_holder->view->exit_callback) { + view_holder->view->exit_callback(view_holder->view->context); + } + view_set_update_callback(view_holder->view, NULL); view_set_update_callback_context(view_holder->view, NULL); } @@ -59,6 +63,10 @@ void view_holder_set_view(ViewHolder* view_holder, View* view) { if(view_holder->view) { view_set_update_callback(view_holder->view, view_holder_update); view_set_update_callback_context(view_holder->view, view_holder); + + if(view_holder->view->enter_callback) { + view_holder->view->enter_callback(view_holder->view->context); + } } } diff --git a/applications/services/gui/elements.c b/applications/services/gui/elements.c index 0f7cf73f4ca..955e1fbddd4 100644 --- a/applications/services/gui/elements.c +++ b/applications/services/gui/elements.c @@ -547,6 +547,52 @@ void elements_string_fit_width(Canvas* canvas, FuriString* string, uint8_t width } } +void elements_scrollable_text_line( + Canvas* canvas, + uint8_t x, + uint8_t y, + uint8_t width, + FuriString* string, + size_t scroll, + bool ellipsis) { + FuriString* line = furi_string_alloc_set(string); + + size_t len_px = canvas_string_width(canvas, furi_string_get_cstr(line)); + if(len_px > width) { + if(ellipsis) { + width -= canvas_string_width(canvas, "..."); + } + + // Calculate scroll size + size_t scroll_size = furi_string_size(string); + size_t right_width = 0; + for(size_t i = scroll_size; i > 0; i--) { + right_width += canvas_glyph_width(canvas, furi_string_get_char(line, i)); + if(right_width > width) break; + scroll_size--; + if(!scroll_size) break; + } + // Ensure that we have something to scroll + if(scroll_size) { + scroll_size += 3; + scroll = scroll % scroll_size; + furi_string_right(line, scroll); + } + + do { + furi_string_left(line, furi_string_size(line) - 1); + len_px = canvas_string_width(canvas, furi_string_get_cstr(line)); + } while(len_px > width); + + if(ellipsis) { + furi_string_cat(line, "..."); + } + } + + canvas_draw_str(canvas, x, y, furi_string_get_cstr(line)); + furi_string_free(line); +} + void elements_text_box( Canvas* canvas, uint8_t x, diff --git a/applications/services/gui/elements.h b/applications/services/gui/elements.h index 9f155402b7f..162f0d410bd 100644 --- a/applications/services/gui/elements.h +++ b/applications/services/gui/elements.h @@ -192,6 +192,25 @@ void elements_bubble_str( */ void elements_string_fit_width(Canvas* canvas, FuriString* string, uint8_t width); +/** Draw scrollable text line + * + * @param canvas The canvas + * @param[in] x X coordinate + * @param[in] y Y coordinate + * @param[in] width The width + * @param string The string + * @param[in] scroll The scroll counter: 0 - no scroll, any other number - scroll. Just count up, everything else will be calculated on the inside. + * @param[in] ellipsis The ellipsis flag: true to add ellipse + */ +void elements_scrollable_text_line( + Canvas* canvas, + uint8_t x, + uint8_t y, + uint8_t width, + FuriString* string, + size_t scroll, + bool ellipsis); + /** Draw text box element * * @param canvas Canvas instance diff --git a/applications/services/gui/modules/file_browser.c b/applications/services/gui/modules/file_browser.c index a5daa91ec27..57e0018ecdb 100644 --- a/applications/services/gui/modules/file_browser.c +++ b/applications/services/gui/modules/file_browser.c @@ -19,6 +19,9 @@ #define CUSTOM_ICON_MAX_SIZE 32 +#define SCROLL_INTERVAL (333) +#define SCROLL_DELAY (2) + typedef enum { BrowserItemTypeLoading, BrowserItemTypeBack, @@ -95,6 +98,7 @@ struct FileBrowser { void* item_context; FuriString* result_path; + FuriTimer* scroll_timer; }; typedef struct { @@ -110,6 +114,7 @@ typedef struct { const Icon* file_icon; bool hide_ext; + size_t scroll_counter; } FileBrowserModel; static const Icon* BrowserItemIcons[] = { @@ -129,6 +134,27 @@ static void browser_list_item_cb(void* context, FuriString* item_path, bool is_folder, bool is_last); static void browser_long_load_cb(void* context); +static void file_browser_scroll_timer_callback(void* context) { + furi_assert(context); + FileBrowser* browser = context; + with_view_model( + browser->view, FileBrowserModel * model, { model->scroll_counter++; }, true); +} + +static void file_browser_view_enter_callback(void* context) { + furi_assert(context); + FileBrowser* browser = context; + with_view_model( + browser->view, FileBrowserModel * model, { model->scroll_counter = 0; }, true); + furi_timer_start(browser->scroll_timer, SCROLL_INTERVAL); +} + +static void file_browser_view_exit_callback(void* context) { + furi_assert(context); + FileBrowser* browser = context; + furi_timer_stop(browser->scroll_timer); +} + FileBrowser* file_browser_alloc(FuriString* result_path) { furi_assert(result_path); FileBrowser* browser = malloc(sizeof(FileBrowser)); @@ -137,6 +163,11 @@ FileBrowser* file_browser_alloc(FuriString* result_path) { 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); + view_set_enter_callback(browser->view, file_browser_view_enter_callback); + view_set_exit_callback(browser->view, file_browser_view_exit_callback); + + browser->scroll_timer = + furi_timer_alloc(file_browser_scroll_timer_callback, FuriTimerTypePeriodic, browser); browser->result_path = result_path; @@ -149,6 +180,8 @@ FileBrowser* file_browser_alloc(FuriString* result_path) { void file_browser_free(FileBrowser* browser) { furi_assert(browser); + furi_timer_free(browser->scroll_timer); + with_view_model( browser->view, FileBrowserModel * model, { items_array_clear(model->items); }, false); @@ -468,13 +501,17 @@ static void browser_draw_list(Canvas* canvas, FileBrowserModel* model) { furi_string_set(filename, ". ."); } - elements_string_fit_width( - canvas, filename, (show_scrollbar ? MAX_LEN_PX - 6 : MAX_LEN_PX)); - + size_t scroll_counter = model->scroll_counter; if(model->item_idx == idx) { browser_draw_frame(canvas, i, show_scrollbar); + if(scroll_counter < SCROLL_DELAY) { + scroll_counter = 0; + } else { + scroll_counter -= SCROLL_DELAY; + } } else { canvas_set_color(canvas, ColorBlack); + scroll_counter = 0; } if(custom_icon_data) { @@ -487,8 +524,14 @@ static void browser_draw_list(Canvas* canvas, FileBrowserModel* model) { 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, furi_string_get_cstr(filename)); + elements_scrollable_text_line( + canvas, + 15, + Y_OFFSET + 9 + i * FRAME_HEIGHT, + (show_scrollbar ? MAX_LEN_PX - 6 : MAX_LEN_PX), + filename, + scroll_counter, + (model->item_idx != idx)); } if(show_scrollbar) { @@ -543,6 +586,7 @@ static bool file_browser_view_input_callback(InputEvent* event, void* context) { file_browser_worker_load( browser->worker, load_offset, ITEM_LIST_LEN_MAX); } + model->scroll_counter = 0; } else if(event->key == InputKeyDown) { model->item_idx = (model->item_idx + 1) % model->item_cnt; if(browser_is_list_load_required(model)) { @@ -554,6 +598,7 @@ static bool file_browser_view_input_callback(InputEvent* event, void* context) { file_browser_worker_load( browser->worker, load_offset, ITEM_LIST_LEN_MAX); } + model->scroll_counter = 0; } }, true); diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index d8db33a38d1..ada82685c27 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -752,6 +752,7 @@ Function,+,elements_multiline_text,void,"Canvas*, uint8_t, uint8_t, const char*" Function,+,elements_multiline_text_aligned,void,"Canvas*, uint8_t, uint8_t, Align, Align, const char*" Function,+,elements_multiline_text_framed,void,"Canvas*, uint8_t, uint8_t, const char*" Function,+,elements_progress_bar,void,"Canvas*, uint8_t, uint8_t, uint8_t, float" +Function,+,elements_scrollable_text_line,void,"Canvas*, uint8_t, uint8_t, uint8_t, FuriString*, size_t, _Bool" Function,+,elements_scrollbar,void,"Canvas*, uint16_t, uint16_t" Function,+,elements_scrollbar_pos,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint16_t, uint16_t" Function,+,elements_slightly_rounded_box,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t" From 4cee550cc65b6208e693fc077ba6052b10120fec Mon Sep 17 00:00:00 2001 From: gornekich Date: Tue, 20 Dec 2022 16:32:24 +0400 Subject: [PATCH 298/824] [FL-2809] Rework BLE key storage (#2154) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * bt: disconnect first on profile change * bt keys: rework bt keys * saved struct: add payload size getter to API * bt: rework bt with new key storage API * bt: add keys storage operation to bt API * hid: save bt keys on sd card * bt: add unit tests for key storage * bt: working profile switch * bt: cleanup * bt hid: change keys storage path Co-authored-by: あく --- applications/debug/unit_tests/bt/bt_test.c | 110 ++++++++++++ applications/debug/unit_tests/test_index.c | 2 + applications/plugins/hid_app/hid.c | 22 ++- applications/plugins/hid_app/hid.h | 3 + applications/services/bt/bt_service/bt.c | 22 ++- applications/services/bt/bt_service/bt.h | 13 ++ applications/services/bt/bt_service/bt_api.c | 15 ++ applications/services/bt/bt_service/bt_i.h | 13 ++ .../services/bt/bt_service/bt_keys_storage.c | 159 ++++++++++++++---- .../services/bt/bt_service/bt_keys_storage.h | 20 ++- firmware/targets/f7/api_symbols.csv | 5 +- lib/toolbox/saved_struct.c | 51 ++++++ lib/toolbox/saved_struct.h | 6 + 13 files changed, 392 insertions(+), 49 deletions(-) create mode 100644 applications/debug/unit_tests/bt/bt_test.c diff --git a/applications/debug/unit_tests/bt/bt_test.c b/applications/debug/unit_tests/bt/bt_test.c new file mode 100644 index 00000000000..2cbfd684a29 --- /dev/null +++ b/applications/debug/unit_tests/bt/bt_test.c @@ -0,0 +1,110 @@ +#include +#include +#include "../minunit.h" + +#include +#include + +#define BT_TEST_KEY_STORAGE_FILE_PATH EXT_PATH("unit_tests/bt_test.keys") +#define BT_TEST_NVM_RAM_BUFF_SIZE (507 * 4) // The same as in ble NVM storage + +typedef struct { + Storage* storage; + BtKeysStorage* bt_keys_storage; + uint8_t* nvm_ram_buff_dut; + uint8_t* nvm_ram_buff_ref; +} BtTest; + +BtTest* bt_test = NULL; + +void bt_test_alloc() { + bt_test = malloc(sizeof(BtTest)); + bt_test->storage = furi_record_open(RECORD_STORAGE); + bt_test->nvm_ram_buff_dut = malloc(BT_TEST_NVM_RAM_BUFF_SIZE); + bt_test->nvm_ram_buff_ref = malloc(BT_TEST_NVM_RAM_BUFF_SIZE); + bt_test->bt_keys_storage = bt_keys_storage_alloc(BT_TEST_KEY_STORAGE_FILE_PATH); + bt_keys_storage_set_ram_params( + bt_test->bt_keys_storage, bt_test->nvm_ram_buff_dut, BT_TEST_NVM_RAM_BUFF_SIZE); +} + +void bt_test_free() { + furi_assert(bt_test); + free(bt_test->nvm_ram_buff_ref); + free(bt_test->nvm_ram_buff_dut); + bt_keys_storage_free(bt_test->bt_keys_storage); + furi_record_close(RECORD_STORAGE); + free(bt_test); + bt_test = NULL; +} + +static void bt_test_keys_storage_profile() { + // Emulate nvm change on initial connection + const int nvm_change_size_on_connection = 88; + for(size_t i = 0; i < nvm_change_size_on_connection; i++) { + bt_test->nvm_ram_buff_dut[i] = rand(); + bt_test->nvm_ram_buff_ref[i] = bt_test->nvm_ram_buff_dut[i]; + } + // Emulate update storage on initial connect + mu_assert( + bt_keys_storage_update( + bt_test->bt_keys_storage, bt_test->nvm_ram_buff_dut, nvm_change_size_on_connection), + "Failed to update key storage on initial connect"); + memset(bt_test->nvm_ram_buff_dut, 0, BT_TEST_NVM_RAM_BUFF_SIZE); + mu_assert(bt_keys_storage_load(bt_test->bt_keys_storage), "Failed to load NVM"); + mu_assert( + memcmp( + bt_test->nvm_ram_buff_ref, bt_test->nvm_ram_buff_dut, nvm_change_size_on_connection) == + 0, + "Wrong buffer loaded"); + + const int nvm_disconnect_update_offset = 84; + const int nvm_disconnect_update_size = 324; + const int nvm_total_size = nvm_change_size_on_connection - + (nvm_change_size_on_connection - nvm_disconnect_update_offset) + + nvm_disconnect_update_size; + // Emulate update storage on initial disconnect + for(size_t i = nvm_disconnect_update_offset; + i < nvm_disconnect_update_offset + nvm_disconnect_update_size; + i++) { + bt_test->nvm_ram_buff_dut[i] = rand(); + bt_test->nvm_ram_buff_ref[i] = bt_test->nvm_ram_buff_dut[i]; + } + mu_assert( + bt_keys_storage_update( + bt_test->bt_keys_storage, + &bt_test->nvm_ram_buff_dut[nvm_disconnect_update_offset], + nvm_disconnect_update_size), + "Failed to update key storage on initial disconnect"); + memset(bt_test->nvm_ram_buff_dut, 0, BT_TEST_NVM_RAM_BUFF_SIZE); + mu_assert(bt_keys_storage_load(bt_test->bt_keys_storage), "Failed to load NVM"); + mu_assert( + memcmp(bt_test->nvm_ram_buff_ref, bt_test->nvm_ram_buff_dut, nvm_total_size) == 0, + "Wrong buffer loaded"); +} + +static void bt_test_keys_remove_test_file() { + mu_assert( + storage_simply_remove(bt_test->storage, BT_TEST_KEY_STORAGE_FILE_PATH), + "Can't remove test file"); +} + +MU_TEST(bt_test_keys_storage_serial_profile) { + furi_assert(bt_test); + + bt_test_keys_remove_test_file(); + bt_test_keys_storage_profile(); + bt_test_keys_remove_test_file(); +} + +MU_TEST_SUITE(test_bt) { + bt_test_alloc(); + + MU_RUN_TEST(bt_test_keys_storage_serial_profile); + + bt_test_free(); +} + +int run_minunit_test_bt() { + MU_RUN_SUITE(test_bt); + return MU_EXIT_CODE; +} diff --git a/applications/debug/unit_tests/test_index.c b/applications/debug/unit_tests/test_index.c index 65fa23c014d..36c2b4aef92 100644 --- a/applications/debug/unit_tests/test_index.c +++ b/applications/debug/unit_tests/test_index.c @@ -24,6 +24,7 @@ int run_minunit_test_protocol_dict(); int run_minunit_test_lfrfid_protocols(); int run_minunit_test_nfc(); int run_minunit_test_bit_lib(); +int run_minunit_test_bt(); typedef int (*UnitTestEntry)(); @@ -49,6 +50,7 @@ const UnitTest unit_tests[] = { {.name = "protocol_dict", .entry = run_minunit_test_protocol_dict}, {.name = "lfrfid", .entry = run_minunit_test_lfrfid_protocols}, {.name = "bit_lib", .entry = run_minunit_test_bit_lib}, + {.name = "bt", .entry = run_minunit_test_bt}, }; void minunit_print_progress() { diff --git a/applications/plugins/hid_app/hid.c b/applications/plugins/hid_app/hid.c index d205d96eba3..1d2235e0842 100644 --- a/applications/plugins/hid_app/hid.c +++ b/applications/plugins/hid_app/hid.c @@ -367,9 +367,17 @@ int32_t hid_ble_app(void* p) { Hid* app = hid_alloc(HidTransportBle); app = hid_app_alloc_view(app); + bt_disconnect(app->bt); + + // Wait 2nd core to update nvm storage + furi_delay_ms(200); + + bt_keys_storage_set_storage_path(app->bt, HID_BT_KEYS_STORAGE_PATH); + if(!bt_set_profile(app->bt, BtProfileHidKeyboard)) { - FURI_LOG_E(TAG, "Failed to switch profile"); + FURI_LOG_E(TAG, "Failed to switch to HID profile"); } + furi_hal_bt_start_advertising(); bt_set_status_changed_callback(app->bt, bt_hid_connection_status_changed_callback, app); @@ -378,7 +386,17 @@ int32_t hid_ble_app(void* p) { view_dispatcher_run(app->view_dispatcher); bt_set_status_changed_callback(app->bt, NULL, NULL); - bt_set_profile(app->bt, BtProfileSerial); + + bt_disconnect(app->bt); + + // Wait 2nd core to update nvm storage + furi_delay_ms(200); + + bt_keys_storage_set_default_path(app->bt); + + if(!bt_set_profile(app->bt, BtProfileSerial)) { + FURI_LOG_E(TAG, "Failed to switch to Serial profile"); + } hid_free(app); diff --git a/applications/plugins/hid_app/hid.h b/applications/plugins/hid_app/hid.h index 375a89706de..fe32a199b45 100644 --- a/applications/plugins/hid_app/hid.h +++ b/applications/plugins/hid_app/hid.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -22,6 +23,8 @@ #include "views/hid_mouse_jiggler.h" #include "views/hid_tiktok.h" +#define HID_BT_KEYS_STORAGE_PATH EXT_PATH("apps/Tools/.bt_hid.keys") + typedef enum { HidTransportUsb, HidTransportBle, diff --git a/applications/services/bt/bt_service/bt.c b/applications/services/bt/bt_service/bt.c index 62b5ab109ec..024cb6e507d 100644 --- a/applications/services/bt/bt_service/bt.c +++ b/applications/services/bt/bt_service/bt.c @@ -117,6 +117,8 @@ Bt* bt_alloc() { if(!bt_settings_load(&bt->bt_settings)) { bt_settings_save(&bt->bt_settings); } + // Keys storage + bt->keys_storage = bt_keys_storage_alloc(BT_KEYS_STORAGE_PATH); // Alloc queue bt->message_queue = furi_message_queue_alloc(8, sizeof(BtMessage)); @@ -285,8 +287,10 @@ static bool bt_on_gap_event_callback(GapEvent event, void* context) { static void bt_on_key_storage_change_callback(uint8_t* addr, uint16_t size, void* context) { furi_assert(context); Bt* bt = context; - FURI_LOG_I(TAG, "Changed addr start: %p, size changed: %d", addr, size); - BtMessage message = {.type = BtMessageTypeKeysStorageUpdated}; + BtMessage message = { + .type = BtMessageTypeKeysStorageUpdated, + .data.key_storage_data.start_address = addr, + .data.key_storage_data.size = size}; furi_check( furi_message_queue_put(bt->message_queue, &message, FuriWaitForever) == FuriStatusOk); } @@ -331,6 +335,8 @@ static void bt_change_profile(Bt* bt, BtMessage* message) { furi_profile = FuriHalBtProfileSerial; } + bt_keys_storage_load(bt->keys_storage); + if(furi_hal_bt_change_app(furi_profile, bt_on_gap_event_callback, bt)) { FURI_LOG_I(TAG, "Bt App started"); if(bt->bt_settings.enabled) { @@ -358,6 +364,7 @@ static void bt_change_profile(Bt* bt, BtMessage* message) { static void bt_close_connection(Bt* bt) { bt_close_rpc_connection(bt); + furi_hal_bt_stop_advertising(); furi_event_flag_set(bt->api_event, BT_API_UNLOCK_EVENT); } @@ -372,8 +379,8 @@ int32_t bt_srv(void* p) { return 0; } - // Read keys - if(!bt_keys_storage_load(bt)) { + // Load keys + if(!bt_keys_storage_load(bt->keys_storage)) { FURI_LOG_W(TAG, "Failed to load bonding keys"); } @@ -418,13 +425,16 @@ int32_t bt_srv(void* p) { // Display PIN code bt_pin_code_show(bt, message.data.pin_code); } else if(message.type == BtMessageTypeKeysStorageUpdated) { - bt_keys_storage_save(bt); + bt_keys_storage_update( + bt->keys_storage, + message.data.key_storage_data.start_address, + message.data.key_storage_data.size); } else if(message.type == BtMessageTypeSetProfile) { bt_change_profile(bt, &message); } else if(message.type == BtMessageTypeDisconnect) { bt_close_connection(bt); } else if(message.type == BtMessageTypeForgetBondedDevices) { - bt_keys_storage_delete(bt); + bt_keys_storage_delete(bt->keys_storage); } } return 0; diff --git a/applications/services/bt/bt_service/bt.h b/applications/services/bt/bt_service/bt.h index 6e4e1b824a5..ca47936dbbb 100644 --- a/applications/services/bt/bt_service/bt.h +++ b/applications/services/bt/bt_service/bt.h @@ -56,6 +56,19 @@ void bt_set_status_changed_callback(Bt* bt, BtStatusChangedCallback callback, vo */ void bt_forget_bonded_devices(Bt* bt); +/** Set keys storage file path + * + * @param bt Bt instance + * @param keys_storage_path Path to file with saved keys + */ +void bt_keys_storage_set_storage_path(Bt* bt, const char* keys_storage_path); + +/** Set default keys storage file path + * + * @param bt Bt instance + */ +void bt_keys_storage_set_default_path(Bt* bt); + #ifdef __cplusplus } #endif diff --git a/applications/services/bt/bt_service/bt_api.c b/applications/services/bt/bt_service/bt_api.c index 3de896d5a3c..e3cf78cc792 100644 --- a/applications/services/bt/bt_service/bt_api.c +++ b/applications/services/bt/bt_service/bt_api.c @@ -39,3 +39,18 @@ void bt_forget_bonded_devices(Bt* bt) { furi_check( furi_message_queue_put(bt->message_queue, &message, FuriWaitForever) == FuriStatusOk); } + +void bt_keys_storage_set_storage_path(Bt* bt, const char* keys_storage_path) { + furi_assert(bt); + furi_assert(bt->keys_storage); + furi_assert(keys_storage_path); + + bt_keys_storage_set_file_path(bt->keys_storage, keys_storage_path); +} + +void bt_keys_storage_set_default_path(Bt* bt) { + furi_assert(bt); + furi_assert(bt->keys_storage); + + bt_keys_storage_set_file_path(bt->keys_storage, BT_KEYS_STORAGE_PATH); +} diff --git a/applications/services/bt/bt_service/bt_i.h b/applications/services/bt/bt_service/bt_i.h index 5769243ecb1..c8a0e99654e 100644 --- a/applications/services/bt/bt_service/bt_i.h +++ b/applications/services/bt/bt_service/bt_i.h @@ -13,8 +13,14 @@ #include #include #include +#include #include +#include + +#include "bt_keys_filename.h" + +#define BT_KEYS_STORAGE_PATH INT_PATH(BT_KEYS_STORAGE_FILE_NAME) #define BT_API_UNLOCK_EVENT (1UL << 0) @@ -29,10 +35,16 @@ typedef enum { BtMessageTypeForgetBondedDevices, } BtMessageType; +typedef struct { + uint8_t* start_address; + uint16_t size; +} BtKeyStorageUpdateData; + typedef union { uint32_t pin_code; uint8_t battery_level; BtProfile profile; + BtKeyStorageUpdateData key_storage_data; } BtMessageData; typedef struct { @@ -46,6 +58,7 @@ struct Bt { uint16_t bt_keys_size; uint16_t max_packet_size; BtSettings bt_settings; + BtKeysStorage* keys_storage; BtStatus status; BtProfile profile; FuriMessageQueue* message_queue; diff --git a/applications/services/bt/bt_service/bt_keys_storage.c b/applications/services/bt/bt_service/bt_keys_storage.c index 91d97d67e98..7cff99944f0 100644 --- a/applications/services/bt/bt_service/bt_keys_storage.c +++ b/applications/services/bt/bt_service/bt_keys_storage.c @@ -1,49 +1,24 @@ #include "bt_keys_storage.h" #include +#include #include #include -#define BT_KEYS_STORAGE_PATH INT_PATH(BT_KEYS_STORAGE_FILE_NAME) #define BT_KEYS_STORAGE_VERSION (0) #define BT_KEYS_STORAGE_MAGIC (0x18) -bool bt_keys_storage_load(Bt* bt) { - furi_assert(bt); - bool file_loaded = false; - - furi_hal_bt_get_key_storage_buff(&bt->bt_keys_addr_start, &bt->bt_keys_size); - furi_hal_bt_nvm_sram_sem_acquire(); - file_loaded = saved_struct_load( - BT_KEYS_STORAGE_PATH, - bt->bt_keys_addr_start, - bt->bt_keys_size, - BT_KEYS_STORAGE_MAGIC, - BT_KEYS_STORAGE_VERSION); - furi_hal_bt_nvm_sram_sem_release(); - - return file_loaded; -} +#define TAG "BtKeyStorage" -bool bt_keys_storage_save(Bt* bt) { - furi_assert(bt); - furi_assert(bt->bt_keys_addr_start); - bool file_saved = false; - - furi_hal_bt_nvm_sram_sem_acquire(); - file_saved = saved_struct_save( - BT_KEYS_STORAGE_PATH, - bt->bt_keys_addr_start, - bt->bt_keys_size, - BT_KEYS_STORAGE_MAGIC, - BT_KEYS_STORAGE_VERSION); - furi_hal_bt_nvm_sram_sem_release(); - - return file_saved; -} +struct BtKeysStorage { + uint8_t* nvm_sram_buff; + uint16_t nvm_sram_buff_size; + FuriString* file_path; +}; + +bool bt_keys_storage_delete(BtKeysStorage* instance) { + furi_assert(instance); -bool bt_keys_storage_delete(Bt* bt) { - furi_assert(bt); bool delete_succeed = false; bool bt_is_active = furi_hal_bt_is_active(); @@ -55,3 +30,117 @@ bool bt_keys_storage_delete(Bt* bt) { return delete_succeed; } + +BtKeysStorage* bt_keys_storage_alloc(const char* keys_storage_path) { + furi_assert(keys_storage_path); + + BtKeysStorage* instance = malloc(sizeof(BtKeysStorage)); + // Set default nvm ram parameters + furi_hal_bt_get_key_storage_buff(&instance->nvm_sram_buff, &instance->nvm_sram_buff_size); + // Set key storage file + instance->file_path = furi_string_alloc(); + furi_string_set_str(instance->file_path, keys_storage_path); + + return instance; +} + +void bt_keys_storage_free(BtKeysStorage* instance) { + furi_assert(instance); + + furi_string_free(instance->file_path); + free(instance); +} + +void bt_keys_storage_set_file_path(BtKeysStorage* instance, const char* path) { + furi_assert(instance); + furi_assert(path); + + furi_string_set_str(instance->file_path, path); +} + +void bt_keys_storage_set_ram_params(BtKeysStorage* instance, uint8_t* buff, uint16_t size) { + furi_assert(instance); + furi_assert(buff); + + instance->nvm_sram_buff = buff; + instance->nvm_sram_buff_size = size; +} + +bool bt_keys_storage_load(BtKeysStorage* instance) { + furi_assert(instance); + + bool loaded = false; + do { + // Get payload size + size_t payload_size = 0; + if(!saved_struct_get_payload_size( + furi_string_get_cstr(instance->file_path), + BT_KEYS_STORAGE_MAGIC, + BT_KEYS_STORAGE_VERSION, + &payload_size)) { + FURI_LOG_E(TAG, "Failed to read payload size"); + break; + } + + if(payload_size > instance->nvm_sram_buff_size) { + FURI_LOG_E(TAG, "Saved data doesn't fit ram buffer"); + break; + } + + // Load saved data to ram + furi_hal_bt_nvm_sram_sem_acquire(); + bool data_loaded = saved_struct_load( + furi_string_get_cstr(instance->file_path), + instance->nvm_sram_buff, + payload_size, + BT_KEYS_STORAGE_MAGIC, + BT_KEYS_STORAGE_VERSION); + furi_hal_bt_nvm_sram_sem_release(); + if(!data_loaded) { + FURI_LOG_E(TAG, "Failed to load struct"); + break; + } + + loaded = true; + } while(false); + + return loaded; +} + +bool bt_keys_storage_update(BtKeysStorage* instance, uint8_t* start_addr, uint32_t size) { + furi_assert(instance); + furi_assert(start_addr); + + bool updated = false; + + FURI_LOG_I( + TAG, + "Base address: %p. Start update address: %p. Size changed: %ld", + (void*)instance->nvm_sram_buff, + start_addr, + size); + + do { + size_t new_size = start_addr - instance->nvm_sram_buff + size; + if(new_size > instance->nvm_sram_buff_size) { + FURI_LOG_E(TAG, "NVM RAM buffer overflow"); + break; + } + + furi_hal_bt_nvm_sram_sem_acquire(); + bool data_updated = saved_struct_save( + furi_string_get_cstr(instance->file_path), + instance->nvm_sram_buff, + new_size, + BT_KEYS_STORAGE_MAGIC, + BT_KEYS_STORAGE_VERSION); + furi_hal_bt_nvm_sram_sem_release(); + if(!data_updated) { + FURI_LOG_E(TAG, "Failed to update key storage"); + break; + } + updated = true; + } while(false); + + return updated; +} diff --git a/applications/services/bt/bt_service/bt_keys_storage.h b/applications/services/bt/bt_service/bt_keys_storage.h index b82b1035f5c..cb808ca3047 100644 --- a/applications/services/bt/bt_service/bt_keys_storage.h +++ b/applications/services/bt/bt_service/bt_keys_storage.h @@ -1,10 +1,20 @@ #pragma once -#include "bt_i.h" -#include "bt_keys_filename.h" +#include +#include -bool bt_keys_storage_load(Bt* bt); +typedef struct BtKeysStorage BtKeysStorage; -bool bt_keys_storage_save(Bt* bt); +BtKeysStorage* bt_keys_storage_alloc(const char* keys_storage_path); -bool bt_keys_storage_delete(Bt* bt); +void bt_keys_storage_free(BtKeysStorage* instance); + +void bt_keys_storage_set_file_path(BtKeysStorage* instance, const char* path); + +void bt_keys_storage_set_ram_params(BtKeysStorage* instance, uint8_t* buff, uint16_t size); + +bool bt_keys_storage_load(BtKeysStorage* instance); + +bool bt_keys_storage_update(BtKeysStorage* instance, uint8_t* start_addr, uint32_t size); + +bool bt_keys_storage_delete(BtKeysStorage* instance); diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index ada82685c27..8503a838abc 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,11.1,, +Version,+,11.2,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -562,6 +562,8 @@ Function,+,ble_glue_wait_for_c2_start,_Bool,int32_t Function,-,bsearch,void*,"const void*, const void*, size_t, size_t, __compar_fn_t" Function,+,bt_disconnect,void,Bt* Function,+,bt_forget_bonded_devices,void,Bt* +Function,+,bt_keys_storage_set_storage_path,void,"Bt*, const char*" +Function,+,bt_keys_storage_set_default_path,void,Bt* Function,+,bt_set_profile,_Bool,"Bt*, BtProfile" Function,+,bt_set_status_changed_callback,void,"Bt*, BtStatusChangedCallback, void*" Function,+,buffered_file_stream_alloc,Stream*,Storage* @@ -2321,6 +2323,7 @@ Function,+,rpc_system_app_set_data_exchange_callback,void,"RpcAppSystem*, RpcApp Function,+,rpc_system_app_set_error_code,void,"RpcAppSystem*, uint32_t" Function,+,rpc_system_app_set_error_text,void,"RpcAppSystem*, const char*" Function,-,rpmatch,int,const char* +Function,+,saved_struct_get_payload_size,_Bool,"const char*, uint8_t, uint8_t, size_t*" Function,+,saved_struct_load,_Bool,"const char*, void*, size_t, uint8_t, uint8_t" Function,+,saved_struct_save,_Bool,"const char*, void*, size_t, uint8_t, uint8_t" Function,-,scalbln,double,"double, long int" diff --git a/lib/toolbox/saved_struct.c b/lib/toolbox/saved_struct.c index 65b761f80ce..02b73f21044 100644 --- a/lib/toolbox/saved_struct.c +++ b/lib/toolbox/saved_struct.c @@ -125,3 +125,54 @@ bool saved_struct_load(const char* path, void* data, size_t size, uint8_t magic, return result; } + +bool saved_struct_get_payload_size( + const char* path, + uint8_t magic, + uint8_t version, + size_t* payload_size) { + furi_assert(path); + furi_assert(payload_size); + + SavedStructHeader header; + Storage* storage = furi_record_open(RECORD_STORAGE); + File* file = storage_file_alloc(storage); + + bool result = false; + do { + if(!storage_file_open(file, path, FSAM_READ, FSOM_OPEN_EXISTING)) { + FURI_LOG_E( + TAG, "Failed to read \"%s\". Error: %s", path, storage_file_get_error_desc(file)); + break; + } + + uint16_t bytes_count = storage_file_read(file, &header, sizeof(SavedStructHeader)); + if(bytes_count != sizeof(SavedStructHeader)) { + FURI_LOG_E(TAG, "Failed to read header"); + break; + } + + if((header.magic != magic) || (header.version != version)) { + FURI_LOG_E( + TAG, + "Magic(%d != %d) or Version(%d != %d) mismatch of file \"%s\"", + header.magic, + magic, + header.version, + version, + path); + break; + } + + uint64_t file_size = storage_file_size(file); + *payload_size = file_size - sizeof(SavedStructHeader); + + result = true; + } while(false); + + storage_file_close(file); + storage_file_free(file); + furi_record_close(RECORD_STORAGE); + + return result; +} diff --git a/lib/toolbox/saved_struct.h b/lib/toolbox/saved_struct.h index aaa04eef5c2..9ce836564be 100644 --- a/lib/toolbox/saved_struct.h +++ b/lib/toolbox/saved_struct.h @@ -12,6 +12,12 @@ bool saved_struct_load(const char* path, void* data, size_t size, uint8_t magic, bool saved_struct_save(const char* path, void* data, size_t size, uint8_t magic, uint8_t version); +bool saved_struct_get_payload_size( + const char* path, + uint8_t magic, + uint8_t version, + size_t* payload_size); + #ifdef __cplusplus } #endif From 797eab8924255043fc6e5d678f24c3e25b602f57 Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Tue, 20 Dec 2022 16:42:16 +0400 Subject: [PATCH 299/824] SubGhz: fix Hormann HSM (#2158) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * SubGhz: fix Hormann HSM * SubGhz: add check pattern hormann protocol Co-authored-by: あく --- lib/subghz/protocols/hormann.c | 34 +++++++++------------------------- 1 file changed, 9 insertions(+), 25 deletions(-) diff --git a/lib/subghz/protocols/hormann.c b/lib/subghz/protocols/hormann.c index cb6adaf6232..67b8cdfca42 100644 --- a/lib/subghz/protocols/hormann.c +++ b/lib/subghz/protocols/hormann.c @@ -8,6 +8,8 @@ #define TAG "SubGhzProtocolHormannHSM" +#define HORMANN_HSM_PATTERN 0xFF000000003 + static const SubGhzBlockConst subghz_protocol_hormann_const = { .te_short = 500, .te_long = 1000, @@ -101,20 +103,13 @@ static bool subghz_protocol_encoder_hormann_get_upload(SubGhzProtocolEncoderHorm furi_assert(instance); size_t index = 0; - size_t size_upload = 3 + (instance->generic.data_count_bit * 2 + 2) * 20 + 1; + size_t size_upload = (instance->generic.data_count_bit * 2 + 2) * 20 + 1; if(size_upload > instance->encoder.size_upload) { FURI_LOG_E(TAG, "Size upload exceeds allocated encoder buffer."); return false; } else { instance->encoder.size_upload = size_upload; } - //Send header - instance->encoder.upload[index++] = - level_duration_make(false, (uint32_t)subghz_protocol_hormann_const.te_short * 64); - instance->encoder.upload[index++] = - level_duration_make(true, (uint32_t)subghz_protocol_hormann_const.te_short * 64); - instance->encoder.upload[index++] = - level_duration_make(false, (uint32_t)subghz_protocol_hormann_const.te_short * 64); instance->encoder.repeat = 10; //original remote does 10 repeats for(size_t repeat = 0; repeat < 20; repeat++) { @@ -209,6 +204,10 @@ void subghz_protocol_decoder_hormann_free(void* context) { free(instance); } +static bool subghz_protocol_decoder_hormann_check_pattern(SubGhzProtocolDecoderHormann* instance) { + return (instance->decoder.decode_data & HORMANN_HSM_PATTERN) == HORMANN_HSM_PATTERN; +} + void subghz_protocol_decoder_hormann_reset(void* context) { furi_assert(context); SubGhzProtocolDecoderHormann* instance = context; @@ -221,25 +220,9 @@ void subghz_protocol_decoder_hormann_feed(void* context, bool level, uint32_t du switch(instance->decoder.parser_step) { case HormannDecoderStepReset: - if((level) && (DURATION_DIFF(duration, subghz_protocol_hormann_const.te_short * 64) < - subghz_protocol_hormann_const.te_delta * 64)) { - instance->decoder.parser_step = HormannDecoderStepFoundStartHeader; - } - break; - case HormannDecoderStepFoundStartHeader: - if((!level) && (DURATION_DIFF(duration, subghz_protocol_hormann_const.te_short * 64) < - subghz_protocol_hormann_const.te_delta * 64)) { - instance->decoder.parser_step = HormannDecoderStepFoundHeader; - } else { - instance->decoder.parser_step = HormannDecoderStepReset; - } - break; - case HormannDecoderStepFoundHeader: if((level) && (DURATION_DIFF(duration, subghz_protocol_hormann_const.te_short * 24) < subghz_protocol_hormann_const.te_delta * 24)) { instance->decoder.parser_step = HormannDecoderStepFoundStartBit; - } else { - instance->decoder.parser_step = HormannDecoderStepReset; } break; case HormannDecoderStepFoundStartBit: @@ -254,7 +237,8 @@ void subghz_protocol_decoder_hormann_feed(void* context, bool level, uint32_t du break; case HormannDecoderStepSaveDuration: if(level) { //save interval - if(duration >= (subghz_protocol_hormann_const.te_short * 5)) { + if(duration >= (subghz_protocol_hormann_const.te_short * 5) && + subghz_protocol_decoder_hormann_check_pattern(instance)) { instance->decoder.parser_step = HormannDecoderStepFoundStartBit; if(instance->decoder.decode_count_bit >= subghz_protocol_hormann_const.min_count_bit_for_found) { From a9c2b4d6a0dc0f890351763ffd94f6a904833191 Mon Sep 17 00:00:00 2001 From: Kowalski Dragon Date: Tue, 20 Dec 2022 14:57:58 +0100 Subject: [PATCH 300/824] Desktop: dummy mode improvements. Fixes: correct scrolling text, correct AM/PM in Clock. (#2160) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Show passport instead of app if SD/app is missing * Desktop: cleanup dummy mode code and add more apps * Gui: fix incorrect trimming in scrollable text Signed-off-by: Kowalski Dragon (kowalski7cc) Co-authored-by: あく --- applications/plugins/clock/clock_app.c | 2 +- .../desktop/scenes/desktop_scene_main.c | 33 +++++++++++++++---- .../services/desktop/views/desktop_events.h | 4 ++- .../desktop/views/desktop_view_main.c | 6 ++-- applications/services/gui/elements.c | 7 ++-- 5 files changed, 38 insertions(+), 14 deletions(-) diff --git a/applications/plugins/clock/clock_app.c b/applications/plugins/clock/clock_app.c index e196f0d29e2..9d87ff9501b 100644 --- a/applications/plugins/clock/clock_app.c +++ b/applications/plugins/clock/clock_app.c @@ -56,7 +56,7 @@ static void clock_render_callback(Canvas* canvas, void* ctx) { 31, AlignLeft, AlignCenter, - (data->datetime.hour > 12) ? "AM" : "PM"); + (data->datetime.hour > 12) ? "PM" : "AM"); } canvas_set_font(canvas, FontSecondary); diff --git a/applications/services/desktop/scenes/desktop_scene_main.c b/applications/services/desktop/scenes/desktop_scene_main.c index 4f01ad5beed..befcf399a69 100644 --- a/applications/services/desktop/scenes/desktop_scene_main.c +++ b/applications/services/desktop/scenes/desktop_scene_main.c @@ -12,6 +12,10 @@ #define TAG "DesktopSrv" +#define MUSIC_PLAYER_APP EXT_PATH("/apps/Misc/music_player.fap") +#define SNAKE_GAME_APP EXT_PATH("/apps/Games/snake_game.fap") +#define CLOCK_APP EXT_PATH("/apps/Tools/clock.fap") + static void desktop_scene_main_new_idle_animation_callback(void* context) { furi_assert(context); Desktop* desktop = context; @@ -60,6 +64,19 @@ static void desktop_switch_to_app(Desktop* desktop, const FlipperApplication* fl } #endif +static void desktop_scene_main_open_app_or_profile(Desktop* desktop, const char* path) { + do { + LoaderStatus status = loader_start(desktop->loader, FAP_LOADER_APP_NAME, path); + if(status == LoaderStatusOk) break; + FURI_LOG_E(TAG, "loader_start failed: %d", status); + + status = loader_start(desktop->loader, "Passport", NULL); + if(status != LoaderStatusOk) { + FURI_LOG_E(TAG, "loader_start failed: %d", status); + } + } while(false); +} + void desktop_scene_main_callback(DesktopEvent event, void* context) { Desktop* desktop = (Desktop*)context; view_dispatcher_send_custom_event(desktop->view_dispatcher, event); @@ -181,12 +198,16 @@ bool desktop_scene_main_on_event(void* context, SceneManagerEvent event) { } break; } - case DesktopMainEventOpenGameMenu: { - LoaderStatus status = loader_start( - desktop->loader, FAP_LOADER_APP_NAME, EXT_PATH("/apps/Games/snake_game.fap")); - if(status != LoaderStatusOk) { - FURI_LOG_E(TAG, "loader_start failed: %d", status); - } + case DesktopMainEventOpenGame: { + desktop_scene_main_open_app_or_profile(desktop, SNAKE_GAME_APP); + break; + } + case DesktopMainEventOpenClock: { + desktop_scene_main_open_app_or_profile(desktop, CLOCK_APP); + break; + } + case DesktopMainEventOpenMusicPlayer: { + desktop_scene_main_open_app_or_profile(desktop, MUSIC_PLAYER_APP); break; } case DesktopLockedEventUpdate: diff --git a/applications/services/desktop/views/desktop_events.h b/applications/services/desktop/views/desktop_events.h index a7e610fff1b..666d179b83d 100644 --- a/applications/services/desktop/views/desktop_events.h +++ b/applications/services/desktop/views/desktop_events.h @@ -10,7 +10,9 @@ typedef enum { DesktopMainEventOpenPassport, DesktopMainEventOpenPowerOff, - DesktopMainEventOpenGameMenu, + DesktopMainEventOpenGame, + DesktopMainEventOpenClock, + DesktopMainEventOpenMusicPlayer, DesktopLockedEventUnlocked, DesktopLockedEventUpdate, diff --git a/applications/services/desktop/views/desktop_view_main.c b/applications/services/desktop/views/desktop_view_main.c index 0edc0b703ef..cbf4a20ffe3 100644 --- a/applications/services/desktop/views/desktop_view_main.c +++ b/applications/services/desktop/views/desktop_view_main.c @@ -72,13 +72,13 @@ bool desktop_main_input_callback(InputEvent* event, void* context) { } else { if(event->type == InputTypeShort) { if(event->key == InputKeyOk) { - main_view->callback(DesktopMainEventOpenGameMenu, main_view->context); + main_view->callback(DesktopMainEventOpenGame, main_view->context); } else if(event->key == InputKeyUp) { main_view->callback(DesktopMainEventOpenLockMenu, main_view->context); } else if(event->key == InputKeyDown) { - main_view->callback(DesktopMainEventOpenPassport, main_view->context); + main_view->callback(DesktopMainEventOpenMusicPlayer, main_view->context); } else if(event->key == InputKeyLeft) { - main_view->callback(DesktopMainEventOpenPassport, main_view->context); + main_view->callback(DesktopMainEventOpenClock, main_view->context); } // Right key is handled by animation manager } diff --git a/applications/services/gui/elements.c b/applications/services/gui/elements.c index 955e1fbddd4..6b796ed5b53 100644 --- a/applications/services/gui/elements.c +++ b/applications/services/gui/elements.c @@ -564,7 +564,7 @@ void elements_scrollable_text_line( } // Calculate scroll size - size_t scroll_size = furi_string_size(string); + size_t scroll_size = furi_string_size(line); size_t right_width = 0; for(size_t i = scroll_size; i > 0; i--) { right_width += canvas_glyph_width(canvas, furi_string_get_char(line, i)); @@ -579,10 +579,11 @@ void elements_scrollable_text_line( furi_string_right(line, scroll); } - do { + len_px = canvas_string_width(canvas, furi_string_get_cstr(line)); + while(len_px > width) { furi_string_left(line, furi_string_size(line) - 1); len_px = canvas_string_width(canvas, furi_string_get_cstr(line)); - } while(len_px > width); + } if(ellipsis) { furi_string_cat(line, "..."); From e0d716647c4936024da2f85234013fc16fc52dc9 Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Tue, 20 Dec 2022 17:46:05 +0300 Subject: [PATCH 301/824] [FL-3053] Archive browser delete fix (#2163) --- applications/main/archive/scenes/archive_scene_browser.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/applications/main/archive/scenes/archive_scene_browser.c b/applications/main/archive/scenes/archive_scene_browser.c index 04f4dcc3a5c..2f469354858 100644 --- a/applications/main/archive/scenes/archive_scene_browser.c +++ b/applications/main/archive/scenes/archive_scene_browser.c @@ -143,6 +143,8 @@ bool archive_scene_browser_on_event(void* context, SceneManagerEvent event) { break; case ArchiveBrowserEventFileMenuDelete: if(archive_get_tab(browser) != ArchiveTabFavorites) { + scene_manager_set_scene_state( + archive->scene_manager, ArchiveAppSceneBrowser, SCENE_STATE_NEED_REFRESH); scene_manager_next_scene(archive->scene_manager, ArchiveAppSceneDelete); } consumed = true; From c34ae66b6e009d4cf1d7b5baadf09131c0417efa Mon Sep 17 00:00:00 2001 From: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> Date: Tue, 20 Dec 2022 18:07:19 +0300 Subject: [PATCH 302/824] [FL-3041] IR format docs (#2162) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- .../file_formats/InfraredFileFormats.md | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 documentation/file_formats/InfraredFileFormats.md diff --git a/documentation/file_formats/InfraredFileFormats.md b/documentation/file_formats/InfraredFileFormats.md new file mode 100644 index 00000000000..a6d6c276d92 --- /dev/null +++ b/documentation/file_formats/InfraredFileFormats.md @@ -0,0 +1,111 @@ +# Infrared Flipper File Formats + +## Infrared Remote File Format +### Example + + Filetype: IR signals file + Version: 1 + # + name: Button_1 + type: parsed + protocol: NECext + address: EE 87 00 00 + command: 5D A0 00 00 + # + name: Button_2 + type: raw + frequency: 38000 + duty_cycle: 0.330000 + data: 504 3432 502 483 500 484 510 502 502 482 501 485 509 1452 504 1458 509 1452 504 481 501 474 509 3420 503 + # + name: Button_3 + type: parsed + protocol: SIRC + address: 01 00 00 00 + command: 15 00 00 00 + +### Description +Filename extension: `.ir` + +This file format is used to store an infrared remote that consists of an arbitrary number of buttons. +Each button is separated from others by a comment character (`#`) for better readability. + +Known protocols are represented in the `parsed` form, whereas non-recognised signals may be saved and re-transmitted as `raw` data. + +#### Version history: +1. Initial version. + +#### Format fields +| Name | Use | Type | Description | +| ---------- | ------- | ------ |------------ | +| name | both | string | Name of the button. Only printable ASCII characters are allowed. | +| type | both | string | Type of the signal. Must be `parsed` or `raw`. | +| protocol | parsed | string | Name of the infrared protocol. Refer to `ir` console command for the complete list of supported protocols. | +| address | parsed | hex | Payload address. Must be 4 bytes long. | +| command | parsed | hex | Payload command. Must be 4 bytes long. | +| frequency | raw | uint32 | Carrier frequency, in Hertz, usually 38000 Hz. | +| duty_cycle | raw | float | Carrier duty cycle, usually 0.33. | +| data | raw | uint32 | Raw signal timings, in microseconds between logic level changes. Individual elements must be space-separated. Maximum timings amount is 1024. | + +## Infrared Library File Format +### Examples +- [TV Universal Library](/assets/resources/infrared/assets/tv.ir) +- [A/C Universal Library](/assets/resources/infrared/assets/ac.ir) +- [Audio Universal Library](/assets/resources/infrared/assets/audio.ir) + +### Description +Filename extension: `.ir` + +This file format is used to store universal remote libraries. It is identical to the previous format, differing only in the `Filetype` field.\ +It also has predefined button names for each universal library type, so that the universal remote application could understand them. +See [Universal Remotes](/documentation/UniversalRemotes.md) for more information. + +### Version history: +1. Initial version. + +## Infrared Test File Format +### Examples +See [Infrared Unit Tests](/assets/unit_tests/infrared/) for various examples. +### Description +Filename extension: `.irtest` + +This file format is used to store technical test data that is too large to keep directly in the firmware. +It is mostly similar to the two previous formats, with the main difference being the addition of the parsed signal arrays. + +Each infrared protocol must have corresponding unit tests complete with an `.irtest` file. + +Known protocols are represented in the `parsed_array` form, whereas raw data has the `raw` type.\ +Note: a single parsed signal must be represented as an array of size 1. + +### Version history: +1. Initial version. + +#### Format fields +| Name | Use | Type | Description | +| ---------- | ------------ | ------ |------------ | +| name | both | string | Name of the signal. Only printable ASCII characters are allowed. | +| type | both | string | Type of the signal. Must be `parsed_array` or `raw`. | +| count | parsed_array | uint32 | The number of parsed signals in an array. Must be at least 1. | +| protocol | parsed_array | string | Same as in previous formats. | +| address | parsed_array | hex | Ditto. | +| command | parsed_array | hex | Ditto. | +| repeat | parsed_array | bool | Indicates whether the signal is a repeated button press. | +| frequency | raw | uint32 | Same as in previous formats. | +| duty_cycle | raw | float | Ditto. | +| data | raw | uint32 | Ditto. | + +#### Signal names +The signal names in an `.irtest` file folow a convention ``, where the name is one of: +- decoder_input +- decoder_expected +- encoder_decoder_input, + +and the number is a sequential integer: 1, 2, 3...etc, which produces names like `decoder_input1`, `encoder_decoder_input3`, and so on. + +| Name | Type | Description | +| --------------------- | ------------ | ----------- | +| decoder_input | raw | A raw signal contaning the decoder input. Is also used as the expected encoder output. | +| decoder_expected | parsed_array | An array of parsed signals containing the expected decoder output. Is also used as the encoder input. | +| encoder_decoder_input | parsed_array | An array of parsed signals containing both the encoder-decoder input and expected output. | + +See [Unit Tests](/documentation/UnitTests.md#infrared) for more info. From 5856746fc9774157aa3f42608f5019efa829f76b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Wed, 21 Dec 2022 18:07:14 +0900 Subject: [PATCH 303/824] iButton: fixed notification on successful read (#2169) --- applications/main/ibutton/scenes/ibutton_scene_read.c | 1 - applications/main/ibutton/scenes/ibutton_scene_read_success.c | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/applications/main/ibutton/scenes/ibutton_scene_read.c b/applications/main/ibutton/scenes/ibutton_scene_read.c index 1fe75e45ae5..b5ee08e6f4d 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_read.c +++ b/applications/main/ibutton/scenes/ibutton_scene_read.c @@ -52,7 +52,6 @@ bool ibutton_scene_read_on_event(void* context, SceneManagerEvent event) { if(success) { ibutton_notification_message(ibutton, iButtonNotificationMessageSuccess); - ibutton_notification_message(ibutton, iButtonNotificationMessageGreenOn); scene_manager_next_scene(scene_manager, iButtonSceneReadSuccess); DOLPHIN_DEED(DolphinDeedIbuttonReadSuccess); } diff --git a/applications/main/ibutton/scenes/ibutton_scene_read_success.c b/applications/main/ibutton/scenes/ibutton_scene_read_success.c index 1c2bcdd292f..749e7af37a0 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_read_success.c +++ b/applications/main/ibutton/scenes/ibutton_scene_read_success.c @@ -48,6 +48,8 @@ void ibutton_scene_read_success_on_enter(void* context) { dialog_ex_set_context(dialog_ex, ibutton); view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewDialogEx); + + ibutton_notification_message(ibutton, iButtonNotificationMessageGreenOn); } bool ibutton_scene_read_success_on_event(void* context, SceneManagerEvent event) { From 279662355149314b14af440a0d396e472419d3e2 Mon Sep 17 00:00:00 2001 From: valentinegb Date: Wed, 21 Dec 2022 01:30:40 -0800 Subject: [PATCH 304/824] Add VIZIO signals to tv.ir (#2167) * Add VIZIO signals to tv.ir * Fix improper formatting Co-authored-by: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> --- assets/resources/infrared/assets/tv.ir | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/assets/resources/infrared/assets/tv.ir b/assets/resources/infrared/assets/tv.ir index 3965013da5b..cc5b0360e6d 100755 --- a/assets/resources/infrared/assets/tv.ir +++ b/assets/resources/infrared/assets/tv.ir @@ -1656,3 +1656,22 @@ type: parsed protocol: RC5 address: 01 00 00 00 command: 21 00 00 00 +# +# Model: VIZIO +name: Mute +type: parsed +protocol: NEC +address: 04 00 00 00 +command: 09 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 04 00 00 00 +command: 02 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 04 00 00 00 +command: 03 00 00 00 From 7ff9414656c11da3164d97a15f670d8865ce77d5 Mon Sep 17 00:00:00 2001 From: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> Date: Wed, 21 Dec 2022 12:42:51 +0300 Subject: [PATCH 305/824] Update CODEOWNERS (#2170) --- .github/CODEOWNERS | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 6b77482c6c8..aa4ada1a81a 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -42,6 +42,9 @@ /applications/debug/unit_tests/ @skotopes @DrZlo13 @hedger @nminaylov @gornekich @Astrrra @gsurkov @Skorpionm +# Assets +/assets/resources/infrared/ @skotopes @DrZlo13 @hedger @gsurkov + # Documentation /documentation/ @skotopes @DrZlo13 @hedger @drunkbatya /scripts/toolchain/ @skotopes @DrZlo13 @hedger @drunkbatya From 566e80abf648c05d046cd4aa90730e173fd4dd70 Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Fri, 23 Dec 2022 12:31:14 +0400 Subject: [PATCH 306/824] [FL-3063] SubGhz: fix start navigation (#2183) --- applications/main/subghz/views/receiver.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/applications/main/subghz/views/receiver.c b/applications/main/subghz/views/receiver.c index 999eef6a56b..aaec2adda7e 100644 --- a/applications/main/subghz/views/receiver.c +++ b/applications/main/subghz/views/receiver.c @@ -309,7 +309,8 @@ bool subghz_view_receiver_input(InputEvent* event, void* context) { subghz_receiver->view, SubGhzViewReceiverModel * model, { - if(model->idx != model->history_item - 1) model->idx++; + if((model->history_item != 0) && (model->idx != model->history_item - 1)) + model->idx++; }, true); } else if(event->key == InputKeyLeft && event->type == InputTypeShort) { From 10580b51171d34c6993cfcd8151fb6a503ef8683 Mon Sep 17 00:00:00 2001 From: valentinegb Date: Fri, 23 Dec 2022 01:13:13 -0800 Subject: [PATCH 307/824] Add Edifier R1850DB signals to audio.ir (#2168) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- assets/resources/infrared/assets/audio.ir | 43 +++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/assets/resources/infrared/assets/audio.ir b/assets/resources/infrared/assets/audio.ir index dec6c05a95f..bcf035df137 100644 --- a/assets/resources/infrared/assets/audio.ir +++ b/assets/resources/infrared/assets/audio.ir @@ -242,3 +242,46 @@ type: raw frequency: 38000 duty_cycle: 0.330000 data: 4639 4406 586 418 585 393 559 447 557 447 557 1477 532 1477 532 472 532 472 532 1476 533 1476 532 1476 532 1476 532 473 555 449 555 449 555 449 555 4455 554 450 554 450 554 450 554 450 554 1455 554 450 554 450 554 450 554 1455 554 1455 553 1455 553 450 554 450 554 1455 554 1455 554 1455 554 450 554 450 554 450 554 1455 554 55454 4557 4458 555 449 555 449 555 450 554 450 554 1455 554 1455 553 450 554 450 554 1455 554 1455 554 1454 554 1455 554 450 554 450 554 450 555 450 554 4455 553 450 554 450 554 450 554 450 554 1455 554 450 554 450 554 450 554 1455 554 1455 554 1455 553 450 554 450 554 1455 554 1455 553 1455 554 450 554 450 554 450 554 1455 554 +# +# Model: Edifier R1850DB +name: Power +type: parsed +protocol: NECext +address: 10 E7 00 00 +command: 46 B9 00 00 +# +name: Play +type: parsed +protocol: NECext +address: 10 E7 00 00 +command: 5E A1 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 10 E7 00 00 +command: 05 FA 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 10 E7 00 00 +command: 49 B6 00 00 +# +name: Next +type: parsed +protocol: NECext +address: 10 E7 00 00 +command: 02 FD 00 00 +# +name: Prev +type: parsed +protocol: NECext +address: 10 E7 00 00 +command: 1E E1 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 10 E7 00 00 +command: 41 BE 00 00 From 2f96fad7c29b994da14100d7fe2ae6af804a7ed5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Fri, 23 Dec 2022 18:46:11 +0900 Subject: [PATCH 308/824] [FL-3026] Documentation: subghz file formats (#2175) Co-authored-by: hedger --- .../file_formats/SubGhzFileFormats.md | 270 ++++++++++++++++++ 1 file changed, 270 insertions(+) create mode 100644 documentation/file_formats/SubGhzFileFormats.md diff --git a/documentation/file_formats/SubGhzFileFormats.md b/documentation/file_formats/SubGhzFileFormats.md new file mode 100644 index 00000000000..ae80e6e0e9c --- /dev/null +++ b/documentation/file_formats/SubGhzFileFormats.md @@ -0,0 +1,270 @@ +# File Formats for Flipper's SubGhz Subsystem + +## `.sub` File Format + +Flipper uses `.sub` files to store SubGhz transmissions. They are text files in Flipper File Format. `.sub` files can contain either a SubGhz Key with a certain protocol or SubGhz RAW data. + +A `.sub` files consist of 3 parts: + +- **header**: contains file type, version, and frequency +- **preset information**: preset type and, in case of a custom preset, transceiver configuration data +- **protocol and its data**: contains protocol name and its specific data, such as key, bit length, etc., or RAW data + +Flipper's SubGhz subsystem uses presets to configure radio transceiver. Presets are used to configure modulation, bandwidth, filters, etc. There are several presets available in stock firmware, and there is a way to create custom presets. See [SubGhz Presets](#adding-a-custom-preset) for more details. + +## Header Format + +Header is a mandatory part of `.sub` file. It contains file type, version, and frequency. + +| Field | Type | Description | +| --- | --- | --- | +| `Filetype` | string | Filetype of subghz file format, must be `Flipper SubGhz Key File` | +| `Version` | uint | Version of subghz file format, current version is 1 | +| `Frequency` | uint | Frequency in Hertz | + +## Preset Information + +Preset information is a mandatory part of `.sub` file. It contains preset type and, in case of custom preset, transceiver configuration data. + +When using one of the standard presets, only `Preset` field is required. When using custom preset, `Custom_preset_module` and `Custom_preset_data` fields are required. + +| Field | Description | +| --- | --- | +| `Preset` | Radio preset name (configures modulation, bandwidth, filters, etc.). When using a custom preset, must be `FuriHalSubGhzPresetCustom` | +| `Custom_preset_module` | Transceiver identifier, `CC1101` for Flipper Zero | +| `Custom_preset_data` | Transceiver configuration data | + +Built-in presets: + +- `FuriHalSubGhzPresetOok270Async` - On/Off Keying, 270kHz bandwidth, async(IO throw GP0) +- `FuriHalSubGhzPresetOok650Async` - On/Off Keying, 650kHz bandwidth, async(IO throw GP0) +- `FuriHalSubGhzPreset2FSKDev238Async` - 2 Frequency Shift Keying, deviation 2kHz, 270kHz bandwidth, async(IO throw GP0) +- `FuriHalSubGhzPreset2FSKDev476Async` - 2 Frequency Shift Keying, deviation 47kHz, 270kHz bandwidth, async(IO throw GP0) + +### Transceiver Configuration Data + +Transceiver configuration data is a string of bytes, encoded in hex format, separated by spaces. For CC1101 data structure is: `XX YY XX YY .. 00 00 ZZ ZZ ZZ ZZ ZZ ZZ ZZ ZZ`, where: + +- XX holds register address, +- YY contains register value, +- 00 00: marks register block end, +- `ZZ ZZ ZZ ZZ ZZ ZZ ZZ ZZ`: 8 byte PA table (Power amplifier ramp table). + +More details can be found in [CC1101 datasheet](https://www.ti.com/lit/ds/symlink/cc1101.pdf) and `furi_hal_subghz` code. + +## File Data + +`.sub` file data section contains either key data — protocol name and its specific data, bit length, etc., or RAW data — an array of signal timings, recorded without any protocol-specific processing. + +### Key Files + +`.sub` files with key data files contain protocol name and its specific data, such as key value, bit length, etc. +Check out protocol registry for full list of supported protocol names. + +Example of key data block in Princeton format: + +``` +... +Protocol: Princeton +Bit: 24 +Key: 00 00 00 00 00 95 D5 D4 +TE: 400 +``` + +Protocol-specific fields in this example: + +| Field | Description | +| --- | --- | +| `Bit` | Princeton payload length, in bits | +| `Key` | Princeton payload data | +| `TE` | Princeton quantization interval | + +This file may contain additional fields, more details on available fields can be found in subghz protocols library. + +### RAW Files + +RAW `.sub` files contain raw signal data that is not processed through protocol-specific decoding. These files are useful for testing purposes, or for sending data that is not supported by any known protocol. + +For RAW files, 2 fields are required: + + * `Protocol`, must be `RAW` + * `RAW_Data`, contains an array of timings, specified in micro seconds. Values must be non-zero, start with a positive number, and interleaved (change sign with each value). Up to 512 values per line. Can be specified multiple times to store multiple lines of data. + +Example of RAW data: + + Protocol: RAW + RAW_Data: 29262 361 -68 2635 -66 24113 -66 11 ... + + +Long payload not fitting into internal memory buffer and consisting of short duration timings (<10us) may not be read fast enough from SD Card. That might cause signal transmission to stop before reaching the end of the payload. Ensure that your SD Card has good performance before transmitting long or complex RAW payloads. + + +## File Examples + +### Key File, Standard Preset + + Filetype: Flipper SubGhz Key File + Version: 1 + Frequency: 433920000 + Preset: FuriHalSubGhzPresetOok650Async + Protocol: Princeton + Bit: 24 + Key: 00 00 00 00 00 95 D5 D4 + TE: 400 + + +### Key File, Custom Preset + + Filetype: Flipper SubGhz Key File + Version: 1 + Frequency: 433920000 + Preset: FuriHalSubGhzPresetCustom + Custom_preset_module: CC1101 + Custom_preset_data: 02 0D 03 07 08 32 0B 06 14 00 13 00 12 30 11 32 10 17 18 18 19 18 1D 91 1C 00 1B 07 20 FB 22 11 21 B6 00 00 00 C0 00 00 00 00 00 00 + Protocol: Princeton + Bit: 24 + Key: 00 00 00 00 00 95 D5 D4 + TE: 400 + +### RAW File, Standard Preset + + Filetype: Flipper SubGhz RAW File + Version: 1 + Frequency: 433920000 + Preset: FuriHalSubGhzPresetOok650Async + Protocol: RAW + RAW_Data: 29262 361 -68 2635 -66 24113 -66 11 ... + RAW_Data: -424 205 -412 159 -412 381 -240 181 ... + RAW_Data: -1448 361 -17056 131 -134 233 -1462 131 -166 953 -100 ... + +### RAW File, Custom Preset + + Filetype: Flipper SubGhz RAW File + Version: 1 + Frequency: 433920000 + Preset: FuriHalSubGhzPresetCustom + Custom_preset_module: CC1101 + Сustom_preset_data: 02 0D 03 07 08 32 0B 06 14 00 13 00 12 30 11 32 10 17 18 18 19 18 1D 91 1C 00 1B 07 20 FB 22 11 21 B6 00 00 00 C0 00 00 00 00 00 00 + Protocol: RAW + RAW_Data: 29262 361 -68 2635 -66 24113 -66 11 ... + RAW_Data: -424 205 -412 159 -412 381 -240 181 ... + RAW_Data: -1448 361 -17056 131 -134 233 -1462 131 -166 953 -100 ... + +# SubGhz Configuration Files + +SubGhz application provides support for adding extra radio presets and additional keys for decoding transmissions in certain protocols. + +## SubGhz `keeloq_mfcodes_user` File + +This file contains additional manufacturer keys for Keeloq protocol. It is used to decode Keeloq transmissions. +This file is loaded at subghz application start and is located at path `/ext/subghz/assets/keeloq_mfcodes_user`. + +### File Format + +File contains a header and a list of manufacturer keys. + +File header format: + +| Field | Type | Description | +| --- | | --- | +| `Filetype` | string | SubGhz Keystore file format, always `Flipper SubGhz Keystore File` | +| `Version` | uint | File format version, 0 | +| `Encryption` | uint | File encryption: for user-provided file, set to 0 (disabled) | + +Following the header, file contains a list of user-provided manufacture keys, one key per line. +For each key, a name and encryption method must be specified, according to comment in file header. More information can be found in keeloq decoder source code. + +### Example + + # to use manual settings and prevent them from being deleted on upgrade, rename *_user.example files to *_user + # for adding manufacture keys + # AABBCCDDEEFFAABB:X:NAME + # AABBCCDDEEFFAABB - man 64 bit + # X - encryption method: + # - 0 - iterates over both previous and man in direct and reverse byte sequence + # - 1 - Simple Learning + # - 2 - Normal_Learning + # - 3 - Secure_Learning + # - 4 - Magic_xor_type1 Learning + # + # NAME - name (string without spaces) max 64 characters long + Filetype: Flipper SubGhz Keystore File + Version: 0 + Encryption: 0 + AABBCCDDEEFFAABB:1:Test1 + AABBCCDDEEFFAABB:1:Test2 + + +## SubGhz `setting_user` File + +This file contains additional radio presets and frequencies for SubGhz application. It is used to add new presets and frequencies for existing presets. This file is be loaded on subghz application start and is located at path `/ext/subghz/assets/setting_user`. + +### File Format + +File contains a header, basic options, and optional lists of presets and frequencies. + +Header must contain following fields: + +- `Filetype`: SubGhz setting file format, must be `Flipper SubGhz Setting File`. +- `Version`: file format version, current is `1`. + +#### Basic Settings + +- `Add_standard_frequencies`: bool, flag indicating whether to load standard frequencies shipped with firmware. If set to `false`, only frequencies specified in this file will be used. +- `Default_frequency`: uint, default frequency used in SubGhz application. + +#### Adding More Frequencies + +- `Frequency`: uint — additional frequency for the subghz application frequency list. Used in Read and Read RAW. You can specify multiple frequencies, one per line. + +#### Adding More Hopper Frequencies + +- `Hopper_frequency`: uint — additional frequency for subghz application frequency hopping. Used in Frequency Analyzer. You can specify multiple frequencies, one per line. + +Repeating same frequency will cause Flipper to listen on this frequency more often. + +#### Adding a Custom Preset + +You can have as many presets as you want. Presets are embedded into `.sub` files, so another Flipper can load them directly from that file. +Each preset is defined by following fields: + +| Field | Description | +| --- | --- | +| `Custom_preset_name` | string, preset name that will be shown in SubGHz application | +| `Custom_preset_module` | string, transceiver identifier. Set to `CC1101` for Flipper Zero | +| `Custom_preset_data` | transceiver configuration data. See [Transceiver Configuration Data](#transceiver-configuration-data) for details. | + +### Example + +``` +# to use manual settings and prevent them from being deleted on upgrade, rename *_user.example files to *_user +Filetype: Flipper SubGhz Setting File +Version: 1 + +# 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 + +# Custom preset +# format for CC1101 "Custom_preset_data:" XX YY XX YY .. 00 00 ZZ ZZ ZZ ZZ ZZ ZZ ZZ ZZ, where: XX-register, YY - register data, 00 00 - end load register, ZZ - 8 byte Pa table register + +#Custom_preset_name: AM_1 +Custom_preset_module: CC1101 +Custom_preset_data: 02 0D 03 07 08 32 0B 06 14 00 13 00 12 30 11 32 10 17 18 18 19 18 1D 91 1C 00 1B 07 20 FB 22 11 21 B6 00 00 00 C0 00 00 00 00 00 00 + +#Custom_preset_name: AM_2 +#Custom_preset_module: CC1101 +#Custom_preset_data: 02 0D 03 07 08 32 0B 06 14 00 13 00 12 30 11 32 10 17 18 18 19 18 1D 91 1C 00 1B 07 20 FB 22 11 21 B6 00 00 00 C0 00 00 00 00 00 00 +``` From dff73dfd388baffea5e57b9efbe8ad34e8da9bb0 Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Fri, 23 Dec 2022 13:25:29 +0300 Subject: [PATCH 309/824] [FL-3037, FL-3038, FL-3039] File format docs: RFID, iButton, BadUSB (#2177) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- .../file_formats/BadUsbScriptFormat.md | 92 +++++++++++++++++++ .../file_formats/LfRfidFileFormat.md | 47 ++++++++++ .../file_formats/iButtonFileFormat.md | 27 ++++++ 3 files changed, 166 insertions(+) create mode 100644 documentation/file_formats/BadUsbScriptFormat.md create mode 100644 documentation/file_formats/LfRfidFileFormat.md create mode 100644 documentation/file_formats/iButtonFileFormat.md diff --git a/documentation/file_formats/BadUsbScriptFormat.md b/documentation/file_formats/BadUsbScriptFormat.md new file mode 100644 index 00000000000..321eff2462e --- /dev/null +++ b/documentation/file_formats/BadUsbScriptFormat.md @@ -0,0 +1,92 @@ +# Command syntax +BadUsb app uses extended Duckyscript syntax. It is compatible with classic USB Rubber Ducky 1.0 scripts, but provides some additional commands and features, such as custom USB ID, ALT+Numpad input method, SYSRQ command and more fuctional keys. +# Script file format +BadUsb app can execute only text scrips from .txt files, no compilation is required. Both `\n` and `\r\n` line endings are supported. Empty lines are allowed. You can use spaces ore tabs for line indentation. +# Command set +## Comment line +Just a single comment line. All text after REM command will be ignored by interpreter +|Command|Parameters|Notes| +|-|-|-| +|REM|Comment text|| + +## Delay +Pause script execution by defined time +|Command|Parameters|Notes| +|-|-|-| +|DELAY|Delay value in ms|Single delay| +|DEFAULT_DELAY|Delay value in ms|Add delay before every next command| +|DEFAULTDELAY|Delay value in ms|Same as DEFAULT_DELAY| + +## Special keys +|Command|Notes| +|-|-| +|DOWNARROW / DOWN|| +|LEFTARROW / LEFT|| +|RIGHTARROW / RIGHT|| +|UPARROW / UP|| +|ENTER|| +|DELETE|| +|BACKSPACE|| +|END|| +|HOME|| +|ESCAPE / ESC|| +|INSERT|| +|PAGEUP|| +|PAGEDOWN|| +|CAPSLOCK|| +|NUMLOCK|| +|SCROLLLOCK|| +|PRINTSCREEN|| +|BREAK|Pause/Break key| +|PAUSE|Pause/Break key| +|SPACE|| +|TAB|| +|MENU|Context menu key| +|APP|Same as MENU| +|Fx|F1-F12 keys| + +## Modifier keys +Can be combined with special key command or single character +|Command|Notes| +|-|-| +|CONTROL / CTRL|| +|SHIFT|| +|ALT|| +|WINDOWS / GUI|| +|CTRL-ALT|CTRL+ALT| +|CTRL-SHIFT|CTRL+SHIFT| +|ALT-SHIFT|ALT+SHIFT| +|ALT-GUI|ALT+WIN| +|GUI-SHIFT|WIN+SHIFT| +## String +|Command|Parameters|Notes| +|-|-|-| +|STRING|Text string|Print text string| +## Repeat +|Command|Parameters|Notes| +|-|-|-| +|REPEAT|Number of additional repeats|Repeat previous command| +## ALT+Numpad input +On Windows and some Linux systems you can print character by pressing ALT key and entering its code on numpad +|Command|Parameters|Notes| +|-|-|-| +|ALTCHAR|Character code|Print single character| +|ALTSTRING|Text string|Print text string using ALT+Numpad method| +|ALTCODE|Text string|Same as ALTSTRING, presents in some Duckyscript implementations| +## SysRq +Send [SysRq command](https://en.wikipedia.org/wiki/Magic_SysRq_key) +|Command|Parameters|Notes| +|-|-|-| +|SYSRQ|Single character|| +## USB device ID +You can set custom ID of Flipper USB HID device. ID command should be in the **first line** of script, it is executed before script run. + +|Command|Parameters|Notes| +|-|-|-| +|ID|VID:PID Manufacturer:Product|| + +Example: +`ID 1234:abcd Flipper Devices:Flipper Zero` + +VID and PID are hex codes and are mandatory, Manufacturer and Product are text strings and are optional. + diff --git a/documentation/file_formats/LfRfidFileFormat.md b/documentation/file_formats/LfRfidFileFormat.md new file mode 100644 index 00000000000..715c49f6ada --- /dev/null +++ b/documentation/file_formats/LfRfidFileFormat.md @@ -0,0 +1,47 @@ +# LF RFID key file format + +## Example +``` +Filetype: Flipper RFID key +Version: 1 +Key type: EM4100 +Data: 01 23 45 67 89 +``` +## Description + +Filename extension: `.rfid` + +The file stores single RFID key of type defined by `Key type` parameter + +### Version history + +1. Initial version. + +### Format fields + +|Name|Description| +|-|-| +|Key type|Key protocol type| +|Data|Key data (HEX values)| + +### Supported key types + +|Type|Full name| +|-|-| +|EM4100|EM-Micro EM4100| +|H10301|HID H10301| +|Idteck|IDTECK| +|Indala26|Motorola Indala26| +|IOProxXSF|Kantech IOProxXSF| +|AWID|AWID| +|FDX-A|FECAVA FDX-A| +|FDX-B|ISO FDX-B| +|HIDProx|Generic HIDProx| +|HIDExt|Generic HIDExt| +|Pyramid|Farpointe Pyramid| +|Viking|Viking| +|Jablotron|Jablotron| +|Paradox|Paradox| +|PAC/Stanley|PAC/Stanley| +|Keri|Keri| +|Gallagher|Gallagher| \ No newline at end of file diff --git a/documentation/file_formats/iButtonFileFormat.md b/documentation/file_formats/iButtonFileFormat.md new file mode 100644 index 00000000000..a5d41b49556 --- /dev/null +++ b/documentation/file_formats/iButtonFileFormat.md @@ -0,0 +1,27 @@ +# iButton key file format + +## Example +``` +Filetype: Flipper iButton key +Version: 1 +# Key type can be Cyfral, Dallas or Metakom +Key type: Dallas +# Data size for Cyfral is 2, for Metakom is 4, for Dallas is 8 +Data: 12 34 56 78 9A BC DE F0 +``` +## Description + +Filename extension: `.ibtn` + +The file stores single iButton key of type defined by `Key type` parameter + +### Version history + +1. Initial version. + +### Format fields + +|Name|Description| +|-|-| +|Key type|Currently supported: Cyfral, Dallas, Metakom| +|Data|Key data (HEX values)| \ No newline at end of file From b0970953b9e29065a809e0a16abe960397fa373a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Fri, 23 Dec 2022 22:46:35 +0900 Subject: [PATCH 310/824] [FL-3067] WeatherStation: fix incorrect history index increment (#2186) --- .../plugins/weather_station/views/weather_station_receiver.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/plugins/weather_station/views/weather_station_receiver.c b/applications/plugins/weather_station/views/weather_station_receiver.c index 124065e3294..de5d7b1a366 100644 --- a/applications/plugins/weather_station/views/weather_station_receiver.c +++ b/applications/plugins/weather_station/views/weather_station_receiver.c @@ -303,7 +303,7 @@ bool ws_view_receiver_input(InputEvent* event, void* context) { ws_receiver->view, WSReceiverModel * model, { - if(model->idx != model->history_item - 1) model->idx++; + if(model->history_item && model->idx != model->history_item - 1) model->idx++; }, true); } else if(event->key == InputKeyLeft && event->type == InputTypeShort) { From c2cb14834d5ef65c36ffcc00ae6bc6ffaf69ca02 Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Sun, 25 Dec 2022 00:13:21 +1000 Subject: [PATCH 311/824] [FL-3062] Fix unit tests (#2180) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * SubGHZ unit test: fail if async_tx is not started * Memgr unit test: fix for multithreaded enviroment * Unit tests: fix failed_tests count * Unit tests: remove debug code * Double update test: increase flipper detection time Co-authored-by: あく --- .../debug/unit_tests/furi/furi_memmgr_test.c | 101 ++++-------------- .../debug/unit_tests/subghz/subghz_test.c | 5 +- applications/debug/unit_tests/test_index.c | 44 ++++---- scripts/testing/await_flipper.py | 2 +- 4 files changed, 48 insertions(+), 104 deletions(-) diff --git a/applications/debug/unit_tests/furi/furi_memmgr_test.c b/applications/debug/unit_tests/furi/furi_memmgr_test.c index cf3848747a8..f7bc234a08c 100644 --- a/applications/debug/unit_tests/furi/furi_memmgr_test.c +++ b/applications/debug/unit_tests/furi/furi_memmgr_test.c @@ -3,98 +3,37 @@ #include #include -// this test is not accurate, but gives a basic understanding -// that memory management is working fine - -// do not include memmgr.h here -// we also test that we are linking against stdlib -extern size_t memmgr_get_free_heap(void); -extern size_t memmgr_get_minimum_free_heap(void); - -// current heap management realization consume: -// X bytes after allocate and 0 bytes after allocate and free, -// where X = sizeof(void*) + sizeof(size_t), look to BlockLink_t -const size_t heap_overhead_max_size = sizeof(void*) + sizeof(size_t); - -bool heap_equal(size_t heap_size, size_t heap_size_old) { - // heap borders with overhead - const size_t heap_low = heap_size_old - heap_overhead_max_size; - const size_t heap_high = heap_size_old + heap_overhead_max_size; - - // not exact, so we must test it against bigger numbers than "overhead size" - const bool result = ((heap_size >= heap_low) && (heap_size <= heap_high)); - - // debug allocation info - if(!result) { - printf("\n(hl: %zu) <= (p: %zu) <= (hh: %zu)\n", heap_low, heap_size, heap_high); - } - - return result; -} - void test_furi_memmgr() { - size_t heap_size = 0; - size_t heap_size_old = 0; - const int alloc_size = 128; - - void* ptr = NULL; - void* original_ptr = NULL; - - // do not include furi memmgr.h case -#ifdef FURI_MEMMGR_GUARD - mu_fail("do not link against furi memmgr.h"); -#endif + void* ptr; // allocate memory case - heap_size_old = memmgr_get_free_heap(); - ptr = malloc(alloc_size); - heap_size = memmgr_get_free_heap(); - mu_assert_pointers_not_eq(ptr, NULL); - mu_assert(heap_equal(heap_size, heap_size_old - alloc_size), "allocate failed"); - - // free memory case - heap_size_old = memmgr_get_free_heap(); + ptr = malloc(100); + mu_check(ptr != NULL); + // test that memory is zero-initialized after allocation + for(int i = 0; i < 100; i++) { + mu_assert_int_eq(0, ((uint8_t*)ptr)[i]); + } free(ptr); - ptr = NULL; - heap_size = memmgr_get_free_heap(); - mu_assert(heap_equal(heap_size, heap_size_old + alloc_size), "free failed"); // reallocate memory case - - // get filled array with some data - original_ptr = malloc(alloc_size); - mu_assert_pointers_not_eq(original_ptr, NULL); - for(int i = 0; i < alloc_size; i++) { - *(unsigned char*)(original_ptr + i) = i; + ptr = malloc(100); + memset(ptr, 66, 100); + ptr = realloc(ptr, 200); + mu_check(ptr != NULL); + + // test that memory is really reallocated + for(int i = 0; i < 100; i++) { + mu_assert_int_eq(66, ((uint8_t*)ptr)[i]); } - // malloc array and copy data - ptr = malloc(alloc_size); - mu_assert_pointers_not_eq(ptr, NULL); - memcpy(ptr, original_ptr, alloc_size); - - // reallocate array - heap_size_old = memmgr_get_free_heap(); - ptr = realloc(ptr, alloc_size * 2); - heap_size = memmgr_get_free_heap(); - mu_assert(heap_equal(heap_size, heap_size_old - alloc_size), "reallocate failed"); - mu_assert_int_eq(memcmp(original_ptr, ptr, alloc_size), 0); - free(original_ptr); + // TODO: fix realloc to copy only old size, and write testcase that leftover of reallocated memory is zero-initialized free(ptr); // allocate and zero-initialize array (calloc) - original_ptr = malloc(alloc_size); - mu_assert_pointers_not_eq(original_ptr, NULL); - - for(int i = 0; i < alloc_size; i++) { - *(unsigned char*)(original_ptr + i) = 0; + ptr = calloc(100, 2); + mu_check(ptr != NULL); + for(int i = 0; i < 100 * 2; i++) { + mu_assert_int_eq(0, ((uint8_t*)ptr)[i]); } - heap_size_old = memmgr_get_free_heap(); - ptr = calloc(1, alloc_size); - heap_size = memmgr_get_free_heap(); - mu_assert(heap_equal(heap_size, heap_size_old - alloc_size), "callocate failed"); - mu_assert_int_eq(memcmp(original_ptr, ptr, alloc_size), 0); - - free(original_ptr); free(ptr); } diff --git a/applications/debug/unit_tests/subghz/subghz_test.c b/applications/debug/unit_tests/subghz/subghz_test.c index d954ddd943e..cb89e1f02ca 100644 --- a/applications/debug/unit_tests/subghz/subghz_test.c +++ b/applications/debug/unit_tests/subghz/subghz_test.c @@ -318,7 +318,10 @@ bool subghz_hal_async_tx_test_run(SubGhzHalAsyncTxTestType type) { furi_hal_subghz_load_preset(FuriHalSubGhzPresetOok650Async); furi_hal_subghz_set_frequency_and_path(433920000); - furi_hal_subghz_start_async_tx(subghz_hal_async_tx_test_yield, &test); + if(!furi_hal_subghz_start_async_tx(subghz_hal_async_tx_test_yield, &test)) { + return false; + } + while(!furi_hal_subghz_is_async_tx_complete()) { furi_delay_ms(10); } diff --git a/applications/debug/unit_tests/test_index.c b/applications/debug/unit_tests/test_index.c index 36c2b4aef92..5bc53c82edb 100644 --- a/applications/debug/unit_tests/test_index.c +++ b/applications/debug/unit_tests/test_index.c @@ -73,7 +73,6 @@ void unit_tests_cli(Cli* cli, FuriString* args, void* context) { UNUSED(cli); UNUSED(args); UNUSED(context); - uint32_t failed_tests = 0; minunit_run = 0; minunit_assert = 0; minunit_fail = 0; @@ -99,32 +98,35 @@ void unit_tests_cli(Cli* cli, FuriString* args, void* context) { if(furi_string_size(args)) { if(furi_string_cmp_str(args, unit_tests[i].name) == 0) { - failed_tests += unit_tests[i].entry(); + unit_tests[i].entry(); } else { printf("Skipping %s\r\n", unit_tests[i].name); } } else { - failed_tests += unit_tests[i].entry(); + unit_tests[i].entry(); } } - printf("\r\nFailed tests: %lu\r\n", failed_tests); - - // Time report - cycle_counter = (furi_get_tick() - cycle_counter); - printf("Consumed: %lu ms\r\n", cycle_counter); - - // Wait for tested services and apps to deallocate memory - furi_delay_ms(200); - uint32_t heap_after = memmgr_get_free_heap(); - printf("Leaked: %ld\r\n", heap_before - heap_after); - - // Final Report - if(failed_tests == 0) { - notification_message(notification, &sequence_success); - printf("Status: PASSED\r\n"); - } else { - notification_message(notification, &sequence_error); - printf("Status: FAILED\r\n"); + + if(minunit_run != 0) { + printf("\r\nFailed tests: %u\r\n", minunit_fail); + + // Time report + cycle_counter = (furi_get_tick() - cycle_counter); + printf("Consumed: %lu ms\r\n", cycle_counter); + + // Wait for tested services and apps to deallocate memory + furi_delay_ms(200); + uint32_t heap_after = memmgr_get_free_heap(); + printf("Leaked: %ld\r\n", heap_before - heap_after); + + // Final Report + if(minunit_fail == 0) { + notification_message(notification, &sequence_success); + printf("Status: PASSED\r\n"); + } else { + notification_message(notification, &sequence_error); + printf("Status: FAILED\r\n"); + } } } diff --git a/scripts/testing/await_flipper.py b/scripts/testing/await_flipper.py index efae6765d07..704d75a7549 100755 --- a/scripts/testing/await_flipper.py +++ b/scripts/testing/await_flipper.py @@ -24,7 +24,7 @@ def flp_serial_by_name(flp_name): return "" -UPDATE_TIMEOUT = 60 +UPDATE_TIMEOUT = 60 * 4 # 4 minutes def main(): From ad3bff0b6793d351080e7cd0be6780d9caaa57fb Mon Sep 17 00:00:00 2001 From: Dmitry Eroshenko Date: Sun, 25 Dec 2022 01:44:04 +0700 Subject: [PATCH 312/824] Update ac.ir (#2184) Co-authored-by: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> --- assets/resources/infrared/assets/ac.ir | 75 ++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/assets/resources/infrared/assets/ac.ir b/assets/resources/infrared/assets/ac.ir index af776706d8f..d9410a30074 100644 --- a/assets/resources/infrared/assets/ac.ir +++ b/assets/resources/infrared/assets/ac.ir @@ -185,4 +185,79 @@ type: raw frequency: 38000 duty_cycle: 0.330000 data: 4403 4333 563 1615 563 528 561 1618 560 1618 560 531 558 561 538 1614 564 556 533 558 541 1611 557 562 537 555 534 1619 559 1618 560 558 541 1613 565 551 538 1614 564 1614 564 1614 564 1613 565 528 561 1617 561 1619 559 1617 561 557 532 535 564 528 561 533 566 1611 557 562 537 558 531 1619 559 1619 559 1619 559 559 540 553 536 557 532 561 538 557 532 559 540 553 536 557 532 1620 558 1620 558 1620 558 1620 558 1618 560 5188 4398 4346 561 1618 560 558 531 1621 557 1621 557 561 538 555 534 1619 559 561 538 553 536 1616 562 556 533 560 539 1614 564 1613 565 554 535 1619 559 557 532 1621 557 1620 558 1620 558 1620 558 560 539 1613 565 1615 563 1613 565 553 536 557 532 535 564 554 535 1618 560 558 531 564 535 1615 563 1615 563 1614 564 555 534 559 540 552 537 530 559 536 563 528 561 532 557 562 537 1615 563 1615 563 1614 564 1614 564 1616 562 +# +# Model: Sharp AH-X9VEW. Doesn't have heat function. +name: Dh +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 307 125509 3831 1867 489 482 491 1392 461 468 485 1397 456 489 464 1411 463 466 487 1397 456 488 465 1390 463 482 481 1402 462 1402 483 471 461 1412 462 468 485 1400 464 1401 484 1397 488 1395 458 471 461 485 457 1408 487 1393 460 485 457 473 459 486 456 474 489 1394 459 471 461 485 457 473 459 488 465 466 455 490 463 467 465 480 462 468 464 482 460 470 483 1399 465 464 457 488 465 465 488 1393 460 484 458 471 461 485 457 1415 459 1405 511 408 482 489 464 466 487 1377 456 489 464 481 461 469 463 482 460 470 462 483 459 470 462 484 458 472 460 485 457 473 459 487 455 474 489 1395 458 471 461 485 457 472 460 486 456 473 459 486 456 474 458 487 455 474 458 488 465 466 487 1396 457 487 434 496 457 488 433 496 457 489 432 497 466 479 432 498 465 480 431 499 464 482 439 490 463 482 460 1394 491 1389 485 1395 490 1392 461 468 464 482 460 469 484 1399 465 1399 465 480 462 483 491 77962 300 +# +name: Cool_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 345 2685 478 1170 176 29680 3832 1891 465 481 482 1400 464 465 488 1395 458 470 483 1396 457 487 466 1406 458 471 492 1389 464 481 461 1411 463 1408 456 489 464 1407 457 473 490 1392 482 1383 481 1399 486 1396 458 462 459 497 456 1409 486 1394 459 459 483 473 459 487 455 474 489 1392 461 468 464 481 461 469 463 483 459 471 482 1398 487 1379 464 481 461 485 457 1416 458 487 466 1407 457 472 460 460 482 474 489 1393 460 469 463 483 459 471 461 485 457 1408 456 488 465 481 461 469 484 1381 462 483 459 486 456 473 459 487 455 474 458 487 455 475 457 489 464 467 465 481 461 1410 485 1380 484 1397 488 1392 461 483 459 471 461 485 457 473 459 486 456 474 458 488 454 476 456 490 463 467 465 481 461 1411 464 481 440 491 462 484 437 492 461 485 436 494 459 487 434 495 458 488 433 497 456 489 432 498 486 450 482 1408 467 1389 485 1394 480 1384 459 486 456 489 464 466 487 1393 460 485 457 1415 459 1406 510 +# +name: Cool_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 181 11813 3819 1899 457 488 486 1421 433 472 491 1416 438 481 461 1417 437 482 492 1416 437 467 486 1419 435 485 457 1439 435 1403 461 483 480 1408 435 484 490 1416 438 1401 484 1395 490 1392 461 467 465 480 462 1427 458 1396 457 486 456 473 459 486 456 472 481 1426 438 466 466 480 462 467 465 480 462 467 465 479 463 1426 438 481 461 484 458 1438 436 1409 486 1378 465 480 462 483 459 471 482 1425 428 475 457 487 455 474 458 487 455 1434 430 489 464 481 461 468 485 1404 439 480 462 483 459 470 462 483 459 469 463 482 460 470 462 483 459 470 462 483 459 1437 458 1381 483 1395 490 1390 463 481 461 469 463 482 460 468 464 481 461 468 464 481 461 469 463 482 460 469 463 481 461 1434 430 474 458 486 456 473 459 486 456 473 459 486 456 473 459 486 456 473 459 485 457 472 460 485 489 449 462 1434 461 1378 486 1393 481 1397 519 400 479 476 456 489 464 1431 433 486 435 495 458 487 487 +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3826 1866 490 481 482 1382 461 484 490 1392 461 467 486 1396 457 471 482 1400 464 479 463 1390 463 481 482 1399 465 1398 518 401 510 1379 464 480 483 1398 456 1408 487 1392 482 1399 517 386 483 487 466 1399 486 1392 461 483 459 470 462 482 460 469 484 1397 456 473 459 459 483 472 460 484 458 471 461 483 459 1411 463 480 462 467 486 1377 487 1392 482 1396 457 486 456 472 460 486 456 473 480 1400 464 465 456 488 454 475 488 1393 460 468 464 480 462 467 486 1395 458 470 462 483 459 485 436 493 460 469 463 481 461 486 435 500 463 463 458 486 456 1397 488 1390 484 1394 480 1400 464 465 456 488 465 464 457 487 455 474 458 487 455 473 459 485 457 472 460 485 457 472 481 1399 517 401 458 497 456 473 459 485 457 487 434 495 458 471 461 484 458 488 433 502 430 507 435 500 432 498 455 1409 486 1392 482 1399 454 1409 455 488 465 480 462 467 465 480 462 1408 456 473 459 486 488 +# +# Model: Electrolux ESV09CRO-B21. Doesn't have heat function. +name: Dh +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3092 3057 3092 4438 579 1675 545 534 576 1650 571 535 575 531 569 1656 575 1652 579 527 573 533 577 1648 572 1655 576 1651 580 526 573 532 578 1647 573 532 578 1647 573 1654 577 529 571 535 575 530 570 535 575 529 571 534 576 529 571 534 576 528 571 534 576 528 572 533 577 528 572 533 577 527 573 1651 580 526 573 532 578 527 572 532 568 537 573 531 568 536 574 1650 571 535 575 531 569 536 574 530 569 535 575 529 571 534 576 529 571 533 577 528 572 533 577 527 572 532 568 536 574 531 569 1655 576 530 570 535 575 530 570 535 575 529 571 534 576 528 572 533 577 527 573 532 578 527 572 531 569 536 574 531 569 535 575 530 570 534 576 529 571 534 576 528 571 533 577 527 573 532 567 537 573 531 569 536 574 530 570 535 575 529 571 534 576 528 571 533 577 527 572 532 578 526 573 531 569 536 574 530 570 535 575 529 571 534 576 528 572 533 577 1646 574 532 578 1646 574 1652 579 527 572 533 577 1647 573 1653 578 1675 545 534 576 1649 571 +# +name: Cool_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 315 101050 3094 3056 3093 4437 580 1648 572 534 576 1649 582 525 574 530 580 1646 574 1653 578 529 570 534 576 529 571 534 576 529 570 1655 576 1651 580 527 572 532 578 1647 573 1654 577 1651 580 526 573 531 579 526 573 531 579 526 573 531 579 526 573 531 579 526 573 531 579 525 574 531 579 525 574 531 579 1646 574 532 578 526 573 531 579 526 573 531 579 526 573 1652 579 527 572 1653 578 528 571 534 576 528 571 533 577 528 571 533 577 528 572 533 577 528 572 532 578 527 572 532 578 527 572 532 578 526 573 1652 579 527 572 532 578 527 572 532 578 527 572 532 578 526 573 531 579 526 573 531 579 526 573 531 579 525 574 530 580 525 574 530 580 525 574 530 580 524 575 529 581 524 575 529 571 534 576 528 571 533 577 528 571 533 577 528 571 533 577 527 572 532 578 527 572 532 578 526 573 531 579 526 573 531 579 525 574 531 579 525 574 530 580 525 574 1650 581 525 574 1651 580 1647 573 533 577 527 572 1653 578 528 572 1654 577 1650 581 1646 574 71637 254 +# +name: Cool_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 284 19161 3098 3053 3096 4435 572 1656 575 532 578 1648 572 534 576 530 570 1682 549 1652 579 527 572 534 576 1649 571 1656 575 1652 579 1649 571 1656 575 531 579 527 572 1653 578 1649 571 1656 575 531 579 527 572 532 578 527 572 533 577 527 572 533 577 527 573 532 578 527 572 532 578 527 573 532 578 527 572 1652 579 527 572 533 577 528 571 533 577 528 571 533 577 1648 572 533 577 1649 571 535 575 530 569 536 574 531 569 536 574 530 569 536 574 530 570 535 575 530 570 535 575 530 569 535 575 530 569 535 575 1649 571 535 575 531 568 536 574 531 568 536 574 531 568 536 574 531 569 536 574 530 569 536 574 530 569 535 575 530 569 535 575 530 569 535 575 530 570 535 575 529 570 534 576 529 570 534 576 529 570 534 576 528 571 534 576 528 571 534 576 528 571 534 576 528 571 534 576 528 571 533 577 528 571 533 577 528 572 533 577 528 571 533 577 528 572 1652 579 527 572 1653 578 529 570 534 576 529 570 535 575 529 570 1654 577 1677 554 1673 547 +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3093 3058 3090 4441 576 1652 579 528 571 1654 577 531 579 526 573 1652 579 1649 582 525 574 1652 579 528 571 1654 577 1651 580 527 572 533 577 528 571 533 577 1649 582 1646 574 1653 578 529 581 525 574 530 580 525 574 530 580 525 574 531 579 526 573 531 579 526 573 531 579 526 573 531 579 526 573 531 579 526 573 531 579 526 573 531 579 526 573 532 578 1647 573 533 577 1648 572 535 575 530 569 535 575 530 580 525 574 531 579 525 574 531 579 526 573 531 579 526 573 531 579 526 573 531 579 526 573 1651 580 527 572 533 577 528 571 533 577 528 571 533 577 528 571 533 577 528 571 534 576 528 571 534 576 529 570 534 576 529 570 534 576 529 570 534 576 529 570 534 576 529 570 534 576 529 570 534 576 529 570 534 576 529 570 534 576 529 570 534 576 529 570 534 576 529 570 534 576 529 570 534 576 529 570 534 576 529 570 534 576 529 570 534 576 1649 582 525 574 1650 581 1647 573 1654 577 1651 580 1647 573 1654 577 531 579 1646 574 1653 578 +# +# Model: Daikin FTE35KV1. Doesn't have heat function. +name: Dh +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 5045 2158 335 1768 358 690 357 723 335 716 331 1771 355 694 364 686 361 720 327 723 335 1767 359 690 357 1775 362 1770 356 692 366 1767 359 1772 354 1777 360 1771 355 1776 361 687 360 690 357 1776 361 687 360 690 357 693 365 716 331 719 328 692 366 1767 359 1772 354 1777 360 1771 355 1776 361 687 360 1773 364 1767 360 689 358 692 366 685 362 688 359 721 326 724 334 717 330 720 327 723 335 715 332 718 329 721 326 1777 360 1771 355 1776 361 1770 356 692 366 715 332 718 329 721 326 29460 5042 2161 332 1770 356 692 366 685 362 688 359 1774 363 685 362 688 359 721 326 694 364 1769 357 691 367 1767 360 1771 355 693 365 1769 358 1773 364 1768 359 1772 365 1766 361 688 359 691 367 1767 360 689 358 692 366 684 363 717 330 720 327 693 365 686 361 689 358 722 336 684 363 1770 357 1774 363 686 361 689 358 1774 363 686 361 1771 356 1776 361 1770 357 692 366 685 362 688 359 690 357 1776 361 687 360 690 357 1776 361 688 359 691 356 694 364 716 331 689 358 1775 362 686 361 689 358 692 366 685 362 688 359 691 356 724 334 716 331 689 358 722 336 685 362 688 359 721 326 693 365 716 331 689 358 692 366 684 363 718 329 690 357 693 365 716 331 689 358 722 336 1767 360 689 358 1774 363 686 361 1771 356 693 365 686 361 689 358 722 336 684 363 717 330 720 327 1776 361 687 360 690 357 693 365 716 331 1771 355 693 365 686 361 1772 355 1776 361 688 359 1773 364 1768 359 +# +name: Cool_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 5038 2165 328 1772 365 686 361 689 358 692 366 1765 362 719 328 692 366 684 363 717 330 1771 366 684 363 1768 359 1774 363 686 361 1771 355 1776 361 1770 357 1775 362 1769 358 691 356 694 364 1767 360 691 356 694 364 686 361 689 358 692 366 685 362 1769 358 1775 362 1769 358 1774 363 1768 359 690 357 1776 361 1770 357 692 366 684 363 687 360 690 357 693 365 686 361 689 358 692 366 684 363 687 360 690 357 693 365 1766 361 1773 364 1767 360 1772 355 694 364 686 361 689 358 692 366 25151 319 3980 5041 2131 362 1769 358 693 365 686 361 689 358 1772 365 686 361 689 358 692 366 684 363 1768 359 692 366 1765 361 1772 354 694 364 1769 358 1774 363 1768 359 1773 364 1767 359 719 328 692 366 1796 331 719 328 692 366 685 362 688 359 691 356 694 364 686 361 689 358 692 366 685 362 1768 359 1775 362 686 361 689 358 1773 364 686 361 1770 357 1777 360 1771 355 693 365 686 361 689 358 1772 365 1769 358 690 357 694 364 1767 360 691 356 694 364 686 361 689 358 723 335 1766 361 690 357 693 365 685 362 688 359 691 356 694 364 687 360 690 357 693 365 685 362 688 359 691 356 694 364 687 360 690 357 693 365 685 362 688 359 691 367 684 363 687 360 1771 366 684 363 687 360 690 357 693 365 1767 360 690 357 1804 333 687 360 690 357 693 365 686 361 689 358 692 366 685 362 1768 359 692 366 685 362 688 359 690 357 1774 363 688 359 691 356 1774 363 1770 356 1775 362 1769 358 691 356 +# +name: Cool_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 301 132136 5036 2167 337 1766 361 689 358 692 366 684 363 1770 357 692 366 684 363 718 329 690 357 1776 361 687 360 1773 364 1767 360 689 358 1775 362 1769 357 1774 363 1768 359 1773 364 684 363 718 329 1773 364 684 363 718 329 691 356 694 364 716 331 719 328 1775 362 1769 358 1774 363 1768 359 1772 365 714 333 1770 357 1774 363 716 331 719 328 722 336 715 332 718 329 721 326 724 334 716 331 719 328 722 336 715 332 718 329 1773 364 1767 360 1772 354 1777 360 719 328 721 326 725 333 717 330 29455 5036 2139 354 1777 360 688 359 691 367 714 333 1770 356 692 366 684 363 687 360 690 357 1776 361 688 359 1773 364 1768 359 689 358 1775 362 1769 357 1774 363 1768 359 1773 364 684 363 687 360 1773 364 685 362 688 359 691 356 694 364 686 361 689 358 692 366 685 362 688 359 691 356 1777 360 1771 355 693 365 685 362 1771 355 693 365 1768 359 1773 364 1767 360 689 358 692 366 685 362 1771 355 1775 362 687 360 690 357 1775 362 687 360 690 357 693 365 716 331 689 358 1774 363 686 361 689 358 692 366 685 362 688 359 691 356 694 364 686 361 689 358 692 366 685 362 688 359 691 356 694 364 686 361 689 358 692 366 685 362 1771 355 693 365 1768 358 1773 364 684 363 687 360 690 357 693 365 1768 359 690 357 1776 361 688 359 691 356 694 364 686 361 689 358 692 366 685 362 1770 356 693 365 685 362 688 359 691 356 1777 360 1771 355 693 365 686 361 689 358 692 366 685 362 1770 356 +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 5043 2132 361 1770 356 723 335 715 332 718 329 1774 363 715 332 719 328 722 336 714 333 1770 356 722 336 1767 360 1772 354 724 334 1769 357 1774 363 1768 358 1773 364 1767 359 720 327 723 335 1768 359 720 327 723 335 716 331 719 328 722 336 714 333 1770 356 1774 363 1769 357 1773 364 1767 360 720 327 1775 362 1769 357 721 326 725 333 717 330 720 327 723 335 716 331 719 328 722 336 714 333 717 330 720 327 723 335 1768 359 1773 364 1767 360 1772 354 724 334 717 330 720 327 723 335 29451 5041 2134 359 1772 354 724 334 717 330 720 327 1775 362 717 330 720 327 723 335 715 332 1771 355 723 335 1768 358 1773 364 715 332 1770 357 1775 362 1769 357 1774 363 1768 359 720 327 723 335 1768 359 720 327 724 334 716 331 719 328 722 336 715 332 718 329 720 327 723 335 716 331 1771 355 1776 361 718 329 721 326 1776 361 718 329 1773 364 1767 360 720 327 723 335 715 332 718 329 1774 363 1768 359 720 327 723 335 1768 358 721 326 724 334 716 331 719 328 722 336 1767 360 719 328 722 336 715 332 718 329 721 326 724 334 717 330 720 327 723 335 715 332 719 328 722 325 725 333 717 330 720 327 723 335 716 331 719 328 1774 363 716 331 1771 355 1776 361 718 329 721 326 724 334 717 330 1772 365 714 333 1770 356 722 336 715 332 718 329 721 326 724 334 717 330 719 328 1775 362 717 330 720 327 723 335 715 332 718 329 1774 363 715 332 718 329 721 326 725 333 717 330 1772 365 From 8582670a341030e43ddf277aad625e9063802cd7 Mon Sep 17 00:00:00 2001 From: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> Date: Mon, 26 Dec 2022 15:13:30 +0300 Subject: [PATCH 313/824] [FL-2811] Fix PVS-Studio warnings (#2142) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく Co-authored-by: gornekich --- .github/workflows/pvs_studio.yml | 3 +- .pvsconfig | 23 +++++++ .pvsoptions | 2 +- .../unit_tests/float_tools/float_tools_test.c | 60 ++++++++++++++++ applications/debug/unit_tests/test_index.c | 2 + .../main/archive/helpers/archive_apps.c | 2 +- .../main/archive/helpers/archive_favorites.c | 2 +- .../archive/scenes/archive_scene_browser.c | 2 +- applications/main/bad_usb/bad_usb_script.c | 14 ++-- applications/main/fap_loader/fap_loader_app.c | 4 +- applications/main/ibutton/ibutton.c | 6 +- applications/main/infrared/infrared.c | 4 +- .../main/infrared/infrared_brute_force.c | 2 +- applications/main/infrared/infrared_cli.c | 10 +-- applications/main/infrared/infrared_remote.c | 14 ++-- applications/main/infrared/infrared_signal.c | 6 +- .../infrared/scenes/infrared_scene_debug.c | 2 +- applications/main/lfrfid/lfrfid.c | 4 +- applications/main/lfrfid/lfrfid_cli.c | 26 +++---- .../main/nfc/scenes/nfc_scene_emulate_uid.c | 2 +- .../nfc/scenes/nfc_scene_mf_classic_emulate.c | 2 +- .../nfc/scenes/nfc_scene_mf_classic_keys.c | 4 +- .../scenes/nfc_scene_mf_classic_keys_list.c | 2 +- .../nfc_scene_mf_desfire_read_success.c | 6 +- .../scenes/nfc_scene_mf_ultralight_emulate.c | 2 +- .../main/nfc/scenes/nfc_scene_nfc_data_info.c | 8 +-- applications/main/nfc/scenes/nfc_scene_read.c | 2 +- .../main/nfc/scenes/nfc_scene_save_name.c | 2 +- .../subghz_frequency_analyzer_worker.c | 5 +- .../subghz/scenes/subghz_scene_read_raw.c | 3 +- .../subghz/scenes/subghz_scene_save_name.c | 2 +- .../subghz/scenes/subghz_scene_set_type.c | 2 +- applications/main/subghz/subghz_cli.c | 19 +++--- applications/main/subghz/subghz_i.c | 5 +- .../subghz/views/subghz_frequency_analyzer.c | 13 ++-- .../main/subghz/views/subghz_read_raw.c | 4 +- .../main/subghz/views/subghz_test_packet.c | 2 +- applications/main/u2f/scenes/u2f_scene_main.c | 2 +- applications/main/u2f/u2f_data.c | 6 +- applications/main/u2f/u2f_hid.c | 6 +- .../plugins/dap_link/usb/dap_v2_usb.c | 8 +-- .../plugins/hid_app/views/hid_keyboard.c | 21 ++---- .../plugins/music_player/music_player.c | 2 +- .../picopass/lib/loclass/optimized_cipher.c | 35 +++------- .../plugins/picopass/picopass_worker.c | 10 ++- .../scenes/picopass_scene_save_name.c | 2 +- .../scenes/signal_gen_scene_pwm.c | 4 +- .../signal_generator/views/signal_gen_pwm.c | 6 +- applications/plugins/snake_game/snake_game.c | 4 +- .../weather_station/protocols/oregon2.c | 2 +- .../weather_station/protocols/oregon_v1.c | 2 +- .../weather_station/protocols/ws_generic.c | 4 +- .../views/weather_station_receiver_info.c | 5 +- .../weather_station/weather_station_app_i.c | 3 - .../weather_station/weather_station_history.c | 2 +- applications/services/bt/bt_service/bt.c | 6 +- .../services/bt/bt_service/bt_keys_storage.c | 2 +- applications/services/cli/cli.c | 4 +- applications/services/cli/cli_command_gpio.c | 68 +++++++++---------- applications/services/cli/cli_commands.c | 14 ++-- applications/services/crypto/crypto_cli.c | 2 +- .../desktop/animations/animation_manager.c | 10 ++- .../desktop/animations/animation_storage.c | 5 +- .../services/desktop/helpers/slideshow.c | 2 +- .../desktop/views/desktop_view_debug.c | 8 +-- .../desktop/views/desktop_view_lock_menu.c | 6 +- .../desktop/views/desktop_view_pin_timeout.c | 2 +- .../services/dolphin/helpers/dolphin_state.c | 2 +- applications/services/gui/elements.c | 2 +- .../services/gui/modules/button_panel.c | 2 +- .../services/gui/modules/byte_input.c | 9 ++- .../services/gui/modules/file_browser.c | 6 +- .../gui/modules/file_browser_worker.c | 4 +- applications/services/gui/modules/submenu.c | 10 +-- .../services/gui/modules/text_input.c | 15 ++-- .../services/gui/modules/validators.c | 7 +- .../services/gui/modules/variable_item_list.c | 4 +- .../widget_elements/widget_element_button.c | 4 +- .../widget_elements/widget_element_string.c | 2 +- .../widget_element_string_multiline.c | 2 +- .../widget_elements/widget_element_text_box.c | 2 +- .../widget_element_text_scroll.c | 2 +- applications/services/gui/view.c | 4 +- applications/services/input/input.c | 4 +- applications/services/input/input_cli.c | 2 +- applications/services/loader/loader.c | 2 +- .../power/power_service/views/power_off.c | 2 +- applications/services/rpc/rpc_app.c | 22 +++--- applications/services/rpc/rpc_cli.c | 2 +- applications/services/rpc/rpc_debug.c | 14 ++-- applications/services/rpc/rpc_storage.c | 2 +- applications/services/rpc/rpc_system.c | 1 - .../services/storage/storage_external_api.c | 8 +-- applications/services/storage/storage_glue.c | 3 +- .../services/storage/storages/storage_int.c | 46 ++++--------- applications/settings/about/about.c | 4 +- .../scenes/desktop_settings_scene_favorite.c | 2 +- .../desktop_settings_scene_pin_setup_howto.c | 4 +- .../desktop_settings_scene_pin_setup_howto2.c | 4 +- .../power_settings_app/views/battery_info.c | 12 ++-- .../storage_settings_scene_format_confirm.c | 2 - .../scenes/storage_settings_scene_sd_info.c | 2 - .../storage_settings_scene_unmount_confirm.c | 2 - .../updater/util/update_task_worker_backup.c | 2 +- .../updater/util/update_task_worker_flasher.c | 8 +-- debug/gdbinit | 10 +++ firmware/targets/f7/Src/update.c | 2 +- firmware/targets/f7/ble_glue/ble_glue.c | 18 +++-- firmware/targets/f7/ble_glue/gap.c | 4 +- firmware/targets/f7/ble_glue/hid_service.c | 2 +- firmware/targets/f7/ble_glue/hw_ipcc.c | 4 +- firmware/targets/f7/fatfs/stm32_adafruit_sd.c | 10 +-- firmware/targets/f7/furi_hal/furi_hal_bt.c | 2 +- .../targets/f7/furi_hal/furi_hal_bt_hid.c | 6 +- .../targets/f7/furi_hal/furi_hal_i2c_config.c | 4 +- .../targets/f7/furi_hal/furi_hal_memory.c | 6 +- firmware/targets/f7/furi_hal/furi_hal_nfc.c | 10 +-- firmware/targets/f7/furi_hal/furi_hal_os.c | 4 +- firmware/targets/f7/furi_hal/furi_hal_pwm.c | 2 +- firmware/targets/f7/furi_hal/furi_hal_rtc.c | 2 +- firmware/targets/f7/furi_hal/furi_hal_uart.c | 4 +- .../targets/f7/furi_hal/furi_hal_usb_hid.c | 4 +- .../targets/f7/furi_hal/furi_hal_version.c | 2 - furi/core/check.c | 2 +- furi/core/core_defines.h | 2 +- furi/core/event_flag.c | 6 +- furi/core/memmgr_heap.c | 5 +- furi/core/message_queue.c | 5 +- furi/core/mutex.c | 2 + furi/core/stream_buffer.c | 10 ++- furi/core/string.c | 10 +-- furi/core/thread.c | 8 +-- furi/core/timer.c | 44 ++++-------- .../view_modules/popup_vm.cpp | 3 +- lib/drivers/cc1101.c | 2 +- lib/drivers/lp5562.c | 4 +- lib/flipper_application/elf/elf_file.c | 22 +++--- lib/flipper_format/flipper_format_stream.c | 4 +- .../common/infrared_common_decoder.c | 2 +- .../common/infrared_common_encoder.c | 1 - .../common/infrared_common_i.h | 2 +- lib/infrared/worker/infrared_transmit.c | 5 +- lib/infrared/worker/infrared_worker.c | 6 +- lib/infrared/worker/infrared_worker.h | 2 +- lib/lfrfid/lfrfid_raw_worker.c | 5 +- lib/lfrfid/lfrfid_worker.c | 4 +- lib/lfrfid/lfrfid_worker_modes.c | 6 +- lib/lfrfid/protocols/protocol_indala26.c | 2 +- lib/lfrfid/protocols/protocol_pac_stanley.c | 8 +-- lib/lfrfid/tools/bit_lib.c | 10 +-- lib/lfrfid/tools/bit_lib.h | 8 +-- lib/lfrfid/tools/varint_pair.c | 4 +- lib/nfc/helpers/mf_classic_dict.c | 10 +-- lib/nfc/helpers/reader_analyzer.c | 4 +- lib/nfc/nfc_device.c | 4 +- lib/nfc/nfc_worker.c | 22 +++--- lib/nfc/parsers/all_in_one.c | 14 +--- lib/nfc/parsers/plantain_4k_parser.c | 19 +----- lib/nfc/parsers/plantain_parser.c | 19 +----- lib/nfc/parsers/two_cities.c | 18 +---- lib/nfc/protocols/crypto1.c | 3 +- lib/nfc/protocols/mifare_classic.c | 45 +++++------- lib/nfc/protocols/mifare_desfire.c | 16 +++-- lib/nfc/protocols/mifare_ultralight.c | 24 +++---- lib/one_wire/ibutton/ibutton_key.c | 2 - lib/one_wire/ibutton/ibutton_worker_modes.c | 1 - lib/one_wire/ibutton/ibutton_writer.c | 2 +- .../ibutton/protocols/protocol_cyfral.c | 6 +- .../ibutton/protocols/protocol_metakom.c | 8 +-- lib/one_wire/one_wire_slave.c | 12 ++-- lib/print/printf_tiny.c | 2 +- lib/subghz/blocks/generic.c | 2 +- lib/subghz/blocks/math.h | 3 +- lib/subghz/protocols/bett.c | 1 - lib/subghz/protocols/holtek.c | 1 - lib/subghz/protocols/keeloq.c | 9 +-- lib/subghz/protocols/kia.c | 2 +- lib/subghz/protocols/megacode.c | 2 +- lib/subghz/protocols/nero_radio.c | 2 +- lib/subghz/protocols/princeton.c | 2 +- lib/subghz/protocols/princeton_for_testing.c | 4 +- lib/subghz/protocols/scher_khan.c | 4 +- lib/subghz/protocols/secplus_v1.c | 8 +-- lib/subghz/protocols/secplus_v2.c | 20 +++--- lib/subghz/protocols/smc5326.c | 4 +- lib/subghz/subghz_file_encoder_worker.c | 2 +- lib/subghz/subghz_keystore.c | 12 ++-- lib/subghz/subghz_setting.c | 10 ++- lib/toolbox/dir_walk.c | 7 +- lib/toolbox/float_tools.c | 8 +++ lib/toolbox/float_tools.h | 19 ++++++ lib/toolbox/hex.c | 16 ++--- lib/toolbox/md5.c | 12 ++-- lib/toolbox/sha256.c | 10 +-- lib/toolbox/stream/stream.c | 4 +- lib/toolbox/tar/tar_archive.c | 4 +- lib/toolbox/varint.c | 4 +- lib/update_util/dfu_file.c | 2 +- lib/update_util/resources/manifest.c | 2 +- lib/update_util/update_operation.c | 4 +- scripts/fbt_tools/fbt_debugopts.py | 4 +- 201 files changed, 717 insertions(+), 741 deletions(-) create mode 100644 applications/debug/unit_tests/float_tools/float_tools_test.c create mode 100644 debug/gdbinit create mode 100644 lib/toolbox/float_tools.c create mode 100644 lib/toolbox/float_tools.h diff --git a/.github/workflows/pvs_studio.yml b/.github/workflows/pvs_studio.yml index 5bb04afcbc5..c521fbca4d3 100644 --- a/.github/workflows/pvs_studio.yml +++ b/.github/workflows/pvs_studio.yml @@ -50,7 +50,7 @@ jobs: - name: 'Generate compile_comands.json' run: | - ./fbt COMPACT=1 version_json proto_ver icons firmware_cdb dolphin_internal dolphin_blocking _fap_icons + ./fbt COMPACT=1 version_json proto_ver icons firmware_cdb dolphin_internal dolphin_blocking _fap_icons api_syms - name: 'Static code analysis' run: | @@ -58,6 +58,7 @@ jobs: pvs-studio-analyzer credentials ${{ secrets.PVS_STUDIO_CREDENTIALS }} pvs-studio-analyzer analyze \ @.pvsoptions \ + -C gccarm \ -j$(grep -c processor /proc/cpuinfo) \ -f build/f7-firmware-DC/compile_commands.json \ -o PVS-Studio.log diff --git a/.pvsconfig b/.pvsconfig index 5f1ffb7cb5b..a9ab9c9f66b 100644 --- a/.pvsconfig +++ b/.pvsconfig @@ -1,4 +1,5 @@ # MLib macros we can't do much about. +//-V:M_LET:1048,1044 //-V:M_EACH:1048,1044 //-V:ARRAY_DEF:760,747,568,776,729,712,654 //-V:LIST_DEF:760,747,568,712,729,654,776 @@ -16,8 +17,30 @@ # Potentially null argument warnings //-V:memset:575 //-V:memcpy:575 +//-V:memcmp:575 +//-V:strlen:575 //-V:strcpy:575 +//-V:strncpy:575 //-V:strchr:575 # For loop warning on M_FOREACH //-V:for:1044 + +# Bitwise OR +//-V:bit:792 + +# Do not complain about similar code +//-V::525 + +# Common embedded development pointer operations +//-V::566 +//-V::1032 + +# Warnings about length mismatch +//-V:property_value_out:666 + +# Model-related warnings +//-V:with_view_model:1044,1048 + +# Functions that always return the same error code +//-V:picopass_device_decrypt:1048 diff --git a/.pvsoptions b/.pvsoptions index 4c80ab66788..31bc4b8046e 100644 --- a/.pvsoptions +++ b/.pvsoptions @@ -1 +1 @@ ---rules-config .pvsconfig -e lib/fatfs -e lib/fnv1a-hash -e lib/FreeRTOS-Kernel -e lib/heatshrink -e lib/libusb_stm32 -e lib/littlefs -e lib/mbedtls -e lib/micro-ecc -e lib/microtar -e lib/mlib -e lib/qrcode -e lib/ST25RFAL002 -e lib/STM32CubeWB -e lib/u8g2 -e */arm-none-eabi/* +--rules-config .pvsconfig -e lib/fatfs -e lib/fnv1a-hash -e lib/FreeRTOS-Kernel -e lib/heatshrink -e lib/libusb_stm32 -e lib/littlefs -e lib/mbedtls -e lib/micro-ecc -e lib/microtar -e lib/mlib -e lib/qrcode -e lib/ST25RFAL002 -e lib/STM32CubeWB -e lib/u8g2 -e lib/nanopb -e */arm-none-eabi/* -e applications/plugins/dap_link/lib/free-dap diff --git a/applications/debug/unit_tests/float_tools/float_tools_test.c b/applications/debug/unit_tests/float_tools/float_tools_test.c new file mode 100644 index 00000000000..fc5b4ecfd8b --- /dev/null +++ b/applications/debug/unit_tests/float_tools/float_tools_test.c @@ -0,0 +1,60 @@ +#include +#include + +#include "../minunit.h" + +MU_TEST(float_tools_equal_test) { + mu_check(float_is_equal(FLT_MAX, FLT_MAX)); + mu_check(float_is_equal(FLT_MIN, FLT_MIN)); + mu_check(float_is_equal(-FLT_MAX, -FLT_MAX)); + mu_check(float_is_equal(-FLT_MIN, -FLT_MIN)); + + mu_check(!float_is_equal(FLT_MIN, FLT_MAX)); + mu_check(!float_is_equal(-FLT_MIN, FLT_MAX)); + mu_check(!float_is_equal(FLT_MIN, -FLT_MAX)); + mu_check(!float_is_equal(-FLT_MIN, -FLT_MAX)); + + const float pi = 3.14159f; + mu_check(float_is_equal(pi, pi)); + mu_check(float_is_equal(-pi, -pi)); + mu_check(!float_is_equal(pi, -pi)); + mu_check(!float_is_equal(-pi, pi)); + + const float one_third = 1.f / 3.f; + const float one_third_dec = 0.3333333f; + mu_check(one_third != one_third_dec); + mu_check(float_is_equal(one_third, one_third_dec)); + + const float big_num = 1.e12f; + const float med_num = 95.389f; + const float smol_num = 1.e-12f; + mu_check(float_is_equal(big_num, big_num)); + mu_check(float_is_equal(med_num, med_num)); + mu_check(float_is_equal(smol_num, smol_num)); + mu_check(!float_is_equal(smol_num, big_num)); + mu_check(!float_is_equal(med_num, smol_num)); + mu_check(!float_is_equal(big_num, med_num)); + + const float more_than_one = 1.f + FLT_EPSILON; + const float less_than_one = 1.f - FLT_EPSILON; + mu_check(!float_is_equal(more_than_one, less_than_one)); + mu_check(!float_is_equal(more_than_one, -less_than_one)); + mu_check(!float_is_equal(-more_than_one, less_than_one)); + mu_check(!float_is_equal(-more_than_one, -less_than_one)); + + const float slightly_more_than_one = 1.f + FLT_EPSILON / 2.f; + const float slightly_less_than_one = 1.f - FLT_EPSILON / 2.f; + mu_check(float_is_equal(slightly_more_than_one, slightly_less_than_one)); + mu_check(float_is_equal(-slightly_more_than_one, -slightly_less_than_one)); + mu_check(!float_is_equal(slightly_more_than_one, -slightly_less_than_one)); + mu_check(!float_is_equal(-slightly_more_than_one, slightly_less_than_one)); +} + +MU_TEST_SUITE(float_tools_suite) { + MU_RUN_TEST(float_tools_equal_test); +} + +int run_minunit_test_float_tools() { + MU_RUN_SUITE(float_tools_suite); + return MU_EXIT_CODE; +} diff --git a/applications/debug/unit_tests/test_index.c b/applications/debug/unit_tests/test_index.c index 5bc53c82edb..ccf47153162 100644 --- a/applications/debug/unit_tests/test_index.c +++ b/applications/debug/unit_tests/test_index.c @@ -24,6 +24,7 @@ int run_minunit_test_protocol_dict(); int run_minunit_test_lfrfid_protocols(); int run_minunit_test_nfc(); int run_minunit_test_bit_lib(); +int run_minunit_test_float_tools(); int run_minunit_test_bt(); typedef int (*UnitTestEntry)(); @@ -50,6 +51,7 @@ const UnitTest unit_tests[] = { {.name = "protocol_dict", .entry = run_minunit_test_protocol_dict}, {.name = "lfrfid", .entry = run_minunit_test_lfrfid_protocols}, {.name = "bit_lib", .entry = run_minunit_test_bit_lib}, + {.name = "float_tools", .entry = run_minunit_test_float_tools}, {.name = "bt", .entry = run_minunit_test_bt}, }; diff --git a/applications/main/archive/helpers/archive_apps.c b/applications/main/archive/helpers/archive_apps.c index 72084f1134e..c8ad67625f1 100644 --- a/applications/main/archive/helpers/archive_apps.c +++ b/applications/main/archive/helpers/archive_apps.c @@ -13,7 +13,7 @@ ArchiveAppTypeEnum archive_get_app_type(const char* path) { } app_name++; - for(size_t i = 0; i < COUNT_OF(known_apps); i++) { + for(size_t i = 0; i < COUNT_OF(known_apps); i++) { //-V1008 if(strncmp(app_name, known_apps[i], strlen(known_apps[i])) == 0) { return i; } diff --git a/applications/main/archive/helpers/archive_favorites.c b/applications/main/archive/helpers/archive_favorites.c index 86a294f789c..8bbcb52136c 100644 --- a/applications/main/archive/helpers/archive_favorites.c +++ b/applications/main/archive/helpers/archive_favorites.c @@ -177,7 +177,7 @@ bool archive_favorites_read(void* context) { archive_set_item_count(browser, file_count); - if(need_refresh) { + if(need_refresh) { //-V547 archive_favourites_rescan(); } diff --git a/applications/main/archive/scenes/archive_scene_browser.c b/applications/main/archive/scenes/archive_scene_browser.c index 2f469354858..c28f91f5244 100644 --- a/applications/main/archive/scenes/archive_scene_browser.c +++ b/applications/main/archive/scenes/archive_scene_browser.c @@ -116,7 +116,7 @@ bool archive_scene_browser_on_event(void* context, SceneManagerEvent event) { case ArchiveBrowserEventFileMenuPin: { const char* name = archive_get_name(browser); if(favorites) { - archive_favorites_delete(name); + archive_favorites_delete("%s", name); archive_file_array_rm_selected(browser); archive_show_file_menu(browser, false); } else if(archive_is_known_app(selected->type)) { diff --git a/applications/main/bad_usb/bad_usb_script.c b/applications/main/bad_usb/bad_usb_script.c index aa465351e20..92c7466f158 100644 --- a/applications/main/bad_usb/bad_usb_script.c +++ b/applications/main/bad_usb/bad_usb_script.c @@ -218,8 +218,8 @@ static bool ducky_string(const char* param) { } static uint16_t ducky_get_keycode(const char* param, bool accept_chars) { - for(uint8_t i = 0; i < (sizeof(ducky_keys) / sizeof(ducky_keys[0])); i++) { - uint8_t key_cmd_len = strlen(ducky_keys[i].name); + for(size_t i = 0; i < (sizeof(ducky_keys) / sizeof(ducky_keys[0])); i++) { + size_t key_cmd_len = strlen(ducky_keys[i].name); if((strncmp(param, ducky_keys[i].name, key_cmd_len) == 0) && (ducky_is_line_end(param[key_cmd_len]))) { return ducky_keys[i].keycode; @@ -417,7 +417,7 @@ static int32_t ducky_script_execute_next(BadUsbScript* bad_usb, File* script_fil return 0; } else if(delay_val < 0) { // Script error bad_usb->st.error_line = bad_usb->st.line_cur - 1; - FURI_LOG_E(WORKER_TAG, "Unknown command at line %u", bad_usb->st.line_cur - 1); + FURI_LOG_E(WORKER_TAG, "Unknown command at line %u", bad_usb->st.line_cur - 1U); return SCRIPT_STATE_ERROR; } else { return (delay_val + bad_usb->defdelay); @@ -596,7 +596,9 @@ static int32_t bad_usb_worker(void* context) { } bad_usb->st.state = worker_state; continue; - } else if((flags == FuriFlagErrorTimeout) || (flags == FuriFlagErrorResource)) { + } else if( + (flags == (unsigned)FuriFlagErrorTimeout) || + (flags == (unsigned)FuriFlagErrorResource)) { if(delay_val > 0) { bad_usb->st.delay_remain--; continue; @@ -650,7 +652,7 @@ static int32_t bad_usb_worker(void* context) { BadUsbScript* bad_usb_script_open(FuriString* file_path) { furi_assert(file_path); - BadUsbScript* bad_usb = malloc(sizeof(BadUsbScript)); //-V773 + BadUsbScript* bad_usb = malloc(sizeof(BadUsbScript)); bad_usb->file_path = furi_string_alloc(); furi_string_set(bad_usb->file_path, file_path); @@ -660,7 +662,7 @@ BadUsbScript* bad_usb_script_open(FuriString* file_path) { bad_usb->thread = furi_thread_alloc_ex("BadUsbWorker", 2048, bad_usb_worker, bad_usb); furi_thread_start(bad_usb->thread); return bad_usb; -} +} //-V773 void bad_usb_script_close(BadUsbScript* bad_usb) { furi_assert(bad_usb); diff --git a/applications/main/fap_loader/fap_loader_app.c b/applications/main/fap_loader/fap_loader_app.c index 90186674614..7911aa06891 100644 --- a/applications/main/fap_loader/fap_loader_app.c +++ b/applications/main/fap_loader/fap_loader_app.c @@ -156,7 +156,7 @@ static bool fap_loader_select_app(FapLoader* loader) { } static FapLoader* fap_loader_alloc(const char* path) { - FapLoader* loader = malloc(sizeof(FapLoader)); //-V773 + FapLoader* loader = malloc(sizeof(FapLoader)); //-V799 loader->fap_path = furi_string_alloc_set(path); loader->storage = furi_record_open(RECORD_STORAGE); loader->dialogs = furi_record_open(RECORD_DIALOGS); @@ -167,7 +167,7 @@ static FapLoader* fap_loader_alloc(const char* path) { loader->view_dispatcher, loader->gui, ViewDispatcherTypeFullscreen); view_dispatcher_add_view(loader->view_dispatcher, 0, loading_get_view(loader->loading)); return loader; -} +} //-V773 static void fap_loader_free(FapLoader* loader) { view_dispatcher_remove_view(loader->view_dispatcher, 0); diff --git a/applications/main/ibutton/ibutton.c b/applications/main/ibutton/ibutton.c index b7c8223b097..85212f42b6b 100644 --- a/applications/main/ibutton/ibutton.c +++ b/applications/main/ibutton/ibutton.c @@ -278,7 +278,7 @@ bool ibutton_save_key(iButton* ibutton, const char* key_name) { flipper_format_free(file); - if(!result) { + if(!result) { //-V547 dialog_message_show_storage_error(ibutton->dialogs, "Cannot save\nkey file"); } @@ -302,7 +302,7 @@ void ibutton_text_store_set(iButton* ibutton, const char* text, ...) { } void ibutton_text_store_clear(iButton* ibutton) { - memset(ibutton->text_store, 0, IBUTTON_TEXT_STORE_SIZE); + memset(ibutton->text_store, 0, IBUTTON_TEXT_STORE_SIZE + 1); } void ibutton_notification_message(iButton* ibutton, uint32_t message) { @@ -343,7 +343,7 @@ int32_t ibutton_app(void* p) { } else { view_dispatcher_attach_to_gui( ibutton->view_dispatcher, ibutton->gui, ViewDispatcherTypeFullscreen); - if(key_loaded) { + if(key_loaded) { //-V547 scene_manager_next_scene(ibutton->scene_manager, iButtonSceneEmulate); DOLPHIN_DEED(DolphinDeedIbuttonEmulate); } else { diff --git a/applications/main/infrared/infrared.c b/applications/main/infrared/infrared.c index f62db14c124..9d78a09b6e2 100644 --- a/applications/main/infrared/infrared.c +++ b/applications/main/infrared/infrared.c @@ -360,7 +360,7 @@ void infrared_text_store_set(Infrared* infrared, uint32_t bank, const char* text } void infrared_text_store_clear(Infrared* infrared, uint32_t bank) { - memset(infrared->text_store[bank], 0, INFRARED_TEXT_STORE_SIZE); + memset(infrared->text_store[bank], 0, INFRARED_TEXT_STORE_SIZE + 1); } void infrared_play_notification_message(Infrared* infrared, uint32_t message) { @@ -455,7 +455,7 @@ int32_t infrared_app(void* p) { } else { view_dispatcher_attach_to_gui( infrared->view_dispatcher, infrared->gui, ViewDispatcherTypeFullscreen); - if(is_remote_loaded) { + if(is_remote_loaded) { //-V547 scene_manager_next_scene(infrared->scene_manager, InfraredSceneRemote); } else { scene_manager_next_scene(infrared->scene_manager, InfraredSceneStart); diff --git a/applications/main/infrared/infrared_brute_force.c b/applications/main/infrared/infrared_brute_force.c index 3f426f1dce0..3ca5c409f79 100644 --- a/applications/main/infrared/infrared_brute_force.c +++ b/applications/main/infrared/infrared_brute_force.c @@ -65,7 +65,7 @@ bool infrared_brute_force_calculate_messages(InfraredBruteForce* brute_force) { while(flipper_format_read_string(ff, "name", signal_name)) { InfraredBruteForceRecord* record = InfraredBruteForceRecordDict_get(brute_force->records, signal_name); - if(record) { + if(record) { //-V547 ++(record->count); } } diff --git a/applications/main/infrared/infrared_cli.c b/applications/main/infrared/infrared_cli.c index 8f35a8fd150..5f5e2d4bbdb 100644 --- a/applications/main/infrared/infrared_cli.c +++ b/applications/main/infrared/infrared_cli.c @@ -55,7 +55,7 @@ static void signal_received_callback(void* context, InfraredWorkerSignal* receiv size_t timings_cnt; infrared_worker_get_raw_signal(received_signal, &timings, &timings_cnt); - buf_cnt = snprintf(buf, sizeof(buf), "RAW, %d samples:\r\n", timings_cnt); + buf_cnt = snprintf(buf, sizeof(buf), "RAW, %zu samples:\r\n", timings_cnt); cli_write(cli, (uint8_t*)buf, buf_cnt); for(size_t i = 0; i < timings_cnt; ++i) { buf_cnt = snprintf(buf, sizeof(buf), "%lu ", timings[i]); @@ -276,7 +276,9 @@ static bool infrared_cli_decode_file(FlipperFormat* input_file, FlipperFormat* o } InfraredRawSignal* raw_signal = infrared_signal_get_raw_signal(signal); printf( - "Raw signal: %s, %u samples\r\n", furi_string_get_cstr(tmp), raw_signal->timings_size); + "Raw signal: %s, %zu samples\r\n", + furi_string_get_cstr(tmp), + raw_signal->timings_size); if(!infrared_cli_decode_raw_signal( raw_signal, decoder, output_file, furi_string_get_cstr(tmp))) break; @@ -382,7 +384,7 @@ static void infrared_cli_list_remote_signals(FuriString* remote_name) { while(flipper_format_read_string(ff, "name", signal_name)) { furi_string_set_str(key, furi_string_get_cstr(signal_name)); int* v = dict_signals_get(signals_dict, key); - if(v != NULL) { + if(v != NULL) { //-V547 (*v)++; max = M_MAX(*v, max); } else { @@ -436,7 +438,7 @@ static void break; } - printf("Sending %ld signal(s)...\r\n", record_count); + printf("Sending %lu signal(s)...\r\n", record_count); printf("Press Ctrl-C to stop.\r\n"); int records_sent = 0; diff --git a/applications/main/infrared/infrared_remote.c b/applications/main/infrared/infrared_remote.c index 3a528a65606..d3dfc2cce91 100644 --- a/applications/main/infrared/infrared_remote.c +++ b/applications/main/infrared/infrared_remote.c @@ -145,15 +145,14 @@ bool infrared_remote_load(InfraredRemote* remote, FuriString* path) { buf = furi_string_alloc(); FURI_LOG_I(TAG, "load file: \'%s\'", furi_string_get_cstr(path)); - bool success = flipper_format_buffered_file_open_existing(ff, furi_string_get_cstr(path)); + bool success = false; - if(success) { + do { + if(!flipper_format_buffered_file_open_existing(ff, furi_string_get_cstr(path))) break; uint32_t version; - success = flipper_format_read_header(ff, buf, &version) && - !furi_string_cmp(buf, "IR signals file") && (version == 1); - } + if(!flipper_format_read_header(ff, buf, &version)) break; + if(!furi_string_equal(buf, "IR signals file") || (version != 1)) break; - if(success) { path_extract_filename(path, buf, true); infrared_remote_clear_buttons(remote); infrared_remote_set_name(remote, furi_string_get_cstr(buf)); @@ -169,7 +168,8 @@ bool infrared_remote_load(InfraredRemote* remote, FuriString* path) { infrared_remote_button_free(button); } } - } + success = true; + } while(false); furi_string_free(buf); flipper_format_free(ff); diff --git a/applications/main/infrared/infrared_signal.c b/applications/main/infrared/infrared_signal.c index d399b95879d..9154dfbf68e 100644 --- a/applications/main/infrared/infrared_signal.c +++ b/applications/main/infrared/infrared_signal.c @@ -74,7 +74,7 @@ static bool infrared_signal_is_raw_valid(InfraredRawSignal* raw) { } else if((raw->timings_size <= 0) || (raw->timings_size > MAX_TIMINGS_AMOUNT)) { FURI_LOG_E( TAG, - "Timings amount is out of range (0 - %X): %X", + "Timings amount is out of range (0 - %X): %zX", MAX_TIMINGS_AMOUNT, raw->timings_size); return false; @@ -275,8 +275,8 @@ bool infrared_signal_search_and_read( is_name_found = furi_string_equal(name, tmp); if(is_name_found) break; } - if(!is_name_found) break; - if(!infrared_signal_read_body(signal, ff)) break; + if(!is_name_found) break; //-V547 + if(!infrared_signal_read_body(signal, ff)) break; //-V779 success = true; } while(false); diff --git a/applications/main/infrared/scenes/infrared_scene_debug.c b/applications/main/infrared/scenes/infrared_scene_debug.c index dd0609b56c1..204978697b8 100644 --- a/applications/main/infrared/scenes/infrared_scene_debug.c +++ b/applications/main/infrared/scenes/infrared_scene_debug.c @@ -26,7 +26,7 @@ bool infrared_scene_debug_on_event(void* context, SceneManagerEvent event) { InfraredRawSignal* raw = infrared_signal_get_raw_signal(signal); infrared_debug_view_set_text(debug_view, "RAW\n%d samples\n", raw->timings_size); - printf("RAW, %d samples:\r\n", raw->timings_size); + printf("RAW, %zu samples:\r\n", raw->timings_size); for(size_t i = 0; i < raw->timings_size; ++i) { printf("%lu ", raw->timings[i]); } diff --git a/applications/main/lfrfid/lfrfid.c b/applications/main/lfrfid/lfrfid.c index 2207e7e0797..85a00eea00d 100644 --- a/applications/main/lfrfid/lfrfid.c +++ b/applications/main/lfrfid/lfrfid.c @@ -32,7 +32,7 @@ static void rpc_command_callback(RpcAppSystemEvent rpc_event, void* context) { } static LfRfid* lfrfid_alloc() { - LfRfid* lfrfid = malloc(sizeof(LfRfid)); //-V773 + LfRfid* lfrfid = malloc(sizeof(LfRfid)); lfrfid->storage = furi_record_open(RECORD_STORAGE); lfrfid->dialogs = furi_record_open(RECORD_DIALOGS); @@ -100,7 +100,7 @@ static LfRfid* lfrfid_alloc() { lfrfid->view_dispatcher, LfRfidViewRead, lfrfid_view_read_get_view(lfrfid->read_view)); return lfrfid; -} +} //-V773 static void lfrfid_free(LfRfid* lfrfid) { furi_assert(lfrfid); diff --git a/applications/main/lfrfid/lfrfid_cli.c b/applications/main/lfrfid/lfrfid_cli.c index 6402745297a..ce3e987e80e 100644 --- a/applications/main/lfrfid/lfrfid_cli.c +++ b/applications/main/lfrfid/lfrfid_cli.c @@ -87,7 +87,7 @@ static void lfrfid_cli_read(Cli* cli, FuriString* args) { uint32_t flags = furi_event_flag_wait(context.event, available_flags, FuriFlagWaitAny, 100); - if(flags != FuriFlagErrorTimeout) { + if(flags != (unsigned)FuriFlagErrorTimeout) { if(FURI_BIT(flags, LFRFIDWorkerReadDone)) { break; } @@ -153,7 +153,7 @@ static bool lfrfid_cli_parse_args(FuriString* args, ProtocolDict* dict, Protocol for(ProtocolId i = 0; i < LFRFIDProtocolMax; i++) { printf( - "\t%s, %d bytes long\r\n", + "\t%s, %zu bytes long\r\n", protocol_dict_get_name(dict, i), protocol_dict_get_data_size(dict, i)); } @@ -165,7 +165,7 @@ static bool lfrfid_cli_parse_args(FuriString* args, ProtocolDict* dict, Protocol // check data arg if(!args_read_hex_bytes(data_text, data, data_size)) { printf( - "%s data needs to be %d bytes long\r\n", + "%s data needs to be %zu bytes long\r\n", protocol_dict_get_name(dict, *protocol), data_size); break; @@ -211,7 +211,7 @@ static void lfrfid_cli_write(Cli* cli, FuriString* args) { while(!cli_cmd_interrupt_received(cli)) { uint32_t flags = furi_event_flag_wait(event, available_flags, FuriFlagWaitAny, 100); - if(flags != FuriFlagErrorTimeout) { + if(flags != (unsigned)FuriFlagErrorTimeout) { if(FURI_BIT(flags, LFRFIDWorkerWriteOK)) { printf("Written!\r\n"); break; @@ -309,9 +309,9 @@ static void lfrfid_cli_raw_analyze(Cli* cli, FuriString* args) { warn = true; } - furi_string_printf(info_string, "[%ld %ld]", pulse, duration); + furi_string_printf(info_string, "[%lu %lu]", pulse, duration); printf("%-16s", furi_string_get_cstr(info_string)); - furi_string_printf(info_string, "[%ld %ld]", pulse, duration - pulse); + furi_string_printf(info_string, "[%lu %lu]", pulse, duration - pulse); printf("%-16s", furi_string_get_cstr(info_string)); if(warn) { @@ -335,7 +335,7 @@ static void lfrfid_cli_raw_analyze(Cli* cli, FuriString* args) { total_pulse += pulse; total_duration += duration; - if(total_protocol != PROTOCOL_NO) { + if(total_protocol != PROTOCOL_NO) { //-V1051 break; } } else { @@ -346,9 +346,9 @@ static void lfrfid_cli_raw_analyze(Cli* cli, FuriString* args) { printf(" Frequency: %f\r\n", (double)frequency); printf(" Duty Cycle: %f\r\n", (double)duty_cycle); - printf(" Warns: %ld\r\n", total_warns); - printf(" Pulse sum: %ld\r\n", total_pulse); - printf("Duration sum: %ld\r\n", total_duration); + printf(" Warns: %lu\r\n", total_warns); + printf(" Pulse sum: %lu\r\n", total_pulse); + printf("Duration sum: %lu\r\n", total_duration); printf(" Average: %f\r\n", (double)((float)total_pulse / (float)total_duration)); printf(" Protocol: "); @@ -435,7 +435,7 @@ static void lfrfid_cli_raw_read(Cli* cli, FuriString* args) { while(true) { uint32_t flags = furi_event_flag_wait(event, available_flags, FuriFlagWaitAny, 100); - if(flags != FuriFlagErrorTimeout) { + if(flags != (unsigned)FuriFlagErrorTimeout) { if(FURI_BIT(flags, LFRFIDWorkerReadRawFileError)) { printf("File is not RFID raw file\r\n"); break; @@ -510,7 +510,7 @@ static void lfrfid_cli_raw_emulate(Cli* cli, FuriString* args) { while(true) { uint32_t flags = furi_event_flag_wait(event, available_flags, FuriFlagWaitAny, 100); - if(flags != FuriFlagErrorTimeout) { + if(flags != (unsigned)FuriFlagErrorTimeout) { if(FURI_BIT(flags, LFRFIDWorkerEmulateRawFileError)) { printf("File is not RFID raw file\r\n"); break; @@ -573,4 +573,4 @@ static void lfrfid_cli(Cli* cli, FuriString* args, void* context) { } furi_string_free(cmd); -} \ No newline at end of file +} diff --git a/applications/main/nfc/scenes/nfc_scene_emulate_uid.c b/applications/main/nfc/scenes/nfc_scene_emulate_uid.c index f901976798d..7316eebe015 100644 --- a/applications/main/nfc/scenes/nfc_scene_emulate_uid.c +++ b/applications/main/nfc/scenes/nfc_scene_emulate_uid.c @@ -39,7 +39,7 @@ static void nfc_scene_emulate_uid_widget_config(Nfc* nfc, bool data_received) { widget_add_icon_element(widget, 0, 3, &I_NFC_dolphin_emulation_47x61); widget_add_string_element(widget, 57, 13, AlignLeft, AlignTop, FontPrimary, "Emulating UID"); - if(strcmp(nfc->dev->dev_name, "")) { + if(strcmp(nfc->dev->dev_name, "") != 0) { furi_string_printf(info_str, "%s", nfc->dev->dev_name); } else { for(uint8_t i = 0; i < data->uid_len; i++) { diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_emulate.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_emulate.c index 1bd9a85a859..8c0f493e12b 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_emulate.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_emulate.c @@ -18,7 +18,7 @@ void nfc_scene_mf_classic_emulate_on_enter(void* context) { // Setup view Popup* popup = nfc->popup; popup_set_header(popup, "Emulating", 67, 13, AlignLeft, AlignTop); - if(strcmp(nfc->dev->dev_name, "")) { + if(strcmp(nfc->dev->dev_name, "") != 0) { nfc_text_store_set(nfc, "%s", nfc->dev->dev_name); } else { nfc_text_store_set(nfc, "MIFARE\nClassic"); diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys.c index 54cc18d327b..dee9553d406 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys.c @@ -28,9 +28,9 @@ void nfc_scene_mf_classic_keys_on_enter(void* context) { widget_add_string_element( nfc->widget, 0, 0, AlignLeft, AlignTop, FontPrimary, "Mifare Classic Keys"); char temp_str[32]; - snprintf(temp_str, sizeof(temp_str), "Flipper list: %ld", flipper_dict_keys_total); + snprintf(temp_str, sizeof(temp_str), "Flipper list: %lu", flipper_dict_keys_total); widget_add_string_element(nfc->widget, 0, 20, AlignLeft, AlignTop, FontSecondary, temp_str); - snprintf(temp_str, sizeof(temp_str), "User list: %ld", user_dict_keys_total); + snprintf(temp_str, sizeof(temp_str), "User list: %lu", user_dict_keys_total); widget_add_string_element(nfc->widget, 0, 32, AlignLeft, AlignTop, FontSecondary, temp_str); widget_add_button_element( nfc->widget, GuiButtonTypeCenter, "Add", nfc_scene_mf_classic_keys_widget_callback, nfc); diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_list.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_list.c index 19d2f556f0d..57f9fe65624 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_list.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_list.c @@ -27,7 +27,7 @@ void nfc_scene_mf_classic_keys_list_prepare(Nfc* nfc, MfClassicDict* dict) { char* current_key = (char*)malloc(sizeof(char) * 13); strncpy(current_key, furi_string_get_cstr(temp_key), 12); MfClassicUserKeys_push_back(nfc->mfc_key_strs, current_key); - FURI_LOG_D("ListKeys", "Key %ld: %s", index, current_key); + FURI_LOG_D("ListKeys", "Key %lu: %s", index, current_key); submenu_add_item( submenu, current_key, index++, nfc_scene_mf_classic_keys_list_submenu_callback, nfc); } diff --git a/applications/main/nfc/scenes/nfc_scene_mf_desfire_read_success.c b/applications/main/nfc/scenes/nfc_scene_mf_desfire_read_success.c index 2ab0355ca2c..39030397fc4 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_desfire_read_success.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_desfire_read_success.c @@ -26,13 +26,13 @@ void nfc_scene_mf_desfire_read_success_on_enter(void* context) { furi_string_cat_printf(temp_str, " %02X", nfc_data->uid[i]); } - uint32_t bytes_total = 1 << (data->version.sw_storage >> 1); + uint32_t bytes_total = 1UL << (data->version.sw_storage >> 1); uint32_t bytes_free = data->free_memory ? data->free_memory->bytes : 0; - furi_string_cat_printf(temp_str, "\n%ld", bytes_total); + furi_string_cat_printf(temp_str, "\n%lu", bytes_total); if(data->version.sw_storage & 1) { furi_string_push_back(temp_str, '+'); } - furi_string_cat_printf(temp_str, " bytes, %ld bytes free\n", bytes_free); + furi_string_cat_printf(temp_str, " bytes, %lu bytes free\n", bytes_free); uint16_t n_apps = 0; uint16_t n_files = 0; diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_emulate.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_emulate.c index c9c617cbef6..9d8f17f9a2c 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_emulate.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_emulate.c @@ -21,7 +21,7 @@ void nfc_scene_mf_ultralight_emulate_on_enter(void* context) { (type == MfUltralightTypeUnknown); Popup* popup = nfc->popup; popup_set_header(popup, "Emulating", 67, 13, AlignLeft, AlignTop); - if(strcmp(nfc->dev->dev_name, "")) { + if(strcmp(nfc->dev->dev_name, "") != 0) { nfc_text_store_set(nfc, "%s", nfc->dev->dev_name); } else if(is_ultralight) { nfc_text_store_set(nfc, "MIFARE\nUltralight"); diff --git a/applications/main/nfc/scenes/nfc_scene_nfc_data_info.c b/applications/main/nfc/scenes/nfc_scene_nfc_data_info.c index d1767a45800..b44ab7823ba 100644 --- a/applications/main/nfc/scenes/nfc_scene_nfc_data_info.c +++ b/applications/main/nfc/scenes/nfc_scene_nfc_data_info.c @@ -57,13 +57,13 @@ void nfc_scene_nfc_data_info_on_enter(void* context) { // Set application specific data if(protocol == NfcDeviceProtocolMifareDesfire) { MifareDesfireData* data = &dev_data->mf_df_data; - uint32_t bytes_total = 1 << (data->version.sw_storage >> 1); + uint32_t bytes_total = 1UL << (data->version.sw_storage >> 1); uint32_t bytes_free = data->free_memory ? data->free_memory->bytes : 0; - furi_string_cat_printf(temp_str, "\n%ld", bytes_total); + furi_string_cat_printf(temp_str, "\n%lu", bytes_total); if(data->version.sw_storage & 1) { furi_string_push_back(temp_str, '+'); } - furi_string_cat_printf(temp_str, " bytes, %ld bytes free\n", bytes_free); + furi_string_cat_printf(temp_str, " bytes, %lu bytes free\n", bytes_free); uint16_t n_apps = 0; uint16_t n_files = 0; @@ -147,4 +147,4 @@ void nfc_scene_nfc_data_info_on_exit(void* context) { Nfc* nfc = context; widget_reset(nfc->widget); -} \ No newline at end of file +} diff --git a/applications/main/nfc/scenes/nfc_scene_read.c b/applications/main/nfc/scenes/nfc_scene_read.c index a64d4d00d91..2607cbd8fb0 100644 --- a/applications/main/nfc/scenes/nfc_scene_read.c +++ b/applications/main/nfc/scenes/nfc_scene_read.c @@ -71,7 +71,7 @@ bool nfc_scene_read_on_event(void* context, SceneManagerEvent event) { } else if(event.event == NfcWorkerEventReadMfUltralight) { notification_message(nfc->notifications, &sequence_success); // Set unlock password input to 0xFFFFFFFF only on fresh read - memset(nfc->byte_input_store, 0xFF, 4); + memset(nfc->byte_input_store, 0xFF, sizeof(nfc->byte_input_store)); scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightReadSuccess); DOLPHIN_DEED(DolphinDeedNfcReadSuccess); consumed = true; diff --git a/applications/main/nfc/scenes/nfc_scene_save_name.c b/applications/main/nfc/scenes/nfc_scene_save_name.c index ca4b1a350f9..00727422676 100644 --- a/applications/main/nfc/scenes/nfc_scene_save_name.c +++ b/applications/main/nfc/scenes/nfc_scene_save_name.c @@ -55,7 +55,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, "")) { + if(strcmp(nfc->dev->dev_name, "") != 0) { nfc_device_delete(nfc->dev, true); } if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSetUid)) { diff --git a/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.c b/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.c index 7463cfaf927..5d1a80a395c 100644 --- a/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.c +++ b/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.c @@ -2,6 +2,7 @@ #include #include +#include #define TAG "SubghzFrequencyAnalyzerWorker" @@ -197,7 +198,7 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) { rssi_temp = (rssi_temp + frequency_rssi.rssi_fine) / 2; frequency_temp = frequency_rssi.frequency_fine; - if(instance->filVal) { + if(!float_is_equal(instance->filVal, 0.f)) { frequency_rssi.frequency_fine = subghz_frequency_analyzer_worker_expRunningAverageAdaptive( instance, frequency_rssi.frequency_fine); @@ -219,7 +220,7 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) { instance->sample_hold_counter = 20; rssi_temp = (rssi_temp + frequency_rssi.rssi_coarse) / 2; frequency_temp = frequency_rssi.frequency_coarse; - if(instance->filVal) { + if(!float_is_equal(instance->filVal, 0.f)) { frequency_rssi.frequency_coarse = subghz_frequency_analyzer_worker_expRunningAverageAdaptive( instance, frequency_rssi.frequency_coarse); diff --git a/applications/main/subghz/scenes/subghz_scene_read_raw.c b/applications/main/subghz/scenes/subghz_scene_read_raw.c index b270dd4821c..6f95c41690e 100644 --- a/applications/main/subghz/scenes/subghz_scene_read_raw.c +++ b/applications/main/subghz/scenes/subghz_scene_read_raw.c @@ -3,6 +3,7 @@ #include #include #include +#include #define RAW_FILE_NAME "Raw_signal_" #define TAG "SubGhzSceneReadRAW" @@ -358,7 +359,7 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { float rssi = furi_hal_subghz_get_rssi(); - if(subghz->txrx->raw_threshold_rssi == SUBGHZ_RAW_TRESHOLD_MIN) { + if(float_is_equal(subghz->txrx->raw_threshold_rssi, SUBGHZ_RAW_TRESHOLD_MIN)) { subghz_read_raw_add_data_rssi(subghz->subghz_read_raw, rssi, true); subghz_protocol_raw_save_to_file_pause( (SubGhzProtocolDecoderRAW*)subghz->txrx->decoder_result, false); diff --git a/applications/main/subghz/scenes/subghz_scene_save_name.c b/applications/main/subghz/scenes/subghz_scene_save_name.c index 33846c283fe..255ba228bcc 100644 --- a/applications/main/subghz/scenes/subghz_scene_save_name.c +++ b/applications/main/subghz/scenes/subghz_scene_save_name.c @@ -94,7 +94,7 @@ bool subghz_scene_save_name_on_event(void* context, SceneManagerEvent event) { return true; } else if(event.type == SceneManagerEventTypeCustom) { if(event.event == SubGhzCustomEventSceneSaveName) { - if(strcmp(subghz->file_name_tmp, "")) { + if(strcmp(subghz->file_name_tmp, "") != 0) { furi_string_cat_printf( subghz->file_path, "/%s%s", subghz->file_name_tmp, SUBGHZ_APP_EXTENSION); if(subghz_path_is_file(subghz->file_path_tmp)) { diff --git a/applications/main/subghz/scenes/subghz_scene_set_type.c b/applications/main/subghz/scenes/subghz_scene_set_type.c index 2ed537193a8..eaa3ccefeff 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_type.c +++ b/applications/main/subghz/scenes/subghz_scene_set_type.c @@ -46,7 +46,7 @@ bool subghz_scene_set_type_submenu_gen_data_protocol( uint8_t key_data[sizeof(uint64_t)] = {0}; for(size_t i = 0; i < sizeof(uint64_t); i++) { - key_data[sizeof(uint64_t) - i - 1] = (key >> i * 8) & 0xFF; + key_data[sizeof(uint64_t) - i - 1] = (key >> (i * 8)) & 0xFF; } if(!flipper_format_update_hex(subghz->txrx->fff_data, "Key", key_data, sizeof(uint64_t))) { FURI_LOG_E(TAG, "Unable to update Key"); diff --git a/applications/main/subghz/subghz_cli.c b/applications/main/subghz/subghz_cli.c index b6471b33c92..536cb535e65 100644 --- a/applications/main/subghz/subghz_cli.c +++ b/applications/main/subghz/subghz_cli.c @@ -152,11 +152,11 @@ void subghz_cli_command_tx(Cli* cli, FuriString* args, void* context) { "Protocol: Princeton\n" "Bit: 24\n" "Key: 00 00 00 00 00 %02X %02X %02X\n" - "TE: %ld\n" - "Repeat: %ld\n", - (uint8_t)((key >> 16) & 0xFF), - (uint8_t)((key >> 8) & 0xFF), - (uint8_t)(key & 0xFF), + "TE: %lu\n" + "Repeat: %lu\n", + (uint8_t)((key >> 16) & 0xFFU), + (uint8_t)((key >> 8) & 0xFFU), + (uint8_t)(key & 0xFFU), te, repeat); FlipperFormat* flipper_format = flipper_format_string_alloc(); @@ -300,7 +300,7 @@ void subghz_cli_command_rx(Cli* cli, FuriString* args, void* context) { furi_hal_power_suppress_charge_exit(); - printf("\r\nPackets received %u\r\n", instance->packet_count); + printf("\r\nPackets received %zu\r\n", instance->packet_count); // Cleanup subghz_receiver_free(receiver); @@ -787,8 +787,9 @@ static bool subghz_on_system_start_istream_decode_band( } region->bands_count += 1; - region = - realloc(region, sizeof(FuriHalRegion) + sizeof(FuriHalRegionBand) * region->bands_count); + region = realloc( //-V701 + region, + sizeof(FuriHalRegion) + sizeof(FuriHalRegionBand) * region->bands_count); size_t pos = region->bands_count - 1; region->bands[pos].start = band.start; region->bands[pos].end = band.end; @@ -798,7 +799,7 @@ static bool subghz_on_system_start_istream_decode_band( FURI_LOG_I( "SubGhzOnStart", - "Add allowed band: start %ldHz, stop %ldHz, power_limit %ddBm, duty_cycle %d%%", + "Add allowed band: start %luHz, stop %luHz, power_limit %ddBm, duty_cycle %u%%", band.start, band.end, band.power_limit, diff --git a/applications/main/subghz/subghz_i.c b/applications/main/subghz/subghz_i.c index 0bcd700615a..7de020a54ff 100644 --- a/applications/main/subghz/subghz_i.c +++ b/applications/main/subghz/subghz_i.c @@ -164,7 +164,7 @@ bool subghz_tx_start(SubGhz* subghz, FlipperFormat* flipper_format) { if(subghz->txrx->transmitter) { if(subghz_transmitter_deserialize(subghz->txrx->transmitter, flipper_format)) { - if(strcmp(furi_string_get_cstr(subghz->txrx->preset->name), "")) { + if(strcmp(furi_string_get_cstr(subghz->txrx->preset->name), "") != 0) { subghz_begin( subghz, subghz_setting_get_preset_data_by_name( @@ -544,11 +544,8 @@ void subghz_hopper_update(SubGhz* subghz) { switch(subghz->txrx->hopper_state) { case SubGhzHopperStateOFF: - return; - break; case SubGhzHopperStatePause: return; - break; case SubGhzHopperStateRSSITimeOut: if(subghz->txrx->hopper_timeout != 0) { subghz->txrx->hopper_timeout--; diff --git a/applications/main/subghz/views/subghz_frequency_analyzer.c b/applications/main/subghz/views/subghz_frequency_analyzer.c index 12989840048..94419084bda 100644 --- a/applications/main/subghz/views/subghz_frequency_analyzer.c +++ b/applications/main/subghz/views/subghz_frequency_analyzer.c @@ -11,6 +11,7 @@ #include "../helpers/subghz_frequency_analyzer_log_item_array.h" #include +#include #define LOG_FREQUENCY_MAX_ITEMS 60 // uint8_t (limited by 'seq' of SubGhzFrequencyAnalyzerLogItem) @@ -47,7 +48,8 @@ typedef struct { } SubGhzFrequencyAnalyzerModel; static inline uint8_t rssi_sanitize(float rssi) { - return (rssi ? (uint8_t)(rssi - SUBGHZ_FREQUENCY_ANALYZER_THRESHOLD) : 0); + return ( + !float_is_equal(rssi, 0.f) ? (uint8_t)(rssi - SUBGHZ_FREQUENCY_ANALYZER_THRESHOLD) : 0); } void subghz_frequency_analyzer_set_callback( @@ -294,9 +296,6 @@ static bool subghz_frequency_analyzer_log_frequency_insert(SubGhzFrequencyAnalyz if(items_count < LOG_FREQUENCY_MAX_ITEMS) { SubGhzFrequencyAnalyzerLogItem_t* item = SubGhzFrequencyAnalyzerLogItemArray_push_new(model->log_frequency); - if(item == NULL) { - return false; - } (*item)->frequency = model->frequency; (*item)->count = 1; (*item)->rssi_max = model->rssi; @@ -340,7 +339,7 @@ void subghz_frequency_analyzer_pair_callback( float rssi, bool signal) { SubGhzFrequencyAnalyzer* instance = context; - if((rssi == 0.f) && (instance->locked)) { + if(float_is_equal(rssi, 0.f) && instance->locked) { if(instance->callback) { instance->callback(SubGhzCustomEventSceneAnalyzerUnlock, instance->context); } @@ -355,13 +354,13 @@ void subghz_frequency_analyzer_pair_callback( model->history_frequency[0] = model->frequency; }, false); - } else if((rssi != 0.f) && (!instance->locked)) { + } else if(!float_is_equal(rssi, 0.f) && !instance->locked) { if(instance->callback) { instance->callback(SubGhzCustomEventSceneAnalyzerLock, instance->context); } } - instance->locked = (rssi != 0.f); + instance->locked = !float_is_equal(rssi, 0.f); with_view_model( instance->view, SubGhzFrequencyAnalyzerModel * model, diff --git a/applications/main/subghz/views/subghz_read_raw.c b/applications/main/subghz/views/subghz_read_raw.c index 6120a210b51..87c8a308284 100644 --- a/applications/main/subghz/views/subghz_read_raw.c +++ b/applications/main/subghz/views/subghz_read_raw.c @@ -91,7 +91,7 @@ void subghz_read_raw_update_sample_write(SubGhzReadRAW* instance, size_t sample) with_view_model( instance->view, SubGhzReadRAWModel * model, - { furi_string_printf(model->sample_write, "%d spl.", sample); }, + { furi_string_printf(model->sample_write, "%zu spl.", sample); }, false); } @@ -161,7 +161,7 @@ void subghz_read_raw_draw_sin(Canvas* canvas, SubGhzReadRAWModel* model) { canvas_draw_line( canvas, i + 1, - 32 - subghz_read_raw_tab_sin((i + model->ind_sin * 16)) / SUBGHZ_RAW_SIN_AMPLITUDE, + 32 - subghz_read_raw_tab_sin(i + model->ind_sin * 16) / SUBGHZ_RAW_SIN_AMPLITUDE, i + 2, 32 + subghz_read_raw_tab_sin((i + model->ind_sin * 16 + 1) * 2) / SUBGHZ_RAW_SIN_AMPLITUDE); diff --git a/applications/main/subghz/views/subghz_test_packet.c b/applications/main/subghz/views/subghz_test_packet.c index a42898f7796..43502180cea 100644 --- a/applications/main/subghz/views/subghz_test_packet.c +++ b/applications/main/subghz/views/subghz_test_packet.c @@ -114,7 +114,7 @@ static void subghz_test_packet_draw(Canvas* canvas, SubGhzTestPacketModel* model snprintf(buffer, sizeof(buffer), "Path: %d - %s", model->path, path_name); canvas_draw_str(canvas, 0, 31, buffer); - snprintf(buffer, sizeof(buffer), "Packets: %d", model->packets); + snprintf(buffer, sizeof(buffer), "Packets: %zu", model->packets); canvas_draw_str(canvas, 0, 42, buffer); if(model->status == SubGhzTestPacketModelStatusRx) { diff --git a/applications/main/u2f/scenes/u2f_scene_main.c b/applications/main/u2f/scenes/u2f_scene_main.c index 60ed71c7869..af7f1159bb7 100644 --- a/applications/main/u2f/scenes/u2f_scene_main.c +++ b/applications/main/u2f/scenes/u2f_scene_main.c @@ -58,7 +58,7 @@ bool u2f_scene_main_on_event(void* context, SceneManagerEvent event) { app->event_cur = event.event; if(event.event == U2fCustomEventRegister) u2f_view_set_state(app->u2f_view, U2fMsgRegister); - else if(event.event == U2fCustomEventAuth) + else if(event.event == U2fCustomEventAuth) //-V547 u2f_view_set_state(app->u2f_view, U2fMsgAuth); notification_message(app->notifications, &sequence_display_backlight_on); notification_message(app->notifications, &sequence_single_vibro); diff --git a/applications/main/u2f/u2f_data.c b/applications/main/u2f/u2f_data.c index 900af462af5..66604d166ac 100644 --- a/applications/main/u2f/u2f_data.c +++ b/applications/main/u2f/u2f_data.c @@ -402,9 +402,9 @@ bool u2f_data_cnt_read(uint32_t* cnt_val) { FURI_LOG_E(TAG, "Unable to load encryption key"); break; } - memset(&cnt, 0, 32); - if(!furi_hal_crypto_decrypt(cnt_encr, (uint8_t*)&cnt, 32)) { - memset(&cnt, 0, 32); + memset(&cnt, 0, sizeof(U2fCounterData)); + if(!furi_hal_crypto_decrypt(cnt_encr, (uint8_t*)&cnt, sizeof(U2fCounterData))) { + memset(&cnt, 0, sizeof(U2fCounterData)); FURI_LOG_E(TAG, "Decryption failed"); break; } diff --git a/applications/main/u2f/u2f_hid.c b/applications/main/u2f/u2f_hid.c index 6e1a51f331b..9b625c1f393 100644 --- a/applications/main/u2f/u2f_hid.c +++ b/applications/main/u2f/u2f_hid.c @@ -94,7 +94,7 @@ static void u2f_hid_send_response(U2fHid* u2f_hid) { uint16_t data_ptr = 0; memset(packet_buf, 0, HID_U2F_PACKET_LEN); - memcpy(packet_buf, &(u2f_hid->packet.cid), 4); + memcpy(packet_buf, &(u2f_hid->packet.cid), sizeof(uint32_t)); //-V1086 // Init packet packet_buf[4] = u2f_hid->packet.cmd; @@ -166,7 +166,7 @@ static bool u2f_hid_parse_request(U2fHid* u2f_hid) { return false; u2f_hid->packet.len = 17; uint32_t random_cid = furi_hal_random_get(); - memcpy(&(u2f_hid->packet.payload[8]), &random_cid, 4); + memcpy(&(u2f_hid->packet.payload[8]), &random_cid, sizeof(uint32_t)); //-V1086 u2f_hid->packet.payload[12] = 2; // Protocol version u2f_hid->packet.payload[13] = 1; // Device version major u2f_hid->packet.payload[14] = 0; // Device version minor @@ -177,7 +177,7 @@ static bool u2f_hid_parse_request(U2fHid* u2f_hid) { } else if(u2f_hid->packet.cmd == U2F_HID_WINK) { // WINK - notify user if(u2f_hid->packet.len != 0) return false; u2f_wink(u2f_hid->u2f_instance); - u2f_hid->packet.len = 0; + u2f_hid->packet.len = 0; //-V1048 u2f_hid_send_response(u2f_hid); } else return false; diff --git a/applications/plugins/dap_link/usb/dap_v2_usb.c b/applications/plugins/dap_link/usb/dap_v2_usb.c index b42df2836b6..cba786648f7 100644 --- a/applications/plugins/dap_link/usb/dap_v2_usb.c +++ b/applications/plugins/dap_link/usb/dap_v2_usb.c @@ -568,12 +568,12 @@ void dap_common_usb_set_state_callback(DapStateCallback callback) { static void* dap_usb_alloc_string_descr(const char* str) { furi_assert(str); - uint8_t len = strlen(str); - uint8_t wlen = (len + 1) * sizeof(uint16_t); + size_t len = strlen(str); + size_t wlen = (len + 1) * sizeof(uint16_t); struct usb_string_descriptor* dev_str_desc = malloc(wlen); dev_str_desc->bLength = wlen; dev_str_desc->bDescriptorType = USB_DTYPE_STRING; - for(uint8_t i = 0; i < len; i++) { + for(size_t i = 0; i < len; i++) { dev_str_desc->wString[i] = str[i]; } @@ -974,4 +974,4 @@ static usbd_respond hid_control(usbd_device* dev, usbd_ctlreq* req, usbd_rqc_cal } return usbd_fail; -} \ No newline at end of file +} diff --git a/applications/plugins/hid_app/views/hid_keyboard.c b/applications/plugins/hid_app/views/hid_keyboard.c index dff4a7df7a2..3e3b63284d2 100644 --- a/applications/plugins/hid_app/views/hid_keyboard.c +++ b/applications/plugins/hid_app/views/hid_keyboard.c @@ -249,30 +249,19 @@ static void hid_keyboard_draw_callback(Canvas* canvas, void* context) { static uint8_t hid_keyboard_get_selected_key(HidKeyboardModel* model) { HidKeyboardKey key = hid_keyboard_keyset[model->y][model->x]; - // Use upper case if shift is toggled - bool useUppercase = model->shift; - // Check if the key has an upper case version - bool hasUppercase = key.shift_key != 0; - if(useUppercase && hasUppercase) - return key.value; - else - return key.value; + return key.value; } static void hid_keyboard_get_select_key(HidKeyboardModel* model, HidKeyboardPoint delta) { // Keep going until a valid spot is found, this allows for nulls and zero width keys in the map do { - if(((int8_t)model->y) + delta.y < 0) - model->y = ROW_COUNT - 1; - else - model->y = (model->y + delta.y) % ROW_COUNT; + const int delta_sum = model->y + delta.y; + model->y = delta_sum < 0 ? ROW_COUNT - 1 : delta_sum % ROW_COUNT; } while(delta.y != 0 && hid_keyboard_keyset[model->y][model->x].value == 0); do { - if(((int8_t)model->x) + delta.x < 0) - model->x = COLUMN_COUNT - 1; - else - model->x = (model->x + delta.x) % COLUMN_COUNT; + const int delta_sum = model->x + delta.x; + model->x = delta_sum < 0 ? COLUMN_COUNT - 1 : delta_sum % COLUMN_COUNT; } while(delta.x != 0 && hid_keyboard_keyset[model->y][model->x].width == 0); // Skip zero width keys, pretend they are one key } diff --git a/applications/plugins/music_player/music_player.c b/applications/plugins/music_player/music_player.c index 07d4e2df49c..28127a575dc 100644 --- a/applications/plugins/music_player/music_player.c +++ b/applications/plugins/music_player/music_player.c @@ -180,7 +180,7 @@ static void render_callback(Canvas* canvas, void* ctx) { // note stack view_port x_pos = 73; - y_pos = 0; + y_pos = 0; //-V1048 canvas_set_color(canvas, ColorBlack); canvas_set_font(canvas, FontPrimary); canvas_draw_frame(canvas, x_pos, y_pos, 49, 64); diff --git a/applications/plugins/picopass/lib/loclass/optimized_cipher.c b/applications/plugins/picopass/lib/loclass/optimized_cipher.c index eba95538f12..94df07bae8d 100644 --- a/applications/plugins/picopass/lib/loclass/optimized_cipher.c +++ b/applications/plugins/picopass/lib/loclass/optimized_cipher.c @@ -111,9 +111,9 @@ static void init_opt_select_LUT(void) { ***********************************************************************************/ #define loclass_opt__select(x, y, r) \ - (4 & (((r & (r << 2)) >> 5) ^ ((r & ~(r << 2)) >> 4) ^ ((r | r << 2) >> 3))) | \ - (2 & (((r | r << 2) >> 6) ^ ((r | r << 2) >> 1) ^ (r >> 5) ^ r ^ ((x ^ y) << 1))) | \ - (1 & (((r & ~(r << 2)) >> 4) ^ ((r & (r << 2)) >> 3) ^ r ^ x)) + (4 & ((((r) & ((r) << 2)) >> 5) ^ (((r) & ~((r) << 2)) >> 4) ^ (((r) | (r) << 2) >> 3))) | \ + (2 & ((((r) | (r) << 2) >> 6) ^ (((r) | (r) << 2) >> 1) ^ ((r) >> 5) ^ (r) ^ (((x) ^ (y)) << 1))) | \ + (1 & ((((r) & ~((r) << 2)) >> 4) ^ (((r) & ((r) << 2)) >> 3) ^ (r) ^ (x))) static void loclass_opt_successor(const uint8_t* k, LoclassState_t* s, uint8_t y) { uint16_t Tt = s->t & 0xc533; @@ -149,30 +149,11 @@ static void loclass_opt_suc( uint8_t length, bool add32Zeroes) { for(int i = 0; i < length; i++) { - uint8_t head; - head = in[i]; - loclass_opt_successor(k, s, head); - - head >>= 1; - loclass_opt_successor(k, s, head); - - head >>= 1; - loclass_opt_successor(k, s, head); - - head >>= 1; - loclass_opt_successor(k, s, head); - - head >>= 1; - loclass_opt_successor(k, s, head); - - head >>= 1; - loclass_opt_successor(k, s, head); - - head >>= 1; - loclass_opt_successor(k, s, head); - - head >>= 1; - loclass_opt_successor(k, s, head); + uint8_t head = in[i]; + for(int j = 0; j < 8; j++) { + loclass_opt_successor(k, s, head); + head >>= 1; + } } //For tag MAC, an additional 32 zeroes if(add32Zeroes) { diff --git a/applications/plugins/picopass/picopass_worker.c b/applications/plugins/picopass/picopass_worker.c index c2a32cb6ae8..bb1e513a22e 100644 --- a/applications/plugins/picopass/picopass_worker.c +++ b/applications/plugins/picopass/picopass_worker.c @@ -143,7 +143,7 @@ ReturnCode picopass_read_preauth(PicopassBlock* AA1) { AA1[PICOPASS_CSN_BLOCK_INDEX].data[7]); rfalPicoPassReadBlockRes cfg = {0}; - err = rfalPicoPassPollerReadBlock(PICOPASS_CONFIG_BLOCK_INDEX, &cfg); + rfalPicoPassPollerReadBlock(PICOPASS_CONFIG_BLOCK_INDEX, &cfg); memcpy(AA1[PICOPASS_CONFIG_BLOCK_INDEX].data, cfg.data, sizeof(cfg.data)); FURI_LOG_D( TAG, @@ -158,7 +158,7 @@ ReturnCode picopass_read_preauth(PicopassBlock* AA1) { AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[7]); rfalPicoPassReadBlockRes aia; - err = rfalPicoPassPollerReadBlock(PICOPASS_AIA_BLOCK_INDEX, &aia); + rfalPicoPassPollerReadBlock(PICOPASS_AIA_BLOCK_INDEX, &aia); memcpy(AA1[PICOPASS_AIA_BLOCK_INDEX].data, aia.data, sizeof(aia.data)); FURI_LOG_D( TAG, @@ -221,7 +221,7 @@ ReturnCode picopass_auth(PicopassBlock* AA1, PicopassPacs* pacs) { while(iclass_elite_dict_get_next_key(dict, key)) { FURI_LOG_D( TAG, - "Try to auth with key %d %02x%02x%02x%02x%02x%02x%02x%02x", + "Try to auth with key %zu %02x%02x%02x%02x%02x%02x%02x%02x", index++, key[0], key[1], @@ -249,9 +249,7 @@ ReturnCode picopass_auth(PicopassBlock* AA1, PicopassPacs* pacs) { } } - if(dict) { - iclass_elite_dict_free(dict); - } + iclass_elite_dict_free(dict); return err; } diff --git a/applications/plugins/picopass/scenes/picopass_scene_save_name.c b/applications/plugins/picopass/scenes/picopass_scene_save_name.c index 17ad5927a12..59f33c79a0e 100644 --- a/applications/plugins/picopass/scenes/picopass_scene_save_name.c +++ b/applications/plugins/picopass/scenes/picopass_scene_save_name.c @@ -54,7 +54,7 @@ bool picopass_scene_save_name_on_event(void* context, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { if(event.event == PicopassCustomEventTextInputDone) { - if(strcmp(picopass->dev->dev_name, "")) { + if(strcmp(picopass->dev->dev_name, "") != 0) { // picopass_device_delete(picopass->dev, true); } strlcpy( diff --git a/applications/plugins/signal_generator/scenes/signal_gen_scene_pwm.c b/applications/plugins/signal_generator/scenes/signal_gen_scene_pwm.c index f302c023228..7ac3fadda4c 100644 --- a/applications/plugins/signal_generator/scenes/signal_gen_scene_pwm.c +++ b/applications/plugins/signal_generator/scenes/signal_gen_scene_pwm.c @@ -15,12 +15,12 @@ static void app->pwm_freq = freq; app->pwm_duty = duty; - if(app->pwm_ch != pwm_ch_id[channel_id]) { + if(app->pwm_ch != pwm_ch_id[channel_id]) { //-V1051 app->pwm_ch_prev = app->pwm_ch; app->pwm_ch = pwm_ch_id[channel_id]; view_dispatcher_send_custom_event(app->view_dispatcher, SignalGenPwmEventChannelChange); } else { - app->pwm_ch = pwm_ch_id[channel_id]; + app->pwm_ch = pwm_ch_id[channel_id]; //-V1048 view_dispatcher_send_custom_event(app->view_dispatcher, SignalGenPwmEventUpdate); } } diff --git a/applications/plugins/signal_generator/views/signal_gen_pwm.c b/applications/plugins/signal_generator/views/signal_gen_pwm.c index 8e618f8a910..b6ba47ab072 100644 --- a/applications/plugins/signal_generator/views/signal_gen_pwm.c +++ b/applications/plugins/signal_generator/views/signal_gen_pwm.c @@ -127,12 +127,12 @@ static void signal_gen_pwm_draw_callback(Canvas* canvas, void* _model) { char* line_label = NULL; char val_text[16]; - for(uint8_t line = 0; line < LineIndexTotalCount; line++) { + for(size_t line = 0; line < LineIndexTotalCount; line++) { if(line == LineIndexChannel) { line_label = "GPIO Pin"; } else if(line == LineIndexFrequency) { line_label = "Frequency"; - } else if(line == LineIndexDuty) { + } else if(line == LineIndexDuty) { //-V547 line_label = "Pulse width"; } @@ -169,7 +169,7 @@ static void signal_gen_pwm_draw_callback(Canvas* canvas, void* _model) { canvas_draw_icon(canvas, icon_x, text_y - 9, &I_SmallArrowUp_3x5); canvas_draw_icon(canvas, icon_x, text_y + 5, &I_SmallArrowDown_3x5); } - } else if(line == LineIndexDuty) { + } else if(line == LineIndexDuty) { //-V547 snprintf(val_text, sizeof(val_text), "%d%%", model->duty); canvas_draw_str_aligned(canvas, VALUE_X, text_y, AlignCenter, AlignCenter, val_text); if(model->duty != 0) { diff --git a/applications/plugins/snake_game/snake_game.c b/applications/plugins/snake_game/snake_game.c index ef4ae2ee854..f9b4d30af92 100644 --- a/applications/plugins/snake_game/snake_game.c +++ b/applications/plugins/snake_game/snake_game.c @@ -130,7 +130,7 @@ static void snake_game_render_callback(Canvas* const canvas, void* ctx) { canvas_set_font(canvas, FontSecondary); char buffer[12]; - snprintf(buffer, sizeof(buffer), "Score: %u", snake_state->len - 7); + snprintf(buffer, sizeof(buffer), "Score: %u", snake_state->len - 7U); canvas_draw_str_aligned(canvas, 64, 41, AlignCenter, AlignBottom, buffer); } @@ -153,7 +153,7 @@ static void snake_game_update_timer_callback(FuriMessageQueue* event_queue) { static void snake_game_init_game(SnakeState* const snake_state) { Point p[] = {{8, 6}, {7, 6}, {6, 6}, {5, 6}, {4, 6}, {3, 6}, {2, 6}}; - memcpy(snake_state->points, p, sizeof(p)); + memcpy(snake_state->points, p, sizeof(p)); //-V1086 snake_state->len = 7; diff --git a/applications/plugins/weather_station/protocols/oregon2.c b/applications/plugins/weather_station/protocols/oregon2.c index 8779e9596e1..8ca80bbe255 100644 --- a/applications/plugins/weather_station/protocols/oregon2.c +++ b/applications/plugins/weather_station/protocols/oregon2.c @@ -343,7 +343,7 @@ bool ws_protocol_decoder_oregon2_deserialize(void* context, FlipperFormat* flipp flipper_format, "VarData", (uint8_t*)&instance->var_data, - sizeof(instance->var_data))) { + sizeof(instance->var_data))) { //-V1051 FURI_LOG_E(TAG, "Missing VarData"); break; } diff --git a/applications/plugins/weather_station/protocols/oregon_v1.c b/applications/plugins/weather_station/protocols/oregon_v1.c index d1cc4c7a7be..1ed9da205c5 100644 --- a/applications/plugins/weather_station/protocols/oregon_v1.c +++ b/applications/plugins/weather_station/protocols/oregon_v1.c @@ -147,7 +147,7 @@ static void ws_protocol_oregon_v1_remote_controller(WSBlockGeneric* instance) { instance->temp = -temp_raw; } - instance->battery_low = !(instance->data >> 23) & 1; + instance->battery_low = !((instance->data >> 23) & 1ULL); instance->btn = WS_NO_BTN; instance->humidity = WS_NO_HUMIDITY; diff --git a/applications/plugins/weather_station/protocols/ws_generic.c b/applications/plugins/weather_station/protocols/ws_generic.c index dcacda2e438..8a88ed52f3d 100644 --- a/applications/plugins/weather_station/protocols/ws_generic.c +++ b/applications/plugins/weather_station/protocols/ws_generic.c @@ -79,7 +79,7 @@ bool ws_block_generic_serialize( 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->data >> i * 8) & 0xFF; + key_data[sizeof(uint64_t) - i - 1] = (instance->data >> (i * 8)) & 0xFF; } if(!flipper_format_write_hex(flipper_format, "Data", key_data, sizeof(uint64_t))) { @@ -208,4 +208,4 @@ bool ws_block_generic_deserialize(WSBlockGeneric* instance, FlipperFormat* flipp } while(0); return res; -} +} \ No newline at end of file diff --git a/applications/plugins/weather_station/views/weather_station_receiver_info.c b/applications/plugins/weather_station/views/weather_station_receiver_info.c index 55d239aad22..b3b3f219392 100644 --- a/applications/plugins/weather_station/views/weather_station_receiver_info.c +++ b/applications/plugins/weather_station/views/weather_station_receiver_info.c @@ -4,8 +4,7 @@ #include "../protocols/ws_generic.h" #include #include - -#define abs(x) ((x) > 0 ? (x) : -(x)) +#include struct WSReceiverInfo { View* view; @@ -79,7 +78,7 @@ void ws_view_receiver_info_draw(Canvas* canvas, WSReceiverInfoModel* model) { elements_bold_rounded_frame(canvas, 0, 38, 127, 25); canvas_set_font(canvas, FontPrimary); - if(model->generic->temp != WS_NO_TEMPERATURE) { + if(!float_is_equal(model->generic->temp, WS_NO_TEMPERATURE)) { canvas_draw_icon(canvas, 6, 43, &I_Therm_7x16); uint8_t temp_x1 = 0; diff --git a/applications/plugins/weather_station/weather_station_app_i.c b/applications/plugins/weather_station/weather_station_app_i.c index 052bb853321..712634a2c04 100644 --- a/applications/plugins/weather_station/weather_station_app_i.c +++ b/applications/plugins/weather_station/weather_station_app_i.c @@ -111,11 +111,8 @@ void ws_hopper_update(WeatherStationApp* app) { switch(app->txrx->hopper_state) { case WSHopperStateOFF: - return; - break; case WSHopperStatePause: return; - break; case WSHopperStateRSSITimeOut: if(app->txrx->hopper_timeout != 0) { app->txrx->hopper_timeout--; diff --git a/applications/plugins/weather_station/weather_station_history.c b/applications/plugins/weather_station/weather_station_history.c index b37009c4658..9adff39c697 100644 --- a/applications/plugins/weather_station/weather_station_history.c +++ b/applications/plugins/weather_station/weather_station_history.c @@ -186,7 +186,7 @@ WSHistoryStateAddKey } // or add new record - if(!sensor_found) { + if(!sensor_found) { //-V547 WSHistoryItem* item = WSHistoryItemArray_push_raw(instance->history->data); item->preset = malloc(sizeof(SubGhzRadioPreset)); item->type = decoder_base->protocol->type; diff --git a/applications/services/bt/bt_service/bt.c b/applications/services/bt/bt_service/bt.c index 024cb6e507d..9e578269034 100644 --- a/applications/services/bt/bt_service/bt.c +++ b/applications/services/bt/bt_service/bt.c @@ -36,7 +36,7 @@ static void bt_pin_code_view_port_draw_callback(Canvas* canvas, void* context) { Bt* bt = context; char pin_code_info[24]; canvas_draw_icon(canvas, 0, 0, &I_BLE_Pairing_128x64); - snprintf(pin_code_info, sizeof(pin_code_info), "Pairing code\n%06ld", bt->pin_code); + snprintf(pin_code_info, sizeof(pin_code_info), "Pairing code\n%06lu", bt->pin_code); elements_multiline_text_aligned(canvas, 64, 4, AlignCenter, AlignTop, pin_code_info); elements_button_left(canvas, "Quit"); } @@ -78,7 +78,7 @@ static bool bt_pin_code_verify_event_handler(Bt* bt, uint32_t pin) { notification_message(bt->notification, &sequence_display_backlight_on); FuriString* pin_str; dialog_message_set_icon(bt->dialog_message, &I_BLE_Pairing_128x64, 0, 0); - pin_str = furi_string_alloc_printf("Verify code\n%06ld", pin); + pin_str = furi_string_alloc_printf("Verify code\n%06lu", pin); dialog_message_set_text( bt->dialog_message, furi_string_get_cstr(pin_str), 64, 4, AlignCenter, AlignTop); dialog_message_set_buttons(bt->dialog_message, "Cancel", "OK", NULL); @@ -163,7 +163,7 @@ static uint16_t bt_serial_event_callback(SerialServiceEvent event, void* context rpc_session_feed(bt->rpc_session, event.data.buffer, event.data.size, 1000); if(bytes_processed != event.data.size) { FURI_LOG_E( - TAG, "Only %d of %d bytes processed by RPC", bytes_processed, event.data.size); + TAG, "Only %zu of %u bytes processed by RPC", bytes_processed, event.data.size); } ret = rpc_session_get_available_size(bt->rpc_session); } else if(event.event == SerialServiceEventTypeDataSent) { diff --git a/applications/services/bt/bt_service/bt_keys_storage.c b/applications/services/bt/bt_service/bt_keys_storage.c index 7cff99944f0..215f19a89cb 100644 --- a/applications/services/bt/bt_service/bt_keys_storage.c +++ b/applications/services/bt/bt_service/bt_keys_storage.c @@ -115,7 +115,7 @@ bool bt_keys_storage_update(BtKeysStorage* instance, uint8_t* start_addr, uint32 FURI_LOG_I( TAG, - "Base address: %p. Start update address: %p. Size changed: %ld", + "Base address: %p. Start update address: %p. Size changed: %lu", (void*)instance->nvm_sram_buff, start_addr, size); diff --git a/applications/services/cli/cli.c b/applications/services/cli/cli.c index f29dca9ce0b..384d17808dd 100644 --- a/applications/services/cli/cli.c +++ b/applications/services/cli/cli.c @@ -225,7 +225,7 @@ static void cli_handle_enter(Cli* cli) { furi_check(furi_mutex_acquire(cli->mutex, FuriWaitForever) == FuriStatusOk); CliCommand* cli_command_ptr = CliCommandTree_get(cli->commands, command); - if(cli_command_ptr) { + if(cli_command_ptr) { //-V547 CliCommand cli_command; memcpy(&cli_command, cli_command_ptr, sizeof(CliCommand)); furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk); @@ -353,7 +353,7 @@ void cli_process_input(Cli* cli) { cli_handle_backspace(cli); } else if(in_chr == CliSymbolAsciiCR) { cli_handle_enter(cli); - } else if(in_chr >= 0x20 && in_chr < 0x7F) { + } else if(in_chr >= 0x20 && in_chr < 0x7F) { //-V560 if(cli->cursor_position == furi_string_size(cli->line)) { furi_string_push_back(cli->line, in_chr); cli_putc(cli, in_chr); diff --git a/applications/services/cli/cli_command_gpio.c b/applications/services/cli/cli_command_gpio.c index 0b29f4853e4..f0d487bec47 100644 --- a/applications/services/cli/cli_command_gpio.c +++ b/applications/services/cli/cli_command_gpio.c @@ -1,5 +1,6 @@ #include "cli_command_gpio.h" +#include "core/string.h" #include #include #include @@ -36,26 +37,24 @@ void cli_command_gpio_print_usage() { } static bool pin_name_to_int(FuriString* pin_name, size_t* result) { - bool found = false; - bool debug = furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug); + bool is_debug_mode = furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug); for(size_t i = 0; i < COUNT_OF(cli_command_gpio_pins); i++) { - if(!furi_string_cmp(pin_name, cli_command_gpio_pins[i].name)) { - if(!cli_command_gpio_pins[i].debug || debug) { + if(furi_string_equal(pin_name, cli_command_gpio_pins[i].name)) { + if(!cli_command_gpio_pins[i].debug || is_debug_mode) { *result = i; - found = true; - break; + return true; } } } - return found; + return false; } static void gpio_print_pins(void) { printf("Wrong pin name. Available pins: "); - bool debug = furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug); + bool is_debug_mode = furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug); for(size_t i = 0; i < COUNT_OF(cli_command_gpio_pins); i++) { - if(!cli_command_gpio_pins[i].debug || debug) { + if(!cli_command_gpio_pins[i].debug || is_debug_mode) { printf("%s ", cli_command_gpio_pins[i].name); } } @@ -69,34 +68,29 @@ typedef enum { } GpioParseReturn; static GpioParseReturn gpio_command_parse(FuriString* args, size_t* pin_num, uint8_t* value) { - FuriString* pin_name; - pin_name = furi_string_alloc(); + GpioParseReturn ret = GpioParseReturnOk; + FuriString* pin_name = furi_string_alloc(); - size_t ws = furi_string_search_char(args, ' '); - if(ws == FURI_STRING_FAILURE) { - return GpioParseReturnCmdSyntaxError; - } + do { + if(!args_read_string_and_trim(args, pin_name)) { + ret = GpioParseReturnCmdSyntaxError; + break; + } else if(!pin_name_to_int(pin_name, pin_num)) { + ret = GpioParseReturnPinError; + break; + } - furi_string_set_n(pin_name, args, 0, ws); - furi_string_right(args, ws); - furi_string_trim(args); + int pin_mode; //-V779 + if(!args_read_int_and_trim(args, &pin_mode) || pin_mode < 0 || pin_mode > 1) { + ret = GpioParseReturnValueError; + break; + } - if(!pin_name_to_int(pin_name, pin_num)) { - furi_string_free(pin_name); - return GpioParseReturnPinError; - } + *value = pin_mode; + } while(false); furi_string_free(pin_name); - - if(!furi_string_cmp(args, "0")) { - *value = 0; - } else if(!furi_string_cmp(args, "1")) { - *value = 1; - } else { - return GpioParseReturnValueError; - } - - return GpioParseReturnOk; + return ret; } void cli_command_gpio_mode(Cli* cli, FuriString* args, void* context) { @@ -111,7 +105,7 @@ void cli_command_gpio_mode(Cli* cli, FuriString* args, void* context) { if(err == GpioParseReturnCmdSyntaxError) { cli_print_usage("gpio mode", " <0|1>", furi_string_get_cstr(args)); return; - } else if(err == GpioParseReturnPinError) { + } else if(err == GpioParseReturnPinError) { //-V547 gpio_print_pins(); return; } else if(err == GpioParseReturnValueError) { @@ -119,7 +113,7 @@ void cli_command_gpio_mode(Cli* cli, FuriString* args, void* context) { return; } - if(cli_command_gpio_pins[num].debug) { + if(cli_command_gpio_pins[num].debug) { //-V779 printf( "Changing this pin mode may damage hardware. Are you sure you want to continue? (y/n)?\r\n"); char c = cli_getc(cli); @@ -149,7 +143,7 @@ void cli_command_gpio_read(Cli* cli, FuriString* args, void* context) { return; } - if(LL_GPIO_MODE_INPUT != + if(LL_GPIO_MODE_INPUT != //-V779 LL_GPIO_GetPinMode( cli_command_gpio_pins[num].pin->port, cli_command_gpio_pins[num].pin->pin)) { printf("Err: pin %s is not set as an input.", cli_command_gpio_pins[num].name); @@ -171,7 +165,7 @@ void cli_command_gpio_set(Cli* cli, FuriString* args, void* context) { if(err == GpioParseReturnCmdSyntaxError) { cli_print_usage("gpio set", " <0|1>", furi_string_get_cstr(args)); return; - } else if(err == GpioParseReturnPinError) { + } else if(err == GpioParseReturnPinError) { //-V547 gpio_print_pins(); return; } else if(err == GpioParseReturnValueError) { @@ -179,7 +173,7 @@ void cli_command_gpio_set(Cli* cli, FuriString* args, void* context) { return; } - if(LL_GPIO_MODE_OUTPUT != + if(LL_GPIO_MODE_OUTPUT != //-V779 LL_GPIO_GetPinMode( cli_command_gpio_pins[num].pin->port, cli_command_gpio_pins[num].pin->pin)) { printf("Err: pin %s is not set as an output.", cli_command_gpio_pins[num].name); diff --git a/applications/services/cli/cli_commands.c b/applications/services/cli/cli_commands.c index 3534a5418ae..4414d365f90 100644 --- a/applications/services/cli/cli_commands.c +++ b/applications/services/cli/cli_commands.c @@ -354,7 +354,7 @@ void cli_command_ps(Cli* cli, FuriString* args, void* context) { for(uint8_t i = 0; i < thread_num; i++) { TaskControlBlock* tcb = (TaskControlBlock*)threads_ids[i]; printf( - "%-20s 0x%-12lx %-8d %-8ld %-8ld\r\n", + "%-20s 0x%-12lx %-8zu %-8lu %-8lu\r\n", furi_thread_get_name(threads_ids[i]), (uint32_t)tcb->pxStack, memmgr_heap_get_thread_memory(threads_ids[i]), @@ -369,13 +369,13 @@ void cli_command_free(Cli* cli, FuriString* args, void* context) { UNUSED(args); UNUSED(context); - printf("Free heap size: %d\r\n", memmgr_get_free_heap()); - printf("Total heap size: %d\r\n", memmgr_get_total_heap()); - printf("Minimum heap size: %d\r\n", memmgr_get_minimum_free_heap()); - printf("Maximum heap block: %d\r\n", memmgr_heap_get_max_free_block()); + printf("Free heap size: %zu\r\n", memmgr_get_free_heap()); + printf("Total heap size: %zu\r\n", memmgr_get_total_heap()); + printf("Minimum heap size: %zu\r\n", memmgr_get_minimum_free_heap()); + printf("Maximum heap block: %zu\r\n", memmgr_heap_get_max_free_block()); - printf("Pool free: %d\r\n", memmgr_pool_get_free()); - printf("Maximum pool block: %d\r\n", memmgr_pool_get_max_block()); + printf("Pool free: %zu\r\n", memmgr_pool_get_free()); + printf("Maximum pool block: %zu\r\n", memmgr_pool_get_max_block()); } void cli_command_free_blocks(Cli* cli, FuriString* args, void* context) { diff --git a/applications/services/crypto/crypto_cli.c b/applications/services/crypto/crypto_cli.c index 1b26ba9fb33..a286f44571d 100644 --- a/applications/services/crypto/crypto_cli.c +++ b/applications/services/crypto/crypto_cli.c @@ -142,7 +142,7 @@ void crypto_cli_decrypt(Cli* cli, FuriString* args) { if(args_read_hex_bytes(hex_input, input, size)) { if(furi_hal_crypto_decrypt(input, output, size)) { printf("Decrypted data:\r\n"); - printf("%s\r\n", output); + printf("%s\r\n", output); //-V576 } else { printf("Failed to decrypt\r\n"); } diff --git a/applications/services/desktop/animations/animation_manager.c b/applications/services/desktop/animations/animation_manager.c index 9c22d131407..f4c8f17a3ae 100644 --- a/applications/services/desktop/animations/animation_manager.c +++ b/applications/services/desktop/animations/animation_manager.c @@ -244,10 +244,8 @@ static bool animation_manager_check_blocking(AnimationManager* animation_manager furi_record_close(RECORD_DOLPHIN); if(!blocking_animation && stats.level_up_is_pending) { blocking_animation = animation_storage_find_animation(NEW_MAIL_ANIMATION_NAME); - furi_assert(blocking_animation); - if(blocking_animation) { - animation_manager->levelup_pending = true; - } + furi_check(blocking_animation); + animation_manager->levelup_pending = true; } if(blocking_animation) { @@ -448,7 +446,7 @@ void animation_manager_unload_and_stall_animation(AnimationManager* animation_ma if(animation_manager->state == AnimationManagerStateBlocked) { animation_manager->state = AnimationManagerStateFreezedBlocked; - } else if(animation_manager->state == AnimationManagerStateIdle) { + } else if(animation_manager->state == AnimationManagerStateIdle) { //-V547 animation_manager->state = AnimationManagerStateFreezedIdle; animation_manager->freezed_animation_time_left = @@ -491,7 +489,7 @@ void animation_manager_load_and_continue_animation(AnimationManager* animation_m furi_assert(restore_animation); animation_manager_replace_current_animation(animation_manager, restore_animation); animation_manager->state = AnimationManagerStateBlocked; - } else if(animation_manager->state == AnimationManagerStateFreezedIdle) { + } else if(animation_manager->state == AnimationManagerStateFreezedIdle) { //-V547 /* check if we missed some system notifications, and set current_animation */ bool blocked = animation_manager_check_blocking(animation_manager); if(!blocked) { diff --git a/applications/services/desktop/animations/animation_storage.c b/applications/services/desktop/animations/animation_storage.c index 0727fd6ae76..2c16cf726da 100644 --- a/applications/services/desktop/animations/animation_storage.c +++ b/applications/services/desktop/animations/animation_storage.c @@ -360,7 +360,6 @@ static bool animation_storage_load_bubbles(BubbleAnimation* animation, FlipperFo if(u32value > 20) break; animation->frame_bubble_sequences_count = u32value; if(animation->frame_bubble_sequences_count == 0) { - animation->frame_bubble_sequences = NULL; success = true; break; } @@ -481,7 +480,7 @@ static BubbleAnimation* animation_storage_load_animation(const char* name) { if(!animation_storage_load_frames(storage, name, animation, u32array, width, height)) break; - if(!flipper_format_read_uint32(ff, "Active cycles", &u32value, 1)) break; + if(!flipper_format_read_uint32(ff, "Active cycles", &u32value, 1)) break; //-V779 animation->active_cycles = u32value; if(!flipper_format_read_uint32(ff, "Frame rate", &u32value, 1)) break; FURI_CONST_ASSIGN(animation->icon_animation.frame_rate, u32value); @@ -500,7 +499,7 @@ static BubbleAnimation* animation_storage_load_animation(const char* name) { free(u32array); } - if(!success) { + if(!success) { //-V547 if(animation->frame_order) { free((void*)animation->frame_order); } diff --git a/applications/services/desktop/helpers/slideshow.c b/applications/services/desktop/helpers/slideshow.c index b4d85cb9075..a8e132779a4 100644 --- a/applications/services/desktop/helpers/slideshow.c +++ b/applications/services/desktop/helpers/slideshow.c @@ -41,7 +41,7 @@ Slideshow* slideshow_alloc() { void slideshow_free(Slideshow* slideshow) { Icon* icon = &slideshow->icon; - if(icon) { + if(icon) { //-V547 for(int frame_idx = 0; frame_idx < icon->frame_count; ++frame_idx) { uint8_t* frame_data = (uint8_t*)icon->frames[frame_idx]; free(frame_data); diff --git a/applications/services/desktop/views/desktop_view_debug.c b/applications/services/desktop/views/desktop_view_debug.c index f9c8aedc2ce..e679cf636e1 100644 --- a/applications/services/desktop/views/desktop_view_debug.c +++ b/applications/services/desktop/views/desktop_view_debug.c @@ -52,7 +52,7 @@ void desktop_debug_render(Canvas* canvas, void* model) { #ifdef SRV_BT c2_ver = ble_glue_get_c2_info(); #endif - if(!ver) { + if(!ver) { //-V1051 canvas_draw_str(canvas, 0, 30 + STATUS_BAR_Y_SHIFT, "No info"); return; } @@ -88,19 +88,19 @@ void desktop_debug_render(Canvas* canvas, void* model) { uint32_t remaining = dolphin_state_xp_to_levelup(m->icounter); canvas_set_font(canvas, FontSecondary); - snprintf(buffer, sizeof(buffer), "Icounter: %ld Butthurt %ld", m->icounter, m->butthurt); + snprintf(buffer, sizeof(buffer), "Icounter: %lu Butthurt %lu", m->icounter, m->butthurt); canvas_draw_str(canvas, 5, 19 + STATUS_BAR_Y_SHIFT, buffer); snprintf( buffer, sizeof(buffer), - "Level: %ld To level up: %ld", + "Level: %lu To level up: %lu", current_lvl, (remaining == (uint32_t)(-1) ? remaining : 0)); canvas_draw_str(canvas, 5, 29 + STATUS_BAR_Y_SHIFT, buffer); // even if timestamp is uint64_t, it's safe to cast it to uint32_t, because furi_hal_rtc_datetime_to_timestamp only returns uint32_t - snprintf(buffer, sizeof(buffer), "%ld", (uint32_t)m->timestamp); + snprintf(buffer, sizeof(buffer), "%lu", (uint32_t)m->timestamp); canvas_draw_str(canvas, 5, 39 + STATUS_BAR_Y_SHIFT, buffer); canvas_draw_str(canvas, 0, 49 + STATUS_BAR_Y_SHIFT, "[< >] icounter value [ok] save"); diff --git a/applications/services/desktop/views/desktop_view_lock_menu.c b/applications/services/desktop/views/desktop_view_lock_menu.c index 486be23b5e8..52570f8ca2b 100644 --- a/applications/services/desktop/views/desktop_view_lock_menu.c +++ b/applications/services/desktop/views/desktop_view_lock_menu.c @@ -53,7 +53,7 @@ void desktop_lock_menu_draw_callback(Canvas* canvas, void* model) { canvas_draw_icon(canvas, 116, 0 + STATUS_BAR_Y_SHIFT, &I_DoorRight_70x55); canvas_set_font(canvas, FontSecondary); - for(uint8_t i = 0; i < DesktopLockMenuIndexTotalCount; ++i) { + for(size_t i = 0; i < DesktopLockMenuIndexTotalCount; ++i) { const char* str = NULL; if(i == DesktopLockMenuIndexLock) { @@ -64,7 +64,7 @@ void desktop_lock_menu_draw_callback(Canvas* canvas, void* model) { } else { str = "Set PIN"; } - } else if(i == DesktopLockMenuIndexDummy) { + } else if(i == DesktopLockMenuIndexDummy) { //-V547 if(m->dummy_mode) { str = "Brainiac Mode"; } else { @@ -72,7 +72,7 @@ void desktop_lock_menu_draw_callback(Canvas* canvas, void* model) { } } - if(str) + if(str) //-V547 canvas_draw_str_aligned( canvas, 64, 9 + (i * 17) + STATUS_BAR_Y_SHIFT, AlignCenter, AlignCenter, str); diff --git a/applications/services/desktop/views/desktop_view_pin_timeout.c b/applications/services/desktop/views/desktop_view_pin_timeout.c index 6e1e807fdb1..e64c264ffde 100644 --- a/applications/services/desktop/views/desktop_view_pin_timeout.c +++ b/applications/services/desktop/views/desktop_view_pin_timeout.c @@ -67,7 +67,7 @@ static void desktop_view_pin_timeout_draw(Canvas* canvas, void* _model) { canvas_set_font(canvas, FontSecondary); char str[30] = {0}; - snprintf(str, sizeof(str), "Timeout: %lds", model->time_left); + snprintf(str, sizeof(str), "Timeout: %lus", model->time_left); canvas_draw_str_aligned(canvas, 64, 38, AlignCenter, AlignCenter, str); } diff --git a/applications/services/dolphin/helpers/dolphin_state.c b/applications/services/dolphin/helpers/dolphin_state.c index 10cb85c2847..14f080464bf 100644 --- a/applications/services/dolphin/helpers/dolphin_state.c +++ b/applications/services/dolphin/helpers/dolphin_state.c @@ -171,7 +171,7 @@ void dolphin_state_on_deed(DolphinState* dolphin_state, DolphinDeed deed) { FURI_LOG_D( TAG, - "icounter %ld, butthurt %ld", + "icounter %lu, butthurt %ld", dolphin_state->data.icounter, dolphin_state->data.butthurt); } diff --git a/applications/services/gui/elements.c b/applications/services/gui/elements.c index 6b796ed5b53..cd4c105ae18 100644 --- a/applications/services/gui/elements.c +++ b/applications/services/gui/elements.c @@ -291,11 +291,11 @@ void elements_multiline_text(Canvas* canvas, uint8_t x, uint8_t y, const char* t end = strchr(start, '\n'); if(end) { furi_string_set_strn(str, start, end - start); + start = end + 1; } else { furi_string_set(str, start); } canvas_draw_str(canvas, x, y, furi_string_get_cstr(str)); - start = end + 1; y += font_height; } while(end && y < 64); furi_string_free(str); diff --git a/applications/services/gui/modules/button_panel.c b/applications/services/gui/modules/button_panel.c index 47b6ed488d6..8f29c65421b 100644 --- a/applications/services/gui/modules/button_panel.c +++ b/applications/services/gui/modules/button_panel.c @@ -172,7 +172,7 @@ void button_panel_add_item( void* callback_context) { furi_assert(button_panel); - with_view_model( + with_view_model( //-V773 button_panel->view, ButtonPanelModel * model, { diff --git a/applications/services/gui/modules/byte_input.c b/applications/services/gui/modules/byte_input.c index bc19f0eeeff..82de129f54b 100644 --- a/applications/services/gui/modules/byte_input.c +++ b/applications/services/gui/modules/byte_input.c @@ -71,11 +71,13 @@ static uint8_t byte_input_get_row_size(uint8_t row_index) { switch(row_index + 1) { case 1: - row_size = sizeof(keyboard_keys_row_1) / sizeof(ByteInputKey); + row_size = COUNT_OF(keyboard_keys_row_1); break; case 2: - row_size = sizeof(keyboard_keys_row_2) / sizeof(ByteInputKey); + row_size = COUNT_OF(keyboard_keys_row_2); break; + default: + furi_crash(NULL); } return row_size; @@ -97,6 +99,8 @@ static const ByteInputKey* byte_input_get_row(uint8_t row_index) { case 2: row = keyboard_keys_row_2; break; + default: + furi_crash(NULL); } return row; @@ -383,6 +387,7 @@ static void byte_input_dec_selected_byte(ByteInputModel* model) { if(model->selected_byte > 0) { model->selected_byte -= 1; + furi_assert(model->selected_byte >= model->first_visible_byte); if(model->selected_byte - model->first_visible_byte < 1) { if(model->first_visible_byte > 0) { model->first_visible_byte--; diff --git a/applications/services/gui/modules/file_browser.c b/applications/services/gui/modules/file_browser.c index 57e0018ecdb..d21a48b54eb 100644 --- a/applications/services/gui/modules/file_browser.c +++ b/applications/services/gui/modules/file_browser.c @@ -514,7 +514,7 @@ static void browser_draw_list(Canvas* canvas, FileBrowserModel* model) { scroll_counter = 0; } - if(custom_icon_data) { + if(custom_icon_data) { //-V547 // Currently only 10*10 icons are supported canvas_draw_bitmap( canvas, 2, Y_OFFSET + 1 + i * FRAME_HEIGHT, 10, 10, custom_icon_data); @@ -657,9 +657,7 @@ static bool file_browser_view_input_callback(InputEvent* event, void* context) { if(!is_root && !file_browser_worker_is_in_start_folder(browser->worker)) { consumed = true; - if(!is_root) { - file_browser_worker_folder_exit(browser->worker); - } + file_browser_worker_folder_exit(browser->worker); } } } diff --git a/applications/services/gui/modules/file_browser_worker.c b/applications/services/gui/modules/file_browser_worker.c index d8b515d0373..a97a4d71aff 100644 --- a/applications/services/gui/modules/file_browser_worker.c +++ b/applications/services/gui/modules/file_browser_worker.c @@ -375,7 +375,7 @@ BrowserWorker* file_browser_worker_alloc( const char* filter_ext, bool skip_assets, bool hide_dot_files) { - BrowserWorker* browser = malloc(sizeof(BrowserWorker)); //-V773 + BrowserWorker* browser = malloc(sizeof(BrowserWorker)); idx_last_array_init(browser->idx_last); @@ -395,7 +395,7 @@ BrowserWorker* file_browser_worker_alloc( furi_thread_start(browser->thread); return browser; -} +} //-V773 void file_browser_worker_free(BrowserWorker* browser) { furi_assert(browser); diff --git a/applications/services/gui/modules/submenu.c b/applications/services/gui/modules/submenu.c index b7152a3d263..f8af44fdbf3 100644 --- a/applications/services/gui/modules/submenu.c +++ b/applications/services/gui/modules/submenu.c @@ -225,8 +225,11 @@ void submenu_set_selected_item(Submenu* submenu, uint32_t index) { if(items_size <= items_on_screen) { model->window_position = 0; - } else if(model->window_position >= items_size - items_on_screen) { - model->window_position = items_size - items_on_screen; + } else { + const size_t pos = items_size - items_on_screen; + if(model->window_position > pos) { + model->window_position = pos; + } } }, true); @@ -242,8 +245,7 @@ void submenu_process_up(Submenu* submenu) { if(model->position > 0) { model->position--; - if((model->position - model->window_position < 1) && - (model->window_position > 0)) { + if((model->position == model->window_position) && (model->window_position > 0)) { model->window_position--; } } else { diff --git a/applications/services/gui/modules/text_input.c b/applications/services/gui/modules/text_input.c index 540e4b7c48b..7c419d96a89 100644 --- a/applications/services/gui/modules/text_input.c +++ b/applications/services/gui/modules/text_input.c @@ -92,14 +92,16 @@ static uint8_t get_row_size(uint8_t row_index) { switch(row_index + 1) { case 1: - row_size = sizeof(keyboard_keys_row_1) / sizeof(TextInputKey); + row_size = COUNT_OF(keyboard_keys_row_1); break; case 2: - row_size = sizeof(keyboard_keys_row_2) / sizeof(TextInputKey); + row_size = COUNT_OF(keyboard_keys_row_2); break; case 3: - row_size = sizeof(keyboard_keys_row_3) / sizeof(TextInputKey); + row_size = COUNT_OF(keyboard_keys_row_3); break; + default: + furi_crash(NULL); } return row_size; @@ -118,6 +120,8 @@ static const TextInputKey* get_row(uint8_t row_index) { case 3: row = keyboard_keys_row_3; break; + default: + furi_crash(NULL); } return row; @@ -184,7 +188,7 @@ static void text_input_view_draw_callback(Canvas* canvas, void* _model) { canvas_set_font(canvas, FontKeyboard); - for(uint8_t row = 0; row <= keyboard_row_count; row++) { + for(uint8_t row = 0; row < keyboard_row_count; row++) { const uint8_t column_count = get_row_size(row); const TextInputKey* keys = get_row(row); @@ -303,7 +307,7 @@ static void text_input_handle_right(TextInput* text_input, TextInputModel* model static void text_input_handle_ok(TextInput* text_input, TextInputModel* model, bool shift) { char selected = get_selected_char(model); - uint8_t text_length = strlen(model->text_buffer); + size_t text_length = strlen(model->text_buffer); if(shift) { selected = char_to_uppercase(selected); @@ -481,7 +485,6 @@ void text_input_reset(TextInput* text_input) { text_input->view, TextInputModel * model, { - model->text_buffer_size = 0; model->header = ""; model->selected_row = 0; model->selected_column = 0; diff --git a/applications/services/gui/modules/validators.c b/applications/services/gui/modules/validators.c index 0463b1c26b8..9c5d0be849f 100644 --- a/applications/services/gui/modules/validators.c +++ b/applications/services/gui/modules/validators.c @@ -18,15 +18,12 @@ bool validator_is_file_callback(const char* text, FuriString* error, void* conte } } - bool ret = true; FuriString* path = furi_string_alloc_printf( "%s/%s%s", instance->app_path_folder, text, instance->app_extension); Storage* storage = furi_record_open(RECORD_STORAGE); - if(storage_common_stat(storage, furi_string_get_cstr(path), NULL) == FSE_OK) { - ret = false; + const bool ret = storage_common_stat(storage, furi_string_get_cstr(path), NULL) != FSE_OK; + if(!ret) { furi_string_printf(error, "This name\nexists!\nChoose\nanother one."); - } else { - ret = true; } furi_string_free(path); furi_record_close(RECORD_STORAGE); diff --git a/applications/services/gui/modules/variable_item_list.c b/applications/services/gui/modules/variable_item_list.c index a9b89d63b52..5060985e160 100644 --- a/applications/services/gui/modules/variable_item_list.c +++ b/applications/services/gui/modules/variable_item_list.c @@ -188,8 +188,8 @@ void variable_item_list_process_up(VariableItemList* variable_item_list) { uint8_t items_on_screen = 4; if(model->position > 0) { model->position--; - if(((model->position - model->window_position) < 1) && - model->window_position > 0) { + + if((model->position == model->window_position) && (model->window_position > 0)) { model->window_position--; } } else { diff --git a/applications/services/gui/modules/widget_elements/widget_element_button.c b/applications/services/gui/modules/widget_elements/widget_element_button.c index e3267058e48..3bfae9df392 100644 --- a/applications/services/gui/modules/widget_elements/widget_element_button.c +++ b/applications/services/gui/modules/widget_elements/widget_element_button.c @@ -60,7 +60,7 @@ WidgetElement* widget_element_button_create( ButtonCallback callback, void* context) { // Allocate and init model - GuiButtonModel* model = malloc(sizeof(GuiButtonModel)); //-V773 + GuiButtonModel* model = malloc(sizeof(GuiButtonModel)); model->button_type = button_type; model->callback = callback; model->context = context; @@ -75,4 +75,4 @@ WidgetElement* widget_element_button_create( gui_button->model = model; return gui_button; -} +} //-V773 diff --git a/applications/services/gui/modules/widget_elements/widget_element_string.c b/applications/services/gui/modules/widget_elements/widget_element_string.c index feb22ad1c1b..4bf7dd693f1 100644 --- a/applications/services/gui/modules/widget_elements/widget_element_string.c +++ b/applications/services/gui/modules/widget_elements/widget_element_string.c @@ -62,4 +62,4 @@ WidgetElement* widget_element_string_create( gui_string->model = model; return gui_string; -} +} //-V773 diff --git a/applications/services/gui/modules/widget_elements/widget_element_string_multiline.c b/applications/services/gui/modules/widget_elements/widget_element_string_multiline.c index 9ad2a1a8340..3fc6b309c07 100644 --- a/applications/services/gui/modules/widget_elements/widget_element_string_multiline.c +++ b/applications/services/gui/modules/widget_elements/widget_element_string_multiline.c @@ -63,4 +63,4 @@ WidgetElement* widget_element_string_multiline_create( gui_string->model = model; return gui_string; -} +} //-V773 diff --git a/applications/services/gui/modules/widget_elements/widget_element_text_box.c b/applications/services/gui/modules/widget_elements/widget_element_text_box.c index 2c69482021e..98f8e83d824 100644 --- a/applications/services/gui/modules/widget_elements/widget_element_text_box.c +++ b/applications/services/gui/modules/widget_elements/widget_element_text_box.c @@ -71,4 +71,4 @@ WidgetElement* widget_element_text_box_create( gui_string->model = model; return gui_string; -} +} //-V773 diff --git a/applications/services/gui/modules/widget_elements/widget_element_text_scroll.c b/applications/services/gui/modules/widget_elements/widget_element_text_scroll.c index a4d76638939..d8fc11311bd 100644 --- a/applications/services/gui/modules/widget_elements/widget_element_text_scroll.c +++ b/applications/services/gui/modules/widget_elements/widget_element_text_scroll.c @@ -241,4 +241,4 @@ WidgetElement* widget_element_text_scroll_create( text_scroll->model_mutex = furi_mutex_alloc(FuriMutexTypeNormal); return text_scroll; -} +} //-V773 diff --git a/applications/services/gui/view.c b/applications/services/gui/view.c index 7ab6d15b753..50c05a406fa 100644 --- a/applications/services/gui/view.c +++ b/applications/services/gui/view.c @@ -86,7 +86,7 @@ void view_allocate_model(View* view, ViewModelType type, size_t size) { model->data = malloc(size); view->model = model; } else { - furi_assert(false); + furi_crash(NULL); } } @@ -103,7 +103,7 @@ void view_free_model(View* view) { free(model); view->model = NULL; } else { - furi_assert(false); + furi_crash(NULL); } } diff --git a/applications/services/input/input.c b/applications/services/input/input.c index 1d02df1e519..e1e581c9f34 100644 --- a/applications/services/input/input.c +++ b/applications/services/input/input.c @@ -80,9 +80,7 @@ int32_t input_srv(void* p) { #ifdef SRV_CLI input->cli = furi_record_open(RECORD_CLI); - if(input->cli) { - cli_add_command(input->cli, "input", CliCommandFlagParallelSafe, input_cli, input); - } + cli_add_command(input->cli, "input", CliCommandFlagParallelSafe, input_cli, input); #endif input->pin_states = malloc(input_pins_count * sizeof(InputPinState)); diff --git a/applications/services/input/input_cli.c b/applications/services/input/input_cli.c index d9a8eaeba8c..f7e904b171a 100644 --- a/applications/services/input/input_cli.c +++ b/applications/services/input/input_cli.c @@ -89,7 +89,7 @@ static void input_cli_send(Cli* cli, FuriString* args, Input* input) { parsed = true; } while(false); - if(parsed) { + if(parsed) { //-V547 furi_pubsub_publish(input->event_pubsub, &event); } else { input_cli_send_print_usage(); diff --git a/applications/services/loader/loader.c b/applications/services/loader/loader.c index 931719723a5..97d1e6e4e52 100644 --- a/applications/services/loader/loader.c +++ b/applications/services/loader/loader.c @@ -280,7 +280,7 @@ static void loader_thread_state_callback(FuriThreadState thread_state, void* con furi_hal_power_insomnia_enter(); } } else if(thread_state == FuriThreadStateStopped) { - FURI_LOG_I(TAG, "Application stopped. Free heap: %d", memmgr_get_free_heap()); + FURI_LOG_I(TAG, "Application stopped. Free heap: %zu", memmgr_get_free_heap()); if(loader_instance->application_arguments) { free(loader_instance->application_arguments); diff --git a/applications/services/power/power_service/views/power_off.c b/applications/services/power/power_service/views/power_off.c index f14a18d7ee8..3a1addbac70 100644 --- a/applications/services/power/power_service/views/power_off.c +++ b/applications/services/power/power_service/views/power_off.c @@ -26,7 +26,7 @@ static void power_off_draw_callback(Canvas* canvas, void* _model) { canvas_set_font(canvas, FontSecondary); if(model->response == PowerOffResponseDefault) { - snprintf(buff, sizeof(buff), "Charge me!\nOff in %lds!", model->time_left_sec); + snprintf(buff, sizeof(buff), "Charge me!\nOff in %lus!", model->time_left_sec); elements_multiline_text_aligned(canvas, 70, 23, AlignLeft, AlignTop, buff); elements_button_left(canvas, "Cancel"); diff --git a/applications/services/rpc/rpc_app.c b/applications/services/rpc/rpc_app.c index b96f043ac7e..cc18b6cec37 100644 --- a/applications/services/rpc/rpc_app.c +++ b/applications/services/rpc/rpc_app.c @@ -39,9 +39,9 @@ static void rpc_system_app_start_process(const PB_Main* request, void* context) furi_assert(!rpc_app->last_id); furi_assert(!rpc_app->last_data); - FURI_LOG_D(TAG, "StartProcess: id %ld", request->command_id); + FURI_LOG_D(TAG, "StartProcess: id %lu", request->command_id); - PB_CommandStatus result = PB_CommandStatus_ERROR_APP_CANT_START; + PB_CommandStatus result; Loader* loader = furi_record_open(RECORD_LOADER); const char* app_name = request->content.app_start_request.name; @@ -62,7 +62,7 @@ static void rpc_system_app_start_process(const PB_Main* request, void* context) } else if(status == LoaderStatusOk) { result = PB_CommandStatus_OK; } else { - furi_crash("Programming Error"); + furi_crash(NULL); } } else { result = PB_CommandStatus_ERROR_INVALID_PARAMETERS; @@ -70,7 +70,7 @@ static void rpc_system_app_start_process(const PB_Main* request, void* context) furi_record_close(RECORD_LOADER); - FURI_LOG_D(TAG, "StartProcess: response id %ld, result %d", request->command_id, result); + FURI_LOG_D(TAG, "StartProcess: response id %lu, result %d", request->command_id, result); rpc_send_and_release_empty(session, request->command_id, result); } @@ -117,7 +117,7 @@ static void rpc_system_app_exit_request(const PB_Main* request, void* context) { PB_CommandStatus status; if(rpc_app->app_callback) { - FURI_LOG_D(TAG, "ExitRequest: id %ld", request->command_id); + FURI_LOG_D(TAG, "ExitRequest: id %lu", request->command_id); furi_assert(!rpc_app->last_id); furi_assert(!rpc_app->last_data); rpc_app->last_id = request->command_id; @@ -125,7 +125,7 @@ static void rpc_system_app_exit_request(const PB_Main* request, void* context) { } else { status = PB_CommandStatus_ERROR_APP_NOT_RUNNING; FURI_LOG_E( - TAG, "ExitRequest: APP_NOT_RUNNING, id %ld, status: %d", request->command_id, status); + TAG, "ExitRequest: APP_NOT_RUNNING, id %lu, status: %d", request->command_id, status); rpc_send_and_release_empty(session, request->command_id, status); } } @@ -142,7 +142,7 @@ static void rpc_system_app_load_file(const PB_Main* request, void* context) { PB_CommandStatus status; if(rpc_app->app_callback) { - FURI_LOG_D(TAG, "LoadFile: id %ld", request->command_id); + FURI_LOG_D(TAG, "LoadFile: id %lu", request->command_id); furi_assert(!rpc_app->last_id); furi_assert(!rpc_app->last_data); rpc_app->last_id = request->command_id; @@ -151,7 +151,7 @@ static void rpc_system_app_load_file(const PB_Main* request, void* context) { } else { status = PB_CommandStatus_ERROR_APP_NOT_RUNNING; FURI_LOG_E( - TAG, "LoadFile: APP_NOT_RUNNING, id %ld, status: %d", request->command_id, status); + TAG, "LoadFile: APP_NOT_RUNNING, id %lu, status: %d", request->command_id, status); rpc_send_and_release_empty(session, request->command_id, status); } } @@ -177,7 +177,7 @@ static void rpc_system_app_button_press(const PB_Main* request, void* context) { } else { status = PB_CommandStatus_ERROR_APP_NOT_RUNNING; FURI_LOG_E( - TAG, "ButtonPress: APP_NOT_RUNNING, id %ld, status: %d", request->command_id, status); + TAG, "ButtonPress: APP_NOT_RUNNING, id %lu, status: %d", request->command_id, status); rpc_send_and_release_empty(session, request->command_id, status); } } @@ -202,7 +202,7 @@ static void rpc_system_app_button_release(const PB_Main* request, void* context) } else { status = PB_CommandStatus_ERROR_APP_NOT_RUNNING; FURI_LOG_E( - TAG, "ButtonRelease: APP_NOT_RUNNING, id %ld, status: %d", request->command_id, status); + TAG, "ButtonRelease: APP_NOT_RUNNING, id %lu, status: %d", request->command_id, status); rpc_send_and_release_empty(session, request->command_id, status); } } @@ -300,7 +300,7 @@ void rpc_system_app_confirm(RpcAppSystem* rpc_app, RpcAppSystemEvent event, bool free(rpc_app->last_data); rpc_app->last_data = NULL; } - FURI_LOG_D(TAG, "AppConfirm: event %d last_id %ld status %d", event, last_id, status); + FURI_LOG_D(TAG, "AppConfirm: event %d last_id %lu status %d", event, last_id, status); rpc_send_and_release_empty(session, last_id, status); break; default: diff --git a/applications/services/rpc/rpc_cli.c b/applications/services/rpc/rpc_cli.c index 82023e3167e..d14b8eee2fa 100644 --- a/applications/services/rpc/rpc_cli.c +++ b/applications/services/rpc/rpc_cli.c @@ -44,7 +44,7 @@ void rpc_cli_command_start_session(Cli* cli, FuriString* args, void* context) { Rpc* rpc = context; uint32_t mem_before = memmgr_get_free_heap(); - FURI_LOG_D(TAG, "Free memory %ld", mem_before); + FURI_LOG_D(TAG, "Free memory %lu", mem_before); furi_hal_usb_lock(); RpcSession* rpc_session = rpc_session_open(rpc); diff --git a/applications/services/rpc/rpc_debug.c b/applications/services/rpc/rpc_debug.c index 8eb81dece1a..edc2b002513 100644 --- a/applications/services/rpc/rpc_debug.c +++ b/applications/services/rpc/rpc_debug.c @@ -10,7 +10,7 @@ static size_t rpc_debug_print_file_msg( for(size_t i = 0; i < msg_files_size; ++i, ++msg_file) { furi_string_cat_printf( str, - "%s[%c] size: %5ld", + "%s[%c] size: %5lu", prefix, msg_file->type == PB_Storage_File_FileType_DIR ? 'd' : 'f', msg_file->size); @@ -40,7 +40,7 @@ void rpc_debug_print_data(const char* prefix, uint8_t* buffer, size_t size) { str = furi_string_alloc(); furi_string_reserve(str, 100 + size * 5); - furi_string_cat_printf(str, "\r\n%s DEC(%d): {", prefix, size); + furi_string_cat_printf(str, "\r\n%s DEC(%zu): {", prefix, size); for(size_t i = 0; i < size; ++i) { furi_string_cat_printf(str, "%d, ", buffer[i]); } @@ -50,7 +50,7 @@ void rpc_debug_print_data(const char* prefix, uint8_t* buffer, size_t size) { furi_string_reset(str); furi_string_reserve(str, 100 + size * 3); - furi_string_cat_printf(str, "%s HEX(%d): {", prefix, size); + furi_string_cat_printf(str, "%s HEX(%zu): {", prefix, size); for(size_t i = 0; i < size; ++i) { furi_string_cat_printf(str, "%02X", buffer[i]); } @@ -66,7 +66,7 @@ void rpc_debug_print_message(const PB_Main* message) { furi_string_cat_printf( str, - "PB_Main: {\r\n\tresult: %d cmd_id: %ld (%s)\r\n", + "PB_Main: {\r\n\tresult: %d cmd_id: %lu (%s)\r\n", message->command_status, message->command_id, message->has_next ? "has_next" : "last"); @@ -110,9 +110,9 @@ void rpc_debug_print_message(const PB_Main* message) { } case PB_Main_storage_md5sum_response_tag: { furi_string_cat_printf(str, "\tmd5sum_response {\r\n"); - const char* path = message->content.storage_md5sum_response.md5sum; - if(path) { - furi_string_cat_printf(str, "\t\tmd5sum: %s\r\n", path); + const char* md5sum = message->content.storage_md5sum_response.md5sum; + if(md5sum) { //-V547 + furi_string_cat_printf(str, "\t\tmd5sum: %s\r\n", md5sum); } break; } diff --git a/applications/services/rpc/rpc_storage.c b/applications/services/rpc/rpc_storage.c index 3c6ff7f94d5..c4493cc74e3 100644 --- a/applications/services/rpc/rpc_storage.c +++ b/applications/services/rpc/rpc_storage.c @@ -597,7 +597,7 @@ static void rpc_system_storage_md5sum_process(const PB_Main* request, void* cont char* md5sum = response.content.storage_md5sum_response.md5sum; size_t md5sum_size = sizeof(response.content.storage_md5sum_response.md5sum); (void)md5sum_size; - furi_assert(hash_size <= ((md5sum_size - 1) / 2)); + furi_assert(hash_size <= ((md5sum_size - 1) / 2)); //-V547 for(uint8_t i = 0; i < hash_size; i++) { md5sum += snprintf(md5sum, md5sum_size, "%02x", hash[i]); } diff --git a/applications/services/rpc/rpc_system.c b/applications/services/rpc/rpc_system.c index a17be7d2d78..77dca4a1a60 100644 --- a/applications/services/rpc/rpc_system.c +++ b/applications/services/rpc/rpc_system.c @@ -30,7 +30,6 @@ static void rpc_system_system_ping_process(const PB_Main* request, void* context } PB_Main response = PB_Main_init_default; - response.has_next = false; response.command_status = PB_CommandStatus_OK; response.command_id = request->command_id; response.which_content = PB_Main_system_ping_response_tag; diff --git a/applications/services/storage/storage_external_api.c b/applications/services/storage/storage_external_api.c index 2c3a7bfc9bc..6929a9cbdf6 100644 --- a/applications/services/storage/storage_external_api.c +++ b/applications/services/storage/storage_external_api.c @@ -385,9 +385,7 @@ FS_Error storage_common_remove(Storage* storage, const char* path) { FS_Error storage_common_rename(Storage* storage, const char* old_path, const char* new_path) { FS_Error error = storage_common_copy(storage, old_path, new_path); if(error == FSE_OK) { - if(storage_simply_remove_recursive(storage, old_path)) { - error = FSE_OK; - } else { + if(!storage_simply_remove_recursive(storage, old_path)) { error = FSE_INTERNAL; } } @@ -743,7 +741,7 @@ bool storage_simply_remove_recursive(Storage* storage, const char* path) { return true; } - char* name = malloc(MAX_NAME_LENGTH + 1); + char* name = malloc(MAX_NAME_LENGTH + 1); //-V799 File* dir = storage_file_alloc(storage); cur_dir = furi_string_alloc_set(path); bool go_deeper = false; @@ -790,7 +788,7 @@ bool storage_simply_remove_recursive(Storage* storage, const char* path) { furi_string_free(cur_dir); free(name); return result; -} +} //-V773 bool storage_simply_remove(Storage* storage, const char* path) { FS_Error result; diff --git a/applications/services/storage/storage_glue.c b/applications/services/storage/storage_glue.c index c6ff08bdc83..22f2e3dfa77 100644 --- a/applications/services/storage/storage_glue.c +++ b/applications/services/storage/storage_glue.c @@ -17,7 +17,7 @@ void storage_file_init_set(StorageFile* obj, const StorageFile* src) { obj->path = furi_string_alloc_set(src->path); } -void storage_file_set(StorageFile* obj, const StorageFile* src) { +void storage_file_set(StorageFile* obj, const StorageFile* src) { //-V524 obj->file = src->file; obj->type = src->type; obj->file_data = src->file_data; @@ -172,7 +172,6 @@ void storage_push_storage_file( StorageType type, StorageData* storage) { StorageFile* storage_file = StorageFileList_push_new(storage->files); - furi_check(storage_file != NULL); file->file_id = (uint32_t)storage_file; storage_file->file = file; diff --git a/applications/services/storage/storages/storage_int.c b/applications/services/storage/storages/storage_int.c index 4fa5d130c8f..2534d47a112 100644 --- a/applications/services/storage/storages/storage_int.c +++ b/applications/services/storage/storages/storage_int.c @@ -77,7 +77,7 @@ static int storage_int_device_read( FURI_LOG_T( TAG, - "Device read: block %ld, off %ld, buffer: %p, size %ld, translated address: %p", + "Device read: block %lu, off %lu, buffer: %p, size %lu, translated address: %p", block, off, buffer, @@ -100,7 +100,7 @@ static int storage_int_device_prog( FURI_LOG_T( TAG, - "Device prog: block %ld, off %ld, buffer: %p, size %ld, translated address: %p", + "Device prog: block %lu, off %lu, buffer: %p, size %lu, translated address: %p", block, off, buffer, @@ -122,7 +122,7 @@ static int storage_int_device_erase(const struct lfs_config* c, lfs_block_t bloc LFSData* lfs_data = c->context; size_t page = lfs_data->start_page + block; - FURI_LOG_D(TAG, "Device erase: page %ld, translated page: %x", block, page); + FURI_LOG_D(TAG, "Device erase: page %lu, translated page: %zx", block, page); furi_hal_flash_erase(page); return 0; @@ -240,56 +240,38 @@ static void storage_int_lfs_mount(LFSData* lfs_data, StorageData* storage) { /****************** Common Functions ******************/ static FS_Error storage_int_parse_error(int error) { - FS_Error result = FSE_INTERNAL; + FS_Error result; if(error >= LFS_ERR_OK) { result = FSE_OK; } else { switch(error) { - case LFS_ERR_IO: - result = FSE_INTERNAL; - break; - case LFS_ERR_CORRUPT: - result = FSE_INTERNAL; - break; case LFS_ERR_NOENT: result = FSE_NOT_EXIST; break; case LFS_ERR_EXIST: result = FSE_EXIST; break; - case LFS_ERR_NOTDIR: - result = FSE_INVALID_NAME; - break; - case LFS_ERR_ISDIR: - result = FSE_INVALID_NAME; - break; case LFS_ERR_NOTEMPTY: result = FSE_DENIED; break; - case LFS_ERR_BADF: - result = FSE_INVALID_NAME; - break; - case LFS_ERR_FBIG: - result = FSE_INTERNAL; - break; case LFS_ERR_INVAL: - result = FSE_INVALID_PARAMETER; - break; - case LFS_ERR_NOSPC: - result = FSE_INTERNAL; - break; - case LFS_ERR_NOMEM: - result = FSE_INTERNAL; - break; case LFS_ERR_NOATTR: result = FSE_INVALID_PARAMETER; break; + case LFS_ERR_BADF: + case LFS_ERR_ISDIR: + case LFS_ERR_NOTDIR: case LFS_ERR_NAMETOOLONG: result = FSE_INVALID_NAME; break; + case LFS_ERR_IO: + case LFS_ERR_FBIG: + case LFS_ERR_NOSPC: + case LFS_ERR_NOMEM: + case LFS_ERR_CORRUPT: default: - break; + result = FSE_INTERNAL; } } @@ -740,7 +722,7 @@ void storage_int_init(StorageData* storage) { LFSData* lfs_data = storage_int_lfs_data_alloc(); FURI_LOG_I( TAG, - "Config: start %p, read %ld, write %ld, page size: %ld, page count: %ld, cycles: %ld", + "Config: start %p, read %lu, write %lu, page size: %lu, page count: %lu, cycles: %ld", (void*)lfs_data->start_address, lfs_data->config.read_size, lfs_data->config.prog_size, diff --git a/applications/settings/about/about.c b/applications/settings/about/about.c index 1719e188d5c..560a683cfe0 100644 --- a/applications/settings/about/about.c +++ b/applications/settings/about/about.c @@ -119,7 +119,7 @@ static DialogMessageButton fw_version_screen(DialogsApp* dialogs, DialogMessage* c2_ver = ble_glue_get_c2_info(); #endif - if(!ver) { + if(!ver) { //-V1051 furi_string_cat_printf(buffer, "No info\n"); } else { furi_string_cat_printf( @@ -208,4 +208,4 @@ int32_t about_settings_app(void* p) { furi_record_close(RECORD_GUI); return 0; -} \ No newline at end of file +} diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c index cf474c546c7..94c5ee9f049 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c @@ -84,7 +84,7 @@ bool desktop_settings_scene_favorite_on_event(void* context, SceneManagerEvent e scene_manager_get_scene_state(app->scene_manager, DesktopSettingsAppSceneFavorite); if(event.type == SceneManagerEventTypeCustom) { - if(strcmp(FLIPPER_APPS[event.event].name, FAP_LOADER_APP_NAME)) { + if(strcmp(FLIPPER_APPS[event.event].name, FAP_LOADER_APP_NAME) != 0) { if(primary_favorite) { app->settings.favorite_primary.is_external = false; strncpy( diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup_howto.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup_howto.c index b8d630f2eed..ec128246fa3 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup_howto.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup_howto.c @@ -32,9 +32,7 @@ bool desktop_settings_scene_pin_setup_howto_on_event(void* context, SceneManager consumed = true; break; default: - furi_assert(0); - consumed = true; - break; + furi_crash(NULL); } } return consumed; diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup_howto2.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup_howto2.c index 477d1f27a73..44b8e1bf795 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup_howto2.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup_howto2.c @@ -52,9 +52,7 @@ bool desktop_settings_scene_pin_setup_howto2_on_event(void* context, SceneManage break; } default: - furi_assert(0); - consumed = true; - break; + furi_crash(NULL); } } return consumed; diff --git a/applications/settings/power_settings_app/views/battery_info.c b/applications/settings/power_settings_app/views/battery_info.c index d760164b989..5353a2e2a67 100644 --- a/applications/settings/power_settings_app/views/battery_info.c +++ b/applications/settings/power_settings_app/views/battery_info.c @@ -49,7 +49,7 @@ static void draw_battery(Canvas* canvas, BatteryInfoModel* data, int x, int y) { snprintf( value, sizeof(value), - "%ld.%ldV %ldmA", + "%lu.%luV %lumA", (uint32_t)(data->vbus_voltage), (uint32_t)(data->vbus_voltage * 10) % 10, charge_current); @@ -75,7 +75,7 @@ static void draw_battery(Canvas* canvas, BatteryInfoModel* data, int x, int y) { snprintf( value, sizeof(value), - "%ld.%ldV", + "%lu.%luV", (uint32_t)(data->charging_voltage), (uint32_t)(data->charging_voltage * 10) % 10); } else { @@ -100,14 +100,14 @@ static void battery_info_draw_callback(Canvas* canvas, void* context) { char voltage[10]; char health[10]; - snprintf(batt_level, sizeof(batt_level), "%ld%%", (uint32_t)model->charge); - snprintf(temperature, sizeof(temperature), "%ld C", (uint32_t)model->gauge_temperature); + snprintf(batt_level, sizeof(batt_level), "%lu%%", (uint32_t)model->charge); + snprintf(temperature, sizeof(temperature), "%lu C", (uint32_t)model->gauge_temperature); snprintf( voltage, sizeof(voltage), - "%ld.%01ld V", + "%lu.%01lu V", (uint32_t)model->gauge_voltage, - (uint32_t)(model->gauge_voltage * 10) % 10); + (uint32_t)(model->gauge_voltage * 10) % 10UL); snprintf(health, sizeof(health), "%d%%", model->health); draw_stat(canvas, 8, 42, &I_Battery_16x16, batt_level); diff --git a/applications/settings/storage_settings/scenes/storage_settings_scene_format_confirm.c b/applications/settings/storage_settings/scenes/storage_settings_scene_format_confirm.c index 261ef1997d0..8af065bf8bb 100644 --- a/applications/settings/storage_settings/scenes/storage_settings_scene_format_confirm.c +++ b/applications/settings/storage_settings/scenes/storage_settings_scene_format_confirm.c @@ -40,8 +40,6 @@ bool storage_settings_scene_format_confirm_on_event(void* context, SceneManagerE if(event.type == SceneManagerEventTypeCustom) { switch(event.event) { case DialogExResultLeft: - consumed = scene_manager_previous_scene(app->scene_manager); - break; case DialogExResultCenter: consumed = scene_manager_previous_scene(app->scene_manager); break; diff --git a/applications/settings/storage_settings/scenes/storage_settings_scene_sd_info.c b/applications/settings/storage_settings/scenes/storage_settings_scene_sd_info.c index ede610d0e22..0c398ed5b49 100644 --- a/applications/settings/storage_settings/scenes/storage_settings_scene_sd_info.c +++ b/applications/settings/storage_settings/scenes/storage_settings_scene_sd_info.c @@ -47,8 +47,6 @@ bool storage_settings_scene_sd_info_on_event(void* context, SceneManagerEvent ev if(event.type == SceneManagerEventTypeCustom) { switch(event.event) { case DialogExResultLeft: - consumed = scene_manager_previous_scene(app->scene_manager); - break; case DialogExResultCenter: consumed = scene_manager_previous_scene(app->scene_manager); break; diff --git a/applications/settings/storage_settings/scenes/storage_settings_scene_unmount_confirm.c b/applications/settings/storage_settings/scenes/storage_settings_scene_unmount_confirm.c index 2b485b7f719..0c15116be1b 100644 --- a/applications/settings/storage_settings/scenes/storage_settings_scene_unmount_confirm.c +++ b/applications/settings/storage_settings/scenes/storage_settings_scene_unmount_confirm.c @@ -41,8 +41,6 @@ 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; diff --git a/applications/system/updater/util/update_task_worker_backup.c b/applications/system/updater/util/update_task_worker_backup.c index ce62da2a1d9..1f88d4f4480 100644 --- a/applications/system/updater/util/update_task_worker_backup.c +++ b/applications/system/updater/util/update_task_worker_backup.c @@ -176,7 +176,7 @@ int32_t update_task_worker_backup_restore(void* context) { if(boot_mode == FuriHalRtcBootModePreUpdate) { success = update_task_pre_update(update_task); - } else if(boot_mode == FuriHalRtcBootModePostUpdate) { + } else if(boot_mode == FuriHalRtcBootModePostUpdate) { //-V547 success = update_task_post_update(update_task); if(success) { update_operation_disarm(); diff --git a/applications/system/updater/util/update_task_worker_flasher.c b/applications/system/updater/util/update_task_worker_flasher.c index 7358a633412..63024ced9cf 100644 --- a/applications/system/updater/util/update_task_worker_flasher.c +++ b/applications/system/updater/util/update_task_worker_flasher.c @@ -143,7 +143,6 @@ static void update_task_wait_for_restart(UpdateTask* update_task) { } static bool update_task_write_stack(UpdateTask* update_task) { - bool success = false; UpdateManifest* manifest = update_task->manifest; do { FURI_LOG_W(TAG, "Writing stack"); @@ -162,13 +161,11 @@ static bool update_task_write_stack(UpdateTask* update_task) { update_task_set_progress(update_task, UpdateTaskStageRadioInstall, 100); /* ...system will restart here. */ update_task_wait_for_restart(update_task); - success = true; } while(false); - return success; + return false; /* will return only in the case of failure */ } static bool update_task_remove_stack(UpdateTask* update_task) { - bool success = false; do { FURI_LOG_W(TAG, "Removing stack"); update_task_set_progress(update_task, UpdateTaskStageRadioErase, 30); @@ -178,9 +175,8 @@ static bool update_task_remove_stack(UpdateTask* update_task) { update_task_set_progress(update_task, UpdateTaskStageRadioErase, 100); /* ...system will restart here. */ update_task_wait_for_restart(update_task); - success = true; } while(false); - return success; + return false; /* will return only in the case of failure */ } static bool update_task_manage_radiostack(UpdateTask* update_task) { diff --git a/debug/gdbinit b/debug/gdbinit new file mode 100644 index 00000000000..cba5d6b1a81 --- /dev/null +++ b/debug/gdbinit @@ -0,0 +1,10 @@ +set confirm off +set pagination off +set print pretty on +set print object on +set print static-members on +set print vtbl on +set print demangle on +set demangle-style gnu-v3 +set print sevenbit-strings off + diff --git a/firmware/targets/f7/Src/update.c b/firmware/targets/f7/Src/update.c index 722a7b61605..a68a8b7a714 100644 --- a/firmware/targets/f7/Src/update.c +++ b/firmware/targets/f7/Src/update.c @@ -149,7 +149,7 @@ static UpdateManifest* flipper_update_process_manifest(const FuriString* manifes do { uint16_t size_read = 0; - if(f_read(&file, manifest_data + bytes_read, MAX_READ, &size_read) != FR_OK) { + if(f_read(&file, manifest_data + bytes_read, MAX_READ, &size_read) != FR_OK) { //-V769 break; } bytes_read += size_read; diff --git a/firmware/targets/f7/ble_glue/ble_glue.c b/firmware/targets/f7/ble_glue/ble_glue.c index b3752f17f2c..83562c73eef 100644 --- a/firmware/targets/f7/ble_glue/ble_glue.c +++ b/firmware/targets/f7/ble_glue/ble_glue.c @@ -456,17 +456,15 @@ BleGlueCommandResult ble_glue_fus_get_status() { BleGlueCommandResult ble_glue_fus_wait_operation() { furi_check(ble_glue->c2_info.mode == BleGlueC2ModeFUS); - bool wip; - do { + + while(true) { BleGlueCommandResult fus_status = ble_glue_fus_get_status(); - if(fus_status == BleGlueCommandResultError) { - return BleGlueCommandResultError; - } - wip = fus_status == BleGlueCommandResultOperationOngoing; - if(wip) { + if(fus_status == BleGlueCommandResultOperationOngoing) { furi_delay_ms(20); + } else if(fus_status == BleGlueCommandResultError) { + return BleGlueCommandResultError; + } else { + return BleGlueCommandResultOK; } - } while(wip); - - return BleGlueCommandResultOK; + } } diff --git a/firmware/targets/f7/ble_glue/gap.c b/firmware/targets/f7/ble_glue/gap.c index 3e29527ecd3..8ef037d6b72 100644 --- a/firmware/targets/f7/ble_glue/gap.c +++ b/firmware/targets/f7/ble_glue/gap.c @@ -227,7 +227,7 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification(void* pckt) { case EVT_BLUE_GAP_NUMERIC_COMPARISON_VALUE: { uint32_t pin = ((aci_gap_numeric_comparison_value_event_rp0*)(blue_evt->data))->Numeric_Value; - FURI_LOG_I(TAG, "Verify numeric comparison: %06ld", pin); + FURI_LOG_I(TAG, "Verify numeric comparison: %06lu", pin); GapEvent event = {.type = GapEventTypePinCodeVerify, .data.pin_code = pin}; bool result = gap->on_event_cb(event, gap->context); aci_gap_numeric_comparison_value_confirm_yesno(gap->service.connection_handle, result); @@ -245,7 +245,7 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification(void* pckt) { } else { FURI_LOG_I(TAG, "Pairing complete"); GapEvent event = {.type = GapEventTypeConnected}; - gap->on_event_cb(event, gap->context); + gap->on_event_cb(event, gap->context); //-V595 } break; diff --git a/firmware/targets/f7/ble_glue/hid_service.c b/firmware/targets/f7/ble_glue/hid_service.c index d0ca9685ab6..47d242d4dff 100644 --- a/firmware/targets/f7/ble_glue/hid_service.c +++ b/firmware/targets/f7/ble_glue/hid_service.c @@ -87,7 +87,7 @@ void hid_svc_start() { #if(HID_SVC_REPORT_COUNT != 0) for(uint8_t i = 0; i < HID_SVC_REPORT_COUNT; i++) { - if(i < HID_SVC_INPUT_REPORT_COUNT) { + if(i < HID_SVC_INPUT_REPORT_COUNT) { //-V547 uint8_t buf[2] = {i + 1, 1}; // 1 input char_uuid.Char_UUID_16 = REPORT_CHAR_UUID; status = aci_gatt_add_char( diff --git a/firmware/targets/f7/ble_glue/hw_ipcc.c b/firmware/targets/f7/ble_glue/hw_ipcc.c index 64dd9ef9b7b..7c84df09fe5 100644 --- a/firmware/targets/f7/ble_glue/hw_ipcc.c +++ b/firmware/targets/f7/ble_glue/hw_ipcc.c @@ -24,9 +24,9 @@ /* Global variables ---------------------------------------------------------*/ /* Private defines -----------------------------------------------------------*/ #define HW_IPCC_TX_PENDING(channel) \ - (!(LL_C1_IPCC_IsActiveFlag_CHx(IPCC, channel))) && (((~(IPCC->C1MR)) & (channel << 16U))) + (!(LL_C1_IPCC_IsActiveFlag_CHx(IPCC, channel))) && (((~(IPCC->C1MR)) & ((channel) << 16U))) #define HW_IPCC_RX_PENDING(channel) \ - (LL_C2_IPCC_IsActiveFlag_CHx(IPCC, channel)) && (((~(IPCC->C1MR)) & (channel << 0U))) + (LL_C2_IPCC_IsActiveFlag_CHx(IPCC, channel)) && (((~(IPCC->C1MR)) & ((channel) << 0U))) /* Private macros ------------------------------------------------------------*/ /* Private typedef -----------------------------------------------------------*/ diff --git a/firmware/targets/f7/fatfs/stm32_adafruit_sd.c b/firmware/targets/f7/fatfs/stm32_adafruit_sd.c index 07cae31fe8e..b9b65f06a43 100644 --- a/firmware/targets/f7/fatfs/stm32_adafruit_sd.c +++ b/firmware/targets/f7/fatfs/stm32_adafruit_sd.c @@ -405,9 +405,9 @@ uint8_t BSP_SD_GetCardInfo(SD_CardInfo* pCardInfo) { pCardInfo->LogBlockNbr = (pCardInfo->CardCapacity) / (pCardInfo->LogBlockSize); } else { pCardInfo->CardCapacity = (pCardInfo->Csd.version.v1.DeviceSize + 1); - pCardInfo->CardCapacity *= (1 << (pCardInfo->Csd.version.v1.DeviceSizeMul + 2)); + pCardInfo->CardCapacity *= (1UL << (pCardInfo->Csd.version.v1.DeviceSizeMul + 2)); pCardInfo->LogBlockSize = 512; - pCardInfo->CardBlockSize = 1 << (pCardInfo->Csd.RdBlockLen); + pCardInfo->CardBlockSize = 1UL << (pCardInfo->Csd.RdBlockLen); pCardInfo->CardCapacity *= pCardInfo->CardBlockSize; pCardInfo->LogBlockNbr = (pCardInfo->CardCapacity) / (pCardInfo->LogBlockSize); } @@ -982,7 +982,8 @@ uint8_t SD_GoIdleState(void) { SD_IO_WriteByte(SD_DUMMY_BYTE); /* Send ACMD41 (SD_CMD_SD_APP_OP_COND) to initialize SDHC or SDXC cards: R1 response (0x00: no errors) */ - response = SD_SendCmd(SD_CMD_SD_APP_OP_COND, 0x00000000, 0xFF, SD_ANSWER_R1_EXPECTED); + response = //-V519 + SD_SendCmd(SD_CMD_SD_APP_OP_COND, 0x00000000, 0xFF, SD_ANSWER_R1_EXPECTED); SD_IO_CSState(1); SD_IO_WriteByte(SD_DUMMY_BYTE); if(counter >= SD_MAX_TRY) { @@ -1001,7 +1002,8 @@ uint8_t SD_GoIdleState(void) { SD_IO_WriteByte(SD_DUMMY_BYTE); /* Send ACMD41 (SD_CMD_SD_APP_OP_COND) to initialize SDHC or SDXC cards: R1 response (0x00: no errors) */ - response = SD_SendCmd(SD_CMD_SD_APP_OP_COND, 0x40000000, 0xFF, SD_ANSWER_R1_EXPECTED); + response = //-V519 + SD_SendCmd(SD_CMD_SD_APP_OP_COND, 0x40000000, 0xFF, SD_ANSWER_R1_EXPECTED); SD_IO_CSState(1); SD_IO_WriteByte(SD_DUMMY_BYTE); if(counter >= SD_MAX_TRY) { diff --git a/firmware/targets/f7/furi_hal/furi_hal_bt.c b/firmware/targets/f7/furi_hal/furi_hal_bt.c index fc1c25c7e57..0857fe4ee56 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_bt.c +++ b/firmware/targets/f7/furi_hal/furi_hal_bt.c @@ -415,7 +415,7 @@ float furi_hal_bt_get_rssi() { val += 6.0; rssi >>= 1; } - val += (417 * rssi + 18080) >> 10; + val += (float)((417 * rssi + 18080) >> 10); } return val; } diff --git a/firmware/targets/f7/furi_hal/furi_hal_bt_hid.c b/firmware/targets/f7/furi_hal/furi_hal_bt_hid.c index 22415199cbd..ab3855f42cc 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_bt_hid.c +++ b/firmware/targets/f7/furi_hal/furi_hal_bt_hid.c @@ -216,7 +216,7 @@ bool furi_hal_bt_hid_kb_release_all() { bool furi_hal_bt_hid_consumer_key_press(uint16_t button) { furi_assert(consumer_report); - for(uint8_t i = 0; i < FURI_HAL_BT_HID_CONSUMER_MAX_KEYS; i++) { + for(uint8_t i = 0; i < FURI_HAL_BT_HID_CONSUMER_MAX_KEYS; i++) { //-V1008 if(consumer_report->key[i] == 0) { consumer_report->key[i] = button; break; @@ -228,7 +228,7 @@ bool furi_hal_bt_hid_consumer_key_press(uint16_t button) { bool furi_hal_bt_hid_consumer_key_release(uint16_t button) { furi_assert(consumer_report); - for(uint8_t i = 0; i < FURI_HAL_BT_HID_CONSUMER_MAX_KEYS; i++) { + for(uint8_t i = 0; i < FURI_HAL_BT_HID_CONSUMER_MAX_KEYS; i++) { //-V1008 if(consumer_report->key[i] == button) { consumer_report->key[i] = 0; break; @@ -240,7 +240,7 @@ bool furi_hal_bt_hid_consumer_key_release(uint16_t button) { bool furi_hal_bt_hid_consumer_key_release_all() { furi_assert(consumer_report); - for(uint8_t i = 0; i < FURI_HAL_BT_HID_CONSUMER_MAX_KEYS; i++) { + for(uint8_t i = 0; i < FURI_HAL_BT_HID_CONSUMER_MAX_KEYS; i++) { //-V1008 consumer_report->key[i] = 0; } return hid_svc_update_input_report( diff --git a/firmware/targets/f7/furi_hal/furi_hal_i2c_config.c b/firmware/targets/f7/furi_hal/furi_hal_i2c_config.c index d832c4f66fb..678eb29652f 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_i2c_config.c +++ b/firmware/targets/f7/furi_hal/furi_hal_i2c_config.c @@ -108,7 +108,7 @@ void furi_hal_i2c_bus_handle_power_event( GpioSpeedLow, GpioAltFn4I2C1); - LL_I2C_InitTypeDef I2C_InitStruct = {0}; + LL_I2C_InitTypeDef I2C_InitStruct; I2C_InitStruct.PeripheralMode = LL_I2C_MODE_I2C; I2C_InitStruct.AnalogFilter = LL_I2C_ANALOGFILTER_ENABLE; I2C_InitStruct.DigitalFilter = 0; @@ -152,7 +152,7 @@ void furi_hal_i2c_bus_handle_external_event( furi_hal_gpio_init_ex( &gpio_ext_pc1, GpioModeAltFunctionOpenDrain, GpioPullNo, GpioSpeedLow, GpioAltFn4I2C3); - LL_I2C_InitTypeDef I2C_InitStruct = {0}; + LL_I2C_InitTypeDef I2C_InitStruct; I2C_InitStruct.PeripheralMode = LL_I2C_MODE_I2C; I2C_InitStruct.AnalogFilter = LL_I2C_ANALOGFILTER_ENABLE; I2C_InitStruct.DigitalFilter = 0; diff --git a/firmware/targets/f7/furi_hal/furi_hal_memory.c b/firmware/targets/f7/furi_hal/furi_hal_memory.c index ec71e666024..9716f1e5292 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_memory.c +++ b/firmware/targets/f7/furi_hal/furi_hal_memory.c @@ -55,9 +55,9 @@ void furi_hal_memory_init() { memory->region[SRAM_B].size = sram2b_unprotected_size; FURI_LOG_I( - TAG, "SRAM2A: 0x%p, %ld", memory->region[SRAM_A].start, memory->region[SRAM_A].size); + TAG, "SRAM2A: 0x%p, %lu", memory->region[SRAM_A].start, memory->region[SRAM_A].size); FURI_LOG_I( - TAG, "SRAM2B: 0x%p, %ld", memory->region[SRAM_B].start, memory->region[SRAM_B].size); + TAG, "SRAM2B: 0x%p, %lu", memory->region[SRAM_B].start, memory->region[SRAM_B].size); if((memory->region[SRAM_A].size > 0) || (memory->region[SRAM_B].size > 0)) { if((memory->region[SRAM_A].size > 0)) { @@ -120,4 +120,4 @@ size_t furi_hal_memory_max_pool_block() { } } return max; -} \ No newline at end of file +} diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc.c b/firmware/targets/f7/furi_hal/furi_hal_nfc.c index 75c695afb9e..6381d1a91b2 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_nfc.c +++ b/firmware/targets/f7/furi_hal/furi_hal_nfc.c @@ -425,7 +425,7 @@ bool furi_hal_nfc_emulate_nfca( buff_rx_len = 0; buff_tx_len = 0; uint32_t flag = furi_event_flag_wait(event, EVENT_FLAG_ALL, FuriFlagWaitAny, timeout); - if(flag == FuriFlagErrorTimeout || flag == EVENT_FLAG_STOP) { + if(flag == (unsigned)FuriFlagErrorTimeout || flag == EVENT_FLAG_STOP) { break; } bool data_received = false; @@ -609,9 +609,9 @@ static uint16_t furi_hal_nfc_data_and_parity_to_bitstream( out[curr_bit_pos / 8] = next_par_bit; curr_bit_pos++; } else { - out[curr_bit_pos / 8] |= data[i] << curr_bit_pos % 8; + out[curr_bit_pos / 8] |= data[i] << (curr_bit_pos % 8); out[curr_bit_pos / 8 + 1] = data[i] >> (8 - curr_bit_pos % 8); - out[curr_bit_pos / 8 + 1] |= next_par_bit << curr_bit_pos % 8; + out[curr_bit_pos / 8 + 1] |= next_par_bit << (curr_bit_pos % 8); curr_bit_pos += 9; } } @@ -635,7 +635,7 @@ uint16_t furi_hal_nfc_bitstream_to_data_and_parity( uint16_t bit_processed = 0; memset(out_parity, 0, in_buff_bits / 9); while(bit_processed < in_buff_bits) { - out_data[curr_byte] = in_buff[bit_processed / 8] >> bit_processed % 8; + out_data[curr_byte] = in_buff[bit_processed / 8] >> (bit_processed % 8); out_data[curr_byte] |= in_buff[bit_processed / 8 + 1] << (8 - bit_processed % 8); out_parity[curr_byte / 8] |= FURI_BIT(in_buff[bit_processed / 8 + 1], bit_processed % 8) << (7 - curr_byte % 8); @@ -802,4 +802,4 @@ FuriHalNfcReturn furi_hal_nfc_ll_txrx_bits( void furi_hal_nfc_ll_poll() { rfalWorker(); -} \ No newline at end of file +} diff --git a/firmware/targets/f7/furi_hal/furi_hal_os.c b/firmware/targets/f7/furi_hal/furi_hal_os.c index 97d022c933a..ee9743e62c6 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_os.c +++ b/firmware/targets/f7/furi_hal/furi_hal_os.c @@ -15,8 +15,8 @@ #define FURI_HAL_IDLE_TIMER_CLK_HZ 32768 #define FURI_HAL_OS_TICK_HZ configTICK_RATE_HZ -#define FURI_HAL_OS_IDLE_CNT_TO_TICKS(x) ((x * FURI_HAL_OS_TICK_HZ) / FURI_HAL_IDLE_TIMER_CLK_HZ) -#define FURI_HAL_OS_TICKS_TO_IDLE_CNT(x) ((x * FURI_HAL_IDLE_TIMER_CLK_HZ) / FURI_HAL_OS_TICK_HZ) +#define FURI_HAL_OS_IDLE_CNT_TO_TICKS(x) (((x)*FURI_HAL_OS_TICK_HZ) / FURI_HAL_IDLE_TIMER_CLK_HZ) +#define FURI_HAL_OS_TICKS_TO_IDLE_CNT(x) (((x)*FURI_HAL_IDLE_TIMER_CLK_HZ) / FURI_HAL_OS_TICK_HZ) #define FURI_HAL_IDLE_TIMER_TICK_PER_EPOCH (FURI_HAL_OS_IDLE_CNT_TO_TICKS(FURI_HAL_IDLE_TIMER_MAX)) #define FURI_HAL_OS_MAX_SLEEP (FURI_HAL_IDLE_TIMER_TICK_PER_EPOCH - 1) diff --git a/firmware/targets/f7/furi_hal/furi_hal_pwm.c b/firmware/targets/f7/furi_hal/furi_hal_pwm.c index e484808d5ab..e47f752abd0 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_pwm.c +++ b/firmware/targets/f7/furi_hal/furi_hal_pwm.c @@ -110,7 +110,7 @@ void furi_hal_pwm_set_params(FuriHalPwmOutputId channel, uint32_t freq, uint8_t bool clock_lse = false; do { - period = freq_div / (1 << prescaler); + period = freq_div / (1UL << prescaler); if(period <= 0xFFFF) { break; } diff --git a/firmware/targets/f7/furi_hal/furi_hal_rtc.c b/firmware/targets/f7/furi_hal/furi_hal_rtc.c index e5fa8c767f1..e011406ad89 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_rtc.c +++ b/firmware/targets/f7/furi_hal/furi_hal_rtc.c @@ -154,7 +154,7 @@ void furi_hal_rtc_deinit_early() { } void furi_hal_rtc_init() { - LL_RTC_InitTypeDef RTC_InitStruct = {0}; + LL_RTC_InitTypeDef RTC_InitStruct; RTC_InitStruct.HourFormat = LL_RTC_HOURFORMAT_24HOUR; RTC_InitStruct.AsynchPrescaler = 127; RTC_InitStruct.SynchPrescaler = 255; diff --git a/firmware/targets/f7/furi_hal/furi_hal_uart.c b/firmware/targets/f7/furi_hal/furi_hal_uart.c index cb44b6d1670..54232e67fa8 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_uart.c +++ b/firmware/targets/f7/furi_hal/furi_hal_uart.c @@ -26,7 +26,7 @@ static void furi_hal_usart_init(uint32_t baud) { GpioSpeedVeryHigh, GpioAltFn7USART1); - LL_USART_InitTypeDef USART_InitStruct = {0}; + LL_USART_InitTypeDef USART_InitStruct; USART_InitStruct.PrescalerValue = LL_USART_PRESCALER_DIV1; USART_InitStruct.BaudRate = baud; USART_InitStruct.DataWidth = LL_USART_DATAWIDTH_8B; @@ -62,7 +62,7 @@ static void furi_hal_lpuart_init(uint32_t baud) { GpioSpeedVeryHigh, GpioAltFn8LPUART1); - LL_LPUART_InitTypeDef LPUART_InitStruct = {0}; + LL_LPUART_InitTypeDef LPUART_InitStruct; LPUART_InitStruct.PrescalerValue = LL_LPUART_PRESCALER_DIV1; LPUART_InitStruct.BaudRate = 115200; LPUART_InitStruct.DataWidth = LL_LPUART_DATAWIDTH_8B; diff --git a/firmware/targets/f7/furi_hal/furi_hal_usb_hid.c b/firmware/targets/f7/furi_hal/furi_hal_usb_hid.c index a7253223b90..fc1ce024c60 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_usb_hid.c +++ b/firmware/targets/f7/furi_hal/furi_hal_usb_hid.c @@ -360,11 +360,11 @@ bool furi_hal_hid_consumer_key_release(uint16_t button) { static void* hid_set_string_descr(char* str) { furi_assert(str); - uint8_t len = strlen(str); + size_t len = strlen(str); struct usb_string_descriptor* dev_str_desc = malloc(len * 2 + 2); dev_str_desc->bLength = len * 2 + 2; dev_str_desc->bDescriptorType = USB_DTYPE_STRING; - for(uint8_t i = 0; i < len; i++) dev_str_desc->wString[i] = str[i]; + for(size_t i = 0; i < len; i++) dev_str_desc->wString[i] = str[i]; return dev_str_desc; } diff --git a/firmware/targets/f7/furi_hal/furi_hal_version.c b/firmware/targets/f7/furi_hal/furi_hal_version.c index 697d659311b..b7827ac7fcd 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_version.c +++ b/firmware/targets/f7/furi_hal/furi_hal_version.c @@ -174,8 +174,6 @@ static void furi_hal_version_load_otp_v2() { void furi_hal_version_init() { switch(furi_hal_version_get_otp_version()) { case FuriHalVersionOtpVersionUnknown: - furi_hal_version_load_otp_default(); - break; case FuriHalVersionOtpVersionEmpty: furi_hal_version_load_otp_default(); break; diff --git a/furi/core/check.c b/furi/core/check.c index 1c2a005f386..910527ceed8 100644 --- a/furi/core/check.c +++ b/furi/core/check.c @@ -117,7 +117,7 @@ FURI_NORETURN void __furi_crash() { if(debug) { furi_hal_console_puts("\r\nSystem halted. Connect debugger for more info\r\n"); furi_hal_console_puts("\033[0m\r\n"); - RESTORE_REGISTERS_AND_HALT_MCU(debug); + RESTORE_REGISTERS_AND_HALT_MCU(true); } else { furi_hal_rtc_set_fault_data((uint32_t)__furi_check_message); furi_hal_console_puts("\r\nRebooting system.\r\n"); diff --git a/furi/core/core_defines.h b/furi/core/core_defines.h index 487fe2ca30c..a0f50aff96a 100644 --- a/furi/core/core_defines.h +++ b/furi/core/core_defines.h @@ -93,7 +93,7 @@ extern "C" { #endif #ifndef FURI_BIT_CLEAR -#define FURI_BIT_CLEAR(x, n) ((x) &= ~(1 << (n))) +#define FURI_BIT_CLEAR(x, n) ((x) &= ~(1UL << (n))) #endif #define FURI_SW_MEMBARRIER() asm volatile("" : : : "memory") diff --git a/furi/core/event_flag.c b/furi/core/event_flag.c index 07dd30a1693..87de65f2df3 100644 --- a/furi/core/event_flag.c +++ b/furi/core/event_flag.c @@ -9,7 +9,11 @@ FuriEventFlag* furi_event_flag_alloc() { furi_assert(!FURI_IS_IRQ_MODE()); - return ((FuriEventFlag*)xEventGroupCreate()); + + EventGroupHandle_t handle = xEventGroupCreate(); + furi_check(handle); + + return ((FuriEventFlag*)handle); } void furi_event_flag_free(FuriEventFlag* instance) { diff --git a/furi/core/memmgr_heap.c b/furi/core/memmgr_heap.c index 01153fe5718..ca206cd3981 100644 --- a/furi/core/memmgr_heap.c +++ b/furi/core/memmgr_heap.c @@ -212,7 +212,8 @@ static inline void traceFREE(void* pointer, size_t size) { MemmgrHeapThreadDict_get(memmgr_heap_thread_dict, (uint32_t)thread_id); if(alloc_dict) { // In some cases thread may want to release memory that was not allocated by it - (void)MemmgrHeapAllocDict_erase(*alloc_dict, (uint32_t)pointer); + const bool res = MemmgrHeapAllocDict_erase(*alloc_dict, (uint32_t)pointer); + UNUSED(res); } memmgr_heap_thread_trace_depth--; } @@ -520,8 +521,8 @@ void vPortFree(void* pv) { { furi_assert((size_t)pv >= SRAM_BASE); furi_assert((size_t)pv < SRAM_BASE + 1024 * 256); + furi_assert(pxLink->xBlockSize >= xHeapStructSize); furi_assert((pxLink->xBlockSize - xHeapStructSize) < 1024 * 256); - furi_assert((int32_t)(pxLink->xBlockSize - xHeapStructSize) >= 0); /* Add this block to the list of free blocks. */ xFreeBytesRemaining += pxLink->xBlockSize; diff --git a/furi/core/message_queue.c b/furi/core/message_queue.c index 2658d6ff736..9a41f877527 100644 --- a/furi/core/message_queue.c +++ b/furi/core/message_queue.c @@ -7,7 +7,10 @@ FuriMessageQueue* furi_message_queue_alloc(uint32_t msg_count, uint32_t msg_size) { furi_assert((furi_is_irq_context() == 0U) && (msg_count > 0U) && (msg_size > 0U)); - return ((FuriMessageQueue*)xQueueCreate(msg_count, msg_size)); + QueueHandle_t handle = xQueueCreate(msg_count, msg_size); + furi_check(handle); + + return ((FuriMessageQueue*)handle); } void furi_message_queue_free(FuriMessageQueue* instance) { diff --git a/furi/core/mutex.c b/furi/core/mutex.c index ab66b0f18af..9fb964a1e53 100644 --- a/furi/core/mutex.c +++ b/furi/core/mutex.c @@ -30,6 +30,8 @@ FuriMutex* furi_mutex_alloc(FuriMutexType type) { void furi_mutex_free(FuriMutex* instance) { furi_assert(!FURI_IS_IRQ_MODE()); + furi_assert(instance); + vSemaphoreDelete((SemaphoreHandle_t)((uint32_t)instance & ~1U)); } diff --git a/furi/core/stream_buffer.c b/furi/core/stream_buffer.c index 2df84fa5bb3..bf483948be6 100644 --- a/furi/core/stream_buffer.c +++ b/furi/core/stream_buffer.c @@ -1,18 +1,26 @@ #include "base.h" +#include "check.h" #include "stream_buffer.h" #include "common_defines.h" #include #include FuriStreamBuffer* furi_stream_buffer_alloc(size_t size, size_t trigger_level) { - return xStreamBufferCreate(size, trigger_level); + furi_assert(size != 0); + + StreamBufferHandle_t handle = xStreamBufferCreate(size, trigger_level); + furi_check(handle); + + return handle; }; void furi_stream_buffer_free(FuriStreamBuffer* stream_buffer) { + furi_assert(stream_buffer); vStreamBufferDelete(stream_buffer); }; bool furi_stream_set_trigger_level(FuriStreamBuffer* stream_buffer, size_t trigger_level) { + furi_assert(stream_buffer); return xStreamBufferSetTriggerLevel(stream_buffer, trigger_level) == pdTRUE; }; diff --git a/furi/core/string.c b/furi/core/string.c index 901b1f625d4..4384fe06a22 100644 --- a/furi/core/string.c +++ b/furi/core/string.c @@ -29,16 +29,16 @@ FuriString* furi_string_alloc() { } FuriString* furi_string_alloc_set(const FuriString* s) { - FuriString* string = malloc(sizeof(FuriString)); //-V773 + FuriString* string = malloc(sizeof(FuriString)); //-V799 string_init_set(string->string, s->string); return string; -} +} //-V773 FuriString* furi_string_alloc_set_str(const char cstr[]) { - FuriString* string = malloc(sizeof(FuriString)); //-V773 + FuriString* string = malloc(sizeof(FuriString)); //-V799 string_init_set(string->string, cstr); return string; -} +} //-V773 FuriString* furi_string_alloc_printf(const char format[], ...) { va_list args; @@ -299,4 +299,4 @@ void furi_string_utf8_decode(char c, FuriStringUTF8State* state, FuriStringUnico m_str1ng_utf8_state_e m_state = furi_state_to_state(*state); m_str1ng_utf8_decode(c, &m_state, unicode); *state = state_to_furi_state(m_state); -} \ No newline at end of file +} diff --git a/furi/core/thread.c b/furi/core/thread.c index 34cc7d987a9..c966dd57257 100644 --- a/furi/core/thread.c +++ b/furi/core/thread.c @@ -49,9 +49,9 @@ static size_t __furi_thread_stdout_write(FuriThread* thread, const char* data, s static int32_t __furi_thread_stdout_flush(FuriThread* thread); /** Catch threads that are trying to exit wrong way */ -__attribute__((__noreturn__)) void furi_thread_catch() { +__attribute__((__noreturn__)) void furi_thread_catch() { //-V1082 asm volatile("nop"); // extra magic - furi_crash("You are doing it wrong"); + furi_crash("You are doing it wrong"); //-V779 __builtin_unreachable(); } @@ -84,10 +84,10 @@ static void furi_thread_body(void* context) { if(thread->heap_trace_enabled == true) { furi_delay_ms(33); thread->heap_size = memmgr_heap_get_thread_memory((FuriThreadId)task_handle); - furi_log_print_format( + furi_log_print_format( //-V576 thread->heap_size ? FuriLogLevelError : FuriLogLevelInfo, TAG, - "%s allocation balance: %d", + "%s allocation balance: %u", thread->name ? thread->name : "Thread", thread->heap_size); memmgr_heap_disable_thread_trace((FuriThreadId)task_handle); diff --git a/furi/core/timer.c b/furi/core/timer.c index c42b0c2acbb..be7efebe2dc 100644 --- a/furi/core/timer.c +++ b/furi/core/timer.c @@ -32,45 +32,29 @@ FuriTimer* furi_timer_alloc(FuriTimerCallback func, FuriTimerType type, void* co TimerHandle_t hTimer; TimerCallback_t* callb; UBaseType_t reload; - uint32_t callb_dyn; hTimer = NULL; - callb = NULL; - callb_dyn = 0U; /* Dynamic memory allocation is available: if memory for callback and */ /* its context is not provided, allocate it from dynamic memory pool */ - if(callb == NULL) { - callb = (TimerCallback_t*)malloc(sizeof(TimerCallback_t)); + callb = (TimerCallback_t*)malloc(sizeof(TimerCallback_t)); - if(callb != NULL) { - /* Callback memory was allocated from dynamic pool, set flag */ - callb_dyn = 1U; - } - } + callb->func = func; + callb->context = context; - if(callb != NULL) { - callb->func = func; - callb->context = context; - - if(type == FuriTimerTypeOnce) { - reload = pdFALSE; - } else { - reload = pdTRUE; - } - - /* Store callback memory dynamic allocation flag */ - callb = (TimerCallback_t*)((uint32_t)callb | callb_dyn); - // TimerCallback function is always provided as a callback and is used to call application - // specified function with its context both stored in structure callb. - hTimer = xTimerCreate(NULL, 1, reload, callb, TimerCallback); - if((hTimer == NULL) && (callb != NULL) && (callb_dyn == 1U)) { - /* Failed to create a timer, release allocated resources */ - callb = (TimerCallback_t*)((uint32_t)callb & ~1U); - free(callb); - } + if(type == FuriTimerTypeOnce) { + reload = pdFALSE; + } else { + reload = pdTRUE; } + /* Store callback memory dynamic allocation flag */ + callb = (TimerCallback_t*)((uint32_t)callb | 1U); + // TimerCallback function is always provided as a callback and is used to call application + // specified function with its context both stored in structure callb. + hTimer = xTimerCreate(NULL, 1, reload, callb, TimerCallback); + furi_check(hTimer); + /* Return timer ID */ return ((FuriTimer*)hTimer); } diff --git a/lib/app-scened-template/view_modules/popup_vm.cpp b/lib/app-scened-template/view_modules/popup_vm.cpp index c378d9cc94a..e2c8732e8dc 100644 --- a/lib/app-scened-template/view_modules/popup_vm.cpp +++ b/lib/app-scened-template/view_modules/popup_vm.cpp @@ -1,4 +1,5 @@ #include "popup_vm.h" +#include "gui/modules/popup.h" PopupVM::PopupVM() { popup = popup_alloc(); } @@ -50,5 +51,5 @@ void PopupVM::enable_timeout() { } void PopupVM::disable_timeout() { - popup_enable_timeout(popup); + popup_disable_timeout(popup); } diff --git a/lib/drivers/cc1101.c b/lib/drivers/cc1101.c index f28165e89c0..d563c30c327 100644 --- a/lib/drivers/cc1101.c +++ b/lib/drivers/cc1101.c @@ -121,7 +121,7 @@ uint32_t cc1101_set_intermediate_frequency(FuriHalSpiBusHandle* handle, uint32_t } void cc1101_set_pa_table(FuriHalSpiBusHandle* handle, const uint8_t value[8]) { - uint8_t tx[9] = {CC1101_PATABLE | CC1101_BURST}; + uint8_t tx[9] = {CC1101_PATABLE | CC1101_BURST}; //-V1009 CC1101Status rx[9] = {0}; memcpy(&tx[1], &value[0], 8); diff --git a/lib/drivers/lp5562.c b/lib/drivers/lp5562.c index f5d765fa937..e755d2d6038 100644 --- a/lib/drivers/lp5562.c +++ b/lib/drivers/lp5562.c @@ -105,7 +105,7 @@ void lp5562_set_channel_src(FuriHalI2cBusHandle* handle, LP5562Channel channel, reg_val &= ~(0x3 << bit_offset); reg_val |= ((src & 0x03) << bit_offset); furi_hal_i2c_write_reg_8(handle, LP5562_ADDRESS, 0x70, reg_val, LP5562_I2C_TIMEOUT); - } while(channel); + } while(channel != 0); } void lp5562_execute_program( @@ -166,7 +166,7 @@ void lp5562_stop_program(FuriHalI2cBusHandle* handle, LP5562Engine eng) { bit_offset = (3 - eng) * 2; furi_hal_i2c_read_reg_8(handle, LP5562_ADDRESS, 0x01, ®_val, LP5562_I2C_TIMEOUT); reg_val &= ~(0x3 << bit_offset); - reg_val |= (0x00 << bit_offset); // Disabled + // Not setting lowest 2 bits here furi_hal_i2c_write_reg_8(handle, LP5562_ADDRESS, 0x01, reg_val, LP5562_I2C_TIMEOUT); } diff --git a/lib/flipper_application/elf/elf_file.c b/lib/flipper_application/elf/elf_file.c index 2082e550f96..bf98650a254 100644 --- a/lib/flipper_application/elf/elf_file.c +++ b/lib/flipper_application/elf/elf_file.c @@ -6,8 +6,8 @@ #define TAG "elf" #define ELF_NAME_BUFFER_LEN 32 -#define SECTION_OFFSET(e, n) (e->section_table + n * sizeof(Elf32_Shdr)) -#define IS_FLAGS_SET(v, m) ((v & m) == m) +#define SECTION_OFFSET(e, n) ((e)->section_table + (n) * sizeof(Elf32_Shdr)) +#define IS_FLAGS_SET(v, m) (((v) & (m)) == (m)) #define RESOLVER_THREAD_YIELD_STEP 30 // #define ELF_DEBUG_LOG 1 @@ -758,15 +758,13 @@ ELFFileLoadStatus elf_file_load_sections(ELFFile* elf) { AddressCache_init(elf->relocation_cache); - if(status == ELFFileLoadStatusSuccess) { - for(ELFSectionDict_it(it, elf->sections); !ELFSectionDict_end_p(it); - ELFSectionDict_next(it)) { - ELFSectionDict_itref_t* itref = ELFSectionDict_ref(it); - FURI_LOG_D(TAG, "Relocating section '%s'", itref->key); - if(!elf_relocate_section(elf, &itref->value)) { - FURI_LOG_E(TAG, "Error relocating section '%s'", itref->key); - status = ELFFileLoadStatusMissingImports; - } + for(ELFSectionDict_it(it, elf->sections); !ELFSectionDict_end_p(it); + ELFSectionDict_next(it)) { + ELFSectionDict_itref_t* itref = ELFSectionDict_ref(it); + FURI_LOG_D(TAG, "Relocating section '%s'", itref->key); + if(!elf_relocate_section(elf, &itref->value)) { + FURI_LOG_E(TAG, "Error relocating section '%s'", itref->key); + status = ELFFileLoadStatusMissingImports; } } @@ -793,7 +791,7 @@ ELFFileLoadStatus elf_file_load_sections(ELFFile* elf) { ELFSectionDict_itref_t* itref = ELFSectionDict_ref(it); total_size += itref->value.size; } - FURI_LOG_I(TAG, "Total size of loaded sections: %u", total_size); + FURI_LOG_I(TAG, "Total size of loaded sections: %u", total_size); //-V576 } return status; diff --git a/lib/flipper_format/flipper_format_stream.c b/lib/flipper_format/flipper_format_stream.c index 9cce95d47e1..405b819a760 100644 --- a/lib/flipper_format/flipper_format_stream.c +++ b/lib/flipper_format/flipper_format_stream.c @@ -298,7 +298,7 @@ bool flipper_format_stream_write_value_line(Stream* stream, FlipperStreamWriteDa }; break; case FlipperStreamValueUint32: { const uint32_t* data = write_data->data; - furi_string_printf(value, "%" PRId32, data[i]); + furi_string_printf(value, "%" PRIu32, data[i]); }; break; case FlipperStreamValueHexUint64: { const uint64_t* data = write_data->data; @@ -396,7 +396,7 @@ bool flipper_format_stream_read_value_line( }; break; case FlipperStreamValueUint32: { uint32_t* data = _data; - scan_values = sscanf(furi_string_get_cstr(value), "%" PRId32, &data[i]); + scan_values = sscanf(furi_string_get_cstr(value), "%" PRIu32, &data[i]); }; break; case FlipperStreamValueHexUint64: { uint64_t* data = _data; diff --git a/lib/infrared/encoder_decoder/common/infrared_common_decoder.c b/lib/infrared/encoder_decoder/common/infrared_common_decoder.c index 7f1c3a4fda9..8acb6751ba0 100644 --- a/lib/infrared/encoder_decoder/common/infrared_common_decoder.c +++ b/lib/infrared/encoder_decoder/common/infrared_common_decoder.c @@ -107,7 +107,7 @@ static InfraredStatus infrared_common_decode_bits(InfraredCommonDecoder* decoder decoder->timings_cnt = consume_samples(decoder->timings, decoder->timings_cnt, 1); /* check if largest protocol version can be decoded */ - if(level && (decoder->protocol->databit_len[0] == decoder->databit_cnt) && + if(level && (decoder->protocol->databit_len[0] == decoder->databit_cnt) && //-V1051 !timings->min_split_time) { status = InfraredStatusReady; break; diff --git a/lib/infrared/encoder_decoder/common/infrared_common_encoder.c b/lib/infrared/encoder_decoder/common/infrared_common_encoder.c index dcf63acc858..9c774617e96 100644 --- a/lib/infrared/encoder_decoder/common/infrared_common_encoder.c +++ b/lib/infrared/encoder_decoder/common/infrared_common_encoder.c @@ -94,7 +94,6 @@ InfraredStatus case InfraredCommonEncoderStateSilence: *duration = encoder->protocol->timings.silence_time; *level = false; - status = InfraredStatusOk; encoder->state = InfraredCommonEncoderStatePreamble; ++encoder->timings_encoded; encoder->timings_sum = 0; diff --git a/lib/infrared/encoder_decoder/common/infrared_common_i.h b/lib/infrared/encoder_decoder/common/infrared_common_i.h index 20dbeb79dc5..f34e81edaef 100644 --- a/lib/infrared/encoder_decoder/common/infrared_common_i.h +++ b/lib/infrared/encoder_decoder/common/infrared_common_i.h @@ -4,7 +4,7 @@ #include "infrared.h" #include "infrared_i.h" -#define MATCH_TIMING(x, v, delta) (((x) < (v + delta)) && ((x) > (v - delta))) +#define MATCH_TIMING(x, v, delta) (((x) < ((v) + (delta))) && ((x) > ((v) - (delta)))) typedef struct InfraredCommonDecoder InfraredCommonDecoder; typedef struct InfraredCommonEncoder InfraredCommonEncoder; diff --git a/lib/infrared/worker/infrared_transmit.c b/lib/infrared/worker/infrared_transmit.c index 695be8d1f31..1a508301911 100644 --- a/lib/infrared/worker/infrared_transmit.c +++ b/lib/infrared/worker/infrared_transmit.c @@ -67,7 +67,7 @@ void infrared_send_raw(const uint32_t timings[], uint32_t timings_cnt, bool star FuriHalInfraredTxGetDataState infrared_get_data_callback(void* context, uint32_t* duration, bool* level) { - FuriHalInfraredTxGetDataState state = FuriHalInfraredTxGetDataStateLastDone; + FuriHalInfraredTxGetDataState state; InfraredEncoderHandler* handler = context; InfraredStatus status = InfraredStatusError; @@ -82,9 +82,10 @@ FuriHalInfraredTxGetDataState } else if(status == InfraredStatusOk) { state = FuriHalInfraredTxGetDataStateOk; } else if(status == InfraredStatusDone) { - state = FuriHalInfraredTxGetDataStateDone; if(--infrared_tx_number_of_transmissions == 0) { state = FuriHalInfraredTxGetDataStateLastDone; + } else { + state = FuriHalInfraredTxGetDataStateDone; } } else { furi_crash(NULL); diff --git a/lib/infrared/worker/infrared_worker.c b/lib/infrared/worker/infrared_worker.c index 57288f3c3a9..033dba52597 100644 --- a/lib/infrared/worker/infrared_worker.c +++ b/lib/infrared/worker/infrared_worker.c @@ -7,6 +7,7 @@ #include #include #include +#include #include @@ -397,8 +398,9 @@ static bool infrared_get_new_signal(InfraredWorker* instance) { } instance->tx.tx_raw_cnt = 0; - instance->tx.need_reinitialization = (new_tx_frequency != instance->tx.frequency) || - (new_tx_duty_cycle != instance->tx.duty_cycle); + instance->tx.need_reinitialization = + (new_tx_frequency != instance->tx.frequency) || + !float_is_equal(new_tx_duty_cycle, instance->tx.duty_cycle); instance->tx.frequency = new_tx_frequency; instance->tx.duty_cycle = new_tx_duty_cycle; if(instance->signal.decoded) { diff --git a/lib/infrared/worker/infrared_worker.h b/lib/infrared/worker/infrared_worker.h index 26919c4f59b..1a8cd9a76c4 100644 --- a/lib/infrared/worker/infrared_worker.h +++ b/lib/infrared/worker/infrared_worker.h @@ -7,7 +7,7 @@ extern "C" { #endif -#define MAX_TIMINGS_AMOUNT 1024 +#define MAX_TIMINGS_AMOUNT 1024U /** Interface struct of infrared worker */ typedef struct InfraredWorker InfraredWorker; diff --git a/lib/lfrfid/lfrfid_raw_worker.c b/lib/lfrfid/lfrfid_raw_worker.c index 8c69acedba8..22c0bbd02c1 100644 --- a/lib/lfrfid/lfrfid_raw_worker.c +++ b/lib/lfrfid/lfrfid_raw_worker.c @@ -114,7 +114,6 @@ void lfrfid_raw_worker_stop(LFRFIDRawWorker* worker) { worker->emulate_callback = NULL; worker->context = NULL; worker->read_callback = NULL; - worker->context = NULL; furi_event_flag_set(worker->events, 1 << LFRFIDRawWorkerEventStop); furi_thread_join(worker->thread); } @@ -335,7 +334,7 @@ static int32_t lfrfid_raw_emulate_worker_thread(void* thread_context) { } if(data->ctx.overrun_count) { - FURI_LOG_E(TAG_EMULATE, "overruns: %u", data->ctx.overrun_count); + FURI_LOG_E(TAG_EMULATE, "overruns: %zu", data->ctx.overrun_count); } furi_stream_buffer_free(data->ctx.stream); @@ -344,4 +343,4 @@ static int32_t lfrfid_raw_emulate_worker_thread(void* thread_context) { free(data); return 0; -} \ No newline at end of file +} diff --git a/lib/lfrfid/lfrfid_worker.c b/lib/lfrfid/lfrfid_worker.c index f33c1aed632..1e491c6b787 100644 --- a/lib/lfrfid/lfrfid_worker.c +++ b/lib/lfrfid/lfrfid_worker.c @@ -139,7 +139,7 @@ static int32_t lfrfid_worker_thread(void* thread_context) { while(true) { uint32_t flags = furi_thread_flags_wait(LFRFIDEventAll, FuriFlagWaitAny, FuriWaitForever); - if(flags != FuriFlagErrorTimeout) { + if(flags != (unsigned)FuriFlagErrorTimeout) { // stop thread if(flags & LFRFIDEventStopThread) break; @@ -161,4 +161,4 @@ static int32_t lfrfid_worker_thread(void* thread_context) { } return 0; -} \ No newline at end of file +} diff --git a/lib/lfrfid/lfrfid_worker_modes.c b/lib/lfrfid/lfrfid_worker_modes.c index 1fbae04c81f..9b6f16eb14c 100644 --- a/lib/lfrfid/lfrfid_worker_modes.c +++ b/lib/lfrfid/lfrfid_worker_modes.c @@ -276,7 +276,7 @@ static LFRFIDWorkerReadState lfrfid_worker_read_internal( FURI_LOG_D( TAG, - "%s, %d, [%s]", + "%s, %zu, [%s]", protocol_dict_get_name(worker->protocols, protocol), last_read_count, furi_string_get_cstr(string_info)); @@ -335,9 +335,9 @@ static LFRFIDWorkerReadState lfrfid_worker_read_internal( } static void lfrfid_worker_mode_read_process(LFRFIDWorker* worker) { - LFRFIDFeature feature = LFRFIDFeatureASK; ProtocolId read_result = PROTOCOL_NO; LFRFIDWorkerReadState state; + LFRFIDFeature feature; if(worker->read_type == LFRFIDWorkerReadTypePSKOnly) { feature = LFRFIDFeaturePSK; @@ -635,4 +635,4 @@ const LFRFIDWorkerModeType lfrfid_worker_modes[] = { [LFRFIDWorkerEmulate] = {.process = lfrfid_worker_mode_emulate_process}, [LFRFIDWorkerReadRaw] = {.process = lfrfid_worker_mode_read_raw_process}, [LFRFIDWorkerEmulateRaw] = {.process = lfrfid_worker_mode_emulate_raw_process}, -}; \ No newline at end of file +}; diff --git a/lib/lfrfid/protocols/protocol_indala26.c b/lib/lfrfid/protocols/protocol_indala26.c index cafc5848948..8319f0a93df 100644 --- a/lib/lfrfid/protocols/protocol_indala26.c +++ b/lib/lfrfid/protocols/protocol_indala26.c @@ -353,4 +353,4 @@ const ProtocolBase protocol_indala26 = { .render_data = (ProtocolRenderData)protocol_indala26_render_data, .render_brief_data = (ProtocolRenderData)protocol_indala26_render_brief_data, .write_data = (ProtocolWriteData)protocol_indala26_write_data, -}; \ No newline at end of file +}; diff --git a/lib/lfrfid/protocols/protocol_pac_stanley.c b/lib/lfrfid/protocols/protocol_pac_stanley.c index 59aaf1e6aab..11c642402a1 100644 --- a/lib/lfrfid/protocols/protocol_pac_stanley.c +++ b/lib/lfrfid/protocols/protocol_pac_stanley.c @@ -12,7 +12,7 @@ #define PAC_STANLEY_ENCODED_BYTE_FULL_SIZE \ (PAC_STANLEY_ENCODED_BYTE_SIZE + PAC_STANLEY_PREAMBLE_BYTE_SIZE) #define PAC_STANLEY_BYTE_LENGTH (10) // start bit, 7 data bits, parity bit, stop bit -#define PAC_STANLEY_DATA_START_INDEX 8 + (3 * PAC_STANLEY_BYTE_LENGTH) + 1 +#define PAC_STANLEY_DATA_START_INDEX (8 + (3 * PAC_STANLEY_BYTE_LENGTH) + 1) #define PAC_STANLEY_DECODED_DATA_SIZE (4) #define PAC_STANLEY_ENCODED_DATA_SIZE (sizeof(ProtocolPACStanley)) @@ -128,7 +128,7 @@ bool protocol_pac_stanley_decoder_feed(ProtocolPACStanley* protocol, bool level, } bool protocol_pac_stanley_encoder_start(ProtocolPACStanley* protocol) { - memset(protocol->encoded_data, 0, PAC_STANLEY_ENCODED_BYTE_SIZE); + memset(protocol->encoded_data, 0, sizeof(protocol->encoded_data)); uint8_t idbytes[10]; idbytes[0] = '2'; @@ -137,7 +137,7 @@ bool protocol_pac_stanley_encoder_start(ProtocolPACStanley* protocol) { uint8_to_hex_chars(protocol->data, &idbytes[2], 8); // insert start and stop bits - for(size_t i = 0; i < 16; i++) protocol->encoded_data[i] = 0x40 >> (i + 3) % 5 * 2; + for(size_t i = 0; i < 16; i++) protocol->encoded_data[i] = 0x40 >> ((i + 3) % 5 * 2); protocol->encoded_data[0] = 0xFF; // mark + stop protocol->encoded_data[1] = 0x20; // start + reflect8(STX) @@ -228,4 +228,4 @@ const ProtocolBase protocol_pac_stanley = { .render_data = (ProtocolRenderData)protocol_pac_stanley_render_data, .render_brief_data = (ProtocolRenderData)protocol_pac_stanley_render_data, .write_data = (ProtocolWriteData)protocol_pac_stanley_write_data, -}; \ No newline at end of file +}; diff --git a/lib/lfrfid/tools/bit_lib.c b/lib/lfrfid/tools/bit_lib.c index c84f4b7ed2d..54decb3e807 100644 --- a/lib/lfrfid/tools/bit_lib.c +++ b/lib/lfrfid/tools/bit_lib.c @@ -25,7 +25,7 @@ void bit_lib_set_bits(uint8_t* data, size_t position, uint8_t byte, uint8_t leng for(uint8_t i = 0; i < length; ++i) { uint8_t shift = (length - 1) - i; - bit_lib_set_bit(data, position + i, (byte >> shift) & 1); + bit_lib_set_bit(data, position + i, (byte >> shift) & 1); //-V610 } } @@ -69,9 +69,9 @@ uint32_t bit_lib_get_bits_32(const uint8_t* data, size_t position, uint8_t lengt value |= bit_lib_get_bits(data, position + 8, 8) << (length - 16); value |= bit_lib_get_bits(data, position + 16, length - 16); } else { - value = bit_lib_get_bits(data, position, 8) << (length - 8); - value |= bit_lib_get_bits(data, position + 8, 8) << (length - 16); - value |= bit_lib_get_bits(data, position + 16, 8) << (length - 24); + value = (uint32_t)bit_lib_get_bits(data, position, 8) << (length - 8); + value |= (uint32_t)bit_lib_get_bits(data, position + 8, 8) << (length - 16); + value |= (uint32_t)bit_lib_get_bits(data, position + 16, 8) << (length - 24); value |= bit_lib_get_bits(data, position + 24, length - 24); } @@ -364,4 +364,4 @@ uint16_t bit_lib_crc16( crc ^= xor_out; return crc; -} \ No newline at end of file +} diff --git a/lib/lfrfid/tools/bit_lib.h b/lib/lfrfid/tools/bit_lib.h index 1b048db3523..bae95462d5b 100644 --- a/lib/lfrfid/tools/bit_lib.h +++ b/lib/lfrfid/tools/bit_lib.h @@ -7,7 +7,7 @@ extern "C" { #endif -#define TOPBIT(X) (1 << (X - 1)) +#define TOPBIT(X) (1 << ((X)-1)) typedef enum { BitLibParityEven, @@ -26,13 +26,13 @@ typedef enum { * @param data value to test * @param index bit index to test */ -#define bit_lib_bit_is_set(data, index) ((data & (1 << (index))) != 0) +#define bit_lib_bit_is_set(data, index) (((data) & (1 << (index))) != 0) /** @brief Test if a bit is not set. * @param data value to test * @param index bit index to test */ -#define bit_lib_bit_is_not_set(data, index) ((data & (1 << (index))) == 0) +#define bit_lib_bit_is_not_set(data, index) (((data) & (1 << (index))) == 0) /** @brief Push a bit into a byte array. * @param data array to push bit into @@ -269,4 +269,4 @@ uint16_t bit_lib_crc16( #ifdef __cplusplus } -#endif \ No newline at end of file +#endif diff --git a/lib/lfrfid/tools/varint_pair.c b/lib/lfrfid/tools/varint_pair.c index c59ba55b40d..1e6c82eeea4 100644 --- a/lib/lfrfid/tools/varint_pair.c +++ b/lib/lfrfid/tools/varint_pair.c @@ -28,11 +28,9 @@ bool varint_pair_pack(VarintPair* pair, bool first, uint32_t value) { pair->data_length = 0; } } else { - if(pair->data_length > 0) { + if(pair->data_length != 0) { pair->data_length += varint_uint32_pack(value, pair->data + pair->data_length); result = true; - } else { - pair->data_length = 0; } } diff --git a/lib/nfc/helpers/mf_classic_dict.c b/lib/nfc/helpers/mf_classic_dict.c index 690bba61b31..98076479f70 100644 --- a/lib/nfc/helpers/mf_classic_dict.c +++ b/lib/nfc/helpers/mf_classic_dict.c @@ -90,7 +90,7 @@ MfClassicDict* mf_classic_dict_alloc(MfClassicDictType dict_type) { } FURI_LOG_T( TAG, - "Read line: %s, len: %d", + "Read line: %s, len: %zu", furi_string_get_cstr(next_line), furi_string_size(next_line)); if(furi_string_get_char(next_line, 0) == '#') continue; @@ -101,7 +101,7 @@ MfClassicDict* mf_classic_dict_alloc(MfClassicDictType dict_type) { stream_rewind(dict->stream); dict_loaded = true; - FURI_LOG_I(TAG, "Loaded dictionary with %ld keys", dict->total_keys); + FURI_LOG_I(TAG, "Loaded dictionary with %lu keys", dict->total_keys); } while(false); if(!dict_loaded) { @@ -136,7 +136,7 @@ static void mf_classic_dict_str_to_int(FuriString* key_str, uint64_t* key_int) { for(uint8_t i = 0; i < 12; i += 2) { args_char_to_hex( furi_string_get_char(key_str, i), furi_string_get_char(key_str, i + 1), &key_byte_tmp); - *key_int |= (uint64_t)key_byte_tmp << 8 * (5 - i / 2); + *key_int |= (uint64_t)key_byte_tmp << (8 * (5 - i / 2)); } } @@ -193,7 +193,7 @@ bool mf_classic_dict_is_key_present_str(MfClassicDict* dict, FuriString* key) { bool key_found = false; stream_rewind(dict->stream); - while(!key_found) { + while(!key_found) { //-V654 if(!stream_read_line(dict->stream, next_line)) break; if(furi_string_get_char(next_line, 0) == '#') continue; if(furi_string_size(next_line) != NFC_MF_CLASSIC_KEY_LEN) continue; @@ -294,7 +294,7 @@ bool mf_classic_dict_find_index_str(MfClassicDict* dict, FuriString* key, uint32 bool key_found = false; uint32_t index = 0; stream_rewind(dict->stream); - while(!key_found) { + while(!key_found) { //-V654 if(!stream_read_line(dict->stream, next_line)) break; if(furi_string_get_char(next_line, 0) == '#') continue; if(furi_string_size(next_line) != NFC_MF_CLASSIC_KEY_LEN) continue; diff --git a/lib/nfc/helpers/reader_analyzer.c b/lib/nfc/helpers/reader_analyzer.c index 73b4b125e6d..af4869ca9f6 100644 --- a/lib/nfc/helpers/reader_analyzer.c +++ b/lib/nfc/helpers/reader_analyzer.c @@ -221,11 +221,11 @@ static void reader_analyzer_write( data_sent = furi_stream_buffer_send( instance->stream, &header, sizeof(ReaderAnalyzerHeader), FuriWaitForever); if(data_sent != sizeof(ReaderAnalyzerHeader)) { - FURI_LOG_W(TAG, "Sent %d out of %d bytes", data_sent, sizeof(ReaderAnalyzerHeader)); + FURI_LOG_W(TAG, "Sent %zu out of %zu bytes", data_sent, sizeof(ReaderAnalyzerHeader)); } data_sent = furi_stream_buffer_send(instance->stream, data, len, FuriWaitForever); if(data_sent != len) { - FURI_LOG_W(TAG, "Sent %d out of %d bytes", data_sent, len); + FURI_LOG_W(TAG, "Sent %zu out of %u bytes", data_sent, len); } } diff --git a/lib/nfc/nfc_device.c b/lib/nfc/nfc_device.c index 49eebc37ddc..52bff24e3e0 100644 --- a/lib/nfc/nfc_device.c +++ b/lib/nfc/nfc_device.c @@ -576,7 +576,7 @@ static bool nfc_device_save_mifare_df_data(FlipperFormat* file, NfcDevice* dev) tmp = malloc(n_apps * 3); int i = 0; for(MifareDesfireApplication* app = data->app_head; app; app = app->next) { - memcpy(tmp + i, app->id, 3); + memcpy(tmp + i, app->id, 3); //-V769 i += 3; } if(!flipper_format_write_hex(file, "Application IDs", tmp, n_apps * 3)) break; @@ -1085,7 +1085,7 @@ bool nfc_device_save(NfcDevice* dev, const char* dev_name) { saved = true; } while(0); - if(!saved) { + if(!saved) { //-V547 dialog_message_show_storage_error(dev->dialogs, "Can not save\nkey file"); } furi_string_free(temp_str); diff --git a/lib/nfc/nfc_worker.c b/lib/nfc/nfc_worker.c index 450428a18a1..0ffe1d07b69 100644 --- a/lib/nfc/nfc_worker.c +++ b/lib/nfc/nfc_worker.c @@ -453,11 +453,11 @@ void nfc_worker_read_type(NfcWorker* nfc_worker) { event = NfcWorkerEventReadUidNfcA; break; } - } else { - if(!card_not_detected_notified) { - nfc_worker->callback(NfcWorkerEventNoCardDetected, nfc_worker->context); - card_not_detected_notified = true; - } + } + } else { + if(!card_not_detected_notified) { + nfc_worker->callback(NfcWorkerEventNoCardDetected, nfc_worker->context); + card_not_detected_notified = true; } } furi_hal_nfc_sleep(); @@ -509,7 +509,7 @@ void nfc_worker_emulate_apdu(NfcWorker* nfc_worker) { reader_analyzer_start(nfc_worker->reader_analyzer, ReaderAnalyzerModeDebugLog); } - while(nfc_worker->state == NfcWorkerStateEmulateApdu) { + while(nfc_worker->state == NfcWorkerStateEmulateApdu) { //-V1044 if(furi_hal_nfc_listen(params.uid, params.uid_len, params.atqa, params.sak, false, 300)) { FURI_LOG_D(TAG, "POS terminal detected"); if(emv_card_emulation(&tx_rx)) { @@ -657,7 +657,7 @@ void nfc_worker_mf_classic_dict_attack(NfcWorker* nfc_worker) { } FURI_LOG_D( - TAG, "Start Dictionary attack, Key Count %ld", mf_classic_dict_get_total_keys(dict)); + TAG, "Start Dictionary attack, Key Count %lu", mf_classic_dict_get_total_keys(dict)); for(size_t i = 0; i < total_sectors; i++) { FURI_LOG_I(TAG, "Sector %d", i); nfc_worker->callback(NfcWorkerEventNewSector, nfc_worker->context); @@ -742,7 +742,7 @@ void nfc_worker_emulate_mf_classic(NfcWorker* nfc_worker) { rfal_platform_spi_acquire(); furi_hal_nfc_listen_start(nfc_data); - while(nfc_worker->state == NfcWorkerStateMfClassicEmulate) { + while(nfc_worker->state == NfcWorkerStateMfClassicEmulate) { //-V1044 if(furi_hal_nfc_listen_rx(&tx_rx, 300)) { mf_classic_emulator(&emulator, &tx_rx); } @@ -776,7 +776,8 @@ void nfc_worker_write_mf_classic(NfcWorker* nfc_worker) { furi_hal_nfc_sleep(); FURI_LOG_I(TAG, "Check low level nfc data"); - if(memcmp(&nfc_data, &nfc_worker->dev_data->nfc_data, sizeof(FuriHalNfcDevData))) { + if(memcmp(&nfc_data, &nfc_worker->dev_data->nfc_data, sizeof(FuriHalNfcDevData)) != + 0) { FURI_LOG_E(TAG, "Wrong card"); nfc_worker->callback(NfcWorkerEventWrongCard, nfc_worker->context); break; @@ -848,7 +849,8 @@ void nfc_worker_update_mf_classic(NfcWorker* nfc_worker) { furi_hal_nfc_sleep(); FURI_LOG_I(TAG, "Check low level nfc data"); - if(memcmp(&nfc_data, &nfc_worker->dev_data->nfc_data, sizeof(FuriHalNfcDevData))) { + if(memcmp(&nfc_data, &nfc_worker->dev_data->nfc_data, sizeof(FuriHalNfcDevData)) != + 0) { FURI_LOG_E(TAG, "Low level nfc data mismatch"); nfc_worker->callback(NfcWorkerEventWrongCard, nfc_worker->context); break; diff --git a/lib/nfc/parsers/all_in_one.c b/lib/nfc/parsers/all_in_one.c index 05cfe576437..c02710a2958 100644 --- a/lib/nfc/parsers/all_in_one.c +++ b/lib/nfc/parsers/all_in_one.c @@ -73,24 +73,14 @@ bool all_in_one_parser_parse(NfcDeviceData* dev_data) { return false; } - // If the layout is a then the ride count is stored in the first byte of page 8 uint8_t ride_count = 0; uint32_t serial = 0; if(all_in_one_get_layout(dev_data) == ALL_IN_ONE_LAYOUT_A) { + // If the layout is A then the ride count is stored in the first byte of page 8 ride_count = dev_data->mf_ul_data.data[4 * 8]; } else if(all_in_one_get_layout(dev_data) == ALL_IN_ONE_LAYOUT_D) { // If the layout is D, the ride count is stored in the second byte of page 9 ride_count = dev_data->mf_ul_data.data[4 * 9 + 1]; - // I hate this with a burning passion. - - // The number starts at the second half of the third byte on page 4, and is 32 bits long - // So we get the second half of the third byte, then bytes 4-6, and then the first half of the 7th byte - // B8 17 A2 A4 BD becomes 81 7A 2A 4B - serial = (dev_data->mf_ul_data.data[4 * 4 + 2] & 0x0F) << 28 | - dev_data->mf_ul_data.data[4 * 4 + 3] << 20 | - dev_data->mf_ul_data.data[4 * 4 + 4] << 12 | - dev_data->mf_ul_data.data[4 * 4 + 5] << 4 | - (dev_data->mf_ul_data.data[4 * 4 + 6] >> 4); } else { FURI_LOG_I("all_in_one", "Unknown layout: %d", all_in_one_get_layout(dev_data)); ride_count = 137; @@ -110,4 +100,4 @@ bool all_in_one_parser_parse(NfcDeviceData* dev_data) { furi_string_printf( dev_data->parsed_data, "\e#All-In-One\nNumber: %lu\nRides left: %u", serial, ride_count); return true; -} \ No newline at end of file +} diff --git a/lib/nfc/parsers/plantain_4k_parser.c b/lib/nfc/parsers/plantain_4k_parser.c index 348b5a64ca0..9a51cdeaf23 100644 --- a/lib/nfc/parsers/plantain_4k_parser.c +++ b/lib/nfc/parsers/plantain_4k_parser.c @@ -116,26 +116,9 @@ bool plantain_4k_parser_parse(NfcDeviceData* dev_data) { for(size_t i = 0; i < 7; i++) { card_number = (card_number << 8) | card_number_arr[i]; } - // Convert card number to string - FuriString* card_number_str; - card_number_str = furi_string_alloc(); - // Should look like "361301047292848684" - furi_string_printf(card_number_str, "%llu", card_number); - // Add suffix with luhn checksum (1 digit) to the card number string - FuriString* card_number_suffix; - card_number_suffix = furi_string_alloc(); - - furi_string_cat_printf(card_number_suffix, "-"); - furi_string_cat_printf(card_number_str, furi_string_get_cstr(card_number_suffix)); - // Free all not needed strings - furi_string_free(card_number_suffix); furi_string_printf( - dev_data->parsed_data, - "\e#Plantain\nN:%s\nBalance:%ld\n", - furi_string_get_cstr(card_number_str), - balance); - furi_string_free(card_number_str); + dev_data->parsed_data, "\e#Plantain\nN:%llu-\nBalance:%ld\n", card_number, balance); return true; } diff --git a/lib/nfc/parsers/plantain_parser.c b/lib/nfc/parsers/plantain_parser.c index 5328b5c4f31..799262171e2 100644 --- a/lib/nfc/parsers/plantain_parser.c +++ b/lib/nfc/parsers/plantain_parser.c @@ -89,26 +89,9 @@ bool plantain_parser_parse(NfcDeviceData* dev_data) { for(size_t i = 0; i < 7; i++) { card_number = (card_number << 8) | card_number_arr[i]; } - // Convert card number to string - FuriString* card_number_str; - card_number_str = furi_string_alloc(); - // Should look like "361301047292848684" - furi_string_printf(card_number_str, "%llu", card_number); - // Add suffix with luhn checksum (1 digit) to the card number string - FuriString* card_number_suffix; - card_number_suffix = furi_string_alloc(); - - furi_string_cat_printf(card_number_suffix, "-"); - furi_string_cat_printf(card_number_str, furi_string_get_cstr(card_number_suffix)); - // Free all not needed strings - furi_string_free(card_number_suffix); furi_string_printf( - dev_data->parsed_data, - "\e#Plantain\nN:%s\nBalance:%ld\n", - furi_string_get_cstr(card_number_str), - balance); - furi_string_free(card_number_str); + dev_data->parsed_data, "\e#Plantain\nN:%llu-\nBalance:%ld\n", card_number, balance); return true; } diff --git a/lib/nfc/parsers/two_cities.c b/lib/nfc/parsers/two_cities.c index 2c6184a71ff..2f4b7dd0d31 100644 --- a/lib/nfc/parsers/two_cities.c +++ b/lib/nfc/parsers/two_cities.c @@ -117,19 +117,6 @@ bool two_cities_parser_parse(NfcDeviceData* dev_data) { for(size_t i = 0; i < 7; i++) { card_number = (card_number << 8) | card_number_arr[i]; } - // Convert card number to string - FuriString* card_number_str; - card_number_str = furi_string_alloc(); - // Should look like "361301047292848684" - furi_string_printf(card_number_str, "%llu", card_number); - // Add suffix with luhn checksum (1 digit) to the card number string - FuriString* card_number_suffix; - card_number_suffix = furi_string_alloc(); - - furi_string_cat_printf(card_number_suffix, "-"); - furi_string_cat_printf(card_number_str, furi_string_get_cstr(card_number_suffix)); - // Free all not needed strings - furi_string_free(card_number_suffix); // ===== // --PLANTAIN-- @@ -149,12 +136,11 @@ bool two_cities_parser_parse(NfcDeviceData* dev_data) { furi_string_printf( dev_data->parsed_data, - "\e#Troika+Plantain\nPN: %s\nPB: %ld rur.\nTN: %ld\nTB: %d rur.\n", - furi_string_get_cstr(card_number_str), + "\e#Troika+Plantain\nPN: %llu-\nPB: %ld rur.\nTN: %ld\nTB: %d rur.\n", + card_number, balance, troika_number, troika_balance); - furi_string_free(card_number_str); return true; } diff --git a/lib/nfc/protocols/crypto1.c b/lib/nfc/protocols/crypto1.c index 2ac0ff0812f..f59651cf45f 100644 --- a/lib/nfc/protocols/crypto1.c +++ b/lib/nfc/protocols/crypto1.c @@ -4,7 +4,8 @@ // Algorithm from https://github.com/RfidResearchGroup/proxmark3.git -#define SWAPENDIAN(x) (x = (x >> 8 & 0xff00ff) | (x & 0xff00ff) << 8, x = x >> 16 | x << 16) +#define SWAPENDIAN(x) \ + ((x) = ((x) >> 8 & 0xff00ff) | ((x)&0xff00ff) << 8, (x) = (x) >> 16 | (x) << 16) #define LF_POLY_ODD (0x29CE5C) #define LF_POLY_EVEN (0x870804) diff --git a/lib/nfc/protocols/mifare_classic.c b/lib/nfc/protocols/mifare_classic.c index f4c7353a86e..e26dfd7f7f9 100644 --- a/lib/nfc/protocols/mifare_classic.c +++ b/lib/nfc/protocols/mifare_classic.c @@ -245,7 +245,8 @@ bool mf_classic_is_allowed_access_sector_trailer( case MfClassicActionKeyARead: { return false; } - case MfClassicActionKeyAWrite: { + case MfClassicActionKeyAWrite: + case MfClassicActionKeyBWrite: { return ( (key == MfClassicKeyA && (AC == 0x00 || AC == 0x01)) || (key == MfClassicKeyB && (AC == 0x04 || AC == 0x03))); @@ -253,11 +254,6 @@ bool mf_classic_is_allowed_access_sector_trailer( 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) || @@ -734,7 +730,7 @@ bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_ MfClassicKey access_key = MfClassicKeyA; // Read command - while(!command_processed) { + while(!command_processed) { //-V654 if(!is_encrypted) { crypto1_reset(&emulator->crypto); memcpy(plain_data, tx_rx->rx_data, tx_rx->rx_bits / 8); @@ -850,7 +846,7 @@ bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_ if(mf_classic_is_sector_trailer(block)) { if(!mf_classic_is_allowed_access( emulator, block, access_key, MfClassicActionKeyARead)) { - memset(block_data, 0, 6); + memset(block_data, 0, 6); //-V1086 } if(!mf_classic_is_allowed_access( emulator, block, access_key, MfClassicActionKeyBRead)) { @@ -860,22 +856,16 @@ bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_ emulator, block, access_key, MfClassicActionACRead)) { memset(&block_data[6], 0, 4); } - } else { - if(!mf_classic_is_allowed_access( - emulator, block, access_key, MfClassicActionDataRead)) { - // Send NACK - uint8_t nack = 0x04; - if(is_encrypted) { - 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); - break; - } + } else if(!mf_classic_is_allowed_access( + emulator, block, access_key, MfClassicActionDataRead)) { + // Send NACK + uint8_t nack = 0x04; + crypto1_encrypt( + &emulator->crypto, NULL, &nack, 4, tx_rx->tx_data, tx_rx->tx_parity); + tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; + tx_rx->tx_bits = 4; + furi_hal_nfc_tx_rx(tx_rx, 300); + break; } nfca_append_crc16(block_data, 16); @@ -908,7 +898,7 @@ bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_ if(mf_classic_is_sector_trailer(block)) { if(mf_classic_is_allowed_access( emulator, block, access_key, MfClassicActionKeyAWrite)) { - memcpy(block_data, plain_data, 6); + memcpy(block_data, plain_data, 6); //-V1086 } if(mf_classic_is_allowed_access( emulator, block, access_key, MfClassicActionKeyBWrite)) { @@ -924,7 +914,7 @@ bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_ memcpy(block_data, plain_data, MF_CLASSIC_BLOCK_SIZE); } } - if(memcmp(block_data, emulator->data.block[block].value, MF_CLASSIC_BLOCK_SIZE)) { + if(memcmp(block_data, emulator->data.block[block].value, MF_CLASSIC_BLOCK_SIZE) != 0) { memcpy(emulator->data.block[block].value, block_data, MF_CLASSIC_BLOCK_SIZE); emulator->data_changed = true; } @@ -1060,7 +1050,8 @@ bool mf_classic_write_sector( bool write_success = true; for(size_t i = first_block; i < first_block + total_blocks; i++) { // Compare blocks - if(memcmp(dest_data->block[i].value, src_data->block[i].value, MF_CLASSIC_BLOCK_SIZE)) { + if(memcmp(dest_data->block[i].value, src_data->block[i].value, MF_CLASSIC_BLOCK_SIZE) != + 0) { bool key_a_write_allowed = mf_classic_is_allowed_access_data_block( dest_data, i, MfClassicKeyA, MfClassicActionDataWrite); bool key_b_write_allowed = mf_classic_is_allowed_access_data_block( diff --git a/lib/nfc/protocols/mifare_desfire.c b/lib/nfc/protocols/mifare_desfire.c index b2247bf2090..23308ae95e3 100644 --- a/lib/nfc/protocols/mifare_desfire.c +++ b/lib/nfc/protocols/mifare_desfire.c @@ -108,7 +108,7 @@ void mf_df_cat_version(MifareDesfireVersion* version, FuriString* out) { } void mf_df_cat_free_mem(MifareDesfireFreeMemory* free_mem, FuriString* out) { - furi_string_cat_printf(out, "freeMem %ld\n", free_mem->bytes); + furi_string_cat_printf(out, "freeMem %lu\n", free_mem->bytes); } void mf_df_cat_key_settings(MifareDesfireKeySettings* ks, FuriString* out) { @@ -191,10 +191,10 @@ void mf_df_cat_file(MifareDesfireFile* file, FuriString* out) { case MifareDesfireFileTypeValue: size = 4; furi_string_cat_printf( - out, "lo %ld hi %ld\n", file->settings.value.lo_limit, file->settings.value.hi_limit); + out, "lo %lu hi %lu\n", file->settings.value.lo_limit, file->settings.value.hi_limit); furi_string_cat_printf( out, - "limit %ld enabled %d\n", + "limit %lu enabled %d\n", file->settings.value.limited_credit_value, file->settings.value.limited_credit_enabled); break; @@ -203,7 +203,7 @@ void mf_df_cat_file(MifareDesfireFile* file, FuriString* out) { size = file->settings.record.size; num = file->settings.record.cur; furi_string_cat_printf(out, "size %d\n", size); - furi_string_cat_printf(out, "num %d max %ld\n", num, file->settings.record.max); + furi_string_cat_printf(out, "num %d max %lu\n", num, file->settings.record.max); break; } uint8_t* data = file->contents; @@ -220,8 +220,9 @@ void mf_df_cat_file(MifareDesfireFile* file, FuriString* out) { } } for(int i = 0; i < 4 && ch + i < size; i++) { - if(isprint(data[rec * size + ch + i])) { - furi_string_cat_printf(out, "%c", data[rec * size + ch + i]); + const size_t data_index = rec * size + ch + i; + if(isprint(data[data_index])) { + furi_string_cat_printf(out, "%c", data[data_index]); } else { furi_string_cat_printf(out, "."); } @@ -547,7 +548,8 @@ bool mf_df_read_card(FuriHalNfcTxRxContext* tx_rx, MifareDesfireData* data) { for(MifareDesfireApplication* app = data->app_head; app; app = app->next) { tx_rx->tx_bits = 8 * mf_df_prepare_select_application(tx_rx->tx_data, app->id); if(!furi_hal_nfc_tx_rx_full(tx_rx) || - !mf_df_parse_select_application_response(tx_rx->rx_data, tx_rx->rx_bits / 8)) { + !mf_df_parse_select_application_response( + tx_rx->rx_data, tx_rx->rx_bits / 8)) { //-V1051 FURI_LOG_W(TAG, "Bad exchange selecting application"); continue; } diff --git a/lib/nfc/protocols/mifare_ultralight.c b/lib/nfc/protocols/mifare_ultralight.c index 85e234bd92b..d642e290a02 100644 --- a/lib/nfc/protocols/mifare_ultralight.c +++ b/lib/nfc/protocols/mifare_ultralight.c @@ -170,6 +170,7 @@ bool mf_ultralight_read_version( } bool mf_ultralight_authenticate(FuriHalNfcTxRxContext* tx_rx, uint32_t key, uint16_t* pack) { + furi_assert(pack); bool authenticated = false; do { @@ -189,9 +190,7 @@ bool mf_ultralight_authenticate(FuriHalNfcTxRxContext* tx_rx, uint32_t key, uint break; } - if(pack != NULL) { - *pack = (tx_rx->rx_data[1] << 8) | tx_rx->rx_data[0]; - } + *pack = (tx_rx->rx_data[1] << 8) | tx_rx->rx_data[0]; FURI_LOG_I(TAG, "Auth success. Password: %08lX. PACK: %04X", key, *pack); authenticated = true; @@ -492,7 +491,7 @@ MfUltralightConfigPages* mf_ultralight_get_config_pages(MfUltralightData* data) } else if( data->type >= MfUltralightTypeNTAGI2CPlus1K && data->type <= MfUltralightTypeNTAGI2CPlus2K) { - return (MfUltralightConfigPages*)&data->data[0xe3 * 4]; + return (MfUltralightConfigPages*)&data->data[0xe3 * 4]; //-V641 } else { return NULL; } @@ -561,7 +560,7 @@ bool mf_ultralight_read_pages_direct( FURI_LOG_D(TAG, "Failed to read pages %d - %d", start_index, start_index + 3); return false; } - memcpy(data, tx_rx->rx_data, 16); + memcpy(data, tx_rx->rx_data, 16); //-V1086 return true; } @@ -584,7 +583,8 @@ bool mf_ultralight_read_pages( curr_sector_index = tag_sector; } - FURI_LOG_D(TAG, "Reading pages %d - %d", i, i + (valid_pages > 4 ? 4 : valid_pages) - 1); + FURI_LOG_D( + TAG, "Reading pages %zu - %zu", i, i + (valid_pages > 4 ? 4 : valid_pages) - 1U); tx_rx->tx_data[0] = MF_UL_READ_CMD; tx_rx->tx_data[1] = tag_page; tx_rx->tx_bits = 16; @@ -593,9 +593,9 @@ bool mf_ultralight_read_pages( if(!furi_hal_nfc_tx_rx(tx_rx, 50) || tx_rx->rx_bits < 16 * 8) { FURI_LOG_D( TAG, - "Failed to read pages %d - %d", + "Failed to read pages %zu - %zu", i, - i + (valid_pages > 4 ? 4 : valid_pages) - 1); + i + (valid_pages > 4 ? 4 : valid_pages) - 1U); break; } @@ -857,7 +857,7 @@ static void mf_ul_ntag_i2c_fill_cross_area_read( } if(apply) { - while(tx_page_offset < 0 && page_length > 0) { + while(tx_page_offset < 0 && page_length > 0) { //-V614 ++tx_page_offset; ++data_page_offset; --page_length; @@ -987,9 +987,9 @@ static bool mf_ul_check_lock(MfUltralightEmulator* emulator, int16_t write_page) switch(emulator->data.type) { // low byte LSB range, MSB range case MfUltralightTypeNTAG203: - if(write_page >= 16 && write_page <= 27) + if(write_page >= 16 && write_page <= 27) //-V560 shift = (write_page - 16) / 4 + 1; - else if(write_page >= 28 && write_page <= 39) + else if(write_page >= 28 && write_page <= 39) //-V560 shift = (write_page - 28) / 4 + 5; else if(write_page == 41) shift = 12; @@ -1216,7 +1216,7 @@ static void mf_ul_emulate_write( page_buff[0] = new_locks & 0xff; page_buff[1] = new_locks >> 8; page_buff[2] = new_block_locks; - if(emulator->data.type >= MfUltralightTypeUL21 && + if(emulator->data.type >= MfUltralightTypeUL21 && //-V1016 emulator->data.type <= MfUltralightTypeNTAG216) page_buff[3] = MF_UL_TEARING_FLAG_DEFAULT; else diff --git a/lib/one_wire/ibutton/ibutton_key.c b/lib/one_wire/ibutton/ibutton_key.c index 2c0f7fa2698..7b7571a29f2 100644 --- a/lib/one_wire/ibutton/ibutton_key.c +++ b/lib/one_wire/ibutton/ibutton_key.c @@ -62,8 +62,6 @@ const char* ibutton_key_get_string_by_type(iButtonKeyType key_type) { break; default: furi_crash("Invalid iButton type"); - return ""; - break; } } diff --git a/lib/one_wire/ibutton/ibutton_worker_modes.c b/lib/one_wire/ibutton/ibutton_worker_modes.c index b1e5904ca35..b284940e7f9 100644 --- a/lib/one_wire/ibutton/ibutton_worker_modes.c +++ b/lib/one_wire/ibutton/ibutton_worker_modes.c @@ -130,7 +130,6 @@ bool ibutton_worker_read_comparator(iButtonWorker* worker) { ibutton_key_set_data(worker->key_p, worker->key_data, ibutton_key_get_max_size()); result = true; break; - break; default: break; } diff --git a/lib/one_wire/ibutton/ibutton_writer.c b/lib/one_wire/ibutton/ibutton_writer.c index 203c4fc077a..84d122491a4 100644 --- a/lib/one_wire/ibutton/ibutton_writer.c +++ b/lib/one_wire/ibutton/ibutton_writer.c @@ -72,7 +72,7 @@ static bool writer_write_TM2004(iButtonWriter* writer, iButtonKey* key) { writer_write_one_bit(writer, 1, 50000); // read written key byte - answer = onewire_host_read(writer->host); + answer = onewire_host_read(writer->host); //-V519 // check that written and read are same if(ibutton_key_get_data_p(key)[i] != answer) { diff --git a/lib/one_wire/ibutton/protocols/protocol_cyfral.c b/lib/one_wire/ibutton/protocols/protocol_cyfral.c index 51c42824fca..0c44c2b455f 100644 --- a/lib/one_wire/ibutton/protocols/protocol_cyfral.c +++ b/lib/one_wire/ibutton/protocols/protocol_cyfral.c @@ -270,10 +270,10 @@ static LevelDuration protocol_cyfral_encoder_yield(ProtocolCyfral* proto) { // start word (0b0001) switch(proto->encoder.index) { case 0: - result = level_duration_make(false, CYFRAL_0_LOW); + result = level_duration_make(false, CYFRAL_0_LOW); //-V1037 break; case 1: - result = level_duration_make(true, CYFRAL_0_HI); + result = level_duration_make(true, CYFRAL_0_HI); //-V1037 break; case 2: result = level_duration_make(false, CYFRAL_0_LOW); @@ -341,4 +341,4 @@ const ProtocolBase protocol_cyfral = { .start = (ProtocolEncoderStart)protocol_cyfral_encoder_start, .yield = (ProtocolEncoderYield)protocol_cyfral_encoder_yield, }, -}; \ No newline at end of file +}; diff --git a/lib/one_wire/ibutton/protocols/protocol_metakom.c b/lib/one_wire/ibutton/protocols/protocol_metakom.c index 00f16e45570..ff65c6678f3 100644 --- a/lib/one_wire/ibutton/protocols/protocol_metakom.c +++ b/lib/one_wire/ibutton/protocols/protocol_metakom.c @@ -248,14 +248,14 @@ static LevelDuration protocol_metakom_encoder_yield(ProtocolMetakom* proto) { if(proto->encoder.index == 0) { // sync bit result = level_duration_make(false, METAKOM_PERIOD); - } else if(proto->encoder.index >= 1 && proto->encoder.index <= 6) { + } else if(proto->encoder.index <= 6) { // start word (0b010) switch(proto->encoder.index) { case 1: - result = level_duration_make(true, METAKOM_0_LOW); + result = level_duration_make(true, METAKOM_0_LOW); //-V1037 break; case 2: - result = level_duration_make(false, METAKOM_0_HI); + result = level_duration_make(false, METAKOM_0_HI); //-V1037 break; case 3: result = level_duration_make(true, METAKOM_1_LOW); @@ -317,4 +317,4 @@ const ProtocolBase protocol_metakom = { .start = (ProtocolEncoderStart)protocol_metakom_encoder_start, .yield = (ProtocolEncoderYield)protocol_metakom_encoder_yield, }, -}; \ No newline at end of file +}; diff --git a/lib/one_wire/one_wire_slave.c b/lib/one_wire/one_wire_slave.c index af04cfdabbc..ad9c34b1982 100644 --- a/lib/one_wire/one_wire_slave.c +++ b/lib/one_wire/one_wire_slave.c @@ -41,7 +41,7 @@ uint32_t onewire_slave_wait_while_gpio_is(OneWireSlave* bus, uint32_t time, cons uint32_t time_ticks = time * furi_hal_cortex_instructions_per_microsecond(); uint32_t time_captured; - do { + do { //-V1044 time_captured = DWT->CYCCNT; if(furi_hal_ibutton_pin_get_level() != pin_value) { uint32_t remaining_time = time_ticks - (time_captured - start); @@ -155,8 +155,10 @@ bool onewire_slave_receive_and_process_cmd(OneWireSlave* bus) { uint8_t cmd; onewire_slave_receive(bus, &cmd, 1); - if(bus->error == RESET_IN_PROGRESS) return true; - if(bus->error != NO_ERROR) return false; + if(bus->error == RESET_IN_PROGRESS) + return true; + else if(bus->error != NO_ERROR) + return false; switch(cmd) { case 0xF0: @@ -172,10 +174,8 @@ bool onewire_slave_receive_and_process_cmd(OneWireSlave* bus) { default: // Unknown command bus->error = INCORRECT_ONEWIRE_CMD; + return false; } - - if(bus->error == RESET_IN_PROGRESS) return true; - return (bus->error == NO_ERROR); } bool onewire_slave_bus_start(OneWireSlave* bus) { diff --git a/lib/print/printf_tiny.c b/lib/print/printf_tiny.c index 6e47f65289a..54f192a609b 100644 --- a/lib/print/printf_tiny.c +++ b/lib/print/printf_tiny.c @@ -541,7 +541,7 @@ static size_t _etoa( exp2 = (int)(expval * 3.321928094887362 + 0.5); const double z = expval * 2.302585092994046 - exp2 * 0.6931471805599453; const double z2 = z * z; - conv.U = ((uint64_t)exp2 + 1023) << 52U; + conv.U = ((uint64_t)exp2 + 1023) << 52U; //-V519 // compute exp(z) using continued fractions, see https://en.wikipedia.org/wiki/Exponential_function#Continued_fractions_for_ex conv.F *= 1 + 2 * z / (2 - z + (z2 / (6 + (z2 / (10 + z2 / 14))))); // correct for rounding errors diff --git a/lib/subghz/blocks/generic.c b/lib/subghz/blocks/generic.c index 1bad5f0a36c..94114676d0c 100644 --- a/lib/subghz/blocks/generic.c +++ b/lib/subghz/blocks/generic.c @@ -71,7 +71,7 @@ bool subghz_block_generic_serialize( 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->data >> i * 8) & 0xFF; + key_data[sizeof(uint64_t) - i - 1] = (instance->data >> (i * 8)) & 0xFF; } if(!flipper_format_write_hex(flipper_format, "Key", key_data, sizeof(uint64_t))) { diff --git a/lib/subghz/blocks/math.h b/lib/subghz/blocks/math.h index a4f04271a62..87c209f71d2 100644 --- a/lib/subghz/blocks/math.h +++ b/lib/subghz/blocks/math.h @@ -8,8 +8,7 @@ #define bit_set(value, bit) ((value) |= (1UL << (bit))) #define bit_clear(value, bit) ((value) &= ~(1UL << (bit))) #define bit_write(value, bit, bitvalue) (bitvalue ? bit_set(value, bit) : bit_clear(value, bit)) -#define DURATION_DIFF(x, y) ((x < y) ? (y - x) : (x - y)) -#define abs(x) ((x) > 0 ? (x) : -(x)) +#define DURATION_DIFF(x, y) (((x) < (y)) ? ((y) - (x)) : ((x) - (y))) #ifdef __cplusplus extern "C" { diff --git a/lib/subghz/protocols/bett.c b/lib/subghz/protocols/bett.c index 2dd39af9e16..644d80fd834 100644 --- a/lib/subghz/protocols/bett.c +++ b/lib/subghz/protocols/bett.c @@ -242,7 +242,6 @@ void subghz_protocol_decoder_bett_feed(void* context, bool level, uint32_t durat if(!level) { if(DURATION_DIFF(duration, subghz_protocol_bett_const.te_short * 44) < (subghz_protocol_bett_const.te_delta * 15)) { - instance->decoder.parser_step = BETTDecoderStepSaveDuration; if(instance->decoder.decode_count_bit == subghz_protocol_bett_const.min_count_bit_for_found) { instance->generic.data = instance->decoder.decode_data; diff --git a/lib/subghz/protocols/holtek.c b/lib/subghz/protocols/holtek.c index 39e27bbf8f2..8aaad3b7177 100644 --- a/lib/subghz/protocols/holtek.c +++ b/lib/subghz/protocols/holtek.c @@ -240,7 +240,6 @@ void subghz_protocol_decoder_holtek_feed(void* context, bool level, uint32_t dur if(!level) { if(duration >= ((uint32_t)subghz_protocol_holtek_const.te_short * 10 + subghz_protocol_holtek_const.te_delta)) { - instance->decoder.parser_step = HoltekDecoderStepSaveDuration; if(instance->decoder.decode_count_bit == subghz_protocol_holtek_const.min_count_bit_for_found) { if((instance->decoder.decode_data & HOLTEK_HEADER_MASK) == HOLTEK_HEADER) { diff --git a/lib/subghz/protocols/keeloq.c b/lib/subghz/protocols/keeloq.c index eef1d09371c..6a9c3468ed1 100644 --- a/lib/subghz/protocols/keeloq.c +++ b/lib/subghz/protocols/keeloq.c @@ -119,8 +119,8 @@ void subghz_protocol_encoder_keeloq_free(void* context) { */ static bool subghz_protocol_keeloq_gen_data(SubGhzProtocolEncoderKeeloq* instance, uint8_t btn) { instance->generic.cnt++; - uint32_t fix = btn << 28 | instance->generic.serial; - uint32_t decrypt = btn << 28 | + uint32_t fix = (uint32_t)btn << 28 | instance->generic.serial; + uint32_t decrypt = (uint32_t)btn << 28 | (instance->generic.serial & 0x3FF) << 16 | //ToDo in some protocols the discriminator is 0 instance->generic.cnt; @@ -271,7 +271,8 @@ bool subghz_protocol_encoder_keeloq_deserialize(void* context, FlipperFormat* fl subghz_protocol_keeloq_check_remote_controller( &instance->generic, instance->keystore, &instance->manufacture_name); - if(strcmp(instance->manufacture_name, "DoorHan")) { + if(strcmp(instance->manufacture_name, "DoorHan") != 0) { + FURI_LOG_E(TAG, "Wrong manufacturer name"); break; } @@ -287,7 +288,7 @@ bool subghz_protocol_encoder_keeloq_deserialize(void* context, FlipperFormat* fl } 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; + 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"); diff --git a/lib/subghz/protocols/kia.c b/lib/subghz/protocols/kia.c index 997f8e1de58..a5d9e37ef09 100644 --- a/lib/subghz/protocols/kia.c +++ b/lib/subghz/protocols/kia.c @@ -142,7 +142,7 @@ void subghz_protocol_decoder_kia_feed(void* context, bool level, uint32_t durati case KIADecoderStepSaveDuration: if(level) { if(duration >= - (uint32_t)(subghz_protocol_kia_const.te_long + subghz_protocol_kia_const.te_delta * 2)) { + (subghz_protocol_kia_const.te_long + subghz_protocol_kia_const.te_delta * 2UL)) { //Found stop bit instance->decoder.parser_step = KIADecoderStepReset; if(instance->decoder.decode_count_bit == diff --git a/lib/subghz/protocols/megacode.c b/lib/subghz/protocols/megacode.c index 1b871a0c6c5..05b5b6894ad 100644 --- a/lib/subghz/protocols/megacode.c +++ b/lib/subghz/protocols/megacode.c @@ -417,7 +417,7 @@ void subghz_protocol_decoder_megacode_get_string(void* context, FuriString* outp output, "%s %dbit\r\n" "Key:0x%06lX\r\n" - "Sn:0x%04lX - %ld\r\n" + "Sn:0x%04lX - %lu\r\n" "Facility:%lX Btn:%X\r\n", instance->generic.protocol_name, instance->generic.data_count_bit, diff --git a/lib/subghz/protocols/nero_radio.c b/lib/subghz/protocols/nero_radio.c index 5fffaa19d7b..c8126b1e152 100644 --- a/lib/subghz/protocols/nero_radio.c +++ b/lib/subghz/protocols/nero_radio.c @@ -308,7 +308,7 @@ void subghz_protocol_decoder_nero_radio_feed(void* context, bool level, uint32_t } instance->decoder.decode_data = 0; instance->decoder.decode_count_bit = 0; - instance->decoder.parser_step = NeroRadioDecoderStepReset; + instance->decoder.parser_step = NeroRadioDecoderStepReset; //-V1048 break; } else if( (DURATION_DIFF( diff --git a/lib/subghz/protocols/princeton.c b/lib/subghz/protocols/princeton.c index ab1c58765df..7fc8f6524dd 100644 --- a/lib/subghz/protocols/princeton.c +++ b/lib/subghz/protocols/princeton.c @@ -363,7 +363,7 @@ void subghz_protocol_decoder_princeton_get_string(void* context, FuriString* out "Key:0x%08lX\r\n" "Yek:0x%08lX\r\n" "Sn:0x%05lX Btn:%01X\r\n" - "Te:%ldus\r\n", + "Te:%luus\r\n", instance->generic.protocol_name, instance->generic.data_count_bit, (uint32_t)(instance->generic.data & 0xFFFFFF), diff --git a/lib/subghz/protocols/princeton_for_testing.c b/lib/subghz/protocols/princeton_for_testing.c index 0987e0ad693..fa5616020e6 100644 --- a/lib/subghz/protocols/princeton_for_testing.c +++ b/lib/subghz/protocols/princeton_for_testing.c @@ -94,12 +94,12 @@ void subghz_encoder_princeton_for_testing_print_log(void* context) { ((float)instance->time_high / (instance->time_high + instance->time_low)) * 100; FURI_LOG_I( TAG "Encoder", - "Radio tx_time=%ldus ON=%ldus, OFF=%ldus, DutyCycle=%ld,%ld%%", + "Radio tx_time=%luus ON=%luus, OFF=%luus, DutyCycle=%lu,%lu%%", instance->time_high + instance->time_low, instance->time_high, instance->time_low, (uint32_t)duty_cycle, - (uint32_t)((duty_cycle - (uint32_t)duty_cycle) * 100)); + (uint32_t)((duty_cycle - (uint32_t)duty_cycle) * 100UL)); } LevelDuration subghz_encoder_princeton_for_testing_yield(void* context) { diff --git a/lib/subghz/protocols/scher_khan.c b/lib/subghz/protocols/scher_khan.c index a9a3078e47f..955104bcfd2 100644 --- a/lib/subghz/protocols/scher_khan.c +++ b/lib/subghz/protocols/scher_khan.c @@ -151,8 +151,8 @@ void subghz_protocol_decoder_scher_khan_feed(void* context, bool level, uint32_t break; case ScherKhanDecoderStepSaveDuration: if(level) { - if(duration >= (uint32_t)(subghz_protocol_scher_khan_const.te_long + - subghz_protocol_scher_khan_const.te_delta * 2)) { + if(duration >= (subghz_protocol_scher_khan_const.te_delta * 2UL + + subghz_protocol_scher_khan_const.te_long)) { //Found stop bit instance->decoder.parser_step = ScherKhanDecoderStepReset; if(instance->decoder.decode_count_bit >= diff --git a/lib/subghz/protocols/secplus_v1.c b/lib/subghz/protocols/secplus_v1.c index 7bd0b5b791c..75a44a26cd0 100644 --- a/lib/subghz/protocols/secplus_v1.c +++ b/lib/subghz/protocols/secplus_v1.c @@ -291,7 +291,7 @@ bool subghz_protocol_encoder_secplus_v1_deserialize(void* context, FlipperFormat 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; + 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"); @@ -550,7 +550,7 @@ bool subghz_protocol_secplus_v1_check_fixed(uint32_t fixed) { do { if(id1 == 0) return false; - if(!(btn == 0 || btn == 1 || btn == 2)) return false; + if(!(btn == 0 || btn == 1 || btn == 2)) return false; //-V560 } while(false); return true; } @@ -588,7 +588,7 @@ void subghz_protocol_decoder_secplus_v1_get_string(void* context, FuriString* ou if(pin <= 9999) { furi_string_cat_printf(output, " pin:%d", pin); - } else if(10000 <= pin && pin <= 11029) { + } else if(pin <= 11029) { furi_string_cat_printf(output, " pin:enter"); } @@ -618,7 +618,7 @@ void subghz_protocol_decoder_secplus_v1_get_string(void* context, FuriString* ou furi_string_cat_printf(output, " Btn:left\r\n"); } else if(instance->generic.btn == 0) { furi_string_cat_printf(output, " Btn:middle\r\n"); - } else if(instance->generic.btn == 2) { + } else if(instance->generic.btn == 2) { //-V547 furi_string_cat_printf(output, " Btn:right\r\n"); } diff --git a/lib/subghz/protocols/secplus_v2.c b/lib/subghz/protocols/secplus_v2.c index 90cc805a339..7b79892b08a 100644 --- a/lib/subghz/protocols/secplus_v2.c +++ b/lib/subghz/protocols/secplus_v2.c @@ -151,7 +151,7 @@ static bool subghz_protocol_secplus_v2_mix_order_decode(uint8_t order, uint16_t case 0x06: // 0b0110 2, 1, 0], case 0x09: // 0b1001 2, 1, 0], p[2] = a; - p[1] = b; + // p[1]: no change p[0] = c; break; case 0x08: // 0b1000 1, 2, 0], @@ -166,20 +166,18 @@ static bool subghz_protocol_secplus_v2_mix_order_decode(uint8_t order, uint16_t p[1] = c; break; case 0x00: // 0b0000 0, 2, 1], - p[0] = a; + // p[0]: no change p[2] = b; p[1] = c; break; case 0x05: // 0b0101 1, 0, 2], p[1] = a; p[0] = b; - p[2] = c; + // p[2]: no change break; case 0x02: // 0b0010 0, 1, 2], case 0x0A: // 0b1010 0, 1, 2], - p[0] = a; - p[1] = b; - p[2] = c; + // no reordering break; default: FURI_LOG_E(TAG, "Order FAIL"); @@ -539,7 +537,7 @@ bool subghz_protocol_encoder_secplus_v2_deserialize(void* context, FlipperFormat //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; + 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"); @@ -547,7 +545,7 @@ bool subghz_protocol_encoder_secplus_v2_deserialize(void* context, FlipperFormat } for(size_t i = 0; i < sizeof(uint64_t); i++) { - key_data[sizeof(uint64_t) - i - 1] = (instance->secplus_packet_1 >> i * 8) & 0xFF; + 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))) { @@ -605,7 +603,7 @@ bool subghz_protocol_secplus_v2_create_data( 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; + key_data[sizeof(uint64_t) - i - 1] = (instance->secplus_packet_1 >> (i * 8)) & 0xFF; } if(res && @@ -691,7 +689,7 @@ void subghz_protocol_decoder_secplus_v2_feed(void* context, bool level, uint32_t subghz_protocol_secplus_v2_const.te_delta) { event = ManchesterEventLongLow; } else if( - duration >= (uint32_t)(subghz_protocol_secplus_v2_const.te_long * 2 + + duration >= (subghz_protocol_secplus_v2_const.te_long * 2UL + subghz_protocol_secplus_v2_const.te_delta)) { if(instance->decoder.decode_count_bit == subghz_protocol_secplus_v2_const.min_count_bit_for_found) { @@ -766,7 +764,7 @@ bool subghz_protocol_decoder_secplus_v2_serialize( 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; + key_data[sizeof(uint64_t) - i - 1] = (instance->secplus_packet_1 >> (i * 8)) & 0xFF; } if(res && diff --git a/lib/subghz/protocols/smc5326.c b/lib/subghz/protocols/smc5326.c index 889e39f05da..9c9b5d4fd7d 100644 --- a/lib/subghz/protocols/smc5326.c +++ b/lib/subghz/protocols/smc5326.c @@ -372,8 +372,8 @@ void subghz_protocol_decoder_smc5326_get_string(void* context, FuriString* outpu furi_string_cat_printf( output, - "%s %dbit\r\n" - "Key:%07lX Te:%ldus\r\n" + "%s %ubit\r\n" + "Key:%07lX Te:%luus\r\n" " +: " DIP_PATTERN "\r\n" " o: " DIP_PATTERN " ", instance->generic.protocol_name, diff --git a/lib/subghz/subghz_file_encoder_worker.c b/lib/subghz/subghz_file_encoder_worker.c index abc33188fff..5c4d36f78e8 100644 --- a/lib/subghz/subghz_file_encoder_worker.c +++ b/lib/subghz/subghz_file_encoder_worker.c @@ -90,7 +90,7 @@ LevelDuration subghz_file_encoder_worker_get_level_duration(void* context) { level_duration = level_duration_make(false, -duration); } else if(duration > 0) { level_duration = level_duration_make(true, duration); - } else if(duration == 0) { + } else if(duration == 0) { //-V547 level_duration = level_duration_reset(); FURI_LOG_I(TAG, "Stop transmission"); instance->worker_stoping = true; diff --git a/lib/subghz/subghz_keystore.c b/lib/subghz/subghz_keystore.c index e06bd9796df..e0b1cf6ca3d 100644 --- a/lib/subghz/subghz_keystore.c +++ b/lib/subghz/subghz_keystore.c @@ -189,7 +189,7 @@ bool subghz_keystore_load(SubGhzKeystore* instance, const char* file_name) { bool result = false; uint8_t iv[16]; uint32_t version; - SubGhzKeystoreEncryption encryption; + uint32_t encryption; FuriString* filetype; filetype = furi_string_alloc(); @@ -324,9 +324,9 @@ bool subghz_keystore_save(SubGhzKeystore* instance, const char* file_name, uint8 size_t total_keys = SubGhzKeyArray_size(instance->data); result = encrypted_line_count == total_keys; if(result) { - FURI_LOG_I(TAG, "Success. Encrypted: %d of %d", encrypted_line_count, total_keys); + FURI_LOG_I(TAG, "Success. Encrypted: %zu of %zu", encrypted_line_count, total_keys); } else { - FURI_LOG_E(TAG, "Failure. Encrypted: %d of %d", encrypted_line_count, total_keys); + FURI_LOG_E(TAG, "Failure. Encrypted: %zu of %zu", encrypted_line_count, total_keys); } } while(0); flipper_format_free(flipper_format); @@ -349,9 +349,9 @@ bool subghz_keystore_raw_encrypted_save( uint8_t* iv) { bool encrypted = false; uint32_t version; + uint32_t encryption; FuriString* filetype; filetype = furi_string_alloc(); - SubGhzKeystoreEncryption encryption; Storage* storage = furi_record_open(RECORD_STORAGE); @@ -464,7 +464,7 @@ bool subghz_keystore_raw_encrypted_save( } stream_write_cstring(output_stream, encrypted_line); - } while(result); + } while(true); flipper_format_free(output_flipper_format); @@ -488,7 +488,7 @@ bool subghz_keystore_raw_get_data(const char* file_name, size_t offset, uint8_t* bool result = false; uint8_t iv[16]; uint32_t version; - SubGhzKeystoreEncryption encryption; + uint32_t encryption; FuriString* str_temp; str_temp = furi_string_alloc(); diff --git a/lib/subghz/subghz_setting.c b/lib/subghz/subghz_setting.c index c5ec5db75cc..57e23c38cf9 100644 --- a/lib/subghz/subghz_setting.c +++ b/lib/subghz/subghz_setting.c @@ -532,9 +532,8 @@ uint8_t* subghz_setting_get_preset_data_by_name(SubGhzSetting* instance, const c uint32_t subghz_setting_get_frequency(SubGhzSetting* instance, size_t idx) { furi_assert(instance); - uint32_t* ret = FrequencyList_get(instance->frequencies, idx); - if(ret) { - return (*ret) & FREQUENCY_MASK; + if(idx < FrequencyList_size(instance->frequencies)) { + return (*FrequencyList_get(instance->frequencies, idx)) & FREQUENCY_MASK; } else { return 0; } @@ -542,9 +541,8 @@ uint32_t subghz_setting_get_frequency(SubGhzSetting* instance, size_t idx) { uint32_t subghz_setting_get_hopper_frequency(SubGhzSetting* instance, size_t idx) { furi_assert(instance); - uint32_t* ret = FrequencyList_get(instance->hopper_frequencies, idx); - if(ret) { - return *ret; + if(idx < FrequencyList_size(instance->frequencies)) { + return *FrequencyList_get(instance->hopper_frequencies, idx); } else { return 0; } diff --git a/lib/toolbox/dir_walk.c b/lib/toolbox/dir_walk.c index b5e2cb52bb5..e5a3cf32b59 100644 --- a/lib/toolbox/dir_walk.c +++ b/lib/toolbox/dir_walk.c @@ -69,8 +69,11 @@ static DirWalkResult if(dir_walk_filter(dir_walk, name, &info)) { if(return_path != NULL) { - furi_string_printf( - return_path, "%s/%s", furi_string_get_cstr(dir_walk->path), name); + furi_string_printf( //-V576 + return_path, + "%s/%s", + furi_string_get_cstr(dir_walk->path), + name); } if(fileinfo != NULL) { diff --git a/lib/toolbox/float_tools.c b/lib/toolbox/float_tools.c new file mode 100644 index 00000000000..9c0fe871e74 --- /dev/null +++ b/lib/toolbox/float_tools.c @@ -0,0 +1,8 @@ +#include "float_tools.h" + +#include +#include + +bool float_is_equal(float a, float b) { + return fabsf(a - b) <= FLT_EPSILON * fmaxf(fabsf(a), fabsf(b)); +} diff --git a/lib/toolbox/float_tools.h b/lib/toolbox/float_tools.h new file mode 100644 index 00000000000..0b758e9f5ec --- /dev/null +++ b/lib/toolbox/float_tools.h @@ -0,0 +1,19 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/** Compare two floating point numbers + * @param a First number to compare + * @param b Second number to compare + * + * @return bool true if a equals b, false otherwise + */ +bool float_is_equal(float a, float b); + +#ifdef __cplusplus +} +#endif diff --git a/lib/toolbox/hex.c b/lib/toolbox/hex.c index 7b2719b795e..25dcb09500e 100644 --- a/lib/toolbox/hex.c +++ b/lib/toolbox/hex.c @@ -1,14 +1,14 @@ #include "hex.h" bool hex_char_to_hex_nibble(char c, uint8_t* nibble) { - if((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f')) { - if((c >= '0' && c <= '9')) { - *nibble = c - '0'; - } else if((c >= 'A' && c <= 'F')) { - *nibble = c - 'A' + 10; - } else { - *nibble = c - 'a' + 10; - } + if((c >= '0' && c <= '9')) { + *nibble = c - '0'; + return true; + } else if((c >= 'A' && c <= 'F')) { + *nibble = c - 'A' + 10; + return true; + } else if(c >= 'a' && c <= 'f') { + *nibble = c - 'a' + 10; return true; } else { return false; diff --git a/lib/toolbox/md5.c b/lib/toolbox/md5.c index 3cf7cf05c07..a907d52e3b4 100644 --- a/lib/toolbox/md5.c +++ b/lib/toolbox/md5.c @@ -115,7 +115,7 @@ void md5_process(md5_context* ctx, const unsigned char data[64]) { GET_UINT32_LE(X[14], data, 56); GET_UINT32_LE(X[15], data, 60); -#define S(x, n) ((x << n) | ((x & 0xFFFFFFFF) >> (32 - n))) +#define S(x, n) (((x) << (n)) | (((x)&0xFFFFFFFF) >> (32 - (n)))) #define P(a, b, c, d, k, s, t) \ { \ @@ -128,7 +128,7 @@ void md5_process(md5_context* ctx, const unsigned char data[64]) { C = ctx->state[2]; D = ctx->state[3]; -#define F(x, y, z) (z ^ (x & (y ^ z))) +#define F(x, y, z) ((z) ^ ((x) & ((y) ^ (z)))) P(A, B, C, D, 0, 7, 0xD76AA478); P(D, A, B, C, 1, 12, 0xE8C7B756); @@ -149,7 +149,7 @@ void md5_process(md5_context* ctx, const unsigned char data[64]) { #undef F -#define F(x, y, z) (y ^ (z & (x ^ y))) +#define F(x, y, z) ((y) ^ ((z) & ((x) ^ (y)))) P(A, B, C, D, 1, 5, 0xF61E2562); P(D, A, B, C, 6, 9, 0xC040B340); @@ -170,7 +170,7 @@ void md5_process(md5_context* ctx, const unsigned char data[64]) { #undef F -#define F(x, y, z) (x ^ y ^ z) +#define F(x, y, z) ((x) ^ (y) ^ (z)) P(A, B, C, D, 5, 4, 0xFFFA3942); P(D, A, B, C, 8, 11, 0x8771F681); @@ -191,7 +191,7 @@ void md5_process(md5_context* ctx, const unsigned char data[64]) { #undef F -#define F(x, y, z) (y ^ (x | ~z)) +#define F(x, y, z) ((y) ^ ((x) | ~(z))) P(A, B, C, D, 0, 6, 0xF4292244); P(D, A, B, C, 7, 10, 0x432AFF97); @@ -295,5 +295,5 @@ void md5(const unsigned char* input, size_t ilen, unsigned char output[16]) { md5_update(&ctx, input, ilen); md5_finish(&ctx, output); - memset(&ctx, 0, sizeof(md5_context)); + memset(&ctx, 0, sizeof(md5_context)); //-V597 } diff --git a/lib/toolbox/sha256.c b/lib/toolbox/sha256.c index ece77955d9e..ff4984439db 100644 --- a/lib/toolbox/sha256.c +++ b/lib/toolbox/sha256.c @@ -62,15 +62,15 @@ static void memcpy_output_bswap32(unsigned char* dst, const uint32_t* p) { } } -#define rotr32(x, n) (((x) >> n) | ((x) << (32 - n))) +#define rotr32(x, n) (((x) >> n) | ((x) << (32 - (n)))) #define ch(x, y, z) ((z) ^ ((x) & ((y) ^ (z)))) #define maj(x, y, z) (((x) & (y)) | ((z) & ((x) ^ (y)))) /* round transforms for SHA256 compression functions */ -#define vf(n, i) v[(n - i) & 7] +#define vf(n, i) v[((n) - (i)) & 7] -#define hf(i) (p[i & 15] += g_1(p[(i + 14) & 15]) + p[(i + 9) & 15] + g_0(p[(i + 1) & 15])) +#define hf(i) (p[(i)&15] += g_1(p[((i) + 14) & 15]) + p[((i) + 9) & 15] + g_0(p[((i) + 1) & 15])) #define v_cycle0(i) \ p[i] = __builtin_bswap32(p[i]); \ @@ -176,8 +176,8 @@ void sha256_finish(sha256_context* ctx, unsigned char output[32]) { uint32_t last = (ctx->total[0] & SHA256_MASK); ctx->wbuf[last >> 2] = __builtin_bswap32(ctx->wbuf[last >> 2]); - ctx->wbuf[last >> 2] &= 0xffffff80 << (8 * (~last & 3)); - ctx->wbuf[last >> 2] |= 0x00000080 << (8 * (~last & 3)); + ctx->wbuf[last >> 2] &= 0xffffff80UL << (8 * (~last & 3)); + ctx->wbuf[last >> 2] |= 0x00000080UL << (8 * (~last & 3)); ctx->wbuf[last >> 2] = __builtin_bswap32(ctx->wbuf[last >> 2]); if(last > SHA256_BLOCK_SIZE - 9) { diff --git a/lib/toolbox/stream/stream.c b/lib/toolbox/stream/stream.c index 86d35c95970..055bab5bfbb 100644 --- a/lib/toolbox/stream/stream.c +++ b/lib/toolbox/stream/stream.c @@ -315,8 +315,8 @@ void stream_dump_data(Stream* stream) { size_t size = stream_size(stream); size_t tell = stream_tell(stream); printf("stream %p\r\n", stream); - printf("size = %u\r\n", size); - printf("tell = %u\r\n", tell); + printf("size = %zu\r\n", size); + printf("tell = %zu\r\n", tell); printf("DATA START\r\n"); uint8_t* data = malloc(STREAM_CACHE_SIZE); stream_rewind(stream); diff --git a/lib/toolbox/tar/tar_archive.c b/lib/toolbox/tar/tar_archive.c index e8b44729138..fd0d175eaa2 100644 --- a/lib/toolbox/tar/tar_archive.c +++ b/lib/toolbox/tar/tar_archive.c @@ -236,7 +236,7 @@ static int archive_extract_foreach_cb(mtar_t* tar, const mtar_header_t* header, return 0; } - FURI_LOG_D(TAG, "Extracting %d bytes to '%s'", header->size, header->name); + FURI_LOG_D(TAG, "Extracting %u bytes to '%s'", header->size, header->name); FuriString* converted_fname = furi_string_alloc_set(header->name); if(op_params->converter) { @@ -382,4 +382,4 @@ bool tar_archive_unpack_file( return false; } return archive_extract_current_file(archive, destination); -} \ No newline at end of file +} diff --git a/lib/toolbox/varint.c b/lib/toolbox/varint.c index ee2f5c3af9d..79777c4ba58 100644 --- a/lib/toolbox/varint.c +++ b/lib/toolbox/varint.c @@ -15,7 +15,7 @@ size_t varint_uint32_unpack(uint32_t* value, const uint8_t* input, size_t input_ uint32_t parsed = 0; for(i = 0; i < input_size; i++) { - parsed |= (input[i] & 0x7F) << (7 * i); + parsed |= (input[i] & 0x7FUL) << (7 * i); if(!(input[i] & 0x80)) { break; @@ -73,4 +73,4 @@ size_t varint_int32_length(int32_t value) { } return varint_uint32_length(v); -} \ No newline at end of file +} diff --git a/lib/update_util/dfu_file.c b/lib/update_util/dfu_file.c index d6f31b60203..62b139e864f 100644 --- a/lib/update_util/dfu_file.c +++ b/lib/update_util/dfu_file.c @@ -35,7 +35,7 @@ uint8_t dfu_file_validate_headers(File* dfuf, const DfuValidationParams* referen return 0; } - if(memcmp(dfu_prefix.szSignature, DFU_SIGNATURE, sizeof(dfu_prefix.szSignature))) { + if(memcmp(dfu_prefix.szSignature, DFU_SIGNATURE, sizeof(dfu_prefix.szSignature)) != 0) { return 0; } diff --git a/lib/update_util/resources/manifest.c b/lib/update_util/resources/manifest.c index 8b6a1b33cf8..baa7acebdfd 100644 --- a/lib/update_util/resources/manifest.c +++ b/lib/update_util/resources/manifest.c @@ -98,7 +98,7 @@ ResourceManifestEntry* resource_manifest_reader_next(ResourceManifestReader* res furi_string_right(resource_manifest->linebuf, offs + 1); furi_string_set(resource_manifest->entry.name, resource_manifest->linebuf); - } else if(resource_manifest->entry.type == ResourceManifestEntryTypeDirectory) { + } else if(resource_manifest->entry.type == ResourceManifestEntryTypeDirectory) { //-V547 /* Parse directory entry D: */ diff --git a/lib/update_util/update_operation.c b/lib/update_util/update_operation.c index 3a44605e0d9..c6a9ccc5feb 100644 --- a/lib/update_util/update_operation.c +++ b/lib/update_util/update_operation.c @@ -11,7 +11,7 @@ #define UPDATE_ROOT_DIR EXT_PATH("update") /* Need at least 4 free LFS pages before update */ -#define UPDATE_MIN_INT_FREE_SPACE 2 * 4 * 1024 +#define UPDATE_MIN_INT_FREE_SPACE (2 * 4 * 1024) static const char* update_prepare_result_descr[] = { [UpdatePrepareResultOK] = "OK", @@ -110,7 +110,7 @@ bool update_operation_get_current_package_manifest_path(Storage* storage, FuriSt } static bool update_operation_persist_manifest_path(Storage* storage, const char* manifest_path) { - const uint16_t manifest_path_len = strlen(manifest_path); + const size_t manifest_path_len = strlen(manifest_path); furi_check(manifest_path && manifest_path_len); bool success = false; File* file = storage_file_alloc(storage); diff --git a/scripts/fbt_tools/fbt_debugopts.py b/scripts/fbt_tools/fbt_debugopts.py index 9ff05cb735d..f4b021c2025 100644 --- a/scripts/fbt_tools/fbt_debugopts.py +++ b/scripts/fbt_tools/fbt_debugopts.py @@ -41,12 +41,10 @@ def generate(env, **kw): "|openocd -c 'gdb_port pipe; log_output ${FBT_DEBUG_DIR}/openocd.log' ${[SINGLEQUOTEFUNC(OPENOCD_OPTS)]}" ], GDBOPTS_BASE=[ - "-ex", - "set pagination off", "-ex", "target extended-remote ${GDBREMOTE}", "-ex", - "set confirm off", + "source ${FBT_DEBUG_DIR}/gdbinit", ], GDBOPTS_BLACKMAGIC=[ "-ex", From a34fbf6976dabd905ad0d88a814dec0c32df7634 Mon Sep 17 00:00:00 2001 From: Max Andreev Date: Mon, 26 Dec 2022 22:33:44 +0300 Subject: [PATCH 314/824] [FL-3056] Prevent merging of PRs if there are warnings from PVS-studio (#2176) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- .github/workflows/pvs_studio.yml | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/.github/workflows/pvs_studio.yml b/.github/workflows/pvs_studio.yml index c521fbca4d3..46ee8801db3 100644 --- a/.github/workflows/pvs_studio.yml +++ b/.github/workflows/pvs_studio.yml @@ -63,11 +63,20 @@ jobs: -f build/f7-firmware-DC/compile_commands.json \ -o PVS-Studio.log - - name: 'Convert PVS-Studio output to html page' - run: plog-converter -a GA:1,2,3 -t fullhtml PVS-Studio.log -o reports/${DEFAULT_TARGET}-${SUFFIX} + - name: 'Convert PVS-Studio output to html and detect warnings' + id: pvs-warn + run: | + WARNINGS=0 + plog-converter \ + -a GA:1,2,3 \ + -t fullhtml \ + --indicate-warnings \ + PVS-Studio.log \ + -o reports/${DEFAULT_TARGET}-${SUFFIX} || WARNINGS=1 + echo "warnings=${WARNINGS}" >> $GITHUB_OUTPUT - name: 'Upload artifacts to update server' - if: ${{ !github.event.pull_request.head.repo.fork }} + if: ${{ !github.event.pull_request.head.repo.fork && (steps.pvs-warn.outputs.warnings != 0) }} run: | mkdir -p ~/.ssh ssh-keyscan -p ${{ secrets.RSYNC_DEPLOY_PORT }} -H ${{ secrets.RSYNC_DEPLOY_HOST }} > ~/.ssh/known_hosts @@ -79,8 +88,8 @@ jobs: rm ./deploy_key; - name: 'Find Previous Comment' - if: ${{ !github.event.pull_request.head.repo.fork && github.event.pull_request }} - uses: peter-evans/find-comment@v1 + if: ${{ !github.event.pull_request.head.repo.fork && github.event.pull_request && (steps.pvs-warn.outputs.warnings != 0) }} + uses: peter-evans/find-comment@v2 id: fc with: issue-number: ${{ github.event.pull_request.number }} @@ -88,7 +97,7 @@ jobs: body-includes: 'PVS-Studio report for commit' - name: 'Create or update comment' - if: ${{ !github.event.pull_request.head.repo.fork && github.event.pull_request}} + if: ${{ !github.event.pull_request.head.repo.fork && github.event.pull_request && (steps.pvs-warn.outputs.warnings != 0) }} uses: peter-evans/create-or-update-comment@v1 with: comment-id: ${{ steps.fc.outputs.comment-id }} @@ -97,3 +106,10 @@ jobs: **PVS-Studio report for commit `${{steps.names.outputs.commit_sha}}`:** - [Report](https://update.flipperzero.one/builds/firmware-pvs-studio-report/${{steps.names.outputs.branch_name}}/${{steps.names.outputs.default_target}}-${{steps.names.outputs.suffix}}/index.html) edit-mode: replace + + - name: 'Raise exception' + if: ${{ steps.pvs-warn.outputs.warnings != 0 }} + run: | + echo "Please fix all PVS varnings before merge" + exit 1 + From 9192520c7096a9ff673c8bfbdd75abb71e4c7e7c Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Tue, 27 Dec 2022 05:46:05 +1000 Subject: [PATCH 315/824] Modules: locking view model (#2189) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Popup, dialog_ex: locking model * Desktop: do not use the model where it is not needed Co-authored-by: あく --- applications/services/desktop/views/desktop_view_main.c | 1 - .../services/desktop/views/desktop_view_pin_setup_done.c | 3 +-- applications/services/gui/modules/dialog_ex.c | 2 +- applications/services/gui/modules/popup.c | 2 +- .../views/desktop_settings_view_pin_setup_howto.c | 3 +-- .../views/desktop_settings_view_pin_setup_howto2.c | 3 +-- 6 files changed, 5 insertions(+), 9 deletions(-) diff --git a/applications/services/desktop/views/desktop_view_main.c b/applications/services/desktop/views/desktop_view_main.c index cbf4a20ffe3..7d956489a6b 100644 --- a/applications/services/desktop/views/desktop_view_main.c +++ b/applications/services/desktop/views/desktop_view_main.c @@ -102,7 +102,6 @@ DesktopMainView* desktop_main_alloc() { DesktopMainView* main_view = malloc(sizeof(DesktopMainView)); main_view->view = view_alloc(); - view_allocate_model(main_view->view, ViewModelTypeLockFree, 1); view_set_context(main_view->view, main_view); view_set_input_callback(main_view->view, desktop_main_input_callback); diff --git a/applications/services/desktop/views/desktop_view_pin_setup_done.c b/applications/services/desktop/views/desktop_view_pin_setup_done.c index 1d82aeaac89..561b12861e1 100644 --- a/applications/services/desktop/views/desktop_view_pin_setup_done.c +++ b/applications/services/desktop/views/desktop_view_pin_setup_done.c @@ -18,7 +18,7 @@ struct DesktopViewPinSetupDone { static void desktop_view_pin_done_draw(Canvas* canvas, void* model) { furi_assert(canvas); - furi_assert(model); + UNUSED(model); canvas_set_font(canvas, FontPrimary); elements_multiline_text_aligned( @@ -59,7 +59,6 @@ void desktop_view_pin_done_set_callback( DesktopViewPinSetupDone* desktop_view_pin_done_alloc() { DesktopViewPinSetupDone* view = malloc(sizeof(DesktopViewPinSetupDone)); view->view = view_alloc(); - view_allocate_model(view->view, ViewModelTypeLockFree, 1); view_set_context(view->view, view); view_set_draw_callback(view->view, desktop_view_pin_done_draw); view_set_input_callback(view->view, desktop_view_pin_done_input); diff --git a/applications/services/gui/modules/dialog_ex.c b/applications/services/gui/modules/dialog_ex.c index 1cb46723247..7c3ef9b4522 100644 --- a/applications/services/gui/modules/dialog_ex.c +++ b/applications/services/gui/modules/dialog_ex.c @@ -147,7 +147,7 @@ DialogEx* dialog_ex_alloc() { DialogEx* dialog_ex = malloc(sizeof(DialogEx)); dialog_ex->view = view_alloc(); view_set_context(dialog_ex->view, dialog_ex); - view_allocate_model(dialog_ex->view, ViewModelTypeLockFree, sizeof(DialogExModel)); + view_allocate_model(dialog_ex->view, ViewModelTypeLocking, sizeof(DialogExModel)); view_set_draw_callback(dialog_ex->view, dialog_ex_view_draw_callback); view_set_input_callback(dialog_ex->view, dialog_ex_view_input_callback); with_view_model( diff --git a/applications/services/gui/modules/popup.c b/applications/services/gui/modules/popup.c index 08e8d8c2b70..d75abb95fa6 100644 --- a/applications/services/gui/modules/popup.c +++ b/applications/services/gui/modules/popup.c @@ -117,7 +117,7 @@ Popup* popup_alloc() { popup->timer_enabled = false; view_set_context(popup->view, popup); - view_allocate_model(popup->view, ViewModelTypeLockFree, sizeof(PopupModel)); + view_allocate_model(popup->view, ViewModelTypeLocking, sizeof(PopupModel)); view_set_draw_callback(popup->view, popup_view_draw_callback); view_set_input_callback(popup->view, popup_view_input_callback); view_set_enter_callback(popup->view, popup_start_timer); diff --git a/applications/settings/desktop_settings/views/desktop_settings_view_pin_setup_howto.c b/applications/settings/desktop_settings/views/desktop_settings_view_pin_setup_howto.c index 3831be8c404..26aa7c3e15a 100644 --- a/applications/settings/desktop_settings/views/desktop_settings_view_pin_setup_howto.c +++ b/applications/settings/desktop_settings/views/desktop_settings_view_pin_setup_howto.c @@ -17,7 +17,7 @@ struct DesktopSettingsViewPinSetupHowto { static void desktop_settings_view_pin_setup_howto_draw(Canvas* canvas, void* model) { furi_assert(canvas); - furi_assert(model); + UNUSED(model); canvas_draw_icon(canvas, 16, 18, &I_Pin_attention_dpad_29x29); elements_button_right(canvas, "Next"); @@ -57,7 +57,6 @@ void desktop_settings_view_pin_setup_howto_set_callback( DesktopSettingsViewPinSetupHowto* desktop_settings_view_pin_setup_howto_alloc() { DesktopSettingsViewPinSetupHowto* view = malloc(sizeof(DesktopSettingsViewPinSetupHowto)); view->view = view_alloc(); - view_allocate_model(view->view, ViewModelTypeLockFree, 1); view_set_context(view->view, view); view_set_draw_callback(view->view, desktop_settings_view_pin_setup_howto_draw); view_set_input_callback(view->view, desktop_settings_view_pin_setup_howto_input); diff --git a/applications/settings/desktop_settings/views/desktop_settings_view_pin_setup_howto2.c b/applications/settings/desktop_settings/views/desktop_settings_view_pin_setup_howto2.c index ab1fa2383b8..c28826e628a 100644 --- a/applications/settings/desktop_settings/views/desktop_settings_view_pin_setup_howto2.c +++ b/applications/settings/desktop_settings/views/desktop_settings_view_pin_setup_howto2.c @@ -18,7 +18,7 @@ struct DesktopSettingsViewPinSetupHowto2 { static void desktop_settings_view_pin_setup_howto2_draw(Canvas* canvas, void* model) { furi_assert(canvas); - furi_assert(model); + UNUSED(model); canvas_set_font(canvas, FontSecondary); elements_multiline_text_aligned( @@ -79,7 +79,6 @@ void desktop_settings_view_pin_setup_howto2_set_ok_callback( DesktopSettingsViewPinSetupHowto2* desktop_settings_view_pin_setup_howto2_alloc() { DesktopSettingsViewPinSetupHowto2* view = malloc(sizeof(DesktopSettingsViewPinSetupHowto2)); view->view = view_alloc(); - view_allocate_model(view->view, ViewModelTypeLockFree, 1); view_set_context(view->view, view); view_set_draw_callback(view->view, desktop_settings_view_pin_setup_howto2_draw); view_set_input_callback(view->view, desktop_settings_view_pin_setup_howto2_input); From 1c926cf8a2989bfb96d4201e813a80dcbe0eeb63 Mon Sep 17 00:00:00 2001 From: Max Andreev Date: Mon, 26 Dec 2022 23:22:22 +0300 Subject: [PATCH 316/824] Trigger amap from another repo (#2171) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add s3 upload, repository dispatch * Add trigger * Fix map file upload * Debug * Add event file upload to s3 * fix triggering * Fix upload process * fix build.yml Co-authored-by: あく --- .github/workflows/amap_analyse.yml | 103 ---------------------- .github/workflows/build.yml | 39 +++++++-- scripts/amap_mariadb_insert.py | 136 ----------------------------- 3 files changed, 32 insertions(+), 246 deletions(-) delete mode 100644 .github/workflows/amap_analyse.yml delete mode 100755 scripts/amap_mariadb_insert.py diff --git a/.github/workflows/amap_analyse.yml b/.github/workflows/amap_analyse.yml deleted file mode 100644 index 1340e4cde91..00000000000 --- a/.github/workflows/amap_analyse.yml +++ /dev/null @@ -1,103 +0,0 @@ -name: 'Analyze .map file with Amap' - -on: - push: - branches: - - dev - - "release*" - tags: - - '*' - pull_request: - -env: - TARGETS: f7 - FBT_TOOLCHAIN_PATH: /opt - -jobs: - amap_analyse: - if: ${{ !github.event.pull_request.head.repo.fork }} - runs-on: [self-hosted,FlipperZeroMacShell] - timeout-minutes: 15 - steps: - - name: 'Wait Build workflow' - uses: fountainhead/action-wait-for-check@v1.0.0 - id: wait-for-build - with: - token: ${{ secrets.GITHUB_TOKEN }} - checkName: 'main' - ref: ${{ github.event.pull_request.head.sha || github.sha }} - intervalSeconds: 20 - - - name: 'Check Build workflow status' - if: steps.wait-for-build.outputs.conclusion == 'failure' - run: | - exit 1 - - - name: 'Decontaminate previous build leftovers' - run: | - if [ -d .git ]; then - git submodule status || git checkout "$(git rev-list --max-parents=0 HEAD | tail -n 1)" - fi - - - name: 'Checkout code' - uses: actions/checkout@v3 - with: - fetch-depth: 0 - ref: ${{ github.event.pull_request.head.sha }} - - - name: 'Get commit details' - run: | - if [[ ${{ github.event_name }} == 'pull_request' ]]; then - TYPE="pull" - elif [[ "${{ github.ref }}" == "refs/tags/"* ]]; then - TYPE="tag" - else - TYPE="other" - fi - python3 scripts/get_env.py "--event_file=${{ github.event_path }}" "--type=$TYPE" - - - name: 'Make artifacts directory' - run: | - rm -rf artifacts - mkdir artifacts - - - name: 'Download build artifacts' - run: | - mkdir -p ~/.ssh - ssh-keyscan -p ${{ secrets.RSYNC_DEPLOY_PORT }} -H ${{ secrets.RSYNC_DEPLOY_HOST }} > ~/.ssh/known_hosts - echo "${{ secrets.RSYNC_DEPLOY_KEY }}" > deploy_key; - chmod 600 ./deploy_key; - rsync -avzP \ - -e 'ssh -p ${{ secrets.RSYNC_DEPLOY_PORT }} -i ./deploy_key' \ - ${{ secrets.RSYNC_DEPLOY_USER }}@${{ secrets.RSYNC_DEPLOY_HOST }}:"${{ secrets.RSYNC_DEPLOY_BASE_PATH }}${BRANCH_NAME}/" artifacts/; - rm ./deploy_key; - - - name: 'Make .map file analyze' - run: | - cd artifacts/ - /Applications/amap/Contents/MacOS/amap -f "flipper-z-f7-firmware-${SUFFIX}.elf.map" - - - name: 'Upload report to DB' - run: | - source scripts/toolchain/fbtenv.sh - get_size() - { - SECTION="$1"; - arm-none-eabi-size \ - -A artifacts/flipper-z-f7-firmware-$SUFFIX.elf \ - | grep "^$SECTION" | awk '{print $2}' - } - export BSS_SIZE="$(get_size ".bss")" - export TEXT_SIZE="$(get_size ".text")" - export RODATA_SIZE="$(get_size ".rodata")" - export DATA_SIZE="$(get_size ".data")" - export FREE_FLASH_SIZE="$(get_size ".free_flash")" - python3 -m pip install mariadb==1.1.4 - python3 scripts/amap_mariadb_insert.py \ - ${{ secrets.AMAP_MARIADB_USER }} \ - ${{ secrets.AMAP_MARIADB_PASSWORD }} \ - ${{ secrets.AMAP_MARIADB_HOST }} \ - ${{ secrets.AMAP_MARIADB_PORT }} \ - ${{ secrets.AMAP_MARIADB_DATABASE }} \ - artifacts/flipper-z-f7-firmware-$SUFFIX.elf.map.all - diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2de0e57c325..9ca60aee6cb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -30,11 +30,6 @@ jobs: fetch-depth: 0 ref: ${{ github.event.pull_request.head.sha }} - - name: 'Make artifacts directory' - run: | - rm -rf artifacts - mkdir artifacts - - name: 'Get commit details' id: names run: | @@ -46,6 +41,15 @@ jobs: TYPE="other" fi python3 scripts/get_env.py "--event_file=${{ github.event_path }}" "--type=$TYPE" + echo random_hash=$(openssl rand -base64 40 | shasum -a 256 | awk '{print $1}') >> $GITHUB_OUTPUT + echo "event_type=$TYPE" >> $GITHUB_OUTPUT + + - name: 'Make artifacts directory' + run: | + rm -rf artifacts + rm -rf map_analyser_files + mkdir artifacts + mkdir map_analyser_files - name: 'Bundle scripts' if: ${{ !github.event.pull_request.head.repo.fork }} @@ -82,9 +86,30 @@ jobs: run: | cp build/core2_firmware.tgz "artifacts/flipper-z-any-core2_firmware-${SUFFIX}.tgz" - - name: 'Copy .map file' + - name: 'Copy map analyser files' run: | - cp build/f7-firmware-*/firmware.elf.map "artifacts/flipper-z-f7-firmware-${SUFFIX}.elf.map" + cp build/f7-firmware-*/firmware.elf.map map_analyser_files/firmware.elf.map + cp build/f7-firmware-*/firmware.elf map_analyser_files/firmware.elf + cp ${{ github.event_path }} map_analyser_files/event.json + + - name: 'Upload map analyser files to storage' + uses: keithweaver/aws-s3-github-action@v1.0.0 + with: + source: map_analyser_files/ + destination: "s3://${{ secrets.MAP_REPORT_AWS_BUCKET }}/${{steps.names.outputs.random_hash}}" + aws_access_key_id: "${{ secrets.MAP_REPORT_AWS_ACCESS_KEY }}" + aws_secret_access_key: "${{ secrets.MAP_REPORT_AWS_SECRET_KEY }}" + aws_region: "${{ secrets.MAP_REPORT_AWS_REGION }}" + flags: --recursive + + - name: 'Trigger map file reporter' + uses: peter-evans/repository-dispatch@v2 + with: + repository: flipperdevices/flipper-map-reporter + token: ${{ secrets.REPOSITORY_DISPATCH_TOKEN }} + event-type: map-file-analyse + client-payload: '{"random_hash": "${{steps.names.outputs.random_hash}}", "event_type": "${{steps.names.outputs.event_type}}"}' + - name: 'Upload artifacts to update server' if: ${{ !github.event.pull_request.head.repo.fork }} diff --git a/scripts/amap_mariadb_insert.py b/scripts/amap_mariadb_insert.py deleted file mode 100755 index 6ff1b3bf038..00000000000 --- a/scripts/amap_mariadb_insert.py +++ /dev/null @@ -1,136 +0,0 @@ -#!/usr/bin/env python3 - -from datetime import datetime -import argparse -import mariadb -import sys -import os - - -def parseArgs(): - parser = argparse.ArgumentParser() - parser.add_argument("db_user", help="MariaDB user") - parser.add_argument("db_pass", help="MariaDB password") - parser.add_argument("db_host", help="MariaDB hostname") - parser.add_argument("db_port", type=int, help="MariaDB port") - parser.add_argument("db_name", help="MariaDB database") - parser.add_argument("report_file", help="Report file(.map.all)") - args = parser.parse_args() - return args - - -def mariadbConnect(args): - try: - conn = mariadb.connect( - user=args.db_user, - password=args.db_pass, - host=args.db_host, - port=args.db_port, - database=args.db_name, - ) - except mariadb.Error as e: - print(f"Error connecting to MariaDB: {e}") - sys.exit(1) - return conn - - -def parseEnv(): - outArr = [] - outArr.append(datetime.now().strftime("%Y-%m-%d %H:%M:%S")) - outArr.append(os.getenv("COMMIT_HASH", default=None)) - outArr.append(os.getenv("COMMIT_MSG", default=None)) - outArr.append(os.getenv("BRANCH_NAME", default=None)) - outArr.append(os.getenv("BSS_SIZE", default=None)) - outArr.append(os.getenv("TEXT_SIZE", default=None)) - outArr.append(os.getenv("RODATA_SIZE", default=None)) - outArr.append(os.getenv("DATA_SIZE", default=None)) - outArr.append(os.getenv("FREE_FLASH_SIZE", default=None)) - outArr.append(os.getenv("PULL_ID", default=None)) - outArr.append(os.getenv("PULL_NAME", default=None)) - return outArr - - -def createTables(cur, conn): - headerTable = "CREATE TABLE IF NOT EXISTS `header` ( \ - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, \ - `datetime` datetime NOT NULL, \ - `commit` varchar(40) NOT NULL, \ - `commit_msg` text NOT NULL, \ - `branch_name` text NOT NULL, \ - `bss_size` int(10) unsigned NOT NULL, \ - `text_size` int(10) unsigned NOT NULL, \ - `rodata_size` int(10) unsigned NOT NULL, \ - `data_size` int(10) unsigned NOT NULL, \ - `free_flash_size` int(10) unsigned NOT NULL, \ - `pullrequest_id` int(10) unsigned DEFAULT NULL, \ - `pullrequest_name` text DEFAULT NULL, \ - PRIMARY KEY (`id`), \ - KEY `header_id_index` (`id`) )" - dataTable = "CREATE TABLE IF NOT EXISTS `data` ( \ - `header_id` int(10) unsigned NOT NULL, \ - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, \ - `section` text NOT NULL, \ - `address` text NOT NULL, \ - `size` int(10) unsigned NOT NULL, \ - `name` text NOT NULL, \ - `lib` text NOT NULL, \ - `obj_name` text NOT NULL, \ - PRIMARY KEY (`id`), \ - KEY `data_id_index` (`id`), \ - KEY `data_header_id_index` (`header_id`), \ - CONSTRAINT `data_header_id_foreign` FOREIGN KEY (`header_id`) REFERENCES `header` (`id`) )" - cur.execute(headerTable) - cur.execute(dataTable) - conn.commit() - - -def insertHeader(data, cur, conn): - query = "INSERT INTO `header` ( \ - datetime, commit, commit_msg, branch_name, bss_size, text_size, \ - rodata_size, data_size, free_flash_size, pullrequest_id, pullrequest_name) \ - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" - cur.execute(query, data) - conn.commit() - return cur.lastrowid - - -def parseFile(fileObj, headerID): - arr = [] - fileLines = fileObj.readlines() - for line in fileLines: - lineArr = [] - tempLineArr = line.split("\t") - lineArr.append(headerID) - lineArr.append(tempLineArr[0]) # section - lineArr.append(int(tempLineArr[2], 16)) # address hex - lineArr.append(int(tempLineArr[3])) # size - lineArr.append(tempLineArr[4]) # name - lineArr.append(tempLineArr[5]) # lib - lineArr.append(tempLineArr[6]) # obj_name - arr.append(tuple(lineArr)) - return arr - - -def insertData(data, cur, conn): - query = "INSERT INTO `data` ( \ - header_id, section, address, size, \ - name, lib, obj_name) \ - VALUES (?, ?, ?, ?, ? ,?, ?)" - cur.executemany(query, data) - conn.commit() - - -def main(): - args = parseArgs() - dbConn = mariadbConnect(args) - reportFile = open(args.report_file) - dbCurs = dbConn.cursor() - createTables(dbCurs, dbConn) - headerID = insertHeader(parseEnv(), dbCurs, dbConn) - insertData(parseFile(reportFile, headerID), dbCurs, dbConn) - reportFile.close() - dbCurs.close() - - -if __name__ == "__main__": - main() From 4e347b207ca8136b69a76d18c1ab01fec13ca2ee Mon Sep 17 00:00:00 2001 From: Max Andreev Date: Tue, 27 Dec 2022 10:35:57 +0300 Subject: [PATCH 317/824] Fix amap on forks (#2200) --- .github/workflows/build.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9ca60aee6cb..4f7233e4663 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -87,12 +87,14 @@ jobs: cp build/core2_firmware.tgz "artifacts/flipper-z-any-core2_firmware-${SUFFIX}.tgz" - name: 'Copy map analyser files' + if: ${{ !github.event.pull_request.head.repo.fork }} run: | cp build/f7-firmware-*/firmware.elf.map map_analyser_files/firmware.elf.map cp build/f7-firmware-*/firmware.elf map_analyser_files/firmware.elf cp ${{ github.event_path }} map_analyser_files/event.json - name: 'Upload map analyser files to storage' + if: ${{ !github.event.pull_request.head.repo.fork }} uses: keithweaver/aws-s3-github-action@v1.0.0 with: source: map_analyser_files/ @@ -103,6 +105,7 @@ jobs: flags: --recursive - name: 'Trigger map file reporter' + if: ${{ !github.event.pull_request.head.repo.fork }} uses: peter-evans/repository-dispatch@v2 with: repository: flipperdevices/flipper-map-reporter From 8a279758fd9334adbc18dc1dcaca7485e66ed13d Mon Sep 17 00:00:00 2001 From: Daniel Carvallo Date: Tue, 27 Dec 2022 01:55:25 -0600 Subject: [PATCH 318/824] Fix quoted error for macOS bad-usb (#2155) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add stderr redirect to null device * Remove stderr redirect and replace <`> with <'> Co-authored-by: あく --- assets/resources/badusb/demo_macos.txt | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/assets/resources/badusb/demo_macos.txt b/assets/resources/badusb/demo_macos.txt index 6da305905a2..3c21a4df81a 100644 --- a/assets/resources/badusb/demo_macos.txt +++ b/assets/resources/badusb/demo_macos.txt @@ -34,43 +34,43 @@ ENTER STRING _.-------.._ -, ENTER HOME -STRING .-"```"--..,,_/ /`-, -, \ +STRING .-"'''"--..,,_/ /'-, -, \ ENTER HOME -STRING .:" /:/ /'\ \ ,_..., `. | | +STRING .:" /:/ /'\ \ ,_..., '. | | ENTER HOME -STRING / ,----/:/ /`\ _\~`_-"` _; +STRING / ,----/:/ /'\ _\~'_-"' _; ENTER HOME -STRING ' / /`"""'\ \ \.~`_-' ,-"'/ +STRING ' / /'"""'\ \ \.~'_-' ,-"'/ ENTER HOME -STRING | | | 0 | | .-' ,/` / +STRING | | | 0 | | .-' ,/' / ENTER HOME -STRING | ,..\ \ ,.-"` ,/` / +STRING | ,..\ \ ,.-"' ,/' / ENTER HOME -STRING ; : `/`""\` ,/--==,/-----, +STRING ; : '/'""\' ,/--==,/-----, ENTER HOME -STRING | `-...| -.___-Z:_______J...---; +STRING | '-...| -.___-Z:_______J...---; ENTER HOME -STRING : ` _-' +STRING : ' _-' ENTER HOME -STRING _L_ _ ___ ___ ___ ___ ____--"` +STRING _L_ _ ___ ___ ___ ___ ____--"' ENTER HOME -STRING | __|| | |_ _|| _ \| _ \| __|| _ \ +STRING | __|| | |_ _|| _ \| _ \| __|| _ \ ENTER HOME -STRING | _| | |__ | | | _/| _/| _| | / +STRING | _| | |__ | | | _/| _/| _| | / ENTER HOME -STRING |_| |____||___||_| |_| |___||_|_\ +STRING |_| |____||___||_| |_| |___||_|_\ ENTER HOME ENTER From f43b76efc21bd7cb248d7dc14d232fb6b2617a95 Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Tue, 27 Dec 2022 11:03:56 +0300 Subject: [PATCH 319/824] [FL-3021] USB/BLE HID Remote icon fix (#2179) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- .../hid_app/assets/Ble_disconnected_15x15.png | Bin 3632 -> 657 bytes applications/plugins/hid_app/hid.c | 14 +++++++++----- .../plugins/hid_app/views/hid_keyboard.c | 9 ++++++++- .../plugins/hid_app/views/hid_keynote.c | 15 +++++++++++---- .../plugins/hid_app/views/hid_media.c | 15 +++++++++++---- .../plugins/hid_app/views/hid_mouse.c | 15 +++++++++++---- .../plugins/hid_app/views/hid_mouse_jiggler.c | 18 ++++++++++++++---- .../plugins/hid_app/views/hid_tiktok.c | 15 +++++++++++---- 8 files changed, 75 insertions(+), 26 deletions(-) diff --git a/applications/plugins/hid_app/assets/Ble_disconnected_15x15.png b/applications/plugins/hid_app/assets/Ble_disconnected_15x15.png index 0858bb93f43d7cfc81e3f6f3b7540bd1a5e17ebf..bd54646d891976d838ecdf1acb1d05ee1f3ba40c 100644 GIT binary patch delta 392 zcmdlWGm&+IvK<3kfKQ0)|2hUBV0ZLc3#5utBRtc5eHpZXYz_t{MneWBAWIR5)!H)| zSb#Jm5PJYI!{kIB1rs36ynvB`8AvmNq!uv274a`%MzBE|T2IA$1C^Cbp3Nma`3;wj zaD;Dwf^&XRs)CuGfu4bq9hZW_ zN&}yC+C4=^_C}p+7pp(c*RPG+WFRMY!+)9eNuJG762JFz%im^jo_*=zKTt?`y85}S Ib4q9e01jJVn*aa+ delta 3367 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 O;LMDyQB?@n$o~M7G41{U diff --git a/applications/plugins/hid_app/hid.c b/applications/plugins/hid_app/hid.c index 1d2235e0842..7f63f0cc6ed 100644 --- a/applications/plugins/hid_app/hid.c +++ b/applications/plugins/hid_app/hid.c @@ -42,10 +42,12 @@ static void bt_hid_connection_status_changed_callback(BtStatus status, void* con furi_assert(context); Hid* hid = context; bool connected = (status == BtStatusConnected); - if(connected) { - notification_internal_message(hid->notifications, &sequence_set_blue_255); - } else { - notification_internal_message(hid->notifications, &sequence_reset_blue); + if(hid->transport == HidTransportBle) { + if(connected) { + notification_internal_message(hid->notifications, &sequence_set_blue_255); + } else { + notification_internal_message(hid->notifications, &sequence_reset_blue); + } } hid_keynote_set_connected_status(hid->hid_keynote, connected); hid_keyboard_set_connected_status(hid->hid_keyboard, connected); @@ -186,7 +188,9 @@ void hid_free(Hid* app) { furi_assert(app); // Reset notification - notification_internal_message(app->notifications, &sequence_reset_blue); + if(app->transport == HidTransportBle) { + notification_internal_message(app->notifications, &sequence_reset_blue); + } // Free views view_dispatcher_remove_view(app->view_dispatcher, HidViewSubmenu); diff --git a/applications/plugins/hid_app/views/hid_keyboard.c b/applications/plugins/hid_app/views/hid_keyboard.c index 3e3b63284d2..8b12e8fd146 100644 --- a/applications/plugins/hid_app/views/hid_keyboard.c +++ b/applications/plugins/hid_app/views/hid_keyboard.c @@ -25,6 +25,7 @@ typedef struct { bool back_pressed; bool connected; char key_string[5]; + HidTransport transport; } HidKeyboardModel; typedef struct { @@ -207,7 +208,7 @@ static void hid_keyboard_draw_callback(Canvas* canvas, void* context) { HidKeyboardModel* model = context; // Header - if(!model->connected) { + if((!model->connected) && (model->transport == HidTransportBle)) { canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); canvas_set_font(canvas, FontPrimary); elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Keyboard"); @@ -361,6 +362,12 @@ HidKeyboard* hid_keyboard_alloc(Hid* bt_hid) { view_set_draw_callback(hid_keyboard->view, hid_keyboard_draw_callback); view_set_input_callback(hid_keyboard->view, hid_keyboard_input_callback); + with_view_model( + hid_keyboard->view, + HidKeyboardModel * model, + { model->transport = bt_hid->transport; }, + true); + return hid_keyboard; } diff --git a/applications/plugins/hid_app/views/hid_keynote.c b/applications/plugins/hid_app/views/hid_keynote.c index c95f427803d..5e5eeb79094 100644 --- a/applications/plugins/hid_app/views/hid_keynote.c +++ b/applications/plugins/hid_app/views/hid_keynote.c @@ -19,6 +19,7 @@ typedef struct { bool ok_pressed; bool back_pressed; bool connected; + HidTransport transport; } HidKeynoteModel; static void hid_keynote_draw_arrow(Canvas* canvas, uint8_t x, uint8_t y, CanvasDirection dir) { @@ -39,11 +40,14 @@ static void hid_keynote_draw_callback(Canvas* canvas, void* context) { HidKeynoteModel* model = context; // Header - if(model->connected) { - canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); - } else { - canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); + if(model->transport == HidTransportBle) { + if(model->connected) { + canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); + } else { + canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); + } } + canvas_set_font(canvas, FontPrimary); elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Keynote"); @@ -186,6 +190,9 @@ HidKeynote* hid_keynote_alloc(Hid* hid) { view_set_draw_callback(hid_keynote->view, hid_keynote_draw_callback); view_set_input_callback(hid_keynote->view, hid_keynote_input_callback); + with_view_model( + hid_keynote->view, HidKeynoteModel * model, { model->transport = hid->transport; }, true); + return hid_keynote; } diff --git a/applications/plugins/hid_app/views/hid_media.c b/applications/plugins/hid_app/views/hid_media.c index 5d764f73ac0..468529d56a8 100644 --- a/applications/plugins/hid_app/views/hid_media.c +++ b/applications/plugins/hid_app/views/hid_media.c @@ -21,6 +21,7 @@ typedef struct { bool down_pressed; bool ok_pressed; bool connected; + HidTransport transport; } HidMediaModel; static void hid_media_draw_arrow(Canvas* canvas, uint8_t x, uint8_t y, CanvasDirection dir) { @@ -41,11 +42,14 @@ static void hid_media_draw_callback(Canvas* canvas, void* context) { HidMediaModel* model = context; // Header - if(model->connected) { - canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); - } else { - canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); + if(model->transport == HidTransportBle) { + if(model->connected) { + canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); + } else { + canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); + } } + canvas_set_font(canvas, FontPrimary); elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Media"); canvas_set_font(canvas, FontSecondary); @@ -190,6 +194,9 @@ HidMedia* hid_media_alloc(Hid* hid) { view_set_draw_callback(hid_media->view, hid_media_draw_callback); view_set_input_callback(hid_media->view, hid_media_input_callback); + with_view_model( + hid_media->view, HidMediaModel * model, { model->transport = hid->transport; }, true); + return hid_media; } diff --git a/applications/plugins/hid_app/views/hid_mouse.c b/applications/plugins/hid_app/views/hid_mouse.c index d1d76c15ab4..30a9d9d0635 100644 --- a/applications/plugins/hid_app/views/hid_mouse.c +++ b/applications/plugins/hid_app/views/hid_mouse.c @@ -20,6 +20,7 @@ typedef struct { bool left_mouse_held; bool right_mouse_pressed; bool connected; + HidTransport transport; } HidMouseModel; static void hid_mouse_draw_callback(Canvas* canvas, void* context) { @@ -27,11 +28,14 @@ static void hid_mouse_draw_callback(Canvas* canvas, void* context) { HidMouseModel* model = context; // Header - if(model->connected) { - canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); - } else { - canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); + if(model->transport == HidTransportBle) { + if(model->connected) { + canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); + } else { + canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); + } } + canvas_set_font(canvas, FontPrimary); elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Mouse"); canvas_set_font(canvas, FontSecondary); @@ -198,6 +202,9 @@ HidMouse* hid_mouse_alloc(Hid* hid) { view_set_draw_callback(hid_mouse->view, hid_mouse_draw_callback); view_set_input_callback(hid_mouse->view, hid_mouse_input_callback); + with_view_model( + hid_mouse->view, HidMouseModel * model, { model->transport = hid->transport; }, true); + return hid_mouse; } diff --git a/applications/plugins/hid_app/views/hid_mouse_jiggler.c b/applications/plugins/hid_app/views/hid_mouse_jiggler.c index a2b07c7a18a..d8f1f892867 100644 --- a/applications/plugins/hid_app/views/hid_mouse_jiggler.c +++ b/applications/plugins/hid_app/views/hid_mouse_jiggler.c @@ -16,6 +16,7 @@ typedef struct { bool connected; bool running; uint8_t counter; + HidTransport transport; } HidMouseJigglerModel; static void hid_mouse_jiggler_draw_callback(Canvas* canvas, void* context) { @@ -23,11 +24,14 @@ static void hid_mouse_jiggler_draw_callback(Canvas* canvas, void* context) { HidMouseJigglerModel* model = context; // Header - if(model->connected) { - canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); - } else { - canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); + if(model->transport == HidTransportBle) { + if(model->connected) { + canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); + } else { + canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); + } } + canvas_set_font(canvas, FontPrimary); elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Mouse Jiggler"); @@ -120,6 +124,12 @@ HidMouseJiggler* hid_mouse_jiggler_alloc(Hid* hid) { hid_mouse_jiggler->timer = furi_timer_alloc( hid_mouse_jiggler_timer_callback, FuriTimerTypePeriodic, hid_mouse_jiggler); + with_view_model( + hid_mouse_jiggler->view, + HidMouseJigglerModel * model, + { model->transport = hid->transport; }, + true); + return hid_mouse_jiggler; } diff --git a/applications/plugins/hid_app/views/hid_tiktok.c b/applications/plugins/hid_app/views/hid_tiktok.c index 42abf41489b..e1f9f4bed4c 100644 --- a/applications/plugins/hid_app/views/hid_tiktok.c +++ b/applications/plugins/hid_app/views/hid_tiktok.c @@ -19,6 +19,7 @@ typedef struct { bool ok_pressed; bool connected; bool is_cursor_set; + HidTransport transport; } HidTikTokModel; static void hid_tiktok_draw_callback(Canvas* canvas, void* context) { @@ -26,11 +27,14 @@ static void hid_tiktok_draw_callback(Canvas* canvas, void* context) { HidTikTokModel* model = context; // Header - if(model->connected) { - canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); - } else { - canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); + if(model->transport == HidTransportBle) { + if(model->connected) { + canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); + } else { + canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); + } } + canvas_set_font(canvas, FontPrimary); elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "TikTok"); canvas_set_font(canvas, FontSecondary); @@ -207,6 +211,9 @@ HidTikTok* hid_tiktok_alloc(Hid* bt_hid) { view_set_draw_callback(hid_tiktok->view, hid_tiktok_draw_callback); view_set_input_callback(hid_tiktok->view, hid_tiktok_input_callback); + with_view_model( + hid_tiktok->view, HidTikTokModel * model, { model->transport = bt_hid->transport; }, true); + return hid_tiktok; } From 1390f10a6fc53aabd128820e563fb98fe6c8079f Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Tue, 27 Dec 2022 12:29:21 +0400 Subject: [PATCH 320/824] [FL-3068] SubGhz: add Holtek_ht12x protocol (#2187) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * SubGhz: add Holtek_ht12x protocol * SubGhz: add unit_test holtek_ht12x * SubGhz: correct string formatting Co-authored-by: あく --- .../debug/unit_tests/subghz/subghz_test.c | 17 +- assets/unit_tests/subghz/holtek_ht12x.sub | 8 + assets/unit_tests/subghz/holtek_ht12x_raw.sub | 10 + assets/unit_tests/subghz/test_random_raw.sub | 5 + lib/subghz/protocols/holtek_ht12x.c | 400 ++++++++++++++++++ lib/subghz/protocols/holtek_ht12x.h | 107 +++++ lib/subghz/protocols/protocol_items.c | 1 + lib/subghz/protocols/protocol_items.h | 1 + 8 files changed, 548 insertions(+), 1 deletion(-) create mode 100644 assets/unit_tests/subghz/holtek_ht12x.sub create mode 100644 assets/unit_tests/subghz/holtek_ht12x_raw.sub create mode 100644 lib/subghz/protocols/holtek_ht12x.c create mode 100644 lib/subghz/protocols/holtek_ht12x.h diff --git a/applications/debug/unit_tests/subghz/subghz_test.c b/applications/debug/unit_tests/subghz/subghz_test.c index cb89e1f02ca..1dee1d59ecd 100644 --- a/applications/debug/unit_tests/subghz/subghz_test.c +++ b/applications/debug/unit_tests/subghz/subghz_test.c @@ -13,7 +13,7 @@ #define CAME_ATOMO_DIR_NAME EXT_PATH("subghz/assets/came_atomo") #define NICE_FLOR_S_DIR_NAME EXT_PATH("subghz/assets/nice_flor_s") #define TEST_RANDOM_DIR_NAME EXT_PATH("unit_tests/subghz/test_random_raw.sub") -#define TEST_RANDOM_COUNT_PARSE 253 +#define TEST_RANDOM_COUNT_PARSE 273 #define TEST_TIMEOUT 10000 static SubGhzEnvironment* environment_handler; @@ -597,6 +597,13 @@ MU_TEST(subghz_decoder_smc5326_test) { "Test decoder " SUBGHZ_PROTOCOL_SMC5326_NAME " error\r\n"); } +MU_TEST(subghz_decoder_holtek_ht12x_test) { + mu_assert( + subghz_decoder_test( + EXT_PATH("unit_tests/subghz/holtek_ht12x_raw.sub"), SUBGHZ_PROTOCOL_HOLTEK_HT12X_NAME), + "Test decoder " SUBGHZ_PROTOCOL_HOLTEK_HT12X_NAME " error\r\n"); +} + //test encoders MU_TEST(subghz_encoder_princeton_test) { mu_assert( @@ -730,6 +737,12 @@ MU_TEST(subghz_encoder_smc5326_test) { "Test encoder " SUBGHZ_PROTOCOL_SMC5326_NAME " error\r\n"); } +MU_TEST(subghz_encoder_holtek_ht12x_test) { + mu_assert( + subghz_encoder_test(EXT_PATH("unit_tests/subghz/holtek_ht12x.sub")), + "Test encoder " SUBGHZ_PROTOCOL_HOLTEK_HT12X_NAME " error\r\n"); +} + MU_TEST(subghz_random_test) { mu_assert(subghz_decode_random_test(TEST_RANDOM_DIR_NAME), "Random test error\r\n"); } @@ -774,6 +787,7 @@ MU_TEST_SUITE(subghz) { MU_RUN_TEST(subghz_decoder_clemsa_test); MU_RUN_TEST(subghz_decoder_ansonic_test); MU_RUN_TEST(subghz_decoder_smc5326_test); + MU_RUN_TEST(subghz_decoder_holtek_ht12x_test); MU_RUN_TEST(subghz_encoder_princeton_test); MU_RUN_TEST(subghz_encoder_came_test); @@ -797,6 +811,7 @@ MU_TEST_SUITE(subghz) { MU_RUN_TEST(subghz_encoder_clemsa_test); MU_RUN_TEST(subghz_encoder_ansonic_test); MU_RUN_TEST(subghz_encoder_smc5326_test); + MU_RUN_TEST(subghz_encoder_holtek_ht12x_test); MU_RUN_TEST(subghz_random_test); subghz_test_deinit(); diff --git a/assets/unit_tests/subghz/holtek_ht12x.sub b/assets/unit_tests/subghz/holtek_ht12x.sub new file mode 100644 index 00000000000..09e20b13423 --- /dev/null +++ b/assets/unit_tests/subghz/holtek_ht12x.sub @@ -0,0 +1,8 @@ +Filetype: Flipper SubGhz Key File +Version: 1 +Frequency: 433920000 +Preset: FuriHalSubGhzPresetOok650Async +Protocol: Holtek_HT12X +Bit: 12 +Key: 00 00 00 00 00 00 0F FB +TE: 205 diff --git a/assets/unit_tests/subghz/holtek_ht12x_raw.sub b/assets/unit_tests/subghz/holtek_ht12x_raw.sub new file mode 100644 index 00000000000..1aeedac38b7 --- /dev/null +++ b/assets/unit_tests/subghz/holtek_ht12x_raw.sub @@ -0,0 +1,10 @@ +Filetype: Flipper SubGhz RAW File +Version: 1 +Frequency: 433920000 +Preset: FuriHalSubGhzPresetOok650Async +Protocol: RAW +RAW_Data: 97 -264 65 -1890 231 -366 165 -232 99 -166 297 -68 401 -100 97 -100 759 -264 823 -132 919 -98 65 -230 367 -98 1055 -52 51 -104 101 -126 173 -148 151 -100 153 -222 77 -288 53 -280 331 -212 81 -108 246 -156 267 -268 183 -1260 53 -622 51 -468 183 -264 105 -990 77 -421 75 -52 53 -214 185 -504 75 -563 131 -338 165 -82 133 -80 53 -236 231 -208 282 -836 187 -406 81 -102 181 -54 107 -415 81 -54 109 -298 55 -406 323 -136 299 -136 437 -52 133 -294 53 -110 133 -420 333 -402 269 -574 79 -270 163 -594 124 -52 51 -320 79 -324 109 -78 105 -236 79 -292 53 -432 181 -110 187 -268 107 -78 79 -188 79 -78 75 -130 75 -128 103 -628 79 -322 79 -136 81 -196 85 -296 51 -349 159 -325 131 -80 131 -80 402 -110 55 -194 109 -108 53 -108 135 -224 81 -162 57 -228 161 -357 105 -54 103 -347 279 -294 422 -152 51 -104 99 -146 147 -205 77 -216 107 -52 181 -52 107 -158 105 -52 133 -134 139 -418 291 -102 103 -106 105 -584 103 -540 53 -52 153 -208 227 -128 307 -184 105 -328 109 -862 129 -196 55 -244 51 -1501 55 -82 81 -326 79 -500 103 -406 207 -130 75 -126 155 -202 53 -376 135 -158 53 -428 107 -467 167 -138 81 -106 79 -346 157 -84 143 -144 85 -136 53 -308 189 -248 79 -711 109 -592 107 -220 107 -104 77 -583 131 -158 53 -110 135 -191 107 -748 105 -478 77 -398 107 -138 83 -410 127 -477 103 -162 119 -72 103 -442 129 -237 107 -664 175 -74 147 -406 53 -78 127 -178 51 -102 101 -259 125 -202 257 -210 77 -52 77 -130 251 -230 77 -76 77 -178 155 -98 97 -100 153 -54 185 -82 79 -82 135 -398 241 -130 77 -76 311 -210 53 -238 181 -404 241 -352 189 -270 265 -164 215 -998 157 -82 77 -106 51 -54 75 -100 328 -178 103 -224 253 -341 147 -334 446 -76 125 -100 75 -126 173 -690 75 -250 51 -554 81 -300 53 -188 53 -106 265 -1024 105 -646 221 -82 191 -362 75 -234 131 -128 83 -224 55 -240 77 -376 157 -78 187 -358 389 -76 103 -128 199 -513 83 -492 129 -618 123 -52 129 -52 79 -376 101 -688 105 -110 55 -84 179 -152 51 -486 358 -276 99 -74 73 -679 51 -210 113 -196 365 -104 223 -104 53 -430 55 -140 55 -84 153 -202 181 -358 149 -820 103 -80 215 -108 309 -368 105 -538 154 -732 233 -588 53 -253 155 -288 55 -136 55715 -102 407 -100 259 -78 157 -130 206 -52 258 -78 441 -104 77 -80 492 -104 182 -104 79 -155 +RAW_Data: 287 -78 807 -104 1588 -52 2952 -78 1018 -240 341 -262 361 -258 335 -280 309 -100 509 -280 307 -302 307 -6968 321 -290 293 -298 323 -294 289 -314 285 -334 285 -330 257 -330 281 -328 293 -310 281 -124 479 -330 281 -326 267 -6994 295 -324 267 -320 287 -340 259 -332 257 -358 257 -354 257 -328 257 -352 257 -352 255 -150 455 -360 255 -326 255 -7024 253 -352 255 -352 255 -354 267 -326 269 -352 239 -346 261 -362 233 -358 257 -356 255 -152 455 -334 255 -354 255 -7006 267 -342 267 -352 241 -350 263 -342 259 -362 233 -356 257 -356 231 -378 231 -380 231 -150 455 -358 231 -378 229 -7050 229 -378 229 -352 253 -352 239 -372 239 -380 243 -348 235 -368 231 -388 231 -356 233 -176 431 -384 229 -380 231 -7028 239 -368 241 -372 241 -350 239 -372 233 -366 233 -384 231 -358 231 -380 231 -378 231 -176 429 -384 205 -402 205 -7064 213 -380 243 -378 215 -374 233 -368 233 -386 231 -382 205 -382 231 -380 229 -380 231 -176 429 -386 203 -378 231 -7062 229 -378 229 -378 229 -378 229 -378 213 -396 203 -404 203 -404 203 -404 203 -404 203 -198 431 -376 215 -376 239 -7064 235 -364 231 -386 231 -384 205 -408 205 -380 231 -380 231 -380 231 -378 207 -404 205 -200 431 -384 205 -404 205 -7118 205 -408 205 -406 205 -408 205 -408 205 -408 205 -436 179 -440 283 -460 75 -1486 105 -54 103 -784 107 -218 113 -824 125 -2140 85 -583 51 -1291 53 -722 113 -622 107 -1176 51 -52 75 -152 113 -56 111 -784 105 -436 81 -112 85 -272 53 -406 81 -1345 105 -3008 53 -1418 105 -254 107 -874 131 -818 53 -595 77 -130 123 -1088 213 -724 131 -793 99 -428 75 -288 51 -334 101 -1471 81 -274 53 -164 107 -2618 129 -532 55 -366 297 -160 83 -166 184 -208 57 -196 328 -1640 235 -1743 79 -594 51 -76 77 -270 163 -728 51 -408 55 -354 101 -126 153 -184 133 -392 53 -604 77 -804 127 -965 51 -78 103 -668 262 -210 237 -439 75 -340 77 -106 105 -1775 79 -284 51 -3100 157 -433 55 -1355 107 -198 165 -82 79 -770 79 -1587 81 -638 79 -530 103 -703 51 -396 71 -956 151 -248 53 -553 103 -154 75 -806 75 -660 53 -698 127 -1210 53 -3175 79 -608 245 -1590 207 -164 107 -232 51 -1094 99 -695 135 -955 53 -804 217 -587 55 -1452 163 -1232 79 -968 79 -720 81 -1110 129 -1194 105 -1736 79 -386 79 -184 77 -652 75 -2442 103 -555 129 -125 75 -1115 81 -108 135 -980 53 -808 105 -1001 155 -1068 131 -850 99 -268 51 -1106 159 -2408 79 -612 51 -1191 +RAW_Data: 135 -212 77 -2816 137 -980 51 -1921 79 -1554 53 -906 83 -1042 99 -5161 51 -290 75 -558 51 -260 77 -738 99 -2128 105 -1384 53 -108 79 -188 55 -830 77 -5690 109 -2652 51 -1374 53 -2469 79 -2163 203 -152 71 -1459 79 -2741 105 -262 75 -1194 75 -228 153 -210 107 -3236 107 -1500 77 -3562 79 -170 55 -540 53 -1270 51 -304 51 -2071 137 -134 105 -266 101 -834 51 -8117 73 -3292 81 -732 181 -1539 53 -132 109 -1375 77 -132 81 -1061 77 -1620 79 -1373 105 -643 183 -190 53 -620 77 -1633 55 -931 51 -398 135 -924 111 -1122 51 -128 125 -2361 97 -2571 51 -3563 51 -794 77 -844 81 -788 81 -2040 77 -232 77 -1000 131 -322 73 -848 55 -336 107 -1546 81 -602 79 -954 107 -1824 73 -1131 240 -1048 197 -2442 73 -720 127 -78 77 -574 79 -624 101 -5754 103 -1829 149 -719 77 -982 53 -250 53 -634 161 -2201 75 -3483 149 -356 73 -98 123 -176 75 -702 231 -1633 121 -704 53 -1163 51 -1227 73 -368 123 -258 103 -530 279 -628 53 -884 55 -82 101 -102 135 -162 107 -1264 159 -3370 77 -3538 131 -130 81 -2460 129 -588 51 -514 77 -376 53 -616 53 -956 51 -1508 53 -1044 53 -1160 261 -442 53 -640 83 -300 79 -1723 75 -124 125 -532 103 -500 79 -764 53 -1220 279 -458 77 -548 159 -450 223 -718 55 -716 51 -716 203 -732 79 -6974 53 -1086 51 -1794 79 -882 53 -4221 53 -1070 77 -1126 75 -2307 155 -1233 131 -156 111 -1352 51 -478 51 -154 75 -724 51 -635 103 -652 77 -294 75 -268 81 -1046 177 -1778 51 -298 109 -336 77 -80 53 -192 133 -556 99 -394 81 -654 51 -106 79 -1893 55 -240 55 -136 55 -2680 77 -136 81 -164 53 -576 135 -3304 131 -654 103 -788 53 -224 187 -354 79 -1872 81 -290 207 -410 77 -1132 55 -112 57 -512 51 -2736 123 -404 239 -1283 51 -1526 213 -720 133 -354 109 -396 77 -1518 105 -632 53 -164 53 -1090 55 -730 79 -1705 75 -370 75 -378 79 -2022 81 -2480 103 -1108 79 -106 53 -1784 105 -512 219 -669 179 -795 105 -376 105 -514 53 -402 53 -282 83 -554 133 -448 105 -972 79 -490 81 -1184 77 -216 109 -132 51 -164 135 -1570 51 -1329 79 -1218 53 -2450 75 -1246 51 -3825 77 -1114 53 -1718 51 -846 79 -826 165 -306 75 -3821 51 -606 55 -880 107 -56 55 -5505 53 -504 79 -1356 77 -569 51 -2268 149 -358 53 -1979 51 -106 103 -176 221 -508 51 -110 53 -3896 127 -234 79 -1012 79 -84 243 -2860 53 -1010 73 -286 133 -2198 151 -370 129 -1942 101 -154 109 -702 79 -696 51 -302 +RAW_Data: 51 -744 83 -496 79 -672 77 -170 111 -264 103 -472 51 -524 75 -1172 51 -240 51963 -254 333 -278 333 -276 333 -278 305 -302 307 -76 531 -280 331 -276 331 -6938 347 -268 319 -272 319 -292 311 -312 283 -306 309 -304 283 -328 293 -310 281 -326 293 -110 479 -322 295 -322 291 -6964 283 -334 283 -304 283 -328 283 -328 281 -328 255 -354 265 -338 255 -352 255 -352 255 -124 481 -338 277 -328 279 -6984 269 -350 265 -320 287 -340 257 -332 257 -358 255 -356 255 -328 257 -354 257 -352 255 -150 455 -360 229 -376 231 -7040 239 -348 267 -350 239 -346 261 -364 231 -360 257 -356 231 -380 231 -378 231 -352 257 -150 453 -360 229 -378 229 -7032 239 -372 227 -378 227 -380 241 -352 241 -374 233 -366 233 -384 233 -356 231 -380 231 -176 431 -384 229 -354 255 -7030 231 -376 231 -376 229 -378 229 -378 229 -352 255 -352 255 -352 229 -378 229 -378 229 -174 427 -384 229 -376 241 -7040 241 -350 235 -368 233 -388 231 -358 233 -380 231 -380 229 -380 231 -380 229 -380 211 -192 421 -366 227 -380 227 -7068 239 -378 215 -402 209 -394 207 -388 231 -386 231 -384 205 -408 205 -408 205 -408 205 -202 409 -412 207 -1400 105 -1002 51 -9495 53 -250 57 -1093 155 -124 73 -344 75 -2759 83 -468 53 -738 77 -134 53 -1581 51 -106 127 -1209 121 -956 51 -918 83 -276 85 -1696 125 -618 81 -1666 51 -152 101 -1324 107 -54 141 -586 75 -784 55 -1828 51 -4052 81 -480 53 -218 141 -1346 105 -1152 127 -776 53 -426 135 -390 105 -939 81 -887 71 -492 107 -1311 105 -1844 101 -1340 77 -2586 51 -2637 51 -1626 105 -54 53 -1672 151 -2830 57 -3143 51 -1859 79 -929 179 -78 77 -890 73 -894 79 -80 79 -1184 53 -323 53 -1344 79 -636 53 -1808 55 -3048 79 -2287 53 -572 51 -822 51 -608 77 -1772 75 -2521 79 -162 81 -664 163 -110 83 -524 53 -930 53 -1816 79 -1305 51 -816 53 -1358 55 -822 55 -594 81 -2230 55 -234 77 -600 201 -3174 151 -2534 71 -122 51 -1370 81 -3130 127 -236 79 -728 101 -1472 53 -800 127 -528 51 -802 77 -52 99 -3144 77 -6346 51 -1090 81 -588 79 -292 169 -2345 107 -370 187 -1218 81 -296 75 -696 51 -516 77 -2154 75 -558 75 -816 103 -2200 125 -2766 229 -376 151 -3375 79 -1466 53 -535 167 -524 217 -54 131 -3408 51 -54 109 -1886 77 -732 83 -536 99 -3128 103 -168 57 -1852 51 -574 79 -296 155 -844 99 -767 147 -2406 99 -1014 81 -4460 175 -226 195 -454 73 -2236 53 -818 155 -352 83 -752 79 -5083 51 -1716 +RAW_Data: 77 -1925 51 -1760 81 -162 53 -1469 79 -362 53 -471 103 -750 53 -562 127 -238 79 -250 107 -52 53 -210 83 -504 79 -1566 107 -82 79 -968 53 -458 131 -1944 301 -594 79 -382 51 -1196 79 -3963 183 -670 111 -360 105 -1990 53 -1084 79 -658 73 -301 73 -264 75 -1335 79 -1110 81 -1856 159 -3986 75 -1262 53 -1189 53 -158 53 -903 81 -1135 133 -350 107 -178 151 -1096 73 -2734 81 -1280 107 -950 79 -52 53 -5912 53 -1653 111 -1601 79 -742 51 -1104 55 -1254 51 -867 53 -136 109 -813 79 -1082 53 -395 53 -54 109 -504 79 -218 109 -114 57 -1620 157 -3003 79 -656 53 -510 209 -1933 107 -1197 159 -5508 79 -1164 77 -1466 77 -742 101 -124 71 -2049 85 -144 109 -1304 105 -2310 55 -381 83 -114 113 -944 103 -184 83 -558 55 -2064 109 -760 75 -1036 77 -574 51 -134 131 -224 57 -104 53 -440 53 -1262 183 -2454 51 -1966 73 -1950 125 -1095 51 -480 121 -1994 57 -1930 103 -786 79 -2272 105 -3312 51 -746 127 -144 133 -1608 77 -692 51 -1136 53 -164 55 -573 55 -3110 53 -1558 105 -6248 53 -1051 111 -886 105 -2234 103 -106 53 -1256 101 -1446 111 -974 79 -851 81 -136 193 -3392 83 -582 103 -1197 111 -196 55 -906 51 -742 77 -2038 71 -686 53 -1943 51 -134 51 -852 51 -1658 133 -2050 161 -388 77 -326 81 -412 55 -1137 81 -3256 55 -1516 53 -1414 117 -372 51 -1144 199 -3087 51 -430 75 -1856 151 -128 95 -192 398 -1207 77 -280 51 -2716 51 -808 53 -78 77 -1524 109 -54 79 -410 79 -132 53 -770 109 -2066 185 -368 131 -2102 125 -1037 75 -780 127 -128 51 -176 53 -1982 77 -140 111 -1046 109 -3166 169 -1956 77 -4040 79 -2778 73 -204 129 -1546 82945 -150 359 -252 333 -76 533 -280 319 -286 305 -6960 319 -300 323 -270 317 -290 313 -308 283 -306 309 -304 283 -328 291 -310 281 -326 281 -124 481 -334 279 -302 305 -6970 293 -318 301 -304 279 -328 279 -328 277 -330 277 -330 293 -298 295 -320 287 -314 283 -128 489 -334 255 -330 281 -7012 265 -340 265 -344 251 -354 253 -354 269 -322 293 -322 263 -344 259 -336 257 -360 257 -152 457 -358 231 -354 257 -7060 237 -364 255 -354 237 -366 255 -354 255 -354 267 -352 241 -352 265 -348 261 -342 259 -154 439 -388 231 -358 255 -7114 237 -370 235 -394 233 -362 233 -386 233 -386 233 -386 233 -386 231 -388 233 -414 233 -180 441 -392 233 -1832 53 -564 53 -370 289 -867 201 -78 103 -352 213 -586 103 -1226 165 -112 55 -300 105 -975 107 -358 77 -410 55 -1777 51 -973 51 -828 diff --git a/assets/unit_tests/subghz/test_random_raw.sub b/assets/unit_tests/subghz/test_random_raw.sub index 900b26207a2..be635f04d97 100644 --- a/assets/unit_tests/subghz/test_random_raw.sub +++ b/assets/unit_tests/subghz/test_random_raw.sub @@ -168,3 +168,8 @@ RAW_Data: 285 -96 627 -362 53 -84 201 -374 113 -202 115 -202 421 -316 85 -58 139 RAW_Data: 535 -142 57 -58 55 -116 115 -432 85 -172 259 -192 167 -120 117 -72 119 -240 334 -72 71 -267 285 -144 119 -374 85 -88 85 -114 143 -202 229 -58 143 -202 115 -202 171 -86 71 -144 87 -56 173 -373 143 -116 113 -462 169 -80 215 -148 115 -336 85 -230 163 -432 85 -374 639 -174 85 -58 57 -82 295 -352 269 -532 414 -322 95 -287 263 -268 115 -56 259 -76 85 -282 401 -305 516 -114 115 -202 171 -86 451 -110 85 -346 201 -274 149 -202 85 -364 366 -258 57 -114 259 -172 142 -144 85 -116 85 -480 171 -144 57 -352 115 -116 535 -404 315 -202 163 -158 517 -316 215 -98 85 -346 85 -144 87 -86 257 -82 167 -58 85 -116 113 -894 233 -186 77 -266 147 -72 71 -82 57 -86 171 -58 57 -86 201 -364 143 -202 115 -114 85 -88 113 -86 87 -230 57 -76 613 -72 85 -96 209 -346 458 -58 547 -490 201 -315 315 -116 75 -168 359 -335 95 -384 93 -120 71 -312 251 -366 233 -96 189 -240 263 -192 271 -58 115 -58 229 -346 459 -174 113 -144 173 -144 218 -224 57 -116 215 -72 103 -202 513 -210 433 -116 113 -174 650 -273 147 -450 375 -86 115 -172 536 -84 85 -230 85 -58 195 -468 287 -110 551 -214 167 -311 213 -250 85 -58 85 -355 113 -230 115 -144 117 -288 195 -202 57 -376 123 -144 236 -168 553 -284 119 -72 143 -188 161 -120 93 -312 335 -58 55 -260 105 -244 143 -120 381 -268 173 -268 635 -168 453 -318 71 -167 71 -406 191 -172 215 -408 119 -144 93 -120 97 -130 143 -192 308 -122 147 -550 313 -96 139 -162 167 -96 431 -80 83 -112 201 -86 287 -86 229 -116 57 -288 113 -174 143 -116 113 -144 115 -518 57 -230 57 -172 231 -86 113 -314 183 -144 119 -72 165 -446 81 -86 135 -190 143 -96 71 -72 411 -96 143 -120 69 -216 349 -72 95 -96 517 -646 163 -86 113 -116 171 -116 143 -116 113 -287 259 -114 517 -168 141 -116 105 -72 95 -96 311 -118 159 -310 191 -54 143 -258 115 -450 219 -54 339 -372 239 -72 167 -174 113 -58 57 -144 259 -172 143 -336 113 -174 85 -230 83 -668 85 -202 113 -144 57 -116 373 -316 719 -288 115 -58 75 -120 139 -144 229 -144 57 -144 171 -192 391 -202 403 -58 315 -188 259 -56 115 -144 85 -404 57 -58 105 -102 429 -406 81 -172 57 -144 287 -230 287 -220 317 -458 283 -58 113 -86 269 -72 281 -58 85 -202 113 -52 421 -58 229 -480 259 -58 143 -660 155 -638 123 -86 57 -86 143 -346 143 -144 57 -144 RAW_Data: 2442 -312 275 -972 949 -310 941 -322 923 -342 921 -352 923 -334 281 -954 945 -350 279 -958 907 -354 289 -980 909 -352 281 -962 907 -330 311 -964 913 -350 317 -930 933 -344 921 -352 893 -330 311 -954 943 -318 315 -958 909 -324 947 -7854 953 -322 289 -948 939 -354 927 -332 911 -324 943 -344 917 -318 317 -964 905 -344 303 -942 947 -312 319 -960 913 -348 281 -958 941 -322 295 -978 905 -350 279 -962 931 -328 947 -324 939 -346 267 -964 935 -348 283 -938 953 -318 931 -7868 935 -346 269 -968 953 -310 941 -322 921 -330 935 -342 931 -318 311 -962 939 -290 337 -950 909 -352 317 -924 943 -324 313 -938 941 -318 317 -932 939 -344 301 -938 933 -350 921 -322 959 -310 301 -942 933 -352 317 -926 957 -314 919 -7868 943 -314 317 -958 909 -322 951 -344 919 -352 921 -324 937 -326 281 -964 941 -318 317 -930 939 -344 301 -938 933 -352 281 -962 953 -314 317 -922 933 -330 315 -954 943 -318 921 -342 943 -320 291 -980 909 -354 281 -962 943 -296 967 -7836 943 -332 309 -950 935 -318 929 -340 943 -320 921 -344 921 -354 283 -960 943 -296 309 -964 945 -318 279 -964 941 -322 333 -944 939 -314 279 -992 903 -342 319 -932 933 -330 931 -340 929 -348 281 -964 935 -334 281 -970 927 -346 921 -7862 951 -314 319 -922 953 -320 923 -346 921 -320 965 -298 943 -324 313 -942 941 -320 317 -930 941 -344 303 -940 945 -312 321 -940 953 -314 303 -960 933 -348 287 -962 911 -352 917 -350 905 -324 333 -918 971 -322 317 -924 945 -324 937 -7872 919 -324 317 -942 941 -318 933 -330 943 -324 943 -310 951 -318 317 -930 939 -344 301 -938 933 -352 317 -926 953 -314 319 -924 939 -324 331 -950 907 -354 315 -926 945 -324 939 -312 953 -318 317 -930 937 -344 301 -940 947 -348 909 -7864 949 -310 319 -956 915 -350 919 -348 905 -322 963 -296 935 -348 317 -922 951 -322 295 -976 939 -314 281 -996 915 -326 307 -940 959 -310 301 -966 935 -346 285 -958 915 -348 921 -348 903 -354 303 -948 911 -350 315 -926 945 -324 941 -7874 943 -290 319 -942 973 -318 929 -314 937 -328 941 -324 939 -310 303 -962 933 -352 285 -962 949 -314 319 -924 951 -320 293 -948 941 -354 283 -962 943 -294 309 -966 943 -320 931 -328 943 -326 311 -940 939 -320 309 -958 933 -338 943 -7840 933 -352 277 -964 941 -322 923 -344 923 -350 931 -310 955 -320 291 -974 907 -350 281 -958 963 -298 313 -956 945 -314 311 -960 937 -312 311 -966 909 -324 319 -944 941 -354 929 -298 945 -324 315 -940 RAW_Data: 943 -354 281 -964 905 -330 933 -7868 951 -324 315 -938 943 -354 893 -330 943 -324 943 -344 919 -318 317 -962 903 -344 301 -974 903 -350 317 -932 931 -342 269 -972 949 -346 285 -938 955 -310 301 -964 935 -348 921 -320 921 -344 301 -940 935 -350 317 -930 929 -318 937 -7872 939 -344 301 -940 947 -346 917 -322 921 -344 923 -352 927 -334 281 -970 925 -334 277 -982 943 -318 317 -932 931 -344 301 -936 935 -350 281 -960 957 -312 303 -960 935 -346 907 -322 929 -344 301 -942 935 -350 317 -924 955 -312 951 -7858 919 -342 309 -940 949 -348 909 -322 923 -344 923 -352 923 -336 317 -924 945 -312 311 -966 921 -340 317 -924 947 -350 281 -958 941 -322 291 -976 905 -350 279 -960 935 -342 943 -320 919 -330 311 -958 943 -320 315 -932 935 -344 919 -7866 957 -312 303 -964 917 -342 945 -320 923 -344 923 -354 929 -298 315 -956 941 -318 315 -960 911 -324 317 -942 939 -354 281 -964 941 -294 311 -968 943 -318 317 -932 937 -330 931 -350 919 -348 283 -960 917 -350 317 -922 939 -322 965 -7864 921 -324 329 -950 909 -354 923 -336 913 -322 947 -344 919 -354 281 -962 941 -294 311 -960 935 -354 281 -962 939 -294 311 -964 937 -354 281 -964 941 -296 309 -964 939 -318 931 -330 945 -324 315 -940 939 -354 281 -964 909 -344 921 -7862 963 -304 307 -976 933 -320 929 -328 941 -324 939 -348 915 -320 317 -930 939 -344 301 -940 965 -320 319 -926 953 -312 303 -960 933 -312 321 -960 913 -348 319 -924 943 -320 959 -310 921 -354 319 -924 943 -324 311 -938 941 -318 957 -7862 943 -318 317 -932 933 -344 925 -352 897 -332 943 -324 943 -346 267 -966 951 -310 321 -960 911 -350 281 -958 949 -320 291 -978 937 -316 279 -964 949 -326 309 -944 943 -314 959 -318 933 -336 317 -934 933 -344 267 -964 937 -350 905 -7896 943 -318 319 -926 955 -314 919 -350 935 -324 941 -294 967 -312 303 -962 933 -348 285 -960 917 -348 317 -922 941 -322 329 -950 907 -354 315 -926 943 -326 313 -940 941 -352 893 -332 949 -324 315 -938 941 -352 283 -962 943 -310 925 -7890 931 -344 269 -968 949 -310 943 -320 923 -350 937 -310 955 -318 317 -930 935 -344 301 -942 947 -346 285 -958 915 -346 317 -924 951 -322 295 -982 905 -352 317 -924 945 -324 941 -346 917 -318 317 -962 905 -330 311 -956 937 -352 897 -7878 939 -354 283 -960 941 -294 965 -312 953 -318 385 -201512 165 -198 265 -526 229 -298 755 -164 61687 -17310 131 -1056 99 -296 195 -296 65 -66 1617 +RAW_Data: 77 -76 131 -244 81 -210 55 -1428 53 -344 53 -238 51 -448 51 -804 125 -1490 51 -452 79 -1816 51 -176 197 -700 133 -563 51 -386 79 -474 109 -626 55 -266 103 -616 283 -1932 51 -1034 51 -2809 75 -244 83 -5339 77 -260 105 -839 107 -1806 53 -1408 81 -810 135 -488 187 -1469 73 -2596 75 -74 51 -726 113 -136 83 -406 55 -194 133 -606 55 -1018 55 -1774 51 -1954 75 -910 51 -944 137 -1337 51 -1606 101 -566 75 -584 51 -1470 133 -242 159 -2798 51 -1568 97 -100 71 -556 77 -1234 53 -320 53 -274 68337 -252 333 -278 333 -74 533 -280 307 -276 331 -6948 347 -262 329 -278 329 -278 329 -278 303 -304 303 -302 303 -304 303 -278 329 -278 329 -74 533 -294 325 -270 317 -6944 335 -282 309 -304 309 -304 281 -328 293 -310 281 -326 281 -326 281 -326 279 -302 305 -100 503 -306 305 -302 293 -6992 295 -294 291 -314 285 -336 283 -334 257 -328 283 -328 291 -310 281 -328 279 -328 279 -124 479 -332 279 -328 255 -7012 295 -324 271 -330 267 -346 261 -342 259 -334 257 -358 255 -356 257 -354 231 -354 255 -152 453 -356 257 -352 255 -7024 255 -352 257 -352 255 -354 255 -326 257 -352 255 -352 255 -354 255 -352 253 -352 255 -150 453 -356 255 -354 253 -7030 255 -352 267 -344 251 -354 253 -354 253 -354 267 -322 267 -350 261 -344 257 -364 231 -154 459 -360 257 -354 231 -7062 231 -380 231 -380 231 -352 257 -352 257 -352 257 -352 257 -352 257 -352 255 -354 231 -174 457 -360 229 -378 229 -7084 239 -364 239 -366 229 -380 229 -378 229 -380 229 -378 255 -354 255 -354 255 -352 255 -150 457 -364 253 -354 255 -9542 101 -3126 53 -814 109 -406 51 -162 109 -2219 183 -496 103 -1369 81 -603 99 -2172 79 -1103 75 -676 77 -560 103 -378 51 -654 95 -888 155 -1322 111 -1626 53 -182 51 -166 83 -52 181 -182 71 -2132 77 -2839 103 -4022 79 -362 81 -466 75 -970 203 -998 51 -2085 51 -1853 99 -328 75 -346 55 -1949 79 -2648 79 -434 75 -6757 51 -1920 109 -306 51 -612 101 -996 77 -764 81 -790 125 -1489 99 -430 77 -4142 165 -372 101 -198 71 -1688 51 -1636 99 -434 81 -794 135 -1973 79 -188 109 -2678 81 -196 109 -2099 51 -504 77 -1854 51 -910 107 -948 75 -122 131 -78 79 -1781 103 -3344 111 -406 79 -184 51 -408 103 -54 79 -1474 127 -1789 213 -683 131 -348 161 -5237 53 -2675 101 -52 105 -474 103 -1336 99 -3548 105 -1724 161 -2180 107 -2514 97 -3784 51 -910 77 -505 71 -494 131 -1154 79 -2295 75 -350 161 -274 81 -222 +RAW_Data: 107 -1501 77 -1518 53 -704 113 -390 107 -650 73 -932 51 -3641 169 -704 187 -574 79 -332 51 -3765 51 -1042 75 -1413 103 -2163 75 -218 73 -4118 73 -716 51 -1720 51 -176 145 -817 79 -602 55 -1270 53 -2290 81 -346 79 -1840 53 -596 97 -1135 155 -1672 157 -1150 53 -52 101 -2753 153 -1546 158 -698 79 -2962 215 -490 161 -766 51 -2170 55 -811 51 -694 51 -1461 103 -2590 149 -3785 130 -54 103 -1108 103 -3978 51 -1626 81 -1825 109 -452 129 -1000 79 -1651 157 -276 53 -104 81 -2440 81 -1780 53 -1554 51 -512 131 -2508 99 -176 73 -914 51 -76 81 -202 111 -690 109 -790 109 -584 53 -244 79 -706 73 -550 129 -142 127 -546 77 -296 53 -874 105 -2623 51 -1004 77 -3131 79 -552 81 -2008 187 -1168 55 -1173 51 -2146 99 -700 51 -1580 101 -252 75 -474 77 -569 73 -5817 77 -614 77 -712 149 -228 73 -562 201 -274 127 -648 77 -578 75 -1810 109 -2106 171 -5996 81 -366 159 -274 55 -1228 77 -3386 53 -106 81 -1024 133 -2331 53 -2636 53 -780 149 -842 79 -2288 53 -2807 107 -1410 51 -620 75 -428 71 -272 75 -1140 103 -4912 55 -2261 53 -716 53 -3093 109 -502 111 -1492 53 -4317 51 -500 83 -338 129 -698 105 -1565 103 -1874 105 -344 77 -546 79 -2826 105 -260 75 -616 103 -1254 113 -2687 77 -977 73 -246 97 -1054 109 -4681 67101 -252 357 -252 333 -276 333 -252 355 -254 333 -276 331 -6932 347 -272 353 -242 343 -268 339 -286 309 -282 309 -306 307 -304 283 -328 281 -328 281 -102 505 -308 291 -314 279 -6996 281 -326 279 -328 277 -328 277 -328 279 -328 277 -328 279 -328 279 -328 277 -330 277 -124 483 -322 297 -298 293 -6972 283 -336 281 -332 257 -330 281 -328 283 -328 281 -330 265 -338 255 -352 253 -352 255 -150 455 -336 279 -328 279 -6992 295 -322 265 -348 261 -342 259 -334 259 -356 257 -356 255 -330 257 -354 257 -352 255 -152 455 -358 239 -368 253 -7026 243 -352 243 -350 265 -344 261 -362 231 -358 255 -356 257 -354 231 -380 229 -354 255 -150 455 -360 231 -376 231 -7050 229 -378 229 -378 229 -378 229 -378 229 -378 229 -378 229 -352 255 -352 253 -352 255 -150 455 -362 229 -378 227 -7050 253 -354 229 -378 229 -378 229 -378 227 -378 229 -378 229 -378 229 -378 229 -376 229 -174 431 -374 243 -378 241 -7032 261 -370 233 -362 233 -384 233 -356 233 -380 231 -380 231 -380 231 -380 231 -378 231 -176 431 -384 231 -380 211 -7114 231 -384 231 -384 205 -408 207 -408 207 -408 231 -384 231 -412 207 -412 181 -530 77 -1144 +RAW_Data: 79 -4798 53 -918 83 -4847 51 -755 103 -732 81 -388 55 -1026 77 -1506 101 -242 107 -469 51 -2026 79 -686 77 -348 51 -104 131 -860 129 -148 73 -446 75 -440 97 -306 99 -600 51 -626 105 -1350 95 -674 83 -230 119 -1714 135 -396 155 -1111 109 -652 111 -482 51 -506 55 -1715 103 -968 207 -1156 81 -164 57 -404 99 -508 205 -126 75 -1417 51 -186 77 -588 53 -54 103 -2854 73 -1010 53 -800 51 -2494 53 -106 105 -52 51 -104 79 -1116 51 -654 103 -220 77 -162 71 -5385 137 -2232 79 -1159 79 -250 57 -108 79 -164 107 -1660 79 -3927 129 -992 73 -1913 51 -1430 51 -1498 55 -514 103 -586 81 -386 53 -2402 175 -1994 85 -3431 53 -3209 99 -372 79 -78 53 -1338 75 -682 97 -680 51 -206 101 -1708 101 -452 131 -1397 161 -2272 53 -456 77 -1413 193 -270 109 -466 53 -2432 77 -222 189 -474 107 -774 171 -192 79 -1327 75 -2141 51 -908 135 -3866 75 -804 129 -468 101 -1040 79 -1470 55 -869 77 -1448 105 -160 55 -1916 240 -588 79 -1587 53 -922 79 -2292 181 -1448 51 -552 77 -2189 75 -2545 77 -384 300 -2478 101 -1092 73 -558 79 -132 105 -884 103 -1177 109 -880 79 -2431 109 -1006 105 -468 53 -1378 235 -684 75 -285 73 -604 129 -528 77 -1582 51 -1240 105 -2750 75 -252 51 -1024 95 -1891 51 -864 107 -326 83 -887 159 -1058 163 -322 105 -722 83 -388 81 -936 155 -880 55 -220 83 -2123 135 -2100 73 -1926 103 -1633 149 -526 51 -324 51 -1538 103 -164 137 -964 81 -152 111 -781 225 -655 53 -2888 105 -151 131 -454 53 -4109 77 -1052 53 -178 163 -910 51 -733 207 -2070 53 -474 79 -54 53 -818 51 -1228 53 -2262 79 -788 79 -480 73 -2747 83 -316 183 -1880 105 -862 53 -662 53 -2287 153 -1630 51 -817 243 -806 55 -510 51 -1389 75 -986 135 -498 109 -532 131 -5521 99 -2948 209 -764 75 -1168 75 -886 83 -2065 53 -710 51 -596 77 -374 73 -628 99 -732 51 -202 73 -632 53 -222 55 -511 79 -4884 53 -1826 81 -1266 107 -356 55 -110 113 -280 83 -756 169 -252 81 -1854 51 -1556 157 -258 75 -748 53 -1438 291 -244 71 -1092 77 -1220 229 -1055 181 -1182 71 -1284 77 -864 79 -138 53 -160 53 -952 81 -80 127 -1272 51 -590 103 -502 77 -634 101 -74 51 -224 101 -912 77 -562 51 -164 83 -396 105 -4643 111 -3293 133 -1395 107 -3047 137 -2353 53 -298 83 -54 81 -80 53 -162 83 -392 105 -606 107 -787 53 -928 51 -2800 161 -1146 51 -182 103 -536 103 -994 81 -2044 83 -732 133 -1881 133 -2160 75 -178 +RAW_Data: 75 -1694 101 -122 73 -864 51 -250 129 -406 77 -630 77 -610 101 -781 125 -128 51 -5075 77 -1992 83 -1272 176 -2100 53 -2044 53 -1234 79 -1704 157 -519 99 -2374 101 -100 103 -202 51 -360 77 -1962 103 -2153 77 -1820 191 -164 167 -1320 77 -1718 127 -1374 81 -1047 53 -54 79 -632 53 -656 51 -128 81 -216 51 -755 79 -2692 103 -1478 125 -452 51 -896 157 -3679 135 -632 105 -134 55 -112 77 -588 79 -188 55 -1118 79 -1152 51 -1950 109 -1858 103 -1104 81 -580 131 -226 255 -2932 77 -1536 51 -1044 159 -2135 67667 -252 333 -278 333 -276 333 -74 533 -280 307 -276 345 -6930 331 -276 329 -278 329 -278 327 -278 349 -270 325 -270 317 -290 313 -308 283 -306 309 -100 509 -306 283 -328 281 -6972 307 -302 281 -326 281 -326 281 -328 279 -328 281 -326 279 -328 279 -326 281 -328 279 -124 481 -308 281 -326 279 -6998 281 -326 279 -328 279 -328 277 -330 277 -328 279 -328 279 -328 277 -304 303 -302 303 -100 503 -306 295 -322 293 -6968 287 -342 259 -336 283 -332 257 -356 255 -328 283 -328 257 -352 257 -352 257 -352 255 -150 455 -334 281 -326 281 -6996 265 -342 253 -354 253 -354 253 -354 253 -354 267 -326 269 -350 263 -342 257 -336 259 -152 459 -360 257 -354 231 -7038 267 -338 255 -352 253 -354 253 -354 239 -354 269 -352 239 -372 233 -366 233 -358 257 -154 457 -360 231 -378 231 -7050 231 -380 231 -378 231 -380 231 -378 231 -354 255 -352 257 -352 255 -354 231 -378 229 -176 453 -358 229 -378 231 -7076 231 -378 231 -380 231 -380 229 -354 255 -354 257 -352 257 -352 257 -352 257 -354 255 -150 455 -358 265 -364 229 -4941 101 -1058 153 -670 157 -532 124 -1396 133 -82 165 -162 153 -258 207 -156 131 -1582 85 -714 53 -774 103 -396 274 -110 131 -1965 55 -402 159 -1026 79 -590 77 -3531 57 -500 51 -4770 109 -722 77 -186 53 -298 79 -502 165 -808 77 -438 53 -382 101 -1914 75 -504 77 -1969 135 -5517 99 -576 51 -608 243 -684 53 -2058 315 -1384 79 -1079 77 -232 79 -212 155 -1500 137 -258 75 -975 204 -752 83 -2542 51 -484 103 -78 77 -210 53 -922 157 -1900 107 -2173 83 -384 101 -80 128 -814 183 -978 127 -772 105 -2073 51 -708 53 -300 83 -739 237 -884 131 -3412 157 -1752 81 -164 83 -3373 53 -1406 105 -3809 79 -432 51 -724 77 -548 53 -1955 79 -807 81 -2096 103 -490 105 -1196 109 -108 79 -394 71 -1159 129 -126 143 -340 107 -556 81 -2390 135 -106 133 -690 133 -4347 189 -290 51 -110 53 -78 103 -1101 51 -1362 +RAW_Data: 83 -320 81 -4648 101 -3726 173 -1418 85 -348 53 -2994 79 -1390 51 -1656 107 -764 53 -134 79 -1619 131 -932 55 -2810 107 -3218 79 -765 107 -654 103 -1498 77 -228 51 -134 247 -1526 51 -3903 103 -1495 179 -282 77 -392 53 -1756 105 -368 111 -486 51 -298 53 -216 113 -358 51 -266 187 -1059 81 -780 105 -238 51 -482 53 -791 109 -2169 77 -5304 53 -398 79 -650 51 -54 51 -1789 73 -198 101 -1580 101 -746 97 -4518 53 -744 51 -1064 101 -928 111 -392 185 -869 103 -320 133 -704 81 -244 53 -1628 75 -634 79 -666 183 -1276 83 -218 107 -1163 55 -1276 127 -1144 73 -1400 81 -266 77 -568 129 -806 121 -1420 103 -848 77 -982 103 -2132 81 -1610 101 -1218 55 -2208 75 -2735 53 -921 53 -724 51 -472 83 -3164 185 -400 77 -812 81 -306 215 -2167 53 -130 53 -272 81 -400 79 -1272 81 -418 51 -1381 73 -340 101 -2169 81 -2330 137 -2698 99 -2340 99 -126 51 -1714 55 -488 81 -3500 51 -404 77 -1422 77 -856 215 -80 51 -2308 53 -134 77 -2036 75 -5175 129 -946 239 -638 53 -244 55 -564 105 -826 71 -1632 77 -106 129 -246 135 -366 79 -724 79 -1535 57 -1085 113 -1320 79 -3111 127 -1578 75 -324 75 -102 173 -364 79 -1374 53 -1508 107 -622 51 -526 109 -584 187 -2648 51 -106 79 -380 103 -604 51 -1244 73 -5766 107 -1934 177 -702 51 -1277 53 -1643 79 -1446 81 -4098 75 -574 103 -432 189 -1436 107 -454 79 -132 105 -136 81 -112 113 -942 239 -1238 79 -952 157 -340 51 -314 191 -456 53 -3368 101 -150 99 -464 51 -718 73 -770 101 -150 73 -2132 75 -557 77 -680 81 -3512 151 -760 75 -332 75 -1212 131 -1468 79 -1955 101 -541 75 -344 79 -2146 53 -2299 97 -720 79 -2518 79 -3807 51 -1272 75 -352 77 -52 75 -586 53 -1142 79 -82 81 -2400 157 -324 81 -268 103 -1154 81 -1175 79 -1191 51 -1074 53 -2566 137 -854 75 -1497 51 -4533 51 -2290 51 -344 77 -348 55 -1182 77 -897 135 -874 51 -1064 51 -208 55 -140 55 -1334 133 -1238 157 -1669 113 -2128 75 -848 85 -510 83590 -126 333 -280 331 -252 331 -6946 331 -276 331 -276 329 -278 329 -276 331 -276 331 -276 331 -276 347 -238 351 -254 353 -268 323 -270 345 -6924 335 -282 307 -304 307 -304 281 -304 307 -302 307 -302 305 -302 307 -302 305 -302 281 -124 507 -282 305 -302 305 -6984 279 -328 277 -328 279 -328 277 -330 277 -304 303 -302 305 -302 305 -302 303 -304 303 -100 507 -314 295 -298 293 -6986 283 -334 281 -306 283 -328 283 -328 281 -328 283 -328 255 -352 diff --git a/lib/subghz/protocols/holtek_ht12x.c b/lib/subghz/protocols/holtek_ht12x.c new file mode 100644 index 00000000000..169387dedd0 --- /dev/null +++ b/lib/subghz/protocols/holtek_ht12x.c @@ -0,0 +1,400 @@ +#include "holtek_ht12x.h" + +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +/* + * Help + * https://www.holtek.com/documents/10179/116711/HT12A_Ev130.pdf + * + */ + +#define TAG "SubGhzProtocolHoltek_HT12X" + +#define DIP_PATTERN "%c%c%c%c%c%c%c%c" +#define CNT_TO_DIP(dip) \ + (dip & 0x0080 ? '0' : '1'), (dip & 0x0040 ? '0' : '1'), (dip & 0x0020 ? '0' : '1'), \ + (dip & 0x0010 ? '0' : '1'), (dip & 0x0008 ? '0' : '1'), (dip & 0x0004 ? '0' : '1'), \ + (dip & 0x0002 ? '0' : '1'), (dip & 0x0001 ? '0' : '1') + +static const SubGhzBlockConst subghz_protocol_holtek_th12x_const = { + .te_short = 320, + .te_long = 640, + .te_delta = 200, + .min_count_bit_for_found = 12, +}; + +struct SubGhzProtocolDecoderHoltek_HT12X { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; + + uint32_t te; + uint32_t last_data; +}; + +struct SubGhzProtocolEncoderHoltek_HT12X { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; + + uint32_t te; +}; + +typedef enum { + Holtek_HT12XDecoderStepReset = 0, + Holtek_HT12XDecoderStepFoundStartBit, + Holtek_HT12XDecoderStepSaveDuration, + Holtek_HT12XDecoderStepCheckDuration, +} Holtek_HT12XDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_holtek_th12x_decoder = { + .alloc = subghz_protocol_decoder_holtek_th12x_alloc, + .free = subghz_protocol_decoder_holtek_th12x_free, + + .feed = subghz_protocol_decoder_holtek_th12x_feed, + .reset = subghz_protocol_decoder_holtek_th12x_reset, + + .get_hash_data = subghz_protocol_decoder_holtek_th12x_get_hash_data, + .serialize = subghz_protocol_decoder_holtek_th12x_serialize, + .deserialize = subghz_protocol_decoder_holtek_th12x_deserialize, + .get_string = subghz_protocol_decoder_holtek_th12x_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_holtek_th12x_encoder = { + .alloc = subghz_protocol_encoder_holtek_th12x_alloc, + .free = subghz_protocol_encoder_holtek_th12x_free, + + .deserialize = subghz_protocol_encoder_holtek_th12x_deserialize, + .stop = subghz_protocol_encoder_holtek_th12x_stop, + .yield = subghz_protocol_encoder_holtek_th12x_yield, +}; + +const SubGhzProtocol subghz_protocol_holtek_th12x = { + .name = SUBGHZ_PROTOCOL_HOLTEK_HT12X_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_868 | SubGhzProtocolFlag_315 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_holtek_th12x_decoder, + .encoder = &subghz_protocol_holtek_th12x_encoder, +}; + +void* subghz_protocol_encoder_holtek_th12x_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderHoltek_HT12X* instance = + malloc(sizeof(SubGhzProtocolEncoderHoltek_HT12X)); + + instance->base.protocol = &subghz_protocol_holtek_th12x; + 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_running = false; + return instance; +} + +void subghz_protocol_encoder_holtek_th12x_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderHoltek_HT12X* instance = context; + free(instance->encoder.upload); + free(instance); +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderHoltek_HT12X instance + * @return true On success + */ +static bool + subghz_protocol_encoder_holtek_th12x_get_upload(SubGhzProtocolEncoderHoltek_HT12X* instance) { + furi_assert(instance); + + size_t index = 0; + size_t size_upload = (instance->generic.data_count_bit * 2) + 2; + if(size_upload > instance->encoder.size_upload) { + FURI_LOG_E(TAG, "Size upload exceeds allocated encoder buffer."); + return false; + } else { + instance->encoder.size_upload = size_upload; + } + + //Send header + instance->encoder.upload[index++] = level_duration_make(false, (uint32_t)instance->te * 36); + //Send start bit + instance->encoder.upload[index++] = level_duration_make(true, (uint32_t)instance->te); + //Send key data + for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) { + if(bit_read(instance->generic.data, i - 1)) { + //send bit 1 + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)instance->te * 2); + instance->encoder.upload[index++] = level_duration_make(true, (uint32_t)instance->te); + } else { + //send bit 0 + instance->encoder.upload[index++] = level_duration_make(false, (uint32_t)instance->te); + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)instance->te * 2); + } + } + return true; +} + +bool subghz_protocol_encoder_holtek_th12x_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderHoltek_HT12X* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + if(!flipper_format_read_uint32(flipper_format, "TE", (uint32_t*)&instance->te, 1)) { + FURI_LOG_E(TAG, "Missing TE"); + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_holtek_th12x_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + if(!subghz_protocol_encoder_holtek_th12x_get_upload(instance)) break; + instance->encoder.is_running = true; + + res = true; + } while(false); + + return res; +} + +void subghz_protocol_encoder_holtek_th12x_stop(void* context) { + SubGhzProtocolEncoderHoltek_HT12X* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_holtek_th12x_yield(void* context) { + SubGhzProtocolEncoderHoltek_HT12X* instance = context; + + if(instance->encoder.repeat == 0 || !instance->encoder.is_running) { + instance->encoder.is_running = 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_holtek_th12x_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderHoltek_HT12X* instance = + malloc(sizeof(SubGhzProtocolDecoderHoltek_HT12X)); + instance->base.protocol = &subghz_protocol_holtek_th12x; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_holtek_th12x_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderHoltek_HT12X* instance = context; + free(instance); +} + +void subghz_protocol_decoder_holtek_th12x_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderHoltek_HT12X* instance = context; + instance->decoder.parser_step = Holtek_HT12XDecoderStepReset; +} + +void subghz_protocol_decoder_holtek_th12x_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderHoltek_HT12X* instance = context; + + switch(instance->decoder.parser_step) { + case Holtek_HT12XDecoderStepReset: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_holtek_th12x_const.te_short * 36) < + subghz_protocol_holtek_th12x_const.te_delta * 36)) { + //Found Preambula + instance->decoder.parser_step = Holtek_HT12XDecoderStepFoundStartBit; + } + break; + case Holtek_HT12XDecoderStepFoundStartBit: + if((level) && (DURATION_DIFF(duration, subghz_protocol_holtek_th12x_const.te_short) < + subghz_protocol_holtek_th12x_const.te_delta)) { + //Found StartBit + instance->decoder.parser_step = Holtek_HT12XDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->te = duration; + } else { + instance->decoder.parser_step = Holtek_HT12XDecoderStepReset; + } + break; + case Holtek_HT12XDecoderStepSaveDuration: + //save duration + if(!level) { + if(duration >= ((uint32_t)subghz_protocol_holtek_th12x_const.te_short * 10 + + subghz_protocol_holtek_th12x_const.te_delta)) { + if(instance->decoder.decode_count_bit == + subghz_protocol_holtek_th12x_const.min_count_bit_for_found) { + if((instance->last_data == instance->decoder.decode_data) && + instance->last_data) { + instance->te /= (instance->decoder.decode_count_bit * 3 + 1); + + 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->last_data = instance->decoder.decode_data; + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->te = 0; + instance->decoder.parser_step = Holtek_HT12XDecoderStepFoundStartBit; + break; + } else { + instance->decoder.te_last = duration; + instance->te += duration; + instance->decoder.parser_step = Holtek_HT12XDecoderStepCheckDuration; + } + } else { + instance->decoder.parser_step = Holtek_HT12XDecoderStepReset; + } + break; + case Holtek_HT12XDecoderStepCheckDuration: + if(level) { + instance->te += duration; + if((DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_holtek_th12x_const.te_long) < + subghz_protocol_holtek_th12x_const.te_delta * 2) && + (DURATION_DIFF(duration, subghz_protocol_holtek_th12x_const.te_short) < + subghz_protocol_holtek_th12x_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = Holtek_HT12XDecoderStepSaveDuration; + } else if( + (DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_holtek_th12x_const.te_short) < + subghz_protocol_holtek_th12x_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_holtek_th12x_const.te_long) < + subghz_protocol_holtek_th12x_const.te_delta * 2)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = Holtek_HT12XDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = Holtek_HT12XDecoderStepReset; + } + } else { + instance->decoder.parser_step = Holtek_HT12XDecoderStepReset; + } + break; + } +} + +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + */ +static void subghz_protocol_holtek_th12x_check_remote_controller(SubGhzBlockGeneric* instance) { + instance->btn = instance->data & 0x0F; + instance->cnt = (instance->data >> 4) & 0xFF; +} + +uint8_t subghz_protocol_decoder_holtek_th12x_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderHoltek_HT12X* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_holtek_th12x_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderHoltek_HT12X* instance = context; + bool res = subghz_block_generic_serialize(&instance->generic, flipper_format, preset); + if(res && !flipper_format_write_uint32(flipper_format, "TE", &instance->te, 1)) { + FURI_LOG_E(TAG, "Unable to add TE"); + res = false; + } + return res; +} + +bool subghz_protocol_decoder_holtek_th12x_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderHoltek_HT12X* instance = context; + bool ret = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_holtek_th12x_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + if(!flipper_format_read_uint32(flipper_format, "TE", (uint32_t*)&instance->te, 1)) { + FURI_LOG_E(TAG, "Missing TE"); + break; + } + ret = true; + } while(false); + return ret; +} + +static void subghz_protocol_holtek_th12x_event_serialize(uint8_t event, FuriString* output) { + furi_string_cat_printf( + output, + "%s%s%s%s\r\n", + (((event >> 3) & 0x1) == 0x0 ? "B1 " : ""), + (((event >> 2) & 0x1) == 0x0 ? "B2 " : ""), + (((event >> 1) & 0x1) == 0x0 ? "B3 " : ""), + (((event >> 0) & 0x1) == 0x0 ? "B4 " : "")); +} + +void subghz_protocol_decoder_holtek_th12x_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderHoltek_HT12X* instance = context; + subghz_protocol_holtek_th12x_check_remote_controller(&instance->generic); + + furi_string_cat_printf( + output, + "%s %db\r\n" + "Key:0x%03lX\r\n" + "Btn: ", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data & 0xFFF)); + subghz_protocol_holtek_th12x_event_serialize(instance->generic.btn, output); + furi_string_cat_printf( + output, + "DIP:" DIP_PATTERN "\r\n" + "Te:%luus\r\n", + CNT_TO_DIP(instance->generic.cnt), + instance->te); +} diff --git a/lib/subghz/protocols/holtek_ht12x.h b/lib/subghz/protocols/holtek_ht12x.h new file mode 100644 index 00000000000..7b5c31dd739 --- /dev/null +++ b/lib/subghz/protocols/holtek_ht12x.h @@ -0,0 +1,107 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_HOLTEK_HT12X_NAME "Holtek_HT12X" + +typedef struct SubGhzProtocolDecoderHoltek_HT12X SubGhzProtocolDecoderHoltek_HT12X; +typedef struct SubGhzProtocolEncoderHoltek_HT12X SubGhzProtocolEncoderHoltek_HT12X; + +extern const SubGhzProtocolDecoder subghz_protocol_holtek_th12x_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_holtek_th12x_encoder; +extern const SubGhzProtocol subghz_protocol_holtek_th12x; + +/** + * Allocate SubGhzProtocolEncoderHoltek_HT12X. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderHoltek_HT12X* pointer to a SubGhzProtocolEncoderHoltek_HT12X instance + */ +void* subghz_protocol_encoder_holtek_th12x_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderHoltek_HT12X. + * @param context Pointer to a SubGhzProtocolEncoderHoltek_HT12X instance + */ +void subghz_protocol_encoder_holtek_th12x_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderHoltek_HT12X instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_encoder_holtek_th12x_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderHoltek_HT12X instance + */ +void subghz_protocol_encoder_holtek_th12x_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderHoltek_HT12X instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_holtek_th12x_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderHoltek_HT12X. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderHoltek_HT12X* pointer to a SubGhzProtocolDecoderHoltek_HT12X instance + */ +void* subghz_protocol_decoder_holtek_th12x_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderHoltek_HT12X. + * @param context Pointer to a SubGhzProtocolDecoderHoltek_HT12X instance + */ +void subghz_protocol_decoder_holtek_th12x_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderHoltek_HT12X. + * @param context Pointer to a SubGhzProtocolDecoderHoltek_HT12X instance + */ +void subghz_protocol_decoder_holtek_th12x_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderHoltek_HT12X instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_holtek_th12x_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderHoltek_HT12X instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_holtek_th12x_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderHoltek_HT12X. + * @param context Pointer to a SubGhzProtocolDecoderHoltek_HT12X instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_decoder_holtek_th12x_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderHoltek_HT12X. + * @param context Pointer to a SubGhzProtocolDecoderHoltek_HT12X instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_decoder_holtek_th12x_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderHoltek_HT12X instance + * @param output Resulting text + */ +void subghz_protocol_decoder_holtek_th12x_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/protocol_items.c b/lib/subghz/protocols/protocol_items.c index ed46f5c9787..2022e9c47c9 100644 --- a/lib/subghz/protocols/protocol_items.c +++ b/lib/subghz/protocols/protocol_items.c @@ -13,6 +13,7 @@ const SubGhzProtocol* subghz_protocol_registry_items[] = { &subghz_protocol_bett, &subghz_protocol_doitrand, &subghz_protocol_phoenix_v2, &subghz_protocol_honeywell_wdb, &subghz_protocol_magellan, &subghz_protocol_intertechno_v3, &subghz_protocol_clemsa, &subghz_protocol_ansonic, &subghz_protocol_smc5326, + &subghz_protocol_holtek_th12x, }; const SubGhzProtocolRegistry subghz_protocol_registry = { diff --git a/lib/subghz/protocols/protocol_items.h b/lib/subghz/protocols/protocol_items.h index 9f446739486..998fb56b301 100644 --- a/lib/subghz/protocols/protocol_items.h +++ b/lib/subghz/protocols/protocol_items.h @@ -37,5 +37,6 @@ #include "clemsa.h" #include "ansonic.h" #include "smc5326.h" +#include "holtek_ht12x.h" extern const SubGhzProtocolRegistry subghz_protocol_registry; From 08eb666f7b45cc9d160c4ea2a36d6a12f00281f2 Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Tue, 27 Dec 2022 11:39:04 +0300 Subject: [PATCH 321/824] [FL-3000] File browser: Empty folder label (#2188) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- applications/services/gui/modules/file_browser.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/applications/services/gui/modules/file_browser.c b/applications/services/gui/modules/file_browser.c index d21a48b54eb..e03a032c182 100644 --- a/applications/services/gui/modules/file_browser.c +++ b/applications/services/gui/modules/file_browser.c @@ -544,6 +544,18 @@ static void browser_draw_list(Canvas* canvas, FileBrowserModel* model) { model->item_cnt); } + uint32_t folder_item_cnt = (model->is_root) ? (model->item_cnt) : (model->item_cnt - 1); + if(folder_item_cnt == 0) { + canvas_set_color(canvas, ColorBlack); + canvas_draw_str_aligned( + canvas, + canvas_width(canvas) / 2, + canvas_height(canvas) / 2, + AlignCenter, + AlignCenter, + ""); + } + furi_string_free(filename); } From ded7e727d03342ade0acf9c32ec42d5d8eda2d63 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Tue, 27 Dec 2022 11:14:03 +0200 Subject: [PATCH 322/824] [FL-3060] New MFC Bruteforce animation (#2190) * Change the wording in the headers * Add support for text in the progress bar * New MFC key bruteforce screen * Typo fix * nfc: rename Flipper Dict to System Dict * elements: fix types * Display the correct key attack sector Co-authored-by: gornekich Co-authored-by: Aleksandr Kutuzov --- .../main/nfc/scenes/nfc_scene_extra_actions.c | 2 +- .../scenes/nfc_scene_mf_classic_dict_attack.c | 19 +++++-- .../nfc/scenes/nfc_scene_mf_classic_keys.c | 8 +-- applications/main/nfc/scenes/nfc_scene_read.c | 2 +- applications/main/nfc/views/dict_attack.c | 50 ++++++++++++++++--- applications/main/nfc/views/dict_attack.h | 4 ++ applications/services/gui/canvas.h | 1 + applications/services/gui/elements.c | 25 ++++++++++ applications/services/gui/elements.h | 17 +++++++ firmware/targets/f7/api_symbols.csv | 5 +- lib/nfc/helpers/mf_classic_dict.c | 4 +- lib/nfc/helpers/mf_classic_dict.h | 2 +- lib/nfc/nfc_device.h | 3 +- lib/nfc/nfc_worker.c | 8 +++ lib/nfc/nfc_worker.h | 3 ++ 15 files changed, 130 insertions(+), 23 deletions(-) diff --git a/applications/main/nfc/scenes/nfc_scene_extra_actions.c b/applications/main/nfc/scenes/nfc_scene_extra_actions.c index fc6021d7392..717e8efc444 100644 --- a/applications/main/nfc/scenes/nfc_scene_extra_actions.c +++ b/applications/main/nfc/scenes/nfc_scene_extra_actions.c @@ -43,7 +43,7 @@ bool nfc_scene_extra_actions_on_event(void* context, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { if(event.event == SubmenuIndexMfClassicKeys) { - if(mf_classic_dict_check_presence(MfClassicDictTypeFlipper)) { + if(mf_classic_dict_check_presence(MfClassicDictTypeSystem)) { scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicKeys); } else { scene_manager_next_scene(nfc->scene_manager, NfcSceneDictNotFound); diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c index acb5b783c3d..b82bf5521f5 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c @@ -53,10 +53,10 @@ static void nfc_scene_mf_classic_dict_attack_prepare_view(Nfc* nfc, DictAttackSt // Setup view if(state == DictAttackStateUserDictInProgress) { worker_state = NfcWorkerStateMfClassicDictAttack; - dict_attack_set_header(nfc->dict_attack, "Mf Classic User Dict."); + dict_attack_set_header(nfc->dict_attack, "MF Classic User Dictionary"); dict = mf_classic_dict_alloc(MfClassicDictTypeUser); - // If failed to load user dictionary - try flipper dictionary + // If failed to load user dictionary - try the system dictionary if(!dict) { FURI_LOG_E(TAG, "User dictionary not found"); state = DictAttackStateFlipperDictInProgress; @@ -64,11 +64,11 @@ static void nfc_scene_mf_classic_dict_attack_prepare_view(Nfc* nfc, DictAttackSt } if(state == DictAttackStateFlipperDictInProgress) { worker_state = NfcWorkerStateMfClassicDictAttack; - dict_attack_set_header(nfc->dict_attack, "Mf Classic Flipper Dict."); - dict = mf_classic_dict_alloc(MfClassicDictTypeFlipper); + dict_attack_set_header(nfc->dict_attack, "MF Classic System Dictionary"); + dict = mf_classic_dict_alloc(MfClassicDictTypeSystem); if(!dict) { FURI_LOG_E(TAG, "Flipper dictionary not found"); - // Pass through to let worker handle the failure + // Pass through to let the worker handle the failure } } // Free previous dictionary @@ -153,6 +153,15 @@ bool nfc_scene_mf_classic_dict_attack_on_event(void* context, SceneManagerEvent nfc_worker_stop(nfc->worker); consumed = true; } + } else if(event.event == NfcWorkerEventKeyAttackStart) { + dict_attack_set_key_attack( + nfc->dict_attack, + true, + nfc->dev->dev_data.mf_classic_dict_attack_data.current_sector); + } else if(event.event == NfcWorkerEventKeyAttackStop) { + dict_attack_set_key_attack(nfc->dict_attack, false, 0); + } else if(event.event == NfcWorkerEventKeyAttackNextSector) { + dict_attack_inc_key_attack_current_sector(nfc->dict_attack); } } else if(event.type == SceneManagerEventTypeBack) { scene_manager_next_scene(nfc->scene_manager, NfcSceneExitConfirm); diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys.c index dee9553d406..8a7dc2c1839 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys.c @@ -12,7 +12,7 @@ void nfc_scene_mf_classic_keys_on_enter(void* context) { // Load flipper dict keys total uint32_t flipper_dict_keys_total = 0; - MfClassicDict* dict = mf_classic_dict_alloc(MfClassicDictTypeFlipper); + MfClassicDict* dict = mf_classic_dict_alloc(MfClassicDictTypeSystem); if(dict) { flipper_dict_keys_total = mf_classic_dict_get_total_keys(dict); mf_classic_dict_free(dict); @@ -26,11 +26,11 @@ void nfc_scene_mf_classic_keys_on_enter(void* context) { } widget_add_string_element( - nfc->widget, 0, 0, AlignLeft, AlignTop, FontPrimary, "Mifare Classic Keys"); + nfc->widget, 0, 0, AlignLeft, AlignTop, FontPrimary, "MIFARE Classic Keys"); char temp_str[32]; - snprintf(temp_str, sizeof(temp_str), "Flipper list: %lu", flipper_dict_keys_total); + snprintf(temp_str, sizeof(temp_str), "System dict: %lu", flipper_dict_keys_total); widget_add_string_element(nfc->widget, 0, 20, AlignLeft, AlignTop, FontSecondary, temp_str); - snprintf(temp_str, sizeof(temp_str), "User list: %lu", user_dict_keys_total); + snprintf(temp_str, sizeof(temp_str), "User dict: %lu", user_dict_keys_total); widget_add_string_element(nfc->widget, 0, 32, AlignLeft, AlignTop, FontSecondary, temp_str); widget_add_button_element( nfc->widget, GuiButtonTypeCenter, "Add", nfc_scene_mf_classic_keys_widget_callback, nfc); diff --git a/applications/main/nfc/scenes/nfc_scene_read.c b/applications/main/nfc/scenes/nfc_scene_read.c index 2607cbd8fb0..4252883b255 100644 --- a/applications/main/nfc/scenes/nfc_scene_read.c +++ b/applications/main/nfc/scenes/nfc_scene_read.c @@ -91,7 +91,7 @@ bool nfc_scene_read_on_event(void* context, SceneManagerEvent event) { DOLPHIN_DEED(DolphinDeedNfcReadSuccess); consumed = true; } else if(event.event == NfcWorkerEventReadMfClassicDictAttackRequired) { - if(mf_classic_dict_check_presence(MfClassicDictTypeFlipper)) { + if(mf_classic_dict_check_presence(MfClassicDictTypeSystem)) { scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicDictAttack); } else { scene_manager_next_scene(nfc->scene_manager, NfcSceneDictNotFound); diff --git a/applications/main/nfc/views/dict_attack.c b/applications/main/nfc/views/dict_attack.c index c5f55ae7621..5ae204a060c 100644 --- a/applications/main/nfc/views/dict_attack.c +++ b/applications/main/nfc/views/dict_attack.c @@ -24,6 +24,8 @@ typedef struct { uint8_t keys_found; uint16_t dict_keys_total; uint16_t dict_keys_current; + bool is_key_attack; + uint8_t key_attack_current_sector; } DictAttackViewModel; static void dict_attack_draw_callback(Canvas* canvas, void* model) { @@ -36,10 +38,19 @@ static void dict_attack_draw_callback(Canvas* canvas, void* model) { canvas, 64, 23, AlignCenter, AlignTop, "Make sure the tag is\npositioned correctly."); } else if(m->state == DictAttackStateRead) { char draw_str[32] = {}; - canvas_set_font(canvas, FontPrimary); - canvas_draw_str_aligned( - canvas, 64, 2, AlignCenter, AlignTop, furi_string_get_cstr(m->header)); canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned( + canvas, 64, 0, AlignCenter, AlignTop, furi_string_get_cstr(m->header)); + if(m->is_key_attack) { + snprintf( + draw_str, + sizeof(draw_str), + "Reuse key check for sector: %d", + m->key_attack_current_sector); + } else { + snprintf(draw_str, sizeof(draw_str), "Unlocking sector: %d", m->sector_current); + } + canvas_draw_str_aligned(canvas, 0, 10, AlignLeft, AlignTop, draw_str); float dict_progress = m->dict_keys_total == 0 ? 0 : (float)(m->dict_keys_current) / (float)(m->dict_keys_total); @@ -49,13 +60,14 @@ static void dict_attack_draw_callback(Canvas* canvas, void* model) { if(progress > 1.0) { progress = 1.0; } - elements_progress_bar(canvas, 5, 15, 120, progress); + snprintf(draw_str, sizeof(draw_str), "%d/%d", m->dict_keys_current, m->dict_keys_total); + elements_progress_bar_with_text(canvas, 0, 20, 128, dict_progress, draw_str); canvas_set_font(canvas, FontSecondary); snprintf(draw_str, sizeof(draw_str), "Keys found: %d/%d", m->keys_found, m->keys_total); - canvas_draw_str_aligned(canvas, 1, 28, AlignLeft, AlignTop, draw_str); + canvas_draw_str_aligned(canvas, 0, 33, AlignLeft, AlignTop, draw_str); snprintf( draw_str, sizeof(draw_str), "Sectors Read: %d/%d", m->sectors_read, m->sectors_total); - canvas_draw_str_aligned(canvas, 1, 40, AlignLeft, AlignTop, draw_str); + canvas_draw_str_aligned(canvas, 0, 43, AlignLeft, AlignTop, draw_str); } elements_button_center(canvas, "Skip"); } @@ -113,6 +125,7 @@ void dict_attack_reset(DictAttack* dict_attack) { model->keys_found = 0; model->dict_keys_total = 0; model->dict_keys_current = 0; + model->is_key_attack = false; furi_string_reset(model->header); }, false); @@ -235,3 +248,28 @@ void dict_attack_inc_current_dict_key(DictAttack* dict_attack, uint16_t keys_tri }, true); } + +void dict_attack_set_key_attack(DictAttack* dict_attack, bool is_key_attack, uint8_t sector) { + furi_assert(dict_attack); + with_view_model( + dict_attack->view, + DictAttackViewModel * model, + { + model->is_key_attack = is_key_attack; + model->key_attack_current_sector = sector; + }, + true); +} + +void dict_attack_inc_key_attack_current_sector(DictAttack* dict_attack) { + furi_assert(dict_attack); + with_view_model( + dict_attack->view, + DictAttackViewModel * model, + { + if(model->key_attack_current_sector < model->sectors_total) { + model->key_attack_current_sector++; + } + }, + true); +} diff --git a/applications/main/nfc/views/dict_attack.h b/applications/main/nfc/views/dict_attack.h index 684f17f063e..2839534a76e 100644 --- a/applications/main/nfc/views/dict_attack.h +++ b/applications/main/nfc/views/dict_attack.h @@ -38,3 +38,7 @@ void dict_attack_inc_keys_found(DictAttack* dict_attack); void dict_attack_set_total_dict_keys(DictAttack* dict_attack, uint16_t dict_keys_total); void dict_attack_inc_current_dict_key(DictAttack* dict_attack, uint16_t keys_tried); + +void dict_attack_set_key_attack(DictAttack* dict_attack, bool is_key_attack, uint8_t sector); + +void dict_attack_inc_key_attack_current_sector(DictAttack* dict_attack); \ No newline at end of file diff --git a/applications/services/gui/canvas.h b/applications/services/gui/canvas.h index a3df5adc7f9..c4eb8649f3a 100644 --- a/applications/services/gui/canvas.h +++ b/applications/services/gui/canvas.h @@ -17,6 +17,7 @@ extern "C" { typedef enum { ColorWhite = 0x00, ColorBlack = 0x01, + ColorXOR = 0x02, } Color; /** Fonts enumeration */ diff --git a/applications/services/gui/elements.c b/applications/services/gui/elements.c index cd4c105ae18..e22889bf9db 100644 --- a/applications/services/gui/elements.c +++ b/applications/services/gui/elements.c @@ -41,6 +41,31 @@ void elements_progress_bar(Canvas* canvas, uint8_t x, uint8_t y, uint8_t width, canvas_draw_box(canvas, x + 1, y + 1, progress_length, height - 2); } +void elements_progress_bar_with_text( + Canvas* canvas, + uint8_t x, + uint8_t y, + uint8_t width, + float progress, + const char* text) { + furi_assert(canvas); + furi_assert((progress >= 0.0f) && (progress <= 1.0f)); + uint8_t height = 11; + + uint8_t progress_length = roundf(progress * (width - 2)); + + canvas_set_color(canvas, ColorWhite); + canvas_draw_box(canvas, x + 1, y + 1, width - 2, height - 2); + canvas_set_color(canvas, ColorBlack); + canvas_draw_rframe(canvas, x, y, width, height, 3); + + canvas_draw_box(canvas, x + 1, y + 1, progress_length, height - 2); + + canvas_set_color(canvas, ColorXOR); + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned(canvas, x + width / 2, y + 2, AlignCenter, AlignTop, text); +} + void elements_scrollbar_pos( Canvas* canvas, uint8_t x, diff --git a/applications/services/gui/elements.h b/applications/services/gui/elements.h index 162f0d410bd..48533513128 100644 --- a/applications/services/gui/elements.h +++ b/applications/services/gui/elements.h @@ -31,6 +31,23 @@ extern "C" { */ void elements_progress_bar(Canvas* canvas, uint8_t x, uint8_t y, uint8_t width, float progress); +/** Draw progress bar with text. + * + * @param canvas Canvas instance + * @param x progress bar position on X axis + * @param y progress bar position on Y axis + * @param width progress bar width + * @param progress progress (0.0 - 1.0) + * @param text text to draw + */ +void elements_progress_bar_with_text( + Canvas* canvas, + uint8_t x, + uint8_t y, + uint8_t width, + float progress, + const char* text); + /** Draw scrollbar on canvas at specific position. * * @param canvas Canvas instance diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 8503a838abc..72d44b5011f 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,11.2,, +Version,+,11.3,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -562,8 +562,8 @@ Function,+,ble_glue_wait_for_c2_start,_Bool,int32_t Function,-,bsearch,void*,"const void*, const void*, size_t, size_t, __compar_fn_t" Function,+,bt_disconnect,void,Bt* Function,+,bt_forget_bonded_devices,void,Bt* -Function,+,bt_keys_storage_set_storage_path,void,"Bt*, const char*" Function,+,bt_keys_storage_set_default_path,void,Bt* +Function,+,bt_keys_storage_set_storage_path,void,"Bt*, const char*" Function,+,bt_set_profile,_Bool,"Bt*, BtProfile" Function,+,bt_set_status_changed_callback,void,"Bt*, BtStatusChangedCallback, void*" Function,+,buffered_file_stream_alloc,Stream*,Storage* @@ -754,6 +754,7 @@ Function,+,elements_multiline_text,void,"Canvas*, uint8_t, uint8_t, const char*" Function,+,elements_multiline_text_aligned,void,"Canvas*, uint8_t, uint8_t, Align, Align, const char*" Function,+,elements_multiline_text_framed,void,"Canvas*, uint8_t, uint8_t, const char*" Function,+,elements_progress_bar,void,"Canvas*, uint8_t, uint8_t, uint8_t, float" +Function,+,elements_progress_bar_with_text,void,"Canvas*, uint8_t, uint8_t, uint8_t, float, const char*" Function,+,elements_scrollable_text_line,void,"Canvas*, uint8_t, uint8_t, uint8_t, FuriString*, size_t, _Bool" Function,+,elements_scrollbar,void,"Canvas*, uint16_t, uint16_t" Function,+,elements_scrollbar_pos,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint16_t, uint16_t" diff --git a/lib/nfc/helpers/mf_classic_dict.c b/lib/nfc/helpers/mf_classic_dict.c index 98076479f70..93098d409bc 100644 --- a/lib/nfc/helpers/mf_classic_dict.c +++ b/lib/nfc/helpers/mf_classic_dict.c @@ -20,7 +20,7 @@ bool mf_classic_dict_check_presence(MfClassicDictType dict_type) { Storage* storage = furi_record_open(RECORD_STORAGE); bool dict_present = false; - if(dict_type == MfClassicDictTypeFlipper) { + if(dict_type == MfClassicDictTypeSystem) { dict_present = storage_common_stat(storage, MF_CLASSIC_DICT_FLIPPER_PATH, NULL) == FSE_OK; } else if(dict_type == MfClassicDictTypeUser) { dict_present = storage_common_stat(storage, MF_CLASSIC_DICT_USER_PATH, NULL) == FSE_OK; @@ -42,7 +42,7 @@ MfClassicDict* mf_classic_dict_alloc(MfClassicDictType dict_type) { bool dict_loaded = false; do { - if(dict_type == MfClassicDictTypeFlipper) { + if(dict_type == MfClassicDictTypeSystem) { if(!buffered_file_stream_open( dict->stream, MF_CLASSIC_DICT_FLIPPER_PATH, diff --git a/lib/nfc/helpers/mf_classic_dict.h b/lib/nfc/helpers/mf_classic_dict.h index 5b0ee312ac6..3b2d560ad1d 100644 --- a/lib/nfc/helpers/mf_classic_dict.h +++ b/lib/nfc/helpers/mf_classic_dict.h @@ -8,7 +8,7 @@ typedef enum { MfClassicDictTypeUser, - MfClassicDictTypeFlipper, + MfClassicDictTypeSystem, MfClassicDictTypeUnitTest, } MfClassicDictType; diff --git a/lib/nfc/nfc_device.h b/lib/nfc/nfc_device.h index 55ee4ac4c85..8b2e6e5ba30 100644 --- a/lib/nfc/nfc_device.h +++ b/lib/nfc/nfc_device.h @@ -18,7 +18,7 @@ extern "C" { #define NFC_DEV_NAME_MAX_LEN 22 #define NFC_READER_DATA_MAX_SIZE 64 -#define NFC_DICT_KEY_BATCH_SIZE 50 +#define NFC_DICT_KEY_BATCH_SIZE 10 #define NFC_APP_EXTENSION ".nfc" #define NFC_APP_SHADOW_EXTENSION ".shd" @@ -48,6 +48,7 @@ typedef struct { typedef struct { MfClassicDict* dict; + uint8_t current_sector; } NfcMfClassicDictAttackData; typedef enum { diff --git a/lib/nfc/nfc_worker.c b/lib/nfc/nfc_worker.c index 0ffe1d07b69..4a66593cb43 100644 --- a/lib/nfc/nfc_worker.c +++ b/lib/nfc/nfc_worker.c @@ -573,17 +573,24 @@ static void nfc_worker_mf_classic_key_attack( FuriHalNfcTxRxContext* tx_rx, uint16_t start_sector) { furi_assert(nfc_worker); + furi_assert(nfc_worker->callback); bool card_found_notified = true; bool card_removed_notified = false; MfClassicData* data = &nfc_worker->dev_data->mf_classic_data; + NfcMfClassicDictAttackData* dict_attack_data = + &nfc_worker->dev_data->mf_classic_dict_attack_data; uint32_t total_sectors = mf_classic_get_total_sectors_num(data->type); furi_assert(start_sector < total_sectors); + nfc_worker->callback(NfcWorkerEventKeyAttackStart, nfc_worker->context); + // Check every sector's A and B keys with the given key for(size_t i = start_sector; i < total_sectors; i++) { + nfc_worker->callback(NfcWorkerEventKeyAttackNextSector, nfc_worker->context); + dict_attack_data->current_sector = i; furi_hal_nfc_sleep(); if(furi_hal_nfc_activate_nfca(200, NULL)) { furi_hal_nfc_sleep(); @@ -632,6 +639,7 @@ static void nfc_worker_mf_classic_key_attack( } if(nfc_worker->state != NfcWorkerStateMfClassicDictAttack) break; } + nfc_worker->callback(NfcWorkerEventKeyAttackStop, nfc_worker->context); } void nfc_worker_mf_classic_dict_attack(NfcWorker* nfc_worker) { diff --git a/lib/nfc/nfc_worker.h b/lib/nfc/nfc_worker.h index fdcaa72fdf4..ce542828ab1 100644 --- a/lib/nfc/nfc_worker.h +++ b/lib/nfc/nfc_worker.h @@ -55,6 +55,9 @@ typedef enum { NfcWorkerEventNewDictKeyBatch, NfcWorkerEventFoundKeyA, NfcWorkerEventFoundKeyB, + NfcWorkerEventKeyAttackStart, + NfcWorkerEventKeyAttackStop, + NfcWorkerEventKeyAttackNextSector, // Write Mifare Classic events NfcWorkerEventWrongCard, From 727f043747ad337a035ea946733e42be7fd166cb Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Tue, 27 Dec 2022 22:59:36 +1000 Subject: [PATCH 323/824] OpenOCD scripts (#2101) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Scripts: option bytes check * Scripts: option bytes set * Scripts: openocd config * Scripts: increased readability, process IPCCBR option byte * Scripts: split dap_ob.py * Updater: process IPCCBR option byte * Scripts: move chip-related functions to chip definition * Scripts: freeze CPU registers * Scripts: flash programming routine * ob.py * otp.py * otp: handle errors correctly * downgrade to python 3.9 * correct type hinting * Scripts: fix path to ob.data Co-authored-by: あく --- firmware/targets/f7/furi_hal/furi_hal_flash.c | 2 +- scripts/flipper/utils/openocd.py | 173 +++++++++ scripts/flipper/utils/programmer.py | 31 ++ scripts/flipper/utils/programmer_openocd.py | 281 ++++++++++++++ scripts/flipper/utils/register.py | 95 +++++ scripts/flipper/utils/stm32wb55.py | 352 ++++++++++++++++++ scripts/ob.py | 92 +++-- scripts/otp.py | 75 ++-- 8 files changed, 1035 insertions(+), 66 deletions(-) create mode 100644 scripts/flipper/utils/openocd.py create mode 100644 scripts/flipper/utils/programmer.py create mode 100644 scripts/flipper/utils/programmer_openocd.py create mode 100644 scripts/flipper/utils/register.py create mode 100644 scripts/flipper/utils/stm32wb55.py diff --git a/firmware/targets/f7/furi_hal/furi_hal_flash.c b/firmware/targets/f7/furi_hal/furi_hal_flash.c index e49cd5f2954..fc021d96921 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_flash.c +++ b/firmware/targets/f7/furi_hal/furi_hal_flash.c @@ -488,7 +488,7 @@ static const FuriHalFlashObMapping furi_hal_flash_ob_reg_map[FURI_HAL_FLASH_OB_T OB_REG_DEF(FuriHalFlashObInvalid, (NULL)), OB_REG_DEF(FuriHalFlashObInvalid, (NULL)), - OB_REG_DEF(FuriHalFlashObRegisterIPCCMail, (NULL)), + OB_REG_DEF(FuriHalFlashObRegisterIPCCMail, (&FLASH->IPCCBR)), OB_REG_DEF(FuriHalFlashObRegisterSecureFlash, (NULL)), OB_REG_DEF(FuriHalFlashObRegisterC2Opts, (NULL)), }; diff --git a/scripts/flipper/utils/openocd.py b/scripts/flipper/utils/openocd.py new file mode 100644 index 00000000000..1309055b8d1 --- /dev/null +++ b/scripts/flipper/utils/openocd.py @@ -0,0 +1,173 @@ +import socket +import subprocess +import logging + + +class OpenOCD: + """OpenOCD cli wrapper""" + + COMMAND_TOKEN = "\x1a" + + def __init__(self, config: dict = {}) -> None: + assert isinstance(config, dict) + + # Params base + self.params = [] + + self.gdb_port = 3333 + self.telnet_port = 4444 + self.tcl_port = 6666 + + # Port + if port_base := config.get("port_base", None): + self.gdb_port = port_base + self.tcl_port = port_base + 1 + self.telnet_port = port_base + 2 + + self._add_command(f"gdb_port {self.gdb_port}") + self._add_command(f"tcl_port {self.tcl_port}") + self._add_command(f"telnet_port {self.telnet_port}") + + # Config files + + if interface := config.get("interface", None): + pass + else: + interface = "interface/stlink.cfg" + + if target := config.get("target", None): + pass + else: + target = "target/stm32wbx.cfg" + + self._add_file(interface) + self._add_file(target) + + # Programmer settings + if serial := config.get("serial", None): + self._add_command(f"{serial}") + + # Other params + if "params" in config: + self.params += config["params"] + + # logging + self.logger = logging.getLogger() + + def _add_command(self, command: str): + self.params.append("-c") + self.params.append(command) + + def _add_file(self, file: str): + self.params.append("-f") + self.params.append(file) + + def start(self, args: list[str] = []): + """Start OpenOCD process""" + + params = ["openocd", *self.params, *args] + self.logger.debug(f"_execute: {params}") + self.process = subprocess.Popen( + params, stderr=subprocess.PIPE, stdout=subprocess.PIPE + ) + + self._wait_for_openocd_tcl() + + self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.socket.connect(("127.0.0.1", self.tcl_port)) + + def _wait_for_openocd_tcl(self): + """Wait for OpenOCD to start""" + # TODO: timeout + while True: + stderr = self.process.stderr + if not stderr: + break + line = stderr.readline() + if not line: + break + line = line.decode("utf-8").strip() + self.logger.debug(f"OpenOCD: {line}") + if "Listening on port" in line and "for tcl connections" in line: + break + + def stop(self): + self.send_tcl("exit") + self.send_tcl("shutdown") + self.socket.close() + try: + self.process.wait(timeout=10) + except subprocess.TimeoutExpired as e: + self.process.kill() + self.logger.error("Failed to stop OpenOCD") + self.logger.exception(e) + self.postmortem() + + def send_tcl(self, cmd) -> str: + """Send a command string to TCL RPC. Return the result that was read.""" + + try: + data = (cmd + OpenOCD.COMMAND_TOKEN).encode("utf-8") + self.logger.debug(f"<- {data}") + + self.socket.send(data) + except Exception as e: + self.logger.error("Failed to send command to OpenOCD") + self.logger.exception(e) + self.postmortem() + raise + + try: + data = self._recv() + return data + except Exception as e: + self.logger.error("Failed to receive response from OpenOCD") + self.logger.exception(e) + self.postmortem() + raise + + def _recv(self): + """Read from the stream until the token (\x1a) was received.""" + # TODO: timeout + data = bytes() + while True: + chunk = self.socket.recv(4096) + data += chunk + if bytes(OpenOCD.COMMAND_TOKEN, encoding="utf-8") in chunk: + break + + self.logger.debug(f"-> {data}") + + data = data.decode("utf-8").strip() + data = data[:-1] # strip trailing \x1a + + return data + + def postmortem(self) -> None: + """Postmortem analysis of the OpenOCD process""" + stdout, stderr = self.process.communicate() + + log = self.logger.error + if self.process.returncode == 0: + log = self.logger.debug + log("OpenOCD exited normally") + else: + log("OpenOCD exited with error") + + log(f"Exit code: {self.process.returncode}") + for line in stdout.decode("utf-8").splitlines(): + log(f"Stdout: {line}") + + for line in stderr.decode("utf-8").splitlines(): + log(f"Stderr: {line}") + + def read_32(self, addr: int) -> int: + """Read 32-bit value from memory""" + data = self.send_tcl(f"mdw {addr}").strip() + data = data.split(": ")[-1] + data = int(data, 16) + return data + + def write_32(self, addr: int, value: int) -> None: + """Write 32-bit value to memory""" + self.send_tcl(f"mww {addr} {value}") diff --git a/scripts/flipper/utils/programmer.py b/scripts/flipper/utils/programmer.py new file mode 100644 index 00000000000..84452d15415 --- /dev/null +++ b/scripts/flipper/utils/programmer.py @@ -0,0 +1,31 @@ +from abc import ABC, abstractmethod +from enum import Enum + + +class Programmer(ABC): + def __init__(self): + pass + + class RunMode(Enum): + Run = "run" + Stop = "stop" + + @abstractmethod + def reset(self, mode: RunMode = RunMode.Run) -> bool: + pass + + @abstractmethod + def flash(self, address: int, file_path: str, verify: bool = True) -> bool: + pass + + @abstractmethod + def option_bytes_validate(self, file_path: str) -> bool: + pass + + @abstractmethod + def option_bytes_set(self, file_path: str) -> bool: + pass + + @abstractmethod + def otp_write(self, address: int, file_path: str) -> bool: + pass diff --git a/scripts/flipper/utils/programmer_openocd.py b/scripts/flipper/utils/programmer_openocd.py new file mode 100644 index 00000000000..b334061035b --- /dev/null +++ b/scripts/flipper/utils/programmer_openocd.py @@ -0,0 +1,281 @@ +import logging +import os +import typing + +from flipper.utils.programmer import Programmer +from flipper.utils.openocd import OpenOCD +from flipper.utils.stm32wb55 import STM32WB55 +from flipper.assets.obdata import OptionBytesData + + +class OpenOCDProgrammer(Programmer): + def __init__( + self, + interface: str = "interface/cmsis-dap.cfg", + port_base: typing.Union[int, None] = None, + serial: typing.Union[str, None] = None, + ): + super().__init__() + + config = {} + + config["interface"] = interface + config["target"] = "target/stm32wbx.cfg" + + if not serial is None: + if interface == "interface/cmsis-dap.cfg": + config["serial"] = f"cmsis_dap_serial {serial}" + elif "stlink" in interface: + config["serial"] = f"stlink_serial {serial}" + + if not port_base is None: + config["port_base"] = port_base + + self.openocd = OpenOCD(config) + self.logger = logging.getLogger() + + def reset(self, mode: Programmer.RunMode = Programmer.RunMode.Run) -> bool: + stm32 = STM32WB55() + if mode == Programmer.RunMode.Run: + stm32.reset(self.openocd, stm32.RunMode.Run) + elif mode == Programmer.RunMode.Stop: + stm32.reset(self.openocd, stm32.RunMode.Init) + else: + raise Exception("Unknown mode") + + return True + + def flash(self, address: int, file_path: str, verify: bool = True) -> bool: + if not os.path.exists(file_path): + raise Exception(f"File {file_path} not found") + + self.openocd.start() + self.openocd.send_tcl(f"init") + self.openocd.send_tcl( + f"program {file_path} 0x{address:08x}{' verify' if verify else ''} reset exit" + ) + self.openocd.stop() + + return True + + def _ob_print_diff_table(self, ob_reference: bytes, ob_read: bytes, print_fn): + print_fn( + f'{"Reference": <20} {"Device": <20} {"Diff Reference": <20} {"Diff Device": <20}' + ) + + # Split into 8 byte, word + word + for i in range(0, len(ob_reference), 8): + ref = ob_reference[i : i + 8] + read = ob_read[i : i + 8] + + diff_str1 = "" + diff_str2 = "" + for j in range(0, len(ref.hex()), 2): + byte_str_1 = ref.hex()[j : j + 2] + byte_str_2 = read.hex()[j : j + 2] + + if byte_str_1 == byte_str_2: + diff_str1 += "__" + diff_str2 += "__" + else: + diff_str1 += byte_str_1 + diff_str2 += byte_str_2 + + print_fn( + f"{ref.hex(): <20} {read.hex(): <20} {diff_str1: <20} {diff_str2: <20}" + ) + + def option_bytes_validate(self, file_path: str) -> bool: + # Registers + stm32 = STM32WB55() + + # OpenOCD + self.openocd.start() + stm32.reset(self.openocd, stm32.RunMode.Init) + + # Generate Option Bytes data + ob_data = OptionBytesData(file_path) + ob_values = ob_data.gen_values().export() + ob_reference = ob_values.reference + ob_compare_mask = ob_values.compare_mask + ob_length = len(ob_reference) + ob_words = int(ob_length / 4) + + # Read Option Bytes + ob_read = bytes() + for i in range(ob_words): + addr = stm32.OPTION_BYTE_BASE + i * 4 + value = self.openocd.read_32(addr) + ob_read += value.to_bytes(4, "little") + + # Compare Option Bytes with reference by mask + ob_compare = bytes() + for i in range(ob_length): + ob_compare += bytes([ob_read[i] & ob_compare_mask[i]]) + + # Compare Option Bytes + return_code = False + + if ob_reference == ob_compare: + self.logger.info("Option Bytes are valid") + return_code = True + else: + self.logger.error("Option Bytes are invalid") + self._ob_print_diff_table(ob_reference, ob_compare, self.logger.error) + + # Stop OpenOCD + stm32.reset(self.openocd, stm32.RunMode.Run) + self.openocd.stop() + + return return_code + + def _unpack_u32(self, data: bytes, offset: int): + return int.from_bytes(data[offset : offset + 4], "little") + + def option_bytes_set(self, file_path: str) -> bool: + # Registers + stm32 = STM32WB55() + + # OpenOCD + self.openocd.start() + stm32.reset(self.openocd, stm32.RunMode.Init) + + # Generate Option Bytes data + ob_data = OptionBytesData(file_path) + ob_values = ob_data.gen_values().export() + ob_reference_bytes = ob_values.reference + ob_compare_mask_bytes = ob_values.compare_mask + ob_write_mask_bytes = ob_values.write_mask + ob_length = len(ob_reference_bytes) + ob_dwords = int(ob_length / 8) + + # Clear flash errors + stm32.clear_flash_errors(self.openocd) + + # Unlock Flash and Option Bytes + stm32.flash_unlock(self.openocd) + stm32.option_bytes_unlock(self.openocd) + + ob_need_to_apply = False + + for i in range(ob_dwords): + device_addr = stm32.OPTION_BYTE_BASE + i * 8 + device_value = self.openocd.read_32(device_addr) + ob_write_mask = self._unpack_u32(ob_write_mask_bytes, i * 8) + ob_compare_mask = self._unpack_u32(ob_compare_mask_bytes, i * 8) + ob_value_ref = self._unpack_u32(ob_reference_bytes, i * 8) + ob_value_masked = device_value & ob_compare_mask + + need_patch = ((ob_value_masked ^ ob_value_ref) & ob_write_mask) != 0 + if need_patch: + ob_need_to_apply = True + + self.logger.info( + f"Need to patch: {device_addr:08X}: {ob_value_masked:08X} != {ob_value_ref:08X}, REG[{i}]" + ) + + # Check if this option byte (dword) is mapped to a register + device_reg_addr = stm32.option_bytes_id_to_address(i) + + # Construct new value for the OB register + ob_value = device_value & (~ob_write_mask) + ob_value |= ob_value_ref & ob_write_mask + + self.logger.info(f"Writing {ob_value:08X} to {device_reg_addr:08X}") + self.openocd.write_32(device_reg_addr, ob_value) + + if ob_need_to_apply: + stm32.option_bytes_apply(self.openocd) + else: + self.logger.info(f"Option Bytes are already correct") + + # Load Option Bytes + # That will reset and also lock the Option Bytes and the Flash + stm32.option_bytes_load(self.openocd) + + # Stop OpenOCD + stm32.reset(self.openocd, stm32.RunMode.Run) + self.openocd.stop() + + return True + + def otp_write(self, address: int, file_path: str) -> bool: + # Open file, check that it aligned to 8 bytes + with open(file_path, "rb") as f: + data = f.read() + if len(data) % 8 != 0: + self.logger.error(f"File {file_path} is not aligned to 8 bytes") + return False + + # Check that address is aligned to 8 bytes + if address % 8 != 0: + self.logger.error(f"Address {address} is not aligned to 8 bytes") + return False + + # Get size of data + data_size = len(data) + + # Check that data size is aligned to 8 bytes + if data_size % 8 != 0: + self.logger.error(f"Data size {data_size} is not aligned to 8 bytes") + return False + + self.logger.debug(f"Writing {data_size} bytes to OTP at {address:08X}") + self.logger.debug(f"Data: {data.hex().upper()}") + + # Start OpenOCD + oocd = self.openocd + oocd.start() + + # Registers + stm32 = STM32WB55() + + try: + # Check that OTP is empty for the given address + # Also check that data is already written + already_written = True + for i in range(0, data_size, 4): + file_word = int.from_bytes(data[i : i + 4], "little") + device_word = oocd.read_32(address + i) + if device_word != 0xFFFFFFFF and device_word != file_word: + self.logger.error( + f"OTP memory at {address + i:08X} is not empty: {device_word:08X}" + ) + raise Exception("OTP memory is not empty") + + if device_word != file_word: + already_written = False + + if already_written: + self.logger.info(f"OTP memory is already written with the given data") + return True + + self.reset(self.RunMode.Stop) + stm32.clear_flash_errors(oocd) + + # Write OTP memory by 8 bytes + for i in range(0, data_size, 8): + word_1 = int.from_bytes(data[i : i + 4], "little") + word_2 = int.from_bytes(data[i + 4 : i + 8], "little") + self.logger.debug( + f"Writing {word_1:08X} {word_2:08X} to {address + i:08X}" + ) + stm32.write_flash_64(oocd, address + i, word_1, word_2) + + # Validate OTP memory + validation_result = True + + for i in range(0, data_size, 4): + file_word = int.from_bytes(data[i : i + 4], "little") + device_word = oocd.read_32(address + i) + if file_word != device_word: + self.logger.error( + f"Validation failed: {file_word:08X} != {device_word:08X} at {address + i:08X}" + ) + validation_result = False + finally: + # Stop OpenOCD + stm32.reset(oocd, stm32.RunMode.Run) + oocd.stop() + + return validation_result diff --git a/scripts/flipper/utils/register.py b/scripts/flipper/utils/register.py new file mode 100644 index 00000000000..26d66730c3a --- /dev/null +++ b/scripts/flipper/utils/register.py @@ -0,0 +1,95 @@ +from dataclasses import dataclass +from flipper.utils.openocd import OpenOCD + + +@dataclass +class RegisterBitDefinition: + name: str + offset: int + size: int + value: int = 0 + + +class Register32: + def __init__(self, address: int, definition_list: list[RegisterBitDefinition]): + self.__dict__["names"] = [definition.name for definition in definition_list] + self.names = [definition.name for definition in definition_list] # typecheck + self.address = address + self.definition_list = definition_list + + # Validate that the definitions are not overlapping + for i in range(len(definition_list)): + for j in range(i + 1, len(definition_list)): + if self._is_overlapping(definition_list[i], definition_list[j]): + raise ValueError("Register definitions are overlapping") + + self.freezed = True + + def _is_overlapping( + self, a: RegisterBitDefinition, b: RegisterBitDefinition + ) -> bool: + if a.offset + a.size <= b.offset: + return False + if b.offset + b.size <= a.offset: + return False + return True + + def _get_definition(self, name: str) -> RegisterBitDefinition: + for definition in self.definition_list: + if definition.name == name: + return definition + raise ValueError(f"Register definition '{name}' not found") + + def get_definition_list(self) -> list[RegisterBitDefinition]: + return self.definition_list + + def get_address(self) -> int: + return self.address + + def set_reg_value(self, name: str, value: int): + definition = self._get_definition(name) + if value > (1 << definition.size) - 1: + raise ValueError( + f"Value {value} is too large for register definition '{name}'" + ) + definition.value = value + + def get_reg_value(self, name: str) -> int: + definition = self._get_definition(name) + return definition.value + + def __getattr__(self, attr): + if str(attr) in self.names: + return self.get_reg_value(str(attr)) + else: + return self.__dict__[attr] + + def __setattr__(self, attr, value): + if str(attr) in self.names: + self.set_reg_value(str(attr), value) + else: + if attr in self.__dict__ or "freezed" not in self.__dict__: + self.__dict__[attr] = value + else: + raise AttributeError(f"Attribute '{attr}' not found") + + def __dir__(self): + return self.names + + def set(self, value: int): + for definition in self.definition_list: + definition.value = (value >> definition.offset) & ( + (1 << definition.size) - 1 + ) + + def get(self) -> int: + value = 0 + for definition in self.definition_list: + value |= definition.value << definition.offset + return value + + def load(self, openocd: OpenOCD): + self.set(openocd.read_32(self.address)) + + def store(self, openocd: OpenOCD): + openocd.write_32(self.address, self.get()) diff --git a/scripts/flipper/utils/stm32wb55.py b/scripts/flipper/utils/stm32wb55.py new file mode 100644 index 00000000000..910b0d7d69e --- /dev/null +++ b/scripts/flipper/utils/stm32wb55.py @@ -0,0 +1,352 @@ +import logging +from enum import Enum + +from flipper.utils.openocd import OpenOCD +from flipper.utils.register import Register32, RegisterBitDefinition + + +class STM32WB55: + # Address of OTP memory in flash + OTP_BASE = 0x1FFF7000 + + # Address of Option byte in flash + OPTION_BYTE_BASE = 0x1FFF8000 + + # Flash base address + FLASH_BASE = 0x58004000 + + # Flash unlock register + FLASH_KEYR = FLASH_BASE + 0x08 + + # Option byte unlock register + FLASH_OPTKEYR = FLASH_BASE + 0x0C + + # Flash unlock keys + FLASH_UNLOCK_KEY1 = 0x45670123 + FLASH_UNLOCK_KEY2 = 0xCDEF89AB + + # Option byte unlock keys + FLASH_UNLOCK_OPTKEY1 = 0x08192A3B + FLASH_UNLOCK_OPTKEY2 = 0x4C5D6E7F + + # Flash control register + FLASH_CR = Register32( + FLASH_BASE + 0x14, + [ + RegisterBitDefinition("PG", 0, 1), + RegisterBitDefinition("PER", 1, 1), + RegisterBitDefinition("MER", 2, 1), + RegisterBitDefinition("PNB", 3, 8), + RegisterBitDefinition("_", 11, 5), + RegisterBitDefinition("STRT", 16, 1), + RegisterBitDefinition("OPT_STRT", 17, 1), + RegisterBitDefinition("FSTPG", 18, 1), + RegisterBitDefinition("_", 19, 5), + RegisterBitDefinition("EOPIE", 24, 1), + RegisterBitDefinition("ERRIE", 25, 1), + RegisterBitDefinition("RD_ERRIE", 26, 1), + RegisterBitDefinition("OBL_LAUNCH", 27, 1), + RegisterBitDefinition("_", 28, 2), + RegisterBitDefinition("OPT_LOCK", 30, 1), + RegisterBitDefinition("LOCK", 31, 1), + ], + ) + + # Flash status register + FLASH_SR = Register32( + FLASH_BASE + 0x10, + [ + RegisterBitDefinition("EOP", 0, 1), + RegisterBitDefinition("OP_ERR", 1, 1), + RegisterBitDefinition("_", 2, 1), + RegisterBitDefinition("PROG_ERR", 3, 1), + RegisterBitDefinition("WRP_ERR", 4, 1), + RegisterBitDefinition("PGA_ERR", 5, 1), + RegisterBitDefinition("SIZE_ERR", 6, 1), + RegisterBitDefinition("PGS_ERR", 7, 1), + RegisterBitDefinition("MISS_ERR", 8, 1), + RegisterBitDefinition("FAST_ERR", 9, 1), + RegisterBitDefinition("_", 10, 3), + RegisterBitDefinition("OPTNV", 13, 1), + RegisterBitDefinition("RD_ERR", 14, 1), + RegisterBitDefinition("OPTV_ERR", 15, 1), + RegisterBitDefinition("BSY", 16, 1), + RegisterBitDefinition("_", 17, 1), + RegisterBitDefinition("CFGBSY", 18, 1), + RegisterBitDefinition("PESD", 19, 1), + RegisterBitDefinition("_", 20, 12), + ], + ) + + # Option byte registers + FLASH_OPTR = FLASH_BASE + 0x20 + FLASH_PCROP1ASR = FLASH_BASE + 0x24 + FLASH_PCROP1AER = FLASH_BASE + 0x28 + FLASH_WRP1AR = FLASH_BASE + 0x2C + FLASH_WRP1BR = FLASH_BASE + 0x30 + FLASH_PCROP1BSR = FLASH_BASE + 0x34 + FLASH_PCROP1BER = FLASH_BASE + 0x38 + FLASH_IPCCBR = FLASH_BASE + 0x3C + + # Map option byte dword index to register address + OPTION_BYTE_MAP_TO_REGS = { + 0: FLASH_OPTR, + 1: FLASH_PCROP1ASR, + 2: FLASH_PCROP1AER, + 3: FLASH_WRP1AR, + 4: FLASH_WRP1BR, + 5: FLASH_PCROP1BSR, + 6: FLASH_PCROP1BER, + 7: None, # Invalid Options + 8: None, # Invalid Options + 9: None, # Invalid Options + 10: None, # Invalid Options + 11: None, # Invalid Options + 12: None, # Invalid Options + 13: FLASH_IPCCBR, + 14: None, # Secure Flash + 15: None, # Core 2 Options + } + + def __init__(self): + self.logger = logging.getLogger("STM32WB55") + + class RunMode(Enum): + Init = "init" + Run = "run" + Halt = "halt" + + def reset(self, oocd: OpenOCD, mode: RunMode): + self.logger.debug("Resetting device") + oocd.send_tcl(f"reset {mode.value}") + + def clear_flash_errors(self, oocd: OpenOCD): + # Errata 2.2.9: Flash OPTVERR flag is always set after system reset + # And also clear all other flash error flags + self.logger.debug(f"Resetting flash errors") + self.FLASH_SR.load(oocd) + self.FLASH_SR.OP_ERR = 1 + self.FLASH_SR.PROG_ERR = 1 + self.FLASH_SR.WRP_ERR = 1 + self.FLASH_SR.PGA_ERR = 1 + self.FLASH_SR.SIZE_ERR = 1 + self.FLASH_SR.PGS_ERR = 1 + self.FLASH_SR.MISS_ERR = 1 + self.FLASH_SR.FAST_ERR = 1 + self.FLASH_SR.RD_ERR = 1 + self.FLASH_SR.OPTV_ERR = 1 + self.FLASH_SR.store(oocd) + + def flash_unlock(self, oocd: OpenOCD): + # Check if flash is already unlocked + self.FLASH_CR.load(oocd) + if self.FLASH_CR.LOCK == 0: + self.logger.debug("Flash is already unlocked") + return + + # Unlock flash + self.logger.debug("Unlocking Flash") + oocd.write_32(self.FLASH_KEYR, self.FLASH_UNLOCK_KEY1) + oocd.write_32(self.FLASH_KEYR, self.FLASH_UNLOCK_KEY2) + + # Check if flash is unlocked + self.FLASH_CR.load(oocd) + if self.FLASH_CR.LOCK == 0: + self.logger.debug("Flash unlocked") + else: + self.logger.error("Flash unlock failed") + raise Exception("Flash unlock failed") + + def option_bytes_unlock(self, oocd: OpenOCD): + # Check if options is already unlocked + self.FLASH_CR.load(oocd) + if self.FLASH_CR.OPT_LOCK == 0: + self.logger.debug("Options is already unlocked") + return + + # Unlock options + self.logger.debug("Unlocking Options") + oocd.write_32(self.FLASH_OPTKEYR, self.FLASH_UNLOCK_OPTKEY1) + oocd.write_32(self.FLASH_OPTKEYR, self.FLASH_UNLOCK_OPTKEY2) + + # Check if options is unlocked + self.FLASH_CR.load(oocd) + if self.FLASH_CR.OPT_LOCK == 0: + self.logger.debug("Options unlocked") + else: + self.logger.error("Options unlock failed") + raise Exception("Options unlock failed") + + def option_bytes_lock(self, oocd: OpenOCD): + # Check if options is already locked + self.FLASH_CR.load(oocd) + if self.FLASH_CR.OPT_LOCK == 1: + self.logger.debug("Options is already locked") + return + + # Lock options + self.logger.debug("Locking Options") + self.FLASH_CR.OPT_LOCK = 1 + self.FLASH_CR.store(oocd) + + # Check if options is locked + self.FLASH_CR.load(oocd) + if self.FLASH_CR.OPT_LOCK == 1: + self.logger.debug("Options locked") + else: + self.logger.error("Options lock failed") + raise Exception("Options lock failed") + + def flash_lock(self, oocd: OpenOCD): + # Check if flash is already locked + self.FLASH_CR.load(oocd) + if self.FLASH_CR.LOCK == 1: + self.logger.debug("Flash is already locked") + return + + # Lock flash + self.logger.debug("Locking Flash") + self.FLASH_CR.LOCK = 1 + self.FLASH_CR.store(oocd) + + # Check if flash is locked + self.FLASH_CR.load(oocd) + if self.FLASH_CR.LOCK == 1: + self.logger.debug("Flash locked") + else: + self.logger.error("Flash lock failed") + raise Exception("Flash lock failed") + + def option_bytes_apply(self, oocd: OpenOCD): + self.logger.debug(f"Applying Option Bytes") + + self.FLASH_CR.load(oocd) + self.FLASH_CR.OPT_STRT = 1 + self.FLASH_CR.store(oocd) + + # Wait for Option Bytes to be applied + self.flash_wait_for_operation(oocd) + + def option_bytes_load(self, oocd: OpenOCD): + self.logger.debug(f"Loading Option Bytes") + self.FLASH_CR.load(oocd) + self.FLASH_CR.OBL_LAUNCH = 1 + self.FLASH_CR.store(oocd) + + def option_bytes_id_to_address(self, id: int) -> int: + # Check if this option byte (dword) is mapped to a register + device_reg_addr = self.OPTION_BYTE_MAP_TO_REGS.get(id, None) + if device_reg_addr is None: + raise Exception(f"Option Byte {id} is not mapped to a register") + + return device_reg_addr + + def flash_wait_for_operation(self, oocd: OpenOCD): + # Wait for flash operation to complete + # TODO: timeout + while True: + self.FLASH_SR.load(oocd) + if self.FLASH_SR.BSY == 0: + break + + def flash_dump_status_register(self, oocd: OpenOCD): + self.FLASH_SR.load(oocd) + self.logger.info(f"FLASH_SR: {self.FLASH_SR.get():08x}") + if self.FLASH_SR.EOP: + self.logger.info(" End of operation") + if self.FLASH_SR.OP_ERR: + self.logger.error(" Operation error") + if self.FLASH_SR.PROG_ERR: + self.logger.error(" Programming error") + if self.FLASH_SR.WRP_ERR: + self.logger.error(" Write protection error") + if self.FLASH_SR.PGA_ERR: + self.logger.error(" Programming alignment error") + if self.FLASH_SR.SIZE_ERR: + self.logger.error(" Size error") + if self.FLASH_SR.PGS_ERR: + self.logger.error(" Programming sequence error") + if self.FLASH_SR.MISS_ERR: + self.logger.error(" Fast programming data miss error") + if self.FLASH_SR.FAST_ERR: + self.logger.error(" Fast programming error") + if self.FLASH_SR.OPTNV: + self.logger.info(" User option OPTVAL indication") + if self.FLASH_SR.RD_ERR: + self.logger.info(" PCROP read error") + if self.FLASH_SR.OPTV_ERR: + self.logger.info(" Option and Engineering bits loading validity error") + if self.FLASH_SR.BSY: + self.logger.info(" Busy") + if self.FLASH_SR.CFGBSY: + self.logger.info(" Programming or erase configuration busy") + if self.FLASH_SR.PESD: + self.logger.info(" Programming / erase operation suspended.") + + def write_flash_64(self, oocd: OpenOCD, address: int, word_1: int, word_2: int): + self.logger.debug(f"Writing flash at address {address:08x}") + + if address % 8 != 0: + self.logger.error("Address must be aligned to 8 bytes") + raise Exception("Address must be aligned to 8 bytes") + + if word_1 == oocd.read_32(address) and word_2 == oocd.read_32(address + 4): + self.logger.debug("Data is already programmed") + return + + self.flash_unlock(oocd) + + # Check that no flash main memory operation is ongoing by checking the BSY bit + self.FLASH_SR.load(oocd) + if self.FLASH_SR.BSY: + self.logger.error("Flash is busy") + self.flash_dump_status_register(oocd) + raise Exception("Flash is busy") + + # Enable end of operation interrupts and error interrupts + self.FLASH_CR.load(oocd) + self.FLASH_CR.EOPIE = 1 + self.FLASH_CR.ERRIE = 1 + self.FLASH_CR.store(oocd) + + # Check that flash memory program and erase operations are allowed + if self.FLASH_SR.PESD: + self.logger.error("Flash operations are not allowed") + self.flash_dump_status_register(oocd) + raise Exception("Flash operations are not allowed") + + # Check and clear all error programming flags due to a previous programming. + self.clear_flash_errors(oocd) + + # Set the PG bit in the Flash memory control register (FLASH_CR) + self.FLASH_CR.load(oocd) + self.FLASH_CR.PG = 1 + self.FLASH_CR.store(oocd) + + # Perform the data write operation at the desired memory address, only double word (64 bits) can be programmed. + # Write the first word + oocd.send_tcl(f"mww 0x{address:08x} 0x{word_1:08x}") + # Write the second word + oocd.send_tcl(f"mww 0x{(address + 4):08x} 0x{word_2:08x}") + + # Wait for the BSY bit to be cleared + self.flash_wait_for_operation(oocd) + + # Check that EOP flag is set in the FLASH_SR register + self.FLASH_SR.load(oocd) + if not self.FLASH_SR.EOP: + self.logger.error("Flash operation failed") + self.flash_dump_status_register(oocd) + raise Exception("Flash operation failed") + + # Clear the EOP flag + self.FLASH_SR.load(oocd) + self.FLASH_SR.EOP = 1 + self.FLASH_SR.store(oocd) + + # Clear the PG bit in the FLASH_CR register + self.FLASH_CR.load(oocd) + self.FLASH_CR.PG = 0 + self.FLASH_CR.store(oocd) + + self.flash_lock(oocd) diff --git a/scripts/ob.py b/scripts/ob.py index 722549d69a7..178fe16a77e 100755 --- a/scripts/ob.py +++ b/scripts/ob.py @@ -1,69 +1,79 @@ #!/usr/bin/env python3 -import logging -import argparse -import subprocess -import sys -import os +from os import path from flipper.app import App -from flipper.cube import CubeProgrammer +from flipper.utils.programmer_openocd import OpenOCDProgrammer class Main(App): def init(self): + # Subparsers self.subparsers = self.parser.add_subparsers(help="sub-command help") + + # Check command self.parser_check = self.subparsers.add_parser( "check", help="Check Option Bytes" ) - self._addArgsSWD(self.parser_check) + self._add_args(self.parser_check) self.parser_check.set_defaults(func=self.check) + # Set command self.parser_set = self.subparsers.add_parser("set", help="Set Option Bytes") - self._addArgsSWD(self.parser_set) + self._add_args(self.parser_set) self.parser_set.set_defaults(func=self.set) - # OB - self.ob = {} - def _addArgsSWD(self, parser): + def _add_args(self, parser): + parser.add_argument( + "--port-base", type=int, help="OpenOCD port base", default=3333 + ) + parser.add_argument( + "--interface", + type=str, + help="OpenOCD interface", + default="interface/cmsis-dap.cfg", + ) + parser.add_argument( + "--serial", type=str, help="OpenOCD interface serial number" + ) parser.add_argument( - "--port", type=str, help="Port to connect: swd or usb1", default="swd" + "--ob-path", + type=str, + help="Option bytes file", + default=path.join(path.dirname(__file__), "ob.data"), ) - parser.add_argument("--serial", type=str, help="ST-Link Serial Number") - - def _getCubeParams(self): - return { - "port": self.args.port, - "serial": self.args.serial, - } - - def before(self): - self.logger.info(f"Loading Option Bytes data") - file_path = os.path.join(os.path.dirname(sys.argv[0]), "ob.data") - with open(file_path, "r") as file: - for line in file.readlines(): - k, v, o = line.split(":") - self.ob[k.strip()] = v.strip(), o.strip() def check(self): self.logger.info(f"Checking Option Bytes") - cp = CubeProgrammer(self._getCubeParams()) - if cp.checkOptionBytes(self.ob): - self.logger.info(f"OB Check OK") - return 0 - else: - self.logger.error(f"OB Check FAIL") - return 255 + + # OpenOCD + openocd = OpenOCDProgrammer( + self.args.interface, + self.args.port_base, + self.args.serial, + ) + + return_code = 1 + if openocd.option_bytes_validate(self.args.ob_path): + return_code = 0 + + return return_code def set(self): self.logger.info(f"Setting Option Bytes") - cp = CubeProgrammer(self._getCubeParams()) - if cp.setOptionBytes(self.ob): - self.logger.info(f"OB Set OK") - return 0 - else: - self.logger.error(f"OB Set FAIL") - return 255 + + # OpenOCD + openocd = OpenOCDProgrammer( + self.args.interface, + self.args.port_base, + self.args.serial, + ) + + return_code = 1 + if openocd.option_bytes_set(self.args.ob_path): + return_code = 0 + + return return_code if __name__ == "__main__": diff --git a/scripts/otp.py b/scripts/otp.py index 48056fd2ac9..3bfe30d2dd7 100755 --- a/scripts/otp.py +++ b/scripts/otp.py @@ -35,6 +35,7 @@ from flipper.app import App from flipper.cube import CubeProgrammer +from flipper.utils.programmer_openocd import OpenOCDProgrammer class Main(App): @@ -53,21 +54,21 @@ def init(self): self.parser_flash_first = self.subparsers.add_parser( "flash_first", help="Flash first block of OTP to device" ) - self._addArgsSWD(self.parser_flash_first) + self._addArgsOpenOCD(self.parser_flash_first) self._addFirstArgs(self.parser_flash_first) self.parser_flash_first.set_defaults(func=self.flash_first) # Flash Second self.parser_flash_second = self.subparsers.add_parser( "flash_second", help="Flash second block of OTP to device" ) - self._addArgsSWD(self.parser_flash_second) + self._addArgsOpenOCD(self.parser_flash_second) self._addSecondArgs(self.parser_flash_second) self.parser_flash_second.set_defaults(func=self.flash_second) # Flash All self.parser_flash_all = self.subparsers.add_parser( "flash_all", help="Flash OTP to device" ) - self._addArgsSWD(self.parser_flash_all) + self._addArgsOpenOCD(self.parser_flash_all) self._addFirstArgs(self.parser_flash_all) self._addSecondArgs(self.parser_flash_all) self.parser_flash_all.set_defaults(func=self.flash_all) @@ -75,17 +76,19 @@ def init(self): self.logger = logging.getLogger() self.timestamp = datetime.datetime.now().timestamp() - def _addArgsSWD(self, parser): + def _addArgsOpenOCD(self, parser): parser.add_argument( - "--port", type=str, help="Port to connect: swd or usb1", default="swd" + "--port-base", type=int, help="OpenOCD port base", default=3333 + ) + parser.add_argument( + "--interface", + type=str, + help="OpenOCD interface", + default="interface/cmsis-dap.cfg", + ) + parser.add_argument( + "--serial", type=str, help="OpenOCD interface serial number" ) - parser.add_argument("--serial", type=str, help="ST-Link Serial Number") - - def _getCubeParams(self): - return { - "port": self.args.port, - "serial": self.args.serial, - } def _addFirstArgs(self, parser): parser.add_argument("--version", type=int, help="Version", required=True) @@ -173,14 +176,22 @@ def flash_first(self): file.write(self._packFirst()) self.logger.info(f"Flashing OTP") - cp = CubeProgrammer(self._getCubeParams()) - cp.flashBin("0x1FFF7000", filename) - cp.resetTarget() + + openocd = OpenOCDProgrammer( + self.args.interface, + self.args.port_base, + self.args.serial, + ) + + if not openocd.otp_write(0x1FFF7000, filename): + raise Exception("Failed to flash OTP") + self.logger.info(f"Flashed Successfully") - os.remove(filename) except Exception as e: self.logger.exception(e) return 1 + finally: + os.remove(filename) return 0 @@ -197,14 +208,22 @@ def flash_second(self): file.write(self._packSecond()) self.logger.info(f"Flashing OTP") - cp = CubeProgrammer(self._getCubeParams()) - cp.flashBin("0x1FFF7010", filename) - cp.resetTarget() + + openocd = OpenOCDProgrammer( + self.args.interface, + self.args.port_base, + self.args.serial, + ) + + if not openocd.otp_write(0x1FFF7010, filename): + raise Exception("Failed to flash OTP") + self.logger.info(f"Flashed Successfully") - os.remove(filename) except Exception as e: self.logger.exception(e) return 1 + finally: + os.remove(filename) return 0 @@ -223,14 +242,22 @@ def flash_all(self): file.write(self._packSecond()) self.logger.info(f"Flashing OTP") - cp = CubeProgrammer(self._getCubeParams()) - cp.flashBin("0x1FFF7000", filename) - cp.resetTarget() + + openocd = OpenOCDProgrammer( + self.args.interface, + self.args.port_base, + self.args.serial, + ) + + if not openocd.otp_write(0x1FFF7000, filename): + raise Exception("Failed to flash OTP") + self.logger.info(f"Flashed Successfully") - os.remove(filename) except Exception as e: self.logger.exception(e) return 1 + finally: + os.remove(filename) return 0 From d7735a1efb983112294108760526f6e48335306d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Wed, 28 Dec 2022 21:32:32 +0900 Subject: [PATCH 324/824] Drop brewfile, drop makefile, update readme and documentation (#2205) * Drop brewfile, drop makefile, update readme * Minor grammar and consistency fixes * ReadMe: add more documentation links, cleanup layout * ReadMe: cleanup and contribution section. Roadmap: update. * Docs: Lots of minor fixes Co-authored-by: Anna Prosvetova Co-authored-by: Astra --- .github/assets/dark_theme_banner.png | Bin 0 -> 48709 bytes .github/assets/light_theme_banner.png | Bin 0 -> 45320 bytes Brewfile | 6 - Makefile | 21 ---- ReadMe.md | 147 +++++++++++-------------- documentation/AppManifests.md | 64 +++++------ RoadMap.md => documentation/RoadMap.md | 30 +++-- 7 files changed, 110 insertions(+), 158 deletions(-) create mode 100644 .github/assets/dark_theme_banner.png create mode 100644 .github/assets/light_theme_banner.png delete mode 100644 Brewfile delete mode 100644 Makefile rename RoadMap.md => documentation/RoadMap.md (59%) diff --git a/.github/assets/dark_theme_banner.png b/.github/assets/dark_theme_banner.png new file mode 100644 index 0000000000000000000000000000000000000000..10a4e8c30099acde9d7047875ab6105a2ddbacf8 GIT binary patch literal 48709 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 GvHt^>$Cph2 literal 0 HcmV?d00001 diff --git a/.github/assets/light_theme_banner.png b/.github/assets/light_theme_banner.png new file mode 100644 index 0000000000000000000000000000000000000000..6c6b3b7080a846f121730169bba688c75b509b13 GIT binary patch literal 45320 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

}(Eut!CnJO%Cy1X&pzO%XoB61iNdqDp&#W#L&9> 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 a7fI|VyFm0Pqy!*8NQ$zmGPP3Xq5lsK>#ZsP literal 0 HcmV?d00001 diff --git a/Brewfile b/Brewfile deleted file mode 100644 index d9ba70e0ae1..00000000000 --- a/Brewfile +++ /dev/null @@ -1,6 +0,0 @@ -cask "gcc-arm-embedded" -brew "protobuf" -brew "gdb" -brew "open-ocd" -brew "clang-format" -brew "dfu-util" diff --git a/Makefile b/Makefile deleted file mode 100644 index ff701a42cec..00000000000 --- a/Makefile +++ /dev/null @@ -1,21 +0,0 @@ -$(info +-------------------------------------------------+) -$(info | |) -$(info | Hello, this is Flipper team speaking! |) -$(info | |) -$(info | We've migrated to new build system |) -$(info | It's nice and based on scons |) -$(info | |) -$(info | Crash course: |) -$(info | |) -$(info | `./fbt` |) -$(info | `./fbt flash` |) -$(info | `./fbt debug` |) -$(info | |) -$(info | More details in documentation/fbt.md |) -$(info | |) -$(info | Also Please leave your feedback here: |) -$(info | https://flipp.dev/4RDu |) -$(info | or |) -$(info | https://flipp.dev/2XM8 |) -$(info | |) -$(info +-------------------------------------------------+) \ No newline at end of file diff --git a/ReadMe.md b/ReadMe.md index c091d7e3f0f..5cf2ff71c22 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1,134 +1,115 @@ -# Flipper Zero Firmware - -[![Discord](https://img.shields.io/discord/740930220399525928.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](http://flipperzero.one/discord) - -![Show me the code](https://habrastorage.org/webt/eo/m0/e4/eom0e4btudte7nrhnyic-laiog0.png) + + + + A pixel art of a Dophin with text: Flipper Zero Official Repo + -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 -``` - -# Read the Docs +# Flipper Zero Firmware -Check out details on [how to build firmware](documentation/fbt.md), [write applications](documentation/AppsOnSDCard.md), [un-brick your device](documentation/KeyCombo.md) and more in `documentation` folder. +- [Flipper Zero Official Website](https://flipperzero.one). A simple way to explain to your friends what the Flipper Zero can do +- [Flipper Zero Firmware Update](https://update.flipperzero.one). Improvements for your dolphin: latest firmware releases, upgrade tools for PC and Mobile devices +- [User Documentation](https://docs.flipperzero.one). Learn more about your dolphin: specs, usage guides, and everything that you wanted to ask -# Update firmware +# Contributing -[Get Latest Firmware from Update Server](https://update.flipperzero.one/) +Our main goal is to build a healthy, sustainable community around the Flipper and be open to any new ideas and contributions. We also have some rules and taboos here, so please read this page and our [Code Of Conduct](/CODE_OF_CONDUCT.md) carefully. -Flipper Zero's firmware consists of two components: +## I need help -- Core2 firmware set - proprietary components by ST: FUS + radio stack. FUS is flashed at factory, and you should never update it. -- Core1 Firmware - HAL + OS + Drivers + Applications. +The best place to search for answers is our [User Documentation](https://docs.flipperzero.one). If you can't find the answer there, you can check our [Discord Server](https://flipp.dev/discord) or our [Forum](https://forum.flipperzero.one/). -They both must be flashed in the order described. +## I want to report an issue -## With offline update package +If you've found an issue and want to report it, please check our [Issues](https://github.com/flipperdevices/flipperzero-firmware/issues) page. Make sure that the description contains information about the firmware version you're using, your platform, and the proper steps to reproduce the issue. -With Flipper attached over USB: +## I want to contribute code -`./fbt flash_usb` +Before opening a PR, please confirm that your changes must be contained in the firmware. Many ideas can easily be implemented as external applications and published in the Flipper Application Catalog (coming soon). If you are unsure, you can ask on the [Discord Server](https://flipp.dev/discord) or the [Issues](https://github.com/flipperdevices/flipperzero-firmware/issues) page, and we'll help you find the right place for your code. -Just building the package: +Also, please read our [Contribution Guide](/CONTRIBUTING.md), and our [Coding Style](/CODING_STYLE.md), and ensure that your code is compatible with our project [License](/LICENSE). -`./fbt updater_package` +Finally, open a [Pull Request](https://github.com/flipperdevices/flipperzero-firmware/pulls) and ensure that CI/CD statuses are all green. -To update, copy the resulting directory to Flipper's SD card and navigate to `update.fuf` file in Archive app. +# Development -## With STLink +The Flipper Zero Firmware is written in C, with some bits and pieces written in C++ and armv7m assembly languages. An intermediate level of C knowledge is recommended for comfortable programming. For Flipper applications, we support C, C++, and armv7m assembly languages. -### Core1 Firmware +## Requirements -Prerequisites: +Supported development platforms: -- Linux / macOS -- Terminal -- [arm-gcc-none-eabi](https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm/downloads) -- openocd +- Windows 10+ with PowerShell and Git (x86_64) +- macOS 12+ with Command Line tools (x86_64, arm64) +- Ubuntu 20.04+ with build-essential and Git (x86_64) -One-liner: `./fbt firmware_flash` +Supported in-circuit debuggers (optional but highly recommended): -## With USB DFU +- [Flipper Zero Wi-Fi Development Board](https://shop.flipperzero.one/products/wifi-devboard) +- ST-Link +- J-Link -1. Download latest [Firmware](https://update.flipperzero.one) +Everything else will be taken care of by Flipper Build System. -2. Reboot Flipper to Bootloader - - Press and hold `← Left` + `↩ Back` for reset - - Release `↩ Back` and keep holding `← Left` until blue LED lights up - - Release `← Left` +## Cloning Source Code -3. Run `dfu-util -D full.dfu -a 0` +Ensure that you have enough space and clone source code with Git: -# Build on Linux/macOS +```shell +git clone --recursive https://github.com/flipperdevices/flipperzero-firmware.git +``` -Check out `documentation/fbt.md` for details on building and flashing firmware. +## Building -## macOS Prerequisites +Build firmware using Flipper Build Tool: -Make sure you have [brew](https://brew.sh) and install all the dependencies: -```sh -brew bundle --verbose +```shell +./fbt ``` -## Linux Prerequisites - -The FBT tool handles everything, only `git` is required. +## Flashing Firmware using an in-circuit debugger -### Optional dependencies +Connect your in-circuit debugger to the Flipper and flash firmware using Flipper Build Tool: -- openocd (debugging/flashing over SWD) -- heatshrink (compiling image assets) -- clang-format (code formatting) -- dfu-util (flashing over USB DFU) -- protobuf (compiling proto sources) - -For example, to install them on Debian, use: -```sh -apt update -apt install openocd clang-format-13 dfu-util protobuf-compiler +```shell +./fbt flash ``` -heatshrink has to be compiled [from sources](https://github.com/atomicobject/heatshrink). +## Flashing Firmware using USB -## Compile everything +Ensure that your Flipper is working, connect it using a USB cable and flash firmware using Flipper Build Tool: -```sh -./fbt +```shell +./fbt flash_usb ``` -Check `dist/` for build outputs. - -Use **`flipper-z-{target}-full-{suffix}.dfu`** to flash your device. +## Documentation -## Flash everything - -Connect your device via ST-Link and run: -```sh -./fbt firmware_flash -``` +- [Flipper Build Tool](/documentation/fbt.md) - building, flashing, and debugging Flipper software +- [Applications](/documentation/AppsOnSDCard.md), [Application Manifest](/documentation/AppManifests.md) - developing, building, deploying, and debugging Flipper applications +- [Hardware combos and Un-bricking](/documentation/KeyCombo.md) - recovering your Flipper from most nasty situations +- [Flipper File Formats](/documentation/file_formats) - everything about how Flipper stores your data and how you can work with it +- [Universal Remotes](/documentation/UniversalRemotes.md) - contributing your infrared remote to the universal remote database +- [Firmware Roadmap](/documentation/RoadMap.md) +- And much more in the [Documentation](/documentation) folder # Links -* Discord: [flipp.dev/discord](https://flipp.dev/discord) -* Website: [flipperzero.one](https://flipperzero.one) -* Kickstarter page: [kickstarter.com](https://www.kickstarter.com/projects/flipper-devices/flipper-zero-tamagochi-for-hackers) -* Forum: [forum.flipperzero.one](https://forum.flipperzero.one/) +- Discord: [flipp.dev/discord](https://flipp.dev/discord) +- Website: [flipperzero.one](https://flipperzero.one) +- Forum: [forum.flipperzero.one](https://forum.flipperzero.one/) +- Kickstarter: [kickstarter.com](https://www.kickstarter.com/projects/flipper-devices/flipper-zero-tamagochi-for-hackers) # Project structure - `applications` - Applications and services used in firmware - `assets` - Assets used by applications and services -- `furi` - Furi Core: os level primitives and helpers +- `furi` - Furi Core: OS-level primitives and helpers - `debug` - Debug tool: GDB-plugins, SVD-file and etc - `documentation` - Documentation generation system configs and input files - `firmware` - Firmware source code - `lib` - Our and 3rd party libraries, drivers, etc. - `scripts` - Supplementary scripts and python libraries home -Also pay attention to `ReadMe.md` files inside those directories. +Also, pay attention to `ReadMe.md` files inside those directories. diff --git a/documentation/AppManifests.md b/documentation/AppManifests.md index d70a12f9c9e..10970bdba0f 100644 --- a/documentation/AppManifests.md +++ b/documentation/AppManifests.md @@ -1,63 +1,63 @@ # Flipper Application Manifests (.fam) -All components of Flipper Zero firmware — services, user applications, system settings — are developed independently. Each component has a build system manifest file, named `application.fam`, which defines basic properties of that component and its relations to other parts of the system. +All components of Flipper Zero firmware — services, user applications, and system settings — are developed independently. Each component has a build system manifest file, named `application.fam`, which defines the basic properties of that component and its relations to other parts of the system. -When building firmware, **`fbt`** collects all application manifests and processes their dependencies. Then it builds only those components that are referenced in the current build configuration. See [fbt docs](./fbt.md#firmware-application-set) for details on build configurations. +When building firmware, **`fbt`** collects all application manifests and processes their dependencies. Then it builds only those components referenced in the current build configuration. See [fbt docs](./fbt.md#firmware-application-set) for details on build configurations. ## Application definition -Properties of a firmware component are declared in a form of a Python code snippet, forming a call to App() function with various parameters. +A firmware component's properties are declared in a Python code snippet, forming a call to App() function with various parameters. -Only 2 parameters are mandatory: ***appid*** and ***apptype***, others are optional and may only be meaningful for certain application types. +Only 2 parameters are mandatory: ***appid*** and ***apptype***; others are optional and may only be meaningful for certain application types. ### Parameters -* **appid**: string, application id within the build system. Used for specifying which applications to include in build configuration and to resolve dependencies and conflicts. +* **appid**: string, application id within the build system. Used to specify which applications to include in the build configuration and resolve dependencies and conflicts. * **apptype**: member of FlipperAppType.* enumeration. Valid values are: | Enum member | Firmware component type | |--------------|--------------------------| | SERVICE | System service, created at early startup | -| SYSTEM | Application not being shown in any menus. Can be started by other apps or from CLI | -| APP | Regular application for main menu | -| PLUGIN | Application to be built as a part of firmware an to be placed in Plugins menu | +| SYSTEM | Application is not being shown in any menus. It can be started by other apps or from CLI | +| APP | Regular application for the main menu | +| PLUGIN | Application to be built as a part of the firmware and to be placed in the Plugins menu | | DEBUG | Application only visible in Debug menu with debug mode enabled | | ARCHIVE | One and only Archive app | -| SETTINGS | Application to be placed in System settings menu | +| SETTINGS | Application to be placed in the system settings menu | | STARTUP | Callback function to run at system startup. Does not define a separate app | | EXTERNAL | Application to be built as .fap plugin | | METAPACKAGE | Does not define any code to be run, used for declaring dependencies and application bundles | * **name**: Name that is displayed in menus. -* **entry_point**: C function to be used as application's entry point. Note that C++ function names are mangled, so you need to wrap them in `extern "C"` in order to use them as entry points. +* **entry_point**: C function to be used as the application's entry point. Note that C++ function names are mangled, so you need to wrap them in `extern "C"` to use them as entry points. * **flags**: Internal flags for system apps. Do not use. -* **cdefines**: C preprocessor definitions to declare globally for other apps when current application is included in active build configuration. -* **requires**: List of application IDs to also include in build configuration, when current application is referenced in list of applications to build. -* **conflicts**: List of application IDs that current application conflicts with. If any of them is found in constructed application list, **`fbt`** will abort firmware build process. +* **cdefines**: C preprocessor definitions to declare globally for other apps when the current application is included in the active build configuration. +* **requires**: List of application IDs to include in the build configuration when the current application is referenced in the list of applications to build. +* **conflicts**: List of application IDs that the current application conflicts with. If any of them is found in the constructed application list, **`fbt`** will abort the firmware build process. * **provides**: Functionally identical to ***requires*** field. -* **stack_size**: Stack size, in bytes, to allocate for application on its startup. Note that allocating a stack that is too small for an app to run will cause system crash due to stack overflow, and allocating too much stack space will reduce usable heap memory size for apps to process data. *Note: you can use `ps` and `free` CLI commands to profile your app's memory usage.* -* **icon**: Animated icon name from built-in assets to be used when building app as a part of firmware. -* **order**: Order of an application within its group when sorting entries in it. The lower the order is, the closer to the start of the list the item is placed. *Used for ordering startup hooks and menu entries.* +* **stack_size**: Stack size, in bytes, to allocate for application on its startup. Note that allocating a stack too small for an app to run will cause a system crash due to stack overflow, and allocating too much stack space will reduce usable heap memory size for apps to process data. *Note: you can use `ps`, and `free` CLI commands to profile your app's memory usage.* +* **icon**: Animated icon name from built-in assets to be used when building the app as a part of the firmware. +* **order**: Order of an application within its group when sorting entries in it. The lower the order is, the closer to the start of the list the item is placed. *Used for ordering startup hooks and menu entries.* * **sdk_headers**: List of C header files from this app's code to include in API definitions for external applications. -* **targets**: list of strings, target names, which this application is compatible with. If not specified, application is built for all targets. Default value is `["all"]`. +* **targets**: list of strings, target names, which this application is compatible with. If not specified, the application is built for all targets. The default value is `["all"]`. #### Parameters for external applications The following parameters are used only for [FAPs](./AppsOnSDCard.md): -* **sources**: list of strings, file name masks, used for gathering sources within app folder. Default value of `["*.c*"]` includes C and C++ source files. Application cannot use `"lib"` folder for their own source code, as it is reserved for **fap_private_libs**. -* **fap_version**: tuple, 2 numbers in form of (x,y): application version to be embedded within .fap file. Default value is (0,1), meaning version "0.1". +* **sources**: list of strings, file name masks used for gathering sources within the app folder. The default value of `["*.c*"]` includes C and C++ source files. Applications cannot use the `"lib"` folder for their own source code, as it is reserved for **fap_private_libs**. +* **fap_version**: tuple, 2 numbers in the form of (x,y): application version to be embedded within .fap file. The default value is (0,1), meaning version "0.1". * **fap_icon**: name of a .png file, 1-bit color depth, 10x10px, to be embedded within .fap file. -* **fap_libs**: list of extra libraries to link application against. Provides access to extra functions that are not exported as a part of main firmware at expense of increased .fap file size and RAM consumption. -* **fap_category**: string, may be empty. App subcategory, also works as path of FAP within apps folder in the file system. +* **fap_libs**: list of extra libraries to link the application against. Provides access to extra functions that are not exported as a part of main firmware at the expense of increased .fap file size and RAM consumption. +* **fap_category**: string, may be empty. App subcategory, also determines the path of the FAP within apps folder in the file system. * **fap_description**: string, may be empty. Short application description. * **fap_author**: string, may be empty. Application's author. * **fap_weburl**: string, may be empty. Application's homepage. -* **fap_icon_assets**: string. If present, defines a folder name to be used for gathering image assets for this application. These images will be preprocessed and built alongside the application. See [FAP assets](./AppsOnSDCard.md#fap-assets) for details. +* **fap_icon_assets**: string. If present defines a folder name to be used for gathering image assets for this application. These images will be preprocessed and built alongside the application. See [FAP assets](./AppsOnSDCard.md#fap-assets) for details. * **fap_extbuild**: provides support for parts of application sources to be built by external tools. Contains a list of `ExtFile(path="file name", command="shell command")` definitions. **`fbt`** will run the specified command for each file in the list. -Note that commands are executed at the firmware root folder's root, and all intermediate files must be placed in a application's temporary build folder. For that, you can use pattern expansion by **`fbt`**: `${FAP_WORK_DIR}` will be replaced with the path to the application's temporary build folder, and `${FAP_SRC_DIR}` will be replaced with the path to the application's source folder. You can also use other variables defined internally by **`fbt`**. +Note that commands are executed at the firmware root folder's root, and all intermediate files must be placed in an application's temporary build folder. For that, you can use pattern expansion by **`fbt`**: `${FAP_WORK_DIR}` will be replaced with the path to the application's temporary build folder, and `${FAP_SRC_DIR}` will be replaced with the path to the application's source folder. You can also use other variables defined internally by **`fbt`**. Example for building an app from Rust sources: @@ -71,16 +71,16 @@ Example for building an app from Rust sources: ), ``` -* **fap_private_libs**: list of additional libraries that are distributed as sources alongside the application. These libraries will be built as a part of the application build process. +* **fap_private_libs**: a list of additional libraries distributed as sources alongside the application. These libraries will be built as a part of the application build process. Library sources must be placed in a subfolder of "`lib`" folder within the application's source folder. Each library is defined as a call to `Lib()` function, accepting the following parameters: - **name**: name of library's folder. Required. - - **fap_include_paths**: list of library's relative paths to add to parent fap's include path list. Default value is `["."]` meaning library's source root. - - **sources**: list of filename masks to be used for gathering include files for this library. Paths are relative to library's source root. Default value is `["*.c*"]`. - - **cflags**: list of additional compiler flags to be used for building this library. Default value is `[]`. - - **cdefines**: list of additional preprocessor definitions to be used for building this library. Default value is `[]`. - - **cincludes**: list of additional include paths to be used for building this library. Paths are relative to application's root. Can be used for providing external search paths for this library's code - for configuration headers. Default value is `[]`. + - **fap_include_paths**: list of library's relative paths to add to parent fap's include path list. The default value is `["."]` meaning the library's source root. + - **sources**: list of filename masks to be used for gathering include files for this library. Paths are relative to the library's source root. The default value is `["*.c*"]`. + - **cflags**: list of additional compiler flags to be used for building this library. The default value is `[]`. + - **cdefines**: list of additional preprocessor definitions to be used for building this library. The default value is `[]`. + - **cincludes**: list of additional include paths to be used for building this library. Paths are relative to the application's root. This can be used for providing external search paths for this library's code - for configuration headers. The default value is `[]`. Example for building an app with a private library: @@ -103,10 +103,10 @@ Example for building an app with a private library: ], ``` -For that snippet, **`fbt`** will build 2 libraries: one from sources in `lib/mbedtls` folder, and another from sources in `lib/loclass` folder. For `mbedtls` library, **`fbt`** will add `lib/mbedtls/include` to the list of include paths for the application and compile only the files specified in `sources` list. Additionally, **`fbt`** will enable `MBEDTLS_ERROR_C` preprocessor definition for `mbedtls` sources. -For `loclass` library, **`fbt`** will add `lib/loclass` to the list of include paths for the application and build all sources in that folder. Also **`fbt`** will disable treating compiler warnings as errors for `loclass` library specifically - that can be useful when compiling large 3rd-party codebases. +For that snippet, **`fbt`** will build 2 libraries: one from sources in `lib/mbedtls` folder and another from sources in `lib/loclass` folder. For the `mbedtls` library, **`fbt`** will add `lib/mbedtls/include` to the list of include paths for the application and compile only the files specified in the `sources` list. Additionally, **`fbt`** will enable `MBEDTLS_ERROR_C` preprocessor definition for `mbedtls` sources. +For the `loclass` library, **`fbt`** will add `lib/loclass` to the list of the include paths for the application and build all sources in that folder. Also, **`fbt`** will disable treating compiler warnings as errors for the `loclass` library, which can be useful when compiling large 3rd-party codebases. -Both libraries will be linked into the application. +Both libraries will be linked with the application. ## .fam file contents diff --git a/RoadMap.md b/documentation/RoadMap.md similarity index 59% rename from RoadMap.md rename to documentation/RoadMap.md index 32ed860f24e..612e55e6296 100644 --- a/RoadMap.md +++ b/documentation/RoadMap.md @@ -2,49 +2,47 @@ # Where we are (0.x.x branch) -Our goal for 0.x.x branch is to build stable usable apps and API. -First public release that we support in this branch is 0.43.1. Your device most likely came with this version. -You can develop applications but keep in mind that API is not final yet. +Our goal for 0.x.x branch is to build stable, usable apps and API. +The first public release in this branch is 0.43.1. ## What's already implemented +**System and HAL** + +- Furi Core +- Furi HAL +- Loading applications from SD + **Applications** - SubGhz: all most common protocols, reading RAW for everything else - 125kHz RFID: all most common protocols -- NFC: reading/emulating Mifare Ultralight, reading MiFare Classic and DESFire, basic EMV, basic NFC-B,F,V +- NFC: reading/emulating Mifare Ultralight, reading MiFare Classic and DESFire, basic EMV, basic NFC-B/F/V - Infrared: all most common RC protocols, RAW format for everything else - GPIO: UART bridge, basic GPIO controls - iButton: DS1990, Cyfral, Metacom - Bad USB: Full USB Rubber Ducky support, some extras for windows alt codes - U2F: Full U2F specification support -**Extras** +**External applications** -- BLE Keyboard +- Bluetooth - Snake game -**System and HAL** - -- Furi Core -- Furi HAL - # Where we're going (Version 1) -Main goal for 1.0.0 is to provide first stable version for both Users and Developers. +The main goal for 1.0.0 is to provide the first stable version for both Users and Developers. ## What we're planning to implement in 1.0.0 -- Loading applications from SD (tested as PoC, work scheduled for Q2) - More protocols (gathering feedback) - User documentation (work in progress) -- FuriCore: get rid of CMSIS API, replace hard real time timers, improve stability and performance (work in progress) - FuriHal: deep sleep mode, stable API, examples, documentation (work in progress) - Application improvements (a ton of things that we want to add and improve that are too numerous to list here) -## When will it happen and where I can see the progress? +## When will it happen, and where can I see the progress? -Release 1.0.0 will most likely happen around the end of Q3 +Release 1.0.0 will most likely happen around the end of 2023Q1 Development progress can be tracked in our public Miro board: From 90573fbeedb36767f35529eea3a063d6f06fa6a1 Mon Sep 17 00:00:00 2001 From: Tiernan Date: Wed, 28 Dec 2022 23:04:58 +1000 Subject: [PATCH 325/824] Picopass read bug fixes: (#2201) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix checking user elite keys * include calculated Kd when saving cards Co-authored-by: あく --- .../plugins/picopass/picopass_device.h | 3 + .../plugins/picopass/picopass_worker.c | 71 +++++++++++++++---- 2 files changed, 61 insertions(+), 13 deletions(-) diff --git a/applications/plugins/picopass/picopass_device.h b/applications/plugins/picopass/picopass_device.h index 26f2159419b..150b095a775 100644 --- a/applications/plugins/picopass/picopass_device.h +++ b/applications/plugins/picopass/picopass_device.h @@ -18,6 +18,9 @@ #define PICOPASS_CSN_BLOCK_INDEX 0 #define PICOPASS_CONFIG_BLOCK_INDEX 1 +#define PICOPASS_EPURSE_BLOCK_INDEX 2 +#define PICOPASS_KD_BLOCK_INDEX 3 +#define PICOPASS_KC_BLOCK_INDEX 4 #define PICOPASS_AIA_BLOCK_INDEX 5 #define PICOPASS_APP_FOLDER ANY_PATH("picopass") diff --git a/applications/plugins/picopass/picopass_worker.c b/applications/plugins/picopass/picopass_worker.c index bb1e513a22e..1ee814aa59d 100644 --- a/applications/plugins/picopass/picopass_worker.c +++ b/applications/plugins/picopass/picopass_worker.c @@ -175,13 +175,12 @@ ReturnCode picopass_read_preauth(PicopassBlock* AA1) { return ERR_NONE; } -ReturnCode picopass_auth(PicopassBlock* AA1, PicopassPacs* pacs) { +static ReturnCode picopass_auth_standard(uint8_t* csn, uint8_t* div_key) { rfalPicoPassReadCheckRes rcRes; rfalPicoPassCheckRes chkRes; ReturnCode err; - uint8_t div_key[8] = {0}; uint8_t mac[4] = {0}; uint8_t ccnr[12] = {0}; @@ -192,26 +191,34 @@ ReturnCode picopass_auth(PicopassBlock* AA1, PicopassPacs* pacs) { } memcpy(ccnr, rcRes.CCNR, sizeof(rcRes.CCNR)); // last 4 bytes left 0 - loclass_diversifyKey(AA1[PICOPASS_CSN_BLOCK_INDEX].data, picopass_iclass_key, div_key); + loclass_diversifyKey(csn, picopass_iclass_key, div_key); loclass_opt_doReaderMAC(ccnr, div_key, mac); - err = rfalPicoPassPollerCheck(mac, &chkRes); - if(err == ERR_NONE) { - return ERR_NONE; - } - FURI_LOG_E(TAG, "rfalPicoPassPollerCheck error %d", err); + return rfalPicoPassPollerCheck(mac, &chkRes); +} + +static ReturnCode picopass_auth_dict( + uint8_t* csn, + PicopassPacs* pacs, + uint8_t* div_key, + IclassEliteDictType dict_type) { + rfalPicoPassReadCheckRes rcRes; + rfalPicoPassCheckRes chkRes; - FURI_LOG_E(TAG, "Starting dictionary attack"); + ReturnCode err = ERR_PARAM; + + uint8_t mac[4] = {0}; + uint8_t ccnr[12] = {0}; size_t index = 0; uint8_t key[PICOPASS_BLOCK_LEN] = {0}; - if(!iclass_elite_dict_check_presence(IclassEliteDictTypeFlipper)) { + if(!iclass_elite_dict_check_presence(dict_type)) { FURI_LOG_E(TAG, "Dictionary not found"); return ERR_PARAM; } - IclassEliteDict* dict = iclass_elite_dict_alloc(IclassEliteDictTypeFlipper); + IclassEliteDict* dict = iclass_elite_dict_alloc(dict_type); if(!dict) { FURI_LOG_E(TAG, "Dictionary not allocated"); return ERR_PARAM; @@ -235,11 +242,11 @@ ReturnCode picopass_auth(PicopassBlock* AA1, PicopassPacs* pacs) { err = rfalPicoPassPollerReadCheck(&rcRes); if(err != ERR_NONE) { FURI_LOG_E(TAG, "rfalPicoPassPollerReadCheck error %d", err); - return err; + break; } memcpy(ccnr, rcRes.CCNR, sizeof(rcRes.CCNR)); // last 4 bytes left 0 - loclass_iclass_calc_div_key(AA1[PICOPASS_CSN_BLOCK_INDEX].data, key, div_key, true); + loclass_iclass_calc_div_key(csn, key, div_key, true); loclass_opt_doReaderMAC(ccnr, div_key, mac); err = rfalPicoPassPollerCheck(mac, &chkRes); @@ -254,6 +261,39 @@ ReturnCode picopass_auth(PicopassBlock* AA1, PicopassPacs* pacs) { return err; } +ReturnCode picopass_auth(PicopassBlock* AA1, PicopassPacs* pacs) { + ReturnCode err; + + FURI_LOG_E(TAG, "Trying standard legacy key"); + err = picopass_auth_standard( + AA1[PICOPASS_CSN_BLOCK_INDEX].data, AA1[PICOPASS_KD_BLOCK_INDEX].data); + if(err == ERR_NONE) { + return ERR_NONE; + } + + FURI_LOG_E(TAG, "Starting user dictionary attack"); + err = picopass_auth_dict( + AA1[PICOPASS_CSN_BLOCK_INDEX].data, + pacs, + AA1[PICOPASS_KD_BLOCK_INDEX].data, + IclassEliteDictTypeUser); + if(err == ERR_NONE) { + return ERR_NONE; + } + + FURI_LOG_E(TAG, "Starting in-built dictionary attack"); + err = picopass_auth_dict( + AA1[PICOPASS_CSN_BLOCK_INDEX].data, + pacs, + AA1[PICOPASS_KD_BLOCK_INDEX].data, + IclassEliteDictTypeFlipper); + if(err == ERR_NONE) { + return ERR_NONE; + } + + return err; +} + ReturnCode picopass_read_card(PicopassBlock* AA1) { ReturnCode err; @@ -262,6 +302,11 @@ ReturnCode picopass_read_card(PicopassBlock* AA1) { PICOPASS_MAX_APP_LIMIT; for(size_t i = 2; i < app_limit; i++) { + if(i == PICOPASS_KD_BLOCK_INDEX) { + // Skip over Kd block which is populated earlier (READ of Kd returns all FF's) + continue; + } + rfalPicoPassReadBlockRes block; err = rfalPicoPassPollerReadBlock(i, &block); if(err != ERR_NONE) { From 3108dc7c8c093f6235f201e3bcddf2500f125f92 Mon Sep 17 00:00:00 2001 From: Konstantin Volkov <72250702+doomwastaken@users.noreply.github.com> Date: Wed, 28 Dec 2022 17:16:06 +0300 Subject: [PATCH 326/824] Splitting units and updater benches (#2165) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * test run, moved updated to separate physical runner/flipper/card * simplified units, removed re-flashing, moved format to beginning of run * added reboot requence and mini optimizations * forgot gitadd, added script modifications, workflow changes * fixed linter issues * moved updater to unit bench for speed up * changes to units, flash (not full) on second update, new fbt GDB thread check * changed serial of second device * testing pipelines, added failing unit test * fixed gdb step * fixed gdb step v2 electric boogaloo * fixed gdb step v3, fixed target * reverted while1 in units, tests complete * testing colored output * trying different term setting * debug outputs for terminal * fixed typo in SConstruct and another terminal test * reverted changes, no colored output, for production * fixed log output to readable format * fixed linter Co-authored-by: Konstantin Volkov Co-authored-by: あく --- .github/workflows/unit_tests.yml | 71 ++++++--------------------- .github/workflows/updater_test.yml | 77 ++++++++++++++++++++++++++++++ SConstruct | 14 ++++++ scripts/power.py | 68 ++++++++++++++++++++++++++ scripts/testing/await_flipper.py | 16 +++++-- scripts/testing/units.py | 46 +++++++++++------- 6 files changed, 214 insertions(+), 78 deletions(-) create mode 100644 .github/workflows/updater_test.yml create mode 100755 scripts/power.py diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index eb687b6c921..ac3fc368433 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -9,7 +9,7 @@ env: FBT_TOOLCHAIN_PATH: /opt jobs: - run_units_on_test_bench: + run_units_on_bench: runs-on: [self-hosted, FlipperZeroTest] steps: - name: 'Decontaminate previous build leftovers' @@ -29,81 +29,38 @@ jobs: run: | echo "flipper=/dev/ttyACM0" >> $GITHUB_OUTPUT - - name: 'Flashing target firmware' - id: first_full_flash - run: | - ./fbt flash_usb_full PORT=${{steps.device.outputs.flipper}} FORCE=1 - source scripts/toolchain/fbtenv.sh - python3 scripts/testing/await_flipper.py ${{steps.device.outputs.flipper}} - - - name: 'Validating updater' - id: second_full_flash - if: success() - run: | - ./fbt flash_usb_full PORT=${{steps.device.outputs.flipper}} FORCE=1 - source scripts/toolchain/fbtenv.sh - python3 scripts/testing/await_flipper.py ${{steps.device.outputs.flipper}} - - name: 'Flash unit tests firmware' id: flashing if: success() - run: | + run: | ./fbt flash OPENOCD_ADAPTER_SERIAL=2A0906016415303030303032 FIRMWARE_APP_SET=unit_tests FORCE=1 - - name: 'Wait for flipper to finish updating' - id: connect + - name: 'Wait for flipper and format ext' + id: format_ext if: steps.flashing.outcome == 'success' run: | source scripts/toolchain/fbtenv.sh python3 scripts/testing/await_flipper.py ${{steps.device.outputs.flipper}} + python3 scripts/storage.py -p ${{steps.device.outputs.flipper}} format_ext - - name: 'Copy assets and unit tests data to flipper' + - name: 'Copy assets and unit data, reboot and wait for flipper' id: copy - if: steps.connect.outcome == 'success' + if: steps.format_ext.outcome == 'success' run: | source scripts/toolchain/fbtenv.sh + python3 scripts/storage.py -p ${{steps.device.outputs.flipper}} -f send assets/resources /ext python3 scripts/storage.py -p ${{steps.device.outputs.flipper}} -f send assets/unit_tests /ext/unit_tests + python3 scripts/power.py -p ${{steps.device.outputs.flipper}} reboot + python3 scripts/testing/await_flipper.py ${{steps.device.outputs.flipper}} - name: 'Run units and validate results' + id: run_units if: steps.copy.outcome == 'success' run: | source scripts/toolchain/fbtenv.sh python3 scripts/testing/units.py ${{steps.device.outputs.flipper}} - - name: 'Get last release tag' - id: release_tag - if: always() - run: | - echo "tag=$(git tag -l --sort=-version:refname | grep -v "rc\|RC" | head -1)" >> $GITHUB_OUTPUT - - - name: 'Decontaminate previous build leftovers' - if: always() + - name: 'Check GDB output' + if: failure() run: | - if [ -d .git ]; then - git submodule status || git checkout "$(git rev-list --max-parents=0 HEAD | tail -n 1)" - fi - - - name: 'Checkout latest release' - uses: actions/checkout@v3 - if: always() - with: - fetch-depth: 0 - ref: ${{ steps.release_tag.outputs.tag }} - - - name: 'Flash last release' - if: always() - run: | - ./fbt flash OPENOCD_ADAPTER_SERIAL=2A0906016415303030303032 FIRMWARE_APP_SET=unit_tests FORCE=1 - - - name: 'Wait for flipper to finish updating' - if: always() - run: | - source scripts/toolchain/fbtenv.sh - python3 scripts/testing/await_flipper.py ${{steps.device.outputs.flipper}} - - - name: 'Format flipper SD card' - id: format - if: always() - run: | - source scripts/toolchain/fbtenv.sh - python3 scripts/storage.py -p ${{steps.device.outputs.flipper}} format_ext + ./fbt gdb_trace_all OPENOCD_ADAPTER_SERIAL=2A0906016415303030303032 FIRMWARE_APP_SET=unit_tests FORCE=1 diff --git a/.github/workflows/updater_test.yml b/.github/workflows/updater_test.yml new file mode 100644 index 00000000000..d4ca56fad7a --- /dev/null +++ b/.github/workflows/updater_test.yml @@ -0,0 +1,77 @@ +name: 'Updater test' + +on: + pull_request: + +env: + TARGETS: f7 + DEFAULT_TARGET: f7 + FBT_TOOLCHAIN_PATH: /opt + +jobs: + test_updater_on_bench: + runs-on: [self-hosted, FlipperZeroTest] # currently on same bench as units, needs different bench + steps: + - name: 'Decontaminate previous build leftovers' + run: | + if [ -d .git ]; then + git submodule status || git checkout "$(git rev-list --max-parents=0 HEAD | tail -n 1)" + fi + + - name: Checkout code + uses: actions/checkout@v3 + with: + fetch-depth: 0 + ref: ${{ github.event.pull_request.head.sha }} + + - name: 'Get flipper from device manager (mock)' + id: device + run: | + echo "flipper=/dev/ttyACM0" >> $GITHUB_OUTPUT + + - name: 'Flashing target firmware' + id: first_full_flash + run: | + source scripts/toolchain/fbtenv.sh + ./fbt flash_usb_full PORT=${{steps.device.outputs.flipper}} FORCE=1 + python3 scripts/testing/await_flipper.py ${{steps.device.outputs.flipper}} + + - name: 'Validating updater' + id: second_full_flash + if: success() + run: | + source scripts/toolchain/fbtenv.sh + ./fbt flash_usb PORT=${{steps.device.outputs.flipper}} FORCE=1 + python3 scripts/testing/await_flipper.py ${{steps.device.outputs.flipper}} + + - name: 'Get last release tag' + id: release_tag + if: failure() + run: | + echo "tag=$(git tag -l --sort=-version:refname | grep -v "rc\|RC" | head -1)" >> $GITHUB_OUTPUT + + - name: 'Decontaminate previous build leftovers' + if: failure() + run: | + if [ -d .git ]; then + git submodule status || git checkout "$(git rev-list --max-parents=0 HEAD | tail -n 1)" + fi + + - name: 'Checkout latest release' + uses: actions/checkout@v3 + if: failure() + with: + fetch-depth: 0 + ref: ${{ steps.release_tag.outputs.tag }} + + - name: 'Flash last release' + if: failure() + run: | + ./fbt flash OPENOCD_ADAPTER_SERIAL=2A0906016415303030303032 FORCE=1 + + - name: 'Wait for flipper and format ext' + if: failure() + run: | + source scripts/toolchain/fbtenv.sh + python3 scripts/testing/await_flipper.py ${{steps.device.outputs.flipper}} + python3 scripts/storage.py -p ${{steps.device.outputs.flipper}} format_ext diff --git a/SConstruct b/SConstruct index 474175c1421..138b52d9363 100644 --- a/SConstruct +++ b/SConstruct @@ -194,6 +194,20 @@ firmware_bm_flash = distenv.PhonyTarget( ], ) +gdb_backtrace_all_threads = distenv.PhonyTarget( + "gdb_trace_all", + "$GDB $GDBOPTS $SOURCES $GDBFLASH", + source=firmware_env["FW_ELF"], + GDBOPTS="${GDBOPTS_BASE}", + GDBREMOTE="${OPENOCD_GDB_PIPE}", + GDBFLASH=[ + "-ex", + "thread apply all bt", + "-ex", + "quit", + ], +) + # Debugging firmware firmware_debug = distenv.PhonyTarget( "debug", diff --git a/scripts/power.py b/scripts/power.py new file mode 100755 index 00000000000..45a130c5917 --- /dev/null +++ b/scripts/power.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 + +from flipper.app import App +from flipper.storage import FlipperStorage +from flipper.utils.cdc import resolve_port + + +class Main(App): + # this is basic use without sub-commands, simply to reboot flipper / power it off, not meant as a full CLI wrapper + def init(self): + self.parser.add_argument("-p", "--port", help="CDC Port", default="auto") + + self.subparsers = self.parser.add_subparsers(help="sub-command help") + + self.parser_power_off = self.subparsers.add_parser( + "power_off", help="Power off command, won't return to CLI" + ) + self.parser_power_off.set_defaults(func=self.power_off) + + self.parser_reboot = self.subparsers.add_parser( + "reboot", help="Reboot command help" + ) + self.parser_reboot.set_defaults(func=self.reboot) + + self.parser_reboot2dfu = self.subparsers.add_parser( + "reboot2dfu", help="Reboot to DFU, won't return to CLI" + ) + self.parser_reboot2dfu.set_defaults(func=self.reboot2dfu) + + def _get_flipper(self): + if not (port := resolve_port(self.logger, self.args.port)): + return None + + flipper = FlipperStorage(port) + flipper.start() + return flipper + + def power_off(self): + if not (flipper := self._get_flipper()): + return 1 + + self.logger.debug("Powering off") + flipper.send("power off" + "\r") + flipper.stop() + return 0 + + def reboot(self): + if not (flipper := self._get_flipper()): + return 1 + + self.logger.debug("Rebooting") + flipper.send("power reboot" + "\r") + flipper.stop() + return 0 + + def reboot2dfu(self): + if not (flipper := self._get_flipper()): + return 1 + + self.logger.debug("Rebooting to DFU") + flipper.send("power reboot2dfu" + "\r") + flipper.stop() + + return 0 + + +if __name__ == "__main__": + Main()() diff --git a/scripts/testing/await_flipper.py b/scripts/testing/await_flipper.py index 704d75a7549..2b4c8b4c39b 100755 --- a/scripts/testing/await_flipper.py +++ b/scripts/testing/await_flipper.py @@ -1,6 +1,8 @@ #!/usr/bin/env python3 - -import sys, os, time +import logging +import os +import sys +import time def flp_serial_by_name(flp_name): @@ -31,6 +33,12 @@ def main(): flipper_name = sys.argv[1] elapsed = 0 flipper = flp_serial_by_name(flipper_name) + logging.basicConfig( + format="%(asctime)s %(levelname)-8s %(message)s", + level=logging.INFO, + datefmt="%Y-%m-%d %H:%M:%S", + ) + logging.info("Waiting for Flipper to be ready...") while flipper == "" and elapsed < UPDATE_TIMEOUT: elapsed += 1 @@ -38,9 +46,11 @@ def main(): flipper = flp_serial_by_name(flipper_name) if flipper == "": - print(f"Cannot find {flipper_name} flipper. Guess your flipper swam away") + logging.error("Flipper not found!") sys.exit(1) + logging.info(f"Found Flipper at {flipper}") + sys.exit(0) diff --git a/scripts/testing/units.py b/scripts/testing/units.py index 83b07899a9a..5083bcd4350 100755 --- a/scripts/testing/units.py +++ b/scripts/testing/units.py @@ -1,28 +1,32 @@ #!/usr/bin/env python3 - -import sys, os -import serial +import logging import re +import sys +import serial from await_flipper import flp_serial_by_name -LEAK_THRESHOLD = 3000 # added until units are fixed - - def main(): + logging.basicConfig( + format="%(asctime)s %(levelname)-8s %(message)s", + level=logging.INFO, + datefmt="%Y-%m-%d %H:%M:%S", + ) + logging.info("Trying to run units on flipper") flp_serial = flp_serial_by_name(sys.argv[1]) if flp_serial == "": - print("Name or serial port is invalid") + logging.error("Flipper not found!") sys.exit(1) with serial.Serial(flp_serial, timeout=1) as flipper: + logging.info(f"Found Flipper at {flp_serial}") flipper.baudrate = 230400 flipper.flushOutput() flipper.flushInput() - flipper.timeout = 300 + flipper.timeout = 180 flipper.read_until(b">: ").decode("utf-8") flipper.write(b"unit_tests\r") @@ -41,9 +45,13 @@ def main(): status_pattern = re.compile(status_re) tests, time, leak, status = None, None, None, None + total = 0 for line in lines: - print(line) + logging.info(line) + if "()" in line: + total += 1 + if not tests: tests = re.match(tests_pattern, line) if not time: @@ -53,8 +61,8 @@ def main(): if not status: status = re.match(status_pattern, line) - if leak is None or time is None or leak is None or status is None: - print("Failed to get data. Or output is corrupt") + if None in (tests, time, leak, status): + logging.error(f"Failed to parse output: {leak} {time} {leak} {status}") sys.exit(1) leak = int(re.findall(r"[- ]\d+", leak.group(0))[0]) @@ -62,16 +70,18 @@ def main(): tests = int(re.findall(r"\d+", tests.group(0))[0]) time = int(re.findall(r"\d+", time.group(0))[0]) - if tests > 0 or leak > LEAK_THRESHOLD or status != "PASSED": - print(f"Got {tests} failed tests.") - print(f"Leaked {leak} bytes.") - print(f"Status by flipper: {status}") - print(f"Time elapsed {time/1000} seconds.") + if tests > 0 or status != "PASSED": + logging.error(f"Got {tests} failed tests.") + logging.error(f"Leaked (not failing on this stat): {leak}") + logging.error(f"Status: {status}") + logging.error(f"Time: {time/1000} seconds") sys.exit(1) - print( - f"Tests ran successfully! Time elapsed {time/1000} seconds. Passed {tests} tests." + logging.info(f"Leaked (not failing on this stat): {leak}") + logging.info( + f"Tests ran successfully! Time elapsed {time/1000} seconds. Passed {total} tests." ) + sys.exit(0) From 27ee0f73f7b81e6791c07f036939015dec9f6a85 Mon Sep 17 00:00:00 2001 From: Konstantin Volkov <72250702+doomwastaken@users.noreply.github.com> Date: Wed, 28 Dec 2022 17:30:20 +0300 Subject: [PATCH 327/824] Fixing various typos in readme files #2208 Co-authored-by: Konstantin Volkov --- .github/ISSUE_TEMPLATE/03_feature_request.yml | 2 +- CODING_STYLE.md | 14 +++++++------- CONTRIBUTING.md | 6 +++--- assets/dolphin/ReadMe.md | 2 +- documentation/AppManifests.md | 2 ++ documentation/AppsOnSDCard.md | 2 +- documentation/file_formats/BadUsbScriptFormat.md | 2 +- documentation/file_formats/InfraredFileFormats.md | 10 +++++----- documentation/file_formats/NfcFileFormats.md | 2 +- documentation/file_formats/SubGhzFileFormats.md | 2 +- firmware/ReadMe.md | 4 ++-- lib/ReadMe.md | 2 +- scripts/ReadMe.md | 6 +++--- 13 files changed, 29 insertions(+), 27 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/03_feature_request.yml b/.github/ISSUE_TEMPLATE/03_feature_request.yml index 2af114547de..052cd34048a 100644 --- a/.github/ISSUE_TEMPLATE/03_feature_request.yml +++ b/.github/ISSUE_TEMPLATE/03_feature_request.yml @@ -14,7 +14,7 @@ body: description: | Please describe your feature request in as many details as possible. - Describe what it should do. - - Note whetever it is to extend existing functionality or introduce new functionality. + - Note whether it is to extend existing functionality or introduce new functionality. validations: required: true - type: textarea diff --git a/CODING_STYLE.md b/CODING_STYLE.md index 31426b941b4..6c7d6d79230 100644 --- a/CODING_STYLE.md +++ b/CODING_STYLE.md @@ -3,15 +3,15 @@ Nice to see you reading this document, we really appreciate it. As all documents of this kind it's unable to cover everything. -But it will cover general rules that we enforcing on PR review. +But it will cover general rules that we are enforcing on PR review. -Also we already have automatic rules checking and formatting, -but it got it's limitations and this guide is still mandatory. +Also, we already have automatic rules checking and formatting, +but it got its limitations and this guide is still mandatory. -Some part of this project do have it's own naming and coding guides. +Some part of this project do have its own naming and coding guides. For example: assets. Take a look into `ReadMe.md` in assets folder for more details. -Also 3rd party libraries are none of our concern. +Also, 3rd party libraries are none of our concern. And yes, this set is not final and we are open to discussion. If you want to add/remove/change something here please feel free to open new ticket. @@ -30,7 +30,7 @@ Our guide is inspired by, but not claiming to be compatible with: Code we write is intended to be public. Avoid one-liners from hell and keep code complexity under control. -Try to make code self explanatory and add comments if needed. +Try to make code self-explanatory and add comments if needed. Leave references to standards that you are implementing. Use project wiki to document new/reverse engineered standards. @@ -89,7 +89,7 @@ Enforced by linter. Suffixes: - `alloc` - allocate and init instance. C style constructor. Returns pointer to instance. -- `free` - deinit and release instance. C style destructor. Takes pointer to instance. +- `free` - de-init and release instance. C style destructor. Takes pointer to instance. # C++ coding style diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index dc1d41e27d1..3f4853116a5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -23,8 +23,8 @@ Before writing code and creating PR make sure that it aligns with our mission an - PR that contains code intended to commit crimes is not going to be accepted. - Your PR must comply with our [Coding Style](CODING_STYLE.md) - Your PR must contain code compatible with project [LICENSE](LICENSE). -- PR will only be merged if it pass CI/CD. -- PR will only be merged if it pass review by code owner. +- PR will only be merged if it passes CI/CD. +- PR will only be merged if it passes review by code owner. Feel free to ask questions in issues if you're not sure. @@ -59,7 +59,7 @@ Commit the changes once you are happy with them. Make sure that code compilation ### Pull Request When you're done making the changes, open a pull request, often referred to as a PR. -- Fill out the "Ready for review" template so we can review your PR. This template helps reviewers understand your changes and the purpose of your pull request. +- Fill out the "Ready for review" template, so we can review your PR. This template helps reviewers understand your changes and the purpose of your pull request. - Don't forget to [link PR to issue](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue) if you are solving one. - Enable the checkbox to [allow maintainer edits](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/allowing-changes-to-a-pull-request-branch-created-from-a-fork) so the branch can be updated for a merge. Once you submit your PR, a Docs team member will review your proposal. We may ask questions or request for additional information. diff --git a/assets/dolphin/ReadMe.md b/assets/dolphin/ReadMe.md index 643086c26f7..e7572571ca0 100644 --- a/assets/dolphin/ReadMe.md +++ b/assets/dolphin/ReadMe.md @@ -26,7 +26,7 @@ Version: 1 - `Name` - name of animation. Must be exact animation directory name. - `Min butthurt`, `Max butthurt` - range of dolphin's butthurt for this animation. - `Min level`, `Max level` - range of dolphin's level for this animation. If 0, this animation doesn't participate in random idle animation selection and can only be selected by exact name. -- `Weight` - chance of this animation to be choosen at random animation selection. +- `Weight` - chance of this animation to be chosen at random animation selection. Some animations can be excluded from participation in random animation selection, such as `L1_NoSd_128x49`. diff --git a/documentation/AppManifests.md b/documentation/AppManifests.md index 10970bdba0f..2ffa3c0c252 100644 --- a/documentation/AppManifests.md +++ b/documentation/AppManifests.md @@ -57,8 +57,10 @@ The following parameters are used only for [FAPs](./AppsOnSDCard.md): * **fap_weburl**: string, may be empty. Application's homepage. * **fap_icon_assets**: string. If present defines a folder name to be used for gathering image assets for this application. These images will be preprocessed and built alongside the application. See [FAP assets](./AppsOnSDCard.md#fap-assets) for details. * **fap_extbuild**: provides support for parts of application sources to be built by external tools. Contains a list of `ExtFile(path="file name", command="shell command")` definitions. **`fbt`** will run the specified command for each file in the list. + Note that commands are executed at the firmware root folder's root, and all intermediate files must be placed in an application's temporary build folder. For that, you can use pattern expansion by **`fbt`**: `${FAP_WORK_DIR}` will be replaced with the path to the application's temporary build folder, and `${FAP_SRC_DIR}` will be replaced with the path to the application's source folder. You can also use other variables defined internally by **`fbt`**. + Example for building an app from Rust sources: ```python diff --git a/documentation/AppsOnSDCard.md b/documentation/AppsOnSDCard.md index 4acb3ec373e..d38837276f4 100644 --- a/documentation/AppsOnSDCard.md +++ b/documentation/AppsOnSDCard.md @@ -37,7 +37,7 @@ With it, you can debug FAPs as if they were a part of main firmware — inspect ### Setting up debugging environment -Debugging support script looks up debugging information in latest firmware build dir (`build/latest`). That directory is symlinked by fbt to the latest firmware configuration (Debug or Release) build dir, when you run `./fbt` for chosen configuration. See [fbt docs](./fbt.md#nb) for details. +Debugging support script looks up debugging information in the latest firmware build dir (`build/latest`). That directory is symlinked by fbt to the latest firmware configuration (Debug or Release) build dir, when you run `./fbt` for chosen configuration. See [fbt docs](./fbt.md#nb) for details. So, to debug FAPs, do the following: 1. Build firmware with `./fbt` diff --git a/documentation/file_formats/BadUsbScriptFormat.md b/documentation/file_formats/BadUsbScriptFormat.md index 321eff2462e..713a9ff2635 100644 --- a/documentation/file_formats/BadUsbScriptFormat.md +++ b/documentation/file_formats/BadUsbScriptFormat.md @@ -1,5 +1,5 @@ # Command syntax -BadUsb app uses extended Duckyscript syntax. It is compatible with classic USB Rubber Ducky 1.0 scripts, but provides some additional commands and features, such as custom USB ID, ALT+Numpad input method, SYSRQ command and more fuctional keys. +BadUsb app uses extended Duckyscript syntax. It is compatible with classic USB Rubber Ducky 1.0 scripts, but provides some additional commands and features, such as custom USB ID, ALT+Numpad input method, SYSRQ command and more functional keys. # Script file format BadUsb app can execute only text scrips from .txt files, no compilation is required. Both `\n` and `\r\n` line endings are supported. Empty lines are allowed. You can use spaces ore tabs for line indentation. # Command set diff --git a/documentation/file_formats/InfraredFileFormats.md b/documentation/file_formats/InfraredFileFormats.md index a6d6c276d92..409bf26e88e 100644 --- a/documentation/file_formats/InfraredFileFormats.md +++ b/documentation/file_formats/InfraredFileFormats.md @@ -95,17 +95,17 @@ Note: a single parsed signal must be represented as an array of size 1. | data | raw | uint32 | Ditto. | #### Signal names -The signal names in an `.irtest` file folow a convention ``, where the name is one of: +The signal names in an `.irtest` file follow a convention ``, where the name is one of: - decoder_input - decoder_expected - encoder_decoder_input, and the number is a sequential integer: 1, 2, 3...etc, which produces names like `decoder_input1`, `encoder_decoder_input3`, and so on. -| Name | Type | Description | -| --------------------- | ------------ | ----------- | -| decoder_input | raw | A raw signal contaning the decoder input. Is also used as the expected encoder output. | +| Name | Type | Description | +| --------------------- | ------------ |-------------------------------------------------------------------------------------------------------| +| decoder_input | raw | A raw signal containing the decoder input. Is also used as the expected encoder output. | | decoder_expected | parsed_array | An array of parsed signals containing the expected decoder output. Is also used as the encoder input. | -| encoder_decoder_input | parsed_array | An array of parsed signals containing both the encoder-decoder input and expected output. | +| encoder_decoder_input | parsed_array | An array of parsed signals containing both the encoder-decoder input and expected output. | See [Unit Tests](/documentation/UnitTests.md#infrared) for more info. diff --git a/documentation/file_formats/NfcFileFormats.md b/documentation/file_formats/NfcFileFormats.md index 90dcaea1c33..e04f3b68ce9 100644 --- a/documentation/file_formats/NfcFileFormats.md +++ b/documentation/file_formats/NfcFileFormats.md @@ -68,7 +68,7 @@ This file format is used to store the UID, SAK and ATQA of a Mifare Ultralight/N The "Signature" field contains the reply of the tag to the READ_SIG command. More on that can be found here: (page 31) -The "Mifare version" field is not related to the file format version, but to the Mifare Ultralight version. It contains the responce of the tag to the GET_VERSION command. More on that can be found here: (page 21) +The "Mifare version" field is not related to the file format version, but to the Mifare Ultralight version. It contains the response of the tag to the GET_VERSION command. More on that can be found here: (page 21) Other fields are the direct representation of the card's internal state, more on them can be found in the same datasheet. diff --git a/documentation/file_formats/SubGhzFileFormats.md b/documentation/file_formats/SubGhzFileFormats.md index ae80e6e0e9c..1446ecd0d91 100644 --- a/documentation/file_formats/SubGhzFileFormats.md +++ b/documentation/file_formats/SubGhzFileFormats.md @@ -197,7 +197,7 @@ For each key, a name and encryption method must be specified, according to comme ## SubGhz `setting_user` File -This file contains additional radio presets and frequencies for SubGhz application. It is used to add new presets and frequencies for existing presets. This file is be loaded on subghz application start and is located at path `/ext/subghz/assets/setting_user`. +This file contains additional radio presets and frequencies for SubGhz application. It is used to add new presets and frequencies for existing presets. This file is being loaded on subghz application start and is located at path `/ext/subghz/assets/setting_user`. ### File Format diff --git a/firmware/ReadMe.md b/firmware/ReadMe.md index d2baedfd150..c39f8c4ab31 100644 --- a/firmware/ReadMe.md +++ b/firmware/ReadMe.md @@ -14,9 +14,9 @@ What does it do? |-----------|-------------------|-----------------------|-----------------------| | f7 | 0x08000000 | L+Back, release both | L+Back, release Back | -Also there is a "hardware" ST bootloader combo available even on a bricked or empty device: L+Ok+Back, release Back, Left. +Also, there is a "hardware" ST bootloader combo available even on a bricked or empty device: L+Ok+Back, release Back, Left. Target independent code and headers in `target/include` folders. More details in `documentation/KeyCombo.md` # Building -Check out `documentation/fbt.md` on how to build and flash firmware. \ No newline at end of file +Check out `documentation/fbt.md` on how to build and flash firmware. diff --git a/lib/ReadMe.md b/lib/ReadMe.md index 9cd846a0ac7..82dcb74c7aa 100644 --- a/lib/ReadMe.md +++ b/lib/ReadMe.md @@ -11,7 +11,7 @@ - `infrared` - Infrared library - `libusb_stm32` - STM32 USB library - `littlefs` - Internal storage file system -- `micro-ecc` - Elliptic Curve Crpytography library +- `micro-ecc` - Elliptic Curve Crytography library - `microtar` - TAR archive support library - `mlib` - Algorithms and containers - `nanopb` - Nano Protobuf library diff --git a/scripts/ReadMe.md b/scripts/ReadMe.md index d37e67c90c0..a9feba11b37 100644 --- a/scripts/ReadMe.md +++ b/scripts/ReadMe.md @@ -27,14 +27,14 @@ Also display type, region and etc... ## Core1 and Core2 firmware flashing Core2 goes first, then Core1. -Never flash FUS or you will loose your job, girlfriend and keys in secure enclave. +Never flash FUS or you will lose your job, girlfriend and keys in secure enclave. ## Option Bytes !!! Setting incorrect Option Bytes may brick your MCU !!! Defaults are mostly OK, but there are couple things that we'd like to tune. -Also OB may be damaged, so we've made couple scripts to check and set option bytes. +Also, OB may be damaged, so we've made couple scripts to check and set option bytes. !!! Setting incorrect Option Bytes may brick your MCU !!! @@ -69,4 +69,4 @@ Then run python scripts/slideshow.py -i assets/slideshow/my_show/ -o assets/slideshow/my_show/.slideshow ``` -Upload generated .slideshow file to Flipper's internal storage and restart it. \ No newline at end of file +Upload generated .slideshow file to Flipper's internal storage and restart it. From 4accce93cfaacee809e431bd9d33363344974ca2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zo=C3=AB=20Prosvetova?= <109866245+ZoeMeetAgain@users.noreply.github.com> Date: Wed, 28 Dec 2022 15:39:36 +0100 Subject: [PATCH 328/824] Update KeyCombo.md (#2213) Grammatical corrections and changes --- documentation/KeyCombo.md | 52 +++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/documentation/KeyCombo.md b/documentation/KeyCombo.md index e6f55dc5295..93ceb204c34 100644 --- a/documentation/KeyCombo.md +++ b/documentation/KeyCombo.md @@ -1,6 +1,6 @@ # Key Combos -There are times when your flipper feels blue and doesn't respond to your commands. +There are times when your Flipper feels blue and doesn't respond to your commands. In that case, you may find this guide useful. @@ -12,10 +12,10 @@ In that case, you may find this guide useful. - Press `LEFT` and `BACK` and hold for a couple of seconds - Release `LEFT` and `BACK` -This combo performs hardware reset by pulling MCU reset line down. +This combo performs a hardware reset by pulling MCU reset line down. Main components involved: Keys -> DD8(NC7SZ32M5X, OR-gate) -> DD1(STM32WB55, MCU) -There is 1 case when it's not working: +There is 1 case where it does not work: - MCU debug block is active and holding reset line from inside. @@ -25,32 +25,32 @@ There is 1 case when it's not working: - Disconnect USB and any external power supplies - Disconnect USB once again - Make sure that you've disconnected USB and any external power supplies -- Press `BACK` and hold for 30 seconds (Only will work with USB Disconnected) +- Press `BACK` and hold for 30 seconds (Will only work with USB disconnected) - If you have not disconnected USB, then disconnect USB and repeat previous step - Release `BACK` key This combo performs a reset by switching SYS power line off and then on. Main components involved: Keys -> DD6(bq25896, charger) -There is 1 case when it's not working: +There is 1 case where it does not work: - Power supply is connected to USB or 5V_ext ### Software DFU -- Press `LEFT` on boot to enter DFU with flipper boot-loader +- Press `LEFT` on boot to enter DFU with Flipper boot-loader -There is 1 case when it's not working: +There is 1 case where it does not work: -- Flipper Boot-loader is damaged or absent +- Flipper boot-loader is damaged or absent ### Hardware DFU - Press `OK` on boot to enter DFU with ST boot-loader -There is 1 case when it's not working: +There is 1 case where it does not work: - Option Bytes are damaged or set to ignore `OK` key @@ -65,25 +65,25 @@ There is 1 case when it's not working: - Device will enter DFU with indication (Blue LED + DFU Screen) - Release `LEFT` -This combo performs hardware reset by pulling MCU reset line down. -Then `LEFT` key indicates to boot-loader that DFU mode is requested. +This combo performs a hardware reset by pulling MCU reset line down. +Then, `LEFT` key indicates to the boot-loader that DFU mode is requested. -There are 2 cases when it's not working: +There are 2 cases where it does not work: - MCU debug block is active and holding reset line from inside -- Flipper Boot-loader is damaged or absent +- Flipper boot-loader is damaged or absent ### Hardware Reset + Hardware DFU -- Press `LEFT` and `BACK` and `OK` and hold for a couple of seconds +- Press `LEFT`, `BACK` and `OK` and hold for a couple of seconds - Release `BACK` and `LEFT` - Device will enter DFU without indication -This combo performs hardware reset by pulling MCU reset line down. -Then `OK` key forces MCU to load internal boot-loader. +This combo performs a hardware reset by pulling MCU reset line down. +Then, `OK` key forces MCU to load internal boot-loader. -There are 2 cases when it's not working: +There are 2 cases where it does not work: - MCU debug block is active and holding reset line from inside - Option Bytes are damaged or set to ignore `OK` key @@ -96,15 +96,15 @@ There are 2 cases when it's not working: - Release `BACK` - Device will enter DFU with indication (Blue LED + DFU Screen) - Release `LEFT` -- Plug USB +- Plug in USB -This combo performs reset by switching SYS power line off and then on. -Then `LEFT` key indicates to boot-loader that DFU mode requested. +This combo performs a reset by switching SYS power line off and then on. +Then, `LEFT` key indicates to boot-loader that DFU mode requested. -There are 2 cases when it's not working: +There are 2 cases where it does not work: - Power supply is connected to USB or 5V_ext -- Flipper Boot-loader is damaged or absent +- Flipper boot-loader is damaged or absent ### Hardware Power Reset + Hardware DFU @@ -115,10 +115,10 @@ There are 2 cases when it's not working: - Device will enter DFU without indication - Plug USB -This combo performs reset by switching SYS power line off and then on. -Then `OK` key forces MCU to load internal boot-loader. +This combo performs a reset by switching SYS power line off and then on. +Then, `OK` key forces MCU to load internal boot-loader. -There are 2 cases when it's not working: +There are 2 cases where it does not work: - Power supply is connected to USB or 5V_ext - Option Bytes are damaged or set to ignore `OK` key @@ -131,4 +131,4 @@ If none of the described methods were useful: - Disconnect the battery and connect again (Requires disassembly) - Try to Flash device with ST-Link or other programmer that supports SWD -If you still here and your device is not working: it's not a software issue. +If you still are here and your device is not working: it's not a software issue. \ No newline at end of file From d58b9f3fe84f6a0ce1f2e8dc88e3a8f946bbd1f7 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Wed, 28 Dec 2022 17:09:33 +0200 Subject: [PATCH 329/824] Fix MFC bruteforce progress bar (#2203) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- applications/main/nfc/views/dict_attack.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/applications/main/nfc/views/dict_attack.c b/applications/main/nfc/views/dict_attack.c index 5ae204a060c..a539e514bed 100644 --- a/applications/main/nfc/views/dict_attack.c +++ b/applications/main/nfc/views/dict_attack.c @@ -60,7 +60,13 @@ static void dict_attack_draw_callback(Canvas* canvas, void* model) { if(progress > 1.0) { progress = 1.0; } - snprintf(draw_str, sizeof(draw_str), "%d/%d", m->dict_keys_current, m->dict_keys_total); + if(m->dict_keys_current == 0) { + // Cause when people see 0 they think it's broken + snprintf(draw_str, sizeof(draw_str), "%d/%d", 1, m->dict_keys_total); + } else { + snprintf( + draw_str, sizeof(draw_str), "%d/%d", m->dict_keys_current, m->dict_keys_total); + } elements_progress_bar_with_text(canvas, 0, 20, 128, dict_progress, draw_str); canvas_set_font(canvas, FontSecondary); snprintf(draw_str, sizeof(draw_str), "Keys found: %d/%d", m->keys_found, m->keys_total); From ea054423b0dc8bec30d76a18a6be819d12067a8a Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Thu, 29 Dec 2022 00:29:23 +0400 Subject: [PATCH 330/824] [FL-3074] SubGhz: CAME 12 bit encoder fix guard time (#2210) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * SubGhz: CAME encoder fix guard ime * SubGhz: fix 2 encoder CAME Co-authored-by: あく --- lib/subghz/protocols/came.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/subghz/protocols/came.c b/lib/subghz/protocols/came.c index 1ac4ec053b3..bed26d7d27c 100644 --- a/lib/subghz/protocols/came.c +++ b/lib/subghz/protocols/came.c @@ -119,7 +119,9 @@ static bool subghz_protocol_encoder_came_get_upload(SubGhzProtocolEncoderCame* i //Send header instance->encoder.upload[index++] = level_duration_make( false, - ((instance->generic.data_count_bit == CAME_24_COUNT_BIT) ? + (((instance->generic.data_count_bit == CAME_24_COUNT_BIT) || + (instance->generic.data_count_bit == + subghz_protocol_came_const.min_count_bit_for_found)) ? (uint32_t)subghz_protocol_came_const.te_short * 76 : (uint32_t)subghz_protocol_came_const.te_short * 39)); //Send start bit From 26e5527a9312097278336583439e1806377a47a5 Mon Sep 17 00:00:00 2001 From: Thomas Roth Date: Thu, 29 Dec 2022 06:20:01 +0100 Subject: [PATCH 331/824] Mifare dictionary attack performance improvements. (#2173) * NFC dictionary attack performance improvements. * Remove unnecessary assignment --- firmware/targets/f7/api_symbols.csv | 1 + lib/nfc/nfc_worker.c | 14 +++++++--- lib/nfc/protocols/mifare_classic.c | 41 ++++++++++++++++++++++------- lib/nfc/protocols/mifare_classic.h | 8 ++++++ 4 files changed, 50 insertions(+), 14 deletions(-) diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 72d44b5011f..28442b5cac6 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1854,6 +1854,7 @@ Function,+,menu_set_selected_item,void,"Menu*, uint32_t" Function,-,mf_classic_auth_attempt,_Bool,"FuriHalNfcTxRxContext*, MfClassicAuthContext*, uint64_t" Function,-,mf_classic_auth_init_context,void,"MfClassicAuthContext*, uint8_t" Function,-,mf_classic_authenticate,_Bool,"FuriHalNfcTxRxContext*, uint8_t, uint64_t, MfClassicKey" +Function,-,mf_classic_authenticate_skip_activate,_Bool,"FuriHalNfcTxRxContext*, uint8_t, uint64_t, MfClassicKey, _Bool, uint32_t" Function,-,mf_classic_check_card_type,_Bool,"uint8_t, uint8_t, uint8_t" Function,-,mf_classic_dict_add_key,_Bool,"MfClassicDict*, uint8_t*" Function,-,mf_classic_dict_add_key_str,_Bool,"MfClassicDict*, FuriString*" diff --git a/lib/nfc/nfc_worker.c b/lib/nfc/nfc_worker.c index 4a66593cb43..a652e088a0a 100644 --- a/lib/nfc/nfc_worker.c +++ b/lib/nfc/nfc_worker.c @@ -680,13 +680,15 @@ void nfc_worker_mf_classic_dict_attack(NfcWorker* nfc_worker) { nfc_worker->callback(NfcWorkerEventNewDictKeyBatch, nfc_worker->context); } furi_hal_nfc_sleep(); - if(furi_hal_nfc_activate_nfca(200, NULL)) { - furi_hal_nfc_sleep(); + uint32_t cuid; + if(furi_hal_nfc_activate_nfca(200, &cuid)) { + bool deactivated = false; if(!card_found_notified) { nfc_worker->callback(NfcWorkerEventCardDetected, nfc_worker->context); card_found_notified = true; card_removed_notified = false; nfc_worker_mf_classic_key_attack(nfc_worker, prev_key, &tx_rx, i); + deactivated = true; } FURI_LOG_D( TAG, @@ -696,22 +698,26 @@ void nfc_worker_mf_classic_dict_attack(NfcWorker* nfc_worker) { (uint32_t)key); if(!is_key_a_found) { is_key_a_found = mf_classic_is_key_found(data, i, MfClassicKeyA); - if(mf_classic_authenticate(&tx_rx, block_num, key, MfClassicKeyA)) { + if(mf_classic_authenticate_skip_activate( + &tx_rx, block_num, key, MfClassicKeyA, !deactivated, cuid)) { mf_classic_set_key_found(data, i, MfClassicKeyA, key); FURI_LOG_D(TAG, "Key found"); nfc_worker->callback(NfcWorkerEventFoundKeyA, nfc_worker->context); nfc_worker_mf_classic_key_attack(nfc_worker, key, &tx_rx, i + 1); } furi_hal_nfc_sleep(); + deactivated = true; } if(!is_key_b_found) { is_key_b_found = mf_classic_is_key_found(data, i, MfClassicKeyB); - if(mf_classic_authenticate(&tx_rx, block_num, key, MfClassicKeyB)) { + if(mf_classic_authenticate_skip_activate( + &tx_rx, block_num, key, MfClassicKeyB, !deactivated, cuid)) { FURI_LOG_D(TAG, "Key found"); mf_classic_set_key_found(data, i, MfClassicKeyB, key); nfc_worker->callback(NfcWorkerEventFoundKeyB, nfc_worker->context); nfc_worker_mf_classic_key_attack(nfc_worker, key, &tx_rx, i + 1); } + deactivated = true; } if(is_key_a_found && is_key_b_found) break; if(nfc_worker->state != NfcWorkerStateMfClassicDictAttack) break; diff --git a/lib/nfc/protocols/mifare_classic.c b/lib/nfc/protocols/mifare_classic.c index e26dfd7f7f9..c91e9c6056e 100644 --- a/lib/nfc/protocols/mifare_classic.c +++ b/lib/nfc/protocols/mifare_classic.c @@ -403,15 +403,16 @@ static bool mf_classic_auth( uint32_t block, uint64_t key, MfClassicKey key_type, - Crypto1* crypto) { + Crypto1* crypto, + bool skip_activate, + uint32_t cuid) { bool auth_success = false; - uint32_t cuid = 0; memset(tx_rx->tx_data, 0, sizeof(tx_rx->tx_data)); memset(tx_rx->tx_parity, 0, sizeof(tx_rx->tx_parity)); tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; do { - if(!furi_hal_nfc_activate_nfca(200, &cuid)) break; + if(!skip_activate && !furi_hal_nfc_activate_nfca(200, &cuid)) break; if(key_type == MfClassicKeyA) { tx_rx->tx_data[0] = MF_CLASSIC_AUTH_KEY_A_CMD; } else { @@ -460,7 +461,23 @@ bool mf_classic_authenticate( furi_assert(tx_rx); Crypto1 crypto = {}; - bool key_found = mf_classic_auth(tx_rx, block_num, key, key_type, &crypto); + bool key_found = mf_classic_auth(tx_rx, block_num, key, key_type, &crypto, false, 0); + furi_hal_nfc_sleep(); + return key_found; +} + +bool mf_classic_authenticate_skip_activate( + FuriHalNfcTxRxContext* tx_rx, + uint8_t block_num, + uint64_t key, + MfClassicKey key_type, + bool skip_activate, + uint32_t cuid) { + furi_assert(tx_rx); + + Crypto1 crypto = {}; + bool key_found = + mf_classic_auth(tx_rx, block_num, key, key_type, &crypto, skip_activate, cuid); furi_hal_nfc_sleep(); return key_found; } @@ -483,7 +500,9 @@ bool mf_classic_auth_attempt( mf_classic_get_first_block_num_of_sector(auth_ctx->sector), key, MfClassicKeyA, - &crypto)) { + &crypto, + false, + 0)) { auth_ctx->key_a = key; found_key = true; } @@ -500,7 +519,9 @@ bool mf_classic_auth_attempt( mf_classic_get_first_block_num_of_sector(auth_ctx->sector), key, MfClassicKeyB, - &crypto)) { + &crypto, + false, + 0)) { auth_ctx->key_b = key; found_key = true; } @@ -568,7 +589,7 @@ void mf_classic_read_sector(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data, u if(!key_a_found) break; FURI_LOG_D(TAG, "Try to read blocks with key A"); key = nfc_util_bytes2num(sec_tr->key_a, sizeof(sec_tr->key_a)); - if(!mf_classic_auth(tx_rx, start_block, key, MfClassicKeyA, &crypto)) break; + if(!mf_classic_auth(tx_rx, start_block, key, MfClassicKeyA, &crypto, false, 0)) break; for(size_t i = start_block; i < start_block + total_blocks; i++) { if(!mf_classic_is_block_read(data, i)) { if(mf_classic_read_block(tx_rx, &crypto, i, &block_tmp)) { @@ -587,7 +608,7 @@ void mf_classic_read_sector(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data, u FURI_LOG_D(TAG, "Try to read blocks with key B"); key = nfc_util_bytes2num(sec_tr->key_b, sizeof(sec_tr->key_b)); furi_hal_nfc_sleep(); - if(!mf_classic_auth(tx_rx, start_block, key, MfClassicKeyB, &crypto)) break; + if(!mf_classic_auth(tx_rx, start_block, key, MfClassicKeyB, &crypto, false, 0)) break; for(size_t i = start_block; i < start_block + total_blocks; i++) { if(!mf_classic_is_block_read(data, i)) { if(mf_classic_read_block(tx_rx, &crypto, i, &block_tmp)) { @@ -631,7 +652,7 @@ static bool mf_classic_read_sector_with_reader( } // Auth to first block in sector - if(!mf_classic_auth(tx_rx, first_block, key, key_type, crypto)) { + if(!mf_classic_auth(tx_rx, first_block, key, key_type, crypto, false, 0)) { // Set key to MF_CLASSIC_NO_KEY to prevent further attempts if(key_type == MfClassicKeyA) { sector_reader->key_a = MF_CLASSIC_NO_KEY; @@ -961,7 +982,7 @@ bool mf_classic_write_block( do { furi_hal_nfc_sleep(); - if(!mf_classic_auth(tx_rx, block_num, key, key_type, &crypto)) { + if(!mf_classic_auth(tx_rx, block_num, key, key_type, &crypto, false, 0)) { FURI_LOG_D(TAG, "Auth fail"); break; } diff --git a/lib/nfc/protocols/mifare_classic.h b/lib/nfc/protocols/mifare_classic.h index 9a0bb579040..74beceb625c 100644 --- a/lib/nfc/protocols/mifare_classic.h +++ b/lib/nfc/protocols/mifare_classic.h @@ -156,6 +156,14 @@ bool mf_classic_authenticate( uint64_t key, MfClassicKey key_type); +bool mf_classic_authenticate_skip_activate( + FuriHalNfcTxRxContext* tx_rx, + uint8_t block_num, + uint64_t key, + MfClassicKey key_type, + bool skip_activate, + uint32_t cuid); + bool mf_classic_auth_attempt( FuriHalNfcTxRxContext* tx_rx, MfClassicAuthContext* auth_ctx, From b11b9f1b38407d22e31443f4e84a20c079923a24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Thu, 29 Dec 2022 20:35:26 +0900 Subject: [PATCH 332/824] Gui: Direct Draw API (#2215) * Furi: allow on-fly thread priority change. Gui: Direct Draw API. DirectDraw debug app. * Gui: drop input in direct draw * Furi: handle priority change for starting threads * DirectDraw: rollback to FreeRTOS primitives for priority change --- .../debug/direct_draw/application.fam | 10 ++ applications/debug/direct_draw/direct_draw.c | 112 +++++++++++++ applications/services/gui/canvas.h | 12 ++ applications/services/gui/canvas_i.h | 12 -- applications/services/gui/gui.c | 156 +++++++++++------- applications/services/gui/gui.h | 22 +++ applications/services/gui/gui_i.h | 1 + firmware/targets/f7/api_symbols.csv | 6 +- 8 files changed, 259 insertions(+), 72 deletions(-) create mode 100644 applications/debug/direct_draw/application.fam create mode 100644 applications/debug/direct_draw/direct_draw.c diff --git a/applications/debug/direct_draw/application.fam b/applications/debug/direct_draw/application.fam new file mode 100644 index 00000000000..11b3bc6ba38 --- /dev/null +++ b/applications/debug/direct_draw/application.fam @@ -0,0 +1,10 @@ +App( + appid="direct_draw", + name="Direct Draw", + apptype=FlipperAppType.DEBUG, + entry_point="direct_draw_app", + requires=["gui", "input"], + stack_size=2 * 1024, + order=70, + fap_category="Debug", +) diff --git a/applications/debug/direct_draw/direct_draw.c b/applications/debug/direct_draw/direct_draw.c new file mode 100644 index 00000000000..71c65eab135 --- /dev/null +++ b/applications/debug/direct_draw/direct_draw.c @@ -0,0 +1,112 @@ +#include +#include +#include +#include + +#define BUFFER_SIZE (32U) + +typedef struct { + FuriPubSub* input; + FuriPubSubSubscription* input_subscription; + Gui* gui; + Canvas* canvas; + bool stop; + uint32_t counter; +} DirectDraw; + +static void gui_input_events_callback(const void* value, void* ctx) { + furi_assert(value); + furi_assert(ctx); + + DirectDraw* instance = ctx; + const InputEvent* event = value; + + if(event->key == InputKeyBack && event->type == InputTypeShort) { + instance->stop = true; + } +} + +static DirectDraw* direct_draw_alloc() { + DirectDraw* instance = malloc(sizeof(DirectDraw)); + + instance->input = furi_record_open(RECORD_INPUT_EVENTS); + instance->gui = furi_record_open(RECORD_GUI); + instance->canvas = gui_direct_draw_acquire(instance->gui); + + instance->input_subscription = + furi_pubsub_subscribe(instance->input, gui_input_events_callback, instance); + + return instance; +} + +static void direct_draw_free(DirectDraw* instance) { + furi_pubsub_unsubscribe(instance->input, instance->input_subscription); + + instance->canvas = NULL; + gui_direct_draw_release(instance->gui); + furi_record_close(RECORD_GUI); + furi_record_close(RECORD_INPUT_EVENTS); +} + +static void direct_draw_block(Canvas* canvas, uint32_t size, uint32_t counter) { + size += 16; + uint8_t width = canvas_width(canvas) - size; + uint8_t height = canvas_height(canvas) - size; + + uint8_t x = counter % width; + if((counter / width) % 2) { + x = width - x; + } + + uint8_t y = counter % height; + if((counter / height) % 2) { + y = height - y; + } + + canvas_draw_box(canvas, x, y, size, size); +} + +static void direct_draw_run(DirectDraw* instance) { + size_t start = DWT->CYCCNT; + size_t counter = 0; + float fps = 0; + + vTaskPrioritySet(furi_thread_get_current_id(), FuriThreadPriorityIdle); + + do { + size_t elapsed = DWT->CYCCNT - start; + char buffer[BUFFER_SIZE] = {0}; + + if(elapsed >= 64000000) { + fps = (float)counter / ((float)elapsed / 64000000.0f); + + start = DWT->CYCCNT; + counter = 0; + } + snprintf(buffer, BUFFER_SIZE, "FPS: %.1f", (double)fps); + + canvas_reset(instance->canvas); + canvas_set_color(instance->canvas, ColorXOR); + direct_draw_block(instance->canvas, instance->counter % 16, instance->counter); + direct_draw_block(instance->canvas, instance->counter * 2 % 16, instance->counter * 2); + direct_draw_block(instance->canvas, instance->counter * 3 % 16, instance->counter * 3); + direct_draw_block(instance->canvas, instance->counter * 4 % 16, instance->counter * 4); + direct_draw_block(instance->canvas, instance->counter * 5 % 16, instance->counter * 5); + canvas_draw_str(instance->canvas, 10, 10, buffer); + canvas_commit(instance->canvas); + + counter++; + instance->counter++; + furi_thread_yield(); + } while(!instance->stop); +} + +int32_t direct_draw_app(void* p) { + UNUSED(p); + + DirectDraw* instance = direct_draw_alloc(); + direct_draw_run(instance); + direct_draw_free(instance); + + return 0; +} diff --git a/applications/services/gui/canvas.h b/applications/services/gui/canvas.h index c4eb8649f3a..0b0c7e6585f 100644 --- a/applications/services/gui/canvas.h +++ b/applications/services/gui/canvas.h @@ -67,6 +67,18 @@ typedef struct { /** Canvas anonymous structure */ typedef struct Canvas Canvas; +/** Reset canvas drawing tools configuration + * + * @param canvas Canvas instance + */ +void canvas_reset(Canvas* canvas); + +/** Commit canvas. Send buffer to display + * + * @param canvas Canvas instance + */ +void canvas_commit(Canvas* canvas); + /** Get Canvas width * * @param canvas Canvas instance diff --git a/applications/services/gui/canvas_i.h b/applications/services/gui/canvas_i.h index d5594c24a08..2b684923550 100644 --- a/applications/services/gui/canvas_i.h +++ b/applications/services/gui/canvas_i.h @@ -31,18 +31,6 @@ Canvas* canvas_init(); */ void canvas_free(Canvas* canvas); -/** Reset canvas drawing tools configuration - * - * @param canvas Canvas instance - */ -void canvas_reset(Canvas* canvas); - -/** Commit canvas. Send buffer to display - * - * @param canvas Canvas instance - */ -void canvas_commit(Canvas* canvas); - /** Get canvas buffer. * * @param canvas Canvas instance diff --git a/applications/services/gui/gui.c b/applications/services/gui/gui.c index 9f6ebcd76f2..b0903e021c9 100644 --- a/applications/services/gui/gui.c +++ b/applications/services/gui/gui.c @@ -20,7 +20,7 @@ ViewPort* gui_view_port_find_enabled(ViewPortArray_t array) { void gui_update(Gui* gui) { furi_assert(gui); - furi_thread_flags_set(gui->thread_id, GUI_THREAD_FLAG_DRAW); + if(!gui->direct_draw) furi_thread_flags_set(gui->thread_id, GUI_THREAD_FLAG_DRAW); } void gui_input_events_callback(const void* value, void* ctx) { @@ -34,7 +34,7 @@ void gui_input_events_callback(const void* value, void* ctx) { } // Only Fullscreen supports vertical display for now -bool gui_redraw_fs(Gui* gui) { +static bool gui_redraw_fs(Gui* gui) { canvas_set_orientation(gui->canvas, CanvasOrientationHorizontal); canvas_frame_set(gui->canvas, 0, 0, GUI_DISPLAY_WIDTH, GUI_DISPLAY_HEIGHT); ViewPort* view_port = gui_view_port_find_enabled(gui->layers[GuiLayerFullscreen]); @@ -192,7 +192,7 @@ static void gui_redraw_status_bar(Gui* gui, bool need_attention) { } } -bool gui_redraw_window(Gui* gui) { +static bool gui_redraw_window(Gui* gui) { canvas_set_orientation(gui->canvas, CanvasOrientationHorizontal); canvas_frame_set(gui->canvas, GUI_WINDOW_X, GUI_WINDOW_Y, GUI_WINDOW_WIDTH, GUI_WINDOW_HEIGHT); ViewPort* view_port = gui_view_port_find_enabled(gui->layers[GuiLayerWindow]); @@ -203,7 +203,7 @@ bool gui_redraw_window(Gui* gui) { return false; } -bool gui_redraw_desktop(Gui* gui) { +static bool gui_redraw_desktop(Gui* gui) { canvas_set_orientation(gui->canvas, CanvasOrientationHorizontal); canvas_frame_set(gui->canvas, 0, 0, GUI_DISPLAY_WIDTH, GUI_DISPLAY_HEIGHT); ViewPort* view_port = gui_view_port_find_enabled(gui->layers[GuiLayerDesktop]); @@ -215,37 +215,44 @@ bool gui_redraw_desktop(Gui* gui) { return false; } -void gui_redraw(Gui* gui) { +static void gui_redraw(Gui* gui) { furi_assert(gui); gui_lock(gui); - canvas_reset(gui->canvas); - - if(gui->lockdown) { - gui_redraw_desktop(gui); - bool need_attention = - (gui_view_port_find_enabled(gui->layers[GuiLayerWindow]) != 0 || - gui_view_port_find_enabled(gui->layers[GuiLayerFullscreen]) != 0); - gui_redraw_status_bar(gui, need_attention); - } else { - if(!gui_redraw_fs(gui)) { - if(!gui_redraw_window(gui)) { - gui_redraw_desktop(gui); + do { + if(gui->direct_draw) break; + + canvas_reset(gui->canvas); + + if(gui->lockdown) { + gui_redraw_desktop(gui); + bool need_attention = + (gui_view_port_find_enabled(gui->layers[GuiLayerWindow]) != 0 || + gui_view_port_find_enabled(gui->layers[GuiLayerFullscreen]) != 0); + gui_redraw_status_bar(gui, need_attention); + } else { + if(!gui_redraw_fs(gui)) { + if(!gui_redraw_window(gui)) { + gui_redraw_desktop(gui); + } + gui_redraw_status_bar(gui, false); } - gui_redraw_status_bar(gui, false); } - } - canvas_commit(gui->canvas); - for - M_EACH(p, gui->canvas_callback_pair, CanvasCallbackPairArray_t) { - p->callback( - canvas_get_buffer(gui->canvas), canvas_get_buffer_size(gui->canvas), p->context); - } + canvas_commit(gui->canvas); + for + M_EACH(p, gui->canvas_callback_pair, CanvasCallbackPairArray_t) { + p->callback( + canvas_get_buffer(gui->canvas), + canvas_get_buffer_size(gui->canvas), + p->context); + } + } while(false); + gui_unlock(gui); } -void gui_input(Gui* gui, InputEvent* input_event) { +static void gui_input(Gui* gui, InputEvent* input_event) { furi_assert(gui); furi_assert(input_event); @@ -267,42 +274,48 @@ void gui_input(Gui* gui, InputEvent* input_event) { gui_lock(gui); - ViewPort* view_port = NULL; + do { + if(gui->direct_draw && !gui->ongoing_input_view_port) { + break; + } - if(gui->lockdown) { - view_port = gui_view_port_find_enabled(gui->layers[GuiLayerDesktop]); - } else { - view_port = gui_view_port_find_enabled(gui->layers[GuiLayerFullscreen]); - if(!view_port) view_port = gui_view_port_find_enabled(gui->layers[GuiLayerWindow]); - if(!view_port) view_port = gui_view_port_find_enabled(gui->layers[GuiLayerDesktop]); - } + ViewPort* view_port = NULL; - if(!(gui->ongoing_input & ~key_bit) && input_event->type == InputTypePress) { - gui->ongoing_input_view_port = view_port; - } + if(gui->lockdown) { + view_port = gui_view_port_find_enabled(gui->layers[GuiLayerDesktop]); + } else { + view_port = gui_view_port_find_enabled(gui->layers[GuiLayerFullscreen]); + if(!view_port) view_port = gui_view_port_find_enabled(gui->layers[GuiLayerWindow]); + if(!view_port) view_port = gui_view_port_find_enabled(gui->layers[GuiLayerDesktop]); + } - if(view_port && view_port == gui->ongoing_input_view_port) { - view_port_input(view_port, input_event); - } else if(gui->ongoing_input_view_port && input_event->type == InputTypeRelease) { - FURI_LOG_D( - TAG, - "ViewPort changed while key press %p -> %p. Sending key: %s, type: %s, sequence: %p to previous view port", - gui->ongoing_input_view_port, - view_port, - input_get_key_name(input_event->key), - input_get_type_name(input_event->type), - (void*)input_event->sequence); - view_port_input(gui->ongoing_input_view_port, input_event); - } else { - FURI_LOG_D( - TAG, - "ViewPort changed while key press %p -> %p. Discarding key: %s, type: %s, sequence: %p", - gui->ongoing_input_view_port, - view_port, - input_get_key_name(input_event->key), - input_get_type_name(input_event->type), - (void*)input_event->sequence); - } + if(!(gui->ongoing_input & ~key_bit) && input_event->type == InputTypePress) { + gui->ongoing_input_view_port = view_port; + } + + if(view_port && view_port == gui->ongoing_input_view_port) { + view_port_input(view_port, input_event); + } else if(gui->ongoing_input_view_port && input_event->type == InputTypeRelease) { + FURI_LOG_D( + TAG, + "ViewPort changed while key press %p -> %p. Sending key: %s, type: %s, sequence: %p to previous view port", + gui->ongoing_input_view_port, + view_port, + input_get_key_name(input_event->key), + input_get_type_name(input_event->type), + (void*)input_event->sequence); + view_port_input(gui->ongoing_input_view_port, input_event); + } else { + FURI_LOG_D( + TAG, + "ViewPort changed while key press %p -> %p. Discarding key: %s, type: %s, sequence: %p", + gui->ongoing_input_view_port, + view_port, + input_get_key_name(input_event->key), + input_get_type_name(input_event->type), + (void*)input_event->sequence); + } + } while(false); gui_unlock(gui); } @@ -471,6 +484,31 @@ void gui_set_lockdown(Gui* gui, bool lockdown) { gui_update(gui); } +Canvas* gui_direct_draw_acquire(Gui* gui) { + furi_assert(gui); + gui_lock(gui); + gui->direct_draw = true; + gui_unlock(gui); + + canvas_reset(gui->canvas); + canvas_commit(gui->canvas); + + return gui->canvas; +} + +void gui_direct_draw_release(Gui* gui) { + furi_assert(gui); + + canvas_reset(gui->canvas); + canvas_commit(gui->canvas); + + gui_lock(gui); + gui->direct_draw = false; + gui_unlock(gui); + + gui_update(gui); +} + Gui* gui_alloc() { Gui* gui = malloc(sizeof(Gui)); // Thread ID diff --git a/applications/services/gui/gui.h b/applications/services/gui/gui.h index f4886758849..4e7fc2e4d87 100644 --- a/applications/services/gui/gui.h +++ b/applications/services/gui/gui.h @@ -106,6 +106,28 @@ size_t gui_get_framebuffer_size(Gui* gui); */ void gui_set_lockdown(Gui* gui, bool lockdown); +/** Acquire Direct Draw lock and get Canvas instance + * + * This method return Canvas instance for use in monopoly mode. Direct draw lock + * disables input and draw call dispatch functions in GUI service. No other + * applications or services will be able to draw until gui_direct_draw_release + * call. + * + * @param gui The graphical user interface + * + * @return Canvas instance + */ +Canvas* gui_direct_draw_acquire(Gui* gui); + +/** Release Direct Draw Lock + * + * Release Direct Draw Lock, enables Input and Draw call processing. Canvas + * acquired in gui_direct_draw_acquire will become invalid after this call. + * + * @param gui Gui instance + */ +void gui_direct_draw_release(Gui* gui); + #ifdef __cplusplus } #endif diff --git a/applications/services/gui/gui_i.h b/applications/services/gui/gui_i.h index b3fd0aa7c61..45061bd58e8 100644 --- a/applications/services/gui/gui_i.h +++ b/applications/services/gui/gui_i.h @@ -62,6 +62,7 @@ struct Gui { // Layers and Canvas bool lockdown; + bool direct_draw; ViewPortArray_t layers[GuiLayerMAX]; Canvas* canvas; CanvasCallbackPairArray_t canvas_callback_pair; diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 28442b5cac6..b41e187213e 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,11.3,, +Version,+,11.4,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -593,6 +593,7 @@ Function,+,byte_input_set_result_callback,void,"ByteInput*, ByteInputCallback, B Function,-,bzero,void,"void*, size_t" Function,-,calloc,void*,"size_t, size_t" Function,+,canvas_clear,void,Canvas* +Function,+,canvas_commit,void,Canvas* Function,+,canvas_current_font_height,uint8_t,Canvas* Function,+,canvas_draw_bitmap,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, const uint8_t*" Function,+,canvas_draw_box,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t" @@ -614,6 +615,7 @@ Function,+,canvas_get_font_params,CanvasFontParameters*,"Canvas*, Font" Function,+,canvas_glyph_width,uint8_t,"Canvas*, char" Function,+,canvas_height,uint8_t,Canvas* Function,+,canvas_invert_color,void,Canvas* +Function,+,canvas_reset,void,Canvas* Function,+,canvas_set_bitmap_mode,void,"Canvas*, _Bool" Function,+,canvas_set_color,void,"Canvas*, Color" Function,+,canvas_set_font,void,"Canvas*, Font" @@ -1554,6 +1556,8 @@ Function,-,gmtime,tm*,const time_t* Function,-,gmtime_r,tm*,"const time_t*, tm*" Function,+,gui_add_framebuffer_callback,void,"Gui*, GuiCanvasCommitCallback, void*" Function,+,gui_add_view_port,void,"Gui*, ViewPort*, GuiLayer" +Function,+,gui_direct_draw_acquire,Canvas*,Gui* +Function,+,gui_direct_draw_release,void,Gui* Function,+,gui_get_framebuffer_size,size_t,Gui* Function,+,gui_remove_framebuffer_callback,void,"Gui*, GuiCanvasCommitCallback, void*" Function,+,gui_remove_view_port,void,"Gui*, ViewPort*" From 06cf1bb86813adf132eca1b270f27ab541b34449 Mon Sep 17 00:00:00 2001 From: Avery <30564701+nullableVoidPtr@users.noreply.github.com> Date: Fri, 30 Dec 2022 11:44:07 +0800 Subject: [PATCH 333/824] Gui: change data ownership model in submenu, own text by default (#2217) * GUI: Refactor submenu to internally use FuriStrings * NFC: Rework DESFIRE dynamic info submenu * GUI: don't mutate label when drawing submenu * Get rid of submenu_add_item_cstr * increase api version * Get rid of submenu_set_header_cstr * rollback api version * Documentation * RFID: use new submenu api Co-authored-by: Sergey Gavrilov --- .../lfrfid/scenes/lfrfid_scene_save_type.c | 17 ++--- .../nfc/scenes/nfc_scene_mf_desfire_app.c | 21 ++--- .../nfc/scenes/nfc_scene_mf_desfire_data.c | 21 +++-- applications/services/gui/modules/submenu.c | 76 +++++++++++++++---- 4 files changed, 84 insertions(+), 51 deletions(-) diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_save_type.c b/applications/main/lfrfid/scenes/lfrfid_scene_save_type.c index 0ba51f0665b..dd20ae4890e 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_save_type.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_save_type.c @@ -1,7 +1,6 @@ #include "../lfrfid_i.h" typedef struct { - FuriString* menu_item_name[LFRFIDProtocolMax]; uint32_t line_sel; } SaveTypeCtx; @@ -16,25 +15,27 @@ void lfrfid_scene_save_type_on_enter(void* context) { Submenu* submenu = app->submenu; SaveTypeCtx* state = malloc(sizeof(SaveTypeCtx)); + FuriString* protocol_string = furi_string_alloc(); for(uint8_t i = 0; i < LFRFIDProtocolMax; i++) { if(strcmp( protocol_dict_get_manufacturer(app->dict, i), protocol_dict_get_name(app->dict, i)) != 0) { - state->menu_item_name[i] = furi_string_alloc_printf( + furi_string_printf( + protocol_string, "%s %s", protocol_dict_get_manufacturer(app->dict, i), protocol_dict_get_name(app->dict, i)); } else { - state->menu_item_name[i] = - furi_string_alloc_printf("%s", protocol_dict_get_name(app->dict, i)); + furi_string_printf(protocol_string, "%s", protocol_dict_get_name(app->dict, i)); } submenu_add_item( submenu, - furi_string_get_cstr(state->menu_item_name[i]), + furi_string_get_cstr(protocol_string), i, lfrfid_scene_save_type_submenu_callback, app); } + furi_string_free(protocol_string); submenu_set_selected_item( submenu, scene_manager_get_scene_state(app->scene_manager, LfRfidSceneSaveType)); @@ -73,13 +74,7 @@ void lfrfid_scene_save_type_on_exit(void* context) { submenu_reset(app->submenu); - for(uint8_t i = 0; i < LFRFIDProtocolMax; i++) { - furi_string_free(state->menu_item_name[i]); - } - uint32_t line_sel = state->line_sel; - free(state); - scene_manager_set_scene_state(app->scene_manager, LfRfidSceneSaveType, line_sel); } diff --git a/applications/main/nfc/scenes/nfc_scene_mf_desfire_app.c b/applications/main/nfc/scenes/nfc_scene_mf_desfire_app.c index afc5f0dee4b..882dc5fea8f 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_desfire_app.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_desfire_app.c @@ -51,23 +51,18 @@ void nfc_scene_mf_desfire_app_on_enter(void* context) { nfc_scene_mf_desfire_app_submenu_callback, nfc); - uint16_t cap = NFC_TEXT_STORE_SIZE; - char* buf = nfc->text_store; + FuriString* label = furi_string_alloc(); int idx = SubmenuIndexDynamic; for(MifareDesfireFile* file = app->file_head; file; file = file->next) { - int size = snprintf(buf, cap, "File %d", file->id); - if(size < 0 || size >= cap) { - FURI_LOG_W( - TAG, - "Exceeded NFC_TEXT_STORE_SIZE when preparing file id strings; menu truncated"); - break; - } - char* label = buf; - cap -= size + 1; - buf += size + 1; + furi_string_printf(label, "File %d", file->id); submenu_add_item( - nfc->submenu, label, idx++, nfc_scene_mf_desfire_app_submenu_callback, nfc); + nfc->submenu, + furi_string_get_cstr(label), + idx++, + nfc_scene_mf_desfire_app_submenu_callback, + nfc); } + furi_string_free(label); view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); } diff --git a/applications/main/nfc/scenes/nfc_scene_mf_desfire_data.c b/applications/main/nfc/scenes/nfc_scene_mf_desfire_data.c index e619d0377c9..c7caee8dc6d 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_desfire_data.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_desfire_data.c @@ -33,21 +33,18 @@ void nfc_scene_mf_desfire_data_on_enter(void* context) { nfc_scene_mf_desfire_data_submenu_callback, nfc); - uint16_t cap = NFC_TEXT_STORE_SIZE; - char* buf = nfc->text_store; + FuriString* label = furi_string_alloc(); int idx = SubmenuIndexDynamic; for(MifareDesfireApplication* app = data->app_head; app; app = app->next) { - int size = snprintf(buf, cap, "App %02x%02x%02x", app->id[0], app->id[1], app->id[2]); - if(size < 0 || size >= cap) { - FURI_LOG_W( - TAG, "Exceeded NFC_TEXT_STORE_SIZE when preparing app id strings; menu truncated"); - break; - } - char* label = buf; - cap -= size + 1; - buf += size + 1; - submenu_add_item(submenu, label, idx++, nfc_scene_mf_desfire_data_submenu_callback, nfc); + furi_string_printf(label, "App %02x%02x%02x", app->id[0], app->id[1], app->id[2]); + submenu_add_item( + submenu, + furi_string_get_cstr(label), + idx++, + nfc_scene_mf_desfire_data_submenu_callback, + nfc); } + furi_string_free(label); if(state >= MifareDesfireDataStateItem) { submenu_set_selected_item( diff --git a/applications/services/gui/modules/submenu.c b/applications/services/gui/modules/submenu.c index f8af44fdbf3..72626c587de 100644 --- a/applications/services/gui/modules/submenu.c +++ b/applications/services/gui/modules/submenu.c @@ -9,17 +9,48 @@ struct Submenu { }; typedef struct { - const char* label; + FuriString* label; uint32_t index; SubmenuItemCallback callback; void* callback_context; } SubmenuItem; -ARRAY_DEF(SubmenuItemArray, SubmenuItem, M_POD_OPLIST); +static void SubmenuItem_init(SubmenuItem* item) { + item->label = furi_string_alloc(); + item->index = 0; + item->callback = NULL; + item->callback_context = NULL; +} + +static void SubmenuItem_init_set(SubmenuItem* item, const SubmenuItem* src) { + item->label = furi_string_alloc_set(src->label); + item->index = src->index; + item->callback = src->callback; + item->callback_context = src->callback_context; +} + +static void SubmenuItem_set(SubmenuItem* item, const SubmenuItem* src) { + furi_string_set(item->label, src->label); + item->index = src->index; + item->callback = src->callback; + item->callback_context = src->callback_context; +} + +static void SubmenuItem_clear(SubmenuItem* item) { + furi_string_free(item->label); +} + +ARRAY_DEF( + SubmenuItemArray, + SubmenuItem, + (INIT(API_2(SubmenuItem_init)), + SET(API_6(SubmenuItem_set)), + INIT_SET(API_6(SubmenuItem_init_set)), + CLEAR(API_2(SubmenuItem_clear)))) typedef struct { SubmenuItemArray_t items; - const char* header; + FuriString* header; size_t position; size_t window_position; } SubmenuModel; @@ -36,9 +67,9 @@ static void submenu_view_draw_callback(Canvas* canvas, void* _model) { canvas_clear(canvas); - if(model->header) { + if(!furi_string_empty(model->header)) { canvas_set_font(canvas, FontPrimary); - canvas_draw_str(canvas, 4, 11, model->header); + canvas_draw_str(canvas, 4, 11, furi_string_get_cstr(model->header)); } canvas_set_font(canvas, FontSecondary); @@ -48,8 +79,8 @@ static void submenu_view_draw_callback(Canvas* canvas, void* _model) { for(SubmenuItemArray_it(it, model->items); !SubmenuItemArray_end_p(it); SubmenuItemArray_next(it)) { const size_t item_position = position - model->window_position; - const size_t items_on_screen = model->header ? 3 : 4; - uint8_t y_offset = model->header ? 16 : 0; + const size_t items_on_screen = furi_string_empty(model->header) ? 4 : 3; + uint8_t y_offset = furi_string_empty(model->header) ? 0 : 16; if(item_position < items_on_screen) { if(position == model->position) { @@ -134,7 +165,7 @@ Submenu* submenu_alloc() { SubmenuItemArray_init(model->items); model->position = 0; model->window_position = 0; - model->header = NULL; + model->header = furi_string_alloc(); }, true); @@ -145,7 +176,13 @@ void submenu_free(Submenu* submenu) { furi_assert(submenu); with_view_model( - submenu->view, SubmenuModel * model, { SubmenuItemArray_clear(model->items); }, true); + submenu->view, + SubmenuModel * model, + { + furi_string_free(model->header); + SubmenuItemArray_clear(model->items); + }, + true); view_free(submenu->view); free(submenu); } @@ -170,7 +207,7 @@ void submenu_add_item( SubmenuModel * model, { item = SubmenuItemArray_push_new(model->items); - item->label = label; + furi_string_set_str(item->label, label); item->index = index; item->callback = callback; item->callback_context = callback_context; @@ -188,7 +225,7 @@ void submenu_reset(Submenu* submenu) { SubmenuItemArray_reset(model->items); model->position = 0; model->window_position = 0; - model->header = NULL; + furi_string_reset(model->header); }, true); } @@ -221,7 +258,7 @@ void submenu_set_selected_item(Submenu* submenu, uint32_t index) { model->window_position -= 1; } - const size_t items_on_screen = model->header ? 3 : 4; + const size_t items_on_screen = furi_string_empty(model->header) ? 4 : 3; if(items_size <= items_on_screen) { model->window_position = 0; @@ -240,7 +277,7 @@ void submenu_process_up(Submenu* submenu) { submenu->view, SubmenuModel * model, { - const size_t items_on_screen = model->header ? 3 : 4; + const size_t items_on_screen = furi_string_empty(model->header) ? 4 : 3; const size_t items_size = SubmenuItemArray_size(model->items); if(model->position > 0) { @@ -263,7 +300,7 @@ void submenu_process_down(Submenu* submenu) { submenu->view, SubmenuModel * model, { - const size_t items_on_screen = model->header ? 3 : 4; + const size_t items_on_screen = furi_string_empty(model->header) ? 4 : 3; const size_t items_size = SubmenuItemArray_size(model->items); if(model->position < items_size - 1) { @@ -303,5 +340,14 @@ void submenu_set_header(Submenu* submenu, const char* header) { furi_assert(submenu); with_view_model( - submenu->view, SubmenuModel * model, { model->header = header; }, true); + submenu->view, + SubmenuModel * model, + { + if(header == NULL) { + furi_string_reset(model->header); + } else { + furi_string_set_str(model->header, header); + } + }, + true); } From 95f8895b431d6e20e0a9cbfe8a0e4f73f0cb4d3b Mon Sep 17 00:00:00 2001 From: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> Date: Fri, 30 Dec 2022 15:52:53 +0300 Subject: [PATCH 334/824] Add float_tools to SDK api (#2225) --- firmware/targets/f7/api_symbols.csv | 4 +++- lib/toolbox/SConscript | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index b41e187213e..9e1fa85d779 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,11.4,, +Version,+,11.5,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -174,6 +174,7 @@ Header,+,lib/subghz/transmitter.h,, Header,+,lib/toolbox/args.h,, Header,+,lib/toolbox/crc32_calc.h,, Header,+,lib/toolbox/dir_walk.h,, +Header,+,lib/toolbox/float_tools.h,, Header,+,lib/toolbox/hmac_sha256.h,, Header,+,lib/toolbox/manchester_decoder.h,, Header,+,lib/toolbox/manchester_encoder.h,, @@ -908,6 +909,7 @@ Function,+,flipper_format_write_int32,_Bool,"FlipperFormat*, const char*, const Function,+,flipper_format_write_string,_Bool,"FlipperFormat*, const char*, FuriString*" Function,+,flipper_format_write_string_cstr,_Bool,"FlipperFormat*, const char*, const char*" Function,+,flipper_format_write_uint32,_Bool,"FlipperFormat*, const char*, const uint32_t*, const uint16_t" +Function,+,float_is_equal,_Bool,"float, float" Function,-,flockfile,void,FILE* Function,-,floor,double,double Function,-,floorf,float,float diff --git a/lib/toolbox/SConscript b/lib/toolbox/SConscript index 015a8ed189a..724d25afacb 100644 --- a/lib/toolbox/SConscript +++ b/lib/toolbox/SConscript @@ -19,6 +19,7 @@ env.Append( File("args.h"), File("saved_struct.h"), File("version.h"), + File("float_tools.h"), File("tar/tar_archive.h"), File("stream/stream.h"), File("stream/file_stream.h"), From 72dc8e95d0941849929af98fd51a24590d61ca31 Mon Sep 17 00:00:00 2001 From: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> Date: Mon, 2 Jan 2023 12:01:59 +0300 Subject: [PATCH 335/824] Add .kateconfig and .kateproject to .gitignore (#2237) --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index 61c594ed106..d54ed4a19a3 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,10 @@ Brewfile.lock.json # Visual Studio Code .vscode/ +# Kate +.kateproject +.kateconfig + # legendary cmake's build CMakeLists.txt From b8dd75884cc073453c1278d863aa1abf716bc655 Mon Sep 17 00:00:00 2001 From: Jack Webb-Heller Date: Thu, 5 Jan 2023 19:39:42 +0000 Subject: [PATCH 336/824] Add Dyson Purifier Hot+Cool support to IR AC Library (#2252) --- assets/resources/infrared/assets/ac.ir | 38 +++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/assets/resources/infrared/assets/ac.ir b/assets/resources/infrared/assets/ac.ir index d9410a30074..1f9c2145f69 100644 --- a/assets/resources/infrared/assets/ac.ir +++ b/assets/resources/infrared/assets/ac.ir @@ -260,4 +260,40 @@ type: raw frequency: 38000 duty_cycle: 0.330000 data: 5043 2132 361 1770 356 723 335 715 332 718 329 1774 363 715 332 719 328 722 336 714 333 1770 356 722 336 1767 360 1772 354 724 334 1769 357 1774 363 1768 358 1773 364 1767 359 720 327 723 335 1768 359 720 327 723 335 716 331 719 328 722 336 714 333 1770 356 1774 363 1769 357 1773 364 1767 360 720 327 1775 362 1769 357 721 326 725 333 717 330 720 327 723 335 716 331 719 328 722 336 714 333 717 330 720 327 723 335 1768 359 1773 364 1767 360 1772 354 724 334 717 330 720 327 723 335 29451 5041 2134 359 1772 354 724 334 717 330 720 327 1775 362 717 330 720 327 723 335 715 332 1771 355 723 335 1768 358 1773 364 715 332 1770 357 1775 362 1769 357 1774 363 1768 359 720 327 723 335 1768 359 720 327 724 334 716 331 719 328 722 336 715 332 718 329 720 327 723 335 716 331 1771 355 1776 361 718 329 721 326 1776 361 718 329 1773 364 1767 360 720 327 723 335 715 332 718 329 1774 363 1768 359 720 327 723 335 1768 358 721 326 724 334 716 331 719 328 722 336 1767 360 719 328 722 336 715 332 718 329 721 326 724 334 717 330 720 327 723 335 715 332 719 328 722 325 725 333 717 330 720 327 723 335 716 331 719 328 1774 363 716 331 1771 355 1776 361 718 329 721 326 724 334 717 330 1772 365 714 333 1770 356 722 336 715 332 718 329 721 326 724 334 717 330 719 328 1775 362 717 330 720 327 723 335 715 332 718 329 1774 363 715 332 718 329 721 326 725 333 717 330 1772 365 - +# +# Model: Dyson Purifier Hot+Cool +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 2309 665 781 672 803 672 803 695 832 643 833 1355 804 694 836 640 781 695 804 1409 778 697 777 702 797 678 798 677 799 678 799 701 803 674 802 1412 801 674 801 674 801 674 802 674 801 51317 2284 670 775 1413 802 51252 2283 670 801 1412 775 51275 2258 673 798 1414 802 51248 2284 670 802 1412 774 51246 2259 695 775 1413 801 +# +name: Heat_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 2316 610 806 671 781 695 806 695 781 695 782 1405 782 694 808 694 780 693 808 1381 779 697 802 1412 776 1438 800 1437 776 1438 775 700 775 1412 776 700 801 701 775 700 776 1438 776 700 776 51695 2258 695 776 1437 776 51248 2258 672 798 1439 776 51240 2258 670 801 1436 776 +# +name: Heat_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 2342 612 781 695 805 668 810 666 810 665 811 1402 811 666 811 692 781 670 781 1432 781 696 778 1436 802 1412 802 1412 802 1413 801 1412 802 1412 801 1412 777 1411 776 1463 776 1412 800 1414 801 51041 2257 697 802 1411 777 51240 2283 671 776 1437 801 51209 2255 672 799 1412 801 +# +name: Cool_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 2317 637 832 644 830 669 805 670 805 672 803 1411 803 673 802 674 802 673 803 1411 803 674 801 1411 802 675 775 1415 800 701 774 1440 801 1412 775 702 799 1414 774 1413 801 701 801 675 800 51681 2257 695 803 1411 801 51226 2283 671 799 1412 803 51246 2257 696 803 1411 775 51255 2282 668 803 1410 802 51243 2258 695 802 1387 798 +# +name: Cool_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 2315 618 853 643 832 644 834 641 833 643 833 1356 805 695 835 640 808 667 809 1404 808 668 806 1409 803 674 801 1412 802 1388 799 677 799 701 775 701 801 1389 799 677 799 676 800 1439 802 51426 2283 671 800 1412 802 51251 2258 697 801 1387 800 51248 2283 669 802 1411 802 51230 2258 696 799 1387 801 51225 2283 670 801 1411 801 51200 2280 695 775 1411 802 51227 2258 696 802 1411 775 51204 2281 669 801 1411 800 +# +name: Dh +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 2320 634 837 637 838 637 838 640 835 642 832 1378 836 645 826 670 809 667 808 1406 806 672 803 674 802 1412 802 1412 800 676 801 675 802 1412 802 674 802 1413 801 1412 801 1413 802 1412 802 50937 2285 671 801 1411 802 51225 2280 696 775 1412 801 51212 2283 671 775 1412 802 From 41c43f480512899b8ee0010f87669ced9a3088f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Fri, 6 Jan 2023 15:31:17 +0900 Subject: [PATCH 337/824] Various improvements: Toolbox, Updater and Unit Tests. (#2250) * Toolbox: add seek to character stream method. UpdateUtils: reverse manifest iterator. UnitTests: more unit tests. * Target: bump API version. Updater: delete empty folders from manifest before resource deployment. * UnitTests: use manifest from unit_tests folder instead of global one * Make PVS happy * sector cache: allocate always * Better PVS config for manifest.c * PVS: Move exception outside of condition * PVS: remove confusing condition Co-authored-by: SG --- .../debug/unit_tests/manifest/manifest.c | 75 +++++++++++++++++ .../debug/unit_tests/stream/stream_test.c | 24 ++++++ applications/debug/unit_tests/test_index.c | 2 + .../updater/util/update_task_worker_backup.c | 44 +++++++++- assets/unit_tests/Manifest | 76 +++++++++++++++++ firmware/targets/f7/api_symbols.csv | 3 +- firmware/targets/f7/fatfs/sector_cache.c | 8 +- lib/toolbox/stream/stream.c | 83 ++++++++++++++++++- lib/toolbox/stream/stream.h | 39 ++++++--- lib/update_util/resources/manifest.c | 54 +++++++++++- lib/update_util/resources/manifest.h | 14 ++++ 11 files changed, 398 insertions(+), 24 deletions(-) create mode 100644 applications/debug/unit_tests/manifest/manifest.c create mode 100644 assets/unit_tests/Manifest diff --git a/applications/debug/unit_tests/manifest/manifest.c b/applications/debug/unit_tests/manifest/manifest.c new file mode 100644 index 00000000000..0b24ad1ed65 --- /dev/null +++ b/applications/debug/unit_tests/manifest/manifest.c @@ -0,0 +1,75 @@ +#include +#include "../minunit.h" +#include + +#define TAG "Manifest" + +MU_TEST(manifest_type_test) { + mu_assert(ResourceManifestEntryTypeUnknown == 0, "ResourceManifestEntryTypeUnknown != 0\r\n"); + mu_assert(ResourceManifestEntryTypeVersion == 1, "ResourceManifestEntryTypeVersion != 1\r\n"); + mu_assert( + ResourceManifestEntryTypeTimestamp == 2, "ResourceManifestEntryTypeTimestamp != 2\r\n"); + mu_assert( + ResourceManifestEntryTypeDirectory == 3, "ResourceManifestEntryTypeDirectory != 3\r\n"); + mu_assert(ResourceManifestEntryTypeFile == 4, "ResourceManifestEntryTypeFile != 4\r\n"); +} + +MU_TEST(manifest_iteration_test) { + bool result = true; + size_t counters[5] = {0}; + + Storage* storage = furi_record_open(RECORD_STORAGE); + ResourceManifestReader* manifest_reader = resource_manifest_reader_alloc(storage); + do { + // Open manifest file + if(!resource_manifest_reader_open(manifest_reader, EXT_PATH("unit_tests/Manifest"))) { + result = false; + break; + } + + // Iterate forward + ResourceManifestEntry* entry_ptr = NULL; + while((entry_ptr = resource_manifest_reader_next(manifest_reader))) { + FURI_LOG_D(TAG, "F:%u:%s", entry_ptr->type, furi_string_get_cstr(entry_ptr->name)); + if(entry_ptr->type > 4) { + mu_fail("entry_ptr->type > 4\r\n"); + result = false; + break; + } + counters[entry_ptr->type]++; + } + if(!result) break; + + // Iterate backward + while((entry_ptr = resource_manifest_reader_previous(manifest_reader))) { + FURI_LOG_D(TAG, "B:%u:%s", entry_ptr->type, furi_string_get_cstr(entry_ptr->name)); + if(entry_ptr->type > 4) { + mu_fail("entry_ptr->type > 4\r\n"); + result = false; + break; + } + counters[entry_ptr->type]--; + } + } while(false); + + resource_manifest_reader_free(manifest_reader); + furi_record_close(RECORD_STORAGE); + + mu_assert(counters[ResourceManifestEntryTypeUnknown] == 0, "Unknown counter != 0\r\n"); + mu_assert(counters[ResourceManifestEntryTypeVersion] == 0, "Version counter != 0\r\n"); + mu_assert(counters[ResourceManifestEntryTypeTimestamp] == 0, "Timestamp counter != 0\r\n"); + mu_assert(counters[ResourceManifestEntryTypeDirectory] == 0, "Directory counter != 0\r\n"); + mu_assert(counters[ResourceManifestEntryTypeFile] == 0, "File counter != 0\r\n"); + + mu_assert(result, "Manifest forward iterate failed\r\n"); +} + +MU_TEST_SUITE(manifest_suite) { + MU_RUN_TEST(manifest_type_test); + MU_RUN_TEST(manifest_iteration_test); +} + +int run_minunit_test_manifest() { + MU_RUN_SUITE(manifest_suite); + return MU_EXIT_CODE; +} \ No newline at end of file diff --git a/applications/debug/unit_tests/stream/stream_test.c b/applications/debug/unit_tests/stream/stream_test.c index 802e340259b..c28696570ea 100644 --- a/applications/debug/unit_tests/stream/stream_test.c +++ b/applications/debug/unit_tests/stream/stream_test.c @@ -72,8 +72,32 @@ MU_TEST_1(stream_composite_subtest, Stream* stream) { mu_check(stream_seek(stream, -3, StreamOffsetFromEnd)); mu_check(stream_tell(stream) == 4); + // test seeks to char. content: '1337_69' + stream_rewind(stream); + mu_check(stream_seek_to_char(stream, '3', StreamDirectionForward)); + mu_check(stream_tell(stream) == 1); + mu_check(stream_seek_to_char(stream, '3', StreamDirectionForward)); + mu_check(stream_tell(stream) == 2); + mu_check(stream_seek_to_char(stream, '_', StreamDirectionForward)); + mu_check(stream_tell(stream) == 4); + mu_check(stream_seek_to_char(stream, '9', StreamDirectionForward)); + mu_check(stream_tell(stream) == 6); + mu_check(!stream_seek_to_char(stream, '9', StreamDirectionForward)); + mu_check(stream_tell(stream) == 6); + mu_check(stream_seek_to_char(stream, '_', StreamDirectionBackward)); + mu_check(stream_tell(stream) == 4); + mu_check(stream_seek_to_char(stream, '3', StreamDirectionBackward)); + mu_check(stream_tell(stream) == 2); + mu_check(stream_seek_to_char(stream, '3', StreamDirectionBackward)); + mu_check(stream_tell(stream) == 1); + mu_check(!stream_seek_to_char(stream, '3', StreamDirectionBackward)); + mu_check(stream_tell(stream) == 1); + mu_check(stream_seek_to_char(stream, '1', StreamDirectionBackward)); + mu_check(stream_tell(stream) == 0); + // write string with replacemet // "1337_69" -> "1337lee" + mu_check(stream_seek(stream, 4, StreamOffsetFromStart)); mu_check(stream_write_string(stream, string_lee) == 3); mu_check(stream_size(stream) == 7); mu_check(stream_tell(stream) == 7); diff --git a/applications/debug/unit_tests/test_index.c b/applications/debug/unit_tests/test_index.c index ccf47153162..2bb9c423f3c 100644 --- a/applications/debug/unit_tests/test_index.c +++ b/applications/debug/unit_tests/test_index.c @@ -13,6 +13,7 @@ int run_minunit_test_furi_hal(); int run_minunit_test_furi_string(); int run_minunit_test_infrared(); int run_minunit_test_rpc(); +int run_minunit_test_manifest(); int run_minunit_test_flipper_format(); int run_minunit_test_flipper_format_string(); int run_minunit_test_stream(); @@ -41,6 +42,7 @@ const UnitTest unit_tests[] = { {.name = "storage", .entry = run_minunit_test_storage}, {.name = "stream", .entry = run_minunit_test_stream}, {.name = "dirwalk", .entry = run_minunit_test_dirwalk}, + {.name = "manifest", .entry = run_minunit_test_manifest}, {.name = "flipper_format", .entry = run_minunit_test_flipper_format}, {.name = "flipper_format_string", .entry = run_minunit_test_flipper_format_string}, {.name = "rpc", .entry = run_minunit_test_rpc}, diff --git a/applications/system/updater/util/update_task_worker_backup.c b/applications/system/updater/util/update_task_worker_backup.c index 1f88d4f4480..78040106845 100644 --- a/applications/system/updater/util/update_task_worker_backup.c +++ b/applications/system/updater/util/update_task_worker_backup.c @@ -79,8 +79,8 @@ static void update_task_set_progress( update_task, UpdateTaskStageProgress, - /* For this stage, first 30% of progress = cleanup */ - (n_processed_files++ * 30) / (n_approx_file_entries + 1)); + /* For this stage, first 20% of progress = cleanup files */ + (n_processed_files++ * 20) / (n_approx_file_entries + 1)); FuriString* file_path = furi_string_alloc(); path_concat( @@ -90,6 +90,46 @@ static void furi_string_free(file_path); } } + + while((entry_ptr = resource_manifest_reader_previous(manifest_reader))) { + if(entry_ptr->type == ResourceManifestEntryTypeDirectory) { + update_task_set_progress( + update_task, + UpdateTaskStageProgress, + /* For this stage, second 10% of progress = cleanup directories */ + (n_processed_files++ * 10) / (n_approx_file_entries + 1)); + + FuriString* folder_path = furi_string_alloc(); + File* folder_file = storage_file_alloc(update_task->storage); + + do { + path_concat( + STORAGE_EXT_PATH_PREFIX, + furi_string_get_cstr(entry_ptr->name), + folder_path); + + FURI_LOG_D(TAG, "Removing folder %s", furi_string_get_cstr(folder_path)); + if(!storage_dir_open(folder_file, furi_string_get_cstr(folder_path))) { + FURI_LOG_W( + TAG, + "%s can't be opened, skipping", + furi_string_get_cstr(folder_path)); + break; + } + + if(storage_dir_read(folder_file, NULL, NULL, 0)) { + FURI_LOG_I( + TAG, "%s is not empty, skipping", furi_string_get_cstr(folder_path)); + break; + } + + storage_simply_remove(update_task->storage, furi_string_get_cstr(folder_path)); + } while(false); + + storage_file_free(folder_file); + furi_string_free(folder_path); + } + } } while(false); resource_manifest_reader_free(manifest_reader); } diff --git a/assets/unit_tests/Manifest b/assets/unit_tests/Manifest new file mode 100644 index 00000000000..db2979ee6f9 --- /dev/null +++ b/assets/unit_tests/Manifest @@ -0,0 +1,76 @@ +V:0 +T:1672935435 +D:infrared +D:nfc +D:subghz +F:4bff70f2a2ae771f81de5cfb090b3d74:3952:infrared/test_kaseikyo.irtest +F:8556d32d7c54e66771d9da78d007d379:21463:infrared/test_nec.irtest +F:860c0c475573878842180a6cb50c85c7:2012:infrared/test_nec42.irtest +F:2b3cbf3fe7d3642190dfb8362dcc0ed6:3522:infrared/test_nec42ext.irtest +F:c74bbd7f885ab8fbc3b3363598041bc1:18976:infrared/test_necext.irtest +F:cab5e604abcb233bcb27903baec24462:7460:infrared/test_rc5.irtest +F:3d22b3ec2531bb8f4842c9c0c6a8d97c:547:infrared/test_rc5x.irtest +F:c9cb9fa4decbdd077741acb845f21343:8608:infrared/test_rc6.irtest +F:97de943385bc6ad1c4a58fc4fedb5244:16975:infrared/test_samsung32.irtest +F:4eb36c62d4f2e737a3e4a64b5ff0a8e7:41623:infrared/test_sirc.irtest +F:e4ec3299cbe1f528fb1b9b45aac53556:4182:nfc/nfc_nfca_signal_long.nfc +F:af4d10974834c2703ad29e859eea78c2:1020:nfc/nfc_nfca_signal_short.nfc +F:224d12457a26774d8d2aa0d4b3a15652:160:subghz/ansonic.sub +F:ce9fc98dc01230387a340332316774f1:13642:subghz/ansonic_raw.sub +F:f958927b656d0804036c28b4a31ff856:157:subghz/bett.sub +F:b4b17b2603fa3a144dbea4d9ede9f61d:5913:subghz/bett_raw.sub +F:370a0c62be967b420da5e60ffcdc078b:157:subghz/came.sub +F:0156915c656d8c038c6d555d34349a36:6877:subghz/came_atomo_raw.sub +F:111a8b796661f3cbd6f49f756cf91107:8614:subghz/came_raw.sub +F:2101b0a5a72c87f9dce77223b2885aa7:162:subghz/came_twee.sub +F:c608b78b8e4646eeb94db37644623254:10924:subghz/came_twee_raw.sub +F:c4a55acddb68fc3111d592c9292022a8:21703:subghz/cenmax_raw.sub +F:51d6bd600345954b9c84a5bc6e999313:159:subghz/clemsa.sub +F:14fa0d5931a32674bfb2ddf288f3842b:21499:subghz/clemsa_raw.sub +F:f38b6dfa0920199200887b2cd5c0a385:161:subghz/doitrand.sub +F:c7e53da8e3588a2c0721aa794699ccd4:24292:subghz/doitrand_raw.sub +F:cc73b6f4d05bfe30c67a0d18b63e58d9:159:subghz/doorhan.sub +F:22fec89c5cc43504ad4391e61e12c7e0:10457:subghz/doorhan_raw.sub +F:3a97d8bd32ddaff42932b4c3033ee2d2:12732:subghz/faac_slh_raw.sub +F:06d3226f5330665f48d41c49e34fed15:159:subghz/gate_tx.sub +F:8b150a8d38ac7c4f7063ee0d42050399:13827:subghz/gate_tx_raw.sub +F:a7904e17b0c18c083ae1acbefc330c7a:159:subghz/holtek.sub +F:72bb528255ef1c135cb3f436414897d3:173:subghz/holtek_ht12x.sub +F:54ceacb8c156f9534fc7ee0a0911f4da:11380:subghz/holtek_ht12x_raw.sub +F:4a9567c1543cf3e7bb5350b635d9076f:31238:subghz/holtek_raw.sub +F:ca86c0d78364d704ff62b0698093d396:162:subghz/honeywell_wdb.sub +F:f606548c935adc8d8bc804326ef67543:38415:subghz/honeywell_wdb_raw.sub +F:20bba4b0aec006ced7e82513f9459e31:15532:subghz/hormann_hsm_raw.sub +F:3392f2db6aa7777e937db619b86203bb:10637:subghz/ido_117_111_raw.sub +F:cc5c7968527cc233ef11a08986e31bf2:167:subghz/intertechno_v3.sub +F:70bceb941739260ab9f6162cfdeb0347:18211:subghz/intertechno_v3_raw.sub +F:bc9a4622f3e22fd7f82eb3f26e61f59b:44952:subghz/kia_seed_raw.sub +F:6b6e95fc70ea481dc6184d291466d16a:159:subghz/linear.sub +F:77aaa9005db54c0357451ced081857b2:14619:subghz/linear_raw.sub +F:1a618e21e6ffa9984d465012e704c450:161:subghz/magellan.sub +F:bf43cb85d79e20644323d6acad87e028:5808:subghz/magellan_raw.sub +F:4ef17320f936ee88e92582a9308b2faa:161:subghz/marantec.sub +F:507a8413a1603ad348eea945123fb7cc:21155:subghz/marantec_raw.sub +F:22b69dc490d5425488342b5c5a838d55:161:subghz/megacode.sub +F:4f8fe9bef8bdd9c52f3f77e829f8986f:6205:subghz/megacode_raw.sub +F:b39f62cb108c2fa9916e0a466596ab87:18655:subghz/nero_radio_raw.sub +F:d0d70f8183032096805a41e1808c093b:26436:subghz/nero_sketch_raw.sub +F:c6999bd0eefd0fccf34820e17bcbc8ba:161:subghz/nice_flo.sub +F:9b1200600b9ec2a73166797ff243fbfc:3375:subghz/nice_flo_raw.sub +F:b52bafb098282676d1c7163bfb0d6e73:8773:subghz/nice_flor_s_raw.sub +F:e4df94dfdee2efadf2ed9a1e9664f8b2:163:subghz/phoenix_v2.sub +F:8ec066976df93fba6335b3f6dc47014c:8548:subghz/phoenix_v2_raw.sub +F:2b1192e4898aaf274caebbb493b9f96e:164:subghz/power_smart.sub +F:8b8195cab1d9022fe38e802383fb923a:3648:subghz/power_smart_raw.sub +F:1ccf1289533e0486a1d010d934ad7b06:170:subghz/princeton.sub +F:8bccc506a61705ec429aecb879e5d7ce:7344:subghz/princeton_raw.sub +F:0bda91d783e464165190c3b3d16666a7:38724:subghz/scher_khan_magic_code.sub +F:116d7e1a532a0c9e00ffeee105f7138b:166:subghz/security_pls_1_0.sub +F:441fc7fc6fa11ce0068fde3f6145177b:69413:subghz/security_pls_1_0_raw.sub +F:e5e33c24c5e55f592ca892b5aa8fa31f:208:subghz/security_pls_2_0.sub +F:2614f0aef367042f8623719d765bf2c0:62287:subghz/security_pls_2_0_raw.sub +F:8eb533544c4c02986800c90e935184ff:168:subghz/smc5326.sub +F:fc67a4fe7e0b3bc81a1c8da8caca7658:4750:subghz/smc5326_raw.sub +F:24196a4c4af1eb03404a2ee434c864bf:4096:subghz/somfy_keytis_raw.sub +F:6a5ece145a5694e543d99bf1b970baf0:9741:subghz/somfy_telis_raw.sub +F:0ad046bfa9ec872e92141a69bbf03d92:382605:subghz/test_random_raw.sub diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 9e1fa85d779..ac7d1a0c575 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,11.5,, +Version,+,11.6,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -2480,6 +2480,7 @@ Function,+,stream_read_line,_Bool,"Stream*, FuriString*" Function,+,stream_rewind,_Bool,Stream* Function,+,stream_save_to_file,size_t,"Stream*, Storage*, const char*, FS_OpenMode" Function,+,stream_seek,_Bool,"Stream*, int32_t, StreamOffset" +Function,+,stream_seek_to_char,_Bool,"Stream*, char, StreamDirection" Function,+,stream_size,size_t,Stream* Function,+,stream_split,_Bool,"Stream*, Stream*, Stream*" Function,+,stream_tell,size_t,Stream* diff --git a/firmware/targets/f7/fatfs/sector_cache.c b/firmware/targets/f7/fatfs/sector_cache.c index 5a4f1b9789b..d23c1d5ad58 100644 --- a/firmware/targets/f7/fatfs/sector_cache.c +++ b/firmware/targets/f7/fatfs/sector_cache.c @@ -8,6 +8,7 @@ #define SECTOR_SIZE 512 #define N_SECTORS 8 +#define TAG "SDCache" typedef struct { uint32_t itr; @@ -19,14 +20,15 @@ static SectorCache* cache = NULL; void sector_cache_init() { if(cache == NULL) { - cache = furi_hal_memory_alloc(sizeof(SectorCache)); + // TODO: tuneup allocation order, to place cache in mem pool (MEM2) + cache = memmgr_alloc_from_pool(sizeof(SectorCache)); } if(cache != NULL) { - FURI_LOG_I("SectorCache", "Initializing sector cache"); + FURI_LOG_I(TAG, "Init"); memset(cache, 0, sizeof(SectorCache)); } else { - FURI_LOG_E("SectorCache", "Cannot enable sector cache"); + FURI_LOG_E(TAG, "Init failed"); } } diff --git a/lib/toolbox/stream/stream.c b/lib/toolbox/stream/stream.c index 055bab5bfbb..407da0f2c19 100644 --- a/lib/toolbox/stream/stream.c +++ b/lib/toolbox/stream/stream.c @@ -4,6 +4,8 @@ #include #include +#define STREAM_BUFFER_SIZE (32U) + void stream_free(Stream* stream) { furi_assert(stream); stream->vtable->free(stream); @@ -24,6 +26,82 @@ bool stream_seek(Stream* stream, int32_t offset, StreamOffset offset_type) { return stream->vtable->seek(stream, offset, offset_type); } +static bool stream_seek_to_char_forward(Stream* stream, char c) { + // Search is starting from seconds character + if(!stream_seek(stream, 1, StreamOffsetFromCurrent)) { + return false; + } + + // Search character in a stream + bool result = false; + while(!result) { + uint8_t buffer[STREAM_BUFFER_SIZE] = {0}; + size_t ret = stream_read(stream, buffer, STREAM_BUFFER_SIZE); + for(size_t i = 0; i < ret; i++) { + if(buffer[i] == c) { + stream_seek(stream, (int32_t)i - ret, StreamOffsetFromCurrent); + result = true; + break; + } + } + if(ret != STREAM_BUFFER_SIZE) break; + } + return result; +} + +static bool stream_seek_to_char_backward(Stream* stream, char c) { + size_t anchor = stream_tell(stream); + + // Special case, no previous characters + if(anchor == 0) { + return false; + } + + bool result = false; + while(!result) { + // Seek back + uint8_t buffer[STREAM_BUFFER_SIZE] = {0}; + size_t to_read = STREAM_BUFFER_SIZE; + if(to_read > anchor) { + to_read = anchor; + } + + anchor -= to_read; + furi_check(stream_seek(stream, anchor, StreamOffsetFromStart)); + + size_t ret = stream_read(stream, buffer, to_read); + for(size_t i = 0; i < ret; i++) { + size_t cursor = ret - i - 1; + if(buffer[cursor] == c) { + result = true; + furi_check(stream_seek(stream, anchor + cursor, StreamOffsetFromStart)); + break; + } else { + } + } + if(ret != STREAM_BUFFER_SIZE) break; + } + return result; +} + +bool stream_seek_to_char(Stream* stream, char c, StreamDirection direction) { + const size_t old_position = stream_tell(stream); + + bool result = false; + if(direction == StreamDirectionForward) { + result = stream_seek_to_char_forward(stream, c); + } else if(direction == StreamDirectionBackward) { + result = stream_seek_to_char_backward(stream, c); + } + + // Rollback + if(!result) { + stream_seek(stream, old_position, StreamOffsetFromStart); + } + + return result; +} + size_t stream_tell(Stream* stream) { furi_assert(stream); return stream->vtable->tell(stream); @@ -69,11 +147,10 @@ static bool stream_write_struct(Stream* stream, const void* context) { bool stream_read_line(Stream* stream, FuriString* str_result) { furi_string_reset(str_result); - const uint8_t buffer_size = 32; - uint8_t buffer[buffer_size]; + uint8_t buffer[STREAM_BUFFER_SIZE]; do { - uint16_t bytes_were_read = stream_read(stream, buffer, buffer_size); + uint16_t bytes_were_read = stream_read(stream, buffer, STREAM_BUFFER_SIZE); if(bytes_were_read == 0) break; bool result = false; diff --git a/lib/toolbox/stream/stream.h b/lib/toolbox/stream/stream.h index fc3855102cb..84b4f0eb2f6 100644 --- a/lib/toolbox/stream/stream.h +++ b/lib/toolbox/stream/stream.h @@ -16,6 +16,11 @@ typedef enum { StreamOffsetFromEnd, } StreamOffset; +typedef enum { + StreamDirectionForward, + StreamDirectionBackward, +} StreamDirection; + typedef bool (*StreamWriteCB)(Stream* stream, const void* context); /** @@ -31,15 +36,15 @@ void stream_free(Stream* stream); void stream_clean(Stream* stream); /** - * Indicates that the rw pointer is at the end of the stream + * Indicates that the RW pointer is at the end of the stream * @param stream Stream instance - * @return true if rw pointer is at the end of the stream - * @return false if rw pointer is not at the end of the stream + * @return true if RW pointer is at the end of the stream + * @return false if RW pointer is not at the end of the stream */ bool stream_eof(Stream* stream); /** - * Moves the rw pointer. + * Moves the RW pointer. * @param stream Stream instance * @param offset how much to move the pointer * @param offset_type starting from what @@ -48,10 +53,20 @@ bool stream_eof(Stream* stream); */ bool stream_seek(Stream* stream, int32_t offset, StreamOffset offset_type); +/** Seek to next occurrence of the character + * + * @param stream Pointer to the stream instance + * @param[in] c The Character + * @param[in] direction The Direction + * + * @return true on success + */ +bool stream_seek_to_char(Stream* stream, char c, StreamDirection direction); + /** - * Gets the value of the rw pointer + * Gets the value of the RW pointer * @param stream Stream instance - * @return size_t value of the rw pointer + * @return size_t value of the RW pointer */ size_t stream_tell(Stream* stream); @@ -101,13 +116,13 @@ bool stream_delete_and_insert( * Read line from a stream (supports LF and CRLF line endings) * @param stream * @param str_result - * @return true if line lenght is not zero + * @return true if line length is not zero * @return false otherwise */ bool stream_read_line(Stream* stream, FuriString* str_result); /** - * Moves the rw pointer to the start + * Moves the RW pointer to the start * @param stream Stream instance */ bool stream_rewind(Stream* stream); @@ -157,7 +172,7 @@ size_t stream_write_vaformat(Stream* stream, const char* format, va_list args); /** * Insert N chars to the stream, starting at the current pointer. - * Data will be inserted, not overwritteт, so the stream will be increased in size. + * Data will be inserted, not overwritten, so the stream will be increased in size. * @param stream Stream instance * @param data data to be inserted * @param size size of data to be inserted @@ -273,7 +288,7 @@ bool stream_delete_and_insert_vaformat( /** * Remove N chars from the stream, starting at the current pointer. - * The size may be larger than stream size, the stream will be cleared from current rw pointer to the end. + * The size may be larger than stream size, the stream will be cleared from current RW pointer to the end. * @param stream Stream instance * @param size how many chars need to be deleted * @return true if the operation was successful @@ -282,7 +297,7 @@ bool stream_delete_and_insert_vaformat( bool stream_delete(Stream* stream, size_t size); /** - * Copy data from one stream to another. Data will be copied from current rw pointer and to current rw pointer. + * Copy data from one stream to another. Data will be copied from current RW pointer and to current RW pointer. * @param stream_from * @param stream_to * @param size @@ -328,7 +343,7 @@ size_t stream_load_from_file(Stream* stream, Storage* storage, const char* path) size_t stream_save_to_file(Stream* stream, Storage* storage, const char* path, FS_OpenMode mode); /** - * Dump stream inner data (size, RW positiot, content) + * Dump stream inner data (size, RW position, content) * @param stream Stream instance */ void stream_dump_data(Stream* stream); diff --git a/lib/update_util/resources/manifest.c b/lib/update_util/resources/manifest.c index baa7acebdfd..5a818a0a4a4 100644 --- a/lib/update_util/resources/manifest.c +++ b/lib/update_util/resources/manifest.c @@ -60,6 +60,12 @@ ResourceManifestEntry* resource_manifest_reader_next(ResourceManifestReader* res char type_code = furi_string_get_char(resource_manifest->linebuf, 0); switch(type_code) { + case 'V': + resource_manifest->entry.type = ResourceManifestEntryTypeVersion; + break; + case 'T': + resource_manifest->entry.type = ResourceManifestEntryTypeTimestamp; + break; case 'F': resource_manifest->entry.type = ResourceManifestEntryTypeFile; break; @@ -98,9 +104,9 @@ ResourceManifestEntry* resource_manifest_reader_next(ResourceManifestReader* res furi_string_right(resource_manifest->linebuf, offs + 1); furi_string_set(resource_manifest->entry.name, resource_manifest->linebuf); - } else if(resource_manifest->entry.type == ResourceManifestEntryTypeDirectory) { //-V547 - /* Parse directory entry - D: */ + } else { //-V547 + /* Everything else is plain key value. Parse version, timestamp or directory entry + : */ /* Remove entry type code */ furi_string_right(resource_manifest->linebuf, 2); @@ -113,3 +119,45 @@ ResourceManifestEntry* resource_manifest_reader_next(ResourceManifestReader* res return NULL; } + +ResourceManifestEntry* + resource_manifest_reader_previous(ResourceManifestReader* resource_manifest) { + furi_assert(resource_manifest); + + // Snapshot position for rollback + const size_t previous_position = stream_tell(resource_manifest->stream); + + // We need to jump 2 lines back + size_t jumps = 2; + // Special case: end of the file. + const bool was_eof = stream_eof(resource_manifest->stream); + if(was_eof) { + jumps = 1; + } + while(jumps) { + if(!stream_seek_to_char(resource_manifest->stream, '\n', StreamDirectionBackward)) { + break; + } + if(stream_tell(resource_manifest->stream) < (previous_position - 1)) { + jumps--; + } + } + + // Special case: first line. Force seek to zero + if(jumps == 1) { + jumps = 0; + stream_seek(resource_manifest->stream, 0, StreamOffsetFromStart); + } + + if(jumps == 0) { + ResourceManifestEntry* entry = resource_manifest_reader_next(resource_manifest); + // Special case: was end of the file, prevent loop + if(was_eof) { + stream_seek(resource_manifest->stream, -1, StreamOffsetFromCurrent); + } + return entry; + } else { + stream_seek(resource_manifest->stream, previous_position, StreamOffsetFromStart); + return NULL; + } +} diff --git a/lib/update_util/resources/manifest.h b/lib/update_util/resources/manifest.h index 8baa1613ea2..ddceb5ffaa0 100644 --- a/lib/update_util/resources/manifest.h +++ b/lib/update_util/resources/manifest.h @@ -11,6 +11,8 @@ extern "C" { typedef enum { ResourceManifestEntryTypeUnknown = 0, + ResourceManifestEntryTypeVersion, + ResourceManifestEntryTypeTimestamp, ResourceManifestEntryTypeDirectory, ResourceManifestEntryTypeFile, } ResourceManifestEntryType; @@ -52,6 +54,18 @@ bool resource_manifest_reader_open(ResourceManifestReader* resource_manifest, co */ ResourceManifestEntry* resource_manifest_reader_next(ResourceManifestReader* resource_manifest); +/** Read previous file/dir entry from manifest + * + * You must be at the end of the manifest to use this function. + * Intended to be used after reaching end with resource_manifest_reader_next + * + * @param resource_manifest Pointer to the ResourceManifestReader instance + * + * @return entry or NULL if end of file + */ +ResourceManifestEntry* + resource_manifest_reader_previous(ResourceManifestReader* resource_manifest); + #ifdef __cplusplus } // extern "C" #endif \ No newline at end of file From 9740dd8c7579fa07fe509664893e770ed27da187 Mon Sep 17 00:00:00 2001 From: yan0f <31446439+yan0f@users.noreply.github.com> Date: Fri, 6 Jan 2023 19:06:50 +0300 Subject: [PATCH 338/824] Fix typos in source code (#2258) --- applications/debug/unit_tests/stream/stream_test.c | 2 +- lib/ST25RFAL002/include/rfal_isoDep.h | 2 +- lib/ST25RFAL002/include/rfal_t4t.h | 2 +- lib/fatfs/ff.c | 4 ++-- lib/infrared/encoder_decoder/infrared.h | 2 +- lib/lfrfid/protocols/protocol_pyramid.c | 2 +- lib/nfc/nfc_device.c | 4 ++-- lib/nfc/parsers/plantain_4k_parser.c | 2 +- lib/nfc/parsers/plantain_parser.c | 2 +- lib/nfc/parsers/two_cities.c | 2 +- lib/one_wire/ibutton/protocols/protocol_cyfral.c | 2 +- lib/subghz/environment.h | 2 +- scripts/flipper/assets/icon.py | 2 +- 13 files changed, 15 insertions(+), 15 deletions(-) diff --git a/applications/debug/unit_tests/stream/stream_test.c b/applications/debug/unit_tests/stream/stream_test.c index c28696570ea..2fa3b21a297 100644 --- a/applications/debug/unit_tests/stream/stream_test.c +++ b/applications/debug/unit_tests/stream/stream_test.c @@ -95,7 +95,7 @@ MU_TEST_1(stream_composite_subtest, Stream* stream) { mu_check(stream_seek_to_char(stream, '1', StreamDirectionBackward)); mu_check(stream_tell(stream) == 0); - // write string with replacemet + // write string with replacement // "1337_69" -> "1337lee" mu_check(stream_seek(stream, 4, StreamOffsetFromStart)); mu_check(stream_write_string(stream, string_lee) == 3); diff --git a/lib/ST25RFAL002/include/rfal_isoDep.h b/lib/ST25RFAL002/include/rfal_isoDep.h index f4ebdac5905..34bd6172a5e 100644 --- a/lib/ST25RFAL002/include/rfal_isoDep.h +++ b/lib/ST25RFAL002/include/rfal_isoDep.h @@ -857,7 +857,7 @@ ReturnCode rfalIsoDepATTRIB( * \brief Deselects PICC * * This function sends a deselect command to PICC and waits for it`s - * responce in a blocking way + * response in a blocking way * * \return ERR_NONE : Deselect successfully sent and acknowledged by PICC * \return ERR_TIMEOUT: No response rcvd from PICC diff --git a/lib/ST25RFAL002/include/rfal_t4t.h b/lib/ST25RFAL002/include/rfal_t4t.h index ff026e1a999..edee1cd8cd7 100644 --- a/lib/ST25RFAL002/include/rfal_t4t.h +++ b/lib/ST25RFAL002/include/rfal_t4t.h @@ -88,7 +88,7 @@ #define RFAL_T4T_ISO7816_P2_SELECT_RETURN_FCI_TEMPLATE \ 0x00U /*!< b4b3 P2 value for Return FCI template */ #define RFAL_T4T_ISO7816_P2_SELECT_NO_RESPONSE_DATA \ - 0x0CU /*!< b4b3 P2 value for No responce data */ + 0x0CU /*!< b4b3 P2 value for No response data */ #define RFAL_T4T_ISO7816_STATUS_COMPLETE \ 0x9000U /*!< Command completed \ Normal processing - No further qualification*/ diff --git a/lib/fatfs/ff.c b/lib/fatfs/ff.c index 85ab9736eb5..d3908957888 100644 --- a/lib/fatfs/ff.c +++ b/lib/fatfs/ff.c @@ -3975,7 +3975,7 @@ FRESULT f_getcwd ( #endif if (i == len) { /* Root-directory */ *tp++ = '/'; - } else { /* Sub-directroy */ + } else { /* Sub-directory */ do /* Add stacked path str */ *tp++ = buff[i++]; while (i < len); @@ -4673,7 +4673,7 @@ FRESULT f_mkdir ( } } if (res == FR_OK) { - res = dir_register(&dj); /* Register the object to the directoy */ + res = dir_register(&dj); /* Register the object to the directory */ } if (res == FR_OK) { #if _FS_EXFAT diff --git a/lib/infrared/encoder_decoder/infrared.h b/lib/infrared/encoder_decoder/infrared.h index 086950f1ef8..2c76645fff8 100644 --- a/lib/infrared/encoder_decoder/infrared.h +++ b/lib/infrared/encoder_decoder/infrared.h @@ -10,7 +10,7 @@ extern "C" { #define INFRARED_COMMON_CARRIER_FREQUENCY ((uint32_t)38000) #define INFRARED_COMMON_DUTY_CYCLE ((float)0.33) -/* if we want to see splitted raw signals during brutforce, +/* if we want to see split raw signals during bruteforce, * we have to have RX raw timing delay less than TX */ #define INFRARED_RAW_RX_TIMING_DELAY_US 150000 #define INFRARED_RAW_TX_TIMING_DELAY_US 180000 diff --git a/lib/lfrfid/protocols/protocol_pyramid.c b/lib/lfrfid/protocols/protocol_pyramid.c index 974bb6da677..d794bb46ead 100644 --- a/lib/lfrfid/protocols/protocol_pyramid.c +++ b/lib/lfrfid/protocols/protocol_pyramid.c @@ -88,7 +88,7 @@ static bool protocol_pyramid_can_be_decoded(uint8_t* data) { } uint8_t fmt_len = 105 - j; - // Only suppport 26bit format for now + // Only support 26bit format for now if(fmt_len != 26) return false; return true; diff --git a/lib/nfc/nfc_device.c b/lib/nfc/nfc_device.c index 52bff24e3e0..d10eaa0e57e 100644 --- a/lib/nfc/nfc_device.c +++ b/lib/nfc/nfc_device.c @@ -856,7 +856,7 @@ static bool nfc_device_load_mifare_classic_data(FlipperFormat* file, NfcDevice* bool old_format = false; // 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 + // Load unread sectors with zero keys access for backward compatibility if(!flipper_format_rewind(file)) break; old_format = true; } else { @@ -1125,7 +1125,7 @@ static bool nfc_device_load_data(NfcDevice* dev, FuriString* path, bool show_dia } do { - // Check existance of shadow file + // Check existence of shadow file nfc_device_get_shadow_path(path, temp_str); dev->shadow_file_exist = storage_common_stat(dev->storage, furi_string_get_cstr(temp_str), NULL) == FSE_OK; diff --git a/lib/nfc/parsers/plantain_4k_parser.c b/lib/nfc/parsers/plantain_4k_parser.c index 9a51cdeaf23..e636bee002b 100644 --- a/lib/nfc/parsers/plantain_4k_parser.c +++ b/lib/nfc/parsers/plantain_4k_parser.c @@ -106,7 +106,7 @@ bool plantain_4k_parser_parse(NfcDeviceData* dev_data) { // Point to block 0 of sector 0, value 0 temp_ptr = &data->block[0 * 4].value[0]; // Read first 7 bytes of block 0 of sector 0 from last to first and convert them to uint64_t - // 80 5C 23 8A 16 31 04 becomes 04 31 16 8A 23 5C 80, and equals to 36130104729284868 decimal + // 04 31 16 8A 23 5C 80 becomes 80 5C 23 8A 16 31 04, and equals to 36130104729284868 decimal uint8_t card_number_arr[7]; for(size_t i = 0; i < 7; i++) { card_number_arr[i] = temp_ptr[6 - i]; diff --git a/lib/nfc/parsers/plantain_parser.c b/lib/nfc/parsers/plantain_parser.c index 799262171e2..c0e2a094723 100644 --- a/lib/nfc/parsers/plantain_parser.c +++ b/lib/nfc/parsers/plantain_parser.c @@ -79,7 +79,7 @@ bool plantain_parser_parse(NfcDeviceData* dev_data) { // Point to block 0 of sector 0, value 0 temp_ptr = &data->block[0 * 4].value[0]; // Read first 7 bytes of block 0 of sector 0 from last to first and convert them to uint64_t - // 80 5C 23 8A 16 31 04 becomes 04 31 16 8A 23 5C 80, and equals to 36130104729284868 decimal + // 04 31 16 8A 23 5C 80 becomes 80 5C 23 8A 16 31 04, and equals to 36130104729284868 decimal uint8_t card_number_arr[7]; for(size_t i = 0; i < 7; i++) { card_number_arr[i] = temp_ptr[6 - i]; diff --git a/lib/nfc/parsers/two_cities.c b/lib/nfc/parsers/two_cities.c index 2f4b7dd0d31..335248b2abd 100644 --- a/lib/nfc/parsers/two_cities.c +++ b/lib/nfc/parsers/two_cities.c @@ -107,7 +107,7 @@ bool two_cities_parser_parse(NfcDeviceData* dev_data) { // Point to block 0 of sector 0, value 0 temp_ptr = &data->block[0 * 4].value[0]; // Read first 7 bytes of block 0 of sector 0 from last to first and convert them to uint64_t - // 80 5C 23 8A 16 31 04 becomes 04 31 16 8A 23 5C 80, and equals to 36130104729284868 decimal + // 04 31 16 8A 23 5C 80 becomes 80 5C 23 8A 16 31 04, and equals to 36130104729284868 decimal uint8_t card_number_arr[7]; for(size_t i = 0; i < 7; i++) { card_number_arr[i] = temp_ptr[6 - i]; diff --git a/lib/one_wire/ibutton/protocols/protocol_cyfral.c b/lib/one_wire/ibutton/protocols/protocol_cyfral.c index 0c44c2b455f..a5f459bc049 100644 --- a/lib/one_wire/ibutton/protocols/protocol_cyfral.c +++ b/lib/one_wire/ibutton/protocols/protocol_cyfral.c @@ -181,7 +181,7 @@ static bool protocol_cyfral_decoder_feed(ProtocolCyfral* proto, bool level, uint cyfral->index++; } - // succefully read 8 nibbles + // successfully read 8 nibbles if(cyfral->index == 8) { cyfral->state = CYFRAL_READ_STOP_NIBBLE; } diff --git a/lib/subghz/environment.h b/lib/subghz/environment.h index 5f8fcf1f546..e994f7c975b 100644 --- a/lib/subghz/environment.h +++ b/lib/subghz/environment.h @@ -26,7 +26,7 @@ void subghz_environment_free(SubGhzEnvironment* instance); * Downloading the manufacture key file. * @param instance Pointer to a SubGhzEnvironment instance * @param filename Full path to the file - * @return true On succes + * @return true On success */ bool subghz_environment_load_keystore(SubGhzEnvironment* instance, const char* filename); diff --git a/scripts/flipper/assets/icon.py b/scripts/flipper/assets/icon.py index 235af7b05ec..ed85b024e85 100644 --- a/scripts/flipper/assets/icon.py +++ b/scripts/flipper/assets/icon.py @@ -104,7 +104,7 @@ def file2image(file): data_enc = bytearray(data_encoded_str) data_enc = bytearray([len(data_enc) & 0xFF, len(data_enc) >> 8]) + data_enc - # Use encoded data only if its lenght less than original, including header + # Use encoded data only if its length less than original, including header if len(data_enc) < len(data_bin) + 1: data = b"\x01\x00" + data_enc else: From 5e74622b2a7bd47749bcc3968d2312e0f4af7cd1 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Fri, 6 Jan 2023 18:16:58 +0200 Subject: [PATCH 339/824] [FL-3072] Add the sleigh ride animation (#2224) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add sleigh animation * Fix manifest Co-authored-by: あく --- .../L1_Sleigh_ride_128x64/frame_0.png | Bin 0 -> 1656 bytes .../L1_Sleigh_ride_128x64/frame_1.png | Bin 0 -> 1754 bytes .../L1_Sleigh_ride_128x64/frame_10.png | Bin 0 -> 1494 bytes .../L1_Sleigh_ride_128x64/frame_11.png | Bin 0 -> 1637 bytes .../L1_Sleigh_ride_128x64/frame_12.png | Bin 0 -> 1713 bytes .../L1_Sleigh_ride_128x64/frame_13.png | Bin 0 -> 1585 bytes .../L1_Sleigh_ride_128x64/frame_14.png | Bin 0 -> 1634 bytes .../L1_Sleigh_ride_128x64/frame_15.png | Bin 0 -> 1771 bytes .../L1_Sleigh_ride_128x64/frame_16.png | Bin 0 -> 1681 bytes .../L1_Sleigh_ride_128x64/frame_17.png | Bin 0 -> 1503 bytes .../L1_Sleigh_ride_128x64/frame_18.png | Bin 0 -> 1663 bytes .../L1_Sleigh_ride_128x64/frame_19.png | Bin 0 -> 1661 bytes .../L1_Sleigh_ride_128x64/frame_2.png | Bin 0 -> 1681 bytes .../L1_Sleigh_ride_128x64/frame_20.png | Bin 0 -> 1559 bytes .../L1_Sleigh_ride_128x64/frame_21.png | Bin 0 -> 1542 bytes .../L1_Sleigh_ride_128x64/frame_22.png | Bin 0 -> 1736 bytes .../L1_Sleigh_ride_128x64/frame_23.png | Bin 0 -> 1621 bytes .../L1_Sleigh_ride_128x64/frame_24.png | Bin 0 -> 1628 bytes .../L1_Sleigh_ride_128x64/frame_25.png | Bin 0 -> 1671 bytes .../L1_Sleigh_ride_128x64/frame_26.png | Bin 0 -> 1636 bytes .../L1_Sleigh_ride_128x64/frame_27.png | Bin 0 -> 1621 bytes .../L1_Sleigh_ride_128x64/frame_28.png | Bin 0 -> 1099 bytes .../L1_Sleigh_ride_128x64/frame_29.png | Bin 0 -> 812 bytes .../L1_Sleigh_ride_128x64/frame_3.png | Bin 0 -> 1651 bytes .../L1_Sleigh_ride_128x64/frame_30.png | Bin 0 -> 536 bytes .../L1_Sleigh_ride_128x64/frame_31.png | Bin 0 -> 492 bytes .../L1_Sleigh_ride_128x64/frame_32.png | Bin 0 -> 503 bytes .../L1_Sleigh_ride_128x64/frame_33.png | Bin 0 -> 897 bytes .../L1_Sleigh_ride_128x64/frame_34.png | Bin 0 -> 1490 bytes .../L1_Sleigh_ride_128x64/frame_35.png | Bin 0 -> 1741 bytes .../L1_Sleigh_ride_128x64/frame_36.png | Bin 0 -> 1538 bytes .../L1_Sleigh_ride_128x64/frame_4.png | Bin 0 -> 1668 bytes .../L1_Sleigh_ride_128x64/frame_5.png | Bin 0 -> 1555 bytes .../L1_Sleigh_ride_128x64/frame_6.png | Bin 0 -> 1521 bytes .../L1_Sleigh_ride_128x64/frame_7.png | Bin 0 -> 1642 bytes .../L1_Sleigh_ride_128x64/frame_8.png | Bin 0 -> 1694 bytes .../L1_Sleigh_ride_128x64/frame_9.png | Bin 0 -> 1605 bytes .../external/L1_Sleigh_ride_128x64/meta.txt | 23 ++++++++++++++++++ assets/dolphin/external/manifest.txt | 7 ++++++ 39 files changed, 30 insertions(+) create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_0.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_1.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_10.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_11.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_12.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_13.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_14.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_15.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_16.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_17.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_18.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_19.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_2.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_20.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_21.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_22.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_23.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_24.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_25.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_26.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_27.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_28.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_29.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_3.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_30.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_31.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_32.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_33.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_34.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_35.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_36.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_4.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_5.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_6.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_7.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_8.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_9.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/meta.txt diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_0.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_0.png new file mode 100644 index 0000000000000000000000000000000000000000..340ceb47f99ee859720fa8cd89aa51f234799bca GIT binary patch literal 1656 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_10.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_10.png new file mode 100644 index 0000000000000000000000000000000000000000..890ea5d70da8bab182cd7d25c234a2e2a1e9d610 GIT binary patch literal 1494 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_13.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_13.png new file mode 100644 index 0000000000000000000000000000000000000000..6eda42b5d814555b5e795572face69b78fcfb0fd GIT binary patch literal 1585 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_15.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_15.png new file mode 100644 index 0000000000000000000000000000000000000000..554a177a93d8203134179860bc35ac3899407026 GIT binary patch literal 1771 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 b4o3I~m)4F_96F#z00000NkvXXu0mjf;*=8M literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_17.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_17.png new file mode 100644 index 0000000000000000000000000000000000000000..971e8f55b5c36b7f5e24c68d4adcd62512986417 GIT binary patch literal 1503 zcmV<51t9u~P)jd3cN7lpBDK~TTc%nL2xyv)oMky*1pDDh#Ld! zR{rk!BY4vG;xfQb!#6O@Nq83~QyCz`2v3TLD)bnChaUehz;#`*#NiD*`;#!jHh&m^ z4rG;CPu+dAd@@7f+IrV`wbojXQ2~4=(8l~SNG*mLAR76+%s34d@W~Om10w;I$^oqU ztbxBQa0XI2fK{*U^W4y1!=Hr<02;vGsO6FFJ$Fxjs589F8NLEP9|l>G?mg2Ik$pZ1 zt7JPN7rNyD0KbCGW`dKQ;K`r?{Nfc@Im!g1r-f2C2O@&zC9Y z;bm3QD9oG=vZo2@HfzSc0j`J6_vtJX{xlHao+C*0GfxvEOt9*1;@wUVCIfKr905R% zY_DE>rX^zJGYBd-A5VII^3hhq1?)?qsm{)+A@i3H}G--ZN45%iSV5a0B|?3wl`;@hAO^i(HWT| z^&{(AYU~{h5TV3~@o9dL%>}Xy;I;E|3@)Rzhg03`EdRNhpM+?{^T^`w@cm3B(K#jK z@n@}nWP5rX1MtOD1<-n}AGXInQrp=|LK%1&HLA4c6O7;5f*gQCiVXfgLaMjMrTsFI zEy<$N#{mYVz=M>0S(!nO7u8$_Id_KO&ux0m`$bD!W0 z+ZiBA%cEmUiqDJ@VUFFApDELF&vbyue%7-MSp73j5SeFpq|YvweHfJ80{A!@ysaUM z9{N17)bwC7Ky)wUii-4+q9!s=6jsE0b7^bYZN#3kSN;pw4?{c()jZ8o@(h~JjF*w& z9hm@S)z)J5zg+}zkBZcLeIkk5ccI4KoVU_MTph_T}DB6~o(RoZ=Kl;5w4t_c-f6I$#!buo3ha zpDSZFs1snh(&*9}dm{c;wgT-@C*TfY)zLP5QPy0Mc6?k3BTJ9&u|0cv<+xtC-2o&e zWJS^p9dV{g_UbiuMApjr(+m)aI?aF)29U;z&gIE9I#*_#462!98Ut_x;$#`cWF5e2 z%NNlrmp4x@X6GiA#&1oy2e8oE{Y#nI{!-uT(tiNL+i1zqh+LIGS$-nyivQ$2Qw}A5 z*&M*y*QIBxFI@;#npLm0iM;br%>ddQ05wS`j#HHghQ1ZdDVGao)obI8)bsgAQDGnp ze5=BDEB1J=41X=03ZzBeifGbgLpiuy(Z00(3P<2HjwPr0*=6c?0owkH0`CBilxPwr zIcr5y8G*$J{CRdc<@*3!fisw4bj(Qq*^VI1vnukt7$CC{X<>*R+e8m`F~UkGD06@a zrP8*v{&cL8Q_2yhmz7(9XIO2|>k(mO3)q#cX0akC_zyu3=q+V8lFR@A002ovPDHLk FV1g}Xz1si) literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_18.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_18.png new file mode 100644 index 0000000000000000000000000000000000000000..4026dc37364f9ff38c20c0ece6b94777fa4c8810 GIT binary patch literal 1663 zcmV-_27vjAP)Fbt%F{{Jtl4+Xaj!(*t+$7X=QYg-a29wSO= zdJs`-tr6om4*KHv;3b_;MATX%{p^l-!W?$s#Toy!%zxT^dVnOs(UMlX4cS`zI&ULB z7~pK1s8$|YR!U)^E zF@PK>x>h6g?5*XK8H(rDJIAB7)_R;ulrw=h=9fX{VweHY%-7dS&`1TJoRMc>B%w+* zfcM^dj*Rui`6$%@-h0c@+*UjK>-cNoAfiU}PRrV<(S4NZcXjq%_V6?CM`kMjUAcO= z^Oka#PFD!Dj~al8-0ZgQWqYq3q{syW;i-6A%B`bd=~#pbxGneX*&SFx^u>CF-vyvs zH|?wn^=ECrfv^^@=%qZtL(T)x%;!GdmQsFZf@Mm5fBTsP{B0mnp0!9;duIlWRXd7Z z1?d$EI~1)o`cv&dl-CFz&;z3tneWNd3^pqJF3x9^7~rFGVHc1+iwuXfiTYJ>Hz)R@ zwHDU^$N>7p4>ca@e12c~F2SoU(EHy|7w{a10zeL?w}jH5*-2tmY5lj=M(d#4SMQ&S zDFMLHfq-N-I;I}i+I{FY(RUl34o_wRlNm>9fDG2LvBwe`l@rJ6F?G*8?hfnFj%YFi zL_&!^mx7GOWu{wgkrT>l=11oocY|ie5(A(obqG8ruf>j6Tk8bbP0g?03-9Yb^j;Kao)bconzR&VpQ{dklfFfozLp{zz?*)dZfr9*kx;ZE z7@x;G&3Hi#;MF9IUC!4w*E%gd36z)}XOd#kjGfIG)tUndmYvQgqW`}Q^bFjKY@M71 zgju}I)4FAuKa)|k%~v~L>i;&Ef9?FqA_n!^tPX(cm^+t^UZ+`@k@@nOY#S~CmQviA zKat`Hdio#7`%y3osdF`BcLV{;@oa=FZ+eW(PA~2#KiXKCQG0`+*ZHhlFM2X+HzTl~ z@+$S0^RR5`CCNunTl{aWPu?K9kER)~y_SPGG>hhpM!oSWPKo;UjytkkAkQQFb#DVJ zPK_e58U&s!6pge$tg)mXa_!=_DD6i&@pw{BM!#n}cZTzcDE?uHN8Oo0EdxhmWjc8BP=-NumE*~A z?{G!zU?Kb`VHq7~5ob}R1QHNd8{`=>hYT|KuHfXz`KYZlNFy zHA~6S=wXrGK4)aQoM&cdOLyI`YYE64X;D8@sct)xvAyGX0g3eAxhzDwS&5*7WoZo{ zm68V0?{)y0Xv8`ZqgYrG1X4aq`+8219o(9rX9@6tP!PjgE)W@BaTB3e9-@zw(L?7_ zzY+Oz9BX;7?w9>Iw3-XBERPs2P!jB0`R?<|Hn0*1S-TNA6^E{L{*0iv#^W&~=cCDx+Op>y0G;@#f$l=* zs{+9*2t8RI8+Z)aTJgV%jf6+-dJSatzapKF&NCaYXI!)n_zPhwl15tgi)R1;002ov JPDHLkV1gE38lnII literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_19.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_19.png new file mode 100644 index 0000000000000000000000000000000000000000..b8cfa50ce63ac29e44091e6a3257d7fa093d7e81 GIT binary patch literal 1661 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 JPDHLkV1f!6*p~nR literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_21.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_21.png new file mode 100644 index 0000000000000000000000000000000000000000..d0e44937c1dc542699af72070b551bd9e9017462 GIT binary patch literal 1542 zcmV+h2Ko7kP)bG@H6lRj=2)NJ9B%jhyW{`3I8MgXhfa5rPjf1f{ZT=my z;jXB#%?AU}g{BU!9f)H(ndQYAgn9t$KJECA;|Mts$4BSAsN(>D0q{xPJacF7)x{l*!e!6K z?!aB1;XCj}vlg#B(`WDL6=4|=Gd*%nu(Olj<@_r=o&4$n0Q4FjWdiBGHs`No+!a6* z_?zDV_zZ-urvqG93J5COgEv@ep!PGr3y?Z{ZJfpoJTPYSX5c~kqJ}T)ChzREtJNcD z;6b7tD)GMzV@T`q{ry$AzYPSK)3wF~GlrN!V~z+5?0V9;(-XuP!)5?!0&cK_3A~1? zo)1`M##`pRxQp`~<#{OZ1*jxh#&SL&+wseyuLoc9Z*s9us>?<4J< zXgrG_-&yAXQCRb>GX`m%<$x9S-y`=)5CfnK+=$#^8tY^pJ3YtJl0Ke$+L*gSvip(% zpwNsbJq37G$$mG%$9fp=$<7C0CxKn4&~v7-)DD_7c@Y_W&Yj@u$ySwsYL_0FYn?Vm zVKLGjg1~b;|6x0C07M(a*@5Gnyyos~YX zbn6jlOaqYrk!n_MSDC0}xR?F`0t{eA(aH5ulkI6TD_1JFqY@n?9lNPpb7Ib zK*eYg$9v^WjTYFa=V_+f*^wY9_W;$b&&=n20sr&Ji)8lfzV#kJuHkq+)A;~R2SF>c zN*fcAHkIJ6~Q)C@)hFbHo>vjhN|D5YZkz#Cux&&y{vNJfiEN|JcYG|ZZpY3dKFj#2&MUZBo)yS3> z)UO&0SG{`C_I0piNHjElewDI;K*pjvUcq_Uo*?RcfF=g8>Wx;LvzfaJT;D4=HR61_ z2ZSdeS{Q)SDZeKL$61UpGs@i1mclJ_uYJDuvq_c61f6f{fzFMujRm7;<*$5(3_A1~=7$(`X0MzHkMSd{MB zW3T1^8AO!F>LA^@`TRzKif zOS(_dz5S@pne}JR=ek(-I6VLmQZtxs+GgxRv2y#F@{ph14Vke#N4usjBJV8R|W%Ry$03c>`-nor4 z8G*mgI6o2$w+R5mj!vG-+Q|sshLFznIA8s|D*$#G>kM42N3e8?K0UQFz>@=E7c@qo s@MO8wEaGRUd>i<}2Df3xmfk+#A2tH&C4KmzjQ{`u07*qoM6N<$f}r2zivR!s literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_22.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_22.png new file mode 100644 index 0000000000000000000000000000000000000000..fd800b8b936fe6d4b2dad012c12f0ddb54903236 GIT binary patch literal 1736 zcmV;(1~>VMP)~5 z-n&O@?L5!3bsWdh{_%^xFHhc+7j=GXzl-MY?uaL>VFg}2x}O&1Pg_q0ND>^UBei-X z&-2{Ed3;-TuxSRq7~txKUOhj8CzThM0saJU;FwB`GQciO=0RnE2qQcxBU+Q!6J2A* z`B&`Y2Ll|(fi(_^Y^c^b6Pmp{*+?AFo>+m7=* z&!|)X20KxQ>f9x>?q+Yt2m`!n=4YRpQ6Bo(yOoIDFtRtH9)N$B9fft6WeUyg#kgb>mUm@3vMseTP%UyEXaOo}A`U+DGN6Or(*dK~v!fEvWd~xm}fW zuLiaM#Xt0q9uxF;98^wY4aE8V8~wZfDT3MjqGzanx^c);9c$QP7 z3`fS?z5chx7=VM!LMu7QanE|c>SVzCso8V60l+s?yB6)=rKe{EpHF%(jq~(posZZV zwPiPSo6S=I736={WVOo^4(sxV-XmzYfpiXOPTFHgj<=kT-g(>&(rFDU0b0%WpIy=Mw(js?kglMy8fy(e;uvo$?8Ptv}GW}3A(;)^xjEH6%f(*k%no!SafS207=UJ0Btx?O0pI-SrqQ_(8zh;krcK&hG=Zcb)J`AM7$BPo zbX;^JB?^sZc(ggQG8fV3*~mAU>gU1cl~Hb=v`@p&lW3I;if#z<_lQ2}y%cmv(4cEU zWW7dEbD9Js0W{_+?ExYTAoU7f{q?BMn@h{yE>qF7NNe%3UD9D~I+sK2 z17(9CYfI%5`gO3D*)$9I`&H=nQS`-C*+KWf+6U>KRR*&5QI;GH0{9<^LYgAFcxqrZ zq$ZFe!y?f!(i*jHJC#^ zKozPQI&Y2)OYxtrzCx1c@6g83vfUal%_n_dathF(QByap%dDD{%5)pFfI*qXTZfmS zmTnDHwyF^<4*-o?DJmKv35YJOe~8+l$@8^YjUU1WlFe2SXmd_?@uWhrZa$6pQv$`{47R5?4&ky8OshN5VXzKn&W75D3uewpWNQ* zrvP;0EJ~z$v~SVwnIbdRqike6vi1zKSzW*O{wpY}0)QK(ISdweR$u)$B+nN9$-7(H>!z z^R+pnK`{Ly$m@I~85U{OJY89MHfX`gzl{c}bzIzv0VKm)i(R0gx<-$f8K6B)rSl~{ z8RwUt0y4(8Rzv4mMyMH%8&{-pBSMc^>rtd7cLce=m)}vAmB2oIeX`{?R#q8(yeHGNfjFM8wd%Z)DC6%$)n1 z@FJsd<5#- z+F8LB+3sMLj~YNa&ShYwg%04+Gt>VGs5K3^E|(iwjSvO;Tj75ts__tTspi&0j6 zS&{EKL)1CAlkra$+BlT+DfzbpQt_U_J7fILv>^41wo7TNGUvBoWS=ioqIJac7L_i7 zqE3TFV-((n&>w8PkwUclb?xq7Le?WQ{r?Qxi}!Kl&(_|}VD!9iIi1!wgK91Cn`(%E zDZq;6%r|_;;VY1*nM{gUg;mDifz3V`Ad?2XcFhU2c`_%8jN*2V=DXzLI(cKmar~^f zM~@l=>-^r8hNSkLo!}096zbM@0lahcs3jR#JFZnOouqWsHTQ4C+iHyDe^hex77pi@ zV5OqSl{J3jWwPdq>xZyct=af)@%Lsj|7kqJ29c9al5QQfY7A|KEp`^ReR>PQCriK^V>nX6KjCT#Spb;@-ose-Ue1q5ME!L z*E=4LTvF{03>=jK5T0R1&-2#Y5;B#&G?vziq`t2<{&^m+=-{k^GlC%tROX0|(bz{q z<(6_MNOvG52JqUjPc%P|L|$ioqOGglckB$)*uvAi3CtjS*;73}T35?_t&Af`bpXA~ zI`tjM8r@_7nh;Sb2({WEVjS&cU^!W)uBg-=+W0##WOQBwP|lT#BW=;@<}-?>{LJOU z@zTjcYDZYd&ge6{pAqm}PR|bGn>Bz}X`{nLPX{xUW}QN`Jl-LPw!aQ`Xf?9M0MerT zZ;K7Q2wMeB4$a7z3^1a!jN!ZFiLj$p0HdtSj4d&M zlt)o$jb0-g^iUa(bpd{&(3rqu2rcrZ|7jiVd8hGz)Bs$;D7+SF$QoL0U_~3Qr6H5Q zBax@c8>Mrr29U<%UT-)4dD=e+$wJz!+_zV3NKRma|Q$8o@cIG zVgN0NMSOX9m!zEGAe<&s;X# zyT=@D3_!r#rR6b8!JW_|rNRKXmk}oL+GnMa$b0P>`ZkbH&NnJ4ypkqHpgCj3Iafyb zOy;2dw=qBklEZY)?Xi%xt|r?wC2#y}`#pfBTvS>&s&h+}c^uC-D(b}R-}-|PRHUWo z9e|3WcXu1H@}xaT&M=(;q$t-YdS+@E0m{3BQ8UKp`phsu=trRu<(;Q|A#+_nk8oE1 z+3->lmv;x_MvTw*M$agtwHJX}glis~WdzTF-QzKR?PPRoAJO?*7=VK!$ck{@9_il& z9wAZe!U!n0^uCh5rX2$b5*2Cf(k8KQHF|NrEb~ZnuB7)YLx!KT9e@jV9R9C#rOgw( zvC;k+xgz7bjomr#?F?Wk&T1z}QJcjIN1(A4V^`K4S^pLW&;*y3MXov+r|M1ope4@ zSazaXUr2M%JZ**GZVo``q;s!thk+s(DHy-z6p+PAI$!Mw-kxR|{Sgd+@aVjA8+S4S z{y+Qq7zZG%sN`|2m5i{$=u;SAm9p-@&Naez4Dij4unJ2VVO9$GbA)lMV1)kwS4dPV Tw_x!v00000NkvXXu0mjf>uC`M literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_24.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_24.png new file mode 100644 index 0000000000000000000000000000000000000000..05c5e969d7c10cb70d949d7d28c0fdf9b2c51c1e GIT binary patch literal 1628 zcmV-i2BZ0jP)e#l8e)t}|xbcaIT5BZB?uaMoumZ0h@lT8Vr|DBcBnYbg~R+*)g`VpJlX31nZg{~v?Q#V`XzBVS&T(`aQ}z$ZuK85jwu zR1RR(cOk6$9}8R$sT{zn_Z+8R``-`Oz(z!kh<4GLxwp#ixU;y+9)1S?UItl_?%ktu zUKT;$DO@2xSY`Z6=qhsnqW^-8FhR%NqW>Gf1NAK{h&~;GM|Sq=A(B7Hwm*#HgQcx^ z0ntd-n89O+ECv3YU_RbfC1-?L3=mm1j)Jy)eY0bQ-v$!Z_WEAVP+^0kUbM_`ggfmZ zT5CB6=orZnWIx%%p-R+dx47SevIBHP4}p*aL@3xhYw(O!enIjjPS9GTSLH!@n#WK0 z&}m=A_-LFibj9%U8SDNE`}3pGbu^Pegvk4XK7ePaXryKH1bH}xo<;ZH68Rh{2k@em z=yPIcXW6J9&naYNTWkJVyOUKwPW~p!t>vhF=ExTr1PL02H@3I*MDv%sgX{4zC&a!P zWbM&M4|FW2fr1&6E+e_(fDz`%o@`zXk=@vC7Z5$~_v*CvT(JJXte559xYd(IO+Kx! zJ2*k)aE|M?%K@w-+$bW+AIc9Z)X%S@he{(SoM$2+2iLi2q(M%tWBl8JMUQ5mqSo1TAs*$sd_~HZdKTD{L0=#ct=RvYgFGEy}l=Tc< zjeNvFNRA3dBiv&|I1kOJjYd#pN$}cQGIUruN7UmZ<42swVj7SAIF2xO#tB}YfHP5v zjtDF9zCGj1|09v@)m!&mmRW20Oa?HMz{+}tsQYNhXtNZqPM&2vuU}r@Wa30W3; z8;I6otxc5ry#6xJddq}tUzx;g4xrVK$Wf8oTB?YG=KxwXXda$%f{O7o2A9uNBG+qI zVHEDTD~ylkqqP?Mi5RrbO3%`OoCO)i6PUM+7(L=xE7#x(U03ClbA$r3w+($FNIHQW zKz6FJf%ObmHUn+qGR_oQVm{WSrm%SFW>V z11kXK{Dwagh6b}HziJrEvEd(MW&fdaFuHWX$R=a6V%|kROr#OI|IDYR5dR0h$u3&6L z_sGzhpeubf%tCAX{ke38Rr$dTbbW}EqUnt2}eC;Q?snPD{p z>;wRjuSJWcAVS38M>E1~Wah?W)on|>4n7)ESnWOoYf`K>vrt(cqRR)WFUd%>|EbtB z5%F47Z&AFq_O8--#*XI*;}a47W{}n4s_k3U5jbYFJrW}cqt$LmCbR@ZjC=)0SV`wI z5!ET;B>cIG{-IwAy_zFjC89@xj4kgg#?LrI_(P#xsF5}^@D$^7pu?Qm#-DN!lyL;F z-4ig|=`(A7G#PetsmeAms`x5aI2%Vff|d|!V`*%l?T}h)V;>2NwtKoG%+^!(SLrvJ a2mAw+lNpPBDxqfp0000(^U_4#5#o7iWbZG?H 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 RwzmKP002ovPDHLkV1h+$`q%&f literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_29.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_29.png new file mode 100644 index 0000000000000000000000000000000000000000..c6caf88d94b0d9536098767bffc4c66c4b9dd2a3 GIT binary patch literal 812 zcmV+{1JnG8P)?aEn`=V06RJ#06u%2);$!TqlGheHXR&I21tN1jb6=91hC-v zo+yBAPcvtl0$LG4iBuHWEC&0GB!1olSRogEyf)evfZhoDlLcrsVian7HICmc3$Vfr zXK9S2M;Z<_8Zg4lnW}9J4*@%Zi+@;UvBY>WKxa2IVJo>49hn{pIocVJq{DNH~d&cdy39L27_9Y z@YYT&sUlykKvvz0APp~AC>W{7Y*A{fDFBHxZf6n9F3HQsPF)0u>?nd6v%W++c2xmL zoc+7PHP&h)0Ki^RfFr#IP%Z%HJ&yv=68<&xQ8FF@jN<|@jtjszj{@+u)dt;%L0Z99 zHd$j^0n`ps{CN4xmI7#{u-1>4Q?}a#Vw}3uo5WE>M}V4hddi)8uvTM`BTfLlWlxk9 z#MI8m3YQZx0>Ey}BvlY&z7xUS*PQ0h#4YIrQ3zfg%HKFsiV)ZZpmqJ#D%u>@EP&hi zQ7HS(or$msK&$fs=)*Rr2)26<;NV~pfacjh3$7^O5x_Vu06}m>0T+OAmJ7hwe_410 zFpdjA7_0XIE&$`W01OjCZ~<7y1z?nzjky2}lBVM9^#IN~N<}DQO2V8rBUMDL+DR9H qB5J?()q3U$7l1-2S8d+}8s`u2Q7b?iK{5mY0000%G@RH6aA}XbjzIR33VGc9!;*7sr=HG2TEr=w+Rw}J#8?u!0IBz1p z7+|&WSC1dTotBp>1MCei;FvSvDoh3$VB5BjkB2*D#3-~le}x@C7+~ABJ#w`&^4=99 z`ND!4&8O5#wQi^I#*QLiFjdTK1ZP zfa9%pXMdacr2yTFKq4Z|1d-Z+Xl&v5OE^QIy;%{WHkw9z22u{{_v=_?1L@nhen9j| z=bnGK6^H!HG?WWn>1 z>gjy#%UFT5xA3V7t8Z&4|GeO-ct!S+$)) z>vskjCa{(VeYR1nF|6-*V*+c*4gV(40%C&Nz_OiJpjkk&&^C6Ye+_qF0(8*#_Burn ztpj9xuU)jiY->3ku?{-p@Pg>7g<)hKX$HtFH2~^>qJ1u04NJ8hVOJ+wie2rY!f9y!BQpQX z`CH;NFv0*{AM{*Lv=u13MrEuVV`VUCM4WH258pfVEx?o{iq90h*j1 zK;N?ruN9jOIj7l~yn;D+u827#K7JMKIovaB>vM?_)L(-P1itpr^J-^DgTccab+9u0 zXlMs9B6?(kO)~=j`wUK->3kxw6Uc@@owI_&3aDkrf;X;3eYHMF4)v@xo zBLZL-W*8PJ-y+L;F44Mv&kBSQ?8S0MIG<>60PI4IV$h=h-ar^t7@nCCBJI0$IwAme z;RETsR#=Xh#u*qP8ziz{i{6I@z&^-8IzP(!))*@oL4R*?ekM4k1b|e8=vX=*i4}|x zEeK`bnzOwC=>9KatVtlqnt;F0$mmvB`py_9;{2#i=yiL@K7co1rofXpi}P0kF@l8m x%B{b9tT2oLv|<@Xc<>2}5#;>{+;v|B#vk{1sswN{smTBU002ovPDHLkV1h3f3a9`8 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_30.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_30.png new file mode 100644 index 0000000000000000000000000000000000000000..8710e1a0a96e97627d7f0938515217a35defa493 GIT binary patch literal 536 zcmeAS@N?(olHy`uVBq!ia0vp^4M6O`!3HERU8}DLQk(@Ik;M!Q+`=Ht$S`Y;1Oo%( zDNh&2kcv5PXWi~QtiZ#Xc=iAP)c3-nBBkcM7RL(G?yZ)pws;bD_Ka6j*4oY9H)hSh z$r^L1k$G|)3fnD0Scemq{ls_4=-(5G6>5ajkV|uSO zR9|r1$o$d!KksJV{qAxZ?YO@+F%7cq|2rp|%GFEP_I`XJxZ(GQ+qZ14-?(?F_Va!@ z8?^)P4(Ho^J@H*y(8lAxZG%<1;GU%wE(}xe-tVaR`1xPM;h*()SOcW%OzsI4uviFc z?p@BZj%ma1#~&s&utZ4kbKYS|_WHoUJvTVg<}8D!Pglo`ylFvNzjmy>`|2M*fb&SbRsVP^tleqWl& z5Vv&u=6$AiuS;`<8r*BEUtiz!Y}?Kw*BL9W#3%og+TQbUHG|AQ+igiJ<~o-$@cozk z#FOQ`g6qITd;eUHcIJW~=gUl5<}1&?^YFW}(z1>pKc-)M#2`~Q-_ErlgL#F&Wz$-L zE~XDJ-rv8cd61W3bKiqx2D!hFXSzRNYxr%>(EqRSsVAgBp9kDNL6Br*LY(r+3NZkcv5P=bX+vY{0`Z-Sz+f=~tZ3qOI3bHq_YK zGJe((W9VaTNM?Kx!C*luGeSja-n>J)Ae{+~9RXMGp9e7?G@QDsuPV0S)v`DHJ+&RO z-7X_r&4Qg+o&y}`a@-h_B>^2Rh9|=oT}B26!PR2 zN4hnf3v0P*xaEQM%kK>CC5qqH)t*&f`*-dprk=S8VxQi4Em^}bckhMm--R{x4+I{) zZ0%Ud7o literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_33.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_33.png new file mode 100644 index 0000000000000000000000000000000000000000..94b79d2ed9aac30fb2e73993906569b17f80079f GIT binary patch literal 897 zcmV-{1AhF8P)cfKZZMOCo}i?TbIa*qCpZXK-ys{c{8fAxT&PwBKpa{;3xIuUfjSa9;#ss3kyp#qCG0HA!nr2mYzGx=lVn za0kHe;j87dIf8_cB&-AAnM#SEcR%-n5`{5;?@B)L6s!aVz(gaFB&c#KoZo^4E^3`RRd^5;4rKt?gXHn zNmPM|*XA8H0yUNhxDtT0>TdMz#nri$Bj9n~unTY_0O?H91idE&%bD*ad_QyiTftNy zM^2zE4tqkFae~N&-zUSD7JgYCZeYn0Ag$Khy;R~QZF|qvw&^W5t*=`PK!8m(0GC*c^`5KtrR_&s z%GTd)H9)8$P}-ilheNxiOb4(YL}x3=F>;VqL_hh%xDY@?o6M6oe2WXX`ZO?7b=|g+ zQO4WDl>;=SHGr3)b0Y9E#vaFyf+>0*z~FmMBUuJKj{A#C0RZ&>8%S@qNvzl+_}U_n z+TmD@#R9hiXy8E292(68-|{%xvcFke)&O5dpY2=pDY<`Dm`>L+?i~QY{_sUQ3wO-Y z)2E6d1kkV!5+{V6Njd2N?w0_7eaX1(oL_>ggP_yAmVAH|z<=Cux~Cz80CvyFmqhRn X`cZE4+77BK00000NkvXXu0mjf+t!%6 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_34.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_34.png new file mode 100644 index 0000000000000000000000000000000000000000..ce0e210f4f2202902eb5e63bb6ea0de06f4d7e92 GIT binary patch literal 1490 zcmV;@1ugoCP)(^Xn1&= zKJWX!<3sQ~&(Znj)@0}XgsuH|djVcVo$v{p-vR9j@H|iI1fEulu4}LWCqUL=Rd%vi z6=X+Ex|84sZIH&z$mVt5tcmx1Lu-AWr)@}r6JTODf$9v;Gf|RsB|-Zf;08EHMV-fy zq~l@uBfw#FBf${@yzhHe{vva0_oAMm6A6wgz%3$pH{7$fZzI7G0??AKV={CD9Y}CA z1ax>{2`eeBbs`CTyWep<1au^U=1^(9XcfCfglknmmoPw2{#vik@26uD(?M zYGNt^`p`U;9|)lvDQ&D3SF4pGy9FH-Y$E#%$EyJE^@?O`stW2=Zzp8Q*y3G)=jF?; zqYy86PT0)a_y`5?fHHY4!Lkpjm3nA&3JV^T5g@zC(#e-Ha?4iLDXp+|V-0N;K>9jH zESFgAa-@__tfWNM$Pj%8EeOEX+}6pLHfX({I!&wfEu45woWbtgB?Q=sv6cdGO29dN zZR2v~sJ2Gh0;cnR6Iv+1gva?(S5Er6+Kz^S2r9Dw6UqW_1xHVRq&qn8r}uJB*P2eA z)1O`YNu@v#1gZcT=g+U4#B*4iIg&z1*}ru9wS2+~1Jy5vs#V}>2w0sJ`9Q`xetlVD zy9`I?)5eSD%z@?^R$hnPG55b8YU2+s(^Bihdk&+GfjZXp1PFHOG%j3 z2bkB;Yn0ZVIn@#8UsV8l(yFl9mz=28|B~~o$~10%Z& z=WAr}K9d!Wq{@JEvZZQsT-AQ3KM_{8F!hu1kA9Yn|HIc8#D6 zqy)|zVl3x-a}_PKkB;HiBbA`sSjnb!PX=3`3EWYENOBgDKTl&uQiG@}WOEa3tcHrj z_V~xbwZ(fRQnG0^bgZ$KUF~E_G20?R6r8f`Vv7LL2eM)!Vq;dj*6H`Iy@CYN`aEdS z2iOIz-`5ko3y(d^`JPOiI#bK&5ehKpL1LQ#G|}Py%xjiRuZ}~2Bf^doJZt0fk6jX= zl#&DdWe}IaQxc$AY=IKNl5)>I(zP*ohgpfH_IFKPb^%KQBP9)osz%2OPmbWJ8a#1a z>Qh*@HPEoxh5(4zHtmd&>(E8!=H`j6-J=jz;^!?prNs3k%RmW~(O&0Bm4!FE_l(OB zTyuW(86aA!6-1Ew2=vu_Mk{wb^{&UZWN#PQ6Y3eqm8yvK zbLC)oU(p2M8X+944x0D=F3^bJsoS%p*d;+pA3$n&>_{LYhE~Pl)h4HUl{vtiQ)?Ef zV=EMZ`w#0L-;RFHJd&a1hb0j(up{i>NCBPW*xPIb9@8UKQDqH3d0??9Xj2IDC sbaVa zMNY9&gk-#G{ zYzvVABnR^vt78t{aej&ez1IgCp}eY&Mjk36sjSS4p(`u-tBk+H?&NEI;JbmS60G_G zIl&cg1b2?)yXHpq^8Y9pMQ7w}KU+9ThZvdNhSYv%C%7A5{6X|o$x^uG7-%1F$=2Fj z#V+3khZJBPs!(_fM)NQNqz})bk0p~Ib4!l%lNsV|HISUZGQM_=&`UTT}ALTHaYwrz;h7PMW^UJ1eM9#(y^lH z0F7a_{|PWtz=DSzyhp`|qR~qYDJ?{cJkR@rZYFpF=A3vJkclqOV7!l()1{pOy3fFp zx%(MH6>EKN2B4i5v@IO(x7yPpO_R@Sfhv~lt80Rtz;{Js1X2JO!k9xkE$kGGf70g6gb1@8f0INEj4`zJAh zE7FDS^I;dzy-;aCkG|(Da)!dK!jXi<5=!ozz17wvGiO4LyOEn0U?Uh64 zQMvM30_E5Ux*|Ny0F`K_i_mrgkujolt3_ml2`nR8I~BxS60V4J7XxHGm*;RQ96hIt z_Q>Y3X|!c`E}%DJ&B~T{VT(#yfr_Qas~W{kc=z75N7S zh(^EFKAQt6duX7nqNN-395IV0Vj@f+_0Krp=?w5!FR>G{OuDe-jZt%)NPphm8~L4) zC#9sT8Q@R^b{f`$t}#*0_4e6;juDg5^>mMOy90Ql)=~;;j~1!P_eM&j{h7{y^w7ot zROx3rf=01Xa5;!(EhoD>@}o{+IZK)WBt>?@%7a#4PkWT(t@a&E5S4ePGkDjN`aM7v z-1zJBoO)HiK$WaH3zz4S?^Sx``_AiHCx1@Y>ROM)0BbRpvUyv{AiNE71a1szoG3GS z7=b>kGQktBYpsXZ!4wizYi*I@k>NAQrX}udS_<01I33Kub(Ug)4&{60B4f}t{8?o@ zBrC!IJ6@^YBK=eb=v2H1C^EYpPf7#)`3^>n(m7wFwT>~sFM^PAJSm_G?#UVFi}c}X z0X^TrxZcR8Yl$$z$5Aj+NJhC4XybXuk{mYDUus*S*DL7WT9bL5^YecMSqk({zzd{& zkMTY8@G_TY9aJZGM|$Tx4zGiyMQe(!ID*AE5+n+bj7#N2`>noL89mBoAqL=`EoD_F zi6)#ur7x8+M&1#2k8n2wtVLOQg-@=c%&Gx99DvFi;~GXls02ZDe0mN2gNQlt{|!dp zd-HD=oyH%-{?VzY-&(VEUH#}a(qEESW`HF$62W{C=zG*AYQU~^lkFQFV+ j=ym|Kh+pJz1ta_c)_1ni5SD6$00000NkvXXu0mjfMD{rX literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_36.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_36.png new file mode 100644 index 0000000000000000000000000000000000000000..1aac855834c50765ba9674b304504ad9a2ed2d19 GIT binary patch literal 1538 zcmV+d2L1VoP)pZ zz;5I3Uf+W!9WPx5cn`dRW3Gg|Fqz5#$8ntgS$I-LR3YR19dA0nkJeh_*{unSMp>1dA8d>;z+W%(5#G$#HbUp$ zr9iq1Mr`>}0|0zxZie>xrX-m^?8gEBe+!^Y006EdK@?~N5#q-H*TDe*4dCpsUiOND z6vs>BuKqOfO#%8Y0s#Ox6L@L^fZD?Gn{b6d`@@X@^S&LsY(Rbc$2$Pd zbZ%|3_ri$Y%WN_`_Xiu)rthZ!DQm4v9(h}6jgc7R7XxTrWKudILmqLiUZL*U0G)BsX*72wptv5z-et?qu#T&EPax zaE|rG5ywicwT{(qWqwrtQ_x!TeGA}y8WUJg58f9_+WOXbt`zMbapsem0R8t-18@WK ztmWSfD5wLetlpbKx(1i!VFKxCf$y_dV@TIeV*+lc69Y(Z0eepbR!tx+Org~nUM09J z?KQS%eh*Jz0?j@jKL`Pc(KQP9qIb+>*Oi>^2^Q_XCz!5|o<45|hz5XEFpi9k$m8=o zPUqvZxI+-QoKD(TU*s84BhY$4dhF8*Mz3sMcUb$5)C5v{)tW#&+r6#C003)_0j>mh z(x|~_7IeGWAmwb00i-j|V=!Ln_rNor_hCzAMhz`JRalvyZBgzDXiqyOEs$RXjb`2m zO-kACWdu)QC>4IAunNghlsegB0L)@;_|>2r;~8tQjFstDHDA$M>t$`gZ85!VY`3FX zGn!>mR&fLM_S$Plb@QfF&5fMqmFu4oooc2#98U1Mi3aEz`K{uU5{r3^K0YyY18Bg$Lr@?afx#%e}DJ!nhym63cwZnO+$F@Q7<;2DfS_tUdWx0Q~7p0OmOWQT3Ik-%^%WUCiYwO$mt%lTAW&S* o`8%+b5oR3$tFq?FSiuPY0nQUmgswmI@Bjb+07*qoM6N<$f>dYDyZ`_I literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_4.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_4.png new file mode 100644 index 0000000000000000000000000000000000000000..ef5e2fc53b5ac0d5110ccf4c1b96219f6c65516c GIT binary patch literal 1668 zcmV-~27CF5P)e^kD*EFS~Lv9sEdDqpe=U;_ir zZSQKW_1=3MxUQ>xkw@?SQG579o!{EO1@L=M#3!ub4*ZO0eNxmS*s-3|vL8u;Uj{gZ z3BH+I2P7EZ7+`k<@79mtlY;c>;A)=Wn4K8gIQHqZa-czFfCwXeQbtrEb>`jQ(Kdxoy(DAe;R}A zUf(nWpqbA{>FB5#M-BY;47HdExKIyZeeQkssd)ODLp^}?xrXta_wS26xLRwywbw}I z+3a0M?zsjXkU3~g_11R6T0H!|3br!cyB^>_N1Q+UgcF#d>fMJo=LF_^>j7GOJ=12G zU?B2N;c3}~I`1^QTYF`=AN~RD+Otd$*&Hp?hYkD$aRMUWpEx1QIbN9B`dt9(REZg) z&!GBt2ZPn;Jn`Ck~>`s0`H!+ zNi*-;t(R62-PB&x))ULXz>h!WKX{MOn*W}_LnITk{eYE)k=}*&M*P0^PF{KwdJ^F8 z1Bed?YrS=fh-^^RIbB1fu|qQaROh!AWB?A25eAUVMs*?1*G?7awdGK<k<~0ljyu z2kk%Y}RWx3bEUPEc3sr~NuSf?jrrC)11Z?F zo+Qfzv(7&uc%H%n0M>m7BhgA+}ogeqD zcM04k=^P%-d^%Pi;qGPuZC@Y;Ksx*kBu1e3r3D~%X>w=mMBZUY_r1Vf3Deoa=$3#L zfsm1DJ!6#_boBIiPR*>P3Nz~t|G$9Bv$Z7)>AQexy&T2!JW4lMy-w z4u#bCNMn{kG&m|YgX-1nuP856Uu6D@G5jKkH+9}z3(+7MjTzS=WuZs+(#V@#K1Z@A z!Ytfw3s3`BFs9!GA`9Rf8I>_0Ii7wdWwMv)+DL8JbXn`AbEW4k+~xFC50FK)re@?1 zdC`W?v)Sq;Bw3a|OJ>ymBN!ly>UfhjcwF-lVE`*wc!`i~L4s^m50e?;Ca?;Tx{1Pb z4Ao6-@XCay%d>^sB|Wn{^S3d;3V6N70G;DGzbZ(15NHANF$|F1zzj2Fby-1>k%#Kz zh%nNs@ht!>`a>2(Hy1@@uB0mq&-p9Lm82K#U6zcUtuWfecpE0wI#iyeAF1^cyn3wZ z@UEk_dOZM&*&q;)fe|X?bFIzP0)#I*TM8o3?BG93*V;YHzDy8kLE#0<6az>GQo-mV zwQ{f`!y}Ak&DR27S{pJCou3)$T9Er82GHO!0}=!kS;N@`k)35VZB0>DSP=x$7?m43 z1$eYbqfpuiYvVk4ay6SY`z=V!<;k}KgUaQfS2{m4ssX^WK>Jw=0%=36x}^ogOkcp| zWuDKl*X|(Tc24yG7BbGsI>M4=1yi)_c>1U;UV@NvU_B!xRz*+XjZ?o3)Tr`qP+JBl z^Hl1%F6@8#NWsY=B~jM-bgk12AW=RyRGWVnq$ZFeVoSz;lmXGKr=M43zILCF<{yZ# z7LN1-(k4_vlNr$^Ie=bgY_OVK?fyW<>7H%NdH_wSW|-8h%<4imU$arOlh3g*8-P?7 z>U65RqH$dEg%B^3DoTdM02Zvq4b@BKL~OI>dQPv9Cyh(K51>)1R|6|`TbN;?NV!1t zP-oLo3Wkb|zl8xfyk4QA_D1cgTDDc+?^sWy;giN&z7W#8{Y_%}Ia+_SsBTC#+oQb* zeLgx;8Ambz2ihY@bFxjACa)UNE?v9!o~v+v`6(a@@6;e6+PQl*yP6Rsn=G59AV38cS7A*m7&a@-dN}xTC6^tvK|H1$f>OvNiLFK6-!Y+3OKYDT)bCx;X z>j9#MRs+pqgwC}M9oWHqXmGA_I@|+1B@if1qJ9rvAm1^D|5E78oGebH^GbL<&^87@ z4Atg%^D;P%&Z~o7>w&g2Kz8A)Sm6ZcQ>+YxqZpuKL7$9OfiPsQ2!g+^GYZtf-cH{D O00005Az&DLY{*`% zwcdMg*LAhtduy$=>$(~Sz7PM$zv;WU4F}r)5BK2#WC4+sK#5Eb(YP&!_q_^MjPVV4 z069QthH!8m+?IQPC4MU73xYQV@OEHDKXnN1p5{zHjDaTz-V^|?9KlkGpwFxM|Aff> zCMUf*Gn+_$S34&~;tAxA!YT7*6=0NyQ&sq;$gz+i|Bm%}Q-G1=vx71HpAaRxqoOt+ zy8yswVzk;}HojSTs_OdSiZ{y{yw0&pbnjxv4A2xF#nVH6*1J4A18@J|Okx$4wRxQb zXj?qbV44xyJ6`9~4yk^~;|b(&i>saedx6_WzZp-S9 zK0LLDT59m@8nD!iP=o51L@BlXXNOT0;G?lYW_4?)9YSTdp7@^Eo(fL+3J7>a8sj34 zn`CzKBmPDJY_rJMj z&_(BJ3F$p(Zygp{AGAg*!3pTSfBroHJjlTNzIMb+9Z0GJB8ODjd401Iz&bzZ&5HL^ zMTj2xO7e>d#5P*T)7%HH|EvT!4ZhY}+67eA!0N}U_&FtzCS;B0Eh4QsoD5E3uj`6! z11s=uA+r%!ElBCO++t~V?dT-ZJn&UL2pVlnYrfM(4?O$)NMS0e5%;@d_iT7b$Jb>J zfDLS|zGxXjyt|Xg3FZ#uM@hd$0W`xUWKR~kF(SJLcrP-}YE($a*U(AetO;2Ad<)u+ zgUjKWa}RD;v1*q<#~Veyx(mRVxiyluX`{x`>Htq+3h)${+EzA-p(Ixn07vPl$ypPh z(H|wp+G5g3$M0$VMbj2%>niel@9`8c+X*TQD1poWcnmro*N!8{`W>k?R+1i40BA_% zaEC%D0vhc!pHpxV4_7Jjx!-u~skwY+4L}!#POJhck6P`bV^CPcd*ocJ0koQf_J8N~ zNsZGcJX)H)$f%7~z-gGh=wRR7gTwk=tol{PrLsJ$Tt z$U>8EDN?lv=7^G%ghs_I+#UH*CxNx`v`;%gCYm(jX}ijEjBM1?K2}Eh%B#x(Bq{e` zW@LInYXN}QIqoJYlP2&SOA6qLSgyS#@v9tzrW9}5fOb2O>bZi=QKbr{04X;&R&4^{?fk+mWGmYamBQ%s$>>( zHR)Cg^YpjuPr_av$B+%Fz}tQ{Xb#1Sb$5=Z>x1u0wZIk%fFY$6DX&>6i0glrdaP6g zjOg6AQveKzDlNkCb5HwGu$*U>B0zuC25gHK@mYIrQ2+`Y;T!>yBH(+tZDdN>NaNnm zM0R%E%J^^B9UK5)Nj-HG4j$?Dw2vaHOfw3?V-j(f&CZm|)2s4&;B>CGZ0EOlV zbRw0$wC9zAaz?>8y1%U*0KkzWElQ>@?m0$viL5areLz3M3eXcA0KgT4ocvcEp;{PP zV?v*IMEsEoKrvFzt-^9v9+&!FLHe-@KrvFzSCKD`xhmqnLIEgd$@z%f9UW%{=~EP7 z)na*V7#Y#49RbEVQUP{y1dNk}ZmA;7N&!1K!s%F{2!G|LaRgS1$SeQ=002ovPDHLk FV1oG?)rtTB literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_6.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_6.png new file mode 100644 index 0000000000000000000000000000000000000000..789eebdfaa25f10335d19e85b2162518cd947a34 GIT binary patch literal 1521 zcmVGwk@m0LO8}3Wpm+_BUaMZ9W)) z4rI02NIm;#`DBK|wN=-6j4?*VsQ^9`Xk*fTT;|6vc}z1vH1m0z2^z`iQvWAs>nhA7 zO{xa4o?BTxGu97lk*Wc#=h{Bco#{Cj=kY#2^xy!10q~i%JUhMX(!|0Tm2p?F#a+(u z9eDp3WM#T{OiUhB7jXl0*e21!0UM^wLGFxYrm4{5e#P6yf3M1;<@f|2WJ zF#(tVohTB&1_Jn*nynck%;DX$GMvlos2lEff-o6?gVzWEbEgN1DLh1-kIyrwlMXhx zlk)+R4DhFOFten75f-r+B4cO_w5XnUbK(qA48Y+rg4TNP5h9r#)s4>;d2jJ8b-@^8 zjsZBbHG(w`&ssWmWZ$>+$>{M^pdF_eKpK=X0?)YUdjsxd?l8@H{4}6d`Bv@mWG0}e zpSPVm89;)Rh0*r}8WTu$fL1p|b&AN}8InEQ89x@jBvb;yI@3$zdPer@22Zw=)y@utDW}sKKsw876Gr4DCHZ%4T!6Y^X+0pZ zjaKbfTby$T=w6O(hx5f6AetqaJv1_AlwvE}K(&8ld~N%fMXQr729PF>7%5}O$e2_y zO*Rv7niFO-pPvq->uQiK$?z-#oDC_f0QxdF;vi2(%Y5lN!f4x^9btg0p*uc#9fRNL z2k&dGmif{(UBlA7{4NaOWwe)FyYz_2ST%sO9q^zy-TAv2fbQ`fg;Iwe7O1Y-8bEu_ zbvV2IZ-s#)pvI|Wc!e3N^U?jh!{|6xBzPXh0JvCc*a{m|*dv;@IA^*MsSuDGZ<)1g|}Lsef0w!YLcSca}pt zw*@*M|L*o5ovRL@nq{*NqWvWnsIW-2Pq*`>akcxrJ?-aU%c0sciQ;DG%Z$aHh^Y4O zaJskN(`x_?R&8K8c_s+7u{;?~H_0g;X7~8naW2CE6d6k8R6bK!(z!M8xmGwK5>(lB zUY-Fc_}*M`@a)_ccvX$}x}9wmjN1K2FaU?e2o(nCEFd*q0pg4xp{n03BVF>xpfU76 zL8pew?!Ogy!GpS@Y^240w9WMItO+j303048P;i-S_E+~_ss9Sow$~ze2mpi@2o?)O zs9Tb^=E@XgbllFuu&o9Fi2ZBWl^_+1cr6)8g6C*Hm(;haZv`uy|G@xJ>grfb7c`^r z=g~q@lbzibJbia&e5eM9819OJ;AL@y&NVhcbgVZTY^$6et^uAJ2ox)+--ESXZE=3| z6!4@#=%VvZaMsz{Mgl-m)RXb#c3?G~cL)7t8KAoGtFXdqI$sqCmt%mA1w9#eWQ6|# XYhRhvxX$)N00000NkvXXu0mjf-Ye1b literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_7.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_7.png new file mode 100644 index 0000000000000000000000000000000000000000..cc4f5cbfff2c3c96ad0a63df6c9a20631c04b5ab GIT binary patch literal 1642 zcmV-w29^1VP)=Kfb^KBn|WR$3dY?YOH_4hAF8(nusn zDW%q03*k79^5Xa4!;N1`DYe!j-`x>U*ux5ZDB_qRAN3FHC7_}5XTD3XpJFovm4BZS6jePmcI1OK* z^&QRY{k-^(o`6Z^09Jq0z}8$XO!TrvA5uAh)vraSG!Spg-?NN@*y2SFM=7P2a*kSQ z46ofRTsTLj0`bQ7FvDGH_!0R2G02K^ug#x?Y&!xT@u-jg^k9|oufSM8a)46)FKjjw zWEeop8N6?QOC)E&1Nb95sI!*|MphA4!pIpxvcPM6j8pDq{VgC{x@qmmcukI1Em%g5 zjQQ>OSe-Juv}cwX(D*2##;1PiTzpS&<&gMmU@5U1yxwX;8dw{jiE47VnU#Z-bqCa1 zZ3aLe@EoC(zUl!nh5Q{MqF^5FT!vMR%qVTIj-!Zw3Qc_+tjW=LksJXRITva_jS1ilYI6b*1d6t74q)X3 z9NHBzlJ6lq{_5bt#C7;*g4>(glrYz zeV?sNqH<;D@TPYn!K$POF?<3TY zoS}s+D8gk96WxcFGiW)0)-U(B()dVk)31UNP*fp@;KuS2GfNiDA6b|6ek2{s<7ai` zf5)~S0j==PCUgX#wsky2)_{<4rddYQ)-@vXxgKfk6b6WRpw=gyLvBjjpy%xo*&ay# zD*Be|w~irCH=2VDzX_DIjg+s^IajXy#$pX?gVLF_b!9Ny_qy^Bd0<7lJif! z3f9hV`Hnk{HBO$N{6|7lfVHkDJfqEa3dsOmH}bvIrF=j92tZVXgh!JV&4D;j6wFL* zRxm)1laMYn=HM9W04Uj@w&ny8c;hn0?xB7ubGNah&R}7v185$nB{egn)PlFJ2zDEH zgz-z6d=H=*dY9ozw#+|xp9a2{-Nr>vT4PqXQyn0>81JaqwJ!AC6^xCH-@31*H-T1; zAls`Fx(u#h?5OdzAd>HwIsg}JWvN%o83?R_9Y%~H947onib^Zt4I8lnf&J?{$Q1V(7`BIeH_0M0tB_ z2Ox~3b1$(Rp{}U2MV!RiOK2@mZ~($cI-g~HO-3f-qa~i!KCR(N4nSBz=OcmcJw_Q{ zdq)^{6F4H#83mpbP literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_8.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_8.png new file mode 100644 index 0000000000000000000000000000000000000000..1a1fe5d2d4a034e644e717e621cc315e92baa085 GIT binary patch literal 1694 zcmV;P24VS$P)4r{O-HzUS&p5R;KO>M# z+Ns(`ZLk{?OfvwQ`TQ9P8p-JuXYD`c0y0zsSmRb!&&WCp{5ss*!)`2)MW_a_#v z8_F}>SjYRRgctPDckYCvwbom+Gr;>hwXh%~BX>3vr7yzhmp~)vY4AVk%d;2V?0E7_ z_x`5O0VMP3XPKHpVv7i(K|R)}a{3M|@>K)0_MHKAypjpHAtQGX6=$@R)jaEGH38Y%#$(fD-{x+~RKU1SM1Hv5MXI2*SIn|wvw0=dASXpR#Fd2Y@ z*9fi6bq^9#ctD*$#-(m#qotd#! z7&=cefFzVM0%r#F-hdmFJ4$l~?*_Cx1JC)I4tIx?jHLlU7vk+%gb6g~NH=$MjII^A z#j9{`1!Ze{s(@4nl{0|0FlPnyp1?Z}tU#0AK{|lQu^_d8J3&e+{#0v#Y=!SB!-D`( zk_QLXPg*0g56j_PNA#W|15byD9LtXUsRn>`i82E&7g3VV<8m}udTUPeoGBeoS|Fc^ zsM9~5P56HR3Dtoh$+c+TTN|Nct1GbNYb?O^wb;o5VpC~4VdShEyfMRREIq44&Sg+%*wEDJOVs+|A906QR4-2V#9$ghOd-5~Xh_MX;#R0hHFdw^L~JBxP~){h2nj5C0ARM7P` z8IsP@I+roP3{6`yrD(4P!YtMtO)sfX<6|7iepht?=`VvUMKz;VSL0crLB(lY%3w*3 zXLLSKT2qsW$WL_vm9S{7s*A|HKPWdEe3H)E8IKrUJKIrRwx|K#Z3H_rQ3uZiLDu=y z_!(#!8x4Zl^-G^KH2{Z4XHvv-dbO%wX&pp;FVifiSjU{Fs}}?r(^uC5r?-I-r+P-^ z^CLP$;W<~*7df*t;BqBhFJXYp2CUCfBSkaZlZnok^yTxdAVA?Ur&VAss{s(Dd3`{Y z)6UKeth1FGkFJZ-zNHhF7by(6j@I9iOs6^_&yxQMcov>WmXlCS|d{&BJ~r(0H<~V9t~=d zEoDwrZL3I&%F8eXcLsky6M$CLuYGfDusiQ9G0z6Oy z@=cr>kBIXvs;xNFXTaHHi!LAoFO#Iqsbl~P(mCzvs#WRMdoz%Z(XAPPBSWdtqRCs^ zg4B^{x_ZT!Br^l6U5q?03jisrHG^cSmabKud1%mTZCh2V*?a8fZwm(S44|F6vqfA6 z$eqQQNB@q>%j}~f2rjDukp7s@jnoKqEYq%O@*?kBEv@uk#uZw72?xOrMIZ{xh+04) zK_kuA)|zb_z26a;T7rzVLN&n3>a-e20VWwED~pa->B}iwT6<=_@G&qL0KKHxDPXJ& zlg1;mq_KhQ-lMj&Dt$}#bPa$gx^p0);})H2>`}4bXkbbm1fu(N4e-=Jps1LeMYMB3 z+R11TKt3oMk oNVkHS0El9jKv<2P0s#$zUs0JT=-M&wV*mgE07*qoM6N<$f;eRor2qf` literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_9.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_9.png new file mode 100644 index 0000000000000000000000000000000000000000..a19da7382eadaf0bc885f76da8dec3899034528c GIT binary patch literal 1605 zcmV-L2DVtUtjJl073;Y!fgJw#SBa zU#+#?dvC3^_IkY<|C>YV!@uKL-bVt;&q9(vEAubn%@>jksTm&;F*NTRky(Y+@}Gh? z8HU>4`;fuOd3axGd<85Ws^_{hB0=!O0N7c(PVk~19YVS$jsF+9b%+GPF9zVP6)hbUmI%4KoKdpTq^YZW62|a^E z zY0QwpUt*AtRs^;-7NH|fK=M88%K5vDe}bchY5>ppS|zSs%NB;34&HkO>~k8f(v3Ub z2XD0Y?FgegfXLWP#UF9XlZ|~6zP8cYy8zxXQ`kv;v^0X_Glq_&j*5EV>G)VTz0?rd zHCQzU9}`8?_|koA2l7{8GQh9S$%v22jBX7_jTUjjtjx$@Nmb0V~=x7SPTIyj_4c2i}Vok(vId%tZ0OzT&r)SPgJMX=Lw?-8J)G} zN#H64Ta%B038Om_4O+1n-Kj(ml5nnF$Qpe~4Ul009ET5LVI&PmGNig<6t^=vYf~qI z5rsy^q2qY_d=K6ZM5_F=y3g!%vcN;DD&sHN1yr0R%oczd`FPKoOJXZ)7c+xWBky1U zu6U(SRqs-Nwm-sogSExv_i+*V{h5@qB*LAx+ySalk)sh@9?~oRjXI!_t?$tS6UXgr z%N<~6Zk&O{1d;S$MZWZ%=F-|LjJ~V}&~oAm88gfvIj3d#=sj(brFoAq{t5=*s49pM zBQiNN5k2!ybk0cRTQvfgceU|*@3jX(lGo3`lNFhZ43IIhcD-uRNmjMok#@<;AYAl} zQ{W=uI4j3y^+RNwX@xN~9A5)y#_)`($(kAC>4!RIvDyi;Y4h?L0COrj1E;!GuojIw z2iI{%rycZ6^YE6>kIy}tPL>=5p%c&t!TU)D(F{ulDmHJd-2LsGF39Ut>f5|QY4LLF%(Uxrk zwWyTtY2#Mc3OFaumznUb3`2#Q`KoL>8RBV%Nf9e zRv%y__CEHneFj61>DlJTW=ZS5ikuyQ#!E-H^vCOy!VIKkMG@g@BT-uUG|3)f&p zxt0cIRGnp&6O5e+jHtZJeh={DfeKUL!&&`TL(8#O&WemU}6&X;p;tIr-dH#E) zmLuSU&B(IuXHv?Fg0WiOtsH Date: Fri, 6 Jan 2023 21:05:58 +0300 Subject: [PATCH 340/824] ReadMe: edit text, move Links to the end (#2219) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Edit text, move Links to the end * ReadMe: can to must Co-authored-by: あく --- ReadMe.md | 64 +++++++++++++++++++++++++++---------------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index 5cf2ff71c22..411f5b41de1 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -8,33 +8,33 @@ # Flipper Zero Firmware -- [Flipper Zero Official Website](https://flipperzero.one). A simple way to explain to your friends what the Flipper Zero can do -- [Flipper Zero Firmware Update](https://update.flipperzero.one). Improvements for your dolphin: latest firmware releases, upgrade tools for PC and Mobile devices -- [User Documentation](https://docs.flipperzero.one). Learn more about your dolphin: specs, usage guides, and everything that you wanted to ask +- [Flipper Zero Official Website](https://flipperzero.one). A simple way to explain to your friends what Flipper Zero can do. +- [Flipper Zero Firmware Update](https://update.flipperzero.one). Improvements for your dolphin: latest firmware releases, upgrade tools for PC and mobile devices. +- [User Documentation](https://docs.flipperzero.one). Learn more about your dolphin: specs, usage guides, and anything you want to ask. # Contributing -Our main goal is to build a healthy, sustainable community around the Flipper and be open to any new ideas and contributions. We also have some rules and taboos here, so please read this page and our [Code Of Conduct](/CODE_OF_CONDUCT.md) carefully. +Our main goal is to build a healthy and sustainable community around Flipper, so we're open to any new ideas and contributions. We also have some rules and taboos here, so please read this page and our [Code of Conduct](/CODE_OF_CONDUCT.md) carefully. ## I need help -The best place to search for answers is our [User Documentation](https://docs.flipperzero.one). If you can't find the answer there, you can check our [Discord Server](https://flipp.dev/discord) or our [Forum](https://forum.flipperzero.one/). +The best place to search for answers is our [User Documentation](https://docs.flipperzero.one). If you can't find the answer there, check our [Discord Server](https://flipp.dev/discord) or our [Forum](https://forum.flipperzero.one/). ## I want to report an issue -If you've found an issue and want to report it, please check our [Issues](https://github.com/flipperdevices/flipperzero-firmware/issues) page. Make sure that the description contains information about the firmware version you're using, your platform, and the proper steps to reproduce the issue. +If you've found an issue and want to report it, please check our [Issues](https://github.com/flipperdevices/flipperzero-firmware/issues) page. Make sure the description contains information about the firmware version you're using, your platform, and a clear explanation of the steps to reproduce the issue. ## I want to contribute code -Before opening a PR, please confirm that your changes must be contained in the firmware. Many ideas can easily be implemented as external applications and published in the Flipper Application Catalog (coming soon). If you are unsure, you can ask on the [Discord Server](https://flipp.dev/discord) or the [Issues](https://github.com/flipperdevices/flipperzero-firmware/issues) page, and we'll help you find the right place for your code. +Before opening a PR, please confirm that your changes must be contained in the firmware. Many ideas can easily be implemented as external applications and published in the Flipper Application Catalog (coming soon). If you are unsure, reach out to us on the [Discord Server](https://flipp.dev/discord) or the [Issues](https://github.com/flipperdevices/flipperzero-firmware/issues) page, and we'll help you find the right place for your code. -Also, please read our [Contribution Guide](/CONTRIBUTING.md), and our [Coding Style](/CODING_STYLE.md), and ensure that your code is compatible with our project [License](/LICENSE). +Also, please read our [Contribution Guide](/CONTRIBUTING.md) and our [Coding Style](/CODING_STYLE.md), and make sure your code is compatible with our [Project License](/LICENSE). -Finally, open a [Pull Request](https://github.com/flipperdevices/flipperzero-firmware/pulls) and ensure that CI/CD statuses are all green. +Finally, open a [Pull Request](https://github.com/flipperdevices/flipperzero-firmware/pulls) and make sure that CI/CD statuses are all green. # Development -The Flipper Zero Firmware is written in C, with some bits and pieces written in C++ and armv7m assembly languages. An intermediate level of C knowledge is recommended for comfortable programming. For Flipper applications, we support C, C++, and armv7m assembly languages. +Flipper Zero Firmware is written in C, with some bits and pieces written in C++ and armv7m assembly languages. An intermediate level of C knowledge is recommended for comfortable programming. C, C++, and armv7m assembly languages are supported for Flipper applications. ## Requirements @@ -50,11 +50,11 @@ Supported in-circuit debuggers (optional but highly recommended): - ST-Link - J-Link -Everything else will be taken care of by Flipper Build System. +Flipper Build System will take care of all the other dependencies. -## Cloning Source Code +## Cloning source code -Ensure that you have enough space and clone source code with Git: +Make sure you have enough space and clone the source code: ```shell git clone --recursive https://github.com/flipperdevices/flipperzero-firmware.git @@ -68,17 +68,17 @@ Build firmware using Flipper Build Tool: ./fbt ``` -## Flashing Firmware using an in-circuit debugger +## Flashing firmware using an in-circuit debugger -Connect your in-circuit debugger to the Flipper and flash firmware using Flipper Build Tool: +Connect your in-circuit debugger to your Flipper and flash firmware using Flipper Build Tool: ```shell ./fbt flash ``` -## Flashing Firmware using USB +## Flashing firmware using USB -Ensure that your Flipper is working, connect it using a USB cable and flash firmware using Flipper Build Tool: +Make sure your Flipper is on, and your firmware is functioning. Connect your Flipper with a USB cable and flash firmware using Flipper Build Tool: ```shell ./fbt flash_usb @@ -88,11 +88,24 @@ Ensure that your Flipper is working, connect it using a USB cable and flash firm - [Flipper Build Tool](/documentation/fbt.md) - building, flashing, and debugging Flipper software - [Applications](/documentation/AppsOnSDCard.md), [Application Manifest](/documentation/AppManifests.md) - developing, building, deploying, and debugging Flipper applications -- [Hardware combos and Un-bricking](/documentation/KeyCombo.md) - recovering your Flipper from most nasty situations +- [Hardware combos and Un-bricking](/documentation/KeyCombo.md) - recovering your Flipper from the most nasty situations - [Flipper File Formats](/documentation/file_formats) - everything about how Flipper stores your data and how you can work with it - [Universal Remotes](/documentation/UniversalRemotes.md) - contributing your infrared remote to the universal remote database - [Firmware Roadmap](/documentation/RoadMap.md) -- And much more in the [Documentation](/documentation) folder +- And much more in the [documentation](/documentation) folder + +# Project structure + +- `applications` - applications and services used in firmware +- `assets` - assets used by applications and services +- `furi` - Furi Core: OS-level primitives and helpers +- `debug` - debug tool: GDB plugins, an SVD file, etc. +- `documentation` - documentation generation system configs and input files +- `firmware` - firmware source code +- `lib` - our and 3rd party libraries, drivers, etc. +- `scripts` - supplementary scripts and python libraries home + +Also, see `ReadMe.md` files inside those directories for further details. # Links @@ -100,16 +113,3 @@ Ensure that your Flipper is working, connect it using a USB cable and flash firm - Website: [flipperzero.one](https://flipperzero.one) - Forum: [forum.flipperzero.one](https://forum.flipperzero.one/) - Kickstarter: [kickstarter.com](https://www.kickstarter.com/projects/flipper-devices/flipper-zero-tamagochi-for-hackers) - -# Project structure - -- `applications` - Applications and services used in firmware -- `assets` - Assets used by applications and services -- `furi` - Furi Core: OS-level primitives and helpers -- `debug` - Debug tool: GDB-plugins, SVD-file and etc -- `documentation` - Documentation generation system configs and input files -- `firmware` - Firmware source code -- `lib` - Our and 3rd party libraries, drivers, etc. -- `scripts` - Supplementary scripts and python libraries home - -Also, pay attention to `ReadMe.md` files inside those directories. From c24bea6b06f3367f5dc4624c5bdce754267dbc60 Mon Sep 17 00:00:00 2001 From: knrn-ai <25254561+knrn-ai@users.noreply.github.com> Date: Fri, 6 Jan 2023 21:18:43 +0300 Subject: [PATCH 341/824] Documentation: edit texts, markdown linting (#2226) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: konerini <25254561+konerini@users.noreply.github.com> Co-authored-by: あく --- documentation/AppManifests.md | 117 +++++++++--------- documentation/AppsOnSDCard.md | 67 +++++----- documentation/KeyCombo.md | 103 +++++++-------- documentation/OTA.md | 108 ++++++++-------- documentation/RoadMap.md | 19 ++- documentation/UnitTests.md | 40 ++++-- documentation/UniversalRemotes.md | 49 ++++---- documentation/fbt.md | 115 +++++++++-------- .../file_formats/BadUsbScriptFormat.md | 106 +++++++++------- .../file_formats/InfraredFileFormats.md | 74 ++++++----- .../file_formats/LfRfidFileFormat.md | 50 ++++---- documentation/file_formats/NfcFileFormats.md | 22 ++-- .../file_formats/SubGhzFileFormats.md | 116 +++++++++-------- .../file_formats/iButtonFileFormat.md | 12 +- 14 files changed, 511 insertions(+), 487 deletions(-) diff --git a/documentation/AppManifests.md b/documentation/AppManifests.md index 2ffa3c0c252..195fe925688 100644 --- a/documentation/AppManifests.md +++ b/documentation/AppManifests.md @@ -1,65 +1,63 @@ # Flipper Application Manifests (.fam) -All components of Flipper Zero firmware — services, user applications, and system settings — are developed independently. Each component has a build system manifest file, named `application.fam`, which defines the basic properties of that component and its relations to other parts of the system. +All components of Flipper Zero firmware — services, user applications, and system settings — are developed independently. Each component has a build system manifest file named `application.fam`, which defines the basic properties of that component and its relations to other parts of the system. -When building firmware, **`fbt`** collects all application manifests and processes their dependencies. Then it builds only those components referenced in the current build configuration. See [fbt docs](./fbt.md#firmware-application-set) for details on build configurations. +When building firmware, **`fbt`** collects all application manifests and processes their dependencies. Then it builds only those components referenced in the current build configuration. See [FBT docs](./fbt.md#firmware-application-set) for details on build configurations. ## Application definition -A firmware component's properties are declared in a Python code snippet, forming a call to App() function with various parameters. +A firmware component's properties are declared in a Python code snippet, forming a call to the `App()` function with various parameters. -Only 2 parameters are mandatory: ***appid*** and ***apptype***; others are optional and may only be meaningful for certain application types. +Only two parameters are mandatory: **_appid_** and **_apptype_**. Others are optional and may only be meaningful for certain application types. ### Parameters -* **appid**: string, application id within the build system. Used to specify which applications to include in the build configuration and resolve dependencies and conflicts. - -* **apptype**: member of FlipperAppType.* enumeration. Valid values are: - -| Enum member | Firmware component type | -|--------------|--------------------------| -| SERVICE | System service, created at early startup | -| SYSTEM | Application is not being shown in any menus. It can be started by other apps or from CLI | -| APP | Regular application for the main menu | -| PLUGIN | Application to be built as a part of the firmware and to be placed in the Plugins menu | -| DEBUG | Application only visible in Debug menu with debug mode enabled | -| ARCHIVE | One and only Archive app | -| SETTINGS | Application to be placed in the system settings menu | -| STARTUP | Callback function to run at system startup. Does not define a separate app | -| EXTERNAL | Application to be built as .fap plugin | -| METAPACKAGE | Does not define any code to be run, used for declaring dependencies and application bundles | - -* **name**: Name that is displayed in menus. -* **entry_point**: C function to be used as the application's entry point. Note that C++ function names are mangled, so you need to wrap them in `extern "C"` to use them as entry points. -* **flags**: Internal flags for system apps. Do not use. -* **cdefines**: C preprocessor definitions to declare globally for other apps when the current application is included in the active build configuration. -* **requires**: List of application IDs to include in the build configuration when the current application is referenced in the list of applications to build. -* **conflicts**: List of application IDs that the current application conflicts with. If any of them is found in the constructed application list, **`fbt`** will abort the firmware build process. -* **provides**: Functionally identical to ***requires*** field. -* **stack_size**: Stack size, in bytes, to allocate for application on its startup. Note that allocating a stack too small for an app to run will cause a system crash due to stack overflow, and allocating too much stack space will reduce usable heap memory size for apps to process data. *Note: you can use `ps`, and `free` CLI commands to profile your app's memory usage.* -* **icon**: Animated icon name from built-in assets to be used when building the app as a part of the firmware. -* **order**: Order of an application within its group when sorting entries in it. The lower the order is, the closer to the start of the list the item is placed. *Used for ordering startup hooks and menu entries.* -* **sdk_headers**: List of C header files from this app's code to include in API definitions for external applications. -* **targets**: list of strings, target names, which this application is compatible with. If not specified, the application is built for all targets. The default value is `["all"]`. - +- **appid**: string, application ID within the build system. It is used to specify which applications to include in the build configuration and resolve dependencies and conflicts. + +- **apptype**: member of FlipperAppType.\* enumeration. Valid values are: + +| Enum member | Firmware component type | +| ----------- | ------------------------------------------------------------------------------------------- | +| SERVICE | System service, created at early startup | +| SYSTEM | Application is not being shown in any menus. It can be started by other apps or from CLI | +| APP | Regular application for the main menu | +| PLUGIN | Application to be built as a part of the firmware and to be placed in the Plugins menu | +| DEBUG | Application only visible in Debug menu with debug mode enabled | +| ARCHIVE | One and only Archive app | +| SETTINGS | Application to be placed in the system settings menu | +| STARTUP | Callback function to run at system startup. Does not define a separate app | +| EXTERNAL | Application to be built as `.fap` plugin | +| METAPACKAGE | Does not define any code to be run, used for declaring dependencies and application bundles | + +- **name**: name displayed in menus. +- **entry_point**: C function to be used as the application's entry point. Note that C++ function names are mangled, so you need to wrap them in `extern "C"` to use them as entry points. +- **flags**: internal flags for system apps. Do not use. +- **cdefines**: C preprocessor definitions to declare globally for other apps when the current application is included in the active build configuration. +- **requires**: list of application IDs to include in the build configuration when the current application is referenced in the list of applications to build. +- **conflicts**: list of application IDs with which the current application conflicts. If any of them is found in the constructed application list, **`fbt`** will abort the firmware build process. +- **provides**: functionally identical to **_requires_** field. +- **stack_size**: stack size in bytes to allocate for an application on its startup. Note that allocating a stack too small for an app to run will cause a system crash due to stack overflow, and allocating too much stack space will reduce usable heap memory size for apps to process data. _Note: you can use `ps` and `free` CLI commands to profile your app's memory usage._ +- **icon**: animated icon name from built-in assets to be used when building the app as a part of the firmware. +- **order**: order of an application within its group when sorting entries in it. The lower the order is, the closer to the start of the list the item is placed. _Used for ordering startup hooks and menu entries._ +- **sdk_headers**: list of C header files from this app's code to include in API definitions for external applications. +- **targets**: list of strings and target names with which this application is compatible. If not specified, the application is built for all targets. The default value is `["all"]`. #### Parameters for external applications The following parameters are used only for [FAPs](./AppsOnSDCard.md): -* **sources**: list of strings, file name masks used for gathering sources within the app folder. The default value of `["*.c*"]` includes C and C++ source files. Applications cannot use the `"lib"` folder for their own source code, as it is reserved for **fap_private_libs**. -* **fap_version**: tuple, 2 numbers in the form of (x,y): application version to be embedded within .fap file. The default value is (0,1), meaning version "0.1". -* **fap_icon**: name of a .png file, 1-bit color depth, 10x10px, to be embedded within .fap file. -* **fap_libs**: list of extra libraries to link the application against. Provides access to extra functions that are not exported as a part of main firmware at the expense of increased .fap file size and RAM consumption. -* **fap_category**: string, may be empty. App subcategory, also determines the path of the FAP within apps folder in the file system. -* **fap_description**: string, may be empty. Short application description. -* **fap_author**: string, may be empty. Application's author. -* **fap_weburl**: string, may be empty. Application's homepage. -* **fap_icon_assets**: string. If present defines a folder name to be used for gathering image assets for this application. These images will be preprocessed and built alongside the application. See [FAP assets](./AppsOnSDCard.md#fap-assets) for details. -* **fap_extbuild**: provides support for parts of application sources to be built by external tools. Contains a list of `ExtFile(path="file name", command="shell command")` definitions. **`fbt`** will run the specified command for each file in the list. - -Note that commands are executed at the firmware root folder's root, and all intermediate files must be placed in an application's temporary build folder. For that, you can use pattern expansion by **`fbt`**: `${FAP_WORK_DIR}` will be replaced with the path to the application's temporary build folder, and `${FAP_SRC_DIR}` will be replaced with the path to the application's source folder. You can also use other variables defined internally by **`fbt`**. +- **sources**: list of strings, file name masks used for gathering sources within the app folder. The default value of `["*.c*"]` includes C and C++ source files. Applications cannot use the `"lib"` folder for their own source code, as it is reserved for **fap_private_libs**. +- **fap_version**: tuple, 2 numbers in the form of (x,y): application version to be embedded within .fap file. The default value is (0,1), meaning version "0.1". +- **fap_icon**: name of a `.png` file, 1-bit color depth, 10x10px, to be embedded within `.fap` file. +- **fap_libs**: list of extra libraries to link the application against. Provides access to extra functions that are not exported as a part of main firmware at the expense of increased `.fap` file size and RAM consumption. +- **fap_category**: string, may be empty. App subcategory, also determines the path of the FAP within the apps folder in the file system. +- **fap_description**: string, may be empty. Short application description. +- **fap_author**: string, may be empty. Application's author. +- **fap_weburl**: string, may be empty. Application's homepage. +- **fap_icon_assets**: string. If present, it defines a folder name to be used for gathering image assets for this application. These images will be preprocessed and built alongside the application. See [FAP assets](./AppsOnSDCard.md#fap-assets) for details. +- **fap_extbuild**: provides support for parts of application sources to be built by external tools. Contains a list of `ExtFile(path="file name", command="shell command")` definitions. **`fbt`** will run the specified command for each file in the list. +Note that commands are executed at the firmware root folder, and all intermediate files must be placed in an application's temporary build folder. For that, you can use pattern expansion by **`fbt`**: `${FAP_WORK_DIR}` will be replaced with the path to the application's temporary build folder, and `${FAP_SRC_DIR}` will be replaced with the path to the application's source folder. You can also use other variables defined internally by **`fbt`**. Example for building an app from Rust sources: @@ -73,16 +71,16 @@ Example for building an app from Rust sources: ), ``` -* **fap_private_libs**: a list of additional libraries distributed as sources alongside the application. These libraries will be built as a part of the application build process. -Library sources must be placed in a subfolder of "`lib`" folder within the application's source folder. -Each library is defined as a call to `Lib()` function, accepting the following parameters: +- **fap_private_libs**: list of additional libraries distributed as sources alongside the application. These libraries will be built as a part of the application build process. + Library sources must be placed in a subfolder of the `lib` folder within the application's source folder. + Each library is defined as a call to the `Lib()` function, accepting the following parameters: - - **name**: name of library's folder. Required. - - **fap_include_paths**: list of library's relative paths to add to parent fap's include path list. The default value is `["."]` meaning the library's source root. - - **sources**: list of filename masks to be used for gathering include files for this library. Paths are relative to the library's source root. The default value is `["*.c*"]`. - - **cflags**: list of additional compiler flags to be used for building this library. The default value is `[]`. - - **cdefines**: list of additional preprocessor definitions to be used for building this library. The default value is `[]`. - - **cincludes**: list of additional include paths to be used for building this library. Paths are relative to the application's root. This can be used for providing external search paths for this library's code - for configuration headers. The default value is `[]`. + - **name**: name of the library's folder. Required. + - **fap_include_paths**: list of the library's relative paths to add to the parent fap's include path list. The default value is `["."]`, meaning the library's source root. + - **sources**: list of filename masks to be used for gathering include files for this library. Paths are relative to the library's source root. The default value is `["*.c*"]`. + - **cflags**: list of additional compiler flags to be used for building this library. The default value is `[]`. + - **cdefines**: list of additional preprocessor definitions to be used for building this library. The default value is `[]`. + - **cincludes**: list of additional include paths to be used for building this library. Paths are relative to the application's root. This can be used for providing external search paths for this library's code — for configuration headers. The default value is `[]`. Example for building an app with a private library: @@ -105,15 +103,14 @@ Example for building an app with a private library: ], ``` -For that snippet, **`fbt`** will build 2 libraries: one from sources in `lib/mbedtls` folder and another from sources in `lib/loclass` folder. For the `mbedtls` library, **`fbt`** will add `lib/mbedtls/include` to the list of include paths for the application and compile only the files specified in the `sources` list. Additionally, **`fbt`** will enable `MBEDTLS_ERROR_C` preprocessor definition for `mbedtls` sources. +For that snippet, **`fbt`** will build 2 libraries: one from sources in `lib/mbedtls` folder and another from sources in the `lib/loclass` folder. For the `mbedtls` library, **`fbt`** will add `lib/mbedtls/include` to the list of include paths for the application and compile only the files specified in the `sources` list. Additionally, **`fbt`** will enable `MBEDTLS_ERROR_C` preprocessor definition for `mbedtls` sources. For the `loclass` library, **`fbt`** will add `lib/loclass` to the list of the include paths for the application and build all sources in that folder. Also, **`fbt`** will disable treating compiler warnings as errors for the `loclass` library, which can be useful when compiling large 3rd-party codebases. Both libraries will be linked with the application. +## `.fam` file contents -## .fam file contents - -.fam file contains one or more Application definitions. For example, here's a part of `applications/service/bt/application.fam`: +The `.fam` file contains one or more application definitions. For example, here's a part of `applications/service/bt/application.fam`: ```python App( @@ -137,4 +134,4 @@ App( ) ``` -For more examples, see .fam files from various firmware parts. +For more examples, see `.fam` files from various firmware parts. diff --git a/documentation/AppsOnSDCard.md b/documentation/AppsOnSDCard.md index d38837276f4..9ab7e9b26bb 100644 --- a/documentation/AppsOnSDCard.md +++ b/documentation/AppsOnSDCard.md @@ -1,83 +1,80 @@ # FAP (Flipper Application Package) -[fbt](./fbt.md) has support for building applications as FAP files. FAP are essentially .elf executables with extra metadata and resources bundled in. +[fbt](./fbt.md) supports building applications as FAP files. FAPs are essentially `.elf` executables with extra metadata and resources bundled in. -FAPs are built with `faps` target. They can also be deployed to `dist` folder with `fap_dist` target. +FAPs are built with the `faps` target. They can also be deployed to the `dist` folder with the `fap_dist` target. FAPs do not depend on being run on a specific firmware version. Compatibility is determined by the FAP's metadata, which includes the required [API version](#api-versioning). - ## How to set up an application to be built as a FAP -FAPs are created and developed the same way as internal applications that are part of the firmware. - -To build your application as a FAP, just create a folder with your app's source code in `applications_user`, then write its code the way you'd do when creating a regular built-in application. Then configure its `application.fam` manifest — and set its *apptype* to FlipperAppType.EXTERNAL. See [Application Manifests](./AppManifests.md#application-definition) for more details. +FAPs are created and developed the same way as internal applications that are part of the firmware. - * To build your application, run `./fbt fap_{APPID}`, where APPID is your application's ID in its manifest. - * To build your app, then upload it over USB & run it on Flipper, use `./fbt launch_app APPSRC=applications/path/to/app`. This command is configured in default [VSCode profile](../.vscode/ReadMe.md) as "Launch App on Flipper" build action (Ctrl+Shift+B menu). - * To build all FAPs, run `./fbt faps` or `./fbt fap_dist`. +To build your application as a FAP, create a folder with your app's source code in `applications_user`, then write its code the way you'd do when creating a regular built-in application. Then configure its `application.fam` manifest, and set its _apptype_ to FlipperAppType.EXTERNAL. See [Application Manifests](./AppManifests.md#application-definition) for more details. +- To build your application, run `./fbt fap_{APPID}`, where APPID is your application's ID in its manifest. +- To build your app and upload it over USB to run on Flipper, use `./fbt launch_app APPSRC=applications/path/to/app`. This command is configured in the default [VS Code profile](../.vscode/ReadMe.md) as a "Launch App on Flipper" build action (Ctrl+Shift+B menu). +- To build all FAPs, run `./fbt faps` or `./fbt fap_dist`. ## FAP assets FAPs can include static and animated images as private assets. They will be automatically compiled alongside application sources and can be referenced the same way as assets from the main firmware. -To use that feature, put your images in a subfolder inside your application's folder, then reference that folder in your application's manifest in `fap_icon_assets` field. See [Application Manifests](./AppManifests.md#application-definition) for more details. +To use that feature, put your images in a subfolder inside your application's folder, then reference that folder in your application's manifest in the `fap_icon_assets` field. See [Application Manifests](./AppManifests.md#application-definition) for more details. To use these assets in your application, put `#include "{APPID}_icons.h"` in your application's source code, where `{APPID}` is the `appid` value field from your application's manifest. Then you can use all icons from your application's assets the same way as if they were a part of `assets_icons.h` of the main firmware. -Images and animated icons must follow the same [naming convention](../assets/ReadMe.md#asset-naming-rules) as those from the main firmware. - +Images and animated icons should follow the same [naming convention](../assets/ReadMe.md#asset-naming-rules) as those from the main firmware. ## Debugging FAPs -**`fbt`** includes a script for gdb-py to provide debugging support for FAPs, `debug/flipperapps.py`. It is loaded in default debugging configurations by **`fbt`** and stock VSCode configurations. +**`fbt`** includes a script for gdb-py to provide debugging support for FAPs, `debug/flipperapps.py`. It is loaded in default debugging configurations by **`fbt`** and stock VS Code configurations. -With it, you can debug FAPs as if they were a part of main firmware — inspect variables, set breakpoints, step through the code, etc. +With it, you can debug FAPs as if they were a part of the main firmware — inspect variables, set breakpoints, step through the code, etc. ### Setting up debugging environment -Debugging support script looks up debugging information in the latest firmware build dir (`build/latest`). That directory is symlinked by fbt to the latest firmware configuration (Debug or Release) build dir, when you run `./fbt` for chosen configuration. See [fbt docs](./fbt.md#nb) for details. +The debugging support script looks up debugging information in the latest firmware build directory (`build/latest`). That directory is symlinked by `fbt` to the latest firmware configuration (Debug or Release) build directory when you run `./fbt` for the chosen configuration. See [fbt docs](./fbt.md#nb) for details. + +To debug FAPs, do the following: -So, to debug FAPs, do the following: 1. Build firmware with `./fbt` 2. Flash it with `./fbt flash` -3. [Build your FAP](#how-to-set-up-an-application-to-be-built-as-a-fap) and run it on Flipper. +3. [Build your FAP](#how-to-set-up-an-application-to-be-built-as-a-fap) and run it on Flipper -After that, you can attach with `./fbt debug` or VSCode and use all debug features. +After that, you can attach with `./fbt debug` or VS Code and use all debug features. -It is **important** that firmware and application build type (debug/release) match, and that matching firmware folder is linked as `build/latest`. Otherwise, debugging will not work. +It is **important** that firmware and application build type (debug/release) match and that the matching firmware folder is linked as `build/latest`. Otherwise, debugging will not work. +## How Flipper runs an application from an SD card -## How Flipper runs an application from SD card +Flipper's MCU cannot run code directly from external storage, so it needs to be copied to RAM first. That is done by the App Loader application responsible for loading the FAP from the SD card, verifying its integrity and compatibility, copying it to RAM, and adjusting it for its new location. -Flipper's MCU cannot run code directly from external storage, so it needs to be copied to RAM first. That is done by the App Loader application, which is responsible for loading the FAP from SD card, verifying its integrity and compatibility, copying it to RAM and adjusting it for its new location. +Since FAP has to be loaded to RAM to be executed, the amount of RAM available for allocations from heap is reduced compared to running the same app from flash, as a part of the firmware. Note that the amount of occupied RAM is less than the total FAP file size since only code and data sections are allocated, while the FAP file includes extra information only used at app load time. -Since FAP has to be loaded to RAM to be executed, the amount of RAM available for allocations from heap is reduced compared to running the same app from flash, as a part of firmware. Note that the amount of occupied RAM is less than total FAP file size, since only code and data sections are allocated, while FAP file includes extra information only used at app load time. +Applications are built for a specific API version. It is a part of the hardware target's definition and contains a major and minor version number. The App Loader checks if the application's major API version matches the firmware's major API version. -Applications are built for a specific API version. It is a part of the hardware target's definition and contains a major and minor version number. Application loader checks if the application's major API version matches firmware's major API version. +The App Loader allocates memory for the application and copies it to RAM, processing relocations and providing concrete addresses for imported symbols using the [symbol table](#symbol-table). Then it starts the application. -App loader allocates memory for the application and copies it to RAM, processing relocations and providing concrete addresses for imported symbols using the [symbol table](#symbol-table). Then it starts the application. +## API versioning +Not all parts of firmware are available for external applications. A subset of available functions and variables is defined in the "api_symbols.csv" file, which is a part of the firmware target definition in the `firmware/targets/` directory. -## API Versioning - -Not all parts of firmware are available for external applications. A subset of available functions and variables is defined in "api_symbols.csv" file, which is a part of firmware target definition in `firmware/targets/` directory. - -**`fbt`** uses semantic versioning for API. Major version is incremented when there are breaking changes in the API, minor version is incremented when new features are added. +**`fbt`** uses semantic versioning for the API. The major version is incremented when there are breaking changes in the API. The minor version is incremented when new features are added. Breaking changes include: -- removal of a function or a global variable; -- changing the signature of a function. -API versioning is mostly automated by **`fbt`**. When rebuilding the firmware, **`fbt`** checks if there are any changes in the API exposed by headers gathered from `SDK_HEADERS`. If so, it stops the build, adjusts the API version and asks the user to go through the changes in .csv file. New entries are marked with "`?`" mark, and the user is supposed to change the mark to "`+`" for the entry to be exposed for FAPs, "`-`" for it to be unavailable. +- Removing a function or a global variable +- Changing the signature of a function + +API versioning is mostly automated by **`fbt`**. When rebuilding the firmware, **`fbt`** checks if there are any changes in the API exposed by headers gathered from `SDK_HEADERS`. If so, it stops the build, adjusts the API version, and asks the user to go through the changes in the `.csv` file. New entries are marked with a "`?`" mark, and the user is supposed to change the mark to "`+`" for the entry to be exposed for FAPs, or to "`-`" for it to be unavailable. **`fbt`** will not allow building a firmware until all "`?`" entries are changed to "`+`" or "`-`". -**NB:** **`fbt`** automatically manages the API version. The only case where manually incrementing the major API version is allowed (and required) is when existing "`+`" entries are to be changed to "`-`". +**NB:** **`fbt`** automatically manages the API version. The only case where manually incrementing the major API version is allowed (and required) is when existing "`+`" entries are to be changed to "`-`". -### Symbol Table +### Symbol table The symbol table is a list of symbols exported by firmware and available for external applications. It is generated by **`fbt`** from the API symbols file and is used by the App Loader to resolve addresses of imported symbols. It is build as a part of the `fap_loader` application. -**`fbt`** also checks if all imported symbols are present in the symbol table. If there are any missing symbols, it will issue a warning listing them. Such application won't be able to run on the device until all requires symbols are provided in the symbol table. +**`fbt`** also checks if all imported symbols are present in the symbol table. If there are any missing symbols, it will issue a warning listing them. The application won't be able to run on the device until all required symbols are provided in the symbol table. diff --git a/documentation/KeyCombo.md b/documentation/KeyCombo.md index 93ceb204c34..6db5b411354 100644 --- a/documentation/KeyCombo.md +++ b/documentation/KeyCombo.md @@ -1,134 +1,119 @@ # Key Combos -There are times when your Flipper feels blue and doesn't respond to your commands. -In that case, you may find this guide useful. +There are times when your Flipper feels blue and doesn't respond to any of your commands due to a software issue. This guide will help you solve this problem. +## Basic combos -## Basic Combos - - -### Hardware Reset +### Hardware reset - Press `LEFT` and `BACK` and hold for a couple of seconds - Release `LEFT` and `BACK` -This combo performs a hardware reset by pulling MCU reset line down. -Main components involved: Keys -> DD8(NC7SZ32M5X, OR-gate) -> DD1(STM32WB55, MCU) - -There is 1 case where it does not work: +This combo performs a hardware reset by pulling the MCU reset line down. +Main components involved: Keys -> DD8(NC7SZ32M5X, OR-gate) -> DD1(STM32WB55, MCU). -- MCU debug block is active and holding reset line from inside. +It won't work only in one case: +- The MCU debug block is active and holding the reset line from inside. ### Hardware Power Reset -- Disconnect USB and any external power supplies -- Disconnect USB once again -- Make sure that you've disconnected USB and any external power supplies -- Press `BACK` and hold for 30 seconds (Will only work with USB disconnected) -- If you have not disconnected USB, then disconnect USB and repeat previous step -- Release `BACK` key +- Disconnect the USB cable and any external power supplies +- Disconnect the USB once again +- Make sure you've disconnected the USB and any external power supplies +- Press `BACK` and hold for 30 seconds (this will only work with the USB disconnected) +- If you haven't disconnected the USB, then disconnect it and repeat the previous step +- Release the `BACK` key This combo performs a reset by switching SYS power line off and then on. -Main components involved: Keys -> DD6(bq25896, charger) +Main components involved: Keys -> DD6(bq25896, charger). -There is 1 case where it does not work: +It won't work only in one case: - Power supply is connected to USB or 5V_ext - ### Software DFU - Press `LEFT` on boot to enter DFU with Flipper boot-loader -There is 1 case where it does not work: +It won't work only in one case: - Flipper boot-loader is damaged or absent - ### Hardware DFU - Press `OK` on boot to enter DFU with ST boot-loader -There is 1 case where it does not work: - -- Option Bytes are damaged or set to ignore `OK` key +It won't work only in one case: +- Option Bytes are damaged or set to ignore the `OK` key -## DFU Combos - +## DFU combos ### Hardware Reset + Software DFU - Press `LEFT` and `BACK` and hold for a couple of seconds - Release `BACK` -- Device will enter DFU with indication (Blue LED + DFU Screen) +- Device will enter DFU with an indication (Blue LED + DFU Screen) - Release `LEFT` -This combo performs a hardware reset by pulling MCU reset line down. -Then, `LEFT` key indicates to the boot-loader that DFU mode is requested. +This combo performs a hardware reset by pulling the MCU reset line down. Then, the `LEFT` key indicates to the boot-loader that DFU mode is requested. -There are 2 cases where it does not work: +It won't work in two cases: -- MCU debug block is active and holding reset line from inside +- The MCU debug block is active and holding the reset line from inside - Flipper boot-loader is damaged or absent - ### Hardware Reset + Hardware DFU - Press `LEFT`, `BACK` and `OK` and hold for a couple of seconds - Release `BACK` and `LEFT` -- Device will enter DFU without indication - -This combo performs a hardware reset by pulling MCU reset line down. -Then, `OK` key forces MCU to load internal boot-loader. +- The device will enter DFU without an indication -There are 2 cases where it does not work: +This combo performs a hardware reset by pulling the MCU reset line down. Then, the `OK` key forces MCU to load the internal boot-loader. -- MCU debug block is active and holding reset line from inside -- Option Bytes are damaged or set to ignore `OK` key +It won't work in two cases: +- The MCU debug block is active and holding the reset line from inside +- Option Bytes are damaged or set to ignore the `OK` key ### Hardware Power Reset + Software DFU -- Disconnect USB and any external power supplies +- Disconnect the USB and any external power supplies - Press `BACK` and `LEFT` for 30 seconds - Release `BACK` -- Device will enter DFU with indication (Blue LED + DFU Screen) +- The device will enter DFU with an indication (Blue LED + DFU Screen) - Release `LEFT` -- Plug in USB +- Plug in the USB -This combo performs a reset by switching SYS power line off and then on. -Then, `LEFT` key indicates to boot-loader that DFU mode requested. +This combo performs a reset by switching the SYS power line off and then on. Next, the `LEFT` key indicates to the boot-loader that DFU mode is requested. -There are 2 cases where it does not work: +It won't work in two cases: - Power supply is connected to USB or 5V_ext - Flipper boot-loader is damaged or absent - ### Hardware Power Reset + Hardware DFU -- Disconnect USB and any external power supplies +- Disconnect the USB and any external power supplies - Press `BACK` and `OK` and hold for 30 seconds - Release `BACK` and `OK` -- Device will enter DFU without indication -- Plug USB +- The device will enter DFU without indication +- Plug in the USB -This combo performs a reset by switching SYS power line off and then on. -Then, `OK` key forces MCU to load internal boot-loader. +This combo performs a reset by switching the SYS power line off and then on. Next, the `OK` key forces MCU to load the internal boot-loader. -There are 2 cases where it does not work: +It won't work in two cases: - Power supply is connected to USB or 5V_ext -- Option Bytes are damaged or set to ignore `OK` key +- Option Bytes are damaged or set to ignore the `OK` key # Alternative ways to recover your device -If none of the described methods were useful: +If none of the described methods helped you: -- Ensure the battery charged -- Disconnect the battery and connect again (Requires disassembly) -- Try to Flash device with ST-Link or other programmer that supports SWD +- Make sure the battery charged +- Disconnect the battery and connect again (requires disassembly) +- Try to flash the device with ST-Link or another programmer that supports SWD -If you still are here and your device is not working: it's not a software issue. \ No newline at end of file +If you're still here and your device is not working: it's not a software issue. diff --git a/documentation/OTA.md b/documentation/OTA.md index 836bc1b71aa..9d09c0f7c08 100644 --- a/documentation/OTA.md +++ b/documentation/OTA.md @@ -1,84 +1,79 @@ # Executing code from RAM -In Flipper firmware, we have a special boot mode that loads a specially crafted system image into RAM and transfers control to it. System image executing in RAM has full write access to whole Flipper's flash memory — something that's not possible when running main code from the same flash. +In Flipper firmware, we have a special boot mode that loads a specially crafted system image into RAM and transfers control to it. System image executing in RAM has full write access to Flipper's entire flash memory — something that's not possible when running main code from the same flash. We leverage that boot mode to perform OTA firmware updates, including operations on a radio stack running on the second MCU core. - # How does Flipper OTA work? Installation of OTA updates goes through 3 stages: -## 1. Backing up internal storage (`/int/`) - -It is a special partition of Flipper's flash memory, taking up all available space not used by firmware code. Newer versions of firmware may be of different size, and simply installing them would cause flash repartitioning and data loss. +## 1. Backing up internal storage (`/int`) -So, before taking any action upon the firmware, we back up current configuration from `/int/` into a plain tar archive on SD card. +It is a special partition of Flipper's flash memory, taking up all available space not used by the firmware code. Newer versions of firmware may be of different size, and simply installing them would cause flash repartitioning and data loss. +So, before taking any action on the firmware, we back up the current configuration from `/int` into a plain tar archive on the SD card. ## 2. Performing device update -For that, main firmware loads an updater image - a customized build of main Flipper firmware — into RAM and runs it. Updater performs operations on system flash that are described by an Update manifest file. +The main firmware loads an updater image — a customized build of the main Flipper firmware — into RAM and runs it. Updater performs operations on system flash as described by an Update manifest file. -First, if there's a Radio stack image bundled with the update, updater compares its version with currently installed one. If they don't match, updater performs stack deinstallation followed by writing and installing a new one. The installation itself is performed by proprietary software, FUS, running on Core2, and leads to a series of system restarts. +First, if there's a Radio stack image bundled with the update, updater compares its version with the currently installed one. If they don't match, updater performs stack deinstallation followed by writing and installing a new one. The installation itself is performed by proprietary software FUS running on Core2, and leads to a series of system restarts. Then, updater validates and corrects Option Bytes — a special memory region containing low-level configuration for Flipper's MCU. After that, updater loads a `.dfu` file with firmware to be flashed, checks its integrity using CRC32, writes it to system flash and validates written data. - ## 3. Restoring internal storage and updating resources After performing operations on flash memory, the system restarts into newly flashed firmware. Then it performs restoration of previously backed up `/int` contents. -If the update package contains an additional resources archive, it is extracted onto SD card. - +If the update package contains an additional resources archive, it is extracted onto the SD card. # Update manifest -Update packages come with a manifest that contains a description of its contents. The manifest is in Flipper File Format — a simple text file, comprised of key-value pairs. +An update package comes with a manifest that contains a description of its contents. The manifest is in Flipper File Format — a simple text file, comprised of key-value pairs. ## Mandatory fields -An update manifest must contain the following keys in given order: +An update manifest must contain the following keys in the given order: -* __Filetype__: a constant string, "Flipper firmware upgrade configuration"; +- **Filetype**: a constant string, "Flipper firmware upgrade configuration". -* __Version__: manifest version. Current value is 2; +- **Version**: manifest version. The current value is 2. -* __Info__: arbitrary string, describing package contents; +- **Info**: arbitrary string, describing package contents. -* __Target__: hardware revision the package is built for; +- **Target**: hardware revision for which the package is built. -* __Loader__: file name of stage 2 loader that is executed from RAM; +- **Loader**: file name of stage 2 loader that is executed from RAM. -* __Loader CRC__: CRC32 of loader file. Note that it is represented in little-endian hex. +- **Loader CRC**: CRC32 of loader file. Note that it is represented in little-endian hex. ## Optional fields -Other fields may have empty values, is such case, updater skips all operations related to such values. +Other fields may have empty values. In this case, updater skips all operations related to these values. -* __Radio__: file name of radio stack image, provided by STM; +- **Radio**: file name of radio stack image, provided by STM. -* __Radio address__: address to install the radio stack at. It is specified in Release Notes by STM; +- **Radio address**: address to install the radio stack at. It is specified in Release Notes by STM. -* __Radio version__: Radio major, minor and sub versions followed by branch, release, and stack type packed into 6 hex-encoded bytes; +- **Radio version**: radio major, minor and sub versions followed by branch, release and stack type packed into 6 hex-encoded bytes. -* __Radio CRC__: CRC32 of radio image; +- **Radio CRC**: CRC32 of radio image. -* __Resources__: file name of TAR archive with resources to be extracted on SD card; - -* __OB reference__, __OB mask__, __OB write mask__: reference values for validating and correcting option bytes. +- **Resources**: file name of TAR archive with resources to be extracted onto the SD card. +- **OB reference**, **OB mask**, **OB write mask**: reference values for validating and correcting option bytes. # OTA update error codes -We designed the OTA update process to be as fail-safe as possible. We don't start any risky operation before validating all related pieces of data to ensure we don't leave the device in a partially updated, or bricked, state. +We designed the OTA update process to be as fail-safe as possible. We don't start any risky operations before validating all related pieces of data to ensure we don't leave the device in a partially updated, or bricked, state. -Even if something goes wrong, Updater gives you an option to retry failed operations, and reports its state with an error code. These error codes have an `[XX-YY]` format, where `XX` encodes an operation that failed, and `YY` contains extra details on its progress where the error occurred. +Even if something goes wrong, updater allows you to retry failed operations and reports its state with an error code. These error codes have an `[XX-YY]` format, where `XX` encodes the failed operation, and `YY` contains extra details on its progress where the error occurred. | Stage description | Code | Progress | Description | -|:-----------------------:|-------:|------------|--------------------------------------------| +| :---------------------: | -----: | ---------- | ------------------------------------------ | | Loading update manifest | **1** | **13** | Updater reported hardware version mismatch | | | | **20** | Failed to get saved manifest path | | | | **30** | Failed to load manifest | @@ -86,61 +81,60 @@ Even if something goes wrong, Updater gives you an option to retry failed operat | | | **50** | Package has mismatching HW target | | | | **60** | Missing DFU file | | | | **80** | Missing radio firmware file | -| Backing up LFS | **2** | **0-100** | FS read/write error | -| Checking radio FW | **3** | **0-99** | Error reading radio firmware file | +| Backing up LFS | **2** | **0-100** | FS read/write error | +| Checking radio FW | **3** | **0-99** | Error reading radio firmware file | | | | **100** | CRC mismatch | -| Uninstalling radio FW | **4** | **0** | SHCI Delete command error | +| Uninstalling radio FW | **4** | **0** | SHCI Delete command error | | | | **80** | Error awaiting command status | -| Writing radio FW | **5** | **0-100** | Block read/write error | -| Installing radio FW | **6** | **0** | SHCI Install command error | +| Writing radio FW | **5** | **0-100** | Block read/write error | +| Installing radio FW | **6** | **0** | SHCI Install command error | | | | **80** | Error awaiting command status | -| Radio is updating | **7** | **10** | Error waiting for operation completion | -| Validating opt. bytes | **8** | **yy** | Option byte code | -| Checking DFU file | **9** | **0** | Error opening DFU file | +| Radio is updating | **7** | **10** | Error waiting for operation completion | +| Validating opt. bytes | **8** | **yy** | Option byte code | +| Checking DFU file | **9** | **0** | Error opening DFU file | | | | **1-98** | Error reading DFU file | | | | **99-100** | Corrupted DFU file | -| Writing flash | **10** | **0-100** | Block read/write error | -| Validating flash | **11** | **0-100** | Block read/write error | -| Restoring LFS | **12** | **0-100** | FS read/write error | -| Updating resources | **13** | **0-100** | SD card read/write error | - +| Writing flash | **10** | **0-100** | Block read/write error | +| Validating flash | **11** | **0-100** | Block read/write error | +| Restoring LFS | **12** | **0-100** | FS read/write error | +| Updating resources | **13** | **0-100** | SD card read/write error | # Building update packages - ## Full package -To build full update package, including firmware, radio stack and resources for SD card, run `./fbt COMPACT=1 DEBUG=0 updater_package` +To build a full update package, including firmware, radio stack and resources for the SD card, run: +`./fbt COMPACT=1 DEBUG=0 updater_package` ## Minimal package -To build minimal update package, including only firmware, run `./fbt COMPACT=1 DEBUG=0 updater_minpackage` +To build a minimal update package, including only firmware, run: +`./fbt COMPACT=1 DEBUG=0 updater_minpackage` ## Customizing update bundles -Default update packages are built with Bluetooth Light stack. -You can pick a different stack, if your firmware version supports it, and build a bundle with it passing stack type and binary name to `fbt`: +Default update packages are built with Bluetooth Light stack. +You can pick a different stack if your firmware version supports it, and build a bundle with it by passing the stack type and binary name to `fbt`: -`./fbt updater_package COMPACT=1 DEBUG=0 COPRO_OB_DATA=scripts/ob_custradio.data COPRO_STACK_BIN=stm32wb5x_BLE_Stack_full_fw.bin COPRO_STACK_TYPE=ble_full` +`./fbt updater_package COMPACT=1 DEBUG=0 COPRO_OB_DATA=scripts/ob_custradio.data COPRO_STACK_BIN=stm32wb5x_BLE_Stack_full_fw.bin COPRO_STACK_TYPE=ble_full` -Note that `COPRO_OB_DATA` must point to a valid file in `scripts` folder containing reference Option Byte data matching to your radio stack type. +Note that `COPRO_OB_DATA` must point to a valid file in the `scripts` folder containing reference Option Byte data matching your radio stack type. In certain cases, you might have to confirm your intentions by adding `COPRO_DISCLAIMER=...` to the build command line. - ## Building partial update packages -You can customize package contents by calling `scripts/update.py` directly. +You can customize package contents by calling `scripts/update.py` directly. For example, to build a package only for installing BLE FULL stack: ```shell scripts/update.py generate \ - -t f7 -d r13.3_full -v "BLE FULL 13.3" \ - --stage dist/f7/flipper-z-f7-updater-*.bin \ - --radio lib/STM32CubeWB/Projects/STM32WB_Copro_Wireless_Binaries/STM32WB5x/stm32wb5x_BLE_Stack_full_fw.bin \ - --radiotype ble_full + -t f7 -d r13.3_full -v "BLE FULL 13.3" \ + --stage dist/f7/flipper-z-f7-updater-*.bin \ + --radio lib/STM32CubeWB/Projects/STM32WB_Copro_Wireless_Binaries/STM32WB5x/stm32wb5x_BLE_Stack_full_fw.bin \ + --radiotype ble_full ``` -For full list of options, check `scripts/update.py generate` help. +For the full list of options, check `scripts/update.py generate` help. diff --git a/documentation/RoadMap.md b/documentation/RoadMap.md index 612e55e6296..658bb20862c 100644 --- a/documentation/RoadMap.md +++ b/documentation/RoadMap.md @@ -1,9 +1,8 @@ # RoadMap -# Where we are (0.x.x branch) +# Where we are now (0.x.x branch) -Our goal for 0.x.x branch is to build stable, usable apps and API. -The first public release in this branch is 0.43.1. +Our goal for the 0.x.x branch is to build an API and apps that are stable and usable. The first public release in this branch is 0.43.1. ## What's already implemented @@ -17,12 +16,12 @@ The first public release in this branch is 0.43.1. - SubGhz: all most common protocols, reading RAW for everything else - 125kHz RFID: all most common protocols -- NFC: reading/emulating Mifare Ultralight, reading MiFare Classic and DESFire, basic EMV, basic NFC-B/F/V +- NFC: reading/emulating MIFARE Ultralight, reading MIFARE Classic and DESFire, basic EMV, and basic NFC-B/F/V - Infrared: all most common RC protocols, RAW format for everything else - GPIO: UART bridge, basic GPIO controls -- iButton: DS1990, Cyfral, Metacom -- Bad USB: Full USB Rubber Ducky support, some extras for windows alt codes -- U2F: Full U2F specification support +- iButton: DS1990, Cyfral, Metakom +- Bad USB: full USB Rubber Ducky support, some extras for Windows Alt codes +- U2F: full U2F specification support **External applications** @@ -42,8 +41,6 @@ The main goal for 1.0.0 is to provide the first stable version for both Users an ## When will it happen, and where can I see the progress? -Release 1.0.0 will most likely happen around the end of 2023Q1 +Release 1.0.0 will likely happen around the end of 2023Q1. -Development progress can be tracked in our public Miro board: - -https://miro.com/app/board/uXjVO_3D6xU=/?moveToWidget=3458764522498020058&cot=14 +You can track the development progress in our public Miro board: https://miro.com/app/board/uXjVO_3D6xU=/?moveToWidget=3458764522498020058&cot=14 diff --git a/documentation/UnitTests.md b/documentation/UnitTests.md index 896426567dd..d38d4c4b197 100644 --- a/documentation/UnitTests.md +++ b/documentation/UnitTests.md @@ -1,49 +1,65 @@ # Unit tests + ## Intro -Unit tests are special pieces of code that apply known inputs to the feature code and check the results to see if they were correct. + +Unit tests are special pieces of code that apply known inputs to the feature code and check the results to see if they are correct. They are crucial for writing robust, bug-free code. Flipper Zero firmware includes a separate application called [unit_tests](/applications/debug/unit_tests). -It is run directly on the Flipper Zero in order to employ its hardware features and to rule out any platform-related differences. +It is run directly on Flipper devices in order to employ their hardware features and rule out any platform-related differences. -When contributing code to the Flipper Zero firmware, it is highly desirable to supply unit tests along with the proposed features. +When contributing code to the Flipper Zero firmware, it is highly desirable to supply unit tests along with the proposed features. Running existing unit tests is useful to ensure that the new code doesn't introduce any regressions. ## Running unit tests -In order to run the unit tests, follow these steps: + +To run the unit tests, follow these steps: + 1. Compile the firmware with the tests enabled: `./fbt FIRMWARE_APP_SET=unit_tests`. 2. Flash the firmware using your preferred method. 3. Copy the [assets/unit_tests](assets/unit_tests) folder to the root of your Flipper Zero's SD card. 4. Launch the CLI session and run the `unit_tests` command. -**NOTE:** To run a particular test (and skip all others), specify its name as the command argument. +**NOTE:** To run a particular test (and skip all others), specify its name as the command argument. See [test_index.c](applications/debug/unit_tests/test_index.c) for the complete list of test names. ## Adding unit tests + ### General + #### Entry point + The common entry point for all tests is the [unit_tests](applications/debug/unit_tests) application. Test-specific code is placed into an arbitrarily named subdirectory and is then called from the [test_index.c](applications/debug/unit_tests/test_index.c) source file. + #### Test assets -Some unit tests require external data in order to function. These files (commonly called assets) reside in the [assets/unit_tests](/assets/unit_tests) directory in their respective subdirectories. Asset files can be of any type (plain text, FlipperFormat(FFF), binary, etc). + +Some unit tests require external data in order to function. These files (commonly called assets) reside in the [assets/unit_tests](/assets/unit_tests) directory in their respective subdirectories. Asset files can be of any type (plain text, FlipperFormat (FFF), binary, etc.). + ### Application-specific + #### Infrared + Each infrared protocol has a corresponding set of unit tests, so it makes sense to implement one when adding support for a new protocol. -In order to add unit tests for your protocol, follow these steps: +To add unit tests for your protocol, follow these steps: + 1. Create a file named `test_.irtest` in the [assets](assets/unit_tests/infrared) directory. 2. Fill it with the test data (more on it below). 3. Add the test code to [infrared_test.c](applications/debug/unit_tests/infrared/infrared_test.c). 4. Update the [assets](assets/unit_tests/infrared) on your Flipper Zero and run the tests to see if they pass. ##### Test data format -Each unit test has 3 sections: -1. `decoder` - takes in raw signal and outputs decoded messages. + +Each unit test has three sections: + +1. `decoder` - takes in a raw signal and outputs decoded messages. 2. `encoder` - takes in decoded messages and outputs a raw signal. -3. `encoder_decoder` - takes in decoded messages, turns them into a raw signal, and then decodes again. +3. `encoder_decoder` - takes in decoded messages, turns them into a raw signal, and then decodes again. Infrared test asset files have an `.irtest` extension and are regular `.ir` files with a few additions. -Decoder input data has signal names `decoder_input_N`, where N is a test sequence number. Expected data goes under the name `decoder_expected_N`. When testing the encoder these two are switched. +Decoder input data has signal names `decoder_input_N`, where N is a test sequence number. Expected data goes under the name `decoder_expected_N`. When testing the encoder, these two are switched. -Decoded data is represented in arrays (since a single raw signal may decode to several messages). If there is only one signal, then it has to be an array of size 1. Use the existing files as syntax examples. +Decoded data is represented in arrays (since a single raw signal may be decoded into several messages). If there is only one signal, then it has to be an array of size 1. Use the existing files as syntax examples. ##### Getting raw signals + Recording raw IR signals are possible using the Flipper Zero. Launch the CLI session, run `ir rx raw`, then point the remote towards Flipper's receiver and send the signals. The raw signal data will be printed to the console in a convenient format. diff --git a/documentation/UniversalRemotes.md b/documentation/UniversalRemotes.md index 186a0e65a1f..264829e1657 100644 --- a/documentation/UniversalRemotes.md +++ b/documentation/UniversalRemotes.md @@ -1,62 +1,69 @@ # Universal Remotes + ## Televisions -Adding your TV set to the universal remote is quite straightforward. Up to 6 signals can be recorded: `Power`, `Mute`, `Vol_up`, `Vol_dn`, `Ch_next`, `Ch_prev`. Any of them can be omitted if not supported by the TV. + +Adding your TV set to the universal remote is quite straightforward. Up to 6 signals can be recorded: `Power`, `Mute`, `Vol_up`, `Vol_dn`, `Ch_next`, and `Ch_prev`. Any of them can be omitted if not supported by your TV. Each signal is recorded using the following algorithm: + 1. Get the remote and point it to Flipper's IR receiver. 2. Start learning a new remote if it's the first button or press `+` to add a new button otherwise. 3. Press a remote button and save it under a corresponding name. 4. Repeat steps 2-3 until all required signals are saved. -The signal names are self-explanatory. Don't forget to make sure that every recorded signal does what it's supposed to. +The signal names are self-explanatory. Remember to make sure that every recorded signal does what it's supposed to. If everything checks out, append these signals **to the end** of the [TV universal remote file](/assets/resources/infrared/assets/tv.ir). -## Audio Players -Adding your audio player to the universal remote is done in the same manner as described above. Up to 8 signals can be recorded: `Power`, `Play`, `Pause`, `Vol_up`, `Vol_dn`, `Next`, `Prev`, `Mute`. Any of them can be omitted if not supported by the player. +## Audio players + +Adding your audio player to the universal remote is done in the same manner as described above. Up to 8 signals can be recorded: `Power`, `Play`, `Pause`, `Vol_up`, `Vol_dn`, `Next`, `Prev`, and `Mute`. Any of them can be omitted if not supported by the player. -The signal names are self-explanatory. -On many remotes, the `Play` button doubles as `Pause`. In this case record it as `Play` omitting the `Pause`. +The signal names are self-explanatory. +On many remotes, the `Play` button doubles as `Pause`. In this case, record it as `Play` omitting the `Pause`. Make sure that every signal does what it's supposed to. -If everything checks out, append these signals **to the end** of the [Audio players universal remote file](/assets/resources/infrared/assets/audio.ir). +If everything checks out, append these signals **to the end** of the [audio player universal remote file](/assets/resources/infrared/assets/audio.ir). + +## Air conditioners -## Air Conditioners Air conditioners differ from most other infrared-controlled devices because their state is tracked by the remote. -The majority of A/C remotes have a small display which shows current mode, temperature and other settings. +The majority of A/C remotes have a small display that shows the current mode, temperature, and other settings. When the user presses a button, a whole set of parameters is transmitted to the device, which must be recorded and used as a whole. -In order to add a particular air conditioner to the universal remote, 6 signals must be recorded: `Off`, `Dh`, `Cool_hi`, `Cool_lo`, `Heat_hi`, `Heat_lo`. +In order to add a particular air conditioner to the universal remote, 6 signals must be recorded: `Off`, `Dh`, `Cool_hi`, `Cool_lo`, `Heat_hi`, and `Heat_lo`. Each signal (except `Off`) is recorded using the following algorithm: 1. Get the remote and press the **Power Button** so that the display shows that A/C is ON. -2. Set the A/C to the corresponding mode (see table below), while leaving other parameters such as fan speed or vane on **AUTO** (if applicable). +2. Set the A/C to the corresponding mode (see table below), leaving other parameters such as fan speed or vane on **AUTO** (if applicable). 3. Press the **POWER** button to switch the A/C off. 4. Start learning a new remote on Flipper if it's the first button or press `+` to add a new button otherwise. 5. Point the remote to Flipper's IR receiver as directed and press **POWER** button once again. 6. Save the resulting signal under the specified name. -7. Repeat the steps 2-6 for each signal from the table below. +7. Repeat steps 2-6 for each signal from the table below. -| Signal | Mode | Temperature | Note | +| Signal | Mode | Temperature | Note | | :-----: | :--------: | :---------: | ----------------------------------- | -| Dh | Dehumidify | N/A | | -| Cool_hi | Cooling | See note | Lowest temperature in cooling mode | -| Cool_lo | Cooling | 23°C | | -| Heat_hi | Heating | See note | Highest temperature in heating mode | -| Heat_lo | Heating | 23°C | | +| Dh | Dehumidify | N/A | | +| Cool_hi | Cooling | See note | Lowest temperature in cooling mode | +| Cool_lo | Cooling | 23°C | | +| Heat_hi | Heating | See note | Highest temperature in heating mode | +| Heat_lo | Heating | 23°C | | Finally, record the `Off` signal: -1. Make sure the display shows that A/C is ON. + +1. Make sure the display shows that the A/C is ON. 2. Start learning a new signal on Flipper and point the remote towards the IR receiver. 3. Press the **POWER** button so that the remote shows the OFF state. 4. Save the resulting signal under the name `Off`. -The resulting remote file should now contain 6 signals. Any of them can be omitted, but that will mean that this functionality will not be used. +The resulting remote file should now contain 6 signals. You can omit any of them, but you then won't be able to use their functionality. Test the file against the actual device. Make sure that every signal does what it's supposed to. If everything checks out, append these signals **to the end** of the [A/C universal remote file](/assets/resources/infrared/assets/ac.ir). ## Final steps -The order of signals is not important, but they must be preceded by a following comment: `# Model: ` in order to keep the library organised. + +The order of signals is not important, but they should be preceded by the following comment: `# Model: ` in order to keep the library organized. When done, open a pull request containing the changed file. diff --git a/documentation/fbt.md b/documentation/fbt.md index 4726268d076..7b1aa8b48d7 100644 --- a/documentation/fbt.md +++ b/documentation/fbt.md @@ -5,102 +5,99 @@ It is invoked by `./fbt` in the firmware project root directory. Internally, it ## Requirements -Please install Python packages required by assets build scripts: `pip3 install -r scripts/requirements.txt` +Install Python packages required by assets build scripts: `pip3 install -r scripts/requirements.txt` ## NB -* `fbt` constructs all referenced environments & their targets' dependency trees on startup. So, to keep startup time as low as possible, we're hiding construction of certain targets behind command-line options. -* `fbt` always performs `git submodule update --init` on start, unless you set `FBT_NO_SYNC=1` in environment: - * On Windows, that's `set "FBT_NO_SYNC=1"` in the shell you're running `fbt` from - * On \*nix, it's `$ FBT_NO_SYNC=1 ./fbt ...` -* `fbt` builds updater & firmware in separate subdirectories in `build`, with their names depending on optimization settings (`COMPACT` & `DEBUG` options). However, for ease of integration with IDEs, latest built variant's directory is always linked as `built/latest`. Additionally, `compile_commands.json` is generated in that folder, which is used for code completion support in IDE. +- `fbt` constructs all referenced environments and their targets' dependency trees on startup. So, to keep startup time as low as possible, we're hiding the construction of certain targets behind command-line options. +- `fbt` always performs `git submodule update --init` on start, unless you set `FBT_NO_SYNC=1` in the environment: + - On Windows, it's `set "FBT_NO_SYNC=1"` in the shell you're running `fbt` from + - On \*nix, it's `$ FBT_NO_SYNC=1 ./fbt ...` +- `fbt` builds updater & firmware in separate subdirectories in `build`, and their names depend on optimization settings (`COMPACT` & `DEBUG` options). However, for ease of integration with IDEs, the latest built variant's directory is always linked as `built/latest`. Additionally, `compile_commands.json` is generated in that folder (used for code completion support in IDE). ## Invoking FBT -To build with FBT, call it specifying configuration options & targets to build. For example, +To build with FBT, call it and specify configuration options & targets to build. For example: `./fbt COMPACT=1 DEBUG=0 VERBOSE=1 updater_package copro_dist` -To run cleanup (think of `make clean`) for specified targets, add `-c` option. +To run cleanup (think of `make clean`) for specified targets, add the `-c` option. ## VSCode integration -`fbt` includes basic development environment configuration for VSCode. To deploy it, run `./fbt vscode_dist`. That will copy initial environment configuration to `.vscode` folder. After that, you can use that configuration by starting VSCode and choosing firmware root folder in "File > Open Folder" menu. - - * On first start, you'll be prompted to install recommended plug-ins. Please install them for best development experience. _You can find a list of them in `.vscode/extensions.json`._ - * Basic build tasks are invoked in Ctrl+Shift+B menu. - * Debugging requires a supported probe. That includes: - * Wi-Fi devboard with stock firmware (blackmagic), - * ST-Link and compatible devices, - * J-Link for flashing and debugging (in VSCode only). _Note that J-Link tools are not included with our toolchain and you have to [download](https://www.segger.com/downloads/jlink/) them yourself and put on your system's PATH._ - * Without a supported probe, you can install firmware on Flipper using USB installation method. +`fbt` includes basic development environment configuration for VS Code. Run `./fbt vscode_dist` to deploy it. That will copy the initial environment configuration to the `.vscode` folder. After that, you can use that configuration by starting VS Code and choosing the firmware root folder in the "File > Open Folder" menu. +- On the first start, you'll be prompted to install recommended plugins. We highly recommend installing them for the best development experience. _You can find a list of them in `.vscode/extensions.json`._ +- Basic build tasks are invoked in the Ctrl+Shift+B menu. +- Debugging requires a supported probe. That includes: + - Wi-Fi devboard with stock firmware (blackmagic). + - ST-Link and compatible devices. + - J-Link for flashing and debugging (in VSCode only). _Note that J-Link tools are not included with our toolchain and you have to [download](https://www.segger.com/downloads/jlink/) them yourself and put them on your system's PATH._ +- Without a supported probe, you can install firmware on Flipper using the USB installation method. ## FBT targets **`fbt`** keeps track of internal dependencies, so you only need to build the highest-level target you need, and **`fbt`** will make sure everything they depend on is up-to-date. ### High-level (what you most likely need) - -- `fw_dist` - build & publish firmware to `dist` folder. This is a default target, when no other are specified -- `fap_dist` - build external plugins & publish to `dist` folder -- `updater_package`, `updater_minpackage` - build self-update package. Minimal version only includes firmware's DFU file; full version also includes radio stack & resources for SD card -- `copro_dist` - bundle Core2 FUS+stack binaries for qFlipper -- `flash` - flash attached device with OpenOCD over ST-Link -- `flash_usb`, `flash_usb_full` - build, upload and install update package to device over USB. See details on `updater_package`, `updater_minpackage` -- `debug` - build and flash firmware, then attach with gdb with firmware's .elf loaded -- `debug_other`, `debug_other_blackmagic` - attach gdb without loading any .elf. Allows to manually add external elf files with `add-symbol-file` in gdb -- `updater_debug` - attach gdb with updater's .elf loaded -- `blackmagic` - debug firmware with Blackmagic probe (WiFi dev board) -- `openocd` - just start OpenOCD -- `get_blackmagic` - output blackmagic address in gdb remote format. Useful for IDE integration + +- `fw_dist` - build & publish firmware to the `dist` folder. This is a default target when no others are specified. +- `fap_dist` - build external plugins & publish to the `dist` folder. +- `updater_package`, `updater_minpackage` - build a self-update package. The minimal version only includes the firmware's DFU file; the full version also includes a radio stack & resources for the SD card. +- `copro_dist` - bundle Core2 FUS+stack binaries for qFlipper. +- `flash` - flash the attached device with OpenOCD over ST-Link. +- `flash_usb`, `flash_usb_full` - build, upload and install the update package to the device over USB. See details on `updater_package` and `updater_minpackage`. +- `debug` - build and flash firmware, then attach with gdb with firmware's .elf loaded. +- `debug_other`, `debug_other_blackmagic` - attach GDB without loading any `.elf`. It will allow you to manually add external `.elf` files with `add-symbol-file` in GDB. +- `updater_debug` - attach GDB with the updater's `.elf` loaded. +- `blackmagic` - debug firmware with Blackmagic probe (WiFi dev board). +- `openocd` - just start OpenOCD. +- `get_blackmagic` - output the blackmagic address in the GDB remote format. Useful for IDE integration. - `get_stlink` - output serial numbers for attached STLink probes. Used for specifying an adapter with `OPENOCD_ADAPTER_SERIAL=...`. -- `lint`, `format` - run clang-format on C source code to check and reformat it according to `.clang-format` specs -- `lint_py`, `format_py` - run [black](https://black.readthedocs.io/en/stable/index.html) on Python source code, build system files & application manifests -- `cli` - start Flipper CLI session over USB +- `lint`, `format` - run clang-format on the C source code to check and reformat it according to the `.clang-format` specs. +- `lint_py`, `format_py` - run [black](https://black.readthedocs.io/en/stable/index.html) on the Python source code, build system files & application manifests. +- `cli` - start a Flipper CLI session over USB. ### Firmware targets -- `faps` - build all external & plugin apps as [.faps](./AppsOnSDCard.md#fap-flipper-application-package). +- `faps` - build all external & plugin apps as [`.faps`](./AppsOnSDCard.md#fap-flipper-application-package). - **`fbt`** also defines per-app targets. For example, for an app with `appid=snake_game` target names are: - - `fap_snake_game`, etc - build single app as .fap by its application ID. - - Check out [`--extra-ext-apps`](#command-line-parameters) for force adding extra apps to external build - - `fap_snake_game_list`, etc - generate source + assembler listing for app's .fap -- `flash`, `firmware_flash` - flash current version to attached device with OpenOCD over ST-Link -- `jflash` - flash current version to attached device with JFlash using J-Link probe. JFlash executable must be on your $PATH -- `flash_blackmagic` - flash current version to attached device with Blackmagic probe -- `firmware_all`, `updater_all` - build basic set of binaries -- `firmware_list`, `updater_list` - generate source + assembler listing -- `firmware_cdb`, `updater_cdb` - generate `compilation_database.json` file for external tools and IDEs. It can be created without actually building the firmware. + - `fap_snake_game`, etc. - build single app as `.fap` by its application ID. + - Check out [`--extra-ext-apps`](#command-line-parameters) for force adding extra apps to external build. + - `fap_snake_game_list`, etc - generate source + assembler listing for app's `.fap`. +- `flash`, `firmware_flash` - flash the current version to the attached device with OpenOCD over ST-Link. +- `jflash` - flash the current version to the attached device with JFlash using a J-Link probe. The JFlash executable must be on your `$PATH`. +- `flash_blackmagic` - flash the current version to the attached device with a Blackmagic probe. +- `firmware_all`, `updater_all` - build a basic set of binaries. +- `firmware_list`, `updater_list` - generate source + assembler listing. +- `firmware_cdb`, `updater_cdb` - generate a `compilation_database.json` file for external tools and IDEs. It can be created without actually building the firmware. ### Assets -- `resources` - build resources and their Manifest - - `dolphin_ext` - process dolphin animations for SD card -- `icons` - generate .c+.h for icons from png assets -- `proto` - generate .pb.c+.pb.h for .proto sources -- `proto_ver` - generate .h with protobuf version -- `dolphin_internal`, `dolphin_blocking` - generate .c+.h for corresponding dolphin assets - +- `resources` - build resources and their manifest files + - `dolphin_ext` - process dolphin animations for the SD card +- `icons` - generate `.c+.h` for icons from PNG assets +- `proto` - generate `.pb.c+.pb.h` for `.proto` sources +- `proto_ver` - generate `.h` with a protobuf version +- `dolphin_internal`, `dolphin_blocking` - generate `.c+.h` for corresponding dolphin assets ## Command-line parameters -- `--options optionfile.py` (default value `fbt_options.py`) - load file with multiple configuration values -- `--extra-int-apps=app1,app2,appN` - forces listed apps to be built as internal with `firmware` target -- `--extra-ext-apps=app1,app2,appN` - forces listed apps to be built as external with `firmware_extapps` target -- `--proxy-env=VAR1,VAR2` - additional environment variables to expose to subprocesses spawned by `fbt`. By default, `fbt` sanitizes execution environment and doesn't forward all inherited environment variables. You can find list of variables that are always forwarded in `environ.scons` file. - +- `--options optionfile.py` (default value `fbt_options.py`) - load a file with multiple configuration values +- `--extra-int-apps=app1,app2,appN` - force listed apps to be built as internal with the `firmware` target +- `--extra-ext-apps=app1,app2,appN` - force listed apps to be built as external with the `firmware_extapps` target +- `--proxy-env=VAR1,VAR2` - additional environment variables to expose to subprocesses spawned by `fbt`. By default, `fbt` sanitizes the execution environment and doesn't forward all inherited environment variables. You can find the list of variables that are always forwarded in the `environ.scons` file. -## Configuration +## Configuration -Default configuration variables are set in the configuration file `fbt_options.py`. -Values set on command-line have higher precedence over configuration file. +Default configuration variables are set in the configuration file: `fbt_options.py`. +Values set in the command line have higher precedence over the configuration file. You can find out available options with `./fbt -h`. ### Firmware application set -You can create customized firmware builds by modifying the application list to be included in the build. Application presets are configured with the `FIRMWARE_APPS` option, which is a map(configuration_name:str -> application_list:tuple(str)). To specify application set to use in a build, set `FIRMWARE_APP_SET` to its name. +You can create customized firmware builds by modifying the list of applications to be included in the build. Application presets are configured with the `FIRMWARE_APPS` option, which is a `map(configuration_name:str -> application_list:tuple(str))`. To specify an application set to use in the build, set `FIRMWARE_APP_SET` to its name. For example, to build a firmware image with unit tests, run `./fbt FIRMWARE_APP_SET=unit_tests`. Check out `fbt_options.py` for details. diff --git a/documentation/file_formats/BadUsbScriptFormat.md b/documentation/file_formats/BadUsbScriptFormat.md index 713a9ff2635..fa503874293 100644 --- a/documentation/file_formats/BadUsbScriptFormat.md +++ b/documentation/file_formats/BadUsbScriptFormat.md @@ -1,16 +1,23 @@ # Command syntax -BadUsb app uses extended Duckyscript syntax. It is compatible with classic USB Rubber Ducky 1.0 scripts, but provides some additional commands and features, such as custom USB ID, ALT+Numpad input method, SYSRQ command and more functional keys. + +BadUsb app uses extended Duckyscript syntax. It is compatible with classic USB Rubber Ducky 1.0 scripts but provides some additional commands and features, such as custom USB ID, ALT+Numpad input method, SYSRQ command, and more functional keys. + # Script file format -BadUsb app can execute only text scrips from .txt files, no compilation is required. Both `\n` and `\r\n` line endings are supported. Empty lines are allowed. You can use spaces ore tabs for line indentation. + +BadUsb app can execute only text scrips from `.txt` files, no compilation is required. Both `\n` and `\r\n` line endings are supported. Empty lines are allowed. You can use spaces or tabs for line indentation. + # Command set + ## Comment line -Just a single comment line. All text after REM command will be ignored by interpreter + +Just a single comment line. The interpreter will ignore all text after the REM command. |Command|Parameters|Notes| |-|-|-| |REM|Comment text|| ## Delay -Pause script execution by defined time + +Pause script execution by a defined time. |Command|Parameters|Notes| |-|-|-| |DELAY|Delay value in ms|Single delay| @@ -18,35 +25,37 @@ Pause script execution by defined time |DEFAULTDELAY|Delay value in ms|Same as DEFAULT_DELAY| ## Special keys -|Command|Notes| -|-|-| -|DOWNARROW / DOWN|| -|LEFTARROW / LEFT|| -|RIGHTARROW / RIGHT|| -|UPARROW / UP|| -|ENTER|| -|DELETE|| -|BACKSPACE|| -|END|| -|HOME|| -|ESCAPE / ESC|| -|INSERT|| -|PAGEUP|| -|PAGEDOWN|| -|CAPSLOCK|| -|NUMLOCK|| -|SCROLLLOCK|| -|PRINTSCREEN|| -|BREAK|Pause/Break key| -|PAUSE|Pause/Break key| -|SPACE|| -|TAB|| -|MENU|Context menu key| -|APP|Same as MENU| -|Fx|F1-F12 keys| + +| Command | Notes | +| ------------------ | ---------------- | +| DOWNARROW / DOWN | | +| LEFTARROW / LEFT | | +| RIGHTARROW / RIGHT | | +| UPARROW / UP | | +| ENTER | | +| DELETE | | +| BACKSPACE | | +| END | | +| HOME | | +| ESCAPE / ESC | | +| INSERT | | +| PAGEUP | | +| PAGEDOWN | | +| CAPSLOCK | | +| NUMLOCK | | +| SCROLLLOCK | | +| PRINTSCREEN | | +| BREAK | Pause/Break key | +| PAUSE | Pause/Break key | +| SPACE | | +| TAB | | +| MENU | Context menu key | +| APP | Same as MENU | +| Fx | F1-F12 keys | ## Modifier keys -Can be combined with special key command or single character + +Can be combined with a special key command or a single character. |Command|Notes| |-|-| |CONTROL / CTRL|| @@ -58,35 +67,44 @@ Can be combined with special key command or single character |ALT-SHIFT|ALT+SHIFT| |ALT-GUI|ALT+WIN| |GUI-SHIFT|WIN+SHIFT| + ## String -|Command|Parameters|Notes| -|-|-|-| -|STRING|Text string|Print text string| + +| Command | Parameters | Notes | +| ------- | ----------- | ----------------- | +| STRING | Text string | Print text string | + ## Repeat -|Command|Parameters|Notes| -|-|-|-| -|REPEAT|Number of additional repeats|Repeat previous command| + +| Command | Parameters | Notes | +| ------- | ---------------------------- | ----------------------- | +| REPEAT | Number of additional repeats | Repeat previous command | + ## ALT+Numpad input -On Windows and some Linux systems you can print character by pressing ALT key and entering its code on numpad + +On Windows and some Linux systems, you can print characters by pressing `ALT` key and entering its code on Numpad. |Command|Parameters|Notes| |-|-|-| |ALTCHAR|Character code|Print single character| |ALTSTRING|Text string|Print text string using ALT+Numpad method| |ALTCODE|Text string|Same as ALTSTRING, presents in some Duckyscript implementations| + ## SysRq + Send [SysRq command](https://en.wikipedia.org/wiki/Magic_SysRq_key) |Command|Parameters|Notes| |-|-|-| |SYSRQ|Single character|| + ## USB device ID -You can set custom ID of Flipper USB HID device. ID command should be in the **first line** of script, it is executed before script run. -|Command|Parameters|Notes| -|-|-|-| -|ID|VID:PID Manufacturer:Product|| +You can set the custom ID of the Flipper USB HID device. ID command should be in the **first line** of script, it is executed before script run. + +| Command | Parameters | Notes | +| ------- | ---------------------------- | ----- | +| ID | VID:PID Manufacturer:Product | | Example: `ID 1234:abcd Flipper Devices:Flipper Zero` -VID and PID are hex codes and are mandatory, Manufacturer and Product are text strings and are optional. - +VID and PID are hex codes and are mandatory. Manufacturer and Product are text strings and are optional. diff --git a/documentation/file_formats/InfraredFileFormats.md b/documentation/file_formats/InfraredFileFormats.md index 409bf26e88e..3c0acdcb738 100644 --- a/documentation/file_formats/InfraredFileFormats.md +++ b/documentation/file_formats/InfraredFileFormats.md @@ -1,11 +1,12 @@ # Infrared Flipper File Formats ## Infrared Remote File Format + ### Example Filetype: IR signals file Version: 1 - # + # name: Button_1 type: parsed protocol: NECext @@ -25,48 +26,59 @@ command: 15 00 00 00 ### Description + Filename extension: `.ir` -This file format is used to store an infrared remote that consists of an arbitrary number of buttons. +This file format is used to store an infrared remote that consists of an arbitrary number of buttons. Each button is separated from others by a comment character (`#`) for better readability. -Known protocols are represented in the `parsed` form, whereas non-recognised signals may be saved and re-transmitted as `raw` data. +Known protocols are represented in the `parsed` form, whereas non-recognized signals may be saved and re-transmitted as `raw` data. #### Version history: + 1. Initial version. #### Format fields -| Name | Use | Type | Description | -| ---------- | ------- | ------ |------------ | -| name | both | string | Name of the button. Only printable ASCII characters are allowed. | -| type | both | string | Type of the signal. Must be `parsed` or `raw`. | -| protocol | parsed | string | Name of the infrared protocol. Refer to `ir` console command for the complete list of supported protocols. | -| address | parsed | hex | Payload address. Must be 4 bytes long. | -| command | parsed | hex | Payload command. Must be 4 bytes long. | -| frequency | raw | uint32 | Carrier frequency, in Hertz, usually 38000 Hz. | -| duty_cycle | raw | float | Carrier duty cycle, usually 0.33. | -| data | raw | uint32 | Raw signal timings, in microseconds between logic level changes. Individual elements must be space-separated. Maximum timings amount is 1024. | + +| Name | Use | Type | Description | +| ---------- | ------ | ------ | --------------------------------------------------------------------------------------------------------------------------------------------- | +| name | both | string | Name of the button. Only printable ASCII characters are allowed. | +| type | both | string | Type of the signal. Must be `parsed` or `raw`. | +| protocol | parsed | string | Name of the infrared protocol. Refer to `ir` console command for the complete list of supported protocols. | +| address | parsed | hex | Payload address. Must be 4 bytes long. | +| command | parsed | hex | Payload command. Must be 4 bytes long. | +| frequency | raw | uint32 | Carrier frequency, in Hertz, usually 38000 Hz. | +| duty_cycle | raw | float | Carrier duty cycle, usually 0.33. | +| data | raw | uint32 | Raw signal timings, in microseconds between logic level changes. Individual elements must be space-separated. Maximum timings amount is 1024. | ## Infrared Library File Format + ### Examples + - [TV Universal Library](/assets/resources/infrared/assets/tv.ir) - [A/C Universal Library](/assets/resources/infrared/assets/ac.ir) - [Audio Universal Library](/assets/resources/infrared/assets/audio.ir) ### Description + Filename extension: `.ir` This file format is used to store universal remote libraries. It is identical to the previous format, differing only in the `Filetype` field.\ -It also has predefined button names for each universal library type, so that the universal remote application could understand them. +It also has predefined button names for each universal library type, so that the universal remote application can understand them. See [Universal Remotes](/documentation/UniversalRemotes.md) for more information. ### Version history: + 1. Initial version. ## Infrared Test File Format + ### Examples + See [Infrared Unit Tests](/assets/unit_tests/infrared/) for various examples. + ### Description + Filename extension: `.irtest` This file format is used to store technical test data that is too large to keep directly in the firmware. @@ -78,34 +90,38 @@ Known protocols are represented in the `parsed_array` form, whereas raw data has Note: a single parsed signal must be represented as an array of size 1. ### Version history: + 1. Initial version. #### Format fields -| Name | Use | Type | Description | -| ---------- | ------------ | ------ |------------ | + +| Name | Use | Type | Description | +| ---------- | ------------ | ------ | ---------------------------------------------------------------- | | name | both | string | Name of the signal. Only printable ASCII characters are allowed. | -| type | both | string | Type of the signal. Must be `parsed_array` or `raw`. | -| count | parsed_array | uint32 | The number of parsed signals in an array. Must be at least 1. | -| protocol | parsed_array | string | Same as in previous formats. | -| address | parsed_array | hex | Ditto. | -| command | parsed_array | hex | Ditto. | -| repeat | parsed_array | bool | Indicates whether the signal is a repeated button press. | -| frequency | raw | uint32 | Same as in previous formats. | -| duty_cycle | raw | float | Ditto. | -| data | raw | uint32 | Ditto. | +| type | both | string | Type of the signal. Must be `parsed_array` or `raw`. | +| count | parsed_array | uint32 | The number of parsed signals in an array. Must be at least 1. | +| protocol | parsed_array | string | Same as in previous formats. | +| address | parsed_array | hex | Ditto. | +| command | parsed_array | hex | Ditto. | +| repeat | parsed_array | bool | Indicates whether the signal is a repeated button press. | +| frequency | raw | uint32 | Same as in previous formats. | +| duty_cycle | raw | float | Ditto. | +| data | raw | uint32 | Ditto. | #### Signal names + The signal names in an `.irtest` file follow a convention ``, where the name is one of: + - decoder_input - decoder_expected - encoder_decoder_input, -and the number is a sequential integer: 1, 2, 3...etc, which produces names like `decoder_input1`, `encoder_decoder_input3`, and so on. +and the number is a sequential integer: 1, 2, 3, etc., which produces names like `decoder_input1`, `encoder_decoder_input3`, and so on. | Name | Type | Description | -| --------------------- | ------------ |-------------------------------------------------------------------------------------------------------| -| decoder_input | raw | A raw signal containing the decoder input. Is also used as the expected encoder output. | -| decoder_expected | parsed_array | An array of parsed signals containing the expected decoder output. Is also used as the encoder input. | +| --------------------- | ------------ | ----------------------------------------------------------------------------------------------------- | +| decoder_input | raw | A raw signal containing the decoder input. Also used as the expected encoder output. | +| decoder_expected | parsed_array | An array of parsed signals containing the expected decoder output. Also used as the encoder input. | | encoder_decoder_input | parsed_array | An array of parsed signals containing both the encoder-decoder input and expected output. | See [Unit Tests](/documentation/UnitTests.md#infrared) for more info. diff --git a/documentation/file_formats/LfRfidFileFormat.md b/documentation/file_formats/LfRfidFileFormat.md index 715c49f6ada..5143d8bc1e7 100644 --- a/documentation/file_formats/LfRfidFileFormat.md +++ b/documentation/file_formats/LfRfidFileFormat.md @@ -1,17 +1,19 @@ # LF RFID key file format ## Example + ``` Filetype: Flipper RFID key Version: 1 Key type: EM4100 Data: 01 23 45 67 89 ``` + ## Description Filename extension: `.rfid` -The file stores single RFID key of type defined by `Key type` parameter +The file stores a single RFID key of the type defined by the `Key type` parameter. ### Version history @@ -19,29 +21,29 @@ The file stores single RFID key of type defined by `Key type` parameter ### Format fields -|Name|Description| -|-|-| -|Key type|Key protocol type| -|Data|Key data (HEX values)| +| Name | Description | +| -------- | --------------------- | +| Key type | Key protocol type | +| Data | Key data (HEX values) | ### Supported key types -|Type|Full name| -|-|-| -|EM4100|EM-Micro EM4100| -|H10301|HID H10301| -|Idteck|IDTECK| -|Indala26|Motorola Indala26| -|IOProxXSF|Kantech IOProxXSF| -|AWID|AWID| -|FDX-A|FECAVA FDX-A| -|FDX-B|ISO FDX-B| -|HIDProx|Generic HIDProx| -|HIDExt|Generic HIDExt| -|Pyramid|Farpointe Pyramid| -|Viking|Viking| -|Jablotron|Jablotron| -|Paradox|Paradox| -|PAC/Stanley|PAC/Stanley| -|Keri|Keri| -|Gallagher|Gallagher| \ No newline at end of file +| Type | Full name | +| ----------- | ----------------- | +| EM4100 | EM-Micro EM4100 | +| H10301 | HID H10301 | +| Idteck | IDTECK | +| Indala26 | Motorola Indala26 | +| IOProxXSF | Kantech IOProxXSF | +| AWID | AWID | +| FDX-A | FECAVA FDX-A | +| FDX-B | ISO FDX-B | +| HIDProx | Generic HIDProx | +| HIDExt | Generic HIDExt | +| Pyramid | Farpointe Pyramid | +| Viking | Viking | +| Jablotron | Jablotron | +| Paradox | Paradox | +| PAC/Stanley | PAC/Stanley | +| Keri | Keri | +| Gallagher | Gallagher | diff --git a/documentation/file_formats/NfcFileFormats.md b/documentation/file_formats/NfcFileFormats.md index e04f3b68ce9..78c6420ee03 100644 --- a/documentation/file_formats/NfcFileFormats.md +++ b/documentation/file_formats/NfcFileFormats.md @@ -19,9 +19,9 @@ This file format is used to store the UID, SAK and ATQA of a NFC-A device. It do Version differences: - 1. Initial version, deprecated - 2. LSB ATQA (e.g. 4400 instead of 0044) - 3. MSB ATQA (current version) +1. Initial version, deprecated +2. LSB ATQA (e.g. 4400 instead of 0044) +3. MSB ATQA (current version) UID can be either 4 or 7 bytes long. ATQA is 2 bytes long. SAK is 1 byte long. @@ -68,13 +68,13 @@ This file format is used to store the UID, SAK and ATQA of a Mifare Ultralight/N The "Signature" field contains the reply of the tag to the READ_SIG command. More on that can be found here: (page 31) -The "Mifare version" field is not related to the file format version, but to the Mifare Ultralight version. It contains the response of the tag to the GET_VERSION command. More on that can be found here: (page 21) +The "Mifare version" field is not related to the file format version but to the Mifare Ultralight version. It contains the response of the tag to the GET_VERSION command. More on that can be found here: (page 21) -Other fields are the direct representation of the card's internal state, more on them can be found in the same datasheet. +Other fields are the direct representation of the card's internal state. Learn more about them in the same datasheet. Version differences: - 1. Current version +1. Current version ## Mifare Classic @@ -126,7 +126,7 @@ This file format is used to store the NFC-A and Mifare Classic specific data of Version differences: - 1. Initial version, has Key A and Key B masks instead of marking unknown data with '??'. +1. Initial version, has Key A and Key B masks instead of marking unknown data with '??'. Example: @@ -137,8 +137,8 @@ Example: Key B map: 000000000000FFFF # Mifare Classic blocks ... - - 2. Current version + +2. Current version ## Mifare DESFire @@ -200,7 +200,7 @@ This file format is used to store the NFC-A and Mifare DESFire specific data of hf mfdes write --aid 123456 --fid 01 -d 1337 Version differences: - None, there are no versions yet. +None, there are no versions yet. ## Mifare Classic Dictionary @@ -252,4 +252,4 @@ This file stores a list of EMV currency codes, country codes, or AIDs and their Version differences: - 1. Initial version +1. Initial version diff --git a/documentation/file_formats/SubGhzFileFormats.md b/documentation/file_formats/SubGhzFileFormats.md index 1446ecd0d91..26863f56424 100644 --- a/documentation/file_formats/SubGhzFileFormats.md +++ b/documentation/file_formats/SubGhzFileFormats.md @@ -2,7 +2,7 @@ ## `.sub` File Format -Flipper uses `.sub` files to store SubGhz transmissions. They are text files in Flipper File Format. `.sub` files can contain either a SubGhz Key with a certain protocol or SubGhz RAW data. +Flipper uses `.sub` files to store SubGhz transmissions. These are text files in Flipper File Format. `.sub` files can contain either a SubGhz Key with a certain protocol or SubGhz RAW data. A `.sub` files consist of 3 parts: @@ -10,36 +10,36 @@ A `.sub` files consist of 3 parts: - **preset information**: preset type and, in case of a custom preset, transceiver configuration data - **protocol and its data**: contains protocol name and its specific data, such as key, bit length, etc., or RAW data -Flipper's SubGhz subsystem uses presets to configure radio transceiver. Presets are used to configure modulation, bandwidth, filters, etc. There are several presets available in stock firmware, and there is a way to create custom presets. See [SubGhz Presets](#adding-a-custom-preset) for more details. +Flipper's SubGhz subsystem uses presets to configure the radio transceiver. Presets are used to configure modulation, bandwidth, filters, etc. There are several presets available in stock firmware, and there is a way to create custom presets. See [SubGhz Presets](#adding-a-custom-preset) for more details. -## Header Format +## Header format Header is a mandatory part of `.sub` file. It contains file type, version, and frequency. -| Field | Type | Description | -| --- | --- | --- | -| `Filetype` | string | Filetype of subghz file format, must be `Flipper SubGhz Key File` | -| `Version` | uint | Version of subghz file format, current version is 1 | -| `Frequency` | uint | Frequency in Hertz | +| Field | Type | Description | +| ----------- | ------ | ----------------------------------------------------------------- | +| `Filetype` | string | Filetype of subghz file format, must be `Flipper SubGhz Key File` | +| `Version` | uint | Version of subghz file format, current version is 1 | +| `Frequency` | uint | Frequency in Hertz | -## Preset Information +## Preset information -Preset information is a mandatory part of `.sub` file. It contains preset type and, in case of custom preset, transceiver configuration data. +Preset information is a mandatory part for `.sub` files. It contains preset type and, in case of custom preset, transceiver configuration data. -When using one of the standard presets, only `Preset` field is required. When using custom preset, `Custom_preset_module` and `Custom_preset_data` fields are required. +When using one of the standard presets, only `Preset` field is required. When using a custom preset, `Custom_preset_module` and `Custom_preset_data` fields are required. -| Field | Description | -| --- | --- | -| `Preset` | Radio preset name (configures modulation, bandwidth, filters, etc.). When using a custom preset, must be `FuriHalSubGhzPresetCustom` | -| `Custom_preset_module` | Transceiver identifier, `CC1101` for Flipper Zero | -| `Custom_preset_data` | Transceiver configuration data | +| Field | Description | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | +| `Preset` | Radio preset name (configures modulation, bandwidth, filters, etc.). When using a custom preset, must be `FuriHalSubGhzPresetCustom` | +| `Custom_preset_module` | Transceiver identifier, `CC1101` for Flipper Zero | +| `Custom_preset_data` | Transceiver configuration data | Built-in presets: -- `FuriHalSubGhzPresetOok270Async` - On/Off Keying, 270kHz bandwidth, async(IO throw GP0) -- `FuriHalSubGhzPresetOok650Async` - On/Off Keying, 650kHz bandwidth, async(IO throw GP0) -- `FuriHalSubGhzPreset2FSKDev238Async` - 2 Frequency Shift Keying, deviation 2kHz, 270kHz bandwidth, async(IO throw GP0) -- `FuriHalSubGhzPreset2FSKDev476Async` - 2 Frequency Shift Keying, deviation 47kHz, 270kHz bandwidth, async(IO throw GP0) +- `FuriHalSubGhzPresetOok270Async` — On/Off Keying, 270kHz bandwidth, async(IO throw GP0) +- `FuriHalSubGhzPresetOok650Async` — On/Off Keying, 650kHz bandwidth, async(IO throw GP0) +- `FuriHalSubGhzPreset2FSKDev238Async` — 2 Frequency Shift Keying, deviation 2kHz, 270kHz bandwidth, async(IO throw GP0) +- `FuriHalSubGhzPreset2FSKDev476Async` — 2 Frequency Shift Keying, deviation 47kHz, 270kHz bandwidth, async(IO throw GP0) ### Transceiver Configuration Data @@ -50,7 +50,7 @@ Transceiver configuration data is a string of bytes, encoded in hex format, sepa - 00 00: marks register block end, - `ZZ ZZ ZZ ZZ ZZ ZZ ZZ ZZ`: 8 byte PA table (Power amplifier ramp table). -More details can be found in [CC1101 datasheet](https://www.ti.com/lit/ds/symlink/cc1101.pdf) and `furi_hal_subghz` code. +You can find more details in the [CC1101 datasheet](https://www.ti.com/lit/ds/symlink/cc1101.pdf) and `furi_hal_subghz` code. ## File Data @@ -59,9 +59,9 @@ More details can be found in [CC1101 datasheet](https://www.ti.com/lit/ds/symlin ### Key Files `.sub` files with key data files contain protocol name and its specific data, such as key value, bit length, etc. -Check out protocol registry for full list of supported protocol names. +Check out the protocol registry for the full list of supported protocol names. -Example of key data block in Princeton format: +Example of a key data block in Princeton format: ``` ... @@ -74,7 +74,7 @@ TE: 400 Protocol-specific fields in this example: | Field | Description | -| --- | --- | +| ----- | --------------------------------- | | `Bit` | Princeton payload length, in bits | | `Key` | Princeton payload data | | `TE` | Princeton quantization interval | @@ -83,25 +83,23 @@ This file may contain additional fields, more details on available fields can be ### RAW Files -RAW `.sub` files contain raw signal data that is not processed through protocol-specific decoding. These files are useful for testing purposes, or for sending data that is not supported by any known protocol. +RAW `.sub` files contain raw signal data that is not processed through protocol-specific decoding. These files are useful for testing or sending data not supported by any known protocol. For RAW files, 2 fields are required: - * `Protocol`, must be `RAW` - * `RAW_Data`, contains an array of timings, specified in micro seconds. Values must be non-zero, start with a positive number, and interleaved (change sign with each value). Up to 512 values per line. Can be specified multiple times to store multiple lines of data. +- `Protocol`, must be `RAW` +- `RAW_Data`, contains an array of timings, specified in microseconds Values must be non-zero, start with a positive number, and interleaved (change sign with each value). Up to 512 values per line. Can be specified multiple times to store multiple lines of data. Example of RAW data: Protocol: RAW RAW_Data: 29262 361 -68 2635 -66 24113 -66 11 ... +Long payload not fitting into internal memory buffer and consisting of short duration timings (< 10us) may not be read fast enough from the SD card. That might cause the signal transmission to stop before reaching the end of the payload. Ensure that your SD Card has good performance before transmitting long or complex RAW payloads. -Long payload not fitting into internal memory buffer and consisting of short duration timings (<10us) may not be read fast enough from SD Card. That might cause signal transmission to stop before reaching the end of the payload. Ensure that your SD Card has good performance before transmitting long or complex RAW payloads. +## File examples - -## File Examples - -### Key File, Standard Preset +### Key file, standard preset Filetype: Flipper SubGhz Key File Version: 1 @@ -112,8 +110,7 @@ Long payload not fitting into internal memory buffer and consisting of short dur Key: 00 00 00 00 00 95 D5 D4 TE: 400 - -### Key File, Custom Preset +### Key file, custom preset Filetype: Flipper SubGhz Key File Version: 1 @@ -126,7 +123,7 @@ Long payload not fitting into internal memory buffer and consisting of short dur Key: 00 00 00 00 00 95 D5 D4 TE: 400 -### RAW File, Standard Preset +### RAW file, standard preset Filetype: Flipper SubGhz RAW File Version: 1 @@ -137,7 +134,7 @@ Long payload not fitting into internal memory buffer and consisting of short dur RAW_Data: -424 205 -412 159 -412 381 -240 181 ... RAW_Data: -1448 361 -17056 131 -134 233 -1462 131 -166 953 -100 ... -### RAW File, Custom Preset +### RAW file, custom preset Filetype: Flipper SubGhz RAW File Version: 1 @@ -150,26 +147,26 @@ Long payload not fitting into internal memory buffer and consisting of short dur RAW_Data: -424 205 -412 159 -412 381 -240 181 ... RAW_Data: -1448 361 -17056 131 -134 233 -1462 131 -166 953 -100 ... -# SubGhz Configuration Files +# SubGhz configuration files SubGhz application provides support for adding extra radio presets and additional keys for decoding transmissions in certain protocols. -## SubGhz `keeloq_mfcodes_user` File +## SubGhz `keeloq_mfcodes_user` file This file contains additional manufacturer keys for Keeloq protocol. It is used to decode Keeloq transmissions. This file is loaded at subghz application start and is located at path `/ext/subghz/assets/keeloq_mfcodes_user`. -### File Format +### File format File contains a header and a list of manufacturer keys. File header format: -| Field | Type | Description | -| --- | | --- | -| `Filetype` | string | SubGhz Keystore file format, always `Flipper SubGhz Keystore File` | -| `Version` | uint | File format version, 0 | -| `Encryption` | uint | File encryption: for user-provided file, set to 0 (disabled) | +| Field | Type | Description | +| ------------ | ------ | ------------------------------------------------------------------ | +| `Filetype` | string | SubGhz Keystore file format, always `Flipper SubGhz Keystore File` | +| `Version` | uint | File format version, 0 | +| `Encryption` | uint | File encryption: for user-provided file, set to 0 (disabled) | Following the header, file contains a list of user-provided manufacture keys, one key per line. For each key, a name and encryption method must be specified, according to comment in file header. More information can be found in keeloq decoder source code. @@ -186,7 +183,7 @@ For each key, a name and encryption method must be specified, according to comme # - 2 - Normal_Learning # - 3 - Secure_Learning # - 4 - Magic_xor_type1 Learning - # + # # NAME - name (string without spaces) max 64 characters long Filetype: Flipper SubGhz Keystore File Version: 0 @@ -194,45 +191,44 @@ For each key, a name and encryption method must be specified, according to comme AABBCCDDEEFFAABB:1:Test1 AABBCCDDEEFFAABB:1:Test2 - -## SubGhz `setting_user` File +## SubGhz `setting_user` file This file contains additional radio presets and frequencies for SubGhz application. It is used to add new presets and frequencies for existing presets. This file is being loaded on subghz application start and is located at path `/ext/subghz/assets/setting_user`. -### File Format +### File format File contains a header, basic options, and optional lists of presets and frequencies. -Header must contain following fields: +Header must contain the following fields: - `Filetype`: SubGhz setting file format, must be `Flipper SubGhz Setting File`. - `Version`: file format version, current is `1`. -#### Basic Settings +#### Basic settings - `Add_standard_frequencies`: bool, flag indicating whether to load standard frequencies shipped with firmware. If set to `false`, only frequencies specified in this file will be used. - `Default_frequency`: uint, default frequency used in SubGhz application. -#### Adding More Frequencies +#### Adding more frequencies - `Frequency`: uint — additional frequency for the subghz application frequency list. Used in Read and Read RAW. You can specify multiple frequencies, one per line. -#### Adding More Hopper Frequencies +#### Adding more hopper frequencies - `Hopper_frequency`: uint — additional frequency for subghz application frequency hopping. Used in Frequency Analyzer. You can specify multiple frequencies, one per line. -Repeating same frequency will cause Flipper to listen on this frequency more often. +Repeating the same frequency will cause Flipper to listen to this frequency more often. #### Adding a Custom Preset You can have as many presets as you want. Presets are embedded into `.sub` files, so another Flipper can load them directly from that file. -Each preset is defined by following fields: +Each preset is defined by the following fields: -| Field | Description | -| --- | --- | -| `Custom_preset_name` | string, preset name that will be shown in SubGHz application | -| `Custom_preset_module` | string, transceiver identifier. Set to `CC1101` for Flipper Zero | -| `Custom_preset_data` | transceiver configuration data. See [Transceiver Configuration Data](#transceiver-configuration-data) for details. | +| Field | Description | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------ | +| `Custom_preset_name` | string, preset name that will be shown in SubGHz application | +| `Custom_preset_module` | string, transceiver identifier. Set to `CC1101` for Flipper Zero | +| `Custom_preset_data` | transceiver configuration data. See [Transceiver Configuration Data](#transceiver-configuration-data) for details. | ### Example @@ -252,7 +248,7 @@ Frequency: 300000000 Frequency: 310000000 Frequency: 320000000 -# Frequencies used for hopping mode (keep this list small or flipper will miss signal) +# Frequencies used for hopping mode (keep this list small or Flipper will miss the signal) Hopper_frequency: 300000000 Hopper_frequency: 310000000 Hopper_frequency: 310000000 diff --git a/documentation/file_formats/iButtonFileFormat.md b/documentation/file_formats/iButtonFileFormat.md index a5d41b49556..c04586195b9 100644 --- a/documentation/file_formats/iButtonFileFormat.md +++ b/documentation/file_formats/iButtonFileFormat.md @@ -1,6 +1,7 @@ # iButton key file format ## Example + ``` Filetype: Flipper iButton key Version: 1 @@ -9,11 +10,12 @@ Key type: Dallas # Data size for Cyfral is 2, for Metakom is 4, for Dallas is 8 Data: 12 34 56 78 9A BC DE F0 ``` + ## Description Filename extension: `.ibtn` -The file stores single iButton key of type defined by `Key type` parameter +The file stores a single iButton key of the type defined by the `Key type` parameter. ### Version history @@ -21,7 +23,7 @@ The file stores single iButton key of type defined by `Key type` parameter ### Format fields -|Name|Description| -|-|-| -|Key type|Currently supported: Cyfral, Dallas, Metakom| -|Data|Key data (HEX values)| \ No newline at end of file +| Name | Description | +| -------- | -------------------------------------------- | +| Key type | Currently supported: Cyfral, Dallas, Metakom | +| Data | Key data (HEX values) | From 2c450bd835dffa18dfa6662d658c3f75079e9b92 Mon Sep 17 00:00:00 2001 From: Maksim Derbasov Date: Sat, 7 Jan 2023 04:28:28 +0900 Subject: [PATCH 342/824] Show region information in sub-GHz app (#2249) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Show region info in sub-GHz app * SubGhz: reset widget on region info scene exit * Format sources Co-authored-by: あく --- .../main/nfc/scenes/nfc_scene_start.c | 6 +-- .../main/subghz/scenes/subghz_scene_config.h | 1 + .../subghz/scenes/subghz_scene_region_info.c | 39 +++++++++++++++++++ .../main/subghz/scenes/subghz_scene_start.c | 20 ++++++++-- .../picopass/scenes/picopass_scene_start.c | 2 +- 5 files changed, 60 insertions(+), 8 deletions(-) create mode 100644 applications/main/subghz/scenes/subghz_scene_region_info.c diff --git a/applications/main/nfc/scenes/nfc_scene_start.c b/applications/main/nfc/scenes/nfc_scene_start.c index f8b9f52c630..2a116fe0911 100644 --- a/applications/main/nfc/scenes/nfc_scene_start.c +++ b/applications/main/nfc/scenes/nfc_scene_start.c @@ -7,7 +7,7 @@ enum SubmenuIndex { SubmenuIndexDetectReader, SubmenuIndexSaved, SubmenuIndexExtraAction, - SubmenuIndexAddManualy, + SubmenuIndexAddManually, SubmenuIndexDebug, }; @@ -28,7 +28,7 @@ void nfc_scene_start_on_enter(void* context) { submenu_add_item( submenu, "Extra Actions", SubmenuIndexExtraAction, nfc_scene_start_submenu_callback, nfc); submenu_add_item( - submenu, "Add Manually", SubmenuIndexAddManualy, nfc_scene_start_submenu_callback, nfc); + submenu, "Add Manually", SubmenuIndexAddManually, nfc_scene_start_submenu_callback, nfc); if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { submenu_add_item( @@ -68,7 +68,7 @@ bool nfc_scene_start_on_event(void* context, SceneManagerEvent event) { } else if(event.event == SubmenuIndexExtraAction) { scene_manager_next_scene(nfc->scene_manager, NfcSceneExtraActions); consumed = true; - } else if(event.event == SubmenuIndexAddManualy) { + } else if(event.event == SubmenuIndexAddManually) { scene_manager_next_scene(nfc->scene_manager, NfcSceneSetType); consumed = true; } else if(event.event == SubmenuIndexDebug) { diff --git a/applications/main/subghz/scenes/subghz_scene_config.h b/applications/main/subghz/scenes/subghz_scene_config.h index fd1271c8c73..86a30731792 100644 --- a/applications/main/subghz/scenes/subghz_scene_config.h +++ b/applications/main/subghz/scenes/subghz_scene_config.h @@ -23,3 +23,4 @@ ADD_SCENE(subghz, more_raw, MoreRAW) ADD_SCENE(subghz, delete_raw, DeleteRAW) ADD_SCENE(subghz, need_saving, NeedSaving) ADD_SCENE(subghz, rpc, Rpc) +ADD_SCENE(subghz, region_info, RegionInfo) diff --git a/applications/main/subghz/scenes/subghz_scene_region_info.c b/applications/main/subghz/scenes/subghz_scene_region_info.c new file mode 100644 index 00000000000..82486314d90 --- /dev/null +++ b/applications/main/subghz/scenes/subghz_scene_region_info.c @@ -0,0 +1,39 @@ +#include "../subghz_i.h" + +#include + +void subghz_scene_region_info_on_enter(void* context) { + SubGhz* subghz = context; + const FuriHalRegion* const region = furi_hal_region_get(); + FuriString* buffer; + buffer = furi_string_alloc(); + if(region) { + furi_string_cat_printf(buffer, "Region: %s, bands:\n", region->country_code); + for(uint16_t i = 0; i < region->bands_count; ++i) { + furi_string_cat_printf( + buffer, + " %lu-%lu kHz\n", + region->bands[i].start / 1000, + region->bands[i].end / 1000); + } + } else { + furi_string_cat_printf(buffer, "Region: N/A\n"); + } + + widget_add_string_multiline_element( + subghz->widget, 0, 0, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(buffer)); + + furi_string_free(buffer); + view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdWidget); +} + +bool subghz_scene_region_info_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + UNUSED(event); + return false; +} + +void subghz_scene_region_info_on_exit(void* context) { + SubGhz* subghz = context; + widget_reset(subghz->widget); +} diff --git a/applications/main/subghz/scenes/subghz_scene_start.c b/applications/main/subghz/scenes/subghz_scene_start.c index 0b1c3c159fd..a50f73a810f 100644 --- a/applications/main/subghz/scenes/subghz_scene_start.c +++ b/applications/main/subghz/scenes/subghz_scene_start.c @@ -5,9 +5,10 @@ enum SubmenuIndex { SubmenuIndexRead = 10, SubmenuIndexSaved, SubmenuIndexTest, - SubmenuIndexAddManualy, + SubmenuIndexAddManually, SubmenuIndexFrequencyAnalyzer, SubmenuIndexReadRAW, + SubmenuIndexShowRegionInfo }; void subghz_scene_start_submenu_callback(void* context, uint32_t index) { @@ -33,7 +34,7 @@ void subghz_scene_start_on_enter(void* context) { submenu_add_item( subghz->submenu, "Add Manually", - SubmenuIndexAddManualy, + SubmenuIndexAddManually, subghz_scene_start_submenu_callback, subghz); submenu_add_item( @@ -42,6 +43,12 @@ void subghz_scene_start_on_enter(void* context) { SubmenuIndexFrequencyAnalyzer, subghz_scene_start_submenu_callback, subghz); + submenu_add_item( + subghz->submenu, + "Region Information", + SubmenuIndexShowRegionInfo, + subghz_scene_start_submenu_callback, + subghz); if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { submenu_add_item( subghz->submenu, "Test", SubmenuIndexTest, subghz_scene_start_submenu_callback, subghz); @@ -76,9 +83,9 @@ bool subghz_scene_start_on_event(void* context, SceneManagerEvent event) { subghz->scene_manager, SubGhzSceneStart, SubmenuIndexSaved); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaved); return true; - } else if(event.event == SubmenuIndexAddManualy) { + } else if(event.event == SubmenuIndexAddManually) { scene_manager_set_scene_state( - subghz->scene_manager, SubGhzSceneStart, SubmenuIndexAddManualy); + subghz->scene_manager, SubGhzSceneStart, SubmenuIndexAddManually); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSetType); return true; } else if(event.event == SubmenuIndexFrequencyAnalyzer) { @@ -92,6 +99,11 @@ bool subghz_scene_start_on_event(void* context, SceneManagerEvent event) { subghz->scene_manager, SubGhzSceneStart, SubmenuIndexTest); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneTest); return true; + } else if(event.event == SubmenuIndexShowRegionInfo) { + scene_manager_set_scene_state( + subghz->scene_manager, SubGhzSceneStart, SubmenuIndexShowRegionInfo); + scene_manager_next_scene(subghz->scene_manager, SubGhzSceneRegionInfo); + return true; } } return false; diff --git a/applications/plugins/picopass/scenes/picopass_scene_start.c b/applications/plugins/picopass/scenes/picopass_scene_start.c index 76c18a22a5d..6d1aeedcddb 100644 --- a/applications/plugins/picopass/scenes/picopass_scene_start.c +++ b/applications/plugins/picopass/scenes/picopass_scene_start.c @@ -3,7 +3,7 @@ enum SubmenuIndex { SubmenuIndexRead, SubmenuIndexRunScript, SubmenuIndexSaved, - SubmenuIndexAddManualy, + SubmenuIndexAddManually, SubmenuIndexDebug, }; From b7046b21792995a5b7bc43c9ad8fd7b66dddc7d3 Mon Sep 17 00:00:00 2001 From: Liam Droog <61334243+LiamDroog@users.noreply.github.com> Date: Sun, 8 Jan 2023 23:03:51 -0700 Subject: [PATCH 343/824] Change broken BadUSB link to Hak5 duckyscript quick reference webpage (#2270) * Change broken link to Hak5 duckyscript quick reference webpage * Update BadUSB syntax link --- assets/resources/badusb/demo_macos.txt | 2 +- assets/resources/badusb/demo_windows.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/resources/badusb/demo_macos.txt b/assets/resources/badusb/demo_macos.txt index 3c21a4df81a..82543b28fd9 100644 --- a/assets/resources/badusb/demo_macos.txt +++ b/assets/resources/badusb/demo_macos.txt @@ -79,7 +79,7 @@ STRING Flipper Zero BadUSB feature is compatible with USB Rubber Ducky script fo ENTER STRING More information about script syntax can be found here: ENTER -STRING https://github.com/hak5darren/USB-Rubber-Ducky/wiki/Duckyscript +STRING https://github.com/flipperdevices/flipperzero-firmware/blob/dev/documentation/file_formats/BadUsbScriptFormat.md ENTER STRING EOF diff --git a/assets/resources/badusb/demo_windows.txt b/assets/resources/badusb/demo_windows.txt index f304f5e8d57..2ed33b3c053 100644 --- a/assets/resources/badusb/demo_windows.txt +++ b/assets/resources/badusb/demo_windows.txt @@ -80,5 +80,5 @@ STRING Flipper Zero BadUSB feature is compatible with USB Rubber Ducky script fo ENTER STRING More information about script syntax can be found here: ENTER -STRING https://github.com/hak5darren/USB-Rubber-Ducky/wiki/Duckyscript +STRING https://github.com/flipperdevices/flipperzero-firmware/blob/dev/documentation/file_formats/BadUsbScriptFormat.md ENTER From 8d2143add54641a256a02db775ee37ae6aff49c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Tue, 10 Jan 2023 03:15:03 +0900 Subject: [PATCH 344/824] Fix various issues reported on github (#2280) * Desktop: Fix use after free in slideshow view * Gui: long press toggles first letter case too * Desktop: remove debug logging --- .../services/desktop/views/desktop_view_slideshow.c | 2 +- applications/services/gui/modules/text_input.c | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/applications/services/desktop/views/desktop_view_slideshow.c b/applications/services/desktop/views/desktop_view_slideshow.c index 58a8f6d0c1b..09e155341c5 100644 --- a/applications/services/desktop/views/desktop_view_slideshow.c +++ b/applications/services/desktop/views/desktop_view_slideshow.c @@ -56,7 +56,7 @@ static bool desktop_view_slideshow_input(InputEvent* event, void* context) { instance->callback(DesktopSlideshowCompleted, instance->context); } update_view = true; - } else if(event->key == InputKeyOk) { + } else if(event->key == InputKeyOk && instance->timer) { if(event->type == InputTypePress) { furi_timer_start(instance->timer, DESKTOP_SLIDESHOW_POWEROFF_SHORT); } else if(event->type == InputTypeRelease) { diff --git a/applications/services/gui/modules/text_input.c b/applications/services/gui/modules/text_input.c index 7c419d96a89..80680fe45e0 100644 --- a/applications/services/gui/modules/text_input.c +++ b/applications/services/gui/modules/text_input.c @@ -138,7 +138,7 @@ static bool char_is_lowercase(char letter) { static char char_to_uppercase(const char letter) { if(letter == '_') { return 0x20; - } else if(isalpha(letter)) { + } else if(islower(letter)) { return (letter - 0x20); } else { return letter; @@ -309,7 +309,9 @@ static void text_input_handle_ok(TextInput* text_input, TextInputModel* model, b char selected = get_selected_char(model); size_t text_length = strlen(model->text_buffer); - if(shift) { + bool toogle_case = text_length == 0; + if(shift) toogle_case = !toogle_case; + if(toogle_case) { selected = char_to_uppercase(selected); } @@ -329,9 +331,6 @@ static void text_input_handle_ok(TextInput* text_input, TextInputModel* model, b text_length = 0; } if(text_length < (model->text_buffer_size - 1)) { - if(text_length == 0 && char_is_lowercase(selected)) { - selected = char_to_uppercase(selected); - } model->text_buffer[text_length] = selected; model->text_buffer[text_length + 1] = 0; } From d2df35a35bfbffd167529dbd4b69dccf7833a225 Mon Sep 17 00:00:00 2001 From: Stephen Kent Date: Wed, 11 Jan 2023 02:13:07 -0800 Subject: [PATCH 345/824] fbt: add `fap_deploy` target to build and copy all .fap apps to flipper (#2146) * fbt: add `faps_copy` target to build and copy all .fap apps to flipper * fbt: restore default runfap.py invocation behavior, use -n for copy only * fbt: proper implementation of fap_deploy target Co-authored-by: hedger --- .vscode/example/tasks.json | 16 ++++++++++++++-- SConstruct | 8 ++++++++ scripts/fbt_tools/fbt_help.py | 2 ++ scripts/runfap.py | 20 +++++++++++++++----- 4 files changed, 39 insertions(+), 7 deletions(-) diff --git a/.vscode/example/tasks.json b/.vscode/example/tasks.json index c16c3ab4f5b..beedf8865b9 100644 --- a/.vscode/example/tasks.json +++ b/.vscode/example/tasks.json @@ -138,6 +138,18 @@ "Serial Console" ] }, + { + "label": "[Debug] Build and upload all FAPs to Flipper over USB", + "group": "build", + "type": "shell", + "command": "./fbt fap_deploy" + }, + { + "label": "[Release] Build and upload all FAPs to Flipper over USB", + "group": "build", + "type": "shell", + "command": "./fbt COMPACT=1 DEBUG=0 fap_deploy" + }, { // Press Ctrl+] to quit "label": "Serial Console", @@ -145,7 +157,7 @@ "command": "./fbt cli", "group": "none", "isBackground": true, - "options": { + "options": { "env": { "FBT_NO_SYNC": "0" } @@ -162,4 +174,4 @@ } } ] -} +} \ No newline at end of file diff --git a/SConstruct b/SConstruct index 138b52d9363..b8c65044d06 100644 --- a/SConstruct +++ b/SConstruct @@ -165,6 +165,14 @@ Alias("fap_dist", fap_dist) distenv.Depends(firmware_env["FW_RESOURCES"], firmware_env["FW_EXTAPPS"].resources_dist) +# Copy all faps to device + +fap_deploy = distenv.PhonyTarget( + "fap_deploy", + "${PYTHON3} ${ROOT_DIR}/scripts/storage.py send ${SOURCE} /ext/apps", + source=Dir("#/assets/resources/apps"), +) + # Target for bundling core2 package for qFlipper copro_dist = distenv.CoproBuilder( diff --git a/scripts/fbt_tools/fbt_help.py b/scripts/fbt_tools/fbt_help.py index 0475f51bc06..8cce9335b61 100644 --- a/scripts/fbt_tools/fbt_help.py +++ b/scripts/fbt_tools/fbt_help.py @@ -11,6 +11,8 @@ Build all FAP apps fap_{APPID}, launch_app APPSRC={APPID}: Build FAP app with appid={APPID}; upload & start it over USB + fap_deploy: + Build and upload all FAP apps over USB Flashing & debugging: flash, flash_blackmagic, jflash: diff --git a/scripts/runfap.py b/scripts/runfap.py index c2c0f78d5cb..410b3e7d2b1 100644 --- a/scripts/runfap.py +++ b/scripts/runfap.py @@ -15,6 +15,13 @@ class Main(App): def init(self): self.parser.add_argument("-p", "--port", help="CDC Port", default="auto") + self.parser.add_argument( + "-n", + "--no-launch", + dest="launch_app", + action="store_false", + help="Don't launch app", + ) self.parser.add_argument("fap_src_path", help="App file to upload") self.parser.add_argument( @@ -84,11 +91,14 @@ def install(self): self.logger.error(f"Error: upload failed: {storage.last_error}") return -3 - storage.send_and_wait_eol(f'loader open "Applications" {fap_dst_path}\r') - result = storage.read.until(storage.CLI_EOL) - if len(result): - self.logger.error(f"Unexpected response: {result.decode('ascii')}") - return -4 + if self.args.launch_app: + storage.send_and_wait_eol( + f'loader open "Applications" {fap_dst_path}\r' + ) + result = storage.read.until(storage.CLI_EOL) + if len(result): + self.logger.error(f"Unexpected response: {result.decode('ascii')}") + return -4 return 0 finally: From 20621da8ac013b1fc447e7f6f5a9e7e69c2bc913 Mon Sep 17 00:00:00 2001 From: Giacomo Ferretti Date: Wed, 11 Jan 2023 14:41:57 +0100 Subject: [PATCH 346/824] Fix typos in source code (#2285) * Fix typo in TextInput module * Fix typo in Widget comment * Fix typo in comment Co-authored-by: hedger --- applications/services/gui/modules/text_input.c | 14 +++++++------- applications/services/gui/modules/widget.h | 2 +- applications/services/input/input.h | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/applications/services/gui/modules/text_input.c b/applications/services/gui/modules/text_input.c index 80680fe45e0..32607e88423 100644 --- a/applications/services/gui/modules/text_input.c +++ b/applications/services/gui/modules/text_input.c @@ -29,7 +29,7 @@ typedef struct { TextInputValidatorCallback validator_callback; void* validator_callback_context; FuriString* validator_text; - bool valadator_message_visible; + bool validator_message_visible; } TextInputModel; static const uint8_t keyboard_origin_x = 1; @@ -254,7 +254,7 @@ static void text_input_view_draw_callback(Canvas* canvas, void* _model) { } } } - if(model->valadator_message_visible) { + if(model->validator_message_visible) { canvas_set_font(canvas, FontSecondary); canvas_set_color(canvas, ColorWhite); canvas_draw_box(canvas, 8, 10, 110, 48); @@ -319,7 +319,7 @@ static void text_input_handle_ok(TextInput* text_input, TextInputModel* model, b if(model->validator_callback && (!model->validator_callback( model->text_buffer, model->validator_text, model->validator_callback_context))) { - model->valadator_message_visible = true; + model->validator_message_visible = true; furi_timer_start(text_input->timer, furi_kernel_get_tick_frequency() * 4); } else if(model->callback != 0 && text_length > 0) { model->callback(model->callback_context); @@ -348,8 +348,8 @@ static bool text_input_view_input_callback(InputEvent* event, void* context) { TextInputModel* model = view_get_model(text_input->view); if((!(event->type == InputTypePress) && !(event->type == InputTypeRelease)) && - model->valadator_message_visible) { - model->valadator_message_visible = false; + model->validator_message_visible) { + model->validator_message_visible = false; consumed = true; } else if(event->type == InputTypeShort) { consumed = true; @@ -435,7 +435,7 @@ void text_input_timer_callback(void* context) { with_view_model( text_input->view, TextInputModel * model, - { model->valadator_message_visible = false; }, + { model->validator_message_visible = false; }, true); } @@ -495,7 +495,7 @@ void text_input_reset(TextInput* text_input) { model->validator_callback = NULL; model->validator_callback_context = NULL; furi_string_reset(model->validator_text); - model->valadator_message_visible = false; + model->validator_message_visible = false; }, true); } diff --git a/applications/services/gui/modules/widget.h b/applications/services/gui/modules/widget.h index 50c26175169..9076ce7f299 100644 --- a/applications/services/gui/modules/widget.h +++ b/applications/services/gui/modules/widget.h @@ -91,7 +91,7 @@ void widget_add_string_element( * @param[in] text Formatted text. The following formats are available: * "\e#Bold text\e#" - bold font is used * "\e*Monospaced text\e*" - monospaced font is used - * "\e#Inversed text\e#" - white text on black background + * "\e!Inversed text\e!" - white text on black background * @param strip_to_dots Strip text to ... if does not fit to width */ void widget_add_text_box_element( diff --git a/applications/services/input/input.h b/applications/services/input/input.h index ec3d09711ed..062dc0fa58e 100644 --- a/applications/services/input/input.h +++ b/applications/services/input/input.h @@ -19,7 +19,7 @@ extern "C" { typedef enum { InputTypePress, /**< Press event, emitted after debounce */ InputTypeRelease, /**< Release event, emitted after debounce */ - InputTypeShort, /**< Short event, emitted after InputTypeRelease done withing INPUT_LONG_PRESS interval */ + InputTypeShort, /**< Short event, emitted after InputTypeRelease done within INPUT_LONG_PRESS interval */ InputTypeLong, /**< Long event, emitted after INPUT_LONG_PRESS_COUNTS interval, asynchronous to InputTypeRelease */ InputTypeRepeat, /**< Repeat event, emitted with INPUT_LONG_PRESS_COUNTS period after InputTypeLong event */ InputTypeMAX, /**< Special value for exceptional */ From ad9d746a279d7856780586304e4bafb92f2b6753 Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Wed, 11 Jan 2023 16:58:51 +0300 Subject: [PATCH 347/824] BadUSB backspace/delete fix (#2288) Co-authored-by: hedger --- applications/main/bad_usb/bad_usb_script.c | 4 ++-- applications/main/gpio/scenes/gpio_scene_usb_uart_config.c | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/applications/main/bad_usb/bad_usb_script.c b/applications/main/bad_usb/bad_usb_script.c index 92c7466f158..bbd721ed2c7 100644 --- a/applications/main/bad_usb/bad_usb_script.c +++ b/applications/main/bad_usb/bad_usb_script.c @@ -71,8 +71,8 @@ static const DuckyKey ducky_keys[] = { {"BREAK", HID_KEYBOARD_PAUSE}, {"PAUSE", HID_KEYBOARD_PAUSE}, {"CAPSLOCK", HID_KEYBOARD_CAPS_LOCK}, - {"DELETE", HID_KEYBOARD_DELETE}, - {"BACKSPACE", HID_KEYPAD_BACKSPACE}, + {"DELETE", HID_KEYBOARD_DELETE_FORWARD}, + {"BACKSPACE", HID_KEYBOARD_DELETE}, {"END", HID_KEYBOARD_END}, {"ESC", HID_KEYBOARD_ESCAPE}, {"ESCAPE", HID_KEYBOARD_ESCAPE}, diff --git a/applications/main/gpio/scenes/gpio_scene_usb_uart_config.c b/applications/main/gpio/scenes/gpio_scene_usb_uart_config.c index 55b04ed67fb..776343fb003 100644 --- a/applications/main/gpio/scenes/gpio_scene_usb_uart_config.c +++ b/applications/main/gpio/scenes/gpio_scene_usb_uart_config.c @@ -14,9 +14,12 @@ static const char* uart_ch[] = {"13,14", "15,16"}; static const char* flow_pins[] = {"None", "2,3", "6,7", "16,15"}; static const char* baudrate_mode[] = {"Host"}; static const uint32_t baudrate_list[] = { + 1200, 2400, + 4800, 9600, 19200, + 28800, 38400, 57600, 115200, From 75e9de12b065bd5e572b8b9232c8b9670c8e6f91 Mon Sep 17 00:00:00 2001 From: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> Date: Fri, 13 Jan 2023 16:50:19 +0300 Subject: [PATCH 348/824] [FL-3078] Per protocol signal repeat count (#2293) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Better Infrared protocol file structure * Rename InfraredProtocolSpec to InfraredProtocolVariant * Slightly better names * Add repeat count field to protocol variant description * Repeat the signal the appropriate number of times when brute-forcing * Repeat the signal the appropriate number of times when sending via worker * Better signal count logic in infrared_transmit * Better variable names * Convert some raw signals to messages in tv.ir Co-authored-by: あく --- assets/resources/infrared/assets/tv.ir | 758 +++++++++--------- firmware/targets/f7/api_symbols.csv | 3 +- .../common/infrared_common_decoder.c | 9 +- .../common/infrared_common_encoder.c | 11 +- .../common/infrared_common_protocol_defs.c | 140 ---- lib/infrared/encoder_decoder/infrared.c | 63 +- lib/infrared/encoder_decoder/infrared.h | 10 + lib/infrared/encoder_decoder/infrared_i.h | 5 +- .../infrared_protocol_defs_i.h | 320 -------- .../kaseikyo/infrared_decoder_kaseikyo.c | 10 +- .../kaseikyo/infrared_encoder_kaseikyo.c | 8 +- .../kaseikyo/infrared_kaseikyo_spec.c | 17 - .../kaseikyo/infrared_protocol_kaseikyo.c | 40 + .../kaseikyo/infrared_protocol_kaseikyo.h | 31 + .../kaseikyo/infrared_protocol_kaseikyo_i.h | 30 + .../nec/infrared_decoder_nec.c | 11 +- .../nec/infrared_encoder_nec.c | 11 +- .../encoder_decoder/nec/infrared_nec_spec.c | 47 -- .../nec/infrared_protocol_nec.c | 74 ++ .../nec/infrared_protocol_nec.h | 30 + .../nec/infrared_protocol_nec_i.h | 29 + .../rc5/infrared_decoder_rc5.c | 13 +- .../rc5/infrared_encoder_rc5.c | 12 +- .../rc5/infrared_protocol_rc5.c | 49 ++ .../rc5/infrared_protocol_rc5.h | 38 + .../rc5/infrared_protocol_rc5_i.h | 20 + .../encoder_decoder/rc5/infrared_rc5_spec.c | 27 - .../rc6/infrared_decoder_rc6.c | 13 +- .../rc6/infrared_encoder_rc6.c | 12 +- .../rc6/infrared_protocol_rc6.c | 39 + .../rc6/infrared_protocol_rc6.h | 37 + .../rc6/infrared_protocol_rc6_i.h | 28 + .../encoder_decoder/rc6/infrared_rc6_spec.c | 17 - .../samsung/infrared_decoder_samsung.c | 10 +- .../samsung/infrared_encoder_samsung.c | 10 +- .../samsung/infrared_protocol_samsung.c | 40 + .../samsung/infrared_protocol_samsung.h | 31 + .../samsung/infrared_protocol_samsung_i.h | 35 + .../samsung/infrared_samsung_spec.c | 17 - .../sirc/infrared_decoder_sirc.c | 11 +- .../sirc/infrared_encoder_sirc.c | 9 +- .../sirc/infrared_protocol_sirc.c | 64 ++ .../sirc/infrared_protocol_sirc.h | 37 + .../sirc/infrared_protocol_sirc_i.h | 26 + .../encoder_decoder/sirc/infrared_sirc_spec.c | 37 - lib/infrared/worker/infrared_transmit.c | 3 +- lib/infrared/worker/infrared_worker.c | 50 +- 47 files changed, 1191 insertions(+), 1151 deletions(-) mode change 100755 => 100644 assets/resources/infrared/assets/tv.ir delete mode 100644 lib/infrared/encoder_decoder/common/infrared_common_protocol_defs.c delete mode 100644 lib/infrared/encoder_decoder/infrared_protocol_defs_i.h delete mode 100644 lib/infrared/encoder_decoder/kaseikyo/infrared_kaseikyo_spec.c create mode 100644 lib/infrared/encoder_decoder/kaseikyo/infrared_protocol_kaseikyo.c create mode 100644 lib/infrared/encoder_decoder/kaseikyo/infrared_protocol_kaseikyo.h create mode 100644 lib/infrared/encoder_decoder/kaseikyo/infrared_protocol_kaseikyo_i.h delete mode 100644 lib/infrared/encoder_decoder/nec/infrared_nec_spec.c create mode 100644 lib/infrared/encoder_decoder/nec/infrared_protocol_nec.c create mode 100644 lib/infrared/encoder_decoder/nec/infrared_protocol_nec.h create mode 100644 lib/infrared/encoder_decoder/nec/infrared_protocol_nec_i.h create mode 100644 lib/infrared/encoder_decoder/rc5/infrared_protocol_rc5.c create mode 100644 lib/infrared/encoder_decoder/rc5/infrared_protocol_rc5.h create mode 100644 lib/infrared/encoder_decoder/rc5/infrared_protocol_rc5_i.h delete mode 100644 lib/infrared/encoder_decoder/rc5/infrared_rc5_spec.c create mode 100644 lib/infrared/encoder_decoder/rc6/infrared_protocol_rc6.c create mode 100644 lib/infrared/encoder_decoder/rc6/infrared_protocol_rc6.h create mode 100644 lib/infrared/encoder_decoder/rc6/infrared_protocol_rc6_i.h delete mode 100644 lib/infrared/encoder_decoder/rc6/infrared_rc6_spec.c create mode 100644 lib/infrared/encoder_decoder/samsung/infrared_protocol_samsung.c create mode 100644 lib/infrared/encoder_decoder/samsung/infrared_protocol_samsung.h create mode 100644 lib/infrared/encoder_decoder/samsung/infrared_protocol_samsung_i.h delete mode 100644 lib/infrared/encoder_decoder/samsung/infrared_samsung_spec.c create mode 100644 lib/infrared/encoder_decoder/sirc/infrared_protocol_sirc.c create mode 100644 lib/infrared/encoder_decoder/sirc/infrared_protocol_sirc.h create mode 100644 lib/infrared/encoder_decoder/sirc/infrared_protocol_sirc_i.h delete mode 100644 lib/infrared/encoder_decoder/sirc/infrared_sirc_spec.c diff --git a/assets/resources/infrared/assets/tv.ir b/assets/resources/infrared/assets/tv.ir old mode 100755 new mode 100644 index cc5b0360e6d..b45171cb142 --- a/assets/resources/infrared/assets/tv.ir +++ b/assets/resources/infrared/assets/tv.ir @@ -47,85 +47,85 @@ name: Mute type: parsed protocol: NEC address: 08 00 00 00 -command: 0b 00 00 00 +command: 0B 00 00 00 # name: Power type: parsed protocol: NECext -address: 00 df 00 00 -command: 1c 00 00 00 +address: 00 DF 00 00 +command: 1C 00 00 00 # name: Vol_up type: parsed protocol: NECext -address: 00 df 00 00 -command: 4b 00 00 00 +address: 00 DF 00 00 +command: 4B 00 00 00 # name: Vol_dn type: parsed protocol: NECext -address: 00 df 00 00 -command: 4f 00 00 00 +address: 00 DF 00 00 +command: 4F 00 00 00 # name: Ch_next type: parsed protocol: NECext -address: 00 df 00 00 +address: 00 DF 00 00 command: 09 00 00 00 # name: Ch_prev type: parsed protocol: NECext -address: 00 df 00 00 +address: 00 DF 00 00 command: 05 00 00 00 # name: Mute type: parsed protocol: NECext -address: 00 df 00 00 +address: 00 DF 00 00 command: 08 00 00 00 # name: Power type: parsed protocol: Samsung32 -address: 0e 00 00 00 -command: 0c 00 00 00 +address: 0E 00 00 00 +command: 0C 00 00 00 # name: Mute type: parsed protocol: Samsung32 -address: 0e 00 00 00 -command: 0d 00 00 00 +address: 0E 00 00 00 +command: 0D 00 00 00 # name: Vol_up type: parsed protocol: Samsung32 -address: 0e 00 00 00 +address: 0E 00 00 00 command: 14 00 00 00 # name: Vol_dn type: parsed protocol: Samsung32 -address: 0e 00 00 00 +address: 0E 00 00 00 command: 15 00 00 00 # name: Ch_next type: parsed protocol: Samsung32 -address: 0e 00 00 00 +address: 0E 00 00 00 command: 12 00 00 00 # name: Ch_prev type: parsed protocol: Samsung32 -address: 0e 00 00 00 +address: 0E 00 00 00 command: 13 00 00 00 # name: Power type: parsed protocol: RC6 address: 00 00 00 00 -command: 0c 00 00 00 +command: 0C 00 00 00 # name: Power type: parsed @@ -190,19 +190,19 @@ command: 63 00 00 00 name: Power type: parsed protocol: NEC -address: aa 00 00 00 -command: 1c 00 00 00 +address: AA 00 00 00 +command: 1C 00 00 00 # name: Power type: parsed protocol: NEC address: 38 00 00 00 -command: 1c 00 00 00 +command: 1C 00 00 00 # name: Power type: parsed protocol: NECext -address: 83 7a 00 00 +address: 83 7A 00 00 command: 08 00 00 00 # name: Power @@ -215,7 +215,7 @@ name: Power type: parsed protocol: NECext address: 18 18 00 00 -command: c0 00 00 00 +command: C0 00 00 00 # name: Power type: parsed @@ -226,8 +226,8 @@ command: 10 00 00 00 name: Power type: parsed protocol: NEC -address: aa 00 00 00 -command: c5 00 00 00 +address: AA 00 00 00 +command: C5 00 00 00 # name: Power type: parsed @@ -250,8 +250,8 @@ command: 08 00 00 00 name: Power type: parsed protocol: NECext -address: 80 6f 00 00 -command: 0a 00 00 00 +address: 80 6F 00 00 +command: 0A 00 00 00 # name: Power type: parsed @@ -262,19 +262,19 @@ command: 00 00 00 00 name: Power type: parsed protocol: NECext -address: 80 7b 00 00 +address: 80 7B 00 00 command: 13 00 00 00 # name: Power type: parsed protocol: Samsung32 -address: 0e 00 00 00 +address: 0E 00 00 00 command: 14 00 00 00 # name: Power type: parsed protocol: NECext -address: 80 7e 00 00 +address: 80 7E 00 00 command: 18 00 00 00 # name: Power @@ -287,25 +287,25 @@ name: Power type: parsed protocol: NECext address: 80 75 00 00 -command: 0a 00 00 00 +command: 0A 00 00 00 # name: Power type: parsed protocol: NECext address: 80 57 00 00 -command: 0a 00 00 00 +command: 0A 00 00 00 # name: Power type: parsed protocol: Samsung32 -address: 0b 00 00 00 -command: 0a 00 00 00 +address: 0B 00 00 00 +command: 0A 00 00 00 # name: Power type: parsed protocol: NEC -address: aa 00 00 00 -command: 1b 00 00 00 +address: AA 00 00 00 +command: 1B 00 00 00 # name: Power type: parsed @@ -323,7 +323,7 @@ name: Power type: parsed protocol: Samsung32 address: 08 00 00 00 -command: 0f 00 00 00 +command: 0F 00 00 00 # name: Power type: parsed @@ -340,626 +340,626 @@ command: 01 00 00 00 name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 634 2571 505 519 479 519 479 518 480 518 480 518 480 518 480 517 481 547 481 517 481 20040 590 2555 501 1007 999 997 510 548 480 486 512 486 512 486 542 485 513 516 482 116758 593 2552 504 1004 992 1004 514 514 514 483 515 513 485 483 545 482 516 482 516 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 525 1955 449 1999 476 4545 446 4544 478 2032 443 2006 469 2011 444 4577 445 4545 447 4574 448 2002 473 4547 444 34913 447 2032 443 2007 478 4542 449 4541 471 2039 446 2004 471 2008 447 4574 448 4543 448 4572 450 2030 445 4545 446 # name: Power -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 2445 582 1221 603 548 602 1191 609 572 602 1191 609 542 607 544 631 1172 603 568 606 545 605 566 608 543 26263 2414 611 1192 607 544 606 1197 602 569 606 1197 602 539 611 540 635 1168 606 565 610 541 608 563 587 564 +type: parsed +protocol: SIRC +address: 01 00 00 00 +command: 15 00 00 00 # name: Power -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 3461 1802 439 452 444 1283 469 448 438 453 443 447 439 452 444 419 497 448 438 425 471 419 467 451 445 445 441 450 446 1281 471 420 466 425 471 446 440 424 472 445 461 456 440 451 445 445 441 450 446 1281 471 447 439 451 445 419 467 423 473 445 441 422 494 450 436 428 468 1259 493 425 471 1256 496 1259 472 1281 471 1285 467 423 473 417 469 1286 466 452 444 1283 469 1285 467 1261 491 1263 468 423 493 1260 471 74142 3578 1713 467 423 473 1281 471 420 466 452 444 419 467 424 472 418 488 429 467 451 445 445 441 450 446 444 442 449 437 1290 472 446 440 423 473 445 441 449 467 396 490 428 468 449 447 444 442 448 438 1289 473 445 441 450 446 444 442 449 437 426 470 421 495 422 464 426 470 1257 495 450 446 1254 488 1267 464 1290 472 1282 470 421 465 453 443 1284 468 450 446 1281 471 1283 469 1259 493 1261 470 448 468 1258 473 +type: parsed +protocol: Kaseikyo +address: 80 02 20 00 +command: D0 03 00 00 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 389 1737 280 796 253 744 295 754 274 775 274 776 273 1827 271 1828 270 805 254 1820 278 796 253 797 252 745 294 1806 302 773 245 48942 305 1821 277 798 251 746 303 747 271 778 271 1829 279 796 253 796 253 1821 277 798 251 1823 275 1824 274 1825 273 802 247 1827 271 42824 381 1745 272 804 245 752 297 753 275 773 276 774 275 1825 273 1826 272 803 246 1828 270 805 254 795 244 753 296 1804 294 781 247 48939 379 1746 271 804 245 779 270 753 275 774 275 1825 273 802 247 802 247 1827 271 804 245 1829 279 1820 278 1821 277 798 251 1823 275 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 562 1721 561 594 557 597 564 617 513 615 536 618 543 1715 566 1716 566 1692 559 594 567 588 563 618 543 611 540 615 536 618 543 1715 556 623 538 617 534 621 530 624 516 638 513 642 509 1722 560 620 541 1717 565 1692 559 1724 568 1715 556 1701 560 1723 559 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 8436 4189 538 1563 566 1559 539 510 559 543 516 507 542 560 509 540 509 567 512 1586 512 1562 567 1559 539 536 533 1566 542 507 562 513 536 540 509 22102 647 1478 559 1568 540 508 541 535 534 515 534 568 511 538 511 539 540 1585 513 1560 559 1567 541 534 535 1564 534 515 534 568 511 538 511 22125 644 1482 565 1561 537 511 538 564 515 508 541 535 534 541 508 516 563 1588 510 1563 556 1570 538 510 559 1567 541 534 515 535 534 541 508 # name: Power -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 929 825 1711 934 797 930 791 909 822 932 789 938 793 934 797 930 791 1719 899 856 1711 907 824 90848 923 830 1706 912 819 908 823 931 790 910 822 933 788 912 819 935 796 1714 904 850 1707 939 792 +type: parsed +protocol: RC5 +address: 00 00 00 00 +command: 0C 00 00 00 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 448 2031 444 2005 480 4540 452 4539 473 2037 438 2011 474 2006 449 4571 451 4539 453 4568 444 2036 449 4541 451 34906 527 1953 451 1998 477 4543 449 4542 480 2030 445 2004 471 2009 446 4575 447 4543 449 4572 450 1999 476 4545 446 # name: Power -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 9021 4496 567 1664 567 1690 561 567 533 1698 563 1667 564 1693 568 1663 568 586 534 567 563 565 535 594 536 591 539 589 531 571 539 563 567 1690 541 560 560 594 536 592 538 564 536 1695 566 1664 567 1690 561 1670 561 1696 555 1675 566 562 558 596 514 562 568 559 561 594 536 565 535 593 537 591 539 1665 566 1692 559 1671 560 1697 564 1666 565 1666 565 1693 558 1672 569 23181 9013 4504 569 1689 542 1689 562 565 535 1697 564 1666 646 1610 560 1672 559 595 535 593 537 590 510 566 564 590 540 588 532 596 514 588 542 1689 542 560 560 594 536 592 538 590 510 1694 567 1664 567 1690 561 1669 562 1695 556 1675 566 561 559 596 514 588 542 585 535 593 537 591 509 593 537 591 539 1665 566 1692 559 1671 560 1697 564 1667 564 1667 564 1693 558 1672 569 +type: parsed +protocol: NEC42 +address: 7B 00 00 00 +command: 00 00 00 00 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 8041 3979 513 536 482 515 513 1559 509 515 514 1560 508 515 514 509 509 514 484 4000 533 1566 512 537 481 1566 512 537 481 1566 512 537 481 516 513 537 481 24150 8044 3977 505 518 510 539 479 1567 511 512 506 1568 510 539 479 543 485 538 480 3977 536 1564 514 534 484 1563 515 508 510 1563 515 508 510 540 489 534 484 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 383 2027 295 2112 271 2138 266 890 271 887 294 926 235 2114 300 887 274 915 236 2145 269 887 274 884 297 923 238 920 241 917 264 895 266 26573 384 2026 296 2111 273 2136 268 889 272 886 295 924 237 2113 301 886 275 914 237 2144 270 886 275 914 267 921 240 919 242 916 265 924 237 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 177 8474 175 5510 174 8476 173 8477 177 8504 171 5515 175 8476 178 8472 177 8473 176 5541 174 8476 173 45583 171 8481 178 5507 177 8473 176 8474 175 8506 173 5512 172 8478 176 8475 174 8476 178 5538 177 8474 175 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 8044 3976 506 517 511 1563 505 517 511 538 480 517 511 538 460 563 455 568 460 3993 530 545 483 1564 514 1559 509 1564 514 509 509 540 488 535 483 513 505 24150 8043 3978 514 509 509 1564 514 509 509 540 478 519 509 540 458 565 464 559 459 3994 529 546 482 1565 513 1560 508 1565 513 510 508 541 487 536 482 541 477 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 558 2942 450 10021 482 2989 484 10015 447 3024 449 10021 472 3100 485 2986 477 2994 500 2999 475 2996 477 2994 479 2992 482 3018 476 2995 479 6464 484 36270 477 3023 450 10020 473 2999 485 10014 448 3022 452 10019 474 3098 478 2994 480 2991 503 2996 477 2994 480 2992 482 2990 484 3015 479 2992 481 6462 485 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 587 2407 476 1062 965 547 482 547 451 577 452 546 452 22108 590 2404 479 1060 967 1028 510 519 509 548 480 487 511 120791 645 2411 472 1066 961 1065 483 515 514 514 504 493 515 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 172 7439 171 7441 169 7443 177 7434 176 7462 178 4887 176 4916 177 4914 169 7469 171 4920 174 4918 175 55174 176 7436 174 7437 173 7439 171 7440 175 7463 172 4894 174 4917 171 4921 172 7465 175 4916 178 4914 169 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 589 2556 500 524 474 554 454 544 454 543 455 543 455 543 455 543 445 583 446 552 446 20046 584 2561 505 1033 485 514 963 518 480 1032 516 512 476 522 506 491 507 522 476 116758 586 2560 506 1033 484 513 964 548 450 1031 507 522 476 521 507 490 508 521 477 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 586 2407 476 1063 964 578 450 548 450 547 481 517 481 577 451 546 452 546 472 556 452 545 453 575 453 514 484 544 484 543 455 14954 510 2483 481 1058 480 548 959 552 446 1067 481 516 512 546 482 515 992 1003 535 493 515 543 486 513 475 522 506 552 446 111671 589 2405 478 1061 477 551 967 514 484 1059 479 549 479 548 480 517 990 1036 512 516 482 546 483 515 503 525 483 544 454 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 8444 4180 537 1564 565 1560 538 1561 557 1568 540 1559 539 536 533 542 517 559 510 1563 535 1564 565 1561 537 512 567 1558 540 535 534 1566 542 1557 562 23122 564 1562 557 1569 539 1560 538 1587 542 1558 540 534 535 515 534 541 538 1587 511 1563 566 1560 538 536 533 1567 541 534 515 1585 533 1566 542 23166 561 1565 564 1561 537 1563 535 1590 539 1561 537 538 541 534 515 535 534 1564 534 1566 563 1563 535 540 539 1560 538 511 538 1588 541 1559 539 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 527 1923 481 1998 477 4543 449 4542 470 2040 445 2004 471 2009 446 4575 447 4543 449 4572 450 1999 476 2034 441 34899 524 1956 448 2001 474 4546 446 4545 477 2033 442 2007 478 2002 443 4578 444 4546 445 4575 447 2003 472 2037 448 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 533 1356 437 3474 427 3483 429 3455 436 1454 430 1459 405 28168 510 1379 434 3477 434 3476 425 3459 432 1457 427 1462 402 # name: Power -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 921 833 1714 932 789 938 793 934 797 903 818 909 822 905 816 938 793 1716 902 853 1714 905 816 90856 922 805 1742 931 790 937 794 933 788 939 792 935 796 930 791 937 794 1715 903 825 1742 904 817 +type: parsed +protocol: RC5 +address: 00 00 00 00 +command: 0C 00 00 00 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 179 7433 177 4915 178 7434 175 7436 174 7464 176 7435 175 4916 177 4915 173 4918 170 4922 171 4920 173 55174 175 7437 173 4919 174 7437 173 7439 171 7467 173 7438 172 4920 173 4919 174 4917 176 4915 178 4914 169 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 169 6731 176 6748 169 6730 177 6748 169 6755 172 4427 177 4447 178 6721 175 6749 178 4446 168 4456 169 54704 176 6723 174 6750 177 6723 173 6750 177 6747 170 4429 175 4449 176 6723 174 6751 176 4448 177 4447 178 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 3506 3494 876 830 840 2576 847 2568 844 862 819 2570 842 864 816 863 818 2570 842 836 844 2572 840 866 815 865 815 2573 839 867 813 866 814 2573 850 857 813 2575 847 2568 844 834 847 2569 843 835 845 2571 872 2571 842 32654 3512 3488 872 834 847 2570 842 2573 839 867 814 2574 849 858 822 857 813 2575 848 859 821 2566 846 832 848 860 821 2567 845 833 848 860 820 2568 844 834 847 2569 843 2572 840 838 843 2574 849 829 841 2575 868 2575 837 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 560 2939 453 10018 475 2996 477 10022 450 3021 452 10018 475 3097 478 6464 483 6460 477 6466 502 6469 479 2993 480 2990 484 36274 564 2936 456 10015 478 2993 481 10020 534 2936 456 10014 479 3093 482 6461 476 6466 482 6461 476 6495 473 2999 485 2986 477 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 10726 41047 10727 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 1617 4604 1559 1537 1560 1537 1560 4661 1533 33422 1613 4607 1566 1530 1556 1540 1536 4685 1539 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 174 4972 177 4910 173 4944 170 6988 174 6984 177 6951 175 6983 174 14269 177 4969 175 4912 176 4941 178 6979 172 6986 175 6953 178 6980 171 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 174 7067 176 10544 1055 719 1053 739 1054 738 953 822 1052 2566 169 3435 171 3431 175 3446 170 3432 174 3446 170 3432 174 3428 178 3442 174 3429 177 3425 171 39320 2323 4900 178 10543 1056 719 1053 739 1054 738 953 821 1053 2566 169 3435 171 3431 175 3445 171 3432 174 3446 170 3432 174 3428 178 3442 174 3428 178 3425 171 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 3506 3492 868 839 841 2575 848 858 822 2566 846 832 848 859 821 858 812 2576 847 860 820 2567 845 833 847 860 821 2568 844 862 818 2570 842 864 817 2571 841 2574 849 2567 845 861 820 2568 845 834 847 2570 873 2570 842 34395 3503 3496 874 833 847 2568 845 834 847 2570 842 864 816 835 846 862 819 2569 843 835 846 2571 841 865 816 864 816 2571 841 837 844 2573 850 857 813 2575 848 2567 845 2570 842 864 816 2572 840 838 842 2574 869 2574 838 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 170 8479 170 5516 178 8472 177 8474 175 8506 173 5513 171 8479 175 8476 178 8472 177 5540 175 8475 174 45584 177 8473 176 5509 175 8476 173 8477 176 8504 170 5516 178 8472 177 8474 175 8476 173 5543 172 8479 170 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 178 4969 170 6958 173 4944 175 6983 173 6956 174 6984 177 6980 171 16308 180 4966 173 6955 176 4941 172 6985 176 6982 169 6960 176 6982 174 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 585 2409 474 550 478 550 448 1033 994 1063 485 512 506 79916 585 2410 473 551 477 550 448 1034 993 1033 515 543 475 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 1192 1012 6649 26844 1192 1013 6648 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 3134 6105 6263 82963 3134 6105 6263 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 588 1511 567 1533 565 589 531 597 513 589 541 587 533 1565 533 569 541 1533 565 562 568 1531 537 1537 561 593 537 591 539 1507 561 1539 569 22152 586 1513 565 1535 563 590 540 562 538 564 566 588 542 1557 531 597 513 1534 564 590 540 1533 535 1539 559 568 562 592 538 1509 569 1531 567 # name: Power -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 923 831 1715 903 818 936 795 932 789 938 793 934 797 1713 1740 932 789 911 821 934 798 930 791 90853 928 799 1737 935 796 931 790 937 795 932 789 938 793 1717 1736 936 796 932 789 911 820 934 798 +type: parsed +protocol: RC5 +address: 00 00 00 00 +command: 20 00 00 00 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 689 1461 566 1534 564 589 531 597 513 1534 564 564 566 1533 535 620 510 1537 561 592 538 1535 543 1531 567 587 533 595 535 1511 567 1533 565 22161 588 1512 566 1534 564 564 556 598 512 1535 563 565 565 1534 534 594 536 1538 560 593 537 1536 542 1532 566 587 543 585 535 1511 567 1534 564 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 588 2558 498 526 482 515 483 546 452 545 453 545 453 545 453 545 453 544 474 554 444 20047 583 2562 504 519 479 519 479 1003 515 483 1024 548 450 1001 995 1001 506 116771 593 2552 504 520 478 551 447 1004 513 514 993 549 449 1002 994 1002 516 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 587 2559 507 517 481 517 481 547 451 516 482 546 452 546 452 546 452 545 473 525 483 20038 592 2553 503 521 477 552 446 521 477 1004 514 515 992 1034 483 514 484 513 485 116769 593 2552 504 520 478 550 448 520 478 1003 515 514 993 1032 486 513 475 522 476 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 4558 4576 558 596 565 97881 4554 4579 565 589 562 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 1039 7202 958 4713 981 4713 961 7255 956 4739 955 16077 1038 7204 956 4714 980 4715 959 7256 954 4741 954 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 506 2638 510 516 482 516 482 546 452 546 452 545 453 545 453 545 453 575 443 554 444 20048 592 2554 502 1036 481 517 481 517 511 516 482 516 961 1035 513 515 483 514 484 116757 594 2552 504 1034 484 514 484 514 504 524 484 513 964 1032 506 522 476 492 506 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 170 7441 169 4924 174 7437 193 7444 171 7441 174 4918 170 7441 174 4917 171 7440 195 7443 172 7439 171 55187 174 7437 173 4919 175 7437 173 7438 172 7466 175 4891 172 7439 171 4921 172 7439 171 7441 174 7464 171 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 179 4967 172 4915 178 6980 171 4945 169 4948 176 4912 176 4941 178 16307 176 4969 175 4912 176 6982 174 4942 171 4945 169 4919 174 4943 176 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 1042 793 898 883 869 858 924 884 878 877 875 879 873 855 928 1717 901 854 1794 878 894 887 875 1716 902 89114 985 798 903 878 874 880 902 852 900 855 897 857 905 876 896 1722 906 849 1789 883 899 855 897 1721 897 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 170 8480 169 8481 178 8472 177 8474 175 8476 173 5513 176 8474 175 8476 173 8507 178 5509 175 8505 174 45558 175 8476 173 8476 173 8477 172 8478 171 8480 169 5517 177 8473 176 8474 175 8506 174 5512 172 8509 171 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 628 2577 499 528 480 545 453 544 454 544 454 544 454 544 454 543 455 543 475 553 445 20047 593 2553 503 521 477 551 447 1004 514 515 992 519 479 1003 515 513 485 543 455 116768 585 2561 505 519 479 519 479 1002 516 513 994 548 450 1001 506 522 476 521 477 # name: Power -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 8987 4505 568 586 534 594 536 591 509 567 563 591 539 563 567 587 513 1692 559 1672 559 1698 563 590 510 592 538 590 540 1664 567 1690 561 593 507 1699 562 1668 563 1694 567 1663 568 586 534 568 562 592 508 595 535 1695 536 592 538 1693 558 595 515 1690 561 593 517 585 535 567 563 39551 8994 4497 566 589 541 560 560 594 516 586 534 594 536 592 538 590 510 1695 566 1664 567 1690 561 593 507 595 535 566 564 1667 564 1693 558 596 534 1670 561 1670 561 1696 565 1666 565 589 541 587 533 595 515 587 533 1697 534 568 562 1695 556 572 538 1693 568 559 541 588 542 585 535 +type: parsed +protocol: NECext +address: 80 63 00 00 +command: 0F 15 00 00 # name: Power -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 8987 4504 569 559 561 593 537 565 535 567 563 591 539 588 532 597 513 1691 560 568 562 592 508 1697 564 589 511 565 565 1692 559 1672 559 569 561 1669 562 566 564 564 566 1665 566 588 542 586 534 1670 561 567 563 565 535 593 537 591 539 1692 539 589 541 586 534 594 536 592 508 40679 8987 4504 569 585 535 593 537 591 509 566 564 591 539 588 532 596 514 1691 560 594 536 592 508 1697 564 589 511 591 539 1692 559 1671 560 594 536 1669 562 592 538 590 540 1664 567 587 543 585 535 1670 561 593 537 565 535 593 537 591 539 1691 540 588 542 586 534 594 536 592 508 +type: parsed +protocol: NECext +address: 80 64 00 00 +command: 49 08 00 00 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 298 1828 270 804 245 1829 279 1820 278 797 252 771 278 1822 276 1824 274 800 249 1825 273 802 247 776 252 771 278 1822 276 799 250 48931 388 1737 280 796 253 1821 277 1822 276 798 251 1823 275 800 249 774 275 1825 273 776 273 1801 297 1828 270 1829 279 796 253 1821 277 42813 301 1825 273 801 248 1826 272 1827 271 804 245 805 244 1830 278 1821 277 798 251 1823 275 799 250 774 244 779 280 1820 278 796 253 48926 382 1744 354 696 271 1828 270 1829 279 796 253 1821 277 797 252 746 303 1823 275 774 275 1799 299 1826 272 1827 271 804 245 1829 279 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 177 5508 176 5539 176 8475 174 5542 173 8478 171 5545 170 8481 168 8482 177 8473 176 5541 174 8476 173 45573 169 5517 177 5538 177 8473 176 5541 174 8476 173 5544 171 8479 170 8481 178 8472 177 5540 175 8475 174 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 175 8474 175 8475 174 8477 172 8478 171 8480 169 8481 178 5538 177 5510 174 5542 173 5543 172 5544 171 45575 177 8472 177 8474 175 8476 173 8477 172 8478 171 8481 178 5507 177 5539 176 5540 175 5542 173 5543 172 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 8050 3971 511 1562 516 1558 510 539 490 1557 511 1563 515 533 485 538 490 533 485 3983 530 1569 509 1564 514 1559 509 1565 513 1560 508 515 513 536 482 541 488 24152 8042 3979 514 1560 508 1565 513 510 508 1565 513 1560 508 515 513 536 482 541 488 3980 533 1567 511 1562 516 1557 511 1563 515 1558 510 539 489 534 484 539 490 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 175 8475 174 5512 177 8473 176 8475 174 8506 179 5508 176 8474 175 8475 174 8477 172 5544 171 8480 169 45587 176 8475 174 5511 173 8477 177 8473 176 8504 170 5516 178 8472 177 8473 176 8475 174 5542 173 8478 171 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 176 7436 174 7438 172 7439 171 7441 174 7463 172 7440 170 4921 172 4919 174 4917 176 4916 177 4914 174 55176 175 7437 173 7439 191 7446 174 7438 177 7434 176 7435 175 4917 176 4915 179 4914 174 4917 171 4946 178 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 176 7435 175 4917 177 7435 175 7436 189 7449 176 7435 175 7437 173 4918 175 7436 174 4918 175 4916 178 55171 175 7436 174 4918 170 7441 174 7438 177 7460 170 7441 174 7438 177 4914 174 7437 178 4914 169 4922 171 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 8049 3973 509 1564 514 509 509 1564 514 535 483 1564 514 535 483 540 489 535 483 3980 533 1566 512 537 481 1566 512 511 507 1566 512 537 481 542 486 511 507 24149 8045 3976 516 1558 510 512 516 1557 511 512 516 1558 510 512 516 507 511 512 506 3984 539 1560 508 515 513 1560 508 541 488 1560 508 541 488 536 482 514 504 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 366 202 867 527 170 130516 343 227 863 529 168 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 176 5992 176 1372 176 1320 177 1345 172 1349 179 1370 178 1317 175 4444 176 1346 171 1351 177 1345 172 1349 179 1344 174 5963 175 65574 172 5995 178 1344 174 1348 175 1347 175 1347 170 1378 170 1325 172 4447 178 1344 173 1349 169 1353 175 1347 170 1351 177 5961 177 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 497 1478 488 511 467 1482 494 531 447 1477 499 501 466 533 445 530 468 507 471 529 438 12857 488 1485 491 509 469 1481 495 529 448 1476 490 510 468 532 445 529 469 480 498 502 465 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 982 6313 961 2662 903 2718 929 2719 907 6363 900 2722 904 6365 909 6361 903 6368 906 2742 905 46364 989 6307 906 2716 911 2712 925 2723 903 6367 907 2715 901 6369 905 6365 909 6361 903 2745 902 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 599 257 975 317 177 130649 308 228 974 319 175 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 176 4969 170 6958 173 6985 177 6981 171 6958 178 6980 177 6981 170 16308 180 4966 173 6955 176 6982 169 6988 174 6956 175 6983 173 6984 172 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 178 5990 173 1349 174 1348 175 1348 174 4444 176 1346 171 1351 177 4416 178 1344 173 1348 169 1353 175 1347 170 1351 177 9048 177 65573 173 5995 173 1348 175 1348 174 1347 176 4444 171 1351 177 1345 173 4421 173 1348 169 1352 176 1347 170 1351 177 1345 172 9053 177 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 174 4972 177 4910 173 6985 177 4940 174 6984 178 6951 175 6983 174 14269 177 4968 176 4912 176 6981 175 4941 173 6985 177 6953 178 6979 172 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 1318 225 177 130305 1609 231 171 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 585 2410 473 1065 962 581 447 519 479 580 448 549 449 579 449 518 480 548 480 517 481 517 481 577 451 516 482 576 452 545 453 14955 510 2483 481 1058 480 549 969 543 445 1037 511 547 481 546 482 516 482 545 484 545 483 514 484 544 963 1063 485 543 445 111612 626 2427 557 954 513 512 995 547 451 1031 507 521 508 551 477 520 478 550 478 549 479 488 510 548 969 1057 481 517 481 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 203 272 1215 302 171 130229 1423 275 178 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 176 7436 174 7438 192 7446 174 7437 178 7434 176 7435 175 4917 176 4915 178 4913 175 4917 197 7440 175 55130 286 7377 177 7435 175 7437 173 7438 172 7466 174 7411 178 4913 170 4921 172 4919 174 4918 175 7436 174 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 175 7437 173 4918 170 7441 174 7437 178 7460 170 7441 179 7433 177 4915 178 4913 170 4922 171 4920 173 55124 291 7372 172 4921 172 7439 171 7441 174 7463 172 7440 170 7442 178 4913 170 4922 171 4920 173 4918 175 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 172 8477 172 8478 171 8480 169 5516 179 8502 178 5509 175 8475 174 8476 173 8479 170 5545 170 8480 169 45588 176 8473 176 8474 175 8476 173 5513 177 8504 171 5515 174 8476 178 8472 177 8473 176 5541 174 8476 173 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 177 7436 174 4918 175 7436 174 4918 175 7462 178 4887 176 4916 177 4914 174 4917 171 4921 172 4919 175 55184 175 7435 175 4918 175 7436 174 4918 175 7462 178 4914 174 4917 171 4920 173 4919 174 4917 176 4915 178 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 508 2484 480 1060 967 544 485 544 454 574 454 543 455 573 445 553 445 522 506 552 446 552 446 551 477 551 447 581 447 520 478 550 478 15908 626 2427 476 1062 476 522 995 547 451 1061 477 520 508 550 478 519 479 519 999 1058 959 1037 990 582 446 1035 483 111666 590 2404 479 1059 479 549 968 543 455 1027 511 548 480 517 511 486 512 546 961 1065 962 1034 993 579 449 1032 486 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 175 8475 174 8475 174 8477 172 8478 171 8480 169 5517 178 8473 175 8475 179 8501 173 5513 176 8505 169 45562 179 8472 177 8473 176 8474 175 8476 173 8478 171 5515 174 8476 178 8473 176 8505 174 5512 172 8508 171 120769 178 93377 175 7437 173 4919 174 7437 173 7439 171 7467 173 7439 171 7441 174 4918 170 4921 172 7439 171 7467 173 55167 171 7440 170 4922 171 7440 195 7443 172 7439 171 7441 174 7438 177 4915 173 4918 195 7442 173 7439 170 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 295 1805 273 776 242 1808 270 754 244 1806 272 777 241 758 270 754 264 760 268 756 272 14149 297 1802 266 784 244 1805 263 762 246 1803 265 785 244 780 248 751 267 758 271 753 265 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 535 1723 569 585 566 615 536 619 542 586 565 616 535 1722 539 615 536 1721 561 620 541 587 564 617 534 621 540 588 563 618 543 1714 537 617 534 621 540 615 536 618 543 612 539 615 536 1722 560 594 567 1717 534 1723 569 1714 557 1700 643 1639 643 1641 559 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 502 2521 504 521 997 545 453 575 453 545 453 575 454 22097 591 2433 511 513 994 1062 476 552 476 522 476 552 476 120839 628 2455 509 515 992 1064 484 513 505 493 515 543 475 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 591 2404 479 1060 967 1029 509 519 509 549 479 518 480 79926 585 2408 556 956 989 1033 515 544 484 543 475 522 476 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 586 2560 506 518 480 548 450 548 450 517 481 517 481 517 481 547 451 546 482 516 482 20040 590 2556 500 1038 480 518 969 543 455 543 475 1006 511 517 481 517 511 486 481 116778 584 2561 505 1033 485 513 964 548 450 548 480 1001 506 522 476 522 506 521 446 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 917 206 175 186 170 21561 170 2280 175 2274 502 1929 174 2276 169 5178 170 2261 173 3724 498 1932 171 2279 176 2273 172 3709 172 2277 178 3720 171 17223 177 7619 174 2275 170 2279 176 2256 168 2280 175 5172 176 2256 168 3729 173 2276 179 2253 171 2278 177 3703 178 2271 174 3724 177 17251 170 7627 177 2272 173 2276 169 2263 171 2277 178 5169 169 2262 172 3726 175 2256 168 2280 175 2274 171 3710 171 2278 177 3720 171 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 565 233 653 313 170 130328 752 235 233 107 229 398 177 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 586 2439 505 549 968 1027 511 517 511 517 481 547 481 21583 584 2439 505 520 997 1059 479 549 479 518 480 548 480 120894 593 2432 501 522 995 1061 477 521 507 520 478 550 478 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 558 8032 474 8115 503 8116 482 8108 480 8141 477 5239 476 8114 504 8115 483 8107 481 5236 509 8111 477 45290 554 8036 481 8108 510 8110 478 8112 476 8145 473 5243 482 8107 511 8109 479 8111 477 5240 505 8115 473 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 173 7438 172 4920 173 7438 172 4920 173 7465 175 4890 173 7439 171 4920 173 4919 174 4917 176 4915 178 55179 170 7441 169 4924 174 7437 178 4913 175 7463 172 4893 170 7441 174 4918 170 4922 171 4920 173 4918 175 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 225 745 222 774 193 778 200 797 175 769 193 777 201 771 196 774 224 747 220 776 202 769 198 248 220 252 196 250 218 253 225 746 221 250 198 248 220 252 226 246 222 223 225 248 220 252 216 229 219 253 225 247 221 277 176 243 220 279 189 230 228 244 224 248 220 252 196 250 218 253 215 257 201 770 197 799 189 257 201 271 197 37716 222 749 218 778 200 771 196 801 172 772 200 771 196 774 193 777 221 750 217 780 197 773 194 251 217 255 193 279 199 273 195 749 218 254 194 252 226 245 223 275 178 242 221 277 201 245 193 253 225 247 221 250 218 254 194 252 226 272 196 223 225 248 220 251 217 255 193 253 225 247 221 251 197 773 194 803 174 297 176 244 219 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 3503 2655 197 642 876 2568 844 834 846 833 848 860 821 831 839 2576 847 2569 843 2572 841 2574 849 858 823 857 813 866 815 865 815 2572 841 2575 848 2568 844 2570 842 836 844 863 818 834 847 861 820 2568 845 2571 872 2571 842 32651 3505 3495 875 2567 845 861 820 832 849 859 811 840 840 2576 847 2568 844 2571 842 2574 849 830 840 867 814 838 842 865 815 2572 840 2575 848 2568 845 2571 841 865 815 864 817 834 846 861 819 2569 843 2572 871 2572 840 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 347 677 219 252 196 276 202 742 225 798 174 770 192 778 200 771 196 775 223 748 219 777 200 770 197 249 219 253 195 251 227 271 197 747 220 252 216 229 219 253 225 273 195 277 176 244 219 253 215 230 228 244 224 248 220 252 196 250 218 280 198 247 201 245 223 249 219 253 195 251 227 245 223 248 200 797 175 795 198 248 200 246 222 37666 344 678 228 245 223 222 226 771 196 800 198 747 220 750 217 753 224 773 194 776 202 769 198 799 173 246 227 245 223 249 199 247 221 749 218 280 198 247 201 245 223 249 219 253 195 251 227 244 224 248 200 272 196 250 218 254 194 252 226 245 223 249 199 274 194 251 227 245 193 253 225 273 195 251 217 753 225 746 221 251 197 275 193 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 174 7437 173 7440 174 7437 178 7433 177 7461 169 7442 178 4914 174 4917 171 4921 172 4919 174 7463 177 55163 177 7435 174 7437 173 7438 172 7440 175 7463 172 7413 176 4915 178 4913 175 4917 171 4920 174 7438 172 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 8042 3979 513 510 508 541 487 1559 509 515 513 1560 508 515 513 510 508 541 477 3981 532 1568 510 1563 515 1558 510 1564 514 1559 509 514 514 535 483 540 488 24151 8042 3979 513 536 482 541 487 1560 508 541 488 1560 508 541 487 536 482 541 477 3980 533 1566 512 1561 507 1566 512 1562 516 1557 511 538 491 533 485 538 490 # name: Power -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 8989 4501 562 1696 565 1665 566 562 568 586 514 588 542 586 534 594 536 1667 564 591 539 1692 539 563 567 1690 561 1669 562 1669 562 1696 565 562 538 564 566 588 542 586 534 1670 561 568 562 592 538 590 510 592 538 590 540 561 539 563 567 561 569 585 535 593 507 595 535 593 537 39547 8987 4504 569 1689 562 1668 563 591 539 589 511 591 539 589 541 561 559 1671 560 568 562 1695 536 592 538 1693 558 1673 568 1663 568 1689 562 592 508 594 536 592 538 590 540 1664 567 561 569 559 561 567 543 585 535 593 537 591 509 593 537 591 539 589 541 587 513 589 541 587 533 +type: parsed +protocol: NECext +address: 83 7A 00 00 +command: 08 00 00 00 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 8988 4504 640 487 562 592 538 590 510 592 538 590 540 588 532 596 514 614 516 1689 562 1668 563 1695 536 1695 566 1664 567 1690 612 1618 643 1430 801 1613 648 481 558 570 540 589 541 587 533 595 535 592 508 594 536 592 538 1667 564 1693 558 1672 569 1688 563 1668 644 1586 563 1694 567 40630 8994 2265 557 96833 8987 2273 538 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 173 7439 171 4922 172 7439 171 4921 172 7466 174 4917 176 4915 173 4918 170 7441 174 7438 192 7445 175 55181 174 7437 173 4918 175 7437 173 4919 175 7463 177 4888 175 4917 176 4915 178 7460 170 7440 175 7437 178 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 1636 4610 1563 1533 1584 7760 1561 28769 1641 4606 1557 1539 1588 7757 1564 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 590 2404 479 1059 968 575 454 544 454 574 454 543 455 543 475 522 476 552 476 552 446 551 447 581 448 520 478 581 447 519 479 549 479 15967 587 2407 476 1062 476 522 995 547 451 1030 508 520 509 550 478 519 479 519 998 1028 999 1057 481 547 960 1066 482 111641 587 2407 476 1063 485 513 994 547 451 1031 507 551 477 551 477 520 478 550 968 1059 968 1027 511 548 959 1036 512 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 588 2406 477 1061 966 577 451 516 482 545 483 545 453 575 443 524 484 514 504 554 454 543 455 543 475 553 445 583 445 521 477 582 446 15969 585 2409 474 1065 483 514 993 549 449 1033 515 543 475 553 475 522 476 552 965 1030 997 576 452 1029 998 1028 479 111668 587 2407 475 1063 485 543 964 517 481 1031 507 552 476 551 477 520 478 550 968 1028 999 574 454 1027 990 1036 481 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 174 7439 196 7441 174 7437 173 7439 171 7441 174 7438 177 7460 170 7442 178 7433 177 4915 173 4918 170 55186 174 7438 172 7440 170 7441 174 7438 177 7461 169 7416 173 7464 176 7435 175 7437 173 4918 175 4917 176 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 173 7438 172 7441 174 7438 177 7434 176 7462 168 7443 177 7435 175 4917 176 4915 173 7438 177 4941 173 55166 174 7438 197 7441 174 7437 173 7439 171 7441 174 7438 177 7460 170 4922 171 4893 195 7443 172 4920 173 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 175 7436 179 4913 175 4917 171 4920 173 4945 169 4896 177 7435 175 7436 174 7464 176 4916 177 4914 169 55180 170 7441 169 4924 169 4922 171 4920 173 4919 174 4917 176 7435 175 7463 177 7434 176 4916 177 4914 174 # name: Power -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 927 827 1709 936 796 932 789 938 793 1717 1736 936 795 932 789 1721 927 827 1709 909 822 90851 898 829 1738 934 798 930 791 936 796 1715 1738 934 797 930 791 1719 899 828 1739 934 797 +type: parsed +protocol: RC5 +address: 02 00 00 00 +command: 0C 00 00 00 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 176 5991 177 1372 176 1320 177 1344 173 1349 174 1374 169 1327 170 4449 176 1346 171 1351 177 1345 172 1349 168 1353 175 5963 175 65573 173 5995 178 1344 174 1348 175 1348 174 1347 170 1378 170 1325 172 4447 178 1344 174 1349 169 1353 175 1347 170 1352 176 5961 177 # name: Power type: parsed protocol: NEC address: 71 00 00 00 -command: 4a 00 00 00 +command: 4A 00 00 00 # name: Power type: parsed @@ -982,26 +982,26 @@ command: 01 00 00 00 name: Power type: parsed protocol: NECext -address: 50 ad 00 00 +address: 50 AD 00 00 command: 00 00 00 00 # name: Power type: parsed protocol: NECext -address: 50 ad 00 00 +address: 50 AD 00 00 command: 02 00 00 00 # name: Power type: parsed protocol: NEC address: 50 00 00 00 -command: 3f 00 00 00 +command: 3F 00 00 00 # name: Power type: parsed protocol: Samsung32 address: 06 00 00 00 -command: 0f 00 00 00 +command: 0F 00 00 00 # name: Power type: parsed @@ -1013,13 +1013,13 @@ name: Power type: parsed protocol: Samsung32 address: 08 00 00 00 -command: 0b 00 00 00 +command: 0B 00 00 00 # name: Power type: parsed protocol: NECext address: 83 55 00 00 -command: c2 00 00 00 +command: C2 00 00 00 # name: Power type: parsed @@ -1030,20 +1030,20 @@ command: 51 00 00 00 name: Power type: parsed protocol: NECext -address: 00 bd 00 00 +address: 00 BD 00 00 command: 01 00 00 00 # name: Power type: parsed protocol: Samsung32 address: 00 00 00 00 -command: 0f 00 00 00 +command: 0F 00 00 00 # name: Power type: parsed protocol: Samsung32 address: 16 00 00 00 -command: 0f 00 00 00 +command: 0F 00 00 00 # name: Power type: parsed @@ -1064,561 +1064,561 @@ address: 86 02 00 00 command: 49 00 00 00 # name: Power -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 2383 609 1214 600 612 580 1213 608 614 583 1210 605 607 586 616 611 1192 599 613 608 614 579 613 615 607 26670 2387 601 1212 600 612 589 1214 603 609 586 1217 595 607 594 618 606 1187 602 610 609 613 588 614 610 612 +type: parsed +protocol: SIRC +address: 01 00 00 00 +command: 15 00 00 00 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 178 7761 176 11308 546 957 540 1958 538 970 537 1955 541 962 545 1953 543 964 543 962 545 957 540 970 548 960 547 1945 541 1950 546 965 542 1953 543 962 545 1945 540 970 537 1958 538 7881 172 7744 172 11318 536 971 536 1956 540 963 534 1964 542 966 541 1951 535 968 539 971 536 971 536 969 538 964 533 1965 541 1954 542 963 534 1956 540 971 536 1959 537 968 539 1951 535 # name: Power -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 3444 1767 413 486 420 1343 419 478 418 485 411 490 416 479 417 489 417 482 414 485 421 482 414 483 413 490 416 485 411 1343 419 487 419 480 416 483 413 490 416 482 414 488 418 483 413 483 413 492 414 1345 417 482 414 489 417 480 416 487 419 482 414 481 415 491 415 484 412 1347 415 488 418 1338 414 1348 414 1347 415 1340 412 494 412 487 419 1340 412 491 415 1341 421 1341 421 1340 412 1343 419 487 419 1339 413 74311 3445 1753 437 461 445 1316 446 456 440 455 441 465 441 458 438 461 445 458 438 460 436 466 440 461 445 451 445 460 446 1313 439 460 446 457 439 459 437 465 441 460 446 450 436 469 437 462 444 456 440 1322 440 457 439 464 442 459 437 459 437 468 438 461 445 455 441 462 444 1312 440 463 443 1317 445 1310 442 1323 439 1320 442 457 439 464 442 1315 437 466 440 1320 442 1313 439 1326 446 1313 439 460 446 1317 445 +type: parsed +protocol: Kaseikyo +address: 80 02 20 00 +command: D0 03 00 00 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 278 1845 274 808 271 806 273 812 278 805 275 805 274 1840 279 1844 275 809 281 1836 272 806 274 812 278 805 274 1842 277 802 277 44956 279 1842 277 804 275 802 277 808 271 811 279 1838 281 798 271 814 276 1844 275 806 273 1841 278 1845 274 1846 273 808 271 1843 276 44959 275 1845 274 807 272 805 275 811 279 804 275 805 274 1839 280 1844 275 808 271 1845 274 805 274 811 279 804 275 1841 278 801 278 44955 280 1841 278 802 277 801 278 807 272 810 280 1837 271 807 272 813 277 1843 276 805 274 1839 280 1843 276 1845 274 807 272 1842 277 # name: Power -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 881 909 1750 928 875 927 876 921 872 929 874 927 876 918 875 930 873 1815 874 924 1745 937 876 88694 880 922 1747 933 880 914 879 926 877 922 871 927 876 927 876 920 873 1818 871 929 1740 934 879 +type: parsed +protocol: RC5 +address: 00 00 00 00 +command: 0C 00 00 00 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 509 1717 504 629 512 631 510 627 504 633 508 633 508 1713 508 1719 512 1713 508 625 505 637 504 633 508 629 512 629 512 623 508 1719 512 626 505 628 513 631 510 627 514 623 507 632 509 1713 508 632 509 1716 505 1715 506 1724 507 1716 505 1719 512 1715 506 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 506 493 505 4059 505 5051 501 506 502 4065 499 511 497 4063 501 5058 504 504 504 4060 504 5052 500 507 501 4066 498 5056 506 5042 500 515 503 119614 504 505 503 4065 499 5051 501 511 497 4069 505 499 499 4072 502 5050 502 506 502 4066 498 5053 499 512 506 4060 504 5044 498 5061 501 508 500 # name: Power -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 8887 4470 532 1738 513 1708 533 1708 533 577 533 576 534 569 531 582 538 1705 536 571 539 1707 534 571 539 571 539 570 540 1699 532 581 539 568 532 575 535 576 534 571 539 571 539 569 541 1698 533 1717 534 1708 533 1710 531 1716 535 1706 535 1711 540 1705 536 567 533 580 540 567 533 39042 8915 2231 530 94849 8917 2256 535 +type: parsed +protocol: NECext +address: 87 22 00 00 +command: E0 1F 00 00 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 8313 4161 515 1574 514 1571 507 569 510 562 507 563 506 562 507 568 511 562 507 1580 508 1577 511 1582 506 567 513 1575 513 554 505 571 509 564 505 22604 513 1573 505 1589 509 563 506 564 505 562 507 569 511 562 507 563 506 1579 509 1584 514 1576 512 558 511 1574 514 562 507 565 515 556 513 22593 514 1581 507 1583 505 564 505 563 506 570 510 563 506 564 505 562 507 1586 512 1578 510 1577 511 557 512 1581 507 566 514 556 513 555 514 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 8735 4383 558 573 557 550 560 568 562 544 566 1722 560 540 560 1732 560 544 566 1719 563 1701 560 1723 559 1704 557 574 556 1699 562 574 556 1703 559 1727 565 1698 563 1721 561 546 564 1723 559 541 559 577 564 541 559 571 560 548 562 565 566 1697 565 567 564 1693 558 1733 559 1701 560 39926 8754 2247 565 92341 8758 2243 589 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 3298 3336 821 2506 825 881 820 2505 826 2529 823 856 825 2524 817 866 825 2528 813 2513 818 888 813 862 819 887 814 865 826 2522 820 864 827 2526 815 861 820 887 814 2510 821 885 816 863 818 2531 821 2512 819 2559 793 32401 3298 3349 818 2507 824 882 819 2509 822 2527 814 868 823 2530 821 855 826 2531 821 2504 827 879 822 857 824 875 816 867 824 2529 823 854 827 2530 821 853 817 889 822 2506 825 873 818 866 825 2527 814 2513 818 2564 788 # name: Power -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 8840 4439 532 566 534 566 534 1668 532 1674 536 1669 531 562 538 566 534 563 537 1667 533 567 533 563 537 563 537 562 538 1662 538 1671 529 568 532 565 535 566 534 1668 532 1674 536 1669 531 562 538 1672 528 1675 536 1668 532 1675 535 559 531 1676 534 565 535 558 532 1678 532 565 535 562 538 563 537 1665 535 565 535 1670 530 1669 531 572 538 1666 534 1669 531 1676 534 22777 8858 4447 535 92577 8858 4413 538 +type: parsed +protocol: NEC42 +address: 1C 01 00 00 +command: 12 00 00 00 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 289 2112 261 2109 295 2101 262 918 294 912 259 915 297 2098 265 916 296 909 262 2107 297 905 266 913 289 918 263 910 292 909 262 918 294 24789 263 2106 298 2098 265 2110 294 913 258 916 296 905 266 2108 296 911 260 914 288 2107 266 914 298 908 263 911 291 910 261 919 293 913 258 # name: Power -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 8898 4453 569 578 573 577 574 571 570 1745 567 1747 565 1743 569 583 568 579 572 1740 572 1744 568 1742 570 579 572 576 575 568 573 1746 566 1746 566 580 571 1745 567 577 574 576 575 1739 573 569 572 581 570 577 574 1738 574 576 575 1735 567 1748 574 574 567 1742 570 1748 574 1737 575 41005 8876 2261 571 93850 8898 2264 568 +type: parsed +protocol: NEC +address: 38 00 00 00 +command: 12 00 00 00 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 3215 1637 410 430 405 439 406 1242 408 435 410 1242 408 428 407 440 405 435 410 1240 410 1243 407 431 404 440 405 437 408 1238 402 1254 406 434 401 439 406 438 407 431 404 440 405 437 408 428 407 439 406 434 401 439 406 438 407 1241 409 434 401 441 404 432 403 444 401 1249 401 439 406 438 407 1241 409 434 401 441 404 433 402 444 401 1249 401 439 406 1248 402 436 409 434 401 441 404 433 402 444 401 439 406 45471 3239 1614 403 435 400 444 401 1250 400 437 408 1248 402 438 407 433 402 442 403 1245 405 1248 423 420 405 431 404 443 402 1248 402 1248 422 421 404 435 400 443 402 440 405 431 404 443 402 438 407 433 402 442 403 435 400 443 402 1250 400 436 399 447 408 432 403 438 407 1246 404 434 401 443 402 1250 400 436 399 447 408 432 403 437 408 1246 404 434 401 1253 407 434 401 436 399 447 408 432 403 437 408 436 399 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 7928 3948 504 515 503 518 500 1583 505 516 502 1584 504 510 508 516 502 516 502 3952 510 1579 509 507 501 1587 501 519 510 1571 507 518 500 517 501 517 501 23073 7931 3943 509 514 504 515 503 1578 500 524 505 1580 508 510 508 514 504 511 507 3951 511 1576 502 513 505 1586 502 515 503 1582 506 515 503 513 505 516 502 # name: Power -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 8837 4464 538 610 531 619 532 613 538 1748 534 1750 532 611 540 613 538 609 532 615 536 614 537 608 533 1753 539 1745 537 606 535 618 533 614 537 609 532 619 532 613 538 611 540 609 532 611 540 1748 534 1749 533 1750 532 1754 538 1743 539 1747 535 1750 532 1747 535 618 533 613 538 44105 8864 2224 537 93399 8912 2202 538 +type: parsed +protocol: NECext +address: 18 18 00 00 +command: C0 3F 00 00 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 492 4975 496 4993 498 498 500 4056 498 504 494 4055 499 4963 497 532 496 4031 492 4996 495 502 496 4060 494 4972 499 4990 491 506 492 4063 491 511 497 4052 492 4970 490 539 500 4027 496 103358 500 4961 500 4995 496 505 493 4056 498 499 499 4057 497 4969 491 533 496 4026 497 4997 494 508 490 4059 495 4967 494 5001 490 511 497 4053 491 505 493 4063 491 4975 496 528 490 4032 491 # name: Power -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 2357 610 1183 592 1191 614 1179 595 1188 618 584 613 1180 593 588 613 1190 612 590 605 587 613 589 605 587 25398 2355 623 1180 591 1181 627 1186 589 1183 618 584 616 1187 587 584 614 1189 615 587 605 587 615 587 609 593 +type: parsed +protocol: SIRC +address: 01 00 00 00 +command: 2F 00 00 00 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 3425 3482 848 2607 846 2639 845 2608 846 2639 845 2612 852 2624 850 2613 851 911 851 885 847 919 843 891 851 915 847 890 852 907 845 897 845 917 845 891 851 915 847 887 845 2639 845 2612 852 2625 849 2613 851 2630 844 34282 3455 3478 852 2601 852 2633 851 2605 848 2629 845 2617 847 2633 851 2604 849 917 845 890 852 913 849 889 843 915 847 896 846 916 846 890 852 914 848 885 847 919 843 895 847 2630 844 2617 847 2634 850 2605 848 2636 848 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 500 500 498 4066 498 5058 504 502 506 4061 503 508 500 4060 504 5055 497 5055 497 511 497 4071 503 5047 505 507 501 4065 499 5049 503 512 496 124314 501 508 500 4067 507 5043 499 513 505 4060 504 501 497 4073 501 5052 500 5052 500 512 496 4066 498 5058 504 506 502 4058 506 5053 499 509 499 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 176 7763 174 8817 169 10842 544 1955 541 967 540 1952 544 959 538 972 546 962 545 1947 538 1952 544 967 540 1955 541 964 543 1947 539 972 546 1949 547 7873 170 7746 170 10350 175 11811 543 960 537 1961 535 972 535 970 537 965 542 1956 540 1956 540 965 542 1947 539 973 534 1960 536 969 538 1952 534 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 2570 2682 1189 1208 1186 2665 1186 1217 1187 2668 1183 1215 1179 2671 1190 2695 1186 1187 1187 2692 1179 2671 1190 1213 1181 1193 1191 1207 1187 2663 1188 1215 1179 2676 1185 46941 2563 2685 1186 1191 1183 2698 1184 1188 1186 2691 1180 1197 1187 2694 1187 2666 1185 1210 1184 2674 1187 2695 1186 1185 1189 1206 1188 1189 1185 2696 1186 1186 1187 2689 1182 # name: Power -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 873 916 1773 906 877 925 878 919 874 928 875 925 878 1806 1770 914 879 1809 870 928 1772 88684 870 926 1774 908 875 926 877 917 876 929 874 925 878 1809 1777 905 877 1808 871 930 1770 +type: parsed +protocol: RC5 +address: 00 00 00 00 +command: 26 00 00 00 # name: Power -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 874 906 877 912 1767 904 879 908 874 918 875 915 878 907 875 920 873 1795 874 914 1775 897 875 88704 879 914 868 922 1767 897 875 919 874 915 878 911 872 920 873 914 879 1792 877 914 1775 889 873 +type: parsed +protocol: RC5 +address: 00 00 00 00 +command: 0C 00 00 00 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 3325 1560 406 444 401 453 402 1226 404 449 406 1226 404 443 402 454 401 449 406 1224 406 1228 402 446 409 444 401 451 404 1223 407 1230 410 440 405 445 410 443 402 446 409 444 401 451 404 442 403 453 402 448 407 443 402 451 404 1224 406 448 407 444 401 445 410 446 409 1221 409 442 403 1231 409 439 406 1227 403 450 405 441 404 452 403 1227 403 448 407 446 409 439 406 447 408 444 401 445 400 456 410 440 405 52348 3320 1553 403 445 400 454 401 1230 400 447 408 1228 402 449 406 444 401 452 403 1225 405 1229 431 421 404 442 403 454 401 1229 401 1229 431 423 402 446 399 455 400 451 404 442 403 453 402 448 407 443 402 451 404 444 401 452 403 1229 401 445 400 457 398 451 404 446 399 1235 405 443 402 1232 408 444 401 1225 405 452 403 447 398 452 403 1231 399 449 406 447 408 443 402 444 401 456 399 450 405 445 400 453 402 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 990 915 980 909 986 924 981 2829 981 934 981 2823 987 923 982 2828 982 933 982 906 989 33895 989 907 988 927 988 900 985 2841 979 915 980 2851 980 909 986 2840 980 914 981 934 981 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 821 5754 848 2490 841 2492 819 2524 817 5726 845 2492 839 5727 844 5757 845 5727 854 2483 848 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 5092 1621 406 2599 406 2598 407 2605 1653 1616 411 2619 1609 1606 1654 1618 409 2623 382 2600 405 # name: Power -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 8879 4446 566 1747 565 584 567 1744 568 581 570 1744 568 574 567 586 565 582 569 578 563 1753 570 575 566 1749 563 585 566 1743 569 1749 563 1748 564 582 569 1747 565 580 571 578 573 1741 571 572 569 584 567 580 571 1741 571 578 573 1738 564 1751 572 577 564 1744 568 1750 572 1740 572 41007 8906 2257 565 93855 8872 2265 567 +type: parsed +protocol: NEC +address: 15 00 00 00 +command: 12 00 00 00 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 1144 1010 6795 26754 1151 997 6798 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 1144 1009 1120 1006 1143 1991 1116 26758 1146 1006 1123 1003 1146 1988 1119 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 170 46238 169 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 8906 4165 572 1672 569 1678 563 640 572 637 565 643 569 633 569 643 569 1674 567 639 563 1684 567 637 565 1681 570 1675 566 1673 568 1681 570 636 566 640 572 637 565 639 563 1684 567 640 572 630 572 640 572 634 568 1675 566 1681 570 1670 571 638 564 1681 570 1669 572 1678 563 1680 571 40485 8898 2252 570 85621 8955 2194 567 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 446 1191 449 1194 446 1195 445 1204 446 1200 1316 459 447 1193 447 1202 448 1197 443 1201 449 1191 449 34491 443 1204 446 1197 443 1198 442 1207 443 1202 1314 436 440 1201 449 1199 441 1205 445 1198 442 1199 441 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 4268 4327 522 1593 516 1603 516 1597 522 519 520 520 519 515 514 531 518 520 519 519 520 521 518 519 520 1597 522 1595 514 1597 522 1599 520 1595 514 524 515 1604 515 521 518 523 516 524 515 519 520 525 514 524 515 1599 520 522 517 1596 513 1605 514 1602 517 1595 514 1607 522 1592 516 40481 8748 2187 523 93986 8721 2189 521 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 247 3006 244 153 178 1007 251 117 637 597 381 678 218 156 175 529 382 679 217 157 174 24963 247 3038 252 117 219 994 249 119 217 483 250 117 173 531 278 779 173 167 169 569 383 679 217 156 175 126538 246 3039 251 118 218 995 247 120 195 504 249 118 172 532 277 780 172 168 178 560 382 680 216 157 174 # name: Power -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 8872 4454 568 579 572 578 573 572 569 581 570 1744 568 575 566 587 564 582 569 1743 569 1747 565 1745 567 1748 564 584 567 1741 571 1747 565 1747 565 1747 565 1751 571 1739 563 1752 570 578 563 1745 567 1751 572 1741 571 575 566 585 566 578 573 577 564 1750 573 571 570 583 568 579 572 41007 8905 2258 574 93846 8871 2266 566 +type: parsed +protocol: NEC +address: 10 00 00 00 +command: EF 00 00 00 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 4278 4318 521 517 522 520 519 517 522 520 519 521 518 516 513 531 518 520 519 1595 514 1605 514 1599 520 1598 521 1595 513 1598 521 1600 519 1596 513 1602 517 1601 518 1595 514 1604 515 525 514 520 519 526 513 525 514 524 515 527 522 513 516 526 513 1603 516 1595 514 1607 522 1593 516 40481 8749 2186 524 93985 8722 2189 521 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 2746 8423 2744 19607 2746 19601 2742 8431 2745 8424 2742 19608 2745 8419 2747 19608 2745 19607 2746 8422 2744 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 170 6683 174 4889 175 10382 372 849 821 2498 176 10378 264 2479 842 2492 839 2475 846 2483 838 2481 840 2495 836 2477 844 845 846 37009 172 6681 176 4888 175 10381 373 848 924 2395 844 2490 174 10383 248 2492 172 10386 245 2495 169 # name: Power -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 8898 4453 569 578 573 577 574 1737 565 584 567 582 569 574 567 1751 572 1741 571 1741 571 1744 568 576 575 1741 571 1743 569 1739 573 579 572 575 566 581 570 580 571 574 567 1748 575 1739 573 570 571 582 569 578 573 1739 573 1742 570 1740 572 577 574 575 566 1742 570 1749 574 1738 574 41006 8875 2262 570 93850 8907 2256 566 +type: parsed +protocol: NEC +address: C4 00 00 00 +command: 18 00 00 00 # name: Power -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 8872 4454 568 1745 567 1749 563 1746 566 584 567 1747 565 1743 569 583 568 579 572 575 566 584 567 578 563 1752 571 578 563 580 571 1747 565 1747 565 581 570 1746 566 578 573 577 564 1751 572 571 570 583 568 579 572 1740 572 578 563 1747 565 1750 573 576 565 1743 569 1749 563 1749 563 41017 8906 2257 565 93855 8872 2265 567 +type: parsed +protocol: NEC +address: 37 00 00 00 +command: 12 00 00 00 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 179 2644 178 8174 170 2646 176 8181 173 2648 174 8177 177 2640 172 5419 174 5413 170 2649 173 2644 178 2646 176 2646 176 5409 174 28831 174 2651 171 8183 171 2648 174 8174 170 2655 177 8177 177 2642 170 5412 171 5420 173 2649 173 2646 176 2640 172 2653 169 5419 174 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 174 2648 174 8178 176 2640 171 8185 169 2653 169 8182 172 2645 177 2647 557 2264 558 5027 174 5410 173 5418 175 5413 170 28837 168 2649 173 8184 170 2652 170 8181 173 2643 169 8188 176 2646 176 2643 168 2648 174 5417 176 5411 172 5413 170 5413 170 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 178 706 266 139 268 137 173 173 198 234 178 126768 175 299 169 86 275 130 267 138 172 230 177 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 554 1922 584 3809 582 3782 578 3821 580 1920 555 1941 544 1922 574 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 476 1470 465 3479 474 3469 474 3477 475 1467 468 1471 474 27473 472 1474 472 3475 467 3478 475 3468 474 1471 475 1468 467 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 301 2188 236 2169 235 2205 230 1009 234 1070 183 1064 179 2198 206 1046 207 1069 173 2207 207 1042 211 1062 201 1043 210 1032 231 1005 237 1039 234 1006 236 # name: Power -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 8872 4453 570 1744 568 582 569 1741 571 1744 568 580 571 1738 564 1754 569 578 563 584 567 1749 563 581 570 580 571 1743 569 573 568 585 566 1746 566 580 571 580 571 1739 563 586 565 1749 563 579 572 582 569 577 564 1748 564 1752 571 574 567 1748 564 584 567 1742 570 1748 564 1747 565 41015 8898 2265 567 93853 8875 2262 570 +type: parsed +protocol: NEC +address: 6D 00 00 00 +command: 14 00 00 00 # name: Power -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 8904 4446 576 572 569 581 570 575 566 584 567 582 569 1739 573 579 572 575 566 1746 566 1750 572 1738 574 1741 571 1742 570 573 568 1750 572 1740 572 1740 572 578 573 572 569 581 570 1744 568 574 567 586 575 572 569 578 573 1742 570 1740 572 1743 569 579 572 1737 565 1753 570 1743 569 41010 8881 2257 565 93855 8903 2259 573 +type: parsed +protocol: NEC +address: 20 00 00 00 +command: 11 00 00 00 # name: Power -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 8878 4448 564 584 567 583 568 577 564 586 565 584 567 1741 571 582 569 1743 569 1743 569 1746 566 1744 568 1747 565 1749 563 579 572 1747 565 581 570 1743 569 1746 566 578 573 1743 569 579 572 571 570 583 568 579 572 575 566 584 567 1743 569 581 570 1744 568 1740 572 1746 566 1746 566 41013 8898 2264 568 93853 8872 2265 567 +type: parsed +protocol: NEC +address: A0 00 00 00 +command: 0B 00 00 00 # name: Power -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 8900 4450 572 575 566 585 566 578 573 1743 569 579 572 1736 566 587 574 572 569 1743 569 1747 565 1745 567 582 569 1745 567 575 566 1753 570 1742 570 1742 570 1746 566 578 573 1742 570 578 573 570 571 582 569 578 573 574 567 583 568 1742 570 579 572 1742 570 1738 574 1744 568 1744 568 41012 8871 2266 566 93855 8903 2258 574 +type: parsed +protocol: NEC +address: 28 00 00 00 +command: 0B 00 00 00 # name: Power -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 8872 4454 568 1745 567 1749 563 1747 565 1750 562 1752 571 1737 565 1754 568 1743 569 578 563 587 564 581 570 580 571 577 564 579 572 581 570 576 565 1748 564 1751 572 1739 563 1752 571 1743 569 1739 563 590 571 575 566 581 570 580 571 574 567 583 568 580 571 572 569 1750 562 1749 563 41017 8905 2257 575 93846 8871 2266 566 +type: parsed +protocol: NEC +address: FF 00 00 00 +command: 3F 00 00 00 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 176 7764 173 11361 544 1951 545 1948 176 8815 171 2319 177 2322 174 2321 175 2318 178 2313 173 18281 170 7746 170 11369 536 1954 542 1957 539 969 538 1954 542 1949 536 1962 534 1960 174 2320 176 2315 170 2328 178 2317 168 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 172 23035 176 2267 178 2285 170 2270 175 2288 177 11602 222 2218 216 2246 173 183 173 935 221 218 174 864 221 1250 171 1310 223 2219 216 2247 218 1244 223 216 176 869 170 1296 217 2239 216 1254 223 216 176 866 219 9127 169 7642 173 2284 171 2273 172 2290 175 2263 171 10143 178 10623 222 1245 222 217 175 1843 220 2226 219 1264 223 1237 174 183 178 952 219 2222 223 216 176 866 219 1249 172 9190 173 7637 178 2266 169 2286 169 2280 175 2284 171 10125 175 10622 224 215 177 868 216 2228 217 2238 171 1299 224 215 177 865 174 183 173 934 222 216 176 1848 215 1247 220 1265 222 762 170 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 7410 1482 382 2742 375 2747 380 2751 1630 1535 400 2724 1657 1529 1629 1538 407 2720 407 2716 401 2722 4125 1549 376 2751 376 2748 379 2744 1626 1541 405 2723 1658 1530 1628 1531 404 2726 401 2726 401 2723 4124 1542 383 2747 380 2747 380 2745 1626 1534 401 2729 1652 1539 1629 1532 403 2719 408 2722 405 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 4277 4319 520 518 521 521 518 518 521 1597 522 1594 515 1597 522 1599 520 1594 515 1600 519 1600 519 1594 515 526 513 527 522 512 517 528 521 517 522 516 513 1605 514 522 517 525 514 525 514 521 518 526 513 525 514 1600 519 523 516 1597 522 1596 513 1603 516 1595 514 1607 522 1593 516 40481 8749 2186 524 93986 8721 2188 522 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 1325 431 445 1199 1317 455 441 1207 443 1202 1294 457 449 1190 440 1209 441 1205 445 1198 1318 454 442 93237 1320 434 442 1201 1325 448 448 1200 440 1205 1291 460 446 1193 447 1202 448 1198 442 1201 1325 447 449 # name: Power -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 8876 4450 572 575 566 585 566 578 573 577 564 585 566 577 564 589 572 574 567 1745 567 1749 563 1747 565 1750 562 1751 571 1737 565 1754 569 1743 569 1743 569 1747 565 579 572 1743 569 579 572 571 570 583 568 579 572 575 566 584 567 1743 569 581 570 1744 568 1740 572 1746 566 1746 566 41013 8898 2265 567 93853 8873 2264 568 +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 0B 00 00 00 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 300 1791 297 744 295 743 296 751 298 745 294 747 302 736 293 1837 271 745 294 747 302 1793 295 752 297 1802 296 1801 297 742 297 1806 302 31592 296 1801 297 742 297 749 300 744 295 745 294 745 294 752 297 1829 269 746 293 745 294 1809 300 744 295 1802 296 1799 299 748 301 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 178 2003 177 2899 177 1996 174 2908 168 2910 177 2000 170 2004 176 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 8898 4173 564 642 570 1677 564 1677 564 645 567 640 572 631 571 641 571 635 567 639 563 1683 568 1673 568 641 571 636 566 637 565 647 565 641 571 634 568 642 570 1671 570 639 563 1682 569 632 570 643 569 636 566 1677 564 1683 568 636 566 1680 571 636 566 1674 567 1682 569 1674 567 40489 8904 2246 566 85626 8902 2248 564 # name: Power -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 8897 4454 569 578 573 1743 569 576 565 1750 572 576 575 1733 569 584 567 1745 567 1745 567 583 568 1742 570 579 572 1742 570 573 568 1750 573 574 567 580 571 580 571 573 568 582 569 580 571 572 569 584 567 1744 568 1744 568 1748 575 1735 567 1749 574 1740 572 1736 566 1752 571 576 575 41005 8878 2259 563 93858 8901 2260 572 +type: parsed +protocol: NEC +address: AA 00 00 00 +command: 80 00 00 00 # name: Power -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 8880 4446 566 1747 565 585 566 578 573 577 564 1750 573 571 570 1748 564 582 569 578 573 1743 569 1741 571 1744 568 580 571 1737 565 588 563 1749 563 583 568 582 569 576 565 1750 573 576 565 578 573 580 571 576 565 1747 565 1751 571 1738 564 586 565 1749 563 1745 567 1751 572 1741 571 41008 8905 2258 574 93846 8871 2266 566 +type: parsed +protocol: NEC +address: 51 00 00 00 +command: 08 00 00 00 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 3892 3856 525 978 529 977 530 969 528 977 530 974 523 975 532 1924 531 971 526 1924 531 975 532 1916 529 976 531 1921 524 1947 508 1925 530 1920 525 1926 529 1925 530 970 527 1927 528 976 531 1915 530 978 529 1921 1033 9201 3871 3867 524 977 530 975 522 981 526 972 525 983 524 978 529 1921 524 982 525 1923 532 973 524 1928 527 971 526 1931 524 1950 505 1922 523 1931 524 1924 531 1923 532 972 525 1922 523 985 533 1918 527 975 532 1922 1032 # name: Power -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 8900 4451 571 576 575 1742 570 1739 573 1742 570 578 573 1736 566 1752 570 576 575 1737 575 575 566 579 572 578 573 1741 571 571 570 583 568 1745 567 579 572 578 573 1738 574 575 566 1748 575 568 573 581 570 576 575 1737 565 1751 572 573 568 1747 576 573 568 1741 571 1747 565 1747 565 41014 8878 2260 562 93858 8901 2261 571 +type: parsed +protocol: NEC +address: 6E 00 00 00 +command: 14 00 00 00 # name: Power -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 875 904 1745 924 879 913 880 1786 873 919 874 917 1742 922 871 1802 877 912 1747 921 872 88714 880 907 1742 930 873 917 876 1788 871 924 879 910 1749 919 874 1798 871 916 1743 928 875 +type: parsed +protocol: RC5 +address: 07 00 00 00 +command: 0C 00 00 00 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 4758 1543 403 2731 407 2726 402 2739 1601 1514 401 2760 1580 1530 1577 1540 406 2758 400 2708 1602 1535 400 2740 408 2729 409 2726 401 2731 1599 1520 405 2758 1572 1540 1578 1532 403 2737 431 2706 1604 1535 411 2722 405 2734 403 2734 404 2731 1599 1512 403 2764 1576 1539 1578 1533 402 2730 428 2712 1608 1534 402 # name: Power -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 8873 4453 570 578 563 1753 570 575 566 1749 563 585 566 1742 570 583 568 1744 568 1744 568 582 569 1741 571 578 573 1741 571 572 569 1749 563 583 568 1745 567 1748 564 1746 566 583 568 581 570 1738 564 589 572 1740 572 574 567 584 567 577 564 1752 571 1743 569 573 568 1751 572 575 566 41014 8900 2263 569 93851 8878 2259 563 +type: parsed +protocol: NEC +address: AA 00 00 00 +command: A7 00 00 00 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 171 313 176 798 337 133 600 232 170 126777 176 65 169 496 176 200 609 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 500 527 491 4033 500 4986 495 510 498 4054 500 499 499 4048 496 4974 497 530 499 4026 497 4989 492 513 495 4057 497 4967 493 4992 499 506 492 4060 494 505 493 4054 500 98563 497 529 500 4025 498 4988 493 512 496 4056 498 501 497 4050 493 4976 495 532 497 4028 495 4991 500 505 493 4059 495 4969 491 4994 497 508 490 4062 492 507 491 4056 498 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 879 901 871 1796 1770 903 869 917 876 916 877 913 880 906 877 918 875 914 879 1788 1768 1784 875 87826 871 921 872 1797 1769 897 875 919 874 915 878 910 873 920 873 914 879 913 870 1800 1776 1767 871 # name: Power -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 874 905 1774 894 878 914 879 908 875 917 876 915 878 907 876 919 874 1793 876 913 1777 895 878 88703 872 920 1769 901 872 913 870 925 878 910 873 916 877 916 877 910 873 1798 871 919 1770 894 878 +type: parsed +protocol: RC5 +address: 00 00 00 00 +command: 0C 00 00 00 # name: Power -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 3339 1714 415 445 420 1283 418 440 415 448 418 444 422 435 420 446 420 440 415 445 421 443 412 445 421 443 412 449 416 1279 412 455 421 439 416 443 412 452 414 444 411 452 414 1287 414 442 413 453 413 1287 414 446 419 444 422 437 418 445 421 441 414 442 413 452 414 447 419 1281 420 443 412 1285 416 1287 414 1287 414 1282 419 447 419 441 414 1286 415 448 418 1280 421 1282 419 443 412 1283 418 448 418 1283 418 73871 3336 1694 414 444 411 1291 410 452 414 442 413 453 413 448 418 442 413 450 416 443 412 450 416 447 419 437 418 448 418 1282 419 441 414 449 417 442 413 449 417 446 420 436 419 1287 414 446 420 440 415 1288 413 445 410 453 413 449 417 439 416 450 416 445 421 439 416 447 419 1279 412 451 415 1287 414 1282 419 1287 414 1285 416 444 411 453 413 1285 416 447 419 1283 418 1278 413 453 413 1287 414 446 419 1284 417 +type: parsed +protocol: Kaseikyo +address: 90 02 20 00 +command: D0 03 00 00 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 4411 4332 558 1660 561 596 565 612 559 597 564 617 565 585 566 620 561 591 560 620 561 595 566 611 560 597 564 1653 558 592 559 627 565 589 562 617 564 1630 560 617 564 592 559 22591 4439 4322 558 1639 561 618 563 590 561 622 560 592 559 624 557 597 564 612 559 600 561 618 563 590 561 622 559 1628 562 621 561 594 567 609 562 597 564 1652 559 595 566 617 564 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 3444 1767 413 487 419 1274 417 481 415 1277 414 488 418 1267 414 492 414 1276 415 484 412 1282 419 478 418 1275 416 1276 415 480 416 1280 421 479 417 1272 419 1275 416 1272 419 1274 417 484 412 484 412 493 413 1277 414 485 421 1273 418 479 417 486 420 1271 420 476 420 486 420 479 417 482 414 1280 411 1276 415 488 418 1273 418 478 418 488 418 481 415 1275 416 487 419 478 418 485 411 1280 421 475 421 1275 416 1273 418 69071 3439 1759 441 456 440 1253 438 463 443 1243 438 468 438 1251 440 460 446 1247 444 454 442 1251 440 461 445 1241 440 1256 445 455 441 1248 443 461 445 1242 439 1254 447 1245 446 1240 441 465 441 458 438 462 444 1249 442 456 440 1252 439 463 443 452 444 1252 439 461 445 454 442 461 445 452 444 1249 442 1250 441 454 442 1254 437 463 443 456 440 463 443 1245 446 456 440 462 444 451 445 1251 440 460 436 1253 438 1256 445 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 8705 4317 583 682 581 688 585 678 585 1721 581 1722 580 681 582 690 583 682 581 1721 581 1725 587 1713 579 689 584 683 580 1718 584 1724 588 1714 588 677 586 683 580 683 580 1726 586 680 583 678 585 687 586 679 584 1718 584 1721 581 1719 583 685 588 1716 586 1713 579 1729 583 1719 583 41145 8706 2217 585 94686 8705 2217 584 # name: Power -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 8888 4470 532 576 534 576 534 571 539 572 538 1706 535 568 532 581 529 578 532 1711 530 581 529 1711 530 1717 534 574 536 1703 538 575 535 572 538 1705 536 1711 530 1711 530 1716 535 1709 532 571 529 585 535 571 529 579 531 579 531 574 536 574 536 573 537 1701 530 1720 531 1712 529 39045 8912 2261 540 94838 8911 2235 536 +type: parsed +protocol: NECext +address: 10 2D 00 00 +command: 1F E0 00 00 # name: Power -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 874 915 1774 904 879 923 870 1816 1770 1800 1776 904 879 1805 874 931 1769 909 874 88698 876 927 1773 904 879 922 871 1819 1767 1796 1770 914 879 1809 870 928 1772 910 873 +type: parsed +protocol: RC5 +address: 05 00 00 00 +command: 0C 00 00 00 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 178 7753 174 2308 178 2284 171 2316 170 2298 177 10228 174 10724 223 1256 221 217 175 1868 169 2293 172 1327 216 1263 178 1316 217 2245 220 218 174 887 218 1261 170 10707 174 7752 175 2296 169 2314 171 2293 172 2307 179 10216 176 6265 174 10729 219 1258 219 1272 215 1268 173 2310 221 1255 222 217 175 877 172 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 532 1692 559 561 529 574 567 559 531 566 564 555 535 1694 557 568 532 1691 560 560 530 573 557 568 532 565 565 555 535 567 563 1688 533 564 567 554 536 567 563 562 538 558 562 558 532 1697 565 561 539 1683 558 1689 532 1697 564 1687 534 1689 562 1684 537 # name: Power -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 874 915 1774 904 879 923 870 927 876 1814 876 925 1775 900 872 1821 879 920 1769 909 874 88702 871 926 1774 907 876 925 878 917 876 1817 873 927 1773 905 878 1813 876 921 1769 912 871 +type: parsed +protocol: RC5 +address: 03 00 00 00 +command: 0C 00 00 00 # name: Power -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 8884 4474 538 569 531 1716 535 570 530 580 530 1715 536 567 533 580 530 577 533 1710 531 1715 536 1705 536 1710 531 1714 537 1702 529 1720 531 1712 529 578 532 1715 536 1705 536 1710 531 577 533 570 530 584 536 571 529 1714 537 573 537 568 532 578 532 1713 538 1701 530 1719 532 1711 530 39044 8913 2260 531 94848 8907 2239 532 +type: parsed +protocol: NECext +address: 12 FF 00 00 +command: 0E F1 00 00 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 446 1694 455 1706 453 1705 424 628 452 597 452 622 458 1673 456 625 455 594 455 1706 454 590 459 621 459 591 458 616 453 591 458 622 539 22750 459 1675 454 1733 427 1712 427 622 458 588 451 622 458 1681 458 618 451 595 454 1705 455 598 451 625 454 592 457 616 453 598 451 626 535 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 3429 3445 875 2555 868 875 867 871 871 2560 873 868 874 2551 872 874 868 871 871 869 873 870 872 865 867 2565 868 873 869 2556 867 2567 866 874 868 2560 873 870 872 2555 868 2564 869 2586 847 2577 846 2589 844 870 872 32983 3470 3430 869 2559 874 868 874 867 875 2550 873 873 869 2559 874 865 867 877 875 862 870 873 869 872 870 2555 868 877 875 2554 869 2559 874 869 873 2554 869 873 869 2562 871 2553 870 2590 843 2586 847 2581 842 876 866 # name: Power -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 8873 4452 571 1743 569 580 571 574 567 583 568 1746 566 1742 570 1748 564 582 569 578 563 1753 570 1740 572 1743 569 579 572 571 570 583 568 1744 568 579 572 578 573 572 569 1746 566 582 569 574 567 586 565 582 569 1743 569 1746 566 1744 568 581 570 1745 567 1741 571 1747 565 1746 566 41014 8898 2264 568 93853 8874 2263 569 +type: parsed +protocol: NEC +address: 71 00 00 00 +command: 08 00 00 00 # name: Power -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 8902 4448 574 1739 573 1742 570 575 566 584 567 581 570 573 568 585 566 1746 566 580 571 580 571 1739 573 1742 570 1743 569 1739 573 1745 567 579 572 1741 571 1744 568 1742 570 1745 567 1747 565 1743 569 1749 573 1739 573 573 568 583 568 576 575 575 566 583 568 575 566 587 574 572 569 41011 8871 2266 566 93854 8902 2260 572 +type: parsed +protocol: NEC +address: 83 00 00 00 +command: FF 00 00 00 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 4275 4320 519 520 519 523 516 520 519 1599 520 520 519 515 524 521 518 520 519 1595 524 1595 524 1588 520 521 518 1599 520 1591 517 1603 516 1599 520 1595 524 1594 525 1588 521 1597 522 518 521 513 526 519 520 518 521 517 522 520 519 517 522 519 520 1597 522 1589 519 1601 518 1597 522 40475 8724 2186 514 93995 8752 2183 516 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 10380 4892 599 620 592 605 597 620 592 604 598 623 589 2081 598 627 595 2080 599 2101 598 2105 574 2099 590 2088 591 2111 599 591 590 2116 594 599 593 626 596 2082 597 619 593 2086 593 626 596 594 598 627 595 598 594 2106 593 604 598 2100 589 607 595 2107 593 2079 590 2116 594 2082 750 41149 8722 2109 590 94682 8727 2104 596 # name: Power -type: raw -frequency: 38000 -duty_cycle: 0.33 -data: 8879 4446 566 1747 565 1751 571 573 568 582 569 580 571 571 570 584 567 1744 568 579 572 578 573 1737 565 1751 572 1742 570 1738 564 1754 568 578 573 574 567 584 567 577 564 1752 571 577 564 580 571 582 569 577 564 1748 564 1752 571 1739 563 587 564 1750 572 1736 566 1752 571 1742 570 41009 8903 2260 572 93848 8880 2257 565 +type: parsed +protocol: NEC +address: 83 00 00 00 +command: 08 00 00 00 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 4271 4324 515 1600 519 1600 519 1594 515 1603 516 1601 518 1593 516 1605 514 1601 518 520 519 522 517 520 519 522 517 523 516 518 521 523 516 522 517 1598 521 1597 522 1591 518 1600 519 521 518 516 523 522 517 521 518 520 519 523 516 520 519 522 517 1599 520 1591 518 1603 516 1599 520 40477 8753 2183 516 93993 8724 2185 515 # name: Power type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 7847 3931 470 1448 467 495 472 1443 472 490 467 1451 464 491 466 499 468 491 466 4413 467 1455 470 486 471 1449 466 495 472 1441 464 501 466 492 465 494 463 22093 7851 3934 467 1454 471 490 467 1446 469 496 471 1446 469 489 468 494 473 484 473 4410 470 1449 466 489 468 1455 470 489 468 1449 466 496 471 486 471 490 467 # name: Power @@ -1656,20 +1656,20 @@ type: parsed protocol: RC5 address: 01 00 00 00 command: 21 00 00 00 -# +# # Model: VIZIO name: Mute type: parsed protocol: NEC address: 04 00 00 00 command: 09 00 00 00 -# +# name: Vol_up type: parsed protocol: NEC address: 04 00 00 00 command: 02 00 00 00 -# +# name: Vol_dn type: parsed protocol: NEC diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index ac7d1a0c575..3d7bd7cd26a 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,11.6,, +Version,+,11.7,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -1630,6 +1630,7 @@ Function,+,infrared_get_protocol_by_name,InfraredProtocol,const char* Function,+,infrared_get_protocol_command_length,uint8_t,InfraredProtocol Function,+,infrared_get_protocol_duty_cycle,float,InfraredProtocol Function,+,infrared_get_protocol_frequency,uint32_t,InfraredProtocol +Function,+,infrared_get_protocol_min_repeat_count,size_t,InfraredProtocol Function,+,infrared_get_protocol_name,const char*,InfraredProtocol Function,+,infrared_is_protocol_valid,_Bool,InfraredProtocol Function,+,infrared_reset_decoder,void,InfraredDecoderHandler* diff --git a/lib/infrared/encoder_decoder/common/infrared_common_decoder.c b/lib/infrared/encoder_decoder/common/infrared_common_decoder.c index 8acb6751ba0..d67b204f52d 100644 --- a/lib/infrared/encoder_decoder/common/infrared_common_decoder.c +++ b/lib/infrared/encoder_decoder/common/infrared_common_decoder.c @@ -1,11 +1,8 @@ +#include "infrared_common_i.h" + +#include #include #include -#include "infrared.h" -#include "infrared_common_i.h" -#include -#include -#include "infrared_i.h" -#include static void infrared_common_decoder_reset_state(InfraredCommonDecoder* decoder); diff --git a/lib/infrared/encoder_decoder/common/infrared_common_encoder.c b/lib/infrared/encoder_decoder/common/infrared_common_encoder.c index 9c774617e96..f145a585a95 100644 --- a/lib/infrared/encoder_decoder/common/infrared_common_encoder.c +++ b/lib/infrared/encoder_decoder/common/infrared_common_encoder.c @@ -1,10 +1,9 @@ -#include -#include "infrared.h" #include "infrared_common_i.h" -#include -#include -#include "infrared_i.h" -#include + +#include +#include +#include +#include static InfraredStatus infrared_common_encode_bits(InfraredCommonEncoder* encoder, uint32_t* duration, bool* level) { diff --git a/lib/infrared/encoder_decoder/common/infrared_common_protocol_defs.c b/lib/infrared/encoder_decoder/common/infrared_common_protocol_defs.c deleted file mode 100644 index 3dd26e9d87b..00000000000 --- a/lib/infrared/encoder_decoder/common/infrared_common_protocol_defs.c +++ /dev/null @@ -1,140 +0,0 @@ -#include "infrared_common_i.h" -#include "infrared_protocol_defs_i.h" - -const InfraredCommonProtocolSpec protocol_nec = { - .timings = - { - .preamble_mark = INFRARED_NEC_PREAMBLE_MARK, - .preamble_space = INFRARED_NEC_PREAMBLE_SPACE, - .bit1_mark = INFRARED_NEC_BIT1_MARK, - .bit1_space = INFRARED_NEC_BIT1_SPACE, - .bit0_mark = INFRARED_NEC_BIT0_MARK, - .bit0_space = INFRARED_NEC_BIT0_SPACE, - .preamble_tolerance = INFRARED_NEC_PREAMBLE_TOLERANCE, - .bit_tolerance = INFRARED_NEC_BIT_TOLERANCE, - .silence_time = INFRARED_NEC_SILENCE, - .min_split_time = INFRARED_NEC_MIN_SPLIT_TIME, - }, - .databit_len[0] = 42, - .databit_len[1] = 32, - .no_stop_bit = false, - .decode = infrared_common_decode_pdwm, - .encode = infrared_common_encode_pdwm, - .interpret = infrared_decoder_nec_interpret, - .decode_repeat = infrared_decoder_nec_decode_repeat, - .encode_repeat = infrared_encoder_nec_encode_repeat, -}; - -const InfraredCommonProtocolSpec protocol_samsung32 = { - .timings = - { - .preamble_mark = INFRARED_SAMSUNG_PREAMBLE_MARK, - .preamble_space = INFRARED_SAMSUNG_PREAMBLE_SPACE, - .bit1_mark = INFRARED_SAMSUNG_BIT1_MARK, - .bit1_space = INFRARED_SAMSUNG_BIT1_SPACE, - .bit0_mark = INFRARED_SAMSUNG_BIT0_MARK, - .bit0_space = INFRARED_SAMSUNG_BIT0_SPACE, - .preamble_tolerance = INFRARED_SAMSUNG_PREAMBLE_TOLERANCE, - .bit_tolerance = INFRARED_SAMSUNG_BIT_TOLERANCE, - .silence_time = INFRARED_SAMSUNG_SILENCE, - .min_split_time = INFRARED_SAMSUNG_MIN_SPLIT_TIME, - }, - .databit_len[0] = 32, - .no_stop_bit = false, - .decode = infrared_common_decode_pdwm, - .encode = infrared_common_encode_pdwm, - .interpret = infrared_decoder_samsung32_interpret, - .decode_repeat = infrared_decoder_samsung32_decode_repeat, - .encode_repeat = infrared_encoder_samsung32_encode_repeat, -}; - -const InfraredCommonProtocolSpec protocol_rc6 = { - .timings = - { - .preamble_mark = INFRARED_RC6_PREAMBLE_MARK, - .preamble_space = INFRARED_RC6_PREAMBLE_SPACE, - .bit1_mark = INFRARED_RC6_BIT, - .preamble_tolerance = INFRARED_RC6_PREAMBLE_TOLERANCE, - .bit_tolerance = INFRARED_RC6_BIT_TOLERANCE, - .silence_time = INFRARED_RC6_SILENCE, - .min_split_time = INFRARED_RC6_MIN_SPLIT_TIME, - }, - .databit_len[0] = - 1 + 3 + 1 + 8 + - 8, // start_bit + 3 mode bits, + 1 toggle bit (x2 timing) + 8 address + 8 command - .manchester_start_from_space = false, - .decode = infrared_decoder_rc6_decode_manchester, - .encode = infrared_encoder_rc6_encode_manchester, - .interpret = infrared_decoder_rc6_interpret, - .decode_repeat = NULL, - .encode_repeat = NULL, -}; - -const InfraredCommonProtocolSpec protocol_rc5 = { - .timings = - { - .preamble_mark = 0, - .preamble_space = 0, - .bit1_mark = INFRARED_RC5_BIT, - .preamble_tolerance = 0, - .bit_tolerance = INFRARED_RC5_BIT_TOLERANCE, - .silence_time = INFRARED_RC5_SILENCE, - .min_split_time = INFRARED_RC5_MIN_SPLIT_TIME, - }, - .databit_len[0] = 1 + 1 + 1 + 5 + - 6, // start_bit + start_bit/command_bit + toggle_bit + 5 address + 6 command - .manchester_start_from_space = true, - .decode = infrared_common_decode_manchester, - .encode = infrared_common_encode_manchester, - .interpret = infrared_decoder_rc5_interpret, - .decode_repeat = NULL, - .encode_repeat = NULL, -}; - -const InfraredCommonProtocolSpec protocol_sirc = { - .timings = - { - .preamble_mark = INFRARED_SIRC_PREAMBLE_MARK, - .preamble_space = INFRARED_SIRC_PREAMBLE_SPACE, - .bit1_mark = INFRARED_SIRC_BIT1_MARK, - .bit1_space = INFRARED_SIRC_BIT1_SPACE, - .bit0_mark = INFRARED_SIRC_BIT0_MARK, - .bit0_space = INFRARED_SIRC_BIT0_SPACE, - .preamble_tolerance = INFRARED_SIRC_PREAMBLE_TOLERANCE, - .bit_tolerance = INFRARED_SIRC_BIT_TOLERANCE, - .silence_time = INFRARED_SIRC_SILENCE, - .min_split_time = INFRARED_SIRC_MIN_SPLIT_TIME, - }, - .databit_len[0] = 20, - .databit_len[1] = 15, - .databit_len[2] = 12, - .no_stop_bit = true, - .decode = infrared_common_decode_pdwm, - .encode = infrared_common_encode_pdwm, - .interpret = infrared_decoder_sirc_interpret, - .decode_repeat = NULL, - .encode_repeat = infrared_encoder_sirc_encode_repeat, -}; - -const InfraredCommonProtocolSpec protocol_kaseikyo = { - .timings = - { - .preamble_mark = INFRARED_KASEIKYO_PREAMBLE_MARK, - .preamble_space = INFRARED_KASEIKYO_PREAMBLE_SPACE, - .bit1_mark = INFRARED_KASEIKYO_BIT1_MARK, - .bit1_space = INFRARED_KASEIKYO_BIT1_SPACE, - .bit0_mark = INFRARED_KASEIKYO_BIT0_MARK, - .bit0_space = INFRARED_KASEIKYO_BIT0_SPACE, - .preamble_tolerance = INFRARED_KASEIKYO_PREAMBLE_TOLERANCE, - .bit_tolerance = INFRARED_KASEIKYO_BIT_TOLERANCE, - .silence_time = INFRARED_KASEIKYO_SILENCE, - .min_split_time = INFRARED_KASEIKYO_MIN_SPLIT_TIME, - }, - .databit_len[0] = 48, - .no_stop_bit = false, - .decode = infrared_common_decode_pdwm, - .encode = infrared_common_encode_pdwm, - .interpret = infrared_decoder_kaseikyo_interpret, - .decode_repeat = NULL, - .encode_repeat = NULL, -}; diff --git a/lib/infrared/encoder_decoder/infrared.c b/lib/infrared/encoder_decoder/infrared.c index 2c5ef0fffd2..fcfc5da2b22 100644 --- a/lib/infrared/encoder_decoder/infrared.c +++ b/lib/infrared/encoder_decoder/infrared.c @@ -1,13 +1,16 @@ #include "infrared.h" -#include -#include "common/infrared_common_i.h" -#include "infrared_protocol_defs_i.h" -#include -#include + #include -#include -#include "infrared_i.h" -#include +#include +#include +#include + +#include "nec/infrared_protocol_nec.h" +#include "samsung/infrared_protocol_samsung.h" +#include "rc5/infrared_protocol_rc5.h" +#include "rc6/infrared_protocol_rc6.h" +#include "sirc/infrared_protocol_sirc.h" +#include "kaseikyo/infrared_protocol_kaseikyo.h" typedef struct { InfraredAlloc alloc; @@ -36,7 +39,7 @@ struct InfraredEncoderHandler { typedef struct { InfraredEncoders encoder; InfraredDecoders decoder; - InfraredGetProtocolSpec get_protocol_spec; + InfraredGetProtocolVariant get_protocol_variant; } InfraredEncoderDecoder; static const InfraredEncoderDecoder infrared_encoder_decoder[] = { @@ -52,7 +55,7 @@ static const InfraredEncoderDecoder infrared_encoder_decoder[] = { .encode = infrared_encoder_nec_encode, .reset = infrared_encoder_nec_reset, .free = infrared_encoder_nec_free}, - .get_protocol_spec = infrared_nec_get_spec, + .get_protocol_variant = infrared_protocol_nec_get_variant, }, { .decoder = @@ -66,7 +69,7 @@ static const InfraredEncoderDecoder infrared_encoder_decoder[] = { .encode = infrared_encoder_samsung32_encode, .reset = infrared_encoder_samsung32_reset, .free = infrared_encoder_samsung32_free}, - .get_protocol_spec = infrared_samsung32_get_spec, + .get_protocol_variant = infrared_protocol_samsung32_get_variant, }, { .decoder = @@ -80,7 +83,7 @@ static const InfraredEncoderDecoder infrared_encoder_decoder[] = { .encode = infrared_encoder_rc5_encode, .reset = infrared_encoder_rc5_reset, .free = infrared_encoder_rc5_free}, - .get_protocol_spec = infrared_rc5_get_spec, + .get_protocol_variant = infrared_protocol_rc5_get_variant, }, { .decoder = @@ -94,7 +97,7 @@ static const InfraredEncoderDecoder infrared_encoder_decoder[] = { .encode = infrared_encoder_rc6_encode, .reset = infrared_encoder_rc6_reset, .free = infrared_encoder_rc6_free}, - .get_protocol_spec = infrared_rc6_get_spec, + .get_protocol_variant = infrared_protocol_rc6_get_variant, }, { .decoder = @@ -108,7 +111,7 @@ static const InfraredEncoderDecoder infrared_encoder_decoder[] = { .encode = infrared_encoder_sirc_encode, .reset = infrared_encoder_sirc_reset, .free = infrared_encoder_sirc_free}, - .get_protocol_spec = infrared_sirc_get_spec, + .get_protocol_variant = infrared_protocol_sirc_get_variant, }, { .decoder = @@ -122,13 +125,12 @@ static const InfraredEncoderDecoder infrared_encoder_decoder[] = { .encode = infrared_encoder_kaseikyo_encode, .reset = infrared_encoder_kaseikyo_reset, .free = infrared_encoder_kaseikyo_free}, - .get_protocol_spec = infrared_kaseikyo_get_spec, + .get_protocol_variant = infrared_protocol_kaseikyo_get_variant, }, }; static int infrared_find_index_by_protocol(InfraredProtocol protocol); -static const InfraredProtocolSpecification* - infrared_get_spec_by_protocol(InfraredProtocol protocol); +static const InfraredProtocolVariant* infrared_get_variant_by_protocol(InfraredProtocol protocol); const InfraredMessage* infrared_decode(InfraredDecoderHandler* handler, bool level, uint32_t duration) { @@ -224,7 +226,7 @@ void infrared_free_encoder(InfraredEncoderHandler* handler) { static int infrared_find_index_by_protocol(InfraredProtocol protocol) { for(size_t i = 0; i < COUNT_OF(infrared_encoder_decoder); ++i) { - if(infrared_encoder_decoder[i].get_protocol_spec(protocol)) { + if(infrared_encoder_decoder[i].get_protocol_variant(protocol)) { return i; } } @@ -282,34 +284,37 @@ InfraredProtocol infrared_get_protocol_by_name(const char* protocol_name) { return InfraredProtocolUnknown; } -static const InfraredProtocolSpecification* - infrared_get_spec_by_protocol(InfraredProtocol protocol) { +static const InfraredProtocolVariant* infrared_get_variant_by_protocol(InfraredProtocol protocol) { int index = infrared_find_index_by_protocol(protocol); - const InfraredProtocolSpecification* spec = NULL; + const InfraredProtocolVariant* variant = NULL; if(index >= 0) { - spec = infrared_encoder_decoder[index].get_protocol_spec(protocol); + variant = infrared_encoder_decoder[index].get_protocol_variant(protocol); } - furi_assert(spec); - return spec; + furi_assert(variant); + return variant; } const char* infrared_get_protocol_name(InfraredProtocol protocol) { - return infrared_get_spec_by_protocol(protocol)->name; + return infrared_get_variant_by_protocol(protocol)->name; } uint8_t infrared_get_protocol_address_length(InfraredProtocol protocol) { - return infrared_get_spec_by_protocol(protocol)->address_length; + return infrared_get_variant_by_protocol(protocol)->address_length; } uint8_t infrared_get_protocol_command_length(InfraredProtocol protocol) { - return infrared_get_spec_by_protocol(protocol)->command_length; + return infrared_get_variant_by_protocol(protocol)->command_length; } uint32_t infrared_get_protocol_frequency(InfraredProtocol protocol) { - return infrared_get_spec_by_protocol(protocol)->frequency; + return infrared_get_variant_by_protocol(protocol)->frequency; } float infrared_get_protocol_duty_cycle(InfraredProtocol protocol) { - return infrared_get_spec_by_protocol(protocol)->duty_cycle; + return infrared_get_variant_by_protocol(protocol)->duty_cycle; +} + +size_t infrared_get_protocol_min_repeat_count(InfraredProtocol protocol) { + return infrared_get_variant_by_protocol(protocol)->repeat_count; } diff --git a/lib/infrared/encoder_decoder/infrared.h b/lib/infrared/encoder_decoder/infrared.h index 2c76645fff8..3ab46cbbf56 100644 --- a/lib/infrared/encoder_decoder/infrared.h +++ b/lib/infrared/encoder_decoder/infrared.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #ifdef __cplusplus @@ -201,6 +202,15 @@ uint32_t infrared_get_protocol_frequency(InfraredProtocol protocol); */ float infrared_get_protocol_duty_cycle(InfraredProtocol protocol); +/** + * Get the minimum count of signal repeats for the selected protocol + * + * \param[in] protocol - protocol to get the repeat count from + * + * \return repeat count + */ +size_t infrared_get_protocol_min_repeat_count(InfraredProtocol protocol); + #ifdef __cplusplus } #endif diff --git a/lib/infrared/encoder_decoder/infrared_i.h b/lib/infrared/encoder_decoder/infrared_i.h index 3a645cede82..3efc41cd67a 100644 --- a/lib/infrared/encoder_decoder/infrared_i.h +++ b/lib/infrared/encoder_decoder/infrared_i.h @@ -22,9 +22,10 @@ typedef struct { uint8_t command_length; uint32_t frequency; float duty_cycle; -} InfraredProtocolSpecification; + size_t repeat_count; +} InfraredProtocolVariant; -typedef const InfraredProtocolSpecification* (*InfraredGetProtocolSpec)(InfraredProtocol protocol); +typedef const InfraredProtocolVariant* (*InfraredGetProtocolVariant)(InfraredProtocol protocol); typedef void* (*InfraredAlloc)(void); typedef void (*InfraredFree)(void*); diff --git a/lib/infrared/encoder_decoder/infrared_protocol_defs_i.h b/lib/infrared/encoder_decoder/infrared_protocol_defs_i.h deleted file mode 100644 index 6146f7b4e2f..00000000000 --- a/lib/infrared/encoder_decoder/infrared_protocol_defs_i.h +++ /dev/null @@ -1,320 +0,0 @@ -#pragma once - -#include -#include -#include -#include "infrared.h" -#include "common/infrared_common_i.h" - -/*************************************************************************************************** -* NEC protocol description -* https://radioparty.ru/manuals/encyclopedia/213-ircontrol?start=1 -**************************************************************************************************** -* Preamble Preamble Pulse Distance/Width Pause Preamble Preamble Stop -* mark space Modulation up to period repeat repeat bit -* mark space -* -* 9000 4500 32 bit + stop bit ...110000 9000 2250 -* __________ _ _ _ _ _ _ _ _ _ _ _ _ _ ___________ _ -* ____ __________ _ _ _ __ __ __ _ _ __ __ _ _ ________________ ____________ ___ -* -***************************************************************************************************/ - -#define INFRARED_NEC_PREAMBLE_MARK 9000 -#define INFRARED_NEC_PREAMBLE_SPACE 4500 -#define INFRARED_NEC_BIT1_MARK 560 -#define INFRARED_NEC_BIT1_SPACE 1690 -#define INFRARED_NEC_BIT0_MARK 560 -#define INFRARED_NEC_BIT0_SPACE 560 -#define INFRARED_NEC_REPEAT_PERIOD 110000 -#define INFRARED_NEC_SILENCE INFRARED_NEC_REPEAT_PERIOD -#define INFRARED_NEC_MIN_SPLIT_TIME INFRARED_NEC_REPEAT_PAUSE_MIN -#define INFRARED_NEC_REPEAT_PAUSE_MIN 4000 -#define INFRARED_NEC_REPEAT_PAUSE_MAX 150000 -#define INFRARED_NEC_REPEAT_MARK 9000 -#define INFRARED_NEC_REPEAT_SPACE 2250 -#define INFRARED_NEC_PREAMBLE_TOLERANCE 200 // us -#define INFRARED_NEC_BIT_TOLERANCE 120 // us - -void* infrared_decoder_nec_alloc(void); -void infrared_decoder_nec_reset(void* decoder); -void infrared_decoder_nec_free(void* decoder); -InfraredMessage* infrared_decoder_nec_check_ready(void* decoder); -InfraredMessage* infrared_decoder_nec_decode(void* decoder, bool level, uint32_t duration); -void* infrared_encoder_nec_alloc(void); -InfraredStatus infrared_encoder_nec_encode(void* encoder_ptr, uint32_t* duration, bool* level); -void infrared_encoder_nec_reset(void* encoder_ptr, const InfraredMessage* message); -void infrared_encoder_nec_free(void* encoder_ptr); -bool infrared_decoder_nec_interpret(InfraredCommonDecoder* decoder); -InfraredStatus infrared_decoder_nec_decode_repeat(InfraredCommonDecoder* decoder); -InfraredStatus infrared_encoder_nec_encode_repeat( - InfraredCommonEncoder* encoder, - uint32_t* duration, - bool* level); -const InfraredProtocolSpecification* infrared_nec_get_spec(InfraredProtocol protocol); - -extern const InfraredCommonProtocolSpec protocol_nec; - -/*************************************************************************************************** -* SAMSUNG32 protocol description -* https://www.mikrocontroller.net/articles/IRMP_-_english#SAMSUNG -**************************************************************************************************** -* Preamble Preamble Pulse Distance/Width Pause Preamble Preamble Bit1 Stop -* mark space Modulation repeat repeat bit -* mark space -* -* 4500 4500 32 bit + stop bit 40000/100000 4500 4500 -* __________ _ _ _ _ _ _ _ _ _ _ _ ___________ _ _ -* _ __________ __ _ __ __ __ _ _ __ __ _ ________________ ____________ ____ ___ -* -***************************************************************************************************/ - -#define INFRARED_SAMSUNG_PREAMBLE_MARK 4500 -#define INFRARED_SAMSUNG_PREAMBLE_SPACE 4500 -#define INFRARED_SAMSUNG_BIT1_MARK 550 -#define INFRARED_SAMSUNG_BIT1_SPACE 1650 -#define INFRARED_SAMSUNG_BIT0_MARK 550 -#define INFRARED_SAMSUNG_BIT0_SPACE 550 -#define INFRARED_SAMSUNG_REPEAT_PAUSE_MIN 30000 -#define INFRARED_SAMSUNG_REPEAT_PAUSE1 46000 -#define INFRARED_SAMSUNG_REPEAT_PAUSE2 97000 -/* Samsung silence have to be greater than REPEAT MAX - * otherwise there can be problems during unit tests parsing - * of some data. Real tolerances we don't know, but in real life - * silence time should be greater than max repeat time. This is - * because of similar preambule timings for repeat and first messages. */ -#define INFRARED_SAMSUNG_MIN_SPLIT_TIME 5000 -#define INFRARED_SAMSUNG_SILENCE 145000 -#define INFRARED_SAMSUNG_REPEAT_PAUSE_MAX 140000 -#define INFRARED_SAMSUNG_REPEAT_MARK 4500 -#define INFRARED_SAMSUNG_REPEAT_SPACE 4500 -#define INFRARED_SAMSUNG_PREAMBLE_TOLERANCE 200 // us -#define INFRARED_SAMSUNG_BIT_TOLERANCE 120 // us - -void* infrared_decoder_samsung32_alloc(void); -void infrared_decoder_samsung32_reset(void* decoder); -void infrared_decoder_samsung32_free(void* decoder); -InfraredMessage* infrared_decoder_samsung32_check_ready(void* ctx); -InfraredMessage* infrared_decoder_samsung32_decode(void* decoder, bool level, uint32_t duration); -InfraredStatus - infrared_encoder_samsung32_encode(void* encoder_ptr, uint32_t* duration, bool* level); -void infrared_encoder_samsung32_reset(void* encoder_ptr, const InfraredMessage* message); -void* infrared_encoder_samsung32_alloc(void); -void infrared_encoder_samsung32_free(void* encoder_ptr); -bool infrared_decoder_samsung32_interpret(InfraredCommonDecoder* decoder); -InfraredStatus infrared_decoder_samsung32_decode_repeat(InfraredCommonDecoder* decoder); -InfraredStatus infrared_encoder_samsung32_encode_repeat( - InfraredCommonEncoder* encoder, - uint32_t* duration, - bool* level); -const InfraredProtocolSpecification* infrared_samsung32_get_spec(InfraredProtocol protocol); - -extern const InfraredCommonProtocolSpec protocol_samsung32; - -/*************************************************************************************************** -* RC6 protocol description -* https://www.mikrocontroller.net/articles/IRMP_-_english#RC6_.2B_RC6A -**************************************************************************************************** -* Preamble Manchester/biphase Silence -* mark/space Modulation -* -* 2666 889 444/888 - bit (x2 for toggle bit) 2666 -* -* ________ __ __ __ __ ____ __ __ __ __ __ __ __ __ -* _ _________ ____ __ __ ____ __ __ __ __ __ __ __ __ _______________ -* | 1 | 0 | 0 | 0 | 0 | ... | ... | | -* s m2 m1 m0 T address (MSB) command (MSB) -* -* s - start bit (always 1) -* m0-2 - mode (000 for RC6) -* T - toggle bit, twice longer -* address - 8 bit -* command - 8 bit -***************************************************************************************************/ - -#define INFRARED_RC6_CARRIER_FREQUENCY 36000 -#define INFRARED_RC6_DUTY_CYCLE 0.33 - -#define INFRARED_RC6_PREAMBLE_MARK 2666 -#define INFRARED_RC6_PREAMBLE_SPACE 889 -#define INFRARED_RC6_BIT 444 // half of time-quant for 1 bit -#define INFRARED_RC6_PREAMBLE_TOLERANCE 200 // us -#define INFRARED_RC6_BIT_TOLERANCE 120 // us -/* protocol allows 2700 silence, but it is hard to send 1 message without repeat */ -#define INFRARED_RC6_SILENCE (2700 * 10) -#define INFRARED_RC6_MIN_SPLIT_TIME 2700 - -void* infrared_decoder_rc6_alloc(void); -void infrared_decoder_rc6_reset(void* decoder); -void infrared_decoder_rc6_free(void* decoder); -InfraredMessage* infrared_decoder_rc6_check_ready(void* ctx); -InfraredMessage* infrared_decoder_rc6_decode(void* decoder, bool level, uint32_t duration); -void* infrared_encoder_rc6_alloc(void); -void infrared_encoder_rc6_reset(void* encoder_ptr, const InfraredMessage* message); -void infrared_encoder_rc6_free(void* decoder); -InfraredStatus infrared_encoder_rc6_encode(void* encoder_ptr, uint32_t* duration, bool* polarity); -bool infrared_decoder_rc6_interpret(InfraredCommonDecoder* decoder); -InfraredStatus infrared_decoder_rc6_decode_manchester( - InfraredCommonDecoder* decoder, - bool level, - uint32_t timing); -InfraredStatus infrared_encoder_rc6_encode_manchester( - InfraredCommonEncoder* encoder_ptr, - uint32_t* duration, - bool* polarity); -const InfraredProtocolSpecification* infrared_rc6_get_spec(InfraredProtocol protocol); - -extern const InfraredCommonProtocolSpec protocol_rc6; - -/*************************************************************************************************** -* RC5 protocol description -* https://www.mikrocontroller.net/articles/IRMP_-_english#RC5_.2B_RC5X -**************************************************************************************************** -* Manchester/biphase -* Modulation -* -* 888/1776 - bit (x2 for toggle bit) -* -* __ ____ __ __ __ __ __ __ __ __ -* __ __ ____ __ __ __ __ __ __ __ _ -* | 1 | 1 | 0 | ... | ... | -* s si T address (MSB) command (MSB) -* -* Note: manchester starts from space timing, so it have to be handled properly -* s - start bit (always 1) -* si - RC5: start bit (always 1), RC5X - 7-th bit of address (in our case always 0) -* T - toggle bit, change it's value every button press -* address - 5 bit -* command - 6/7 bit -***************************************************************************************************/ - -#define INFRARED_RC5_CARRIER_FREQUENCY 36000 -#define INFRARED_RC5_DUTY_CYCLE 0.33 - -#define INFRARED_RC5_PREAMBLE_MARK 0 -#define INFRARED_RC5_PREAMBLE_SPACE 0 -#define INFRARED_RC5_BIT 888 // half of time-quant for 1 bit -#define INFRARED_RC5_PREAMBLE_TOLERANCE 200 // us -#define INFRARED_RC5_BIT_TOLERANCE 120 // us -/* protocol allows 2700 silence, but it is hard to send 1 message without repeat */ -#define INFRARED_RC5_SILENCE (2700 * 10) -#define INFRARED_RC5_MIN_SPLIT_TIME 2700 - -void* infrared_decoder_rc5_alloc(void); -void infrared_decoder_rc5_reset(void* decoder); -void infrared_decoder_rc5_free(void* decoder); -InfraredMessage* infrared_decoder_rc5_check_ready(void* ctx); -InfraredMessage* infrared_decoder_rc5_decode(void* decoder, bool level, uint32_t duration); -void* infrared_encoder_rc5_alloc(void); -void infrared_encoder_rc5_reset(void* encoder_ptr, const InfraredMessage* message); -void infrared_encoder_rc5_free(void* decoder); -InfraredStatus infrared_encoder_rc5_encode(void* encoder_ptr, uint32_t* duration, bool* polarity); -bool infrared_decoder_rc5_interpret(InfraredCommonDecoder* decoder); -const InfraredProtocolSpecification* infrared_rc5_get_spec(InfraredProtocol protocol); - -extern const InfraredCommonProtocolSpec protocol_rc5; - -/*************************************************************************************************** -* Sony SIRC protocol description -* https://www.sbprojects.net/knowledge/ir/sirc.php -* http://picprojects.org.uk/ -**************************************************************************************************** -* Preamble Preamble Pulse Width Modulation Pause Entirely repeat -* mark space up to period message.. -* -* 2400 600 12/15/20 bits (600,1200) ...45000 2400 600 -* __________ _ _ _ _ _ _ _ _ _ _ _ _ _ __________ _ _ -* ____ __________ _ _ _ __ __ __ _ _ __ __ _ _ ____________________ __________ _ -* | command | address | -* SIRC | 7b LSB | 5b LSB | -* SIRC15 | 7b LSB | 8b LSB | -* SIRC20 | 7b LSB | 13b LSB | -* -* No way to determine either next message is repeat or not, -* so recognize only fact message received. Sony remotes always send at least 3 messages. -* Assume 8 last extended bits for SIRC20 are address bits. -***************************************************************************************************/ - -#define INFRARED_SIRC_CARRIER_FREQUENCY 40000 -#define INFRARED_SIRC_DUTY_CYCLE 0.33 -#define INFRARED_SIRC_PREAMBLE_MARK 2400 -#define INFRARED_SIRC_PREAMBLE_SPACE 600 -#define INFRARED_SIRC_BIT1_MARK 1200 -#define INFRARED_SIRC_BIT1_SPACE 600 -#define INFRARED_SIRC_BIT0_MARK 600 -#define INFRARED_SIRC_BIT0_SPACE 600 -#define INFRARED_SIRC_PREAMBLE_TOLERANCE 200 // us -#define INFRARED_SIRC_BIT_TOLERANCE 120 // us -#define INFRARED_SIRC_SILENCE 10000 -#define INFRARED_SIRC_MIN_SPLIT_TIME (INFRARED_SIRC_SILENCE - 1000) -#define INFRARED_SIRC_REPEAT_PERIOD 45000 - -void* infrared_decoder_sirc_alloc(void); -void infrared_decoder_sirc_reset(void* decoder); -InfraredMessage* infrared_decoder_sirc_check_ready(void* decoder); -uint32_t infrared_decoder_sirc_get_timeout(void* decoder); -void infrared_decoder_sirc_free(void* decoder); -InfraredMessage* infrared_decoder_sirc_decode(void* decoder, bool level, uint32_t duration); -void* infrared_encoder_sirc_alloc(void); -void infrared_encoder_sirc_reset(void* encoder_ptr, const InfraredMessage* message); -void infrared_encoder_sirc_free(void* decoder); -InfraredStatus infrared_encoder_sirc_encode(void* encoder_ptr, uint32_t* duration, bool* polarity); -bool infrared_decoder_sirc_interpret(InfraredCommonDecoder* decoder); -const InfraredProtocolSpecification* infrared_sirc_get_spec(InfraredProtocol protocol); -InfraredStatus infrared_encoder_sirc_encode_repeat( - InfraredCommonEncoder* encoder, - uint32_t* duration, - bool* level); - -extern const InfraredCommonProtocolSpec protocol_sirc; - -/*************************************************************************************************** -* Kaseikyo protocol description -* https://github.com/Arduino-IRremote/Arduino-IRremote/blob/master/src/ir_Kaseikyo.hpp -**************************************************************************************************** -* Preamble Preamble Pulse Distance/Width Pause Preamble Preamble -* mark space Modulation up to period repeat repeat -* mark space -* -* 3360 1665 48 bit ...130000 3456 1728 -* __________ _ _ _ _ _ _ _ _ _ _ _ _ _ ___________ -* ____ __________ _ _ _ __ __ __ _ _ __ __ _ _ ________________ ___________ -* -***************************************************************************************************/ - -#define INFRARED_KASEIKYO_UNIT 432 -#define INFRARED_KASEIKYO_PREAMBLE_MARK (8 * INFRARED_KASEIKYO_UNIT) -#define INFRARED_KASEIKYO_PREAMBLE_SPACE (4 * INFRARED_KASEIKYO_UNIT) -#define INFRARED_KASEIKYO_BIT1_MARK INFRARED_KASEIKYO_UNIT -#define INFRARED_KASEIKYO_BIT1_SPACE (3 * INFRARED_KASEIKYO_UNIT) -#define INFRARED_KASEIKYO_BIT0_MARK INFRARED_KASEIKYO_UNIT -#define INFRARED_KASEIKYO_BIT0_SPACE INFRARED_KASEIKYO_UNIT -#define INFRARED_KASEIKYO_REPEAT_PERIOD 130000 -#define INFRARED_KASEIKYO_SILENCE INFRARED_KASEIKYO_REPEAT_PERIOD -#define INFRARED_KASEIKYO_MIN_SPLIT_TIME INFRARED_KASEIKYO_REPEAT_PAUSE_MIN -#define INFRARED_KASEIKYO_REPEAT_PAUSE_MIN 4000 -#define INFRARED_KASEIKYO_REPEAT_PAUSE_MAX 150000 -#define INFRARED_KASEIKYO_REPEAT_MARK INFRARED_KASEIKYO_PREAMBLE_MARK -#define INFRARED_KASEIKYO_REPEAT_SPACE (INFRARED_KASEIKYO_REPEAT_PERIOD - 56000) -#define INFRARED_KASEIKYO_PREAMBLE_TOLERANCE 200 // us -#define INFRARED_KASEIKYO_BIT_TOLERANCE 120 // us - -void* infrared_decoder_kaseikyo_alloc(void); -void infrared_decoder_kaseikyo_reset(void* decoder); -void infrared_decoder_kaseikyo_free(void* decoder); -InfraredMessage* infrared_decoder_kaseikyo_check_ready(void* decoder); -InfraredMessage* infrared_decoder_kaseikyo_decode(void* decoder, bool level, uint32_t duration); -void* infrared_encoder_kaseikyo_alloc(void); -InfraredStatus - infrared_encoder_kaseikyo_encode(void* encoder_ptr, uint32_t* duration, bool* level); -void infrared_encoder_kaseikyo_reset(void* encoder_ptr, const InfraredMessage* message); -void infrared_encoder_kaseikyo_free(void* encoder_ptr); -bool infrared_decoder_kaseikyo_interpret(InfraredCommonDecoder* decoder); -InfraredStatus infrared_decoder_kaseikyo_decode_repeat(InfraredCommonDecoder* decoder); -InfraredStatus infrared_encoder_kaseikyo_encode_repeat( - InfraredCommonEncoder* encoder, - uint32_t* duration, - bool* level); -const InfraredProtocolSpecification* infrared_kaseikyo_get_spec(InfraredProtocol protocol); - -extern const InfraredCommonProtocolSpec protocol_kaseikyo; diff --git a/lib/infrared/encoder_decoder/kaseikyo/infrared_decoder_kaseikyo.c b/lib/infrared/encoder_decoder/kaseikyo/infrared_decoder_kaseikyo.c index b8db81d7ef8..e85a8965259 100644 --- a/lib/infrared/encoder_decoder/kaseikyo/infrared_decoder_kaseikyo.c +++ b/lib/infrared/encoder_decoder/kaseikyo/infrared_decoder_kaseikyo.c @@ -1,9 +1,5 @@ -#include "infrared.h" -#include "infrared_protocol_defs_i.h" -#include -#include -#include -#include "../infrared_i.h" +#include "infrared_protocol_kaseikyo_i.h" +#include InfraredMessage* infrared_decoder_kaseikyo_check_ready(void* ctx) { return infrared_common_decoder_check_ready(ctx); @@ -38,7 +34,7 @@ bool infrared_decoder_kaseikyo_interpret(InfraredCommonDecoder* decoder) { } void* infrared_decoder_kaseikyo_alloc(void) { - return infrared_common_decoder_alloc(&protocol_kaseikyo); + return infrared_common_decoder_alloc(&infrared_protocol_kaseikyo); } InfraredMessage* infrared_decoder_kaseikyo_decode(void* decoder, bool level, uint32_t duration) { diff --git a/lib/infrared/encoder_decoder/kaseikyo/infrared_encoder_kaseikyo.c b/lib/infrared/encoder_decoder/kaseikyo/infrared_encoder_kaseikyo.c index 5814c725577..618fc3babdc 100644 --- a/lib/infrared/encoder_decoder/kaseikyo/infrared_encoder_kaseikyo.c +++ b/lib/infrared/encoder_decoder/kaseikyo/infrared_encoder_kaseikyo.c @@ -1,9 +1,5 @@ +#include "infrared_protocol_kaseikyo_i.h" #include -#include "common/infrared_common_i.h" -#include -#include "../infrared_i.h" -#include "infrared_protocol_defs_i.h" -#include void infrared_encoder_kaseikyo_reset(void* encoder_ptr, const InfraredMessage* message) { furi_assert(encoder_ptr); @@ -32,7 +28,7 @@ void infrared_encoder_kaseikyo_reset(void* encoder_ptr, const InfraredMessage* m } void* infrared_encoder_kaseikyo_alloc(void) { - return infrared_common_encoder_alloc(&protocol_kaseikyo); + return infrared_common_encoder_alloc(&infrared_protocol_kaseikyo); } void infrared_encoder_kaseikyo_free(void* encoder_ptr) { diff --git a/lib/infrared/encoder_decoder/kaseikyo/infrared_kaseikyo_spec.c b/lib/infrared/encoder_decoder/kaseikyo/infrared_kaseikyo_spec.c deleted file mode 100644 index 87c86c7b3d1..00000000000 --- a/lib/infrared/encoder_decoder/kaseikyo/infrared_kaseikyo_spec.c +++ /dev/null @@ -1,17 +0,0 @@ -#include "../infrared_i.h" -#include "infrared_protocol_defs_i.h" - -static const InfraredProtocolSpecification infrared_kaseikyo_protocol_specification = { - .name = "Kaseikyo", - .address_length = 26, - .command_length = 10, - .frequency = INFRARED_COMMON_CARRIER_FREQUENCY, - .duty_cycle = INFRARED_COMMON_DUTY_CYCLE, -}; - -const InfraredProtocolSpecification* infrared_kaseikyo_get_spec(InfraredProtocol protocol) { - if(protocol == InfraredProtocolKaseikyo) - return &infrared_kaseikyo_protocol_specification; - else - return NULL; -} diff --git a/lib/infrared/encoder_decoder/kaseikyo/infrared_protocol_kaseikyo.c b/lib/infrared/encoder_decoder/kaseikyo/infrared_protocol_kaseikyo.c new file mode 100644 index 00000000000..0c61be3bf8c --- /dev/null +++ b/lib/infrared/encoder_decoder/kaseikyo/infrared_protocol_kaseikyo.c @@ -0,0 +1,40 @@ +#include "infrared_protocol_kaseikyo_i.h" + +const InfraredCommonProtocolSpec infrared_protocol_kaseikyo = { + .timings = + { + .preamble_mark = INFRARED_KASEIKYO_PREAMBLE_MARK, + .preamble_space = INFRARED_KASEIKYO_PREAMBLE_SPACE, + .bit1_mark = INFRARED_KASEIKYO_BIT1_MARK, + .bit1_space = INFRARED_KASEIKYO_BIT1_SPACE, + .bit0_mark = INFRARED_KASEIKYO_BIT0_MARK, + .bit0_space = INFRARED_KASEIKYO_BIT0_SPACE, + .preamble_tolerance = INFRARED_KASEIKYO_PREAMBLE_TOLERANCE, + .bit_tolerance = INFRARED_KASEIKYO_BIT_TOLERANCE, + .silence_time = INFRARED_KASEIKYO_SILENCE, + .min_split_time = INFRARED_KASEIKYO_MIN_SPLIT_TIME, + }, + .databit_len[0] = 48, + .no_stop_bit = false, + .decode = infrared_common_decode_pdwm, + .encode = infrared_common_encode_pdwm, + .interpret = infrared_decoder_kaseikyo_interpret, + .decode_repeat = NULL, + .encode_repeat = NULL, +}; + +static const InfraredProtocolVariant infrared_protocol_variant_kaseikyo = { + .name = "Kaseikyo", + .address_length = 26, + .command_length = 10, + .frequency = INFRARED_COMMON_CARRIER_FREQUENCY, + .duty_cycle = INFRARED_COMMON_DUTY_CYCLE, + .repeat_count = INFRARED_KASEIKYO_REPEAT_COUNT_MIN, +}; + +const InfraredProtocolVariant* infrared_protocol_kaseikyo_get_variant(InfraredProtocol protocol) { + if(protocol == InfraredProtocolKaseikyo) + return &infrared_protocol_variant_kaseikyo; + else + return NULL; +} diff --git a/lib/infrared/encoder_decoder/kaseikyo/infrared_protocol_kaseikyo.h b/lib/infrared/encoder_decoder/kaseikyo/infrared_protocol_kaseikyo.h new file mode 100644 index 00000000000..61ff0ca1cf7 --- /dev/null +++ b/lib/infrared/encoder_decoder/kaseikyo/infrared_protocol_kaseikyo.h @@ -0,0 +1,31 @@ +#pragma once + +#include "../infrared_i.h" + +/*************************************************************************************************** +* Kaseikyo protocol description +* https://github.com/Arduino-IRremote/Arduino-IRremote/blob/master/src/ir_Kaseikyo.hpp +**************************************************************************************************** +* Preamble Preamble Pulse Distance/Width Pause Preamble Preamble +* mark space Modulation up to period repeat repeat +* mark space +* +* 3360 1665 48 bit ...130000 3456 1728 +* __________ _ _ _ _ _ _ _ _ _ _ _ _ _ ___________ +* ____ __________ _ _ _ __ __ __ _ _ __ __ _ _ ________________ ___________ +* +***************************************************************************************************/ + +void* infrared_decoder_kaseikyo_alloc(void); +void infrared_decoder_kaseikyo_reset(void* decoder); +void infrared_decoder_kaseikyo_free(void* decoder); +InfraredMessage* infrared_decoder_kaseikyo_check_ready(void* decoder); +InfraredMessage* infrared_decoder_kaseikyo_decode(void* decoder, bool level, uint32_t duration); + +void* infrared_encoder_kaseikyo_alloc(void); +InfraredStatus + infrared_encoder_kaseikyo_encode(void* encoder_ptr, uint32_t* duration, bool* level); +void infrared_encoder_kaseikyo_reset(void* encoder_ptr, const InfraredMessage* message); +void infrared_encoder_kaseikyo_free(void* encoder_ptr); + +const InfraredProtocolVariant* infrared_protocol_kaseikyo_get_variant(InfraredProtocol protocol); diff --git a/lib/infrared/encoder_decoder/kaseikyo/infrared_protocol_kaseikyo_i.h b/lib/infrared/encoder_decoder/kaseikyo/infrared_protocol_kaseikyo_i.h new file mode 100644 index 00000000000..bee116c4d15 --- /dev/null +++ b/lib/infrared/encoder_decoder/kaseikyo/infrared_protocol_kaseikyo_i.h @@ -0,0 +1,30 @@ +#pragma once + +#include "../common/infrared_common_i.h" + +#define INFRARED_KASEIKYO_UNIT 432 +#define INFRARED_KASEIKYO_PREAMBLE_MARK (8 * INFRARED_KASEIKYO_UNIT) +#define INFRARED_KASEIKYO_PREAMBLE_SPACE (4 * INFRARED_KASEIKYO_UNIT) +#define INFRARED_KASEIKYO_BIT1_MARK INFRARED_KASEIKYO_UNIT +#define INFRARED_KASEIKYO_BIT1_SPACE (3 * INFRARED_KASEIKYO_UNIT) +#define INFRARED_KASEIKYO_BIT0_MARK INFRARED_KASEIKYO_UNIT +#define INFRARED_KASEIKYO_BIT0_SPACE INFRARED_KASEIKYO_UNIT +#define INFRARED_KASEIKYO_REPEAT_PERIOD 130000 +#define INFRARED_KASEIKYO_SILENCE INFRARED_KASEIKYO_REPEAT_PERIOD +#define INFRARED_KASEIKYO_MIN_SPLIT_TIME INFRARED_KASEIKYO_REPEAT_PAUSE_MIN +#define INFRARED_KASEIKYO_REPEAT_PAUSE_MIN 4000 +#define INFRARED_KASEIKYO_REPEAT_PAUSE_MAX 150000 +#define INFRARED_KASEIKYO_REPEAT_COUNT_MIN 1 +#define INFRARED_KASEIKYO_REPEAT_MARK INFRARED_KASEIKYO_PREAMBLE_MARK +#define INFRARED_KASEIKYO_REPEAT_SPACE (INFRARED_KASEIKYO_REPEAT_PERIOD - 56000) +#define INFRARED_KASEIKYO_PREAMBLE_TOLERANCE 200 // us +#define INFRARED_KASEIKYO_BIT_TOLERANCE 120 // us + +extern const InfraredCommonProtocolSpec infrared_protocol_kaseikyo; + +bool infrared_decoder_kaseikyo_interpret(InfraredCommonDecoder* decoder); +InfraredStatus infrared_decoder_kaseikyo_decode_repeat(InfraredCommonDecoder* decoder); +InfraredStatus infrared_encoder_kaseikyo_encode_repeat( + InfraredCommonEncoder* encoder, + uint32_t* duration, + bool* level); diff --git a/lib/infrared/encoder_decoder/nec/infrared_decoder_nec.c b/lib/infrared/encoder_decoder/nec/infrared_decoder_nec.c index 3ad14a7ab1a..91384d702a2 100644 --- a/lib/infrared/encoder_decoder/nec/infrared_decoder_nec.c +++ b/lib/infrared/encoder_decoder/nec/infrared_decoder_nec.c @@ -1,10 +1,5 @@ -#include "common/infrared_common_i.h" -#include "infrared.h" -#include "infrared_protocol_defs_i.h" -#include -#include -#include -#include "../infrared_i.h" +#include "infrared_protocol_nec_i.h" +#include InfraredMessage* infrared_decoder_nec_check_ready(void* ctx) { return infrared_common_decoder_check_ready(ctx); @@ -86,7 +81,7 @@ InfraredStatus infrared_decoder_nec_decode_repeat(InfraredCommonDecoder* decoder } void* infrared_decoder_nec_alloc(void) { - return infrared_common_decoder_alloc(&protocol_nec); + return infrared_common_decoder_alloc(&infrared_protocol_nec); } InfraredMessage* infrared_decoder_nec_decode(void* decoder, bool level, uint32_t duration) { diff --git a/lib/infrared/encoder_decoder/nec/infrared_encoder_nec.c b/lib/infrared/encoder_decoder/nec/infrared_encoder_nec.c index d0039c330b0..87f815142ab 100644 --- a/lib/infrared/encoder_decoder/nec/infrared_encoder_nec.c +++ b/lib/infrared/encoder_decoder/nec/infrared_encoder_nec.c @@ -1,10 +1,7 @@ +#include "infrared_protocol_nec_i.h" + +#include #include -#include "infrared.h" -#include "common/infrared_common_i.h" -#include -#include "../infrared_i.h" -#include "infrared_protocol_defs_i.h" -#include static const uint32_t repeat_timings[] = { INFRARED_NEC_REPEAT_PERIOD - INFRARED_NEC_REPEAT_MARK - INFRARED_NEC_REPEAT_SPACE - @@ -81,7 +78,7 @@ InfraredStatus infrared_encoder_nec_encode_repeat( } void* infrared_encoder_nec_alloc(void) { - return infrared_common_encoder_alloc(&protocol_nec); + return infrared_common_encoder_alloc(&infrared_protocol_nec); } void infrared_encoder_nec_free(void* encoder_ptr) { diff --git a/lib/infrared/encoder_decoder/nec/infrared_nec_spec.c b/lib/infrared/encoder_decoder/nec/infrared_nec_spec.c deleted file mode 100644 index 16cab8b5f50..00000000000 --- a/lib/infrared/encoder_decoder/nec/infrared_nec_spec.c +++ /dev/null @@ -1,47 +0,0 @@ -#include "../infrared_i.h" -#include "infrared_protocol_defs_i.h" - -static const InfraredProtocolSpecification infrared_nec_protocol_specification = { - .name = "NEC", - .address_length = 8, - .command_length = 8, - .frequency = INFRARED_COMMON_CARRIER_FREQUENCY, - .duty_cycle = INFRARED_COMMON_DUTY_CYCLE, -}; - -static const InfraredProtocolSpecification infrared_necext_protocol_specification = { - .name = "NECext", - .address_length = 16, - .command_length = 16, - .frequency = INFRARED_COMMON_CARRIER_FREQUENCY, - .duty_cycle = INFRARED_COMMON_DUTY_CYCLE, -}; - -static const InfraredProtocolSpecification infrared_nec42_protocol_specification = { - .name = "NEC42", - .address_length = 13, - .command_length = 8, - .frequency = INFRARED_COMMON_CARRIER_FREQUENCY, - .duty_cycle = INFRARED_COMMON_DUTY_CYCLE, -}; - -static const InfraredProtocolSpecification infrared_nec42ext_protocol_specification = { - .name = "NEC42ext", - .address_length = 26, - .command_length = 16, - .frequency = INFRARED_COMMON_CARRIER_FREQUENCY, - .duty_cycle = INFRARED_COMMON_DUTY_CYCLE, -}; - -const InfraredProtocolSpecification* infrared_nec_get_spec(InfraredProtocol protocol) { - if(protocol == InfraredProtocolNEC) - return &infrared_nec_protocol_specification; - else if(protocol == InfraredProtocolNECext) - return &infrared_necext_protocol_specification; - else if(protocol == InfraredProtocolNEC42) - return &infrared_nec42_protocol_specification; - else if(protocol == InfraredProtocolNEC42ext) - return &infrared_nec42ext_protocol_specification; - else - return NULL; -} diff --git a/lib/infrared/encoder_decoder/nec/infrared_protocol_nec.c b/lib/infrared/encoder_decoder/nec/infrared_protocol_nec.c new file mode 100644 index 00000000000..3444f78b612 --- /dev/null +++ b/lib/infrared/encoder_decoder/nec/infrared_protocol_nec.c @@ -0,0 +1,74 @@ +#include "infrared_protocol_nec_i.h" + +const InfraredCommonProtocolSpec infrared_protocol_nec = { + .timings = + { + .preamble_mark = INFRARED_NEC_PREAMBLE_MARK, + .preamble_space = INFRARED_NEC_PREAMBLE_SPACE, + .bit1_mark = INFRARED_NEC_BIT1_MARK, + .bit1_space = INFRARED_NEC_BIT1_SPACE, + .bit0_mark = INFRARED_NEC_BIT0_MARK, + .bit0_space = INFRARED_NEC_BIT0_SPACE, + .preamble_tolerance = INFRARED_NEC_PREAMBLE_TOLERANCE, + .bit_tolerance = INFRARED_NEC_BIT_TOLERANCE, + .silence_time = INFRARED_NEC_SILENCE, + .min_split_time = INFRARED_NEC_MIN_SPLIT_TIME, + }, + .databit_len[0] = 42, + .databit_len[1] = 32, + .no_stop_bit = false, + .decode = infrared_common_decode_pdwm, + .encode = infrared_common_encode_pdwm, + .interpret = infrared_decoder_nec_interpret, + .decode_repeat = infrared_decoder_nec_decode_repeat, + .encode_repeat = infrared_encoder_nec_encode_repeat, +}; + +static const InfraredProtocolVariant infrared_protocol_variant_nec = { + .name = "NEC", + .address_length = 8, + .command_length = 8, + .frequency = INFRARED_COMMON_CARRIER_FREQUENCY, + .duty_cycle = INFRARED_COMMON_DUTY_CYCLE, + .repeat_count = INFRARED_NEC_REPEAT_COUNT_MIN, +}; + +static const InfraredProtocolVariant infrared_protocol_variant_necext = { + .name = "NECext", + .address_length = 16, + .command_length = 16, + .frequency = INFRARED_COMMON_CARRIER_FREQUENCY, + .duty_cycle = INFRARED_COMMON_DUTY_CYCLE, + .repeat_count = INFRARED_NEC_REPEAT_COUNT_MIN, +}; + +static const InfraredProtocolVariant infrared_protocol_variant_nec42 = { + .name = "NEC42", + .address_length = 13, + .command_length = 8, + .frequency = INFRARED_COMMON_CARRIER_FREQUENCY, + .duty_cycle = INFRARED_COMMON_DUTY_CYCLE, + .repeat_count = INFRARED_NEC_REPEAT_COUNT_MIN, +}; + +static const InfraredProtocolVariant infrared_protocol_variant_nec42ext = { + .name = "NEC42ext", + .address_length = 26, + .command_length = 16, + .frequency = INFRARED_COMMON_CARRIER_FREQUENCY, + .duty_cycle = INFRARED_COMMON_DUTY_CYCLE, + .repeat_count = INFRARED_NEC_REPEAT_COUNT_MIN, +}; + +const InfraredProtocolVariant* infrared_protocol_nec_get_variant(InfraredProtocol protocol) { + if(protocol == InfraredProtocolNEC) + return &infrared_protocol_variant_nec; + else if(protocol == InfraredProtocolNECext) + return &infrared_protocol_variant_necext; + else if(protocol == InfraredProtocolNEC42) + return &infrared_protocol_variant_nec42; + else if(protocol == InfraredProtocolNEC42ext) + return &infrared_protocol_variant_nec42ext; + else + return NULL; +} diff --git a/lib/infrared/encoder_decoder/nec/infrared_protocol_nec.h b/lib/infrared/encoder_decoder/nec/infrared_protocol_nec.h new file mode 100644 index 00000000000..559e31fda13 --- /dev/null +++ b/lib/infrared/encoder_decoder/nec/infrared_protocol_nec.h @@ -0,0 +1,30 @@ +#pragma once + +#include "../infrared_i.h" + +/*************************************************************************************************** +* NEC protocol description +* https://radioparty.ru/manuals/encyclopedia/213-ircontrol?start=1 +**************************************************************************************************** +* Preamble Preamble Pulse Distance/Width Pause Preamble Preamble Stop +* mark space Modulation up to period repeat repeat bit +* mark space +* +* 9000 4500 32 bit + stop bit ...110000 9000 2250 +* __________ _ _ _ _ _ _ _ _ _ _ _ _ _ ___________ _ +* ____ __________ _ _ _ __ __ __ _ _ __ __ _ _ ________________ ____________ ___ +* +***************************************************************************************************/ + +void* infrared_decoder_nec_alloc(void); +void infrared_decoder_nec_reset(void* decoder); +void infrared_decoder_nec_free(void* decoder); +InfraredMessage* infrared_decoder_nec_check_ready(void* decoder); +InfraredMessage* infrared_decoder_nec_decode(void* decoder, bool level, uint32_t duration); + +void* infrared_encoder_nec_alloc(void); +InfraredStatus infrared_encoder_nec_encode(void* encoder_ptr, uint32_t* duration, bool* level); +void infrared_encoder_nec_reset(void* encoder_ptr, const InfraredMessage* message); +void infrared_encoder_nec_free(void* encoder_ptr); + +const InfraredProtocolVariant* infrared_protocol_nec_get_variant(InfraredProtocol protocol); diff --git a/lib/infrared/encoder_decoder/nec/infrared_protocol_nec_i.h b/lib/infrared/encoder_decoder/nec/infrared_protocol_nec_i.h new file mode 100644 index 00000000000..05df1f474ea --- /dev/null +++ b/lib/infrared/encoder_decoder/nec/infrared_protocol_nec_i.h @@ -0,0 +1,29 @@ +#pragma once + +#include "../common/infrared_common_i.h" + +#define INFRARED_NEC_PREAMBLE_MARK 9000 +#define INFRARED_NEC_PREAMBLE_SPACE 4500 +#define INFRARED_NEC_BIT1_MARK 560 +#define INFRARED_NEC_BIT1_SPACE 1690 +#define INFRARED_NEC_BIT0_MARK 560 +#define INFRARED_NEC_BIT0_SPACE 560 +#define INFRARED_NEC_REPEAT_PERIOD 110000 +#define INFRARED_NEC_SILENCE INFRARED_NEC_REPEAT_PERIOD +#define INFRARED_NEC_MIN_SPLIT_TIME INFRARED_NEC_REPEAT_PAUSE_MIN +#define INFRARED_NEC_REPEAT_PAUSE_MIN 4000 +#define INFRARED_NEC_REPEAT_PAUSE_MAX 150000 +#define INFRARED_NEC_REPEAT_COUNT_MIN 1 +#define INFRARED_NEC_REPEAT_MARK 9000 +#define INFRARED_NEC_REPEAT_SPACE 2250 +#define INFRARED_NEC_PREAMBLE_TOLERANCE 200 // us +#define INFRARED_NEC_BIT_TOLERANCE 120 // us + +extern const InfraredCommonProtocolSpec infrared_protocol_nec; + +bool infrared_decoder_nec_interpret(InfraredCommonDecoder* decoder); +InfraredStatus infrared_decoder_nec_decode_repeat(InfraredCommonDecoder* decoder); +InfraredStatus infrared_encoder_nec_encode_repeat( + InfraredCommonEncoder* encoder, + uint32_t* duration, + bool* level); diff --git a/lib/infrared/encoder_decoder/rc5/infrared_decoder_rc5.c b/lib/infrared/encoder_decoder/rc5/infrared_decoder_rc5.c index 6b4a7c2e35f..1b2f2f3019f 100644 --- a/lib/infrared/encoder_decoder/rc5/infrared_decoder_rc5.c +++ b/lib/infrared/encoder_decoder/rc5/infrared_decoder_rc5.c @@ -1,10 +1,7 @@ -#include "infrared.h" -#include -#include -#include -#include -#include "../infrared_i.h" -#include "../infrared_protocol_defs_i.h" +#include "infrared_protocol_rc5_i.h" + +#include +#include typedef struct { InfraredCommonDecoder* common_decoder; @@ -60,7 +57,7 @@ bool infrared_decoder_rc5_interpret(InfraredCommonDecoder* decoder) { void* infrared_decoder_rc5_alloc(void) { InfraredRc5Decoder* decoder = malloc(sizeof(InfraredRc5Decoder)); decoder->toggle = false; - decoder->common_decoder = infrared_common_decoder_alloc(&protocol_rc5); + decoder->common_decoder = infrared_common_decoder_alloc(&infrared_protocol_rc5); decoder->common_decoder->context = decoder; return decoder; } diff --git a/lib/infrared/encoder_decoder/rc5/infrared_encoder_rc5.c b/lib/infrared/encoder_decoder/rc5/infrared_encoder_rc5.c index 7b55cdc4479..df47fb7c433 100644 --- a/lib/infrared/encoder_decoder/rc5/infrared_encoder_rc5.c +++ b/lib/infrared/encoder_decoder/rc5/infrared_encoder_rc5.c @@ -1,9 +1,7 @@ -#include -#include "infrared.h" -#include "common/infrared_common_i.h" -#include "infrared_protocol_defs_i.h" -#include -#include "../infrared_i.h" +#include "infrared_protocol_rc5_i.h" + +#include +#include typedef struct InfraredEncoderRC5 { InfraredCommonEncoder* common_encoder; @@ -41,7 +39,7 @@ InfraredStatus infrared_encoder_rc5_encode(void* encoder_ptr, uint32_t* duration void* infrared_encoder_rc5_alloc(void) { InfraredEncoderRC5* encoder = malloc(sizeof(InfraredEncoderRC5)); - encoder->common_encoder = infrared_common_encoder_alloc(&protocol_rc5); + encoder->common_encoder = infrared_common_encoder_alloc(&infrared_protocol_rc5); encoder->toggle_bit = false; return encoder; } diff --git a/lib/infrared/encoder_decoder/rc5/infrared_protocol_rc5.c b/lib/infrared/encoder_decoder/rc5/infrared_protocol_rc5.c new file mode 100644 index 00000000000..bc7e299fda2 --- /dev/null +++ b/lib/infrared/encoder_decoder/rc5/infrared_protocol_rc5.c @@ -0,0 +1,49 @@ +#include "infrared_protocol_rc5_i.h" + +const InfraredCommonProtocolSpec infrared_protocol_rc5 = { + .timings = + { + .preamble_mark = 0, + .preamble_space = 0, + .bit1_mark = INFRARED_RC5_BIT, + .preamble_tolerance = 0, + .bit_tolerance = INFRARED_RC5_BIT_TOLERANCE, + .silence_time = INFRARED_RC5_SILENCE, + .min_split_time = INFRARED_RC5_MIN_SPLIT_TIME, + }, + .databit_len[0] = 1 + 1 + 1 + 5 + + 6, // start_bit + start_bit/command_bit + toggle_bit + 5 address + 6 command + .manchester_start_from_space = true, + .decode = infrared_common_decode_manchester, + .encode = infrared_common_encode_manchester, + .interpret = infrared_decoder_rc5_interpret, + .decode_repeat = NULL, + .encode_repeat = NULL, +}; + +static const InfraredProtocolVariant infrared_protocol_variant_rc5 = { + .name = "RC5", + .address_length = 5, + .command_length = 6, + .frequency = INFRARED_RC5_CARRIER_FREQUENCY, + .duty_cycle = INFRARED_RC5_DUTY_CYCLE, + .repeat_count = INFRARED_RC5_REPEAT_COUNT_MIN, +}; + +static const InfraredProtocolVariant infrared_protocol_variant_rc5x = { + .name = "RC5X", + .address_length = 5, + .command_length = 7, + .frequency = INFRARED_RC5_CARRIER_FREQUENCY, + .duty_cycle = INFRARED_RC5_DUTY_CYCLE, + .repeat_count = INFRARED_RC5_REPEAT_COUNT_MIN, +}; + +const InfraredProtocolVariant* infrared_protocol_rc5_get_variant(InfraredProtocol protocol) { + if(protocol == InfraredProtocolRC5) + return &infrared_protocol_variant_rc5; + else if(protocol == InfraredProtocolRC5X) + return &infrared_protocol_variant_rc5x; + else + return NULL; +} diff --git a/lib/infrared/encoder_decoder/rc5/infrared_protocol_rc5.h b/lib/infrared/encoder_decoder/rc5/infrared_protocol_rc5.h new file mode 100644 index 00000000000..9dd5802a93c --- /dev/null +++ b/lib/infrared/encoder_decoder/rc5/infrared_protocol_rc5.h @@ -0,0 +1,38 @@ +#pragma once + +#include "../infrared_i.h" + +/*************************************************************************************************** +* RC5 protocol description +* https://www.mikrocontroller.net/articles/IRMP_-_english#RC5_.2B_RC5X +**************************************************************************************************** +* Manchester/biphase +* Modulation +* +* 888/1776 - bit (x2 for toggle bit) +* +* __ ____ __ __ __ __ __ __ __ __ +* __ __ ____ __ __ __ __ __ __ __ _ +* | 1 | 1 | 0 | ... | ... | +* s si T address (MSB) command (MSB) +* +* Note: manchester starts from space timing, so it have to be handled properly +* s - start bit (always 1) +* si - RC5: start bit (always 1), RC5X - 7-th bit of address (in our case always 0) +* T - toggle bit, change it's value every button press +* address - 5 bit +* command - 6/7 bit +***************************************************************************************************/ + +void* infrared_decoder_rc5_alloc(void); +void infrared_decoder_rc5_reset(void* decoder); +void infrared_decoder_rc5_free(void* decoder); +InfraredMessage* infrared_decoder_rc5_check_ready(void* ctx); +InfraredMessage* infrared_decoder_rc5_decode(void* decoder, bool level, uint32_t duration); + +void* infrared_encoder_rc5_alloc(void); +void infrared_encoder_rc5_reset(void* encoder_ptr, const InfraredMessage* message); +void infrared_encoder_rc5_free(void* decoder); +InfraredStatus infrared_encoder_rc5_encode(void* encoder_ptr, uint32_t* duration, bool* polarity); + +const InfraredProtocolVariant* infrared_protocol_rc5_get_variant(InfraredProtocol protocol); diff --git a/lib/infrared/encoder_decoder/rc5/infrared_protocol_rc5_i.h b/lib/infrared/encoder_decoder/rc5/infrared_protocol_rc5_i.h new file mode 100644 index 00000000000..b906c369e00 --- /dev/null +++ b/lib/infrared/encoder_decoder/rc5/infrared_protocol_rc5_i.h @@ -0,0 +1,20 @@ +#pragma once + +#include "../common/infrared_common_i.h" + +#define INFRARED_RC5_CARRIER_FREQUENCY 36000 +#define INFRARED_RC5_DUTY_CYCLE 0.33 + +#define INFRARED_RC5_PREAMBLE_MARK 0 +#define INFRARED_RC5_PREAMBLE_SPACE 0 +#define INFRARED_RC5_BIT 888 // half of time-quant for 1 bit +#define INFRARED_RC5_PREAMBLE_TOLERANCE 200 // us +#define INFRARED_RC5_BIT_TOLERANCE 120 // us +/* protocol allows 2700 silence, but it is hard to send 1 message without repeat */ +#define INFRARED_RC5_SILENCE (2700 * 10) +#define INFRARED_RC5_MIN_SPLIT_TIME 2700 +#define INFRARED_RC5_REPEAT_COUNT_MIN 1 + +extern const InfraredCommonProtocolSpec infrared_protocol_rc5; + +bool infrared_decoder_rc5_interpret(InfraredCommonDecoder* decoder); diff --git a/lib/infrared/encoder_decoder/rc5/infrared_rc5_spec.c b/lib/infrared/encoder_decoder/rc5/infrared_rc5_spec.c deleted file mode 100644 index 25ea230ef34..00000000000 --- a/lib/infrared/encoder_decoder/rc5/infrared_rc5_spec.c +++ /dev/null @@ -1,27 +0,0 @@ -#include "../infrared_i.h" -#include "infrared_protocol_defs_i.h" - -static const InfraredProtocolSpecification infrared_rc5_protocol_specification = { - .name = "RC5", - .address_length = 5, - .command_length = 6, - .frequency = INFRARED_RC5_CARRIER_FREQUENCY, - .duty_cycle = INFRARED_RC5_DUTY_CYCLE, -}; - -static const InfraredProtocolSpecification infrared_rc5x_protocol_specification = { - .name = "RC5X", - .address_length = 5, - .command_length = 7, - .frequency = INFRARED_RC5_CARRIER_FREQUENCY, - .duty_cycle = INFRARED_RC5_DUTY_CYCLE, -}; - -const InfraredProtocolSpecification* infrared_rc5_get_spec(InfraredProtocol protocol) { - if(protocol == InfraredProtocolRC5) - return &infrared_rc5_protocol_specification; - else if(protocol == InfraredProtocolRC5X) - return &infrared_rc5x_protocol_specification; - else - return NULL; -} diff --git a/lib/infrared/encoder_decoder/rc6/infrared_decoder_rc6.c b/lib/infrared/encoder_decoder/rc6/infrared_decoder_rc6.c index 13d3e5364dc..b70f7ceb854 100644 --- a/lib/infrared/encoder_decoder/rc6/infrared_decoder_rc6.c +++ b/lib/infrared/encoder_decoder/rc6/infrared_decoder_rc6.c @@ -1,10 +1,7 @@ -#include "infrared.h" -#include -#include -#include -#include -#include "../infrared_i.h" -#include "../infrared_protocol_defs_i.h" +#include "infrared_protocol_rc6_i.h" + +#include +#include typedef struct { InfraredCommonDecoder* common_decoder; @@ -93,7 +90,7 @@ InfraredStatus infrared_decoder_rc6_decode_manchester( void* infrared_decoder_rc6_alloc(void) { InfraredRc6Decoder* decoder = malloc(sizeof(InfraredRc6Decoder)); decoder->toggle = false; - decoder->common_decoder = infrared_common_decoder_alloc(&protocol_rc6); + decoder->common_decoder = infrared_common_decoder_alloc(&infrared_protocol_rc6); decoder->common_decoder->context = decoder; return decoder; } diff --git a/lib/infrared/encoder_decoder/rc6/infrared_encoder_rc6.c b/lib/infrared/encoder_decoder/rc6/infrared_encoder_rc6.c index f1240b17a79..d13a204ea0e 100644 --- a/lib/infrared/encoder_decoder/rc6/infrared_encoder_rc6.c +++ b/lib/infrared/encoder_decoder/rc6/infrared_encoder_rc6.c @@ -1,9 +1,7 @@ -#include -#include "infrared.h" -#include "common/infrared_common_i.h" -#include "infrared_protocol_defs_i.h" -#include -#include "../infrared_i.h" +#include "infrared_protocol_rc6_i.h" + +#include +#include typedef struct InfraredEncoderRC6 { InfraredCommonEncoder* common_encoder; @@ -35,7 +33,7 @@ InfraredStatus infrared_encoder_rc6_encode(void* encoder_ptr, uint32_t* duration void* infrared_encoder_rc6_alloc(void) { InfraredEncoderRC6* encoder = malloc(sizeof(InfraredEncoderRC6)); - encoder->common_encoder = infrared_common_encoder_alloc(&protocol_rc6); + encoder->common_encoder = infrared_common_encoder_alloc(&infrared_protocol_rc6); encoder->toggle_bit = false; return encoder; } diff --git a/lib/infrared/encoder_decoder/rc6/infrared_protocol_rc6.c b/lib/infrared/encoder_decoder/rc6/infrared_protocol_rc6.c new file mode 100644 index 00000000000..40a187d85cb --- /dev/null +++ b/lib/infrared/encoder_decoder/rc6/infrared_protocol_rc6.c @@ -0,0 +1,39 @@ +#include "infrared_protocol_rc6_i.h" + +const InfraredCommonProtocolSpec infrared_protocol_rc6 = { + .timings = + { + .preamble_mark = INFRARED_RC6_PREAMBLE_MARK, + .preamble_space = INFRARED_RC6_PREAMBLE_SPACE, + .bit1_mark = INFRARED_RC6_BIT, + .preamble_tolerance = INFRARED_RC6_PREAMBLE_TOLERANCE, + .bit_tolerance = INFRARED_RC6_BIT_TOLERANCE, + .silence_time = INFRARED_RC6_SILENCE, + .min_split_time = INFRARED_RC6_MIN_SPLIT_TIME, + }, + .databit_len[0] = + 1 + 3 + 1 + 8 + + 8, // start_bit + 3 mode bits, + 1 toggle bit (x2 timing) + 8 address + 8 command + .manchester_start_from_space = false, + .decode = infrared_decoder_rc6_decode_manchester, + .encode = infrared_encoder_rc6_encode_manchester, + .interpret = infrared_decoder_rc6_interpret, + .decode_repeat = NULL, + .encode_repeat = NULL, +}; + +static const InfraredProtocolVariant infrared_protocol_variant_rc6 = { + .name = "RC6", + .address_length = 8, + .command_length = 8, + .frequency = INFRARED_RC6_CARRIER_FREQUENCY, + .duty_cycle = INFRARED_RC6_DUTY_CYCLE, + .repeat_count = INFRARED_RC6_REPEAT_COUNT_MIN, +}; + +const InfraredProtocolVariant* infrared_protocol_rc6_get_variant(InfraredProtocol protocol) { + if(protocol == InfraredProtocolRC6) + return &infrared_protocol_variant_rc6; + else + return NULL; +} diff --git a/lib/infrared/encoder_decoder/rc6/infrared_protocol_rc6.h b/lib/infrared/encoder_decoder/rc6/infrared_protocol_rc6.h new file mode 100644 index 00000000000..f0b1634111c --- /dev/null +++ b/lib/infrared/encoder_decoder/rc6/infrared_protocol_rc6.h @@ -0,0 +1,37 @@ +#pragma once + +#include "../infrared_i.h" + +/*************************************************************************************************** +* RC6 protocol description +* https://www.mikrocontroller.net/articles/IRMP_-_english#RC6_.2B_RC6A +**************************************************************************************************** +* Preamble Manchester/biphase Silence +* mark/space Modulation +* +* 2666 889 444/888 - bit (x2 for toggle bit) 2666 +* +* ________ __ __ __ __ ____ __ __ __ __ __ __ __ __ +* _ _________ ____ __ __ ____ __ __ __ __ __ __ __ __ _______________ +* | 1 | 0 | 0 | 0 | 0 | ... | ... | | +* s m2 m1 m0 T address (MSB) command (MSB) +* +* s - start bit (always 1) +* m0-2 - mode (000 for RC6) +* T - toggle bit, twice longer +* address - 8 bit +* command - 8 bit +***************************************************************************************************/ + +void* infrared_decoder_rc6_alloc(void); +void infrared_decoder_rc6_reset(void* decoder); +void infrared_decoder_rc6_free(void* decoder); +InfraredMessage* infrared_decoder_rc6_check_ready(void* ctx); +InfraredMessage* infrared_decoder_rc6_decode(void* decoder, bool level, uint32_t duration); + +void* infrared_encoder_rc6_alloc(void); +void infrared_encoder_rc6_reset(void* encoder_ptr, const InfraredMessage* message); +void infrared_encoder_rc6_free(void* decoder); +InfraredStatus infrared_encoder_rc6_encode(void* encoder_ptr, uint32_t* duration, bool* polarity); + +const InfraredProtocolVariant* infrared_protocol_rc6_get_variant(InfraredProtocol protocol); diff --git a/lib/infrared/encoder_decoder/rc6/infrared_protocol_rc6_i.h b/lib/infrared/encoder_decoder/rc6/infrared_protocol_rc6_i.h new file mode 100644 index 00000000000..06aa2a6a519 --- /dev/null +++ b/lib/infrared/encoder_decoder/rc6/infrared_protocol_rc6_i.h @@ -0,0 +1,28 @@ +#pragma once + +#include "../common/infrared_common_i.h" + +#define INFRARED_RC6_CARRIER_FREQUENCY 36000 +#define INFRARED_RC6_DUTY_CYCLE 0.33 + +#define INFRARED_RC6_PREAMBLE_MARK 2666 +#define INFRARED_RC6_PREAMBLE_SPACE 889 +#define INFRARED_RC6_BIT 444 // half of time-quant for 1 bit +#define INFRARED_RC6_PREAMBLE_TOLERANCE 200 // us +#define INFRARED_RC6_BIT_TOLERANCE 120 // us +/* protocol allows 2700 silence, but it is hard to send 1 message without repeat */ +#define INFRARED_RC6_SILENCE (2700 * 10) +#define INFRARED_RC6_MIN_SPLIT_TIME 2700 +#define INFRARED_RC6_REPEAT_COUNT_MIN 1 + +extern const InfraredCommonProtocolSpec infrared_protocol_rc6; + +bool infrared_decoder_rc6_interpret(InfraredCommonDecoder* decoder); +InfraredStatus infrared_decoder_rc6_decode_manchester( + InfraredCommonDecoder* decoder, + bool level, + uint32_t timing); +InfraredStatus infrared_encoder_rc6_encode_manchester( + InfraredCommonEncoder* encoder_ptr, + uint32_t* duration, + bool* polarity); diff --git a/lib/infrared/encoder_decoder/rc6/infrared_rc6_spec.c b/lib/infrared/encoder_decoder/rc6/infrared_rc6_spec.c deleted file mode 100644 index 9e0ba746251..00000000000 --- a/lib/infrared/encoder_decoder/rc6/infrared_rc6_spec.c +++ /dev/null @@ -1,17 +0,0 @@ -#include "../infrared_i.h" -#include "infrared_protocol_defs_i.h" - -static const InfraredProtocolSpecification infrared_rc6_protocol_specification = { - .name = "RC6", - .address_length = 8, - .command_length = 8, - .frequency = INFRARED_RC6_CARRIER_FREQUENCY, - .duty_cycle = INFRARED_RC6_DUTY_CYCLE, -}; - -const InfraredProtocolSpecification* infrared_rc6_get_spec(InfraredProtocol protocol) { - if(protocol == InfraredProtocolRC6) - return &infrared_rc6_protocol_specification; - else - return NULL; -} diff --git a/lib/infrared/encoder_decoder/samsung/infrared_decoder_samsung.c b/lib/infrared/encoder_decoder/samsung/infrared_decoder_samsung.c index e8cd3b05cb5..32881d3c959 100644 --- a/lib/infrared/encoder_decoder/samsung/infrared_decoder_samsung.c +++ b/lib/infrared/encoder_decoder/samsung/infrared_decoder_samsung.c @@ -1,9 +1,5 @@ -#include "infrared.h" -#include "infrared_protocol_defs_i.h" -#include -#include -#include -#include "../infrared_i.h" +#include "infrared_protocol_samsung_i.h" +#include InfraredMessage* infrared_decoder_samsung32_check_ready(void* ctx) { return infrared_common_decoder_check_ready(ctx); @@ -57,7 +53,7 @@ InfraredStatus infrared_decoder_samsung32_decode_repeat(InfraredCommonDecoder* d } void* infrared_decoder_samsung32_alloc(void) { - return infrared_common_decoder_alloc(&protocol_samsung32); + return infrared_common_decoder_alloc(&infrared_protocol_samsung32); } InfraredMessage* infrared_decoder_samsung32_decode(void* decoder, bool level, uint32_t duration) { diff --git a/lib/infrared/encoder_decoder/samsung/infrared_encoder_samsung.c b/lib/infrared/encoder_decoder/samsung/infrared_encoder_samsung.c index 75b037f0076..3aed50656e2 100644 --- a/lib/infrared/encoder_decoder/samsung/infrared_encoder_samsung.c +++ b/lib/infrared/encoder_decoder/samsung/infrared_encoder_samsung.c @@ -1,9 +1,7 @@ +#include "infrared_protocol_samsung_i.h" + #include -#include "common/infrared_common_i.h" -#include -#include "../infrared_i.h" -#include "infrared_protocol_defs_i.h" -#include +#include static const uint32_t repeat_timings[] = { INFRARED_SAMSUNG_REPEAT_PAUSE2, @@ -58,7 +56,7 @@ InfraredStatus infrared_encoder_samsung32_encode_repeat( } void* infrared_encoder_samsung32_alloc(void) { - return infrared_common_encoder_alloc(&protocol_samsung32); + return infrared_common_encoder_alloc(&infrared_protocol_samsung32); } void infrared_encoder_samsung32_free(void* encoder_ptr) { diff --git a/lib/infrared/encoder_decoder/samsung/infrared_protocol_samsung.c b/lib/infrared/encoder_decoder/samsung/infrared_protocol_samsung.c new file mode 100644 index 00000000000..ca78726acf9 --- /dev/null +++ b/lib/infrared/encoder_decoder/samsung/infrared_protocol_samsung.c @@ -0,0 +1,40 @@ +#include "infrared_protocol_samsung_i.h" + +const InfraredCommonProtocolSpec infrared_protocol_samsung32 = { + .timings = + { + .preamble_mark = INFRARED_SAMSUNG_PREAMBLE_MARK, + .preamble_space = INFRARED_SAMSUNG_PREAMBLE_SPACE, + .bit1_mark = INFRARED_SAMSUNG_BIT1_MARK, + .bit1_space = INFRARED_SAMSUNG_BIT1_SPACE, + .bit0_mark = INFRARED_SAMSUNG_BIT0_MARK, + .bit0_space = INFRARED_SAMSUNG_BIT0_SPACE, + .preamble_tolerance = INFRARED_SAMSUNG_PREAMBLE_TOLERANCE, + .bit_tolerance = INFRARED_SAMSUNG_BIT_TOLERANCE, + .silence_time = INFRARED_SAMSUNG_SILENCE, + .min_split_time = INFRARED_SAMSUNG_MIN_SPLIT_TIME, + }, + .databit_len[0] = 32, + .no_stop_bit = false, + .decode = infrared_common_decode_pdwm, + .encode = infrared_common_encode_pdwm, + .interpret = infrared_decoder_samsung32_interpret, + .decode_repeat = infrared_decoder_samsung32_decode_repeat, + .encode_repeat = infrared_encoder_samsung32_encode_repeat, +}; + +static const InfraredProtocolVariant infrared_protocol_variant_samsung32 = { + .name = "Samsung32", + .address_length = 8, + .command_length = 8, + .frequency = INFRARED_COMMON_CARRIER_FREQUENCY, + .duty_cycle = INFRARED_COMMON_DUTY_CYCLE, + .repeat_count = INFRARED_SAMSUNG_REPEAT_COUNT_MIN, +}; + +const InfraredProtocolVariant* infrared_protocol_samsung32_get_variant(InfraredProtocol protocol) { + if(protocol == InfraredProtocolSamsung32) + return &infrared_protocol_variant_samsung32; + else + return NULL; +} diff --git a/lib/infrared/encoder_decoder/samsung/infrared_protocol_samsung.h b/lib/infrared/encoder_decoder/samsung/infrared_protocol_samsung.h new file mode 100644 index 00000000000..9abcb2e3e65 --- /dev/null +++ b/lib/infrared/encoder_decoder/samsung/infrared_protocol_samsung.h @@ -0,0 +1,31 @@ +#pragma once + +#include "../infrared_i.h" + +/*************************************************************************************************** +* SAMSUNG32 protocol description +* https://www.mikrocontroller.net/articles/IRMP_-_english#SAMSUNG +**************************************************************************************************** +* Preamble Preamble Pulse Distance/Width Pause Preamble Preamble Bit1 Stop +* mark space Modulation repeat repeat bit +* mark space +* +* 4500 4500 32 bit + stop bit 40000/100000 4500 4500 +* __________ _ _ _ _ _ _ _ _ _ _ _ ___________ _ _ +* _ __________ __ _ __ __ __ _ _ __ __ _ ________________ ____________ ____ ___ +* +***************************************************************************************************/ + +void* infrared_decoder_samsung32_alloc(void); +void infrared_decoder_samsung32_reset(void* decoder); +void infrared_decoder_samsung32_free(void* decoder); +InfraredMessage* infrared_decoder_samsung32_check_ready(void* ctx); +InfraredMessage* infrared_decoder_samsung32_decode(void* decoder, bool level, uint32_t duration); + +InfraredStatus + infrared_encoder_samsung32_encode(void* encoder_ptr, uint32_t* duration, bool* level); +void infrared_encoder_samsung32_reset(void* encoder_ptr, const InfraredMessage* message); +void* infrared_encoder_samsung32_alloc(void); +void infrared_encoder_samsung32_free(void* encoder_ptr); + +const InfraredProtocolVariant* infrared_protocol_samsung32_get_variant(InfraredProtocol protocol); diff --git a/lib/infrared/encoder_decoder/samsung/infrared_protocol_samsung_i.h b/lib/infrared/encoder_decoder/samsung/infrared_protocol_samsung_i.h new file mode 100644 index 00000000000..b8538494296 --- /dev/null +++ b/lib/infrared/encoder_decoder/samsung/infrared_protocol_samsung_i.h @@ -0,0 +1,35 @@ +#pragma once + +#include "../common/infrared_common_i.h" + +#define INFRARED_SAMSUNG_PREAMBLE_MARK 4500 +#define INFRARED_SAMSUNG_PREAMBLE_SPACE 4500 +#define INFRARED_SAMSUNG_BIT1_MARK 550 +#define INFRARED_SAMSUNG_BIT1_SPACE 1650 +#define INFRARED_SAMSUNG_BIT0_MARK 550 +#define INFRARED_SAMSUNG_BIT0_SPACE 550 +#define INFRARED_SAMSUNG_REPEAT_PAUSE_MIN 30000 +#define INFRARED_SAMSUNG_REPEAT_PAUSE_MAX 140000 +#define INFRARED_SAMSUNG_REPEAT_PAUSE1 46000 +#define INFRARED_SAMSUNG_REPEAT_PAUSE2 97000 +#define INFRARED_SAMSUNG_REPEAT_COUNT_MIN 1 +/* Samsung silence have to be greater than REPEAT MAX + * otherwise there can be problems during unit tests parsing + * of some data. Real tolerances we don't know, but in real life + * silence time should be greater than max repeat time. This is + * because of similar preambule timings for repeat and first messages. */ +#define INFRARED_SAMSUNG_MIN_SPLIT_TIME 5000 +#define INFRARED_SAMSUNG_SILENCE 145000 +#define INFRARED_SAMSUNG_REPEAT_MARK 4500 +#define INFRARED_SAMSUNG_REPEAT_SPACE 4500 +#define INFRARED_SAMSUNG_PREAMBLE_TOLERANCE 200 // us +#define INFRARED_SAMSUNG_BIT_TOLERANCE 120 // us + +bool infrared_decoder_samsung32_interpret(InfraredCommonDecoder* decoder); +InfraredStatus infrared_decoder_samsung32_decode_repeat(InfraredCommonDecoder* decoder); +InfraredStatus infrared_encoder_samsung32_encode_repeat( + InfraredCommonEncoder* encoder, + uint32_t* duration, + bool* level); + +extern const InfraredCommonProtocolSpec infrared_protocol_samsung32; diff --git a/lib/infrared/encoder_decoder/samsung/infrared_samsung_spec.c b/lib/infrared/encoder_decoder/samsung/infrared_samsung_spec.c deleted file mode 100644 index f4cbf699ec8..00000000000 --- a/lib/infrared/encoder_decoder/samsung/infrared_samsung_spec.c +++ /dev/null @@ -1,17 +0,0 @@ -#include "../infrared_i.h" -#include "infrared_protocol_defs_i.h" - -static const InfraredProtocolSpecification infrared_samsung32_protocol_specification = { - .name = "Samsung32", - .address_length = 8, - .command_length = 8, - .frequency = INFRARED_COMMON_CARRIER_FREQUENCY, - .duty_cycle = INFRARED_COMMON_DUTY_CYCLE, -}; - -const InfraredProtocolSpecification* infrared_samsung32_get_spec(InfraredProtocol protocol) { - if(protocol == InfraredProtocolSamsung32) - return &infrared_samsung32_protocol_specification; - else - return NULL; -} diff --git a/lib/infrared/encoder_decoder/sirc/infrared_decoder_sirc.c b/lib/infrared/encoder_decoder/sirc/infrared_decoder_sirc.c index 45f06c9422a..fc18a183ca7 100644 --- a/lib/infrared/encoder_decoder/sirc/infrared_decoder_sirc.c +++ b/lib/infrared/encoder_decoder/sirc/infrared_decoder_sirc.c @@ -1,10 +1,5 @@ -#include "common/infrared_common_i.h" -#include "infrared.h" -#include "infrared_protocol_defs_i.h" -#include -#include -#include -#include "../infrared_i.h" +#include "infrared_protocol_sirc_i.h" +#include InfraredMessage* infrared_decoder_sirc_check_ready(void* ctx) { return infrared_common_decoder_check_ready(ctx); @@ -44,7 +39,7 @@ bool infrared_decoder_sirc_interpret(InfraredCommonDecoder* decoder) { } void* infrared_decoder_sirc_alloc(void) { - return infrared_common_decoder_alloc(&protocol_sirc); + return infrared_common_decoder_alloc(&infrared_protocol_sirc); } InfraredMessage* infrared_decoder_sirc_decode(void* decoder, bool level, uint32_t duration) { diff --git a/lib/infrared/encoder_decoder/sirc/infrared_encoder_sirc.c b/lib/infrared/encoder_decoder/sirc/infrared_encoder_sirc.c index 2c2bda1afd8..6adf2235ce4 100644 --- a/lib/infrared/encoder_decoder/sirc/infrared_encoder_sirc.c +++ b/lib/infrared/encoder_decoder/sirc/infrared_encoder_sirc.c @@ -1,10 +1,5 @@ +#include "infrared_protocol_sirc_i.h" #include -#include "infrared.h" -#include "common/infrared_common_i.h" -#include -#include "../infrared_i.h" -#include "infrared_protocol_defs_i.h" -#include void infrared_encoder_sirc_reset(void* encoder_ptr, const InfraredMessage* message) { furi_assert(encoder_ptr); @@ -53,7 +48,7 @@ InfraredStatus infrared_encoder_sirc_encode_repeat( } void* infrared_encoder_sirc_alloc(void) { - return infrared_common_encoder_alloc(&protocol_sirc); + return infrared_common_encoder_alloc(&infrared_protocol_sirc); } void infrared_encoder_sirc_free(void* encoder_ptr) { diff --git a/lib/infrared/encoder_decoder/sirc/infrared_protocol_sirc.c b/lib/infrared/encoder_decoder/sirc/infrared_protocol_sirc.c new file mode 100644 index 00000000000..b527fba980c --- /dev/null +++ b/lib/infrared/encoder_decoder/sirc/infrared_protocol_sirc.c @@ -0,0 +1,64 @@ +#include "infrared_protocol_sirc_i.h" + +const InfraredCommonProtocolSpec infrared_protocol_sirc = { + .timings = + { + .preamble_mark = INFRARED_SIRC_PREAMBLE_MARK, + .preamble_space = INFRARED_SIRC_PREAMBLE_SPACE, + .bit1_mark = INFRARED_SIRC_BIT1_MARK, + .bit1_space = INFRARED_SIRC_BIT1_SPACE, + .bit0_mark = INFRARED_SIRC_BIT0_MARK, + .bit0_space = INFRARED_SIRC_BIT0_SPACE, + .preamble_tolerance = INFRARED_SIRC_PREAMBLE_TOLERANCE, + .bit_tolerance = INFRARED_SIRC_BIT_TOLERANCE, + .silence_time = INFRARED_SIRC_SILENCE, + .min_split_time = INFRARED_SIRC_MIN_SPLIT_TIME, + }, + .databit_len[0] = 20, + .databit_len[1] = 15, + .databit_len[2] = 12, + .no_stop_bit = true, + .decode = infrared_common_decode_pdwm, + .encode = infrared_common_encode_pdwm, + .interpret = infrared_decoder_sirc_interpret, + .decode_repeat = NULL, + .encode_repeat = infrared_encoder_sirc_encode_repeat, +}; + +static const InfraredProtocolVariant infrared_protocol_variant_sirc = { + .name = "SIRC", + .address_length = 5, + .command_length = 7, + .frequency = INFRARED_SIRC_CARRIER_FREQUENCY, + .duty_cycle = INFRARED_SIRC_DUTY_CYCLE, + .repeat_count = INFRARED_SIRC_REPEAT_COUNT_MIN, +}; + +static const InfraredProtocolVariant infrared_protocol_variant_sirc15 = { + .name = "SIRC15", + .address_length = 8, + .command_length = 7, + .frequency = INFRARED_SIRC_CARRIER_FREQUENCY, + .duty_cycle = INFRARED_SIRC_DUTY_CYCLE, + .repeat_count = INFRARED_SIRC_REPEAT_COUNT_MIN, +}; + +static const InfraredProtocolVariant infrared_protocol_variant_sirc20 = { + .name = "SIRC20", + .address_length = 13, + .command_length = 7, + .frequency = INFRARED_SIRC_CARRIER_FREQUENCY, + .duty_cycle = INFRARED_SIRC_DUTY_CYCLE, + .repeat_count = INFRARED_SIRC_REPEAT_COUNT_MIN, +}; + +const InfraredProtocolVariant* infrared_protocol_sirc_get_variant(InfraredProtocol protocol) { + if(protocol == InfraredProtocolSIRC) + return &infrared_protocol_variant_sirc; + else if(protocol == InfraredProtocolSIRC15) + return &infrared_protocol_variant_sirc15; + else if(protocol == InfraredProtocolSIRC20) + return &infrared_protocol_variant_sirc20; + else + return NULL; +} diff --git a/lib/infrared/encoder_decoder/sirc/infrared_protocol_sirc.h b/lib/infrared/encoder_decoder/sirc/infrared_protocol_sirc.h new file mode 100644 index 00000000000..0c3bcd8a2af --- /dev/null +++ b/lib/infrared/encoder_decoder/sirc/infrared_protocol_sirc.h @@ -0,0 +1,37 @@ +#pragma once + +#include "../infrared_i.h" + +/*************************************************************************************************** +* Sony SIRC protocol description +* https://www.sbprojects.net/knowledge/ir/sirc.php +* http://picprojects.org.uk/ +**************************************************************************************************** +* Preamble Preamble Pulse Width Modulation Pause Entirely repeat +* mark space up to period message.. +* +* 2400 600 12/15/20 bits (600,1200) ...45000 2400 600 +* __________ _ _ _ _ _ _ _ _ _ _ _ _ _ __________ _ _ +* ____ __________ _ _ _ __ __ __ _ _ __ __ _ _ ____________________ __________ _ +* | command | address | +* SIRC | 7b LSB | 5b LSB | +* SIRC15 | 7b LSB | 8b LSB | +* SIRC20 | 7b LSB | 13b LSB | +* +* No way to determine either next message is repeat or not, +* so recognize only fact message received. Sony remotes always send at least 3 messages. +* Assume 8 last extended bits for SIRC20 are address bits. +***************************************************************************************************/ + +void* infrared_decoder_sirc_alloc(void); +void infrared_decoder_sirc_reset(void* decoder); +InfraredMessage* infrared_decoder_sirc_check_ready(void* decoder); +void infrared_decoder_sirc_free(void* decoder); +InfraredMessage* infrared_decoder_sirc_decode(void* decoder, bool level, uint32_t duration); + +void* infrared_encoder_sirc_alloc(void); +void infrared_encoder_sirc_reset(void* encoder_ptr, const InfraredMessage* message); +void infrared_encoder_sirc_free(void* decoder); +InfraredStatus infrared_encoder_sirc_encode(void* encoder_ptr, uint32_t* duration, bool* polarity); + +const InfraredProtocolVariant* infrared_protocol_sirc_get_variant(InfraredProtocol protocol); diff --git a/lib/infrared/encoder_decoder/sirc/infrared_protocol_sirc_i.h b/lib/infrared/encoder_decoder/sirc/infrared_protocol_sirc_i.h new file mode 100644 index 00000000000..e38be9bc868 --- /dev/null +++ b/lib/infrared/encoder_decoder/sirc/infrared_protocol_sirc_i.h @@ -0,0 +1,26 @@ +#pragma once + +#include "../common/infrared_common_i.h" + +#define INFRARED_SIRC_CARRIER_FREQUENCY 40000 +#define INFRARED_SIRC_DUTY_CYCLE 0.33 +#define INFRARED_SIRC_PREAMBLE_MARK 2400 +#define INFRARED_SIRC_PREAMBLE_SPACE 600 +#define INFRARED_SIRC_BIT1_MARK 1200 +#define INFRARED_SIRC_BIT1_SPACE 600 +#define INFRARED_SIRC_BIT0_MARK 600 +#define INFRARED_SIRC_BIT0_SPACE 600 +#define INFRARED_SIRC_PREAMBLE_TOLERANCE 200 // us +#define INFRARED_SIRC_BIT_TOLERANCE 120 // us +#define INFRARED_SIRC_SILENCE 10000 +#define INFRARED_SIRC_MIN_SPLIT_TIME (INFRARED_SIRC_SILENCE - 1000) +#define INFRARED_SIRC_REPEAT_PERIOD 45000 +#define INFRARED_SIRC_REPEAT_COUNT_MIN 3 + +extern const InfraredCommonProtocolSpec infrared_protocol_sirc; + +bool infrared_decoder_sirc_interpret(InfraredCommonDecoder* decoder); +InfraredStatus infrared_encoder_sirc_encode_repeat( + InfraredCommonEncoder* encoder, + uint32_t* duration, + bool* level); diff --git a/lib/infrared/encoder_decoder/sirc/infrared_sirc_spec.c b/lib/infrared/encoder_decoder/sirc/infrared_sirc_spec.c deleted file mode 100644 index 9bf35908e98..00000000000 --- a/lib/infrared/encoder_decoder/sirc/infrared_sirc_spec.c +++ /dev/null @@ -1,37 +0,0 @@ -#include "../infrared_i.h" -#include "infrared_protocol_defs_i.h" - -static const InfraredProtocolSpecification infrared_sirc_protocol_specification = { - .name = "SIRC", - .address_length = 5, - .command_length = 7, - .frequency = INFRARED_SIRC_CARRIER_FREQUENCY, - .duty_cycle = INFRARED_SIRC_DUTY_CYCLE, -}; - -static const InfraredProtocolSpecification infrared_sirc15_protocol_specification = { - .name = "SIRC15", - .address_length = 8, - .command_length = 7, - .frequency = INFRARED_SIRC_CARRIER_FREQUENCY, - .duty_cycle = INFRARED_SIRC_DUTY_CYCLE, -}; - -static const InfraredProtocolSpecification infrared_sirc20_protocol_specification = { - .name = "SIRC20", - .address_length = 13, - .command_length = 7, - .frequency = INFRARED_SIRC_CARRIER_FREQUENCY, - .duty_cycle = INFRARED_SIRC_DUTY_CYCLE, -}; - -const InfraredProtocolSpecification* infrared_sirc_get_spec(InfraredProtocol protocol) { - if(protocol == InfraredProtocolSIRC) - return &infrared_sirc_protocol_specification; - else if(protocol == InfraredProtocolSIRC15) - return &infrared_sirc15_protocol_specification; - else if(protocol == InfraredProtocolSIRC20) - return &infrared_sirc20_protocol_specification; - else - return NULL; -} diff --git a/lib/infrared/worker/infrared_transmit.c b/lib/infrared/worker/infrared_transmit.c index 1a508301911..113fb632448 100644 --- a/lib/infrared/worker/infrared_transmit.c +++ b/lib/infrared/worker/infrared_transmit.c @@ -101,7 +101,8 @@ void infrared_send(const InfraredMessage* message, int times) { InfraredEncoderHandler* handler = infrared_alloc_encoder(); infrared_reset_encoder(handler, message); - infrared_tx_number_of_transmissions = times; + infrared_tx_number_of_transmissions = + MAX((int)infrared_get_protocol_min_repeat_count(message->protocol), times); uint32_t frequency = infrared_get_protocol_frequency(message->protocol); float duty_cycle = infrared_get_protocol_duty_cycle(message->protocol); diff --git a/lib/infrared/worker/infrared_worker.c b/lib/infrared/worker/infrared_worker.c index 033dba52597..5add1413e90 100644 --- a/lib/infrared/worker/infrared_worker.c +++ b/lib/infrared/worker/infrared_worker.c @@ -1,14 +1,11 @@ -#include -#include -#include "sys/_stdint.h" #include "infrared_worker.h" -#include + #include -#include -#include -#include #include +#include +#include + #include #define INFRARED_WORKER_RX_TIMEOUT INFRARED_RAW_RX_TIMING_DELAY_US @@ -471,18 +468,23 @@ static int32_t infrared_worker_tx_thread(void* thread_context) { furi_assert(instance->state == InfraredWorkerStateStartTx); furi_assert(thread_context); + size_t repeats_left = + instance->signal.decoded ? + infrared_get_protocol_min_repeat_count(instance->signal.message.protocol) : + 1; uint32_t events = 0; - bool new_data_available = true; - bool exit = false; - exit = !infrared_get_new_signal(instance); - furi_assert(!exit); + bool exit_pending = false; - while(!exit) { + bool running = infrared_get_new_signal(instance); + furi_assert(running); + + while(running) { switch(instance->state) { case InfraredWorkerStateStartTx: + --repeats_left; /* The first message does not result in TX_MESSAGE_SENT event for some reason */ instance->tx.need_reinitialization = false; - new_data_available = infrared_worker_tx_fill_buffer(instance); + const bool new_data_available = infrared_worker_tx_fill_buffer(instance); furi_hal_infrared_async_tx_start(instance->tx.frequency, instance->tx.duty_cycle); if(!new_data_available) { @@ -496,7 +498,7 @@ static int32_t infrared_worker_tx_thread(void* thread_context) { break; case InfraredWorkerStateStopTx: furi_hal_infrared_async_tx_stop(); - exit = true; + running = false; break; case InfraredWorkerStateWaitTxEnd: furi_hal_infrared_async_tx_wait_termination(); @@ -504,18 +506,18 @@ static int32_t infrared_worker_tx_thread(void* thread_context) { events = furi_thread_flags_get(); if(events & INFRARED_WORKER_EXIT) { - exit = true; + running = false; break; } break; case InfraredWorkerStateRunTx: - events = furi_thread_flags_wait(INFRARED_WORKER_ALL_TX_EVENTS, 0, FuriWaitForever); + events = furi_thread_flags_wait( + INFRARED_WORKER_ALL_TX_EVENTS, FuriFlagWaitAny, FuriWaitForever); furi_check(events & INFRARED_WORKER_ALL_TX_EVENTS); /* at least one caught */ if(events & INFRARED_WORKER_EXIT) { - instance->state = InfraredWorkerStateStopTx; - break; + exit_pending = true; } if(events & INFRARED_WORKER_TX_FILL_BUFFER) { @@ -527,9 +529,19 @@ static int32_t infrared_worker_tx_thread(void* thread_context) { } if(events & INFRARED_WORKER_TX_MESSAGE_SENT) { - if(instance->tx.message_sent_callback) + if(repeats_left > 0) { + --repeats_left; + } + + if(instance->tx.message_sent_callback) { instance->tx.message_sent_callback(instance->tx.message_sent_context); + } } + + if(exit_pending && repeats_left == 0) { + instance->state = InfraredWorkerStateStopTx; + } + break; default: furi_assert(0); From 073fb3861ac310c07bdbbb3fd12fc37fe8d9799c Mon Sep 17 00:00:00 2001 From: gid9798 <30450294+gid9798@users.noreply.github.com> Date: Fri, 13 Jan 2023 17:11:12 +0300 Subject: [PATCH 349/824] Add the ability to turn pages in infrared (#2271) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- .../services/gui/modules/button_menu.c | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/applications/services/gui/modules/button_menu.c b/applications/services/gui/modules/button_menu.c index 60bd160c5ba..427a3e1aeac 100644 --- a/applications/services/gui/modules/button_menu.c +++ b/applications/services/gui/modules/button_menu.c @@ -178,6 +178,47 @@ static void button_menu_process_down(ButtonMenu* button_menu) { true); } +static void button_menu_process_right(ButtonMenu* button_menu) { + furi_assert(button_menu); + + with_view_model( + button_menu->view, + ButtonMenuModel * model, + { + if(ButtonMenuItemArray_size(model->items) > BUTTONS_PER_SCREEN) { + size_t position_candidate = model->position + BUTTONS_PER_SCREEN; + position_candidate -= position_candidate % BUTTONS_PER_SCREEN; + if(position_candidate < (ButtonMenuItemArray_size(model->items))) { + model->position = position_candidate; + } else { + model->position = 0; + } + } + }, + true); +} + +static void button_menu_process_left(ButtonMenu* button_menu) { + furi_assert(button_menu); + + with_view_model( + button_menu->view, + ButtonMenuModel * model, + { + if(ButtonMenuItemArray_size(model->items) > BUTTONS_PER_SCREEN) { + size_t position_candidate; + if(model->position < BUTTONS_PER_SCREEN) { + position_candidate = (ButtonMenuItemArray_size(model->items) - 1); + } else { + position_candidate = model->position - BUTTONS_PER_SCREEN; + }; + position_candidate -= position_candidate % BUTTONS_PER_SCREEN; + model->position = position_candidate; + } + }, + true); +} + static void button_menu_process_ok(ButtonMenu* button_menu, InputType type) { furi_assert(button_menu); @@ -239,6 +280,14 @@ static bool button_menu_view_input_callback(InputEvent* event, void* context) { consumed = true; button_menu_process_down(button_menu); break; + case InputKeyRight: + consumed = true; + button_menu_process_right(button_menu); + break; + case InputKeyLeft: + consumed = true; + button_menu_process_left(button_menu); + break; default: break; } From 9e1a6a6d2ec4a340b2fc0dceda86116044044070 Mon Sep 17 00:00:00 2001 From: Eric Betts Date: Mon, 16 Jan 2023 01:36:59 -0800 Subject: [PATCH 350/824] relocate R_ARM_CALL (#2305) --- lib/flipper_application/elf/elf_file.c | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/flipper_application/elf/elf_file.c b/lib/flipper_application/elf/elf_file.c index bf98650a254..64d5755efa5 100644 --- a/lib/flipper_application/elf/elf_file.c +++ b/lib/flipper_application/elf/elf_file.c @@ -315,6 +315,7 @@ static bool elf_relocate_symbol(ELFFile* elf, Elf32_Addr relAddr, int type, Elf3 FURI_LOG_D(TAG, " R_ARM_ABS32 relocated is 0x%08X", (unsigned int)*((uint32_t*)relAddr)); break; case R_ARM_THM_PC22: + case R_ARM_CALL: case R_ARM_THM_JUMP24: elf_relocate_jmp_call(elf, relAddr, type, symAddr); FURI_LOG_D( From 341610b8a18fbff13521c3441ae30f4a3bb865fe Mon Sep 17 00:00:00 2001 From: hedger Date: Tue, 17 Jan 2023 15:55:49 +0300 Subject: [PATCH 351/824] [FL-3080] fbt: PVS support (#2286) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fbt: added firmware_pvscheck & firmware_pvs targets for *nix * fbt: pvs support on Windows * fbt: additional fixes & docs for PVS support * fbt: fixes for updater env configuration * github: reworked pvs workflow * vscode: added PVS shortcut * pvs: added --ignore-ccache flag * fbt: pvs: opening web browser if there are warnings * fbt: pvs: added browser handler for mac * github: fixed report path for PVS * fbt: pvs: fixed report upload path * removed intentional PVS warning * fixed more PVS warnings * fixed secplus_v1 PVS warning * fbt: added PVSNOBROWSER flag * github: setting PVSNOBROWSER for pvs runs * fbt: less debug output Co-authored-by: あく --- .github/workflows/pvs_studio.yml | 27 +----- .pvsoptions | 2 +- .vscode/example/tasks.json | 6 ++ assets/SConscript | 6 +- documentation/fbt.md | 1 + firmware.scons | 21 +++++ furi/core/core_defines.h | 6 +- lib/lfrfid/protocols/protocol_fdx_b.c | 2 +- lib/subghz/blocks/math.h | 12 ++- lib/subghz/protocols/chamberlain_code.c | 4 +- lib/subghz/protocols/secplus_v1.c | 2 +- scripts/fbt_tools/fbt_extapps.py | 2 + scripts/fbt_tools/fbt_help.py | 2 + scripts/fbt_tools/pvsstudio.py | 106 ++++++++++++++++++++++++ site_scons/commandline.scons | 5 ++ site_scons/extapps.scons | 5 ++ 16 files changed, 174 insertions(+), 35 deletions(-) create mode 100644 scripts/fbt_tools/pvsstudio.py diff --git a/.github/workflows/pvs_studio.yml b/.github/workflows/pvs_studio.yml index 46ee8801db3..50f8f0aa61a 100644 --- a/.github/workflows/pvs_studio.yml +++ b/.github/workflows/pvs_studio.yml @@ -43,36 +43,15 @@ jobs: fi python3 scripts/get_env.py "--event_file=${{ github.event_path }}" "--type=$TYPE" - - name: 'Make reports directory' + - name: 'Supply PVS credentials' run: | - rm -rf reports/ - mkdir reports - - - name: 'Generate compile_comands.json' - run: | - ./fbt COMPACT=1 version_json proto_ver icons firmware_cdb dolphin_internal dolphin_blocking _fap_icons api_syms - - - name: 'Static code analysis' - run: | - source scripts/toolchain/fbtenv.sh pvs-studio-analyzer credentials ${{ secrets.PVS_STUDIO_CREDENTIALS }} - pvs-studio-analyzer analyze \ - @.pvsoptions \ - -C gccarm \ - -j$(grep -c processor /proc/cpuinfo) \ - -f build/f7-firmware-DC/compile_commands.json \ - -o PVS-Studio.log - name: 'Convert PVS-Studio output to html and detect warnings' id: pvs-warn run: | WARNINGS=0 - plog-converter \ - -a GA:1,2,3 \ - -t fullhtml \ - --indicate-warnings \ - PVS-Studio.log \ - -o reports/${DEFAULT_TARGET}-${SUFFIX} || WARNINGS=1 + ./fbt COMPACT=1 PVSNOBROWSER=1 firmware_pvs || WARNINGS=1 echo "warnings=${WARNINGS}" >> $GITHUB_OUTPUT - name: 'Upload artifacts to update server' @@ -84,7 +63,7 @@ jobs: chmod 600 ./deploy_key; rsync -avrzP --mkpath \ -e 'ssh -p ${{ secrets.RSYNC_DEPLOY_PORT }} -i ./deploy_key' \ - reports/ ${{ secrets.RSYNC_DEPLOY_USER }}@${{ secrets.RSYNC_DEPLOY_HOST }}:/home/data/firmware-pvs-studio-report/"${BRANCH_NAME}/"; + build/f7-firmware-DC/pvsreport/ ${{ secrets.RSYNC_DEPLOY_USER }}@${{ secrets.RSYNC_DEPLOY_HOST }}:/home/data/firmware-pvs-studio-report/"${BRANCH_NAME}/${{steps.names.outputs.default_target}}-${{steps.names.outputs.suffix}}/"; rm ./deploy_key; - name: 'Find Previous Comment' diff --git a/.pvsoptions b/.pvsoptions index 31bc4b8046e..ca1b2b572a4 100644 --- a/.pvsoptions +++ b/.pvsoptions @@ -1 +1 @@ ---rules-config .pvsconfig -e lib/fatfs -e lib/fnv1a-hash -e lib/FreeRTOS-Kernel -e lib/heatshrink -e lib/libusb_stm32 -e lib/littlefs -e lib/mbedtls -e lib/micro-ecc -e lib/microtar -e lib/mlib -e lib/qrcode -e lib/ST25RFAL002 -e lib/STM32CubeWB -e lib/u8g2 -e lib/nanopb -e */arm-none-eabi/* -e applications/plugins/dap_link/lib/free-dap +--ignore-ccache -C gccarm --rules-config .pvsconfig -e lib/fatfs -e lib/fnv1a-hash -e lib/FreeRTOS-Kernel -e lib/heatshrink -e lib/libusb_stm32 -e lib/littlefs -e lib/mbedtls -e lib/micro-ecc -e lib/microtar -e lib/mlib -e lib/qrcode -e lib/ST25RFAL002 -e lib/STM32CubeWB -e lib/u8g2 -e lib/nanopb -e */arm-none-eabi/* -e applications/plugins/dap_link/lib/free-dap diff --git a/.vscode/example/tasks.json b/.vscode/example/tasks.json index beedf8865b9..28e67d456dc 100644 --- a/.vscode/example/tasks.json +++ b/.vscode/example/tasks.json @@ -105,6 +105,12 @@ "type": "shell", "command": "./fbt COMPACT=1 DEBUG=0 FORCE=1 flash_usb_full" }, + { + "label": "[Debug] Create PVS-Studio report", + "group": "build", + "type": "shell", + "command": "./fbt firmware_pvs" + }, { "label": "[Debug] Build FAPs", "group": "build", diff --git a/assets/SConscript b/assets/SConscript index 63141829ec3..ef5d83c79bf 100644 --- a/assets/SConscript +++ b/assets/SConscript @@ -54,13 +54,14 @@ assetsenv.Alias("proto_ver", proto_ver) # Gather everything into a static lib assets_parts = (icons, proto, dolphin_blocking, dolphin_internal, proto_ver) +env.Replace(FW_ASSETS_HEADERS=assets_parts) assetslib = assetsenv.Library("${FW_LIB_NAME}", assets_parts) assetsenv.Install("${LIB_DIST_DIR}", assetslib) # Resources for SD card - +env.SetDefault(FW_RESOURCES=None) if assetsenv["IS_BASE_FIRMWARE"]: # External dolphin animations dolphin_external = assetsenv.DolphinExtBuilder( @@ -92,8 +93,7 @@ if assetsenv["IS_BASE_FIRMWARE"]: ) # Exporting resources node to external environment - env["FW_ASSETS_HEADERS"] = assets_parts - env["FW_RESOURCES"] = resources + env.Replace(FW_RESOURCES=resources) assetsenv.Alias("resources", resources) Return("assetslib") diff --git a/documentation/fbt.md b/documentation/fbt.md index 7b1aa8b48d7..5166d0ab719 100644 --- a/documentation/fbt.md +++ b/documentation/fbt.md @@ -56,6 +56,7 @@ To run cleanup (think of `make clean`) for specified targets, add the `-c` optio - `get_stlink` - output serial numbers for attached STLink probes. Used for specifying an adapter with `OPENOCD_ADAPTER_SERIAL=...`. - `lint`, `format` - run clang-format on the C source code to check and reformat it according to the `.clang-format` specs. - `lint_py`, `format_py` - run [black](https://black.readthedocs.io/en/stable/index.html) on the Python source code, build system files & application manifests. +- `firmware_pvs` - generate a PVS Studio report for the firmware. Requires PVS Studio to be availabe on your system's `PATH`. - `cli` - start a Flipper CLI session over USB. ### Firmware targets diff --git a/firmware.scons b/firmware.scons index d674bf160ea..8a52650c4b0 100644 --- a/firmware.scons +++ b/firmware.scons @@ -15,6 +15,7 @@ env = ENV.Clone( ("compilation_db", {"COMPILATIONDB_COMSTR": "\tCDB\t${TARGET}"}), "fwbin", "fbt_apps", + "pvsstudio", ], COMPILATIONDB_USE_ABSPATH=False, BUILD_DIR=fw_build_meta["build_dir"], @@ -69,6 +70,8 @@ env = ENV.Clone( ], }, }, + SDK_APISYMS=None, + _APP_ICONS=None, ) @@ -273,6 +276,24 @@ Precious(fwcdb) NoClean(fwcdb) Alias(fwenv["FIRMWARE_BUILD_CFG"] + "_cdb", fwcdb) +pvscheck = fwenv.PVSCheck("pvsreport.log", fwcdb) +Depends( + pvscheck, + [ + fwenv["FW_VERSION_JSON"], + fwenv["FW_ASSETS_HEADERS"], + fwenv["SDK_APISYMS"], + fwenv["_APP_ICONS"], + ], +) +Alias(fwenv["FIRMWARE_BUILD_CFG"] + "_pvscheck", pvscheck) +AlwaysBuild(pvscheck) +Precious(pvscheck) + +pvsreport = fwenv.PVSReport(None, pvscheck, REPORT_DIR=Dir("pvsreport")) +Alias(fwenv["FIRMWARE_BUILD_CFG"] + "_pvs", pvsreport) +AlwaysBuild(pvsreport) + # If current configuration was explicitly requested, generate compilation database # and link its directory as build/latest if should_gen_cdb_and_link_dir(fwenv, BUILD_TARGETS): diff --git a/furi/core/core_defines.h b/furi/core/core_defines.h index a0f50aff96a..03a364abd0f 100644 --- a/furi/core/core_defines.h +++ b/furi/core/core_defines.h @@ -93,7 +93,11 @@ extern "C" { #endif #ifndef FURI_BIT_CLEAR -#define FURI_BIT_CLEAR(x, n) ((x) &= ~(1UL << (n))) +#define FURI_BIT_CLEAR(x, n) \ + ({ \ + __typeof__(x) _x = (1); \ + (x) &= ~(_x << (n)); \ + }) #endif #define FURI_SW_MEMBARRIER() asm volatile("" : : : "memory") diff --git a/lib/lfrfid/protocols/protocol_fdx_b.c b/lib/lfrfid/protocols/protocol_fdx_b.c index 855356f2afd..dd54cffb060 100644 --- a/lib/lfrfid/protocols/protocol_fdx_b.c +++ b/lib/lfrfid/protocols/protocol_fdx_b.c @@ -244,7 +244,7 @@ LevelDuration protocol_fdx_b_encoder_yield(ProtocolFDXB* protocol) { static uint64_t protocol_fdx_b_get_national_code(const uint8_t* data) { uint64_t national_code = bit_lib_get_bits_32(data, 0, 32); national_code = national_code << 32; - national_code |= bit_lib_get_bits_32(data, 32, 6) << (32 - 6); + national_code |= (uint64_t)bit_lib_get_bits_32(data, 32, 6) << (32 - 6); bit_lib_reverse_bits((uint8_t*)&national_code, 0, 64); return national_code; } diff --git a/lib/subghz/blocks/math.h b/lib/subghz/blocks/math.h index 87c209f71d2..dcea3da5faf 100644 --- a/lib/subghz/blocks/math.h +++ b/lib/subghz/blocks/math.h @@ -5,8 +5,16 @@ #include #define bit_read(value, bit) (((value) >> (bit)) & 0x01) -#define bit_set(value, bit) ((value) |= (1UL << (bit))) -#define bit_clear(value, bit) ((value) &= ~(1UL << (bit))) +#define bit_set(value, bit) \ + ({ \ + __typeof__(value) _one = (1); \ + (value) |= (_one << (bit)); \ + }) +#define bit_clear(value, bit) \ + ({ \ + __typeof__(value) _one = (1); \ + (value) &= ~(_one << (bit)); \ + }) #define bit_write(value, bit, bitvalue) (bitvalue ? bit_set(value, bit) : bit_clear(value, bit)) #define DURATION_DIFF(x, y) (((x) < (y)) ? ((y) - (x)) : ((x) - (y))) diff --git a/lib/subghz/protocols/chamberlain_code.c b/lib/subghz/protocols/chamberlain_code.c index 3650a986731..32f4e9520e2 100644 --- a/lib/subghz/protocols/chamberlain_code.c +++ b/lib/subghz/protocols/chamberlain_code.c @@ -280,9 +280,9 @@ 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) { + if((data_tmp & 0xFll) == CHAMBERLAIN_CODE_BIT_0) { bit_write(data_res, i, 0); - } else if((data_tmp & 0xF) == CHAMBERLAIN_CODE_BIT_1) { + } else if((data_tmp & 0xFll) == CHAMBERLAIN_CODE_BIT_1) { bit_write(data_res, i, 1); } else { return false; diff --git a/lib/subghz/protocols/secplus_v1.c b/lib/subghz/protocols/secplus_v1.c index 75a44a26cd0..a1161101194 100644 --- a/lib/subghz/protocols/secplus_v1.c +++ b/lib/subghz/protocols/secplus_v1.c @@ -224,7 +224,7 @@ static bool subghz_protocol_secplus_v1_encode(SubGhzProtocolEncoderSecPlus_v1* i instance->generic.data &= 0xFFFFFFFF00000000; instance->generic.data |= rolling; - if(rolling > 0xFFFFFFFF) { + if(rolling == 0xFFFFFFFF) { rolling = 0xE6000000; } if(fixed > 0xCFD41B90) { diff --git a/scripts/fbt_tools/fbt_extapps.py b/scripts/fbt_tools/fbt_extapps.py index f0015cf2559..1199f5f8219 100644 --- a/scripts/fbt_tools/fbt_extapps.py +++ b/scripts/fbt_tools/fbt_extapps.py @@ -32,6 +32,7 @@ def BuildAppElf(env, app): ext_apps_work_dir = env.subst("$EXT_APPS_WORK_DIR") app_work_dir = os.path.join(ext_apps_work_dir, app.appid) + env.SetDefault(_APP_ICONS=[]) env.VariantDir(app_work_dir, app._appdir, duplicate=False) app_env = env.Clone(FAP_SRC_DIR=app._appdir, FAP_WORK_DIR=app_work_dir) @@ -63,6 +64,7 @@ def BuildAppElf(env, app): icon_bundle_name=f"{app.fap_icon_assets_symbol if app.fap_icon_assets_symbol else app.appid }_icons", ) app_env.Alias("_fap_icons", fap_icons) + env.Append(_APP_ICONS=[fap_icons]) private_libs = [] diff --git a/scripts/fbt_tools/fbt_help.py b/scripts/fbt_tools/fbt_help.py index 8cce9335b61..afdb36665c7 100644 --- a/scripts/fbt_tools/fbt_help.py +++ b/scripts/fbt_tools/fbt_help.py @@ -31,6 +31,8 @@ run linters format, format_py: run code formatters + firmware_pvs: + generate a PVS-Studio report For more targets & info, see documentation/fbt.md """ diff --git a/scripts/fbt_tools/pvsstudio.py b/scripts/fbt_tools/pvsstudio.py new file mode 100644 index 00000000000..02014836ae0 --- /dev/null +++ b/scripts/fbt_tools/pvsstudio.py @@ -0,0 +1,106 @@ +from SCons.Builder import Builder +from SCons.Action import Action +from SCons.Script import Delete, Mkdir, GetBuildFailures +import multiprocessing +import webbrowser +import atexit +import sys +import subprocess + +__no_browser = False + + +def _set_browser_action(target, source, env): + if env["PVSNOBROWSER"]: + global __no_browser + __no_browser = True + + +def emit_pvsreport(target, source, env): + target_dir = env["REPORT_DIR"] + if env["PLATFORM"] == "win32": + # Report generator on Windows emits to a subfolder of given output folder + target_dir = target_dir.Dir("fullhtml") + return [target_dir.File("index.html")], source + + +def atexist_handler(): + global __no_browser + if __no_browser: + return + + for bf in GetBuildFailures(): + if bf.node.exists and bf.node.name.endswith(".html"): + # macOS + if sys.platform == "darwin": + subprocess.run(["open", bf.node.abspath]) + else: + webbrowser.open(bf.node.abspath) + break + + +def generate(env): + env.SetDefault( + PVSNCORES=multiprocessing.cpu_count(), + PVSOPTIONS=[ + "@.pvsoptions", + "-j${PVSNCORES}", + # "--incremental", # kinda broken on PVS side + ], + PVSCONVOPTIONS=[ + "-a", + "GA:1,2,3", + "-t", + "fullhtml", + "--indicate-warnings", + ], + ) + + if env["PLATFORM"] == "win32": + env.SetDefault( + PVSCHECKBIN="CompilerCommandsAnalyzer.exe", + PVSCONVBIN="PlogConverter.exe", + ) + else: + env.SetDefault( + PVSCHECKBIN="pvs-studio-analyzer", + PVSCONVBIN="plog-converter", + ) + + if not env["VERBOSE"]: + env.SetDefault( + PVSCHECKCOMSTR="\tPVS\t${TARGET}", + PVSCONVCOMSTR="\tPVSREP\t${TARGET}", + ) + + env.Append( + BUILDERS={ + "PVSCheck": Builder( + action=Action( + '${PVSCHECKBIN} analyze ${PVSOPTIONS} -f "${SOURCE}" -o "${TARGET}"', + "${PVSCHECKCOMSTR}", + ), + suffix=".log", + src_suffix=".json", + ), + "PVSReport": Builder( + action=Action( + [ + Delete("${TARGET.dir}"), + # PlogConverter.exe and plog-converter have different behavior + Mkdir("${TARGET.dir}") if env["PLATFORM"] == "win32" else None, + Action(_set_browser_action, None), + '${PVSCONVBIN} ${PVSCONVOPTIONS} "${SOURCE}" -o "${REPORT_DIR}"', + ], + "${PVSCONVCOMSTR}", + ), + emitter=emit_pvsreport, + src_suffix=".log", + ), + } + ) + atexit.register(atexist_handler) + + +def exists(env): + return True diff --git a/site_scons/commandline.scons b/site_scons/commandline.scons index fc2534ed523..6d01eb8f6f8 100644 --- a/site_scons/commandline.scons +++ b/site_scons/commandline.scons @@ -235,6 +235,11 @@ vars.AddVariables( ("applications_user", False), ], ), + BoolVariable( + "PVSNOBROWSER", + help="Don't open browser after generating error repots", + default=False, + ), ) Return("vars") diff --git a/site_scons/extapps.scons b/site_scons/extapps.scons index b8f210563cd..4eff65cbf21 100644 --- a/site_scons/extapps.scons +++ b/site_scons/extapps.scons @@ -143,6 +143,11 @@ sdk_apisyms = appenv.SDKSymGenerator( "${BUILD_DIR}/assets/compiled/symbols.h", appenv["SDK_DEFINITION"] ) Alias("api_syms", sdk_apisyms) +ENV.Replace( + SDK_APISYMS=sdk_apisyms, + _APP_ICONS=appenv["_APP_ICONS"], +) + if appenv["FORCE"]: appenv.AlwaysBuild(sdk_source, sdk_tree, sdk_apicheck, sdk_apisyms) From a0814aab31d7f08eeffa5d424769ab1e303f4eb3 Mon Sep 17 00:00:00 2001 From: hedger Date: Tue, 17 Jan 2023 18:07:47 +0300 Subject: [PATCH 352/824] [FL-3081] fbt: removed DEBUG_TOOLS & FAP_EXAMPLES flags (#2291) * fbt: deprecated DEBUG_TOOLS & FAP_EXAMPLES flags * fixes for PVS warnings * scripts: fbt: fixed leaking PYTHONHOME --- SConstruct | 9 ++++++--- applications/debug/accessor/accessor_app.cpp | 3 ++- applications/debug/accessor/helpers/wiegand.cpp | 3 --- applications/debug/bt_debug_app/views/bt_test.c | 11 +++++++---- .../debug/file_browser_test/file_browser_app.c | 2 +- applications/examples/application.fam | 4 ++-- firmware.scons | 3 --- scripts/fbt_tools/fbt_extapps.py | 17 ++++++++++++----- scripts/toolchain/fbtenv.sh | 10 +++++++--- site_scons/commandline.scons | 11 +---------- site_scons/extapps.scons | 3 +-- 11 files changed, 39 insertions(+), 37 deletions(-) diff --git a/SConstruct b/SConstruct index b8c65044d06..62e37dfdcbc 100644 --- a/SConstruct +++ b/SConstruct @@ -148,9 +148,12 @@ fap_dist = [ for app_artifact in firmware_env["FW_EXTAPPS"].applications.values() ), ), - distenv.Install( - f"#/dist/{dist_dir}/apps", - "#/assets/resources/apps", + *( + distenv.Install( + f"#/dist/{dist_dir}/apps/{app_artifact.app.fap_category}", + app_artifact.compact[0], + ) + for app_artifact in firmware_env["FW_EXTAPPS"].applications.values() ), ] Depends( diff --git a/applications/debug/accessor/accessor_app.cpp b/applications/debug/accessor/accessor_app.cpp index 2e3e27ec456..9d3708ebebf 100644 --- a/applications/debug/accessor/accessor_app.cpp +++ b/applications/debug/accessor/accessor_app.cpp @@ -31,7 +31,8 @@ void AccessorApp::run(void) { onewire_host_stop(onewire_host); } -AccessorApp::AccessorApp() { +AccessorApp::AccessorApp() + : text_store{0} { notification = static_cast(furi_record_open(RECORD_NOTIFICATION)); onewire_host = onewire_host_alloc(); furi_hal_power_enable_otg(); diff --git a/applications/debug/accessor/helpers/wiegand.cpp b/applications/debug/accessor/helpers/wiegand.cpp index bb288554991..5cb3a85f58a 100644 --- a/applications/debug/accessor/helpers/wiegand.cpp +++ b/applications/debug/accessor/helpers/wiegand.cpp @@ -171,9 +171,6 @@ bool WIEGAND::DoWiegandConversion() { return true; } else { _lastWiegand = sysTick; - _bitCount = 0; - _cardTemp = 0; - _cardTempHigh = 0; return false; } diff --git a/applications/debug/bt_debug_app/views/bt_test.c b/applications/debug/bt_debug_app/views/bt_test.c index 9588b667b6c..cd52b8650e5 100644 --- a/applications/debug/bt_debug_app/views/bt_test.c +++ b/applications/debug/bt_debug_app/views/bt_test.c @@ -2,8 +2,11 @@ #include #include + +#include #include #include +#include #include struct BtTestParam { @@ -98,16 +101,16 @@ static void bt_test_draw_callback(Canvas* canvas, void* _model) { elements_scrollbar(canvas, model->position, BtTestParamArray_size(model->params)); canvas_draw_str(canvas, 6, 60, model->message); if(model->state == BtTestStateStarted) { - if(model->rssi != 0.0f) { + if(!float_is_equal(model->rssi, 0.0f)) { snprintf(info_str, sizeof(info_str), "RSSI:%3.1f dB", (double)model->rssi); canvas_draw_str_aligned(canvas, 124, 60, AlignRight, AlignBottom, info_str); } } else if(model->state == BtTestStateStopped) { if(model->packets_num_rx) { - snprintf(info_str, sizeof(info_str), "%ld pack rcv", model->packets_num_rx); + snprintf(info_str, sizeof(info_str), "%" PRIu32 " pack rcv", model->packets_num_rx); canvas_draw_str_aligned(canvas, 124, 60, AlignRight, AlignBottom, info_str); } else if(model->packets_num_tx) { - snprintf(info_str, sizeof(info_str), "%ld pack sent", model->packets_num_tx); + snprintf(info_str, sizeof(info_str), "%" PRIu32 " pack sent", model->packets_num_tx); canvas_draw_str_aligned(canvas, 124, 60, AlignRight, AlignBottom, info_str); } } @@ -153,7 +156,7 @@ static bool bt_test_input_callback(InputEvent* event, void* context) { } void bt_test_process_up(BtTest* bt_test) { - with_view_model( + with_view_model( // -V658 bt_test->view, BtTestModel * model, { diff --git a/applications/debug/file_browser_test/file_browser_app.c b/applications/debug/file_browser_test/file_browser_app.c index 996cb2bd247..bf423d34edd 100644 --- a/applications/debug/file_browser_test/file_browser_app.c +++ b/applications/debug/file_browser_test/file_browser_app.c @@ -48,7 +48,7 @@ FileBrowserApp* file_browser_app_alloc(char* arg) { app->file_path = furi_string_alloc(); app->file_browser = file_browser_alloc(app->file_path); - file_browser_configure(app->file_browser, "*", NULL, true, &I_badusb_10px, true); + file_browser_configure(app->file_browser, "*", NULL, true, false, &I_badusb_10px, true); view_dispatcher_add_view( app->view_dispatcher, FileBrowserAppViewStart, widget_get_view(app->widget)); diff --git a/applications/examples/application.fam b/applications/examples/application.fam index 16d240ccf43..8556714c902 100644 --- a/applications/examples/application.fam +++ b/applications/examples/application.fam @@ -1,5 +1,5 @@ App( - appid="sample_apps", - name="Sample apps bundle", + appid="example_apps", + name="Example apps bundle", apptype=FlipperAppType.METAPACKAGE, ) diff --git a/firmware.scons b/firmware.scons index 8a52650c4b0..3922c136e1a 100644 --- a/firmware.scons +++ b/firmware.scons @@ -131,9 +131,6 @@ if extra_int_apps := GetOption("extra_int_apps"): fwenv.Append(APPS=extra_int_apps.split(",")) -if fwenv["FAP_EXAMPLES"]: - fwenv.Append(APPDIRS=[("applications/examples", False)]) - for app_dir, _ in env["APPDIRS"]: app_dir_node = env.Dir("#").Dir(app_dir) diff --git a/scripts/fbt_tools/fbt_extapps.py b/scripts/fbt_tools/fbt_extapps.py index 1199f5f8219..214afd8afe8 100644 --- a/scripts/fbt_tools/fbt_extapps.py +++ b/scripts/fbt_tools/fbt_extapps.py @@ -7,7 +7,7 @@ import SCons.Warnings from fbt.elfmanifest import assemble_manifest_data -from fbt.appmanifest import FlipperApplication, FlipperManifestException +from fbt.appmanifest import FlipperApplication, FlipperManifestException, FlipperAppType from fbt.sdk.cache import SdkCache from fbt.util import extract_abs_dir_path @@ -234,11 +234,18 @@ def GetExtAppFromPath(env, app_dir): return app_artifacts -def fap_dist_emitter(target, source, env): +def resources_fap_dist_emitter(target, source, env): target_dir = target[0] target = [] for _, app_artifacts in env["EXT_APPS"].items(): + # We don't deploy example apps & debug tools with SD card resources + if ( + app_artifacts.app.apptype == FlipperAppType.DEBUG + or app_artifacts.app.fap_category == "Examples" + ): + continue + source.extend(app_artifacts.compact) target.append( target_dir.Dir(app_artifacts.app.fap_category).File( @@ -249,7 +256,7 @@ def fap_dist_emitter(target, source, env): return (target, source) -def fap_dist_action(target, source, env): +def resources_fap_dist_action(target, source, env): # FIXME target_dir = env.Dir("#/assets/resources/apps") @@ -282,10 +289,10 @@ def generate(env, **kw): BUILDERS={ "FapDist": Builder( action=Action( - fap_dist_action, + resources_fap_dist_action, "$FAPDISTCOMSTR", ), - emitter=fap_dist_emitter, + emitter=resources_fap_dist_emitter, ), "EmbedAppMetadata": Builder( action=[ diff --git a/scripts/toolchain/fbtenv.sh b/scripts/toolchain/fbtenv.sh index f3e4cb1fa3f..dd5484aa9ab 100755 --- a/scripts/toolchain/fbtenv.sh +++ b/scripts/toolchain/fbtenv.sh @@ -43,9 +43,11 @@ fbtenv_restore_env() PYTHONNOUSERSITE="$SAVED_PYTHONNOUSERSITE"; PYTHONPATH="$SAVED_PYTHONPATH"; + PYTHONHOME="$SAVED_PYTHONHOME"; unset SAVED_PYTHONNOUSERSITE; unset SAVED_PYTHONPATH; + unset SAVED_PYTHONHOME; unset SCRIPT_PATH; unset FBT_TOOLCHAIN_VERSION; @@ -69,7 +71,7 @@ fbtenv_check_sourced() return 1; } -fbtenv_chck_many_source() +fbtenv_check_if_sourced_multiple_times() { if ! echo "${PS1:-""}" | grep -qF "[fbt]"; then if ! echo "${PROMPT:-""}" | grep -qF "[fbt]"; then @@ -275,7 +277,7 @@ fbtenv_main() fbtenv_restore_env; return 0; fi - fbtenv_chck_many_source; # many source it's just a warning + fbtenv_check_if_sourced_multiple_times; # many source it's just a warning fbtenv_check_script_path || return 1; fbtenv_check_download_toolchain || return 1; fbtenv_set_shell_prompt; @@ -283,12 +285,14 @@ fbtenv_main() PATH="$TOOLCHAIN_ARCH_DIR/bin:$PATH"; PATH="$TOOLCHAIN_ARCH_DIR/protobuf/bin:$PATH"; PATH="$TOOLCHAIN_ARCH_DIR/openocd/bin:$PATH"; - + SAVED_PYTHONNOUSERSITE="${PYTHONNOUSERSITE:-""}"; SAVED_PYTHONPATH="${PYTHONPATH:-""}"; + SAVED_PYTHONHOME="${PYTHONHOME:-""}"; PYTHONNOUSERSITE=1; PYTHONPATH=; + PYTHONHOME=; } fbtenv_main "${1:-""}"; diff --git a/site_scons/commandline.scons b/site_scons/commandline.scons index 6d01eb8f6f8..e01c8a39edb 100644 --- a/site_scons/commandline.scons +++ b/site_scons/commandline.scons @@ -81,16 +81,6 @@ vars.AddVariables( "7", ], ), - BoolVariable( - "DEBUG_TOOLS", - help="Enable debug tools to be built", - default=False, - ), - BoolVariable( - "FAP_EXAMPLES", - help="Enable example applications to be built", - default=False, - ), ( "DIST_SUFFIX", "Suffix for binaries in build output for dist targets", @@ -232,6 +222,7 @@ vars.AddVariables( ("applications/system", False), ("applications/debug", False), ("applications/plugins", False), + ("applications/examples", False), ("applications_user", False), ], ), diff --git a/site_scons/extapps.scons b/site_scons/extapps.scons index 4eff65cbf21..bff9a8c30f9 100644 --- a/site_scons/extapps.scons +++ b/site_scons/extapps.scons @@ -65,9 +65,8 @@ class FlipperExtAppBuildArtifacts: apps_to_build_as_faps = [ FlipperAppType.PLUGIN, FlipperAppType.EXTERNAL, + FlipperAppType.DEBUG, ] -if appenv["DEBUG_TOOLS"]: - apps_to_build_as_faps.append(FlipperAppType.DEBUG) known_extapps = [ app From 709fa633ffdd50ea4cf5111681027944f566b775 Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Wed, 25 Jan 2023 12:15:01 +0700 Subject: [PATCH 353/824] ELF-loader: wait for notification to complete on app exit (#2335) --- applications/plugins/snake_game/snake_game.c | 4 ++-- lib/flipper_application/flipper_application.c | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/applications/plugins/snake_game/snake_game.c b/applications/plugins/snake_game/snake_game.c index f9b4d30af92..2815e2f372e 100644 --- a/applications/plugins/snake_game/snake_game.c +++ b/applications/plugins/snake_game/snake_game.c @@ -394,8 +394,8 @@ int32_t snake_game_app(void* p) { release_mutex(&state_mutex, snake_state); } - // Wait for all notifications to be played and return backlight to normal state - notification_message_block(notification, &sequence_display_backlight_enforce_auto); + // Return backlight to normal state + notification_message(notification, &sequence_display_backlight_enforce_auto); furi_timer_free(timer); view_port_enabled_set(view_port, false); diff --git a/lib/flipper_application/flipper_application.c b/lib/flipper_application/flipper_application.c index 618a3623113..8b049a7d4f4 100644 --- a/lib/flipper_application/flipper_application.c +++ b/lib/flipper_application/flipper_application.c @@ -1,5 +1,6 @@ #include "flipper_application.h" #include "elf/elf_file.h" +#include #define TAG "fapp" @@ -95,6 +96,15 @@ static int32_t flipper_application_thread(void* context) { elf_file_pre_run(last_loaded_app->elf); int32_t result = elf_file_run(last_loaded_app->elf, context); elf_file_post_run(last_loaded_app->elf); + + // wait until all notifications from RAM are completed + NotificationApp* notifications = furi_record_open(RECORD_NOTIFICATION); + const NotificationSequence sequence_empty = { + NULL, + }; + notification_message_block(notifications, &sequence_empty); + furi_record_close(RECORD_NOTIFICATION); + return result; } From 5134f44c09d39344a8747655c0d59864bb574b96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Ha=C5=82adyn=20=28krzys=5Fh=29?= Date: Thu, 26 Jan 2023 09:28:36 +0100 Subject: [PATCH 354/824] nfc: Fix crash when using debug PCAP trace (#2338) --- lib/nfc/helpers/reader_analyzer.c | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/nfc/helpers/reader_analyzer.c b/lib/nfc/helpers/reader_analyzer.c index af4869ca9f6..9bf37a60d29 100644 --- a/lib/nfc/helpers/reader_analyzer.c +++ b/lib/nfc/helpers/reader_analyzer.c @@ -159,6 +159,7 @@ void reader_analyzer_stop(ReaderAnalyzer* instance) { } if(instance->pcap) { nfc_debug_pcap_free(instance->pcap); + instance->pcap = NULL; } } From 8fc834090d289ad75de8261ad69ec310a39b5834 Mon Sep 17 00:00:00 2001 From: Krzysztof Zdulski Date: Fri, 27 Jan 2023 06:00:25 +0100 Subject: [PATCH 355/824] nfc: Fix sector reads when one block is unreadable for MIFARE Classic (#2296) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix sector reads when one block is unreadable * Auth on the correct block instead of first * Fix in sector reader as well * Apply patch by @gornekich Co-authored-by: gornekich Co-authored-by: あく --- lib/nfc/protocols/mifare_classic.c | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/lib/nfc/protocols/mifare_classic.c b/lib/nfc/protocols/mifare_classic.c index c91e9c6056e..d1aadf06f62 100644 --- a/lib/nfc/protocols/mifare_classic.c +++ b/lib/nfc/protocols/mifare_classic.c @@ -595,6 +595,14 @@ void mf_classic_read_sector(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data, u if(mf_classic_read_block(tx_rx, &crypto, i, &block_tmp)) { mf_classic_set_block_read(data, i, &block_tmp); blocks_read++; + } else if(i > start_block) { + // Try to re-auth to read block in case prevous block was protected from read + furi_hal_nfc_sleep(); + if(!mf_classic_auth(tx_rx, i, key, MfClassicKeyA, &crypto, false, 0)) break; + if(mf_classic_read_block(tx_rx, &crypto, i, &block_tmp)) { + mf_classic_set_block_read(data, i, &block_tmp); + blocks_read++; + } } } else { blocks_read++; @@ -607,13 +615,20 @@ void mf_classic_read_sector(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data, u if(!key_b_found) break; FURI_LOG_D(TAG, "Try to read blocks with key B"); key = nfc_util_bytes2num(sec_tr->key_b, sizeof(sec_tr->key_b)); - furi_hal_nfc_sleep(); if(!mf_classic_auth(tx_rx, start_block, key, MfClassicKeyB, &crypto, false, 0)) break; for(size_t i = start_block; i < start_block + total_blocks; i++) { if(!mf_classic_is_block_read(data, i)) { if(mf_classic_read_block(tx_rx, &crypto, i, &block_tmp)) { mf_classic_set_block_read(data, i, &block_tmp); blocks_read++; + } else if(i > start_block) { + // Try to re-auth to read block in case prevous block was protected from read + furi_hal_nfc_sleep(); + if(!mf_classic_auth(tx_rx, i, key, MfClassicKeyB, &crypto, false, 0)) break; + if(mf_classic_read_block(tx_rx, &crypto, i, &block_tmp)) { + mf_classic_set_block_read(data, i, &block_tmp); + blocks_read++; + } } } else { blocks_read++; @@ -665,6 +680,11 @@ static bool mf_classic_read_sector_with_reader( // Read blocks for(uint8_t i = 0; i < sector->total_blocks; i++) { + if(mf_classic_read_block(tx_rx, crypto, first_block + i, §or->block[i])) continue; + if(i == 0) continue; + // Try to auth to read next block in case previous is locked + furi_hal_nfc_sleep(); + if(!mf_classic_auth(tx_rx, first_block + i, key, key_type, crypto, false, 0)) continue; mf_classic_read_block(tx_rx, crypto, first_block + i, §or->block[i]); } // Save sector keys in last block From 4dc4d34d04c4d998ba5e05a2bca8a6d17aeefd01 Mon Sep 17 00:00:00 2001 From: Emily Trau Date: Fri, 27 Jan 2023 16:10:08 +1100 Subject: [PATCH 356/824] emv: parse track1&2 equivalent data (#2332) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * emv: parse track1&2 equivalent data * emv: alternate expiry parser * nfc: log EMV track1&2 data to trace output Co-authored-by: gornekich Co-authored-by: あく --- lib/nfc/protocols/emv.c | 35 +++++++++++++++++++++++++++++------ lib/nfc/protocols/emv.h | 3 ++- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/lib/nfc/protocols/emv.c b/lib/nfc/protocols/emv.c index e00d09e70ab..4c4ac856b1c 100644 --- a/lib/nfc/protocols/emv.c +++ b/lib/nfc/protocols/emv.c @@ -142,21 +142,44 @@ static bool emv_decode_response(uint8_t* buff, uint16_t len, EmvApplication* app 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) + case EMV_TAG_TRACK_1_EQUIV: { + char track_1_equiv[80]; + memcpy(track_1_equiv, &buff[i], tlen); + track_1_equiv[tlen] = '\0'; + success = true; + FURI_LOG_T(TAG, "found EMV_TAG_TRACK_1_EQUIV %x : %s", tag, track_1_equiv); + break; + } + case EMV_TAG_TRACK_2_EQUIV: { + // 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; + app->exp_year = (buff[i + x + 1] << 4) | (buff[i + x + 2] >> 4); + app->exp_month = (buff[i + x + 2] << 4) | (buff[i + x + 3] >> 4); break; } } + + // Convert 4-bit to ASCII representation + char track_2_equiv[41]; + uint8_t track_2_equiv_len = 0; + for(int x = 0; x < tlen; x++) { + char top = (buff[i + x] >> 4) + '0'; + char bottom = (buff[i + x] & 0x0F) + '0'; + track_2_equiv[x * 2] = top; + track_2_equiv_len++; + if(top == '?') break; + track_2_equiv[x * 2 + 1] = bottom; + track_2_equiv_len++; + if(bottom == '?') break; + } + track_2_equiv[track_2_equiv_len] = '\0'; success = true; - FURI_LOG_T( - TAG, - "found EMV_TAG_CARD_NUM %x (len=%d)", - EMV_TAG_CARD_NUM, - app->card_number_len); + FURI_LOG_T(TAG, "found EMV_TAG_TRACK_2_EQUIV %x : %s", tag, track_2_equiv); break; + } case EMV_TAG_PAN: memcpy(app->card_number, &buff[i], tlen); app->card_number_len = tlen; diff --git a/lib/nfc/protocols/emv.h b/lib/nfc/protocols/emv.h index 0ccf7c3e049..c5b089fdffc 100644 --- a/lib/nfc/protocols/emv.h +++ b/lib/nfc/protocols/emv.h @@ -11,7 +11,8 @@ #define EMV_TAG_CARD_NAME 0x50 #define EMV_TAG_FCI 0xBF0C #define EMV_TAG_LOG_CTRL 0x9F4D -#define EMV_TAG_CARD_NUM 0x57 +#define EMV_TAG_TRACK_1_EQUIV 0x56 +#define EMV_TAG_TRACK_2_EQUIV 0x57 #define EMV_TAG_PAN 0x5A #define EMV_TAG_AFL 0x94 #define EMV_TAG_EXP_DATE 0x5F24 From eee5c3540060abb4c7e48dbdbbbc045f9cc1b1ed Mon Sep 17 00:00:00 2001 From: Giacomo Ferretti Date: Fri, 27 Jan 2023 06:51:47 +0100 Subject: [PATCH 357/824] NFC: add MIFARE MINI support (#2307) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * NFC: add MIFARE MINI support * Move new value to end of enum * nfc: added missing unit test Co-authored-by: gornekich Co-authored-by: あく --- applications/debug/unit_tests/nfc/nfc_test.c | 5 +++++ lib/nfc/helpers/nfc_generators.c | 22 ++++++++++++++++++++ lib/nfc/nfc_device.c | 18 ++++++++++++---- lib/nfc/nfc_types.c | 4 +++- lib/nfc/protocols/mifare_classic.c | 20 +++++++++++++----- lib/nfc/protocols/mifare_classic.h | 2 ++ 6 files changed, 61 insertions(+), 10 deletions(-) diff --git a/applications/debug/unit_tests/nfc/nfc_test.c b/applications/debug/unit_tests/nfc/nfc_test.c index e9e7b35f642..d613be2b947 100644 --- a/applications/debug/unit_tests/nfc/nfc_test.c +++ b/applications/debug/unit_tests/nfc/nfc_test.c @@ -466,6 +466,10 @@ static void mf_classic_generator_test(uint8_t uid_len, MfClassicType type) { nfc_device_free(nfc_keys); } +MU_TEST(mf_mini_file_test) { + mf_classic_generator_test(4, MfClassicTypeMini); +} + MU_TEST(mf_classic_1k_4b_file_test) { mf_classic_generator_test(4, MfClassicType1k); } @@ -486,6 +490,7 @@ MU_TEST_SUITE(nfc) { nfc_test_alloc(); MU_RUN_TEST(nfca_file_test); + MU_RUN_TEST(mf_mini_file_test); MU_RUN_TEST(mf_classic_1k_4b_file_test); MU_RUN_TEST(mf_classic_4k_4b_file_test); MU_RUN_TEST(mf_classic_1k_7b_file_test); diff --git a/lib/nfc/helpers/nfc_generators.c b/lib/nfc/helpers/nfc_generators.c index 769b9c7b6bb..590ff4d50ba 100644 --- a/lib/nfc/helpers/nfc_generators.c +++ b/lib/nfc/helpers/nfc_generators.c @@ -352,11 +352,27 @@ void nfc_generate_mf_classic(NfcDeviceData* data, uint8_t uid_len, MfClassicType } // Set SAK to 08 data->nfc_data.sak = 0x08; + } else if(type == MfClassicTypeMini) { + // Set every block to 0xFF + for(uint16_t i = 1; i < MF_MINI_TOTAL_SECTORS_NUM * 4; i += 1) { + if(mf_classic_is_sector_trailer(i)) { + nfc_generate_mf_classic_sector_trailer(mfc, i); + } else { + memset(&mfc->block[i].value, 0xFF, 16); + } + mf_classic_set_block_read(mfc, i, &mfc->block[i]); + } + // Set SAK to 09 + data->nfc_data.sak = 0x09; } mfc->type = type; } +static void nfc_generate_mf_mini(NfcDeviceData* data) { + nfc_generate_mf_classic(data, 4, MfClassicTypeMini); +} + static void nfc_generate_mf_classic_1k_4b_uid(NfcDeviceData* data) { nfc_generate_mf_classic(data, 4, MfClassicType1k); } @@ -438,6 +454,11 @@ static const NfcGenerator ntag_i2c_plus_2k_generator = { .generator_func = nfc_generate_ntag_i2c_plus_2k, }; +static const NfcGenerator mifare_mini_generator = { + .name = "Mifare Mini", + .generator_func = nfc_generate_mf_mini, +}; + static const NfcGenerator mifare_classic_1k_4b_uid_generator = { .name = "Mifare Classic 1k 4byte UID", .generator_func = nfc_generate_mf_classic_1k_4b_uid, @@ -472,6 +493,7 @@ const NfcGenerator* const nfc_generators[] = { &ntag_i2c_2k_generator, &ntag_i2c_plus_1k_generator, &ntag_i2c_plus_2k_generator, + &mifare_mini_generator, &mifare_classic_1k_4b_uid_generator, &mifare_classic_1k_7b_uid_generator, &mifare_classic_4k_4b_uid_generator, diff --git a/lib/nfc/nfc_device.c b/lib/nfc/nfc_device.c index d10eaa0e57e..5179130702d 100644 --- a/lib/nfc/nfc_device.c +++ b/lib/nfc/nfc_device.c @@ -745,7 +745,10 @@ static bool nfc_device_save_mifare_classic_data(FlipperFormat* file, NfcDevice* do { if(!flipper_format_write_comment_cstr(file, "Mifare Classic specific data")) break; - if(data->type == MfClassicType1k) { + if(data->type == MfClassicTypeMini) { + if(!flipper_format_write_string_cstr(file, "Mifare Classic type", "MINI")) break; + blocks = 20; + } else if(data->type == MfClassicType1k) { if(!flipper_format_write_string_cstr(file, "Mifare Classic type", "1K")) break; blocks = 64; } else if(data->type == MfClassicType4k) { @@ -843,7 +846,10 @@ static bool nfc_device_load_mifare_classic_data(FlipperFormat* file, NfcDevice* do { // Read Mifare Classic type if(!flipper_format_read_string(file, "Mifare Classic type", temp_str)) break; - if(!furi_string_cmp(temp_str, "1K")) { + if(!furi_string_cmp(temp_str, "MINI")) { + data->type = MfClassicTypeMini; + data_blocks = 20; + } else if(!furi_string_cmp(temp_str, "1K")) { data->type = MfClassicType1k; data_blocks = 64; } else if(!furi_string_cmp(temp_str, "4K")) { @@ -918,7 +924,9 @@ static bool nfc_device_save_mifare_classic_keys(NfcDevice* dev) { if(!flipper_format_file_open_always(file, furi_string_get_cstr(temp_str))) break; if(!flipper_format_write_header_cstr(file, nfc_keys_file_header, nfc_keys_file_version)) break; - if(data->type == MfClassicType1k) { + if(data->type == MfClassicTypeMini) { + if(!flipper_format_write_string_cstr(file, "Mifare Classic type", "MINI")) break; + } else if(data->type == MfClassicType1k) { if(!flipper_format_write_string_cstr(file, "Mifare Classic type", "1K")) break; } else if(data->type == MfClassicType4k) { if(!flipper_format_write_string_cstr(file, "Mifare Classic type", "4K")) break; @@ -968,7 +976,9 @@ bool nfc_device_load_key_cache(NfcDevice* dev) { if(furi_string_cmp_str(temp_str, nfc_keys_file_header)) break; if(version != nfc_keys_file_version) break; if(!flipper_format_read_string(file, "Mifare Classic type", temp_str)) break; - if(!furi_string_cmp(temp_str, "1K")) { + if(!furi_string_cmp(temp_str, "MINI")) { + data->type = MfClassicTypeMini; + } else if(!furi_string_cmp(temp_str, "1K")) { data->type = MfClassicType1k; } else if(!furi_string_cmp(temp_str, "4K")) { data->type = MfClassicType4k; diff --git a/lib/nfc/nfc_types.c b/lib/nfc/nfc_types.c index 42762876993..02ca85580db 100644 --- a/lib/nfc/nfc_types.c +++ b/lib/nfc/nfc_types.c @@ -55,7 +55,9 @@ const char* nfc_mf_ul_type(MfUltralightType type, bool full_name) { } const char* nfc_mf_classic_type(MfClassicType type) { - if(type == MfClassicType1k) { + if(type == MfClassicTypeMini) { + return "Mifare Mini 0.3K"; + } else if(type == MfClassicType1k) { return "Mifare Classic 1K"; } else if(type == MfClassicType4k) { return "Mifare Classic 4K"; diff --git a/lib/nfc/protocols/mifare_classic.c b/lib/nfc/protocols/mifare_classic.c index d1aadf06f62..2f340a5cec3 100644 --- a/lib/nfc/protocols/mifare_classic.c +++ b/lib/nfc/protocols/mifare_classic.c @@ -13,7 +13,9 @@ #define MF_CLASSIC_WRITE_BLOCK_CMD (0xA0) const char* mf_classic_get_type_str(MfClassicType type) { - if(type == MfClassicType1k) { + if(type == MfClassicTypeMini) { + return "MIFARE Mini 0.3K"; + } else if(type == MfClassicType1k) { return "MIFARE Classic 1K"; } else if(type == MfClassicType4k) { return "MIFARE Classic 4K"; @@ -73,7 +75,9 @@ MfClassicSectorTrailer* } uint8_t mf_classic_get_total_sectors_num(MfClassicType type) { - if(type == MfClassicType1k) { + if(type == MfClassicTypeMini) { + return MF_MINI_TOTAL_SECTORS_NUM; + } else if(type == MfClassicType1k) { return MF_CLASSIC_1K_TOTAL_SECTORS_NUM; } else if(type == MfClassicType4k) { return MF_CLASSIC_4K_TOTAL_SECTORS_NUM; @@ -83,7 +87,9 @@ uint8_t mf_classic_get_total_sectors_num(MfClassicType type) { } uint16_t mf_classic_get_total_block_num(MfClassicType type) { - if(type == MfClassicType1k) { + if(type == MfClassicTypeMini) { + return 20; + } else if(type == MfClassicType1k) { return 64; } else if(type == MfClassicType4k) { return 256; @@ -363,8 +369,12 @@ bool mf_classic_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK) { MfClassicType mf_classic_get_classic_type(int8_t ATQA0, uint8_t ATQA1, uint8_t SAK) { UNUSED(ATQA1); - if((ATQA0 == 0x44 || ATQA0 == 0x04) && (SAK == 0x08 || SAK == 0x88 || SAK == 0x09)) { - return MfClassicType1k; + if((ATQA0 == 0x44 || ATQA0 == 0x04)) { + if((SAK == 0x08 || SAK == 0x88)) { + return MfClassicType1k; + } else if(SAK == 0x09) { + return MfClassicTypeMini; + } } else if((ATQA0 == 0x01) && (ATQA1 == 0x0F) && (SAK == 0x01)) { //skylanders support return MfClassicType1k; diff --git a/lib/nfc/protocols/mifare_classic.h b/lib/nfc/protocols/mifare_classic.h index 74beceb625c..0346da0f7fa 100644 --- a/lib/nfc/protocols/mifare_classic.h +++ b/lib/nfc/protocols/mifare_classic.h @@ -6,6 +6,7 @@ #define MF_CLASSIC_BLOCK_SIZE (16) #define MF_CLASSIC_TOTAL_BLOCKS_MAX (256) +#define MF_MINI_TOTAL_SECTORS_NUM (5) #define MF_CLASSIC_1K_TOTAL_SECTORS_NUM (16) #define MF_CLASSIC_4K_TOTAL_SECTORS_NUM (40) @@ -20,6 +21,7 @@ typedef enum { MfClassicType1k, MfClassicType4k, + MfClassicTypeMini, } MfClassicType; typedef enum { From 126a9efd091ea949cb9a26a01b0ee7f8c22a4a68 Mon Sep 17 00:00:00 2001 From: Giacomo Ferretti Date: Fri, 27 Jan 2023 09:21:52 +0100 Subject: [PATCH 358/824] NFC: change from int8_t to uint8_t (#2302) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: gornekich Co-authored-by: あく --- firmware/targets/f7/api_symbols.csv | 2 +- lib/nfc/protocols/mifare_classic.c | 2 +- lib/nfc/protocols/mifare_classic.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 3d7bd7cd26a..0e4af3c1da6 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1880,7 +1880,7 @@ Function,-,mf_classic_dict_is_key_present,_Bool,"MfClassicDict*, uint8_t*" Function,-,mf_classic_dict_is_key_present_str,_Bool,"MfClassicDict*, FuriString*" Function,-,mf_classic_dict_rewind,_Bool,MfClassicDict* Function,-,mf_classic_emulator,_Bool,"MfClassicEmulator*, FuriHalNfcTxRxContext*" -Function,-,mf_classic_get_classic_type,MfClassicType,"int8_t, uint8_t, uint8_t" +Function,-,mf_classic_get_classic_type,MfClassicType,"uint8_t, uint8_t, uint8_t" Function,-,mf_classic_get_read_sectors_and_keys,void,"MfClassicData*, uint8_t*, uint8_t*" Function,-,mf_classic_get_sector_by_block,uint8_t,uint8_t Function,-,mf_classic_get_sector_trailer_block_num_by_sector,uint8_t,uint8_t diff --git a/lib/nfc/protocols/mifare_classic.c b/lib/nfc/protocols/mifare_classic.c index 2f340a5cec3..5887ab4c185 100644 --- a/lib/nfc/protocols/mifare_classic.c +++ b/lib/nfc/protocols/mifare_classic.c @@ -367,7 +367,7 @@ bool mf_classic_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK) { } } -MfClassicType mf_classic_get_classic_type(int8_t ATQA0, uint8_t ATQA1, uint8_t SAK) { +MfClassicType mf_classic_get_classic_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK) { UNUSED(ATQA1); if((ATQA0 == 0x44 || ATQA0 == 0x04)) { if((SAK == 0x08 || SAK == 0x88)) { diff --git a/lib/nfc/protocols/mifare_classic.h b/lib/nfc/protocols/mifare_classic.h index 0346da0f7fa..321ab5f0360 100644 --- a/lib/nfc/protocols/mifare_classic.h +++ b/lib/nfc/protocols/mifare_classic.h @@ -96,7 +96,7 @@ const char* mf_classic_get_type_str(MfClassicType type); bool mf_classic_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK); -MfClassicType mf_classic_get_classic_type(int8_t ATQA0, uint8_t ATQA1, uint8_t SAK); +MfClassicType mf_classic_get_classic_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK); uint8_t mf_classic_get_total_sectors_num(MfClassicType type); From 24a23e5dc7b1719874f075abbf428bd5dd59e5e6 Mon Sep 17 00:00:00 2001 From: Emily Trau Date: Fri, 27 Jan 2023 20:41:55 +1100 Subject: [PATCH 359/824] debug apps: made runnable as .faps; sdk: resolved additional APIs in use by faps (#2333) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * sdk: resolve additional APIs in use by faps * debug tools: fixed battery_test, bt_debug, display_test, rpc_debug Co-authored-by: hedger Co-authored-by: hedger Co-authored-by: あく --- .../debug/battery_test_app/application.fam | 1 + .../battery_test_app/views/battery_info.c | 148 ++++++++++++++++++ .../battery_test_app/views/battery_info.h | 23 +++ .../debug/bt_debug_app/bt_debug_app.c | 7 +- .../debug/bt_debug_app/bt_debug_app.h | 5 +- .../debug/display_test/application.fam | 1 + .../debug/display_test/display_test.c | 1 - .../rpc_debug_app_scene_input_error_code.c | 6 +- applications/services/bt/bt_settings.h | 8 + firmware/targets/f7/api_symbols.csv | 8 +- lib/toolbox/SConscript | 1 + 11 files changed, 198 insertions(+), 11 deletions(-) create mode 100644 applications/debug/battery_test_app/views/battery_info.c create mode 100644 applications/debug/battery_test_app/views/battery_info.h diff --git a/applications/debug/battery_test_app/application.fam b/applications/debug/battery_test_app/application.fam index b388445cc39..f97d1027914 100644 --- a/applications/debug/battery_test_app/application.fam +++ b/applications/debug/battery_test_app/application.fam @@ -11,4 +11,5 @@ App( stack_size=1 * 1024, order=130, fap_category="Debug", + fap_libs=["assets"], ) diff --git a/applications/debug/battery_test_app/views/battery_info.c b/applications/debug/battery_test_app/views/battery_info.c new file mode 100644 index 00000000000..5353a2e2a67 --- /dev/null +++ b/applications/debug/battery_test_app/views/battery_info.c @@ -0,0 +1,148 @@ +#include "battery_info.h" +#include +#include +#include + +#define LOW_CHARGE_THRESHOLD 10 +#define HIGH_DRAIN_CURRENT_THRESHOLD 100 + +struct BatteryInfo { + View* view; +}; + +static void draw_stat(Canvas* canvas, int x, int y, const Icon* icon, char* val) { + canvas_draw_frame(canvas, x - 7, y + 7, 30, 13); + canvas_draw_icon(canvas, x, y, icon); + canvas_set_color(canvas, ColorWhite); + canvas_draw_box(canvas, x - 4, y + 16, 24, 6); + canvas_set_color(canvas, ColorBlack); + canvas_draw_str_aligned(canvas, x + 8, y + 22, AlignCenter, AlignBottom, val); +}; + +static void draw_battery(Canvas* canvas, BatteryInfoModel* data, int x, int y) { + char emote[20] = {}; + char header[20] = {}; + char value[20] = {}; + + int32_t drain_current = data->gauge_current * (-1000); + uint32_t charge_current = data->gauge_current * 1000; + + // Draw battery + canvas_draw_icon(canvas, x, y, &I_BatteryBody_52x28); + if(charge_current > 0) { + canvas_draw_icon(canvas, x + 16, y + 7, &I_FaceCharging_29x14); + } else if(drain_current > HIGH_DRAIN_CURRENT_THRESHOLD) { + canvas_draw_icon(canvas, x + 16, y + 7, &I_FaceConfused_29x14); + } else if(data->charge < LOW_CHARGE_THRESHOLD) { + canvas_draw_icon(canvas, x + 16, y + 7, &I_FaceNopower_29x14); + } else { + canvas_draw_icon(canvas, x + 16, y + 7, &I_FaceNormal_29x14); + } + + // Draw bubble + elements_bubble(canvas, 53, 0, 71, 39); + + // Set text + if(charge_current > 0) { + snprintf(emote, sizeof(emote), "%s", "Yummy!"); + snprintf(header, sizeof(header), "%s", "Charging at"); + snprintf( + value, + sizeof(value), + "%lu.%luV %lumA", + (uint32_t)(data->vbus_voltage), + (uint32_t)(data->vbus_voltage * 10) % 10, + charge_current); + } else if(drain_current > 0) { + snprintf( + emote, + sizeof(emote), + "%s", + drain_current > HIGH_DRAIN_CURRENT_THRESHOLD ? "Oh no!" : "Om-nom-nom!"); + snprintf(header, sizeof(header), "%s", "Consumption is"); + snprintf( + value, + sizeof(value), + "%ld %s", + drain_current, + drain_current > HIGH_DRAIN_CURRENT_THRESHOLD ? "mA!" : "mA"); + } else if(drain_current != 0) { + snprintf(header, 20, "..."); + } else if(data->charging_voltage < 4.2) { + // Non-default battery charging limit, mention it + snprintf(emote, sizeof(emote), "Charged!"); + snprintf(header, sizeof(header), "Limited to"); + snprintf( + value, + sizeof(value), + "%lu.%luV", + (uint32_t)(data->charging_voltage), + (uint32_t)(data->charging_voltage * 10) % 10); + } else { + snprintf(header, sizeof(header), "Charged!"); + } + + canvas_draw_str_aligned(canvas, 92, y + 3, AlignCenter, AlignCenter, emote); + canvas_draw_str_aligned(canvas, 92, y + 15, AlignCenter, AlignCenter, header); + canvas_draw_str_aligned(canvas, 92, y + 27, AlignCenter, AlignCenter, value); +}; + +static void battery_info_draw_callback(Canvas* canvas, void* context) { + furi_assert(context); + BatteryInfoModel* model = context; + + canvas_clear(canvas); + canvas_set_color(canvas, ColorBlack); + draw_battery(canvas, model, 0, 5); + + char batt_level[10]; + char temperature[10]; + char voltage[10]; + char health[10]; + + snprintf(batt_level, sizeof(batt_level), "%lu%%", (uint32_t)model->charge); + snprintf(temperature, sizeof(temperature), "%lu C", (uint32_t)model->gauge_temperature); + snprintf( + voltage, + sizeof(voltage), + "%lu.%01lu V", + (uint32_t)model->gauge_voltage, + (uint32_t)(model->gauge_voltage * 10) % 10UL); + snprintf(health, sizeof(health), "%d%%", model->health); + + draw_stat(canvas, 8, 42, &I_Battery_16x16, batt_level); + draw_stat(canvas, 40, 42, &I_Temperature_16x16, temperature); + draw_stat(canvas, 72, 42, &I_Voltage_16x16, voltage); + draw_stat(canvas, 104, 42, &I_Health_16x16, health); +} + +BatteryInfo* battery_info_alloc() { + BatteryInfo* battery_info = malloc(sizeof(BatteryInfo)); + battery_info->view = view_alloc(); + view_set_context(battery_info->view, battery_info); + view_allocate_model(battery_info->view, ViewModelTypeLocking, sizeof(BatteryInfoModel)); + view_set_draw_callback(battery_info->view, battery_info_draw_callback); + + return battery_info; +} + +void battery_info_free(BatteryInfo* battery_info) { + furi_assert(battery_info); + view_free(battery_info->view); + free(battery_info); +} + +View* battery_info_get_view(BatteryInfo* battery_info) { + furi_assert(battery_info); + return battery_info->view; +} + +void battery_info_set_data(BatteryInfo* battery_info, BatteryInfoModel* data) { + furi_assert(battery_info); + furi_assert(data); + with_view_model( + battery_info->view, + BatteryInfoModel * model, + { memcpy(model, data, sizeof(BatteryInfoModel)); }, + true); +} diff --git a/applications/debug/battery_test_app/views/battery_info.h b/applications/debug/battery_test_app/views/battery_info.h new file mode 100644 index 00000000000..7bfacf69e27 --- /dev/null +++ b/applications/debug/battery_test_app/views/battery_info.h @@ -0,0 +1,23 @@ +#pragma once + +#include + +typedef struct BatteryInfo BatteryInfo; + +typedef struct { + float vbus_voltage; + float gauge_voltage; + float gauge_current; + float gauge_temperature; + float charging_voltage; + uint8_t charge; + uint8_t health; +} BatteryInfoModel; + +BatteryInfo* battery_info_alloc(); + +void battery_info_free(BatteryInfo* battery_info); + +View* battery_info_get_view(BatteryInfo* battery_info); + +void battery_info_set_data(BatteryInfo* battery_info, BatteryInfoModel* data); diff --git a/applications/debug/bt_debug_app/bt_debug_app.c b/applications/debug/bt_debug_app/bt_debug_app.c index 405051a4aca..bf13f6570a0 100644 --- a/applications/debug/bt_debug_app/bt_debug_app.c +++ b/applications/debug/bt_debug_app/bt_debug_app.c @@ -31,9 +31,6 @@ uint32_t bt_debug_start_view(void* context) { BtDebugApp* bt_debug_app_alloc() { BtDebugApp* app = malloc(sizeof(BtDebugApp)); - // Load settings - bt_settings_load(&app->settings); - // Gui app->gui = furi_record_open(RECORD_GUI); @@ -105,13 +102,15 @@ int32_t bt_debug_app(void* p) { } BtDebugApp* app = bt_debug_app_alloc(); + // Was bt active? + const bool was_active = furi_hal_bt_is_active(); // Stop advertising furi_hal_bt_stop_advertising(); view_dispatcher_run(app->view_dispatcher); // Restart advertising - if(app->settings.enabled) { + if(was_active) { furi_hal_bt_start_advertising(); } bt_debug_app_free(app); diff --git a/applications/debug/bt_debug_app/bt_debug_app.h b/applications/debug/bt_debug_app/bt_debug_app.h index cd59e4d0034..0ad94d7ddc1 100644 --- a/applications/debug/bt_debug_app/bt_debug_app.h +++ b/applications/debug/bt_debug_app/bt_debug_app.h @@ -4,15 +4,14 @@ #include #include #include +#include + #include -#include #include "views/bt_carrier_test.h" #include "views/bt_packet_test.h" -#include typedef struct { - BtSettings settings; Gui* gui; ViewDispatcher* view_dispatcher; Submenu* submenu; diff --git a/applications/debug/display_test/application.fam b/applications/debug/display_test/application.fam index 4b40322fbd1..e8a00d2ae11 100644 --- a/applications/debug/display_test/application.fam +++ b/applications/debug/display_test/application.fam @@ -5,6 +5,7 @@ App( entry_point="display_test_app", cdefines=["APP_DISPLAY_TEST"], requires=["gui"], + fap_libs=["misc"], stack_size=1 * 1024, order=120, fap_category="Debug", diff --git a/applications/debug/display_test/display_test.c b/applications/debug/display_test/display_test.c index 5b46d2b4110..8065a23a1f9 100644 --- a/applications/debug/display_test/display_test.c +++ b/applications/debug/display_test/display_test.c @@ -91,7 +91,6 @@ static void display_test_reload_config(DisplayTest* instance) { instance->config_contrast, instance->config_regulation_ratio, instance->config_bias); - gui_update(instance->gui); } static void display_config_set_bias(VariableItem* item) { diff --git a/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_input_error_code.c b/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_input_error_code.c index eae12e6eeca..367ca7a4ff1 100644 --- a/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_input_error_code.c +++ b/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_input_error_code.c @@ -44,7 +44,11 @@ bool rpc_debug_app_scene_input_error_code_on_event(void* context, SceneManagerEv if(event.type == SceneManagerEventTypeCustom) { if(event.event == RpcDebugAppCustomEventInputErrorCode) { - rpc_system_app_set_error_code(app->rpc, (uint32_t)atol(app->text_store)); + char* end; + int error_code = strtol(app->text_store, &end, 10); + if(!*end) { + rpc_system_app_set_error_code(app->rpc, error_code); + } scene_manager_previous_scene(app->scene_manager); consumed = true; } diff --git a/applications/services/bt/bt_settings.h b/applications/services/bt/bt_settings.h index 260d9c0e0f4..9ed8be89c4a 100644 --- a/applications/services/bt/bt_settings.h +++ b/applications/services/bt/bt_settings.h @@ -5,6 +5,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + typedef struct { bool enabled; } BtSettings; @@ -12,3 +16,7 @@ typedef struct { bool bt_settings_load(BtSettings* bt_settings); bool bt_settings_save(BtSettings* bt_settings); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 0e4af3c1da6..0e027a6a2ca 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,11.7,, +Version,+,11.8,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -188,6 +188,7 @@ Header,+,lib/toolbox/stream/file_stream.h,, Header,+,lib/toolbox/stream/stream.h,, Header,+,lib/toolbox/stream/string_stream.h,, Header,+,lib/toolbox/tar/tar_archive.h,, +Header,+,lib/toolbox/value_index.h,, Header,+,lib/toolbox/version.h,, Function,-,LL_ADC_CommonDeInit,ErrorStatus,ADC_Common_TypeDef* Function,-,LL_ADC_CommonInit,ErrorStatus,"ADC_Common_TypeDef*, LL_ADC_CommonInitTypeDef*" @@ -2066,7 +2067,7 @@ Function,-,posix_memalign,int,"void**, size_t, size_t" Function,-,pow,double,"double, double" Function,-,pow10,double,double Function,-,pow10f,float,float -Function,-,power_enable_low_battery_level_notification,void,"Power*, _Bool" +Function,+,power_enable_low_battery_level_notification,void,"Power*, _Bool" Function,+,power_get_info,void,"Power*, PowerInfo*" Function,+,power_get_pubsub,FuriPubSub*,Power* Function,+,power_is_battery_healthy,_Bool,Power* @@ -2804,6 +2805,9 @@ Function,-,vTimerSetTimerNumber,void,"TimerHandle_t, UBaseType_t" Function,+,validator_is_file_alloc_init,ValidatorIsFile*,"const char*, const char*, const char*" Function,+,validator_is_file_callback,_Bool,"const char*, FuriString*, void*" Function,+,validator_is_file_free,void,ValidatorIsFile* +Function,+,value_index_bool,uint8_t,"const _Bool, const _Bool[], uint8_t" +Function,+,value_index_float,uint8_t,"const float, const float[], uint8_t" +Function,+,value_index_uint32,uint8_t,"const uint32_t, const uint32_t[], uint8_t" Function,+,variable_item_get_context,void*,VariableItem* Function,+,variable_item_get_current_value_index,uint8_t,VariableItem* Function,+,variable_item_list_add,VariableItem*,"VariableItemList*, const char*, uint8_t, VariableItemChangeCallback, void*" diff --git a/lib/toolbox/SConscript b/lib/toolbox/SConscript index 724d25afacb..8ce45d25f7d 100644 --- a/lib/toolbox/SConscript +++ b/lib/toolbox/SConscript @@ -20,6 +20,7 @@ env.Append( File("saved_struct.h"), File("version.h"), File("float_tools.h"), + File("value_index.h"), File("tar/tar_archive.h"), File("stream/stream.h"), File("stream/file_stream.h"), From d93ed003fec62ec8b9dc8a6b5ffbe93a25a0ef40 Mon Sep 17 00:00:00 2001 From: Krzysztof Zdulski Date: Sun, 29 Jan 2023 06:53:35 +0100 Subject: [PATCH 360/824] Change camelCase to PascalCase in code style (#2329) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- CODING_STYLE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CODING_STYLE.md b/CODING_STYLE.md index 6c7d6d79230..c62009eff5f 100644 --- a/CODING_STYLE.md +++ b/CODING_STYLE.md @@ -52,7 +52,7 @@ Almost everything in flipper firmware is built around this concept. ## Naming -### Type names are CamelCase +### Type names are PascalCase Examples: From f5fe0ff694fd5c51472d3430565a156a66a05c15 Mon Sep 17 00:00:00 2001 From: Petr Portnov | PROgrm_JARvis Date: Sun, 29 Jan 2023 13:12:24 +0300 Subject: [PATCH 361/824] Furi: make `furi_is_irq_context` public (#2276) * Furi: make `furi_is_irq_context` public * Furi: proper name and documentation for furi_kernel_is_irq_or_masked. * Target: bump symbol table version * Furi: proper doxygen context for warnings Co-authored-by: Aleksandr Kutuzov --- firmware/targets/f7/api_symbols.csv | 1 + furi/core/common_defines.h | 24 ------------------- furi/core/kernel.c | 36 ++++++++++++++++++++++++----- furi/core/kernel.h | 29 ++++++++++++++++++++++- furi/core/message_queue.c | 16 ++++++------- furi/core/timer.c | 11 ++++----- furi/furi.c | 4 ++-- 7 files changed, 74 insertions(+), 47 deletions(-) diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 0e027a6a2ca..a75e88bad8a 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1389,6 +1389,7 @@ Function,+,furi_hal_version_uid_size,size_t, Function,-,furi_hal_vibro_init,void, Function,+,furi_hal_vibro_on,void,_Bool Function,-,furi_init,void, +Function,+,furi_kernel_is_irq_or_masked,_Bool, Function,+,furi_kernel_get_tick_frequency,uint32_t, Function,+,furi_kernel_lock,int32_t, Function,+,furi_kernel_restore_lock,int32_t,int32_t diff --git a/furi/core/common_defines.h b/furi/core/common_defines.h index c7acf95b438..1ec847d4594 100644 --- a/furi/core/common_defines.h +++ b/furi/core/common_defines.h @@ -52,30 +52,6 @@ extern "C" { } #endif -static inline bool furi_is_irq_context() { - bool irq = false; - BaseType_t state; - - if(FURI_IS_IRQ_MODE()) { - /* Called from interrupt context */ - irq = true; - } else { - /* Get FreeRTOS scheduler state */ - state = xTaskGetSchedulerState(); - - if(state != taskSCHEDULER_NOT_STARTED) { - /* Scheduler was started */ - if(FURI_IS_IRQ_MASKED()) { - /* Interrupts are masked */ - irq = true; - } - } - } - - /* Return context, 0: thread context, 1: IRQ context */ - return (irq); -} - #ifdef __cplusplus } #endif diff --git a/furi/core/kernel.c b/furi/core/kernel.c index 73d2012b4d6..7928ad11c38 100644 --- a/furi/core/kernel.c +++ b/furi/core/kernel.c @@ -7,8 +7,32 @@ #include CMSIS_device_header +bool furi_kernel_is_irq_or_masked() { + bool irq = false; + BaseType_t state; + + if(FURI_IS_IRQ_MODE()) { + /* Called from interrupt context */ + irq = true; + } else { + /* Get FreeRTOS scheduler state */ + state = xTaskGetSchedulerState(); + + if(state != taskSCHEDULER_NOT_STARTED) { + /* Scheduler was started */ + if(FURI_IS_IRQ_MASKED()) { + /* Interrupts are masked */ + irq = true; + } + } + } + + /* Return context, 0: thread context, 1: IRQ context */ + return (irq); +} + int32_t furi_kernel_lock() { - furi_assert(!furi_is_irq_context()); + furi_assert(!furi_kernel_is_irq_or_masked()); int32_t lock; @@ -33,7 +57,7 @@ int32_t furi_kernel_lock() { } int32_t furi_kernel_unlock() { - furi_assert(!furi_is_irq_context()); + furi_assert(!furi_kernel_is_irq_or_masked()); int32_t lock; @@ -63,7 +87,7 @@ int32_t furi_kernel_unlock() { } int32_t furi_kernel_restore_lock(int32_t lock) { - furi_assert(!furi_is_irq_context()); + furi_assert(!furi_kernel_is_irq_or_masked()); switch(xTaskGetSchedulerState()) { case taskSCHEDULER_SUSPENDED: @@ -99,7 +123,7 @@ uint32_t furi_kernel_get_tick_frequency() { } void furi_delay_tick(uint32_t ticks) { - furi_assert(!furi_is_irq_context()); + furi_assert(!furi_kernel_is_irq_or_masked()); if(ticks == 0U) { taskYIELD(); } else { @@ -108,7 +132,7 @@ void furi_delay_tick(uint32_t ticks) { } FuriStatus furi_delay_until_tick(uint32_t tick) { - furi_assert(!furi_is_irq_context()); + furi_assert(!furi_kernel_is_irq_or_masked()); TickType_t tcnt, delay; FuriStatus stat; @@ -137,7 +161,7 @@ FuriStatus furi_delay_until_tick(uint32_t tick) { uint32_t furi_get_tick() { TickType_t ticks; - if(furi_is_irq_context() != 0U) { + if(furi_kernel_is_irq_or_masked() != 0U) { ticks = xTaskGetTickCountFromISR(); } else { ticks = xTaskGetTickCount(); diff --git a/furi/core/kernel.h b/furi/core/kernel.h index f30f109bb84..371f76c1f78 100644 --- a/furi/core/kernel.h +++ b/furi/core/kernel.h @@ -10,19 +10,42 @@ extern "C" { #endif +/** Check if CPU is in IRQ or kernel running and IRQ is masked + * + * Originally this primitive was born as a workaround for FreeRTOS kernel primitives shenanigans with PRIMASK. + * + * Meaningful use cases are: + * + * - When kernel is started and you want to ensure that you are not in IRQ or IRQ is not masked(like in critical section) + * - When kernel is not started and you want to make sure that you are not in IRQ mode, ignoring PRIMASK. + * + * As you can see there will be edge case when kernel is not started and PRIMASK is not 0 that may cause some funky behavior. + * Most likely it will happen after kernel primitives being used, but control not yet passed to kernel. + * It's up to you to figure out if it is safe for your code or not. + * + * @return true if CPU is in IRQ or kernel running and IRQ is masked + */ +bool furi_kernel_is_irq_or_masked(); + /** Lock kernel, pause process scheduling + * + * @warning This should never be called in interrupt request context. * * @return previous lock state(0 - unlocked, 1 - locked) */ int32_t furi_kernel_lock(); /** Unlock kernel, resume process scheduling + * + * @warning This should never be called in interrupt request context. * * @return previous lock state(0 - unlocked, 1 - locked) */ int32_t furi_kernel_unlock(); /** Restore kernel lock state + * + * @warning This should never be called in interrupt request context. * * @param[in] lock The lock state * @@ -37,7 +60,9 @@ int32_t furi_kernel_restore_lock(int32_t lock); uint32_t furi_kernel_get_tick_frequency(); /** Delay execution - * + * + * @warning This should never be called in interrupt request context. + * * Also keep in mind delay is aliased to scheduler timer intervals. * * @param[in] ticks The ticks count to pause @@ -45,6 +70,8 @@ uint32_t furi_kernel_get_tick_frequency(); void furi_delay_tick(uint32_t ticks); /** Delay until tick + * + * @warning This should never be called in interrupt request context. * * @param[in] ticks The tick until which kerel should delay task execution * diff --git a/furi/core/message_queue.c b/furi/core/message_queue.c index 9a41f877527..ddf56f00640 100644 --- a/furi/core/message_queue.c +++ b/furi/core/message_queue.c @@ -1,11 +1,11 @@ +#include "kernel.h" #include "message_queue.h" -#include "core/common_defines.h" #include #include #include "check.h" FuriMessageQueue* furi_message_queue_alloc(uint32_t msg_count, uint32_t msg_size) { - furi_assert((furi_is_irq_context() == 0U) && (msg_count > 0U) && (msg_size > 0U)); + furi_assert((furi_kernel_is_irq_or_masked() == 0U) && (msg_count > 0U) && (msg_size > 0U)); QueueHandle_t handle = xQueueCreate(msg_count, msg_size); furi_check(handle); @@ -14,7 +14,7 @@ FuriMessageQueue* furi_message_queue_alloc(uint32_t msg_count, uint32_t msg_size } void furi_message_queue_free(FuriMessageQueue* instance) { - furi_assert(furi_is_irq_context() == 0U); + furi_assert(furi_kernel_is_irq_or_masked() == 0U); furi_assert(instance); vQueueDelete((QueueHandle_t)instance); @@ -28,7 +28,7 @@ FuriStatus stat = FuriStatusOk; - if(furi_is_irq_context() != 0U) { + if(furi_kernel_is_irq_or_masked() != 0U) { if((hQueue == NULL) || (msg_ptr == NULL) || (timeout != 0U)) { stat = FuriStatusErrorParameter; } else { @@ -65,7 +65,7 @@ FuriStatus furi_message_queue_get(FuriMessageQueue* instance, void* msg_ptr, uin stat = FuriStatusOk; - if(furi_is_irq_context() != 0U) { + if(furi_kernel_is_irq_or_masked() != 0U) { if((hQueue == NULL) || (msg_ptr == NULL) || (timeout != 0U)) { stat = FuriStatusErrorParameter; } else { @@ -131,7 +131,7 @@ uint32_t furi_message_queue_get_count(FuriMessageQueue* instance) { if(hQueue == NULL) { count = 0U; - } else if(furi_is_irq_context() != 0U) { + } else if(furi_kernel_is_irq_or_masked() != 0U) { count = uxQueueMessagesWaitingFromISR(hQueue); } else { count = uxQueueMessagesWaiting(hQueue); @@ -148,7 +148,7 @@ uint32_t furi_message_queue_get_space(FuriMessageQueue* instance) { if(mq == NULL) { space = 0U; - } else if(furi_is_irq_context() != 0U) { + } else if(furi_kernel_is_irq_or_masked() != 0U) { isrm = taskENTER_CRITICAL_FROM_ISR(); /* space = pxQueue->uxLength - pxQueue->uxMessagesWaiting; */ @@ -167,7 +167,7 @@ FuriStatus furi_message_queue_reset(FuriMessageQueue* instance) { QueueHandle_t hQueue = (QueueHandle_t)instance; FuriStatus stat; - if(furi_is_irq_context() != 0U) { + if(furi_kernel_is_irq_or_masked() != 0U) { stat = FuriStatusErrorISR; } else if(hQueue == NULL) { stat = FuriStatusErrorParameter; diff --git a/furi/core/timer.c b/furi/core/timer.c index be7efebe2dc..4b6ccecba5f 100644 --- a/furi/core/timer.c +++ b/furi/core/timer.c @@ -3,7 +3,6 @@ #include "memmgr.h" #include "kernel.h" -#include "core/common_defines.h" #include #include @@ -27,7 +26,7 @@ static void TimerCallback(TimerHandle_t hTimer) { } FuriTimer* furi_timer_alloc(FuriTimerCallback func, FuriTimerType type, void* context) { - furi_assert((furi_is_irq_context() == 0U) && (func != NULL)); + furi_assert((furi_kernel_is_irq_or_masked() == 0U) && (func != NULL)); TimerHandle_t hTimer; TimerCallback_t* callb; @@ -60,7 +59,7 @@ FuriTimer* furi_timer_alloc(FuriTimerCallback func, FuriTimerType type, void* co } void furi_timer_free(FuriTimer* instance) { - furi_assert(!furi_is_irq_context()); + furi_assert(!furi_kernel_is_irq_or_masked()); furi_assert(instance); TimerHandle_t hTimer = (TimerHandle_t)instance; @@ -82,7 +81,7 @@ void furi_timer_free(FuriTimer* instance) { } FuriStatus furi_timer_start(FuriTimer* instance, uint32_t ticks) { - furi_assert(!furi_is_irq_context()); + furi_assert(!furi_kernel_is_irq_or_masked()); furi_assert(instance); TimerHandle_t hTimer = (TimerHandle_t)instance; @@ -99,7 +98,7 @@ FuriStatus furi_timer_start(FuriTimer* instance, uint32_t ticks) { } FuriStatus furi_timer_stop(FuriTimer* instance) { - furi_assert(!furi_is_irq_context()); + furi_assert(!furi_kernel_is_irq_or_masked()); furi_assert(instance); TimerHandle_t hTimer = (TimerHandle_t)instance; @@ -117,7 +116,7 @@ FuriStatus furi_timer_stop(FuriTimer* instance) { } uint32_t furi_timer_is_running(FuriTimer* instance) { - furi_assert(!furi_is_irq_context()); + furi_assert(!furi_kernel_is_irq_or_masked()); furi_assert(instance); TimerHandle_t hTimer = (TimerHandle_t)instance; diff --git a/furi/furi.c b/furi/furi.c index a616bce63c1..cc0e3f4f12f 100644 --- a/furi/furi.c +++ b/furi/furi.c @@ -3,7 +3,7 @@ #include "queue.h" void furi_init() { - furi_assert(!furi_is_irq_context()); + furi_assert(!furi_kernel_is_irq_or_masked()); furi_assert(xTaskGetSchedulerState() == taskSCHEDULER_NOT_STARTED); furi_log_init(); @@ -11,7 +11,7 @@ void furi_init() { } void furi_run() { - furi_assert(!furi_is_irq_context()); + furi_assert(!furi_kernel_is_irq_or_masked()); furi_assert(xTaskGetSchedulerState() == taskSCHEDULER_NOT_STARTED); #if(__ARM_ARCH_7A__ == 0U) From 55054fc1a7467aff87f7250178ecd8efd9e7a738 Mon Sep 17 00:00:00 2001 From: DEXV <89728480+DXVVAY@users.noreply.github.com> Date: Sun, 29 Jan 2023 11:55:04 +0100 Subject: [PATCH 362/824] Assets: correct MicroSD card pinout in service animations (#2323) * New Micro Sd-Card pinout: fixing the Micro Sd-Card pinout for the blocking an internal sd card animations * Updating the sd card pinout * Updating magnifying glass --- .../blocking/L0_NoDb_128x51/frame_0.png | Bin 1398 -> 1424 bytes .../blocking/L0_NoDb_128x51/frame_1.png | Bin 1403 -> 1425 bytes .../blocking/L0_NoDb_128x51/frame_2.png | Bin 1403 -> 1423 bytes .../blocking/L0_NoDb_128x51/frame_3.png | Bin 1401 -> 1420 bytes .../blocking/L0_SdBad_128x51/frame_0.png | Bin 1552 -> 1370 bytes .../blocking/L0_SdBad_128x51/frame_1.png | Bin 1586 -> 1387 bytes .../blocking/L0_SdOk_128x51/frame_0.png | Bin 1549 -> 1387 bytes .../blocking/L0_SdOk_128x51/frame_1.png | Bin 1559 -> 1395 bytes .../blocking/L0_SdOk_128x51/frame_2.png | Bin 1578 -> 1413 bytes .../blocking/L0_SdOk_128x51/frame_3.png | Bin 1567 -> 1403 bytes .../blocking/L0_Url_128x51/frame_0.png | Bin 1358 -> 1380 bytes .../blocking/L0_Url_128x51/frame_1.png | Bin 1964 -> 2046 bytes .../blocking/L0_Url_128x51/frame_2.png | Bin 1956 -> 2044 bytes .../blocking/L0_Url_128x51/frame_3.png | Bin 1955 -> 2045 bytes .../internal/L1_NoSd_128x49/frame_0.png | Bin 1382 -> 1411 bytes .../internal/L1_NoSd_128x49/frame_1.png | Bin 1376 -> 1410 bytes .../internal/L1_NoSd_128x49/frame_2.png | Bin 1381 -> 1416 bytes .../internal/L1_NoSd_128x49/frame_3.png | Bin 1378 -> 1415 bytes .../internal/L1_NoSd_128x49/frame_4.png | Bin 1372 -> 1401 bytes .../internal/L1_NoSd_128x49/frame_5.png | Bin 1377 -> 1412 bytes 20 files changed, 0 insertions(+), 0 deletions(-) diff --git a/assets/dolphin/blocking/L0_NoDb_128x51/frame_0.png b/assets/dolphin/blocking/L0_NoDb_128x51/frame_0.png index e82c6f2e9c6a2880b0b983e1a80497daac351704..759007623af4e16c1bbdd167750e2ee47152ff88 100644 GIT binary patch delta 602 zcmeyyHGzAAL?8<@0|UcaKUrlU#aJBV?!>U}oXkrghb7(7*O7r?V?XzwL{=c5Ex;$l z_5c6>KxXKn%XL7Cv%n*=n1O-s00=W?UF+mzU|^m((aWESmuupzJZ4@lg~=R@POLy$ zL1D8O;{rx@FjGNc@++o=^$PlS5U!1WQA(PXOKNd)QD#9&W_})693mRz;s)f|=vP=( zD9qCSiJ- zyW#1BPdSXM*p9d|D~2C5xiCr9gyl|;y#gb9`nMI)?=+sNMr(WO`!U+tTj@JUKXd43 z{Gi!Y|7GU6jt?Hb4EH3G9nM+%8b4F2)?(by@44lvs=}@Yjs1)%+zm__#v8t`=4hJ9 zc9Zq>j%)|lW|jln)juy-^<5+&DE+5E13$y-z#EJSoEb6Uu?I`}&M{_8KU?Bjaj>zN z_m_3M_?=#n*I#lSJ}P{gZNT_o%Z7!FOEkC{b@}3-$_-7%~V00001JzVIRd000?uMObuGZ)S9NVRB^vL1b@YWgtmyVP|Dh zWnpA_ami&o000Aik!eo>Ad$LmlPdv2vvL8T0h1X6p?@zvAUrQ}WM(=fFD zZ*D#yJTG!&W;#+tMm``sFL*k5ZE$U6bYVUqJU@7FVPk7$bRcDJWIZBsB0oM;Hv@wJ z000J1OjJbx00960|E1ZV-2eaqd`Uz>RCwC#n28R9APk28zW+;aS)`X$px}`Ow|GRb z-+KKr(0{`l7Qkx&aJpR&06avX0Dc$o_ay-EIfH{^TdLtk0MHzOa;2wyclM-QZUa%U z*?=0=nS|IrRQv%)YIbt+Grk{?0pKUq05-pwu~PtAJs>|`oG}Yvp9554D$(WetC@Vy zl=hn%(E?Zi*4PYyQYWBlD+Vc3`Y-|@?Vzd!+JC+-=6(R|N}K^;+JzZp?UQEd1aMuB zyA)9*6ao<1x2G8Z69Ut}&j2tD03M+30TOeLpeqM#2&Aq=415ka08T-=sSkuczY~BF zJj!zbgl7PTt6&-cjRQ2wo7V!{{qg|y8K7e=P_KbWfR+id7AQ5evAGr?h>m+N$N=DA zS#E|JK=fvk0zmAGomvy>1UV0sr5x0X(X4fA078%qK`4Pm?pgpa2tXFVER5R#FcqLX z2QbtJ*n+H*(N_h70O>yP9{^oiFdu-LpNO{Mm#Ojkvw8v0M}PqUpUe`n6H&y000000 LNkvXXu0mjfS)JjK diff --git a/assets/dolphin/blocking/L0_NoDb_128x51/frame_1.png b/assets/dolphin/blocking/L0_NoDb_128x51/frame_1.png index 58dab74d4a618d0ccc4674be0754b2f11a28e111..c9810b61e34bfc61f4b62a26225afaab6a05490d 100644 GIT binary patch delta 603 zcmey(HIaLQL?8<@0|UcaKUrlU#aJBV?!>U}oXkrghb7(7*O7r?V?XzwL{=c5Ex;$l z_5c6>KxXKn%XL7Cv%n*=n1O-s00=W?UF+mzU|^m((aWESmuupzJZ4@lg~=R@POLy$ zL1D8O;{rx@FjGNc@++o=^$PlS5U!1WQA(PXOKNd)QD#9&W_})693mRz;s)f|=vP=( zw#+_v4VT99ldTO;^Yd!VcHOU+&u6IPQh4!Rae=@sf5r)id;HRkUwNA| zYDhC^zKm_2#HKbe>{OA5@tck2OBoCgo|as@q*SYF^1Har;g*yxYf5 zObhI0*hDuhb6mKJQ9RhubiypFDNK2{dZ`~99 z7&mBk)t@k)DL7BLo%usHS4X9K+^-mw;-ie)W>v&TG1{gf=iXSmvmTNHLopF6}g0)F({~@jmx34pL>g6#1O7XGdSBqbDyZ&o% b*eUK(MH8nPTONl3(*uL2tDnm{r-UW|yvo^{ delta 578 zcmV-I0=@l_3;PO?7%~V00001JzVIRd000?uMObuGZ)S9NVRB^vL1b@YWgtmyVP|Dh zWnpA_ami&o000Aik!eo>Ad$LmlPdv2vvL8T0h1X6p?@zvAUrQ}WM(=fFD zZ*D#yJTG!&W;#+tMm``sFL*k5ZE$U6bYVUqJU@7FVPk7$bRcDJWIZBsB0oOC5CQ4{ z000J1OjJbx00960|E1ZV-2eaqfk{L`RCwC#m9uK7aBSSEh13Cm;RV)TJ2MmBnK-$s=sy^Qd zAP}f^+ze0@;x>TEDp&?U^8ikHE484a3ITxr4A7kxh!?>SfM-HW3xpa9S3V3-jgNXS zh;sm-u`EL$pnNk40if=SLahmXf|v)=QVwFpsMflD0JuY14M9}`lic|LFa&U>cv7h@-A}f&37T^=& z`v3obAT#vPO>_%)r2R0E8K{u61%UFfh-Y=;hDE%QbOU9y2eO!ekCcCsrV> zps?ACaRDPcn5m#J`4!W`dIfzu2-ilxC?(CxCAB!YD6^m>Gd~Y34iOD7gEebrY#pM&; z)Nh_Vy+fg+g4D?$Q&(m^P|J;PVZPhH(v9(An@T*T|+p7|IW>`T;td(YD#u8JhAX$_#oi=Y^Iyb z6A#q}m3n*j?+rcHZ-qCW&{Jiw(@K8JDG=;%MW5kEf(=6fgB7ce*nP#sC(U!3eDBmc zwDU75JQKUu)x1cyaJej#)6sN>8m*Qgr1$WJiwROa&G5npZC0?L% zgzZ6%x$rZF5NYXk4CjxnIKxnL%Ao%W!+*IQi_;lye>xJe-Sd4dgTe~DWM4fw#Vk% delta 578 zcmV-I0=@l@3;PO?7%~V00001JzVIRd000?uMObuGZ)S9NVRB^vL1b@YWgtmyVP|Dh zWnpA_ami&o000Aik!eo>Ad$LmlPdv2vvL8T0h1X6p?@zvAUrQ}WM(=fFD zZ*D#yJTG!&W;#+tMm``sFL*k5ZE$U6bYVUqJU@7FVPk7$bRcDJWIZBsB0oMwV421M z000J1OjJbx00960|E1ZV-2eaqfk{L`RCwC#n2QdBAPhxs|Np1A7@#kdg2-ILw)lu> z4{hnS1AqPagAMQ+09<~a2LOIVAOMb&`tKzGusMU1eJs_m5CAj>AfL%wz8igzPt!nT z>|#KS>P$kUFJ*tgkg5$I9LD{C5rFcf7C`cw8ao9b>H(fSJ7X4r&jHFY<>;31i#y9T zwY1+|YxxTRy0lh_w0q7&Z07-ZfvS0hb Qf&c&j07*qoM6N<$f^|vo?*IS* diff --git a/assets/dolphin/blocking/L0_NoDb_128x51/frame_3.png b/assets/dolphin/blocking/L0_NoDb_128x51/frame_3.png index 789dc8822e8442ddd49ea503b9ac8d109475e1ef..b48aef97839e4766b73c9630ae25c4fc2abb3d3a 100644 GIT binary patch delta 598 zcmey#)x$kOB9Midfq`MIpR6*FVk{1FcVbv~PUa<$!;&U>cv7h@-A}f&37T^=& z`v3obAT#vPO>_%)r2R0E8K{u61%UFfh-Y=;hDE%QbOU9y2eO!ekCcCsrV> zps?ACaRDPcn5m#J`4!W`dIfzu2-ilxC?(CxCAB!YD6^m>Gd~Y34iOD4`zo``_31b32qV@>rWQ9?@9NxS(A+JlrtPTc2?O zYs0iRdCiyD5=+-k`+6bm$K#AwOjktKhi}_-b>^F8cNaffU(&9?&yYJcV=v>a%#f-4 z43{6gi)0d!7m&9TDAJ$7za_0{SHtvg%zKzBPR+fl^U3JI|6qrS-W1%uJMFH1XI%QP7`_(^63JMb_Du4nvkZU$okLlmoyh3c1@3C8n+Z@cV{t0*s(kaZm5ZOoNS`1{exrSNu33E&5X58#IG@YS5MbV;d#e+ zYWqwB#s#--6fjnprBwozw^c4-`cWlNC&!p`TKK-&-_Wm86D}J`JKa<{xoH(^ON(Ly zlftJJ3My+De!OdTK2#%C)BdbXtgGd$#g0`!q!vu(KHXp@X}zDl+5YNN|G(PGznGtL WnK&{0-W3K+3JjjEelF{r5}E+=mf!XO delta 576 zcmV-G0>Ax?3;7C=7%~V00001JzVIRd000?uMObuGZ)S9NVRB^vL1b@YWgtmyVP|Dh zWnpA_ami&o000Aik!eo>Ad$LmlPdv2vvL8T0h1X6p?@zvAUrQ}WM(=fFD zZ*D#yJTG!&W;#+tMm``sFL*k5ZE$U6bYVUqJU@7FVPk7$bRcDJWIZBsB0oN5K?$w^ z000J1OjJbx00960|E1ZV-2eaqe@R3^RCwC#n2QdBAPhxs|Np1AEI=O=t@6+%xXnoh z_E1W%9e?QI0~_Eq060A60{{;Z2!LP1f4>9(HfGTHj)fW)0)WN<P3?TVU^&J8b^#D(voiPfajsePR%Bx$JUyLlj zsjdB{M6>}mz<7YXPe7-s7&HNZ)4{10X#1W^;(wF2Tf6{3r3=+b*-y3&LVVIP%atre zNJ}!Ebpog*Q2DzJK*BI}0>AJ3020R{j{A`-F)HA1og O0000U}oXkrghb7(7*O7r?V?XzwL{=c5Ex;$l z_5c6>KxXKn%XL7Cv%n*=n1O-s5C}7hYIrp;Ffh-Y=;hDE%QbOU9y2eO!ekCcCsrV> zps?ACaRDPcn5m#J`4!W`dIfzu2-ilxC?(CxCAB!YD6^m>Gd~Y34iODOmSOyv4Q%M`>aOn5*dHIgX+W?QkAwwy`O3H@L=4^)oo@i zzKS~6l>_CIVl`i!?wmJiOC^P?u>qNI^UZB z_TvrHHgG$v%Vlz4ePO4tWOtCCvx9krSD@q};RpLxJ`q-W(!~5ma>3c%THX2ywIc{w;!)foz+D=s%LwzPi+6`4nyAd$LmlPdv2vvL8T0h1X6p?@zvAUrQ}WM(=fFD zZ*D#yJTG!&W;#+tMm``sFL*k5ZE$U6bYVUqJU@7FVPk7$bRcDJWIZBsB0oMEGI*l^ z00JmUL_t(|+U#2Gj>I4cJR9Htm3y1poM}J-KWNnsKQ`Uex^+fiU=8Pa{=lBI0}y}! z5CD75Vt?sI2*K$mk#%$r@I&8w^4&tv`DX(!U<>2`?jmhYlb))5Jp3E^+V8u3hB)WG z_`GQHnEqu~IUo7y|>!DM4zFZj0QpJW%S`&z|Xm@w-`6)LFY#hn+m z91!%sUrXEYdY3;=23%*$a<39V-iJ@f{brdE^na{itqnLm?}E<;bG1f-Ask9JZNT@Wp1^M2ZR$(1-DI zOn>O`sBI$217y*rYG>oHr9+V2IYuU7l4s<8S^&>QfFSb+{#`Xy+2txdVy(au1ipM^VL9lB zuOf>~MK7qi*aP~)wmxdnm(ToM&m(=2zagi3NQd~FXriq;F=f!015yA LNkvXXu0mjfqA^8s diff --git a/assets/dolphin/blocking/L0_SdBad_128x51/frame_1.png b/assets/dolphin/blocking/L0_SdBad_128x51/frame_1.png index bd0f2b9338264ef39ffcf378f9e6017edc05286e..147561f0a40ab1c8ac7134844843d659783c2109 100644 GIT binary patch delta 565 zcmdnQ^O|ddL?8<@0|UcaKUrlU#aJBV?!>U}oXkrghb7(7*O7r?V?XzwL{=c5Ex;$l z_5c6>KxXKn%XL7Cv%n*=n1O-s5C}7hYIrp;Ffh-Y=;hDE%QbOU9y2eO!ekCcCsrV> zps?ACaRDPcn5m#J`4!W`dIfzu2-ilxC?(CxCAB!YD6^m>Gd~Y34iOD7K5f@kuUvl$d`dceBj?BVltoMHa~b5`YnL%L=JGReh8H(%VBLSrcGDk* zDh8IljMrOF)S5HQkYrL4lStTm?Zq_NE6?Wsy({qME`!`B%ibRQd*1h(UmW3g;5s-j zwBeYM8RI0z8O%!}WF4;8hz8XgvKKr3id86X=sUJw5tmEpqsDVK~`H#}*0;5XZpp|#)pPTYlQP6`fNS=RV0Viu5-JENbR%=aaL z{dmKy4crf&G9D1!z{{X*(B_oIb~;9&;^;f3d9JGO1rk`6@$@hR+3qf43~ZEQkJuQ) ze#5<1_E8QGV`Z@>qt+~Ap~V6_%oR3HTRMl)aN!pghG{0P=C&5In!jnSVc@D}yl|#< pYS$IHNJgvo^ZzgZ+bnj7-E@s%@+~b+J77#Rc)I$ztaD0e0sw!z%fSEu delta 763 zcmVAd$LmlPdv2vvL8T0h1X6p?@zvAUrQ}WM(=fFD zZ*D#yJTG!&W;#+tMm``sFL*k5ZE$U6bYVUqJU@7FVPk7$bRcDJWIZBsB0oO0n2LV@ z00K%$L_t(|+U#1{j>8}fED`_z%h{^cqR2M33AhPl{IE(D!ZCx-b=vp+1-VW(KmYOA6dF+Fn_yp6!bpi7k6@Caxsq&{Mz!rl7BzSD_O;&bERA51j7THWi=qk+FW}Z9! zP3kc8qR=tx_6Mw+Gb$kQR&6Ecuz7t(kx?FH6VW%pRbX`vTTCtZVZ}0$5d|seLwGu- zRDXEPGLfbMdeCOZ&dy;|n;^e(jC8`p&*=5YRXG^WR&LckT~VCFmVwwT{+MMVT5x|( z3W`Jw!U#a`!tdo|G0yy79gvM8KxtMeuO56Bl`m^5&hSp zeSxJOMQCdMCEXA9jO)^RdCfIJB9Midfq`MIpR6*FVk{1FcVbv~PUa<$!;&U>cv7h@-A}f&37T^=& z`v3obAT#vPO>_%)r2R2!t6$HM|-a7?@{H^zvuo<(fDvkC~TCVKN7!6DyEb zP}uCnxPXxz%v4aA{EBH|y@I|SglnT;l#*uUl3JWxlvz-cnV$z1hlmEbxB+=K`W03c zxdpkYC5a%OeMLcHa&~HoLTX-$tx}Paz1{yC;&wpydwRM!hIkx*I<0%%VFix1pZ6K= z{ZBo9G%EM%#xl>%#UA3~UGq=4de^%stc|H>DEZvFp6SH=YzDTg&u=quY|L`_$ekEk zD0CpNmf^y2hOf>MXH8twQnj{D=5c7wI-OV@%eZcp+h>zD_T+{;tR{K;m@9Y^m>XOD z41f5k{h*gK?#Oig delta 726 zcmV;{0xA9L3XKeq7%~U~0002c0+&Ys000?uMObuGZ)S9NVRB^vL1b@YWgtmyVP|Dh zWnpA_ami&o000Aik!eo>Ad$LmlPdv2vvL8T0h1X6p?@zvAUrQ}WM(=fFD zZ*D#yJTG!&W;#+tMm``sFL*k5ZE$U6bYVUqJU@7FVPk7$bRcDJWIZBsB0oNTQ!`Ki z00JdRL_t(|+U#1}vV$NDEDrzw%bYXq)JcKF5Qr#lo={sZ*<4pcO6dieCmU1%0001F zo<#0J2!Fw8No1X45Af5z_vEXEp!Al31IU6oAbf>-Sk=!s=QBGcU5ielpD-?<{oS|d z_#Ek9?kQCeb&@}6t6LKi9u`&&?{P`p4J?11SSB_E36|c}Slt&w zC+%}O*_z^vzBNaNFeYK;M@}3JpGT`|zkm7>syud~A_OO0*zkpqP|Lue57)70LkJ*( zu9XU}oXkrghb7(7*O7r?V?XzwL{=c5Ex;$l z_5c6>KxXKn%XL7Cv%n*=n1O-s5C}7hYIrp;Ffh-Y=;hDE%QbOU9y2eO!ekCcCsrV> zps?ACaRDPcn5m#J`4!W`dIfzu2-ilxC?(CxCAB!YD6^m>Gd~Y34iODB1mW<1j6)WVvJ9oPPxyQ!48jGOlskvre!-&%m?l{BlNtq^k{E}{ z?x>7senJa+~jX3G0qkQoWZYdcR2>Cs}-xt=ljs?3_^!8hfzIzx&1YFU5FJ^T-N zN?o)M)q5-R__NnM>H7YwdzSvx_g7t9tG^uLXoygq#8Akh|3d75X}c!dM$5~b2N)lS zHdL?&^g3ELovr54IKJTZ!A%FQUo>Nw&0u%#lZb*egUi2**Brxl)t>(A^lDvxxt;8b zuTJN$pN`RbT4}zHVcB;_W(%oTOcA>#9(d~aAi=aDI!E1x;YV~eb3*gY+Vjn8C3rS( y;PLpx>abm0Z~d{C(_eiGOBN5?{>$F!3-e7Dv!zz0+0TJ7&fw|l=d#Wzp$P!L_tz)@ delta 736 zcmV<60w4YJ3YQF!7%~U~0002c0+&Ys000?uMObuGZ)S9NVRB^vL1b@YWgtmyVP|Dh zWnpA_ami&o000Aik!eo>Ad$LmlPdv2vvL8T0h1X6p?@zvAUrQ}WM(=fFD zZ*D#yJTG!&W;#+tMm``sFL*k5ZE$U6bYVUqJU@7FVPk7$bRcDJWIZBsB0oNGb`Hz{ z00J*bL_t(|+U#1}vV$NDEDrzw%bYXq)JcKF5Qr#lo={sZ*<4pcO6dieCmU1%0001F zo<#0J2!Fw8No1X45Af5z_vEXEp!Al31IU6oAbf>-Sk=!s=QBGcU5ielpD-?<{oS|d z_#Ek9?kQCeb&cR(W=_7euOHIU8o4bNf$PJ;Um;C zFzCZ|?AZ_kh@fjF26!p&qJ6!M0)X*_ja>KxfRiwA&jC+Xbf(<$&7HoD004F_fzx&U>cv7h@-A}f&36W|l# z`v3obAoK6vzyJRI`{g134#?pw@Q5sCVBk9h!i=ICUJVQk%rhr?`7`lyO`Mg-%*&-P znS;@Z6-X;6Z1!SYz{n0}Dkw~T#k8Y^rpl>0b=AFvA)PXxA4=E<|EgDRR_$kXFpuBBrLg}RLqgW| z-HbXcTMdrtvt4QNWVln!zF<0|RJ+67lI}C7eO{>>Gx)C3J^rf z4qTtP9#|&q7Ic20$^L%jk@xHh_Iu23{9=sgyL!XtS)00bPLzqL9s3KnBK1xYhoApg z5@OG09p64Dmi+Y5p9;U(`@Eru??RgX)yDJP%WBvYHr``Yu+?Eava}&o zw;+%?o}q1*tJV^3hx$*S#143HFODdGAEY^dbs<~9Eq*Hp(JOog9#(x{Wi9gq*LHum0&Vv8-3GiKkC_`5ij}!PerbR8<iZ0h1pCp?@zvAUrQ}WM(=fFD zZ*D#yJTG!&W;#+tMm``sFL*k5ZE$U6bYVUqJU@7FVPk7$bRcDJWIZBsB0oM%Oi~C71l@rZ^CN`rWH(PVL%q>bkUycgPaP-qu>$(mxMhY~CNP0@yp6uo9o!5R!k$u$X4#%CD7S(J% z`hR87A$*qJ`BIl#-kgE;&9&sq$)Y z|G5W~8GX5!ph=A-a`6;g(D+Wcbar4mKmpbmnqCI_M|(h4$$^JBS^*{8K;O3ts`hB| z5%#SK2@jJ}HDXrmrZjxzp<@rr#D*Y&jejulQ1>-|C-gZ?3|x?@HAhNlOu~8wO6U(} zMZfwdRC(+|MF>thvEd7ULM;P>K3vD14IzLCSSxjaLwOhU^)?Cxj4y2D!XFAaNC)mY z;K@LjOmG&9^lbzb(9R`rh?|-?grJKxVi3O~yIL|K1hKLH7&dfiNZcO#xN@v_og_2h lG!Z!f3V=WXkCi_G1^|H}F1Di~quBre002ovPDHLkV1k$oN;v=k diff --git a/assets/dolphin/blocking/L0_SdOk_128x51/frame_3.png b/assets/dolphin/blocking/L0_SdOk_128x51/frame_3.png index 614ef9c5aeea94fc93a47ccf41b0ba8b4c8ee7ee..1e52f1513742e3b985d00385f5e153ccc3937edf 100644 GIT binary patch delta 581 zcmbQw^P6jeL?8<@0|UcaKUrlU#aJBV?!>U}oXkrghb7(7*O7r?V?XzwL{=c5Ex;$l z_5c6>KxXKn%XL7Cv%n*=n1O-s5C}7hYIrp;Ffh-Y=;hDE%QbOU9y2eO!ekCcCsrV> zps?ACaRDPcn5m#J`4!W`dIfzu2-ilxC?(CxCAB!YD6^m>Gd~Y34iOD9odm%?2E0G_Ydu}l%9{f|7{Pwvlh}5fR*!A_)YKG3&QVkQz_DVB|&SDFybIs7w zW0JVW-mr&fgT<7zH1{WA>x`^p86$%3%5J;PE3hif#QvDjL5T(BjGAZC8%{9(QCo1z zneSe3=YRPFe?H7Q@SJhs=e!&(^T*Be_O0O&ox}V_;o?>ot_APx858d9DSLE6u(lx~ zLLp2ruwLqD0l&h#oF6Op2Yuf9Y4NNjS=-}gFb0IJRCBoLBvQhkV10N>^P@SNm^Uz5 zFnX|Mlr(ISxuW~y_ld?QG8@7i#1C##V6dA~$o*jBJw^lD7^aLZOzXcfWtl(VOt`UK zM*LDi?fXjpge%;OBlzD3Y0fWy%=Mr|uB2h@LcWHjoh;W}W*#{7ib31BkXd2hEpvvz zl0PXd+lvfrTT>k-@iVmTE%;$F?_Y3inJItb)vND2PROSwnAsjIw2T7AJcFmJpUXO@ GgeCxpA=_&J delta 744 zcmVAd$LmlPdv2vvL8T0h1X6p?@zvAUrQ}WM(=fFD zZ*D#yJTG!&W;#+tMm``sFL*k5ZE$U6bYVUqJU@7FVPk7$bRcDJWIZBsB0oN1o5w={ z00K8jL_t(|+U#1{vV$NDEe`+x%icTflu3cautX3}p0HY$ll`bW&+`QtCkG&a0-ylM zIPu&K=YO2hlE^yO9$<&Q_2jFCpz|#q2apAGfcpydu&keP&R6Y}bS*lGenP(h`oXv8 z_#SCrZkH;Ex=7?Sk^M&g6j`*!&x93=J%C%ddN3rb2hRwo(aZla{e|9YcaG15>HO=2 zAQ}f|uCKVg`4?npw(y%Ao*H0uDLk3}P6ry34}VoS_pf}}VC~E33AU?(pa2LIFs0Nd z;UUQb3ixR;if#=jDk^IB^f-;>gQ^h=&0A5S7S&$Qb%iXsWJfpoj_4~VsW8UK5(V-= z?uxnDs>@|=QF?qiD){c`cho`F7&&A)?uC0MqHD6plofEfsAlti<}6B2RZX_E)ECOt zTYrMmQG~?}39x$I^DA9~p7md>xS+mOP?kT>+&rG&~IS@Ad#1WPttv1*C8TecLK1+gQoX(W40o3zN!*ci&ij zFKPJ7myRusJsW}q3Zba6dan5!q0eD*6n_yo`qmsNp)v_8KXT&K@VT`r`q__A<*^AB zAvo#6hA(`Cng#}axb`g@LI4r4R_XvRU}oXkrghb7(7*O7r?V?XzwL{=c5Ex;$l z_5c6>KxXKn%XL7Cv%n*=n1O+BKL|5gum-CzFfg}F^zvuo<(fDvpP83SVKOJ96DyEb zP}uCvxPXxz%v4aA{FCzM>#8IXksPAvG_>R;ftI-tPJ7X{JE;hkLp>hIkx*JB>T9#em1f*nGy$ z|Eby5iuPu^8MQ*QUb|*Ap6Pz}&n8*Ht_WcUcAM?y#sVlDj4O{@87Xw70?z zOtDLMGhA>x_Tskp494r1en=$vMe+FDP5PkQ`o)_u;=vE53l?{UU9V1RXHZ@cx65m( zcr}B9!b0DUzz!>)Ejy10AK1XSV3E_}Ka%ASjxKn{;M60TAZ~VNS^|^4gZ<|J)5Tx$ zwM=`iTL0vyl!JCd7(;Kv6+hbp&(pQCUu=G`J)1|uu;I1NE?qX(h9A1G8caV0J6Scv zF|69lDzh$l*G_G5ML7ns$Q>L9|T#nOn<}vA5HgA*J2Fp};AT{lWY2k31Lj5}rDK`NEeV z%KS1?%#87D9b3V>)@+8J$ocs%KTm)4Eu{Wv&@ZO<4Ti?5S8OVQ@y_7s>gTe~DWM4f DD0JIS delta 554 zcmV+_0@eNG3eF0U7%~V00001JzVIRd000?uMObuGZ)S9NVRB^vL1b@YWgtmyVP|Dh zWnpA_ami&o000ANk!eo>Ad$LnlPm#3vvUEU0h1a7i+?XZAUrQ}WM(=fFD zZ*D#yJTG!&W;#+tMm``sFL*k5ZE$U6bYVUqJU@7FVPk7$bRcDJWIZBsB0oOw-4GW5 z000J1OjJbx00960|E1ZV-2eaqX-PyuRCwC#*nzHtAPhj!+yDRRB@Ss}1wldH+cZnm zMClQs#eX4-Hy*&B0Z`rM17MNB0JanV_XU8Y3GN4wgk1#KqG&8`9fa?Qgn04EZUxA1 z1Hv#ugWfy_{zoQ!0NsF28O;DsR36x&89oEhavFCgl--p9+btn3?(3n>E2o`FcbVTpnNNCEilzEeK&If&fCe>1s4IfYUG_? zSz8Vn!8JhkSwJg*Gb^teAnDu|fC`2H%$#opFcfg22V!MjGzJ)z2Tsm{ae%%&@NIyG sS}+qJ2QAX{WjL=IKYvy~0C@>806$X`4`zs|dH?_b07*qoM6N<$f+3vSUH||9 diff --git a/assets/dolphin/blocking/L0_Url_128x51/frame_1.png b/assets/dolphin/blocking/L0_Url_128x51/frame_1.png index 69f1fb365988f248aa238fa6a2d672fa7d8bcee1..9975ca3f042623b1cefdd5eb913a804dd185d734 100644 GIT binary patch delta 682 zcmZ3(|BruyL?8<@0|UcaKUrlU#aJBV?!>U}oXkrghb7(7*O7r?V?XzwL{=c5Ex;$l z_5c6>KxXKn%XL7Cv%n*=n1O*?7=#%aX3dcRs#2KflA_ zpXF>ll4hVPeRO3u`q)f_sB=jzPAbocRnwN7ht`GX(lx-}lyu z|AzPDq#B&{BP!Dh5S#|Hi>aZs!4%{dD^NuZ+)Mju{Sg6x+O`tx~ zx3|>Li$7o$b4%R9xPOm~HzXx&Wju0&@r&cttE{FQn6?DIihsBwpP}$|Y{yrw24@C# zhHVWYZEOtIKK$~pbZ(UNHCFH>lu6wcYG!VbSohkY=*jMu5QaF0O*JfYqQ(0d=RY+# zB7C4!%#1ODIb?15v>z|`m>=P3SS5Tru47J)KjYc#lhPe|KJU(%Da=>CSWxU>!clcV zaH^$4?pnh~DeoE1%)iIUuq=#1%;Y|NnpK&G~mH28c7U-kg}}&!{kQSN>!j zMyJiij9-~1=d&!7(YI5usmLwx^|kWIEH23}ss!?jQqrt~Qw!}Td$69Z2Px2pi2#}C ziotR&sl~}fnFS@8`FSwqK`w3}W`z|{LvCtGB9LQWQIMFNom!%hnwMg$RHS5YckBCC zYX%0!bWaz@kch)?XWZV@tiamQiU zxS;ARdqX=1%Yie0YZx6;ntn5O+_7UxNb8<^RD8SI!$%XZ-nZKv&G=?J<24f{i@lcY z|J>DU7VDGYvW8teSGB_FhEI#%ayX={<85eeeZw?iRzCX!r-op`3(8l8Ef1L1J}425VZCsheING% sNA5JH8;!2B{MtTW`SEx2FV+qGb}NPXFA6B10HzKGPgg&ebxsLQ04xUSEdT%j diff --git a/assets/dolphin/blocking/L0_Url_128x51/frame_2.png b/assets/dolphin/blocking/L0_Url_128x51/frame_2.png index 855e7450e53b99c522d473e9e913271be4b36536..84241c3f16ab65f696964e17ab96c1deb8a7dc6c 100644 GIT binary patch delta 680 zcmZ3&|A&8qL?8<@0|UcaKUrlU#aJBV?!>U}oXkrghb7(7*O7r?V?XzwL{=c5Ex;$l z_5c6>KxXKn%XL7Cv%n*=n1O*?7=#%aX3dcRs#2KflA_ zpXF>ll4hVPeRO3u`q)f_sB=jzPARUho>;uzv_{Oz>tqQeS2EnCea?*892QNUkV$miC- zjMX12at_sT*cLpwe`t&Oz3a&gFEkrIycSS!*;|snPr9Fx=dC&eW9-u93>VUt-8>$> zgX!JUkI4;|nT7_X-3OL8TN_JU(V znKXk*Q~sk_toF&-JTsCQu1A(c^Dr&2I~FRt!R_Cq2}}SSw$=v*})r_JJ=3#c-)oHf6uV&>#>f> zdl?tqo!)+fwPA)WLs8r}hJUr8=NQ(?|99q}#BJS>Zm6L*CvD-bf(Z6z;rrY(svpWR nB%fIC{OjNRSKor_kGjaE>KGbtX4qr|Ok50}u6{1-oD!Mq=#1%;Y|NnpK&G~mH28c7U-kg}}&!{kQSN>!j zMyJiij9-~1=d&!7(YI5usmLwx^|kWIEH23}ss!?jQqrt~Qw!}Td$69Z2Px2pi2#}C ziotR&sl~}fnFS@8`FSwqK`w3}W`z|{LvCtGB9LQWQIMFNom!%hnwMg$RHS5YcjC&P zrwj~?@t!V@ArXh)&bZxoSb@jo{Gb2+-xXQTm~LRbRG%vx>ZP@HUbA5`$F84`7=K7G z%(`C1u%q(;gU8kL;tVc|H~1Y?s)ZYtD4$z+^qP{5NMTi7N;Kn_+YIY^I2O&$eo$}m zklBF8z~fjBL--9no}CP~@qcqA7!I!VF03&T7GPZRt1?LIT^Hj6lS&awJEI8|?i(1E zBz+5GRXEe=&02r#-J#+`3H47Hll~s4uw*TKBExWuF{P!H*Fo6f=yJ7&bD6!IS*r!4 zEw3>J{5+S!)e!l4FT<1e-+LuLu*}X@P`$B-tEg$Oox%5%FN_SbdlOU}^pq}@ j8bn|3oj?65e?#1h55oNRTYdxrQw4*ktDnm{r-UW|Ys2O5 diff --git a/assets/dolphin/blocking/L0_Url_128x51/frame_3.png b/assets/dolphin/blocking/L0_Url_128x51/frame_3.png index 9e9a79405817d4f1f3ec242de249beedac773c57..c44b171bfaae20b74657855c263a48cb0ca3ae85 100644 GIT binary patch delta 681 zcmZ3?|CfJ)L?8<@0|UcaKUrlU#aJBV?!>U}oXkrghb7(7*O7r?V?XzwL{=c5Ex;$l z_5c6>KxXKn%XL7Cv%n*=n1O*?7=#%aX3dcRs#2KflA_ zpXF>ll4hVPeRO3u`q)f_sB=jzPA_)(Vst@sWaSZV|{&re+(P0IimYdRR-qqiH)yR5d*})}m z`?vL|CtKh0ck=K$^}kE?@1tT{wuU!M9J|dJTTgh0y~!<>&a&}@i^PKDw=QKk_mO148k(ujE_RS<(PUJYUVI9Fmlau?r~Z&^K=30ixu^S z3`e!EoSy%WEsW<9Yrqn}X2u2lS?i($8bue>Zrb17`zlC$*TdPmE&JIfa5Cr}<8^Rz z$dtEjSanw5SMUbO*_I2QGc3FOW}A$~fjiBYtrF7yl_-b`Y;<1c{59BNB|}$^-&3t4 zyBU5imdKI;Vst04103%>V!Z delta 569 zcmey%znFi5gfs^;0|P_Gz7I-3iY>q=#1%;Y|NnpK&G~mH28c7U-kg}}&!{kQSN>!j zMyJiij9-~1=d&!7(YI5usmLwx^|kWIEH23}ss!?jQqrt~Qw!}Td$69Z2Px2pi2#}C ziotR&sl~}fnFS@8`FSwqK`w3}W`z|{LvCtGB9LQWQIMFNom!%hnwMg$RHS5YXE%4t z0R{%fI8PVHkch)?XLR!&Qs8m*|NnovubR_ouBJ`r>W_<^-FD=w+&eFBC3X2226<+O z?-p_leo6rhw|oEpWMmL-s$l4O!Okg=ld>|y*8Ty7=GqAOmP#r_(0}IpH~H= z53|ptjZzA#Wn72j8YbSZnZd6Md9nT+5Rr(SumCB;Q{Rp6TdSUa4UF4xG}t7>ZrcTD3IHzmT}gp z+Pcd zYZ-)h@jh7VGmT+|r9ImL?dU}oXkrghb7(7*O7r?V?XzwL{=c5Ex;$l z_5c6>KxXKn%XL7Cv%n*=n1O-s5C}7hYIrp;Ffh-b=;hDE%QbOU0W&X`!elN+CsrV> zps?A8aS0Gd~Y34iODy-7)iZ-yW^cKASs1s%!TYpTt@Q=^YUv z71E3s9z|MqZhqO+z!AgXaF0!9k{Cmc{DWD~b_;&8dr+eQS#MaGCx|y}NpR<3 zP(OIptDbd(nU`bt>72mw1I;@H7~7rAS!;IkH!NV>`*+nn;fhk{fOoRD7(tYR5DGNX3G}xo6A4`&apd}80Y#kUU=8jr}l|)!F2rx-0IKMfN{^@>FVdQ I&MBb@00G9?xc~qF delta 551 zcmV+?0@(e73+4)t7%~V00000bc2Ow+000?uMObuGZ)S9NVRB^vL1b@YWgtmyVP|Dh zWnpA_ami&o000Aok!eo>Ad$LolPv*4vvdKZ0h1pCp?@zvAUrQ}WM(=fFD zZ*D#yJTG!&W;#+tMm``sFL*k5ZE$U6bYVUqJU@7FVPk7$bRcDJWIZBsB0oL}eh47| z000J1OjJex|Nj6009C@%egFUgW=TXrRCwCtn2ip@AP9u--v6bSt<$Lkg{pKvW?7nQ zxvwaQ1%F~1i-z#lh(Yq%H(_gqvV83gECM*!&j&z5tqP0KouFdS##onB48q zUkUJZKc#Xg?L7HPf}Quk!5{z>)07x9@WLp_=6?kM;`x_H%>huPI_z};n3l!d6m$~- zbbw3tf*RmuU}6gB>aPF_FH}TJ&jZxYB~56a190kt3c=ZHlymlHutNY;m9%BapX#+?Pm2=7!0A+&c)!6Oo z=~z#K4?zC9GRm{d0Elv@P~)?H_fhPw02o6CH~_$P pEZ_$H6MOBI0I)7?qq&;^0|4Z21;)FNg7E+V002ovPDHLkV1nMUU}oXkrghb7(7*O7r?V?XzwL{=c5Ex;$l z_5c6>KxXKn%XL7Cv%n*=n1O-s5C}7hYIrp;Ffh-b=;hDE%QbOU0W&X`!elN+CsrV> zps?A8aS0Gd~Y34iOD+9 zf0HHJC8H14qjK5W)do3KJdj%!Q&RggW877sQGTozB4}j`kuj#_1p&zg}wr&gcZ{N zI0cwff@PQ{`26%}R1Mt4B*yqF^cl}(uSNN9`1h=F*dNUx)?qZ$bluU&0EPgD1}o_# zrX3v1>ZeIH*liYA@@m(jyaxSmPE0J#i5!0n`WjX=x<3!CR$JiX9Fiw&#bCm;hM|f< zV_n`&u>#$rKR9oS34CYL5I@1n7}MEU;Sjs>0_&B3D*m%iZfauEe7e|D){Q~qd7olu5+Z>X@>H4PuH#$#hsOG3V$Z7UH8dXogv*L zy5ZEmdd}Gldour*{FAycM_YoSO#emA?ef_SheRB{FcyUBXO=|lJ_n3?22WQ%mvv4F FO#pDe(sBR* delta 545 zcmV++0^a?C3*ZWn7%~V00000bc2Ow+000?uMObuGZ)S9NVRB^vL1b@YWgtmyVP|Dh zWnpA_ami&o000Aok!eo>Ad$LolPv*4vvdKZ0h1pCp?@zvAUrQ}WM(=fFD zZ*D#yJTG!&W;#+tMm``sFL*k5ZE$U6bYVUqJU@7FVPk7$bRcDJWIZBsB0oM(r97wr z000J1OjJex|Nj6009C@%egFUgU`a$lRCwCtn2QdBAPhx&|No~KryyWKN+0e)mcl00jdy=@o$z5*z`P!XBl2hhKlG@-c$Ak-&x2v)DMQvFLVtUGpu>T~_Fjeg%w z_5ZnUwbwz(JAsvkkPX|Ww%h>Rd2tyaJ>Vw9buIA(z~x9~)pAc^2tdF9fKvV_)osr} zl~Sn~0SuO!0r0PRF8-dWXe$7qvScywDJiIy0K7%>D40r=^X-HVz~FZLH~=jN!MMUW jzXCRNfP%mP;3dESp_c`?`EG1V00000NkvXXu0mjfVa42_ diff --git a/assets/dolphin/internal/L1_NoSd_128x49/frame_2.png b/assets/dolphin/internal/L1_NoSd_128x49/frame_2.png index 12dc13430908844d89e0c7a002030cdc4999124b..5b474fff262306ecccbdc2a0ace273bd26e55219 100644 GIT binary patch delta 588 zcmaFL)xkYMB9Midfq~&c+a3)d#aJBV?!>U}oXkrghb7(7*O7r?V?XzwL{=c5Ex;$l z_5c6>KxXKn%XL7Cv%n*=n1O-s5C}7hYIrp;Ffh-b=;hDE%QbOU0W&X`!elN+CsrV> zps?A8aS0Gd~Y34iODv^(Izmxfoo%Z3S(t+2?qoBLQV~aLI#7j;5jElsuK4x{$WbkrF~__S?SyQ z4Xv$nD`T=F?l_n+eD*4}o3*)*;cTVZ&nds`8h%zWE%E-i*H6c~{A-oBtp0-CE~X9k zT!Gfn`<94*+8YqlaIVk2q0mO>u*F<$8OCPK^Y318J6LGRIdI6=gzKB#_`MI5Y#2OU L{an^LB{Ts5>L%C3 delta 550 zcmV+>0@?kD3*`!s7%~V00000bc2Ow+000?uMObuGZ)S9NVRB^vL1b@YWgtmyVP|Dh zWnpA_ami&o000Aok!eo>Ad$LolPv*4vvdKZ0h1pCp?@zvAUrQ}WM(=fFD zZ*D#yJTG!&W;#+tMm``sFL*k5ZE$U6bYVUqJU@7FVPk7$bRcDJWIZBsB0oML+;Z^% z000J1OjJex|Nj6009C@%egFUgWl2OqRCwC#n2Qd>AP7ad|Nqm=Qf+;Jq5|2LnPus! z<(>+HlYc0tp=cD|8qsJ5PA(8Zl4|Z_(9ZOAU4Rn-l$~!8@dGFQvfBv&>H$6gppOT* zhqwTMRsbAU0Tu>%0D!&%fPNQX?WsRN9Dral3g9x<+XRIHWDG3m+W}n0I-@gvOMt8n zaB+b&BwcZ^=QDub1(nK$_a1;7*fa>!TM`2RN`GHB#2Ekv>zzgb$YF8j0xV{s3=okC z(RSAWkAVXh%=IAw6thqf+317QXa2fAKehl=`VdM%wPg1IR`t7FSnt?j>3=o~N_}p> zY*mP*g;2dew_CNVgFCws1Av*0Wq=75xB_GefV#G_5teT2W^fZAUqCMVN(UYS5TvqO zI#(aIF`xlxHW_5P3Of53#NpHrI{^GK7k~U@z6y#o$sV@#2M-hlAlM^6fMGt;=h4j& om?U^|JQ>#kcpg9-0=xtm0DO@JvS@mv#Q*>R07*qoM6N<$g7Hz*umAu6 diff --git a/assets/dolphin/internal/L1_NoSd_128x49/frame_3.png b/assets/dolphin/internal/L1_NoSd_128x49/frame_3.png index f17f8713d34b7702ad58def6cf332b1770412808..952f968fb673a5de24fefb834e6c8655cc29a6d4 100644 GIT binary patch delta 587 zcmaFF)y_RZB9Midfq~&c+a3)d#aJBV?!>U}oXkrghb7(7*O7r?V?XzwL{=c5Ex;$l z_5c6>KxXKn%XL7Cv%n*=n1O-s5C}7hYIrp;Ffh-b=;hDE%QbOU0W&X`!elN+CsrV> zps?A8aS0Gd~Y34iOD!g+4&{$9uXshIkymJ1uh3VFezSm(p9_ z)!&?WBpwptZfZ+(^0SUjtyk(cTKl47|Yk0XXAgSzR zb~yt#x3x!1_DR=AtTVz_eJ*`=GxU&o!?Qix-qkRC{>tFsCH3#o^V=Fv&fFDAmR(-$ zP~Q29^B{M^pDTYiU3-xXHjCsf*;S(Qsp`MB3A-}~ky{o^?wipBB OpTX1B&t;ucLK6VtnBzwP delta 547 zcmV+;0^I$F3*rip7%~V00000bc2Ow+000?uMObuGZ)S9NVRB^vL1b@YWgtmyVP|Dh zWnpA_ami&o000Aok!eo>Ad$LolPv*4vvdKZ0h1pCp?@zvAUrQ}WM(=fFD zZ*D#yJTG!&W;#+tMm``sFL*k5ZE$U6bYVUqJU@7FVPk7$bRcDJWIZBsB0oN4z+l$^ z000J1OjJex|Nj6009C@%egFUgVo5|nRCwC#m;nxhAPhzO?tkfJ5fr2dg+jJv#26>6 z|6xn1iht~BBu%!ro9J!^O>QGXP3m@^hc>6**9D6R=&0s#360Qp@2+rRz*`v58?cL5y6N}I?wfQW%X-w)t0Rv2yRdjdpt z(BgtHBwT5*^G|@%1)0i$_a49r>>7k&mZAV4;eT{O%mMJQ-sK5^7#4Fbz?g+1z>ZAF zy*mfE3{1G7txpXgc?ubkk3O2d^6Gm17y{_hr?M4fOLh#vs$b;7ykf`G|7sMZ`dYp0 zwGr1ALN7P^ lpO$=HP6xC7PXN3G7yx#e1*^`5DHH$z002ovPDHLkV1mgA;jjPz diff --git a/assets/dolphin/internal/L1_NoSd_128x49/frame_4.png b/assets/dolphin/internal/L1_NoSd_128x49/frame_4.png index 7a6992a2b04cdaf35abb8c23f269293e143a4ea0..2bb43b306f3bf9025ba68190fb4903cc5a132133 100644 GIT binary patch delta 573 zcmcb^^^;M1%fy~fDm+OEOXMsm#F#`kNArNL1)$nRyU|^m<(aWESmuupz0%l$=g~?ouPOLy$ zL1D8G;}S-8FjGNc@&~4c^$PlS5U!1WQA(PXOKNd)QD#9&W_})693mRz;s)f|=vP=( z3GX z?|;K45}z%!CFpnd%qu-752SNv`LMGECR^2S72>k&jo)(aQFL@wwWL&=wfvcc^9*_g zsw*$N=4}vt@1-87e^Vf!L7HL7dPcQO<^%5;S82w%EqTxQ?ZjdJTC;trFl$Ns3O1XL@Pk`6ubuwKq0WDbz=F?@ x#S-4|pEmM)FWT2|aB6k>7gmMbNR|ng_&I;+l{b0*l?BE)gQu&X%Q~loCIB#J)NKF& delta 541 zcmV+&0^Ad$LolPv*4vvdKZ0h1pCp?@zvAUrQ}WM(=fFD zZ*D#yJTG!&W;#+tMm``sFL*k5ZE$U6bYVUqJU@7FVPk7$bRcDJWIZBsB0oL=qvEvy z000J1OjJex|Nj6009C@%egFUgTuDShRCwC#n2Qd>AP7ad|Nqm=nrTr{QGx8`mZqyM z_e>C}M1L_2tWkKYM57rbxj=-TRCOJLcBZfE0-Ok-?0kxJzi`s$-A({d-{1oPdV7F; ziwgi~1;AkzVBsbY0MJJO(DwqYJ@p6pc>qu}KvkoKk3ea3rf&xTmiO8KNGnf1L+UaF z5dTzh1s4M7`n<-*&IR(I-UIMDWQa3>!5GjG0Dmbg&Rl@SB#;0iW&!H%65uv);DWh6 zdH_6Bu=^s1sW`^3uJ?~E0F^#EO98vI`zpJt-{iuwVu$TAAVdMzm+EC3{d)b8^&wTO zHaT#&7%%{s*;odcUs0ZWtOW?Q@QKv4i4d*lZ&%t!h>x)}nK1W%4T<2nG( f4QNAvhX4Zr1eOJu%LfM}00000NkvXXu0mjf9sAk! diff --git a/assets/dolphin/internal/L1_NoSd_128x49/frame_5.png b/assets/dolphin/internal/L1_NoSd_128x49/frame_5.png index 00d9843007e85a247de0c23257c310512b5f71f5..d7f8c6402a27c5a2eb565d12b595a3a438ed94ee 100644 GIT binary patch delta 584 zcmaFJ)xteNB9Midfq~&c+a3)d#aJBV?!>U}oXkrghb7(7*O7r?V?XzwL{=c5Ex;$l z_5c6>KxXKn%XL7Cv%n*=n1O-s5C}7hYIrp;Ffh-b=;hDE%QbOU0W&X`!elN+CsrV> zps?A8aS0Gd~Y34iODz$gZO)!`%nnn~{g8^b441&v9d6lX$2%~n|wpQUNDi>ERVTD;TVg~x&%=k2Q!AubA1(z zAx*2N)^jGDkLIu{+3kCmaeoB2xPqijTf^-4JSU_+l*FA)XNs2GRHXlcIYH=wbOQf^ zCpXvLW_lHJ#`(pzhCN~$sm(kb2Nc?FF`F<*{B>>aUYRR@`DFq7fs*hQn)CgqTQ^K^ zom)98JK~Om9K*b+vud`aS|uyuqh zX6oPe`*r@3B|}Ns9j2bgiIK-%#ysSZ@Yz}J9?y`n+Lb}cz9mQhq<7|ZMPTGJc)I$z JtaD0e0syY@+hza& delta 546 zcmV+-0^R+D3*ico7%~V00000bc2Ow+000?uMObuGZ)S9NVRB^vL1b@YWgtmyVP|Dh zWnpA_ami&o000Aok!eo>Ad$LolPv*4vvdKZ0h1pCp?@zvAUrQ}WM(=fFD zZ*D#yJTG!&W;#+tMm``sFL*k5ZE$U6bYVUqJU@7FVPk7$bRcDJWIZBsB0oNIez$f2 z000J1OjJex|Nj6009C@%egFUgVM#AP7ad|Nqm=)@jv(q5|E+G)*%N z_e=#5qJNl%qEUEjM57rvxj+O-s`(wAwx_S_0-Ok-?0kxd51jPNZYKb!2lxPhJ{}+s zaRC6$062I7CI)!`fW88Neir~8sT%=a?#7sm0yu0jHva_xCqR=8P%uCfUJ*D0NYg!` zZwc_8esJkfI(hOJ2fIH52Ll68EK_34z=mFs&3`ihp!X|}+5-Sfb=d0yAZ?4iCFlkM zk^ycx3u=JVz{V0_s=op#W}zZ7y$?XXmo%=q1~92l5C~SUva|Y^SXgK52-WBEWgC6j zPWAt}Y}GCkSV;&C07f>Z0R~u1nA=H-ne?eSTg8B{4dsgf=>y`iuU)`R0D>6s%VXYl z4^wCWnk0ivz2IUWoqaf5hZO+6%*B_V%)OuplWei=T6mx+0Kp#l0d(V$K8`MKfkA*L k$I7@4z_S2t2=EYK0A@u6zLP8OIRF3v07*qoM6N<$g6II$yZ`_I From e12958d408dfe0e1571b60451bf73b1d41914981 Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Sun, 29 Jan 2023 15:08:26 +0400 Subject: [PATCH 363/824] [FL-3082] WS: add protocol LaCrosse-TX (TFA Dostmann) (#2292) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * WS: add protocol LaCrosse-TX (TFA Dostmann) * WS: fix syntax * WS: fix MSG_TYPE * WS: fix PVS Co-authored-by: あく --- .../helpers/weather_station_types.h | 2 +- .../weather_station/protocols/lacrosse_tx.c | 329 ++++++++++++++++++ .../weather_station/protocols/lacrosse_tx.h | 79 +++++ .../protocols/protocol_items.c | 1 + .../protocols/protocol_items.h | 1 + 5 files changed, 411 insertions(+), 1 deletion(-) create mode 100644 applications/plugins/weather_station/protocols/lacrosse_tx.c create mode 100644 applications/plugins/weather_station/protocols/lacrosse_tx.h diff --git a/applications/plugins/weather_station/helpers/weather_station_types.h b/applications/plugins/weather_station/helpers/weather_station_types.h index d512251f172..1f5612e2e9a 100644 --- a/applications/plugins/weather_station/helpers/weather_station_types.h +++ b/applications/plugins/weather_station/helpers/weather_station_types.h @@ -3,7 +3,7 @@ #include #include -#define WS_VERSION_APP "0.6.1" +#define WS_VERSION_APP "0.7" #define WS_DEVELOPED "SkorP" #define WS_GITHUB "https://github.com/flipperdevices/flipperzero-firmware" diff --git a/applications/plugins/weather_station/protocols/lacrosse_tx.c b/applications/plugins/weather_station/protocols/lacrosse_tx.c new file mode 100644 index 00000000000..8d8a24e244a --- /dev/null +++ b/applications/plugins/weather_station/protocols/lacrosse_tx.c @@ -0,0 +1,329 @@ +#include "lacrosse_tx.h" + +#define TAG "WSProtocolLaCrosse_TX" + +/* + * Help + * https://github.com/merbanan/rtl_433/blob/master/src/devices/lacrosse.c + * + * + * LaCrosse TX 433 Mhz Temperature and Humidity Sensors. + * - Tested: TX-7U and TX-6U (Temperature only) + * - Not Tested but should work: TX-3, TX-4 + * - also TFA Dostmann 30.3120.90 sensor (for e.g. 35.1018.06 (WS-9015) station) + * - also TFA Dostmann 30.3121 sensor + * Protocol Documentation: http://www.f6fbb.org/domo/sensors/tx3_th.php + * Message is 44 bits, 11 x 4 bit nybbles: + * [00] [cnt = 10] [type] [addr] [addr + parity] [v1] [v2] [v3] [iv1] [iv2] [check] + * Notes: + * - Zero Pulses are longer (1,400 uS High, 1,000 uS Low) = 2,400 uS + * - One Pulses are shorter ( 550 uS High, 1,000 uS Low) = 1,600 uS + * - Sensor id changes when the battery is changed + * - Primary Value are BCD with one decimal place: vvv = 12.3 + * - Secondary value is integer only intval = 12, seems to be a repeat of primary + * This may actually be an additional data check because the 4 bit checksum + * and parity bit is pretty week at detecting errors. + * - Temperature is in Celsius with 50.0 added (to handle negative values) + * - Humidity values appear to be integer precision, decimal always 0. + * - There is a 4 bit checksum and a parity bit covering the three digit value + * - Parity check for TX-3 and TX-4 might be different. + * - Msg sent with one repeat after 30 mS + * - Temperature and humidity are sent as separate messages + * - Frequency for each sensor may be could be off by as much as 50-75 khz + * - LaCrosse Sensors in other frequency ranges (915 Mhz) use FSK not OOK + * so they can't be decoded by rtl_433 currently. + * - Temperature and Humidity are sent in different messages bursts. +*/ + +#define LACROSSE_TX_GAP 1000 +#define LACROSSE_TX_BIT_SIZE 44 +#define LACROSSE_TX_SUNC_PATTERN 0x0A000000000 +#define LACROSSE_TX_SUNC_MASK 0x0F000000000 +#define LACROSSE_TX_MSG_TYPE_TEMP 0x00 +#define LACROSSE_TX_MSG_TYPE_HUM 0x0E + +static const SubGhzBlockConst ws_protocol_lacrosse_tx_const = { + .te_short = 550, + .te_long = 1300, + .te_delta = 120, + .min_count_bit_for_found = 40, +}; + +struct WSProtocolDecoderLaCrosse_TX { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + WSBlockGeneric generic; + + uint16_t header_count; +}; + +struct WSProtocolEncoderLaCrosse_TX { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + WSBlockGeneric generic; +}; + +typedef enum { + LaCrosse_TXDecoderStepReset = 0, + LaCrosse_TXDecoderStepCheckPreambule, + LaCrosse_TXDecoderStepSaveDuration, + LaCrosse_TXDecoderStepCheckDuration, +} LaCrosse_TXDecoderStep; + +const SubGhzProtocolDecoder ws_protocol_lacrosse_tx_decoder = { + .alloc = ws_protocol_decoder_lacrosse_tx_alloc, + .free = ws_protocol_decoder_lacrosse_tx_free, + + .feed = ws_protocol_decoder_lacrosse_tx_feed, + .reset = ws_protocol_decoder_lacrosse_tx_reset, + + .get_hash_data = ws_protocol_decoder_lacrosse_tx_get_hash_data, + .serialize = ws_protocol_decoder_lacrosse_tx_serialize, + .deserialize = ws_protocol_decoder_lacrosse_tx_deserialize, + .get_string = ws_protocol_decoder_lacrosse_tx_get_string, +}; + +const SubGhzProtocolEncoder ws_protocol_lacrosse_tx_encoder = { + .alloc = NULL, + .free = NULL, + + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol ws_protocol_lacrosse_tx = { + .name = WS_PROTOCOL_LACROSSE_TX_NAME, + .type = SubGhzProtocolWeatherStation, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable, + + .decoder = &ws_protocol_lacrosse_tx_decoder, + .encoder = &ws_protocol_lacrosse_tx_encoder, +}; + +void* ws_protocol_decoder_lacrosse_tx_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + WSProtocolDecoderLaCrosse_TX* instance = malloc(sizeof(WSProtocolDecoderLaCrosse_TX)); + instance->base.protocol = &ws_protocol_lacrosse_tx; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void ws_protocol_decoder_lacrosse_tx_free(void* context) { + furi_assert(context); + WSProtocolDecoderLaCrosse_TX* instance = context; + free(instance); +} + +void ws_protocol_decoder_lacrosse_tx_reset(void* context) { + furi_assert(context); + WSProtocolDecoderLaCrosse_TX* instance = context; + instance->header_count = 0; + instance->decoder.parser_step = LaCrosse_TXDecoderStepReset; +} + +static bool ws_protocol_lacrosse_tx_check_crc(WSProtocolDecoderLaCrosse_TX* instance) { + if(!instance->decoder.decode_data) return false; + uint8_t msg[] = { + (instance->decoder.decode_data >> 36) & 0x0F, + (instance->decoder.decode_data >> 32) & 0x0F, + (instance->decoder.decode_data >> 28) & 0x0F, + (instance->decoder.decode_data >> 24) & 0x0F, + (instance->decoder.decode_data >> 20) & 0x0F, + (instance->decoder.decode_data >> 16) & 0x0F, + (instance->decoder.decode_data >> 12) & 0x0F, + (instance->decoder.decode_data >> 8) & 0x0F, + (instance->decoder.decode_data >> 4) & 0x0F}; + + uint8_t crc = subghz_protocol_blocks_add_bytes(msg, 9); + return ((crc & 0x0F) == ((instance->decoder.decode_data) & 0x0F)); +} + +/** + * Analysis of received data + * @param instance Pointer to a WSBlockGeneric* instance + */ +static void ws_protocol_lacrosse_tx_remote_controller(WSBlockGeneric* instance) { + uint8_t msg_type = (instance->data >> 32) & 0x0F; + instance->id = (((instance->data >> 28) & 0x0F) << 3) | (((instance->data >> 24) & 0x0F) >> 1); + + float msg_value = (float)((instance->data >> 20) & 0x0F) * 10.0f + + (float)((instance->data >> 16) & 0x0F) + + (float)((instance->data >> 12) & 0x0F) * 0.1f; + + if(msg_type == LACROSSE_TX_MSG_TYPE_TEMP) { //-V1051 + instance->temp = msg_value - 50.0f; + instance->humidity = WS_NO_HUMIDITY; + } else if(msg_type == LACROSSE_TX_MSG_TYPE_HUM) { + //ToDo for verification, records are needed with sensors maintaining temperature and temperature for this standard + instance->humidity = (uint8_t)msg_value; + } else { + furi_crash("WS: WSProtocolLaCrosse_TX incorrect msg_type."); + } + + instance->btn = WS_NO_BTN; + instance->battery_low = WS_NO_BATT; + instance->channel = WS_NO_CHANNEL; +} + +void ws_protocol_decoder_lacrosse_tx_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + WSProtocolDecoderLaCrosse_TX* instance = context; + + switch(instance->decoder.parser_step) { + case LaCrosse_TXDecoderStepReset: + if((!level) && (DURATION_DIFF(duration, LACROSSE_TX_GAP) < + ws_protocol_lacrosse_tx_const.te_delta * 2)) { + instance->decoder.parser_step = LaCrosse_TXDecoderStepCheckPreambule; + instance->header_count = 0; + } + break; + + case LaCrosse_TXDecoderStepCheckPreambule: + + if(level) { + if((DURATION_DIFF(duration, ws_protocol_lacrosse_tx_const.te_short) < + ws_protocol_lacrosse_tx_const.te_delta) && + (instance->header_count > 1)) { + instance->decoder.parser_step = LaCrosse_TXDecoderStepCheckDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->decoder.te_last = duration; + } else if(duration > (ws_protocol_lacrosse_tx_const.te_long * 2)) { + instance->decoder.parser_step = LaCrosse_TXDecoderStepReset; + } + } else { + if(DURATION_DIFF(duration, LACROSSE_TX_GAP) < + ws_protocol_lacrosse_tx_const.te_delta * 2) { + instance->decoder.te_last = duration; + instance->header_count++; + } else { + instance->decoder.parser_step = LaCrosse_TXDecoderStepReset; + } + } + + break; + + case LaCrosse_TXDecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = LaCrosse_TXDecoderStepCheckDuration; + } else { + instance->decoder.parser_step = LaCrosse_TXDecoderStepReset; + } + break; + + case LaCrosse_TXDecoderStepCheckDuration: + + if(!level) { + if(duration > LACROSSE_TX_GAP * 3) { + if(DURATION_DIFF( + instance->decoder.te_last, ws_protocol_lacrosse_tx_const.te_short) < + ws_protocol_lacrosse_tx_const.te_delta) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = LaCrosse_TXDecoderStepSaveDuration; + } else if( + DURATION_DIFF( + instance->decoder.te_last, ws_protocol_lacrosse_tx_const.te_long) < + ws_protocol_lacrosse_tx_const.te_delta) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = LaCrosse_TXDecoderStepSaveDuration; + } + if((instance->decoder.decode_data & LACROSSE_TX_SUNC_MASK) == + LACROSSE_TX_SUNC_PATTERN) { + if(ws_protocol_lacrosse_tx_check_crc(instance)) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = LACROSSE_TX_BIT_SIZE; + ws_protocol_lacrosse_tx_remote_controller(&instance->generic); + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + } + + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->header_count = 0; + instance->decoder.parser_step = LaCrosse_TXDecoderStepReset; + break; + } else if( + (DURATION_DIFF(instance->decoder.te_last, ws_protocol_lacrosse_tx_const.te_short) < + ws_protocol_lacrosse_tx_const.te_delta) && + (DURATION_DIFF(duration, LACROSSE_TX_GAP) < + ws_protocol_lacrosse_tx_const.te_delta * 2)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = LaCrosse_TXDecoderStepSaveDuration; + } else if( + (DURATION_DIFF(instance->decoder.te_last, ws_protocol_lacrosse_tx_const.te_long) < + ws_protocol_lacrosse_tx_const.te_delta) && + (DURATION_DIFF(duration, LACROSSE_TX_GAP) < + ws_protocol_lacrosse_tx_const.te_delta * 2)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = LaCrosse_TXDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = LaCrosse_TXDecoderStepReset; + } + + } else { + instance->decoder.parser_step = LaCrosse_TXDecoderStepReset; + } + + break; + } +} + +uint8_t ws_protocol_decoder_lacrosse_tx_get_hash_data(void* context) { + furi_assert(context); + WSProtocolDecoderLaCrosse_TX* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool ws_protocol_decoder_lacrosse_tx_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + WSProtocolDecoderLaCrosse_TX* instance = context; + return ws_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +bool ws_protocol_decoder_lacrosse_tx_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + WSProtocolDecoderLaCrosse_TX* instance = context; + bool ret = false; + do { + if(!ws_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if(instance->generic.data_count_bit != + ws_protocol_lacrosse_tx_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + ret = true; + } while(false); + return ret; +} + +void ws_protocol_decoder_lacrosse_tx_get_string(void* context, FuriString* output) { + furi_assert(context); + WSProtocolDecoderLaCrosse_TX* instance = context; + furi_string_printf( + output, + "%s %dbit\r\n" + "Key:0x%lX%08lX\r\n" + "Sn:0x%lX Ch:%d Bat:%d\r\n" + "Temp:%3.1f C Hum:%d%%", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data >> 32), + (uint32_t)(instance->generic.data), + instance->generic.id, + instance->generic.channel, + instance->generic.battery_low, + (double)instance->generic.temp, + instance->generic.humidity); +} diff --git a/applications/plugins/weather_station/protocols/lacrosse_tx.h b/applications/plugins/weather_station/protocols/lacrosse_tx.h new file mode 100644 index 00000000000..e884556897e --- /dev/null +++ b/applications/plugins/weather_station/protocols/lacrosse_tx.h @@ -0,0 +1,79 @@ +#pragma once + +#include + +#include +#include +#include +#include "ws_generic.h" +#include + +#define WS_PROTOCOL_LACROSSE_TX_NAME "LaCrosse_TX" + +typedef struct WSProtocolDecoderLaCrosse_TX WSProtocolDecoderLaCrosse_TX; +typedef struct WSProtocolEncoderLaCrosse_TX WSProtocolEncoderLaCrosse_TX; + +extern const SubGhzProtocolDecoder ws_protocol_lacrosse_tx_decoder; +extern const SubGhzProtocolEncoder ws_protocol_lacrosse_tx_encoder; +extern const SubGhzProtocol ws_protocol_lacrosse_tx; + +/** + * Allocate WSProtocolDecoderLaCrosse_TX. + * @param environment Pointer to a SubGhzEnvironment instance + * @return WSProtocolDecoderLaCrosse_TX* pointer to a WSProtocolDecoderLaCrosse_TX instance + */ +void* ws_protocol_decoder_lacrosse_tx_alloc(SubGhzEnvironment* environment); + +/** + * Free WSProtocolDecoderLaCrosse_TX. + * @param context Pointer to a WSProtocolDecoderLaCrosse_TX instance + */ +void ws_protocol_decoder_lacrosse_tx_free(void* context); + +/** + * Reset decoder WSProtocolDecoderLaCrosse_TX. + * @param context Pointer to a WSProtocolDecoderLaCrosse_TX instance + */ +void ws_protocol_decoder_lacrosse_tx_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a WSProtocolDecoderLaCrosse_TX instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void ws_protocol_decoder_lacrosse_tx_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a WSProtocolDecoderLaCrosse_TX instance + * @return hash Hash sum + */ +uint8_t ws_protocol_decoder_lacrosse_tx_get_hash_data(void* context); + +/** + * Serialize data WSProtocolDecoderLaCrosse_TX. + * @param context Pointer to a WSProtocolDecoderLaCrosse_TX instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool ws_protocol_decoder_lacrosse_tx_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data WSProtocolDecoderLaCrosse_TX. + * @param context Pointer to a WSProtocolDecoderLaCrosse_TX instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool ws_protocol_decoder_lacrosse_tx_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a WSProtocolDecoderLaCrosse_TX instance + * @param output Resulting text + */ +void ws_protocol_decoder_lacrosse_tx_get_string(void* context, FuriString* output); diff --git a/applications/plugins/weather_station/protocols/protocol_items.c b/applications/plugins/weather_station/protocols/protocol_items.c index 99c8344f491..2c9d751c7ef 100644 --- a/applications/plugins/weather_station/protocols/protocol_items.c +++ b/applications/plugins/weather_station/protocols/protocol_items.c @@ -8,6 +8,7 @@ const SubGhzProtocol* weather_station_protocol_registry_items[] = { &ws_protocol_gt_wt_03, &ws_protocol_acurite_606tx, &ws_protocol_acurite_609txc, + &ws_protocol_lacrosse_tx, &ws_protocol_lacrosse_tx141thbv2, &ws_protocol_oregon2, &ws_protocol_acurite_592txr, diff --git a/applications/plugins/weather_station/protocols/protocol_items.h b/applications/plugins/weather_station/protocols/protocol_items.h index 9d5d096f8f7..f9e443abcff 100644 --- a/applications/plugins/weather_station/protocols/protocol_items.h +++ b/applications/plugins/weather_station/protocols/protocol_items.h @@ -8,6 +8,7 @@ #include "gt_wt_03.h" #include "acurite_606tx.h" #include "acurite_609txc.h" +#include "lacrosse_tx.h" #include "lacrosse_tx141thbv2.h" #include "oregon2.h" #include "acurite_592txr.h" From a8e5f2250026c4260bb13fddf696acb7f9322b77 Mon Sep 17 00:00:00 2001 From: Angel Date: Sun, 29 Jan 2023 06:23:45 -0500 Subject: [PATCH 364/824] LF-RFID: add CRC calculation to paradox protocol (#2299) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Angel Co-authored-by: あく --- lib/lfrfid/protocols/protocol_paradox.c | 47 +++++++++++++++++++++---- 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/lib/lfrfid/protocols/protocol_paradox.c b/lib/lfrfid/protocols/protocol_paradox.c index 7e029f1de24..26c9b55dcd1 100644 --- a/lib/lfrfid/protocols/protocol_paradox.c +++ b/lib/lfrfid/protocols/protocol_paradox.c @@ -136,17 +136,45 @@ LevelDuration protocol_paradox_encoder_yield(ProtocolParadox* protocol) { return level_duration_make(level, duration); }; +static uint8_t protocol_paradox_calculate_checksum(uint8_t fc, uint16_t card_id) { + uint8_t card_hi = (card_id >> 8) & 0xff; + uint8_t card_lo = card_id & 0xff; + + uint8_t arr[5] = {0, 0, fc, card_hi, card_lo}; + + uint8_t manchester[9]; + + bit_lib_push_bit(manchester, 9, false); + bit_lib_push_bit(manchester, 9, false); + bit_lib_push_bit(manchester, 9, false); + bit_lib_push_bit(manchester, 9, false); + + for(uint8_t i = 6; i < 40; i += 1) { + if(bit_lib_get_bit(arr, i) == 0b1) { + bit_lib_push_bit(manchester, 9, true); + bit_lib_push_bit(manchester, 9, false); + } else { + bit_lib_push_bit(manchester, 9, false); + bit_lib_push_bit(manchester, 9, true); + } + } + + uint8_t output = bit_lib_crc8(manchester, 9, 0x31, 0x00, true, true, 0x06); + + return output; +} + void protocol_paradox_render_data(ProtocolParadox* protocol, FuriString* result) { uint8_t* decoded_data = protocol->data; uint8_t fc = bit_lib_get_bits(decoded_data, 10, 8); uint16_t card_id = bit_lib_get_bits_16(decoded_data, 18, 16); + uint8_t card_crc = bit_lib_get_bits_16(decoded_data, 34, 8); + uint8_t calc_crc = protocol_paradox_calculate_checksum(fc, card_id); furi_string_cat_printf(result, "Facility: %u\r\n", fc); furi_string_cat_printf(result, "Card: %u\r\n", card_id); - furi_string_cat_printf(result, "Data: "); - for(size_t i = 0; i < PARADOX_DECODED_DATA_SIZE; i++) { - furi_string_cat_printf(result, "%02X", decoded_data[i]); - } + furi_string_cat_printf(result, "CRC: %u Calc CRC: %u\r\n", card_crc, calc_crc); + if(card_crc != calc_crc) furi_string_cat_printf(result, "CRC Mismatch, Invalid Card!\r\n"); }; void protocol_paradox_render_brief_data(ProtocolParadox* protocol, FuriString* result) { @@ -154,8 +182,15 @@ void protocol_paradox_render_brief_data(ProtocolParadox* protocol, FuriString* r uint8_t fc = bit_lib_get_bits(decoded_data, 10, 8); uint16_t card_id = bit_lib_get_bits_16(decoded_data, 18, 16); - - furi_string_cat_printf(result, "FC: %03u, Card: %05u", fc, card_id); + uint8_t card_crc = bit_lib_get_bits_16(decoded_data, 34, 8); + uint8_t calc_crc = protocol_paradox_calculate_checksum(fc, card_id); + + furi_string_cat_printf(result, "FC: %03u, Card: %05u\r\n", fc, card_id); + if(calc_crc == card_crc) { + furi_string_cat_printf(result, "CRC : %03u", card_crc); + } else { + furi_string_cat_printf(result, "Card is Invalid!"); + } }; bool protocol_paradox_write_data(ProtocolParadox* protocol, void* data) { From b1496ee9bd97ff8c34a67a49ec4ecdc6cfbd9436 Mon Sep 17 00:00:00 2001 From: Milk-Cool <43724263+Milk-Cool@users.noreply.github.com> Date: Mon, 30 Jan 2023 10:54:15 +0300 Subject: [PATCH 365/824] Furi: getter for current thread stdout write callback (#2344) --- firmware/targets/f7/api_symbols.csv | 5 +++-- furi/core/thread.c | 6 ++++++ furi/core/thread.h | 6 ++++++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index a75e88bad8a..7b247daedd4 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,11.8,, +Version,+,11.9,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -1389,8 +1389,8 @@ Function,+,furi_hal_version_uid_size,size_t, Function,-,furi_hal_vibro_init,void, Function,+,furi_hal_vibro_on,void,_Bool Function,-,furi_init,void, -Function,+,furi_kernel_is_irq_or_masked,_Bool, Function,+,furi_kernel_get_tick_frequency,uint32_t, +Function,+,furi_kernel_is_irq_or_masked,_Bool, Function,+,furi_kernel_lock,int32_t, Function,+,furi_kernel_restore_lock,int32_t,int32_t Function,+,furi_kernel_unlock,int32_t, @@ -1515,6 +1515,7 @@ Function,+,furi_thread_get_name,const char*,FuriThreadId Function,+,furi_thread_get_return_code,int32_t,FuriThread* Function,+,furi_thread_get_stack_space,uint32_t,FuriThreadId Function,+,furi_thread_get_state,FuriThreadState,FuriThread* +Function,+,furi_thread_get_stdout_callback,FuriThreadStdoutWriteCallback, Function,+,furi_thread_is_suspended,_Bool,FuriThreadId Function,+,furi_thread_join,_Bool,FuriThread* Function,+,furi_thread_mark_as_service,void,FuriThread* diff --git a/furi/core/thread.c b/furi/core/thread.c index c966dd57257..ef9560b4a5a 100644 --- a/furi/core/thread.c +++ b/furi/core/thread.c @@ -530,6 +530,12 @@ bool furi_thread_set_stdout_callback(FuriThreadStdoutWriteCallback callback) { return true; } +FuriThreadStdoutWriteCallback furi_thread_get_stdout_callback() { + FuriThread* thread = furi_thread_get_current(); + + return thread->output.write_callback; +} + size_t furi_thread_stdout_write(const char* data, size_t size) { FuriThread* thread = furi_thread_get_current(); diff --git a/furi/core/thread.h b/furi/core/thread.h index c2f5a91303b..1542d5bf016 100644 --- a/furi/core/thread.h +++ b/furi/core/thread.h @@ -227,6 +227,12 @@ const char* furi_thread_get_name(FuriThreadId thread_id); uint32_t furi_thread_get_stack_space(FuriThreadId thread_id); +/** Get STDOUT callback for thead + * + * @return STDOUT callback + */ +FuriThreadStdoutWriteCallback furi_thread_get_stdout_callback(); + /** Set STDOUT callback for thread * * @param callback callback or NULL to clear From 5db7fdf9852ec4a412765e265cd10d39c02305b7 Mon Sep 17 00:00:00 2001 From: Noam Drong Date: Mon, 30 Jan 2023 09:03:10 +0100 Subject: [PATCH 366/824] Add support for `GUI-CTRL` in bad_usb (#2315) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- applications/main/bad_usb/bad_usb_script.c | 1 + 1 file changed, 1 insertion(+) diff --git a/applications/main/bad_usb/bad_usb_script.c b/applications/main/bad_usb/bad_usb_script.c index bbd721ed2c7..e2281133fa7 100644 --- a/applications/main/bad_usb/bad_usb_script.c +++ b/applications/main/bad_usb/bad_usb_script.c @@ -50,6 +50,7 @@ static const DuckyKey ducky_keys[] = { {"ALT-SHIFT", KEY_MOD_LEFT_ALT | KEY_MOD_LEFT_SHIFT}, {"ALT-GUI", KEY_MOD_LEFT_ALT | KEY_MOD_LEFT_GUI}, {"GUI-SHIFT", KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT}, + {"GUI-CTRL", KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_CTRL}, {"CTRL", KEY_MOD_LEFT_CTRL}, {"CONTROL", KEY_MOD_LEFT_CTRL}, From 7f3ebcd110e57193d903fed588920402952b0d0d Mon Sep 17 00:00:00 2001 From: Konstantin Volkov <72250702+doomwastaken@users.noreply.github.com> Date: Mon, 30 Jan 2023 15:59:45 +0700 Subject: [PATCH 367/824] Changed bench target, stlink serial and added error for testing the run (#2275) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * changed bench target, stlink serial and added error for testing the run * changed flipper name for macos and changed serial aquisition for device manager * tested broken pipeline, reverting test data * added timeout-minutes, testing if its int or float Co-authored-by: Konstantin Volkov Co-authored-by: あく --- .github/workflows/unit_tests.yml | 1 + .github/workflows/updater_test.yml | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index ac3fc368433..7e625229ab2 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -56,6 +56,7 @@ jobs: - name: 'Run units and validate results' id: run_units if: steps.copy.outcome == 'success' + timeout-minutes: 2.5 run: | source scripts/toolchain/fbtenv.sh python3 scripts/testing/units.py ${{steps.device.outputs.flipper}} diff --git a/.github/workflows/updater_test.yml b/.github/workflows/updater_test.yml index d4ca56fad7a..0b02920fa38 100644 --- a/.github/workflows/updater_test.yml +++ b/.github/workflows/updater_test.yml @@ -10,7 +10,7 @@ env: jobs: test_updater_on_bench: - runs-on: [self-hosted, FlipperZeroTest] # currently on same bench as units, needs different bench + runs-on: [self-hosted, FlipperZeroTestMac1] steps: - name: 'Decontaminate previous build leftovers' run: | @@ -27,7 +27,8 @@ jobs: - name: 'Get flipper from device manager (mock)' id: device run: | - echo "flipper=/dev/ttyACM0" >> $GITHUB_OUTPUT + echo "flipper=/dev/tty.usbmodemflip_Rekigyn1" >> $GITHUB_OUTPUT + echo "stlink=0F020D026415303030303032" >> $GITHUB_OUTPUT - name: 'Flashing target firmware' id: first_full_flash @@ -67,7 +68,7 @@ jobs: - name: 'Flash last release' if: failure() run: | - ./fbt flash OPENOCD_ADAPTER_SERIAL=2A0906016415303030303032 FORCE=1 + ./fbt flash OPENOCD_ADAPTER_SERIAL=${{steps.device.outputs.stlink}} FORCE=1 - name: 'Wait for flipper and format ext' if: failure() From d9be81588955812942c13eda2d58864fc282b4e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Such=C3=A1nek?= Date: Mon, 30 Jan 2023 10:14:30 +0100 Subject: [PATCH 368/824] Print card CID in storage info (#2227) --- applications/services/storage/storage_cli.c | 16 +++++++- .../scenes/storage_settings_scene_sd_info.c | 19 +++++++-- firmware/targets/f7/fatfs/stm32_adafruit_sd.c | 41 ++++++++++--------- firmware/targets/f7/fatfs/stm32_adafruit_sd.h | 21 +++++----- 4 files changed, 62 insertions(+), 35 deletions(-) diff --git a/applications/services/storage/storage_cli.c b/applications/services/storage/storage_cli.c index c83f164997a..eeaa7fce8fa 100644 --- a/applications/services/storage/storage_cli.c +++ b/applications/services/storage/storage_cli.c @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -60,17 +61,28 @@ static void storage_cli_info(Cli* cli, FuriString* path) { } } else if(furi_string_cmp_str(path, STORAGE_EXT_PATH_PREFIX) == 0) { SDInfo sd_info; + SD_CID sd_cid; FS_Error error = storage_sd_info(api, &sd_info); + BSP_SD_GetCIDRegister(&sd_cid); if(error != FSE_OK) { storage_cli_print_error(error); } else { printf( - "Label: %s\r\nType: %s\r\n%luKiB total\r\n%luKiB free\r\n", + "Label: %s\r\nType: %s\r\n%luKiB total\r\n%luKiB free\r\n" + "%02x%2.2s %5.5s %i.%i\r\nSN:%04lx %02i/%i\r\n", sd_info.label, sd_api_get_fs_type_text(sd_info.fs_type), sd_info.kb_total, - sd_info.kb_free); + sd_info.kb_free, + sd_cid.ManufacturerID, + sd_cid.OEM_AppliID, + sd_cid.ProdName, + sd_cid.ProdRev >> 4, + sd_cid.ProdRev & 0xf, + sd_cid.ProdSN, + sd_cid.ManufactMonth, + sd_cid.ManufactYear + 2000); } } else { storage_cli_print_usage(); diff --git a/applications/settings/storage_settings/scenes/storage_settings_scene_sd_info.c b/applications/settings/storage_settings/scenes/storage_settings_scene_sd_info.c index 0c398ed5b49..a7991bc193a 100644 --- a/applications/settings/storage_settings/scenes/storage_settings_scene_sd_info.c +++ b/applications/settings/storage_settings/scenes/storage_settings_scene_sd_info.c @@ -1,4 +1,5 @@ #include "../storage_settings.h" +#include static void storage_settings_scene_sd_info_dialog_callback(DialogExResult result, void* context) { StorageSettings* app = context; @@ -11,7 +12,10 @@ void storage_settings_scene_sd_info_on_enter(void* context) { DialogEx* dialog_ex = app->dialog_ex; SDInfo sd_info; + SD_CID sd_cid; FS_Error sd_status = storage_sd_info(app->fs_api, &sd_info); + BSP_SD_GetCIDRegister(&sd_cid); + scene_manager_set_scene_state(app->scene_manager, StorageSettingsSDInfo, sd_status); dialog_ex_set_context(dialog_ex, app); @@ -26,13 +30,22 @@ void storage_settings_scene_sd_info_on_enter(void* context) { } else { furi_string_printf( app->text_string, - "Label: %s\nType: %s\n%lu KiB total\n%lu KiB free", + "Label: %s\nType: %s\n%lu KiB total\n%lu KiB free\n" + "%02X%2.2s %5.5s %i.%i\nSN:%04lX %02i/%i", sd_info.label, sd_api_get_fs_type_text(sd_info.fs_type), sd_info.kb_total, - sd_info.kb_free); + sd_info.kb_free, + sd_cid.ManufacturerID, + sd_cid.OEM_AppliID, + sd_cid.ProdName, + sd_cid.ProdRev >> 4, + sd_cid.ProdRev & 0xf, + sd_cid.ProdSN, + sd_cid.ManufactMonth, + sd_cid.ManufactYear + 2000); dialog_ex_set_text( - dialog_ex, furi_string_get_cstr(app->text_string), 4, 4, AlignLeft, AlignTop); + dialog_ex, furi_string_get_cstr(app->text_string), 4, 1, AlignLeft, AlignTop); } view_dispatcher_switch_to_view(app->view_dispatcher, StorageSettingsViewDialogEx); diff --git a/firmware/targets/f7/fatfs/stm32_adafruit_sd.c b/firmware/targets/f7/fatfs/stm32_adafruit_sd.c index b9b65f06a43..998adee2997 100644 --- a/firmware/targets/f7/fatfs/stm32_adafruit_sd.c +++ b/firmware/targets/f7/fatfs/stm32_adafruit_sd.c @@ -779,25 +779,10 @@ uint8_t SD_GetCIDRegister(SD_CID* Cid) { Cid->ManufacturerID = CID_Tab[0]; /* Byte 1 */ - Cid->OEM_AppliID = CID_Tab[1] << 8; - - /* Byte 2 */ - Cid->OEM_AppliID |= CID_Tab[2]; + memcpy(Cid->OEM_AppliID, CID_Tab + 1, 2); /* Byte 3 */ - Cid->ProdName1 = CID_Tab[3] << 24; - - /* Byte 4 */ - Cid->ProdName1 |= CID_Tab[4] << 16; - - /* Byte 5 */ - Cid->ProdName1 |= CID_Tab[5] << 8; - - /* Byte 6 */ - Cid->ProdName1 |= CID_Tab[6]; - - /* Byte 7 */ - Cid->ProdName2 = CID_Tab[7]; + memcpy(Cid->ProdName, CID_Tab + 3, 5); /* Byte 8 */ Cid->ProdRev = CID_Tab[8]; @@ -815,11 +800,12 @@ uint8_t SD_GetCIDRegister(SD_CID* Cid) { Cid->ProdSN |= CID_Tab[12]; /* Byte 13 */ - Cid->Reserved1 |= (CID_Tab[13] & 0xF0) >> 4; - Cid->ManufactDate = (CID_Tab[13] & 0x0F) << 8; + Cid->Reserved1 = (CID_Tab[13] & 0xF0) >> 4; + Cid->ManufactYear = (CID_Tab[13] & 0x0F) << 4; /* Byte 14 */ - Cid->ManufactDate |= CID_Tab[14]; + Cid->ManufactYear |= (CID_Tab[14] & 0xF0) >> 4; + Cid->ManufactMonth = (CID_Tab[14] & 0x0F); /* Byte 15 */ Cid->CID_CRC = (CID_Tab[15] & 0xFE) >> 1; @@ -837,6 +823,21 @@ uint8_t SD_GetCIDRegister(SD_CID* Cid) { return retr; } +uint8_t BSP_SD_GetCIDRegister(SD_CID* Cid) { + uint8_t retr = BSP_SD_ERROR; + + /* Slow speed init */ + furi_hal_spi_acquire(&furi_hal_spi_bus_handle_sd_slow); + furi_hal_sd_spi_handle = &furi_hal_spi_bus_handle_sd_slow; + + memset(Cid, 0, sizeof(SD_CID)); + retr = SD_GetCIDRegister(Cid); + + furi_hal_sd_spi_handle = NULL; + furi_hal_spi_release(&furi_hal_spi_bus_handle_sd_slow); + return retr; +} + /** * @brief Sends 5 bytes command to the SD card and get response * @param Cmd: The user expected command to send to SD card. diff --git a/firmware/targets/f7/fatfs/stm32_adafruit_sd.h b/firmware/targets/f7/fatfs/stm32_adafruit_sd.h index e0c5e3beff4..a133c5922ba 100644 --- a/firmware/targets/f7/fatfs/stm32_adafruit_sd.h +++ b/firmware/targets/f7/fatfs/stm32_adafruit_sd.h @@ -133,16 +133,16 @@ typedef struct { * @brief Card Identification Data: CID Register */ typedef struct { - __IO uint8_t ManufacturerID; /* ManufacturerID */ - __IO uint16_t OEM_AppliID; /* OEM/Application ID */ - __IO uint32_t ProdName1; /* Product Name part1 */ - __IO uint8_t ProdName2; /* Product Name part2*/ - __IO uint8_t ProdRev; /* Product Revision */ - __IO uint32_t ProdSN; /* Product Serial Number */ - __IO uint8_t Reserved1; /* Reserved1 */ - __IO uint16_t ManufactDate; /* Manufacturing Date */ - __IO uint8_t CID_CRC; /* CID CRC */ - __IO uint8_t Reserved2; /* always 1 */ + uint8_t ManufacturerID; /* ManufacturerID */ + char OEM_AppliID[2]; /* OEM/Application ID */ + char ProdName[5]; /* Product Name */ + uint8_t ProdRev; /* Product Revision */ + uint32_t ProdSN; /* Product Serial Number */ + uint8_t Reserved1; /* Reserved1 */ + uint8_t ManufactYear; /* Manufacturing Year */ + uint8_t ManufactMonth; /* Manufacturing Month */ + uint8_t CID_CRC; /* CID CRC */ + uint8_t Reserved2; /* always 1 */ } SD_CID; /** @@ -207,6 +207,7 @@ uint8_t uint8_t BSP_SD_Erase(uint32_t StartAddr, uint32_t EndAddr); uint8_t BSP_SD_GetCardState(void); uint8_t BSP_SD_GetCardInfo(SD_CardInfo* pCardInfo); +uint8_t BSP_SD_GetCIDRegister(SD_CID* Cid); /* Link functions for SD Card peripheral*/ void SD_SPI_Slow_Init(void); From 01a9854f8af8898a9a1138f1e1f496ff74b51b8d Mon Sep 17 00:00:00 2001 From: Giacomo Ferretti Date: Mon, 30 Jan 2023 10:49:51 +0100 Subject: [PATCH 369/824] Documentation: add BadUSB GUI-CTRL #2347 --- documentation/file_formats/BadUsbScriptFormat.md | 1 + 1 file changed, 1 insertion(+) diff --git a/documentation/file_formats/BadUsbScriptFormat.md b/documentation/file_formats/BadUsbScriptFormat.md index fa503874293..2ef1d3135e5 100644 --- a/documentation/file_formats/BadUsbScriptFormat.md +++ b/documentation/file_formats/BadUsbScriptFormat.md @@ -67,6 +67,7 @@ Can be combined with a special key command or a single character. |ALT-SHIFT|ALT+SHIFT| |ALT-GUI|ALT+WIN| |GUI-SHIFT|WIN+SHIFT| +|GUI-CTRL|WIN+CTRL| ## String From 46fb86265c1a450eecdb05e0f1f49dcf54339535 Mon Sep 17 00:00:00 2001 From: AloneLiberty <111039319+AloneLiberty@users.noreply.github.com> Date: Thu, 2 Feb 2023 15:18:39 +0000 Subject: [PATCH 370/824] NFC: fix creating MF Classic tags from "Add Manually" menu (BCC calulation and ATQA/SAK writing) (#2342) * NFC: fix creating MF Classic cards from "Add Manually" menu (BCC calculation and AQTA/SAK writing) * NFC: Fix BCC/SAK/ATQA in unit_tests and SAK in nfc_generate_mf_classic Co-authored-by: gornekich --- applications/debug/unit_tests/nfc/nfc_test.c | 28 +++++++++++++-- lib/nfc/helpers/nfc_generators.c | 36 +++++++++++++++++--- 2 files changed, 57 insertions(+), 7 deletions(-) diff --git a/applications/debug/unit_tests/nfc/nfc_test.c b/applications/debug/unit_tests/nfc/nfc_test.c index d613be2b947..54bdd59097e 100644 --- a/applications/debug/unit_tests/nfc/nfc_test.c +++ b/applications/debug/unit_tests/nfc/nfc_test.c @@ -348,13 +348,37 @@ static void mf_classic_generator_test(uint8_t uid_len, MfClassicType type) { memcpy(atqa, nfc_dev->dev_data.nfc_data.atqa, 2); MfClassicData* mf_data = &nfc_dev->dev_data.mf_classic_data; - // Check the manufacturer block (should be uid[uid_len] + 0xFF[rest]) + // Check the manufacturer block (should be uid[uid_len] + BCC (for 4byte only) + SAK + ATQA0 + ATQA1 + 0xFF[rest]) uint8_t manufacturer_block[16] = {0}; memcpy(manufacturer_block, nfc_dev->dev_data.mf_classic_data.block[0].value, 16); mu_assert( memcmp(manufacturer_block, uid, uid_len) == 0, "manufacturer_block uid doesn't match the file\r\n"); - for(uint8_t i = uid_len; i < 16; i++) { + + uint8_t position = 0; + if(uid_len == 4) { + position = uid_len; + + uint8_t bcc = 0; + + for(int i = 0; i < uid_len; i++) { + bcc ^= uid[i]; + } + + mu_assert(manufacturer_block[position] == bcc, "manufacturer_block bcc assert failed\r\n"); + } else { + position = uid_len - 1; + } + + mu_assert(manufacturer_block[position + 1] == sak, "manufacturer_block sak assert failed\r\n"); + + mu_assert( + manufacturer_block[position + 2] == atqa[0], "manufacturer_block atqa0 assert failed\r\n"); + + mu_assert( + manufacturer_block[position + 3] == atqa[1], "manufacturer_block atqa1 assert failed\r\n"); + + for(uint8_t i = position + 4; i < 16; i++) { mu_assert( manufacturer_block[i] == 0xFF, "manufacturer_block[i] == 0xFF assert failed\r\n"); } diff --git a/lib/nfc/helpers/nfc_generators.c b/lib/nfc/helpers/nfc_generators.c index 590ff4d50ba..50c89aba85c 100644 --- a/lib/nfc/helpers/nfc_generators.c +++ b/lib/nfc/helpers/nfc_generators.c @@ -30,12 +30,32 @@ static void nfc_generate_mf_classic_uid(uint8_t* uid, uint8_t length) { furi_hal_random_fill_buf(&uid[1], length - 1); } -static void nfc_generate_mf_classic_block_0(uint8_t* block, uint8_t uid_len) { +static void nfc_generate_mf_classic_block_0( + uint8_t* block, + uint8_t uid_len, + uint8_t sak, + uint8_t atqa0, + uint8_t atqa1) { // Block length is always 16 bytes, and the UID can be either 4 or 7 bytes furi_assert(uid_len == 4 || uid_len == 7); furi_assert(block); - nfc_generate_mf_classic_uid(block, uid_len); - for(int i = uid_len; i < 16; i++) { + + if(uid_len == 4) { + // Calculate BCC + block[uid_len] = 0; + + for(int i = 0; i < uid_len; i++) { + block[uid_len] ^= block[i]; + } + } else { + uid_len -= 1; + } + + block[uid_len + 1] = sak; + block[uid_len + 2] = atqa0; + block[uid_len + 3] = atqa1; + + for(int i = uid_len + 4; i < 16; i++) { block[i] = 0xFF; } } @@ -74,7 +94,6 @@ static void data->nfc_data.type = FuriHalNfcTypeA; data->nfc_data.interface = FuriHalNfcInterfaceRf; data->nfc_data.uid_len = uid_len; - nfc_generate_mf_classic_block_0(data->mf_classic_data.block[0].value, uid_len); data->nfc_data.atqa[0] = 0x44; data->nfc_data.atqa[1] = 0x00; data->nfc_data.sak = 0x08; @@ -316,6 +335,7 @@ static void nfc_generate_ntag_i2c_plus_2k(NfcDeviceData* data) { void nfc_generate_mf_classic(NfcDeviceData* data, uint8_t uid_len, MfClassicType type) { nfc_generate_common_start(data); + nfc_generate_mf_classic_uid(data->mf_classic_data.block[0].value, uid_len); nfc_generate_mf_classic_common(data, uid_len, type); // Set the UID @@ -339,7 +359,6 @@ void nfc_generate_mf_classic(NfcDeviceData* data, uint8_t uid_len, MfClassicType } // Set SAK to 18 data->nfc_data.sak = 0x18; - } else if(type == MfClassicType1k) { // Set every block to 0xFF for(uint16_t i = 1; i < MF_CLASSIC_1K_TOTAL_SECTORS_NUM * 4; i += 1) { @@ -366,6 +385,13 @@ void nfc_generate_mf_classic(NfcDeviceData* data, uint8_t uid_len, MfClassicType data->nfc_data.sak = 0x09; } + nfc_generate_mf_classic_block_0( + data->mf_classic_data.block[0].value, + uid_len, + data->nfc_data.sak, + data->nfc_data.atqa[0], + data->nfc_data.atqa[1]); + mfc->type = type; } From 52680fd14e025056bedc538d53e2c9e54b4da5d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Sat, 4 Feb 2023 01:09:20 +0700 Subject: [PATCH 371/824] FreeRTOS: update to 10.5.1 (#2353) --- firmware/targets/f7/api_symbols.csv | 12 +++++++----- lib/FreeRTOS-Kernel | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 7b247daedd4..02f476cc04f 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,11.9,, +Version,+,11.10,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -2108,6 +2108,7 @@ Function,-,putchar_unlocked,int,int Function,-,putenv,int,char* Function,-,puts,int,const char* Function,-,putw,int,"int, FILE*" +Function,-,pvPortCalloc,void*,"size_t, size_t" Function,-,pvPortMalloc,void*,size_t Function,-,pvTaskGetThreadLocalStoragePointer,void*,"TaskHandle_t, BaseType_t" Function,-,pvTaskIncrementMutexHeldCount,TaskHandle_t, @@ -2797,11 +2798,11 @@ Function,-,vTaskSetTaskNumber,void,"TaskHandle_t, const UBaseType_t" Function,-,vTaskSetThreadLocalStoragePointer,void,"TaskHandle_t, BaseType_t, void*" Function,-,vTaskSetTimeOutState,void,TimeOut_t* Function,-,vTaskStartScheduler,void, -Function,-,vTaskStepTick,void,const TickType_t +Function,-,vTaskStepTick,void,TickType_t Function,-,vTaskSuspend,void,TaskHandle_t Function,-,vTaskSuspendAll,void, Function,-,vTaskSwitchContext,void, -Function,-,vTimerSetReloadMode,void,"TimerHandle_t, const UBaseType_t" +Function,-,vTimerSetReloadMode,void,"TimerHandle_t, const BaseType_t" Function,-,vTimerSetTimerID,void,"TimerHandle_t, void*" Function,-,vTimerSetTimerNumber,void,"TimerHandle_t, UBaseType_t" Function,+,validator_is_file_alloc_init,ValidatorIsFile*,"const char*, const char*, const char*" @@ -2942,12 +2943,13 @@ Function,-,xTaskPriorityInherit,BaseType_t,const TaskHandle_t Function,-,xTaskRemoveFromEventList,BaseType_t,const List_t* Function,-,xTaskResumeAll,BaseType_t, Function,-,xTaskResumeFromISR,BaseType_t,TaskHandle_t -Function,-,xTimerCreate,TimerHandle_t,"const char*, const TickType_t, const UBaseType_t, void*, TimerCallbackFunction_t" -Function,-,xTimerCreateStatic,TimerHandle_t,"const char*, const TickType_t, const UBaseType_t, void*, TimerCallbackFunction_t, StaticTimer_t*" +Function,-,xTimerCreate,TimerHandle_t,"const char*, const TickType_t, const BaseType_t, void*, TimerCallbackFunction_t" +Function,-,xTimerCreateStatic,TimerHandle_t,"const char*, const TickType_t, const BaseType_t, void*, TimerCallbackFunction_t, StaticTimer_t*" Function,-,xTimerCreateTimerTask,BaseType_t, Function,-,xTimerGenericCommand,BaseType_t,"TimerHandle_t, const BaseType_t, const TickType_t, BaseType_t*, const TickType_t" Function,-,xTimerGetExpiryTime,TickType_t,TimerHandle_t Function,-,xTimerGetPeriod,TickType_t,TimerHandle_t +Function,-,xTimerGetReloadMode,BaseType_t,TimerHandle_t Function,-,xTimerGetTimerDaemonTaskHandle,TaskHandle_t, Function,-,xTimerIsTimerActive,BaseType_t,TimerHandle_t Function,-,xTimerPendFunctionCall,BaseType_t,"PendedFunction_t, void*, uint32_t, TickType_t" diff --git a/lib/FreeRTOS-Kernel b/lib/FreeRTOS-Kernel index 4c4089b1544..def7d2df2b0 160000 --- a/lib/FreeRTOS-Kernel +++ b/lib/FreeRTOS-Kernel @@ -1 +1 @@ -Subproject commit 4c4089b1544b590ed3b72491a15365ec020c921f +Subproject commit def7d2df2b0506d3d249334974f51e427c17a41c From 9f279ac87268365e39a1571f60c42be94cd7d3f8 Mon Sep 17 00:00:00 2001 From: Max Andreev Date: Mon, 6 Feb 2023 17:03:29 +0300 Subject: [PATCH 372/824] [FL-2744] SPI Mem Manager C port (#1860) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- .gitignore | 1 + .../plugins/spi_mem_manager/application.fam | 17 + .../images/ChipLooking_64x64/frame_01.png | Bin 0 -> 7231 bytes .../images/ChipLooking_64x64/frame_02.png | Bin 0 -> 6909 bytes .../images/ChipLooking_64x64/frame_03.png | Bin 0 -> 7308 bytes .../images/ChipLooking_64x64/frame_rate | 1 + .../spi_mem_manager/images/Dip8_10px.png | Bin 0 -> 195 bytes .../spi_mem_manager/images/Dip8_32x36.png | Bin 0 -> 977 bytes .../images/DolphinMafia_115x62.png | Bin 0 -> 2504 bytes .../images/DolphinNice_96x59.png | Bin 0 -> 2459 bytes .../images/SDQuestion_35x43.png | Bin 0 -> 1950 bytes .../images/Wiring_SPI_128x64.png | Bin 0 -> 4446 bytes .../spi_mem_manager/lib/spi/spi_mem_chip.c | 105 ++ .../spi_mem_manager/lib/spi/spi_mem_chip.h | 34 + .../lib/spi/spi_mem_chip_arr.c | 1399 +++++++++++++++++ .../spi_mem_manager/lib/spi/spi_mem_chip_i.h | 85 + .../spi_mem_manager/lib/spi/spi_mem_tools.c | 152 ++ .../spi_mem_manager/lib/spi/spi_mem_tools.h | 14 + .../spi_mem_manager/lib/spi/spi_mem_worker.c | 129 ++ .../spi_mem_manager/lib/spi/spi_mem_worker.h | 54 + .../lib/spi/spi_mem_worker_i.h | 24 + .../lib/spi/spi_mem_worker_modes.c | 214 +++ .../spi_mem_manager/scenes/spi_mem_scene.c | 30 + .../spi_mem_manager/scenes/spi_mem_scene.h | 29 + .../scenes/spi_mem_scene_about.c | 42 + .../scenes/spi_mem_scene_chip_detect.c | 37 + .../scenes/spi_mem_scene_chip_detect_fail.c | 57 + .../scenes/spi_mem_scene_chip_detected.c | 94 ++ .../scenes/spi_mem_scene_chip_error.c | 52 + .../scenes/spi_mem_scene_config.h | 21 + .../scenes/spi_mem_scene_delete_confirm.c | 62 + .../scenes/spi_mem_scene_erase.c | 65 + .../scenes/spi_mem_scene_file_info.c | 29 + .../scenes/spi_mem_scene_read.c | 57 + .../scenes/spi_mem_scene_read_filename.c | 46 + .../scenes/spi_mem_scene_saved_file_menu.c | 76 + .../scenes/spi_mem_scene_select_file.c | 22 + .../scenes/spi_mem_scene_select_model.c | 45 + .../scenes/spi_mem_scene_select_vendor.c | 70 + .../scenes/spi_mem_scene_start.c | 84 + .../scenes/spi_mem_scene_storage_error.c | 56 + .../scenes/spi_mem_scene_success.c | 40 + .../scenes/spi_mem_scene_verify.c | 59 + .../scenes/spi_mem_scene_verify_error.c | 43 + .../scenes/spi_mem_scene_wiring.c | 18 + .../scenes/spi_mem_scene_write.c | 58 + .../plugins/spi_mem_manager/spi_mem_app.c | 112 ++ .../plugins/spi_mem_manager/spi_mem_app.h | 3 + .../plugins/spi_mem_manager/spi_mem_app_i.h | 79 + .../plugins/spi_mem_manager/spi_mem_files.c | 74 + .../plugins/spi_mem_manager/spi_mem_files.h | 14 + .../plugins/spi_mem_manager/tools/README.md | 7 + .../spi_mem_manager/tools/chiplist/LICENSE | 22 + .../tools/chiplist/chiplist.xml | 984 ++++++++++++ .../spi_mem_manager/tools/chiplist_convert.py | 109 ++ .../views/spi_mem_view_detect.c | 64 + .../views/spi_mem_view_detect.h | 9 + .../views/spi_mem_view_progress.c | 230 +++ .../views/spi_mem_view_progress.h | 26 + 59 files changed, 5154 insertions(+) create mode 100644 applications/plugins/spi_mem_manager/application.fam create mode 100644 applications/plugins/spi_mem_manager/images/ChipLooking_64x64/frame_01.png create mode 100644 applications/plugins/spi_mem_manager/images/ChipLooking_64x64/frame_02.png create mode 100644 applications/plugins/spi_mem_manager/images/ChipLooking_64x64/frame_03.png create mode 100644 applications/plugins/spi_mem_manager/images/ChipLooking_64x64/frame_rate create mode 100644 applications/plugins/spi_mem_manager/images/Dip8_10px.png create mode 100644 applications/plugins/spi_mem_manager/images/Dip8_32x36.png create mode 100644 applications/plugins/spi_mem_manager/images/DolphinMafia_115x62.png create mode 100644 applications/plugins/spi_mem_manager/images/DolphinNice_96x59.png create mode 100644 applications/plugins/spi_mem_manager/images/SDQuestion_35x43.png create mode 100644 applications/plugins/spi_mem_manager/images/Wiring_SPI_128x64.png create mode 100644 applications/plugins/spi_mem_manager/lib/spi/spi_mem_chip.c create mode 100644 applications/plugins/spi_mem_manager/lib/spi/spi_mem_chip.h create mode 100644 applications/plugins/spi_mem_manager/lib/spi/spi_mem_chip_arr.c create mode 100644 applications/plugins/spi_mem_manager/lib/spi/spi_mem_chip_i.h create mode 100644 applications/plugins/spi_mem_manager/lib/spi/spi_mem_tools.c create mode 100644 applications/plugins/spi_mem_manager/lib/spi/spi_mem_tools.h create mode 100644 applications/plugins/spi_mem_manager/lib/spi/spi_mem_worker.c create mode 100644 applications/plugins/spi_mem_manager/lib/spi/spi_mem_worker.h create mode 100644 applications/plugins/spi_mem_manager/lib/spi/spi_mem_worker_i.h create mode 100644 applications/plugins/spi_mem_manager/lib/spi/spi_mem_worker_modes.c create mode 100644 applications/plugins/spi_mem_manager/scenes/spi_mem_scene.c create mode 100644 applications/plugins/spi_mem_manager/scenes/spi_mem_scene.h create mode 100644 applications/plugins/spi_mem_manager/scenes/spi_mem_scene_about.c create mode 100644 applications/plugins/spi_mem_manager/scenes/spi_mem_scene_chip_detect.c create mode 100644 applications/plugins/spi_mem_manager/scenes/spi_mem_scene_chip_detect_fail.c create mode 100644 applications/plugins/spi_mem_manager/scenes/spi_mem_scene_chip_detected.c create mode 100644 applications/plugins/spi_mem_manager/scenes/spi_mem_scene_chip_error.c create mode 100644 applications/plugins/spi_mem_manager/scenes/spi_mem_scene_config.h create mode 100644 applications/plugins/spi_mem_manager/scenes/spi_mem_scene_delete_confirm.c create mode 100644 applications/plugins/spi_mem_manager/scenes/spi_mem_scene_erase.c create mode 100644 applications/plugins/spi_mem_manager/scenes/spi_mem_scene_file_info.c create mode 100644 applications/plugins/spi_mem_manager/scenes/spi_mem_scene_read.c create mode 100644 applications/plugins/spi_mem_manager/scenes/spi_mem_scene_read_filename.c create mode 100644 applications/plugins/spi_mem_manager/scenes/spi_mem_scene_saved_file_menu.c create mode 100644 applications/plugins/spi_mem_manager/scenes/spi_mem_scene_select_file.c create mode 100644 applications/plugins/spi_mem_manager/scenes/spi_mem_scene_select_model.c create mode 100644 applications/plugins/spi_mem_manager/scenes/spi_mem_scene_select_vendor.c create mode 100644 applications/plugins/spi_mem_manager/scenes/spi_mem_scene_start.c create mode 100644 applications/plugins/spi_mem_manager/scenes/spi_mem_scene_storage_error.c create mode 100644 applications/plugins/spi_mem_manager/scenes/spi_mem_scene_success.c create mode 100644 applications/plugins/spi_mem_manager/scenes/spi_mem_scene_verify.c create mode 100644 applications/plugins/spi_mem_manager/scenes/spi_mem_scene_verify_error.c create mode 100644 applications/plugins/spi_mem_manager/scenes/spi_mem_scene_wiring.c create mode 100644 applications/plugins/spi_mem_manager/scenes/spi_mem_scene_write.c create mode 100644 applications/plugins/spi_mem_manager/spi_mem_app.c create mode 100644 applications/plugins/spi_mem_manager/spi_mem_app.h create mode 100644 applications/plugins/spi_mem_manager/spi_mem_app_i.h create mode 100644 applications/plugins/spi_mem_manager/spi_mem_files.c create mode 100644 applications/plugins/spi_mem_manager/spi_mem_files.h create mode 100644 applications/plugins/spi_mem_manager/tools/README.md create mode 100644 applications/plugins/spi_mem_manager/tools/chiplist/LICENSE create mode 100644 applications/plugins/spi_mem_manager/tools/chiplist/chiplist.xml create mode 100755 applications/plugins/spi_mem_manager/tools/chiplist_convert.py create mode 100644 applications/plugins/spi_mem_manager/views/spi_mem_view_detect.c create mode 100644 applications/plugins/spi_mem_manager/views/spi_mem_view_detect.h create mode 100644 applications/plugins/spi_mem_manager/views/spi_mem_view_progress.c create mode 100644 applications/plugins/spi_mem_manager/views/spi_mem_view_progress.h diff --git a/.gitignore b/.gitignore index d54ed4a19a3..542652eb0d4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ *.swp +*.swo *.gdb_history diff --git a/applications/plugins/spi_mem_manager/application.fam b/applications/plugins/spi_mem_manager/application.fam new file mode 100644 index 00000000000..09d80187690 --- /dev/null +++ b/applications/plugins/spi_mem_manager/application.fam @@ -0,0 +1,17 @@ +App( + appid="spi_mem_manager", + name="SPI Mem Manager", + apptype=FlipperAppType.EXTERNAL, + entry_point="spi_mem_app", + requires=["gui"], + stack_size=1 * 2048, + order=30, + fap_icon="images/Dip8_10px.png", + fap_category="Tools", + fap_icon_assets="images", + fap_private_libs=[ + Lib( + name="spi", + ), + ], +) diff --git a/applications/plugins/spi_mem_manager/images/ChipLooking_64x64/frame_01.png b/applications/plugins/spi_mem_manager/images/ChipLooking_64x64/frame_01.png new file mode 100644 index 0000000000000000000000000000000000000000..4ff2e3042e540d8b499118e9edf4ecd180b39511 GIT binary patch literal 7231 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 NY%KPgSDX4C`yZHc9FG71 literal 0 HcmV?d00001 diff --git a/applications/plugins/spi_mem_manager/images/ChipLooking_64x64/frame_03.png b/applications/plugins/spi_mem_manager/images/ChipLooking_64x64/frame_03.png new file mode 100644 index 0000000000000000000000000000000000000000..1342dc7bf95265db5d0ab2924e1ef03fe4df9943 GIT binary patch literal 7308 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 literal 0 HcmV?d00001 diff --git a/applications/plugins/spi_mem_manager/images/ChipLooking_64x64/frame_rate b/applications/plugins/spi_mem_manager/images/ChipLooking_64x64/frame_rate new file mode 100644 index 00000000000..d8263ee9860 --- /dev/null +++ b/applications/plugins/spi_mem_manager/images/ChipLooking_64x64/frame_rate @@ -0,0 +1 @@ +2 \ No newline at end of file diff --git a/applications/plugins/spi_mem_manager/images/Dip8_10px.png b/applications/plugins/spi_mem_manager/images/Dip8_10px.png new file mode 100644 index 0000000000000000000000000000000000000000..9de9364d13c5cbd7fe43a6df59789193d9133057 GIT binary patch literal 195 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V6Od#Ih{XE z)7O>#85cXB6sN>NS&&A_64!_l=ltB<)VvY~=c3falGGH1^30M91$R&1fbd2>aiAhw zPZ!4!iOaqHj$8}|9EV^1`~UonL4lUvGtG!wL5x*034X4|#-7<_>P#}{{_l#8SgPZ+ js&kpz9*+3KCj7Uj_`9EfRa>$PXb^*^tDnm{r-UW|gHJf_ literal 0 HcmV?d00001 diff --git a/applications/plugins/spi_mem_manager/images/Dip8_32x36.png b/applications/plugins/spi_mem_manager/images/Dip8_32x36.png new file mode 100644 index 0000000000000000000000000000000000000000..8f01af2760363b31029cbc425d9a9f2e2becf96b GIT binary patch literal 977 zcmeAS@N?(olHy`uVBq!ia0vp^3P7yF!3-qto1VD9z`)E9;1l8s2$&H|6fVg?3oVGw3ym^DWNC|K_4;uvDl`*xDGP=kVibG*#E`lk^cO$zev z+}xJ~R3@KooBBeTVTU7E1RI;jy{)QdNiPgUSiTrM5ocMrlj*@Cjo*LH^-f?g|F=lv zl)Fs&L8nKli4M~yCl?8HDNptH;hVjxE12WemhD};j?@R{i;C*6a`dzQvVm!LtxQw_ S>l}Mfba=Y@xvXZB8b0=g#+k z_y7La$vcsnwa$(njyxXES*27&ad(Ehf@a%szx(??vFC0MMrAy=Img9%&ES885R5X2P@K{dB9p<$p?SQ()g~i~r4cNkC3JdH&i}sg6d%yza(--p8dMv@ zh!njtmnNcfH8EIj8V2M1)j>d@3E>C~1d9SDLpsSICOLnC7va{{Z80C1fUs$Deu(uz zAWj_#gi$mBNJXF!13?Io!6J#&-(L!@03Z+o#bAI~0tqEj1oTHFGGOY%=T4*XWF$)Q z`>C_ICpkZbWsQhfoSmI5%Jvgcv`#F6VOR`8Vh9p)2qBY0vZzT&GE1fz6a<6OdLyf+ zNWjX7YNwhuAF&nut zlTM%T7{|m!I$L;81?0JCCML&7h@%LG%A_%3 zO%`|J5~~^`5=Ij!OVKeDl|G%Q$Z2^1BoRS?PpqEAscc5@GXp|_vV@$^WlbUkA?_Ok zK?n#V5acTX5fGe&s<}GAQ5OB*z!a`e&iO^CEx1S+l}^!W3g`Ur;{!N`BvZ5jW@%VH?>OF2Tk@O zPGOv@&rGEfX|lv0Cxk2gKu)ie6Af#Vr9x}>!CI+Aiv@szVry$~6u{(al2-hTBEgTzn_D^}jklllIvu1V{Q`ig6OgP|0jI zN)sVEE|=@hm?j7H6PqgYzU5==|fB0<6@J90B?N8); z?B48M`Q6&q<>QYftD|a*tJ$!0YduA;TS}(23t@i9jJ}9E&d>+O-{j}lDtd6mP7wiU?pLh0* zla-TQ!!6f>9b(>jct-Z*@vzVmEjaUp9adYyRH)W#u&{1)0G7#K8z}OOe9Z4J`?k~5 z;u#n4^?R%GdBZDjly!H8xtVMF9ud_Q|CsUp%X4BI?jMd19&&9{QqgG_a)Rz9J*BH| z$zM9cbZYA6R(n(=QYD(cO(#Aoy6CQh;hG<}_gRz&>ZIovmNuT&Z9VwM8m5pu&$kG$ zvTJ!+pA|E6E-UBtJJrv;*XaRo7|Z#x4L(qON`UQa?6`jZqnkg3XliTEuJKo%PCa~M z@WlnE3u1ZRT?c;b@m&$07PGImr1km-TQZ8*DS|rZudw{x4R!5F9=$VOt{XWj(Y>BT zd-yG`a(KJ-o0Dfs8h&U=J*C(_ z=8hNq6aC?^r7wqGy5!v`zvX@KNEDDEpXqBVXiB`Z=eNZRgGG2tG`F;x~xDn9)G1Y@4Fl28Px*E!|ivy@~-8Lx%@`DyQ}?V z4f!BGF*jl}N~1D%!=YeZY6W)9lyDw_Uq#NDJx^=CJZDD2|CF# zA7Ixt{Z7BT8@4fZgFkI{D9fJxang<$JS``+d(*81cbB@prG*c!rZ)8U4y-<__Pt)Z zZ3lJfK;Y5eZHd?A3O-!mWX3$UChhmy)r@4iKkvyz(mdTtF7?TWn4`7t4=} zZ`OLe!fHzEo3eUH7jwVD-n?Xnx$AC<-H6`;RB2iYH9UO}ROfZkPOl32mRZ%`xW#FL zD@GqK${E&#=gzidc(qkxLZ^tk7u}u0Uu|;00}}A@rq4$9xE75>Hwj!4$Nk!`)YmDg{{4HeKCy?7Z85xPzg%Peucca}QJ6#D*z!+`G0ZOj literal 0 HcmV?d00001 diff --git a/applications/plugins/spi_mem_manager/images/DolphinNice_96x59.png b/applications/plugins/spi_mem_manager/images/DolphinNice_96x59.png new file mode 100644 index 0000000000000000000000000000000000000000..a299d3630239b4486e249cc501872bed5996df3b GIT binary patch literal 2459 zcmbVO3s4i+8V(M(gEFORwSrA`4O0uPn|M|5y* zB*aMDxC&7(gP9JN;POOi-9khrC>Z9YJs2U!LnVcQEEC0fDtKo&ILlzb30%M}3J^;~ zv7RzcsilOs4Mq@tD*&R;!LMSk2A~{(`HK9|hQBqEX)3sQr9Je6SZU*F-^fD-p+~Hs; zHLkO%v?>ZoxEv+F#whudr%615FkA0DYR0tMEo}3OOY#xecLWe>xV?u5KtSmC^ z7)Fmj6gjfKstiEV-*Cxbbb+&rRWuI_rBJ)ybs_f1Rn&f2>q3pYwI^|J(hdn{j{0EZIm_F zpIyIWLsRUgOItR-dUbVd|6Zo=_BU_Tj4|{{jxO#=JH4o8er(5{!nZD_j4}MH&zh~9 zVLC~y(0-D6GO0ghZD8BYzP?o{>22~lT6^d@X{SwQ8vrNY-PPIMajIwC)`s14Ep72@ zeq7YOzM`?U{+W)ocXBr`eSOcpk?Rxc=ou5&)fWW|pD};-Z0mvk9}=&`Rb&y<77W~a z(>6YM;6Y5aIU~JKZ}mQZynKHiSTQ#Bczn@&jTiN^?vPJ(jhm7cXLx0oum5P$`TceG zU+wR;OO^)8CVlnM)5p$CO&e94KJt>HccCaHGusmW_b`T6m| z-R6V6Db1pErTot?^d22ojm+2>_)FbD`_+WbDGMx9f@hO27maS2`csiV(D&Fs`PS2& zvrq18du_&zXID(!KIxsU$)iuTYuZ?zmYiP&n&i@Be{IdbS-jA2c0QAlu5NXQv_0K< z3Hvs4eeu6B7yD&CNT~gIkMV&UkRU=V!iQ(+_(O&u^ah$+s{_yn(yBYeD40HeU{xGsIT6W Zfq!wOp!Q8gyJRa{_+4UIP z$!=1i2t*O^Q!1)9DGybMBGF3a6SW|L6h5LVfJD{LegT4thW>zPQHvPws{yrXept!t zv3=&;bMHMf^XAC#V8_NS8##{a$PeX4*}aQh-5b`i|CfLH7_-|w{?PLw$KCsIeBHqv zeQy)T-TjJN7>~xyod%|r1hT0`619rY&>XjYN6klgf<(MUimsOtE`R=|z`J%v*qt1RpF9hwQq*vxPN&rD$57IyUT+iM0RsE`QpwMy9wjao*i^BQa%zm^2P4v8i*LT?<9 zA2&z%EDZ>+C4h(lkolCJfSRgm;7MKvGLS%0g0cuT1E>Z}@y(yWq6M~NjOGTKvDi~a zC`FNPNK&<0O;nWx4T=)fbzK6oB+DX0h~cysp_=H0T`h(j331^1kxM;3W<(a9j4}dK z+DM_|w`skwSteF6sfK(BCP1803uv0FLo1awI*j_KSd^yTn-YhGX`e`=B&3r8CjC>y zi@I9D{1T05SfaPk*8co2g*I*n^e2OIy*xISNSRa^cgV1?uFp5J0YMQB3Y3;xjT&i1 zyC$M##e?pUVhLRKj&_L)9?Wld>u%HCq;SStTN}Y*kccThRe>U`i)-U2J}i z;>oxY@%)BuZHgI3yPAgMs43vsNJP47iHfQ!qLpHl$)u9T2onYB=@#3rz-223l~=OH zs_a-*O3@VL0MW4s7KyDoOqHyNDzSA4a2n~Dsk#w2OUpDcsm-dZtbCu(W=8_*xMlVs z93AZA^Zi*3>Y66X2`KP3HXIsM5Hp%vK}90@UNN>klflv*azobR>E=QjBQG^aWtXqJ z(?B?06d3`>ZXmYMeC^(>%xg-hL0c^mM!Jei8nBQ$Q56NGx5!#@TNg^V5+9y5Z&x22##B&{B?u64ye+M3KZ=XlsY71%@jTp=Dy zHDISkcY5&@J8>5Bx!%I~{^i3@-@m}$cNaW+{nKk_j+x(U>F+k}c?6!^@X+fADi3lO z_rLKG{`yum;ZU>ayk!Z+q>VzZOn^bqQ>?UO0Drz@_TNg$rjt zNcQEpt?wS&gMaKe^8V2SorGL{ZtT%R?yPd~`n9FG(}zAOOPl5My;t&Z3(*F9I?g}- zvp)ag@#QCe{o%9hrGrn+&dpu9`rFcD;MqUV>^-?JG4|Gpeamy-PL6)jzu5T`{Cd7~ fwr}T&J8Rt1;5!ej|9##1_yo=O59dzx?S1thQe1#H literal 0 HcmV?d00001 diff --git a/applications/plugins/spi_mem_manager/images/Wiring_SPI_128x64.png b/applications/plugins/spi_mem_manager/images/Wiring_SPI_128x64.png new file mode 100644 index 0000000000000000000000000000000000000000..e6c3ce363f801cff5080d10599ca258d07783fa1 GIT binary patch literal 4446 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 != SPIMemChipVendorUnknown && vendor->vendor_enum != vendor_enum) + vendor++; + return vendor->vendor_name; +} + +bool spi_mem_chip_find_all(SPIMemChip* chip_info, found_chips_t found_chips) { + const SPIMemChip* chip_info_arr; + found_chips_reset(found_chips); + for(chip_info_arr = SPIMemChips; chip_info_arr->model_name != NULL; chip_info_arr++) { + if(chip_info->vendor_id != chip_info_arr->vendor_id) continue; + if(chip_info->type_id != chip_info_arr->type_id) continue; + if(chip_info->capacity_id != chip_info_arr->capacity_id) continue; + found_chips_push_back(found_chips, chip_info_arr); + } + if(found_chips_size(found_chips)) return true; + return false; +} + +void spi_mem_chip_copy_chip_info(SPIMemChip* dest, const SPIMemChip* src) { + memcpy(dest, src, sizeof(SPIMemChip)); +} + +size_t spi_mem_chip_get_size(SPIMemChip* chip) { + return (chip->size); +} + +const char* spi_mem_chip_get_vendor_name(const SPIMemChip* chip) { + return (spi_mem_chip_search_vendor_name(chip->vendor_enum)); +} + +const char* spi_mem_chip_get_vendor_name_by_enum(uint32_t vendor_enum) { + return (spi_mem_chip_search_vendor_name(vendor_enum)); +} + +const char* spi_mem_chip_get_model_name(const SPIMemChip* chip) { + return (chip->model_name); +} + +uint8_t spi_mem_chip_get_vendor_id(SPIMemChip* chip) { + return (chip->vendor_id); +} + +uint8_t spi_mem_chip_get_type_id(SPIMemChip* chip) { + return (chip->type_id); +} + +uint8_t spi_mem_chip_get_capacity_id(SPIMemChip* chip) { + return (chip->capacity_id); +} + +SPIMemChipWriteMode spi_mem_chip_get_write_mode(SPIMemChip* chip) { + return (chip->write_mode); +} + +size_t spi_mem_chip_get_page_size(SPIMemChip* chip) { + return (chip->page_size); +} + +uint32_t spi_mem_chip_get_vendor_enum(const SPIMemChip* chip) { + return ((uint32_t)chip->vendor_enum); +} diff --git a/applications/plugins/spi_mem_manager/lib/spi/spi_mem_chip.h b/applications/plugins/spi_mem_manager/lib/spi/spi_mem_chip.h new file mode 100644 index 00000000000..683937df4fd --- /dev/null +++ b/applications/plugins/spi_mem_manager/lib/spi/spi_mem_chip.h @@ -0,0 +1,34 @@ +#pragma once + +#include +#include + +typedef struct SPIMemChip SPIMemChip; + +ARRAY_DEF(found_chips, const SPIMemChip*, M_POD_OPLIST) + +typedef enum { + SPIMemChipStatusBusy, + SPIMemChipStatusIdle, + SPIMemChipStatusError +} SPIMemChipStatus; + +typedef enum { + SPIMemChipWriteModeUnknown = 0, + SPIMemChipWriteModePage = (0x01 << 0), + SPIMemChipWriteModeAAIByte = (0x01 << 1), + SPIMemChipWriteModeAAIWord = (0x01 << 2), +} SPIMemChipWriteMode; + +const char* spi_mem_chip_get_vendor_name(const SPIMemChip* chip); +const char* spi_mem_chip_get_model_name(const SPIMemChip* chip); +size_t spi_mem_chip_get_size(SPIMemChip* chip); +uint8_t spi_mem_chip_get_vendor_id(SPIMemChip* chip); +uint8_t spi_mem_chip_get_type_id(SPIMemChip* chip); +uint8_t spi_mem_chip_get_capacity_id(SPIMemChip* chip); +SPIMemChipWriteMode spi_mem_chip_get_write_mode(SPIMemChip* chip); +size_t spi_mem_chip_get_page_size(SPIMemChip* chip); +bool spi_mem_chip_find_all(SPIMemChip* chip_info, found_chips_t found_chips); +void spi_mem_chip_copy_chip_info(SPIMemChip* dest, const SPIMemChip* src); +uint32_t spi_mem_chip_get_vendor_enum(const SPIMemChip* chip); +const char* spi_mem_chip_get_vendor_name_by_enum(uint32_t vendor_enum); diff --git a/applications/plugins/spi_mem_manager/lib/spi/spi_mem_chip_arr.c b/applications/plugins/spi_mem_manager/lib/spi/spi_mem_chip_arr.c new file mode 100644 index 00000000000..25b237d6829 --- /dev/null +++ b/applications/plugins/spi_mem_manager/lib/spi/spi_mem_chip_arr.c @@ -0,0 +1,1399 @@ +#include "spi_mem_chip_i.h" +const SPIMemChip SPIMemChips[] = { + {0x1F, 0x40, 0x00, "AT25DN256", 32768, 256, SPIMemChipVendorADESTO, SPIMemChipWriteModePage}, + {0x37, 0x20, 0x20, "A25L05PT", 65536, 256, SPIMemChipVendorAMIC, SPIMemChipWriteModePage}, + {0x37, 0x20, 0x10, "A25L05PU", 65536, 256, SPIMemChipVendorAMIC, SPIMemChipWriteModePage}, + {0x37, 0x20, 0x21, "A25L10PT", 131072, 256, SPIMemChipVendorAMIC, SPIMemChipWriteModePage}, + {0x37, 0x20, 0x11, "A25L10PU", 131072, 256, SPIMemChipVendorAMIC, SPIMemChipWriteModePage}, + {0x37, 0x20, 0x22, "A25L20PT", 262144, 256, SPIMemChipVendorAMIC, SPIMemChipWriteModePage}, + {0x37, 0x20, 0x12, "A25L20PU", 262144, 256, SPIMemChipVendorAMIC, SPIMemChipWriteModePage}, + {0x37, 0x20, 0x23, "A25L40PT", 524288, 256, SPIMemChipVendorAMIC, SPIMemChipWriteModePage}, + {0x37, 0x20, 0x13, "A25L40PU", 524288, 256, SPIMemChipVendorAMIC, SPIMemChipWriteModePage}, + {0x37, 0x20, 0x24, "A25L80PT", 1048576, 256, SPIMemChipVendorAMIC, SPIMemChipWriteModePage}, + {0x37, 0x20, 0x14, "A25L80PU", 1048576, 256, SPIMemChipVendorAMIC, SPIMemChipWriteModePage}, + {0x37, 0x20, 0x25, "A25L16PT", 2097152, 256, SPIMemChipVendorAMIC, SPIMemChipWriteModePage}, + {0x37, 0x20, 0x15, "A25L16PU", 2097152, 256, SPIMemChipVendorAMIC, SPIMemChipWriteModePage}, + {0x37, 0x30, 0x10, "A25L512", 65536, 256, SPIMemChipVendorAMIC, SPIMemChipWriteModePage}, + {0x37, 0x30, 0x11, "A25L010", 131072, 256, SPIMemChipVendorAMIC, SPIMemChipWriteModePage}, + {0x37, 0x30, 0x12, "A25L020", 262144, 256, SPIMemChipVendorAMIC, SPIMemChipWriteModePage}, + {0x37, 0x30, 0x13, "A25L040", 524288, 256, SPIMemChipVendorAMIC, SPIMemChipWriteModePage}, + {0x37, 0x30, 0x14, "A25L080", 1048576, 256, SPIMemChipVendorAMIC, SPIMemChipWriteModePage}, + {0x37, 0x30, 0x15, "A25L016", 2097152, 256, SPIMemChipVendorAMIC, SPIMemChipWriteModePage}, + {0x37, 0x30, 0x16, "A25L032", 4194304, 256, SPIMemChipVendorAMIC, SPIMemChipWriteModePage}, + {0x37, 0x40, 0x15, "A25LQ16", 2097152, 256, SPIMemChipVendorAMIC, SPIMemChipWriteModePage}, + {0x37, 0x40, 0x16, "A25LQ32A", 4194304, 256, SPIMemChipVendorAMIC, SPIMemChipWriteModePage}, + {0x68, 0x40, 0x14, "BY25D80", 1048576, 256, SPIMemChipVendorBoya, SPIMemChipWriteModePage}, + {0x1C, 0x20, 0x10, "EN25B05", 65536, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, + {0x1C, 0x20, 0x10, "EN25B05T", 65536, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, + {0x1C, 0x20, 0x11, "EN25B10", 131072, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, + {0x1C, 0x20, 0x11, "EN25B10T", 131072, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, + {0x1C, 0x20, 0x12, "EN25B20", 262144, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, + {0x1C, 0x20, 0x12, "EN25B20T", 262144, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, + {0x1C, 0x20, 0x13, "EN25B40", 524288, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, + {0x1C, 0x20, 0x13, "EN25B40T", 524288, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, + {0x1C, 0x20, 0x14, "EN25B80", 1048576, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, + {0x1C, 0x20, 0x14, "EN25B80T", 1048576, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, + {0x1C, 0x20, 0x15, "EN25B16", 2097152, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, + {0x1C, 0x20, 0x15, "EN25B16T", 2097152, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, + {0x1C, 0x20, 0x16, "EN25B32", 4194304, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, + {0x1C, 0x20, 0x16, "EN25B32T", 4194304, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, + {0x1C, 0x20, 0x17, "EN25B64", 8388608, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, + {0x1C, 0x20, 0x17, "EN25B64T", 8388608, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, + {0x1C, 0x31, 0x10, "EN25F05", 65536, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, + {0x1C, 0x31, 0x11, "EN25F10", 131072, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, + {0x1C, 0x31, 0x12, "EN25F20", 262144, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, + {0x1C, 0x31, 0x13, "EN25F40", 524288, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, + {0x1C, 0x31, 0x14, "EN25F80", 1048576, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, + {0x1C, 0x31, 0x15, "EN25F16", 2097152, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, + {0x1C, 0x31, 0x16, "EN25F32", 4194304, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, + {0x1C, 0x31, 0x10, "EN25LF05", 65536, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, + {0x1C, 0x31, 0x11, "EN25LF10", 131072, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, + {0x1C, 0x31, 0x12, "EN25LF20", 262144, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, + {0x1C, 0x31, 0x13, "EN25LF40", 524288, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, + {0x1C, 0x20, 0x10, "EN25P05", 65536, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, + {0x1C, 0x20, 0x11, "EN25P10", 131072, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, + {0x1C, 0x20, 0x12, "EN25P20", 262144, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, + {0x1C, 0x20, 0x13, "EN25P40", 524288, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, + {0x1C, 0x20, 0x14, "EN25P80", 1048576, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, + {0x1C, 0x20, 0x15, "EN25P16", 2097152, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, + {0x1C, 0x20, 0x16, "EN25P32", 4194304, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, + {0x1C, 0x20, 0x17, "EN25P64", 8388608, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, + {0x1C, 0x30, 0x13, "EN25Q40", 524288, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, + {0x1C, 0x30, 0x14, "EN25Q80A", 1048576, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, + {0x1C, 0x30, 0x15, "EN25Q16A", 2097152, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, + {0x1C, 0x30, 0x16, "EN25Q32A", 4194304, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, + {0x1C, 0x70, 0x16, "EN25Q32A", 4194304, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, + {0x1C, 0x30, 0x16, "EN25Q32B", 4194304, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, + {0x1C, 0x30, 0x17, "EN25Q64", 8388608, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, + {0x1C, 0x30, 0x18, "EN25Q128", 16777216, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, + {0x1C, 0x70, 0x15, "EN25QH16", 2097152, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, + {0x1C, 0x70, 0x16, "EN25QH32", 4194304, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, + {0x1C, 0x70, 0x17, "EN25QH64", 8388608, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, + {0x1C, 0x70, 0x18, "EN25QH128", 16777216, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, + {0x1C, 0x70, 0x19, "EN25QH256", 33554432, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, + {0x1C, 0x51, 0x14, "EN25T80", 1048576, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, + {0x1C, 0x51, 0x15, "EN25T16", 2097152, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, + {0x1C, 0x31, 0x17, "EN25F64", 8388608, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, + {0x7F, 0x9D, 0x7C, "Pm25LV010", 131072, 256, SPIMemChipVendorPFLASH, SPIMemChipWriteModePage}, + {0x7F, 0x9D, 0x21, "Pm25LD010", 131072, 256, SPIMemChipVendorPFLASH, SPIMemChipWriteModePage}, + {0x7F, 0x9D, 0x22, "Pm25LV020", 262144, 256, SPIMemChipVendorPFLASH, SPIMemChipWriteModePage}, + {0x7F, 0x9D, 0x7D, "Pm25W020", 262144, 256, SPIMemChipVendorPFLASH, SPIMemChipWriteModePage}, + {0x7F, 0x9D, 0x7E, "Pm25LV040", 524288, 256, SPIMemChipVendorPFLASH, SPIMemChipWriteModePage}, + {0x37, 0x30, 0x10, "TS25L512A", 65536, 256, SPIMemChipVendorTERRA, SPIMemChipWriteModePage}, + {0x37, 0x30, 0x11, "TS25L010A", 131072, 256, SPIMemChipVendorTERRA, SPIMemChipWriteModePage}, + {0x37, 0x30, 0x12, "TS25L020A", 262144, 256, SPIMemChipVendorTERRA, SPIMemChipWriteModePage}, + {0x20, 0x20, 0x15, "TS25L16AP", 2097152, 256, SPIMemChipVendorTERRA, SPIMemChipWriteModePage}, + {0x20, 0x20, 0x15, "TS25L16BP", 2097152, 256, SPIMemChipVendorTERRA, SPIMemChipWriteModePage}, + {0x20, 0x20, 0x15, "ZP25L16P", 2097152, 256, SPIMemChipVendorTERRA, SPIMemChipWriteModePage}, + {0x20, 0x80, 0x15, "TS25L16PE", 2097152, 256, SPIMemChipVendorTERRA, SPIMemChipWriteModePage}, + {0x20, 0x80, 0x14, "TS25L80PE", 1048576, 256, SPIMemChipVendorTERRA, SPIMemChipWriteModePage}, + {0x37, 0x30, 0x16, "TS25L032A", 4194304, 256, SPIMemChipVendorTERRA, SPIMemChipWriteModePage}, + {0x20, 0x20, 0x13, "TS25L40P", 524288, 256, SPIMemChipVendorTERRA, SPIMemChipWriteModePage}, + {0xC2, + 0x20, + 0x10, + "GPR25L005E", + 65536, + 256, + SPIMemChipVendorGeneralplus, + SPIMemChipWriteModePage}, + {0xC2, + 0x20, + 0x15, + "GPR25L161B", + 262144, + 256, + SPIMemChipVendorGeneralplus, + SPIMemChipWriteModePage}, + {0xC2, + 0x20, + 0x12, + "GPR25L020B", + 262144, + 256, + SPIMemChipVendorGeneralplus, + SPIMemChipWriteModePage}, + {0xC2, + 0x20, + 0x16, + "GPR25L3203F", + 4194304, + 256, + SPIMemChipVendorGeneralplus, + SPIMemChipWriteModePage}, + {0x9D, 0x7B, 0x00, "AC25LV512", 65536, 256, SPIMemChipVendorDEUTRON, SPIMemChipWriteModePage}, + {0x9D, 0x7C, 0x00, "AC25LV010", 131072, 256, SPIMemChipVendorDEUTRON, SPIMemChipWriteModePage}, + {0x9D, 0x7B, 0x00, "EM25LV512", 65536, 256, SPIMemChipVendorEFST, SPIMemChipWriteModePage}, + {0x9D, 0x7C, 0x00, "EM25LV010", 131072, 256, SPIMemChipVendorEFST, SPIMemChipWriteModePage}, + {0x8C, 0x20, 0x13, "F25L004A", 524288, 256, SPIMemChipVendorEFST, SPIMemChipWriteModePage}, + {0x8C, 0x20, 0x14, "F25L008A", 1048576, 256, SPIMemChipVendorEFST, SPIMemChipWriteModePage}, + {0x8C, 0x20, 0x15, "F25L016A", 2097152, 256, SPIMemChipVendorEFST, SPIMemChipWriteModePage}, + {0x8C, 0x8C, 0x8C, "F25L04UA", 524288, 256, SPIMemChipVendorEFST, SPIMemChipWriteModePage}, + {0x8C, 0x20, 0x13, "F25L04P", 524288, 256, SPIMemChipVendorEFST, SPIMemChipWriteModePage}, + {0x8C, 0x30, 0x13, "F25S04P", 524288, 256, SPIMemChipVendorEFST, SPIMemChipWriteModePage}, + {0x8C, 0x20, 0x14, "F25L08P", 1048576, 256, SPIMemChipVendorEFST, SPIMemChipWriteModePage}, + {0x8C, 0x20, 0x15, "F25L16P", 2097152, 256, SPIMemChipVendorEFST, SPIMemChipWriteModePage}, + {0x8C, 0x20, 0x16, "F25L32P", 4194304, 256, SPIMemChipVendorEFST, SPIMemChipWriteModePage}, + {0x8C, 0x40, 0x16, "F25L32Q", 4194304, 256, SPIMemChipVendorEFST, SPIMemChipWriteModePage}, + {0x4A, 0x20, 0x11, "ES25P10", 131072, 256, SPIMemChipVendorEXCELSEMI, SPIMemChipWriteModePage}, + {0x4A, 0x20, 0x12, "ES25P20", 262144, 256, SPIMemChipVendorEXCELSEMI, SPIMemChipWriteModePage}, + {0x4A, 0x20, 0x13, "ES25P40", 524288, 256, SPIMemChipVendorEXCELSEMI, SPIMemChipWriteModePage}, + {0x4A, 0x20, 0x14, "ES25P80", 1048576, 256, SPIMemChipVendorEXCELSEMI, SPIMemChipWriteModePage}, + {0x4A, 0x20, 0x15, "ES25P16", 2097152, 256, SPIMemChipVendorEXCELSEMI, SPIMemChipWriteModePage}, + {0x4A, 0x20, 0x16, "ES25P32", 4194304, 256, SPIMemChipVendorEXCELSEMI, SPIMemChipWriteModePage}, + {0x4A, 0x32, 0x13, "ES25M40A", 524288, 256, SPIMemChipVendorEXCELSEMI, SPIMemChipWriteModePage}, + {0x4A, 0x32, 0x14, "ES25M80A", 1048576, 256, SPIMemChipVendorEXCELSEMI, SPIMemChipWriteModePage}, + {0x4A, 0x32, 0x15, "ES25M16A", 2097152, 256, SPIMemChipVendorEXCELSEMI, SPIMemChipWriteModePage}, + {0xF8, 0x32, 0x14, "FM25Q08A", 1048576, 256, SPIMemChipVendorFIDELIX, SPIMemChipWriteModePage}, + {0xF8, 0x32, 0x15, "FM25Q16A", 2097152, 256, SPIMemChipVendorFIDELIX, SPIMemChipWriteModePage}, + {0xF8, 0x32, 0x15, "FM25Q16B", 2097152, 256, SPIMemChipVendorFIDELIX, SPIMemChipWriteModePage}, + {0xF8, 0x32, 0x16, "FM25Q32A", 4194304, 256, SPIMemChipVendorFIDELIX, SPIMemChipWriteModePage}, + {0xF8, 0x32, 0x17, "FM25Q64A", 8388608, 256, SPIMemChipVendorFIDELIX, SPIMemChipWriteModePage}, + {0xC8, 0x30, 0x13, "GD25D40", 524288, 256, SPIMemChipVendorGIGADEVICE, SPIMemChipWriteModePage}, + {0xC8, 0x30, 0x14, "GD25D80", 1048576, 256, SPIMemChipVendorGIGADEVICE, SPIMemChipWriteModePage}, + {0xC8, 0x20, 0x13, "GD25F40", 524288, 256, SPIMemChipVendorGIGADEVICE, SPIMemChipWriteModePage}, + {0xC8, 0x20, 0x14, "GD25F80", 1048576, 256, SPIMemChipVendorGIGADEVICE, SPIMemChipWriteModePage}, + {0xC8, 0x40, 0x10, "GD25Q512", 65536, 256, SPIMemChipVendorGIGADEVICE, SPIMemChipWriteModePage}, + {0xC8, 0x40, 0x11, "GD25Q10", 131072, 256, SPIMemChipVendorGIGADEVICE, SPIMemChipWriteModePage}, + {0xC8, 0x40, 0x12, "GD25Q20", 262144, 256, SPIMemChipVendorGIGADEVICE, SPIMemChipWriteModePage}, + {0xC8, + 0x60, + 0x12, + "GD25LQ20C_1.8V", + 262144, + 256, + SPIMemChipVendorGIGADEVICE, + SPIMemChipWriteModePage}, + {0xC8, 0x40, 0x13, "GD25Q40", 524288, 256, SPIMemChipVendorGIGADEVICE, SPIMemChipWriteModePage}, + {0xC8, 0x40, 0x14, "GD25Q80", 1048576, 256, SPIMemChipVendorGIGADEVICE, SPIMemChipWriteModePage}, + {0xC8, + 0x40, + 0x14, + "GD25Q80B", + 1048576, + 256, + SPIMemChipVendorGIGADEVICE, + SPIMemChipWriteModePage}, + {0xC8, + 0x40, + 0x14, + "GD25Q80C", + 1048576, + 256, + SPIMemChipVendorGIGADEVICE, + SPIMemChipWriteModePage}, + {0xC8, 0x40, 0x15, "GD25Q16", 2097152, 256, SPIMemChipVendorGIGADEVICE, SPIMemChipWriteModePage}, + {0xC8, + 0x40, + 0x15, + "GD25Q16B", + 2097152, + 256, + SPIMemChipVendorGIGADEVICE, + SPIMemChipWriteModePage}, + {0xC8, 0x40, 0x16, "GD25Q32", 4194304, 256, SPIMemChipVendorGIGADEVICE, SPIMemChipWriteModePage}, + {0xC8, + 0x40, + 0x16, + "GD25Q32B", + 4194304, + 256, + SPIMemChipVendorGIGADEVICE, + SPIMemChipWriteModePage}, + {0xC8, 0x40, 0x17, "GD25Q64", 8388608, 256, SPIMemChipVendorGIGADEVICE, SPIMemChipWriteModePage}, + {0xC8, + 0x40, + 0x17, + "GD25Q64B", + 8388608, + 256, + SPIMemChipVendorGIGADEVICE, + SPIMemChipWriteModePage}, + {0xC8, + 0x40, + 0x17, + "GD25B64C", + 8388608, + 256, + SPIMemChipVendorGIGADEVICE, + SPIMemChipWriteModePage}, + {0xC8, + 0x40, + 0x18, + "GD25Q128B", + 16777216, + 256, + SPIMemChipVendorGIGADEVICE, + SPIMemChipWriteModePage}, + {0xC8, + 0x40, + 0x18, + "GD25Q128C", + 16777216, + 256, + SPIMemChipVendorGIGADEVICE, + SPIMemChipWriteModePage}, + {0xC8, + 0x60, + 0x17, + "GD25LQ064C_1.8V", + 8388608, + 256, + SPIMemChipVendorGIGADEVICE, + SPIMemChipWriteModePage}, + {0xC8, + 0x60, + 0x18, + "GD25LQ128C_1.8V", + 16777216, + 256, + SPIMemChipVendorGIGADEVICE, + SPIMemChipWriteModePage}, + {0xC8, + 0x60, + 0x19, + "GD25LQ256C_1.8V", + 33554432, + 256, + SPIMemChipVendorGIGADEVICE, + SPIMemChipWriteModePage}, + {0xC8, 0x31, 0x14, "MD25T80", 1048576, 256, SPIMemChipVendorGIGADEVICE, SPIMemChipWriteModePage}, + {0x51, 0x40, 0x12, "MD25D20", 262144, 256, SPIMemChipVendorGIGADEVICE, SPIMemChipWriteModePage}, + {0x51, 0x40, 0x13, "MD25D40", 524288, 256, SPIMemChipVendorGIGADEVICE, SPIMemChipWriteModePage}, + {0x51, 0x40, 0x14, "MD25D80", 1048576, 256, SPIMemChipVendorGIGADEVICE, SPIMemChipWriteModePage}, + {0x51, 0x40, 0x15, "MD25D16", 2097152, 256, SPIMemChipVendorGIGADEVICE, SPIMemChipWriteModePage}, + {0x1C, 0x20, 0x10, "ICE25P05", 65536, 128, SPIMemChipVendorICE, SPIMemChipWriteModePage}, + {0x89, 0x89, 0x11, "QB25F016S33B", 2097152, 256, SPIMemChipVendorINTEL, SPIMemChipWriteModePage}, + {0x89, 0x89, 0x11, "QB25F160S33B", 2097152, 256, SPIMemChipVendorINTEL, SPIMemChipWriteModePage}, + {0x89, 0x89, 0x12, "QB25F320S33B", 4194304, 256, SPIMemChipVendorINTEL, SPIMemChipWriteModePage}, + {0x89, 0x89, 0x13, "QB25F640S33B", 8388608, 256, SPIMemChipVendorINTEL, SPIMemChipWriteModePage}, + {0x89, 0x89, 0x11, "QH25F016S33B", 2097152, 256, SPIMemChipVendorINTEL, SPIMemChipWriteModePage}, + {0x89, 0x89, 0x11, "QH25F160S33B", 2097152, 256, SPIMemChipVendorINTEL, SPIMemChipWriteModePage}, + {0x89, 0x89, 0x12, "QH25F320S33B", 4194304, 256, SPIMemChipVendorINTEL, SPIMemChipWriteModePage}, + {0xC2, 0x20, 0x11, "KH25L1005", 131072, 256, SPIMemChipVendorKHIC, SPIMemChipWriteModePage}, + {0xC2, 0x20, 0x11, "KH25L1005A", 131072, 256, SPIMemChipVendorKHIC, SPIMemChipWriteModePage}, + {0xC2, 0x20, 0x12, "KH25L2005", 262144, 256, SPIMemChipVendorKHIC, SPIMemChipWriteModePage}, + {0xC2, 0x20, 0x13, "KH25L4005", 524288, 256, SPIMemChipVendorKHIC, SPIMemChipWriteModePage}, + {0xC2, 0x20, 0x13, "KH25L4005A", 524288, 256, SPIMemChipVendorKHIC, SPIMemChipWriteModePage}, + {0xC2, 0x20, 0x10, "KH25L512", 65536, 256, SPIMemChipVendorKHIC, SPIMemChipWriteModePage}, + {0xC2, 0x20, 0x10, "KH25L512A", 65536, 256, SPIMemChipVendorKHIC, SPIMemChipWriteModePage}, + {0xC2, 0x20, 0x14, "KH25L8005", 1048576, 256, SPIMemChipVendorKHIC, SPIMemChipWriteModePage}, + {0xC2, 0x26, 0x15, "KH25L8036D", 1048576, 256, SPIMemChipVendorKHIC, SPIMemChipWriteModePage}, + {0xC2, 0x20, 0x11, "MX25L1005", 131072, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, + {0xC2, 0x20, 0x11, "MX25L1005A", 131072, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, + {0xC2, 0x20, 0x11, "MX25L1005C", 131072, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, + {0xC2, 0x20, 0x11, "MX25L1006E", 131072, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, + {0xC2, 0x22, 0x11, "MX25L1021E", 131072, 32, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, + {0xC2, 0x20, 0x11, "MX25L1025C", 131072, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, + {0xC2, 0x20, 0x11, "MX25L1026E", 131072, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, + {0xC2, + 0x20, + 0x18, + "MX25L12805D", + 16777216, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x20, + 0x18, + "MX25L12835E", + 16777216, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x20, + 0x18, + "MX25L12835F", + 16777216, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x20, + 0x18, + "MX25L12836E", + 16777216, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x20, + 0x18, + "MX25L12839F", + 16777216, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x20, + 0x18, + "MX25L12845E", + 16777216, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x20, + 0x18, + "MX25L12845G", + 16777216, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x20, + 0x18, + "MX25L12845F", + 16777216, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x20, + 0x18, + "MX25L12865E", + 16777216, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x20, + 0x18, + "MX25L12865F", + 16777216, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x20, + 0x18, + "MX25L12873F", + 16777216, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x20, + 0x18, + "MX25L12875F", + 16777216, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x20, + 0x19, + "MX25L25635E", + 33554432, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, 0x20, 0x15, "MX25L1605", 2097152, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, + {0xC2, + 0x20, + 0x15, + "MX25L1605A", + 2097152, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x20, + 0x15, + "MX25L1605D", + 2097152, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x20, + 0x15, + "MX25L1606E", + 2097152, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x24, + 0x15, + "MX25L1633E", + 2097152, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x24, + 0x15, + "MX25L1635D", + 2097152, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x25, + 0x15, + "MX25L1635E", + 2097152, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x24, + 0x15, + "MX25L1636D", + 2097152, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x25, + 0x15, + "MX25L1636E", + 2097152, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x24, + 0x15, + "MX25L1673E", + 2097152, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x24, + 0x15, + "MX25L1675E", + 2097152, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, 0x20, 0x12, "MX25L2005", 262144, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, + {0xC2, 0x20, 0x12, "MX25L2005C", 262144, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, + {0xC2, 0x20, 0x12, "MX25L2006E", 262144, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, + {0xC2, 0x20, 0x12, "MX25L2026C", 262144, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, + {0xC2, 0x20, 0x12, "MX25L2026E", 262144, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, + {0xC2, 0x20, 0x16, "MX25L3205", 4194304, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, + {0xC2, + 0x20, + 0x16, + "MX25L3205A", + 4194304, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x20, + 0x16, + "MX25L3205D", + 4194304, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x20, + 0x16, + "MX25L3206E", + 4194304, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x20, + 0x16, + "MX25L3208E", + 4194304, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x5E, + 0x16, + "MX25L3225D", + 4194304, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x20, + 0x16, + "MX25L3233F", + 4194304, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x5E, + 0x16, + "MX25L3235D", + 4194304, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x20, + 0x16, + "MX25L3235E", + 4194304, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x5E, + 0x16, + "MX25L3236D", + 4194304, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x5E, + 0x16, + "MX25L3237D", + 4194304, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x25, + 0x36, + "MX25L3239E", + 4194304, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x20, + 0x16, + "MX25L3273E", + 4194304, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x20, + 0x16, + "MX25L3273F", + 4194304, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x20, + 0x16, + "MX25L3275E", + 4194304, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, 0x20, 0x13, "MX25L4005", 524288, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, + {0xC2, 0x20, 0x13, "MX25L4005A", 524288, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, + {0xC2, 0x20, 0x13, "MX25L4005C", 524288, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, + {0xC2, 0x20, 0x13, "MX25L4006E", 524288, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, + {0xC2, 0x20, 0x13, "MX25L4026E", 524288, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, + {0xC2, 0x20, 0x10, "MX25L512", 65536, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, + {0xC2, 0x20, 0x10, "MX25L512A", 65536, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, + {0xC2, 0x20, 0x10, "MX25L512C", 65536, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, + {0xC2, 0x22, 0x10, "MX25L5121E", 65536, 32, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, + {0xC2, 0x20, 0x17, "MX25L6405", 8388608, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, + {0xC2, + 0x20, + 0x17, + "MX25L6405D", + 8388608, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x20, + 0x17, + "MX25L6406E", + 8388608, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x20, + 0x17, + "MX25L6408E", + 8388608, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x20, + 0x17, + "MX25L6433F", + 8388608, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x20, + 0x17, + "MX25L6435E", + 8388608, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x20, + 0x17, + "MX25L6436E", + 8388608, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x20, + 0x17, + "MX25L6436F", + 8388608, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x25, + 0x37, + "MX25L6439E", + 8388608, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x20, + 0x17, + "MX25L6445E", + 8388608, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x20, + 0x17, + "MX25L6465E", + 8388608, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x20, + 0x17, + "MX25L6473E", + 8388608, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x20, + 0x17, + "MX25L6473F", + 8388608, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x20, + 0x17, + "MX25L6475E", + 8388608, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, 0x20, 0x14, "MX25L8005", 1048576, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, + {0xC2, + 0x20, + 0x14, + "MX25L8006E", + 1048576, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x20, + 0x14, + "MX25L8008E", + 1048576, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x20, + 0x14, + "MX25L8035E", + 1048576, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x20, + 0x14, + "MX25L8036E", + 1048576, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x20, + 0x14, + "MX25L8073E", + 1048576, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x20, + 0x14, + "MX25L8075E", + 1048576, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x20, + 0x19, + "MX25L25673G", + 33554432, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, 0x28, 0x10, "MX25R512F", 65536, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, + {0xC2, 0x28, 0x11, "MX25R1035F", 131072, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, + {0xC2, + 0x28, + 0x15, + "MX25R1635F", + 2097152, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, 0x28, 0x12, "MX25R2035F", 262144, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, + {0xC2, + 0x28, + 0x16, + "MX25R3235F", + 4194304, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, 0x28, 0x13, "MX25R4035F", 524288, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, + {0xC2, + 0x28, + 0x17, + "MX25R6435F", + 8388608, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x28, + 0x14, + "MX25R8035F", + 1048576, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x25, + 0x31, + "MX25U1001E_1.8V", + 131072, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x25, + 0x18, + "MX25U12835F_1.8V", + 16777216, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x25, + 0x39, + "MX25U25673G_1.8V", + 33554432, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x25, + 0x39, + "MX25U25645G_1.8V", + 33554432, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x25, + 0x35, + "MX25U1635E_1.8V", + 2097152, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x25, + 0x35, + "MX25U1635F_1.8V", + 2097152, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x25, + 0x32, + "MX25U2032E_1.8V", + 262144, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x25, + 0x32, + "MX25U2033E_1.8V", + 262144, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x25, + 0x36, + "MX25U3235E_1.8V", + 4194304, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x25, + 0x36, + "MX25U3235F_1.8V", + 4194304, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x25, + 0x33, + "MX25U4032E_1.8V", + 524288, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x25, + 0x33, + "MX25U4033E_1.8V", + 524288, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x25, + 0x33, + "MX25U4035_1.8V", + 524288, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x25, + 0x30, + "MX25U5121E_1.8V", + 65536, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x25, + 0x37, + "MX25U6435F_1.8V", + 8388608, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x25, + 0x37, + "MX25U6473F_1.8V", + 8388608, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x25, + 0x34, + "MX25U8032E_1.8V", + 1048576, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x25, + 0x34, + "MX25U8033E_1.8V", + 1048576, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x25, + 0x34, + "MX25U8035_1.8V", + 1048576, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x25, + 0x34, + "MX25U8035E_1.8V", + 1048576, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x25, + 0x38, + "MX25U12873F_1.8V", + 16777216, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, 0x20, 0x11, "MX25V1006E", 131072, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, + {0xC2, 0x23, 0x11, "MX25V1035F", 131072, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, + {0xC2, 0x20, 0x12, "MX25V2006E", 262144, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, + {0xC2, 0x23, 0x12, "MX25V2035F", 262144, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, + {0xC2, 0x20, 0x10, "MX25V512", 65536, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, + {0xC2, 0x20, 0x10, "MX25V512C", 65536, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, + {0xC2, 0x20, 0x10, "MX25V512E", 65536, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, + {0xC2, 0x23, 0x10, "MX25V512F", 65536, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, + {0xC2, 0x20, 0x13, "MX25V4005", 524288, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, + {0xC2, 0x20, 0x13, "MX25V4006E", 524288, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, + {0xC2, 0x25, 0x53, "MX25V4035", 524288, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, + {0xC2, 0x23, 0x13, "MX25V4035F", 524288, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, + {0xC2, 0x20, 0x14, "MX25V8005", 1048576, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, + {0xC2, + 0x20, + 0x14, + "MX25V8006E", + 1048576, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, 0x25, 0x54, "MX25V8035", 1048576, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, + {0xC2, + 0x23, + 0x14, + "MX25V8035F", + 1048576, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x25, + 0x3A, + "MX66U51235F_1.8V", + 67108864, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0xC2, + 0x25, + 0x3B, + "MX66U1G45G_1.8V", + 134217728, + 256, + SPIMemChipVendorMACRONIX, + SPIMemChipWriteModePage}, + {0x20, 0xBA, 0x16, "N25Q032A", 4194304, 256, SPIMemChipVendorMICRON, SPIMemChipWriteModePage}, + {0x20, 0xBA, 0x17, "N25Q064A", 8388608, 256, SPIMemChipVendorMICRON, SPIMemChipWriteModePage}, + {0x20, 0xBA, 0x19, "N25Q256A13", 33554432, 256, SPIMemChipVendorMICRON, SPIMemChipWriteModePage}, + {0x20, 0xBA, 0x20, "N25Q512A83", 67108864, 256, SPIMemChipVendorMICRON, SPIMemChipWriteModePage}, + {0x2C, 0xCB, 0x19, "N25W256A11", 33554432, 256, SPIMemChipVendorMICRON, SPIMemChipWriteModePage}, + {0x20, + 0xBA, + 0x18, + "MT25QL128AB", + 16777216, + 256, + SPIMemChipVendorMICRON, + SPIMemChipWriteModePage}, + {0x20, 0xBA, 0x19, "MT25QL256A", 33554432, 256, SPIMemChipVendorMICRON, SPIMemChipWriteModePage}, + {0x20, 0xBA, 0x20, "MT25QL512A", 67108864, 256, SPIMemChipVendorMICRON, SPIMemChipWriteModePage}, + {0x20, + 0xBA, + 0x22, + "MT25QL02GC", + 268435456, + 256, + SPIMemChipVendorMICRON, + SPIMemChipWriteModePage}, + {0x20, 0xBB, 0x19, "MT25QU256", 33554432, 256, SPIMemChipVendorMICRON, SPIMemChipWriteModePage}, + {0x20, + 0xBA, + 0x21, + "N25Q00AA13G", + 134217728, + 256, + SPIMemChipVendorMICRON, + SPIMemChipWriteModePage}, + {0x37, 0x30, 0x10, "MS25X512", 65536, 256, SPIMemChipVendorMSHINE, SPIMemChipWriteModePage}, + {0x37, 0x30, 0x11, "MS25X10", 131072, 256, SPIMemChipVendorMSHINE, SPIMemChipWriteModePage}, + {0x37, 0x30, 0x12, "MS25X20", 262144, 256, SPIMemChipVendorMSHINE, SPIMemChipWriteModePage}, + {0x37, 0x30, 0x13, "MS25X40", 524288, 256, SPIMemChipVendorMSHINE, SPIMemChipWriteModePage}, + {0x37, 0x30, 0x14, "MS25X80", 1048576, 256, SPIMemChipVendorMSHINE, SPIMemChipWriteModePage}, + {0x37, 0x30, 0x15, "MS25X16", 2097152, 256, SPIMemChipVendorMSHINE, SPIMemChipWriteModePage}, + {0x37, 0x30, 0x16, "MS25X32", 4194304, 256, SPIMemChipVendorMSHINE, SPIMemChipWriteModePage}, + {0xD5, 0x30, 0x11, "N25S10", 131072, 256, SPIMemChipVendorNANTRONICS, SPIMemChipWriteModePage}, + {0xD5, 0x30, 0x12, "N25S20", 262144, 256, SPIMemChipVendorNANTRONICS, SPIMemChipWriteModePage}, + {0xD5, 0x30, 0x13, "N25S40", 524288, 256, SPIMemChipVendorNANTRONICS, SPIMemChipWriteModePage}, + {0xD5, 0x30, 0x15, "N25S16", 2097152, 256, SPIMemChipVendorNANTRONICS, SPIMemChipWriteModePage}, + {0xD5, 0x30, 0x16, "N25S32", 4194304, 256, SPIMemChipVendorNANTRONICS, SPIMemChipWriteModePage}, + {0xD5, 0x30, 0x14, "N25S80", 1048576, 256, SPIMemChipVendorNANTRONICS, SPIMemChipWriteModePage}, + {0x9D, 0x7F, 0x7C, "NX25P10", 131072, 256, SPIMemChipVendorNEXFLASH, SPIMemChipWriteModePage}, + {0xEF, 0x20, 0x15, "NX25P16", 2097152, 256, SPIMemChipVendorNEXFLASH, SPIMemChipWriteModePage}, + {0x9D, 0x7F, 0x7D, "NX25P20", 262144, 256, SPIMemChipVendorNEXFLASH, SPIMemChipWriteModePage}, + {0xEF, 0x20, 0x16, "NX25P32", 4194304, 256, SPIMemChipVendorNEXFLASH, SPIMemChipWriteModePage}, + {0x9D, 0x7F, 0x7E, "NX25P40", 524288, 256, SPIMemChipVendorNEXFLASH, SPIMemChipWriteModePage}, + {0x9D, 0x7F, 0x13, "NX25P80", 1048576, 256, SPIMemChipVendorNEXFLASH, SPIMemChipWriteModePage}, + {0x20, 0x40, 0x15, "M45PE16", 2097152, 256, SPIMemChipVendorNUMONYX, SPIMemChipWriteModePage}, + {0x20, 0x20, 0x10, "M25P05", 65536, 128, SPIMemChipVendorNUMONYX, SPIMemChipWriteModePage}, + {0x20, 0x20, 0x10, "M25P05A", 65536, 256, SPIMemChipVendorNUMONYX, SPIMemChipWriteModePage}, + {0x20, 0x20, 0x11, "M25P10", 131072, 128, SPIMemChipVendorNUMONYX, SPIMemChipWriteModePage}, + {0x20, 0x20, 0x11, "M25P10A", 131072, 256, SPIMemChipVendorNUMONYX, SPIMemChipWriteModePage}, + {0x20, 0x20, 0x12, "M25P20", 262144, 256, SPIMemChipVendorNUMONYX, SPIMemChipWriteModePage}, + {0x20, 0x20, 0x13, "M25P40", 524288, 256, SPIMemChipVendorNUMONYX, SPIMemChipWriteModePage}, + {0x20, 0x20, 0x14, "M25P80", 1048576, 256, SPIMemChipVendorNUMONYX, SPIMemChipWriteModePage}, + {0x20, 0x20, 0x15, "M25P16", 2097152, 256, SPIMemChipVendorNUMONYX, SPIMemChipWriteModePage}, + {0x20, 0x20, 0x16, "M25P32", 4194304, 256, SPIMemChipVendorNUMONYX, SPIMemChipWriteModePage}, + {0x20, 0x20, 0x17, "M25P64", 8388608, 256, SPIMemChipVendorNUMONYX, SPIMemChipWriteModePage}, + {0x20, + 0x20, + 0x18, + "M25P128_ST25P28V6G", + 16777216, + 256, + SPIMemChipVendorNUMONYX, + SPIMemChipWriteModePage}, + {0x20, 0x80, 0x11, "M25PE10", 131072, 256, SPIMemChipVendorNUMONYX, SPIMemChipWriteModePage}, + {0x20, 0x80, 0x15, "M25PE16", 2097152, 256, SPIMemChipVendorNUMONYX, SPIMemChipWriteModePage}, + {0x20, 0x80, 0x12, "M25PE20", 262144, 256, SPIMemChipVendorNUMONYX, SPIMemChipWriteModePage}, + {0x20, 0x80, 0x13, "M25PE40", 524288, 256, SPIMemChipVendorNUMONYX, SPIMemChipWriteModePage}, + {0x20, 0x80, 0x14, "M25PE80", 1048576, 256, SPIMemChipVendorNUMONYX, SPIMemChipWriteModePage}, + {0xBF, 0x43, 0x00, "PCT25LF020A", 262144, 256, SPIMemChipVendorPCT, SPIMemChipWriteModePage}, + {0xBF, 0x49, 0x00, "PCT25VF010A", 131072, 256, SPIMemChipVendorPCT, SPIMemChipWriteModePage}, + {0xBF, 0x25, 0x41, "PCT25VF016B", 2097152, 256, SPIMemChipVendorPCT, SPIMemChipWriteModePage}, + {0xBF, 0x43, 0x00, "PCT25VF020A", 262144, 256, SPIMemChipVendorPCT, SPIMemChipWriteModePage}, + {0xBF, 0x25, 0x4A, "PCT25VF032B", 4194304, 256, SPIMemChipVendorPCT, SPIMemChipWriteModePage}, + {0xBF, 0x44, 0x00, "PCT25VF040A", 524288, 256, SPIMemChipVendorPCT, SPIMemChipWriteModePage}, + {0xBF, 0x25, 0x8D, "PCT25VF040B", 524288, 256, SPIMemChipVendorPCT, SPIMemChipWriteModePage}, + {0xBF, 0x25, 0x8E, "PCT25VF080B", 1048576, 256, SPIMemChipVendorPCT, SPIMemChipWriteModePage}, + {0x01, 0x02, 0x10, "S25FL001D", 131072, 256, SPIMemChipVendorSPANSION, SPIMemChipWriteModePage}, + {0x01, 0x02, 0x11, "S25FL002D", 262144, 256, SPIMemChipVendorSPANSION, SPIMemChipWriteModePage}, + {0x01, 0x02, 0x12, "S25FL004A", 524288, 256, SPIMemChipVendorSPANSION, SPIMemChipWriteModePage}, + {0x01, 0x02, 0x12, "S25FL004D", 524288, 256, SPIMemChipVendorSPANSION, SPIMemChipWriteModePage}, + {0xEF, 0x40, 0x13, "S25FL004K", 524288, 256, SPIMemChipVendorSPANSION, SPIMemChipWriteModePage}, + {0x01, 0x02, 0x13, "S25FL008A", 1048576, 256, SPIMemChipVendorSPANSION, SPIMemChipWriteModePage}, + {0x01, 0x02, 0x13, "S25FL008D", 1048576, 256, SPIMemChipVendorSPANSION, SPIMemChipWriteModePage}, + {0xEF, 0x40, 0x14, "S25FL008K", 1048576, 256, SPIMemChipVendorSPANSION, SPIMemChipWriteModePage}, + {0x01, 0x02, 0x14, "S25FL016A", 2097152, 256, SPIMemChipVendorSPANSION, SPIMemChipWriteModePage}, + {0xEF, 0x40, 0x15, "S25FL016K", 2097152, 256, SPIMemChipVendorSPANSION, SPIMemChipWriteModePage}, + {0x01, 0x02, 0x15, "S25FL032A", 4194304, 256, SPIMemChipVendorSPANSION, SPIMemChipWriteModePage}, + {0xEF, 0x40, 0x16, "S25FL032K", 4194304, 256, SPIMemChipVendorSPANSION, SPIMemChipWriteModePage}, + {0x01, 0x02, 0x15, "S25FL032P", 4194304, 256, SPIMemChipVendorSPANSION, SPIMemChipWriteModePage}, + {0x01, 0x02, 0x12, "S25FL040A", 524288, 256, SPIMemChipVendorSPANSION, SPIMemChipWriteModePage}, + {0x01, + 0x02, + 0x26, + "S25FL040A_BOT", + 524288, + 256, + SPIMemChipVendorSPANSION, + SPIMemChipWriteModePage}, + {0x01, + 0x02, + 0x25, + "S25FL040A_TOP", + 524288, + 256, + SPIMemChipVendorSPANSION, + SPIMemChipWriteModePage}, + {0x01, 0x02, 0x16, "S25FL064A", 8388608, 256, SPIMemChipVendorSPANSION, SPIMemChipWriteModePage}, + {0xEF, 0x40, 0x17, "S25FL064K", 8388608, 256, SPIMemChipVendorSPANSION, SPIMemChipWriteModePage}, + {0x01, 0x02, 0x16, "S25FL064P", 8388608, 256, SPIMemChipVendorSPANSION, SPIMemChipWriteModePage}, + {0x01, 0x40, 0x15, "S25FL116K", 2097152, 256, SPIMemChipVendorSPANSION, SPIMemChipWriteModePage}, + {0xEF, + 0x40, + 0x18, + "S25FL128K", + 16777216, + 256, + SPIMemChipVendorSPANSION, + SPIMemChipWriteModePage}, + {0x01, + 0x20, + 0x18, + "S25FL128P", + 16777216, + 256, + SPIMemChipVendorSPANSION, + SPIMemChipWriteModePage}, + {0x01, + 0x20, + 0x18, + "S25FL128S", + 16777216, + 256, + SPIMemChipVendorSPANSION, + SPIMemChipWriteModePage}, + {0x01, 0x40, 0x16, "S25FL132K", 4194304, 256, SPIMemChipVendorSPANSION, SPIMemChipWriteModePage}, + {0x01, 0x40, 0x17, "S25FL164K", 8388608, 256, SPIMemChipVendorSPANSION, SPIMemChipWriteModePage}, + {0x01, + 0x02, + 0x19, + "S25FL256S", + 33554432, + 256, + SPIMemChipVendorSPANSION, + SPIMemChipWriteModePage}, + {0xBF, 0x25, 0x41, "SST25VF016B", 2097152, 1, SPIMemChipVendorSST, SPIMemChipWriteModeAAIWord}, + {0xBF, 0x25, 0x8C, "SST25VF020B", 262144, 1, SPIMemChipVendorSST, SPIMemChipWriteModeAAIWord}, + {0xBF, 0x25, 0x4A, "SST25VF032B", 4194304, 1, SPIMemChipVendorSST, SPIMemChipWriteModeAAIWord}, + {0xBF, 0x25, 0x4B, "SST25VF064C", 8388608, 256, SPIMemChipVendorSST, SPIMemChipWriteModePage}, + {0xBF, 0x25, 0x8D, "SST25VF040B", 524288, 1, SPIMemChipVendorSST, SPIMemChipWriteModeAAIWord}, + {0xBF, 0x25, 0x8E, "SST25VF080B", 1048576, 1, SPIMemChipVendorSST, SPIMemChipWriteModeAAIWord}, + {0x20, 0x71, 0x15, "M25PX16", 2097152, 256, SPIMemChipVendorST, SPIMemChipWriteModePage}, + {0x20, 0x71, 0x16, "M25PX32", 4194304, 256, SPIMemChipVendorST, SPIMemChipWriteModePage}, + {0x20, 0x71, 0x17, "M25PX64", 8388608, 256, SPIMemChipVendorST, SPIMemChipWriteModePage}, + {0x20, 0x71, 0x14, "M25PX80", 1048576, 256, SPIMemChipVendorST, SPIMemChipWriteModePage}, + {0x20, 0x20, 0x10, "ST25P05", 65536, 128, SPIMemChipVendorST, SPIMemChipWriteModePage}, + {0x20, 0x20, 0x10, "ST25P05A", 65536, 256, SPIMemChipVendorST, SPIMemChipWriteModePage}, + {0x20, 0x20, 0x11, "ST25P10", 131072, 128, SPIMemChipVendorST, SPIMemChipWriteModePage}, + {0x20, 0x20, 0x11, "ST25P10A", 131072, 256, SPIMemChipVendorST, SPIMemChipWriteModePage}, + {0x20, 0x20, 0x15, "ST25P16", 2097152, 256, SPIMemChipVendorST, SPIMemChipWriteModePage}, + {0x20, 0x20, 0x12, "ST25P20", 262144, 256, SPIMemChipVendorST, SPIMemChipWriteModePage}, + {0x20, 0x20, 0x16, "ST25P32", 4194304, 256, SPIMemChipVendorST, SPIMemChipWriteModePage}, + {0x20, 0x20, 0x13, "ST25P40", 524288, 256, SPIMemChipVendorST, SPIMemChipWriteModePage}, + {0x20, 0x20, 0x17, "ST25P64", 8388608, 256, SPIMemChipVendorST, SPIMemChipWriteModePage}, + {0x20, 0x20, 0x14, "ST25P80", 1048576, 256, SPIMemChipVendorST, SPIMemChipWriteModePage}, + {0xEF, 0x10, 0x00, "W25P10", 131072, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, 0x20, 0x15, "W25P16", 2097152, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, 0x11, 0x00, "W25P20", 262144, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, 0x20, 0x16, "W25P32", 4194304, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, 0x12, 0x00, "W25P40", 524288, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, 0x20, 0x17, "W25P64", 8388608, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, 0x20, 0x14, "W25P80", 1048576, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, + 0x60, + 0x11, + "W25Q10EW_1.8V", + 131072, + 256, + SPIMemChipVendorWINBOND, + SPIMemChipWriteModePage}, + {0xEF, 0x40, 0x18, "W25Q128BV", 16777216, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, 0x40, 0x18, "W25Q128FV", 16777216, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, 0x70, 0x18, "W25Q128JV", 16777216, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, 0x40, 0x19, "W25Q256FV", 33554432, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, 0x40, 0x19, "W25Q256JV", 33554432, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, 0x70, 0x19, "W25Q256JV", 33554432, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, + 0x60, + 0x18, + "W25Q128FW_1.8V", + 16777216, + 256, + SPIMemChipVendorWINBOND, + SPIMemChipWriteModePage}, + {0xEF, 0x40, 0x15, "W25Q16", 2097152, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, 0x40, 0x15, "W25Q16BV", 2097152, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, 0x40, 0x15, "W25Q16CL", 2097152, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, 0x40, 0x15, "W25Q16CV", 2097152, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, 0x40, 0x15, "W25Q16DV", 2097152, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, + 0x60, + 0x15, + "W25Q16FW_1.8V", + 2097152, + 256, + SPIMemChipVendorWINBOND, + SPIMemChipWriteModePage}, + {0xEF, 0x40, 0x15, "W25Q16V", 2097152, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, 0x40, 0x12, "W25Q20CL", 262144, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, + 0x60, + 0x12, + "W25Q20EW_1.8V", + 262144, + 256, + SPIMemChipVendorWINBOND, + SPIMemChipWriteModePage}, + {0xEF, 0x40, 0x16, "W25Q32", 4194304, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, 0x40, 0x16, "W25Q32BV", 4194304, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, 0x40, 0x16, "W25Q32FV", 4194304, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, + 0x60, + 0x16, + "W25Q32FW_1.8V", + 4194304, + 256, + SPIMemChipVendorWINBOND, + SPIMemChipWriteModePage}, + {0xEF, 0x40, 0x16, "W25Q32V", 4194304, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, 0x40, 0x13, "W25Q40BL", 524288, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, 0x40, 0x13, "W25Q40BV", 524288, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, 0x40, 0x13, "W25Q40CL", 524288, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, + 0x60, + 0x13, + "W25Q40EW_1.8V", + 524288, + 256, + SPIMemChipVendorWINBOND, + SPIMemChipWriteModePage}, + {0xEF, 0x40, 0x17, "W25Q64BV", 8388608, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, 0x40, 0x17, "W25Q64CV", 8388608, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, 0x40, 0x17, "W25Q64FV", 8388608, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, 0x40, 0x17, "W25Q64JV", 8388608, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, + 0x60, + 0x17, + "W25Q64FW_1.8V", + 8388608, + 256, + SPIMemChipVendorWINBOND, + SPIMemChipWriteModePage}, + {0xEF, 0x40, 0x14, "W25Q80BL", 1048576, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, 0x40, 0x14, "W25Q80BV", 1048576, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, + 0x50, + 0x14, + "W25Q80BW_1.8V", + 1048576, + 256, + SPIMemChipVendorWINBOND, + SPIMemChipWriteModePage}, + {0xEF, 0x40, 0x14, "W25Q80DV", 1048576, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, + 0x60, + 0x14, + "W25Q80EW_1.8V", + 1048576, + 256, + SPIMemChipVendorWINBOND, + SPIMemChipWriteModePage}, + {0xEF, 0x30, 0x10, "W25X05", 65536, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, 0x30, 0x10, "W25X05CL", 65536, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, 0x30, 0x11, "W25X10AV", 131072, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, 0x30, 0x11, "W25X10BL", 131072, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, 0x30, 0x11, "W25X10BV", 131072, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, 0x30, 0x11, "W25X10CL", 131072, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, 0x30, 0x11, "W25X10L", 131072, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, 0x30, 0x11, "W25X10V", 131072, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, 0x30, 0x15, "W25X16", 2097152, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, 0x30, 0x15, "W25X16AL", 2097152, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, 0x30, 0x15, "W25X16AV", 2097152, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, 0x30, 0x15, "W25X16BV", 2097152, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, 0x30, 0x15, "W25X16V", 2097152, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, 0x30, 0x12, "W25X20AL", 262144, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, 0x30, 0x12, "W25X20AV", 262144, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, 0x30, 0x12, "W25X20BL", 262144, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, 0x30, 0x12, "W25X20BV", 262144, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, 0x30, 0x12, "W25X20CL", 262144, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, 0x30, 0x12, "W25X20L", 262144, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, 0x30, 0x12, "W25X20V", 262144, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, 0x30, 0x16, "W25X32", 4194304, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, 0x30, 0x16, "W25X32AV", 4194304, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, 0x30, 0x16, "W25X32BV", 4194304, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, 0x30, 0x16, "W25X32V", 4194304, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, 0x30, 0x13, "W25X40AL", 524288, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, 0x30, 0x13, "W25X40AV", 524288, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, 0x30, 0x13, "W25X40BL", 524288, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, 0x30, 0x13, "W25X40BV", 524288, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, 0x30, 0x13, "W25X40CL", 524288, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, 0x30, 0x13, "W25X40L", 524288, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, 0x30, 0x13, "W25X40V", 524288, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, 0x30, 0x17, "W25X64", 8388608, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, 0x30, 0x17, "W25X64BV", 8388608, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, 0x30, 0x17, "W25X64V", 8388608, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, 0x30, 0x14, "W25X80AL", 1048576, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, 0x30, 0x14, "W25X80AV", 1048576, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, 0x30, 0x14, "W25X80BV", 1048576, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, 0x30, 0x14, "W25X80L", 1048576, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, 0x30, 0x14, "W25X80V", 1048576, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, 0x71, 0x19, "W25M512JV", 67108864, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0xEF, 0x40, 0x19, "W25R256JV", 33554432, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, + {0x37, 0x20, 0x10, "TS25L512A", 65536, 256, SPIMemChipVendorZEMPRO, SPIMemChipWriteModePage}, + {0x37, 0x30, 0x11, "TS25L010A", 131072, 256, SPIMemChipVendorZEMPRO, SPIMemChipWriteModePage}, + {0x37, 0x30, 0x12, "TS25L020A", 262144, 256, SPIMemChipVendorZEMPRO, SPIMemChipWriteModePage}, + {0x20, 0x20, 0x15, "TS25L16AP", 2097152, 256, SPIMemChipVendorZEMPRO, SPIMemChipWriteModePage}, + {0x20, 0x20, 0x15, "TS25L16BP", 2097152, 256, SPIMemChipVendorZEMPRO, SPIMemChipWriteModePage}, + {0x37, 0x20, 0x15, "TS25L16P", 2097152, 256, SPIMemChipVendorZEMPRO, SPIMemChipWriteModePage}, + {0x5E, 0x40, 0x15, "ZB25D16", 2097152, 256, SPIMemChipVendorZbit, SPIMemChipWriteModePage}, + {0xE0, 0x40, 0x13, "BG25Q40A", 524288, 256, SPIMemChipVendorBerg_Micro, SPIMemChipWriteModePage}, + {0xE0, + 0x40, + 0x14, + "BG25Q80A", + 1048576, + 256, + SPIMemChipVendorBerg_Micro, + SPIMemChipWriteModePage}, + {0xE0, + 0x40, + 0x15, + "BG25Q16A", + 2097152, + 256, + SPIMemChipVendorBerg_Micro, + SPIMemChipWriteModePage}, + {0xE0, + 0x40, + 0x16, + "BG25Q32A", + 4194304, + 256, + SPIMemChipVendorBerg_Micro, + SPIMemChipWriteModePage}, + {0x1F, 0x23, 0x00, "AT45DB021D", 270336, 264, SPIMemChipVendorATMEL, SPIMemChipWriteModePage}, + {0x1F, 0x24, 0x00, "AT45DB041D", 540672, 264, SPIMemChipVendorATMEL, SPIMemChipWriteModePage}, + {0x1F, 0x26, 0x00, "AT45DB161D", 2162688, 528, SPIMemChipVendorATMEL, SPIMemChipWriteModePage}, + {0x1F, 0x27, 0x01, "AT45DB321D", 4325376, 528, SPIMemChipVendorATMEL, SPIMemChipWriteModePage}, + {0x1F, 0x43, 0x00, "AT25DF021", 262144, 256, SPIMemChipVendorATMEL, SPIMemChipWriteModePage}, + {0x1F, 0x44, 0x00, "AT25DF041", 524288, 256, SPIMemChipVendorATMEL, SPIMemChipWriteModePage}, + {0x1F, 0x44, 0x00, "AT25DF041A", 524288, 256, SPIMemChipVendorATMEL, SPIMemChipWriteModePage}, + {0x1F, 0x84, 0x00, "AT25SF041", 524288, 256, SPIMemChipVendorATMEL, SPIMemChipWriteModePage}, + {0x1F, 0x45, 0x00, "AT25DF081", 1048576, 256, SPIMemChipVendorATMEL, SPIMemChipWriteModePage}, + {0x1F, 0x45, 0x00, "AT25DF081A", 1048576, 256, SPIMemChipVendorATMEL, SPIMemChipWriteModePage}, + {0x1F, 0x46, 0x00, "AT25DF161", 2097152, 256, SPIMemChipVendorATMEL, SPIMemChipWriteModePage}, + {0x1F, 0x47, 0x00, "AT25DF321", 4194304, 256, SPIMemChipVendorATMEL, SPIMemChipWriteModePage}, + {0x1F, 0x47, 0x00, "AT25DF321A", 4194304, 256, SPIMemChipVendorATMEL, SPIMemChipWriteModePage}, + {0x1F, 0x48, 0x00, "AT25DF641", 8388608, 256, SPIMemChipVendorATMEL, SPIMemChipWriteModePage}, + {0x1F, 0x65, 0x00, "AT25F512B", 65536, 256, SPIMemChipVendorATMEL, SPIMemChipWriteModePage}, + {0x1F, 0x45, 0x00, "AT26DF081", 1048576, 256, SPIMemChipVendorATMEL, SPIMemChipWriteModePage}, + {0x1F, 0x45, 0x00, "AT26DF081A", 1048576, 256, SPIMemChipVendorATMEL, SPIMemChipWriteModePage}, + {0x1F, 0x46, 0x00, "AT26DF161", 2097152, 256, SPIMemChipVendorATMEL, SPIMemChipWriteModePage}, + {0x1F, 0x46, 0x00, "AT26DF161A", 2097152, 256, SPIMemChipVendorATMEL, SPIMemChipWriteModePage}, + {0x1F, 0x47, 0x00, "AT26DF321", 4194304, 256, SPIMemChipVendorATMEL, SPIMemChipWriteModePage}, + {0x1F, 0x47, 0x00, "AT26DF321A", 4194304, 256, SPIMemChipVendorATMEL, SPIMemChipWriteModePage}, + {0x1F, 0x04, 0x00, "AT26F004", 524288, 256, SPIMemChipVendorATMEL, SPIMemChipWriteModePage}, + {0xE0, + 0x60, + 0x18, + "ACE25A128G_1.8V", + 16777216, + 256, + SPIMemChipVendorACE, + SPIMemChipWriteModePage}, + {0x9B, 0x32, 0x16, "ATO25Q32", 4194304, 256, SPIMemChipVendorATO, SPIMemChipWriteModePage}, + {0x54, 0x40, 0x17, "DQ25Q64A", 8388608, 256, SPIMemChipVendorDOUQI, SPIMemChipWriteModePage}, + {0x0E, 0x40, 0x15, "FT25H16", 2097152, 256, SPIMemChipVendorFremont, SPIMemChipWriteModePage}, + {0xA1, 0x40, 0x13, "FM25Q04A", 524288, 256, SPIMemChipVendorFudan, SPIMemChipWriteModePage}, + {0xA1, 0x40, 0x16, "FM25Q32", 4194304, 256, SPIMemChipVendorFudan, SPIMemChipWriteModePage}, + {0xE0, 0x40, 0x14, "GT25Q80A", 1048576, 256, SPIMemChipVendorGenitop, SPIMemChipWriteModePage}, + {0xE0, 0x40, 0x13, "PN25F04A", 524288, 256, SPIMemChipVendorParagon, SPIMemChipWriteModePage}}; diff --git a/applications/plugins/spi_mem_manager/lib/spi/spi_mem_chip_i.h b/applications/plugins/spi_mem_manager/lib/spi/spi_mem_chip_i.h new file mode 100644 index 00000000000..30d6070945b --- /dev/null +++ b/applications/plugins/spi_mem_manager/lib/spi/spi_mem_chip_i.h @@ -0,0 +1,85 @@ +#pragma once + +#include +#include "spi_mem_chip.h" + +typedef enum { + SPIMemChipVendorUnknown, + SPIMemChipVendorADESTO, + SPIMemChipVendorAMIC, + SPIMemChipVendorBoya, + SPIMemChipVendorEON, + SPIMemChipVendorPFLASH, + SPIMemChipVendorTERRA, + SPIMemChipVendorGeneralplus, + SPIMemChipVendorDEUTRON, + SPIMemChipVendorEFST, + SPIMemChipVendorEXCELSEMI, + SPIMemChipVendorFIDELIX, + SPIMemChipVendorGIGADEVICE, + SPIMemChipVendorICE, + SPIMemChipVendorINTEL, + SPIMemChipVendorKHIC, + SPIMemChipVendorMACRONIX, + SPIMemChipVendorMICRON, + SPIMemChipVendorMSHINE, + SPIMemChipVendorNANTRONICS, + SPIMemChipVendorNEXFLASH, + SPIMemChipVendorNUMONYX, + SPIMemChipVendorPCT, + SPIMemChipVendorSPANSION, + SPIMemChipVendorSST, + SPIMemChipVendorST, + SPIMemChipVendorWINBOND, + SPIMemChipVendorZEMPRO, + SPIMemChipVendorZbit, + SPIMemChipVendorBerg_Micro, + SPIMemChipVendorATMEL, + SPIMemChipVendorACE, + SPIMemChipVendorATO, + SPIMemChipVendorDOUQI, + SPIMemChipVendorFremont, + SPIMemChipVendorFudan, + SPIMemChipVendorGenitop, + SPIMemChipVendorParagon +} SPIMemChipVendor; + +typedef enum { + SPIMemChipCMDReadJEDECChipID = 0x9F, + SPIMemChipCMDReadData = 0x03, + SPIMemChipCMDChipErase = 0xC7, + SPIMemChipCMDWriteEnable = 0x06, + SPIMemChipCMDWriteDisable = 0x04, + SPIMemChipCMDReadStatus = 0x05, + SPIMemChipCMDWriteData = 0x02, + SPIMemChipCMDReleasePowerDown = 0xAB +} SPIMemChipCMD; + +enum SPIMemChipStatusBit { + SPIMemChipStatusBitBusy = (0x01 << 0), + SPIMemChipStatusBitWriteEnabled = (0x01 << 1), + SPIMemChipStatusBitBitProtection1 = (0x01 << 2), + SPIMemChipStatusBitBitProtection2 = (0x01 << 3), + SPIMemChipStatusBitBitProtection3 = (0x01 << 4), + SPIMemChipStatusBitTopBottomProtection = (0x01 << 5), + SPIMemChipStatusBitSectorProtect = (0x01 << 6), + SPIMemChipStatusBitRegisterProtect = (0x01 << 7) +}; + +typedef struct { + const char* vendor_name; + SPIMemChipVendor vendor_enum; +} SPIMemChipVendorName; + +struct SPIMemChip { + uint8_t vendor_id; + uint8_t type_id; + uint8_t capacity_id; + const char* model_name; + size_t size; + size_t page_size; + SPIMemChipVendor vendor_enum; + SPIMemChipWriteMode write_mode; +}; + +extern const SPIMemChip SPIMemChips[]; diff --git a/applications/plugins/spi_mem_manager/lib/spi/spi_mem_tools.c b/applications/plugins/spi_mem_manager/lib/spi/spi_mem_tools.c new file mode 100644 index 00000000000..3518ca25c01 --- /dev/null +++ b/applications/plugins/spi_mem_manager/lib/spi/spi_mem_tools.c @@ -0,0 +1,152 @@ +#include +#include +#include "spi_mem_chip_i.h" +#include "spi_mem_tools.h" + +static uint8_t spi_mem_tools_addr_to_byte_arr(uint32_t addr, uint8_t* cmd) { + uint8_t len = 3; // TODO(add support of 4 bytes address mode) + for(uint8_t i = 0; i < len; i++) { + cmd[i] = (addr >> ((len - (i + 1)) * 8)) & 0xFF; + } + return len; +} + +static bool spi_mem_tools_trx( + SPIMemChipCMD cmd, + uint8_t* tx_buf, + size_t tx_size, + uint8_t* rx_buf, + size_t rx_size) { + bool success = false; + furi_hal_spi_acquire(&furi_hal_spi_bus_handle_external); + do { + if(!furi_hal_spi_bus_tx( + &furi_hal_spi_bus_handle_external, (uint8_t*)&cmd, 1, SPI_MEM_SPI_TIMEOUT)) + break; + if(tx_buf) { + if(!furi_hal_spi_bus_tx( + &furi_hal_spi_bus_handle_external, tx_buf, tx_size, SPI_MEM_SPI_TIMEOUT)) + break; + } + if(rx_buf) { + if(!furi_hal_spi_bus_rx( + &furi_hal_spi_bus_handle_external, rx_buf, rx_size, SPI_MEM_SPI_TIMEOUT)) + break; + } + success = true; + } while(0); + furi_hal_spi_release(&furi_hal_spi_bus_handle_external); + return success; +} + +static bool spi_mem_tools_write_buffer(uint8_t* data, size_t size, size_t offset) { + furi_hal_spi_acquire(&furi_hal_spi_bus_handle_external); + uint8_t cmd = (uint8_t)SPIMemChipCMDWriteData; + uint8_t address[4]; + uint8_t address_size = spi_mem_tools_addr_to_byte_arr(offset, address); + bool success = false; + do { + if(!furi_hal_spi_bus_tx(&furi_hal_spi_bus_handle_external, &cmd, 1, SPI_MEM_SPI_TIMEOUT)) + break; + if(!furi_hal_spi_bus_tx( + &furi_hal_spi_bus_handle_external, address, address_size, SPI_MEM_SPI_TIMEOUT)) + break; + if(!furi_hal_spi_bus_tx(&furi_hal_spi_bus_handle_external, data, size, SPI_MEM_SPI_TIMEOUT)) + break; + success = true; + } while(0); + furi_hal_spi_release(&furi_hal_spi_bus_handle_external); + return success; +} + +bool spi_mem_tools_read_chip_info(SPIMemChip* chip) { + uint8_t rx_buf[3] = {0, 0, 0}; + do { + if(!spi_mem_tools_trx(SPIMemChipCMDReadJEDECChipID, NULL, 0, rx_buf, 3)) break; + if(rx_buf[0] == 0 || rx_buf[0] == 255) break; + chip->vendor_id = rx_buf[0]; + chip->type_id = rx_buf[1]; + chip->capacity_id = rx_buf[2]; + return true; + } while(0); + return false; +} + +bool spi_mem_tools_check_chip_info(SPIMemChip* chip) { + SPIMemChip new_chip_info; + spi_mem_tools_read_chip_info(&new_chip_info); + do { + if(chip->vendor_id != new_chip_info.vendor_id) break; + if(chip->type_id != new_chip_info.type_id) break; + if(chip->capacity_id != new_chip_info.capacity_id) break; + return true; + } while(0); + return false; +} + +bool spi_mem_tools_read_block(SPIMemChip* chip, size_t offset, uint8_t* data, size_t block_size) { + if(!spi_mem_tools_check_chip_info(chip)) return false; + for(size_t i = 0; i < block_size; i += SPI_MEM_MAX_BLOCK_SIZE) { + uint8_t cmd[4]; + if((offset + SPI_MEM_MAX_BLOCK_SIZE) > chip->size) return false; + if(!spi_mem_tools_trx( + SPIMemChipCMDReadData, + cmd, + spi_mem_tools_addr_to_byte_arr(offset, cmd), + data, + SPI_MEM_MAX_BLOCK_SIZE)) + return false; + offset += SPI_MEM_MAX_BLOCK_SIZE; + data += SPI_MEM_MAX_BLOCK_SIZE; + } + return true; +} + +size_t spi_mem_tools_get_file_max_block_size(SPIMemChip* chip) { + UNUSED(chip); + return (SPI_MEM_FILE_BUFFER_SIZE); +} + +SPIMemChipStatus spi_mem_tools_get_chip_status(SPIMemChip* chip) { + UNUSED(chip); + uint8_t status; + if(!spi_mem_tools_trx(SPIMemChipCMDReadStatus, NULL, 0, &status, 1)) + return SPIMemChipStatusError; + if(status & SPIMemChipStatusBitBusy) return SPIMemChipStatusBusy; + return SPIMemChipStatusIdle; +} + +static bool spi_mem_tools_set_write_enabled(SPIMemChip* chip, bool enable) { + UNUSED(chip); + uint8_t status; + SPIMemChipCMD cmd = SPIMemChipCMDWriteDisable; + if(enable) cmd = SPIMemChipCMDWriteEnable; + do { + if(!spi_mem_tools_trx(cmd, NULL, 0, NULL, 0)) break; + if(!spi_mem_tools_trx(SPIMemChipCMDReadStatus, NULL, 0, &status, 1)) break; + if(!(status & SPIMemChipStatusBitWriteEnabled) && enable) break; + if((status & SPIMemChipStatusBitWriteEnabled) && !enable) break; + return true; + } while(0); + return false; +} + +bool spi_mem_tools_erase_chip(SPIMemChip* chip) { + do { + if(!spi_mem_tools_set_write_enabled(chip, true)) break; + if(!spi_mem_tools_trx(SPIMemChipCMDChipErase, NULL, 0, NULL, 0)) break; + return true; + } while(0); + return true; +} + +bool spi_mem_tools_write_bytes(SPIMemChip* chip, size_t offset, uint8_t* data, size_t block_size) { + do { + if(!spi_mem_tools_check_chip_info(chip)) break; + if(!spi_mem_tools_set_write_enabled(chip, true)) break; + if((offset + block_size) > chip->size) break; + if(!spi_mem_tools_write_buffer(data, block_size, offset)) break; + return true; + } while(0); + return false; +} diff --git a/applications/plugins/spi_mem_manager/lib/spi/spi_mem_tools.h b/applications/plugins/spi_mem_manager/lib/spi/spi_mem_tools.h new file mode 100644 index 00000000000..ad006b8fff9 --- /dev/null +++ b/applications/plugins/spi_mem_manager/lib/spi/spi_mem_tools.h @@ -0,0 +1,14 @@ +#pragma once + +#include "spi_mem_chip.h" + +#define SPI_MEM_SPI_TIMEOUT 1000 +#define SPI_MEM_MAX_BLOCK_SIZE 256 +#define SPI_MEM_FILE_BUFFER_SIZE 4096 + +bool spi_mem_tools_read_chip_info(SPIMemChip* chip); +bool spi_mem_tools_read_block(SPIMemChip* chip, size_t offset, uint8_t* data, size_t block_size); +size_t spi_mem_tools_get_file_max_block_size(SPIMemChip* chip); +SPIMemChipStatus spi_mem_tools_get_chip_status(SPIMemChip* chip); +bool spi_mem_tools_erase_chip(SPIMemChip* chip); +bool spi_mem_tools_write_bytes(SPIMemChip* chip, size_t offset, uint8_t* data, size_t block_size); diff --git a/applications/plugins/spi_mem_manager/lib/spi/spi_mem_worker.c b/applications/plugins/spi_mem_manager/lib/spi/spi_mem_worker.c new file mode 100644 index 00000000000..438f338f130 --- /dev/null +++ b/applications/plugins/spi_mem_manager/lib/spi/spi_mem_worker.c @@ -0,0 +1,129 @@ +#include "spi_mem_worker_i.h" + +typedef enum { + SPIMemEventStopThread = (1 << 0), + SPIMemEventChipDetect = (1 << 1), + SPIMemEventRead = (1 << 2), + SPIMemEventVerify = (1 << 3), + SPIMemEventErase = (1 << 4), + SPIMemEventWrite = (1 << 5), + SPIMemEventAll = + (SPIMemEventStopThread | SPIMemEventChipDetect | SPIMemEventRead | SPIMemEventVerify | + SPIMemEventErase | SPIMemEventWrite) +} SPIMemEventEventType; + +static int32_t spi_mem_worker_thread(void* thread_context); + +SPIMemWorker* spi_mem_worker_alloc() { + SPIMemWorker* worker = malloc(sizeof(SPIMemWorker)); + worker->callback = NULL; + worker->thread = furi_thread_alloc(); + worker->mode_index = SPIMemWorkerModeIdle; + furi_thread_set_name(worker->thread, "SPIMemWorker"); + furi_thread_set_callback(worker->thread, spi_mem_worker_thread); + furi_thread_set_context(worker->thread, worker); + furi_thread_set_stack_size(worker->thread, 10240); + return worker; +} + +void spi_mem_worker_free(SPIMemWorker* worker) { + furi_thread_free(worker->thread); + free(worker); +} + +bool spi_mem_worker_check_for_stop(SPIMemWorker* worker) { + UNUSED(worker); + uint32_t flags = furi_thread_flags_get(); + return (flags & SPIMemEventStopThread); +} + +static int32_t spi_mem_worker_thread(void* thread_context) { + SPIMemWorker* worker = thread_context; + while(true) { + uint32_t flags = furi_thread_flags_wait(SPIMemEventAll, FuriFlagWaitAny, FuriWaitForever); + if(flags != (unsigned)FuriFlagErrorTimeout) { + if(flags & SPIMemEventStopThread) break; + if(flags & SPIMemEventChipDetect) worker->mode_index = SPIMemWorkerModeChipDetect; + if(flags & SPIMemEventRead) worker->mode_index = SPIMemWorkerModeRead; + if(flags & SPIMemEventVerify) worker->mode_index = SPIMemWorkerModeVerify; + if(flags & SPIMemEventErase) worker->mode_index = SPIMemWorkerModeErase; + if(flags & SPIMemEventWrite) worker->mode_index = SPIMemWorkerModeWrite; + if(spi_mem_worker_modes[worker->mode_index].process) { + spi_mem_worker_modes[worker->mode_index].process(worker); + } + worker->mode_index = SPIMemWorkerModeIdle; + } + } + return 0; +} + +void spi_mem_worker_start_thread(SPIMemWorker* worker) { + furi_thread_start(worker->thread); +} + +void spi_mem_worker_stop_thread(SPIMemWorker* worker) { + furi_thread_flags_set(furi_thread_get_id(worker->thread), SPIMemEventStopThread); + furi_thread_join(worker->thread); +} + +void spi_mem_worker_chip_detect_start( + SPIMemChip* chip_info, + found_chips_t* found_chips, + SPIMemWorker* worker, + SPIMemWorkerCallback callback, + void* context) { + furi_check(worker->mode_index == SPIMemWorkerModeIdle); + worker->callback = callback; + worker->cb_ctx = context; + worker->chip_info = chip_info; + worker->found_chips = found_chips; + furi_thread_flags_set(furi_thread_get_id(worker->thread), SPIMemEventChipDetect); +} + +void spi_mem_worker_read_start( + SPIMemChip* chip_info, + SPIMemWorker* worker, + SPIMemWorkerCallback callback, + void* context) { + furi_check(worker->mode_index == SPIMemWorkerModeIdle); + worker->callback = callback; + worker->cb_ctx = context; + worker->chip_info = chip_info; + furi_thread_flags_set(furi_thread_get_id(worker->thread), SPIMemEventRead); +} + +void spi_mem_worker_verify_start( + SPIMemChip* chip_info, + SPIMemWorker* worker, + SPIMemWorkerCallback callback, + void* context) { + furi_check(worker->mode_index == SPIMemWorkerModeIdle); + worker->callback = callback; + worker->cb_ctx = context; + worker->chip_info = chip_info; + furi_thread_flags_set(furi_thread_get_id(worker->thread), SPIMemEventVerify); +} + +void spi_mem_worker_erase_start( + SPIMemChip* chip_info, + SPIMemWorker* worker, + SPIMemWorkerCallback callback, + void* context) { + furi_check(worker->mode_index == SPIMemWorkerModeIdle); + worker->callback = callback; + worker->cb_ctx = context; + worker->chip_info = chip_info; + furi_thread_flags_set(furi_thread_get_id(worker->thread), SPIMemEventErase); +} + +void spi_mem_worker_write_start( + SPIMemChip* chip_info, + SPIMemWorker* worker, + SPIMemWorkerCallback callback, + void* context) { + furi_check(worker->mode_index == SPIMemWorkerModeIdle); + worker->callback = callback; + worker->cb_ctx = context; + worker->chip_info = chip_info; + furi_thread_flags_set(furi_thread_get_id(worker->thread), SPIMemEventWrite); +} diff --git a/applications/plugins/spi_mem_manager/lib/spi/spi_mem_worker.h b/applications/plugins/spi_mem_manager/lib/spi/spi_mem_worker.h new file mode 100644 index 00000000000..c3761cd5a77 --- /dev/null +++ b/applications/plugins/spi_mem_manager/lib/spi/spi_mem_worker.h @@ -0,0 +1,54 @@ +#pragma once + +#include +#include "spi_mem_chip.h" + +typedef struct SPIMemWorker SPIMemWorker; + +typedef struct { + void (*const process)(SPIMemWorker* worker); +} SPIMemWorkerModeType; + +typedef enum { + SPIMemCustomEventWorkerChipIdentified, + SPIMemCustomEventWorkerChipUnknown, + SPIMemCustomEventWorkerBlockReaded, + SPIMemCustomEventWorkerChipFail, + SPIMemCustomEventWorkerFileFail, + SPIMemCustomEventWorkerDone, + SPIMemCustomEventWorkerVerifyFail, +} SPIMemCustomEventWorker; + +typedef void (*SPIMemWorkerCallback)(void* context, SPIMemCustomEventWorker event); + +SPIMemWorker* spi_mem_worker_alloc(); +void spi_mem_worker_free(SPIMemWorker* worker); +void spi_mem_worker_start_thread(SPIMemWorker* worker); +void spi_mem_worker_stop_thread(SPIMemWorker* worker); +bool spi_mem_worker_check_for_stop(SPIMemWorker* worker); +void spi_mem_worker_chip_detect_start( + SPIMemChip* chip_info, + found_chips_t* found_chips, + SPIMemWorker* worker, + SPIMemWorkerCallback callback, + void* context); +void spi_mem_worker_read_start( + SPIMemChip* chip_info, + SPIMemWorker* worker, + SPIMemWorkerCallback callback, + void* context); +void spi_mem_worker_verify_start( + SPIMemChip* chip_info, + SPIMemWorker* worker, + SPIMemWorkerCallback callback, + void* context); +void spi_mem_worker_erase_start( + SPIMemChip* chip_info, + SPIMemWorker* worker, + SPIMemWorkerCallback callback, + void* context); +void spi_mem_worker_write_start( + SPIMemChip* chip_info, + SPIMemWorker* worker, + SPIMemWorkerCallback callback, + void* context); diff --git a/applications/plugins/spi_mem_manager/lib/spi/spi_mem_worker_i.h b/applications/plugins/spi_mem_manager/lib/spi/spi_mem_worker_i.h new file mode 100644 index 00000000000..43e2d228703 --- /dev/null +++ b/applications/plugins/spi_mem_manager/lib/spi/spi_mem_worker_i.h @@ -0,0 +1,24 @@ +#pragma once + +#include "spi_mem_worker.h" + +typedef enum { + SPIMemWorkerModeIdle, + SPIMemWorkerModeChipDetect, + SPIMemWorkerModeRead, + SPIMemWorkerModeVerify, + SPIMemWorkerModeErase, + SPIMemWorkerModeWrite +} SPIMemWorkerMode; + +struct SPIMemWorker { + SPIMemChip* chip_info; + found_chips_t* found_chips; + SPIMemWorkerMode mode_index; + SPIMemWorkerCallback callback; + void* cb_ctx; + FuriThread* thread; + FuriString* file_name; +}; + +extern const SPIMemWorkerModeType spi_mem_worker_modes[]; diff --git a/applications/plugins/spi_mem_manager/lib/spi/spi_mem_worker_modes.c b/applications/plugins/spi_mem_manager/lib/spi/spi_mem_worker_modes.c new file mode 100644 index 00000000000..a393e54903e --- /dev/null +++ b/applications/plugins/spi_mem_manager/lib/spi/spi_mem_worker_modes.c @@ -0,0 +1,214 @@ +#include "spi_mem_worker_i.h" +#include "spi_mem_chip.h" +#include "spi_mem_tools.h" +#include "../../spi_mem_files.h" + +static void spi_mem_worker_chip_detect_process(SPIMemWorker* worker); +static void spi_mem_worker_read_process(SPIMemWorker* worker); +static void spi_mem_worker_verify_process(SPIMemWorker* worker); +static void spi_mem_worker_erase_process(SPIMemWorker* worker); +static void spi_mem_worker_write_process(SPIMemWorker* worker); + +const SPIMemWorkerModeType spi_mem_worker_modes[] = { + [SPIMemWorkerModeIdle] = {.process = NULL}, + [SPIMemWorkerModeChipDetect] = {.process = spi_mem_worker_chip_detect_process}, + [SPIMemWorkerModeRead] = {.process = spi_mem_worker_read_process}, + [SPIMemWorkerModeVerify] = {.process = spi_mem_worker_verify_process}, + [SPIMemWorkerModeErase] = {.process = spi_mem_worker_erase_process}, + [SPIMemWorkerModeWrite] = {.process = spi_mem_worker_write_process}}; + +static void spi_mem_worker_run_callback(SPIMemWorker* worker, SPIMemCustomEventWorker event) { + if(worker->callback) { + worker->callback(worker->cb_ctx, event); + } +} + +static bool spi_mem_worker_await_chip_busy(SPIMemWorker* worker) { + while(true) { + furi_delay_tick(10); // to give some time to OS + if(spi_mem_worker_check_for_stop(worker)) return true; + SPIMemChipStatus chip_status = spi_mem_tools_get_chip_status(worker->chip_info); + if(chip_status == SPIMemChipStatusError) return false; + if(chip_status == SPIMemChipStatusBusy) continue; + return true; + } +} + +static size_t spi_mem_worker_modes_get_total_size(SPIMemWorker* worker) { + size_t chip_size = spi_mem_chip_get_size(worker->chip_info); + size_t file_size = spi_mem_file_get_size(worker->cb_ctx); + size_t total_size = chip_size; + if(chip_size > file_size) total_size = file_size; + return total_size; +} + +// ChipDetect +static void spi_mem_worker_chip_detect_process(SPIMemWorker* worker) { + SPIMemCustomEventWorker event; + while(!spi_mem_tools_read_chip_info(worker->chip_info)) { + furi_delay_tick(10); // to give some time to OS + if(spi_mem_worker_check_for_stop(worker)) return; + } + if(spi_mem_chip_find_all(worker->chip_info, *worker->found_chips)) { + event = SPIMemCustomEventWorkerChipIdentified; + } else { + event = SPIMemCustomEventWorkerChipUnknown; + } + spi_mem_worker_run_callback(worker, event); +} + +// Read +static bool spi_mem_worker_read(SPIMemWorker* worker, SPIMemCustomEventWorker* event) { + uint8_t data_buffer[SPI_MEM_FILE_BUFFER_SIZE]; + size_t chip_size = spi_mem_chip_get_size(worker->chip_info); + size_t offset = 0; + bool success = true; + while(true) { + furi_delay_tick(10); // to give some time to OS + size_t block_size = SPI_MEM_FILE_BUFFER_SIZE; + if(spi_mem_worker_check_for_stop(worker)) break; + if(offset >= chip_size) break; + if((offset + block_size) > chip_size) block_size = chip_size - offset; + if(!spi_mem_tools_read_block(worker->chip_info, offset, data_buffer, block_size)) { + *event = SPIMemCustomEventWorkerChipFail; + success = false; + break; + } + if(!spi_mem_file_write_block(worker->cb_ctx, data_buffer, block_size)) { + success = false; + break; + } + offset += block_size; + spi_mem_worker_run_callback(worker, SPIMemCustomEventWorkerBlockReaded); + } + if(success) *event = SPIMemCustomEventWorkerDone; + return success; +} + +static void spi_mem_worker_read_process(SPIMemWorker* worker) { + SPIMemCustomEventWorker event = SPIMemCustomEventWorkerFileFail; + do { + if(!spi_mem_worker_await_chip_busy(worker)) break; + if(!spi_mem_file_create_open(worker->cb_ctx)) break; + if(!spi_mem_worker_read(worker, &event)) break; + } while(0); + spi_mem_file_close(worker->cb_ctx); + spi_mem_worker_run_callback(worker, event); +} + +// Verify +static bool + spi_mem_worker_verify(SPIMemWorker* worker, size_t total_size, SPIMemCustomEventWorker* event) { + uint8_t data_buffer_chip[SPI_MEM_FILE_BUFFER_SIZE]; + uint8_t data_buffer_file[SPI_MEM_FILE_BUFFER_SIZE]; + size_t offset = 0; + bool success = true; + while(true) { + furi_delay_tick(10); // to give some time to OS + size_t block_size = SPI_MEM_FILE_BUFFER_SIZE; + if(spi_mem_worker_check_for_stop(worker)) break; + if(offset >= total_size) break; + if((offset + block_size) > total_size) block_size = total_size - offset; + if(!spi_mem_tools_read_block(worker->chip_info, offset, data_buffer_chip, block_size)) { + *event = SPIMemCustomEventWorkerChipFail; + success = false; + break; + } + if(!spi_mem_file_read_block(worker->cb_ctx, data_buffer_file, block_size)) { + success = false; + break; + } + if(memcmp(data_buffer_chip, data_buffer_file, block_size) != 0) { + *event = SPIMemCustomEventWorkerVerifyFail; + success = false; + break; + } + offset += block_size; + spi_mem_worker_run_callback(worker, SPIMemCustomEventWorkerBlockReaded); + } + if(success) *event = SPIMemCustomEventWorkerDone; + return success; +} + +static void spi_mem_worker_verify_process(SPIMemWorker* worker) { + SPIMemCustomEventWorker event = SPIMemCustomEventWorkerFileFail; + size_t total_size = spi_mem_worker_modes_get_total_size(worker); + do { + if(!spi_mem_worker_await_chip_busy(worker)) break; + if(!spi_mem_file_open(worker->cb_ctx)) break; + if(!spi_mem_worker_verify(worker, total_size, &event)) break; + } while(0); + spi_mem_file_close(worker->cb_ctx); + spi_mem_worker_run_callback(worker, event); +} + +// Erase +static void spi_mem_worker_erase_process(SPIMemWorker* worker) { + SPIMemCustomEventWorker event = SPIMemCustomEventWorkerChipFail; + do { + if(!spi_mem_worker_await_chip_busy(worker)) break; + if(!spi_mem_tools_erase_chip(worker->chip_info)) break; + if(!spi_mem_worker_await_chip_busy(worker)) break; + event = SPIMemCustomEventWorkerDone; + } while(0); + spi_mem_worker_run_callback(worker, event); +} + +// Write +static bool spi_mem_worker_write_block_by_page( + SPIMemWorker* worker, + size_t offset, + uint8_t* data, + size_t block_size, + size_t page_size) { + for(size_t i = 0; i < block_size; i += page_size) { + if(!spi_mem_worker_await_chip_busy(worker)) return false; + if(!spi_mem_tools_write_bytes(worker->chip_info, offset, data, page_size)) return false; + offset += page_size; + data += page_size; + } + return true; +} + +static bool + spi_mem_worker_write(SPIMemWorker* worker, size_t total_size, SPIMemCustomEventWorker* event) { + bool success = true; + uint8_t data_buffer[SPI_MEM_FILE_BUFFER_SIZE]; + size_t page_size = spi_mem_chip_get_page_size(worker->chip_info); + size_t offset = 0; + while(true) { + furi_delay_tick(10); // to give some time to OS + size_t block_size = SPI_MEM_FILE_BUFFER_SIZE; + if(spi_mem_worker_check_for_stop(worker)) break; + if(offset >= total_size) break; + if((offset + block_size) > total_size) block_size = total_size - offset; + if(!spi_mem_file_read_block(worker->cb_ctx, data_buffer, block_size)) { + *event = SPIMemCustomEventWorkerFileFail; + success = false; + break; + } + if(!spi_mem_worker_write_block_by_page( + worker, offset, data_buffer, block_size, page_size)) { + success = false; + break; + } + offset += block_size; + spi_mem_worker_run_callback(worker, SPIMemCustomEventWorkerBlockReaded); + } + return success; +} + +static void spi_mem_worker_write_process(SPIMemWorker* worker) { + SPIMemCustomEventWorker event = SPIMemCustomEventWorkerChipFail; + size_t total_size = + spi_mem_worker_modes_get_total_size(worker); // need to be executed before opening file + do { + if(!spi_mem_file_open(worker->cb_ctx)) break; + if(!spi_mem_worker_await_chip_busy(worker)) break; + if(!spi_mem_worker_write(worker, total_size, &event)) break; + if(!spi_mem_worker_await_chip_busy(worker)) break; + event = SPIMemCustomEventWorkerDone; + } while(0); + spi_mem_file_close(worker->cb_ctx); + spi_mem_worker_run_callback(worker, event); +} diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene.c b/applications/plugins/spi_mem_manager/scenes/spi_mem_scene.c new file mode 100644 index 00000000000..7780005f41c --- /dev/null +++ b/applications/plugins/spi_mem_manager/scenes/spi_mem_scene.c @@ -0,0 +1,30 @@ +#include "spi_mem_scene.h" + +// Generate scene on_enter handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, +void (*const spi_mem_on_enter_handlers[])(void*) = { +#include "spi_mem_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 spi_mem_on_event_handlers[])(void* context, SceneManagerEvent event) = { +#include "spi_mem_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 spi_mem_on_exit_handlers[])(void* context) = { +#include "spi_mem_scene_config.h" +}; +#undef ADD_SCENE + +// Initialize scene handlers configuration structure +const SceneManagerHandlers spi_mem_scene_handlers = { + .on_enter_handlers = spi_mem_on_enter_handlers, + .on_event_handlers = spi_mem_on_event_handlers, + .on_exit_handlers = spi_mem_on_exit_handlers, + .scene_num = SPIMemSceneNum, +}; diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene.h b/applications/plugins/spi_mem_manager/scenes/spi_mem_scene.h new file mode 100644 index 00000000000..2ac6d21e3cd --- /dev/null +++ b/applications/plugins/spi_mem_manager/scenes/spi_mem_scene.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +// Generate scene id and total number +#define ADD_SCENE(prefix, name, id) SPIMemScene##id, +typedef enum { +#include "spi_mem_scene_config.h" + SPIMemSceneNum, +} SPIMemScene; +#undef ADD_SCENE + +extern const SceneManagerHandlers spi_mem_scene_handlers; + +// Generate scene on_enter handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); +#include "spi_mem_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 "spi_mem_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 "spi_mem_scene_config.h" +#undef ADD_SCENE diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_about.c b/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_about.c new file mode 100644 index 00000000000..dc0cc4fe4de --- /dev/null +++ b/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_about.c @@ -0,0 +1,42 @@ +#include "../spi_mem_app_i.h" +#include "../lib/spi/spi_mem_chip.h" + +#define SPI_MEM_VERSION_APP "0.1.0" +#define SPI_MEM_DEVELOPER "DrunkBatya" +#define SPI_MEM_GITHUB "https://github.com/flipperdevices/flipperzero-firmware" +#define SPI_MEM_NAME "\e#\e! SPI Mem Manager \e!\n" +#define SPI_MEM_BLANK_INV "\e#\e! \e!\n" + +void spi_mem_scene_about_on_enter(void* context) { + SPIMemApp* app = context; + FuriString* tmp_string = furi_string_alloc(); + + widget_add_text_box_element( + app->widget, 0, 0, 128, 14, AlignCenter, AlignBottom, SPI_MEM_BLANK_INV, false); + widget_add_text_box_element( + app->widget, 0, 2, 128, 14, AlignCenter, AlignBottom, SPI_MEM_NAME, false); + furi_string_printf(tmp_string, "\e#%s\n", "Information"); + furi_string_cat_printf(tmp_string, "Version: %s\n", SPI_MEM_VERSION_APP); + furi_string_cat_printf(tmp_string, "Developed by: %s\n", SPI_MEM_DEVELOPER); + furi_string_cat_printf(tmp_string, "Github: %s\n\n", SPI_MEM_GITHUB); + furi_string_cat_printf(tmp_string, "\e#%s\n", "Description"); + furi_string_cat_printf( + tmp_string, + "SPI memory dumper\n" + "Originally written by Hedger, ghettorce and x893 at\n" + "Flipper Hackathon 2021\n\n"); + widget_add_text_scroll_element(app->widget, 0, 16, 128, 50, furi_string_get_cstr(tmp_string)); + + furi_string_free(tmp_string); + view_dispatcher_switch_to_view(app->view_dispatcher, SPIMemViewWidget); +} + +bool spi_mem_scene_about_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + UNUSED(event); + return false; +} +void spi_mem_scene_about_on_exit(void* context) { + SPIMemApp* app = context; + widget_reset(app->widget); +} diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_chip_detect.c b/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_chip_detect.c new file mode 100644 index 00000000000..d9b8f0aa386 --- /dev/null +++ b/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_chip_detect.c @@ -0,0 +1,37 @@ +#include "../spi_mem_app_i.h" + +static void spi_mem_scene_chip_detect_callback(void* context, SPIMemCustomEventWorker event) { + SPIMemApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, event); +} + +void spi_mem_scene_chip_detect_on_enter(void* context) { + SPIMemApp* app = context; + notification_message(app->notifications, &sequence_blink_start_yellow); + view_dispatcher_switch_to_view(app->view_dispatcher, SPIMemViewDetect); + spi_mem_worker_start_thread(app->worker); + spi_mem_worker_chip_detect_start( + app->chip_info, &app->found_chips, app->worker, spi_mem_scene_chip_detect_callback, app); +} + +bool spi_mem_scene_chip_detect_on_event(void* context, SceneManagerEvent event) { + SPIMemApp* app = context; + bool success = false; + if(event.type == SceneManagerEventTypeCustom) { + success = true; + if(event.event == SPIMemCustomEventWorkerChipIdentified) { + scene_manager_set_scene_state(app->scene_manager, SPIMemSceneSelectVendor, 0); + scene_manager_next_scene(app->scene_manager, SPIMemSceneSelectVendor); + } else if(event.event == SPIMemCustomEventWorkerChipUnknown) { + scene_manager_next_scene(app->scene_manager, SPIMemSceneChipDetectFail); + } + } + return success; +} + +void spi_mem_scene_chip_detect_on_exit(void* context) { + SPIMemApp* app = context; + spi_mem_worker_stop_thread(app->worker); + notification_message(app->notifications, &sequence_blink_stop); + popup_reset(app->popup); +} diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_chip_detect_fail.c b/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_chip_detect_fail.c new file mode 100644 index 00000000000..876a287212a --- /dev/null +++ b/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_chip_detect_fail.c @@ -0,0 +1,57 @@ +#include "../spi_mem_app_i.h" +#include "../lib/spi/spi_mem_chip.h" + +static void spi_mem_scene_chip_detect_fail_widget_callback( + GuiButtonType result, + InputType type, + void* context) { + SPIMemApp* app = context; + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(app->view_dispatcher, result); + } +} + +void spi_mem_scene_chip_detect_fail_on_enter(void* context) { + SPIMemApp* app = context; + FuriString* str = furi_string_alloc(); + widget_add_button_element( + app->widget, + GuiButtonTypeCenter, + "Retry", + spi_mem_scene_chip_detect_fail_widget_callback, + app); + widget_add_string_element( + app->widget, 64, 9, AlignCenter, AlignBottom, FontPrimary, "Detected"); + widget_add_string_element( + app->widget, 64, 20, AlignCenter, AlignBottom, FontPrimary, "unknown SPI chip"); + furi_string_printf(str, "Vendor\nid: 0x%02X", spi_mem_chip_get_vendor_id(app->chip_info)); + widget_add_string_multiline_element( + app->widget, 16, 44, AlignCenter, AlignBottom, FontSecondary, furi_string_get_cstr(str)); + furi_string_printf(str, "Type\nid: 0x%02X", spi_mem_chip_get_type_id(app->chip_info)); + widget_add_string_multiline_element( + app->widget, 64, 44, AlignCenter, AlignBottom, FontSecondary, furi_string_get_cstr(str)); + furi_string_printf(str, "Capacity\nid: 0x%02X", spi_mem_chip_get_capacity_id(app->chip_info)); + widget_add_string_multiline_element( + app->widget, 110, 44, AlignCenter, AlignBottom, FontSecondary, furi_string_get_cstr(str)); + furi_string_free(str); + view_dispatcher_switch_to_view(app->view_dispatcher, SPIMemViewWidget); +} + +bool spi_mem_scene_chip_detect_fail_on_event(void* context, SceneManagerEvent event) { + SPIMemApp* app = context; + bool success = false; + if(event.type == SceneManagerEventTypeBack) { + success = true; + scene_manager_search_and_switch_to_previous_scene(app->scene_manager, SPIMemSceneStart); + } else if(event.type == SceneManagerEventTypeCustom) { + success = true; + if(event.event == GuiButtonTypeCenter) { + scene_manager_previous_scene(app->scene_manager); + } + } + return success; +} +void spi_mem_scene_chip_detect_fail_on_exit(void* context) { + SPIMemApp* app = context; + widget_reset(app->widget); +} diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_chip_detected.c b/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_chip_detected.c new file mode 100644 index 00000000000..539578a4545 --- /dev/null +++ b/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_chip_detected.c @@ -0,0 +1,94 @@ +#include "../spi_mem_app_i.h" + +static void spi_mem_scene_chip_detected_widget_callback( + GuiButtonType result, + InputType type, + void* context) { + SPIMemApp* app = context; + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(app->view_dispatcher, result); + } +} + +static void spi_mem_scene_chip_detected_print_chip_info(Widget* widget, SPIMemChip* chip_info) { + FuriString* tmp_string = furi_string_alloc(); + widget_add_string_element( + widget, + 40, + 12, + AlignLeft, + AlignTop, + FontSecondary, + spi_mem_chip_get_vendor_name(chip_info)); + widget_add_string_element( + widget, 40, 20, AlignLeft, AlignTop, FontSecondary, spi_mem_chip_get_model_name(chip_info)); + furi_string_printf(tmp_string, "Size: %zu KB", spi_mem_chip_get_size(chip_info) / 1024); + widget_add_string_element( + widget, 40, 28, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(tmp_string)); + furi_string_free(tmp_string); +} + +static void spi_mem_scene_chip_detect_draw_next_button(SPIMemApp* app) { + FuriString* str = furi_string_alloc(); + if(app->mode == SPIMemModeRead) furi_string_printf(str, "%s", "Read"); + if(app->mode == SPIMemModeWrite) furi_string_printf(str, "%s", "Write"); + if(app->mode == SPIMemModeErase) furi_string_printf(str, "%s", "Erase"); + if(app->mode == SPIMemModeCompare) furi_string_printf(str, "%s", "Check"); + widget_add_button_element( + app->widget, + GuiButtonTypeRight, + furi_string_get_cstr(str), + spi_mem_scene_chip_detected_widget_callback, + app); + furi_string_free(str); +} + +static void spi_mem_scene_chip_detected_set_previous_scene(SPIMemApp* app) { + uint32_t scene = SPIMemSceneStart; + if(app->mode == SPIMemModeCompare || app->mode == SPIMemModeWrite) + scene = SPIMemSceneSavedFileMenu; + scene_manager_search_and_switch_to_previous_scene(app->scene_manager, scene); +} + +static void spi_mem_scene_chip_detected_set_next_scene(SPIMemApp* app) { + uint32_t scene = SPIMemSceneStart; + if(app->mode == SPIMemModeRead) scene = SPIMemSceneReadFilename; + if(app->mode == SPIMemModeWrite) scene = SPIMemSceneErase; + if(app->mode == SPIMemModeErase) scene = SPIMemSceneErase; + if(app->mode == SPIMemModeCompare) scene = SPIMemSceneVerify; + scene_manager_next_scene(app->scene_manager, scene); +} + +void spi_mem_scene_chip_detected_on_enter(void* context) { + SPIMemApp* app = context; + widget_add_button_element( + app->widget, GuiButtonTypeLeft, "Retry", spi_mem_scene_chip_detected_widget_callback, app); + spi_mem_scene_chip_detect_draw_next_button(app); + widget_add_icon_element(app->widget, 0, 12, &I_Dip8_32x36); + widget_add_string_element( + app->widget, 64, 9, AlignCenter, AlignBottom, FontPrimary, "Detected SPI chip"); + spi_mem_scene_chip_detected_print_chip_info(app->widget, app->chip_info); + view_dispatcher_switch_to_view(app->view_dispatcher, SPIMemViewWidget); +} + +bool spi_mem_scene_chip_detected_on_event(void* context, SceneManagerEvent event) { + SPIMemApp* app = context; + bool success = false; + if(event.type == SceneManagerEventTypeBack) { + success = true; + spi_mem_scene_chip_detected_set_previous_scene(app); + } else if(event.type == SceneManagerEventTypeCustom) { + success = true; + if(event.event == GuiButtonTypeLeft) { + scene_manager_search_and_switch_to_previous_scene( + app->scene_manager, SPIMemSceneChipDetect); + } else if(event.event == GuiButtonTypeRight) { + spi_mem_scene_chip_detected_set_next_scene(app); + } + } + return success; +} +void spi_mem_scene_chip_detected_on_exit(void* context) { + SPIMemApp* app = context; + widget_reset(app->widget); +} diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_chip_error.c b/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_chip_error.c new file mode 100644 index 00000000000..ca4b765a209 --- /dev/null +++ b/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_chip_error.c @@ -0,0 +1,52 @@ +#include "../spi_mem_app_i.h" + +static void + spi_mem_scene_chip_error_widget_callback(GuiButtonType result, InputType type, void* context) { + SPIMemApp* app = context; + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(app->view_dispatcher, result); + } +} + +void spi_mem_scene_chip_error_on_enter(void* context) { + SPIMemApp* app = context; + widget_add_button_element( + app->widget, GuiButtonTypeLeft, "Back", spi_mem_scene_chip_error_widget_callback, app); + widget_add_string_element( + app->widget, 85, 15, AlignCenter, AlignBottom, FontPrimary, "SPI chip error"); + widget_add_string_multiline_element( + app->widget, + 85, + 52, + AlignCenter, + AlignBottom, + FontSecondary, + "Error while\ncommunicating\nwith chip"); + widget_add_icon_element(app->widget, 5, 6, &I_Dip8_32x36); + view_dispatcher_switch_to_view(app->view_dispatcher, SPIMemViewWidget); +} + +static void spi_mem_scene_chip_error_set_previous_scene(SPIMemApp* app) { + uint32_t scene = SPIMemSceneChipDetect; + if(app->mode == SPIMemModeRead || app->mode == SPIMemModeErase) scene = SPIMemSceneStart; + scene_manager_search_and_switch_to_previous_scene(app->scene_manager, scene); +} + +bool spi_mem_scene_chip_error_on_event(void* context, SceneManagerEvent event) { + SPIMemApp* app = context; + bool success = false; + if(event.type == SceneManagerEventTypeBack) { + success = true; + spi_mem_scene_chip_error_set_previous_scene(app); + } else if(event.type == SceneManagerEventTypeCustom) { + success = true; + if(event.event == GuiButtonTypeLeft) { + spi_mem_scene_chip_error_set_previous_scene(app); + } + } + return success; +} +void spi_mem_scene_chip_error_on_exit(void* context) { + SPIMemApp* app = context; + widget_reset(app->widget); +} diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_config.h b/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_config.h new file mode 100644 index 00000000000..c0e37730359 --- /dev/null +++ b/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_config.h @@ -0,0 +1,21 @@ +ADD_SCENE(spi_mem, start, Start) +ADD_SCENE(spi_mem, chip_detect, ChipDetect) +ADD_SCENE(spi_mem, chip_detected, ChipDetected) +ADD_SCENE(spi_mem, chip_detect_fail, ChipDetectFail) +ADD_SCENE(spi_mem, select_file, SelectFile) +ADD_SCENE(spi_mem, saved_file_menu, SavedFileMenu) +ADD_SCENE(spi_mem, read, Read) +ADD_SCENE(spi_mem, read_filename, ReadFilename) +ADD_SCENE(spi_mem, delete_confirm, DeleteConfirm) +ADD_SCENE(spi_mem, success, Success) +ADD_SCENE(spi_mem, about, About) +ADD_SCENE(spi_mem, verify, Verify) +ADD_SCENE(spi_mem, file_info, FileInfo) +ADD_SCENE(spi_mem, erase, Erase) +ADD_SCENE(spi_mem, chip_error, ChipError) +ADD_SCENE(spi_mem, verify_error, VerifyError) +ADD_SCENE(spi_mem, write, Write) +ADD_SCENE(spi_mem, storage_error, StorageError) +ADD_SCENE(spi_mem, select_vendor, SelectVendor) +ADD_SCENE(spi_mem, select_model, SelectModel) +ADD_SCENE(spi_mem, wiring, Wiring) diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_delete_confirm.c b/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_delete_confirm.c new file mode 100644 index 00000000000..bb514245260 --- /dev/null +++ b/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_delete_confirm.c @@ -0,0 +1,62 @@ +#include "../spi_mem_app_i.h" +#include "../spi_mem_files.h" + +static void spi_mem_scene_delete_confirm_widget_callback( + GuiButtonType result, + InputType type, + void* context) { + SPIMemApp* app = context; + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(app->view_dispatcher, result); + } +} + +void spi_mem_scene_delete_confirm_on_enter(void* context) { + SPIMemApp* app = context; + FuriString* file_name = furi_string_alloc(); + FuriString* message = furi_string_alloc(); + path_extract_filename(app->file_path, file_name, true); + furi_string_printf(message, "\e#Delete %s?\e#", furi_string_get_cstr(file_name)); + widget_add_text_box_element( + app->widget, 0, 0, 128, 27, AlignCenter, AlignCenter, furi_string_get_cstr(message), true); + widget_add_button_element( + app->widget, + GuiButtonTypeLeft, + "Cancel", + spi_mem_scene_delete_confirm_widget_callback, + app); + widget_add_button_element( + app->widget, + GuiButtonTypeRight, + "Delete", + spi_mem_scene_delete_confirm_widget_callback, + app); + view_dispatcher_switch_to_view(app->view_dispatcher, SPIMemViewWidget); + furi_string_free(file_name); + furi_string_free(message); +} + +bool spi_mem_scene_delete_confirm_on_event(void* context, SceneManagerEvent event) { + SPIMemApp* app = context; + bool success = false; + if(event.type == SceneManagerEventTypeCustom) { + success = true; + if(event.event == GuiButtonTypeRight) { + app->mode = SPIMemModeDelete; + if(spi_mem_file_delete(app)) { + scene_manager_next_scene(app->scene_manager, SPIMemSceneSuccess); + } else { + scene_manager_next_scene(app->scene_manager, SPIMemSceneStorageError); + } + } else if(event.event == GuiButtonTypeLeft) { + scene_manager_search_and_switch_to_previous_scene( + app->scene_manager, SPIMemSceneSavedFileMenu); + } + } + return success; +} + +void spi_mem_scene_delete_confirm_on_exit(void* context) { + SPIMemApp* app = context; + widget_reset(app->widget); +} diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_erase.c b/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_erase.c new file mode 100644 index 00000000000..0d3ae66bfb2 --- /dev/null +++ b/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_erase.c @@ -0,0 +1,65 @@ +#include "../spi_mem_app_i.h" + +static void + spi_mem_scene_erase_widget_callback(GuiButtonType result, InputType type, void* context) { + SPIMemApp* app = context; + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(app->view_dispatcher, result); + } +} + +static void spi_mem_scene_erase_callback(void* context, SPIMemCustomEventWorker event) { + SPIMemApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, event); +} + +void spi_mem_scene_erase_on_enter(void* context) { + SPIMemApp* app = context; + widget_add_button_element( + app->widget, GuiButtonTypeLeft, "Cancel", spi_mem_scene_erase_widget_callback, app); + widget_add_string_element( + app->widget, 64, 15, AlignCenter, AlignBottom, FontPrimary, "Erasing SPI chip"); + widget_add_string_element( + app->widget, 64, 27, AlignCenter, AlignBottom, FontSecondary, "Please be patient"); + notification_message(app->notifications, &sequence_blink_start_magenta); + view_dispatcher_switch_to_view(app->view_dispatcher, SPIMemViewWidget); + spi_mem_worker_start_thread(app->worker); + spi_mem_worker_erase_start(app->chip_info, app->worker, spi_mem_scene_erase_callback, app); +} + +static void spi_mem_scene_erase_set_previous_scene(SPIMemApp* app) { + uint32_t scene = SPIMemSceneStart; + if(app->mode == SPIMemModeWrite) scene = SPIMemSceneSavedFileMenu; + scene_manager_search_and_switch_to_previous_scene(app->scene_manager, scene); +} + +static void spi_mem_scene_erase_set_next_scene(SPIMemApp* app) { + uint32_t scene = SPIMemSceneSuccess; + if(app->mode == SPIMemModeWrite) scene = SPIMemSceneWrite; + scene_manager_next_scene(app->scene_manager, scene); +} + +bool spi_mem_scene_erase_on_event(void* context, SceneManagerEvent event) { + SPIMemApp* app = context; + bool success = false; + if(event.type == SceneManagerEventTypeBack) { + success = true; + spi_mem_scene_erase_set_previous_scene(app); + } else if(event.type == SceneManagerEventTypeCustom) { + success = true; + if(event.event == GuiButtonTypeLeft) { + scene_manager_previous_scene(app->scene_manager); + } else if(event.event == SPIMemCustomEventWorkerDone) { + spi_mem_scene_erase_set_next_scene(app); + } else if(event.event == SPIMemCustomEventWorkerChipFail) { + scene_manager_next_scene(app->scene_manager, SPIMemSceneChipError); + } + } + return success; +} +void spi_mem_scene_erase_on_exit(void* context) { + SPIMemApp* app = context; + spi_mem_worker_stop_thread(app->worker); + notification_message(app->notifications, &sequence_blink_stop); + widget_reset(app->widget); +} diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_file_info.c b/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_file_info.c new file mode 100644 index 00000000000..687f17f8194 --- /dev/null +++ b/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_file_info.c @@ -0,0 +1,29 @@ +#include "../spi_mem_app_i.h" +#include "../spi_mem_files.h" + +void spi_mem_scene_file_info_on_enter(void* context) { + SPIMemApp* app = context; + FuriString* str = furi_string_alloc(); + furi_string_printf(str, "Size: %zu KB", spi_mem_file_get_size(app) / 1024); + widget_add_string_element( + app->widget, 64, 9, AlignCenter, AlignBottom, FontPrimary, "File info"); + widget_add_string_element( + app->widget, 64, 20, AlignCenter, AlignBottom, FontSecondary, furi_string_get_cstr(str)); + furi_string_free(str); + view_dispatcher_switch_to_view(app->view_dispatcher, SPIMemViewWidget); +} + +bool spi_mem_scene_file_info_on_event(void* context, SceneManagerEvent event) { + SPIMemApp* app = context; + bool success = false; + if(event.type == SceneManagerEventTypeBack) { + success = true; + scene_manager_search_and_switch_to_previous_scene( + app->scene_manager, SPIMemSceneSavedFileMenu); + } + return success; +} +void spi_mem_scene_file_info_on_exit(void* context) { + SPIMemApp* app = context; + widget_reset(app->widget); +} diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_read.c b/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_read.c new file mode 100644 index 00000000000..bbf38a3036d --- /dev/null +++ b/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_read.c @@ -0,0 +1,57 @@ +#include "../spi_mem_app_i.h" +#include "../spi_mem_files.h" +#include "../lib/spi/spi_mem_chip.h" +#include "../lib/spi/spi_mem_tools.h" + +void spi_mem_scene_read_progress_view_result_callback(void* context) { + SPIMemApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, SPIMemCustomEventViewReadCancel); +} + +static void spi_mem_scene_read_callback(void* context, SPIMemCustomEventWorker event) { + SPIMemApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, event); +} + +void spi_mem_scene_read_on_enter(void* context) { + SPIMemApp* app = context; + spi_mem_view_progress_set_read_callback( + app->view_progress, spi_mem_scene_read_progress_view_result_callback, app); + notification_message(app->notifications, &sequence_blink_start_blue); + spi_mem_view_progress_set_chip_size(app->view_progress, spi_mem_chip_get_size(app->chip_info)); + spi_mem_view_progress_set_block_size( + app->view_progress, spi_mem_tools_get_file_max_block_size(app->chip_info)); + view_dispatcher_switch_to_view(app->view_dispatcher, SPIMemViewProgress); + spi_mem_worker_start_thread(app->worker); + spi_mem_worker_read_start(app->chip_info, app->worker, spi_mem_scene_read_callback, app); +} + +bool spi_mem_scene_read_on_event(void* context, SceneManagerEvent event) { + SPIMemApp* app = context; + UNUSED(app); + bool success = false; + if(event.type == SceneManagerEventTypeBack) { + success = true; + } else if(event.type == SceneManagerEventTypeCustom) { + success = true; + if(event.event == SPIMemCustomEventViewReadCancel) { + scene_manager_search_and_switch_to_previous_scene( + app->scene_manager, SPIMemSceneChipDetect); + } else if(event.event == SPIMemCustomEventWorkerBlockReaded) { + spi_mem_view_progress_inc_progress(app->view_progress); + } else if(event.event == SPIMemCustomEventWorkerDone) { + scene_manager_next_scene(app->scene_manager, SPIMemSceneVerify); + } else if(event.event == SPIMemCustomEventWorkerChipFail) { + scene_manager_next_scene(app->scene_manager, SPIMemSceneChipError); + } else if(event.event == SPIMemCustomEventWorkerFileFail) { + scene_manager_next_scene(app->scene_manager, SPIMemSceneStorageError); + } + } + return success; +} +void spi_mem_scene_read_on_exit(void* context) { + SPIMemApp* app = context; + spi_mem_worker_stop_thread(app->worker); + spi_mem_view_progress_reset(app->view_progress); + notification_message(app->notifications, &sequence_blink_stop); +} diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_read_filename.c b/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_read_filename.c new file mode 100644 index 00000000000..4b16baa2efb --- /dev/null +++ b/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_read_filename.c @@ -0,0 +1,46 @@ +#include "../spi_mem_app_i.h" +#include "../spi_mem_files.h" + +void spi_mem_scene_read_filename_view_result_callback(void* context) { + SPIMemApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, SPIMemCustomEventTextEditResult); +} + +void spi_mem_scene_read_set_random_filename(SPIMemApp* app) { + if(furi_string_end_with(app->file_path, SPI_MEM_FILE_EXTENSION)) { + size_t filename_start = furi_string_search_rchar(app->file_path, '/'); + furi_string_left(app->file_path, filename_start); + } + set_random_name(app->text_buffer, SPI_MEM_TEXT_BUFFER_SIZE); +} + +void spi_mem_scene_read_filename_on_enter(void* context) { + SPIMemApp* app = context; + spi_mem_scene_read_set_random_filename(app); + text_input_set_header_text(app->text_input, "Name the dump"); + text_input_set_result_callback( + app->text_input, + spi_mem_scene_read_filename_view_result_callback, + app, + app->text_buffer, + SPI_MEM_FILE_NAME_SIZE, + true); + view_dispatcher_switch_to_view(app->view_dispatcher, SPIMemViewTextInput); +} + +bool spi_mem_scene_read_filename_on_event(void* context, SceneManagerEvent event) { + SPIMemApp* app = context; + UNUSED(app); + bool success = false; + if(event.type == SceneManagerEventTypeCustom) { + success = true; + if(event.event == SPIMemCustomEventTextEditResult) { + scene_manager_next_scene(app->scene_manager, SPIMemSceneRead); + } + } + return success; +} +void spi_mem_scene_read_filename_on_exit(void* context) { + SPIMemApp* app = context; + text_input_reset(app->text_input); +} diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_saved_file_menu.c b/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_saved_file_menu.c new file mode 100644 index 00000000000..d5767455e84 --- /dev/null +++ b/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_saved_file_menu.c @@ -0,0 +1,76 @@ +#include "../spi_mem_app_i.h" + +typedef enum { + SPIMemSceneSavedFileMenuSubmenuIndexWrite, + SPIMemSceneSavedFileMenuSubmenuIndexCompare, + SPIMemSceneSavedFileMenuSubmenuIndexInfo, + SPIMemSceneSavedFileMenuSubmenuIndexDelete, +} SPIMemSceneSavedFileMenuSubmenuIndex; + +static void spi_mem_scene_saved_file_menu_submenu_callback(void* context, uint32_t index) { + SPIMemApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, index); +} + +void spi_mem_scene_saved_file_menu_on_enter(void* context) { + SPIMemApp* app = context; + submenu_add_item( + app->submenu, + "Write", + SPIMemSceneSavedFileMenuSubmenuIndexWrite, + spi_mem_scene_saved_file_menu_submenu_callback, + app); + submenu_add_item( + app->submenu, + "Compare", + SPIMemSceneSavedFileMenuSubmenuIndexCompare, + spi_mem_scene_saved_file_menu_submenu_callback, + app); + submenu_add_item( + app->submenu, + "Info", + SPIMemSceneSavedFileMenuSubmenuIndexInfo, + spi_mem_scene_saved_file_menu_submenu_callback, + app); + submenu_add_item( + app->submenu, + "Delete", + SPIMemSceneSavedFileMenuSubmenuIndexDelete, + spi_mem_scene_saved_file_menu_submenu_callback, + app); + submenu_set_selected_item( + app->submenu, scene_manager_get_scene_state(app->scene_manager, SPIMemSceneSavedFileMenu)); + view_dispatcher_switch_to_view(app->view_dispatcher, SPIMemViewSubmenu); +} + +bool spi_mem_scene_saved_file_menu_on_event(void* context, SceneManagerEvent event) { + SPIMemApp* app = context; + bool success = false; + if(event.type == SceneManagerEventTypeCustom) { + scene_manager_set_scene_state(app->scene_manager, SPIMemSceneSavedFileMenu, event.event); + if(event.event == SPIMemSceneSavedFileMenuSubmenuIndexWrite) { + app->mode = SPIMemModeWrite; + scene_manager_next_scene(app->scene_manager, SPIMemSceneChipDetect); + success = true; + } + if(event.event == SPIMemSceneSavedFileMenuSubmenuIndexCompare) { + app->mode = SPIMemModeCompare; + scene_manager_next_scene(app->scene_manager, SPIMemSceneChipDetect); + success = true; + } + if(event.event == SPIMemSceneSavedFileMenuSubmenuIndexDelete) { + scene_manager_next_scene(app->scene_manager, SPIMemSceneDeleteConfirm); + success = true; + } + if(event.event == SPIMemSceneSavedFileMenuSubmenuIndexInfo) { + scene_manager_next_scene(app->scene_manager, SPIMemSceneFileInfo); + success = true; + } + } + return success; +} + +void spi_mem_scene_saved_file_menu_on_exit(void* context) { + SPIMemApp* app = context; + submenu_reset(app->submenu); +} diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_select_file.c b/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_select_file.c new file mode 100644 index 00000000000..cb48035b5f3 --- /dev/null +++ b/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_select_file.c @@ -0,0 +1,22 @@ +#include "../spi_mem_app_i.h" +#include "../spi_mem_files.h" + +void spi_mem_scene_select_file_on_enter(void* context) { + SPIMemApp* app = context; + if(spi_mem_file_select(app)) { + scene_manager_set_scene_state(app->scene_manager, SPIMemSceneSavedFileMenu, 0); + scene_manager_next_scene(app->scene_manager, SPIMemSceneSavedFileMenu); + } else { + scene_manager_search_and_switch_to_previous_scene(app->scene_manager, SPIMemSceneStart); + } +} + +bool spi_mem_scene_select_file_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + UNUSED(event); + return false; +} + +void spi_mem_scene_select_file_on_exit(void* context) { + UNUSED(context); +} diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_select_model.c b/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_select_model.c new file mode 100644 index 00000000000..c39c4a1828b --- /dev/null +++ b/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_select_model.c @@ -0,0 +1,45 @@ +#include "../spi_mem_app_i.h" + +static void spi_mem_scene_select_model_submenu_callback(void* context, uint32_t index) { + SPIMemApp* app = context; + spi_mem_chip_copy_chip_info(app->chip_info, *found_chips_get(app->found_chips, index)); + view_dispatcher_send_custom_event(app->view_dispatcher, index); +} + +void spi_mem_scene_select_model_on_enter(void* context) { + SPIMemApp* app = context; + size_t models_on_vendor = 0; + for(size_t index = 0; index < found_chips_size(app->found_chips); index++) { + if(spi_mem_chip_get_vendor_enum(*found_chips_get(app->found_chips, index)) != + app->chip_vendor_enum) + continue; + submenu_add_item( + app->submenu, + spi_mem_chip_get_model_name(*found_chips_get(app->found_chips, index)), + index, + spi_mem_scene_select_model_submenu_callback, + app); + models_on_vendor++; + } + if(models_on_vendor == 1) spi_mem_scene_select_model_submenu_callback(context, 0); + submenu_set_header(app->submenu, "Choose chip model"); + submenu_set_selected_item( + app->submenu, scene_manager_get_scene_state(app->scene_manager, SPIMemSceneSelectVendor)); + view_dispatcher_switch_to_view(app->view_dispatcher, SPIMemViewSubmenu); +} + +bool spi_mem_scene_select_model_on_event(void* context, SceneManagerEvent event) { + SPIMemApp* app = context; + bool success = false; + if(event.type == SceneManagerEventTypeCustom) { + scene_manager_set_scene_state(app->scene_manager, SPIMemSceneSelectVendor, event.event); + scene_manager_next_scene(app->scene_manager, SPIMemSceneChipDetected); + success = true; + } + return success; +} + +void spi_mem_scene_select_model_on_exit(void* context) { + SPIMemApp* app = context; + submenu_reset(app->submenu); +} diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_select_vendor.c b/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_select_vendor.c new file mode 100644 index 00000000000..c7f736f8848 --- /dev/null +++ b/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_select_vendor.c @@ -0,0 +1,70 @@ +#include "../spi_mem_app_i.h" +#include +#include + +ARRAY_DEF(vendors, uint32_t) +ALGO_DEF(vendors, ARRAY_OPLIST(vendors)) + +static void spi_mem_scene_select_vendor_submenu_callback(void* context, uint32_t index) { + SPIMemApp* app = context; + app->chip_vendor_enum = index; + view_dispatcher_send_custom_event(app->view_dispatcher, index); +} + +static void spi_mem_scene_select_vendor_sort_vendors(SPIMemApp* app, vendors_t vendors_arr) { + for(size_t index = 0; index < found_chips_size(app->found_chips); index++) { + vendors_push_back( + vendors_arr, spi_mem_chip_get_vendor_enum(*found_chips_get(app->found_chips, index))); + } + vendors_uniq(vendors_arr); +} + +void spi_mem_scene_select_vendor_on_enter(void* context) { + SPIMemApp* app = context; + vendors_t vendors_arr; + vendors_init(vendors_arr); + spi_mem_scene_select_vendor_sort_vendors(app, vendors_arr); + size_t vendors_arr_size = vendors_size(vendors_arr); + if(vendors_arr_size == 1) + spi_mem_scene_select_vendor_submenu_callback(context, *vendors_get(vendors_arr, 0)); + for(size_t index = 0; index < vendors_arr_size; index++) { + uint32_t vendor_enum = *vendors_get(vendors_arr, index); + submenu_add_item( + app->submenu, + spi_mem_chip_get_vendor_name_by_enum(vendor_enum), + vendor_enum, + spi_mem_scene_select_vendor_submenu_callback, + app); + } + vendors_clear(vendors_arr); + submenu_set_header(app->submenu, "Choose chip vendor"); + submenu_set_selected_item( + app->submenu, scene_manager_get_scene_state(app->scene_manager, SPIMemSceneSelectVendor)); + view_dispatcher_switch_to_view(app->view_dispatcher, SPIMemViewSubmenu); +} + +static void spi_mem_scene_select_vendor_set_previous_scene(SPIMemApp* app) { + uint32_t scene = SPIMemSceneStart; + if(app->mode == SPIMemModeCompare || app->mode == SPIMemModeWrite) + scene = SPIMemSceneSavedFileMenu; + scene_manager_search_and_switch_to_previous_scene(app->scene_manager, scene); +} + +bool spi_mem_scene_select_vendor_on_event(void* context, SceneManagerEvent event) { + SPIMemApp* app = context; + bool success = false; + if(event.type == SceneManagerEventTypeBack) { + success = true; + spi_mem_scene_select_vendor_set_previous_scene(app); + } else if(event.type == SceneManagerEventTypeCustom) { + scene_manager_set_scene_state(app->scene_manager, SPIMemSceneSelectVendor, event.event); + scene_manager_next_scene(app->scene_manager, SPIMemSceneSelectModel); + success = true; + } + return success; +} + +void spi_mem_scene_select_vendor_on_exit(void* context) { + SPIMemApp* app = context; + submenu_reset(app->submenu); +} diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_start.c b/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_start.c new file mode 100644 index 00000000000..b664df687d4 --- /dev/null +++ b/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_start.c @@ -0,0 +1,84 @@ +#include "../spi_mem_app_i.h" + +typedef enum { + SPIMemSceneStartSubmenuIndexRead, + SPIMemSceneStartSubmenuIndexSaved, + SPIMemSceneStartSubmenuIndexErase, + SPIMemSceneStartSubmenuIndexWiring, + SPIMemSceneStartSubmenuIndexAbout +} SPIMemSceneStartSubmenuIndex; + +static void spi_mem_scene_start_submenu_callback(void* context, uint32_t index) { + SPIMemApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, index); +} + +void spi_mem_scene_start_on_enter(void* context) { + SPIMemApp* app = context; + submenu_add_item( + app->submenu, + "Read", + SPIMemSceneStartSubmenuIndexRead, + spi_mem_scene_start_submenu_callback, + app); + submenu_add_item( + app->submenu, + "Saved", + SPIMemSceneStartSubmenuIndexSaved, + spi_mem_scene_start_submenu_callback, + app); + submenu_add_item( + app->submenu, + "Erase", + SPIMemSceneStartSubmenuIndexErase, + spi_mem_scene_start_submenu_callback, + app); + submenu_add_item( + app->submenu, + "Wiring", + SPIMemSceneStartSubmenuIndexWiring, + spi_mem_scene_start_submenu_callback, + app); + submenu_add_item( + app->submenu, + "About", + SPIMemSceneStartSubmenuIndexAbout, + spi_mem_scene_start_submenu_callback, + app); + submenu_set_selected_item( + app->submenu, scene_manager_get_scene_state(app->scene_manager, SPIMemSceneStart)); + view_dispatcher_switch_to_view(app->view_dispatcher, SPIMemViewSubmenu); +} + +bool spi_mem_scene_start_on_event(void* context, SceneManagerEvent event) { + SPIMemApp* app = context; + bool success = false; + if(event.type == SceneManagerEventTypeCustom) { + scene_manager_set_scene_state(app->scene_manager, SPIMemSceneStart, event.event); + if(event.event == SPIMemSceneStartSubmenuIndexRead) { + app->mode = SPIMemModeRead; + scene_manager_next_scene(app->scene_manager, SPIMemSceneChipDetect); + success = true; + } else if(event.event == SPIMemSceneStartSubmenuIndexSaved) { + furi_string_set(app->file_path, SPI_MEM_FILE_FOLDER); + scene_manager_next_scene(app->scene_manager, SPIMemSceneSelectFile); + success = true; + } else if(event.event == SPIMemSceneStartSubmenuIndexErase) { + app->mode = SPIMemModeErase; + scene_manager_next_scene(app->scene_manager, SPIMemSceneChipDetect); + success = true; + } else if(event.event == SPIMemSceneStartSubmenuIndexWiring) { + scene_manager_next_scene(app->scene_manager, SPIMemSceneWiring); + success = true; + } else if(event.event == SPIMemSceneStartSubmenuIndexAbout) { + scene_manager_next_scene(app->scene_manager, SPIMemSceneAbout); + success = true; + } + } + return success; +} + +void spi_mem_scene_start_on_exit(void* context) { + SPIMemApp* app = context; + submenu_reset(app->submenu); +} diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_storage_error.c b/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_storage_error.c new file mode 100644 index 00000000000..d5e289e2447 --- /dev/null +++ b/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_storage_error.c @@ -0,0 +1,56 @@ +#include "../spi_mem_app_i.h" + +static void spi_mem_scene_storage_error_widget_callback( + GuiButtonType result, + InputType type, + void* context) { + SPIMemApp* app = context; + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(app->view_dispatcher, result); + } +} + +void spi_mem_scene_storage_error_on_enter(void* context) { + SPIMemApp* app = context; + widget_add_button_element( + app->widget, GuiButtonTypeLeft, "Back", spi_mem_scene_storage_error_widget_callback, app); + widget_add_string_element( + app->widget, 85, 15, AlignCenter, AlignBottom, FontPrimary, "Storage error"); + widget_add_string_multiline_element( + app->widget, + 85, + 52, + AlignCenter, + AlignBottom, + FontSecondary, + "Error while\nworking with\nfilesystem"); + widget_add_icon_element(app->widget, 5, 6, &I_SDQuestion_35x43); + view_dispatcher_switch_to_view(app->view_dispatcher, SPIMemViewWidget); +} + +static void spi_mem_scene_storage_error_set_previous_scene(SPIMemApp* app) { + uint32_t scene = SPIMemSceneChipDetect; + if(app->mode == SPIMemModeRead) scene = SPIMemSceneStart; + if(app->mode == SPIMemModeErase) scene = SPIMemSceneStart; + if(app->mode == SPIMemModeDelete) scene = SPIMemSceneStart; + scene_manager_search_and_switch_to_previous_scene(app->scene_manager, scene); +} + +bool spi_mem_scene_storage_error_on_event(void* context, SceneManagerEvent event) { + SPIMemApp* app = context; + bool success = false; + if(event.type == SceneManagerEventTypeBack) { + success = true; + spi_mem_scene_storage_error_set_previous_scene(app); + } else if(event.type == SceneManagerEventTypeCustom) { + success = true; + if(event.event == GuiButtonTypeLeft) { + spi_mem_scene_storage_error_set_previous_scene(app); + } + } + return success; +} +void spi_mem_scene_storage_error_on_exit(void* context) { + SPIMemApp* app = context; + widget_reset(app->widget); +} diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_success.c b/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_success.c new file mode 100644 index 00000000000..39039466fc1 --- /dev/null +++ b/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_success.c @@ -0,0 +1,40 @@ +#include "../spi_mem_app_i.h" + +static void spi_mem_scene_success_popup_callback(void* context) { + SPIMemApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, SPIMemCustomEventPopupBack); +} + +void spi_mem_scene_success_on_enter(void* context) { + SPIMemApp* app = context; + popup_set_icon(app->popup, 32, 5, &I_DolphinNice_96x59); + popup_set_header(app->popup, "Success!", 5, 7, AlignLeft, AlignTop); + popup_set_callback(app->popup, spi_mem_scene_success_popup_callback); + popup_set_context(app->popup, app); + popup_set_timeout(app->popup, 1500); + popup_enable_timeout(app->popup); + view_dispatcher_switch_to_view(app->view_dispatcher, SPIMemViewPopup); +} + +static void spi_mem_scene_success_set_previous_scene(SPIMemApp* app) { + uint32_t scene = SPIMemSceneSelectFile; + if(app->mode == SPIMemModeErase) scene = SPIMemSceneStart; + scene_manager_search_and_switch_to_another_scene(app->scene_manager, scene); +} + +bool spi_mem_scene_success_on_event(void* context, SceneManagerEvent event) { + SPIMemApp* app = context; + bool success = false; + if(event.type == SceneManagerEventTypeCustom) { + success = true; + if(event.event == SPIMemCustomEventPopupBack) { + spi_mem_scene_success_set_previous_scene(app); + } + } + return success; +} + +void spi_mem_scene_success_on_exit(void* context) { + SPIMemApp* app = context; + popup_reset(app->popup); +} diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_verify.c b/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_verify.c new file mode 100644 index 00000000000..08a8d1057ed --- /dev/null +++ b/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_verify.c @@ -0,0 +1,59 @@ +#include "../spi_mem_app_i.h" +#include "../spi_mem_files.h" +#include "../lib/spi/spi_mem_chip.h" +#include "../lib/spi/spi_mem_tools.h" + +void spi_mem_scene_verify_view_result_callback(void* context) { + SPIMemApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, SPIMemCustomEventViewVerifySkip); +} + +static void spi_mem_scene_verify_callback(void* context, SPIMemCustomEventWorker event) { + SPIMemApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, event); +} + +void spi_mem_scene_verify_on_enter(void* context) { + SPIMemApp* app = context; + spi_mem_view_progress_set_verify_callback( + app->view_progress, spi_mem_scene_verify_view_result_callback, app); + notification_message(app->notifications, &sequence_blink_start_cyan); + spi_mem_view_progress_set_chip_size(app->view_progress, spi_mem_chip_get_size(app->chip_info)); + spi_mem_view_progress_set_file_size(app->view_progress, spi_mem_file_get_size(app)); + spi_mem_view_progress_set_block_size( + app->view_progress, spi_mem_tools_get_file_max_block_size(app->chip_info)); + view_dispatcher_switch_to_view(app->view_dispatcher, SPIMemViewProgress); + spi_mem_worker_start_thread(app->worker); + spi_mem_worker_verify_start(app->chip_info, app->worker, spi_mem_scene_verify_callback, app); +} + +bool spi_mem_scene_verify_on_event(void* context, SceneManagerEvent event) { + SPIMemApp* app = context; + UNUSED(app); + bool success = false; + if(event.type == SceneManagerEventTypeBack) { + success = true; + } else if(event.type == SceneManagerEventTypeCustom) { + success = true; + if(event.event == SPIMemCustomEventViewVerifySkip) { + scene_manager_next_scene(app->scene_manager, SPIMemSceneSuccess); + } else if(event.event == SPIMemCustomEventWorkerBlockReaded) { + spi_mem_view_progress_inc_progress(app->view_progress); + } else if(event.event == SPIMemCustomEventWorkerChipFail) { + scene_manager_next_scene(app->scene_manager, SPIMemSceneChipError); + } else if(event.event == SPIMemCustomEventWorkerFileFail) { + scene_manager_next_scene(app->scene_manager, SPIMemSceneStorageError); + } else if(event.event == SPIMemCustomEventWorkerDone) { + scene_manager_next_scene(app->scene_manager, SPIMemSceneSuccess); + } else if(event.event == SPIMemCustomEventWorkerVerifyFail) { + scene_manager_next_scene(app->scene_manager, SPIMemSceneVerifyError); + } + } + return success; +} +void spi_mem_scene_verify_on_exit(void* context) { + SPIMemApp* app = context; + spi_mem_worker_stop_thread(app->worker); + spi_mem_view_progress_reset(app->view_progress); + notification_message(app->notifications, &sequence_blink_stop); +} diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_verify_error.c b/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_verify_error.c new file mode 100644 index 00000000000..fbe954fa6c7 --- /dev/null +++ b/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_verify_error.c @@ -0,0 +1,43 @@ +#include "../spi_mem_app_i.h" + +static void spi_mem_scene_verify_error_widget_callback( + GuiButtonType result, + InputType type, + void* context) { + SPIMemApp* app = context; + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(app->view_dispatcher, result); + } +} + +void spi_mem_scene_verify_error_on_enter(void* context) { + SPIMemApp* app = context; + widget_add_button_element( + app->widget, GuiButtonTypeLeft, "Back", spi_mem_scene_verify_error_widget_callback, app); + widget_add_string_element( + app->widget, 64, 9, AlignCenter, AlignBottom, FontPrimary, "Verification error"); + widget_add_string_element( + app->widget, 64, 21, AlignCenter, AlignBottom, FontSecondary, "Data mismatch"); + view_dispatcher_switch_to_view(app->view_dispatcher, SPIMemViewWidget); +} + +bool spi_mem_scene_verify_error_on_event(void* context, SceneManagerEvent event) { + SPIMemApp* app = context; + bool success = false; + if(event.type == SceneManagerEventTypeBack) { + success = true; + scene_manager_search_and_switch_to_previous_scene( + app->scene_manager, SPIMemSceneChipDetect); + } else if(event.type == SceneManagerEventTypeCustom) { + success = true; + if(event.event == GuiButtonTypeLeft) { + scene_manager_search_and_switch_to_previous_scene( + app->scene_manager, SPIMemSceneChipDetect); + } + } + return success; +} +void spi_mem_scene_verify_error_on_exit(void* context) { + SPIMemApp* app = context; + widget_reset(app->widget); +} diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_wiring.c b/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_wiring.c new file mode 100644 index 00000000000..22036f4bc32 --- /dev/null +++ b/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_wiring.c @@ -0,0 +1,18 @@ +#include "../spi_mem_app_i.h" +#include "../lib/spi/spi_mem_chip.h" + +void spi_mem_scene_wiring_on_enter(void* context) { + SPIMemApp* app = context; + widget_add_icon_element(app->widget, 0, 0, &I_Wiring_SPI_128x64); + view_dispatcher_switch_to_view(app->view_dispatcher, SPIMemViewWidget); +} + +bool spi_mem_scene_wiring_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + UNUSED(event); + return false; +} +void spi_mem_scene_wiring_on_exit(void* context) { + SPIMemApp* app = context; + widget_reset(app->widget); +} diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_write.c b/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_write.c new file mode 100644 index 00000000000..dfa384fbbe0 --- /dev/null +++ b/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_write.c @@ -0,0 +1,58 @@ +#include "../spi_mem_app_i.h" +#include "../spi_mem_files.h" +#include "../lib/spi/spi_mem_chip.h" +#include "../lib/spi/spi_mem_tools.h" + +void spi_mem_scene_write_progress_view_result_callback(void* context) { + SPIMemApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, SPIMemCustomEventViewReadCancel); +} + +static void spi_mem_scene_write_callback(void* context, SPIMemCustomEventWorker event) { + SPIMemApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, event); +} + +void spi_mem_scene_write_on_enter(void* context) { + SPIMemApp* app = context; + spi_mem_view_progress_set_write_callback( + app->view_progress, spi_mem_scene_write_progress_view_result_callback, app); + notification_message(app->notifications, &sequence_blink_start_cyan); + spi_mem_view_progress_set_chip_size(app->view_progress, spi_mem_chip_get_size(app->chip_info)); + spi_mem_view_progress_set_file_size(app->view_progress, spi_mem_file_get_size(app)); + spi_mem_view_progress_set_block_size( + app->view_progress, spi_mem_tools_get_file_max_block_size(app->chip_info)); + view_dispatcher_switch_to_view(app->view_dispatcher, SPIMemViewProgress); + spi_mem_worker_start_thread(app->worker); + spi_mem_worker_write_start(app->chip_info, app->worker, spi_mem_scene_write_callback, app); +} + +bool spi_mem_scene_write_on_event(void* context, SceneManagerEvent event) { + SPIMemApp* app = context; + UNUSED(app); + bool success = false; + if(event.type == SceneManagerEventTypeBack) { + success = true; + } else if(event.type == SceneManagerEventTypeCustom) { + success = true; + if(event.event == SPIMemCustomEventViewReadCancel) { + scene_manager_search_and_switch_to_previous_scene( + app->scene_manager, SPIMemSceneChipDetect); + } else if(event.event == SPIMemCustomEventWorkerBlockReaded) { + spi_mem_view_progress_inc_progress(app->view_progress); + } else if(event.event == SPIMemCustomEventWorkerDone) { + scene_manager_next_scene(app->scene_manager, SPIMemSceneVerify); + } else if(event.event == SPIMemCustomEventWorkerChipFail) { + scene_manager_next_scene(app->scene_manager, SPIMemSceneChipError); + } else if(event.event == SPIMemCustomEventWorkerFileFail) { + scene_manager_next_scene(app->scene_manager, SPIMemSceneStorageError); + } + } + return success; +} +void spi_mem_scene_write_on_exit(void* context) { + SPIMemApp* app = context; + spi_mem_worker_stop_thread(app->worker); + spi_mem_view_progress_reset(app->view_progress); + notification_message(app->notifications, &sequence_blink_stop); +} diff --git a/applications/plugins/spi_mem_manager/spi_mem_app.c b/applications/plugins/spi_mem_manager/spi_mem_app.c new file mode 100644 index 00000000000..63531b74c0b --- /dev/null +++ b/applications/plugins/spi_mem_manager/spi_mem_app.c @@ -0,0 +1,112 @@ +#include +#include "spi_mem_app_i.h" +#include "spi_mem_files.h" +#include "lib/spi/spi_mem_chip_i.h" + +static bool spi_mem_custom_event_callback(void* context, uint32_t event) { + furi_assert(context); + SPIMemApp* app = context; + return scene_manager_handle_custom_event(app->scene_manager, event); +} + +static bool spi_mem_back_event_callback(void* context) { + furi_assert(context); + SPIMemApp* app = context; + return scene_manager_handle_back_event(app->scene_manager); +} + +SPIMemApp* spi_mem_alloc(void) { + SPIMemApp* instance = malloc(sizeof(SPIMemApp)); + + instance->file_path = furi_string_alloc(); + instance->gui = furi_record_open(RECORD_GUI); + instance->notifications = furi_record_open(RECORD_NOTIFICATION); + instance->view_dispatcher = view_dispatcher_alloc(); + instance->scene_manager = scene_manager_alloc(&spi_mem_scene_handlers, instance); + instance->submenu = submenu_alloc(); + instance->dialog_ex = dialog_ex_alloc(); + instance->popup = popup_alloc(); + instance->worker = spi_mem_worker_alloc(); + instance->dialogs = furi_record_open(RECORD_DIALOGS); + instance->storage = furi_record_open(RECORD_STORAGE); + instance->widget = widget_alloc(); + instance->chip_info = malloc(sizeof(SPIMemChip)); + found_chips_init(instance->found_chips); + instance->view_progress = spi_mem_view_progress_alloc(); + instance->view_detect = spi_mem_view_detect_alloc(); + instance->text_input = text_input_alloc(); + instance->mode = SPIMemModeUnknown; + + furi_string_set(instance->file_path, SPI_MEM_FILE_FOLDER); + + view_dispatcher_enable_queue(instance->view_dispatcher); + view_dispatcher_set_event_callback_context(instance->view_dispatcher, instance); + view_dispatcher_set_custom_event_callback( + instance->view_dispatcher, spi_mem_custom_event_callback); + view_dispatcher_set_navigation_event_callback( + instance->view_dispatcher, spi_mem_back_event_callback); + view_dispatcher_attach_to_gui( + instance->view_dispatcher, instance->gui, ViewDispatcherTypeFullscreen); + view_dispatcher_add_view( + instance->view_dispatcher, SPIMemViewSubmenu, submenu_get_view(instance->submenu)); + view_dispatcher_add_view( + instance->view_dispatcher, SPIMemViewDialogEx, dialog_ex_get_view(instance->dialog_ex)); + view_dispatcher_add_view( + instance->view_dispatcher, SPIMemViewPopup, popup_get_view(instance->popup)); + view_dispatcher_add_view( + instance->view_dispatcher, SPIMemViewWidget, widget_get_view(instance->widget)); + view_dispatcher_add_view( + instance->view_dispatcher, + SPIMemViewProgress, + spi_mem_view_progress_get_view(instance->view_progress)); + view_dispatcher_add_view( + instance->view_dispatcher, + SPIMemViewDetect, + spi_mem_view_detect_get_view(instance->view_detect)); + view_dispatcher_add_view( + instance->view_dispatcher, SPIMemViewTextInput, text_input_get_view(instance->text_input)); + + furi_hal_power_enable_otg(); + furi_hal_spi_bus_handle_init(&furi_hal_spi_bus_handle_external); + scene_manager_next_scene(instance->scene_manager, SPIMemSceneStart); + return instance; +} + +void spi_mem_free(SPIMemApp* instance) { + view_dispatcher_remove_view(instance->view_dispatcher, SPIMemViewSubmenu); + view_dispatcher_remove_view(instance->view_dispatcher, SPIMemViewDialogEx); + view_dispatcher_remove_view(instance->view_dispatcher, SPIMemViewPopup); + view_dispatcher_remove_view(instance->view_dispatcher, SPIMemViewWidget); + view_dispatcher_remove_view(instance->view_dispatcher, SPIMemViewProgress); + view_dispatcher_remove_view(instance->view_dispatcher, SPIMemViewDetect); + view_dispatcher_remove_view(instance->view_dispatcher, SPIMemViewTextInput); + spi_mem_view_progress_free(instance->view_progress); + spi_mem_view_detect_free(instance->view_detect); + submenu_free(instance->submenu); + dialog_ex_free(instance->dialog_ex); + popup_free(instance->popup); + widget_free(instance->widget); + text_input_free(instance->text_input); + view_dispatcher_free(instance->view_dispatcher); + scene_manager_free(instance->scene_manager); + spi_mem_worker_free(instance->worker); + free(instance->chip_info); + found_chips_clear(instance->found_chips); + furi_record_close(RECORD_STORAGE); + furi_record_close(RECORD_DIALOGS); + furi_record_close(RECORD_NOTIFICATION); + furi_record_close(RECORD_GUI); + furi_string_free(instance->file_path); + furi_hal_spi_bus_handle_deinit(&furi_hal_spi_bus_handle_external); + furi_hal_power_disable_otg(); + free(instance); +} + +int32_t spi_mem_app(void* p) { + UNUSED(p); + SPIMemApp* instance = spi_mem_alloc(); + spi_mem_file_create_folder(instance); + view_dispatcher_run(instance->view_dispatcher); + spi_mem_free(instance); + return 0; +} diff --git a/applications/plugins/spi_mem_manager/spi_mem_app.h b/applications/plugins/spi_mem_manager/spi_mem_app.h new file mode 100644 index 00000000000..37ac927dbc3 --- /dev/null +++ b/applications/plugins/spi_mem_manager/spi_mem_app.h @@ -0,0 +1,3 @@ +#pragma once + +typedef struct SPIMemApp SPIMemApp; diff --git a/applications/plugins/spi_mem_manager/spi_mem_app_i.h b/applications/plugins/spi_mem_manager/spi_mem_app_i.h new file mode 100644 index 00000000000..4ce0561758e --- /dev/null +++ b/applications/plugins/spi_mem_manager/spi_mem_app_i.h @@ -0,0 +1,79 @@ +#pragma once + +#include +#include +#include +#include "spi_mem_app.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "scenes/spi_mem_scene.h" +#include "lib/spi/spi_mem_worker.h" +#include "spi_mem_manager_icons.h" +#include "views/spi_mem_view_progress.h" +#include "views/spi_mem_view_detect.h" + +#define TAG "SPIMem" +#define SPI_MEM_FILE_EXTENSION ".bin" +#define SPI_MEM_FILE_FOLDER EXT_PATH("spimem") +#define SPI_MEM_FILE_NAME_SIZE 100 +#define SPI_MEM_TEXT_BUFFER_SIZE 128 + +typedef enum { + SPIMemModeRead, + SPIMemModeWrite, + SPIMemModeCompare, + SPIMemModeErase, + SPIMemModeDelete, + SPIMemModeUnknown +} SPIMemMode; + +struct SPIMemApp { + Gui* gui; + ViewDispatcher* view_dispatcher; + SceneManager* scene_manager; + Submenu* submenu; + DialogEx* dialog_ex; + Popup* popup; + NotificationApp* notifications; + FuriString* file_path; + DialogsApp* dialogs; + Storage* storage; + File* file; + Widget* widget; + SPIMemWorker* worker; + SPIMemChip* chip_info; + found_chips_t found_chips; + uint32_t chip_vendor_enum; + SPIMemProgressView* view_progress; + SPIMemDetectView* view_detect; + TextInput* text_input; + SPIMemMode mode; + char text_buffer[SPI_MEM_TEXT_BUFFER_SIZE + 1]; +}; + +typedef enum { + SPIMemViewSubmenu, + SPIMemViewDialogEx, + SPIMemViewPopup, + SPIMemViewWidget, + SPIMemViewTextInput, + SPIMemViewProgress, + SPIMemViewDetect +} SPIMemView; + +typedef enum { + SPIMemCustomEventViewReadCancel, + SPIMemCustomEventViewVerifySkip, + SPIMemCustomEventTextEditResult, + SPIMemCustomEventPopupBack +} SPIMemCustomEvent; diff --git a/applications/plugins/spi_mem_manager/spi_mem_files.c b/applications/plugins/spi_mem_manager/spi_mem_files.c new file mode 100644 index 00000000000..a7374da191f --- /dev/null +++ b/applications/plugins/spi_mem_manager/spi_mem_files.c @@ -0,0 +1,74 @@ +#include "spi_mem_app_i.h" + +void spi_mem_file_create_folder(SPIMemApp* app) { + if(!storage_simply_mkdir(app->storage, SPI_MEM_FILE_FOLDER)) { + dialog_message_show_storage_error(app->dialogs, "Cannot create\napp folder"); + } +} + +bool spi_mem_file_delete(SPIMemApp* app) { + return (storage_simply_remove(app->storage, furi_string_get_cstr(app->file_path))); +} + +bool spi_mem_file_select(SPIMemApp* app) { + DialogsFileBrowserOptions browser_options; + dialog_file_browser_set_basic_options(&browser_options, SPI_MEM_FILE_EXTENSION, &I_Dip8_10px); + browser_options.base_path = SPI_MEM_FILE_FOLDER; + bool success = + dialog_file_browser_show(app->dialogs, app->file_path, app->file_path, &browser_options); + return success; +} + +bool spi_mem_file_create_open(SPIMemApp* app) { + bool success = false; + app->file = storage_file_alloc(app->storage); + do { + if(furi_string_end_with(app->file_path, SPI_MEM_FILE_EXTENSION)) { + if(!spi_mem_file_delete(app)) break; + size_t filename_start = furi_string_search_rchar(app->file_path, '/'); + furi_string_left(app->file_path, filename_start); + } + furi_string_cat_printf(app->file_path, "/%s%s", app->text_buffer, SPI_MEM_FILE_EXTENSION); + if(!storage_file_open( + app->file, furi_string_get_cstr(app->file_path), FSAM_WRITE, FSOM_CREATE_NEW)) + break; + success = true; + } while(0); + if(!success) { //-V547 + dialog_message_show_storage_error(app->dialogs, "Cannot save\nfile"); + } + return success; +} + +bool spi_mem_file_open(SPIMemApp* app) { + app->file = storage_file_alloc(app->storage); + if(!storage_file_open( + app->file, furi_string_get_cstr(app->file_path), FSAM_READ_WRITE, FSOM_OPEN_EXISTING)) { + dialog_message_show_storage_error(app->dialogs, "Cannot save\nfile"); + return false; + } + return true; +} + +bool spi_mem_file_write_block(SPIMemApp* app, uint8_t* data, size_t size) { + if(storage_file_write(app->file, data, size) != size) return false; + return true; +} + +bool spi_mem_file_read_block(SPIMemApp* app, uint8_t* data, size_t size) { + if(storage_file_read(app->file, data, size) != size) return false; + return true; +} + +void spi_mem_file_close(SPIMemApp* app) { + storage_file_close(app->file); + storage_file_free(app->file); +} + +size_t spi_mem_file_get_size(SPIMemApp* app) { + FileInfo file_info; + if(storage_common_stat(app->storage, furi_string_get_cstr(app->file_path), &file_info) != + FSE_OK) + return 0; + return file_info.size; +} diff --git a/applications/plugins/spi_mem_manager/spi_mem_files.h b/applications/plugins/spi_mem_manager/spi_mem_files.h new file mode 100644 index 00000000000..0e735d95192 --- /dev/null +++ b/applications/plugins/spi_mem_manager/spi_mem_files.h @@ -0,0 +1,14 @@ +#pragma once +#include "spi_mem_app.h" + +void spi_mem_file_create_folder(SPIMemApp* app); +bool spi_mem_file_select(SPIMemApp* app); +bool spi_mem_file_create(SPIMemApp* app, const char* file_name); +bool spi_mem_file_delete(SPIMemApp* app); +bool spi_mem_file_create_open(SPIMemApp* app); +bool spi_mem_file_open(SPIMemApp* app); +bool spi_mem_file_write_block(SPIMemApp* app, uint8_t* data, size_t size); +bool spi_mem_file_read_block(SPIMemApp* app, uint8_t* data, size_t size); +void spi_mem_file_close(SPIMemApp* app); +void spi_mem_file_show_storage_error(SPIMemApp* app, const char* error_text); +size_t spi_mem_file_get_size(SPIMemApp* app); diff --git a/applications/plugins/spi_mem_manager/tools/README.md b/applications/plugins/spi_mem_manager/tools/README.md new file mode 100644 index 00000000000..91080941ff5 --- /dev/null +++ b/applications/plugins/spi_mem_manager/tools/README.md @@ -0,0 +1,7 @@ +This utility can convert nofeletru's UsbAsp-flash's chiplist.xml to C array + +Usage: +```bash + ./chiplist_convert.py chiplist/chiplist.xml + mv spi_mem_chip_arr.c ../lib/spi/spi_mem_chip_arr.c +``` diff --git a/applications/plugins/spi_mem_manager/tools/chiplist/LICENSE b/applications/plugins/spi_mem_manager/tools/chiplist/LICENSE new file mode 100644 index 00000000000..56364f15060 --- /dev/null +++ b/applications/plugins/spi_mem_manager/tools/chiplist/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 nofeletru + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/applications/plugins/spi_mem_manager/tools/chiplist/chiplist.xml b/applications/plugins/spi_mem_manager/tools/chiplist/chiplist.xml new file mode 100644 index 00000000000..91a65474322 --- /dev/null +++ b/applications/plugins/spi_mem_manager/tools/chiplist/chiplist.xml @@ -0,0 +1,984 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <_25AA010A page="16" size="128" spicmd="95"/> + <_25AA020A page="16" size="256" spicmd="95"/> + <_25AA040 page="16" size="512" spicmd="95"/> + <_25AA040A page="16" size="512" spicmd="95"/> + <_25AA080 page="16" size="1024" spicmd="95"/> + <_25AA080A page="16" size="1024" spicmd="95"/> + <_25AA080B page="32" size="1024" spicmd="95"/> + <_25AA080C page="16" size="1024" spicmd="95"/> + <_25AA080D page="32" size="1024" spicmd="95"/> + <_25AA1024 page="256" size="131072" spicmd="95"/> + <_25AA128 page="64" size="16384" spicmd="95"/> + <_25AA160 page="16" size="2048" spicmd="95"/> + <_25AA160A page="16" size="2048" spicmd="95"/> + <_25AA160B page="32" size="2048" spicmd="95"/> + <_25AA256 page="64" size="32768" spicmd="95"/> + <_25AA320 page="32" size="4096" spicmd="95"/> + <_25AA512 page="128" size="65536" spicmd="95"/> + <_25AA640 page="32" size="8192" spicmd="95"/> + <_25C040 page="16" size="512" spicmd="95"/> + <_25C080 page="16" size="1024" spicmd="95"/> + <_25C160 page="16" size="2048" spicmd="95"/> + <_25C320 page="32" size="4096" spicmd="95"/> + <_25C640 page="32" size="8192" spicmd="95"/> + <_25LC010A page="16" size="128" spicmd="95"/> + <_25LC020A page="16" size="256" spicmd="95"/> + <_25LC040 page="16" size="512" spicmd="95"/> + <_25LC040A page="16" size="512" spicmd="95"/> + <_25LC080 page="16" size="1024" spicmd="95"/> + <_25LC080A page="16" size="1024" spicmd="95"/> + <_25LC080B page="32" size="1024" spicmd="95"/> + <_25LC080C page="16" size="1024" spicmd="95"/> + <_25LC080D page="32" size="1024" spicmd="95"/> + <_25LC1024 page="256" size="131072" spicmd="95"/> + <_25LC128 page="64" size="16384" spicmd="95"/> + <_25LC160 page="16" size="2048" spicmd="95"/> + <_25LC160A page="16" size="2048" spicmd="95"/> + <_25LC160B page="32" size="2048" spicmd="95"/> + <_25LC256 page="64" size="32768" spicmd="95"/> + <_25LC320 page="32" size="4096" spicmd="95"/> + <_25LC512 page="128" size="65536" spicmd="95"/> + <_25LC640 page="32" size="8192" spicmd="95"/> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <_24Cxxx> + + <_24C01 page="1" size="128" addrtype="1"/> + <_24C02 page="1" size="256" addrtype="1"/> + <_24C04 page="1" size="512" addrtype="2"/> + <_24C08 page="16" size="1024" addrtype="3"/> + <_24C16 page="16" size="2048" addrtype="4"/> + <_24C32 page="32" size="4096" addrtype="5"/> + <_24C64 page="32" size="8192" addrtype="5"/> + <_24C128 page="64" size="16384" addrtype="5"/> + <_24C256 page="64" size="32768" addrtype="5"/> + <_24C512 page="128" size="65536" addrtype="5"/> + <_24C1024 page="128" size="131072" addrtype="6"/> + + + + + + + + + + + + + diff --git a/applications/plugins/spi_mem_manager/tools/chiplist_convert.py b/applications/plugins/spi_mem_manager/tools/chiplist_convert.py new file mode 100755 index 00000000000..8b623eb3e97 --- /dev/null +++ b/applications/plugins/spi_mem_manager/tools/chiplist_convert.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python3 + +import argparse +import xml.etree.ElementTree as XML +import sys + + +def getArgs(): + parser = argparse.ArgumentParser( + description="chiplist.xml to C array converter", + ) + parser.add_argument("file", help="chiplist.xml file") + return parser.parse_args() + + +def getXML(file): + tree = XML.parse(file) + root = tree.getroot() + return root + + +def parseChip(cur, arr, vendor, vendorCodeArr): + chip = {} + chipAttr = cur.attrib + if "page" not in chipAttr: # chip without page size not supported + return + if "id" not in chipAttr: # I2C not supported yet + return + if len(chipAttr["id"]) < 6: # ID wihout capacity id not supported yet + return + chip["modelName"] = cur.tag + chip["vendorEnum"] = "SPIMemChipVendor" + vendor + chip["vendorID"] = "0x" + chipAttr["id"][0] + chipAttr["id"][1] + chip["typeID"] = chipAttr["id"][2] + chipAttr["id"][3] + chip["capacityID"] = chipAttr["id"][4] + chipAttr["id"][5] + chip["size"] = chipAttr["size"] + if chipAttr["page"] == "SSTW": + chip["writeMode"] = "SPIMemChipWriteModeAAIWord" + chip["pageSize"] = "1" + elif chipAttr["page"] == "SSTB": + chip["writeMode"] = "SPIMemChipWriteModeAAIByte" + chip["pageSize"] = "1" + else: + chip["writeMode"] = "SPIMemChipWriteModePage" + chip["pageSize"] = chipAttr["page"] + arr.append(chip) + vendorCodeArr[vendor].add(chip["vendorID"]) + + +def cleanEmptyVendors(vendors): + for cur in list(vendors): + if not vendors[cur]: + vendors.pop(cur) + + +def getVendors(xml, interface): + arr = {} + for cur in xml.find(interface): + arr[cur.tag] = set() + return arr + + +def parseXML(xml, interface, vendorCodeArr): + arr = [] + for vendor in xml.find(interface): + for cur in vendor: + parseChip(cur, arr, vendor.tag, vendorCodeArr) + return arr + + +def getVendorNameEnum(vendorID): + try: + return vendors[vendorID] + except: + print("Unknown vendor: " + vendorID) + sys.exit(1) + + +def generateCArr(arr, filename): + with open(filename, "w") as out: + print('#include "spi_mem_chip_i.h"', file=out) + print("const SPIMemChip SPIMemChips[] = {", file=out) + for cur in arr: + print(" {" + cur["vendorID"] + ",", file=out, end="") + print(" 0x" + cur["typeID"] + ",", file=out, end="") + print(" 0x" + cur["capacityID"] + ",", file=out, end="") + print(' "' + cur["modelName"] + '",', file=out, end="") + print(" " + cur["size"] + ",", file=out, end="") + print(" " + cur["pageSize"] + ",", file=out, end="") + print(" " + cur["vendorEnum"] + ",", file=out, end="") + if cur == arr[-1]: + print(" " + cur["writeMode"] + "}};", file=out) + else: + print(" " + cur["writeMode"] + "},", file=out) + +def main(): + filename = "spi_mem_chip_arr.c" + args = getArgs() + xml = getXML(args.file) + vendors = getVendors(xml, "SPI") + chipArr = parseXML(xml, "SPI", vendors) + cleanEmptyVendors(vendors) + for cur in vendors: + print(' {"' + cur + '", SPIMemChipVendor' + cur + "},") + generateCArr(chipArr, filename) + + +if __name__ == "__main__": + main() diff --git a/applications/plugins/spi_mem_manager/views/spi_mem_view_detect.c b/applications/plugins/spi_mem_manager/views/spi_mem_view_detect.c new file mode 100644 index 00000000000..eddf36e4957 --- /dev/null +++ b/applications/plugins/spi_mem_manager/views/spi_mem_view_detect.c @@ -0,0 +1,64 @@ +#include "spi_mem_view_detect.h" +#include "spi_mem_manager_icons.h" +#include + +struct SPIMemDetectView { + View* view; + IconAnimation* icon; + SPIMemDetectViewCallback callback; + void* cb_ctx; +}; + +typedef struct { + IconAnimation* icon; +} SPIMemDetectViewModel; + +View* spi_mem_view_detect_get_view(SPIMemDetectView* app) { + return app->view; +} + +static void spi_mem_view_detect_draw_callback(Canvas* canvas, void* context) { + SPIMemDetectViewModel* model = context; + canvas_set_font(canvas, FontPrimary); + canvas_draw_icon_animation(canvas, 0, 0, model->icon); + canvas_draw_str_aligned(canvas, 64, 26, AlignLeft, AlignCenter, "Detecting"); + canvas_draw_str_aligned(canvas, 64, 36, AlignLeft, AlignCenter, "SPI chip..."); +} + +static void spi_mem_view_detect_enter_callback(void* context) { + SPIMemDetectView* app = context; + with_view_model( + app->view, SPIMemDetectViewModel * model, { icon_animation_start(model->icon); }, false); +} + +static void spi_mem_view_detect_exit_callback(void* context) { + SPIMemDetectView* app = context; + with_view_model( + app->view, SPIMemDetectViewModel * model, { icon_animation_stop(model->icon); }, false); +} + +SPIMemDetectView* spi_mem_view_detect_alloc() { + SPIMemDetectView* app = malloc(sizeof(SPIMemDetectView)); + app->view = view_alloc(); + view_set_context(app->view, app); + view_allocate_model(app->view, ViewModelTypeLocking, sizeof(SPIMemDetectViewModel)); + with_view_model( + app->view, + SPIMemDetectViewModel * model, + { + model->icon = icon_animation_alloc(&A_ChipLooking_64x64); + view_tie_icon_animation(app->view, model->icon); + }, + false); + view_set_draw_callback(app->view, spi_mem_view_detect_draw_callback); + view_set_enter_callback(app->view, spi_mem_view_detect_enter_callback); + view_set_exit_callback(app->view, spi_mem_view_detect_exit_callback); + return app; +} + +void spi_mem_view_detect_free(SPIMemDetectView* app) { + with_view_model( + app->view, SPIMemDetectViewModel * model, { icon_animation_free(model->icon); }, false); + view_free(app->view); + free(app); +} diff --git a/applications/plugins/spi_mem_manager/views/spi_mem_view_detect.h b/applications/plugins/spi_mem_manager/views/spi_mem_view_detect.h new file mode 100644 index 00000000000..f95edb60d29 --- /dev/null +++ b/applications/plugins/spi_mem_manager/views/spi_mem_view_detect.h @@ -0,0 +1,9 @@ +#pragma once +#include + +typedef struct SPIMemDetectView SPIMemDetectView; +typedef void (*SPIMemDetectViewCallback)(void* context); + +View* spi_mem_view_detect_get_view(SPIMemDetectView* app); +SPIMemDetectView* spi_mem_view_detect_alloc(); +void spi_mem_view_detect_free(SPIMemDetectView* app); diff --git a/applications/plugins/spi_mem_manager/views/spi_mem_view_progress.c b/applications/plugins/spi_mem_manager/views/spi_mem_view_progress.c new file mode 100644 index 00000000000..790f979974b --- /dev/null +++ b/applications/plugins/spi_mem_manager/views/spi_mem_view_progress.c @@ -0,0 +1,230 @@ +#include "spi_mem_view_progress.h" +#include + +struct SPIMemProgressView { + View* view; + SPIMemProgressViewCallback callback; + void* cb_ctx; +}; + +typedef enum { + SPIMemProgressViewTypeRead, + SPIMemProgressViewTypeVerify, + SPIMemProgressViewTypeWrite, + SPIMemProgressViewTypeUnknown +} SPIMemProgressViewType; + +typedef struct { + size_t chip_size; + size_t file_size; + size_t blocks_written; + size_t block_size; + float progress; + SPIMemProgressViewType view_type; +} SPIMemProgressViewModel; + +View* spi_mem_view_progress_get_view(SPIMemProgressView* app) { + return app->view; +} + +static void spi_mem_view_progress_draw_progress(Canvas* canvas, float progress) { + FuriString* progress_str = furi_string_alloc(); + if(progress > 1.0) progress = 1.0; + furi_string_printf(progress_str, "%d %%", (int)(progress * 100)); + elements_progress_bar(canvas, 13, 35, 100, progress); + canvas_draw_str_aligned( + canvas, 64, 25, AlignCenter, AlignTop, furi_string_get_cstr(progress_str)); + furi_string_free(progress_str); +} + +static void + spi_mem_view_progress_read_draw_callback(Canvas* canvas, SPIMemProgressViewModel* model) { + canvas_draw_str_aligned(canvas, 64, 4, AlignCenter, AlignTop, "Reading dump"); + spi_mem_view_progress_draw_progress(canvas, model->progress); + elements_button_left(canvas, "Cancel"); +} + +static void + spi_mem_view_progress_draw_size_warning(Canvas* canvas, SPIMemProgressViewModel* model) { + if(model->file_size > model->chip_size) { + canvas_draw_str_aligned(canvas, 64, 13, AlignCenter, AlignTop, "Size clamped to chip!"); + } + if(model->chip_size > model->file_size) { + canvas_draw_str_aligned(canvas, 64, 13, AlignCenter, AlignTop, "Size clamped to file!"); + } +} + +static void + spi_mem_view_progress_verify_draw_callback(Canvas* canvas, SPIMemProgressViewModel* model) { + canvas_draw_str_aligned(canvas, 64, 2, AlignCenter, AlignTop, "Verifying dump"); + spi_mem_view_progress_draw_size_warning(canvas, model); + spi_mem_view_progress_draw_progress(canvas, model->progress); + elements_button_center(canvas, "Skip"); +} + +static void + spi_mem_view_progress_write_draw_callback(Canvas* canvas, SPIMemProgressViewModel* model) { + canvas_draw_str_aligned(canvas, 64, 4, AlignCenter, AlignTop, "Writing dump"); + spi_mem_view_progress_draw_size_warning(canvas, model); + spi_mem_view_progress_draw_progress(canvas, model->progress); + elements_button_left(canvas, "Cancel"); +} + +static void spi_mem_view_progress_draw_callback(Canvas* canvas, void* context) { + SPIMemProgressViewModel* model = context; + SPIMemProgressViewType view_type = model->view_type; + if(view_type == SPIMemProgressViewTypeRead) { + spi_mem_view_progress_read_draw_callback(canvas, model); + } else if(view_type == SPIMemProgressViewTypeVerify) { + spi_mem_view_progress_verify_draw_callback(canvas, model); + } else if(view_type == SPIMemProgressViewTypeWrite) { + spi_mem_view_progress_write_draw_callback(canvas, model); + } +} + +static bool + spi_mem_view_progress_read_write_input_callback(InputEvent* event, SPIMemProgressView* app) { + bool success = false; + if(event->type == InputTypeShort && event->key == InputKeyLeft) { + if(app->callback) { + app->callback(app->cb_ctx); + } + success = true; + } + return success; +} + +static bool + spi_mem_view_progress_verify_input_callback(InputEvent* event, SPIMemProgressView* app) { + bool success = false; + if(event->type == InputTypeShort && event->key == InputKeyOk) { + if(app->callback) { + app->callback(app->cb_ctx); + } + success = true; + } + return success; +} + +static bool spi_mem_view_progress_input_callback(InputEvent* event, void* context) { + SPIMemProgressView* app = context; + bool success = false; + SPIMemProgressViewType view_type; + with_view_model( + app->view, SPIMemProgressViewModel * model, { view_type = model->view_type; }, true); + if(view_type == SPIMemProgressViewTypeRead) { + success = spi_mem_view_progress_read_write_input_callback(event, app); + } else if(view_type == SPIMemProgressViewTypeVerify) { + success = spi_mem_view_progress_verify_input_callback(event, app); + } else if(view_type == SPIMemProgressViewTypeWrite) { + success = spi_mem_view_progress_read_write_input_callback(event, app); + } + return success; +} + +SPIMemProgressView* spi_mem_view_progress_alloc() { + SPIMemProgressView* app = malloc(sizeof(SPIMemProgressView)); + app->view = view_alloc(); + view_allocate_model(app->view, ViewModelTypeLocking, sizeof(SPIMemProgressViewModel)); + view_set_context(app->view, app); + view_set_draw_callback(app->view, spi_mem_view_progress_draw_callback); + view_set_input_callback(app->view, spi_mem_view_progress_input_callback); + spi_mem_view_progress_reset(app); + return app; +} + +void spi_mem_view_progress_free(SPIMemProgressView* app) { + view_free(app->view); + free(app); +} + +void spi_mem_view_progress_set_read_callback( + SPIMemProgressView* app, + SPIMemProgressViewCallback callback, + void* cb_ctx) { + app->callback = callback; + app->cb_ctx = cb_ctx; + with_view_model( + app->view, + SPIMemProgressViewModel * model, + { model->view_type = SPIMemProgressViewTypeRead; }, + true); +} + +void spi_mem_view_progress_set_verify_callback( + SPIMemProgressView* app, + SPIMemProgressViewCallback callback, + void* cb_ctx) { + app->callback = callback; + app->cb_ctx = cb_ctx; + with_view_model( + app->view, + SPIMemProgressViewModel * model, + { model->view_type = SPIMemProgressViewTypeVerify; }, + true); +} + +void spi_mem_view_progress_set_write_callback( + SPIMemProgressView* app, + SPIMemProgressViewCallback callback, + void* cb_ctx) { + app->callback = callback; + app->cb_ctx = cb_ctx; + with_view_model( + app->view, + SPIMemProgressViewModel * model, + { model->view_type = SPIMemProgressViewTypeWrite; }, + true); +} + +void spi_mem_view_progress_set_chip_size(SPIMemProgressView* app, size_t chip_size) { + with_view_model( + app->view, SPIMemProgressViewModel * model, { model->chip_size = chip_size; }, true); +} + +void spi_mem_view_progress_set_file_size(SPIMemProgressView* app, size_t file_size) { + with_view_model( + app->view, SPIMemProgressViewModel * model, { model->file_size = file_size; }, true); +} + +void spi_mem_view_progress_set_block_size(SPIMemProgressView* app, size_t block_size) { + with_view_model( + app->view, SPIMemProgressViewModel * model, { model->block_size = block_size; }, true); +} + +static size_t spi_mem_view_progress_set_total_size(SPIMemProgressViewModel* model) { + size_t total_size = model->chip_size; + if((model->chip_size > model->file_size) && model->view_type != SPIMemProgressViewTypeRead) { + total_size = model->file_size; + } + return total_size; +} + +void spi_mem_view_progress_inc_progress(SPIMemProgressView* app) { + with_view_model( + app->view, + SPIMemProgressViewModel * model, + { + size_t total_size = spi_mem_view_progress_set_total_size(model); + if(total_size == 0) total_size = 1; + model->blocks_written++; + model->progress = + ((float)model->block_size * (float)model->blocks_written) / ((float)total_size); + }, + true); +} + +void spi_mem_view_progress_reset(SPIMemProgressView* app) { + with_view_model( + app->view, + SPIMemProgressViewModel * model, + { + model->blocks_written = 0; + model->block_size = 0; + model->chip_size = 0; + model->file_size = 0; + model->progress = 0; + model->view_type = SPIMemProgressViewTypeUnknown; + }, + true); +} diff --git a/applications/plugins/spi_mem_manager/views/spi_mem_view_progress.h b/applications/plugins/spi_mem_manager/views/spi_mem_view_progress.h new file mode 100644 index 00000000000..6a8645b6ce0 --- /dev/null +++ b/applications/plugins/spi_mem_manager/views/spi_mem_view_progress.h @@ -0,0 +1,26 @@ +#pragma once +#include + +typedef struct SPIMemProgressView SPIMemProgressView; +typedef void (*SPIMemProgressViewCallback)(void* context); + +View* spi_mem_view_progress_get_view(SPIMemProgressView* app); +SPIMemProgressView* spi_mem_view_progress_alloc(); +void spi_mem_view_progress_free(SPIMemProgressView* app); +void spi_mem_view_progress_set_read_callback( + SPIMemProgressView* app, + SPIMemProgressViewCallback callback, + void* cb_ctx); +void spi_mem_view_progress_set_verify_callback( + SPIMemProgressView* app, + SPIMemProgressViewCallback callback, + void* cb_ctx); +void spi_mem_view_progress_set_write_callback( + SPIMemProgressView* app, + SPIMemProgressViewCallback callback, + void* cb_ctx); +void spi_mem_view_progress_set_chip_size(SPIMemProgressView* app, size_t chip_size); +void spi_mem_view_progress_set_file_size(SPIMemProgressView* app, size_t file_size); +void spi_mem_view_progress_set_block_size(SPIMemProgressView* app, size_t block_size); +void spi_mem_view_progress_inc_progress(SPIMemProgressView* app); +void spi_mem_view_progress_reset(SPIMemProgressView* app); From 6e179bda1f69f3ce1c216faa90d24577ebeea631 Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Mon, 6 Feb 2023 17:56:36 +0300 Subject: [PATCH 373/824] Script that can find programmer and flash firmware via it. (#2193) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Init * Fallback to networked interface * remove unneeded cmsis_dap_backend * serial number * windows :( * remove jlink, fix path handling * scripts: program: path normalization * scripts: program: path normalization: second encounter Co-authored-by: hedger Co-authored-by: あく --- scripts/program.py | 459 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 459 insertions(+) create mode 100755 scripts/program.py diff --git a/scripts/program.py b/scripts/program.py new file mode 100755 index 00000000000..c140a9024e9 --- /dev/null +++ b/scripts/program.py @@ -0,0 +1,459 @@ +#!/usr/bin/env python3 +import typing +import subprocess +import logging +import time +import os +import socket + +from abc import ABC, abstractmethod +from dataclasses import dataclass +from flipper.app import App + + +class Programmer(ABC): + @abstractmethod + def flash(self, bin: str) -> bool: + pass + + @abstractmethod + def probe(self) -> bool: + pass + + @abstractmethod + def get_name(self) -> str: + pass + + @abstractmethod + def set_serial(self, serial: str): + pass + + +@dataclass +class OpenOCDInterface: + name: str + file: str + serial_cmd: str + additional_args: typing.Optional[list[str]] = None + + +class OpenOCDProgrammer(Programmer): + def __init__(self, interface: OpenOCDInterface): + self.interface = interface + self.logger = logging.getLogger("OpenOCD") + self.serial: typing.Optional[str] = None + + def _add_file(self, params: list[str], file: str): + params.append("-f") + params.append(file) + + def _add_command(self, params: list[str], command: str): + params.append("-c") + params.append(command) + + def _add_serial(self, params: list[str], serial: str): + self._add_command(params, f"{self.interface.serial_cmd} {serial}") + + def set_serial(self, serial: str): + self.serial = serial + + def flash(self, bin: str) -> bool: + i = self.interface + + if os.altsep: + bin = bin.replace(os.sep, os.altsep) + + openocd_launch_params = ["openocd"] + self._add_file(openocd_launch_params, i.file) + if self.serial: + self._add_serial(openocd_launch_params, self.serial) + if i.additional_args: + for a in i.additional_args: + self._add_command(openocd_launch_params, a) + self._add_file(openocd_launch_params, "target/stm32wbx.cfg") + self._add_command(openocd_launch_params, "init") + self._add_command(openocd_launch_params, f"program {bin} reset exit 0x8000000") + + # join the list of parameters into a string, but add quote if there are spaces + openocd_launch_params_string = " ".join( + [f'"{p}"' if " " in p else p for p in openocd_launch_params] + ) + + self.logger.debug(f"Launching: {openocd_launch_params_string}") + + process = subprocess.Popen( + openocd_launch_params, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + ) + + while process.poll() is None: + time.sleep(0.25) + print(".", end="", flush=True) + print() + + success = process.returncode == 0 + + if not success: + self.logger.error("OpenOCD failed to flash") + if process.stdout: + self.logger.error(process.stdout.read().decode("utf-8").strip()) + + return success + + def probe(self) -> bool: + i = self.interface + + openocd_launch_params = ["openocd"] + self._add_file(openocd_launch_params, i.file) + if self.serial: + self._add_serial(openocd_launch_params, self.serial) + if i.additional_args: + for a in i.additional_args: + self._add_command(openocd_launch_params, a) + self._add_file(openocd_launch_params, "target/stm32wbx.cfg") + self._add_command(openocd_launch_params, "init") + self._add_command(openocd_launch_params, "exit") + + self.logger.debug(f"Launching: {' '.join(openocd_launch_params)}") + + process = subprocess.Popen( + openocd_launch_params, + stderr=subprocess.STDOUT, + stdout=subprocess.PIPE, + ) + + # Wait for OpenOCD to end and get the return code + process.wait() + found = process.returncode == 0 + + if process.stdout: + self.logger.debug(process.stdout.read().decode("utf-8").strip()) + + return found + + def get_name(self) -> str: + return self.interface.name + + +def blackmagic_find_serial(serial: str): + import serial.tools.list_ports as list_ports + + if serial and os.name == "nt": + if not serial.startswith("\\\\.\\"): + serial = f"\\\\.\\{serial}" + + ports = list(list_ports.grep("blackmagic")) + if len(ports) == 0: + return None + elif len(ports) > 2: + if serial: + ports = list( + filter( + lambda p: p.serial_number == serial + or p.name == serial + or p.device == serial, + ports, + ) + ) + if len(ports) == 0: + return None + + if len(ports) > 2: + raise Exception("More than one Blackmagic probe found") + + # If you're getting any issues with auto lookup, uncomment this + # print("\n".join([f"{p.device} {vars(p)}" for p in ports])) + port = sorted(ports, key=lambda p: f"{p.location}_{p.name}")[0] + + if serial: + if ( + serial != port.serial_number + and serial != port.name + and serial != port.device + ): + return None + + if os.name == "nt": + port.device = f"\\\\.\\{port.device}" + return port.device + + +def _resolve_hostname(hostname): + try: + return socket.gethostbyname(hostname) + except socket.gaierror: + return None + + +def blackmagic_find_networked(serial: str): + if not serial: + serial = "blackmagic.local" + + # remove the tcp: prefix if it's there + if serial.startswith("tcp:"): + serial = serial[4:] + + # remove the port if it's there + if ":" in serial: + serial = serial.split(":")[0] + + if not (probe := _resolve_hostname(serial)): + return None + + return f"tcp:{probe}:2345" + + +class BlackmagicProgrammer(Programmer): + def __init__( + self, + port_resolver, # typing.Callable[typing.Union[str, None], typing.Optional[str]] + name: str, + ): + self.port_resolver = port_resolver + self.name = name + self.logger = logging.getLogger("BlackmagicUSB") + self.port: typing.Optional[str] = None + + def _add_command(self, params: list[str], command: str): + params.append("-ex") + params.append(command) + + def _valid_ip(self, address): + try: + socket.inet_aton(address) + return True + except: + return False + + def set_serial(self, serial: str): + if self._valid_ip(serial): + self.port = f"{serial}:2345" + elif ip := _resolve_hostname(serial): + self.port = f"{ip}:2345" + else: + self.port = serial + + def flash(self, bin: str) -> bool: + if not self.port: + if not self.probe(): + return False + + # We can convert .bin to .elf with objcopy: + # arm-none-eabi-objcopy -I binary -O elf32-littlearm --change-section-address=.data=0x8000000 -B arm -S app.bin app.elf + # But I choose to use the .elf file directly because we are flashing our own firmware and it always has an elf predecessor. + elf = bin.replace(".bin", ".elf") + if not os.path.exists(elf): + self.logger.error( + f"Sorry, but Blackmagic can't flash .bin file, and {elf} doesn't exist" + ) + return False + + # arm-none-eabi-gdb build/f7-firmware-D/firmware.bin + # -ex 'set pagination off' + # -ex 'target extended-remote /dev/cu.usbmodem21201' + # -ex 'set confirm off' + # -ex 'monitor swdp_scan' + # -ex 'attach 1' + # -ex 'set mem inaccessible-by-default off' + # -ex 'load' + # -ex 'compare-sections' + # -ex 'quit' + + gdb_launch_params = ["arm-none-eabi-gdb", elf] + self._add_command(gdb_launch_params, f"target extended-remote {self.port}") + self._add_command(gdb_launch_params, "set pagination off") + self._add_command(gdb_launch_params, "set confirm off") + self._add_command(gdb_launch_params, "monitor swdp_scan") + self._add_command(gdb_launch_params, "attach 1") + self._add_command(gdb_launch_params, "set mem inaccessible-by-default off") + self._add_command(gdb_launch_params, "load") + self._add_command(gdb_launch_params, "compare-sections") + self._add_command(gdb_launch_params, "quit") + + self.logger.debug(f"Launching: {' '.join(gdb_launch_params)}") + + process = subprocess.Popen( + gdb_launch_params, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + ) + + while process.poll() is None: + time.sleep(0.5) + print(".", end="", flush=True) + print() + + if not process.stdout: + return False + + output = process.stdout.read().decode("utf-8").strip() + flashed = "Loading section .text," in output + + # Check flash verification + if "MIS-MATCHED!" in output: + flashed = False + + if "target image does not match the loaded file" in output: + flashed = False + + if not flashed: + self.logger.error("Blackmagic failed to flash") + self.logger.error(output) + + return flashed + + def probe(self) -> bool: + if not (port := self.port_resolver(self.port)): + return False + + self.port = port + return True + + def get_name(self) -> str: + return self.name + + +programmers: list[Programmer] = [ + OpenOCDProgrammer( + OpenOCDInterface( + "cmsis-dap", + "interface/cmsis-dap.cfg", + "cmsis_dap_serial", + ["transport select swd"], + ), + ), + OpenOCDProgrammer( + OpenOCDInterface( + "stlink", "interface/stlink.cfg", "hla_serial", ["transport select hla_swd"] + ), + ), + BlackmagicProgrammer(blackmagic_find_serial, "blackmagic_usb"), +] + +network_programmers = [ + BlackmagicProgrammer(blackmagic_find_networked, "blackmagic_wifi") +] + + +class Main(App): + def init(self): + self.subparsers = self.parser.add_subparsers(help="sub-command help") + self.parser_flash = self.subparsers.add_parser("flash", help="Flash a binary") + self.parser_flash.add_argument( + "bin", + type=str, + help="Binary to flash", + ) + interfaces = [i.get_name() for i in programmers] + interfaces.extend([i.get_name() for i in network_programmers]) + self.parser_flash.add_argument( + "--interface", + choices=interfaces, + type=str, + help="Interface to use", + ) + self.parser_flash.add_argument( + "--serial", + type=str, + help="Serial number or port of the programmer", + ) + self.parser_flash.set_defaults(func=self.flash) + + def _search_interface(self, serial: typing.Optional[str]) -> list[Programmer]: + found_programmers = [] + + for p in programmers: + name = p.get_name() + if serial: + p.set_serial(serial) + self.logger.debug(f"Trying {name} with {serial}") + else: + self.logger.debug(f"Trying {name}") + + if p.probe(): + self.logger.debug(f"Found {name}") + found_programmers += [p] + else: + self.logger.debug(f"Failed to probe {name}") + + return found_programmers + + def _search_network_interface( + self, serial: typing.Optional[str] + ) -> list[Programmer]: + found_programmers = [] + + for p in network_programmers: + name = p.get_name() + + if serial: + p.set_serial(serial) + self.logger.debug(f"Trying {name} with {serial}") + else: + self.logger.debug(f"Trying {name}") + + if p.probe(): + self.logger.debug(f"Found {name}") + found_programmers += [p] + else: + self.logger.debug(f"Failed to probe {name}") + + return found_programmers + + def flash(self): + start_time = time.time() + bin_path = os.path.abspath(self.args.bin) + + if not os.path.exists(bin_path): + self.logger.error(f"Binary file not found: {bin_path}") + return 1 + + if self.args.interface: + i_name = self.args.interface + interfaces = [p for p in programmers if p.get_name() == i_name] + if len(interfaces) == 0: + interfaces = [p for p in network_programmers if p.get_name() == i_name] + else: + self.logger.info(f"Probing for interfaces...") + interfaces = self._search_interface(self.args.serial) + + if len(interfaces) == 0: + # Probe network blackmagic + self.logger.info(f"Probing for network interfaces...") + interfaces = self._search_network_interface(self.args.serial) + + if len(interfaces) == 0: + self.logger.error("No interface found") + return 1 + + if len(interfaces) > 1: + self.logger.error("Multiple interfaces found: ") + self.logger.error( + f"Please specify '--interface={[i.get_name() for i in interfaces]}'" + ) + return 1 + + interface = interfaces[0] + + if self.args.serial: + interface.set_serial(self.args.serial) + self.logger.info( + f"Flashing {bin_path} via {interface.get_name()} with {self.args.serial}" + ) + else: + self.logger.info(f"Flashing {bin_path} via {interface.get_name()}") + + if not interface.flash(bin_path): + self.logger.error(f"Failed to flash via {interface.get_name()}") + return 1 + + flash_time = time.time() - start_time + bin_size = os.path.getsize(bin_path) + self.logger.info(f"Flashed successfully in {flash_time:.2f}s") + self.logger.info(f"Effective speed: {bin_size / flash_time / 1024:.2f} KiB/s") + return 0 + + +if __name__ == "__main__": + Main()() From 79d45c97f93d54b0082519d7a6a7e9a77e579406 Mon Sep 17 00:00:00 2001 From: nas Date: Mon, 6 Feb 2023 20:24:05 +0500 Subject: [PATCH 374/824] AleX83Xpert/add f keys to keyboard layout (#2362) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Assets: png images for F1..F12 keys: Will be used for HID keyboard layout * HID KEYBOARD F1..F12 keys were added - a new row with functional keys was added - logic for displaying the keyboard layout was improved * HidApp: hid functional keys by default Co-authored-by: あく --- .../plugins/hid_app/assets/ButtonF10_5x8.png | Bin 0 -> 172 bytes .../plugins/hid_app/assets/ButtonF11_5x8.png | Bin 0 -> 173 bytes .../plugins/hid_app/assets/ButtonF12_5x8.png | Bin 0 -> 180 bytes .../plugins/hid_app/assets/ButtonF1_5x8.png | Bin 0 -> 177 bytes .../plugins/hid_app/assets/ButtonF2_5x8.png | Bin 0 -> 179 bytes .../plugins/hid_app/assets/ButtonF3_5x8.png | Bin 0 -> 178 bytes .../plugins/hid_app/assets/ButtonF4_5x8.png | Bin 0 -> 177 bytes .../plugins/hid_app/assets/ButtonF5_5x8.png | Bin 0 -> 178 bytes .../plugins/hid_app/assets/ButtonF6_5x8.png | Bin 0 -> 177 bytes .../plugins/hid_app/assets/ButtonF7_5x8.png | Bin 0 -> 176 bytes .../plugins/hid_app/assets/ButtonF8_5x8.png | Bin 0 -> 176 bytes .../plugins/hid_app/assets/ButtonF9_5x8.png | Bin 0 -> 179 bytes .../plugins/hid_app/views/hid_keyboard.c | 28 ++++++++++++++++-- 13 files changed, 25 insertions(+), 3 deletions(-) create mode 100644 applications/plugins/hid_app/assets/ButtonF10_5x8.png create mode 100644 applications/plugins/hid_app/assets/ButtonF11_5x8.png create mode 100644 applications/plugins/hid_app/assets/ButtonF12_5x8.png create mode 100644 applications/plugins/hid_app/assets/ButtonF1_5x8.png create mode 100644 applications/plugins/hid_app/assets/ButtonF2_5x8.png create mode 100644 applications/plugins/hid_app/assets/ButtonF3_5x8.png create mode 100644 applications/plugins/hid_app/assets/ButtonF4_5x8.png create mode 100644 applications/plugins/hid_app/assets/ButtonF5_5x8.png create mode 100644 applications/plugins/hid_app/assets/ButtonF6_5x8.png create mode 100644 applications/plugins/hid_app/assets/ButtonF7_5x8.png create mode 100644 applications/plugins/hid_app/assets/ButtonF8_5x8.png create mode 100644 applications/plugins/hid_app/assets/ButtonF9_5x8.png diff --git a/applications/plugins/hid_app/assets/ButtonF10_5x8.png b/applications/plugins/hid_app/assets/ButtonF10_5x8.png new file mode 100644 index 0000000000000000000000000000000000000000..d1a7a04f06dcc4b5446aad5a40a9863038bf56b5 GIT binary patch literal 172 zcmeAS@N?(olHy`uVBq!ia0vp^tU%1c!2~4tO5(ej@)Wnk16ovB4k_-iRPv3y>Mm}+% zA~jDJ#}JO|$tewu|Ns9tHZU+a6e)0^L#NYq9;5F(PTxtKzKT}z3_A)0e;QviHwNlp N@O1TaS?83{1OPU#E%g8Z literal 0 HcmV?d00001 diff --git a/applications/plugins/hid_app/assets/ButtonF11_5x8.png b/applications/plugins/hid_app/assets/ButtonF11_5x8.png new file mode 100644 index 0000000000000000000000000000000000000000..7e177358e81695342f2a283a220c7cacc7bda939 GIT binary patch literal 173 zcmeAS@N?(olHy`uVBq!ia0vp^tU%1c!2~4tOpBPSmNg(OQ{BTAg}b8}PkN*J7rQWHy3QxwWGOEMJPJ$(bh8~Mb6 ziqt(_978y+C#N(t{{R2q*ucQxP^7?t4xLU{IYmyznHN-MN(3-k$uq=X7x{LAUCkJ% Og~8L+&t;ucLK6T7Y%hHP literal 0 HcmV?d00001 diff --git a/applications/plugins/hid_app/assets/ButtonF12_5x8.png b/applications/plugins/hid_app/assets/ButtonF12_5x8.png new file mode 100644 index 0000000000000000000000000000000000000000..50d2a7dc63b9d366ccfbacbc05e6bb0d9e335b5b GIT binary patch literal 180 zcmeAS@N?(olHy`uVBq!ia0vp^tU%1c!2~4tO zk)EfEV+hCf+#W|R1_Pc$J^%l2ww|&zRcE|OcGEe{j literal 0 HcmV?d00001 diff --git a/applications/plugins/hid_app/assets/ButtonF1_5x8.png b/applications/plugins/hid_app/assets/ButtonF1_5x8.png new file mode 100644 index 0000000000000000000000000000000000000000..7394d27105fd0a27067803bfd633a26bedd0f1d5 GIT binary patch literal 177 zcmeAS@N?(olHy`uVBq!ia0vp^tU%1c!2~4tO5(ej@)Wnk16ovB4k_-iRPv3y>Mm}+% zB5h9>#}JO|$tewu|Ns9tHZU+a6e)0^L+9jy0|#0HPM+X+s?4mGa`wiPi$55Iw(~PB TTpxE5sExtX)z4*}Q$iB}`k6I% literal 0 HcmV?d00001 diff --git a/applications/plugins/hid_app/assets/ButtonF2_5x8.png b/applications/plugins/hid_app/assets/ButtonF2_5x8.png new file mode 100644 index 0000000000000000000000000000000000000000..9d922a3858147116d65b6f03e2b36ea846b2f60c GIT binary patch literal 179 zcmeAS@N?(olHy`uVBq!ia0vp^tU%1c!2~4tO zk*=qUV+hCf)NV&U1_ho&w~qX;m*hWYIaS!#v1}4USoB8kx*gxNJa@^Tf4zarr_o#d UH)s04hd_-Cp00i_>zopr0BX-NbN~PV literal 0 HcmV?d00001 diff --git a/applications/plugins/hid_app/assets/ButtonF3_5x8.png b/applications/plugins/hid_app/assets/ButtonF3_5x8.png new file mode 100644 index 0000000000000000000000000000000000000000..95c2dd4f4198e182a1a62927c4d3627400a7b883 GIT binary patch literal 178 zcmeAS@N?(olHy`uVBq!ia0vp^tU%1c!2~4tO zk&dT}V+hCf)GkXd1_g%0LLdIeuWPOlF*aSY$&;z#+9C5#-hV?Uh3H=_oX_AhhhO~n UO!>#_f%+IcUHx3vIVCg!073*Z7ytkO literal 0 HcmV?d00001 diff --git a/applications/plugins/hid_app/assets/ButtonF4_5x8.png b/applications/plugins/hid_app/assets/ButtonF4_5x8.png new file mode 100644 index 0000000000000000000000000000000000000000..602466f4b667b6df4d5335517bd9d43e5f0b6e69 GIT binary patch literal 177 zcmeAS@N?(olHy`uVBq!ia0vp^tU%1c!2~4tO5(ej@)Wnk16ovB4k_-iRPv3y>Mm}+% zB5h9>#}JO|vE3Va85}s64*o6Qr{DAJBNr3n8pa7vN_+nsOQj%x4ZT`lf}PS TtvtgA)W+cH>gTe~DWM4fb!sy& literal 0 HcmV?d00001 diff --git a/applications/plugins/hid_app/assets/ButtonF5_5x8.png b/applications/plugins/hid_app/assets/ButtonF5_5x8.png new file mode 100644 index 0000000000000000000000000000000000000000..d73b5405275f6c53f7a2f157df063db1ca351caa GIT binary patch literal 178 zcmeAS@N?(olHy`uVBq!ia0vp^tU%1c!2~4tOFVdQ&MBb@0Fom!%>V!Z literal 0 HcmV?d00001 diff --git a/applications/plugins/hid_app/assets/ButtonF6_5x8.png b/applications/plugins/hid_app/assets/ButtonF6_5x8.png new file mode 100644 index 0000000000000000000000000000000000000000..c50748257ab8e06f90007e93b913d5be4999d096 GIT binary patch literal 177 zcmeAS@N?(olHy`uVBq!ia0vp^tU%1c!2~4tOA;}Wgh!W@g+}zZ>5(ej@)Wnk16ovB4k_-iRPv3y>Mm}+% zB5h9>#}JO|shy5|3<^BWD?a{@Kh_*L{p89i1p$qBwtDvdG5Wpwnq5@=JeG)jb@A^; T#rkJ}+88`t{an^LB{Ts5$FwwN literal 0 HcmV?d00001 diff --git a/applications/plugins/hid_app/assets/ButtonF7_5x8.png b/applications/plugins/hid_app/assets/ButtonF7_5x8.png new file mode 100644 index 0000000000000000000000000000000000000000..396c98f5104f94b6310593ce6c7e6ce3d2369ef3 GIT binary patch literal 176 zcmeAS@N?(olHy`uVBq!ia0vp^tU%1c!2~4tO+nf~Hxmr&P}udD~(3jVRo RuLQY`!PC{xWt~$(69B*FH3|R# literal 0 HcmV?d00001 diff --git a/applications/plugins/hid_app/assets/ButtonF8_5x8.png b/applications/plugins/hid_app/assets/ButtonF8_5x8.png new file mode 100644 index 0000000000000000000000000000000000000000..6304d7fb888b2cf38c54e7124aaa604d1610629c GIT binary patch literal 176 zcmeAS@N?(olHy`uVBq!ia0vp^tU%1c!2~4tO5(ej@)Wnk16ovB4k_-iRPv3y>Mm}+% zA}voB#}JO|wVjT93<^BWEB^mCmh0Jd#>H=GOE1@xHLh7tre2KydVRe*qgvi_@vq`% Sf29C*F?hQAxvXEZzkxv|` zNY~TFF@)oKa!Nzv|NsAu4GatpMG73~&^g&~GSNx=@BjbyU3DUS%F5hxSQ8l-I!}xL U>{@7g2&j?4)78&qol`;+0Ic0IyZ`_I literal 0 HcmV?d00001 diff --git a/applications/plugins/hid_app/views/hid_keyboard.c b/applications/plugins/hid_app/views/hid_keyboard.c index 8b12e8fd146..82e772b15d0 100644 --- a/applications/plugins/hid_app/views/hid_keyboard.c +++ b/applications/plugins/hid_app/views/hid_keyboard.c @@ -46,11 +46,25 @@ typedef struct { #define KEY_WIDTH 9 #define KEY_HEIGHT 12 #define KEY_PADDING 1 -#define ROW_COUNT 6 +#define ROW_COUNT 7 #define COLUMN_COUNT 12 // 0 width items are not drawn, but there value is used const HidKeyboardKey hid_keyboard_keyset[ROW_COUNT][COLUMN_COUNT] = { + { + {.width = 1, .icon = &I_ButtonF1_5x8, .value = HID_KEYBOARD_F1}, + {.width = 1, .icon = &I_ButtonF2_5x8, .value = HID_KEYBOARD_F2}, + {.width = 1, .icon = &I_ButtonF3_5x8, .value = HID_KEYBOARD_F3}, + {.width = 1, .icon = &I_ButtonF4_5x8, .value = HID_KEYBOARD_F4}, + {.width = 1, .icon = &I_ButtonF5_5x8, .value = HID_KEYBOARD_F5}, + {.width = 1, .icon = &I_ButtonF6_5x8, .value = HID_KEYBOARD_F6}, + {.width = 1, .icon = &I_ButtonF7_5x8, .value = HID_KEYBOARD_F7}, + {.width = 1, .icon = &I_ButtonF8_5x8, .value = HID_KEYBOARD_F8}, + {.width = 1, .icon = &I_ButtonF9_5x8, .value = HID_KEYBOARD_F9}, + {.width = 1, .icon = &I_ButtonF10_5x8, .value = HID_KEYBOARD_F10}, + {.width = 1, .icon = &I_ButtonF11_5x8, .value = HID_KEYBOARD_F11}, + {.width = 1, .icon = &I_ButtonF12_5x8, .value = HID_KEYBOARD_F12}, + }, { {.width = 1, .icon = NULL, .key = "1", .shift_key = "!", .value = HID_KEYBOARD_1}, {.width = 1, .icon = NULL, .key = "2", .shift_key = "@", .value = HID_KEYBOARD_2}, @@ -224,7 +238,12 @@ static void hid_keyboard_draw_callback(Canvas* canvas, void* context) { canvas_set_font(canvas, FontKeyboard); // Start shifting the all keys up if on the next row (Scrolling) - uint8_t initY = model->y - 4 > 0 ? model->y - 4 : 0; + uint8_t initY = model->y == 0 ? 0 : 1; + + if(model->y > 5) { + initY = model->y - 4; + } + for(uint8_t y = initY; y < ROW_COUNT; y++) { const HidKeyboardKey* keyboardKeyRow = hid_keyboard_keyset[y]; uint8_t x = 0; @@ -365,7 +384,10 @@ HidKeyboard* hid_keyboard_alloc(Hid* bt_hid) { with_view_model( hid_keyboard->view, HidKeyboardModel * model, - { model->transport = bt_hid->transport; }, + { + model->transport = bt_hid->transport; + model->y = 1; + }, true); return hid_keyboard; From 147f42a2b7bbcd5a497e862be47a72ef2c233801 Mon Sep 17 00:00:00 2001 From: Wild Rat Date: Mon, 6 Feb 2023 17:49:34 +0200 Subject: [PATCH 375/824] Add Daikin FTXM20M & Mitsubishi SRK63HE (#2349) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- assets/resources/infrared/assets/ac.ir | 74 ++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/assets/resources/infrared/assets/ac.ir b/assets/resources/infrared/assets/ac.ir index 1f9c2145f69..96a2a0f38eb 100644 --- a/assets/resources/infrared/assets/ac.ir +++ b/assets/resources/infrared/assets/ac.ir @@ -297,3 +297,77 @@ type: raw frequency: 38000 duty_cycle: 0.330000 data: 2320 634 837 637 838 637 838 640 835 642 832 1378 836 645 826 670 809 667 808 1406 806 672 803 674 802 1412 802 1412 800 676 801 675 802 1412 802 674 802 1413 801 1412 801 1413 802 1412 802 50937 2285 671 801 1411 802 51225 2280 696 775 1412 801 51212 2283 671 775 1412 802 +# Model: Daikin FTXM20M. +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 500 393 473 392 473 368 498 368 497 367 499 25050 3533 1662 502 1230 501 392 472 393 471 395 469 1263 467 400 465 401 465 401 465 401 465 1267 465 401 465 1268 464 1268 464 402 464 1268 464 1269 463 1269 463 1292 440 1269 463 404 462 426 439 1293 439 426 440 426 440 426 440 426 440 426 440 427 439 426 440 427 439 427 438 427 439 1294 438 427 439 1294 438 427 439 427 439 428 438 1294 438 1294 438 428 438 428 437 428 438 428 438 1294 438 428 438 428 438 429 437 429 437 429 436 429 437 429 437 429 437 429 436 429 437 430 436 1296 436 1296 436 1296 436 430 436 430 435 1297 435 1297 435 1298 434 35482 3500 1699 464 1268 464 402 463 402 463 403 463 1269 463 426 440 426 439 426 440 426 439 1293 439 426 440 1293 439 1293 439 427 439 1293 439 1293 439 1293 439 1293 439 1294 438 427 438 427 439 1294 438 427 439 428 438 428 438 428 438 427 438 428 438 428 437 428 438 428 437 428 438 428 438 1295 437 429 437 429 437 429 436 429 437 1296 436 429 437 429 436 429 437 430 436 430 436 430 435 430 436 430 435 431 435 431 435 431 435 455 410 456 409 1322 410 456 409 456 410 456 410 456 410 456 410 1323 409 457 409 457 409 1323 409 1323 409 457 409 35483 3500 1699 464 1268 464 402 464 402 463 403 463 1269 463 426 440 426 440 426 440 426 439 1293 439 426 439 1293 439 1293 439 427 439 1293 439 1293 439 1293 439 1294 438 1293 438 427 439 427 438 1294 438 428 438 427 438 428 438 428 438 428 438 428 437 428 438 428 438 428 438 428 438 428 438 428 438 429 437 429 437 429 437 429 437 429 437 429 437 429 437 429 437 430 436 1296 436 430 436 1297 435 430 436 430 436 431 435 431 435 456 410 456 410 456 410 456 409 1323 409 1322 410 456 410 456 409 457 409 457 409 457 409 457 409 457 408 457 409 1324 408 1324 408 1324 408 1324 408 458 408 1325 406 459 407 1351 356 1376 356 1376 356 1376 356 1377 355 510 355 510 356 511 355 511 354 511 355 537 328 537 329 538 328 538 327 538 328 539 327 565 300 566 300 1432 300 1433 298 593 272 594 271 621 245 621 244 622 243 +# +name: Dh +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 500 368 498 369 497 367 499 366 499 367 499 25050 3533 1662 502 1230 501 392 472 393 471 395 469 1264 467 400 465 401 465 401 465 401 465 1267 465 401 465 1268 464 1268 464 402 464 1269 463 1269 463 1269 463 1292 440 1292 440 426 440 426 440 1293 439 426 440 426 439 426 440 426 440 427 439 427 438 427 439 427 439 427 439 427 439 1293 439 427 439 1294 438 427 439 427 439 428 437 1294 438 1294 438 428 437 428 438 428 437 429 437 1295 437 428 437 429 437 429 437 429 436 429 437 429 437 429 437 429 437 429 437 429 437 430 436 1296 436 1297 435 1297 435 431 435 455 410 1298 434 1322 410 1322 410 35482 3500 1700 464 1269 463 403 463 403 463 403 462 1292 440 426 440 426 440 426 440 426 439 1293 439 426 440 1293 439 1293 439 427 439 1293 439 1293 439 1293 439 1293 439 1293 439 427 439 427 439 1294 438 427 439 427 439 427 439 428 438 427 438 428 438 428 438 428 437 428 438 428 438 428 438 1295 437 429 437 429 436 429 437 429 437 1296 436 429 437 429 437 429 437 430 436 430 436 430 436 431 435 431 435 431 435 455 410 456 410 456 410 456 410 1322 410 456 410 456 410 456 409 457 409 456 409 1323 409 457 409 457 409 1323 409 1324 408 458 408 35483 3500 1700 464 1268 464 402 464 403 463 402 464 1292 440 426 440 426 440 426 439 426 440 1293 439 426 440 1293 439 1293 439 427 439 1293 439 1293 439 1293 438 1293 439 1293 439 427 439 427 438 1294 438 428 438 428 438 427 438 428 438 428 438 428 438 428 438 428 438 428 438 428 437 428 438 429 436 429 437 429 437 429 437 429 437 429 437 429 437 1296 436 430 435 430 436 1297 435 431 435 1297 435 431 435 456 409 456 410 456 409 456 410 456 410 456 410 456 410 1323 409 1323 409 457 409 457 409 457 408 457 409 457 409 458 408 458 407 458 408 1325 407 1326 405 1327 406 1351 381 485 356 1376 356 510 355 1377 355 1377 355 1377 355 1378 354 1378 354 512 354 512 354 538 327 538 328 538 328 539 326 565 300 566 300 566 299 567 299 593 272 621 244 596 270 1488 244 +# +name: Cool_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 504 389 477 364 501 364 502 364 501 364 502 25048 3511 1684 505 1228 504 389 475 390 474 392 472 1260 470 396 469 396 470 396 469 397 469 1263 469 397 469 1263 469 1263 469 397 468 1263 469 1263 469 1264 468 1263 469 1264 468 397 468 398 468 1264 468 398 468 398 468 398 468 398 468 398 468 399 467 398 468 399 467 423 443 400 466 1289 443 423 443 1289 443 423 443 423 443 423 443 1289 443 1290 442 424 442 423 442 423 443 424 442 1290 442 424 442 423 443 424 442 424 441 424 442 424 441 424 442 424 442 424 441 424 442 424 442 1291 441 1291 441 1291 441 425 441 425 441 1291 441 1291 441 1291 441 35478 3505 1695 468 1264 468 398 467 398 468 398 468 1265 467 398 467 398 468 398 468 399 467 1265 467 399 467 1266 466 1266 466 423 443 1289 443 1290 442 1290 442 1290 442 1290 442 424 442 423 442 1290 442 423 443 424 442 424 442 424 442 424 441 424 442 424 442 424 441 424 442 424 442 424 442 1291 441 425 441 425 441 424 442 424 441 1291 441 425 441 425 440 425 441 425 441 425 441 425 441 425 441 425 440 425 441 425 441 425 440 426 440 426 440 1292 440 426 440 426 440 426 440 426 439 427 439 1293 439 427 439 427 438 1294 438 1294 438 428 437 35479 3504 1695 468 1264 468 398 468 398 468 398 467 1265 467 399 467 399 467 399 466 399 467 1265 467 399 467 1289 443 1290 442 423 443 1290 442 1290 442 1290 442 1290 442 1290 442 423 443 424 442 1290 442 424 442 424 442 424 441 424 442 424 442 424 442 424 442 424 442 424 442 424 442 424 442 424 442 425 441 425 440 425 441 425 441 425 441 425 441 1291 441 425 441 425 441 1292 440 1292 440 1292 440 426 440 425 441 426 440 426 440 1292 440 426 440 426 440 1292 440 426 440 426 440 426 440 427 439 426 440 426 440 427 438 427 439 427 439 427 439 1294 438 1295 437 1294 438 1319 413 453 413 1319 413 453 412 1320 412 1319 413 1319 413 1319 413 1320 412 453 412 453 413 453 412 454 412 454 411 454 412 454 412 454 412 454 412 454 412 455 411 454 412 455 411 1321 411 1321 411 456 409 456 410 481 384 482 384 482 383 482 359 506 360 507 359 507 359 507 358 1374 358 1374 358 508 357 508 358 509 357 534 332 534 332 534 332 534 332 535 331 535 331 535 331 535 330 536 330 536 330 562 303 563 303 563 302 564 302 1430 302 565 301 564 302 591 274 619 247 619 247 1512 219 1539 190 +# +name: Cool_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 502 390 476 365 501 365 500 365 500 365 501 25049 3535 1660 504 1229 502 390 474 391 473 393 471 1261 469 397 468 398 468 398 468 398 468 1264 468 398 468 1265 467 1265 467 399 467 1265 467 1265 467 1265 467 1266 466 1265 467 400 466 401 465 1266 466 401 465 401 465 401 465 424 442 424 441 424 442 424 441 424 442 424 442 424 442 1291 441 424 442 1291 441 424 442 425 440 425 441 1291 441 1291 441 425 441 425 441 425 440 425 441 1292 440 425 441 425 441 425 441 425 441 425 441 425 441 425 441 425 440 426 440 426 440 426 440 1292 440 1293 439 1293 439 426 439 427 439 1293 439 1293 439 1293 439 35480 3503 1696 467 1265 467 399 467 398 468 399 466 1266 466 399 467 399 467 399 467 399 467 1266 466 400 466 1267 465 1290 442 424 442 1290 442 1290 442 1290 442 1290 441 1291 441 424 442 424 442 1291 441 424 442 424 442 425 441 424 442 424 442 425 441 425 440 425 441 425 441 425 441 425 441 1292 440 425 441 425 441 425 440 426 440 1292 440 426 440 426 439 426 440 426 439 426 440 426 440 426 440 426 440 426 440 427 438 427 439 427 439 427 439 1293 439 427 438 427 439 428 438 428 438 428 438 1294 438 428 438 428 438 1295 437 1320 412 453 413 35480 3503 1696 467 1265 467 399 467 399 467 399 467 1266 466 399 467 399 467 400 466 400 466 1290 442 424 442 1289 443 1290 442 424 442 1291 441 1290 442 1290 442 1291 441 1291 441 424 442 424 442 1291 441 425 441 425 441 425 441 425 441 425 441 425 441 425 441 425 441 425 440 425 441 425 441 425 441 425 441 425 441 425 440 426 440 426 440 426 440 1292 440 426 440 426 440 1292 440 1293 439 1293 439 427 439 426 439 427 439 1293 439 1293 439 1293 439 427 438 1294 438 427 439 427 438 428 438 427 438 453 413 429 437 429 437 429 437 453 412 454 412 1320 412 1320 412 1320 412 1320 412 454 412 1321 411 454 412 1320 412 1321 411 1321 411 1321 411 1321 411 455 410 455 411 455 411 455 411 455 411 456 410 456 410 457 409 481 384 457 409 482 383 483 383 483 358 1374 358 1374 358 508 357 508 358 509 357 509 357 509 357 509 357 510 356 535 330 536 330 536 330 1402 330 1403 329 537 329 536 329 563 302 564 302 564 302 565 300 565 300 592 273 619 246 620 246 619 246 620 245 674 188 +# +name: Heat_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 504 365 500 365 500 365 500 365 501 365 501 25049 3535 1660 504 1229 502 390 474 391 473 393 471 1261 470 397 468 397 469 398 468 398 468 1264 468 398 467 1265 467 1265 467 398 468 1265 467 1265 467 1265 467 1266 466 1266 466 399 466 399 467 1266 466 400 466 400 466 400 465 400 466 424 442 424 442 424 442 424 441 424 442 424 442 1291 441 424 442 1291 441 424 442 424 442 425 441 1291 441 1291 441 425 440 425 441 425 441 425 440 1292 440 425 441 425 440 425 441 425 441 425 441 425 441 425 441 426 440 426 440 426 440 426 439 1293 439 1293 439 1293 439 426 440 427 439 1293 439 1293 439 1293 439 35480 3503 1696 468 1264 468 398 468 398 468 398 467 1265 467 399 467 399 467 399 467 399 467 1267 465 400 466 1266 466 1267 465 424 441 1290 442 1290 442 1290 442 1290 442 1291 441 424 442 424 442 1291 441 424 442 424 442 424 442 424 442 424 441 425 441 425 441 425 441 425 441 425 441 425 441 1292 440 425 441 425 441 425 441 425 441 1292 440 426 440 426 439 426 440 426 440 426 439 426 440 426 440 426 439 427 439 426 440 427 439 427 439 427 438 1293 439 427 439 427 439 427 439 428 438 428 438 1295 437 428 438 429 436 1296 436 1296 436 453 412 35481 3502 1696 467 1265 467 399 466 399 467 399 467 1265 467 399 467 399 467 399 466 400 466 1266 466 400 466 1290 442 1267 465 424 442 1290 442 1290 442 1290 442 1290 442 1291 441 424 442 424 441 1291 441 424 442 424 442 424 442 424 441 425 441 425 441 425 441 425 441 425 441 425 441 425 441 425 441 425 440 426 440 425 441 425 441 425 441 426 440 1292 440 426 440 426 440 1292 440 426 440 426 439 1293 439 427 439 427 439 427 439 1293 439 1294 438 1294 438 1293 439 427 439 428 438 428 438 428 438 429 436 429 437 429 437 453 412 453 413 453 412 1320 412 1320 412 1320 412 1320 412 454 412 1320 412 454 412 1321 411 1321 411 1321 411 1321 411 1322 410 455 411 455 411 456 410 456 410 455 410 456 410 456 409 481 384 458 408 482 383 482 384 483 358 508 358 1374 358 1374 358 508 357 509 357 509 357 510 355 535 330 536 330 536 330 536 329 536 330 536 330 1403 329 1430 301 564 302 564 302 564 301 591 274 566 301 592 273 619 247 619 247 620 245 647 219 +# +name: Heat_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 503 365 500 364 501 366 499 365 500 364 502 25049 3535 1660 504 1228 503 390 474 391 473 393 471 1261 469 397 468 397 469 397 469 397 469 1264 468 398 468 1264 468 1264 468 398 468 1265 467 1265 467 1265 467 1265 467 1265 467 399 467 399 467 1266 466 399 467 400 466 400 466 400 466 423 443 423 443 401 465 423 442 424 442 424 442 1290 442 424 442 1290 442 424 442 424 441 424 442 1290 442 1291 441 425 441 424 442 425 441 425 441 1291 441 425 440 425 441 425 441 425 441 425 441 425 440 425 441 425 441 425 441 425 441 426 440 1292 440 1292 440 1292 440 426 440 426 440 1292 440 1293 439 1293 439 35480 3503 1696 467 1264 468 398 468 398 467 398 468 1265 467 398 467 399 467 399 466 399 467 1265 467 399 467 1266 466 1267 465 400 466 1290 442 1290 442 1290 442 1290 442 1290 442 424 442 424 442 1290 442 424 441 424 442 424 442 424 442 424 442 424 441 424 442 425 441 424 442 425 441 425 441 1291 441 425 441 425 441 425 441 425 440 1292 440 425 441 425 440 426 440 426 440 426 440 426 440 426 440 426 440 426 440 426 439 427 439 426 440 426 440 1293 439 427 439 427 439 427 438 427 439 428 438 1294 438 428 437 428 438 1295 437 1319 413 453 413 35480 3503 1696 468 1265 467 398 468 398 468 398 468 1265 467 398 468 399 466 399 467 399 467 1266 466 399 466 1267 465 1290 442 401 465 1290 442 1290 442 1290 442 1290 442 1290 442 424 442 424 441 1291 441 424 442 424 442 424 442 424 442 424 441 424 442 425 441 424 442 424 441 425 441 425 441 425 440 425 441 425 441 425 441 425 441 425 441 425 441 1292 440 426 440 426 440 1292 440 426 440 426 440 1293 439 426 440 426 440 1293 439 1293 439 1293 439 427 439 1294 438 427 438 427 439 427 438 428 438 428 438 428 438 428 438 453 413 429 437 453 413 1319 413 1320 412 1320 412 1320 412 454 412 1320 412 454 412 1321 411 1321 411 1321 411 1321 411 1321 411 455 410 455 411 455 411 455 410 456 410 456 410 456 410 481 384 481 385 482 383 482 383 483 358 507 359 1374 358 1374 358 508 358 508 358 508 358 509 357 509 357 535 331 535 331 535 330 535 330 536 330 1403 329 1403 329 563 302 564 301 564 302 564 301 565 301 591 274 619 246 593 273 620 245 620 245 621 245 673 189 +# Model: Mitsubishi SRK63HE. +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3232 1526 463 334 462 1127 464 334 460 334 461 1128 463 336 459 1128 464 359 436 359 436 1133 458 1156 434 1157 434 362 433 1159 432 363 432 1159 432 1159 432 1159 432 363 433 363 432 363 432 363 432 1160 431 1160 432 363 432 1160 431 1160 431 363 432 364 432 1160 431 363 432 363 432 1160 432 363 432 364 431 1160 431 1160 431 364 431 1160 431 1160 431 1160 431 1160 431 1160 431 1160 431 364 431 1160 431 1160 431 1160 431 364 432 364 432 364 431 364 431 1160 432 364 432 364 431 364 431 1160 431 1160 431 1160 431 364 431 1161 430 1161 430 1160 431 1161 430 364 432 364 431 365 431 1161 430 364 431 365 431 364 431 365 430 365 431 1161 430 1161 430 1161 430 365 430 1161 430 365 430 365 430 1161 430 365 430 365 431 365 430 1161 430 365 430 1161 430 1161 430 +# +name: Dh +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3229 1528 461 335 461 1128 463 334 461 335 460 1129 463 337 458 1129 463 359 436 360 434 1156 434 1157 433 1159 432 364 431 1161 430 364 431 1161 430 1161 430 1160 431 364 431 364 431 365 430 365 430 1161 430 1161 430 365 430 1161 430 1161 431 365 430 365 431 1161 430 365 430 365 430 1161 430 365 430 365 431 1161 430 1161 430 365 431 1161 430 1161 430 1161 430 1161 430 1161 431 1161 430 365 430 1161 430 1161 430 1161 431 365 430 365 430 365 430 365 430 1162 430 365 430 365 430 365 431 1162 429 1162 429 1162 430 365 430 1162 429 1162 429 1162 429 1162 429 366 430 366 429 366 430 1162 429 366 429 366 430 366 429 366 429 1162 429 366 429 1163 428 366 429 1162 429 366 429 366 429 1162 429 366 430 1162 429 366 429 1163 429 366 430 1163 428 1163 428 367 428 +# +name: Cool_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3230 1526 463 335 460 1128 464 333 461 335 460 1128 463 337 458 1128 463 359 436 359 436 1155 435 1157 433 1158 432 363 432 1160 431 364 431 1161 430 1160 431 1160 431 364 431 364 431 364 431 364 431 1161 430 1161 430 364 431 1161 430 1160 431 365 430 364 431 1161 431 364 431 364 431 1161 430 365 430 365 431 1161 430 1161 431 364 431 1161 430 1161 430 1161 430 1161 431 1161 430 1161 431 365 430 1161 431 1161 430 1162 430 365 430 365 430 365 431 365 430 1161 430 365 430 365 430 365 431 1161 430 1162 430 1162 429 365 430 1162 429 1162 429 1162 429 1162 429 366 429 366 430 366 430 1162 429 366 430 366 429 366 429 366 430 366 429 1162 429 1163 429 366 429 366 429 1163 428 1163 428 1163 429 1163 428 366 429 366 430 1163 428 1163 428 367 428 367 429 366 429 +# +name: Cool_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3231 1526 463 334 461 1127 465 333 461 334 460 1128 463 336 459 1129 463 359 436 359 436 1133 458 1156 434 1157 433 362 433 1159 432 363 432 1159 432 1160 431 1159 433 363 432 363 432 363 432 363 432 1159 433 1159 432 363 432 1159 432 1160 432 363 432 363 432 1159 432 363 432 363 433 1159 432 364 432 363 432 1160 431 1160 432 363 432 1160 431 1160 431 1160 431 1160 432 1160 431 1160 431 364 432 1160 431 1160 431 1160 431 364 431 364 431 364 432 364 431 1160 432 364 431 364 431 364 432 1160 431 1161 430 1161 430 364 431 1161 431 1161 430 1161 430 1161 430 364 432 364 431 364 431 1161 430 365 430 365 430 365 430 365 431 365 430 1161 430 1161 431 365 430 1161 430 365 430 365 431 1161 430 1161 430 365 431 365 430 1162 430 365 431 1161 430 1162 429 365 430 +# +name: Heat_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3230 1526 463 334 461 1128 464 333 461 334 461 1128 463 336 459 1129 463 359 436 359 436 1155 435 1156 434 1158 433 363 432 1159 432 363 432 1159 432 1160 431 1160 431 363 433 363 432 363 432 363 432 1160 432 1160 431 364 431 1160 431 1160 432 364 431 364 431 1160 431 364 431 364 432 1160 431 364 431 364 431 1160 431 1160 431 364 432 1160 431 1160 431 1160 431 1160 431 1160 431 1160 431 364 431 1160 432 1160 431 1160 431 364 432 364 431 364 431 364 431 1161 430 364 431 364 432 364 431 1161 430 1161 430 1161 430 365 430 1161 431 1161 430 1161 430 1161 430 365 430 365 430 365 430 1161 430 365 431 365 431 365 430 365 431 1161 430 1161 430 365 430 365 430 365 430 1162 430 365 430 365 430 365 431 365 430 1162 429 1162 430 1162 429 366 429 1162 429 1162 429 +# +name: Heat_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3234 1525 463 333 462 1127 465 332 462 333 436 1153 518 307 488 1073 518 307 488 308 434 1131 459 1155 435 1156 434 362 433 1159 432 363 432 1159 432 1159 432 1159 433 363 432 363 432 363 433 363 432 1159 433 1159 432 363 432 1159 433 1159 432 363 432 363 432 1159 432 363 433 363 432 1159 432 363 432 363 432 1159 432 1159 432 363 432 1160 432 1160 431 1160 432 1160 431 1160 431 1160 431 364 431 1160 431 1160 431 1160 431 364 431 364 431 364 431 364 431 1160 431 364 431 364 431 364 431 1160 432 1160 431 1160 432 364 431 1160 431 1160 431 1161 431 1161 430 364 431 364 431 364 431 1160 432 364 431 364 431 364 432 364 431 1161 431 1161 431 364 431 364 431 1161 430 364 432 364 431 1161 430 365 431 365 431 1161 430 1161 430 365 431 1161 430 1161 430 365 430 From 1ff5843ee690e3e6c0d74a5fa569de3ffcf2b8d1 Mon Sep 17 00:00:00 2001 From: Round-Pi <9893098+Round-Pi@users.noreply.github.com> Date: Mon, 6 Feb 2023 22:05:52 -0500 Subject: [PATCH 376/824] battery info temperature shown in C or F based on settings (#2360) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * battery Info temperature displays C or F * PowerSettings: add locale module to dependencies Co-authored-by: あく --- .../settings/power_settings_app/application.fam | 1 + .../settings/power_settings_app/views/battery_info.c | 11 ++++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/applications/settings/power_settings_app/application.fam b/applications/settings/power_settings_app/application.fam index a5b1966b2bf..6f3589e1a84 100644 --- a/applications/settings/power_settings_app/application.fam +++ b/applications/settings/power_settings_app/application.fam @@ -6,6 +6,7 @@ App( requires=[ "gui", "power", + "locale", ], flags=["InsomniaSafe"], stack_size=1 * 1024, diff --git a/applications/settings/power_settings_app/views/battery_info.c b/applications/settings/power_settings_app/views/battery_info.c index 5353a2e2a67..d29769d2185 100644 --- a/applications/settings/power_settings_app/views/battery_info.c +++ b/applications/settings/power_settings_app/views/battery_info.c @@ -2,6 +2,7 @@ #include #include #include +#include #define LOW_CHARGE_THRESHOLD 10 #define HIGH_DRAIN_CURRENT_THRESHOLD 100 @@ -101,7 +102,15 @@ static void battery_info_draw_callback(Canvas* canvas, void* context) { char health[10]; snprintf(batt_level, sizeof(batt_level), "%lu%%", (uint32_t)model->charge); - snprintf(temperature, sizeof(temperature), "%lu C", (uint32_t)model->gauge_temperature); + if(locale_get_measurement_unit() == LocaleMeasurementUnitsMetric) { + snprintf(temperature, sizeof(temperature), "%lu C", (uint32_t)model->gauge_temperature); + } else { + snprintf( + temperature, + sizeof(temperature), + "%lu F", + (uint32_t)locale_celsius_to_fahrenheit(model->gauge_temperature)); + } snprintf( voltage, sizeof(voltage), From d035872cf657eccc7a488c61e4cbf1850ab0bf7c Mon Sep 17 00:00:00 2001 From: Krzysztof Zdulski Date: Tue, 7 Feb 2023 04:21:25 +0100 Subject: [PATCH 377/824] nfc: Add mifare classic value block commands (#2317) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: gornekich Co-authored-by: あく --- firmware/targets/f7/api_symbols.csv | 11 +- lib/nfc/protocols/mifare_classic.c | 549 +++++++++++++++++++++++----- lib/nfc/protocols/mifare_classic.h | 37 ++ lib/nfc/protocols/nfc_util.c | 25 +- lib/nfc/protocols/nfc_util.h | 4 +- 5 files changed, 538 insertions(+), 88 deletions(-) diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 02f476cc04f..14036ef1a84 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1863,8 +1863,10 @@ Function,+,menu_reset,void,Menu* Function,+,menu_set_selected_item,void,"Menu*, uint32_t" Function,-,mf_classic_auth_attempt,_Bool,"FuriHalNfcTxRxContext*, MfClassicAuthContext*, uint64_t" Function,-,mf_classic_auth_init_context,void,"MfClassicAuthContext*, uint8_t" +Function,-,mf_classic_auth_write_block,_Bool,"FuriHalNfcTxRxContext*, MfClassicBlock*, uint8_t, MfClassicKey, uint64_t" Function,-,mf_classic_authenticate,_Bool,"FuriHalNfcTxRxContext*, uint8_t, uint64_t, MfClassicKey" Function,-,mf_classic_authenticate_skip_activate,_Bool,"FuriHalNfcTxRxContext*, uint8_t, uint64_t, MfClassicKey, _Bool, uint32_t" +Function,-,mf_classic_block_to_value,_Bool,"const uint8_t*, int32_t*, uint8_t*" Function,-,mf_classic_check_card_type,_Bool,"uint8_t, uint8_t, uint8_t" Function,-,mf_classic_dict_add_key,_Bool,"MfClassicDict*, uint8_t*" Function,-,mf_classic_dict_add_key_str,_Bool,"MfClassicDict*, FuriString*" @@ -1891,6 +1893,7 @@ Function,-,mf_classic_get_sector_trailer_by_sector,MfClassicSectorTrailer*,"MfCl Function,-,mf_classic_get_total_block_num,uint16_t,MfClassicType Function,-,mf_classic_get_total_sectors_num,uint8_t,MfClassicType Function,-,mf_classic_get_type_str,const char*,MfClassicType +Function,-,mf_classic_halt,void,"FuriHalNfcTxRxContext*, Crypto1*" Function,-,mf_classic_is_allowed_access_data_block,_Bool,"MfClassicData*, uint8_t, MfClassicKey, MfClassicAction" Function,-,mf_classic_is_allowed_access_sector_trailer,_Bool,"MfClassicData*, uint8_t, MfClassicKey, MfClassicAction" Function,-,mf_classic_is_block_read,_Bool,"MfClassicData*, uint8_t" @@ -1899,6 +1902,8 @@ Function,-,mf_classic_is_key_found,_Bool,"MfClassicData*, uint8_t, MfClassicKey" Function,-,mf_classic_is_sector_data_read,_Bool,"MfClassicData*, uint8_t" Function,-,mf_classic_is_sector_read,_Bool,"MfClassicData*, uint8_t" Function,-,mf_classic_is_sector_trailer,_Bool,uint8_t +Function,-,mf_classic_is_value_block,_Bool,"MfClassicData*, uint8_t" +Function,-,mf_classic_read_block,_Bool,"FuriHalNfcTxRxContext*, Crypto1*, uint8_t, MfClassicBlock*" Function,-,mf_classic_read_card,uint8_t,"FuriHalNfcTxRxContext*, MfClassicReader*, MfClassicData*" Function,-,mf_classic_read_sector,void,"FuriHalNfcTxRxContext*, MfClassicData*, uint8_t" Function,-,mf_classic_reader_add_sector,void,"MfClassicReader*, uint8_t, uint64_t, uint64_t" @@ -1906,8 +1911,12 @@ Function,-,mf_classic_set_block_read,void,"MfClassicData*, uint8_t, MfClassicBlo Function,-,mf_classic_set_key_found,void,"MfClassicData*, uint8_t, MfClassicKey, uint64_t" Function,-,mf_classic_set_key_not_found,void,"MfClassicData*, uint8_t, MfClassicKey" Function,-,mf_classic_set_sector_data_not_read,void,MfClassicData* +Function,-,mf_classic_transfer,_Bool,"FuriHalNfcTxRxContext*, Crypto1*, uint8_t" Function,-,mf_classic_update_card,uint8_t,"FuriHalNfcTxRxContext*, MfClassicData*" -Function,-,mf_classic_write_block,_Bool,"FuriHalNfcTxRxContext*, MfClassicBlock*, uint8_t, MfClassicKey, uint64_t" +Function,-,mf_classic_value_cmd,_Bool,"FuriHalNfcTxRxContext*, Crypto1*, uint8_t, uint8_t, int32_t" +Function,-,mf_classic_value_cmd_full,_Bool,"FuriHalNfcTxRxContext*, MfClassicBlock*, uint8_t, MfClassicKey, uint64_t, int32_t" +Function,-,mf_classic_value_to_block,void,"int32_t, uint8_t, uint8_t*" +Function,-,mf_classic_write_block,_Bool,"FuriHalNfcTxRxContext*, Crypto1*, uint8_t, MfClassicBlock*" Function,-,mf_classic_write_sector,_Bool,"FuriHalNfcTxRxContext*, MfClassicData*, MfClassicData*, uint8_t" Function,-,mf_df_cat_application,void,"MifareDesfireApplication*, FuriString*" Function,-,mf_df_cat_application_info,void,"MifareDesfireApplication*, FuriString*" diff --git a/lib/nfc/protocols/mifare_classic.c b/lib/nfc/protocols/mifare_classic.c index 5887ab4c185..28667f09bd4 100644 --- a/lib/nfc/protocols/mifare_classic.c +++ b/lib/nfc/protocols/mifare_classic.c @@ -7,10 +7,17 @@ #define TAG "MfClassic" -#define MF_CLASSIC_AUTH_KEY_A_CMD (0x60U) -#define MF_CLASSIC_AUTH_KEY_B_CMD (0x61U) -#define MF_CLASSIC_READ_BLOCK_CMD (0x30) -#define MF_CLASSIC_WRITE_BLOCK_CMD (0xA0) +#define MF_CLASSIC_ACK_CMD 0xAU +#define MF_CLASSIC_NACK_BUF_VALID_CMD 0x0U +#define MF_CLASSIC_NACK_BUF_INVALID_CMD 0x4U +#define MF_CLASSIC_AUTH_KEY_A_CMD 0x60U +#define MF_CLASSIC_AUTH_KEY_B_CMD 0x61U +#define MF_CLASSIC_READ_BLOCK_CMD 0x30U +#define MF_CLASSIC_WRITE_BLOCK_CMD 0xA0U +#define MF_CLASSIC_TRANSFER_CMD 0xB0U +#define MF_CLASSIC_DECREMENT_CMD 0xC0U +#define MF_CLASSIC_INCREMENT_CMD 0xC1U +#define MF_CLASSIC_RESTORE_CMD 0xC2U const char* mf_classic_get_type_str(MfClassicType type) { if(type == MfClassicTypeMini) { @@ -217,8 +224,8 @@ void mf_classic_get_read_sectors_and_keys( uint8_t first_block = mf_classic_get_first_block_num_of_sector(i); uint8_t total_blocks_in_sec = mf_classic_get_blocks_num_in_sector(i); bool blocks_read = true; - for(size_t i = first_block; i < first_block + total_blocks_in_sec; i++) { - blocks_read = mf_classic_is_block_read(data, i); + for(size_t j = first_block; j < first_block + total_blocks_in_sec; j++) { + blocks_read = mf_classic_is_block_read(data, j); if(!blocks_read) break; } if(blocks_read) { @@ -353,6 +360,16 @@ static bool mf_classic_is_allowed_access( } } +bool mf_classic_is_value_block(MfClassicData* data, uint8_t block_num) { + // Check if key A can write, if it can, it's transport configuration, not data block + return !mf_classic_is_allowed_access_data_block( + data, block_num, MfClassicKeyA, MfClassicActionDataWrite) && + (mf_classic_is_allowed_access_data_block( + data, block_num, MfClassicKeyB, MfClassicActionDataInc) || + mf_classic_is_allowed_access_data_block( + data, block_num, MfClassicKeyB, MfClassicActionDataDec)); +} + bool mf_classic_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK) { UNUSED(ATQA1); if((ATQA0 == 0x44 || ATQA0 == 0x04) && (SAK == 0x08 || SAK == 0x88 || SAK == 0x09)) { @@ -401,6 +418,36 @@ void mf_classic_reader_add_sector( } } +bool mf_classic_block_to_value(const uint8_t* block, int32_t* value, uint8_t* addr) { + uint32_t v = *(uint32_t*)&block[0]; + uint32_t v_inv = *(uint32_t*)&block[4]; + uint32_t v1 = *(uint32_t*)&block[8]; + + bool val_checks = + ((v == v1) && (v == ~v_inv) && (block[12] == (~block[13] & 0xFF)) && + (block[14] == (~block[15] & 0xFF)) && (block[12] == block[14])); + if(value) { + *value = (int32_t)v; + } + if(addr) { + *addr = block[12]; + } + return val_checks; +} + +void mf_classic_value_to_block(int32_t value, uint8_t addr, uint8_t* block) { + uint32_t v_inv = ~((uint32_t)value); + + memcpy(block, &value, 4); + memcpy(block + 4, &v_inv, 4); + memcpy(block + 8, &value, 4); + + block[12] = addr; + block[13] = ~addr & 0xFF; + block[14] = addr; + block[15] = ~addr & 0xFF; +} + void mf_classic_auth_init_context(MfClassicAuthContext* auth_ctx, uint8_t sector) { furi_assert(auth_ctx); auth_ctx->sector = sector; @@ -553,8 +600,9 @@ bool mf_classic_read_block( uint8_t plain_cmd[4] = {MF_CLASSIC_READ_BLOCK_CMD, block_num, 0x00, 0x00}; nfca_append_crc16(plain_cmd, 2); - crypto1_encrypt(crypto, NULL, plain_cmd, 4 * 8, tx_rx->tx_data, tx_rx->tx_parity); - tx_rx->tx_bits = 4 * 9; + crypto1_encrypt( + crypto, NULL, plain_cmd, sizeof(plain_cmd) * 8, tx_rx->tx_data, tx_rx->tx_parity); + tx_rx->tx_bits = sizeof(plain_cmd) * 8; tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRaw; if(furi_hal_nfc_tx_rx(tx_rx, 50)) { @@ -779,6 +827,9 @@ bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_ bool is_encrypted = false; uint8_t plain_data[MF_CLASSIC_MAX_DATA_SIZE]; MfClassicKey access_key = MfClassicKeyA; + // Used for decrement and increment - copy to block on transfer + uint8_t transfer_buf[MF_CLASSIC_BLOCK_SIZE] = {}; + bool transfer_buf_valid = false; // Read command while(!command_processed) { //-V654 @@ -797,18 +848,25 @@ bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_ crypto1_decrypt(&emulator->crypto, tx_rx->rx_data, tx_rx->rx_bits, plain_data); } - if(plain_data[0] == 0x50 && plain_data[1] == 0x00) { + // After increment, decrement or restore the only allowed command is transfer + uint8_t cmd = plain_data[0]; + if(transfer_buf_valid && cmd != MF_CLASSIC_TRANSFER_CMD) { + break; + } + + if(cmd == 0x50 && plain_data[1] == 0x00) { FURI_LOG_T(TAG, "Halt received"); furi_hal_nfc_listen_sleep(); command_processed = true; break; - } else if(plain_data[0] == 0x60 || plain_data[0] == 0x61) { + } + if(cmd == MF_CLASSIC_AUTH_KEY_A_CMD || cmd == MF_CLASSIC_AUTH_KEY_B_CMD) { uint8_t block = plain_data[1]; uint64_t key = 0; uint8_t sector_trailer_block = mf_classic_get_sector_trailer_num_by_block(block); MfClassicSectorTrailer* sector_trailer = (MfClassicSectorTrailer*)emulator->data.block[sector_trailer_block].value; - if(plain_data[0] == 0x60) { + if(cmd == MF_CLASSIC_AUTH_KEY_A_CMD) { key = nfc_util_bytes2num(sector_trailer->key_a, 6); access_key = MfClassicKeyA; } else { @@ -826,9 +884,7 @@ bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_ crypto1_word(&emulator->crypto, emulator->cuid ^ nonce, 0); memcpy(tx_rx->tx_data, nt, sizeof(nt)); tx_rx->tx_parity[0] = 0; - for(size_t i = 0; i < sizeof(nt); i++) { - tx_rx->tx_parity[0] |= nfc_util_odd_parity8(nt[i]) << (7 - i); - } + nfc_util_odd_parity(tx_rx->tx_data, tx_rx->tx_parity, sizeof(nt)); tx_rx->tx_bits = sizeof(nt) * 8; tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; } else { @@ -849,7 +905,7 @@ bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_ } if(tx_rx->rx_bits != 64) { - FURI_LOG_W(TAG, "Incorrect nr + ar"); + FURI_LOG_W(TAG, "Incorrect nr + ar length: %d", tx_rx->rx_bits); command_processed = true; break; } @@ -857,16 +913,6 @@ bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_ uint32_t nr = nfc_util_bytes2num(tx_rx->rx_data, 4); uint32_t ar = nfc_util_bytes2num(&tx_rx->rx_data[4], 4); - FURI_LOG_D( - TAG, - "%08lx key%c block %d nt/nr/ar: %08lx %08lx %08lx", - emulator->cuid, - access_key == MfClassicKeyA ? 'A' : 'B', - sector_trailer_block, - nonce, - nr, - ar); - crypto1_word(&emulator->crypto, nr, 1); uint32_t cardRr = ar ^ crypto1_word(&emulator->crypto, 0, 0); if(cardRr != prng_successor(nonce, 64)) { @@ -877,22 +923,30 @@ bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_ } uint32_t ans = prng_successor(nonce, 96); - uint8_t responce[4] = {}; - nfc_util_num2bytes(ans, 4, responce); + uint8_t response[4] = {}; + nfc_util_num2bytes(ans, 4, response); crypto1_encrypt( &emulator->crypto, NULL, - responce, - sizeof(responce) * 8, + response, + sizeof(response) * 8, tx_rx->tx_data, tx_rx->tx_parity); - tx_rx->tx_bits = sizeof(responce) * 8; + tx_rx->tx_bits = sizeof(response) * 8; tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; is_encrypted = true; - } else if(is_encrypted && plain_data[0] == 0x30) { + continue; + } + + if(!is_encrypted) { + FURI_LOG_T(TAG, "Invalid command before auth session established: %02X", cmd); + break; + } + + if(cmd == MF_CLASSIC_READ_BLOCK_CMD) { uint8_t block = plain_data[1]; - uint8_t block_data[18] = {}; + uint8_t block_data[MF_CLASSIC_BLOCK_SIZE + 2] = {}; 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( @@ -927,24 +981,24 @@ bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_ sizeof(block_data) * 8, tx_rx->tx_data, tx_rx->tx_parity); - tx_rx->tx_bits = 18 * 8; + tx_rx->tx_bits = (MF_CLASSIC_BLOCK_SIZE + 2) * 8; tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; - } else if(is_encrypted && plain_data[0] == 0xA0) { + } else if(cmd == MF_CLASSIC_WRITE_BLOCK_CMD) { uint8_t block = plain_data[1]; if(block > mf_classic_get_total_block_num(emulator->data.type)) { break; } // Send ACK - uint8_t ack = 0x0A; + uint8_t ack = MF_CLASSIC_ACK_CMD; 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; + if(tx_rx->rx_bits != (MF_CLASSIC_BLOCK_SIZE + 2) * 8) break; crypto1_decrypt(&emulator->crypto, tx_rx->rx_data, tx_rx->rx_bits, plain_data); - uint8_t block_data[16] = {}; + uint8_t block_data[MF_CLASSIC_BLOCK_SIZE] = {}; 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( @@ -963,6 +1017,8 @@ bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_ if(mf_classic_is_allowed_access( emulator, block, access_key, MfClassicActionDataWrite)) { memcpy(block_data, plain_data, MF_CLASSIC_BLOCK_SIZE); + } else { + break; } } if(memcmp(block_data, emulator->data.block[block].value, MF_CLASSIC_BLOCK_SIZE) != 0) { @@ -970,19 +1026,83 @@ bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_ emulator->data_changed = true; } // Send ACK - ack = 0x0A; + ack = MF_CLASSIC_ACK_CMD; + 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 if( + cmd == MF_CLASSIC_DECREMENT_CMD || cmd == MF_CLASSIC_INCREMENT_CMD || + cmd == MF_CLASSIC_RESTORE_CMD) { + uint8_t block = plain_data[1]; + + if(block > mf_classic_get_total_block_num(emulator->data.type)) { + break; + } + + MfClassicAction action = MfClassicActionDataDec; + if(cmd == MF_CLASSIC_INCREMENT_CMD) { + action = MfClassicActionDataInc; + } + if(!mf_classic_is_allowed_access(emulator, block, access_key, action)) { + break; + } + + int32_t prev_value; + uint8_t addr; + if(!mf_classic_block_to_value(emulator->data.block[block].value, &prev_value, &addr)) { + break; + } + + // Send ACK + uint8_t ack = MF_CLASSIC_ACK_CMD; + 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 != (sizeof(int32_t) + 2) * 8) break; + + crypto1_decrypt(&emulator->crypto, tx_rx->rx_data, tx_rx->rx_bits, plain_data); + int32_t value = *(int32_t*)&plain_data[0]; + if(value < 0) { + value = -value; + } + if(cmd == MF_CLASSIC_DECREMENT_CMD) { + value = -value; + } else if(cmd == MF_CLASSIC_RESTORE_CMD) { + value = 0; + } + + mf_classic_value_to_block(prev_value + value, addr, transfer_buf); + transfer_buf_valid = true; + // Commands do not ACK + tx_rx->tx_bits = 0; + } else if(cmd == MF_CLASSIC_TRANSFER_CMD) { + uint8_t block = plain_data[1]; + if(!mf_classic_is_allowed_access(emulator, block, access_key, MfClassicActionDataDec)) { + break; + } + if(memcmp(transfer_buf, emulator->data.block[block].value, MF_CLASSIC_BLOCK_SIZE) != + 0) { + memcpy(emulator->data.block[block].value, transfer_buf, MF_CLASSIC_BLOCK_SIZE); + emulator->data_changed = true; + } + transfer_buf_valid = false; + + uint8_t ack = MF_CLASSIC_ACK_CMD; 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 + FURI_LOG_T(TAG, "Unknown command: %02X", cmd); break; } } if(!command_processed) { // Send NACK - uint8_t nack = 0x04; + uint8_t nack = transfer_buf_valid ? MF_CLASSIC_NACK_BUF_VALID_CMD : + MF_CLASSIC_NACK_BUF_INVALID_CMD; if(is_encrypted) { crypto1_encrypt(&emulator->crypto, NULL, &nack, 4, tx_rx->tx_data, tx_rx->tx_parity); } else { @@ -996,37 +1116,50 @@ bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_ return true; } +void mf_classic_halt(FuriHalNfcTxRxContext* tx_rx, Crypto1* crypto) { + furi_assert(tx_rx); + + uint8_t plain_data[4] = {0x50, 0x00, 0x00, 0x00}; + + nfca_append_crc16(plain_data, 2); + if(crypto) { + crypto1_encrypt( + crypto, NULL, plain_data, sizeof(plain_data) * 8, tx_rx->tx_data, tx_rx->tx_parity); + } else { + memcpy(tx_rx->tx_data, plain_data, sizeof(plain_data)); + nfc_util_odd_parity(tx_rx->tx_data, tx_rx->tx_parity, sizeof(plain_data)); + } + + tx_rx->tx_bits = sizeof(plain_data) * 8; + tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRaw; + furi_hal_nfc_tx_rx(tx_rx, 50); +} + bool mf_classic_write_block( FuriHalNfcTxRxContext* tx_rx, - MfClassicBlock* src_block, + Crypto1* crypto, uint8_t block_num, - MfClassicKey key_type, - uint64_t key) { + MfClassicBlock* src_block) { furi_assert(tx_rx); + furi_assert(crypto); furi_assert(src_block); - Crypto1 crypto = {}; - uint8_t plain_data[18] = {}; - uint8_t resp = 0; bool write_success = false; + uint8_t plain_data[MF_CLASSIC_BLOCK_SIZE + 2] = {}; + uint8_t resp; do { - furi_hal_nfc_sleep(); - if(!mf_classic_auth(tx_rx, block_num, key, key_type, &crypto, false, 0)) { - FURI_LOG_D(TAG, "Auth fail"); - break; - } // Send write command plain_data[0] = MF_CLASSIC_WRITE_BLOCK_CMD; plain_data[1] = block_num; nfca_append_crc16(plain_data, 2); - crypto1_encrypt(&crypto, NULL, plain_data, 4 * 8, tx_rx->tx_data, tx_rx->tx_parity); + crypto1_encrypt(crypto, NULL, plain_data, 4 * 8, tx_rx->tx_data, tx_rx->tx_parity); tx_rx->tx_bits = 4 * 8; tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRaw; if(furi_hal_nfc_tx_rx(tx_rx, 50)) { if(tx_rx->rx_bits == 4) { - crypto1_decrypt(&crypto, tx_rx->rx_data, 4, &resp); + crypto1_decrypt(crypto, tx_rx->rx_data, 4, &resp); if(resp != 0x0A) { FURI_LOG_D(TAG, "NACK received on write cmd: %02X", resp); break; @@ -1044,7 +1177,7 @@ bool mf_classic_write_block( memcpy(plain_data, src_block->value, MF_CLASSIC_BLOCK_SIZE); nfca_append_crc16(plain_data, MF_CLASSIC_BLOCK_SIZE); crypto1_encrypt( - &crypto, + crypto, NULL, plain_data, (MF_CLASSIC_BLOCK_SIZE + 2) * 8, @@ -1054,8 +1187,8 @@ bool mf_classic_write_block( tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRaw; if(furi_hal_nfc_tx_rx(tx_rx, 50)) { if(tx_rx->rx_bits == 4) { - crypto1_decrypt(&crypto, tx_rx->rx_data, 4, &resp); - if(resp != 0x0A) { + crypto1_decrypt(crypto, tx_rx->rx_data, 4, &resp); + if(resp != MF_CLASSIC_ACK_CMD) { FURI_LOG_D(TAG, "NACK received on sending data"); break; } @@ -1067,20 +1200,198 @@ bool mf_classic_write_block( FURI_LOG_D(TAG, "Failed to send data"); break; } + write_success = true; + } while(false); - // Send Halt - plain_data[0] = 0x50; - plain_data[1] = 0x00; + return write_success; +} + +bool mf_classic_auth_write_block( + FuriHalNfcTxRxContext* tx_rx, + MfClassicBlock* src_block, + uint8_t block_num, + MfClassicKey key_type, + uint64_t key) { + furi_assert(tx_rx); + furi_assert(src_block); + + Crypto1 crypto = {}; + bool write_success = false; + + do { + furi_hal_nfc_sleep(); + if(!mf_classic_auth(tx_rx, block_num, key, key_type, &crypto, false, 0)) { + FURI_LOG_D(TAG, "Auth fail"); + break; + } + + if(!mf_classic_write_block(tx_rx, &crypto, block_num, src_block)) { + FURI_LOG_D(TAG, "Write fail"); + break; + } + write_success = true; + + mf_classic_halt(tx_rx, &crypto); + } while(false); + + return write_success; +} + +bool mf_classic_transfer(FuriHalNfcTxRxContext* tx_rx, Crypto1* crypto, uint8_t block_num) { + furi_assert(tx_rx); + furi_assert(crypto); + + // Send transfer command + uint8_t plain_data[4] = {MF_CLASSIC_TRANSFER_CMD, block_num, 0, 0}; + uint8_t resp = 0; + bool transfer_success = false; + + nfca_append_crc16(plain_data, 2); + crypto1_encrypt( + crypto, NULL, plain_data, sizeof(plain_data) * 8, tx_rx->tx_data, tx_rx->tx_parity); + tx_rx->tx_bits = sizeof(plain_data) * 8; + tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRaw; + + do { + if(furi_hal_nfc_tx_rx(tx_rx, 50)) { + if(tx_rx->rx_bits == 4) { + crypto1_decrypt(crypto, tx_rx->rx_data, 4, &resp); + if(resp != 0x0A) { + FURI_LOG_D(TAG, "NACK received on transfer cmd: %02X", resp); + break; + } + } else { + FURI_LOG_D(TAG, "Not ACK received"); + break; + } + } else { + FURI_LOG_D(TAG, "Failed to send transfer cmd"); + break; + } + + transfer_success = true; + } while(false); + + return transfer_success; +} + +bool mf_classic_value_cmd( + FuriHalNfcTxRxContext* tx_rx, + Crypto1* crypto, + uint8_t block_num, + uint8_t cmd, + int32_t d_value) { + furi_assert(tx_rx); + furi_assert(crypto); + furi_assert( + cmd == MF_CLASSIC_INCREMENT_CMD || cmd == MF_CLASSIC_DECREMENT_CMD || + cmd == MF_CLASSIC_RESTORE_CMD); + furi_assert(d_value >= 0); + + uint8_t plain_data[sizeof(d_value) + 2] = {}; + uint8_t resp = 0; + bool success = false; + + do { + // Send cmd + plain_data[0] = cmd; + plain_data[1] = block_num; nfca_append_crc16(plain_data, 2); - crypto1_encrypt(&crypto, NULL, plain_data, 2 * 8, tx_rx->tx_data, tx_rx->tx_parity); - tx_rx->tx_bits = 2 * 8; + crypto1_encrypt(crypto, NULL, plain_data, 4 * 8, tx_rx->tx_data, tx_rx->tx_parity); + tx_rx->tx_bits = 4 * 8; tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRaw; - // No response is expected - furi_hal_nfc_tx_rx(tx_rx, 50); + + if(furi_hal_nfc_tx_rx(tx_rx, 50)) { + if(tx_rx->rx_bits == 4) { + crypto1_decrypt(crypto, tx_rx->rx_data, 4, &resp); + if(resp != 0x0A) { + FURI_LOG_D(TAG, "NACK received on write cmd: %02X", resp); + break; + } + } else { + FURI_LOG_D(TAG, "Not ACK received"); + break; + } + } else { + FURI_LOG_D(TAG, "Failed to send write cmd"); + break; + } + + // Send data + memcpy(plain_data, &d_value, sizeof(d_value)); + nfca_append_crc16(plain_data, sizeof(d_value)); + crypto1_encrypt( + crypto, NULL, plain_data, (sizeof(d_value) + 2) * 8, tx_rx->tx_data, tx_rx->tx_parity); + tx_rx->tx_bits = (sizeof(d_value) + 2) * 8; + tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRaw; + // inc, dec, restore do not ACK, but they do NACK + if(furi_hal_nfc_tx_rx(tx_rx, 50)) { + if(tx_rx->rx_bits == 4) { + crypto1_decrypt(crypto, tx_rx->rx_data, 4, &resp); + if(resp != 0x0A) { + FURI_LOG_D(TAG, "NACK received on transfer cmd: %02X", resp); + break; + } + } else { + FURI_LOG_D(TAG, "Not NACK received"); + break; + } + } + + success = true; + } while(false); - return write_success; + return success; +} + +bool mf_classic_value_cmd_full( + FuriHalNfcTxRxContext* tx_rx, + MfClassicBlock* src_block, + uint8_t block_num, + MfClassicKey key_type, + uint64_t key, + int32_t d_value) { + furi_assert(tx_rx); + furi_assert(src_block); + + Crypto1 crypto = {}; + uint8_t cmd; + bool success = false; + + if(d_value > 0) { + cmd = MF_CLASSIC_INCREMENT_CMD; + } else if(d_value < 0) { + cmd = MF_CLASSIC_DECREMENT_CMD; + d_value = -d_value; + } else { + cmd = MF_CLASSIC_RESTORE_CMD; + } + + do { + furi_hal_nfc_sleep(); + if(!mf_classic_auth(tx_rx, block_num, key, key_type, &crypto, false, 0)) { + FURI_LOG_D(TAG, "Value cmd auth fail"); + break; + } + if(!mf_classic_value_cmd(tx_rx, &crypto, block_num, cmd, d_value)) { + FURI_LOG_D(TAG, "Value cmd inc/dec/res fail"); + break; + } + + if(!mf_classic_transfer(tx_rx, &crypto, block_num)) { + FURI_LOG_D(TAG, "Value cmd transfer fail"); + break; + } + + success = true; + + // Send Halt + mf_classic_halt(tx_rx, &crypto); + } while(false); + + return success; } bool mf_classic_write_sector( @@ -1103,31 +1414,99 @@ bool mf_classic_write_sector( // Compare blocks if(memcmp(dest_data->block[i].value, src_data->block[i].value, MF_CLASSIC_BLOCK_SIZE) != 0) { - bool key_a_write_allowed = mf_classic_is_allowed_access_data_block( - dest_data, i, MfClassicKeyA, MfClassicActionDataWrite); - bool key_b_write_allowed = mf_classic_is_allowed_access_data_block( - dest_data, i, MfClassicKeyB, MfClassicActionDataWrite); - - if(key_a_found && key_a_write_allowed) { - FURI_LOG_I(TAG, "Writing block %d with key A", i); - uint64_t key = nfc_util_bytes2num(sec_tr->key_a, 6); - if(!mf_classic_write_block(tx_rx, &src_data->block[i], i, MfClassicKeyA, key)) { - FURI_LOG_E(TAG, "Failed to write block %d", i); - write_success = false; - break; + if(mf_classic_is_value_block(dest_data, i)) { + bool key_a_inc_allowed = mf_classic_is_allowed_access_data_block( + dest_data, i, MfClassicKeyA, MfClassicActionDataInc); + bool key_b_inc_allowed = mf_classic_is_allowed_access_data_block( + dest_data, i, MfClassicKeyB, MfClassicActionDataInc); + bool key_a_dec_allowed = mf_classic_is_allowed_access_data_block( + dest_data, i, MfClassicKeyA, MfClassicActionDataDec); + bool key_b_dec_allowed = mf_classic_is_allowed_access_data_block( + dest_data, i, MfClassicKeyB, MfClassicActionDataDec); + + int32_t src_value, dst_value; + + mf_classic_block_to_value(src_data->block[i].value, &src_value, NULL); + mf_classic_block_to_value(dest_data->block[i].value, &dst_value, NULL); + + int32_t diff = src_value - dst_value; + + if(diff > 0) { + if(key_a_found && key_a_inc_allowed) { + FURI_LOG_I(TAG, "Incrementing block %d with key A by %ld", i, diff); + uint64_t key = nfc_util_bytes2num(sec_tr->key_a, 6); + if(!mf_classic_value_cmd_full( + tx_rx, &src_data->block[i], i, MfClassicKeyA, key, diff)) { + FURI_LOG_E(TAG, "Failed to increment block %d", i); + write_success = false; + break; + } + } else if(key_b_found && key_b_inc_allowed) { + FURI_LOG_I(TAG, "Incrementing block %d with key B by %ld", i, diff); + uint64_t key = nfc_util_bytes2num(sec_tr->key_b, 6); + if(!mf_classic_value_cmd_full( + tx_rx, &src_data->block[i], i, MfClassicKeyB, key, diff)) { + FURI_LOG_E(TAG, "Failed to increment block %d", i); + write_success = false; + break; + } + } else { + FURI_LOG_E(TAG, "Failed to increment block %d", i); + } + } else if(diff < 0) { + if(key_a_found && key_a_dec_allowed) { + FURI_LOG_I(TAG, "Decrementing block %d with key A by %ld", i, -diff); + uint64_t key = nfc_util_bytes2num(sec_tr->key_a, 6); + if(!mf_classic_value_cmd_full( + tx_rx, &src_data->block[i], i, MfClassicKeyA, key, diff)) { + FURI_LOG_E(TAG, "Failed to decrement block %d", i); + write_success = false; + break; + } + } else if(key_b_found && key_b_dec_allowed) { + FURI_LOG_I(TAG, "Decrementing block %d with key B by %ld", i, diff); + uint64_t key = nfc_util_bytes2num(sec_tr->key_b, 6); + if(!mf_classic_value_cmd_full( + tx_rx, &src_data->block[i], i, MfClassicKeyB, key, diff)) { + FURI_LOG_E(TAG, "Failed to decrement block %d", i); + write_success = false; + break; + } + } else { + FURI_LOG_E(TAG, "Failed to decrement block %d", i); + } + } else { + FURI_LOG_E(TAG, "Value block %d address changed, cannot write it", i); } - } else if(key_b_found && key_b_write_allowed) { - FURI_LOG_I(TAG, "Writing block %d with key A", i); - uint64_t key = nfc_util_bytes2num(sec_tr->key_b, 6); - if(!mf_classic_write_block(tx_rx, &src_data->block[i], i, MfClassicKeyB, key)) { - FURI_LOG_E(TAG, "Failed to write block %d", i); + } else { + bool key_a_write_allowed = mf_classic_is_allowed_access_data_block( + dest_data, i, MfClassicKeyA, MfClassicActionDataWrite); + bool key_b_write_allowed = mf_classic_is_allowed_access_data_block( + dest_data, i, MfClassicKeyB, MfClassicActionDataWrite); + + if(key_a_found && key_a_write_allowed) { + FURI_LOG_I(TAG, "Writing block %d with key A", i); + uint64_t key = nfc_util_bytes2num(sec_tr->key_a, 6); + if(!mf_classic_auth_write_block( + tx_rx, &src_data->block[i], i, MfClassicKeyA, key)) { + FURI_LOG_E(TAG, "Failed to write block %d", i); + write_success = false; + break; + } + } else if(key_b_found && key_b_write_allowed) { + FURI_LOG_I(TAG, "Writing block %d with key A", i); + uint64_t key = nfc_util_bytes2num(sec_tr->key_b, 6); + if(!mf_classic_auth_write_block( + tx_rx, &src_data->block[i], i, MfClassicKeyB, key)) { + FURI_LOG_E(TAG, "Failed to write block %d", i); + write_success = false; + break; + } + } else { + FURI_LOG_E(TAG, "Failed to find key with write access"); write_success = false; break; } - } else { - FURI_LOG_E(TAG, "Failed to find key with write access"); - write_success = false; - break; } } else { FURI_LOG_D(TAG, "Blocks %d are equal", i); diff --git a/lib/nfc/protocols/mifare_classic.h b/lib/nfc/protocols/mifare_classic.h index 321ab5f0360..a88781f9c8e 100644 --- a/lib/nfc/protocols/mifare_classic.h +++ b/lib/nfc/protocols/mifare_classic.h @@ -120,6 +120,12 @@ bool mf_classic_is_allowed_access_data_block( MfClassicKey key, MfClassicAction action); +bool mf_classic_is_value_block(MfClassicData* data, uint8_t block_num); + +bool mf_classic_block_to_value(const uint8_t* block, int32_t* value, uint8_t* addr); + +void mf_classic_value_to_block(int32_t value, uint8_t addr, uint8_t* block); + bool mf_classic_is_key_found(MfClassicData* data, uint8_t sector_num, MfClassicKey key_type); void mf_classic_set_key_found( @@ -177,6 +183,12 @@ void mf_classic_reader_add_sector( uint64_t key_a, uint64_t key_b); +bool mf_classic_read_block( + FuriHalNfcTxRxContext* tx_rx, + Crypto1* crypto, + uint8_t block_num, + MfClassicBlock* block); + void mf_classic_read_sector(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data, uint8_t sec_num); uint8_t mf_classic_read_card( @@ -188,13 +200,38 @@ uint8_t mf_classic_update_card(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_rx); +void mf_classic_halt(FuriHalNfcTxRxContext* tx_rx, Crypto1* crypto); + bool mf_classic_write_block( + FuriHalNfcTxRxContext* tx_rx, + Crypto1* crypto, + uint8_t block_num, + MfClassicBlock* src_block); + +bool mf_classic_auth_write_block( FuriHalNfcTxRxContext* tx_rx, MfClassicBlock* src_block, uint8_t block_num, MfClassicKey key_type, uint64_t key); +bool mf_classic_transfer(FuriHalNfcTxRxContext* tx_rx, Crypto1* crypto, uint8_t block_num); + +bool mf_classic_value_cmd( + FuriHalNfcTxRxContext* tx_rx, + Crypto1* crypto, + uint8_t block_num, + uint8_t cmd, + int32_t d_value); + +bool mf_classic_value_cmd_full( + FuriHalNfcTxRxContext* tx_rx, + MfClassicBlock* src_block, + uint8_t block_num, + MfClassicKey key_type, + uint64_t key, + int32_t d_value); + bool mf_classic_write_sector( FuriHalNfcTxRxContext* tx_rx, MfClassicData* dest_data, diff --git a/lib/nfc/protocols/nfc_util.c b/lib/nfc/protocols/nfc_util.c index 9de6f982b63..8cb6d57f218 100644 --- a/lib/nfc/protocols/nfc_util.c +++ b/lib/nfc/protocols/nfc_util.c @@ -23,7 +23,7 @@ void nfc_util_num2bytes(uint64_t src, uint8_t len, uint8_t* dest) { } } -uint64_t nfc_util_bytes2num(uint8_t* src, uint8_t len) { +uint64_t nfc_util_bytes2num(const uint8_t* src, uint8_t len) { furi_assert(src); furi_assert(len <= 8); @@ -45,3 +45,26 @@ uint8_t nfc_util_even_parity32(uint32_t data) { uint8_t nfc_util_odd_parity8(uint8_t data) { return nfc_util_odd_byte_parity[data]; } + +void nfc_util_odd_parity(const uint8_t* src, uint8_t* dst, uint8_t len) { + furi_assert(src); + furi_assert(dst); + + uint8_t parity = 0; + uint8_t bit = 0; + while(len--) { + parity |= nfc_util_odd_parity8(*src) << (7 - bit); // parity is MSB first + bit++; + if(bit == 8) { + *dst = parity; + dst++; + parity = 0; + bit = 0; + } + src++; + } + + if(bit) { + *dst = parity; + } +} \ No newline at end of file diff --git a/lib/nfc/protocols/nfc_util.h b/lib/nfc/protocols/nfc_util.h index d530badc3d7..04fa7622b93 100644 --- a/lib/nfc/protocols/nfc_util.h +++ b/lib/nfc/protocols/nfc_util.h @@ -4,8 +4,10 @@ void nfc_util_num2bytes(uint64_t src, uint8_t len, uint8_t* dest); -uint64_t nfc_util_bytes2num(uint8_t* src, uint8_t len); +uint64_t nfc_util_bytes2num(const uint8_t* src, uint8_t len); uint8_t nfc_util_even_parity32(uint32_t data); uint8_t nfc_util_odd_parity8(uint8_t data); + +void nfc_util_odd_parity(const uint8_t* src, uint8_t* dst, uint8_t len); From 1eda913367e3ab7a42344c42c5d8e7e0f9144d12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Wed, 8 Feb 2023 00:35:49 +0900 Subject: [PATCH 378/824] [FL-3075] Pin Reset (#2367) * Nfc: fix PVS warnings * Factory reset combo, initial version * Recovery screen and correct input pin initialization * Better pin and factory reset message * Down to cancel factory reset --- firmware/targets/f7/Inc/alt_boot.h | 2 + firmware/targets/f7/Src/main.c | 4 ++ firmware/targets/f7/Src/recovery.c | 54 +++++++++++++++++++ .../targets/f7/furi_hal/furi_hal_resources.c | 18 +++++-- lib/nfc/protocols/mifare_classic.c | 6 +-- 5 files changed, 76 insertions(+), 8 deletions(-) create mode 100644 firmware/targets/f7/Src/recovery.c diff --git a/firmware/targets/f7/Inc/alt_boot.h b/firmware/targets/f7/Inc/alt_boot.h index 91bf9bdf71d..d8be3aa487d 100644 --- a/firmware/targets/f7/Inc/alt_boot.h +++ b/firmware/targets/f7/Inc/alt_boot.h @@ -8,6 +8,8 @@ void flipper_boot_update_exec(); void flipper_boot_dfu_exec(); +void flipper_boot_recovery_exec(); + #ifdef __cplusplus } #endif diff --git a/firmware/targets/f7/Src/main.c b/firmware/targets/f7/Src/main.c index d9a2221a21f..1f2b5d6e42a 100644 --- a/firmware/targets/f7/Src/main.c +++ b/firmware/targets/f7/Src/main.c @@ -49,6 +49,10 @@ int main() { // But if we do, abandon to avoid bootloops furi_hal_rtc_set_boot_mode(FuriHalRtcBootModeNormal); furi_hal_power_reset(); + } else if(!furi_hal_gpio_read(&gpio_button_up)) { + furi_hal_light_sequence("rgb WR"); + flipper_boot_recovery_exec(); + furi_hal_power_reset(); } else { furi_hal_light_sequence("rgb G"); furi_thread_start(main_thread); diff --git a/firmware/targets/f7/Src/recovery.c b/firmware/targets/f7/Src/recovery.c new file mode 100644 index 00000000000..fa57482ca5b --- /dev/null +++ b/firmware/targets/f7/Src/recovery.c @@ -0,0 +1,54 @@ +#include +#include +#include +#include +#include +#include + +#define COUNTER_VALUE (100U) + +static void flipper_boot_recovery_draw_splash(u8g2_t* fb, size_t progress) { + u8g2_ClearBuffer(fb); + u8g2_SetDrawColor(fb, 0x01); + + u8g2_SetFont(fb, u8g2_font_helvB08_tr); + u8g2_DrawStr(fb, 2, 8, "PIN and Factory Reset"); + u8g2_SetFont(fb, u8g2_font_haxrcorp4089_tr); + u8g2_DrawStr(fb, 2, 21, "Hold Right to confirm"); + u8g2_DrawStr(fb, 2, 31, "Press Down to cancel"); + + if(progress < COUNTER_VALUE) { + size_t width = progress / (COUNTER_VALUE / 100); + u8g2_DrawBox(fb, 14 + (50 - width / 2), 54, width, 3); + } + + u8g2_SetPowerSave(fb, 0); + u8g2_SendBuffer(fb); +} + +void flipper_boot_recovery_exec() { + u8g2_t* fb = malloc(sizeof(u8g2_t)); + u8g2_Setup_st756x_flipper(fb, U8G2_R0, u8x8_hw_spi_stm32, u8g2_gpio_and_delay_stm32); + u8g2_InitDisplay(fb); + + size_t counter = COUNTER_VALUE; + while(counter) { + if(!furi_hal_gpio_read(&gpio_button_down)) { + break; + } + + if(!furi_hal_gpio_read(&gpio_button_right)) { + counter--; + } else { + counter = COUNTER_VALUE; + } + + flipper_boot_recovery_draw_splash(fb, counter); + } + + if(!counter) { + furi_hal_rtc_set_flag(FuriHalRtcFlagFactoryReset); + furi_hal_rtc_set_pin_fails(0); + furi_hal_rtc_reset_flag(FuriHalRtcFlagLock); + } +} \ No newline at end of file diff --git a/firmware/targets/f7/furi_hal/furi_hal_resources.c b/firmware/targets/f7/furi_hal/furi_hal_resources.c index 98ebedf51cf..4a32d70871c 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_resources.c +++ b/firmware/targets/f7/furi_hal/furi_hal_resources.c @@ -73,8 +73,18 @@ const InputPin input_pins[] = { const size_t input_pins_count = sizeof(input_pins) / sizeof(InputPin); +static void furi_hal_resources_init_input_pins(GpioMode mode) { + for(size_t i = 0; i < input_pins_count; i++) { + furi_hal_gpio_init( + input_pins[i].gpio, + mode, + (input_pins[i].inverted) ? GpioPullUp : GpioPullDown, + GpioSpeedLow); + } +} + void furi_hal_resources_init_early() { - furi_hal_gpio_init(&gpio_button_left, GpioModeInput, GpioPullUp, GpioSpeedLow); + furi_hal_resources_init_input_pins(GpioModeInput); // SD Card stepdown control furi_hal_gpio_write(&periph_power, 1); @@ -117,14 +127,12 @@ void furi_hal_resources_init_early() { } void furi_hal_resources_deinit_early() { + furi_hal_resources_init_input_pins(GpioModeAnalog); } void furi_hal_resources_init() { // Button pins - for(size_t i = 0; i < input_pins_count; i++) { - furi_hal_gpio_init( - input_pins[i].gpio, GpioModeInterruptRiseFall, GpioPullUp, GpioSpeedLow); - } + furi_hal_resources_init_input_pins(GpioModeInterruptRiseFall); // Display pins furi_hal_gpio_init(&gpio_display_rst_n, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); diff --git a/lib/nfc/protocols/mifare_classic.c b/lib/nfc/protocols/mifare_classic.c index 28667f09bd4..e4d5e0274bb 100644 --- a/lib/nfc/protocols/mifare_classic.c +++ b/lib/nfc/protocols/mifare_classic.c @@ -438,9 +438,9 @@ bool mf_classic_block_to_value(const uint8_t* block, int32_t* value, uint8_t* ad void mf_classic_value_to_block(int32_t value, uint8_t addr, uint8_t* block) { uint32_t v_inv = ~((uint32_t)value); - memcpy(block, &value, 4); - memcpy(block + 4, &v_inv, 4); - memcpy(block + 8, &value, 4); + memcpy(block, &value, 4); //-V1086 + memcpy(block + 4, &v_inv, 4); //-V1086 + memcpy(block + 8, &value, 4); //-V1086 block[12] = addr; block[13] = ~addr & 0xFF; From 224d0aefe41edd8356aa41aa6b39cbe7659c5d98 Mon Sep 17 00:00:00 2001 From: hedger Date: Tue, 7 Feb 2023 19:33:05 +0300 Subject: [PATCH 379/824] [FL-2733] multitarget support for fbt (#2209) * First part of multitarget porting * Delete firmware/targets/f7/Inc directory * Delete firmware/targets/f7/Src directory * gpio: cli fixes; about: using version from HAL * sdk: path fixes * gui: include fixes * applications: more include fixes * gpio: ported to new apis * hal: introduced furi_hal_target_hw.h; libs: added one_wire * hal: f18 target * github: also build f18 by default * typo fix * fbt: removed extra checks on app list * api: explicitly bundling select mlib headers with sdk * hal: f18: changed INPUT_DEBOUNCE_TICKS to match f7 * cleaned up commented out code * docs: added info on hw targets * docs: targets: formatting fixes * f18: fixed link error * f18: fixed API version to match f7 * docs: hardware: minor wording fixes * faploader: added fw target check * docs: typo fixes * github: not building komi target by default * fbt: support for `targets` field for built-in apps * github: reworked build flow to exclude app_set; fbt: removed komi-specific appset; added additional target buildset check * github: fixed build; nfc: fixed pvs warnings * attempt to fix target id * f7, f18: removed certain HAL function from public API * apps: debug: enabled bt_debug_app for f18 * Targets: backport input pins configuration routine from F7 to F18 Co-authored-by: Aleksandr Kutuzov --- .github/workflows/build.yml | 11 +- .github/workflows/pvs_studio.yml | 2 +- .vscode/extensions.json | 7 +- applications/debug/accessor/application.fam | 1 + .../bt_debug_app/views/bt_carrier_test.c | 2 +- .../debug/bt_debug_app/views/bt_packet_test.c | 2 +- .../file_browser_test/file_browser_app.c | 9 +- .../debug/lfrfid_debug/application.fam | 1 + .../main/archive/helpers/archive_browser.c | 5 +- .../main/archive/views/archive_browser_view.h | 9 +- .../scenes/bad_usb_scene_file_select.c | 4 +- .../main/bad_usb/scenes/bad_usb_scene_work.c | 2 +- applications/main/gpio/gpio_app.c | 4 +- applications/main/gpio/gpio_app_i.h | 3 +- applications/main/gpio/gpio_item.c | 51 - applications/main/gpio/gpio_item.h | 15 - applications/main/gpio/gpio_items.c | 69 + applications/main/gpio/gpio_items.h | 29 + .../main/gpio/scenes/gpio_scene_start.c | 4 +- .../main/gpio/scenes/gpio_scene_test.c | 8 +- .../gpio/scenes/gpio_scene_usb_uart_config.c | 2 +- applications/main/gpio/usb_uart_bridge.c | 8 +- applications/main/gpio/views/gpio_test.c | 30 +- applications/main/gpio/views/gpio_test.h | 4 +- applications/main/gpio/views/gpio_usb_uart.c | 2 +- applications/main/ibutton/application.fam | 1 + applications/main/infrared/application.fam | 1 + .../main/infrared/scenes/infrared_scene_rpc.c | 2 +- .../main/infrared/views/infrared_debug_view.c | 6 +- .../infrared/views/infrared_progress_view.c | 18 +- applications/main/lfrfid/application.fam | 1 + applications/main/nfc/application.fam | 1 + applications/main/nfc/nfc.c | 2 +- applications/main/subghz/application.fam | 1 + applications/main/u2f/scenes/u2f_scene_main.c | 2 +- .../music_player/music_player_worker.c | 1 + .../plugins/nfc_magic/application.fam | 1 + applications/plugins/picopass/application.fam | 1 + .../signal_generator/signal_gen_app_i.h | 4 +- .../signal_generator/views/signal_gen_pwm.c | 2 +- .../plugins/weather_station/application.fam | 1 + .../weather_station/protocols/ws_generic.h | 2 +- applications/services/cli/cli_command_gpio.c | 66 +- applications/services/dialogs/dialogs_api.c | 2 +- .../dialogs/dialogs_module_file_browser.c | 3 +- applications/services/dolphin/dolphin.h | 5 +- applications/services/gui/elements.c | 7 +- applications/services/gui/gui.c | 1 - .../services/gui/modules/button_menu.c | 13 +- .../services/gui/modules/button_panel.c | 13 +- .../services/gui/modules/byte_input.c | 5 +- .../services/gui/modules/file_browser.c | 13 +- .../gui/modules/file_browser_worker.c | 11 +- applications/services/gui/modules/loading.c | 9 +- applications/services/gui/modules/menu.c | 2 +- applications/services/gui/modules/submenu.c | 2 +- applications/services/gui/modules/text_box.c | 4 +- .../services/gui/modules/validators.c | 2 +- .../services/gui/modules/validators.h | 1 + .../services/gui/modules/variable_item_list.c | 6 +- applications/services/gui/modules/widget.c | 4 +- applications/services/gui/view_stack.c | 5 +- .../services/notification/notification_app.c | 2 +- .../notification/notification_messages.c | 2 +- applications/settings/about/about.c | 26 +- .../bt_settings_scene_forget_dev_confirm.c | 2 +- .../bt_settings_scene_forget_dev_success.c | 2 +- .../scenes/bt_settings_scene_start.c | 2 +- .../scenes/storage_move_to_sd_scene_confirm.c | 6 +- .../storage_move_to_sd/storage_move_to_sd.h | 7 +- applications/system/updater/cli/updater_cli.c | 2 +- .../system/updater/util/update_task.c | 4 +- documentation/HardwareTargets.md | 44 + firmware.scons | 57 +- firmware/SConscript | 17 +- firmware/targets/f18/api_symbols.csv | 2307 +++++++++++++++++ firmware/targets/f18/furi_hal/furi_hal.c | 91 + .../targets/f18/furi_hal/furi_hal_resources.c | 201 ++ .../targets/f18/furi_hal/furi_hal_resources.h | 116 + .../f18/furi_hal/furi_hal_spi_config.c | 377 +++ .../f18/furi_hal/furi_hal_spi_config.h | 55 + .../targets/f18/furi_hal/furi_hal_target_hw.h | 1 + .../f18/furi_hal/furi_hal_version_device.c | 21 + firmware/targets/f18/target.json | 55 + firmware/targets/f7/api_symbols.csv | 41 +- firmware/targets/f7/furi_hal/furi_hal.c | 6 +- .../targets/f7/furi_hal/furi_hal_bt_hid.c | 4 +- .../targets/f7/furi_hal/furi_hal_bt_serial.c | 2 +- .../targets/f7/furi_hal/furi_hal_cortex.c | 2 +- .../targets/f7/furi_hal/furi_hal_i2c_config.c | 2 +- .../furi_hal}/furi_hal_ibutton.h | 2 +- .../targets/f7/furi_hal/furi_hal_infrared.c | 2 +- .../targets/f7/furi_hal/furi_hal_interrupt.c | 4 +- firmware/targets/f7/furi_hal/furi_hal_light.c | 2 +- firmware/targets/f7/furi_hal/furi_hal_nfc.c | 2 +- .../furi_hal}/furi_hal_nfc.h | 0 firmware/targets/f7/furi_hal/furi_hal_pwm.c | 2 +- .../targets/f7/furi_hal/furi_hal_random.c | 2 +- .../targets/f7/furi_hal/furi_hal_resources.c | 17 + .../targets/f7/furi_hal/furi_hal_resources.h | 9 + .../furi_hal}/furi_hal_rfid.h | 0 firmware/targets/f7/furi_hal/furi_hal_sd.c | 13 +- firmware/targets/f7/furi_hal/furi_hal_spi.c | 28 +- .../targets/f7/furi_hal/furi_hal_spi_config.c | 25 + .../targets/f7/furi_hal/furi_hal_subghz.c | 4 +- .../furi_hal}/furi_hal_subghz.h | 0 .../targets/f7/furi_hal/furi_hal_target_hw.h | 6 + firmware/targets/f7/furi_hal/furi_hal_usb.c | 6 +- .../targets/f7/furi_hal/furi_hal_usb_cdc.c | 8 +- .../targets/f7/furi_hal/furi_hal_usb_hid.c | 8 +- .../targets/f7/furi_hal/furi_hal_usb_u2f.c | 8 +- .../targets/f7/furi_hal/furi_hal_version.c | 8 - .../f7/furi_hal/furi_hal_version_device.c | 21 + .../targets/f7/{Inc => inc}/FreeRTOSConfig.h | 0 firmware/targets/f7/{Inc => inc}/alt_boot.h | 0 firmware/targets/f7/{Inc => inc}/stm32.h | 0 .../targets/f7/{Inc => inc}/stm32_assert.h | 0 firmware/targets/f7/{Src => src}/dfu.c | 0 firmware/targets/f7/{Src => src}/main.c | 0 firmware/targets/f7/{Src => src}/recovery.c | 2 +- .../f7/{Src => src}/system_stm32wbxx.c | 0 firmware/targets/f7/{Src => src}/update.c | 2 +- firmware/targets/f7/stm32wb55xx_flash.ld | 2 +- firmware/targets/f7/stm32wb55xx_ram_fw.ld | 2 +- firmware/targets/f7/target.json | 45 + firmware/targets/furi_hal_include/furi_hal.h | 59 +- .../targets/furi_hal_include/furi_hal_bt.h | 2 +- .../targets/furi_hal_include/furi_hal_spi.h | 6 +- .../furi_hal_include/furi_hal_version.h | 18 + lib/SConscript | 34 +- .../view_modules/popup_vm.cpp | 3 +- .../application_manifest.c | 7 + .../application_manifest.h | 8 + lib/flipper_application/flipper_application.c | 4 + lib/misc.scons | 1 - lib/nfc/parsers/all_in_one.c | 2 +- lib/nfc/parsers/plantain_4k_parser.c | 2 +- lib/nfc/parsers/plantain_parser.c | 2 +- lib/nfc/parsers/two_cities.c | 2 +- lib/nfc/protocols/mifare_ultralight.c | 2 +- lib/one_wire/SConscript | 27 + lib/subghz/blocks/generic.h | 4 +- lib/subghz/protocols/princeton_for_testing.c | 2 +- lib/subghz/subghz_setting.c | 2 +- lib/update_util/update_operation.c | 7 +- scripts/fbt/appmanifest.py | 74 +- scripts/fbt_tools/fbt_apps.py | 11 +- scripts/fbt_tools/fbt_hwtarget.py | 134 + scripts/fbt_tools/sconsmodular.py | 2 + site_scons/commandline.scons | 3 +- site_scons/extapps.scons | 17 +- site_scons/firmwareopts.scons | 9 +- 152 files changed, 4139 insertions(+), 494 deletions(-) delete mode 100644 applications/main/gpio/gpio_item.c delete mode 100644 applications/main/gpio/gpio_item.h create mode 100644 applications/main/gpio/gpio_items.c create mode 100644 applications/main/gpio/gpio_items.h create mode 100644 documentation/HardwareTargets.md create mode 100644 firmware/targets/f18/api_symbols.csv create mode 100644 firmware/targets/f18/furi_hal/furi_hal.c create mode 100644 firmware/targets/f18/furi_hal/furi_hal_resources.c create mode 100644 firmware/targets/f18/furi_hal/furi_hal_resources.h create mode 100644 firmware/targets/f18/furi_hal/furi_hal_spi_config.c create mode 100644 firmware/targets/f18/furi_hal/furi_hal_spi_config.h create mode 100644 firmware/targets/f18/furi_hal/furi_hal_target_hw.h create mode 100644 firmware/targets/f18/furi_hal/furi_hal_version_device.c create mode 100644 firmware/targets/f18/target.json rename firmware/targets/{furi_hal_include => f7/furi_hal}/furi_hal_ibutton.h (98%) rename firmware/targets/{furi_hal_include => f7/furi_hal}/furi_hal_nfc.h (100%) rename firmware/targets/{furi_hal_include => f7/furi_hal}/furi_hal_rfid.h (100%) rename firmware/targets/{furi_hal_include => f7/furi_hal}/furi_hal_subghz.h (100%) create mode 100644 firmware/targets/f7/furi_hal/furi_hal_target_hw.h create mode 100644 firmware/targets/f7/furi_hal/furi_hal_version_device.c rename firmware/targets/f7/{Inc => inc}/FreeRTOSConfig.h (100%) rename firmware/targets/f7/{Inc => inc}/alt_boot.h (100%) rename firmware/targets/f7/{Inc => inc}/stm32.h (100%) rename firmware/targets/f7/{Inc => inc}/stm32_assert.h (100%) rename firmware/targets/f7/{Src => src}/dfu.c (100%) rename firmware/targets/f7/{Src => src}/main.c (100%) rename firmware/targets/f7/{Src => src}/recovery.c (99%) rename firmware/targets/f7/{Src => src}/system_stm32wbxx.c (100%) rename firmware/targets/f7/{Src => src}/update.c (99%) create mode 100644 firmware/targets/f7/target.json create mode 100644 lib/one_wire/SConscript create mode 100644 scripts/fbt_tools/fbt_hwtarget.py diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4f7233e4663..080e7c2c220 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,7 +10,7 @@ on: pull_request: env: - TARGETS: f7 + TARGETS: f7 f18 DEFAULT_TARGET: f7 FBT_TOOLCHAIN_PATH: /runner/_work @@ -60,8 +60,9 @@ jobs: run: | set -e for TARGET in ${TARGETS}; do - ./fbt TARGET_HW="$(echo "${TARGET}" | sed 's/f//')" \ - copro_dist updater_package ${{ startsWith(github.ref, 'refs/tags') && 'DEBUG=0 COMPACT=1' || '' }} + TARGET="$(echo "${TARGET}" | sed 's/f//')"; \ + ./fbt TARGET_HW=$TARGET copro_dist updater_package \ + ${{ startsWith(github.ref, 'refs/tags') && 'DEBUG=0 COMPACT=1' || '' }} done - name: 'Move upload files' @@ -186,6 +187,6 @@ jobs: run: | set -e for TARGET in ${TARGETS}; do - ./fbt TARGET_HW="$(echo "${TARGET}" | sed 's/f//')" \ - updater_package DEBUG=0 COMPACT=1 + TARGET="$(echo "${TARGET}" | sed 's/f//')"; \ + ./fbt TARGET_HW=$TARGET DEBUG=0 COMPACT=1 updater_package done diff --git a/.github/workflows/pvs_studio.yml b/.github/workflows/pvs_studio.yml index 50f8f0aa61a..a4ac6e30127 100644 --- a/.github/workflows/pvs_studio.yml +++ b/.github/workflows/pvs_studio.yml @@ -89,6 +89,6 @@ jobs: - name: 'Raise exception' if: ${{ steps.pvs-warn.outputs.warnings != 0 }} run: | - echo "Please fix all PVS varnings before merge" + echo "Please fix all PVS warnings before merge" exit 1 diff --git a/.vscode/extensions.json b/.vscode/extensions.json index b53ffc24cfc..b5791a91e12 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -11,5 +11,8 @@ "augustocdias.tasks-shell-input" ], // List of extensions recommended by VS Code that should not be recommended for users of this workspace. - "unwantedRecommendations": [] -} \ No newline at end of file + "unwantedRecommendations": [ + "twxs.cmake", + "ms-vscode.cmake-tools" + ] +} diff --git a/applications/debug/accessor/application.fam b/applications/debug/accessor/application.fam index 93fc9bc3f86..6b84727112b 100644 --- a/applications/debug/accessor/application.fam +++ b/applications/debug/accessor/application.fam @@ -2,6 +2,7 @@ App( appid="accessor", name="Accessor", apptype=FlipperAppType.DEBUG, + targets=["f7"], entry_point="accessor_app", cdefines=["APP_ACCESSOR"], requires=["gui"], diff --git a/applications/debug/bt_debug_app/views/bt_carrier_test.c b/applications/debug/bt_debug_app/views/bt_carrier_test.c index c09aa3fdfaa..8e224049574 100644 --- a/applications/debug/bt_debug_app/views/bt_carrier_test.c +++ b/applications/debug/bt_debug_app/views/bt_carrier_test.c @@ -1,7 +1,7 @@ #include "bt_carrier_test.h" #include "bt_test.h" #include "bt_test_types.h" -#include "furi_hal_bt.h" +#include struct BtCarrierTest { BtTest* bt_test; diff --git a/applications/debug/bt_debug_app/views/bt_packet_test.c b/applications/debug/bt_debug_app/views/bt_packet_test.c index 7cbc3c5c537..8a56a30031a 100644 --- a/applications/debug/bt_debug_app/views/bt_packet_test.c +++ b/applications/debug/bt_debug_app/views/bt_packet_test.c @@ -1,7 +1,7 @@ #include "bt_packet_test.h" #include "bt_test.h" #include "bt_test_types.h" -#include "furi_hal_bt.h" +#include struct BtPacketTest { BtTest* bt_test; diff --git a/applications/debug/file_browser_test/file_browser_app.c b/applications/debug/file_browser_test/file_browser_app.c index bf423d34edd..c3e7c898bf0 100644 --- a/applications/debug/file_browser_test/file_browser_app.c +++ b/applications/debug/file_browser_test/file_browser_app.c @@ -1,10 +1,11 @@ -#include #include "file_browser_app_i.h" -#include "gui/modules/file_browser.h" -#include -#include +#include + +#include #include #include +#include +#include static bool file_browser_app_custom_event_callback(void* context, uint32_t event) { furi_assert(context); diff --git a/applications/debug/lfrfid_debug/application.fam b/applications/debug/lfrfid_debug/application.fam index 6844f9291be..323f77818fc 100644 --- a/applications/debug/lfrfid_debug/application.fam +++ b/applications/debug/lfrfid_debug/application.fam @@ -2,6 +2,7 @@ App( appid="lfrfid_debug", name="LF-RFID Debug", apptype=FlipperAppType.DEBUG, + targets=["f7"], entry_point="lfrfid_debug_app", requires=[ "gui", diff --git a/applications/main/archive/helpers/archive_browser.c b/applications/main/archive/helpers/archive_browser.c index 6c8c9633a82..1133f6d7168 100644 --- a/applications/main/archive/helpers/archive_browser.c +++ b/applications/main/archive/helpers/archive_browser.c @@ -1,10 +1,11 @@ -#include #include "archive_files.h" #include "archive_apps.h" #include "archive_browser.h" +#include "../views/archive_browser_view.h" + #include #include -#include "gui/modules/file_browser_worker.h" +#include #include #include diff --git a/applications/main/archive/views/archive_browser_view.h b/applications/main/archive/views/archive_browser_view.h index 915b5307a70..0a000e5ac34 100644 --- a/applications/main/archive/views/archive_browser_view.h +++ b/applications/main/archive/views/archive_browser_view.h @@ -1,14 +1,15 @@ #pragma once +#include "../helpers/archive_files.h" +#include "../helpers/archive_favorites.h" + #include #include #include #include -#include +#include #include -#include "../helpers/archive_files.h" -#include "../helpers/archive_favorites.h" -#include "gui/modules/file_browser_worker.h" +#include #define MAX_LEN_PX 110 #define MAX_NAME_LEN 255 diff --git a/applications/main/bad_usb/scenes/bad_usb_scene_file_select.c b/applications/main/bad_usb/scenes/bad_usb_scene_file_select.c index 9264eb97686..f1f34f5bc26 100644 --- a/applications/main/bad_usb/scenes/bad_usb_scene_file_select.c +++ b/applications/main/bad_usb/scenes/bad_usb_scene_file_select.c @@ -1,6 +1,6 @@ #include "../bad_usb_app_i.h" -#include "furi_hal_power.h" -#include "furi_hal_usb.h" +#include +#include #include static bool bad_usb_file_select(BadUsbApp* bad_usb) { diff --git a/applications/main/bad_usb/scenes/bad_usb_scene_work.c b/applications/main/bad_usb/scenes/bad_usb_scene_work.c index 689f3b4ea9c..1e35348225d 100644 --- a/applications/main/bad_usb/scenes/bad_usb_scene_work.c +++ b/applications/main/bad_usb/scenes/bad_usb_scene_work.c @@ -1,7 +1,7 @@ #include "../bad_usb_script.h" #include "../bad_usb_app_i.h" #include "../views/bad_usb_view.h" -#include "furi_hal.h" +#include #include "toolbox/path.h" void bad_usb_scene_work_ok_callback(InputType type, void* context) { diff --git a/applications/main/gpio/gpio_app.c b/applications/main/gpio/gpio_app.c index b8afdc8ea1e..1ecff1ec2ed 100644 --- a/applications/main/gpio/gpio_app.c +++ b/applications/main/gpio/gpio_app.c @@ -25,6 +25,7 @@ GpioApp* gpio_app_alloc() { GpioApp* app = malloc(sizeof(GpioApp)); app->gui = furi_record_open(RECORD_GUI); + app->gpio_items = gpio_items_alloc(); app->view_dispatcher = view_dispatcher_alloc(); app->scene_manager = scene_manager_alloc(&gpio_scene_handlers, app); @@ -47,7 +48,7 @@ GpioApp* gpio_app_alloc() { app->view_dispatcher, GpioAppViewVarItemList, variable_item_list_get_view(app->var_item_list)); - app->gpio_test = gpio_test_alloc(); + app->gpio_test = gpio_test_alloc(app->gpio_items); view_dispatcher_add_view( app->view_dispatcher, GpioAppViewGpioTest, gpio_test_get_view(app->gpio_test)); @@ -91,6 +92,7 @@ void gpio_app_free(GpioApp* app) { furi_record_close(RECORD_GUI); furi_record_close(RECORD_NOTIFICATION); + gpio_items_free(app->gpio_items); free(app); } diff --git a/applications/main/gpio/gpio_app_i.h b/applications/main/gpio/gpio_app_i.h index 8f805891bbc..03fe9f48949 100644 --- a/applications/main/gpio/gpio_app_i.h +++ b/applications/main/gpio/gpio_app_i.h @@ -1,7 +1,7 @@ #pragma once #include "gpio_app.h" -#include "gpio_item.h" +#include "gpio_items.h" #include "scenes/gpio_scene.h" #include "gpio_custom_event.h" #include "usb_uart_bridge.h" @@ -28,6 +28,7 @@ struct GpioApp { VariableItem* var_item_flow; GpioTest* gpio_test; GpioUsbUart* gpio_usb_uart; + GPIOItems* gpio_items; UsbUartBridge* usb_uart_bridge; UsbUartConfig* usb_uart_cfg; }; diff --git a/applications/main/gpio/gpio_item.c b/applications/main/gpio/gpio_item.c deleted file mode 100644 index 2d0f5f6762b..00000000000 --- a/applications/main/gpio/gpio_item.c +++ /dev/null @@ -1,51 +0,0 @@ -#include "gpio_item.h" - -#include - -typedef struct { - const char* name; - const GpioPin* pin; -} GpioItem; - -static const GpioItem gpio_item[GPIO_ITEM_COUNT] = { - {"1.2: PA7", &gpio_ext_pa7}, - {"1.3: PA6", &gpio_ext_pa6}, - {"1.4: PA4", &gpio_ext_pa4}, - {"1.5: PB3", &gpio_ext_pb3}, - {"1.6: PB2", &gpio_ext_pb2}, - {"1.7: PC3", &gpio_ext_pc3}, - {"2.7: PC1", &gpio_ext_pc1}, - {"2.8: PC0", &gpio_ext_pc0}, -}; - -void gpio_item_configure_pin(uint8_t index, GpioMode mode) { - furi_assert(index < GPIO_ITEM_COUNT); - furi_hal_gpio_write(gpio_item[index].pin, false); - furi_hal_gpio_init(gpio_item[index].pin, mode, GpioPullNo, GpioSpeedVeryHigh); -} - -void gpio_item_configure_all_pins(GpioMode mode) { - for(uint8_t i = 0; i < GPIO_ITEM_COUNT; i++) { - gpio_item_configure_pin(i, mode); - } -} - -void gpio_item_set_pin(uint8_t index, bool level) { - furi_assert(index < GPIO_ITEM_COUNT); - furi_hal_gpio_write(gpio_item[index].pin, level); -} - -void gpio_item_set_all_pins(bool level) { - for(uint8_t i = 0; i < GPIO_ITEM_COUNT; i++) { - gpio_item_set_pin(i, level); - } -} - -const char* gpio_item_get_pin_name(uint8_t index) { - furi_assert(index < GPIO_ITEM_COUNT + 1); - if(index == GPIO_ITEM_COUNT) { - return "ALL"; - } else { - return gpio_item[index].name; - } -} diff --git a/applications/main/gpio/gpio_item.h b/applications/main/gpio/gpio_item.h deleted file mode 100644 index 5cb2b86c1ba..00000000000 --- a/applications/main/gpio/gpio_item.h +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once - -#include - -#define GPIO_ITEM_COUNT 8 - -void gpio_item_configure_pin(uint8_t index, GpioMode mode); - -void gpio_item_configure_all_pins(GpioMode mode); - -void gpio_item_set_pin(uint8_t index, bool level); - -void gpio_item_set_all_pins(bool level); - -const char* gpio_item_get_pin_name(uint8_t index); diff --git a/applications/main/gpio/gpio_items.c b/applications/main/gpio/gpio_items.c new file mode 100644 index 00000000000..02f7d95b0e6 --- /dev/null +++ b/applications/main/gpio/gpio_items.c @@ -0,0 +1,69 @@ +#include "gpio_items.h" + +#include + +struct GPIOItems { + GpioPinRecord* pins; + size_t count; +}; + +GPIOItems* gpio_items_alloc() { + GPIOItems* items = malloc(sizeof(GPIOItems)); + + items->count = 0; + for(size_t i = 0; i < gpio_pins_count; i++) { + if(!gpio_pins[i].debug) { + items->count++; + } + } + + items->pins = malloc(sizeof(GpioPinRecord) * items->count); + for(size_t i = 0; i < items->count; i++) { + if(!gpio_pins[i].debug) { + items->pins[i].pin = gpio_pins[i].pin; + items->pins[i].name = gpio_pins[i].name; + } + } + return items; +} + +void gpio_items_free(GPIOItems* items) { + free(items->pins); + free(items); +} + +uint8_t gpio_items_get_count(GPIOItems* items) { + return items->count; +} + +void gpio_items_configure_pin(GPIOItems* items, uint8_t index, GpioMode mode) { + furi_assert(index < items->count); + furi_hal_gpio_write(items->pins[index].pin, false); + furi_hal_gpio_init(items->pins[index].pin, mode, GpioPullNo, GpioSpeedVeryHigh); +} + +void gpio_items_configure_all_pins(GPIOItems* items, GpioMode mode) { + for(uint8_t i = 0; i < items->count; i++) { + gpio_items_configure_pin(items, i, mode); + } +} + +void gpio_items_set_pin(GPIOItems* items, uint8_t index, bool level) { + furi_assert(index < items->count); + furi_hal_gpio_write(items->pins[index].pin, level); +} + +void gpio_items_set_all_pins(GPIOItems* items, bool level) { + for(uint8_t i = 0; i < items->count; i++) { + gpio_items_set_pin(items, i, level); + } +} + +const char* gpio_items_get_pin_name(GPIOItems* items, uint8_t index) { + furi_assert(index < items->count + 1); + if(index == items->count) { + return "ALL"; + } else { + return items->pins[index].name; + } +} diff --git a/applications/main/gpio/gpio_items.h b/applications/main/gpio/gpio_items.h new file mode 100644 index 00000000000..68afbe6934e --- /dev/null +++ b/applications/main/gpio/gpio_items.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct GPIOItems GPIOItems; + +GPIOItems* gpio_items_alloc(); + +void gpio_items_free(GPIOItems* items); + +uint8_t gpio_items_get_count(GPIOItems* items); + +void gpio_items_configure_pin(GPIOItems* items, uint8_t index, GpioMode mode); + +void gpio_items_configure_all_pins(GPIOItems* items, GpioMode mode); + +void gpio_items_set_pin(GPIOItems* items, uint8_t index, bool level); + +void gpio_items_set_all_pins(GPIOItems* items, bool level); + +const char* gpio_items_get_pin_name(GPIOItems* items, uint8_t index); + +#ifdef __cplusplus +} +#endif diff --git a/applications/main/gpio/scenes/gpio_scene_start.c b/applications/main/gpio/scenes/gpio_scene_start.c index 72992294981..0272677934c 100644 --- a/applications/main/gpio/scenes/gpio_scene_start.c +++ b/applications/main/gpio/scenes/gpio_scene_start.c @@ -1,6 +1,6 @@ #include "../gpio_app_i.h" -#include "furi_hal_power.h" -#include "furi_hal_usb.h" +#include +#include #include enum GpioItem { diff --git a/applications/main/gpio/scenes/gpio_scene_test.c b/applications/main/gpio/scenes/gpio_scene_test.c index b015d80902e..9940b95d459 100644 --- a/applications/main/gpio/scenes/gpio_scene_test.c +++ b/applications/main/gpio/scenes/gpio_scene_test.c @@ -12,8 +12,9 @@ void gpio_scene_test_ok_callback(InputType type, void* context) { } void gpio_scene_test_on_enter(void* context) { + furi_assert(context); GpioApp* app = context; - gpio_item_configure_all_pins(GpioModeOutputPushPull); + gpio_items_configure_all_pins(app->gpio_items, GpioModeOutputPushPull); gpio_test_set_ok_callback(app->gpio_test, gpio_scene_test_ok_callback, app); view_dispatcher_switch_to_view(app->view_dispatcher, GpioAppViewGpioTest); } @@ -25,6 +26,7 @@ bool gpio_scene_test_on_event(void* context, SceneManagerEvent event) { } void gpio_scene_test_on_exit(void* context) { - UNUSED(context); - gpio_item_configure_all_pins(GpioModeAnalog); + furi_assert(context); + GpioApp* app = context; + gpio_items_configure_all_pins(app->gpio_items, GpioModeAnalog); } diff --git a/applications/main/gpio/scenes/gpio_scene_usb_uart_config.c b/applications/main/gpio/scenes/gpio_scene_usb_uart_config.c index 776343fb003..e2ab6626447 100644 --- a/applications/main/gpio/scenes/gpio_scene_usb_uart_config.c +++ b/applications/main/gpio/scenes/gpio_scene_usb_uart_config.c @@ -1,6 +1,6 @@ #include "../usb_uart_bridge.h" #include "../gpio_app_i.h" -#include "furi_hal.h" +#include typedef enum { UsbUartLineIndexVcp, diff --git a/applications/main/gpio/usb_uart_bridge.c b/applications/main/gpio/usb_uart_bridge.c index 1a82dbdc2e5..9bc759dc895 100644 --- a/applications/main/gpio/usb_uart_bridge.c +++ b/applications/main/gpio/usb_uart_bridge.c @@ -1,10 +1,10 @@ #include "usb_uart_bridge.h" -#include "furi_hal.h" -#include #include "usb_cdc.h" -#include "cli/cli_vcp.h" +#include +#include #include -#include "cli/cli.h" +#include +#include #define USB_CDC_PKT_LEN CDC_DATA_SZ #define USB_UART_RX_BUF_SIZE (USB_CDC_PKT_LEN * 5) diff --git a/applications/main/gpio/views/gpio_test.c b/applications/main/gpio/views/gpio_test.c index 69dc0f67bfe..c154a7275b4 100644 --- a/applications/main/gpio/views/gpio_test.c +++ b/applications/main/gpio/views/gpio_test.c @@ -1,5 +1,5 @@ #include "gpio_test.h" -#include "../gpio_item.h" +#include "../gpio_items.h" #include @@ -11,6 +11,7 @@ struct GpioTest { typedef struct { uint8_t pin_idx; + GPIOItems* gpio_items; } GpioTestModel; static bool gpio_test_process_left(GpioTest* gpio_test); @@ -25,7 +26,12 @@ static void gpio_test_draw_callback(Canvas* canvas, void* _model) { elements_multiline_text_aligned( canvas, 64, 16, AlignCenter, AlignTop, "Press < or > to change pin"); elements_multiline_text_aligned( - canvas, 64, 32, AlignCenter, AlignTop, gpio_item_get_pin_name(model->pin_idx)); + canvas, + 64, + 32, + AlignCenter, + AlignTop, + gpio_items_get_pin_name(model->gpio_items, model->pin_idx)); } static bool gpio_test_input_callback(InputEvent* event, void* context) { @@ -64,7 +70,7 @@ static bool gpio_test_process_right(GpioTest* gpio_test) { gpio_test->view, GpioTestModel * model, { - if(model->pin_idx < GPIO_ITEM_COUNT) { + if(model->pin_idx < gpio_items_get_count(model->gpio_items)) { model->pin_idx++; } }, @@ -80,17 +86,17 @@ static bool gpio_test_process_ok(GpioTest* gpio_test, InputEvent* event) { GpioTestModel * model, { if(event->type == InputTypePress) { - if(model->pin_idx < GPIO_ITEM_COUNT) { - gpio_item_set_pin(model->pin_idx, true); + if(model->pin_idx < gpio_items_get_count(model->gpio_items)) { + gpio_items_set_pin(model->gpio_items, model->pin_idx, true); } else { - gpio_item_set_all_pins(true); + gpio_items_set_all_pins(model->gpio_items, true); } consumed = true; } else if(event->type == InputTypeRelease) { - if(model->pin_idx < GPIO_ITEM_COUNT) { - gpio_item_set_pin(model->pin_idx, false); + if(model->pin_idx < gpio_items_get_count(model->gpio_items)) { + gpio_items_set_pin(model->gpio_items, model->pin_idx, false); } else { - gpio_item_set_all_pins(false); + gpio_items_set_all_pins(model->gpio_items, false); } consumed = true; } @@ -101,11 +107,15 @@ static bool gpio_test_process_ok(GpioTest* gpio_test, InputEvent* event) { return consumed; } -GpioTest* gpio_test_alloc() { +GpioTest* gpio_test_alloc(GPIOItems* gpio_items) { GpioTest* gpio_test = malloc(sizeof(GpioTest)); gpio_test->view = view_alloc(); view_allocate_model(gpio_test->view, ViewModelTypeLocking, sizeof(GpioTestModel)); + + with_view_model( + gpio_test->view, GpioTestModel * model, { model->gpio_items = gpio_items; }, false); + view_set_context(gpio_test->view, gpio_test); view_set_draw_callback(gpio_test->view, gpio_test_draw_callback); view_set_input_callback(gpio_test->view, gpio_test_input_callback); diff --git a/applications/main/gpio/views/gpio_test.h b/applications/main/gpio/views/gpio_test.h index 5cbd11e82cf..38fcbc5fb09 100644 --- a/applications/main/gpio/views/gpio_test.h +++ b/applications/main/gpio/views/gpio_test.h @@ -1,11 +1,13 @@ #pragma once +#include "../gpio_items.h" + #include typedef struct GpioTest GpioTest; typedef void (*GpioTestOkCallback)(InputType type, void* context); -GpioTest* gpio_test_alloc(); +GpioTest* gpio_test_alloc(GPIOItems* gpio_items); void gpio_test_free(GpioTest* gpio_test); diff --git a/applications/main/gpio/views/gpio_usb_uart.c b/applications/main/gpio/views/gpio_usb_uart.c index c7406d29ba4..837f2e3ec58 100644 --- a/applications/main/gpio/views/gpio_usb_uart.c +++ b/applications/main/gpio/views/gpio_usb_uart.c @@ -1,6 +1,6 @@ #include "../usb_uart_bridge.h" #include "../gpio_app_i.h" -#include "furi_hal.h" +#include #include struct GpioUsbUart { diff --git a/applications/main/ibutton/application.fam b/applications/main/ibutton/application.fam index 77bb9a33c63..06968bba48f 100644 --- a/applications/main/ibutton/application.fam +++ b/applications/main/ibutton/application.fam @@ -2,6 +2,7 @@ App( appid="ibutton", name="iButton", apptype=FlipperAppType.APP, + targets=["f7"], entry_point="ibutton_app", cdefines=["APP_IBUTTON"], requires=[ diff --git a/applications/main/infrared/application.fam b/applications/main/infrared/application.fam index 9c5eaf392c4..e5483e9ffa9 100644 --- a/applications/main/infrared/application.fam +++ b/applications/main/infrared/application.fam @@ -3,6 +3,7 @@ App( name="Infrared", apptype=FlipperAppType.APP, entry_point="infrared_app", + targets=["f7"], cdefines=["APP_INFRARED"], requires=[ "gui", diff --git a/applications/main/infrared/scenes/infrared_scene_rpc.c b/applications/main/infrared/scenes/infrared_scene_rpc.c index 8044e8318fc..04f17416d90 100644 --- a/applications/main/infrared/scenes/infrared_scene_rpc.c +++ b/applications/main/infrared/scenes/infrared_scene_rpc.c @@ -1,5 +1,5 @@ #include "../infrared_i.h" -#include "gui/canvas.h" +#include typedef enum { InfraredRpcStateIdle, diff --git a/applications/main/infrared/views/infrared_debug_view.c b/applications/main/infrared/views/infrared_debug_view.c index ab2c679c4d0..ec08962813e 100644 --- a/applications/main/infrared/views/infrared_debug_view.c +++ b/applications/main/infrared/views/infrared_debug_view.c @@ -1,11 +1,11 @@ #include "infrared_debug_view.h" -#include -#include - #include #include +#include +#include + #define INFRARED_DEBUG_TEXT_LENGTH 64 struct InfraredDebugView { diff --git a/applications/main/infrared/views/infrared_progress_view.c b/applications/main/infrared/views/infrared_progress_view.c index 3c50f89e42c..432da7ff1cd 100644 --- a/applications/main/infrared/views/infrared_progress_view.c +++ b/applications/main/infrared/views/infrared_progress_view.c @@ -1,13 +1,15 @@ -#include -#include "furi_hal_resources.h" -#include "assets_icons.h" -#include "gui/canvas.h" -#include "gui/view.h" -#include "input/input.h" +#include "infrared_progress_view.h" + +#include +#include +#include #include +#include +#include + #include -#include "infrared_progress_view.h" -#include "gui/modules/button_panel.h" +#include +#include #include struct InfraredProgressView { diff --git a/applications/main/lfrfid/application.fam b/applications/main/lfrfid/application.fam index 150a6f3dbb2..8fe1bac4dc6 100644 --- a/applications/main/lfrfid/application.fam +++ b/applications/main/lfrfid/application.fam @@ -2,6 +2,7 @@ App( appid="lfrfid", name="125 kHz RFID", apptype=FlipperAppType.APP, + targets=["f7"], entry_point="lfrfid_app", cdefines=["APP_LF_RFID"], requires=[ diff --git a/applications/main/nfc/application.fam b/applications/main/nfc/application.fam index ce21bfd2738..f091270455f 100644 --- a/applications/main/nfc/application.fam +++ b/applications/main/nfc/application.fam @@ -2,6 +2,7 @@ App( appid="nfc", name="NFC", apptype=FlipperAppType.APP, + targets=["f7"], entry_point="nfc_app", cdefines=["APP_NFC"], requires=[ diff --git a/applications/main/nfc/nfc.c b/applications/main/nfc/nfc.c index 6e6dc4dcce9..4540f5d9f06 100644 --- a/applications/main/nfc/nfc.c +++ b/applications/main/nfc/nfc.c @@ -1,5 +1,5 @@ #include "nfc_i.h" -#include "furi_hal_nfc.h" +#include #include bool nfc_custom_event_callback(void* context, uint32_t event) { diff --git a/applications/main/subghz/application.fam b/applications/main/subghz/application.fam index 7d31ecae50e..6df4b1a8aa9 100644 --- a/applications/main/subghz/application.fam +++ b/applications/main/subghz/application.fam @@ -2,6 +2,7 @@ App( appid="subghz", name="Sub-GHz", apptype=FlipperAppType.APP, + targets=["f7"], entry_point="subghz_app", cdefines=["APP_SUBGHZ"], requires=[ diff --git a/applications/main/u2f/scenes/u2f_scene_main.c b/applications/main/u2f/scenes/u2f_scene_main.c index af7f1159bb7..251bc4d991a 100644 --- a/applications/main/u2f/scenes/u2f_scene_main.c +++ b/applications/main/u2f/scenes/u2f_scene_main.c @@ -1,7 +1,7 @@ #include "../u2f_app_i.h" #include "../views/u2f_view.h" #include -#include "furi_hal.h" +#include #include "../u2f.h" #define U2F_REQUEST_TIMEOUT 500 diff --git a/applications/plugins/music_player/music_player_worker.c b/applications/plugins/music_player/music_player_worker.c index 60fd33a1753..ee350ee8045 100644 --- a/applications/plugins/music_player/music_player_worker.c +++ b/applications/plugins/music_player/music_player_worker.c @@ -6,6 +6,7 @@ #include #include +#include #include #define TAG "MusicPlayerWorker" diff --git a/applications/plugins/nfc_magic/application.fam b/applications/plugins/nfc_magic/application.fam index f09d65c90cc..bf42681cae4 100644 --- a/applications/plugins/nfc_magic/application.fam +++ b/applications/plugins/nfc_magic/application.fam @@ -2,6 +2,7 @@ App( appid="nfc_magic", name="Nfc Magic", apptype=FlipperAppType.EXTERNAL, + targets=["f7"], entry_point="nfc_magic_app", requires=[ "storage", diff --git a/applications/plugins/picopass/application.fam b/applications/plugins/picopass/application.fam index bfbe5ed0234..f2da6a9fab6 100644 --- a/applications/plugins/picopass/application.fam +++ b/applications/plugins/picopass/application.fam @@ -2,6 +2,7 @@ App( appid="picopass", name="PicoPass Reader", apptype=FlipperAppType.EXTERNAL, + targets=["f7"], entry_point="picopass_app", requires=[ "storage", diff --git a/applications/plugins/signal_generator/signal_gen_app_i.h b/applications/plugins/signal_generator/signal_gen_app_i.h index 47c266475b1..60e4d7ed9ab 100644 --- a/applications/plugins/signal_generator/signal_gen_app_i.h +++ b/applications/plugins/signal_generator/signal_gen_app_i.h @@ -2,8 +2,8 @@ #include "scenes/signal_gen_scene.h" -#include "furi_hal_clock.h" -#include "furi_hal_pwm.h" +#include +#include #include #include diff --git a/applications/plugins/signal_generator/views/signal_gen_pwm.c b/applications/plugins/signal_generator/views/signal_gen_pwm.c index b6ba47ab072..d625ed5a9e2 100644 --- a/applications/plugins/signal_generator/views/signal_gen_pwm.c +++ b/applications/plugins/signal_generator/views/signal_gen_pwm.c @@ -1,5 +1,5 @@ #include "../signal_gen_app_i.h" -#include "furi_hal.h" +#include #include #include diff --git a/applications/plugins/weather_station/application.fam b/applications/plugins/weather_station/application.fam index 1074fc040f2..769b6dd2750 100644 --- a/applications/plugins/weather_station/application.fam +++ b/applications/plugins/weather_station/application.fam @@ -2,6 +2,7 @@ App( appid="weather_station", name="Weather Station", apptype=FlipperAppType.PLUGIN, + targets=["f7"], entry_point="weather_station_app", cdefines=["APP_WEATHER_STATION"], requires=["gui"], diff --git a/applications/plugins/weather_station/protocols/ws_generic.h b/applications/plugins/weather_station/protocols/ws_generic.h index 8e6e061adbe..47cfa74b3a8 100644 --- a/applications/plugins/weather_station/protocols/ws_generic.h +++ b/applications/plugins/weather_station/protocols/ws_generic.h @@ -6,7 +6,7 @@ #include #include "furi.h" -#include "furi_hal.h" +#include #include #include diff --git a/applications/services/cli/cli_command_gpio.c b/applications/services/cli/cli_command_gpio.c index f0d487bec47..d0246273494 100644 --- a/applications/services/cli/cli_command_gpio.c +++ b/applications/services/cli/cli_command_gpio.c @@ -5,28 +5,6 @@ #include #include -typedef struct { - const GpioPin* pin; - const char* name; - const bool debug; -} CliCommandGpio; - -const CliCommandGpio cli_command_gpio_pins[] = { - {.pin = &gpio_ext_pc0, .name = "PC0", .debug = false}, - {.pin = &gpio_ext_pc1, .name = "PC1", .debug = false}, - {.pin = &gpio_ext_pc3, .name = "PC3", .debug = false}, - {.pin = &gpio_ext_pb2, .name = "PB2", .debug = false}, - {.pin = &gpio_ext_pb3, .name = "PB3", .debug = false}, - {.pin = &gpio_ext_pa4, .name = "PA4", .debug = false}, - {.pin = &gpio_ext_pa6, .name = "PA6", .debug = false}, - {.pin = &gpio_ext_pa7, .name = "PA7", .debug = false}, - /* Dangerous pins, may damage hardware */ - {.pin = &gpio_infrared_rx, .name = "PA0", .debug = true}, - {.pin = &gpio_usart_rx, .name = "PB7", .debug = true}, - {.pin = &gpio_speaker, .name = "PB8", .debug = true}, - {.pin = &gpio_infrared_tx, .name = "PB9", .debug = true}, -}; - void cli_command_gpio_print_usage() { printf("Usage:\r\n"); printf("gpio \r\n"); @@ -38,9 +16,9 @@ void cli_command_gpio_print_usage() { static bool pin_name_to_int(FuriString* pin_name, size_t* result) { bool is_debug_mode = furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug); - for(size_t i = 0; i < COUNT_OF(cli_command_gpio_pins); i++) { - if(furi_string_equal(pin_name, cli_command_gpio_pins[i].name)) { - if(!cli_command_gpio_pins[i].debug || is_debug_mode) { + for(size_t i = 0; i < gpio_pins_count; i++) { + if(furi_string_equal(pin_name, gpio_pins[i].name)) { + if(!gpio_pins[i].debug || is_debug_mode) { *result = i; return true; } @@ -53,9 +31,9 @@ static bool pin_name_to_int(FuriString* pin_name, size_t* result) { static void gpio_print_pins(void) { printf("Wrong pin name. Available pins: "); bool is_debug_mode = furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug); - for(size_t i = 0; i < COUNT_OF(cli_command_gpio_pins); i++) { - if(!cli_command_gpio_pins[i].debug || is_debug_mode) { - printf("%s ", cli_command_gpio_pins[i].name); + for(size_t i = 0; i < gpio_pins_count; i++) { + if(!gpio_pins[i].debug || is_debug_mode) { + printf("%s ", gpio_pins[i].name); } } } @@ -113,7 +91,7 @@ void cli_command_gpio_mode(Cli* cli, FuriString* args, void* context) { return; } - if(cli_command_gpio_pins[num].debug) { //-V779 + if(gpio_pins[num].debug) { //-V779 printf( "Changing this pin mode may damage hardware. Are you sure you want to continue? (y/n)?\r\n"); char c = cli_getc(cli); @@ -124,12 +102,12 @@ void cli_command_gpio_mode(Cli* cli, FuriString* args, void* context) { } if(value == 1) { // output - furi_hal_gpio_write(cli_command_gpio_pins[num].pin, false); - furi_hal_gpio_init_simple(cli_command_gpio_pins[num].pin, GpioModeOutputPushPull); - printf("Pin %s is now an output (low)", cli_command_gpio_pins[num].name); + furi_hal_gpio_write(gpio_pins[num].pin, false); + furi_hal_gpio_init_simple(gpio_pins[num].pin, GpioModeOutputPushPull); + printf("Pin %s is now an output (low)", gpio_pins[num].name); } else { // input - furi_hal_gpio_init_simple(cli_command_gpio_pins[num].pin, GpioModeInput); - printf("Pin %s is now an input", cli_command_gpio_pins[num].name); + furi_hal_gpio_init_simple(gpio_pins[num].pin, GpioModeInput); + printf("Pin %s is now an input", gpio_pins[num].name); } } @@ -144,15 +122,14 @@ void cli_command_gpio_read(Cli* cli, FuriString* args, void* context) { } if(LL_GPIO_MODE_INPUT != //-V779 - LL_GPIO_GetPinMode( - cli_command_gpio_pins[num].pin->port, cli_command_gpio_pins[num].pin->pin)) { - printf("Err: pin %s is not set as an input.", cli_command_gpio_pins[num].name); + LL_GPIO_GetPinMode(gpio_pins[num].pin->port, gpio_pins[num].pin->pin)) { + printf("Err: pin %s is not set as an input.", gpio_pins[num].name); return; } - uint8_t val = !!furi_hal_gpio_read(cli_command_gpio_pins[num].pin); + uint8_t val = !!furi_hal_gpio_read(gpio_pins[num].pin); - printf("Pin %s <= %u", cli_command_gpio_pins[num].name, val); + printf("Pin %s <= %u", gpio_pins[num].name, val); } void cli_command_gpio_set(Cli* cli, FuriString* args, void* context) { @@ -174,14 +151,13 @@ void cli_command_gpio_set(Cli* cli, FuriString* args, void* context) { } if(LL_GPIO_MODE_OUTPUT != //-V779 - LL_GPIO_GetPinMode( - cli_command_gpio_pins[num].pin->port, cli_command_gpio_pins[num].pin->pin)) { - printf("Err: pin %s is not set as an output.", cli_command_gpio_pins[num].name); + LL_GPIO_GetPinMode(gpio_pins[num].pin->port, gpio_pins[num].pin->pin)) { + printf("Err: pin %s is not set as an output.", gpio_pins[num].name); return; } // Extra check if debug pins used - if(cli_command_gpio_pins[num].debug) { + if(gpio_pins[num].debug) { printf( "Setting this pin may damage hardware. Are you sure you want to continue? (y/n)?\r\n"); char c = cli_getc(cli); @@ -191,8 +167,8 @@ void cli_command_gpio_set(Cli* cli, FuriString* args, void* context) { } } - furi_hal_gpio_write(cli_command_gpio_pins[num].pin, !!value); - printf("Pin %s => %u", cli_command_gpio_pins[num].name, !!value); + furi_hal_gpio_write(gpio_pins[num].pin, !!value); + printf("Pin %s => %u", gpio_pins[num].name, !!value); } void cli_command_gpio(Cli* cli, FuriString* args, void* context) { diff --git a/applications/services/dialogs/dialogs_api.c b/applications/services/dialogs/dialogs_api.c index 5e2b0683ee3..ca2435b9b34 100644 --- a/applications/services/dialogs/dialogs_api.c +++ b/applications/services/dialogs/dialogs_api.c @@ -1,4 +1,4 @@ -#include "dialogs/dialogs_message.h" +#include "dialogs_message.h" #include "dialogs_i.h" #include #include diff --git a/applications/services/dialogs/dialogs_module_file_browser.c b/applications/services/dialogs/dialogs_module_file_browser.c index 8d486dbaf4e..79d151c590a 100644 --- a/applications/services/dialogs/dialogs_module_file_browser.c +++ b/applications/services/dialogs/dialogs_module_file_browser.c @@ -1,6 +1,7 @@ #include "dialogs_i.h" + +#include #include -#include "gui/modules/file_browser.h" typedef struct { FuriApiLock lock; diff --git a/applications/services/dolphin/dolphin.h b/applications/services/dolphin/dolphin.h index 41a6a6089ce..8757e2a3773 100644 --- a/applications/services/dolphin/dolphin.h +++ b/applications/services/dolphin/dolphin.h @@ -1,8 +1,9 @@ #pragma once -#include -#include "gui/view.h" #include "helpers/dolphin_deed.h" + +#include +#include #include #ifdef __cplusplus diff --git a/applications/services/gui/elements.c b/applications/services/gui/elements.c index e22889bf9db..54c36af76ca 100644 --- a/applications/services/gui/elements.c +++ b/applications/services/gui/elements.c @@ -1,16 +1,17 @@ #include "elements.h" -#include "m-core.h" +#include #include -#include "furi_hal_resources.h" +#include #include -#include "gui/canvas.h" +#include #include #include #include #include "canvas_i.h" +#include #include #include #include diff --git a/applications/services/gui/gui.c b/applications/services/gui/gui.c index b0903e021c9..af5cf862d0a 100644 --- a/applications/services/gui/gui.c +++ b/applications/services/gui/gui.c @@ -1,4 +1,3 @@ -#include "gui/canvas.h" #include "gui_i.h" #include diff --git a/applications/services/gui/modules/button_menu.c b/applications/services/gui/modules/button_menu.c index 427a3e1aeac..6ac786c922e 100644 --- a/applications/services/gui/modules/button_menu.c +++ b/applications/services/gui/modules/button_menu.c @@ -1,12 +1,15 @@ #include "button_menu.h" -#include "gui/canvas.h" -#include "gui/elements.h" -#include "input/input.h" -#include + +#include +#include +#include + #include -#include #include +#include +#include + #define ITEM_FIRST_OFFSET 17 #define ITEM_NEXT_OFFSET 4 #define ITEM_HEIGHT 14 diff --git a/applications/services/gui/modules/button_panel.c b/applications/services/gui/modules/button_panel.c index 8f29c65421b..9b816eed0c6 100644 --- a/applications/services/gui/modules/button_panel.c +++ b/applications/services/gui/modules/button_panel.c @@ -1,12 +1,15 @@ #include "button_panel.h" -#include "furi_hal_resources.h" -#include "gui/canvas.h" + +#include +#include + +#include +#include +#include + #include #include #include -#include -#include -#include typedef struct { // uint16_t to support multi-screen, wide button panel diff --git a/applications/services/gui/modules/byte_input.c b/applications/services/gui/modules/byte_input.c index 82de129f54b..b2d21f7ae39 100644 --- a/applications/services/gui/modules/byte_input.c +++ b/applications/services/gui/modules/byte_input.c @@ -1,7 +1,8 @@ -#include +#include "byte_input.h" + #include +#include #include -#include "byte_input.h" struct ByteInput { View* view; diff --git a/applications/services/gui/modules/file_browser.c b/applications/services/gui/modules/file_browser.c index e03a032c182..d12a00ba598 100644 --- a/applications/services/gui/modules/file_browser.c +++ b/applications/services/gui/modules/file_browser.c @@ -1,14 +1,17 @@ #include "file_browser.h" -#include "assets_icons.h" #include "file_browser_worker.h" + +#include +#include +#include + +#include +#include + #include #include #include -#include "furi_hal_resources.h" #include -#include -#include -#include "toolbox/path.h" #define LIST_ITEMS 5u #define MAX_LEN_PX 110 diff --git a/applications/services/gui/modules/file_browser_worker.c b/applications/services/gui/modules/file_browser_worker.c index a97a4d71aff..80c4f4c43a8 100644 --- a/applications/services/gui/modules/file_browser_worker.c +++ b/applications/services/gui/modules/file_browser_worker.c @@ -1,13 +1,16 @@ #include "file_browser_worker.h" + +#include +#include + +#include #include #include -#include "storage/filesystem_api_defines.h" +#include + #include #include -#include -#include #include -#include "toolbox/path.h" #define TAG "BrowserWorker" diff --git a/applications/services/gui/modules/loading.c b/applications/services/gui/modules/loading.c index 0fa2c128635..e64aeb78fb1 100644 --- a/applications/services/gui/modules/loading.c +++ b/applications/services/gui/modules/loading.c @@ -1,13 +1,14 @@ -#include -#include -#include +#include "loading.h" + #include #include #include #include #include -#include "loading.h" +#include +#include +#include struct Loading { View* view; diff --git a/applications/services/gui/modules/menu.c b/applications/services/gui/modules/menu.c index 6983e0108b1..3e3b6c2e481 100644 --- a/applications/services/gui/modules/menu.c +++ b/applications/services/gui/modules/menu.c @@ -1,9 +1,9 @@ #include "menu.h" -#include #include #include #include +#include struct Menu { View* view; diff --git a/applications/services/gui/modules/submenu.c b/applications/services/gui/modules/submenu.c index 72626c587de..00e4d68b5ac 100644 --- a/applications/services/gui/modules/submenu.c +++ b/applications/services/gui/modules/submenu.c @@ -1,8 +1,8 @@ #include "submenu.h" -#include #include #include +#include struct Submenu { View* view; diff --git a/applications/services/gui/modules/text_box.c b/applications/services/gui/modules/text_box.c index 079a1294d83..01ccdbf52b5 100644 --- a/applications/services/gui/modules/text_box.c +++ b/applications/services/gui/modules/text_box.c @@ -1,7 +1,7 @@ #include "text_box.h" -#include "gui/canvas.h" -#include +#include #include +#include #include struct TextBox { diff --git a/applications/services/gui/modules/validators.c b/applications/services/gui/modules/validators.c index 9c5d0be849f..a18cb925f14 100644 --- a/applications/services/gui/modules/validators.c +++ b/applications/services/gui/modules/validators.c @@ -1,6 +1,6 @@ -#include #include "validators.h" #include +#include struct ValidatorIsFile { char* app_path_folder; diff --git a/applications/services/gui/modules/validators.h b/applications/services/gui/modules/validators.h index d9200b6dbfa..e509fd8336c 100644 --- a/applications/services/gui/modules/validators.h +++ b/applications/services/gui/modules/validators.h @@ -1,6 +1,7 @@ #pragma once #include +#include #ifdef __cplusplus extern "C" { diff --git a/applications/services/gui/modules/variable_item_list.c b/applications/services/gui/modules/variable_item_list.c index 5060985e160..cf7f64ca350 100644 --- a/applications/services/gui/modules/variable_item_list.c +++ b/applications/services/gui/modules/variable_item_list.c @@ -1,8 +1,8 @@ #include "variable_item_list.h" -#include "gui/canvas.h" -#include -#include #include +#include +#include +#include #include struct VariableItem { diff --git a/applications/services/gui/modules/widget.c b/applications/services/gui/modules/widget.c index 6153b425bde..21b8e561629 100644 --- a/applications/services/gui/modules/widget.c +++ b/applications/services/gui/modules/widget.c @@ -1,7 +1,7 @@ -#include #include "widget.h" -#include #include "widget_elements/widget_element_i.h" +#include +#include ARRAY_DEF(ElementArray, WidgetElement*, M_PTR_OPLIST); diff --git a/applications/services/gui/view_stack.c b/applications/services/gui/view_stack.c index 3bb00fbf25a..0a106f1ba57 100644 --- a/applications/services/gui/view_stack.c +++ b/applications/services/gui/view_stack.c @@ -1,8 +1,9 @@ -#include "gui/view.h" -#include #include "view_stack.h" #include "view_i.h" +#include +#include + #define MAX_VIEWS 3 typedef struct { diff --git a/applications/services/notification/notification_app.c b/applications/services/notification/notification_app.c index b6579f54769..2e170f54766 100644 --- a/applications/services/notification/notification_app.c +++ b/applications/services/notification/notification_app.c @@ -1,4 +1,4 @@ -#include "furi_hal_light.h" +#include #include #include #include diff --git a/applications/services/notification/notification_messages.c b/applications/services/notification/notification_messages.c index d795c55d91a..51f3533c32d 100644 --- a/applications/services/notification/notification_messages.c +++ b/applications/services/notification/notification_messages.c @@ -1,4 +1,4 @@ -#include "furi_hal_resources.h" +#include #include "notification.h" #include "notification_messages_notes.h" #include diff --git a/applications/settings/about/about.c b/applications/settings/about/about.c index 560a683cfe0..61c72496680 100644 --- a/applications/settings/about/about.c +++ b/applications/settings/about/about.c @@ -13,17 +13,29 @@ typedef DialogMessageButton (*AboutDialogScreen)(DialogsApp* dialogs, DialogMess static DialogMessageButton product_screen(DialogsApp* dialogs, DialogMessage* message) { DialogMessageButton result; - const char* screen_header = "Product: Flipper Zero\n" - "Model: FZ.1\n"; - const char* screen_text = "FCC ID: 2A2V6-FZ\n" - "IC: 27624-FZ"; - - dialog_message_set_header(message, screen_header, 0, 0, AlignLeft, AlignTop); - dialog_message_set_text(message, screen_text, 0, 26, AlignLeft, AlignTop); + FuriString* screen_header = furi_string_alloc_printf( + "Product: %s\n" + "Model: %s", + furi_hal_version_get_model_name(), + furi_hal_version_get_model_code()); + + FuriString* screen_text = furi_string_alloc_printf( + "FCC ID: %s\n" + "IC: %s", + furi_hal_version_get_fcc_id(), + furi_hal_version_get_ic_id()); + + dialog_message_set_header( + message, furi_string_get_cstr(screen_header), 0, 0, AlignLeft, AlignTop); + dialog_message_set_text( + message, furi_string_get_cstr(screen_text), 0, 26, AlignLeft, AlignTop); result = dialog_message_show(dialogs, message); dialog_message_set_header(message, NULL, 0, 0, AlignLeft, AlignTop); dialog_message_set_text(message, NULL, 0, 0, AlignLeft, AlignTop); + furi_string_free(screen_header); + furi_string_free(screen_text); + return result; } diff --git a/applications/settings/bt_settings_app/scenes/bt_settings_scene_forget_dev_confirm.c b/applications/settings/bt_settings_app/scenes/bt_settings_scene_forget_dev_confirm.c index 964736b6695..31921b9f339 100644 --- a/applications/settings/bt_settings_app/scenes/bt_settings_scene_forget_dev_confirm.c +++ b/applications/settings/bt_settings_app/scenes/bt_settings_scene_forget_dev_confirm.c @@ -1,5 +1,5 @@ #include "../bt_settings_app.h" -#include "furi_hal_bt.h" +#include void bt_settings_scene_forget_dev_confirm_dialog_callback(DialogExResult result, void* context) { furi_assert(context); diff --git a/applications/settings/bt_settings_app/scenes/bt_settings_scene_forget_dev_success.c b/applications/settings/bt_settings_app/scenes/bt_settings_scene_forget_dev_success.c index c0b0c80a917..481ba6d5c85 100644 --- a/applications/settings/bt_settings_app/scenes/bt_settings_scene_forget_dev_success.c +++ b/applications/settings/bt_settings_app/scenes/bt_settings_scene_forget_dev_success.c @@ -1,5 +1,5 @@ #include "../bt_settings_app.h" -#include "furi_hal_bt.h" +#include void bt_settings_app_scene_forget_dev_success_popup_callback(void* context) { BtSettingsApp* app = context; diff --git a/applications/settings/bt_settings_app/scenes/bt_settings_scene_start.c b/applications/settings/bt_settings_app/scenes/bt_settings_scene_start.c index bcbc902b248..5db98e9dec3 100644 --- a/applications/settings/bt_settings_app/scenes/bt_settings_scene_start.c +++ b/applications/settings/bt_settings_app/scenes/bt_settings_scene_start.c @@ -1,5 +1,5 @@ #include "../bt_settings_app.h" -#include "furi_hal_bt.h" +#include enum BtSetting { BtSettingOff, diff --git a/applications/system/storage_move_to_sd/scenes/storage_move_to_sd_scene_confirm.c b/applications/system/storage_move_to_sd/scenes/storage_move_to_sd_scene_confirm.c index 71ce5a7c3fc..08c9e2d7fc2 100644 --- a/applications/system/storage_move_to_sd/scenes/storage_move_to_sd_scene_confirm.c +++ b/applications/system/storage_move_to_sd/scenes/storage_move_to_sd_scene_confirm.c @@ -1,7 +1,7 @@ #include "../storage_move_to_sd.h" -#include "gui/canvas.h" -#include "gui/modules/widget_elements/widget_element_i.h" -#include "storage/storage.h" +#include +#include +#include static void storage_move_to_sd_scene_confirm_widget_callback( GuiButtonType result, diff --git a/applications/system/storage_move_to_sd/storage_move_to_sd.h b/applications/system/storage_move_to_sd/storage_move_to_sd.h index a62d87c1f22..135f3e9b0be 100644 --- a/applications/system/storage_move_to_sd/storage_move_to_sd.h +++ b/applications/system/storage_move_to_sd/storage_move_to_sd.h @@ -1,17 +1,16 @@ #pragma once -#include "gui/modules/widget_elements/widget_element_i.h" -#include #include #include #include #include -#include - #include #include +#include +#include #include #include +#include #include "scenes/storage_move_to_sd_scene.h" diff --git a/applications/system/updater/cli/updater_cli.c b/applications/system/updater/cli/updater_cli.c index f8e21ca3ee4..2bf6dab26e4 100644 --- a/applications/system/updater/cli/updater_cli.c +++ b/applications/system/updater/cli/updater_cli.c @@ -76,8 +76,8 @@ static void updater_cli_ep(Cli* cli, FuriString* args, void* context) { for(size_t idx = 0; idx < COUNT_OF(update_cli_subcommands); ++idx) { const CliSubcommand* subcmd_def = &update_cli_subcommands[idx]; if(furi_string_cmp_str(subcommand, subcmd_def->command) == 0) { - furi_string_free(subcommand); subcmd_def->handler(args); + furi_string_free(subcommand); return; } } diff --git a/applications/system/updater/util/update_task.c b/applications/system/updater/util/update_task.c index b43a6df167d..54fe27995cb 100644 --- a/applications/system/updater/util/update_task.c +++ b/applications/system/updater/util/update_task.c @@ -287,7 +287,9 @@ bool update_task_parse_manifest(UpdateTask* update_task) { } update_task_set_progress(update_task, UpdateTaskStageProgress, 50); - if(manifest->target != furi_hal_version_get_hw_target()) { + /* Check target only if it's set - skipped for pre-production samples */ + if(furi_hal_version_get_hw_target() && + (manifest->target != furi_hal_version_get_hw_target())) { break; } diff --git a/documentation/HardwareTargets.md b/documentation/HardwareTargets.md new file mode 100644 index 00000000000..0c3474839b7 --- /dev/null +++ b/documentation/HardwareTargets.md @@ -0,0 +1,44 @@ +## What a Firmware Target is + +Flipper's firmware is modular and supports different hardware configurations in a common code base. It encapsulates hardware-specific differences in `furi_hal`, board initialization code, linker files, SDK data and other information in a _target definition_. + +Target-specific files are placed in a single sub-folder in `firmware/targets`. It must contain a target definition file, `target.json`, and may contain other files if they are referenced by current target's definition. By default, `fbt` gathers all source files in target folder, unless they are explicitly excluded. + +Targets can inherit most code parts from other targets, to reduce common code duplication. + + +## Target Definition File + +A target definition file, `target.json`, is a JSON file that can contain the following fields: + +* `include_paths`: list of strings, folder paths relative to current target folder to add to global C/C++ header path lookup list. +* `sdk_header_paths`: list of strings, folder paths relative to current target folder to gather headers from for including in SDK. +* `startup_script`: filename of a startup script, performing initial hardware initialization. +* `linker_script_flash`: filename of a linker script for creating the main firmware image. +* `linker_script_ram`: filename of a linker script to use in "updater" build configuration. +* `linker_script_app`: filename of a linker script to use for linking .fap files. +* `sdk_symbols`: filename of a .csv file containing current SDK configuration for this target. +* `linker_dependencies`: list of libraries to link the firmware with. Note that those not in the list won't be built by `fbt`. Also several link passes might be needed, in such case you may need to specify same library name twice. +* `inherit`: string, specifies hardware target to borrow main configuration from. Current configuration may specify additional values for parameters that are lists of strings, or override values that are not lists. +* `excluded_sources`: list of filenames from the inherited configuration(s) NOT to be built. +* `excluded_headers`: list of headers from the inherited configuration(s) NOT to be included in generated SDK. +* `excluded_modules`: list of strings specifying fbt library (module) names to exclude from being used to configure build environment. + + +## Applications & Hardware + +Not all applications are available on different hardware targets. + +* For applications built into the firmware, you have to specify a compatible application set using `FIRMWARE_APP_SET=...` fbt option. See [fbt docs](./fbt.md#firmware-application-set) for details on build configurations. + +* For applications built as external .faps, you have to explicitly specify compatible targets in application's manifest, `application.fam`. For example, to limit application to a single target, add `targets=["f7"],` to the manifest. It won't be built for other targets. + +For details on application manifests, check out [their docs page](./AppManifests.md). + + +## Building Firmware for a Specific Target + +You have to specify TARGET_HW (and, optionally, FIRMWARE_APP_SET) for `fbt` to build firmware for non-default target. For example, building and flashing debug firmware for f18 can be done with + + ./fbt TARGET_HW=18 flash_usb_full + diff --git a/firmware.scons b/firmware.scons index 3922c136e1a..92f2d1a91c9 100644 --- a/firmware.scons +++ b/firmware.scons @@ -16,6 +16,7 @@ env = ENV.Clone( "fwbin", "fbt_apps", "pvsstudio", + "fbt_hwtarget", ], COMPILATIONDB_USE_ABSPATH=False, BUILD_DIR=fw_build_meta["build_dir"], @@ -31,10 +32,6 @@ env = ENV.Clone( CPPPATH=[ "#/furi", *(f"#/{app_dir[0]}" for app_dir in ENV["APPDIRS"] if app_dir[1]), - "#/firmware/targets/f${TARGET_HW}/ble_glue", - "#/firmware/targets/f${TARGET_HW}/fatfs", - "#/firmware/targets/f${TARGET_HW}/furi_hal", - "#/firmware/targets/f${TARGET_HW}/Inc", "#/firmware/targets/furi_hal_include", ], # Specific flags for building libraries - always do optimized builds @@ -74,20 +71,6 @@ env = ENV.Clone( _APP_ICONS=None, ) - -def ApplyLibFlags(env): - flags_to_apply = env["FW_LIB_OPTS"].get( - env.get("FW_LIB_NAME"), - env["FW_LIB_OPTS"]["Default"], - ) - # print("Flags for ", env.get("FW_LIB_NAME", "Default"), flags_to_apply) - env.MergeFlags(flags_to_apply) - - -env.AddMethod(ApplyLibFlags) - -Export("env") - if env["IS_BASE_FIRMWARE"]: env.Append( FIRMWARE_BUILD_CFG="firmware", @@ -102,7 +85,11 @@ else: ], ) -# Invoke child SConscripts to populate global `env` + build their own part of the code +env.ConfigureForTarget(env.subst("${TARGET_HW}")) + +Export("env") + +# Invoke child SCopscripts to populate global `env` + build their own part of the code lib_targets = env.BuildModules( [ "lib", @@ -131,7 +118,7 @@ if extra_int_apps := GetOption("extra_int_apps"): fwenv.Append(APPS=extra_int_apps.split(",")) -for app_dir, _ in env["APPDIRS"]: +for app_dir, _ in fwenv["APPDIRS"]: app_dir_node = env.Dir("#").Dir(app_dir) for entry in app_dir_node.glob("*"): @@ -196,37 +183,11 @@ sources.extend( fwelf = fwenv["FW_ELF"] = fwenv.Program( "${FIRMWARE_BUILD_CFG}", sources, - LIBS=[ - "print", - "flipper${TARGET_HW}", - "furi", - "freertos", - "stm32cubewb", - "hwdrivers", - "fatfs", - "littlefs", - "subghz", - "flipperformat", - "toolbox", - "nfc", - "microtar", - "usb_stm32", - "st25rfal002", - "infrared", - "appframe", - "assets", - "misc", - "mbedtls", - "lfrfid", - "flipper_application", - # 2nd round - "flipperformat", - "toolbox", - ], + LIBS=fwenv["TARGET_CFG"].linker_dependencies, ) # Firmware depends on everything child builders returned -Depends(fwelf, lib_targets) +# Depends(fwelf, lib_targets) # Output extra details after building firmware AddPostAction(fwelf, fwenv["APPBUILD_DUMP"]) AddPostAction( diff --git a/firmware/SConscript b/firmware/SConscript index a16f14e65a3..fa96b0adf09 100644 --- a/firmware/SConscript +++ b/firmware/SConscript @@ -2,15 +2,6 @@ Import("env") env.Append( LINT_SOURCES=[Dir(".")], - SDK_HEADERS=[ - *env.GlobRecursive("*.h", "targets/furi_hal_include", "*_i.h"), - *env.GlobRecursive("*.h", "targets/f${TARGET_HW}/furi_hal", "*_i.h"), - File("targets/f7/platform_specific/intrinsic_export.h"), - ], -) - -env.SetDefault( - SDK_DEFINITION=env.File("./targets/f${TARGET_HW}/api_symbols.csv").srcnode() ) libenv = env.Clone(FW_LIB_NAME="flipper${TARGET_HW}") @@ -22,9 +13,9 @@ libenv.Append( libenv.ApplyLibFlags() -sources = ["targets/f${TARGET_HW}/startup_stm32wb55xx_cm4.s"] -sources += libenv.GlobRecursive("*.c") - -lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources) +lib = libenv.StaticLibrary( + "${FW_LIB_NAME}", + env.get("TARGET_CFG").gatherSources(), +) libenv.Install("${LIB_DIST_DIR}", lib) Return("lib") diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv new file mode 100644 index 00000000000..e4a7dfdd5ce --- /dev/null +++ b/firmware/targets/f18/api_symbols.csv @@ -0,0 +1,2307 @@ +entry,status,name,type,params +Version,+,12.1,, +Header,+,applications/services/bt/bt_service/bt.h,, +Header,+,applications/services/cli/cli.h,, +Header,+,applications/services/cli/cli_vcp.h,, +Header,+,applications/services/dialogs/dialogs.h,, +Header,+,applications/services/dolphin/dolphin.h,, +Header,+,applications/services/gui/elements.h,, +Header,+,applications/services/gui/gui.h,, +Header,+,applications/services/gui/icon_i.h,, +Header,+,applications/services/gui/modules/button_menu.h,, +Header,+,applications/services/gui/modules/button_panel.h,, +Header,+,applications/services/gui/modules/byte_input.h,, +Header,+,applications/services/gui/modules/dialog_ex.h,, +Header,+,applications/services/gui/modules/empty_screen.h,, +Header,+,applications/services/gui/modules/file_browser.h,, +Header,+,applications/services/gui/modules/file_browser_worker.h,, +Header,+,applications/services/gui/modules/loading.h,, +Header,+,applications/services/gui/modules/menu.h,, +Header,+,applications/services/gui/modules/popup.h,, +Header,+,applications/services/gui/modules/submenu.h,, +Header,+,applications/services/gui/modules/text_box.h,, +Header,+,applications/services/gui/modules/text_input.h,, +Header,+,applications/services/gui/modules/validators.h,, +Header,+,applications/services/gui/modules/variable_item_list.h,, +Header,+,applications/services/gui/modules/widget.h,, +Header,+,applications/services/gui/modules/widget_elements/widget_element.h,, +Header,+,applications/services/gui/view_dispatcher.h,, +Header,+,applications/services/gui/view_stack.h,, +Header,+,applications/services/input/input.h,, +Header,+,applications/services/loader/loader.h,, +Header,+,applications/services/locale/locale.h,, +Header,+,applications/services/notification/notification.h,, +Header,+,applications/services/notification/notification_messages.h,, +Header,+,applications/services/power/power_service/power.h,, +Header,+,applications/services/rpc/rpc_app.h,, +Header,+,applications/services/storage/storage.h,, +Header,+,firmware/targets/f18/furi_hal/furi_hal_resources.h,, +Header,+,firmware/targets/f18/furi_hal/furi_hal_spi_config.h,, +Header,+,firmware/targets/f18/furi_hal/furi_hal_target_hw.h,, +Header,+,firmware/targets/f7/furi_hal/furi_hal_clock.h,, +Header,+,firmware/targets/f7/furi_hal/furi_hal_console.h,, +Header,+,firmware/targets/f7/furi_hal/furi_hal_flash.h,, +Header,+,firmware/targets/f7/furi_hal/furi_hal_gpio.h,, +Header,+,firmware/targets/f7/furi_hal/furi_hal_i2c_config.h,, +Header,+,firmware/targets/f7/furi_hal/furi_hal_i2c_types.h,, +Header,+,firmware/targets/f7/furi_hal/furi_hal_idle_timer.h,, +Header,+,firmware/targets/f7/furi_hal/furi_hal_interrupt.h,, +Header,+,firmware/targets/f7/furi_hal/furi_hal_os.h,, +Header,+,firmware/targets/f7/furi_hal/furi_hal_pwm.h,, +Header,+,firmware/targets/f7/furi_hal/furi_hal_spi_types.h,, +Header,+,firmware/targets/f7/furi_hal/furi_hal_uart.h,, +Header,+,firmware/targets/f7/furi_hal/furi_hal_usb_cdc.h,, +Header,+,firmware/targets/f7/platform_specific/intrinsic_export.h,, +Header,+,firmware/targets/furi_hal_include/furi_hal.h,, +Header,+,firmware/targets/furi_hal_include/furi_hal_bt.h,, +Header,+,firmware/targets/furi_hal_include/furi_hal_bt_hid.h,, +Header,+,firmware/targets/furi_hal_include/furi_hal_bt_serial.h,, +Header,+,firmware/targets/furi_hal_include/furi_hal_compress.h,, +Header,+,firmware/targets/furi_hal_include/furi_hal_cortex.h,, +Header,+,firmware/targets/furi_hal_include/furi_hal_crypto.h,, +Header,+,firmware/targets/furi_hal_include/furi_hal_debug.h,, +Header,+,firmware/targets/furi_hal_include/furi_hal_i2c.h,, +Header,+,firmware/targets/furi_hal_include/furi_hal_info.h,, +Header,+,firmware/targets/furi_hal_include/furi_hal_light.h,, +Header,+,firmware/targets/furi_hal_include/furi_hal_memory.h,, +Header,+,firmware/targets/furi_hal_include/furi_hal_mpu.h,, +Header,+,firmware/targets/furi_hal_include/furi_hal_power.h,, +Header,+,firmware/targets/furi_hal_include/furi_hal_random.h,, +Header,+,firmware/targets/furi_hal_include/furi_hal_region.h,, +Header,+,firmware/targets/furi_hal_include/furi_hal_rtc.h,, +Header,+,firmware/targets/furi_hal_include/furi_hal_sd.h,, +Header,+,firmware/targets/furi_hal_include/furi_hal_speaker.h,, +Header,+,firmware/targets/furi_hal_include/furi_hal_spi.h,, +Header,+,firmware/targets/furi_hal_include/furi_hal_usb.h,, +Header,+,firmware/targets/furi_hal_include/furi_hal_usb_hid.h,, +Header,+,firmware/targets/furi_hal_include/furi_hal_usb_hid_u2f.h,, +Header,+,firmware/targets/furi_hal_include/furi_hal_version.h,, +Header,+,firmware/targets/furi_hal_include/furi_hal_vibro.h,, +Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_adc.h,, +Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_bus.h,, +Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_comp.h,, +Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_cortex.h,, +Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_crc.h,, +Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_crs.h,, +Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_dma.h,, +Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_dmamux.h,, +Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_exti.h,, +Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_gpio.h,, +Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_hsem.h,, +Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_i2c.h,, +Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_ipcc.h,, +Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_iwdg.h,, +Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_lptim.h,, +Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_lpuart.h,, +Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_pka.h,, +Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_pwr.h,, +Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_rcc.h,, +Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_rng.h,, +Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_rtc.h,, +Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_spi.h,, +Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_system.h,, +Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_tim.h,, +Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_usart.h,, +Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_utils.h,, +Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_wwdg.h,, +Header,+,lib/flipper_application/flipper_application.h,, +Header,+,lib/flipper_format/flipper_format.h,, +Header,+,lib/flipper_format/flipper_format_i.h,, +Header,+,lib/libusb_stm32/inc/hid_usage_button.h,, +Header,+,lib/libusb_stm32/inc/hid_usage_consumer.h,, +Header,+,lib/libusb_stm32/inc/hid_usage_desktop.h,, +Header,+,lib/libusb_stm32/inc/hid_usage_device.h,, +Header,+,lib/libusb_stm32/inc/hid_usage_game.h,, +Header,+,lib/libusb_stm32/inc/hid_usage_keyboard.h,, +Header,+,lib/libusb_stm32/inc/hid_usage_led.h,, +Header,+,lib/libusb_stm32/inc/hid_usage_ordinal.h,, +Header,+,lib/libusb_stm32/inc/hid_usage_power.h,, +Header,+,lib/libusb_stm32/inc/hid_usage_simulation.h,, +Header,+,lib/libusb_stm32/inc/hid_usage_sport.h,, +Header,+,lib/libusb_stm32/inc/hid_usage_telephony.h,, +Header,+,lib/libusb_stm32/inc/hid_usage_vr.h,, +Header,-,lib/libusb_stm32/inc/stm32_compat.h,, +Header,+,lib/libusb_stm32/inc/usb.h,, +Header,+,lib/libusb_stm32/inc/usb_cdc.h,, +Header,+,lib/libusb_stm32/inc/usb_cdca.h,, +Header,+,lib/libusb_stm32/inc/usb_cdce.h,, +Header,+,lib/libusb_stm32/inc/usb_cdci.h,, +Header,+,lib/libusb_stm32/inc/usb_cdcp.h,, +Header,+,lib/libusb_stm32/inc/usb_cdcw.h,, +Header,+,lib/libusb_stm32/inc/usb_dfu.h,, +Header,+,lib/libusb_stm32/inc/usb_hid.h,, +Header,+,lib/libusb_stm32/inc/usb_std.h,, +Header,+,lib/libusb_stm32/inc/usb_tmc.h,, +Header,+,lib/libusb_stm32/inc/usbd_core.h,, +Header,+,lib/mbedtls/include/mbedtls/des.h,, +Header,+,lib/mbedtls/include/mbedtls/sha1.h,, +Header,+,lib/micro-ecc/uECC.h,, +Header,+,lib/mlib/m-algo.h,, +Header,+,lib/mlib/m-array.h,, +Header,+,lib/mlib/m-bptree.h,, +Header,+,lib/mlib/m-core.h,, +Header,+,lib/mlib/m-deque.h,, +Header,+,lib/mlib/m-dict.h,, +Header,+,lib/mlib/m-list.h,, +Header,+,lib/mlib/m-rbtree.h,, +Header,+,lib/mlib/m-tuple.h,, +Header,+,lib/mlib/m-variant.h,, +Header,+,lib/print/wrappers.h,, +Header,+,lib/toolbox/args.h,, +Header,+,lib/toolbox/crc32_calc.h,, +Header,+,lib/toolbox/dir_walk.h,, +Header,+,lib/toolbox/float_tools.h,, +Header,+,lib/toolbox/hmac_sha256.h,, +Header,+,lib/toolbox/manchester_decoder.h,, +Header,+,lib/toolbox/manchester_encoder.h,, +Header,+,lib/toolbox/md5.h,, +Header,+,lib/toolbox/path.h,, +Header,+,lib/toolbox/protocols/protocol_dict.h,, +Header,+,lib/toolbox/random_name.h,, +Header,+,lib/toolbox/saved_struct.h,, +Header,+,lib/toolbox/stream/buffered_file_stream.h,, +Header,+,lib/toolbox/stream/file_stream.h,, +Header,+,lib/toolbox/stream/stream.h,, +Header,+,lib/toolbox/stream/string_stream.h,, +Header,+,lib/toolbox/tar/tar_archive.h,, +Header,+,lib/toolbox/value_index.h,, +Header,+,lib/toolbox/version.h,, +Function,-,LL_ADC_CommonDeInit,ErrorStatus,ADC_Common_TypeDef* +Function,-,LL_ADC_CommonInit,ErrorStatus,"ADC_Common_TypeDef*, LL_ADC_CommonInitTypeDef*" +Function,-,LL_ADC_CommonStructInit,void,LL_ADC_CommonInitTypeDef* +Function,-,LL_ADC_DeInit,ErrorStatus,ADC_TypeDef* +Function,-,LL_ADC_INJ_Init,ErrorStatus,"ADC_TypeDef*, LL_ADC_INJ_InitTypeDef*" +Function,-,LL_ADC_INJ_StructInit,void,LL_ADC_INJ_InitTypeDef* +Function,-,LL_ADC_Init,ErrorStatus,"ADC_TypeDef*, LL_ADC_InitTypeDef*" +Function,-,LL_ADC_REG_Init,ErrorStatus,"ADC_TypeDef*, LL_ADC_REG_InitTypeDef*" +Function,-,LL_ADC_REG_StructInit,void,LL_ADC_REG_InitTypeDef* +Function,-,LL_ADC_StructInit,void,LL_ADC_InitTypeDef* +Function,-,LL_COMP_DeInit,ErrorStatus,COMP_TypeDef* +Function,+,LL_COMP_Init,ErrorStatus,"COMP_TypeDef*, LL_COMP_InitTypeDef*" +Function,-,LL_COMP_StructInit,void,LL_COMP_InitTypeDef* +Function,-,LL_CRC_DeInit,ErrorStatus,CRC_TypeDef* +Function,-,LL_CRS_DeInit,ErrorStatus, +Function,+,LL_DMA_DeInit,ErrorStatus,"DMA_TypeDef*, uint32_t" +Function,+,LL_DMA_Init,ErrorStatus,"DMA_TypeDef*, uint32_t, LL_DMA_InitTypeDef*" +Function,-,LL_DMA_StructInit,void,LL_DMA_InitTypeDef* +Function,-,LL_EXTI_DeInit,ErrorStatus, +Function,-,LL_EXTI_Init,ErrorStatus,LL_EXTI_InitTypeDef* +Function,-,LL_EXTI_StructInit,void,LL_EXTI_InitTypeDef* +Function,-,LL_GPIO_DeInit,ErrorStatus,GPIO_TypeDef* +Function,+,LL_GPIO_Init,ErrorStatus,"GPIO_TypeDef*, LL_GPIO_InitTypeDef*" +Function,-,LL_GPIO_StructInit,void,LL_GPIO_InitTypeDef* +Function,-,LL_I2C_DeInit,ErrorStatus,I2C_TypeDef* +Function,+,LL_I2C_Init,ErrorStatus,"I2C_TypeDef*, LL_I2C_InitTypeDef*" +Function,-,LL_I2C_StructInit,void,LL_I2C_InitTypeDef* +Function,-,LL_Init1msTick,void,uint32_t +Function,+,LL_LPTIM_DeInit,ErrorStatus,LPTIM_TypeDef* +Function,-,LL_LPTIM_Disable,void,LPTIM_TypeDef* +Function,+,LL_LPTIM_Init,ErrorStatus,"LPTIM_TypeDef*, LL_LPTIM_InitTypeDef*" +Function,-,LL_LPTIM_StructInit,void,LL_LPTIM_InitTypeDef* +Function,-,LL_LPUART_DeInit,ErrorStatus,USART_TypeDef* +Function,+,LL_LPUART_Init,ErrorStatus,"USART_TypeDef*, LL_LPUART_InitTypeDef*" +Function,-,LL_LPUART_StructInit,void,LL_LPUART_InitTypeDef* +Function,-,LL_PKA_DeInit,ErrorStatus,PKA_TypeDef* +Function,-,LL_PKA_Init,ErrorStatus,"PKA_TypeDef*, LL_PKA_InitTypeDef*" +Function,-,LL_PKA_StructInit,void,LL_PKA_InitTypeDef* +Function,-,LL_PLL_ConfigSystemClock_HSE,ErrorStatus,"uint32_t, LL_UTILS_PLLInitTypeDef*, LL_UTILS_ClkInitTypeDef*" +Function,-,LL_PLL_ConfigSystemClock_HSI,ErrorStatus,"LL_UTILS_PLLInitTypeDef*, LL_UTILS_ClkInitTypeDef*" +Function,-,LL_PLL_ConfigSystemClock_MSI,ErrorStatus,"LL_UTILS_PLLInitTypeDef*, LL_UTILS_ClkInitTypeDef*" +Function,-,LL_PWR_DeInit,ErrorStatus, +Function,-,LL_RCC_DeInit,ErrorStatus, +Function,-,LL_RCC_GetADCClockFreq,uint32_t,uint32_t +Function,-,LL_RCC_GetCLK48ClockFreq,uint32_t,uint32_t +Function,-,LL_RCC_GetI2CClockFreq,uint32_t,uint32_t +Function,-,LL_RCC_GetLPTIMClockFreq,uint32_t,uint32_t +Function,+,LL_RCC_GetLPUARTClockFreq,uint32_t,uint32_t +Function,-,LL_RCC_GetRFWKPClockFreq,uint32_t, +Function,-,LL_RCC_GetRNGClockFreq,uint32_t,uint32_t +Function,-,LL_RCC_GetRTCClockFreq,uint32_t, +Function,-,LL_RCC_GetSAIClockFreq,uint32_t,uint32_t +Function,-,LL_RCC_GetSMPSClockFreq,uint32_t, +Function,-,LL_RCC_GetSystemClocksFreq,void,LL_RCC_ClocksTypeDef* +Function,+,LL_RCC_GetUSARTClockFreq,uint32_t,uint32_t +Function,-,LL_RCC_GetUSBClockFreq,uint32_t,uint32_t +Function,-,LL_RNG_DeInit,ErrorStatus,RNG_TypeDef* +Function,-,LL_RNG_Init,ErrorStatus,"RNG_TypeDef*, LL_RNG_InitTypeDef*" +Function,-,LL_RNG_StructInit,void,LL_RNG_InitTypeDef* +Function,-,LL_RTC_ALMA_Init,ErrorStatus,"RTC_TypeDef*, uint32_t, LL_RTC_AlarmTypeDef*" +Function,-,LL_RTC_ALMA_StructInit,void,LL_RTC_AlarmTypeDef* +Function,-,LL_RTC_ALMB_Init,ErrorStatus,"RTC_TypeDef*, uint32_t, LL_RTC_AlarmTypeDef*" +Function,-,LL_RTC_ALMB_StructInit,void,LL_RTC_AlarmTypeDef* +Function,-,LL_RTC_DATE_Init,ErrorStatus,"RTC_TypeDef*, uint32_t, LL_RTC_DateTypeDef*" +Function,-,LL_RTC_DATE_StructInit,void,LL_RTC_DateTypeDef* +Function,-,LL_RTC_DeInit,ErrorStatus,RTC_TypeDef* +Function,+,LL_RTC_EnterInitMode,ErrorStatus,RTC_TypeDef* +Function,-,LL_RTC_ExitInitMode,ErrorStatus,RTC_TypeDef* +Function,+,LL_RTC_Init,ErrorStatus,"RTC_TypeDef*, LL_RTC_InitTypeDef*" +Function,-,LL_RTC_StructInit,void,LL_RTC_InitTypeDef* +Function,-,LL_RTC_TIME_Init,ErrorStatus,"RTC_TypeDef*, uint32_t, LL_RTC_TimeTypeDef*" +Function,-,LL_RTC_TIME_StructInit,void,LL_RTC_TimeTypeDef* +Function,-,LL_RTC_WaitForSynchro,ErrorStatus,RTC_TypeDef* +Function,-,LL_SPI_DeInit,ErrorStatus,SPI_TypeDef* +Function,+,LL_SPI_Init,ErrorStatus,"SPI_TypeDef*, LL_SPI_InitTypeDef*" +Function,-,LL_SPI_StructInit,void,LL_SPI_InitTypeDef* +Function,-,LL_SetFlashLatency,ErrorStatus,uint32_t +Function,+,LL_SetSystemCoreClock,void,uint32_t +Function,-,LL_TIM_BDTR_Init,ErrorStatus,"TIM_TypeDef*, LL_TIM_BDTR_InitTypeDef*" +Function,-,LL_TIM_BDTR_StructInit,void,LL_TIM_BDTR_InitTypeDef* +Function,+,LL_TIM_DeInit,ErrorStatus,TIM_TypeDef* +Function,-,LL_TIM_ENCODER_Init,ErrorStatus,"TIM_TypeDef*, LL_TIM_ENCODER_InitTypeDef*" +Function,-,LL_TIM_ENCODER_StructInit,void,LL_TIM_ENCODER_InitTypeDef* +Function,-,LL_TIM_HALLSENSOR_Init,ErrorStatus,"TIM_TypeDef*, LL_TIM_HALLSENSOR_InitTypeDef*" +Function,-,LL_TIM_HALLSENSOR_StructInit,void,LL_TIM_HALLSENSOR_InitTypeDef* +Function,-,LL_TIM_IC_Init,ErrorStatus,"TIM_TypeDef*, uint32_t, LL_TIM_IC_InitTypeDef*" +Function,-,LL_TIM_IC_StructInit,void,LL_TIM_IC_InitTypeDef* +Function,+,LL_TIM_Init,ErrorStatus,"TIM_TypeDef*, LL_TIM_InitTypeDef*" +Function,+,LL_TIM_OC_Init,ErrorStatus,"TIM_TypeDef*, uint32_t, LL_TIM_OC_InitTypeDef*" +Function,-,LL_TIM_OC_StructInit,void,LL_TIM_OC_InitTypeDef* +Function,-,LL_TIM_StructInit,void,LL_TIM_InitTypeDef* +Function,-,LL_USART_ClockInit,ErrorStatus,"USART_TypeDef*, LL_USART_ClockInitTypeDef*" +Function,-,LL_USART_ClockStructInit,void,LL_USART_ClockInitTypeDef* +Function,-,LL_USART_DeInit,ErrorStatus,USART_TypeDef* +Function,+,LL_USART_Init,ErrorStatus,"USART_TypeDef*, LL_USART_InitTypeDef*" +Function,-,LL_USART_StructInit,void,LL_USART_InitTypeDef* +Function,-,LL_mDelay,void,uint32_t +Function,-,SystemCoreClockUpdate,void, +Function,-,SystemInit,void, +Function,-,_Exit,void,int +Function,-,__assert,void,"const char*, int, const char*" +Function,+,__assert_func,void,"const char*, int, const char*, const char*" +Function,+,__clear_cache,void,"void*, void*" +Function,-,__eprintf,void,"const char*, const char*, unsigned int, const char*" +Function,+,__errno,int*, +Function,+,__furi_crash,void, +Function,+,__furi_halt,void, +Function,-,__getdelim,ssize_t,"char**, size_t*, int, FILE*" +Function,-,__getline,ssize_t,"char**, size_t*, FILE*" +Function,-,__itoa,char*,"int, char*, int" +Function,-,__locale_mb_cur_max,int, +Function,+,__retarget_lock_acquire,void,_LOCK_T +Function,+,__retarget_lock_acquire_recursive,void,_LOCK_T +Function,-,__retarget_lock_close,void,_LOCK_T +Function,+,__retarget_lock_close_recursive,void,_LOCK_T +Function,-,__retarget_lock_init,void,_LOCK_T* +Function,+,__retarget_lock_init_recursive,void,_LOCK_T* +Function,+,__retarget_lock_release,void,_LOCK_T +Function,+,__retarget_lock_release_recursive,void,_LOCK_T +Function,-,__retarget_lock_try_acquire,int,_LOCK_T +Function,-,__retarget_lock_try_acquire_recursive,int,_LOCK_T +Function,-,__srget_r,int,"_reent*, FILE*" +Function,-,__swbuf_r,int,"_reent*, int, FILE*" +Function,-,__utoa,char*,"unsigned, char*, int" +Function,+,__wrap___assert,void,"const char*, int, const char*" +Function,+,__wrap___assert_func,void,"const char*, int, const char*, const char*" +Function,+,__wrap_fflush,int,FILE* +Function,+,__wrap_printf,int,"const char*, ..." +Function,+,__wrap_putc,int,"int, FILE*" +Function,+,__wrap_putchar,int,int +Function,+,__wrap_puts,int,const char* +Function,+,__wrap_snprintf,int,"char*, size_t, const char*, ..." +Function,+,__wrap_vsnprintf,int,"char*, size_t, const char*, va_list" +Function,-,_asiprintf_r,int,"_reent*, char**, const char*, ..." +Function,-,_asniprintf_r,char*,"_reent*, char*, size_t*, const char*, ..." +Function,-,_asnprintf_r,char*,"_reent*, char*, size_t*, const char*, ..." +Function,-,_asprintf_r,int,"_reent*, char**, const char*, ..." +Function,-,_atoi_r,int,"_reent*, const char*" +Function,-,_atol_r,long,"_reent*, const char*" +Function,-,_atoll_r,long long,"_reent*, const char*" +Function,-,_calloc_r,void*,"_reent*, size_t, size_t" +Function,-,_diprintf_r,int,"_reent*, int, const char*, ..." +Function,-,_dprintf_r,int,"_reent*, int, const char*, ..." +Function,-,_drand48_r,double,_reent* +Function,-,_dtoa_r,char*,"_reent*, double, int, int, int*, int*, char**" +Function,-,_erand48_r,double,"_reent*, unsigned short[3]" +Function,-,_fclose_r,int,"_reent*, FILE*" +Function,-,_fcloseall_r,int,_reent* +Function,-,_fdopen_r,FILE*,"_reent*, int, const char*" +Function,-,_fflush_r,int,"_reent*, FILE*" +Function,-,_fgetc_r,int,"_reent*, FILE*" +Function,-,_fgetc_unlocked_r,int,"_reent*, FILE*" +Function,-,_fgetpos_r,int,"_reent*, FILE*, fpos_t*" +Function,-,_fgets_r,char*,"_reent*, char*, int, FILE*" +Function,-,_fgets_unlocked_r,char*,"_reent*, char*, int, FILE*" +Function,-,_findenv,char*,"const char*, int*" +Function,-,_findenv_r,char*,"_reent*, const char*, int*" +Function,-,_fiprintf_r,int,"_reent*, FILE*, const char*, ..." +Function,-,_fiscanf_r,int,"_reent*, FILE*, const char*, ..." +Function,-,_fmemopen_r,FILE*,"_reent*, void*, size_t, const char*" +Function,-,_fopen_r,FILE*,"_reent*, const char*, const char*" +Function,-,_fopencookie_r,FILE*,"_reent*, void*, const char*, cookie_io_functions_t" +Function,-,_fprintf_r,int,"_reent*, FILE*, const char*, ..." +Function,-,_fpurge_r,int,"_reent*, FILE*" +Function,-,_fputc_r,int,"_reent*, int, FILE*" +Function,-,_fputc_unlocked_r,int,"_reent*, int, FILE*" +Function,-,_fputs_r,int,"_reent*, const char*, FILE*" +Function,-,_fputs_unlocked_r,int,"_reent*, const char*, FILE*" +Function,-,_fread_r,size_t,"_reent*, void*, size_t, size_t, FILE*" +Function,-,_fread_unlocked_r,size_t,"_reent*, void*, size_t, size_t, FILE*" +Function,-,_free_r,void,"_reent*, void*" +Function,-,_freopen_r,FILE*,"_reent*, const char*, const char*, FILE*" +Function,-,_fscanf_r,int,"_reent*, FILE*, const char*, ..." +Function,-,_fseek_r,int,"_reent*, FILE*, long, int" +Function,-,_fseeko_r,int,"_reent*, FILE*, _off_t, int" +Function,-,_fsetpos_r,int,"_reent*, FILE*, const fpos_t*" +Function,-,_ftell_r,long,"_reent*, FILE*" +Function,-,_ftello_r,_off_t,"_reent*, FILE*" +Function,-,_funopen_r,FILE*,"_reent*, const void*, int (*)(void*, char*, int), int (*)(void*, const char*, int), fpos_t (*)(void*, fpos_t, int), int (*)(void*)" +Function,-,_fwrite_r,size_t,"_reent*, const void*, size_t, size_t, FILE*" +Function,-,_fwrite_unlocked_r,size_t,"_reent*, const void*, size_t, size_t, FILE*" +Function,-,_getc_r,int,"_reent*, FILE*" +Function,-,_getc_unlocked_r,int,"_reent*, FILE*" +Function,-,_getchar_r,int,_reent* +Function,-,_getchar_unlocked_r,int,_reent* +Function,-,_getenv_r,char*,"_reent*, const char*" +Function,-,_gets_r,char*,"_reent*, char*" +Function,-,_iprintf_r,int,"_reent*, const char*, ..." +Function,-,_iscanf_r,int,"_reent*, const char*, ..." +Function,-,_jrand48_r,long,"_reent*, unsigned short[3]" +Function,-,_l64a_r,char*,"_reent*, long" +Function,-,_lcong48_r,void,"_reent*, unsigned short[7]" +Function,-,_lrand48_r,long,_reent* +Function,-,_malloc_r,void*,"_reent*, size_t" +Function,-,_mblen_r,int,"_reent*, const char*, size_t, _mbstate_t*" +Function,-,_mbstowcs_r,size_t,"_reent*, wchar_t*, const char*, size_t, _mbstate_t*" +Function,-,_mbtowc_r,int,"_reent*, wchar_t*, const char*, size_t, _mbstate_t*" +Function,-,_mkdtemp_r,char*,"_reent*, char*" +Function,-,_mkostemp_r,int,"_reent*, char*, int" +Function,-,_mkostemps_r,int,"_reent*, char*, int, int" +Function,-,_mkstemp_r,int,"_reent*, char*" +Function,-,_mkstemps_r,int,"_reent*, char*, int" +Function,-,_mktemp_r,char*,"_reent*, char*" +Function,-,_mrand48_r,long,_reent* +Function,-,_mstats_r,void,"_reent*, char*" +Function,-,_nrand48_r,long,"_reent*, unsigned short[3]" +Function,-,_open_memstream_r,FILE*,"_reent*, char**, size_t*" +Function,-,_perror_r,void,"_reent*, const char*" +Function,-,_printf_r,int,"_reent*, const char*, ..." +Function,-,_putc_r,int,"_reent*, int, FILE*" +Function,-,_putc_unlocked_r,int,"_reent*, int, FILE*" +Function,-,_putchar,void,char +Function,-,_putchar_r,int,"_reent*, int" +Function,-,_putchar_unlocked_r,int,"_reent*, int" +Function,-,_putenv_r,int,"_reent*, char*" +Function,-,_puts_r,int,"_reent*, const char*" +Function,-,_realloc_r,void*,"_reent*, void*, size_t" +Function,-,_reallocf_r,void*,"_reent*, void*, size_t" +Function,-,_reclaim_reent,void,_reent* +Function,-,_remove_r,int,"_reent*, const char*" +Function,-,_rename_r,int,"_reent*, const char*, const char*" +Function,-,_rewind_r,void,"_reent*, FILE*" +Function,-,_scanf_r,int,"_reent*, const char*, ..." +Function,-,_seed48_r,unsigned short*,"_reent*, unsigned short[3]" +Function,-,_setenv_r,int,"_reent*, const char*, const char*, int" +Function,-,_siprintf_r,int,"_reent*, char*, const char*, ..." +Function,-,_siscanf_r,int,"_reent*, const char*, const char*, ..." +Function,-,_sniprintf_r,int,"_reent*, char*, size_t, const char*, ..." +Function,-,_snprintf_r,int,"_reent*, char*, size_t, const char*, ..." +Function,-,_sprintf_r,int,"_reent*, char*, const char*, ..." +Function,-,_srand48_r,void,"_reent*, long" +Function,-,_sscanf_r,int,"_reent*, const char*, const char*, ..." +Function,-,_strdup_r,char*,"_reent*, const char*" +Function,-,_strerror_r,char*,"_reent*, int, int, int*" +Function,-,_strndup_r,char*,"_reent*, const char*, size_t" +Function,-,_strtod_r,double,"_reent*, const char*, char**" +Function,-,_strtol_r,long,"_reent*, const char*, char**, int" +Function,-,_strtold_r,long double,"_reent*, const char*, char**" +Function,-,_strtoll_r,long long,"_reent*, const char*, char**, int" +Function,-,_strtoul_r,unsigned long,"_reent*, const char*, char**, int" +Function,-,_strtoull_r,unsigned long long,"_reent*, const char*, char**, int" +Function,-,_system_r,int,"_reent*, const char*" +Function,-,_tempnam_r,char*,"_reent*, const char*, const char*" +Function,-,_tmpfile_r,FILE*,_reent* +Function,-,_tmpnam_r,char*,"_reent*, char*" +Function,-,_tzset_r,void,_reent* +Function,-,_ungetc_r,int,"_reent*, int, FILE*" +Function,-,_unsetenv_r,int,"_reent*, const char*" +Function,-,_vasiprintf_r,int,"_reent*, char**, const char*, __gnuc_va_list" +Function,-,_vasniprintf_r,char*,"_reent*, char*, size_t*, const char*, __gnuc_va_list" +Function,-,_vasnprintf_r,char*,"_reent*, char*, size_t*, const char*, __gnuc_va_list" +Function,-,_vasprintf_r,int,"_reent*, char**, const char*, __gnuc_va_list" +Function,-,_vdiprintf_r,int,"_reent*, int, const char*, __gnuc_va_list" +Function,-,_vdprintf_r,int,"_reent*, int, const char*, __gnuc_va_list" +Function,-,_vfiprintf_r,int,"_reent*, FILE*, const char*, __gnuc_va_list" +Function,-,_vfiscanf_r,int,"_reent*, FILE*, const char*, __gnuc_va_list" +Function,-,_vfprintf_r,int,"_reent*, FILE*, const char*, __gnuc_va_list" +Function,-,_vfscanf_r,int,"_reent*, FILE*, const char*, __gnuc_va_list" +Function,-,_viprintf_r,int,"_reent*, const char*, __gnuc_va_list" +Function,-,_viscanf_r,int,"_reent*, const char*, __gnuc_va_list" +Function,-,_vprintf_r,int,"_reent*, const char*, __gnuc_va_list" +Function,-,_vscanf_r,int,"_reent*, const char*, __gnuc_va_list" +Function,-,_vsiprintf_r,int,"_reent*, char*, const char*, __gnuc_va_list" +Function,-,_vsiscanf_r,int,"_reent*, const char*, const char*, __gnuc_va_list" +Function,-,_vsniprintf_r,int,"_reent*, char*, size_t, const char*, __gnuc_va_list" +Function,-,_vsnprintf_r,int,"_reent*, char*, size_t, const char*, __gnuc_va_list" +Function,-,_vsprintf_r,int,"_reent*, char*, const char*, __gnuc_va_list" +Function,-,_vsscanf_r,int,"_reent*, const char*, const char*, __gnuc_va_list" +Function,-,_wcstombs_r,size_t,"_reent*, char*, const wchar_t*, size_t, _mbstate_t*" +Function,-,_wctomb_r,int,"_reent*, char*, wchar_t, _mbstate_t*" +Function,-,a64l,long,const char* +Function,+,abort,void, +Function,-,abs,int,int +Function,+,acquire_mutex,void*,"ValueMutex*, uint32_t" +Function,-,aligned_alloc,void*,"size_t, size_t" +Function,+,aligned_free,void,void* +Function,+,aligned_malloc,void*,"size_t, size_t" +Function,-,arc4random,__uint32_t, +Function,-,arc4random_buf,void,"void*, size_t" +Function,-,arc4random_uniform,__uint32_t,__uint32_t +Function,+,args_char_to_hex,_Bool,"char, char, uint8_t*" +Function,+,args_get_first_word_length,size_t,FuriString* +Function,+,args_length,size_t,FuriString* +Function,+,args_read_hex_bytes,_Bool,"FuriString*, uint8_t*, size_t" +Function,+,args_read_int_and_trim,_Bool,"FuriString*, int*" +Function,+,args_read_probably_quoted_string_and_trim,_Bool,"FuriString*, FuriString*" +Function,+,args_read_string_and_trim,_Bool,"FuriString*, FuriString*" +Function,-,asctime,char*,const tm* +Function,-,asctime_r,char*,"const tm*, char*" +Function,-,asiprintf,int,"char**, const char*, ..." +Function,-,asniprintf,char*,"char*, size_t*, const char*, ..." +Function,-,asnprintf,char*,"char*, size_t*, const char*, ..." +Function,-,asprintf,int,"char**, const char*, ..." +Function,-,at_quick_exit,int,void (*)() +Function,-,atexit,int,void (*)() +Function,-,atof,double,const char* +Function,-,atoff,float,const char* +Function,+,atoi,int,const char* +Function,-,atol,long,const char* +Function,-,atoll,long long,const char* +Function,-,basename,char*,const char* +Function,-,bcmp,int,"const void*, const void*, size_t" +Function,-,bcopy,void,"const void*, void*, size_t" +Function,+,ble_app_get_key_storage_buff,void,"uint8_t**, uint16_t*" +Function,+,ble_app_init,_Bool, +Function,+,ble_app_thread_stop,void, +Function,+,ble_glue_force_c2_mode,BleGlueCommandResult,BleGlueC2Mode +Function,-,ble_glue_fus_get_status,BleGlueCommandResult, +Function,-,ble_glue_fus_stack_delete,BleGlueCommandResult, +Function,-,ble_glue_fus_stack_install,BleGlueCommandResult,"uint32_t, uint32_t" +Function,-,ble_glue_fus_wait_operation,BleGlueCommandResult, +Function,+,ble_glue_get_c2_info,const BleGlueC2Info*, +Function,-,ble_glue_get_c2_status,BleGlueStatus, +Function,+,ble_glue_init,void, +Function,+,ble_glue_is_alive,_Bool, +Function,+,ble_glue_is_radio_stack_ready,_Bool, +Function,+,ble_glue_reinit_c2,_Bool, +Function,+,ble_glue_set_key_storage_changed_callback,void,"BleGlueKeyStorageChangedCallback, void*" +Function,+,ble_glue_start,_Bool, +Function,+,ble_glue_thread_stop,void, +Function,+,ble_glue_wait_for_c2_start,_Bool,int32_t +Function,-,bsearch,void*,"const void*, const void*, size_t, size_t, __compar_fn_t" +Function,+,bt_disconnect,void,Bt* +Function,+,bt_forget_bonded_devices,void,Bt* +Function,+,bt_keys_storage_set_default_path,void,Bt* +Function,+,bt_keys_storage_set_storage_path,void,"Bt*, const char*" +Function,+,bt_set_profile,_Bool,"Bt*, BtProfile" +Function,+,bt_set_status_changed_callback,void,"Bt*, BtStatusChangedCallback, void*" +Function,+,buffered_file_stream_alloc,Stream*,Storage* +Function,+,buffered_file_stream_close,_Bool,Stream* +Function,+,buffered_file_stream_get_error,FS_Error,Stream* +Function,+,buffered_file_stream_open,_Bool,"Stream*, const char*, FS_AccessMode, FS_OpenMode" +Function,+,buffered_file_stream_sync,_Bool,Stream* +Function,+,button_menu_add_item,ButtonMenuItem*,"ButtonMenu*, const char*, int32_t, ButtonMenuItemCallback, ButtonMenuItemType, void*" +Function,+,button_menu_alloc,ButtonMenu*, +Function,+,button_menu_free,void,ButtonMenu* +Function,+,button_menu_get_view,View*,ButtonMenu* +Function,+,button_menu_reset,void,ButtonMenu* +Function,+,button_menu_set_header,void,"ButtonMenu*, const char*" +Function,+,button_menu_set_selected_item,void,"ButtonMenu*, uint32_t" +Function,+,button_panel_add_item,void,"ButtonPanel*, uint32_t, uint16_t, uint16_t, uint16_t, uint16_t, const Icon*, const Icon*, ButtonItemCallback, void*" +Function,+,button_panel_add_label,void,"ButtonPanel*, uint16_t, uint16_t, Font, const char*" +Function,+,button_panel_alloc,ButtonPanel*, +Function,+,button_panel_free,void,ButtonPanel* +Function,+,button_panel_get_view,View*,ButtonPanel* +Function,+,button_panel_reserve,void,"ButtonPanel*, size_t, size_t" +Function,+,button_panel_reset,void,ButtonPanel* +Function,+,byte_input_alloc,ByteInput*, +Function,+,byte_input_free,void,ByteInput* +Function,+,byte_input_get_view,View*,ByteInput* +Function,+,byte_input_set_header_text,void,"ByteInput*, const char*" +Function,+,byte_input_set_result_callback,void,"ByteInput*, ByteInputCallback, ByteChangedCallback, void*, uint8_t*, uint8_t" +Function,-,bzero,void,"void*, size_t" +Function,-,calloc,void*,"size_t, size_t" +Function,+,canvas_clear,void,Canvas* +Function,+,canvas_commit,void,Canvas* +Function,+,canvas_current_font_height,uint8_t,Canvas* +Function,+,canvas_draw_bitmap,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, const uint8_t*" +Function,+,canvas_draw_box,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t" +Function,+,canvas_draw_circle,void,"Canvas*, uint8_t, uint8_t, uint8_t" +Function,+,canvas_draw_disc,void,"Canvas*, uint8_t, uint8_t, uint8_t" +Function,+,canvas_draw_dot,void,"Canvas*, uint8_t, uint8_t" +Function,+,canvas_draw_frame,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t" +Function,+,canvas_draw_glyph,void,"Canvas*, uint8_t, uint8_t, uint16_t" +Function,+,canvas_draw_icon,void,"Canvas*, uint8_t, uint8_t, const Icon*" +Function,+,canvas_draw_icon_animation,void,"Canvas*, uint8_t, uint8_t, IconAnimation*" +Function,+,canvas_draw_line,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t" +Function,+,canvas_draw_rbox,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t" +Function,+,canvas_draw_rframe,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t" +Function,+,canvas_draw_str,void,"Canvas*, uint8_t, uint8_t, const char*" +Function,+,canvas_draw_str_aligned,void,"Canvas*, uint8_t, uint8_t, Align, Align, const char*" +Function,+,canvas_draw_triangle,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, CanvasDirection" +Function,+,canvas_draw_xbm,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, const uint8_t*" +Function,+,canvas_get_font_params,CanvasFontParameters*,"Canvas*, Font" +Function,+,canvas_glyph_width,uint8_t,"Canvas*, char" +Function,+,canvas_height,uint8_t,Canvas* +Function,+,canvas_invert_color,void,Canvas* +Function,+,canvas_reset,void,Canvas* +Function,+,canvas_set_bitmap_mode,void,"Canvas*, _Bool" +Function,+,canvas_set_color,void,"Canvas*, Color" +Function,+,canvas_set_font,void,"Canvas*, Font" +Function,+,canvas_set_font_direction,void,"Canvas*, CanvasDirection" +Function,+,canvas_string_width,uint16_t,"Canvas*, const char*" +Function,+,canvas_width,uint8_t,Canvas* +Function,-,cfree,void,void* +Function,-,clearerr,void,FILE* +Function,-,clearerr_unlocked,void,FILE* +Function,+,cli_add_command,void,"Cli*, const char*, CliCommandFlag, CliCallback, void*" +Function,+,cli_cmd_interrupt_received,_Bool,Cli* +Function,+,cli_delete_command,void,"Cli*, const char*" +Function,+,cli_getc,char,Cli* +Function,+,cli_is_connected,_Bool,Cli* +Function,+,cli_nl,void, +Function,+,cli_print_usage,void,"const char*, const char*, const char*" +Function,+,cli_read,size_t,"Cli*, uint8_t*, size_t" +Function,+,cli_read_timeout,size_t,"Cli*, uint8_t*, size_t, uint32_t" +Function,+,cli_session_close,void,Cli* +Function,+,cli_session_open,void,"Cli*, void*" +Function,+,cli_write,void,"Cli*, const uint8_t*, size_t" +Function,-,clock,clock_t, +Function,+,crc32_calc_buffer,uint32_t,"uint32_t, const void*, size_t" +Function,+,crc32_calc_file,uint32_t,"File*, const FileCrcProgressCb, void*" +Function,-,ctermid,char*,char* +Function,-,ctime,char*,const time_t* +Function,-,ctime_r,char*,"const time_t*, char*" +Function,-,cuserid,char*,char* +Function,+,delete_mutex,_Bool,ValueMutex* +Function,+,dialog_ex_alloc,DialogEx*, +Function,+,dialog_ex_disable_extended_events,void,DialogEx* +Function,+,dialog_ex_enable_extended_events,void,DialogEx* +Function,+,dialog_ex_free,void,DialogEx* +Function,+,dialog_ex_get_view,View*,DialogEx* +Function,+,dialog_ex_reset,void,DialogEx* +Function,+,dialog_ex_set_center_button_text,void,"DialogEx*, const char*" +Function,+,dialog_ex_set_context,void,"DialogEx*, void*" +Function,+,dialog_ex_set_header,void,"DialogEx*, const char*, uint8_t, uint8_t, Align, Align" +Function,+,dialog_ex_set_icon,void,"DialogEx*, uint8_t, uint8_t, const Icon*" +Function,+,dialog_ex_set_left_button_text,void,"DialogEx*, const char*" +Function,+,dialog_ex_set_result_callback,void,"DialogEx*, DialogExResultCallback" +Function,+,dialog_ex_set_right_button_text,void,"DialogEx*, const char*" +Function,+,dialog_ex_set_text,void,"DialogEx*, const char*, uint8_t, uint8_t, Align, Align" +Function,+,dialog_file_browser_set_basic_options,void,"DialogsFileBrowserOptions*, const char*, const Icon*" +Function,+,dialog_file_browser_show,_Bool,"DialogsApp*, FuriString*, FuriString*, const DialogsFileBrowserOptions*" +Function,+,dialog_message_alloc,DialogMessage*, +Function,+,dialog_message_free,void,DialogMessage* +Function,+,dialog_message_set_buttons,void,"DialogMessage*, const char*, const char*, const char*" +Function,+,dialog_message_set_header,void,"DialogMessage*, const char*, uint8_t, uint8_t, Align, Align" +Function,+,dialog_message_set_icon,void,"DialogMessage*, const Icon*, uint8_t, uint8_t" +Function,+,dialog_message_set_text,void,"DialogMessage*, const char*, uint8_t, uint8_t, Align, Align" +Function,+,dialog_message_show,DialogMessageButton,"DialogsApp*, const DialogMessage*" +Function,+,dialog_message_show_storage_error,void,"DialogsApp*, const char*" +Function,-,difftime,double,"time_t, time_t" +Function,-,diprintf,int,"int, const char*, ..." +Function,+,dir_walk_alloc,DirWalk*,Storage* +Function,+,dir_walk_close,void,DirWalk* +Function,+,dir_walk_free,void,DirWalk* +Function,+,dir_walk_get_error,FS_Error,DirWalk* +Function,+,dir_walk_open,_Bool,"DirWalk*, const char*" +Function,+,dir_walk_read,DirWalkResult,"DirWalk*, FuriString*, FileInfo*" +Function,+,dir_walk_set_filter_cb,void,"DirWalk*, DirWalkFilterCb, void*" +Function,+,dir_walk_set_recursive,void,"DirWalk*, _Bool" +Function,-,div,div_t,"int, int" +Function,+,dolphin_deed,void,"Dolphin*, DolphinDeed" +Function,+,dolphin_deed_get_app,DolphinApp,DolphinDeed +Function,+,dolphin_deed_get_app_limit,uint8_t,DolphinApp +Function,+,dolphin_deed_get_weight,uint8_t,DolphinDeed +Function,+,dolphin_flush,void,Dolphin* +Function,+,dolphin_get_pubsub,FuriPubSub*,Dolphin* +Function,+,dolphin_stats,DolphinStats,Dolphin* +Function,+,dolphin_upgrade_level,void,Dolphin* +Function,-,dprintf,int,"int, const char*, ..." +Function,-,drand48,double, +Function,-,eTaskConfirmSleepModeStatus,eSleepModeStatus, +Function,-,eTaskGetState,eTaskState,TaskHandle_t +Function,+,elements_bold_rounded_frame,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t" +Function,+,elements_bubble,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t" +Function,+,elements_bubble_str,void,"Canvas*, uint8_t, uint8_t, const char*, Align, Align" +Function,+,elements_button_center,void,"Canvas*, const char*" +Function,+,elements_button_left,void,"Canvas*, const char*" +Function,+,elements_button_right,void,"Canvas*, const char*" +Function,+,elements_frame,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t" +Function,+,elements_multiline_text,void,"Canvas*, uint8_t, uint8_t, const char*" +Function,+,elements_multiline_text_aligned,void,"Canvas*, uint8_t, uint8_t, Align, Align, const char*" +Function,+,elements_multiline_text_framed,void,"Canvas*, uint8_t, uint8_t, const char*" +Function,+,elements_progress_bar,void,"Canvas*, uint8_t, uint8_t, uint8_t, float" +Function,+,elements_progress_bar_with_text,void,"Canvas*, uint8_t, uint8_t, uint8_t, float, const char*" +Function,+,elements_scrollable_text_line,void,"Canvas*, uint8_t, uint8_t, uint8_t, FuriString*, size_t, _Bool" +Function,+,elements_scrollbar,void,"Canvas*, uint16_t, uint16_t" +Function,+,elements_scrollbar_pos,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint16_t, uint16_t" +Function,+,elements_slightly_rounded_box,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t" +Function,+,elements_slightly_rounded_frame,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t" +Function,+,elements_string_fit_width,void,"Canvas*, FuriString*, uint8_t" +Function,+,elements_text_box,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, Align, Align, const char*, _Bool" +Function,+,empty_screen_alloc,EmptyScreen*, +Function,+,empty_screen_free,void,EmptyScreen* +Function,+,empty_screen_get_view,View*,EmptyScreen* +Function,-,erand48,double,unsigned short[3] +Function,-,exit,void,int +Function,-,explicit_bzero,void,"void*, size_t" +Function,-,fclose,int,FILE* +Function,-,fcloseall,int, +Function,-,fdopen,FILE*,"int, const char*" +Function,-,feof,int,FILE* +Function,-,feof_unlocked,int,FILE* +Function,-,ferror,int,FILE* +Function,-,ferror_unlocked,int,FILE* +Function,-,fflush,int,FILE* +Function,-,fflush_unlocked,int,FILE* +Function,-,ffs,int,int +Function,-,ffsl,int,long +Function,-,ffsll,int,long long +Function,-,fgetc,int,FILE* +Function,-,fgetc_unlocked,int,FILE* +Function,-,fgetpos,int,"FILE*, fpos_t*" +Function,-,fgets,char*,"char*, int, FILE*" +Function,-,fgets_unlocked,char*,"char*, int, FILE*" +Function,+,file_browser_alloc,FileBrowser*,FuriString* +Function,+,file_browser_configure,void,"FileBrowser*, const char*, const char*, _Bool, _Bool, const Icon*, _Bool" +Function,+,file_browser_free,void,FileBrowser* +Function,+,file_browser_get_view,View*,FileBrowser* +Function,+,file_browser_set_callback,void,"FileBrowser*, FileBrowserCallback, void*" +Function,+,file_browser_set_item_callback,void,"FileBrowser*, FileBrowserLoadItemCallback, void*" +Function,+,file_browser_start,void,"FileBrowser*, FuriString*" +Function,+,file_browser_stop,void,FileBrowser* +Function,+,file_browser_worker_alloc,BrowserWorker*,"FuriString*, const char*, const char*, _Bool, _Bool" +Function,+,file_browser_worker_folder_enter,void,"BrowserWorker*, FuriString*, int32_t" +Function,+,file_browser_worker_folder_exit,void,BrowserWorker* +Function,+,file_browser_worker_folder_refresh,void,"BrowserWorker*, int32_t" +Function,+,file_browser_worker_free,void,BrowserWorker* +Function,+,file_browser_worker_is_in_start_folder,_Bool,BrowserWorker* +Function,+,file_browser_worker_load,void,"BrowserWorker*, uint32_t, uint32_t" +Function,+,file_browser_worker_set_callback_context,void,"BrowserWorker*, void*" +Function,+,file_browser_worker_set_config,void,"BrowserWorker*, FuriString*, const char*, _Bool, _Bool" +Function,+,file_browser_worker_set_folder_callback,void,"BrowserWorker*, BrowserWorkerFolderOpenCallback" +Function,+,file_browser_worker_set_item_callback,void,"BrowserWorker*, BrowserWorkerListItemCallback" +Function,+,file_browser_worker_set_list_callback,void,"BrowserWorker*, BrowserWorkerListLoadCallback" +Function,+,file_browser_worker_set_long_load_callback,void,"BrowserWorker*, BrowserWorkerLongLoadCallback" +Function,+,file_stream_alloc,Stream*,Storage* +Function,+,file_stream_close,_Bool,Stream* +Function,+,file_stream_get_error,FS_Error,Stream* +Function,+,file_stream_open,_Bool,"Stream*, const char*, FS_AccessMode, FS_OpenMode" +Function,-,fileno,int,FILE* +Function,-,fileno_unlocked,int,FILE* +Function,+,filesystem_api_error_get_desc,const char*,FS_Error +Function,-,fiprintf,int,"FILE*, const char*, ..." +Function,-,fiscanf,int,"FILE*, const char*, ..." +Function,+,flipper_application_alloc,FlipperApplication*,"Storage*, const ElfApiInterface*" +Function,+,flipper_application_free,void,FlipperApplication* +Function,+,flipper_application_get_manifest,const FlipperApplicationManifest*,FlipperApplication* +Function,+,flipper_application_load_status_to_string,const char*,FlipperApplicationLoadStatus +Function,+,flipper_application_manifest_is_compatible,_Bool,"const FlipperApplicationManifest*, const ElfApiInterface*" +Function,+,flipper_application_manifest_is_target_compatible,_Bool,const FlipperApplicationManifest* +Function,+,flipper_application_manifest_is_valid,_Bool,const FlipperApplicationManifest* +Function,+,flipper_application_map_to_memory,FlipperApplicationLoadStatus,FlipperApplication* +Function,+,flipper_application_preload,FlipperApplicationPreloadStatus,"FlipperApplication*, const char*" +Function,+,flipper_application_preload_manifest,FlipperApplicationPreloadStatus,"FlipperApplication*, const char*" +Function,-,flipper_application_preload_status_to_string,const char*,FlipperApplicationPreloadStatus +Function,+,flipper_application_spawn,FuriThread*,"FlipperApplication*, void*" +Function,+,flipper_format_buffered_file_alloc,FlipperFormat*,Storage* +Function,+,flipper_format_buffered_file_close,_Bool,FlipperFormat* +Function,+,flipper_format_buffered_file_open_existing,_Bool,"FlipperFormat*, const char*" +Function,+,flipper_format_delete_key,_Bool,"FlipperFormat*, const char*" +Function,+,flipper_format_file_alloc,FlipperFormat*,Storage* +Function,+,flipper_format_file_close,_Bool,FlipperFormat* +Function,+,flipper_format_file_open_always,_Bool,"FlipperFormat*, const char*" +Function,+,flipper_format_file_open_append,_Bool,"FlipperFormat*, const char*" +Function,+,flipper_format_file_open_existing,_Bool,"FlipperFormat*, const char*" +Function,+,flipper_format_file_open_new,_Bool,"FlipperFormat*, const char*" +Function,+,flipper_format_free,void,FlipperFormat* +Function,+,flipper_format_get_raw_stream,Stream*,FlipperFormat* +Function,+,flipper_format_get_value_count,_Bool,"FlipperFormat*, const char*, uint32_t*" +Function,+,flipper_format_insert_or_update_bool,_Bool,"FlipperFormat*, const char*, const _Bool*, const uint16_t" +Function,+,flipper_format_insert_or_update_float,_Bool,"FlipperFormat*, const char*, const float*, const uint16_t" +Function,+,flipper_format_insert_or_update_hex,_Bool,"FlipperFormat*, const char*, const uint8_t*, const uint16_t" +Function,+,flipper_format_insert_or_update_int32,_Bool,"FlipperFormat*, const char*, const int32_t*, const uint16_t" +Function,+,flipper_format_insert_or_update_string,_Bool,"FlipperFormat*, const char*, FuriString*" +Function,+,flipper_format_insert_or_update_string_cstr,_Bool,"FlipperFormat*, const char*, const char*" +Function,+,flipper_format_insert_or_update_uint32,_Bool,"FlipperFormat*, const char*, const uint32_t*, const uint16_t" +Function,+,flipper_format_key_exist,_Bool,"FlipperFormat*, const char*" +Function,+,flipper_format_read_bool,_Bool,"FlipperFormat*, const char*, _Bool*, const uint16_t" +Function,+,flipper_format_read_float,_Bool,"FlipperFormat*, const char*, float*, const uint16_t" +Function,+,flipper_format_read_header,_Bool,"FlipperFormat*, FuriString*, uint32_t*" +Function,+,flipper_format_read_hex,_Bool,"FlipperFormat*, const char*, uint8_t*, const uint16_t" +Function,+,flipper_format_read_hex_uint64,_Bool,"FlipperFormat*, const char*, uint64_t*, const uint16_t" +Function,+,flipper_format_read_int32,_Bool,"FlipperFormat*, const char*, int32_t*, const uint16_t" +Function,+,flipper_format_read_string,_Bool,"FlipperFormat*, const char*, FuriString*" +Function,+,flipper_format_read_uint32,_Bool,"FlipperFormat*, const char*, uint32_t*, const uint16_t" +Function,+,flipper_format_rewind,_Bool,FlipperFormat* +Function,+,flipper_format_seek_to_end,_Bool,FlipperFormat* +Function,+,flipper_format_set_strict_mode,void,"FlipperFormat*, _Bool" +Function,+,flipper_format_string_alloc,FlipperFormat*, +Function,+,flipper_format_update_bool,_Bool,"FlipperFormat*, const char*, const _Bool*, const uint16_t" +Function,+,flipper_format_update_float,_Bool,"FlipperFormat*, const char*, const float*, const uint16_t" +Function,+,flipper_format_update_hex,_Bool,"FlipperFormat*, const char*, const uint8_t*, const uint16_t" +Function,+,flipper_format_update_int32,_Bool,"FlipperFormat*, const char*, const int32_t*, const uint16_t" +Function,+,flipper_format_update_string,_Bool,"FlipperFormat*, const char*, FuriString*" +Function,+,flipper_format_update_string_cstr,_Bool,"FlipperFormat*, const char*, const char*" +Function,+,flipper_format_update_uint32,_Bool,"FlipperFormat*, const char*, const uint32_t*, const uint16_t" +Function,+,flipper_format_write_bool,_Bool,"FlipperFormat*, const char*, const _Bool*, const uint16_t" +Function,+,flipper_format_write_comment,_Bool,"FlipperFormat*, FuriString*" +Function,+,flipper_format_write_comment_cstr,_Bool,"FlipperFormat*, const char*" +Function,+,flipper_format_write_float,_Bool,"FlipperFormat*, const char*, const float*, const uint16_t" +Function,+,flipper_format_write_header,_Bool,"FlipperFormat*, FuriString*, const uint32_t" +Function,+,flipper_format_write_header_cstr,_Bool,"FlipperFormat*, const char*, const uint32_t" +Function,+,flipper_format_write_hex,_Bool,"FlipperFormat*, const char*, const uint8_t*, const uint16_t" +Function,+,flipper_format_write_hex_uint64,_Bool,"FlipperFormat*, const char*, const uint64_t*, const uint16_t" +Function,+,flipper_format_write_int32,_Bool,"FlipperFormat*, const char*, const int32_t*, const uint16_t" +Function,+,flipper_format_write_string,_Bool,"FlipperFormat*, const char*, FuriString*" +Function,+,flipper_format_write_string_cstr,_Bool,"FlipperFormat*, const char*, const char*" +Function,+,flipper_format_write_uint32,_Bool,"FlipperFormat*, const char*, const uint32_t*, const uint16_t" +Function,+,float_is_equal,_Bool,"float, float" +Function,-,flockfile,void,FILE* +Function,-,fls,int,int +Function,-,flsl,int,long +Function,-,flsll,int,long long +Function,-,fmemopen,FILE*,"void*, size_t, const char*" +Function,-,fopen,FILE*,"const char*, const char*" +Function,-,fopencookie,FILE*,"void*, const char*, cookie_io_functions_t" +Function,-,fprintf,int,"FILE*, const char*, ..." +Function,-,fpurge,int,FILE* +Function,-,fputc,int,"int, FILE*" +Function,-,fputc_unlocked,int,"int, FILE*" +Function,-,fputs,int,"const char*, FILE*" +Function,-,fputs_unlocked,int,"const char*, FILE*" +Function,-,fread,size_t,"void*, size_t, size_t, FILE*" +Function,-,fread_unlocked,size_t,"void*, size_t, size_t, FILE*" +Function,+,free,void,void* +Function,-,freopen,FILE*,"const char*, const char*, FILE*" +Function,-,fscanf,int,"FILE*, const char*, ..." +Function,-,fseek,int,"FILE*, long, int" +Function,-,fseeko,int,"FILE*, off_t, int" +Function,-,fsetpos,int,"FILE*, const fpos_t*" +Function,-,ftell,long,FILE* +Function,-,ftello,off_t,FILE* +Function,-,ftrylockfile,int,FILE* +Function,-,funlockfile,void,FILE* +Function,-,funopen,FILE*,"const void*, int (*)(void*, char*, int), int (*)(void*, const char*, int), fpos_t (*)(void*, fpos_t, int), int (*)(void*)" +Function,+,furi_delay_ms,void,uint32_t +Function,+,furi_delay_tick,void,uint32_t +Function,+,furi_delay_until_tick,FuriStatus,uint32_t +Function,+,furi_delay_us,void,uint32_t +Function,+,furi_event_flag_alloc,FuriEventFlag*, +Function,+,furi_event_flag_clear,uint32_t,"FuriEventFlag*, uint32_t" +Function,+,furi_event_flag_free,void,FuriEventFlag* +Function,+,furi_event_flag_get,uint32_t,FuriEventFlag* +Function,+,furi_event_flag_set,uint32_t,"FuriEventFlag*, uint32_t" +Function,+,furi_event_flag_wait,uint32_t,"FuriEventFlag*, uint32_t, uint32_t, uint32_t" +Function,+,furi_get_tick,uint32_t, +Function,+,furi_hal_bt_change_app,_Bool,"FuriHalBtProfile, GapEventCallback, void*" +Function,+,furi_hal_bt_clear_white_list,_Bool, +Function,+,furi_hal_bt_dump_state,void,FuriString* +Function,+,furi_hal_bt_ensure_c2_mode,_Bool,BleGlueC2Mode +Function,+,furi_hal_bt_get_key_storage_buff,void,"uint8_t**, uint16_t*" +Function,+,furi_hal_bt_get_radio_stack,FuriHalBtStack, +Function,+,furi_hal_bt_get_rssi,float, +Function,+,furi_hal_bt_get_transmitted_packets,uint32_t, +Function,+,furi_hal_bt_hid_consumer_key_press,_Bool,uint16_t +Function,+,furi_hal_bt_hid_consumer_key_release,_Bool,uint16_t +Function,+,furi_hal_bt_hid_consumer_key_release_all,_Bool, +Function,+,furi_hal_bt_hid_kb_press,_Bool,uint16_t +Function,+,furi_hal_bt_hid_kb_release,_Bool,uint16_t +Function,+,furi_hal_bt_hid_kb_release_all,_Bool, +Function,+,furi_hal_bt_hid_mouse_move,_Bool,"int8_t, int8_t" +Function,+,furi_hal_bt_hid_mouse_press,_Bool,uint8_t +Function,+,furi_hal_bt_hid_mouse_release,_Bool,uint8_t +Function,+,furi_hal_bt_hid_mouse_release_all,_Bool, +Function,+,furi_hal_bt_hid_mouse_scroll,_Bool,int8_t +Function,+,furi_hal_bt_hid_start,void, +Function,+,furi_hal_bt_hid_stop,void, +Function,-,furi_hal_bt_init,void, +Function,+,furi_hal_bt_is_active,_Bool, +Function,+,furi_hal_bt_is_alive,_Bool, +Function,+,furi_hal_bt_is_ble_gatt_gap_supported,_Bool, +Function,+,furi_hal_bt_is_testing_supported,_Bool, +Function,+,furi_hal_bt_lock_core2,void, +Function,+,furi_hal_bt_nvm_sram_sem_acquire,void, +Function,+,furi_hal_bt_nvm_sram_sem_release,void, +Function,+,furi_hal_bt_reinit,void, +Function,+,furi_hal_bt_serial_notify_buffer_is_empty,void, +Function,+,furi_hal_bt_serial_set_event_callback,void,"uint16_t, FuriHalBtSerialCallback, void*" +Function,+,furi_hal_bt_serial_set_rpc_status,void,FuriHalBtSerialRpcStatus +Function,+,furi_hal_bt_serial_start,void, +Function,+,furi_hal_bt_serial_stop,void, +Function,+,furi_hal_bt_serial_tx,_Bool,"uint8_t*, uint16_t" +Function,+,furi_hal_bt_set_key_storage_change_callback,void,"BleGlueKeyStorageChangedCallback, void*" +Function,+,furi_hal_bt_start_advertising,void, +Function,+,furi_hal_bt_start_app,_Bool,"FuriHalBtProfile, GapEventCallback, void*" +Function,+,furi_hal_bt_start_packet_rx,void,"uint8_t, uint8_t" +Function,+,furi_hal_bt_start_packet_tx,void,"uint8_t, uint8_t, uint8_t" +Function,+,furi_hal_bt_start_radio_stack,_Bool, +Function,+,furi_hal_bt_start_rx,void,uint8_t +Function,+,furi_hal_bt_start_tone_tx,void,"uint8_t, uint8_t" +Function,+,furi_hal_bt_stop_advertising,void, +Function,+,furi_hal_bt_stop_packet_test,uint16_t, +Function,+,furi_hal_bt_stop_rx,void, +Function,+,furi_hal_bt_stop_tone_tx,void, +Function,+,furi_hal_bt_unlock_core2,void, +Function,+,furi_hal_bt_update_battery_level,void,uint8_t +Function,+,furi_hal_bt_update_power_state,void, +Function,+,furi_hal_cdc_get_ctrl_line_state,uint8_t,uint8_t +Function,+,furi_hal_cdc_get_port_settings,usb_cdc_line_coding*,uint8_t +Function,+,furi_hal_cdc_receive,int32_t,"uint8_t, uint8_t*, uint16_t" +Function,+,furi_hal_cdc_send,void,"uint8_t, uint8_t*, uint16_t" +Function,+,furi_hal_cdc_set_callbacks,void,"uint8_t, CdcCallbacks*, void*" +Function,-,furi_hal_clock_deinit_early,void, +Function,-,furi_hal_clock_init,void, +Function,-,furi_hal_clock_init_early,void, +Function,+,furi_hal_clock_mco_disable,void, +Function,+,furi_hal_clock_mco_enable,void,"FuriHalClockMcoSourceId, FuriHalClockMcoDivisorId" +Function,-,furi_hal_clock_resume_tick,void, +Function,-,furi_hal_clock_suspend_tick,void, +Function,-,furi_hal_clock_switch_to_hsi,void, +Function,-,furi_hal_clock_switch_to_pll,void, +Function,-,furi_hal_compress_alloc,FuriHalCompress*,uint16_t +Function,-,furi_hal_compress_decode,_Bool,"FuriHalCompress*, uint8_t*, size_t, uint8_t*, size_t, size_t*" +Function,-,furi_hal_compress_encode,_Bool,"FuriHalCompress*, uint8_t*, size_t, uint8_t*, size_t, size_t*" +Function,-,furi_hal_compress_free,void,FuriHalCompress* +Function,-,furi_hal_compress_icon_decode,void,"const uint8_t*, uint8_t**" +Function,-,furi_hal_compress_icon_init,void, +Function,+,furi_hal_console_disable,void, +Function,+,furi_hal_console_enable,void, +Function,+,furi_hal_console_init,void, +Function,+,furi_hal_console_printf,void,"const char[], ..." +Function,+,furi_hal_console_puts,void,const char* +Function,+,furi_hal_console_set_tx_callback,void,"FuriHalConsoleTxCallback, void*" +Function,+,furi_hal_console_tx,void,"const uint8_t*, size_t" +Function,+,furi_hal_console_tx_with_new_line,void,"const uint8_t*, size_t" +Function,+,furi_hal_cortex_delay_us,void,uint32_t +Function,-,furi_hal_cortex_init_early,void, +Function,+,furi_hal_cortex_instructions_per_microsecond,uint32_t, +Function,+,furi_hal_cortex_timer_get,FuriHalCortexTimer,uint32_t +Function,+,furi_hal_cortex_timer_is_expired,_Bool,FuriHalCortexTimer +Function,+,furi_hal_cortex_timer_wait,void,FuriHalCortexTimer +Function,+,furi_hal_crypto_decrypt,_Bool,"const uint8_t*, uint8_t*, size_t" +Function,+,furi_hal_crypto_encrypt,_Bool,"const uint8_t*, uint8_t*, size_t" +Function,-,furi_hal_crypto_init,void, +Function,+,furi_hal_crypto_store_add_key,_Bool,"FuriHalCryptoKey*, uint8_t*" +Function,+,furi_hal_crypto_store_load_key,_Bool,"uint8_t, const uint8_t*" +Function,+,furi_hal_crypto_store_unload_key,_Bool,uint8_t +Function,+,furi_hal_crypto_verify_enclave,_Bool,"uint8_t*, uint8_t*" +Function,+,furi_hal_crypto_verify_key,_Bool,uint8_t +Function,+,furi_hal_debug_disable,void, +Function,+,furi_hal_debug_enable,void, +Function,-,furi_hal_deinit_early,void, +Function,-,furi_hal_flash_erase,void,uint8_t +Function,-,furi_hal_flash_get_base,size_t, +Function,-,furi_hal_flash_get_cycles_count,size_t, +Function,-,furi_hal_flash_get_free_end_address,const void*, +Function,-,furi_hal_flash_get_free_page_count,size_t, +Function,-,furi_hal_flash_get_free_page_start_address,size_t, +Function,-,furi_hal_flash_get_free_start_address,const void*, +Function,-,furi_hal_flash_get_page_number,int16_t,size_t +Function,-,furi_hal_flash_get_page_size,size_t, +Function,-,furi_hal_flash_get_read_block_size,size_t, +Function,-,furi_hal_flash_get_write_block_size,size_t, +Function,-,furi_hal_flash_init,void, +Function,-,furi_hal_flash_ob_apply,void, +Function,-,furi_hal_flash_ob_get_raw_ptr,const FuriHalFlashRawOptionByteData*, +Function,-,furi_hal_flash_ob_set_word,_Bool,"size_t, const uint32_t" +Function,-,furi_hal_flash_program_page,void,"const uint8_t, const uint8_t*, uint16_t" +Function,-,furi_hal_flash_write_dword,void,"size_t, uint64_t" +Function,+,furi_hal_gpio_add_int_callback,void,"const GpioPin*, GpioExtiCallback, void*" +Function,+,furi_hal_gpio_disable_int_callback,void,const GpioPin* +Function,+,furi_hal_gpio_enable_int_callback,void,const GpioPin* +Function,+,furi_hal_gpio_init,void,"const GpioPin*, const GpioMode, const GpioPull, const GpioSpeed" +Function,+,furi_hal_gpio_init_ex,void,"const GpioPin*, const GpioMode, const GpioPull, const GpioSpeed, const GpioAltFn" +Function,+,furi_hal_gpio_init_simple,void,"const GpioPin*, const GpioMode" +Function,+,furi_hal_gpio_remove_int_callback,void,const GpioPin* +Function,+,furi_hal_hid_consumer_key_press,_Bool,uint16_t +Function,+,furi_hal_hid_consumer_key_release,_Bool,uint16_t +Function,+,furi_hal_hid_get_led_state,uint8_t, +Function,+,furi_hal_hid_is_connected,_Bool, +Function,+,furi_hal_hid_kb_press,_Bool,uint16_t +Function,+,furi_hal_hid_kb_release,_Bool,uint16_t +Function,+,furi_hal_hid_kb_release_all,_Bool, +Function,+,furi_hal_hid_mouse_move,_Bool,"int8_t, int8_t" +Function,+,furi_hal_hid_mouse_press,_Bool,uint8_t +Function,+,furi_hal_hid_mouse_release,_Bool,uint8_t +Function,+,furi_hal_hid_mouse_scroll,_Bool,int8_t +Function,+,furi_hal_hid_set_state_callback,void,"HidStateCallback, void*" +Function,+,furi_hal_hid_u2f_get_request,uint32_t,uint8_t* +Function,+,furi_hal_hid_u2f_is_connected,_Bool, +Function,+,furi_hal_hid_u2f_send_response,void,"uint8_t*, uint8_t" +Function,+,furi_hal_hid_u2f_set_callback,void,"HidU2fCallback, void*" +Function,+,furi_hal_i2c_acquire,void,FuriHalI2cBusHandle* +Function,-,furi_hal_i2c_deinit_early,void, +Function,-,furi_hal_i2c_init,void, +Function,-,furi_hal_i2c_init_early,void, +Function,+,furi_hal_i2c_is_device_ready,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint32_t" +Function,+,furi_hal_i2c_read_mem,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint8_t*, uint8_t, uint32_t" +Function,+,furi_hal_i2c_read_reg_16,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint16_t*, uint32_t" +Function,+,furi_hal_i2c_read_reg_8,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint8_t*, uint32_t" +Function,+,furi_hal_i2c_release,void,FuriHalI2cBusHandle* +Function,+,furi_hal_i2c_rx,_Bool,"FuriHalI2cBusHandle*, const uint8_t, uint8_t*, const uint8_t, uint32_t" +Function,+,furi_hal_i2c_trx,_Bool,"FuriHalI2cBusHandle*, const uint8_t, const uint8_t*, const uint8_t, uint8_t*, const uint8_t, uint32_t" +Function,+,furi_hal_i2c_tx,_Bool,"FuriHalI2cBusHandle*, const uint8_t, const uint8_t*, const uint8_t, uint32_t" +Function,+,furi_hal_i2c_write_mem,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint8_t*, uint8_t, uint32_t" +Function,+,furi_hal_i2c_write_reg_16,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint16_t, uint32_t" +Function,+,furi_hal_i2c_write_reg_8,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint8_t, uint32_t" +Function,+,furi_hal_info_get,void,"PropertyValueCallback, char, void*" +Function,-,furi_hal_init,void, +Function,-,furi_hal_init_early,void, +Function,-,furi_hal_interrupt_init,void, +Function,+,furi_hal_interrupt_set_isr,void,"FuriHalInterruptId, FuriHalInterruptISR, void*" +Function,+,furi_hal_interrupt_set_isr_ex,void,"FuriHalInterruptId, uint16_t, FuriHalInterruptISR, void*" +Function,+,furi_hal_light_blink_set_color,void,Light +Function,+,furi_hal_light_blink_start,void,"Light, uint8_t, uint16_t, uint16_t" +Function,+,furi_hal_light_blink_stop,void, +Function,-,furi_hal_light_init,void, +Function,+,furi_hal_light_sequence,void,const char* +Function,+,furi_hal_light_set,void,"Light, uint8_t" +Function,+,furi_hal_memory_alloc,void*,size_t +Function,+,furi_hal_memory_get_free,size_t, +Function,+,furi_hal_memory_init,void, +Function,+,furi_hal_memory_max_pool_block,size_t, +Function,+,furi_hal_mpu_disable,void, +Function,+,furi_hal_mpu_enable,void, +Function,-,furi_hal_mpu_init,void, +Function,+,furi_hal_mpu_protect_disable,void,FuriHalMpuRegion +Function,+,furi_hal_mpu_protect_no_access,void,"FuriHalMpuRegion, uint32_t, FuriHalMPURegionSize" +Function,+,furi_hal_mpu_protect_read_only,void,"FuriHalMpuRegion, uint32_t, FuriHalMPURegionSize" +Function,-,furi_hal_os_init,void, +Function,+,furi_hal_os_tick,void, +Function,+,furi_hal_power_check_otg_status,void, +Function,+,furi_hal_power_debug_get,void,"PropertyValueCallback, void*" +Function,+,furi_hal_power_deep_sleep_available,_Bool, +Function,+,furi_hal_power_disable_external_3_3v,void, +Function,+,furi_hal_power_disable_otg,void, +Function,+,furi_hal_power_enable_external_3_3v,void, +Function,+,furi_hal_power_enable_otg,void, +Function,+,furi_hal_power_gauge_is_ok,_Bool, +Function,+,furi_hal_power_get_bat_health_pct,uint8_t, +Function,+,furi_hal_power_get_battery_charging_voltage,float, +Function,+,furi_hal_power_get_battery_current,float,FuriHalPowerIC +Function,+,furi_hal_power_get_battery_design_capacity,uint32_t, +Function,+,furi_hal_power_get_battery_full_capacity,uint32_t, +Function,+,furi_hal_power_get_battery_remaining_capacity,uint32_t, +Function,+,furi_hal_power_get_battery_temperature,float,FuriHalPowerIC +Function,+,furi_hal_power_get_battery_voltage,float,FuriHalPowerIC +Function,+,furi_hal_power_get_pct,uint8_t, +Function,+,furi_hal_power_get_usb_voltage,float, +Function,+,furi_hal_power_info_get,void,"PropertyValueCallback, char, void*" +Function,-,furi_hal_power_init,void, +Function,+,furi_hal_power_insomnia_enter,void, +Function,+,furi_hal_power_insomnia_exit,void, +Function,-,furi_hal_power_insomnia_level,uint16_t, +Function,+,furi_hal_power_is_charging,_Bool, +Function,+,furi_hal_power_is_charging_done,_Bool, +Function,+,furi_hal_power_is_otg_enabled,_Bool, +Function,+,furi_hal_power_off,void, +Function,+,furi_hal_power_reset,void, +Function,+,furi_hal_power_set_battery_charging_voltage,void,float +Function,+,furi_hal_power_shutdown,void, +Function,+,furi_hal_power_sleep,void, +Function,+,furi_hal_power_sleep_available,_Bool, +Function,+,furi_hal_power_suppress_charge_enter,void, +Function,+,furi_hal_power_suppress_charge_exit,void, +Function,+,furi_hal_pwm_set_params,void,"FuriHalPwmOutputId, uint32_t, uint8_t" +Function,+,furi_hal_pwm_start,void,"FuriHalPwmOutputId, uint32_t, uint8_t" +Function,+,furi_hal_pwm_stop,void,FuriHalPwmOutputId +Function,+,furi_hal_random_fill_buf,void,"uint8_t*, uint32_t" +Function,+,furi_hal_random_get,uint32_t, +Function,+,furi_hal_region_get,const FuriHalRegion*, +Function,+,furi_hal_region_get_band,const FuriHalRegionBand*,uint32_t +Function,+,furi_hal_region_get_name,const char*, +Function,-,furi_hal_region_init,void, +Function,+,furi_hal_region_is_frequency_allowed,_Bool,uint32_t +Function,+,furi_hal_region_is_provisioned,_Bool, +Function,+,furi_hal_region_set,void,FuriHalRegion* +Function,-,furi_hal_resources_deinit_early,void, +Function,-,furi_hal_resources_init,void, +Function,-,furi_hal_resources_init_early,void, +Function,+,furi_hal_rtc_datetime_to_timestamp,uint32_t,FuriHalRtcDateTime* +Function,-,furi_hal_rtc_deinit_early,void, +Function,+,furi_hal_rtc_get_boot_mode,FuriHalRtcBootMode, +Function,+,furi_hal_rtc_get_datetime,void,FuriHalRtcDateTime* +Function,+,furi_hal_rtc_get_fault_data,uint32_t, +Function,+,furi_hal_rtc_get_heap_track_mode,FuriHalRtcHeapTrackMode, +Function,+,furi_hal_rtc_get_locale_dateformat,FuriHalRtcLocaleDateFormat, +Function,+,furi_hal_rtc_get_locale_timeformat,FuriHalRtcLocaleTimeFormat, +Function,+,furi_hal_rtc_get_locale_units,FuriHalRtcLocaleUnits, +Function,+,furi_hal_rtc_get_log_level,uint8_t, +Function,+,furi_hal_rtc_get_pin_fails,uint32_t, +Function,+,furi_hal_rtc_get_register,uint32_t,FuriHalRtcRegister +Function,+,furi_hal_rtc_get_timestamp,uint32_t, +Function,-,furi_hal_rtc_init,void, +Function,-,furi_hal_rtc_init_early,void, +Function,+,furi_hal_rtc_is_flag_set,_Bool,FuriHalRtcFlag +Function,+,furi_hal_rtc_reset_flag,void,FuriHalRtcFlag +Function,+,furi_hal_rtc_set_boot_mode,void,FuriHalRtcBootMode +Function,+,furi_hal_rtc_set_datetime,void,FuriHalRtcDateTime* +Function,+,furi_hal_rtc_set_fault_data,void,uint32_t +Function,+,furi_hal_rtc_set_flag,void,FuriHalRtcFlag +Function,+,furi_hal_rtc_set_heap_track_mode,void,FuriHalRtcHeapTrackMode +Function,+,furi_hal_rtc_set_locale_dateformat,void,FuriHalRtcLocaleDateFormat +Function,+,furi_hal_rtc_set_locale_timeformat,void,FuriHalRtcLocaleTimeFormat +Function,+,furi_hal_rtc_set_locale_units,void,FuriHalRtcLocaleUnits +Function,+,furi_hal_rtc_set_log_level,void,uint8_t +Function,+,furi_hal_rtc_set_pin_fails,void,uint32_t +Function,+,furi_hal_rtc_set_register,void,"FuriHalRtcRegister, uint32_t" +Function,+,furi_hal_rtc_validate_datetime,_Bool,FuriHalRtcDateTime* +Function,+,furi_hal_speaker_acquire,_Bool,uint32_t +Function,-,furi_hal_speaker_deinit,void, +Function,-,furi_hal_speaker_init,void, +Function,+,furi_hal_speaker_is_mine,_Bool, +Function,+,furi_hal_speaker_release,void, +Function,+,furi_hal_speaker_set_volume,void,float +Function,+,furi_hal_speaker_start,void,"float, float" +Function,+,furi_hal_speaker_stop,void, +Function,+,furi_hal_spi_acquire,void,FuriHalSpiBusHandle* +Function,+,furi_hal_spi_bus_deinit,void,FuriHalSpiBus* +Function,+,furi_hal_spi_bus_handle_deinit,void,FuriHalSpiBusHandle* +Function,+,furi_hal_spi_bus_handle_init,void,FuriHalSpiBusHandle* +Function,+,furi_hal_spi_bus_init,void,FuriHalSpiBus* +Function,+,furi_hal_spi_bus_rx,_Bool,"FuriHalSpiBusHandle*, uint8_t*, size_t, uint32_t" +Function,+,furi_hal_spi_bus_trx,_Bool,"FuriHalSpiBusHandle*, uint8_t*, uint8_t*, size_t, uint32_t" +Function,+,furi_hal_spi_bus_tx,_Bool,"FuriHalSpiBusHandle*, uint8_t*, size_t, uint32_t" +Function,-,furi_hal_spi_config_deinit_early,void, +Function,-,furi_hal_spi_config_init,void, +Function,-,furi_hal_spi_config_init_early,void, +Function,+,furi_hal_spi_release,void,FuriHalSpiBusHandle* +Function,+,furi_hal_switch,void,void* +Function,+,furi_hal_uart_deinit,void,FuriHalUartId +Function,+,furi_hal_uart_init,void,"FuriHalUartId, uint32_t" +Function,+,furi_hal_uart_resume,void,FuriHalUartId +Function,+,furi_hal_uart_set_br,void,"FuriHalUartId, uint32_t" +Function,+,furi_hal_uart_set_irq_cb,void,"FuriHalUartId, void (*)(UartIrqEvent, uint8_t, void*), void*" +Function,+,furi_hal_uart_suspend,void,FuriHalUartId +Function,+,furi_hal_uart_tx,void,"FuriHalUartId, uint8_t*, size_t" +Function,+,furi_hal_usb_disable,void, +Function,+,furi_hal_usb_enable,void, +Function,+,furi_hal_usb_get_config,FuriHalUsbInterface*, +Function,-,furi_hal_usb_init,void, +Function,+,furi_hal_usb_is_locked,_Bool, +Function,+,furi_hal_usb_lock,void, +Function,+,furi_hal_usb_reinit,void, +Function,+,furi_hal_usb_set_config,_Bool,"FuriHalUsbInterface*, void*" +Function,-,furi_hal_usb_set_state_callback,void,"FuriHalUsbStateCallback, void*" +Function,+,furi_hal_usb_unlock,void, +Function,+,furi_hal_version_do_i_belong_here,_Bool, +Function,+,furi_hal_version_get_ble_local_device_name_ptr,const char*, +Function,+,furi_hal_version_get_ble_mac,const uint8_t*, +Function,+,furi_hal_version_get_device_name_ptr,const char*, +Function,+,furi_hal_version_get_fcc_id,const char*, +Function,+,furi_hal_version_get_firmware_version,const Version*, +Function,+,furi_hal_version_get_hw_body,uint8_t, +Function,+,furi_hal_version_get_hw_color,FuriHalVersionColor, +Function,+,furi_hal_version_get_hw_connect,uint8_t, +Function,+,furi_hal_version_get_hw_display,FuriHalVersionDisplay, +Function,+,furi_hal_version_get_hw_region,FuriHalVersionRegion, +Function,+,furi_hal_version_get_hw_region_name,const char*, +Function,+,furi_hal_version_get_hw_target,uint8_t, +Function,+,furi_hal_version_get_hw_timestamp,uint32_t, +Function,+,furi_hal_version_get_hw_version,uint8_t, +Function,+,furi_hal_version_get_ic_id,const char*, +Function,+,furi_hal_version_get_model_code,const char*, +Function,+,furi_hal_version_get_model_name,const char*, +Function,+,furi_hal_version_get_name_ptr,const char*, +Function,+,furi_hal_version_get_otp_version,FuriHalVersionOtpVersion, +Function,-,furi_hal_version_init,void, +Function,+,furi_hal_version_uid,const uint8_t*, +Function,+,furi_hal_version_uid_size,size_t, +Function,-,furi_hal_vibro_init,void, +Function,+,furi_hal_vibro_on,void,_Bool +Function,-,furi_init,void, +Function,+,furi_kernel_get_tick_frequency,uint32_t, +Function,+,furi_kernel_is_irq_or_masked,_Bool, +Function,+,furi_kernel_lock,int32_t, +Function,+,furi_kernel_restore_lock,int32_t,int32_t +Function,+,furi_kernel_unlock,int32_t, +Function,+,furi_log_get_level,FuriLogLevel, +Function,-,furi_log_init,void, +Function,+,furi_log_print_format,void,"FuriLogLevel, const char*, const char*, ..." +Function,+,furi_log_set_level,void,FuriLogLevel +Function,-,furi_log_set_puts,void,FuriLogPuts +Function,-,furi_log_set_timestamp,void,FuriLogTimestamp +Function,+,furi_message_queue_alloc,FuriMessageQueue*,"uint32_t, uint32_t" +Function,+,furi_message_queue_free,void,FuriMessageQueue* +Function,+,furi_message_queue_get,FuriStatus,"FuriMessageQueue*, void*, uint32_t" +Function,+,furi_message_queue_get_capacity,uint32_t,FuriMessageQueue* +Function,+,furi_message_queue_get_count,uint32_t,FuriMessageQueue* +Function,+,furi_message_queue_get_message_size,uint32_t,FuriMessageQueue* +Function,+,furi_message_queue_get_space,uint32_t,FuriMessageQueue* +Function,+,furi_message_queue_put,FuriStatus,"FuriMessageQueue*, const void*, uint32_t" +Function,+,furi_message_queue_reset,FuriStatus,FuriMessageQueue* +Function,+,furi_ms_to_ticks,uint32_t,uint32_t +Function,+,furi_mutex_acquire,FuriStatus,"FuriMutex*, uint32_t" +Function,+,furi_mutex_alloc,FuriMutex*,FuriMutexType +Function,+,furi_mutex_free,void,FuriMutex* +Function,+,furi_mutex_get_owner,FuriThreadId,FuriMutex* +Function,+,furi_mutex_release,FuriStatus,FuriMutex* +Function,+,furi_pubsub_alloc,FuriPubSub*, +Function,-,furi_pubsub_free,void,FuriPubSub* +Function,+,furi_pubsub_publish,void,"FuriPubSub*, void*" +Function,+,furi_pubsub_subscribe,FuriPubSubSubscription*,"FuriPubSub*, FuriPubSubCallback, void*" +Function,+,furi_pubsub_unsubscribe,void,"FuriPubSub*, FuriPubSubSubscription*" +Function,+,furi_record_close,void,const char* +Function,+,furi_record_create,void,"const char*, void*" +Function,-,furi_record_destroy,_Bool,const char* +Function,+,furi_record_exists,_Bool,const char* +Function,-,furi_record_init,void, +Function,+,furi_record_open,void*,const char* +Function,+,furi_run,void, +Function,+,furi_semaphore_acquire,FuriStatus,"FuriSemaphore*, uint32_t" +Function,+,furi_semaphore_alloc,FuriSemaphore*,"uint32_t, uint32_t" +Function,+,furi_semaphore_free,void,FuriSemaphore* +Function,+,furi_semaphore_get_count,uint32_t,FuriSemaphore* +Function,+,furi_semaphore_release,FuriStatus,FuriSemaphore* +Function,+,furi_stream_buffer_alloc,FuriStreamBuffer*,"size_t, size_t" +Function,+,furi_stream_buffer_bytes_available,size_t,FuriStreamBuffer* +Function,+,furi_stream_buffer_free,void,FuriStreamBuffer* +Function,+,furi_stream_buffer_is_empty,_Bool,FuriStreamBuffer* +Function,+,furi_stream_buffer_is_full,_Bool,FuriStreamBuffer* +Function,+,furi_stream_buffer_receive,size_t,"FuriStreamBuffer*, void*, size_t, uint32_t" +Function,+,furi_stream_buffer_reset,FuriStatus,FuriStreamBuffer* +Function,+,furi_stream_buffer_send,size_t,"FuriStreamBuffer*, const void*, size_t, uint32_t" +Function,+,furi_stream_buffer_spaces_available,size_t,FuriStreamBuffer* +Function,+,furi_stream_set_trigger_level,_Bool,"FuriStreamBuffer*, size_t" +Function,+,furi_string_alloc,FuriString*, +Function,+,furi_string_alloc_move,FuriString*,FuriString* +Function,+,furi_string_alloc_printf,FuriString*,"const char[], ..." +Function,+,furi_string_alloc_set,FuriString*,const FuriString* +Function,+,furi_string_alloc_set_str,FuriString*,const char[] +Function,+,furi_string_alloc_vprintf,FuriString*,"const char[], va_list" +Function,+,furi_string_cat,void,"FuriString*, const FuriString*" +Function,+,furi_string_cat_printf,int,"FuriString*, const char[], ..." +Function,+,furi_string_cat_str,void,"FuriString*, const char[]" +Function,+,furi_string_cat_vprintf,int,"FuriString*, const char[], va_list" +Function,+,furi_string_cmp,int,"const FuriString*, const FuriString*" +Function,+,furi_string_cmp_str,int,"const FuriString*, const char[]" +Function,+,furi_string_cmpi,int,"const FuriString*, const FuriString*" +Function,+,furi_string_cmpi_str,int,"const FuriString*, const char[]" +Function,+,furi_string_empty,_Bool,const FuriString* +Function,+,furi_string_end_with,_Bool,"const FuriString*, const FuriString*" +Function,+,furi_string_end_with_str,_Bool,"const FuriString*, const char[]" +Function,+,furi_string_equal,_Bool,"const FuriString*, const FuriString*" +Function,+,furi_string_equal_str,_Bool,"const FuriString*, const char[]" +Function,+,furi_string_free,void,FuriString* +Function,+,furi_string_get_char,char,"const FuriString*, size_t" +Function,+,furi_string_get_cstr,const char*,const FuriString* +Function,+,furi_string_hash,size_t,const FuriString* +Function,+,furi_string_left,void,"FuriString*, size_t" +Function,+,furi_string_mid,void,"FuriString*, size_t, size_t" +Function,+,furi_string_move,void,"FuriString*, FuriString*" +Function,+,furi_string_printf,int,"FuriString*, const char[], ..." +Function,+,furi_string_push_back,void,"FuriString*, char" +Function,+,furi_string_replace,size_t,"FuriString*, FuriString*, FuriString*, size_t" +Function,+,furi_string_replace_all,void,"FuriString*, const FuriString*, const FuriString*" +Function,+,furi_string_replace_all_str,void,"FuriString*, const char[], const char[]" +Function,+,furi_string_replace_at,void,"FuriString*, size_t, size_t, const char[]" +Function,+,furi_string_replace_str,size_t,"FuriString*, const char[], const char[], size_t" +Function,+,furi_string_reserve,void,"FuriString*, size_t" +Function,+,furi_string_reset,void,FuriString* +Function,+,furi_string_right,void,"FuriString*, size_t" +Function,+,furi_string_search,size_t,"const FuriString*, const FuriString*, size_t" +Function,+,furi_string_search_char,size_t,"const FuriString*, char, size_t" +Function,+,furi_string_search_rchar,size_t,"const FuriString*, char, size_t" +Function,+,furi_string_search_str,size_t,"const FuriString*, const char[], size_t" +Function,+,furi_string_set,void,"FuriString*, FuriString*" +Function,+,furi_string_set_char,void,"FuriString*, size_t, const char" +Function,+,furi_string_set_n,void,"FuriString*, const FuriString*, size_t, size_t" +Function,+,furi_string_set_str,void,"FuriString*, const char[]" +Function,+,furi_string_set_strn,void,"FuriString*, const char[], size_t" +Function,+,furi_string_size,size_t,const FuriString* +Function,+,furi_string_start_with,_Bool,"const FuriString*, const FuriString*" +Function,+,furi_string_start_with_str,_Bool,"const FuriString*, const char[]" +Function,+,furi_string_swap,void,"FuriString*, FuriString*" +Function,+,furi_string_trim,void,"FuriString*, const char[]" +Function,+,furi_string_utf8_decode,void,"char, FuriStringUTF8State*, FuriStringUnicodeValue*" +Function,+,furi_string_utf8_length,size_t,FuriString* +Function,+,furi_string_utf8_push,void,"FuriString*, FuriStringUnicodeValue" +Function,+,furi_string_vprintf,int,"FuriString*, const char[], va_list" +Function,+,furi_thread_alloc,FuriThread*, +Function,+,furi_thread_alloc_ex,FuriThread*,"const char*, uint32_t, FuriThreadCallback, void*" +Function,+,furi_thread_catch,void, +Function,-,furi_thread_disable_heap_trace,void,FuriThread* +Function,+,furi_thread_enable_heap_trace,void,FuriThread* +Function,+,furi_thread_enumerate,uint32_t,"FuriThreadId*, uint32_t" +Function,+,furi_thread_flags_clear,uint32_t,uint32_t +Function,+,furi_thread_flags_get,uint32_t, +Function,+,furi_thread_flags_set,uint32_t,"FuriThreadId, uint32_t" +Function,+,furi_thread_flags_wait,uint32_t,"uint32_t, uint32_t, uint32_t" +Function,+,furi_thread_free,void,FuriThread* +Function,+,furi_thread_get_current,FuriThread*, +Function,+,furi_thread_get_current_id,FuriThreadId, +Function,+,furi_thread_get_heap_size,size_t,FuriThread* +Function,+,furi_thread_get_id,FuriThreadId,FuriThread* +Function,+,furi_thread_get_name,const char*,FuriThreadId +Function,+,furi_thread_get_return_code,int32_t,FuriThread* +Function,+,furi_thread_get_stack_space,uint32_t,FuriThreadId +Function,+,furi_thread_get_state,FuriThreadState,FuriThread* +Function,+,furi_thread_get_stdout_callback,FuriThreadStdoutWriteCallback, +Function,+,furi_thread_is_suspended,_Bool,FuriThreadId +Function,+,furi_thread_join,_Bool,FuriThread* +Function,+,furi_thread_mark_as_service,void,FuriThread* +Function,+,furi_thread_resume,void,FuriThreadId +Function,+,furi_thread_set_callback,void,"FuriThread*, FuriThreadCallback" +Function,+,furi_thread_set_context,void,"FuriThread*, void*" +Function,+,furi_thread_set_name,void,"FuriThread*, const char*" +Function,+,furi_thread_set_priority,void,"FuriThread*, FuriThreadPriority" +Function,+,furi_thread_set_stack_size,void,"FuriThread*, size_t" +Function,+,furi_thread_set_state_callback,void,"FuriThread*, FuriThreadStateCallback" +Function,+,furi_thread_set_state_context,void,"FuriThread*, void*" +Function,+,furi_thread_set_stdout_callback,_Bool,FuriThreadStdoutWriteCallback +Function,+,furi_thread_start,void,FuriThread* +Function,+,furi_thread_stdout_flush,int32_t, +Function,+,furi_thread_stdout_write,size_t,"const char*, size_t" +Function,+,furi_thread_suspend,void,FuriThreadId +Function,+,furi_thread_yield,void, +Function,+,furi_timer_alloc,FuriTimer*,"FuriTimerCallback, FuriTimerType, void*" +Function,+,furi_timer_free,void,FuriTimer* +Function,+,furi_timer_is_running,uint32_t,FuriTimer* +Function,+,furi_timer_start,FuriStatus,"FuriTimer*, uint32_t" +Function,+,furi_timer_stop,FuriStatus,FuriTimer* +Function,-,fwrite,size_t,"const void*, size_t, size_t, FILE*" +Function,-,fwrite_unlocked,size_t,"const void*, size_t, size_t, FILE*" +Function,-,gap_get_state,GapState, +Function,-,gap_init,_Bool,"GapConfig*, GapEventCallback, void*" +Function,-,gap_start_advertising,void, +Function,-,gap_stop_advertising,void, +Function,-,gap_thread_stop,void, +Function,-,getc,int,FILE* +Function,-,getc_unlocked,int,FILE* +Function,-,getchar,int, +Function,-,getchar_unlocked,int, +Function,-,getenv,char*,const char* +Function,-,gets,char*,char* +Function,-,getsubopt,int,"char**, char**, char**" +Function,-,getw,int,FILE* +Function,-,gmtime,tm*,const time_t* +Function,-,gmtime_r,tm*,"const time_t*, tm*" +Function,+,gui_add_framebuffer_callback,void,"Gui*, GuiCanvasCommitCallback, void*" +Function,+,gui_add_view_port,void,"Gui*, ViewPort*, GuiLayer" +Function,+,gui_direct_draw_acquire,Canvas*,Gui* +Function,+,gui_direct_draw_release,void,Gui* +Function,+,gui_get_framebuffer_size,size_t,Gui* +Function,+,gui_remove_framebuffer_callback,void,"Gui*, GuiCanvasCommitCallback, void*" +Function,+,gui_remove_view_port,void,"Gui*, ViewPort*" +Function,+,gui_set_lockdown,void,"Gui*, _Bool" +Function,-,gui_view_port_send_to_back,void,"Gui*, ViewPort*" +Function,+,gui_view_port_send_to_front,void,"Gui*, ViewPort*" +Function,+,hal_sd_detect,_Bool, +Function,+,hal_sd_detect_init,void, +Function,+,hal_sd_detect_set_low,void, +Function,+,hmac_sha256_finish,void,"const hmac_sha256_context*, const uint8_t*, uint8_t*" +Function,+,hmac_sha256_init,void,"hmac_sha256_context*, const uint8_t*" +Function,+,hmac_sha256_update,void,"const hmac_sha256_context*, const uint8_t*, unsigned" +Function,+,icon_animation_alloc,IconAnimation*,const Icon* +Function,+,icon_animation_free,void,IconAnimation* +Function,+,icon_animation_get_height,uint8_t,IconAnimation* +Function,+,icon_animation_get_width,uint8_t,IconAnimation* +Function,+,icon_animation_is_last_frame,_Bool,IconAnimation* +Function,+,icon_animation_set_update_callback,void,"IconAnimation*, IconAnimationCallback, void*" +Function,+,icon_animation_start,void,IconAnimation* +Function,+,icon_animation_stop,void,IconAnimation* +Function,+,icon_get_data,const uint8_t*,const Icon* +Function,+,icon_get_height,uint8_t,const Icon* +Function,+,icon_get_width,uint8_t,const Icon* +Function,-,index,char*,"const char*, int" +Function,+,init_mutex,_Bool,"ValueMutex*, void*, size_t" +Function,-,initstate,char*,"unsigned, char*, size_t" +Function,+,input_get_key_name,const char*,InputKey +Function,+,input_get_type_name,const char*,InputType +Function,-,iprintf,int,"const char*, ..." +Function,-,isalnum,int,int +Function,-,isalnum_l,int,"int, locale_t" +Function,-,isalpha,int,int +Function,-,isalpha_l,int,"int, locale_t" +Function,-,isascii,int,int +Function,-,isascii_l,int,"int, locale_t" +Function,-,isblank,int,int +Function,-,isblank_l,int,"int, locale_t" +Function,-,iscanf,int,"const char*, ..." +Function,-,iscntrl,int,int +Function,-,iscntrl_l,int,"int, locale_t" +Function,-,isdigit,int,int +Function,-,isdigit_l,int,"int, locale_t" +Function,-,isgraph,int,int +Function,-,isgraph_l,int,"int, locale_t" +Function,-,islower,int,int +Function,-,islower_l,int,"int, locale_t" +Function,-,isprint,int,int +Function,-,isprint_l,int,"int, locale_t" +Function,-,ispunct,int,int +Function,-,ispunct_l,int,"int, locale_t" +Function,-,isspace,int,int +Function,-,isspace_l,int,"int, locale_t" +Function,-,isupper,int,int +Function,-,isupper_l,int,"int, locale_t" +Function,-,isxdigit,int,int +Function,-,isxdigit_l,int,"int, locale_t" +Function,-,itoa,char*,"int, char*, int" +Function,-,jrand48,long,unsigned short[3] +Function,-,l64a,char*,long +Function,-,labs,long,long +Function,-,lcong48,void,unsigned short[7] +Function,-,ldiv,ldiv_t,"long, long" +Function,-,llabs,long long,long long +Function,-,lldiv,lldiv_t,"long long, long long" +Function,+,loader_get_pubsub,FuriPubSub*,Loader* +Function,+,loader_is_locked,_Bool,Loader* +Function,+,loader_lock,_Bool,Loader* +Function,+,loader_show_menu,void, +Function,+,loader_start,LoaderStatus,"Loader*, const char*, const char*" +Function,+,loader_unlock,void,Loader* +Function,+,loader_update_menu,void, +Function,+,loading_alloc,Loading*, +Function,+,loading_free,void,Loading* +Function,+,loading_get_view,View*,Loading* +Function,+,locale_celsius_to_fahrenheit,float,float +Function,+,locale_fahrenheit_to_celsius,float,float +Function,+,locale_format_date,void,"FuriString*, const FuriHalRtcDateTime*, const LocaleDateFormat, const char*" +Function,+,locale_format_time,void,"FuriString*, const FuriHalRtcDateTime*, const LocaleTimeFormat, const _Bool" +Function,+,locale_get_date_format,LocaleDateFormat, +Function,+,locale_get_measurement_unit,LocaleMeasurementUnits, +Function,+,locale_get_time_format,LocaleTimeFormat, +Function,+,locale_set_date_format,void,LocaleDateFormat +Function,+,locale_set_measurement_unit,void,LocaleMeasurementUnits +Function,+,locale_set_time_format,void,LocaleTimeFormat +Function,-,localtime,tm*,const time_t* +Function,-,localtime_r,tm*,"const time_t*, tm*" +Function,-,lrand48,long, +Function,+,malloc,void*,size_t +Function,+,manchester_advance,_Bool,"ManchesterState, ManchesterEvent, ManchesterState*, _Bool*" +Function,+,manchester_encoder_advance,_Bool,"ManchesterEncoderState*, const _Bool, ManchesterEncoderResult*" +Function,+,manchester_encoder_finish,ManchesterEncoderResult,ManchesterEncoderState* +Function,+,manchester_encoder_reset,void,ManchesterEncoderState* +Function,-,mbedtls_des3_crypt_cbc,int,"mbedtls_des3_context*, int, size_t, unsigned char[8], const unsigned char*, unsigned char*" +Function,-,mbedtls_des3_crypt_ecb,int,"mbedtls_des3_context*, const unsigned char[8], unsigned char[8]" +Function,-,mbedtls_des3_free,void,mbedtls_des3_context* +Function,-,mbedtls_des3_init,void,mbedtls_des3_context* +Function,-,mbedtls_des3_set2key_dec,int,"mbedtls_des3_context*, const unsigned char[8 * 2]" +Function,-,mbedtls_des3_set2key_enc,int,"mbedtls_des3_context*, const unsigned char[8 * 2]" +Function,-,mbedtls_des3_set3key_dec,int,"mbedtls_des3_context*, const unsigned char[8 * 3]" +Function,-,mbedtls_des3_set3key_enc,int,"mbedtls_des3_context*, const unsigned char[8 * 3]" +Function,-,mbedtls_des_crypt_cbc,int,"mbedtls_des_context*, int, size_t, unsigned char[8], const unsigned char*, unsigned char*" +Function,-,mbedtls_des_crypt_ecb,int,"mbedtls_des_context*, const unsigned char[8], unsigned char[8]" +Function,-,mbedtls_des_free,void,mbedtls_des_context* +Function,-,mbedtls_des_init,void,mbedtls_des_context* +Function,-,mbedtls_des_key_check_key_parity,int,const unsigned char[8] +Function,-,mbedtls_des_key_check_weak,int,const unsigned char[8] +Function,-,mbedtls_des_key_set_parity,void,unsigned char[8] +Function,-,mbedtls_des_self_test,int,int +Function,-,mbedtls_des_setkey,void,"uint32_t[32], const unsigned char[8]" +Function,-,mbedtls_des_setkey_dec,int,"mbedtls_des_context*, const unsigned char[8]" +Function,-,mbedtls_des_setkey_enc,int,"mbedtls_des_context*, const unsigned char[8]" +Function,-,mbedtls_internal_sha1_process,int,"mbedtls_sha1_context*, const unsigned char[64]" +Function,-,mbedtls_platform_gmtime_r,tm*,"const mbedtls_time_t*, tm*" +Function,-,mbedtls_platform_zeroize,void,"void*, size_t" +Function,-,mbedtls_sha1,int,"const unsigned char*, size_t, unsigned char[20]" +Function,-,mbedtls_sha1_clone,void,"mbedtls_sha1_context*, const mbedtls_sha1_context*" +Function,-,mbedtls_sha1_finish,int,"mbedtls_sha1_context*, unsigned char[20]" +Function,-,mbedtls_sha1_free,void,mbedtls_sha1_context* +Function,-,mbedtls_sha1_init,void,mbedtls_sha1_context* +Function,-,mbedtls_sha1_self_test,int,int +Function,-,mbedtls_sha1_starts,int,mbedtls_sha1_context* +Function,-,mbedtls_sha1_update,int,"mbedtls_sha1_context*, const unsigned char*, size_t" +Function,-,mblen,int,"const char*, size_t" +Function,-,mbstowcs,size_t,"wchar_t*, const char*, size_t" +Function,-,mbtowc,int,"wchar_t*, const char*, size_t" +Function,+,md5,void,"const unsigned char*, size_t, unsigned char[16]" +Function,+,md5_finish,void,"md5_context*, unsigned char[16]" +Function,+,md5_process,void,"md5_context*, const unsigned char[64]" +Function,+,md5_starts,void,md5_context* +Function,+,md5_update,void,"md5_context*, const unsigned char*, size_t" +Function,-,memccpy,void*,"void*, const void*, int, size_t" +Function,+,memchr,void*,"const void*, int, size_t" +Function,+,memcmp,int,"const void*, const void*, size_t" +Function,+,memcpy,void*,"void*, const void*, size_t" +Function,-,memmem,void*,"const void*, size_t, const void*, size_t" +Function,-,memmgr_alloc_from_pool,void*,size_t +Function,+,memmgr_get_free_heap,size_t, +Function,+,memmgr_get_minimum_free_heap,size_t, +Function,+,memmgr_get_total_heap,size_t, +Function,+,memmgr_heap_disable_thread_trace,void,FuriThreadId +Function,+,memmgr_heap_enable_thread_trace,void,FuriThreadId +Function,+,memmgr_heap_get_max_free_block,size_t, +Function,+,memmgr_heap_get_thread_memory,size_t,FuriThreadId +Function,+,memmgr_heap_printf_free_blocks,void, +Function,-,memmgr_pool_get_free,size_t, +Function,-,memmgr_pool_get_max_block,size_t, +Function,+,memmove,void*,"void*, const void*, size_t" +Function,-,mempcpy,void*,"void*, const void*, size_t" +Function,-,memrchr,void*,"const void*, int, size_t" +Function,+,memset,void*,"void*, int, size_t" +Function,+,menu_add_item,void,"Menu*, const char*, const Icon*, uint32_t, MenuItemCallback, void*" +Function,+,menu_alloc,Menu*, +Function,+,menu_free,void,Menu* +Function,+,menu_get_view,View*,Menu* +Function,+,menu_reset,void,Menu* +Function,+,menu_set_selected_item,void,"Menu*, uint32_t" +Function,-,mkdtemp,char*,char* +Function,-,mkostemp,int,"char*, int" +Function,-,mkostemps,int,"char*, int, int" +Function,-,mkstemp,int,char* +Function,-,mkstemps,int,"char*, int" +Function,-,mktemp,char*,char* +Function,-,mktime,time_t,tm* +Function,-,mrand48,long, +Function,+,notification_internal_message,void,"NotificationApp*, const NotificationSequence*" +Function,+,notification_internal_message_block,void,"NotificationApp*, const NotificationSequence*" +Function,+,notification_message,void,"NotificationApp*, const NotificationSequence*" +Function,+,notification_message_block,void,"NotificationApp*, const NotificationSequence*" +Function,-,nrand48,long,unsigned short[3] +Function,-,on_exit,int,"void (*)(int, void*), void*" +Function,-,open_memstream,FILE*,"char**, size_t*" +Function,+,path_append,void,"FuriString*, const char*" +Function,+,path_concat,void,"const char*, const char*, FuriString*" +Function,+,path_contains_only_ascii,_Bool,const char* +Function,+,path_extract_basename,void,"const char*, FuriString*" +Function,+,path_extract_dirname,void,"const char*, FuriString*" +Function,+,path_extract_extension,void,"FuriString*, char*, size_t" +Function,+,path_extract_filename,void,"FuriString*, FuriString*, _Bool" +Function,+,path_extract_filename_no_ext,void,"const char*, FuriString*" +Function,-,pcTaskGetName,char*,TaskHandle_t +Function,-,pcTimerGetName,const char*,TimerHandle_t +Function,-,pclose,int,FILE* +Function,-,perror,void,const char* +Function,-,popen,FILE*,"const char*, const char*" +Function,+,popup_alloc,Popup*, +Function,+,popup_disable_timeout,void,Popup* +Function,+,popup_enable_timeout,void,Popup* +Function,+,popup_free,void,Popup* +Function,+,popup_get_view,View*,Popup* +Function,+,popup_reset,void,Popup* +Function,+,popup_set_callback,void,"Popup*, PopupCallback" +Function,+,popup_set_context,void,"Popup*, void*" +Function,+,popup_set_header,void,"Popup*, const char*, uint8_t, uint8_t, Align, Align" +Function,+,popup_set_icon,void,"Popup*, uint8_t, uint8_t, const Icon*" +Function,+,popup_set_text,void,"Popup*, const char*, uint8_t, uint8_t, Align, Align" +Function,+,popup_set_timeout,void,"Popup*, uint32_t" +Function,-,posix_memalign,int,"void**, size_t, size_t" +Function,+,power_enable_low_battery_level_notification,void,"Power*, _Bool" +Function,+,power_get_info,void,"Power*, PowerInfo*" +Function,+,power_get_pubsub,FuriPubSub*,Power* +Function,+,power_is_battery_healthy,_Bool,Power* +Function,+,power_off,void,Power* +Function,+,power_reboot,void,PowerBootMode +Function,-,printf,int,"const char*, ..." +Function,+,property_value_out,void,"PropertyValueContext*, const char*, unsigned int, ..." +Function,+,protocol_dict_alloc,ProtocolDict*,"const ProtocolBase**, size_t" +Function,+,protocol_dict_decoders_feed,ProtocolId,"ProtocolDict*, _Bool, uint32_t" +Function,+,protocol_dict_decoders_feed_by_feature,ProtocolId,"ProtocolDict*, uint32_t, _Bool, uint32_t" +Function,+,protocol_dict_decoders_feed_by_id,ProtocolId,"ProtocolDict*, size_t, _Bool, uint32_t" +Function,+,protocol_dict_decoders_start,void,ProtocolDict* +Function,+,protocol_dict_encoder_start,_Bool,"ProtocolDict*, size_t" +Function,+,protocol_dict_encoder_yield,LevelDuration,"ProtocolDict*, size_t" +Function,+,protocol_dict_free,void,ProtocolDict* +Function,+,protocol_dict_get_data,void,"ProtocolDict*, size_t, uint8_t*, size_t" +Function,+,protocol_dict_get_data_size,size_t,"ProtocolDict*, size_t" +Function,+,protocol_dict_get_features,uint32_t,"ProtocolDict*, size_t" +Function,+,protocol_dict_get_manufacturer,const char*,"ProtocolDict*, size_t" +Function,+,protocol_dict_get_max_data_size,size_t,ProtocolDict* +Function,+,protocol_dict_get_name,const char*,"ProtocolDict*, size_t" +Function,+,protocol_dict_get_protocol_by_name,ProtocolId,"ProtocolDict*, const char*" +Function,+,protocol_dict_get_validate_count,uint32_t,"ProtocolDict*, size_t" +Function,+,protocol_dict_get_write_data,_Bool,"ProtocolDict*, size_t, void*" +Function,+,protocol_dict_render_brief_data,void,"ProtocolDict*, FuriString*, size_t" +Function,+,protocol_dict_render_data,void,"ProtocolDict*, FuriString*, size_t" +Function,+,protocol_dict_set_data,void,"ProtocolDict*, size_t, const uint8_t*, size_t" +Function,-,pselect,int,"int, fd_set*, fd_set*, fd_set*, const timespec*, const sigset_t*" +Function,-,putc,int,"int, FILE*" +Function,-,putc_unlocked,int,"int, FILE*" +Function,-,putchar,int,int +Function,-,putchar_unlocked,int,int +Function,-,putenv,int,char* +Function,-,puts,int,const char* +Function,-,putw,int,"int, FILE*" +Function,-,pvPortCalloc,void*,"size_t, size_t" +Function,-,pvPortMalloc,void*,size_t +Function,-,pvTaskGetThreadLocalStoragePointer,void*,"TaskHandle_t, BaseType_t" +Function,-,pvTaskIncrementMutexHeldCount,TaskHandle_t, +Function,-,pvTimerGetTimerID,void*,const TimerHandle_t +Function,-,pxPortInitialiseStack,StackType_t*,"StackType_t*, TaskFunction_t, void*" +Function,-,qsort,void,"void*, size_t, size_t, __compar_fn_t" +Function,-,qsort_r,void,"void*, size_t, size_t, int (*)(const void*, const void*, void*), void*" +Function,-,quick_exit,void,int +Function,+,rand,int, +Function,-,rand_r,int,unsigned* +Function,+,random,long, +Function,-,rawmemchr,void*,"const void*, int" +Function,-,read_mutex,_Bool,"ValueMutex*, void*, size_t, uint32_t" +Function,+,realloc,void*,"void*, size_t" +Function,-,reallocarray,void*,"void*, size_t, size_t" +Function,-,reallocf,void*,"void*, size_t" +Function,-,realpath,char*,"const char*, char*" +Function,+,release_mutex,_Bool,"ValueMutex*, const void*" +Function,-,remove,int,const char* +Function,-,rename,int,"const char*, const char*" +Function,-,renameat,int,"int, const char*, int, const char*" +Function,-,rewind,void,FILE* +Function,-,rindex,char*,"const char*, int" +Function,+,rpc_session_close,void,RpcSession* +Function,+,rpc_session_feed,size_t,"RpcSession*, uint8_t*, size_t, TickType_t" +Function,+,rpc_session_get_available_size,size_t,RpcSession* +Function,+,rpc_session_open,RpcSession*,Rpc* +Function,+,rpc_session_set_buffer_is_empty_callback,void,"RpcSession*, RpcBufferIsEmptyCallback" +Function,+,rpc_session_set_close_callback,void,"RpcSession*, RpcSessionClosedCallback" +Function,+,rpc_session_set_context,void,"RpcSession*, void*" +Function,+,rpc_session_set_send_bytes_callback,void,"RpcSession*, RpcSendBytesCallback" +Function,+,rpc_session_set_terminated_callback,void,"RpcSession*, RpcSessionTerminatedCallback" +Function,+,rpc_system_app_confirm,void,"RpcAppSystem*, RpcAppSystemEvent, _Bool" +Function,+,rpc_system_app_error_reset,void,RpcAppSystem* +Function,+,rpc_system_app_exchange_data,void,"RpcAppSystem*, const uint8_t*, size_t" +Function,+,rpc_system_app_get_data,const char*,RpcAppSystem* +Function,+,rpc_system_app_send_exited,void,RpcAppSystem* +Function,+,rpc_system_app_send_started,void,RpcAppSystem* +Function,+,rpc_system_app_set_callback,void,"RpcAppSystem*, RpcAppSystemCallback, void*" +Function,+,rpc_system_app_set_data_exchange_callback,void,"RpcAppSystem*, RpcAppSystemDataExchangeCallback, void*" +Function,+,rpc_system_app_set_error_code,void,"RpcAppSystem*, uint32_t" +Function,+,rpc_system_app_set_error_text,void,"RpcAppSystem*, const char*" +Function,-,rpmatch,int,const char* +Function,+,saved_struct_get_payload_size,_Bool,"const char*, uint8_t, uint8_t, size_t*" +Function,+,saved_struct_load,_Bool,"const char*, void*, size_t, uint8_t, uint8_t" +Function,+,saved_struct_save,_Bool,"const char*, void*, size_t, uint8_t, uint8_t" +Function,-,scanf,int,"const char*, ..." +Function,+,scene_manager_alloc,SceneManager*,"const SceneManagerHandlers*, void*" +Function,+,scene_manager_free,void,SceneManager* +Function,+,scene_manager_get_scene_state,uint32_t,"SceneManager*, uint32_t" +Function,+,scene_manager_handle_back_event,_Bool,SceneManager* +Function,+,scene_manager_handle_custom_event,_Bool,"SceneManager*, uint32_t" +Function,+,scene_manager_handle_tick_event,void,SceneManager* +Function,+,scene_manager_has_previous_scene,_Bool,"SceneManager*, uint32_t" +Function,+,scene_manager_next_scene,void,"SceneManager*, uint32_t" +Function,+,scene_manager_previous_scene,_Bool,SceneManager* +Function,+,scene_manager_search_and_switch_to_another_scene,_Bool,"SceneManager*, uint32_t" +Function,+,scene_manager_search_and_switch_to_previous_scene,_Bool,"SceneManager*, uint32_t" +Function,+,scene_manager_search_and_switch_to_previous_scene_one_of,_Bool,"SceneManager*, const uint32_t*, size_t" +Function,+,scene_manager_set_scene_state,void,"SceneManager*, uint32_t, uint32_t" +Function,+,scene_manager_stop,void,SceneManager* +Function,+,sd_api_get_fs_type_text,const char*,SDFsType +Function,-,secure_getenv,char*,const char* +Function,-,seed48,unsigned short*,unsigned short[3] +Function,-,select,int,"int, fd_set*, fd_set*, fd_set*, timeval*" +Function,-,serial_svc_is_started,_Bool, +Function,-,serial_svc_notify_buffer_is_empty,void, +Function,-,serial_svc_set_callbacks,void,"uint16_t, SerialServiceEventCallback, void*" +Function,-,serial_svc_set_rpc_status,void,SerialServiceRpcStatus +Function,-,serial_svc_start,void, +Function,-,serial_svc_stop,void, +Function,-,serial_svc_update_tx,_Bool,"uint8_t*, uint16_t" +Function,+,set_random_name,void,"char*, uint8_t" +Function,-,setbuf,void,"FILE*, char*" +Function,-,setbuffer,void,"FILE*, char*, int" +Function,-,setenv,int,"const char*, const char*, int" +Function,-,setkey,void,const char* +Function,-,setlinebuf,int,FILE* +Function,-,setstate,char*,char* +Function,-,setvbuf,int,"FILE*, char*, int, size_t" +Function,+,sha256,void,"const unsigned char*, unsigned int, unsigned char[32]" +Function,+,sha256_finish,void,"sha256_context*, unsigned char[32]" +Function,+,sha256_process,void,sha256_context* +Function,+,sha256_start,void,sha256_context* +Function,+,sha256_update,void,"sha256_context*, const unsigned char*, unsigned int" +Function,-,siprintf,int,"char*, const char*, ..." +Function,-,siscanf,int,"const char*, const char*, ..." +Function,-,sniprintf,int,"char*, size_t, const char*, ..." +Function,+,snprintf,int,"char*, size_t, const char*, ..." +Function,-,sprintf,int,"char*, const char*, ..." +Function,+,srand,void,unsigned +Function,-,srand48,void,long +Function,-,srandom,void,unsigned +Function,+,sscanf,int,"const char*, const char*, ..." +Function,+,storage_common_copy,FS_Error,"Storage*, const char*, const char*" +Function,+,storage_common_fs_info,FS_Error,"Storage*, const char*, uint64_t*, uint64_t*" +Function,+,storage_common_merge,FS_Error,"Storage*, const char*, const char*" +Function,+,storage_common_mkdir,FS_Error,"Storage*, const char*" +Function,+,storage_common_remove,FS_Error,"Storage*, const char*" +Function,+,storage_common_rename,FS_Error,"Storage*, const char*, const char*" +Function,+,storage_common_stat,FS_Error,"Storage*, const char*, FileInfo*" +Function,+,storage_common_timestamp,FS_Error,"Storage*, const char*, uint32_t*" +Function,+,storage_dir_close,_Bool,File* +Function,+,storage_dir_open,_Bool,"File*, const char*" +Function,+,storage_dir_read,_Bool,"File*, FileInfo*, char*, uint16_t" +Function,-,storage_dir_rewind,_Bool,File* +Function,+,storage_error_get_desc,const char*,FS_Error +Function,+,storage_file_alloc,File*,Storage* +Function,+,storage_file_close,_Bool,File* +Function,+,storage_file_eof,_Bool,File* +Function,+,storage_file_exists,_Bool,"Storage*, const char*" +Function,+,storage_file_free,void,File* +Function,+,storage_file_get_error,FS_Error,File* +Function,+,storage_file_get_error_desc,const char*,File* +Function,-,storage_file_get_internal_error,int32_t,File* +Function,+,storage_file_is_dir,_Bool,File* +Function,+,storage_file_is_open,_Bool,File* +Function,+,storage_file_open,_Bool,"File*, const char*, FS_AccessMode, FS_OpenMode" +Function,+,storage_file_read,uint16_t,"File*, void*, uint16_t" +Function,+,storage_file_seek,_Bool,"File*, uint32_t, _Bool" +Function,+,storage_file_size,uint64_t,File* +Function,-,storage_file_sync,_Bool,File* +Function,+,storage_file_tell,uint64_t,File* +Function,+,storage_file_truncate,_Bool,File* +Function,+,storage_file_write,uint16_t,"File*, const void*, uint16_t" +Function,+,storage_get_next_filename,void,"Storage*, const char*, const char*, const char*, FuriString*, uint8_t" +Function,+,storage_get_pubsub,FuriPubSub*,Storage* +Function,+,storage_int_backup,FS_Error,"Storage*, const char*" +Function,+,storage_int_restore,FS_Error,"Storage*, const char*, Storage_name_converter" +Function,+,storage_sd_format,FS_Error,Storage* +Function,+,storage_sd_info,FS_Error,"Storage*, SDInfo*" +Function,+,storage_sd_status,FS_Error,Storage* +Function,+,storage_sd_unmount,FS_Error,Storage* +Function,+,storage_simply_mkdir,_Bool,"Storage*, const char*" +Function,+,storage_simply_remove,_Bool,"Storage*, const char*" +Function,+,storage_simply_remove_recursive,_Bool,"Storage*, const char*" +Function,-,stpcpy,char*,"char*, const char*" +Function,-,stpncpy,char*,"char*, const char*, size_t" +Function,-,strcasecmp,int,"const char*, const char*" +Function,-,strcasecmp_l,int,"const char*, const char*, locale_t" +Function,+,strcasestr,char*,"const char*, const char*" +Function,-,strcat,char*,"char*, const char*" +Function,+,strchr,char*,"const char*, int" +Function,-,strchrnul,char*,"const char*, int" +Function,+,strcmp,int,"const char*, const char*" +Function,-,strcoll,int,"const char*, const char*" +Function,-,strcoll_l,int,"const char*, const char*, locale_t" +Function,+,strcpy,char*,"char*, const char*" +Function,+,strcspn,size_t,"const char*, const char*" +Function,+,strdup,char*,const char* +Function,+,stream_clean,void,Stream* +Function,+,stream_copy,size_t,"Stream*, Stream*, size_t" +Function,+,stream_copy_full,size_t,"Stream*, Stream*" +Function,+,stream_delete,_Bool,"Stream*, size_t" +Function,+,stream_delete_and_insert,_Bool,"Stream*, size_t, StreamWriteCB, const void*" +Function,+,stream_delete_and_insert_char,_Bool,"Stream*, size_t, char" +Function,+,stream_delete_and_insert_cstring,_Bool,"Stream*, size_t, const char*" +Function,+,stream_delete_and_insert_format,_Bool,"Stream*, size_t, const char*, ..." +Function,+,stream_delete_and_insert_string,_Bool,"Stream*, size_t, FuriString*" +Function,+,stream_delete_and_insert_vaformat,_Bool,"Stream*, size_t, const char*, va_list" +Function,+,stream_dump_data,void,Stream* +Function,+,stream_eof,_Bool,Stream* +Function,+,stream_free,void,Stream* +Function,+,stream_insert,_Bool,"Stream*, const uint8_t*, size_t" +Function,+,stream_insert_char,_Bool,"Stream*, char" +Function,+,stream_insert_cstring,_Bool,"Stream*, const char*" +Function,+,stream_insert_format,_Bool,"Stream*, const char*, ..." +Function,+,stream_insert_string,_Bool,"Stream*, FuriString*" +Function,+,stream_insert_vaformat,_Bool,"Stream*, const char*, va_list" +Function,+,stream_load_from_file,size_t,"Stream*, Storage*, const char*" +Function,+,stream_read,size_t,"Stream*, uint8_t*, size_t" +Function,+,stream_read_line,_Bool,"Stream*, FuriString*" +Function,+,stream_rewind,_Bool,Stream* +Function,+,stream_save_to_file,size_t,"Stream*, Storage*, const char*, FS_OpenMode" +Function,+,stream_seek,_Bool,"Stream*, int32_t, StreamOffset" +Function,+,stream_seek_to_char,_Bool,"Stream*, char, StreamDirection" +Function,+,stream_size,size_t,Stream* +Function,+,stream_split,_Bool,"Stream*, Stream*, Stream*" +Function,+,stream_tell,size_t,Stream* +Function,+,stream_write,size_t,"Stream*, const uint8_t*, size_t" +Function,+,stream_write_char,size_t,"Stream*, char" +Function,+,stream_write_cstring,size_t,"Stream*, const char*" +Function,+,stream_write_format,size_t,"Stream*, const char*, ..." +Function,+,stream_write_string,size_t,"Stream*, FuriString*" +Function,+,stream_write_vaformat,size_t,"Stream*, const char*, va_list" +Function,-,strerror,char*,int +Function,-,strerror_l,char*,"int, locale_t" +Function,-,strerror_r,char*,"int, char*, size_t" +Function,-,strftime,size_t,"char*, size_t, const char*, const tm*" +Function,-,strftime_l,size_t,"char*, size_t, const char*, const tm*, locale_t" +Function,+,string_stream_alloc,Stream*, +Function,-,strlcat,size_t,"char*, const char*, size_t" +Function,+,strlcpy,size_t,"char*, const char*, size_t" +Function,+,strlen,size_t,const char* +Function,-,strlwr,char*,char* +Function,+,strncasecmp,int,"const char*, const char*, size_t" +Function,-,strncasecmp_l,int,"const char*, const char*, size_t, locale_t" +Function,-,strncat,char*,"char*, const char*, size_t" +Function,+,strncmp,int,"const char*, const char*, size_t" +Function,+,strncpy,char*,"char*, const char*, size_t" +Function,-,strndup,char*,"const char*, size_t" +Function,-,strnlen,size_t,"const char*, size_t" +Function,-,strnstr,char*,"const char*, const char*, size_t" +Function,-,strpbrk,char*,"const char*, const char*" +Function,-,strptime,char*,"const char*, const char*, tm*" +Function,-,strptime_l,char*,"const char*, const char*, tm*, locale_t" +Function,+,strrchr,char*,"const char*, int" +Function,-,strsep,char*,"char**, const char*" +Function,-,strsignal,char*,int +Function,+,strspn,size_t,"const char*, const char*" +Function,+,strstr,char*,"const char*, const char*" +Function,-,strtod,double,"const char*, char**" +Function,-,strtod_l,double,"const char*, char**, locale_t" +Function,+,strtof,float,"const char*, char**" +Function,-,strtof_l,float,"const char*, char**, locale_t" +Function,-,strtok,char*,"char*, const char*" +Function,-,strtok_r,char*,"char*, const char*, char**" +Function,+,strtol,long,"const char*, char**, int" +Function,-,strtol_l,long,"const char*, char**, int, locale_t" +Function,-,strtold,long double,"const char*, char**" +Function,-,strtold_l,long double,"const char*, char**, locale_t" +Function,-,strtoll,long long,"const char*, char**, int" +Function,-,strtoll_l,long long,"const char*, char**, int, locale_t" +Function,+,strtoul,unsigned long,"const char*, char**, int" +Function,-,strtoul_l,unsigned long,"const char*, char**, int, locale_t" +Function,+,strtoull,unsigned long long,"const char*, char**, int" +Function,-,strtoull_l,unsigned long long,"const char*, char**, int, locale_t" +Function,-,strupr,char*,char* +Function,-,strverscmp,int,"const char*, const char*" +Function,-,strxfrm,size_t,"char*, const char*, size_t" +Function,-,strxfrm_l,size_t,"char*, const char*, size_t, locale_t" +Function,+,submenu_add_item,void,"Submenu*, const char*, uint32_t, SubmenuItemCallback, void*" +Function,+,submenu_alloc,Submenu*, +Function,+,submenu_free,void,Submenu* +Function,+,submenu_get_view,View*,Submenu* +Function,+,submenu_reset,void,Submenu* +Function,+,submenu_set_header,void,"Submenu*, const char*" +Function,+,submenu_set_selected_item,void,"Submenu*, uint32_t" +Function,-,system,int,const char* +Function,+,tar_archive_add_dir,_Bool,"TarArchive*, const char*, const char*" +Function,+,tar_archive_add_file,_Bool,"TarArchive*, const char*, const char*, const int32_t" +Function,+,tar_archive_alloc,TarArchive*,Storage* +Function,+,tar_archive_dir_add_element,_Bool,"TarArchive*, const char*" +Function,+,tar_archive_file_add_data_block,_Bool,"TarArchive*, const uint8_t*, const int32_t" +Function,+,tar_archive_file_add_header,_Bool,"TarArchive*, const char*, const int32_t" +Function,+,tar_archive_file_finalize,_Bool,TarArchive* +Function,+,tar_archive_finalize,_Bool,TarArchive* +Function,+,tar_archive_free,void,TarArchive* +Function,+,tar_archive_get_entries_count,int32_t,TarArchive* +Function,+,tar_archive_open,_Bool,"TarArchive*, const char*, TarOpenMode" +Function,+,tar_archive_set_file_callback,void,"TarArchive*, tar_unpack_file_cb, void*" +Function,+,tar_archive_store_data,_Bool,"TarArchive*, const char*, const uint8_t*, const int32_t" +Function,+,tar_archive_unpack_file,_Bool,"TarArchive*, const char*, const char*" +Function,+,tar_archive_unpack_to,_Bool,"TarArchive*, const char*, Storage_name_converter" +Function,-,tempnam,char*,"const char*, const char*" +Function,+,text_box_alloc,TextBox*, +Function,+,text_box_free,void,TextBox* +Function,+,text_box_get_view,View*,TextBox* +Function,+,text_box_reset,void,TextBox* +Function,+,text_box_set_focus,void,"TextBox*, TextBoxFocus" +Function,+,text_box_set_font,void,"TextBox*, TextBoxFont" +Function,+,text_box_set_text,void,"TextBox*, const char*" +Function,+,text_input_alloc,TextInput*, +Function,+,text_input_free,void,TextInput* +Function,+,text_input_get_validator_callback,TextInputValidatorCallback,TextInput* +Function,+,text_input_get_validator_callback_context,void*,TextInput* +Function,+,text_input_get_view,View*,TextInput* +Function,+,text_input_reset,void,TextInput* +Function,+,text_input_set_header_text,void,"TextInput*, const char*" +Function,+,text_input_set_result_callback,void,"TextInput*, TextInputCallback, void*, char*, size_t, _Bool" +Function,+,text_input_set_validator,void,"TextInput*, TextInputValidatorCallback, void*" +Function,-,time,time_t,time_t* +Function,-,timingsafe_bcmp,int,"const void*, const void*, size_t" +Function,-,timingsafe_memcmp,int,"const void*, const void*, size_t" +Function,-,tmpfile,FILE*, +Function,-,tmpnam,char*,char* +Function,-,toascii,int,int +Function,-,toascii_l,int,"int, locale_t" +Function,-,tolower,int,int +Function,-,tolower_l,int,"int, locale_t" +Function,-,toupper,int,int +Function,-,toupper_l,int,"int, locale_t" +Function,-,tzset,void, +Function,-,uECC_compress,void,"const uint8_t*, uint8_t*, uECC_Curve" +Function,+,uECC_compute_public_key,int,"const uint8_t*, uint8_t*, uECC_Curve" +Function,-,uECC_curve_private_key_size,int,uECC_Curve +Function,-,uECC_curve_public_key_size,int,uECC_Curve +Function,-,uECC_decompress,void,"const uint8_t*, uint8_t*, uECC_Curve" +Function,-,uECC_get_rng,uECC_RNG_Function, +Function,-,uECC_make_key,int,"uint8_t*, uint8_t*, uECC_Curve" +Function,-,uECC_secp160r1,uECC_Curve, +Function,-,uECC_secp192r1,uECC_Curve, +Function,-,uECC_secp224r1,uECC_Curve, +Function,-,uECC_secp256k1,uECC_Curve, +Function,+,uECC_secp256r1,uECC_Curve, +Function,+,uECC_set_rng,void,uECC_RNG_Function +Function,-,uECC_shared_secret,int,"const uint8_t*, const uint8_t*, uint8_t*, uECC_Curve" +Function,+,uECC_sign,int,"const uint8_t*, const uint8_t*, unsigned, uint8_t*, uECC_Curve" +Function,-,uECC_sign_deterministic,int,"const uint8_t*, const uint8_t*, unsigned, const uECC_HashContext*, uint8_t*, uECC_Curve" +Function,-,uECC_valid_public_key,int,"const uint8_t*, uECC_Curve" +Function,-,uECC_verify,int,"const uint8_t*, const uint8_t*, unsigned, const uint8_t*, uECC_Curve" +Function,-,ulTaskGenericNotifyTake,uint32_t,"UBaseType_t, BaseType_t, TickType_t" +Function,-,ulTaskGenericNotifyValueClear,uint32_t,"TaskHandle_t, UBaseType_t, uint32_t" +Function,-,ulTaskGetIdleRunTimeCounter,uint32_t, +Function,-,ulTaskGetIdleRunTimePercent,uint32_t, +Function,-,ungetc,int,"int, FILE*" +Function,-,unsetenv,int,const char* +Function,-,usbd_poll,void,usbd_device* +Function,-,utoa,char*,"unsigned, char*, int" +Function,-,uxListRemove,UBaseType_t,ListItem_t* +Function,-,uxTaskGetNumberOfTasks,UBaseType_t, +Function,-,uxTaskGetStackHighWaterMark,UBaseType_t,TaskHandle_t +Function,-,uxTaskGetStackHighWaterMark2,uint16_t,TaskHandle_t +Function,-,uxTaskGetSystemState,UBaseType_t,"TaskStatus_t*, const UBaseType_t, uint32_t*" +Function,-,uxTaskGetTaskNumber,UBaseType_t,TaskHandle_t +Function,-,uxTaskPriorityGet,UBaseType_t,const TaskHandle_t +Function,-,uxTaskPriorityGetFromISR,UBaseType_t,const TaskHandle_t +Function,-,uxTaskResetEventItemValue,TickType_t, +Function,-,uxTimerGetReloadMode,UBaseType_t,TimerHandle_t +Function,-,uxTimerGetTimerNumber,UBaseType_t,TimerHandle_t +Function,-,vApplicationGetIdleTaskMemory,void,"StaticTask_t**, StackType_t**, uint32_t*" +Function,-,vApplicationGetTimerTaskMemory,void,"StaticTask_t**, StackType_t**, uint32_t*" +Function,-,vListInitialise,void,List_t* +Function,-,vListInitialiseItem,void,ListItem_t* +Function,-,vListInsert,void,"List_t*, ListItem_t*" +Function,-,vListInsertEnd,void,"List_t*, ListItem_t*" +Function,-,vPortDefineHeapRegions,void,const HeapRegion_t* +Function,-,vPortEndScheduler,void, +Function,+,vPortEnterCritical,void, +Function,+,vPortExitCritical,void, +Function,-,vPortFree,void,void* +Function,-,vPortGetHeapStats,void,HeapStats_t* +Function,-,vPortInitialiseBlocks,void, +Function,-,vPortSuppressTicksAndSleep,void,TickType_t +Function,-,vTaskAllocateMPURegions,void,"TaskHandle_t, const MemoryRegion_t*" +Function,-,vTaskDelay,void,const TickType_t +Function,-,vTaskDelete,void,TaskHandle_t +Function,-,vTaskEndScheduler,void, +Function,-,vTaskGenericNotifyGiveFromISR,void,"TaskHandle_t, UBaseType_t, BaseType_t*" +Function,-,vTaskGetInfo,void,"TaskHandle_t, TaskStatus_t*, BaseType_t, eTaskState" +Function,-,vTaskGetRunTimeStats,void,char* +Function,-,vTaskInternalSetTimeOutState,void,TimeOut_t* +Function,-,vTaskList,void,char* +Function,-,vTaskMissedYield,void, +Function,-,vTaskPlaceOnEventList,void,"List_t*, const TickType_t" +Function,-,vTaskPlaceOnEventListRestricted,void,"List_t*, TickType_t, const BaseType_t" +Function,-,vTaskPlaceOnUnorderedEventList,void,"List_t*, const TickType_t, const TickType_t" +Function,-,vTaskPriorityDisinheritAfterTimeout,void,"const TaskHandle_t, UBaseType_t" +Function,+,vTaskPrioritySet,void,"TaskHandle_t, UBaseType_t" +Function,-,vTaskRemoveFromUnorderedEventList,void,"ListItem_t*, const TickType_t" +Function,-,vTaskResume,void,TaskHandle_t +Function,-,vTaskSetTaskNumber,void,"TaskHandle_t, const UBaseType_t" +Function,-,vTaskSetThreadLocalStoragePointer,void,"TaskHandle_t, BaseType_t, void*" +Function,-,vTaskSetTimeOutState,void,TimeOut_t* +Function,-,vTaskStartScheduler,void, +Function,-,vTaskStepTick,void,TickType_t +Function,-,vTaskSuspend,void,TaskHandle_t +Function,-,vTaskSuspendAll,void, +Function,-,vTaskSwitchContext,void, +Function,-,vTimerSetReloadMode,void,"TimerHandle_t, const BaseType_t" +Function,-,vTimerSetTimerID,void,"TimerHandle_t, void*" +Function,-,vTimerSetTimerNumber,void,"TimerHandle_t, UBaseType_t" +Function,+,validator_is_file_alloc_init,ValidatorIsFile*,"const char*, const char*, const char*" +Function,+,validator_is_file_callback,_Bool,"const char*, FuriString*, void*" +Function,+,validator_is_file_free,void,ValidatorIsFile* +Function,+,value_index_bool,uint8_t,"const _Bool, const _Bool[], uint8_t" +Function,+,value_index_float,uint8_t,"const float, const float[], uint8_t" +Function,+,value_index_uint32,uint8_t,"const uint32_t, const uint32_t[], uint8_t" +Function,+,variable_item_get_context,void*,VariableItem* +Function,+,variable_item_get_current_value_index,uint8_t,VariableItem* +Function,+,variable_item_list_add,VariableItem*,"VariableItemList*, const char*, uint8_t, VariableItemChangeCallback, void*" +Function,+,variable_item_list_alloc,VariableItemList*, +Function,+,variable_item_list_free,void,VariableItemList* +Function,+,variable_item_list_get_selected_item_index,uint8_t,VariableItemList* +Function,+,variable_item_list_get_view,View*,VariableItemList* +Function,+,variable_item_list_reset,void,VariableItemList* +Function,+,variable_item_list_set_enter_callback,void,"VariableItemList*, VariableItemListEnterCallback, void*" +Function,+,variable_item_list_set_selected_item,void,"VariableItemList*, uint8_t" +Function,+,variable_item_set_current_value_index,void,"VariableItem*, uint8_t" +Function,+,variable_item_set_current_value_text,void,"VariableItem*, const char*" +Function,+,variable_item_set_values_count,void,"VariableItem*, uint8_t" +Function,-,vasiprintf,int,"char**, const char*, __gnuc_va_list" +Function,-,vasniprintf,char*,"char*, size_t*, const char*, __gnuc_va_list" +Function,-,vasnprintf,char*,"char*, size_t*, const char*, __gnuc_va_list" +Function,-,vasprintf,int,"char**, const char*, __gnuc_va_list" +Function,-,vdiprintf,int,"int, const char*, __gnuc_va_list" +Function,-,vdprintf,int,"int, const char*, __gnuc_va_list" +Function,+,version_get,const Version*, +Function,+,version_get_builddate,const char*,const Version* +Function,+,version_get_dirty_flag,_Bool,const Version* +Function,+,version_get_gitbranch,const char*,const Version* +Function,+,version_get_gitbranchnum,const char*,const Version* +Function,+,version_get_githash,const char*,const Version* +Function,+,version_get_target,uint8_t,const Version* +Function,+,version_get_version,const char*,const Version* +Function,-,vfiprintf,int,"FILE*, const char*, __gnuc_va_list" +Function,-,vfiscanf,int,"FILE*, const char*, __gnuc_va_list" +Function,-,vfprintf,int,"FILE*, const char*, __gnuc_va_list" +Function,-,vfscanf,int,"FILE*, const char*, __gnuc_va_list" +Function,+,view_alloc,View*, +Function,+,view_allocate_model,void,"View*, ViewModelType, size_t" +Function,+,view_commit_model,void,"View*, _Bool" +Function,+,view_dispatcher_add_view,void,"ViewDispatcher*, uint32_t, View*" +Function,+,view_dispatcher_alloc,ViewDispatcher*, +Function,+,view_dispatcher_attach_to_gui,void,"ViewDispatcher*, Gui*, ViewDispatcherType" +Function,+,view_dispatcher_enable_queue,void,ViewDispatcher* +Function,+,view_dispatcher_free,void,ViewDispatcher* +Function,+,view_dispatcher_remove_view,void,"ViewDispatcher*, uint32_t" +Function,+,view_dispatcher_run,void,ViewDispatcher* +Function,+,view_dispatcher_send_custom_event,void,"ViewDispatcher*, uint32_t" +Function,+,view_dispatcher_send_to_back,void,ViewDispatcher* +Function,+,view_dispatcher_send_to_front,void,ViewDispatcher* +Function,+,view_dispatcher_set_custom_event_callback,void,"ViewDispatcher*, ViewDispatcherCustomEventCallback" +Function,+,view_dispatcher_set_event_callback_context,void,"ViewDispatcher*, void*" +Function,+,view_dispatcher_set_navigation_event_callback,void,"ViewDispatcher*, ViewDispatcherNavigationEventCallback" +Function,+,view_dispatcher_set_tick_event_callback,void,"ViewDispatcher*, ViewDispatcherTickEventCallback, uint32_t" +Function,+,view_dispatcher_stop,void,ViewDispatcher* +Function,+,view_dispatcher_switch_to_view,void,"ViewDispatcher*, uint32_t" +Function,+,view_free,void,View* +Function,+,view_free_model,void,View* +Function,+,view_get_model,void*,View* +Function,+,view_port_alloc,ViewPort*, +Function,+,view_port_draw_callback_set,void,"ViewPort*, ViewPortDrawCallback, void*" +Function,+,view_port_enabled_set,void,"ViewPort*, _Bool" +Function,+,view_port_free,void,ViewPort* +Function,+,view_port_get_height,uint8_t,ViewPort* +Function,+,view_port_get_orientation,ViewPortOrientation,const ViewPort* +Function,+,view_port_get_width,uint8_t,ViewPort* +Function,+,view_port_input_callback_set,void,"ViewPort*, ViewPortInputCallback, void*" +Function,+,view_port_is_enabled,_Bool,ViewPort* +Function,+,view_port_set_height,void,"ViewPort*, uint8_t" +Function,+,view_port_set_orientation,void,"ViewPort*, ViewPortOrientation" +Function,+,view_port_set_width,void,"ViewPort*, uint8_t" +Function,+,view_port_update,void,ViewPort* +Function,+,view_set_context,void,"View*, void*" +Function,+,view_set_custom_callback,void,"View*, ViewCustomCallback" +Function,+,view_set_draw_callback,void,"View*, ViewDrawCallback" +Function,+,view_set_enter_callback,void,"View*, ViewCallback" +Function,+,view_set_exit_callback,void,"View*, ViewCallback" +Function,+,view_set_input_callback,void,"View*, ViewInputCallback" +Function,+,view_set_orientation,void,"View*, ViewOrientation" +Function,+,view_set_previous_callback,void,"View*, ViewNavigationCallback" +Function,+,view_set_update_callback,void,"View*, ViewUpdateCallback" +Function,+,view_set_update_callback_context,void,"View*, void*" +Function,+,view_stack_add_view,void,"ViewStack*, View*" +Function,+,view_stack_alloc,ViewStack*, +Function,+,view_stack_free,void,ViewStack* +Function,+,view_stack_get_view,View*,ViewStack* +Function,+,view_stack_remove_view,void,"ViewStack*, View*" +Function,+,view_tie_icon_animation,void,"View*, IconAnimation*" +Function,-,viprintf,int,"const char*, __gnuc_va_list" +Function,-,viscanf,int,"const char*, __gnuc_va_list" +Function,-,vprintf,int,"const char*, __gnuc_va_list" +Function,-,vscanf,int,"const char*, __gnuc_va_list" +Function,-,vsiprintf,int,"char*, const char*, __gnuc_va_list" +Function,-,vsiscanf,int,"const char*, const char*, __gnuc_va_list" +Function,-,vsniprintf,int,"char*, size_t, const char*, __gnuc_va_list" +Function,-,vsnprintf,int,"char*, size_t, const char*, __gnuc_va_list" +Function,-,vsprintf,int,"char*, const char*, __gnuc_va_list" +Function,-,vsscanf,int,"const char*, const char*, __gnuc_va_list" +Function,-,wcstombs,size_t,"char*, const wchar_t*, size_t" +Function,-,wctomb,int,"char*, wchar_t" +Function,+,widget_add_button_element,void,"Widget*, GuiButtonType, const char*, ButtonCallback, void*" +Function,+,widget_add_frame_element,void,"Widget*, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t" +Function,+,widget_add_icon_element,void,"Widget*, uint8_t, uint8_t, const Icon*" +Function,+,widget_add_string_element,void,"Widget*, uint8_t, uint8_t, Align, Align, Font, const char*" +Function,+,widget_add_string_multiline_element,void,"Widget*, uint8_t, uint8_t, Align, Align, Font, const char*" +Function,+,widget_add_text_box_element,void,"Widget*, uint8_t, uint8_t, uint8_t, uint8_t, Align, Align, const char*, _Bool" +Function,+,widget_add_text_scroll_element,void,"Widget*, uint8_t, uint8_t, uint8_t, uint8_t, const char*" +Function,+,widget_alloc,Widget*, +Function,+,widget_free,void,Widget* +Function,+,widget_get_view,View*,Widget* +Function,+,widget_reset,void,Widget* +Function,-,write_mutex,_Bool,"ValueMutex*, void*, size_t, uint32_t" +Function,-,xPortGetFreeHeapSize,size_t, +Function,-,xPortGetMinimumEverFreeHeapSize,size_t, +Function,-,xPortStartScheduler,BaseType_t, +Function,-,xTaskAbortDelay,BaseType_t,TaskHandle_t +Function,-,xTaskCallApplicationTaskHook,BaseType_t,"TaskHandle_t, void*" +Function,-,xTaskCatchUpTicks,BaseType_t,TickType_t +Function,-,xTaskCheckForTimeOut,BaseType_t,"TimeOut_t*, TickType_t*" +Function,-,xTaskCreate,BaseType_t,"TaskFunction_t, const char*, const uint16_t, void*, UBaseType_t, TaskHandle_t*" +Function,-,xTaskCreateStatic,TaskHandle_t,"TaskFunction_t, const char*, const uint32_t, void*, UBaseType_t, StackType_t*, StaticTask_t*" +Function,-,xTaskDelayUntil,BaseType_t,"TickType_t*, const TickType_t" +Function,-,xTaskGenericNotify,BaseType_t,"TaskHandle_t, UBaseType_t, uint32_t, eNotifyAction, uint32_t*" +Function,-,xTaskGenericNotifyFromISR,BaseType_t,"TaskHandle_t, UBaseType_t, uint32_t, eNotifyAction, uint32_t*, BaseType_t*" +Function,-,xTaskGenericNotifyStateClear,BaseType_t,"TaskHandle_t, UBaseType_t" +Function,-,xTaskGenericNotifyWait,BaseType_t,"UBaseType_t, uint32_t, uint32_t, uint32_t*, TickType_t" +Function,-,xTaskGetCurrentTaskHandle,TaskHandle_t, +Function,+,xTaskGetHandle,TaskHandle_t,const char* +Function,-,xTaskGetIdleTaskHandle,TaskHandle_t, +Function,+,xTaskGetSchedulerState,BaseType_t, +Function,+,xTaskGetTickCount,TickType_t, +Function,-,xTaskGetTickCountFromISR,TickType_t, +Function,-,xTaskIncrementTick,BaseType_t, +Function,-,xTaskPriorityDisinherit,BaseType_t,const TaskHandle_t +Function,-,xTaskPriorityInherit,BaseType_t,const TaskHandle_t +Function,-,xTaskRemoveFromEventList,BaseType_t,const List_t* +Function,-,xTaskResumeAll,BaseType_t, +Function,-,xTaskResumeFromISR,BaseType_t,TaskHandle_t +Function,-,xTimerCreate,TimerHandle_t,"const char*, const TickType_t, const BaseType_t, void*, TimerCallbackFunction_t" +Function,-,xTimerCreateStatic,TimerHandle_t,"const char*, const TickType_t, const BaseType_t, void*, TimerCallbackFunction_t, StaticTimer_t*" +Function,-,xTimerCreateTimerTask,BaseType_t, +Function,-,xTimerGenericCommand,BaseType_t,"TimerHandle_t, const BaseType_t, const TickType_t, BaseType_t*, const TickType_t" +Function,-,xTimerGetExpiryTime,TickType_t,TimerHandle_t +Function,-,xTimerGetPeriod,TickType_t,TimerHandle_t +Function,-,xTimerGetReloadMode,BaseType_t,TimerHandle_t +Function,-,xTimerGetTimerDaemonTaskHandle,TaskHandle_t, +Function,-,xTimerIsTimerActive,BaseType_t,TimerHandle_t +Function,-,xTimerPendFunctionCall,BaseType_t,"PendedFunction_t, void*, uint32_t, TickType_t" +Function,-,xTimerPendFunctionCallFromISR,BaseType_t,"PendedFunction_t, void*, uint32_t, BaseType_t*" +Variable,-,AHBPrescTable,const uint32_t[16], +Variable,-,APBPrescTable,const uint32_t[8], +Variable,-,ITM_RxBuffer,volatile int32_t, +Variable,-,MSIRangeTable,const uint32_t[16], +Variable,-,SmpsPrescalerTable,const uint32_t[4][6], +Variable,+,SystemCoreClock,uint32_t, +Variable,+,_ctype_,const char[], +Variable,-,_daylight,int, +Variable,+,_global_impure_ptr,_reent*, +Variable,+,_impure_ptr,_reent*, +Variable,-,_sys_errlist,const char*[], +Variable,-,_sys_nerr,int, +Variable,-,_timezone,long, +Variable,-,_tzname,char*[2], +Variable,+,cli_vcp,CliSession, +Variable,+,furi_hal_i2c_bus_external,FuriHalI2cBus, +Variable,+,furi_hal_i2c_bus_power,FuriHalI2cBus, +Variable,+,furi_hal_i2c_handle_external,FuriHalI2cBusHandle, +Variable,+,furi_hal_i2c_handle_power,FuriHalI2cBusHandle, +Variable,+,furi_hal_sd_spi_handle,FuriHalSpiBusHandle*, +Variable,+,furi_hal_spi_bus_d,FuriHalSpiBus, +Variable,+,furi_hal_spi_bus_handle_display,FuriHalSpiBusHandle, +Variable,+,furi_hal_spi_bus_handle_external,FuriHalSpiBusHandle, +Variable,+,furi_hal_spi_bus_handle_sd_fast,FuriHalSpiBusHandle, +Variable,+,furi_hal_spi_bus_handle_sd_slow,FuriHalSpiBusHandle, +Variable,+,furi_hal_spi_bus_r,FuriHalSpiBus, +Variable,+,furi_hal_spi_preset_1edge_low_16m,const LL_SPI_InitTypeDef, +Variable,+,furi_hal_spi_preset_1edge_low_2m,const LL_SPI_InitTypeDef, +Variable,+,furi_hal_spi_preset_1edge_low_4m,const LL_SPI_InitTypeDef, +Variable,+,furi_hal_spi_preset_1edge_low_8m,const LL_SPI_InitTypeDef, +Variable,+,furi_hal_spi_preset_2edge_low_8m,const LL_SPI_InitTypeDef, +Variable,+,gpio_button_back,const GpioPin, +Variable,+,gpio_button_down,const GpioPin, +Variable,+,gpio_button_left,const GpioPin, +Variable,+,gpio_button_ok,const GpioPin, +Variable,+,gpio_button_right,const GpioPin, +Variable,+,gpio_button_up,const GpioPin, +Variable,+,gpio_display_cs,const GpioPin, +Variable,+,gpio_display_di,const GpioPin, +Variable,+,gpio_display_rst_n,const GpioPin, +Variable,+,gpio_ext_pa0,const GpioPin, +Variable,+,gpio_ext_pa1,const GpioPin, +Variable,+,gpio_ext_pa15,const GpioPin, +Variable,+,gpio_ext_pa2,const GpioPin, +Variable,+,gpio_ext_pa4,const GpioPin, +Variable,+,gpio_ext_pa5,const GpioPin, +Variable,+,gpio_ext_pa6,const GpioPin, +Variable,+,gpio_ext_pa7,const GpioPin, +Variable,+,gpio_ext_pb13,const GpioPin, +Variable,+,gpio_ext_pb2,const GpioPin, +Variable,+,gpio_ext_pb3,const GpioPin, +Variable,+,gpio_ext_pb4,const GpioPin, +Variable,+,gpio_ext_pb5,const GpioPin, +Variable,+,gpio_ext_pb9,const GpioPin, +Variable,+,gpio_ext_pc0,const GpioPin, +Variable,+,gpio_ext_pc1,const GpioPin, +Variable,+,gpio_ext_pc3,const GpioPin, +Variable,+,gpio_ext_pc4,const GpioPin, +Variable,+,gpio_ext_pc5,const GpioPin, +Variable,+,gpio_ext_pd0,const GpioPin, +Variable,+,gpio_ext_pe4,const GpioPin, +Variable,+,gpio_i2c_power_scl,const GpioPin, +Variable,+,gpio_i2c_power_sda,const GpioPin, +Variable,+,gpio_pins,const GpioPinRecord[], +Variable,+,gpio_pins_count,const size_t, +Variable,+,gpio_sdcard_cd,const GpioPin, +Variable,+,gpio_sdcard_cs,const GpioPin, +Variable,+,gpio_speaker,const GpioPin, +Variable,+,gpio_spi_d_miso,const GpioPin, +Variable,+,gpio_spi_d_mosi,const GpioPin, +Variable,+,gpio_spi_d_sck,const GpioPin, +Variable,+,gpio_usart_rx,const GpioPin, +Variable,+,gpio_usart_tx,const GpioPin, +Variable,+,gpio_usb_dm,const GpioPin, +Variable,+,gpio_usb_dp,const GpioPin, +Variable,+,ibutton_gpio,const GpioPin, +Variable,+,input_pins,const InputPin[], +Variable,+,input_pins_count,const size_t, +Variable,+,message_blink_set_color_blue,const NotificationMessage, +Variable,+,message_blink_set_color_cyan,const NotificationMessage, +Variable,+,message_blink_set_color_green,const NotificationMessage, +Variable,+,message_blink_set_color_magenta,const NotificationMessage, +Variable,+,message_blink_set_color_red,const NotificationMessage, +Variable,+,message_blink_set_color_white,const NotificationMessage, +Variable,+,message_blink_set_color_yellow,const NotificationMessage, +Variable,+,message_blink_start_10,const NotificationMessage, +Variable,+,message_blink_start_100,const NotificationMessage, +Variable,+,message_blink_stop,const NotificationMessage, +Variable,+,message_blue_0,const NotificationMessage, +Variable,+,message_blue_255,const NotificationMessage, +Variable,+,message_click,const NotificationMessage, +Variable,+,message_delay_1,const NotificationMessage, +Variable,+,message_delay_10,const NotificationMessage, +Variable,+,message_delay_100,const NotificationMessage, +Variable,+,message_delay_1000,const NotificationMessage, +Variable,+,message_delay_25,const NotificationMessage, +Variable,+,message_delay_250,const NotificationMessage, +Variable,+,message_delay_50,const NotificationMessage, +Variable,+,message_delay_500,const NotificationMessage, +Variable,+,message_display_backlight_enforce_auto,const NotificationMessage, +Variable,+,message_display_backlight_enforce_on,const NotificationMessage, +Variable,+,message_display_backlight_off,const NotificationMessage, +Variable,+,message_display_backlight_on,const NotificationMessage, +Variable,+,message_do_not_reset,const NotificationMessage, +Variable,+,message_force_display_brightness_setting_1f,const NotificationMessage, +Variable,+,message_force_speaker_volume_setting_1f,const NotificationMessage, +Variable,+,message_force_vibro_setting_off,const NotificationMessage, +Variable,+,message_force_vibro_setting_on,const NotificationMessage, +Variable,+,message_green_0,const NotificationMessage, +Variable,+,message_green_255,const NotificationMessage, +Variable,+,message_note_a0,const NotificationMessage, +Variable,+,message_note_a1,const NotificationMessage, +Variable,+,message_note_a2,const NotificationMessage, +Variable,+,message_note_a3,const NotificationMessage, +Variable,+,message_note_a4,const NotificationMessage, +Variable,+,message_note_a5,const NotificationMessage, +Variable,+,message_note_a6,const NotificationMessage, +Variable,+,message_note_a7,const NotificationMessage, +Variable,+,message_note_a8,const NotificationMessage, +Variable,+,message_note_as0,const NotificationMessage, +Variable,+,message_note_as1,const NotificationMessage, +Variable,+,message_note_as2,const NotificationMessage, +Variable,+,message_note_as3,const NotificationMessage, +Variable,+,message_note_as4,const NotificationMessage, +Variable,+,message_note_as5,const NotificationMessage, +Variable,+,message_note_as6,const NotificationMessage, +Variable,+,message_note_as7,const NotificationMessage, +Variable,+,message_note_as8,const NotificationMessage, +Variable,+,message_note_b0,const NotificationMessage, +Variable,+,message_note_b1,const NotificationMessage, +Variable,+,message_note_b2,const NotificationMessage, +Variable,+,message_note_b3,const NotificationMessage, +Variable,+,message_note_b4,const NotificationMessage, +Variable,+,message_note_b5,const NotificationMessage, +Variable,+,message_note_b6,const NotificationMessage, +Variable,+,message_note_b7,const NotificationMessage, +Variable,+,message_note_b8,const NotificationMessage, +Variable,+,message_note_c0,const NotificationMessage, +Variable,+,message_note_c1,const NotificationMessage, +Variable,+,message_note_c2,const NotificationMessage, +Variable,+,message_note_c3,const NotificationMessage, +Variable,+,message_note_c4,const NotificationMessage, +Variable,+,message_note_c5,const NotificationMessage, +Variable,+,message_note_c6,const NotificationMessage, +Variable,+,message_note_c7,const NotificationMessage, +Variable,+,message_note_c8,const NotificationMessage, +Variable,+,message_note_cs0,const NotificationMessage, +Variable,+,message_note_cs1,const NotificationMessage, +Variable,+,message_note_cs2,const NotificationMessage, +Variable,+,message_note_cs3,const NotificationMessage, +Variable,+,message_note_cs4,const NotificationMessage, +Variable,+,message_note_cs5,const NotificationMessage, +Variable,+,message_note_cs6,const NotificationMessage, +Variable,+,message_note_cs7,const NotificationMessage, +Variable,+,message_note_cs8,const NotificationMessage, +Variable,+,message_note_d0,const NotificationMessage, +Variable,+,message_note_d1,const NotificationMessage, +Variable,+,message_note_d2,const NotificationMessage, +Variable,+,message_note_d3,const NotificationMessage, +Variable,+,message_note_d4,const NotificationMessage, +Variable,+,message_note_d5,const NotificationMessage, +Variable,+,message_note_d6,const NotificationMessage, +Variable,+,message_note_d7,const NotificationMessage, +Variable,+,message_note_d8,const NotificationMessage, +Variable,+,message_note_ds0,const NotificationMessage, +Variable,+,message_note_ds1,const NotificationMessage, +Variable,+,message_note_ds2,const NotificationMessage, +Variable,+,message_note_ds3,const NotificationMessage, +Variable,+,message_note_ds4,const NotificationMessage, +Variable,+,message_note_ds5,const NotificationMessage, +Variable,+,message_note_ds6,const NotificationMessage, +Variable,+,message_note_ds7,const NotificationMessage, +Variable,+,message_note_ds8,const NotificationMessage, +Variable,+,message_note_e0,const NotificationMessage, +Variable,+,message_note_e1,const NotificationMessage, +Variable,+,message_note_e2,const NotificationMessage, +Variable,+,message_note_e3,const NotificationMessage, +Variable,+,message_note_e4,const NotificationMessage, +Variable,+,message_note_e5,const NotificationMessage, +Variable,+,message_note_e6,const NotificationMessage, +Variable,+,message_note_e7,const NotificationMessage, +Variable,+,message_note_e8,const NotificationMessage, +Variable,+,message_note_f0,const NotificationMessage, +Variable,+,message_note_f1,const NotificationMessage, +Variable,+,message_note_f2,const NotificationMessage, +Variable,+,message_note_f3,const NotificationMessage, +Variable,+,message_note_f4,const NotificationMessage, +Variable,+,message_note_f5,const NotificationMessage, +Variable,+,message_note_f6,const NotificationMessage, +Variable,+,message_note_f7,const NotificationMessage, +Variable,+,message_note_f8,const NotificationMessage, +Variable,+,message_note_fs0,const NotificationMessage, +Variable,+,message_note_fs1,const NotificationMessage, +Variable,+,message_note_fs2,const NotificationMessage, +Variable,+,message_note_fs3,const NotificationMessage, +Variable,+,message_note_fs4,const NotificationMessage, +Variable,+,message_note_fs5,const NotificationMessage, +Variable,+,message_note_fs6,const NotificationMessage, +Variable,+,message_note_fs7,const NotificationMessage, +Variable,+,message_note_fs8,const NotificationMessage, +Variable,+,message_note_g0,const NotificationMessage, +Variable,+,message_note_g1,const NotificationMessage, +Variable,+,message_note_g2,const NotificationMessage, +Variable,+,message_note_g3,const NotificationMessage, +Variable,+,message_note_g4,const NotificationMessage, +Variable,+,message_note_g5,const NotificationMessage, +Variable,+,message_note_g6,const NotificationMessage, +Variable,+,message_note_g7,const NotificationMessage, +Variable,+,message_note_g8,const NotificationMessage, +Variable,+,message_note_gs0,const NotificationMessage, +Variable,+,message_note_gs1,const NotificationMessage, +Variable,+,message_note_gs2,const NotificationMessage, +Variable,+,message_note_gs3,const NotificationMessage, +Variable,+,message_note_gs4,const NotificationMessage, +Variable,+,message_note_gs5,const NotificationMessage, +Variable,+,message_note_gs6,const NotificationMessage, +Variable,+,message_note_gs7,const NotificationMessage, +Variable,+,message_note_gs8,const NotificationMessage, +Variable,+,message_red_0,const NotificationMessage, +Variable,+,message_red_255,const NotificationMessage, +Variable,+,message_sound_off,const NotificationMessage, +Variable,+,message_vibro_off,const NotificationMessage, +Variable,+,message_vibro_on,const NotificationMessage, +Variable,+,periph_power,const GpioPin, +Variable,+,sequence_audiovisual_alert,const NotificationSequence, +Variable,+,sequence_blink_blue_10,const NotificationSequence, +Variable,+,sequence_blink_blue_100,const NotificationSequence, +Variable,+,sequence_blink_cyan_10,const NotificationSequence, +Variable,+,sequence_blink_cyan_100,const NotificationSequence, +Variable,+,sequence_blink_green_10,const NotificationSequence, +Variable,+,sequence_blink_green_100,const NotificationSequence, +Variable,+,sequence_blink_magenta_10,const NotificationSequence, +Variable,+,sequence_blink_magenta_100,const NotificationSequence, +Variable,+,sequence_blink_red_10,const NotificationSequence, +Variable,+,sequence_blink_red_100,const NotificationSequence, +Variable,+,sequence_blink_start_blue,const NotificationSequence, +Variable,+,sequence_blink_start_cyan,const NotificationSequence, +Variable,+,sequence_blink_start_green,const NotificationSequence, +Variable,+,sequence_blink_start_magenta,const NotificationSequence, +Variable,+,sequence_blink_start_red,const NotificationSequence, +Variable,+,sequence_blink_start_yellow,const NotificationSequence, +Variable,+,sequence_blink_stop,const NotificationSequence, +Variable,+,sequence_blink_white_100,const NotificationSequence, +Variable,+,sequence_blink_yellow_10,const NotificationSequence, +Variable,+,sequence_blink_yellow_100,const NotificationSequence, +Variable,+,sequence_charged,const NotificationSequence, +Variable,+,sequence_charging,const NotificationSequence, +Variable,+,sequence_display_backlight_enforce_auto,const NotificationSequence, +Variable,+,sequence_display_backlight_enforce_on,const NotificationSequence, +Variable,+,sequence_display_backlight_off,const NotificationSequence, +Variable,+,sequence_display_backlight_off_delay_1000,const NotificationSequence, +Variable,+,sequence_display_backlight_on,const NotificationSequence, +Variable,+,sequence_double_vibro,const NotificationSequence, +Variable,+,sequence_error,const NotificationSequence, +Variable,+,sequence_not_charging,const NotificationSequence, +Variable,+,sequence_reset_blue,const NotificationSequence, +Variable,+,sequence_reset_display,const NotificationSequence, +Variable,+,sequence_reset_green,const NotificationSequence, +Variable,+,sequence_reset_red,const NotificationSequence, +Variable,+,sequence_reset_rgb,const NotificationSequence, +Variable,+,sequence_reset_sound,const NotificationSequence, +Variable,+,sequence_reset_vibro,const NotificationSequence, +Variable,+,sequence_set_blue_255,const NotificationSequence, +Variable,+,sequence_set_green_255,const NotificationSequence, +Variable,+,sequence_set_only_blue_255,const NotificationSequence, +Variable,+,sequence_set_only_green_255,const NotificationSequence, +Variable,+,sequence_set_only_red_255,const NotificationSequence, +Variable,+,sequence_set_red_255,const NotificationSequence, +Variable,+,sequence_set_vibro_on,const NotificationSequence, +Variable,+,sequence_single_vibro,const NotificationSequence, +Variable,+,sequence_solid_yellow,const NotificationSequence, +Variable,+,sequence_success,const NotificationSequence, +Variable,-,suboptarg,char*, +Variable,+,usb_cdc_dual,FuriHalUsbInterface, +Variable,+,usb_cdc_single,FuriHalUsbInterface, +Variable,+,usb_hid,FuriHalUsbInterface, +Variable,+,usb_hid_u2f,FuriHalUsbInterface, +Variable,+,usbd_devfs,const usbd_driver, +Variable,+,vibro_gpio,const GpioPin, diff --git a/firmware/targets/f18/furi_hal/furi_hal.c b/firmware/targets/f18/furi_hal/furi_hal.c new file mode 100644 index 00000000000..ad35a0074ee --- /dev/null +++ b/firmware/targets/f18/furi_hal/furi_hal.c @@ -0,0 +1,91 @@ +#include +#include + +#include + +#include + +#define TAG "FuriHal" + +void furi_hal_init_early() { + furi_hal_cortex_init_early(); + + furi_hal_clock_init_early(); + + furi_hal_resources_init_early(); + + furi_hal_os_init(); + + furi_hal_spi_config_init_early(); + + furi_hal_i2c_init_early(); + furi_hal_light_init(); + + furi_hal_rtc_init_early(); +} + +void furi_hal_deinit_early() { + furi_hal_rtc_deinit_early(); + + furi_hal_i2c_deinit_early(); + furi_hal_spi_config_deinit_early(); + + furi_hal_resources_deinit_early(); + + furi_hal_clock_deinit_early(); +} + +void furi_hal_init() { + furi_hal_mpu_init(); + furi_hal_clock_init(); + furi_hal_console_init(); + furi_hal_rtc_init(); + + furi_hal_interrupt_init(); + + furi_hal_flash_init(); + + furi_hal_resources_init(); + FURI_LOG_I(TAG, "GPIO OK"); + + furi_hal_version_init(); + + furi_hal_spi_config_init(); + + furi_hal_speaker_init(); + FURI_LOG_I(TAG, "Speaker OK"); + + furi_hal_crypto_init(); + + // USB +#ifndef FURI_RAM_EXEC + furi_hal_usb_init(); + FURI_LOG_I(TAG, "USB OK"); +#endif + + furi_hal_i2c_init(); + + // High Level + furi_hal_power_init(); + furi_hal_light_init(); +#ifndef FURI_RAM_EXEC + furi_hal_vibro_init(); +#endif + furi_hal_bt_init(); + furi_hal_compress_icon_init(); + + // FatFS driver initialization + MX_FATFS_Init(); + FURI_LOG_I(TAG, "FATFS OK"); +} + +void furi_hal_switch(void* address) { + __set_BASEPRI(0); + asm volatile("ldr r3, [%0] \n" + "msr msp, r3 \n" + "ldr r3, [%1] \n" + "mov pc, r3 \n" + : + : "r"(address), "r"(address + 0x4) + : "r3"); +} diff --git a/firmware/targets/f18/furi_hal/furi_hal_resources.c b/firmware/targets/f18/furi_hal/furi_hal_resources.c new file mode 100644 index 00000000000..dafeefdd0fc --- /dev/null +++ b/firmware/targets/f18/furi_hal/furi_hal_resources.c @@ -0,0 +1,201 @@ +#include +#include + +#include +#include + +const GpioPin vibro_gpio = {.port = GPIOA, .pin = LL_GPIO_PIN_8}; +const GpioPin ibutton_gpio = {.port = GPIOB, .pin = LL_GPIO_PIN_14}; + +const GpioPin gpio_display_cs = {.port = GPIOC, .pin = LL_GPIO_PIN_11}; +const GpioPin gpio_display_rst_n = {.port = GPIOB, .pin = LL_GPIO_PIN_0}; +const GpioPin gpio_display_di = {.port = GPIOB, .pin = LL_GPIO_PIN_1}; +const GpioPin gpio_sdcard_cs = {.port = GPIOC, .pin = LL_GPIO_PIN_12}; +const GpioPin gpio_sdcard_cd = {.port = GPIOC, .pin = LL_GPIO_PIN_10}; + +const GpioPin gpio_button_up = {.port = GPIOB, .pin = LL_GPIO_PIN_10}; +const GpioPin gpio_button_down = {.port = GPIOC, .pin = LL_GPIO_PIN_6}; +const GpioPin gpio_button_right = {.port = GPIOB, .pin = LL_GPIO_PIN_12}; +const GpioPin gpio_button_left = {.port = GPIOB, .pin = LL_GPIO_PIN_11}; +const GpioPin gpio_button_ok = {.port = GPIOH, .pin = LL_GPIO_PIN_3}; +const GpioPin gpio_button_back = {.port = GPIOC, .pin = LL_GPIO_PIN_13}; + +const GpioPin gpio_spi_d_miso = {.port = GPIOC, .pin = LL_GPIO_PIN_2}; +const GpioPin gpio_spi_d_mosi = {.port = GPIOB, .pin = LL_GPIO_PIN_15}; +const GpioPin gpio_spi_d_sck = {.port = GPIOD, .pin = LL_GPIO_PIN_1}; + +const GpioPin gpio_ext_pc0 = {.port = GPIOC, .pin = LL_GPIO_PIN_0}; +const GpioPin gpio_ext_pc1 = {.port = GPIOC, .pin = LL_GPIO_PIN_1}; +const GpioPin gpio_ext_pc3 = {.port = GPIOC, .pin = LL_GPIO_PIN_3}; +const GpioPin gpio_ext_pb2 = {.port = GPIOB, .pin = LL_GPIO_PIN_2}; +const GpioPin gpio_ext_pb3 = {.port = GPIOB, .pin = LL_GPIO_PIN_3}; +const GpioPin gpio_ext_pa4 = {.port = GPIOA, .pin = LL_GPIO_PIN_4}; +const GpioPin gpio_ext_pa6 = {.port = GPIOA, .pin = LL_GPIO_PIN_6}; +const GpioPin gpio_ext_pa7 = {.port = GPIOA, .pin = LL_GPIO_PIN_7}; + +const GpioPin gpio_ext_pc5 = {.port = GPIOC, .pin = LL_GPIO_PIN_5}; +const GpioPin gpio_ext_pc4 = {.port = GPIOC, .pin = LL_GPIO_PIN_4}; +const GpioPin gpio_ext_pa5 = {.port = GPIOA, .pin = LL_GPIO_PIN_5}; +const GpioPin gpio_ext_pb9 = {.port = GPIOB, .pin = LL_GPIO_PIN_9}; +const GpioPin gpio_ext_pa0 = {.port = GPIOA, .pin = LL_GPIO_PIN_0}; +const GpioPin gpio_ext_pa1 = {.port = GPIOA, .pin = LL_GPIO_PIN_1}; +const GpioPin gpio_ext_pa15 = {.port = GPIOA, .pin = LL_GPIO_PIN_15}; +const GpioPin gpio_ext_pe4 = {.port = GPIOE, .pin = LL_GPIO_PIN_4}; +const GpioPin gpio_ext_pa2 = {.port = GPIOA, .pin = LL_GPIO_PIN_2}; +const GpioPin gpio_ext_pb4 = {.port = GPIOB, .pin = LL_GPIO_PIN_4}; +const GpioPin gpio_ext_pb5 = {.port = GPIOB, .pin = LL_GPIO_PIN_5}; +const GpioPin gpio_ext_pd0 = {.port = GPIOD, .pin = LL_GPIO_PIN_0}; +const GpioPin gpio_ext_pb13 = {.port = GPIOB, .pin = LL_GPIO_PIN_13}; + +const GpioPin gpio_usart_tx = {.port = GPIOB, .pin = LL_GPIO_PIN_6}; +const GpioPin gpio_usart_rx = {.port = GPIOB, .pin = LL_GPIO_PIN_7}; + +const GpioPin gpio_i2c_power_sda = {.port = GPIOA, .pin = LL_GPIO_PIN_10}; +const GpioPin gpio_i2c_power_scl = {.port = GPIOA, .pin = LL_GPIO_PIN_9}; + +const GpioPin gpio_speaker = {.port = GPIOB, .pin = LL_GPIO_PIN_8}; + +const GpioPin periph_power = {.port = GPIOA, .pin = LL_GPIO_PIN_3}; + +const GpioPin gpio_usb_dm = {.port = GPIOA, .pin = LL_GPIO_PIN_11}; +const GpioPin gpio_usb_dp = {.port = GPIOA, .pin = LL_GPIO_PIN_12}; + +const GpioPinRecord gpio_pins[] = { + {.pin = &gpio_ext_pa7, .name = "PA7", .debug = false}, + {.pin = &gpio_ext_pa6, .name = "PA6", .debug = false}, + {.pin = &gpio_ext_pa4, .name = "PA4", .debug = false}, + {.pin = &gpio_ext_pb3, .name = "PB3", .debug = false}, + {.pin = &gpio_ext_pb2, .name = "PB2", .debug = false}, + {.pin = &gpio_ext_pc3, .name = "PC3", .debug = false}, + {.pin = &gpio_ext_pc1, .name = "PC1", .debug = false}, + {.pin = &gpio_ext_pc0, .name = "PC0", .debug = false}, + + {.pin = &gpio_ext_pc5, .name = "PC5", .debug = false}, + {.pin = &gpio_ext_pc4, .name = "PC4", .debug = false}, + {.pin = &gpio_ext_pa5, .name = "PA5", .debug = false}, + {.pin = &gpio_ext_pb9, .name = "PB9", .debug = false}, + {.pin = &gpio_ext_pa0, .name = "PA0", .debug = false}, + {.pin = &gpio_ext_pa1, .name = "PA1", .debug = false}, + {.pin = &gpio_ext_pa15, .name = "PA15", .debug = false}, + {.pin = &gpio_ext_pe4, .name = "PE4", .debug = false}, + {.pin = &gpio_ext_pa2, .name = "PA2", .debug = false}, + {.pin = &gpio_ext_pb4, .name = "PB4", .debug = false}, + {.pin = &gpio_ext_pb5, .name = "PB5", .debug = false}, + {.pin = &gpio_ext_pd0, .name = "PD0", .debug = false}, + {.pin = &gpio_ext_pb13, .name = "PB13", .debug = false}, + + /* Dangerous pins, may damage hardware */ + {.pin = &gpio_usart_rx, .name = "PB7", .debug = true}, + {.pin = &gpio_speaker, .name = "PB8", .debug = true}, +}; + +const size_t gpio_pins_count = sizeof(gpio_pins) / sizeof(GpioPinRecord); + +const InputPin input_pins[] = { + {.gpio = &gpio_button_up, .key = InputKeyUp, .inverted = true, .name = "Up"}, + {.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_back, .key = InputKeyBack, .inverted = true, .name = "Back"}, +}; + +const size_t input_pins_count = sizeof(input_pins) / sizeof(InputPin); + +static void furi_hal_resources_init_input_pins(GpioMode mode) { + for(size_t i = 0; i < input_pins_count; i++) { + furi_hal_gpio_init( + input_pins[i].gpio, + mode, + (input_pins[i].inverted) ? GpioPullUp : GpioPullDown, + GpioSpeedLow); + } +} + +void furi_hal_resources_init_early() { + furi_hal_resources_init_input_pins(GpioModeInput); + + // SD Card stepdown control + furi_hal_gpio_write(&periph_power, 1); + furi_hal_gpio_init(&periph_power, GpioModeOutputOpenDrain, GpioPullNo, GpioSpeedLow); + + // Display pins + furi_hal_gpio_write(&gpio_display_rst_n, 1); + furi_hal_gpio_init_simple(&gpio_display_rst_n, GpioModeOutputPushPull); + furi_hal_gpio_init(&gpio_display_di, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); + + // Pullup display reset pin for shutdown + SET_BIT(PWR->PUCRB, gpio_display_rst_n.pin); + CLEAR_BIT(PWR->PDCRB, gpio_display_rst_n.pin); + SET_BIT(PWR->CR3, PWR_CR3_APC); + + // Hard reset USB + furi_hal_gpio_write(&gpio_usb_dm, 1); + furi_hal_gpio_write(&gpio_usb_dp, 1); + furi_hal_gpio_init_simple(&gpio_usb_dm, GpioModeOutputOpenDrain); + furi_hal_gpio_init_simple(&gpio_usb_dp, GpioModeOutputOpenDrain); + furi_hal_gpio_write(&gpio_usb_dm, 0); + furi_hal_gpio_write(&gpio_usb_dp, 0); + furi_delay_us(5); // Device Driven disconnect: 2.5us + extra to compensate cables + furi_hal_gpio_write(&gpio_usb_dm, 1); + furi_hal_gpio_write(&gpio_usb_dp, 1); + furi_hal_gpio_init_simple(&gpio_usb_dm, GpioModeAnalog); + furi_hal_gpio_init_simple(&gpio_usb_dp, GpioModeAnalog); + furi_hal_gpio_write(&gpio_usb_dm, 0); + furi_hal_gpio_write(&gpio_usb_dp, 0); + + // External header pins + furi_hal_gpio_init(&gpio_ext_pc0, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_init(&gpio_ext_pc1, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_init(&gpio_ext_pc3, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_init(&gpio_ext_pb2, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_init(&gpio_ext_pb3, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_init(&gpio_ext_pa4, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_init(&gpio_ext_pa6, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_init(&gpio_ext_pa7, GpioModeAnalog, GpioPullNo, GpioSpeedLow); +} + +void furi_hal_resources_deinit_early() { + furi_hal_resources_init_input_pins(GpioModeAnalog); +} + +void furi_hal_resources_init() { + // Button pins + furi_hal_resources_init_input_pins(GpioModeInterruptRiseFall); + + // Display pins + furi_hal_gpio_init(&gpio_display_rst_n, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_write(&gpio_display_rst_n, 0); + + furi_hal_gpio_init(&gpio_display_di, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_write(&gpio_display_di, 0); + + // SD pins + furi_hal_gpio_init(&gpio_sdcard_cd, GpioModeInput, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_write(&gpio_sdcard_cd, 0); + + furi_hal_gpio_init(&vibro_gpio, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + + furi_hal_gpio_init(&ibutton_gpio, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + + NVIC_SetPriority(EXTI0_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 5, 0)); + NVIC_EnableIRQ(EXTI0_IRQn); + + NVIC_SetPriority(EXTI1_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 5, 0)); + NVIC_EnableIRQ(EXTI1_IRQn); + + NVIC_SetPriority(EXTI2_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 5, 0)); + NVIC_EnableIRQ(EXTI2_IRQn); + + NVIC_SetPriority(EXTI3_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 5, 0)); + NVIC_EnableIRQ(EXTI3_IRQn); + + NVIC_SetPriority(EXTI4_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 5, 0)); + NVIC_EnableIRQ(EXTI4_IRQn); + + NVIC_SetPriority(EXTI9_5_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 5, 0)); + NVIC_EnableIRQ(EXTI9_5_IRQn); + + NVIC_SetPriority(EXTI15_10_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 5, 0)); + NVIC_EnableIRQ(EXTI15_10_IRQn); +} diff --git a/firmware/targets/f18/furi_hal/furi_hal_resources.h b/firmware/targets/f18/furi_hal/furi_hal_resources.h new file mode 100644 index 00000000000..ef2cdae7f8e --- /dev/null +++ b/firmware/targets/f18/furi_hal/furi_hal_resources.h @@ -0,0 +1,116 @@ +#pragma once + +#include + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Input Related Constants */ +#define INPUT_DEBOUNCE_TICKS 4 + +/* Input Keys */ +typedef enum { + InputKeyUp, + InputKeyDown, + InputKeyRight, + InputKeyLeft, + InputKeyOk, + InputKeyBack, + InputKeyMAX, /**< Special value */ +} InputKey; + +/* Light */ +typedef enum { + LightRed = (1 << 0), + LightGreen = (1 << 1), + LightBlue = (1 << 2), + LightBacklight = (1 << 3), +} Light; + +typedef struct { + const GpioPin* gpio; + const InputKey key; + const bool inverted; + const char* name; +} InputPin; + +typedef struct { + const GpioPin* pin; + const char* name; + const bool debug; +} GpioPinRecord; + +extern const InputPin input_pins[]; +extern const size_t input_pins_count; + +extern const GpioPinRecord gpio_pins[]; +extern const size_t gpio_pins_count; + +extern const GpioPin vibro_gpio; +extern const GpioPin ibutton_gpio; + +extern const GpioPin gpio_display_cs; +extern const GpioPin gpio_display_rst_n; +extern const GpioPin gpio_display_di; +extern const GpioPin gpio_sdcard_cs; +extern const GpioPin gpio_sdcard_cd; + +extern const GpioPin gpio_button_up; +extern const GpioPin gpio_button_down; +extern const GpioPin gpio_button_right; +extern const GpioPin gpio_button_left; +extern const GpioPin gpio_button_ok; +extern const GpioPin gpio_button_back; + +extern const GpioPin gpio_spi_d_miso; +extern const GpioPin gpio_spi_d_mosi; +extern const GpioPin gpio_spi_d_sck; + +extern const GpioPin gpio_ext_pc0; +extern const GpioPin gpio_ext_pc1; +extern const GpioPin gpio_ext_pc3; +extern const GpioPin gpio_ext_pb2; +extern const GpioPin gpio_ext_pb3; +extern const GpioPin gpio_ext_pa4; +extern const GpioPin gpio_ext_pa6; +extern const GpioPin gpio_ext_pa7; + +extern const GpioPin gpio_ext_pc5; +extern const GpioPin gpio_ext_pc4; +extern const GpioPin gpio_ext_pa5; +extern const GpioPin gpio_ext_pb9; +extern const GpioPin gpio_ext_pa0; +extern const GpioPin gpio_ext_pa1; +extern const GpioPin gpio_ext_pa15; +extern const GpioPin gpio_ext_pe4; +extern const GpioPin gpio_ext_pa2; +extern const GpioPin gpio_ext_pb4; +extern const GpioPin gpio_ext_pb5; +extern const GpioPin gpio_ext_pd0; +extern const GpioPin gpio_ext_pb13; + +extern const GpioPin gpio_usart_tx; +extern const GpioPin gpio_usart_rx; +extern const GpioPin gpio_i2c_power_sda; +extern const GpioPin gpio_i2c_power_scl; + +extern const GpioPin gpio_speaker; + +extern const GpioPin periph_power; + +extern const GpioPin gpio_usb_dm; +extern const GpioPin gpio_usb_dp; + +void furi_hal_resources_init_early(); + +void furi_hal_resources_deinit_early(); + +void furi_hal_resources_init(); + +#ifdef __cplusplus +} +#endif diff --git a/firmware/targets/f18/furi_hal/furi_hal_spi_config.c b/firmware/targets/f18/furi_hal/furi_hal_spi_config.c new file mode 100644 index 00000000000..0fbe55e2ac9 --- /dev/null +++ b/firmware/targets/f18/furi_hal/furi_hal_spi_config.c @@ -0,0 +1,377 @@ +#include +#include +#include +#include + +#define TAG "FuriHalSpiConfig" + +/* SPI Presets */ + +const LL_SPI_InitTypeDef furi_hal_spi_preset_2edge_low_8m = { + .Mode = LL_SPI_MODE_MASTER, + .TransferDirection = LL_SPI_FULL_DUPLEX, + .DataWidth = LL_SPI_DATAWIDTH_8BIT, + .ClockPolarity = LL_SPI_POLARITY_LOW, + .ClockPhase = LL_SPI_PHASE_2EDGE, + .NSS = LL_SPI_NSS_SOFT, + .BaudRate = LL_SPI_BAUDRATEPRESCALER_DIV8, + .BitOrder = LL_SPI_MSB_FIRST, + .CRCCalculation = LL_SPI_CRCCALCULATION_DISABLE, + .CRCPoly = 7, +}; + +const LL_SPI_InitTypeDef furi_hal_spi_preset_1edge_low_8m = { + .Mode = LL_SPI_MODE_MASTER, + .TransferDirection = LL_SPI_FULL_DUPLEX, + .DataWidth = LL_SPI_DATAWIDTH_8BIT, + .ClockPolarity = LL_SPI_POLARITY_LOW, + .ClockPhase = LL_SPI_PHASE_1EDGE, + .NSS = LL_SPI_NSS_SOFT, + .BaudRate = LL_SPI_BAUDRATEPRESCALER_DIV8, + .BitOrder = LL_SPI_MSB_FIRST, + .CRCCalculation = LL_SPI_CRCCALCULATION_DISABLE, + .CRCPoly = 7, +}; + +const LL_SPI_InitTypeDef furi_hal_spi_preset_1edge_low_4m = { + .Mode = LL_SPI_MODE_MASTER, + .TransferDirection = LL_SPI_FULL_DUPLEX, + .DataWidth = LL_SPI_DATAWIDTH_8BIT, + .ClockPolarity = LL_SPI_POLARITY_LOW, + .ClockPhase = LL_SPI_PHASE_1EDGE, + .NSS = LL_SPI_NSS_SOFT, + .BaudRate = LL_SPI_BAUDRATEPRESCALER_DIV16, + .BitOrder = LL_SPI_MSB_FIRST, + .CRCCalculation = LL_SPI_CRCCALCULATION_DISABLE, + .CRCPoly = 7, +}; + +const LL_SPI_InitTypeDef furi_hal_spi_preset_1edge_low_16m = { + .Mode = LL_SPI_MODE_MASTER, + .TransferDirection = LL_SPI_FULL_DUPLEX, + .DataWidth = LL_SPI_DATAWIDTH_8BIT, + .ClockPolarity = LL_SPI_POLARITY_LOW, + .ClockPhase = LL_SPI_PHASE_1EDGE, + .NSS = LL_SPI_NSS_SOFT, + .BaudRate = LL_SPI_BAUDRATEPRESCALER_DIV2, + .BitOrder = LL_SPI_MSB_FIRST, + .CRCCalculation = LL_SPI_CRCCALCULATION_DISABLE, + .CRCPoly = 7, +}; + +const LL_SPI_InitTypeDef furi_hal_spi_preset_1edge_low_2m = { + .Mode = LL_SPI_MODE_MASTER, + .TransferDirection = LL_SPI_FULL_DUPLEX, + .DataWidth = LL_SPI_DATAWIDTH_8BIT, + .ClockPolarity = LL_SPI_POLARITY_LOW, + .ClockPhase = LL_SPI_PHASE_1EDGE, + .NSS = LL_SPI_NSS_SOFT, + .BaudRate = LL_SPI_BAUDRATEPRESCALER_DIV32, + .BitOrder = LL_SPI_MSB_FIRST, + .CRCCalculation = LL_SPI_CRCCALCULATION_DISABLE, + .CRCPoly = 7, +}; + +/* SPI Buses */ + +FuriMutex* furi_hal_spi_bus_r_mutex = NULL; + +void furi_hal_spi_config_init_early() { + furi_hal_spi_bus_init(&furi_hal_spi_bus_d); + furi_hal_spi_bus_handle_init(&furi_hal_spi_bus_handle_display); +} + +void furi_hal_spi_config_deinit_early() { + furi_hal_spi_bus_handle_deinit(&furi_hal_spi_bus_handle_display); + furi_hal_spi_bus_deinit(&furi_hal_spi_bus_d); +} + +void furi_hal_spi_config_init() { + furi_hal_spi_bus_handle_init(&furi_hal_spi_bus_handle_sd_fast); + furi_hal_spi_bus_handle_init(&furi_hal_spi_bus_handle_sd_slow); + + FURI_LOG_I(TAG, "Init OK"); +} + +static void furi_hal_spi_bus_r_event_callback(FuriHalSpiBus* bus, FuriHalSpiBusEvent event) { + if(event == FuriHalSpiBusEventInit) { + furi_hal_spi_bus_r_mutex = furi_mutex_alloc(FuriMutexTypeNormal); + FURI_CRITICAL_ENTER(); + LL_APB2_GRP1_ForceReset(LL_APB2_GRP1_PERIPH_SPI1); + FURI_CRITICAL_EXIT(); + bus->current_handle = NULL; + } else if(event == FuriHalSpiBusEventDeinit) { + furi_mutex_free(furi_hal_spi_bus_r_mutex); + FURI_CRITICAL_ENTER(); + LL_APB2_GRP1_ForceReset(LL_APB2_GRP1_PERIPH_SPI1); + LL_APB2_GRP1_ReleaseReset(LL_APB2_GRP1_PERIPH_SPI1); + FURI_CRITICAL_EXIT(); + } else if(event == FuriHalSpiBusEventLock) { + furi_check(furi_mutex_acquire(furi_hal_spi_bus_r_mutex, FuriWaitForever) == FuriStatusOk); + } else if(event == FuriHalSpiBusEventUnlock) { + furi_check(furi_mutex_release(furi_hal_spi_bus_r_mutex) == FuriStatusOk); + } else if(event == FuriHalSpiBusEventActivate) { + FURI_CRITICAL_ENTER(); + LL_APB2_GRP1_ReleaseReset(LL_APB2_GRP1_PERIPH_SPI1); + FURI_CRITICAL_EXIT(); + } else if(event == FuriHalSpiBusEventDeactivate) { + FURI_CRITICAL_ENTER(); + LL_APB2_GRP1_ForceReset(LL_APB2_GRP1_PERIPH_SPI1); + FURI_CRITICAL_EXIT(); + } +} + +FuriHalSpiBus furi_hal_spi_bus_r = { + .spi = SPI1, + .callback = furi_hal_spi_bus_r_event_callback, +}; + +FuriMutex* furi_hal_spi_bus_d_mutex = NULL; + +static void furi_hal_spi_bus_d_event_callback(FuriHalSpiBus* bus, FuriHalSpiBusEvent event) { + if(event == FuriHalSpiBusEventInit) { + furi_hal_spi_bus_d_mutex = furi_mutex_alloc(FuriMutexTypeNormal); + FURI_CRITICAL_ENTER(); + LL_APB1_GRP1_ForceReset(LL_APB1_GRP1_PERIPH_SPI2); + FURI_CRITICAL_EXIT(); + bus->current_handle = NULL; + } else if(event == FuriHalSpiBusEventDeinit) { + furi_mutex_free(furi_hal_spi_bus_d_mutex); + FURI_CRITICAL_ENTER(); + LL_APB1_GRP1_ForceReset(LL_APB1_GRP1_PERIPH_SPI2); + LL_APB1_GRP1_ReleaseReset(LL_APB1_GRP1_PERIPH_SPI2); + FURI_CRITICAL_EXIT(); + } else if(event == FuriHalSpiBusEventLock) { + furi_check(furi_mutex_acquire(furi_hal_spi_bus_d_mutex, FuriWaitForever) == FuriStatusOk); + } else if(event == FuriHalSpiBusEventUnlock) { + furi_check(furi_mutex_release(furi_hal_spi_bus_d_mutex) == FuriStatusOk); + } else if(event == FuriHalSpiBusEventActivate) { + FURI_CRITICAL_ENTER(); + LL_APB1_GRP1_ReleaseReset(LL_APB1_GRP1_PERIPH_SPI2); + FURI_CRITICAL_EXIT(); + } else if(event == FuriHalSpiBusEventDeactivate) { + FURI_CRITICAL_ENTER(); + LL_APB1_GRP1_ForceReset(LL_APB1_GRP1_PERIPH_SPI2); + FURI_CRITICAL_EXIT(); + } +} + +FuriHalSpiBus furi_hal_spi_bus_d = { + .spi = SPI2, + .callback = furi_hal_spi_bus_d_event_callback, +}; + +/* SPI Bus Handles */ + +inline static void furi_hal_spi_bus_r_handle_event_callback( + FuriHalSpiBusHandle* handle, + FuriHalSpiBusHandleEvent event, + const LL_SPI_InitTypeDef* preset) { + if(event == FuriHalSpiBusHandleEventInit) { + furi_hal_gpio_write(handle->cs, true); + furi_hal_gpio_init(handle->cs, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); + } else if(event == FuriHalSpiBusHandleEventDeinit) { + furi_hal_gpio_write(handle->cs, true); + furi_hal_gpio_init(handle->cs, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + } else if(event == FuriHalSpiBusHandleEventActivate) { + LL_SPI_Init(handle->bus->spi, (LL_SPI_InitTypeDef*)preset); + LL_SPI_SetRxFIFOThreshold(handle->bus->spi, LL_SPI_RX_FIFO_TH_QUARTER); + LL_SPI_Enable(handle->bus->spi); + + furi_hal_gpio_init_ex( + handle->miso, + GpioModeAltFunctionPushPull, + GpioPullNo, + GpioSpeedVeryHigh, + GpioAltFn5SPI1); + furi_hal_gpio_init_ex( + handle->mosi, + GpioModeAltFunctionPushPull, + GpioPullNo, + GpioSpeedVeryHigh, + GpioAltFn5SPI1); + furi_hal_gpio_init_ex( + handle->sck, + GpioModeAltFunctionPushPull, + GpioPullNo, + GpioSpeedVeryHigh, + GpioAltFn5SPI1); + + furi_hal_gpio_write(handle->cs, false); + } else if(event == FuriHalSpiBusHandleEventDeactivate) { + furi_hal_gpio_write(handle->cs, true); + + furi_hal_gpio_init(handle->miso, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_init(handle->mosi, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_init(handle->sck, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + + LL_SPI_Disable(handle->bus->spi); + } +} + +inline static void furi_hal_spi_bus_nfc_handle_event_callback( + FuriHalSpiBusHandle* handle, + FuriHalSpiBusHandleEvent event, + const LL_SPI_InitTypeDef* preset) { + if(event == FuriHalSpiBusHandleEventInit) { + // Configure GPIOs in normal SPI mode + furi_hal_gpio_init_ex( + handle->miso, + GpioModeAltFunctionPushPull, + GpioPullNo, + GpioSpeedVeryHigh, + GpioAltFn5SPI1); + furi_hal_gpio_init_ex( + handle->mosi, + GpioModeAltFunctionPushPull, + GpioPullNo, + GpioSpeedVeryHigh, + GpioAltFn5SPI1); + furi_hal_gpio_init_ex( + handle->sck, + GpioModeAltFunctionPushPull, + GpioPullNo, + GpioSpeedVeryHigh, + GpioAltFn5SPI1); + furi_hal_gpio_write(handle->cs, true); + furi_hal_gpio_init(handle->cs, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); + } else if(event == FuriHalSpiBusHandleEventDeinit) { + // Configure GPIOs for st25r3916 Transparent mode + furi_hal_gpio_init(handle->sck, GpioModeInput, GpioPullUp, GpioSpeedLow); + furi_hal_gpio_init(handle->miso, GpioModeInput, GpioPullUp, GpioSpeedLow); + furi_hal_gpio_init(handle->cs, GpioModeInput, GpioPullUp, GpioSpeedLow); + furi_hal_gpio_write(handle->mosi, false); + furi_hal_gpio_init(handle->mosi, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); + } else if(event == FuriHalSpiBusHandleEventActivate) { + LL_SPI_Init(handle->bus->spi, (LL_SPI_InitTypeDef*)preset); + LL_SPI_SetRxFIFOThreshold(handle->bus->spi, LL_SPI_RX_FIFO_TH_QUARTER); + LL_SPI_Enable(handle->bus->spi); + + furi_hal_gpio_init_ex( + handle->miso, + GpioModeAltFunctionPushPull, + GpioPullNo, + GpioSpeedVeryHigh, + GpioAltFn5SPI1); + furi_hal_gpio_init_ex( + handle->mosi, + GpioModeAltFunctionPushPull, + GpioPullNo, + GpioSpeedVeryHigh, + GpioAltFn5SPI1); + furi_hal_gpio_init_ex( + handle->sck, + GpioModeAltFunctionPushPull, + GpioPullNo, + GpioSpeedVeryHigh, + GpioAltFn5SPI1); + + } else if(event == FuriHalSpiBusHandleEventDeactivate) { + furi_hal_gpio_init(handle->miso, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_init(handle->mosi, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_init(handle->sck, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + + LL_SPI_Disable(handle->bus->spi); + } +} + +static void furi_hal_spi_bus_handle_external_event_callback( + FuriHalSpiBusHandle* handle, + FuriHalSpiBusHandleEvent event) { + furi_hal_spi_bus_r_handle_event_callback(handle, event, &furi_hal_spi_preset_1edge_low_2m); +} + +FuriHalSpiBusHandle furi_hal_spi_bus_handle_external = { + .bus = &furi_hal_spi_bus_r, + .callback = furi_hal_spi_bus_handle_external_event_callback, + .miso = &gpio_ext_pa6, + .mosi = &gpio_ext_pa7, + .sck = &gpio_ext_pb3, + .cs = &gpio_ext_pa4, +}; + +inline static void furi_hal_spi_bus_d_handle_event_callback( + FuriHalSpiBusHandle* handle, + FuriHalSpiBusHandleEvent event, + const LL_SPI_InitTypeDef* preset) { + if(event == FuriHalSpiBusHandleEventInit) { + furi_hal_gpio_write(handle->cs, true); + furi_hal_gpio_init(handle->cs, GpioModeOutputPushPull, GpioPullUp, GpioSpeedVeryHigh); + + furi_hal_gpio_init_ex( + handle->miso, + GpioModeAltFunctionPushPull, + GpioPullNo, + GpioSpeedVeryHigh, + GpioAltFn5SPI2); + furi_hal_gpio_init_ex( + handle->mosi, + GpioModeAltFunctionPushPull, + GpioPullNo, + GpioSpeedVeryHigh, + GpioAltFn5SPI2); + furi_hal_gpio_init_ex( + handle->sck, + GpioModeAltFunctionPushPull, + GpioPullNo, + GpioSpeedVeryHigh, + GpioAltFn5SPI2); + + } else if(event == FuriHalSpiBusHandleEventDeinit) { + furi_hal_gpio_write(handle->cs, true); + furi_hal_gpio_init(handle->cs, GpioModeAnalog, GpioPullUp, GpioSpeedLow); + } else if(event == FuriHalSpiBusHandleEventActivate) { + LL_SPI_Init(handle->bus->spi, (LL_SPI_InitTypeDef*)preset); + LL_SPI_SetRxFIFOThreshold(handle->bus->spi, LL_SPI_RX_FIFO_TH_QUARTER); + LL_SPI_Enable(handle->bus->spi); + furi_hal_gpio_write(handle->cs, false); + } else if(event == FuriHalSpiBusHandleEventDeactivate) { + furi_hal_gpio_write(handle->cs, true); + LL_SPI_Disable(handle->bus->spi); + } +} + +static void furi_hal_spi_bus_handle_display_event_callback( + FuriHalSpiBusHandle* handle, + FuriHalSpiBusHandleEvent event) { + furi_hal_spi_bus_d_handle_event_callback(handle, event, &furi_hal_spi_preset_1edge_low_4m); +} + +FuriHalSpiBusHandle furi_hal_spi_bus_handle_display = { + .bus = &furi_hal_spi_bus_d, + .callback = furi_hal_spi_bus_handle_display_event_callback, + .miso = &gpio_spi_d_miso, + .mosi = &gpio_spi_d_mosi, + .sck = &gpio_spi_d_sck, + .cs = &gpio_display_cs, +}; + +static void furi_hal_spi_bus_handle_sd_fast_event_callback( + FuriHalSpiBusHandle* handle, + FuriHalSpiBusHandleEvent event) { + furi_hal_spi_bus_d_handle_event_callback(handle, event, &furi_hal_spi_preset_1edge_low_16m); +} + +FuriHalSpiBusHandle furi_hal_spi_bus_handle_sd_fast = { + .bus = &furi_hal_spi_bus_d, + .callback = furi_hal_spi_bus_handle_sd_fast_event_callback, + .miso = &gpio_spi_d_miso, + .mosi = &gpio_spi_d_mosi, + .sck = &gpio_spi_d_sck, + .cs = &gpio_sdcard_cs, +}; + +static void furi_hal_spi_bus_handle_sd_slow_event_callback( + FuriHalSpiBusHandle* handle, + FuriHalSpiBusHandleEvent event) { + furi_hal_spi_bus_d_handle_event_callback(handle, event, &furi_hal_spi_preset_1edge_low_2m); +} + +FuriHalSpiBusHandle furi_hal_spi_bus_handle_sd_slow = { + .bus = &furi_hal_spi_bus_d, + .callback = furi_hal_spi_bus_handle_sd_slow_event_callback, + .miso = &gpio_spi_d_miso, + .mosi = &gpio_spi_d_mosi, + .sck = &gpio_spi_d_sck, + .cs = &gpio_sdcard_cs, +}; diff --git a/firmware/targets/f18/furi_hal/furi_hal_spi_config.h b/firmware/targets/f18/furi_hal/furi_hal_spi_config.h new file mode 100644 index 00000000000..da39fbfa6d0 --- /dev/null +++ b/firmware/targets/f18/furi_hal/furi_hal_spi_config.h @@ -0,0 +1,55 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** Preset for ST25R916 */ +extern const LL_SPI_InitTypeDef furi_hal_spi_preset_2edge_low_8m; + +/** Preset for CC1101 */ +extern const LL_SPI_InitTypeDef furi_hal_spi_preset_1edge_low_8m; + +/** Preset for ST7567 (Display) */ +extern const LL_SPI_InitTypeDef furi_hal_spi_preset_1edge_low_4m; + +/** Preset for SdCard in fast mode */ +extern const LL_SPI_InitTypeDef furi_hal_spi_preset_1edge_low_16m; + +/** Preset for SdCard in slow mode */ +extern const LL_SPI_InitTypeDef furi_hal_spi_preset_1edge_low_2m; + +/** Furi Hal Spi Bus R (External) */ +extern FuriHalSpiBus furi_hal_spi_bus_r; + +/** Furi Hal Spi Bus D (Display, SdCard) */ +extern FuriHalSpiBus furi_hal_spi_bus_d; + +/** External on `furi_hal_spi_bus_r` + * Preset: `furi_hal_spi_preset_1edge_low_2m` + * + * miso: pa6 + * mosi: pa7 + * sck: pb3 + * cs: pa4 (software controlled) + * + * @warning not initialized by default, call `furi_hal_spi_bus_handle_init` to initialize + * Bus pins are floating on inactive state, CS high after initialization + * + */ +extern FuriHalSpiBusHandle furi_hal_spi_bus_handle_external; + +/** ST7567(Display) on `furi_hal_spi_bus_d` */ +extern FuriHalSpiBusHandle furi_hal_spi_bus_handle_display; + +/** SdCard in fast mode on `furi_hal_spi_bus_d` */ +extern FuriHalSpiBusHandle furi_hal_spi_bus_handle_sd_fast; + +/** SdCard in slow mode on `furi_hal_spi_bus_d` */ +extern FuriHalSpiBusHandle furi_hal_spi_bus_handle_sd_slow; + +#ifdef __cplusplus +} +#endif diff --git a/firmware/targets/f18/furi_hal/furi_hal_target_hw.h b/firmware/targets/f18/furi_hal/furi_hal_target_hw.h new file mode 100644 index 00000000000..6f70f09beec --- /dev/null +++ b/firmware/targets/f18/furi_hal/furi_hal_target_hw.h @@ -0,0 +1 @@ +#pragma once diff --git a/firmware/targets/f18/furi_hal/furi_hal_version_device.c b/firmware/targets/f18/furi_hal/furi_hal_version_device.c new file mode 100644 index 00000000000..1b5090b930a --- /dev/null +++ b/firmware/targets/f18/furi_hal/furi_hal_version_device.c @@ -0,0 +1,21 @@ +#include + +bool furi_hal_version_do_i_belong_here() { + return (furi_hal_version_get_hw_target() == 18) || (furi_hal_version_get_hw_target() == 0); +} + +const char* furi_hal_version_get_model_name() { + return "Komi"; +} + +const char* furi_hal_version_get_model_code() { + return "N/A"; +} + +const char* furi_hal_version_get_fcc_id() { + return "N/A"; +} + +const char* furi_hal_version_get_ic_id() { + return "N/A"; +} diff --git a/firmware/targets/f18/target.json b/firmware/targets/f18/target.json new file mode 100644 index 00000000000..2c3b27ab1d4 --- /dev/null +++ b/firmware/targets/f18/target.json @@ -0,0 +1,55 @@ +{ + "inherit": "7", + "include_paths": [ + "furi_hal" + ], + "sdk_header_paths": [ + "../furi_hal_include", + "furi_hal", + "platform_specific" + ], + "sdk_symbols": "api_symbols.csv", + "linker_dependencies": [ + "print", + "flipper18", + "furi", + "freertos", + "stm32cubewb", + "hwdrivers", + "fatfs", + "littlefs", + "flipperformat", + "toolbox", + "microtar", + "usb_stm32", + "appframe", + "assets", + "misc", + "flipper_application", + "flipperformat", + "toolbox", + "flipper18" + ], + "excluded_sources": [ + "furi_hal_infrared.c", + "furi_hal_nfc.c", + "furi_hal_rfid.c", + "furi_hal_subghz.c" + ], + "excluded_headers": [ + "furi_hal_infrared.h", + "furi_hal_nfc.h", + "furi_hal_rfid.h", + "furi_hal_subghz.h", + "furi_hal_ibutton.h", + "furi_hal_subghz_configs.h" + ], + "excluded_modules": [ + "one_wire", + "nfc", + "lfrfid", + "subghz", + "infrared", + "st25rfal002" + ] +} \ No newline at end of file diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 14036ef1a84..c1a979859e5 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,11.10,, +Version,+,12.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -41,14 +41,19 @@ Header,+,firmware/targets/f7/furi_hal/furi_hal_flash.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_gpio.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_i2c_config.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_i2c_types.h,, +Header,+,firmware/targets/f7/furi_hal/furi_hal_ibutton.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_idle_timer.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_interrupt.h,, +Header,+,firmware/targets/f7/furi_hal/furi_hal_nfc.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_os.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_pwm.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_resources.h,, +Header,+,firmware/targets/f7/furi_hal/furi_hal_rfid.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_spi_config.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_spi_types.h,, +Header,+,firmware/targets/f7/furi_hal/furi_hal_subghz.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_subghz_configs.h,, +Header,+,firmware/targets/f7/furi_hal/furi_hal_target_hw.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_uart.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_usb_cdc.h,, Header,+,firmware/targets/f7/platform_specific/intrinsic_export.h,, @@ -61,22 +66,18 @@ Header,+,firmware/targets/furi_hal_include/furi_hal_cortex.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_crypto.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_debug.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_i2c.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_ibutton.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_info.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_infrared.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_light.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_memory.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_mpu.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_nfc.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_power.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_random.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_region.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_rfid.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_rtc.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_sd.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_speaker.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_spi.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_subghz.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_usb.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_usb_hid.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_usb_hid_u2f.h,, @@ -150,6 +151,16 @@ Header,+,lib/libusb_stm32/inc/usbd_core.h,, Header,+,lib/mbedtls/include/mbedtls/des.h,, Header,+,lib/mbedtls/include/mbedtls/sha1.h,, Header,+,lib/micro-ecc/uECC.h,, +Header,+,lib/mlib/m-algo.h,, +Header,+,lib/mlib/m-array.h,, +Header,+,lib/mlib/m-bptree.h,, +Header,+,lib/mlib/m-core.h,, +Header,+,lib/mlib/m-deque.h,, +Header,+,lib/mlib/m-dict.h,, +Header,+,lib/mlib/m-list.h,, +Header,+,lib/mlib/m-rbtree.h,, +Header,+,lib/mlib/m-tuple.h,, +Header,+,lib/mlib/m-variant.h,, Header,+,lib/nfc/nfc_device.h,, Header,+,lib/one_wire/ibutton/ibutton_worker.h,, Header,+,lib/one_wire/maxim_crc.h,, @@ -852,6 +863,7 @@ Function,+,flipper_application_free,void,FlipperApplication* Function,+,flipper_application_get_manifest,const FlipperApplicationManifest*,FlipperApplication* Function,+,flipper_application_load_status_to_string,const char*,FlipperApplicationLoadStatus Function,+,flipper_application_manifest_is_compatible,_Bool,"const FlipperApplicationManifest*, const ElfApiInterface*" +Function,+,flipper_application_manifest_is_target_compatible,_Bool,const FlipperApplicationManifest* Function,+,flipper_application_manifest_is_valid,_Bool,const FlipperApplicationManifest* Function,+,flipper_application_map_to_memory,FlipperApplicationLoadStatus,FlipperApplication* Function,+,flipper_application_preload,FlipperApplicationPreloadStatus,"FlipperApplication*, const char*" @@ -1022,7 +1034,7 @@ Function,+,furi_hal_cdc_get_port_settings,usb_cdc_line_coding*,uint8_t Function,+,furi_hal_cdc_receive,int32_t,"uint8_t, uint8_t*, uint16_t" Function,+,furi_hal_cdc_send,void,"uint8_t, uint8_t*, uint16_t" Function,+,furi_hal_cdc_set_callbacks,void,"uint8_t, CdcCallbacks*, void*" -Function,+,furi_hal_clock_deinit_early,void, +Function,-,furi_hal_clock_deinit_early,void, Function,-,furi_hal_clock_init,void, Function,-,furi_hal_clock_init_early,void, Function,+,furi_hal_clock_mco_disable,void, @@ -1103,7 +1115,7 @@ Function,+,furi_hal_hid_u2f_is_connected,_Bool, Function,+,furi_hal_hid_u2f_send_response,void,"uint8_t*, uint8_t" Function,+,furi_hal_hid_u2f_set_callback,void,"HidU2fCallback, void*" Function,+,furi_hal_i2c_acquire,void,FuriHalI2cBusHandle* -Function,+,furi_hal_i2c_deinit_early,void, +Function,-,furi_hal_i2c_deinit_early,void, Function,-,furi_hal_i2c_init,void, Function,-,furi_hal_i2c_init_early,void, Function,+,furi_hal_i2c_is_device_ready,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint32_t" @@ -1241,7 +1253,7 @@ Function,-,furi_hal_region_init,void, Function,+,furi_hal_region_is_frequency_allowed,_Bool,uint32_t Function,+,furi_hal_region_is_provisioned,_Bool, Function,+,furi_hal_region_set,void,FuriHalRegion* -Function,+,furi_hal_resources_deinit_early,void, +Function,-,furi_hal_resources_deinit_early,void, Function,-,furi_hal_resources_init,void, Function,-,furi_hal_resources_init_early,void, Function,+,furi_hal_rfid_change_read_config,void,"float, float" @@ -1270,7 +1282,7 @@ Function,+,furi_hal_rfid_tim_read_start,void, Function,+,furi_hal_rfid_tim_read_stop,void, Function,+,furi_hal_rfid_tim_reset,void, Function,+,furi_hal_rtc_datetime_to_timestamp,uint32_t,FuriHalRtcDateTime* -Function,+,furi_hal_rtc_deinit_early,void, +Function,-,furi_hal_rtc_deinit_early,void, Function,+,furi_hal_rtc_get_boot_mode,FuriHalRtcBootMode, Function,+,furi_hal_rtc_get_datetime,void,FuriHalRtcDateTime* Function,+,furi_hal_rtc_get_fault_data,uint32_t, @@ -1314,9 +1326,9 @@ Function,+,furi_hal_spi_bus_init,void,FuriHalSpiBus* Function,+,furi_hal_spi_bus_rx,_Bool,"FuriHalSpiBusHandle*, uint8_t*, size_t, uint32_t" Function,+,furi_hal_spi_bus_trx,_Bool,"FuriHalSpiBusHandle*, uint8_t*, uint8_t*, size_t, uint32_t" Function,+,furi_hal_spi_bus_tx,_Bool,"FuriHalSpiBusHandle*, uint8_t*, size_t, uint32_t" -Function,+,furi_hal_spi_deinit_early,void, -Function,-,furi_hal_spi_init,void, -Function,+,furi_hal_spi_init_early,void, +Function,-,furi_hal_spi_config_deinit_early,void, +Function,-,furi_hal_spi_config_init,void, +Function,-,furi_hal_spi_config_init_early,void, Function,+,furi_hal_spi_release,void,FuriHalSpiBusHandle* Function,-,furi_hal_subghz_dump_state,void, Function,+,furi_hal_subghz_flush_rx,void, @@ -1370,6 +1382,7 @@ Function,+,furi_hal_version_do_i_belong_here,_Bool, Function,+,furi_hal_version_get_ble_local_device_name_ptr,const char*, Function,+,furi_hal_version_get_ble_mac,const uint8_t*, Function,+,furi_hal_version_get_device_name_ptr,const char*, +Function,+,furi_hal_version_get_fcc_id,const char*, Function,+,furi_hal_version_get_firmware_version,const Version*, Function,+,furi_hal_version_get_hw_body,uint8_t, Function,+,furi_hal_version_get_hw_color,FuriHalVersionColor, @@ -1380,6 +1393,8 @@ Function,+,furi_hal_version_get_hw_region_name,const char*, Function,+,furi_hal_version_get_hw_target,uint8_t, Function,+,furi_hal_version_get_hw_timestamp,uint32_t, Function,+,furi_hal_version_get_hw_version,uint8_t, +Function,+,furi_hal_version_get_ic_id,const char*, +Function,+,furi_hal_version_get_model_code,const char*, Function,+,furi_hal_version_get_model_name,const char*, Function,+,furi_hal_version_get_name_ptr,const char*, Function,+,furi_hal_version_get_otp_version,FuriHalVersionOtpVersion, @@ -3026,6 +3041,8 @@ Variable,+,gpio_infrared_rx,const GpioPin, Variable,+,gpio_infrared_tx,const GpioPin, Variable,+,gpio_nfc_cs,const GpioPin, Variable,+,gpio_nfc_irq_rfid_pull,const GpioPin, +Variable,+,gpio_pins,const GpioPinRecord[], +Variable,+,gpio_pins_count,const size_t, Variable,+,gpio_rf_sw_0,const GpioPin, Variable,+,gpio_rfid_carrier,const GpioPin, Variable,+,gpio_rfid_carrier_out,const GpioPin, diff --git a/firmware/targets/f7/furi_hal/furi_hal.c b/firmware/targets/f7/furi_hal/furi_hal.c index ec82c377aa9..7f2f5759f58 100644 --- a/firmware/targets/f7/furi_hal/furi_hal.c +++ b/firmware/targets/f7/furi_hal/furi_hal.c @@ -17,7 +17,7 @@ void furi_hal_init_early() { furi_hal_os_init(); - furi_hal_spi_init_early(); + furi_hal_spi_config_init_early(); furi_hal_i2c_init_early(); furi_hal_light_init(); @@ -29,7 +29,7 @@ void furi_hal_deinit_early() { furi_hal_rtc_deinit_early(); furi_hal_i2c_deinit_early(); - furi_hal_spi_deinit_early(); + furi_hal_spi_config_deinit_early(); furi_hal_resources_deinit_early(); @@ -52,7 +52,7 @@ void furi_hal_init() { furi_hal_version_init(); furi_hal_region_init(); - furi_hal_spi_init(); + furi_hal_spi_config_init(); furi_hal_ibutton_init(); FURI_LOG_I(TAG, "iButton OK"); diff --git a/firmware/targets/f7/furi_hal/furi_hal_bt_hid.c b/firmware/targets/f7/furi_hal/furi_hal_bt_hid.c index ab3855f42cc..8259be2f6cd 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_bt_hid.c +++ b/firmware/targets/f7/furi_hal/furi_hal_bt_hid.c @@ -1,5 +1,5 @@ -#include "furi_hal_bt_hid.h" -#include "furi_hal_usb_hid.h" +#include +#include #include "usb_hid.h" #include "dev_info_service.h" #include "battery_service.h" diff --git a/firmware/targets/f7/furi_hal/furi_hal_bt_serial.c b/firmware/targets/f7/furi_hal/furi_hal_bt_serial.c index aa09dde5235..2539e6bd0e9 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_bt_serial.c +++ b/firmware/targets/f7/furi_hal/furi_hal_bt_serial.c @@ -1,4 +1,4 @@ -#include "furi_hal_bt_serial.h" +#include #include "dev_info_service.h" #include "battery_service.h" #include "serial_service.h" diff --git a/firmware/targets/f7/furi_hal/furi_hal_cortex.c b/firmware/targets/f7/furi_hal/furi_hal_cortex.c index 192b83ee452..d0bce503817 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_cortex.c +++ b/firmware/targets/f7/furi_hal/furi_hal_cortex.c @@ -1,4 +1,4 @@ -#include "furi_hal_cortex.h" +#include #include diff --git a/firmware/targets/f7/furi_hal/furi_hal_i2c_config.c b/firmware/targets/f7/furi_hal/furi_hal_i2c_config.c index 678eb29652f..afc4fdf5247 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_i2c_config.c +++ b/firmware/targets/f7/furi_hal/furi_hal_i2c_config.c @@ -1,4 +1,4 @@ -#include "furi_hal_i2c_config.h" +#include #include #include #include diff --git a/firmware/targets/furi_hal_include/furi_hal_ibutton.h b/firmware/targets/f7/furi_hal/furi_hal_ibutton.h similarity index 98% rename from firmware/targets/furi_hal_include/furi_hal_ibutton.h rename to firmware/targets/f7/furi_hal/furi_hal_ibutton.h index 84ef0cd6a49..fb57d628be7 100644 --- a/firmware/targets/furi_hal_include/furi_hal_ibutton.h +++ b/firmware/targets/f7/furi_hal/furi_hal_ibutton.h @@ -7,7 +7,7 @@ #include #include -#include "furi_hal_gpio.h" +#include #ifdef __cplusplus extern "C" { diff --git a/firmware/targets/f7/furi_hal/furi_hal_infrared.c b/firmware/targets/f7/furi_hal/furi_hal_infrared.c index 442ae715d0f..dea1112ab4c 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_infrared.c +++ b/firmware/targets/f7/furi_hal/furi_hal_infrared.c @@ -1,4 +1,4 @@ -#include "furi_hal_infrared.h" +#include #include #include "stm32wbxx_ll_dma.h" #include "sys/_stdint.h" diff --git a/firmware/targets/f7/furi_hal/furi_hal_interrupt.c b/firmware/targets/f7/furi_hal/furi_hal_interrupt.c index 038ae948946..1b1132d0c3b 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_interrupt.c +++ b/firmware/targets/f7/furi_hal/furi_hal_interrupt.c @@ -1,5 +1,5 @@ -#include "furi_hal_interrupt.h" -#include "furi_hal_os.h" +#include +#include #include diff --git a/firmware/targets/f7/furi_hal/furi_hal_light.c b/firmware/targets/f7/furi_hal/furi_hal_light.c index e6b3ab7d9a6..83e1603b75a 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_light.c +++ b/firmware/targets/f7/furi_hal/furi_hal_light.c @@ -1,5 +1,5 @@ #include -#include "furi_hal_resources.h" +#include #include #include #include diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc.c b/firmware/targets/f7/furi_hal/furi_hal_nfc.c index 6381d1a91b2..ce81fd0585d 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_nfc.c +++ b/firmware/targets/f7/furi_hal/furi_hal_nfc.c @@ -1,5 +1,5 @@ #include -#include "furi_hal_nfc.h" +#include #include #include #include diff --git a/firmware/targets/furi_hal_include/furi_hal_nfc.h b/firmware/targets/f7/furi_hal/furi_hal_nfc.h similarity index 100% rename from firmware/targets/furi_hal_include/furi_hal_nfc.h rename to firmware/targets/f7/furi_hal/furi_hal_nfc.h diff --git a/firmware/targets/f7/furi_hal/furi_hal_pwm.c b/firmware/targets/f7/furi_hal/furi_hal_pwm.c index e47f752abd0..8f84b5fd8d5 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_pwm.c +++ b/firmware/targets/f7/furi_hal/furi_hal_pwm.c @@ -1,4 +1,4 @@ -#include "furi_hal_pwm.h" +#include #include #include diff --git a/firmware/targets/f7/furi_hal/furi_hal_random.c b/firmware/targets/f7/furi_hal/furi_hal_random.c index cd019c0d9ad..f36407cc16a 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_random.c +++ b/firmware/targets/f7/furi_hal/furi_hal_random.c @@ -1,4 +1,4 @@ -#include "furi_hal_random.h" +#include #include #include diff --git a/firmware/targets/f7/furi_hal/furi_hal_resources.c b/firmware/targets/f7/furi_hal/furi_hal_resources.c index 4a32d70871c..03cc6e714c9 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_resources.c +++ b/firmware/targets/f7/furi_hal/furi_hal_resources.c @@ -62,6 +62,23 @@ const GpioPin periph_power = {.port = GPIOA, .pin = LL_GPIO_PIN_3}; const GpioPin gpio_usb_dm = {.port = GPIOA, .pin = LL_GPIO_PIN_11}; const GpioPin gpio_usb_dp = {.port = GPIOA, .pin = LL_GPIO_PIN_12}; +const GpioPinRecord gpio_pins[] = { + {.pin = &gpio_ext_pa7, .name = "PA7", .debug = false}, + {.pin = &gpio_ext_pa6, .name = "PA6", .debug = false}, + {.pin = &gpio_ext_pa4, .name = "PA4", .debug = false}, + {.pin = &gpio_ext_pb3, .name = "PB3", .debug = false}, + {.pin = &gpio_ext_pb2, .name = "PB2", .debug = false}, + {.pin = &gpio_ext_pc3, .name = "PC3", .debug = false}, + {.pin = &gpio_ext_pc1, .name = "PC1", .debug = false}, + {.pin = &gpio_ext_pc0, .name = "PC0", .debug = false}, + + /* Dangerous pins, may damage hardware */ + {.pin = &gpio_usart_rx, .name = "PB7", .debug = true}, + {.pin = &gpio_speaker, .name = "PB8", .debug = true}, +}; + +const size_t gpio_pins_count = sizeof(gpio_pins) / sizeof(GpioPinRecord); + const InputPin input_pins[] = { {.gpio = &gpio_button_up, .key = InputKeyUp, .inverted = true, .name = "Up"}, {.gpio = &gpio_button_down, .key = InputKeyDown, .inverted = true, .name = "Down"}, diff --git a/firmware/targets/f7/furi_hal/furi_hal_resources.h b/firmware/targets/f7/furi_hal/furi_hal_resources.h index 64e5e19f900..51ea7bcc142 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_resources.h +++ b/firmware/targets/f7/furi_hal/furi_hal_resources.h @@ -38,9 +38,18 @@ typedef struct { const char* name; } InputPin; +typedef struct { + const GpioPin* pin; + const char* name; + const bool debug; +} GpioPinRecord; + extern const InputPin input_pins[]; extern const size_t input_pins_count; +extern const GpioPinRecord gpio_pins[]; +extern const size_t gpio_pins_count; + extern const GpioPin vibro_gpio; extern const GpioPin ibutton_gpio; diff --git a/firmware/targets/furi_hal_include/furi_hal_rfid.h b/firmware/targets/f7/furi_hal/furi_hal_rfid.h similarity index 100% rename from firmware/targets/furi_hal_include/furi_hal_rfid.h rename to firmware/targets/f7/furi_hal/furi_hal_rfid.h diff --git a/firmware/targets/f7/furi_hal/furi_hal_sd.c b/firmware/targets/f7/furi_hal/furi_hal_sd.c index 688a4e61bc8..1b0de562876 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_sd.c +++ b/firmware/targets/f7/furi_hal/furi_hal_sd.c @@ -1,24 +1,21 @@ -#include "furi_hal_sd.h" +#include #include #include #include void hal_sd_detect_init(void) { // low speed input with pullup - LL_GPIO_SetPinMode(SD_CD_GPIO_Port, SD_CD_Pin, LL_GPIO_MODE_INPUT); - LL_GPIO_SetPinSpeed(SD_CD_GPIO_Port, SD_CD_Pin, LL_GPIO_SPEED_FREQ_LOW); - LL_GPIO_SetPinPull(SD_CD_GPIO_Port, SD_CD_Pin, LL_GPIO_PULL_UP); + furi_hal_gpio_init(&gpio_sdcard_cd, GpioModeInput, GpioPullUp, GpioSpeedLow); } void hal_sd_detect_set_low(void) { // low speed input with pullup - LL_GPIO_SetPinMode(SD_CD_GPIO_Port, SD_CD_Pin, LL_GPIO_MODE_OUTPUT); - LL_GPIO_SetPinOutputType(SD_CD_GPIO_Port, SD_CD_Pin, LL_GPIO_OUTPUT_OPENDRAIN); - LL_GPIO_ResetOutputPin(SD_CD_GPIO_Port, SD_CD_Pin); + furi_hal_gpio_init_simple(&gpio_sdcard_cd, GpioModeOutputOpenDrain); + furi_hal_gpio_write(&gpio_sdcard_cd, 0); } bool hal_sd_detect(void) { - bool result = !(LL_GPIO_IsInputPinSet(SD_CD_GPIO_Port, SD_CD_Pin)); + bool result = !furi_hal_gpio_read(&gpio_sdcard_cd); return result; } diff --git a/firmware/targets/f7/furi_hal/furi_hal_spi.c b/firmware/targets/f7/furi_hal/furi_hal_spi.c index 2d54278d64b..2f9d8708086 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_spi.c +++ b/firmware/targets/f7/furi_hal/furi_hal_spi.c @@ -1,38 +1,14 @@ -#include "furi_hal_spi.h" -#include "furi_hal_resources.h" +#include +#include #include #include #include -#include #include #include #include -#define TAG "FuriHalSpi" - -void furi_hal_spi_init_early() { - furi_hal_spi_bus_init(&furi_hal_spi_bus_d); - furi_hal_spi_bus_handle_init(&furi_hal_spi_bus_handle_display); -} - -void furi_hal_spi_deinit_early() { - furi_hal_spi_bus_handle_deinit(&furi_hal_spi_bus_handle_display); - furi_hal_spi_bus_deinit(&furi_hal_spi_bus_d); -} - -void furi_hal_spi_init() { - furi_hal_spi_bus_init(&furi_hal_spi_bus_r); - - furi_hal_spi_bus_handle_init(&furi_hal_spi_bus_handle_subghz); - furi_hal_spi_bus_handle_init(&furi_hal_spi_bus_handle_nfc); - furi_hal_spi_bus_handle_init(&furi_hal_spi_bus_handle_sd_fast); - furi_hal_spi_bus_handle_init(&furi_hal_spi_bus_handle_sd_slow); - - FURI_LOG_I(TAG, "Init OK"); -} - void furi_hal_spi_bus_init(FuriHalSpiBus* bus) { furi_assert(bus); bus->callback(bus, FuriHalSpiBusEventInit); diff --git a/firmware/targets/f7/furi_hal/furi_hal_spi_config.c b/firmware/targets/f7/furi_hal/furi_hal_spi_config.c index 56f67bbf8a4..9cf332dacd6 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_spi_config.c +++ b/firmware/targets/f7/furi_hal/furi_hal_spi_config.c @@ -1,5 +1,9 @@ #include #include +#include +#include + +#define TAG "FuriHalSpiConfig" /* SPI Presets */ @@ -72,6 +76,27 @@ const LL_SPI_InitTypeDef furi_hal_spi_preset_1edge_low_2m = { FuriMutex* furi_hal_spi_bus_r_mutex = NULL; +void furi_hal_spi_config_init_early() { + furi_hal_spi_bus_init(&furi_hal_spi_bus_d); + furi_hal_spi_bus_handle_init(&furi_hal_spi_bus_handle_display); +} + +void furi_hal_spi_config_deinit_early() { + furi_hal_spi_bus_handle_deinit(&furi_hal_spi_bus_handle_display); + furi_hal_spi_bus_deinit(&furi_hal_spi_bus_d); +} + +void furi_hal_spi_config_init() { + furi_hal_spi_bus_init(&furi_hal_spi_bus_r); + + furi_hal_spi_bus_handle_init(&furi_hal_spi_bus_handle_subghz); + furi_hal_spi_bus_handle_init(&furi_hal_spi_bus_handle_nfc); + furi_hal_spi_bus_handle_init(&furi_hal_spi_bus_handle_sd_fast); + furi_hal_spi_bus_handle_init(&furi_hal_spi_bus_handle_sd_slow); + + FURI_LOG_I(TAG, "Init OK"); +} + static void furi_hal_spi_bus_r_event_callback(FuriHalSpiBus* bus, FuriHalSpiBusEvent event) { if(event == FuriHalSpiBusEventInit) { furi_hal_spi_bus_r_mutex = furi_mutex_alloc(FuriMutexTypeNormal); diff --git a/firmware/targets/f7/furi_hal/furi_hal_subghz.c b/firmware/targets/f7/furi_hal/furi_hal_subghz.c index 3441fd965a5..9bde15c33f2 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_subghz.c +++ b/firmware/targets/f7/furi_hal/furi_hal_subghz.c @@ -1,5 +1,5 @@ -#include "furi_hal_subghz.h" -#include "furi_hal_subghz_configs.h" +#include +#include #include #include diff --git a/firmware/targets/furi_hal_include/furi_hal_subghz.h b/firmware/targets/f7/furi_hal/furi_hal_subghz.h similarity index 100% rename from firmware/targets/furi_hal_include/furi_hal_subghz.h rename to firmware/targets/f7/furi_hal/furi_hal_subghz.h diff --git a/firmware/targets/f7/furi_hal/furi_hal_target_hw.h b/firmware/targets/f7/furi_hal/furi_hal_target_hw.h new file mode 100644 index 00000000000..128122f8439 --- /dev/null +++ b/firmware/targets/f7/furi_hal/furi_hal_target_hw.h @@ -0,0 +1,6 @@ +#pragma once + +#include +#include +#include +#include diff --git a/firmware/targets/f7/furi_hal/furi_hal_usb.c b/firmware/targets/f7/furi_hal/furi_hal_usb.c index e740155f56c..0038bd348c0 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_usb.c +++ b/firmware/targets/f7/furi_hal/furi_hal_usb.c @@ -1,6 +1,6 @@ -#include "furi_hal_version.h" -#include "furi_hal_usb_i.h" -#include "furi_hal_usb.h" +#include +#include +#include #include #include #include diff --git a/firmware/targets/f7/furi_hal/furi_hal_usb_cdc.c b/firmware/targets/f7/furi_hal/furi_hal_usb_cdc.c index 88deafdcc08..4c4f727badb 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_usb_cdc.c +++ b/firmware/targets/f7/furi_hal/furi_hal_usb_cdc.c @@ -1,7 +1,7 @@ -#include "furi_hal_version.h" -#include "furi_hal_usb_i.h" -#include "furi_hal_usb.h" -#include "furi_hal_usb_cdc.h" +#include +#include +#include +#include #include #include "usb.h" diff --git a/firmware/targets/f7/furi_hal/furi_hal_usb_hid.c b/firmware/targets/f7/furi_hal/furi_hal_usb_hid.c index fc1ce024c60..a3bc842274a 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_usb_hid.c +++ b/firmware/targets/f7/furi_hal/furi_hal_usb_hid.c @@ -1,7 +1,7 @@ -#include "furi_hal_version.h" -#include "furi_hal_usb_i.h" -#include "furi_hal_usb.h" -#include "furi_hal_usb_hid.h" +#include +#include +#include +#include #include #include "usb.h" diff --git a/firmware/targets/f7/furi_hal/furi_hal_usb_u2f.c b/firmware/targets/f7/furi_hal/furi_hal_usb_u2f.c index c099aec8a7c..fe711512a14 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_usb_u2f.c +++ b/firmware/targets/f7/furi_hal/furi_hal_usb_u2f.c @@ -1,7 +1,7 @@ -#include "furi_hal_version.h" -#include "furi_hal_usb_i.h" -#include "furi_hal_usb_hid_u2f.h" -#include "furi_hal_usb.h" +#include +#include +#include +#include #include #include "usb.h" #include "usb_hid.h" diff --git a/firmware/targets/f7/furi_hal/furi_hal_version.c b/firmware/targets/f7/furi_hal/furi_hal_version.c index b7827ac7fcd..859a8c39fc6 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_version.c +++ b/firmware/targets/f7/furi_hal/furi_hal_version.c @@ -195,14 +195,6 @@ void furi_hal_version_init() { FURI_LOG_I(TAG, "Init OK"); } -bool furi_hal_version_do_i_belong_here() { - return furi_hal_version_get_hw_target() == 7; -} - -const char* furi_hal_version_get_model_name() { - return "Flipper Zero"; -} - FuriHalVersionOtpVersion furi_hal_version_get_otp_version() { if(*(uint64_t*)FURI_HAL_VERSION_OTP_ADDRESS == 0xFFFFFFFF) { return FuriHalVersionOtpVersionEmpty; diff --git a/firmware/targets/f7/furi_hal/furi_hal_version_device.c b/firmware/targets/f7/furi_hal/furi_hal_version_device.c new file mode 100644 index 00000000000..c059c2cbe69 --- /dev/null +++ b/firmware/targets/f7/furi_hal/furi_hal_version_device.c @@ -0,0 +1,21 @@ +#include + +bool furi_hal_version_do_i_belong_here() { + return (furi_hal_version_get_hw_target() == 7) || (furi_hal_version_get_hw_target() == 0); +} + +const char* furi_hal_version_get_model_name() { + return "Flipper Zero"; +} + +const char* furi_hal_version_get_model_code() { + return "FZ.1"; +} + +const char* furi_hal_version_get_fcc_id() { + return "2A2V6-FZ"; +} + +const char* furi_hal_version_get_ic_id() { + return "27624-FZ"; +} diff --git a/firmware/targets/f7/Inc/FreeRTOSConfig.h b/firmware/targets/f7/inc/FreeRTOSConfig.h similarity index 100% rename from firmware/targets/f7/Inc/FreeRTOSConfig.h rename to firmware/targets/f7/inc/FreeRTOSConfig.h diff --git a/firmware/targets/f7/Inc/alt_boot.h b/firmware/targets/f7/inc/alt_boot.h similarity index 100% rename from firmware/targets/f7/Inc/alt_boot.h rename to firmware/targets/f7/inc/alt_boot.h diff --git a/firmware/targets/f7/Inc/stm32.h b/firmware/targets/f7/inc/stm32.h similarity index 100% rename from firmware/targets/f7/Inc/stm32.h rename to firmware/targets/f7/inc/stm32.h diff --git a/firmware/targets/f7/Inc/stm32_assert.h b/firmware/targets/f7/inc/stm32_assert.h similarity index 100% rename from firmware/targets/f7/Inc/stm32_assert.h rename to firmware/targets/f7/inc/stm32_assert.h diff --git a/firmware/targets/f7/Src/dfu.c b/firmware/targets/f7/src/dfu.c similarity index 100% rename from firmware/targets/f7/Src/dfu.c rename to firmware/targets/f7/src/dfu.c diff --git a/firmware/targets/f7/Src/main.c b/firmware/targets/f7/src/main.c similarity index 100% rename from firmware/targets/f7/Src/main.c rename to firmware/targets/f7/src/main.c diff --git a/firmware/targets/f7/Src/recovery.c b/firmware/targets/f7/src/recovery.c similarity index 99% rename from firmware/targets/f7/Src/recovery.c rename to firmware/targets/f7/src/recovery.c index fa57482ca5b..d56be4fe076 100644 --- a/firmware/targets/f7/Src/recovery.c +++ b/firmware/targets/f7/src/recovery.c @@ -51,4 +51,4 @@ void flipper_boot_recovery_exec() { furi_hal_rtc_set_pin_fails(0); furi_hal_rtc_reset_flag(FuriHalRtcFlagLock); } -} \ No newline at end of file +} diff --git a/firmware/targets/f7/Src/system_stm32wbxx.c b/firmware/targets/f7/src/system_stm32wbxx.c similarity index 100% rename from firmware/targets/f7/Src/system_stm32wbxx.c rename to firmware/targets/f7/src/system_stm32wbxx.c diff --git a/firmware/targets/f7/Src/update.c b/firmware/targets/f7/src/update.c similarity index 99% rename from firmware/targets/f7/Src/update.c rename to firmware/targets/f7/src/update.c index a68a8b7a714..b223a5dc372 100644 --- a/firmware/targets/f7/Src/update.c +++ b/firmware/targets/f7/src/update.c @@ -42,7 +42,7 @@ static bool flipper_update_init() { furi_hal_rtc_init(); furi_hal_interrupt_init(); - furi_hal_spi_init(); + furi_hal_spi_config_init(); MX_FATFS_Init(); if(!hal_sd_detect()) { diff --git a/firmware/targets/f7/stm32wb55xx_flash.ld b/firmware/targets/f7/stm32wb55xx_flash.ld index e1fb98b9fed..df4c5b72662 100644 --- a/firmware/targets/f7/stm32wb55xx_flash.ld +++ b/firmware/targets/f7/stm32wb55xx_flash.ld @@ -1,7 +1,7 @@ /** ***************************************************************************** ** -** File : stm32wb55xx_flash_cm4.ld +** File : stm32wb55xx_flash.ld ** ** Abstract : System Workbench Minimal System calls file ** diff --git a/firmware/targets/f7/stm32wb55xx_ram_fw.ld b/firmware/targets/f7/stm32wb55xx_ram_fw.ld index db9e407c9d7..0ac9be4df81 100644 --- a/firmware/targets/f7/stm32wb55xx_ram_fw.ld +++ b/firmware/targets/f7/stm32wb55xx_ram_fw.ld @@ -1,7 +1,7 @@ /** ***************************************************************************** ** -** File : stm32wb55xx_flash_cm4.ld +** File : stm32wb55xx_ram_fw.ld ** ** Abstract : System Workbench Minimal System calls file ** diff --git a/firmware/targets/f7/target.json b/firmware/targets/f7/target.json new file mode 100644 index 00000000000..49aa109bd76 --- /dev/null +++ b/firmware/targets/f7/target.json @@ -0,0 +1,45 @@ +{ + "include_paths": [ + "ble_glue", + "fatfs", + "furi_hal", + "inc" + ], + "sdk_header_paths": [ + "../furi_hal_include", + "furi_hal", + "platform_specific" + ], + "startup_script": "startup_stm32wb55xx_cm4.s", + "linker_script_flash": "stm32wb55xx_flash.ld", + "linker_script_ram": "stm32wb55xx_ram_fw.ld", + "linker_script_app": "application_ext.ld", + "sdk_symbols": "api_symbols.csv", + "linker_dependencies": [ + "print", + "flipper7", + "furi", + "freertos", + "stm32cubewb", + "hwdrivers", + "fatfs", + "littlefs", + "subghz", + "flipperformat", + "toolbox", + "nfc", + "microtar", + "usb_stm32", + "st25rfal002", + "infrared", + "appframe", + "assets", + "one_wire", + "misc", + "mbedtls", + "lfrfid", + "flipper_application", + "flipperformat", + "toolbox" + ] +} \ No newline at end of file diff --git a/firmware/targets/furi_hal_include/furi_hal.h b/firmware/targets/furi_hal_include/furi_hal.h index 8613c4d5e04..ad4340dd409 100644 --- a/firmware/targets/furi_hal_include/furi_hal.h +++ b/firmware/targets/furi_hal_include/furi_hal.h @@ -10,37 +10,34 @@ template struct STOP_EXTERNING_ME {}; #endif -#include "furi_hal_cortex.h" -#include "furi_hal_clock.h" -#include "furi_hal_crypto.h" -#include "furi_hal_console.h" -#include "furi_hal_debug.h" -#include "furi_hal_os.h" -#include "furi_hal_sd.h" -#include "furi_hal_i2c.h" -#include "furi_hal_resources.h" -#include "furi_hal_region.h" -#include "furi_hal_rtc.h" -#include "furi_hal_speaker.h" -#include "furi_hal_gpio.h" -#include "furi_hal_light.h" -#include "furi_hal_power.h" -#include "furi_hal_interrupt.h" -#include "furi_hal_version.h" -#include "furi_hal_bt.h" -#include "furi_hal_spi.h" -#include "furi_hal_flash.h" -#include "furi_hal_subghz.h" -#include "furi_hal_vibro.h" -#include "furi_hal_ibutton.h" -#include "furi_hal_rfid.h" -#include "furi_hal_nfc.h" -#include "furi_hal_usb.h" -#include "furi_hal_usb_hid.h" -#include "furi_hal_compress.h" -#include "furi_hal_uart.h" -#include "furi_hal_info.h" -#include "furi_hal_random.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #ifdef __cplusplus extern "C" { diff --git a/firmware/targets/furi_hal_include/furi_hal_bt.h b/firmware/targets/furi_hal_include/furi_hal_bt.h index 800fc3fe3f7..196b2edb3ad 100644 --- a/firmware/targets/furi_hal_include/furi_hal_bt.h +++ b/firmware/targets/furi_hal_include/furi_hal_bt.h @@ -12,7 +12,7 @@ #include #include -#include "furi_hal_bt_serial.h" +#include #define FURI_HAL_BT_STACK_VERSION_MAJOR (1) #define FURI_HAL_BT_STACK_VERSION_MINOR (12) diff --git a/firmware/targets/furi_hal_include/furi_hal_spi.h b/firmware/targets/furi_hal_include/furi_hal_spi.h index df7ffa93d23..ab00ef0d700 100644 --- a/firmware/targets/furi_hal_include/furi_hal_spi.h +++ b/firmware/targets/furi_hal_include/furi_hal_spi.h @@ -8,13 +8,13 @@ extern "C" { #endif /** Early initialize SPI HAL */ -void furi_hal_spi_init_early(); +void furi_hal_spi_config_init_early(); /** Early deinitialize SPI HAL */ -void furi_hal_spi_deinit_early(); +void furi_hal_spi_config_deinit_early(); /** Initialize SPI HAL */ -void furi_hal_spi_init(); +void furi_hal_spi_config_init(); /** Initialize SPI Bus * diff --git a/firmware/targets/furi_hal_include/furi_hal_version.h b/firmware/targets/furi_hal_include/furi_hal_version.h index 720fdfd173b..aec4fc787aa 100644 --- a/firmware/targets/furi_hal_include/furi_hal_version.h +++ b/firmware/targets/furi_hal_include/furi_hal_version.h @@ -67,6 +67,24 @@ bool furi_hal_version_do_i_belong_here(); */ const char* furi_hal_version_get_model_name(); +/** Get model name + * + * @return model code C-string + */ +const char* furi_hal_version_get_model_code(); + +/** Get FCC ID + * + * @return FCC id as C-string + */ +const char* furi_hal_version_get_fcc_id(); + +/** Get IC id + * + * @return IC id as C-string + */ +const char* furi_hal_version_get_ic_id(); + /** Get OTP version * * @return OTP Version diff --git a/lib/SConscript b/lib/SConscript index abede5f3324..d1e8e8c7eea 100644 --- a/lib/SConscript +++ b/lib/SConscript @@ -8,7 +8,6 @@ env.Append( Dir("flipper_format"), Dir("infrared"), Dir("nfc"), - Dir("one_wire"), Dir("ST25RFAL002"), Dir("subghz"), Dir("toolbox"), @@ -16,16 +15,9 @@ env.Append( Dir("update_util"), Dir("print"), ], - SDK_HEADERS=[ - File("one_wire/one_wire_host_timing.h"), - File("one_wire/one_wire_host.h"), - File("one_wire/one_wire_slave.h"), - File("one_wire/one_wire_device.h"), - File("one_wire/ibutton/ibutton_worker.h"), - File("one_wire/maxim_crc.h"), - ], ) + env.Append( CPPPATH=[ "#/", @@ -34,6 +26,23 @@ env.Append( # Ugly hack Dir("../assets/compiled"), ], + SDK_HEADERS=[ + *( + File(f"#/lib/mlib/m-{name}.h") + for name in ( + "algo", + "array", + "bptree", + "core", + "deque", + "dict", + "list", + "rbtree", + "tuple", + "variant", + ) + ), + ], CPPDEFINES=[ '"M_MEMORY_FULL(x)=abort()"', ], @@ -47,13 +56,13 @@ env.Append( # littlefs # subghz # toolbox +# one_wire +# micro-ecc # misc # digital_signal -# fnv1a-hash -# micro-ecc +# fnv1a_hash # microtar # nfc -# one_wire # qrcode # u8g2 # update_util @@ -77,6 +86,7 @@ libs = env.BuildModules( "drivers", "fatfs", "flipper_format", + "one_wire", "infrared", "littlefs", "mbedtls", diff --git a/lib/app-scened-template/view_modules/popup_vm.cpp b/lib/app-scened-template/view_modules/popup_vm.cpp index e2c8732e8dc..330aa44ca96 100644 --- a/lib/app-scened-template/view_modules/popup_vm.cpp +++ b/lib/app-scened-template/view_modules/popup_vm.cpp @@ -1,5 +1,6 @@ #include "popup_vm.h" -#include "gui/modules/popup.h" +#include + PopupVM::PopupVM() { popup = popup_alloc(); } diff --git a/lib/flipper_application/application_manifest.c b/lib/flipper_application/application_manifest.c index ab92e493046..fea92c26227 100644 --- a/lib/flipper_application/application_manifest.c +++ b/lib/flipper_application/application_manifest.c @@ -1,5 +1,7 @@ #include "application_manifest.h" +#include + bool flipper_application_manifest_is_valid(const FlipperApplicationManifest* manifest) { if((manifest->base.manifest_magic != FAP_MANIFEST_MAGIC) || (manifest->base.manifest_version != FAP_MANIFEST_SUPPORTED_VERSION)) { @@ -19,3 +21,8 @@ bool flipper_application_manifest_is_compatible( return true; } + +bool flipper_application_manifest_is_target_compatible(const FlipperApplicationManifest* manifest) { + const Version* version = furi_hal_version_get_firmware_version(); + return version_get_target(version) == manifest->base.hardware_target_id; +} \ No newline at end of file diff --git a/lib/flipper_application/application_manifest.h b/lib/flipper_application/application_manifest.h index f46d44fd7d0..25e4f8d0a51 100644 --- a/lib/flipper_application/application_manifest.h +++ b/lib/flipper_application/application_manifest.h @@ -65,6 +65,14 @@ bool flipper_application_manifest_is_compatible( const FlipperApplicationManifest* manifest, const ElfApiInterface* api_interface); +/** + * @brief Check if application is compatible with current hardware + * + * @param manifest + * @return bool + */ +bool flipper_application_manifest_is_target_compatible(const FlipperApplicationManifest* manifest); + #ifdef __cplusplus } #endif diff --git a/lib/flipper_application/flipper_application.c b/lib/flipper_application/flipper_application.c index 8b049a7d4f4..58909218ae1 100644 --- a/lib/flipper_application/flipper_application.c +++ b/lib/flipper_application/flipper_application.c @@ -43,6 +43,10 @@ static FlipperApplicationPreloadStatus return FlipperApplicationPreloadStatusInvalidManifest; } + if(!flipper_application_manifest_is_target_compatible(&app->manifest)) { + return FlipperApplicationPreloadStatusTargetMismatch; + } + if(!flipper_application_manifest_is_compatible( &app->manifest, elf_file_get_api_interface(app->elf))) { return FlipperApplicationPreloadStatusApiMismatch; diff --git a/lib/misc.scons b/lib/misc.scons index 91ad276a0b2..cd2377ceb75 100644 --- a/lib/misc.scons +++ b/lib/misc.scons @@ -26,7 +26,6 @@ sources = [] libs_recurse = [ "digital_signal", "micro-ecc", - "one_wire", "u8g2", "update_util", ] diff --git a/lib/nfc/parsers/all_in_one.c b/lib/nfc/parsers/all_in_one.c index c02710a2958..edcc0d0c7ca 100644 --- a/lib/nfc/parsers/all_in_one.c +++ b/lib/nfc/parsers/all_in_one.c @@ -4,7 +4,7 @@ #include #include -#include "furi_hal.h" +#include #define ALL_IN_ONE_LAYOUT_UNKNOWN 0 #define ALL_IN_ONE_LAYOUT_A 1 diff --git a/lib/nfc/parsers/plantain_4k_parser.c b/lib/nfc/parsers/plantain_4k_parser.c index e636bee002b..aed41965c9f 100644 --- a/lib/nfc/parsers/plantain_4k_parser.c +++ b/lib/nfc/parsers/plantain_4k_parser.c @@ -3,7 +3,7 @@ #include #include -#include "furi_hal.h" +#include static const MfClassicAuthContext plantain_keys_4k[] = { {.sector = 0, .key_a = 0xFFFFFFFFFFFF, .key_b = 0xFFFFFFFFFFFF}, diff --git a/lib/nfc/parsers/plantain_parser.c b/lib/nfc/parsers/plantain_parser.c index c0e2a094723..3a1d1773256 100644 --- a/lib/nfc/parsers/plantain_parser.c +++ b/lib/nfc/parsers/plantain_parser.c @@ -3,7 +3,7 @@ #include #include -#include "furi_hal.h" +#include static const MfClassicAuthContext plantain_keys[] = { {.sector = 0, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, diff --git a/lib/nfc/parsers/two_cities.c b/lib/nfc/parsers/two_cities.c index 335248b2abd..0e2ed5690c2 100644 --- a/lib/nfc/parsers/two_cities.c +++ b/lib/nfc/parsers/two_cities.c @@ -4,7 +4,7 @@ #include #include -#include "furi_hal.h" +#include static const MfClassicAuthContext two_cities_keys_4k[] = { {.sector = 0, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, diff --git a/lib/nfc/protocols/mifare_ultralight.c b/lib/nfc/protocols/mifare_ultralight.c index d642e290a02..0e28c0074f1 100644 --- a/lib/nfc/protocols/mifare_ultralight.c +++ b/lib/nfc/protocols/mifare_ultralight.c @@ -3,7 +3,7 @@ #include "mifare_ultralight.h" #include "nfc_util.h" #include -#include "furi_hal_nfc.h" +#include #define TAG "MfUltralight" diff --git a/lib/one_wire/SConscript b/lib/one_wire/SConscript new file mode 100644 index 00000000000..5e06d21e391 --- /dev/null +++ b/lib/one_wire/SConscript @@ -0,0 +1,27 @@ +Import("env") + +env.Append( + LINT_SOURCES=[ + Dir("."), + ], + CPPPATH=[ + "#/lib/one_wire", + ], + SDK_HEADERS=[ + File("one_wire_host_timing.h"), + File("one_wire_host.h"), + File("one_wire_slave.h"), + File("one_wire_device.h"), + File("ibutton/ibutton_worker.h"), + File("maxim_crc.h"), + ], +) + +libenv = env.Clone(FW_LIB_NAME="one_wire") +libenv.ApplyLibFlags() + +sources = libenv.GlobRecursive("*.c*") + +lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources) +libenv.Install("${LIB_DIST_DIR}", lib) +Return("lib") diff --git a/lib/subghz/blocks/generic.h b/lib/subghz/blocks/generic.h index 1448f0ea622..d1c7dc356e0 100644 --- a/lib/subghz/blocks/generic.h +++ b/lib/subghz/blocks/generic.h @@ -5,8 +5,8 @@ #include #include -#include "furi.h" -#include "furi_hal.h" +#include +#include #include "../types.h" #ifdef __cplusplus diff --git a/lib/subghz/protocols/princeton_for_testing.c b/lib/subghz/protocols/princeton_for_testing.c index fa5616020e6..478d14cdfca 100644 --- a/lib/subghz/protocols/princeton_for_testing.c +++ b/lib/subghz/protocols/princeton_for_testing.c @@ -1,6 +1,6 @@ #include "princeton_for_testing.h" -#include "furi_hal.h" +#include #include "../blocks/math.h" /* diff --git a/lib/subghz/subghz_setting.c b/lib/subghz/subghz_setting.c index 57e23c38cf9..5d7ea0cecf8 100644 --- a/lib/subghz/subghz_setting.c +++ b/lib/subghz/subghz_setting.c @@ -4,7 +4,7 @@ #include #include -#include "furi_hal_subghz_configs.h" +#include #define TAG "SubGhzSetting" diff --git a/lib/update_util/update_operation.c b/lib/update_util/update_operation.c index c6a9ccc5feb..6e05b023317 100644 --- a/lib/update_util/update_operation.c +++ b/lib/update_util/update_operation.c @@ -21,8 +21,10 @@ static const char* update_prepare_result_descr[] = { [UpdatePrepareResultStageMissing] = "Missing Stage2 loader", [UpdatePrepareResultStageIntegrityError] = "Corrupted Stage2 loader", [UpdatePrepareResultManifestPointerError] = "Failed to create update pointer file", + [UpdatePrepareResultTargetMismatch] = "Hardware target mismatch", [UpdatePrepareResultOutdatedManifestVersion] = "Update package is too old", [UpdatePrepareResultIntFull] = "Need more free space in internal storage", + [UpdatePrepareResultUnspecifiedError] = "Unknown error", }; const char* update_operation_describe_preparation_result(const UpdatePrepareResult value) { @@ -163,8 +165,9 @@ UpdatePrepareResult update_operation_prepare(const char* manifest_file_path) { result = UpdatePrepareResultOutdatedManifestVersion; break; } - - if(furi_hal_version_get_hw_target() != manifest->target) { + /* Only compare hardware target if it is set - pre-production devices accept any firmware*/ + if(furi_hal_version_get_hw_target() && + (furi_hal_version_get_hw_target() != manifest->target)) { result = UpdatePrepareResultTargetMismatch; break; } diff --git a/scripts/fbt/appmanifest.py b/scripts/fbt/appmanifest.py index a7460d889df..aa03d265b3f 100644 --- a/scripts/fbt/appmanifest.py +++ b/scripts/fbt/appmanifest.py @@ -1,5 +1,5 @@ from dataclasses import dataclass, field -from typing import List, Optional, Tuple +from typing import List, Optional, Tuple, Callable from enum import Enum import os @@ -71,6 +71,9 @@ class Library: _appdir: Optional[object] = None _apppath: Optional[str] = None + def supports_hardware_target(self, target: str): + return target in self.targets or "all" in self.targets + class AppManager: def __init__(self): @@ -158,15 +161,26 @@ class AppBuildset: FlipperAppType.STARTUP, ) - def __init__(self, appmgr: AppManager, appnames: List[str], hw_target: str): + @staticmethod + def print_writer(message): + print(message) + + def __init__( + self, + appmgr: AppManager, + appnames: List[str], + hw_target: str, + message_writer: Callable = None, + ): self.appmgr = appmgr self.appnames = set(appnames) - self.hw_target = hw_target self._orig_appnames = appnames + self.hw_target = hw_target + self._writer = message_writer if message_writer else self.print_writer self._process_deps() - self._filter_by_target() self._check_conflicts() self._check_unsatisfied() # unneeded? + self._check_target_match() self.apps = sorted( list(map(self.appmgr.get, self.appnames)), key=lambda app: app.appid, @@ -175,28 +189,32 @@ def __init__(self, appmgr: AppManager, appnames: List[str], hw_target: str): def _is_missing_dep(self, dep_name: str): return dep_name not in self.appnames - def _filter_by_target(self): - for appname in self.appnames.copy(): - app = self.appmgr.get(appname) - # if app.apptype not in self.BUILTIN_APP_TYPES: - if not any(map(lambda t: t in app.targets, ["all", self.hw_target])): - print( - f"Removing {appname} due to target mismatch (building for {self.hw_target}, app supports {app.targets}" - ) - self.appnames.remove(appname) + def _check_if_app_target_supported(self, app_name: str): + return self.appmgr.get(app_name).supports_hardware_target(self.hw_target) + + def _get_app_depends(self, app_name: str) -> List[str]: + # Skip app if its target is not supported by the target we are building for + if not self._check_if_app_target_supported(app_name): + self._writer( + f"Skipping {app_name} due to target mismatch (building for {self.hw_target}, app supports {app_def.targets}" + ) + return [] + + app_def = self.appmgr.get(app_name) + return list( + filter( + self._check_if_app_target_supported, + filter(self._is_missing_dep, app_def.provides + app_def.requires), + ) + ) def _process_deps(self): while True: provided = [] - for app in self.appnames: - # print(app) - provided.extend( - filter( - self._is_missing_dep, - self.appmgr.get(app).provides + self.appmgr.get(app).requires, - ) - ) - # print("provides round", provided) + for app_name in self.appnames: + provided.extend(self._get_app_depends(app_name)) + + # print("provides round: ", provided) if len(provided) == 0: break self.appnames.update(provided) @@ -204,7 +222,6 @@ def _process_deps(self): def _check_conflicts(self): conflicts = [] for app in self.appnames: - # print(app) if conflict_app_name := list( filter( lambda dep_name: dep_name in self.appnames, @@ -231,6 +248,17 @@ def _check_unsatisfied(self): f"Unsatisfied dependencies for {', '.join(f'{missing_dep[0]}: {missing_dep[1]}' for missing_dep in unsatisfied)}" ) + def _check_target_match(self): + incompatible = [] + for app in self.appnames: + if not self.appmgr.get(app).supports_hardware_target(self.hw_target): + incompatible.append(app) + + if len(incompatible): + raise AppBuilderException( + f"Apps incompatible with target {self.hw_target}: {', '.join(incompatible)}" + ) + def get_apps_cdefs(self): cdefs = set() for app in self.apps: diff --git a/scripts/fbt_tools/fbt_apps.py b/scripts/fbt_tools/fbt_apps.py index 96528f4e57b..9dbe307203c 100644 --- a/scripts/fbt_tools/fbt_apps.py +++ b/scripts/fbt_tools/fbt_apps.py @@ -1,5 +1,6 @@ from SCons.Builder import Builder from SCons.Action import Action +from SCons.Errors import StopError from SCons.Warnings import warn, WarningOnByDefault from ansi.color import fg @@ -32,9 +33,13 @@ def LoadAppManifest(env, entry): def PrepareApplicationsBuild(env): - appbuild = env["APPBUILD"] = env["APPMGR"].filter_apps( - env["APPS"], env.subst("f${TARGET_HW}") - ) + try: + appbuild = env["APPBUILD"] = env["APPMGR"].filter_apps( + env["APPS"], env.subst("f${TARGET_HW}") + ) + except Exception as e: + raise StopError(e) + env.Append( SDK_HEADERS=appbuild.get_sdk_headers(), ) diff --git a/scripts/fbt_tools/fbt_hwtarget.py b/scripts/fbt_tools/fbt_hwtarget.py new file mode 100644 index 00000000000..b4e1e58ac1f --- /dev/null +++ b/scripts/fbt_tools/fbt_hwtarget.py @@ -0,0 +1,134 @@ +from SCons.Builder import Builder +from SCons.Action import Action +import json + + +class HardwareTargetLoader: + def __init__(self, env, target_scons_dir, target_id): + self.env = env + self.target_scons_dir = target_scons_dir + self.target_dir = self._getTargetDir(target_id) + # self.target_id = target_id + self.layered_target_dirs = [] + + self.include_paths = [] + self.sdk_header_paths = [] + self.startup_script = None + self.linker_script_flash = None + self.linker_script_ram = None + self.linker_script_app = None + self.sdk_symbols = None + self.linker_dependencies = [] + self.excluded_sources = [] + self.excluded_headers = [] + self.excluded_modules = [] + self._processTargetDefinitions(target_id) + + def _getTargetDir(self, target_id): + return self.target_scons_dir.Dir(f"f{target_id}") + + def _loadDescription(self, target_id): + target_json_file = self._getTargetDir(target_id).File("target.json") + if not target_json_file.exists(): + raise Exception(f"Target file {target_json_file} does not exist") + with open(target_json_file.get_abspath(), "r") as f: + vals = json.load(f) + return vals + + def _processTargetDefinitions(self, target_id): + self.layered_target_dirs.append(f"targets/f{target_id}") + + config = self._loadDescription(target_id) + + for path_list in ("include_paths", "sdk_header_paths"): + getattr(self, path_list).extend( + f"#/firmware/targets/f{target_id}/{p}" + for p in config.get(path_list, []) + ) + + self.excluded_sources.extend(config.get("excluded_sources", [])) + self.excluded_headers.extend(config.get("excluded_headers", [])) + self.excluded_modules.extend(config.get("excluded_modules", [])) + + file_attrs = ( + # (name, use_src_node) + ("startup_script", False), + ("linker_script_flash", True), + ("linker_script_ram", True), + ("linker_script_app", True), + ("sdk_symbols", True), + ) + + for attr_name, use_src_node in file_attrs: + if (val := config.get(attr_name)) and not getattr(self, attr_name): + node = self.env.File(f"firmware/targets/f{target_id}/{val}") + if use_src_node: + node = node.srcnode() + setattr(self, attr_name, node) + + for attr_name in ("linker_dependencies",): + if (val := config.get(attr_name)) and not getattr(self, attr_name): + setattr(self, attr_name, val) + + if inherited_target := config.get("inherit", None): + self._processTargetDefinitions(inherited_target) + + def gatherSources(self): + sources = [self.startup_script] + seen_filenames = set(self.excluded_sources) + # print("Layers: ", self.layered_target_dirs) + for target_dir in self.layered_target_dirs: + accepted_sources = list( + filter( + lambda f: f.name not in seen_filenames, + self.env.GlobRecursive("*.c", target_dir), + ) + ) + seen_filenames.update(f.name for f in accepted_sources) + sources.extend(accepted_sources) + # print(f"Found {len(sources)} sources: {list(f.name for f in sources)}") + return sources + + def gatherSdkHeaders(self): + sdk_headers = [] + seen_sdk_headers = set(self.excluded_headers) + for sdk_path in self.sdk_header_paths: + # dirty, but fast - exclude headers from overlayed targets by name + # proper way would be to use relative paths, but names will do for now + for header in self.env.GlobRecursive("*.h", sdk_path, "*_i.h"): + if header.name not in seen_sdk_headers: + seen_sdk_headers.add(header.name) + sdk_headers.append(header) + return sdk_headers + + +def ConfigureForTarget(env, target_id): + target_loader = HardwareTargetLoader(env, env.Dir("#/firmware/targets"), target_id) + env.Replace( + TARGET_CFG=target_loader, + SDK_DEFINITION=target_loader.sdk_symbols, + SKIP_MODULES=target_loader.excluded_modules, + ) + + env.Append( + CPPPATH=target_loader.include_paths, + SDK_HEADERS=target_loader.gatherSdkHeaders(), + ) + + +def ApplyLibFlags(env): + flags_to_apply = env["FW_LIB_OPTS"].get( + env.get("FW_LIB_NAME"), + env["FW_LIB_OPTS"]["Default"], + ) + # print("Flags for ", env.get("FW_LIB_NAME", "Default"), flags_to_apply) + env.MergeFlags(flags_to_apply) + + +def generate(env): + env.AddMethod(ConfigureForTarget) + env.AddMethod(ApplyLibFlags) + + +def exists(env): + return True diff --git a/scripts/fbt_tools/sconsmodular.py b/scripts/fbt_tools/sconsmodular.py index b115706cbab..57ae8f055c3 100644 --- a/scripts/fbt_tools/sconsmodular.py +++ b/scripts/fbt_tools/sconsmodular.py @@ -22,6 +22,8 @@ def BuildModule(env, module): def BuildModules(env, modules): result = [] for module in modules: + if module in env.get("SKIP_MODULES", []): + continue build_res = env.BuildModule(module) # print("module ", module, build_res) if build_res is None: diff --git a/site_scons/commandline.scons b/site_scons/commandline.scons index e01c8a39edb..e3ddc59aaa7 100644 --- a/site_scons/commandline.scons +++ b/site_scons/commandline.scons @@ -79,6 +79,7 @@ vars.AddVariables( default="7", allowed_values=[ "7", + "18", ], ), ( @@ -197,7 +198,7 @@ vars.AddVariables( # "basic_plugins", # Debug # "debug_apps", - ) + ), }, ), ( diff --git a/site_scons/extapps.scons b/site_scons/extapps.scons index bff9a8c30f9..abe1a4534b9 100644 --- a/site_scons/extapps.scons +++ b/site_scons/extapps.scons @@ -16,7 +16,7 @@ appenv = ENV["APPENV"] = ENV.Clone( ) appenv.Replace( - LINKER_SCRIPT=appenv.subst("$APP_LINKER_SCRIPT"), + LINKER_SCRIPT_PATH=appenv["APP_LINKER_SCRIPT_PATH"], ) appenv.AppendUnique( @@ -78,17 +78,20 @@ known_extapps = [ if extra_app_list := GetOption("extra_ext_apps"): known_extapps.extend(map(appenv["APPMGR"].get, extra_app_list.split(","))) +incompatible_apps = [] for app in known_extapps: - if not any(map(lambda t: t in app.targets, ["all", appenv.subst("f${TARGET_HW}")])): - warn( - WarningOnByDefault, - f"Can't build '{app.name}' (id '{app.appid}'): target mismatch" - f" (building for {appenv.subst('f${TARGET_HW}')}, app supports {app.targets}", - ) + if not app.supports_hardware_target(appenv.subst("f${TARGET_HW}")): + incompatible_apps.append(app) continue appenv.BuildAppElf(app) +if incompatible_apps: + warn( + WarningOnByDefault, + f"Skipping build of {len(incompatible_apps)} incompatible app(s): " + + ", ".join(f"'{app.name}' (id '{app.appid}')" for app in incompatible_apps), + ) if appenv["FORCE"]: appenv.AlwaysBuild( diff --git a/site_scons/firmwareopts.scons b/site_scons/firmwareopts.scons index 9f707b4d8f0..e4cc8db5824 100644 --- a/site_scons/firmwareopts.scons +++ b/site_scons/firmwareopts.scons @@ -49,18 +49,15 @@ ENV.AppendUnique( ], ) -ENV.SetDefault( - LINKER_SCRIPT_PATH="firmware/targets/f${TARGET_HW}/${LINKER_SCRIPT}.ld", -) if ENV["FIRMWARE_BUILD_CFG"] == "updater": ENV.Append( IMAGE_BASE_ADDRESS="0x20000000", - LINKER_SCRIPT="stm32wb55xx_ram_fw", + LINKER_SCRIPT_PATH=ENV["TARGET_CFG"].linker_script_ram, ) else: ENV.Append( IMAGE_BASE_ADDRESS="0x8000000", - LINKER_SCRIPT="stm32wb55xx_flash", - APP_LINKER_SCRIPT="application_ext", + LINKER_SCRIPT_PATH=ENV["TARGET_CFG"].linker_script_flash, + APP_LINKER_SCRIPT_PATH=ENV["TARGET_CFG"].linker_script_app, ) From e3d473bf429bd254b53550ebcbbb0d389b49240c Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Wed, 8 Feb 2023 07:41:22 +0300 Subject: [PATCH 380/824] [FL-2435] SD over SPI improvements (#2204) * get rid of BSP layer * sector_cache: init in any case * new sd-spi driver: init * Delete stm32_adafruit_sd.c.old * Delete spi_sd_hal.c.old * Storage: faster api lock primitive * Threads: priority control * Flags: correct error code * Spi: dma mode * SD-card: use DMA for big blocks of SPI data * Fix wrong SD_TOKEN_START_DATA_MULTIPLE_BLOCK_WRITE value * do not memset cache if it is NULL * remove top-level timeouts * sd hal: disable debug * Furi HAL: DMA * Furi HAL RFID: use furi_hal_dma * Furi HAL DMA: tests * Furi HAL DMA: docs * rollback "Furi HAL RFID: use furi_hal_dma" * 4 channels taken from DMA manager for core HAL. * Furi HAL DMA: live fast, die young * RPC tests: increase message buffer * SPI HAL: use second DMA instance * sd hal: new CID getter * SPI hal: use non-DMA version if kernel is not running * IR hal: generalize DMA definition * storage: add CID data to sd info * RFID hal: generalize DMA definition * SUBGHZ hal: generalize DMA definition. Core hal moved to DMA2. * Storage: small optimizations, removal of extra mutex * Storage: redundant macro * SD hal: more timeouts * SPI hal: DMA init * Target: make furi_hal_spi_dma_init symbol private * Update F18 api symbols Co-authored-by: Aleksandr Kutuzov --- applications/debug/unit_tests/rpc/rpc_test.c | 2 +- applications/services/storage/storage_cli.c | 21 +- .../services/storage/storage_external_api.c | 9 +- applications/services/storage/storage_glue.c | 18 +- applications/services/storage/storage_glue.h | 3 - .../services/storage/storage_message.h | 3 +- .../services/storage/storage_processing.c | 39 +- .../services/storage/storage_sd_api.h | 10 + .../services/storage/storages/storage_ext.c | 33 +- .../scenes/storage_settings_scene_sd_info.c | 21 +- firmware/targets/f18/api_symbols.csv | 6 +- firmware/targets/f18/furi_hal/furi_hal.c | 1 + firmware/targets/f7/api_symbols.csv | 6 +- firmware/targets/f7/fatfs/sd_spi_io.c | 877 +++++++++++++ firmware/targets/f7/fatfs/sd_spi_io.h | 157 +++ firmware/targets/f7/fatfs/sector_cache.c | 5 - firmware/targets/f7/fatfs/spi_sd_hal.c | 98 -- firmware/targets/f7/fatfs/stm32_adafruit_sd.c | 1113 ----------------- firmware/targets/f7/fatfs/stm32_adafruit_sd.h | 245 ---- firmware/targets/f7/fatfs/user_diskio.c | 36 +- firmware/targets/f7/fatfs/user_diskio.h | 2 +- firmware/targets/f7/furi_hal/furi_hal.c | 1 + .../targets/f7/furi_hal/furi_hal_infrared.c | 121 +- firmware/targets/f7/furi_hal/furi_hal_rfid.c | 46 +- firmware/targets/f7/furi_hal/furi_hal_spi.c | 231 +++- .../targets/f7/furi_hal/furi_hal_subghz.c | 49 +- firmware/targets/f7/src/update.c | 4 +- .../targets/furi_hal_include/furi_hal_spi.h | 20 + furi/core/event_flag.c | 2 +- furi/core/thread.c | 9 + furi/core/thread.h | 12 + 31 files changed, 1539 insertions(+), 1661 deletions(-) create mode 100644 firmware/targets/f7/fatfs/sd_spi_io.c create mode 100644 firmware/targets/f7/fatfs/sd_spi_io.h delete mode 100644 firmware/targets/f7/fatfs/spi_sd_hal.c delete mode 100644 firmware/targets/f7/fatfs/stm32_adafruit_sd.c delete mode 100644 firmware/targets/f7/fatfs/stm32_adafruit_sd.h diff --git a/applications/debug/unit_tests/rpc/rpc_test.c b/applications/debug/unit_tests/rpc/rpc_test.c index 5b52df2fa39..76acf6be98f 100644 --- a/applications/debug/unit_tests/rpc/rpc_test.c +++ b/applications/debug/unit_tests/rpc/rpc_test.c @@ -89,7 +89,7 @@ static void test_rpc_setup(void) { } furi_check(rpc_session[0].session); - rpc_session[0].output_stream = furi_stream_buffer_alloc(1000, 1); + rpc_session[0].output_stream = furi_stream_buffer_alloc(4096, 1); rpc_session_set_send_bytes_callback(rpc_session[0].session, output_bytes_callback); rpc_session[0].close_session_semaphore = xSemaphoreCreateBinary(); rpc_session[0].terminate_semaphore = xSemaphoreCreateBinary(); diff --git a/applications/services/storage/storage_cli.c b/applications/services/storage/storage_cli.c index eeaa7fce8fa..ff205569772 100644 --- a/applications/services/storage/storage_cli.c +++ b/applications/services/storage/storage_cli.c @@ -1,6 +1,5 @@ #include #include -#include #include #include @@ -61,28 +60,26 @@ static void storage_cli_info(Cli* cli, FuriString* path) { } } else if(furi_string_cmp_str(path, STORAGE_EXT_PATH_PREFIX) == 0) { SDInfo sd_info; - SD_CID sd_cid; FS_Error error = storage_sd_info(api, &sd_info); - BSP_SD_GetCIDRegister(&sd_cid); if(error != FSE_OK) { storage_cli_print_error(error); } else { printf( "Label: %s\r\nType: %s\r\n%luKiB total\r\n%luKiB free\r\n" - "%02x%2.2s %5.5s %i.%i\r\nSN:%04lx %02i/%i\r\n", + "%02x%s %s v%i.%i\r\nSN:%04lx %02i/%i\r\n", sd_info.label, sd_api_get_fs_type_text(sd_info.fs_type), sd_info.kb_total, sd_info.kb_free, - sd_cid.ManufacturerID, - sd_cid.OEM_AppliID, - sd_cid.ProdName, - sd_cid.ProdRev >> 4, - sd_cid.ProdRev & 0xf, - sd_cid.ProdSN, - sd_cid.ManufactMonth, - sd_cid.ManufactYear + 2000); + sd_info.manufacturer_id, + sd_info.oem_id, + sd_info.product_name, + sd_info.product_revision_major, + sd_info.product_revision_minor, + sd_info.product_serial_number, + sd_info.manufacturing_month, + sd_info.manufacturing_year); } } else { storage_cli_print_usage(); diff --git a/applications/services/storage/storage_external_api.c b/applications/services/storage/storage_external_api.c index 6929a9cbdf6..c5dfd533eec 100644 --- a/applications/services/storage/storage_external_api.c +++ b/applications/services/storage/storage_external_api.c @@ -12,9 +12,7 @@ #define TAG "StorageAPI" -#define S_API_PROLOGUE \ - FuriSemaphore* semaphore = furi_semaphore_alloc(1, 0); \ - furi_check(semaphore != NULL); +#define S_API_PROLOGUE FuriApiLock lock = api_lock_alloc_locked(); #define S_FILE_API_PROLOGUE \ Storage* storage = file->storage; \ @@ -24,13 +22,12 @@ furi_check( \ furi_message_queue_put(storage->message_queue, &message, FuriWaitForever) == \ FuriStatusOk); \ - furi_semaphore_acquire(semaphore, FuriWaitForever); \ - furi_semaphore_free(semaphore); + api_lock_wait_unlock_and_free(lock) #define S_API_MESSAGE(_command) \ SAReturn return_data; \ StorageMessage message = { \ - .semaphore = semaphore, \ + .lock = lock, \ .command = _command, \ .data = &data, \ .return_data = &return_data, \ diff --git a/applications/services/storage/storage_glue.c b/applications/services/storage/storage_glue.c index 22f2e3dfa77..cccf4046adb 100644 --- a/applications/services/storage/storage_glue.c +++ b/applications/services/storage/storage_glue.c @@ -31,29 +31,13 @@ void storage_file_clear(StorageFile* obj) { /****************** storage data ******************/ void storage_data_init(StorageData* storage) { - storage->mutex = furi_mutex_alloc(FuriMutexTypeNormal); - furi_check(storage->mutex != NULL); storage->data = NULL; storage->status = StorageStatusNotReady; StorageFileList_init(storage->files); } -bool storage_data_lock(StorageData* storage) { - return (furi_mutex_acquire(storage->mutex, FuriWaitForever) == FuriStatusOk); -} - -bool storage_data_unlock(StorageData* storage) { - return (furi_mutex_release(storage->mutex) == FuriStatusOk); -} - StorageStatus storage_data_status(StorageData* storage) { - StorageStatus status; - - storage_data_lock(storage); - status = storage->status; - storage_data_unlock(storage); - - return status; + return storage->status; } const char* storage_data_status_text(StorageData* storage) { diff --git a/applications/services/storage/storage_glue.h b/applications/services/storage/storage_glue.h index 6fdc700997c..501c26abccc 100644 --- a/applications/services/storage/storage_glue.h +++ b/applications/services/storage/storage_glue.h @@ -38,8 +38,6 @@ void storage_file_set(StorageFile* obj, const StorageFile* src); void storage_file_clear(StorageFile* obj); void storage_data_init(StorageData* storage); -bool storage_data_lock(StorageData* storage); -bool storage_data_unlock(StorageData* storage); StorageStatus storage_data_status(StorageData* storage); const char* storage_data_status_text(StorageData* storage); void storage_data_timestamp(StorageData* storage); @@ -57,7 +55,6 @@ struct StorageData { const FS_Api* fs_api; StorageApi api; void* data; - FuriMutex* mutex; StorageStatus status; StorageFileList_t files; uint32_t timestamp; diff --git a/applications/services/storage/storage_message.h b/applications/services/storage/storage_message.h index 98726801792..3edb1018e10 100644 --- a/applications/services/storage/storage_message.h +++ b/applications/services/storage/storage_message.h @@ -1,5 +1,6 @@ #pragma once #include +#include #ifdef __cplusplus extern "C" { @@ -130,7 +131,7 @@ typedef enum { } StorageCommand; typedef struct { - FuriSemaphore* semaphore; + FuriApiLock lock; StorageCommand command; SAData* data; SAReturn* return_data; diff --git a/applications/services/storage/storage_processing.c b/applications/services/storage/storage_processing.c index 795a5d11c8e..b1ea5d29b72 100644 --- a/applications/services/storage/storage_processing.c +++ b/applications/services/storage/storage_processing.c @@ -2,15 +2,7 @@ #include #include -#define FS_CALL(_storage, _fn) \ - storage_data_lock(_storage); \ - ret = _storage->fs_api->_fn; \ - storage_data_unlock(_storage); - -#define ST_CALL(_storage, _fn) \ - storage_data_lock(_storage); \ - ret = _storage->api._fn; \ - storage_data_unlock(_storage); +#define FS_CALL(_storage, _fn) ret = _storage->fs_api->_fn; static StorageData* storage_get_storage_by_type(Storage* app, StorageType type) { furi_check(type == ST_EXT || type == ST_INT); @@ -44,16 +36,11 @@ static const char* remove_vfs(const char* path) { static StorageType storage_get_type_by_path(Storage* app, const char* path) { StorageType type = ST_ERROR; - if(strlen(path) >= strlen(STORAGE_EXT_PATH_PREFIX) && - memcmp(path, STORAGE_EXT_PATH_PREFIX, strlen(STORAGE_EXT_PATH_PREFIX)) == 0) { + if(memcmp(path, STORAGE_EXT_PATH_PREFIX, strlen(STORAGE_EXT_PATH_PREFIX)) == 0) { type = ST_EXT; - } else if( - strlen(path) >= strlen(STORAGE_INT_PATH_PREFIX) && - memcmp(path, STORAGE_INT_PATH_PREFIX, strlen(STORAGE_INT_PATH_PREFIX)) == 0) { + } else if(memcmp(path, STORAGE_INT_PATH_PREFIX, strlen(STORAGE_INT_PATH_PREFIX)) == 0) { type = ST_INT; - } else if( - strlen(path) >= strlen(STORAGE_ANY_PATH_PREFIX) && - memcmp(path, STORAGE_ANY_PATH_PREFIX, strlen(STORAGE_ANY_PATH_PREFIX)) == 0) { + } else if(memcmp(path, STORAGE_ANY_PATH_PREFIX, strlen(STORAGE_ANY_PATH_PREFIX)) == 0) { type = ST_ANY; } @@ -68,21 +55,15 @@ static StorageType storage_get_type_by_path(Storage* app, const char* path) { } static void storage_path_change_to_real_storage(FuriString* path, StorageType real_storage) { - if(memcmp( - furi_string_get_cstr(path), STORAGE_ANY_PATH_PREFIX, strlen(STORAGE_ANY_PATH_PREFIX)) == - 0) { + if(furi_string_search(path, STORAGE_ANY_PATH_PREFIX) == 0) { switch(real_storage) { case ST_EXT: - furi_string_set_char(path, 0, STORAGE_EXT_PATH_PREFIX[0]); - furi_string_set_char(path, 1, STORAGE_EXT_PATH_PREFIX[1]); - furi_string_set_char(path, 2, STORAGE_EXT_PATH_PREFIX[2]); - furi_string_set_char(path, 3, STORAGE_EXT_PATH_PREFIX[3]); + furi_string_replace_at( + path, 0, strlen(STORAGE_EXT_PATH_PREFIX), STORAGE_EXT_PATH_PREFIX); break; case ST_INT: - furi_string_set_char(path, 0, STORAGE_INT_PATH_PREFIX[0]); - furi_string_set_char(path, 1, STORAGE_INT_PATH_PREFIX[1]); - furi_string_set_char(path, 2, STORAGE_INT_PATH_PREFIX[2]); - furi_string_set_char(path, 3, STORAGE_INT_PATH_PREFIX[3]); + furi_string_replace_at( + path, 0, strlen(STORAGE_INT_PATH_PREFIX), STORAGE_INT_PATH_PREFIX); break; default: break; @@ -604,7 +585,7 @@ void storage_process_message_internal(Storage* app, StorageMessage* message) { break; } - furi_semaphore_release(message->semaphore); + api_lock_unlock(message->lock); } void storage_process_message(Storage* app, StorageMessage* message) { diff --git a/applications/services/storage/storage_sd_api.h b/applications/services/storage/storage_sd_api.h index f83360955e9..88064039456 100644 --- a/applications/services/storage/storage_sd_api.h +++ b/applications/services/storage/storage_sd_api.h @@ -23,6 +23,16 @@ typedef struct { uint16_t cluster_size; uint16_t sector_size; char label[SD_LABEL_LENGTH]; + + uint8_t manufacturer_id; + char oem_id[3]; + char product_name[6]; + uint8_t product_revision_major; + uint8_t product_revision_minor; + uint32_t product_serial_number; + uint8_t manufacturing_month; + uint16_t manufacturing_year; + FS_Error error; } SDInfo; diff --git a/applications/services/storage/storages/storage_ext.c b/applications/services/storage/storages/storage_ext.c index 0c81a0006c2..530c88f851f 100644 --- a/applications/services/storage/storages/storage_ext.c +++ b/applications/services/storage/storages/storage_ext.c @@ -26,12 +26,10 @@ static FS_Error storage_ext_parse_error(SDError error); static bool sd_mount_card(StorageData* storage, bool notify) { bool result = false; - uint8_t counter = BSP_SD_MaxMountRetryCount(); + uint8_t counter = sd_max_mount_retry_count(); uint8_t bsp_result; SDData* sd_data = storage->data; - storage_data_lock(storage); - while(result == false && counter > 0 && hal_sd_detect()) { if(notify) { NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); @@ -41,9 +39,9 @@ static bool sd_mount_card(StorageData* storage, bool notify) { if((counter % 2) == 0) { // power reset sd card - bsp_result = BSP_SD_Init(true); + bsp_result = sd_init(true); } else { - bsp_result = BSP_SD_Init(false); + bsp_result = sd_init(false); } if(bsp_result) { @@ -91,7 +89,6 @@ static bool sd_mount_card(StorageData* storage, bool notify) { } storage_data_timestamp(storage); - storage_data_unlock(storage); return result; } @@ -100,14 +97,12 @@ FS_Error sd_unmount_card(StorageData* storage) { SDData* sd_data = storage->data; SDError error; - storage_data_lock(storage); storage->status = StorageStatusNotReady; error = FR_DISK_ERR; // TODO do i need to close the files? - f_mount(0, sd_data->path, 0); - storage_data_unlock(storage); + return storage_ext_parse_error(error); } @@ -120,8 +115,6 @@ FS_Error sd_format_card(StorageData* storage) { SDData* sd_data = storage->data; SDError error; - storage_data_lock(storage); - work_area = malloc(_MAX_SS); error = f_mkfs(sd_data->path, FM_ANY, 0, work_area, _MAX_SS); free(work_area); @@ -138,8 +131,6 @@ FS_Error sd_format_card(StorageData* storage) { storage->status = StorageStatusOK; } while(false); - storage_data_unlock(storage); - return storage_ext_parse_error(error); #endif } @@ -156,14 +147,12 @@ FS_Error sd_card_info(StorageData* storage, SDInfo* sd_info) { memset(sd_info, 0, sizeof(SDInfo)); // get fs info - storage_data_lock(storage); error = f_getlabel(sd_data->path, sd_info->label, NULL); if(error == FR_OK) { #ifndef FURI_RAM_EXEC error = f_getfree(sd_data->path, &free_clusters, &fs); #endif } - storage_data_unlock(storage); if(error == FR_OK) { // calculate size @@ -210,6 +199,20 @@ FS_Error sd_card_info(StorageData* storage, SDInfo* sd_info) { #endif } + SD_CID cid; + SdSpiStatus status = sd_get_cid(&cid); + + if(status == SdSpiStatusOK) { + sd_info->manufacturer_id = cid.ManufacturerID; + memcpy(sd_info->oem_id, cid.OEM_AppliID, sizeof(cid.OEM_AppliID)); + memcpy(sd_info->product_name, cid.ProdName, sizeof(cid.ProdName)); + sd_info->product_revision_major = cid.ProdRev >> 4; + sd_info->product_revision_minor = cid.ProdRev & 0x0F; + sd_info->product_serial_number = cid.ProdSN; + sd_info->manufacturing_year = 2000 + cid.ManufactYear; + sd_info->manufacturing_month = cid.ManufactMonth; + } + return storage_ext_parse_error(error); } diff --git a/applications/settings/storage_settings/scenes/storage_settings_scene_sd_info.c b/applications/settings/storage_settings/scenes/storage_settings_scene_sd_info.c index a7991bc193a..81c786d0cb0 100644 --- a/applications/settings/storage_settings/scenes/storage_settings_scene_sd_info.c +++ b/applications/settings/storage_settings/scenes/storage_settings_scene_sd_info.c @@ -1,5 +1,4 @@ #include "../storage_settings.h" -#include static void storage_settings_scene_sd_info_dialog_callback(DialogExResult result, void* context) { StorageSettings* app = context; @@ -12,9 +11,7 @@ void storage_settings_scene_sd_info_on_enter(void* context) { DialogEx* dialog_ex = app->dialog_ex; SDInfo sd_info; - SD_CID sd_cid; FS_Error sd_status = storage_sd_info(app->fs_api, &sd_info); - BSP_SD_GetCIDRegister(&sd_cid); scene_manager_set_scene_state(app->scene_manager, StorageSettingsSDInfo, sd_status); @@ -31,19 +28,19 @@ void storage_settings_scene_sd_info_on_enter(void* context) { furi_string_printf( app->text_string, "Label: %s\nType: %s\n%lu KiB total\n%lu KiB free\n" - "%02X%2.2s %5.5s %i.%i\nSN:%04lX %02i/%i", + "%02X%s %s v%i.%i\nSN:%04lX %02i/%i", sd_info.label, sd_api_get_fs_type_text(sd_info.fs_type), sd_info.kb_total, sd_info.kb_free, - sd_cid.ManufacturerID, - sd_cid.OEM_AppliID, - sd_cid.ProdName, - sd_cid.ProdRev >> 4, - sd_cid.ProdRev & 0xf, - sd_cid.ProdSN, - sd_cid.ManufactMonth, - sd_cid.ManufactYear + 2000); + sd_info.manufacturer_id, + sd_info.oem_id, + sd_info.product_name, + sd_info.product_revision_major, + sd_info.product_revision_minor, + sd_info.product_serial_number, + sd_info.manufacturing_month, + sd_info.manufacturing_year); dialog_ex_set_text( dialog_ex, furi_string_get_cstr(app->text_string), 4, 1, AlignLeft, AlignTop); } diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index e4a7dfdd5ce..ff6dbc2391a 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,12.1,, +Version,+,12.2,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -1062,10 +1062,12 @@ Function,+,furi_hal_spi_bus_handle_init,void,FuriHalSpiBusHandle* Function,+,furi_hal_spi_bus_init,void,FuriHalSpiBus* Function,+,furi_hal_spi_bus_rx,_Bool,"FuriHalSpiBusHandle*, uint8_t*, size_t, uint32_t" Function,+,furi_hal_spi_bus_trx,_Bool,"FuriHalSpiBusHandle*, uint8_t*, uint8_t*, size_t, uint32_t" +Function,+,furi_hal_spi_bus_trx_dma,_Bool,"FuriHalSpiBusHandle*, uint8_t*, uint8_t*, size_t, uint32_t" Function,+,furi_hal_spi_bus_tx,_Bool,"FuriHalSpiBusHandle*, uint8_t*, size_t, uint32_t" Function,-,furi_hal_spi_config_deinit_early,void, Function,-,furi_hal_spi_config_init,void, Function,-,furi_hal_spi_config_init_early,void, +Function,-,furi_hal_spi_dma_init,void, Function,+,furi_hal_spi_release,void,FuriHalSpiBusHandle* Function,+,furi_hal_switch,void,void* Function,+,furi_hal_uart_deinit,void,FuriHalUartId @@ -1231,6 +1233,7 @@ Function,+,furi_thread_flags_wait,uint32_t,"uint32_t, uint32_t, uint32_t" Function,+,furi_thread_free,void,FuriThread* Function,+,furi_thread_get_current,FuriThread*, Function,+,furi_thread_get_current_id,FuriThreadId, +Function,+,furi_thread_get_current_priority,FuriThreadPriority, Function,+,furi_thread_get_heap_size,size_t,FuriThread* Function,+,furi_thread_get_id,FuriThreadId,FuriThread* Function,+,furi_thread_get_name,const char*,FuriThreadId @@ -1244,6 +1247,7 @@ Function,+,furi_thread_mark_as_service,void,FuriThread* Function,+,furi_thread_resume,void,FuriThreadId Function,+,furi_thread_set_callback,void,"FuriThread*, FuriThreadCallback" Function,+,furi_thread_set_context,void,"FuriThread*, void*" +Function,+,furi_thread_set_current_priority,void,FuriThreadPriority Function,+,furi_thread_set_name,void,"FuriThread*, const char*" Function,+,furi_thread_set_priority,void,"FuriThread*, FuriThreadPriority" Function,+,furi_thread_set_stack_size,void,"FuriThread*, size_t" diff --git a/firmware/targets/f18/furi_hal/furi_hal.c b/firmware/targets/f18/furi_hal/furi_hal.c index ad35a0074ee..0a68fdb696f 100644 --- a/firmware/targets/f18/furi_hal/furi_hal.c +++ b/firmware/targets/f18/furi_hal/furi_hal.c @@ -51,6 +51,7 @@ void furi_hal_init() { furi_hal_version_init(); furi_hal_spi_config_init(); + furi_hal_spi_dma_init(); furi_hal_speaker_init(); FURI_LOG_I(TAG, "Speaker OK"); diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index c1a979859e5..a1f34f10608 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,12.1,, +Version,+,12.2,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -1325,10 +1325,12 @@ Function,+,furi_hal_spi_bus_handle_init,void,FuriHalSpiBusHandle* Function,+,furi_hal_spi_bus_init,void,FuriHalSpiBus* Function,+,furi_hal_spi_bus_rx,_Bool,"FuriHalSpiBusHandle*, uint8_t*, size_t, uint32_t" Function,+,furi_hal_spi_bus_trx,_Bool,"FuriHalSpiBusHandle*, uint8_t*, uint8_t*, size_t, uint32_t" +Function,+,furi_hal_spi_bus_trx_dma,_Bool,"FuriHalSpiBusHandle*, uint8_t*, uint8_t*, size_t, uint32_t" Function,+,furi_hal_spi_bus_tx,_Bool,"FuriHalSpiBusHandle*, uint8_t*, size_t, uint32_t" Function,-,furi_hal_spi_config_deinit_early,void, Function,-,furi_hal_spi_config_init,void, Function,-,furi_hal_spi_config_init_early,void, +Function,-,furi_hal_spi_dma_init,void, Function,+,furi_hal_spi_release,void,FuriHalSpiBusHandle* Function,-,furi_hal_subghz_dump_state,void, Function,+,furi_hal_subghz_flush_rx,void, @@ -1524,6 +1526,7 @@ Function,+,furi_thread_flags_wait,uint32_t,"uint32_t, uint32_t, uint32_t" Function,+,furi_thread_free,void,FuriThread* Function,+,furi_thread_get_current,FuriThread*, Function,+,furi_thread_get_current_id,FuriThreadId, +Function,+,furi_thread_get_current_priority,FuriThreadPriority, Function,+,furi_thread_get_heap_size,size_t,FuriThread* Function,+,furi_thread_get_id,FuriThreadId,FuriThread* Function,+,furi_thread_get_name,const char*,FuriThreadId @@ -1537,6 +1540,7 @@ Function,+,furi_thread_mark_as_service,void,FuriThread* Function,+,furi_thread_resume,void,FuriThreadId Function,+,furi_thread_set_callback,void,"FuriThread*, FuriThreadCallback" Function,+,furi_thread_set_context,void,"FuriThread*, void*" +Function,+,furi_thread_set_current_priority,void,FuriThreadPriority Function,+,furi_thread_set_name,void,"FuriThread*, const char*" Function,+,furi_thread_set_priority,void,"FuriThread*, FuriThreadPriority" Function,+,furi_thread_set_stack_size,void,"FuriThread*, size_t" diff --git a/firmware/targets/f7/fatfs/sd_spi_io.c b/firmware/targets/f7/fatfs/sd_spi_io.c new file mode 100644 index 00000000000..93b837e8520 --- /dev/null +++ b/firmware/targets/f7/fatfs/sd_spi_io.c @@ -0,0 +1,877 @@ +#include "sd_spi_io.h" +#include "sector_cache.h" +#include +#include +#include + +// #define SD_SPI_DEBUG 1 +#define TAG "SdSpi" + +#ifdef SD_SPI_DEBUG +#define sd_spi_debug(...) FURI_LOG_I(TAG, __VA_ARGS__) +#else +#define sd_spi_debug(...) +#endif + +#define SD_CMD_LENGTH 6 +#define SD_DUMMY_BYTE 0xFF +#define SD_ANSWER_RETRY_COUNT 8 +#define SD_IDLE_RETRY_COUNT 100 +#define SD_BLOCK_SIZE 512 + +#define FLAG_SET(x, y) (((x) & (y)) == (y)) + +static bool sd_high_capacity = false; + +typedef enum { + SdSpiDataResponceOK = 0x05, + SdSpiDataResponceCRCError = 0x0B, + SdSpiDataResponceWriteError = 0x0D, + SdSpiDataResponceOtherError = 0xFF, +} SdSpiDataResponce; + +typedef struct { + uint8_t r1; + uint8_t r2; + uint8_t r3; + uint8_t r4; + uint8_t r5; +} SdSpiCmdAnswer; + +typedef enum { + SdSpiCmdAnswerTypeR1, + SdSpiCmdAnswerTypeR1B, + SdSpiCmdAnswerTypeR2, + SdSpiCmdAnswerTypeR3, + SdSpiCmdAnswerTypeR4R5, + SdSpiCmdAnswerTypeR7, +} SdSpiCmdAnswerType; + +/* + SdSpiCmd and SdSpiToken use non-standard enum value names convention, + because it is more convenient to look for documentation on a specific command. + For example, to find out what the SD_CMD23_SET_BLOCK_COUNT command does, you need to look for + SET_BLOCK_COUNT or CMD23 in the "Part 1 Physical Layer Simplified Specification". + + Do not use that naming convention in other places. +*/ + +typedef enum { + SD_CMD0_GO_IDLE_STATE = 0, + SD_CMD1_SEND_OP_COND = 1, + SD_CMD8_SEND_IF_COND = 8, + SD_CMD9_SEND_CSD = 9, + SD_CMD10_SEND_CID = 10, + SD_CMD12_STOP_TRANSMISSION = 12, + SD_CMD13_SEND_STATUS = 13, + SD_CMD16_SET_BLOCKLEN = 16, + SD_CMD17_READ_SINGLE_BLOCK = 17, + SD_CMD18_READ_MULT_BLOCK = 18, + SD_CMD23_SET_BLOCK_COUNT = 23, + SD_CMD24_WRITE_SINGLE_BLOCK = 24, + SD_CMD25_WRITE_MULT_BLOCK = 25, + SD_CMD27_PROG_CSD = 27, + SD_CMD28_SET_WRITE_PROT = 28, + SD_CMD29_CLR_WRITE_PROT = 29, + SD_CMD30_SEND_WRITE_PROT = 30, + SD_CMD32_SD_ERASE_GRP_START = 32, + SD_CMD33_SD_ERASE_GRP_END = 33, + SD_CMD34_UNTAG_SECTOR = 34, + SD_CMD35_ERASE_GRP_START = 35, + SD_CMD36_ERASE_GRP_END = 36, + SD_CMD37_UNTAG_ERASE_GROUP = 37, + SD_CMD38_ERASE = 38, + SD_CMD41_SD_APP_OP_COND = 41, + SD_CMD55_APP_CMD = 55, + SD_CMD58_READ_OCR = 58, +} SdSpiCmd; + +/** Data tokens */ +typedef enum { + SD_TOKEN_START_DATA_SINGLE_BLOCK_READ = 0xFE, + SD_TOKEN_START_DATA_MULTIPLE_BLOCK_READ = 0xFE, + SD_TOKEN_START_DATA_SINGLE_BLOCK_WRITE = 0xFE, + SD_TOKEN_START_DATA_MULTIPLE_BLOCK_WRITE = 0xFC, + SD_TOKEN_STOP_DATA_MULTIPLE_BLOCK_WRITE = 0xFD, +} SdSpiToken; + +/** R1 answer value */ +typedef enum { + SdSpi_R1_NO_ERROR = 0x00, + SdSpi_R1_IN_IDLE_STATE = 0x01, + SdSpi_R1_ERASE_RESET = 0x02, + SdSpi_R1_ILLEGAL_COMMAND = 0x04, + SdSpi_R1_COM_CRC_ERROR = 0x08, + SdSpi_R1_ERASE_SEQUENCE_ERROR = 0x10, + SdSpi_R1_ADDRESS_ERROR = 0x20, + SdSpi_R1_PARAMETER_ERROR = 0x40, +} SdSpiR1; + +/** R2 answer value */ +typedef enum { + /* R2 answer value */ + SdSpi_R2_NO_ERROR = 0x00, + SdSpi_R2_CARD_LOCKED = 0x01, + SdSpi_R2_LOCKUNLOCK_ERROR = 0x02, + SdSpi_R2_ERROR = 0x04, + SdSpi_R2_CC_ERROR = 0x08, + SdSpi_R2_CARD_ECC_FAILED = 0x10, + SdSpi_R2_WP_VIOLATION = 0x20, + SdSpi_R2_ERASE_PARAM = 0x40, + SdSpi_R2_OUTOFRANGE = 0x80, +} SdSpiR2; + +static inline void sd_spi_select_card() { + furi_hal_gpio_write(furi_hal_sd_spi_handle->cs, false); + furi_delay_us(10); // Entry guard time for some SD cards +} + +static inline void sd_spi_deselect_card() { + furi_delay_us(10); // Exit guard time for some SD cards + furi_hal_gpio_write(furi_hal_sd_spi_handle->cs, true); +} + +static void sd_spi_bus_to_ground() { + furi_hal_gpio_init_ex( + furi_hal_sd_spi_handle->miso, + GpioModeOutputPushPull, + GpioPullNo, + GpioSpeedVeryHigh, + GpioAltFnUnused); + furi_hal_gpio_init_ex( + furi_hal_sd_spi_handle->mosi, + GpioModeOutputPushPull, + GpioPullNo, + GpioSpeedVeryHigh, + GpioAltFnUnused); + furi_hal_gpio_init_ex( + furi_hal_sd_spi_handle->sck, + GpioModeOutputPushPull, + GpioPullNo, + GpioSpeedVeryHigh, + GpioAltFnUnused); + + sd_spi_select_card(); + furi_hal_gpio_write(furi_hal_sd_spi_handle->miso, false); + furi_hal_gpio_write(furi_hal_sd_spi_handle->mosi, false); + furi_hal_gpio_write(furi_hal_sd_spi_handle->sck, false); +} + +static void sd_spi_bus_rise_up() { + sd_spi_deselect_card(); + + furi_hal_gpio_init_ex( + furi_hal_sd_spi_handle->miso, + GpioModeAltFunctionPushPull, + GpioPullUp, + GpioSpeedVeryHigh, + GpioAltFn5SPI2); + furi_hal_gpio_init_ex( + furi_hal_sd_spi_handle->mosi, + GpioModeAltFunctionPushPull, + GpioPullUp, + GpioSpeedVeryHigh, + GpioAltFn5SPI2); + furi_hal_gpio_init_ex( + furi_hal_sd_spi_handle->sck, + GpioModeAltFunctionPushPull, + GpioPullUp, + GpioSpeedVeryHigh, + GpioAltFn5SPI2); +} + +static inline uint8_t sd_spi_read_byte(void) { + uint8_t responce; + furi_check(furi_hal_spi_bus_trx(furi_hal_sd_spi_handle, NULL, &responce, 1, SD_TIMEOUT_MS)); + return responce; +} + +static inline void sd_spi_write_byte(uint8_t data) { + furi_check(furi_hal_spi_bus_trx(furi_hal_sd_spi_handle, &data, NULL, 1, SD_TIMEOUT_MS)); +} + +static inline uint8_t sd_spi_write_and_read_byte(uint8_t data) { + uint8_t responce; + furi_check(furi_hal_spi_bus_trx(furi_hal_sd_spi_handle, &data, &responce, 1, SD_TIMEOUT_MS)); + return responce; +} + +static inline void sd_spi_write_bytes(uint8_t* data, uint32_t size) { + furi_check(furi_hal_spi_bus_trx(furi_hal_sd_spi_handle, data, NULL, size, SD_TIMEOUT_MS)); +} + +static inline void sd_spi_read_bytes(uint8_t* data, uint32_t size) { + furi_check(furi_hal_spi_bus_trx(furi_hal_sd_spi_handle, NULL, data, size, SD_TIMEOUT_MS)); +} + +static inline void sd_spi_write_bytes_dma(uint8_t* data, uint32_t size) { + uint32_t timeout_mul = (size / 512) + 1; + furi_check(furi_hal_spi_bus_trx_dma( + furi_hal_sd_spi_handle, data, NULL, size, SD_TIMEOUT_MS * timeout_mul)); +} + +static inline void sd_spi_read_bytes_dma(uint8_t* data, uint32_t size) { + uint32_t timeout_mul = (size / 512) + 1; + furi_check(furi_hal_spi_bus_trx_dma( + furi_hal_sd_spi_handle, NULL, data, size, SD_TIMEOUT_MS * timeout_mul)); +} + +static uint8_t sd_spi_wait_for_data_and_read(void) { + uint8_t retry_count = SD_ANSWER_RETRY_COUNT; + uint8_t responce; + + // Wait until we get a valid data + do { + responce = sd_spi_read_byte(); + retry_count--; + + } while((responce == SD_DUMMY_BYTE) && retry_count); + + return responce; +} + +static SdSpiStatus sd_spi_wait_for_data(uint8_t data, uint32_t timeout_ms) { + FuriHalCortexTimer timer = furi_hal_cortex_timer_get(timeout_ms * 1000); + uint8_t byte; + + do { + byte = sd_spi_read_byte(); + if(furi_hal_cortex_timer_is_expired(timer)) { + return SdSpiStatusTimeout; + } + } while((byte != data)); + + return SdSpiStatusOK; +} + +static inline void sd_spi_deselect_card_and_purge() { + sd_spi_deselect_card(); + sd_spi_read_byte(); +} + +static inline void sd_spi_purge_crc() { + sd_spi_read_byte(); + sd_spi_read_byte(); +} + +static SdSpiCmdAnswer + sd_spi_send_cmd(SdSpiCmd cmd, uint32_t arg, uint8_t crc, SdSpiCmdAnswerType answer_type) { + uint8_t frame[SD_CMD_LENGTH]; + SdSpiCmdAnswer cmd_answer = { + .r1 = SD_DUMMY_BYTE, + .r2 = SD_DUMMY_BYTE, + .r3 = SD_DUMMY_BYTE, + .r4 = SD_DUMMY_BYTE, + .r5 = SD_DUMMY_BYTE, + }; + + // R1 Length = NCS(0)+ 6 Bytes command + NCR(min1 max8) + 1 Bytes answer + NEC(0) = 15bytes + // R1b identical to R1 + Busy information + // R2 Length = NCS(0)+ 6 Bytes command + NCR(min1 max8) + 2 Bytes answer + NEC(0) = 16bytes + + frame[0] = ((uint8_t)cmd | 0x40); + frame[1] = (uint8_t)(arg >> 24); + frame[2] = (uint8_t)(arg >> 16); + frame[3] = (uint8_t)(arg >> 8); + frame[4] = (uint8_t)(arg); + frame[5] = (crc | 0x01); + + sd_spi_select_card(); + sd_spi_write_bytes(frame, sizeof(frame)); + + switch(answer_type) { + case SdSpiCmdAnswerTypeR1: + cmd_answer.r1 = sd_spi_wait_for_data_and_read(); + break; + case SdSpiCmdAnswerTypeR1B: + // TODO: can be wrong, at least for SD_CMD12_STOP_TRANSMISSION you need to purge one byte before reading R1 + cmd_answer.r1 = sd_spi_wait_for_data_and_read(); + + // In general this shenenigans seems suspicious, please double check SD specs if you are using SdSpiCmdAnswerTypeR1B + // reassert card + sd_spi_deselect_card(); + furi_delay_us(1000); + sd_spi_deselect_card(); + + // and wait for it to be ready + while(sd_spi_read_byte() != 0xFF) { + }; + + break; + case SdSpiCmdAnswerTypeR2: + cmd_answer.r1 = sd_spi_wait_for_data_and_read(); + cmd_answer.r2 = sd_spi_read_byte(); + break; + case SdSpiCmdAnswerTypeR3: + case SdSpiCmdAnswerTypeR7: + cmd_answer.r1 = sd_spi_wait_for_data_and_read(); + cmd_answer.r2 = sd_spi_read_byte(); + cmd_answer.r3 = sd_spi_read_byte(); + cmd_answer.r4 = sd_spi_read_byte(); + cmd_answer.r5 = sd_spi_read_byte(); + break; + default: + break; + } + return cmd_answer; +} + +static SdSpiDataResponce sd_spi_get_data_response(uint32_t timeout_ms) { + SdSpiDataResponce responce = sd_spi_read_byte(); + // read busy response byte + sd_spi_read_byte(); + + switch(responce & 0x1F) { + case SdSpiDataResponceOK: + // TODO: check timings + sd_spi_deselect_card(); + sd_spi_select_card(); + + // wait for 0xFF + if(sd_spi_wait_for_data(0xFF, timeout_ms) == SdSpiStatusOK) { + return SdSpiDataResponceOK; + } else { + return SdSpiDataResponceOtherError; + } + case SdSpiDataResponceCRCError: + return SdSpiDataResponceCRCError; + case SdSpiDataResponceWriteError: + return SdSpiDataResponceWriteError; + default: + return SdSpiDataResponceOtherError; + } +} + +static SdSpiStatus sd_spi_init_spi_mode_v1(void) { + SdSpiCmdAnswer response; + uint8_t retry_count = 0; + + sd_spi_debug("Init SD card in SPI mode v1"); + + do { + retry_count++; + + // CMD55 (APP_CMD) before any ACMD command: R1 response (0x00: no errors) + sd_spi_send_cmd(SD_CMD55_APP_CMD, 0, 0xFF, SdSpiCmdAnswerTypeR1); + sd_spi_deselect_card_and_purge(); + + // ACMD41 (SD_APP_OP_COND) to initialize SDHC or SDXC cards: R1 response (0x00: no errors) + response = sd_spi_send_cmd(SD_CMD41_SD_APP_OP_COND, 0, 0xFF, SdSpiCmdAnswerTypeR1); + sd_spi_deselect_card_and_purge(); + + if(retry_count >= SD_IDLE_RETRY_COUNT) { + return SdSpiStatusError; + } + } while(response.r1 == SdSpi_R1_IN_IDLE_STATE); + + sd_spi_debug("Init SD card in SPI mode v1 done"); + + return SdSpiStatusOK; +} + +static SdSpiStatus sd_spi_init_spi_mode_v2(void) { + SdSpiCmdAnswer response; + uint8_t retry_count = 0; + + sd_spi_debug("Init SD card in SPI mode v2"); + + do { + retry_count++; + // CMD55 (APP_CMD) before any ACMD command: R1 response (0x00: no errors) + sd_spi_send_cmd(SD_CMD55_APP_CMD, 0, 0xFF, SdSpiCmdAnswerTypeR1); + sd_spi_deselect_card_and_purge(); + + // ACMD41 (APP_OP_COND) to initialize SDHC or SDXC cards: R1 response (0x00: no errors) + response = + sd_spi_send_cmd(SD_CMD41_SD_APP_OP_COND, 0x40000000, 0xFF, SdSpiCmdAnswerTypeR1); + sd_spi_deselect_card_and_purge(); + + if(retry_count >= SD_IDLE_RETRY_COUNT) { + sd_spi_debug("ACMD41 failed"); + return SdSpiStatusError; + } + } while(response.r1 == SdSpi_R1_IN_IDLE_STATE); + + if(FLAG_SET(response.r1, SdSpi_R1_ILLEGAL_COMMAND)) { + sd_spi_debug("ACMD41 is illegal command"); + retry_count = 0; + do { + retry_count++; + // CMD55 (APP_CMD) before any ACMD command: R1 response (0x00: no errors) + response = sd_spi_send_cmd(SD_CMD55_APP_CMD, 0, 0xFF, SdSpiCmdAnswerTypeR1); + sd_spi_deselect_card_and_purge(); + + if(response.r1 != SdSpi_R1_IN_IDLE_STATE) { + sd_spi_debug("CMD55 failed"); + return SdSpiStatusError; + } + // ACMD41 (SD_APP_OP_COND) to initialize SDHC or SDXC cards: R1 response (0x00: no errors) + response = sd_spi_send_cmd(SD_CMD41_SD_APP_OP_COND, 0, 0xFF, SdSpiCmdAnswerTypeR1); + sd_spi_deselect_card_and_purge(); + + if(retry_count >= SD_IDLE_RETRY_COUNT) { + sd_spi_debug("ACMD41 failed"); + return SdSpiStatusError; + } + } while(response.r1 == SdSpi_R1_IN_IDLE_STATE); + } + + sd_spi_debug("Init SD card in SPI mode v2 done"); + + return SdSpiStatusOK; +} + +static SdSpiStatus sd_spi_init_spi_mode(void) { + SdSpiCmdAnswer response; + uint8_t retry_count; + + // CMD0 (GO_IDLE_STATE) to put SD in SPI mode and + // wait for In Idle State Response (R1 Format) equal to 0x01 + retry_count = 0; + do { + retry_count++; + response = sd_spi_send_cmd(SD_CMD0_GO_IDLE_STATE, 0, 0x95, SdSpiCmdAnswerTypeR1); + sd_spi_deselect_card_and_purge(); + + if(retry_count >= SD_IDLE_RETRY_COUNT) { + sd_spi_debug("CMD0 failed"); + return SdSpiStatusError; + } + } while(response.r1 != SdSpi_R1_IN_IDLE_STATE); + + // CMD8 (SEND_IF_COND) to check the power supply status + // and wait until response (R7 Format) equal to 0xAA and + response = sd_spi_send_cmd(SD_CMD8_SEND_IF_COND, 0x1AA, 0x87, SdSpiCmdAnswerTypeR7); + sd_spi_deselect_card_and_purge(); + + if(FLAG_SET(response.r1, SdSpi_R1_ILLEGAL_COMMAND)) { + if(sd_spi_init_spi_mode_v1() != SdSpiStatusOK) { + sd_spi_debug("Init mode v1 failed"); + return SdSpiStatusError; + } + sd_high_capacity = 0; + } else if(response.r1 == SdSpi_R1_IN_IDLE_STATE) { + if(sd_spi_init_spi_mode_v2() != SdSpiStatusOK) { + sd_spi_debug("Init mode v2 failed"); + return SdSpiStatusError; + } + + // CMD58 (READ_OCR) to initialize SDHC or SDXC cards: R3 response + response = sd_spi_send_cmd(SD_CMD58_READ_OCR, 0, 0xFF, SdSpiCmdAnswerTypeR3); + sd_spi_deselect_card_and_purge(); + + if(response.r1 != SdSpi_R1_NO_ERROR) { + sd_spi_debug("CMD58 failed"); + return SdSpiStatusError; + } + sd_high_capacity = (response.r2 & 0x40) >> 6; + } else { + return SdSpiStatusError; + } + + sd_spi_debug("SD card is %s", sd_high_capacity ? "SDHC or SDXC" : "SDSC"); + return SdSpiStatusOK; +} + +static SdSpiStatus sd_spi_get_csd(SD_CSD* csd) { + uint16_t counter = 0; + uint8_t csd_data[16]; + SdSpiStatus ret = SdSpiStatusError; + SdSpiCmdAnswer response; + + // CMD9 (SEND_CSD): R1 format (0x00 is no errors) + response = sd_spi_send_cmd(SD_CMD9_SEND_CSD, 0, 0xFF, SdSpiCmdAnswerTypeR1); + + if(response.r1 == SdSpi_R1_NO_ERROR) { + if(sd_spi_wait_for_data(SD_TOKEN_START_DATA_SINGLE_BLOCK_READ, SD_TIMEOUT_MS) == + SdSpiStatusOK) { + // read CSD data + for(counter = 0; counter < 16; counter++) { + csd_data[counter] = sd_spi_read_byte(); + } + + sd_spi_purge_crc(); + + /************************************************************************* + CSD header decoding + *************************************************************************/ + + csd->CSDStruct = (csd_data[0] & 0xC0) >> 6; + csd->Reserved1 = csd_data[0] & 0x3F; + csd->TAAC = csd_data[1]; + csd->NSAC = csd_data[2]; + csd->MaxBusClkFrec = csd_data[3]; + csd->CardComdClasses = (csd_data[4] << 4) | ((csd_data[5] & 0xF0) >> 4); + csd->RdBlockLen = csd_data[5] & 0x0F; + csd->PartBlockRead = (csd_data[6] & 0x80) >> 7; + csd->WrBlockMisalign = (csd_data[6] & 0x40) >> 6; + csd->RdBlockMisalign = (csd_data[6] & 0x20) >> 5; + csd->DSRImpl = (csd_data[6] & 0x10) >> 4; + + /************************************************************************* + CSD v1/v2 decoding + *************************************************************************/ + + if(sd_high_capacity == 0) { + csd->version.v1.Reserved1 = ((csd_data[6] & 0x0C) >> 2); + csd->version.v1.DeviceSize = ((csd_data[6] & 0x03) << 10) | (csd_data[7] << 2) | + ((csd_data[8] & 0xC0) >> 6); + csd->version.v1.MaxRdCurrentVDDMin = (csd_data[8] & 0x38) >> 3; + csd->version.v1.MaxRdCurrentVDDMax = (csd_data[8] & 0x07); + csd->version.v1.MaxWrCurrentVDDMin = (csd_data[9] & 0xE0) >> 5; + csd->version.v1.MaxWrCurrentVDDMax = (csd_data[9] & 0x1C) >> 2; + csd->version.v1.DeviceSizeMul = ((csd_data[9] & 0x03) << 1) | + ((csd_data[10] & 0x80) >> 7); + } else { + csd->version.v2.Reserved1 = ((csd_data[6] & 0x0F) << 2) | + ((csd_data[7] & 0xC0) >> 6); + csd->version.v2.DeviceSize = ((csd_data[7] & 0x3F) << 16) | (csd_data[8] << 8) | + csd_data[9]; + csd->version.v2.Reserved2 = ((csd_data[10] & 0x80) >> 8); + } + + csd->EraseSingleBlockEnable = (csd_data[10] & 0x40) >> 6; + csd->EraseSectorSize = ((csd_data[10] & 0x3F) << 1) | ((csd_data[11] & 0x80) >> 7); + csd->WrProtectGrSize = (csd_data[11] & 0x7F); + csd->WrProtectGrEnable = (csd_data[12] & 0x80) >> 7; + csd->Reserved2 = (csd_data[12] & 0x60) >> 5; + csd->WrSpeedFact = (csd_data[12] & 0x1C) >> 2; + csd->MaxWrBlockLen = ((csd_data[12] & 0x03) << 2) | ((csd_data[13] & 0xC0) >> 6); + csd->WriteBlockPartial = (csd_data[13] & 0x20) >> 5; + csd->Reserved3 = (csd_data[13] & 0x1F); + csd->FileFormatGrouop = (csd_data[14] & 0x80) >> 7; + csd->CopyFlag = (csd_data[14] & 0x40) >> 6; + csd->PermWrProtect = (csd_data[14] & 0x20) >> 5; + csd->TempWrProtect = (csd_data[14] & 0x10) >> 4; + csd->FileFormat = (csd_data[14] & 0x0C) >> 2; + csd->Reserved4 = (csd_data[14] & 0x03); + csd->crc = (csd_data[15] & 0xFE) >> 1; + csd->Reserved5 = (csd_data[15] & 0x01); + + ret = SdSpiStatusOK; + } + } + + sd_spi_deselect_card_and_purge(); + + return ret; +} + +static SdSpiStatus sd_spi_get_cid(SD_CID* Cid) { + uint16_t counter = 0; + uint8_t cid_data[16]; + SdSpiStatus ret = SdSpiStatusError; + SdSpiCmdAnswer response; + + // CMD10 (SEND_CID): R1 format (0x00 is no errors) + response = sd_spi_send_cmd(SD_CMD10_SEND_CID, 0, 0xFF, SdSpiCmdAnswerTypeR1); + + if(response.r1 == SdSpi_R1_NO_ERROR) { + if(sd_spi_wait_for_data(SD_TOKEN_START_DATA_SINGLE_BLOCK_READ, SD_TIMEOUT_MS) == + SdSpiStatusOK) { + // read CID data + for(counter = 0; counter < 16; counter++) { + cid_data[counter] = sd_spi_read_byte(); + } + + sd_spi_purge_crc(); + + Cid->ManufacturerID = cid_data[0]; + memcpy(Cid->OEM_AppliID, cid_data + 1, 2); + memcpy(Cid->ProdName, cid_data + 3, 5); + Cid->ProdRev = cid_data[8]; + Cid->ProdSN = cid_data[9] << 24; + Cid->ProdSN |= cid_data[10] << 16; + Cid->ProdSN |= cid_data[11] << 8; + Cid->ProdSN |= cid_data[12]; + Cid->Reserved1 = (cid_data[13] & 0xF0) >> 4; + Cid->ManufactYear = (cid_data[13] & 0x0F) << 4; + Cid->CID_CRC = (cid_data[15] & 0xFE) >> 1; + Cid->Reserved2 = 1; + + ret = SdSpiStatusOK; + } + } + + sd_spi_deselect_card_and_purge(); + + return ret; +} + +static inline bool sd_cache_get(uint32_t address, uint32_t* data) { + uint8_t* cached_data = sector_cache_get(address); + if(cached_data) { + memcpy(data, cached_data, SD_BLOCK_SIZE); + return true; + } + return false; +} + +static inline void sd_cache_put(uint32_t address, uint32_t* data) { + sector_cache_put(address, (uint8_t*)data); +} + +static inline void sd_cache_invalidate_range(uint32_t start_sector, uint32_t end_sector) { + sector_cache_invalidate_range(start_sector, end_sector); +} + +static SdSpiStatus + sd_spi_cmd_read_blocks(uint32_t* data, uint32_t address, uint32_t blocks, uint32_t timeout_ms) { + uint32_t block_address = address; + uint32_t offset = 0; + + // CMD16 (SET_BLOCKLEN): R1 response (0x00: no errors) + SdSpiCmdAnswer response = + sd_spi_send_cmd(SD_CMD16_SET_BLOCKLEN, SD_BLOCK_SIZE, 0xFF, SdSpiCmdAnswerTypeR1); + sd_spi_deselect_card_and_purge(); + + if(response.r1 != SdSpi_R1_NO_ERROR) { + return SdSpiStatusError; + } + + if(!sd_high_capacity) { + block_address = address * SD_BLOCK_SIZE; + } + + while(blocks--) { + // CMD17 (READ_SINGLE_BLOCK): R1 response (0x00: no errors) + response = + sd_spi_send_cmd(SD_CMD17_READ_SINGLE_BLOCK, block_address, 0xFF, SdSpiCmdAnswerTypeR1); + if(response.r1 != SdSpi_R1_NO_ERROR) { + sd_spi_deselect_card_and_purge(); + return SdSpiStatusError; + } + + // Wait for the data start token + if(sd_spi_wait_for_data(SD_TOKEN_START_DATA_SINGLE_BLOCK_READ, timeout_ms) == + SdSpiStatusOK) { + // Read the data block + sd_spi_read_bytes_dma((uint8_t*)data + offset, SD_BLOCK_SIZE); + sd_spi_purge_crc(); + + // increase offset + offset += SD_BLOCK_SIZE; + + // increase block address + if(sd_high_capacity) { + block_address += 1; + } else { + block_address += SD_BLOCK_SIZE; + } + } else { + sd_spi_deselect_card_and_purge(); + return SdSpiStatusError; + } + + sd_spi_deselect_card_and_purge(); + } + + return SdSpiStatusOK; +} + +static SdSpiStatus sd_spi_cmd_write_blocks( + uint32_t* data, + uint32_t address, + uint32_t blocks, + uint32_t timeout_ms) { + uint32_t block_address = address; + uint32_t offset = 0; + + // CMD16 (SET_BLOCKLEN): R1 response (0x00: no errors) + SdSpiCmdAnswer response = + sd_spi_send_cmd(SD_CMD16_SET_BLOCKLEN, SD_BLOCK_SIZE, 0xFF, SdSpiCmdAnswerTypeR1); + sd_spi_deselect_card_and_purge(); + + if(response.r1 != SdSpi_R1_NO_ERROR) { + return SdSpiStatusError; + } + + if(!sd_high_capacity) { + block_address = address * SD_BLOCK_SIZE; + } + + while(blocks--) { + // CMD24 (WRITE_SINGLE_BLOCK): R1 response (0x00: no errors) + response = sd_spi_send_cmd( + SD_CMD24_WRITE_SINGLE_BLOCK, block_address, 0xFF, SdSpiCmdAnswerTypeR1); + if(response.r1 != SdSpi_R1_NO_ERROR) { + sd_spi_deselect_card_and_purge(); + return SdSpiStatusError; + } + + // Send dummy byte for NWR timing : one byte between CMD_WRITE and TOKEN + // TODO: check bytes count + sd_spi_write_byte(SD_DUMMY_BYTE); + sd_spi_write_byte(SD_DUMMY_BYTE); + + // Send the data start token + sd_spi_write_byte(SD_TOKEN_START_DATA_SINGLE_BLOCK_WRITE); + sd_spi_write_bytes_dma((uint8_t*)data + offset, SD_BLOCK_SIZE); + sd_spi_purge_crc(); + + // Read data response + SdSpiDataResponce data_responce = sd_spi_get_data_response(timeout_ms); + sd_spi_deselect_card_and_purge(); + + if(data_responce != SdSpiDataResponceOK) { + return SdSpiStatusError; + } + + // increase offset + offset += SD_BLOCK_SIZE; + + // increase block address + if(sd_high_capacity) { + block_address += 1; + } else { + block_address += SD_BLOCK_SIZE; + } + } + + return SdSpiStatusOK; +} + +uint8_t sd_max_mount_retry_count() { + return 10; +} + +SdSpiStatus sd_init(bool power_reset) { + // Slow speed init + furi_hal_spi_acquire(&furi_hal_spi_bus_handle_sd_slow); + furi_hal_sd_spi_handle = &furi_hal_spi_bus_handle_sd_slow; + + // We reset card in spi_lock context, so it is safe to disturb spi bus + if(power_reset) { + sd_spi_debug("Power reset"); + + // disable power and set low on all bus pins + furi_hal_power_disable_external_3_3v(); + sd_spi_bus_to_ground(); + hal_sd_detect_set_low(); + furi_delay_ms(250); + + // reinit bus and enable power + sd_spi_bus_rise_up(); + hal_sd_detect_init(); + furi_hal_power_enable_external_3_3v(); + furi_delay_ms(100); + } + + SdSpiStatus status = SdSpiStatusError; + + // Send 80 dummy clocks with CS high + sd_spi_deselect_card(); + for(uint8_t i = 0; i < 80; i++) { + sd_spi_write_byte(SD_DUMMY_BYTE); + } + + for(uint8_t i = 0; i < 128; i++) { + status = sd_spi_init_spi_mode(); + if(status == SdSpiStatusOK) { + // SD initialized and init to SPI mode properly + sd_spi_debug("SD init OK after %d retries", i); + break; + } + } + + furi_hal_sd_spi_handle = NULL; + furi_hal_spi_release(&furi_hal_spi_bus_handle_sd_slow); + + // Init sector cache + sector_cache_init(); + + return status; +} + +SdSpiStatus sd_get_card_state(void) { + SdSpiCmdAnswer response; + + // Send CMD13 (SEND_STATUS) to get SD status + response = sd_spi_send_cmd(SD_CMD13_SEND_STATUS, 0, 0xFF, SdSpiCmdAnswerTypeR2); + sd_spi_deselect_card_and_purge(); + + // Return status OK if response is valid + if((response.r1 == SdSpi_R1_NO_ERROR) && (response.r2 == SdSpi_R2_NO_ERROR)) { + return SdSpiStatusOK; + } + + return SdSpiStatusError; +} + +SdSpiStatus sd_get_card_info(SD_CardInfo* card_info) { + SdSpiStatus status; + + status = sd_spi_get_csd(&(card_info->Csd)); + + if(status != SdSpiStatusOK) { + return status; + } + + status = sd_spi_get_cid(&(card_info->Cid)); + + if(status != SdSpiStatusOK) { + return status; + } + + if(sd_high_capacity == 1) { + card_info->LogBlockSize = 512; + card_info->CardBlockSize = 512; + card_info->CardCapacity = ((uint64_t)card_info->Csd.version.v2.DeviceSize + 1UL) * 1024UL * + (uint64_t)card_info->LogBlockSize; + card_info->LogBlockNbr = (card_info->CardCapacity) / (card_info->LogBlockSize); + } else { + card_info->CardCapacity = (card_info->Csd.version.v1.DeviceSize + 1); + card_info->CardCapacity *= (1UL << (card_info->Csd.version.v1.DeviceSizeMul + 2)); + card_info->LogBlockSize = 512; + card_info->CardBlockSize = 1UL << (card_info->Csd.RdBlockLen); + card_info->CardCapacity *= card_info->CardBlockSize; + card_info->LogBlockNbr = (card_info->CardCapacity) / (card_info->LogBlockSize); + } + + return status; +} + +SdSpiStatus + sd_read_blocks(uint32_t* data, uint32_t address, uint32_t blocks, uint32_t timeout_ms) { + SdSpiStatus status = SdSpiStatusError; + + bool single_sector_read = (blocks == 1); + + if(single_sector_read) { + if(sd_cache_get(address, data)) { + return SdSpiStatusOK; + } + + status = sd_spi_cmd_read_blocks(data, address, blocks, timeout_ms); + + if(status == SdSpiStatusOK) { + sd_cache_put(address, data); + } + } else { + status = sd_spi_cmd_read_blocks(data, address, blocks, timeout_ms); + } + + return status; +} + +SdSpiStatus + sd_write_blocks(uint32_t* data, uint32_t address, uint32_t blocks, uint32_t timeout_ms) { + sd_cache_invalidate_range(address, address + blocks); + SdSpiStatus status = sd_spi_cmd_write_blocks(data, address, blocks, timeout_ms); + return status; +} + +SdSpiStatus sd_get_cid(SD_CID* cid) { + SdSpiStatus status; + + furi_hal_spi_acquire(&furi_hal_spi_bus_handle_sd_fast); + furi_hal_sd_spi_handle = &furi_hal_spi_bus_handle_sd_fast; + + memset(cid, 0, sizeof(SD_CID)); + status = sd_spi_get_cid(cid); + + furi_hal_sd_spi_handle = NULL; + furi_hal_spi_release(&furi_hal_spi_bus_handle_sd_fast); + + return status; +} \ No newline at end of file diff --git a/firmware/targets/f7/fatfs/sd_spi_io.h b/firmware/targets/f7/fatfs/sd_spi_io.h new file mode 100644 index 00000000000..8850eceb765 --- /dev/null +++ b/firmware/targets/f7/fatfs/sd_spi_io.h @@ -0,0 +1,157 @@ +#pragma once +#include +#include + +#define __IO volatile + +#define SD_TIMEOUT_MS (1000) + +typedef enum { + SdSpiStatusOK, + SdSpiStatusError, + SdSpiStatusTimeout, +} SdSpiStatus; + +/** + * @brief Card Specific Data: CSD Register + */ +typedef struct { + /* Header part */ + uint8_t CSDStruct : 2; /* CSD structure */ + uint8_t Reserved1 : 6; /* Reserved */ + uint8_t TAAC : 8; /* Data read access-time 1 */ + uint8_t NSAC : 8; /* Data read access-time 2 in CLK cycles */ + uint8_t MaxBusClkFrec : 8; /* Max. bus clock frequency */ + uint16_t CardComdClasses : 12; /* Card command classes */ + uint8_t RdBlockLen : 4; /* Max. read data block length */ + uint8_t PartBlockRead : 1; /* Partial blocks for read allowed */ + uint8_t WrBlockMisalign : 1; /* Write block misalignment */ + uint8_t RdBlockMisalign : 1; /* Read block misalignment */ + uint8_t DSRImpl : 1; /* DSR implemented */ + + /* v1 or v2 struct */ + union csd_version { + struct { + uint8_t Reserved1 : 2; /* Reserved */ + uint16_t DeviceSize : 12; /* Device Size */ + uint8_t MaxRdCurrentVDDMin : 3; /* Max. read current @ VDD min */ + uint8_t MaxRdCurrentVDDMax : 3; /* Max. read current @ VDD max */ + uint8_t MaxWrCurrentVDDMin : 3; /* Max. write current @ VDD min */ + uint8_t MaxWrCurrentVDDMax : 3; /* Max. write current @ VDD max */ + uint8_t DeviceSizeMul : 3; /* Device size multiplier */ + } v1; + struct { + uint8_t Reserved1 : 6; /* Reserved */ + uint32_t DeviceSize : 22; /* Device Size */ + uint8_t Reserved2 : 1; /* Reserved */ + } v2; + } version; + + uint8_t EraseSingleBlockEnable : 1; /* Erase single block enable */ + uint8_t EraseSectorSize : 7; /* Erase group size multiplier */ + uint8_t WrProtectGrSize : 7; /* Write protect group size */ + uint8_t WrProtectGrEnable : 1; /* Write protect group enable */ + uint8_t Reserved2 : 2; /* Reserved */ + uint8_t WrSpeedFact : 3; /* Write speed factor */ + uint8_t MaxWrBlockLen : 4; /* Max. write data block length */ + uint8_t WriteBlockPartial : 1; /* Partial blocks for write allowed */ + uint8_t Reserved3 : 5; /* Reserved */ + uint8_t FileFormatGrouop : 1; /* File format group */ + uint8_t CopyFlag : 1; /* Copy flag (OTP) */ + uint8_t PermWrProtect : 1; /* Permanent write protection */ + uint8_t TempWrProtect : 1; /* Temporary write protection */ + uint8_t FileFormat : 2; /* File Format */ + uint8_t Reserved4 : 2; /* Reserved */ + uint8_t crc : 7; /* Reserved */ + uint8_t Reserved5 : 1; /* always 1*/ + +} SD_CSD; + +/** + * @brief Card Identification Data: CID Register + */ +typedef struct { + uint8_t ManufacturerID; /* ManufacturerID */ + char OEM_AppliID[2]; /* OEM/Application ID */ + char ProdName[5]; /* Product Name */ + uint8_t ProdRev; /* Product Revision */ + uint32_t ProdSN; /* Product Serial Number */ + uint8_t Reserved1; /* Reserved1 */ + uint8_t ManufactYear; /* Manufacturing Year */ + uint8_t ManufactMonth; /* Manufacturing Month */ + uint8_t CID_CRC; /* CID CRC */ + uint8_t Reserved2; /* always 1 */ +} SD_CID; + +/** + * @brief SD Card information structure + */ +typedef struct { + SD_CSD Csd; + SD_CID Cid; + uint64_t CardCapacity; /*!< Card Capacity */ + uint32_t CardBlockSize; /*!< Card Block Size */ + uint32_t LogBlockNbr; /*!< Specifies the Card logical Capacity in blocks */ + uint32_t LogBlockSize; /*!< Specifies logical block size in bytes */ +} SD_CardInfo; + +/** + * @brief SD card max mount retry count + * + * @return uint8_t + */ +uint8_t sd_max_mount_retry_count(); + +/** + * @brief Init sd card + * + * @param power_reset reset card power + * @return SdSpiStatus + */ +SdSpiStatus sd_init(bool power_reset); + +/** + * @brief Get card state + * + * @return SdSpiStatus + */ +SdSpiStatus sd_get_card_state(void); + +/** + * @brief Get card info + * + * @param card_info + * @return SdSpiStatus + */ +SdSpiStatus sd_get_card_info(SD_CardInfo* card_info); + +/** + * @brief Read blocks + * + * @param data + * @param address + * @param blocks + * @param timeout_ms + * @return SdSpiStatus + */ +SdSpiStatus sd_read_blocks(uint32_t* data, uint32_t address, uint32_t blocks, uint32_t timeout_ms); + +/** + * @brief Write blocks + * + * @param data + * @param address + * @param blocks + * @param timeout_ms + * @return SdSpiStatus + */ +SdSpiStatus + sd_write_blocks(uint32_t* data, uint32_t address, uint32_t blocks, uint32_t timeout_ms); + +/** + * @brief Get card CSD register + * + * @param Cid + * @return SdSpiStatus + */ +SdSpiStatus sd_get_cid(SD_CID* cid); \ No newline at end of file diff --git a/firmware/targets/f7/fatfs/sector_cache.c b/firmware/targets/f7/fatfs/sector_cache.c index d23c1d5ad58..efb520ec842 100644 --- a/firmware/targets/f7/fatfs/sector_cache.c +++ b/firmware/targets/f7/fatfs/sector_cache.c @@ -8,7 +8,6 @@ #define SECTOR_SIZE 512 #define N_SECTORS 8 -#define TAG "SDCache" typedef struct { uint32_t itr; @@ -20,15 +19,11 @@ static SectorCache* cache = NULL; void sector_cache_init() { if(cache == NULL) { - // TODO: tuneup allocation order, to place cache in mem pool (MEM2) cache = memmgr_alloc_from_pool(sizeof(SectorCache)); } if(cache != NULL) { - FURI_LOG_I(TAG, "Init"); memset(cache, 0, sizeof(SectorCache)); - } else { - FURI_LOG_E(TAG, "Init failed"); } } diff --git a/firmware/targets/f7/fatfs/spi_sd_hal.c b/firmware/targets/f7/fatfs/spi_sd_hal.c deleted file mode 100644 index bfe046b588d..00000000000 --- a/firmware/targets/f7/fatfs/spi_sd_hal.c +++ /dev/null @@ -1,98 +0,0 @@ -#include -#include - -#define SD_DUMMY_BYTE 0xFF - -const uint32_t SpiTimeout = 1000; -uint8_t SD_IO_WriteByte(uint8_t Data); - -/****************************************************************************** - BUS OPERATIONS - *******************************************************************************/ - -/** - * @brief SPI Write byte(s) to device - * @param DataIn: Pointer to data buffer to write - * @param DataOut: Pointer to data buffer for read data - * @param DataLength: number of bytes to write - * @retval None - */ -static void SPIx_WriteReadData(const uint8_t* DataIn, uint8_t* DataOut, uint16_t DataLength) { - furi_check(furi_hal_spi_bus_trx( - furi_hal_sd_spi_handle, (uint8_t*)DataIn, DataOut, DataLength, SpiTimeout)); -} - -/** - * @brief SPI Write a byte to device - * @param Value: value to be written - * @retval None - */ -__attribute__((unused)) static void SPIx_Write(uint8_t Value) { - furi_check(furi_hal_spi_bus_tx(furi_hal_sd_spi_handle, (uint8_t*)&Value, 1, SpiTimeout)); -} - -/****************************************************************************** - LINK OPERATIONS - *******************************************************************************/ - -/********************************* LINK SD ************************************/ -/** - * @brief Initialize the SD Card and put it into StandBy State (Ready for - * data transfer). - * @retval None - */ -void SD_IO_Init(void) { - uint8_t counter = 0; - - /* SD chip select high */ - furi_hal_gpio_write(furi_hal_sd_spi_handle->cs, true); - furi_delay_us(10); - - /* Send dummy byte 0xFF, 10 times with CS high */ - /* Rise CS and MOSI for 80 clocks cycles */ - for(counter = 0; counter <= 200; counter++) { - /* Send dummy byte 0xFF */ - SD_IO_WriteByte(SD_DUMMY_BYTE); - } -} - -/** - * @brief Set SD interface Chip Select state - * @param val: 0 (low) or 1 (high) state - * @retval None - */ -void SD_IO_CSState(uint8_t val) { - /* Some SD Cards are prone to fail if CLK-ed too soon after CS transition. Worst case found: 8us */ - if(val == 1) { - furi_delay_us(10); // Exit guard time for some SD cards - furi_hal_gpio_write(furi_hal_sd_spi_handle->cs, true); - } else { - furi_hal_gpio_write(furi_hal_sd_spi_handle->cs, false); - furi_delay_us(10); // Entry guard time for some SD cards - } -} - -/** - * @brief Write byte(s) on the SD - * @param DataIn: Pointer to data buffer to write - * @param DataOut: Pointer to data buffer for read data - * @param DataLength: number of bytes to write - * @retval None - */ -void SD_IO_WriteReadData(const uint8_t* DataIn, uint8_t* DataOut, uint16_t DataLength) { - /* Send the byte */ - SPIx_WriteReadData(DataIn, DataOut, DataLength); -} - -/** - * @brief Write a byte on the SD. - * @param Data: byte to send. - * @retval Data written - */ -uint8_t SD_IO_WriteByte(uint8_t Data) { - uint8_t tmp; - - /* Send the byte */ - SPIx_WriteReadData(&Data, &tmp, 1); - return tmp; -} diff --git a/firmware/targets/f7/fatfs/stm32_adafruit_sd.c b/firmware/targets/f7/fatfs/stm32_adafruit_sd.c deleted file mode 100644 index 998adee2997..00000000000 --- a/firmware/targets/f7/fatfs/stm32_adafruit_sd.c +++ /dev/null @@ -1,1113 +0,0 @@ -/** - ****************************************************************************** - * @file stm32_adafruit_sd.c - * @author MCD Application Team - * @version V3.0.0 - * @date 23-December-2016 - * @brief This file provides a set of functions needed to manage the SD card - * mounted on the Adafruit 1.8" TFT LCD shield (reference ID 802), - * that is used with the STM32 Nucleo board through SPI interface. - * It implements a high level communication layer for read and write - * from/to this memory. The needed STM32XXxx hardware resources (SPI and - * GPIO) are defined in stm32XXxx_nucleo.h file, and the initialization is - * performed in SD_IO_Init() function declared in stm32XXxx_nucleo.c - * file. - * You can easily tailor this driver to any other development board, - * by just adapting the defines for hardware resources and - * SD_IO_Init() function. - * - * +-------------------------------------------------------+ - * | Pin assignment | - * +-------------------------+---------------+-------------+ - * | STM32XXxx SPI Pins | SD | Pin | - * +-------------------------+---------------+-------------+ - * | SD_SPI_CS_PIN | ChipSelect | 1 | - * | SD_SPI_MOSI_PIN / MOSI | DataIn | 2 | - * | | GND | 3 (0 V) | - * | | VDD | 4 (3.3 V)| - * | SD_SPI_SCK_PIN / SCLK | Clock | 5 | - * | | GND | 6 (0 V) | - * | SD_SPI_MISO_PIN / MISO | DataOut | 7 | - * +-------------------------+---------------+-------------+ - ****************************************************************************** - * @attention - * - *

© COPYRIGHT(c) 2016 STMicroelectronics

- * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. Neither the name of STMicroelectronics nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - ****************************************************************************** - */ - -/* File Info : ----------------------------------------------------------------- - User NOTES -1. How to use this driver: --------------------------- - - This driver does not need a specific component driver for the micro SD device - to be included with. - -2. Driver description: ---------------------- - + Initialization steps: - o Initialize the micro SD card using the BSP_SD_Init() function. - o Checking the SD card presence is not managed because SD detection pin is - not physically mapped on the Adafruit shield. - o The function BSP_SD_GetCardInfo() is used to get the micro SD card information - which is stored in the structure "SD_CardInfo". - - + Micro SD card operations - o The micro SD card can be accessed with read/write block(s) operations once - it is ready for access. The access can be performed in polling - mode by calling the functions BSP_SD_ReadBlocks()/BSP_SD_WriteBlocks() - - o The SD erase block(s) is performed using the function BSP_SD_Erase() with - specifying the number of blocks to erase. - o The SD runtime status is returned when calling the function BSP_SD_GetStatus(). - -------------------------------------------------------------------------------*/ - -/* Includes ------------------------------------------------------------------*/ -#include "stm32_adafruit_sd.h" -#include "stdlib.h" -#include "string.h" -#include "stdio.h" -#include -#include "sector_cache.h" - -/** @addtogroup BSP - * @{ - */ - -/** @addtogroup STM32_ADAFRUIT - * @{ - */ - -/** @defgroup STM32_ADAFRUIT_SD - * @{ - */ - -/* Private typedef -----------------------------------------------------------*/ - -/** @defgroup STM32_ADAFRUIT_SD_Private_Types_Definitions - * @{ - */ -typedef struct { - uint8_t r1; - uint8_t r2; - uint8_t r3; - uint8_t r4; - uint8_t r5; -} SD_CmdAnswer_typedef; - -/** - * @} - */ - -/* Private define ------------------------------------------------------------*/ - -/** @defgroup STM32_ADAFRUIT_SD_Private_Defines - * @{ - */ -#define SD_DUMMY_BYTE 0xFF - -#define SD_MAX_FRAME_LENGTH 17 /* Lenght = 16 + 1 */ -#define SD_CMD_LENGTH 6 - -#define SD_MAX_TRY 100 /* Number of try */ - -#define SD_CSD_STRUCT_V1 0x2 /* CSD struct version V1 */ -#define SD_CSD_STRUCT_V2 0x1 /* CSD struct version V2 */ - -/** - * @brief SD ansewer format - */ -typedef enum { - SD_ANSWER_R1_EXPECTED, - SD_ANSWER_R1B_EXPECTED, - SD_ANSWER_R2_EXPECTED, - SD_ANSWER_R3_EXPECTED, - SD_ANSWER_R4R5_EXPECTED, - SD_ANSWER_R7_EXPECTED, -} SD_Answer_type; - -/** - * @brief Start Data tokens: - * Tokens (necessary because at nop/idle (and CS active) only 0xff is - * on the data/command line) - */ -#define SD_TOKEN_START_DATA_SINGLE_BLOCK_READ \ - 0xFE /* Data token start byte, Start Single Block Read */ -#define SD_TOKEN_START_DATA_MULTIPLE_BLOCK_READ \ - 0xFE /* Data token start byte, Start Multiple Block Read */ -#define SD_TOKEN_START_DATA_SINGLE_BLOCK_WRITE \ - 0xFE /* Data token start byte, Start Single Block Write */ -#define SD_TOKEN_START_DATA_MULTIPLE_BLOCK_WRITE \ - 0xFD /* Data token start byte, Start Multiple Block Write */ -#define SD_TOKEN_STOP_DATA_MULTIPLE_BLOCK_WRITE \ - 0xFD /* Data toke stop byte, Stop Multiple Block Write */ - -/** - * @brief Commands: CMDxx = CMD-number | 0x40 - */ -#define SD_CMD_GO_IDLE_STATE 0 /* CMD0 = 0x40 */ -#define SD_CMD_SEND_OP_COND 1 /* CMD1 = 0x41 */ -#define SD_CMD_SEND_IF_COND 8 /* CMD8 = 0x48 */ -#define SD_CMD_SEND_CSD 9 /* CMD9 = 0x49 */ -#define SD_CMD_SEND_CID 10 /* CMD10 = 0x4A */ -#define SD_CMD_STOP_TRANSMISSION 12 /* CMD12 = 0x4C */ -#define SD_CMD_SEND_STATUS 13 /* CMD13 = 0x4D */ -#define SD_CMD_SET_BLOCKLEN 16 /* CMD16 = 0x50 */ -#define SD_CMD_READ_SINGLE_BLOCK 17 /* CMD17 = 0x51 */ -#define SD_CMD_READ_MULT_BLOCK 18 /* CMD18 = 0x52 */ -#define SD_CMD_SET_BLOCK_COUNT 23 /* CMD23 = 0x57 */ -#define SD_CMD_WRITE_SINGLE_BLOCK 24 /* CMD24 = 0x58 */ -#define SD_CMD_WRITE_MULT_BLOCK 25 /* CMD25 = 0x59 */ -#define SD_CMD_PROG_CSD 27 /* CMD27 = 0x5B */ -#define SD_CMD_SET_WRITE_PROT 28 /* CMD28 = 0x5C */ -#define SD_CMD_CLR_WRITE_PROT 29 /* CMD29 = 0x5D */ -#define SD_CMD_SEND_WRITE_PROT 30 /* CMD30 = 0x5E */ -#define SD_CMD_SD_ERASE_GRP_START 32 /* CMD32 = 0x60 */ -#define SD_CMD_SD_ERASE_GRP_END 33 /* CMD33 = 0x61 */ -#define SD_CMD_UNTAG_SECTOR 34 /* CMD34 = 0x62 */ -#define SD_CMD_ERASE_GRP_START 35 /* CMD35 = 0x63 */ -#define SD_CMD_ERASE_GRP_END 36 /* CMD36 = 0x64 */ -#define SD_CMD_UNTAG_ERASE_GROUP 37 /* CMD37 = 0x65 */ -#define SD_CMD_ERASE 38 /* CMD38 = 0x66 */ -#define SD_CMD_SD_APP_OP_COND 41 /* CMD41 = 0x69 */ -#define SD_CMD_APP_CMD 55 /* CMD55 = 0x77 */ -#define SD_CMD_READ_OCR 58 /* CMD55 = 0x79 */ - -/** - * @brief SD reponses and error flags - */ -typedef enum { - /* R1 answer value */ - SD_R1_NO_ERROR = (0x00), - SD_R1_IN_IDLE_STATE = (0x01), - SD_R1_ERASE_RESET = (0x02), - SD_R1_ILLEGAL_COMMAND = (0x04), - SD_R1_COM_CRC_ERROR = (0x08), - SD_R1_ERASE_SEQUENCE_ERROR = (0x10), - SD_R1_ADDRESS_ERROR = (0x20), - SD_R1_PARAMETER_ERROR = (0x40), - - /* R2 answer value */ - SD_R2_NO_ERROR = 0x00, - SD_R2_CARD_LOCKED = 0x01, - SD_R2_LOCKUNLOCK_ERROR = 0x02, - SD_R2_ERROR = 0x04, - SD_R2_CC_ERROR = 0x08, - SD_R2_CARD_ECC_FAILED = 0x10, - SD_R2_WP_VIOLATION = 0x20, - SD_R2_ERASE_PARAM = 0x40, - SD_R2_OUTOFRANGE = 0x80, - - /** - * @brief Data response error - */ - SD_DATA_OK = (0x05), - SD_DATA_CRC_ERROR = (0x0B), - SD_DATA_WRITE_ERROR = (0x0D), - SD_DATA_OTHER_ERROR = (0xFF) -} SD_Error; - -/** - * @} - */ - -/* Private macro -------------------------------------------------------------*/ - -/** @defgroup STM32_ADAFRUIT_SD_Private_Macros - * @{ - */ - -/** - * @} - */ - -/* Private variables ---------------------------------------------------------*/ - -/** @defgroup STM32_ADAFRUIT_SD_Private_Variables - * @{ - */ -__IO uint8_t SdStatus = SD_NOT_PRESENT; - -/* flag_SDHC : - 0 : Standard capacity - 1 : High capacity -*/ -uint16_t flag_SDHC = 0; - -/** - * @} - */ - -/* Private function prototypes -----------------------------------------------*/ -static uint8_t SD_GetCIDRegister(SD_CID* Cid); -static uint8_t SD_GetCSDRegister(SD_CSD* Csd); -static uint8_t SD_GetDataResponse(void); -static uint8_t SD_GoIdleState(void); -static SD_CmdAnswer_typedef SD_SendCmd(uint8_t Cmd, uint32_t Arg, uint8_t Crc, uint8_t Answer); -static uint8_t SD_WaitData(uint8_t data); -static uint8_t SD_ReadData(void); -/** @defgroup STM32_ADAFRUIT_SD_Private_Function_Prototypes - * @{ - */ -/** - * @} - */ - -/* Private functions ---------------------------------------------------------*/ - -void SD_SPI_Bus_To_Down_State() { - furi_hal_gpio_init_ex( - furi_hal_sd_spi_handle->miso, - GpioModeOutputPushPull, - GpioPullNo, - GpioSpeedVeryHigh, - GpioAltFnUnused); - furi_hal_gpio_init_ex( - furi_hal_sd_spi_handle->mosi, - GpioModeOutputPushPull, - GpioPullNo, - GpioSpeedVeryHigh, - GpioAltFnUnused); - furi_hal_gpio_init_ex( - furi_hal_sd_spi_handle->sck, - GpioModeOutputPushPull, - GpioPullNo, - GpioSpeedVeryHigh, - GpioAltFnUnused); - - furi_hal_gpio_write(furi_hal_sd_spi_handle->cs, false); - furi_hal_gpio_write(furi_hal_sd_spi_handle->miso, false); - furi_hal_gpio_write(furi_hal_sd_spi_handle->mosi, false); - furi_hal_gpio_write(furi_hal_sd_spi_handle->sck, false); -} - -void SD_SPI_Bus_To_Normal_State() { - furi_hal_gpio_write(furi_hal_sd_spi_handle->cs, true); - - furi_hal_gpio_init_ex( - furi_hal_sd_spi_handle->miso, - GpioModeAltFunctionPushPull, - GpioPullUp, - GpioSpeedVeryHigh, - GpioAltFn5SPI2); - furi_hal_gpio_init_ex( - furi_hal_sd_spi_handle->mosi, - GpioModeAltFunctionPushPull, - GpioPullUp, - GpioSpeedVeryHigh, - GpioAltFn5SPI2); - furi_hal_gpio_init_ex( - furi_hal_sd_spi_handle->sck, - GpioModeAltFunctionPushPull, - GpioPullUp, - GpioSpeedVeryHigh, - GpioAltFn5SPI2); -} - -/** @defgroup STM32_ADAFRUIT_SD_Private_Functions - * @{ - */ - -uint8_t BSP_SD_MaxMountRetryCount() { - return 10; -} - -/** - * @brief Initializes the SD/SD communication. - * @param None - * @retval The SD Response: - * - MSD_ERROR: Sequence failed - * - MSD_OK: Sequence succeed - */ -uint8_t BSP_SD_Init(bool reset_card) { - /* Slow speed init */ - furi_hal_spi_acquire(&furi_hal_spi_bus_handle_sd_slow); - furi_hal_sd_spi_handle = &furi_hal_spi_bus_handle_sd_slow; - - /* We must reset card in spi_lock context */ - if(reset_card) { - /* disable power and set low on all bus pins */ - furi_hal_power_disable_external_3_3v(); - SD_SPI_Bus_To_Down_State(); - hal_sd_detect_set_low(); - furi_delay_ms(250); - - /* reinit bus and enable power */ - SD_SPI_Bus_To_Normal_State(); - hal_sd_detect_init(); - furi_hal_power_enable_external_3_3v(); - furi_delay_ms(100); - } - - /* Configure IO functionalities for SD pin */ - SD_IO_Init(); - - /* SD detection pin is not physically mapped on the Adafruit shield */ - SdStatus = SD_PRESENT; - uint8_t res = BSP_SD_ERROR; - - for(uint8_t i = 0; i < 128; i++) { - res = SD_GoIdleState(); - if(res == BSP_SD_OK) break; - } - - furi_hal_sd_spi_handle = NULL; - furi_hal_spi_release(&furi_hal_spi_bus_handle_sd_slow); - - sector_cache_init(); - - /* SD initialized and set to SPI mode properly */ - return res; -} - -/** - * @brief Returns information about specific card. - * @param pCardInfo: Pointer to a SD_CardInfo structure that contains all SD - * card information. - * @retval The SD Response: - * - MSD_ERROR: Sequence failed - * - MSD_OK: Sequence succeed - */ -uint8_t BSP_SD_GetCardInfo(SD_CardInfo* pCardInfo) { - uint8_t status; - - status = SD_GetCSDRegister(&(pCardInfo->Csd)); - status |= SD_GetCIDRegister(&(pCardInfo->Cid)); - if(flag_SDHC == 1) { - pCardInfo->LogBlockSize = 512; - pCardInfo->CardBlockSize = 512; - pCardInfo->CardCapacity = ((uint64_t)pCardInfo->Csd.version.v2.DeviceSize + 1UL) * 1024UL * - (uint64_t)pCardInfo->LogBlockSize; - pCardInfo->LogBlockNbr = (pCardInfo->CardCapacity) / (pCardInfo->LogBlockSize); - } else { - pCardInfo->CardCapacity = (pCardInfo->Csd.version.v1.DeviceSize + 1); - pCardInfo->CardCapacity *= (1UL << (pCardInfo->Csd.version.v1.DeviceSizeMul + 2)); - pCardInfo->LogBlockSize = 512; - pCardInfo->CardBlockSize = 1UL << (pCardInfo->Csd.RdBlockLen); - pCardInfo->CardCapacity *= pCardInfo->CardBlockSize; - pCardInfo->LogBlockNbr = (pCardInfo->CardCapacity) / (pCardInfo->LogBlockSize); - } - - return status; -} - -/** - * @brief Reads block(s) from a specified address in the SD card, in polling mode. - * @param pData: Pointer to the buffer that will contain the data to transmit - * @param ReadAddr: Address from where data is to be read. The address is counted - * in blocks of 512bytes - * @param NumOfBlocks: Number of SD blocks to read - * @param Timeout: This parameter is used for compatibility with BSP implementation - * @retval SD status - */ -uint8_t - BSP_SD_ReadBlocks(uint32_t* pData, uint32_t ReadAddr, uint32_t NumOfBlocks, uint32_t Timeout) { - UNUSED(Timeout); // FIXME! - uint32_t offset = 0; - uint32_t addr; - uint8_t retr = BSP_SD_ERROR; - SD_CmdAnswer_typedef response; - uint16_t BlockSize = 512; - uint8_t* cached_data; - - bool single_sector_read = (NumOfBlocks == 1); - if(single_sector_read && (cached_data = sector_cache_get(ReadAddr))) { - memcpy(pData, cached_data, BlockSize); - return BSP_SD_OK; - } - - /* Send CMD16 (SD_CMD_SET_BLOCKLEN) to set the size of the block and - Check if the SD acknowledged the set block length command: R1 response (0x00: no errors) */ - response = SD_SendCmd(SD_CMD_SET_BLOCKLEN, BlockSize, 0xFF, SD_ANSWER_R1_EXPECTED); - SD_IO_CSState(1); - SD_IO_WriteByte(SD_DUMMY_BYTE); - if(response.r1 != SD_R1_NO_ERROR) { - goto error; - } - - /* Initialize the address */ - addr = (ReadAddr * ((flag_SDHC == 1) ? 1 : BlockSize)); - - /* Data transfer */ - while(NumOfBlocks--) { - /* Send CMD17 (SD_CMD_READ_SINGLE_BLOCK) to read one block */ - /* Check if the SD acknowledged the read block command: R1 response (0x00: no errors) */ - response = SD_SendCmd(SD_CMD_READ_SINGLE_BLOCK, addr, 0xFF, SD_ANSWER_R1_EXPECTED); - if(response.r1 != SD_R1_NO_ERROR) { - goto error; - } - - /* Now look for the data token to signify the start of the data */ - if(SD_WaitData(SD_TOKEN_START_DATA_SINGLE_BLOCK_READ) == BSP_SD_OK) { - /* Read the SD block data : read NumByteToRead data */ - SD_IO_WriteReadData(NULL, (uint8_t*)pData + offset, BlockSize); - - /* Set next read address*/ - offset += BlockSize; - addr = ((flag_SDHC == 1) ? (addr + 1) : (addr + BlockSize)); - - /* get CRC bytes (not really needed by us, but required by SD) */ - SD_IO_WriteByte(SD_DUMMY_BYTE); - SD_IO_WriteByte(SD_DUMMY_BYTE); - } else { - goto error; - } - - /* End the command data read cycle */ - SD_IO_CSState(1); - SD_IO_WriteByte(SD_DUMMY_BYTE); - } - - if(single_sector_read) { - sector_cache_put(ReadAddr, (uint8_t*)pData); - } - - retr = BSP_SD_OK; - -error: - /* Send dummy byte: 8 Clock pulses of delay */ - SD_IO_CSState(1); - SD_IO_WriteByte(SD_DUMMY_BYTE); - - /* Return the reponse */ - return retr; -} - -/** - * @brief Writes block(s) to a specified address in the SD card, in polling mode. - * @param pData: Pointer to the buffer that will contain the data to transmit - * @param WriteAddr: Address from where data is to be written. The address is counted - * in blocks of 512bytes - * @param NumOfBlocks: Number of SD blocks to write - * @param Timeout: This parameter is used for compatibility with BSP implementation - * @retval SD status - */ -uint8_t BSP_SD_WriteBlocks( - uint32_t* pData, - uint32_t WriteAddr, - uint32_t NumOfBlocks, - uint32_t Timeout) { - UNUSED(Timeout); // FIXME! - uint32_t offset = 0; - uint32_t addr; - uint8_t retr = BSP_SD_ERROR; - SD_CmdAnswer_typedef response; - uint16_t BlockSize = 512; - sector_cache_invalidate_range(WriteAddr, WriteAddr + NumOfBlocks); - - /* Send CMD16 (SD_CMD_SET_BLOCKLEN) to set the size of the block and - Check if the SD acknowledged the set block length command: R1 response (0x00: no errors) */ - response = SD_SendCmd(SD_CMD_SET_BLOCKLEN, BlockSize, 0xFF, SD_ANSWER_R1_EXPECTED); - SD_IO_CSState(1); - SD_IO_WriteByte(SD_DUMMY_BYTE); - if(response.r1 != SD_R1_NO_ERROR) { - goto error; - } - - /* Initialize the address */ - addr = (WriteAddr * ((flag_SDHC == 1) ? 1 : BlockSize)); - - /* Data transfer */ - while(NumOfBlocks--) { - /* Send CMD24 (SD_CMD_WRITE_SINGLE_BLOCK) to write blocks and - Check if the SD acknowledged the write block command: R1 response (0x00: no errors) */ - response = SD_SendCmd(SD_CMD_WRITE_SINGLE_BLOCK, addr, 0xFF, SD_ANSWER_R1_EXPECTED); - if(response.r1 != SD_R1_NO_ERROR) { - goto error; - } - - /* Send dummy byte for NWR timing : one byte between CMDWRITE and TOKEN */ - SD_IO_WriteByte(SD_DUMMY_BYTE); - SD_IO_WriteByte(SD_DUMMY_BYTE); - - /* Send the data token to signify the start of the data */ - SD_IO_WriteByte(SD_TOKEN_START_DATA_SINGLE_BLOCK_WRITE); - - /* Write the block data to SD */ - SD_IO_WriteReadData((uint8_t*)pData + offset, NULL, BlockSize); - - /* Set next write address */ - offset += BlockSize; - addr = ((flag_SDHC == 1) ? (addr + 1) : (addr + BlockSize)); - - /* Put CRC bytes (not really needed by us, but required by SD) */ - SD_IO_WriteByte(SD_DUMMY_BYTE); - SD_IO_WriteByte(SD_DUMMY_BYTE); - - /* Read data response */ - if(SD_GetDataResponse() != SD_DATA_OK) { - /* Set response value to failure */ - goto error; - } - - SD_IO_CSState(1); - SD_IO_WriteByte(SD_DUMMY_BYTE); - } - retr = BSP_SD_OK; - -error: - - /* Send dummy byte: 8 Clock pulses of delay */ - SD_IO_CSState(1); - SD_IO_WriteByte(SD_DUMMY_BYTE); - - /* Return the reponse */ - return retr; -} - -/** - * @brief Erases the specified memory area of the given SD card. - * @param StartAddr: Start address in Blocks (Size of a block is 512bytes) - * @param EndAddr: End address in Blocks (Size of a block is 512bytes) - * @retval SD status - */ -uint8_t BSP_SD_Erase(uint32_t StartAddr, uint32_t EndAddr) { - uint8_t retr = BSP_SD_ERROR; - SD_CmdAnswer_typedef response; - uint16_t BlockSize = 512; - - /* Send CMD32 (Erase group start) and check if the SD acknowledged the erase command: R1 response (0x00: no errors) */ - response = SD_SendCmd( - SD_CMD_SD_ERASE_GRP_START, - (StartAddr) * (flag_SDHC == 1 ? 1 : BlockSize), - 0xFF, - SD_ANSWER_R1_EXPECTED); - SD_IO_CSState(1); - SD_IO_WriteByte(SD_DUMMY_BYTE); - if(response.r1 == SD_R1_NO_ERROR) { - /* Send CMD33 (Erase group end) and Check if the SD acknowledged the erase command: R1 response (0x00: no errors) */ - response = SD_SendCmd( - SD_CMD_SD_ERASE_GRP_END, - (EndAddr * 512) * (flag_SDHC == 1 ? 1 : BlockSize), - 0xFF, - SD_ANSWER_R1_EXPECTED); - SD_IO_CSState(1); - SD_IO_WriteByte(SD_DUMMY_BYTE); - if(response.r1 == SD_R1_NO_ERROR) { - /* Send CMD38 (Erase) and Check if the SD acknowledged the erase command: R1 response (0x00: no errors) */ - response = SD_SendCmd(SD_CMD_ERASE, 0, 0xFF, SD_ANSWER_R1B_EXPECTED); - if(response.r1 == SD_R1_NO_ERROR) { - retr = BSP_SD_OK; - } - SD_IO_CSState(1); - SD_IO_WriteByte(SD_DUMMY_BYTE); - } - } - - /* Return the reponse */ - return retr; -} - -/** - * @brief Returns the SD status. - * @param None - * @retval The SD status. - */ -uint8_t BSP_SD_GetCardState(void) { - SD_CmdAnswer_typedef retr; - - /* Send CMD13 (SD_SEND_STATUS) to get SD status */ - retr = SD_SendCmd(SD_CMD_SEND_STATUS, 0, 0xFF, SD_ANSWER_R2_EXPECTED); - SD_IO_CSState(1); - SD_IO_WriteByte(SD_DUMMY_BYTE); - - /* Find SD status according to card state */ - if((retr.r1 == SD_R1_NO_ERROR) && (retr.r2 == SD_R2_NO_ERROR)) { - return BSP_SD_OK; - } - - return BSP_SD_ERROR; -} - -/** - * @brief Reads the SD card SCD register. - * Reading the contents of the CSD register in SPI mode is a simple - * read-block transaction. - * @param Csd: pointer on an SCD register structure - * @retval SD status - */ -uint8_t SD_GetCSDRegister(SD_CSD* Csd) { - uint16_t counter = 0; - uint8_t CSD_Tab[16]; - uint8_t retr = BSP_SD_ERROR; - SD_CmdAnswer_typedef response; - - /* Send CMD9 (CSD register) or CMD10(CSD register) and Wait for response in the R1 format (0x00 is no errors) */ - response = SD_SendCmd(SD_CMD_SEND_CSD, 0, 0xFF, SD_ANSWER_R1_EXPECTED); - if(response.r1 == SD_R1_NO_ERROR) { - if(SD_WaitData(SD_TOKEN_START_DATA_SINGLE_BLOCK_READ) == BSP_SD_OK) { - for(counter = 0; counter < 16; counter++) { - /* Store CSD register value on CSD_Tab */ - CSD_Tab[counter] = SD_IO_WriteByte(SD_DUMMY_BYTE); - } - - /* Get CRC bytes (not really needed by us, but required by SD) */ - SD_IO_WriteByte(SD_DUMMY_BYTE); - SD_IO_WriteByte(SD_DUMMY_BYTE); - - /************************************************************************* - CSD header decoding - *************************************************************************/ - - /* Byte 0 */ - Csd->CSDStruct = (CSD_Tab[0] & 0xC0) >> 6; - Csd->Reserved1 = CSD_Tab[0] & 0x3F; - - /* Byte 1 */ - Csd->TAAC = CSD_Tab[1]; - - /* Byte 2 */ - Csd->NSAC = CSD_Tab[2]; - - /* Byte 3 */ - Csd->MaxBusClkFrec = CSD_Tab[3]; - - /* Byte 4/5 */ - Csd->CardComdClasses = (CSD_Tab[4] << 4) | ((CSD_Tab[5] & 0xF0) >> 4); - Csd->RdBlockLen = CSD_Tab[5] & 0x0F; - - /* Byte 6 */ - Csd->PartBlockRead = (CSD_Tab[6] & 0x80) >> 7; - Csd->WrBlockMisalign = (CSD_Tab[6] & 0x40) >> 6; - Csd->RdBlockMisalign = (CSD_Tab[6] & 0x20) >> 5; - Csd->DSRImpl = (CSD_Tab[6] & 0x10) >> 4; - - /************************************************************************* - CSD v1/v2 decoding - *************************************************************************/ - - if(flag_SDHC == 0) { - Csd->version.v1.Reserved1 = ((CSD_Tab[6] & 0x0C) >> 2); - - Csd->version.v1.DeviceSize = ((CSD_Tab[6] & 0x03) << 10) | (CSD_Tab[7] << 2) | - ((CSD_Tab[8] & 0xC0) >> 6); - Csd->version.v1.MaxRdCurrentVDDMin = (CSD_Tab[8] & 0x38) >> 3; - Csd->version.v1.MaxRdCurrentVDDMax = (CSD_Tab[8] & 0x07); - Csd->version.v1.MaxWrCurrentVDDMin = (CSD_Tab[9] & 0xE0) >> 5; - Csd->version.v1.MaxWrCurrentVDDMax = (CSD_Tab[9] & 0x1C) >> 2; - Csd->version.v1.DeviceSizeMul = ((CSD_Tab[9] & 0x03) << 1) | - ((CSD_Tab[10] & 0x80) >> 7); - } else { - Csd->version.v2.Reserved1 = ((CSD_Tab[6] & 0x0F) << 2) | - ((CSD_Tab[7] & 0xC0) >> 6); - Csd->version.v2.DeviceSize = ((CSD_Tab[7] & 0x3F) << 16) | (CSD_Tab[8] << 8) | - CSD_Tab[9]; - Csd->version.v2.Reserved2 = ((CSD_Tab[10] & 0x80) >> 8); - } - - Csd->EraseSingleBlockEnable = (CSD_Tab[10] & 0x40) >> 6; - Csd->EraseSectorSize = ((CSD_Tab[10] & 0x3F) << 1) | ((CSD_Tab[11] & 0x80) >> 7); - Csd->WrProtectGrSize = (CSD_Tab[11] & 0x7F); - Csd->WrProtectGrEnable = (CSD_Tab[12] & 0x80) >> 7; - Csd->Reserved2 = (CSD_Tab[12] & 0x60) >> 5; - Csd->WrSpeedFact = (CSD_Tab[12] & 0x1C) >> 2; - Csd->MaxWrBlockLen = ((CSD_Tab[12] & 0x03) << 2) | ((CSD_Tab[13] & 0xC0) >> 6); - Csd->WriteBlockPartial = (CSD_Tab[13] & 0x20) >> 5; - Csd->Reserved3 = (CSD_Tab[13] & 0x1F); - Csd->FileFormatGrouop = (CSD_Tab[14] & 0x80) >> 7; - Csd->CopyFlag = (CSD_Tab[14] & 0x40) >> 6; - Csd->PermWrProtect = (CSD_Tab[14] & 0x20) >> 5; - Csd->TempWrProtect = (CSD_Tab[14] & 0x10) >> 4; - Csd->FileFormat = (CSD_Tab[14] & 0x0C) >> 2; - Csd->Reserved4 = (CSD_Tab[14] & 0x03); - Csd->crc = (CSD_Tab[15] & 0xFE) >> 1; - Csd->Reserved5 = (CSD_Tab[15] & 0x01); - - retr = BSP_SD_OK; - } - } - - /* Send dummy byte: 8 Clock pulses of delay */ - SD_IO_CSState(1); - SD_IO_WriteByte(SD_DUMMY_BYTE); - - /* Return the reponse */ - return retr; -} - -/** - * @brief Reads the SD card CID register. - * Reading the contents of the CID register in SPI mode is a simple - * read-block transaction. - * @param Cid: pointer on an CID register structure - * @retval SD status - */ -uint8_t SD_GetCIDRegister(SD_CID* Cid) { - uint32_t counter = 0; - uint8_t retr = BSP_SD_ERROR; - uint8_t CID_Tab[16]; - SD_CmdAnswer_typedef response; - - /* Send CMD10 (CID register) and Wait for response in the R1 format (0x00 is no errors) */ - response = SD_SendCmd(SD_CMD_SEND_CID, 0, 0xFF, SD_ANSWER_R1_EXPECTED); - if(response.r1 == SD_R1_NO_ERROR) { - if(SD_WaitData(SD_TOKEN_START_DATA_SINGLE_BLOCK_READ) == BSP_SD_OK) { - /* Store CID register value on CID_Tab */ - for(counter = 0; counter < 16; counter++) { - CID_Tab[counter] = SD_IO_WriteByte(SD_DUMMY_BYTE); - } - - /* Get CRC bytes (not really needed by us, but required by SD) */ - SD_IO_WriteByte(SD_DUMMY_BYTE); - SD_IO_WriteByte(SD_DUMMY_BYTE); - - /* Byte 0 */ - Cid->ManufacturerID = CID_Tab[0]; - - /* Byte 1 */ - memcpy(Cid->OEM_AppliID, CID_Tab + 1, 2); - - /* Byte 3 */ - memcpy(Cid->ProdName, CID_Tab + 3, 5); - - /* Byte 8 */ - Cid->ProdRev = CID_Tab[8]; - - /* Byte 9 */ - Cid->ProdSN = CID_Tab[9] << 24; - - /* Byte 10 */ - Cid->ProdSN |= CID_Tab[10] << 16; - - /* Byte 11 */ - Cid->ProdSN |= CID_Tab[11] << 8; - - /* Byte 12 */ - Cid->ProdSN |= CID_Tab[12]; - - /* Byte 13 */ - Cid->Reserved1 = (CID_Tab[13] & 0xF0) >> 4; - Cid->ManufactYear = (CID_Tab[13] & 0x0F) << 4; - - /* Byte 14 */ - Cid->ManufactYear |= (CID_Tab[14] & 0xF0) >> 4; - Cid->ManufactMonth = (CID_Tab[14] & 0x0F); - - /* Byte 15 */ - Cid->CID_CRC = (CID_Tab[15] & 0xFE) >> 1; - Cid->Reserved2 = 1; - - retr = BSP_SD_OK; - } - } - - /* Send dummy byte: 8 Clock pulses of delay */ - SD_IO_CSState(1); - SD_IO_WriteByte(SD_DUMMY_BYTE); - - /* Return the reponse */ - return retr; -} - -uint8_t BSP_SD_GetCIDRegister(SD_CID* Cid) { - uint8_t retr = BSP_SD_ERROR; - - /* Slow speed init */ - furi_hal_spi_acquire(&furi_hal_spi_bus_handle_sd_slow); - furi_hal_sd_spi_handle = &furi_hal_spi_bus_handle_sd_slow; - - memset(Cid, 0, sizeof(SD_CID)); - retr = SD_GetCIDRegister(Cid); - - furi_hal_sd_spi_handle = NULL; - furi_hal_spi_release(&furi_hal_spi_bus_handle_sd_slow); - return retr; -} - -/** - * @brief Sends 5 bytes command to the SD card and get response - * @param Cmd: The user expected command to send to SD card. - * @param Arg: The command argument. - * @param Crc: The CRC. - * @param Answer: SD_ANSWER_NOT_EXPECTED or SD_ANSWER_EXPECTED - * @retval SD status - */ -SD_CmdAnswer_typedef SD_SendCmd(uint8_t Cmd, uint32_t Arg, uint8_t Crc, uint8_t Answer) { - uint8_t frame[SD_CMD_LENGTH], frameout[SD_CMD_LENGTH]; - SD_CmdAnswer_typedef retr = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; - - /* R1 Lenght = NCS(0)+ 6 Bytes command + NCR(min1 max8) + 1 Bytes answer + NEC(0) = 15bytes */ - /* R1b identical to R1 + Busy information */ - /* R2 Lenght = NCS(0)+ 6 Bytes command + NCR(min1 max8) + 2 Bytes answer + NEC(0) = 16bytes */ - - /* Prepare Frame to send */ - frame[0] = (Cmd | 0x40); /* Construct byte 1 */ - frame[1] = (uint8_t)(Arg >> 24); /* Construct byte 2 */ - frame[2] = (uint8_t)(Arg >> 16); /* Construct byte 3 */ - frame[3] = (uint8_t)(Arg >> 8); /* Construct byte 4 */ - frame[4] = (uint8_t)(Arg); /* Construct byte 5 */ - frame[5] = (Crc | 0x01); /* Construct byte 6 */ - - /* Send the command */ - SD_IO_CSState(0); - SD_IO_WriteReadData(frame, frameout, SD_CMD_LENGTH); /* Send the Cmd bytes */ - - switch(Answer) { - case SD_ANSWER_R1_EXPECTED: - retr.r1 = SD_ReadData(); - break; - case SD_ANSWER_R1B_EXPECTED: - retr.r1 = SD_ReadData(); - retr.r2 = SD_IO_WriteByte(SD_DUMMY_BYTE); - /* Set CS High */ - SD_IO_CSState(1); - furi_delay_us(1000); - /* Set CS Low */ - SD_IO_CSState(0); - - /* Wait IO line return 0xFF */ - while(SD_IO_WriteByte(SD_DUMMY_BYTE) != 0xFF) - ; - break; - case SD_ANSWER_R2_EXPECTED: - retr.r1 = SD_ReadData(); - retr.r2 = SD_IO_WriteByte(SD_DUMMY_BYTE); - break; - case SD_ANSWER_R3_EXPECTED: - case SD_ANSWER_R7_EXPECTED: - retr.r1 = SD_ReadData(); - retr.r2 = SD_IO_WriteByte(SD_DUMMY_BYTE); - retr.r3 = SD_IO_WriteByte(SD_DUMMY_BYTE); - retr.r4 = SD_IO_WriteByte(SD_DUMMY_BYTE); - retr.r5 = SD_IO_WriteByte(SD_DUMMY_BYTE); - break; - default: - break; - } - return retr; -} - -/** - * @brief Gets the SD card data response and check the busy flag. - * @param None - * @retval The SD status: Read data response xxx01 - * - status 010: Data accecpted - * - status 101: Data rejected due to a crc error - * - status 110: Data rejected due to a Write error. - * - status 111: Data rejected due to other error. - */ -uint8_t SD_GetDataResponse(void) { - uint8_t dataresponse; - uint8_t rvalue = SD_DATA_OTHER_ERROR; - - dataresponse = SD_IO_WriteByte(SD_DUMMY_BYTE); - SD_IO_WriteByte(SD_DUMMY_BYTE); /* read the busy response byte*/ - - /* Mask unused bits */ - switch(dataresponse & 0x1F) { - case SD_DATA_OK: - rvalue = SD_DATA_OK; - - /* Set CS High */ - SD_IO_CSState(1); - /* Set CS Low */ - SD_IO_CSState(0); - - /* Wait IO line return 0xFF */ - while(SD_IO_WriteByte(SD_DUMMY_BYTE) != 0xFF) - ; - break; - case SD_DATA_CRC_ERROR: - rvalue = SD_DATA_CRC_ERROR; - break; - case SD_DATA_WRITE_ERROR: - rvalue = SD_DATA_WRITE_ERROR; - break; - default: - break; - } - - /* Return response */ - return rvalue; -} - -/** - * @brief Put the SD in Idle state. - * @param None - * @retval SD status - */ -uint8_t SD_GoIdleState(void) { - SD_CmdAnswer_typedef response; - __IO uint8_t counter; - /* Send CMD0 (SD_CMD_GO_IDLE_STATE) to put SD in SPI mode and - wait for In Idle State Response (R1 Format) equal to 0x01 */ - counter = 0; - do { - counter++; - response = SD_SendCmd(SD_CMD_GO_IDLE_STATE, 0, 0x95, SD_ANSWER_R1_EXPECTED); - SD_IO_CSState(1); - SD_IO_WriteByte(SD_DUMMY_BYTE); - if(counter >= SD_MAX_TRY) { - return BSP_SD_ERROR; - } - } while(response.r1 != SD_R1_IN_IDLE_STATE); - - /* Send CMD8 (SD_CMD_SEND_IF_COND) to check the power supply status - and wait until response (R7 Format) equal to 0xAA and */ - response = SD_SendCmd(SD_CMD_SEND_IF_COND, 0x1AA, 0x87, SD_ANSWER_R7_EXPECTED); - SD_IO_CSState(1); - SD_IO_WriteByte(SD_DUMMY_BYTE); - if((response.r1 & SD_R1_ILLEGAL_COMMAND) == SD_R1_ILLEGAL_COMMAND) { - /* initialise card V1 */ - counter = 0; - do { - counter++; - /* initialise card V1 */ - /* Send CMD55 (SD_CMD_APP_CMD) before any ACMD command: R1 response (0x00: no errors) */ - response = SD_SendCmd(SD_CMD_APP_CMD, 0x00000000, 0xFF, SD_ANSWER_R1_EXPECTED); - SD_IO_CSState(1); - SD_IO_WriteByte(SD_DUMMY_BYTE); - - /* Send ACMD41 (SD_CMD_SD_APP_OP_COND) to initialize SDHC or SDXC cards: R1 response (0x00: no errors) */ - response = //-V519 - SD_SendCmd(SD_CMD_SD_APP_OP_COND, 0x00000000, 0xFF, SD_ANSWER_R1_EXPECTED); - SD_IO_CSState(1); - SD_IO_WriteByte(SD_DUMMY_BYTE); - if(counter >= SD_MAX_TRY) { - return BSP_SD_ERROR; - } - } while(response.r1 == SD_R1_IN_IDLE_STATE); - flag_SDHC = 0; - } else if(response.r1 == SD_R1_IN_IDLE_STATE) { - /* initialise card V2 */ - counter = 0; - do { - counter++; - /* Send CMD55 (SD_CMD_APP_CMD) before any ACMD command: R1 response (0x00: no errors) */ - response = SD_SendCmd(SD_CMD_APP_CMD, 0, 0xFF, SD_ANSWER_R1_EXPECTED); - SD_IO_CSState(1); - SD_IO_WriteByte(SD_DUMMY_BYTE); - - /* Send ACMD41 (SD_CMD_SD_APP_OP_COND) to initialize SDHC or SDXC cards: R1 response (0x00: no errors) */ - response = //-V519 - SD_SendCmd(SD_CMD_SD_APP_OP_COND, 0x40000000, 0xFF, SD_ANSWER_R1_EXPECTED); - SD_IO_CSState(1); - SD_IO_WriteByte(SD_DUMMY_BYTE); - if(counter >= SD_MAX_TRY) { - return BSP_SD_ERROR; - } - } while(response.r1 == SD_R1_IN_IDLE_STATE); - - if((response.r1 & SD_R1_ILLEGAL_COMMAND) == SD_R1_ILLEGAL_COMMAND) { - counter = 0; - do { - counter++; - /* Send CMD55 (SD_CMD_APP_CMD) before any ACMD command: R1 response (0x00: no errors) */ - response = SD_SendCmd(SD_CMD_APP_CMD, 0, 0xFF, SD_ANSWER_R1_EXPECTED); - SD_IO_CSState(1); - SD_IO_WriteByte(SD_DUMMY_BYTE); - if(response.r1 != SD_R1_IN_IDLE_STATE) { - return BSP_SD_ERROR; - } - /* Send ACMD41 (SD_CMD_SD_APP_OP_COND) to initialize SDHC or SDXC cards: R1 response (0x00: no errors) */ - response = - SD_SendCmd(SD_CMD_SD_APP_OP_COND, 0x00000000, 0xFF, SD_ANSWER_R1_EXPECTED); - SD_IO_CSState(1); - SD_IO_WriteByte(SD_DUMMY_BYTE); - if(counter >= SD_MAX_TRY) { - return BSP_SD_ERROR; - } - } while(response.r1 == SD_R1_IN_IDLE_STATE); - } - - /* Send CMD58 (SD_CMD_READ_OCR) to initialize SDHC or SDXC cards: R3 response (0x00: no errors) */ - response = SD_SendCmd(SD_CMD_READ_OCR, 0x00000000, 0xFF, SD_ANSWER_R3_EXPECTED); - SD_IO_CSState(1); - SD_IO_WriteByte(SD_DUMMY_BYTE); - if(response.r1 != SD_R1_NO_ERROR) { - return BSP_SD_ERROR; - } - flag_SDHC = (response.r2 & 0x40) >> 6; - } else { - return BSP_SD_ERROR; - } - - return BSP_SD_OK; -} - -/** - * @brief Waits a data until a value different from SD_DUMMY_BITE - * @param None - * @retval the value read - */ -uint8_t SD_ReadData(void) { - uint8_t timeout = 0x08; - uint8_t readvalue; - - /* Check if response is got or a timeout is happen */ - do { - readvalue = SD_IO_WriteByte(SD_DUMMY_BYTE); - timeout--; - - } while((readvalue == SD_DUMMY_BYTE) && timeout); - - /* Right response got */ - return readvalue; -} - -/** - * @brief Waits a data from the SD card - * @param data : Expected data from the SD card - * @retval BSP_SD_OK or BSP_SD_TIMEOUT - */ -uint8_t SD_WaitData(uint8_t data) { - uint16_t timeout = 0xFFFF; - uint8_t readvalue; - - /* Check if response is got or a timeout is happen */ - - do { - readvalue = SD_IO_WriteByte(SD_DUMMY_BYTE); - timeout--; - } while((readvalue != data) && timeout); - - if(timeout == 0) { - /* After time out */ - return BSP_SD_TIMEOUT; - } - - /* Right response got */ - return BSP_SD_OK; -} - -/** - * @} - */ - -/** - * @} - */ - -/** - * @} - */ - -/** - * @} - */ - -/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ diff --git a/firmware/targets/f7/fatfs/stm32_adafruit_sd.h b/firmware/targets/f7/fatfs/stm32_adafruit_sd.h deleted file mode 100644 index a133c5922ba..00000000000 --- a/firmware/targets/f7/fatfs/stm32_adafruit_sd.h +++ /dev/null @@ -1,245 +0,0 @@ -/** - ****************************************************************************** - * @file stm32_adafruit_sd.h - * @author MCD Application Team - * @version V3.0.0 - * @date 23-December-2016 - * @brief This file contains the common defines and functions prototypes for - * the stm32_adafruit_sd.c driver. - ****************************************************************************** - * @attention - * - *

© COPYRIGHT(c) 2016 STMicroelectronics

- * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. Neither the name of STMicroelectronics nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - ****************************************************************************** - */ - -/* Define to prevent recursive inclusion -------------------------------------*/ -#ifndef __STM32_ADAFRUIT_SD_H -#define __STM32_ADAFRUIT_SD_H - -#ifdef __cplusplus -extern "C" { -#endif - -/* Includes ------------------------------------------------------------------*/ -#include -#include - -/** @addtogroup BSP - * @{ - */ -#define __IO volatile - -/** @addtogroup STM32_ADAFRUIT - * @{ - */ - -/** @defgroup STM32_ADAFRUIT_SD - * @{ - */ - -/** @defgroup STM32_ADAFRUIT_SD_Exported_Types - * @{ - */ - -/** - * @brief SD status structure definition - */ -enum { BSP_SD_OK = 0x00, MSD_OK = 0x00, BSP_SD_ERROR = 0x01, BSP_SD_TIMEOUT }; - -typedef struct { - uint8_t Reserved1 : 2; /* Reserved */ - uint16_t DeviceSize : 12; /* Device Size */ - uint8_t MaxRdCurrentVDDMin : 3; /* Max. read current @ VDD min */ - uint8_t MaxRdCurrentVDDMax : 3; /* Max. read current @ VDD max */ - uint8_t MaxWrCurrentVDDMin : 3; /* Max. write current @ VDD min */ - uint8_t MaxWrCurrentVDDMax : 3; /* Max. write current @ VDD max */ - uint8_t DeviceSizeMul : 3; /* Device size multiplier */ -} struct_v1; - -typedef struct { - uint8_t Reserved1 : 6; /* Reserved */ - uint32_t DeviceSize : 22; /* Device Size */ - uint8_t Reserved2 : 1; /* Reserved */ -} struct_v2; - -/** - * @brief Card Specific Data: CSD Register - */ -typedef struct { - /* Header part */ - uint8_t CSDStruct : 2; /* CSD structure */ - uint8_t Reserved1 : 6; /* Reserved */ - uint8_t TAAC : 8; /* Data read access-time 1 */ - uint8_t NSAC : 8; /* Data read access-time 2 in CLK cycles */ - uint8_t MaxBusClkFrec : 8; /* Max. bus clock frequency */ - uint16_t CardComdClasses : 12; /* Card command classes */ - uint8_t RdBlockLen : 4; /* Max. read data block length */ - uint8_t PartBlockRead : 1; /* Partial blocks for read allowed */ - uint8_t WrBlockMisalign : 1; /* Write block misalignment */ - uint8_t RdBlockMisalign : 1; /* Read block misalignment */ - uint8_t DSRImpl : 1; /* DSR implemented */ - - /* v1 or v2 struct */ - union csd_version { - struct_v1 v1; - struct_v2 v2; - } version; - - uint8_t EraseSingleBlockEnable : 1; /* Erase single block enable */ - uint8_t EraseSectorSize : 7; /* Erase group size multiplier */ - uint8_t WrProtectGrSize : 7; /* Write protect group size */ - uint8_t WrProtectGrEnable : 1; /* Write protect group enable */ - uint8_t Reserved2 : 2; /* Reserved */ - uint8_t WrSpeedFact : 3; /* Write speed factor */ - uint8_t MaxWrBlockLen : 4; /* Max. write data block length */ - uint8_t WriteBlockPartial : 1; /* Partial blocks for write allowed */ - uint8_t Reserved3 : 5; /* Reserved */ - uint8_t FileFormatGrouop : 1; /* File format group */ - uint8_t CopyFlag : 1; /* Copy flag (OTP) */ - uint8_t PermWrProtect : 1; /* Permanent write protection */ - uint8_t TempWrProtect : 1; /* Temporary write protection */ - uint8_t FileFormat : 2; /* File Format */ - uint8_t Reserved4 : 2; /* Reserved */ - uint8_t crc : 7; /* Reserved */ - uint8_t Reserved5 : 1; /* always 1*/ - -} SD_CSD; - -/** - * @brief Card Identification Data: CID Register - */ -typedef struct { - uint8_t ManufacturerID; /* ManufacturerID */ - char OEM_AppliID[2]; /* OEM/Application ID */ - char ProdName[5]; /* Product Name */ - uint8_t ProdRev; /* Product Revision */ - uint32_t ProdSN; /* Product Serial Number */ - uint8_t Reserved1; /* Reserved1 */ - uint8_t ManufactYear; /* Manufacturing Year */ - uint8_t ManufactMonth; /* Manufacturing Month */ - uint8_t CID_CRC; /* CID CRC */ - uint8_t Reserved2; /* always 1 */ -} SD_CID; - -/** - * @brief SD Card information - */ -typedef struct { - SD_CSD Csd; - SD_CID Cid; - uint64_t CardCapacity; /*!< Card Capacity */ - uint32_t CardBlockSize; /*!< Card Block Size */ - uint32_t LogBlockNbr; /*!< Specifies the Card logical Capacity in blocks */ - uint32_t LogBlockSize; /*!< Specifies logical block size in bytes */ -} SD_CardInfo; - -/** - * @} - */ - -/** @defgroup STM32_ADAFRUIT_SPI_SD_Exported_Constants - * @{ - */ - -/** - * @brief Block Size - */ -#define SD_BLOCK_SIZE 0x200 - -/** - * @brief SD detection on its memory slot - */ -#define SD_PRESENT ((uint8_t)0x01) -#define SD_NOT_PRESENT ((uint8_t)0x00) - -#define SD_DATATIMEOUT ((uint32_t)100000000) - -/** - * @brief SD Card information structure - */ -#define BSP_SD_CardInfo SD_CardInfo - -/** - * @} - */ - -/** @defgroup STM32_ADAFRUIT_SD_Exported_Macro - * @{ - */ - -/** - * @} - */ - -/** @defgroup STM32_ADAFRUIT_SD_Exported_Functions - * @{ - */ -uint8_t BSP_SD_MaxMountRetryCount(); -uint8_t BSP_SD_Init(bool reset_card); -uint8_t - BSP_SD_ReadBlocks(uint32_t* pData, uint32_t ReadAddr, uint32_t NumOfBlocks, uint32_t Timeout); -uint8_t - BSP_SD_WriteBlocks(uint32_t* pData, uint32_t WriteAddr, uint32_t NumOfBlocks, uint32_t Timeout); -uint8_t BSP_SD_Erase(uint32_t StartAddr, uint32_t EndAddr); -uint8_t BSP_SD_GetCardState(void); -uint8_t BSP_SD_GetCardInfo(SD_CardInfo* pCardInfo); -uint8_t BSP_SD_GetCIDRegister(SD_CID* Cid); - -/* Link functions for SD Card peripheral*/ -void SD_SPI_Slow_Init(void); -void SD_SPI_Fast_Init(void); -void SD_IO_Init(void); -void SD_IO_CSState(uint8_t state); -void SD_IO_WriteReadData(const uint8_t* DataIn, uint8_t* DataOut, uint16_t DataLength); -uint8_t SD_IO_WriteByte(uint8_t Data); - -/* Link function for HAL delay */ -void HAL_Delay(__IO uint32_t Delay); - -#ifdef __cplusplus -} -#endif - -#endif /* __STM32_ADAFRUIT_SD_H */ - -/** - * @} - */ - -/** - * @} - */ - -/** - * @} - */ - -/** - * @} - */ - -/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ diff --git a/firmware/targets/f7/fatfs/user_diskio.c b/firmware/targets/f7/fatfs/user_diskio.c index b504fcd516b..16ac78e4dc4 100644 --- a/firmware/targets/f7/fatfs/user_diskio.c +++ b/firmware/targets/f7/fatfs/user_diskio.c @@ -46,7 +46,7 @@ static volatile DSTATUS Stat = STA_NOINIT; static DSTATUS User_CheckStatus(BYTE lun) { UNUSED(lun); Stat = STA_NOINIT; - if(BSP_SD_GetCardState() == MSD_OK) { + if(sd_get_card_state() == SdSpiStatusOK) { Stat &= ~STA_NOINIT; } @@ -128,11 +128,18 @@ DRESULT USER_read(BYTE pdrv, BYTE* buff, DWORD sector, UINT count) { furi_hal_spi_acquire(&furi_hal_spi_bus_handle_sd_fast); furi_hal_sd_spi_handle = &furi_hal_spi_bus_handle_sd_fast; - if(BSP_SD_ReadBlocks((uint32_t*)buff, (uint32_t)(sector), count, SD_DATATIMEOUT) == MSD_OK) { + if(sd_read_blocks((uint32_t*)buff, (uint32_t)(sector), count, SD_TIMEOUT_MS) == + SdSpiStatusOK) { + FuriHalCortexTimer timer = furi_hal_cortex_timer_get(SD_TIMEOUT_MS * 1000); + /* wait until the read operation is finished */ - while(BSP_SD_GetCardState() != MSD_OK) { - } res = RES_OK; + while(sd_get_card_state() != SdSpiStatusOK) { + if(furi_hal_cortex_timer_is_expired(timer)) { + res = RES_ERROR; + break; + } + } } furi_hal_sd_spi_handle = NULL; @@ -160,11 +167,18 @@ DRESULT USER_write(BYTE pdrv, const BYTE* buff, DWORD sector, UINT count) { furi_hal_spi_acquire(&furi_hal_spi_bus_handle_sd_fast); furi_hal_sd_spi_handle = &furi_hal_spi_bus_handle_sd_fast; - if(BSP_SD_WriteBlocks((uint32_t*)buff, (uint32_t)(sector), count, SD_DATATIMEOUT) == MSD_OK) { + if(sd_write_blocks((uint32_t*)buff, (uint32_t)(sector), count, SD_TIMEOUT_MS) == + SdSpiStatusOK) { + FuriHalCortexTimer timer = furi_hal_cortex_timer_get(SD_TIMEOUT_MS * 1000); + /* wait until the Write operation is finished */ - while(BSP_SD_GetCardState() != MSD_OK) { - } res = RES_OK; + while(sd_get_card_state() != SdSpiStatusOK) { + if(furi_hal_cortex_timer_is_expired(timer)) { + res = RES_ERROR; + break; + } + } } furi_hal_sd_spi_handle = NULL; @@ -187,7 +201,7 @@ DRESULT USER_ioctl(BYTE pdrv, BYTE cmd, void* buff) { /* USER CODE BEGIN IOCTL */ UNUSED(pdrv); DRESULT res = RES_ERROR; - BSP_SD_CardInfo CardInfo; + SD_CardInfo CardInfo; if(Stat & STA_NOINIT) return RES_NOTRDY; @@ -202,21 +216,21 @@ DRESULT USER_ioctl(BYTE pdrv, BYTE cmd, void* buff) { /* Get number of sectors on the disk (DWORD) */ case GET_SECTOR_COUNT: - BSP_SD_GetCardInfo(&CardInfo); + sd_get_card_info(&CardInfo); *(DWORD*)buff = CardInfo.LogBlockNbr; res = RES_OK; break; /* Get R/W sector size (WORD) */ case GET_SECTOR_SIZE: - BSP_SD_GetCardInfo(&CardInfo); + sd_get_card_info(&CardInfo); *(WORD*)buff = CardInfo.LogBlockSize; res = RES_OK; break; /* Get erase block size in unit of sector (DWORD) */ case GET_BLOCK_SIZE: - BSP_SD_GetCardInfo(&CardInfo); + sd_get_card_info(&CardInfo); *(DWORD*)buff = CardInfo.LogBlockSize; res = RES_OK; break; diff --git a/firmware/targets/f7/fatfs/user_diskio.h b/firmware/targets/f7/fatfs/user_diskio.h index 177723be1a9..12e0f27dc60 100644 --- a/firmware/targets/f7/fatfs/user_diskio.h +++ b/firmware/targets/f7/fatfs/user_diskio.h @@ -30,7 +30,7 @@ extern "C" { /* USER CODE BEGIN 0 */ /* Includes ------------------------------------------------------------------*/ -#include "stm32_adafruit_sd.h" +#include "sd_spi_io.h" #include "fatfs/ff_gen_drv.h" /* Exported types ------------------------------------------------------------*/ /* Exported constants --------------------------------------------------------*/ diff --git a/firmware/targets/f7/furi_hal/furi_hal.c b/firmware/targets/f7/furi_hal/furi_hal.c index 7f2f5759f58..afe46c4ed3e 100644 --- a/firmware/targets/f7/furi_hal/furi_hal.c +++ b/firmware/targets/f7/furi_hal/furi_hal.c @@ -53,6 +53,7 @@ void furi_hal_init() { furi_hal_region_init(); furi_hal_spi_config_init(); + furi_hal_spi_dma_init(); furi_hal_ibutton_init(); FURI_LOG_I(TAG, "iButton OK"); diff --git a/firmware/targets/f7/furi_hal/furi_hal_infrared.c b/firmware/targets/f7/furi_hal/furi_hal_infrared.c index dea1112ab4c..c1d24f8039c 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_infrared.c +++ b/firmware/targets/f7/furi_hal/furi_hal_infrared.c @@ -28,6 +28,15 @@ const GpioPin gpio_infrared_tx_debug = {.port = GPIOA, .pin = GPIO_PIN_7}; #define INFRARED_TX_CCMR_LOW \ (TIM_CCMR2_OC3PE | LL_TIM_OCMODE_FORCED_INACTIVE) /* Space time - force low */ +/* DMA Channels definition */ +#define IR_DMA DMA2 +#define IR_DMA_CH1_CHANNEL LL_DMA_CHANNEL_1 +#define IR_DMA_CH2_CHANNEL LL_DMA_CHANNEL_2 +#define IR_DMA_CH1_IRQ FuriHalInterruptIdDma2Ch1 +#define IR_DMA_CH2_IRQ FuriHalInterruptIdDma2Ch2 +#define IR_DMA_CH1_DEF IR_DMA, IR_DMA_CH1_CHANNEL +#define IR_DMA_CH2_DEF IR_DMA, IR_DMA_CH2_CHANNEL + typedef struct { FuriHalInfraredRxCaptureCallback capture_callback; void* capture_context; @@ -213,15 +222,15 @@ void furi_hal_infrared_async_rx_set_timeout_isr_callback( } static void furi_hal_infrared_tx_dma_terminate(void) { - LL_DMA_DisableIT_TC(DMA1, LL_DMA_CHANNEL_1); - LL_DMA_DisableIT_HT(DMA1, LL_DMA_CHANNEL_2); - LL_DMA_DisableIT_TC(DMA1, LL_DMA_CHANNEL_2); + LL_DMA_DisableIT_TC(IR_DMA_CH1_DEF); + LL_DMA_DisableIT_HT(IR_DMA_CH2_DEF); + LL_DMA_DisableIT_TC(IR_DMA_CH2_DEF); furi_assert(furi_hal_infrared_state == InfraredStateAsyncTxStopInProgress); - LL_DMA_DisableIT_TC(DMA1, LL_DMA_CHANNEL_1); - LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_2); - LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_1); + LL_DMA_DisableIT_TC(IR_DMA_CH1_DEF); + LL_DMA_DisableChannel(IR_DMA_CH2_DEF); + LL_DMA_DisableChannel(IR_DMA_CH1_DEF); LL_TIM_DisableCounter(TIM1); FuriStatus status = furi_semaphore_release(infrared_tim_tx.stop_semaphore); furi_check(status == FuriStatusOk); @@ -230,7 +239,7 @@ static void furi_hal_infrared_tx_dma_terminate(void) { static uint8_t furi_hal_infrared_get_current_dma_tx_buffer(void) { uint8_t buf_num = 0; - uint32_t buffer_adr = LL_DMA_GetMemoryAddress(DMA1, LL_DMA_CHANNEL_2); + uint32_t buffer_adr = LL_DMA_GetMemoryAddress(IR_DMA_CH2_DEF); if(buffer_adr == (uint32_t)infrared_tim_tx.buffer[0].data) { buf_num = 0; } else if(buffer_adr == (uint32_t)infrared_tim_tx.buffer[1].data) { @@ -242,12 +251,13 @@ static uint8_t furi_hal_infrared_get_current_dma_tx_buffer(void) { } static void furi_hal_infrared_tx_dma_polarity_isr() { - if(LL_DMA_IsActiveFlag_TE1(DMA1)) { - LL_DMA_ClearFlag_TE1(DMA1); +#if IR_DMA_CH1_CHANNEL == LL_DMA_CHANNEL_1 + if(LL_DMA_IsActiveFlag_TE1(IR_DMA)) { + LL_DMA_ClearFlag_TE1(IR_DMA); furi_crash(NULL); } - if(LL_DMA_IsActiveFlag_TC1(DMA1) && LL_DMA_IsEnabledIT_TC(DMA1, LL_DMA_CHANNEL_1)) { - LL_DMA_ClearFlag_TC1(DMA1); + if(LL_DMA_IsActiveFlag_TC1(IR_DMA) && LL_DMA_IsEnabledIT_TC(IR_DMA_CH1_DEF)) { + LL_DMA_ClearFlag_TC1(IR_DMA); furi_check( (furi_hal_infrared_state == InfraredStateAsyncTx) || @@ -257,25 +267,29 @@ static void furi_hal_infrared_tx_dma_polarity_isr() { uint8_t next_buf_num = furi_hal_infrared_get_current_dma_tx_buffer(); furi_hal_infrared_tx_dma_set_polarity(next_buf_num, 0); } +#else +#error Update this code. Would you kindly? +#endif } static void furi_hal_infrared_tx_dma_isr() { - if(LL_DMA_IsActiveFlag_TE2(DMA1)) { - LL_DMA_ClearFlag_TE2(DMA1); +#if IR_DMA_CH2_CHANNEL == LL_DMA_CHANNEL_2 + if(LL_DMA_IsActiveFlag_TE2(IR_DMA)) { + LL_DMA_ClearFlag_TE2(IR_DMA); furi_crash(NULL); } - if(LL_DMA_IsActiveFlag_HT2(DMA1) && LL_DMA_IsEnabledIT_HT(DMA1, LL_DMA_CHANNEL_2)) { - LL_DMA_ClearFlag_HT2(DMA1); + if(LL_DMA_IsActiveFlag_HT2(IR_DMA) && LL_DMA_IsEnabledIT_HT(IR_DMA_CH2_DEF)) { + LL_DMA_ClearFlag_HT2(IR_DMA); uint8_t buf_num = furi_hal_infrared_get_current_dma_tx_buffer(); uint8_t next_buf_num = !buf_num; if(infrared_tim_tx.buffer[buf_num].last_packet_end) { - LL_DMA_DisableIT_HT(DMA1, LL_DMA_CHANNEL_2); + LL_DMA_DisableIT_HT(IR_DMA_CH2_DEF); } else if( !infrared_tim_tx.buffer[buf_num].packet_end || (furi_hal_infrared_state == InfraredStateAsyncTx)) { furi_hal_infrared_tx_fill_buffer(next_buf_num, 0); if(infrared_tim_tx.buffer[next_buf_num].last_packet_end) { - LL_DMA_DisableIT_HT(DMA1, LL_DMA_CHANNEL_2); + LL_DMA_DisableIT_HT(IR_DMA_CH2_DEF); } } else if(furi_hal_infrared_state == InfraredStateAsyncTxStopReq) { /* fallthrough */ @@ -283,8 +297,8 @@ static void furi_hal_infrared_tx_dma_isr() { furi_crash(NULL); } } - if(LL_DMA_IsActiveFlag_TC2(DMA1) && LL_DMA_IsEnabledIT_TC(DMA1, LL_DMA_CHANNEL_2)) { - LL_DMA_ClearFlag_TC2(DMA1); + if(LL_DMA_IsActiveFlag_TC2(IR_DMA) && LL_DMA_IsEnabledIT_TC(IR_DMA_CH2_DEF)) { + LL_DMA_ClearFlag_TC2(IR_DMA); furi_check( (furi_hal_infrared_state == InfraredStateAsyncTxStopInProgress) || (furi_hal_infrared_state == InfraredStateAsyncTxStopReq) || @@ -310,6 +324,9 @@ static void furi_hal_infrared_tx_dma_isr() { infrared_tim_tx.signal_sent_callback(infrared_tim_tx.signal_sent_context); } } +#else +#error Update this code. Would you kindly? +#endif } static void furi_hal_infrared_configure_tim_pwm_tx(uint32_t freq, float duty_cycle) { @@ -369,16 +386,19 @@ static void furi_hal_infrared_configure_tim_cmgr2_dma_tx(void) { dma_config.NbData = 0; dma_config.PeriphRequest = LL_DMAMUX_REQ_TIM1_UP; dma_config.Priority = LL_DMA_PRIORITY_VERYHIGH; - LL_DMA_Init(DMA1, LL_DMA_CHANNEL_1, &dma_config); + LL_DMA_Init(IR_DMA_CH1_DEF, &dma_config); - LL_DMA_ClearFlag_TE1(DMA1); - LL_DMA_ClearFlag_TC1(DMA1); +#if IR_DMA_CH1_CHANNEL == LL_DMA_CHANNEL_1 + LL_DMA_ClearFlag_TE1(IR_DMA); + LL_DMA_ClearFlag_TC1(IR_DMA); +#else +#error Update this code. Would you kindly? +#endif - LL_DMA_EnableIT_TE(DMA1, LL_DMA_CHANNEL_1); - LL_DMA_EnableIT_TC(DMA1, LL_DMA_CHANNEL_1); + LL_DMA_EnableIT_TE(IR_DMA_CH1_DEF); + LL_DMA_EnableIT_TC(IR_DMA_CH1_DEF); - furi_hal_interrupt_set_isr_ex( - FuriHalInterruptIdDma1Ch1, 4, furi_hal_infrared_tx_dma_polarity_isr, NULL); + furi_hal_interrupt_set_isr_ex(IR_DMA_CH1_IRQ, 4, furi_hal_infrared_tx_dma_polarity_isr, NULL); } static void furi_hal_infrared_configure_tim_rcr_dma_tx(void) { @@ -394,18 +414,21 @@ static void furi_hal_infrared_configure_tim_rcr_dma_tx(void) { dma_config.NbData = 0; dma_config.PeriphRequest = LL_DMAMUX_REQ_TIM1_UP; dma_config.Priority = LL_DMA_PRIORITY_MEDIUM; - LL_DMA_Init(DMA1, LL_DMA_CHANNEL_2, &dma_config); + LL_DMA_Init(IR_DMA_CH2_DEF, &dma_config); - LL_DMA_ClearFlag_TC2(DMA1); - LL_DMA_ClearFlag_HT2(DMA1); - LL_DMA_ClearFlag_TE2(DMA1); +#if IR_DMA_CH2_CHANNEL == LL_DMA_CHANNEL_2 + LL_DMA_ClearFlag_TC2(IR_DMA); + LL_DMA_ClearFlag_HT2(IR_DMA); + LL_DMA_ClearFlag_TE2(IR_DMA); +#else +#error Update this code. Would you kindly? +#endif - LL_DMA_EnableIT_TC(DMA1, LL_DMA_CHANNEL_2); - LL_DMA_EnableIT_HT(DMA1, LL_DMA_CHANNEL_2); - LL_DMA_EnableIT_TE(DMA1, LL_DMA_CHANNEL_2); + LL_DMA_EnableIT_TC(IR_DMA_CH2_DEF); + LL_DMA_EnableIT_HT(IR_DMA_CH2_DEF); + LL_DMA_EnableIT_TE(IR_DMA_CH2_DEF); - furi_hal_interrupt_set_isr_ex( - FuriHalInterruptIdDma1Ch2, 5, furi_hal_infrared_tx_dma_isr, NULL); + furi_hal_interrupt_set_isr_ex(IR_DMA_CH2_IRQ, 5, furi_hal_infrared_tx_dma_isr, NULL); } static void furi_hal_infrared_tx_fill_buffer_last(uint8_t buf_num) { @@ -507,14 +530,14 @@ static void furi_hal_infrared_tx_dma_set_polarity(uint8_t buf_num, uint8_t polar furi_assert(buffer->polarity != NULL); FURI_CRITICAL_ENTER(); - bool channel_enabled = LL_DMA_IsEnabledChannel(DMA1, LL_DMA_CHANNEL_1); + bool channel_enabled = LL_DMA_IsEnabledChannel(IR_DMA_CH1_DEF); if(channel_enabled) { - LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_1); + LL_DMA_DisableChannel(IR_DMA_CH1_DEF); } - LL_DMA_SetMemoryAddress(DMA1, LL_DMA_CHANNEL_1, (uint32_t)buffer->polarity); - LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_1, buffer->size + polarity_shift); + LL_DMA_SetMemoryAddress(IR_DMA_CH1_DEF, (uint32_t)buffer->polarity); + LL_DMA_SetDataLength(IR_DMA_CH1_DEF, buffer->size + polarity_shift); if(channel_enabled) { - LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_1); + LL_DMA_EnableChannel(IR_DMA_CH1_DEF); } FURI_CRITICAL_EXIT(); } @@ -527,14 +550,14 @@ static void furi_hal_infrared_tx_dma_set_buffer(uint8_t buf_num) { /* non-circular mode requires disabled channel before setup */ FURI_CRITICAL_ENTER(); - bool channel_enabled = LL_DMA_IsEnabledChannel(DMA1, LL_DMA_CHANNEL_2); + bool channel_enabled = LL_DMA_IsEnabledChannel(IR_DMA_CH2_DEF); if(channel_enabled) { - LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_2); + LL_DMA_DisableChannel(IR_DMA_CH2_DEF); } - LL_DMA_SetMemoryAddress(DMA1, LL_DMA_CHANNEL_2, (uint32_t)buffer->data); - LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_2, buffer->size); + LL_DMA_SetMemoryAddress(IR_DMA_CH2_DEF, (uint32_t)buffer->data); + LL_DMA_SetDataLength(IR_DMA_CH2_DEF, buffer->size); if(channel_enabled) { - LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_2); + LL_DMA_EnableChannel(IR_DMA_CH2_DEF); } FURI_CRITICAL_EXIT(); } @@ -545,8 +568,8 @@ static void furi_hal_infrared_async_tx_free_resources(void) { (furi_hal_infrared_state == InfraredStateAsyncTxStopped)); furi_hal_gpio_init(&gpio_infrared_tx, GpioModeOutputOpenDrain, GpioPullDown, GpioSpeedLow); - furi_hal_interrupt_set_isr(FuriHalInterruptIdDma1Ch1, NULL, NULL); - furi_hal_interrupt_set_isr(FuriHalInterruptIdDma1Ch2, NULL, NULL); + furi_hal_interrupt_set_isr(IR_DMA_CH1_IRQ, NULL, NULL); + furi_hal_interrupt_set_isr(IR_DMA_CH2_IRQ, NULL, NULL); LL_TIM_DeInit(TIM1); furi_semaphore_free(infrared_tim_tx.stop_semaphore); @@ -597,8 +620,8 @@ void furi_hal_infrared_async_tx_start(uint32_t freq, float duty_cycle) { furi_hal_infrared_state = InfraredStateAsyncTx; LL_TIM_ClearFlag_UPDATE(TIM1); - LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_1); - LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_2); + LL_DMA_EnableChannel(IR_DMA_CH1_DEF); + LL_DMA_EnableChannel(IR_DMA_CH2_DEF); furi_delay_us(5); LL_TIM_GenerateEvent_UPDATE(TIM1); /* DMA -> TIMx_RCR */ furi_delay_us(5); diff --git a/firmware/targets/f7/furi_hal/furi_hal_rfid.c b/firmware/targets/f7/furi_hal/furi_hal_rfid.c index 0ade85e0afb..b0238f79f23 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_rfid.c +++ b/firmware/targets/f7/furi_hal/furi_hal_rfid.c @@ -21,6 +21,14 @@ #define RFID_CAPTURE_IND_CH LL_TIM_CHANNEL_CH3 #define RFID_CAPTURE_DIR_CH LL_TIM_CHANNEL_CH4 +/* DMA Channels definition */ +#define RFID_DMA DMA2 +#define RFID_DMA_CH1_CHANNEL LL_DMA_CHANNEL_1 +#define RFID_DMA_CH2_CHANNEL LL_DMA_CHANNEL_2 +#define RFID_DMA_CH1_IRQ FuriHalInterruptIdDma2Ch1 +#define RFID_DMA_CH1_DEF RFID_DMA, RFID_DMA_CH1_CHANNEL +#define RFID_DMA_CH2_DEF RFID_DMA, RFID_DMA_CH2_CHANNEL + typedef struct { FuriHalRfidEmulateCallback callback; FuriHalRfidDMACallback dma_callback; @@ -302,15 +310,19 @@ void furi_hal_rfid_tim_read_capture_stop() { } static void furi_hal_rfid_dma_isr() { - if(LL_DMA_IsActiveFlag_HT1(DMA1)) { - LL_DMA_ClearFlag_HT1(DMA1); +#if RFID_DMA_CH1_CHANNEL == LL_DMA_CHANNEL_1 + if(LL_DMA_IsActiveFlag_HT1(RFID_DMA)) { + LL_DMA_ClearFlag_HT1(RFID_DMA); furi_hal_rfid->dma_callback(true, furi_hal_rfid->context); } - if(LL_DMA_IsActiveFlag_TC1(DMA1)) { - LL_DMA_ClearFlag_TC1(DMA1); + if(LL_DMA_IsActiveFlag_TC1(RFID_DMA)) { + LL_DMA_ClearFlag_TC1(RFID_DMA); furi_hal_rfid->dma_callback(false, furi_hal_rfid->context); } +#else +#error Update this code. Would you kindly? +#endif } void furi_hal_rfid_tim_emulate_dma_start( @@ -347,8 +359,8 @@ void furi_hal_rfid_tim_emulate_dma_start( dma_config.NbData = length; dma_config.PeriphRequest = LL_DMAMUX_REQ_TIM2_UP; dma_config.Priority = LL_DMA_MODE_NORMAL; - LL_DMA_Init(DMA1, LL_DMA_CHANNEL_1, &dma_config); - LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_1); + LL_DMA_Init(RFID_DMA_CH1_DEF, &dma_config); + LL_DMA_EnableChannel(RFID_DMA_CH1_DEF); // configure DMA "mem -> CCR3" channel #if FURI_HAL_RFID_EMULATE_TIMER_CHANNEL == LL_TIM_CHANNEL_CH3 @@ -366,13 +378,13 @@ void furi_hal_rfid_tim_emulate_dma_start( dma_config.NbData = length; dma_config.PeriphRequest = LL_DMAMUX_REQ_TIM2_UP; dma_config.Priority = LL_DMA_MODE_NORMAL; - LL_DMA_Init(DMA1, LL_DMA_CHANNEL_2, &dma_config); - LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_2); + LL_DMA_Init(RFID_DMA_CH2_DEF, &dma_config); + LL_DMA_EnableChannel(RFID_DMA_CH2_DEF); // attach interrupt to one of DMA channels - furi_hal_interrupt_set_isr(FuriHalInterruptIdDma1Ch1, furi_hal_rfid_dma_isr, NULL); - LL_DMA_EnableIT_TC(DMA1, LL_DMA_CHANNEL_1); - LL_DMA_EnableIT_HT(DMA1, LL_DMA_CHANNEL_1); + furi_hal_interrupt_set_isr(RFID_DMA_CH1_IRQ, furi_hal_rfid_dma_isr, NULL); + LL_DMA_EnableIT_TC(RFID_DMA_CH1_DEF); + LL_DMA_EnableIT_HT(RFID_DMA_CH1_DEF); // start LL_TIM_EnableAllOutputs(FURI_HAL_RFID_EMULATE_TIMER); @@ -385,14 +397,14 @@ void furi_hal_rfid_tim_emulate_dma_stop() { LL_TIM_DisableCounter(FURI_HAL_RFID_EMULATE_TIMER); LL_TIM_DisableAllOutputs(FURI_HAL_RFID_EMULATE_TIMER); - furi_hal_interrupt_set_isr(FuriHalInterruptIdDma1Ch1, NULL, NULL); - LL_DMA_DisableIT_TC(DMA1, LL_DMA_CHANNEL_1); - LL_DMA_DisableIT_HT(DMA1, LL_DMA_CHANNEL_1); + furi_hal_interrupt_set_isr(RFID_DMA_CH1_IRQ, NULL, NULL); + LL_DMA_DisableIT_TC(RFID_DMA_CH1_DEF); + LL_DMA_DisableIT_HT(RFID_DMA_CH1_DEF); FURI_CRITICAL_ENTER(); - LL_DMA_DeInit(DMA1, LL_DMA_CHANNEL_1); - LL_DMA_DeInit(DMA1, LL_DMA_CHANNEL_2); + LL_DMA_DeInit(RFID_DMA_CH1_DEF); + LL_DMA_DeInit(RFID_DMA_CH2_DEF); LL_TIM_DeInit(FURI_HAL_RFID_EMULATE_TIMER); FURI_CRITICAL_EXIT(); @@ -471,4 +483,4 @@ void COMP_IRQHandler() { (LL_COMP_ReadOutputLevel(COMP1) == LL_COMP_OUTPUT_LEVEL_LOW), furi_hal_rfid_comp_callback_context); } -} +} \ No newline at end of file diff --git a/firmware/targets/f7/furi_hal/furi_hal_spi.c b/firmware/targets/f7/furi_hal/furi_hal_spi.c index 2f9d8708086..8dba8327f5f 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_spi.c +++ b/firmware/targets/f7/furi_hal/furi_hal_spi.c @@ -1,14 +1,33 @@ +#include #include #include #include +#include -#include -#include - +#include #include #include #include +#define TAG "FuriHalSpi" + +#define SPI_DMA DMA2 +#define SPI_DMA_RX_CHANNEL LL_DMA_CHANNEL_3 +#define SPI_DMA_TX_CHANNEL LL_DMA_CHANNEL_4 +#define SPI_DMA_RX_IRQ FuriHalInterruptIdDma2Ch3 +#define SPI_DMA_TX_IRQ FuriHalInterruptIdDma2Ch4 +#define SPI_DMA_RX_DEF SPI_DMA, SPI_DMA_RX_CHANNEL +#define SPI_DMA_TX_DEF SPI_DMA, SPI_DMA_TX_CHANNEL + +// For simplicity, I assume that only one SPI DMA transaction can occur at a time. +static FuriSemaphore* spi_dma_lock = NULL; +static FuriSemaphore* spi_dma_completed = NULL; + +void furi_hal_spi_dma_init() { + spi_dma_lock = furi_semaphore_alloc(1, 1); + spi_dma_completed = furi_semaphore_alloc(1, 1); +} + void furi_hal_spi_bus_init(FuriHalSpiBus* bus) { furi_assert(bus); bus->callback(bus, FuriHalSpiBusEventInit); @@ -149,3 +168,209 @@ bool furi_hal_spi_bus_trx( return ret; } + +static void spi_dma_isr() { +#if SPI_DMA_RX_CHANNEL == LL_DMA_CHANNEL_3 + if(LL_DMA_IsActiveFlag_TC3(SPI_DMA) && LL_DMA_IsEnabledIT_TC(SPI_DMA_RX_DEF)) { + LL_DMA_ClearFlag_TC3(SPI_DMA); + furi_check(furi_semaphore_release(spi_dma_completed) == FuriStatusOk); + } +#else +#error Update this code. Would you kindly? +#endif + +#if SPI_DMA_TX_CHANNEL == LL_DMA_CHANNEL_4 + if(LL_DMA_IsActiveFlag_TC4(SPI_DMA) && LL_DMA_IsEnabledIT_TC(SPI_DMA_TX_DEF)) { + LL_DMA_ClearFlag_TC4(SPI_DMA); + furi_check(furi_semaphore_release(spi_dma_completed) == FuriStatusOk); + } +#else +#error Update this code. Would you kindly? +#endif +} + +bool furi_hal_spi_bus_trx_dma( + FuriHalSpiBusHandle* handle, + uint8_t* tx_buffer, + uint8_t* rx_buffer, + size_t size, + uint32_t timeout_ms) { + furi_assert(handle); + furi_assert(handle->bus->current_handle == handle); + furi_assert(size > 0); + + // If scheduler is not running, use blocking mode + if(xTaskGetSchedulerState() != taskSCHEDULER_RUNNING) { + return furi_hal_spi_bus_trx(handle, tx_buffer, rx_buffer, size, timeout_ms); + } + + // Lock DMA + furi_check(furi_semaphore_acquire(spi_dma_lock, FuriWaitForever) == FuriStatusOk); + + const uint32_t dma_dummy_u32 = 0xFFFFFFFF; + + bool ret = true; + SPI_TypeDef* spi = handle->bus->spi; + uint32_t dma_rx_req; + uint32_t dma_tx_req; + + if(spi == SPI1) { + dma_rx_req = LL_DMAMUX_REQ_SPI1_RX; + dma_tx_req = LL_DMAMUX_REQ_SPI1_TX; + } else if(spi == SPI2) { + dma_rx_req = LL_DMAMUX_REQ_SPI2_RX; + dma_tx_req = LL_DMAMUX_REQ_SPI2_TX; + } else { + furi_crash(NULL); + } + + if(rx_buffer == NULL) { + // Only TX mode, do not use RX channel + + LL_DMA_InitTypeDef dma_config = {0}; + dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (spi->DR); + dma_config.MemoryOrM2MDstAddress = (uint32_t)tx_buffer; + 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_BYTE; + dma_config.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_BYTE; + dma_config.NbData = size; + dma_config.PeriphRequest = dma_tx_req; + dma_config.Priority = LL_DMA_PRIORITY_MEDIUM; + LL_DMA_Init(SPI_DMA_TX_DEF, &dma_config); + +#if SPI_DMA_TX_CHANNEL == LL_DMA_CHANNEL_4 + LL_DMA_ClearFlag_TC4(SPI_DMA); +#else +#error Update this code. Would you kindly? +#endif + + furi_hal_interrupt_set_isr(SPI_DMA_TX_IRQ, spi_dma_isr, NULL); + + bool dma_tx_was_enabled = LL_SPI_IsEnabledDMAReq_TX(spi); + if(!dma_tx_was_enabled) { + LL_SPI_EnableDMAReq_TX(spi); + } + + // acquire semaphore before enabling DMA + furi_check(furi_semaphore_acquire(spi_dma_completed, timeout_ms) == FuriStatusOk); + + LL_DMA_EnableIT_TC(SPI_DMA_TX_DEF); + LL_DMA_EnableChannel(SPI_DMA_TX_DEF); + + // and wait for it to be released (DMA transfer complete) + if(furi_semaphore_acquire(spi_dma_completed, timeout_ms) != FuriStatusOk) { + ret = false; + FURI_LOG_E(TAG, "DMA timeout\r\n"); + } + // release semaphore, because we are using it as a flag + furi_semaphore_release(spi_dma_completed); + + LL_DMA_DisableIT_TC(SPI_DMA_TX_DEF); + LL_DMA_DisableChannel(SPI_DMA_TX_DEF); + if(!dma_tx_was_enabled) { + LL_SPI_DisableDMAReq_TX(spi); + } + furi_hal_interrupt_set_isr(SPI_DMA_TX_IRQ, NULL, NULL); + + LL_DMA_DeInit(SPI_DMA_TX_DEF); + } else { + // TRX or RX mode, use both channels + uint32_t tx_mem_increase_mode; + + if(tx_buffer == NULL) { + // RX mode, use dummy data instead of TX buffer + tx_buffer = (uint8_t*)&dma_dummy_u32; + tx_mem_increase_mode = LL_DMA_PERIPH_NOINCREMENT; + } else { + tx_mem_increase_mode = LL_DMA_MEMORY_INCREMENT; + } + + LL_DMA_InitTypeDef dma_config = {0}; + dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (spi->DR); + dma_config.MemoryOrM2MDstAddress = (uint32_t)tx_buffer; + 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 = tx_mem_increase_mode; + dma_config.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_BYTE; + dma_config.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_BYTE; + dma_config.NbData = size; + dma_config.PeriphRequest = dma_tx_req; + dma_config.Priority = LL_DMA_PRIORITY_MEDIUM; + LL_DMA_Init(SPI_DMA_TX_DEF, &dma_config); + + dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (spi->DR); + dma_config.MemoryOrM2MDstAddress = (uint32_t)rx_buffer; + dma_config.Direction = LL_DMA_DIRECTION_PERIPH_TO_MEMORY; + 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_BYTE; + dma_config.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_BYTE; + dma_config.NbData = size; + dma_config.PeriphRequest = dma_rx_req; + dma_config.Priority = LL_DMA_PRIORITY_MEDIUM; + LL_DMA_Init(SPI_DMA_RX_DEF, &dma_config); + +#if SPI_DMA_RX_CHANNEL == LL_DMA_CHANNEL_3 + LL_DMA_ClearFlag_TC3(SPI_DMA); +#else +#error Update this code. Would you kindly? +#endif + + furi_hal_interrupt_set_isr(SPI_DMA_RX_IRQ, spi_dma_isr, NULL); + + bool dma_tx_was_enabled = LL_SPI_IsEnabledDMAReq_TX(spi); + bool dma_rx_was_enabled = LL_SPI_IsEnabledDMAReq_RX(spi); + + if(!dma_tx_was_enabled) { + LL_SPI_EnableDMAReq_TX(spi); + } + + if(!dma_rx_was_enabled) { + LL_SPI_EnableDMAReq_RX(spi); + } + + // acquire semaphore before enabling DMA + furi_check(furi_semaphore_acquire(spi_dma_completed, timeout_ms) == FuriStatusOk); + + LL_DMA_EnableIT_TC(SPI_DMA_RX_DEF); + LL_DMA_EnableChannel(SPI_DMA_RX_DEF); + LL_DMA_EnableChannel(SPI_DMA_TX_DEF); + + // and wait for it to be released (DMA transfer complete) + if(furi_semaphore_acquire(spi_dma_completed, timeout_ms) != FuriStatusOk) { + ret = false; + FURI_LOG_E(TAG, "DMA timeout\r\n"); + } + // release semaphore, because we are using it as a flag + furi_semaphore_release(spi_dma_completed); + + LL_DMA_DisableIT_TC(SPI_DMA_RX_DEF); + + LL_DMA_DisableChannel(SPI_DMA_TX_DEF); + LL_DMA_DisableChannel(SPI_DMA_RX_DEF); + + if(!dma_tx_was_enabled) { + LL_SPI_DisableDMAReq_TX(spi); + } + + if(!dma_rx_was_enabled) { + LL_SPI_DisableDMAReq_RX(spi); + } + + furi_hal_interrupt_set_isr(SPI_DMA_RX_IRQ, NULL, NULL); + + LL_DMA_DeInit(SPI_DMA_TX_DEF); + LL_DMA_DeInit(SPI_DMA_RX_DEF); + } + + furi_hal_spi_bus_end_txrx(handle, timeout_ms); + + furi_check(furi_semaphore_release(spi_dma_lock) == FuriStatusOk); + + return ret; +} \ No newline at end of file diff --git a/firmware/targets/f7/furi_hal/furi_hal_subghz.c b/firmware/targets/f7/furi_hal/furi_hal_subghz.c index 9bde15c33f2..a6d275308ea 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_subghz.c +++ b/firmware/targets/f7/furi_hal/furi_hal_subghz.c @@ -18,6 +18,14 @@ static uint32_t furi_hal_subghz_debug_gpio_buff[2]; +/* DMA Channels definition */ +#define SUBGHZ_DMA DMA2 +#define SUBGHZ_DMA_CH1_CHANNEL LL_DMA_CHANNEL_1 +#define SUBGHZ_DMA_CH2_CHANNEL LL_DMA_CHANNEL_2 +#define SUBGHZ_DMA_CH1_IRQ FuriHalInterruptIdDma2Ch1 +#define SUBGHZ_DMA_CH1_DEF SUBGHZ_DMA, SUBGHZ_DMA_CH1_CHANNEL +#define SUBGHZ_DMA_CH2_DEF SUBGHZ_DMA, SUBGHZ_DMA_CH2_CHANNEL + typedef struct { volatile SubGhzState state; volatile SubGhzRegulation regulation; @@ -525,8 +533,8 @@ static void furi_hal_subghz_async_tx_refill(uint32_t* buffer, size_t samples) { *buffer = 0; buffer++; samples--; - LL_DMA_DisableIT_HT(DMA1, LL_DMA_CHANNEL_1); - LL_DMA_DisableIT_TC(DMA1, LL_DMA_CHANNEL_1); + LL_DMA_DisableIT_HT(SUBGHZ_DMA_CH1_DEF); + LL_DMA_DisableIT_TC(SUBGHZ_DMA_CH1_DEF); LL_TIM_EnableIT_UPDATE(TIM2); break; } else { @@ -567,17 +575,22 @@ static void furi_hal_subghz_async_tx_refill(uint32_t* buffer, size_t samples) { static void furi_hal_subghz_async_tx_dma_isr() { furi_assert(furi_hal_subghz.state == SubGhzStateAsyncTx); - if(LL_DMA_IsActiveFlag_HT1(DMA1)) { - LL_DMA_ClearFlag_HT1(DMA1); + +#if SUBGHZ_DMA_CH1_CHANNEL == LL_DMA_CHANNEL_1 + if(LL_DMA_IsActiveFlag_HT1(SUBGHZ_DMA)) { + LL_DMA_ClearFlag_HT1(SUBGHZ_DMA); furi_hal_subghz_async_tx_refill( furi_hal_subghz_async_tx.buffer, API_HAL_SUBGHZ_ASYNC_TX_BUFFER_HALF); } - if(LL_DMA_IsActiveFlag_TC1(DMA1)) { - LL_DMA_ClearFlag_TC1(DMA1); + if(LL_DMA_IsActiveFlag_TC1(SUBGHZ_DMA)) { + LL_DMA_ClearFlag_TC1(SUBGHZ_DMA); furi_hal_subghz_async_tx_refill( furi_hal_subghz_async_tx.buffer + API_HAL_SUBGHZ_ASYNC_TX_BUFFER_HALF, API_HAL_SUBGHZ_ASYNC_TX_BUFFER_HALF); } +#else +#error Update this code. Would you kindly? +#endif } static void furi_hal_subghz_async_tx_timer_isr() { @@ -586,7 +599,7 @@ static void furi_hal_subghz_async_tx_timer_isr() { if(LL_TIM_GetAutoReload(TIM2) == 0) { if(furi_hal_subghz.state == SubGhzStateAsyncTx) { furi_hal_subghz.state = SubGhzStateAsyncTxLast; - LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_1); + LL_DMA_DisableChannel(SUBGHZ_DMA_CH1_DEF); } else if(furi_hal_subghz.state == SubGhzStateAsyncTxLast) { furi_hal_subghz.state = SubGhzStateAsyncTxEnd; //forcibly pulls the pin to the ground so that there is no carrier @@ -634,11 +647,11 @@ bool furi_hal_subghz_start_async_tx(FuriHalSubGhzAsyncTxCallback callback, void* dma_config.NbData = API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL; dma_config.PeriphRequest = LL_DMAMUX_REQ_TIM2_UP; dma_config.Priority = LL_DMA_MODE_NORMAL; - LL_DMA_Init(DMA1, LL_DMA_CHANNEL_1, &dma_config); - furi_hal_interrupt_set_isr(FuriHalInterruptIdDma1Ch1, furi_hal_subghz_async_tx_dma_isr, NULL); - LL_DMA_EnableIT_TC(DMA1, LL_DMA_CHANNEL_1); - LL_DMA_EnableIT_HT(DMA1, LL_DMA_CHANNEL_1); - LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_1); + LL_DMA_Init(SUBGHZ_DMA_CH1_DEF, &dma_config); + furi_hal_interrupt_set_isr(SUBGHZ_DMA_CH1_IRQ, furi_hal_subghz_async_tx_dma_isr, NULL); + LL_DMA_EnableIT_TC(SUBGHZ_DMA_CH1_DEF); + LL_DMA_EnableIT_HT(SUBGHZ_DMA_CH1_DEF); + LL_DMA_EnableChannel(SUBGHZ_DMA_CH1_DEF); // Configure TIM2 LL_TIM_InitTypeDef TIM_InitStruct = {0}; @@ -696,9 +709,9 @@ bool furi_hal_subghz_start_async_tx(FuriHalSubGhzAsyncTxCallback callback, void* 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_2, &dma_config); - LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_2, 2); - LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_2); + LL_DMA_Init(SUBGHZ_DMA_CH2_DEF, &dma_config); + LL_DMA_SetDataLength(SUBGHZ_DMA_CH2_DEF, 2); + LL_DMA_EnableChannel(SUBGHZ_DMA_CH2_DEF); } return true; @@ -726,16 +739,16 @@ void furi_hal_subghz_stop_async_tx() { furi_hal_interrupt_set_isr(FuriHalInterruptIdTIM2, NULL, NULL); // Deinitialize DMA - LL_DMA_DeInit(DMA1, LL_DMA_CHANNEL_1); + LL_DMA_DeInit(SUBGHZ_DMA_CH1_DEF); - furi_hal_interrupt_set_isr(FuriHalInterruptIdDma1Ch1, NULL, NULL); + furi_hal_interrupt_set_isr(SUBGHZ_DMA_CH1_IRQ, NULL, NULL); // Deinitialize GPIO furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeAnalog, GpioPullNo, GpioSpeedLow); // Stop debug if(furi_hal_subghz_stop_debug()) { - LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_2); + LL_DMA_DisableChannel(SUBGHZ_DMA_CH2_DEF); } FURI_CRITICAL_EXIT(); diff --git a/firmware/targets/f7/src/update.c b/firmware/targets/f7/src/update.c index b223a5dc372..d8d26eb7cec 100644 --- a/firmware/targets/f7/src/update.c +++ b/firmware/targets/f7/src/update.c @@ -23,8 +23,8 @@ static FATFS* pfs = NULL; } static bool flipper_update_mount_sd() { - for(int i = 0; i < BSP_SD_MaxMountRetryCount(); ++i) { - if(BSP_SD_Init((i % 2) == 0) != MSD_OK) { + for(int i = 0; i < sd_max_mount_retry_count(); ++i) { + if(sd_init((i % 2) == 0) != SdSpiStatusOK) { /* Next attempt will be without card reset, let it settle */ furi_delay_ms(1000); continue; diff --git a/firmware/targets/furi_hal_include/furi_hal_spi.h b/firmware/targets/furi_hal_include/furi_hal_spi.h index ab00ef0d700..79e80931754 100644 --- a/firmware/targets/furi_hal_include/furi_hal_spi.h +++ b/firmware/targets/furi_hal_include/furi_hal_spi.h @@ -16,6 +16,9 @@ void furi_hal_spi_config_deinit_early(); /** Initialize SPI HAL */ void furi_hal_spi_config_init(); +/** Initialize SPI DMA HAL */ +void furi_hal_spi_dma_init(); + /** Initialize SPI Bus * * @param handle pointer to FuriHalSpiBus instance @@ -103,6 +106,23 @@ bool furi_hal_spi_bus_trx( size_t size, uint32_t timeout); +/** SPI Transmit and Receive with DMA + * + * @param handle pointer to FuriHalSpiBusHandle instance + * @param tx_buffer pointer to tx buffer + * @param rx_buffer pointer to rx buffer + * @param size transaction size (buffer size) + * @param timeout_ms operation timeout in ms + * + * @return true on success + */ +bool furi_hal_spi_bus_trx_dma( + FuriHalSpiBusHandle* handle, + uint8_t* tx_buffer, + uint8_t* rx_buffer, + size_t size, + uint32_t timeout_ms); + #ifdef __cplusplus } #endif diff --git a/furi/core/event_flag.c b/furi/core/event_flag.c index 87de65f2df3..9406a581f7b 100644 --- a/furi/core/event_flag.c +++ b/furi/core/event_flag.c @@ -32,7 +32,7 @@ uint32_t furi_event_flag_set(FuriEventFlag* instance, uint32_t flags) { if(FURI_IS_IRQ_MODE()) { yield = pdFALSE; if(xEventGroupSetBitsFromISR(hEventGroup, (EventBits_t)flags, &yield) == pdFAIL) { - rflags = (uint32_t)FuriStatusErrorResource; + rflags = (uint32_t)FuriFlagErrorResource; } else { rflags = flags; portYIELD_FROM_ISR(yield); diff --git a/furi/core/thread.c b/furi/core/thread.c index ef9560b4a5a..9a112d9a87d 100644 --- a/furi/core/thread.c +++ b/furi/core/thread.c @@ -195,6 +195,15 @@ void furi_thread_set_priority(FuriThread* thread, FuriThreadPriority priority) { thread->priority = priority; } +void furi_thread_set_current_priority(FuriThreadPriority priority) { + UBaseType_t new_priority = priority ? priority : FuriThreadPriorityNormal; + vTaskPrioritySet(NULL, new_priority); +} + +FuriThreadPriority furi_thread_get_current_priority() { + return (FuriThreadPriority)uxTaskPriorityGet(NULL); +} + void furi_thread_set_state_callback(FuriThread* thread, FuriThreadStateCallback callback) { furi_assert(thread); furi_assert(thread->state == FuriThreadStateStopped); diff --git a/furi/core/thread.h b/furi/core/thread.h index 1542d5bf016..2675f7cee7d 100644 --- a/furi/core/thread.h +++ b/furi/core/thread.h @@ -122,6 +122,18 @@ void furi_thread_set_context(FuriThread* thread, void* context); */ void furi_thread_set_priority(FuriThread* thread, FuriThreadPriority priority); +/** Set current thread priority + * + * @param priority FuriThreadPriority value + */ +void furi_thread_set_current_priority(FuriThreadPriority priority); + +/** Get current thread priority + * + * @return FuriThreadPriority value + */ +FuriThreadPriority furi_thread_get_current_priority(); + /** Set FuriThread state change callback * * @param thread FuriThread instance From 7a3a1aaf0db35501b6ee5891ad039605285f2fea Mon Sep 17 00:00:00 2001 From: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> Date: Wed, 8 Feb 2023 08:40:44 +0300 Subject: [PATCH 381/824] [FL-3057] Allow use of any suitable pin for 1-Wire devices (#2350) * Add 1-wire thermometer example app stub * Working 1-wire thermometer app * Refactor app to use threads * Clean up code, add comments * Add CRC checking * Increase update period * Fix error in fbt * Revert the old update period * Use settable pin in onewire_host * Use settable pin for onewire_slave * Clear EXTI flag after callback, make private methods static in onewire_slave * Do not hardcode GPIO pin number * Remove iButton hal from furi_hal_rfid * Remove most of furi_hal_ibutton * Add some of furi_hal_ibutton back * Slightly neater code * Fix formatting * Fix PVS-studio warnings * Update CODEOWNERS * Add furi_hal_gpio_get_ext_pin_number * Create README.md * FuriHal: move furi_hal_gpio_get_ext_pin_number to resources --------- Co-authored-by: Aleksandr Kutuzov --- .github/CODEOWNERS | 2 + .../examples/example_thermo/README.md | 44 +++ .../examples/example_thermo/application.fam | 10 + .../examples/example_thermo/example_thermo.c | 356 ++++++++++++++++++ .../example_thermo/example_thermo_10px.png | Bin 0 -> 7293 bytes applications/main/ibutton/ibutton_cli.c | 2 +- firmware/targets/f18/api_symbols.csv | 3 +- .../targets/f18/furi_hal/furi_hal_resources.c | 24 ++ .../targets/f18/furi_hal/furi_hal_resources.h | 7 + firmware/targets/f7/api_symbols.csv | 20 +- firmware/targets/f7/furi_hal/furi_hal_gpio.c | 33 +- .../targets/f7/furi_hal/furi_hal_ibutton.c | 43 +-- .../targets/f7/furi_hal/furi_hal_ibutton.h | 62 +-- .../targets/f7/furi_hal/furi_hal_resources.c | 23 ++ .../targets/f7/furi_hal/furi_hal_resources.h | 7 + firmware/targets/f7/furi_hal/furi_hal_rfid.c | 10 +- lib/one_wire/ibutton/ibutton_worker.c | 4 +- lib/one_wire/ibutton/ibutton_worker_modes.c | 13 +- lib/one_wire/one_wire_host.c | 48 ++- lib/one_wire/one_wire_host.h | 6 +- lib/one_wire/one_wire_slave.c | 51 +-- lib/one_wire/one_wire_slave.h | 4 +- 22 files changed, 590 insertions(+), 182 deletions(-) create mode 100644 applications/examples/example_thermo/README.md create mode 100644 applications/examples/example_thermo/application.fam create mode 100644 applications/examples/example_thermo/example_thermo.c create mode 100644 applications/examples/example_thermo/example_thermo_10px.png diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index aa4ada1a81a..69f8289f9e3 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -42,6 +42,8 @@ /applications/debug/unit_tests/ @skotopes @DrZlo13 @hedger @nminaylov @gornekich @Astrrra @gsurkov @Skorpionm +/applications/examples/example_thermo/ @skotopes @DrZlo13 @hedger @gsurkov + # Assets /assets/resources/infrared/ @skotopes @DrZlo13 @hedger @gsurkov diff --git a/applications/examples/example_thermo/README.md b/applications/examples/example_thermo/README.md new file mode 100644 index 00000000000..fa00264dc15 --- /dev/null +++ b/applications/examples/example_thermo/README.md @@ -0,0 +1,44 @@ +# 1-Wire Thermometer +This example application demonstrates the use of the 1-Wire library with a DS18B20 thermometer. +It also covers basic GUI, input handling, threads and localisation. + +## Electrical connections +Before launching the application, connect the sensor to Flipper's external GPIO according to the table below: +| DS18B20 | Flipper | +| :-----: | :-----: | +| VDD | 9 | +| GND | 18 | +| DQ | 17 | + +*NOTE 1*: GND is also available on pins 8 and 11. + +*NOTE 2*: For any other pin than 17, connect an external 4.7k pull-up resistor to pin 9. + +## Launching the application +In order to launch this demo, follow the steps below: +1. Make sure your Flipper has an SD card installed. +2. Connect your Flipper to the computer via a USB cable. +3. Run `./fbt launch_app APPSRC=example_thermo` in your terminal emulator of choice. + +## Changing the data pin +It is possible to use other GPIO pin as a 1-Wire data pin. In order to change it, set the `THERMO_GPIO_PIN` macro to any of the options listed below: + +```c +/* Possible GPIO pin choices: + - gpio_ext_pc0 + - gpio_ext_pc1 + - gpio_ext_pc3 + - gpio_ext_pb2 + - gpio_ext_pb3 + - gpio_ext_pa4 + - gpio_ext_pa6 + - gpio_ext_pa7 + - ibutton_gpio +*/ + +#define THERMO_GPIO_PIN (ibutton_gpio) +``` +Do not forget about the external pull-up resistor as these pins do not have one built-in. + +With the changes been made, recompile and launch the application again. +The on-screen text should reflect it by asking to connect the thermometer to another pin. diff --git a/applications/examples/example_thermo/application.fam b/applications/examples/example_thermo/application.fam new file mode 100644 index 00000000000..b4a05c7f96f --- /dev/null +++ b/applications/examples/example_thermo/application.fam @@ -0,0 +1,10 @@ +App( + appid="example_thermo", + name="Example: Thermometer", + apptype=FlipperAppType.EXTERNAL, + entry_point="example_thermo_main", + requires=["gui"], + stack_size=1 * 1024, + fap_icon="example_thermo_10px.png", + fap_category="Examples", +) diff --git a/applications/examples/example_thermo/example_thermo.c b/applications/examples/example_thermo/example_thermo.c new file mode 100644 index 00000000000..b3bc7cd9906 --- /dev/null +++ b/applications/examples/example_thermo/example_thermo.c @@ -0,0 +1,356 @@ +/* + * This file contains an example application that reads and displays + * the temperature from a DS18B20 1-wire thermometer. + * + * It also covers basic GUI, input handling, threads and localisation. + * + * References: + * [1] DS18B20 Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/DS18B20.pdf + */ + +#include +#include + +#include +#include + +#include + +#include +#include + +#define UPDATE_PERIOD_MS 1000UL +#define TEXT_STORE_SIZE 64U + +#define DS18B20_CMD_CONVERT 0x44U +#define DS18B20_CMD_READ_SCRATCHPAD 0xbeU + +#define DS18B20_CFG_RESOLUTION_POS 5U +#define DS18B20_CFG_RESOLUTION_MASK 0x03U +#define DS18B20_DECIMAL_PART_MASK 0x0fU + +#define DS18B20_SIGN_MASK 0xf0U + +/* Possible GPIO pin choices: + - gpio_ext_pc0 + - gpio_ext_pc1 + - gpio_ext_pc3 + - gpio_ext_pb2 + - gpio_ext_pb3 + - gpio_ext_pa4 + - gpio_ext_pa6 + - gpio_ext_pa7 + - ibutton_gpio +*/ + +#define THERMO_GPIO_PIN (ibutton_gpio) + +/* Flags which the reader thread responds to */ +typedef enum { + ReaderThreadFlagExit = 1, +} ReaderThreadFlag; + +typedef union { + struct { + uint8_t temp_lsb; /* Least significant byte of the temperature */ + uint8_t temp_msb; /* Most significant byte of the temperature */ + uint8_t user_alarm_high; /* User register 1 (Temp high alarm) */ + uint8_t user_alarm_low; /* User register 2 (Temp low alarm) */ + uint8_t config; /* Configuration register */ + uint8_t reserved[3]; /* Not used */ + uint8_t crc; /* CRC checksum for error detection */ + } fields; + uint8_t bytes[9]; +} DS18B20Scratchpad; + +/* Application context structure */ +typedef struct { + Gui* gui; + ViewPort* view_port; + FuriThread* reader_thread; + FuriMessageQueue* event_queue; + OneWireHost* onewire; + float temp_celsius; + bool has_device; +} ExampleThermoContext; + +/*************** 1-Wire Communication and Processing *****************/ + +/* Commands the thermometer to begin measuring the temperature. */ +static void example_thermo_request_temperature(ExampleThermoContext* context) { + OneWireHost* onewire = context->onewire; + + /* All 1-wire transactions must happen in a critical section, i.e + not interrupted by other threads. */ + FURI_CRITICAL_ENTER(); + + bool success = false; + do { + /* Each communication with a 1-wire device starts by a reset. + The functon will return true if a device responded with a presence pulse. */ + if(!onewire_host_reset(onewire)) break; + /* After the reset, a ROM operation must follow. + If there is only one device connected, the "Skip ROM" command is most appropriate + (it can also be used to address all of the connected devices in some cases).*/ + onewire_host_skip(onewire); + /* After the ROM operation, a device-specific command is issued. + In this case, it's a request to start measuring the temperature. */ + onewire_host_write(onewire, DS18B20_CMD_CONVERT); + + success = true; + } while(false); + + context->has_device = success; + + FURI_CRITICAL_EXIT(); +} + +/* Reads the measured temperature from the thermometer. */ +static void example_thermo_read_temperature(ExampleThermoContext* context) { + /* If there was no device detected, don't try to read the temperature */ + if(!context->has_device) { + return; + } + + OneWireHost* onewire = context->onewire; + + /* All 1-wire transactions must happen in a critical section, i.e + not interrupted by other threads. */ + FURI_CRITICAL_ENTER(); + + bool success = false; + + do { + DS18B20Scratchpad buf; + + /* Attempt reading the temperature 10 times before giving up */ + size_t attempts_left = 10; + do { + /* Each communication with a 1-wire device starts by a reset. + The functon will return true if a device responded with a presence pulse. */ + if(!onewire_host_reset(onewire)) continue; + + /* After the reset, a ROM operation must follow. + If there is only one device connected, the "Skip ROM" command is most appropriate + (it can also be used to address all of the connected devices in some cases).*/ + onewire_host_skip(onewire); + + /* After the ROM operation, a device-specific command is issued. + This time, it will be the "Read Scratchpad" command which will + prepare the device's internal buffer memory for reading. */ + onewire_host_write(onewire, DS18B20_CMD_READ_SCRATCHPAD); + + /* The actual reading happens here. A total of 9 bytes is read. */ + onewire_host_read_bytes(onewire, buf.bytes, sizeof(buf.bytes)); + + /* Calculate the checksum and compare it with one provided by the device. */ + const uint8_t crc = maxim_crc8(buf.bytes, sizeof(buf.bytes) - 1, MAXIM_CRC8_INIT); + + /* Checksums match, exit the loop */ + if(crc == buf.fields.crc) break; + + } while(--attempts_left); + + if(attempts_left == 0) break; + + /* Get the measurement resolution from the configuration register. (See [1] page 9) */ + const uint8_t resolution_mode = (buf.fields.config >> DS18B20_CFG_RESOLUTION_POS) & + DS18B20_CFG_RESOLUTION_MASK; + + /* Generate a mask for undefined bits in the decimal part. (See [1] page 6) */ + const uint8_t decimal_mask = + (DS18B20_DECIMAL_PART_MASK << (DS18B20_CFG_RESOLUTION_MASK - resolution_mode)) & + DS18B20_DECIMAL_PART_MASK; + + /* Get the integer and decimal part of the temperature (See [1] page 6) */ + const uint8_t integer_part = (buf.fields.temp_msb << 4U) | (buf.fields.temp_lsb >> 4U); + const uint8_t decimal_part = buf.fields.temp_lsb & decimal_mask; + + /* Calculate the sign of the temperature (See [1] page 6) */ + const bool is_negative = (buf.fields.temp_msb & DS18B20_SIGN_MASK) != 0; + + /* Combine the integer and decimal part together */ + const float temp_celsius_abs = integer_part + decimal_part / 16.f; + + /* Set the appropriate sign */ + context->temp_celsius = is_negative ? -temp_celsius_abs : temp_celsius_abs; + + success = true; + } while(false); + + context->has_device = success; + + FURI_CRITICAL_EXIT(); +} + +/* Periodically requests measurements and reads temperature. This function runs in a separare thread. */ +static int32_t example_thermo_reader_thread_callback(void* ctx) { + ExampleThermoContext* context = ctx; + + for(;;) { + /* Tell the termometer to start measuring the temperature. The process may take up to 750ms. */ + example_thermo_request_temperature(context); + + /* Wait for the measurement to finish. At the same time wait for an exit signal. */ + const uint32_t flags = + furi_thread_flags_wait(ReaderThreadFlagExit, FuriFlagWaitAny, UPDATE_PERIOD_MS); + + /* If an exit signal was received, return from this thread. */ + if(flags != (unsigned)FuriFlagErrorTimeout) break; + + /* The measurement is now ready, read it from the termometer. */ + example_thermo_read_temperature(context); + } + + return 0; +} + +/*************** GUI, Input and Main Loop *****************/ + +/* Draw the GUI of the application. The screen is completely redrawn during each call. */ +static void example_thermo_draw_callback(Canvas* canvas, void* ctx) { + ExampleThermoContext* context = ctx; + char text_store[TEXT_STORE_SIZE]; + const size_t middle_x = canvas_width(canvas) / 2U; + + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, middle_x, 12, AlignCenter, AlignBottom, "Thermometer Demo"); + canvas_draw_line(canvas, 0, 16, 128, 16); + + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned( + canvas, middle_x, 30, AlignCenter, AlignBottom, "Connnect thermometer"); + + snprintf( + text_store, + TEXT_STORE_SIZE, + "to GPIO pin %ld", + furi_hal_resources_get_ext_pin_number(&THERMO_GPIO_PIN)); + canvas_draw_str_aligned(canvas, middle_x, 42, AlignCenter, AlignBottom, text_store); + + canvas_set_font(canvas, FontKeyboard); + + if(context->has_device) { + float temp; + char temp_units; + + /* The applicaton is locale-aware. + Change Settings->System->Units to check it out. */ + switch(locale_get_measurement_unit()) { + case LocaleMeasurementUnitsMetric: + temp = context->temp_celsius; + temp_units = 'C'; + break; + case LocaleMeasurementUnitsImperial: + temp = locale_celsius_to_fahrenheit(context->temp_celsius); + temp_units = 'F'; + break; + default: + furi_crash("Illegal measurement units"); + } + /* If a reading is available, display it */ + snprintf(text_store, TEXT_STORE_SIZE, "Temperature: %+.1f%c", (double)temp, temp_units); + } else { + /* Or show a message that no data is available */ + strncpy(text_store, "-- No data --", TEXT_STORE_SIZE); + } + + canvas_draw_str_aligned(canvas, middle_x, 58, AlignCenter, AlignBottom, text_store); +} + +/* This function is called from the GUI thread. All it does is put the event + into the application's queue so it can be processed later. */ +static void example_thermo_input_callback(InputEvent* event, void* ctx) { + ExampleThermoContext* context = ctx; + furi_message_queue_put(context->event_queue, event, FuriWaitForever); +} + +/* Starts the reader thread and handles the input */ +static void example_thermo_run(ExampleThermoContext* context) { + /* Configure the hardware in host mode */ + onewire_host_start(context->onewire); + + /* Start the reader thread. It will talk to the thermometer in the background. */ + furi_thread_start(context->reader_thread); + + /* An endless loop which handles the input*/ + for(bool is_running = true; is_running;) { + InputEvent event; + /* Wait for an input event. Input events come from the GUI thread via a callback. */ + const FuriStatus status = + furi_message_queue_get(context->event_queue, &event, FuriWaitForever); + + /* This application is only interested in short button presses. */ + if((status != FuriStatusOk) || (event.type != InputTypeShort)) { + continue; + } + + /* When the user presses the "Back" button, break the loop and exit the application. */ + if(event.key == InputKeyBack) { + is_running = false; + } + } + + /* Signal the reader thread to cease operation and exit */ + furi_thread_flags_set(furi_thread_get_id(context->reader_thread), ReaderThreadFlagExit); + + /* Wait for the reader thread to finish */ + furi_thread_join(context->reader_thread); + + /* Reset the hardware */ + onewire_host_stop(context->onewire); +} + +/******************** Initialisation & startup *****************************/ + +/* Allocate the memory and initialise the variables */ +static ExampleThermoContext* example_thermo_context_alloc() { + ExampleThermoContext* context = malloc(sizeof(ExampleThermoContext)); + + context->view_port = view_port_alloc(); + view_port_draw_callback_set(context->view_port, example_thermo_draw_callback, context); + view_port_input_callback_set(context->view_port, example_thermo_input_callback, context); + + context->event_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); + + context->reader_thread = furi_thread_alloc(); + furi_thread_set_stack_size(context->reader_thread, 1024U); + furi_thread_set_context(context->reader_thread, context); + furi_thread_set_callback(context->reader_thread, example_thermo_reader_thread_callback); + + context->gui = furi_record_open(RECORD_GUI); + gui_add_view_port(context->gui, context->view_port, GuiLayerFullscreen); + + context->onewire = onewire_host_alloc(&THERMO_GPIO_PIN); + + return context; +} + +/* Release the unused resources and deallocate memory */ +static void example_thermo_context_free(ExampleThermoContext* context) { + view_port_enabled_set(context->view_port, false); + gui_remove_view_port(context->gui, context->view_port); + + onewire_host_free(context->onewire); + furi_thread_free(context->reader_thread); + furi_message_queue_free(context->event_queue); + view_port_free(context->view_port); + + furi_record_close(RECORD_GUI); +} + +/* The application's entry point. Execution starts from here. */ +int32_t example_thermo_main(void* p) { + UNUSED(p); + + /* Allocate all of the necessary structures */ + ExampleThermoContext* context = example_thermo_context_alloc(); + + /* Start the applicaton's main loop. It won't return until the application was requested to exit. */ + example_thermo_run(context); + + /* Release all unneeded resources */ + example_thermo_context_free(context); + + return 0; +} diff --git a/applications/examples/example_thermo/example_thermo_10px.png b/applications/examples/example_thermo/example_thermo_10px.png new file mode 100644 index 0000000000000000000000000000000000000000..3d527f306c2d325fc72380856f14860f51b3742c GIT binary patch literal 7293 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 literal 0 HcmV?d00001 diff --git a/applications/main/ibutton/ibutton_cli.c b/applications/main/ibutton/ibutton_cli.c index fab1ccf05ab..9ddb079dc9c 100644 --- a/applications/main/ibutton/ibutton_cli.c +++ b/applications/main/ibutton/ibutton_cli.c @@ -271,7 +271,7 @@ void onewire_cli_print_usage() { static void onewire_cli_search(Cli* cli) { UNUSED(cli); - OneWireHost* onewire = onewire_host_alloc(); + OneWireHost* onewire = onewire_host_alloc(&ibutton_gpio); uint8_t address[8]; bool done = false; diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index ff6dbc2391a..025b605fc94 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,12.2,, +Version,+,13.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -910,6 +910,7 @@ Function,-,furi_hal_flash_write_dword,void,"size_t, uint64_t" Function,+,furi_hal_gpio_add_int_callback,void,"const GpioPin*, GpioExtiCallback, void*" Function,+,furi_hal_gpio_disable_int_callback,void,const GpioPin* Function,+,furi_hal_gpio_enable_int_callback,void,const GpioPin* +Function,+,furi_hal_resources_get_ext_pin_number,int32_t,const GpioPin* Function,+,furi_hal_gpio_init,void,"const GpioPin*, const GpioMode, const GpioPull, const GpioSpeed" Function,+,furi_hal_gpio_init_ex,void,"const GpioPin*, const GpioMode, const GpioPull, const GpioSpeed, const GpioAltFn" Function,+,furi_hal_gpio_init_simple,void,"const GpioPin*, const GpioMode" diff --git a/firmware/targets/f18/furi_hal/furi_hal_resources.c b/firmware/targets/f18/furi_hal/furi_hal_resources.c index dafeefdd0fc..41cc80bfba1 100644 --- a/firmware/targets/f18/furi_hal/furi_hal_resources.c +++ b/firmware/targets/f18/furi_hal/furi_hal_resources.c @@ -199,3 +199,27 @@ void furi_hal_resources_init() { NVIC_SetPriority(EXTI15_10_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 5, 0)); NVIC_EnableIRQ(EXTI15_10_IRQn); } + +int32_t furi_hal_resources_get_ext_pin_number(const GpioPin* gpio) { + // TODO: describe second ROW + if(gpio == &gpio_ext_pa7) + return 2; + else if(gpio == &gpio_ext_pa6) + return 3; + else if(gpio == &gpio_ext_pa4) + return 4; + else if(gpio == &gpio_ext_pb3) + return 5; + else if(gpio == &gpio_ext_pb2) + return 6; + else if(gpio == &gpio_ext_pc3) + return 7; + else if(gpio == &gpio_ext_pc1) + return 15; + else if(gpio == &gpio_ext_pc0) + return 16; + else if(gpio == &ibutton_gpio) + return 17; + else + return -1; +} diff --git a/firmware/targets/f18/furi_hal/furi_hal_resources.h b/firmware/targets/f18/furi_hal/furi_hal_resources.h index ef2cdae7f8e..a24afbf9410 100644 --- a/firmware/targets/f18/furi_hal/furi_hal_resources.h +++ b/firmware/targets/f18/furi_hal/furi_hal_resources.h @@ -111,6 +111,13 @@ void furi_hal_resources_deinit_early(); void furi_hal_resources_init(); +/** + * Get a corresponding external connector pin number for a gpio + * @param gpio GpioPin + * @return pin number or -1 if gpio is not on the external connector + */ +int32_t furi_hal_resources_get_ext_pin_number(const GpioPin* gpio); + #ifdef __cplusplus } #endif diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index a1f34f10608..35985aebb97 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,12.2,, +Version,+,13.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -1094,6 +1094,7 @@ Function,-,furi_hal_flash_write_dword,void,"size_t, uint64_t" Function,+,furi_hal_gpio_add_int_callback,void,"const GpioPin*, GpioExtiCallback, void*" Function,+,furi_hal_gpio_disable_int_callback,void,const GpioPin* Function,+,furi_hal_gpio_enable_int_callback,void,const GpioPin* +Function,+,furi_hal_resources_get_ext_pin_number,int32_t,const GpioPin* Function,+,furi_hal_gpio_init,void,"const GpioPin*, const GpioMode, const GpioPull, const GpioSpeed" Function,+,furi_hal_gpio_init_ex,void,"const GpioPin*, const GpioMode, const GpioPull, const GpioSpeed, const GpioAltFn" Function,+,furi_hal_gpio_init_simple,void,"const GpioPin*, const GpioMode" @@ -1129,20 +1130,13 @@ Function,+,furi_hal_i2c_tx,_Bool,"FuriHalI2cBusHandle*, const uint8_t, const uin Function,+,furi_hal_i2c_write_mem,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint8_t*, uint8_t, uint32_t" Function,+,furi_hal_i2c_write_reg_16,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint16_t, uint32_t" Function,+,furi_hal_i2c_write_reg_8,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint8_t, uint32_t" -Function,+,furi_hal_ibutton_add_interrupt,void,"GpioExtiCallback, void*" Function,+,furi_hal_ibutton_emulate_set_next,void,uint32_t Function,+,furi_hal_ibutton_emulate_start,void,"uint32_t, FuriHalIbuttonEmulateCallback, void*" Function,+,furi_hal_ibutton_emulate_stop,void, Function,-,furi_hal_ibutton_init,void, -Function,+,furi_hal_ibutton_pin_get_level,_Bool, -Function,+,furi_hal_ibutton_pin_high,void, -Function,+,furi_hal_ibutton_pin_low,void, -Function,+,furi_hal_ibutton_remove_interrupt,void, -Function,+,furi_hal_ibutton_start_drive,void, -Function,+,furi_hal_ibutton_start_drive_in_isr,void, -Function,+,furi_hal_ibutton_start_interrupt,void, -Function,+,furi_hal_ibutton_start_interrupt_in_isr,void, -Function,+,furi_hal_ibutton_stop,void, +Function,+,furi_hal_ibutton_pin_configure,void, +Function,+,furi_hal_ibutton_pin_reset,void, +Function,+,furi_hal_ibutton_pin_write,void,const _Bool Function,+,furi_hal_info_get,void,"PropertyValueCallback, char, void*" Function,+,furi_hal_infrared_async_rx_set_capture_isr_callback,void,"FuriHalInfraredRxCaptureCallback, void*" Function,+,furi_hal_infrared_async_rx_set_timeout,void,uint32_t @@ -2040,7 +2034,7 @@ Function,+,onewire_device_detach,void,OneWireDevice* Function,+,onewire_device_free,void,OneWireDevice* Function,+,onewire_device_get_id_p,uint8_t*,OneWireDevice* Function,+,onewire_device_send_id,void,OneWireDevice* -Function,+,onewire_host_alloc,OneWireHost*, +Function,+,onewire_host_alloc,OneWireHost*,const GpioPin* Function,+,onewire_host_free,void,OneWireHost* Function,+,onewire_host_read,uint8_t,OneWireHost* Function,+,onewire_host_read_bit,_Bool,OneWireHost* @@ -2054,7 +2048,7 @@ Function,+,onewire_host_stop,void,OneWireHost* Function,+,onewire_host_target_search,void,"OneWireHost*, uint8_t" Function,+,onewire_host_write,void,"OneWireHost*, uint8_t" Function,+,onewire_host_write_bit,void,"OneWireHost*, _Bool" -Function,+,onewire_slave_alloc,OneWireSlave*, +Function,+,onewire_slave_alloc,OneWireSlave*,const GpioPin* Function,+,onewire_slave_attach,void,"OneWireSlave*, OneWireDevice*" Function,+,onewire_slave_detach,void,OneWireSlave* Function,+,onewire_slave_free,void,OneWireSlave* diff --git a/firmware/targets/f7/furi_hal/furi_hal_gpio.c b/firmware/targets/f7/furi_hal/furi_hal_gpio.c index a148a3af294..e8318afcf59 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_gpio.c +++ b/firmware/targets/f7/furi_hal/furi_hal_gpio.c @@ -1,6 +1,7 @@ #include #include #include +#include #include #define GET_SYSCFG_EXTI_PORT(gpio) \ @@ -224,85 +225,85 @@ static void furi_hal_gpio_int_call(uint16_t pin_num) { /* Interrupt handlers */ void EXTI0_IRQHandler(void) { if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_0)) { - LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_0); furi_hal_gpio_int_call(0); + LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_0); } } void EXTI1_IRQHandler(void) { if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_1)) { - LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_1); furi_hal_gpio_int_call(1); + LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_1); } } void EXTI2_IRQHandler(void) { if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_2)) { - LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_2); furi_hal_gpio_int_call(2); + LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_2); } } void EXTI3_IRQHandler(void) { if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_3)) { - LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_3); furi_hal_gpio_int_call(3); + LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_3); } } void EXTI4_IRQHandler(void) { if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_4)) { - LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_4); furi_hal_gpio_int_call(4); + LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_4); } } void EXTI9_5_IRQHandler(void) { if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_5)) { - LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_5); furi_hal_gpio_int_call(5); + LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_5); } if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_6)) { - LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_6); furi_hal_gpio_int_call(6); + LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_6); } if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_7)) { - LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_7); furi_hal_gpio_int_call(7); + LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_7); } if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_8)) { - LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_8); furi_hal_gpio_int_call(8); + LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_8); } if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_9)) { - LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_9); furi_hal_gpio_int_call(9); + LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_9); } } void EXTI15_10_IRQHandler(void) { if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_10)) { - LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_10); furi_hal_gpio_int_call(10); + LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_10); } if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_11)) { - LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_11); furi_hal_gpio_int_call(11); + LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_11); } if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_12)) { - LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_12); furi_hal_gpio_int_call(12); + LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_12); } if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_13)) { - LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_13); furi_hal_gpio_int_call(13); + LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_13); } if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_14)) { - LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_14); furi_hal_gpio_int_call(14); + LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_14); } if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_15)) { - LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_15); furi_hal_gpio_int_call(15); + LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_15); } } diff --git a/firmware/targets/f7/furi_hal/furi_hal_ibutton.c b/firmware/targets/f7/furi_hal/furi_hal_ibutton.c index 0375893e7ae..c05cd69a825 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_ibutton.c +++ b/firmware/targets/f7/furi_hal/furi_hal_ibutton.c @@ -89,47 +89,16 @@ void furi_hal_ibutton_emulate_stop() { } } -void furi_hal_ibutton_start_drive() { - furi_hal_ibutton_pin_high(); - furi_hal_gpio_init(&ibutton_gpio, GpioModeOutputOpenDrain, GpioPullNo, GpioSpeedLow); -} - -void furi_hal_ibutton_start_drive_in_isr() { +void furi_hal_ibutton_pin_configure() { + furi_hal_gpio_write(&ibutton_gpio, true); furi_hal_gpio_init(&ibutton_gpio, GpioModeOutputOpenDrain, GpioPullNo, GpioSpeedLow); - LL_EXTI_ClearFlag_0_31(ibutton_gpio.pin); -} - -void furi_hal_ibutton_start_interrupt() { - furi_hal_ibutton_pin_high(); - furi_hal_gpio_init(&ibutton_gpio, GpioModeInterruptRiseFall, GpioPullNo, GpioSpeedLow); -} - -void furi_hal_ibutton_start_interrupt_in_isr() { - furi_hal_gpio_init(&ibutton_gpio, GpioModeInterruptRiseFall, GpioPullNo, GpioSpeedLow); - LL_EXTI_ClearFlag_0_31(ibutton_gpio.pin); -} - -void furi_hal_ibutton_stop() { - furi_hal_ibutton_pin_high(); - furi_hal_gpio_init(&ibutton_gpio, GpioModeAnalog, GpioPullNo, GpioSpeedLow); } -void furi_hal_ibutton_add_interrupt(GpioExtiCallback cb, void* context) { - furi_hal_gpio_add_int_callback(&ibutton_gpio, cb, context); -} - -void furi_hal_ibutton_remove_interrupt() { - furi_hal_gpio_remove_int_callback(&ibutton_gpio); -} - -void furi_hal_ibutton_pin_low() { - furi_hal_gpio_write(&ibutton_gpio, false); -} - -void furi_hal_ibutton_pin_high() { +void furi_hal_ibutton_pin_reset() { furi_hal_gpio_write(&ibutton_gpio, true); + furi_hal_gpio_init(&ibutton_gpio, GpioModeAnalog, GpioPullNo, GpioSpeedLow); } -bool furi_hal_ibutton_pin_get_level() { - return furi_hal_gpio_read(&ibutton_gpio); +void furi_hal_ibutton_pin_write(const bool state) { + furi_hal_gpio_write(&ibutton_gpio, state); } diff --git a/firmware/targets/f7/furi_hal/furi_hal_ibutton.h b/firmware/targets/f7/furi_hal/furi_hal_ibutton.h index fb57d628be7..b723fe17686 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_ibutton.h +++ b/firmware/targets/f7/furi_hal/furi_hal_ibutton.h @@ -7,7 +7,6 @@ #include #include -#include #ifdef __cplusplus extern "C" { @@ -18,70 +17,43 @@ typedef void (*FuriHalIbuttonEmulateCallback)(void* context); /** Initialize */ void furi_hal_ibutton_init(); +/** + * Start emulation timer + * @param period timer period + * @param callback timer callback + * @param context callback context + */ void furi_hal_ibutton_emulate_start( uint32_t period, FuriHalIbuttonEmulateCallback callback, void* context); -void furi_hal_ibutton_emulate_set_next(uint32_t period); - -void furi_hal_ibutton_emulate_stop(); - /** - * Sets the pin to normal mode (open collector), and sets it to float + * Update emulation timer period + * @param period new timer period */ -void furi_hal_ibutton_start_drive(); - -/** - * Sets the pin to normal mode (open collector), and clears pin EXTI interrupt. - * Used in EXTI interrupt context. - */ -void furi_hal_ibutton_start_drive_in_isr(); +void furi_hal_ibutton_emulate_set_next(uint32_t period); /** - * Sets the pin to interrupt mode (EXTI interrupt on rise or fall), and sets it to float + * Stop emulation timer */ -void furi_hal_ibutton_start_interrupt(); +void furi_hal_ibutton_emulate_stop(); /** - * Sets the pin to interrupt mode (EXTI interrupt on rise or fall), and clears pin EXTI interrupt. - * Used in EXTI interrupt context. + * Set the pin to normal mode (open collector), and sets it to float */ -void furi_hal_ibutton_start_interrupt_in_isr(); +void furi_hal_ibutton_pin_configure(); /** * Sets the pin to analog mode, and sets it to float */ -void furi_hal_ibutton_stop(); - -/** - * Attach interrupt callback to iButton pin - * @param cb callback - * @param context context - */ -void furi_hal_ibutton_add_interrupt(GpioExtiCallback cb, void* context); - -/** - * Remove interrupt callback from iButton pin - */ -void furi_hal_ibutton_remove_interrupt(); - -/** - * Sets the pin to low - */ -void furi_hal_ibutton_pin_low(); - -/** - * Sets the pin to high (float in iButton pin modes) - */ -void furi_hal_ibutton_pin_high(); +void furi_hal_ibutton_pin_reset(); /** - * Get pin level - * @return true if level is high - * @return false if level is low + * iButton write pin + * @param state true / false */ -bool furi_hal_ibutton_pin_get_level(); +void furi_hal_ibutton_pin_write(const bool state); #ifdef __cplusplus } diff --git a/firmware/targets/f7/furi_hal/furi_hal_resources.c b/firmware/targets/f7/furi_hal/furi_hal_resources.c index 03cc6e714c9..c0eb9ee67c1 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_resources.c +++ b/firmware/targets/f7/furi_hal/furi_hal_resources.c @@ -191,3 +191,26 @@ void furi_hal_resources_init() { NVIC_SetPriority(EXTI15_10_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 5, 0)); NVIC_EnableIRQ(EXTI15_10_IRQn); } + +int32_t furi_hal_resources_get_ext_pin_number(const GpioPin* gpio) { + if(gpio == &gpio_ext_pa7) + return 2; + else if(gpio == &gpio_ext_pa6) + return 3; + else if(gpio == &gpio_ext_pa4) + return 4; + else if(gpio == &gpio_ext_pb3) + return 5; + else if(gpio == &gpio_ext_pb2) + return 6; + else if(gpio == &gpio_ext_pc3) + return 7; + else if(gpio == &gpio_ext_pc1) + return 15; + else if(gpio == &gpio_ext_pc0) + return 16; + else if(gpio == &ibutton_gpio) + return 17; + else + return -1; +} diff --git a/firmware/targets/f7/furi_hal/furi_hal_resources.h b/firmware/targets/f7/furi_hal/furi_hal_resources.h index 51ea7bcc142..04b99a84e19 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_resources.h +++ b/firmware/targets/f7/furi_hal/furi_hal_resources.h @@ -216,6 +216,13 @@ void furi_hal_resources_deinit_early(); void furi_hal_resources_init(); +/** + * Get a corresponding external connector pin number for a gpio + * @param gpio GpioPin + * @return pin number or -1 if gpio is not on the external connector + */ +int32_t furi_hal_resources_get_ext_pin_number(const GpioPin* gpio); + #ifdef __cplusplus } #endif diff --git a/firmware/targets/f7/furi_hal/furi_hal_rfid.c b/firmware/targets/f7/furi_hal/furi_hal_rfid.c index b0238f79f23..145e49df2c1 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_rfid.c +++ b/firmware/targets/f7/furi_hal/furi_hal_rfid.c @@ -77,7 +77,7 @@ void furi_hal_rfid_init() { void furi_hal_rfid_pins_reset() { // ibutton bus disable - furi_hal_ibutton_stop(); + furi_hal_ibutton_pin_reset(); // pulldown rfid antenna furi_hal_gpio_init(&gpio_rfid_carrier_out, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); @@ -94,8 +94,8 @@ void furi_hal_rfid_pins_reset() { void furi_hal_rfid_pins_emulate() { // ibutton low - furi_hal_ibutton_start_drive(); - furi_hal_ibutton_pin_low(); + furi_hal_ibutton_pin_configure(); + furi_hal_ibutton_pin_write(false); // pull pin to timer out furi_hal_gpio_init_ex( @@ -115,8 +115,8 @@ void furi_hal_rfid_pins_emulate() { void furi_hal_rfid_pins_read() { // ibutton low - furi_hal_ibutton_start_drive(); - furi_hal_ibutton_pin_low(); + furi_hal_ibutton_pin_configure(); + furi_hal_ibutton_pin_write(false); // dont pull rfid antenna furi_hal_gpio_init(&gpio_nfc_irq_rfid_pull, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); diff --git a/lib/one_wire/ibutton/ibutton_worker.c b/lib/one_wire/ibutton/ibutton_worker.c index 9d5ea38970d..1fe39b5e5e7 100644 --- a/lib/one_wire/ibutton/ibutton_worker.c +++ b/lib/one_wire/ibutton/ibutton_worker.c @@ -25,8 +25,8 @@ iButtonWorker* ibutton_worker_alloc() { iButtonWorker* worker = malloc(sizeof(iButtonWorker)); worker->key_p = NULL; worker->key_data = malloc(ibutton_key_get_max_size()); - worker->host = onewire_host_alloc(); - worker->slave = onewire_slave_alloc(); + worker->host = onewire_host_alloc(&ibutton_gpio); + worker->slave = onewire_slave_alloc(&ibutton_gpio); worker->writer = ibutton_writer_alloc(worker->host); worker->device = onewire_device_alloc(0, 0, 0, 0, 0, 0, 0, 0); worker->messages = furi_message_queue_alloc(1, sizeof(iButtonMessage)); diff --git a/lib/one_wire/ibutton/ibutton_worker_modes.c b/lib/one_wire/ibutton/ibutton_worker_modes.c index b284940e7f9..da6b107668a 100644 --- a/lib/one_wire/ibutton/ibutton_worker_modes.c +++ b/lib/one_wire/ibutton/ibutton_worker_modes.c @@ -234,16 +234,13 @@ void ibutton_worker_emulate_timer_cb(void* context) { furi_assert(context); iButtonWorker* worker = context; - LevelDuration level = + const LevelDuration level_duration = protocol_dict_encoder_yield(worker->protocols, worker->protocol_to_encode); - furi_hal_ibutton_emulate_set_next(level_duration_get_duration(level)); + const bool level = level_duration_get_level(level_duration); - if(level_duration_get_level(level)) { - furi_hal_ibutton_pin_high(); - } else { - furi_hal_ibutton_pin_low(); - } + furi_hal_ibutton_emulate_set_next(level); + furi_hal_ibutton_pin_write(level); } void ibutton_worker_emulate_timer_start(iButtonWorker* worker) { @@ -266,7 +263,7 @@ void ibutton_worker_emulate_timer_start(iButtonWorker* worker) { protocol_dict_set_data(worker->protocols, worker->protocol_to_encode, key_id, key_size); protocol_dict_encoder_start(worker->protocols, worker->protocol_to_encode); - furi_hal_ibutton_start_drive(); + furi_hal_ibutton_pin_configure(); furi_hal_ibutton_emulate_start(0, ibutton_worker_emulate_timer_cb, worker); } diff --git a/lib/one_wire/one_wire_host.c b/lib/one_wire/one_wire_host.c index 654b9e45d02..f3d3d3c4dcd 100644 --- a/lib/one_wire/one_wire_host.c +++ b/lib/one_wire/one_wire_host.c @@ -1,18 +1,19 @@ #include -#include + #include "one_wire_host.h" #include "one_wire_host_timing.h" struct OneWireHost { - // global search state - unsigned char saved_rom[8]; + const GpioPin* gpio_pin; + unsigned char saved_rom[8]; /** < global search state */ uint8_t last_discrepancy; uint8_t last_family_discrepancy; bool last_device_flag; }; -OneWireHost* onewire_host_alloc() { +OneWireHost* onewire_host_alloc(const GpioPin* gpio_pin) { OneWireHost* host = malloc(sizeof(OneWireHost)); + host->gpio_pin = gpio_pin; onewire_host_reset_search(host); return host; } @@ -23,49 +24,47 @@ void onewire_host_free(OneWireHost* host) { } bool onewire_host_reset(OneWireHost* host) { - UNUSED(host); uint8_t r; uint8_t retries = 125; // wait until the gpio is high - furi_hal_ibutton_pin_high(); + furi_hal_gpio_write(host->gpio_pin, true); do { if(--retries == 0) return 0; furi_delay_us(2); - } while(!furi_hal_ibutton_pin_get_level()); + } while(!furi_hal_gpio_read(host->gpio_pin)); // pre delay furi_delay_us(OWH_RESET_DELAY_PRE); // drive low - furi_hal_ibutton_pin_low(); + furi_hal_gpio_write(host->gpio_pin, false); furi_delay_us(OWH_RESET_DRIVE); // release - furi_hal_ibutton_pin_high(); + furi_hal_gpio_write(host->gpio_pin, true); furi_delay_us(OWH_RESET_RELEASE); // read and post delay - r = !furi_hal_ibutton_pin_get_level(); + r = !furi_hal_gpio_read(host->gpio_pin); furi_delay_us(OWH_RESET_DELAY_POST); return r; } bool onewire_host_read_bit(OneWireHost* host) { - UNUSED(host); bool result; // drive low - furi_hal_ibutton_pin_low(); + furi_hal_gpio_write(host->gpio_pin, false); furi_delay_us(OWH_READ_DRIVE); // release - furi_hal_ibutton_pin_high(); + furi_hal_gpio_write(host->gpio_pin, true); furi_delay_us(OWH_READ_RELEASE); // read and post delay - result = furi_hal_ibutton_pin_get_level(); + result = furi_hal_gpio_read(host->gpio_pin); furi_delay_us(OWH_READ_DELAY_POST); return result; @@ -90,22 +89,21 @@ void onewire_host_read_bytes(OneWireHost* host, uint8_t* buffer, uint16_t count) } void onewire_host_write_bit(OneWireHost* host, bool value) { - UNUSED(host); if(value) { // drive low - furi_hal_ibutton_pin_low(); + furi_hal_gpio_write(host->gpio_pin, false); furi_delay_us(OWH_WRITE_1_DRIVE); // release - furi_hal_ibutton_pin_high(); + furi_hal_gpio_write(host->gpio_pin, true); furi_delay_us(OWH_WRITE_1_RELEASE); } else { // drive low - furi_hal_ibutton_pin_low(); + furi_hal_gpio_write(host->gpio_pin, false); furi_delay_us(OWH_WRITE_0_DRIVE); // release - furi_hal_ibutton_pin_high(); + furi_hal_gpio_write(host->gpio_pin, true); furi_delay_us(OWH_WRITE_0_RELEASE); } } @@ -123,13 +121,13 @@ void onewire_host_skip(OneWireHost* host) { } void onewire_host_start(OneWireHost* host) { - UNUSED(host); - furi_hal_ibutton_start_drive(); + furi_hal_gpio_write(host->gpio_pin, true); + furi_hal_gpio_init(host->gpio_pin, GpioModeOutputOpenDrain, GpioPullNo, GpioSpeedLow); } void onewire_host_stop(OneWireHost* host) { - UNUSED(host); - furi_hal_ibutton_stop(); + furi_hal_gpio_write(host->gpio_pin, true); + furi_hal_gpio_init(host->gpio_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); } void onewire_host_reset_search(OneWireHost* host) { @@ -150,7 +148,7 @@ void onewire_host_target_search(OneWireHost* host, uint8_t family_code) { host->last_device_flag = false; } -uint8_t onewire_host_search(OneWireHost* host, uint8_t* newAddr, OneWireHostSearchMode mode) { +uint8_t onewire_host_search(OneWireHost* host, uint8_t* new_addr, OneWireHostSearchMode mode) { uint8_t id_bit_number; uint8_t last_zero, rom_byte_number, search_result; uint8_t id_bit, cmp_id_bit; @@ -259,7 +257,7 @@ uint8_t onewire_host_search(OneWireHost* host, uint8_t* newAddr, OneWireHostSear host->last_family_discrepancy = 0; search_result = false; } else { - for(int i = 0; i < 8; i++) newAddr[i] = host->saved_rom[i]; + for(int i = 0; i < 8; i++) new_addr[i] = host->saved_rom[i]; } return search_result; diff --git a/lib/one_wire/one_wire_host.h b/lib/one_wire/one_wire_host.h index 5b55ee8dd80..9c01abc1147 100644 --- a/lib/one_wire/one_wire_host.h +++ b/lib/one_wire/one_wire_host.h @@ -22,10 +22,10 @@ typedef struct OneWireHost OneWireHost; /** * Allocate onewire host bus - * @param gpio + * @param pin * @return OneWireHost* */ -OneWireHost* onewire_host_alloc(); +OneWireHost* onewire_host_alloc(const GpioPin* gpio_pin); /** * Deallocate onewire host bus @@ -114,7 +114,7 @@ void onewire_host_target_search(OneWireHost* host, uint8_t family_code); * @param mode * @return uint8_t */ -uint8_t onewire_host_search(OneWireHost* host, uint8_t* newAddr, OneWireHostSearchMode mode); +uint8_t onewire_host_search(OneWireHost* host, uint8_t* new_addr, OneWireHostSearchMode mode); #ifdef __cplusplus } diff --git a/lib/one_wire/one_wire_slave.c b/lib/one_wire/one_wire_slave.c index ad9c34b1982..4b54c4f99f7 100644 --- a/lib/one_wire/one_wire_slave.c +++ b/lib/one_wire/one_wire_slave.c @@ -27,6 +27,7 @@ typedef enum { } OneWireSlaveError; struct OneWireSlave { + const GpioPin* gpio_pin; OneWireSlaveError error; OneWireDevice* device; OneWireSlaveResultCallback result_cb; @@ -35,15 +36,15 @@ struct OneWireSlave { /*********************** PRIVATE ***********************/ -uint32_t onewire_slave_wait_while_gpio_is(OneWireSlave* bus, uint32_t time, const bool pin_value) { - UNUSED(bus); +static uint32_t + onewire_slave_wait_while_gpio_is(OneWireSlave* bus, uint32_t time, const bool pin_value) { uint32_t start = DWT->CYCCNT; uint32_t time_ticks = time * furi_hal_cortex_instructions_per_microsecond(); uint32_t time_captured; do { //-V1044 time_captured = DWT->CYCCNT; - if(furi_hal_ibutton_pin_get_level() != pin_value) { + if(furi_hal_gpio_read(bus->gpio_pin) != pin_value) { uint32_t remaining_time = time_ticks - (time_captured - start); remaining_time /= furi_hal_cortex_instructions_per_microsecond(); return remaining_time; @@ -53,14 +54,14 @@ uint32_t onewire_slave_wait_while_gpio_is(OneWireSlave* bus, uint32_t time, cons return 0; } -bool onewire_slave_show_presence(OneWireSlave* bus) { +static bool onewire_slave_show_presence(OneWireSlave* bus) { // wait while master delay presence check onewire_slave_wait_while_gpio_is(bus, OWS_PRESENCE_TIMEOUT, true); // show presence - furi_hal_ibutton_pin_low(); + furi_hal_gpio_write(bus->gpio_pin, false); furi_delay_us(OWS_PRESENCE_MIN); - furi_hal_ibutton_pin_high(); + furi_hal_gpio_write(bus->gpio_pin, true); // somebody also can show presence const uint32_t wait_low_time = OWS_PRESENCE_MAX - OWS_PRESENCE_MIN; @@ -74,7 +75,7 @@ bool onewire_slave_show_presence(OneWireSlave* bus) { return true; } -bool onewire_slave_receive_bit(OneWireSlave* bus) { +static bool onewire_slave_receive_bit(OneWireSlave* bus) { // wait while bus is low uint32_t time = OWS_SLOT_MAX; time = onewire_slave_wait_while_gpio_is(bus, time, false); @@ -98,7 +99,7 @@ bool onewire_slave_receive_bit(OneWireSlave* bus) { return (time > 0); } -bool onewire_slave_send_bit(OneWireSlave* bus, bool value) { +static bool onewire_slave_send_bit(OneWireSlave* bus, bool value) { const bool write_zero = !value; // wait while bus is low @@ -119,7 +120,7 @@ bool onewire_slave_send_bit(OneWireSlave* bus, bool value) { // choose write time if(write_zero) { - furi_hal_ibutton_pin_low(); + furi_hal_gpio_write(bus->gpio_pin, false); time = OWS_WRITE_ZERO; } else { time = OWS_READ_MAX; @@ -127,12 +128,12 @@ bool onewire_slave_send_bit(OneWireSlave* bus, bool value) { // hold line for ZERO or ONE time furi_delay_us(time); - furi_hal_ibutton_pin_high(); + furi_hal_gpio_write(bus->gpio_pin, true); return true; } -void onewire_slave_cmd_search_rom(OneWireSlave* bus) { +static void onewire_slave_cmd_search_rom(OneWireSlave* bus) { const uint8_t key_bytes = 8; uint8_t* key = onewire_device_get_id_p(bus->device); @@ -151,7 +152,7 @@ void onewire_slave_cmd_search_rom(OneWireSlave* bus) { } } -bool onewire_slave_receive_and_process_cmd(OneWireSlave* bus) { +static bool onewire_slave_receive_and_process_cmd(OneWireSlave* bus) { uint8_t cmd; onewire_slave_receive(bus, &cmd, 1); @@ -178,14 +179,14 @@ bool onewire_slave_receive_and_process_cmd(OneWireSlave* bus) { } } -bool onewire_slave_bus_start(OneWireSlave* bus) { +static bool onewire_slave_bus_start(OneWireSlave* bus) { bool result = true; if(bus->device == NULL) { result = false; } else { FURI_CRITICAL_ENTER(); - furi_hal_ibutton_start_drive_in_isr(); + furi_hal_gpio_init(bus->gpio_pin, GpioModeOutputOpenDrain, GpioPullNo, GpioSpeedLow); bus->error = NO_ERROR; if(onewire_slave_show_presence(bus)) { @@ -197,7 +198,7 @@ bool onewire_slave_bus_start(OneWireSlave* bus) { result = false; } - furi_hal_ibutton_start_interrupt_in_isr(); + furi_hal_gpio_init(bus->gpio_pin, GpioModeInterruptRiseFall, GpioPullNo, GpioSpeedLow); FURI_CRITICAL_EXIT(); } @@ -207,7 +208,7 @@ bool onewire_slave_bus_start(OneWireSlave* bus) { static void exti_cb(void* context) { OneWireSlave* bus = context; - volatile bool input_state = furi_hal_ibutton_pin_get_level(); + volatile bool input_state = furi_hal_gpio_read(bus->gpio_pin); static uint32_t pulse_start = 0; if(input_state) { @@ -234,8 +235,9 @@ static void exti_cb(void* context) { /*********************** PUBLIC ***********************/ -OneWireSlave* onewire_slave_alloc() { +OneWireSlave* onewire_slave_alloc(const GpioPin* gpio_pin) { OneWireSlave* bus = malloc(sizeof(OneWireSlave)); + bus->gpio_pin = gpio_pin; bus->error = NO_ERROR; bus->device = NULL; bus->result_cb = NULL; @@ -249,14 +251,15 @@ void onewire_slave_free(OneWireSlave* bus) { } void onewire_slave_start(OneWireSlave* bus) { - furi_hal_ibutton_add_interrupt(exti_cb, bus); - furi_hal_ibutton_start_interrupt(); + furi_hal_gpio_add_int_callback(bus->gpio_pin, exti_cb, bus); + furi_hal_gpio_write(bus->gpio_pin, true); + furi_hal_gpio_init(bus->gpio_pin, GpioModeInterruptRiseFall, GpioPullNo, GpioSpeedLow); } void onewire_slave_stop(OneWireSlave* bus) { - UNUSED(bus); - furi_hal_ibutton_stop(); - furi_hal_ibutton_remove_interrupt(); + furi_hal_gpio_write(bus->gpio_pin, true); + furi_hal_gpio_init(bus->gpio_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_remove_int_callback(bus->gpio_pin); } void onewire_slave_attach(OneWireSlave* bus, OneWireDevice* device) { @@ -282,7 +285,7 @@ void onewire_slave_set_result_callback( bool onewire_slave_send(OneWireSlave* bus, const uint8_t* address, const uint8_t data_length) { uint8_t bytes_sent = 0; - furi_hal_ibutton_pin_high(); + furi_hal_gpio_write(bus->gpio_pin, true); // bytes loop for(; bytes_sent < data_length; ++bytes_sent) { @@ -304,7 +307,7 @@ bool onewire_slave_send(OneWireSlave* bus, const uint8_t* address, const uint8_t bool onewire_slave_receive(OneWireSlave* bus, uint8_t* data, const uint8_t data_length) { uint8_t bytes_received = 0; - furi_hal_ibutton_pin_high(); + furi_hal_gpio_write(bus->gpio_pin, true); for(; bytes_received < data_length; ++bytes_received) { uint8_t value = 0; diff --git a/lib/one_wire/one_wire_slave.h b/lib/one_wire/one_wire_slave.h index 82e9f552331..2e5db3a1c2a 100644 --- a/lib/one_wire/one_wire_slave.h +++ b/lib/one_wire/one_wire_slave.h @@ -19,10 +19,10 @@ typedef void (*OneWireSlaveResultCallback)(void* context); /** * Allocate onewire slave - * @param pin + * @param gpio_pin * @return OneWireSlave* */ -OneWireSlave* onewire_slave_alloc(); +OneWireSlave* onewire_slave_alloc(const GpioPin* gpio_pin); /** * Free onewire slave From 111c7557b3c3c5a371f3fddf1a63592b7ad04b6c Mon Sep 17 00:00:00 2001 From: Liam Hays Date: Wed, 8 Feb 2023 01:08:50 -0700 Subject: [PATCH 382/824] Fix minor UI inconsistencies and bugs (#2361) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Changed blue LED to cyan in NFC Magic and Picopass apps. * Fix capitalization of ATQA and UID in NFC Add Manually wizard. * Fix reselection of "Saved" menu item in NFC and RFID apps. * Fix double back press after deleting a file in the SubGhz browser. * Make NFC app behave like other apps: return to the file browser after deleting a file. * Rename NfcSceneSetAtqua to NfcSceneSetAtqa. * Save selected menu items in NFC Magic and Picopass apps in a way that always works. * Restore previous selection in Universal Remotes menu. * Other way to do universal remote menu saving, and NFC Extra Actions saves last selection. Co-authored-by: あく --- .../main/infrared/scenes/infrared_scene_universal.c | 4 +++- .../main/lfrfid/scenes/lfrfid_scene_start.c | 9 ++++++++- applications/main/nfc/scenes/nfc_scene_config.h | 2 +- .../main/nfc/scenes/nfc_scene_delete_success.c | 2 +- .../main/nfc/scenes/nfc_scene_extra_actions.c | 2 ++ applications/main/nfc/scenes/nfc_scene_set_atqa.c | 2 +- applications/main/nfc/scenes/nfc_scene_set_sak.c | 2 +- applications/main/nfc/scenes/nfc_scene_set_uid.c | 2 +- applications/main/nfc/scenes/nfc_scene_start.c | 13 ++++++++++++- .../subghz/scenes/subghz_scene_delete_success.c | 5 ++++- applications/plugins/nfc_magic/nfc_magic.c | 6 +++--- .../nfc_magic/scenes/nfc_magic_scene_start.c | 10 +++++++++- applications/plugins/picopass/picopass.c | 6 +++--- .../plugins/picopass/scenes/picopass_scene_start.c | 7 ++++++- 14 files changed, 55 insertions(+), 17 deletions(-) diff --git a/applications/main/infrared/scenes/infrared_scene_universal.c b/applications/main/infrared/scenes/infrared_scene_universal.c index 914360d78cf..5043c9bd71d 100644 --- a/applications/main/infrared/scenes/infrared_scene_universal.c +++ b/applications/main/infrared/scenes/infrared_scene_universal.c @@ -33,7 +33,8 @@ void infrared_scene_universal_on_enter(void* context) { SubmenuIndexUniversalAC, infrared_scene_universal_submenu_callback, context); - submenu_set_selected_item(submenu, 0); + submenu_set_selected_item( + submenu, scene_manager_get_scene_state(infrared->scene_manager, InfraredSceneUniversal)); view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewSubmenu); } @@ -54,6 +55,7 @@ bool infrared_scene_universal_on_event(void* context, SceneManagerEvent event) { scene_manager_next_scene(scene_manager, InfraredSceneUniversalAudio); consumed = true; } + scene_manager_set_scene_state(scene_manager, InfraredSceneUniversal, event.event); } return consumed; diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_start.c b/applications/main/lfrfid/scenes/lfrfid_scene_start.c index 8e1c92dbb29..2d83ba53b3b 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_start.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_start.c @@ -47,21 +47,28 @@ bool lfrfid_scene_start_on_event(void* context, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { if(event.event == SubmenuIndexRead) { + scene_manager_set_scene_state(app->scene_manager, LfRfidSceneStart, SubmenuIndexRead); scene_manager_next_scene(app->scene_manager, LfRfidSceneRead); DOLPHIN_DEED(DolphinDeedRfidRead); consumed = true; } else if(event.event == SubmenuIndexSaved) { + // Like in the other apps, explicitly save the scene state + // in each branch in case the user cancels loading a file. + scene_manager_set_scene_state(app->scene_manager, LfRfidSceneStart, SubmenuIndexSaved); furi_string_set(app->file_path, LFRFID_APP_FOLDER); scene_manager_next_scene(app->scene_manager, LfRfidSceneSelectKey); consumed = true; } else if(event.event == SubmenuIndexAddManually) { + scene_manager_set_scene_state( + app->scene_manager, LfRfidSceneStart, SubmenuIndexAddManually); scene_manager_next_scene(app->scene_manager, LfRfidSceneSaveType); consumed = true; } else if(event.event == SubmenuIndexExtraActions) { + scene_manager_set_scene_state( + app->scene_manager, LfRfidSceneStart, SubmenuIndexExtraActions); scene_manager_next_scene(app->scene_manager, LfRfidSceneExtraActions); consumed = true; } - scene_manager_set_scene_state(app->scene_manager, LfRfidSceneStart, event.event); } return consumed; diff --git a/applications/main/nfc/scenes/nfc_scene_config.h b/applications/main/nfc/scenes/nfc_scene_config.h index ce51d000d39..f81fe178e25 100644 --- a/applications/main/nfc/scenes/nfc_scene_config.h +++ b/applications/main/nfc/scenes/nfc_scene_config.h @@ -4,7 +4,7 @@ ADD_SCENE(nfc, saved_menu, SavedMenu) ADD_SCENE(nfc, extra_actions, ExtraActions) ADD_SCENE(nfc, set_type, SetType) ADD_SCENE(nfc, set_sak, SetSak) -ADD_SCENE(nfc, set_atqa, SetAtqua) +ADD_SCENE(nfc, set_atqa, SetAtqa) ADD_SCENE(nfc, set_uid, SetUid) ADD_SCENE(nfc, generate_info, GenerateInfo) ADD_SCENE(nfc, read_card_success, ReadCardSuccess) diff --git a/applications/main/nfc/scenes/nfc_scene_delete_success.c b/applications/main/nfc/scenes/nfc_scene_delete_success.c index 1664a9e5bb1..795363527fc 100644 --- a/applications/main/nfc/scenes/nfc_scene_delete_success.c +++ b/applications/main/nfc/scenes/nfc_scene_delete_success.c @@ -30,7 +30,7 @@ bool nfc_scene_delete_success_on_event(void* context, SceneManagerEvent event) { nfc->scene_manager, NfcSceneMfClassicKeys); } else { consumed = scene_manager_search_and_switch_to_previous_scene( - nfc->scene_manager, NfcSceneStart); + nfc->scene_manager, NfcSceneFileSelect); } } } diff --git a/applications/main/nfc/scenes/nfc_scene_extra_actions.c b/applications/main/nfc/scenes/nfc_scene_extra_actions.c index 717e8efc444..66aaf5a26dc 100644 --- a/applications/main/nfc/scenes/nfc_scene_extra_actions.c +++ b/applications/main/nfc/scenes/nfc_scene_extra_actions.c @@ -34,6 +34,8 @@ void nfc_scene_extra_actions_on_enter(void* context) { SubmenuIndexMfUltralightUnlock, nfc_scene_extra_actions_submenu_callback, nfc); + submenu_set_selected_item( + submenu, scene_manager_get_scene_state(nfc->scene_manager, NfcSceneExtraActions)); view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); } diff --git a/applications/main/nfc/scenes/nfc_scene_set_atqa.c b/applications/main/nfc/scenes/nfc_scene_set_atqa.c index f2100aa196b..d079b380475 100644 --- a/applications/main/nfc/scenes/nfc_scene_set_atqa.c +++ b/applications/main/nfc/scenes/nfc_scene_set_atqa.c @@ -11,7 +11,7 @@ void nfc_scene_set_atqa_on_enter(void* context) { // Setup view ByteInput* byte_input = nfc->byte_input; - byte_input_set_header_text(byte_input, "Enter atqa in hex"); + byte_input_set_header_text(byte_input, "Enter ATQA in hex"); byte_input_set_result_callback( byte_input, nfc_scene_set_atqa_byte_input_callback, diff --git a/applications/main/nfc/scenes/nfc_scene_set_sak.c b/applications/main/nfc/scenes/nfc_scene_set_sak.c index 3c88f350465..60a1e1494b8 100644 --- a/applications/main/nfc/scenes/nfc_scene_set_sak.c +++ b/applications/main/nfc/scenes/nfc_scene_set_sak.c @@ -28,7 +28,7 @@ bool nfc_scene_set_sak_on_event(void* context, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { if(event.event == NfcCustomEventByteInputDone) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneSetAtqua); + scene_manager_next_scene(nfc->scene_manager, NfcSceneSetAtqa); consumed = true; } } diff --git a/applications/main/nfc/scenes/nfc_scene_set_uid.c b/applications/main/nfc/scenes/nfc_scene_set_uid.c index 5f0f52f6ef4..54606b68eec 100644 --- a/applications/main/nfc/scenes/nfc_scene_set_uid.c +++ b/applications/main/nfc/scenes/nfc_scene_set_uid.c @@ -11,7 +11,7 @@ void nfc_scene_set_uid_on_enter(void* context) { // Setup view ByteInput* byte_input = nfc->byte_input; - byte_input_set_header_text(byte_input, "Enter uid in hex"); + byte_input_set_header_text(byte_input, "Enter UID in hex"); nfc->dev_edit_data = nfc->dev->dev_data.nfc_data; byte_input_set_result_callback( byte_input, diff --git a/applications/main/nfc/scenes/nfc_scene_start.c b/applications/main/nfc/scenes/nfc_scene_start.c index 2a116fe0911..a01f871ab6a 100644 --- a/applications/main/nfc/scenes/nfc_scene_start.c +++ b/applications/main/nfc/scenes/nfc_scene_start.c @@ -48,11 +48,14 @@ bool nfc_scene_start_on_event(void* context, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { if(event.event == SubmenuIndexRead) { + scene_manager_set_scene_state(nfc->scene_manager, NfcSceneStart, SubmenuIndexRead); nfc->dev->dev_data.read_mode = NfcReadModeAuto; scene_manager_next_scene(nfc->scene_manager, NfcSceneRead); DOLPHIN_DEED(DolphinDeedNfcRead); consumed = true; } else if(event.event == SubmenuIndexDetectReader) { + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneStart, SubmenuIndexDetectReader); bool sd_exist = storage_sd_status(nfc->dev->storage) == FSE_OK; if(sd_exist) { nfc_device_data_clear(&nfc->dev->dev_data); @@ -63,19 +66,27 @@ bool nfc_scene_start_on_event(void* context, SceneManagerEvent event) { } consumed = true; } else if(event.event == SubmenuIndexSaved) { + // Save the scene state explicitly in each branch, so that + // if the user cancels loading a file, the Saved menu item + // is properly reselected. + scene_manager_set_scene_state(nfc->scene_manager, NfcSceneStart, SubmenuIndexSaved); scene_manager_next_scene(nfc->scene_manager, NfcSceneFileSelect); consumed = true; } else if(event.event == SubmenuIndexExtraAction) { + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneStart, SubmenuIndexExtraAction); scene_manager_next_scene(nfc->scene_manager, NfcSceneExtraActions); consumed = true; } else if(event.event == SubmenuIndexAddManually) { + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneStart, SubmenuIndexAddManually); scene_manager_next_scene(nfc->scene_manager, NfcSceneSetType); consumed = true; } else if(event.event == SubmenuIndexDebug) { + scene_manager_set_scene_state(nfc->scene_manager, NfcSceneStart, SubmenuIndexDebug); scene_manager_next_scene(nfc->scene_manager, NfcSceneDebug); consumed = true; } - scene_manager_set_scene_state(nfc->scene_manager, NfcSceneStart, event.event); } return consumed; } diff --git a/applications/main/subghz/scenes/subghz_scene_delete_success.c b/applications/main/subghz/scenes/subghz_scene_delete_success.c index 4f98b6a394f..8a254624344 100644 --- a/applications/main/subghz/scenes/subghz_scene_delete_success.c +++ b/applications/main/subghz/scenes/subghz_scene_delete_success.c @@ -31,7 +31,10 @@ bool subghz_scene_delete_success_on_event(void* context, SceneManagerEvent event 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); + // Commented so that the user doesn't have to press + // back twice to get to the main SubGhz menu after + // deleting a file. + //scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaved); } else { scene_manager_search_and_switch_to_previous_scene( subghz->scene_manager, SubGhzSceneStart); diff --git a/applications/plugins/nfc_magic/nfc_magic.c b/applications/plugins/nfc_magic/nfc_magic.c index e4e0ffde08a..1805f35ed05 100644 --- a/applications/plugins/nfc_magic/nfc_magic.c +++ b/applications/plugins/nfc_magic/nfc_magic.c @@ -136,9 +136,9 @@ void nfc_magic_free(NfcMagic* nfc_magic) { free(nfc_magic); } -static const NotificationSequence nfc_magic_sequence_blink_start_blue = { +static const NotificationSequence nfc_magic_sequence_blink_start_cyan = { &message_blink_start_10, - &message_blink_set_color_blue, + &message_blink_set_color_cyan, &message_do_not_reset, NULL, }; @@ -149,7 +149,7 @@ static const NotificationSequence nfc_magic_sequence_blink_stop = { }; void nfc_magic_blink_start(NfcMagic* nfc_magic) { - notification_message(nfc_magic->notifications, &nfc_magic_sequence_blink_start_blue); + notification_message(nfc_magic->notifications, &nfc_magic_sequence_blink_start_cyan); } void nfc_magic_blink_stop(NfcMagic* nfc_magic) { diff --git a/applications/plugins/nfc_magic/scenes/nfc_magic_scene_start.c b/applications/plugins/nfc_magic/scenes/nfc_magic_scene_start.c index f2984443fb9..a70eb8accfe 100644 --- a/applications/plugins/nfc_magic/scenes/nfc_magic_scene_start.c +++ b/applications/plugins/nfc_magic/scenes/nfc_magic_scene_start.c @@ -40,16 +40,24 @@ bool nfc_magic_scene_start_on_event(void* context, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { if(event.event == SubmenuIndexCheck) { + scene_manager_set_scene_state( + nfc_magic->scene_manager, NfcMagicSceneStart, SubmenuIndexCheck); scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneCheck); consumed = true; } else if(event.event == SubmenuIndexWriteGen1A) { + // Explicitly save state in each branch so that the + // correct option is reselected if the user cancels + // loading a file. + scene_manager_set_scene_state( + nfc_magic->scene_manager, NfcMagicSceneStart, SubmenuIndexWriteGen1A); scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneFileSelect); consumed = true; } else if(event.event == SubmenuIndexWipe) { + scene_manager_set_scene_state( + nfc_magic->scene_manager, NfcMagicSceneStart, SubmenuIndexWipe); scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneWipe); consumed = true; } - scene_manager_set_scene_state(nfc_magic->scene_manager, NfcMagicSceneStart, event.event); } return consumed; diff --git a/applications/plugins/picopass/picopass.c b/applications/plugins/picopass/picopass.c index e9f48b6765c..217f963d364 100644 --- a/applications/plugins/picopass/picopass.c +++ b/applications/plugins/picopass/picopass.c @@ -137,9 +137,9 @@ void picopass_text_store_clear(Picopass* picopass) { memset(picopass->text_store, 0, sizeof(picopass->text_store)); } -static const NotificationSequence picopass_sequence_blink_start_blue = { +static const NotificationSequence picopass_sequence_blink_start_cyan = { &message_blink_start_10, - &message_blink_set_color_blue, + &message_blink_set_color_cyan, &message_do_not_reset, NULL, }; @@ -150,7 +150,7 @@ static const NotificationSequence picopass_sequence_blink_stop = { }; void picopass_blink_start(Picopass* picopass) { - notification_message(picopass->notifications, &picopass_sequence_blink_start_blue); + notification_message(picopass->notifications, &picopass_sequence_blink_start_cyan); } void picopass_blink_stop(Picopass* picopass) { diff --git a/applications/plugins/picopass/scenes/picopass_scene_start.c b/applications/plugins/picopass/scenes/picopass_scene_start.c index 6d1aeedcddb..d33a1d26492 100644 --- a/applications/plugins/picopass/scenes/picopass_scene_start.c +++ b/applications/plugins/picopass/scenes/picopass_scene_start.c @@ -32,13 +32,18 @@ bool picopass_scene_start_on_event(void* context, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { if(event.event == SubmenuIndexRead) { + scene_manager_set_scene_state( + picopass->scene_manager, PicopassSceneStart, SubmenuIndexRead); scene_manager_next_scene(picopass->scene_manager, PicopassSceneReadCard); consumed = true; } else if(event.event == SubmenuIndexSaved) { + // Explicitly save state so that the correct item is + // reselected if the user cancels loading a file. + scene_manager_set_scene_state( + picopass->scene_manager, PicopassSceneStart, SubmenuIndexSaved); scene_manager_next_scene(picopass->scene_manager, PicopassSceneFileSelect); consumed = true; } - scene_manager_set_scene_state(picopass->scene_manager, PicopassSceneStart, event.event); } return consumed; From 23ecc186c24062002aeac5e73235b3a6c0021f77 Mon Sep 17 00:00:00 2001 From: LTVA1 <87536432+LTVA1@users.noreply.github.com> Date: Wed, 8 Feb 2023 12:26:17 +0300 Subject: [PATCH 383/824] Custom font set function (#2261) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * custom font set function * update API symbols * add example of custom font usage * delete u8g2 dependency in example custom font * rename to canvas_set_custom_u8g2_font * now change the name in ALL places Co-authored-by: あく Co-authored-by: hedger --- .../debug/example_custom_font/application.fam | 9 ++ .../example_custom_font/example_custom_font.c | 98 +++++++++++++++++++ applications/services/gui/canvas.c | 6 ++ applications/services/gui/canvas.h | 7 ++ firmware/targets/f18/api_symbols.csv | 5 +- firmware/targets/f7/api_symbols.csv | 1 + 6 files changed, 124 insertions(+), 2 deletions(-) create mode 100644 applications/debug/example_custom_font/application.fam create mode 100644 applications/debug/example_custom_font/example_custom_font.c diff --git a/applications/debug/example_custom_font/application.fam b/applications/debug/example_custom_font/application.fam new file mode 100644 index 00000000000..02285b8a01f --- /dev/null +++ b/applications/debug/example_custom_font/application.fam @@ -0,0 +1,9 @@ +App( + appid="example_custom_font", + name="Example: custom font", + apptype=FlipperAppType.EXTERNAL, + entry_point="example_custom_font_main", + requires=["gui"], + stack_size=1 * 1024, + fap_category="Debug", +) diff --git a/applications/debug/example_custom_font/example_custom_font.c b/applications/debug/example_custom_font/example_custom_font.c new file mode 100644 index 00000000000..15eeb5f02a4 --- /dev/null +++ b/applications/debug/example_custom_font/example_custom_font.c @@ -0,0 +1,98 @@ +#include +#include + +#include +#include + +//This arrays contains the font itself. You can use any u8g2 font you want + +/* +Fontname: -Raccoon-Fixed4x6-Medium-R-Normal--6-60-75-75-P-40-ISO10646-1 +Copyright: +Glyphs: 95/203 +BBX Build Mode: 0 +*/ +const uint8_t u8g2_font_tom_thumb_4x6_tr[725] = + "_\0\2\2\2\3\3\4\4\3\6\0\377\5\377\5\0\0\352\1\330\2\270 \5\340\315\0!\6\265\310" + "\254\0\42\6\213\313$\25#\10\227\310\244\241\206\12$\10\227\310\215\70b\2%\10\227\310d\324F\1" + "&\10\227\310(\65R\22'\5\251\313\10(\6\266\310\251\62)\10\226\310\304\224\24\0*\6\217\312\244" + "\16+\7\217\311\245\225\0,\6\212\310)\0-\5\207\312\14.\5\245\310\4/\7\227\310Ve\4\60" + "\7\227\310-k\1\61\6\226\310\255\6\62\10\227\310h\220\312\1\63\11\227\310h\220\62X\0\64\10\227" + "\310$\65b\1\65\10\227\310\214\250\301\2\66\10\227\310\315\221F\0\67\10\227\310\314TF\0\70\10\227" + "\310\214\64\324\10\71\10\227\310\214\64\342\2:\6\255\311\244\0;\7\222\310e\240\0<\10\227\310\246\32" + "d\20=\6\217\311l\60>\11\227\310d\220A*\1\77\10\227\310\314\224a\2@\10\227\310UC\3" + "\1A\10\227\310UC\251\0B\10\227\310\250\264\322\2C\7\227\310\315\32\10D\10\227\310\250d-\0" + "E\10\227\310\214\70\342\0F\10\227\310\214\70b\4G\10\227\310\315\221\222\0H\10\227\310$\65\224\12" + "I\7\227\310\254X\15J\7\227\310\226\252\2K\10\227\310$\265\222\12L\7\227\310\304\346\0M\10\227" + "\310\244\61\224\12N\10\227\310\244q\250\0O\7\227\310UV\5P\10\227\310\250\264b\4Q\10\227\310" + "Uj$\1R\10\227\310\250\64V\1S\10\227\310m\220\301\2T\7\227\310\254\330\2U\7\227\310$" + "W\22V\10\227\310$\253L\0W\10\227\310$\65\206\12X\10\227\310$\325R\1Y\10\227\310$U" + "V\0Z\7\227\310\314T\16[\7\227\310\214X\16\134\10\217\311d\220A\0]\7\227\310\314r\4^" + "\5\213\313\65_\5\207\310\14`\6\212\313\304\0a\7\223\310\310\65\2b\10\227\310D\225\324\2c\7" + "\223\310\315\14\4d\10\227\310\246\245\222\0e\6\223\310\235\2f\10\227\310\246\264b\2g\10\227\307\35" + "\61%\0h\10\227\310D\225\254\0i\6\265\310\244\1j\10\233\307f\30U\5k\10\227\310\304\264T" + "\1l\7\227\310\310\326\0m\7\223\310fb, 1); + u8g2_SetFont(&canvas->fb, font); +} + void canvas_draw_str(Canvas* canvas, uint8_t x, uint8_t y, const char* str) { furi_assert(canvas); if(!str) return; diff --git a/applications/services/gui/canvas.h b/applications/services/gui/canvas.h index 0b0c7e6585f..b2a065ca775 100644 --- a/applications/services/gui/canvas.h +++ b/applications/services/gui/canvas.h @@ -146,6 +146,13 @@ void canvas_invert_color(Canvas* canvas); */ void canvas_set_font(Canvas* canvas, Font font); +/** Set custom drawing font + * + * @param canvas Canvas instance + * @param font Pointer to u8g2 const uint8_t* font array + */ +void canvas_set_custom_u8g2_font(Canvas* canvas, const uint8_t* font); + /** Draw string at position of baseline defined by x, y. * * @param canvas Canvas instance diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index 025b605fc94..d29d696fc5c 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,13.0,, +Version,+,13.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -546,6 +546,7 @@ Function,+,canvas_invert_color,void,Canvas* Function,+,canvas_reset,void,Canvas* Function,+,canvas_set_bitmap_mode,void,"Canvas*, _Bool" Function,+,canvas_set_color,void,"Canvas*, Color" +Function,+,canvas_set_custom_u8g2_font,void,"Canvas*, const uint8_t*" Function,+,canvas_set_font,void,"Canvas*, Font" Function,+,canvas_set_font_direction,void,"Canvas*, CanvasDirection" Function,+,canvas_string_width,uint16_t,"Canvas*, const char*" @@ -910,7 +911,6 @@ Function,-,furi_hal_flash_write_dword,void,"size_t, uint64_t" Function,+,furi_hal_gpio_add_int_callback,void,"const GpioPin*, GpioExtiCallback, void*" Function,+,furi_hal_gpio_disable_int_callback,void,const GpioPin* Function,+,furi_hal_gpio_enable_int_callback,void,const GpioPin* -Function,+,furi_hal_resources_get_ext_pin_number,int32_t,const GpioPin* Function,+,furi_hal_gpio_init,void,"const GpioPin*, const GpioMode, const GpioPull, const GpioSpeed" Function,+,furi_hal_gpio_init_ex,void,"const GpioPin*, const GpioMode, const GpioPull, const GpioSpeed, const GpioAltFn" Function,+,furi_hal_gpio_init_simple,void,"const GpioPin*, const GpioMode" @@ -1017,6 +1017,7 @@ Function,+,furi_hal_region_is_frequency_allowed,_Bool,uint32_t Function,+,furi_hal_region_is_provisioned,_Bool, Function,+,furi_hal_region_set,void,FuriHalRegion* Function,-,furi_hal_resources_deinit_early,void, +Function,+,furi_hal_resources_get_ext_pin_number,int32_t,const GpioPin* Function,-,furi_hal_resources_init,void, Function,-,furi_hal_resources_init_early,void, Function,+,furi_hal_rtc_datetime_to_timestamp,uint32_t,FuriHalRtcDateTime* diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 35985aebb97..6456555d8f6 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -632,6 +632,7 @@ Function,+,canvas_reset,void,Canvas* Function,+,canvas_set_bitmap_mode,void,"Canvas*, _Bool" Function,+,canvas_set_color,void,"Canvas*, Color" Function,+,canvas_set_font,void,"Canvas*, Font" +Function,+,canvas_set_custom_u8g2_font,void,"Canvas*, const uint8_t*" Function,+,canvas_set_font_direction,void,"Canvas*, CanvasDirection" Function,+,canvas_string_width,uint16_t,"Canvas*, const char*" Function,+,canvas_width,uint8_t,Canvas* From 00076deece707d61aed1f9cc01151b430b4f3a13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Such=C3=A1nek?= Date: Wed, 8 Feb 2023 10:35:38 +0100 Subject: [PATCH 384/824] SCons: do not include backup files in build (#2221) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * SCons: do not include backup files in build * fbt: now GlobRecursive by default excludes backup files * fbt: added backup file exclusion to plain libs Signed-off-by: Michal Suchanek Co-authored-by: hedger Co-authored-by: hedger Co-authored-by: あく --- assets/SConscript | 4 +++- firmware.scons | 3 ++- lib/misc.scons | 8 +++++++- scripts/fbt/util.py | 4 ++++ scripts/fbt_tools/fbt_assets.py | 4 +--- scripts/fbt_tools/sconsrecursiveglob.py | 8 ++++++-- 6 files changed, 23 insertions(+), 8 deletions(-) diff --git a/assets/SConscript b/assets/SConscript index ef5d83c79bf..21437aa301b 100644 --- a/assets/SConscript +++ b/assets/SConscript @@ -78,7 +78,9 @@ if assetsenv["IS_BASE_FIRMWARE"]: resources = assetsenv.Command( "#/assets/resources/Manifest", assetsenv.GlobRecursive( - "*", assetsenv.Dir("resources").srcnode(), exclude="Manifest" + "*", + assetsenv.Dir("resources").srcnode(), + exclude=["Manifest"], ), action=Action( '${PYTHON3} "${ASSETS_COMPILER}" manifest "${TARGET.dir.posix}" --timestamp=${GIT_UNIX_TIMESTAMP}', diff --git a/firmware.scons b/firmware.scons index 92f2d1a91c9..a094765af7b 100644 --- a/firmware.scons +++ b/firmware.scons @@ -2,6 +2,7 @@ Import("ENV", "fw_build_meta") from SCons.Errors import UserError from SCons.Node import FS + import itertools from fbt_extra.util import ( @@ -171,7 +172,7 @@ sources = [apps_c] # Gather sources only from app folders in current configuration sources.extend( itertools.chain.from_iterable( - fwenv.GlobRecursive(source_type, appdir.relpath, exclude="lib") + fwenv.GlobRecursive(source_type, appdir.relpath, exclude=["lib"]) for appdir, source_type in fwenv["APPBUILD"].get_builtin_app_folders() ) ) diff --git a/lib/misc.scons b/lib/misc.scons index cd2377ceb75..49b6b61d97b 100644 --- a/lib/misc.scons +++ b/lib/misc.scons @@ -1,5 +1,7 @@ Import("env") +from fbt.util import GLOB_FILE_EXCLUSION + env.Append( CPPPATH=[ "#/lib/digital_signal", @@ -39,7 +41,11 @@ libs_plain = [ ] for lib in libs_plain: - sources += Glob(lib + "/*.c*", source=True) + sources += Glob( + lib + "/*.c*", + exclude=GLOB_FILE_EXCLUSION, + source=True, + ) lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources) libenv.Install("${LIB_DIST_DIR}", lib) diff --git a/scripts/fbt/util.py b/scripts/fbt/util.py index ee756205882..7bdaea031fe 100644 --- a/scripts/fbt/util.py +++ b/scripts/fbt/util.py @@ -8,6 +8,10 @@ WINPATHSEP_RE = re.compile(r"\\([^\"'\\]|$)") +# Used by default when globbing for files with GlobRecursive +# Excludes all files ending with ~, usually created by editors as backup files +GLOB_FILE_EXCLUSION = ["*~"] + def tempfile_arg_esc_func(arg): arg = quote_spaces(arg) diff --git a/scripts/fbt_tools/fbt_assets.py b/scripts/fbt_tools/fbt_assets.py index e1748735874..d2a58f3fb82 100644 --- a/scripts/fbt_tools/fbt_assets.py +++ b/scripts/fbt_tools/fbt_assets.py @@ -27,9 +27,7 @@ def proto_emitter(target, source, env): def dolphin_emitter(target, source, env): res_root_dir = source[0].Dir(env["DOLPHIN_RES_TYPE"]) source = [res_root_dir] - source.extend( - env.GlobRecursive("*.*", res_root_dir.srcnode()), - ) + source.extend(env.GlobRecursive("*.*", res_root_dir.srcnode())) target_base_dir = target[0] env.Replace(_DOLPHIN_OUT_DIR=target[0]) diff --git a/scripts/fbt_tools/sconsrecursiveglob.py b/scripts/fbt_tools/sconsrecursiveglob.py index 32ff55ea10c..fbcee965b1a 100644 --- a/scripts/fbt_tools/sconsrecursiveglob.py +++ b/scripts/fbt_tools/sconsrecursiveglob.py @@ -1,7 +1,11 @@ import SCons +from SCons.Script import Flatten +from fbt.util import GLOB_FILE_EXCLUSION -def GlobRecursive(env, pattern, node=".", exclude=None): +def GlobRecursive(env, pattern, node=".", exclude=[]): + exclude = list(set(Flatten(exclude) + GLOB_FILE_EXCLUSION)) + # print(f"Starting glob for {pattern} from {node} (exclude: {exclude})") results = [] if isinstance(node, str): node = env.Dir(node) @@ -13,7 +17,7 @@ def GlobRecursive(env, pattern, node=".", exclude=None): source=True, exclude=exclude, ) - # print(f"Glob for {pattern} from {node}: {results}") + # print(f"Glob result for {pattern} from {node}: {results}") return results From 20f98050f2030e3a8f1509421cc43729617919b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Wed, 8 Feb 2023 19:38:09 +0900 Subject: [PATCH 385/824] Github: disable f18 build (#2375) --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 080e7c2c220..a6c9219c24c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,7 +10,7 @@ on: pull_request: env: - TARGETS: f7 f18 + TARGETS: f7 DEFAULT_TARGET: f7 FBT_TOOLCHAIN_PATH: /runner/_work From cee9b640b30502f65ab1081134a891a1e6841343 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Wed, 8 Feb 2023 20:45:33 +0900 Subject: [PATCH 386/824] Update Missing SD Card icon from PR 2373 (#2376) --- assets/icons/SDCard/SDQuestion_35x43.png | Bin 1950 -> 2042 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/assets/icons/SDCard/SDQuestion_35x43.png b/assets/icons/SDCard/SDQuestion_35x43.png index 9b9c9a58e3257f926677533f8cc99ffb19dd74f5..257ab1d852460ad83dffa5989e765902fcb09578 100644 GIT binary patch delta 523 zcmbQo|BHWugar#T0}w3HVF!|o#X;^)4C~IxyaaMs(j9#r85lP9bN@+X1@hSfd_r9R z|NjqUh90_HH&Ic7@%uz8ezSl|hu0(V4SA9t0%7SFZhTNGdi(6U zqtX3c^NQF~FPUjxUAjEGb!OR09>>O*+iqu6ZhUtYncXoe)wdUX*U5dow13$b cea!*}8+O&+FAqd3fX-m>boFyt=akR{04GeM8vp%dtcP1$Xn~K~5UtcSa z%;J*#qDngjo1&C7tKif^yUBA|b~EXNDDla)tXmZHfpYpVNgxwl-{k*nGF%YtPKoJu zlNYnuaYJ>0`G45tb)bBg)Z*l#%z~24{5+VUK`w3}W`z~d*xb~TL_3Aaf$YNd+7>6) z0X=Zo)5S3)qV?@GZ@$9{JgkPN{;vPCYk}`2GmG;QF`|a6IRxAKa|IY)wstHrWSDT* z#4q^7^nZ)qe+@L{O>(>JubebcTr>xA;mo&QS_`}msD%H&0j`ym{bIX}= zOW&%!e!SiJ%|z?^88d$Te0N6PAoTmLgw@r0!CkAW-_AH!8nnmtgxFKVBQwS7oqSd? aS1_D$pUltxg^3;L9|liXKbLh*2~7Y$MyGH9 From 99253a0e28f45476d631ac8db8c8717d13e0bac8 Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Wed, 8 Feb 2023 17:20:42 +0400 Subject: [PATCH 387/824] [FL-3093, FL-3087] SubGhz: Fix Raw write, add short duration filter setting (#2300) * SubGhz: Fix recording RAW files, sometimes could not start at a high level * SubGhz: subghz_worker, add short duration filter setting * SubGhz: capture raw timings in cli. Furi: clear pending interrupts on ISR set/reset * SubGhz: fix start duration in furi_hal_subghz_start_async_rx * [FL-3093] SubGhz: hopping issue in some regions * [FL-3087] SubGhz: fix delete-ok issue * SubGhz: remove copypasta from rx_raw cli command Co-authored-by: Aleksandr Kutuzov --- .../scenes/subghz_scene_delete_success.c | 5 -- .../scenes/subghz_scene_receiver_info.c | 15 ++++ applications/main/subghz/subghz_cli.c | 83 ++++++++++++++++++- firmware/targets/f7/api_symbols.csv | 3 +- .../targets/f7/furi_hal/furi_hal_interrupt.c | 17 ++++ .../targets/f7/furi_hal/furi_hal_subghz.c | 11 ++- lib/subghz/protocols/raw.c | 1 + lib/subghz/subghz_worker.c | 38 ++++----- lib/subghz/subghz_worker.h | 8 ++ 9 files changed, 151 insertions(+), 30 deletions(-) diff --git a/applications/main/subghz/scenes/subghz_scene_delete_success.c b/applications/main/subghz/scenes/subghz_scene_delete_success.c index 8a254624344..4d9f33e37e0 100644 --- a/applications/main/subghz/scenes/subghz_scene_delete_success.c +++ b/applications/main/subghz/scenes/subghz_scene_delete_success.c @@ -28,13 +28,8 @@ bool subghz_scene_delete_success_on_event(void* context, SceneManagerEvent event if(event.event == SubGhzCustomEventSceneDeleteSuccess) { 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)) { - // Commented so that the user doesn't have to press - // back twice to get to the main SubGhz menu after - // deleting a file. - //scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaved); } else { scene_manager_search_and_switch_to_previous_scene( subghz->scene_manager, SubGhzSceneStart); diff --git a/applications/main/subghz/scenes/subghz_scene_receiver_info.c b/applications/main/subghz/scenes/subghz_scene_receiver_info.c index c0f90157849..4733b0e1d1a 100644 --- a/applications/main/subghz/scenes/subghz_scene_receiver_info.c +++ b/applications/main/subghz/scenes/subghz_scene_receiver_info.c @@ -129,6 +129,21 @@ bool subghz_scene_receiver_info_on_event(void* context, SceneManagerEvent event) subghz_history_get_raw_data( subghz->txrx->history, subghz->txrx->idx_menu_chosen))) { scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowOnlyRx); + if(subghz->txrx->txrx_state == SubGhzTxRxStateTx) { + subghz_tx_stop(subghz); + } + if(subghz->txrx->txrx_state == SubGhzTxRxStateIDLE) { + subghz_begin( + subghz, + subghz_setting_get_preset_data_by_name( + subghz->setting, + furi_string_get_cstr(subghz->txrx->preset->name))); + subghz_rx(subghz, subghz->txrx->preset->frequency); + } + if(subghz->txrx->hopper_state == SubGhzHopperStatePause) { + subghz->txrx->hopper_state = SubGhzHopperStateRunnig; + } + subghz->state_notifications = SubGhzNotificationStateRx; } else { subghz->state_notifications = SubGhzNotificationStateTx; } diff --git a/applications/main/subghz/subghz_cli.c b/applications/main/subghz/subghz_cli.c index 536cb535e65..9271443a843 100644 --- a/applications/main/subghz/subghz_cli.c +++ b/applications/main/subghz/subghz_cli.c @@ -309,6 +309,81 @@ void subghz_cli_command_rx(Cli* cli, FuriString* args, void* context) { free(instance); } +void subghz_cli_command_rx_raw(Cli* cli, FuriString* args, void* context) { + UNUSED(context); + uint32_t frequency = 433920000; + + if(furi_string_size(args)) { + int ret = sscanf(furi_string_get_cstr(args), "%lu", &frequency); + if(ret != 1) { + printf("sscanf returned %d, frequency: %lu\r\n", ret, frequency); + cli_print_usage("subghz rx", "", furi_string_get_cstr(args)); + return; + } + if(!furi_hal_subghz_is_frequency_valid(frequency)) { + printf( + "Frequency must be in " SUBGHZ_FREQUENCY_RANGE_STR " range, not %lu\r\n", + frequency); + return; + } + } + + // Allocate context and buffers + SubGhzCliCommandRx* instance = malloc(sizeof(SubGhzCliCommandRx)); + instance->stream = + furi_stream_buffer_alloc(sizeof(LevelDuration) * 1024, sizeof(LevelDuration)); + furi_check(instance->stream); + + // Configure radio + furi_hal_subghz_reset(); + furi_hal_subghz_load_preset(FuriHalSubGhzPresetOok270Async); + frequency = furi_hal_subghz_set_frequency_and_path(frequency); + furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow); + + furi_hal_power_suppress_charge_enter(); + + // Prepare and start RX + furi_hal_subghz_start_async_rx(subghz_cli_command_rx_capture_callback, instance); + + // Wait for packets to arrive + printf("Listening at %lu. Press CTRL+C to stop\r\n", frequency); + LevelDuration level_duration; + size_t counter = 0; + while(!cli_cmd_interrupt_received(cli)) { + int ret = furi_stream_buffer_receive( + instance->stream, &level_duration, sizeof(LevelDuration), 10); + if(ret == 0) { + continue; + } + if(ret != sizeof(LevelDuration)) { + puts("stream corrupt"); + break; + } + if(level_duration_is_reset(level_duration)) { + puts(". "); + } else { + bool level = level_duration_get_level(level_duration); + uint32_t duration = level_duration_get_duration(level_duration); + printf("%c%lu ", level ? '+' : '-', duration); + } + furi_thread_stdout_flush(); + counter++; + if(counter > 255) { + puts("\r\n"); + counter = 0; + } + } + + // Shutdown radio + furi_hal_subghz_stop_async_rx(); + furi_hal_subghz_sleep(); + + furi_hal_power_suppress_charge_exit(); + + // Cleanup + furi_stream_buffer_free(instance->stream); + free(instance); +} void subghz_cli_command_decode_raw(Cli* cli, FuriString* args, void* context) { UNUSED(context); FuriString* file_name; @@ -431,7 +506,8 @@ static void subghz_cli_command_print_usage() { printf("\tchat \t - Chat with other Flippers\r\n"); printf( "\ttx <3 byte Key: in hex> \t - Transmitting key\r\n"); - printf("\trx \t - Reception key\r\n"); + printf("\trx \t - Receive\r\n"); + printf("\trx_raw \t - Receive RAW\r\n"); printf("\tdecode_raw \t - Testing\r\n"); if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { @@ -733,6 +809,11 @@ static void subghz_cli_command(Cli* cli, FuriString* args, void* context) { break; } + if(furi_string_cmp_str(cmd, "rx_raw") == 0) { + subghz_cli_command_rx_raw(cli, args, context); + break; + } + if(furi_string_cmp_str(cmd, "decode_raw") == 0) { subghz_cli_command_decode_raw(cli, args, context); break; diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 6456555d8f6..65ab375ef49 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,13.0,, +Version,+,13.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -2675,6 +2675,7 @@ Function,+,subghz_worker_free,void,SubGhzWorker* Function,+,subghz_worker_is_running,_Bool,SubGhzWorker* Function,+,subghz_worker_rx_callback,void,"_Bool, uint32_t, void*" Function,+,subghz_worker_set_context,void,"SubGhzWorker*, void*" +Function,+,subghz_worker_set_filter,void,"SubGhzWorker*, uint16_t" Function,+,subghz_worker_set_overrun_callback,void,"SubGhzWorker*, SubGhzWorkerOverrunCallback" Function,+,subghz_worker_set_pair_callback,void,"SubGhzWorker*, SubGhzWorkerPairCallback" Function,+,subghz_worker_start,void,SubGhzWorker* diff --git a/firmware/targets/f7/furi_hal/furi_hal_interrupt.c b/firmware/targets/f7/furi_hal/furi_hal_interrupt.c index 1b1132d0c3b..b5639d2300c 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_interrupt.c +++ b/firmware/targets/f7/furi_hal/furi_hal_interrupt.c @@ -74,6 +74,21 @@ __attribute__((always_inline)) static inline void NVIC_EnableIRQ(furi_hal_interrupt_irqn[index]); } +__attribute__((always_inline)) static inline void + furi_hal_interrupt_clear_pending(FuriHalInterruptId index) { + NVIC_ClearPendingIRQ(furi_hal_interrupt_irqn[index]); +} + +__attribute__((always_inline)) static inline void + furi_hal_interrupt_get_pending(FuriHalInterruptId index) { + NVIC_GetPendingIRQ(furi_hal_interrupt_irqn[index]); +} + +__attribute__((always_inline)) static inline void + furi_hal_interrupt_set_pending(FuriHalInterruptId index) { + NVIC_SetPendingIRQ(furi_hal_interrupt_irqn[index]); +} + __attribute__((always_inline)) static inline void furi_hal_interrupt_disable(FuriHalInterruptId index) { NVIC_DisableIRQ(furi_hal_interrupt_irqn[index]); @@ -123,6 +138,7 @@ void furi_hal_interrupt_set_isr_ex( // Pre ISR clear furi_assert(furi_hal_interrupt_isr[index].isr != NULL); furi_hal_interrupt_disable(index); + furi_hal_interrupt_clear_pending(index); } furi_hal_interrupt_isr[index].isr = isr; @@ -131,6 +147,7 @@ void furi_hal_interrupt_set_isr_ex( if(isr) { // Post ISR set + furi_hal_interrupt_clear_pending(index); furi_hal_interrupt_enable(index, priority); } else { // Post ISR clear diff --git a/firmware/targets/f7/furi_hal/furi_hal_subghz.c b/firmware/targets/f7/furi_hal/furi_hal_subghz.c index a6d275308ea..59646461352 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_subghz.c +++ b/firmware/targets/f7/furi_hal/furi_hal_subghz.c @@ -438,7 +438,7 @@ void furi_hal_subghz_start_async_rx(FuriHalSubGhzCaptureCallback callback, void* TIM_InitStruct.Prescaler = 64 - 1; TIM_InitStruct.CounterMode = LL_TIM_COUNTERMODE_UP; TIM_InitStruct.Autoreload = 0x7FFFFFFE; - TIM_InitStruct.ClockDivision = LL_TIM_CLOCKDIVISION_DIV4; + TIM_InitStruct.ClockDivision = LL_TIM_CLOCKDIVISION_DIV4; // Clock division for capture filter LL_TIM_Init(TIM2, &TIM_InitStruct); // Timer: advanced @@ -455,13 +455,15 @@ void furi_hal_subghz_start_async_rx(FuriHalSubGhzCaptureCallback callback, void* LL_TIM_IC_SetActiveInput(TIM2, LL_TIM_CHANNEL_CH1, LL_TIM_ACTIVEINPUT_INDIRECTTI); LL_TIM_IC_SetPrescaler(TIM2, LL_TIM_CHANNEL_CH1, LL_TIM_ICPSC_DIV1); LL_TIM_IC_SetPolarity(TIM2, LL_TIM_CHANNEL_CH1, LL_TIM_IC_POLARITY_FALLING); - LL_TIM_IC_SetFilter(TIM2, LL_TIM_CHANNEL_CH1, LL_TIM_IC_FILTER_FDIV1); // Timer: channel 2 direct LL_TIM_IC_SetActiveInput(TIM2, LL_TIM_CHANNEL_CH2, LL_TIM_ACTIVEINPUT_DIRECTTI); LL_TIM_IC_SetPrescaler(TIM2, LL_TIM_CHANNEL_CH2, LL_TIM_ICPSC_DIV1); LL_TIM_IC_SetPolarity(TIM2, LL_TIM_CHANNEL_CH2, LL_TIM_IC_POLARITY_RISING); - LL_TIM_IC_SetFilter(TIM2, LL_TIM_CHANNEL_CH2, LL_TIM_IC_FILTER_FDIV32_N8); + LL_TIM_IC_SetFilter( + TIM2, + LL_TIM_CHANNEL_CH2, + LL_TIM_IC_FILTER_FDIV32_N8); // Capture filter: 1/(64000000/64/4/32*8) = 16us // ISR setup furi_hal_interrupt_set_isr(FuriHalInterruptIdTIM2, furi_hal_subghz_capture_ISR, NULL); @@ -481,6 +483,9 @@ void furi_hal_subghz_start_async_rx(FuriHalSubGhzCaptureCallback callback, void* // Switch to RX furi_hal_subghz_rx(); + + //Clear the variable after the end of the session + furi_hal_subghz_capture_delta_duration = 0; } void furi_hal_subghz_stop_async_rx() { diff --git a/lib/subghz/protocols/raw.c b/lib/subghz/protocols/raw.c index b639c93b924..ac3492e7803 100644 --- a/lib/subghz/protocols/raw.c +++ b/lib/subghz/protocols/raw.c @@ -159,6 +159,7 @@ bool subghz_protocol_raw_save_to_file_init( instance->upload_raw = malloc(SUBGHZ_DOWNLOAD_MAX_SIZE * sizeof(int32_t)); instance->file_is_open = RAWFileIsOpenWrite; instance->sample_write = 0; + instance->last_level = false; instance->pause = false; init = true; } while(0); diff --git a/lib/subghz/subghz_worker.c b/lib/subghz/subghz_worker.c index 35e39988585..50b5aba5116 100644 --- a/lib/subghz/subghz_worker.c +++ b/lib/subghz/subghz_worker.c @@ -12,7 +12,6 @@ struct SubGhzWorker { volatile bool overrun; LevelDuration filter_level_duration; - bool filter_running; uint16_t filter_duration; SubGhzWorkerOverrunCallback overrun_callback; @@ -59,24 +58,19 @@ static int32_t subghz_worker_thread_callback(void* context) { bool level = level_duration_get_level(level_duration); uint32_t duration = level_duration_get_duration(level_duration); - if(instance->filter_running) { - if((duration < instance->filter_duration) || - (instance->filter_level_duration.level == level)) { - instance->filter_level_duration.duration += duration; - - } else if(instance->filter_level_duration.level != level) { - if(instance->pair_callback) - instance->pair_callback( - instance->context, - instance->filter_level_duration.level, - instance->filter_level_duration.duration); - - instance->filter_level_duration.duration = duration; - instance->filter_level_duration.level = level; - } - } else { + if((duration < instance->filter_duration) || + (instance->filter_level_duration.level == level)) { + instance->filter_level_duration.duration += duration; + + } else if(instance->filter_level_duration.level != level) { if(instance->pair_callback) - instance->pair_callback(instance->context, level, duration); + instance->pair_callback( + instance->context, + instance->filter_level_duration.level, + instance->filter_level_duration.duration); + + instance->filter_level_duration.duration = duration; + instance->filter_level_duration.level = level; } } } @@ -94,8 +88,7 @@ SubGhzWorker* subghz_worker_alloc() { instance->stream = furi_stream_buffer_alloc(sizeof(LevelDuration) * 4096, sizeof(LevelDuration)); - //setting filter - instance->filter_running = true; + //setting default filter in us instance->filter_duration = 30; return instance; @@ -149,3 +142,8 @@ bool subghz_worker_is_running(SubGhzWorker* instance) { furi_assert(instance); return instance->running; } + +void subghz_worker_set_filter(SubGhzWorker* instance, uint16_t timeout) { + furi_assert(instance); + instance->filter_duration = timeout; +} \ No newline at end of file diff --git a/lib/subghz/subghz_worker.h b/lib/subghz/subghz_worker.h index f85b1fdc7aa..657278f5053 100644 --- a/lib/subghz/subghz_worker.h +++ b/lib/subghz/subghz_worker.h @@ -67,6 +67,14 @@ void subghz_worker_stop(SubGhzWorker* instance); */ bool subghz_worker_is_running(SubGhzWorker* instance); +/** + * Short duration filter setting. + * glues short durations into 1. The default setting is 30 us, if set to 0 the filter will be disabled + * @param instance Pointer to a SubGhzWorker instance + * @param timeout time in us + */ +void subghz_worker_set_filter(SubGhzWorker* instance, uint16_t timeout); + #ifdef __cplusplus } #endif From b1f581239bbc83af1720e7180e6bf4a7c319288e Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 8 Feb 2023 18:01:00 +0300 Subject: [PATCH 388/824] BadUSB: Keyboard Layouts (#2256) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * BadUSB: Keyboard Layouts * Apply requested changes pt1 * Add layout file check when we loading config Co-authored-by: Nikolay Minaylov Co-authored-by: あく --- applications/main/bad_usb/bad_usb_app.c | 72 +++++++++++++- applications/main/bad_usb/bad_usb_app_i.h | 11 ++- applications/main/bad_usb/bad_usb_script.c | 51 ++++++++-- applications/main/bad_usb/bad_usb_script.h | 2 + .../main/bad_usb/bad_usb_settings_filename.h | 3 + .../bad_usb/scenes/bad_usb_scene_config.c | 53 ++++++++++ .../bad_usb/scenes/bad_usb_scene_config.h | 2 + .../scenes/bad_usb_scene_config_layout.c | 50 ++++++++++ .../scenes/bad_usb_scene_file_select.c | 14 ++- .../main/bad_usb/scenes/bad_usb_scene_work.c | 27 ++++-- .../main/bad_usb/views/bad_usb_view.c | 91 ++++++++++++------ .../main/bad_usb/views/bad_usb_view.h | 6 +- assets/icons/Archive/keyboard_10px.png | Bin 0 -> 147 bytes .../resources/badusb/assets/layouts/ba-BA.kl | Bin 0 -> 256 bytes .../resources/badusb/assets/layouts/cz_CS.kl | Bin 0 -> 256 bytes .../resources/badusb/assets/layouts/da-DA.kl | Bin 0 -> 256 bytes .../resources/badusb/assets/layouts/de-CH.kl | Bin 0 -> 256 bytes .../resources/badusb/assets/layouts/de-DE.kl | Bin 0 -> 256 bytes .../resources/badusb/assets/layouts/dvorak.kl | Bin 0 -> 256 bytes .../resources/badusb/assets/layouts/en-UK.kl | Bin 0 -> 256 bytes .../resources/badusb/assets/layouts/en-US.kl | Bin 0 -> 256 bytes .../resources/badusb/assets/layouts/es-ES.kl | Bin 0 -> 256 bytes .../resources/badusb/assets/layouts/fr-BE.kl | Bin 0 -> 256 bytes .../resources/badusb/assets/layouts/fr-CH.kl | Bin 0 -> 256 bytes .../resources/badusb/assets/layouts/fr-FR.kl | Bin 0 -> 256 bytes .../resources/badusb/assets/layouts/hr-HR.kl | Bin 0 -> 256 bytes .../resources/badusb/assets/layouts/hu-HU.kl | Bin 0 -> 256 bytes .../resources/badusb/assets/layouts/it-IT.kl | Bin 0 -> 256 bytes .../resources/badusb/assets/layouts/nb-NO.kl | Bin 0 -> 256 bytes .../resources/badusb/assets/layouts/nl-NL.kl | Bin 0 -> 256 bytes .../resources/badusb/assets/layouts/pt-BR.kl | Bin 0 -> 256 bytes .../resources/badusb/assets/layouts/pt-PT.kl | Bin 0 -> 256 bytes .../resources/badusb/assets/layouts/si-SI.kl | Bin 0 -> 256 bytes .../resources/badusb/assets/layouts/sk-SK.kl | Bin 0 -> 256 bytes .../resources/badusb/assets/layouts/sv-SE.kl | Bin 0 -> 256 bytes .../resources/badusb/assets/layouts/tr-TR.kl | Bin 0 -> 256 bytes 36 files changed, 323 insertions(+), 59 deletions(-) create mode 100644 applications/main/bad_usb/bad_usb_settings_filename.h create mode 100644 applications/main/bad_usb/scenes/bad_usb_scene_config.c create mode 100644 applications/main/bad_usb/scenes/bad_usb_scene_config_layout.c create mode 100644 assets/icons/Archive/keyboard_10px.png create mode 100644 assets/resources/badusb/assets/layouts/ba-BA.kl create mode 100644 assets/resources/badusb/assets/layouts/cz_CS.kl create mode 100644 assets/resources/badusb/assets/layouts/da-DA.kl create mode 100644 assets/resources/badusb/assets/layouts/de-CH.kl create mode 100644 assets/resources/badusb/assets/layouts/de-DE.kl create mode 100644 assets/resources/badusb/assets/layouts/dvorak.kl create mode 100644 assets/resources/badusb/assets/layouts/en-UK.kl create mode 100644 assets/resources/badusb/assets/layouts/en-US.kl create mode 100644 assets/resources/badusb/assets/layouts/es-ES.kl create mode 100644 assets/resources/badusb/assets/layouts/fr-BE.kl create mode 100644 assets/resources/badusb/assets/layouts/fr-CH.kl create mode 100644 assets/resources/badusb/assets/layouts/fr-FR.kl create mode 100644 assets/resources/badusb/assets/layouts/hr-HR.kl create mode 100644 assets/resources/badusb/assets/layouts/hu-HU.kl create mode 100644 assets/resources/badusb/assets/layouts/it-IT.kl create mode 100644 assets/resources/badusb/assets/layouts/nb-NO.kl create mode 100644 assets/resources/badusb/assets/layouts/nl-NL.kl create mode 100644 assets/resources/badusb/assets/layouts/pt-BR.kl create mode 100644 assets/resources/badusb/assets/layouts/pt-PT.kl create mode 100644 assets/resources/badusb/assets/layouts/si-SI.kl create mode 100755 assets/resources/badusb/assets/layouts/sk-SK.kl create mode 100644 assets/resources/badusb/assets/layouts/sv-SE.kl create mode 100644 assets/resources/badusb/assets/layouts/tr-TR.kl diff --git a/applications/main/bad_usb/bad_usb_app.c b/applications/main/bad_usb/bad_usb_app.c index 6fd29cd7047..5f2aa4789f2 100644 --- a/applications/main/bad_usb/bad_usb_app.c +++ b/applications/main/bad_usb/bad_usb_app.c @@ -1,9 +1,12 @@ #include "bad_usb_app_i.h" +#include "bad_usb_settings_filename.h" #include #include #include #include +#define BAD_USB_SETTINGS_PATH BAD_USB_APP_BASE_FOLDER "/" BAD_USB_SETTINGS_FILE_NAME + static bool bad_usb_app_custom_event_callback(void* context, uint32_t event) { furi_assert(context); BadUsbApp* app = context; @@ -22,15 +25,62 @@ static void bad_usb_app_tick_event_callback(void* context) { scene_manager_handle_tick_event(app->scene_manager); } +static void bad_usb_load_settings(BadUsbApp* app) { + File* settings_file = storage_file_alloc(furi_record_open(RECORD_STORAGE)); + if(storage_file_open(settings_file, BAD_USB_SETTINGS_PATH, FSAM_READ, FSOM_OPEN_EXISTING)) { + char chr; + while((storage_file_read(settings_file, &chr, 1) == 1) && + !storage_file_eof(settings_file) && !isspace(chr)) { + furi_string_push_back(app->keyboard_layout, chr); + } + } else { + furi_string_reset(app->keyboard_layout); + } + storage_file_close(settings_file); + storage_file_free(settings_file); + + if(!furi_string_empty(app->keyboard_layout)) { + Storage* fs_api = furi_record_open(RECORD_STORAGE); + FileInfo layout_file_info; + FS_Error file_check_err = storage_common_stat( + fs_api, furi_string_get_cstr(app->keyboard_layout), &layout_file_info); + furi_record_close(RECORD_STORAGE); + if(file_check_err != FSE_OK) { + furi_string_reset(app->keyboard_layout); + return; + } + if(layout_file_info.size != 256) { + furi_string_reset(app->keyboard_layout); + } + } +} + +static void bad_usb_save_settings(BadUsbApp* app) { + File* settings_file = storage_file_alloc(furi_record_open(RECORD_STORAGE)); + if(storage_file_open(settings_file, BAD_USB_SETTINGS_PATH, FSAM_WRITE, FSOM_OPEN_ALWAYS)) { + storage_file_write( + settings_file, + furi_string_get_cstr(app->keyboard_layout), + furi_string_size(app->keyboard_layout)); + storage_file_write(settings_file, "\n", 1); + } + storage_file_close(settings_file); + storage_file_free(settings_file); +} + BadUsbApp* bad_usb_app_alloc(char* arg) { BadUsbApp* app = malloc(sizeof(BadUsbApp)); - app->file_path = furi_string_alloc(); + app->bad_usb_script = NULL; + app->file_path = furi_string_alloc(); + app->keyboard_layout = furi_string_alloc(); if(arg && strlen(arg)) { furi_string_set(app->file_path, arg); } + bad_usb_load_settings(app); + app->gui = furi_record_open(RECORD_GUI); app->notifications = furi_record_open(RECORD_NOTIFICATION); app->dialogs = furi_record_open(RECORD_DIALOGS); @@ -53,6 +103,10 @@ BadUsbApp* bad_usb_app_alloc(char* arg) { view_dispatcher_add_view( app->view_dispatcher, BadUsbAppViewError, widget_get_view(app->widget)); + app->submenu = submenu_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, BadUsbAppViewConfig, submenu_get_view(app->submenu)); + app->bad_usb_view = bad_usb_alloc(); view_dispatcher_add_view( app->view_dispatcher, BadUsbAppViewWork, bad_usb_get_view(app->bad_usb_view)); @@ -64,9 +118,11 @@ BadUsbApp* bad_usb_app_alloc(char* arg) { scene_manager_next_scene(app->scene_manager, BadUsbSceneError); } else { if(!furi_string_empty(app->file_path)) { + app->bad_usb_script = bad_usb_script_open(app->file_path); + bad_usb_script_set_keyboard_layout(app->bad_usb_script, app->keyboard_layout); scene_manager_next_scene(app->scene_manager, BadUsbSceneWork); } else { - furi_string_set(app->file_path, BAD_USB_APP_PATH_FOLDER); + furi_string_set(app->file_path, BAD_USB_APP_BASE_FOLDER); scene_manager_next_scene(app->scene_manager, BadUsbSceneFileSelect); } } @@ -77,6 +133,11 @@ BadUsbApp* bad_usb_app_alloc(char* arg) { void bad_usb_app_free(BadUsbApp* app) { furi_assert(app); + if(app->bad_usb_script) { + bad_usb_script_close(app->bad_usb_script); + app->bad_usb_script = NULL; + } + // Views view_dispatcher_remove_view(app->view_dispatcher, BadUsbAppViewWork); bad_usb_free(app->bad_usb_view); @@ -85,6 +146,10 @@ void bad_usb_app_free(BadUsbApp* app) { view_dispatcher_remove_view(app->view_dispatcher, BadUsbAppViewError); widget_free(app->widget); + // Submenu + view_dispatcher_remove_view(app->view_dispatcher, BadUsbAppViewConfig); + submenu_free(app->submenu); + // View dispatcher view_dispatcher_free(app->view_dispatcher); scene_manager_free(app->scene_manager); @@ -94,7 +159,10 @@ void bad_usb_app_free(BadUsbApp* app) { furi_record_close(RECORD_NOTIFICATION); furi_record_close(RECORD_DIALOGS); + bad_usb_save_settings(app); + furi_string_free(app->file_path); + furi_string_free(app->keyboard_layout); free(app); } diff --git a/applications/main/bad_usb/bad_usb_app_i.h b/applications/main/bad_usb/bad_usb_app_i.h index f333c36d81d..1bd1964c86c 100644 --- a/applications/main/bad_usb/bad_usb_app_i.h +++ b/applications/main/bad_usb/bad_usb_app_i.h @@ -15,8 +15,10 @@ #include #include "views/bad_usb_view.h" -#define BAD_USB_APP_PATH_FOLDER ANY_PATH("badusb") -#define BAD_USB_APP_EXTENSION ".txt" +#define BAD_USB_APP_BASE_FOLDER ANY_PATH("badusb") +#define BAD_USB_APP_PATH_LAYOUT_FOLDER BAD_USB_APP_BASE_FOLDER "/assets/layouts" +#define BAD_USB_APP_SCRIPT_EXTENSION ".txt" +#define BAD_USB_APP_LAYOUT_EXTENSION ".kl" typedef enum { BadUsbAppErrorNoFiles, @@ -30,9 +32,11 @@ struct BadUsbApp { NotificationApp* notifications; DialogsApp* dialogs; Widget* widget; + Submenu* submenu; BadUsbAppError error; FuriString* file_path; + FuriString* keyboard_layout; BadUsb* bad_usb_view; BadUsbScript* bad_usb_script; }; @@ -40,4 +44,5 @@ struct BadUsbApp { typedef enum { BadUsbAppViewError, BadUsbAppViewWork, -} BadUsbAppView; + BadUsbAppViewConfig, +} BadUsbAppView; \ No newline at end of file diff --git a/applications/main/bad_usb/bad_usb_script.c b/applications/main/bad_usb/bad_usb_script.c index e2281133fa7..1416acfee18 100644 --- a/applications/main/bad_usb/bad_usb_script.c +++ b/applications/main/bad_usb/bad_usb_script.c @@ -16,6 +16,9 @@ #define SCRIPT_STATE_END (-2) #define SCRIPT_STATE_NEXT_LINE (-3) +#define BADUSB_ASCII_TO_KEY(script, x) \ + (((uint8_t)x < 128) ? (script->layout[(uint8_t)x]) : HID_KEYBOARD_NONE) + typedef enum { WorkerEvtToggle = (1 << 0), WorkerEvtEnd = (1 << 1), @@ -28,6 +31,7 @@ struct BadUsbScript { BadUsbState st; FuriString* file_path; uint32_t defdelay; + uint16_t layout[128]; FuriThread* thread; uint8_t file_buf[FILE_BUFFER_LEN + 1]; uint8_t buf_start; @@ -205,10 +209,10 @@ static bool ducky_altstring(const char* param) { return state; } -static bool ducky_string(const char* param) { +static bool ducky_string(BadUsbScript* bad_usb, const char* param) { uint32_t i = 0; while(param[i] != '\0') { - uint16_t keycode = HID_ASCII_TO_KEY(param[i]); + uint16_t keycode = BADUSB_ASCII_TO_KEY(bad_usb, param[i]); if(keycode != HID_KEYBOARD_NONE) { furi_hal_hid_kb_press(keycode); furi_hal_hid_kb_release(keycode); @@ -218,7 +222,7 @@ static bool ducky_string(const char* param) { return true; } -static uint16_t ducky_get_keycode(const char* param, bool accept_chars) { +static uint16_t ducky_get_keycode(BadUsbScript* bad_usb, const char* param, bool accept_chars) { for(size_t i = 0; i < (sizeof(ducky_keys) / sizeof(ducky_keys[0])); i++) { size_t key_cmd_len = strlen(ducky_keys[i].name); if((strncmp(param, ducky_keys[i].name, key_cmd_len) == 0) && @@ -227,7 +231,7 @@ static uint16_t ducky_get_keycode(const char* param, bool accept_chars) { } } if((accept_chars) && (strlen(param) > 0)) { - return (HID_ASCII_TO_KEY(param[0]) & 0xFF); + return (BADUSB_ASCII_TO_KEY(bad_usb, param[0]) & 0xFF); } return 0; } @@ -276,7 +280,7 @@ static int32_t } else if(strncmp(line_tmp, ducky_cmd_string, strlen(ducky_cmd_string)) == 0) { // STRING line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; - state = ducky_string(line_tmp); + state = ducky_string(bad_usb, line_tmp); if(!state && error != NULL) { snprintf(error, error_len, "Invalid string %s", line_tmp); } @@ -312,14 +316,14 @@ static int32_t } else if(strncmp(line_tmp, ducky_cmd_sysrq, strlen(ducky_cmd_sysrq)) == 0) { // SYSRQ line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; - uint16_t key = ducky_get_keycode(line_tmp, true); + uint16_t key = ducky_get_keycode(bad_usb, line_tmp, true); furi_hal_hid_kb_press(KEY_MOD_LEFT_ALT | HID_KEYBOARD_PRINT_SCREEN); furi_hal_hid_kb_press(key); furi_hal_hid_kb_release_all(); return (0); } else { // Special keys + modifiers - uint16_t key = ducky_get_keycode(line_tmp, false); + uint16_t key = ducky_get_keycode(bad_usb, line_tmp, false); if(key == HID_KEYBOARD_NONE) { if(error != NULL) { snprintf(error, error_len, "No keycode defined for %s", line_tmp); @@ -329,7 +333,7 @@ static int32_t if((key & 0xFF00) != 0) { // It's a modifier key line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; - key |= ducky_get_keycode(line_tmp, true); + key |= ducky_get_keycode(bad_usb, line_tmp, true); } furi_hal_hid_kb_press(key); furi_hal_hid_kb_release(key); @@ -650,12 +654,19 @@ static int32_t bad_usb_worker(void* context) { return 0; } +static void bad_usb_script_set_default_keyboard_layout(BadUsbScript* bad_usb) { + furi_assert(bad_usb); + memset(bad_usb->layout, HID_KEYBOARD_NONE, sizeof(bad_usb->layout)); + memcpy(bad_usb->layout, hid_asciimap, MIN(sizeof(hid_asciimap), sizeof(bad_usb->layout))); +} + BadUsbScript* bad_usb_script_open(FuriString* file_path) { furi_assert(file_path); BadUsbScript* bad_usb = malloc(sizeof(BadUsbScript)); bad_usb->file_path = furi_string_alloc(); furi_string_set(bad_usb->file_path, file_path); + bad_usb_script_set_default_keyboard_layout(bad_usb); bad_usb->st.state = BadUsbStateInit; bad_usb->st.error[0] = '\0'; @@ -674,6 +685,30 @@ void bad_usb_script_close(BadUsbScript* bad_usb) { free(bad_usb); } +void bad_usb_script_set_keyboard_layout(BadUsbScript* bad_usb, FuriString* layout_path) { + furi_assert(bad_usb); + + if((bad_usb->st.state == BadUsbStateRunning) || (bad_usb->st.state == BadUsbStateDelay)) { + // do not update keyboard layout while a script is running + return; + } + + File* layout_file = storage_file_alloc(furi_record_open(RECORD_STORAGE)); + if(!furi_string_empty(layout_path)) { + if(storage_file_open( + layout_file, furi_string_get_cstr(layout_path), FSAM_READ, FSOM_OPEN_EXISTING)) { + uint16_t layout[128]; + if(storage_file_read(layout_file, layout, sizeof(layout)) == sizeof(layout)) { + memcpy(bad_usb->layout, layout, sizeof(layout)); + } + } + storage_file_close(layout_file); + } else { + bad_usb_script_set_default_keyboard_layout(bad_usb); + } + storage_file_free(layout_file); +} + void bad_usb_script_toggle(BadUsbScript* bad_usb) { furi_assert(bad_usb); furi_thread_flags_set(furi_thread_get_id(bad_usb->thread), WorkerEvtToggle); diff --git a/applications/main/bad_usb/bad_usb_script.h b/applications/main/bad_usb/bad_usb_script.h index 188142db850..1e4d98fe7d5 100644 --- a/applications/main/bad_usb/bad_usb_script.h +++ b/applications/main/bad_usb/bad_usb_script.h @@ -33,6 +33,8 @@ BadUsbScript* bad_usb_script_open(FuriString* file_path); void bad_usb_script_close(BadUsbScript* bad_usb); +void bad_usb_script_set_keyboard_layout(BadUsbScript* bad_usb, FuriString* layout_path); + void bad_usb_script_start(BadUsbScript* bad_usb); void bad_usb_script_stop(BadUsbScript* bad_usb); diff --git a/applications/main/bad_usb/bad_usb_settings_filename.h b/applications/main/bad_usb/bad_usb_settings_filename.h new file mode 100644 index 00000000000..12ba8f31c4c --- /dev/null +++ b/applications/main/bad_usb/bad_usb_settings_filename.h @@ -0,0 +1,3 @@ +#pragma once + +#define BAD_USB_SETTINGS_FILE_NAME ".badusb.settings" diff --git a/applications/main/bad_usb/scenes/bad_usb_scene_config.c b/applications/main/bad_usb/scenes/bad_usb_scene_config.c new file mode 100644 index 00000000000..2a9f2f76c91 --- /dev/null +++ b/applications/main/bad_usb/scenes/bad_usb_scene_config.c @@ -0,0 +1,53 @@ +#include "../bad_usb_app_i.h" +#include "furi_hal_power.h" +#include "furi_hal_usb.h" + +enum SubmenuIndex { + SubmenuIndexKeyboardLayout, +}; + +void bad_usb_scene_config_submenu_callback(void* context, uint32_t index) { + BadUsbApp* bad_usb = context; + view_dispatcher_send_custom_event(bad_usb->view_dispatcher, index); +} + +void bad_usb_scene_config_on_enter(void* context) { + BadUsbApp* bad_usb = context; + Submenu* submenu = bad_usb->submenu; + + submenu_add_item( + submenu, + "Keyboard layout", + SubmenuIndexKeyboardLayout, + bad_usb_scene_config_submenu_callback, + bad_usb); + + submenu_set_selected_item( + submenu, scene_manager_get_scene_state(bad_usb->scene_manager, BadUsbSceneConfig)); + + view_dispatcher_switch_to_view(bad_usb->view_dispatcher, BadUsbAppViewConfig); +} + +bool bad_usb_scene_config_on_event(void* context, SceneManagerEvent event) { + BadUsbApp* bad_usb = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + scene_manager_set_scene_state(bad_usb->scene_manager, BadUsbSceneConfig, event.event); + consumed = true; + if(event.event == SubmenuIndexKeyboardLayout) { + scene_manager_next_scene(bad_usb->scene_manager, BadUsbSceneConfigLayout); + } else { + furi_crash("Unknown key type"); + } + } + + return consumed; +} + +void bad_usb_scene_config_on_exit(void* context) { + BadUsbApp* bad_usb = context; + Submenu* submenu = bad_usb->submenu; + + submenu_reset(submenu); +} diff --git a/applications/main/bad_usb/scenes/bad_usb_scene_config.h b/applications/main/bad_usb/scenes/bad_usb_scene_config.h index 0ab8f54f874..423aedc51bd 100644 --- a/applications/main/bad_usb/scenes/bad_usb_scene_config.h +++ b/applications/main/bad_usb/scenes/bad_usb_scene_config.h @@ -1,3 +1,5 @@ ADD_SCENE(bad_usb, file_select, FileSelect) ADD_SCENE(bad_usb, work, Work) ADD_SCENE(bad_usb, error, Error) +ADD_SCENE(bad_usb, config, Config) +ADD_SCENE(bad_usb, config_layout, ConfigLayout) diff --git a/applications/main/bad_usb/scenes/bad_usb_scene_config_layout.c b/applications/main/bad_usb/scenes/bad_usb_scene_config_layout.c new file mode 100644 index 00000000000..7708ed1d834 --- /dev/null +++ b/applications/main/bad_usb/scenes/bad_usb_scene_config_layout.c @@ -0,0 +1,50 @@ +#include "../bad_usb_app_i.h" +#include "furi_hal_power.h" +#include "furi_hal_usb.h" +#include + +static bool bad_usb_layout_select(BadUsbApp* bad_usb) { + furi_assert(bad_usb); + + FuriString* predefined_path; + predefined_path = furi_string_alloc(); + if(!furi_string_empty(bad_usb->keyboard_layout)) { + furi_string_set(predefined_path, bad_usb->keyboard_layout); + } else { + furi_string_set(predefined_path, BAD_USB_APP_PATH_LAYOUT_FOLDER); + } + + DialogsFileBrowserOptions browser_options; + dialog_file_browser_set_basic_options( + &browser_options, BAD_USB_APP_LAYOUT_EXTENSION, &I_keyboard_10px); + browser_options.base_path = BAD_USB_APP_PATH_LAYOUT_FOLDER; + browser_options.skip_assets = false; + + // Input events and views are managed by file_browser + bool res = dialog_file_browser_show( + bad_usb->dialogs, bad_usb->keyboard_layout, predefined_path, &browser_options); + + furi_string_free(predefined_path); + return res; +} + +void bad_usb_scene_config_layout_on_enter(void* context) { + BadUsbApp* bad_usb = context; + + if(bad_usb_layout_select(bad_usb)) { + bad_usb_script_set_keyboard_layout(bad_usb->bad_usb_script, bad_usb->keyboard_layout); + } + scene_manager_previous_scene(bad_usb->scene_manager); +} + +bool bad_usb_scene_config_layout_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + UNUSED(event); + // BadUsbApp* bad_usb = context; + return false; +} + +void bad_usb_scene_config_layout_on_exit(void* context) { + UNUSED(context); + // BadUsbApp* bad_usb = context; +} diff --git a/applications/main/bad_usb/scenes/bad_usb_scene_file_select.c b/applications/main/bad_usb/scenes/bad_usb_scene_file_select.c index f1f34f5bc26..b046692528c 100644 --- a/applications/main/bad_usb/scenes/bad_usb_scene_file_select.c +++ b/applications/main/bad_usb/scenes/bad_usb_scene_file_select.c @@ -7,8 +7,10 @@ static bool bad_usb_file_select(BadUsbApp* bad_usb) { furi_assert(bad_usb); DialogsFileBrowserOptions browser_options; - dialog_file_browser_set_basic_options(&browser_options, BAD_USB_APP_EXTENSION, &I_badusb_10px); - browser_options.base_path = BAD_USB_APP_PATH_FOLDER; + dialog_file_browser_set_basic_options( + &browser_options, BAD_USB_APP_SCRIPT_EXTENSION, &I_badusb_10px); + browser_options.base_path = BAD_USB_APP_BASE_FOLDER; + browser_options.skip_assets = true; // Input events and views are managed by file_browser bool res = dialog_file_browser_show( @@ -21,12 +23,18 @@ void bad_usb_scene_file_select_on_enter(void* context) { BadUsbApp* bad_usb = context; furi_hal_usb_disable(); + if(bad_usb->bad_usb_script) { + bad_usb_script_close(bad_usb->bad_usb_script); + bad_usb->bad_usb_script = NULL; + } if(bad_usb_file_select(bad_usb)) { + bad_usb->bad_usb_script = bad_usb_script_open(bad_usb->file_path); + bad_usb_script_set_keyboard_layout(bad_usb->bad_usb_script, bad_usb->keyboard_layout); + scene_manager_next_scene(bad_usb->scene_manager, BadUsbSceneWork); } else { furi_hal_usb_enable(); - //scene_manager_previous_scene(bad_usb->scene_manager); view_dispatcher_stop(bad_usb->view_dispatcher); } } diff --git a/applications/main/bad_usb/scenes/bad_usb_scene_work.c b/applications/main/bad_usb/scenes/bad_usb_scene_work.c index 1e35348225d..187b83bd96d 100644 --- a/applications/main/bad_usb/scenes/bad_usb_scene_work.c +++ b/applications/main/bad_usb/scenes/bad_usb_scene_work.c @@ -4,10 +4,10 @@ #include #include "toolbox/path.h" -void bad_usb_scene_work_ok_callback(InputType type, void* context) { +void bad_usb_scene_work_button_callback(InputKey key, void* context) { furi_assert(context); BadUsbApp* app = context; - view_dispatcher_send_custom_event(app->view_dispatcher, type); + view_dispatcher_send_custom_event(app->view_dispatcher, key); } bool bad_usb_scene_work_on_event(void* context, SceneManagerEvent event) { @@ -15,8 +15,13 @@ bool bad_usb_scene_work_on_event(void* context, SceneManagerEvent event) { bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { - bad_usb_script_toggle(app->bad_usb_script); - consumed = true; + if(event.event == InputKeyLeft) { + scene_manager_next_scene(app->scene_manager, BadUsbSceneConfig); + consumed = true; + } else if(event.event == InputKeyOk) { + bad_usb_script_toggle(app->bad_usb_script); + consumed = true; + } } else if(event.type == SceneManagerEventTypeTick) { bad_usb_set_state(app->bad_usb_view, bad_usb_script_get_state(app->bad_usb_script)); } @@ -28,20 +33,22 @@ void bad_usb_scene_work_on_enter(void* context) { FuriString* file_name; file_name = furi_string_alloc(); - path_extract_filename(app->file_path, file_name, true); bad_usb_set_file_name(app->bad_usb_view, furi_string_get_cstr(file_name)); - app->bad_usb_script = bad_usb_script_open(app->file_path); - furi_string_free(file_name); + FuriString* layout; + layout = furi_string_alloc(); + path_extract_filename(app->keyboard_layout, layout, true); + bad_usb_set_layout(app->bad_usb_view, furi_string_get_cstr(layout)); + furi_string_free(layout); + bad_usb_set_state(app->bad_usb_view, bad_usb_script_get_state(app->bad_usb_script)); - bad_usb_set_ok_callback(app->bad_usb_view, bad_usb_scene_work_ok_callback, app); + bad_usb_set_button_callback(app->bad_usb_view, bad_usb_scene_work_button_callback, app); view_dispatcher_switch_to_view(app->view_dispatcher, BadUsbAppViewWork); } void bad_usb_scene_work_on_exit(void* context) { - BadUsbApp* app = context; - bad_usb_script_close(app->bad_usb_script); + UNUSED(context); } diff --git a/applications/main/bad_usb/views/bad_usb_view.c b/applications/main/bad_usb/views/bad_usb_view.c index b3eb9bb5636..bb9dc3b7e3b 100644 --- a/applications/main/bad_usb/views/bad_usb_view.c +++ b/applications/main/bad_usb/views/bad_usb_view.c @@ -1,5 +1,6 @@ #include "bad_usb_view.h" #include "../bad_usb_script.h" +#include #include #include @@ -7,12 +8,13 @@ struct BadUsb { View* view; - BadUsbOkCallback callback; + BadUsbButtonCallback callback; void* context; }; typedef struct { char file_name[MAX_NAME_LEN]; + char layout[MAX_NAME_LEN]; BadUsbState state; uint8_t anim_frame; } BadUsbModel; @@ -25,9 +27,23 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) { elements_string_fit_width(canvas, disp_str, 128 - 2); canvas_set_font(canvas, FontSecondary); canvas_draw_str(canvas, 2, 8, furi_string_get_cstr(disp_str)); + + if(strlen(model->layout) == 0) { + furi_string_set(disp_str, "(default)"); + } else { + furi_string_reset(disp_str); + furi_string_push_back(disp_str, '('); + for(size_t i = 0; i < strlen(model->layout); i++) + furi_string_push_back(disp_str, model->layout[i]); + furi_string_push_back(disp_str, ')'); + } + elements_string_fit_width(canvas, disp_str, 128 - 2); + canvas_draw_str( + canvas, 2, 8 + canvas_current_font_height(canvas), furi_string_get_cstr(disp_str)); + furi_string_reset(disp_str); - canvas_draw_icon(canvas, 22, 20, &I_UsbTree_48x22); + canvas_draw_icon(canvas, 22, 24, &I_UsbTree_48x22); if((model->state.state == BadUsbStateIdle) || (model->state.state == BadUsbStateDone) || (model->state.state == BadUsbStateNotConnected)) { @@ -38,23 +54,28 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) { elements_button_center(canvas, "Cancel"); } + if((model->state.state == BadUsbStateNotConnected) || + (model->state.state == BadUsbStateIdle) || (model->state.state == BadUsbStateDone)) { + elements_button_left(canvas, "Config"); + } + if(model->state.state == BadUsbStateNotConnected) { - canvas_draw_icon(canvas, 4, 22, &I_Clock_18x18); + canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18); canvas_set_font(canvas, FontPrimary); - canvas_draw_str_aligned(canvas, 127, 27, AlignRight, AlignBottom, "Connect"); - canvas_draw_str_aligned(canvas, 127, 39, AlignRight, AlignBottom, "to USB"); + canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "Connect"); + canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "to USB"); } else if(model->state.state == BadUsbStateWillRun) { - canvas_draw_icon(canvas, 4, 22, &I_Clock_18x18); + canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18); canvas_set_font(canvas, FontPrimary); - canvas_draw_str_aligned(canvas, 127, 27, AlignRight, AlignBottom, "Will run"); - canvas_draw_str_aligned(canvas, 127, 39, AlignRight, AlignBottom, "on connect"); + canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "Will run"); + canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "on connect"); } else if(model->state.state == BadUsbStateFileError) { - canvas_draw_icon(canvas, 4, 22, &I_Error_18x18); + canvas_draw_icon(canvas, 4, 26, &I_Error_18x18); canvas_set_font(canvas, FontPrimary); - canvas_draw_str_aligned(canvas, 127, 27, AlignRight, AlignBottom, "File"); - canvas_draw_str_aligned(canvas, 127, 39, AlignRight, AlignBottom, "ERROR"); + canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "File"); + canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "ERROR"); } else if(model->state.state == BadUsbStateScriptError) { - canvas_draw_icon(canvas, 4, 22, &I_Error_18x18); + canvas_draw_icon(canvas, 4, 26, &I_Error_18x18); canvas_set_font(canvas, FontPrimary); canvas_draw_str_aligned(canvas, 127, 33, AlignRight, AlignBottom, "ERROR:"); canvas_set_font(canvas, FontSecondary); @@ -64,49 +85,49 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) { furi_string_reset(disp_str); canvas_draw_str_aligned(canvas, 127, 56, AlignRight, AlignBottom, model->state.error); } else if(model->state.state == BadUsbStateIdle) { - canvas_draw_icon(canvas, 4, 22, &I_Smile_18x18); + canvas_draw_icon(canvas, 4, 26, &I_Smile_18x18); canvas_set_font(canvas, FontBigNumbers); - canvas_draw_str_aligned(canvas, 114, 36, AlignRight, AlignBottom, "0"); - canvas_draw_icon(canvas, 117, 22, &I_Percent_10x14); + canvas_draw_str_aligned(canvas, 114, 40, AlignRight, AlignBottom, "0"); + canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14); } else if(model->state.state == BadUsbStateRunning) { if(model->anim_frame == 0) { - canvas_draw_icon(canvas, 4, 19, &I_EviSmile1_18x21); + canvas_draw_icon(canvas, 4, 23, &I_EviSmile1_18x21); } else { - canvas_draw_icon(canvas, 4, 19, &I_EviSmile2_18x21); + canvas_draw_icon(canvas, 4, 23, &I_EviSmile2_18x21); } canvas_set_font(canvas, FontBigNumbers); furi_string_printf( disp_str, "%u", ((model->state.line_cur - 1) * 100) / model->state.line_nb); canvas_draw_str_aligned( - canvas, 114, 36, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); + canvas, 114, 40, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); furi_string_reset(disp_str); - canvas_draw_icon(canvas, 117, 22, &I_Percent_10x14); + canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14); } else if(model->state.state == BadUsbStateDone) { - canvas_draw_icon(canvas, 4, 19, &I_EviSmile1_18x21); + canvas_draw_icon(canvas, 4, 23, &I_EviSmile1_18x21); canvas_set_font(canvas, FontBigNumbers); - canvas_draw_str_aligned(canvas, 114, 36, AlignRight, AlignBottom, "100"); + canvas_draw_str_aligned(canvas, 114, 40, AlignRight, AlignBottom, "100"); furi_string_reset(disp_str); - canvas_draw_icon(canvas, 117, 22, &I_Percent_10x14); + canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14); } else if(model->state.state == BadUsbStateDelay) { if(model->anim_frame == 0) { - canvas_draw_icon(canvas, 4, 19, &I_EviWaiting1_18x21); + canvas_draw_icon(canvas, 4, 23, &I_EviWaiting1_18x21); } else { - canvas_draw_icon(canvas, 4, 19, &I_EviWaiting2_18x21); + canvas_draw_icon(canvas, 4, 23, &I_EviWaiting2_18x21); } canvas_set_font(canvas, FontBigNumbers); furi_string_printf( disp_str, "%u", ((model->state.line_cur - 1) * 100) / model->state.line_nb); canvas_draw_str_aligned( - canvas, 114, 36, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); + canvas, 114, 40, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); furi_string_reset(disp_str); - canvas_draw_icon(canvas, 117, 22, &I_Percent_10x14); + canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14); canvas_set_font(canvas, FontSecondary); furi_string_printf(disp_str, "delay %lus", model->state.delay_remain); canvas_draw_str_aligned( - canvas, 127, 46, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); + canvas, 127, 50, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); furi_string_reset(disp_str); } else { - canvas_draw_icon(canvas, 4, 22, &I_Clock_18x18); + canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18); } furi_string_free(disp_str); @@ -118,10 +139,10 @@ static bool bad_usb_input_callback(InputEvent* event, void* context) { bool consumed = false; if(event->type == InputTypeShort) { - if(event->key == InputKeyOk) { + if((event->key == InputKeyLeft) || (event->key == InputKeyOk)) { consumed = true; furi_assert(bad_usb->callback); - bad_usb->callback(InputTypeShort, bad_usb->context); + bad_usb->callback(event->key, bad_usb->context); } } @@ -151,7 +172,7 @@ View* bad_usb_get_view(BadUsb* bad_usb) { return bad_usb->view; } -void bad_usb_set_ok_callback(BadUsb* bad_usb, BadUsbOkCallback callback, void* context) { +void bad_usb_set_button_callback(BadUsb* bad_usb, BadUsbButtonCallback callback, void* context) { furi_assert(bad_usb); furi_assert(callback); with_view_model( @@ -174,6 +195,14 @@ void bad_usb_set_file_name(BadUsb* bad_usb, const char* name) { true); } +void bad_usb_set_layout(BadUsb* bad_usb, const char* layout) { + furi_assert(layout); + with_view_model( + bad_usb->view, + BadUsbModel * model, + { strlcpy(model->layout, layout, MAX_NAME_LEN); }, + true); +} void bad_usb_set_state(BadUsb* bad_usb, BadUsbState* st) { furi_assert(st); with_view_model( diff --git a/applications/main/bad_usb/views/bad_usb_view.h b/applications/main/bad_usb/views/bad_usb_view.h index 80a47e2ca67..8447fb05596 100644 --- a/applications/main/bad_usb/views/bad_usb_view.h +++ b/applications/main/bad_usb/views/bad_usb_view.h @@ -4,7 +4,7 @@ #include "../bad_usb_script.h" typedef struct BadUsb BadUsb; -typedef void (*BadUsbOkCallback)(InputType type, void* context); +typedef void (*BadUsbButtonCallback)(InputKey key, void* context); BadUsb* bad_usb_alloc(); @@ -12,8 +12,10 @@ void bad_usb_free(BadUsb* bad_usb); 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_button_callback(BadUsb* bad_usb, BadUsbButtonCallback callback, void* context); void bad_usb_set_file_name(BadUsb* bad_usb, const char* name); +void bad_usb_set_layout(BadUsb* bad_usb, const char* layout); + void bad_usb_set_state(BadUsb* bad_usb, BadUsbState* st); diff --git a/assets/icons/Archive/keyboard_10px.png b/assets/icons/Archive/keyboard_10px.png new file mode 100644 index 0000000000000000000000000000000000000000..74a10e6db2e784486a6781c0db8346373b9c7409 GIT binary patch literal 147 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2xGmzZ=C-xtZVhivIasB`QKad%E=yDy9;wgN=z( mnGc5;aHhO3XW%q4U|?wcz_Hx?k)a$=ErX}4pUXO@geCyYPARJZ literal 0 HcmV?d00001 diff --git a/assets/resources/badusb/assets/layouts/ba-BA.kl b/assets/resources/badusb/assets/layouts/ba-BA.kl new file mode 100644 index 0000000000000000000000000000000000000000..379f6c649c98ef7f38df9c6d1c2b1588167ee46a GIT binary patch literal 256 zcmaLL%XYy47y#ipa|DTNh+7ar(9@P>V)?)SQ*~|A*?j|_ee=WD=lWN_RGzFod-3X% zn|EKnXnE7o_@LrM&6x{V8gAU_Y;EIV7f*N5GqLZ$p{bc8$4;C&b8ha!rG+ckZrr-F ybnnrFX%vw~9dDcHkcVK4j(2-*&PMujecVX$$ zm1{Rv)^6RqGmA3vIK<6U^hikQGaw^p$cQl$rpzdqv!GYm`cD?ePm*T4GZVB*m| zTln^pGi$gvZr&YZh&>B_Ym yx9;4VM-@dp{CKxUod!)>wCT{LN1p*hMvR#-WyYKZOIEDeuw}=dEbdXpBj69fFAeVi literal 0 HcmV?d00001 diff --git a/assets/resources/badusb/assets/layouts/de-CH.kl b/assets/resources/badusb/assets/layouts/de-CH.kl new file mode 100644 index 0000000000000000000000000000000000000000..1704bc9dbaae9fcc90213cb33b0132bca1b71866 GIT binary patch literal 256 zcmaLLH;%#p6adlDh@5i<8!$oa;st31l>ack{~4%hnC=aHcHxKf=lZH&ZYHL2XXoB4 z2al$1eDLB)=7|jnDO+~z$vAN2>BEAPg;$d(S+-);nspmCZP~VC*PeX`4jnmm;?$XQ z7cO19@-K?GMb69I5@jk>sZpmvlNN0{bm`G&z>pDRCQO+zXTg#+EB+&kd*lIc##;^x literal 0 HcmV?d00001 diff --git a/assets/resources/badusb/assets/layouts/de-DE.kl b/assets/resources/badusb/assets/layouts/de-DE.kl new file mode 100644 index 0000000000000000000000000000000000000000..67b05c042f1649ab6aa17c4d369b096c88ec2785 GIT binary patch literal 256 zcmaLLHx9y30KiboqW2Pd4WuBN!ay1SVRHX7Ftgz88Gbu+;q<%y>dMxg4`L4uK0?Afwu&6#5(8&;hd+qL7+fvJ5TwhjIGHgM$E s3wH`~pT4NjqeYV%GsZ+Ts4^j@ONSu?Qu;h-6Y}9rz=$sw?-ss^@Y|jPyWjO!M`q5wk-2p3gN<8r z&%QYE=EIg5a~5PQS+OQ(!`(YC&%FKkuw>baRci*;Z5Z0LW!sLCv58$%d-feTbmZ8H tQ!f-;S$XnFi82+c)Cj24Af!o)HXR~j61t@H=rdr*h%pnUTr9kL6TT^X3*7(! literal 0 HcmV?d00001 diff --git a/assets/resources/badusb/assets/layouts/en-US.kl b/assets/resources/badusb/assets/layouts/en-US.kl new file mode 100644 index 0000000000000000000000000000000000000000..8089d8257881765fe67691a206b6bf6e9cb2c97d GIT binary patch literal 256 zcmaLL#}dH+007aQ9YGK+QKLn)QG!wb|I3_nvA4HS?#PKldHvOyg-=&zuHE=#=iaS_ zZ!UcJvS-1P6&Y)CHf-7P@WI=Hk6)Ko46ItSZfL`%EhF1@?Ao($Y~sM7BgamhnmTjt sl{+_bFTSV{P^CtlkOoa!M6~J9rAMEbgaJcFjF~VcWyYMVcW=Ig9}}MoYybcN literal 0 HcmV?d00001 diff --git a/assets/resources/badusb/assets/layouts/es-ES.kl b/assets/resources/badusb/assets/layouts/es-ES.kl new file mode 100644 index 0000000000000000000000000000000000000000..15e9d7997c3f5178982fe212b5341f09469486bc GIT binary patch literal 256 zcmaLLH;%#p6adlDh@5lA#$@e+Y@}VF{DLgOx;B0)}2=l z9xW`m^W?$a6A39BGPdm4bKuCsyDv_@yqZSIvK6b=tlO|@%eEc6_Ut=w=*Y1Xr_P+a zaOujmdHhEaxleOSl&MgqMx6#tTD0lVrAMCuLq?35FlEM^1xr?}`HLiOkp;W~w>u5X literal 0 HcmV?d00001 diff --git a/assets/resources/badusb/assets/layouts/fr-BE.kl b/assets/resources/badusb/assets/layouts/fr-BE.kl new file mode 100644 index 0000000000000000000000000000000000000000..ea9e553e894a470639ee48648a386898ed5cfa67 GIT binary patch literal 256 zcmaLLNm9Z96adk#F6Mcj2}uy4P%#!3Wcd%1`=7xz8+-Q}Uc2(a<#qk5PfnzyQSfU* zM$Q+7r6X=Ue0nzVU}|RW(Kk=N`{Y4IN#)2x%&gn6Y0I`9yY}omaOmB!6Q|akyKw2s zwR^Yj+?YodMSOGfCuY=X(4ack{~4%hnC=aHcHxKf=lZH&ZYHL2XXoB4 z2al$1eDLB)=7|jnDO+~z$vAN2>BEAPg;$d(S+-);nspmCZP~VC*PeX`4jnmm;?$XQ z7cO19@-K?GMb69I5@jk>sZpmvlNN0{bm`G&z>pDRCQO+zXTg#+EB+&kd*lIc##;^x literal 0 HcmV?d00001 diff --git a/assets/resources/badusb/assets/layouts/fr-FR.kl b/assets/resources/badusb/assets/layouts/fr-FR.kl new file mode 100644 index 0000000000000000000000000000000000000000..f9193297e58722fd4f1547cb9ef62474beb2487b GIT binary patch literal 256 zcmaLLH*Nv}6adlDh@5i{8(2U#EVi(~}3$%7XqF9)7tV%3^;8#Zm(wqw_xeeVt(Ikw`|nR6E| z-MV(;$}Ebw#*KnQOsG<$PJ<>b+H~mBqtBfoBgRyiGGoqyC0o{PSdm5^W$Xj~0L-fm A_W%F@ literal 0 HcmV?d00001 diff --git a/assets/resources/badusb/assets/layouts/hr-HR.kl b/assets/resources/badusb/assets/layouts/hr-HR.kl new file mode 100644 index 0000000000000000000000000000000000000000..379f6c649c98ef7f38df9c6d1c2b1588167ee46a GIT binary patch literal 256 zcmaLL%XYy47y#ipa|DTNh+7ar(9@P>V)?)SQ*~|A*?j|_ee=WD=lWN_RGzFod-3X% zn|EKnXnE7o_@LrM&6x{V8gAU_Y;EIV7f*N5GqLZ$p{bc8$4;C&b8ha!rG+ckZrr-F ybnnrFX%vw~9dDcHk)x=fVv{{L5{w#jy9;k{43_;_Fc>YIyakMR>% z$I*KC;Silq2439!rR7Y=g)29D?mT$;j68aqcv{8C#IX~nre@BZyKw2s+`_dROSkUa zd$6+hXyeH=%E+RN!9GSLjF~VcC1b{%1xr@s6s*}$vSr7f0~Iw#8crlpM-gqnA2aL@ AGynhq literal 0 HcmV?d00001 diff --git a/assets/resources/badusb/assets/layouts/it-IT.kl b/assets/resources/badusb/assets/layouts/it-IT.kl new file mode 100644 index 0000000000000000000000000000000000000000..059e428808a07b26d0da37d7f37d5d8d4614365b GIT binary patch literal 256 zcmaKnw++Go0KhCO)P&wbCv;IGQOAf|`2PmP$bvU}GyJk+&-OR{wF48UUbDox3y-W^ zo0@Xx$%CaECQO-;FlWJ%6>A>eeY5fH(MuMXGjGA7CCi3JR;*gHZo{T6+ji{QGq&%* yp(Dqhvm0r4@+k!5$Wx$5i83J(6{^&z)1XO96ECB#K@^L=Pq0t xyK-$Ft`y%!)Nxcub+H~j=&?BVJfFUEsOqeobPMlv%@{{ldJ?{*0 literal 0 HcmV?d00001 diff --git a/assets/resources/badusb/assets/layouts/pt-BR.kl b/assets/resources/badusb/assets/layouts/pt-BR.kl new file mode 100644 index 0000000000000000000000000000000000000000..d36421cfc45747687dba3cabe05ff84a4550050a GIT binary patch literal 256 zcmaKnM-ssR004J(1W9z!q7!8hWkx%CjQ_uZtBbwu-WGn@v1j|6{@Q_wH)qaWn0aIE z#+66!wV)?)SQ*~|A*?j|_ee=WD=lWN_RGzFod-3X% zn|EKnXnE7o_@LrM&6x{V8gAU_Y;EIV7f*N5GqLZ$p{bc8$4;C&b8ha!rG+ckZrr-F ybnnrFX%vw~9dDcHk17S!45fq0~DYekR@*l?gpTRX7d-ob{d*zLn+x4&B`SxrR z=Qx?hB0l}{*Orbe7e0Kp_0GKq?|ty-qfcJ^p^Fo{=&Yh=Vrt)knYlwpj-5DlX5rk0 zrAt??-B?-MxN~b3W#qAstHmjD0& literal 0 HcmV?d00001 diff --git a/assets/resources/badusb/assets/layouts/sv-SE.kl b/assets/resources/badusb/assets/layouts/sv-SE.kl new file mode 100644 index 0000000000000000000000000000000000000000..5c55bb9ef1df3785fb143eb463a5375be10ce96f GIT binary patch literal 256 zcmaLLH*x|26u?l8hMaSTC2U}9uLl~S{D;~5pMjbNbx-iw55HVL*H`^JB+m2m(_8mBMwCT{LN1p*hMvR#-WyYKZOIEDeuw}=dG^!}#7VrkLXASNE literal 0 HcmV?d00001 diff --git a/assets/resources/badusb/assets/layouts/tr-TR.kl b/assets/resources/badusb/assets/layouts/tr-TR.kl new file mode 100644 index 0000000000000000000000000000000000000000..6377b770365ed544824108765ce2587f315bc1da GIT binary patch literal 256 zcmaLLM{dFZ6adlDh~A6oU~F)Sk{HDcB$od$x&IlmW|P^yf%iW7;^Te&t8Y@DWRb_s zyH`sq8**D7yeOY}m46&w(Q+&Ri(sH|qEa_ygc8 B4s!qi literal 0 HcmV?d00001 From 8288a08eb3ab1c49451c1abcf1bc38bbfdcc8c71 Mon Sep 17 00:00:00 2001 From: Brandon Weeks Date: Wed, 8 Feb 2023 07:26:45 -0800 Subject: [PATCH 389/824] SubGhz: add protocol "Linear Delta-3" (#2239) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * SubGhz: add protocol "Linear Delta-3" * SubGhz: fix Leniar Delta 3 * BadUSB: mask pvs studio warning for valid code Co-authored-by: SkorP Co-authored-by: あく Co-authored-by: Skorpionm <85568270+Skorpionm@users.noreply.github.com> --- .../debug/unit_tests/subghz/subghz_test.c | 18 +- applications/main/bad_usb/bad_usb_script.c | 2 +- assets/unit_tests/subghz/linear_delta3.sub | 7 + .../unit_tests/subghz/linear_delta3_raw.sub | 8 + assets/unit_tests/subghz/test_random_raw.sub | 2 + lib/subghz/protocols/linear_delta3.c | 359 ++++++++++++++++++ lib/subghz/protocols/linear_delta3.h | 111 ++++++ lib/subghz/protocols/protocol_items.c | 49 ++- lib/subghz/protocols/protocol_items.h | 1 + 9 files changed, 543 insertions(+), 14 deletions(-) create mode 100644 assets/unit_tests/subghz/linear_delta3.sub create mode 100644 assets/unit_tests/subghz/linear_delta3_raw.sub create mode 100644 lib/subghz/protocols/linear_delta3.c create mode 100644 lib/subghz/protocols/linear_delta3.h diff --git a/applications/debug/unit_tests/subghz/subghz_test.c b/applications/debug/unit_tests/subghz/subghz_test.c index 1dee1d59ecd..83fadeda973 100644 --- a/applications/debug/unit_tests/subghz/subghz_test.c +++ b/applications/debug/unit_tests/subghz/subghz_test.c @@ -13,7 +13,7 @@ #define CAME_ATOMO_DIR_NAME EXT_PATH("subghz/assets/came_atomo") #define NICE_FLOR_S_DIR_NAME EXT_PATH("subghz/assets/nice_flor_s") #define TEST_RANDOM_DIR_NAME EXT_PATH("unit_tests/subghz/test_random_raw.sub") -#define TEST_RANDOM_COUNT_PARSE 273 +#define TEST_RANDOM_COUNT_PARSE 295 #define TEST_TIMEOUT 10000 static SubGhzEnvironment* environment_handler; @@ -489,6 +489,14 @@ MU_TEST(subghz_decoder_linear_test) { "Test decoder " SUBGHZ_PROTOCOL_LINEAR_NAME " error\r\n"); } +MU_TEST(subghz_decoder_linear_delta3_test) { + mu_assert( + subghz_decoder_test( + EXT_PATH("unit_tests/subghz/linear_delta3_raw.sub"), + SUBGHZ_PROTOCOL_LINEAR_DELTA3_NAME), + "Test decoder " SUBGHZ_PROTOCOL_LINEAR_DELTA3_NAME " error\r\n"); +} + MU_TEST(subghz_decoder_megacode_test) { mu_assert( subghz_decoder_test( @@ -647,6 +655,12 @@ MU_TEST(subghz_encoder_linear_test) { "Test encoder " SUBGHZ_PROTOCOL_LINEAR_NAME " error\r\n"); } +MU_TEST(subghz_encoder_linear_delta3_test) { + mu_assert( + subghz_encoder_test(EXT_PATH("unit_tests/subghz/linear_delta3.sub")), + "Test encoder " SUBGHZ_PROTOCOL_LINEAR_DELTA3_NAME " error\r\n"); +} + MU_TEST(subghz_encoder_megacode_test) { mu_assert( subghz_encoder_test(EXT_PATH("unit_tests/subghz/megacode.sub")), @@ -772,6 +786,7 @@ MU_TEST_SUITE(subghz) { MU_RUN_TEST(subghz_decoder_somfy_telis_test); MU_RUN_TEST(subghz_decoder_star_line_test); MU_RUN_TEST(subghz_decoder_linear_test); + MU_RUN_TEST(subghz_decoder_linear_delta3_test); MU_RUN_TEST(subghz_decoder_megacode_test); MU_RUN_TEST(subghz_decoder_secplus_v1_test); MU_RUN_TEST(subghz_decoder_secplus_v2_test); @@ -796,6 +811,7 @@ MU_TEST_SUITE(subghz) { MU_RUN_TEST(subghz_encoder_nice_flo_test); MU_RUN_TEST(subghz_encoder_keelog_test); MU_RUN_TEST(subghz_encoder_linear_test); + MU_RUN_TEST(subghz_encoder_linear_delta3_test); MU_RUN_TEST(subghz_encoder_megacode_test); MU_RUN_TEST(subghz_encoder_holtek_test); MU_RUN_TEST(subghz_encoder_secplus_v1_test); diff --git a/applications/main/bad_usb/bad_usb_script.c b/applications/main/bad_usb/bad_usb_script.c index 1416acfee18..0fadbcc0793 100644 --- a/applications/main/bad_usb/bad_usb_script.c +++ b/applications/main/bad_usb/bad_usb_script.c @@ -694,7 +694,7 @@ void bad_usb_script_set_keyboard_layout(BadUsbScript* bad_usb, FuriString* layou } File* layout_file = storage_file_alloc(furi_record_open(RECORD_STORAGE)); - if(!furi_string_empty(layout_path)) { + if(!furi_string_empty(layout_path)) { //-V1051 if(storage_file_open( layout_file, furi_string_get_cstr(layout_path), FSAM_READ, FSOM_OPEN_EXISTING)) { uint16_t layout[128]; diff --git a/assets/unit_tests/subghz/linear_delta3.sub b/assets/unit_tests/subghz/linear_delta3.sub new file mode 100644 index 00000000000..f005074284d --- /dev/null +++ b/assets/unit_tests/subghz/linear_delta3.sub @@ -0,0 +1,7 @@ +Filetype: Flipper SubGhz Key File +Version: 1 +Frequency: 433920000 +Preset: FuriHalSubGhzPresetOok650Async +Protocol: LinearDelta3 +Bit: 8 +Key: 00 00 00 00 00 00 00 D0 diff --git a/assets/unit_tests/subghz/linear_delta3_raw.sub b/assets/unit_tests/subghz/linear_delta3_raw.sub new file mode 100644 index 00000000000..1973622a535 --- /dev/null +++ b/assets/unit_tests/subghz/linear_delta3_raw.sub @@ -0,0 +1,8 @@ +Filetype: Flipper SubGhz RAW File +Version: 1 +Frequency: 433920000 +Preset: FuriHalSubGhzPresetOok650Async +Protocol: RAW +RAW_Data: -66 11813 -100 14655 -98 40111 -66 1625 -2116 1933 -34732 501 -11730 235 -3728 1887 -2106 1933 -2092 1971 -2072 1959 -34712 511 -3554 445 -3556 1997 -2036 455 -3594 1963 -2046 1979 -2076 1961 -2070 1989 -34690 483 -7724 1739 -2226 355 -3684 1857 -2138 1929 -2078 1965 -2074 1947 -34750 487 -3538 473 -3544 1993 -2042 485 -3548 1961 -2070 1965 -2070 1969 -2042 1997 -34716 443 -7734 1753 -2236 323 -3676 1903 -2098 1945 -2102 1927 -2070 1989 -34710 521 -3532 473 -3544 1991 -2032 481 -3556 1969 -2076 1967 -2036 1991 -2066 1969 -34718 467 -7756 1739 -2192 363 -3654 1889 -2132 1929 -2096 1935 -2070 1987 -34716 511 -3522 471 -3554 2009 -2036 459 -3550 2003 -2038 1979 -2042 1999 -2042 1999 -34704 471 -11774 225 -3710 1879 -2162 1885 -2112 1925 -2110 1939 -34738 459 -3636 403 -3612 1939 -2062 451 -3566 1985 -2044 1995 -2040 2009 -2032 2003 -34684 495 -3680 295 -3648 1935 -2098 423 -3562 2001 -2038 1989 -2044 2003 -2036 1977 -34718 461 -3678 295 -3684 1901 -2098 429 -3596 1967 -2036 1981 -2048 1993 -2042 2013 -34686 521 -3530 457 -3568 1999 -2036 455 -3552 1999 -2032 2019 -2024 1995 -2022 1997 -34716 441 -15774 1809 -2192 1905 -2100 1919 -2112 1961 -34720 417 -3830 167 -3710 1863 -2144 357 -3674 1909 -2100 1955 -2062 1977 -2072 1965 -34710 487 -3562 453 -3554 1985 -2052 481 -3536 2019 -2010 2001 -2042 1997 -2038 2005 -34716 451 -3602 433 -3584 1959 -2070 451 -3560 2001 -2038 1993 -2042 1967 -2072 1973 -34712 459 -3622 393 -3624 1933 -2068 457 -3584 1965 -2064 1979 -2052 1967 -2044 1981 -34722 477 -3608 397 -3588 1961 -2096 413 -3596 1971 -2040 1979 -2072 1963 -2070 1959 -34714 495 -3558 483 -3538 1985 -2042 479 -3562 1985 -2046 1967 -2070 1973 -2054 1995 -34688 493 -3578 413 -3614 1939 -2074 465 -3560 1971 -2038 2017 -2018 1995 -2042 2013 -34726 479 -3528 475 -3556 1999 -2036 455 -3570 1999 -2040 1973 -2054 2001 -2032 1987 -34720 477 -3562 445 -3602 1949 -2054 481 -3562 1975 -2060 1963 -2064 1977 -2038 2005 -34702 485 -3570 447 -3550 2015 -2020 479 -3564 1983 -2048 1999 -2034 1971 -2064 1993 -34688 517 -3516 497 -3532 1999 -2038 481 -3558 1997 -2004 2027 -2042 1963 -2038 1997 -34716 491 -3562 461 -3548 1995 -2032 491 -3524 2005 -2036 1989 -2038 1995 -2046 1979 -34714 465 -3682 293 -3680 1905 -2096 431 -3592 1969 -2070 1977 -2052 1965 -2044 1981 -34734 479 -3564 463 -3556 1999 -2032 457 -3550 1995 -2044 2011 -2042 1997 -2006 2027 -34680 531 -3524 483 -3538 1987 -2044 479 -3534 2013 -2048 1965 -2062 1987 -2030 1997 -34712 473 -3592 445 -3562 1975 -2072 451 -3566 1965 -2042 2013 -2046 1963 -2064 1993 -34700 459 -3632 371 -3638 1915 -2084 449 -3568 1987 -2046 1971 -2070 1983 -2022 1997 -34726 487 -3524 477 -3562 1985 -2044 481 -3542 2005 -2040 1995 -2038 1967 -2046 1993 -34710 511 -3528 471 -3560 1967 -2070 459 -3558 1971 +RAW_Data: -2072 1971 -2056 1971 -2074 1973 -34714 455 -3634 373 -3634 1901 -2110 419 -3620 1941 -2070 1991 -2040 1999 -2038 1965 -34740 467 -3562 481 -3534 1983 -2070 449 -3546 1999 -2044 1993 -2042 2003 -2036 1975 -34702 521 -3560 443 -3586 1969 -2044 449 -3562 1997 -2046 1987 -2042 2007 -2034 1973 -34732 487 -3562 443 -3582 1979 -2058 445 -3560 1995 -2044 1997 -2028 1987 -2034 2003 -34710 515 -3518 485 -3566 1977 -2036 483 -3536 1999 -2044 2009 -2024 1995 -2068 1973 -34710 487 -3564 471 -3558 1977 -2054 447 -3564 1991 -2042 1997 -2036 2007 -2034 2001 -34684 529 -3526 469 -3548 1989 -2038 483 -3562 1997 -2038 1973 -2034 1999 -2036 1997 -34728 487 -3536 479 -3534 2013 -2044 449 -3570 1985 -2042 1993 -2044 2005 -2014 1995 -34710 473 -3594 439 -3562 1995 -2040 457 -3564 2001 -2040 1975 -2046 1995 -2046 1999 -34704 491 -3548 451 -3570 1991 -2042 447 -3578 1967 -2046 1995 -2042 1999 -2034 2001 -34712 491 -3562 443 -3584 1981 -2018 479 -3562 1985 -2044 1997 -2030 1989 -2040 1997 -34722 489 -3554 459 -3560 1969 -2068 453 -3554 1999 -2034 1987 -2058 1997 -2046 1983 -34702 487 -3534 479 -3564 1983 -2040 483 -3538 1981 -2048 1993 -2048 2007 -2044 1995 -34696 489 -3550 453 -3570 1995 -2050 447 -3564 1983 -2040 1999 -2034 2003 -2034 1995 -34690 495 -3580 433 -3586 1969 -2064 453 -3552 1995 -2036 1991 -2056 1997 -2046 1987 -34706 441 -3636 373 -3626 1959 -2074 419 -3592 1963 -2074 1989 -2044 1971 -2070 1981 -34698 509 -3526 503 -3528 2005 -2034 481 -3528 1993 -2042 1999 -2066 1989 -2034 2003 -34678 495 -3540 481 -3546 1997 -2046 473 -3554 1999 -2034 2001 -2036 1995 -2046 1983 -34720 475 -3560 469 -3548 1997 -2030 485 -3566 1963 -2066 1983 -2046 1999 -2034 1973 -34734 487 -3560 443 -3584 1981 -2052 445 -3568 1987 -2044 1999 -2032 1993 -2034 2007 -34702 491 -3560 459 -3558 1967 -2070 455 -3556 2003 -2036 1977 -2042 2005 -2028 1997 -34730 461 -3564 473 -3536 2011 -2046 449 -3566 1989 -2044 1997 -2042 1971 -2054 2001 -34708 475 -3560 479 -3528 1999 -2040 485 -3566 1963 -2040 2013 -2042 1995 -2034 1987 -34694 519 -3554 441 -3582 1981 -2052 449 -3564 1985 -2040 1993 -2034 1991 -2062 1975 -34714 529 -3534 463 -3558 1969 -2068 451 -3560 2003 -2038 1993 -2042 1969 -2070 1975 -34720 493 -3582 383 -3616 1937 -2072 469 -3558 1995 -2036 1975 -2066 1995 -2042 1989 -34678 531 -3560 391 -3622 1937 -2094 429 -3588 1967 -2070 1981 -2054 1965 -2038 2021 -34682 525 -3524 481 -3564 1989 -2040 445 -3554 1997 -2040 2005 -2034 2001 -2024 1991 -34706 517 -3586 409 -3610 1927 -2076 451 -3558 1967 -2074 1993 -2038 2001 -2040 1975 -34714 495 -3588 409 -3602 1933 -2088 447 -3584 1965 -2044 1999 -2036 2007 -2030 1995 -34692 525 -3538 447 -3580 1981 -2042 487 -3542 1995 -2040 1969 -2072 1969 -2044 1991 -34714 443 -3636 399 -3630 1899 -2106 413 -3584 1997 +RAW_Data: -2034 2007 -2038 1969 -2076 1965 -34708 493 -3564 451 -3570 1965 -2074 449 -3548 2003 -2044 1987 -2038 1999 -2030 1991 -34710 493 -3602 403 -3612 1943 -2092 419 -3596 1963 -2062 1963 -2042 2001 -2064 1967 -34716 497 -3616 357 -3648 1903 -2132 399 -3596 1963 -2068 1977 -2052 1967 -2046 2019 -34684 497 -3614 359 -3650 1909 -2100 405 -3630 1925 -2098 1965 -2066 1965 -2056 1971 -34712 477 -3634 371 -3628 1931 -2104 391 -3624 1939 -2066 1975 -2052 2005 -2036 1985 -34714 449 -3668 337 -3664 1901 -2124 417 -3594 1963 -2048 1995 -2028 1993 -2066 1971 -34698 463 -3642 353 -3650 1943 -2066 433 -3594 1963 -2066 1995 -2034 1997 -2046 1981 -34730 479 -3560 445 -3562 1997 -2032 485 -3560 1965 -2062 1989 -2044 1999 -2032 1971 -34724 463 -3608 399 -3620 1943 -2096 421 -3592 1961 -2074 1979 -2036 2011 -2032 1971 -34734 469 -3558 485 -3552 1999 -2028 473 -3552 2003 -2032 2003 -2032 1997 -2044 1993 -34704 443 -3602 431 -3596 1967 -2076 447 -3556 1975 -2058 1997 -2040 1991 -2048 1971 -161100 97 -428 165 -200 395 -428 97 -100 559 -130 97 -164 129 -98 391 -98 295 -166 52395 -66 16239 -66 42541 -66 755 -132 14015 -98 2885 -68 10385 -98 40045 -100 987 -68 25539 -66 19799 -98 136101 -100 5141 -66 5709 -68 23177 -66 11097 -66 329 -100 261 -66 15755 -98 20575 -66 3645 -100 51411 -66 14441 -132 4467 -66 3965 -132 3707 -66 33107 -66 10373 -66 1775 -66 4185 -132 1429 -68 4675 -100 13419 -66 33985 diff --git a/assets/unit_tests/subghz/test_random_raw.sub b/assets/unit_tests/subghz/test_random_raw.sub index be635f04d97..7571d688d5c 100644 --- a/assets/unit_tests/subghz/test_random_raw.sub +++ b/assets/unit_tests/subghz/test_random_raw.sub @@ -173,3 +173,5 @@ RAW_Data: 107 -1501 77 -1518 53 -704 113 -390 107 -650 73 -932 51 -3641 169 -704 RAW_Data: 79 -4798 53 -918 83 -4847 51 -755 103 -732 81 -388 55 -1026 77 -1506 101 -242 107 -469 51 -2026 79 -686 77 -348 51 -104 131 -860 129 -148 73 -446 75 -440 97 -306 99 -600 51 -626 105 -1350 95 -674 83 -230 119 -1714 135 -396 155 -1111 109 -652 111 -482 51 -506 55 -1715 103 -968 207 -1156 81 -164 57 -404 99 -508 205 -126 75 -1417 51 -186 77 -588 53 -54 103 -2854 73 -1010 53 -800 51 -2494 53 -106 105 -52 51 -104 79 -1116 51 -654 103 -220 77 -162 71 -5385 137 -2232 79 -1159 79 -250 57 -108 79 -164 107 -1660 79 -3927 129 -992 73 -1913 51 -1430 51 -1498 55 -514 103 -586 81 -386 53 -2402 175 -1994 85 -3431 53 -3209 99 -372 79 -78 53 -1338 75 -682 97 -680 51 -206 101 -1708 101 -452 131 -1397 161 -2272 53 -456 77 -1413 193 -270 109 -466 53 -2432 77 -222 189 -474 107 -774 171 -192 79 -1327 75 -2141 51 -908 135 -3866 75 -804 129 -468 101 -1040 79 -1470 55 -869 77 -1448 105 -160 55 -1916 240 -588 79 -1587 53 -922 79 -2292 181 -1448 51 -552 77 -2189 75 -2545 77 -384 300 -2478 101 -1092 73 -558 79 -132 105 -884 103 -1177 109 -880 79 -2431 109 -1006 105 -468 53 -1378 235 -684 75 -285 73 -604 129 -528 77 -1582 51 -1240 105 -2750 75 -252 51 -1024 95 -1891 51 -864 107 -326 83 -887 159 -1058 163 -322 105 -722 83 -388 81 -936 155 -880 55 -220 83 -2123 135 -2100 73 -1926 103 -1633 149 -526 51 -324 51 -1538 103 -164 137 -964 81 -152 111 -781 225 -655 53 -2888 105 -151 131 -454 53 -4109 77 -1052 53 -178 163 -910 51 -733 207 -2070 53 -474 79 -54 53 -818 51 -1228 53 -2262 79 -788 79 -480 73 -2747 83 -316 183 -1880 105 -862 53 -662 53 -2287 153 -1630 51 -817 243 -806 55 -510 51 -1389 75 -986 135 -498 109 -532 131 -5521 99 -2948 209 -764 75 -1168 75 -886 83 -2065 53 -710 51 -596 77 -374 73 -628 99 -732 51 -202 73 -632 53 -222 55 -511 79 -4884 53 -1826 81 -1266 107 -356 55 -110 113 -280 83 -756 169 -252 81 -1854 51 -1556 157 -258 75 -748 53 -1438 291 -244 71 -1092 77 -1220 229 -1055 181 -1182 71 -1284 77 -864 79 -138 53 -160 53 -952 81 -80 127 -1272 51 -590 103 -502 77 -634 101 -74 51 -224 101 -912 77 -562 51 -164 83 -396 105 -4643 111 -3293 133 -1395 107 -3047 137 -2353 53 -298 83 -54 81 -80 53 -162 83 -392 105 -606 107 -787 53 -928 51 -2800 161 -1146 51 -182 103 -536 103 -994 81 -2044 83 -732 133 -1881 133 -2160 75 -178 RAW_Data: 75 -1694 101 -122 73 -864 51 -250 129 -406 77 -630 77 -610 101 -781 125 -128 51 -5075 77 -1992 83 -1272 176 -2100 53 -2044 53 -1234 79 -1704 157 -519 99 -2374 101 -100 103 -202 51 -360 77 -1962 103 -2153 77 -1820 191 -164 167 -1320 77 -1718 127 -1374 81 -1047 53 -54 79 -632 53 -656 51 -128 81 -216 51 -755 79 -2692 103 -1478 125 -452 51 -896 157 -3679 135 -632 105 -134 55 -112 77 -588 79 -188 55 -1118 79 -1152 51 -1950 109 -1858 103 -1104 81 -580 131 -226 255 -2932 77 -1536 51 -1044 159 -2135 67667 -252 333 -278 333 -276 333 -74 533 -280 307 -276 345 -6930 331 -276 329 -278 329 -278 327 -278 349 -270 325 -270 317 -290 313 -308 283 -306 309 -100 509 -306 283 -328 281 -6972 307 -302 281 -326 281 -326 281 -328 279 -328 281 -326 279 -328 279 -326 281 -328 279 -124 481 -308 281 -326 279 -6998 281 -326 279 -328 279 -328 277 -330 277 -328 279 -328 279 -328 277 -304 303 -302 303 -100 503 -306 295 -322 293 -6968 287 -342 259 -336 283 -332 257 -356 255 -328 283 -328 257 -352 257 -352 257 -352 255 -150 455 -334 281 -326 281 -6996 265 -342 253 -354 253 -354 253 -354 253 -354 267 -326 269 -350 263 -342 257 -336 259 -152 459 -360 257 -354 231 -7038 267 -338 255 -352 253 -354 253 -354 239 -354 269 -352 239 -372 233 -366 233 -358 257 -154 457 -360 231 -378 231 -7050 231 -380 231 -378 231 -380 231 -378 231 -354 255 -352 257 -352 255 -354 231 -378 229 -176 453 -358 229 -378 231 -7076 231 -378 231 -380 231 -380 229 -354 255 -354 257 -352 257 -352 257 -352 257 -354 255 -150 455 -358 265 -364 229 -4941 101 -1058 153 -670 157 -532 124 -1396 133 -82 165 -162 153 -258 207 -156 131 -1582 85 -714 53 -774 103 -396 274 -110 131 -1965 55 -402 159 -1026 79 -590 77 -3531 57 -500 51 -4770 109 -722 77 -186 53 -298 79 -502 165 -808 77 -438 53 -382 101 -1914 75 -504 77 -1969 135 -5517 99 -576 51 -608 243 -684 53 -2058 315 -1384 79 -1079 77 -232 79 -212 155 -1500 137 -258 75 -975 204 -752 83 -2542 51 -484 103 -78 77 -210 53 -922 157 -1900 107 -2173 83 -384 101 -80 128 -814 183 -978 127 -772 105 -2073 51 -708 53 -300 83 -739 237 -884 131 -3412 157 -1752 81 -164 83 -3373 53 -1406 105 -3809 79 -432 51 -724 77 -548 53 -1955 79 -807 81 -2096 103 -490 105 -1196 109 -108 79 -394 71 -1159 129 -126 143 -340 107 -556 81 -2390 135 -106 133 -690 133 -4347 189 -290 51 -110 53 -78 103 -1101 51 -1362 RAW_Data: 83 -320 81 -4648 101 -3726 173 -1418 85 -348 53 -2994 79 -1390 51 -1656 107 -764 53 -134 79 -1619 131 -932 55 -2810 107 -3218 79 -765 107 -654 103 -1498 77 -228 51 -134 247 -1526 51 -3903 103 -1495 179 -282 77 -392 53 -1756 105 -368 111 -486 51 -298 53 -216 113 -358 51 -266 187 -1059 81 -780 105 -238 51 -482 53 -791 109 -2169 77 -5304 53 -398 79 -650 51 -54 51 -1789 73 -198 101 -1580 101 -746 97 -4518 53 -744 51 -1064 101 -928 111 -392 185 -869 103 -320 133 -704 81 -244 53 -1628 75 -634 79 -666 183 -1276 83 -218 107 -1163 55 -1276 127 -1144 73 -1400 81 -266 77 -568 129 -806 121 -1420 103 -848 77 -982 103 -2132 81 -1610 101 -1218 55 -2208 75 -2735 53 -921 53 -724 51 -472 83 -3164 185 -400 77 -812 81 -306 215 -2167 53 -130 53 -272 81 -400 79 -1272 81 -418 51 -1381 73 -340 101 -2169 81 -2330 137 -2698 99 -2340 99 -126 51 -1714 55 -488 81 -3500 51 -404 77 -1422 77 -856 215 -80 51 -2308 53 -134 77 -2036 75 -5175 129 -946 239 -638 53 -244 55 -564 105 -826 71 -1632 77 -106 129 -246 135 -366 79 -724 79 -1535 57 -1085 113 -1320 79 -3111 127 -1578 75 -324 75 -102 173 -364 79 -1374 53 -1508 107 -622 51 -526 109 -584 187 -2648 51 -106 79 -380 103 -604 51 -1244 73 -5766 107 -1934 177 -702 51 -1277 53 -1643 79 -1446 81 -4098 75 -574 103 -432 189 -1436 107 -454 79 -132 105 -136 81 -112 113 -942 239 -1238 79 -952 157 -340 51 -314 191 -456 53 -3368 101 -150 99 -464 51 -718 73 -770 101 -150 73 -2132 75 -557 77 -680 81 -3512 151 -760 75 -332 75 -1212 131 -1468 79 -1955 101 -541 75 -344 79 -2146 53 -2299 97 -720 79 -2518 79 -3807 51 -1272 75 -352 77 -52 75 -586 53 -1142 79 -82 81 -2400 157 -324 81 -268 103 -1154 81 -1175 79 -1191 51 -1074 53 -2566 137 -854 75 -1497 51 -4533 51 -2290 51 -344 77 -348 55 -1182 77 -897 135 -874 51 -1064 51 -208 55 -140 55 -1334 133 -1238 157 -1669 113 -2128 75 -848 85 -510 83590 -126 333 -280 331 -252 331 -6946 331 -276 331 -276 329 -278 329 -276 331 -276 331 -276 331 -276 347 -238 351 -254 353 -268 323 -270 345 -6924 335 -282 307 -304 307 -304 281 -304 307 -302 307 -302 305 -302 307 -302 305 -302 281 -124 507 -282 305 -302 305 -6984 279 -328 277 -328 279 -328 277 -330 277 -304 303 -302 305 -302 305 -302 303 -304 303 -100 507 -314 295 -298 293 -6986 283 -334 281 -306 283 -328 283 -328 281 -328 283 -328 255 -352 +RAW_Data: -66 11813 -100 14655 -98 40111 -66 1625 -2116 1933 -34732 501 -11730 235 -3728 1887 -2106 1933 -2092 1971 -2072 1959 -34712 511 -3554 445 -3556 1997 -2036 455 -3594 1963 -2046 1979 -2076 1961 -2070 1989 -34690 483 -7724 1739 -2226 355 -3684 1857 -2138 1929 -2078 1965 -2074 1947 -34750 487 -3538 473 -3544 1993 -2042 485 -3548 1961 -2070 1965 -2070 1969 -2042 1997 -34716 443 -7734 1753 -2236 323 -3676 1903 -2098 1945 -2102 1927 -2070 1989 -34710 521 -3532 473 -3544 1991 -2032 481 -3556 1969 -2076 1967 -2036 1991 -2066 1969 -34718 467 -7756 1739 -2192 363 -3654 1889 -2132 1929 -2096 1935 -2070 1987 -34716 511 -3522 471 -3554 2009 -2036 459 -3550 2003 -2038 1979 -2042 1999 -2042 1999 -34704 471 -11774 225 -3710 1879 -2162 1885 -2112 1925 -2110 1939 -34738 459 -3636 403 -3612 1939 -2062 451 -3566 1985 -2044 1995 -2040 2009 -2032 2003 -34684 495 -3680 295 -3648 1935 -2098 423 -3562 2001 -2038 1989 -2044 2003 -2036 1977 -34718 461 -3678 295 -3684 1901 -2098 429 -3596 1967 -2036 1981 -2048 1993 -2042 2013 -34686 521 -3530 457 -3568 1999 -2036 455 -3552 1999 -2032 2019 -2024 1995 -2022 1997 -34716 441 -15774 1809 -2192 1905 -2100 1919 -2112 1961 -34720 417 -3830 167 -3710 1863 -2144 357 -3674 1909 -2100 1955 -2062 1977 -2072 1965 -34710 487 -3562 453 -3554 1985 -2052 481 -3536 2019 -2010 2001 -2042 1997 -2038 2005 -34716 451 -3602 433 -3584 1959 -2070 451 -3560 2001 -2038 1993 -2042 1967 -2072 1973 -34712 459 -3622 393 -3624 1933 -2068 457 -3584 1965 -2064 1979 -2052 1967 -2044 1981 -34722 477 -3608 397 -3588 1961 -2096 413 -3596 1971 -2040 1979 -2072 1963 -2070 1959 -34714 495 -3558 483 -3538 1985 -2042 479 -3562 1985 -2046 1967 -2070 1973 -2054 1995 -34688 493 -3578 413 -3614 1939 -2074 465 -3560 1971 -2038 2017 -2018 1995 -2042 2013 -34726 479 -3528 475 -3556 1999 -2036 455 -3570 1999 -2040 1973 -2054 2001 -2032 1987 -34720 477 -3562 445 -3602 1949 -2054 481 -3562 1975 -2060 1963 -2064 1977 -2038 2005 -34702 485 -3570 447 -3550 2015 -2020 479 -3564 1983 -2048 1999 -2034 1971 -2064 1993 -34688 517 -3516 497 -3532 1999 -2038 481 -3558 1997 -2004 2027 -2042 1963 -2038 1997 -34716 491 -3562 461 -3548 1995 -2032 491 -3524 2005 -2036 1989 -2038 1995 -2046 1979 -34714 465 -3682 293 -3680 1905 -2096 431 -3592 1969 -2070 1977 -2052 1965 -2044 1981 -34734 479 -3564 463 -3556 1999 -2032 457 -3550 1995 -2044 2011 -2042 1997 -2006 2027 -34680 531 -3524 483 -3538 1987 -2044 479 -3534 2013 -2048 1965 -2062 1987 -2030 1997 -34712 473 -3592 445 -3562 1975 -2072 451 -3566 1965 -2042 2013 -2046 1963 -2064 1993 -34700 459 -3632 371 -3638 1915 -2084 449 -3568 1987 -2046 1971 -2070 1983 -2022 1997 -34726 487 -3524 477 -3562 1985 -2044 481 -3542 2005 -2040 1995 -2038 1967 -2046 1993 -34710 511 -3528 471 -3560 1967 -2070 459 -3558 1971 +RAW_Data: -2072 1971 -2056 1971 -2074 1973 -34714 455 -3634 373 -3634 1901 -2110 419 -3620 1941 -2070 1991 -2040 1999 -2038 1965 -34740 467 -3562 481 -3534 1983 -2070 449 -3546 1999 -2044 1993 -2042 2003 -2036 1975 -34702 521 -3560 443 -3586 1969 -2044 449 -3562 1997 -2046 1987 -2042 2007 -2034 1973 -34732 487 -3562 443 -3582 1979 -2058 445 -3560 1995 -2044 1997 -2028 1987 -2034 2003 -34710 515 -3518 485 -3566 1977 -2036 483 -3536 1999 -2044 2009 -2024 1995 -2068 1973 -34710 487 -3564 471 -3558 1977 -2054 447 -3564 1991 -2042 1997 -2036 2007 -2034 2001 -34684 529 -3526 469 -3548 1989 -2038 483 -3562 1997 -2038 1973 -2034 1999 -2036 1997 -34728 487 -3536 479 -3534 2013 -2044 449 -3570 1985 -2042 1993 -2044 2005 -2014 1995 -34710 473 -3594 439 -3562 1995 -2040 457 -3564 2001 -2040 1975 -2046 1995 -2046 1999 -34704 491 -3548 451 -3570 1991 -2042 447 -3578 1967 -2046 1995 -2042 1999 -2034 2001 -34712 491 -3562 443 -3584 1981 -2018 479 -3562 1985 -2044 1997 -2030 1989 -2040 1997 -34722 489 -3554 459 -3560 1969 -2068 453 -3554 1999 -2034 1987 -2058 1997 -2046 1983 -34702 487 -3534 479 -3564 1983 -2040 483 -3538 1981 -2048 1993 -2048 2007 -2044 1995 -34696 489 -3550 453 -3570 1995 -2050 447 -3564 1983 -2040 1999 -2034 2003 -2034 1995 -34690 495 -3580 433 -3586 1969 -2064 453 -3552 1995 -2036 1991 -2056 1997 -2046 1987 -34706 441 -3636 373 -3626 1959 -2074 419 -3592 1963 -2074 1989 -2044 1971 -2070 1981 -34698 509 -3526 503 -3528 2005 -2034 481 -3528 1993 -2042 1999 -2066 1989 -2034 2003 -34678 495 -3540 481 -3546 1997 -2046 473 -3554 1999 -2034 2001 -2036 1995 -2046 1983 -34720 475 -3560 469 -3548 1997 -2030 485 -3566 1963 -2066 1983 -2046 1999 -2034 1973 -34734 487 -3560 443 -3584 1981 -2052 445 -3568 1987 -2044 1999 -2032 1993 -2034 2007 -34702 491 -3560 459 -3558 1967 -2070 455 -3556 2003 -2036 1977 -2042 2005 -2028 1997 -34730 461 -3564 473 -3536 2011 -2046 449 -3566 1989 -2044 1997 -2042 1971 -2054 2001 -34708 475 -3560 479 -3528 1999 -2040 485 -3566 1963 -2040 2013 -2042 1995 -2034 1987 -34694 519 -3554 441 -3582 1981 -2052 449 -3564 1985 -2040 1993 -2034 1991 -2062 1975 -34714 529 -3534 463 -3558 1969 -2068 451 -3560 2003 -2038 1993 -2042 1969 -2070 1975 -34720 493 -3582 383 -3616 1937 -2072 469 -3558 1995 -2036 1975 -2066 1995 -2042 1989 -34678 531 -3560 391 -3622 1937 -2094 429 -3588 1967 -2070 1981 -2054 1965 -2038 2021 -34682 525 -3524 481 -3564 1989 -2040 445 -3554 1997 -2040 2005 -2034 2001 -2024 1991 -34706 517 -3586 409 -3610 1927 -2076 451 -3558 1967 -2074 1993 -2038 2001 -2040 1975 -34714 495 -3588 409 -3602 1933 -2088 447 -3584 1965 -2044 1999 -2036 2007 -2030 1995 -34692 525 -3538 447 -3580 1981 -2042 487 -3542 1995 -2040 1969 -2072 1969 -2044 1991 -34714 443 -3636 399 -3630 1899 -2106 413 -3584 1997 diff --git a/lib/subghz/protocols/linear_delta3.c b/lib/subghz/protocols/linear_delta3.c new file mode 100644 index 00000000000..869edac84e6 --- /dev/null +++ b/lib/subghz/protocols/linear_delta3.c @@ -0,0 +1,359 @@ +#include "linear_delta3.h" + +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#define TAG "SubGhzProtocolLinearDelta3" + +#define DIP_PATTERN "%c%c%c%c%c%c%c%c" +#define 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 & 0x0004 ? '1' : '0'), \ + (dip & 0x0002 ? '1' : '0'), (dip & 0x0001 ? '1' : '0') + +static const SubGhzBlockConst subghz_protocol_linear_delta3_const = { + .te_short = 500, + .te_long = 2000, + .te_delta = 150, + .min_count_bit_for_found = 8, +}; + +struct SubGhzProtocolDecoderLinearDelta3 { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; + + uint32_t last_data; +}; + +struct SubGhzProtocolEncoderLinearDelta3 { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + LinearDecoderStepReset = 0, + LinearDecoderStepSaveDuration, + LinearDecoderStepCheckDuration, +} LinearDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_linear_delta3_decoder = { + .alloc = subghz_protocol_decoder_linear_delta3_alloc, + .free = subghz_protocol_decoder_linear_delta3_free, + + .feed = subghz_protocol_decoder_linear_delta3_feed, + .reset = subghz_protocol_decoder_linear_delta3_reset, + + .get_hash_data = subghz_protocol_decoder_linear_delta3_get_hash_data, + .serialize = subghz_protocol_decoder_linear_delta3_serialize, + .deserialize = subghz_protocol_decoder_linear_delta3_deserialize, + .get_string = subghz_protocol_decoder_linear_delta3_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_linear_delta3_encoder = { + .alloc = subghz_protocol_encoder_linear_delta3_alloc, + .free = subghz_protocol_encoder_linear_delta3_free, + + .deserialize = subghz_protocol_encoder_linear_delta3_deserialize, + .stop = subghz_protocol_encoder_linear_delta3_stop, + .yield = subghz_protocol_encoder_linear_delta3_yield, +}; + +const SubGhzProtocol subghz_protocol_linear_delta3 = { + .name = SUBGHZ_PROTOCOL_LINEAR_DELTA3_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_linear_delta3_decoder, + .encoder = &subghz_protocol_linear_delta3_encoder, +}; + +void* subghz_protocol_encoder_linear_delta3_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderLinearDelta3* instance = + malloc(sizeof(SubGhzProtocolEncoderLinearDelta3)); + + instance->base.protocol = &subghz_protocol_linear_delta3; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->encoder.repeat = 10; + instance->encoder.size_upload = 16; + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_running = false; + return instance; +} + +void subghz_protocol_encoder_linear_delta3_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderLinearDelta3* instance = context; + free(instance->encoder.upload); + free(instance); +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderLinearDelta3 instance + * @return true On success + */ +static bool + subghz_protocol_encoder_linear_delta3_get_upload(SubGhzProtocolEncoderLinearDelta3* 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, "Size upload exceeds allocated encoder buffer."); + return false; + } else { + instance->encoder.size_upload = size_upload; + } + + //Send key data + for(uint8_t i = instance->generic.data_count_bit; i > 1; i--) { + if(bit_read(instance->generic.data, i - 1)) { + //send bit 1 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_linear_delta3_const.te_short); + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_linear_delta3_const.te_short * 7); + } else { + //send bit 0 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_linear_delta3_const.te_long); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_linear_delta3_const.te_long); + } + } + //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_linear_delta3_const.te_short); + //Send PT_GUARD + instance->encoder.upload[index] = level_duration_make( + false, (uint32_t)subghz_protocol_linear_delta3_const.te_short * 73); + } else { + //send bit 0 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_linear_delta3_const.te_long); + //Send PT_GUARD + instance->encoder.upload[index] = level_duration_make( + false, (uint32_t)subghz_protocol_linear_delta3_const.te_short * 70); + } + + return true; +} + +bool subghz_protocol_encoder_linear_delta3_deserialize( + void* context, + FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderLinearDelta3* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_linear_delta3_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + if(!subghz_protocol_encoder_linear_delta3_get_upload(instance)) break; + instance->encoder.is_running = true; + + res = true; + } while(false); + + return res; +} + +void subghz_protocol_encoder_linear_delta3_stop(void* context) { + SubGhzProtocolEncoderLinearDelta3* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_linear_delta3_yield(void* context) { + SubGhzProtocolEncoderLinearDelta3* instance = context; + + if(instance->encoder.repeat == 0 || !instance->encoder.is_running) { + instance->encoder.is_running = 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_linear_delta3_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderLinearDelta3* instance = + malloc(sizeof(SubGhzProtocolDecoderLinearDelta3)); + instance->base.protocol = &subghz_protocol_linear_delta3; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_linear_delta3_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderLinearDelta3* instance = context; + free(instance); +} + +void subghz_protocol_decoder_linear_delta3_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderLinearDelta3* instance = context; + instance->decoder.parser_step = LinearDecoderStepReset; + instance->last_data = 0; +} + +void subghz_protocol_decoder_linear_delta3_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderLinearDelta3* instance = context; + switch(instance->decoder.parser_step) { + case LinearDecoderStepReset: + if((!level) && + (DURATION_DIFF(duration, subghz_protocol_linear_delta3_const.te_short * 70) < + subghz_protocol_linear_delta3_const.te_delta * 24)) { + //Found header Linear + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->decoder.parser_step = LinearDecoderStepSaveDuration; + } + break; + case LinearDecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = LinearDecoderStepCheckDuration; + } else { + instance->decoder.parser_step = LinearDecoderStepReset; + } + break; + case LinearDecoderStepCheckDuration: + if(!level) { + if(duration >= (subghz_protocol_linear_delta3_const.te_short * 10)) { + instance->decoder.parser_step = LinearDecoderStepReset; + if(DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_linear_delta3_const.te_short) < + subghz_protocol_linear_delta3_const.te_delta) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + } else if( + DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_linear_delta3_const.te_long) < + subghz_protocol_linear_delta3_const.te_delta) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + } + if(instance->decoder.decode_count_bit == + subghz_protocol_linear_delta3_const.min_count_bit_for_found) { + if((instance->last_data == instance->decoder.decode_data) && + instance->last_data) { + instance->generic.serial = 0x0; + instance->generic.btn = 0x0; + + 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 = LinearDecoderStepSaveDuration; + instance->last_data = instance->decoder.decode_data; + } + break; + } + + if((DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_linear_delta3_const.te_short) < + subghz_protocol_linear_delta3_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_linear_delta3_const.te_short * 7) < + subghz_protocol_linear_delta3_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = LinearDecoderStepSaveDuration; + } else if( + (DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_linear_delta3_const.te_long) < + subghz_protocol_linear_delta3_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_linear_delta3_const.te_long) < + subghz_protocol_linear_delta3_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = LinearDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = LinearDecoderStepReset; + } + + } else { + instance->decoder.parser_step = LinearDecoderStepReset; + } + break; + } +} + +uint8_t subghz_protocol_decoder_linear_delta3_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderLinearDelta3* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8)); +} + +bool subghz_protocol_decoder_linear_delta3_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderLinearDelta3* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +bool subghz_protocol_decoder_linear_delta3_deserialize( + void* context, + FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderLinearDelta3* instance = context; + bool ret = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_linear_delta3_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + ret = true; + } while(false); + return ret; +} + +void subghz_protocol_decoder_linear_delta3_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderLinearDelta3* instance = context; + + uint32_t data = instance->generic.data & 0xFF; + + furi_string_cat_printf( + output, + "%s %dbit\r\n" + "Key:0x%lX\r\n" + "DIP:" DIP_PATTERN "\r\n", + instance->generic.protocol_name, + instance->generic.data_count_bit, + data, + DATA_TO_DIP(data)); +} diff --git a/lib/subghz/protocols/linear_delta3.h b/lib/subghz/protocols/linear_delta3.h new file mode 100644 index 00000000000..2f0a32e6827 --- /dev/null +++ b/lib/subghz/protocols/linear_delta3.h @@ -0,0 +1,111 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_LINEAR_DELTA3_NAME "LinearDelta3" + +typedef struct SubGhzProtocolDecoderLinearDelta3 SubGhzProtocolDecoderLinearDelta3; +typedef struct SubGhzProtocolEncoderLinearDelta3 SubGhzProtocolEncoderLinearDelta3; + +extern const SubGhzProtocolDecoder subghz_protocol_linear_delta3_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_linear_delta3_encoder; +extern const SubGhzProtocol subghz_protocol_linear_delta3; + +/** + * Allocate SubGhzProtocolEncoderLinearDelta3. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderLinearDelta3* pointer to a SubGhzProtocolEncoderLinearDelta3 instance + */ +void* subghz_protocol_encoder_linear_delta3_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderLinearDelta3. + * @param context Pointer to a SubGhzProtocolEncoderLinearDelta3 instance + */ +void subghz_protocol_encoder_linear_delta3_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderLinearDelta3 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_encoder_linear_delta3_deserialize( + void* context, + FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderLinearDelta3 instance + */ +void subghz_protocol_encoder_linear_delta3_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderLinearDelta3 instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_linear_delta3_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderLinearDelta3. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderLinearDelta3* pointer to a SubGhzProtocolDecoderLinearDelta3 instance + */ +void* subghz_protocol_decoder_linear_delta3_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderLinearDelta3. + * @param context Pointer to a SubGhzProtocolDecoderLinearDelta3 instance + */ +void subghz_protocol_decoder_linear_delta3_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderLinearDelta3. + * @param context Pointer to a SubGhzProtocolDecoderLinearDelta3 instance + */ +void subghz_protocol_decoder_linear_delta3_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderLinearDelta3 instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_linear_delta3_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderLinearDelta3 instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_linear_delta3_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderLinearDelta3. + * @param context Pointer to a SubGhzProtocolDecoderLinearDelta3 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_decoder_linear_delta3_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderLinearDelta3. + * @param context Pointer to a SubGhzProtocolDecoderLinearDelta3 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_decoder_linear_delta3_deserialize( + void* context, + FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderLinearDelta3 instance + * @param output Resulting text + */ +void subghz_protocol_decoder_linear_delta3_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/protocol_items.c b/lib/subghz/protocols/protocol_items.c index 2022e9c47c9..a1f1bc149fe 100644 --- a/lib/subghz/protocols/protocol_items.c +++ b/lib/subghz/protocols/protocol_items.c @@ -1,19 +1,44 @@ #include "protocol_items.h" const SubGhzProtocol* subghz_protocol_registry_items[] = { - &subghz_protocol_gate_tx, &subghz_protocol_keeloq, &subghz_protocol_star_line, - &subghz_protocol_nice_flo, &subghz_protocol_came, &subghz_protocol_faac_slh, - &subghz_protocol_nice_flor_s, &subghz_protocol_came_twee, &subghz_protocol_came_atomo, - &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_linear, &subghz_protocol_secplus_v2, - &subghz_protocol_secplus_v1, &subghz_protocol_megacode, &subghz_protocol_holtek, - &subghz_protocol_chamb_code, &subghz_protocol_power_smart, &subghz_protocol_marantec, - &subghz_protocol_bett, &subghz_protocol_doitrand, &subghz_protocol_phoenix_v2, - &subghz_protocol_honeywell_wdb, &subghz_protocol_magellan, &subghz_protocol_intertechno_v3, - &subghz_protocol_clemsa, &subghz_protocol_ansonic, &subghz_protocol_smc5326, + &subghz_protocol_gate_tx, + &subghz_protocol_keeloq, + &subghz_protocol_star_line, + &subghz_protocol_nice_flo, + &subghz_protocol_came, + &subghz_protocol_faac_slh, + &subghz_protocol_nice_flor_s, + &subghz_protocol_came_twee, + &subghz_protocol_came_atomo, + &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_linear, + &subghz_protocol_secplus_v2, + &subghz_protocol_secplus_v1, + &subghz_protocol_megacode, + &subghz_protocol_holtek, + &subghz_protocol_chamb_code, + &subghz_protocol_power_smart, + &subghz_protocol_marantec, + &subghz_protocol_bett, + &subghz_protocol_doitrand, + &subghz_protocol_phoenix_v2, + &subghz_protocol_honeywell_wdb, + &subghz_protocol_magellan, + &subghz_protocol_intertechno_v3, + &subghz_protocol_clemsa, + &subghz_protocol_ansonic, + &subghz_protocol_smc5326, &subghz_protocol_holtek_th12x, + &subghz_protocol_linear_delta3, }; const SubGhzProtocolRegistry subghz_protocol_registry = { diff --git a/lib/subghz/protocols/protocol_items.h b/lib/subghz/protocols/protocol_items.h index 998fb56b301..185e47394fa 100644 --- a/lib/subghz/protocols/protocol_items.h +++ b/lib/subghz/protocols/protocol_items.h @@ -21,6 +21,7 @@ #include "gate_tx.h" #include "raw.h" #include "linear.h" +#include "linear_delta3.h" #include "secplus_v2.h" #include "secplus_v1.h" #include "megacode.h" From 0afc4a8982265d432a473b26cc94973f105973ca Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Wed, 8 Feb 2023 20:37:24 +0400 Subject: [PATCH 390/824] [FL-3092] SubGhz: add DOOYA protocol (#2178) * SubGhz: add DOOYA protocol * SubGhz: add unit_test DOOYA protocol * SubGhz: fix protocol Dooya Co-authored-by: Aleksandr Kutuzov --- .../debug/unit_tests/subghz/subghz_test.c | 17 +- assets/unit_tests/subghz/dooya.sub | 7 + assets/unit_tests/subghz/dooya_raw.sub | 8 + assets/unit_tests/subghz/test_random_raw.sub | 3 + lib/subghz/protocols/dooya.c | 447 ++++++++++++++++++ lib/subghz/protocols/dooya.h | 107 +++++ lib/subghz/protocols/protocol_items.c | 3 +- lib/subghz/protocols/protocol_items.h | 1 + 8 files changed, 591 insertions(+), 2 deletions(-) create mode 100644 assets/unit_tests/subghz/dooya.sub create mode 100644 assets/unit_tests/subghz/dooya_raw.sub create mode 100644 lib/subghz/protocols/dooya.c create mode 100644 lib/subghz/protocols/dooya.h diff --git a/applications/debug/unit_tests/subghz/subghz_test.c b/applications/debug/unit_tests/subghz/subghz_test.c index 83fadeda973..0dba19d9081 100644 --- a/applications/debug/unit_tests/subghz/subghz_test.c +++ b/applications/debug/unit_tests/subghz/subghz_test.c @@ -13,7 +13,7 @@ #define CAME_ATOMO_DIR_NAME EXT_PATH("subghz/assets/came_atomo") #define NICE_FLOR_S_DIR_NAME EXT_PATH("subghz/assets/nice_flor_s") #define TEST_RANDOM_DIR_NAME EXT_PATH("unit_tests/subghz/test_random_raw.sub") -#define TEST_RANDOM_COUNT_PARSE 295 +#define TEST_RANDOM_COUNT_PARSE 300 #define TEST_TIMEOUT 10000 static SubGhzEnvironment* environment_handler; @@ -612,6 +612,13 @@ MU_TEST(subghz_decoder_holtek_ht12x_test) { "Test decoder " SUBGHZ_PROTOCOL_HOLTEK_HT12X_NAME " error\r\n"); } +MU_TEST(subghz_decoder_dooya_test) { + mu_assert( + subghz_decoder_test( + EXT_PATH("unit_tests/subghz/dooya_raw.sub"), SUBGHZ_PROTOCOL_DOOYA_NAME), + "Test decoder " SUBGHZ_PROTOCOL_DOOYA_NAME " error\r\n"); +} + //test encoders MU_TEST(subghz_encoder_princeton_test) { mu_assert( @@ -757,6 +764,12 @@ MU_TEST(subghz_encoder_holtek_ht12x_test) { "Test encoder " SUBGHZ_PROTOCOL_HOLTEK_HT12X_NAME " error\r\n"); } +MU_TEST(subghz_encoder_dooya_test) { + mu_assert( + subghz_encoder_test(EXT_PATH("unit_tests/subghz/dooya.sub")), + "Test encoder " SUBGHZ_PROTOCOL_DOOYA_NAME " error\r\n"); +} + MU_TEST(subghz_random_test) { mu_assert(subghz_decode_random_test(TEST_RANDOM_DIR_NAME), "Random test error\r\n"); } @@ -803,6 +816,7 @@ MU_TEST_SUITE(subghz) { MU_RUN_TEST(subghz_decoder_ansonic_test); MU_RUN_TEST(subghz_decoder_smc5326_test); MU_RUN_TEST(subghz_decoder_holtek_ht12x_test); + MU_RUN_TEST(subghz_decoder_dooya_test); MU_RUN_TEST(subghz_encoder_princeton_test); MU_RUN_TEST(subghz_encoder_came_test); @@ -828,6 +842,7 @@ MU_TEST_SUITE(subghz) { MU_RUN_TEST(subghz_encoder_ansonic_test); MU_RUN_TEST(subghz_encoder_smc5326_test); MU_RUN_TEST(subghz_encoder_holtek_ht12x_test); + MU_RUN_TEST(subghz_encoder_dooya_test); MU_RUN_TEST(subghz_random_test); subghz_test_deinit(); diff --git a/assets/unit_tests/subghz/dooya.sub b/assets/unit_tests/subghz/dooya.sub new file mode 100644 index 00000000000..0767a1a738f --- /dev/null +++ b/assets/unit_tests/subghz/dooya.sub @@ -0,0 +1,7 @@ +Filetype: Flipper SubGhz Key File +Version: 1 +Frequency: 433920000 +Preset: FuriHalSubGhzPresetOok650Async +Protocol: Dooya +Bit: 40 +Key: 00 00 00 E1 DC 03 05 11 diff --git a/assets/unit_tests/subghz/dooya_raw.sub b/assets/unit_tests/subghz/dooya_raw.sub new file mode 100644 index 00000000000..6c3ca1627a7 --- /dev/null +++ b/assets/unit_tests/subghz/dooya_raw.sub @@ -0,0 +1,8 @@ +Filetype: Flipper SubGhz RAW File +Version: 1 +Frequency: 433920000 +Preset: FuriHalSubGhzPresetOok650Async +Protocol: RAW +RAW_Data: 4046 -17306 65 -298 97 -100 133 -268 265 -330 133 -132 1723 -16806 165 -132 99 -920 65 -622 789 -130 99 -66 361 -98 295 -166 73573 -17510 97 -492 129 -728 529 -100 1063 -164 295 -66 1119 -14962 627 -166 363 -264 427 -132 593 -100 633 -132 39555 -16938 99 -2024 65 -100 97 -164 99 -66 399 -100 123891 -16736 163 -200 97 -200 165 -264 65 -828 427 -132 871 -5132 591 -490 595 -486 605 -454 275 -822 241 -824 273 -784 321 -782 649 -444 653 -408 657 -428 321 -744 693 -388 699 -388 707 -392 313 -752 345 -750 317 -744 351 -730 355 -738 323 -774 327 -748 329 -750 695 -386 701 -354 381 -722 351 -720 385 -718 351 -718 345 -738 705 -382 329 -736 713 -360 387 -718 369 -718 367 -706 735 -352 375 -726 351 -722 351 -720 719 -7808 4845 -1474 743 -332 741 -370 705 -370 349 -718 383 -716 345 -712 381 -704 747 -326 747 -350 737 -352 351 -718 719 -360 741 -366 687 -362 375 -704 381 -724 351 -740 353 -712 357 -718 359 -744 363 -688 365 -722 727 -354 727 -354 379 -724 351 -722 353 -720 387 -718 353 -718 703 -374 351 -716 735 -354 365 -708 353 -734 351 -746 717 -356 359 -720 371 -704 371 -720 731 -7786 4847 -1482 711 -386 711 -358 743 -330 373 -708 359 -748 349 -740 351 -716 719 -356 727 -354 739 -354 351 -718 719 -362 743 -364 721 -330 373 -706 381 -722 351 -740 353 -712 359 -720 361 -722 361 -720 361 -720 725 -354 731 -354 381 -720 353 -722 385 -720 351 -720 349 -716 735 -354 361 -748 711 -364 347 -740 365 -722 365 -720 695 -384 371 -704 381 -702 377 -710 709 -7804 4853 -1468 743 -336 735 -358 719 -352 379 -724 353 -722 353 -720 387 -686 721 -360 721 -362 743 -332 387 -718 721 -366 701 -382 701 -350 377 -720 351 -740 353 -714 357 -710 397 -710 365 -702 385 -688 377 -724 731 -352 703 -354 379 -736 343 -740 357 -720 349 -706 385 -718 719 -354 365 -724 735 -352 377 -724 355 -720 353 -720 721 -358 387 -686 387 -718 353 -718 733 -7796 4821 -1492 739 -350 719 -334 737 -350 365 -722 373 -722 367 -708 371 -702 747 -352 711 -358 743 -364 343 -706 749 -352 717 -350 717 -384 327 -736 351 -746 355 -716 357 -720 359 -710 365 -742 365 -708 367 -704 711 -354 743 -356 387 -684 373 -706 381 -722 351 -740 353 -714 721 -356 361 -720 733 -352 375 -694 385 -724 353 -722 719 -356 385 -686 385 -718 351 -716 731 -7792 4843 -1480 717 -354 719 -386 717 -354 359 -720 351 -708 387 -712 355 -718 721 -356 727 -354 739 -356 351 -718 741 -364 +RAW_Data: 705 -370 703 -372 351 -718 383 -720 347 -720 347 -714 381 -704 353 -744 357 -718 355 -720 723 -356 725 -354 379 -722 351 -722 353 -722 385 -718 351 -718 721 -372 351 -716 719 -372 351 -718 383 -716 345 -714 743 -346 361 -740 353 -712 357 -710 725 -7818 4837 -1498 713 -356 709 -360 741 -332 375 -706 359 -750 351 -706 353 -748 719 -356 723 -352 739 -354 351 -718 709 -364 719 -362 721 -364 385 -718 353 -718 383 -682 377 -712 349 -734 353 -742 355 -712 359 -722 723 -354 729 -352 381 -722 353 -722 351 -720 387 -718 353 -716 701 -388 345 -722 737 -354 357 -722 351 -708 387 -712 717 -350 731 -354 741 -356 743 -330 375 -8180 4829 -1468 739 -364 707 -354 729 -352 379 -722 353 -720 387 -686 387 -718 707 -368 721 -366 707 -368 351 -718 735 -354 719 -354 719 -388 329 -746 349 -738 351 -712 359 -718 361 -742 365 -708 371 -706 373 -720 733 -320 733 -354 383 -720 353 -720 387 -718 351 -716 385 -714 703 -388 327 -746 705 -348 387 -702 385 -690 385 -724 713 -358 709 -362 743 -364 709 -370 351 -8162 4837 -1482 715 -388 715 -352 715 -384 325 -730 353 -744 353 -712 359 -720 723 -354 733 -354 745 -356 351 -720 719 -362 741 -330 737 -382 349 -722 345 -724 361 -744 349 -704 383 -716 357 -718 357 -720 361 -720 723 -354 733 -354 383 -720 387 -686 387 -718 353 -718 349 -716 731 -384 347 -724 721 -352 365 -706 353 -732 353 -746 717 -356 723 -352 739 -354 711 -360 385 -8146 4841 -1470 737 -344 739 -326 751 -352 377 -690 387 -724 353 -724 353 -722 711 -360 743 -364 721 -330 387 -716 703 -386 721 -356 721 -354 363 -706 349 -734 351 -746 355 -718 355 -712 363 -744 365 -708 369 -722 695 -352 731 -354 381 -722 353 -722 351 -734 351 -716 383 -720 723 -354 333 -736 739 -348 361 -708 351 -748 355 -712 725 -354 727 -352 741 -352 713 -358 385 -8134 4855 -1474 719 -358 709 -362 721 -364 387 -716 351 -718 385 -712 347 -712 739 -334 739 -354 729 -352 379 -722 717 -354 711 -360 743 -332 387 -718 351 -716 377 -708 349 -730 353 -742 355 -710 359 -720 359 -720 723 -354 729 -352 381 -720 353 -722 351 -722 387 -684 387 -716 703 -384 349 -722 737 -354 329 -750 349 -738 353 -712 719 -356 725 -354 741 -354 717 -358 385 -8126 4861 -1470 735 -344 731 -346 729 -348 383 -718 347 -712 353 -734 353 -746 715 -356 725 -350 741 -352 351 -718 741 -366 721 -366 705 -370 353 -718 385 -682 377 -710 349 -734 353 -744 355 -710 359 -710 397 -688 +RAW_Data: 727 -354 729 -352 379 -724 353 -722 353 -718 387 -716 353 -716 735 -348 383 -682 727 -386 347 -722 347 -712 381 -706 747 -326 747 -350 737 -352 711 -358 diff --git a/assets/unit_tests/subghz/test_random_raw.sub b/assets/unit_tests/subghz/test_random_raw.sub index 7571d688d5c..aee15a16d9c 100644 --- a/assets/unit_tests/subghz/test_random_raw.sub +++ b/assets/unit_tests/subghz/test_random_raw.sub @@ -175,3 +175,6 @@ RAW_Data: 75 -1694 101 -122 73 -864 51 -250 129 -406 77 -630 77 -610 101 -781 12 RAW_Data: 83 -320 81 -4648 101 -3726 173 -1418 85 -348 53 -2994 79 -1390 51 -1656 107 -764 53 -134 79 -1619 131 -932 55 -2810 107 -3218 79 -765 107 -654 103 -1498 77 -228 51 -134 247 -1526 51 -3903 103 -1495 179 -282 77 -392 53 -1756 105 -368 111 -486 51 -298 53 -216 113 -358 51 -266 187 -1059 81 -780 105 -238 51 -482 53 -791 109 -2169 77 -5304 53 -398 79 -650 51 -54 51 -1789 73 -198 101 -1580 101 -746 97 -4518 53 -744 51 -1064 101 -928 111 -392 185 -869 103 -320 133 -704 81 -244 53 -1628 75 -634 79 -666 183 -1276 83 -218 107 -1163 55 -1276 127 -1144 73 -1400 81 -266 77 -568 129 -806 121 -1420 103 -848 77 -982 103 -2132 81 -1610 101 -1218 55 -2208 75 -2735 53 -921 53 -724 51 -472 83 -3164 185 -400 77 -812 81 -306 215 -2167 53 -130 53 -272 81 -400 79 -1272 81 -418 51 -1381 73 -340 101 -2169 81 -2330 137 -2698 99 -2340 99 -126 51 -1714 55 -488 81 -3500 51 -404 77 -1422 77 -856 215 -80 51 -2308 53 -134 77 -2036 75 -5175 129 -946 239 -638 53 -244 55 -564 105 -826 71 -1632 77 -106 129 -246 135 -366 79 -724 79 -1535 57 -1085 113 -1320 79 -3111 127 -1578 75 -324 75 -102 173 -364 79 -1374 53 -1508 107 -622 51 -526 109 -584 187 -2648 51 -106 79 -380 103 -604 51 -1244 73 -5766 107 -1934 177 -702 51 -1277 53 -1643 79 -1446 81 -4098 75 -574 103 -432 189 -1436 107 -454 79 -132 105 -136 81 -112 113 -942 239 -1238 79 -952 157 -340 51 -314 191 -456 53 -3368 101 -150 99 -464 51 -718 73 -770 101 -150 73 -2132 75 -557 77 -680 81 -3512 151 -760 75 -332 75 -1212 131 -1468 79 -1955 101 -541 75 -344 79 -2146 53 -2299 97 -720 79 -2518 79 -3807 51 -1272 75 -352 77 -52 75 -586 53 -1142 79 -82 81 -2400 157 -324 81 -268 103 -1154 81 -1175 79 -1191 51 -1074 53 -2566 137 -854 75 -1497 51 -4533 51 -2290 51 -344 77 -348 55 -1182 77 -897 135 -874 51 -1064 51 -208 55 -140 55 -1334 133 -1238 157 -1669 113 -2128 75 -848 85 -510 83590 -126 333 -280 331 -252 331 -6946 331 -276 331 -276 329 -278 329 -276 331 -276 331 -276 331 -276 347 -238 351 -254 353 -268 323 -270 345 -6924 335 -282 307 -304 307 -304 281 -304 307 -302 307 -302 305 -302 307 -302 305 -302 281 -124 507 -282 305 -302 305 -6984 279 -328 277 -328 279 -328 277 -330 277 -304 303 -302 305 -302 305 -302 303 -304 303 -100 507 -314 295 -298 293 -6986 283 -334 281 -306 283 -328 283 -328 281 -328 283 -328 255 -352 RAW_Data: -66 11813 -100 14655 -98 40111 -66 1625 -2116 1933 -34732 501 -11730 235 -3728 1887 -2106 1933 -2092 1971 -2072 1959 -34712 511 -3554 445 -3556 1997 -2036 455 -3594 1963 -2046 1979 -2076 1961 -2070 1989 -34690 483 -7724 1739 -2226 355 -3684 1857 -2138 1929 -2078 1965 -2074 1947 -34750 487 -3538 473 -3544 1993 -2042 485 -3548 1961 -2070 1965 -2070 1969 -2042 1997 -34716 443 -7734 1753 -2236 323 -3676 1903 -2098 1945 -2102 1927 -2070 1989 -34710 521 -3532 473 -3544 1991 -2032 481 -3556 1969 -2076 1967 -2036 1991 -2066 1969 -34718 467 -7756 1739 -2192 363 -3654 1889 -2132 1929 -2096 1935 -2070 1987 -34716 511 -3522 471 -3554 2009 -2036 459 -3550 2003 -2038 1979 -2042 1999 -2042 1999 -34704 471 -11774 225 -3710 1879 -2162 1885 -2112 1925 -2110 1939 -34738 459 -3636 403 -3612 1939 -2062 451 -3566 1985 -2044 1995 -2040 2009 -2032 2003 -34684 495 -3680 295 -3648 1935 -2098 423 -3562 2001 -2038 1989 -2044 2003 -2036 1977 -34718 461 -3678 295 -3684 1901 -2098 429 -3596 1967 -2036 1981 -2048 1993 -2042 2013 -34686 521 -3530 457 -3568 1999 -2036 455 -3552 1999 -2032 2019 -2024 1995 -2022 1997 -34716 441 -15774 1809 -2192 1905 -2100 1919 -2112 1961 -34720 417 -3830 167 -3710 1863 -2144 357 -3674 1909 -2100 1955 -2062 1977 -2072 1965 -34710 487 -3562 453 -3554 1985 -2052 481 -3536 2019 -2010 2001 -2042 1997 -2038 2005 -34716 451 -3602 433 -3584 1959 -2070 451 -3560 2001 -2038 1993 -2042 1967 -2072 1973 -34712 459 -3622 393 -3624 1933 -2068 457 -3584 1965 -2064 1979 -2052 1967 -2044 1981 -34722 477 -3608 397 -3588 1961 -2096 413 -3596 1971 -2040 1979 -2072 1963 -2070 1959 -34714 495 -3558 483 -3538 1985 -2042 479 -3562 1985 -2046 1967 -2070 1973 -2054 1995 -34688 493 -3578 413 -3614 1939 -2074 465 -3560 1971 -2038 2017 -2018 1995 -2042 2013 -34726 479 -3528 475 -3556 1999 -2036 455 -3570 1999 -2040 1973 -2054 2001 -2032 1987 -34720 477 -3562 445 -3602 1949 -2054 481 -3562 1975 -2060 1963 -2064 1977 -2038 2005 -34702 485 -3570 447 -3550 2015 -2020 479 -3564 1983 -2048 1999 -2034 1971 -2064 1993 -34688 517 -3516 497 -3532 1999 -2038 481 -3558 1997 -2004 2027 -2042 1963 -2038 1997 -34716 491 -3562 461 -3548 1995 -2032 491 -3524 2005 -2036 1989 -2038 1995 -2046 1979 -34714 465 -3682 293 -3680 1905 -2096 431 -3592 1969 -2070 1977 -2052 1965 -2044 1981 -34734 479 -3564 463 -3556 1999 -2032 457 -3550 1995 -2044 2011 -2042 1997 -2006 2027 -34680 531 -3524 483 -3538 1987 -2044 479 -3534 2013 -2048 1965 -2062 1987 -2030 1997 -34712 473 -3592 445 -3562 1975 -2072 451 -3566 1965 -2042 2013 -2046 1963 -2064 1993 -34700 459 -3632 371 -3638 1915 -2084 449 -3568 1987 -2046 1971 -2070 1983 -2022 1997 -34726 487 -3524 477 -3562 1985 -2044 481 -3542 2005 -2040 1995 -2038 1967 -2046 1993 -34710 511 -3528 471 -3560 1967 -2070 459 -3558 1971 RAW_Data: -2072 1971 -2056 1971 -2074 1973 -34714 455 -3634 373 -3634 1901 -2110 419 -3620 1941 -2070 1991 -2040 1999 -2038 1965 -34740 467 -3562 481 -3534 1983 -2070 449 -3546 1999 -2044 1993 -2042 2003 -2036 1975 -34702 521 -3560 443 -3586 1969 -2044 449 -3562 1997 -2046 1987 -2042 2007 -2034 1973 -34732 487 -3562 443 -3582 1979 -2058 445 -3560 1995 -2044 1997 -2028 1987 -2034 2003 -34710 515 -3518 485 -3566 1977 -2036 483 -3536 1999 -2044 2009 -2024 1995 -2068 1973 -34710 487 -3564 471 -3558 1977 -2054 447 -3564 1991 -2042 1997 -2036 2007 -2034 2001 -34684 529 -3526 469 -3548 1989 -2038 483 -3562 1997 -2038 1973 -2034 1999 -2036 1997 -34728 487 -3536 479 -3534 2013 -2044 449 -3570 1985 -2042 1993 -2044 2005 -2014 1995 -34710 473 -3594 439 -3562 1995 -2040 457 -3564 2001 -2040 1975 -2046 1995 -2046 1999 -34704 491 -3548 451 -3570 1991 -2042 447 -3578 1967 -2046 1995 -2042 1999 -2034 2001 -34712 491 -3562 443 -3584 1981 -2018 479 -3562 1985 -2044 1997 -2030 1989 -2040 1997 -34722 489 -3554 459 -3560 1969 -2068 453 -3554 1999 -2034 1987 -2058 1997 -2046 1983 -34702 487 -3534 479 -3564 1983 -2040 483 -3538 1981 -2048 1993 -2048 2007 -2044 1995 -34696 489 -3550 453 -3570 1995 -2050 447 -3564 1983 -2040 1999 -2034 2003 -2034 1995 -34690 495 -3580 433 -3586 1969 -2064 453 -3552 1995 -2036 1991 -2056 1997 -2046 1987 -34706 441 -3636 373 -3626 1959 -2074 419 -3592 1963 -2074 1989 -2044 1971 -2070 1981 -34698 509 -3526 503 -3528 2005 -2034 481 -3528 1993 -2042 1999 -2066 1989 -2034 2003 -34678 495 -3540 481 -3546 1997 -2046 473 -3554 1999 -2034 2001 -2036 1995 -2046 1983 -34720 475 -3560 469 -3548 1997 -2030 485 -3566 1963 -2066 1983 -2046 1999 -2034 1973 -34734 487 -3560 443 -3584 1981 -2052 445 -3568 1987 -2044 1999 -2032 1993 -2034 2007 -34702 491 -3560 459 -3558 1967 -2070 455 -3556 2003 -2036 1977 -2042 2005 -2028 1997 -34730 461 -3564 473 -3536 2011 -2046 449 -3566 1989 -2044 1997 -2042 1971 -2054 2001 -34708 475 -3560 479 -3528 1999 -2040 485 -3566 1963 -2040 2013 -2042 1995 -2034 1987 -34694 519 -3554 441 -3582 1981 -2052 449 -3564 1985 -2040 1993 -2034 1991 -2062 1975 -34714 529 -3534 463 -3558 1969 -2068 451 -3560 2003 -2038 1993 -2042 1969 -2070 1975 -34720 493 -3582 383 -3616 1937 -2072 469 -3558 1995 -2036 1975 -2066 1995 -2042 1989 -34678 531 -3560 391 -3622 1937 -2094 429 -3588 1967 -2070 1981 -2054 1965 -2038 2021 -34682 525 -3524 481 -3564 1989 -2040 445 -3554 1997 -2040 2005 -2034 2001 -2024 1991 -34706 517 -3586 409 -3610 1927 -2076 451 -3558 1967 -2074 1993 -2038 2001 -2040 1975 -34714 495 -3588 409 -3602 1933 -2088 447 -3584 1965 -2044 1999 -2036 2007 -2030 1995 -34692 525 -3538 447 -3580 1981 -2042 487 -3542 1995 -2040 1969 -2072 1969 -2044 1991 -34714 443 -3636 399 -3630 1899 -2106 413 -3584 1997 +RAW_Data: 4046 -17306 65 -298 97 -100 133 -268 265 -330 133 -132 1723 -16806 165 -132 99 -920 65 -622 789 -130 99 -66 361 -98 295 -166 73573 -17510 97 -492 129 -728 529 -100 1063 -164 295 -66 1119 -14962 627 -166 363 -264 427 -132 593 -100 633 -132 39555 -16938 99 -2024 65 -100 97 -164 99 -66 399 -100 123891 -16736 163 -200 97 -200 165 -264 65 -828 427 -132 871 -5132 591 -490 595 -486 605 -454 275 -822 241 -824 273 -784 321 -782 649 -444 653 -408 657 -428 321 -744 693 -388 699 -388 707 -392 313 -752 345 -750 317 -744 351 -730 355 -738 323 -774 327 -748 329 -750 695 -386 701 -354 381 -722 351 -720 385 -718 351 -718 345 -738 705 -382 329 -736 713 -360 387 -718 369 -718 367 -706 735 -352 375 -726 351 -722 351 -720 719 -7808 4845 -1474 743 -332 741 -370 705 -370 349 -718 383 -716 345 -712 381 -704 747 -326 747 -350 737 -352 351 -718 719 -360 741 -366 687 -362 375 -704 381 -724 351 -740 353 -712 357 -718 359 -744 363 -688 365 -722 727 -354 727 -354 379 -724 351 -722 353 -720 387 -718 353 -718 703 -374 351 -716 735 -354 365 -708 353 -734 351 -746 717 -356 359 -720 371 -704 371 -720 731 -7786 4847 -1482 711 -386 711 -358 743 -330 373 -708 359 -748 349 -740 351 -716 719 -356 727 -354 739 -354 351 -718 719 -362 743 -364 721 -330 373 -706 381 -722 351 -740 353 -712 359 -720 361 -722 361 -720 361 -720 725 -354 731 -354 381 -720 353 -722 385 -720 351 -720 349 -716 735 -354 361 -748 711 -364 347 -740 365 -722 365 -720 695 -384 371 -704 381 -702 377 -710 709 -7804 4853 -1468 743 -336 735 -358 719 -352 379 -724 353 -722 353 -720 387 -686 721 -360 721 -362 743 -332 387 -718 721 -366 701 -382 701 -350 377 -720 351 -740 353 -714 357 -710 397 -710 365 -702 385 -688 377 -724 731 -352 703 -354 379 -736 343 -740 357 -720 349 -706 385 -718 719 -354 365 -724 735 -352 377 -724 355 -720 353 -720 721 -358 387 -686 387 -718 353 -718 733 -7796 4821 -1492 739 -350 719 -334 737 -350 365 -722 373 -722 367 -708 371 -702 747 -352 711 -358 743 -364 343 -706 749 -352 717 -350 717 -384 327 -736 351 -746 355 -716 357 -720 359 -710 365 -742 365 -708 367 -704 711 -354 743 -356 387 -684 373 -706 381 -722 351 -740 353 -714 721 -356 361 -720 733 -352 375 -694 385 -724 353 -722 719 -356 385 -686 385 -718 351 -716 731 -7792 4843 -1480 717 -354 719 -386 717 -354 359 -720 351 -708 387 -712 355 -718 721 -356 727 -354 739 -356 351 -718 741 -364 +RAW_Data: 705 -370 703 -372 351 -718 383 -720 347 -720 347 -714 381 -704 353 -744 357 -718 355 -720 723 -356 725 -354 379 -722 351 -722 353 -722 385 -718 351 -718 721 -372 351 -716 719 -372 351 -718 383 -716 345 -714 743 -346 361 -740 353 -712 357 -710 725 -7818 4837 -1498 713 -356 709 -360 741 -332 375 -706 359 -750 351 -706 353 -748 719 -356 723 -352 739 -354 351 -718 709 -364 719 -362 721 -364 385 -718 353 -718 383 -682 377 -712 349 -734 353 -742 355 -712 359 -722 723 -354 729 -352 381 -722 353 -722 351 -720 387 -718 353 -716 701 -388 345 -722 737 -354 357 -722 351 -708 387 -712 717 -350 731 -354 741 -356 743 -330 375 -8180 4829 -1468 739 -364 707 -354 729 -352 379 -722 353 -720 387 -686 387 -718 707 -368 721 -366 707 -368 351 -718 735 -354 719 -354 719 -388 329 -746 349 -738 351 -712 359 -718 361 -742 365 -708 371 -706 373 -720 733 -320 733 -354 383 -720 353 -720 387 -718 351 -716 385 -714 703 -388 327 -746 705 -348 387 -702 385 -690 385 -724 713 -358 709 -362 743 -364 709 -370 351 -8162 4837 -1482 715 -388 715 -352 715 -384 325 -730 353 -744 353 -712 359 -720 723 -354 733 -354 745 -356 351 -720 719 -362 741 -330 737 -382 349 -722 345 -724 361 -744 349 -704 383 -716 357 -718 357 -720 361 -720 723 -354 733 -354 383 -720 387 -686 387 -718 353 -718 349 -716 731 -384 347 -724 721 -352 365 -706 353 -732 353 -746 717 -356 723 -352 739 -354 711 -360 385 -8146 4841 -1470 737 -344 739 -326 751 -352 377 -690 387 -724 353 -724 353 -722 711 -360 743 -364 721 -330 387 -716 703 -386 721 -356 721 -354 363 -706 349 -734 351 -746 355 -718 355 -712 363 -744 365 -708 369 -722 695 -352 731 -354 381 -722 353 -722 351 -734 351 -716 383 -720 723 -354 333 -736 739 -348 361 -708 351 -748 355 -712 725 -354 727 -352 741 -352 713 -358 385 -8134 4855 -1474 719 -358 709 -362 721 -364 387 -716 351 -718 385 -712 347 -712 739 -334 739 -354 729 -352 379 -722 717 -354 711 -360 743 -332 387 -718 351 -716 377 -708 349 -730 353 -742 355 -710 359 -720 359 -720 723 -354 729 -352 381 -720 353 -722 351 -722 387 -684 387 -716 703 -384 349 -722 737 -354 329 -750 349 -738 353 -712 719 -356 725 -354 741 -354 717 -358 385 -8126 4861 -1470 735 -344 731 -346 729 -348 383 -718 347 -712 353 -734 353 -746 715 -356 725 -350 741 -352 351 -718 741 -366 721 -366 705 -370 353 -718 385 -682 377 -710 349 -734 353 -744 355 -710 359 -710 397 -688 +RAW_Data: 727 -354 729 -352 379 -724 353 -722 353 -718 387 -716 353 -716 735 -348 383 -682 727 -386 347 -722 347 -712 381 -706 747 -326 747 -350 737 -352 711 -358 diff --git a/lib/subghz/protocols/dooya.c b/lib/subghz/protocols/dooya.c new file mode 100644 index 00000000000..c70b6d54ee9 --- /dev/null +++ b/lib/subghz/protocols/dooya.c @@ -0,0 +1,447 @@ +#include "dooya.h" +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#define TAG "SubGhzProtocolDooya" + +#define DOYA_SINGLE_CHANNEL 0xFF + +static const SubGhzBlockConst subghz_protocol_dooya_const = { + .te_short = 366, + .te_long = 733, + .te_delta = 120, + .min_count_bit_for_found = 40, +}; + +struct SubGhzProtocolDecoderDooya { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; +}; + +struct SubGhzProtocolEncoderDooya { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + DooyaDecoderStepReset = 0, + DooyaDecoderStepFoundStartBit, + DooyaDecoderStepSaveDuration, + DooyaDecoderStepCheckDuration, +} DooyaDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_dooya_decoder = { + .alloc = subghz_protocol_decoder_dooya_alloc, + .free = subghz_protocol_decoder_dooya_free, + + .feed = subghz_protocol_decoder_dooya_feed, + .reset = subghz_protocol_decoder_dooya_reset, + + .get_hash_data = subghz_protocol_decoder_dooya_get_hash_data, + .serialize = subghz_protocol_decoder_dooya_serialize, + .deserialize = subghz_protocol_decoder_dooya_deserialize, + .get_string = subghz_protocol_decoder_dooya_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_dooya_encoder = { + .alloc = subghz_protocol_encoder_dooya_alloc, + .free = subghz_protocol_encoder_dooya_free, + + .deserialize = subghz_protocol_encoder_dooya_deserialize, + .stop = subghz_protocol_encoder_dooya_stop, + .yield = subghz_protocol_encoder_dooya_yield, +}; + +const SubGhzProtocol subghz_protocol_dooya = { + .name = SUBGHZ_PROTOCOL_DOOYA_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_AM | + SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | + SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_dooya_decoder, + .encoder = &subghz_protocol_dooya_encoder, +}; + +void* subghz_protocol_encoder_dooya_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderDooya* instance = malloc(sizeof(SubGhzProtocolEncoderDooya)); + + instance->base.protocol = &subghz_protocol_dooya; + 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_running = false; + return instance; +} + +void subghz_protocol_encoder_dooya_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderDooya* instance = context; + free(instance->encoder.upload); + free(instance); +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderDooya instance + * @return true On success + */ +static bool subghz_protocol_encoder_dooya_get_upload(SubGhzProtocolEncoderDooya* instance) { + furi_assert(instance); + + size_t index = 0; + size_t size_upload = (instance->generic.data_count_bit * 2) + 2; + if(size_upload > instance->encoder.size_upload) { + FURI_LOG_E(TAG, "Size upload exceeds allocated encoder buffer."); + return false; + } else { + instance->encoder.size_upload = size_upload; + } + + //Send header + if(bit_read(instance->generic.data, 0)) { + instance->encoder.upload[index++] = level_duration_make( + false, + (uint32_t)subghz_protocol_dooya_const.te_long * 12 + + subghz_protocol_dooya_const.te_long); + } else { + instance->encoder.upload[index++] = level_duration_make( + false, + (uint32_t)subghz_protocol_dooya_const.te_long * 12 + + subghz_protocol_dooya_const.te_short); + } + + //Send start bit + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_dooya_const.te_short * 13); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_dooya_const.te_long * 2); + + //Send key data + for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) { + if(bit_read(instance->generic.data, i - 1)) { + //send bit 1 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_dooya_const.te_long); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_dooya_const.te_short); + } else { + //send bit 0 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_dooya_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_dooya_const.te_long); + } + } + return true; +} + +bool subghz_protocol_encoder_dooya_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderDooya* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_dooya_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + if(!subghz_protocol_encoder_dooya_get_upload(instance)) break; + instance->encoder.is_running = true; + + res = true; + } while(false); + + return res; +} + +void subghz_protocol_encoder_dooya_stop(void* context) { + SubGhzProtocolEncoderDooya* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_dooya_yield(void* context) { + SubGhzProtocolEncoderDooya* instance = context; + + if(instance->encoder.repeat == 0 || !instance->encoder.is_running) { + instance->encoder.is_running = 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_dooya_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderDooya* instance = malloc(sizeof(SubGhzProtocolDecoderDooya)); + instance->base.protocol = &subghz_protocol_dooya; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_dooya_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderDooya* instance = context; + free(instance); +} + +void subghz_protocol_decoder_dooya_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderDooya* instance = context; + instance->decoder.parser_step = DooyaDecoderStepReset; +} + +void subghz_protocol_decoder_dooya_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderDooya* instance = context; + + switch(instance->decoder.parser_step) { + case DooyaDecoderStepReset: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_dooya_const.te_long * 12) < + subghz_protocol_dooya_const.te_delta * 20)) { + instance->decoder.parser_step = DooyaDecoderStepFoundStartBit; + } + break; + + case DooyaDecoderStepFoundStartBit: + if(!level) { + if(DURATION_DIFF(duration, subghz_protocol_dooya_const.te_long * 2) < + subghz_protocol_dooya_const.te_delta * 3) { + instance->decoder.parser_step = DooyaDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } else { + instance->decoder.parser_step = DooyaDecoderStepReset; + } + } else if( + DURATION_DIFF(duration, subghz_protocol_dooya_const.te_short * 13) < + subghz_protocol_dooya_const.te_delta * 5) { + break; + } else { + instance->decoder.parser_step = DooyaDecoderStepReset; + } + break; + + case DooyaDecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = DooyaDecoderStepCheckDuration; + } else { + instance->decoder.parser_step = DooyaDecoderStepReset; + } + break; + + case DooyaDecoderStepCheckDuration: + if(!level) { + if(duration >= (subghz_protocol_dooya_const.te_long * 4)) { + //add last bit + if(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_dooya_const.te_short) < + subghz_protocol_dooya_const.te_delta) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + } else if( + DURATION_DIFF(instance->decoder.te_last, subghz_protocol_dooya_const.te_long) < + subghz_protocol_dooya_const.te_delta * 2) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + } else { + instance->decoder.parser_step = DooyaDecoderStepReset; + break; + } + instance->decoder.parser_step = DooyaDecoderStepFoundStartBit; + if(instance->decoder.decode_count_bit == + subghz_protocol_dooya_const.min_count_bit_for_found) { + 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); + } + break; + } else if( + (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_dooya_const.te_short) < + subghz_protocol_dooya_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_dooya_const.te_long) < + subghz_protocol_dooya_const.te_delta * 2)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = DooyaDecoderStepSaveDuration; + } else if( + (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_dooya_const.te_long) < + subghz_protocol_dooya_const.te_delta * 2) && + (DURATION_DIFF(duration, subghz_protocol_dooya_const.te_short) < + subghz_protocol_dooya_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = DooyaDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = DooyaDecoderStepReset; + } + } else { + instance->decoder.parser_step = DooyaDecoderStepReset; + } + break; + } +} + +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + */ +static void subghz_protocol_somfy_telis_check_remote_controller(SubGhzBlockGeneric* instance) { + /* + * serial s/m ch key + * long press down X * E1DC030533, 40b 111000011101110000000011 0000 0101 0011 0011 + * + * short press down 3 * E1DC030533, 40b 111000011101110000000011 0000 0101 0011 0011 + * 3 * E1DC03053C, 40b 111000011101110000000011 0000 0101 0011 1100 + * + * press stop X * E1DC030555, 40b 111000011101110000000011 0000 0101 0101 0101 + * + * long press up X * E1DC030511, 40b 111000011101110000000011 0000 0101 0001 0001 + * + * short press up 3 * E1DC030511, 40b 111000011101110000000011 0000 0101 0001 0001 + * 3 * E1DC03051E, 40b 111000011101110000000011 0000 0101 0001 1110 + * + * serial: 3 byte serial number + * s/m: single (b0000) / multi (b0001) channel console + * ch: channel if single (always b0101) or multi + * key: 0b00010001 - long press up + * 0b00011110 - short press up + * 0b00110011 - long press down + * 0b00111100 - short press down + * 0b01010101 - press stop + * 0b01111001 - press up + down + * 0b10000000 - press up + stop + * 0b10000001 - press down + stop + * 0b11001100 - press P2 + * +*/ + + instance->serial = (instance->data >> 16); + if((instance->data >> 12) & 0x0F) { + instance->cnt = (instance->data >> 8) & 0x0F; + } else { + instance->cnt = DOYA_SINGLE_CHANNEL; + } + instance->btn = instance->data & 0xFF; +} + +uint8_t subghz_protocol_decoder_dooya_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderDooya* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_dooya_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderDooya* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +bool subghz_protocol_decoder_dooya_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderDooya* instance = context; + bool ret = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_dooya_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + ret = true; + } while(false); + return ret; +} + +/** + * Get button name. + * @param btn Button number, 8 bit + */ +static const char* subghz_protocol_dooya_get_name_button(uint8_t btn) { + const char* btn_name; + switch(btn) { + case 0b00010001: + btn_name = "Up_Long"; + break; + case 0b00011110: + btn_name = "Up_Short"; + break; + case 0b00110011: + btn_name = "Down_Long"; + break; + case 0b00111100: + btn_name = "Down_Short"; + break; + case 0b01010101: + btn_name = "Stop"; + break; + case 0b01111001: + btn_name = "Up+Down"; + break; + case 0b10000000: + btn_name = "Up+Stop"; + break; + case 0b10000001: + btn_name = "Down+Stop"; + break; + case 0b11001100: + btn_name = "P2"; + break; + default: + btn_name = "Unknown"; + break; + } + return btn_name; +} + +void subghz_protocol_decoder_dooya_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderDooya* instance = context; + + subghz_protocol_somfy_telis_check_remote_controller(&instance->generic); + + furi_string_cat_printf( + output, + "%s %dbit\r\n" + "Key:0x%010llX\r\n" + "Sn:0x%08lX\r\n" + "Btn:%s\r\n", + instance->generic.protocol_name, + instance->generic.data_count_bit, + instance->generic.data, + instance->generic.serial, + subghz_protocol_dooya_get_name_button(instance->generic.btn)); + if(instance->generic.cnt == DOYA_SINGLE_CHANNEL) { + furi_string_cat_printf(output, "Ch:Single\r\n"); + } else { + furi_string_cat_printf(output, "Ch:%lu\r\n", instance->generic.cnt); + } +} diff --git a/lib/subghz/protocols/dooya.h b/lib/subghz/protocols/dooya.h new file mode 100644 index 00000000000..f0cf843c0d8 --- /dev/null +++ b/lib/subghz/protocols/dooya.h @@ -0,0 +1,107 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_DOOYA_NAME "Dooya" + +typedef struct SubGhzProtocolDecoderDooya SubGhzProtocolDecoderDooya; +typedef struct SubGhzProtocolEncoderDooya SubGhzProtocolEncoderDooya; + +extern const SubGhzProtocolDecoder subghz_protocol_dooya_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_dooya_encoder; +extern const SubGhzProtocol subghz_protocol_dooya; + +/** + * Allocate SubGhzProtocolEncoderDooya. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderDooya* pointer to a SubGhzProtocolEncoderDooya instance + */ +void* subghz_protocol_encoder_dooya_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderDooya. + * @param context Pointer to a SubGhzProtocolEncoderDooya instance + */ +void subghz_protocol_encoder_dooya_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderDooya instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_encoder_dooya_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderDooya instance + */ +void subghz_protocol_encoder_dooya_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderDooya instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_dooya_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderDooya. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderDooya* pointer to a SubGhzProtocolDecoderDooya instance + */ +void* subghz_protocol_decoder_dooya_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderDooya. + * @param context Pointer to a SubGhzProtocolDecoderDooya instance + */ +void subghz_protocol_decoder_dooya_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderDooya. + * @param context Pointer to a SubGhzProtocolDecoderDooya instance + */ +void subghz_protocol_decoder_dooya_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderDooya instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_dooya_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderDooya instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_dooya_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderDooya. + * @param context Pointer to a SubGhzProtocolDecoderDooya instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_decoder_dooya_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderDooya. + * @param context Pointer to a SubGhzProtocolDecoderDooya instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_decoder_dooya_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderDooya instance + * @param output Resulting text + */ +void subghz_protocol_decoder_dooya_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/protocol_items.c b/lib/subghz/protocols/protocol_items.c index a1f1bc149fe..99de18c93d8 100644 --- a/lib/subghz/protocols/protocol_items.c +++ b/lib/subghz/protocols/protocol_items.c @@ -39,8 +39,9 @@ const SubGhzProtocol* subghz_protocol_registry_items[] = { &subghz_protocol_smc5326, &subghz_protocol_holtek_th12x, &subghz_protocol_linear_delta3, + &subghz_protocol_dooya, }; const SubGhzProtocolRegistry subghz_protocol_registry = { .items = subghz_protocol_registry_items, - .size = COUNT_OF(subghz_protocol_registry_items)}; \ No newline at end of file + .size = COUNT_OF(subghz_protocol_registry_items)}; diff --git a/lib/subghz/protocols/protocol_items.h b/lib/subghz/protocols/protocol_items.h index 185e47394fa..4d1551bb3ca 100644 --- a/lib/subghz/protocols/protocol_items.h +++ b/lib/subghz/protocols/protocol_items.h @@ -39,5 +39,6 @@ #include "ansonic.h" #include "smc5326.h" #include "holtek_ht12x.h" +#include "dooya.h" extern const SubGhzProtocolRegistry subghz_protocol_registry; From 31259d530499b2ff6e07fd24a5ec3b8583a41ac0 Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Wed, 8 Feb 2023 20:59:49 +0400 Subject: [PATCH 391/824] [FL-3091] SubGhz: add protocol Alutech at-4n (#2352) * GubGhz: add protocol Alutech at-4n * SubGhz: fix syntax * SubGhz: fix subghz_protocol_decoder_alutech_at_4n_get_hash_data * SubGhz: add unit test alutech at-4n * SubGhz: add name key Co-authored-by: Aleksandr Kutuzov --- .../debug/unit_tests/subghz/subghz_test.c | 14 +- applications/main/subghz/subghz.c | 2 + applications/main/subghz/subghz_cli.c | 4 + assets/resources/subghz/assets/alutech_at_4n | 6 + .../unit_tests/subghz/alutech_at_4n_raw.sub | 10 + assets/unit_tests/subghz/test_random_raw.sub | 5 + firmware/targets/f7/api_symbols.csv | 2 + lib/subghz/environment.c | 16 + lib/subghz/environment.h | 17 + lib/subghz/protocols/alutech_at_4n.c | 455 ++++++++++++++++++ lib/subghz/protocols/alutech_at_4n.h | 74 +++ lib/subghz/protocols/came_atomo.c | 2 +- lib/subghz/protocols/keeloq.c | 13 +- lib/subghz/protocols/protocol_items.c | 1 + lib/subghz/protocols/protocol_items.h | 1 + 15 files changed, 617 insertions(+), 5 deletions(-) create mode 100644 assets/resources/subghz/assets/alutech_at_4n create mode 100644 assets/unit_tests/subghz/alutech_at_4n_raw.sub create mode 100644 lib/subghz/protocols/alutech_at_4n.c create mode 100644 lib/subghz/protocols/alutech_at_4n.h diff --git a/applications/debug/unit_tests/subghz/subghz_test.c b/applications/debug/unit_tests/subghz/subghz_test.c index 0dba19d9081..705d6f2f6f2 100644 --- a/applications/debug/unit_tests/subghz/subghz_test.c +++ b/applications/debug/unit_tests/subghz/subghz_test.c @@ -12,8 +12,9 @@ #define KEYSTORE_DIR_NAME EXT_PATH("subghz/assets/keeloq_mfcodes") #define CAME_ATOMO_DIR_NAME EXT_PATH("subghz/assets/came_atomo") #define NICE_FLOR_S_DIR_NAME EXT_PATH("subghz/assets/nice_flor_s") +#define ALUTECH_AT_4N_DIR_NAME EXT_PATH("subghz/assets/alutech_at_4n") #define TEST_RANDOM_DIR_NAME EXT_PATH("unit_tests/subghz/test_random_raw.sub") -#define TEST_RANDOM_COUNT_PARSE 300 +#define TEST_RANDOM_COUNT_PARSE 304 #define TEST_TIMEOUT 10000 static SubGhzEnvironment* environment_handler; @@ -43,6 +44,8 @@ static void subghz_test_init(void) { environment_handler, CAME_ATOMO_DIR_NAME); subghz_environment_set_nice_flor_s_rainbow_table_file_name( environment_handler, NICE_FLOR_S_DIR_NAME); + subghz_environment_set_alutech_at_4n_rainbow_table_file_name( + environment_handler, ALUTECH_AT_4N_DIR_NAME); subghz_environment_set_protocol_registry( environment_handler, (void*)&subghz_protocol_registry); @@ -619,6 +622,14 @@ MU_TEST(subghz_decoder_dooya_test) { "Test decoder " SUBGHZ_PROTOCOL_DOOYA_NAME " error\r\n"); } +MU_TEST(subghz_decoder_alutech_at_4n_test) { + mu_assert( + subghz_decoder_test( + EXT_PATH("unit_tests/subghz/alutech_at_4n_raw.sub"), + SUBGHZ_PROTOCOL_ALUTECH_AT_4N_NAME), + "Test decoder " SUBGHZ_PROTOCOL_ALUTECH_AT_4N_NAME " error\r\n"); +} + //test encoders MU_TEST(subghz_encoder_princeton_test) { mu_assert( @@ -817,6 +828,7 @@ MU_TEST_SUITE(subghz) { MU_RUN_TEST(subghz_decoder_smc5326_test); MU_RUN_TEST(subghz_decoder_holtek_ht12x_test); MU_RUN_TEST(subghz_decoder_dooya_test); + MU_RUN_TEST(subghz_decoder_alutech_at_4n_test); MU_RUN_TEST(subghz_encoder_princeton_test); MU_RUN_TEST(subghz_encoder_came_test); diff --git a/applications/main/subghz/subghz.c b/applications/main/subghz/subghz.c index b7564ab57e6..200be626291 100644 --- a/applications/main/subghz/subghz.c +++ b/applications/main/subghz/subghz.c @@ -187,6 +187,8 @@ SubGhz* subghz_alloc() { subghz->txrx->environment = subghz_environment_alloc(); subghz_environment_set_came_atomo_rainbow_table_file_name( subghz->txrx->environment, EXT_PATH("subghz/assets/came_atomo")); + subghz_environment_set_alutech_at_4n_rainbow_table_file_name( + subghz->txrx->environment, EXT_PATH("subghz/assets/alutech_at_4n")); subghz_environment_set_nice_flor_s_rainbow_table_file_name( subghz->txrx->environment, EXT_PATH("subghz/assets/nice_flor_s")); subghz_environment_set_protocol_registry( diff --git a/applications/main/subghz/subghz_cli.c b/applications/main/subghz/subghz_cli.c index 9271443a843..ac19d65b499 100644 --- a/applications/main/subghz/subghz_cli.c +++ b/applications/main/subghz/subghz_cli.c @@ -257,6 +257,8 @@ void subghz_cli_command_rx(Cli* cli, FuriString* args, void* context) { subghz_environment_load_keystore(environment, EXT_PATH("subghz/assets/keeloq_mfcodes_user")); subghz_environment_set_came_atomo_rainbow_table_file_name( environment, EXT_PATH("subghz/assets/came_atomo")); + subghz_environment_set_alutech_at_4n_rainbow_table_file_name( + environment, EXT_PATH("subghz/assets/alutech_at_4n")); subghz_environment_set_nice_flor_s_rainbow_table_file_name( environment, EXT_PATH("subghz/assets/nice_flor_s")); subghz_environment_set_protocol_registry(environment, (void*)&subghz_protocol_registry); @@ -452,6 +454,8 @@ void subghz_cli_command_decode_raw(Cli* cli, FuriString* args, void* context) { } subghz_environment_set_came_atomo_rainbow_table_file_name( environment, EXT_PATH("subghz/assets/came_atomo")); + subghz_environment_set_alutech_at_4n_rainbow_table_file_name( + environment, EXT_PATH("subghz/assets/alutech_at_4n")); subghz_environment_set_nice_flor_s_rainbow_table_file_name( environment, EXT_PATH("subghz/assets/nice_flor_s")); subghz_environment_set_protocol_registry(environment, (void*)&subghz_protocol_registry); diff --git a/assets/resources/subghz/assets/alutech_at_4n b/assets/resources/subghz/assets/alutech_at_4n new file mode 100644 index 00000000000..5d7beacec42 --- /dev/null +++ b/assets/resources/subghz/assets/alutech_at_4n @@ -0,0 +1,6 @@ +Filetype: Flipper SubGhz Keystore RAW File +Version: 0 +Encryption: 1 +IV: 88 64 A6 A6 44 47 67 8A D6 32 36 F6 B9 06 57 31 +Encrypt_data: RAW +E811BD4F0955D217AE6677906E799D45D8DAAFD1F7923E1660B5E24574631B60 \ No newline at end of file diff --git a/assets/unit_tests/subghz/alutech_at_4n_raw.sub b/assets/unit_tests/subghz/alutech_at_4n_raw.sub new file mode 100644 index 00000000000..ae5db9715be --- /dev/null +++ b/assets/unit_tests/subghz/alutech_at_4n_raw.sub @@ -0,0 +1,10 @@ +Filetype: Flipper SubGhz RAW File +Version: 1 +Frequency: 433920000 +Preset: FuriHalSubGhzPresetOok650Async +Protocol: RAW +RAW_Data: -854 811 -454 811 -444 409 -838 811 -454 823 -432 385 -842 811 -454 389 -854 821 -418 837 -444 401 -850 417 -854 395 -830 417 -846 819 -432 811 -448 789 -444 839 -454 401 -856 381 -850 825 -410 841 -418 417 -834 411 -840 827 -442 417 -844 799 -472 809 -420 411 -842 419 -848 397 -822 413 -850 799 -486 381 -848 415 -854 423 -16394 449 -358 437 -386 411 -384 449 -382 417 -384 419 -386 417 -386 419 -388 419 -388 419 -388 419 -390 437 -4036 421 -810 425 -820 393 -866 813 -422 415 -856 397 -858 811 -456 427 -820 815 -416 419 -850 401 -854 805 -420 835 -444 409 -842 809 -454 433 -820 421 -838 813 -420 417 -822 435 -820 419 -834 431 -852 411 -866 805 -420 815 -444 805 -454 403 -824 809 -448 819 -448 413 -844 811 -446 811 -456 409 -816 809 -456 389 -866 387 -842 809 -454 827 -432 413 -850 829 -428 809 -452 381 -852 799 -452 413 -852 807 -450 801 -444 409 -872 411 -840 413 -812 413 -832 807 -450 815 -442 801 -454 809 -454 429 -820 419 -838 811 -456 785 -428 409 -842 439 -824 813 -448 415 -858 819 -418 831 -426 449 -808 427 -820 393 -866 421 -808 825 -436 413 -852 403 -884 421 -16394 407 -478 257 -574 229 -576 229 -564 231 -592 267 -490 305 -520 307 -486 309 -522 307 -458 341 -486 337 -4096 343 -882 389 -880 341 -874 807 -476 351 -882 397 -860 807 -450 431 -824 811 -450 399 -824 417 -844 817 -432 807 -448 411 -872 801 -460 417 -810 425 -836 809 -420 411 -838 409 -852 417 -844 425 -852 385 -872 801 -426 841 -420 811 -422 409 -844 809 -454 823 -432 415 -842 835 -450 805 -454 403 -822 809 -450 399 -826 417 -844 821 -434 807 -448 411 -876 801 -440 807 -450 383 -850 833 -416 415 -852 807 -456 811 -444 411 -838 419 -848 401 -852 377 -850 819 -454 795 -436 809 -448 821 -448 411 -846 417 -842 817 -432 811 -412 411 -864 417 -844 791 -438 415 -876 793 -458 809 -450 383 -832 413 -840 407 -866 387 -844 821 -434 413 -874 377 -868 419 -16408 411 -392 421 -390 421 -388 421 -376 427 -394 433 -388 409 -386 411 -384 449 -382 419 -384 419 -384 419 -4018 343 -89684 97 -430 65 -166 163 -66 231 -100 161 -392 161 -64 229 -1056 97 -198 97 -198 259 -166 691 -66 395 -98 131 -100 99 -66 199 -198 1657 -406 365 -462 361 -436 357 -438 387 -444 353 -444 385 -414 387 -448 355 -448 355 -444 395 -394 399 -4050 819 -434 811 -414 819 -450 409 -852 809 -450 805 -448 805 -446 831 -428 409 -846 389 -854 395 -862 811 +RAW_Data: -452 783 -462 811 -446 411 -874 383 -852 403 -852 777 -450 411 -838 805 -452 397 -858 805 -484 387 -872 803 -442 805 -448 383 -846 797 -484 777 -474 381 -846 831 -430 807 -482 387 -852 817 -418 805 -452 823 -430 385 -878 377 -876 411 -846 391 -884 811 -444 805 -420 415 -846 399 -852 807 -452 805 -444 803 -450 803 -454 807 -452 397 -850 395 -862 385 -844 427 -840 809 -456 379 -876 407 -880 383 -846 819 -432 387 -872 375 -854 413 -846 387 -886 779 -476 803 -450 801 -444 415 -846 793 -438 415 -846 417 -822 407 -852 417 -852 817 -444 409 -16426 443 -358 431 -388 409 -386 447 -350 449 -382 419 -386 419 -386 417 -386 419 -388 453 -354 453 -356 445 -4018 813 -416 839 -420 817 -418 451 -816 835 -444 809 -450 811 -440 803 -444 407 -830 419 -844 419 -836 805 -420 835 -444 803 -446 409 -868 421 -814 431 -822 807 -452 397 -828 819 -452 415 -856 787 -454 419 -848 837 -410 839 -416 395 -866 787 -450 811 -454 407 -850 805 -454 805 -470 381 -850 833 -418 805 -438 809 -448 397 -860 421 -810 431 -856 381 -888 791 -424 841 -422 415 -820 437 -818 813 -450 813 -454 803 -446 817 -448 829 -410 451 -816 403 -818 413 -850 409 -846 811 -448 415 -834 413 -884 399 -822 815 -452 381 -852 407 -846 415 -846 385 -866 807 -456 809 -446 835 -412 407 -834 815 -450 415 -822 405 -848 419 -844 427 -852 809 -442 407 -16420 425 -422 335 -486 309 -486 309 -522 307 -492 337 -454 351 -466 329 -498 327 -468 323 -472 355 -442 355 -4120 757 -456 773 -478 781 -458 381 -874 771 -482 801 -470 777 -482 805 -450 399 -826 415 -846 387 -866 805 -420 813 -446 831 -458 417 -846 401 -852 381 -840 835 -420 415 -846 795 -440 413 -842 835 -450 407 -860 811 -418 815 -424 413 -842 807 -454 823 -428 411 -842 801 -454 807 -488 401 -822 805 -448 803 -446 803 -426 447 -844 397 -856 381 -870 411 -870 777 -452 829 -432 385 -840 419 -848 797 -438 809 -450 815 -448 833 -440 803 -452 411 -820 415 -848 409 -844 411 -846 779 -462 409 -848 409 -864 421 -844 793 -438 385 -846 419 -850 399 -838 415 -872 777 -478 803 -448 799 -442 417 -848 799 -438 415 -842 409 -826 417 -844 427 -854 807 -452 409 -16402 461 -352 421 -386 421 -386 419 -388 453 -354 453 -354 447 -378 409 -386 439 -376 421 -410 385 -414 415 -4024 831 -418 809 -438 807 -444 409 -838 809 -456 821 -432 841 -414 255 -87638 131 -66 97 -296 97 -264 131 -196 65 -132 231 -632 197 -664 131 +RAW_Data: -500 395 -132 461 -132 689 -98 2685 -100 997 -1508 99 -2186 231 -166 231 -134 133 -932 65 -268 99 -132 65 -200 97 -68 163 -234 65 -68 99 -930 331 -98 763 -100 2025 -418 353 -446 385 -414 385 -416 387 -448 355 -442 383 -412 397 -424 405 -388 409 -418 379 -418 415 -4014 443 -814 413 -822 817 -454 801 -436 409 -842 409 -866 415 -838 441 -836 811 -432 387 -842 419 -846 793 -440 807 -448 837 -446 803 -448 835 -420 807 -448 383 -868 379 -850 409 -866 387 -844 825 -468 381 -884 793 -426 415 -842 427 -818 817 -440 407 -830 419 -844 429 -852 387 -872 409 -826 811 -450 813 -418 837 -412 409 -864 417 -844 397 -852 809 -454 805 -448 409 -840 809 -420 813 -458 409 -844 407 -860 385 -878 793 -470 809 -420 817 -416 417 -850 403 -852 381 -852 827 -428 447 -844 401 -854 813 -424 421 -840 419 -812 823 -438 415 -846 409 -844 415 -846 389 -868 809 -458 803 -416 409 -866 813 -418 417 -854 397 -862 419 -842 401 -854 415 -16404 435 -376 409 -410 407 -384 439 -384 409 -410 417 -368 421 -410 407 -378 447 -376 415 -378 447 -380 407 -4022 421 -844 423 -822 821 -418 807 -454 429 -820 421 -836 439 -854 421 -810 821 -436 385 -840 441 -822 813 -448 811 -452 803 -444 835 -444 801 -446 801 -426 447 -808 423 -834 413 -852 407 -840 819 -452 389 -856 813 -444 409 -848 415 -812 809 -458 409 -848 411 -842 415 -844 421 -834 415 -834 835 -418 819 -418 807 -456 393 -856 393 -866 421 -846 799 -474 809 -420 421 -836 811 -420 813 -458 407 -850 413 -842 415 -846 819 -428 835 -416 835 -412 407 -832 421 -842 423 -822 813 -446 407 -864 419 -846 799 -440 413 -850 419 -816 797 -442 413 -850 409 -844 417 -846 423 -834 841 -428 805 -414 435 -822 813 -450 413 -822 437 -818 421 -844 429 -854 411 -16406 427 -418 309 -522 307 -488 303 -520 289 -530 295 -500 323 -470 325 -504 321 -476 321 -476 355 -444 357 -4080 355 -906 339 -882 771 -476 777 -486 381 -874 383 -884 375 -884 387 -852 819 -418 417 -846 399 -854 809 -418 815 -446 837 -420 839 -454 801 -436 807 -452 399 -826 417 -844 391 -852 423 -838 809 -452 431 -852 811 -414 409 -836 417 -844 821 -432 385 -876 385 -850 409 -848 415 -854 421 -840 817 -420 815 -424 817 -448 409 -848 413 -844 389 -854 815 -446 829 -426 413 -842 819 -434 809 -446 409 -838 419 -846 401 -852 811 -456 811 -444 803 -418 417 -848 403 -850 381 -864 805 -450 395 -866 419 -848 801 -474 381 -848 411 +RAW_Data: -842 807 -446 381 -872 377 -866 421 -846 401 -854 813 -458 779 -446 407 -832 811 -450 415 -856 399 -856 385 -876 399 -854 411 -16398 435 -392 395 -400 421 -412 385 -412 417 -384 415 -386 415 -418 385 -420 385 -420 417 -390 417 -388 419 -4020 421 -838 421 -812 819 -434 809 -448 397 -864 421 -844 401 -850 413 -858 789 -426 413 -844 419 -836 807 -424 843 -410 829 -442 835 -446 801 -454 809 -420 417 -832 411 -848 249 -88020 133 -896 231 -466 67 -1062 131 -728 163 -98 621 -98 1051 -100 680933 -452 269 -522 273 -554 273 -558 239 -558 271 -490 337 -488 321 -498 295 -500 325 -470 323 -474 353 -4082 757 -492 375 -880 357 -872 777 -486 773 -480 807 -450 805 -444 805 -476 407 -812 413 -834 411 -848 407 -828 813 -450 811 -458 803 -448 835 -446 791 -424 447 -808 427 -818 423 -840 419 -848 401 -854 811 -458 809 -446 801 -416 439 -826 415 -848 813 -430 809 -450 395 -866 419 -846 403 -850 413 -820 407 -848 415 -846 781 -460 805 -446 803 -474 803 -448 835 -420 805 -454 389 -836 409 -842 407 -866 419 -842 399 -854 809 -456 809 -446 409 -840 385 -844 819 -434 809 -450 395 -860 811 -452 393 -886 779 -446 409 -830 419 -842 423 -818 423 -838 419 -844 799 -472 809 -454 385 -844 807 -454 391 -854 395 -860 385 -844 429 -852 809 -454 385 -874 409 -16402 427 -368 455 -358 433 -380 443 -378 415 -378 447 -380 411 -384 409 -406 421 -408 387 -412 415 -386 415 -4026 831 -398 441 -810 417 -832 837 -418 833 -444 803 -446 833 -448 801 -424 449 -810 427 -820 423 -838 419 -812 825 -438 841 -416 845 -446 825 -418 809 -422 419 -822 433 -822 419 -844 425 -820 421 -840 841 -458 797 -436 809 -414 435 -822 419 -844 819 -432 809 -448 395 -864 421 -846 407 -850 411 -808 433 -824 419 -844 819 -432 809 -446 823 -416 837 -454 807 -440 809 -414 435 -828 417 -844 425 -828 415 -848 419 -818 839 -446 807 -422 411 -844 419 -846 795 -438 807 -450 395 -866 811 -454 391 -854 845 -412 407 -832 421 -842 419 -832 411 -824 435 -820 815 -450 811 -460 409 -850 799 -454 407 -824 413 -848 411 -842 415 -844 815 -432 415 -848 405 -16400 441 -432 327 -492 301 -516 307 -484 309 -520 307 -492 339 -454 337 -490 331 -464 327 -500 325 -472 325 -4110 763 -480 373 -852 385 -878 759 -482 775 -474 813 -458 781 -482 789 -454 415 -846 397 -820 411 -840 405 -852 809 -450 811 -458 809 -450 817 -448 803 -426 411 -844 391 -854 393 -866 419 -848 399 -854 811 +RAW_Data: -454 811 -444 803 -418 417 -846 403 -850 809 -452 805 -444 411 -840 419 -846 407 -850 415 -836 385 -842 419 -850 797 -438 807 -452 817 -446 801 -486 813 -444 775 -450 409 -838 419 -810 431 -854 379 -848 405 -884 809 -450 817 -430 385 -874 375 -856 811 -446 809 -422 421 -836 835 -452 419 -848 783 -460 409 -814 407 -856 415 -846 383 -870 381 -848 819 -450 811 -472 383 -850 803 -454 415 -838 399 -854 379 -850 407 -848 811 -448 415 -872 387 -16400 451 -374 445 -374 415 -378 415 -412 411 -384 405 -422 409 -410 387 -410 417 -382 417 -384 415 -420 383 -4030 827 -428 411 -842 425 -820 817 -418 833 -426 845 -452 815 -428 837 -416 409 -842 421 -810 431 -820 421 -89106 265 -662 99 -532 131 -598 97 -668 65 -300 761 -198 231 -132 265 -100 233 -100 197 diff --git a/assets/unit_tests/subghz/test_random_raw.sub b/assets/unit_tests/subghz/test_random_raw.sub index aee15a16d9c..44e1613c71d 100644 --- a/assets/unit_tests/subghz/test_random_raw.sub +++ b/assets/unit_tests/subghz/test_random_raw.sub @@ -178,3 +178,8 @@ RAW_Data: -2072 1971 -2056 1971 -2074 1973 -34714 455 -3634 373 -3634 1901 -2110 RAW_Data: 4046 -17306 65 -298 97 -100 133 -268 265 -330 133 -132 1723 -16806 165 -132 99 -920 65 -622 789 -130 99 -66 361 -98 295 -166 73573 -17510 97 -492 129 -728 529 -100 1063 -164 295 -66 1119 -14962 627 -166 363 -264 427 -132 593 -100 633 -132 39555 -16938 99 -2024 65 -100 97 -164 99 -66 399 -100 123891 -16736 163 -200 97 -200 165 -264 65 -828 427 -132 871 -5132 591 -490 595 -486 605 -454 275 -822 241 -824 273 -784 321 -782 649 -444 653 -408 657 -428 321 -744 693 -388 699 -388 707 -392 313 -752 345 -750 317 -744 351 -730 355 -738 323 -774 327 -748 329 -750 695 -386 701 -354 381 -722 351 -720 385 -718 351 -718 345 -738 705 -382 329 -736 713 -360 387 -718 369 -718 367 -706 735 -352 375 -726 351 -722 351 -720 719 -7808 4845 -1474 743 -332 741 -370 705 -370 349 -718 383 -716 345 -712 381 -704 747 -326 747 -350 737 -352 351 -718 719 -360 741 -366 687 -362 375 -704 381 -724 351 -740 353 -712 357 -718 359 -744 363 -688 365 -722 727 -354 727 -354 379 -724 351 -722 353 -720 387 -718 353 -718 703 -374 351 -716 735 -354 365 -708 353 -734 351 -746 717 -356 359 -720 371 -704 371 -720 731 -7786 4847 -1482 711 -386 711 -358 743 -330 373 -708 359 -748 349 -740 351 -716 719 -356 727 -354 739 -354 351 -718 719 -362 743 -364 721 -330 373 -706 381 -722 351 -740 353 -712 359 -720 361 -722 361 -720 361 -720 725 -354 731 -354 381 -720 353 -722 385 -720 351 -720 349 -716 735 -354 361 -748 711 -364 347 -740 365 -722 365 -720 695 -384 371 -704 381 -702 377 -710 709 -7804 4853 -1468 743 -336 735 -358 719 -352 379 -724 353 -722 353 -720 387 -686 721 -360 721 -362 743 -332 387 -718 721 -366 701 -382 701 -350 377 -720 351 -740 353 -714 357 -710 397 -710 365 -702 385 -688 377 -724 731 -352 703 -354 379 -736 343 -740 357 -720 349 -706 385 -718 719 -354 365 -724 735 -352 377 -724 355 -720 353 -720 721 -358 387 -686 387 -718 353 -718 733 -7796 4821 -1492 739 -350 719 -334 737 -350 365 -722 373 -722 367 -708 371 -702 747 -352 711 -358 743 -364 343 -706 749 -352 717 -350 717 -384 327 -736 351 -746 355 -716 357 -720 359 -710 365 -742 365 -708 367 -704 711 -354 743 -356 387 -684 373 -706 381 -722 351 -740 353 -714 721 -356 361 -720 733 -352 375 -694 385 -724 353 -722 719 -356 385 -686 385 -718 351 -716 731 -7792 4843 -1480 717 -354 719 -386 717 -354 359 -720 351 -708 387 -712 355 -718 721 -356 727 -354 739 -356 351 -718 741 -364 RAW_Data: 705 -370 703 -372 351 -718 383 -720 347 -720 347 -714 381 -704 353 -744 357 -718 355 -720 723 -356 725 -354 379 -722 351 -722 353 -722 385 -718 351 -718 721 -372 351 -716 719 -372 351 -718 383 -716 345 -714 743 -346 361 -740 353 -712 357 -710 725 -7818 4837 -1498 713 -356 709 -360 741 -332 375 -706 359 -750 351 -706 353 -748 719 -356 723 -352 739 -354 351 -718 709 -364 719 -362 721 -364 385 -718 353 -718 383 -682 377 -712 349 -734 353 -742 355 -712 359 -722 723 -354 729 -352 381 -722 353 -722 351 -720 387 -718 353 -716 701 -388 345 -722 737 -354 357 -722 351 -708 387 -712 717 -350 731 -354 741 -356 743 -330 375 -8180 4829 -1468 739 -364 707 -354 729 -352 379 -722 353 -720 387 -686 387 -718 707 -368 721 -366 707 -368 351 -718 735 -354 719 -354 719 -388 329 -746 349 -738 351 -712 359 -718 361 -742 365 -708 371 -706 373 -720 733 -320 733 -354 383 -720 353 -720 387 -718 351 -716 385 -714 703 -388 327 -746 705 -348 387 -702 385 -690 385 -724 713 -358 709 -362 743 -364 709 -370 351 -8162 4837 -1482 715 -388 715 -352 715 -384 325 -730 353 -744 353 -712 359 -720 723 -354 733 -354 745 -356 351 -720 719 -362 741 -330 737 -382 349 -722 345 -724 361 -744 349 -704 383 -716 357 -718 357 -720 361 -720 723 -354 733 -354 383 -720 387 -686 387 -718 353 -718 349 -716 731 -384 347 -724 721 -352 365 -706 353 -732 353 -746 717 -356 723 -352 739 -354 711 -360 385 -8146 4841 -1470 737 -344 739 -326 751 -352 377 -690 387 -724 353 -724 353 -722 711 -360 743 -364 721 -330 387 -716 703 -386 721 -356 721 -354 363 -706 349 -734 351 -746 355 -718 355 -712 363 -744 365 -708 369 -722 695 -352 731 -354 381 -722 353 -722 351 -734 351 -716 383 -720 723 -354 333 -736 739 -348 361 -708 351 -748 355 -712 725 -354 727 -352 741 -352 713 -358 385 -8134 4855 -1474 719 -358 709 -362 721 -364 387 -716 351 -718 385 -712 347 -712 739 -334 739 -354 729 -352 379 -722 717 -354 711 -360 743 -332 387 -718 351 -716 377 -708 349 -730 353 -742 355 -710 359 -720 359 -720 723 -354 729 -352 381 -720 353 -722 351 -722 387 -684 387 -716 703 -384 349 -722 737 -354 329 -750 349 -738 353 -712 719 -356 725 -354 741 -354 717 -358 385 -8126 4861 -1470 735 -344 731 -346 729 -348 383 -718 347 -712 353 -734 353 -746 715 -356 725 -350 741 -352 351 -718 741 -366 721 -366 705 -370 353 -718 385 -682 377 -710 349 -734 353 -744 355 -710 359 -710 397 -688 RAW_Data: 727 -354 729 -352 379 -724 353 -722 353 -718 387 -716 353 -716 735 -348 383 -682 727 -386 347 -722 347 -712 381 -706 747 -326 747 -350 737 -352 711 -358 +RAW_Data: -854 811 -454 811 -444 409 -838 811 -454 823 -432 385 -842 811 -454 389 -854 821 -418 837 -444 401 -850 417 -854 395 -830 417 -846 819 -432 811 -448 789 -444 839 -454 401 -856 381 -850 825 -410 841 -418 417 -834 411 -840 827 -442 417 -844 799 -472 809 -420 411 -842 419 -848 397 -822 413 -850 799 -486 381 -848 415 -854 423 -16394 449 -358 437 -386 411 -384 449 -382 417 -384 419 -386 417 -386 419 -388 419 -388 419 -388 419 -390 437 -4036 421 -810 425 -820 393 -866 813 -422 415 -856 397 -858 811 -456 427 -820 815 -416 419 -850 401 -854 805 -420 835 -444 409 -842 809 -454 433 -820 421 -838 813 -420 417 -822 435 -820 419 -834 431 -852 411 -866 805 -420 815 -444 805 -454 403 -824 809 -448 819 -448 413 -844 811 -446 811 -456 409 -816 809 -456 389 -866 387 -842 809 -454 827 -432 413 -850 829 -428 809 -452 381 -852 799 -452 413 -852 807 -450 801 -444 409 -872 411 -840 413 -812 413 -832 807 -450 815 -442 801 -454 809 -454 429 -820 419 -838 811 -456 785 -428 409 -842 439 -824 813 -448 415 -858 819 -418 831 -426 449 -808 427 -820 393 -866 421 -808 825 -436 413 -852 403 -884 421 -16394 407 -478 257 -574 229 -576 229 -564 231 -592 267 -490 305 -520 307 -486 309 -522 307 -458 341 -486 337 -4096 343 -882 389 -880 341 -874 807 -476 351 -882 397 -860 807 -450 431 -824 811 -450 399 -824 417 -844 817 -432 807 -448 411 -872 801 -460 417 -810 425 -836 809 -420 411 -838 409 -852 417 -844 425 -852 385 -872 801 -426 841 -420 811 -422 409 -844 809 -454 823 -432 415 -842 835 -450 805 -454 403 -822 809 -450 399 -826 417 -844 821 -434 807 -448 411 -876 801 -440 807 -450 383 -850 833 -416 415 -852 807 -456 811 -444 411 -838 419 -848 401 -852 377 -850 819 -454 795 -436 809 -448 821 -448 411 -846 417 -842 817 -432 811 -412 411 -864 417 -844 791 -438 415 -876 793 -458 809 -450 383 -832 413 -840 407 -866 387 -844 821 -434 413 -874 377 -868 419 -16408 411 -392 421 -390 421 -388 421 -376 427 -394 433 -388 409 -386 411 -384 449 -382 419 -384 419 -384 419 -4018 343 -89684 97 -430 65 -166 163 -66 231 -100 161 -392 161 -64 229 -1056 97 -198 97 -198 259 -166 691 -66 395 -98 131 -100 99 -66 199 -198 1657 -406 365 -462 361 -436 357 -438 387 -444 353 -444 385 -414 387 -448 355 -448 355 -444 395 -394 399 -4050 819 -434 811 -414 819 -450 409 -852 809 -450 805 -448 805 -446 831 -428 409 -846 389 -854 395 -862 811 +RAW_Data: -452 783 -462 811 -446 411 -874 383 -852 403 -852 777 -450 411 -838 805 -452 397 -858 805 -484 387 -872 803 -442 805 -448 383 -846 797 -484 777 -474 381 -846 831 -430 807 -482 387 -852 817 -418 805 -452 823 -430 385 -878 377 -876 411 -846 391 -884 811 -444 805 -420 415 -846 399 -852 807 -452 805 -444 803 -450 803 -454 807 -452 397 -850 395 -862 385 -844 427 -840 809 -456 379 -876 407 -880 383 -846 819 -432 387 -872 375 -854 413 -846 387 -886 779 -476 803 -450 801 -444 415 -846 793 -438 415 -846 417 -822 407 -852 417 -852 817 -444 409 -16426 443 -358 431 -388 409 -386 447 -350 449 -382 419 -386 419 -386 417 -386 419 -388 453 -354 453 -356 445 -4018 813 -416 839 -420 817 -418 451 -816 835 -444 809 -450 811 -440 803 -444 407 -830 419 -844 419 -836 805 -420 835 -444 803 -446 409 -868 421 -814 431 -822 807 -452 397 -828 819 -452 415 -856 787 -454 419 -848 837 -410 839 -416 395 -866 787 -450 811 -454 407 -850 805 -454 805 -470 381 -850 833 -418 805 -438 809 -448 397 -860 421 -810 431 -856 381 -888 791 -424 841 -422 415 -820 437 -818 813 -450 813 -454 803 -446 817 -448 829 -410 451 -816 403 -818 413 -850 409 -846 811 -448 415 -834 413 -884 399 -822 815 -452 381 -852 407 -846 415 -846 385 -866 807 -456 809 -446 835 -412 407 -834 815 -450 415 -822 405 -848 419 -844 427 -852 809 -442 407 -16420 425 -422 335 -486 309 -486 309 -522 307 -492 337 -454 351 -466 329 -498 327 -468 323 -472 355 -442 355 -4120 757 -456 773 -478 781 -458 381 -874 771 -482 801 -470 777 -482 805 -450 399 -826 415 -846 387 -866 805 -420 813 -446 831 -458 417 -846 401 -852 381 -840 835 -420 415 -846 795 -440 413 -842 835 -450 407 -860 811 -418 815 -424 413 -842 807 -454 823 -428 411 -842 801 -454 807 -488 401 -822 805 -448 803 -446 803 -426 447 -844 397 -856 381 -870 411 -870 777 -452 829 -432 385 -840 419 -848 797 -438 809 -450 815 -448 833 -440 803 -452 411 -820 415 -848 409 -844 411 -846 779 -462 409 -848 409 -864 421 -844 793 -438 385 -846 419 -850 399 -838 415 -872 777 -478 803 -448 799 -442 417 -848 799 -438 415 -842 409 -826 417 -844 427 -854 807 -452 409 -16402 461 -352 421 -386 421 -386 419 -388 453 -354 453 -354 447 -378 409 -386 439 -376 421 -410 385 -414 415 -4024 831 -418 809 -438 807 -444 409 -838 809 -456 821 -432 841 -414 255 -87638 131 -66 97 -296 97 -264 131 -196 65 -132 231 -632 197 -664 131 +RAW_Data: -500 395 -132 461 -132 689 -98 2685 -100 997 -1508 99 -2186 231 -166 231 -134 133 -932 65 -268 99 -132 65 -200 97 -68 163 -234 65 -68 99 -930 331 -98 763 -100 2025 -418 353 -446 385 -414 385 -416 387 -448 355 -442 383 -412 397 -424 405 -388 409 -418 379 -418 415 -4014 443 -814 413 -822 817 -454 801 -436 409 -842 409 -866 415 -838 441 -836 811 -432 387 -842 419 -846 793 -440 807 -448 837 -446 803 -448 835 -420 807 -448 383 -868 379 -850 409 -866 387 -844 825 -468 381 -884 793 -426 415 -842 427 -818 817 -440 407 -830 419 -844 429 -852 387 -872 409 -826 811 -450 813 -418 837 -412 409 -864 417 -844 397 -852 809 -454 805 -448 409 -840 809 -420 813 -458 409 -844 407 -860 385 -878 793 -470 809 -420 817 -416 417 -850 403 -852 381 -852 827 -428 447 -844 401 -854 813 -424 421 -840 419 -812 823 -438 415 -846 409 -844 415 -846 389 -868 809 -458 803 -416 409 -866 813 -418 417 -854 397 -862 419 -842 401 -854 415 -16404 435 -376 409 -410 407 -384 439 -384 409 -410 417 -368 421 -410 407 -378 447 -376 415 -378 447 -380 407 -4022 421 -844 423 -822 821 -418 807 -454 429 -820 421 -836 439 -854 421 -810 821 -436 385 -840 441 -822 813 -448 811 -452 803 -444 835 -444 801 -446 801 -426 447 -808 423 -834 413 -852 407 -840 819 -452 389 -856 813 -444 409 -848 415 -812 809 -458 409 -848 411 -842 415 -844 421 -834 415 -834 835 -418 819 -418 807 -456 393 -856 393 -866 421 -846 799 -474 809 -420 421 -836 811 -420 813 -458 407 -850 413 -842 415 -846 819 -428 835 -416 835 -412 407 -832 421 -842 423 -822 813 -446 407 -864 419 -846 799 -440 413 -850 419 -816 797 -442 413 -850 409 -844 417 -846 423 -834 841 -428 805 -414 435 -822 813 -450 413 -822 437 -818 421 -844 429 -854 411 -16406 427 -418 309 -522 307 -488 303 -520 289 -530 295 -500 323 -470 325 -504 321 -476 321 -476 355 -444 357 -4080 355 -906 339 -882 771 -476 777 -486 381 -874 383 -884 375 -884 387 -852 819 -418 417 -846 399 -854 809 -418 815 -446 837 -420 839 -454 801 -436 807 -452 399 -826 417 -844 391 -852 423 -838 809 -452 431 -852 811 -414 409 -836 417 -844 821 -432 385 -876 385 -850 409 -848 415 -854 421 -840 817 -420 815 -424 817 -448 409 -848 413 -844 389 -854 815 -446 829 -426 413 -842 819 -434 809 -446 409 -838 419 -846 401 -852 811 -456 811 -444 803 -418 417 -848 403 -850 381 -864 805 -450 395 -866 419 -848 801 -474 381 -848 411 +RAW_Data: -842 807 -446 381 -872 377 -866 421 -846 401 -854 813 -458 779 -446 407 -832 811 -450 415 -856 399 -856 385 -876 399 -854 411 -16398 435 -392 395 -400 421 -412 385 -412 417 -384 415 -386 415 -418 385 -420 385 -420 417 -390 417 -388 419 -4020 421 -838 421 -812 819 -434 809 -448 397 -864 421 -844 401 -850 413 -858 789 -426 413 -844 419 -836 807 -424 843 -410 829 -442 835 -446 801 -454 809 -420 417 -832 411 -848 249 -88020 133 -896 231 -466 67 -1062 131 -728 163 -98 621 -98 1051 -100 680933 -452 269 -522 273 -554 273 -558 239 -558 271 -490 337 -488 321 -498 295 -500 325 -470 323 -474 353 -4082 757 -492 375 -880 357 -872 777 -486 773 -480 807 -450 805 -444 805 -476 407 -812 413 -834 411 -848 407 -828 813 -450 811 -458 803 -448 835 -446 791 -424 447 -808 427 -818 423 -840 419 -848 401 -854 811 -458 809 -446 801 -416 439 -826 415 -848 813 -430 809 -450 395 -866 419 -846 403 -850 413 -820 407 -848 415 -846 781 -460 805 -446 803 -474 803 -448 835 -420 805 -454 389 -836 409 -842 407 -866 419 -842 399 -854 809 -456 809 -446 409 -840 385 -844 819 -434 809 -450 395 -860 811 -452 393 -886 779 -446 409 -830 419 -842 423 -818 423 -838 419 -844 799 -472 809 -454 385 -844 807 -454 391 -854 395 -860 385 -844 429 -852 809 -454 385 -874 409 -16402 427 -368 455 -358 433 -380 443 -378 415 -378 447 -380 411 -384 409 -406 421 -408 387 -412 415 -386 415 -4026 831 -398 441 -810 417 -832 837 -418 833 -444 803 -446 833 -448 801 -424 449 -810 427 -820 423 -838 419 -812 825 -438 841 -416 845 -446 825 -418 809 -422 419 -822 433 -822 419 -844 425 -820 421 -840 841 -458 797 -436 809 -414 435 -822 419 -844 819 -432 809 -448 395 -864 421 -846 407 -850 411 -808 433 -824 419 -844 819 -432 809 -446 823 -416 837 -454 807 -440 809 -414 435 -828 417 -844 425 -828 415 -848 419 -818 839 -446 807 -422 411 -844 419 -846 795 -438 807 -450 395 -866 811 -454 391 -854 845 -412 407 -832 421 -842 419 -832 411 -824 435 -820 815 -450 811 -460 409 -850 799 -454 407 -824 413 -848 411 -842 415 -844 815 -432 415 -848 405 -16400 441 -432 327 -492 301 -516 307 -484 309 -520 307 -492 339 -454 337 -490 331 -464 327 -500 325 -472 325 -4110 763 -480 373 -852 385 -878 759 -482 775 -474 813 -458 781 -482 789 -454 415 -846 397 -820 411 -840 405 -852 809 -450 811 -458 809 -450 817 -448 803 -426 411 -844 391 -854 393 -866 419 -848 399 -854 811 +RAW_Data: -454 811 -444 803 -418 417 -846 403 -850 809 -452 805 -444 411 -840 419 -846 407 -850 415 -836 385 -842 419 -850 797 -438 807 -452 817 -446 801 -486 813 -444 775 -450 409 -838 419 -810 431 -854 379 -848 405 -884 809 -450 817 -430 385 -874 375 -856 811 -446 809 -422 421 -836 835 -452 419 -848 783 -460 409 -814 407 -856 415 -846 383 -870 381 -848 819 -450 811 -472 383 -850 803 -454 415 -838 399 -854 379 -850 407 -848 811 -448 415 -872 387 -16400 451 -374 445 -374 415 -378 415 -412 411 -384 405 -422 409 -410 387 -410 417 -382 417 -384 415 -420 383 -4030 827 -428 411 -842 425 -820 817 -418 833 -426 845 -452 815 -428 837 -416 409 -842 421 -810 431 -820 421 -89106 265 -662 99 -532 131 -598 97 -668 65 -300 761 -198 231 -132 265 -100 233 -100 197 diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 65ab375ef49..846041c3e9b 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -2569,12 +2569,14 @@ Function,+,subghz_block_generic_get_preset_name,void,"const char*, FuriString*" Function,+,subghz_block_generic_serialize,_Bool,"SubGhzBlockGeneric*, FlipperFormat*, SubGhzRadioPreset*" Function,+,subghz_environment_alloc,SubGhzEnvironment*, Function,+,subghz_environment_free,void,SubGhzEnvironment* +Function,+,subghz_environment_get_alutech_at_4n_rainbow_table_file_name,const char*,SubGhzEnvironment* Function,+,subghz_environment_get_came_atomo_rainbow_table_file_name,const char*,SubGhzEnvironment* Function,+,subghz_environment_get_keystore,SubGhzKeystore*,SubGhzEnvironment* Function,+,subghz_environment_get_nice_flor_s_rainbow_table_file_name,const char*,SubGhzEnvironment* Function,+,subghz_environment_get_protocol_name_registry,const char*,"SubGhzEnvironment*, size_t" Function,+,subghz_environment_get_protocol_registry,void*,SubGhzEnvironment* Function,+,subghz_environment_load_keystore,_Bool,"SubGhzEnvironment*, const char*" +Function,+,subghz_environment_set_alutech_at_4n_rainbow_table_file_name,void,"SubGhzEnvironment*, const char*" Function,+,subghz_environment_set_came_atomo_rainbow_table_file_name,void,"SubGhzEnvironment*, const char*" Function,+,subghz_environment_set_nice_flor_s_rainbow_table_file_name,void,"SubGhzEnvironment*, const char*" Function,+,subghz_environment_set_protocol_registry,void,"SubGhzEnvironment*, void*" diff --git a/lib/subghz/environment.c b/lib/subghz/environment.c index 0a4b7b60682..b39b259d416 100644 --- a/lib/subghz/environment.c +++ b/lib/subghz/environment.c @@ -6,6 +6,7 @@ struct SubGhzEnvironment { const SubGhzProtocolRegistry* protocol_registry; const char* came_atomo_rainbow_table_file_name; const char* nice_flor_s_rainbow_table_file_name; + const char* alutech_at_4n_rainbow_table_file_name; }; SubGhzEnvironment* subghz_environment_alloc() { @@ -57,6 +58,21 @@ const char* return instance->came_atomo_rainbow_table_file_name; } +void subghz_environment_set_alutech_at_4n_rainbow_table_file_name( + SubGhzEnvironment* instance, + const char* filename) { + furi_assert(instance); + + instance->alutech_at_4n_rainbow_table_file_name = filename; +} + +const char* + subghz_environment_get_alutech_at_4n_rainbow_table_file_name(SubGhzEnvironment* instance) { + furi_assert(instance); + + return instance->alutech_at_4n_rainbow_table_file_name; +} + void subghz_environment_set_nice_flor_s_rainbow_table_file_name( SubGhzEnvironment* instance, const char* filename) { diff --git a/lib/subghz/environment.h b/lib/subghz/environment.h index e994f7c975b..7bd38ba2fe5 100644 --- a/lib/subghz/environment.h +++ b/lib/subghz/environment.h @@ -52,6 +52,23 @@ void subghz_environment_set_came_atomo_rainbow_table_file_name( */ const char* subghz_environment_get_came_atomo_rainbow_table_file_name(SubGhzEnvironment* instance); +/** + * Set filename to work with Alutech at-4n. + * @param instance Pointer to a SubGhzEnvironment instance + * @param filename Full path to the file + */ +void subghz_environment_set_alutech_at_4n_rainbow_table_file_name( + SubGhzEnvironment* instance, + const char* filename); + +/** + * Get filename to work with Alutech at-4n. + * @param instance Pointer to a SubGhzEnvironment instance + * @return Full path to the file + */ +const char* + subghz_environment_get_alutech_at_4n_rainbow_table_file_name(SubGhzEnvironment* instance); + /** * Set filename to work with Nice Flor-S. * @param instance Pointer to a SubGhzEnvironment instance diff --git a/lib/subghz/protocols/alutech_at_4n.c b/lib/subghz/protocols/alutech_at_4n.c new file mode 100644 index 00000000000..6bcf9f25de3 --- /dev/null +++ b/lib/subghz/protocols/alutech_at_4n.c @@ -0,0 +1,455 @@ +#include "alutech_at_4n.h" +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#define TAG "SubGhzProtocoAlutech_at_4n" + +#define SUBGHZ_NO_ALUTECH_AT_4N_RAINBOW_TABLE 0xFFFFFFFF + +static const SubGhzBlockConst subghz_protocol_alutech_at_4n_const = { + .te_short = 400, + .te_long = 800, + .te_delta = 140, + .min_count_bit_for_found = 72, +}; + +struct SubGhzProtocolDecoderAlutech_at_4n { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; + + uint64_t data; + uint32_t crc; + uint16_t header_count; + + const char* alutech_at_4n_rainbow_table_file_name; +}; + +struct SubGhzProtocolEncoderAlutech_at_4n { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + Alutech_at_4nDecoderStepReset = 0, + Alutech_at_4nDecoderStepCheckPreambula, + Alutech_at_4nDecoderStepSaveDuration, + Alutech_at_4nDecoderStepCheckDuration, +} Alutech_at_4nDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_alutech_at_4n_decoder = { + .alloc = subghz_protocol_decoder_alutech_at_4n_alloc, + .free = subghz_protocol_decoder_alutech_at_4n_free, + + .feed = subghz_protocol_decoder_alutech_at_4n_feed, + .reset = subghz_protocol_decoder_alutech_at_4n_reset, + + .get_hash_data = subghz_protocol_decoder_alutech_at_4n_get_hash_data, + .serialize = subghz_protocol_decoder_alutech_at_4n_serialize, + .deserialize = subghz_protocol_decoder_alutech_at_4n_deserialize, + .get_string = subghz_protocol_decoder_alutech_at_4n_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_alutech_at_4n_encoder = { + .alloc = NULL, + .free = NULL, + + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol subghz_protocol_alutech_at_4n = { + .name = SUBGHZ_PROTOCOL_ALUTECH_AT_4N_NAME, + .type = SubGhzProtocolTypeDynamic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable, + + .decoder = &subghz_protocol_alutech_at_4n_decoder, + .encoder = &subghz_protocol_alutech_at_4n_encoder, +}; + +/** + * Read bytes from rainbow table + * @param file_name Full path to rainbow table the file + * @param number_alutech_at_4n_magic_data number in the array + * @return alutech_at_4n_magic_data + */ +static uint32_t subghz_protocol_alutech_at_4n_get_magic_data_in_file( + const char* file_name, + uint8_t number_alutech_at_4n_magic_data) { + if(!strcmp(file_name, "")) return SUBGHZ_NO_ALUTECH_AT_4N_RAINBOW_TABLE; + + uint8_t buffer[sizeof(uint32_t)] = {0}; + uint32_t address = number_alutech_at_4n_magic_data * sizeof(uint32_t); + uint32_t alutech_at_4n_magic_data = 0; + + if(subghz_keystore_raw_get_data(file_name, address, buffer, sizeof(uint32_t))) { + for(size_t i = 0; i < sizeof(uint32_t); i++) { + alutech_at_4n_magic_data = (alutech_at_4n_magic_data << 8) | buffer[i]; + } + } else { + alutech_at_4n_magic_data = SUBGHZ_NO_ALUTECH_AT_4N_RAINBOW_TABLE; + } + return alutech_at_4n_magic_data; +} + +static uint8_t subghz_protocol_alutech_at_4n_crc(uint64_t data) { + uint8_t* p = (uint8_t*)&data; + uint8_t crc = 0xff; + for(uint8_t y = 0; y < 8; y++) { + crc = crc ^ p[y]; + for(uint8_t i = 0; i < 8; i++) { + if((crc & 0x80) != 0) { + crc <<= 1; + crc ^= 0x31; + } else { + crc <<= 1; + } + } + } + return crc; +} + +static uint8_t subghz_protocol_alutech_at_4n_decrypt_data_crc(uint8_t data) { + uint8_t crc = data; + for(uint8_t i = 0; i < 8; i++) { + if((crc & 0x80) != 0) { + crc <<= 1; + crc ^= 0x31; + } else { + crc <<= 1; + } + } + return ~crc; +} + +static uint64_t subghz_protocol_alutech_at_4n_decrypt(uint64_t data, const char* file_name) { + uint8_t* p = (uint8_t*)&data; + uint32_t data1 = p[0] << 24 | p[1] << 16 | p[2] << 8 | p[3]; + uint32_t data2 = p[4] << 24 | p[5] << 16 | p[6] << 8 | p[7]; + uint32_t data3 = 0; + uint32_t magic_data[] = { + subghz_protocol_alutech_at_4n_get_magic_data_in_file(file_name, 0), + subghz_protocol_alutech_at_4n_get_magic_data_in_file(file_name, 1), + subghz_protocol_alutech_at_4n_get_magic_data_in_file(file_name, 2), + subghz_protocol_alutech_at_4n_get_magic_data_in_file(file_name, 3), + subghz_protocol_alutech_at_4n_get_magic_data_in_file(file_name, 4), + subghz_protocol_alutech_at_4n_get_magic_data_in_file(file_name, 5)}; + + uint32_t i = magic_data[0]; + do { + data2 = data2 - + ((magic_data[1] + (data1 << 4)) ^ ((magic_data[2] + (data1 >> 5)) ^ (data1 + i))); + data3 = data2 + i; + i += magic_data[3]; + data1 = + data1 - ((magic_data[4] + (data2 << 4)) ^ ((magic_data[5] + (data2 >> 5)) ^ data3)); + } while(i != 0); + + p[0] = (uint8_t)(data1 >> 24); + p[1] = (uint8_t)(data1 >> 16); + p[3] = (uint8_t)data1; + p[4] = (uint8_t)(data2 >> 24); + p[5] = (uint8_t)(data2 >> 16); + p[2] = (uint8_t)(data1 >> 8); + p[6] = (uint8_t)(data2 >> 8); + p[7] = (uint8_t)data2; + + return data; +} + +// static uint64_t subghz_protocol_alutech_at_4n_encrypt(uint64_t data, const char* file_name) { +// uint8_t* p = (uint8_t*)&data; +// uint32_t data1 = 0; +// uint32_t data2 = p[0] << 24 | p[1] << 16 | p[2] << 8 | p[3]; +// uint32_t data3 = p[4] << 24 | p[5] << 16 | p[6] << 8 | p[7]; +// uint32_t magic_data[] = { +// subghz_protocol_alutech_at_4n_get_magic_data_in_file(file_name, 6), +// subghz_protocol_alutech_at_4n_get_magic_data_in_file(file_name, 4), +// subghz_protocol_alutech_at_4n_get_magic_data_in_file(file_name, 5), +// subghz_protocol_alutech_at_4n_get_magic_data_in_file(file_name, 1), +// subghz_protocol_alutech_at_4n_get_magic_data_in_file(file_name, 2), +// subghz_protocol_alutech_at_4n_get_magic_data_in_file(file_name, 0)}; + +// do { +// data1 = data1 + magic_data[0]; +// data2 = data2 + ((magic_data[1] + (data3 << 4)) ^ +// ((magic_data[2] + (data3 >> 5)) ^ (data1 + data3))); +// data3 = data3 + ((magic_data[3] + (data2 << 4)) ^ +// ((magic_data[4] + (data2 >> 5)) ^ (data1 + data2))); +// } while(data1 != magic_data[5]); +// p[0] = (uint8_t)(data2 >> 24); +// p[1] = (uint8_t)(data2 >> 16); +// p[3] = (uint8_t)data2; +// p[4] = (uint8_t)(data3 >> 24); +// p[5] = (uint8_t)(data3 >> 16); +// p[2] = (uint8_t)(data2 >> 8); +// p[6] = (uint8_t)(data3 >> 8); +// p[7] = (uint8_t)data3; + +// return data; +// } + +void* subghz_protocol_decoder_alutech_at_4n_alloc(SubGhzEnvironment* environment) { + SubGhzProtocolDecoderAlutech_at_4n* instance = + malloc(sizeof(SubGhzProtocolDecoderAlutech_at_4n)); + instance->base.protocol = &subghz_protocol_alutech_at_4n; + instance->generic.protocol_name = instance->base.protocol->name; + instance->alutech_at_4n_rainbow_table_file_name = + subghz_environment_get_alutech_at_4n_rainbow_table_file_name(environment); + if(instance->alutech_at_4n_rainbow_table_file_name) { + FURI_LOG_I( + TAG, "Loading rainbow table from %s", instance->alutech_at_4n_rainbow_table_file_name); + } + return instance; +} + +void subghz_protocol_decoder_alutech_at_4n_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderAlutech_at_4n* instance = context; + instance->alutech_at_4n_rainbow_table_file_name = NULL; + free(instance); +} + +void subghz_protocol_decoder_alutech_at_4n_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderAlutech_at_4n* instance = context; + instance->decoder.parser_step = Alutech_at_4nDecoderStepReset; +} + +void subghz_protocol_decoder_alutech_at_4n_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderAlutech_at_4n* instance = context; + + switch(instance->decoder.parser_step) { + case Alutech_at_4nDecoderStepReset: + if((level) && DURATION_DIFF(duration, subghz_protocol_alutech_at_4n_const.te_short) < + subghz_protocol_alutech_at_4n_const.te_delta) { + instance->decoder.parser_step = Alutech_at_4nDecoderStepCheckPreambula; + instance->header_count++; + } + break; + case Alutech_at_4nDecoderStepCheckPreambula: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_alutech_at_4n_const.te_short) < + subghz_protocol_alutech_at_4n_const.te_delta)) { + instance->decoder.parser_step = Alutech_at_4nDecoderStepReset; + break; + } + if((instance->header_count > 2) && + (DURATION_DIFF(duration, subghz_protocol_alutech_at_4n_const.te_short * 10) < + subghz_protocol_alutech_at_4n_const.te_delta * 10)) { + // Found header + instance->decoder.parser_step = Alutech_at_4nDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->data = 0; + instance->decoder.decode_count_bit = 0; + } else { + instance->decoder.parser_step = Alutech_at_4nDecoderStepReset; + instance->header_count = 0; + } + break; + case Alutech_at_4nDecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = Alutech_at_4nDecoderStepCheckDuration; + } + break; + case Alutech_at_4nDecoderStepCheckDuration: + if(!level) { + if(duration >= ((uint32_t)subghz_protocol_alutech_at_4n_const.te_short * 2 + + subghz_protocol_alutech_at_4n_const.te_delta)) { + //add last bit + if(DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_alutech_at_4n_const.te_short) < + subghz_protocol_alutech_at_4n_const.te_delta) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + } else if( + DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_alutech_at_4n_const.te_long) < + subghz_protocol_alutech_at_4n_const.te_delta * 2) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + } + + // Found end TX + instance->decoder.parser_step = Alutech_at_4nDecoderStepReset; + if(instance->decoder.decode_count_bit == + subghz_protocol_alutech_at_4n_const.min_count_bit_for_found) { + if(instance->generic.data != instance->data) { + instance->generic.data = instance->data; + + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + instance->crc = instance->decoder.decode_data; + + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + instance->decoder.decode_data = 0; + instance->data = 0; + instance->decoder.decode_count_bit = 0; + instance->header_count = 0; + } + break; + } else if( + (DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_alutech_at_4n_const.te_short) < + subghz_protocol_alutech_at_4n_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_alutech_at_4n_const.te_long) < + subghz_protocol_alutech_at_4n_const.te_delta * 2)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + if(instance->decoder.decode_count_bit == 64) { + instance->data = instance->decoder.decode_data; + instance->decoder.decode_data = 0; + } + instance->decoder.parser_step = Alutech_at_4nDecoderStepSaveDuration; + } else if( + (DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_alutech_at_4n_const.te_long) < + subghz_protocol_alutech_at_4n_const.te_delta * 2) && + (DURATION_DIFF(duration, subghz_protocol_alutech_at_4n_const.te_short) < + subghz_protocol_alutech_at_4n_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + if(instance->decoder.decode_count_bit == 64) { + instance->data = instance->decoder.decode_data; + instance->decoder.decode_data = 0; + } + instance->decoder.parser_step = Alutech_at_4nDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = Alutech_at_4nDecoderStepReset; + instance->header_count = 0; + } + } else { + instance->decoder.parser_step = Alutech_at_4nDecoderStepReset; + instance->header_count = 0; + } + break; + } +} + +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + * @param file_name Full path to rainbow table the file + */ +static void subghz_protocol_alutech_at_4n_remote_controller( + SubGhzBlockGeneric* instance, + uint8_t crc, + const char* file_name) { + /** + * Message format 72bit LSB first + * data crc + * XXXXXXXXXXXXXXXX CC + * + * For analysis, you need to turn the package MSB + * in decoded messages format + * + * crc1 serial cnt key + * cc SSSSSSSS XXxx BB + * + * crc1 is calculated from the lower part of cnt + * key 1=0xff, 2=0x11, 3=0x22, 4=0x33, 5=0x44 + * + */ + + bool status = false; + uint64_t data = subghz_protocol_blocks_reverse_key(instance->data, 64); + crc = subghz_protocol_blocks_reverse_key(crc, 8); + + if(crc == subghz_protocol_alutech_at_4n_crc(data)) { + data = subghz_protocol_alutech_at_4n_decrypt(data, file_name); + status = true; + } + + if(status && ((uint8_t)(data >> 56) == + subghz_protocol_alutech_at_4n_decrypt_data_crc((uint8_t)((data >> 8) & 0xFF)))) { + instance->btn = (uint8_t)data & 0xFF; + instance->cnt = (uint16_t)(data >> 8) & 0xFFFF; + instance->serial = (uint32_t)(data >> 24) & 0xFFFFFFFF; + } + + if(!status) { + instance->btn = 0; + instance->cnt = 0; + instance->serial = 0; + } +} + +uint8_t subghz_protocol_decoder_alutech_at_4n_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderAlutech_at_4n* instance = context; + return (uint8_t)instance->crc; +} + +bool subghz_protocol_decoder_alutech_at_4n_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderAlutech_at_4n* instance = context; + bool res = subghz_block_generic_serialize(&instance->generic, flipper_format, preset); + if(res && !flipper_format_write_uint32(flipper_format, "CRC", &instance->crc, 1)) { + FURI_LOG_E(TAG, "Unable to add CRC"); + res = false; + } + return res; + + return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +bool subghz_protocol_decoder_alutech_at_4n_deserialize( + void* context, + FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderAlutech_at_4n* instance = context; + bool ret = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_alutech_at_4n_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + if(!flipper_format_read_uint32(flipper_format, "CRC", (uint32_t*)&instance->crc, 1)) { + FURI_LOG_E(TAG, "Missing CRC"); + break; + } + ret = true; + } while(false); + return ret; +} + +void subghz_protocol_decoder_alutech_at_4n_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderAlutech_at_4n* instance = context; + subghz_protocol_alutech_at_4n_remote_controller( + &instance->generic, instance->crc, instance->alutech_at_4n_rainbow_table_file_name); + uint32_t code_found_hi = instance->generic.data >> 32; + uint32_t code_found_lo = instance->generic.data & 0x00000000ffffffff; + + furi_string_cat_printf( + output, + "%s %d\r\n" + "Key:0x%08lX%08lX%02X\r\n" + "Sn:0x%08lX Btn:0x%01X\r\n" + "Cnt:0x%03lX\r\n", + + instance->generic.protocol_name, + instance->generic.data_count_bit, + code_found_hi, + code_found_lo, + (uint8_t)instance->crc, + instance->generic.serial, + instance->generic.btn, + instance->generic.cnt); +} diff --git a/lib/subghz/protocols/alutech_at_4n.h b/lib/subghz/protocols/alutech_at_4n.h new file mode 100644 index 00000000000..38bac3ea6a4 --- /dev/null +++ b/lib/subghz/protocols/alutech_at_4n.h @@ -0,0 +1,74 @@ +#pragma once +#include "base.h" + +#define SUBGHZ_PROTOCOL_ALUTECH_AT_4N_NAME "Alutech at-4n" + +typedef struct SubGhzProtocolDecoderAlutech_at_4n SubGhzProtocolDecoderAlutech_at_4n; +typedef struct SubGhzProtocolEncoderAlutech_at_4n SubGhzProtocolEncoderAlutech_at_4n; + +extern const SubGhzProtocolDecoder subghz_protocol_alutech_at_4n_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_alutech_at_4n_encoder; +extern const SubGhzProtocol subghz_protocol_alutech_at_4n; + +/** + * Allocate SubGhzProtocolDecoderAlutech_at_4n. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderAlutech_at_4n* pointer to a SubGhzProtocolDecoderAlutech_at_4n instance + */ +void* subghz_protocol_decoder_alutech_at_4n_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderAlutech_at_4n. + * @param context Pointer to a SubGhzProtocolDecoderAlutech_at_4n instance + */ +void subghz_protocol_decoder_alutech_at_4n_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderAlutech_at_4n. + * @param context Pointer to a SubGhzProtocolDecoderAlutech_at_4n instance + */ +void subghz_protocol_decoder_alutech_at_4n_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderAlutech_at_4n instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_alutech_at_4n_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderAlutech_at_4n instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_alutech_at_4n_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderAlutech_at_4n. + * @param context Pointer to a SubGhzProtocolDecoderAlutech_at_4n instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_decoder_alutech_at_4n_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderAlutech_at_4n. + * @param context Pointer to a SubGhzProtocolDecoderAlutech_at_4n instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_decoder_alutech_at_4n_deserialize( + void* context, + FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderAlutech_at_4n instance + * @param output Resulting text + */ +void subghz_protocol_decoder_alutech_at_4n_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/came_atomo.c b/lib/subghz/protocols/came_atomo.c index 3f6045beaf7..2e2718485cd 100644 --- a/lib/subghz/protocols/came_atomo.c +++ b/lib/subghz/protocols/came_atomo.c @@ -189,7 +189,7 @@ void subghz_protocol_decoder_came_atomo_feed(void* context, bool level, uint32_t /** * Read bytes from rainbow table * @param file_name Full path to rainbow table the file - * @param number_atomo_magic_xor Сell number in the array + * @param number_atomo_magic_xor number in the array * @return atomo_magic_xor */ static uint64_t subghz_protocol_came_atomo_get_magic_xor_in_file( diff --git a/lib/subghz/protocols/keeloq.c b/lib/subghz/protocols/keeloq.c index 6a9c3468ed1..4a3602fbeb3 100644 --- a/lib/subghz/protocols/keeloq.c +++ b/lib/subghz/protocols/keeloq.c @@ -390,11 +390,14 @@ void subghz_protocol_decoder_keeloq_feed(void* context, bool level, uint32_t dur subghz_protocol_keeloq_const.te_delta)) { // Found end TX instance->decoder.parser_step = KeeloqDecoderStepReset; - if(instance->decoder.decode_count_bit >= - subghz_protocol_keeloq_const.min_count_bit_for_found) { + if((instance->decoder.decode_count_bit >= + subghz_protocol_keeloq_const.min_count_bit_for_found) && + (instance->decoder.decode_count_bit <= + subghz_protocol_keeloq_const.min_count_bit_for_found + 2)) { if(instance->generic.data != instance->decoder.decode_data) { instance->generic.data = instance->decoder.decode_data; - instance->generic.data_count_bit = instance->decoder.decode_count_bit; + instance->generic.data_count_bit = + subghz_protocol_keeloq_const.min_count_bit_for_found; if(instance->base.callback) instance->base.callback(&instance->base, instance->base.context); } @@ -411,6 +414,8 @@ void subghz_protocol_decoder_keeloq_feed(void* context, bool level, uint32_t dur if(instance->decoder.decode_count_bit < subghz_protocol_keeloq_const.min_count_bit_for_found) { subghz_protocol_blocks_add_bit(&instance->decoder, 1); + } else { + instance->decoder.decode_count_bit++; } instance->decoder.parser_step = KeeloqDecoderStepSaveDuration; } else if( @@ -421,6 +426,8 @@ void subghz_protocol_decoder_keeloq_feed(void* context, bool level, uint32_t dur if(instance->decoder.decode_count_bit < subghz_protocol_keeloq_const.min_count_bit_for_found) { subghz_protocol_blocks_add_bit(&instance->decoder, 0); + } else { + instance->decoder.decode_count_bit++; } instance->decoder.parser_step = KeeloqDecoderStepSaveDuration; } else { diff --git a/lib/subghz/protocols/protocol_items.c b/lib/subghz/protocols/protocol_items.c index 99de18c93d8..3904c18118b 100644 --- a/lib/subghz/protocols/protocol_items.c +++ b/lib/subghz/protocols/protocol_items.c @@ -40,6 +40,7 @@ const SubGhzProtocol* subghz_protocol_registry_items[] = { &subghz_protocol_holtek_th12x, &subghz_protocol_linear_delta3, &subghz_protocol_dooya, + &subghz_protocol_alutech_at_4n, }; const SubGhzProtocolRegistry subghz_protocol_registry = { diff --git a/lib/subghz/protocols/protocol_items.h b/lib/subghz/protocols/protocol_items.h index 4d1551bb3ca..cd9b7c6edf1 100644 --- a/lib/subghz/protocols/protocol_items.h +++ b/lib/subghz/protocols/protocol_items.h @@ -40,5 +40,6 @@ #include "smc5326.h" #include "holtek_ht12x.h" #include "dooya.h" +#include "alutech_at_4n.h" extern const SubGhzProtocolRegistry subghz_protocol_registry; From bf4d00a7d1cb94a2b17d26647359ab93207880c1 Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Wed, 8 Feb 2023 21:20:28 +0400 Subject: [PATCH 392/824] [FL-3100] SubGhz: add protocol Nice One (#2358) * SubGhz: add protocol Nice One * SubGhz: fix annotation * SubGhz: add unit test Co-authored-by: Aleksandr Kutuzov --- .../debug/unit_tests/subghz/subghz_test.c | 10 +- assets/unit_tests/subghz/nice_one_raw.sub | 12 ++ assets/unit_tests/subghz/test_random_raw.sub | 7 + lib/subghz/protocols/nice_flor_s.c | 169 +++++++++++++++--- 4 files changed, 175 insertions(+), 23 deletions(-) create mode 100644 assets/unit_tests/subghz/nice_one_raw.sub diff --git a/applications/debug/unit_tests/subghz/subghz_test.c b/applications/debug/unit_tests/subghz/subghz_test.c index 705d6f2f6f2..97629efea94 100644 --- a/applications/debug/unit_tests/subghz/subghz_test.c +++ b/applications/debug/unit_tests/subghz/subghz_test.c @@ -14,7 +14,7 @@ #define NICE_FLOR_S_DIR_NAME EXT_PATH("subghz/assets/nice_flor_s") #define ALUTECH_AT_4N_DIR_NAME EXT_PATH("subghz/assets/alutech_at_4n") #define TEST_RANDOM_DIR_NAME EXT_PATH("unit_tests/subghz/test_random_raw.sub") -#define TEST_RANDOM_COUNT_PARSE 304 +#define TEST_RANDOM_COUNT_PARSE 317 #define TEST_TIMEOUT 10000 static SubGhzEnvironment* environment_handler; @@ -630,6 +630,13 @@ MU_TEST(subghz_decoder_alutech_at_4n_test) { "Test decoder " SUBGHZ_PROTOCOL_ALUTECH_AT_4N_NAME " error\r\n"); } +MU_TEST(subghz_decoder_nice_one_test) { + mu_assert( + subghz_decoder_test( + EXT_PATH("unit_tests/subghz/nice_one_raw.sub"), SUBGHZ_PROTOCOL_NICE_FLOR_S_NAME), + "Test decoder " SUBGHZ_PROTOCOL_NICE_FLOR_S_NAME " error\r\n"); +} + //test encoders MU_TEST(subghz_encoder_princeton_test) { mu_assert( @@ -829,6 +836,7 @@ MU_TEST_SUITE(subghz) { MU_RUN_TEST(subghz_decoder_holtek_ht12x_test); MU_RUN_TEST(subghz_decoder_dooya_test); MU_RUN_TEST(subghz_decoder_alutech_at_4n_test); + MU_RUN_TEST(subghz_decoder_nice_one_test); MU_RUN_TEST(subghz_encoder_princeton_test); MU_RUN_TEST(subghz_encoder_came_test); diff --git a/assets/unit_tests/subghz/nice_one_raw.sub b/assets/unit_tests/subghz/nice_one_raw.sub new file mode 100644 index 00000000000..169b3f08899 --- /dev/null +++ b/assets/unit_tests/subghz/nice_one_raw.sub @@ -0,0 +1,12 @@ +Filetype: Flipper SubGhz RAW File +Version: 1 +Frequency: 433920000 +Preset: FuriHalSubGhzPresetOok650Async +Protocol: RAW +RAW_Data: 7855 -12784 1413 -1544 469 -1040 465 -1010 479 -1020 967 -548 445 -1046 973 -524 967 -520 981 -516 483 -1042 449 -1034 949 -528 495 -1008 479 -1016 985 -518 453 -1042 449 -1052 949 -514 483 -1012 985 -512 477 -1042 445 -1050 951 -548 971 -512 975 -520 967 -554 949 -548 451 -1040 967 -520 987 -518 455 -1038 475 -1016 977 -518 983 -514 473 -1018 975 -518 487 -1002 475 -1020 965 -516 477 -1012 1007 -522 445 -1034 491 -1008 973 -524 +RAW_Data: 481 -992 481 -1010 483 -1030 977 -520 487 -1008 973 -522 987 -518 983 -514 965 -522 987 -520 489 -1004 473 -1018 471 -1016 1005 -476 511 -1012 457 -1018 1001 -510 975 -520 471 -1022 483 -1016 969 -536 1003 -454 981 -480 479 -986 981 -486 479 -946 989 -492 973 -484 473 -976 1503 -23606 1433 -1542 493 -1006 473 -1032 441 -1048 971 -514 483 -1012 985 -518 479 -1014 481 -1012 457 -1050 443 -1044 977 -520 473 -1004 495 -1004 969 -556 453 -1036 451 -1038 973 -520 485 -994 981 -520 457 -1050 477 -1014 977 -494 985 -538 961 -512 1005 -518 951 -526 491 -1006 969 -520 985 -524 455 -1044 447 -1048 983 -518 983 -514 441 -1050 981 -518 453 -1042 447 -1050 981 -518 451 -1046 975 -520 451 -1022 483 -1008 1001 -522 447 -1020 485 -1008 473 -1016 981 -550 449 -1044 977 -520 949 -550 979 -516 967 -520 983 -522 455 -1042 447 -1050 451 -1024 981 -520 483 -1018 963 -546 479 -1010 967 -520 483 -1022 975 -522 967 -552 487 -960 481 -990 451 -994 481 -980 479 -986 449 -984 969 -480 983 -510 1465 -23612 1473 -1520 479 -1026 453 -1044 451 -1036 943 -552 453 -1044 949 -518 481 -1018 977 -524 459 -1046 439 -1046 973 -528 463 -1012 471 -1046 943 -552 443 -1034 457 -1042 977 -518 479 -1028 949 -554 451 -1014 481 -1018 981 -524 985 -518 971 -514 979 -522 987 -512 477 -1016 977 -522 969 -552 449 -1016 483 -1014 985 -518 973 -516 481 -1012 967 -552 449 -1020 483 -1010 969 -554 447 -1022 977 -520 475 -1018 479 -1018 975 -522 457 -1036 479 -1016 479 -1002 969 -552 447 -1054 943 -548 969 -520 983 -520 983 -516 969 -518 479 -1030 453 -1044 449 -1048 943 -548 451 -1044 945 -552 975 -518 947 -552 449 -1034 975 -524 455 -1040 969 -520 449 -982 969 -518 945 -484 481 -984 481 -994 447 -986 477 -998 1435 -23658 1441 -1530 483 -1008 483 -1034 449 -1022 977 -520 485 -1018 479 -1018 975 -506 473 -1036 469 -1042 463 -1010 977 -520 487 -1030 451 -1010 981 -520 481 -1018 481 -1014 983 -518 479 -1016 975 -492 497 -1014 467 -1014 977 -520 975 -526 985 -516 979 -506 1005 -496 493 -1008 975 -522 983 -518 453 -1040 475 -1016 975 -524 987 -514 471 -1038 955 -514 473 -1046 445 -1044 967 -514 477 -1016 975 -520 457 -1050 477 -1010 973 -522 473 -1000 479 -1030 453 -1038 969 -506 473 -1050 971 -512 979 -524 955 -548 973 -512 975 -518 475 -1036 473 -1006 493 -1008 975 -520 973 -526 487 -1004 475 -1018 965 -516 1005 -512 481 -1014 985 -518 483 -986 975 -488 977 -480 977 -486 975 -482 481 -982 975 -480 977 -488 1477 -23618 1389 -1634 369 -1114 383 -1078 431 -1072 +RAW_Data: 931 -550 451 -1046 447 -1042 967 -552 945 -522 459 -1042 445 -1050 943 -552 439 -1036 459 -1046 977 -508 477 -1030 455 -1044 945 -552 451 -1020 979 -524 459 -1046 443 -1048 979 -518 967 -534 957 -516 977 -518 973 -528 455 -1042 973 -520 975 -526 459 -1040 481 -1020 969 -510 967 -546 447 -1050 955 -544 441 -1044 449 -1048 953 -550 443 -1046 975 -518 485 -1010 455 -1044 943 -554 447 -1054 449 -1010 475 -1048 943 -550 453 -1040 969 -520 973 -522 985 -514 969 -554 949 -524 459 -1040 477 -1014 483 -1034 947 -520 981 -554 447 -1016 977 -524 983 -516 973 -516 483 -1016 455 -1046 973 -484 977 -518 449 -986 447 -1016 971 -482 449 -1018 443 -1014 449 -984 1461 -129764 65 -3200 133 -464 133 -298 429 -132 265 -98 231 -134 265 -164 3439 -132 727 -132 199 -2058 133 -1644 361 -166 65 -492 165 -264 591 -428 197 -198 201 -98 831 -68 2313 -100 5839 -10922 65 -1320 425 -262 297 -428 97 -362 2463 -98 1025 -66 5263 -5030 99 -6924 461 -1092 133 -98 333 -166 2739 -132 3131 -66 10535 -2008 131 -434 297 -1058 65 -132 99 -198 529 -198 97 -526 97 -66 493 -664 99 -232 2613 -132 5371 -11166 229 -198 163 -394 199 -398 365 -132 99 -166 2121 -100 1195 -68 1821 -100 10635 -468 67 -1256 65 -2144 229 -100 163 -394 593 -98 67 -166 1677 -66 791 -66 335 -98 11033 -566 65 -1460 165 -1520 497 -1254 491 -564 99 -330 99 -232 1227 -132 2973 -66 3661 -11964 131 -132 99 -398 131 -328 97 -232 363 -396 1379 -98 99 -166 1591 -66 12171 -4136 65 -298 265 -298 199 -462 99 -330 65 -166 163 -66 1591 -66 165 -166 12079 -1002 65 -366 465 -530 97 -134 561 -66 497 -494 99 -64 131 -134 1095 -66 6537 -5066 65 -5458 397 -724 165 -466 131 -166 14293 -436 65 -1590 65 -1462 459 -332 65 -396 563 -794 197 -300 1255 -12100 99 -130 495 -166 97 -296 97 -658 757 -98 959 -66 1029 -1346 165 -2620 395 -494 197 -166 163 -198 65 -98 195 -394 821 -98 3063 -100 4469 -12120 497 -166 65 -462 195 -164 295 -66 4361 -100 1755 -100 131 -66 9415 -3840 99 -530 197 -364 463 -330 365 -332 133 -100 165 -166 2113 -100 1461 -132 4175 -3772 97 -7124 231 -1258 165 -100 429 -1326 995 -200 1755 -66 1519 -100 6437 -7198 133 -300 527 -398 165 -232 131 -166 67 -164 16443 -3270 131 -658 131 -726 97 -858 97 -300 331 -100 629 -10288 67 -164 133 -1458 297 -364 65 -98 163 -758 1189 -66 199 -68 1791 -66 897 -132 165 -3410 163 -364 99 -98 99 -66 365 -232 789 -494 65 -328 629 -66 1259 -66 365 -11422 7923 -12864 1405 -1562 +RAW_Data: 451 -1040 441 -1052 449 -1050 945 -554 449 -1052 451 -1020 481 -1010 473 -1050 449 -1052 451 -1040 969 -520 977 -520 455 -1042 977 -522 447 -1056 947 -518 979 -546 447 -1052 451 -1040 441 -1048 983 -518 455 -1044 449 -1018 979 -548 947 -554 449 -1032 481 -992 483 -1012 985 -514 999 -512 479 -1012 485 -1014 961 -544 477 -1010 965 -522 981 -512 483 -1012 487 -1020 477 -1014 479 -1016 459 -1014 471 -1012 1003 -492 997 -522 483 -1016 979 -522 985 -520 975 -512 975 -520 999 -488 985 -514 481 -1006 1001 -522 483 -990 483 -1008 483 -1020 977 -516 975 -518 999 -524 451 -1018 1009 -482 999 -506 983 -524 487 -1004 473 -980 501 -952 517 -940 497 -982 489 -974 987 -452 495 -974 487 -954 1485 -23662 1457 -1556 445 -1026 483 -1010 475 -1016 975 -518 483 -1014 487 -1034 447 -1022 977 -522 457 -1046 475 -1018 975 -524 985 -518 477 -1016 977 -524 459 -1048 969 -514 977 -522 457 -1038 479 -1018 481 -1002 1001 -520 447 -1054 449 -1008 1001 -520 977 -520 451 -1040 475 -1014 479 -1028 949 -518 983 -542 447 -1058 449 -1044 947 -552 447 -1024 977 -520 967 -542 479 -1024 451 -1040 441 -1050 451 -1028 481 -1014 483 -1010 965 -548 973 -518 485 -1010 981 -516 967 -520 983 -524 981 -514 969 -538 967 -518 481 -1016 973 -524 485 -1016 465 -1012 479 -1020 983 -532 959 -514 975 -554 949 -526 985 -512 969 -554 967 -534 461 -1042 443 -1014 967 -478 455 -1006 969 -486 967 -480 983 -486 969 -514 451 -982 1461 -23692 563 -4014 291 -1220 263 -1228 829 -620 883 -626 851 -608 903 -622 387 -1082 391 -1102 409 -1084 913 -588 941 -548 443 -1056 945 -522 445 -1046 971 -552 977 -516 441 -1048 481 -992 483 -1010 979 -554 451 -1018 481 -1014 983 -518 977 -514 479 -1040 447 -1034 485 -996 975 -520 979 -520 483 -1016 481 -1008 999 -506 471 -1050 971 -514 975 -520 473 -1000 483 -1020 481 -1008 473 -1018 481 -1020 481 -1008 967 -554 945 -518 481 -1038 967 -520 985 -520 981 -514 967 -520 985 -520 981 -508 479 -1016 1003 -518 479 -1010 479 -1010 473 -1018 975 -516 979 -520 983 -520 975 -514 977 -518 999 -520 979 -518 451 -1040 479 -986 479 -962 1007 -486 451 -986 975 -486 977 -482 483 -980 477 -982 1473 -23656 1453 -1548 447 -1016 485 -1012 491 -1012 973 -520 981 -526 983 -514 971 -554 947 -526 491 -1008 475 -1020 983 -498 989 -516 483 -1014 977 -524 453 -1044 979 -518 979 -520 453 -1042 449 -1048 447 -1022 975 -518 475 -1050 447 -1020 977 -522 983 -518 481 -1016 481 -1012 473 -1002 973 -550 945 -552 449 -1050 447 -1020 975 -522 487 -1034 973 -520 +RAW_Data: 979 -514 443 -1046 479 -1028 451 -1042 451 -1048 447 -1022 485 -1014 983 -520 973 -516 483 -1012 983 -518 973 -516 977 -520 1003 -520 975 -520 981 -514 475 -1034 969 -516 479 -1016 447 -1046 475 -1018 975 -516 975 -522 983 -510 469 -1010 1007 -518 951 -530 989 -516 973 -556 951 -494 481 -978 487 -978 975 -460 1005 -466 979 -486 969 -508 981 -450 1489 -23666 571 -4036 269 -1224 257 -1250 787 -642 867 -622 883 -622 359 -1136 373 -1086 421 -1080 417 -1074 935 -550 947 -552 445 -1048 939 -552 451 -1046 947 -552 947 -550 451 -1040 443 -1048 453 -1024 977 -522 471 -1034 449 -1020 973 -540 975 -508 479 -1032 453 -1042 449 -1050 977 -518 979 -518 449 -1018 481 -1018 975 -518 473 -1034 963 -542 961 -544 447 -1044 473 -1020 479 -1014 481 -1010 473 -1032 471 -1010 959 -546 973 -492 499 -1006 997 -510 977 -524 953 -552 971 -512 973 -508 979 -554 451 -1016 977 -518 471 -1038 485 -1010 457 -1036 969 -506 999 -520 481 -1014 975 -522 967 -520 975 -548 451 -1038 475 -1022 965 -518 463 -978 985 -486 465 -978 457 -1016 463 -978 985 -486 963 -480 1477 -129906 495 -726 197 -328 295 -132 2547 -66 233 -98 11033 -1856 233 -1458 65 -198 165 -134 199 -168 101 -694 463 -530 165 -300 99 -232 2479 -98 1745 -98 3029 -132 163 -1460 65 -500 65 -400 99 -664 895 -398 65 -564 331 -166 97 -66 197 -98 3813 -98 10097 -3848 165 -232 67 -266 397 -596 165 -66 199 -166 99 -66 199 -398 165 -166 1721 -232 429 -166 133 -330 133 -698 493 -200 197 -428 11029 -12118 65 -198 199 -68 231 -230 101 -166 99 -664 131 -132 3163 -4238 331 -298 531 -398 299 -98 199 -166 563 -100 131 -98 893 -66 3141 -1556 133 -1722 131 -830 197 -262 195 -66 163 -462 195 -396 195 -134 499 -132 265 -66 1717 -166 3175 -11366 199 -164 131 -66 163 -98 525 -98 363 -264 4495 -100 229 -66 131 -66 593 -3002 97 -394 131 -426 99 -462 597 -692 295 -298 431 -230 4231 -66 9711 -3246 131 -100 99 -400 263 -498 65 -100 297 -98 99 -132 65 -862 131 -66 365 -396 99 -166 1991 -98 1611 -132 10333 -790 65 -1984 99 -896 165 -332 365 -232 131 -830 65 -66 397 -166 197 -66 65 -496 199 -100 9975 -1728 67 -5008 727 -98 131 -100 2873 -66 12011 -3150 67 -960 99 -234 99 -298 231 -232 195 -266 165 -296 261 -166 757 -66 629 -196 657 -100 197 -134 297 -364 11237 -1684 65 -2076 165 -462 491 -100 663 -630 329 -264 263 -100 1357 -66 461 -1676 99 -1782 295 -296 65 -296 163 -230 99 -132 295 -66 163 -362 197 -724 757 -66 +RAW_Data: 3785 -66 13551 -1808 97 -730 65 -100 231 -132 131 -1230 593 -232 1579 -66 2667 -200 101 -3480 165 -692 133 -396 427 -1524 363 -66 431 -132 10305 -8288 461 -628 67 -430 725 -66 1053 -66 4501 -230 165 -66 331 -66 355 -266 263 -132 63 -562 459 -462 197 -66 129 -132 65 -100 2643 -132 2107 -66 9651 -3692 99 -100 195 -294 97 -660 759 -328 165 -560 891 -66 1953 -66 11305 -362 263 -662 131 -432 65 -134 563 -430 131 -132 1819 -100 165 -166 1061 -98 10089 -2476 65 -854 395 -198 99 -492 131 -164 229 -466 199 -428 299 -100 927 -200 1557 -134 4269 -10464 133 -1624 65 -198 265 -398 131 -430 729 -134 6189 -66 5421 -2082 165 -3342 19967 -12808 1439 -1536 453 -1046 449 -1032 449 -1056 947 -552 977 -522 977 -518 453 -1038 977 -522 977 -520 457 -1038 977 -506 1005 -496 495 -1008 975 -538 973 -530 465 -1008 975 -554 453 -1036 947 -518 487 -1008 475 -1042 443 -1050 461 -1008 1005 -510 447 -1048 985 -510 469 -1006 1005 -494 997 -514 975 -514 975 -504 999 -506 479 -1034 491 -1010 975 -508 973 -524 491 -1004 473 -1018 997 -520 975 -512 975 -518 473 -1030 983 -516 981 -514 471 -998 997 -522 481 -1012 481 -1012 457 -1050 973 -512 977 -524 459 -1016 1003 -512 479 -1014 459 -1016 475 -1012 1007 -522 969 -502 495 -1008 477 -1030 965 -522 975 -514 479 -1000 471 -1062 471 -964 483 -982 471 -1000 471 -980 979 -448 503 -988 465 -976 487 -974 1459 -23696 1407 -1616 401 -1068 429 -1080 419 -1058 935 -566 923 -584 417 -1078 939 -524 457 -1042 973 -550 443 -1028 949 -554 945 -552 447 -1022 979 -518 971 -542 479 -1024 947 -550 441 -1048 979 -518 453 -1044 449 -1050 449 -1020 485 -1014 981 -518 479 -1014 975 -524 459 -1036 973 -516 979 -518 971 -552 945 -550 945 -552 449 -1030 479 -1026 947 -554 949 -552 449 -1018 479 -1008 981 -518 975 -548 945 -554 451 -1034 967 -514 997 -514 445 -1036 967 -554 447 -1022 485 -1010 475 -1016 975 -518 977 -520 487 -1014 973 -552 451 -1040 441 -1050 447 -1022 485 -1014 987 -516 479 -1014 483 -1014 459 -1046 969 -514 449 -1044 967 -546 973 -488 447 -1016 443 -1000 973 -490 475 -980 983 -482 441 -1016 465 -976 1475 -23652 1451 -1548 479 -1014 461 -1014 471 -1044 975 -520 971 -502 495 -1012 977 -506 1005 -498 989 -516 481 -1016 975 -520 981 -514 475 -1014 979 -522 983 -512 475 -1022 965 -514 471 -1046 973 -494 473 -1016 475 -1046 447 -1050 463 -1012 999 -512 481 -1012 983 -520 477 -1014 977 -524 955 -548 973 -512 975 -520 967 -556 449 -1020 483 -1012 983 -520 973 -516 481 -1008 473 -1034 +RAW_Data: 967 -538 963 -544 973 -522 471 -1006 989 -512 1007 -520 443 -1036 985 -516 449 -1048 451 -1022 483 -1012 983 -520 977 -514 481 -1012 979 -514 483 -1022 481 -1010 471 -1020 479 -1020 979 -524 457 -1048 973 -514 483 -1012 981 -520 483 -1018 481 -1014 485 -986 467 -980 981 -486 469 -978 457 -1004 963 -480 983 -486 971 -514 1441 -23704 1383 -1628 389 -1112 385 -1092 407 -1092 915 -552 941 -570 441 -1064 423 -1046 451 -1044 939 -556 455 -1048 945 -552 973 -522 453 -1046 945 -552 947 -550 451 -1040 969 -518 479 -1028 951 -552 451 -1018 479 -1018 483 -1014 459 -1044 971 -514 483 -1010 971 -544 447 -1020 977 -524 987 -518 973 -516 979 -524 985 -518 479 -1016 447 -1050 953 -548 971 -514 483 -1014 459 -1048 967 -514 977 -526 953 -548 443 -1046 975 -492 995 -512 471 -1050 943 -552 445 -1032 455 -1044 449 -1048 941 -550 945 -552 449 -1050 945 -552 451 -1044 449 -1018 479 -1016 479 -1002 969 -542 973 -522 455 -1040 477 -1022 967 -534 959 -514 975 -554 469 -1008 449 -980 469 -1008 943 -484 1001 -484 467 -980 983 -482 961 -514 1439 -23700 1469 -1510 495 -1008 473 -1036 463 -1012 969 -546 973 -522 473 -1018 479 -1014 975 -526 955 -516 475 -1046 975 -490 999 -518 481 -1014 975 -520 967 -514 481 -1022 979 -524 457 -1048 971 -514 481 -1010 485 -1020 477 -1014 479 -1000 1001 -522 451 -1020 977 -520 473 -1032 967 -538 959 -514 1005 -522 965 -504 989 -514 475 -1046 441 -1050 971 -514 975 -520 473 -1018 481 -1014 979 -520 983 -520 977 -516 485 -1010 979 -544 975 -518 453 -1042 981 -520 453 -1024 483 -1010 457 -1050 975 -512 975 -524 459 -1048 973 -514 481 -1010 473 -1016 479 -1016 477 -1036 967 -506 995 -512 965 -546 445 -1048 957 -516 1005 -512 445 -1046 979 -486 473 -980 979 -486 473 -980 981 -486 473 -980 485 -986 467 -976 1477 -142204 197 -1486 165 -198 165 -664 295 -232 99 -266 231 -166 3045 -100 13411 -3670 197 -498 131 -166 231 -198 165 -66 265 -134 129 -1062 431 -130 465 -134 13447 -3848 329 -100 163 -298 99 -164 463 -98 197 -98 131 -198 65 -296 493 -264 789 -66 7225 -12438 99 -164 463 -132 197 -630 65 -198 2487 -66 165 -100 10097 -6554 459 -664 297 -460 4925 -132 6063 -12078 497 -98 99 -200 97 -234 165 -298 1721 -134 265 -100 3035 -100 12081 -3674 231 -100 97 -200 97 -264 461 -100 99 -132 231 -100 97 -430 527 -200 231 -64 2081 -132 327 -100 529 -66 831 -66 3067 -4704 99 -5520 97 -496 67 -198 167 -498 693 -462 2341 -15926 65 -1392 659 -134 131 -298 165 -66 99 -298 4777 -4208 429 -66 diff --git a/assets/unit_tests/subghz/test_random_raw.sub b/assets/unit_tests/subghz/test_random_raw.sub index 44e1613c71d..12b08d48da4 100644 --- a/assets/unit_tests/subghz/test_random_raw.sub +++ b/assets/unit_tests/subghz/test_random_raw.sub @@ -183,3 +183,10 @@ RAW_Data: -452 783 -462 811 -446 411 -874 383 -852 403 -852 777 -450 411 -838 80 RAW_Data: -500 395 -132 461 -132 689 -98 2685 -100 997 -1508 99 -2186 231 -166 231 -134 133 -932 65 -268 99 -132 65 -200 97 -68 163 -234 65 -68 99 -930 331 -98 763 -100 2025 -418 353 -446 385 -414 385 -416 387 -448 355 -442 383 -412 397 -424 405 -388 409 -418 379 -418 415 -4014 443 -814 413 -822 817 -454 801 -436 409 -842 409 -866 415 -838 441 -836 811 -432 387 -842 419 -846 793 -440 807 -448 837 -446 803 -448 835 -420 807 -448 383 -868 379 -850 409 -866 387 -844 825 -468 381 -884 793 -426 415 -842 427 -818 817 -440 407 -830 419 -844 429 -852 387 -872 409 -826 811 -450 813 -418 837 -412 409 -864 417 -844 397 -852 809 -454 805 -448 409 -840 809 -420 813 -458 409 -844 407 -860 385 -878 793 -470 809 -420 817 -416 417 -850 403 -852 381 -852 827 -428 447 -844 401 -854 813 -424 421 -840 419 -812 823 -438 415 -846 409 -844 415 -846 389 -868 809 -458 803 -416 409 -866 813 -418 417 -854 397 -862 419 -842 401 -854 415 -16404 435 -376 409 -410 407 -384 439 -384 409 -410 417 -368 421 -410 407 -378 447 -376 415 -378 447 -380 407 -4022 421 -844 423 -822 821 -418 807 -454 429 -820 421 -836 439 -854 421 -810 821 -436 385 -840 441 -822 813 -448 811 -452 803 -444 835 -444 801 -446 801 -426 447 -808 423 -834 413 -852 407 -840 819 -452 389 -856 813 -444 409 -848 415 -812 809 -458 409 -848 411 -842 415 -844 421 -834 415 -834 835 -418 819 -418 807 -456 393 -856 393 -866 421 -846 799 -474 809 -420 421 -836 811 -420 813 -458 407 -850 413 -842 415 -846 819 -428 835 -416 835 -412 407 -832 421 -842 423 -822 813 -446 407 -864 419 -846 799 -440 413 -850 419 -816 797 -442 413 -850 409 -844 417 -846 423 -834 841 -428 805 -414 435 -822 813 -450 413 -822 437 -818 421 -844 429 -854 411 -16406 427 -418 309 -522 307 -488 303 -520 289 -530 295 -500 323 -470 325 -504 321 -476 321 -476 355 -444 357 -4080 355 -906 339 -882 771 -476 777 -486 381 -874 383 -884 375 -884 387 -852 819 -418 417 -846 399 -854 809 -418 815 -446 837 -420 839 -454 801 -436 807 -452 399 -826 417 -844 391 -852 423 -838 809 -452 431 -852 811 -414 409 -836 417 -844 821 -432 385 -876 385 -850 409 -848 415 -854 421 -840 817 -420 815 -424 817 -448 409 -848 413 -844 389 -854 815 -446 829 -426 413 -842 819 -434 809 -446 409 -838 419 -846 401 -852 811 -456 811 -444 803 -418 417 -848 403 -850 381 -864 805 -450 395 -866 419 -848 801 -474 381 -848 411 RAW_Data: -842 807 -446 381 -872 377 -866 421 -846 401 -854 813 -458 779 -446 407 -832 811 -450 415 -856 399 -856 385 -876 399 -854 411 -16398 435 -392 395 -400 421 -412 385 -412 417 -384 415 -386 415 -418 385 -420 385 -420 417 -390 417 -388 419 -4020 421 -838 421 -812 819 -434 809 -448 397 -864 421 -844 401 -850 413 -858 789 -426 413 -844 419 -836 807 -424 843 -410 829 -442 835 -446 801 -454 809 -420 417 -832 411 -848 249 -88020 133 -896 231 -466 67 -1062 131 -728 163 -98 621 -98 1051 -100 680933 -452 269 -522 273 -554 273 -558 239 -558 271 -490 337 -488 321 -498 295 -500 325 -470 323 -474 353 -4082 757 -492 375 -880 357 -872 777 -486 773 -480 807 -450 805 -444 805 -476 407 -812 413 -834 411 -848 407 -828 813 -450 811 -458 803 -448 835 -446 791 -424 447 -808 427 -818 423 -840 419 -848 401 -854 811 -458 809 -446 801 -416 439 -826 415 -848 813 -430 809 -450 395 -866 419 -846 403 -850 413 -820 407 -848 415 -846 781 -460 805 -446 803 -474 803 -448 835 -420 805 -454 389 -836 409 -842 407 -866 419 -842 399 -854 809 -456 809 -446 409 -840 385 -844 819 -434 809 -450 395 -860 811 -452 393 -886 779 -446 409 -830 419 -842 423 -818 423 -838 419 -844 799 -472 809 -454 385 -844 807 -454 391 -854 395 -860 385 -844 429 -852 809 -454 385 -874 409 -16402 427 -368 455 -358 433 -380 443 -378 415 -378 447 -380 411 -384 409 -406 421 -408 387 -412 415 -386 415 -4026 831 -398 441 -810 417 -832 837 -418 833 -444 803 -446 833 -448 801 -424 449 -810 427 -820 423 -838 419 -812 825 -438 841 -416 845 -446 825 -418 809 -422 419 -822 433 -822 419 -844 425 -820 421 -840 841 -458 797 -436 809 -414 435 -822 419 -844 819 -432 809 -448 395 -864 421 -846 407 -850 411 -808 433 -824 419 -844 819 -432 809 -446 823 -416 837 -454 807 -440 809 -414 435 -828 417 -844 425 -828 415 -848 419 -818 839 -446 807 -422 411 -844 419 -846 795 -438 807 -450 395 -866 811 -454 391 -854 845 -412 407 -832 421 -842 419 -832 411 -824 435 -820 815 -450 811 -460 409 -850 799 -454 407 -824 413 -848 411 -842 415 -844 815 -432 415 -848 405 -16400 441 -432 327 -492 301 -516 307 -484 309 -520 307 -492 339 -454 337 -490 331 -464 327 -500 325 -472 325 -4110 763 -480 373 -852 385 -878 759 -482 775 -474 813 -458 781 -482 789 -454 415 -846 397 -820 411 -840 405 -852 809 -450 811 -458 809 -450 817 -448 803 -426 411 -844 391 -854 393 -866 419 -848 399 -854 811 RAW_Data: -454 811 -444 803 -418 417 -846 403 -850 809 -452 805 -444 411 -840 419 -846 407 -850 415 -836 385 -842 419 -850 797 -438 807 -452 817 -446 801 -486 813 -444 775 -450 409 -838 419 -810 431 -854 379 -848 405 -884 809 -450 817 -430 385 -874 375 -856 811 -446 809 -422 421 -836 835 -452 419 -848 783 -460 409 -814 407 -856 415 -846 383 -870 381 -848 819 -450 811 -472 383 -850 803 -454 415 -838 399 -854 379 -850 407 -848 811 -448 415 -872 387 -16400 451 -374 445 -374 415 -378 415 -412 411 -384 405 -422 409 -410 387 -410 417 -382 417 -384 415 -420 383 -4030 827 -428 411 -842 425 -820 817 -418 833 -426 845 -452 815 -428 837 -416 409 -842 421 -810 431 -820 421 -89106 265 -662 99 -532 131 -598 97 -668 65 -300 761 -198 231 -132 265 -100 233 -100 197 +RAW_Data: 7855 -12784 1413 -1544 469 -1040 465 -1010 479 -1020 967 -548 445 -1046 973 -524 967 -520 981 -516 483 -1042 449 -1034 949 -528 495 -1008 479 -1016 985 -518 453 -1042 449 -1052 949 -514 483 -1012 985 -512 477 -1042 445 -1050 951 -548 971 -512 975 -520 967 -554 949 -548 451 -1040 967 -520 987 -518 455 -1038 475 -1016 977 -518 983 -514 473 -1018 975 -518 487 -1002 475 -1020 965 -516 477 -1012 1007 -522 445 -1034 491 -1008 973 -524 +RAW_Data: 481 -992 481 -1010 483 -1030 977 -520 487 -1008 973 -522 987 -518 983 -514 965 -522 987 -520 489 -1004 473 -1018 471 -1016 1005 -476 511 -1012 457 -1018 1001 -510 975 -520 471 -1022 483 -1016 969 -536 1003 -454 981 -480 479 -986 981 -486 479 -946 989 -492 973 -484 473 -976 1503 -23606 1433 -1542 493 -1006 473 -1032 441 -1048 971 -514 483 -1012 985 -518 479 -1014 481 -1012 457 -1050 443 -1044 977 -520 473 -1004 495 -1004 969 -556 453 -1036 451 -1038 973 -520 485 -994 981 -520 457 -1050 477 -1014 977 -494 985 -538 961 -512 1005 -518 951 -526 491 -1006 969 -520 985 -524 455 -1044 447 -1048 983 -518 983 -514 441 -1050 981 -518 453 -1042 447 -1050 981 -518 451 -1046 975 -520 451 -1022 483 -1008 1001 -522 447 -1020 485 -1008 473 -1016 981 -550 449 -1044 977 -520 949 -550 979 -516 967 -520 983 -522 455 -1042 447 -1050 451 -1024 981 -520 483 -1018 963 -546 479 -1010 967 -520 483 -1022 975 -522 967 -552 487 -960 481 -990 451 -994 481 -980 479 -986 449 -984 969 -480 983 -510 1465 -23612 1473 -1520 479 -1026 453 -1044 451 -1036 943 -552 453 -1044 949 -518 481 -1018 977 -524 459 -1046 439 -1046 973 -528 463 -1012 471 -1046 943 -552 443 -1034 457 -1042 977 -518 479 -1028 949 -554 451 -1014 481 -1018 981 -524 985 -518 971 -514 979 -522 987 -512 477 -1016 977 -522 969 -552 449 -1016 483 -1014 985 -518 973 -516 481 -1012 967 -552 449 -1020 483 -1010 969 -554 447 -1022 977 -520 475 -1018 479 -1018 975 -522 457 -1036 479 -1016 479 -1002 969 -552 447 -1054 943 -548 969 -520 983 -520 983 -516 969 -518 479 -1030 453 -1044 449 -1048 943 -548 451 -1044 945 -552 975 -518 947 -552 449 -1034 975 -524 455 -1040 969 -520 449 -982 969 -518 945 -484 481 -984 481 -994 447 -986 477 -998 1435 -23658 1441 -1530 483 -1008 483 -1034 449 -1022 977 -520 485 -1018 479 -1018 975 -506 473 -1036 469 -1042 463 -1010 977 -520 487 -1030 451 -1010 981 -520 481 -1018 481 -1014 983 -518 479 -1016 975 -492 497 -1014 467 -1014 977 -520 975 -526 985 -516 979 -506 1005 -496 493 -1008 975 -522 983 -518 453 -1040 475 -1016 975 -524 987 -514 471 -1038 955 -514 473 -1046 445 -1044 967 -514 477 -1016 975 -520 457 -1050 477 -1010 973 -522 473 -1000 479 -1030 453 -1038 969 -506 473 -1050 971 -512 979 -524 955 -548 973 -512 975 -518 475 -1036 473 -1006 493 -1008 975 -520 973 -526 487 -1004 475 -1018 965 -516 1005 -512 481 -1014 985 -518 483 -986 975 -488 977 -480 977 -486 975 -482 481 -982 975 -480 977 -488 1477 -23618 1389 -1634 369 -1114 383 -1078 431 -1072 +RAW_Data: 931 -550 451 -1046 447 -1042 967 -552 945 -522 459 -1042 445 -1050 943 -552 439 -1036 459 -1046 977 -508 477 -1030 455 -1044 945 -552 451 -1020 979 -524 459 -1046 443 -1048 979 -518 967 -534 957 -516 977 -518 973 -528 455 -1042 973 -520 975 -526 459 -1040 481 -1020 969 -510 967 -546 447 -1050 955 -544 441 -1044 449 -1048 953 -550 443 -1046 975 -518 485 -1010 455 -1044 943 -554 447 -1054 449 -1010 475 -1048 943 -550 453 -1040 969 -520 973 -522 985 -514 969 -554 949 -524 459 -1040 477 -1014 483 -1034 947 -520 981 -554 447 -1016 977 -524 983 -516 973 -516 483 -1016 455 -1046 973 -484 977 -518 449 -986 447 -1016 971 -482 449 -1018 443 -1014 449 -984 1461 -129764 65 -3200 133 -464 133 -298 429 -132 265 -98 231 -134 265 -164 3439 -132 727 -132 199 -2058 133 -1644 361 -166 65 -492 165 -264 591 -428 197 -198 201 -98 831 -68 2313 -100 5839 -10922 65 -1320 425 -262 297 -428 97 -362 2463 -98 1025 -66 5263 -5030 99 -6924 461 -1092 133 -98 333 -166 2739 -132 3131 -66 10535 -2008 131 -434 297 -1058 65 -132 99 -198 529 -198 97 -526 97 -66 493 -664 99 -232 2613 -132 5371 -11166 229 -198 163 -394 199 -398 365 -132 99 -166 2121 -100 1195 -68 1821 -100 10635 -468 67 -1256 65 -2144 229 -100 163 -394 593 -98 67 -166 1677 -66 791 -66 335 -98 11033 -566 65 -1460 165 -1520 497 -1254 491 -564 99 -330 99 -232 1227 -132 2973 -66 3661 -11964 131 -132 99 -398 131 -328 97 -232 363 -396 1379 -98 99 -166 1591 -66 12171 -4136 65 -298 265 -298 199 -462 99 -330 65 -166 163 -66 1591 -66 165 -166 12079 -1002 65 -366 465 -530 97 -134 561 -66 497 -494 99 -64 131 -134 1095 -66 6537 -5066 65 -5458 397 -724 165 -466 131 -166 14293 -436 65 -1590 65 -1462 459 -332 65 -396 563 -794 197 -300 1255 -12100 99 -130 495 -166 97 -296 97 -658 757 -98 959 -66 1029 -1346 165 -2620 395 -494 197 -166 163 -198 65 -98 195 -394 821 -98 3063 -100 4469 -12120 497 -166 65 -462 195 -164 295 -66 4361 -100 1755 -100 131 -66 9415 -3840 99 -530 197 -364 463 -330 365 -332 133 -100 165 -166 2113 -100 1461 -132 4175 -3772 97 -7124 231 -1258 165 -100 429 -1326 995 -200 1755 -66 1519 -100 6437 -7198 133 -300 527 -398 165 -232 131 -166 67 -164 16443 -3270 131 -658 131 -726 97 -858 97 -300 331 -100 629 -10288 67 -164 133 -1458 297 -364 65 -98 163 -758 1189 -66 199 -68 1791 -66 897 -132 165 -3410 163 -364 99 -98 99 -66 365 -232 789 -494 65 -328 629 -66 1259 -66 365 -11422 7923 -12864 1405 -1562 +RAW_Data: 451 -1040 441 -1052 449 -1050 945 -554 449 -1052 451 -1020 481 -1010 473 -1050 449 -1052 451 -1040 969 -520 977 -520 455 -1042 977 -522 447 -1056 947 -518 979 -546 447 -1052 451 -1040 441 -1048 983 -518 455 -1044 449 -1018 979 -548 947 -554 449 -1032 481 -992 483 -1012 985 -514 999 -512 479 -1012 485 -1014 961 -544 477 -1010 965 -522 981 -512 483 -1012 487 -1020 477 -1014 479 -1016 459 -1014 471 -1012 1003 -492 997 -522 483 -1016 979 -522 985 -520 975 -512 975 -520 999 -488 985 -514 481 -1006 1001 -522 483 -990 483 -1008 483 -1020 977 -516 975 -518 999 -524 451 -1018 1009 -482 999 -506 983 -524 487 -1004 473 -980 501 -952 517 -940 497 -982 489 -974 987 -452 495 -974 487 -954 1485 -23662 1457 -1556 445 -1026 483 -1010 475 -1016 975 -518 483 -1014 487 -1034 447 -1022 977 -522 457 -1046 475 -1018 975 -524 985 -518 477 -1016 977 -524 459 -1048 969 -514 977 -522 457 -1038 479 -1018 481 -1002 1001 -520 447 -1054 449 -1008 1001 -520 977 -520 451 -1040 475 -1014 479 -1028 949 -518 983 -542 447 -1058 449 -1044 947 -552 447 -1024 977 -520 967 -542 479 -1024 451 -1040 441 -1050 451 -1028 481 -1014 483 -1010 965 -548 973 -518 485 -1010 981 -516 967 -520 983 -524 981 -514 969 -538 967 -518 481 -1016 973 -524 485 -1016 465 -1012 479 -1020 983 -532 959 -514 975 -554 949 -526 985 -512 969 -554 967 -534 461 -1042 443 -1014 967 -478 455 -1006 969 -486 967 -480 983 -486 969 -514 451 -982 1461 -23692 563 -4014 291 -1220 263 -1228 829 -620 883 -626 851 -608 903 -622 387 -1082 391 -1102 409 -1084 913 -588 941 -548 443 -1056 945 -522 445 -1046 971 -552 977 -516 441 -1048 481 -992 483 -1010 979 -554 451 -1018 481 -1014 983 -518 977 -514 479 -1040 447 -1034 485 -996 975 -520 979 -520 483 -1016 481 -1008 999 -506 471 -1050 971 -514 975 -520 473 -1000 483 -1020 481 -1008 473 -1018 481 -1020 481 -1008 967 -554 945 -518 481 -1038 967 -520 985 -520 981 -514 967 -520 985 -520 981 -508 479 -1016 1003 -518 479 -1010 479 -1010 473 -1018 975 -516 979 -520 983 -520 975 -514 977 -518 999 -520 979 -518 451 -1040 479 -986 479 -962 1007 -486 451 -986 975 -486 977 -482 483 -980 477 -982 1473 -23656 1453 -1548 447 -1016 485 -1012 491 -1012 973 -520 981 -526 983 -514 971 -554 947 -526 491 -1008 475 -1020 983 -498 989 -516 483 -1014 977 -524 453 -1044 979 -518 979 -520 453 -1042 449 -1048 447 -1022 975 -518 475 -1050 447 -1020 977 -522 983 -518 481 -1016 481 -1012 473 -1002 973 -550 945 -552 449 -1050 447 -1020 975 -522 487 -1034 973 -520 +RAW_Data: 979 -514 443 -1046 479 -1028 451 -1042 451 -1048 447 -1022 485 -1014 983 -520 973 -516 483 -1012 983 -518 973 -516 977 -520 1003 -520 975 -520 981 -514 475 -1034 969 -516 479 -1016 447 -1046 475 -1018 975 -516 975 -522 983 -510 469 -1010 1007 -518 951 -530 989 -516 973 -556 951 -494 481 -978 487 -978 975 -460 1005 -466 979 -486 969 -508 981 -450 1489 -23666 571 -4036 269 -1224 257 -1250 787 -642 867 -622 883 -622 359 -1136 373 -1086 421 -1080 417 -1074 935 -550 947 -552 445 -1048 939 -552 451 -1046 947 -552 947 -550 451 -1040 443 -1048 453 -1024 977 -522 471 -1034 449 -1020 973 -540 975 -508 479 -1032 453 -1042 449 -1050 977 -518 979 -518 449 -1018 481 -1018 975 -518 473 -1034 963 -542 961 -544 447 -1044 473 -1020 479 -1014 481 -1010 473 -1032 471 -1010 959 -546 973 -492 499 -1006 997 -510 977 -524 953 -552 971 -512 973 -508 979 -554 451 -1016 977 -518 471 -1038 485 -1010 457 -1036 969 -506 999 -520 481 -1014 975 -522 967 -520 975 -548 451 -1038 475 -1022 965 -518 463 -978 985 -486 465 -978 457 -1016 463 -978 985 -486 963 -480 1477 -129906 495 -726 197 -328 295 -132 2547 -66 233 -98 11033 -1856 233 -1458 65 -198 165 -134 199 -168 101 -694 463 -530 165 -300 99 -232 2479 -98 1745 -98 3029 -132 163 -1460 65 -500 65 -400 99 -664 895 -398 65 -564 331 -166 97 -66 197 -98 3813 -98 10097 -3848 165 -232 67 -266 397 -596 165 -66 199 -166 99 -66 199 -398 165 -166 1721 -232 429 -166 133 -330 133 -698 493 -200 197 -428 11029 -12118 65 -198 199 -68 231 -230 101 -166 99 -664 131 -132 3163 -4238 331 -298 531 -398 299 -98 199 -166 563 -100 131 -98 893 -66 3141 -1556 133 -1722 131 -830 197 -262 195 -66 163 -462 195 -396 195 -134 499 -132 265 -66 1717 -166 3175 -11366 199 -164 131 -66 163 -98 525 -98 363 -264 4495 -100 229 -66 131 -66 593 -3002 97 -394 131 -426 99 -462 597 -692 295 -298 431 -230 4231 -66 9711 -3246 131 -100 99 -400 263 -498 65 -100 297 -98 99 -132 65 -862 131 -66 365 -396 99 -166 1991 -98 1611 -132 10333 -790 65 -1984 99 -896 165 -332 365 -232 131 -830 65 -66 397 -166 197 -66 65 -496 199 -100 9975 -1728 67 -5008 727 -98 131 -100 2873 -66 12011 -3150 67 -960 99 -234 99 -298 231 -232 195 -266 165 -296 261 -166 757 -66 629 -196 657 -100 197 -134 297 -364 11237 -1684 65 -2076 165 -462 491 -100 663 -630 329 -264 263 -100 1357 -66 461 -1676 99 -1782 295 -296 65 -296 163 -230 99 -132 295 -66 163 -362 197 -724 757 -66 +RAW_Data: 3785 -66 13551 -1808 97 -730 65 -100 231 -132 131 -1230 593 -232 1579 -66 2667 -200 101 -3480 165 -692 133 -396 427 -1524 363 -66 431 -132 10305 -8288 461 -628 67 -430 725 -66 1053 -66 4501 -230 165 -66 331 -66 355 -266 263 -132 63 -562 459 -462 197 -66 129 -132 65 -100 2643 -132 2107 -66 9651 -3692 99 -100 195 -294 97 -660 759 -328 165 -560 891 -66 1953 -66 11305 -362 263 -662 131 -432 65 -134 563 -430 131 -132 1819 -100 165 -166 1061 -98 10089 -2476 65 -854 395 -198 99 -492 131 -164 229 -466 199 -428 299 -100 927 -200 1557 -134 4269 -10464 133 -1624 65 -198 265 -398 131 -430 729 -134 6189 -66 5421 -2082 165 -3342 19967 -12808 1439 -1536 453 -1046 449 -1032 449 -1056 947 -552 977 -522 977 -518 453 -1038 977 -522 977 -520 457 -1038 977 -506 1005 -496 495 -1008 975 -538 973 -530 465 -1008 975 -554 453 -1036 947 -518 487 -1008 475 -1042 443 -1050 461 -1008 1005 -510 447 -1048 985 -510 469 -1006 1005 -494 997 -514 975 -514 975 -504 999 -506 479 -1034 491 -1010 975 -508 973 -524 491 -1004 473 -1018 997 -520 975 -512 975 -518 473 -1030 983 -516 981 -514 471 -998 997 -522 481 -1012 481 -1012 457 -1050 973 -512 977 -524 459 -1016 1003 -512 479 -1014 459 -1016 475 -1012 1007 -522 969 -502 495 -1008 477 -1030 965 -522 975 -514 479 -1000 471 -1062 471 -964 483 -982 471 -1000 471 -980 979 -448 503 -988 465 -976 487 -974 1459 -23696 1407 -1616 401 -1068 429 -1080 419 -1058 935 -566 923 -584 417 -1078 939 -524 457 -1042 973 -550 443 -1028 949 -554 945 -552 447 -1022 979 -518 971 -542 479 -1024 947 -550 441 -1048 979 -518 453 -1044 449 -1050 449 -1020 485 -1014 981 -518 479 -1014 975 -524 459 -1036 973 -516 979 -518 971 -552 945 -550 945 -552 449 -1030 479 -1026 947 -554 949 -552 449 -1018 479 -1008 981 -518 975 -548 945 -554 451 -1034 967 -514 997 -514 445 -1036 967 -554 447 -1022 485 -1010 475 -1016 975 -518 977 -520 487 -1014 973 -552 451 -1040 441 -1050 447 -1022 485 -1014 987 -516 479 -1014 483 -1014 459 -1046 969 -514 449 -1044 967 -546 973 -488 447 -1016 443 -1000 973 -490 475 -980 983 -482 441 -1016 465 -976 1475 -23652 1451 -1548 479 -1014 461 -1014 471 -1044 975 -520 971 -502 495 -1012 977 -506 1005 -498 989 -516 481 -1016 975 -520 981 -514 475 -1014 979 -522 983 -512 475 -1022 965 -514 471 -1046 973 -494 473 -1016 475 -1046 447 -1050 463 -1012 999 -512 481 -1012 983 -520 477 -1014 977 -524 955 -548 973 -512 975 -520 967 -556 449 -1020 483 -1012 983 -520 973 -516 481 -1008 473 -1034 +RAW_Data: 967 -538 963 -544 973 -522 471 -1006 989 -512 1007 -520 443 -1036 985 -516 449 -1048 451 -1022 483 -1012 983 -520 977 -514 481 -1012 979 -514 483 -1022 481 -1010 471 -1020 479 -1020 979 -524 457 -1048 973 -514 483 -1012 981 -520 483 -1018 481 -1014 485 -986 467 -980 981 -486 469 -978 457 -1004 963 -480 983 -486 971 -514 1441 -23704 1383 -1628 389 -1112 385 -1092 407 -1092 915 -552 941 -570 441 -1064 423 -1046 451 -1044 939 -556 455 -1048 945 -552 973 -522 453 -1046 945 -552 947 -550 451 -1040 969 -518 479 -1028 951 -552 451 -1018 479 -1018 483 -1014 459 -1044 971 -514 483 -1010 971 -544 447 -1020 977 -524 987 -518 973 -516 979 -524 985 -518 479 -1016 447 -1050 953 -548 971 -514 483 -1014 459 -1048 967 -514 977 -526 953 -548 443 -1046 975 -492 995 -512 471 -1050 943 -552 445 -1032 455 -1044 449 -1048 941 -550 945 -552 449 -1050 945 -552 451 -1044 449 -1018 479 -1016 479 -1002 969 -542 973 -522 455 -1040 477 -1022 967 -534 959 -514 975 -554 469 -1008 449 -980 469 -1008 943 -484 1001 -484 467 -980 983 -482 961 -514 1439 -23700 1469 -1510 495 -1008 473 -1036 463 -1012 969 -546 973 -522 473 -1018 479 -1014 975 -526 955 -516 475 -1046 975 -490 999 -518 481 -1014 975 -520 967 -514 481 -1022 979 -524 457 -1048 971 -514 481 -1010 485 -1020 477 -1014 479 -1000 1001 -522 451 -1020 977 -520 473 -1032 967 -538 959 -514 1005 -522 965 -504 989 -514 475 -1046 441 -1050 971 -514 975 -520 473 -1018 481 -1014 979 -520 983 -520 977 -516 485 -1010 979 -544 975 -518 453 -1042 981 -520 453 -1024 483 -1010 457 -1050 975 -512 975 -524 459 -1048 973 -514 481 -1010 473 -1016 479 -1016 477 -1036 967 -506 995 -512 965 -546 445 -1048 957 -516 1005 -512 445 -1046 979 -486 473 -980 979 -486 473 -980 981 -486 473 -980 485 -986 467 -976 1477 -142204 197 -1486 165 -198 165 -664 295 -232 99 -266 231 -166 3045 -100 13411 -3670 197 -498 131 -166 231 -198 165 -66 265 -134 129 -1062 431 -130 465 -134 13447 -3848 329 -100 163 -298 99 -164 463 -98 197 -98 131 -198 65 -296 493 -264 789 -66 7225 -12438 99 -164 463 -132 197 -630 65 -198 2487 -66 165 -100 10097 -6554 459 -664 297 -460 4925 -132 6063 -12078 497 -98 99 -200 97 -234 165 -298 1721 -134 265 -100 3035 -100 12081 -3674 231 -100 97 -200 97 -264 461 -100 99 -132 231 -100 97 -430 527 -200 231 -64 2081 -132 327 -100 529 -66 831 -66 3067 -4704 99 -5520 97 -496 67 -198 167 -498 693 -462 2341 -15926 65 -1392 659 -134 131 -298 165 -66 99 -298 4777 -4208 429 -66 diff --git a/lib/subghz/protocols/nice_flor_s.c b/lib/subghz/protocols/nice_flor_s.c index 411ceeacfd6..dd5521a6442 100644 --- a/lib/subghz/protocols/nice_flor_s.c +++ b/lib/subghz/protocols/nice_flor_s.c @@ -14,6 +14,9 @@ #define TAG "SubGhzProtocoNiceFlorS" +#define NICE_ONE_COUNT_BIT 72 +#define NICE_ONE_NAME "Nice One" + static const SubGhzBlockConst subghz_protocol_nice_flor_s_const = { .te_short = 500, .te_long = 1000, @@ -28,6 +31,7 @@ struct SubGhzProtocolDecoderNiceFlorS { SubGhzBlockGeneric generic; const char* nice_flor_s_rainbow_table_file_name; + uint64_t data; }; struct SubGhzProtocolEncoderNiceFlorS { @@ -77,6 +81,64 @@ const SubGhzProtocol subghz_protocol_nice_flor_s = { .encoder = &subghz_protocol_nice_flor_s_encoder, }; +// /** +// * Read bytes from rainbow table +// * @param p array[10] P0-P1|P2-P3-P4-P5-P6-P7-P8-P9-P10 +// * @return crc +// */ +// static uint32_t subghz_protocol_nice_one_crc(uint8_t* p) { +// uint8_t crc = 0; +// uint8_t crc_data = 0xff; +// for(uint8_t i = 4; i < 68; i++) { +// if(subghz_protocol_blocks_get_bit_array(p, i)) { +// crc = crc_data ^ 1; +// } else { +// crc = crc_data; +// } +// crc_data >>= 1; +// if((crc & 0x01)) { +// crc_data ^= 0x97; +// } +// } +// crc = 0; +// for(uint8_t i = 0; i < 8; i++) { +// crc <<= 1; +// if((crc_data >> i) & 0x01) crc = crc | 1; +// } +// return crc; +// } + +// /** +// * Read bytes from rainbow table +// * @param p array[10] P0-P1|P2-P3-P4-P5-P6-P7-XX-XX-XX +// * @param num_parcel parcel number 0..15 +// * @param hold_bit 0 - the button was only pressed, 1 - the button was held down +// */ +// static void subghz_protocol_nice_one_get_data(uint8_t* p, uint8_t num_parcel, uint8_t hold_bit) { +// uint8_t k = 0; +// uint8_t crc = 0; +// p[1] = (p[1] & 0x0f) | ((0x0f ^ (p[0] & 0x0F) ^ num_parcel) << 4); +// if(num_parcel < 4) { +// k = 0x8f; +// } else { +// k = 0x80; +// } + +// if(!hold_bit) { +// hold_bit = 0; +// } else { +// hold_bit = 0x10; +// } +// k = num_parcel ^ k; +// p[7] = k; +// p[8] = hold_bit ^ (k << 4); + +// crc = subghz_protocol_nice_one_crc(p); + +// p[8] |= crc >> 4; +// p[9] = crc << 4; +// } + /** * Read bytes from rainbow table * @param file_name Full path to rainbow table the file @@ -237,10 +299,14 @@ void subghz_protocol_decoder_nice_flor_s_feed(void* context, bool level, uint32_ subghz_protocol_nice_flor_s_const.te_delta) { //Found STOP bit instance->decoder.parser_step = NiceFlorSDecoderStepReset; - if(instance->decoder.decode_count_bit == - subghz_protocol_nice_flor_s_const.min_count_bit_for_found) { - instance->generic.data = instance->decoder.decode_data; + if((instance->decoder.decode_count_bit == + subghz_protocol_nice_flor_s_const.min_count_bit_for_found) || + (instance->decoder.decode_count_bit == NICE_ONE_COUNT_BIT)) { + instance->generic.data = instance->data; + instance->data = instance->decoder.decode_data; + instance->decoder.decode_data = instance->generic.data; instance->generic.data_count_bit = instance->decoder.decode_count_bit; + if(instance->base.callback) instance->base.callback(&instance->base, instance->base.context); } @@ -274,6 +340,11 @@ void subghz_protocol_decoder_nice_flor_s_feed(void* context, bool level, uint32_ } else { instance->decoder.parser_step = NiceFlorSDecoderStepReset; } + if(instance->decoder.decode_count_bit == + subghz_protocol_nice_flor_s_const.min_count_bit_for_found) { + instance->data = instance->decoder.decode_data; + instance->decoder.decode_data = 0; + } break; } } @@ -287,6 +358,7 @@ static void subghz_protocol_nice_flor_s_remote_controller( SubGhzBlockGeneric* instance, const char* file_name) { /* + * Protocol Nice Flor-S * Packet format Nice Flor-s: START-P0-P1-P2-P3-P4-P5-P6-P7-STOP * P0 (4-bit) - button positional code - 1:0x1, 2:0x2, 3:0x4, 4:0x8; * P1 (4-bit) - batch repetition number, calculated by the formula: @@ -307,6 +379,24 @@ static void subghz_protocol_nice_flor_s_remote_controller( * data => 0x1c5783607f7b3 key serial cnt * decrypt => 0x10436c6820444 => 0x1 0436c682 0444 * + * Protocol Nice One + * Generally repeats the Nice Flor-S protocol, but there are a few changes + * Packet format first 52 bytes repeat Nice Flor-S protocol + * The additional 20 bytes contain the code of the pressed button, + * the button hold bit and the CRC of the entire message. + * START-P0-P1-P2-P3-P4-P5-P6-P7-P8-P9-P10-STOP + * P7 (byte) - if (n<4) k=0x8f : k=0x80; P7= k^n; + * P8 (byte) - if (hold bit) b=0x00 : b=0x10; P8= b^(k<<4) | 4 hi bit crc + * P10 (4-bit) - 4 lo bit crc + * key+b crc + * data => 0x1724A7D9A522F 899 D6 hold bit = 0 - just pressed the button + * data => 0x1424A7D9A522F 8AB 03 hold bit = 1 - button hold + * + * A small button hold counter (0..15) is stored between each press, + * i.e. if 1 press of the button stops counter 6, then the next press + * of the button will start from the value 7 (hold bit = 0), 8 (hold bit = 1)... + * further up to 15 with overflow + * */ if(!file_name) { instance->cnt = 0; @@ -333,7 +423,15 @@ bool subghz_protocol_decoder_nice_flor_s_serialize( SubGhzRadioPreset* preset) { furi_assert(context); SubGhzProtocolDecoderNiceFlorS* instance = context; - return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); + bool res = subghz_block_generic_serialize(&instance->generic, flipper_format, preset); + if(instance->generic.data_count_bit == NICE_ONE_COUNT_BIT) { + if(res && + !flipper_format_write_uint32(flipper_format, "Data", (uint32_t*)&instance->data, 1)) { + FURI_LOG_E(TAG, "Unable to add Data"); + res = false; + } + } + return res; } bool subghz_protocol_decoder_nice_flor_s_deserialize(void* context, FlipperFormat* flipper_format) { @@ -344,11 +442,25 @@ bool subghz_protocol_decoder_nice_flor_s_deserialize(void* context, FlipperForma if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { break; } - if(instance->generic.data_count_bit != - subghz_protocol_nice_flor_s_const.min_count_bit_for_found) { + if((instance->generic.data_count_bit != + subghz_protocol_nice_flor_s_const.min_count_bit_for_found) && + (instance->generic.data_count_bit != NICE_ONE_COUNT_BIT)) { FURI_LOG_E(TAG, "Wrong number of bits in key"); break; } + if(instance->generic.data_count_bit == NICE_ONE_COUNT_BIT) { + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + uint32_t temp = 0; + if(!flipper_format_read_uint32(flipper_format, "Data", (uint32_t*)&temp, 1)) { + FURI_LOG_E(TAG, "Missing Data"); + break; + } + instance->data = (uint64_t)temp; + } + ret = true; } while(false); return ret; @@ -360,20 +472,33 @@ void subghz_protocol_decoder_nice_flor_s_get_string(void* context, FuriString* o subghz_protocol_nice_flor_s_remote_controller( &instance->generic, instance->nice_flor_s_rainbow_table_file_name); - uint32_t code_found_hi = instance->generic.data >> 32; - uint32_t code_found_lo = instance->generic.data & 0x00000000ffffffff; - - furi_string_cat_printf( - output, - "%s %dbit\r\n" - "Key:0x%lX%08lX\r\n" - "Sn:%05lX\r\n" - "Cnt:%04lX Btn:%02X\r\n", - instance->generic.protocol_name, - instance->generic.data_count_bit, - code_found_hi, - code_found_lo, - instance->generic.serial, - instance->generic.cnt, - instance->generic.btn); + + if(instance->generic.data_count_bit == NICE_ONE_COUNT_BIT) { + furi_string_cat_printf( + output, + "%s %dbit\r\n" + "Key:0x%013llX%llX\r\n" + "Sn:%05lX\r\n" + "Cnt:%04lX Btn:%02X\r\n", + NICE_ONE_NAME, + instance->generic.data_count_bit, + instance->generic.data, + instance->data, + instance->generic.serial, + instance->generic.cnt, + instance->generic.btn); + } else { + furi_string_cat_printf( + output, + "%s %dbit\r\n" + "Key:0x%013llX\r\n" + "Sn:%05lX\r\n" + "Cnt:%04lX Btn:%02X\r\n", + instance->generic.protocol_name, + instance->generic.data_count_bit, + instance->generic.data, + instance->generic.serial, + instance->generic.cnt, + instance->generic.btn); + } } From 39841bd5a9ad1d6b1f4d222ab0670be96aadc732 Mon Sep 17 00:00:00 2001 From: itsweekly <92674764+itsweekly@users.noreply.github.com> Date: Wed, 8 Feb 2023 18:28:34 +0100 Subject: [PATCH 393/824] Universal Projector Remote (#2343) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Georgii Surkov Co-authored-by: あく --- applications/main/infrared/infrared_cli.c | 2 +- .../infrared/scenes/infrared_scene_config.h | 1 + .../scenes/infrared_scene_universal.c | 12 +- .../infrared_scene_universal_projector.c | 86 ++ assets/resources/infrared/assets/projector.ir | 829 ++++++++++++++++++ documentation/UniversalRemotes.md | 7 + 6 files changed, 935 insertions(+), 2 deletions(-) create mode 100644 applications/main/infrared/scenes/infrared_scene_universal_projector.c create mode 100644 assets/resources/infrared/assets/projector.ir diff --git a/applications/main/infrared/infrared_cli.c b/applications/main/infrared/infrared_cli.c index 5f5e2d4bbdb..3fa99cb02f3 100644 --- a/applications/main/infrared/infrared_cli.c +++ b/applications/main/infrared/infrared_cli.c @@ -86,7 +86,7 @@ static void infrared_cli_print_usage(void) { printf("\tir universal \r\n"); printf("\tir universal list \r\n"); // TODO: Do not hardcode universal remote names - printf("\tAvailable universal remotes: tv audio ac\r\n"); + printf("\tAvailable universal remotes: tv audio ac projector\r\n"); } static void infrared_cli_start_ir_rx(Cli* cli, FuriString* args) { diff --git a/applications/main/infrared/scenes/infrared_scene_config.h b/applications/main/infrared/scenes/infrared_scene_config.h index 111fd2d31b2..27eabe225f5 100644 --- a/applications/main/infrared/scenes/infrared_scene_config.h +++ b/applications/main/infrared/scenes/infrared_scene_config.h @@ -17,6 +17,7 @@ ADD_SCENE(infrared, universal, Universal) ADD_SCENE(infrared, universal_tv, UniversalTV) ADD_SCENE(infrared, universal_ac, UniversalAC) ADD_SCENE(infrared, universal_audio, UniversalAudio) +ADD_SCENE(infrared, universal_projector, UniversalProjector) ADD_SCENE(infrared, debug, Debug) ADD_SCENE(infrared, error_databases, ErrorDatabases) ADD_SCENE(infrared, rpc, Rpc) diff --git a/applications/main/infrared/scenes/infrared_scene_universal.c b/applications/main/infrared/scenes/infrared_scene_universal.c index 5043c9bd71d..4ef7c5c26d4 100644 --- a/applications/main/infrared/scenes/infrared_scene_universal.c +++ b/applications/main/infrared/scenes/infrared_scene_universal.c @@ -4,6 +4,7 @@ typedef enum { SubmenuIndexUniversalTV, SubmenuIndexUniversalAC, SubmenuIndexUniversalAudio, + SubmenuIndexUniversalProjector, } SubmenuIndex; static void infrared_scene_universal_submenu_callback(void* context, uint32_t index) { @@ -27,6 +28,12 @@ void infrared_scene_universal_on_enter(void* context) { SubmenuIndexUniversalAudio, infrared_scene_universal_submenu_callback, context); + submenu_add_item( + submenu, + "Projectors", + SubmenuIndexUniversalProjector, + infrared_scene_universal_submenu_callback, + context); submenu_add_item( submenu, "Air Conditioners", @@ -54,6 +61,9 @@ bool infrared_scene_universal_on_event(void* context, SceneManagerEvent event) { } else if(event.event == SubmenuIndexUniversalAudio) { scene_manager_next_scene(scene_manager, InfraredSceneUniversalAudio); consumed = true; + } else if(event.event == SubmenuIndexUniversalProjector) { + scene_manager_next_scene(scene_manager, InfraredSceneUniversalProjector); + consumed = true; } scene_manager_set_scene_state(scene_manager, InfraredSceneUniversal, event.event); } @@ -64,4 +74,4 @@ bool infrared_scene_universal_on_event(void* context, SceneManagerEvent event) { void infrared_scene_universal_on_exit(void* context) { Infrared* infrared = context; submenu_reset(infrared->submenu); -} +} \ No newline at end of file diff --git a/applications/main/infrared/scenes/infrared_scene_universal_projector.c b/applications/main/infrared/scenes/infrared_scene_universal_projector.c new file mode 100644 index 00000000000..c1df91c3440 --- /dev/null +++ b/applications/main/infrared/scenes/infrared_scene_universal_projector.c @@ -0,0 +1,86 @@ +#include "../infrared_i.h" + +#include "common/infrared_scene_universal_common.h" + +void infrared_scene_universal_projector_on_enter(void* context) { + infrared_scene_universal_common_on_enter(context); + + Infrared* infrared = context; + ButtonPanel* button_panel = infrared->button_panel; + InfraredBruteForce* brute_force = infrared->brute_force; + + infrared_brute_force_set_db_filename(brute_force, EXT_PATH("infrared/assets/projector.ir")); + + button_panel_reserve(button_panel, 2, 2); + uint32_t i = 0; + button_panel_add_item( + button_panel, + i, + 0, + 0, + 3, + 19, + &I_Power_25x27, + &I_Power_hvr_25x27, + infrared_scene_universal_common_item_callback, + context); + infrared_brute_force_add_record(brute_force, i++, "Power"); + button_panel_add_item( + button_panel, + i, + 1, + 0, + 36, + 19, + &I_Mute_25x27, + &I_Mute_hvr_25x27, + infrared_scene_universal_common_item_callback, + context); + infrared_brute_force_add_record(brute_force, i++, "Mute"); + button_panel_add_item( + button_panel, + i, + 0, + 1, + 3, + 66, + &I_Vol_up_25x27, + &I_Vol_up_hvr_25x27, + infrared_scene_universal_common_item_callback, + context); + infrared_brute_force_add_record(brute_force, i++, "Vol_up"); + button_panel_add_item( + button_panel, + i, + 1, + 1, + 36, + 66, + &I_Vol_down_25x27, + &I_Vol_down_hvr_25x27, + infrared_scene_universal_common_item_callback, + context); + infrared_brute_force_add_record(brute_force, i++, "Vol_dn"); + + button_panel_add_label(button_panel, 2, 11, FontPrimary, "Proj. remote"); + button_panel_add_label(button_panel, 17, 62, FontSecondary, "Volume"); + + view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical); + view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack); + + infrared_show_loading_popup(infrared, true); + bool success = infrared_brute_force_calculate_messages(brute_force); + infrared_show_loading_popup(infrared, false); + + if(!success) { + scene_manager_next_scene(infrared->scene_manager, InfraredSceneErrorDatabases); + } +} + +bool infrared_scene_universal_projector_on_event(void* context, SceneManagerEvent event) { + return infrared_scene_universal_common_on_event(context, event); +} + +void infrared_scene_universal_projector_on_exit(void* context) { + infrared_scene_universal_common_on_exit(context); +} diff --git a/assets/resources/infrared/assets/projector.ir b/assets/resources/infrared/assets/projector.ir new file mode 100644 index 00000000000..e9861de21f6 --- /dev/null +++ b/assets/resources/infrared/assets/projector.ir @@ -0,0 +1,829 @@ +Filetype: IR library file +Version: 1 +# +# Model: Smart +name: Power +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 8A 00 00 00 +# +# Model: Epson +name: Power +type: parsed +protocol: NECext +address: 83 55 00 00 +command: 90 6F 00 00 +# +# Model: Epson +name: Power +type: parsed +protocol: NECext +address: 81 03 00 00 +command: F0 0F 00 00 +# +# Model: Hitatchi +name: Power +type: parsed +protocol: NECext +address: 87 45 00 00 +command: 17 E8 00 00 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 310 27591 171 27662 241 27731 307 27575 107 27749 306 27551 130 55520 243 27614 217 55584 129 27743 119 27756 115 27747 163 27712 308 27502 243 27650 217 27732 175 27693 167 27698 166 27689 171 27622 215 27712 133 27658 216 27716 129 27732 162 27698 305 27571 131 27753 310 27570 170 27707 162 27707 175 10960 9194 4518 618 542 618 543 725 434 672 1623 671 1647 646 514 592 568 592 568 592 1702 592 568 592 567 593 1702 592 568 618 1676 618 1676 618 1676 618 543 617 543 617 543 617 1677 617 544 616 544 616 544 616 544 616 1678 616 1678 616 1678 616 544 616 1678 616 1679 615 1678 616 1678 616 40239 9196 2250 617 +# +name: Vol_up +type: parsed +protocol: NEC +address: 08 00 00 00 +command: 48 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 08 00 00 00 +command: 49 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 08 00 00 00 +command: 14 00 00 00 +# +name: Power +type: parsed +protocol: NEC +address: 08 00 00 00 +command: 0B 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 40 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 48 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 44 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 00 30 00 00 +command: 83 7C 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 00 30 00 00 +command: 82 7D 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 08 13 00 00 +command: 87 78 00 00 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9055 4338 672 1551 669 1553 618 1603 619 481 617 482 616 481 617 507 591 1605 645 479 619 1577 645 1578 644 1578 644 479 619 480 618 1581 641 480 617 1605 617 1606 616 1606 615 483 615 1608 614 484 614 484 614 484 614 484 614 484 614 484 614 1609 614 484 614 1609 614 1609 613 1609 613 40058 9000 2068 614 95467 9022 2068 614 +# +name: Mute +type: parsed +protocol: NECext +address: 87 4E 00 00 +command: 29 D6 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 87 4E 00 00 +command: 08 F7 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 87 4E 00 00 +command: 04 FB 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 83 55 00 00 +command: 93 6C 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 02 00 00 00 +command: 15 00 00 00 +# +name: Vol_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9032 4462 598 501 627 1604 627 530 598 531 677 423 706 422 706 421 707 451 677 1554 677 451 598 1633 598 1634 597 1634 598 1634 598 1634 625 1606 681 1550 626 502 598 530 599 529 600 1632 600 528 600 528 601 528 601 528 601 1631 600 1631 625 1607 625 504 625 1607 624 1608 624 1608 623 +# +name: Mute +type: parsed +protocol: NEC +address: 02 00 00 00 +command: 02 00 00 00 +# +name: Power +type: parsed +protocol: NEC +address: 02 00 00 00 +command: 1D 00 00 00 +# +# ON +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9096 4436 620 505 647 478 648 501 623 1599 647 1624 623 502 623 503 621 504 619 1628 618 507 617 507 617 1630 617 508 616 1630 617 1630 617 1631 616 508 616 508 617 508 616 1631 616 508 617 508 617 508 616 508 616 1630 616 1630 616 1631 616 508 616 1630 617 1630 617 1630 617 1631 617 509 616 508 616 509 616 509 616 509 616 509 615 509 616 508 617 1631 616 1631 615 1631 616 1631 616 1631 616 1631 616 1631 615 1631 616 14435 9093 2186 615 96359 9095 2184 617 +# +name: Vol_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9091 4465 594 530 595 530 594 530 594 1651 595 1652 595 529 621 504 620 504 619 1628 618 507 617 508 616 1631 616 509 615 1631 616 1631 616 1632 615 509 616 509 616 509 615 1631 616 509 616 508 616 1631 616 509 616 1631 615 1631 616 1631 617 508 616 1631 616 1631 616 508 616 1631 617 508 617 509 616 509 616 509 616 509 616 509 616 509 616 509 616 1631 616 1631 616 1631 616 1631 616 1631 615 1631 615 1631 615 1631 616 14435 9090 2190 615 +# +name: Vol_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9092 4439 620 506 619 506 618 530 593 1627 620 1630 643 504 620 505 618 506 617 1630 617 508 616 508 616 1632 616 508 617 1631 616 1631 616 1631 616 1631 616 509 616 508 616 1631 616 509 616 509 615 1632 616 509 616 508 616 1631 616 1631 616 508 616 1631 615 1631 616 509 615 1632 615 509 616 509 616 509 616 509 616 509 616 510 615 509 616 509 616 1631 616 1631 615 1631 616 1631 615 1631 615 1631 615 1631 615 1631 615 14434 9088 2191 615 96339 9115 2189 616 96343 9117 2189 616 96343 9114 2189 616 +# AV-Mute +name: Mute +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9092 4439 620 506 618 506 618 530 594 1627 619 1629 643 505 619 505 619 506 617 1629 617 508 616 508 616 1631 616 508 616 1630 616 1630 616 1630 617 1630 616 1630 616 1631 616 508 616 508 616 508 616 1631 616 508 617 508 616 508 616 508 616 1630 616 1631 615 1631 616 508 616 1631 616 508 617 508 616 509 615 509 616 508 616 509 615 509 616 508 616 1631 615 1631 615 1631 616 1631 615 1631 615 1631 615 1631 615 1631 616 14433 9088 2191 615 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9014 4332 661 1570 661 471 660 473 658 474 657 476 655 498 633 498 634 502 633 499 633 1599 632 1599 632 1599 632 1599 632 1599 632 1600 631 1603 632 500 632 501 631 501 631 501 631 501 631 501 631 1601 631 504 631 1601 631 1601 631 1601 631 1601 631 1601 630 1601 630 501 631 1601 631 38177 8983 2149 630 +# +name: Vol_up +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 11 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 4C 00 00 00 +# +name: Mute +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9042 4306 690 1541 665 468 664 468 664 469 663 470 662 471 660 495 636 499 636 497 634 1597 634 1598 633 1598 633 1599 633 1599 632 1599 633 1603 632 1599 633 499 633 499 633 500 632 499 633 500 632 1600 632 503 633 500 632 1600 632 1600 632 1600 633 1600 632 1600 632 500 632 1600 632 37912 8986 2145 633 +# ON +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3522 1701 472 426 444 1269 472 426 444 426 443 427 443 427 443 426 444 427 443 426 444 427 442 428 441 429 440 431 438 1304 437 433 437 433 438 433 437 433 437 434 436 434 436 434 436 434 436 434 436 1305 436 434 436 434 436 434 436 1305 436 434 436 434 436 1305 436 435 435 435 435 435 435 435 435 435 435 435 435 435 435 459 411 459 411 459 411 1330 411 1330 411 1330 411 1330 411 1330 411 460 410 459 411 459 411 1330 411 1330 411 460 410 1330 411 1330 411 1331 410 1330 411 74392 3516 1736 436 433 437 1304 437 433 437 433 437 433 437 433 437 433 437 434 436 433 437 434 436 434 436 434 436 434 436 1305 436 434 436 434 436 434 436 434 436 434 436 434 436 434 436 434 436 434 436 1305 436 434 436 434 436 435 435 1305 436 435 435 435 435 1306 435 435 435 435 435 435 435 436 434 436 434 436 434 435 435 436 434 436 434 436 434 1330 411 1331 410 1330 411 1330 411 1330 411 459 411 460 410 460 410 1331 410 1331 410 460 410 1331 410 1331 410 1331 410 1331 410 74392 3515 1736 437 433 437 1304 437 433 437 433 437 434 436 433 437 434 436 433 437 434 436 434 436 434 436 434 436 434 436 1305 436 434 436 434 436 434 436 434 436 434 436 434 436 434 436 434 436 434 436 1305 436 434 436 435 436 434 436 1306 435 435 435 435 435 1306 435 435 435 435 435 435 435 435 435 435 435 436 434 436 434 435 435 436 434 435 435 1306 435 1330 411 1307 434 1331 410 1308 433 436 434 436 434 460 410 1331 410 1331 410 460 410 1331 410 1331 410 1331 410 1331 410 74392 3515 1736 437 433 437 1304 437 434 436 433 437 434 436 433 437 434 436 434 436 434 436 434 436 434 436 434 436 434 436 1305 436 434 436 434 436 434 436 435 435 434 436 434 436 434 436 434 436 434 436 1306 435 435 435 435 435 435 435 1306 435 435 435 436 434 1306 435 435 435 436 434 436 434 435 435 436 434 436 434 460 410 460 410 460 410 460 410 1331 410 1331 410 1331 410 1331 410 1331 410 460 410 460 410 460 410 1331 410 1331 410 460 410 1331 410 1331 410 1331 410 1331 410 74392 3515 1736 437 433 437 1304 437 433 437 434 436 434 436 433 437 434 436 434 436 434 436 434 436 434 436 434 436 434 436 1305 436 434 436 434 436 434 436 434 436 434 436 434 436 435 435 435 435 434 436 1306 435 434 436 435 435 435 435 1306 435 436 434 435 435 1306 435 435 435 436 434 436 434 436 434 436 434 460 410 437 433 459 411 460 410 460 410 1331 410 1331 410 1331 410 1331 410 1331 410 460 410 460 410 460 410 1331 410 1331 410 460 410 1331 410 1331 410 1331 410 1331 410 74393 3514 1736 437 434 436 1304 437 433 437 434 436 433 437 434 436 433 437 434 436 434 436 434 436 434 436 434 436 434 436 1305 436 434 436 434 436 434 436 434 436 435 435 434 436 434 436 435 435 434 436 1305 436 435 435 435 435 435 435 1306 435 435 435 435 435 1306 435 435 435 436 434 435 435 459 411 436 434 435 435 459 411 459 411 459 411 459 411 1330 411 1306 435 1330 411 1330 411 1331 410 460 410 460 410 460 410 1331 410 1331 410 460 410 1331 410 1331 410 1331 410 1331 410 +# ON +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 529 7218 126 6585 219 703 180 5362 427 18618 177 +# +name: Vol_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9069 4362 622 486 621 487 621 491 622 1608 623 1603 622 487 621 487 621 491 622 1604 621 487 622 491 622 1604 621 491 622 1608 622 1609 621 1604 622 486 622 487 621 491 621 1605 621 487 621 491 622 1604 622 491 621 1609 621 1609 621 1604 622 491 621 1609 622 1604 621 491 621 1604 622 487 621 487 622 486 622 487 621 488 621 487 621 488 620 491 621 1609 622 1609 620 1609 621 1609 621 1609 621 1609 621 1609 621 1618 621 14330 9047 2137 620 +# +name: Vol_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9047 4362 621 486 622 463 645 490 622 1609 622 1604 622 487 620 487 621 491 622 1604 622 484 625 490 621 1605 649 463 621 1609 620 1611 621 1608 622 1605 621 486 622 491 622 1604 621 487 621 492 620 1604 621 488 621 492 620 1609 622 1604 621 492 622 1609 620 1605 621 491 622 1603 622 488 621 488 620 488 620 488 621 488 620 487 622 485 621 492 596 1635 621 1609 622 1585 643 1611 620 1608 621 1610 619 1611 620 1619 619 14332 9074 2109 647 +# +name: Mute +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9073 4336 648 461 647 484 624 489 623 1607 623 1603 622 486 622 486 622 491 622 1604 621 487 621 491 622 1604 622 491 621 1609 621 1609 621 1609 621 1608 622 1609 621 1604 621 486 622 486 622 491 622 1604 622 486 622 487 621 487 621 491 622 1608 622 1609 621 1604 622 491 621 1604 621 487 621 486 622 487 621 487 621 487 621 487 621 487 621 491 622 1608 622 1608 622 1609 621 1608 622 1608 622 1608 622 1609 621 1617 622 14330 9047 2137 620 +# ON +name: Power +type: parsed +protocol: NECext +address: 83 F4 00 00 +command: 4F B0 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 80 19 00 00 +command: 10 EF 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 80 19 00 00 +command: 1C E3 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 80 19 00 00 +command: 46 B9 00 00 +# +name: Power +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 51 00 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 40 40 00 00 +command: 0A F5 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 00 30 00 00 +command: 4E B1 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 00 30 00 00 +command: 0E F1 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 00 30 00 00 +command: 0D F2 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 00 30 00 00 +command: 4F B0 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 00 30 00 00 +command: 14 EB 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 08 16 00 00 +command: 87 78 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 08 16 00 00 +command: C8 37 00 00 +# +name: Power +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 01 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 02 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 28 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 29 00 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 84 F4 00 00 +command: 0B F4 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 33 00 00 00 +command: 00 FF 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 33 00 00 00 +command: 1E E1 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 33 00 00 00 +command: 1D E2 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 33 00 00 00 +command: 0B F4 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 83 55 00 00 +command: 90 6F 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 83 55 00 00 +command: 99 66 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 83 55 00 00 +command: 98 67 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 00 DF 00 00 +command: 1C E3 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 00 DF 00 00 +command: 4F B0 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 00 DF 00 00 +command: 4B B4 00 00 +# +name: Power +type: parsed +protocol: NEC +address: 32 00 00 00 +command: 02 00 00 00 +# +name: Power +type: parsed +protocol: NEC +address: 32 00 00 00 +command: 2E 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 32 00 00 00 +command: 52 00 00 00 +# +name: Power +type: parsed +protocol: NEC +address: 20 00 00 00 +command: 41 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 20 00 00 00 +command: 51 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 20 00 00 00 +command: 56 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 20 00 00 00 +command: 5A 00 00 00 +# +name: Power +type: parsed +protocol: SIRC15 +address: 54 00 00 00 +command: 15 00 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 83 F4 00 00 +command: 82 7D 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 83 F4 00 00 +command: 83 7C 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 83 F4 00 00 +command: 14 EB 00 00 +# +name: Power +type: parsed +protocol: NEC +address: 31 00 00 00 +command: 91 00 00 00 +# +name: Power +type: parsed +protocol: NEC +address: 31 00 00 00 +command: 90 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 31 00 00 00 +command: D0 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 31 00 00 00 +command: 89 00 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 86 00 00 00 +command: 00 00 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 86 00 00 00 +command: 30 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 86 00 00 00 +command: 31 00 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 86 00 00 00 +command: 32 00 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 30 00 00 00 +command: 00 00 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 87 4E 00 00 +command: 0D 00 00 00 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9032 4479 597 560 572 558 564 566 566 1666 589 1671 594 562 570 560 562 568 564 1669 596 560 562 568 564 1669 596 560 562 1671 594 1666 588 1671 594 562 570 560 562 568 564 1669 596 560 562 568 564 566 566 563 569 1664 591 1669 596 1664 590 565 567 1667 598 1661 593 1666 588 1671 594 562 570 560 562 568 564 565 567 563 569 560 562 568 564 565 567 1666 588 1671 594 1665 589 1670 595 1665 590 1669 596 1664 590 1668 597 13983 9029 2222 599 96237 9030 2221 589 96244 9034 2217 594 96244 9033 2218 592 96249 9038 2213 597 96239 9037 2214 596 96238 9028 2223 598 96221 9032 2215 595 +# +name: Vol_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9034 4482 593 563 569 561 571 559 563 1698 566 1694 570 559 563 568 564 566 566 1695 569 560 572 559 563 1671 593 563 569 1692 562 1671 593 1693 571 558 564 567 565 565 567 1693 571 532 590 567 565 1695 569 560 562 1698 566 1694 570 1663 591 539 593 1693 571 1688 566 564 568 1691 563 567 565 565 567 563 569 561 571 559 563 567 565 565 567 563 569 1690 564 1695 569 1691 563 1696 568 1691 563 1697 567 1692 562 1697 567 13988 9030 2223 597 96250 9035 2219 591 96245 9032 2221 589 96240 9038 2215 595 96235 9033 2220 590 +# +name: Vol_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9028 4482 593 563 569 561 571 558 564 1696 568 1690 564 566 566 563 569 561 571 1688 566 563 569 561 571 1688 566 563 569 1690 564 1695 569 1689 565 1668 596 560 562 568 564 1695 569 560 562 568 564 1695 569 560 562 568 564 1695 569 1690 564 566 566 1692 572 1687 567 563 569 1690 564 566 566 564 568 562 570 559 563 567 565 565 567 562 570 560 562 1696 568 1665 589 1670 594 1665 589 1670 594 1664 590 1669 647 1612 590 13987 9031 2220 590 96223 9033 2217 593 96223 9034 2218 592 96225 9032 2219 591 96221 9087 2164 595 +# +name: Mute +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9031 4479 596 560 572 558 564 566 566 1693 571 1688 566 563 569 561 571 559 563 1696 568 561 571 559 563 1697 567 562 570 1689 565 1694 570 1688 566 1693 571 1661 593 1693 571 558 564 566 566 564 568 1691 563 541 591 564 568 562 570 560 562 1697 567 1692 562 1696 568 562 570 1689 565 564 568 561 571 559 563 567 565 564 568 562 570 560 562 567 565 1694 570 1689 565 1694 570 1688 566 1693 571 1688 566 1693 571 1662 592 13987 9031 2220 590 96231 9034 2217 593 96234 9030 2222 588 96247 9037 2215 595 +# +name: Vol_up +type: parsed +protocol: NEC +address: 32 00 00 00 +command: 11 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 32 00 00 00 +command: 14 00 00 00 +# OFF +name: Power +type: parsed +protocol: NECext +address: 83 F4 00 00 +command: 4E B1 00 00 +# +name: Power +type: parsed +protocol: NEC +address: 03 00 00 00 +command: 1D 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 03 00 00 00 +command: 11 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 03 00 00 00 +command: 15 00 00 00 +# OFF +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9075 4307 677 433 675 456 651 461 651 1579 650 1576 649 459 649 460 648 465 648 1578 647 461 622 491 622 1604 647 465 647 1583 622 1608 647 1579 647 461 647 466 622 1604 647 465 647 1579 647 461 645 463 648 465 648 1583 646 1580 646 466 647 1579 622 491 647 1583 622 1608 647 1579 647 461 647 461 622 486 622 486 647 461 647 462 646 462 622 491 646 1584 622 1608 647 1584 621 1608 647 1583 646 1584 647 1584 646 1592 622 14330 9047 2137 621 +# +name: Power +type: parsed +protocol: Samsung32 +address: 07 00 00 00 +command: E6 00 00 00 +# +name: Vol_up +type: parsed +protocol: Samsung32 +address: 07 00 00 00 +command: 07 00 00 00 +# +name: Vol_dn +type: parsed +protocol: Samsung32 +address: 07 00 00 00 +command: 0B 00 00 00 +# +name: Mute +type: parsed +protocol: Samsung32 +address: 07 00 00 00 +command: 0F 00 00 00 +# OFF +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3523 1701 472 426 444 1269 472 426 444 426 442 429 443 427 443 426 444 426 444 426 443 427 442 429 440 430 439 432 438 1304 437 433 437 432 438 432 438 433 437 433 437 433 437 433 437 433 437 433 437 1304 437 433 437 433 437 433 437 1304 437 433 437 433 437 1304 437 433 437 434 436 433 437 434 436 434 436 434 436 433 437 433 437 434 436 1304 437 1305 436 1305 436 1305 436 1305 436 1305 436 434 436 434 436 1305 436 1305 436 1305 436 434 436 1305 436 1305 436 1306 435 1306 435 74393 3515 1736 437 433 437 1304 437 433 437 433 437 433 437 433 437 433 437 433 437 433 437 434 436 433 437 434 436 434 436 1304 437 434 436 434 436 434 436 434 436 434 436 434 436 434 436 434 436 434 436 1305 436 434 436 434 436 434 436 1305 436 434 436 434 436 1306 435 435 435 435 435 435 435 435 435 435 435 435 435 435 435 436 434 435 435 1307 434 1331 410 1307 434 1307 434 1330 411 1307 434 460 410 460 410 1331 410 1331 410 1331 410 460 410 1331 410 1331 410 1331 410 1331 410 74393 3515 1736 437 433 437 1304 437 433 437 433 437 433 437 433 437 433 437 433 437 433 437 434 436 434 436 433 437 433 437 1304 437 434 436 434 436 434 437 434 436 434 436 434 436 434 436 434 436 434 436 1305 436 434 436 434 436 434 436 1305 436 435 435 434 436 1305 436 434 436 435 435 435 435 435 435 435 435 435 435 435 435 435 435 435 435 1307 434 1306 435 1307 434 1307 434 1307 434 1331 410 460 410 460 410 1331 410 1331 410 1331 410 460 410 1331 410 1331 410 1331 410 1331 410 74393 3515 1736 437 433 437 1304 437 433 437 433 437 433 437 433 437 433 437 433 437 433 437 433 437 433 437 434 436 433 437 1304 437 433 437 434 436 434 436 434 436 434 436 434 436 434 436 434 436 434 437 1305 436 434 436 434 436 434 436 1305 436 434 436 434 436 1306 435 435 435 435 435 435 435 435 435 435 435 435 435 435 435 435 435 435 435 1307 434 1330 411 1330 411 1330 411 1330 411 1330 411 460 410 460 410 1331 410 1331 410 1331 410 460 410 1331 410 1331 410 1331 410 1331 410 +# OFF +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9093 4441 620 507 618 530 594 531 593 1652 595 1653 620 505 620 505 619 506 617 1630 616 508 616 508 616 1632 615 509 615 1631 616 1632 615 1632 615 510 615 509 615 1632 615 509 615 1632 615 510 615 510 614 509 615 1632 614 1633 614 509 615 1633 614 509 615 1632 615 1632 614 1633 614 510 614 510 615 510 615 510 614 510 614 510 615 510 615 510 614 1632 615 1632 614 1632 615 1632 615 1632 615 1632 615 1632 615 1633 614 14439 9088 2192 614 96349 9112 2190 616 +# OFF +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 243 27700 170 27632 246 27694 282 27595 307 27497 241 27696 177 27710 164 27644 245 27629 246 27712 174 27638 211 27736 131 27741 306 27504 214 27727 135 27749 132 27761 126 27744 131 27753 127 27764 121 27767 132 27773 307 27577 131 27706 213 27761 129 27759 128 27770 125 27694 213 27751 307 27578 131 27737 131 27745 304 27575 335 27540 124 27752 132 27749 132 27747 134 27757 134 27758 127 27762 131 27748 131 27750 122 27749 130 27748 125 27772 131 27774 136 27762 135 27686 215 27742 131 27749 132 27756 133 27764 126 24073 9255 4460 672 488 618 541 619 541 619 1675 619 1676 618 542 618 542 618 542 618 1676 618 542 618 543 617 1678 616 568 592 1702 592 1702 592 1703 617 543 617 543 617 1677 617 543 617 1678 615 544 616 544 616 544 616 1678 616 1679 615 544 616 1679 615 545 615 1679 615 1679 615 1679 615 40240 9173 2273 591 +# +name: Vol_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 219 27658 217 27663 216 27658 216 27634 216 27642 215 27646 217 27662 217 27637 216 27649 216 27649 218 27656 217 27658 215 27640 214 27636 217 27649 216 27644 218 27635 217 27630 215 27645 216 27631 215 27632 216 27650 216 27628 217 27630 214 27627 217 27623 215 27632 215 27641 216 27634 214 27633 215 27648 215 27648 217 27651 215 27635 216 27629 216 27630 216 2021 9254 4461 618 542 618 542 618 542 618 1675 619 1676 618 541 619 541 619 542 618 1677 617 543 617 543 617 1678 616 568 592 1702 592 1702 618 1676 618 542 618 542 618 543 617 1677 617 543 617 544 616 1678 616 544 616 1678 616 1678 616 1678 616 544 616 1678 616 1678 616 544 616 1678 616 40239 9200 2247 617 99930 110 27739 119 27738 123 27750 126 27738 175 27617 214 27716 203 27604 213 27639 217 27631 214 27722 136 27753 119 27736 175 27618 246 27683 177 27619 245 27685 171 55486 244 27693 158 27635 241 27695 170 27693 129 27717 340 27530 113 27757 106 27751 124 27728 172 27707 126 27666 215 27708 123 27733 123 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 18 E9 00 00 +command: 49 B6 00 00 +# +name: Power +type: parsed +protocol: NEC +address: 02 00 00 00 +command: 14 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 02 00 00 00 +command: 48 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 02 00 00 00 +command: 40 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 02 00 00 00 +command: 18 00 00 00 +# +name: Power +type: parsed +protocol: NECext +address: B8 57 00 00 +command: 0C F3 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: B8 57 00 00 +command: 0D F2 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: B8 57 00 00 +command: 1E E1 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: B8 57 00 00 +command: 1F E0 00 00 +# +name: Power +type: parsed +protocol: NEC +address: 32 00 00 00 +command: 81 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 32 00 00 00 +command: 8F 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 32 00 00 00 +command: 8C 00 00 00 +# +name: Mute +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9066 4428 608 507 609 1622 609 507 609 507 609 1623 608 1623 609 507 609 506 610 1623 609 507 609 1622 610 1623 608 507 609 506 610 1622 609 1623 609 506 610 1622 610 506 610 1623 637 478 690 425 638 478 637 1594 637 1594 664 451 636 1594 610 506 610 1621 611 1621 610 1621 610 505 611 40183 9065 2156 637 95953 9037 2185 608 +# +name: Power +type: parsed +protocol: NEC +address: 00 00 00 00 +command: A8 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 88 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 9C 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 8C 00 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 87 45 00 00 +command: 17 E8 00 00 +# +name: Vol_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9064 4354 666 1559 666 1562 662 1586 638 475 636 477 635 477 635 478 635 1590 635 1591 634 478 635 1591 634 478 634 478 635 478 634 1591 635 478 634 1591 634 478 635 478 634 478 635 1591 634 478 634 1591 635 478 634 478 634 1591 634 1591 635 1591 634 478 635 1591 634 478 634 1591 635 40957 9035 2144 634 95483 9047 2155 632 95484 9048 2153 633 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 87 45 00 00 +command: 50 AF 00 00 +# +name: Mute +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9034 4385 638 1587 664 1562 663 1587 637 476 635 478 634 478 635 478 635 1591 634 1591 634 478 635 1591 635 478 634 478 635 478 635 1591 635 478 634 478 634 1591 634 478 635 479 634 1591 635 478 634 1591 635 478 634 1592 634 478 634 1591 635 1591 635 478 634 1592 634 478 634 1591 634 40958 9033 2144 635 +# +name: Power +type: parsed +protocol: NECext +address: FF FF 00 00 +command: E8 17 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: FF FF 00 00 +command: BD 42 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: FF FF 00 00 +command: F2 0D 00 00 +# +name: Power +type: parsed +protocol: Kaseikyo +address: 41 54 32 00 +command: 05 00 00 00 +# +name: Vol_up +type: parsed +protocol: Kaseikyo +address: 41 54 32 00 +command: 70 01 00 00 +# +name: Vol_dn +type: parsed +protocol: Kaseikyo +address: 41 54 32 00 +command: 71 01 00 00 +# +name: Power +type: parsed +protocol: NEC +address: 31 00 00 00 +command: 81 00 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 83 F4 00 00 +command: 17 E8 00 00 +# +name: Vol_up +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9010 4413 532 1617 532 1617 533 489 533 489 533 489 558 464 558 465 557 1593 557 465 557 466 556 1594 555 467 555 1595 529 1621 554 1595 581 1569 581 441 581 1569 581 441 581 441 581 441 581 441 581 441 581 1569 581 1569 581 441 581 1569 580 1569 580 1570 580 1595 554 1595 555 468 554 42156 8983 2135 556 +# +name: Vol_dn +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9032 4390 556 1592 559 1591 559 463 559 463 558 464 557 465 556 465 557 1593 583 440 581 441 580 1569 581 441 581 1569 580 1569 581 1569 581 1570 580 1596 554 1596 554 468 554 468 554 468 554 442 580 442 580 1596 554 469 553 469 553 1596 554 1596 553 1597 550 1598 553 1598 552 469 551 42155 9008 2107 531 95218 9006 2108 582 +# +name: Mute +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9011 4388 557 1617 532 1617 532 489 533 489 558 464 558 440 582 440 582 1593 556 466 556 466 556 1594 556 467 555 1595 555 1595 529 1620 554 1596 554 467 554 468 555 1595 579 443 581 1569 581 441 581 441 580 442 581 1569 581 1569 581 441 581 1569 580 441 581 1569 581 1569 581 1570 579 42152 8957 2159 556 \ No newline at end of file diff --git a/documentation/UniversalRemotes.md b/documentation/UniversalRemotes.md index 264829e1657..325f640d7e0 100644 --- a/documentation/UniversalRemotes.md +++ b/documentation/UniversalRemotes.md @@ -25,6 +25,13 @@ Make sure that every signal does what it's supposed to. If everything checks out, append these signals **to the end** of the [audio player universal remote file](/assets/resources/infrared/assets/audio.ir). +## Projectors + +Adding your projector to the universal remote is really simple. Up to 4 signals can be recorded: `Power`, `Mute`, `Vol_up`, `Vol_dn`. Any of them can be omitted if not supported by your projector. +To save time, please make sure every recording has been named accordingly. +In case of omitting, on most projectors with the 4 following buttons, you should not have a problem. + + ## Air conditioners Air conditioners differ from most other infrared-controlled devices because their state is tracked by the remote. From db1a8f8014ebff5243ac624d0c7c62614e3fb428 Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Wed, 8 Feb 2023 21:47:39 +0400 Subject: [PATCH 394/824] [FL-3099] SubGhz: add protocol KingGates Stylo4k (#2368) * [FL-3099] SubGhz: add protocol KingGates Stylo4k * SubGhz: add unit test file * f7: api: reverted symbols Co-authored-by: hedger Co-authored-by: Aleksandr Kutuzov --- .../debug/unit_tests/subghz/subghz_test.c | 11 +- assets/resources/subghz/assets/keeloq_mfcodes | 105 +++--- .../subghz/kinggates_stylo4k_raw.sub | 11 + assets/unit_tests/subghz/test_random_raw.sub | 6 + lib/subghz/protocols/kinggates_stylo_4k.c | 336 ++++++++++++++++++ lib/subghz/protocols/kinggates_stylo_4k.h | 74 ++++ lib/subghz/protocols/protocol_items.c | 1 + lib/subghz/protocols/protocol_items.h | 1 + 8 files changed, 492 insertions(+), 53 deletions(-) create mode 100644 assets/unit_tests/subghz/kinggates_stylo4k_raw.sub create mode 100644 lib/subghz/protocols/kinggates_stylo_4k.c create mode 100644 lib/subghz/protocols/kinggates_stylo_4k.h diff --git a/applications/debug/unit_tests/subghz/subghz_test.c b/applications/debug/unit_tests/subghz/subghz_test.c index 97629efea94..c7e9c96f1d1 100644 --- a/applications/debug/unit_tests/subghz/subghz_test.c +++ b/applications/debug/unit_tests/subghz/subghz_test.c @@ -14,7 +14,7 @@ #define NICE_FLOR_S_DIR_NAME EXT_PATH("subghz/assets/nice_flor_s") #define ALUTECH_AT_4N_DIR_NAME EXT_PATH("subghz/assets/alutech_at_4n") #define TEST_RANDOM_DIR_NAME EXT_PATH("unit_tests/subghz/test_random_raw.sub") -#define TEST_RANDOM_COUNT_PARSE 317 +#define TEST_RANDOM_COUNT_PARSE 329 #define TEST_TIMEOUT 10000 static SubGhzEnvironment* environment_handler; @@ -637,6 +637,14 @@ MU_TEST(subghz_decoder_nice_one_test) { "Test decoder " SUBGHZ_PROTOCOL_NICE_FLOR_S_NAME " error\r\n"); } +MU_TEST(subghz_decoder_kinggates_stylo4k_test) { + mu_assert( + subghz_decoder_test( + EXT_PATH("unit_tests/subghz/kinggates_stylo4k_raw.sub"), + SUBGHZ_PROTOCOL_KINGGATES_STYLO_4K_NAME), + "Test decoder " SUBGHZ_PROTOCOL_KINGGATES_STYLO_4K_NAME " error\r\n"); +} + //test encoders MU_TEST(subghz_encoder_princeton_test) { mu_assert( @@ -837,6 +845,7 @@ MU_TEST_SUITE(subghz) { MU_RUN_TEST(subghz_decoder_dooya_test); MU_RUN_TEST(subghz_decoder_alutech_at_4n_test); MU_RUN_TEST(subghz_decoder_nice_one_test); + MU_RUN_TEST(subghz_decoder_kinggates_stylo4k_test); MU_RUN_TEST(subghz_encoder_princeton_test); MU_RUN_TEST(subghz_encoder_came_test); diff --git a/assets/resources/subghz/assets/keeloq_mfcodes b/assets/resources/subghz/assets/keeloq_mfcodes index 1b27bfb01fb..3eedc564f27 100644 --- a/assets/resources/subghz/assets/keeloq_mfcodes +++ b/assets/resources/subghz/assets/keeloq_mfcodes @@ -1,55 +1,56 @@ Filetype: Flipper SubGhz Keystore File Version: 0 Encryption: 1 -IV: AA FF DE 54 A1 BB F1 21 83 46 FE 2A 1E B7 3D 33 -95B8CD65BBAC95EACE67CA94F679B82877A921396D461ECB479722F8A369454A -61065C41297B9FF8F8168814F49A03D1FE7B4CB79DFFCBBF0402AAA6A2211E84 -A1557AC139188FF105D1081A4B688C5CA440FB5DA7F40901B541120AD08A544F -AF0A6056D7F0D97DAD6C16C4E63204E4B3B1C5A20AC82B983B516F4F718EE29F -6861BFAE46A1AADB1DB2D6DFAA7E39D21D5B3E46A41BD50F4F2828879EB328EF0A406F2B9C79A031AB361257E6D69756 -0DDB3DAC53678541981CC46C22CED245CBA314C9BBE1BA9383B8505B75AC5E40 -99AB5D9404934F2D257ED04D9F8CCEE06D00F38157B121AFD63101E4E5C08268 -5114A6C42B342C7D933A76F9052FF963C2047E85EA524497C21B4C35C38EF6E7 -88CA2A1907D94B972FF93DBB9B88CB576F3E1BB0FE8F85A5B2CCA7D44B00374D -349C4153FE7CA8AE044E9F75F77D9694304474CE3F127CF968662B5F78A7F421 -62AA02E20CA7E691EFC0B55CA41C9BDF889FB23868289284241CD31AA1A0E499AE2A770B6B5AB3170CDCCDB8A246D36C -97901B5EB76228ADF8E5073F1BAB1502878DEFF1C4EBF12A43D105556CB7E80F947A8BD7831666BD838C57CDF64A6F3F -B05959D210B500943A93BDFAF783D9DB215FC84503B152EAFBCFB5B6237E3888 -B393DE4489BCAFD5DB80592A12E329E18913E185D2042580048029A8C4C3A257 -B4B30492A5F0C3C763E2F43C02D1451A5B9CFB468CFE62BE85B1F56FF49DAB9A -CE5D57C0EE3D717FC717EB725970A9F25D211546EE7AC5C237950CEA323D85D4 -4E9028944813FD40A17AF6DF5A97E76179B48EE79265BBD38B07E3A270587A813DADB51B3367479AC5644F754B5613F8 -3B3C3000B9D1361711ECE3DB77C90A059576F738CB167679DA36DD3D128B27A1 -997023148148DE7B9CBA47D3FD48DEF73AA1715FF4BC1E7A1DBA6D52A0DCB2C0 -C8428D18E69FB92486434FCE470F1FF37D40507F27D824679C132A70D516530367277F02DDB5C464D03450FF6B425A24 -3701200DF5DA7235971FD95844056E74C7D61A8EB12A8772E04F52037C63D50B6229A7F905F3E6F84C565FCC7632870C -BB392A464CDC0D5D923AA9EF8ECC3C6F020D0AD82165462DF0DE7C5025AAAAAC -999C82209B30638506E5D708471676D2CBB4A432E5AF86ABD61179111EDAE636 -FDE2A452A6B47261338117EC20FC57731DA492562ECD21BBC61F098A5442CF20 -D923BABB5C4DFB48E3F763898B2796C7830D3EE9A91DF904AC2223A0F4736507 -0987DDAC695DD5E4607048DF1D4EF96599E17ED52F41785E676AA048AB7213FE -26CB3E6CFA10338A8DDD99BFF6957C53DEF435CB0FF977B71B5164ADFE11292A -097908FD07A0A093CA80E6FF59524707C1A11169D0CB6F8E4967D8DAA725FE7A -8C629E70A5CC6FCB039DFA1A6AC58CB7B7E92C85BDA66266AB49E6B1285FC7A6 -39A2052350CD446EDC1B9AD0C2DD51C78B2E5F3A76AAD0EC200F74B40ACD4AC5 -A1685CF8C4A5401F2CA0C8172CB5B4B5726C61CE68A72AE834B0A472CEB2F3DE -1F5ED5793DB381D1B501BA8A4DF3E74FB11FC1A922DDC8AE62E5BA8934C37EA8 -D80EF661BF36E2F6C179E253CE5BC3732684ACBC7C65E526A628442A2EBF8FAA -7785BF721F21E19A8CFBFBBB56BD76B96A4E8EF9F8A2344009B14AB385909598F834A5533B648DA7D62BD6D4314A43A5 -C8F6F943DE615B5827569B283577344C0455B3279C73634FC4E0E9A8088DF633 -FB4F4C786FC51BBDA679A212B4A05EF120AC62F7EBFFE8263BD50A4D9BC9C6E0 -16EEC35CF69BA86DB3BE999CDF9B39F5736F3727B2AA2C5AB9141A48F176D831 -AD1AD6DE813E7710DA3AF546D4F9EA085831E6B3FA17B64F1B8765F48134EA54 -345D743BC35B4A8614632ADD11E809C0D1E6C78F9469256B9A738DA0B648B2B8 -7C876CECAC839EBB4609C3996966C3EC454F51C8ABCC51097E405370C4B6F086 -0F857C031FD3047607647148C534F969567F207FF1691D8D06DCDF4C2514695D -EC0630EDC82241C1952F49B6B1B0C1A954A7DDD6BDB1326ACC54AD449D1BF985 -286EF9F7FD0D09F2604CCE867C52144CD0C4773A3D8183066C61B8BF9860AE7C -EA55424097A08722A66966E3177E09DE91AC65175E5C68CB47B6153E6585DF85 -D54FCDF9EA4BD1FE4F316DB6D5CE4A2675F2D0144772865EDC781FBA7DFD23E4 -7A2F5C5CA9F97FE9527BAA760E64B930C407A27DE036476737E6BDD9422F4056A5F1F414F12F0982109FD7C30E8CC1CB -06BAD9B4EEEEB1BCF8C97672D271534FE84D772282EE9642698788D3842D7641 -101C1B2DBD963E23777294C22E553D145D5B40838F91355CA86D571A0CEFF68F -1B148C2B502B3E0A5BD40858E019C513DD4CCAF2A114CBB29C59BFB018079285 -8DF4D07EC20FF873EA989ACEF4AF96E9787FE6E0F71965858B4186C3AF302A31 -2317DC8C098CD60F3467B3644A19CCE887339708820CD37F6F5277D6648F837512F70CE90E23D7339CDDE002BD8D83DB +IV: 41 84 34 43 84 1D 43 04 45 44 34 38 41 24 3E 74 +8C5AEF725F0620DB3952B40BB8E76A815BCEE1D1B92F7E8E8E63D1894F1C7FD0 +1DFF1D6A322D6B3D8AD7C594A02462AADE723D417B9233585526982F08187DAA +0A9184F15D4A5589DDDA6422063BACD58580661CFE60EE600D87F73F0CB5013E +6E56802DAA049C3DFDEDC90432A0E694A172C369EBECD136F4C911B979AA098D +A659716B51053604059F7FC3651D6A153F5EAB1852F95B20C44C41A7889A0DE91A078B63E3C311280C4315F0A3C8BA1F +A315170EDC51627157725D9A96490DB75EBF8232957FBA313C03B2BA2884EA85 +DEAB3C2C2E2DC76FE45AEBAC7EBFB478CECCD970A63B8DE2024FBFDCCBD1B26E +7BBFC36CBA77468B4624C6B685610877D53985C985DAD8EFE47527EB7C7260CD +879EE18B314ED4F3F548B41176099176FB97F4F1A062481C935B2DDFBCE2FE4D +493372D7D47A96A66305DFDC8A915EB651620881AE1D603B7E9605E004C04CA9 +F80AAA4C447F8E8C0B039DDAECF9126119C32FF164118780BE268E326A8CBF8010DE2EBF94033CEAC39815D6A8958CF4 +41C1393A039E665F6A53A5E5D1C105008BD14D9755751545A667860C39B2E39AA47306E76E2BA7DDDAA2A286FDB58D23 +34853A4CDE42CB80045E641AB4800C86C1CF6907EAAFA399156CCC727A008584 +D0783A34BD6A36D31BFF5F70FA1116CAE48EF02716D80481AE406DABB3C3400E +0BB3582605434CF2A5D74A27773B88DA331B6033C444837E9F7A155897258B03 +E4E71F3EB290B9436FFF0FDADA468BE37D964E89BE8D9971A14776F821822769 +744AA59D129C892120B5DAB81C8A3D573B0AD80EF0708C1B9ECF13DA60CECA07DC0591A08611DB4D3A8B7C70994D5DEF +716F9F8D5D2C697BC4183EFCC97E52D08ECA07B613A0F389C801F65803DFF4A4 +560262DA8489D2C18C8D97E314DC663403AFE4DE9DCB6D453087D2BFBD36532D +9E31F7152C50B6940EE3E3894C98F75702C7897F702B48E5C9B54B6E25083641AD2E521267505066C7E5BAB7F6CF1433 +6630EDA18A6E58BD395792CCC656DD10CD9C5DD2B1949FE677122FA39A53C724E79C0D0752A3A39A03407BBA2282185E +00D15A06F5DD82A4B926B78809CC4D129AAFA9A92B0A81568F255D15697FE0FD +29FF9A4F5346ABEE8FEDE034988F87FCD29EA747735898F1E7207EF74FAB71A8 +C0E8EB6AE6F77EE38DF2AB1B7742E34ED5236F3D8E964845E66762A4675AA21F +00FC4C459DC4CE92B62D0AC2546F9FBBE0893F84D2AF0A20ED462A5EAE63DE3B +E92EF482A40CEEFC8339BBB713BBC452A266A09B2645EDEB12716544B2DB9B09 +D7D9C5C757831BCE2FF1DB25A080D77769FB36A1F3F48F4361418A0A45609280 +C19246F52AE1EE5CE968CED52F642D9CD78B020029632FE83C49C657D23ED075 +FEE3C05432FB3860D5D28562323F5D1B053B8F3ADCD416BD0C4645F6F4D43DCF +D780A4AADD0205E0BACDCC9AF46ED259E0946C5DA888C341BFE96E09A87CCCFA +CE3C13CFA08E532B637FDB707E29548D57EE92EAEF6516C3D67E9D36FCD59CF9 +5E88CE71258CB0D91631FEB41C9A2F47AE0FF4810A9A1EDF3F308BBDE6944D5E +1531F4107FC64810BA5DB5E46C7B9AD61531AF5430E137B7688109FBC06B6221 +68050A39C0B302E0B713FAAC5F829C79AB30E18B1D982A94005DBAC7CCFB95379A619C0B9F7409C44D19FF2C5E8E4546 +3F73E8BA22C602280496EF8E88E2CAA9EC442E3B3083B684942DBF9CB5121241 +FA1FCD7C9182FAE8FFF4E88433AE68F66076B3BDFF8AD0BF5CEA43870082E9BE +DFF7DD2678C03401656B093BF7AC7E033F15FD0F30188E48A62045740B423699 +371BCFF653E7811D99C048A1A39921AAA563E06AC86CB3D2F392C2C955A1ABD0 +F4F1766DEAEDE934478208B9EB3050326D9FFCC001C73EEE93407D8B12CD49E4 +A241C9FC62DDF67D645936245FAFFE2A42C86151F484B7BCE5410E8F36FC87901D3AC4E40334E08FFFC2AD676E490D94 +3566A94A9C0479E0C4387D9137375ADF2C921504364F3903F198D6757CDFD21B +7274E1B5A6445FDC29C355D550E981C17F349BC4A14251B3B51BC96FC334FBCA +04EEA5EDD9B3BC3E0638E53A5561DC8BF761D615A64D435BD31A94AF2650159E +B84818CC1695FE8B731CD653D0679D1AAA0578C0B06AD1E3510785B2DE20841C +4121343D6B79E38C06DD038D770D76D10336AFF47ED0D0DCDDD6B0FEA4DAE67C +75E49C839CCD7019D9CE90AC364F488468B2AB01E387A8BEF8815915925166A6 +CFAA9F4717568C1EC7B96E0D71D260B828A70484E1D9CA7C99A50D10704F8BBBAE62EE98C9FBDFF06F357F1C1E2F2677 +41E4D250B92BC57442B91DE2015C41226531CF9A8D77B83AFC8E4F3183DB11DE +45EA8BD854D7F044FB249C16F08A0C24FF117D54BC20A4CC667B3DAD09EAC4F9 +F455CA0BB8B496C301406DE4FB52C9B0F64645776803BC2935A2F38675318BE2 +22FF72A5D2E1A2EBFB6C55FFD0A3CEA0474CCBD13462D63229C9708276E87D3F +8470F9A300170F226C0216C07AA829591CBD4CE34AA918EAE49363BDE86CC77EEEBEEA84A097488D35B92F773F5DBB4C diff --git a/assets/unit_tests/subghz/kinggates_stylo4k_raw.sub b/assets/unit_tests/subghz/kinggates_stylo4k_raw.sub new file mode 100644 index 00000000000..49b19000210 --- /dev/null +++ b/assets/unit_tests/subghz/kinggates_stylo4k_raw.sub @@ -0,0 +1,11 @@ +Filetype: Flipper SubGhz RAW File +Version: 1 +Frequency: 433920000 +Preset: FuriHalSubGhzPresetOok650Async +Protocol: RAW +RAW_Data: 377 -386 1117 -410 1121 -352 1141 -384 1151 -378 1119 -350 1139 -386 1115 -1134 389 -1114 395 -1122 363 -1136 389 -358 1167 -356 1145 -1120 389 -1110 391 -356 1139 -1126 389 -1114 391 -1122 363 -1146 389 -1110 395 -1122 363 -1138 389 -1110 393 -1122 363 -1140 389 -1112 393 -1120 389 -1118 389 -1112 397 -1124 363 -1142 389 -1112 359 -1154 367 -1134 389 -1144 365 -1138 355 -394 1119 -380 1107 -1152 353 -398 1113 -384 1139 -1118 385 -376 1141 -386 1129 -350 1143 -388 1109 -1132 389 -1112 393 -390 1107 -1128 389 -1112 397 -388 1111 -1132 389 -358 1127 -1118 417 -1116 383 -1120 353 -1158 389 -1108 375 -384 1121 -408 1123 -350 1139 -386 1111 -1130 389 -1114 395 -1122 395 -1114 389 -1116 395 -1122 363 -1138 387 -9444 373 -374 379 -374 381 -346 403 -346 389 -376 389 -390 353 -376 359 -382 383 -360 419 -360 359 -386 359 -2264 777 -356 1127 -390 1143 -362 1131 -1138 365 -1122 359 -386 1153 -1106 377 -1152 385 -372 1113 -1140 385 -1118 381 -1114 383 -1150 383 -1120 355 -1122 389 -358 1165 -386 1113 -1128 389 -360 1125 -384 1131 -368 1157 -350 1139 -386 1115 -406 1099 -384 1141 -1122 383 -1110 373 -1130 385 -1128 393 -380 1131 -380 1129 -1112 383 -1132 391 -356 1143 -1124 383 -1130 367 -1136 385 -1136 387 -1112 371 -1120 389 -1118 383 -1130 371 -1130 383 -1110 383 -1120 413 -1118 383 -1144 347 -1144 389 -1110 393 -1122 363 -1140 389 -1112 359 -1154 363 -1146 389 -1110 393 -374 1115 -384 1115 -1144 385 -368 1141 -388 1111 -1110 421 -360 1125 -388 1109 -392 1137 -358 1125 -1144 365 -1138 389 -358 1123 -1118 401 -1138 389 -360 1121 -1120 417 -358 1109 -1154 355 -1120 375 -1138 385 -1130 391 -1136 355 -398 1115 -380 1141 -384 1121 -382 1119 -1104 413 -1118 355 -1156 387 -1112 377 -1122 389 -1118 387 -1112 397 -9422 417 -352 363 -416 355 -388 345 -382 377 -380 375 -380 375 -380 375 -380 385 -356 379 -366 385 -374 387 -2246 745 -380 1141 -386 1113 -370 1125 -1140 373 -1152 355 -394 1117 -1140 381 -1120 385 -374 1145 -1112 385 -1122 381 -1116 383 -1120 375 -1120 389 -1120 373 -380 1171 -358 1121 -1142 377 -356 1127 -384 1137 -378 1155 -390 1105 -366 1125 -386 1135 -386 1111 -1132 389 -1112 393 -1120 365 -1138 387 -360 1163 -356 1143 -1126 387 -1114 357 -386 1141 -1126 383 -1130 365 -1132 381 -1140 377 -1116 383 -1130 371 -1128 381 -1140 347 -1148 385 -1128 369 -1128 381 -1142 377 -1114 389 -1112 395 -1124 361 -1142 389 -1114 393 -1122 365 -1138 389 -1114 397 -1108 389 -392 1119 -350 1139 -1152 355 -396 1115 -382 1109 -1156 385 -374 1111 -384 1139 -368 1147 -388 1109 -1112 389 -1120 383 -388 1107 -1150 389 -1112 +RAW_Data: 393 -390 1109 -1128 389 -360 1125 -1120 381 -1152 383 -1118 353 -1158 387 -1112 375 -386 1117 -408 1121 -350 1143 -388 1109 -1132 389 -1114 391 -1122 395 -1120 389 -1112 393 -1122 365 -1136 389 -9442 373 -376 389 -354 377 -366 387 -384 357 -378 361 -418 347 -394 385 -358 363 -382 361 -414 357 -392 333 -2290 751 -384 1113 -406 1121 -350 1137 -1136 353 -1160 385 -356 1135 -1120 361 -1146 385 -388 1137 -1108 361 -1150 387 -1112 395 -1136 349 -1154 353 -1142 371 -384 1145 -378 1117 -1138 381 -382 1087 -410 1121 -382 1143 -380 1121 -380 1115 -384 1107 -418 1115 -1106 385 -1148 365 -1118 359 -1146 387 -388 1135 -388 1113 -1126 383 -1130 367 -376 1113 -1142 383 -1114 375 -1154 355 -1160 385 -1110 371 -1152 357 -1118 385 -1146 365 -1122 361 -1146 387 -1114 395 -1134 355 -1160 351 -1146 369 -1154 355 -1120 387 -1114 397 -1136 357 -1118 407 -1144 351 -1134 359 -420 1111 -366 1131 -1142 379 -384 1089 -410 1119 -1142 379 -366 1141 -386 1109 -388 1131 -350 1141 -1118 391 -1114 375 -378 1153 -1116 385 -1136 383 -358 1139 -1120 359 -420 1099 -1142 383 -1118 383 -1138 347 -1144 385 -1144 369 -386 1113 -404 1089 -386 1141 -382 1099 -1136 381 -1128 375 -1130 383 -1140 359 -1146 387 -1114 395 -1138 357 -9430 383 -378 359 -418 347 -392 387 -360 363 -384 361 -414 357 -386 347 -384 375 -382 385 -358 383 -364 387 -2252 773 -354 1131 -384 1137 -386 1111 -1132 387 -1112 397 -388 1113 -1124 389 -1116 387 -388 1115 -1132 389 -1114 393 -1120 365 -1140 389 -1114 357 -1154 365 -378 1151 -358 1127 -1156 367 -376 1135 -358 1125 -388 1141 -368 1125 -386 1133 -388 1109 -370 1155 -1106 375 -1122 389 -1118 389 -1114 395 -386 1117 -410 1123 -1106 377 -1130 383 -388 1107 -1152 353 -1148 353 -1150 367 -1142 389 -1110 397 -1120 365 -1138 389 -1110 391 -1122 363 -1142 387 -1116 389 -1120 391 -1116 389 -1116 395 -1122 365 -1140 389 -1114 357 -1154 363 -1138 389 -1142 365 -1138 355 -396 1115 -382 1141 -1118 353 -400 1111 -384 1139 -1120 381 -386 1139 -366 1119 -392 1121 -388 1107 -1152 389 -1114 355 -388 1139 -1126 387 -1114 397 -376 1111 -1144 375 -380 1129 -1138 375 -1098 385 -1140 377 -1118 387 -1144 371 -386 1115 -404 1121 -348 1137 -386 1113 -1134 389 -1112 395 -1124 395 -1118 389 -1110 395 -1122 363 -1138 389 -9418 391 -360 407 -384 361 -388 355 -390 367 -376 373 -380 387 -356 377 -366 385 -388 355 -378 359 -384 377 -2266 777 -346 1149 -388 1107 -390 1129 -1104 415 -1118 353 -398 1113 -1138 383 -1122 381 -388 1141 -1118 389 -1112 357 -1154 365 -1138 389 -1114 357 -1154 363 -378 1155 -358 1123 -1156 381 -360 1107 -384 +RAW_Data: 1153 -378 1119 -382 1143 -382 1121 -382 1117 -382 1113 -1120 389 -1120 387 -1112 399 -1120 393 -392 1119 -350 1141 -1154 357 -1116 389 -360 1163 -1120 365 -1138 387 -1114 389 -1122 389 -1116 387 -1114 397 -1104 379 -1156 353 -1148 367 -1118 377 -1122 423 -1110 373 -1122 389 -1118 383 -1130 373 -1128 383 -1140 345 -1146 383 -1130 399 -1130 353 -1142 377 -358 1127 -384 1143 -1118 385 -372 1111 -386 1137 -1120 381 -388 1141 -364 1127 -384 1133 -374 1111 -1148 383 -1114 373 -384 1115 -1136 387 -1144 371 -386 1115 -1132 387 -360 1123 -1150 345 -1148 383 -1128 371 -1132 381 -1140 379 -390 1123 -350 1139 -388 1113 -406 1089 -1142 373 -1120 389 -1118 423 -1110 371 -1120 379 -1122 407 -1104 417 -9440 389 -356 379 -366 385 -388 355 -378 359 -384 381 -394 387 -358 361 -386 359 -416 355 -388 345 -384 377 -2262 747 -384 1149 -380 1115 -382 1113 -1120 389 -1120 389 -360 1129 -1152 367 -1136 389 -358 1131 -1152 367 -1138 389 -1116 355 -1152 367 -1134 389 -1116 353 -388 1141 -368 1123 -1138 375 -386 1113 -408 1121 -350 1175 -372 1105 -386 1145 -352 1141 -366 1145 -1114 385 -1116 377 -1122 389 -1120 421 -354 1139 -388 1109 -1132 383 -1130 369 -374 1113 -1144 385 -1114 377 -1120 391 -1128 373 -1138 385 -1130 359 -1138 377 -1120 373 -1138 383 -1130 359 -1138 379 -1160 375 -1106 385 -1130 393 -1120 377 -1118 389 -1112 393 -1140 355 -1120 421 -1114 371 -1122 391 -390 1123 -350 1139 -1134 353 -402 1113 -384 1141 -1118 385 -376 1143 -352 1161 -352 1135 -386 1113 -1132 387 -1114 395 -388 1111 -1128 387 -1114 399 -374 1115 -1142 375 -380 1117 -1118 387 -1144 363 -1136 385 -1130 367 -1130 383 -388 1107 -392 1129 -380 1115 -384 1113 -1136 389 -1114 393 -1124 393 -1120 389 -1114 393 -1124 363 -1140 389 -9416 391 -360 405 -386 329 -416 357 -392 365 -374 377 -380 343 -412 341 -412 353 -390 375 -366 385 -386 355 -2264 743 -394 1123 -388 1111 -392 1133 -1110 395 -1120 363 -382 1133 -1142 381 -1118 383 -376 1111 -1146 383 -1122 383 -1146 347 -1150 381 -1116 353 -1158 343 -410 1133 -382 1111 -1152 355 -394 1119 -382 1109 -382 1153 -378 1131 -354 1137 -396 1119 -388 1111 -1150 351 -1152 351 -1150 365 -1136 387 -356 1131 -386 1143 -1122 387 -1112 357 -420 1107 -1128 387 -1114 359 -1152 363 -1148 387 -1114 395 -1122 361 -1140 387 -1110 395 -1120 361 -1140 387 -1114 393 -1154 355 -1120 387 -1146 365 -1118 361 -1146 387 -1112 395 -1120 363 -1140 385 -1144 367 -1120 359 -420 1099 -384 1139 -1118 383 -384 1109 -392 1129 -1138 379 -366 1147 -388 1109 -386 1099 -384 1139 -1132 349 -1158 375 -380 1131 -1104 411 -1122 351 -416 1111 -1148 +RAW_Data: 353 -396 1121 -1142 347 -1150 381 -1116 355 -1156 375 -1144 387 -360 1119 -388 1107 -394 1131 -386 1101 -1152 363 -1138 387 -1112 391 -1152 357 -1116 375 -1136 383 -1122 383 -9448 357 -392 357 -398 363 -378 385 -358 383 -364 389 -386 357 -380 389 -386 347 -382 375 -384 375 -380 373 -2262 747 -376 1145 -390 1107 -386 1129 -1104 413 -1120 353 -396 1115 -1140 381 -1122 383 -376 1143 -1110 385 -1118 383 -1114 417 -1114 383 -1120 353 -1156 389 -356 1135 -386 1113 -1134 389 -358 1129 -390 1107 -392 1153 -358 1127 -388 1143 -362 1131 -356 1131 -1154 365 -1136 389 -1114 357 -1150 363 -386 1153 -358 1125 -1118 417 -1120 381 -350 1123 -1134 391 -1112 395 -1124 395 -1116 389 -1112 393 -1124 363 -1140 389 -1114 359 -1154 363 -1138 389 -1114 391 -1124 361 -1144 389 -1112 393 -1122 363 -1138 389 -1112 391 -1122 363 -1138 389 -1144 365 -1124 361 -382 1155 -350 1137 -1120 391 -386 1131 -350 1151 -1120 383 -378 1141 -352 1137 -394 1117 -390 1107 -1150 389 -1114 355 -388 1143 -1120 387 -1112 397 -388 1113 -1130 385 -344 1163 -1104 379 -1122 373 -1140 383 -1130 389 -1124 359 -386 1127 -386 1139 -368 1141 -390 1107 -1112 387 -1116 385 -1150 367 -1140 389 -1112 393 -1124 363 -1136 389 -9444 379 -340 417 -360 359 -386 359 -416 355 -386 347 -384 375 -382 375 -380 375 -378 375 -380 385 -356 379 -2278 745 -354 1151 -368 1141 -390 1105 -1114 387 -1116 385 -386 1141 -1120 389 -1114 389 -388 1113 -1130 389 -1112 393 -1124 363 -1138 389 -1112 389 -1122 363 -380 1159 -350 1137 -1122 391 -388 1097 -384 1139 -382 1125 -386 1145 -352 1141 -366 1145 -390 1107 -1110 387 -1150 353 -1150 367 -1138 387 -360 1125 -390 1109 -1152 389 -1112 357 -388 1141 -1122 389 -1110 391 -1122 395 -1112 389 -1110 397 -1120 363 -1144 389 -1114 391 -1122 365 -1138 389 -1116 389 -1142 355 -1120 389 -1112 397 -1122 363 -1140 389 -1110 393 -1130 349 -1140 405 -1134 389 -1112 357 -388 1141 -364 1129 -1142 367 -388 1111 -370 1131 -1140 383 -364 1149 -388 1109 -388 1137 -356 1127 -1118 383 -1120 413 -350 1121 -1132 407 -1140 355 -364 1149 -1112 371 -406 1129 -1104 409 -1098 383 -1116 417 -1118 381 -1118 385 -388 1111 -390 1129 -350 1137 -386 1113 -1138 389 -1114 395 -1122 393 -1120 389 -1112 393 -1124 363 -1138 389 -9444 379 -340 417 -360 359 -386 361 -414 357 -386 347 -384 375 -382 375 -380 375 -380 375 -378 373 -380 373 -2262 749 -386 1111 -408 1117 -348 1143 -1120 391 -1116 389 -358 1127 -1154 365 -1136 387 -360 1129 -1154 365 -1136 389 -1114 357 -1154 365 -1134 389 -1112 357 -390 1137 -406 1119 -1106 377 -386 1117 -406 1123 -350 1143 -384 +RAW_Data: 1149 -378 1121 -350 1145 -380 1133 -1104 375 -1136 385 -1130 359 -1138 379 -400 1129 -354 1139 -1148 355 -1150 365 -378 1119 -1146 355 -1152 365 -1134 389 -1110 397 -1122 363 -1140 389 -1110 395 -1122 363 -1142 389 -1116 357 -1152 365 -1146 389 -1112 393 -1130 349 -1138 383 -1116 413 -1120 353 -1122 387 -1114 397 -1154 355 -1124 387 -360 1133 -384 1131 -1134 383 -376 1133 -352 1133 -1132 383 -376 1139 -378 1135 -380 1115 -382 1141 -1118 353 -1160 387 -356 1133 -1118 379 -1158 353 -392 1133 -1104 379 -398 1117 -1138 383 -1118 353 -1164 353 -1146 403 -1120 353 -398 1115 -382 1143 -384 1089 -412 1121 -1106 377 -1154 355 -1118 407 -1146 351 -1130 395 -1138 355 -1118 407 -9434 353 -396 363 -380 383 -360 383 -364 389 -386 357 -414 355 -386 345 -386 375 -382 375 -380 375 -378 375 -2256 769 -384 1119 -382 1117 -380 1113 -1122 389 -1118 389 -360 1131 -1140 377 -1118 421 -354 1139 -1140 355 -1118 405 -1104 387 -1128 391 -1122 363 -1138 387 -360 1157 -354 1143 -1128 387 -360 1121 -388 1141 -362 1129 -384 1139 -388 1111 -370 1127 -384 1135 -1122 363 -1140 389 -1116 393 -1122 395 -356 1129 -384 1141 -1120 383 -1134 387 -356 1133 -1130 349 -1140 383 -1116 413 -1118 381 -1110 377 -1146 389 -1114 391 -1124 365 -1138 391 -1112 357 -1150 365 -1144 387 -1112 395 -1122 361 -1140 387 -1112 393 -1104 379 -1158 353 -1144 403 -1118 353 -1158 353 -390 1133 -390 1107 -1130 389 -358 1125 -388 1111 -1154 389 -358 1129 -386 1131 -368 1129 -382 1139 -1118 353 -1164 387 -356 1135 -1120 393 -1118 389 -358 1131 -1154 365 -376 1135 -1108 395 -1136 347 -1126 387 -1144 403 -1120 353 -398 1115 -384 1141 -372 1103 -386 1145 -1108 387 -1120 383 -1116 403 -1140 389 -1116 353 -1146 361 -1144 389 -9420 393 -362 401 -338 377 -388 385 -374 361 -382 375 -382 375 -380 373 -380 373 -380 373 -380 389 -354 379 -2272 743 -388 1137 -360 1121 -386 1111 -1152 351 -1148 359 -384 1143 -1126 387 -1114 391 -384 1117 -1130 383 -1132 369 -1134 351 -1138 377 -1142 385 -1112 359 -420 1107 -406 1121 -1104 377 -384 1119 -406 1119 -352 1171 -382 1123 -378 1119 -384 1107 -384 1119 -1136 385 -1116 393 -1120 361 -1142 387 -388 1137 -386 1113 -1128 385 -1116 357 -420 1113 -1124 387 -1114 359 -1148 395 -1116 385 -1146 363 -1116 361 -1144 387 -1144 363 -1120 361 -1144 385 -1112 393 -1152 355 -1154 353 -1144 367 -1136 355 -1158 351 -1146 369 -1120 361 -1144 385 -1148 369 -1152 357 -392 1117 -380 1107 -1152 355 -398 1119 -382 1109 -1152 385 -374 1113 -386 1139 -366 1145 -388 1111 -1110 385 -1148 353 -386 1139 -1124 387 -1142 365 -386 1113 -1132 385 -360 1125 -1144 +RAW_Data: 363 -1140 387 -1114 357 -1152 363 -1146 387 -358 1129 -386 1139 -366 1125 -384 1139 -1120 363 -1140 387 -1112 393 -1130 381 -1136 383 -1114 371 -1132 383 -116626 65 -934 133 -1954 131 -102 133 -136 97 -332 65 -430 299 -296 129 -100 265 -168 367 -100 65 -66 231 -336 9643 -7766 529 -68 467 -166 65 -134 99 -500 331 -132 65 -130 329 -98 497 -100 1195 -100 1959 -66 4163 -7346 97 -392 165 -194 97 -2978 433 -298 531 -298 65 -200 131 -132 261 -98 229 -68 12837 -340 99 -268 165 -134 65 -898 67 -100 265 -66 165 -100 597 -166 199 -298 199 -200 99 -132 233 -132 299 -132 233 -166 65 -66 4021 -168 133 -68 231 -168 4647 -130 1399 -7750 133 -1714 197 -2480 131 -200 65 -100 265 -890 63 -1152 197 -98 293 -134 65 -300 361 -100 1035 -100 231 -132 299 -100 3399 -66 6287 -4506 99 -100 65 -130 99 -196 461 -98 331 -164 97 -162 227 -64 197 -98 229 -130 195 -100 425 -526 165 -130 95 -522 457 -560 233 -98 261 -66 1155 -100 259 -130 1407 -98 553 -66 7793 -494 65 -232 65 -3652 229 -2716 361 -266 333 -200 133 -166 99 -132 267 -66 133 -132 199 -166 331 -132 331 -166 197 -950 229 -198 303 -298 365 -100 4839 -3816 165 -130 229 -696 131 -130 261 -262 97 -166 263 -894 165 -230 365 -566 129 -560 197 -324 99 -98 261 -134 131 -100 67 -334 67 -232 199 -132 165 -302 67 -100 1467 -98 459 -100 1081 -130 131 -66 8927 -232 165 -3104 99 -2812 65 -982 131 -98 195 -98 263 -264 231 -66 195 -132 193 -164 65 -100 365 -132 1629 -66 1009 -132 8383 -632 131 -3060 131 -492 425 -100 763 -166 371 -132 1197 -134 229 -694 461 -366 365 -98 329 -198 267 -168 399 -68 131 -332 493 -132 231 -132 569 -66 7765 -7568 99 -532 65 -634 133 -3540 65 -100 263 -592 261 -1484 299 -302 265 -234 1129 -304 99 -436 163 -360 97 -556 231 -166 265 -1164 165 -134 235 -100 163 -332 297 -100 197 -132 99 -566 133 -234 133 -328 295 -98 985 -98 163 -396 399 -134 1557 -134 297 -266 6875 -68 1759 -7194 133 -166 99 -266 65 -432 67 -432 393 -5086 99 -66 199 -68 263 -866 429 -100 359 -130 261 -132 267 -134 533 -134 9251 -4184 65 -1156 165 -198 65 -426 297 -492 67 -164 131 -198 259 -164 199 -100 733 -134 865 -100 397 -132 65 -100 197 -66 327 -164 227 -98 231 -132 97 -262 99 -130 229 -66 589 -96 1119 -98 1905 -7486 599 -66 561 -66 359 -98 757 -162 261 -66 323 -130 5573 -8538 99 -894 131 -594 229 -364 63 -1378 197 -1682 331 -100 199 -166 diff --git a/assets/unit_tests/subghz/test_random_raw.sub b/assets/unit_tests/subghz/test_random_raw.sub index 12b08d48da4..949a444585e 100644 --- a/assets/unit_tests/subghz/test_random_raw.sub +++ b/assets/unit_tests/subghz/test_random_raw.sub @@ -190,3 +190,9 @@ RAW_Data: 451 -1040 441 -1052 449 -1050 945 -554 449 -1052 451 -1020 481 -1010 4 RAW_Data: 979 -514 443 -1046 479 -1028 451 -1042 451 -1048 447 -1022 485 -1014 983 -520 973 -516 483 -1012 983 -518 973 -516 977 -520 1003 -520 975 -520 981 -514 475 -1034 969 -516 479 -1016 447 -1046 475 -1018 975 -516 975 -522 983 -510 469 -1010 1007 -518 951 -530 989 -516 973 -556 951 -494 481 -978 487 -978 975 -460 1005 -466 979 -486 969 -508 981 -450 1489 -23666 571 -4036 269 -1224 257 -1250 787 -642 867 -622 883 -622 359 -1136 373 -1086 421 -1080 417 -1074 935 -550 947 -552 445 -1048 939 -552 451 -1046 947 -552 947 -550 451 -1040 443 -1048 453 -1024 977 -522 471 -1034 449 -1020 973 -540 975 -508 479 -1032 453 -1042 449 -1050 977 -518 979 -518 449 -1018 481 -1018 975 -518 473 -1034 963 -542 961 -544 447 -1044 473 -1020 479 -1014 481 -1010 473 -1032 471 -1010 959 -546 973 -492 499 -1006 997 -510 977 -524 953 -552 971 -512 973 -508 979 -554 451 -1016 977 -518 471 -1038 485 -1010 457 -1036 969 -506 999 -520 481 -1014 975 -522 967 -520 975 -548 451 -1038 475 -1022 965 -518 463 -978 985 -486 465 -978 457 -1016 463 -978 985 -486 963 -480 1477 -129906 495 -726 197 -328 295 -132 2547 -66 233 -98 11033 -1856 233 -1458 65 -198 165 -134 199 -168 101 -694 463 -530 165 -300 99 -232 2479 -98 1745 -98 3029 -132 163 -1460 65 -500 65 -400 99 -664 895 -398 65 -564 331 -166 97 -66 197 -98 3813 -98 10097 -3848 165 -232 67 -266 397 -596 165 -66 199 -166 99 -66 199 -398 165 -166 1721 -232 429 -166 133 -330 133 -698 493 -200 197 -428 11029 -12118 65 -198 199 -68 231 -230 101 -166 99 -664 131 -132 3163 -4238 331 -298 531 -398 299 -98 199 -166 563 -100 131 -98 893 -66 3141 -1556 133 -1722 131 -830 197 -262 195 -66 163 -462 195 -396 195 -134 499 -132 265 -66 1717 -166 3175 -11366 199 -164 131 -66 163 -98 525 -98 363 -264 4495 -100 229 -66 131 -66 593 -3002 97 -394 131 -426 99 -462 597 -692 295 -298 431 -230 4231 -66 9711 -3246 131 -100 99 -400 263 -498 65 -100 297 -98 99 -132 65 -862 131 -66 365 -396 99 -166 1991 -98 1611 -132 10333 -790 65 -1984 99 -896 165 -332 365 -232 131 -830 65 -66 397 -166 197 -66 65 -496 199 -100 9975 -1728 67 -5008 727 -98 131 -100 2873 -66 12011 -3150 67 -960 99 -234 99 -298 231 -232 195 -266 165 -296 261 -166 757 -66 629 -196 657 -100 197 -134 297 -364 11237 -1684 65 -2076 165 -462 491 -100 663 -630 329 -264 263 -100 1357 -66 461 -1676 99 -1782 295 -296 65 -296 163 -230 99 -132 295 -66 163 -362 197 -724 757 -66 RAW_Data: 3785 -66 13551 -1808 97 -730 65 -100 231 -132 131 -1230 593 -232 1579 -66 2667 -200 101 -3480 165 -692 133 -396 427 -1524 363 -66 431 -132 10305 -8288 461 -628 67 -430 725 -66 1053 -66 4501 -230 165 -66 331 -66 355 -266 263 -132 63 -562 459 -462 197 -66 129 -132 65 -100 2643 -132 2107 -66 9651 -3692 99 -100 195 -294 97 -660 759 -328 165 -560 891 -66 1953 -66 11305 -362 263 -662 131 -432 65 -134 563 -430 131 -132 1819 -100 165 -166 1061 -98 10089 -2476 65 -854 395 -198 99 -492 131 -164 229 -466 199 -428 299 -100 927 -200 1557 -134 4269 -10464 133 -1624 65 -198 265 -398 131 -430 729 -134 6189 -66 5421 -2082 165 -3342 19967 -12808 1439 -1536 453 -1046 449 -1032 449 -1056 947 -552 977 -522 977 -518 453 -1038 977 -522 977 -520 457 -1038 977 -506 1005 -496 495 -1008 975 -538 973 -530 465 -1008 975 -554 453 -1036 947 -518 487 -1008 475 -1042 443 -1050 461 -1008 1005 -510 447 -1048 985 -510 469 -1006 1005 -494 997 -514 975 -514 975 -504 999 -506 479 -1034 491 -1010 975 -508 973 -524 491 -1004 473 -1018 997 -520 975 -512 975 -518 473 -1030 983 -516 981 -514 471 -998 997 -522 481 -1012 481 -1012 457 -1050 973 -512 977 -524 459 -1016 1003 -512 479 -1014 459 -1016 475 -1012 1007 -522 969 -502 495 -1008 477 -1030 965 -522 975 -514 479 -1000 471 -1062 471 -964 483 -982 471 -1000 471 -980 979 -448 503 -988 465 -976 487 -974 1459 -23696 1407 -1616 401 -1068 429 -1080 419 -1058 935 -566 923 -584 417 -1078 939 -524 457 -1042 973 -550 443 -1028 949 -554 945 -552 447 -1022 979 -518 971 -542 479 -1024 947 -550 441 -1048 979 -518 453 -1044 449 -1050 449 -1020 485 -1014 981 -518 479 -1014 975 -524 459 -1036 973 -516 979 -518 971 -552 945 -550 945 -552 449 -1030 479 -1026 947 -554 949 -552 449 -1018 479 -1008 981 -518 975 -548 945 -554 451 -1034 967 -514 997 -514 445 -1036 967 -554 447 -1022 485 -1010 475 -1016 975 -518 977 -520 487 -1014 973 -552 451 -1040 441 -1050 447 -1022 485 -1014 987 -516 479 -1014 483 -1014 459 -1046 969 -514 449 -1044 967 -546 973 -488 447 -1016 443 -1000 973 -490 475 -980 983 -482 441 -1016 465 -976 1475 -23652 1451 -1548 479 -1014 461 -1014 471 -1044 975 -520 971 -502 495 -1012 977 -506 1005 -498 989 -516 481 -1016 975 -520 981 -514 475 -1014 979 -522 983 -512 475 -1022 965 -514 471 -1046 973 -494 473 -1016 475 -1046 447 -1050 463 -1012 999 -512 481 -1012 983 -520 477 -1014 977 -524 955 -548 973 -512 975 -520 967 -556 449 -1020 483 -1012 983 -520 973 -516 481 -1008 473 -1034 RAW_Data: 967 -538 963 -544 973 -522 471 -1006 989 -512 1007 -520 443 -1036 985 -516 449 -1048 451 -1022 483 -1012 983 -520 977 -514 481 -1012 979 -514 483 -1022 481 -1010 471 -1020 479 -1020 979 -524 457 -1048 973 -514 483 -1012 981 -520 483 -1018 481 -1014 485 -986 467 -980 981 -486 469 -978 457 -1004 963 -480 983 -486 971 -514 1441 -23704 1383 -1628 389 -1112 385 -1092 407 -1092 915 -552 941 -570 441 -1064 423 -1046 451 -1044 939 -556 455 -1048 945 -552 973 -522 453 -1046 945 -552 947 -550 451 -1040 969 -518 479 -1028 951 -552 451 -1018 479 -1018 483 -1014 459 -1044 971 -514 483 -1010 971 -544 447 -1020 977 -524 987 -518 973 -516 979 -524 985 -518 479 -1016 447 -1050 953 -548 971 -514 483 -1014 459 -1048 967 -514 977 -526 953 -548 443 -1046 975 -492 995 -512 471 -1050 943 -552 445 -1032 455 -1044 449 -1048 941 -550 945 -552 449 -1050 945 -552 451 -1044 449 -1018 479 -1016 479 -1002 969 -542 973 -522 455 -1040 477 -1022 967 -534 959 -514 975 -554 469 -1008 449 -980 469 -1008 943 -484 1001 -484 467 -980 983 -482 961 -514 1439 -23700 1469 -1510 495 -1008 473 -1036 463 -1012 969 -546 973 -522 473 -1018 479 -1014 975 -526 955 -516 475 -1046 975 -490 999 -518 481 -1014 975 -520 967 -514 481 -1022 979 -524 457 -1048 971 -514 481 -1010 485 -1020 477 -1014 479 -1000 1001 -522 451 -1020 977 -520 473 -1032 967 -538 959 -514 1005 -522 965 -504 989 -514 475 -1046 441 -1050 971 -514 975 -520 473 -1018 481 -1014 979 -520 983 -520 977 -516 485 -1010 979 -544 975 -518 453 -1042 981 -520 453 -1024 483 -1010 457 -1050 975 -512 975 -524 459 -1048 973 -514 481 -1010 473 -1016 479 -1016 477 -1036 967 -506 995 -512 965 -546 445 -1048 957 -516 1005 -512 445 -1046 979 -486 473 -980 979 -486 473 -980 981 -486 473 -980 485 -986 467 -976 1477 -142204 197 -1486 165 -198 165 -664 295 -232 99 -266 231 -166 3045 -100 13411 -3670 197 -498 131 -166 231 -198 165 -66 265 -134 129 -1062 431 -130 465 -134 13447 -3848 329 -100 163 -298 99 -164 463 -98 197 -98 131 -198 65 -296 493 -264 789 -66 7225 -12438 99 -164 463 -132 197 -630 65 -198 2487 -66 165 -100 10097 -6554 459 -664 297 -460 4925 -132 6063 -12078 497 -98 99 -200 97 -234 165 -298 1721 -134 265 -100 3035 -100 12081 -3674 231 -100 97 -200 97 -264 461 -100 99 -132 231 -100 97 -430 527 -200 231 -64 2081 -132 327 -100 529 -66 831 -66 3067 -4704 99 -5520 97 -496 67 -198 167 -498 693 -462 2341 -15926 65 -1392 659 -134 131 -298 165 -66 99 -298 4777 -4208 429 -66 +RAW_Data: 377 -386 1117 -410 1121 -352 1141 -384 1151 -378 1119 -350 1139 -386 1115 -1134 389 -1114 395 -1122 363 -1136 389 -358 1167 -356 1145 -1120 389 -1110 391 -356 1139 -1126 389 -1114 391 -1122 363 -1146 389 -1110 395 -1122 363 -1138 389 -1110 393 -1122 363 -1140 389 -1112 393 -1120 389 -1118 389 -1112 397 -1124 363 -1142 389 -1112 359 -1154 367 -1134 389 -1144 365 -1138 355 -394 1119 -380 1107 -1152 353 -398 1113 -384 1139 -1118 385 -376 1141 -386 1129 -350 1143 -388 1109 -1132 389 -1112 393 -390 1107 -1128 389 -1112 397 -388 1111 -1132 389 -358 1127 -1118 417 -1116 383 -1120 353 -1158 389 -1108 375 -384 1121 -408 1123 -350 1139 -386 1111 -1130 389 -1114 395 -1122 395 -1114 389 -1116 395 -1122 363 -1138 387 -9444 373 -374 379 -374 381 -346 403 -346 389 -376 389 -390 353 -376 359 -382 383 -360 419 -360 359 -386 359 -2264 777 -356 1127 -390 1143 -362 1131 -1138 365 -1122 359 -386 1153 -1106 377 -1152 385 -372 1113 -1140 385 -1118 381 -1114 383 -1150 383 -1120 355 -1122 389 -358 1165 -386 1113 -1128 389 -360 1125 -384 1131 -368 1157 -350 1139 -386 1115 -406 1099 -384 1141 -1122 383 -1110 373 -1130 385 -1128 393 -380 1131 -380 1129 -1112 383 -1132 391 -356 1143 -1124 383 -1130 367 -1136 385 -1136 387 -1112 371 -1120 389 -1118 383 -1130 371 -1130 383 -1110 383 -1120 413 -1118 383 -1144 347 -1144 389 -1110 393 -1122 363 -1140 389 -1112 359 -1154 363 -1146 389 -1110 393 -374 1115 -384 1115 -1144 385 -368 1141 -388 1111 -1110 421 -360 1125 -388 1109 -392 1137 -358 1125 -1144 365 -1138 389 -358 1123 -1118 401 -1138 389 -360 1121 -1120 417 -358 1109 -1154 355 -1120 375 -1138 385 -1130 391 -1136 355 -398 1115 -380 1141 -384 1121 -382 1119 -1104 413 -1118 355 -1156 387 -1112 377 -1122 389 -1118 387 -1112 397 -9422 417 -352 363 -416 355 -388 345 -382 377 -380 375 -380 375 -380 375 -380 385 -356 379 -366 385 -374 387 -2246 745 -380 1141 -386 1113 -370 1125 -1140 373 -1152 355 -394 1117 -1140 381 -1120 385 -374 1145 -1112 385 -1122 381 -1116 383 -1120 375 -1120 389 -1120 373 -380 1171 -358 1121 -1142 377 -356 1127 -384 1137 -378 1155 -390 1105 -366 1125 -386 1135 -386 1111 -1132 389 -1112 393 -1120 365 -1138 387 -360 1163 -356 1143 -1126 387 -1114 357 -386 1141 -1126 383 -1130 365 -1132 381 -1140 377 -1116 383 -1130 371 -1128 381 -1140 347 -1148 385 -1128 369 -1128 381 -1142 377 -1114 389 -1112 395 -1124 361 -1142 389 -1114 393 -1122 365 -1138 389 -1114 397 -1108 389 -392 1119 -350 1139 -1152 355 -396 1115 -382 1109 -1156 385 -374 1111 -384 1139 -368 1147 -388 1109 -1112 389 -1120 383 -388 1107 -1150 389 -1112 +RAW_Data: 393 -390 1109 -1128 389 -360 1125 -1120 381 -1152 383 -1118 353 -1158 387 -1112 375 -386 1117 -408 1121 -350 1143 -388 1109 -1132 389 -1114 391 -1122 395 -1120 389 -1112 393 -1122 365 -1136 389 -9442 373 -376 389 -354 377 -366 387 -384 357 -378 361 -418 347 -394 385 -358 363 -382 361 -414 357 -392 333 -2290 751 -384 1113 -406 1121 -350 1137 -1136 353 -1160 385 -356 1135 -1120 361 -1146 385 -388 1137 -1108 361 -1150 387 -1112 395 -1136 349 -1154 353 -1142 371 -384 1145 -378 1117 -1138 381 -382 1087 -410 1121 -382 1143 -380 1121 -380 1115 -384 1107 -418 1115 -1106 385 -1148 365 -1118 359 -1146 387 -388 1135 -388 1113 -1126 383 -1130 367 -376 1113 -1142 383 -1114 375 -1154 355 -1160 385 -1110 371 -1152 357 -1118 385 -1146 365 -1122 361 -1146 387 -1114 395 -1134 355 -1160 351 -1146 369 -1154 355 -1120 387 -1114 397 -1136 357 -1118 407 -1144 351 -1134 359 -420 1111 -366 1131 -1142 379 -384 1089 -410 1119 -1142 379 -366 1141 -386 1109 -388 1131 -350 1141 -1118 391 -1114 375 -378 1153 -1116 385 -1136 383 -358 1139 -1120 359 -420 1099 -1142 383 -1118 383 -1138 347 -1144 385 -1144 369 -386 1113 -404 1089 -386 1141 -382 1099 -1136 381 -1128 375 -1130 383 -1140 359 -1146 387 -1114 395 -1138 357 -9430 383 -378 359 -418 347 -392 387 -360 363 -384 361 -414 357 -386 347 -384 375 -382 385 -358 383 -364 387 -2252 773 -354 1131 -384 1137 -386 1111 -1132 387 -1112 397 -388 1113 -1124 389 -1116 387 -388 1115 -1132 389 -1114 393 -1120 365 -1140 389 -1114 357 -1154 365 -378 1151 -358 1127 -1156 367 -376 1135 -358 1125 -388 1141 -368 1125 -386 1133 -388 1109 -370 1155 -1106 375 -1122 389 -1118 389 -1114 395 -386 1117 -410 1123 -1106 377 -1130 383 -388 1107 -1152 353 -1148 353 -1150 367 -1142 389 -1110 397 -1120 365 -1138 389 -1110 391 -1122 363 -1142 387 -1116 389 -1120 391 -1116 389 -1116 395 -1122 365 -1140 389 -1114 357 -1154 363 -1138 389 -1142 365 -1138 355 -396 1115 -382 1141 -1118 353 -400 1111 -384 1139 -1120 381 -386 1139 -366 1119 -392 1121 -388 1107 -1152 389 -1114 355 -388 1139 -1126 387 -1114 397 -376 1111 -1144 375 -380 1129 -1138 375 -1098 385 -1140 377 -1118 387 -1144 371 -386 1115 -404 1121 -348 1137 -386 1113 -1134 389 -1112 395 -1124 395 -1118 389 -1110 395 -1122 363 -1138 389 -9418 391 -360 407 -384 361 -388 355 -390 367 -376 373 -380 387 -356 377 -366 385 -388 355 -378 359 -384 377 -2266 777 -346 1149 -388 1107 -390 1129 -1104 415 -1118 353 -398 1113 -1138 383 -1122 381 -388 1141 -1118 389 -1112 357 -1154 365 -1138 389 -1114 357 -1154 363 -378 1155 -358 1123 -1156 381 -360 1107 -384 +RAW_Data: 1153 -378 1119 -382 1143 -382 1121 -382 1117 -382 1113 -1120 389 -1120 387 -1112 399 -1120 393 -392 1119 -350 1141 -1154 357 -1116 389 -360 1163 -1120 365 -1138 387 -1114 389 -1122 389 -1116 387 -1114 397 -1104 379 -1156 353 -1148 367 -1118 377 -1122 423 -1110 373 -1122 389 -1118 383 -1130 373 -1128 383 -1140 345 -1146 383 -1130 399 -1130 353 -1142 377 -358 1127 -384 1143 -1118 385 -372 1111 -386 1137 -1120 381 -388 1141 -364 1127 -384 1133 -374 1111 -1148 383 -1114 373 -384 1115 -1136 387 -1144 371 -386 1115 -1132 387 -360 1123 -1150 345 -1148 383 -1128 371 -1132 381 -1140 379 -390 1123 -350 1139 -388 1113 -406 1089 -1142 373 -1120 389 -1118 423 -1110 371 -1120 379 -1122 407 -1104 417 -9440 389 -356 379 -366 385 -388 355 -378 359 -384 381 -394 387 -358 361 -386 359 -416 355 -388 345 -384 377 -2262 747 -384 1149 -380 1115 -382 1113 -1120 389 -1120 389 -360 1129 -1152 367 -1136 389 -358 1131 -1152 367 -1138 389 -1116 355 -1152 367 -1134 389 -1116 353 -388 1141 -368 1123 -1138 375 -386 1113 -408 1121 -350 1175 -372 1105 -386 1145 -352 1141 -366 1145 -1114 385 -1116 377 -1122 389 -1120 421 -354 1139 -388 1109 -1132 383 -1130 369 -374 1113 -1144 385 -1114 377 -1120 391 -1128 373 -1138 385 -1130 359 -1138 377 -1120 373 -1138 383 -1130 359 -1138 379 -1160 375 -1106 385 -1130 393 -1120 377 -1118 389 -1112 393 -1140 355 -1120 421 -1114 371 -1122 391 -390 1123 -350 1139 -1134 353 -402 1113 -384 1141 -1118 385 -376 1143 -352 1161 -352 1135 -386 1113 -1132 387 -1114 395 -388 1111 -1128 387 -1114 399 -374 1115 -1142 375 -380 1117 -1118 387 -1144 363 -1136 385 -1130 367 -1130 383 -388 1107 -392 1129 -380 1115 -384 1113 -1136 389 -1114 393 -1124 393 -1120 389 -1114 393 -1124 363 -1140 389 -9416 391 -360 405 -386 329 -416 357 -392 365 -374 377 -380 343 -412 341 -412 353 -390 375 -366 385 -386 355 -2264 743 -394 1123 -388 1111 -392 1133 -1110 395 -1120 363 -382 1133 -1142 381 -1118 383 -376 1111 -1146 383 -1122 383 -1146 347 -1150 381 -1116 353 -1158 343 -410 1133 -382 1111 -1152 355 -394 1119 -382 1109 -382 1153 -378 1131 -354 1137 -396 1119 -388 1111 -1150 351 -1152 351 -1150 365 -1136 387 -356 1131 -386 1143 -1122 387 -1112 357 -420 1107 -1128 387 -1114 359 -1152 363 -1148 387 -1114 395 -1122 361 -1140 387 -1110 395 -1120 361 -1140 387 -1114 393 -1154 355 -1120 387 -1146 365 -1118 361 -1146 387 -1112 395 -1120 363 -1140 385 -1144 367 -1120 359 -420 1099 -384 1139 -1118 383 -384 1109 -392 1129 -1138 379 -366 1147 -388 1109 -386 1099 -384 1139 -1132 349 -1158 375 -380 1131 -1104 411 -1122 351 -416 1111 -1148 +RAW_Data: 353 -396 1121 -1142 347 -1150 381 -1116 355 -1156 375 -1144 387 -360 1119 -388 1107 -394 1131 -386 1101 -1152 363 -1138 387 -1112 391 -1152 357 -1116 375 -1136 383 -1122 383 -9448 357 -392 357 -398 363 -378 385 -358 383 -364 389 -386 357 -380 389 -386 347 -382 375 -384 375 -380 373 -2262 747 -376 1145 -390 1107 -386 1129 -1104 413 -1120 353 -396 1115 -1140 381 -1122 383 -376 1143 -1110 385 -1118 383 -1114 417 -1114 383 -1120 353 -1156 389 -356 1135 -386 1113 -1134 389 -358 1129 -390 1107 -392 1153 -358 1127 -388 1143 -362 1131 -356 1131 -1154 365 -1136 389 -1114 357 -1150 363 -386 1153 -358 1125 -1118 417 -1120 381 -350 1123 -1134 391 -1112 395 -1124 395 -1116 389 -1112 393 -1124 363 -1140 389 -1114 359 -1154 363 -1138 389 -1114 391 -1124 361 -1144 389 -1112 393 -1122 363 -1138 389 -1112 391 -1122 363 -1138 389 -1144 365 -1124 361 -382 1155 -350 1137 -1120 391 -386 1131 -350 1151 -1120 383 -378 1141 -352 1137 -394 1117 -390 1107 -1150 389 -1114 355 -388 1143 -1120 387 -1112 397 -388 1113 -1130 385 -344 1163 -1104 379 -1122 373 -1140 383 -1130 389 -1124 359 -386 1127 -386 1139 -368 1141 -390 1107 -1112 387 -1116 385 -1150 367 -1140 389 -1112 393 -1124 363 -1136 389 -9444 379 -340 417 -360 359 -386 359 -416 355 -386 347 -384 375 -382 375 -380 375 -378 375 -380 385 -356 379 -2278 745 -354 1151 -368 1141 -390 1105 -1114 387 -1116 385 -386 1141 -1120 389 -1114 389 -388 1113 -1130 389 -1112 393 -1124 363 -1138 389 -1112 389 -1122 363 -380 1159 -350 1137 -1122 391 -388 1097 -384 1139 -382 1125 -386 1145 -352 1141 -366 1145 -390 1107 -1110 387 -1150 353 -1150 367 -1138 387 -360 1125 -390 1109 -1152 389 -1112 357 -388 1141 -1122 389 -1110 391 -1122 395 -1112 389 -1110 397 -1120 363 -1144 389 -1114 391 -1122 365 -1138 389 -1116 389 -1142 355 -1120 389 -1112 397 -1122 363 -1140 389 -1110 393 -1130 349 -1140 405 -1134 389 -1112 357 -388 1141 -364 1129 -1142 367 -388 1111 -370 1131 -1140 383 -364 1149 -388 1109 -388 1137 -356 1127 -1118 383 -1120 413 -350 1121 -1132 407 -1140 355 -364 1149 -1112 371 -406 1129 -1104 409 -1098 383 -1116 417 -1118 381 -1118 385 -388 1111 -390 1129 -350 1137 -386 1113 -1138 389 -1114 395 -1122 393 -1120 389 -1112 393 -1124 363 -1138 389 -9444 379 -340 417 -360 359 -386 361 -414 357 -386 347 -384 375 -382 375 -380 375 -380 375 -378 373 -380 373 -2262 749 -386 1111 -408 1117 -348 1143 -1120 391 -1116 389 -358 1127 -1154 365 -1136 387 -360 1129 -1154 365 -1136 389 -1114 357 -1154 365 -1134 389 -1112 357 -390 1137 -406 1119 -1106 377 -386 1117 -406 1123 -350 1143 -384 +RAW_Data: 1149 -378 1121 -350 1145 -380 1133 -1104 375 -1136 385 -1130 359 -1138 379 -400 1129 -354 1139 -1148 355 -1150 365 -378 1119 -1146 355 -1152 365 -1134 389 -1110 397 -1122 363 -1140 389 -1110 395 -1122 363 -1142 389 -1116 357 -1152 365 -1146 389 -1112 393 -1130 349 -1138 383 -1116 413 -1120 353 -1122 387 -1114 397 -1154 355 -1124 387 -360 1133 -384 1131 -1134 383 -376 1133 -352 1133 -1132 383 -376 1139 -378 1135 -380 1115 -382 1141 -1118 353 -1160 387 -356 1133 -1118 379 -1158 353 -392 1133 -1104 379 -398 1117 -1138 383 -1118 353 -1164 353 -1146 403 -1120 353 -398 1115 -382 1143 -384 1089 -412 1121 -1106 377 -1154 355 -1118 407 -1146 351 -1130 395 -1138 355 -1118 407 -9434 353 -396 363 -380 383 -360 383 -364 389 -386 357 -414 355 -386 345 -386 375 -382 375 -380 375 -378 375 -2256 769 -384 1119 -382 1117 -380 1113 -1122 389 -1118 389 -360 1131 -1140 377 -1118 421 -354 1139 -1140 355 -1118 405 -1104 387 -1128 391 -1122 363 -1138 387 -360 1157 -354 1143 -1128 387 -360 1121 -388 1141 -362 1129 -384 1139 -388 1111 -370 1127 -384 1135 -1122 363 -1140 389 -1116 393 -1122 395 -356 1129 -384 1141 -1120 383 -1134 387 -356 1133 -1130 349 -1140 383 -1116 413 -1118 381 -1110 377 -1146 389 -1114 391 -1124 365 -1138 391 -1112 357 -1150 365 -1144 387 -1112 395 -1122 361 -1140 387 -1112 393 -1104 379 -1158 353 -1144 403 -1118 353 -1158 353 -390 1133 -390 1107 -1130 389 -358 1125 -388 1111 -1154 389 -358 1129 -386 1131 -368 1129 -382 1139 -1118 353 -1164 387 -356 1135 -1120 393 -1118 389 -358 1131 -1154 365 -376 1135 -1108 395 -1136 347 -1126 387 -1144 403 -1120 353 -398 1115 -384 1141 -372 1103 -386 1145 -1108 387 -1120 383 -1116 403 -1140 389 -1116 353 -1146 361 -1144 389 -9420 393 -362 401 -338 377 -388 385 -374 361 -382 375 -382 375 -380 373 -380 373 -380 373 -380 389 -354 379 -2272 743 -388 1137 -360 1121 -386 1111 -1152 351 -1148 359 -384 1143 -1126 387 -1114 391 -384 1117 -1130 383 -1132 369 -1134 351 -1138 377 -1142 385 -1112 359 -420 1107 -406 1121 -1104 377 -384 1119 -406 1119 -352 1171 -382 1123 -378 1119 -384 1107 -384 1119 -1136 385 -1116 393 -1120 361 -1142 387 -388 1137 -386 1113 -1128 385 -1116 357 -420 1113 -1124 387 -1114 359 -1148 395 -1116 385 -1146 363 -1116 361 -1144 387 -1144 363 -1120 361 -1144 385 -1112 393 -1152 355 -1154 353 -1144 367 -1136 355 -1158 351 -1146 369 -1120 361 -1144 385 -1148 369 -1152 357 -392 1117 -380 1107 -1152 355 -398 1119 -382 1109 -1152 385 -374 1113 -386 1139 -366 1145 -388 1111 -1110 385 -1148 353 -386 1139 -1124 387 -1142 365 -386 1113 -1132 385 -360 1125 -1144 +RAW_Data: 363 -1140 387 -1114 357 -1152 363 -1146 387 -358 1129 -386 1139 -366 1125 -384 1139 -1120 363 -1140 387 -1112 393 -1130 381 -1136 383 -1114 371 -1132 383 -116626 65 -934 133 -1954 131 -102 133 -136 97 -332 65 -430 299 -296 129 -100 265 -168 367 -100 65 -66 231 -336 9643 -7766 529 -68 467 -166 65 -134 99 -500 331 -132 65 -130 329 -98 497 -100 1195 -100 1959 -66 4163 -7346 97 -392 165 -194 97 -2978 433 -298 531 -298 65 -200 131 -132 261 -98 229 -68 12837 -340 99 -268 165 -134 65 -898 67 -100 265 -66 165 -100 597 -166 199 -298 199 -200 99 -132 233 -132 299 -132 233 -166 65 -66 4021 -168 133 -68 231 -168 4647 -130 1399 -7750 133 -1714 197 -2480 131 -200 65 -100 265 -890 63 -1152 197 -98 293 -134 65 -300 361 -100 1035 -100 231 -132 299 -100 3399 -66 6287 -4506 99 -100 65 -130 99 -196 461 -98 331 -164 97 -162 227 -64 197 -98 229 -130 195 -100 425 -526 165 -130 95 -522 457 -560 233 -98 261 -66 1155 -100 259 -130 1407 -98 553 -66 7793 -494 65 -232 65 -3652 229 -2716 361 -266 333 -200 133 -166 99 -132 267 -66 133 -132 199 -166 331 -132 331 -166 197 -950 229 -198 303 -298 365 -100 4839 -3816 165 -130 229 -696 131 -130 261 -262 97 -166 263 -894 165 -230 365 -566 129 -560 197 -324 99 -98 261 -134 131 -100 67 -334 67 -232 199 -132 165 -302 67 -100 1467 -98 459 -100 1081 -130 131 -66 8927 -232 165 -3104 99 -2812 65 -982 131 -98 195 -98 263 -264 231 -66 195 -132 193 -164 65 -100 365 -132 1629 -66 1009 -132 8383 -632 131 -3060 131 -492 425 -100 763 -166 371 -132 1197 -134 229 -694 461 -366 365 -98 329 -198 267 -168 399 -68 131 -332 493 -132 231 -132 569 -66 7765 -7568 99 -532 65 -634 133 -3540 65 -100 263 -592 261 -1484 299 -302 265 -234 1129 -304 99 -436 163 -360 97 -556 231 -166 265 -1164 165 -134 235 -100 163 -332 297 -100 197 -132 99 -566 133 -234 133 -328 295 -98 985 -98 163 -396 399 -134 1557 -134 297 -266 6875 -68 1759 -7194 133 -166 99 -266 65 -432 67 -432 393 -5086 99 -66 199 -68 263 -866 429 -100 359 -130 261 -132 267 -134 533 -134 9251 -4184 65 -1156 165 -198 65 -426 297 -492 67 -164 131 -198 259 -164 199 -100 733 -134 865 -100 397 -132 65 -100 197 -66 327 -164 227 -98 231 -132 97 -262 99 -130 229 -66 589 -96 1119 -98 1905 -7486 599 -66 561 -66 359 -98 757 -162 261 -66 323 -130 5573 -8538 99 -894 131 -594 229 -364 63 -1378 197 -1682 331 -100 199 -166 diff --git a/lib/subghz/protocols/kinggates_stylo_4k.c b/lib/subghz/protocols/kinggates_stylo_4k.c new file mode 100644 index 00000000000..2c8de0d2d21 --- /dev/null +++ b/lib/subghz/protocols/kinggates_stylo_4k.c @@ -0,0 +1,336 @@ +#include "kinggates_stylo_4k.h" +#include "keeloq_common.h" + +#include "../subghz_keystore.h" +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#define TAG "SubGhzProtocoKingGates_stylo_4k" + +static const SubGhzBlockConst subghz_protocol_kinggates_stylo_4k_const = { + .te_short = 400, + .te_long = 1100, + .te_delta = 140, + .min_count_bit_for_found = 89, +}; + +struct SubGhzProtocolDecoderKingGates_stylo_4k { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; + + uint64_t data; + uint16_t header_count; + SubGhzKeystore* keystore; +}; + +struct SubGhzProtocolEncoderKingGates_stylo_4k { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + KingGates_stylo_4kDecoderStepReset = 0, + KingGates_stylo_4kDecoderStepCheckPreambula, + KingGates_stylo_4kDecoderStepCheckStartBit, + KingGates_stylo_4kDecoderStepSaveDuration, + KingGates_stylo_4kDecoderStepCheckDuration, +} KingGates_stylo_4kDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_kinggates_stylo_4k_decoder = { + .alloc = subghz_protocol_decoder_kinggates_stylo_4k_alloc, + .free = subghz_protocol_decoder_kinggates_stylo_4k_free, + + .feed = subghz_protocol_decoder_kinggates_stylo_4k_feed, + .reset = subghz_protocol_decoder_kinggates_stylo_4k_reset, + + .get_hash_data = subghz_protocol_decoder_kinggates_stylo_4k_get_hash_data, + .serialize = subghz_protocol_decoder_kinggates_stylo_4k_serialize, + .deserialize = subghz_protocol_decoder_kinggates_stylo_4k_deserialize, + .get_string = subghz_protocol_decoder_kinggates_stylo_4k_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_kinggates_stylo_4k_encoder = { + .alloc = NULL, + .free = NULL, + + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol subghz_protocol_kinggates_stylo_4k = { + .name = SUBGHZ_PROTOCOL_KINGGATES_STYLO_4K_NAME, + .type = SubGhzProtocolTypeDynamic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable, + + .decoder = &subghz_protocol_kinggates_stylo_4k_decoder, + .encoder = &subghz_protocol_kinggates_stylo_4k_encoder, +}; + +void* subghz_protocol_decoder_kinggates_stylo_4k_alloc(SubGhzEnvironment* environment) { + SubGhzProtocolDecoderKingGates_stylo_4k* instance = + malloc(sizeof(SubGhzProtocolDecoderKingGates_stylo_4k)); + instance->base.protocol = &subghz_protocol_kinggates_stylo_4k; + instance->generic.protocol_name = instance->base.protocol->name; + instance->keystore = subghz_environment_get_keystore(environment); + return instance; +} + +void subghz_protocol_decoder_kinggates_stylo_4k_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderKingGates_stylo_4k* instance = context; + free(instance); +} + +void subghz_protocol_decoder_kinggates_stylo_4k_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderKingGates_stylo_4k* instance = context; + instance->decoder.parser_step = KingGates_stylo_4kDecoderStepReset; +} + +void subghz_protocol_decoder_kinggates_stylo_4k_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderKingGates_stylo_4k* instance = context; + + switch(instance->decoder.parser_step) { + case KingGates_stylo_4kDecoderStepReset: + if((level) && DURATION_DIFF(duration, subghz_protocol_kinggates_stylo_4k_const.te_short) < + subghz_protocol_kinggates_stylo_4k_const.te_delta) { + instance->decoder.parser_step = KingGates_stylo_4kDecoderStepCheckPreambula; + instance->header_count++; + } + break; + case KingGates_stylo_4kDecoderStepCheckPreambula: + if((!level) && + (DURATION_DIFF(duration, subghz_protocol_kinggates_stylo_4k_const.te_short) < + subghz_protocol_kinggates_stylo_4k_const.te_delta)) { + instance->decoder.parser_step = KingGates_stylo_4kDecoderStepReset; + break; + } + if((instance->header_count > 2) && + (DURATION_DIFF(duration, subghz_protocol_kinggates_stylo_4k_const.te_long * 2) < + subghz_protocol_kinggates_stylo_4k_const.te_delta * 2)) { + // Found header + instance->decoder.parser_step = KingGates_stylo_4kDecoderStepCheckStartBit; + } else { + instance->decoder.parser_step = KingGates_stylo_4kDecoderStepReset; + instance->header_count = 0; + } + break; + case KingGates_stylo_4kDecoderStepCheckStartBit: + if((level) && + DURATION_DIFF(duration, subghz_protocol_kinggates_stylo_4k_const.te_short * 2) < + subghz_protocol_kinggates_stylo_4k_const.te_delta * 2) { + instance->decoder.parser_step = KingGates_stylo_4kDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->data = 0; + instance->decoder.decode_count_bit = 0; + instance->header_count = 0; + } + break; + case KingGates_stylo_4kDecoderStepSaveDuration: + if(!level) { + if(duration >= ((uint32_t)subghz_protocol_kinggates_stylo_4k_const.te_long * 3)) { + if(instance->decoder.decode_count_bit == + subghz_protocol_kinggates_stylo_4k_const.min_count_bit_for_found) { + instance->generic.data = instance->data; + instance->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 = KingGates_stylo_4kDecoderStepReset; + instance->decoder.decode_data = 0; + instance->data = 0; + instance->decoder.decode_count_bit = 0; + instance->header_count = 0; + break; + } else { + instance->decoder.te_last = duration; + instance->decoder.parser_step = KingGates_stylo_4kDecoderStepCheckDuration; + } + } else { + instance->decoder.parser_step = KingGates_stylo_4kDecoderStepReset; + instance->header_count = 0; + } + break; + case KingGates_stylo_4kDecoderStepCheckDuration: + if(level) { + if((DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_kinggates_stylo_4k_const.te_short) < + subghz_protocol_kinggates_stylo_4k_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_kinggates_stylo_4k_const.te_long) < + subghz_protocol_kinggates_stylo_4k_const.te_delta * 2)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = KingGates_stylo_4kDecoderStepSaveDuration; + } else if( + (DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_kinggates_stylo_4k_const.te_long) < + subghz_protocol_kinggates_stylo_4k_const.te_delta * 2) && + (DURATION_DIFF(duration, subghz_protocol_kinggates_stylo_4k_const.te_short) < + subghz_protocol_kinggates_stylo_4k_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = KingGates_stylo_4kDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = KingGates_stylo_4kDecoderStepReset; + instance->header_count = 0; + } + if(instance->decoder.decode_count_bit == 53) { + instance->data = instance->decoder.decode_data; + instance->decoder.decode_data = 0; + } + } else { + instance->decoder.parser_step = KingGates_stylo_4kDecoderStepReset; + instance->header_count = 0; + } + break; + } +} + +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + * @param file_name Full path to rainbow table the file + */ +static void subghz_protocol_kinggates_stylo_4k_remote_controller( + SubGhzBlockGeneric* instance, + uint64_t data, + SubGhzKeystore* keystore) { + /** + * 9500us 12*(400/400) 2200/800|1-bit|0-bit| + * _ _ _ __ ___ _ + * ________| |_| |_..._| |_____| |_| |___| |..... + * + * 1-bit 400/1100 us + * 0-bit 1100/400 us + * + * The package consists of 89 bits of data, LSB first + * Data - 1C9037F0C80000 CE280BA00 + * S[3] S[2] 1 key S[1] S[0] 2 byte always 0 Hop[3] Hop[2] Hop[1] Hop[0] 0 + * 11100100 10000001 1 0111 11110000 11001000 00000000 00000000 11001110 00101000 00001011 10100000 0000 + * + * Encryption - keeloq Simple Learning + * key C S[3] CNT + * Decrypt - 0xEC270B9C => 0x E C 27 0B9C + * + * + * +*/ + + uint32_t hop = subghz_protocol_blocks_reverse_key(data >> 4, 32); + uint64_t fix = subghz_protocol_blocks_reverse_key(instance->data, 53); + bool ret = false; + uint32_t decrypt = 0; + instance->btn = (fix >> 17) & 0x0F; + instance->serial = ((fix >> 5) & 0xFFFF0000) | (fix & 0xFFFF); + + for + M_EACH(manufacture_code, *subghz_keystore_get_data(keystore), SubGhzKeyArray_t) { + if(manufacture_code->type == KEELOQ_LEARNING_SIMPLE) { + decrypt = subghz_protocol_keeloq_common_decrypt(hop, manufacture_code->key); + if(((decrypt >> 28) == instance->btn) && (((decrypt >> 24) & 0x0F) == 0x0C) && + (((decrypt >> 16) & 0xFF) == (instance->serial & 0xFF))) { + ret = true; + break; + } + } + } + if(ret) { + instance->cnt = decrypt & 0xFFFF; + } else { + instance->btn = 0; + instance->serial = 0; + instance->cnt = 0; + } +} + +uint8_t subghz_protocol_decoder_kinggates_stylo_4k_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderKingGates_stylo_4k* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_kinggates_stylo_4k_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderKingGates_stylo_4k* instance = context; + bool res = subghz_block_generic_serialize(&instance->generic, flipper_format, 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->data >> (i * 8)) & 0xFF; + } + + if(res && !flipper_format_write_hex(flipper_format, "Data", key_data, sizeof(uint64_t))) { + FURI_LOG_E(TAG, "Unable to add Data"); + res = false; + } + return res; + + return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +bool subghz_protocol_decoder_kinggates_stylo_4k_deserialize( + void* context, + FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderKingGates_stylo_4k* instance = context; + bool ret = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_kinggates_stylo_4k_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + uint8_t key_data[sizeof(uint64_t)] = {0}; + if(!flipper_format_read_hex(flipper_format, "Data", key_data, sizeof(uint64_t))) { + FURI_LOG_E(TAG, "Missing Data"); + break; + } + for(uint8_t i = 0; i < sizeof(uint64_t); i++) { + instance->data = instance->data << 8 | key_data[i]; + } + ret = true; + } while(false); + return ret; +} + +void subghz_protocol_decoder_kinggates_stylo_4k_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderKingGates_stylo_4k* instance = context; + subghz_protocol_kinggates_stylo_4k_remote_controller( + &instance->generic, instance->data, instance->keystore); + + furi_string_cat_printf( + output, + "%s\r\n" + "Key:0x%llX%07llX %dbit\r\n" + "Sn:0x%08lX Btn:0x%01X\r\n" + "Cnt:0x%04lX\r\n", + instance->generic.protocol_name, + instance->generic.data, + instance->data, + instance->generic.data_count_bit, + instance->generic.serial, + instance->generic.btn, + instance->generic.cnt); +} diff --git a/lib/subghz/protocols/kinggates_stylo_4k.h b/lib/subghz/protocols/kinggates_stylo_4k.h new file mode 100644 index 00000000000..c9f1cf380ad --- /dev/null +++ b/lib/subghz/protocols/kinggates_stylo_4k.h @@ -0,0 +1,74 @@ +#pragma once +#include "base.h" + +#define SUBGHZ_PROTOCOL_KINGGATES_STYLO_4K_NAME "KingGates Stylo4k" + +typedef struct SubGhzProtocolDecoderKingGates_stylo_4k SubGhzProtocolDecoderKingGates_stylo_4k; +typedef struct SubGhzProtocolEncoderKingGates_stylo_4k SubGhzProtocolEncoderKingGates_stylo_4k; + +extern const SubGhzProtocolDecoder subghz_protocol_kinggates_stylo_4k_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_kinggates_stylo_4k_encoder; +extern const SubGhzProtocol subghz_protocol_kinggates_stylo_4k; + +/** + * Allocate SubGhzProtocolDecoderKingGates_stylo_4k. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderKingGates_stylo_4k* pointer to a SubGhzProtocolDecoderKingGates_stylo_4k instance + */ +void* subghz_protocol_decoder_kinggates_stylo_4k_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderKingGates_stylo_4k. + * @param context Pointer to a SubGhzProtocolDecoderKingGates_stylo_4k instance + */ +void subghz_protocol_decoder_kinggates_stylo_4k_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderKingGates_stylo_4k. + * @param context Pointer to a SubGhzProtocolDecoderKingGates_stylo_4k instance + */ +void subghz_protocol_decoder_kinggates_stylo_4k_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderKingGates_stylo_4k instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_kinggates_stylo_4k_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderKingGates_stylo_4k instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_kinggates_stylo_4k_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderKingGates_stylo_4k. + * @param context Pointer to a SubGhzProtocolDecoderKingGates_stylo_4k instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_decoder_kinggates_stylo_4k_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderKingGates_stylo_4k. + * @param context Pointer to a SubGhzProtocolDecoderKingGates_stylo_4k instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_decoder_kinggates_stylo_4k_deserialize( + void* context, + FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderKingGates_stylo_4k instance + * @param output Resulting text + */ +void subghz_protocol_decoder_kinggates_stylo_4k_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/protocol_items.c b/lib/subghz/protocols/protocol_items.c index 3904c18118b..050904eece5 100644 --- a/lib/subghz/protocols/protocol_items.c +++ b/lib/subghz/protocols/protocol_items.c @@ -41,6 +41,7 @@ const SubGhzProtocol* subghz_protocol_registry_items[] = { &subghz_protocol_linear_delta3, &subghz_protocol_dooya, &subghz_protocol_alutech_at_4n, + &subghz_protocol_kinggates_stylo_4k, }; const SubGhzProtocolRegistry subghz_protocol_registry = { diff --git a/lib/subghz/protocols/protocol_items.h b/lib/subghz/protocols/protocol_items.h index cd9b7c6edf1..522931d2256 100644 --- a/lib/subghz/protocols/protocol_items.h +++ b/lib/subghz/protocols/protocol_items.h @@ -41,5 +41,6 @@ #include "holtek_ht12x.h" #include "dooya.h" #include "alutech_at_4n.h" +#include "kinggates_stylo_4k.h" extern const SubGhzProtocolRegistry subghz_protocol_registry; From 8f2f2d810ab4112605adee7f605613ff3c151f10 Mon Sep 17 00:00:00 2001 From: Eric Betts Date: Wed, 8 Feb 2023 10:06:42 -0800 Subject: [PATCH 395/824] Move CSN space to revent overflow (#2232) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- .../plugins/picopass/scenes/picopass_scene_read_card_success.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/plugins/picopass/scenes/picopass_scene_read_card_success.c b/applications/plugins/picopass/scenes/picopass_scene_read_card_success.c index bb170ac4503..0d1cc78c3dd 100644 --- a/applications/plugins/picopass/scenes/picopass_scene_read_card_success.c +++ b/applications/plugins/picopass/scenes/picopass_scene_read_card_success.c @@ -33,7 +33,7 @@ void picopass_scene_read_card_success_on_enter(void* context) { uint8_t csn[PICOPASS_BLOCK_LEN]; memcpy(csn, &AA1->data[PICOPASS_CSN_BLOCK_INDEX], PICOPASS_BLOCK_LEN); for(uint8_t i = 0; i < PICOPASS_BLOCK_LEN; i++) { - furi_string_cat_printf(csn_str, " %02X", csn[i]); + furi_string_cat_printf(csn_str, "%02X ", csn[i]); } // Neither of these are valid. Indicates the block was all 0x00 or all 0xff From a00508763652a424049b337b26386adc2ad4248d Mon Sep 17 00:00:00 2001 From: hedger Date: Wed, 8 Feb 2023 22:16:05 +0400 Subject: [PATCH 396/824] fbt: building fap_dist for compact gh build; accessor: fixed for latest ibutton changes (#2377) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fbt: building fap_dist as default target; accessor: fixed for latest ibutton changes * fbt: not building fap_dist as default target; github: doing fap_dist for compact builds Co-authored-by: あく --- .github/workflows/build.yml | 2 +- applications/debug/accessor/accessor_app.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a6c9219c24c..be981768444 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -188,5 +188,5 @@ jobs: set -e for TARGET in ${TARGETS}; do TARGET="$(echo "${TARGET}" | sed 's/f//')"; \ - ./fbt TARGET_HW=$TARGET DEBUG=0 COMPACT=1 updater_package + ./fbt TARGET_HW=$TARGET DEBUG=0 COMPACT=1 fap_dist updater_package done diff --git a/applications/debug/accessor/accessor_app.cpp b/applications/debug/accessor/accessor_app.cpp index 9d3708ebebf..337437d0eba 100644 --- a/applications/debug/accessor/accessor_app.cpp +++ b/applications/debug/accessor/accessor_app.cpp @@ -34,7 +34,7 @@ void AccessorApp::run(void) { AccessorApp::AccessorApp() : text_store{0} { notification = static_cast(furi_record_open(RECORD_NOTIFICATION)); - onewire_host = onewire_host_alloc(); + onewire_host = onewire_host_alloc(&ibutton_gpio); furi_hal_power_enable_otg(); } From d7ecc95de44d3b4d57a80cab045ba787807dc50b Mon Sep 17 00:00:00 2001 From: Victor Nikitchuk Date: Thu, 9 Feb 2023 06:40:04 +0300 Subject: [PATCH 397/824] Firmware fixes and improvements for flashing via blackmagic (#2321) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: quen0n Co-authored-by: あく Co-authored-by: hedger --- scripts/fbt_tools/fbt_debugopts.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/fbt_tools/fbt_debugopts.py b/scripts/fbt_tools/fbt_debugopts.py index f4b021c2025..9abe59893eb 100644 --- a/scripts/fbt_tools/fbt_debugopts.py +++ b/scripts/fbt_tools/fbt_debugopts.py @@ -47,6 +47,7 @@ def generate(env, **kw): "source ${FBT_DEBUG_DIR}/gdbinit", ], GDBOPTS_BLACKMAGIC=[ + "-q", "-ex", "monitor swdp_scan", "-ex", From 71871949ec6999ab71fff5d25be8fb18a17a8186 Mon Sep 17 00:00:00 2001 From: Patrick Cunningham Date: Wed, 8 Feb 2023 19:47:16 -0800 Subject: [PATCH 398/824] Picopass: show elite key used from dictionary (#2119) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * show elite key used from dictionary * remove space so it fits on screen Co-authored-by: あく --- .../scenes/picopass_scene_read_card_success.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/applications/plugins/picopass/scenes/picopass_scene_read_card_success.c b/applications/plugins/picopass/scenes/picopass_scene_read_card_success.c index 0d1cc78c3dd..d89a5d89bb8 100644 --- a/applications/plugins/picopass/scenes/picopass_scene_read_card_success.c +++ b/applications/plugins/picopass/scenes/picopass_scene_read_card_success.c @@ -69,6 +69,19 @@ void picopass_scene_read_card_success_on_enter(void* context) { furi_string_cat_printf(sio_str, "+SIO"); } + if(pacs->key) { + if(pacs->sio) { + furi_string_cat_printf(sio_str, " "); + } + furi_string_cat_printf(sio_str, "Key: "); + + uint8_t key[PICOPASS_BLOCK_LEN]; + memcpy(key, &pacs->key, PICOPASS_BLOCK_LEN); + for(uint8_t i = 0; i < PICOPASS_BLOCK_LEN; i++) { + furi_string_cat_printf(sio_str, "%02X", key[i]); + } + } + widget_add_button_element( widget, GuiButtonTypeLeft, From 163be139ebb7a56d495d97ca87b662dfcd8a0bb8 Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Thu, 9 Feb 2023 08:48:06 +0400 Subject: [PATCH 399/824] SubGhz: add protocol BinRAW (binarization of data quantized by the minimum correlated duration) (#2322) * SubGhz: add protocol DataRAW (binarization of data quantized by the minimum correlated duration) * SubGhz: fix name history * SubGhz: add encoder Data_RAW protocol * SubGhz: decreasing the size of the LevelDuration structure * SubGhz: history, added check that there is free RAM * SubGhz: checking for free memory, support to pass without gap * SubGhz: add running average to average the result, auto cut noise at the end of a burst * SubGhz: support for repeating sequences * SubGhz: fix secplus_v2 decoder * SubGhz: bin_RAW fix add history * SubGhz: add debug * SubGhz: debug refactoring * FURI_LOG: add FURI_LOG_RAW_x formatted string output like printf * SubGhz: fix new FURI_LOG metod * FURI_LOG: fix unit test * SubGhz: add enable/disable BinRAW protocol decoding * SubGhz: fix PVS * SubGhz: forcibly turn off the speaker when exiting SubGhz * SubGhz: adaptive adjustment to the noise level Co-authored-by: Aleksandr Kutuzov --- applications/debug/unit_tests/test_index.c | 2 +- applications/main/subghz/application.fam | 2 +- .../subghz/scenes/subghz_scene_read_raw.c | 2 +- .../subghz/scenes/subghz_scene_receiver.c | 13 + .../scenes/subghz_scene_receiver_config.c | 32 + applications/main/subghz/subghz.c | 5 +- applications/main/subghz/subghz_history.c | 40 +- applications/main/subghz/subghz_i.h | 1 + applications/main/subghz/views/receiver.c | 33 +- applications/main/subghz/views/receiver.h | 2 + .../subghz/views/subghz_frequency_analyzer.c | 1 - applications/main/subghz/views/transmitter.c | 7 +- firmware/targets/f7/api_symbols.csv | 3 +- furi/core/log.c | 31 +- furi/core/log.h | 55 +- lib/subghz/blocks/encoder.c | 23 +- lib/subghz/blocks/encoder.h | 11 +- lib/subghz/blocks/generic.c | 2 +- lib/subghz/blocks/generic.h | 2 +- lib/subghz/protocols/bin_raw.c | 1120 +++++++++++++++++ lib/subghz/protocols/bin_raw.h | 111 ++ lib/subghz/protocols/chamberlain_code.c | 5 +- lib/subghz/protocols/protocol_items.c | 1 + lib/subghz/protocols/protocol_items.h | 1 + lib/subghz/protocols/secplus_v2.c | 8 +- lib/subghz/receiver.c | 2 +- lib/subghz/types.h | 1 + lib/toolbox/level_duration.h | 4 +- 28 files changed, 1453 insertions(+), 67 deletions(-) create mode 100644 lib/subghz/protocols/bin_raw.c create mode 100644 lib/subghz/protocols/bin_raw.h diff --git a/applications/debug/unit_tests/test_index.c b/applications/debug/unit_tests/test_index.c index 2bb9c423f3c..ac71ca397ee 100644 --- a/applications/debug/unit_tests/test_index.c +++ b/applications/debug/unit_tests/test_index.c @@ -70,7 +70,7 @@ void minunit_print_progress() { } void minunit_print_fail(const char* str) { - printf(FURI_LOG_CLR_E "%s\r\n" FURI_LOG_CLR_RESET, str); + printf(_FURI_LOG_CLR_E "%s\r\n" _FURI_LOG_CLR_RESET, str); } void unit_tests_cli(Cli* cli, FuriString* args, void* context) { diff --git a/applications/main/subghz/application.fam b/applications/main/subghz/application.fam index 6df4b1a8aa9..f0dc66e89cc 100644 --- a/applications/main/subghz/application.fam +++ b/applications/main/subghz/application.fam @@ -12,7 +12,7 @@ App( ], provides=["subghz_start"], icon="A_Sub1ghz_14", - stack_size=2 * 1024, + stack_size=3 * 1024, order=10, ) diff --git a/applications/main/subghz/scenes/subghz_scene_read_raw.c b/applications/main/subghz/scenes/subghz_scene_read_raw.c index 6f95c41690e..96acc90eeee 100644 --- a/applications/main/subghz/scenes/subghz_scene_read_raw.c +++ b/applications/main/subghz/scenes/subghz_scene_read_raw.c @@ -411,5 +411,5 @@ void subghz_scene_read_raw_on_exit(void* context) { notification_message(subghz->notifications, &sequence_reset_rgb); //filter restoration - subghz_receiver_set_filter(subghz->txrx->receiver, SubGhzProtocolFlag_Decodable); + subghz_receiver_set_filter(subghz->txrx->receiver, subghz->txrx->filter); } diff --git a/applications/main/subghz/scenes/subghz_scene_receiver.c b/applications/main/subghz/scenes/subghz_scene_receiver.c index 2b01e29752f..93c369092e9 100644 --- a/applications/main/subghz/scenes/subghz_scene_receiver.c +++ b/applications/main/subghz/scenes/subghz_scene_receiver.c @@ -1,6 +1,7 @@ #include "../subghz_i.h" #include "../views/receiver.h" #include +#include static const NotificationSequence subghs_sequence_rx = { &message_green_255, @@ -143,6 +144,11 @@ void subghz_scene_receiver_on_enter(void* context) { } subghz_view_receiver_set_idx_menu(subghz->subghz_receiver, subghz->txrx->idx_menu_chosen); + //to use a universal decoder, we are looking for a link to it + subghz->txrx->decoder_result = subghz_receiver_search_decoder_base_by_name( + subghz->txrx->receiver, SUBGHZ_PROTOCOL_BIN_RAW_NAME); + furi_assert(subghz->txrx->decoder_result); + view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdReceiver); } @@ -208,6 +214,13 @@ bool subghz_scene_receiver_on_event(void* context, SceneManagerEvent event) { subghz_hopper_update(subghz); subghz_scene_receiver_update_statusbar(subghz); } + + //get RSSI + float rssi = furi_hal_subghz_get_rssi(); + subghz_receiver_rssi(subghz->subghz_receiver, rssi); + subghz_protocol_decoder_bin_raw_data_input_rssi( + (SubGhzProtocolDecoderBinRAW*)subghz->txrx->decoder_result, rssi); + switch(subghz->state_notifications) { case SubGhzNotificationStateRx: notification_message(subghz->notifications, &sequence_blink_cyan_10); diff --git a/applications/main/subghz/scenes/subghz_scene_receiver_config.c b/applications/main/subghz/scenes/subghz_scene_receiver_config.c index b49aac92292..895e4334285 100644 --- a/applications/main/subghz/scenes/subghz_scene_receiver_config.c +++ b/applications/main/subghz/scenes/subghz_scene_receiver_config.c @@ -5,6 +5,7 @@ enum SubGhzSettingIndex { SubGhzSettingIndexFrequency, SubGhzSettingIndexHopping, SubGhzSettingIndexModulation, + SubGhzSettingIndexBinRAW, SubGhzSettingIndexSound, SubGhzSettingIndexLock, SubGhzSettingIndexRAWThesholdRSSI, @@ -58,6 +59,15 @@ const uint32_t speaker_value[SPEAKER_COUNT] = { SubGhzSpeakerStateShutdown, SubGhzSpeakerStateEnable, }; +#define BIN_RAW_COUNT 2 +const char* const bin_raw_text[BIN_RAW_COUNT] = { + "OFF", + "ON", +}; +const uint32_t bin_raw_value[BIN_RAW_COUNT] = { + SubGhzProtocolFlag_Decodable, + SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_BinRAW, +}; uint8_t subghz_scene_receiver_config_next_frequency(const uint32_t value, void* context) { furi_assert(context); @@ -186,6 +196,15 @@ static void subghz_scene_receiver_config_set_speaker(VariableItem* item) { subghz->txrx->speaker_state = speaker_value[index]; } +static void subghz_scene_receiver_config_set_bin_raw(VariableItem* item) { + SubGhz* subghz = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + variable_item_set_current_value_text(item, bin_raw_text[index]); + subghz->txrx->filter = bin_raw_value[index]; + subghz_receiver_set_filter(subghz->txrx->receiver, subghz->txrx->filter); +} + static void subghz_scene_receiver_config_set_raw_threshold_rssi(VariableItem* item) { SubGhz* subghz = variable_item_get_context(item); uint8_t index = variable_item_get_current_value_index(item); @@ -254,6 +273,19 @@ void subghz_scene_receiver_config_on_enter(void* context) { variable_item_set_current_value_text( item, subghz_setting_get_preset_name(subghz->setting, value_index)); + if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAW) != + SubGhzCustomEventManagerSet) { + item = variable_item_list_add( + subghz->variable_item_list, + "Bin_RAW:", + BIN_RAW_COUNT, + subghz_scene_receiver_config_set_bin_raw, + subghz); + value_index = value_index_uint32(subghz->txrx->filter, bin_raw_value, BIN_RAW_COUNT); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, bin_raw_text[value_index]); + } + item = variable_item_list_add( subghz->variable_item_list, "Sound:", diff --git a/applications/main/subghz/subghz.c b/applications/main/subghz/subghz.c index 200be626291..25233fe21cf 100644 --- a/applications/main/subghz/subghz.c +++ b/applications/main/subghz/subghz.c @@ -194,7 +194,8 @@ SubGhz* subghz_alloc() { subghz_environment_set_protocol_registry( subghz->txrx->environment, (void*)&subghz_protocol_registry); subghz->txrx->receiver = subghz_receiver_alloc_init(subghz->txrx->environment); - subghz_receiver_set_filter(subghz->txrx->receiver, SubGhzProtocolFlag_Decodable); + subghz->txrx->filter = SubGhzProtocolFlag_Decodable; + subghz_receiver_set_filter(subghz->txrx->receiver, subghz->txrx->filter); subghz_worker_set_overrun_callback( subghz->txrx->worker, (SubGhzWorkerOverrunCallback)subghz_receiver_reset); @@ -218,6 +219,8 @@ void subghz_free(SubGhz* subghz) { subghz->rpc_ctx = NULL; } + subghz_speaker_off(subghz); + // Packet Test view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdTestPacket); subghz_test_packet_free(subghz->subghz_test_packet); diff --git a/applications/main/subghz/subghz_history.c b/applications/main/subghz/subghz_history.c index beecc6e8b26..e6c93e05cc0 100644 --- a/applications/main/subghz/subghz_history.c +++ b/applications/main/subghz/subghz_history.c @@ -5,6 +5,7 @@ #include #define SUBGHZ_HISTORY_MAX 50 +#define SUBGHZ_HISTORY_FREE_HEAP 20480 #define TAG "SubGhzHistory" typedef struct { @@ -121,8 +122,12 @@ FlipperFormat* subghz_history_get_raw_data(SubGhzHistory* instance, uint16_t idx } bool subghz_history_get_text_space_left(SubGhzHistory* instance, FuriString* output) { furi_assert(instance); + if(memmgr_get_free_heap() < SUBGHZ_HISTORY_FREE_HEAP) { + if(output != NULL) furi_string_printf(output, " Free heap LOW"); + return true; + } if(instance->last_index_write == SUBGHZ_HISTORY_MAX) { - if(output != NULL) furi_string_printf(output, "Memory is FULL"); + if(output != NULL) furi_string_printf(output, " Memory is FULL"); return true; } if(output != NULL) @@ -142,6 +147,7 @@ bool subghz_history_add_to_history( furi_assert(instance); furi_assert(context); + if(memmgr_get_free_heap() < SUBGHZ_HISTORY_FREE_HEAP) return false; if(instance->last_index_write >= SUBGHZ_HISTORY_MAX) return false; SubGhzProtocolDecoderBase* decoder_base = context; @@ -200,27 +206,31 @@ bool subghz_history_add_to_history( } uint8_t key_data[sizeof(uint64_t)] = {0}; if(!flipper_format_read_hex(item->flipper_string, "Key", key_data, sizeof(uint64_t))) { - FURI_LOG_E(TAG, "Missing Key"); - break; + FURI_LOG_D(TAG, "No Key"); } uint64_t data = 0; for(uint8_t i = 0; i < sizeof(uint64_t); i++) { data = (data << 8) | key_data[i]; } - if(!(uint32_t)(data >> 32)) { - furi_string_printf( - item->item_str, - "%s %lX", - furi_string_get_cstr(instance->tmp_string), - (uint32_t)(data & 0xFFFFFFFF)); + if(data != 0) { + if(!(uint32_t)(data >> 32)) { + furi_string_printf( + item->item_str, + "%s %lX", + furi_string_get_cstr(instance->tmp_string), + (uint32_t)(data & 0xFFFFFFFF)); + } else { + furi_string_printf( + item->item_str, + "%s %lX%08lX", + furi_string_get_cstr(instance->tmp_string), + (uint32_t)(data >> 32), + (uint32_t)(data & 0xFFFFFFFF)); + } } else { - furi_string_printf( - item->item_str, - "%s %lX%08lX", - furi_string_get_cstr(instance->tmp_string), - (uint32_t)(data >> 32), - (uint32_t)(data & 0xFFFFFFFF)); + furi_string_printf(item->item_str, "%s", furi_string_get_cstr(instance->tmp_string)); } + } while(false); furi_string_free(text); diff --git a/applications/main/subghz/subghz_i.h b/applications/main/subghz/subghz_i.h index cd33da447a6..65480c6fd00 100644 --- a/applications/main/subghz/subghz_i.h +++ b/applications/main/subghz/subghz_i.h @@ -45,6 +45,7 @@ struct SubGhzTxRx { SubGhzEnvironment* environment; SubGhzReceiver* receiver; SubGhzTransmitter* transmitter; + SubGhzProtocolFlag filter; SubGhzProtocolDecoderBase* decoder_result; FlipperFormat* fff_data; diff --git a/applications/main/subghz/views/receiver.c b/applications/main/subghz/views/receiver.c index aaec2adda7e..acc39e25810 100644 --- a/applications/main/subghz/views/receiver.c +++ b/applications/main/subghz/views/receiver.c @@ -12,6 +12,8 @@ #define MENU_ITEMS 4u #define UNLOCK_CNT 3 +#define SUBGHZ_RAW_TRESHOLD_MIN -90.0f + typedef struct { FuriString* item_str; uint8_t type; @@ -59,8 +61,24 @@ typedef struct { uint16_t list_offset; uint16_t history_item; SubGhzViewReceiverBarShow bar_show; + uint8_t u_rssi; } SubGhzViewReceiverModel; +void subghz_receiver_rssi(SubGhzViewReceiver* instance, float rssi) { + furi_assert(instance); + with_view_model( + instance->view, + SubGhzViewReceiverModel * model, + { + if(rssi < SUBGHZ_RAW_TRESHOLD_MIN) { + model->u_rssi = 0; + } else { + model->u_rssi = (uint8_t)(rssi - SUBGHZ_RAW_TRESHOLD_MIN); + } + }, + true); +} + void subghz_view_receiver_set_lock(SubGhzViewReceiver* subghz_receiver, SubGhzLock lock) { furi_assert(subghz_receiver); subghz_receiver->lock_count = 0; @@ -168,13 +186,22 @@ static void subghz_view_receiver_draw_frame(Canvas* canvas, uint16_t idx, bool s canvas_draw_dot(canvas, scrollbar ? 121 : 126, (0 + idx * FRAME_HEIGHT) + 11); } +static void subghz_view_rssi_draw(Canvas* canvas, SubGhzViewReceiverModel* model) { + for(uint8_t i = 1; i < model->u_rssi; i++) { + if(i % 5) { + canvas_draw_dot(canvas, 46 + i, 50); + canvas_draw_dot(canvas, 47 + i, 51); + canvas_draw_dot(canvas, 46 + i, 52); + } + } +} + void subghz_view_receiver_draw(Canvas* canvas, SubGhzViewReceiverModel* model) { canvas_clear(canvas); canvas_set_color(canvas, ColorBlack); canvas_set_font(canvas, FontSecondary); elements_button_left(canvas, "Config"); - canvas_draw_line(canvas, 46, 51, 125, 51); bool scrollbar = model->history_item > 4; FuriString* str_buff; @@ -206,11 +233,11 @@ void subghz_view_receiver_draw(Canvas* canvas, SubGhzViewReceiverModel* model) { if(model->history_item == 0) { canvas_draw_icon(canvas, 0, 0, &I_Scanning_123x52); canvas_set_font(canvas, FontPrimary); - canvas_draw_str(canvas, 63, 46, "Scanning..."); - canvas_draw_line(canvas, 46, 51, 125, 51); + canvas_draw_str(canvas, 63, 44, "Scanning..."); canvas_set_font(canvas, FontSecondary); } + subghz_view_rssi_draw(canvas, model); switch(model->bar_show) { case SubGhzViewReceiverBarShowLock: canvas_draw_icon(canvas, 64, 55, &I_Lock_7x8); diff --git a/applications/main/subghz/views/receiver.h b/applications/main/subghz/views/receiver.h index aab7a76c545..9b12ccfee89 100644 --- a/applications/main/subghz/views/receiver.h +++ b/applications/main/subghz/views/receiver.h @@ -8,6 +8,8 @@ typedef struct SubGhzViewReceiver SubGhzViewReceiver; typedef void (*SubGhzViewReceiverCallback)(SubGhzCustomEvent event, void* context); +void subghz_receiver_rssi(SubGhzViewReceiver* instance, float rssi); + void subghz_view_receiver_set_lock(SubGhzViewReceiver* subghz_receiver, SubGhzLock keyboard); void subghz_view_receiver_set_callback( diff --git a/applications/main/subghz/views/subghz_frequency_analyzer.c b/applications/main/subghz/views/subghz_frequency_analyzer.c index 94419084bda..325664f4a9c 100644 --- a/applications/main/subghz/views/subghz_frequency_analyzer.c +++ b/applications/main/subghz/views/subghz_frequency_analyzer.c @@ -79,7 +79,6 @@ void subghz_frequency_analyzer_draw_rssi(Canvas* canvas, uint8_t rssi, uint8_t x void subghz_frequency_analyzer_draw_log_rssi(Canvas* canvas, uint8_t rssi, uint8_t x, uint8_t y) { uint8_t column_height = 6; if(rssi) { - //rssi = rssi if(rssi > 54) rssi = 54; for(uint8_t i = 1; i < rssi; i++) { if(i % 5) { diff --git a/applications/main/subghz/views/transmitter.c b/applications/main/subghz/views/transmitter.c index 833805ccb43..4a13460a375 100644 --- a/applications/main/subghz/views/transmitter.c +++ b/applications/main/subghz/views/transmitter.c @@ -84,9 +84,10 @@ void subghz_view_transmitter_draw(Canvas* canvas, SubGhzViewTransmitterModel* mo canvas_clear(canvas); canvas_set_color(canvas, ColorBlack); canvas_set_font(canvas, FontSecondary); - elements_multiline_text(canvas, 0, 8, furi_string_get_cstr(model->key_str)); - canvas_draw_str(canvas, 78, 8, furi_string_get_cstr(model->frequency_str)); - canvas_draw_str(canvas, 113, 8, furi_string_get_cstr(model->preset_str)); + elements_multiline_text_aligned( + canvas, 0, 0, AlignLeft, AlignTop, furi_string_get_cstr(model->key_str)); + canvas_draw_str(canvas, 78, 7, furi_string_get_cstr(model->frequency_str)); + canvas_draw_str(canvas, 113, 7, furi_string_get_cstr(model->preset_str)); if(model->show_button) subghz_view_transmitter_button_right(canvas, "Send"); } diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 846041c3e9b..906ab357aaa 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1409,6 +1409,7 @@ Function,+,furi_kernel_unlock,int32_t, Function,+,furi_log_get_level,FuriLogLevel, Function,-,furi_log_init,void, Function,+,furi_log_print_format,void,"FuriLogLevel, const char*, const char*, ..." +Function,+,furi_log_print_raw_format,void,"FuriLogLevel, const char*, ..." Function,+,furi_log_set_level,void,FuriLogLevel Function,-,furi_log_set_puts,void,FuriLogPuts Function,-,furi_log_set_timestamp,void,FuriLogTimestamp @@ -2599,7 +2600,7 @@ Function,+,subghz_protocol_blocks_crc8le,uint8_t,"const uint8_t[], size_t, uint8 Function,+,subghz_protocol_blocks_get_bit_array,_Bool,"uint8_t[], size_t" Function,+,subghz_protocol_blocks_get_hash_data,uint8_t,"SubGhzBlockDecoder*, size_t" Function,+,subghz_protocol_blocks_get_parity,uint8_t,"uint64_t, uint8_t" -Function,+,subghz_protocol_blocks_get_upload,size_t,"uint8_t[], size_t, LevelDuration*, size_t, uint32_t" +Function,+,subghz_protocol_blocks_get_upload_from_bit_array,size_t,"uint8_t[], size_t, LevelDuration*, size_t, uint32_t, SubGhzProtocolBlockAlignBit" Function,+,subghz_protocol_blocks_lfsr_digest16,uint16_t,"const uint8_t[], size_t, uint16_t, uint16_t" Function,+,subghz_protocol_blocks_lfsr_digest8,uint8_t,"const uint8_t[], size_t, uint8_t, uint8_t" Function,+,subghz_protocol_blocks_lfsr_digest8_reflect,uint8_t,"const uint8_t[], size_t, uint8_t, uint8_t" diff --git a/furi/core/log.c b/furi/core/log.c index a3967ed9282..d910ecf2113 100644 --- a/furi/core/log.c +++ b/furi/core/log.c @@ -28,27 +28,27 @@ void furi_log_print_format(FuriLogLevel level, const char* tag, const char* form FuriString* string; string = furi_string_alloc(); - const char* color = FURI_LOG_CLR_RESET; + const char* color = _FURI_LOG_CLR_RESET; const char* log_letter = " "; switch(level) { case FuriLogLevelError: - color = FURI_LOG_CLR_E; + color = _FURI_LOG_CLR_E; log_letter = "E"; break; case FuriLogLevelWarn: - color = FURI_LOG_CLR_W; + color = _FURI_LOG_CLR_W; log_letter = "W"; break; case FuriLogLevelInfo: - color = FURI_LOG_CLR_I; + color = _FURI_LOG_CLR_I; log_letter = "I"; break; case FuriLogLevelDebug: - color = FURI_LOG_CLR_D; + color = _FURI_LOG_CLR_D; log_letter = "D"; break; case FuriLogLevelTrace: - color = FURI_LOG_CLR_T; + color = _FURI_LOG_CLR_T; log_letter = "T"; break; default: @@ -58,7 +58,7 @@ void furi_log_print_format(FuriLogLevel level, const char* tag, const char* form // Timestamp furi_string_printf( string, - "%lu %s[%s][%s] " FURI_LOG_CLR_RESET, + "%lu %s[%s][%s] " _FURI_LOG_CLR_RESET, furi_log.timestamp(), color, log_letter, @@ -80,6 +80,23 @@ void furi_log_print_format(FuriLogLevel level, const char* tag, const char* form } } +void furi_log_print_raw_format(FuriLogLevel level, const char* format, ...) { + if(level <= furi_log.log_level && + furi_mutex_acquire(furi_log.mutex, FuriWaitForever) == FuriStatusOk) { + FuriString* string; + string = furi_string_alloc(); + va_list args; + va_start(args, format); + furi_string_vprintf(string, format, args); + va_end(args); + + furi_log.puts(furi_string_get_cstr(string)); + furi_string_free(string); + + furi_mutex_release(furi_log.mutex); + } +} + void furi_log_set_level(FuriLogLevel level) { if(level == FuriLogLevelDefault) { level = FURI_LOG_LEVEL_DEFAULT; diff --git a/furi/core/log.h b/furi/core/log.h index cb8b3d9cc30..46ae7f00713 100644 --- a/furi/core/log.h +++ b/furi/core/log.h @@ -22,21 +22,21 @@ typedef enum { FuriLogLevelTrace = 6, } FuriLogLevel; -#define FURI_LOG_CLR(clr) "\033[0;" clr "m" -#define FURI_LOG_CLR_RESET "\033[0m" - -#define FURI_LOG_CLR_BLACK "30" -#define FURI_LOG_CLR_RED "31" -#define FURI_LOG_CLR_GREEN "32" -#define FURI_LOG_CLR_BROWN "33" -#define FURI_LOG_CLR_BLUE "34" -#define FURI_LOG_CLR_PURPLE "35" - -#define FURI_LOG_CLR_E FURI_LOG_CLR(FURI_LOG_CLR_RED) -#define FURI_LOG_CLR_W FURI_LOG_CLR(FURI_LOG_CLR_BROWN) -#define FURI_LOG_CLR_I FURI_LOG_CLR(FURI_LOG_CLR_GREEN) -#define FURI_LOG_CLR_D FURI_LOG_CLR(FURI_LOG_CLR_BLUE) -#define FURI_LOG_CLR_T FURI_LOG_CLR(FURI_LOG_CLR_PURPLE) +#define _FURI_LOG_CLR(clr) "\033[0;" clr "m" +#define _FURI_LOG_CLR_RESET "\033[0m" + +#define _FURI_LOG_CLR_BLACK "30" +#define _FURI_LOG_CLR_RED "31" +#define _FURI_LOG_CLR_GREEN "32" +#define _FURI_LOG_CLR_BROWN "33" +#define _FURI_LOG_CLR_BLUE "34" +#define _FURI_LOG_CLR_PURPLE "35" + +#define _FURI_LOG_CLR_E _FURI_LOG_CLR(_FURI_LOG_CLR_RED) +#define _FURI_LOG_CLR_W _FURI_LOG_CLR(_FURI_LOG_CLR_BROWN) +#define _FURI_LOG_CLR_I _FURI_LOG_CLR(_FURI_LOG_CLR_GREEN) +#define _FURI_LOG_CLR_D _FURI_LOG_CLR(_FURI_LOG_CLR_BLUE) +#define _FURI_LOG_CLR_T _FURI_LOG_CLR(_FURI_LOG_CLR_PURPLE) typedef void (*FuriLogPuts)(const char* data); typedef uint32_t (*FuriLogTimestamp)(void); @@ -54,6 +54,15 @@ void furi_log_init(); void furi_log_print_format(FuriLogLevel level, const char* tag, const char* format, ...) _ATTRIBUTE((__format__(__printf__, 3, 4))); +/** Print log record + * + * @param level + * @param format + * @param ... + */ +void furi_log_print_raw_format(FuriLogLevel level, const char* format, ...) + _ATTRIBUTE((__format__(__printf__, 2, 3))); + /** Set log level * * @param[in] level The level @@ -95,6 +104,22 @@ void furi_log_set_timestamp(FuriLogTimestamp timestamp); #define FURI_LOG_T(tag, format, ...) \ furi_log_print_format(FuriLogLevelTrace, tag, format, ##__VA_ARGS__) +/** Log methods + * + * @param format The raw format + * @param ... VA Args + */ +#define FURI_LOG_RAW_E(format, ...) \ + furi_log_print_raw_format(FuriLogLevelError, format, ##__VA_ARGS__) +#define FURI_LOG_RAW_W(format, ...) \ + furi_log_print_raw_format(FuriLogLevelWarn, format, ##__VA_ARGS__) +#define FURI_LOG_RAW_I(format, ...) \ + furi_log_print_raw_format(FuriLogLevelInfo, format, ##__VA_ARGS__) +#define FURI_LOG_RAW_D(format, ...) \ + furi_log_print_raw_format(FuriLogLevelDebug, format, ##__VA_ARGS__) +#define FURI_LOG_RAW_T(format, ...) \ + furi_log_print_raw_format(FuriLogLevelTrace, format, ##__VA_ARGS__) + #ifdef __cplusplus } #endif diff --git a/lib/subghz/blocks/encoder.c b/lib/subghz/blocks/encoder.c index f3349b5fcb9..49ec4f17701 100644 --- a/lib/subghz/blocks/encoder.c +++ b/lib/subghz/blocks/encoder.c @@ -2,6 +2,8 @@ #include "math.h" #include +#include "furi.h" + #define TAG "SubGhzBlockEncoder" void subghz_protocol_blocks_set_bit_array( @@ -17,21 +19,32 @@ bool subghz_protocol_blocks_get_bit_array(uint8_t data_array[], size_t read_inde return bit_read(data_array[read_index_bit >> 3], 7 - (read_index_bit & 0x7)); } -size_t subghz_protocol_blocks_get_upload( +size_t subghz_protocol_blocks_get_upload_from_bit_array( 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; + uint32_t duration_bit, + SubGhzProtocolBlockAlignBit align_bit) { + size_t bias_bit = 0; size_t size_upload = 0; uint32_t duration = duration_bit; + + if(align_bit == SubGhzProtocolBlockAlignBitRight) { + if(count_bit_data_array & 0x7) { + bias_bit = 8 - (count_bit_data_array & 0x7); + } + } + size_t index_bit = bias_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++) { + for(size_t i = 1 + bias_bit; i < count_bit_data_array + bias_bit; 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); + if(size_upload > max_size_upload) { + furi_crash("SubGhz: Encoder buffer overflow"); + } upload[size_upload++] = level_duration_make( subghz_protocol_blocks_get_bit_array(data_array, index_bit - 1), duration); last_bit = !last_bit; diff --git a/lib/subghz/blocks/encoder.h b/lib/subghz/blocks/encoder.h index 1ff0777261d..aeaa2add023 100644 --- a/lib/subghz/blocks/encoder.h +++ b/lib/subghz/blocks/encoder.h @@ -19,6 +19,11 @@ typedef struct { } SubGhzProtocolBlockEncoder; +typedef enum { + SubGhzProtocolBlockAlignBitLeft, + SubGhzProtocolBlockAlignBitRight, +} SubGhzProtocolBlockAlignBit; + /** * Set data bit when encoding HEX array. * @param bit_value The value of the bit to be set @@ -47,13 +52,15 @@ bool subghz_protocol_blocks_get_bit_array(uint8_t data_array[], size_t read_inde * @param upload Pointer to a LevelDuration * @param max_size_upload upload size, check not to overflow * @param duration_bit duration 1 bit + * @param align_bit alignment of useful bits in an array */ -size_t subghz_protocol_blocks_get_upload( +size_t subghz_protocol_blocks_get_upload_from_bit_array( uint8_t data_array[], size_t count_bit_data_array, LevelDuration* upload, size_t max_size_upload, - uint32_t duration_bit); + uint32_t duration_bit, + SubGhzProtocolBlockAlignBit align_bit); #ifdef __cplusplus } diff --git a/lib/subghz/blocks/generic.c b/lib/subghz/blocks/generic.c index 94114676d0c..3d59adc82cf 100644 --- a/lib/subghz/blocks/generic.c +++ b/lib/subghz/blocks/generic.c @@ -100,7 +100,7 @@ bool subghz_block_generic_deserialize(SubGhzBlockGeneric* instance, FlipperForma FURI_LOG_E(TAG, "Missing Bit"); break; } - instance->data_count_bit = (uint8_t)temp_data; + instance->data_count_bit = (uint16_t)temp_data; uint8_t key_data[sizeof(uint64_t)] = {0}; if(!flipper_format_read_hex(flipper_format, "Key", key_data, sizeof(uint64_t))) { diff --git a/lib/subghz/blocks/generic.h b/lib/subghz/blocks/generic.h index d1c7dc356e0..284df51aebb 100644 --- a/lib/subghz/blocks/generic.h +++ b/lib/subghz/blocks/generic.h @@ -19,7 +19,7 @@ struct SubGhzBlockGeneric { const char* protocol_name; uint64_t data; uint32_t serial; - uint8_t data_count_bit; + uint16_t data_count_bit; uint8_t btn; uint32_t cnt; }; diff --git a/lib/subghz/protocols/bin_raw.c b/lib/subghz/protocols/bin_raw.c new file mode 100644 index 00000000000..c3f54411090 --- /dev/null +++ b/lib/subghz/protocols/bin_raw.c @@ -0,0 +1,1120 @@ +#include "bin_raw.h" + +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" +#include +#include +#include + +#define TAG "SubGhzProtocolBinRAW" + +//change very carefully, RAM ends at the most inopportune moment +#define BIN_RAW_BUF_RAW_SIZE 2048 +#define BIN_RAW_BUF_DATA_SIZE 512 + +#define BIN_RAW_THRESHOLD_RSSI -85.0f +#define BIN_RAW_DELTA_RSSI 7.0f +#define BIN_RAW_SEARCH_CLASSES 20 +#define BIN_RAW_TE_MIN_COUNT 40 +#define BIN_RAW_BUF_MIN_DATA_COUNT 128 +#define BIN_RAW_MAX_MARKUP_COUNT 20 + +//#define BIN_RAW_DEBUG + +#ifdef BIN_RAW_DEBUG +#define bin_raw_debug(...) FURI_LOG_RAW_D(__VA_ARGS__) +#define bin_raw_debug_tag(tag, ...) \ + FURI_LOG_RAW_D("\033[0;32m[" tag "]\033[0m "); \ + FURI_LOG_RAW_D(__VA_ARGS__) +#else +#define bin_raw_debug(...) +#define bin_raw_debug_tag(...) +#endif + +static const SubGhzBlockConst subghz_protocol_bin_raw_const = { + .te_short = 30, + .te_long = 65000, + .te_delta = 0, + .min_count_bit_for_found = 0, +}; + +typedef enum { + BinRAWDecoderStepReset = 0, + BinRAWDecoderStepWrite, + BinRAWDecoderStepBufFull, + BinRAWDecoderStepNoParse, +} BinRAWDecoderStep; + +typedef enum { + BinRAWTypeUnknown = 0, + BinRAWTypeNoGap, + BinRAWTypeGap, + BinRAWTypeGapRecurring, + BinRAWTypeGapRolling, + BinRAWTypeGapUnknown, +} BinRAWType; + +struct BinRAW_Markup { + uint16_t byte_bias; + uint16_t bit_count; +}; +typedef struct BinRAW_Markup BinRAW_Markup; + +struct SubGhzProtocolDecoderBinRAW { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; + int32_t* data_raw; + uint8_t* data; + BinRAW_Markup data_markup[BIN_RAW_MAX_MARKUP_COUNT]; + size_t data_raw_ind; + uint32_t te; + float adaptive_threshold_rssi; +}; + +struct SubGhzProtocolEncoderBinRAW { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; + + uint8_t* data; + BinRAW_Markup data_markup[BIN_RAW_MAX_MARKUP_COUNT]; + uint32_t te; +}; + +const SubGhzProtocolDecoder subghz_protocol_bin_raw_decoder = { + .alloc = subghz_protocol_decoder_bin_raw_alloc, + .free = subghz_protocol_decoder_bin_raw_free, + + .feed = subghz_protocol_decoder_bin_raw_feed, + .reset = subghz_protocol_decoder_bin_raw_reset, + + .get_hash_data = subghz_protocol_decoder_bin_raw_get_hash_data, + .serialize = subghz_protocol_decoder_bin_raw_serialize, + .deserialize = subghz_protocol_decoder_bin_raw_deserialize, + .get_string = subghz_protocol_decoder_bin_raw_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_bin_raw_encoder = { + .alloc = subghz_protocol_encoder_bin_raw_alloc, + .free = subghz_protocol_encoder_bin_raw_free, + + .deserialize = subghz_protocol_encoder_bin_raw_deserialize, + .stop = subghz_protocol_encoder_bin_raw_stop, + .yield = subghz_protocol_encoder_bin_raw_yield, +}; + +const SubGhzProtocol subghz_protocol_bin_raw = { + .name = SUBGHZ_PROTOCOL_BIN_RAW_NAME, + .type = SubGhzProtocolTypeStatic, +#ifdef BIN_RAW_DEBUG + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, +#else + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_BinRAW | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, +#endif + .decoder = &subghz_protocol_bin_raw_decoder, + .encoder = &subghz_protocol_bin_raw_encoder, +}; + +static uint16_t subghz_protocol_bin_raw_get_full_byte(uint16_t bit_count) { + if(bit_count & 0x7) { + return (bit_count >> 3) + 1; + } else { + return (bit_count >> 3); + } +} + +void* subghz_protocol_encoder_bin_raw_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderBinRAW* instance = malloc(sizeof(SubGhzProtocolEncoderBinRAW)); + + instance->base.protocol = &subghz_protocol_bin_raw; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->encoder.repeat = 10; + instance->encoder.size_upload = BIN_RAW_BUF_DATA_SIZE * 5; + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->data = malloc(instance->encoder.size_upload * sizeof(uint8_t)); + memset(instance->data_markup, 0x00, BIN_RAW_MAX_MARKUP_COUNT * sizeof(BinRAW_Markup)); + instance->encoder.is_running = false; + return instance; +} + +void subghz_protocol_encoder_bin_raw_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderBinRAW* instance = context; + free(instance->encoder.upload); + free(instance->data); + free(instance); +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderBinRAW instance + * @return true On success + */ +static bool subghz_protocol_encoder_bin_raw_get_upload(SubGhzProtocolEncoderBinRAW* instance) { + furi_assert(instance); + + //we glue all the pieces of the package into 1 long sequence with left alignment, + //in the uploaded data we have right alignment. + + bin_raw_debug_tag(TAG, "Recovery of offset bits in sequences\r\n"); + uint16_t i = 0; + uint16_t ind = 0; + bin_raw_debug("\tind byte_bias\tbit_count\tbit_bias\r\n"); + while((i < BIN_RAW_MAX_MARKUP_COUNT) && (instance->data_markup[i].bit_count != 0)) { + uint8_t bit_bias = + subghz_protocol_bin_raw_get_full_byte(instance->data_markup[i].bit_count) * 8 - + instance->data_markup[i].bit_count; + bin_raw_debug( + "\t%d\t%d\t%d :\t\t%d\r\n", + i, + instance->data_markup[i].byte_bias, + instance->data_markup[i].bit_count, + bit_bias); + for(uint16_t y = instance->data_markup[i].byte_bias * 8; + y < instance->data_markup[i].byte_bias * 8 + + subghz_protocol_bin_raw_get_full_byte(instance->data_markup[i].bit_count) * 8 - + bit_bias; + y++) { + subghz_protocol_blocks_set_bit_array( + subghz_protocol_blocks_get_bit_array(instance->data, y + bit_bias), + instance->data, + ind++, + BIN_RAW_BUF_DATA_SIZE); + } + i++; + } + bin_raw_debug("\r\n"); +#ifdef BIN_RAW_DEBUG + bin_raw_debug_tag(TAG, "Restored Sequence left aligned\r\n"); + for(uint16_t y = 0; y < subghz_protocol_bin_raw_get_full_byte(ind); y++) { + bin_raw_debug("%02X ", instance->data[y]); + } + bin_raw_debug("\r\n\tbin_count_result= %d\r\n\r\n", ind); + + bin_raw_debug_tag( + TAG, "Maximum levels encoded in upload %zu\r\n", instance->encoder.size_upload); +#endif + instance->encoder.size_upload = subghz_protocol_blocks_get_upload_from_bit_array( + instance->data, + ind, + instance->encoder.upload, + instance->encoder.size_upload, + instance->te, + SubGhzProtocolBlockAlignBitLeft); + + bin_raw_debug_tag(TAG, "The result %zu is levels\r\n", instance->encoder.size_upload); + bin_raw_debug_tag(TAG, "Remaining free memory %zu\r\n", memmgr_get_free_heap()); + return true; +} + +bool subghz_protocol_encoder_bin_raw_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderBinRAW* instance = context; + + bool res = false; + uint32_t temp_data = 0; + + do { + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + if(!flipper_format_read_uint32(flipper_format, "Bit", (uint32_t*)&temp_data, 1)) { + FURI_LOG_E(TAG, "Missing Bit"); + break; + } + + instance->generic.data_count_bit = (uint16_t)temp_data; + + if(!flipper_format_read_uint32(flipper_format, "TE", (uint32_t*)&instance->te, 1)) { + FURI_LOG_E(TAG, "Missing TE"); + break; + } + + temp_data = 0; + uint16_t ind = 0; + uint16_t byte_bias = 0; + uint16_t byte_count = 0; + memset(instance->data_markup, 0x00, BIN_RAW_MAX_MARKUP_COUNT * sizeof(BinRAW_Markup)); + while(flipper_format_read_uint32(flipper_format, "Bit_RAW", (uint32_t*)&temp_data, 1)) { + if(ind >= BIN_RAW_MAX_MARKUP_COUNT) { + FURI_LOG_E(TAG, "Markup overflow"); + break; + } + byte_count += subghz_protocol_bin_raw_get_full_byte(temp_data); + if(byte_count > BIN_RAW_BUF_DATA_SIZE) { + FURI_LOG_E(TAG, "Receive buffer overflow"); + break; + } + + instance->data_markup[ind].bit_count = temp_data; + instance->data_markup[ind].byte_bias = byte_bias; + byte_bias += subghz_protocol_bin_raw_get_full_byte(temp_data); + + if(!flipper_format_read_hex( + flipper_format, + "Data_RAW", + instance->data + instance->data_markup[ind].byte_bias, + subghz_protocol_bin_raw_get_full_byte(temp_data))) { + instance->data_markup[ind].bit_count = 0; + FURI_LOG_E(TAG, "Missing Data_RAW"); + break; + } + ind++; + } + +#ifdef BIN_RAW_DEBUG + uint16_t i = 0; + bin_raw_debug_tag(TAG, "Download data to encoder\r\n"); + bin_raw_debug("\tind byte_bias\tbit_count\t\tbin_data"); + while((i < BIN_RAW_MAX_MARKUP_COUNT) && (instance->data_markup[i].bit_count != 0)) { + bin_raw_debug( + "\r\n\t%d\t%d\t%d :\t", + i, + instance->data_markup[i].byte_bias, + instance->data_markup[i].bit_count); + for(uint16_t y = instance->data_markup[i].byte_bias; + y < instance->data_markup[i].byte_bias + + subghz_protocol_bin_raw_get_full_byte(instance->data_markup[i].bit_count); + y++) { + bin_raw_debug("%02X ", instance->data[y]); + } + i++; + } + bin_raw_debug("\r\n\r\n"); +#endif + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + if(!subghz_protocol_encoder_bin_raw_get_upload(instance)) break; + instance->encoder.is_running = true; + + res = true; + } while(0); + + return res; +} + +void subghz_protocol_encoder_bin_raw_stop(void* context) { + SubGhzProtocolEncoderBinRAW* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_bin_raw_yield(void* context) { + SubGhzProtocolEncoderBinRAW* instance = context; + + if(instance->encoder.repeat == 0 || !instance->encoder.is_running) { + instance->encoder.is_running = 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_bin_raw_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderBinRAW* instance = malloc(sizeof(SubGhzProtocolDecoderBinRAW)); + instance->base.protocol = &subghz_protocol_bin_raw; + instance->generic.protocol_name = instance->base.protocol->name; + instance->data_raw_ind = 0; + instance->data_raw = malloc(BIN_RAW_BUF_RAW_SIZE * sizeof(int32_t)); + instance->data = malloc(BIN_RAW_BUF_RAW_SIZE * sizeof(uint8_t)); + memset(instance->data_markup, 0x00, BIN_RAW_MAX_MARKUP_COUNT * sizeof(BinRAW_Markup)); + instance->adaptive_threshold_rssi = BIN_RAW_THRESHOLD_RSSI; + return instance; +} + +void subghz_protocol_decoder_bin_raw_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderBinRAW* instance = context; + free(instance->data_raw); + free(instance->data); + free(instance); +} + +void subghz_protocol_decoder_bin_raw_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderBinRAW* instance = context; +#ifdef BIN_RAW_DEBUG + UNUSED(instance); +#else + instance->decoder.parser_step = BinRAWDecoderStepNoParse; + instance->data_raw_ind = 0; +#endif +} + +void subghz_protocol_decoder_bin_raw_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderBinRAW* instance = context; + + if(instance->decoder.parser_step == BinRAWDecoderStepWrite) { + if(instance->data_raw_ind == BIN_RAW_BUF_RAW_SIZE) { + instance->decoder.parser_step = BinRAWDecoderStepBufFull; + } else { + instance->data_raw[instance->data_raw_ind++] = (level ? duration : -duration); + } + } +} + +/** + * Analysis of received data + * @param instance Pointer to a SubGhzProtocolDecoderBinRAW* instance + */ +static bool + subghz_protocol_bin_raw_check_remote_controller(SubGhzProtocolDecoderBinRAW* instance) { + struct { + float data; + uint16_t count; + } classes[BIN_RAW_SEARCH_CLASSES]; + + size_t ind = 0; + + memset(classes, 0x00, sizeof(classes)); + + uint16_t data_markup_ind = 0; + memset(instance->data_markup, 0x00, BIN_RAW_MAX_MARKUP_COUNT * sizeof(BinRAW_Markup)); + + if(instance->data_raw_ind < 512) { + ind = + instance->data_raw_ind - + 100; //there is usually garbage at the end of the record, we exclude it from the classification + } else { + ind = 512; + } + + //sort the durations to find the shortest correlated interval + for(size_t i = 0; i < ind; i++) { + for(size_t k = 0; k < BIN_RAW_SEARCH_CLASSES; k++) { + if(classes[k].count == 0) { + classes[k].data = (float)(abs(instance->data_raw[i])); + classes[k].count++; + break; + } else if( + DURATION_DIFF((float)(abs(instance->data_raw[i])), (classes[k].data)) < + (classes[k].data / 4)) { //if the test value does not differ by more than 25% + classes[k].data += ((float)(abs(instance->data_raw[i])) - classes[k].data) * + 0.05f; //running average k=0.05 + classes[k].count++; + break; + } + } + } + + // if(classes[BIN_RAW_SEARCH_CLASSES - 1].count != 0) { + // //filling the classifier, it means that they received an unclean signal + // return false; + // } + + //looking for the minimum te with an occurrence greater than BIN_RAW_TE_MIN_COUNT + instance->te = subghz_protocol_bin_raw_const.te_long * 2; + + bool te_ok = false; + uint16_t gap_ind = 0; + uint16_t gap_delta = 0; + uint32_t gap = 0; + int data_temp = 0; + BinRAWType bin_raw_type = BinRAWTypeUnknown; + + //sort by number of occurrences + bool swap = true; + while(swap) { + swap = false; + for(size_t i = 1; i < BIN_RAW_SEARCH_CLASSES; i++) { + if(classes[i].count > classes[i - 1].count) { + uint32_t data = classes[i - 1].data; + uint32_t count = classes[i - 1].count; + classes[i - 1].data = classes[i].data; + classes[i - 1].count = classes[i].count; + classes[i].data = data; + classes[i].count = count; + swap = true; + } + } + } +#ifdef BIN_RAW_DEBUG + bin_raw_debug_tag(TAG, "Sorted durations\r\n"); + bin_raw_debug("\t\tind\tcount\tus\r\n"); + for(size_t k = 0; k < BIN_RAW_SEARCH_CLASSES; k++) { + bin_raw_debug("\t\t%zu\t%u\t%lu\r\n", k, classes[k].count, (uint32_t)classes[k].data); + } + bin_raw_debug("\r\n"); +#endif + if((classes[0].count > BIN_RAW_TE_MIN_COUNT) && (classes[1].count == 0)) { + //adopted only the preamble + instance->te = (uint32_t)classes[0].data; + te_ok = true; + gap = 0; //gap no + } else { + //take the 2 most common durations + //check that there are enough + if((classes[0].count < BIN_RAW_TE_MIN_COUNT) || + (classes[1].count < (BIN_RAW_TE_MIN_COUNT >> 1))) + return false; + //arrange the first 2 date values in ascending order + if(classes[0].data > classes[1].data) { + uint32_t data = classes[1].data; + classes[0].data = classes[1].data; + classes[1].data = data; + } + + //determine the value to be corrected + for(uint8_t k = 1; k < 5; k++) { + float delta = (classes[1].data / (classes[0].data / k)); + bin_raw_debug_tag(TAG, "K_div= %f\r\n", (double)(delta)); + delta -= (uint32_t)delta; + + if((delta < 0.20f) || (delta > 0.80f)) { + instance->te = (uint32_t)classes[0].data / k; + bin_raw_debug_tag(TAG, "K= %d\r\n", k); + te_ok = true; //found a correlated duration + break; + } + } + if(!te_ok) { + //did not find the minimum TE satisfying the condition + return false; + } + bin_raw_debug_tag(TAG, "TE= %lu\r\n\r\n", instance->te); + + //looking for a gap + for(size_t k = 2; k < BIN_RAW_SEARCH_CLASSES; k++) { + if((classes[k].count > 2) && (classes[k].data > gap)) { + gap = (uint32_t)classes[k].data; + gap_delta = gap / 5; //calculate 20% deviation from ideal value + } + } + + if((gap / instance->te) < + 10) { //make an assumption, the longest gap should be more than 10 TE + gap = 0; //check that our signal has a gap greater than 10*TE + bin_raw_type = BinRAWTypeNoGap; + } else { + bin_raw_type = BinRAWTypeGap; + //looking for the last occurrence of gap + ind = instance->data_raw_ind - 1; + while((ind > 0) && (DURATION_DIFF(abs(instance->data_raw[ind]), gap) > gap_delta)) { + ind--; + } + gap_ind = ind; + } + } + + //if we consider that there is a gap, then we divide the signal with respect to this gap + //processing input data from the end + if(bin_raw_type == BinRAWTypeGap) { + bin_raw_debug_tag(TAG, "Tinted sequence\r\n"); + ind = (BIN_RAW_BUF_DATA_SIZE * 8); + uint16_t bit_count = 0; + do { + gap_ind--; + data_temp = (int)(round((float)(instance->data_raw[gap_ind]) / instance->te)); + bin_raw_debug("%d ", data_temp); + if(data_temp == 0) bit_count++; //there is noise in the package + for(size_t i = 0; i < abs(data_temp); i++) { + bit_count++; + if(ind) { + ind--; + } else { + break; + } + if(data_temp > 0) { + subghz_protocol_blocks_set_bit_array( + true, instance->data, ind, BIN_RAW_BUF_DATA_SIZE); + } else { + subghz_protocol_blocks_set_bit_array( + false, instance->data, ind, BIN_RAW_BUF_DATA_SIZE); + } + } + //split into full bytes if gap is caught + if(DURATION_DIFF(abs(instance->data_raw[gap_ind]), gap) < gap_delta) { + instance->data_markup[data_markup_ind].byte_bias = ind >> 3; + instance->data_markup[data_markup_ind++].bit_count = bit_count; + bit_count = 0; + + if(data_markup_ind == BIN_RAW_MAX_MARKUP_COUNT) break; + ind &= 0xFFFFFFF8; //jump to the pre whole byte + } + } while(gap_ind != 0); + if((data_markup_ind != BIN_RAW_MAX_MARKUP_COUNT) && (ind != 0)) { + instance->data_markup[data_markup_ind].byte_bias = ind >> 3; + instance->data_markup[data_markup_ind++].bit_count = bit_count; + } + + bin_raw_debug("\r\n\t count bit= %zu\r\n\r\n", (BIN_RAW_BUF_DATA_SIZE * 8) - ind); + + //reset the classifier and classify the received data + memset(classes, 0x00, sizeof(classes)); + + bin_raw_debug_tag(TAG, "Sort the found pieces by the number of bits in them\r\n"); + for(size_t i = 0; i < data_markup_ind; i++) { + for(size_t k = 0; k < BIN_RAW_SEARCH_CLASSES; k++) { + if(classes[k].count == 0) { + classes[k].data = instance->data_markup[i].bit_count; + classes[k].count++; + break; + } else if(instance->data_markup[i].bit_count == (uint16_t)classes[k].data) { + classes[k].count++; + break; + } + } + } + +#ifdef BIN_RAW_DEBUG + bin_raw_debug("\t\tind\tcount\tus\r\n"); + for(size_t k = 0; k < BIN_RAW_SEARCH_CLASSES; k++) { + bin_raw_debug("\t\t%zu\t%u\t%lu\r\n", k, classes[k].count, (uint32_t)classes[k].data); + } + bin_raw_debug("\r\n"); +#endif + + //choose the value with the maximum repetition + data_temp = 0; + for(size_t i = 0; i < BIN_RAW_SEARCH_CLASSES; i++) { + if((classes[i].count > 1) && (data_temp < classes[i].count)) + data_temp = (int)classes[i].data; + } + + //if(data_markup_ind == 0) return false; + +#ifdef BIN_RAW_DEBUG + //output in reverse order + bin_raw_debug_tag(TAG, "Found sequences\r\n"); + bin_raw_debug("\tind byte_bias\tbit_count\t\tbin_data\r\n"); + uint16_t data_markup_ind_temp = data_markup_ind; + if(data_markup_ind) { + data_markup_ind_temp--; + for(size_t i = (ind / 8); i < BIN_RAW_BUF_DATA_SIZE; i++) { + if(instance->data_markup[data_markup_ind_temp].byte_bias == i) { + bin_raw_debug( + "\r\n\t%d\t%d\t%d :\t", + data_markup_ind_temp, + instance->data_markup[data_markup_ind_temp].byte_bias, + instance->data_markup[data_markup_ind_temp].bit_count); + if(data_markup_ind_temp != 0) data_markup_ind_temp--; + } + bin_raw_debug("%02X ", instance->data[i]); + } + bin_raw_debug("\r\n\r\n"); + } + //compare data in chunks with the same number of bits + bin_raw_debug_tag(TAG, "Analyze sequences of long %d bit\r\n\r\n", data_temp); +#endif + + //if(data_temp == 0) data_temp = (int)classes[0].data; + + if(data_temp != 0) { + //check that data in transmission is repeated every packet + for(uint16_t i = 0; i < data_markup_ind - 1; i++) { + if((instance->data_markup[i].bit_count == data_temp) && + (instance->data_markup[i + 1].bit_count == data_temp)) { + //if the number of bits in adjacent parcels is the same, compare the data + bin_raw_debug_tag( + TAG, + "Comparison of neighboring sequences ind_1=%d ind_2=%d %02X=%02X .... %02X=%02X\r\n", + i, + i + 1, + instance->data[instance->data_markup[i].byte_bias], + instance->data[instance->data_markup[i + 1].byte_bias], + instance->data + [instance->data_markup[i].byte_bias + + subghz_protocol_bin_raw_get_full_byte( + instance->data_markup[i].bit_count) - + 1], + instance->data + [instance->data_markup[i + 1].byte_bias + + subghz_protocol_bin_raw_get_full_byte( + instance->data_markup[i + 1].bit_count) - + 1]); + + uint16_t byte_count = + subghz_protocol_bin_raw_get_full_byte(instance->data_markup[i].bit_count); + if(memcmp( + instance->data + instance->data_markup[i].byte_bias, + instance->data + instance->data_markup[i + 1].byte_bias, + byte_count - 1) == 0) { + bin_raw_debug_tag( + TAG, "Match found bin_raw_type=BinRAWTypeGapRecurring\r\n\r\n"); + + //place in 1 element the offset to valid data + instance->data_markup[0].bit_count = instance->data_markup[i].bit_count; + instance->data_markup[0].byte_bias = instance->data_markup[i].byte_bias; + //markup end sign + instance->data_markup[1].bit_count = 0; + instance->data_markup[1].byte_bias = 0; + + bin_raw_type = BinRAWTypeGapRecurring; + i = data_markup_ind; + break; + } + } + } + } + + if(bin_raw_type == BinRAWTypeGap) { + // check that retry occurs every n packets + for(uint16_t i = 0; i < data_markup_ind - 2; i++) { + uint16_t byte_count = + subghz_protocol_bin_raw_get_full_byte(instance->data_markup[i].bit_count); + for(uint16_t y = i + 1; y < data_markup_ind - 1; y++) { + bin_raw_debug_tag( + TAG, + "Comparison every N sequences ind_1=%d ind_2=%d %02X=%02X .... %02X=%02X\r\n", + i, + y, + instance->data[instance->data_markup[i].byte_bias], + instance->data[instance->data_markup[y].byte_bias], + instance->data + [instance->data_markup[i].byte_bias + + subghz_protocol_bin_raw_get_full_byte( + instance->data_markup[i].bit_count) - + 1], + instance->data + [instance->data_markup[y].byte_bias + + subghz_protocol_bin_raw_get_full_byte( + instance->data_markup[y].bit_count) - + 1]); + + if(byte_count == + subghz_protocol_bin_raw_get_full_byte( + instance->data_markup[y].bit_count)) { //if the length in bytes matches + + if((memcmp( + instance->data + instance->data_markup[i].byte_bias, + instance->data + instance->data_markup[y].byte_bias, + byte_count - 1) == 0) && + (memcmp( + instance->data + instance->data_markup[i + 1].byte_bias, + instance->data + instance->data_markup[y + 1].byte_bias, + byte_count - 1) == 0)) { + uint8_t index = 0; +#ifdef BIN_RAW_DEBUG + bin_raw_debug_tag( + TAG, "Match found bin_raw_type=BinRAWTypeGapRolling\r\n\r\n"); + //output in reverse order + bin_raw_debug("\tind byte_bias\tbit_count\t\tbin_data\r\n"); + index = y - 1; + for(size_t z = instance->data_markup[y].byte_bias + byte_count; + z < instance->data_markup[i].byte_bias + byte_count; + z++) { + if(instance->data_markup[index].byte_bias == z) { + bin_raw_debug( + "\r\n\t%d\t%d\t%d :\t", + index, + instance->data_markup[index].byte_bias, + instance->data_markup[index].bit_count); + if(index != 0) index--; + } + bin_raw_debug("%02X ", instance->data[z]); + } + + bin_raw_debug("\r\n\r\n"); +#endif + //todo can be optimized + BinRAW_Markup markup_temp[BIN_RAW_MAX_MARKUP_COUNT]; + memcpy( + markup_temp, + instance->data_markup, + BIN_RAW_MAX_MARKUP_COUNT * sizeof(BinRAW_Markup)); + memset( + instance->data_markup, + 0x00, + BIN_RAW_MAX_MARKUP_COUNT * sizeof(BinRAW_Markup)); + + for(index = i; index < y; index++) { + instance->data_markup[index - i].bit_count = + markup_temp[y - index - 1].bit_count; + instance->data_markup[index - i].byte_bias = + markup_temp[y - index - 1].byte_bias; + } + + bin_raw_type = BinRAWTypeGapRolling; + i = data_markup_ind; + break; + } + } + } + } + } + //todo can be optimized + if(bin_raw_type == BinRAWTypeGap) { + if(data_temp != 0) { //there are sequences with the same number of bits + + BinRAW_Markup markup_temp[BIN_RAW_MAX_MARKUP_COUNT]; + memcpy( + markup_temp, + instance->data_markup, + BIN_RAW_MAX_MARKUP_COUNT * sizeof(BinRAW_Markup)); + memset( + instance->data_markup, 0x00, BIN_RAW_MAX_MARKUP_COUNT * sizeof(BinRAW_Markup)); + uint16_t byte_count = subghz_protocol_bin_raw_get_full_byte(data_temp); + uint16_t index = 0; + uint16_t it = BIN_RAW_MAX_MARKUP_COUNT; + do { + it--; + if(subghz_protocol_bin_raw_get_full_byte(markup_temp[it].bit_count) == + byte_count) { + instance->data_markup[index].bit_count = markup_temp[it].bit_count; + instance->data_markup[index].byte_bias = markup_temp[it].byte_bias; + index++; + bin_raw_type = BinRAWTypeGapUnknown; + } + } while(it != 0); + } + } + + if(bin_raw_type != BinRAWTypeGap) + return true; + else + return false; + + } else { + // if bin_raw_type == BinRAWTypeGap + bin_raw_debug_tag(TAG, "Sequence analysis without gap\r\n"); + ind = 0; + for(size_t i = 0; i < instance->data_raw_ind; i++) { + int data_temp = (int)(round((float)(instance->data_raw[i]) / instance->te)); + if(data_temp == 0) break; //found an interval 2 times shorter than TE, this is noise + bin_raw_debug("%d ", data_temp); + + for(size_t k = 0; k < abs(data_temp); k++) { + if(data_temp > 0) { + subghz_protocol_blocks_set_bit_array( + true, instance->data, ind++, BIN_RAW_BUF_DATA_SIZE); + } else { + subghz_protocol_blocks_set_bit_array( + false, instance->data, ind++, BIN_RAW_BUF_DATA_SIZE); + } + if(ind == BIN_RAW_BUF_DATA_SIZE * 8) { + i = instance->data_raw_ind; + break; + } + } + } + + if(ind != 0) { + bin_raw_type = BinRAWTypeNoGap; + //right alignment + uint8_t bit_bias = (subghz_protocol_bin_raw_get_full_byte(ind) << 3) - ind; +#ifdef BIN_RAW_DEBUG + bin_raw_debug( + "\r\n\t count bit= %zu\tcount full byte= %d\tbias bit= %d\r\n\r\n", + ind, + subghz_protocol_bin_raw_get_full_byte(ind), + bit_bias); + + for(size_t i = 0; i < subghz_protocol_bin_raw_get_full_byte(ind); i++) { + bin_raw_debug("%02X ", instance->data[i]); + } + bin_raw_debug("\r\n\r\n"); +#endif + //checking that the received sequence contains useful data + bool data_check = false; + for(size_t i = 0; i < subghz_protocol_bin_raw_get_full_byte(ind); i++) { + if(instance->data[i] != 0) { + data_check = true; + break; + } + } + + if(data_check) { + for(size_t i = subghz_protocol_bin_raw_get_full_byte(ind) - 1; i > 0; i--) { + instance->data[i] = (instance->data[i - 1] << (8 - bit_bias)) | + (instance->data[i] >> bit_bias); + } + instance->data[0] = (instance->data[0] >> bit_bias); + +#ifdef BIN_RAW_DEBUG + bin_raw_debug_tag(TAG, "Data right alignment\r\n"); + for(size_t i = 0; i < subghz_protocol_bin_raw_get_full_byte(ind); i++) { + bin_raw_debug("%02X ", instance->data[i]); + } + bin_raw_debug("\r\n\r\n"); +#endif + instance->data_markup[0].bit_count = ind; + instance->data_markup[0].byte_bias = 0; + + return true; + } else { + return false; + } + } else { + return false; + } + } + return false; +} + +void subghz_protocol_decoder_bin_raw_data_input_rssi( + SubGhzProtocolDecoderBinRAW* instance, + float rssi) { + furi_assert(instance); + switch(instance->decoder.parser_step) { + case BinRAWDecoderStepReset: + + bin_raw_debug("%ld %ld :", (int32_t)rssi, (int32_t)instance->adaptive_threshold_rssi); + if(rssi > (instance->adaptive_threshold_rssi + BIN_RAW_DELTA_RSSI)) { + instance->data_raw_ind = 0; + memset(instance->data_raw, 0x00, BIN_RAW_BUF_RAW_SIZE * sizeof(int32_t)); + memset(instance->data, 0x00, BIN_RAW_BUF_RAW_SIZE * sizeof(uint8_t)); + instance->decoder.parser_step = BinRAWDecoderStepWrite; + bin_raw_debug_tag(TAG, "RSSI\r\n"); + } else { + //adaptive noise level adjustment + instance->adaptive_threshold_rssi += (rssi - instance->adaptive_threshold_rssi) * 0.2f; + } + break; + + case BinRAWDecoderStepBufFull: + case BinRAWDecoderStepWrite: +#ifdef BIN_RAW_DEBUG + if(rssi > (instance->adaptive_threshold_rssi + BIN_RAW_DELTA_RSSI)) { + bin_raw_debug("\033[0;32m%ld \033[0m ", (int32_t)rssi); + } else { + bin_raw_debug("%ld ", (int32_t)rssi); + } +#endif + if(rssi < instance->adaptive_threshold_rssi + BIN_RAW_DELTA_RSSI) { +#ifdef BIN_RAW_DEBUG + bin_raw_debug("\r\n\r\n"); + bin_raw_debug_tag(TAG, "Data for analysis, positive high, negative low, us\r\n"); + for(size_t i = 0; i < instance->data_raw_ind; i++) { + bin_raw_debug("%ld ", instance->data_raw[i]); + } + bin_raw_debug("\r\n\t count data= %zu\r\n\r\n", instance->data_raw_ind); +#endif + instance->decoder.parser_step = BinRAWDecoderStepReset; + instance->generic.data_count_bit = 0; + if(instance->data_raw_ind >= BIN_RAW_BUF_MIN_DATA_COUNT) { + if(subghz_protocol_bin_raw_check_remote_controller(instance)) { + bin_raw_debug_tag(TAG, "Sequence found\r\n"); + bin_raw_debug("\tind byte_bias\tbit_count\t\tbin_data"); + uint16_t i = 0; + while((i < BIN_RAW_MAX_MARKUP_COUNT) && + (instance->data_markup[i].bit_count != 0)) { + instance->generic.data_count_bit += instance->data_markup[i].bit_count; +#ifdef BIN_RAW_DEBUG + bin_raw_debug( + "\r\n\t%d\t%d\t%d :\t", + i, + instance->data_markup[i].byte_bias, + instance->data_markup[i].bit_count); + for(uint16_t y = instance->data_markup[i].byte_bias; + y < instance->data_markup[i].byte_bias + + subghz_protocol_bin_raw_get_full_byte( + instance->data_markup[i].bit_count); + y++) { + bin_raw_debug("%02X ", instance->data[y]); + } +#endif + i++; + } + bin_raw_debug("\r\n"); + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + } + } + break; + + default: + //if instance->decoder.parser_step == BinRAWDecoderStepNoParse or others, restore the initial state + if(rssi < instance->adaptive_threshold_rssi + BIN_RAW_DELTA_RSSI) { + instance->decoder.parser_step = BinRAWDecoderStepReset; + } + break; + } +} + +uint8_t subghz_protocol_decoder_bin_raw_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderBinRAW* instance = context; + return subghz_protocol_blocks_add_bytes( + instance->data + instance->data_markup[0].byte_bias, + subghz_protocol_bin_raw_get_full_byte(instance->data_markup[0].bit_count)); +} + +bool subghz_protocol_decoder_bin_raw_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderBinRAW* instance = context; + + bool res = false; + FuriString* temp_str; + temp_str = furi_string_alloc(); + do { + stream_clean(flipper_format_get_raw_stream(flipper_format)); + if(!flipper_format_write_header_cstr( + flipper_format, SUBGHZ_KEY_FILE_TYPE, SUBGHZ_KEY_FILE_VERSION)) { + FURI_LOG_E(TAG, "Unable to add header"); + break; + } + + if(!flipper_format_write_uint32(flipper_format, "Frequency", &preset->frequency, 1)) { + FURI_LOG_E(TAG, "Unable to add Frequency"); + break; + } + + subghz_block_generic_get_preset_name(furi_string_get_cstr(preset->name), temp_str); + if(!flipper_format_write_string_cstr( + flipper_format, "Preset", furi_string_get_cstr(temp_str))) { + FURI_LOG_E(TAG, "Unable to add Preset"); + break; + } + if(!strcmp(furi_string_get_cstr(temp_str), "FuriHalSubGhzPresetCustom")) { + if(!flipper_format_write_string_cstr( + flipper_format, "Custom_preset_module", "CC1101")) { + FURI_LOG_E(TAG, "Unable to add Custom_preset_module"); + break; + } + if(!flipper_format_write_hex( + flipper_format, "Custom_preset_data", preset->data, preset->data_size)) { + FURI_LOG_E(TAG, "Unable to add Custom_preset_data"); + break; + } + } + if(!flipper_format_write_string_cstr( + flipper_format, "Protocol", instance->generic.protocol_name)) { + FURI_LOG_E(TAG, "Unable to add Protocol"); + break; + } + + uint32_t temp = instance->generic.data_count_bit; + if(!flipper_format_write_uint32(flipper_format, "Bit", &temp, 1)) { + FURI_LOG_E(TAG, "Unable to add Bit"); + break; + } + + if(!flipper_format_write_uint32(flipper_format, "TE", &instance->te, 1)) { + FURI_LOG_E(TAG, "Unable to add TE"); + break; + } + + uint16_t i = 0; + while((i < BIN_RAW_MAX_MARKUP_COUNT) && (instance->data_markup[i].bit_count != 0)) { + temp = instance->data_markup[i].bit_count; + if(!flipper_format_write_uint32(flipper_format, "Bit_RAW", &temp, 1)) { + FURI_LOG_E(TAG, "Bit_RAW"); + break; + } + if(!flipper_format_write_hex( + flipper_format, + "Data_RAW", + instance->data + instance->data_markup[i].byte_bias, + subghz_protocol_bin_raw_get_full_byte(instance->data_markup[i].bit_count))) { + FURI_LOG_E(TAG, "Unable to add Data_RAW"); + break; + } + i++; + } + + res = true; + } while(false); + furi_string_free(temp_str); + return res; +} + +bool subghz_protocol_decoder_bin_raw_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderBinRAW* instance = context; + + bool res = false; + uint32_t temp_data = 0; + + do { + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + if(!flipper_format_read_uint32(flipper_format, "Bit", (uint32_t*)&temp_data, 1)) { + FURI_LOG_E(TAG, "Missing Bit"); + break; + } + + instance->generic.data_count_bit = (uint16_t)temp_data; + + if(!flipper_format_read_uint32(flipper_format, "TE", (uint32_t*)&instance->te, 1)) { + FURI_LOG_E(TAG, "Missing TE"); + break; + } + + temp_data = 0; + uint16_t ind = 0; + uint16_t byte_bias = 0; + uint16_t byte_count = 0; + memset(instance->data_markup, 0x00, BIN_RAW_MAX_MARKUP_COUNT * sizeof(BinRAW_Markup)); + while(flipper_format_read_uint32(flipper_format, "Bit_RAW", (uint32_t*)&temp_data, 1)) { + if(ind >= BIN_RAW_MAX_MARKUP_COUNT) { + FURI_LOG_E(TAG, "Markup overflow"); + break; + } + byte_count += subghz_protocol_bin_raw_get_full_byte(temp_data); + if(byte_count > BIN_RAW_BUF_DATA_SIZE) { + FURI_LOG_E(TAG, "Receive buffer overflow"); + break; + } + + instance->data_markup[ind].bit_count = temp_data; + instance->data_markup[ind].byte_bias = byte_bias; + byte_bias += subghz_protocol_bin_raw_get_full_byte(temp_data); + + if(!flipper_format_read_hex( + flipper_format, + "Data_RAW", + instance->data + instance->data_markup[ind].byte_bias, + subghz_protocol_bin_raw_get_full_byte(temp_data))) { + instance->data_markup[ind].bit_count = 0; + FURI_LOG_E(TAG, "Missing Data_RAW"); + break; + } + ind++; + } + + res = true; + } while(0); + + return res; +} + +void subghz_protocol_decoder_bin_raw_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderBinRAW* instance = context; + furi_string_cat_printf( + output, + "%s %dbit\r\n" + "Key:", + instance->generic.protocol_name, + instance->generic.data_count_bit); + + uint16_t byte_count = subghz_protocol_bin_raw_get_full_byte(instance->generic.data_count_bit); + for(size_t i = 0; (byte_count < 36 ? i < byte_count : i < 36); i++) { + furi_string_cat_printf(output, "%02X", instance->data[i]); + } + + furi_string_cat_printf(output, "\r\nTe:%luus\r\n", instance->te); +} diff --git a/lib/subghz/protocols/bin_raw.h b/lib/subghz/protocols/bin_raw.h new file mode 100644 index 00000000000..c63f86ce6f5 --- /dev/null +++ b/lib/subghz/protocols/bin_raw.h @@ -0,0 +1,111 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_BIN_RAW_NAME "BinRAW" + +typedef struct SubGhzProtocolDecoderBinRAW SubGhzProtocolDecoderBinRAW; +typedef struct SubGhzProtocolEncoderBinRAW SubGhzProtocolEncoderBinRAW; + +extern const SubGhzProtocolDecoder subghz_protocol_bin_raw_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_bin_raw_encoder; +extern const SubGhzProtocol subghz_protocol_bin_raw; + +/** + * Allocate SubGhzProtocolEncoderBinRAW. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderBinRAW* pointer to a SubGhzProtocolEncoderBinRAW instance + */ +void* subghz_protocol_encoder_bin_raw_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderBinRAW. + * @param context Pointer to a SubGhzProtocolEncoderBinRAW instance + */ +void subghz_protocol_encoder_bin_raw_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderBinRAW instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_encoder_bin_raw_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderBinRAW instance + */ +void subghz_protocol_encoder_bin_raw_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderBinRAW instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_bin_raw_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderBinRAW. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderBinRAW* pointer to a SubGhzProtocolDecoderBinRAW instance + */ +void* subghz_protocol_decoder_bin_raw_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderBinRAW. + * @param context Pointer to a SubGhzProtocolDecoderBinRAW instance + */ +void subghz_protocol_decoder_bin_raw_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderBinRAW. + * @param context Pointer to a SubGhzProtocolDecoderBinRAW instance + */ +void subghz_protocol_decoder_bin_raw_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderBinRAW instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_bin_raw_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderBinRAW instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_bin_raw_get_hash_data(void* context); + +void subghz_protocol_decoder_bin_raw_data_input_rssi( + SubGhzProtocolDecoderBinRAW* instance, + float rssi); + +/** + * Serialize data SubGhzProtocolDecoderBinRAW. + * @param context Pointer to a SubGhzProtocolDecoderBinRAW instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_decoder_bin_raw_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderBinRAW. + * @param context Pointer to a SubGhzProtocolDecoderBinRAW instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_decoder_bin_raw_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderBinRAW instance + * @param output Resulting text + */ +void subghz_protocol_decoder_bin_raw_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/chamberlain_code.c b/lib/subghz/protocols/chamberlain_code.c index 32f4e9520e2..9c8e5ee4ada 100644 --- a/lib/subghz/protocols/chamberlain_code.c +++ b/lib/subghz/protocols/chamberlain_code.c @@ -196,12 +196,13 @@ static bool break; } - instance->encoder.size_upload = subghz_protocol_blocks_get_upload( + instance->encoder.size_upload = subghz_protocol_blocks_get_upload_from_bit_array( upload_hex_data, upload_hex_count_bit, instance->encoder.upload, instance->encoder.size_upload, - subghz_protocol_chamb_code_const.te_short); + subghz_protocol_chamb_code_const.te_short, + SubGhzProtocolBlockAlignBitLeft); return true; } diff --git a/lib/subghz/protocols/protocol_items.c b/lib/subghz/protocols/protocol_items.c index 050904eece5..74244c5ff40 100644 --- a/lib/subghz/protocols/protocol_items.c +++ b/lib/subghz/protocols/protocol_items.c @@ -42,6 +42,7 @@ const SubGhzProtocol* subghz_protocol_registry_items[] = { &subghz_protocol_dooya, &subghz_protocol_alutech_at_4n, &subghz_protocol_kinggates_stylo_4k, + &subghz_protocol_bin_raw, }; const SubGhzProtocolRegistry subghz_protocol_registry = { diff --git a/lib/subghz/protocols/protocol_items.h b/lib/subghz/protocols/protocol_items.h index 522931d2256..4ca1c4679d1 100644 --- a/lib/subghz/protocols/protocol_items.h +++ b/lib/subghz/protocols/protocol_items.h @@ -42,5 +42,6 @@ #include "dooya.h" #include "alutech_at_4n.h" #include "kinggates_stylo_4k.h" +#include "bin_raw.h" extern const SubGhzProtocolRegistry subghz_protocol_registry; diff --git a/lib/subghz/protocols/secplus_v2.c b/lib/subghz/protocols/secplus_v2.c index 7b79892b08a..f7262bd1769 100644 --- a/lib/subghz/protocols/secplus_v2.c +++ b/lib/subghz/protocols/secplus_v2.c @@ -261,16 +261,16 @@ static bool 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) { + 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) { + roll_array[k] = (p[2] >> i) & 0x03; + if(roll_array[k++] == 3) { FURI_LOG_E(TAG, "Roll_Array FAIL"); return false; } diff --git a/lib/subghz/receiver.c b/lib/subghz/receiver.c index fd6f1493e24..698fe098ecd 100644 --- a/lib/subghz/receiver.c +++ b/lib/subghz/receiver.c @@ -64,7 +64,7 @@ void subghz_receiver_decode(SubGhzReceiver* instance, bool level, uint32_t durat for M_EACH(slot, instance->slots, SubGhzReceiverSlotArray_t) { - if((slot->base->protocol->flag & instance->filter) == instance->filter) { + if((slot->base->protocol->flag & instance->filter) != 0) { slot->base->protocol->decoder->feed(slot->base, level, duration); } } diff --git a/lib/subghz/types.h b/lib/subghz/types.h index 6c34dc7281d..1b8ef6a1412 100644 --- a/lib/subghz/types.h +++ b/lib/subghz/types.h @@ -90,6 +90,7 @@ typedef enum { SubGhzProtocolFlag_Save = (1 << 7), SubGhzProtocolFlag_Load = (1 << 8), SubGhzProtocolFlag_Send = (1 << 9), + SubGhzProtocolFlag_BinRAW = (1 << 10), } SubGhzProtocolFlag; typedef struct { diff --git a/lib/toolbox/level_duration.h b/lib/toolbox/level_duration.h index 9406cef3145..7618344ac3c 100644 --- a/lib/toolbox/level_duration.h +++ b/lib/toolbox/level_duration.h @@ -13,8 +13,8 @@ #define LEVEL_DURATION_RESERVED 0x800000U typedef struct { - uint32_t level; - uint32_t duration; + uint32_t duration : 30; + uint8_t level : 2; } LevelDuration; static inline LevelDuration level_duration_make(bool level, uint32_t duration) { From 4265057ee864a8d68a68e9a9ef4088b39d2b1e5c Mon Sep 17 00:00:00 2001 From: Petr Portnov | PROgrm_JARvis Date: Thu, 9 Feb 2023 07:58:01 +0300 Subject: [PATCH 400/824] feat: add missing `const` qualifiers (#2233) * feat: make `ViewPort` getters const * feat: make tx-buffers const * feat: make `canvas_get_buffer_size` const * feat: make `canvas` methods const * feat: make `icon_animation` methods const * feat: make `scene_manager` methods const * feat: make `loader` method const * feat: make `canvas_get_font_params` const Co-authored-by: Aleksandr Kutuzov --- applications/services/gui/canvas.c | 12 +++--- applications/services/gui/canvas.h | 8 ++-- applications/services/gui/canvas_i.h | 2 +- applications/services/gui/elements.c | 2 +- applications/services/gui/gui.c | 2 +- applications/services/gui/gui.h | 2 +- applications/services/gui/icon_animation.c | 8 ++-- applications/services/gui/icon_animation.h | 6 +-- applications/services/gui/icon_animation_i.h | 2 +- .../widget_element_text_scroll.c | 4 +- applications/services/gui/scene_manager.c | 4 +- applications/services/gui/scene_manager.h | 4 +- applications/services/gui/view_port.c | 6 +-- applications/services/gui/view_port.h | 6 +-- applications/services/loader/loader.c | 2 +- applications/services/loader/loader.h | 2 +- applications/services/locale/locale.h | 2 +- firmware/targets/f18/api_symbols.csv | 35 ++++++++--------- firmware/targets/f7/api_symbols.csv | 38 +++++++++---------- firmware/targets/f7/furi_hal/furi_hal_spi.c | 4 +- .../targets/furi_hal_include/furi_hal_spi.h | 4 +- 21 files changed, 78 insertions(+), 77 deletions(-) diff --git a/applications/services/gui/canvas.c b/applications/services/gui/canvas.c index 18636ecedd4..d47bba6b2cf 100644 --- a/applications/services/gui/canvas.c +++ b/applications/services/gui/canvas.c @@ -57,7 +57,7 @@ uint8_t* canvas_get_buffer(Canvas* canvas) { return u8g2_GetBufferPtr(&canvas->fb); } -size_t canvas_get_buffer_size(Canvas* canvas) { +size_t canvas_get_buffer_size(const Canvas* canvas) { furi_assert(canvas); return u8g2_GetBufferTileWidth(&canvas->fb) * u8g2_GetBufferTileHeight(&canvas->fb) * 8; } @@ -75,17 +75,17 @@ void canvas_frame_set( canvas->height = height; } -uint8_t canvas_width(Canvas* canvas) { +uint8_t canvas_width(const Canvas* canvas) { furi_assert(canvas); return canvas->width; } -uint8_t canvas_height(Canvas* canvas) { +uint8_t canvas_height(const Canvas* canvas) { furi_assert(canvas); return canvas->height; } -uint8_t canvas_current_font_height(Canvas* canvas) { +uint8_t canvas_current_font_height(const Canvas* canvas) { furi_assert(canvas); uint8_t font_height = u8g2_GetMaxCharHeight(&canvas->fb); @@ -96,10 +96,10 @@ uint8_t canvas_current_font_height(Canvas* canvas) { return font_height; } -CanvasFontParameters* canvas_get_font_params(Canvas* canvas, Font font) { +const CanvasFontParameters* canvas_get_font_params(const Canvas* canvas, Font font) { furi_assert(canvas); furi_assert(font < FontTotalNumber); - return (CanvasFontParameters*)&canvas_font_params[font]; + return &canvas_font_params[font]; } void canvas_clear(Canvas* canvas) { diff --git a/applications/services/gui/canvas.h b/applications/services/gui/canvas.h index b2a065ca775..f8fe2c1db56 100644 --- a/applications/services/gui/canvas.h +++ b/applications/services/gui/canvas.h @@ -85,7 +85,7 @@ void canvas_commit(Canvas* canvas); * * @return width in pixels. */ -uint8_t canvas_width(Canvas* canvas); +uint8_t canvas_width(const Canvas* canvas); /** Get Canvas height * @@ -93,7 +93,7 @@ uint8_t canvas_width(Canvas* canvas); * * @return height in pixels. */ -uint8_t canvas_height(Canvas* canvas); +uint8_t canvas_height(const Canvas* canvas); /** Get current font height * @@ -101,7 +101,7 @@ uint8_t canvas_height(Canvas* canvas); * * @return height in pixels. */ -uint8_t canvas_current_font_height(Canvas* canvas); +uint8_t canvas_current_font_height(const Canvas* canvas); /** Get font parameters * @@ -110,7 +110,7 @@ uint8_t canvas_current_font_height(Canvas* canvas); * * @return pointer to CanvasFontParameters structure */ -CanvasFontParameters* canvas_get_font_params(Canvas* canvas, Font font); +const CanvasFontParameters* canvas_get_font_params(const Canvas* canvas, Font font); /** Clear canvas * diff --git a/applications/services/gui/canvas_i.h b/applications/services/gui/canvas_i.h index 2b684923550..12cabfa7d9c 100644 --- a/applications/services/gui/canvas_i.h +++ b/applications/services/gui/canvas_i.h @@ -45,7 +45,7 @@ uint8_t* canvas_get_buffer(Canvas* canvas); * * @return size of canvas in bytes */ -size_t canvas_get_buffer_size(Canvas* canvas); +size_t canvas_get_buffer_size(const Canvas* canvas); /** Set drawing region relative to real screen buffer * diff --git a/applications/services/gui/elements.c b/applications/services/gui/elements.c index 54c36af76ca..9b7c84ece19 100644 --- a/applications/services/gui/elements.c +++ b/applications/services/gui/elements.c @@ -639,7 +639,7 @@ void elements_text_box( bool inversed_present = false; Font current_font = FontSecondary; Font prev_font = FontSecondary; - CanvasFontParameters* font_params = canvas_get_font_params(canvas, current_font); + const CanvasFontParameters* font_params = canvas_get_font_params(canvas, current_font); // Fill line parameters uint8_t line_leading_min = font_params->leading_min; diff --git a/applications/services/gui/gui.c b/applications/services/gui/gui.c index af5cf862d0a..94bab140282 100644 --- a/applications/services/gui/gui.c +++ b/applications/services/gui/gui.c @@ -467,7 +467,7 @@ void gui_remove_framebuffer_callback(Gui* gui, GuiCanvasCommitCallback callback, gui_unlock(gui); } -size_t gui_get_framebuffer_size(Gui* gui) { +size_t gui_get_framebuffer_size(const Gui* gui) { furi_assert(gui); return canvas_get_buffer_size(gui->canvas); } diff --git a/applications/services/gui/gui.h b/applications/services/gui/gui.h index 4e7fc2e4d87..d7d73f27b69 100644 --- a/applications/services/gui/gui.h +++ b/applications/services/gui/gui.h @@ -94,7 +94,7 @@ void gui_remove_framebuffer_callback(Gui* gui, GuiCanvasCommitCallback callback, * @param gui Gui instance * @return size_t size of frame buffer in bytes */ -size_t gui_get_framebuffer_size(Gui* gui); +size_t gui_get_framebuffer_size(const Gui* gui); /** Set lockdown mode * diff --git a/applications/services/gui/icon_animation.c b/applications/services/gui/icon_animation.c index 48c86220886..b63d233f3d5 100644 --- a/applications/services/gui/icon_animation.c +++ b/applications/services/gui/icon_animation.c @@ -29,7 +29,7 @@ void icon_animation_set_update_callback( instance->callback_context = context; } -const uint8_t* icon_animation_get_data(IconAnimation* instance) { +const uint8_t* icon_animation_get_data(const IconAnimation* instance) { return instance->icon->frames[instance->frame]; } @@ -51,12 +51,12 @@ void icon_animation_timer_callback(void* context) { } } -uint8_t icon_animation_get_width(IconAnimation* instance) { +uint8_t icon_animation_get_width(const IconAnimation* instance) { furi_assert(instance); return instance->icon->width; } -uint8_t icon_animation_get_height(IconAnimation* instance) { +uint8_t icon_animation_get_height(const IconAnimation* instance) { furi_assert(instance); return instance->icon->height; } @@ -83,7 +83,7 @@ void icon_animation_stop(IconAnimation* instance) { } } -bool icon_animation_is_last_frame(IconAnimation* instance) { +bool icon_animation_is_last_frame(const IconAnimation* instance) { furi_assert(instance); return instance->icon->frame_count - instance->frame <= 1; } diff --git a/applications/services/gui/icon_animation.h b/applications/services/gui/icon_animation.h index 684790353a8..6e58df31dee 100644 --- a/applications/services/gui/icon_animation.h +++ b/applications/services/gui/icon_animation.h @@ -55,7 +55,7 @@ void icon_animation_set_update_callback( * * @return width in pixels */ -uint8_t icon_animation_get_width(IconAnimation* instance); +uint8_t icon_animation_get_width(const IconAnimation* instance); /** Get icon animation height * @@ -63,7 +63,7 @@ uint8_t icon_animation_get_width(IconAnimation* instance); * * @return height in pixels */ -uint8_t icon_animation_get_height(IconAnimation* instance); +uint8_t icon_animation_get_height(const IconAnimation* instance); /** Start icon animation * @@ -83,7 +83,7 @@ void icon_animation_stop(IconAnimation* instance); * * @return true if last frame */ -bool icon_animation_is_last_frame(IconAnimation* instance); +bool icon_animation_is_last_frame(const IconAnimation* instance); #ifdef __cplusplus } diff --git a/applications/services/gui/icon_animation_i.h b/applications/services/gui/icon_animation_i.h index 4053a120d3b..62858d288c0 100644 --- a/applications/services/gui/icon_animation_i.h +++ b/applications/services/gui/icon_animation_i.h @@ -24,7 +24,7 @@ struct IconAnimation { * * @return pointer to current frame XBM bitmap data */ -const uint8_t* icon_animation_get_data(IconAnimation* instance); +const uint8_t* icon_animation_get_data(const IconAnimation* instance); /** Advance to next frame * diff --git a/applications/services/gui/modules/widget_elements/widget_element_text_scroll.c b/applications/services/gui/modules/widget_elements/widget_element_text_scroll.c index d8fc11311bd..5d522c74bfc 100644 --- a/applications/services/gui/modules/widget_elements/widget_element_text_scroll.c +++ b/applications/services/gui/modules/widget_elements/widget_element_text_scroll.c @@ -74,7 +74,7 @@ static void widget_element_text_scroll_fill_lines(Canvas* canvas, WidgetElement* } // Set canvas font canvas_set_font(canvas, line_tmp.font); - CanvasFontParameters* params = canvas_get_font_params(canvas, line_tmp.font); + const CanvasFontParameters* params = canvas_get_font_params(canvas, line_tmp.font); total_height += params->height; if(total_height > model->height) { model->scroll_pos_total++; @@ -138,7 +138,7 @@ static void widget_element_text_scroll_draw(Canvas* canvas, WidgetElement* eleme TextScrollLineArray_next(it), curr_line++) { if(curr_line < model->scroll_pos_current) continue; TextScrollLineArray* line = TextScrollLineArray_ref(it); - CanvasFontParameters* params = canvas_get_font_params(canvas, line->font); + const CanvasFontParameters* params = canvas_get_font_params(canvas, line->font); if(y + params->descender > model->y + model->height) break; canvas_set_font(canvas, line->font); if(line->horizontal == AlignLeft) { diff --git a/applications/services/gui/scene_manager.c b/applications/services/gui/scene_manager.c index 590145e1e7b..4064dafb662 100644 --- a/applications/services/gui/scene_manager.c +++ b/applications/services/gui/scene_manager.c @@ -34,7 +34,7 @@ void scene_manager_set_scene_state(SceneManager* scene_manager, uint32_t scene_i scene_manager->scene[scene_id].state = state; } -uint32_t scene_manager_get_scene_state(SceneManager* scene_manager, uint32_t scene_id) { +uint32_t scene_manager_get_scene_state(const SceneManager* scene_manager, uint32_t scene_id) { furi_assert(scene_manager); furi_assert(scene_id < scene_manager->scene_handlers->scene_num); @@ -184,7 +184,7 @@ bool scene_manager_search_and_switch_to_previous_scene_one_of( return scene_found; } -bool scene_manager_has_previous_scene(SceneManager* scene_manager, uint32_t scene_id) { +bool scene_manager_has_previous_scene(const SceneManager* scene_manager, uint32_t scene_id) { furi_assert(scene_manager); bool scene_found = false; diff --git a/applications/services/gui/scene_manager.h b/applications/services/gui/scene_manager.h index 5b833e5de20..c349a12ceac 100644 --- a/applications/services/gui/scene_manager.h +++ b/applications/services/gui/scene_manager.h @@ -63,7 +63,7 @@ void scene_manager_set_scene_state(SceneManager* scene_manager, uint32_t scene_i * * @return Scene state */ -uint32_t scene_manager_get_scene_state(SceneManager* scene_manager, uint32_t scene_id); +uint32_t scene_manager_get_scene_state(const SceneManager* scene_manager, uint32_t scene_id); /** Scene Manager allocation and configuration * @@ -134,7 +134,7 @@ bool scene_manager_previous_scene(SceneManager* scene_manager); * * @return true if previous scene was found, false otherwise */ -bool scene_manager_has_previous_scene(SceneManager* scene_manager, uint32_t scene_id); +bool scene_manager_has_previous_scene(const SceneManager* scene_manager, uint32_t scene_id); /** Search and switch to previous Scene * diff --git a/applications/services/gui/view_port.c b/applications/services/gui/view_port.c index ffd01450b27..5760ed577c4 100644 --- a/applications/services/gui/view_port.c +++ b/applications/services/gui/view_port.c @@ -89,7 +89,7 @@ void view_port_set_width(ViewPort* view_port, uint8_t width) { view_port->width = width; } -uint8_t view_port_get_width(ViewPort* view_port) { +uint8_t view_port_get_width(const ViewPort* view_port) { furi_assert(view_port); return view_port->width; } @@ -99,7 +99,7 @@ void view_port_set_height(ViewPort* view_port, uint8_t height) { view_port->height = height; } -uint8_t view_port_get_height(ViewPort* view_port) { +uint8_t view_port_get_height(const ViewPort* view_port) { furi_assert(view_port); return view_port->height; } @@ -112,7 +112,7 @@ void view_port_enabled_set(ViewPort* view_port, bool enabled) { } } -bool view_port_is_enabled(ViewPort* view_port) { +bool view_port_is_enabled(const ViewPort* view_port) { furi_assert(view_port); return view_port->is_enabled; } diff --git a/applications/services/gui/view_port.h b/applications/services/gui/view_port.h index 703e99248e8..752fc46ba64 100644 --- a/applications/services/gui/view_port.h +++ b/applications/services/gui/view_port.h @@ -56,7 +56,7 @@ void view_port_free(ViewPort* view_port); * @param width wanted width, 0 - auto. */ void view_port_set_width(ViewPort* view_port, uint8_t width); -uint8_t view_port_get_width(ViewPort* view_port); +uint8_t view_port_get_width(const ViewPort* view_port); /** Set view_port height. * @@ -66,7 +66,7 @@ uint8_t view_port_get_width(ViewPort* view_port); * @param height wanted height, 0 - auto. */ void view_port_set_height(ViewPort* view_port, uint8_t height); -uint8_t view_port_get_height(ViewPort* view_port); +uint8_t view_port_get_height(const ViewPort* view_port); /** Enable or disable view_port rendering. * @@ -75,7 +75,7 @@ uint8_t view_port_get_height(ViewPort* view_port); * @warning automatically dispatches update event */ void view_port_enabled_set(ViewPort* view_port, bool enabled); -bool view_port_is_enabled(ViewPort* view_port); +bool view_port_is_enabled(const ViewPort* view_port); /** ViewPort event callbacks * diff --git a/applications/services/loader/loader.c b/applications/services/loader/loader.c index 97d1e6e4e52..caaf9f11b5d 100644 --- a/applications/services/loader/loader.c +++ b/applications/services/loader/loader.c @@ -262,7 +262,7 @@ void loader_unlock(Loader* instance) { FURI_CRITICAL_EXIT(); } -bool loader_is_locked(Loader* instance) { +bool loader_is_locked(const Loader* instance) { return instance->lock_count > 0; } diff --git a/applications/services/loader/loader.h b/applications/services/loader/loader.h index 954e79c572b..8dbc4fc358f 100644 --- a/applications/services/loader/loader.h +++ b/applications/services/loader/loader.h @@ -43,7 +43,7 @@ bool loader_lock(Loader* instance); void loader_unlock(Loader* instance); /** Get loader lock status */ -bool loader_is_locked(Loader* instance); +bool loader_is_locked(const Loader* instance); /** Show primary loader */ void loader_show_menu(); diff --git a/applications/services/locale/locale.h b/applications/services/locale/locale.h index 5f2a4d87f22..61fb4c60558 100644 --- a/applications/services/locale/locale.h +++ b/applications/services/locale/locale.h @@ -81,7 +81,7 @@ void locale_format_time( * * @return The Locale DateFormat. */ -LocaleDateFormat locale_get_date_format(); +LocaleDateFormat locale_get_date_format(void); /** Set Locale DateFormat * diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index d29d696fc5c..462fbf739d3 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,13.1,, +Version,+,14.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -522,7 +522,7 @@ Function,-,bzero,void,"void*, size_t" Function,-,calloc,void*,"size_t, size_t" Function,+,canvas_clear,void,Canvas* Function,+,canvas_commit,void,Canvas* -Function,+,canvas_current_font_height,uint8_t,Canvas* +Function,+,canvas_current_font_height,uint8_t,const Canvas* Function,+,canvas_draw_bitmap,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, const uint8_t*" Function,+,canvas_draw_box,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t" Function,+,canvas_draw_circle,void,"Canvas*, uint8_t, uint8_t, uint8_t" @@ -539,9 +539,9 @@ Function,+,canvas_draw_str,void,"Canvas*, uint8_t, uint8_t, const char*" Function,+,canvas_draw_str_aligned,void,"Canvas*, uint8_t, uint8_t, Align, Align, const char*" Function,+,canvas_draw_triangle,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, CanvasDirection" Function,+,canvas_draw_xbm,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, const uint8_t*" -Function,+,canvas_get_font_params,CanvasFontParameters*,"Canvas*, Font" +Function,+,canvas_get_font_params,const CanvasFontParameters*,"const Canvas*, Font" Function,+,canvas_glyph_width,uint8_t,"Canvas*, char" -Function,+,canvas_height,uint8_t,Canvas* +Function,+,canvas_height,uint8_t,const Canvas* Function,+,canvas_invert_color,void,Canvas* Function,+,canvas_reset,void,Canvas* Function,+,canvas_set_bitmap_mode,void,"Canvas*, _Bool" @@ -550,7 +550,7 @@ Function,+,canvas_set_custom_u8g2_font,void,"Canvas*, const uint8_t*" Function,+,canvas_set_font,void,"Canvas*, Font" Function,+,canvas_set_font_direction,void,"Canvas*, CanvasDirection" Function,+,canvas_string_width,uint16_t,"Canvas*, const char*" -Function,+,canvas_width,uint8_t,Canvas* +Function,+,canvas_width,uint8_t,const Canvas* Function,-,cfree,void,void* Function,-,clearerr,void,FILE* Function,-,clearerr_unlocked,void,FILE* @@ -1063,9 +1063,9 @@ Function,+,furi_hal_spi_bus_handle_deinit,void,FuriHalSpiBusHandle* Function,+,furi_hal_spi_bus_handle_init,void,FuriHalSpiBusHandle* Function,+,furi_hal_spi_bus_init,void,FuriHalSpiBus* Function,+,furi_hal_spi_bus_rx,_Bool,"FuriHalSpiBusHandle*, uint8_t*, size_t, uint32_t" -Function,+,furi_hal_spi_bus_trx,_Bool,"FuriHalSpiBusHandle*, uint8_t*, uint8_t*, size_t, uint32_t" +Function,+,furi_hal_spi_bus_trx,_Bool,"FuriHalSpiBusHandle*, const uint8_t*, uint8_t*, size_t, uint32_t" Function,+,furi_hal_spi_bus_trx_dma,_Bool,"FuriHalSpiBusHandle*, uint8_t*, uint8_t*, size_t, uint32_t" -Function,+,furi_hal_spi_bus_tx,_Bool,"FuriHalSpiBusHandle*, uint8_t*, size_t, uint32_t" +Function,+,furi_hal_spi_bus_tx,_Bool,"FuriHalSpiBusHandle*, const uint8_t*, size_t, uint32_t" Function,-,furi_hal_spi_config_deinit_early,void, Function,-,furi_hal_spi_config_init,void, Function,-,furi_hal_spi_config_init_early,void, @@ -1123,6 +1123,7 @@ Function,+,furi_kernel_unlock,int32_t, Function,+,furi_log_get_level,FuriLogLevel, Function,-,furi_log_init,void, Function,+,furi_log_print_format,void,"FuriLogLevel, const char*, const char*, ..." +Function,+,furi_log_print_raw_format,void,"FuriLogLevel, const char*, ..." Function,+,furi_log_set_level,void,FuriLogLevel Function,-,furi_log_set_puts,void,FuriLogPuts Function,-,furi_log_set_timestamp,void,FuriLogTimestamp @@ -1287,7 +1288,7 @@ Function,+,gui_add_framebuffer_callback,void,"Gui*, GuiCanvasCommitCallback, voi Function,+,gui_add_view_port,void,"Gui*, ViewPort*, GuiLayer" Function,+,gui_direct_draw_acquire,Canvas*,Gui* Function,+,gui_direct_draw_release,void,Gui* -Function,+,gui_get_framebuffer_size,size_t,Gui* +Function,+,gui_get_framebuffer_size,size_t,const Gui* Function,+,gui_remove_framebuffer_callback,void,"Gui*, GuiCanvasCommitCallback, void*" Function,+,gui_remove_view_port,void,"Gui*, ViewPort*" Function,+,gui_set_lockdown,void,"Gui*, _Bool" @@ -1301,9 +1302,9 @@ Function,+,hmac_sha256_init,void,"hmac_sha256_context*, const uint8_t*" Function,+,hmac_sha256_update,void,"const hmac_sha256_context*, const uint8_t*, unsigned" Function,+,icon_animation_alloc,IconAnimation*,const Icon* Function,+,icon_animation_free,void,IconAnimation* -Function,+,icon_animation_get_height,uint8_t,IconAnimation* -Function,+,icon_animation_get_width,uint8_t,IconAnimation* -Function,+,icon_animation_is_last_frame,_Bool,IconAnimation* +Function,+,icon_animation_get_height,uint8_t,const IconAnimation* +Function,+,icon_animation_get_width,uint8_t,const IconAnimation* +Function,+,icon_animation_is_last_frame,_Bool,const IconAnimation* Function,+,icon_animation_set_update_callback,void,"IconAnimation*, IconAnimationCallback, void*" Function,+,icon_animation_start,void,IconAnimation* Function,+,icon_animation_stop,void,IconAnimation* @@ -1352,7 +1353,7 @@ Function,-,ldiv,ldiv_t,"long, long" Function,-,llabs,long long,long long Function,-,lldiv,lldiv_t,"long long, long long" Function,+,loader_get_pubsub,FuriPubSub*,Loader* -Function,+,loader_is_locked,_Bool,Loader* +Function,+,loader_is_locked,_Bool,const Loader* Function,+,loader_lock,_Bool,Loader* Function,+,loader_show_menu,void, Function,+,loader_start,LoaderStatus,"Loader*, const char*, const char*" @@ -1570,11 +1571,11 @@ Function,+,saved_struct_save,_Bool,"const char*, void*, size_t, uint8_t, uint8_t Function,-,scanf,int,"const char*, ..." Function,+,scene_manager_alloc,SceneManager*,"const SceneManagerHandlers*, void*" Function,+,scene_manager_free,void,SceneManager* -Function,+,scene_manager_get_scene_state,uint32_t,"SceneManager*, uint32_t" +Function,+,scene_manager_get_scene_state,uint32_t,"const SceneManager*, uint32_t" Function,+,scene_manager_handle_back_event,_Bool,SceneManager* Function,+,scene_manager_handle_custom_event,_Bool,"SceneManager*, uint32_t" Function,+,scene_manager_handle_tick_event,void,SceneManager* -Function,+,scene_manager_has_previous_scene,_Bool,"SceneManager*, uint32_t" +Function,+,scene_manager_has_previous_scene,_Bool,"const SceneManager*, uint32_t" Function,+,scene_manager_next_scene,void,"SceneManager*, uint32_t" Function,+,scene_manager_previous_scene,_Bool,SceneManager* Function,+,scene_manager_search_and_switch_to_another_scene,_Bool,"SceneManager*, uint32_t" @@ -1946,11 +1947,11 @@ Function,+,view_port_alloc,ViewPort*, Function,+,view_port_draw_callback_set,void,"ViewPort*, ViewPortDrawCallback, void*" Function,+,view_port_enabled_set,void,"ViewPort*, _Bool" Function,+,view_port_free,void,ViewPort* -Function,+,view_port_get_height,uint8_t,ViewPort* +Function,+,view_port_get_height,uint8_t,const ViewPort* Function,+,view_port_get_orientation,ViewPortOrientation,const ViewPort* -Function,+,view_port_get_width,uint8_t,ViewPort* +Function,+,view_port_get_width,uint8_t,const ViewPort* Function,+,view_port_input_callback_set,void,"ViewPort*, ViewPortInputCallback, void*" -Function,+,view_port_is_enabled,_Bool,ViewPort* +Function,+,view_port_is_enabled,_Bool,const ViewPort* Function,+,view_port_set_height,void,"ViewPort*, uint8_t" Function,+,view_port_set_orientation,void,"ViewPort*, ViewPortOrientation" Function,+,view_port_set_width,void,"ViewPort*, uint8_t" diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 906ab357aaa..33c443ae056 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,13.1,, +Version,+,14.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -607,7 +607,7 @@ Function,-,bzero,void,"void*, size_t" Function,-,calloc,void*,"size_t, size_t" Function,+,canvas_clear,void,Canvas* Function,+,canvas_commit,void,Canvas* -Function,+,canvas_current_font_height,uint8_t,Canvas* +Function,+,canvas_current_font_height,uint8_t,const Canvas* Function,+,canvas_draw_bitmap,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, const uint8_t*" Function,+,canvas_draw_box,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t" Function,+,canvas_draw_circle,void,"Canvas*, uint8_t, uint8_t, uint8_t" @@ -624,18 +624,18 @@ Function,+,canvas_draw_str,void,"Canvas*, uint8_t, uint8_t, const char*" Function,+,canvas_draw_str_aligned,void,"Canvas*, uint8_t, uint8_t, Align, Align, const char*" Function,+,canvas_draw_triangle,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, CanvasDirection" Function,+,canvas_draw_xbm,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, const uint8_t*" -Function,+,canvas_get_font_params,CanvasFontParameters*,"Canvas*, Font" +Function,+,canvas_get_font_params,const CanvasFontParameters*,"const Canvas*, Font" Function,+,canvas_glyph_width,uint8_t,"Canvas*, char" -Function,+,canvas_height,uint8_t,Canvas* +Function,+,canvas_height,uint8_t,const Canvas* Function,+,canvas_invert_color,void,Canvas* Function,+,canvas_reset,void,Canvas* Function,+,canvas_set_bitmap_mode,void,"Canvas*, _Bool" Function,+,canvas_set_color,void,"Canvas*, Color" -Function,+,canvas_set_font,void,"Canvas*, Font" Function,+,canvas_set_custom_u8g2_font,void,"Canvas*, const uint8_t*" +Function,+,canvas_set_font,void,"Canvas*, Font" Function,+,canvas_set_font_direction,void,"Canvas*, CanvasDirection" Function,+,canvas_string_width,uint16_t,"Canvas*, const char*" -Function,+,canvas_width,uint8_t,Canvas* +Function,+,canvas_width,uint8_t,const Canvas* Function,-,cbrt,double,double Function,-,cbrtf,float,float Function,-,cbrtl,long double,long double @@ -1095,7 +1095,6 @@ Function,-,furi_hal_flash_write_dword,void,"size_t, uint64_t" Function,+,furi_hal_gpio_add_int_callback,void,"const GpioPin*, GpioExtiCallback, void*" Function,+,furi_hal_gpio_disable_int_callback,void,const GpioPin* Function,+,furi_hal_gpio_enable_int_callback,void,const GpioPin* -Function,+,furi_hal_resources_get_ext_pin_number,int32_t,const GpioPin* Function,+,furi_hal_gpio_init,void,"const GpioPin*, const GpioMode, const GpioPull, const GpioSpeed" Function,+,furi_hal_gpio_init_ex,void,"const GpioPin*, const GpioMode, const GpioPull, const GpioSpeed, const GpioAltFn" Function,+,furi_hal_gpio_init_simple,void,"const GpioPin*, const GpioMode" @@ -1249,6 +1248,7 @@ Function,+,furi_hal_region_is_frequency_allowed,_Bool,uint32_t Function,+,furi_hal_region_is_provisioned,_Bool, Function,+,furi_hal_region_set,void,FuriHalRegion* Function,-,furi_hal_resources_deinit_early,void, +Function,+,furi_hal_resources_get_ext_pin_number,int32_t,const GpioPin* Function,-,furi_hal_resources_init,void, Function,-,furi_hal_resources_init_early,void, Function,+,furi_hal_rfid_change_read_config,void,"float, float" @@ -1319,9 +1319,9 @@ Function,+,furi_hal_spi_bus_handle_deinit,void,FuriHalSpiBusHandle* Function,+,furi_hal_spi_bus_handle_init,void,FuriHalSpiBusHandle* Function,+,furi_hal_spi_bus_init,void,FuriHalSpiBus* Function,+,furi_hal_spi_bus_rx,_Bool,"FuriHalSpiBusHandle*, uint8_t*, size_t, uint32_t" -Function,+,furi_hal_spi_bus_trx,_Bool,"FuriHalSpiBusHandle*, uint8_t*, uint8_t*, size_t, uint32_t" +Function,+,furi_hal_spi_bus_trx,_Bool,"FuriHalSpiBusHandle*, const uint8_t*, uint8_t*, size_t, uint32_t" Function,+,furi_hal_spi_bus_trx_dma,_Bool,"FuriHalSpiBusHandle*, uint8_t*, uint8_t*, size_t, uint32_t" -Function,+,furi_hal_spi_bus_tx,_Bool,"FuriHalSpiBusHandle*, uint8_t*, size_t, uint32_t" +Function,+,furi_hal_spi_bus_tx,_Bool,"FuriHalSpiBusHandle*, const uint8_t*, size_t, uint32_t" Function,-,furi_hal_spi_config_deinit_early,void, Function,-,furi_hal_spi_config_init,void, Function,-,furi_hal_spi_config_init_early,void, @@ -1578,7 +1578,7 @@ Function,+,gui_add_framebuffer_callback,void,"Gui*, GuiCanvasCommitCallback, voi Function,+,gui_add_view_port,void,"Gui*, ViewPort*, GuiLayer" Function,+,gui_direct_draw_acquire,Canvas*,Gui* Function,+,gui_direct_draw_release,void,Gui* -Function,+,gui_get_framebuffer_size,size_t,Gui* +Function,+,gui_get_framebuffer_size,size_t,const Gui* Function,+,gui_remove_framebuffer_callback,void,"Gui*, GuiCanvasCommitCallback, void*" Function,+,gui_remove_view_port,void,"Gui*, ViewPort*" Function,+,gui_set_lockdown,void,"Gui*, _Bool" @@ -1621,9 +1621,9 @@ Function,+,ibutton_worker_write_set_callback,void,"iButtonWorker*, iButtonWorker Function,+,ibutton_worker_write_start,void,"iButtonWorker*, iButtonKey*" Function,+,icon_animation_alloc,IconAnimation*,const Icon* Function,+,icon_animation_free,void,IconAnimation* -Function,+,icon_animation_get_height,uint8_t,IconAnimation* -Function,+,icon_animation_get_width,uint8_t,IconAnimation* -Function,+,icon_animation_is_last_frame,_Bool,IconAnimation* +Function,+,icon_animation_get_height,uint8_t,const IconAnimation* +Function,+,icon_animation_get_width,uint8_t,const IconAnimation* +Function,+,icon_animation_is_last_frame,_Bool,const IconAnimation* Function,+,icon_animation_set_update_callback,void,"IconAnimation*, IconAnimationCallback, void*" Function,+,icon_animation_start,void,IconAnimation* Function,+,icon_animation_stop,void,IconAnimation* @@ -1763,7 +1763,7 @@ Function,-,llround,long long int,double Function,-,llroundf,long long int,float Function,-,llroundl,long long int,long double Function,+,loader_get_pubsub,FuriPubSub*,Loader* -Function,+,loader_is_locked,_Bool,Loader* +Function,+,loader_is_locked,_Bool,const Loader* Function,+,loader_lock,_Bool,Loader* Function,+,loader_show_menu,void, Function,+,loader_start,LoaderStatus,"Loader*, const char*, const char*" @@ -2372,11 +2372,11 @@ Function,-,scalbnl,long double,"long double, int" Function,-,scanf,int,"const char*, ..." Function,+,scene_manager_alloc,SceneManager*,"const SceneManagerHandlers*, void*" Function,+,scene_manager_free,void,SceneManager* -Function,+,scene_manager_get_scene_state,uint32_t,"SceneManager*, uint32_t" +Function,+,scene_manager_get_scene_state,uint32_t,"const SceneManager*, uint32_t" Function,+,scene_manager_handle_back_event,_Bool,SceneManager* Function,+,scene_manager_handle_custom_event,_Bool,"SceneManager*, uint32_t" Function,+,scene_manager_handle_tick_event,void,SceneManager* -Function,+,scene_manager_has_previous_scene,_Bool,"SceneManager*, uint32_t" +Function,+,scene_manager_has_previous_scene,_Bool,"const SceneManager*, uint32_t" Function,+,scene_manager_next_scene,void,"SceneManager*, uint32_t" Function,+,scene_manager_previous_scene,_Bool,SceneManager* Function,+,scene_manager_search_and_switch_to_another_scene,_Bool,"SceneManager*, uint32_t" @@ -2895,11 +2895,11 @@ Function,+,view_port_alloc,ViewPort*, Function,+,view_port_draw_callback_set,void,"ViewPort*, ViewPortDrawCallback, void*" Function,+,view_port_enabled_set,void,"ViewPort*, _Bool" Function,+,view_port_free,void,ViewPort* -Function,+,view_port_get_height,uint8_t,ViewPort* +Function,+,view_port_get_height,uint8_t,const ViewPort* Function,+,view_port_get_orientation,ViewPortOrientation,const ViewPort* -Function,+,view_port_get_width,uint8_t,ViewPort* +Function,+,view_port_get_width,uint8_t,const ViewPort* Function,+,view_port_input_callback_set,void,"ViewPort*, ViewPortInputCallback, void*" -Function,+,view_port_is_enabled,_Bool,ViewPort* +Function,+,view_port_is_enabled,_Bool,const ViewPort* Function,+,view_port_set_height,void,"ViewPort*, uint8_t" Function,+,view_port_set_orientation,void,"ViewPort*, ViewPortOrientation" Function,+,view_port_set_width,void,"ViewPort*, uint8_t" diff --git a/firmware/targets/f7/furi_hal/furi_hal_spi.c b/firmware/targets/f7/furi_hal/furi_hal_spi.c index 8dba8327f5f..42b85479955 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_spi.c +++ b/firmware/targets/f7/furi_hal/furi_hal_spi.c @@ -103,7 +103,7 @@ bool furi_hal_spi_bus_rx( bool furi_hal_spi_bus_tx( FuriHalSpiBusHandle* handle, - uint8_t* buffer, + const uint8_t* buffer, size_t size, uint32_t timeout) { furi_assert(handle); @@ -128,7 +128,7 @@ bool furi_hal_spi_bus_tx( bool furi_hal_spi_bus_trx( FuriHalSpiBusHandle* handle, - uint8_t* tx_buffer, + const uint8_t* tx_buffer, uint8_t* rx_buffer, size_t size, uint32_t timeout) { diff --git a/firmware/targets/furi_hal_include/furi_hal_spi.h b/firmware/targets/furi_hal_include/furi_hal_spi.h index 79e80931754..af15a88381a 100644 --- a/firmware/targets/furi_hal_include/furi_hal_spi.h +++ b/firmware/targets/furi_hal_include/furi_hal_spi.h @@ -85,7 +85,7 @@ bool furi_hal_spi_bus_rx( */ bool furi_hal_spi_bus_tx( FuriHalSpiBusHandle* handle, - uint8_t* buffer, + const uint8_t* buffer, size_t size, uint32_t timeout); @@ -101,7 +101,7 @@ bool furi_hal_spi_bus_tx( */ bool furi_hal_spi_bus_trx( FuriHalSpiBusHandle* handle, - uint8_t* tx_buffer, + const uint8_t* tx_buffer, uint8_t* rx_buffer, size_t size, uint32_t timeout); From 82c730b6bec8095fea3aef1aaf2014212b90410a Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Thu, 9 Feb 2023 09:45:30 +0400 Subject: [PATCH 401/824] SubGhz: fix cc1101_read_fifo func (#2379) --- lib/drivers/cc1101.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/lib/drivers/cc1101.c b/lib/drivers/cc1101.c index d563c30c327..d0feb021878 100644 --- a/lib/drivers/cc1101.c +++ b/lib/drivers/cc1101.c @@ -150,9 +150,8 @@ uint8_t cc1101_write_fifo(FuriHalSpiBusHandle* handle, const uint8_t* data, uint } uint8_t cc1101_read_fifo(FuriHalSpiBusHandle* handle, uint8_t* data, uint8_t* size) { - uint8_t buff_tx[64]; - buff_tx[0] = CC1101_FIFO | CC1101_READ | CC1101_BURST; - uint8_t buff_rx[2]; + uint8_t buff_trx[2]; + buff_trx[0] = CC1101_FIFO | CC1101_READ | CC1101_BURST; // Start transaction // Wait IC to become ready @@ -160,15 +159,15 @@ uint8_t cc1101_read_fifo(FuriHalSpiBusHandle* handle, uint8_t* data, uint8_t* si ; // First byte - packet length - furi_hal_spi_bus_trx(handle, buff_tx, buff_rx, 2, CC1101_TIMEOUT); + furi_hal_spi_bus_trx(handle, buff_trx, buff_trx, 2, CC1101_TIMEOUT); // Check that the packet is placed in the receive buffer - if(buff_rx[1] > 64) { + if(buff_trx[1] > 64) { *size = 64; } else { - *size = buff_rx[1]; + *size = buff_trx[1]; } - furi_hal_spi_bus_trx(handle, &buff_tx[1], data, *size, CC1101_TIMEOUT); + furi_hal_spi_bus_trx(handle, NULL, data, *size, CC1101_TIMEOUT); return *size; -} +} \ No newline at end of file From 67c2d1cf6144da79e0077427b6ca09ee85f730ea Mon Sep 17 00:00:00 2001 From: Max Andreev Date: Thu, 9 Feb 2023 13:42:41 +0300 Subject: [PATCH 402/824] Migrating CI/CD to Linode S3 (#2380) * Test PVS linode S3 * Migrating to Linode S3 * Disable PVS action debug * Fix pvs_studio.yml --- .github/workflows/build.yml | 11 +++++------ .github/workflows/pvs_studio.yml | 21 ++++++++++----------- scripts/merge_report_qa.py | 2 +- 3 files changed, 16 insertions(+), 18 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index be981768444..689dd2037f5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -96,14 +96,14 @@ jobs: - name: 'Upload map analyser files to storage' if: ${{ !github.event.pull_request.head.repo.fork }} - uses: keithweaver/aws-s3-github-action@v1.0.0 + uses: prewk/s3-cp-action@v2 with: - source: map_analyser_files/ - destination: "s3://${{ secrets.MAP_REPORT_AWS_BUCKET }}/${{steps.names.outputs.random_hash}}" + aws_s3_endpoint: "${{ secrets.MAP_REPORT_AWS_ENDPOINT }}" aws_access_key_id: "${{ secrets.MAP_REPORT_AWS_ACCESS_KEY }}" aws_secret_access_key: "${{ secrets.MAP_REPORT_AWS_SECRET_KEY }}" - aws_region: "${{ secrets.MAP_REPORT_AWS_REGION }}" - flags: --recursive + source: "./map_analyser_files/" + dest: "s3://${{ secrets.MAP_REPORT_AWS_BUCKET }}/${{steps.names.outputs.random_hash}}" + flags: "--recursive --acl public-read" - name: 'Trigger map file reporter' if: ${{ !github.event.pull_request.head.repo.fork }} @@ -114,7 +114,6 @@ jobs: event-type: map-file-analyse client-payload: '{"random_hash": "${{steps.names.outputs.random_hash}}", "event_type": "${{steps.names.outputs.event_type}}"}' - - name: 'Upload artifacts to update server' if: ${{ !github.event.pull_request.head.repo.fork }} run: | diff --git a/.github/workflows/pvs_studio.yml b/.github/workflows/pvs_studio.yml index a4ac6e30127..65a8b6150e0 100644 --- a/.github/workflows/pvs_studio.yml +++ b/.github/workflows/pvs_studio.yml @@ -54,17 +54,16 @@ jobs: ./fbt COMPACT=1 PVSNOBROWSER=1 firmware_pvs || WARNINGS=1 echo "warnings=${WARNINGS}" >> $GITHUB_OUTPUT - - name: 'Upload artifacts to update server' + - name: 'Upload report' if: ${{ !github.event.pull_request.head.repo.fork && (steps.pvs-warn.outputs.warnings != 0) }} - run: | - mkdir -p ~/.ssh - ssh-keyscan -p ${{ secrets.RSYNC_DEPLOY_PORT }} -H ${{ secrets.RSYNC_DEPLOY_HOST }} > ~/.ssh/known_hosts - echo "${{ secrets.RSYNC_DEPLOY_KEY }}" > deploy_key; - chmod 600 ./deploy_key; - rsync -avrzP --mkpath \ - -e 'ssh -p ${{ secrets.RSYNC_DEPLOY_PORT }} -i ./deploy_key' \ - build/f7-firmware-DC/pvsreport/ ${{ secrets.RSYNC_DEPLOY_USER }}@${{ secrets.RSYNC_DEPLOY_HOST }}:/home/data/firmware-pvs-studio-report/"${BRANCH_NAME}/${{steps.names.outputs.default_target}}-${{steps.names.outputs.suffix}}/"; - rm ./deploy_key; + uses: prewk/s3-cp-action@v2 + with: + aws_s3_endpoint: "${{ secrets.PVS_AWS_ENDPOINT }}" + aws_access_key_id: "${{ secrets.PVS_AWS_ACCESS_KEY }}" + aws_secret_access_key: "${{ secrets.PVS_AWS_SECRET_KEY }}" + source: "./build/f7-firmware-DC/pvsreport" + dest: "s3://${{ secrets.PVS_AWS_BUCKET }}/${{steps.names.outputs.branch_name}}/${{steps.names.outputs.default_target}}-${{steps.names.outputs.suffix}}/" + flags: "--recursive --acl public-read" - name: 'Find Previous Comment' if: ${{ !github.event.pull_request.head.repo.fork && github.event.pull_request && (steps.pvs-warn.outputs.warnings != 0) }} @@ -83,7 +82,7 @@ jobs: issue-number: ${{ github.event.pull_request.number }} body: | **PVS-Studio report for commit `${{steps.names.outputs.commit_sha}}`:** - - [Report](https://update.flipperzero.one/builds/firmware-pvs-studio-report/${{steps.names.outputs.branch_name}}/${{steps.names.outputs.default_target}}-${{steps.names.outputs.suffix}}/index.html) + - [Report](https://pvs.flipp.dev/${{steps.names.outputs.branch_name}}/${{steps.names.outputs.default_target}}-${{steps.names.outputs.suffix}}/index.html) edit-mode: replace - name: 'Raise exception' diff --git a/scripts/merge_report_qa.py b/scripts/merge_report_qa.py index c0707848eed..caa74240854 100755 --- a/scripts/merge_report_qa.py +++ b/scripts/merge_report_qa.py @@ -17,7 +17,7 @@ def parse_args(): def checkCommitMessage(msg): - regex = re.compile(r"^'?\[FL-\d+\]") + regex = re.compile(r"^'?\[(FL-\d+,?\s?)+\]") if regex.match(msg): return True return False From 3eacb0c715b09242fa48d6158803d6af09059a02 Mon Sep 17 00:00:00 2001 From: Pierce Date: Fri, 10 Feb 2023 04:52:53 -0800 Subject: [PATCH 403/824] Fixed typo in nfc_magic_scene_wrong_card.c (#2382) --- .../plugins/nfc_magic/scenes/nfc_magic_scene_wrong_card.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/plugins/nfc_magic/scenes/nfc_magic_scene_wrong_card.c b/applications/plugins/nfc_magic/scenes/nfc_magic_scene_wrong_card.c index 69bf9eb5026..4b80896932d 100644 --- a/applications/plugins/nfc_magic/scenes/nfc_magic_scene_wrong_card.c +++ b/applications/plugins/nfc_magic/scenes/nfc_magic_scene_wrong_card.c @@ -26,7 +26,7 @@ void nfc_magic_scene_wrong_card_on_enter(void* context) { AlignLeft, AlignTop, FontSecondary, - "Writing is supported\nonly for 4 bytes UID\nMifare CLassic 1k"); + "Writing is supported\nonly for 4 bytes UID\nMifare Classic 1k"); widget_add_button_element( widget, GuiButtonTypeLeft, "Retry", nfc_magic_scene_wrong_card_widget_callback, nfc_magic); From 628f089c420e4a79d6e8134ea5934b3661678669 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Mon, 13 Feb 2023 04:10:36 +0000 Subject: [PATCH 404/824] Mark debug app as debug not external (#2387) --- applications/debug/example_custom_font/application.fam | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/debug/example_custom_font/application.fam b/applications/debug/example_custom_font/application.fam index 02285b8a01f..06c0a7f6183 100644 --- a/applications/debug/example_custom_font/application.fam +++ b/applications/debug/example_custom_font/application.fam @@ -1,7 +1,7 @@ App( appid="example_custom_font", name="Example: custom font", - apptype=FlipperAppType.EXTERNAL, + apptype=FlipperAppType.DEBUG, entry_point="example_custom_font_main", requires=["gui"], stack_size=1 * 1024, From 5a730e3adc4e762b06f45e9348bb4d1e28b431b3 Mon Sep 17 00:00:00 2001 From: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> Date: Mon, 13 Feb 2023 14:33:15 +0300 Subject: [PATCH 405/824] [FL-3107] Fix Cyfral & Metakom emulation (#2392) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix Cyfral & Metakom emulation Co-authored-by: あく --- lib/one_wire/ibutton/ibutton_worker_modes.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/one_wire/ibutton/ibutton_worker_modes.c b/lib/one_wire/ibutton/ibutton_worker_modes.c index da6b107668a..4f7f0855abd 100644 --- a/lib/one_wire/ibutton/ibutton_worker_modes.c +++ b/lib/one_wire/ibutton/ibutton_worker_modes.c @@ -237,10 +237,8 @@ void ibutton_worker_emulate_timer_cb(void* context) { const LevelDuration level_duration = protocol_dict_encoder_yield(worker->protocols, worker->protocol_to_encode); - const bool level = level_duration_get_level(level_duration); - - furi_hal_ibutton_emulate_set_next(level); - furi_hal_ibutton_pin_write(level); + furi_hal_ibutton_emulate_set_next(level_duration_get_duration(level_duration)); + furi_hal_ibutton_pin_write(level_duration_get_level(level_duration)); } void ibutton_worker_emulate_timer_start(iButtonWorker* worker) { From d0c6c3402c608da22162e670d03b262d70f812be Mon Sep 17 00:00:00 2001 From: hedger Date: Mon, 13 Feb 2023 18:07:53 +0400 Subject: [PATCH 406/824] Updater visual fixes (#2391) * updater: removed unused code; fbt: fixed build error processing in certain cases * updater: simplified internal pre-update state * updater: rebalanced stage weights; fixed progress hiccups --- .../updater/scenes/updater_scene_error.c | 6 ++- .../updater/scenes/updater_scene_loadcfg.c | 19 ++++------ applications/system/updater/updater_i.h | 8 +--- .../system/updater/util/update_task.c | 22 +++++------ .../updater/util/update_task_worker_backup.c | 38 +++++++++++++------ lib/toolbox/tar/tar_archive.c | 1 + scripts/fbt_tools/pvsstudio.py | 17 +++++---- 7 files changed, 62 insertions(+), 49 deletions(-) diff --git a/applications/system/updater/scenes/updater_scene_error.c b/applications/system/updater/scenes/updater_scene_error.c index 21bf1637224..dbe97c96b01 100644 --- a/applications/system/updater/scenes/updater_scene_error.c +++ b/applications/system/updater/scenes/updater_scene_error.c @@ -58,8 +58,12 @@ bool updater_scene_error_on_event(void* context, SceneManagerEvent event) { } void updater_scene_error_on_exit(void* context) { + furi_assert(context); Updater* updater = (Updater*)context; widget_reset(updater->widget); - free(updater->pending_update); + + if(updater->loaded_manifest) { + update_manifest_free(updater->loaded_manifest); + } } diff --git a/applications/system/updater/scenes/updater_scene_loadcfg.c b/applications/system/updater/scenes/updater_scene_loadcfg.c index 14f7b203af2..99866a6dfdd 100644 --- a/applications/system/updater/scenes/updater_scene_loadcfg.c +++ b/applications/system/updater/scenes/updater_scene_loadcfg.c @@ -21,11 +21,9 @@ void updater_scene_loadcfg_apply_callback(GuiButtonType result, InputType type, void updater_scene_loadcfg_on_enter(void* context) { Updater* updater = (Updater*)context; - UpdaterManifestProcessingState* pending_upd = updater->pending_update = - malloc(sizeof(UpdaterManifestProcessingState)); - pending_upd->manifest = update_manifest_alloc(); + UpdateManifest* loaded_manifest = updater->loaded_manifest = update_manifest_alloc(); - if(update_manifest_init(pending_upd->manifest, furi_string_get_cstr(updater->startup_arg))) { + if(update_manifest_init(loaded_manifest, furi_string_get_cstr(updater->startup_arg))) { widget_add_string_element( updater->widget, 64, 12, AlignCenter, AlignCenter, FontPrimary, "Update"); @@ -37,7 +35,7 @@ void updater_scene_loadcfg_on_enter(void* context) { 32, AlignCenter, AlignCenter, - furi_string_get_cstr(pending_upd->manifest->version), + furi_string_get_cstr(loaded_manifest->version), true); widget_add_button_element( @@ -95,13 +93,12 @@ bool updater_scene_loadcfg_on_event(void* context, SceneManagerEvent event) { } void updater_scene_loadcfg_on_exit(void* context) { + furi_assert(context); Updater* updater = (Updater*)context; - if(updater->pending_update) { - update_manifest_free(updater->pending_update->manifest); - furi_string_free(updater->pending_update->message); - } - widget_reset(updater->widget); - free(updater->pending_update); + + if(updater->loaded_manifest) { + update_manifest_free(updater->loaded_manifest); + } } diff --git a/applications/system/updater/updater_i.h b/applications/system/updater/updater_i.h index ae249f38f17..4e3c704d21a 100644 --- a/applications/system/updater/updater_i.h +++ b/applications/system/updater/updater_i.h @@ -33,12 +33,6 @@ typedef enum { UpdaterCustomEventSdUnmounted, } UpdaterCustomEvent; -typedef struct UpdaterManifestProcessingState { - UpdateManifest* manifest; - FuriString* message; - bool ready_to_be_applied; -} UpdaterManifestProcessingState; - typedef struct { // GUI Gui* gui; @@ -49,7 +43,7 @@ typedef struct { UpdaterMainView* main_view; - UpdaterManifestProcessingState* pending_update; + UpdateManifest* loaded_manifest; UpdatePrepareResult preparation_result; UpdateTask* update_task; diff --git a/applications/system/updater/util/update_task.c b/applications/system/updater/util/update_task.c index 54fe27995cb..708d10ce040 100644 --- a/applications/system/updater/util/update_task.c +++ b/applications/system/updater/util/update_task.c @@ -41,22 +41,22 @@ typedef struct { static const UpdateTaskStageGroupMap update_task_stage_progress[] = { [UpdateTaskStageProgress] = STAGE_DEF(UpdateTaskStageGroupMisc, 0), - [UpdateTaskStageReadManifest] = STAGE_DEF(UpdateTaskStageGroupPreUpdate, 5), - [UpdateTaskStageLfsBackup] = STAGE_DEF(UpdateTaskStageGroupPreUpdate, 15), + [UpdateTaskStageReadManifest] = STAGE_DEF(UpdateTaskStageGroupPreUpdate, 45), + [UpdateTaskStageLfsBackup] = STAGE_DEF(UpdateTaskStageGroupPreUpdate, 5), [UpdateTaskStageRadioImageValidate] = STAGE_DEF(UpdateTaskStageGroupRadio, 15), - [UpdateTaskStageRadioErase] = STAGE_DEF(UpdateTaskStageGroupRadio, 60), - [UpdateTaskStageRadioWrite] = STAGE_DEF(UpdateTaskStageGroupRadio, 80), - [UpdateTaskStageRadioInstall] = STAGE_DEF(UpdateTaskStageGroupRadio, 60), - [UpdateTaskStageRadioBusy] = STAGE_DEF(UpdateTaskStageGroupRadio, 80), + [UpdateTaskStageRadioErase] = STAGE_DEF(UpdateTaskStageGroupRadio, 35), + [UpdateTaskStageRadioWrite] = STAGE_DEF(UpdateTaskStageGroupRadio, 60), + [UpdateTaskStageRadioInstall] = STAGE_DEF(UpdateTaskStageGroupRadio, 30), + [UpdateTaskStageRadioBusy] = STAGE_DEF(UpdateTaskStageGroupRadio, 5), - [UpdateTaskStageOBValidation] = STAGE_DEF(UpdateTaskStageGroupOptionBytes, 10), + [UpdateTaskStageOBValidation] = STAGE_DEF(UpdateTaskStageGroupOptionBytes, 2), - [UpdateTaskStageValidateDFUImage] = STAGE_DEF(UpdateTaskStageGroupFirmware, 50), - [UpdateTaskStageFlashWrite] = STAGE_DEF(UpdateTaskStageGroupFirmware, 200), - [UpdateTaskStageFlashValidate] = STAGE_DEF(UpdateTaskStageGroupFirmware, 30), + [UpdateTaskStageValidateDFUImage] = STAGE_DEF(UpdateTaskStageGroupFirmware, 30), + [UpdateTaskStageFlashWrite] = STAGE_DEF(UpdateTaskStageGroupFirmware, 150), + [UpdateTaskStageFlashValidate] = STAGE_DEF(UpdateTaskStageGroupFirmware, 15), - [UpdateTaskStageLfsRestore] = STAGE_DEF(UpdateTaskStageGroupPostUpdate, 30), + [UpdateTaskStageLfsRestore] = STAGE_DEF(UpdateTaskStageGroupPostUpdate, 5), [UpdateTaskStageResourcesUpdate] = STAGE_DEF(UpdateTaskStageGroupResources, 255), [UpdateTaskStageSplashscreenInstall] = STAGE_DEF(UpdateTaskStageGroupSplashscreen, 5), diff --git a/applications/system/updater/util/update_task_worker_backup.c b/applications/system/updater/util/update_task_worker_backup.c index 78040106845..ed53c353bd1 100644 --- a/applications/system/updater/util/update_task_worker_backup.c +++ b/applications/system/updater/util/update_task_worker_backup.c @@ -41,6 +41,14 @@ static bool update_task_pre_update(UpdateTask* update_task) { return success; } +typedef enum { + UpdateTaskResourcesWeightsFileCleanup = 20, + UpdateTaskResourcesWeightsDirCleanup = 20, + UpdateTaskResourcesWeightsFileUnpack = 60, +} UpdateTaskResourcesWeights; + +#define UPDATE_TASK_RESOURCES_FILE_TO_TOTAL_PERCENT 90 + typedef struct { UpdateTask* update_task; int32_t total_files, processed_files; @@ -54,33 +62,36 @@ static bool update_task_resource_unpack_cb(const char* name, bool is_directory, update_task_set_progress( unpack_progress->update_task, UpdateTaskStageProgress, - /* For this stage, last 70% of progress = extraction */ - 30 + (unpack_progress->processed_files * 70) / (unpack_progress->total_files + 1)); + /* For this stage, last progress segment = extraction */ + (UpdateTaskResourcesWeightsFileCleanup + UpdateTaskResourcesWeightsDirCleanup) + + (unpack_progress->processed_files * UpdateTaskResourcesWeightsFileUnpack) / + (unpack_progress->total_files + 1)); return true; } -static void - update_task_cleanup_resources(UpdateTask* update_task, uint32_t n_approx_file_entries) { +static void update_task_cleanup_resources(UpdateTask* update_task, const uint32_t n_tar_entries) { ResourceManifestReader* manifest_reader = resource_manifest_reader_alloc(update_task->storage); do { - FURI_LOG_I(TAG, "Cleaning up old manifest"); + FURI_LOG_D(TAG, "Cleaning up old manifest"); if(!resource_manifest_reader_open(manifest_reader, EXT_PATH("Manifest"))) { FURI_LOG_W(TAG, "No existing manifest"); break; } - /* We got # of entries in TAR file. Approx 1/4th is dir entries, we skip them */ - n_approx_file_entries = n_approx_file_entries * 3 / 4 + 1; - uint32_t n_processed_files = 0; + const uint32_t n_approx_file_entries = + n_tar_entries * UPDATE_TASK_RESOURCES_FILE_TO_TOTAL_PERCENT / 100 + 1; + uint32_t n_dir_entries = 1; ResourceManifestEntry* entry_ptr = NULL; + uint32_t n_processed_entries = 0; while((entry_ptr = resource_manifest_reader_next(manifest_reader))) { if(entry_ptr->type == ResourceManifestEntryTypeFile) { update_task_set_progress( update_task, UpdateTaskStageProgress, - /* For this stage, first 20% of progress = cleanup files */ - (n_processed_files++ * 20) / (n_approx_file_entries + 1)); + /* For this stage, first pass = old manifest's file cleanup */ + (n_processed_entries++ * UpdateTaskResourcesWeightsFileCleanup) / + n_approx_file_entries); FuriString* file_path = furi_string_alloc(); path_concat( @@ -88,16 +99,21 @@ static void FURI_LOG_D(TAG, "Removing %s", furi_string_get_cstr(file_path)); storage_simply_remove(update_task->storage, furi_string_get_cstr(file_path)); furi_string_free(file_path); + } else if(entry_ptr->type == ResourceManifestEntryTypeDirectory) { + n_dir_entries++; } } + n_processed_entries = 0; while((entry_ptr = resource_manifest_reader_previous(manifest_reader))) { if(entry_ptr->type == ResourceManifestEntryTypeDirectory) { update_task_set_progress( update_task, UpdateTaskStageProgress, /* For this stage, second 10% of progress = cleanup directories */ - (n_processed_files++ * 10) / (n_approx_file_entries + 1)); + UpdateTaskResourcesWeightsFileCleanup + + (n_processed_entries++ * UpdateTaskResourcesWeightsDirCleanup) / + n_dir_entries); FuriString* folder_path = furi_string_alloc(); File* folder_file = storage_file_alloc(update_task->storage); diff --git a/lib/toolbox/tar/tar_archive.c b/lib/toolbox/tar/tar_archive.c index fd0d175eaa2..01969b47126 100644 --- a/lib/toolbox/tar/tar_archive.c +++ b/lib/toolbox/tar/tar_archive.c @@ -106,6 +106,7 @@ void tar_archive_set_file_callback(TarArchive* archive, tar_unpack_file_cb callb static int tar_archive_entry_counter(mtar_t* tar, const mtar_header_t* header, void* param) { UNUSED(tar); UNUSED(header); + furi_assert(param); int32_t* counter = param; (*counter)++; return 0; diff --git a/scripts/fbt_tools/pvsstudio.py b/scripts/fbt_tools/pvsstudio.py index 02014836ae0..593559a33f0 100644 --- a/scripts/fbt_tools/pvsstudio.py +++ b/scripts/fbt_tools/pvsstudio.py @@ -1,6 +1,6 @@ from SCons.Builder import Builder from SCons.Action import Action -from SCons.Script import Delete, Mkdir, GetBuildFailures +from SCons.Script import Delete, Mkdir, GetBuildFailures, Flatten import multiprocessing import webbrowser import atexit @@ -30,13 +30,14 @@ def atexist_handler(): return for bf in GetBuildFailures(): - if bf.node.exists and bf.node.name.endswith(".html"): - # macOS - if sys.platform == "darwin": - subprocess.run(["open", bf.node.abspath]) - else: - webbrowser.open(bf.node.abspath) - break + for node in Flatten(bf.node): + if node.exists and node.name.endswith(".html"): + # macOS + if sys.platform == "darwin": + subprocess.run(["open", node.abspath]) + else: + webbrowser.open(node.abspath) + break def generate(env): From bc06d407f3931af79b2deaea4e76b4eaa5d1d0e6 Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Mon, 13 Feb 2023 18:43:29 +0300 Subject: [PATCH 407/824] [FL-3113] BadUSB: disable CDC mode, USB mode switch fix (#2394) --- applications/main/bad_usb/bad_usb_app.c | 8 ++++++++ applications/main/bad_usb/bad_usb_app_i.h | 3 +++ applications/main/bad_usb/bad_usb_script.c | 4 ---- firmware/targets/f7/furi_hal/furi_hal_usb.c | 2 +- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/applications/main/bad_usb/bad_usb_app.c b/applications/main/bad_usb/bad_usb_app.c index 5f2aa4789f2..1b24957915c 100644 --- a/applications/main/bad_usb/bad_usb_app.c +++ b/applications/main/bad_usb/bad_usb_app.c @@ -115,8 +115,12 @@ BadUsbApp* bad_usb_app_alloc(char* arg) { if(furi_hal_usb_is_locked()) { app->error = BadUsbAppErrorCloseRpc; + app->usb_if_prev = NULL; scene_manager_next_scene(app->scene_manager, BadUsbSceneError); } else { + app->usb_if_prev = furi_hal_usb_get_config(); + furi_check(furi_hal_usb_set_config(NULL, NULL)); + if(!furi_string_empty(app->file_path)) { app->bad_usb_script = bad_usb_script_open(app->file_path); bad_usb_script_set_keyboard_layout(app->bad_usb_script, app->keyboard_layout); @@ -138,6 +142,10 @@ void bad_usb_app_free(BadUsbApp* app) { app->bad_usb_script = NULL; } + if(app->usb_if_prev) { + furi_check(furi_hal_usb_set_config(app->usb_if_prev, NULL)); + } + // Views view_dispatcher_remove_view(app->view_dispatcher, BadUsbAppViewWork); bad_usb_free(app->bad_usb_view); diff --git a/applications/main/bad_usb/bad_usb_app_i.h b/applications/main/bad_usb/bad_usb_app_i.h index 1bd1964c86c..588c4c2d5f5 100644 --- a/applications/main/bad_usb/bad_usb_app_i.h +++ b/applications/main/bad_usb/bad_usb_app_i.h @@ -14,6 +14,7 @@ #include #include #include "views/bad_usb_view.h" +#include #define BAD_USB_APP_BASE_FOLDER ANY_PATH("badusb") #define BAD_USB_APP_PATH_LAYOUT_FOLDER BAD_USB_APP_BASE_FOLDER "/assets/layouts" @@ -39,6 +40,8 @@ struct BadUsbApp { FuriString* keyboard_layout; BadUsb* bad_usb_view; BadUsbScript* bad_usb_script; + + FuriHalUsbInterface* usb_if_prev; }; typedef enum { diff --git a/applications/main/bad_usb/bad_usb_script.c b/applications/main/bad_usb/bad_usb_script.c index 0fadbcc0793..34dfec2ca85 100644 --- a/applications/main/bad_usb/bad_usb_script.c +++ b/applications/main/bad_usb/bad_usb_script.c @@ -490,8 +490,6 @@ static int32_t bad_usb_worker(void* context) { BadUsbWorkerState worker_state = BadUsbStateInit; int32_t delay_val = 0; - FuriHalUsbInterface* usb_mode_prev = furi_hal_usb_get_config(); - FURI_LOG_I(WORKER_TAG, "Init"); File* script_file = storage_file_alloc(furi_record_open(RECORD_STORAGE)); bad_usb->line = furi_string_alloc(); @@ -642,8 +640,6 @@ static int32_t bad_usb_worker(void* context) { furi_hal_hid_set_state_callback(NULL, NULL); - furi_hal_usb_set_config(usb_mode_prev, NULL); - storage_file_close(script_file); storage_file_free(script_file); furi_string_free(bad_usb->line); diff --git a/firmware/targets/f7/furi_hal/furi_hal_usb.c b/firmware/targets/f7/furi_hal/furi_hal_usb.c index 0038bd348c0..fc679114fa6 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_usb.c +++ b/firmware/targets/f7/furi_hal/furi_hal_usb.c @@ -340,7 +340,7 @@ static void usb_process_mode_start(FuriHalUsbInterface* interface, void* context } static void usb_process_mode_change(FuriHalUsbInterface* interface, void* context) { - if(interface != usb.interface) { + if((interface != usb.interface) || (context != usb.interface_context)) { if(usb.enabled) { // Disable current interface susp_evt(&udev, 0, 0); From 25e89472821701624def0553b4cb4de171a67ee0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Wed, 15 Feb 2023 19:05:08 +0900 Subject: [PATCH 408/824] Dolphin: drop holiday animation (#2398) --- .../L1_Happy_holidays_128x64/frame_0.png | Bin 1787 -> 0 bytes .../L1_Happy_holidays_128x64/frame_1.png | Bin 1795 -> 0 bytes .../L1_Happy_holidays_128x64/frame_10.png | Bin 1791 -> 0 bytes .../L1_Happy_holidays_128x64/frame_11.png | Bin 1814 -> 0 bytes .../L1_Happy_holidays_128x64/frame_12.png | Bin 1779 -> 0 bytes .../L1_Happy_holidays_128x64/frame_2.png | Bin 1774 -> 0 bytes .../L1_Happy_holidays_128x64/frame_3.png | Bin 1801 -> 0 bytes .../L1_Happy_holidays_128x64/frame_4.png | Bin 1799 -> 0 bytes .../L1_Happy_holidays_128x64/frame_5.png | Bin 1797 -> 0 bytes .../L1_Happy_holidays_128x64/frame_6.png | Bin 1789 -> 0 bytes .../L1_Happy_holidays_128x64/frame_7.png | Bin 1804 -> 0 bytes .../L1_Happy_holidays_128x64/frame_8.png | Bin 1809 -> 0 bytes .../L1_Happy_holidays_128x64/frame_9.png | Bin 1816 -> 0 bytes .../L1_Happy_holidays_128x64/meta.txt | 23 ------------------ assets/dolphin/external/manifest.txt | 7 ------ 15 files changed, 30 deletions(-) delete mode 100644 assets/dolphin/external/L1_Happy_holidays_128x64/frame_0.png delete mode 100644 assets/dolphin/external/L1_Happy_holidays_128x64/frame_1.png delete mode 100644 assets/dolphin/external/L1_Happy_holidays_128x64/frame_10.png delete mode 100644 assets/dolphin/external/L1_Happy_holidays_128x64/frame_11.png delete mode 100644 assets/dolphin/external/L1_Happy_holidays_128x64/frame_12.png delete mode 100644 assets/dolphin/external/L1_Happy_holidays_128x64/frame_2.png delete mode 100644 assets/dolphin/external/L1_Happy_holidays_128x64/frame_3.png delete mode 100644 assets/dolphin/external/L1_Happy_holidays_128x64/frame_4.png delete mode 100644 assets/dolphin/external/L1_Happy_holidays_128x64/frame_5.png delete mode 100644 assets/dolphin/external/L1_Happy_holidays_128x64/frame_6.png delete mode 100644 assets/dolphin/external/L1_Happy_holidays_128x64/frame_7.png delete mode 100644 assets/dolphin/external/L1_Happy_holidays_128x64/frame_8.png delete mode 100644 assets/dolphin/external/L1_Happy_holidays_128x64/frame_9.png delete mode 100644 assets/dolphin/external/L1_Happy_holidays_128x64/meta.txt diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_0.png b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_0.png deleted file mode 100644 index 909cc330392b999e38b0326b5381344c952380b8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1787 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 diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_10.png b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_10.png deleted file mode 100644 index 3a43c6b844541f9be3cadc2aa78864e9147919c6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1791 zcmVnvyf3eBg-+&c%%cGpOsw&ZJ*b5*$z;l?5Yi0jy7XFZ9XH9ar)E7M=0e0a+Ci( z*H%Q2(@-5?W+Nw@07V4tJf0gn{`g#T0?YV)Y^APuj|?se z%wo3_XlwL;BVyKdU19uBz)K3K-u#Yqz>+ZSnT!yhp3gOSd--#KNP{F|$U6{2D@90T zb^8>D(e!+21hHjLCR{RkI08$a|KbQkCoyWq)1?u(OR=_A+r`sG&SQhH~mDq%3O>Yy?=Ilw#UdV2kEcJ z|1^18l+nhqE#sYidTXyypvp&LSs93c8Z^vU`HW33+N~ewYdpf5TLtFEvk_5(A@*Ax zU_8V~)uEMY?0V=K(+ynN%V6bMPFsO;2k?kg#tA&e?aA;C%St~hk#!~+rDh$W+ySiH zYE=)O6KKYX8jLwCuVs9<&k3p=A@pfrl$6Nv5u(+J%or`?YfqjRDY7JM7L}<)b~B?$ zeHJ9wP4(ivowkk6{ul2ybEWQm(Or>;k2p2`8(5al&q zytyY}m73^%IlsCRkUB)(mtfk{BVHna)kHx+<38a7k_;lwi!K9dUyt)MH=B)0bQLg` zaoT%*(X1jTfeaL#!yxl{N{01^8bNILE7L8zqZ4GKlSUrLTWVDvRdxGJ`8Vp=Uq41+ z+5(<12}`l46L=jtZ;n?9>~Kir5S5NFBkN%u#F%%ydYkQ4drt6)oWaZQ5>Y`OJ`Mn=e{w+ykX)}gf)|3=W?M5VVMTKHz1U>5SSNYaeXh&e;lruO8> z=c8_E2VfQV=CjZ?V9BvI>!mD|v$e6Bj;LCaGFU`pF-}nC02N@!COiK0SjGV~c?eAU z_2y_}`?HsC1@C|t$}BD+390pgk2TifDM+q2R@(#8Ym~#nBj6sa>Z#8^@`Vt2h+em> zk74AW9y_9HUr2srUG40*{y+pGji|xl9g;V;-(1PMQD{YLL=Yu+Rbchq{tF>qpP)7F zv*HjVa-%#ZvsTp}Wo1JBT>#tYVaG8F&eXP++>RA;vz=2-hQ>WsaWt0$j7p)- zpj3WFX|{gW=2>9s=#1P5G$+u~*NmeIijt zavZC>sNqDDO@q{vasVcj&xCt#E;7Enxd>zAis&|j*;JSXb7rlzc0C8pR@J1IBHEhr zJx%FM5b(1ifW2JSLpy=7s*me8ViMIt;}IDYy9Zk$huWcpK9WX%1ZTX z;c|jB{%oZ1Zi6I56$Z4{V-MT96fALLQ`GKM@#X-Uf!W&~!OGHyG4Kq5Wudj7EnQBL z#3KfsGTv0Ro-b4V~63n$)d(UL0@Fx)i~~L9UKs%_C951OJANj` zX^kRFmgYPwdM)05-u^BCEk2_NLItn51ES8XMTtoH>|x{~*7-iP2?)ru**u4{!{KzqK5I3i?oWoFz;UKB+%t#*AMY#DSS0<6A? zOam&Sd!zwX|23?V1N7HYc9otUqD~w!tn95s)`_fzWdBtGmREX)l8cPv zR&oHOr}r$P6JqKXSi{JQDiRr@%`R)s%MV0U8mb~cqEgc13Dq9FFv!TPESbD9cQJg&%8T&wSwN;2(4m!TUKxmF hpSXqJX8hJ#`vpJ+v^_(~quc-h002ovPDHLkV1n0aQh)#e diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_11.png b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_11.png deleted file mode 100644 index 20ea12eb06d4e6d10d9dcea9f1d2c69277765e1a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1814 zcmV+x2kH2UP)F$kQo{QqB`eacqE2AWGUy4zKj8gn6mK!ct= z=W!g@@y~Ew_Lk4{+$72Uc-$?(Zd}*(uN3UzJpPdfm~Z}ho~9?*X(XOU>cYI|vA-uX zkQMNbn`J50Q?l&C^tWWN#%Jg50#g2QI0jG^c;$wXu~n3Z#xwIVMt`^REwplgPu=7y zba;=up&CGTGtvd`fVPfC_%vnxRdT4g|KlJzKos-A@31*0d|A~mV}`;=^?&7o+EgB`89yZh9k=Sgo9)aF>9pX zVleO`9HJ1!=FQPf%Ujj~8Sk~W(Jt3UGO_z&Gk~gbFh=PWv@`;E90?Mbjg@0u=WRhe zBSP?yK`?DQ5VzC%6O}}z4_2Qch0!k)!!a6GFXOe zfsuWy0n7?O!Z8&+?*P0o!;nO2oU1qhJ9pG*BltCdt_dx7DsMA;yCRHx%)27n_=F0f zG}5X}?HoWOAzJppq@Ck=eyz1>pR;R7*}9FX3mL+P5Hf)1VisAOf!gKaqt{w~n@Lr7 zD!DrquJDw{m?VPeVx4IpyPkaZvWai0=4r?p!5az8RKi$f*(?i`DOgo7OFm|;IbUB@ z@yoJ)b{xzCsz$(A<4%v@fhi`ff~T;kLK(6=)}jkftY>kaOPr6BaaYSS1qE@Bs-~kLObpTj=yRPeE z+_<+ntu$-(`72vk>H2Y-U5> zsALN)$@7X=-7>KAzx^U;Oy~{-xpoA9EgV=i2n~_5tAvFp8=etV>JGd6A_HJc?Y5u zMkFDX&t6{{gPD1~zGx4~qs)&?K_}8~0eQ1qeh?xL(IZl$4>SMWeP?JjkuhgSK}l=( zLe#)k4`9VctH9wMl-GAI|7tOvkv#$?<=t75){y#Da0Omv@i@4zkLNOU=OJe1^*Dis zNLZ-sQS-Lfb#Wnpy*1g}HNzrMvY%Yjn-|Gx^<(DuhiBGgFn<&(4?`m`=;n7C0Yc@FDz`C<$a(Bc#VHb~ zvGxk7Vt|aGa_cS3u|G0zl!JT%@tZ;akti$wt=x2Wtd`cWeIxUFDd|{St1d>@-Ff4s zoKHpoEACf~fN_n86v~6IDxK-ON>Sb6Ml)(;tWVh~GO285Zxiw)=nyoav$rmb&f<+W z_exlDKP_cw?GlmCxYK05mJ_c7qX8SDlwxV}){Lri3!5yP^KW5X5-r*Odc9?tbU(=y2s0 zBB|u+eS6u;n*mrJz^Vjh1)JAEuhIMH{7BwPDD%iN!&6og%Q63~FHM#Qa#IMM9AFkw zYmdfQ2}VX)++s0v6HPwvhF6Tw3L{#!q-Fu5qsQF4J-{sF>S`i`4x>h4)5?0}xNJSf z0@B+o(?d2`i=)>as488%$-7xM%AT~xal9(t^Q@JNXN9DAHZYBGB5X1Dya~Z-=lcSuA?DySWym8!P?~1d2T=P8;!m}nhrs@GKcm`v&q4C~2S{{wS?p0<~k#mZiCzS?vD+n1x zWe(ZqrJ+_^cApXBVO%r%obd*ox^g{07noZku|k$;_{g~Cxj$10^u~6b0}7wZJOEle z^4SXFXQi!3o{lrAdK>v#o0I8-u*CyZvw)T8;0fKxcqxYeAiVvNsEn&-1Vm$5QiaB3 zz^wCW`TT0Ud>7zt-1;0<4ZSqbnmg;#D!5bmcU4M%0Cc+*>zlWu!2kdN07*qoM6N<$ Ef-Tf;h5!Hn diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_12.png b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_12.png deleted file mode 100644 index a05abd80f1bbcf8c4c31985df242787246776d05..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1779 zcmV0r&z%C3U6&)&Q402Ruv+OXzmGsctVBk~ZZKW%&qr8NNGen&b$i!kXm9wELxpG)xe@^*j-K@u_K6AD8uMQ~(w z`xb|h^t=&4Xxozsmq;FtK$FL706JcC#tfH$b^uxD@SG8qsp)wmf+7lLbEQ-hMA!D@ zY587rTFD_iW3Pnfyxv#|sJ^Kpu*zty>9-JB3oSGy<0ChHSGvkv22WR1_n+Ol%d5z( zjr87cUy?j2%1Gl-%6M1jGP_Pz#*udeJtyE}sllcBk`o{|nvRnk&C_ibD^g2T-ZCCQ z0^M+=>yTPCbUo`egbiH9WoQvkL#9qcQ0@R;A(e3gui~C9h@!@rMaGe=E=rPzo^81U zXt!1C9y~=Q<3tUHL=TTLu5ylS6tP;^?Af*!bbx3W=HsnG3-xfRde`K6UBYfssxpO0 zyhjG9A~3qBR8*}55+QaJc{?!ML3}vC3Obn1S?LV)FzELctsGBv-kx@mNl}O?09Do_ zUc9*{prs7!IiLSTLuPSvfK1Edktp99S}jDJz@yHq+Q2MD4;O(_QAM9=N`8YAX!=EJ zi0qotf_k8WIqaQ$C{#17#=4>7>0(bp_5h{g(Q3ss8JRcJBt0?&(Jkd&*L6wk7+)Yt zz#uH!Z-I420}>Hd0Vy)0Z+i9K+e0+HN<4a%%e0<<8%{ela;BCIO&-AWY|cg@ z2K+`NTX;fxn>o|)CxYG+CqvrlL#sw44Xi>m4cv;rC}#6n^yYXQ^2YMqhsgED@^RK# zaCS0!*(9Lczh$ebH-oBLk;obr&FhV|YL$#!1e(Dk9fS2O`^M6HzwLht*p-{nTPAfO z<9B|mk+GT%mRxTv)!AETC#;%{N3kOx2;quYksuN>pD>ord1ArK_1gN8Fe?{nH=qBk z$+gCgc6NMuW4$#_0!=^YxUCVOHH;@fl(%wr1u}Uac~(_7MdeUED0>P0T>wh-pyMn^ zxxE5b1z#Z-rN(Dc12tR~+!UY{Sk|~%<4J`bI!^;n0abFNb7+)9qg`t`$3dc`nF9D> zX=I0%w^#54*&K~Jl~s#Gp`Agdtz1=u^vjTp^v&AyF=mz((+C{8uGRKP@Wy(qUepOI zoGF5cLqSEL70%EFrMa0_4AE=0fb-JaSle?>KDMGpiU6s5AO}EhYejKmJ?G#Dk9vJ1 z@1k-M@RYVX>`Q4!bat8oR<%?dmS^&3(hbW*9!V}b-%CMUR}IniGC(hrngX+$EBgJg}b$+bY+FHgTLP7zJw$HJ}fQPH1!1>`tv-hF8EqlmDK)|y<;mGf8O z!vQoekAyk|)eM;?YefMY1&tu9jc#2~>GdNKfF3?V&P3O%$hB2YPK(qi`7^t=_4@!+ z1JI(WDgs!!|5RS3Iw3kBtf)74W%x(-@bRHgWK*l0fWD6CWXVMJo7s=l&1ATN6X{^! zXAB(QwOmzQ15hQc-z#KB##il)UYICL>dB3y5pRv3Arx6y`nduptKdp#T!k0qP=oSf z($fJ+Gar8&=o!Oe(@-bLKJ#>{0xHkrG*Ds)Bo}K zv5Udv$mlwtkxSC$sxZDb|HT(T3o|;;>Kd%YpNiEEWf@c|AZIRu=RE2sg190cKOnun zqtfNbtc4nU6%8z*au1ecywEo2LBG^ zCriHd9ChMIAzHr>)FmU?^ z5tW9L9AZWWf6Z${t=DE#64KrwYe+3MGPlZBy>~wXRKfG`N<+=)p^cB|fjVo?8It25(No$3 zV$Y9%5|s(n9=uzC$gIkF$e1S(SCs{)h1+`p87^ReGgn?Gz&Ez?7NU5H@dtK9z<;d< Vp()s7p}_zE002ovPDHLkV1hccKn4H+ diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_2.png b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_2.png deleted file mode 100644 index fc7e0364afbfa0c0750a59c1d546dc9211c42161..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1774 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 diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_4.png b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_4.png deleted file mode 100644 index bcc7f28a9eb40eda4f9d472409a93fab7f0d98ef..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1799 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 diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_6.png b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_6.png deleted file mode 100644 index c0b1a4fe24af639bddda8ca7f69d2bee8aed8689..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1789 zcmVgl@z3GiqHRtsLHW&}w z$K$%L*Y(Trdf_EM9*<3yoX5{;19s!}di^Q~M|fPnCh;4e0%<>@HZ&zpfwvW({7sU@O>WoM*ky$YX;3wDS>KIlx^P zxe6WG<22j=s4hmQ;2rSvc!bY$*8fTlz4U)wBnQYsZv2ju;RW66^)iFNbN-myY46=L zgBbuF2Ja36OV9Wk5wl_I3g>?Vmi7U5Tn8)(v&M9W`1bqULbR5D0gzd6#+i56NH!3w zLHaELV;+P{HGIlE|m$~vI$dv9!vYu%Pj?7G+rU?w=&fQ%Yi9s#_r0tu|%mE*n6 z(_-|tR@u7@`cz`i(JB91`l0F&M==j*xADc%8jKr(wg=1We)+PtI; zM9`;pjA?kO+fDt!$)p!L!f1Q)dEumY`;ANHu~yl9cI^&0Gn)QFWr%r9@?J zC&8TQ16Y1|N?{!ad4^?!05MwT#G?KHl2Nhk1enGcgJ;x!WfgR!q&sqk2$hn!yAOc6 zuaQpJ2#TVtauUAL$q0A2-ezq0_=%t)FnrOdwNr|3kVZQZ^&OCt*mgMj9*gza)A4o{ zUc~^=3M;6VHCk`9SmYceH=1kd^qdvd{A>_tRLXJ~0J~Fk6a+JUhda#G?{yv+ritXu zA9J?<51jNN+9Y{liRgMpRJj{HD~-bA!9nR@rc+&hw5 zZ$GWQ-Jzd&Jy}{l*tuvBq*@@<{b5Wlpsz{Iy#B(x0dl?3RjB-SRydL?i z!EC^F&DHCTGfPvME2;f+U8mE9C6pS|-l&YvY{hyY7lES{vtyKL&h2MI*wi~d|i z^u8u6GtHeb-y%R6=nQ1pCJkX)m*}v;;zOA}g7t5;4aYKo)`Q68Z2IfIgR^AW%Md$YU4PQX zAAJfVw%> z_YZpL3GZG=2hoW>-vOsfKeL3ruJ~sHbqREG5lDYEOR?8m<~h>8q6x%wZ6(H>k1GHX zFnt|Fcb`6u*0thYKa3s^m5t4E0FR?Ea>hHiX#~j=BiVj*@=-6myL4QMQ`ZL52qNYlB>4-2RpLIZ{Qet5>g0gF=l+D8@Pp?L`0z!}JlkSaI>)>d^XAwDU z#^x*{#*S=^o}*_UB(MWI16T%1!?gAO?GHq(Dod)e&AJs_3+z~RogL*{HI?hRUA_u4 zKo^)(qp`x4?DestX1Cy-((bIOqnfmwyPBhEjejXrNIC}1-DpUp{*(>it f52u`evP=30f`%HTZ&_~;00000NkvXXu0mjfZU|{H diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_7.png b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_7.png deleted file mode 100644 index 19c8181d0369a8edf52ef2124d403f5472c8e280..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1804 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 diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_9.png b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_9.png deleted file mode 100644 index 929a207ef58a63e946d60e4413e5ad96a75d7c0a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1816 zcmV+z2j}>SP)fcfU1=V^L^okrq$q%O>R9{YPT z16cv@xLKA`JtfONOn*xTYkYR@E+FL}hhqR$fmd!A8CykpXgo6?WAt|`-$E+~_|#3V zLWlRb8>#_hHzQr}4ruFWgilk}UnPf{`#%nn14J<&{EnNU8QpbV&H>04*8V+c=`X^PI)Dc$9Ae?JuZ*{v+nW!~bLWhkUH$1HBZFng z78u#L8o;aoBpg%G^A5lZGYm*!ARZFPr$5YMzFy5xkMWOeKs(md&z2nSxaXv*csen)CHl z6~8R&XUD-TplSq+HSY8X9++a%DtHQuDwHA1GagJ41ufw9Lw!F1l>$o!)@5KSep#~D zUI>!u+i07AXT8DQuoQ&eR}H&DQX?{i3PBo4id4xeAz?SNO4O>99Xx%CDJnrPb%#ID>}>*x=e)Jj(o&DdH)0SXcaiTgYx>$o*Ya@V0pjMUO7(-t^v$KS(QPp zMxKGRQct#dT(z&hR2(4Ca?Y51-BVb%3B8Bqct4e43u2NKYxY3Ln8S7JaicBio+1rFX2|5H#X!h251rZjV z#T#wzm9XSKujz@EetE2z^#b~_J?aurViZW`j3P2# zo3rvPwU5f!q|riW{SC zJQHK1huA>~E%`hn%$}haMzm~6ljGMPhoXa*M6BClwc=6~Rbtr9Ydiy!=`JJxID7U| zNIB5Bhb5$39KG&9mqBF|s*TAOwd8y2Qq5k%^Q@JNXBp#LA&5zrG>c`8B>Rj(CPY~- z907&PsB*N-$}5OfA@}tb0xSY1!+{mU<#R2pXyJ!hN5c+g9d*SsEQQYAAWFJ3MXFZ_ zkVb&gG^;*<+E=DptFYR5SX2xwg4c&ZQkl{vc@;v`-kmyn$gaa0roCFh>c@7GttJVkicB*#=efCbNBtTr^>TZgUiY3pip&s-~VPLcDZ(!g#7 zA%m#QA=|t()N0G_Gh#e!CD7tkU7^W>>jApJ+!~1$vP8p2#x>9VnM$BHw(A^F_*~`z z(BhG2D~O+!wjy~t&ZO#XSL4@b0p7-~=csDvrGeJmS(jG9oyxzfQu+f7>@m9|qN_##0000 Date: Wed, 15 Feb 2023 19:56:25 +0300 Subject: [PATCH 409/824] [FL-3098] Up toolchain to version 20 (#2397) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Up toolchain to 20 * Python reformat, add version info into fbtenv Co-authored-by: あく --- scripts/flipper/assets/dolphin.py | 2 -- scripts/flipper/utils/cdc.py | 1 + scripts/flipper/utils/templite.py | 2 ++ scripts/toolchain/fbtenv.cmd | 5 ++++- scripts/toolchain/fbtenv.sh | 11 ++++++++++- 5 files changed, 17 insertions(+), 4 deletions(-) diff --git a/scripts/flipper/assets/dolphin.py b/scripts/flipper/assets/dolphin.py index 096d31629f3..cbd1320b653 100644 --- a/scripts/flipper/assets/dolphin.py +++ b/scripts/flipper/assets/dolphin.py @@ -22,7 +22,6 @@ def _convert_image(source_filename: str): class DolphinBubbleAnimation: - FILE_TYPE = "Flipper Animation" FILE_VERSION = 1 @@ -243,7 +242,6 @@ def process(self): class DolphinManifest: - FILE_TYPE = "Flipper Animation Manifest" FILE_VERSION = 1 diff --git a/scripts/flipper/utils/cdc.py b/scripts/flipper/utils/cdc.py index 7047db2a686..7c735167029 100644 --- a/scripts/flipper/utils/cdc.py +++ b/scripts/flipper/utils/cdc.py @@ -1,5 +1,6 @@ import serial.tools.list_ports as list_ports + # Returns a valid port or None, if it cannot be found def resolve_port(logger, portname: str = "auto"): if portname != "auto": diff --git a/scripts/flipper/utils/templite.py b/scripts/flipper/utils/templite.py index 8af57d7f8f4..2d958bd77ab 100644 --- a/scripts/flipper/utils/templite.py +++ b/scripts/flipper/utils/templite.py @@ -173,12 +173,14 @@ def render(self, **namespace): """Renders the template according to the given namespace.""" stack = [] namespace["__file__"] = self.file + # add write method def write(*args): for value in args: stack.append(str(value)) namespace["write"] = write + # add include method def include(file): if not os.path.isabs(file): diff --git a/scripts/toolchain/fbtenv.cmd b/scripts/toolchain/fbtenv.cmd index 9fbd8fd9b60..5dff4b25e6e 100644 --- a/scripts/toolchain/fbtenv.cmd +++ b/scripts/toolchain/fbtenv.cmd @@ -13,7 +13,7 @@ if not ["%FBT_NOENV%"] == [""] ( exit /b 0 ) -set "FLIPPER_TOOLCHAIN_VERSION=19" +set "FLIPPER_TOOLCHAIN_VERSION=20" if ["%FBT_TOOLCHAIN_ROOT%"] == [""] ( set "FBT_TOOLCHAIN_ROOT=%FBT_ROOT%\toolchain\x86_64-windows" @@ -31,9 +31,12 @@ if not exist "%FBT_TOOLCHAIN_VERSION_FILE%" ( set /p REAL_TOOLCHAIN_VERSION=<"%FBT_TOOLCHAIN_VERSION_FILE%" if not "%REAL_TOOLCHAIN_VERSION%" == "%FLIPPER_TOOLCHAIN_VERSION%" ( + echo FBT: starting toolchain upgrade process.. powershell -ExecutionPolicy Bypass -File "%FBT_ROOT%\scripts\toolchain\windows-toolchain-download.ps1" %flipper_toolchain_version% "%FBT_TOOLCHAIN_ROOT%" + set /p REAL_TOOLCHAIN_VERSION=<"%FBT_TOOLCHAIN_VERSION_FILE%" ) +echo FBT: using toolchain version %REAL_TOOLCHAIN_VERSION% set "HOME=%USERPROFILE%" set "PYTHONHOME=%FBT_TOOLCHAIN_ROOT%\python" set "PYTHONPATH=" diff --git a/scripts/toolchain/fbtenv.sh b/scripts/toolchain/fbtenv.sh index dd5484aa9ab..bedb3450a0a 100755 --- a/scripts/toolchain/fbtenv.sh +++ b/scripts/toolchain/fbtenv.sh @@ -5,7 +5,7 @@ # public variables DEFAULT_SCRIPT_PATH="$(pwd -P)"; SCRIPT_PATH="${SCRIPT_PATH:-$DEFAULT_SCRIPT_PATH}"; -FBT_TOOLCHAIN_VERSION="${FBT_TOOLCHAIN_VERSION:-"19"}"; +FBT_TOOLCHAIN_VERSION="${FBT_TOOLCHAIN_VERSION:-"20"}"; FBT_TOOLCHAIN_PATH="${FBT_TOOLCHAIN_PATH:-$SCRIPT_PATH}"; fbtenv_show_usage() @@ -35,6 +35,7 @@ fbtenv_restore_env() PATH="$(echo "$PATH" | /usr/bin/sed "s/$TOOLCHAIN_ARCH_DIR_SED\/bin://g")"; PATH="$(echo "$PATH" | /usr/bin/sed "s/$TOOLCHAIN_ARCH_DIR_SED\/protobuf\/bin://g")"; PATH="$(echo "$PATH" | /usr/bin/sed "s/$TOOLCHAIN_ARCH_DIR_SED\/openocd\/bin://g")"; + PATH="$(echo "$PATH" | /usr/bin/sed "s/$TOOLCHAIN_ARCH_DIR_SED\/openssl\/bin://g")"; if [ -n "${PS1:-""}" ]; then PS1="$(echo "$PS1" | sed 's/\[fbt\]//g')"; elif [ -n "${PROMPT:-""}" ]; then @@ -248,6 +249,7 @@ fbtenv_check_download_toolchain() elif [ ! -f "$TOOLCHAIN_ARCH_DIR/VERSION" ]; then fbtenv_download_toolchain || return 1; elif [ "$(cat "$TOOLCHAIN_ARCH_DIR/VERSION")" -ne "$FBT_TOOLCHAIN_VERSION" ]; then + echo "FBT: starting toolchain upgrade process.." fbtenv_download_toolchain || return 1; fi return 0; @@ -269,6 +271,11 @@ fbtenv_download_toolchain() return 0; } +fbtenv_print_version() +{ + echo "FBT: using toolchain version $(cat "$TOOLCHAIN_ARCH_DIR/VERSION")"; +} + fbtenv_main() { fbtenv_check_sourced || return 1; @@ -281,10 +288,12 @@ fbtenv_main() fbtenv_check_script_path || return 1; fbtenv_check_download_toolchain || return 1; fbtenv_set_shell_prompt; + fbtenv_print_version; PATH="$TOOLCHAIN_ARCH_DIR/python/bin:$PATH"; PATH="$TOOLCHAIN_ARCH_DIR/bin:$PATH"; PATH="$TOOLCHAIN_ARCH_DIR/protobuf/bin:$PATH"; PATH="$TOOLCHAIN_ARCH_DIR/openocd/bin:$PATH"; + PATH="$TOOLCHAIN_ARCH_DIR/openssl/bin:$PATH"; SAVED_PYTHONNOUSERSITE="${PYTHONNOUSERSITE:-""}"; SAVED_PYTHONPATH="${PYTHONPATH:-""}"; From 1e983612997af682e935ebad3bf0a7e9eb5820dc Mon Sep 17 00:00:00 2001 From: Ari Bytyqi <101530102+Z3BRO@users.noreply.github.com> Date: Fri, 17 Feb 2023 02:17:57 -0600 Subject: [PATCH 410/824] Fixed first start animation typo (#2384) * fixed first start animation typo * updated pixels. (bottom left corner was off by two pixels) Co-authored-by: jbohack --- assets/slideshow/first_start/frame_02.png | Bin 562 -> 656 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/assets/slideshow/first_start/frame_02.png b/assets/slideshow/first_start/frame_02.png index dc1080abaab43b40f9214c53e444839f9f3c9ffb..adff6af6672bc6f13ad0be1c0f7d19db4955f589 100644 GIT binary patch delta 643 zcmV-}0(||l1ds)g7=H)@0002x2#-ww0004VQb$4nuFf3k00006P)t-s|Ns900033O z(|!N|0v<_3K~!jg?Usvjgdhw>$^ZX!J1dVoZW2)3PG`upu7Yw8m|Q94#EBCpPMkRL zUxPM8HfpH9WfE3Bx_$wG#06fF2*6vzLUVv@bXUo3_W+915q}RJtu^i%0G}Cu{yD(a zRwp*joUy}v!<}7?F1h+w6Ng%xIJiS6xc~`VfZ8jxue5LhxB!hol-Lsh@c;mbrSKA{ z96$gn2V4MN3sC5u_w+6loi|c zh+yjh?R@sAKbpSdNL~|U0K;)Qz`_c6*1rHCx0^Hi+SzXacsa0I1@?BEMa~L53h`|> z9oqcmT{{87?DtwXI^m@LYXGzQCJDC01Ar|Azr*|h{u~6R)=RWQ0000EWmrjOO-%qQ d00008000000002eQcz2XilNogTwuu7~B5w5vn;e3F20e`W(DTKpH#XNu(EBFSM zQ(E~93pSQ1E!4gLH#-~GJi|?z>@VMb^XHoh{x4aotd<4hvLLj=4+hxc2hagBpg=5O z(fjT}Swz-D!g9hg*%AI8B|kPF2<_@ceLDKQpRe<4mreQ}lKuRN&{qkP8nnrfGC@~* zjM(FzkTJohH-Dd_mBMBc4INbjdvHs%qefDOLb3>%K#C;Y319~SjEBBL34L()FxmKB zFf_)kh}%x{_37>IIU$;L72za*3NR?r_(dvo4bF9*p?}@oPc}Sk1HBn{v3`yO<@gq~ zLJgeIAJ-f*hr!cmKnm3mlR8S()jG$HFIhfWY-x>MNGyj{1sW)uR=FJ*r(a)QP}+KndE%yd>|C%9KqbsgZ!@ n!-X*daUS@46g7nZOX>arU#^{;{__u)00000NkvXXu0mjfq&fEl From 32b74b968e8bb5730d95d0b3fd18b9cf90dca5b6 Mon Sep 17 00:00:00 2001 From: Max Andreev Date: Fri, 17 Feb 2023 12:16:53 +0300 Subject: [PATCH 411/824] Fix openssl cert path in fbtenv (#2408) * Temp fix openssl cert path * Moving fix from CI to fbtenv --- scripts/toolchain/fbtenv.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/toolchain/fbtenv.sh b/scripts/toolchain/fbtenv.sh index bedb3450a0a..2d4d1724496 100755 --- a/scripts/toolchain/fbtenv.sh +++ b/scripts/toolchain/fbtenv.sh @@ -289,6 +289,9 @@ fbtenv_main() fbtenv_check_download_toolchain || return 1; fbtenv_set_shell_prompt; fbtenv_print_version; + if [ "$SYS_TYPE" = "Linux" ]; then + SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt + fi PATH="$TOOLCHAIN_ARCH_DIR/python/bin:$PATH"; PATH="$TOOLCHAIN_ARCH_DIR/bin:$PATH"; PATH="$TOOLCHAIN_ARCH_DIR/protobuf/bin:$PATH"; From 487d03eca4093d6e699c07f192531dd8689b0bf7 Mon Sep 17 00:00:00 2001 From: Max Andreev Date: Fri, 17 Feb 2023 12:59:08 +0300 Subject: [PATCH 412/824] Fix openssl path again (#2409) * Temp fix openssl cert path * Moving fix from CI to fbtenv * Fix openssl again --- scripts/toolchain/fbtenv.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/toolchain/fbtenv.sh b/scripts/toolchain/fbtenv.sh index 2d4d1724496..8933bc48d10 100755 --- a/scripts/toolchain/fbtenv.sh +++ b/scripts/toolchain/fbtenv.sh @@ -290,7 +290,7 @@ fbtenv_main() fbtenv_set_shell_prompt; fbtenv_print_version; if [ "$SYS_TYPE" = "Linux" ]; then - SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt + export SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt fi PATH="$TOOLCHAIN_ARCH_DIR/python/bin:$PATH"; PATH="$TOOLCHAIN_ARCH_DIR/bin:$PATH"; From 009c9b1b71bd8e7bd68f5ad9b955ff924dd5a913 Mon Sep 17 00:00:00 2001 From: Mathie <62908057+MathieDev@users.noreply.github.com> Date: Fri, 17 Feb 2023 06:43:06 -0500 Subject: [PATCH 413/824] Update nfc_cli.c (#2402) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Modified the f to a capital F, looks cleaner. Co-authored-by: あく --- applications/main/nfc/nfc_cli.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/main/nfc/nfc_cli.c b/applications/main/nfc/nfc_cli.c index a6475ca681d..23335e2993a 100644 --- a/applications/main/nfc/nfc_cli.c +++ b/applications/main/nfc/nfc_cli.c @@ -32,7 +32,7 @@ static void nfc_cli_detect(Cli* cli, FuriString* args) { while(!cmd_exit) { cmd_exit |= cli_cmd_interrupt_received(cli); if(furi_hal_nfc_detect(&dev_data, 400)) { - printf("found: %s ", nfc_get_dev_type(dev_data.type)); + printf("Found: %s ", nfc_get_dev_type(dev_data.type)); printf("UID length: %d, UID:", dev_data.uid_len); for(size_t i = 0; i < dev_data.uid_len; i++) { printf("%02X", dev_data.uid[i]); From 335f8b9aff670b033072e78ca477d4057ae54284 Mon Sep 17 00:00:00 2001 From: hedger Date: Fri, 17 Feb 2023 16:22:08 +0400 Subject: [PATCH 414/824] fbt: FBT_QUIET option; docs on environment (#2403) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fbt: added FBT_QUIET to suppress toolchain version output; docs: added info on fbt environment * docs: typo fixes * fbt: fix for FBT_QUIET handling on *nix * Add FBT_VERBOSE flag * Add export * Fix export * docs: updates for FBT_VERBOSE Co-authored-by: DrunkBatya Co-authored-by: あく --- .vscode/example/launch.json | 5 +++-- documentation/fbt.md | 28 ++++++++++++++++++++++------ fbt | 11 ++++++++--- fbt.cmd | 7 ++++++- scripts/toolchain/fbtenv.cmd | 5 ++++- scripts/toolchain/fbtenv.sh | 28 ++++++++++++++++------------ 6 files changed, 59 insertions(+), 25 deletions(-) diff --git a/.vscode/example/launch.json b/.vscode/example/launch.json index 5c46d397919..f7a9f8269d3 100644 --- a/.vscode/example/launch.json +++ b/.vscode/example/launch.json @@ -11,9 +11,10 @@ "args": { "useSingleResult": true, "env": { - "PATH": "${workspaceFolder};${env:PATH}" + "PATH": "${workspaceFolder};${env:PATH}", + "FBT_QUIET": 1 }, - "command": "./fbt get_blackmagic", + "command": "fbt get_blackmagic", "description": "Get Blackmagic device", } } diff --git a/documentation/fbt.md b/documentation/fbt.md index 5166d0ab719..65c3ee68251 100644 --- a/documentation/fbt.md +++ b/documentation/fbt.md @@ -3,17 +3,27 @@ FBT is the entry point for firmware-related commands and utilities. It is invoked by `./fbt` in the firmware project root directory. Internally, it is a wrapper around [scons](https://scons.org/) build system. -## Requirements +## Environment -Install Python packages required by assets build scripts: `pip3 install -r scripts/requirements.txt` +To use `fbt`, you only need `git` installed in your system. -## NB +`fbt` by default downloads and unpacks a pre-built toolchain, and then modifies environment variables for itself to use it. It does not contaminate your global system's path with the toolchain. + > However, if you wish to use tools supplied with the toolchain outside `fbt`, you can open an *fbt shell*, with properly configured environment. + > - On Windows, simply run `scripts/toochain/fbtenv.cmd`. + > - On Linux & MacOS, run `source scripts/toochain/fbtenv.sh` in a new shell. + + If your system is not supported by pre-built toolchain variants or you want to use custom versions of dependencies, you can `set FBT_NOENV=1`. `fbt` will skip toolchain & environment configuration and will expect all tools to be available on your system's `PATH`. *(this option is not available on Windows)* + + If `FBT_TOOLCHAIN_PATH` variable is set, `fbt` will use that directory to unpack toolchain into. By default, it downloads toolchain into `toolchain` subdirectory repo's root. -- `fbt` constructs all referenced environments and their targets' dependency trees on startup. So, to keep startup time as low as possible, we're hiding the construction of certain targets behind command-line options. -- `fbt` always performs `git submodule update --init` on start, unless you set `FBT_NO_SYNC=1` in the environment: +If you want to enable extra debug output for `fbt` and toolchain management scripts, you can `set FBT_VERBOSE=1`. + +`fbt` always performs `git submodule update --init` on start, unless you set `FBT_NO_SYNC=1` in the environment: - On Windows, it's `set "FBT_NO_SYNC=1"` in the shell you're running `fbt` from - On \*nix, it's `$ FBT_NO_SYNC=1 ./fbt ...` -- `fbt` builds updater & firmware in separate subdirectories in `build`, and their names depend on optimization settings (`COMPACT` & `DEBUG` options). However, for ease of integration with IDEs, the latest built variant's directory is always linked as `built/latest`. Additionally, `compile_commands.json` is generated in that folder (used for code completion support in IDE). + + > There are more variables controlling basic `fbt` behavior. See `fbt` & `fbtenv` scripts' sources for details. + ## Invoking FBT @@ -23,6 +33,12 @@ To build with FBT, call it and specify configuration options & targets to build. To run cleanup (think of `make clean`) for specified targets, add the `-c` option. +## Build directories + +`fbt` builds updater & firmware in separate subdirectories in `build`, and their names depend on optimization settings (`COMPACT` & `DEBUG` options). However, for ease of integration with IDEs, the latest built variant's directory is always linked as `built/latest`. Additionally, `compile_commands.json` is generated in that folder (it is used for code completion support in IDEs). + +`build/latest` symlink & compilation database are only updated upon *firmware build targets* - that is, when you're re-building the firmware itself. Running other tasks, like firmware flashing or building update bundles *for a different debug/release configuration or hardware target*, does not update `built/latest` dir to point to that configuration. + ## VSCode integration `fbt` includes basic development environment configuration for VS Code. Run `./fbt vscode_dist` to deploy it. That will copy the initial environment configuration to the `.vscode` folder. After that, you can use that configuration by starting VS Code and choosing the firmware root folder in the "File > Open Folder" menu. diff --git a/fbt b/fbt index e576f37acf3..f80e802b627 100755 --- a/fbt +++ b/fbt @@ -6,16 +6,21 @@ set -eu; # private variables SCRIPT_PATH="$(cd "$(dirname "$0")" && pwd -P)"; -SCONS_DEFAULT_FLAGS="-Q --warn=target-not-built"; -SCONS_EP="python3 -m SCons" +SCONS_DEFAULT_FLAGS="--warn=target-not-built"; +SCONS_EP="python3 -m SCons"; # public variables FBT_NOENV="${FBT_NOENV:-""}"; FBT_NO_SYNC="${FBT_NO_SYNC:-""}"; FBT_TOOLCHAIN_PATH="${FBT_TOOLCHAIN_PATH:-$SCRIPT_PATH}"; +FBT_VERBOSE="${FBT_VERBOSE:-""}"; if [ -z "$FBT_NOENV" ]; then - . "$SCRIPT_PATH/scripts/toolchain/fbtenv.sh"; + FBT_VERBOSE="$FBT_VERBOSE" . "$SCRIPT_PATH/scripts/toolchain/fbtenv.sh"; +fi + +if [ -z "$FBT_VERBOSE" ]; then + SCONS_DEFAULT_FLAGS="$SCONS_DEFAULT_FLAGS -Q"; fi if [ -z "$FBT_NO_SYNC" ]; then diff --git a/fbt.cmd b/fbt.cmd index 197f2359c76..92c734860a0 100644 --- a/fbt.cmd +++ b/fbt.cmd @@ -12,5 +12,10 @@ if [%FBT_NO_SYNC%] == [] ( ) ) -set "SCONS_DEFAULT_FLAGS=-Q --warn=target-not-built" +set "SCONS_DEFAULT_FLAGS=--warn=target-not-built" + +if not defined FBT_VERBOSE ( + set "SCONS_DEFAULT_FLAGS=%SCONS_DEFAULT_FLAGS% -Q" +) + %SCONS_EP% %SCONS_DEFAULT_FLAGS% %* diff --git a/scripts/toolchain/fbtenv.cmd b/scripts/toolchain/fbtenv.cmd index 5dff4b25e6e..3f69de00b02 100644 --- a/scripts/toolchain/fbtenv.cmd +++ b/scripts/toolchain/fbtenv.cmd @@ -36,7 +36,10 @@ if not "%REAL_TOOLCHAIN_VERSION%" == "%FLIPPER_TOOLCHAIN_VERSION%" ( set /p REAL_TOOLCHAIN_VERSION=<"%FBT_TOOLCHAIN_VERSION_FILE%" ) -echo FBT: using toolchain version %REAL_TOOLCHAIN_VERSION% +if defined FBT_VERBOSE ( + echo FBT: using toolchain version %REAL_TOOLCHAIN_VERSION% +) + set "HOME=%USERPROFILE%" set "PYTHONHOME=%FBT_TOOLCHAIN_ROOT%\python" set "PYTHONPATH=" diff --git a/scripts/toolchain/fbtenv.sh b/scripts/toolchain/fbtenv.sh index 8933bc48d10..ddd27c3707b 100755 --- a/scripts/toolchain/fbtenv.sh +++ b/scripts/toolchain/fbtenv.sh @@ -7,13 +7,14 @@ DEFAULT_SCRIPT_PATH="$(pwd -P)"; SCRIPT_PATH="${SCRIPT_PATH:-$DEFAULT_SCRIPT_PATH}"; FBT_TOOLCHAIN_VERSION="${FBT_TOOLCHAIN_VERSION:-"20"}"; FBT_TOOLCHAIN_PATH="${FBT_TOOLCHAIN_PATH:-$SCRIPT_PATH}"; +FBT_VERBOSE="${FBT_VERBOSE:-""}"; fbtenv_show_usage() { echo "Running this script manually is wrong, please source it"; echo "Example:"; printf "\tsource scripts/toolchain/fbtenv.sh\n"; - echo "To restore your environment source fbtenv.sh with '--restore'." + echo "To restore your environment, source fbtenv.sh with '--restore'." echo "Example:"; printf "\tsource scripts/toolchain/fbtenv.sh --restore\n"; } @@ -42,9 +43,9 @@ fbtenv_restore_env() PROMPT="$(echo "$PROMPT" | sed 's/\[fbt\]//g')"; fi - PYTHONNOUSERSITE="$SAVED_PYTHONNOUSERSITE"; - PYTHONPATH="$SAVED_PYTHONPATH"; - PYTHONHOME="$SAVED_PYTHONHOME"; + export PYTHONNOUSERSITE="$SAVED_PYTHONNOUSERSITE"; + export PYTHONPATH="$SAVED_PYTHONPATH"; + export PYTHONHOME="$SAVED_PYTHONHOME"; unset SAVED_PYTHONNOUSERSITE; unset SAVED_PYTHONPATH; @@ -122,7 +123,7 @@ fbtenv_get_kernel_type() TOOLCHAIN_ARCH_DIR="$FBT_TOOLCHAIN_PATH/toolchain/x86_64-linux"; TOOLCHAIN_URL="https://update.flipperzero.one/builds/toolchain/gcc-arm-none-eabi-10.3-x86_64-linux-flipper-$FBT_TOOLCHAIN_VERSION.tar.gz"; elif echo "$SYS_TYPE" | grep -q "MINGW"; then - echo "In MinGW shell use \"[u]fbt.cmd\" instead of \"[u]fbt\""; + echo "In MinGW shell, use \"[u]fbt.cmd\" instead of \"[u]fbt\""; return 1; else echo "Your system configuration is not supported. Sorry.. Please report us your configuration."; @@ -273,7 +274,9 @@ fbtenv_download_toolchain() fbtenv_print_version() { - echo "FBT: using toolchain version $(cat "$TOOLCHAIN_ARCH_DIR/VERSION")"; + if [ -n "$FBT_VERBOSE" ]; then + echo "FBT: using toolchain version $(cat "$TOOLCHAIN_ARCH_DIR/VERSION")"; + fi } fbtenv_main() @@ -297,14 +300,15 @@ fbtenv_main() PATH="$TOOLCHAIN_ARCH_DIR/protobuf/bin:$PATH"; PATH="$TOOLCHAIN_ARCH_DIR/openocd/bin:$PATH"; PATH="$TOOLCHAIN_ARCH_DIR/openssl/bin:$PATH"; + export PATH; - SAVED_PYTHONNOUSERSITE="${PYTHONNOUSERSITE:-""}"; - SAVED_PYTHONPATH="${PYTHONPATH:-""}"; - SAVED_PYTHONHOME="${PYTHONHOME:-""}"; + export SAVED_PYTHONNOUSERSITE="${PYTHONNOUSERSITE:-""}"; + export SAVED_PYTHONPATH="${PYTHONPATH:-""}"; + export SAVED_PYTHONHOME="${PYTHONHOME:-""}"; - PYTHONNOUSERSITE=1; - PYTHONPATH=; - PYTHONHOME=; + export PYTHONNOUSERSITE=1; + export PYTHONPATH=; + export PYTHONHOME=; } fbtenv_main "${1:-""}"; From c7fbc8323b93337c94dc1e69a3ce755224e2621c Mon Sep 17 00:00:00 2001 From: Max Andreev Date: Fri, 17 Feb 2023 19:06:48 +0300 Subject: [PATCH 415/824] Toolchain 20 rollback (#2410) * Toolchain rollback * Remove extra code --- scripts/toolchain/fbtenv.cmd | 2 +- scripts/toolchain/fbtenv.sh | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/scripts/toolchain/fbtenv.cmd b/scripts/toolchain/fbtenv.cmd index 3f69de00b02..dce5f37c4da 100644 --- a/scripts/toolchain/fbtenv.cmd +++ b/scripts/toolchain/fbtenv.cmd @@ -13,7 +13,7 @@ if not ["%FBT_NOENV%"] == [""] ( exit /b 0 ) -set "FLIPPER_TOOLCHAIN_VERSION=20" +set "FLIPPER_TOOLCHAIN_VERSION=19" if ["%FBT_TOOLCHAIN_ROOT%"] == [""] ( set "FBT_TOOLCHAIN_ROOT=%FBT_ROOT%\toolchain\x86_64-windows" diff --git a/scripts/toolchain/fbtenv.sh b/scripts/toolchain/fbtenv.sh index ddd27c3707b..c68673b7beb 100755 --- a/scripts/toolchain/fbtenv.sh +++ b/scripts/toolchain/fbtenv.sh @@ -5,7 +5,7 @@ # public variables DEFAULT_SCRIPT_PATH="$(pwd -P)"; SCRIPT_PATH="${SCRIPT_PATH:-$DEFAULT_SCRIPT_PATH}"; -FBT_TOOLCHAIN_VERSION="${FBT_TOOLCHAIN_VERSION:-"20"}"; +FBT_TOOLCHAIN_VERSION="${FBT_TOOLCHAIN_VERSION:-"19"}"; FBT_TOOLCHAIN_PATH="${FBT_TOOLCHAIN_PATH:-$SCRIPT_PATH}"; FBT_VERBOSE="${FBT_VERBOSE:-""}"; @@ -292,9 +292,6 @@ fbtenv_main() fbtenv_check_download_toolchain || return 1; fbtenv_set_shell_prompt; fbtenv_print_version; - if [ "$SYS_TYPE" = "Linux" ]; then - export SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt - fi PATH="$TOOLCHAIN_ARCH_DIR/python/bin:$PATH"; PATH="$TOOLCHAIN_ARCH_DIR/bin:$PATH"; PATH="$TOOLCHAIN_ARCH_DIR/protobuf/bin:$PATH"; From 78afaab7e8b7beea2b3789c175d613bd0089ce4d Mon Sep 17 00:00:00 2001 From: ComputerCarsten <50232606+ComputerCarsten@users.noreply.github.com> Date: Mon, 20 Feb 2023 10:24:51 +0100 Subject: [PATCH 416/824] IR Universal Audio Remote: Add Grundig CMS 5000 (#2414) Add Grundig CMS 5000 to Infrared Universal Audio Remote. The 'Play' button doubles as 'Pause' button. The 'Pause' button is unused. Issue: 'Prev' button rewinds to start of title, to skip to previous title two consecutive button presses in a short time are required, however the timing is not satisfied. --- assets/resources/infrared/assets/audio.ir | 43 +++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/assets/resources/infrared/assets/audio.ir b/assets/resources/infrared/assets/audio.ir index bcf035df137..825d1bc3ef1 100644 --- a/assets/resources/infrared/assets/audio.ir +++ b/assets/resources/infrared/assets/audio.ir @@ -285,3 +285,46 @@ type: parsed protocol: NECext address: 10 E7 00 00 command: 41 BE 00 00 +# +# Model: Grundig CMS 5000 +name: Power +type: parsed +protocol: NECext +address: 30 FC 00 00 +command: 10 EF 00 00 +# Also Pause +name: Play +type: parsed +protocol: NECext +address: 30 FC 00 00 +command: 02 FD 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 30 FC 00 00 +command: 0D F2 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 30 FC 00 00 +command: 17 E8 00 00 +# +name: Next +type: parsed +protocol: NECext +address: 30 FC 00 00 +command: 13 EC 00 00 +# +name: Prev +type: parsed +protocol: NECext +address: 30 FC 00 00 +command: 11 EE 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 30 FC 00 00 +command: 0C F3 00 00 From 3de6ae07b7bfdc6d75f82d4c688eac00db313318 Mon Sep 17 00:00:00 2001 From: Max Andreev Date: Mon, 20 Feb 2023 15:21:29 +0300 Subject: [PATCH 417/824] [FL-2974] Up toolchain version to 21 (#2416) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- scripts/toolchain/fbtenv.cmd | 2 +- scripts/toolchain/fbtenv.sh | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/scripts/toolchain/fbtenv.cmd b/scripts/toolchain/fbtenv.cmd index dce5f37c4da..8587f6d0e0a 100644 --- a/scripts/toolchain/fbtenv.cmd +++ b/scripts/toolchain/fbtenv.cmd @@ -13,7 +13,7 @@ if not ["%FBT_NOENV%"] == [""] ( exit /b 0 ) -set "FLIPPER_TOOLCHAIN_VERSION=19" +set "FLIPPER_TOOLCHAIN_VERSION=21" if ["%FBT_TOOLCHAIN_ROOT%"] == [""] ( set "FBT_TOOLCHAIN_ROOT=%FBT_ROOT%\toolchain\x86_64-windows" diff --git a/scripts/toolchain/fbtenv.sh b/scripts/toolchain/fbtenv.sh index c68673b7beb..8f05c23ca82 100755 --- a/scripts/toolchain/fbtenv.sh +++ b/scripts/toolchain/fbtenv.sh @@ -5,7 +5,7 @@ # public variables DEFAULT_SCRIPT_PATH="$(pwd -P)"; SCRIPT_PATH="${SCRIPT_PATH:-$DEFAULT_SCRIPT_PATH}"; -FBT_TOOLCHAIN_VERSION="${FBT_TOOLCHAIN_VERSION:-"19"}"; +FBT_TOOLCHAIN_VERSION="${FBT_TOOLCHAIN_VERSION:-"21"}"; FBT_TOOLCHAIN_PATH="${FBT_TOOLCHAIN_PATH:-$SCRIPT_PATH}"; FBT_VERBOSE="${FBT_VERBOSE:-""}"; @@ -43,10 +43,19 @@ fbtenv_restore_env() PROMPT="$(echo "$PROMPT" | sed 's/\[fbt\]//g')"; fi + if [ -n "$SAVED_SSL_CERT_FILE" ]; then + export SSL_CERT_FILE="$SAVED_SSL_CERT_FILE"; + export REQUESTS_CA_BUNDLE="$SAVED_REQUESTS_CA_BUNDLE"; + else + unset SSL_CERT_FILE; + unset REQUESTS_CA_BUNDLE; + fi export PYTHONNOUSERSITE="$SAVED_PYTHONNOUSERSITE"; export PYTHONPATH="$SAVED_PYTHONPATH"; export PYTHONHOME="$SAVED_PYTHONHOME"; + unset SAVED_SSL_CERT_FILE; + unset SAVED_REQUESTS_CA_BUNDLE; unset SAVED_PYTHONNOUSERSITE; unset SAVED_PYTHONPATH; unset SAVED_PYTHONHOME; @@ -299,10 +308,14 @@ fbtenv_main() PATH="$TOOLCHAIN_ARCH_DIR/openssl/bin:$PATH"; export PATH; + export SAVED_SSL_CERT_FILE="${SSL_CERT_FILE:-""}"; + export SAVED_REQUESTS_CA_BUNDLE="${REQUESTS_CA_BUNDLE:-""}"; export SAVED_PYTHONNOUSERSITE="${PYTHONNOUSERSITE:-""}"; export SAVED_PYTHONPATH="${PYTHONPATH:-""}"; export SAVED_PYTHONHOME="${PYTHONHOME:-""}"; + export SSL_CERT_FILE="$TOOLCHAIN_ARCH_DIR/python/lib/python3.11/site-packages/certifi/cacert.pem"; + export REQUESTS_CA_BUNDLE="$SSL_CERT_FILE"; export PYTHONNOUSERSITE=1; export PYTHONPATH=; export PYTHONHOME=; From 738e0df4f460733ec9ae46c3d90a56372fb81249 Mon Sep 17 00:00:00 2001 From: Igor Danilov <59930161+polarikus@users.noreply.github.com> Date: Mon, 20 Feb 2023 15:52:15 +0300 Subject: [PATCH 418/824] Delete rwfiletest.bin on exit SDcard benchmark (#2415) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update storage_settings_scene_benchmark.c: delete rwfiletest.bin on exit SDcard brencmark * Settings: cleanup SD Benchmark temp file only if test successful Co-authored-by: あく --- .../storage_settings/scenes/storage_settings_scene_benchmark.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/applications/settings/storage_settings/scenes/storage_settings_scene_benchmark.c b/applications/settings/storage_settings/scenes/storage_settings_scene_benchmark.c index 71a3df78b15..8359c00be3d 100644 --- a/applications/settings/storage_settings/scenes/storage_settings_scene_benchmark.c +++ b/applications/settings/storage_settings/scenes/storage_settings_scene_benchmark.c @@ -103,6 +103,9 @@ static void storage_settings_scene_benchmark(StorageSettings* app) { break; furi_string_cat_printf(app->text_string, "R %luK", bench_r_speed[i]); + + storage_common_remove(app->fs_api, BENCH_FILE); + dialog_ex_set_text( dialog_ex, furi_string_get_cstr(app->text_string), 0, 32, AlignLeft, AlignCenter); } From b15c4afea11cf02068216057f6fa1f15ded7fc2d Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Mon, 20 Feb 2023 16:44:03 +0200 Subject: [PATCH 419/824] [FL-3122] Re-init NFC when starting the worker (#2399) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Re-init NFC when starting the worker * FuriHal: cleanup nfc init/deinit sequence * FuriHal: a little bit more defensive nfc init Co-authored-by: あく --- firmware/targets/f7/api_symbols.csv | 3 ++- firmware/targets/f7/furi_hal/furi_hal_nfc.c | 20 ++++++++++++++++++-- firmware/targets/f7/furi_hal/furi_hal_nfc.h | 4 ++++ lib/nfc/nfc_worker.c | 2 ++ 4 files changed, 26 insertions(+), 3 deletions(-) diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 33c443ae056..e320fc92bab 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,14.0,, +Version,+,14.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -1171,6 +1171,7 @@ Function,+,furi_hal_mpu_protect_disable,void,FuriHalMpuRegion Function,+,furi_hal_mpu_protect_no_access,void,"FuriHalMpuRegion, uint32_t, FuriHalMPURegionSize" Function,+,furi_hal_mpu_protect_read_only,void,"FuriHalMpuRegion, uint32_t, FuriHalMPURegionSize" Function,+,furi_hal_nfc_activate_nfca,_Bool,"uint32_t, uint32_t*" +Function,-,furi_hal_nfc_deinit,void, Function,+,furi_hal_nfc_detect,_Bool,"FuriHalNfcDevData*, uint32_t" Function,+,furi_hal_nfc_emulate_nfca,_Bool,"uint8_t*, uint8_t, uint8_t*, uint8_t, FuriHalNfcEmulateCallback, void*, uint32_t" Function,+,furi_hal_nfc_exit_sleep,void, diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc.c b/firmware/targets/f7/furi_hal/furi_hal_nfc.c index ce81fd0585d..8910d887bcb 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_nfc.c +++ b/firmware/targets/f7/furi_hal/furi_hal_nfc.c @@ -24,13 +24,29 @@ FuriEventFlag* event = NULL; #define FURI_HAL_NFC_UID_INCOMPLETE (0x04) void furi_hal_nfc_init() { + furi_assert(!event); + event = furi_event_flag_alloc(); + ReturnCode ret = rfalNfcInitialize(); if(ret == ERR_NONE) { furi_hal_nfc_start_sleep(); - event = furi_event_flag_alloc(); FURI_LOG_I(TAG, "Init OK"); } else { - FURI_LOG_W(TAG, "Initialization failed, RFAL returned: %d", ret); + FURI_LOG_W(TAG, "Init Failed, RFAL returned: %d", ret); + } +} + +void furi_hal_nfc_deinit() { + ReturnCode ret = rfalDeinitialize(); + if(ret == ERR_NONE) { + FURI_LOG_I(TAG, "Deinit OK"); + } else { + FURI_LOG_W(TAG, "Deinit Failed, RFAL returned: %d", ret); + } + + if(event) { + furi_event_flag_free(event); + event = NULL; } } diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc.h b/firmware/targets/f7/furi_hal/furi_hal_nfc.h index d3f6de60284..dc3f873f346 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_nfc.h +++ b/firmware/targets/f7/furi_hal/furi_hal_nfc.h @@ -101,6 +101,10 @@ typedef struct { */ void furi_hal_nfc_init(); +/** Deinit nfc + */ +void furi_hal_nfc_deinit(); + /** Check if nfc worker is busy * * @return true if busy diff --git a/lib/nfc/nfc_worker.c b/lib/nfc/nfc_worker.c index a652e088a0a..54bdbb24caa 100644 --- a/lib/nfc/nfc_worker.c +++ b/lib/nfc/nfc_worker.c @@ -56,6 +56,8 @@ void nfc_worker_start( while(furi_hal_nfc_is_busy()) { furi_delay_ms(10); } + furi_hal_nfc_deinit(); + furi_hal_nfc_init(); nfc_worker->callback = callback; nfc_worker->context = context; From 0a3ff7f85ae653629957dfdd970a6fd3310ad14e Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 20 Feb 2023 18:19:53 +0300 Subject: [PATCH 420/824] Show RSSI in Weather Station app (#2395) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Show RSSI in weather station app: copy changes from main SubGHz app * WeatherStation: remove dead code * WeatherStation: sync naming schema with current code. Co-authored-by: あく --- .../scenes/weather_station_receiver.c | 4 +++ .../views/weather_station_receiver.c | 32 +++++++++++++++++-- .../views/weather_station_receiver.h | 2 ++ 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/applications/plugins/weather_station/scenes/weather_station_receiver.c b/applications/plugins/weather_station/scenes/weather_station_receiver.c index 670c8c38615..e76810430a7 100644 --- a/applications/plugins/weather_station/scenes/weather_station_receiver.c +++ b/applications/plugins/weather_station/scenes/weather_station_receiver.c @@ -195,6 +195,10 @@ bool weather_station_scene_receiver_on_event(void* context, SceneManagerEvent ev ws_hopper_update(app); weather_station_scene_receiver_update_statusbar(app); } + // Get current RSSI + float rssi = furi_hal_subghz_get_rssi(); + ws_view_receiver_set_rssi(app->ws_receiver, rssi); + if(app->txrx->txrx_state == WSTxRxStateRx) { notification_message(app->notifications, &sequence_blink_cyan_10); } diff --git a/applications/plugins/weather_station/views/weather_station_receiver.c b/applications/plugins/weather_station/views/weather_station_receiver.c index de5d7b1a366..f8e2e328899 100644 --- a/applications/plugins/weather_station/views/weather_station_receiver.c +++ b/applications/plugins/weather_station/views/weather_station_receiver.c @@ -12,6 +12,7 @@ #define MENU_ITEMS 4u #define UNLOCK_CNT 3 +#define SUBGHZ_RAW_TRESHOLD_MIN -90.0f typedef struct { FuriString* item_str; uint8_t type; @@ -59,8 +60,24 @@ typedef struct { uint16_t list_offset; uint16_t history_item; WSReceiverBarShow bar_show; + uint8_t u_rssi; } WSReceiverModel; +void ws_view_receiver_set_rssi(WSReceiver* instance, float rssi) { + furi_assert(instance); + with_view_model( + instance->view, + WSReceiverModel * model, + { + if(rssi < SUBGHZ_RAW_TRESHOLD_MIN) { + model->u_rssi = 0; + } else { + model->u_rssi = (uint8_t)(rssi - SUBGHZ_RAW_TRESHOLD_MIN); + } + }, + true); +} + void ws_view_receiver_set_lock(WSReceiver* ws_receiver, WSLock lock) { furi_assert(ws_receiver); ws_receiver->lock_count = 0; @@ -164,13 +181,22 @@ static void ws_view_receiver_draw_frame(Canvas* canvas, uint16_t idx, bool scrol canvas_draw_dot(canvas, scrollbar ? 121 : 126, (0 + idx * FRAME_HEIGHT) + 11); } +static void ws_view_rssi_draw(Canvas* canvas, WSReceiverModel* model) { + for(uint8_t i = 1; i < model->u_rssi; i++) { + if(i % 5) { + canvas_draw_dot(canvas, 46 + i, 50); + canvas_draw_dot(canvas, 47 + i, 51); + canvas_draw_dot(canvas, 46 + i, 52); + } + } +} + void ws_view_receiver_draw(Canvas* canvas, WSReceiverModel* model) { canvas_clear(canvas); canvas_set_color(canvas, ColorBlack); canvas_set_font(canvas, FontSecondary); elements_button_left(canvas, "Config"); - canvas_draw_line(canvas, 46, 51, 125, 51); bool scrollbar = model->history_item > 4; FuriString* str_buff; @@ -203,10 +229,12 @@ void ws_view_receiver_draw(Canvas* canvas, WSReceiverModel* model) { canvas_draw_icon(canvas, 0, 0, &I_Scanning_123x52); canvas_set_font(canvas, FontPrimary); canvas_draw_str(canvas, 63, 46, "Scanning..."); - canvas_draw_line(canvas, 46, 51, 125, 51); canvas_set_font(canvas, FontSecondary); } + // Draw RSSI + ws_view_rssi_draw(canvas, model); + switch(model->bar_show) { case WSReceiverBarShowLock: canvas_draw_icon(canvas, 64, 55, &I_Lock_7x8); diff --git a/applications/plugins/weather_station/views/weather_station_receiver.h b/applications/plugins/weather_station/views/weather_station_receiver.h index 30c6516d53c..f81aa1f5ec6 100644 --- a/applications/plugins/weather_station/views/weather_station_receiver.h +++ b/applications/plugins/weather_station/views/weather_station_receiver.h @@ -8,6 +8,8 @@ typedef struct WSReceiver WSReceiver; typedef void (*WSReceiverCallback)(WSCustomEvent event, void* context); +void ws_view_receiver_set_rssi(WSReceiver* instance, float rssi); + void ws_view_receiver_set_lock(WSReceiver* ws_receiver, WSLock keyboard); void ws_view_receiver_set_callback( From 663eb6cd6d4a14d00caf93d402b71eadb9a4d63f Mon Sep 17 00:00:00 2001 From: Liam Hays Date: Tue, 21 Feb 2023 00:15:48 -0700 Subject: [PATCH 421/824] Display Mifare Classic data in NFC app (#2389) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add data display for Mifare Classic cards. * Clean up log statements and data display. Co-authored-by: あく --- .../main/nfc/scenes/nfc_scene_config.h | 1 + .../nfc/scenes/nfc_scene_mf_classic_data.c | 106 ++++++++++++++++++ .../main/nfc/scenes/nfc_scene_nfc_data_info.c | 6 +- .../main/nfc/scenes/nfc_scene_saved_menu.c | 2 + 4 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_classic_data.c diff --git a/applications/main/nfc/scenes/nfc_scene_config.h b/applications/main/nfc/scenes/nfc_scene_config.h index f81fe178e25..a9da07dfda0 100644 --- a/applications/main/nfc/scenes/nfc_scene_config.h +++ b/applications/main/nfc/scenes/nfc_scene_config.h @@ -29,6 +29,7 @@ ADD_SCENE(nfc, mf_desfire_menu, MfDesfireMenu) ADD_SCENE(nfc, mf_desfire_data, MfDesfireData) ADD_SCENE(nfc, mf_desfire_app, MfDesfireApp) ADD_SCENE(nfc, mf_classic_read_success, MfClassicReadSuccess) +ADD_SCENE(nfc, mf_classic_data, MfClassicData) ADD_SCENE(nfc, mf_classic_menu, MfClassicMenu) ADD_SCENE(nfc, mf_classic_emulate, MfClassicEmulate) ADD_SCENE(nfc, mf_classic_keys, MfClassicKeys) diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_data.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_data.c new file mode 100644 index 00000000000..dcb02d3645a --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_data.c @@ -0,0 +1,106 @@ +#include "../nfc_i.h" + +void nfc_scene_mf_classic_data_on_enter(void* context) { + Nfc* nfc = context; + MfClassicType type = nfc->dev->dev_data.mf_classic_data.type; + MfClassicData* data = &nfc->dev->dev_data.mf_classic_data; + TextBox* text_box = nfc->text_box; + + text_box_set_font(text_box, TextBoxFontHex); + + int card_blocks = 0; + if(type == MfClassicType1k) { + card_blocks = MF_CLASSIC_1K_TOTAL_SECTORS_NUM * 4; + } else if(type == MfClassicType4k) { + // 16 sectors of 4 blocks each plus 8 sectors of 16 blocks each + card_blocks = MF_CLASSIC_1K_TOTAL_SECTORS_NUM * 4 + 8 * 16; + } else if(type == MfClassicTypeMini) { + card_blocks = MF_MINI_TOTAL_SECTORS_NUM * 4; + } + + int bytes_written = 0; + for(int block_num = 0; block_num < card_blocks; block_num++) { + bool is_sec_trailer = mf_classic_is_sector_trailer(block_num); + if(is_sec_trailer) { + uint8_t sector_num = mf_classic_get_sector_by_block(block_num); + MfClassicSectorTrailer* sec_tr = + mf_classic_get_sector_trailer_by_sector(data, sector_num); + // Key A + for(size_t i = 0; i < sizeof(sec_tr->key_a); i += 2) { + if((bytes_written % 8 == 0) && (bytes_written != 0)) { + furi_string_push_back(nfc->text_box_store, '\n'); + } + if(mf_classic_is_key_found(data, sector_num, MfClassicKeyA)) { + furi_string_cat_printf( + nfc->text_box_store, "%02X%02X ", sec_tr->key_a[i], sec_tr->key_a[i + 1]); + } else { + furi_string_cat_printf(nfc->text_box_store, "???? "); + } + bytes_written += 2; + } + // Access bytes + for(size_t i = 0; i < MF_CLASSIC_ACCESS_BYTES_SIZE; i += 2) { + if((bytes_written % 8 == 0) && (bytes_written != 0)) { + furi_string_push_back(nfc->text_box_store, '\n'); + } + if(mf_classic_is_block_read(data, block_num)) { + furi_string_cat_printf( + nfc->text_box_store, + "%02X%02X ", + sec_tr->access_bits[i], + sec_tr->access_bits[i + 1]); + } else { + furi_string_cat_printf(nfc->text_box_store, "???? "); + } + bytes_written += 2; + } + // Key B + for(size_t i = 0; i < sizeof(sec_tr->key_b); i += 2) { + if((bytes_written % 8 == 0) && (bytes_written != 0)) { + furi_string_push_back(nfc->text_box_store, '\n'); + } + if(mf_classic_is_key_found(data, sector_num, MfClassicKeyB)) { + furi_string_cat_printf( + nfc->text_box_store, "%02X%02X ", sec_tr->key_b[i], sec_tr->key_b[i + 1]); + } else { + furi_string_cat_printf(nfc->text_box_store, "???? "); + } + bytes_written += 2; + } + } else { + // Write data block + for(size_t i = 0; i < MF_CLASSIC_BLOCK_SIZE; i += 2) { + if((bytes_written % 8 == 0) && (bytes_written != 0)) { + furi_string_push_back(nfc->text_box_store, '\n'); + } + if(mf_classic_is_block_read(data, block_num)) { + furi_string_cat_printf( + nfc->text_box_store, + "%02X%02X ", + data->block[block_num].value[i], + data->block[block_num].value[i + 1]); + } else { + furi_string_cat_printf(nfc->text_box_store, "???? "); + } + bytes_written += 2; + } + } + } + text_box_set_text(text_box, furi_string_get_cstr(nfc->text_box_store)); + + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewTextBox); +} + +bool nfc_scene_mf_classic_data_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + UNUSED(event); + return false; +} + +void nfc_scene_mf_classic_data_on_exit(void* context) { + Nfc* nfc = context; + + // Clean view + text_box_reset(nfc->text_box); + furi_string_reset(nfc->text_box_store); +} diff --git a/applications/main/nfc/scenes/nfc_scene_nfc_data_info.c b/applications/main/nfc/scenes/nfc_scene_nfc_data_info.c index b44ab7823ba..92ad7b56ef4 100644 --- a/applications/main/nfc/scenes/nfc_scene_nfc_data_info.c +++ b/applications/main/nfc/scenes/nfc_scene_nfc_data_info.c @@ -14,7 +14,8 @@ void nfc_scene_nfc_data_info_on_enter(void* context) { NfcDeviceData* dev_data = &nfc->dev->dev_data; NfcProtocol protocol = dev_data->protocol; uint8_t text_scroll_height = 0; - if((protocol == NfcDeviceProtocolMifareDesfire) || (protocol == NfcDeviceProtocolMifareUl)) { + if((protocol == NfcDeviceProtocolMifareDesfire) || (protocol == NfcDeviceProtocolMifareUl) || + (protocol == NfcDeviceProtocolMifareClassic)) { widget_add_button_element( widget, GuiButtonTypeRight, "More", nfc_scene_nfc_data_info_widget_callback, nfc); text_scroll_height = 52; @@ -136,6 +137,9 @@ bool nfc_scene_nfc_data_info_on_event(void* context, SceneManagerEvent event) { } else if(protocol == NfcDeviceProtocolMifareUl) { scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightData); consumed = true; + } else if(protocol == NfcDeviceProtocolMifareClassic) { + scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicData); + consumed = true; } } } diff --git a/applications/main/nfc/scenes/nfc_scene_saved_menu.c b/applications/main/nfc/scenes/nfc_scene_saved_menu.c index 04c686fbe85..f42e0fe9516 100644 --- a/applications/main/nfc/scenes/nfc_scene_saved_menu.c +++ b/applications/main/nfc/scenes/nfc_scene_saved_menu.c @@ -150,6 +150,8 @@ bool nfc_scene_saved_menu_on_event(void* context, SceneManagerEvent event) { application_info_present = nfc_supported_card_verify_and_parse(dev_data); } + FURI_LOG_I("nfc", "application_info_present: %d", application_info_present); + if(application_info_present) { scene_manager_next_scene(nfc->scene_manager, NfcSceneDeviceInfo); } else { From 82ad44a8631bd9fa9c9876d519500f8e331446aa Mon Sep 17 00:00:00 2001 From: Konstantin Volkov <72250702+doomwastaken@users.noreply.github.com> Date: Sat, 25 Feb 2023 15:05:02 +0300 Subject: [PATCH 422/824] changed updater and unit benches (#2427) * changed updater and unit benches * switched flipper name from macos style to searching Co-authored-by: Konstantin Volkov --- .github/workflows/unit_tests.yml | 2 +- .github/workflows/updater_test.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 7e625229ab2..527e9a71ed1 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -10,7 +10,7 @@ env: jobs: run_units_on_bench: - runs-on: [self-hosted, FlipperZeroTest] + runs-on: [self-hosted, FlipperZeroUnitTest] steps: - name: 'Decontaminate previous build leftovers' run: | diff --git a/.github/workflows/updater_test.yml b/.github/workflows/updater_test.yml index 0b02920fa38..300440aae51 100644 --- a/.github/workflows/updater_test.yml +++ b/.github/workflows/updater_test.yml @@ -10,7 +10,7 @@ env: jobs: test_updater_on_bench: - runs-on: [self-hosted, FlipperZeroTestMac1] + runs-on: [self-hosted, FlipperZeroUpdaterTest] steps: - name: 'Decontaminate previous build leftovers' run: | @@ -27,7 +27,7 @@ jobs: - name: 'Get flipper from device manager (mock)' id: device run: | - echo "flipper=/dev/tty.usbmodemflip_Rekigyn1" >> $GITHUB_OUTPUT + echo "flipper=Rekigyn" >> $GITHUB_OUTPUT echo "stlink=0F020D026415303030303032" >> $GITHUB_OUTPUT - name: 'Flashing target firmware' From 203adabc46ff70f7179de849c165001dd6c1abaa Mon Sep 17 00:00:00 2001 From: Mathie <62908057+MathieDev@users.noreply.github.com> Date: Sat, 25 Feb 2023 08:41:49 -0500 Subject: [PATCH 423/824] Update update.py (#2426) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixed grammar mistake Co-authored-by: あく --- scripts/update.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/update.py b/scripts/update.py index 3259c5b09df..2b015726053 100755 --- a/scripts/update.py +++ b/scripts/update.py @@ -199,7 +199,7 @@ def layout_check(self, fw_size, radio_addr): def disclaimer(self): self.logger.error( - "You might brick you device into a state in which you'd need an SWD programmer to fix it." + "You might brick your device into a state in which you'd need an SWD programmer to fix it." ) self.logger.error( "Please confirm that you REALLY want to do that with --I-understand-what-I-am-doing=yes" From e999c3574977db5243cf011d21673b4d1a929819 Mon Sep 17 00:00:00 2001 From: Logandev_ Date: Sat, 25 Feb 2023 05:49:53 -0800 Subject: [PATCH 424/824] Grammar fix in CLI (#2390) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fixed grammar * Update cli_commands.c: made it a little nicer Co-authored-by: あく --- applications/services/cli/cli_commands.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/applications/services/cli/cli_commands.c b/applications/services/cli/cli_commands.c index 4414d365f90..b0f1bdbdf03 100644 --- a/applications/services/cli/cli_commands.c +++ b/applications/services/cli/cli_commands.c @@ -31,7 +31,7 @@ void cli_command_device_info(Cli* cli, FuriString* args, void* context) { void cli_command_help(Cli* cli, FuriString* args, void* context) { UNUSED(args); UNUSED(context); - printf("Commands we have:"); + printf("Commands available:"); // Command count const size_t commands_count = CliCommandTree_size(cli->commands); @@ -61,9 +61,9 @@ void cli_command_help(Cli* cli, FuriString* args, void* context) { if(furi_string_size(args) > 0) { cli_nl(); - printf("Also I have no clue what '"); + printf("`"); printf("%s", furi_string_get_cstr(args)); - printf("' is."); + printf("` command not found"); } } From eaf965c66f7e57855f2cd9be8247882070bf6453 Mon Sep 17 00:00:00 2001 From: n30f0x <87524177+n30f0x@users.noreply.github.com> Date: Sat, 25 Feb 2023 20:34:48 +0300 Subject: [PATCH 425/824] BadUsb: STRINGDELAY feature, worker signal handling refactoring (#2269) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * BadUsb: Added stringdelay feature * BadUsb: added stringdelay feature, fixed delay * BadUsb: fix cursed delay structure * BadUsb: long delay check added * BadUsb: long delay distribution * furi_delay_ms(0) edgecase fix, add documentation entry * additional documentation entry * BadUsb: get rid of bad logic, fixed documentation * BadUSB script: fix events handling * Delay value fix * Script execution fix Co-authored-by: あく Co-authored-by: nminaylov --- applications/main/bad_usb/bad_usb_script.c | 81 ++++++++++++++----- .../scenes/bad_usb_scene_file_select.c | 2 - .../file_formats/BadUsbScriptFormat.md | 8 ++ 3 files changed, 69 insertions(+), 22 deletions(-) diff --git a/applications/main/bad_usb/bad_usb_script.c b/applications/main/bad_usb/bad_usb_script.c index 34dfec2ca85..d66ce8a917a 100644 --- a/applications/main/bad_usb/bad_usb_script.c +++ b/applications/main/bad_usb/bad_usb_script.c @@ -32,6 +32,7 @@ struct BadUsbScript { FuriString* file_path; uint32_t defdelay; uint16_t layout[128]; + uint32_t stringdelay; FuriThread* thread; uint8_t file_buf[FILE_BUFFER_LEN + 1]; uint8_t buf_start; @@ -113,6 +114,8 @@ static const char ducky_cmd_delay[] = {"DELAY "}; static const char ducky_cmd_string[] = {"STRING "}; static const char ducky_cmd_defdelay_1[] = {"DEFAULT_DELAY "}; static const char ducky_cmd_defdelay_2[] = {"DEFAULTDELAY "}; +static const char ducky_cmd_stringdelay_1[] = {"STRINGDELAY "}; +static const char ducky_cmd_stringdelay_2[] = {"STRING_DELAY "}; static const char ducky_cmd_repeat[] = {"REPEAT "}; static const char ducky_cmd_sysrq[] = {"SYSRQ "}; @@ -211,14 +214,19 @@ static bool ducky_altstring(const char* param) { static bool ducky_string(BadUsbScript* bad_usb, const char* param) { uint32_t i = 0; + while(param[i] != '\0') { uint16_t keycode = BADUSB_ASCII_TO_KEY(bad_usb, param[i]); if(keycode != HID_KEYBOARD_NONE) { furi_hal_hid_kb_press(keycode); furi_hal_hid_kb_release(keycode); + if(bad_usb->stringdelay > 0) { + furi_delay_ms(bad_usb->stringdelay); + } } i++; } + bad_usb->stringdelay = 0; return true; } @@ -277,6 +285,20 @@ static int32_t snprintf(error, error_len, "Invalid number %s", line_tmp); } return (state) ? (0) : SCRIPT_STATE_ERROR; + } else if( + (strncmp(line_tmp, ducky_cmd_stringdelay_1, strlen(ducky_cmd_stringdelay_1)) == 0) || + (strncmp(line_tmp, ducky_cmd_stringdelay_2, strlen(ducky_cmd_stringdelay_2)) == 0)) { + //STRINGDELAY, finally it's here + line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; + state = ducky_get_number(line_tmp, &bad_usb->stringdelay); + if((state) && (bad_usb->stringdelay > 0)) { + return state; + } + if(error != NULL) { + snprintf(error, error_len, "Invalid number %s", line_tmp); + } + return SCRIPT_STATE_ERROR; + } else if(strncmp(line_tmp, ducky_cmd_string, strlen(ducky_cmd_string)) == 0) { // STRING line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; @@ -484,6 +506,19 @@ static void bad_usb_hid_state_callback(bool state, void* context) { furi_thread_flags_set(furi_thread_get_id(bad_usb->thread), WorkerEvtDisconnect); } +static uint32_t bad_usb_flags_get(uint32_t flags_mask, uint32_t timeout) { + uint32_t flags = furi_thread_flags_get(); + furi_check((flags & FuriFlagError) == 0); + if(flags == 0) { + flags = furi_thread_flags_wait(flags_mask, FuriFlagWaitAny, timeout); + furi_check(((flags & FuriFlagError) == 0) || (flags == FuriFlagErrorTimeout)); + } else { + uint32_t state = furi_thread_flags_clear(flags); + furi_check((state & FuriFlagError) == 0); + } + return flags; +} + static int32_t bad_usb_worker(void* context) { BadUsbScript* bad_usb = context; @@ -520,11 +555,9 @@ static int32_t bad_usb_worker(void* context) { bad_usb->st.state = worker_state; } else if(worker_state == BadUsbStateNotConnected) { // State: USB not connected - uint32_t flags = furi_thread_flags_wait( - WorkerEvtEnd | WorkerEvtConnect | WorkerEvtToggle, - FuriFlagWaitAny, - FuriWaitForever); - furi_check((flags & FuriFlagError) == 0); + uint32_t flags = bad_usb_flags_get( + WorkerEvtEnd | WorkerEvtConnect | WorkerEvtToggle, FuriWaitForever); + if(flags & WorkerEvtEnd) { break; } else if(flags & WorkerEvtConnect) { @@ -535,11 +568,9 @@ static int32_t bad_usb_worker(void* context) { bad_usb->st.state = worker_state; } else if(worker_state == BadUsbStateIdle) { // State: ready to start - uint32_t flags = furi_thread_flags_wait( - WorkerEvtEnd | WorkerEvtToggle | WorkerEvtDisconnect, - FuriFlagWaitAny, - FuriWaitForever); - furi_check((flags & FuriFlagError) == 0); + uint32_t flags = bad_usb_flags_get( + WorkerEvtEnd | WorkerEvtToggle | WorkerEvtDisconnect, FuriWaitForever); + if(flags & WorkerEvtEnd) { break; } else if(flags & WorkerEvtToggle) { // Start executing script @@ -548,6 +579,7 @@ static int32_t bad_usb_worker(void* context) { bad_usb->buf_len = 0; bad_usb->st.line_cur = 0; bad_usb->defdelay = 0; + bad_usb->stringdelay = 0; bad_usb->repeat_cnt = 0; bad_usb->file_end = false; storage_file_seek(script_file, 0, true); @@ -558,11 +590,9 @@ static int32_t bad_usb_worker(void* context) { bad_usb->st.state = worker_state; } else if(worker_state == BadUsbStateWillRun) { // State: start on connection - uint32_t flags = furi_thread_flags_wait( - WorkerEvtEnd | WorkerEvtConnect | WorkerEvtToggle, - FuriFlagWaitAny, - FuriWaitForever); - furi_check((flags & FuriFlagError) == 0); + uint32_t flags = bad_usb_flags_get( + WorkerEvtEnd | WorkerEvtConnect | WorkerEvtToggle, FuriWaitForever); + if(flags & WorkerEvtEnd) { break; } else if(flags & WorkerEvtConnect) { // Start executing script @@ -571,12 +601,22 @@ static int32_t bad_usb_worker(void* context) { bad_usb->buf_len = 0; bad_usb->st.line_cur = 0; bad_usb->defdelay = 0; + bad_usb->stringdelay = 0; bad_usb->repeat_cnt = 0; bad_usb->file_end = false; storage_file_seek(script_file, 0, true); // extra time for PC to recognize Flipper as keyboard - furi_thread_flags_wait(0, FuriFlagWaitAny, 1500); - worker_state = BadUsbStateRunning; + flags = furi_thread_flags_wait( + WorkerEvtEnd | WorkerEvtDisconnect | WorkerEvtToggle, + FuriFlagWaitAny | FuriFlagNoClear, + 1500); + if(flags == FuriFlagErrorTimeout) { + // If nothing happened - start script execution + worker_state = BadUsbStateRunning; + } else if(flags & WorkerEvtToggle) { + worker_state = BadUsbStateIdle; + furi_thread_flags_clear(WorkerEvtToggle); + } } else if(flags & WorkerEvtToggle) { // Cancel scheduled execution worker_state = BadUsbStateNotConnected; } @@ -586,6 +626,7 @@ static int32_t bad_usb_worker(void* context) { uint16_t delay_cur = (delay_val > 1000) ? (1000) : (delay_val); uint32_t flags = furi_thread_flags_wait( WorkerEvtEnd | WorkerEvtToggle | WorkerEvtDisconnect, FuriFlagWaitAny, delay_cur); + delay_val -= delay_cur; if(!(flags & FuriFlagError)) { if(flags & WorkerEvtEnd) { @@ -629,9 +670,9 @@ static int32_t bad_usb_worker(void* context) { } else if( (worker_state == BadUsbStateFileError) || (worker_state == BadUsbStateScriptError)) { // State: error - uint32_t flags = furi_thread_flags_wait( - WorkerEvtEnd, FuriFlagWaitAny, FuriWaitForever); // Waiting for exit command - furi_check((flags & FuriFlagError) == 0); + uint32_t flags = + bad_usb_flags_get(WorkerEvtEnd, FuriWaitForever); // Waiting for exit command + if(flags & WorkerEvtEnd) { break; } diff --git a/applications/main/bad_usb/scenes/bad_usb_scene_file_select.c b/applications/main/bad_usb/scenes/bad_usb_scene_file_select.c index b046692528c..d6f05a1edec 100644 --- a/applications/main/bad_usb/scenes/bad_usb_scene_file_select.c +++ b/applications/main/bad_usb/scenes/bad_usb_scene_file_select.c @@ -22,7 +22,6 @@ static bool bad_usb_file_select(BadUsbApp* bad_usb) { void bad_usb_scene_file_select_on_enter(void* context) { BadUsbApp* bad_usb = context; - furi_hal_usb_disable(); if(bad_usb->bad_usb_script) { bad_usb_script_close(bad_usb->bad_usb_script); bad_usb->bad_usb_script = NULL; @@ -34,7 +33,6 @@ void bad_usb_scene_file_select_on_enter(void* context) { scene_manager_next_scene(bad_usb->scene_manager, BadUsbSceneWork); } else { - furi_hal_usb_enable(); view_dispatcher_stop(bad_usb->view_dispatcher); } } diff --git a/documentation/file_formats/BadUsbScriptFormat.md b/documentation/file_formats/BadUsbScriptFormat.md index 2ef1d3135e5..94dee59439c 100644 --- a/documentation/file_formats/BadUsbScriptFormat.md +++ b/documentation/file_formats/BadUsbScriptFormat.md @@ -75,6 +75,14 @@ Can be combined with a special key command or a single character. | ------- | ----------- | ----------------- | | STRING | Text string | Print text string | +## String delay + +Delay between keypresses. +|Command|Parameters|Notes| +|-|-|-| +|STRING_DELAY|Delay value in ms|Applied once to next appearing string| +|STRINGDELAY|Delay value in ms|Same as STRING_DELAY| + ## Repeat | Command | Parameters | Notes | From 03f889962bc6fec3ed19ef558ef65e9683f6fd86 Mon Sep 17 00:00:00 2001 From: Eric Betts Date: Sat, 25 Feb 2023 23:59:50 -0800 Subject: [PATCH 426/824] Picopass: factory key support, app rename and move to NFC category, minor code cleanup (#2417) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * message on successful card write * auth using factory key * auth using factory default * factory default screen * write standard iclass key * pass block explicitly * Fix array indexing, add empty detection * PicoPass: rename app and move to NFC group, minor code cleanup Co-authored-by: あく --- applications/plugins/picopass/application.fam | 4 +- applications/plugins/picopass/picopass.c | 10 ++ .../plugins/picopass/picopass_device.c | 2 +- .../plugins/picopass/picopass_device.h | 1 + applications/plugins/picopass/picopass_i.h | 12 ++ .../plugins/picopass/picopass_worker.c | 165 +++++++++++++++++- .../plugins/picopass/picopass_worker.h | 1 + .../plugins/picopass/picopass_worker_i.h | 1 + .../picopass/scenes/picopass_scene_config.h | 2 + .../scenes/picopass_scene_read_card.c | 11 +- .../scenes/picopass_scene_read_card_success.c | 41 +++-- .../picopass_scene_read_factory_success.c | 78 +++++++++ .../picopass_scene_write_card_success.c | 13 ++ .../scenes/picopass_scene_write_key.c | 53 ++++++ 14 files changed, 367 insertions(+), 27 deletions(-) create mode 100644 applications/plugins/picopass/scenes/picopass_scene_read_factory_success.c create mode 100644 applications/plugins/picopass/scenes/picopass_scene_write_key.c diff --git a/applications/plugins/picopass/application.fam b/applications/plugins/picopass/application.fam index f2da6a9fab6..c5087b80457 100644 --- a/applications/plugins/picopass/application.fam +++ b/applications/plugins/picopass/application.fam @@ -1,6 +1,6 @@ App( appid="picopass", - name="PicoPass Reader", + name="PicoPass", apptype=FlipperAppType.EXTERNAL, targets=["f7"], entry_point="picopass_app", @@ -11,7 +11,7 @@ App( stack_size=4 * 1024, order=30, fap_icon="125_10px.png", - fap_category="Tools", + fap_category="NFC", fap_libs=["mbedtls"], fap_private_libs=[ Lib( diff --git a/applications/plugins/picopass/picopass.c b/applications/plugins/picopass/picopass.c index 217f963d364..96ea82c3d73 100644 --- a/applications/plugins/picopass/picopass.c +++ b/applications/plugins/picopass/picopass.c @@ -171,6 +171,16 @@ void picopass_show_loading_popup(void* context, bool show) { } } +bool picopass_is_memset(const uint8_t* data, const uint8_t pattern, size_t size) { + bool result = size > 0; + while(size > 0) { + result &= (*data == pattern); + data++; + size--; + } + return result; +} + int32_t picopass_app(void* p) { UNUSED(p); Picopass* picopass = picopass_alloc(); diff --git a/applications/plugins/picopass/picopass_device.c b/applications/plugins/picopass/picopass_device.c index fd8ddbfbd27..e3940698c25 100644 --- a/applications/plugins/picopass/picopass_device.c +++ b/applications/plugins/picopass/picopass_device.c @@ -368,7 +368,7 @@ ReturnCode picopass_device_parse_wiegand(uint8_t* data, PicopassWiegandRecord* r record->CardNumber = (bot >> 1) & 0xFFFF; record->FacilityCode = (bot >> 17) & 0xFF; - FURI_LOG_D(TAG, "FC:%u CN: %u\n", record->FacilityCode, record->CardNumber); + FURI_LOG_D(TAG, "FC: %u CN: %u", record->FacilityCode, record->CardNumber); record->valid = true; } else { record->CardNumber = 0; diff --git a/applications/plugins/picopass/picopass_device.h b/applications/plugins/picopass/picopass_device.h index 150b095a775..99f1ceea649 100644 --- a/applications/plugins/picopass/picopass_device.h +++ b/applications/plugins/picopass/picopass_device.h @@ -22,6 +22,7 @@ #define PICOPASS_KD_BLOCK_INDEX 3 #define PICOPASS_KC_BLOCK_INDEX 4 #define PICOPASS_AIA_BLOCK_INDEX 5 +#define PICOPASS_PACS_CFG_BLOCK_INDEX 6 #define PICOPASS_APP_FOLDER ANY_PATH("picopass") #define PICOPASS_APP_EXTENSION ".picopass" diff --git a/applications/plugins/picopass/picopass_i.h b/applications/plugins/picopass/picopass_i.h index 469a672b7e7..54533e82364 100644 --- a/applications/plugins/picopass/picopass_i.h +++ b/applications/plugins/picopass/picopass_i.h @@ -81,3 +81,15 @@ void picopass_blink_start(Picopass* picopass); void picopass_blink_stop(Picopass* picopass); void picopass_show_loading_popup(void* context, bool show); + +/** Check if memory is set to pattern + * + * @warning zero size will return false + * + * @param[in] data Pointer to the byte array + * @param[in] pattern The pattern + * @param[in] size The byte array size + * + * @return True if memory is set to pattern, false otherwise + */ +bool picopass_is_memset(const uint8_t* data, const uint8_t pattern, size_t size); diff --git a/applications/plugins/picopass/picopass_worker.c b/applications/plugins/picopass/picopass_worker.c index 1ee814aa59d..6d904478caa 100644 --- a/applications/plugins/picopass/picopass_worker.c +++ b/applications/plugins/picopass/picopass_worker.c @@ -5,7 +5,8 @@ #define TAG "PicopassWorker" const uint8_t picopass_iclass_key[] = {0xaf, 0xa7, 0x85, 0xa7, 0xda, 0xb3, 0x33, 0x78}; -const uint8_t picopass_factory_key[] = {0x76, 0x65, 0x54, 0x43, 0x32, 0x21, 0x10, 0x00}; +const uint8_t picopass_factory_credit_key[] = {0x76, 0x65, 0x54, 0x43, 0x32, 0x21, 0x10, 0x00}; +const uint8_t picopass_factory_debit_key[] = {0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87}; static void picopass_worker_enable_field() { furi_hal_nfc_ll_txrx_on(); @@ -197,6 +198,28 @@ static ReturnCode picopass_auth_standard(uint8_t* csn, uint8_t* div_key) { return rfalPicoPassPollerCheck(mac, &chkRes); } +static ReturnCode picopass_auth_factory(uint8_t* csn, uint8_t* div_key) { + rfalPicoPassReadCheckRes rcRes; + rfalPicoPassCheckRes chkRes; + + ReturnCode err; + + uint8_t mac[4] = {0}; + uint8_t ccnr[12] = {0}; + + err = rfalPicoPassPollerReadCheck(&rcRes); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "rfalPicoPassPollerReadCheck error %d", err); + return err; + } + memcpy(ccnr, rcRes.CCNR, sizeof(rcRes.CCNR)); // last 4 bytes left 0 + + loclass_diversifyKey(csn, picopass_factory_debit_key, div_key); + loclass_opt_doReaderMAC(ccnr, div_key, mac); + + return rfalPicoPassPollerCheck(mac, &chkRes); +} + static ReturnCode picopass_auth_dict( uint8_t* csn, PicopassPacs* pacs, @@ -264,14 +287,23 @@ static ReturnCode picopass_auth_dict( ReturnCode picopass_auth(PicopassBlock* AA1, PicopassPacs* pacs) { ReturnCode err; - FURI_LOG_E(TAG, "Trying standard legacy key"); + FURI_LOG_I(TAG, "Trying standard legacy key"); err = picopass_auth_standard( AA1[PICOPASS_CSN_BLOCK_INDEX].data, AA1[PICOPASS_KD_BLOCK_INDEX].data); if(err == ERR_NONE) { + memcpy(pacs->key, picopass_iclass_key, PICOPASS_BLOCK_LEN); + return ERR_NONE; + } + + FURI_LOG_I(TAG, "Trying factory default key"); + err = picopass_auth_factory( + AA1[PICOPASS_CSN_BLOCK_INDEX].data, AA1[PICOPASS_KD_BLOCK_INDEX].data); + if(err == ERR_NONE) { + memcpy(pacs->key, picopass_factory_debit_key, PICOPASS_BLOCK_LEN); return ERR_NONE; } - FURI_LOG_E(TAG, "Starting user dictionary attack"); + FURI_LOG_I(TAG, "Starting user dictionary attack"); err = picopass_auth_dict( AA1[PICOPASS_CSN_BLOCK_INDEX].data, pacs, @@ -281,7 +313,7 @@ ReturnCode picopass_auth(PicopassBlock* AA1, PicopassPacs* pacs) { return ERR_NONE; } - FURI_LOG_E(TAG, "Starting in-built dictionary attack"); + FURI_LOG_I(TAG, "Starting system dictionary attack"); err = picopass_auth_dict( AA1[PICOPASS_CSN_BLOCK_INDEX].data, pacs, @@ -406,6 +438,84 @@ ReturnCode picopass_write_card(PicopassBlock* AA1) { return ERR_NONE; } +ReturnCode picopass_write_block(PicopassPacs* pacs, uint8_t blockNo, uint8_t* newBlock) { + rfalPicoPassIdentifyRes idRes; + rfalPicoPassSelectRes selRes; + rfalPicoPassReadCheckRes rcRes; + rfalPicoPassCheckRes chkRes; + + ReturnCode err; + + uint8_t div_key[8] = {0}; + uint8_t mac[4] = {0}; + uint8_t ccnr[12] = {0}; + + err = rfalPicoPassPollerIdentify(&idRes); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "rfalPicoPassPollerIdentify error %d", err); + return err; + } + + err = rfalPicoPassPollerSelect(idRes.CSN, &selRes); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "rfalPicoPassPollerSelect error %d", err); + return err; + } + + err = rfalPicoPassPollerReadCheck(&rcRes); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "rfalPicoPassPollerReadCheck error %d", err); + return err; + } + memcpy(ccnr, rcRes.CCNR, sizeof(rcRes.CCNR)); // last 4 bytes left 0 + + loclass_diversifyKey(selRes.CSN, pacs->key, div_key); + loclass_opt_doReaderMAC(ccnr, div_key, mac); + + err = rfalPicoPassPollerCheck(mac, &chkRes); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "rfalPicoPassPollerCheck error %d", err); + return err; + } + + FURI_LOG_D(TAG, "rfalPicoPassPollerWriteBlock %d", blockNo); + uint8_t data[9] = { + blockNo, + newBlock[0], + newBlock[1], + newBlock[2], + newBlock[3], + newBlock[4], + newBlock[5], + newBlock[6], + newBlock[7]}; + loclass_doMAC_N(data, sizeof(data), div_key, mac); + FURI_LOG_D( + TAG, + "loclass_doMAC_N %d %02x%02x%02x%02x%02x%02x%02x%02x %02x%02x%02x%02x", + blockNo, + data[1], + data[2], + data[3], + data[4], + data[5], + data[6], + data[7], + data[8], + mac[0], + mac[1], + mac[2], + mac[3]); + + err = rfalPicoPassPollerWriteBlock(data[0], data + 1, mac); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "rfalPicoPassPollerWriteBlock error %d", err); + return err; + } + + return ERR_NONE; +} + int32_t picopass_worker_task(void* context) { PicopassWorker* picopass_worker = context; @@ -414,6 +524,8 @@ int32_t picopass_worker_task(void* context) { picopass_worker_detect(picopass_worker); } else if(picopass_worker->state == PicopassWorkerStateWrite) { picopass_worker_write(picopass_worker); + } else if(picopass_worker->state == PicopassWorkerStateWriteStandardKey) { + picopass_worker_write_standard_key(picopass_worker); } picopass_worker_disable_field(ERR_NONE); @@ -448,7 +560,7 @@ void picopass_worker_detect(PicopassWorker* picopass_worker) { } // Thank you proxmark! - pacs->legacy = (memcmp(AA1[5].data, "\xff\xff\xff\xff\xff\xff\xff\xff", 8) == 0); + pacs->legacy = picopass_is_memset(AA1[5].data, 0xFF, 8); pacs->se_enabled = (memcmp(AA1[5].data, "\xff\xff\xff\x00\x06\xff\xff\xff", 8) == 0); if(pacs->se_enabled) { FURI_LOG_D(TAG, "SE enabled"); @@ -520,3 +632,46 @@ void picopass_worker_write(PicopassWorker* picopass_worker) { furi_delay_ms(100); } } + +void picopass_worker_write_standard_key(PicopassWorker* picopass_worker) { + PicopassDeviceData* dev_data = picopass_worker->dev_data; + PicopassBlock* AA1 = dev_data->AA1; + PicopassPacs* pacs = &dev_data->pacs; + ReturnCode err; + PicopassWorkerEvent nextState = PicopassWorkerEventSuccess; + + uint8_t* csn = AA1[PICOPASS_CSN_BLOCK_INDEX].data; + uint8_t* configBlock = AA1[PICOPASS_CONFIG_BLOCK_INDEX].data; + uint8_t fuses = configBlock[7]; + uint8_t* oldKey = AA1[PICOPASS_KD_BLOCK_INDEX].data; + + uint8_t newKey[PICOPASS_BLOCK_LEN] = {0}; + loclass_diversifyKey(csn, picopass_iclass_key, newKey); + + if((fuses & 0x80) == 0x80) { + FURI_LOG_D(TAG, "Plain write for personalized mode key change"); + } else { + FURI_LOG_D(TAG, "XOR write for application mode key change"); + // XOR when in application mode + for(size_t i = 0; i < PICOPASS_BLOCK_LEN; i++) { + newKey[i] ^= oldKey[i]; + } + } + + while(picopass_worker->state == PicopassWorkerStateWriteStandardKey) { + if(picopass_detect_card(1000) == ERR_NONE) { + err = picopass_write_block(pacs, PICOPASS_KD_BLOCK_INDEX, newKey); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "picopass_write_block error %d", err); + nextState = PicopassWorkerEventFail; + } + + // Notify caller and exit + if(picopass_worker->callback) { + picopass_worker->callback(nextState, picopass_worker->context); + } + break; + } + furi_delay_ms(100); + } +} diff --git a/applications/plugins/picopass/picopass_worker.h b/applications/plugins/picopass/picopass_worker.h index 29a890a188b..775212c6611 100644 --- a/applications/plugins/picopass/picopass_worker.h +++ b/applications/plugins/picopass/picopass_worker.h @@ -12,6 +12,7 @@ typedef enum { // Main worker states PicopassWorkerStateDetect, PicopassWorkerStateWrite, + PicopassWorkerStateWriteStandardKey, // Transition PicopassWorkerStateStop, } PicopassWorkerState; diff --git a/applications/plugins/picopass/picopass_worker_i.h b/applications/plugins/picopass/picopass_worker_i.h index ded40e6c63d..cf55fbdf58f 100644 --- a/applications/plugins/picopass/picopass_worker_i.h +++ b/applications/plugins/picopass/picopass_worker_i.h @@ -31,3 +31,4 @@ int32_t picopass_worker_task(void* context); void picopass_worker_detect(PicopassWorker* picopass_worker); void picopass_worker_write(PicopassWorker* picopass_worker); +void picopass_worker_write_standard_key(PicopassWorker* picopass_worker); diff --git a/applications/plugins/picopass/scenes/picopass_scene_config.h b/applications/plugins/picopass/scenes/picopass_scene_config.h index 27d6bbcd777..95700787fb4 100644 --- a/applications/plugins/picopass/scenes/picopass_scene_config.h +++ b/applications/plugins/picopass/scenes/picopass_scene_config.h @@ -11,3 +11,5 @@ ADD_SCENE(picopass, delete, Delete) ADD_SCENE(picopass, delete_success, DeleteSuccess) ADD_SCENE(picopass, write_card, WriteCard) ADD_SCENE(picopass, write_card_success, WriteCardSuccess) +ADD_SCENE(picopass, read_factory_success, ReadFactorySuccess) +ADD_SCENE(picopass, write_key, WriteKey) diff --git a/applications/plugins/picopass/scenes/picopass_scene_read_card.c b/applications/plugins/picopass/scenes/picopass_scene_read_card.c index 8188207a2a1..90422a2e738 100644 --- a/applications/plugins/picopass/scenes/picopass_scene_read_card.c +++ b/applications/plugins/picopass/scenes/picopass_scene_read_card.c @@ -1,6 +1,8 @@ #include "../picopass_i.h" #include +const uint8_t picopass_factory_key_check[] = {0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87}; + void picopass_read_card_worker_callback(PicopassWorkerEvent event, void* context) { UNUSED(event); Picopass* picopass = context; @@ -34,7 +36,14 @@ bool picopass_scene_read_card_on_event(void* context, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { if(event.event == PicopassCustomEventWorkerExit) { - scene_manager_next_scene(picopass->scene_manager, PicopassSceneReadCardSuccess); + if(memcmp( + picopass->dev->dev_data.pacs.key, + picopass_factory_key_check, + PICOPASS_BLOCK_LEN) == 0) { + scene_manager_next_scene(picopass->scene_manager, PicopassSceneReadFactorySuccess); + } else { + scene_manager_next_scene(picopass->scene_manager, PicopassSceneReadCardSuccess); + } consumed = true; } } diff --git a/applications/plugins/picopass/scenes/picopass_scene_read_card_success.c b/applications/plugins/picopass/scenes/picopass_scene_read_card_success.c index d89a5d89bb8..f078d460a29 100644 --- a/applications/plugins/picopass/scenes/picopass_scene_read_card_success.c +++ b/applications/plugins/picopass/scenes/picopass_scene_read_card_success.c @@ -15,6 +15,7 @@ void picopass_scene_read_card_success_widget_callback( void picopass_scene_read_card_success_on_enter(void* context) { Picopass* picopass = context; + FuriString* csn_str = furi_string_alloc_set("CSN:"); FuriString* credential_str = furi_string_alloc(); FuriString* wiegand_str = furi_string_alloc(); @@ -30,27 +31,31 @@ void picopass_scene_read_card_success_on_enter(void* context) { PicopassPacs* pacs = &picopass->dev->dev_data.pacs; Widget* widget = picopass->widget; - uint8_t csn[PICOPASS_BLOCK_LEN]; - memcpy(csn, &AA1->data[PICOPASS_CSN_BLOCK_INDEX], PICOPASS_BLOCK_LEN); + uint8_t csn[PICOPASS_BLOCK_LEN] = {0}; + memcpy(csn, AA1[PICOPASS_CSN_BLOCK_INDEX].data, PICOPASS_BLOCK_LEN); for(uint8_t i = 0; i < PICOPASS_BLOCK_LEN; i++) { furi_string_cat_printf(csn_str, "%02X ", csn[i]); } - // Neither of these are valid. Indicates the block was all 0x00 or all 0xff - if(pacs->record.bitLength == 0 || pacs->record.bitLength == 255) { + bool no_key = picopass_is_memset(pacs->key, 0x00, PICOPASS_BLOCK_LEN); + bool empty = + picopass_is_memset(AA1[PICOPASS_PACS_CFG_BLOCK_INDEX].data, 0xFF, PICOPASS_BLOCK_LEN); + + if(no_key) { furi_string_cat_printf(wiegand_str, "Read Failed"); if(pacs->se_enabled) { furi_string_cat_printf(credential_str, "SE enabled"); } + } else if(empty) { + furi_string_cat_printf(wiegand_str, "Empty"); + } else if(pacs->record.bitLength == 0 || pacs->record.bitLength == 255) { + // Neither of these are valid. Indicates the block was all 0x00 or all 0xff + furi_string_cat_printf(wiegand_str, "Invalid PACS"); - widget_add_button_element( - widget, - GuiButtonTypeLeft, - "Retry", - picopass_scene_read_card_success_widget_callback, - picopass); - + if(pacs->se_enabled) { + furi_string_cat_printf(credential_str, "SE enabled"); + } } else { size_t bytesLength = 1 + pacs->record.bitLength / 8; furi_string_set(credential_str, ""); @@ -82,13 +87,6 @@ void picopass_scene_read_card_success_on_enter(void* context) { } } - widget_add_button_element( - widget, - GuiButtonTypeLeft, - "Retry", - picopass_scene_read_card_success_widget_callback, - picopass); - widget_add_button_element( widget, GuiButtonTypeRight, @@ -97,6 +95,13 @@ void picopass_scene_read_card_success_on_enter(void* context) { picopass); } + widget_add_button_element( + widget, + GuiButtonTypeLeft, + "Retry", + picopass_scene_read_card_success_widget_callback, + picopass); + widget_add_string_element( widget, 64, 5, AlignCenter, AlignCenter, FontSecondary, furi_string_get_cstr(csn_str)); widget_add_string_element( diff --git a/applications/plugins/picopass/scenes/picopass_scene_read_factory_success.c b/applications/plugins/picopass/scenes/picopass_scene_read_factory_success.c new file mode 100644 index 00000000000..8e32d21f7b5 --- /dev/null +++ b/applications/plugins/picopass/scenes/picopass_scene_read_factory_success.c @@ -0,0 +1,78 @@ +#include "../picopass_i.h" +#include + +void picopass_scene_read_factory_success_widget_callback( + GuiButtonType result, + InputType type, + void* context) { + furi_assert(context); + Picopass* picopass = context; + + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(picopass->view_dispatcher, result); + } +} + +void picopass_scene_read_factory_success_on_enter(void* context) { + Picopass* picopass = context; + FuriString* title = furi_string_alloc_set("Factory Default"); + FuriString* subtitle = furi_string_alloc_set(""); + + DOLPHIN_DEED(DolphinDeedNfcReadSuccess); + + // Send notification + notification_message(picopass->notifications, &sequence_success); + + // Setup view + Widget* widget = picopass->widget; + //PicopassPacs* pacs = &picopass->dev->dev_data.pacs; + PicopassBlock* AA1 = picopass->dev->dev_data.AA1; + + uint8_t* configBlock = AA1[PICOPASS_CONFIG_BLOCK_INDEX].data; + uint8_t fuses = configBlock[7]; + + if((fuses & 0x80) == 0x80) { + furi_string_cat_printf(subtitle, "Personalization mode"); + } else { + furi_string_cat_printf(subtitle, "Application mode"); + } + + widget_add_button_element( + widget, + GuiButtonTypeCenter, + "Write Standard iClass Key", + picopass_scene_read_factory_success_widget_callback, + picopass); + + widget_add_string_element( + widget, 64, 5, AlignCenter, AlignCenter, FontSecondary, furi_string_get_cstr(title)); + widget_add_string_element( + widget, 64, 20, AlignCenter, AlignCenter, FontPrimary, furi_string_get_cstr(subtitle)); + + furi_string_free(title); + furi_string_free(subtitle); + + view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewWidget); +} + +bool picopass_scene_read_factory_success_on_event(void* context, SceneManagerEvent event) { + Picopass* picopass = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == GuiButtonTypeLeft) { + consumed = scene_manager_previous_scene(picopass->scene_manager); + } else if(event.event == GuiButtonTypeCenter) { + scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteKey); + consumed = true; + } + } + return consumed; +} + +void picopass_scene_read_factory_success_on_exit(void* context) { + Picopass* picopass = context; + + // Clear view + widget_reset(picopass->widget); +} diff --git a/applications/plugins/picopass/scenes/picopass_scene_write_card_success.c b/applications/plugins/picopass/scenes/picopass_scene_write_card_success.c index 108e7d1cefd..4bbca816aa2 100644 --- a/applications/plugins/picopass/scenes/picopass_scene_write_card_success.c +++ b/applications/plugins/picopass/scenes/picopass_scene_write_card_success.c @@ -16,6 +16,7 @@ void picopass_scene_write_card_success_widget_callback( void picopass_scene_write_card_success_on_enter(void* context) { Picopass* picopass = context; Widget* widget = picopass->widget; + FuriString* str = furi_string_alloc_set("Write Success!"); DOLPHIN_DEED(DolphinDeedNfcReadSuccess); @@ -29,6 +30,18 @@ void picopass_scene_write_card_success_on_enter(void* context) { picopass_scene_write_card_success_widget_callback, picopass); + widget_add_button_element( + widget, + GuiButtonTypeRight, + "Menu", + picopass_scene_write_card_success_widget_callback, + picopass); + + widget_add_string_element( + widget, 64, 5, AlignCenter, AlignCenter, FontSecondary, furi_string_get_cstr(str)); + + furi_string_free(str); + view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewWidget); } diff --git a/applications/plugins/picopass/scenes/picopass_scene_write_key.c b/applications/plugins/picopass/scenes/picopass_scene_write_key.c new file mode 100644 index 00000000000..83d594ca2bc --- /dev/null +++ b/applications/plugins/picopass/scenes/picopass_scene_write_key.c @@ -0,0 +1,53 @@ +#include "../picopass_i.h" +#include + +void picopass_write_key_worker_callback(PicopassWorkerEvent event, void* context) { + UNUSED(event); + Picopass* picopass = context; + view_dispatcher_send_custom_event(picopass->view_dispatcher, PicopassCustomEventWorkerExit); +} + +void picopass_scene_write_key_on_enter(void* context) { + Picopass* picopass = context; + DOLPHIN_DEED(DolphinDeedNfcSave); + + // Setup view + Popup* popup = picopass->popup; + popup_set_header(popup, "Writing\niClass\nkey", 68, 30, AlignLeft, AlignTop); + popup_set_icon(popup, 0, 3, &I_RFIDDolphinSend_97x61); + + // Start worker + view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewPopup); + picopass_worker_start( + picopass->worker, + PicopassWorkerStateWriteStandardKey, + &picopass->dev->dev_data, + picopass_write_key_worker_callback, + picopass); + + picopass_blink_start(picopass); +} + +bool picopass_scene_write_key_on_event(void* context, SceneManagerEvent event) { + Picopass* picopass = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == PicopassCustomEventWorkerExit) { + scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteCardSuccess); + consumed = true; + } + } + return consumed; +} + +void picopass_scene_write_key_on_exit(void* context) { + Picopass* picopass = context; + + // Stop worker + picopass_worker_stop(picopass->worker); + // Clear view + popup_reset(picopass->popup); + + picopass_blink_stop(picopass); +} From 12c1ec37a20a2b4f3a4ed5359bb708fcab79ca6c Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 26 Feb 2023 11:08:05 +0300 Subject: [PATCH 427/824] Fix PVS warnings (#2430) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- applications/main/bad_usb/bad_usb_script.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/applications/main/bad_usb/bad_usb_script.c b/applications/main/bad_usb/bad_usb_script.c index d66ce8a917a..beb35b894f7 100644 --- a/applications/main/bad_usb/bad_usb_script.c +++ b/applications/main/bad_usb/bad_usb_script.c @@ -511,7 +511,7 @@ static uint32_t bad_usb_flags_get(uint32_t flags_mask, uint32_t timeout) { furi_check((flags & FuriFlagError) == 0); if(flags == 0) { flags = furi_thread_flags_wait(flags_mask, FuriFlagWaitAny, timeout); - furi_check(((flags & FuriFlagError) == 0) || (flags == FuriFlagErrorTimeout)); + furi_check(((flags & FuriFlagError) == 0) || (flags == (unsigned)FuriFlagErrorTimeout)); } else { uint32_t state = furi_thread_flags_clear(flags); furi_check((state & FuriFlagError) == 0); @@ -610,7 +610,7 @@ static int32_t bad_usb_worker(void* context) { WorkerEvtEnd | WorkerEvtDisconnect | WorkerEvtToggle, FuriFlagWaitAny | FuriFlagNoClear, 1500); - if(flags == FuriFlagErrorTimeout) { + if(flags == (unsigned)FuriFlagErrorTimeout) { // If nothing happened - start script execution worker_state = BadUsbStateRunning; } else if(flags & WorkerEvtToggle) { From 9bda3e62eec4d81cc59352431ee468024c5921ed Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Sun, 26 Feb 2023 14:28:51 +0300 Subject: [PATCH 428/824] SD Cache: moved to diskio layer, invalidation in case of error (#2428) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- firmware/targets/f7/fatfs/sd_spi_io.c | 38 +------------------------ firmware/targets/f7/fatfs/sd_spi_io.h | 1 + firmware/targets/f7/fatfs/user_diskio.c | 37 ++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 37 deletions(-) diff --git a/firmware/targets/f7/fatfs/sd_spi_io.c b/firmware/targets/f7/fatfs/sd_spi_io.c index 93b837e8520..68903acfb65 100644 --- a/firmware/targets/f7/fatfs/sd_spi_io.c +++ b/firmware/targets/f7/fatfs/sd_spi_io.c @@ -17,7 +17,6 @@ #define SD_DUMMY_BYTE 0xFF #define SD_ANSWER_RETRY_COUNT 8 #define SD_IDLE_RETRY_COUNT 100 -#define SD_BLOCK_SIZE 512 #define FLAG_SET(x, y) (((x) & (y)) == (y)) @@ -598,23 +597,6 @@ static SdSpiStatus sd_spi_get_cid(SD_CID* Cid) { return ret; } -static inline bool sd_cache_get(uint32_t address, uint32_t* data) { - uint8_t* cached_data = sector_cache_get(address); - if(cached_data) { - memcpy(data, cached_data, SD_BLOCK_SIZE); - return true; - } - return false; -} - -static inline void sd_cache_put(uint32_t address, uint32_t* data) { - sector_cache_put(address, (uint8_t*)data); -} - -static inline void sd_cache_invalidate_range(uint32_t start_sector, uint32_t end_sector) { - sector_cache_invalidate_range(start_sector, end_sector); -} - static SdSpiStatus sd_spi_cmd_read_blocks(uint32_t* data, uint32_t address, uint32_t blocks, uint32_t timeout_ms) { uint32_t block_address = address; @@ -833,30 +815,12 @@ SdSpiStatus sd_get_card_info(SD_CardInfo* card_info) { SdSpiStatus sd_read_blocks(uint32_t* data, uint32_t address, uint32_t blocks, uint32_t timeout_ms) { - SdSpiStatus status = SdSpiStatusError; - - bool single_sector_read = (blocks == 1); - - if(single_sector_read) { - if(sd_cache_get(address, data)) { - return SdSpiStatusOK; - } - - status = sd_spi_cmd_read_blocks(data, address, blocks, timeout_ms); - - if(status == SdSpiStatusOK) { - sd_cache_put(address, data); - } - } else { - status = sd_spi_cmd_read_blocks(data, address, blocks, timeout_ms); - } - + SdSpiStatus status = sd_spi_cmd_read_blocks(data, address, blocks, timeout_ms); return status; } SdSpiStatus sd_write_blocks(uint32_t* data, uint32_t address, uint32_t blocks, uint32_t timeout_ms) { - sd_cache_invalidate_range(address, address + blocks); SdSpiStatus status = sd_spi_cmd_write_blocks(data, address, blocks, timeout_ms); return status; } diff --git a/firmware/targets/f7/fatfs/sd_spi_io.h b/firmware/targets/f7/fatfs/sd_spi_io.h index 8850eceb765..954c78c4085 100644 --- a/firmware/targets/f7/fatfs/sd_spi_io.h +++ b/firmware/targets/f7/fatfs/sd_spi_io.h @@ -5,6 +5,7 @@ #define __IO volatile #define SD_TIMEOUT_MS (1000) +#define SD_BLOCK_SIZE 512 typedef enum { SdSpiStatusOK, diff --git a/firmware/targets/f7/fatfs/user_diskio.c b/firmware/targets/f7/fatfs/user_diskio.c index 16ac78e4dc4..d7be09c5315 100644 --- a/firmware/targets/f7/fatfs/user_diskio.c +++ b/firmware/targets/f7/fatfs/user_diskio.c @@ -36,6 +36,7 @@ /* Includes ------------------------------------------------------------------*/ #include "user_diskio.h" #include +#include "sector_cache.h" /* Private typedef -----------------------------------------------------------*/ /* Private define ------------------------------------------------------------*/ @@ -79,6 +80,26 @@ Diskio_drvTypeDef USER_Driver = { }; /* Private functions ---------------------------------------------------------*/ +static inline bool sd_cache_get(uint32_t address, uint32_t* data) { + uint8_t* cached_data = sector_cache_get(address); + if(cached_data) { + memcpy(data, cached_data, SD_BLOCK_SIZE); + return true; + } + return false; +} + +static inline void sd_cache_put(uint32_t address, uint32_t* data) { + sector_cache_put(address, (uint8_t*)data); +} + +static inline void sd_cache_invalidate_range(uint32_t start_sector, uint32_t end_sector) { + sector_cache_invalidate_range(start_sector, end_sector); +} + +static inline void sd_cache_invalidate_all() { + sector_cache_init(); +} /** * @brief Initializes a Drive @@ -125,6 +146,14 @@ DRESULT USER_read(BYTE pdrv, BYTE* buff, DWORD sector, UINT count) { UNUSED(pdrv); DRESULT res = RES_ERROR; + bool single_sector = count == 1; + + if(single_sector) { + if(sd_cache_get(sector, (uint32_t*)buff)) { + return RES_OK; + } + } + furi_hal_spi_acquire(&furi_hal_spi_bus_handle_sd_fast); furi_hal_sd_spi_handle = &furi_hal_spi_bus_handle_sd_fast; @@ -145,6 +174,10 @@ DRESULT USER_read(BYTE pdrv, BYTE* buff, DWORD sector, UINT count) { furi_hal_sd_spi_handle = NULL; furi_hal_spi_release(&furi_hal_spi_bus_handle_sd_fast); + if(single_sector && res == RES_OK) { + sd_cache_put(sector, (uint32_t*)buff); + } + return res; /* USER CODE END READ */ } @@ -164,6 +197,8 @@ DRESULT USER_write(BYTE pdrv, const BYTE* buff, DWORD sector, UINT count) { UNUSED(pdrv); DRESULT res = RES_ERROR; + sd_cache_invalidate_range(sector, sector + count); + furi_hal_spi_acquire(&furi_hal_spi_bus_handle_sd_fast); furi_hal_sd_spi_handle = &furi_hal_spi_bus_handle_sd_fast; @@ -175,6 +210,8 @@ DRESULT USER_write(BYTE pdrv, const BYTE* buff, DWORD sector, UINT count) { res = RES_OK; while(sd_get_card_state() != SdSpiStatusOK) { if(furi_hal_cortex_timer_is_expired(timer)) { + sd_cache_invalidate_all(); + res = RES_ERROR; break; } From 0c06e54831f85e0b4d11efda860e44a6db2a4a8e Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Sun, 26 Feb 2023 14:28:52 +0200 Subject: [PATCH 429/824] [FL-3105] Unify power info, power debug, and device_info into one info command (#2393) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Unify power info, power debug, and device_info into one info command * Fix the storage script * Cli: return device_info command for compatibility, rollback storage script * Cli: remove unused context in info_get calls * Cli: cleanup device info callbacks, switch to new separator Co-authored-by: あく --- applications/services/cli/cli_commands.c | 41 ++++++++++++++++++------ applications/services/power/power_cli.c | 30 ----------------- 2 files changed, 32 insertions(+), 39 deletions(-) diff --git a/applications/services/cli/cli_commands.c b/applications/services/cli/cli_commands.c index b0f1bdbdf03..ca9d8b98a69 100644 --- a/applications/services/cli/cli_commands.c +++ b/applications/services/cli/cli_commands.c @@ -12,20 +12,42 @@ // Close to ISO, `date +'%Y-%m-%d %H:%M:%S %u'` #define CLI_DATE_FORMAT "%.4d-%.2d-%.2d %.2d:%.2d:%.2d %d" -void cli_command_device_info_callback(const char* key, const char* value, bool last, void* context) { - UNUSED(context); +void cli_command_info_callback(const char* key, const char* value, bool last, void* context) { UNUSED(last); + UNUSED(context); printf("%-30s: %s\r\n", key, value); } -/* - * Device Info Command +/** Info Command + * * This command is intended to be used by humans + * + * Arguments: + * - device - print device info + * - power - print power info + * - power_debug - print power debug info + * + * @param cli The cli instance + * @param args The arguments + * @param context The context */ -void cli_command_device_info(Cli* cli, FuriString* args, void* context) { +void cli_command_info(Cli* cli, FuriString* args, void* context) { UNUSED(cli); - UNUSED(args); - furi_hal_info_get(cli_command_device_info_callback, '_', context); + + if(context) { + furi_hal_info_get(cli_command_info_callback, '_', NULL); + return; + } + + if(!furi_string_cmp(args, "device")) { + furi_hal_info_get(cli_command_info_callback, '.', NULL); + } else if(!furi_string_cmp(args, "power")) { + furi_hal_power_info_get(cli_command_info_callback, '.', NULL); + } else if(!furi_string_cmp(args, "power_debug")) { + furi_hal_power_debug_get(cli_command_info_callback, NULL); + } else { + cli_print_usage("info", "", furi_string_get_cstr(args)); + } } void cli_command_help(Cli* cli, FuriString* args, void* context) { @@ -410,8 +432,9 @@ void cli_command_i2c(Cli* cli, FuriString* args, void* context) { } void cli_commands_init(Cli* cli) { - cli_add_command(cli, "!", CliCommandFlagParallelSafe, cli_command_device_info, NULL); - cli_add_command(cli, "device_info", CliCommandFlagParallelSafe, cli_command_device_info, NULL); + cli_add_command(cli, "!", CliCommandFlagParallelSafe, cli_command_info, (void*)true); + cli_add_command(cli, "info", CliCommandFlagParallelSafe, cli_command_info, NULL); + cli_add_command(cli, "device_info", CliCommandFlagParallelSafe, cli_command_info, (void*)true); cli_add_command(cli, "?", CliCommandFlagParallelSafe, cli_command_help, NULL); cli_add_command(cli, "help", CliCommandFlagParallelSafe, cli_command_help, NULL); diff --git a/applications/services/power/power_cli.c b/applications/services/power/power_cli.c index f4a10f0a98d..021ce35536c 100644 --- a/applications/services/power/power_cli.c +++ b/applications/services/power/power_cli.c @@ -26,24 +26,6 @@ void power_cli_reboot2dfu(Cli* cli, FuriString* args) { power_reboot(PowerBootModeDfu); } -static void power_cli_callback(const char* key, const char* value, bool last, void* context) { - UNUSED(last); - UNUSED(context); - printf("%-24s: %s\r\n", key, value); -} - -void power_cli_info(Cli* cli, FuriString* args) { - UNUSED(cli); - UNUSED(args); - furi_hal_power_info_get(power_cli_callback, '_', NULL); -} - -void power_cli_debug(Cli* cli, FuriString* args) { - UNUSED(cli); - UNUSED(args); - furi_hal_power_debug_get(power_cli_callback, NULL); -} - void power_cli_5v(Cli* cli, FuriString* args) { UNUSED(cli); if(!furi_string_cmp(args, "0")) { @@ -74,8 +56,6 @@ static void power_cli_command_print_usage() { printf("\toff\t - shutdown power\r\n"); printf("\treboot\t - reboot\r\n"); printf("\treboot2dfu\t - reboot to dfu bootloader\r\n"); - printf("\tinfo\t - show power info\r\n"); - printf("\tdebug\t - show debug information\r\n"); printf("\t5v <0 or 1>\t - enable or disable 5v ext\r\n"); if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { printf("\t3v3 <0 or 1>\t - enable or disable 3v3 ext\r\n"); @@ -108,16 +88,6 @@ void power_cli(Cli* cli, FuriString* args, void* context) { break; } - if(furi_string_cmp_str(cmd, "info") == 0) { - power_cli_info(cli, args); - break; - } - - if(furi_string_cmp_str(cmd, "debug") == 0) { - power_cli_debug(cli, args); - break; - } - if(furi_string_cmp_str(cmd, "5v") == 0) { power_cli_5v(cli, args); break; From 3efb7d4050ef3183a43e59fd65dc2ef24fd4f604 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Mon, 27 Feb 2023 00:15:26 +0900 Subject: [PATCH 430/824] Updater: handle storage errors when removing files, fix folder remove routine, prevent unused services from starting (#2432) * Updater: handle storage errors when removing files * Updater: properly handle folder removal in post update cleanup stage. Prevent power, desktop and dolphin services from starting on update. * Desktop, Dolphin, Power: proper handling and message for special boot mode. * Desktop, Power: add missing TAG * Updater: unify start skip message and fix double delete in backup worker * Cli: unify special boot mode message --- applications/services/bt/bt_service/bt.c | 2 +- applications/services/cli/cli.c | 2 +- applications/services/desktop/desktop.c | 8 +++++ applications/services/dolphin/dolphin.c | 6 ++++ .../services/power/power_service/power.c | 7 ++++ .../updater/util/update_task_worker_backup.c | 33 ++++++++++--------- furi/core/thread.c | 4 +-- furi/flipper.c | 7 ++-- 8 files changed, 46 insertions(+), 23 deletions(-) diff --git a/applications/services/bt/bt_service/bt.c b/applications/services/bt/bt_service/bt.c index 9e578269034..16b60231b24 100644 --- a/applications/services/bt/bt_service/bt.c +++ b/applications/services/bt/bt_service/bt.c @@ -373,7 +373,7 @@ int32_t bt_srv(void* p) { Bt* bt = bt_alloc(); if(furi_hal_rtc_get_boot_mode() != FuriHalRtcBootModeNormal) { - FURI_LOG_W(TAG, "Skipped BT init: device in special startup mode"); + FURI_LOG_W(TAG, "Skipping start in special boot mode"); ble_glue_wait_for_c2_start(FURI_HAL_BT_C2_START_TIMEOUT); furi_record_create(RECORD_BT, bt); return 0; diff --git a/applications/services/cli/cli.c b/applications/services/cli/cli.c index 384d17808dd..b68505c51b9 100644 --- a/applications/services/cli/cli.c +++ b/applications/services/cli/cli.c @@ -461,7 +461,7 @@ int32_t cli_srv(void* p) { if(furi_hal_rtc_get_boot_mode() == FuriHalRtcBootModeNormal) { cli_session_open(cli, &cli_vcp); } else { - FURI_LOG_W(TAG, "Skipped CLI session open: device in special startup mode"); + FURI_LOG_W(TAG, "Skipping start in special boot mode"); } while(1) { diff --git a/applications/services/desktop/desktop.c b/applications/services/desktop/desktop.c index 848f5cb636b..f8716e6cb91 100644 --- a/applications/services/desktop/desktop.c +++ b/applications/services/desktop/desktop.c @@ -17,6 +17,8 @@ #include "helpers/pin_lock.h" #include "helpers/slideshow_filename.h" +#define TAG "Desktop" + static void desktop_auto_lock_arm(Desktop*); static void desktop_auto_lock_inhibit(Desktop*); static void desktop_start_auto_lock_timer(Desktop*); @@ -321,6 +323,12 @@ static bool desktop_check_file_flag(const char* flag_path) { int32_t desktop_srv(void* p) { UNUSED(p); + + if(furi_hal_rtc_get_boot_mode() != FuriHalRtcBootModeNormal) { + FURI_LOG_W(TAG, "Skipping start in special boot mode"); + return 0; + } + Desktop* desktop = desktop_alloc(); bool loaded = DESKTOP_SETTINGS_LOAD(&desktop->settings); diff --git a/applications/services/dolphin/dolphin.c b/applications/services/dolphin/dolphin.c index 41eeef3b1d0..dd8b7105f09 100644 --- a/applications/services/dolphin/dolphin.c +++ b/applications/services/dolphin/dolphin.c @@ -154,6 +154,12 @@ static void dolphin_update_clear_limits_timer_period(Dolphin* dolphin) { int32_t dolphin_srv(void* p) { UNUSED(p); + + if(furi_hal_rtc_get_boot_mode() != FuriHalRtcBootModeNormal) { + FURI_LOG_W(TAG, "Skipping start in special boot mode"); + return 0; + } + Dolphin* dolphin = dolphin_alloc(); furi_record_create(RECORD_DOLPHIN, dolphin); diff --git a/applications/services/power/power_service/power.c b/applications/services/power/power_service/power.c index 5df611a7469..d9319d3d91e 100644 --- a/applications/services/power/power_service/power.c +++ b/applications/services/power/power_service/power.c @@ -4,6 +4,7 @@ #include #define POWER_OFF_TIMEOUT 90 +#define TAG "Power" void power_draw_battery_callback(Canvas* canvas, void* context) { furi_assert(context); @@ -217,6 +218,12 @@ static void power_check_battery_level_change(Power* power) { int32_t power_srv(void* p) { UNUSED(p); + + if(furi_hal_rtc_get_boot_mode() != FuriHalRtcBootModeNormal) { + FURI_LOG_W(TAG, "Skipping start in special boot mode"); + return 0; + } + Power* power = power_alloc(); power_update_info(power); furi_record_create(RECORD_POWER, power); diff --git a/applications/system/updater/util/update_task_worker_backup.c b/applications/system/updater/util/update_task_worker_backup.c index ed53c353bd1..f2c33c2edb4 100644 --- a/applications/system/updater/util/update_task_worker_backup.c +++ b/applications/system/updater/util/update_task_worker_backup.c @@ -97,7 +97,16 @@ static void update_task_cleanup_resources(UpdateTask* update_task, const uint32_ path_concat( STORAGE_EXT_PATH_PREFIX, furi_string_get_cstr(entry_ptr->name), file_path); FURI_LOG_D(TAG, "Removing %s", furi_string_get_cstr(file_path)); - storage_simply_remove(update_task->storage, furi_string_get_cstr(file_path)); + + FS_Error result = + storage_common_remove(update_task->storage, furi_string_get_cstr(file_path)); + if(result != FSE_OK && result != FSE_EXIST) { + FURI_LOG_E( + TAG, + "%s remove failed, cause %s", + furi_string_get_cstr(file_path), + storage_error_get_desc(result)); + } furi_string_free(file_path); } else if(entry_ptr->type == ResourceManifestEntryTypeDirectory) { n_dir_entries++; @@ -116,7 +125,6 @@ static void update_task_cleanup_resources(UpdateTask* update_task, const uint32_ n_dir_entries); FuriString* folder_path = furi_string_alloc(); - File* folder_file = storage_file_alloc(update_task->storage); do { path_concat( @@ -125,24 +133,17 @@ static void update_task_cleanup_resources(UpdateTask* update_task, const uint32_ folder_path); FURI_LOG_D(TAG, "Removing folder %s", furi_string_get_cstr(folder_path)); - if(!storage_dir_open(folder_file, furi_string_get_cstr(folder_path))) { - FURI_LOG_W( + FS_Error result = storage_common_remove( + update_task->storage, furi_string_get_cstr(folder_path)); + if(result != FSE_OK && result != FSE_EXIST) { + FURI_LOG_E( TAG, - "%s can't be opened, skipping", - furi_string_get_cstr(folder_path)); - break; - } - - if(storage_dir_read(folder_file, NULL, NULL, 0)) { - FURI_LOG_I( - TAG, "%s is not empty, skipping", furi_string_get_cstr(folder_path)); - break; + "%s remove failed, cause %s", + furi_string_get_cstr(folder_path), + storage_error_get_desc(result)); } - - storage_simply_remove(update_task->storage, furi_string_get_cstr(folder_path)); } while(false); - storage_file_free(folder_file); furi_string_free(folder_path); } } diff --git a/furi/core/thread.c b/furi/core/thread.c index 9a112d9a87d..ea9f45e842a 100644 --- a/furi/core/thread.c +++ b/furi/core/thread.c @@ -96,9 +96,9 @@ static void furi_thread_body(void* context) { furi_assert(thread->state == FuriThreadStateRunning); if(thread->is_service) { - FURI_LOG_E( + FURI_LOG_W( TAG, - "%s service thread exited. Thread memory cannot be reclaimed.", + "%s service thread TCB memory will not be reclaimed", thread->name ? thread->name : ""); } diff --git a/furi/flipper.c b/furi/flipper.c index 73899e58bf6..d16a84a1018 100644 --- a/furi/flipper.c +++ b/furi/flipper.c @@ -3,6 +3,7 @@ #include #include #include +#include #define TAG "Flipper" @@ -29,10 +30,10 @@ static void flipper_print_version(const char* target, const Version* version) { void flipper_init() { flipper_print_version("Firmware", furi_hal_version_get_firmware_version()); - FURI_LOG_I(TAG, "starting services"); + FURI_LOG_I(TAG, "Boot mode %d, starting services", furi_hal_rtc_get_boot_mode()); for(size_t i = 0; i < FLIPPER_SERVICES_COUNT; i++) { - FURI_LOG_I(TAG, "starting service %s", FLIPPER_SERVICES[i].name); + FURI_LOG_I(TAG, "Starting service %s", FLIPPER_SERVICES[i].name); FuriThread* thread = furi_thread_alloc_ex( FLIPPER_SERVICES[i].name, @@ -44,7 +45,7 @@ void flipper_init() { furi_thread_start(thread); } - FURI_LOG_I(TAG, "services startup complete"); + FURI_LOG_I(TAG, "Startup complete"); } void vApplicationGetIdleTaskMemory( From 1d55aee39cbb329758a50f0110f6d69bc0d00274 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Mon, 27 Feb 2023 01:29:42 +0900 Subject: [PATCH 431/824] Fix incorrect type choise condition in image compressor (#2434) --- scripts/flipper/assets/icon.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/flipper/assets/icon.py b/scripts/flipper/assets/icon.py index ed85b024e85..f0dae25bea5 100644 --- a/scripts/flipper/assets/icon.py +++ b/scripts/flipper/assets/icon.py @@ -105,7 +105,7 @@ def file2image(file): data_enc = bytearray([len(data_enc) & 0xFF, len(data_enc) >> 8]) + data_enc # Use encoded data only if its length less than original, including header - if len(data_enc) < len(data_bin) + 1: + if len(data_enc) + 2 < len(data_bin) + 1: data = b"\x01\x00" + data_enc else: data = b"\x00" + data_bin From 09edf66a2a00c20bce65203c84c6081c9d129aa7 Mon Sep 17 00:00:00 2001 From: Shane Synan Date: Sun, 26 Feb 2023 12:23:39 -0500 Subject: [PATCH 432/824] FuriHal, Power, UnitTests: fix, rename battery charging voltage limit API (#2228) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * FuriHal, Power, UnitTests: rename battery charge voltage limit API * FuriHal: bump API, power info major versions * Power: fix battery charge voltage limit for > 7.935v Co-authored-by: あく --- .../debug/unit_tests/power/power_test.c | 61 +++++++++++-------- .../services/power/power_service/power.c | 6 +- .../services/power/power_service/power.h | 2 +- .../power_settings_scene_battery_info.c | 2 +- .../power_settings_app/views/battery_info.c | 6 +- .../power_settings_app/views/battery_info.h | 2 +- firmware/targets/f7/api_symbols.csv | 4 +- firmware/targets/f7/furi_hal/furi_hal_power.c | 12 ++-- .../targets/furi_hal_include/furi_hal_power.h | 10 +-- lib/drivers/bq25896.c | 17 +++--- lib/drivers/bq25896.h | 4 +- 11 files changed, 69 insertions(+), 57 deletions(-) diff --git a/applications/debug/unit_tests/power/power_test.c b/applications/debug/unit_tests/power/power_test.c index ce2c7aad729..a9b66b22111 100644 --- a/applications/debug/unit_tests/power/power_test.c +++ b/applications/debug/unit_tests/power/power_test.c @@ -3,56 +3,63 @@ #include "../minunit.h" static void power_test_deinit(void) { - // Try to reset to default charging voltage - furi_hal_power_set_battery_charging_voltage(4.208f); + // Try to reset to default charge voltage limit + furi_hal_power_set_battery_charge_voltage_limit(4.208f); } -MU_TEST(test_power_charge_voltage_exact) { - // Power of 16mV charge voltages get applied exactly +MU_TEST(test_power_charge_voltage_limit_exact) { + // Power of 16mV charge voltage limits get applied exactly // (bq25896 charge controller works in 16mV increments) // // This test may need adapted if other charge controllers are used in the future. for(uint16_t charge_mv = 3840; charge_mv <= 4208; charge_mv += 16) { float charge_volt = (float)charge_mv / 1000.0f; - furi_hal_power_set_battery_charging_voltage(charge_volt); - mu_assert_double_eq(charge_volt, furi_hal_power_get_battery_charging_voltage()); + furi_hal_power_set_battery_charge_voltage_limit(charge_volt); + mu_assert_double_eq(charge_volt, furi_hal_power_get_battery_charge_voltage_limit()); } } -MU_TEST(test_power_charge_voltage_floating_imprecision) { +MU_TEST(test_power_charge_voltage_limit_floating_imprecision) { // 4.016f should act as 4.016 V, even with floating point imprecision - furi_hal_power_set_battery_charging_voltage(4.016f); - mu_assert_double_eq(4.016f, furi_hal_power_get_battery_charging_voltage()); + furi_hal_power_set_battery_charge_voltage_limit(4.016f); + mu_assert_double_eq(4.016f, furi_hal_power_get_battery_charge_voltage_limit()); } -MU_TEST(test_power_charge_voltage_inexact) { - // Charge voltages that are not power of 16mV get truncated down - furi_hal_power_set_battery_charging_voltage(3.841f); - mu_assert_double_eq(3.840, furi_hal_power_get_battery_charging_voltage()); +MU_TEST(test_power_charge_voltage_limit_inexact) { + // Charge voltage limits that are not power of 16mV get truncated down + furi_hal_power_set_battery_charge_voltage_limit(3.841f); + mu_assert_double_eq(3.840, furi_hal_power_get_battery_charge_voltage_limit()); - furi_hal_power_set_battery_charging_voltage(3.900f); - mu_assert_double_eq(3.888, furi_hal_power_get_battery_charging_voltage()); + furi_hal_power_set_battery_charge_voltage_limit(3.900f); + mu_assert_double_eq(3.888, furi_hal_power_get_battery_charge_voltage_limit()); - furi_hal_power_set_battery_charging_voltage(4.200f); - mu_assert_double_eq(4.192, furi_hal_power_get_battery_charging_voltage()); + furi_hal_power_set_battery_charge_voltage_limit(4.200f); + mu_assert_double_eq(4.192, furi_hal_power_get_battery_charge_voltage_limit()); } -MU_TEST(test_power_charge_voltage_invalid_clamped) { - // Out-of-range charge voltages get clamped to 3.840 V and 4.208 V - furi_hal_power_set_battery_charging_voltage(3.808f); - mu_assert_double_eq(3.840, furi_hal_power_get_battery_charging_voltage()); +MU_TEST(test_power_charge_voltage_limit_invalid_clamped) { + // Out-of-range charge voltage limits get clamped to 3.840 V and 4.208 V + furi_hal_power_set_battery_charge_voltage_limit(3.808f); + mu_assert_double_eq(3.840, furi_hal_power_get_battery_charge_voltage_limit()); + furi_hal_power_set_battery_charge_voltage_limit(1.0f); + mu_assert_double_eq(3.840, furi_hal_power_get_battery_charge_voltage_limit()); // NOTE: Intentionally picking a small increment above 4.208 V to reduce the risk of an // unhappy battery if this fails. - furi_hal_power_set_battery_charging_voltage(4.240f); - mu_assert_double_eq(4.208, furi_hal_power_get_battery_charging_voltage()); + furi_hal_power_set_battery_charge_voltage_limit(4.240f); + mu_assert_double_eq(4.208, furi_hal_power_get_battery_charge_voltage_limit()); + // Likewise, picking a number that the uint8_t wraparound in the driver would result in a + // VREG value under 23 if this test fails. + // E.g. (uint8_t)((8105-3840)/16) -> 10 + furi_hal_power_set_battery_charge_voltage_limit(8.105f); + mu_assert_double_eq(4.208, furi_hal_power_get_battery_charge_voltage_limit()); } MU_TEST_SUITE(test_power_suite) { - MU_RUN_TEST(test_power_charge_voltage_exact); - MU_RUN_TEST(test_power_charge_voltage_floating_imprecision); - MU_RUN_TEST(test_power_charge_voltage_inexact); - MU_RUN_TEST(test_power_charge_voltage_invalid_clamped); + MU_RUN_TEST(test_power_charge_voltage_limit_exact); + MU_RUN_TEST(test_power_charge_voltage_limit_floating_imprecision); + MU_RUN_TEST(test_power_charge_voltage_limit_inexact); + MU_RUN_TEST(test_power_charge_voltage_limit_invalid_clamped); power_test_deinit(); } diff --git a/applications/services/power/power_service/power.c b/applications/services/power/power_service/power.c index d9319d3d91e..56dbd0f87cf 100644 --- a/applications/services/power/power_service/power.c +++ b/applications/services/power/power_service/power.c @@ -13,8 +13,8 @@ void power_draw_battery_callback(Canvas* canvas, void* context) { if(power->info.gauge_is_ok) { canvas_draw_box(canvas, 2, 2, (power->info.charge + 4) / 5, 4); - if(power->info.voltage_battery_charging < 4.2) { - // Battery charging voltage is modified, indicate with cross pattern + if(power->info.voltage_battery_charge_limit < 4.2) { + // Battery charge voltage limit is modified, indicate with cross pattern canvas_invert_color(canvas); uint8_t battery_bar_width = (power->info.charge + 4) / 5; bool cross_odd = false; @@ -147,7 +147,7 @@ static bool power_update_info(Power* power) { info.capacity_full = furi_hal_power_get_battery_full_capacity(); info.current_charger = furi_hal_power_get_battery_current(FuriHalPowerICCharger); info.current_gauge = furi_hal_power_get_battery_current(FuriHalPowerICFuelGauge); - info.voltage_battery_charging = furi_hal_power_get_battery_charging_voltage(); + info.voltage_battery_charge_limit = furi_hal_power_get_battery_charge_voltage_limit(); info.voltage_charger = furi_hal_power_get_battery_voltage(FuriHalPowerICCharger); info.voltage_gauge = furi_hal_power_get_battery_voltage(FuriHalPowerICFuelGauge); info.voltage_vbus = furi_hal_power_get_usb_voltage(); diff --git a/applications/services/power/power_service/power.h b/applications/services/power/power_service/power.h index 8b9019c4244..c7f5d7e3501 100644 --- a/applications/services/power/power_service/power.h +++ b/applications/services/power/power_service/power.h @@ -41,7 +41,7 @@ typedef struct { float current_charger; float current_gauge; - float voltage_battery_charging; + float voltage_battery_charge_limit; float voltage_charger; float voltage_gauge; float voltage_vbus; diff --git a/applications/settings/power_settings_app/scenes/power_settings_scene_battery_info.c b/applications/settings/power_settings_app/scenes/power_settings_scene_battery_info.c index 5fa38df7299..5181c93f7bc 100644 --- a/applications/settings/power_settings_app/scenes/power_settings_scene_battery_info.c +++ b/applications/settings/power_settings_app/scenes/power_settings_scene_battery_info.c @@ -7,7 +7,7 @@ static void power_settings_scene_battery_info_update_model(PowerSettingsApp* app .gauge_voltage = app->info.voltage_gauge, .gauge_current = app->info.current_gauge, .gauge_temperature = app->info.temperature_gauge, - .charging_voltage = app->info.voltage_battery_charging, + .charge_voltage_limit = app->info.voltage_battery_charge_limit, .charge = app->info.charge, .health = app->info.health, }; diff --git a/applications/settings/power_settings_app/views/battery_info.c b/applications/settings/power_settings_app/views/battery_info.c index d29769d2185..7394fd3c5a6 100644 --- a/applications/settings/power_settings_app/views/battery_info.c +++ b/applications/settings/power_settings_app/views/battery_info.c @@ -69,7 +69,7 @@ static void draw_battery(Canvas* canvas, BatteryInfoModel* data, int x, int y) { drain_current > HIGH_DRAIN_CURRENT_THRESHOLD ? "mA!" : "mA"); } else if(drain_current != 0) { snprintf(header, 20, "..."); - } else if(data->charging_voltage < 4.2) { + } else if(data->charge_voltage_limit < 4.2) { // Non-default battery charging limit, mention it snprintf(emote, sizeof(emote), "Charged!"); snprintf(header, sizeof(header), "Limited to"); @@ -77,8 +77,8 @@ static void draw_battery(Canvas* canvas, BatteryInfoModel* data, int x, int y) { value, sizeof(value), "%lu.%luV", - (uint32_t)(data->charging_voltage), - (uint32_t)(data->charging_voltage * 10) % 10); + (uint32_t)(data->charge_voltage_limit), + (uint32_t)(data->charge_voltage_limit * 10) % 10); } else { snprintf(header, sizeof(header), "Charged!"); } diff --git a/applications/settings/power_settings_app/views/battery_info.h b/applications/settings/power_settings_app/views/battery_info.h index 7bfacf69e27..e52d1844c8d 100644 --- a/applications/settings/power_settings_app/views/battery_info.h +++ b/applications/settings/power_settings_app/views/battery_info.h @@ -9,7 +9,7 @@ typedef struct { float gauge_voltage; float gauge_current; float gauge_temperature; - float charging_voltage; + float charge_voltage_limit; uint8_t charge; uint8_t health; } BatteryInfoModel; diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index e320fc92bab..8a76f8c94f2 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1211,7 +1211,7 @@ Function,+,furi_hal_power_enable_external_3_3v,void, Function,+,furi_hal_power_enable_otg,void, Function,+,furi_hal_power_gauge_is_ok,_Bool, Function,+,furi_hal_power_get_bat_health_pct,uint8_t, -Function,+,furi_hal_power_get_battery_charging_voltage,float, +Function,+,furi_hal_power_get_battery_charge_voltage_limit,float, Function,+,furi_hal_power_get_battery_current,float,FuriHalPowerIC Function,+,furi_hal_power_get_battery_design_capacity,uint32_t, Function,+,furi_hal_power_get_battery_full_capacity,uint32_t, @@ -1230,7 +1230,7 @@ Function,+,furi_hal_power_is_charging_done,_Bool, Function,+,furi_hal_power_is_otg_enabled,_Bool, Function,+,furi_hal_power_off,void, Function,+,furi_hal_power_reset,void, -Function,+,furi_hal_power_set_battery_charging_voltage,void,float +Function,+,furi_hal_power_set_battery_charge_voltage_limit,void,float Function,+,furi_hal_power_shutdown,void, Function,+,furi_hal_power_sleep,void, Function,+,furi_hal_power_sleep_available,_Bool, diff --git a/firmware/targets/f7/furi_hal/furi_hal_power.c b/firmware/targets/f7/furi_hal/furi_hal_power.c index 2d709620d56..dd7c34ae735 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_power.c +++ b/firmware/targets/f7/furi_hal/furi_hal_power.c @@ -341,14 +341,14 @@ bool furi_hal_power_is_otg_enabled() { return ret; } -float furi_hal_power_get_battery_charging_voltage() { +float furi_hal_power_get_battery_charge_voltage_limit() { furi_hal_i2c_acquire(&furi_hal_i2c_handle_power); float ret = (float)bq25896_get_vreg_voltage(&furi_hal_i2c_handle_power) / 1000.0f; furi_hal_i2c_release(&furi_hal_i2c_handle_power); return ret; } -void furi_hal_power_set_battery_charging_voltage(float voltage) { +void furi_hal_power_set_battery_charge_voltage_limit(float voltage) { furi_hal_i2c_acquire(&furi_hal_i2c_handle_power); // Adding 0.0005 is necessary because 4.016f is 4.015999794000, which gets truncated bq25896_set_vreg_voltage(&furi_hal_i2c_handle_power, (uint16_t)(voltage * 1000.0f + 0.0005f)); @@ -486,7 +486,7 @@ void furi_hal_power_info_get(PropertyValueCallback out, char sep, void* context) property_value_out(&property_context, NULL, 2, "format", "major", "2"); property_value_out(&property_context, NULL, 2, "format", "minor", "1"); } else { - property_value_out(&property_context, NULL, 3, "power", "info", "major", "1"); + property_value_out(&property_context, NULL, 3, "power", "info", "major", "2"); property_value_out(&property_context, NULL, 3, "power", "info", "minor", "1"); } @@ -505,8 +505,10 @@ void furi_hal_power_info_get(PropertyValueCallback out, char sep, void* context) } property_value_out(&property_context, NULL, 2, "charge", "state", charge_state); - uint16_t charge_voltage = (uint16_t)(furi_hal_power_get_battery_charging_voltage() * 1000.f); - property_value_out(&property_context, "%u", 2, "charge", "voltage", charge_voltage); + uint16_t charge_voltage_limit = + (uint16_t)(furi_hal_power_get_battery_charge_voltage_limit() * 1000.f); + property_value_out( + &property_context, "%u", 3, "charge", "voltage", "limit", charge_voltage_limit); uint16_t voltage = (uint16_t)(furi_hal_power_get_battery_voltage(FuriHalPowerICFuelGauge) * 1000.f); property_value_out(&property_context, "%u", 2, "battery", "voltage", voltage); diff --git a/firmware/targets/furi_hal_include/furi_hal_power.h b/firmware/targets/furi_hal_include/furi_hal_power.h index 39a11e99f7a..462e20e41db 100644 --- a/firmware/targets/furi_hal_include/furi_hal_power.h +++ b/firmware/targets/furi_hal_include/furi_hal_power.h @@ -121,21 +121,21 @@ void furi_hal_power_check_otg_status(); */ bool furi_hal_power_is_otg_enabled(); -/** Get battery charging voltage in V +/** Get battery charge voltage limit in V * * @return voltage in V */ -float furi_hal_power_get_battery_charging_voltage(); +float furi_hal_power_get_battery_charge_voltage_limit(); -/** Set battery charging voltage in V +/** Set battery charge voltage limit in V * - * Invalid values will be clamped to the nearest valid value. + * Invalid values will be clamped downward to the nearest valid value. * * @param voltage[in] voltage in V * * @return voltage in V */ -void furi_hal_power_set_battery_charging_voltage(float voltage); +void furi_hal_power_set_battery_charge_voltage_limit(float voltage); /** Get remaining battery battery capacity in mAh * diff --git a/lib/drivers/bq25896.c b/lib/drivers/bq25896.c index 7e3008d62df..99534fb13ac 100644 --- a/lib/drivers/bq25896.c +++ b/lib/drivers/bq25896.c @@ -140,15 +140,18 @@ uint16_t bq25896_get_vreg_voltage(FuriHalI2cBusHandle* handle) { void bq25896_set_vreg_voltage(FuriHalI2cBusHandle* handle, uint16_t vreg_voltage) { if(vreg_voltage < 3840) { - // Minimum value is 3840 mV - bq25896_regs.r06.VREG = 0; - } else { - // Find the nearest voltage value (subtract offset, divide into sections) - // Values are truncated downward as needed (e.g. 4200mV -> 4192 mV) - bq25896_regs.r06.VREG = (uint8_t)((vreg_voltage - 3840) / 16); + // Minimum valid value is 3840 mV + vreg_voltage = 3840; + } else if(vreg_voltage > 4208) { + // Maximum safe value is 4208 mV + vreg_voltage = 4208; } - // Do not allow values above 23 (0x17, 4208mV) + // Find the nearest voltage value (subtract offset, divide into sections) + // Values are truncated downward as needed (e.g. 4200mV -> 4192 mV) + bq25896_regs.r06.VREG = (uint8_t)((vreg_voltage - 3840) / 16); + + // Double check: do not allow values above 23 (0x17, 4208mV) // Exceeding 4.2v will overcharge the battery! if(bq25896_regs.r06.VREG > 23) { bq25896_regs.r06.VREG = 23; diff --git a/lib/drivers/bq25896.h b/lib/drivers/bq25896.h index c8a8526a163..f3d1d0e0583 100644 --- a/lib/drivers/bq25896.h +++ b/lib/drivers/bq25896.h @@ -36,10 +36,10 @@ void bq25896_disable_otg(FuriHalI2cBusHandle* handle); /** Is otg enabled */ bool bq25896_is_otg_enabled(FuriHalI2cBusHandle* handle); -/** Get VREG (charging) voltage in mV */ +/** Get VREG (charging limit) voltage in mV */ uint16_t bq25896_get_vreg_voltage(FuriHalI2cBusHandle* handle); -/** Set VREG (charging) voltage in mV +/** Set VREG (charging limit) voltage in mV * * Valid range: 3840mV - 4208mV, in steps of 16mV */ From b054912167764e37e968a8996fc50047392c6be5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Mon, 27 Feb 2023 02:39:26 +0900 Subject: [PATCH 433/824] F8, F18: bump API symbols version (#2435) --- firmware/targets/f18/api_symbols.csv | 6 +++--- firmware/targets/f7/api_symbols.csv | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index 462fbf739d3..549d3812963 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,14.0,, +Version,+,15.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -979,7 +979,7 @@ Function,+,furi_hal_power_enable_external_3_3v,void, Function,+,furi_hal_power_enable_otg,void, Function,+,furi_hal_power_gauge_is_ok,_Bool, Function,+,furi_hal_power_get_bat_health_pct,uint8_t, -Function,+,furi_hal_power_get_battery_charging_voltage,float, +Function,+,furi_hal_power_get_battery_charge_voltage_limit,float, Function,+,furi_hal_power_get_battery_current,float,FuriHalPowerIC Function,+,furi_hal_power_get_battery_design_capacity,uint32_t, Function,+,furi_hal_power_get_battery_full_capacity,uint32_t, @@ -998,7 +998,7 @@ Function,+,furi_hal_power_is_charging_done,_Bool, Function,+,furi_hal_power_is_otg_enabled,_Bool, Function,+,furi_hal_power_off,void, Function,+,furi_hal_power_reset,void, -Function,+,furi_hal_power_set_battery_charging_voltage,void,float +Function,+,furi_hal_power_set_battery_charge_voltage_limit,void,float Function,+,furi_hal_power_shutdown,void, Function,+,furi_hal_power_sleep,void, Function,+,furi_hal_power_sleep_available,_Bool, diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 8a76f8c94f2..8152095dca2 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,14.1,, +Version,+,15.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, From 478390de191f876ef12ed9a54301390a01e8ce40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Mon, 27 Feb 2023 03:06:19 +0900 Subject: [PATCH 434/824] Drivers: remove excessive check in bq25896 and make PVS happy (#2436) --- lib/drivers/bq25896.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/lib/drivers/bq25896.c b/lib/drivers/bq25896.c index 99534fb13ac..4c1d687cb02 100644 --- a/lib/drivers/bq25896.c +++ b/lib/drivers/bq25896.c @@ -151,12 +151,6 @@ void bq25896_set_vreg_voltage(FuriHalI2cBusHandle* handle, uint16_t vreg_voltage // Values are truncated downward as needed (e.g. 4200mV -> 4192 mV) bq25896_regs.r06.VREG = (uint8_t)((vreg_voltage - 3840) / 16); - // Double check: do not allow values above 23 (0x17, 4208mV) - // Exceeding 4.2v will overcharge the battery! - if(bq25896_regs.r06.VREG > 23) { - bq25896_regs.r06.VREG = 23; - } - // Apply changes furi_hal_i2c_write_reg_8( handle, BQ25896_ADDRESS, 0x06, *(uint8_t*)&bq25896_regs.r06, BQ25896_I2C_TIMEOUT); From e6d1bcc42126ec2f0aa7df3811a2aa98caec0eef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Mon, 27 Feb 2023 22:33:45 +0900 Subject: [PATCH 435/824] Plugins: move to designated categories (#2438) --- applications/plugins/dap_link/application.fam | 2 +- applications/plugins/hid_app/application.fam | 8 ++++---- applications/plugins/music_player/application.fam | 2 +- applications/plugins/nfc_magic/application.fam | 2 +- applications/plugins/signal_generator/application.fam | 2 +- applications/plugins/spi_mem_manager/application.fam | 2 +- applications/plugins/weather_station/application.fam | 2 +- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/applications/plugins/dap_link/application.fam b/applications/plugins/dap_link/application.fam index 3b99d5ef372..711e4833d05 100644 --- a/applications/plugins/dap_link/application.fam +++ b/applications/plugins/dap_link/application.fam @@ -10,7 +10,7 @@ App( stack_size=4 * 1024, order=20, fap_icon="dap_link.png", - fap_category="Tools", + fap_category="GPIO", fap_private_libs=[ Lib( name="free-dap", diff --git a/applications/plugins/hid_app/application.fam b/applications/plugins/hid_app/application.fam index b8c13e35389..b6e4e3bf8f3 100644 --- a/applications/plugins/hid_app/application.fam +++ b/applications/plugins/hid_app/application.fam @@ -1,10 +1,10 @@ App( appid="hid_usb", - name="USB Remote", + name="Remote", apptype=FlipperAppType.PLUGIN, entry_point="hid_usb_app", stack_size=1 * 1024, - fap_category="Tools", + fap_category="USB", fap_icon="hid_usb_10px.png", fap_icon_assets="assets", fap_icon_assets_symbol="hid", @@ -13,11 +13,11 @@ App( App( appid="hid_ble", - name="Bluetooth Remote", + name="Remote", apptype=FlipperAppType.PLUGIN, entry_point="hid_ble_app", stack_size=1 * 1024, - fap_category="Tools", + fap_category="Bluetooth", fap_icon="hid_ble_10px.png", fap_icon_assets="assets", fap_icon_assets_symbol="hid", diff --git a/applications/plugins/music_player/application.fam b/applications/plugins/music_player/application.fam index a3698898332..c51abf19449 100644 --- a/applications/plugins/music_player/application.fam +++ b/applications/plugins/music_player/application.fam @@ -12,7 +12,7 @@ App( stack_size=2 * 1024, order=20, fap_icon="icons/music_10px.png", - fap_category="Misc", + fap_category="Media", fap_icon_assets="icons", ) diff --git a/applications/plugins/nfc_magic/application.fam b/applications/plugins/nfc_magic/application.fam index bf42681cae4..a89b45d009f 100644 --- a/applications/plugins/nfc_magic/application.fam +++ b/applications/plugins/nfc_magic/application.fam @@ -11,7 +11,7 @@ App( stack_size=4 * 1024, order=30, fap_icon="../../../assets/icons/Archive/125_10px.png", - fap_category="Tools", + fap_category="NFC", fap_private_libs=[ Lib( name="magic", diff --git a/applications/plugins/signal_generator/application.fam b/applications/plugins/signal_generator/application.fam index de915733c49..60f8deffb9d 100644 --- a/applications/plugins/signal_generator/application.fam +++ b/applications/plugins/signal_generator/application.fam @@ -8,6 +8,6 @@ App( stack_size=1 * 1024, order=50, fap_icon="signal_gen_10px.png", - fap_category="Tools", + fap_category="GPIO", fap_icon_assets="icons", ) diff --git a/applications/plugins/spi_mem_manager/application.fam b/applications/plugins/spi_mem_manager/application.fam index 09d80187690..c1b10bfee24 100644 --- a/applications/plugins/spi_mem_manager/application.fam +++ b/applications/plugins/spi_mem_manager/application.fam @@ -7,7 +7,7 @@ App( stack_size=1 * 2048, order=30, fap_icon="images/Dip8_10px.png", - fap_category="Tools", + fap_category="GPIO", fap_icon_assets="images", fap_private_libs=[ Lib( diff --git a/applications/plugins/weather_station/application.fam b/applications/plugins/weather_station/application.fam index 769b6dd2750..935f92573b1 100644 --- a/applications/plugins/weather_station/application.fam +++ b/applications/plugins/weather_station/application.fam @@ -9,6 +9,6 @@ App( stack_size=4 * 1024, order=50, fap_icon="weather_station_10px.png", - fap_category="Tools", + fap_category="Sub-GHz", fap_icon_assets="images", ) From 9ae58f5462b1a4d8efba88e0ac24cd1fff83ede6 Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Mon, 27 Feb 2023 17:04:14 +0300 Subject: [PATCH 436/824] [FL-3116, FL-3136] BadUSB UI fixes (#2439) --- applications/main/bad_usb/bad_usb_app.c | 8 +++---- .../main/bad_usb/scenes/bad_usb_scene_work.c | 4 +++- .../main/bad_usb/views/bad_usb_view.c | 23 +++++++++++++++---- .../main/bad_usb/views/bad_usb_view.h | 2 ++ 4 files changed, 27 insertions(+), 10 deletions(-) diff --git a/applications/main/bad_usb/bad_usb_app.c b/applications/main/bad_usb/bad_usb_app.c index 1b24957915c..ea97c448782 100644 --- a/applications/main/bad_usb/bad_usb_app.c +++ b/applications/main/bad_usb/bad_usb_app.c @@ -142,10 +142,6 @@ void bad_usb_app_free(BadUsbApp* app) { app->bad_usb_script = NULL; } - if(app->usb_if_prev) { - furi_check(furi_hal_usb_set_config(app->usb_if_prev, NULL)); - } - // Views view_dispatcher_remove_view(app->view_dispatcher, BadUsbAppViewWork); bad_usb_free(app->bad_usb_view); @@ -172,6 +168,10 @@ void bad_usb_app_free(BadUsbApp* app) { furi_string_free(app->file_path); furi_string_free(app->keyboard_layout); + if(app->usb_if_prev) { + furi_check(furi_hal_usb_set_config(app->usb_if_prev, NULL)); + } + free(app); } diff --git a/applications/main/bad_usb/scenes/bad_usb_scene_work.c b/applications/main/bad_usb/scenes/bad_usb_scene_work.c index 187b83bd96d..6f2b8269356 100644 --- a/applications/main/bad_usb/scenes/bad_usb_scene_work.c +++ b/applications/main/bad_usb/scenes/bad_usb_scene_work.c @@ -16,7 +16,9 @@ bool bad_usb_scene_work_on_event(void* context, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { if(event.event == InputKeyLeft) { - scene_manager_next_scene(app->scene_manager, BadUsbSceneConfig); + if(bad_usb_is_idle_state(app->bad_usb_view)) { + scene_manager_next_scene(app->scene_manager, BadUsbSceneConfig); + } consumed = true; } else if(event.event == InputKeyOk) { bad_usb_script_toggle(app->bad_usb_script); diff --git a/applications/main/bad_usb/views/bad_usb_view.c b/applications/main/bad_usb/views/bad_usb_view.c index bb9dc3b7e3b..9ee9dc341c6 100644 --- a/applications/main/bad_usb/views/bad_usb_view.c +++ b/applications/main/bad_usb/views/bad_usb_view.c @@ -48,17 +48,13 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) { if((model->state.state == BadUsbStateIdle) || (model->state.state == BadUsbStateDone) || (model->state.state == BadUsbStateNotConnected)) { elements_button_center(canvas, "Run"); + elements_button_left(canvas, "Config"); } else if((model->state.state == BadUsbStateRunning) || (model->state.state == BadUsbStateDelay)) { elements_button_center(canvas, "Stop"); } else if(model->state.state == BadUsbStateWillRun) { elements_button_center(canvas, "Cancel"); } - if((model->state.state == BadUsbStateNotConnected) || - (model->state.state == BadUsbStateIdle) || (model->state.state == BadUsbStateDone)) { - elements_button_left(canvas, "Config"); - } - if(model->state.state == BadUsbStateNotConnected) { canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18); canvas_set_font(canvas, FontPrimary); @@ -203,6 +199,7 @@ void bad_usb_set_layout(BadUsb* bad_usb, const char* layout) { { strlcpy(model->layout, layout, MAX_NAME_LEN); }, true); } + void bad_usb_set_state(BadUsb* bad_usb, BadUsbState* st) { furi_assert(st); with_view_model( @@ -214,3 +211,19 @@ void bad_usb_set_state(BadUsb* bad_usb, BadUsbState* st) { }, true); } + +bool bad_usb_is_idle_state(BadUsb* bad_usb) { + bool is_idle = false; + with_view_model( + bad_usb->view, + BadUsbModel * model, + { + if((model->state.state == BadUsbStateIdle) || + (model->state.state == BadUsbStateDone) || + (model->state.state == BadUsbStateNotConnected)) { + is_idle = true; + } + }, + false); + return is_idle; +} diff --git a/applications/main/bad_usb/views/bad_usb_view.h b/applications/main/bad_usb/views/bad_usb_view.h index 8447fb05596..2fc01688aaf 100644 --- a/applications/main/bad_usb/views/bad_usb_view.h +++ b/applications/main/bad_usb/views/bad_usb_view.h @@ -19,3 +19,5 @@ void bad_usb_set_file_name(BadUsb* bad_usb, const char* name); void bad_usb_set_layout(BadUsb* bad_usb, const char* layout); void bad_usb_set_state(BadUsb* bad_usb, BadUsbState* st); + +bool bad_usb_is_idle_state(BadUsb* bad_usb); From 777a4d109d723fbe2300d0f66788eed6b24ee208 Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Wed, 1 Mar 2023 20:57:27 +0300 Subject: [PATCH 437/824] [FL-3055] Getter for application data path (#2181) * Threads: application id * Unit tests: appsdata getter test * Unit tests: moar test cases for appsdata getter * Unit tests: remove folders after test * Storage: dir_is_exist, migrate, + unit_tests * Plugins: migration * Storage: common_exists, moar unit_tests 4 "common_migrate", "common_migrate" and "common_merge" bugfixes * Storage: use FuriString for path handling * Storage API: send caller thread id with path * Storage: remove StorageType field in storage file list * Storage: simplify processing * Storage API: send caller thread id with path everywhere * Storage: /app alias, unit tests and path creation * Storage, path helper: remove unused * Examples: app data example * App plugins: use new VFS path * Storage: file_info_is_dir * Services: handle alias if the service accepts a path. * App plugins: fixes * Make PVS happy * Storage: fix storage_merge_recursive * Storage: rename process_aliases to resolve_path. Rename APPS_DATA to APP_DATA. * Apps: use predefined macro instead of raw paths. Example Apps Data: README fixes. * Storage: rename storage_common_resolve_path to storage_common_resolve_path_and_ensure_app_directory * Api: fix version * Storage: rename alias message * Storage: do not create app folders in path resolving process in certain cases. --------- Co-authored-by: Astra <93453568+Astrrra@users.noreply.github.com> Co-authored-by: Aleksandr Kutuzov --- .pvsconfig | 3 + applications/debug/unit_tests/rpc/rpc_test.c | 9 +- .../debug/unit_tests/storage/dirwalk_test.c | 8 +- .../debug/unit_tests/storage/storage_test.c | 292 ++++++++++++++- .../examples/example_apps_data/README.md | 18 + .../example_apps_data/application.fam | 9 + .../example_apps_data/example_apps_data.c | 40 ++ .../main/archive/helpers/archive_browser.c | 2 +- .../main/archive/helpers/archive_favorites.c | 2 +- .../main/archive/helpers/archive_files.c | 2 +- applications/main/fap_loader/fap_loader_app.c | 7 + applications/plugins/hid_app/hid.c | 12 +- applications/plugins/hid_app/hid.h | 2 +- .../plugins/music_player/music_player.c | 11 +- .../picopass/helpers/iclass_elite_dict.c | 20 +- applications/plugins/picopass/picopass.c | 8 + .../plugins/picopass/picopass_device.c | 14 +- .../plugins/picopass/picopass_device.h | 2 - .../scenes/picopass_scene_save_name.c | 4 +- .../scenes/spi_mem_scene_start.c | 2 +- .../plugins/spi_mem_manager/spi_mem_app.c | 10 +- .../plugins/spi_mem_manager/spi_mem_app_i.h | 1 - .../plugins/spi_mem_manager/spi_mem_files.c | 8 +- .../plugins/spi_mem_manager/spi_mem_files.h | 1 - applications/services/applications.h | 1 + applications/services/bt/bt_service/bt_api.c | 9 +- applications/services/cli/cli_commands.c | 11 +- applications/services/dialogs/dialogs_api.c | 22 +- .../gui/modules/file_browser_worker.c | 12 +- applications/services/loader/loader.c | 2 + applications/services/rpc/rpc_storage.c | 9 +- .../services/storage/filesystem_api.c | 4 + .../services/storage/filesystem_api_defines.h | 11 +- applications/services/storage/storage.h | 41 +++ applications/services/storage/storage_cli.c | 6 +- .../services/storage/storage_external_api.c | 103 ++++-- applications/services/storage/storage_glue.c | 11 +- applications/services/storage/storage_glue.h | 7 +- applications/services/storage/storage_i.h | 2 + .../services/storage/storage_message.h | 13 + .../services/storage/storage_processing.c | 260 +++++++------ .../services/storage/storage_test_app.c | 341 ------------------ .../storage_move_to_sd/storage_move_to_sd.c | 2 +- .../music_player/Marble_Machine.fmf | 0 .../picopass/assets/iclass_elite_dict.txt | 0 firmware/targets/f7/api_symbols.csv | 9 +- furi/core/thread.c | 40 +- furi/core/thread.h | 37 ++ furi/flipper.c | 1 + lib/toolbox/dir_walk.c | 2 +- lib/toolbox/tar/tar_archive.c | 2 +- scripts/fbt/appmanifest.py | 1 + 52 files changed, 870 insertions(+), 576 deletions(-) create mode 100644 applications/examples/example_apps_data/README.md create mode 100644 applications/examples/example_apps_data/application.fam create mode 100644 applications/examples/example_apps_data/example_apps_data.c delete mode 100644 applications/services/storage/storage_test_app.c rename assets/resources/{ => apps_data}/music_player/Marble_Machine.fmf (100%) rename assets/resources/{ => apps_data}/picopass/assets/iclass_elite_dict.txt (100%) diff --git a/.pvsconfig b/.pvsconfig index a9ab9c9f66b..49c63ad7398 100644 --- a/.pvsconfig +++ b/.pvsconfig @@ -44,3 +44,6 @@ # Functions that always return the same error code //-V:picopass_device_decrypt:1048 + +# Examples +//V_EXCLUDE_PATH applications/examples/ \ No newline at end of file diff --git a/applications/debug/unit_tests/rpc/rpc_test.c b/applications/debug/unit_tests/rpc/rpc_test.c index 76acf6be98f..329f3b7412e 100644 --- a/applications/debug/unit_tests/rpc/rpc_test.c +++ b/applications/debug/unit_tests/rpc/rpc_test.c @@ -191,7 +191,7 @@ static void clean_directory(Storage* fs_api, const char* clean_dir) { size_t size = strlen(clean_dir) + strlen(name) + 1 + 1; char* fullname = malloc(size); snprintf(fullname, size, "%s/%s", clean_dir, name); - if(fileinfo.flags & FSF_DIRECTORY) { + if(file_info_is_dir(&fileinfo)) { clean_directory(fs_api, fullname); } FS_Error error = storage_common_remove(fs_api, fullname); @@ -608,9 +608,8 @@ static void test_rpc_storage_list_create_expected_list( } if(path_contains_only_ascii(name)) { - list->file[i].type = (fileinfo.flags & FSF_DIRECTORY) ? - PB_Storage_File_FileType_DIR : - PB_Storage_File_FileType_FILE; + list->file[i].type = file_info_is_dir(&fileinfo) ? PB_Storage_File_FileType_DIR : + PB_Storage_File_FileType_FILE; list->file[i].size = fileinfo.size; list->file[i].data = NULL; /* memory free inside rpc_encode_and_send() -> pb_release() */ @@ -873,7 +872,7 @@ static void test_rpc_storage_stat_run(const char* path, uint32_t command_id) { if(error == FSE_OK) { response->which_content = PB_Main_storage_stat_response_tag; response->content.storage_stat_response.has_file = true; - response->content.storage_stat_response.file.type = (fileinfo.flags & FSF_DIRECTORY) ? + response->content.storage_stat_response.file.type = file_info_is_dir(&fileinfo) ? PB_Storage_File_FileType_DIR : PB_Storage_File_FileType_FILE; response->content.storage_stat_response.file.size = fileinfo.size; diff --git a/applications/debug/unit_tests/storage/dirwalk_test.c b/applications/debug/unit_tests/storage/dirwalk_test.c index 97aaa358071..e0842a7a438 100644 --- a/applications/debug/unit_tests/storage/dirwalk_test.c +++ b/applications/debug/unit_tests/storage/dirwalk_test.c @@ -179,7 +179,7 @@ MU_TEST_1(test_dirwalk_full, Storage* storage) { while(dir_walk_read(dir_walk, path, &fileinfo) == DirWalkOK) { furi_string_right(path, strlen(EXT_PATH("dirwalk/"))); - mu_check(storage_test_paths_mark(paths, path, (fileinfo.flags & FSF_DIRECTORY))); + mu_check(storage_test_paths_mark(paths, path, file_info_is_dir(&fileinfo))); } dir_walk_free(dir_walk); @@ -204,7 +204,7 @@ MU_TEST_1(test_dirwalk_no_recursive, Storage* storage) { while(dir_walk_read(dir_walk, path, &fileinfo) == DirWalkOK) { furi_string_right(path, strlen(EXT_PATH("dirwalk/"))); - mu_check(storage_test_paths_mark(paths, path, (fileinfo.flags & FSF_DIRECTORY))); + mu_check(storage_test_paths_mark(paths, path, file_info_is_dir(&fileinfo))); } dir_walk_free(dir_walk); @@ -219,7 +219,7 @@ static bool test_dirwalk_filter_no_folder_ext(const char* name, FileInfo* filein UNUSED(ctx); // only files - if(!(fileinfo->flags & FSF_DIRECTORY)) { + if(!file_info_is_dir(fileinfo)) { // with ".test" in name if(strstr(name, ".test") != NULL) { return true; @@ -243,7 +243,7 @@ MU_TEST_1(test_dirwalk_filter, Storage* storage) { while(dir_walk_read(dir_walk, path, &fileinfo) == DirWalkOK) { furi_string_right(path, strlen(EXT_PATH("dirwalk/"))); - mu_check(storage_test_paths_mark(paths, path, (fileinfo.flags & FSF_DIRECTORY))); + mu_check(storage_test_paths_mark(paths, path, file_info_is_dir(&fileinfo))); } dir_walk_free(dir_walk); diff --git a/applications/debug/unit_tests/storage/storage_test.c b/applications/debug/unit_tests/storage/storage_test.c index 115009701c4..582be7902bb 100644 --- a/applications/debug/unit_tests/storage/storage_test.c +++ b/applications/debug/unit_tests/storage/storage_test.c @@ -2,9 +2,40 @@ #include #include +// DO NOT USE THIS IN PRODUCTION CODE +// This is a hack to access internal storage functions and definitions +#include + +#define UNIT_TESTS_PATH(path) EXT_PATH("unit_tests/" path) + #define STORAGE_LOCKED_FILE EXT_PATH("locked_file.test") #define STORAGE_LOCKED_DIR STORAGE_INT_PATH_PREFIX +#define STORAGE_TEST_DIR UNIT_TESTS_PATH("test_dir") + +static bool storage_file_create(Storage* storage, const char* path, const char* data) { + File* file = storage_file_alloc(storage); + bool result = false; + do { + if(!storage_file_open(file, path, FSAM_WRITE, FSOM_CREATE_NEW)) { + break; + } + + if(storage_file_write(file, data, strlen(data)) != strlen(data)) { + break; + } + + if(!storage_file_close(file)) { + break; + } + + result = true; + } while(0); + + storage_file_free(file); + return result; +} + static void storage_file_open_lock_setup() { Storage* storage = furi_record_open(RECORD_STORAGE); File* file = storage_file_alloc(storage); @@ -115,7 +146,7 @@ static int32_t storage_dir_locker(void* ctx) { File* file = storage_file_alloc(storage); furi_check(storage_dir_open(file, STORAGE_LOCKED_DIR)); furi_semaphore_release(semaphore); - furi_delay_ms(1000); + furi_delay_ms(100); furi_check(storage_dir_close(file)); furi_record_close(RECORD_STORAGE); @@ -152,9 +183,21 @@ MU_TEST(storage_dir_open_lock) { mu_assert(result, "cannot open locked dir"); } +MU_TEST(storage_dir_exists_test) { + Storage* storage = furi_record_open(RECORD_STORAGE); + + mu_check(!storage_dir_exists(storage, STORAGE_TEST_DIR)); + mu_assert_int_eq(FSE_OK, storage_common_mkdir(storage, STORAGE_TEST_DIR)); + mu_check(storage_dir_exists(storage, STORAGE_TEST_DIR)); + mu_assert_int_eq(FSE_OK, storage_common_remove(storage, STORAGE_TEST_DIR)); + + furi_record_close(RECORD_STORAGE); +} + MU_TEST_SUITE(storage_dir) { MU_RUN_TEST(storage_dir_open_close); MU_RUN_TEST(storage_dir_open_lock); + MU_RUN_TEST(storage_dir_exists_test); } static const char* const storage_copy_test_paths[] = { @@ -303,9 +346,256 @@ MU_TEST_SUITE(storage_rename) { furi_record_close(RECORD_STORAGE); } +#define APPSDATA_APP_PATH(path) APPS_DATA_PATH "/" path + +static const char* storage_test_apps[] = { + "-_twilight_-", + "-_rainbow_-", + "-_pinkie_-", + "-_apple_-", + "-_flutter_-", + "-_rare_-", +}; + +static size_t storage_test_apps_count = COUNT_OF(storage_test_apps); + +static int32_t storage_test_app(void* arg) { + UNUSED(arg); + Storage* storage = furi_record_open(RECORD_STORAGE); + storage_common_remove(storage, "/app/test"); + int32_t ret = storage_file_create(storage, "/app/test", "test"); + furi_record_close(RECORD_STORAGE); + return ret; +} + +MU_TEST(test_storage_data_path_apps) { + for(size_t i = 0; i < storage_test_apps_count; i++) { + FuriThread* thread = + furi_thread_alloc_ex(storage_test_apps[i], 1024, storage_test_app, NULL); + furi_thread_set_appid(thread, storage_test_apps[i]); + furi_thread_start(thread); + furi_thread_join(thread); + + mu_assert_int_eq(true, furi_thread_get_return_code(thread)); + + // Check if app data dir and file exists + Storage* storage = furi_record_open(RECORD_STORAGE); + FuriString* expected = furi_string_alloc(); + furi_string_printf(expected, APPSDATA_APP_PATH("%s"), storage_test_apps[i]); + + mu_check(storage_dir_exists(storage, furi_string_get_cstr(expected))); + furi_string_cat(expected, "/test"); + mu_check(storage_file_exists(storage, furi_string_get_cstr(expected))); + + furi_string_printf(expected, APPSDATA_APP_PATH("%s"), storage_test_apps[i]); + storage_simply_remove_recursive(storage, furi_string_get_cstr(expected)); + + furi_record_close(RECORD_STORAGE); + + furi_string_free(expected); + furi_thread_free(thread); + } +} + +MU_TEST(test_storage_data_path) { + Storage* storage = furi_record_open(RECORD_STORAGE); + + File* file = storage_file_alloc(storage); + mu_check(storage_dir_open(file, "/app")); + mu_check(storage_dir_close(file)); + storage_file_free(file); + + // check that appsdata folder exists + mu_check(storage_dir_exists(storage, APPS_DATA_PATH)); + + // check that cli folder exists + mu_check(storage_dir_exists(storage, APPSDATA_APP_PATH("cli"))); + + storage_simply_remove(storage, APPSDATA_APP_PATH("cli")); + + furi_record_close(RECORD_STORAGE); +} + +MU_TEST(test_storage_common_migrate) { + Storage* storage = furi_record_open(RECORD_STORAGE); + + // Setup test folders + storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_old")); + storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_new")); + + // Test migration from non existing + mu_assert_int_eq( + FSE_OK, + storage_common_migrate( + storage, UNIT_TESTS_PATH("migrate_old"), UNIT_TESTS_PATH("migrate_new"))); + + // Test migration from existing folder to non existing + mu_assert_int_eq(FSE_OK, storage_common_mkdir(storage, UNIT_TESTS_PATH("migrate_old"))); + mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_old/file1"), "test1")); + mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_old/file2.ext"), "test2")); + mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_old/file3.ext.ext"), "test3")); + mu_assert_int_eq( + FSE_OK, + storage_common_migrate( + storage, UNIT_TESTS_PATH("migrate_old"), UNIT_TESTS_PATH("migrate_new"))); + + mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new/file1"))); + mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new/file2.ext"))); + mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new/file3.ext.ext"))); + mu_check(storage_dir_exists(storage, UNIT_TESTS_PATH("migrate_new"))); + mu_check(!storage_dir_exists(storage, UNIT_TESTS_PATH("migrate_old"))); + + // Test migration from existing folder to existing folder + mu_assert_int_eq(FSE_OK, storage_common_mkdir(storage, UNIT_TESTS_PATH("migrate_old"))); + mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_old/file1"), "test1")); + mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_old/file2.ext"), "test2")); + mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_old/file3.ext.ext"), "test3")); + + mu_assert_int_eq( + FSE_OK, + storage_common_migrate( + storage, UNIT_TESTS_PATH("migrate_old"), UNIT_TESTS_PATH("migrate_new"))); + + mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new/file1"))); + mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new/file2.ext"))); + mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new/file3.ext.ext"))); + mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new/file11"))); + mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new/file21.ext"))); + mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new/file3.ext1.ext"))); + mu_check(storage_dir_exists(storage, UNIT_TESTS_PATH("migrate_new"))); + mu_check(!storage_dir_exists(storage, UNIT_TESTS_PATH("migrate_old"))); + + storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_old")); + storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_new")); + + // Test migration from empty folder to existing file + // Expected result: FSE_OK, folder removed, file untouched + mu_assert_int_eq(FSE_OK, storage_common_mkdir(storage, UNIT_TESTS_PATH("migrate_old"))); + mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_new"), "test1")); + + mu_assert_int_eq( + FSE_OK, + storage_common_migrate( + storage, UNIT_TESTS_PATH("migrate_old"), UNIT_TESTS_PATH("migrate_new"))); + + mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new"))); + mu_check(!storage_dir_exists(storage, UNIT_TESTS_PATH("migrate_old"))); + + storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_old")); + storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_new")); + + // Test migration from empty folder to existing folder + // Expected result: FSE_OK, old folder removed, new folder untouched + mu_assert_int_eq(FSE_OK, storage_common_mkdir(storage, UNIT_TESTS_PATH("migrate_old"))); + mu_assert_int_eq(FSE_OK, storage_common_mkdir(storage, UNIT_TESTS_PATH("migrate_new"))); + + mu_assert_int_eq( + FSE_OK, + storage_common_migrate( + storage, UNIT_TESTS_PATH("migrate_old"), UNIT_TESTS_PATH("migrate_new"))); + + mu_check(storage_dir_exists(storage, UNIT_TESTS_PATH("migrate_new"))); + mu_check(!storage_dir_exists(storage, UNIT_TESTS_PATH("migrate_old"))); + + storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_old")); + storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_new")); + + // Test migration from existing file to non existing, no extension + mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_old"), "test1")); + + mu_assert_int_eq( + FSE_OK, + storage_common_migrate( + storage, UNIT_TESTS_PATH("migrate_old"), UNIT_TESTS_PATH("migrate_new"))); + + mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new"))); + mu_check(!storage_file_exists(storage, UNIT_TESTS_PATH("migrate_old"))); + + storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_old")); + storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_new")); + + // Test migration from existing file to non existing, with extension + mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_old.file"), "test1")); + + mu_assert_int_eq( + FSE_OK, + storage_common_migrate( + storage, UNIT_TESTS_PATH("migrate_old.file"), UNIT_TESTS_PATH("migrate_new.file"))); + + mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new.file"))); + mu_check(!storage_file_exists(storage, UNIT_TESTS_PATH("migrate_old.file"))); + + storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_old.file")); + storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_new.file")); + + // Test migration from existing file to existing file, no extension + mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_old"), "test1")); + mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_new"), "test2")); + + mu_assert_int_eq( + FSE_OK, + storage_common_migrate( + storage, UNIT_TESTS_PATH("migrate_old"), UNIT_TESTS_PATH("migrate_new"))); + + mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new"))); + mu_check(!storage_file_exists(storage, UNIT_TESTS_PATH("migrate_old"))); + mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new1"))); + + storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_old")); + storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_new")); + storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_new1")); + + // Test migration from existing file to existing file, with extension + mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_old.file"), "test1")); + mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_new.file"), "test2")); + + mu_assert_int_eq( + FSE_OK, + storage_common_migrate( + storage, UNIT_TESTS_PATH("migrate_old.file"), UNIT_TESTS_PATH("migrate_new.file"))); + + mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new.file"))); + mu_check(!storage_file_exists(storage, UNIT_TESTS_PATH("migrate_old.file"))); + mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new1.file"))); + + storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_old.file")); + storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_new.file")); + storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_new1.file")); + + // Test migration from existing file to existing folder + mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_old"), "test1")); + mu_assert_int_eq(FSE_OK, storage_common_mkdir(storage, UNIT_TESTS_PATH("migrate_new"))); + + mu_assert_int_eq( + FSE_OK, + storage_common_migrate( + storage, UNIT_TESTS_PATH("migrate_old"), UNIT_TESTS_PATH("migrate_new"))); + + mu_check(storage_dir_exists(storage, UNIT_TESTS_PATH("migrate_new"))); + mu_check(!storage_file_exists(storage, UNIT_TESTS_PATH("migrate_old"))); + mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new1"))); + + storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_old")); + storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_new")); + storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_new1")); + + furi_record_close(RECORD_STORAGE); +} + +MU_TEST_SUITE(test_data_path) { + MU_RUN_TEST(test_storage_data_path); + MU_RUN_TEST(test_storage_data_path_apps); +} + +MU_TEST_SUITE(test_storage_common) { + MU_RUN_TEST(test_storage_common_migrate); +} + int run_minunit_test_storage() { MU_RUN_SUITE(storage_file); MU_RUN_SUITE(storage_dir); MU_RUN_SUITE(storage_rename); + MU_RUN_SUITE(test_data_path); + MU_RUN_SUITE(test_storage_common); return MU_EXIT_CODE; } diff --git a/applications/examples/example_apps_data/README.md b/applications/examples/example_apps_data/README.md new file mode 100644 index 00000000000..fd866607754 --- /dev/null +++ b/applications/examples/example_apps_data/README.md @@ -0,0 +1,18 @@ +# Apps Data folder Example + +This example demonstrates how to utilize the Apps Data folder to store data that is not part of the app itself, such as user data, configuration files, and so forth. + +## What is the Apps Data Folder? + +The **Apps Data** folder is a folder used to store data for external apps that are not part of the main firmware. + +The path to the current application folder is related to the `appid` of the app. The `appid` is used to identify the app in the app store and is stored in the `application.fam` file. +The Apps Data folder is located only on the external storage, the SD card. + +For example, if the `appid` of the app is `snake_game`, the path to the Apps Data folder will be `/ext/apps_data/snake_game`. But using raw paths is not recommended, because the path to the Apps Data folder can change in the future. Use the `/app` alias instead. + +## How to get the path to the Apps Data folder? + +You can use `/app` alias to get the path to the current application data folder. For example, if you want to open a file `config.txt` in the Apps Data folder, you can use the next path: `/app/config.txt`. But this way is not recommended, because even the `/app` alias can change in the future. + +We recommend to use the `APP_DATA_PATH` macro to get the path to the Apps Data folder. For example, if you want to open a file `config.txt` in the Apps Data folder, you can use the next path: `APP_DATA_PATH("config.txt")`. \ No newline at end of file diff --git a/applications/examples/example_apps_data/application.fam b/applications/examples/example_apps_data/application.fam new file mode 100644 index 00000000000..f44dca97d9a --- /dev/null +++ b/applications/examples/example_apps_data/application.fam @@ -0,0 +1,9 @@ +App( + appid="example_apps_data", + name="Example: Apps Data", + apptype=FlipperAppType.EXTERNAL, + entry_point="example_apps_data_main", + requires=["gui"], + stack_size=1 * 1024, + fap_category="Examples", +) diff --git a/applications/examples/example_apps_data/example_apps_data.c b/applications/examples/example_apps_data/example_apps_data.c new file mode 100644 index 00000000000..d6104c1374c --- /dev/null +++ b/applications/examples/example_apps_data/example_apps_data.c @@ -0,0 +1,40 @@ +#include +#include + +// Define log tag +#define TAG "example_apps_data" + +// Application entry point +int32_t example_apps_data_main(void* p) { + // Mark argument as unused + UNUSED(p); + + // Open storage + Storage* storage = furi_record_open(RECORD_STORAGE); + + // Allocate file + File* file = storage_file_alloc(storage); + + // Get the path to the current application data folder + // That is: /ext/apps_data/ + // And it will create folders in the path if they don't exist + // In this example it will create /ext/apps_data/example_apps_data + // And file will be /ext/apps_data/example_apps_data/test.txt + + // Open file, write data and close it + if(!storage_file_open(file, APP_DATA_PATH("test.txt"), FSAM_WRITE, FSOM_CREATE_ALWAYS)) { + FURI_LOG_E(TAG, "Failed to open file"); + } + if(!storage_file_write(file, "Hello World!", strlen("Hello World!"))) { + FURI_LOG_E(TAG, "Failed to write to file"); + } + storage_file_close(file); + + // Deallocate file + storage_file_free(file); + + // Close storage + furi_record_close(RECORD_STORAGE); + + return 0; +} diff --git a/applications/main/archive/helpers/archive_browser.c b/applications/main/archive/helpers/archive_browser.c index 1133f6d7168..f5efca4698e 100644 --- a/applications/main/archive/helpers/archive_browser.c +++ b/applications/main/archive/helpers/archive_browser.c @@ -436,7 +436,7 @@ static bool archive_is_dir_exists(FuriString* path) { FileInfo file_info; Storage* storage = furi_record_open(RECORD_STORAGE); if(storage_common_stat(storage, furi_string_get_cstr(path), &file_info) == FSE_OK) { - if(file_info.flags & FSF_DIRECTORY) { + if(file_info_is_dir(&file_info)) { state = true; } } diff --git a/applications/main/archive/helpers/archive_favorites.c b/applications/main/archive/helpers/archive_favorites.c index 8bbcb52136c..f395ee5a116 100644 --- a/applications/main/archive/helpers/archive_favorites.c +++ b/applications/main/archive/helpers/archive_favorites.c @@ -160,7 +160,7 @@ bool archive_favorites_read(void* context) { if(storage_file_exists(storage, furi_string_get_cstr(buffer))) { storage_common_stat(storage, furi_string_get_cstr(buffer), &file_info); archive_add_file_item( - browser, (file_info.flags & FSF_DIRECTORY), furi_string_get_cstr(buffer)); + browser, file_info_is_dir(&file_info), furi_string_get_cstr(buffer)); file_count++; } else { need_refresh = true; diff --git a/applications/main/archive/helpers/archive_files.c b/applications/main/archive/helpers/archive_files.c index 87265a45d24..a8bd937c99b 100644 --- a/applications/main/archive/helpers/archive_files.c +++ b/applications/main/archive/helpers/archive_files.c @@ -91,7 +91,7 @@ void archive_delete_file(void* context, const char* format, ...) { bool res = false; - if(fileinfo.flags & FSF_DIRECTORY) { + if(file_info_is_dir(&fileinfo)) { res = storage_simply_remove_recursive(fs_api, furi_string_get_cstr(filename)); } else { res = (storage_common_remove(fs_api, furi_string_get_cstr(filename)) == FSE_OK); diff --git a/applications/main/fap_loader/fap_loader_app.c b/applications/main/fap_loader/fap_loader_app.c index 7911aa06891..e81a3ce4c84 100644 --- a/applications/main/fap_loader/fap_loader_app.c +++ b/applications/main/fap_loader/fap_loader_app.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include "elf_cpp/elf_hashtable.h" #include "fap_loader_app.h" @@ -105,6 +106,12 @@ static bool fap_loader_run_selected_app(FapLoader* loader) { FURI_LOG_I(TAG, "FAP Loader is starting app"); FuriThread* thread = flipper_application_spawn(loader->app, NULL); + + FuriString* app_name = furi_string_alloc(); + path_extract_filename_no_ext(furi_string_get_cstr(loader->fap_path), app_name); + furi_thread_set_appid(thread, furi_string_get_cstr(app_name)); + furi_string_free(app_name); + furi_thread_start(thread); furi_thread_join(thread); diff --git a/applications/plugins/hid_app/hid.c b/applications/plugins/hid_app/hid.c index 7f63f0cc6ed..949ff63b3ed 100644 --- a/applications/plugins/hid_app/hid.c +++ b/applications/plugins/hid_app/hid.c @@ -376,7 +376,17 @@ int32_t hid_ble_app(void* p) { // Wait 2nd core to update nvm storage furi_delay_ms(200); - bt_keys_storage_set_storage_path(app->bt, HID_BT_KEYS_STORAGE_PATH); + // Migrate data from old sd-card folder + Storage* storage = furi_record_open(RECORD_STORAGE); + + storage_common_migrate( + storage, + EXT_PATH("apps/Tools/" HID_BT_KEYS_STORAGE_NAME), + APP_DATA_PATH(HID_BT_KEYS_STORAGE_NAME)); + + bt_keys_storage_set_storage_path(app->bt, APP_DATA_PATH(HID_BT_KEYS_STORAGE_NAME)); + + furi_record_close(RECORD_STORAGE); if(!bt_set_profile(app->bt, BtProfileHidKeyboard)) { FURI_LOG_E(TAG, "Failed to switch to HID profile"); diff --git a/applications/plugins/hid_app/hid.h b/applications/plugins/hid_app/hid.h index fe32a199b45..8ed1664a340 100644 --- a/applications/plugins/hid_app/hid.h +++ b/applications/plugins/hid_app/hid.h @@ -23,7 +23,7 @@ #include "views/hid_mouse_jiggler.h" #include "views/hid_tiktok.h" -#define HID_BT_KEYS_STORAGE_PATH EXT_PATH("apps/Tools/.bt_hid.keys") +#define HID_BT_KEYS_STORAGE_NAME ".bt_hid.keys" typedef enum { HidTransportUsb, diff --git a/applications/plugins/music_player/music_player.c b/applications/plugins/music_player/music_player.c index 28127a575dc..2380d7d17be 100644 --- a/applications/plugins/music_player/music_player.c +++ b/applications/plugins/music_player/music_player.c @@ -10,7 +10,6 @@ #define TAG "MusicPlayer" -#define MUSIC_PLAYER_APP_PATH_FOLDER ANY_PATH("music_player") #define MUSIC_PLAYER_APP_EXTENSION "*" #define MUSIC_PLAYER_SEMITONE_HISTORY_SIZE 4 @@ -307,18 +306,24 @@ int32_t music_player_app(void* p) { if(p && strlen(p)) { furi_string_set(file_path, (const char*)p); } else { - furi_string_set(file_path, MUSIC_PLAYER_APP_PATH_FOLDER); + Storage* storage = furi_record_open(RECORD_STORAGE); + storage_common_migrate( + storage, EXT_PATH("music_player"), STORAGE_APP_DATA_PATH_PREFIX); + furi_record_close(RECORD_STORAGE); + + furi_string_set(file_path, STORAGE_APP_DATA_PATH_PREFIX); DialogsFileBrowserOptions browser_options; dialog_file_browser_set_basic_options( &browser_options, MUSIC_PLAYER_APP_EXTENSION, &I_music_10px); browser_options.hide_ext = false; - browser_options.base_path = MUSIC_PLAYER_APP_PATH_FOLDER; + browser_options.base_path = STORAGE_APP_DATA_PATH_PREFIX; DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); bool res = dialog_file_browser_show(dialogs, file_path, file_path, &browser_options); furi_record_close(RECORD_DIALOGS); + if(!res) { FURI_LOG_E(TAG, "No file selected"); break; diff --git a/applications/plugins/picopass/helpers/iclass_elite_dict.c b/applications/plugins/picopass/helpers/iclass_elite_dict.c index 455eb23c170..e8c13dd1df0 100644 --- a/applications/plugins/picopass/helpers/iclass_elite_dict.c +++ b/applications/plugins/picopass/helpers/iclass_elite_dict.c @@ -3,8 +3,8 @@ #include #include -#define ICLASS_ELITE_DICT_FLIPPER_PATH EXT_PATH("picopass/assets/iclass_elite_dict.txt") -#define ICLASS_ELITE_DICT_USER_PATH EXT_PATH("picopass/assets/iclass_elite_dict_user.txt") +#define ICLASS_ELITE_DICT_FLIPPER_NAME APP_DATA_PATH("assets/iclass_elite_dict.txt") +#define ICLASS_ELITE_DICT_USER_NAME APP_DATA_PATH("assets/iclass_elite_dict_user.txt") #define TAG "IclassEliteDict" @@ -21,10 +21,10 @@ bool iclass_elite_dict_check_presence(IclassEliteDictType dict_type) { bool dict_present = false; if(dict_type == IclassEliteDictTypeFlipper) { - dict_present = storage_common_stat(storage, ICLASS_ELITE_DICT_FLIPPER_PATH, NULL) == - FSE_OK; + dict_present = + (storage_common_stat(storage, ICLASS_ELITE_DICT_FLIPPER_NAME, NULL) == FSE_OK); } else if(dict_type == IclassEliteDictTypeUser) { - dict_present = storage_common_stat(storage, ICLASS_ELITE_DICT_USER_PATH, NULL) == FSE_OK; + dict_present = (storage_common_stat(storage, ICLASS_ELITE_DICT_USER_NAME, NULL) == FSE_OK); } furi_record_close(RECORD_STORAGE); @@ -36,27 +36,26 @@ IclassEliteDict* iclass_elite_dict_alloc(IclassEliteDictType dict_type) { IclassEliteDict* dict = malloc(sizeof(IclassEliteDict)); Storage* storage = furi_record_open(RECORD_STORAGE); dict->stream = buffered_file_stream_alloc(storage); - furi_record_close(RECORD_STORAGE); FuriString* next_line = furi_string_alloc(); bool dict_loaded = false; do { if(dict_type == IclassEliteDictTypeFlipper) { if(!buffered_file_stream_open( - dict->stream, ICLASS_ELITE_DICT_FLIPPER_PATH, FSAM_READ, FSOM_OPEN_EXISTING)) { + dict->stream, ICLASS_ELITE_DICT_FLIPPER_NAME, FSAM_READ, FSOM_OPEN_EXISTING)) { buffered_file_stream_close(dict->stream); break; } } else if(dict_type == IclassEliteDictTypeUser) { if(!buffered_file_stream_open( - dict->stream, ICLASS_ELITE_DICT_USER_PATH, FSAM_READ_WRITE, FSOM_OPEN_ALWAYS)) { + dict->stream, ICLASS_ELITE_DICT_USER_NAME, FSAM_READ_WRITE, FSOM_OPEN_ALWAYS)) { buffered_file_stream_close(dict->stream); break; } } // Read total amount of keys - while(true) { + while(true) { //-V547 if(!stream_read_line(dict->stream, next_line)) break; if(furi_string_get_char(next_line, 0) == '#') continue; if(furi_string_size(next_line) != ICLASS_ELITE_KEY_LINE_LEN) continue; @@ -69,12 +68,13 @@ IclassEliteDict* iclass_elite_dict_alloc(IclassEliteDictType dict_type) { FURI_LOG_I(TAG, "Loaded dictionary with %lu keys", dict->total_keys); } while(false); - if(!dict_loaded) { + if(!dict_loaded) { //-V547 buffered_file_stream_close(dict->stream); free(dict); dict = NULL; } + furi_record_close(RECORD_STORAGE); furi_string_free(next_line); return dict; diff --git a/applications/plugins/picopass/picopass.c b/applications/plugins/picopass/picopass.c index 96ea82c3d73..5d1cee70edd 100644 --- a/applications/plugins/picopass/picopass.c +++ b/applications/plugins/picopass/picopass.c @@ -171,6 +171,12 @@ void picopass_show_loading_popup(void* context, bool show) { } } +static void picopass_migrate_from_old_folder() { + Storage* storage = furi_record_open(RECORD_STORAGE); + storage_common_migrate(storage, "/ext/picopass", STORAGE_APP_DATA_PATH_PREFIX); + furi_record_close(RECORD_STORAGE); +} + bool picopass_is_memset(const uint8_t* data, const uint8_t pattern, size_t size) { bool result = size > 0; while(size > 0) { @@ -183,6 +189,8 @@ bool picopass_is_memset(const uint8_t* data, const uint8_t pattern, size_t size) int32_t picopass_app(void* p) { UNUSED(p); + picopass_migrate_from_old_folder(); + Picopass* picopass = picopass_alloc(); scene_manager_next_scene(picopass->scene_manager, PicopassSceneStart); diff --git a/applications/plugins/picopass/picopass_device.c b/applications/plugins/picopass/picopass_device.c index e3940698c25..ec0bc5af8e5 100644 --- a/applications/plugins/picopass/picopass_device.c +++ b/applications/plugins/picopass/picopass_device.c @@ -48,13 +48,9 @@ static bool picopass_device_save_file( if(use_load_path && !furi_string_empty(dev->load_path)) { // Get directory name path_extract_dirname(furi_string_get_cstr(dev->load_path), temp_str); - // Create picopass directory if necessary - if(!storage_simply_mkdir(dev->storage, furi_string_get_cstr(temp_str))) break; // Make path to file to save furi_string_cat_printf(temp_str, "/%s%s", dev_name, extension); } else { - // Create picopass directory if necessary - if(!storage_simply_mkdir(dev->storage, PICOPASS_APP_FOLDER)) break; // First remove picopass device file if it was saved furi_string_printf(temp_str, "%s/%s%s", folder, dev_name, extension); } @@ -126,10 +122,11 @@ static bool picopass_device_save_file( bool picopass_device_save(PicopassDevice* dev, const char* dev_name) { if(dev->format == PicopassDeviceSaveFormatHF) { return picopass_device_save_file( - dev, dev_name, PICOPASS_APP_FOLDER, PICOPASS_APP_EXTENSION, true); + dev, dev_name, STORAGE_APP_DATA_PATH_PREFIX, PICOPASS_APP_EXTENSION, true); } else if(dev->format == PicopassDeviceSaveFormatLF) { return picopass_device_save_file(dev, dev_name, ANY_PATH("lfrfid"), ".rfid", true); } + return false; } @@ -225,13 +222,12 @@ void picopass_device_free(PicopassDevice* picopass_dev) { bool picopass_file_select(PicopassDevice* dev) { furi_assert(dev); - // Input events and views are managed by file_browser FuriString* picopass_app_folder; - picopass_app_folder = furi_string_alloc_set(PICOPASS_APP_FOLDER); + picopass_app_folder = furi_string_alloc_set(STORAGE_APP_DATA_PATH_PREFIX); DialogsFileBrowserOptions browser_options; dialog_file_browser_set_basic_options(&browser_options, PICOPASS_APP_EXTENSION, &I_Nfc_10px); - browser_options.base_path = PICOPASS_APP_FOLDER; + browser_options.base_path = STORAGE_APP_DATA_PATH_PREFIX; bool res = dialog_file_browser_show( dev->dialogs, dev->load_path, picopass_app_folder, &browser_options); @@ -274,7 +270,7 @@ bool picopass_device_delete(PicopassDevice* dev, bool use_load_path) { furi_string_set(file_path, dev->load_path); } else { furi_string_printf( - file_path, "%s/%s%s", PICOPASS_APP_FOLDER, dev->dev_name, PICOPASS_APP_EXTENSION); + file_path, APP_DATA_PATH("%s%s"), dev->dev_name, PICOPASS_APP_EXTENSION); } if(!storage_simply_remove(dev->storage, furi_string_get_cstr(file_path))) break; deleted = true; diff --git a/applications/plugins/picopass/picopass_device.h b/applications/plugins/picopass/picopass_device.h index 99f1ceea649..d7d0977df4c 100644 --- a/applications/plugins/picopass/picopass_device.h +++ b/applications/plugins/picopass/picopass_device.h @@ -24,7 +24,6 @@ #define PICOPASS_AIA_BLOCK_INDEX 5 #define PICOPASS_PACS_CFG_BLOCK_INDEX 6 -#define PICOPASS_APP_FOLDER ANY_PATH("picopass") #define PICOPASS_APP_EXTENSION ".picopass" #define PICOPASS_APP_SHADOW_EXTENSION ".pas" @@ -81,7 +80,6 @@ typedef struct { PicopassDeviceSaveFormat format; PicopassLoadingCallback loading_cb; void* loading_cb_ctx; - } PicopassDevice; PicopassDevice* picopass_device_alloc(); diff --git a/applications/plugins/picopass/scenes/picopass_scene_save_name.c b/applications/plugins/picopass/scenes/picopass_scene_save_name.c index 59f33c79a0e..baf882b8075 100644 --- a/applications/plugins/picopass/scenes/picopass_scene_save_name.c +++ b/applications/plugins/picopass/scenes/picopass_scene_save_name.c @@ -31,12 +31,10 @@ void picopass_scene_save_name_on_enter(void* context) { dev_name_empty); FuriString* folder_path; - folder_path = furi_string_alloc(); + folder_path = furi_string_alloc_set(STORAGE_APP_DATA_PATH_PREFIX); if(furi_string_end_with(picopass->dev->load_path, PICOPASS_APP_EXTENSION)) { path_extract_dirname(furi_string_get_cstr(picopass->dev->load_path), folder_path); - } else { - furi_string_set(folder_path, PICOPASS_APP_FOLDER); } ValidatorIsFile* validator_is_file = validator_is_file_alloc_init( diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_start.c b/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_start.c index b664df687d4..38d064a4d2e 100644 --- a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_start.c +++ b/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_start.c @@ -60,7 +60,7 @@ bool spi_mem_scene_start_on_event(void* context, SceneManagerEvent event) { scene_manager_next_scene(app->scene_manager, SPIMemSceneChipDetect); success = true; } else if(event.event == SPIMemSceneStartSubmenuIndexSaved) { - furi_string_set(app->file_path, SPI_MEM_FILE_FOLDER); + furi_string_set(app->file_path, STORAGE_APP_DATA_PATH_PREFIX); scene_manager_next_scene(app->scene_manager, SPIMemSceneSelectFile); success = true; } else if(event.event == SPIMemSceneStartSubmenuIndexErase) { diff --git a/applications/plugins/spi_mem_manager/spi_mem_app.c b/applications/plugins/spi_mem_manager/spi_mem_app.c index 63531b74c0b..96c3632d056 100644 --- a/applications/plugins/spi_mem_manager/spi_mem_app.c +++ b/applications/plugins/spi_mem_manager/spi_mem_app.c @@ -16,9 +16,9 @@ static bool spi_mem_back_event_callback(void* context) { } SPIMemApp* spi_mem_alloc(void) { - SPIMemApp* instance = malloc(sizeof(SPIMemApp)); + SPIMemApp* instance = malloc(sizeof(SPIMemApp)); //-V799 - instance->file_path = furi_string_alloc(); + instance->file_path = furi_string_alloc_set(STORAGE_APP_DATA_PATH_PREFIX); instance->gui = furi_record_open(RECORD_GUI); instance->notifications = furi_record_open(RECORD_NOTIFICATION); instance->view_dispatcher = view_dispatcher_alloc(); @@ -37,7 +37,8 @@ SPIMemApp* spi_mem_alloc(void) { instance->text_input = text_input_alloc(); instance->mode = SPIMemModeUnknown; - furi_string_set(instance->file_path, SPI_MEM_FILE_FOLDER); + // Migrate data from old sd-card folder + storage_common_migrate(instance->storage, EXT_PATH("spimem"), STORAGE_APP_DATA_PATH_PREFIX); view_dispatcher_enable_queue(instance->view_dispatcher); view_dispatcher_set_event_callback_context(instance->view_dispatcher, instance); @@ -70,7 +71,7 @@ SPIMemApp* spi_mem_alloc(void) { furi_hal_spi_bus_handle_init(&furi_hal_spi_bus_handle_external); scene_manager_next_scene(instance->scene_manager, SPIMemSceneStart); return instance; -} +} //-V773 void spi_mem_free(SPIMemApp* instance) { view_dispatcher_remove_view(instance->view_dispatcher, SPIMemViewSubmenu); @@ -105,7 +106,6 @@ void spi_mem_free(SPIMemApp* instance) { int32_t spi_mem_app(void* p) { UNUSED(p); SPIMemApp* instance = spi_mem_alloc(); - spi_mem_file_create_folder(instance); view_dispatcher_run(instance->view_dispatcher); spi_mem_free(instance); return 0; diff --git a/applications/plugins/spi_mem_manager/spi_mem_app_i.h b/applications/plugins/spi_mem_manager/spi_mem_app_i.h index 4ce0561758e..285ca66d2f3 100644 --- a/applications/plugins/spi_mem_manager/spi_mem_app_i.h +++ b/applications/plugins/spi_mem_manager/spi_mem_app_i.h @@ -24,7 +24,6 @@ #define TAG "SPIMem" #define SPI_MEM_FILE_EXTENSION ".bin" -#define SPI_MEM_FILE_FOLDER EXT_PATH("spimem") #define SPI_MEM_FILE_NAME_SIZE 100 #define SPI_MEM_TEXT_BUFFER_SIZE 128 diff --git a/applications/plugins/spi_mem_manager/spi_mem_files.c b/applications/plugins/spi_mem_manager/spi_mem_files.c index a7374da191f..9b787bd7f9c 100644 --- a/applications/plugins/spi_mem_manager/spi_mem_files.c +++ b/applications/plugins/spi_mem_manager/spi_mem_files.c @@ -1,11 +1,5 @@ #include "spi_mem_app_i.h" -void spi_mem_file_create_folder(SPIMemApp* app) { - if(!storage_simply_mkdir(app->storage, SPI_MEM_FILE_FOLDER)) { - dialog_message_show_storage_error(app->dialogs, "Cannot create\napp folder"); - } -} - bool spi_mem_file_delete(SPIMemApp* app) { return (storage_simply_remove(app->storage, furi_string_get_cstr(app->file_path))); } @@ -13,7 +7,7 @@ bool spi_mem_file_delete(SPIMemApp* app) { bool spi_mem_file_select(SPIMemApp* app) { DialogsFileBrowserOptions browser_options; dialog_file_browser_set_basic_options(&browser_options, SPI_MEM_FILE_EXTENSION, &I_Dip8_10px); - browser_options.base_path = SPI_MEM_FILE_FOLDER; + browser_options.base_path = STORAGE_APP_DATA_PATH_PREFIX; bool success = dialog_file_browser_show(app->dialogs, app->file_path, app->file_path, &browser_options); return success; diff --git a/applications/plugins/spi_mem_manager/spi_mem_files.h b/applications/plugins/spi_mem_manager/spi_mem_files.h index 0e735d95192..6a529d3279f 100644 --- a/applications/plugins/spi_mem_manager/spi_mem_files.h +++ b/applications/plugins/spi_mem_manager/spi_mem_files.h @@ -1,7 +1,6 @@ #pragma once #include "spi_mem_app.h" -void spi_mem_file_create_folder(SPIMemApp* app); bool spi_mem_file_select(SPIMemApp* app); bool spi_mem_file_create(SPIMemApp* app, const char* file_name); bool spi_mem_file_delete(SPIMemApp* app); diff --git a/applications/services/applications.h b/applications/services/applications.h index acbfea31252..871e9af54a1 100644 --- a/applications/services/applications.h +++ b/applications/services/applications.h @@ -11,6 +11,7 @@ typedef enum { typedef struct { const FuriThreadCallback app; const char* name; + const char* appid; const size_t stack_size; const Icon* icon; const FlipperApplicationFlag flags; diff --git a/applications/services/bt/bt_service/bt_api.c b/applications/services/bt/bt_service/bt_api.c index e3cf78cc792..2f56b50a39a 100644 --- a/applications/services/bt/bt_service/bt_api.c +++ b/applications/services/bt/bt_service/bt_api.c @@ -45,7 +45,14 @@ void bt_keys_storage_set_storage_path(Bt* bt, const char* keys_storage_path) { furi_assert(bt->keys_storage); furi_assert(keys_storage_path); - bt_keys_storage_set_file_path(bt->keys_storage, keys_storage_path); + Storage* storage = furi_record_open(RECORD_STORAGE); + FuriString* path = furi_string_alloc_set(keys_storage_path); + storage_common_resolve_path_and_ensure_app_directory(storage, path); + + bt_keys_storage_set_file_path(bt->keys_storage, furi_string_get_cstr(path)); + + furi_string_free(path); + furi_record_close(RECORD_STORAGE); } void bt_keys_storage_set_default_path(Bt* bt) { diff --git a/applications/services/cli/cli_commands.c b/applications/services/cli/cli_commands.c index ca9d8b98a69..0f042f6c48d 100644 --- a/applications/services/cli/cli_commands.c +++ b/applications/services/cli/cli_commands.c @@ -372,11 +372,18 @@ void cli_command_ps(Cli* cli, FuriString* args, void* context) { FuriThreadId threads_ids[threads_num_max]; uint8_t thread_num = furi_thread_enumerate(threads_ids, threads_num_max); printf( - "%-20s %-14s %-8s %-8s %s\r\n", "Name", "Stack start", "Heap", "Stack", "Stack min free"); + "%-20s %-20s %-14s %-8s %-8s %s\r\n", + "AppID", + "Name", + "Stack start", + "Heap", + "Stack", + "Stack min free"); for(uint8_t i = 0; i < thread_num; i++) { TaskControlBlock* tcb = (TaskControlBlock*)threads_ids[i]; printf( - "%-20s 0x%-12lx %-8zu %-8lu %-8lu\r\n", + "%-20s %-20s 0x%-12lx %-8zu %-8lu %-8lu\r\n", + furi_thread_get_appid(threads_ids[i]), furi_thread_get_name(threads_ids[i]), (uint32_t)tcb->pxStack, memmgr_heap_get_thread_memory(threads_ids[i]), diff --git a/applications/services/dialogs/dialogs_api.c b/applications/services/dialogs/dialogs_api.c index ca2435b9b34..4723a1f91a1 100644 --- a/applications/services/dialogs/dialogs_api.c +++ b/applications/services/dialogs/dialogs_api.c @@ -2,6 +2,7 @@ #include "dialogs_i.h" #include #include +#include /****************** File browser ******************/ @@ -13,6 +14,22 @@ bool dialog_file_browser_show( FuriApiLock lock = api_lock_alloc_locked(); furi_check(lock != NULL); + Storage* storage = furi_record_open(RECORD_STORAGE); + FuriString* base_path = furi_string_alloc(); + + if(options && options->base_path) { + furi_string_set(base_path, options->base_path); + storage_common_resolve_path_and_ensure_app_directory(storage, base_path); + } + + if(result_path) { + storage_common_resolve_path_and_ensure_app_directory(storage, result_path); + } + + if(path) { + storage_common_resolve_path_and_ensure_app_directory(storage, path); + } + DialogsAppData data = { .file_browser = { .extension = options ? options->extension : "", @@ -24,7 +41,7 @@ bool dialog_file_browser_show( .preselected_filename = path, .item_callback = options ? options->item_loader_callback : NULL, .item_callback_context = options ? options->item_loader_context : NULL, - .base_path = options ? options->base_path : NULL, + .base_path = furi_string_get_cstr(base_path), }}; DialogsAppReturn return_data; @@ -39,6 +56,9 @@ bool dialog_file_browser_show( furi_message_queue_put(context->message_queue, &message, FuriWaitForever) == FuriStatusOk); api_lock_wait_unlock_and_free(lock); + furi_record_close(RECORD_STORAGE); + furi_string_free(base_path); + return return_data.bool_value; } diff --git a/applications/services/gui/modules/file_browser_worker.c b/applications/services/gui/modules/file_browser_worker.c index 80c4f4c43a8..857acbbaa41 100644 --- a/applications/services/gui/modules/file_browser_worker.c +++ b/applications/services/gui/modules/file_browser_worker.c @@ -60,7 +60,7 @@ static bool browser_path_is_file(FuriString* path) { FileInfo file_info; Storage* storage = furi_record_open(RECORD_STORAGE); if(storage_common_stat(storage, furi_string_get_cstr(path), &file_info) == FSE_OK) { - if((file_info.flags & FSF_DIRECTORY) == 0) { + if(!file_info_is_dir(&file_info)) { state = true; } } @@ -119,7 +119,7 @@ static bool browser_folder_check_and_switch(FuriString* path) { while(1) { // Check if folder is existing and navigate back if not if(storage_common_stat(storage, furi_string_get_cstr(path), &file_info) == FSE_OK) { - if(file_info.flags & FSF_DIRECTORY) { + if(file_info_is_dir(&file_info)) { break; } } @@ -161,7 +161,7 @@ static bool browser_folder_init( if((storage_file_get_error(directory) == FSE_OK) && (name_temp[0] != '\0')) { total_files_cnt++; furi_string_set(name_str, name_temp); - if(browser_filter_by_name(browser, name_str, (file_info.flags & FSF_DIRECTORY))) { + if(browser_filter_by_name(browser, name_str, file_info_is_dir(&file_info))) { if(!furi_string_empty(filename)) { if(furi_string_cmp(name_str, filename) == 0) { *file_idx = *item_cnt; @@ -214,7 +214,7 @@ static bool } if(storage_file_get_error(directory) == FSE_OK) { furi_string_set(name_str, name_temp); - if(browser_filter_by_name(browser, name_str, (file_info.flags & FSF_DIRECTORY))) { + if(browser_filter_by_name(browser, name_str, file_info_is_dir(&file_info))) { items_cnt++; } } else { @@ -236,11 +236,11 @@ static bool } if(storage_file_get_error(directory) == FSE_OK) { furi_string_set(name_str, name_temp); - if(browser_filter_by_name(browser, name_str, (file_info.flags & FSF_DIRECTORY))) { + if(browser_filter_by_name(browser, name_str, file_info_is_dir(&file_info))) { furi_string_printf(name_str, "%s/%s", furi_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); + browser->cb_ctx, name_str, file_info_is_dir(&file_info), false); } items_cnt++; } diff --git a/applications/services/loader/loader.c b/applications/services/loader/loader.c index caaf9f11b5d..5f2d8a2e739 100644 --- a/applications/services/loader/loader.c +++ b/applications/services/loader/loader.c @@ -29,6 +29,8 @@ static bool } furi_thread_set_name(loader_instance->application_thread, loader_instance->application->name); + furi_thread_set_appid( + loader_instance->application_thread, loader_instance->application->appid); furi_thread_set_stack_size( loader_instance->application_thread, loader_instance->application->stack_size); furi_thread_set_context( diff --git a/applications/services/rpc/rpc_storage.c b/applications/services/rpc/rpc_storage.c index c4493cc74e3..c3a4a047049 100644 --- a/applications/services/rpc/rpc_storage.c +++ b/applications/services/rpc/rpc_storage.c @@ -201,7 +201,7 @@ static void rpc_system_storage_stat_process(const PB_Main* request, void* contex if(error == FSE_OK) { response->which_content = PB_Main_storage_stat_response_tag; response->content.storage_stat_response.has_file = true; - response->content.storage_stat_response.file.type = (fileinfo.flags & FSF_DIRECTORY) ? + response->content.storage_stat_response.file.type = file_info_is_dir(&fileinfo) ? PB_Storage_File_FileType_DIR : PB_Storage_File_FileType_FILE; response->content.storage_stat_response.file.size = fileinfo.size; @@ -291,9 +291,8 @@ static void rpc_system_storage_list_process(const PB_Main* request, void* contex rpc_send_and_release(session, &response); i = 0; } - list->file[i].type = (fileinfo.flags & FSF_DIRECTORY) ? - PB_Storage_File_FileType_DIR : - PB_Storage_File_FileType_FILE; + list->file[i].type = file_info_is_dir(&fileinfo) ? PB_Storage_File_FileType_DIR : + PB_Storage_File_FileType_FILE; list->file[i].size = fileinfo.size; list->file[i].data = NULL; list->file[i].name = name; @@ -458,7 +457,7 @@ static bool rpc_system_storage_is_dir_is_empty(Storage* fs_api, const char* path FileInfo fileinfo; bool is_dir_is_empty = true; FS_Error error = storage_common_stat(fs_api, path, &fileinfo); - if((error == FSE_OK) && (fileinfo.flags & FSF_DIRECTORY)) { + if((error == FSE_OK) && file_info_is_dir(&fileinfo)) { File* dir = storage_file_alloc(fs_api); if(storage_dir_open(dir, path)) { char* name = malloc(MAX_NAME_LENGTH); diff --git a/applications/services/storage/filesystem_api.c b/applications/services/storage/filesystem_api.c index b979967acb5..30b20ede4d7 100644 --- a/applications/services/storage/filesystem_api.c +++ b/applications/services/storage/filesystem_api.c @@ -36,3 +36,7 @@ const char* filesystem_api_error_get_desc(FS_Error error_id) { } return result; } + +bool file_info_is_dir(const FileInfo* file_info) { + return (file_info->flags & FSF_DIRECTORY); +} \ No newline at end of file diff --git a/applications/services/storage/filesystem_api_defines.h b/applications/services/storage/filesystem_api_defines.h index b73e6eb33dc..cd24b88253f 100644 --- a/applications/services/storage/filesystem_api_defines.h +++ b/applications/services/storage/filesystem_api_defines.h @@ -1,5 +1,6 @@ #pragma once #include +#include #ifdef __cplusplus extern "C" { @@ -40,10 +41,10 @@ typedef enum { FSF_DIRECTORY = (1 << 0), /**< Directory */ } FS_Flags; -/** Structure that hold file index and returned api errors */ +/** Structure that hold file index and returned api errors */ typedef struct File File; -/** Structure that hold file info */ +/** Structure that hold file info */ typedef struct { uint8_t flags; /**< flags from FS_Flags enum */ uint64_t size; /**< file size */ @@ -55,6 +56,12 @@ typedef struct { */ const char* filesystem_api_error_get_desc(FS_Error error_id); +/** Checks if file info is directory + * @param file_info file info pointer + * @return bool is directory + */ +bool file_info_is_dir(const FileInfo* file_info); + #ifdef __cplusplus } #endif diff --git a/applications/services/storage/storage.h b/applications/services/storage/storage.h index e093cbe0f46..e35b8164c2c 100644 --- a/applications/services/storage/storage.h +++ b/applications/services/storage/storage.h @@ -10,10 +10,12 @@ extern "C" { #define STORAGE_INT_PATH_PREFIX "/int" #define STORAGE_EXT_PATH_PREFIX "/ext" #define STORAGE_ANY_PATH_PREFIX "/any" +#define STORAGE_APP_DATA_PATH_PREFIX "/app" #define INT_PATH(path) STORAGE_INT_PATH_PREFIX "/" path #define EXT_PATH(path) STORAGE_EXT_PATH_PREFIX "/" path #define ANY_PATH(path) STORAGE_ANY_PATH_PREFIX "/" path +#define APP_DATA_PATH(path) STORAGE_APP_DATA_PATH_PREFIX "/" path #define RECORD_STORAGE "storage" @@ -175,6 +177,15 @@ bool storage_dir_read(File* file, FileInfo* fileinfo, char* name, uint16_t name_ */ bool storage_dir_rewind(File* file); +/** + * @brief Check that dir exists + * + * @param storage + * @param path + * @return bool + */ +bool storage_dir_exists(Storage* storage, const char* path); + /******************* Common Functions *******************/ /** Retrieves unix timestamp of last access @@ -246,6 +257,36 @@ FS_Error storage_common_fs_info( uint64_t* total_space, uint64_t* free_space); +/** + * @brief Parse aliases in path and replace them with real path + * Also will create special folders if they are not exist + * + * @param storage + * @param path + * @return bool + */ +void storage_common_resolve_path_and_ensure_app_directory(Storage* storage, FuriString* path); + +/** + * @brief Move content of one folder to another, with rename of all conflicting files. + * Source folder will be deleted if the migration is successful. + * + * @param storage + * @param source + * @param dest + * @return FS_Error + */ +FS_Error storage_common_migrate(Storage* storage, const char* source, const char* dest); + +/** + * @brief Check that file or dir exists + * + * @param storage + * @param path + * @return bool + */ +bool storage_common_exists(Storage* storage, const char* path); + /******************* Error Functions *******************/ /** Retrieves the error text from the error id diff --git a/applications/services/storage/storage_cli.c b/applications/services/storage/storage_cli.c index ff205569772..8e2dcdbbbd3 100644 --- a/applications/services/storage/storage_cli.c +++ b/applications/services/storage/storage_cli.c @@ -131,7 +131,7 @@ static void storage_cli_list(Cli* cli, FuriString* path) { while(storage_dir_read(file, &fileinfo, name, MAX_NAME_LENGTH)) { read_done = true; - if(fileinfo.flags & FSF_DIRECTORY) { + if(file_info_is_dir(&fileinfo)) { printf("\t[D] %s\r\n", name); } else { printf("\t[F] %s %lub\r\n", name, (uint32_t)(fileinfo.size)); @@ -169,7 +169,7 @@ static void storage_cli_tree(Cli* cli, FuriString* path) { while(dir_walk_read(dir_walk, name, &fileinfo) == DirWalkOK) { read_done = true; - if(fileinfo.flags & FSF_DIRECTORY) { + if(file_info_is_dir(&fileinfo)) { printf("\t[D] %s\r\n", furi_string_get_cstr(name)); } else { printf( @@ -383,7 +383,7 @@ static void storage_cli_stat(Cli* cli, FuriString* path) { FS_Error error = storage_common_stat(api, furi_string_get_cstr(path), &fileinfo); if(error == FSE_OK) { - if(fileinfo.flags & FSF_DIRECTORY) { + if(file_info_is_dir(&fileinfo)) { printf("Directory\r\n"); } else { printf("File, size: %lub\r\n", (uint32_t)(fileinfo.size)); diff --git a/applications/services/storage/storage_external_api.c b/applications/services/storage/storage_external_api.c index c5dfd533eec..8d8220f816b 100644 --- a/applications/services/storage/storage_external_api.c +++ b/applications/services/storage/storage_external_api.c @@ -39,12 +39,6 @@ .file = file, \ }}; -#define S_API_DATA_PATH \ - SAData data = { \ - .path = { \ - .path = path, \ - }}; - #define S_RETURN_BOOL (return_data.bool_value); #define S_RETURN_UINT16 (return_data.uint16_value); #define S_RETURN_UINT64 (return_data.uint64_value); @@ -70,6 +64,7 @@ static bool storage_file_open_internal( .path = path, .access_mode = access_mode, .open_mode = open_mode, + .thread_id = furi_thread_get_current_id(), }}; file->type = FileTypeOpenFile; @@ -249,7 +244,7 @@ bool storage_file_exists(Storage* storage, const char* path) { FileInfo fileinfo; FS_Error error = storage_common_stat(storage, path, &fileinfo); - if(error == FSE_OK && !(fileinfo.flags & FSF_DIRECTORY)) { + if(error == FSE_OK && !file_info_is_dir(&fileinfo)) { exist = true; } @@ -266,6 +261,7 @@ static bool storage_dir_open_internal(File* file, const char* path) { .dopen = { .file = file, .path = path, + .thread_id = furi_thread_get_current_id(), }}; file->type = FileTypeOpenDir; @@ -349,12 +345,28 @@ bool storage_dir_rewind(File* file) { return S_RETURN_BOOL; } +bool storage_dir_exists(Storage* storage, const char* path) { + bool exist = false; + FileInfo fileinfo; + FS_Error error = storage_common_stat(storage, path, &fileinfo); + + if(error == FSE_OK && file_info_is_dir(&fileinfo)) { + exist = true; + } + + return exist; +} /****************** COMMON ******************/ FS_Error storage_common_timestamp(Storage* storage, const char* path, uint32_t* timestamp) { S_API_PROLOGUE; - SAData data = {.ctimestamp = {.path = path, .timestamp = timestamp}}; + SAData data = { + .ctimestamp = { + .path = path, + .timestamp = timestamp, + .thread_id = furi_thread_get_current_id(), + }}; S_API_MESSAGE(StorageCommandCommonTimestamp); S_API_EPILOGUE; @@ -363,8 +375,12 @@ FS_Error storage_common_timestamp(Storage* storage, const char* path, uint32_t* FS_Error storage_common_stat(Storage* storage, const char* path, FileInfo* fileinfo) { S_API_PROLOGUE; - - SAData data = {.cstat = {.path = path, .fileinfo = fileinfo}}; + SAData data = { + .cstat = { + .path = path, + .fileinfo = fileinfo, + .thread_id = furi_thread_get_current_id(), + }}; S_API_MESSAGE(StorageCommandCommonStat); S_API_EPILOGUE; @@ -373,7 +389,12 @@ FS_Error storage_common_stat(Storage* storage, const char* path, FileInfo* filei FS_Error storage_common_remove(Storage* storage, const char* path) { S_API_PROLOGUE; - S_API_DATA_PATH; + SAData data = { + .path = { + .path = path, + .thread_id = furi_thread_get_current_id(), + }}; + S_API_MESSAGE(StorageCommandCommonRemove); S_API_EPILOGUE; return S_RETURN_ERROR; @@ -423,7 +444,7 @@ static FS_Error furi_string_right(path, strlen(old_path)); furi_string_printf(tmp_new_path, "%s%s", new_path, furi_string_get_cstr(path)); - if(fileinfo.flags & FSF_DIRECTORY) { + if(file_info_is_dir(&fileinfo)) { error = storage_common_mkdir(storage, furi_string_get_cstr(tmp_new_path)); } else { error = storage_common_copy( @@ -452,7 +473,7 @@ FS_Error storage_common_copy(Storage* storage, const char* old_path, const char* error = storage_common_stat(storage, old_path, &fileinfo); if(error == FSE_OK) { - if(fileinfo.flags & FSF_DIRECTORY) { + if(file_info_is_dir(&fileinfo)) { error = storage_copy_recursive(storage, old_path, new_path); } else { Stream* stream_from = file_stream_alloc(storage); @@ -479,7 +500,7 @@ FS_Error storage_common_copy(Storage* storage, const char* old_path, const char* static FS_Error storage_merge_recursive(Storage* storage, const char* old_path, const char* new_path) { - FS_Error error = storage_common_mkdir(storage, new_path); + FS_Error error = FSE_OK; DirWalk* dir_walk = dir_walk_alloc(storage); FuriString *path, *file_basename, *tmp_new_path; FileInfo fileinfo; @@ -488,7 +509,7 @@ static FS_Error tmp_new_path = furi_string_alloc(); do { - if((error != FSE_OK) && (error != FSE_EXIST)) break; + if(!storage_simply_mkdir(storage, new_path)) break; dir_walk_set_recursive(dir_walk, false); if(!dir_walk_open(dir_walk, old_path)) { @@ -508,13 +529,13 @@ static FS_Error path_extract_basename(furi_string_get_cstr(path), file_basename); path_concat(new_path, furi_string_get_cstr(file_basename), tmp_new_path); - if(fileinfo.flags & FSF_DIRECTORY) { + if(file_info_is_dir(&fileinfo)) { if(storage_common_stat( storage, furi_string_get_cstr(tmp_new_path), &fileinfo) == FSE_OK) { - if(fileinfo.flags & FSF_DIRECTORY) { + if(file_info_is_dir(&fileinfo)) { error = storage_common_mkdir(storage, furi_string_get_cstr(tmp_new_path)); - if(error != FSE_OK) { + if(error != FSE_OK && error != FSE_EXIST) { break; } } @@ -548,7 +569,7 @@ FS_Error storage_common_merge(Storage* storage, const char* old_path, const char error = storage_common_stat(storage, old_path, &fileinfo); if(error == FSE_OK) { - if(fileinfo.flags & FSF_DIRECTORY) { + if(file_info_is_dir(&fileinfo)) { error = storage_merge_recursive(storage, old_path, new_path); } else { error = storage_common_stat(storage, new_path, &fileinfo); @@ -556,7 +577,7 @@ FS_Error storage_common_merge(Storage* storage, const char* old_path, const char furi_string_set(new_path_next, new_path); FuriString* dir_path; FuriString* filename; - char extension[MAX_EXT_LEN]; + char extension[MAX_EXT_LEN] = {0}; dir_path = furi_string_alloc(); filename = furi_string_alloc(); @@ -608,7 +629,12 @@ FS_Error storage_common_merge(Storage* storage, const char* old_path, const char FS_Error storage_common_mkdir(Storage* storage, const char* path) { S_API_PROLOGUE; - S_API_DATA_PATH; + SAData data = { + .path = { + .path = path, + .thread_id = furi_thread_get_current_id(), + }}; + S_API_MESSAGE(StorageCommandCommonMkDir); S_API_EPILOGUE; return S_RETURN_ERROR; @@ -626,6 +652,7 @@ FS_Error storage_common_fs_info( .fs_path = fs_path, .total_space = total_space, .free_space = free_space, + .thread_id = furi_thread_get_current_id(), }}; S_API_MESSAGE(StorageCommandCommonFSInfo); @@ -633,6 +660,38 @@ FS_Error storage_common_fs_info( return S_RETURN_ERROR; } +void storage_common_resolve_path_and_ensure_app_directory(Storage* storage, FuriString* path) { + S_API_PROLOGUE; + + SAData data = { + .cresolvepath = { + .path = path, + .thread_id = furi_thread_get_current_id(), + }}; + + S_API_MESSAGE(StorageCommandCommonResolvePath); + S_API_EPILOGUE; +} + +FS_Error storage_common_migrate(Storage* storage, const char* source, const char* dest) { + if(!storage_common_exists(storage, source)) { + return FSE_OK; + } + + FS_Error error = storage_common_merge(storage, source, dest); + + if(error == FSE_OK) { + storage_simply_remove_recursive(storage, source); + } + + return error; +} + +bool storage_common_exists(Storage* storage, const char* path) { + FileInfo file_info; + return storage_common_stat(storage, path, &file_info) == FSE_OK; +} + /****************** ERROR ******************/ const char* storage_error_get_desc(FS_Error error_id) { @@ -750,7 +809,7 @@ bool storage_simply_remove_recursive(Storage* storage, const char* path) { } while(storage_dir_read(dir, &fileinfo, name, MAX_NAME_LENGTH)) { - if(fileinfo.flags & FSF_DIRECTORY) { + if(file_info_is_dir(&fileinfo)) { furi_string_cat_printf(cur_dir, "/%s", name); go_deeper = true; break; diff --git a/applications/services/storage/storage_glue.c b/applications/services/storage/storage_glue.c index cccf4046adb..5dabfa38604 100644 --- a/applications/services/storage/storage_glue.c +++ b/applications/services/storage/storage_glue.c @@ -5,21 +5,18 @@ void storage_file_init(StorageFile* obj) { obj->file = NULL; - obj->type = ST_ERROR; obj->file_data = NULL; obj->path = furi_string_alloc(); } void storage_file_init_set(StorageFile* obj, const StorageFile* src) { obj->file = src->file; - obj->type = src->type; obj->file_data = src->file_data; obj->path = furi_string_alloc_set(src->path); } void storage_file_set(StorageFile* obj, const StorageFile* src) { //-V524 obj->file = src->file; - obj->type = src->type; obj->file_data = src->file_data; furi_string_set(obj->path, src->path); } @@ -150,16 +147,10 @@ void* storage_get_storage_file_data(const File* file, StorageData* storage) { return founded_file->file_data; } -void storage_push_storage_file( - File* file, - FuriString* path, - StorageType type, - StorageData* storage) { +void storage_push_storage_file(File* file, FuriString* path, StorageData* storage) { StorageFile* storage_file = StorageFileList_push_new(storage->files); - file->file_id = (uint32_t)storage_file; storage_file->file = file; - storage_file->type = type; furi_string_set(storage_file->path, path); } diff --git a/applications/services/storage/storage_glue.h b/applications/services/storage/storage_glue.h index 501c26abccc..bf0a1c69eda 100644 --- a/applications/services/storage/storage_glue.h +++ b/applications/services/storage/storage_glue.h @@ -18,7 +18,6 @@ typedef struct { typedef struct { File* file; - StorageType type; void* file_data; FuriString* path; } StorageFile; @@ -66,11 +65,7 @@ bool storage_path_already_open(FuriString* path, StorageFileList_t files); void storage_set_storage_file_data(const File* file, void* file_data, StorageData* storage); void* storage_get_storage_file_data(const File* file, StorageData* storage); -void storage_push_storage_file( - File* file, - FuriString* path, - StorageType type, - StorageData* storage); +void storage_push_storage_file(File* file, FuriString* path, StorageData* storage); bool storage_pop_storage_file(File* file, StorageData* storage); #ifdef __cplusplus diff --git a/applications/services/storage/storage_i.h b/applications/services/storage/storage_i.h index 406fc921ef9..85df5d92657 100644 --- a/applications/services/storage/storage_i.h +++ b/applications/services/storage/storage_i.h @@ -12,6 +12,8 @@ extern "C" { #define STORAGE_COUNT (ST_INT + 1) +#define APPS_DATA_PATH EXT_PATH("apps_data") + typedef struct { ViewPort* view_port; bool enabled; diff --git a/applications/services/storage/storage_message.h b/applications/services/storage/storage_message.h index 3edb1018e10..9e13bf83d58 100644 --- a/applications/services/storage/storage_message.h +++ b/applications/services/storage/storage_message.h @@ -11,6 +11,7 @@ typedef struct { const char* path; FS_AccessMode access_mode; FS_OpenMode open_mode; + FuriThreadId thread_id; } SADataFOpen; typedef struct { @@ -34,6 +35,7 @@ typedef struct { typedef struct { File* file; const char* path; + FuriThreadId thread_id; } SADataDOpen; typedef struct { @@ -46,25 +48,34 @@ typedef struct { typedef struct { const char* path; uint32_t* timestamp; + FuriThreadId thread_id; } SADataCTimestamp; typedef struct { const char* path; FileInfo* fileinfo; + FuriThreadId thread_id; } SADataCStat; typedef struct { const char* fs_path; uint64_t* total_space; uint64_t* free_space; + FuriThreadId thread_id; } SADataCFSInfo; +typedef struct { + FuriString* path; + FuriThreadId thread_id; +} SADataCResolvePath; + typedef struct { uint32_t id; } SADataError; typedef struct { const char* path; + FuriThreadId thread_id; } SADataPath; typedef struct { @@ -87,6 +98,7 @@ typedef union { SADataCTimestamp ctimestamp; SADataCStat cstat; SADataCFSInfo cfsinfo; + SADataCResolvePath cresolvepath; SADataError error; @@ -128,6 +140,7 @@ typedef enum { StorageCommandSDUnmount, StorageCommandSDInfo, StorageCommandSDStatus, + StorageCommandCommonResolvePath, } StorageCommand; typedef struct { diff --git a/applications/services/storage/storage_processing.c b/applications/services/storage/storage_processing.c index b1ea5d29b72..cab1edff58b 100644 --- a/applications/services/storage/storage_processing.c +++ b/applications/services/storage/storage_processing.c @@ -4,17 +4,11 @@ #define FS_CALL(_storage, _fn) ret = _storage->fs_api->_fn; -static StorageData* storage_get_storage_by_type(Storage* app, StorageType type) { - furi_check(type == ST_EXT || type == ST_INT); - StorageData* storage = &app->storage[type]; - return storage; -} - -static bool storage_type_is_not_valid(StorageType type) { +static bool storage_type_is_valid(StorageType type) { #ifdef FURI_RAM_EXEC - return type != ST_EXT; + return type == ST_EXT; #else - return type >= ST_ERROR; + return type < ST_ERROR; #endif } @@ -30,27 +24,23 @@ static StorageData* get_storage_by_file(File* file, StorageData* storages) { return storage_data; } -static const char* remove_vfs(const char* path) { - return path + MIN(4u, strlen(path)); +static const char* cstr_path_without_vfs_prefix(FuriString* path) { + const char* path_cstr = furi_string_get_cstr(path); + return path_cstr + MIN(4u, strlen(path_cstr)); } -static StorageType storage_get_type_by_path(Storage* app, const char* path) { +static StorageType storage_get_type_by_path(FuriString* path) { StorageType type = ST_ERROR; - if(memcmp(path, STORAGE_EXT_PATH_PREFIX, strlen(STORAGE_EXT_PATH_PREFIX)) == 0) { + const char* path_cstr = furi_string_get_cstr(path); + + if(memcmp(path_cstr, STORAGE_EXT_PATH_PREFIX, strlen(STORAGE_EXT_PATH_PREFIX)) == 0) { type = ST_EXT; - } else if(memcmp(path, STORAGE_INT_PATH_PREFIX, strlen(STORAGE_INT_PATH_PREFIX)) == 0) { + } else if(memcmp(path_cstr, STORAGE_INT_PATH_PREFIX, strlen(STORAGE_INT_PATH_PREFIX)) == 0) { type = ST_INT; - } else if(memcmp(path, STORAGE_ANY_PATH_PREFIX, strlen(STORAGE_ANY_PATH_PREFIX)) == 0) { + } else if(memcmp(path_cstr, STORAGE_ANY_PATH_PREFIX, strlen(STORAGE_ANY_PATH_PREFIX)) == 0) { type = ST_ANY; } - if(type == ST_ANY) { - type = ST_INT; - if(storage_data_status(&app->storage[ST_EXT]) == StorageStatusOK) { - type = ST_EXT; - } - } - return type; } @@ -71,38 +61,51 @@ static void storage_path_change_to_real_storage(FuriString* path, StorageType re } } +FS_Error storage_get_data(Storage* app, FuriString* path, StorageData** storage) { + StorageType type = storage_get_type_by_path(path); + + if(storage_type_is_valid(type)) { + if(type == ST_ANY) { + type = ST_INT; + if(storage_data_status(&app->storage[ST_EXT]) == StorageStatusOK) { + type = ST_EXT; + } + storage_path_change_to_real_storage(path, type); + } + + furi_assert(type == ST_EXT || type == ST_INT); + *storage = &app->storage[type]; + + return FSE_OK; + } else { + return FSE_INVALID_NAME; + } +} + /******************* File Functions *******************/ bool storage_process_file_open( Storage* app, File* file, - const char* path, + FuriString* path, FS_AccessMode access_mode, FS_OpenMode open_mode) { bool ret = false; - StorageType type = storage_get_type_by_path(app, path); StorageData* storage; - file->error_id = FSE_OK; - - if(storage_type_is_not_valid(type)) { - file->error_id = FSE_INVALID_NAME; - } else { - storage = storage_get_storage_by_type(app, type); - FuriString* real_path; - real_path = furi_string_alloc_set(path); - storage_path_change_to_real_storage(real_path, type); + file->error_id = storage_get_data(app, path, &storage); - if(storage_path_already_open(real_path, storage->files)) { + if(file->error_id == FSE_OK) { + if(storage_path_already_open(path, storage->files)) { file->error_id = FSE_ALREADY_OPEN; } else { if(access_mode & FSAM_WRITE) { storage_data_timestamp(storage); } - storage_push_storage_file(file, real_path, type, storage); - FS_CALL(storage, file.open(storage, file, remove_vfs(path), access_mode, open_mode)); - } + storage_push_storage_file(file, path, storage); - furi_string_free(real_path); + const char* path_cstr_no_vfs = cstr_path_without_vfs_prefix(path); + FS_CALL(storage, file.open(storage, file, path_cstr_no_vfs, access_mode, open_mode)); + } } return ret; @@ -243,27 +246,18 @@ static bool storage_process_file_eof(Storage* app, File* file) { /******************* Dir Functions *******************/ -bool storage_process_dir_open(Storage* app, File* file, const char* path) { +bool storage_process_dir_open(Storage* app, File* file, FuriString* path) { bool ret = false; - StorageType type = storage_get_type_by_path(app, path); StorageData* storage; - file->error_id = FSE_OK; - - if(storage_type_is_not_valid(type)) { - file->error_id = FSE_INVALID_NAME; - } else { - storage = storage_get_storage_by_type(app, type); - FuriString* real_path; - real_path = furi_string_alloc_set(path); - storage_path_change_to_real_storage(real_path, type); + file->error_id = storage_get_data(app, path, &storage); - if(storage_path_already_open(real_path, storage->files)) { + if(file->error_id == FSE_OK) { + if(storage_path_already_open(path, storage->files)) { file->error_id = FSE_ALREADY_OPEN; } else { - storage_push_storage_file(file, real_path, type, storage); - FS_CALL(storage, dir.open(storage, file, remove_vfs(path))); + storage_push_storage_file(file, path, storage); + FS_CALL(storage, dir.open(storage, file, cstr_path_without_vfs_prefix(path))); } - furi_string_free(real_path); } return ret; @@ -320,73 +314,52 @@ bool storage_process_dir_rewind(Storage* app, File* file) { /******************* Common FS Functions *******************/ static FS_Error - storage_process_common_timestamp(Storage* app, const char* path, uint32_t* timestamp) { - FS_Error ret = FSE_OK; - StorageType type = storage_get_type_by_path(app, path); + storage_process_common_timestamp(Storage* app, FuriString* path, uint32_t* timestamp) { + StorageData* storage; + FS_Error ret = storage_get_data(app, path, &storage); - if(storage_type_is_not_valid(type)) { - ret = FSE_INVALID_NAME; - } else { - StorageData* storage = storage_get_storage_by_type(app, type); + if(ret == FSE_OK) { *timestamp = storage_data_get_timestamp(storage); } return ret; } -static FS_Error storage_process_common_stat(Storage* app, const char* path, FileInfo* fileinfo) { - FS_Error ret = FSE_OK; - StorageType type = storage_get_type_by_path(app, path); +static FS_Error storage_process_common_stat(Storage* app, FuriString* path, FileInfo* fileinfo) { + StorageData* storage; + FS_Error ret = storage_get_data(app, path, &storage); - if(storage_type_is_not_valid(type)) { - ret = FSE_INVALID_NAME; - } else { - StorageData* storage = storage_get_storage_by_type(app, type); - FS_CALL(storage, common.stat(storage, remove_vfs(path), fileinfo)); + if(ret == FSE_OK) { + FS_CALL(storage, common.stat(storage, cstr_path_without_vfs_prefix(path), fileinfo)); } return ret; } -static FS_Error storage_process_common_remove(Storage* app, const char* path) { - FS_Error ret = FSE_OK; - StorageType type = storage_get_type_by_path(app, path); - - FuriString* real_path; - real_path = furi_string_alloc_set(path); - storage_path_change_to_real_storage(real_path, type); +static FS_Error storage_process_common_remove(Storage* app, FuriString* path) { + StorageData* storage; + FS_Error ret = storage_get_data(app, path, &storage); do { - if(storage_type_is_not_valid(type)) { - ret = FSE_INVALID_NAME; - break; - } - - StorageData* storage = storage_get_storage_by_type(app, type); - if(storage_path_already_open(real_path, storage->files)) { + if(storage_path_already_open(path, storage->files)) { ret = FSE_ALREADY_OPEN; break; } storage_data_timestamp(storage); - FS_CALL(storage, common.remove(storage, remove_vfs(path))); + FS_CALL(storage, common.remove(storage, cstr_path_without_vfs_prefix(path))); } while(false); - furi_string_free(real_path); - return ret; } -static FS_Error storage_process_common_mkdir(Storage* app, const char* path) { - FS_Error ret = FSE_OK; - StorageType type = storage_get_type_by_path(app, path); +static FS_Error storage_process_common_mkdir(Storage* app, FuriString* path) { + StorageData* storage; + FS_Error ret = storage_get_data(app, path, &storage); - if(storage_type_is_not_valid(type)) { - ret = FSE_INVALID_NAME; - } else { - StorageData* storage = storage_get_storage_by_type(app, type); + if(ret == FSE_OK) { storage_data_timestamp(storage); - FS_CALL(storage, common.mkdir(storage, remove_vfs(path))); + FS_CALL(storage, common.mkdir(storage, cstr_path_without_vfs_prefix(path))); } return ret; @@ -394,17 +367,16 @@ static FS_Error storage_process_common_mkdir(Storage* app, const char* path) { static FS_Error storage_process_common_fs_info( Storage* app, - const char* fs_path, + FuriString* path, uint64_t* total_space, uint64_t* free_space) { - FS_Error ret = FSE_OK; - StorageType type = storage_get_type_by_path(app, fs_path); + StorageData* storage; + FS_Error ret = storage_get_data(app, path, &storage); - if(storage_type_is_not_valid(type)) { - ret = FSE_INVALID_NAME; - } else { - StorageData* storage = storage_get_storage_by_type(app, type); - FS_CALL(storage, common.fs_info(storage, remove_vfs(fs_path), total_space, free_space)); + if(ret == FSE_OK) { + FS_CALL( + storage, + common.fs_info(storage, cstr_path_without_vfs_prefix(path), total_space, free_space)); } return ret; @@ -471,14 +443,52 @@ static FS_Error storage_process_sd_status(Storage* app) { return ret; } +/******************** Aliases processing *******************/ + +void storage_process_alias( + Storage* app, + FuriString* path, + FuriThreadId thread_id, + bool create_folders) { + if(furi_string_start_with(path, STORAGE_APP_DATA_PATH_PREFIX)) { + FuriString* apps_data_path_with_appsid = furi_string_alloc_set(APPS_DATA_PATH "/"); + furi_string_cat(apps_data_path_with_appsid, furi_thread_get_appid(thread_id)); + + // "/app" -> "/ext/apps_data/appsid" + furi_string_replace_at( + path, + 0, + strlen(STORAGE_APP_DATA_PATH_PREFIX), + furi_string_get_cstr(apps_data_path_with_appsid)); + + // Create app data folder if not exists + if(create_folders && + storage_process_common_stat(app, apps_data_path_with_appsid, NULL) != FSE_OK) { + furi_string_set(apps_data_path_with_appsid, APPS_DATA_PATH); + storage_process_common_mkdir(app, apps_data_path_with_appsid); + furi_string_cat(apps_data_path_with_appsid, "/"); + furi_string_cat(apps_data_path_with_appsid, furi_thread_get_appid(thread_id)); + storage_process_common_mkdir(app, apps_data_path_with_appsid); + } + + furi_string_free(apps_data_path_with_appsid); + } +} + /****************** API calls processing ******************/ + void storage_process_message_internal(Storage* app, StorageMessage* message) { + FuriString* path = NULL; + switch(message->command) { + // File operations case StorageCommandFileOpen: + path = furi_string_alloc_set(message->data->fopen.path); + storage_process_alias(app, path, message->data->fopen.thread_id, true); message->return_data->bool_value = storage_process_file_open( app, message->data->fopen.file, - message->data->fopen.path, + path, message->data->fopen.access_mode, message->data->fopen.open_mode); break; @@ -527,9 +537,12 @@ void storage_process_message_internal(Storage* app, StorageMessage* message) { message->return_data->bool_value = storage_process_file_eof(app, message->data->file.file); break; + // Dir operations case StorageCommandDirOpen: + path = furi_string_alloc_set(message->data->dopen.path); + storage_process_alias(app, path, message->data->dopen.thread_id, true); message->return_data->bool_value = - storage_process_dir_open(app, message->data->dopen.file, message->data->dopen.path); + storage_process_dir_open(app, message->data->dopen.file, path); break; case StorageCommandDirClose: message->return_data->bool_value = @@ -547,29 +560,42 @@ void storage_process_message_internal(Storage* app, StorageMessage* message) { message->return_data->bool_value = storage_process_dir_rewind(app, message->data->file.file); break; + + // Common operations case StorageCommandCommonTimestamp: - message->return_data->error_value = storage_process_common_timestamp( - app, message->data->ctimestamp.path, message->data->ctimestamp.timestamp); + path = furi_string_alloc_set(message->data->ctimestamp.path); + storage_process_alias(app, path, message->data->ctimestamp.thread_id, false); + message->return_data->error_value = + storage_process_common_timestamp(app, path, message->data->ctimestamp.timestamp); break; case StorageCommandCommonStat: - message->return_data->error_value = storage_process_common_stat( - app, message->data->cstat.path, message->data->cstat.fileinfo); + path = furi_string_alloc_set(message->data->cstat.path); + storage_process_alias(app, path, message->data->cstat.thread_id, false); + message->return_data->error_value = + storage_process_common_stat(app, path, message->data->cstat.fileinfo); break; case StorageCommandCommonRemove: - message->return_data->error_value = - storage_process_common_remove(app, message->data->path.path); + path = furi_string_alloc_set(message->data->path.path); + storage_process_alias(app, path, message->data->path.thread_id, false); + message->return_data->error_value = storage_process_common_remove(app, path); break; case StorageCommandCommonMkDir: - message->return_data->error_value = - storage_process_common_mkdir(app, message->data->path.path); + path = furi_string_alloc_set(message->data->path.path); + storage_process_alias(app, path, message->data->path.thread_id, true); + message->return_data->error_value = storage_process_common_mkdir(app, path); break; case StorageCommandCommonFSInfo: + path = furi_string_alloc_set(message->data->cfsinfo.fs_path); + storage_process_alias(app, path, message->data->cfsinfo.thread_id, false); message->return_data->error_value = storage_process_common_fs_info( - app, - message->data->cfsinfo.fs_path, - message->data->cfsinfo.total_space, - message->data->cfsinfo.free_space); + app, path, message->data->cfsinfo.total_space, message->data->cfsinfo.free_space); + break; + case StorageCommandCommonResolvePath: + storage_process_alias( + app, message->data->cresolvepath.path, message->data->cresolvepath.thread_id, true); break; + + // SD operations case StorageCommandSDFormat: message->return_data->error_value = storage_process_sd_format(app); break; @@ -585,6 +611,10 @@ void storage_process_message_internal(Storage* app, StorageMessage* message) { break; } + if(path != NULL) { //-V547 + furi_string_free(path); + } + api_lock_unlock(message->lock); } diff --git a/applications/services/storage/storage_test_app.c b/applications/services/storage/storage_test_app.c deleted file mode 100644 index 852953e9935..00000000000 --- a/applications/services/storage/storage_test_app.c +++ /dev/null @@ -1,341 +0,0 @@ -#include -#include -#include - -#define TAG "StorageTest" -#define BYTES_COUNT 16 -#define TEST_STRING "TestDataStringProvidedByDiceRoll" -#define SEEK_OFFSET_FROM_START 10 -#define SEEK_OFFSET_INCREASE 12 -#define SEEK_OFFSET_SUM (SEEK_OFFSET_FROM_START + SEEK_OFFSET_INCREASE) - -static void do_file_test(Storage* api, const char* path) { - File* file = storage_file_alloc(api); - bool result; - uint8_t bytes[BYTES_COUNT + 1]; - uint8_t bytes_count; - uint64_t position; - uint64_t size; - - FURI_LOG_I(TAG, "--------- FILE \"%s\" ---------", path); - - // open - result = storage_file_open(file, path, FSAM_WRITE, FSOM_CREATE_ALWAYS); - if(result) { - FURI_LOG_I(TAG, "open"); - } else { - FURI_LOG_E(TAG, "open, %s", storage_file_get_error_desc(file)); - } - - // write - bytes_count = storage_file_write(file, TEST_STRING, strlen(TEST_STRING)); - if(bytes_count == 0) { - FURI_LOG_E(TAG, "write, %s", storage_file_get_error_desc(file)); - } else { - FURI_LOG_I(TAG, "write"); - } - - // sync - result = storage_file_sync(file); - if(result) { - FURI_LOG_I(TAG, "sync"); - } else { - FURI_LOG_E(TAG, "sync, %s", storage_file_get_error_desc(file)); - } - - // eof #1 - result = storage_file_eof(file); - if(result) { - FURI_LOG_I(TAG, "eof #1"); - } else { - FURI_LOG_E(TAG, "eof #1, %s", storage_file_get_error_desc(file)); - } - - // seek from start and tell - result = storage_file_seek(file, SEEK_OFFSET_FROM_START, true); - if(result) { - FURI_LOG_I(TAG, "seek #1"); - } else { - FURI_LOG_E(TAG, "seek #1, %s", storage_file_get_error_desc(file)); - } - position = storage_file_tell(file); - if(position != SEEK_OFFSET_FROM_START) { - FURI_LOG_E(TAG, "tell #1, %s", storage_file_get_error_desc(file)); - } else { - FURI_LOG_I(TAG, "tell #1"); - } - - // size - size = storage_file_size(file); - if(size != strlen(TEST_STRING)) { - FURI_LOG_E(TAG, "size #1, %s", storage_file_get_error_desc(file)); - } else { - FURI_LOG_I(TAG, "size #1"); - } - - // seek and tell - result = storage_file_seek(file, SEEK_OFFSET_INCREASE, false); - if(result) { - FURI_LOG_I(TAG, "seek #2"); - } else { - FURI_LOG_E(TAG, "seek #2, %s", storage_file_get_error_desc(file)); - } - position = storage_file_tell(file); - if(position != SEEK_OFFSET_SUM) { - FURI_LOG_E(TAG, "tell #2, %s", storage_file_get_error_desc(file)); - } else { - FURI_LOG_I(TAG, "tell #2"); - } - - // eof #2 - result = storage_file_eof(file); - if(!result) { - FURI_LOG_I(TAG, "eof #2"); - } else { - FURI_LOG_E(TAG, "eof #2, %s", storage_file_get_error_desc(file)); - } - - // truncate - result = storage_file_truncate(file); - if(result) { - FURI_LOG_I(TAG, "truncate"); - } else { - FURI_LOG_E(TAG, "truncate, %s", storage_file_get_error_desc(file)); - } - size = storage_file_size(file); - if(size != SEEK_OFFSET_SUM) { - FURI_LOG_E(TAG, "size #2, %s", storage_file_get_error_desc(file)); - } else { - FURI_LOG_I(TAG, "size #2"); - } - - // close - result = storage_file_close(file); - if(result) { - FURI_LOG_I(TAG, "close"); - } else { - FURI_LOG_E(TAG, "close, error"); - } - - // open - result = storage_file_open(file, path, FSAM_READ, FSOM_OPEN_EXISTING); - if(result) { - FURI_LOG_I(TAG, "open"); - } else { - FURI_LOG_E(TAG, "open, %s", storage_file_get_error_desc(file)); - } - - // read - memset(bytes, 0, BYTES_COUNT + 1); - bytes_count = storage_file_read(file, bytes, BYTES_COUNT); - if(bytes_count == 0) { - FURI_LOG_E(TAG, "read, %s", storage_file_get_error_desc(file)); - } else { - if(memcmp(TEST_STRING, bytes, bytes_count) == 0) { - FURI_LOG_I(TAG, "read"); - } else { - FURI_LOG_E(TAG, "read, garbage"); - } - } - - // close - result = storage_file_close(file); - if(result) { - FURI_LOG_I(TAG, "close"); - } else { - FURI_LOG_E(TAG, "close, error"); - } - - storage_file_free(file); -} - -static void do_dir_test(Storage* api, const char* path) { - File* file = storage_file_alloc(api); - bool result; - - FURI_LOG_I(TAG, "--------- DIR \"%s\" ---------", path); - - // open - result = storage_dir_open(file, path); - if(result) { - FURI_LOG_I(TAG, "open"); - } else { - FURI_LOG_E(TAG, "open, %s", storage_file_get_error_desc(file)); - } - - // read - const uint8_t filename_size = 100; - char* filename = malloc(filename_size); - FileInfo fileinfo; - - do { - result = storage_dir_read(file, &fileinfo, filename, filename_size); - if(result) { - if(strlen(filename)) { - FURI_LOG_I( - TAG, - "read #1, [%s]%s", - ((fileinfo.flags & FSF_DIRECTORY) ? "D" : "F"), - filename); - } - } else if(storage_file_get_error(file) != FSE_NOT_EXIST) { - FURI_LOG_E(TAG, "read #1, %s", storage_file_get_error_desc(file)); - break; - } - - } while(result); - - // rewind - result = storage_dir_rewind(file); - if(result) { - FURI_LOG_I(TAG, "rewind"); - } else { - FURI_LOG_E(TAG, "rewind, %s", storage_file_get_error_desc(file)); - } - - // read - do { - result = storage_dir_read(file, &fileinfo, filename, filename_size); - if(result) { - if(strlen(filename)) { - FURI_LOG_I( - TAG, - "read #2, [%s]%s", - ((fileinfo.flags & FSF_DIRECTORY) ? "D" : "F"), - filename); - } - } else if(storage_file_get_error(file) != FSE_NOT_EXIST) { - FURI_LOG_E(TAG, "read #2, %s", storage_file_get_error_desc(file)); - break; - } - - } while((strlen(filename))); - - // close - result = storage_dir_close(file); - if(result) { - FURI_LOG_I(TAG, "close"); - } else { - FURI_LOG_E(TAG, "close, error"); - } - - storage_file_free(file); - free(filename); -} - -static void do_test_start(Storage* api, const char* path) { - FuriString* str_path = furi_string_alloc_printf("%s/test-folder", path); - - FURI_LOG_I(TAG, "--------- START \"%s\" ---------", path); - - // mkdir - FS_Error result = storage_common_mkdir(api, furi_string_get_cstr(str_path)); - - if(result == FSE_OK) { - FURI_LOG_I(TAG, "mkdir ok"); - } else { - FURI_LOG_E(TAG, "mkdir, %s", storage_error_get_desc(result)); - } - - // stat - FileInfo fileinfo; - result = storage_common_stat(api, furi_string_get_cstr(str_path), &fileinfo); - - if(result == FSE_OK) { - if(fileinfo.flags & FSF_DIRECTORY) { - FURI_LOG_I(TAG, "stat #1 ok"); - } else { - FURI_LOG_E(TAG, "stat #1, %s", storage_error_get_desc(result)); - } - } else { - FURI_LOG_E(TAG, "stat #1, %s", storage_error_get_desc(result)); - } - - furi_string_free(str_path); -} - -static void do_test_end(Storage* api, const char* path) { - uint64_t total_space; - uint64_t free_space; - FuriString* str_path_1 = furi_string_alloc_printf("%s/test-folder", path); - FuriString* str_path_2 = furi_string_alloc_printf("%s/test-folder2", path); - - FURI_LOG_I(TAG, "--------- END \"%s\" ---------", path); - - // fs stat - FS_Error result = storage_common_fs_info(api, path, &total_space, &free_space); - - if(result == FSE_OK) { - uint32_t total_kb = total_space / 1024; - uint32_t free_kb = free_space / 1024; - FURI_LOG_I(TAG, "fs_info: total %luk, free %luk", total_kb, free_kb); - } else { - FURI_LOG_E(TAG, "fs_info, %s", storage_error_get_desc(result)); - } - - // rename #1 - result = storage_common_rename( - api, furi_string_get_cstr(str_path_1), furi_string_get_cstr(str_path_2)); - if(result == FSE_OK) { - FURI_LOG_I(TAG, "rename #1 ok"); - } else { - FURI_LOG_E(TAG, "rename #1, %s", storage_error_get_desc(result)); - } - - // remove #1 - result = storage_common_remove(api, furi_string_get_cstr(str_path_2)); - if(result == FSE_OK) { - FURI_LOG_I(TAG, "remove #1 ok"); - } else { - FURI_LOG_E(TAG, "remove #1, %s", storage_error_get_desc(result)); - } - - // rename #2 - furi_string_printf(str_path_1, "%s/test.txt", path); - furi_string_printf(str_path_2, "%s/test2.txt", path); - - result = storage_common_rename( - api, furi_string_get_cstr(str_path_1), furi_string_get_cstr(str_path_2)); - if(result == FSE_OK) { - FURI_LOG_I(TAG, "rename #2 ok"); - } else { - FURI_LOG_E(TAG, "rename #2, %s", storage_error_get_desc(result)); - } - - // remove #2 - result = storage_common_remove(api, furi_string_get_cstr(str_path_2)); - if(result == FSE_OK) { - FURI_LOG_I(TAG, "remove #2 ok"); - } else { - FURI_LOG_E(TAG, "remove #2, %s", storage_error_get_desc(result)); - } - - furi_string_free(str_path_1); - furi_string_free(str_path_2); -} - -int32_t storage_test_app(void* p) { - UNUSED(p); - Storage* api = furi_record_open(RECORD_STORAGE); - do_test_start(api, STORAGE_INT_PATH_PREFIX); - do_test_start(api, STORAGE_ANY_PATH_PREFIX); - do_test_start(api, STORAGE_EXT_PATH_PREFIX); - - do_file_test(api, INT_PATH("test.txt")); - do_file_test(api, ANY_PATH("test.txt")); - do_file_test(api, EXT_PATH("test.txt")); - - do_dir_test(api, STORAGE_INT_PATH_PREFIX); - do_dir_test(api, STORAGE_ANY_PATH_PREFIX); - do_dir_test(api, STORAGE_EXT_PATH_PREFIX); - - do_test_end(api, STORAGE_INT_PATH_PREFIX); - do_test_end(api, STORAGE_ANY_PATH_PREFIX); - do_test_end(api, STORAGE_EXT_PATH_PREFIX); - - while(true) { - furi_delay_ms(1000); - } - - return 0; -} diff --git a/applications/system/storage_move_to_sd/storage_move_to_sd.c b/applications/system/storage_move_to_sd/storage_move_to_sd.c index 2027bd23759..9c91b926680 100644 --- a/applications/system/storage_move_to_sd/storage_move_to_sd.c +++ b/applications/system/storage_move_to_sd/storage_move_to_sd.c @@ -13,7 +13,7 @@ static bool storage_move_to_sd_check_entry(const char* name, FileInfo* fileinfo, void* ctx) { UNUSED(ctx); - if((fileinfo->flags & FSF_DIRECTORY) != 0) { + if(file_info_is_dir(fileinfo)) { return true; } diff --git a/assets/resources/music_player/Marble_Machine.fmf b/assets/resources/apps_data/music_player/Marble_Machine.fmf similarity index 100% rename from assets/resources/music_player/Marble_Machine.fmf rename to assets/resources/apps_data/music_player/Marble_Machine.fmf diff --git a/assets/resources/picopass/assets/iclass_elite_dict.txt b/assets/resources/apps_data/picopass/assets/iclass_elite_dict.txt similarity index 100% rename from assets/resources/picopass/assets/iclass_elite_dict.txt rename to assets/resources/apps_data/picopass/assets/iclass_elite_dict.txt diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 8152095dca2..33bca491a04 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,15.0,, +Version,+,15.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -847,6 +847,7 @@ Function,+,file_browser_worker_set_folder_callback,void,"BrowserWorker*, Browser Function,+,file_browser_worker_set_item_callback,void,"BrowserWorker*, BrowserWorkerListItemCallback" Function,+,file_browser_worker_set_list_callback,void,"BrowserWorker*, BrowserWorkerListLoadCallback" Function,+,file_browser_worker_set_long_load_callback,void,"BrowserWorker*, BrowserWorkerLongLoadCallback" +Function,+,file_info_is_dir,_Bool,const FileInfo* Function,+,file_stream_alloc,Stream*,Storage* Function,+,file_stream_close,_Bool,Stream* Function,+,file_stream_get_error,FS_Error,Stream* @@ -1521,6 +1522,7 @@ Function,+,furi_thread_flags_get,uint32_t, Function,+,furi_thread_flags_set,uint32_t,"FuriThreadId, uint32_t" Function,+,furi_thread_flags_wait,uint32_t,"uint32_t, uint32_t, uint32_t" Function,+,furi_thread_free,void,FuriThread* +Function,+,furi_thread_get_appid,const char*,FuriThreadId Function,+,furi_thread_get_current,FuriThread*, Function,+,furi_thread_get_current_id,FuriThreadId, Function,+,furi_thread_get_current_priority,FuriThreadPriority, @@ -1535,6 +1537,7 @@ Function,+,furi_thread_is_suspended,_Bool,FuriThreadId Function,+,furi_thread_join,_Bool,FuriThread* Function,+,furi_thread_mark_as_service,void,FuriThread* Function,+,furi_thread_resume,void,FuriThreadId +Function,+,furi_thread_set_appid,void,"FuriThread*, const char*" Function,+,furi_thread_set_callback,void,"FuriThread*, FuriThreadCallback" Function,+,furi_thread_set_context,void,"FuriThread*, void*" Function,+,furi_thread_set_current_priority,void,FuriThreadPriority @@ -2430,14 +2433,18 @@ Function,-,srand48,void,long Function,-,srandom,void,unsigned Function,+,sscanf,int,"const char*, const char*, ..." Function,+,storage_common_copy,FS_Error,"Storage*, const char*, const char*" +Function,+,storage_common_exists,_Bool,"Storage*, const char*" Function,+,storage_common_fs_info,FS_Error,"Storage*, const char*, uint64_t*, uint64_t*" Function,+,storage_common_merge,FS_Error,"Storage*, const char*, const char*" +Function,+,storage_common_migrate,FS_Error,"Storage*, const char*, const char*" Function,+,storage_common_mkdir,FS_Error,"Storage*, const char*" Function,+,storage_common_remove,FS_Error,"Storage*, const char*" Function,+,storage_common_rename,FS_Error,"Storage*, const char*, const char*" +Function,+,storage_common_resolve_path_and_ensure_app_directory,void,"Storage*, FuriString*" Function,+,storage_common_stat,FS_Error,"Storage*, const char*, FileInfo*" Function,+,storage_common_timestamp,FS_Error,"Storage*, const char*, uint32_t*" Function,+,storage_dir_close,_Bool,File* +Function,+,storage_dir_exists,_Bool,"Storage*, const char*" Function,+,storage_dir_open,_Bool,"File*, const char*" Function,+,storage_dir_read,_Bool,"File*, FileInfo*, char*, uint16_t" Function,-,storage_dir_rewind,_Bool,File* diff --git a/furi/core/thread.c b/furi/core/thread.c index ea9f45e842a..b45651c29d1 100644 --- a/furi/core/thread.c +++ b/furi/core/thread.c @@ -35,6 +35,8 @@ struct FuriThread { void* state_context; char* name; + char* appid; + configSTACK_DEPTH_TYPE stack_size; FuriThreadPriority priority; @@ -122,11 +124,25 @@ FuriThread* furi_thread_alloc() { thread->output.buffer = furi_string_alloc(); thread->is_service = false; + FuriThread* parent = NULL; + if(xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED) { + // TLS is not available, if we called not from thread context + parent = pvTaskGetThreadLocalStoragePointer(NULL, 0); + + if(parent && parent->appid) { + furi_thread_set_appid(thread, parent->appid); + } else { + furi_thread_set_appid(thread, "unknown"); + } + } else { + // if scheduler is not started, we are starting driver thread + furi_thread_set_appid(thread, "driver"); + } + FuriHalRtcHeapTrackMode mode = furi_hal_rtc_get_heap_track_mode(); if(mode == FuriHalRtcHeapTrackModeAll) { thread->heap_trace_enabled = true; } else if(mode == FuriHalRtcHeapTrackModeTree && furi_thread_get_current_id()) { - FuriThread* parent = pvTaskGetThreadLocalStoragePointer(NULL, 0); if(parent) thread->heap_trace_enabled = parent->heap_trace_enabled; } else { thread->heap_trace_enabled = false; @@ -153,6 +169,7 @@ void furi_thread_free(FuriThread* thread) { furi_assert(thread->state == FuriThreadStateStopped); if(thread->name) free((void*)thread->name); + if(thread->appid) free((void*)thread->appid); furi_string_free(thread->output.buffer); free(thread); @@ -165,6 +182,13 @@ void furi_thread_set_name(FuriThread* thread, const char* name) { thread->name = name ? strdup(name) : NULL; } +void furi_thread_set_appid(FuriThread* thread, const char* appid) { + furi_assert(thread); + furi_assert(thread->state == FuriThreadStateStopped); + if(thread->appid) free((void*)thread->appid); + thread->appid = appid ? strdup(appid) : NULL; +} + void furi_thread_mark_as_service(FuriThread* thread) { thread->is_service = true; } @@ -498,6 +522,20 @@ const char* furi_thread_get_name(FuriThreadId thread_id) { return (name); } +const char* furi_thread_get_appid(FuriThreadId thread_id) { + TaskHandle_t hTask = (TaskHandle_t)thread_id; + const char* appid = "system"; + + if(!FURI_IS_IRQ_MODE() && (hTask != NULL)) { + FuriThread* thread = (FuriThread*)pvTaskGetThreadLocalStoragePointer(hTask, 0); + if(thread) { + appid = thread->appid; + } + } + + return (appid); +} + uint32_t furi_thread_get_stack_space(FuriThreadId thread_id) { TaskHandle_t hTask = (TaskHandle_t)thread_id; uint32_t sz; diff --git a/furi/core/thread.h b/furi/core/thread.h index 2675f7cee7d..8f43984197b 100644 --- a/furi/core/thread.h +++ b/furi/core/thread.h @@ -87,6 +87,16 @@ void furi_thread_free(FuriThread* thread); */ void furi_thread_set_name(FuriThread* thread, const char* name); +/** + * @brief Set FuriThread appid + * Technically, it is like a "process id", but it is not a system-wide unique identifier. + * All threads spawned by the same app will have the same appid. + * + * @param thread + * @param appid + */ +void furi_thread_set_appid(FuriThread* thread, const char* appid); + /** Mark thread as service * The service cannot be stopped or removed, and cannot exit from the thread body * @@ -233,10 +243,37 @@ uint32_t furi_thread_flags_get(void); uint32_t furi_thread_flags_wait(uint32_t flags, uint32_t options, uint32_t timeout); +/** + * @brief Enumerate threads + * + * @param thread_array array of FuriThreadId, where thread ids will be stored + * @param array_items array size + * @return uint32_t threads count + */ uint32_t furi_thread_enumerate(FuriThreadId* thread_array, uint32_t array_items); +/** + * @brief Get thread name + * + * @param thread_id + * @return const char* name or NULL + */ const char* furi_thread_get_name(FuriThreadId thread_id); +/** + * @brief Get thread appid + * + * @param thread_id + * @return const char* appid + */ +const char* furi_thread_get_appid(FuriThreadId thread_id); + +/** + * @brief Get thread stack watermark + * + * @param thread_id + * @return uint32_t + */ uint32_t furi_thread_get_stack_space(FuriThreadId thread_id); /** Get STDOUT callback for thead diff --git a/furi/flipper.c b/furi/flipper.c index d16a84a1018..f0147c06029 100644 --- a/furi/flipper.c +++ b/furi/flipper.c @@ -41,6 +41,7 @@ void flipper_init() { FLIPPER_SERVICES[i].app, NULL); furi_thread_mark_as_service(thread); + furi_thread_set_appid(thread, FLIPPER_SERVICES[i].appid); furi_thread_start(thread); } diff --git a/lib/toolbox/dir_walk.c b/lib/toolbox/dir_walk.c index e5a3cf32b59..509ceb5b42d 100644 --- a/lib/toolbox/dir_walk.c +++ b/lib/toolbox/dir_walk.c @@ -83,7 +83,7 @@ static DirWalkResult end = true; } - if((info.flags & FSF_DIRECTORY) && dir_walk->recursive) { + if(file_info_is_dir(&info) && dir_walk->recursive) { // step into DirIndexList_push_back(dir_walk->index_list, dir_walk->current_index); dir_walk->current_index = 0; diff --git a/lib/toolbox/tar/tar_archive.c b/lib/toolbox/tar/tar_archive.c index 01969b47126..fcfc22a7035 100644 --- a/lib/toolbox/tar/tar_archive.c +++ b/lib/toolbox/tar/tar_archive.c @@ -344,7 +344,7 @@ bool tar_archive_add_dir(TarArchive* archive, const char* fs_full_path, const ch furi_string_set(element_name, name); } - if(file_info.flags & FSF_DIRECTORY) { + if(file_info_is_dir(&file_info)) { success = tar_archive_dir_add_element(archive, furi_string_get_cstr(element_name)) && tar_archive_add_dir( diff --git a/scripts/fbt/appmanifest.py b/scripts/fbt/appmanifest.py index aa03d265b3f..64b9f6f39b3 100644 --- a/scripts/fbt/appmanifest.py +++ b/scripts/fbt/appmanifest.py @@ -321,6 +321,7 @@ def get_app_descr(self, app: FlipperApplication): return f""" {{.app = {app.entry_point}, .name = "{app.name}", + .appid = "{app.appid}", .stack_size = {app.stack_size}, .icon = {f"&{app.icon}" if app.icon else "NULL"}, .flags = {'|'.join(f"FlipperApplicationFlag{flag}" for flag in app.flags)} }}""" From 4359e2eaa9fd42ff66200b6dbc82d67034c40a90 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Thu, 2 Mar 2023 13:56:23 +0200 Subject: [PATCH 438/824] [FL-3103] New pin reset splashscreen (#2405) * New pin reset splashscreen * Recovery: optimize drawing routine * Recovery: increase erase confirmation time * Change the required button Co-authored-by: Aleksandr Kutuzov --- assets/icons/ErasePin/Erase_pin_128x64.png | Bin 0 -> 5220 bytes firmware/targets/f7/src/recovery.c | 34 +++++++++++++-------- 2 files changed, 21 insertions(+), 13 deletions(-) create mode 100644 assets/icons/ErasePin/Erase_pin_128x64.png diff --git a/assets/icons/ErasePin/Erase_pin_128x64.png b/assets/icons/ErasePin/Erase_pin_128x64.png new file mode 100644 index 0000000000000000000000000000000000000000..92ca5f91cbb528563ced1b4f585c40ca3e8c27c6 GIT binary patch literal 5220 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 I(sK{{A0@`iOaK4? literal 0 HcmV?d00001 diff --git a/firmware/targets/f7/src/recovery.c b/firmware/targets/f7/src/recovery.c index d56be4fe076..db538b0d511 100644 --- a/firmware/targets/f7/src/recovery.c +++ b/firmware/targets/f7/src/recovery.c @@ -5,24 +5,20 @@ #include #include -#define COUNTER_VALUE (100U) +#define COUNTER_VALUE (136U) static void flipper_boot_recovery_draw_splash(u8g2_t* fb, size_t progress) { - u8g2_ClearBuffer(fb); - u8g2_SetDrawColor(fb, 0x01); - - u8g2_SetFont(fb, u8g2_font_helvB08_tr); - u8g2_DrawStr(fb, 2, 8, "PIN and Factory Reset"); - u8g2_SetFont(fb, u8g2_font_haxrcorp4089_tr); - u8g2_DrawStr(fb, 2, 21, "Hold Right to confirm"); - u8g2_DrawStr(fb, 2, 31, "Press Down to cancel"); - if(progress < COUNTER_VALUE) { - size_t width = progress / (COUNTER_VALUE / 100); - u8g2_DrawBox(fb, 14 + (50 - width / 2), 54, width, 3); + // Fill the progress bar while the progress is going down + u8g2_SetDrawColor(fb, 0x01); + u8g2_DrawRFrame(fb, 59, 41, 69, 8, 2); + size_t width = (COUNTER_VALUE - progress) * 68 / COUNTER_VALUE; + u8g2_DrawBox(fb, 60, 42, width, 6); + } else { + u8g2_SetDrawColor(fb, 0x00); + u8g2_DrawRBox(fb, 59, 41, 69, 8, 2); } - u8g2_SetPowerSave(fb, 0); u8g2_SendBuffer(fb); } @@ -31,6 +27,18 @@ void flipper_boot_recovery_exec() { u8g2_Setup_st756x_flipper(fb, U8G2_R0, u8x8_hw_spi_stm32, u8g2_gpio_and_delay_stm32); u8g2_InitDisplay(fb); + furi_hal_compress_icon_init(); + uint8_t* splash_data = NULL; + furi_hal_compress_icon_decode(icon_get_data(&I_Erase_pin_128x64), &splash_data); + + u8g2_ClearBuffer(fb); + u8g2_SetDrawColor(fb, 0x01); + + // Draw the recovery picture + u8g2_DrawXBM(fb, 0, 0, 128, 64, splash_data); + u8g2_SendBuffer(fb); + u8g2_SetPowerSave(fb, 0); + size_t counter = COUNTER_VALUE; while(counter) { if(!furi_hal_gpio_read(&gpio_button_down)) { From 806428efeb172a9e5adaf72955098a50fe22fadb Mon Sep 17 00:00:00 2001 From: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> Date: Thu, 2 Mar 2023 16:23:33 +0300 Subject: [PATCH 439/824] [FL-3070] iButton system and app refactoring (#2388) * Add 1-wire thermometer example app stub * Working 1-wire thermometer app * Refactor app to use threads * Clean up code, add comments * Add CRC checking * Increase update period * Fix error in fbt * Revert the old update period * Use settable pin in onewire_host * Use settable pin for onewire_slave * Clear EXTI flag after callback, make private methods static in onewire_slave * Do not hardcode GPIO pin number * Remove iButton hal from furi_hal_rfid * Remove most of furi_hal_ibutton * Add some of furi_hal_ibutton back * Slightly neater code * Update CODEOWNERS * Add furi_hal_gpio_get_ext_pin_number * Create README.md * Temporary get Metakom and Cyfral keys out of the way * Better enum name * Syncing work, does not compile * Syncing work, now compiles * Working read impl for DS1990 and DS1992 * Add the ability to display extended key data * Get rid of DialogEx * Add save and load API * Better iButtonKey encapsulation * Fix crash * Load key code boilerplate * More load key code boilerplate * Minor code cleanup * Implement loading and saving DS1990 keys * Implement the Info scene * Implement loading & saving for DS1992 * Implement read error scene stub * Implement delete confirmation screen * Better error messages (protocol-dependent) * Minor old code cleanup * Remove iButtonDevice, add command callback to iButtonSlave * Implement draft emulation for DS1990 * Better emulation for DS1990 * Initial emulation implementation for DS1992 * Better common command definitions * Use common submenu callback, add protocol list * Improve ViewData screen * Improve scene_add_type * Add stubs for write functionality * Improve naming consistency * Implement writing a DS1992 onto another one * Improve DS1992 write code * Improve DS1992 write code once more * Prepare write_blank for DS1990, delete ibutton_writer * Implement writing DS1990 onto blanks * Fix reading DS1990 * Partially implement writing DS1992 onto blanks * Implement GUI for writing keys * Implement GUI for emulating keys * Reduce memory usage for pretty_format * Automatically truncate data more than 256 bytes * Initial implementation of DS1996 (not tested) * Fix crash due to missing virtual function * Improve emulation code * Improve DS1992 emulation code * Correct return value for onewire_slave_send * Correct return value for onewire_slave_receive * Implement emulation for DS1992 & DS1996 * Better constant names * Simplify & optimise the emulation code * Remove duplicate code * Add skip rom command emulation * Show loading animation for large keys * Implement manual adding & editing of keys * Use buffered file streams to speed up saving & loading * Reset key name before adding a new one * Sync a buffered file stream before saving * Use the DSGeneric protocol as a fallback option * Implement emulation via RPC * Refactor iButton code in preparation for comparator keys * Refactor iButton code in preparation for comparator keys once more * Make some functions static * Make protocols not rely on one_wire classes * Improve ProtocolDict usage * Improve ProtocolDict usage more * Implement reading Metakom & Cyfral keys * Rename some files * Better file structure * Implement a unified interface for misc protocols * Implement a unified interface for dallas protocols * Concrete types for Dallas protocols * Implement a unified interface for all key types * Improved type naming * Improved private types * Proper types in protocol definitions * Implement emulation for Cyfral & Metakom keys * Implement save&load for Metakom & Cyfral keys * Better type names * Rename files, better names * Allocate iButtonProtocols like a normal class * Reset the key each time the start scene is selected * Improve comments and constants * Add ibutton_protocols to SDK headers * Add ibutton_key to SDK headers * Add ibutton_key to SDK headers * Implement reading via cli * Implement emulation via cli * Implement writing Dallas blanks via cli * Correctly revert the editing if cancelled by the user * Correct committing mishap * Elide the long text on the info screen * Change key name for data in Misc keys * Update iButtonFileFormat.md * Remember the key's folder * Save menu position in ReadKeyMenu and SavedKeyMenu * Correct use of preselected path in file browser Co-authored-by: Aleksandr Kutuzov --- applications/main/ibutton/ibutton.c | 230 +++++------- applications/main/ibutton/ibutton_cli.c | 176 +++++---- .../main/ibutton/ibutton_custom_event.h | 1 + applications/main/ibutton/ibutton_i.h | 50 ++- .../ibutton/scenes/ibutton_scene_add_type.c | 60 ++- .../ibutton/scenes/ibutton_scene_add_value.c | 43 ++- .../ibutton/scenes/ibutton_scene_config.h | 4 +- .../scenes/ibutton_scene_delete_confirm.c | 74 +--- .../ibutton/scenes/ibutton_scene_emulate.c | 66 +--- .../main/ibutton/scenes/ibutton_scene_info.c | 72 ++-- .../main/ibutton/scenes/ibutton_scene_read.c | 26 +- .../scenes/ibutton_scene_read_crc_error.c | 70 ---- .../ibutton/scenes/ibutton_scene_read_error.c | 58 +++ .../scenes/ibutton_scene_read_key_menu.c | 57 ++- .../scenes/ibutton_scene_read_not_key_error.c | 71 ---- .../scenes/ibutton_scene_read_success.c | 78 ++-- .../main/ibutton/scenes/ibutton_scene_rpc.c | 42 +-- .../ibutton/scenes/ibutton_scene_save_name.c | 44 +-- .../scenes/ibutton_scene_saved_key_menu.c | 72 ++-- .../ibutton/scenes/ibutton_scene_select_key.c | 6 +- .../main/ibutton/scenes/ibutton_scene_start.c | 17 +- .../ibutton/scenes/ibutton_scene_view_data.c | 26 ++ .../main/ibutton/scenes/ibutton_scene_write.c | 78 ++-- .../scenes/infrared_scene_universal.c | 2 +- applications/services/gui/modules/widget.h | 1 + .../widget_element_text_scroll.c | 2 + .../file_formats/iButtonFileFormat.md | 47 ++- firmware/targets/f18/api_symbols.csv | 2 +- firmware/targets/f7/api_symbols.csv | 67 ++-- lib/flipper_format/flipper_format.c | 6 + lib/flipper_format/flipper_format.h | 11 +- lib/one_wire/SConscript | 3 +- lib/one_wire/ibutton/ibutton_key.c | 111 ++---- lib/one_wire/ibutton/ibutton_key.h | 120 ++---- lib/one_wire/ibutton/ibutton_key_command.h | 28 -- lib/one_wire/ibutton/ibutton_key_i.h | 9 + lib/one_wire/ibutton/ibutton_protocols.c | 309 ++++++++++++++++ lib/one_wire/ibutton/ibutton_protocols.h | 197 ++++++++++ lib/one_wire/ibutton/ibutton_worker.c | 73 ++-- lib/one_wire/ibutton/ibutton_worker.h | 15 +- lib/one_wire/ibutton/ibutton_worker_i.h | 31 +- lib/one_wire/ibutton/ibutton_worker_modes.c | 308 +++------------- lib/one_wire/ibutton/ibutton_writer.c | 298 --------------- lib/one_wire/ibutton/ibutton_writer.h | 60 --- .../ibutton/protocols/blanks/rw1990.c | 95 +++++ .../ibutton/protocols/blanks/rw1990.h | 9 + .../ibutton/protocols/blanks/tm2004.c | 42 +++ .../ibutton/protocols/blanks/tm2004.h | 7 + .../ibutton/protocols/dallas/dallas_common.c | 250 +++++++++++++ .../ibutton/protocols/dallas/dallas_common.h | 107 ++++++ .../protocols/dallas/protocol_dallas_base.h | 39 ++ .../protocols/dallas/protocol_ds1990.c | 144 ++++++++ .../protocols/dallas/protocol_ds1990.h | 5 + .../protocols/dallas/protocol_ds1992.c | 218 +++++++++++ .../protocols/dallas/protocol_ds1992.h | 5 + .../protocols/dallas/protocol_ds1996.c | 212 +++++++++++ .../protocols/dallas/protocol_ds1996.h | 5 + .../protocols/dallas/protocol_ds_generic.c | 133 +++++++ .../protocols/dallas/protocol_ds_generic.h | 5 + .../protocols/dallas/protocol_group_dallas.c | 310 ++++++++++++++++ .../protocols/dallas/protocol_group_dallas.h | 5 + .../dallas/protocol_group_dallas_defs.c | 16 + .../dallas/protocol_group_dallas_defs.h | 16 + .../ibutton/protocols/ibutton_protocols.c | 8 - .../ibutton/protocols/ibutton_protocols.h | 11 - .../protocols/{ => misc}/protocol_cyfral.c | 10 +- .../ibutton/protocols/misc/protocol_cyfral.h | 4 + .../protocols/misc/protocol_group_misc.c | 295 +++++++++++++++ .../protocols/misc/protocol_group_misc.h | 5 + .../protocols/misc/protocol_group_misc_defs.c | 10 + .../protocols/misc/protocol_group_misc_defs.h | 11 + .../protocols/{ => misc}/protocol_metakom.c | 10 +- .../ibutton/protocols/misc/protocol_metakom.h | 4 + .../ibutton/protocols/protocol_common.h | 21 ++ .../ibutton/protocols/protocol_common_i.h | 6 + .../ibutton/protocols/protocol_cyfral.h | 4 - .../ibutton/protocols/protocol_group_base.h | 104 ++++++ .../ibutton/protocols/protocol_group_defs.c | 9 + .../ibutton/protocols/protocol_group_defs.h | 11 + .../ibutton/protocols/protocol_metakom.h | 4 - lib/one_wire/one_wire_device.c | 59 --- lib/one_wire/one_wire_device.h | 74 ---- lib/one_wire/one_wire_host.c | 10 +- lib/one_wire/one_wire_host.h | 12 +- lib/one_wire/one_wire_slave.c | 349 +++++++++--------- lib/one_wire/one_wire_slave.h | 58 ++- lib/one_wire/one_wire_slave_i.h | 38 -- lib/toolbox/SConscript | 1 + lib/toolbox/pretty_format.c | 60 +++ lib/toolbox/pretty_format.h | 29 ++ lib/toolbox/stream/buffered_file_stream.c | 1 + 91 files changed, 3811 insertions(+), 2151 deletions(-) delete mode 100644 applications/main/ibutton/scenes/ibutton_scene_read_crc_error.c create mode 100644 applications/main/ibutton/scenes/ibutton_scene_read_error.c delete mode 100644 applications/main/ibutton/scenes/ibutton_scene_read_not_key_error.c create mode 100644 applications/main/ibutton/scenes/ibutton_scene_view_data.c delete mode 100644 lib/one_wire/ibutton/ibutton_key_command.h create mode 100644 lib/one_wire/ibutton/ibutton_key_i.h create mode 100644 lib/one_wire/ibutton/ibutton_protocols.c create mode 100644 lib/one_wire/ibutton/ibutton_protocols.h delete mode 100644 lib/one_wire/ibutton/ibutton_writer.c delete mode 100644 lib/one_wire/ibutton/ibutton_writer.h create mode 100644 lib/one_wire/ibutton/protocols/blanks/rw1990.c create mode 100644 lib/one_wire/ibutton/protocols/blanks/rw1990.h create mode 100644 lib/one_wire/ibutton/protocols/blanks/tm2004.c create mode 100644 lib/one_wire/ibutton/protocols/blanks/tm2004.h create mode 100644 lib/one_wire/ibutton/protocols/dallas/dallas_common.c create mode 100644 lib/one_wire/ibutton/protocols/dallas/dallas_common.h create mode 100644 lib/one_wire/ibutton/protocols/dallas/protocol_dallas_base.h create mode 100644 lib/one_wire/ibutton/protocols/dallas/protocol_ds1990.c create mode 100644 lib/one_wire/ibutton/protocols/dallas/protocol_ds1990.h create mode 100644 lib/one_wire/ibutton/protocols/dallas/protocol_ds1992.c create mode 100644 lib/one_wire/ibutton/protocols/dallas/protocol_ds1992.h create mode 100644 lib/one_wire/ibutton/protocols/dallas/protocol_ds1996.c create mode 100644 lib/one_wire/ibutton/protocols/dallas/protocol_ds1996.h create mode 100644 lib/one_wire/ibutton/protocols/dallas/protocol_ds_generic.c create mode 100644 lib/one_wire/ibutton/protocols/dallas/protocol_ds_generic.h create mode 100644 lib/one_wire/ibutton/protocols/dallas/protocol_group_dallas.c create mode 100644 lib/one_wire/ibutton/protocols/dallas/protocol_group_dallas.h create mode 100644 lib/one_wire/ibutton/protocols/dallas/protocol_group_dallas_defs.c create mode 100644 lib/one_wire/ibutton/protocols/dallas/protocol_group_dallas_defs.h delete mode 100644 lib/one_wire/ibutton/protocols/ibutton_protocols.c delete mode 100644 lib/one_wire/ibutton/protocols/ibutton_protocols.h rename lib/one_wire/ibutton/protocols/{ => misc}/protocol_cyfral.c (96%) create mode 100644 lib/one_wire/ibutton/protocols/misc/protocol_cyfral.h create mode 100644 lib/one_wire/ibutton/protocols/misc/protocol_group_misc.c create mode 100644 lib/one_wire/ibutton/protocols/misc/protocol_group_misc.h create mode 100644 lib/one_wire/ibutton/protocols/misc/protocol_group_misc_defs.c create mode 100644 lib/one_wire/ibutton/protocols/misc/protocol_group_misc_defs.h rename lib/one_wire/ibutton/protocols/{ => misc}/protocol_metakom.c (96%) create mode 100644 lib/one_wire/ibutton/protocols/misc/protocol_metakom.h create mode 100644 lib/one_wire/ibutton/protocols/protocol_common.h create mode 100644 lib/one_wire/ibutton/protocols/protocol_common_i.h delete mode 100644 lib/one_wire/ibutton/protocols/protocol_cyfral.h create mode 100644 lib/one_wire/ibutton/protocols/protocol_group_base.h create mode 100644 lib/one_wire/ibutton/protocols/protocol_group_defs.c create mode 100644 lib/one_wire/ibutton/protocols/protocol_group_defs.h delete mode 100644 lib/one_wire/ibutton/protocols/protocol_metakom.h delete mode 100644 lib/one_wire/one_wire_device.c delete mode 100644 lib/one_wire/one_wire_device.h delete mode 100644 lib/one_wire/one_wire_slave_i.h create mode 100644 lib/toolbox/pretty_format.c create mode 100644 lib/toolbox/pretty_format.h diff --git a/applications/main/ibutton/ibutton.c b/applications/main/ibutton/ibutton.c index 85212f42b6b..79999adb28c 100644 --- a/applications/main/ibutton/ibutton.c +++ b/applications/main/ibutton/ibutton.c @@ -1,10 +1,6 @@ -#include "ibutton.h" -#include "assets_icons.h" #include "ibutton_i.h" -#include "ibutton/scenes/ibutton_scene.h" + #include -#include -#include #include #define TAG "iButtonApp" @@ -34,50 +30,13 @@ static const NotificationSequence* ibutton_notification_sequences[] = { }; static void ibutton_make_app_folder(iButton* ibutton) { - if(!storage_simply_mkdir(ibutton->storage, IBUTTON_APP_FOLDER)) { - dialog_message_show_storage_error(ibutton->dialogs, "Cannot create\napp folder"); - } -} - -bool ibutton_load_key_data(iButton* ibutton, FuriString* key_path, bool show_dialog) { - FlipperFormat* file = flipper_format_file_alloc(ibutton->storage); - bool result = false; - FuriString* data; - data = furi_string_alloc(); - - do { - if(!flipper_format_file_open_existing(file, furi_string_get_cstr(key_path))) break; - - // header - uint32_t version; - if(!flipper_format_read_header(file, data, &version)) break; - if(furi_string_cmp_str(data, IBUTTON_APP_FILE_TYPE) != 0) break; - if(version != 1) break; + Storage* storage = furi_record_open(RECORD_STORAGE); - // key type - iButtonKeyType type; - if(!flipper_format_read_string(file, "Key type", data)) break; - if(!ibutton_key_get_type_by_string(furi_string_get_cstr(data), &type)) break; - - // key data - uint8_t key_data[IBUTTON_KEY_DATA_SIZE] = {0}; - if(!flipper_format_read_hex(file, "Data", key_data, ibutton_key_get_size_by_type(type))) - break; - - ibutton_key_set_type(ibutton->key, type); - ibutton_key_set_data(ibutton->key, key_data, IBUTTON_KEY_DATA_SIZE); - - result = true; - } while(false); - - flipper_format_free(file); - furi_string_free(data); - - if((!result) && (show_dialog)) { - dialog_message_show_storage_error(ibutton->dialogs, "Cannot load\nkey file"); + if(!storage_simply_mkdir(storage, IBUTTON_APP_FOLDER)) { + dialog_message_show_storage_error(ibutton->dialogs, "Cannot create\napp folder"); } - return result; + furi_record_close(RECORD_STORAGE); } static void ibutton_rpc_command_callback(RpcAppSystemEvent event, void* context) { @@ -87,14 +46,14 @@ static void ibutton_rpc_command_callback(RpcAppSystemEvent event, void* context) if(event == RpcAppEventSessionClose) { view_dispatcher_send_custom_event( ibutton->view_dispatcher, iButtonCustomEventRpcSessionClose); - rpc_system_app_set_callback(ibutton->rpc_ctx, NULL, NULL); - ibutton->rpc_ctx = NULL; + rpc_system_app_set_callback(ibutton->rpc, NULL, NULL); + ibutton->rpc = NULL; } else if(event == RpcAppEventAppExit) { view_dispatcher_send_custom_event(ibutton->view_dispatcher, iButtonCustomEventRpcExit); } else if(event == RpcAppEventLoadFile) { view_dispatcher_send_custom_event(ibutton->view_dispatcher, iButtonCustomEventRpcLoad); } else { - rpc_system_app_confirm(ibutton->rpc_ctx, event, false); + rpc_system_app_confirm(ibutton->rpc, event, false); } } @@ -135,13 +94,13 @@ iButton* ibutton_alloc() { ibutton->gui = furi_record_open(RECORD_GUI); - ibutton->storage = furi_record_open(RECORD_STORAGE); ibutton->dialogs = furi_record_open(RECORD_DIALOGS); ibutton->notifications = furi_record_open(RECORD_NOTIFICATION); - ibutton->key = ibutton_key_alloc(); - ibutton->key_worker = ibutton_worker_alloc(); - ibutton_worker_start_thread(ibutton->key_worker); + ibutton->protocols = ibutton_protocols_alloc(); + ibutton->key = ibutton_key_alloc(ibutton_protocols_get_max_data_size(ibutton->protocols)); + ibutton->worker = ibutton_worker_alloc(ibutton->protocols); + ibutton_worker_start_thread(ibutton->worker); ibutton->submenu = submenu_alloc(); view_dispatcher_add_view( @@ -163,9 +122,9 @@ iButton* ibutton_alloc() { view_dispatcher_add_view( ibutton->view_dispatcher, iButtonViewWidget, widget_get_view(ibutton->widget)); - ibutton->dialog_ex = dialog_ex_alloc(); + ibutton->loading = loading_alloc(); view_dispatcher_add_view( - ibutton->view_dispatcher, iButtonViewDialogEx, dialog_ex_get_view(ibutton->dialog_ex)); + ibutton->view_dispatcher, iButtonViewLoading, loading_get_view(ibutton->loading)); return ibutton; } @@ -173,8 +132,8 @@ iButton* ibutton_alloc() { void ibutton_free(iButton* ibutton) { furi_assert(ibutton); - view_dispatcher_remove_view(ibutton->view_dispatcher, iButtonViewDialogEx); - dialog_ex_free(ibutton->dialog_ex); + view_dispatcher_remove_view(ibutton->view_dispatcher, iButtonViewLoading); + loading_free(ibutton->loading); view_dispatcher_remove_view(ibutton->view_dispatcher, iButtonViewWidget); widget_free(ibutton->widget); @@ -194,9 +153,6 @@ void ibutton_free(iButton* ibutton) { view_dispatcher_free(ibutton->view_dispatcher); scene_manager_free(ibutton->scene_manager); - furi_record_close(RECORD_STORAGE); - ibutton->storage = NULL; - furi_record_close(RECORD_NOTIFICATION); ibutton->notifications = NULL; @@ -206,103 +162,83 @@ void ibutton_free(iButton* ibutton) { furi_record_close(RECORD_GUI); ibutton->gui = NULL; - ibutton_worker_stop_thread(ibutton->key_worker); - ibutton_worker_free(ibutton->key_worker); + ibutton_worker_stop_thread(ibutton->worker); + ibutton_worker_free(ibutton->worker); ibutton_key_free(ibutton->key); + ibutton_protocols_free(ibutton->protocols); furi_string_free(ibutton->file_path); free(ibutton); } -bool ibutton_file_select(iButton* ibutton) { - DialogsFileBrowserOptions browser_options; - dialog_file_browser_set_basic_options(&browser_options, IBUTTON_APP_EXTENSION, &I_ibutt_10px); - browser_options.base_path = IBUTTON_APP_FOLDER; - - bool success = dialog_file_browser_show( - ibutton->dialogs, ibutton->file_path, ibutton->file_path, &browser_options); +bool ibutton_load_key(iButton* ibutton) { + view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewLoading); - if(success) { - success = ibutton_load_key_data(ibutton, ibutton->file_path, true); - } - - return success; -} - -bool ibutton_save_key(iButton* ibutton, const char* key_name) { - // Create ibutton directory if necessary - ibutton_make_app_folder(ibutton); - - FlipperFormat* file = flipper_format_file_alloc(ibutton->storage); - iButtonKey* key = ibutton->key; + const bool success = ibutton_protocols_load( + ibutton->protocols, ibutton->key, furi_string_get_cstr(ibutton->file_path)); - bool result = false; + if(!success) { + dialog_message_show_storage_error(ibutton->dialogs, "Cannot load\nkey file"); - do { - // Check if we has old key - if(furi_string_end_with(ibutton->file_path, IBUTTON_APP_EXTENSION)) { - // First remove old key - ibutton_delete_key(ibutton); + } else { + FuriString* tmp = furi_string_alloc(); - // Remove old key name from path - size_t filename_start = furi_string_search_rchar(ibutton->file_path, '/'); - furi_string_left(ibutton->file_path, filename_start); - } + path_extract_filename(ibutton->file_path, tmp, true); + strncpy(ibutton->key_name, furi_string_get_cstr(tmp), IBUTTON_KEY_NAME_SIZE); - furi_string_cat_printf(ibutton->file_path, "/%s%s", key_name, IBUTTON_APP_EXTENSION); + furi_string_free(tmp); + } - // Open file for write - if(!flipper_format_file_open_always(file, furi_string_get_cstr(ibutton->file_path))) break; + return success; +} - // Write header - if(!flipper_format_write_header_cstr(file, IBUTTON_APP_FILE_TYPE, 1)) break; +bool ibutton_select_and_load_key(iButton* ibutton) { + DialogsFileBrowserOptions browser_options; + dialog_file_browser_set_basic_options(&browser_options, IBUTTON_APP_EXTENSION, &I_ibutt_10px); + browser_options.base_path = IBUTTON_APP_FOLDER; - // Write key type - if(!flipper_format_write_comment_cstr(file, "Key type can be Cyfral, Dallas or Metakom")) - break; - const char* key_type = ibutton_key_get_string_by_type(ibutton_key_get_type(key)); - if(!flipper_format_write_string_cstr(file, "Key type", key_type)) break; + if(furi_string_empty(ibutton->file_path)) { + furi_string_set(ibutton->file_path, browser_options.base_path); + } - // Write data - if(!flipper_format_write_comment_cstr( - file, "Data size for Cyfral is 2, for Metakom is 4, for Dallas is 8")) - break; + return dialog_file_browser_show( + ibutton->dialogs, ibutton->file_path, ibutton->file_path, &browser_options) && + ibutton_load_key(ibutton); +} - if(!flipper_format_write_hex( - file, "Data", ibutton_key_get_data_p(key), ibutton_key_get_data_size(key))) - break; - result = true; +bool ibutton_save_key(iButton* ibutton) { + view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewLoading); - } while(false); + ibutton_make_app_folder(ibutton); - flipper_format_free(file); + iButtonKey* key = ibutton->key; + const bool success = + ibutton_protocols_save(ibutton->protocols, key, furi_string_get_cstr(ibutton->file_path)); - if(!result) { //-V547 + if(!success) { dialog_message_show_storage_error(ibutton->dialogs, "Cannot save\nkey file"); } - return result; + return success; } bool ibutton_delete_key(iButton* ibutton) { bool result = false; - result = storage_simply_remove(ibutton->storage, furi_string_get_cstr(ibutton->file_path)); - return result; -} - -void ibutton_text_store_set(iButton* ibutton, const char* text, ...) { - va_list args; - va_start(args, text); + Storage* storage = furi_record_open(RECORD_STORAGE); + result = storage_simply_remove(storage, furi_string_get_cstr(ibutton->file_path)); + furi_record_close(RECORD_STORAGE); - vsnprintf(ibutton->text_store, IBUTTON_TEXT_STORE_SIZE, text, args); + ibutton_reset_key(ibutton); - va_end(args); + return result; } -void ibutton_text_store_clear(iButton* ibutton) { - memset(ibutton->text_store, 0, IBUTTON_TEXT_STORE_SIZE + 1); +void ibutton_reset_key(iButton* ibutton) { + memset(ibutton->key_name, 0, IBUTTON_KEY_NAME_SIZE + 1); + furi_string_reset(ibutton->file_path); + ibutton_key_reset(ibutton->key); } void ibutton_notification_message(iButton* ibutton, uint32_t message) { @@ -310,36 +246,44 @@ void ibutton_notification_message(iButton* ibutton, uint32_t message) { notification_message(ibutton->notifications, ibutton_notification_sequences[message]); } -int32_t ibutton_app(void* p) { +void ibutton_submenu_callback(void* context, uint32_t index) { + iButton* ibutton = context; + view_dispatcher_send_custom_event(ibutton->view_dispatcher, index); +} + +void ibutton_widget_callback(GuiButtonType result, InputType type, void* context) { + iButton* ibutton = context; + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(ibutton->view_dispatcher, result); + } +} + +int32_t ibutton_app(void* arg) { iButton* ibutton = ibutton_alloc(); ibutton_make_app_folder(ibutton); bool key_loaded = false; - bool rpc_mode = false; - if(p && strlen(p)) { - uint32_t rpc_ctx = 0; - if(sscanf(p, "RPC %lX", &rpc_ctx) == 1) { + if((arg != NULL) && (strlen(arg) != 0)) { + if(sscanf(arg, "RPC %lX", (uint32_t*)&ibutton->rpc) == 1) { FURI_LOG_D(TAG, "Running in RPC mode"); - ibutton->rpc_ctx = (void*)rpc_ctx; - rpc_mode = true; - rpc_system_app_set_callback(ibutton->rpc_ctx, ibutton_rpc_command_callback, ibutton); - rpc_system_app_send_started(ibutton->rpc_ctx); + + rpc_system_app_set_callback(ibutton->rpc, ibutton_rpc_command_callback, ibutton); + rpc_system_app_send_started(ibutton->rpc); + } else { - furi_string_set(ibutton->file_path, (const char*)p); - if(ibutton_load_key_data(ibutton, ibutton->file_path, true)) { - key_loaded = true; - // TODO: Display an error if the key from p could not be loaded - } + furi_string_set(ibutton->file_path, (const char*)arg); + key_loaded = ibutton_load_key(ibutton); } } - if(rpc_mode) { + if(ibutton->rpc != NULL) { view_dispatcher_attach_to_gui( ibutton->view_dispatcher, ibutton->gui, ViewDispatcherTypeDesktop); scene_manager_next_scene(ibutton->scene_manager, iButtonSceneRpc); DOLPHIN_DEED(DolphinDeedIbuttonEmulate); + } else { view_dispatcher_attach_to_gui( ibutton->view_dispatcher, ibutton->gui, ViewDispatcherTypeFullscreen); @@ -353,9 +297,9 @@ int32_t ibutton_app(void* p) { view_dispatcher_run(ibutton->view_dispatcher); - if(ibutton->rpc_ctx) { - rpc_system_app_set_callback(ibutton->rpc_ctx, NULL, NULL); - rpc_system_app_send_exited(ibutton->rpc_ctx); + if(ibutton->rpc) { + rpc_system_app_set_callback(ibutton->rpc, NULL, NULL); + rpc_system_app_send_exited(ibutton->rpc); } ibutton_free(ibutton); return 0; diff --git a/applications/main/ibutton/ibutton_cli.c b/applications/main/ibutton/ibutton_cli.c index 9ddb079dc9c..2b88b200769 100644 --- a/applications/main/ibutton/ibutton_cli.c +++ b/applications/main/ibutton/ibutton_cli.c @@ -1,11 +1,15 @@ #include #include -#include + #include -#include -#include +#include + #include +#include +#include +#include + static void ibutton_cli(Cli* cli, FuriString* args, void* context); static void onewire_cli(Cli* cli, FuriString* args, void* context); @@ -22,7 +26,7 @@ void ibutton_on_system_start() { #endif } -void ibutton_cli_print_usage() { +static void ibutton_cli_print_usage() { printf("Usage:\r\n"); printf("ikey read\r\n"); printf("ikey emulate \r\n"); @@ -34,30 +38,52 @@ void ibutton_cli_print_usage() { printf("\t are hex-formatted\r\n"); }; -bool ibutton_cli_get_key_type(FuriString* data, iButtonKeyType* type) { +static bool ibutton_cli_parse_key(iButtonProtocols* protocols, iButtonKey* key, FuriString* args) { bool result = false; + FuriString* name = furi_string_alloc(); + + do { + // Read protocol name + if(!args_read_string_and_trim(args, name)) break; + + // Make the protocol name uppercase + const char first = furi_string_get_char(name, 0); + furi_string_set_char(name, 0, toupper((int)first)); + + const iButtonProtocolId id = + ibutton_protocols_get_id_by_name(protocols, furi_string_get_cstr(name)); + if(id == iButtonProtocolIdInvalid) break; + + ibutton_key_set_protocol_id(key, id); + + // Get the data pointer + iButtonEditableData data; + ibutton_protocols_get_editable_data(protocols, key, &data); + + // Read data + if(!args_read_hex_bytes(args, data.ptr, data.size)) break; - if(furi_string_cmp_str(data, "Dallas") == 0 || furi_string_cmp_str(data, "dallas") == 0) { - result = true; - *type = iButtonKeyDS1990; - } else if(furi_string_cmp_str(data, "Cyfral") == 0 || furi_string_cmp_str(data, "cyfral") == 0) { - result = true; - *type = iButtonKeyCyfral; - } else if(furi_string_cmp_str(data, "Metakom") == 0 || furi_string_cmp_str(data, "metakom") == 0) { result = true; - *type = iButtonKeyMetakom; - } + } while(false); + furi_string_free(name); return result; } -void ibutton_cli_print_key_data(iButtonKey* key) { - const uint8_t* key_data = ibutton_key_get_data_p(key); - iButtonKeyType type = ibutton_key_get_type(key); +static void ibutton_cli_print_key(iButtonProtocols* protocols, iButtonKey* key) { + const char* name = ibutton_protocols_get_name(protocols, ibutton_key_get_protocol_id(key)); - printf("%s ", ibutton_key_get_string_by_type(type)); - for(size_t i = 0; i < ibutton_key_get_size_by_type(type); i++) { - printf("%02X", key_data[i]); + if(strncmp(name, "DS", 2) == 0) { + name = "Dallas"; + } + + printf("%s ", name); + + iButtonEditableData data; + ibutton_protocols_get_editable_data(protocols, key, &data); + + for(size_t i = 0; i < data.size; i++) { + printf("%02X", data.ptr[i]); } printf("\r\n"); @@ -71,9 +97,10 @@ static void ibutton_cli_worker_read_cb(void* context) { furi_event_flag_set(event, EVENT_FLAG_IBUTTON_COMPLETE); } -void ibutton_cli_read(Cli* cli) { - iButtonKey* key = ibutton_key_alloc(); - iButtonWorker* worker = ibutton_worker_alloc(); +static void ibutton_cli_read(Cli* cli) { + iButtonProtocols* protocols = ibutton_protocols_alloc(); + iButtonWorker* worker = ibutton_worker_alloc(protocols); + iButtonKey* key = ibutton_key_alloc(ibutton_protocols_get_max_data_size(protocols)); FuriEventFlag* event = furi_event_flag_alloc(); ibutton_worker_start_thread(worker); @@ -81,32 +108,25 @@ void ibutton_cli_read(Cli* cli) { printf("Reading iButton...\r\nPress Ctrl+C to abort\r\n"); ibutton_worker_read_start(worker, key); + while(true) { uint32_t flags = furi_event_flag_wait(event, EVENT_FLAG_IBUTTON_COMPLETE, FuriFlagWaitAny, 100); if(flags & EVENT_FLAG_IBUTTON_COMPLETE) { - ibutton_cli_print_key_data(key); - - if(ibutton_key_get_type(key) == iButtonKeyDS1990) { - if(!ibutton_key_dallas_crc_is_valid(key)) { - printf("Warning: invalid CRC\r\n"); - } - - if(!ibutton_key_dallas_is_1990_key(key)) { - printf("Warning: not a key\r\n"); - } - } + ibutton_cli_print_key(protocols, key); break; } if(cli_cmd_interrupt_received(cli)) break; } - ibutton_worker_stop(worker); + ibutton_worker_stop(worker); ibutton_worker_stop_thread(worker); - ibutton_worker_free(worker); + ibutton_key_free(key); + ibutton_worker_free(worker); + ibutton_protocols_free(protocols); furi_event_flag_free(event); }; @@ -124,48 +144,33 @@ static void ibutton_cli_worker_write_cb(void* context, iButtonWorkerWriteResult } void ibutton_cli_write(Cli* cli, FuriString* args) { - iButtonKey* key = ibutton_key_alloc(); - iButtonWorker* worker = ibutton_worker_alloc(); - iButtonKeyType type; - iButtonWriteContext write_context; - uint8_t key_data[IBUTTON_KEY_DATA_SIZE]; - FuriString* data; + iButtonProtocols* protocols = ibutton_protocols_alloc(); + iButtonWorker* worker = ibutton_worker_alloc(protocols); + iButtonKey* key = ibutton_key_alloc(ibutton_protocols_get_max_data_size(protocols)); + iButtonWriteContext write_context; write_context.event = furi_event_flag_alloc(); - data = furi_string_alloc(); ibutton_worker_start_thread(worker); ibutton_worker_write_set_callback(worker, ibutton_cli_worker_write_cb, &write_context); do { - if(!args_read_string_and_trim(args, data)) { - ibutton_cli_print_usage(); - break; - } - - if(!ibutton_cli_get_key_type(data, &type)) { - ibutton_cli_print_usage(); - break; - } - - if(type != iButtonKeyDS1990) { + if(!ibutton_cli_parse_key(protocols, key, args)) { ibutton_cli_print_usage(); break; } - if(!args_read_hex_bytes(args, key_data, ibutton_key_get_size_by_type(type))) { + if(!(ibutton_protocols_get_features(protocols, ibutton_key_get_protocol_id(key)) & + iButtonProtocolFeatureWriteBlank)) { ibutton_cli_print_usage(); break; } - ibutton_key_set_type(key, type); - ibutton_key_set_data(key, key_data, ibutton_key_get_size_by_type(type)); - printf("Writing key "); - ibutton_cli_print_key_data(key); + ibutton_cli_print_key(protocols, key); printf("Press Ctrl+C to abort\r\n"); - ibutton_worker_write_start(worker, key); + ibutton_worker_write_blank_start(worker, key); while(true) { uint32_t flags = furi_event_flag_wait( write_context.event, EVENT_FLAG_IBUTTON_COMPLETE, FuriFlagWaitAny, 100); @@ -183,64 +188,53 @@ void ibutton_cli_write(Cli* cli, FuriString* args) { if(cli_cmd_interrupt_received(cli)) break; } - ibutton_worker_stop(worker); } while(false); - furi_string_free(data); + ibutton_worker_stop(worker); ibutton_worker_stop_thread(worker); - ibutton_worker_free(worker); + ibutton_key_free(key); + ibutton_worker_free(worker); + ibutton_protocols_free(protocols); furi_event_flag_free(write_context.event); -}; +} void ibutton_cli_emulate(Cli* cli, FuriString* args) { - iButtonKey* key = ibutton_key_alloc(); - iButtonWorker* worker = ibutton_worker_alloc(); - iButtonKeyType type; - uint8_t key_data[IBUTTON_KEY_DATA_SIZE]; - FuriString* data; + iButtonProtocols* protocols = ibutton_protocols_alloc(); + iButtonWorker* worker = ibutton_worker_alloc(protocols); + iButtonKey* key = ibutton_key_alloc(ibutton_protocols_get_max_data_size(protocols)); - data = furi_string_alloc(); ibutton_worker_start_thread(worker); do { - if(!args_read_string_and_trim(args, data)) { + if(!ibutton_cli_parse_key(protocols, key, args)) { ibutton_cli_print_usage(); break; } - if(!ibutton_cli_get_key_type(data, &type)) { - ibutton_cli_print_usage(); - break; - } - - if(!args_read_hex_bytes(args, key_data, ibutton_key_get_size_by_type(type))) { - ibutton_cli_print_usage(); - break; - } - - ibutton_key_set_type(key, type); - ibutton_key_set_data(key, key_data, ibutton_key_get_size_by_type(type)); - printf("Emulating key "); - ibutton_cli_print_key_data(key); + ibutton_cli_print_key(protocols, key); printf("Press Ctrl+C to abort\r\n"); ibutton_worker_emulate_start(worker, key); + while(!cli_cmd_interrupt_received(cli)) { furi_delay_ms(100); }; - ibutton_worker_stop(worker); + } while(false); - furi_string_free(data); + ibutton_worker_stop(worker); ibutton_worker_stop_thread(worker); - ibutton_worker_free(worker); + ibutton_key_free(key); + ibutton_worker_free(worker); + ibutton_protocols_free(protocols); }; -static void ibutton_cli(Cli* cli, FuriString* args, void* context) { +void ibutton_cli(Cli* cli, FuriString* args, void* context) { + UNUSED(cli); UNUSED(context); FuriString* cmd; cmd = furi_string_alloc(); @@ -264,7 +258,7 @@ static void ibutton_cli(Cli* cli, FuriString* args, void* context) { furi_string_free(cmd); } -void onewire_cli_print_usage() { +static void onewire_cli_print_usage() { printf("Usage:\r\n"); printf("onewire search\r\n"); }; @@ -281,7 +275,7 @@ static void onewire_cli_search(Cli* cli) { furi_hal_power_enable_otg(); while(!done) { - if(onewire_host_search(onewire, address, NORMAL_SEARCH) != 1) { + if(onewire_host_search(onewire, address, OneWireHostSearchModeNormal) != 1) { printf("Search finished\r\n"); onewire_host_reset_search(onewire); done = true; diff --git a/applications/main/ibutton/ibutton_custom_event.h b/applications/main/ibutton/ibutton_custom_event.h index 1e2f0300dca..28bcb94a0dc 100644 --- a/applications/main/ibutton/ibutton_custom_event.h +++ b/applications/main/ibutton/ibutton_custom_event.h @@ -6,6 +6,7 @@ enum iButtonCustomEvent { iButtonCustomEventBack, iButtonCustomEventTextEditResult, + iButtonCustomEventByteEditChanged, iButtonCustomEventByteEditResult, iButtonCustomEventWorkerEmulated, iButtonCustomEventWorkerRead, diff --git a/applications/main/ibutton/ibutton_i.h b/applications/main/ibutton/ibutton_i.h index 0a80993517b..8ad0b90e4a8 100644 --- a/applications/main/ibutton/ibutton_i.h +++ b/applications/main/ibutton/ibutton_i.h @@ -4,31 +4,40 @@ #include #include -#include -#include #include -#include +#include #include +#include + +#include #include #include +#include +#include #include #include -#include #include #include #include +#include + +#include #include "ibutton_custom_event.h" #include "scenes/ibutton_scene.h" -#define IBUTTON_FILE_NAME_SIZE 100 -#define IBUTTON_TEXT_STORE_SIZE 128 - #define IBUTTON_APP_FOLDER ANY_PATH("ibutton") #define IBUTTON_APP_EXTENSION ".ibtn" -#define IBUTTON_APP_FILE_TYPE "Flipper iButton key" + +#define IBUTTON_KEY_NAME_SIZE 22 + +typedef enum { + iButtonWriteModeInvalid, + iButtonWriteModeBlank, + iButtonWriteModeCopy, +} iButtonWriteMode; struct iButton { SceneManager* scene_manager; @@ -38,21 +47,22 @@ struct iButton { Storage* storage; DialogsApp* dialogs; NotificationApp* notifications; + RpcAppSystem* rpc; - iButtonWorker* key_worker; iButtonKey* key; + iButtonWorker* worker; + iButtonProtocols* protocols; + iButtonWriteMode write_mode; FuriString* file_path; - char text_store[IBUTTON_TEXT_STORE_SIZE + 1]; + char key_name[IBUTTON_KEY_NAME_SIZE + 1]; Submenu* submenu; ByteInput* byte_input; TextInput* text_input; Popup* popup; Widget* widget; - DialogEx* dialog_ex; - - void* rpc_ctx; + Loading* loading; }; typedef enum { @@ -61,7 +71,7 @@ typedef enum { iButtonViewTextInput, iButtonViewPopup, iButtonViewWidget, - iButtonViewDialogEx, + iButtonViewLoading, } iButtonView; typedef enum { @@ -78,10 +88,12 @@ typedef enum { iButtonNotificationMessageBlinkStop, } iButtonNotificationMessage; -bool ibutton_file_select(iButton* ibutton); -bool ibutton_load_key_data(iButton* ibutton, FuriString* key_path, bool show_dialog); -bool ibutton_save_key(iButton* ibutton, const char* key_name); +bool ibutton_select_and_load_key(iButton* ibutton); +bool ibutton_load_key(iButton* ibutton); +bool ibutton_save_key(iButton* ibutton); bool ibutton_delete_key(iButton* ibutton); -void ibutton_text_store_set(iButton* ibutton, const char* text, ...); -void ibutton_text_store_clear(iButton* ibutton); +void ibutton_reset_key(iButton* ibutton); void ibutton_notification_message(iButton* ibutton, uint32_t message); + +void ibutton_submenu_callback(void* context, uint32_t index); +void ibutton_widget_callback(GuiButtonType result, InputType type, void* context); diff --git a/applications/main/ibutton/scenes/ibutton_scene_add_type.c b/applications/main/ibutton/scenes/ibutton_scene_add_type.c index 38373999c97..968bbac1cf7 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_add_type.c +++ b/applications/main/ibutton/scenes/ibutton_scene_add_type.c @@ -1,54 +1,48 @@ #include "../ibutton_i.h" -enum SubmenuIndex { - SubmenuIndexCyfral, - SubmenuIndexDallas, - SubmenuIndexMetakom, -}; - -void ibutton_scene_add_type_submenu_callback(void* context, uint32_t index) { - iButton* ibutton = context; - view_dispatcher_send_custom_event(ibutton->view_dispatcher, index); -} - void ibutton_scene_add_type_on_enter(void* context) { iButton* ibutton = context; Submenu* submenu = ibutton->submenu; - submenu_add_item( - submenu, "Cyfral", SubmenuIndexCyfral, ibutton_scene_add_type_submenu_callback, ibutton); - submenu_add_item( - submenu, "Dallas", SubmenuIndexDallas, ibutton_scene_add_type_submenu_callback, ibutton); - submenu_add_item( - submenu, "Metakom", SubmenuIndexMetakom, ibutton_scene_add_type_submenu_callback, ibutton); + FuriString* tmp = furi_string_alloc(); + + for(uint32_t protocol_id = 0; protocol_id < ibutton_protocols_get_protocol_count(); + ++protocol_id) { + furi_string_printf( + tmp, + "%s %s", + ibutton_protocols_get_manufacturer(ibutton->protocols, protocol_id), + ibutton_protocols_get_name(ibutton->protocols, protocol_id)); + + submenu_add_item( + submenu, furi_string_get_cstr(tmp), protocol_id, ibutton_submenu_callback, context); + } - submenu_set_selected_item( - submenu, scene_manager_get_scene_state(ibutton->scene_manager, iButtonSceneAddType)); + const uint32_t prev_protocol_id = + scene_manager_get_scene_state(ibutton->scene_manager, iButtonSceneAddType); + submenu_set_selected_item(submenu, prev_protocol_id); view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewSubmenu); + furi_string_free(tmp); } bool ibutton_scene_add_type_on_event(void* context, SceneManagerEvent event) { iButton* ibutton = context; iButtonKey* key = ibutton->key; + bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { - scene_manager_set_scene_state(ibutton->scene_manager, iButtonSceneAddType, event.event); - consumed = true; - if(event.event == SubmenuIndexCyfral) { - ibutton_key_set_type(key, iButtonKeyCyfral); - } else if(event.event == SubmenuIndexDallas) { - ibutton_key_set_type(key, iButtonKeyDS1990); - } else if(event.event == SubmenuIndexMetakom) { - ibutton_key_set_type(key, iButtonKeyMetakom); - } else { - furi_crash("Unknown key type"); - } - - furi_string_set(ibutton->file_path, IBUTTON_APP_FOLDER); - ibutton_key_clear_data(key); + const iButtonProtocolId protocol_id = event.event; + + ibutton_key_reset(key); + ibutton_key_set_protocol_id(key, protocol_id); + ibutton_protocols_apply_edits(ibutton->protocols, key); + + scene_manager_set_scene_state(ibutton->scene_manager, iButtonSceneAddType, protocol_id); scene_manager_next_scene(ibutton->scene_manager, iButtonSceneAddValue); + + consumed = true; } return consumed; diff --git a/applications/main/ibutton/scenes/ibutton_scene_add_value.c b/applications/main/ibutton/scenes/ibutton_scene_add_value.c index ccac7612102..dc340771b2a 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_add_value.c +++ b/applications/main/ibutton/scenes/ibutton_scene_add_value.c @@ -1,42 +1,52 @@ #include "../ibutton_i.h" -void ibutton_scene_add_type_byte_input_callback(void* context) { +static void ibutton_scene_add_type_byte_input_callback(void* context) { iButton* ibutton = context; view_dispatcher_send_custom_event(ibutton->view_dispatcher, iButtonCustomEventByteEditResult); } +static void ibutton_scene_add_type_byte_changed_callback(void* context) { + iButton* ibutton = context; + view_dispatcher_send_custom_event(ibutton->view_dispatcher, iButtonCustomEventByteEditChanged); +} + void ibutton_scene_add_value_on_enter(void* context) { iButton* ibutton = context; - iButtonKey* key = ibutton->key; - uint8_t* new_key_data = malloc(IBUTTON_KEY_DATA_SIZE); + byte_input_set_header_text(ibutton->byte_input, "Enter the key"); - scene_manager_set_scene_state( - ibutton->scene_manager, iButtonSceneAddValue, (uint32_t)new_key_data); - memcpy(new_key_data, ibutton_key_get_data_p(key), ibutton_key_get_data_size(key)); + iButtonEditableData editable_data; + ibutton_protocols_get_editable_data(ibutton->protocols, ibutton->key, &editable_data); byte_input_set_result_callback( ibutton->byte_input, ibutton_scene_add_type_byte_input_callback, - NULL, - ibutton, - new_key_data, - ibutton_key_get_data_size(key)); + ibutton_scene_add_type_byte_changed_callback, + context, + editable_data.ptr, + editable_data.size); - byte_input_set_header_text(ibutton->byte_input, "Enter the key"); view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewByteInput); } bool ibutton_scene_add_value_on_event(void* context, SceneManagerEvent event) { iButton* ibutton = context; - uint8_t* new_key_data = - (uint8_t*)scene_manager_get_scene_state(ibutton->scene_manager, iButtonSceneAddValue); + SceneManager* scene_manager = ibutton->scene_manager; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { consumed = true; if(event.event == iButtonCustomEventByteEditResult) { - ibutton_key_set_data(ibutton->key, new_key_data, IBUTTON_KEY_DATA_SIZE); - scene_manager_next_scene(ibutton->scene_manager, iButtonSceneSaveName); + scene_manager_next_scene(scene_manager, iButtonSceneSaveName); + } else if(event.event == iButtonCustomEventByteEditChanged) { + ibutton_protocols_apply_edits(ibutton->protocols, ibutton->key); + } + } else if(event.type == SceneManagerEventTypeBack) { + // User cancelled editing, reload the key from storage + if(scene_manager_has_previous_scene(scene_manager, iButtonSceneSavedKeyMenu)) { + if(!ibutton_load_key(ibutton)) { + consumed = scene_manager_search_and_switch_to_previous_scene( + scene_manager, iButtonSceneStart); + } } } @@ -45,10 +55,7 @@ bool ibutton_scene_add_value_on_event(void* context, SceneManagerEvent event) { void ibutton_scene_add_value_on_exit(void* context) { iButton* ibutton = context; - uint8_t* new_key_data = - (uint8_t*)scene_manager_get_scene_state(ibutton->scene_manager, iButtonSceneAddValue); byte_input_set_result_callback(ibutton->byte_input, NULL, NULL, NULL, NULL, 0); byte_input_set_header_text(ibutton->byte_input, NULL); - free(new_key_data); } diff --git a/applications/main/ibutton/scenes/ibutton_scene_config.h b/applications/main/ibutton/scenes/ibutton_scene_config.h index 87fa1a036a7..79f6791b3b9 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_config.h +++ b/applications/main/ibutton/scenes/ibutton_scene_config.h @@ -6,8 +6,7 @@ ADD_SCENE(ibutton, info, Info) ADD_SCENE(ibutton, read, Read) ADD_SCENE(ibutton, read_key_menu, ReadKeyMenu) ADD_SCENE(ibutton, read_success, ReadSuccess) -ADD_SCENE(ibutton, read_crc_error, ReadCRCError) -ADD_SCENE(ibutton, read_not_key_error, ReadNotKeyError) +ADD_SCENE(ibutton, read_error, ReadError) ADD_SCENE(ibutton, select_key, SelectKey) ADD_SCENE(ibutton, add_type, AddType) ADD_SCENE(ibutton, add_value, AddValue) @@ -18,4 +17,5 @@ ADD_SCENE(ibutton, delete_confirm, DeleteConfirm) ADD_SCENE(ibutton, delete_success, DeleteSuccess) ADD_SCENE(ibutton, retry_confirm, RetryConfirm) ADD_SCENE(ibutton, exit_confirm, ExitConfirm) +ADD_SCENE(ibutton, view_data, ViewData) ADD_SCENE(ibutton, rpc, Rpc) diff --git a/applications/main/ibutton/scenes/ibutton_scene_delete_confirm.c b/applications/main/ibutton/scenes/ibutton_scene_delete_confirm.c index 3d609e833f2..587cb748cd4 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_delete_confirm.c +++ b/applications/main/ibutton/scenes/ibutton_scene_delete_confirm.c @@ -1,74 +1,29 @@ #include "../ibutton_i.h" #include -static void ibutton_scene_delete_confirm_widget_callback( - GuiButtonType result, - InputType type, - void* context) { - iButton* ibutton = context; - if(type == InputTypeShort) { - view_dispatcher_send_custom_event(ibutton->view_dispatcher, result); - } -} - void ibutton_scene_delete_confirm_on_enter(void* context) { iButton* ibutton = context; - Widget* widget = ibutton->widget; iButtonKey* key = ibutton->key; - const uint8_t* key_data = ibutton_key_get_data_p(key); + Widget* widget = ibutton->widget; - FuriString* key_name; - key_name = furi_string_alloc(); - path_extract_filename(ibutton->file_path, key_name, true); + FuriString* tmp = furi_string_alloc(); - ibutton_text_store_set(ibutton, "\e#Delete %s?\e#", furi_string_get_cstr(key_name)); - widget_add_text_box_element( - widget, 0, 0, 128, 27, AlignCenter, AlignCenter, ibutton->text_store, true); + widget_add_button_element(widget, GuiButtonTypeLeft, "Back", ibutton_widget_callback, context); widget_add_button_element( - widget, GuiButtonTypeLeft, "Cancel", ibutton_scene_delete_confirm_widget_callback, ibutton); - widget_add_button_element( - widget, - GuiButtonTypeRight, - "Delete", - ibutton_scene_delete_confirm_widget_callback, - ibutton); + widget, GuiButtonTypeRight, "Delete", ibutton_widget_callback, context); - switch(ibutton_key_get_type(key)) { - case iButtonKeyDS1990: - ibutton_text_store_set( - ibutton, - "%02X %02X %02X %02X %02X %02X %02X %02X", - key_data[0], - key_data[1], - key_data[2], - key_data[3], - key_data[4], - key_data[5], - key_data[6], - key_data[7]); - widget_add_string_element( - widget, 64, 34, AlignCenter, AlignBottom, FontSecondary, "Dallas"); - break; + furi_string_printf(tmp, "Delete %s?", ibutton->key_name); + widget_add_string_element( + widget, 128 / 2, 0, AlignCenter, AlignTop, FontPrimary, furi_string_get_cstr(tmp)); - case iButtonKeyCyfral: - ibutton_text_store_set(ibutton, "%02X %02X", key_data[0], key_data[1]); - widget_add_string_element( - widget, 64, 34, AlignCenter, AlignBottom, FontSecondary, "Cyfral"); - break; + furi_string_reset(tmp); + ibutton_protocols_render_brief_data(ibutton->protocols, key, tmp); - case iButtonKeyMetakom: - ibutton_text_store_set( - ibutton, "%02X %02X %02X %02X", key_data[0], key_data[1], key_data[2], key_data[3]); - widget_add_string_element( - widget, 64, 34, AlignCenter, AlignBottom, FontSecondary, "Metakom"); - break; - } - widget_add_string_element( - widget, 64, 46, AlignCenter, AlignBottom, FontSecondary, ibutton->text_store); + widget_add_string_multiline_element( + widget, 128 / 2, 16, AlignCenter, AlignTop, FontSecondary, furi_string_get_cstr(tmp)); view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget); - - furi_string_free(key_name); + furi_string_free(tmp); } bool ibutton_scene_delete_confirm_on_event(void* context, SceneManagerEvent event) { @@ -81,8 +36,10 @@ bool ibutton_scene_delete_confirm_on_event(void* context, SceneManagerEvent even if(event.event == GuiButtonTypeRight) { if(ibutton_delete_key(ibutton)) { scene_manager_next_scene(scene_manager, iButtonSceneDeleteSuccess); + } else { + dialog_message_show_storage_error(ibutton->dialogs, "Cannot delete\nkey file"); + scene_manager_previous_scene(scene_manager); } - //TODO: What if the key could not be deleted? } else if(event.event == GuiButtonTypeLeft) { scene_manager_previous_scene(scene_manager); } @@ -93,6 +50,5 @@ bool ibutton_scene_delete_confirm_on_event(void* context, SceneManagerEvent even void ibutton_scene_delete_confirm_on_exit(void* context) { iButton* ibutton = context; - ibutton_text_store_clear(ibutton); widget_reset(ibutton->widget); } diff --git a/applications/main/ibutton/scenes/ibutton_scene_emulate.c b/applications/main/ibutton/scenes/ibutton_scene_emulate.c index 6f6ffcf574c..713b8331c36 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_emulate.c +++ b/applications/main/ibutton/scenes/ibutton_scene_emulate.c @@ -14,61 +14,32 @@ static void ibutton_scene_emulate_callback(void* context, bool emulated) { void ibutton_scene_emulate_on_enter(void* context) { iButton* ibutton = context; - Widget* widget = ibutton->widget; iButtonKey* key = ibutton->key; - const uint8_t* key_data = ibutton_key_get_data_p(key); + Widget* widget = ibutton->widget; + FuriString* tmp = furi_string_alloc(); - FuriString* key_name; - key_name = furi_string_alloc(); - if(furi_string_end_with(ibutton->file_path, IBUTTON_APP_EXTENSION)) { - path_extract_filename(ibutton->file_path, key_name, true); - } + widget_add_icon_element(widget, 3, 10, &I_iButtonKey_49x44); - // check that stored key has name - if(!furi_string_empty(key_name)) { - ibutton_text_store_set(ibutton, "%s", furi_string_get_cstr(key_name)); - } else { - // if not, show key data - switch(ibutton_key_get_type(key)) { - case iButtonKeyDS1990: - ibutton_text_store_set( - ibutton, - "%02X %02X %02X %02X\n%02X %02X %02X %02X", - key_data[0], - key_data[1], - key_data[2], - key_data[3], - key_data[4], - key_data[5], - key_data[6], - key_data[7]); - break; - case iButtonKeyCyfral: - ibutton_text_store_set(ibutton, "%02X %02X", key_data[0], key_data[1]); - break; - case iButtonKeyMetakom: - ibutton_text_store_set( - ibutton, "%02X %02X %02X %02X", key_data[0], key_data[1], key_data[2], key_data[3]); - break; - } - } + furi_string_printf( + tmp, + "%s\n[%s]", + furi_string_empty(ibutton->file_path) ? "Unsaved Key" : ibutton->key_name, + ibutton_protocols_get_name(ibutton->protocols, ibutton_key_get_protocol_id(key))); - widget_add_string_multiline_element( - widget, 90, 10, AlignCenter, AlignTop, FontPrimary, "iButton\nemulating"); - widget_add_icon_element(widget, 3, 10, &I_iButtonKey_49x44); widget_add_text_box_element( - widget, 54, 39, 75, 22, AlignCenter, AlignCenter, ibutton->text_store, true); - - view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget); + widget, 52, 38, 75, 26, AlignCenter, AlignCenter, furi_string_get_cstr(tmp), true); - ibutton_worker_emulate_set_callback( - ibutton->key_worker, ibutton_scene_emulate_callback, ibutton); - ibutton_worker_emulate_start(ibutton->key_worker, key); + widget_add_string_multiline_element( + widget, 88, 10, AlignCenter, AlignTop, FontPrimary, "iButton\nemulating"); - furi_string_free(key_name); + ibutton_worker_emulate_set_callback(ibutton->worker, ibutton_scene_emulate_callback, ibutton); + ibutton_worker_emulate_start(ibutton->worker, key); ibutton_notification_message(ibutton, iButtonNotificationMessageEmulateStart); + view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget); + + furi_string_free(tmp); } bool ibutton_scene_emulate_on_event(void* context, SceneManagerEvent event) { @@ -78,8 +49,7 @@ bool ibutton_scene_emulate_on_event(void* context, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeTick) { uint32_t cnt = scene_manager_get_scene_state(ibutton->scene_manager, iButtonSceneEmulate); if(cnt > 0) { - cnt--; - if(cnt == 0) { + if(--cnt == 0) { ibutton_notification_message(ibutton, iButtonNotificationMessageEmulateBlink); } scene_manager_set_scene_state(ibutton->scene_manager, iButtonSceneEmulate, cnt); @@ -101,7 +71,7 @@ bool ibutton_scene_emulate_on_event(void* context, SceneManagerEvent event) { void ibutton_scene_emulate_on_exit(void* context) { iButton* ibutton = context; - ibutton_worker_stop(ibutton->key_worker); + ibutton_worker_stop(ibutton->worker); widget_reset(ibutton->widget); ibutton_notification_message(ibutton, iButtonNotificationMessageBlinkStop); } diff --git a/applications/main/ibutton/scenes/ibutton_scene_info.c b/applications/main/ibutton/scenes/ibutton_scene_info.c index 15648f6f293..cf44d6a8654 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_info.c +++ b/applications/main/ibutton/scenes/ibutton_scene_info.c @@ -1,66 +1,54 @@ #include "../ibutton_i.h" -#include void ibutton_scene_info_on_enter(void* context) { iButton* ibutton = context; - Widget* widget = ibutton->widget; iButtonKey* key = ibutton->key; + Widget* widget = ibutton->widget; + + const iButtonProtocolId protocol_id = ibutton_key_get_protocol_id(key); - const uint8_t* key_data = ibutton_key_get_data_p(key); + FuriString* tmp = furi_string_alloc(); - FuriString* key_name; - key_name = furi_string_alloc(); - path_extract_filename(ibutton->file_path, key_name, true); + furi_string_printf( + tmp, + "\e#%s [%s]\e#", + ibutton->key_name, + ibutton_protocols_get_name(ibutton->protocols, protocol_id)); - ibutton_text_store_set(ibutton, "%s", furi_string_get_cstr(key_name)); widget_add_text_box_element( - widget, 0, 0, 128, 23, AlignCenter, AlignCenter, ibutton->text_store, true); + widget, 0, 2, 128, 12, AlignLeft, AlignTop, furi_string_get_cstr(tmp), true); - switch(ibutton_key_get_type(key)) { - case iButtonKeyDS1990: - ibutton_text_store_set( - ibutton, - "%02X %02X %02X %02X %02X %02X %02X %02X", - key_data[0], - key_data[1], - key_data[2], - key_data[3], - key_data[4], - key_data[5], - key_data[6], - key_data[7]); - widget_add_string_element(widget, 64, 36, AlignCenter, AlignBottom, FontPrimary, "Dallas"); - break; + furi_string_reset(tmp); + ibutton_protocols_render_brief_data(ibutton->protocols, key, tmp); - case iButtonKeyMetakom: - ibutton_text_store_set( - ibutton, "%02X %02X %02X %02X", key_data[0], key_data[1], key_data[2], key_data[3]); - widget_add_string_element( - widget, 64, 36, AlignCenter, AlignBottom, FontPrimary, "Metakom"); - break; + widget_add_string_multiline_element( + widget, 0, 16, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(tmp)); - case iButtonKeyCyfral: - ibutton_text_store_set(ibutton, "%02X %02X", key_data[0], key_data[1]); - widget_add_string_element(widget, 64, 36, AlignCenter, AlignBottom, FontPrimary, "Cyfral"); - break; + if(ibutton_protocols_get_features(ibutton->protocols, protocol_id) & + iButtonProtocolFeatureExtData) { + widget_add_button_element( + widget, GuiButtonTypeRight, "More", ibutton_widget_callback, context); } - widget_add_string_element( - widget, 64, 50, AlignCenter, AlignBottom, FontSecondary, ibutton->text_store); - view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget); - - furi_string_free(key_name); + furi_string_free(tmp); } bool ibutton_scene_info_on_event(void* context, SceneManagerEvent event) { - UNUSED(context); - UNUSED(event); - return false; + iButton* ibutton = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + if(event.event == GuiButtonTypeRight) { + scene_manager_next_scene(ibutton->scene_manager, iButtonSceneViewData); + } + } + + return consumed; } void ibutton_scene_info_on_exit(void* context) { iButton* ibutton = context; - ibutton_text_store_clear(ibutton); widget_reset(ibutton->widget); } diff --git a/applications/main/ibutton/scenes/ibutton_scene_read.c b/applications/main/ibutton/scenes/ibutton_scene_read.c index b5ee08e6f4d..2c43b82bbb1 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_read.c +++ b/applications/main/ibutton/scenes/ibutton_scene_read.c @@ -10,14 +10,13 @@ void ibutton_scene_read_on_enter(void* context) { iButton* ibutton = context; Popup* popup = ibutton->popup; iButtonKey* key = ibutton->key; - iButtonWorker* worker = ibutton->key_worker; + iButtonWorker* worker = ibutton->worker; popup_set_header(popup, "iButton", 95, 26, AlignCenter, AlignBottom); popup_set_text(popup, "Waiting\nfor key ...", 95, 30, AlignCenter, AlignTop); popup_set_icon(popup, 0, 5, &I_DolphinWait_61x59); view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewPopup); - furi_string_set(ibutton->file_path, IBUTTON_APP_FOLDER); ibutton_worker_read_set_callback(worker, ibutton_scene_read_callback, ibutton); ibutton_worker_read_start(worker, key); @@ -35,25 +34,14 @@ bool ibutton_scene_read_on_event(void* context, SceneManagerEvent event) { } else if(event.type == SceneManagerEventTypeCustom) { consumed = true; if(event.event == iButtonCustomEventWorkerRead) { - bool success = false; - iButtonKey* key = ibutton->key; - - if(ibutton_key_get_type(key) == iButtonKeyDS1990) { - if(!ibutton_key_dallas_crc_is_valid(key)) { - scene_manager_next_scene(scene_manager, iButtonSceneReadCRCError); - } else if(!ibutton_key_dallas_is_1990_key(key)) { - scene_manager_next_scene(scene_manager, iButtonSceneReadNotKeyError); - } else { - success = true; - } - } else { - success = true; - } - - if(success) { + if(ibutton_protocols_is_valid(ibutton->protocols, ibutton->key)) { ibutton_notification_message(ibutton, iButtonNotificationMessageSuccess); scene_manager_next_scene(scene_manager, iButtonSceneReadSuccess); + DOLPHIN_DEED(DolphinDeedIbuttonReadSuccess); + + } else { + scene_manager_next_scene(scene_manager, iButtonSceneReadError); } } } @@ -64,7 +52,7 @@ bool ibutton_scene_read_on_event(void* context, SceneManagerEvent event) { void ibutton_scene_read_on_exit(void* context) { iButton* ibutton = context; Popup* popup = ibutton->popup; - ibutton_worker_stop(ibutton->key_worker); + ibutton_worker_stop(ibutton->worker); popup_set_header(popup, NULL, 0, 0, AlignCenter, AlignBottom); popup_set_text(popup, NULL, 0, 0, AlignCenter, AlignTop); popup_set_icon(popup, 0, 0, NULL); diff --git a/applications/main/ibutton/scenes/ibutton_scene_read_crc_error.c b/applications/main/ibutton/scenes/ibutton_scene_read_crc_error.c deleted file mode 100644 index f822ff6a287..00000000000 --- a/applications/main/ibutton/scenes/ibutton_scene_read_crc_error.c +++ /dev/null @@ -1,70 +0,0 @@ -#include "../ibutton_i.h" -#include - -static void ibutton_scene_read_crc_error_dialog_ex_callback(DialogExResult result, void* context) { - iButton* ibutton = context; - view_dispatcher_send_custom_event(ibutton->view_dispatcher, result); -} - -void ibutton_scene_read_crc_error_on_enter(void* context) { - iButton* ibutton = context; - DialogEx* dialog_ex = ibutton->dialog_ex; - iButtonKey* key = ibutton->key; - const uint8_t* key_data = ibutton_key_get_data_p(key); - - ibutton_text_store_set( - ibutton, - "%02X %02X %02X %02X %02X %02X %02X %02X\nExpected CRC: %X", - key_data[0], - key_data[1], - key_data[2], - key_data[3], - key_data[4], - key_data[5], - key_data[6], - key_data[7], - maxim_crc8(key_data, 7, MAXIM_CRC8_INIT)); - - dialog_ex_set_header(dialog_ex, "CRC ERROR", 64, 10, AlignCenter, AlignCenter); - dialog_ex_set_text(dialog_ex, ibutton->text_store, 64, 19, AlignCenter, AlignTop); - dialog_ex_set_left_button_text(dialog_ex, "Retry"); - dialog_ex_set_right_button_text(dialog_ex, "More"); - dialog_ex_set_result_callback(dialog_ex, ibutton_scene_read_crc_error_dialog_ex_callback); - dialog_ex_set_context(dialog_ex, ibutton); - - view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewDialogEx); - - ibutton_notification_message(ibutton, iButtonNotificationMessageError); - ibutton_notification_message(ibutton, iButtonNotificationMessageRedOn); -} - -bool ibutton_scene_read_crc_error_on_event(void* context, SceneManagerEvent event) { - iButton* ibutton = context; - SceneManager* scene_manager = ibutton->scene_manager; - bool consumed = false; - - if(event.type == SceneManagerEventTypeBack) { - consumed = true; - scene_manager_next_scene(scene_manager, iButtonSceneExitConfirm); - } else if(event.type == SceneManagerEventTypeCustom) { - consumed = true; - if(event.event == DialogExResultRight) { - scene_manager_next_scene(scene_manager, iButtonSceneReadKeyMenu); - } else if(event.event == DialogExResultLeft) { - scene_manager_previous_scene(scene_manager); - } - } - - return consumed; -} - -void ibutton_scene_read_crc_error_on_exit(void* context) { - iButton* ibutton = context; - DialogEx* dialog_ex = ibutton->dialog_ex; - - ibutton_text_store_clear(ibutton); - - dialog_ex_reset(dialog_ex); - - ibutton_notification_message(ibutton, iButtonNotificationMessageRedOff); -} diff --git a/applications/main/ibutton/scenes/ibutton_scene_read_error.c b/applications/main/ibutton/scenes/ibutton_scene_read_error.c new file mode 100644 index 00000000000..e966384bfc0 --- /dev/null +++ b/applications/main/ibutton/scenes/ibutton_scene_read_error.c @@ -0,0 +1,58 @@ +#include "../ibutton_i.h" +#include + +void ibutton_scene_read_error_on_enter(void* context) { + iButton* ibutton = context; + iButtonKey* key = ibutton->key; + + Widget* widget = ibutton->widget; + + FuriString* tmp = furi_string_alloc(); + + widget_add_button_element( + widget, GuiButtonTypeLeft, "Retry", ibutton_widget_callback, context); + widget_add_button_element( + widget, GuiButtonTypeRight, "More", ibutton_widget_callback, context); + + widget_add_string_element( + widget, 128 / 2, 2, AlignCenter, AlignTop, FontPrimary, "Read Error"); + + ibutton_protocols_render_error(ibutton->protocols, key, tmp); + + widget_add_string_multiline_element( + widget, 128 / 2, 16, AlignCenter, AlignTop, FontSecondary, furi_string_get_cstr(tmp)); + + ibutton_notification_message(ibutton, iButtonNotificationMessageError); + ibutton_notification_message(ibutton, iButtonNotificationMessageRedOn); + + view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget); + furi_string_free(tmp); +} + +bool ibutton_scene_read_error_on_event(void* context, SceneManagerEvent event) { + iButton* ibutton = context; + SceneManager* scene_manager = ibutton->scene_manager; + bool consumed = false; + + if(event.type == SceneManagerEventTypeBack) { + consumed = true; + scene_manager_next_scene(scene_manager, iButtonSceneExitConfirm); + + } else if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + if(event.event == GuiButtonTypeLeft) { + scene_manager_previous_scene(scene_manager); + } else if(event.event == GuiButtonTypeRight) { + scene_manager_next_scene(scene_manager, iButtonSceneReadKeyMenu); + } + } + + return consumed; +} + +void ibutton_scene_read_error_on_exit(void* context) { + iButton* ibutton = context; + + ibutton_notification_message(ibutton, iButtonNotificationMessageRedOff); + widget_reset(ibutton->widget); +} diff --git a/applications/main/ibutton/scenes/ibutton_scene_read_key_menu.c b/applications/main/ibutton/scenes/ibutton_scene_read_key_menu.c index 0a8ecfa55f7..716f72c7d3d 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_read_key_menu.c +++ b/applications/main/ibutton/scenes/ibutton_scene_read_key_menu.c @@ -4,7 +4,9 @@ typedef enum { SubmenuIndexSave, SubmenuIndexEmulate, - SubmenuIndexWrite, + SubmenuIndexViewData, + SubmenuIndexWriteBlank, + SubmenuIndexWriteCopy, } SubmenuIndex; void ibutton_scene_read_key_menu_submenu_callback(void* context, uint32_t index) { @@ -16,6 +18,9 @@ void ibutton_scene_read_key_menu_on_enter(void* context) { iButton* ibutton = context; Submenu* submenu = ibutton->submenu; + const iButtonProtocolId protocol_id = ibutton_key_get_protocol_id(ibutton->key); + const uint32_t features = ibutton_protocols_get_features(ibutton->protocols, protocol_id); + submenu_add_item( submenu, "Save", SubmenuIndexSave, ibutton_scene_read_key_menu_submenu_callback, ibutton); submenu_add_item( @@ -24,36 +29,66 @@ void ibutton_scene_read_key_menu_on_enter(void* context) { SubmenuIndexEmulate, ibutton_scene_read_key_menu_submenu_callback, ibutton); - if(ibutton_key_get_type(ibutton->key) == iButtonKeyDS1990) { + + if(features & iButtonProtocolFeatureExtData) { + submenu_add_item( + submenu, + "View Data", + SubmenuIndexViewData, + ibutton_scene_read_key_menu_submenu_callback, + ibutton); + } + + if(features & iButtonProtocolFeatureWriteBlank) { submenu_add_item( submenu, - "Write", - SubmenuIndexWrite, + "Write Blank", + SubmenuIndexWriteBlank, ibutton_scene_read_key_menu_submenu_callback, ibutton); } + + if(features & iButtonProtocolFeatureWriteCopy) { + submenu_add_item( + submenu, + "Write Copy", + SubmenuIndexWriteCopy, + ibutton_scene_read_key_menu_submenu_callback, + ibutton); + } + submenu_set_selected_item( submenu, scene_manager_get_scene_state(ibutton->scene_manager, iButtonSceneReadKeyMenu)); - view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewSubmenu); } bool ibutton_scene_read_key_menu_on_event(void* context, SceneManagerEvent event) { iButton* ibutton = context; + SceneManager* scene_manager = ibutton->scene_manager; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { - scene_manager_set_scene_state( - ibutton->scene_manager, iButtonSceneReadKeyMenu, event.event); + scene_manager_set_scene_state(scene_manager, iButtonSceneReadKeyMenu, event.event); consumed = true; + if(event.event == SubmenuIndexSave) { - scene_manager_next_scene(ibutton->scene_manager, iButtonSceneSaveName); + scene_manager_next_scene(scene_manager, iButtonSceneSaveName); } else if(event.event == SubmenuIndexEmulate) { - scene_manager_next_scene(ibutton->scene_manager, iButtonSceneEmulate); + scene_manager_next_scene(scene_manager, iButtonSceneEmulate); DOLPHIN_DEED(DolphinDeedIbuttonEmulate); - } else if(event.event == SubmenuIndexWrite) { - scene_manager_next_scene(ibutton->scene_manager, iButtonSceneWrite); + } else if(event.event == SubmenuIndexViewData) { + scene_manager_next_scene(scene_manager, iButtonSceneViewData); + } else if(event.event == SubmenuIndexWriteBlank) { + ibutton->write_mode = iButtonWriteModeBlank; + scene_manager_next_scene(scene_manager, iButtonSceneWrite); + } else if(event.event == SubmenuIndexWriteCopy) { + ibutton->write_mode = iButtonWriteModeCopy; + scene_manager_next_scene(scene_manager, iButtonSceneWrite); } + } else if(event.event == SceneManagerEventTypeBack) { + scene_manager_set_scene_state( + ibutton->scene_manager, iButtonSceneReadKeyMenu, SubmenuIndexSave); + // Event is not consumed } return consumed; diff --git a/applications/main/ibutton/scenes/ibutton_scene_read_not_key_error.c b/applications/main/ibutton/scenes/ibutton_scene_read_not_key_error.c deleted file mode 100644 index 8a7528031c6..00000000000 --- a/applications/main/ibutton/scenes/ibutton_scene_read_not_key_error.c +++ /dev/null @@ -1,71 +0,0 @@ -#include "../ibutton_i.h" -#include - -static void - ibutton_scene_read_not_key_error_dialog_ex_callback(DialogExResult result, void* context) { - iButton* ibutton = context; - view_dispatcher_send_custom_event(ibutton->view_dispatcher, result); -} - -void ibutton_scene_read_not_key_error_on_enter(void* context) { - iButton* ibutton = context; - DialogEx* dialog_ex = ibutton->dialog_ex; - iButtonKey* key = ibutton->key; - const uint8_t* key_data = ibutton_key_get_data_p(key); - - ibutton_text_store_set( - ibutton, - "THIS IS NOT A KEY\n%02X %02X %02X %02X %02X %02X %02X %02X", - key_data[0], - key_data[1], - key_data[2], - key_data[3], - key_data[4], - key_data[5], - key_data[6], - key_data[7], - maxim_crc8(key_data, 7, MAXIM_CRC8_INIT)); - - dialog_ex_set_header(dialog_ex, "CRC ERROR", 64, 10, AlignCenter, AlignCenter); - dialog_ex_set_text(dialog_ex, ibutton->text_store, 64, 19, AlignCenter, AlignTop); - dialog_ex_set_left_button_text(dialog_ex, "Retry"); - dialog_ex_set_right_button_text(dialog_ex, "More"); - dialog_ex_set_result_callback(dialog_ex, ibutton_scene_read_not_key_error_dialog_ex_callback); - dialog_ex_set_context(dialog_ex, ibutton); - - view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewDialogEx); - - ibutton_notification_message(ibutton, iButtonNotificationMessageError); - ibutton_notification_message(ibutton, iButtonNotificationMessageRedOn); -} - -bool ibutton_scene_read_not_key_error_on_event(void* context, SceneManagerEvent event) { - iButton* ibutton = context; - SceneManager* scene_manager = ibutton->scene_manager; - bool consumed = false; - - if(event.type == SceneManagerEventTypeBack) { - consumed = true; - scene_manager_next_scene(scene_manager, iButtonSceneExitConfirm); - } else if(event.type == SceneManagerEventTypeCustom) { - consumed = true; - if(event.event == DialogExResultRight) { - scene_manager_next_scene(scene_manager, iButtonSceneReadKeyMenu); - } else if(event.event == DialogExResultLeft) { - scene_manager_previous_scene(scene_manager); - } - } - - return consumed; -} - -void ibutton_scene_read_not_key_error_on_exit(void* context) { - iButton* ibutton = context; - DialogEx* dialog_ex = ibutton->dialog_ex; - - ibutton_text_store_clear(ibutton); - - dialog_ex_reset(dialog_ex); - - ibutton_notification_message(ibutton, iButtonNotificationMessageRedOff); -} diff --git a/applications/main/ibutton/scenes/ibutton_scene_read_success.c b/applications/main/ibutton/scenes/ibutton_scene_read_success.c index 749e7af37a0..2e50bc99647 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_read_success.c +++ b/applications/main/ibutton/scenes/ibutton_scene_read_success.c @@ -1,55 +1,40 @@ #include "../ibutton_i.h" -#include -static void ibutton_scene_read_success_dialog_ex_callback(DialogExResult result, void* context) { - iButton* ibutton = context; - view_dispatcher_send_custom_event(ibutton->view_dispatcher, result); -} +#include void ibutton_scene_read_success_on_enter(void* context) { iButton* ibutton = context; - DialogEx* dialog_ex = ibutton->dialog_ex; iButtonKey* key = ibutton->key; - const uint8_t* key_data = ibutton_key_get_data_p(key); - - switch(ibutton_key_get_type(key)) { - case iButtonKeyDS1990: - ibutton_text_store_set( - ibutton, - "Dallas\n%02X %02X %02X %02X\n%02X %02X %02X %02X", - key_data[0], - key_data[1], - key_data[2], - key_data[3], - key_data[4], - key_data[5], - key_data[6], - key_data[7]); - break; - case iButtonKeyCyfral: - ibutton_text_store_set(ibutton, "Cyfral\n%02X %02X", key_data[0], key_data[1]); - break; - case iButtonKeyMetakom: - ibutton_text_store_set( - ibutton, - "Metakom\n%02X %02X %02X %02X", - key_data[0], - key_data[1], - key_data[2], - key_data[3]); - break; - } + Widget* widget = ibutton->widget; + + FuriString* tmp = furi_string_alloc(); - dialog_ex_set_text(dialog_ex, ibutton->text_store, 95, 30, AlignCenter, AlignCenter); - dialog_ex_set_left_button_text(dialog_ex, "Retry"); - dialog_ex_set_right_button_text(dialog_ex, "More"); - dialog_ex_set_icon(dialog_ex, 0, 1, &I_DolphinReadingSuccess_59x63); - dialog_ex_set_result_callback(dialog_ex, ibutton_scene_read_success_dialog_ex_callback); - dialog_ex_set_context(dialog_ex, ibutton); + const iButtonProtocolId protocol_id = ibutton_key_get_protocol_id(key); - view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewDialogEx); + widget_add_button_element( + widget, GuiButtonTypeLeft, "Retry", ibutton_widget_callback, context); + widget_add_button_element( + widget, GuiButtonTypeRight, "More", ibutton_widget_callback, context); + furi_string_printf( + tmp, + "%s[%s]", + ibutton_protocols_get_name(ibutton->protocols, protocol_id), + ibutton_protocols_get_manufacturer(ibutton->protocols, protocol_id)); + + widget_add_string_element( + widget, 0, 2, AlignLeft, AlignTop, FontPrimary, furi_string_get_cstr(tmp)); + + furi_string_reset(tmp); + ibutton_protocols_render_brief_data(ibutton->protocols, key, tmp); + + widget_add_string_multiline_element( + widget, 0, 16, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(tmp)); + + view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget); ibutton_notification_message(ibutton, iButtonNotificationMessageGreenOn); + + furi_string_free(tmp); } bool ibutton_scene_read_success_on_event(void* context, SceneManagerEvent event) { @@ -62,9 +47,9 @@ bool ibutton_scene_read_success_on_event(void* context, SceneManagerEvent event) scene_manager_next_scene(scene_manager, iButtonSceneExitConfirm); } else if(event.type == SceneManagerEventTypeCustom) { consumed = true; - if(event.event == DialogExResultRight) { + if(event.event == GuiButtonTypeRight) { scene_manager_next_scene(scene_manager, iButtonSceneReadKeyMenu); - } else if(event.event == DialogExResultLeft) { + } else if(event.event == GuiButtonTypeLeft) { scene_manager_next_scene(scene_manager, iButtonSceneRetryConfirm); } } @@ -74,11 +59,8 @@ bool ibutton_scene_read_success_on_event(void* context, SceneManagerEvent event) void ibutton_scene_read_success_on_exit(void* context) { iButton* ibutton = context; - DialogEx* dialog_ex = ibutton->dialog_ex; - - ibutton_text_store_clear(ibutton); - dialog_ex_reset(dialog_ex); + widget_reset(ibutton->widget); ibutton_notification_message(ibutton, iButtonNotificationMessageGreenOff); } diff --git a/applications/main/ibutton/scenes/ibutton_scene_rpc.c b/applications/main/ibutton/scenes/ibutton_scene_rpc.c index b25b1b8dd86..9205eb4b46f 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_rpc.c +++ b/applications/main/ibutton/scenes/ibutton_scene_rpc.c @@ -1,6 +1,4 @@ #include "../ibutton_i.h" -#include -#include void ibutton_scene_rpc_on_enter(void* context) { iButton* ibutton = context; @@ -17,8 +15,6 @@ void ibutton_scene_rpc_on_enter(void* context) { } bool ibutton_scene_rpc_on_event(void* context, SceneManagerEvent event) { - UNUSED(context); - UNUSED(event); iButton* ibutton = context; Popup* popup = ibutton->popup; @@ -26,40 +22,32 @@ bool ibutton_scene_rpc_on_event(void* context, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { consumed = true; + if(event.event == iButtonCustomEventRpcLoad) { - const char* arg = rpc_system_app_get_data(ibutton->rpc_ctx); bool result = false; - if(arg && (furi_string_empty(ibutton->file_path))) { - furi_string_set(ibutton->file_path, arg); - if(ibutton_load_key_data(ibutton, ibutton->file_path, false)) { - ibutton_worker_emulate_start(ibutton->key_worker, ibutton->key); - FuriString* key_name; - key_name = furi_string_alloc(); - if(furi_string_end_with(ibutton->file_path, IBUTTON_APP_EXTENSION)) { - path_extract_filename(ibutton->file_path, key_name, true); - } - - if(!furi_string_empty(key_name)) { - ibutton_text_store_set( - ibutton, "emulating\n%s", furi_string_get_cstr(key_name)); - } else { - ibutton_text_store_set(ibutton, "emulating"); - } - popup_set_text(popup, ibutton->text_store, 82, 32, AlignCenter, AlignTop); + const char* file_path = rpc_system_app_get_data(ibutton->rpc); + + if(file_path && (furi_string_empty(ibutton->file_path))) { + furi_string_set(ibutton->file_path, file_path); + + if(ibutton_load_key(ibutton)) { + popup_set_text(popup, ibutton->key_name, 82, 32, AlignCenter, AlignTop); + view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewPopup); ibutton_notification_message(ibutton, iButtonNotificationMessageEmulateStart); + ibutton_worker_emulate_start(ibutton->worker, ibutton->key); - furi_string_free(key_name); result = true; - } else { - furi_string_reset(ibutton->file_path); } } - rpc_system_app_confirm(ibutton->rpc_ctx, RpcAppEventLoadFile, result); + + rpc_system_app_confirm(ibutton->rpc, RpcAppEventLoadFile, result); + } else if(event.event == iButtonCustomEventRpcExit) { - rpc_system_app_confirm(ibutton->rpc_ctx, RpcAppEventAppExit, true); + rpc_system_app_confirm(ibutton->rpc, RpcAppEventAppExit, true); scene_manager_stop(ibutton->scene_manager); view_dispatcher_stop(ibutton->view_dispatcher); + } else if(event.event == iButtonCustomEventRpcSessionClose) { scene_manager_stop(ibutton->scene_manager); view_dispatcher_stop(ibutton->view_dispatcher); diff --git a/applications/main/ibutton/scenes/ibutton_scene_save_name.c b/applications/main/ibutton/scenes/ibutton_scene_save_name.c index 5f25a0002ff..4ad0315e54e 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_save_name.c +++ b/applications/main/ibutton/scenes/ibutton_scene_save_name.c @@ -1,6 +1,8 @@ #include "../ibutton_i.h" -#include + +#include #include + #include static void ibutton_scene_save_name_text_input_callback(void* context) { @@ -12,17 +14,10 @@ void ibutton_scene_save_name_on_enter(void* context) { iButton* ibutton = context; TextInput* text_input = ibutton->text_input; - FuriString* key_name; - key_name = furi_string_alloc(); - if(furi_string_end_with(ibutton->file_path, IBUTTON_APP_EXTENSION)) { - path_extract_filename(ibutton->file_path, key_name, true); - } + const bool is_new_file = furi_string_empty(ibutton->file_path); - const bool key_name_is_empty = furi_string_empty(key_name); - if(key_name_is_empty) { - set_random_name(ibutton->text_store, IBUTTON_TEXT_STORE_SIZE); - } else { - ibutton_text_store_set(ibutton, "%s", furi_string_get_cstr(key_name)); + if(is_new_file) { + set_random_name(ibutton->key_name, IBUTTON_KEY_NAME_SIZE); } text_input_set_header_text(text_input, "Name the key"); @@ -30,23 +25,15 @@ void ibutton_scene_save_name_on_enter(void* context) { text_input, ibutton_scene_save_name_text_input_callback, ibutton, - ibutton->text_store, + ibutton->key_name, IBUTTON_KEY_NAME_SIZE, - key_name_is_empty); - - FuriString* folder_path; - folder_path = furi_string_alloc(); + is_new_file); - path_extract_dirname(furi_string_get_cstr(ibutton->file_path), folder_path); - - ValidatorIsFile* validator_is_file = validator_is_file_alloc_init( - furi_string_get_cstr(folder_path), IBUTTON_APP_EXTENSION, furi_string_get_cstr(key_name)); + ValidatorIsFile* validator_is_file = + validator_is_file_alloc_init(IBUTTON_APP_FOLDER, IBUTTON_APP_EXTENSION, ibutton->key_name); text_input_set_validator(text_input, validator_is_file_callback, validator_is_file); view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewTextInput); - - furi_string_free(key_name); - furi_string_free(folder_path); } bool ibutton_scene_save_name_on_event(void* context, SceneManagerEvent event) { @@ -56,8 +43,16 @@ bool ibutton_scene_save_name_on_event(void* context, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { consumed = true; if(event.event == iButtonCustomEventTextEditResult) { - if(ibutton_save_key(ibutton, ibutton->text_store)) { + furi_string_printf( + ibutton->file_path, + "%s/%s%s", + IBUTTON_APP_FOLDER, + ibutton->key_name, + IBUTTON_APP_EXTENSION); + + if(ibutton_save_key(ibutton)) { scene_manager_next_scene(ibutton->scene_manager, iButtonSceneSaveSuccess); + if(scene_manager_has_previous_scene( ibutton->scene_manager, iButtonSceneSavedKeyMenu)) { // Nothing, do not count editing as saving @@ -67,6 +62,7 @@ bool ibutton_scene_save_name_on_event(void* context, SceneManagerEvent event) { } else { DOLPHIN_DEED(DolphinDeedIbuttonSave); } + } else { const uint32_t possible_scenes[] = { iButtonSceneReadKeyMenu, iButtonSceneSavedKeyMenu, iButtonSceneAddType}; diff --git a/applications/main/ibutton/scenes/ibutton_scene_saved_key_menu.c b/applications/main/ibutton/scenes/ibutton_scene_saved_key_menu.c index e4c9c350ae2..80fca28b5ee 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_saved_key_menu.c +++ b/applications/main/ibutton/scenes/ibutton_scene_saved_key_menu.c @@ -3,72 +3,70 @@ enum SubmenuIndex { SubmenuIndexEmulate, - SubmenuIndexWrite, + SubmenuIndexWriteBlank, + SubmenuIndexWriteCopy, SubmenuIndexEdit, SubmenuIndexDelete, SubmenuIndexInfo, }; -void ibutton_scene_saved_key_menu_submenu_callback(void* context, uint32_t index) { - iButton* ibutton = context; - view_dispatcher_send_custom_event(ibutton->view_dispatcher, index); -} - void ibutton_scene_saved_key_menu_on_enter(void* context) { iButton* ibutton = context; Submenu* submenu = ibutton->submenu; - submenu_add_item( - submenu, - "Emulate", - SubmenuIndexEmulate, - ibutton_scene_saved_key_menu_submenu_callback, - ibutton); - if(ibutton_key_get_type(ibutton->key) == iButtonKeyDS1990) { + const uint32_t features = ibutton_protocols_get_features( + ibutton->protocols, ibutton_key_get_protocol_id(ibutton->key)); + + submenu_add_item(submenu, "Emulate", SubmenuIndexEmulate, ibutton_submenu_callback, ibutton); + + if(features & iButtonProtocolFeatureWriteBlank) { submenu_add_item( - submenu, - "Write", - SubmenuIndexWrite, - ibutton_scene_saved_key_menu_submenu_callback, - ibutton); + submenu, "Write Blank", SubmenuIndexWriteBlank, ibutton_submenu_callback, ibutton); } - submenu_add_item( - submenu, "Edit", SubmenuIndexEdit, ibutton_scene_saved_key_menu_submenu_callback, ibutton); - submenu_add_item( - submenu, - "Delete", - SubmenuIndexDelete, - ibutton_scene_saved_key_menu_submenu_callback, - ibutton); - submenu_add_item( - submenu, "Info", SubmenuIndexInfo, ibutton_scene_saved_key_menu_submenu_callback, ibutton); + + if(features & iButtonProtocolFeatureWriteCopy) { + submenu_add_item( + submenu, "Write Copy", SubmenuIndexWriteCopy, ibutton_submenu_callback, ibutton); + } + + submenu_add_item(submenu, "Edit", SubmenuIndexEdit, ibutton_submenu_callback, ibutton); + submenu_add_item(submenu, "Delete", SubmenuIndexDelete, ibutton_submenu_callback, ibutton); + submenu_add_item(submenu, "Info", SubmenuIndexInfo, ibutton_submenu_callback, ibutton); submenu_set_selected_item( submenu, scene_manager_get_scene_state(ibutton->scene_manager, iButtonSceneSavedKeyMenu)); - view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewSubmenu); } bool ibutton_scene_saved_key_menu_on_event(void* context, SceneManagerEvent event) { iButton* ibutton = context; + SceneManager* scene_manager = ibutton->scene_manager; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { - scene_manager_set_scene_state( - ibutton->scene_manager, iButtonSceneSavedKeyMenu, event.event); + scene_manager_set_scene_state(scene_manager, iButtonSceneSavedKeyMenu, event.event); consumed = true; if(event.event == SubmenuIndexEmulate) { - scene_manager_next_scene(ibutton->scene_manager, iButtonSceneEmulate); + scene_manager_next_scene(scene_manager, iButtonSceneEmulate); DOLPHIN_DEED(DolphinDeedIbuttonEmulate); - } else if(event.event == SubmenuIndexWrite) { - scene_manager_next_scene(ibutton->scene_manager, iButtonSceneWrite); + } else if(event.event == SubmenuIndexWriteBlank) { + ibutton->write_mode = iButtonWriteModeBlank; + scene_manager_next_scene(scene_manager, iButtonSceneWrite); + } else if(event.event == SubmenuIndexWriteCopy) { + ibutton->write_mode = iButtonWriteModeCopy; + scene_manager_next_scene(scene_manager, iButtonSceneWrite); } else if(event.event == SubmenuIndexEdit) { - scene_manager_next_scene(ibutton->scene_manager, iButtonSceneAddValue); + scene_manager_next_scene(scene_manager, iButtonSceneAddValue); } else if(event.event == SubmenuIndexDelete) { - scene_manager_next_scene(ibutton->scene_manager, iButtonSceneDeleteConfirm); + scene_manager_next_scene(scene_manager, iButtonSceneDeleteConfirm); } else if(event.event == SubmenuIndexInfo) { - scene_manager_next_scene(ibutton->scene_manager, iButtonSceneInfo); + scene_manager_next_scene(scene_manager, iButtonSceneInfo); } + + } else if(event.type == SceneManagerEventTypeBack) { + scene_manager_set_scene_state( + scene_manager, iButtonSceneSavedKeyMenu, SubmenuIndexEmulate); + // Event is not consumed } return consumed; diff --git a/applications/main/ibutton/scenes/ibutton_scene_select_key.c b/applications/main/ibutton/scenes/ibutton_scene_select_key.c index 32169a9c08d..ebd504b1f13 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_select_key.c +++ b/applications/main/ibutton/scenes/ibutton_scene_select_key.c @@ -3,11 +3,11 @@ void ibutton_scene_select_key_on_enter(void* context) { iButton* ibutton = context; - if(!ibutton_file_select(ibutton)) { + if(ibutton_select_and_load_key(ibutton)) { + scene_manager_next_scene(ibutton->scene_manager, iButtonSceneSavedKeyMenu); + } else { scene_manager_search_and_switch_to_previous_scene( ibutton->scene_manager, iButtonSceneStart); - } else { - scene_manager_next_scene(ibutton->scene_manager, iButtonSceneSavedKeyMenu); } } diff --git a/applications/main/ibutton/scenes/ibutton_scene_start.c b/applications/main/ibutton/scenes/ibutton_scene_start.c index b8f6b07d6ca..37bf96f39f0 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_start.c +++ b/applications/main/ibutton/scenes/ibutton_scene_start.c @@ -8,21 +8,15 @@ enum SubmenuIndex { SubmenuIndexAdd, }; -void ibutton_scene_start_submenu_callback(void* context, uint32_t index) { - iButton* ibutton = context; - view_dispatcher_send_custom_event(ibutton->view_dispatcher, index); -} - void ibutton_scene_start_on_enter(void* context) { iButton* ibutton = context; Submenu* submenu = ibutton->submenu; - submenu_add_item( - submenu, "Read", SubmenuIndexRead, ibutton_scene_start_submenu_callback, ibutton); - submenu_add_item( - submenu, "Saved", SubmenuIndexSaved, ibutton_scene_start_submenu_callback, ibutton); - submenu_add_item( - submenu, "Add Manually", SubmenuIndexAdd, ibutton_scene_start_submenu_callback, ibutton); + ibutton_reset_key(ibutton); + + submenu_add_item(submenu, "Read", SubmenuIndexRead, ibutton_submenu_callback, ibutton); + submenu_add_item(submenu, "Saved", SubmenuIndexSaved, ibutton_submenu_callback, ibutton); + submenu_add_item(submenu, "Add Manually", SubmenuIndexAdd, ibutton_submenu_callback, ibutton); submenu_set_selected_item( submenu, scene_manager_get_scene_state(ibutton->scene_manager, iButtonSceneStart)); @@ -41,7 +35,6 @@ bool ibutton_scene_start_on_event(void* context, SceneManagerEvent event) { scene_manager_next_scene(ibutton->scene_manager, iButtonSceneRead); DOLPHIN_DEED(DolphinDeedIbuttonRead); } else if(event.event == SubmenuIndexSaved) { - furi_string_set(ibutton->file_path, IBUTTON_APP_FOLDER); scene_manager_next_scene(ibutton->scene_manager, iButtonSceneSelectKey); } else if(event.event == SubmenuIndexAdd) { scene_manager_next_scene(ibutton->scene_manager, iButtonSceneAddType); diff --git a/applications/main/ibutton/scenes/ibutton_scene_view_data.c b/applications/main/ibutton/scenes/ibutton_scene_view_data.c new file mode 100644 index 00000000000..7e063d7ec84 --- /dev/null +++ b/applications/main/ibutton/scenes/ibutton_scene_view_data.c @@ -0,0 +1,26 @@ +#include "../ibutton_i.h" + +void ibutton_scene_view_data_on_enter(void* context) { + iButton* ibutton = context; + iButtonKey* key = ibutton->key; + Widget* widget = ibutton->widget; + + FuriString* tmp = furi_string_alloc(); + ibutton_protocols_render_data(ibutton->protocols, key, tmp); + + widget_add_text_scroll_element(widget, 0, 0, 128, 64, furi_string_get_cstr(tmp)); + + view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget); + furi_string_free(tmp); +} + +bool ibutton_scene_view_data_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + UNUSED(event); + return false; +} + +void ibutton_scene_view_data_on_exit(void* context) { + iButton* ibutton = context; + widget_reset(ibutton->widget); +} diff --git a/applications/main/ibutton/scenes/ibutton_scene_write.c b/applications/main/ibutton/scenes/ibutton_scene_write.c index cdea04db3d4..541aa1c52b0 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_write.c +++ b/applications/main/ibutton/scenes/ibutton_scene_write.c @@ -1,5 +1,4 @@ #include "../ibutton_i.h" -#include "toolbox/path.h" typedef enum { iButtonSceneWriteStateDefault, @@ -13,61 +12,46 @@ static void ibutton_scene_write_callback(void* context, iButtonWorkerWriteResult void ibutton_scene_write_on_enter(void* context) { iButton* ibutton = context; + furi_assert(ibutton->write_mode != iButtonWriteModeInvalid); + iButtonKey* key = ibutton->key; - Widget* widget = ibutton->widget; - iButtonWorker* worker = ibutton->key_worker; + iButtonWorker* worker = ibutton->worker; + const iButtonProtocolId protocol_id = ibutton_key_get_protocol_id(key); - const uint8_t* key_data = ibutton_key_get_data_p(key); + Widget* widget = ibutton->widget; + FuriString* tmp = furi_string_alloc(); - FuriString* key_name; - key_name = furi_string_alloc(); - if(furi_string_end_with(ibutton->file_path, IBUTTON_APP_EXTENSION)) { - path_extract_filename(ibutton->file_path, key_name, true); - } + widget_add_icon_element(widget, 3, 10, &I_iButtonKey_49x44); - // check that stored key has name - if(!furi_string_empty(key_name)) { - ibutton_text_store_set(ibutton, "%s", furi_string_get_cstr(key_name)); - } else { - // if not, show key data - switch(ibutton_key_get_type(key)) { - case iButtonKeyDS1990: - ibutton_text_store_set( - ibutton, - "%02X %02X %02X %02X\n%02X %02X %02X %02X", - key_data[0], - key_data[1], - key_data[2], - key_data[3], - key_data[4], - key_data[5], - key_data[6], - key_data[7]); - break; - case iButtonKeyCyfral: - ibutton_text_store_set(ibutton, "%02X %02X", key_data[0], key_data[1]); - break; - case iButtonKeyMetakom: - ibutton_text_store_set( - ibutton, "%02X %02X %02X %02X", key_data[0], key_data[1], key_data[2], key_data[3]); - break; - } - } + furi_string_printf( + tmp, + "%s\n[%s]", + ibutton->key_name, + ibutton_protocols_get_name(ibutton->protocols, protocol_id)); - widget_add_string_multiline_element( - widget, 90, 10, AlignCenter, AlignTop, FontPrimary, "iButton\nwriting"); - widget_add_icon_element(widget, 3, 10, &I_iButtonKey_49x44); widget_add_text_box_element( - widget, 54, 39, 75, 22, AlignCenter, AlignCenter, ibutton->text_store, true); - - view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget); + widget, 52, 38, 75, 26, AlignCenter, AlignCenter, furi_string_get_cstr(tmp), true); ibutton_worker_write_set_callback(worker, ibutton_scene_write_callback, ibutton); - ibutton_worker_write_start(worker, key); - furi_string_free(key_name); + furi_string_set(tmp, "iButton\nwriting "); + + if(ibutton->write_mode == iButtonWriteModeBlank) { + furi_string_cat(tmp, "Blank"); + ibutton_worker_write_blank_start(worker, key); + + } else if(ibutton->write_mode == iButtonWriteModeCopy) { + furi_string_cat(tmp, "Copy"); + ibutton_worker_write_copy_start(worker, key); + } + + widget_add_string_multiline_element( + widget, 88, 10, AlignCenter, AlignTop, FontPrimary, furi_string_get_cstr(tmp)); ibutton_notification_message(ibutton, iButtonNotificationMessageEmulateStart); + view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget); + + furi_string_free(tmp); } bool ibutton_scene_write_on_event(void* context, SceneManagerEvent event) { @@ -94,7 +78,9 @@ bool ibutton_scene_write_on_event(void* context, SceneManagerEvent event) { void ibutton_scene_write_on_exit(void* context) { iButton* ibutton = context; - ibutton_worker_stop(ibutton->key_worker); + ibutton->write_mode = iButtonWriteModeInvalid; + + ibutton_worker_stop(ibutton->worker); widget_reset(ibutton->widget); ibutton_notification_message(ibutton, iButtonNotificationMessageBlinkStop); diff --git a/applications/main/infrared/scenes/infrared_scene_universal.c b/applications/main/infrared/scenes/infrared_scene_universal.c index 4ef7c5c26d4..e09abde70e9 100644 --- a/applications/main/infrared/scenes/infrared_scene_universal.c +++ b/applications/main/infrared/scenes/infrared_scene_universal.c @@ -74,4 +74,4 @@ bool infrared_scene_universal_on_event(void* context, SceneManagerEvent event) { void infrared_scene_universal_on_exit(void* context) { Infrared* infrared = context; submenu_reset(infrared->submenu); -} \ No newline at end of file +} diff --git a/applications/services/gui/modules/widget.h b/applications/services/gui/modules/widget.h index 9076ce7f299..d998516781b 100644 --- a/applications/services/gui/modules/widget.h +++ b/applications/services/gui/modules/widget.h @@ -115,6 +115,7 @@ void widget_add_text_box_element( * @param[in] text Formatted text. Default format: align left, Secondary font. * The following formats are available: * "\e#Bold text" - sets bold font before until next '\n' symbol + * "\e*Monospaced text\e*" - sets monospaced font before until next '\n' symbol * "\ecCenter-aligned text" - sets center horizontal align until the next '\n' symbol * "\erRight-aligned text" - sets right horizontal align until the next '\n' symbol */ diff --git a/applications/services/gui/modules/widget_elements/widget_element_text_scroll.c b/applications/services/gui/modules/widget_elements/widget_element_text_scroll.c index 5d522c74bfc..4c9c39dffb1 100644 --- a/applications/services/gui/modules/widget_elements/widget_element_text_scroll.c +++ b/applications/services/gui/modules/widget_elements/widget_element_text_scroll.c @@ -37,6 +37,8 @@ static bool line->horizontal = AlignRight; } else if(ctrl_symbol == '#') { line->font = FontPrimary; + } else if(ctrl_symbol == '*') { + line->font = FontKeyboard; } furi_string_right(text, 2); processed = true; diff --git a/documentation/file_formats/iButtonFileFormat.md b/documentation/file_formats/iButtonFileFormat.md index c04586195b9..adb493e0552 100644 --- a/documentation/file_formats/iButtonFileFormat.md +++ b/documentation/file_formats/iButtonFileFormat.md @@ -4,26 +4,47 @@ ``` Filetype: Flipper iButton key -Version: 1 -# Key type can be Cyfral, Dallas or Metakom -Key type: Dallas -# Data size for Cyfral is 2, for Metakom is 4, for Dallas is 8 -Data: 12 34 56 78 9A BC DE F0 +Version: 2 +Protocol: DS1992 +Rom Data: 08 DE AD BE EF FA CE 4E +Sram Data: 4E 65 76 65 72 47 6F 6E 6E 61 47 69 76 65 59 6F 75 55 70 4E 65 76 65 72 47 6F 6E 6E 61 4C 65 74 59 6F 75 44 6F 77 6E 4E 65 76 65 72 47 6F 6E 6E 61 52 75 6E 41 72 6F 75 6E 64 41 6E 64 44 65 73 65 72 74 59 6F 75 4E 65 76 65 72 47 6F 6E 6E 61 4D 61 6B 65 59 6F 75 43 72 79 4E 65 76 65 72 47 6F 6E 6E 61 53 61 79 47 6F 6F 64 62 79 65 4E 65 76 65 72 47 6F 6E 6E 61 54 65 6C 6C 41 4C 69 65 ``` ## Description Filename extension: `.ibtn` -The file stores a single iButton key of the type defined by the `Key type` parameter. +The file stores a single iButton key, complete with all data required by the protocol. -### Version history +## Version history +### 2. Current version. +Changelog: +- Added support for different Dallas protocols +- Fields after `Protocol` are protocol-dependent for flexibiliy + +#### Format fields + +| Name | Type | Description | +| --------- | ------ | -------------------------------------------- | +| Protocol | string | Currently supported: DS1990, DS1992, DS1996, DSGeneric*, Cyfral, Metakom | +| Rom Data | hex | Read-only memory data (Dallas protocols only) | +| Sram Data | hex | Static RAM data (DS1992 and DS1996 only) +| Data | hex | Key data (Cyfral & Metakom only) | + +NOTE 1: DSGeneric is a catch-all protocol for all unknown 1-Wire devices. It reads only the ROM and does not perform any checks on the read data. +It can also be used if a key with a deliberately invalid family code or checksum is required. + +NOTE 2: When adding new protocols, it is not necessarily to increase the format version, define the format in the protocol implementation instead. + +### 1. Initial version. +Deprecated, will be converted to current version upon saving. + +#### Format fields + +| Name | Type | Description | +| -------- | ------ | -------------------------------------------- | +| Key type | string | Currently supported: Cyfral, Dallas, Metakom | +| Data | hex | Key data | -1. Initial version. -### Format fields -| Name | Description | -| -------- | -------------------------------------------- | -| Key type | Currently supported: Cyfral, Dallas, Metakom | -| Data | Key data (HEX values) | diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index 549d3812963..f306deb8cd6 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,15.0,, +Version,+,16.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 33bca491a04..f896dd1b319 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,15.1,, +Version,+,16.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -162,9 +162,10 @@ Header,+,lib/mlib/m-rbtree.h,, Header,+,lib/mlib/m-tuple.h,, Header,+,lib/mlib/m-variant.h,, Header,+,lib/nfc/nfc_device.h,, +Header,+,lib/one_wire/ibutton/ibutton_key.h,, +Header,+,lib/one_wire/ibutton/ibutton_protocols.h,, Header,+,lib/one_wire/ibutton/ibutton_worker.h,, Header,+,lib/one_wire/maxim_crc.h,, -Header,+,lib/one_wire/one_wire_device.h,, Header,+,lib/one_wire/one_wire_host.h,, Header,+,lib/one_wire/one_wire_host_timing.h,, Header,+,lib/one_wire/one_wire_slave.h,, @@ -191,6 +192,7 @@ Header,+,lib/toolbox/manchester_decoder.h,, Header,+,lib/toolbox/manchester_encoder.h,, Header,+,lib/toolbox/md5.h,, Header,+,lib/toolbox/path.h,, +Header,+,lib/toolbox/pretty_format.h,, Header,+,lib/toolbox/protocols/protocol_dict.h,, Header,+,lib/toolbox/random_name.h,, Header,+,lib/toolbox/saved_struct.h,, @@ -874,6 +876,7 @@ Function,-,flipper_application_preload_status_to_string,const char*,FlipperAppli Function,+,flipper_application_spawn,FuriThread*,"FlipperApplication*, void*" Function,+,flipper_format_buffered_file_alloc,FlipperFormat*,Storage* Function,+,flipper_format_buffered_file_close,_Bool,FlipperFormat* +Function,+,flipper_format_buffered_file_open_always,_Bool,"FlipperFormat*, const char*" Function,+,flipper_format_buffered_file_open_existing,_Bool,"FlipperFormat*, const char*" Function,+,flipper_format_delete_key,_Bool,"FlipperFormat*, const char*" Function,+,flipper_format_file_alloc,FlipperFormat*,Storage* @@ -1597,22 +1600,33 @@ Function,+,hmac_sha256_update,void,"const hmac_sha256_context*, const uint8_t*, Function,-,hypot,double,"double, double" Function,-,hypotf,float,"float, float" Function,-,hypotl,long double,"long double, long double" -Function,+,ibutton_key_alloc,iButtonKey*, -Function,+,ibutton_key_clear_data,void,iButtonKey* -Function,+,ibutton_key_dallas_crc_is_valid,_Bool,iButtonKey* -Function,+,ibutton_key_dallas_is_1990_key,_Bool,iButtonKey* +Function,+,ibutton_key_alloc,iButtonKey*,size_t Function,+,ibutton_key_free,void,iButtonKey* -Function,+,ibutton_key_get_data_p,const uint8_t*,iButtonKey* -Function,+,ibutton_key_get_data_size,uint8_t,iButtonKey* -Function,+,ibutton_key_get_max_size,uint8_t, -Function,+,ibutton_key_get_size_by_type,uint8_t,iButtonKeyType -Function,+,ibutton_key_get_string_by_type,const char*,iButtonKeyType -Function,+,ibutton_key_get_type,iButtonKeyType,iButtonKey* -Function,+,ibutton_key_get_type_by_string,_Bool,"const char*, iButtonKeyType*" -Function,+,ibutton_key_set,void,"iButtonKey*, const iButtonKey*" -Function,+,ibutton_key_set_data,void,"iButtonKey*, uint8_t*, uint8_t" -Function,+,ibutton_key_set_type,void,"iButtonKey*, iButtonKeyType" -Function,+,ibutton_worker_alloc,iButtonWorker*, +Function,+,ibutton_key_get_protocol_id,iButtonProtocolId,const iButtonKey* +Function,+,ibutton_key_reset,void,iButtonKey* +Function,+,ibutton_key_set_protocol_id,void,"iButtonKey*, iButtonProtocolId" +Function,+,ibutton_protocols_alloc,iButtonProtocols*, +Function,+,ibutton_protocols_apply_edits,void,"iButtonProtocols*, const iButtonKey*" +Function,+,ibutton_protocols_emulate_start,void,"iButtonProtocols*, iButtonKey*" +Function,+,ibutton_protocols_emulate_stop,void,"iButtonProtocols*, iButtonKey*" +Function,+,ibutton_protocols_free,void,iButtonProtocols* +Function,+,ibutton_protocols_get_editable_data,void,"iButtonProtocols*, const iButtonKey*, iButtonEditableData*" +Function,+,ibutton_protocols_get_features,uint32_t,"iButtonProtocols*, iButtonProtocolId" +Function,+,ibutton_protocols_get_id_by_name,iButtonProtocolId,"iButtonProtocols*, const char*" +Function,+,ibutton_protocols_get_manufacturer,const char*,"iButtonProtocols*, iButtonProtocolId" +Function,+,ibutton_protocols_get_max_data_size,size_t,iButtonProtocols* +Function,+,ibutton_protocols_get_name,const char*,"iButtonProtocols*, iButtonProtocolId" +Function,+,ibutton_protocols_get_protocol_count,uint32_t, +Function,+,ibutton_protocols_is_valid,_Bool,"iButtonProtocols*, const iButtonKey*" +Function,+,ibutton_protocols_load,_Bool,"iButtonProtocols*, iButtonKey*, const char*" +Function,+,ibutton_protocols_read,_Bool,"iButtonProtocols*, iButtonKey*" +Function,+,ibutton_protocols_render_brief_data,void,"iButtonProtocols*, const iButtonKey*, FuriString*" +Function,+,ibutton_protocols_render_data,void,"iButtonProtocols*, const iButtonKey*, FuriString*" +Function,+,ibutton_protocols_render_error,void,"iButtonProtocols*, const iButtonKey*, FuriString*" +Function,+,ibutton_protocols_save,_Bool,"iButtonProtocols*, const iButtonKey*, const char*" +Function,+,ibutton_protocols_write_blank,_Bool,"iButtonProtocols*, iButtonKey*" +Function,+,ibutton_protocols_write_copy,_Bool,"iButtonProtocols*, iButtonKey*" +Function,+,ibutton_worker_alloc,iButtonWorker*,iButtonProtocols* Function,+,ibutton_worker_emulate_set_callback,void,"iButtonWorker*, iButtonWorkerEmulateCallback, void*" Function,+,ibutton_worker_emulate_start,void,"iButtonWorker*, iButtonKey*" Function,+,ibutton_worker_free,void,iButtonWorker* @@ -1621,8 +1635,9 @@ Function,+,ibutton_worker_read_start,void,"iButtonWorker*, iButtonKey*" Function,+,ibutton_worker_start_thread,void,iButtonWorker* Function,+,ibutton_worker_stop,void,iButtonWorker* Function,+,ibutton_worker_stop_thread,void,iButtonWorker* +Function,+,ibutton_worker_write_blank_start,void,"iButtonWorker*, iButtonKey*" +Function,+,ibutton_worker_write_copy_start,void,"iButtonWorker*, iButtonKey*" Function,+,ibutton_worker_write_set_callback,void,"iButtonWorker*, iButtonWorkerWriteCallback, void*" -Function,+,ibutton_worker_write_start,void,"iButtonWorker*, iButtonKey*" Function,+,icon_animation_alloc,IconAnimation*,const Icon* Function,+,icon_animation_free,void,IconAnimation* Function,+,icon_animation_get_height,uint8_t,const IconAnimation* @@ -2034,12 +2049,6 @@ Function,+,notification_message,void,"NotificationApp*, const NotificationSequen Function,+,notification_message_block,void,"NotificationApp*, const NotificationSequence*" Function,-,nrand48,long,unsigned short[3] Function,-,on_exit,int,"void (*)(int, void*), void*" -Function,+,onewire_device_alloc,OneWireDevice*,"uint8_t, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t" -Function,+,onewire_device_attach,void,"OneWireDevice*, OneWireSlave*" -Function,+,onewire_device_detach,void,OneWireDevice* -Function,+,onewire_device_free,void,OneWireDevice* -Function,+,onewire_device_get_id_p,uint8_t*,OneWireDevice* -Function,+,onewire_device_send_id,void,OneWireDevice* Function,+,onewire_host_alloc,OneWireHost*,const GpioPin* Function,+,onewire_host_free,void,OneWireHost* Function,+,onewire_host_read,uint8_t,OneWireHost* @@ -2054,10 +2063,15 @@ Function,+,onewire_host_stop,void,OneWireHost* Function,+,onewire_host_target_search,void,"OneWireHost*, uint8_t" Function,+,onewire_host_write,void,"OneWireHost*, uint8_t" Function,+,onewire_host_write_bit,void,"OneWireHost*, _Bool" +Function,+,onewire_host_write_bytes,void,"OneWireHost*, const uint8_t*, uint16_t" Function,+,onewire_slave_alloc,OneWireSlave*,const GpioPin* -Function,+,onewire_slave_attach,void,"OneWireSlave*, OneWireDevice*" -Function,+,onewire_slave_detach,void,OneWireSlave* Function,+,onewire_slave_free,void,OneWireSlave* +Function,+,onewire_slave_receive,_Bool,"OneWireSlave*, uint8_t*, size_t" +Function,+,onewire_slave_receive_bit,_Bool,OneWireSlave* +Function,+,onewire_slave_send,_Bool,"OneWireSlave*, const uint8_t*, size_t" +Function,+,onewire_slave_send_bit,_Bool,"OneWireSlave*, _Bool" +Function,+,onewire_slave_set_command_callback,void,"OneWireSlave*, OneWireSlaveCommandCallback, void*" +Function,+,onewire_slave_set_reset_callback,void,"OneWireSlave*, OneWireSlaveResetCallback, void*" Function,+,onewire_slave_set_result_callback,void,"OneWireSlave*, OneWireSlaveResultCallback, void*" Function,+,onewire_slave_start,void,OneWireSlave* Function,+,onewire_slave_stop,void,OneWireSlave* @@ -2105,6 +2119,7 @@ Function,+,power_off,void,Power* Function,+,power_reboot,void,PowerBootMode Function,+,powf,float,"float, float" Function,-,powl,long double,"long double, long double" +Function,+,pretty_format_bytes_hex_canonical,void,"FuriString*, size_t, const char*, const uint8_t*, size_t" Function,-,printf,int,"const char*, ..." Function,-,prng_successor,uint32_t,"uint32_t, uint32_t" Function,+,property_value_out,void,"PropertyValueContext*, const char*, unsigned int, ..." diff --git a/lib/flipper_format/flipper_format.c b/lib/flipper_format/flipper_format.c index 292dab5f1b4..bb1aa59f586 100644 --- a/lib/flipper_format/flipper_format.c +++ b/lib/flipper_format/flipper_format.c @@ -91,6 +91,12 @@ bool flipper_format_file_open_always(FlipperFormat* flipper_format, const char* return file_stream_open(flipper_format->stream, path, FSAM_READ_WRITE, FSOM_CREATE_ALWAYS); } +bool flipper_format_buffered_file_open_always(FlipperFormat* flipper_format, const char* path) { + furi_assert(flipper_format); + return buffered_file_stream_open( + flipper_format->stream, path, FSAM_READ_WRITE, FSOM_CREATE_ALWAYS); +} + bool flipper_format_file_open_new(FlipperFormat* flipper_format, const char* path) { furi_assert(flipper_format); return file_stream_open(flipper_format->stream, path, FSAM_READ_WRITE, FSOM_CREATE_NEW); diff --git a/lib/flipper_format/flipper_format.h b/lib/flipper_format/flipper_format.h index 743918e3bc0..671ff6fa0cc 100644 --- a/lib/flipper_format/flipper_format.h +++ b/lib/flipper_format/flipper_format.h @@ -131,7 +131,7 @@ bool flipper_format_file_open_existing(FlipperFormat* flipper_format, const char /** * Open existing file, buffered mode. - * Use only if FlipperFormat allocated as a file. + * Use only if FlipperFormat allocated as a buffered file. * @param flipper_format Pointer to a FlipperFormat instance * @param path File path * @return True on success @@ -156,6 +156,15 @@ bool flipper_format_file_open_append(FlipperFormat* flipper_format, const char* */ bool flipper_format_file_open_always(FlipperFormat* flipper_format, const char* path); +/** + * Open file. Creates a new file, or deletes the contents of the file if it already exists, buffered mode. + * Use only if FlipperFormat allocated as a buffered file. + * @param flipper_format Pointer to a FlipperFormat instance + * @param path File path + * @return True on success + */ +bool flipper_format_buffered_file_open_always(FlipperFormat* flipper_format, const char* path); + /** * Open file. Creates a new file, fails if file already exists. * Use only if FlipperFormat allocated as a file. diff --git a/lib/one_wire/SConscript b/lib/one_wire/SConscript index 5e06d21e391..56d4759ebe1 100644 --- a/lib/one_wire/SConscript +++ b/lib/one_wire/SConscript @@ -11,8 +11,9 @@ env.Append( File("one_wire_host_timing.h"), File("one_wire_host.h"), File("one_wire_slave.h"), - File("one_wire_device.h"), + File("ibutton/ibutton_key.h"), File("ibutton/ibutton_worker.h"), + File("ibutton/ibutton_protocols.h"), File("maxim_crc.h"), ], ) diff --git a/lib/one_wire/ibutton/ibutton_key.c b/lib/one_wire/ibutton/ibutton_key.c index 7b7571a29f2..926a826a26e 100644 --- a/lib/one_wire/ibutton/ibutton_key.c +++ b/lib/one_wire/ibutton/ibutton_key.c @@ -1,110 +1,43 @@ -#include -#include -#include "ibutton_key.h" +#include "ibutton_key_i.h" struct iButtonKey { - uint8_t data[IBUTTON_KEY_DATA_SIZE]; - iButtonKeyType type; + iButtonProtocolId protocol_id; + iButtonProtocolData* protocol_data; + size_t protocol_data_size; }; -iButtonKey* ibutton_key_alloc() { +iButtonKey* ibutton_key_alloc(size_t data_size) { iButtonKey* key = malloc(sizeof(iButtonKey)); - memset(key, 0, sizeof(iButtonKey)); + + key->protocol_id = iButtonProtocolIdInvalid; + key->protocol_data = malloc(data_size); + key->protocol_data_size = data_size; + return key; } void ibutton_key_free(iButtonKey* key) { + free(key->protocol_data); free(key); } -void ibutton_key_set(iButtonKey* to, const iButtonKey* from) { - memcpy(to, from, sizeof(iButtonKey)); -} - -void ibutton_key_set_data(iButtonKey* key, uint8_t* data, uint8_t data_count) { - furi_check(data_count > 0); - furi_check(data_count <= IBUTTON_KEY_DATA_SIZE); - - memset(key->data, 0, IBUTTON_KEY_DATA_SIZE); - memcpy(key->data, data, data_count); -} - -void ibutton_key_clear_data(iButtonKey* key) { - memset(key->data, 0, IBUTTON_KEY_DATA_SIZE); -} - -const uint8_t* ibutton_key_get_data_p(iButtonKey* key) { - return key->data; -} - -uint8_t ibutton_key_get_data_size(iButtonKey* key) { - return ibutton_key_get_size_by_type(key->type); +void ibutton_key_reset(iButtonKey* key) { + key->protocol_id = iButtonProtocolIdInvalid; + memset(key->protocol_data, 0, key->protocol_data_size); } -void ibutton_key_set_type(iButtonKey* key, iButtonKeyType key_type) { - key->type = key_type; -} - -iButtonKeyType ibutton_key_get_type(iButtonKey* key) { - return key->type; -} - -const char* ibutton_key_get_string_by_type(iButtonKeyType key_type) { - switch(key_type) { - case iButtonKeyCyfral: - return "Cyfral"; - break; - case iButtonKeyMetakom: - return "Metakom"; - break; - case iButtonKeyDS1990: - return "Dallas"; - break; - default: - furi_crash("Invalid iButton type"); - } -} - -bool ibutton_key_get_type_by_string(const char* type_string, iButtonKeyType* key_type) { - if(strcmp(type_string, ibutton_key_get_string_by_type(iButtonKeyCyfral)) == 0) { - *key_type = iButtonKeyCyfral; - } else if(strcmp(type_string, ibutton_key_get_string_by_type(iButtonKeyMetakom)) == 0) { - *key_type = iButtonKeyMetakom; - } else if(strcmp(type_string, ibutton_key_get_string_by_type(iButtonKeyDS1990)) == 0) { - *key_type = iButtonKeyDS1990; - } else { - return false; - } - - return true; -} - -uint8_t ibutton_key_get_size_by_type(iButtonKeyType key_type) { - uint8_t size = 0; - - switch(key_type) { - case iButtonKeyCyfral: - size = 2; - break; - case iButtonKeyMetakom: - size = 4; - break; - case iButtonKeyDS1990: - size = 8; - break; - } - - return size; +iButtonProtocolId ibutton_key_get_protocol_id(const iButtonKey* key) { + return key->protocol_id; } -uint8_t ibutton_key_get_max_size() { - return IBUTTON_KEY_DATA_SIZE; +void ibutton_key_set_protocol_id(iButtonKey* key, iButtonProtocolId protocol_id) { + key->protocol_id = protocol_id; } -bool ibutton_key_dallas_crc_is_valid(iButtonKey* key) { - return (maxim_crc8(key->data, 8, MAXIM_CRC8_INIT) == 0); +iButtonProtocolData* ibutton_key_get_protocol_data(const iButtonKey* key) { + return key->protocol_data; } -bool ibutton_key_dallas_is_1990_key(iButtonKey* key) { - return (key->data[0] == 0x01); +size_t ibutton_key_get_protocol_data_size(const iButtonKey* key) { + return key->protocol_data_size; } diff --git a/lib/one_wire/ibutton/ibutton_key.h b/lib/one_wire/ibutton/ibutton_key.h index d682555a86d..1848cd67212 100644 --- a/lib/one_wire/ibutton/ibutton_key.h +++ b/lib/one_wire/ibutton/ibutton_key.h @@ -5,127 +5,49 @@ */ #pragma once -#include + +#include + +#include "protocols/protocol_common.h" #ifdef __cplusplus extern "C" { #endif -#define IBUTTON_KEY_DATA_SIZE 8 -#define IBUTTON_KEY_NAME_SIZE 22 - -typedef enum { - iButtonKeyDS1990, - iButtonKeyCyfral, - iButtonKeyMetakom, -} iButtonKeyType; - typedef struct iButtonKey iButtonKey; /** - * Allocate key - * @return iButtonKey* + * Allocate a key object + * @param [in] data_size maximum data size held by the key + * @return pointer to the key object */ -iButtonKey* ibutton_key_alloc(); +iButtonKey* ibutton_key_alloc(size_t data_size); /** - * Free key - * @param key + * Destroy the key object, free resources + * @param [in] key pointer to the key object */ void ibutton_key_free(iButtonKey* key); /** - * Copy key - * @param to - * @param from - */ -void ibutton_key_set(iButtonKey* to, const iButtonKey* from); - -/** - * Set key data - * @param key - * @param data - * @param data_count - */ -void ibutton_key_set_data(iButtonKey* key, uint8_t* data, uint8_t data_count); - -/** - * Clear key data - * @param key - */ -void ibutton_key_clear_data(iButtonKey* key); - -/** - * Get pointer to key data - * @param key - * @return const uint8_t* - */ -const uint8_t* ibutton_key_get_data_p(iButtonKey* key); - -/** - * Get key data size - * @param key - * @return uint8_t - */ -uint8_t ibutton_key_get_data_size(iButtonKey* key); - -/** - * Set key type - * @param key - * @param key_type - */ -void ibutton_key_set_type(iButtonKey* key, iButtonKeyType key_type); - -/** - * Get key type - * @param key - * @return iButtonKeyType - */ -iButtonKeyType ibutton_key_get_type(iButtonKey* key); - -/** - * Get type string from key type - * @param key_type - * @return const char* - */ -const char* ibutton_key_get_string_by_type(iButtonKeyType key_type); - -/** - * Get key type from string - * @param type_string - * @param key_type - * @return bool - */ -bool ibutton_key_get_type_by_string(const char* type_string, iButtonKeyType* key_type); - -/** - * Get key data size from type - * @param key_type - * @return uint8_t - */ -uint8_t ibutton_key_get_size_by_type(iButtonKeyType key_type); - -/** - * Get max key size - * @return uint8_t + * Get the protocol id held by the key + * @param [in] key pointer to the key object + * @return protocol id held by the key */ -uint8_t ibutton_key_get_max_size(); +iButtonProtocolId ibutton_key_get_protocol_id(const iButtonKey* key); /** - * Check if CRC for onewire key is valid - * @param key - * @return true - * @return false + * Set the protocol id held by the key + * @param [in] key pointer to the key object + * @param [in] protocol_id new protocol id */ -bool ibutton_key_dallas_crc_is_valid(iButtonKey* key); +void ibutton_key_set_protocol_id(iButtonKey* key, iButtonProtocolId protocol_id); /** - * Check if onewire key is a DS1990 key - * @param key - * @return true - * @return false + * Reset the protocol id and data held by the key + * @param [in] key pointer to the key object */ -bool ibutton_key_dallas_is_1990_key(iButtonKey* key); +void ibutton_key_reset(iButtonKey* key); #ifdef __cplusplus } diff --git a/lib/one_wire/ibutton/ibutton_key_command.h b/lib/one_wire/ibutton/ibutton_key_command.h deleted file mode 100644 index 88049d0643d..00000000000 --- a/lib/one_wire/ibutton/ibutton_key_command.h +++ /dev/null @@ -1,28 +0,0 @@ -/** - * @file ibutton_key_command.h - * - * List of misc commands for Dallas and blanks - */ - -#pragma once - -#define RW1990_1_CMD_WRITE_RECORD_FLAG 0xD1 -#define RW1990_1_CMD_READ_RECORD_FLAG 0xB5 -#define RW1990_1_CMD_WRITE_ROM 0xD5 - -#define RW1990_2_CMD_WRITE_RECORD_FLAG 0x1D -#define RW1990_2_CMD_READ_RECORD_FLAG 0x1E -#define RW1990_2_CMD_WRITE_ROM 0xD5 - -#define TM2004_CMD_READ_STATUS 0xAA -#define TM2004_CMD_READ_MEMORY 0xF0 -#define TM2004_CMD_WRITE_ROM 0x3C -#define TM2004_CMD_FINALIZATION 0x35 -#define TM2004_ANSWER_READ_MEMORY 0xF5 - -#define TM01_CMD_WRITE_RECORD_FLAG 0xC1 -#define TM01_CMD_WRITE_ROM 0xC5 -#define TM01_CMD_SWITCH_TO_CYFRAL 0xCA -#define TM01_CMD_SWITCH_TO_METAKOM 0xCB - -#define DS1990_CMD_READ_ROM 0x33 diff --git a/lib/one_wire/ibutton/ibutton_key_i.h b/lib/one_wire/ibutton/ibutton_key_i.h new file mode 100644 index 00000000000..b527c65b444 --- /dev/null +++ b/lib/one_wire/ibutton/ibutton_key_i.h @@ -0,0 +1,9 @@ +#pragma once + +#include "ibutton_key.h" + +#include "protocols/protocol_common_i.h" + +iButtonProtocolData* ibutton_key_get_protocol_data(const iButtonKey* key); + +size_t ibutton_key_get_protocol_data_size(const iButtonKey* key); diff --git a/lib/one_wire/ibutton/ibutton_protocols.c b/lib/one_wire/ibutton/ibutton_protocols.c new file mode 100644 index 00000000000..75aa225efe4 --- /dev/null +++ b/lib/one_wire/ibutton/ibutton_protocols.c @@ -0,0 +1,309 @@ +#include "ibutton_protocols.h" + +#include + +#include "ibutton_key_i.h" + +#include "protocols/protocol_group_defs.h" + +#define IBUTTON_FILE_TYPE "Flipper iButton key" + +#define IBUTTON_PROTOCOL_KEY_V1 "Key type" +#define IBUTTON_PROTOCOL_KEY_V2 "Protocol" + +#define IBUTTON_CURRENT_FORMAT_VERSION 2U + +#define GET_PROTOCOL_GROUP(id) \ + iButtonProtocolGroupInfo info; \ + ibutton_protocols_get_group_by_id(protocols, (id), &info); + +#define GROUP_BASE (info.base) +#define GROUP_DATA (info.group) +#define PROTOCOL_ID (info.id) + +struct iButtonProtocols { + iButtonProtocolGroupData** group_datas; +}; + +typedef struct { + const iButtonProtocolGroupBase* base; + iButtonProtocolGroupData* group; + iButtonProtocolLocalId id; +} iButtonProtocolGroupInfo; + +static void ibutton_protocols_get_group_by_id( + iButtonProtocols* protocols, + iButtonProtocolId id, + iButtonProtocolGroupInfo* info) { + iButtonProtocolLocalId local_id = id; + + for(iButtonProtocolGroupId i = 0; i < iButtonProtocolGroupMax; ++i) { + if(local_id < (signed)ibutton_protocol_groups[i]->protocol_count) { + info->base = ibutton_protocol_groups[i]; + info->group = protocols->group_datas[i]; + info->id = local_id; + return; + + } else { + local_id -= ibutton_protocol_groups[i]->protocol_count; + } + } + furi_crash(NULL); +} + +iButtonProtocols* ibutton_protocols_alloc() { + iButtonProtocols* protocols = malloc(sizeof(iButtonProtocols*)); + + protocols->group_datas = malloc(sizeof(iButtonProtocolGroupData*) * iButtonProtocolGroupMax); + + for(iButtonProtocolGroupId i = 0; i < iButtonProtocolGroupMax; ++i) { + protocols->group_datas[i] = ibutton_protocol_groups[i]->alloc(); + } + + return protocols; +} + +void ibutton_protocols_free(iButtonProtocols* protocols) { + for(iButtonProtocolGroupId i = 0; i < iButtonProtocolGroupMax; ++i) { + ibutton_protocol_groups[i]->free(protocols->group_datas[i]); + } + + free(protocols->group_datas); + free(protocols); +} + +uint32_t ibutton_protocols_get_protocol_count() { + uint32_t count = 0; + + for(iButtonProtocolGroupId i = 0; i < iButtonProtocolGroupMax; ++i) { + count += ibutton_protocol_groups[i]->protocol_count; + } + + return count; +} + +iButtonProtocolId ibutton_protocols_get_id_by_name(iButtonProtocols* protocols, const char* name) { + iButtonProtocolLocalId offset = 0; + + for(iButtonProtocolGroupId i = 0; i < iButtonProtocolGroupMax; ++i) { + iButtonProtocolLocalId local_id; + if(ibutton_protocol_groups[i]->get_id_by_name(protocols->group_datas[i], &local_id, name)) { + return local_id + offset; + } + offset += ibutton_protocol_groups[i]->protocol_count; + } + return iButtonProtocolIdInvalid; +} + +uint32_t ibutton_protocols_get_features(iButtonProtocols* protocols, iButtonProtocolId id) { + GET_PROTOCOL_GROUP(id); + return GROUP_BASE->get_features(GROUP_DATA, PROTOCOL_ID); +} + +size_t ibutton_protocols_get_max_data_size(iButtonProtocols* protocols) { + size_t max_size = 0; + + for(iButtonProtocolGroupId i = 0; i < iButtonProtocolGroupMax; ++i) { + const size_t current_max_size = + ibutton_protocol_groups[i]->get_max_data_size(protocols->group_datas[i]); + if(current_max_size > max_size) { + max_size = current_max_size; + } + } + + return max_size; +} + +const char* ibutton_protocols_get_manufacturer(iButtonProtocols* protocols, iButtonProtocolId id) { + GET_PROTOCOL_GROUP(id); + return GROUP_BASE->get_manufacturer(GROUP_DATA, PROTOCOL_ID); +} + +const char* ibutton_protocols_get_name(iButtonProtocols* protocols, iButtonProtocolId id) { + GET_PROTOCOL_GROUP(id); + return GROUP_BASE->get_name(GROUP_DATA, PROTOCOL_ID); +} + +bool ibutton_protocols_read(iButtonProtocols* protocols, iButtonKey* key) { + iButtonProtocolLocalId id = iButtonProtocolIdInvalid; + iButtonProtocolData* data = ibutton_key_get_protocol_data(key); + + iButtonProtocolLocalId offset = 0; + for(iButtonProtocolGroupId i = 0; i < iButtonProtocolGroupMax; ++i) { + if(ibutton_protocol_groups[i]->read(protocols->group_datas[i], data, &id)) { + id += offset; + break; + } + offset += ibutton_protocol_groups[i]->protocol_count; + } + + ibutton_key_set_protocol_id(key, id); + return id != iButtonProtocolIdInvalid; +} + +bool ibutton_protocols_write_blank(iButtonProtocols* protocols, iButtonKey* key) { + const iButtonProtocolId id = ibutton_key_get_protocol_id(key); + iButtonProtocolData* data = ibutton_key_get_protocol_data(key); + + GET_PROTOCOL_GROUP(id); + return GROUP_BASE->write_blank(GROUP_DATA, data, PROTOCOL_ID); +} + +bool ibutton_protocols_write_copy(iButtonProtocols* protocols, iButtonKey* key) { + const iButtonProtocolId id = ibutton_key_get_protocol_id(key); + iButtonProtocolData* data = ibutton_key_get_protocol_data(key); + + GET_PROTOCOL_GROUP(id); + return GROUP_BASE->write_copy(GROUP_DATA, data, PROTOCOL_ID); +} + +void ibutton_protocols_emulate_start(iButtonProtocols* protocols, iButtonKey* key) { + const iButtonProtocolId id = ibutton_key_get_protocol_id(key); + iButtonProtocolData* data = ibutton_key_get_protocol_data(key); + + GET_PROTOCOL_GROUP(id); + GROUP_BASE->emulate_start(GROUP_DATA, data, PROTOCOL_ID); +} + +void ibutton_protocols_emulate_stop(iButtonProtocols* protocols, iButtonKey* key) { + const iButtonProtocolId id = ibutton_key_get_protocol_id(key); + iButtonProtocolData* data = ibutton_key_get_protocol_data(key); + + GET_PROTOCOL_GROUP(id); + GROUP_BASE->emulate_stop(GROUP_DATA, data, PROTOCOL_ID); +} + +bool ibutton_protocols_save( + iButtonProtocols* protocols, + const iButtonKey* key, + const char* file_name) { + const iButtonProtocolId id = ibutton_key_get_protocol_id(key); + const iButtonProtocolData* data = ibutton_key_get_protocol_data(key); + + bool success = false; + Storage* storage = furi_record_open(RECORD_STORAGE); + + FlipperFormat* ff = flipper_format_buffered_file_alloc(storage); + + do { + const char* protocol_name = ibutton_protocols_get_name(protocols, id); + + if(!flipper_format_buffered_file_open_always(ff, file_name)) break; + + if(!flipper_format_write_header_cstr(ff, IBUTTON_FILE_TYPE, IBUTTON_CURRENT_FORMAT_VERSION)) + break; + if(!flipper_format_write_string_cstr(ff, IBUTTON_PROTOCOL_KEY_V2, protocol_name)) break; + + GET_PROTOCOL_GROUP(id); + if(!GROUP_BASE->save(GROUP_DATA, data, PROTOCOL_ID, ff)) break; + + success = true; + } while(false); + + flipper_format_free(ff); + furi_record_close(RECORD_STORAGE); + + return success; +} + +bool ibutton_protocols_load(iButtonProtocols* protocols, iButtonKey* key, const char* file_name) { + iButtonProtocolData* data = ibutton_key_get_protocol_data(key); + + bool success = false; + Storage* storage = furi_record_open(RECORD_STORAGE); + + FlipperFormat* ff = flipper_format_buffered_file_alloc(storage); + FuriString* tmp = furi_string_alloc(); + + do { + if(!flipper_format_buffered_file_open_existing(ff, file_name)) break; + + uint32_t version; + + if(!flipper_format_read_header(ff, tmp, &version)) break; + if(!furi_string_equal(tmp, IBUTTON_FILE_TYPE)) break; + + if(version == 1) { + if(!flipper_format_read_string(ff, IBUTTON_PROTOCOL_KEY_V1, tmp)) break; + } else if(version == 2) { + if(!flipper_format_read_string(ff, IBUTTON_PROTOCOL_KEY_V2, tmp)) break; + } else { + break; + } + + const iButtonProtocolId id = + ibutton_protocols_get_id_by_name(protocols, furi_string_get_cstr(tmp)); + ibutton_key_set_protocol_id(key, id); + + GET_PROTOCOL_GROUP(id); + if(!GROUP_BASE->load(GROUP_DATA, data, PROTOCOL_ID, version, ff)) break; + + success = true; + } while(false); + + flipper_format_free(ff); + furi_string_free(tmp); + furi_record_close(RECORD_STORAGE); + + return success; +} + +void ibutton_protocols_render_data( + iButtonProtocols* protocols, + const iButtonKey* key, + FuriString* result) { + const iButtonProtocolId id = ibutton_key_get_protocol_id(key); + const iButtonProtocolData* data = ibutton_key_get_protocol_data(key); + + GET_PROTOCOL_GROUP(id); + GROUP_BASE->render_data(GROUP_DATA, data, PROTOCOL_ID, result); +} + +void ibutton_protocols_render_brief_data( + iButtonProtocols* protocols, + const iButtonKey* key, + FuriString* result) { + const iButtonProtocolId id = ibutton_key_get_protocol_id(key); + const iButtonProtocolData* data = ibutton_key_get_protocol_data(key); + + GET_PROTOCOL_GROUP(id); + GROUP_BASE->render_brief_data(GROUP_DATA, data, PROTOCOL_ID, result); +} + +void ibutton_protocols_render_error( + iButtonProtocols* protocols, + const iButtonKey* key, + FuriString* result) { + const iButtonProtocolId id = ibutton_key_get_protocol_id(key); + const iButtonProtocolData* data = ibutton_key_get_protocol_data(key); + + GET_PROTOCOL_GROUP(id); + GROUP_BASE->render_error(GROUP_DATA, data, PROTOCOL_ID, result); +} + +bool ibutton_protocols_is_valid(iButtonProtocols* protocols, const iButtonKey* key) { + const iButtonProtocolId id = ibutton_key_get_protocol_id(key); + const iButtonProtocolData* data = ibutton_key_get_protocol_data(key); + + GET_PROTOCOL_GROUP(id); + return GROUP_BASE->is_valid(GROUP_DATA, data, PROTOCOL_ID); +} + +void ibutton_protocols_get_editable_data( + iButtonProtocols* protocols, + const iButtonKey* key, + iButtonEditableData* editable) { + const iButtonProtocolId id = ibutton_key_get_protocol_id(key); + iButtonProtocolData* data = ibutton_key_get_protocol_data(key); + + GET_PROTOCOL_GROUP(id); + GROUP_BASE->get_editable_data(GROUP_DATA, data, PROTOCOL_ID, editable); +} + +void ibutton_protocols_apply_edits(iButtonProtocols* protocols, const iButtonKey* key) { + const iButtonProtocolId id = ibutton_key_get_protocol_id(key); + iButtonProtocolData* data = ibutton_key_get_protocol_data(key); + + GET_PROTOCOL_GROUP(id); + GROUP_BASE->apply_edits(GROUP_DATA, data, PROTOCOL_ID); +} diff --git a/lib/one_wire/ibutton/ibutton_protocols.h b/lib/one_wire/ibutton/ibutton_protocols.h new file mode 100644 index 00000000000..0e7ed0a804a --- /dev/null +++ b/lib/one_wire/ibutton/ibutton_protocols.h @@ -0,0 +1,197 @@ +/** + * @file ibutton_protocols.h + * + * Common interface for accessing various iButton protocols + */ + +#pragma once + +#include +#include + +#include "protocols/protocol_common.h" + +#include "ibutton_key.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct iButtonProtocols iButtonProtocols; + +/** + * Allocate an iButtonProtocols object + * @return pointer to an iButtonProtocols object + */ +iButtonProtocols* ibutton_protocols_alloc(); + +/** + * Destroy an iButtonProtocols object, free resources + * @param [in] protocols pointer to an iButtonProtocols object + */ +void ibutton_protocols_free(iButtonProtocols* protocols); + +/** + * Get the total number of available protocols + */ +uint32_t ibutton_protocols_get_protocol_count(); + +/** + * Get maximum data size out of all protocols available + * @param [in] protocols pointer to an iButtonProtocols object + * @return maximum data size in bytes + */ +size_t ibutton_protocols_get_max_data_size(iButtonProtocols* protocols); + +/** + * Get the protocol id based on its name + * @param [in] protocols pointer to an iButtonProtocols object + * @param [in] name pointer to a string containing the name + * @return protocol id on success on iButtonProtocolIdInvalid on failure + */ +iButtonProtocolId ibutton_protocols_get_id_by_name(iButtonProtocols* protocols, const char* name); + +/** + * Get the manufacturer name based on the protocol id + * @param [in] protocols pointer to an iButtonProtocols object + * @param [in] id id of the protocol in question + * @return pointer to a statically allocated string with manufacturer name + */ +const char* ibutton_protocols_get_manufacturer(iButtonProtocols* protocols, iButtonProtocolId id); + +/** + * Get the protocol name based on the protocol id + * @param [in] protocols pointer to an iButtonProtocols object + * @param [in] id id of the protocol in question + * @return pointer to a statically allocated string with protocol name + */ +const char* ibutton_protocols_get_name(iButtonProtocols* protocols, iButtonProtocolId id); + +/** + * Get protocol features bitmask by protocol id + * @param [in] protocols pointer to an iButtonProtocols object + * @param [in] id id of the protocol in question + */ +uint32_t ibutton_protocols_get_features(iButtonProtocols* protocols, iButtonProtocolId id); + +/** + * Read a physical device (a key or an emulator) + * @param [in] protocols pointer to an iButtonProtocols object + * @param [out] key pointer to the key to read into (must be allocated before) + * @return true on success, false on failure + */ +bool ibutton_protocols_read(iButtonProtocols* protocols, iButtonKey* key); + +/** + * Write the key to a blank + * @param [in] protocols pointer to an iButtonProtocols object + * @param [in] key pointer to the key to be written + * @return true on success, false on failure + */ +bool ibutton_protocols_write_blank(iButtonProtocols* protocols, iButtonKey* key); + +/** + * Write the key to another one of the same type + * @param [in] protocols pointer to an iButtonProtocols object + * @param [in] key pointer to the key to be written + * @return true on success, false on failure + */ +bool ibutton_protocols_write_copy(iButtonProtocols* protocols, iButtonKey* key); + +/** + * Start emulating the key + * @param [in] protocols pointer to an iButtonProtocols object + * @param [in] key pointer to the key to be emulated + */ +void ibutton_protocols_emulate_start(iButtonProtocols* protocols, iButtonKey* key); + +/** + * Stop emulating the key + * @param [in] protocols pointer to an iButtonProtocols object + * @param [in] key pointer to the key to be emulated + */ +void ibutton_protocols_emulate_stop(iButtonProtocols* protocols, iButtonKey* key); + +/** + * Save the key data to a file. + * @param [in] protocols pointer to an iButtonProtocols object + * @param [in] key pointer to the key to be saved + * @param [in] file_name full absolute path to the file name + * @return true on success, false on failure + */ +bool ibutton_protocols_save( + iButtonProtocols* protocols, + const iButtonKey* key, + const char* file_name); + +/** + * Load the key from a file. + * @param [in] protocols pointer to an iButtonProtocols object + * @param [out] key pointer to the key to load into (must be allocated before) + * @param [in] file_name full absolute path to the file name + * @return true on success, false on failure + */ +bool ibutton_protocols_load(iButtonProtocols* protocols, iButtonKey* key, const char* file_name); + +/** + * Format a string containing device full data + * @param [in] protocols pointer to an iButtonProtocols object + * @param [in] key pointer to the key to be rendered + * @param [out] result pointer to the FuriString instance (must be initialized) + */ +void ibutton_protocols_render_data( + iButtonProtocols* protocols, + const iButtonKey* key, + FuriString* result); + +/** + * Format a string containing device brief data + * @param [in] protocols pointer to an iButtonProtocols object + * @param [in] key pointer to the key to be rendered + * @param [out] result pointer to the FuriString instance (must be initialized) + */ +void ibutton_protocols_render_brief_data( + iButtonProtocols* protocols, + const iButtonKey* key, + FuriString* result); + +/** + * Format a string containing error message (for invalid keys) + * @param [in] protocols pointer to an iButtonProtocols object + * @param [in] key pointer to the key to be rendered + * @param [out] result pointer to the FuriString instance (must be initialized) + */ +void ibutton_protocols_render_error( + iButtonProtocols* protocols, + const iButtonKey* key, + FuriString* result); + +/** + * Check whether the key data is valid + * @param [in] protocols pointer to an iButtonProtocols object + * @param [in] key pointer to the key to be checked + * @return true if data is valid, false otherwise + */ +bool ibutton_protocols_is_valid(iButtonProtocols* protocols, const iButtonKey* key); + +/** + * Get a pointer to the key's editable data (for in-place editing) + * @param [in] protocols pointer to an iButtonProtocols object + * @param [in] key pointer to the key to be checked + * @param [out] editable pointer to a structure to contain the editable data + */ +void ibutton_protocols_get_editable_data( + iButtonProtocols* protocols, + const iButtonKey* key, + iButtonEditableData* editable); + +/** + * Make all necessary internal adjustments after editing the key + * @param [in] protocols pointer to an iButtonProtocols object + * @param [in,out] key pointer to the key to be adjusted + */ +void ibutton_protocols_apply_edits(iButtonProtocols* protocols, const iButtonKey* key); + +#ifdef __cplusplus +} +#endif diff --git a/lib/one_wire/ibutton/ibutton_worker.c b/lib/one_wire/ibutton/ibutton_worker.c index 1fe39b5e5e7..d40dba71aeb 100644 --- a/lib/one_wire/ibutton/ibutton_worker.c +++ b/lib/one_wire/ibutton/ibutton_worker.c @@ -1,13 +1,14 @@ -#include -#include -#include #include "ibutton_worker_i.h" +#include "ibutton_protocols.h" + +#include typedef enum { iButtonMessageEnd, iButtonMessageStop, iButtonMessageRead, - iButtonMessageWrite, + iButtonMessageWriteBlank, + iButtonMessageWriteCopy, iButtonMessageEmulate, iButtonMessageNotifyEmulate, } iButtonMessageType; @@ -21,26 +22,15 @@ typedef struct { static int32_t ibutton_worker_thread(void* thread_context); -iButtonWorker* ibutton_worker_alloc() { +iButtonWorker* ibutton_worker_alloc(iButtonProtocols* protocols) { iButtonWorker* worker = malloc(sizeof(iButtonWorker)); - worker->key_p = NULL; - worker->key_data = malloc(ibutton_key_get_max_size()); - worker->host = onewire_host_alloc(&ibutton_gpio); - worker->slave = onewire_slave_alloc(&ibutton_gpio); - worker->writer = ibutton_writer_alloc(worker->host); - worker->device = onewire_device_alloc(0, 0, 0, 0, 0, 0, 0, 0); - worker->messages = furi_message_queue_alloc(1, sizeof(iButtonMessage)); - worker->mode_index = iButtonWorkerIdle; - worker->read_cb = NULL; - worker->write_cb = NULL; - worker->emulate_cb = NULL; - worker->cb_ctx = NULL; + worker->protocols = protocols; + worker->messages = furi_message_queue_alloc(1, sizeof(iButtonMessage)); + worker->mode_index = iButtonWorkerModeIdle; worker->thread = furi_thread_alloc_ex("iButtonWorker", 2048, ibutton_worker_thread, worker); - worker->protocols = protocol_dict_alloc(ibutton_protocols, iButtonProtocolMax); - return worker; } @@ -48,7 +38,7 @@ void ibutton_worker_read_set_callback( iButtonWorker* worker, iButtonWorkerReadCallback callback, void* context) { - furi_check(worker->mode_index == iButtonWorkerIdle); + furi_check(worker->mode_index == iButtonWorkerModeIdle); worker->read_cb = callback; worker->cb_ctx = context; } @@ -57,7 +47,7 @@ void ibutton_worker_write_set_callback( iButtonWorker* worker, iButtonWorkerWriteCallback callback, void* context) { - furi_check(worker->mode_index == iButtonWorkerIdle); + furi_check(worker->mode_index == iButtonWorkerModeIdle); worker->write_cb = callback; worker->cb_ctx = context; } @@ -66,7 +56,7 @@ void ibutton_worker_emulate_set_callback( iButtonWorker* worker, iButtonWorkerEmulateCallback callback, void* context) { - furi_check(worker->mode_index == iButtonWorkerIdle); + furi_check(worker->mode_index == iButtonWorkerModeIdle); worker->emulate_cb = callback; worker->cb_ctx = context; } @@ -77,8 +67,14 @@ void ibutton_worker_read_start(iButtonWorker* worker, iButtonKey* key) { furi_message_queue_put(worker->messages, &message, FuriWaitForever) == FuriStatusOk); } -void ibutton_worker_write_start(iButtonWorker* worker, iButtonKey* key) { - iButtonMessage message = {.type = iButtonMessageWrite, .data.key = key}; +void ibutton_worker_write_blank_start(iButtonWorker* worker, iButtonKey* key) { + iButtonMessage message = {.type = iButtonMessageWriteBlank, .data.key = key}; + furi_check( + furi_message_queue_put(worker->messages, &message, FuriWaitForever) == FuriStatusOk); +} + +void ibutton_worker_write_copy_start(iButtonWorker* worker, iButtonKey* key) { + iButtonMessage message = {.type = iButtonMessageWriteCopy, .data.key = key}; furi_check( furi_message_queue_put(worker->messages, &message, FuriWaitForever) == FuriStatusOk); } @@ -96,19 +92,8 @@ void ibutton_worker_stop(iButtonWorker* worker) { } void ibutton_worker_free(iButtonWorker* worker) { - ibutton_writer_free(worker->writer); - - onewire_slave_free(worker->slave); - - onewire_host_free(worker->host); - onewire_device_free(worker->device); - - protocol_dict_free(worker->protocols); - furi_message_queue_free(worker->messages); - furi_thread_free(worker->thread); - free(worker->key_data); free(worker); } @@ -137,7 +122,7 @@ void ibutton_worker_notify_emulate(iButtonWorker* worker) { } void ibutton_worker_set_key_p(iButtonWorker* worker, iButtonKey* key) { - worker->key_p = key; + worker->key = key; } static int32_t ibutton_worker_thread(void* thread_context) { @@ -154,25 +139,29 @@ static int32_t ibutton_worker_thread(void* thread_context) { if(status == FuriStatusOk) { switch(message.type) { case iButtonMessageEnd: - ibutton_worker_switch_mode(worker, iButtonWorkerIdle); + ibutton_worker_switch_mode(worker, iButtonWorkerModeIdle); ibutton_worker_set_key_p(worker, NULL); running = false; break; case iButtonMessageStop: - ibutton_worker_switch_mode(worker, iButtonWorkerIdle); + ibutton_worker_switch_mode(worker, iButtonWorkerModeIdle); ibutton_worker_set_key_p(worker, NULL); break; case iButtonMessageRead: ibutton_worker_set_key_p(worker, message.data.key); - ibutton_worker_switch_mode(worker, iButtonWorkerRead); + ibutton_worker_switch_mode(worker, iButtonWorkerModeRead); + break; + case iButtonMessageWriteBlank: + ibutton_worker_set_key_p(worker, message.data.key); + ibutton_worker_switch_mode(worker, iButtonWorkerModeWriteBlank); break; - case iButtonMessageWrite: + case iButtonMessageWriteCopy: ibutton_worker_set_key_p(worker, message.data.key); - ibutton_worker_switch_mode(worker, iButtonWorkerWrite); + ibutton_worker_switch_mode(worker, iButtonWorkerModeWriteCopy); break; case iButtonMessageEmulate: ibutton_worker_set_key_p(worker, message.data.key); - ibutton_worker_switch_mode(worker, iButtonWorkerEmulate); + ibutton_worker_switch_mode(worker, iButtonWorkerModeEmulate); break; case iButtonMessageNotifyEmulate: if(worker->emulate_cb) { diff --git a/lib/one_wire/ibutton/ibutton_worker.h b/lib/one_wire/ibutton/ibutton_worker.h index 5c8b1fc399b..2a12a3194de 100644 --- a/lib/one_wire/ibutton/ibutton_worker.h +++ b/lib/one_wire/ibutton/ibutton_worker.h @@ -5,7 +5,9 @@ */ #pragma once + #include "ibutton_key.h" +#include "ibutton_protocols.h" #ifdef __cplusplus extern "C" { @@ -28,7 +30,7 @@ typedef struct iButtonWorker iButtonWorker; * Allocate ibutton worker * @return iButtonWorker* */ -iButtonWorker* ibutton_worker_alloc(); +iButtonWorker* ibutton_worker_alloc(iButtonProtocols* protocols); /** * Free ibutton worker @@ -78,11 +80,18 @@ void ibutton_worker_write_set_callback( void* context); /** - * Start write mode + * Start write blank mode * @param worker * @param key */ -void ibutton_worker_write_start(iButtonWorker* worker, iButtonKey* key); +void ibutton_worker_write_blank_start(iButtonWorker* worker, iButtonKey* key); + +/** + * Start write copy mode + * @param worker + * @param key + */ +void ibutton_worker_write_copy_start(iButtonWorker* worker, iButtonKey* key); /** * Set "emulate success" callback diff --git a/lib/one_wire/ibutton/ibutton_worker_i.h b/lib/one_wire/ibutton/ibutton_worker_i.h index 2396fbd61c4..5f259a38af3 100644 --- a/lib/one_wire/ibutton/ibutton_worker_i.h +++ b/lib/one_wire/ibutton/ibutton_worker_i.h @@ -5,13 +5,11 @@ */ #pragma once + +#include +#include + #include "ibutton_worker.h" -#include "ibutton_writer.h" -#include "../one_wire_host.h" -#include "../one_wire_slave.h" -#include "../one_wire_device.h" -#include -#include "protocols/ibutton_protocols.h" #ifdef __cplusplus extern "C" { @@ -25,19 +23,16 @@ typedef struct { } iButtonWorkerModeType; typedef enum { - iButtonWorkerIdle = 0, - iButtonWorkerRead = 1, - iButtonWorkerWrite = 2, - iButtonWorkerEmulate = 3, + iButtonWorkerModeIdle, + iButtonWorkerModeRead, + iButtonWorkerModeWriteBlank, + iButtonWorkerModeWriteCopy, + iButtonWorkerModeEmulate, } iButtonWorkerMode; struct iButtonWorker { - iButtonKey* key_p; - uint8_t* key_data; - OneWireHost* host; - OneWireSlave* slave; - OneWireDevice* device; - iButtonWriter* writer; + iButtonKey* key; + iButtonProtocols* protocols; iButtonWorkerMode mode_index; FuriMessageQueue* messages; FuriThread* thread; @@ -45,10 +40,8 @@ struct iButtonWorker { iButtonWorkerReadCallback read_cb; iButtonWorkerWriteCallback write_cb; iButtonWorkerEmulateCallback emulate_cb; - void* cb_ctx; - ProtocolDict* protocols; - iButtonProtocol protocol_to_encode; + void* cb_ctx; }; extern const iButtonWorkerModeType ibutton_worker_modes[]; diff --git a/lib/one_wire/ibutton/ibutton_worker_modes.c b/lib/one_wire/ibutton/ibutton_worker_modes.c index 4f7f0855abd..1b8e0a3b893 100644 --- a/lib/one_wire/ibutton/ibutton_worker_modes.c +++ b/lib/one_wire/ibutton/ibutton_worker_modes.c @@ -1,23 +1,28 @@ -#include -#include #include "ibutton_worker_i.h" -#include "ibutton_key_command.h" -void ibutton_worker_mode_idle_start(iButtonWorker* worker); -void ibutton_worker_mode_idle_tick(iButtonWorker* worker); -void ibutton_worker_mode_idle_stop(iButtonWorker* worker); +#include -void ibutton_worker_mode_emulate_start(iButtonWorker* worker); -void ibutton_worker_mode_emulate_tick(iButtonWorker* worker); -void ibutton_worker_mode_emulate_stop(iButtonWorker* worker); +#include +#include -void ibutton_worker_mode_read_start(iButtonWorker* worker); -void ibutton_worker_mode_read_tick(iButtonWorker* worker); -void ibutton_worker_mode_read_stop(iButtonWorker* worker); +#include "ibutton_protocols.h" -void ibutton_worker_mode_write_start(iButtonWorker* worker); -void ibutton_worker_mode_write_tick(iButtonWorker* worker); -void ibutton_worker_mode_write_stop(iButtonWorker* worker); +static void ibutton_worker_mode_idle_start(iButtonWorker* worker); +static void ibutton_worker_mode_idle_tick(iButtonWorker* worker); +static void ibutton_worker_mode_idle_stop(iButtonWorker* worker); + +static void ibutton_worker_mode_emulate_start(iButtonWorker* worker); +static void ibutton_worker_mode_emulate_tick(iButtonWorker* worker); +static void ibutton_worker_mode_emulate_stop(iButtonWorker* worker); + +static void ibutton_worker_mode_read_start(iButtonWorker* worker); +static void ibutton_worker_mode_read_tick(iButtonWorker* worker); +static void ibutton_worker_mode_read_stop(iButtonWorker* worker); + +static void ibutton_worker_mode_write_common_start(iButtonWorker* worker); +static void ibutton_worker_mode_write_blank_tick(iButtonWorker* worker); +static void ibutton_worker_mode_write_copy_tick(iButtonWorker* worker); +static void ibutton_worker_mode_write_common_stop(iButtonWorker* worker); const iButtonWorkerModeType ibutton_worker_modes[] = { { @@ -34,9 +39,15 @@ const iButtonWorkerModeType ibutton_worker_modes[] = { }, { .quant = 1000, - .start = ibutton_worker_mode_write_start, - .tick = ibutton_worker_mode_write_tick, - .stop = ibutton_worker_mode_write_stop, + .start = ibutton_worker_mode_write_common_start, + .tick = ibutton_worker_mode_write_blank_tick, + .stop = ibutton_worker_mode_write_common_stop, + }, + { + .quant = 1000, + .start = ibutton_worker_mode_write_common_start, + .tick = ibutton_worker_mode_write_copy_tick, + .stop = ibutton_worker_mode_write_common_stop, }, { .quant = 1000, @@ -62,143 +73,18 @@ void ibutton_worker_mode_idle_stop(iButtonWorker* worker) { /*********************** READ ***********************/ -typedef struct { - uint32_t last_dwt_value; - FuriStreamBuffer* stream; -} iButtonReadContext; - -void ibutton_worker_comparator_callback(bool level, void* context) { - iButtonReadContext* read_context = context; - - uint32_t current_dwt_value = DWT->CYCCNT; - - LevelDuration data = - level_duration_make(level, current_dwt_value - read_context->last_dwt_value); - furi_stream_buffer_send(read_context->stream, &data, sizeof(LevelDuration), 0); - - read_context->last_dwt_value = current_dwt_value; -} - -bool ibutton_worker_read_comparator(iButtonWorker* worker) { - bool result = false; - - protocol_dict_decoders_start(worker->protocols); - - furi_hal_rfid_pins_reset(); - // pulldown pull pin, we sense the signal through the analog part of the RFID schematic - furi_hal_rfid_pin_pull_pulldown(); - - iButtonReadContext read_context = { - .last_dwt_value = DWT->CYCCNT, - .stream = furi_stream_buffer_alloc(sizeof(LevelDuration) * 512, 1), - }; - - furi_hal_rfid_comp_set_callback(ibutton_worker_comparator_callback, &read_context); - furi_hal_rfid_comp_start(); - - uint32_t tick_start = furi_get_tick(); - while(true) { - LevelDuration level; - size_t ret = - furi_stream_buffer_receive(read_context.stream, &level, sizeof(LevelDuration), 100); - - if((furi_get_tick() - tick_start) > 100) { - break; - } - - if(ret > 0) { - ProtocolId decoded_index = protocol_dict_decoders_feed( - worker->protocols, - level_duration_get_level(level), - level_duration_get_duration(level)); - - if(decoded_index == PROTOCOL_NO) continue; - - protocol_dict_get_data( - worker->protocols, decoded_index, worker->key_data, ibutton_key_get_max_size()); - - switch(decoded_index) { - case iButtonProtocolCyfral: - furi_check(worker->key_p != NULL); - ibutton_key_set_type(worker->key_p, iButtonKeyCyfral); - ibutton_key_set_data(worker->key_p, worker->key_data, ibutton_key_get_max_size()); - result = true; - break; - case iButtonProtocolMetakom: - furi_check(worker->key_p != NULL); - ibutton_key_set_type(worker->key_p, iButtonKeyMetakom); - ibutton_key_set_data(worker->key_p, worker->key_data, ibutton_key_get_max_size()); - result = true; - break; - default: - break; - } - } - } - - furi_hal_rfid_comp_stop(); - furi_hal_rfid_comp_set_callback(NULL, NULL); - furi_hal_rfid_pins_reset(); - - furi_stream_buffer_free(read_context.stream); - - return result; -} - -bool ibutton_worker_read_dallas(iButtonWorker* worker) { - bool result = false; - onewire_host_start(worker->host); - furi_delay_ms(100); - FURI_CRITICAL_ENTER(); - if(onewire_host_search(worker->host, worker->key_data, NORMAL_SEARCH)) { - onewire_host_reset_search(worker->host); - - // key found, verify - if(onewire_host_reset(worker->host)) { - onewire_host_write(worker->host, DS1990_CMD_READ_ROM); - bool key_valid = true; - for(uint8_t i = 0; i < ibutton_key_get_max_size(); i++) { - if(onewire_host_read(worker->host) != worker->key_data[i]) { - key_valid = false; - break; - } - } - - if(key_valid) { - result = true; - - furi_check(worker->key_p != NULL); - ibutton_key_set_type(worker->key_p, iButtonKeyDS1990); - ibutton_key_set_data(worker->key_p, worker->key_data, ibutton_key_get_max_size()); - } - } - } else { - onewire_host_reset_search(worker->host); - } - onewire_host_stop(worker->host); - FURI_CRITICAL_EXIT(); - return result; -} - void ibutton_worker_mode_read_start(iButtonWorker* worker) { UNUSED(worker); furi_hal_power_enable_otg(); } void ibutton_worker_mode_read_tick(iButtonWorker* worker) { - bool valid = false; - if(ibutton_worker_read_dallas(worker)) { - valid = true; - } else if(ibutton_worker_read_comparator(worker)) { - valid = true; - } - - if(valid) { + if(ibutton_protocols_read(worker->protocols, worker->key)) { if(worker->read_cb != NULL) { worker->read_cb(worker->cb_ctx); } - ibutton_worker_switch_mode(worker, iButtonWorkerIdle); + ibutton_worker_switch_mode(worker, iButtonWorkerModeIdle); } } @@ -208,83 +94,14 @@ void ibutton_worker_mode_read_stop(iButtonWorker* worker) { } /*********************** EMULATE ***********************/ -static void onewire_slave_callback(void* context) { - furi_assert(context); - iButtonWorker* worker = context; - ibutton_worker_notify_emulate(worker); -} - -void ibutton_worker_emulate_dallas_start(iButtonWorker* worker) { - uint8_t* device_id = onewire_device_get_id_p(worker->device); - const uint8_t* key_id = ibutton_key_get_data_p(worker->key_p); - const uint8_t key_size = ibutton_key_get_max_size(); - memcpy(device_id, key_id, key_size); - - onewire_slave_attach(worker->slave, worker->device); - onewire_slave_set_result_callback(worker->slave, onewire_slave_callback, worker); - onewire_slave_start(worker->slave); -} - -void ibutton_worker_emulate_dallas_stop(iButtonWorker* worker) { - onewire_slave_stop(worker->slave); - onewire_slave_detach(worker->slave); -} - -void ibutton_worker_emulate_timer_cb(void* context) { - furi_assert(context); - iButtonWorker* worker = context; - - const LevelDuration level_duration = - protocol_dict_encoder_yield(worker->protocols, worker->protocol_to_encode); - - furi_hal_ibutton_emulate_set_next(level_duration_get_duration(level_duration)); - furi_hal_ibutton_pin_write(level_duration_get_level(level_duration)); -} - -void ibutton_worker_emulate_timer_start(iButtonWorker* worker) { - furi_assert(worker->key_p); - const uint8_t* key_id = ibutton_key_get_data_p(worker->key_p); - const uint8_t key_size = ibutton_key_get_max_size(); - - switch(ibutton_key_get_type(worker->key_p)) { - case iButtonKeyDS1990: - return; - break; - case iButtonKeyCyfral: - worker->protocol_to_encode = iButtonProtocolCyfral; - break; - case iButtonKeyMetakom: - worker->protocol_to_encode = iButtonProtocolMetakom; - break; - } - - protocol_dict_set_data(worker->protocols, worker->protocol_to_encode, key_id, key_size); - protocol_dict_encoder_start(worker->protocols, worker->protocol_to_encode); - - furi_hal_ibutton_pin_configure(); - furi_hal_ibutton_emulate_start(0, ibutton_worker_emulate_timer_cb, worker); -} - -void ibutton_worker_emulate_timer_stop(iButtonWorker* worker) { - UNUSED(worker); - furi_hal_ibutton_emulate_stop(); -} void ibutton_worker_mode_emulate_start(iButtonWorker* worker) { - furi_assert(worker->key_p); + furi_assert(worker->key); furi_hal_rfid_pins_reset(); furi_hal_rfid_pin_pull_pulldown(); - switch(ibutton_key_get_type(worker->key_p)) { - case iButtonKeyDS1990: - ibutton_worker_emulate_dallas_start(worker); - break; - case iButtonKeyCyfral: - case iButtonKeyMetakom: - ibutton_worker_emulate_timer_start(worker); - break; - } + ibutton_protocols_emulate_start(worker->protocols, worker->key); } void ibutton_worker_mode_emulate_tick(iButtonWorker* worker) { @@ -292,56 +109,45 @@ void ibutton_worker_mode_emulate_tick(iButtonWorker* worker) { } void ibutton_worker_mode_emulate_stop(iButtonWorker* worker) { - furi_assert(worker->key_p); + furi_assert(worker->key); - furi_hal_rfid_pins_reset(); + ibutton_protocols_emulate_stop(worker->protocols, worker->key); - switch(ibutton_key_get_type(worker->key_p)) { - case iButtonKeyDS1990: - ibutton_worker_emulate_dallas_stop(worker); - break; - case iButtonKeyCyfral: - case iButtonKeyMetakom: - ibutton_worker_emulate_timer_stop(worker); - break; - } + furi_hal_rfid_pins_reset(); } /*********************** WRITE ***********************/ -void ibutton_worker_mode_write_start(iButtonWorker* worker) { +void ibutton_worker_mode_write_common_start(iButtonWorker* worker) { //-V524 + UNUSED(worker); furi_hal_power_enable_otg(); - onewire_host_start(worker->host); } -void ibutton_worker_mode_write_tick(iButtonWorker* worker) { - furi_check(worker->key_p != NULL); - iButtonWriterResult writer_result = ibutton_writer_write(worker->writer, worker->key_p); - iButtonWorkerWriteResult result; - switch(writer_result) { - case iButtonWriterOK: - result = iButtonWorkerWriteOK; - break; - case iButtonWriterSameKey: - result = iButtonWorkerWriteSameKey; - break; - case iButtonWriterNoDetect: - result = iButtonWorkerWriteNoDetect; - break; - case iButtonWriterCannotWrite: - result = iButtonWorkerWriteCannotWrite; - break; - default: - result = iButtonWorkerWriteNoDetect; - break; +void ibutton_worker_mode_write_blank_tick(iButtonWorker* worker) { + furi_assert(worker->key); + + const bool success = ibutton_protocols_write_blank(worker->protocols, worker->key); + // TODO: pass a proper result to the callback + const iButtonWorkerWriteResult result = success ? iButtonWorkerWriteOK : + iButtonWorkerWriteNoDetect; + if(worker->write_cb != NULL) { + worker->write_cb(worker->cb_ctx, result); } +} +void ibutton_worker_mode_write_copy_tick(iButtonWorker* worker) { + furi_assert(worker->key); + + const bool success = ibutton_protocols_write_copy(worker->protocols, worker->key); + // TODO: pass a proper result to the callback + const iButtonWorkerWriteResult result = success ? iButtonWorkerWriteOK : + iButtonWorkerWriteNoDetect; if(worker->write_cb != NULL) { worker->write_cb(worker->cb_ctx, result); } } -void ibutton_worker_mode_write_stop(iButtonWorker* worker) { +void ibutton_worker_mode_write_common_stop(iButtonWorker* worker) { //-V524 + UNUSED(worker); furi_hal_power_disable_otg(); - onewire_host_stop(worker->host); } diff --git a/lib/one_wire/ibutton/ibutton_writer.c b/lib/one_wire/ibutton/ibutton_writer.c deleted file mode 100644 index 84d122491a4..00000000000 --- a/lib/one_wire/ibutton/ibutton_writer.c +++ /dev/null @@ -1,298 +0,0 @@ -#include -#include -#include "ibutton_writer.h" -#include "ibutton_key_command.h" - -/*********************** PRIVATE ***********************/ - -struct iButtonWriter { - OneWireHost* host; -}; - -static void writer_write_one_bit(iButtonWriter* writer, bool value, uint32_t delay) { - onewire_host_write_bit(writer->host, value); - furi_delay_us(delay); -} - -static void writer_write_byte_ds1990(iButtonWriter* writer, uint8_t data) { - for(uint8_t n_bit = 0; n_bit < 8; n_bit++) { - onewire_host_write_bit(writer->host, data & 1); - furi_delay_us(5000); - data = data >> 1; - } -} - -static bool writer_compare_key_ds1990(iButtonWriter* writer, iButtonKey* key) { - bool result = false; - - if(ibutton_key_get_type(key) == iButtonKeyDS1990) { - FURI_CRITICAL_ENTER(); - bool presence = onewire_host_reset(writer->host); - - if(presence) { - onewire_host_write(writer->host, DS1990_CMD_READ_ROM); - - result = true; - for(uint8_t i = 0; i < ibutton_key_get_data_size(key); i++) { - if(ibutton_key_get_data_p(key)[i] != onewire_host_read(writer->host)) { - result = false; - break; - } - } - } - - FURI_CRITICAL_EXIT(); - } - - return result; -} - -static bool writer_write_TM2004(iButtonWriter* writer, iButtonKey* key) { - uint8_t answer; - bool result = true; - - if(ibutton_key_get_type(key) == iButtonKeyDS1990) { - FURI_CRITICAL_ENTER(); - - // write rom, addr is 0x0000 - onewire_host_reset(writer->host); - onewire_host_write(writer->host, TM2004_CMD_WRITE_ROM); - onewire_host_write(writer->host, 0x00); - onewire_host_write(writer->host, 0x00); - - // write key - for(uint8_t i = 0; i < ibutton_key_get_data_size(key); i++) { - // write key byte - onewire_host_write(writer->host, ibutton_key_get_data_p(key)[i]); - answer = onewire_host_read(writer->host); - // TODO: check answer CRC - - // pulse indicating that data is correct - furi_delay_us(600); - writer_write_one_bit(writer, 1, 50000); - - // read written key byte - answer = onewire_host_read(writer->host); //-V519 - - // check that written and read are same - if(ibutton_key_get_data_p(key)[i] != answer) { - result = false; - break; - } - } - - if(!writer_compare_key_ds1990(writer, key)) { - result = false; - } - - onewire_host_reset(writer->host); - - FURI_CRITICAL_EXIT(); - } else { - result = false; - } - - return result; -} - -static bool writer_write_1990_1(iButtonWriter* writer, iButtonKey* key) { - bool result = false; - - if(ibutton_key_get_type(key) == iButtonKeyDS1990) { - FURI_CRITICAL_ENTER(); - - // unlock - onewire_host_reset(writer->host); - onewire_host_write(writer->host, RW1990_1_CMD_WRITE_RECORD_FLAG); - furi_delay_us(10); - writer_write_one_bit(writer, 0, 5000); - - // write key - onewire_host_reset(writer->host); - onewire_host_write(writer->host, RW1990_1_CMD_WRITE_ROM); - for(uint8_t i = 0; i < ibutton_key_get_data_size(key); i++) { - // inverted key for RW1990.1 - writer_write_byte_ds1990(writer, ~ibutton_key_get_data_p(key)[i]); - furi_delay_us(30000); - } - - // lock - onewire_host_write(writer->host, RW1990_1_CMD_WRITE_RECORD_FLAG); - writer_write_one_bit(writer, 1, 10000); - - FURI_CRITICAL_EXIT(); - - if(writer_compare_key_ds1990(writer, key)) { - result = true; - } - } - - return result; -} - -static bool writer_write_1990_2(iButtonWriter* writer, iButtonKey* key) { - bool result = false; - - if(ibutton_key_get_type(key) == iButtonKeyDS1990) { - FURI_CRITICAL_ENTER(); - - // unlock - onewire_host_reset(writer->host); - onewire_host_write(writer->host, RW1990_2_CMD_WRITE_RECORD_FLAG); - furi_delay_us(10); - writer_write_one_bit(writer, 1, 5000); - - // write key - onewire_host_reset(writer->host); - onewire_host_write(writer->host, RW1990_2_CMD_WRITE_ROM); - for(uint8_t i = 0; i < ibutton_key_get_data_size(key); i++) { - writer_write_byte_ds1990(writer, ibutton_key_get_data_p(key)[i]); - furi_delay_us(30000); - } - - // lock - onewire_host_write(writer->host, RW1990_2_CMD_WRITE_RECORD_FLAG); - writer_write_one_bit(writer, 0, 10000); - - FURI_CRITICAL_EXIT(); - - if(writer_compare_key_ds1990(writer, key)) { - result = true; - } - } - - return result; -} - -/* -// TODO: adapt and test -static bool writer_write_TM01( - iButtonWriter* writer, - iButtonKey type, - const uint8_t* key, - uint8_t key_length) { - bool result = true; - - { - // TODO test and encoding - FURI_CRITICAL_ENTER(); - - // unlock - onewire_host_reset(writer->host); - onewire_host_write(writer->host, TM01::CMD_WRITE_RECORD_FLAG); - onewire_write_one_bit(1, 10000); - - // write key - onewire_host_reset(writer->host); - onewire_host_write(writer->host, TM01::CMD_WRITE_ROM); - - // TODO: key types - //if(type == KEY_METAKOM || type == KEY_CYFRAL) { - //} else { - for(uint8_t i = 0; i < key->get_type_data_size(); i++) { - write_byte_ds1990(key->get_data()[i]); - furi_delay_us(10000); - } - //} - - // lock - onewire_host_write(writer->host, TM01::CMD_WRITE_RECORD_FLAG); - onewire_write_one_bit(0, 10000); - - FURI_CRITICAL_EXIT(); - } - - if(!compare_key_ds1990(key)) { - result = false; - } - - { - FURI_CRITICAL_ENTER(); - - if(key->get_key_type() == iButtonKeyType::KeyMetakom || - key->get_key_type() == iButtonKeyType::KeyCyfral) { - onewire_host_reset(writer->host); - if(key->get_key_type() == iButtonKeyType::KeyCyfral) - onewire_host_write(writer->host, TM01::CMD_SWITCH_TO_CYFRAL); - else - onewire_host_write(writer->host, TM01::CMD_SWITCH_TO_METAKOM); - onewire_write_one_bit(1); - } - - FURI_CRITICAL_EXIT(); - } - - return result; -} -*/ - -static iButtonWriterResult writer_write_DS1990(iButtonWriter* writer, iButtonKey* key) { - iButtonWriterResult result = iButtonWriterNoDetect; - bool same_key = writer_compare_key_ds1990(writer, key); - - if(!same_key) { - // currently we can write: - // RW1990_1, TM08v2, TM08vi-2 by write_1990_1() - // RW1990_2 by write_1990_2() - // RW2004, RW2004, TM2004 with EEPROM by write_TM2004(); - - bool write_result = true; - do { - if(writer_write_1990_1(writer, key)) break; - if(writer_write_1990_2(writer, key)) break; - if(writer_write_TM2004(writer, key)) break; - write_result = false; - } while(false); - - if(write_result) { - result = iButtonWriterOK; - } else { - result = iButtonWriterCannotWrite; - } - } else { - result = iButtonWriterSameKey; - } - - return result; -} - -/*********************** PUBLIC ***********************/ - -iButtonWriter* ibutton_writer_alloc(OneWireHost* host) { - iButtonWriter* writer = malloc(sizeof(iButtonWriter)); - writer->host = host; - return writer; -} - -void ibutton_writer_free(iButtonWriter* writer) { - free(writer); -} - -iButtonWriterResult ibutton_writer_write(iButtonWriter* writer, iButtonKey* key) { - iButtonWriterResult result = iButtonWriterNoDetect; - - furi_kernel_lock(); - bool blank_present = onewire_host_reset(writer->host); - furi_kernel_unlock(); - - if(blank_present) { - switch(ibutton_key_get_type(key)) { - case iButtonKeyDS1990: - result = writer_write_DS1990(writer, key); - default: - break; - } - } - - return result; -} - -void ibutton_writer_start(iButtonWriter* writer) { - furi_hal_power_enable_otg(); - onewire_host_start(writer->host); -} - -void ibutton_writer_stop(iButtonWriter* writer) { - onewire_host_stop(writer->host); - furi_hal_power_disable_otg(); -} diff --git a/lib/one_wire/ibutton/ibutton_writer.h b/lib/one_wire/ibutton/ibutton_writer.h deleted file mode 100644 index 6dbdbb27a10..00000000000 --- a/lib/one_wire/ibutton/ibutton_writer.h +++ /dev/null @@ -1,60 +0,0 @@ -/** - * @file ibutton_writer.h - * - * iButton blanks writer - */ - -#pragma once -#include -#include "ibutton_key.h" -#include "../one_wire_host.h" - -#ifdef __cplusplus -extern "C" { -#endif - -typedef enum { - iButtonWriterOK, - iButtonWriterSameKey, - iButtonWriterNoDetect, - iButtonWriterCannotWrite, -} iButtonWriterResult; - -typedef struct iButtonWriter iButtonWriter; - -/** - * Allocate writer - * @param host - * @return iButtonWriter* - */ -iButtonWriter* ibutton_writer_alloc(OneWireHost* host); - -/** - * Deallocate writer - * @param writer - */ -void ibutton_writer_free(iButtonWriter* writer); - -/** - * Write key to blank - * @param writer - * @param key - * @return iButtonWriterResult - */ -iButtonWriterResult ibutton_writer_write(iButtonWriter* writer, iButtonKey* key); - -/** - * Start writing. Must be called before write attempt - * @param writer - */ -void ibutton_writer_start(iButtonWriter* writer); - -/** - * Stop writing - * @param writer - */ -void ibutton_writer_stop(iButtonWriter* writer); - -#ifdef __cplusplus -} -#endif diff --git a/lib/one_wire/ibutton/protocols/blanks/rw1990.c b/lib/one_wire/ibutton/protocols/blanks/rw1990.c new file mode 100644 index 00000000000..d3350fcf00a --- /dev/null +++ b/lib/one_wire/ibutton/protocols/blanks/rw1990.c @@ -0,0 +1,95 @@ +#include "rw1990.h" + +#include + +#define RW1990_1_CMD_WRITE_RECORD_FLAG 0xD1 +#define RW1990_1_CMD_READ_RECORD_FLAG 0xB5 +#define RW1990_1_CMD_WRITE_ROM 0xD5 + +#define RW1990_2_CMD_WRITE_RECORD_FLAG 0x1D +#define RW1990_2_CMD_READ_RECORD_FLAG 0x1E +#define RW1990_2_CMD_WRITE_ROM 0xD5 + +#define DS1990_CMD_READ_ROM 0x33 + +static void rw1990_write_byte(OneWireHost* host, uint8_t value) { + for(uint8_t bitMask = 0x01; bitMask; bitMask <<= 1) { + onewire_host_write_bit(host, (bool)(bitMask & value)); + furi_delay_us(5000); + } +} + +static bool rw1990_read_and_compare(OneWireHost* host, const uint8_t* data, size_t data_size) { + bool success = false; + + if(onewire_host_reset(host)) { + success = true; + onewire_host_write(host, DS1990_CMD_READ_ROM); + + for(size_t i = 0; i < data_size; ++i) { + if(data[i] != onewire_host_read(host)) { + success = false; + break; + } + } + } + + return success; +} + +bool rw1990_write_v1(OneWireHost* host, const uint8_t* data, size_t data_size) { + // Unlock sequence + onewire_host_reset(host); + onewire_host_write(host, RW1990_1_CMD_WRITE_RECORD_FLAG); + furi_delay_us(10); + + onewire_host_write_bit(host, false); + furi_delay_us(5000); + + // Write data + onewire_host_reset(host); + onewire_host_write(host, RW1990_1_CMD_WRITE_ROM); + + for(size_t i = 0; i < data_size; ++i) { + // inverted key for RW1990.1 + rw1990_write_byte(host, ~(data[i])); + furi_delay_us(30000); + } + + // Lock sequence + onewire_host_write(host, RW1990_1_CMD_WRITE_RECORD_FLAG); + + onewire_host_write_bit(host, true); + furi_delay_us(10000); + + // TODO: Better error handling + return rw1990_read_and_compare(host, data, data_size); +} + +bool rw1990_write_v2(OneWireHost* host, const uint8_t* data, size_t data_size) { + // Unlock sequence + onewire_host_reset(host); + onewire_host_write(host, RW1990_2_CMD_WRITE_RECORD_FLAG); + furi_delay_us(10); + + onewire_host_write_bit(host, true); + furi_delay_us(5000); + + // Write data + onewire_host_reset(host); + onewire_host_write(host, RW1990_2_CMD_WRITE_ROM); + + for(size_t i = 0; i < data_size; ++i) { + rw1990_write_byte(host, data[i]); + furi_delay_us(30000); + } + + // Lock sequence + onewire_host_write(host, RW1990_2_CMD_WRITE_RECORD_FLAG); + + onewire_host_write_bit(host, false); + furi_delay_us(10000); + + // TODO: Better error handling + return rw1990_read_and_compare(host, data, data_size); +} diff --git a/lib/one_wire/ibutton/protocols/blanks/rw1990.h b/lib/one_wire/ibutton/protocols/blanks/rw1990.h new file mode 100644 index 00000000000..bdd27f6bfbc --- /dev/null +++ b/lib/one_wire/ibutton/protocols/blanks/rw1990.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +#include + +bool rw1990_write_v1(OneWireHost* host, const uint8_t* data, size_t data_size); + +bool rw1990_write_v2(OneWireHost* host, const uint8_t* data, size_t data_size); diff --git a/lib/one_wire/ibutton/protocols/blanks/tm2004.c b/lib/one_wire/ibutton/protocols/blanks/tm2004.c new file mode 100644 index 00000000000..ef6f0619e12 --- /dev/null +++ b/lib/one_wire/ibutton/protocols/blanks/tm2004.c @@ -0,0 +1,42 @@ +#include "tm2004.h" + +#include + +#define TM2004_CMD_READ_STATUS 0xAA +#define TM2004_CMD_READ_MEMORY 0xF0 +#define TM2004_CMD_WRITE_ROM 0x3C +#define TM2004_CMD_FINALIZATION 0x35 +#define TM2004_ANSWER_READ_MEMORY 0xF5 + +bool tm2004_write(OneWireHost* host, const uint8_t* data, size_t data_size) { + onewire_host_reset(host); + onewire_host_write(host, TM2004_CMD_WRITE_ROM); + // Starting writing from address 0x0000 + onewire_host_write(host, 0x00); + onewire_host_write(host, 0x00); + + size_t i; + for(i = 0; i < data_size; ++i) { + uint8_t answer; + + onewire_host_write(host, data[i]); + answer = onewire_host_read(host); + // TODO: check answer CRC + + // pulse indicating that data is correct + furi_delay_us(600); + onewire_host_write_bit(host, true); + furi_delay_us(50000); + + // read written key byte + answer = onewire_host_read(host); //-V519 + + // check that written and read are same + if(data[i] != answer) { + break; + } + } + + // TODO: Better error handling + return i == data_size; +} diff --git a/lib/one_wire/ibutton/protocols/blanks/tm2004.h b/lib/one_wire/ibutton/protocols/blanks/tm2004.h new file mode 100644 index 00000000000..15713af565f --- /dev/null +++ b/lib/one_wire/ibutton/protocols/blanks/tm2004.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +#include + +bool tm2004_write(OneWireHost* host, const uint8_t* data, size_t data_size); diff --git a/lib/one_wire/ibutton/protocols/dallas/dallas_common.c b/lib/one_wire/ibutton/protocols/dallas/dallas_common.c new file mode 100644 index 00000000000..57a873b1d16 --- /dev/null +++ b/lib/one_wire/ibutton/protocols/dallas/dallas_common.c @@ -0,0 +1,250 @@ +#include "dallas_common.h" + +#include +#include + +#define BITS_IN_BYTE 8U + +#define DALLAS_COMMON_ROM_DATA_KEY_V1 "Data" +#define DALLAS_COMMON_ROM_DATA_KEY_V2 "Rom Data" + +#define DALLAS_COMMON_COPY_SCRATCH_MIN_TIMEOUT_US 5U +#define DALLAS_COMMON_COPY_SCRATCH_POLL_COUNT 20U + +#define DALLAS_COMMON_END_ADDRESS_MASK 0x01F +#define DALLAS_COMMON_STATUS_FLAG_PF (1U << 5) +#define DALLAS_COMMON_STATUS_FLAG_OF (1U << 6) +#define DALLAS_COMMON_STATUS_FLAG_AA (1U << 7) + +#define DALLAS_COMMON_BRIEF_HEAD_COUNT 4U +#define DALLAS_COMMON_BRIEF_TAIL_COUNT 3U + +#define BITS_IN_BYTE 8U +#define BITS_IN_KBIT 1024U + +bool dallas_common_skip_rom(OneWireHost* host) { + onewire_host_write(host, DALLAS_COMMON_CMD_SKIP_ROM); + return true; +} + +bool dallas_common_read_rom(OneWireHost* host, DallasCommonRomData* rom_data) { + onewire_host_write(host, DALLAS_COMMON_CMD_READ_ROM); + onewire_host_read_bytes(host, rom_data->bytes, sizeof(DallasCommonRomData)); + + return dallas_common_is_valid_crc(rom_data); +} + +bool dallas_common_write_scratchpad( + OneWireHost* host, + uint16_t address, + const uint8_t* data, + size_t data_size) { + onewire_host_write(host, DALLAS_COMMON_CMD_WRITE_SCRATCH); + onewire_host_write(host, (uint8_t)address); + onewire_host_write(host, (uint8_t)(address >> BITS_IN_BYTE)); + + onewire_host_write_bytes(host, data, data_size); + + return true; +} + +bool dallas_common_read_scratchpad( + OneWireHost* host, + DallasCommonAddressRegs* regs, + uint8_t* data, + size_t data_size) { + onewire_host_write(host, DALLAS_COMMON_CMD_READ_SCRATCH); + onewire_host_read_bytes(host, regs->bytes, sizeof(DallasCommonAddressRegs)); + onewire_host_read_bytes(host, data, data_size); + + return true; +} + +bool dallas_common_copy_scratchpad( + OneWireHost* host, + const DallasCommonAddressRegs* regs, + uint32_t timeout_us) { + onewire_host_write(host, DALLAS_COMMON_CMD_COPY_SCRATCH); + onewire_host_write_bytes(host, regs->bytes, sizeof(DallasCommonAddressRegs)); + + const uint32_t poll_delay = + MAX(timeout_us / DALLAS_COMMON_COPY_SCRATCH_POLL_COUNT, + DALLAS_COMMON_COPY_SCRATCH_MIN_TIMEOUT_US); + + uint32_t time_elapsed; + for(time_elapsed = 0; time_elapsed < timeout_us; time_elapsed += poll_delay) { + if(!onewire_host_read_bit(host)) break; + furi_delay_us(poll_delay); + } + + return time_elapsed < timeout_us; +} + +bool dallas_common_read_mem(OneWireHost* host, uint16_t address, uint8_t* data, size_t data_size) { + onewire_host_write(host, DALLAS_COMMON_CMD_READ_MEM); + + onewire_host_write(host, (uint8_t)address); + onewire_host_write(host, (uint8_t)(address > BITS_IN_BYTE)); + + onewire_host_read_bytes(host, data, (uint16_t)data_size); + + return true; +} + +bool dallas_common_write_mem( + OneWireHost* host, + uint32_t timeout_us, + size_t page_size, + const uint8_t* data, + size_t data_size) { + // Data size must be a multiple of page size + furi_check(data_size % page_size == 0); + + DallasCommonAddressRegs regs; + uint8_t* scratch = malloc(page_size); + + size_t i; + for(i = 0; i < data_size; i += page_size) { + const uint8_t* data_ptr = data + i; + + // Write scratchpad with the next page value + if(!onewire_host_reset(host)) break; + if(!dallas_common_skip_rom(host)) break; + if(!dallas_common_write_scratchpad(host, i, data_ptr, page_size)) break; + + // Read back the scratchpad contents and address registers + if(!onewire_host_reset(host)) break; + if(!dallas_common_skip_rom(host)) break; + if(!dallas_common_read_scratchpad(host, ®s, scratch, page_size)) break; + + // Verify scratchpad contents + if(memcmp(data_ptr, scratch, page_size) != 0) break; + + // Write scratchpad to internal memory + if(!onewire_host_reset(host)) break; + if(!dallas_common_skip_rom(host)) break; + if(!dallas_common_copy_scratchpad(host, ®s, timeout_us)) break; + + // Read back the address registers again + if(!onewire_host_reset(host)) break; + if(!dallas_common_skip_rom(host)) break; + if(!dallas_common_read_scratchpad(host, ®s, scratch, 0)) break; + + // Check if AA flag is set + if(!(regs.fields.status & DALLAS_COMMON_STATUS_FLAG_AA)) break; + } + + free(scratch); + + return i == data_size; +} + +bool dallas_common_emulate_search_rom(OneWireSlave* bus, const DallasCommonRomData* rom_data) { + for(size_t i = 0; i < sizeof(DallasCommonRomData); i++) { + for(size_t j = 0; j < BITS_IN_BYTE; j++) { + bool bit = (rom_data->bytes[i] >> j) & 0x01; + + if(!onewire_slave_send_bit(bus, bit)) return false; + if(!onewire_slave_send_bit(bus, !bit)) return false; + + onewire_slave_receive_bit(bus); + // TODO: check for errors and return if any + } + } + + return true; +} + +bool dallas_common_emulate_read_rom(OneWireSlave* bus, const DallasCommonRomData* rom_data) { + return onewire_slave_send(bus, rom_data->bytes, sizeof(DallasCommonRomData)); +} + +bool dallas_common_emulate_read_mem(OneWireSlave* bus, const uint8_t* data, size_t data_size) { + bool success = false; + + union { + uint8_t bytes[sizeof(uint16_t)]; + uint16_t word; + } address; + + do { + if(!onewire_slave_receive(bus, address.bytes, sizeof(address))) break; + if(address.word >= data_size) break; + if(!onewire_slave_send(bus, data + address.word, data_size - address.word)) break; + + success = true; + } while(false); + + return success; +} + +bool dallas_common_save_rom_data(FlipperFormat* ff, const DallasCommonRomData* rom_data) { + return flipper_format_write_hex( + ff, DALLAS_COMMON_ROM_DATA_KEY_V2, rom_data->bytes, sizeof(DallasCommonRomData)); +} + +bool dallas_common_load_rom_data( + FlipperFormat* ff, + uint32_t format_version, + DallasCommonRomData* rom_data) { + switch(format_version) { + case 1: + return flipper_format_read_hex( + ff, DALLAS_COMMON_ROM_DATA_KEY_V1, rom_data->bytes, sizeof(DallasCommonRomData)); + case 2: + return flipper_format_read_hex( + ff, DALLAS_COMMON_ROM_DATA_KEY_V2, rom_data->bytes, sizeof(DallasCommonRomData)); + default: + return false; + } +} + +bool dallas_common_is_valid_crc(const DallasCommonRomData* rom_data) { + const uint8_t crc_calculated = + maxim_crc8(rom_data->bytes, sizeof(DallasCommonRomData) - 1, MAXIM_CRC8_INIT); + const uint8_t crc_received = rom_data->fields.checksum; + + return crc_calculated == crc_received; +} + +void dallas_common_render_brief_data( + FuriString* result, + const DallasCommonRomData* rom_data, + const uint8_t* sram_data, + size_t sram_data_size) { + for(size_t i = 0; i < sizeof(rom_data->bytes); ++i) { + furi_string_cat_printf(result, "%02X ", rom_data->bytes[i]); + } + + furi_string_cat_printf( + result, + "\nInternal SRAM: %zu Kbit\n", + (size_t)(sram_data_size * BITS_IN_BYTE / BITS_IN_KBIT)); + + for(size_t i = 0; i < DALLAS_COMMON_BRIEF_HEAD_COUNT; ++i) { + furi_string_cat_printf(result, "%02X ", sram_data[i]); + } + + furi_string_cat_printf(result, "[ . . . ]"); + + for(size_t i = sram_data_size - DALLAS_COMMON_BRIEF_TAIL_COUNT; i < sram_data_size; ++i) { + furi_string_cat_printf(result, " %02X", sram_data[i]); + } +} + +void dallas_common_render_crc_error(FuriString* result, const DallasCommonRomData* rom_data) { + furi_string_set(result, "CRC Error\n"); + + const size_t data_size = sizeof(DallasCommonRomData); + + for(size_t i = 0; i < data_size; ++i) { + furi_string_cat_printf(result, (i < data_size - 1) ? "%02X " : "%02X", rom_data->bytes[i]); + } +} + +void dallas_common_apply_edits(DallasCommonRomData* rom_data, uint8_t family_code) { + rom_data->fields.family_code = family_code; + const uint8_t crc = + maxim_crc8(rom_data->bytes, sizeof(DallasCommonRomData) - 1, MAXIM_CRC8_INIT); + rom_data->fields.checksum = crc; +} diff --git a/lib/one_wire/ibutton/protocols/dallas/dallas_common.h b/lib/one_wire/ibutton/protocols/dallas/dallas_common.h new file mode 100644 index 00000000000..7ad13eb2cb0 --- /dev/null +++ b/lib/one_wire/ibutton/protocols/dallas/dallas_common.h @@ -0,0 +1,107 @@ +#pragma once + +#include + +#include +#include + +#define DALLAS_COMMON_MANUFACTURER_NAME "Dallas" + +#define DALLAS_COMMON_CMD_READ_ROM 0x33U +#define DALLAS_COMMON_CMD_MATCH_ROM 0x55U +#define DALLAS_COMMON_CMD_SKIP_ROM 0xCCU +#define DALLAS_COMMON_CMD_COND_SEARCH 0xECU +#define DALLAS_COMMON_CMD_SEARCH_ROM 0xF0U + +#define DALLAS_COMMON_CMD_READ_SCRATCH 0xAAU +#define DALLAS_COMMON_CMD_WRITE_SCRATCH 0x0FU +#define DALLAS_COMMON_CMD_COPY_SCRATCH 0x55U + +#define DALLAS_COMMON_CMD_READ_MEM 0xF0U + +#define DALLAS_COMMON_CMD_OVERDRIVE_SKIP_ROM 0x3CU +#define DALLAS_COMMON_CMD_OVERDRIVE_MATCH_ROM 0x69U + +typedef enum { + DallasCommonCommandStateIdle, + DallasCommonCommandStateRomCmd, + DallasCommonCommandStateMemCmd, +} DallasCommonCommandState; + +typedef union { + struct { + uint8_t family_code; + uint8_t serial_number[6]; + uint8_t checksum; + } fields; + uint8_t bytes[8]; +} DallasCommonRomData; + +typedef union { + struct { + uint8_t address_lo; + uint8_t address_hi; + uint8_t status; + } fields; + uint8_t bytes[3]; +} DallasCommonAddressRegs; + +/* Standard(ish) iButton commands */ +bool dallas_common_skip_rom(OneWireHost* host); + +bool dallas_common_read_rom(OneWireHost* host, DallasCommonRomData* rom_data); + +bool dallas_common_write_scratchpad( + OneWireHost* host, + uint16_t address, + const uint8_t* data, + size_t data_size); + +bool dallas_common_read_scratchpad( + OneWireHost* host, + DallasCommonAddressRegs* regs, + uint8_t* data, + size_t data_size); + +bool dallas_common_copy_scratchpad( + OneWireHost* host, + const DallasCommonAddressRegs* regs, + uint32_t timeout_us); + +bool dallas_common_read_mem(OneWireHost* host, uint16_t address, uint8_t* data, size_t data_size); + +/* Combined operations */ +bool dallas_common_write_mem( + OneWireHost* host, + uint32_t timeout_us, + size_t page_size, + const uint8_t* data, + size_t data_size); + +/* Emulation */ +bool dallas_common_emulate_search_rom(OneWireSlave* bus, const DallasCommonRomData* rom_data); + +bool dallas_common_emulate_read_rom(OneWireSlave* bus, const DallasCommonRomData* rom_data); + +bool dallas_common_emulate_read_mem(OneWireSlave* bus, const uint8_t* data, size_t data_size); + +/* Save & Load */ +bool dallas_common_save_rom_data(FlipperFormat* ff, const DallasCommonRomData* rom_data); + +bool dallas_common_load_rom_data( + FlipperFormat* ff, + uint32_t format_version, + DallasCommonRomData* rom_data); + +/* Miscellaneous */ +bool dallas_common_is_valid_crc(const DallasCommonRomData* rom_data); + +void dallas_common_render_brief_data( + FuriString* result, + const DallasCommonRomData* rom_data, + const uint8_t* sram_data, + size_t sram_data_size); + +void dallas_common_render_crc_error(FuriString* result, const DallasCommonRomData* rom_data); + +void dallas_common_apply_edits(DallasCommonRomData* rom_data, uint8_t family_code); diff --git a/lib/one_wire/ibutton/protocols/dallas/protocol_dallas_base.h b/lib/one_wire/ibutton/protocols/dallas/protocol_dallas_base.h new file mode 100644 index 00000000000..b4edb2b20da --- /dev/null +++ b/lib/one_wire/ibutton/protocols/dallas/protocol_dallas_base.h @@ -0,0 +1,39 @@ +#pragma once + +#include "../protocol_common_i.h" + +#include + +#include +#include + +typedef bool (*iButtonProtocolDallasReadWriteFunc)(OneWireHost*, iButtonProtocolData*); +typedef void (*iButtonProtocolDallasEmulateFunc)(OneWireSlave*, iButtonProtocolData*); +typedef bool (*iButtonProtocolDallasSaveFunc)(FlipperFormat*, const iButtonProtocolData*); +typedef bool (*iButtonProtocolDallasLoadFunc)(FlipperFormat*, uint32_t, iButtonProtocolData*); +typedef void (*iButtonProtocolDallasRenderDataFunc)(FuriString*, const iButtonProtocolData*); +typedef bool (*iButtonProtocolDallasIsValidFunc)(const iButtonProtocolData*); +typedef void ( + *iButtonProtocolDallasGetEditableDataFunc)(iButtonEditableData*, iButtonProtocolData*); +typedef void (*iButtonProtocolDallasApplyEditsFunc)(iButtonProtocolData*); + +typedef struct { + const uint8_t family_code; + const uint32_t features; + const size_t data_size; + const char* manufacturer; + const char* name; + + iButtonProtocolDallasReadWriteFunc read; + iButtonProtocolDallasReadWriteFunc write_blank; + iButtonProtocolDallasReadWriteFunc write_copy; + iButtonProtocolDallasEmulateFunc emulate; + iButtonProtocolDallasSaveFunc save; + iButtonProtocolDallasLoadFunc load; + iButtonProtocolDallasRenderDataFunc render_data; + iButtonProtocolDallasRenderDataFunc render_brief_data; + iButtonProtocolDallasRenderDataFunc render_error; + iButtonProtocolDallasIsValidFunc is_valid; + iButtonProtocolDallasGetEditableDataFunc get_editable_data; + iButtonProtocolDallasApplyEditsFunc apply_edits; +} iButtonProtocolDallasBase; diff --git a/lib/one_wire/ibutton/protocols/dallas/protocol_ds1990.c b/lib/one_wire/ibutton/protocols/dallas/protocol_ds1990.c new file mode 100644 index 00000000000..0d9c937ee4d --- /dev/null +++ b/lib/one_wire/ibutton/protocols/dallas/protocol_ds1990.c @@ -0,0 +1,144 @@ +#include "protocol_ds1990.h" + +#include +#include + +#include "dallas_common.h" + +#include "../blanks/rw1990.h" +#include "../blanks/tm2004.h" + +#define DS1990_FAMILY_CODE 0x01U +#define DS1990_FAMILY_NAME "DS1990" + +#define DS1990_CMD_READ_ROM 0x0FU + +typedef struct { + OneWireSlave* bus; +} DS1990ProtocolState; + +typedef struct { + DallasCommonRomData rom_data; + DS1990ProtocolState state; +} DS1990ProtocolData; + +static bool dallas_ds1990_read(OneWireHost*, iButtonProtocolData*); +static bool dallas_ds1990_write_blank(OneWireHost*, iButtonProtocolData*); +static void dallas_ds1990_emulate(OneWireSlave*, iButtonProtocolData*); +static bool dallas_ds1990_load(FlipperFormat*, uint32_t, iButtonProtocolData*); +static bool dallas_ds1990_save(FlipperFormat*, const iButtonProtocolData*); +static void dallas_ds1990_render_brief_data(FuriString*, const iButtonProtocolData*); +static void dallas_ds1990_render_error(FuriString*, const iButtonProtocolData*); +static bool dallas_ds1990_is_data_valid(const iButtonProtocolData*); +static void dallas_ds1990_get_editable_data(iButtonEditableData*, iButtonProtocolData*); +static void dallas_ds1990_apply_edits(iButtonProtocolData*); + +const iButtonProtocolDallasBase ibutton_protocol_ds1990 = { + .family_code = DS1990_FAMILY_CODE, + .features = iButtonProtocolFeatureWriteBlank, + .data_size = sizeof(DS1990ProtocolData), + .manufacturer = DALLAS_COMMON_MANUFACTURER_NAME, + .name = DS1990_FAMILY_NAME, + + .read = dallas_ds1990_read, + .write_blank = dallas_ds1990_write_blank, + .write_copy = NULL, /* No data to write a copy */ + .emulate = dallas_ds1990_emulate, + .save = dallas_ds1990_save, + .load = dallas_ds1990_load, + .render_data = NULL, /* No data to render */ + .render_brief_data = dallas_ds1990_render_brief_data, + .render_error = dallas_ds1990_render_error, + .is_valid = dallas_ds1990_is_data_valid, + .get_editable_data = dallas_ds1990_get_editable_data, + .apply_edits = dallas_ds1990_apply_edits, +}; + +bool dallas_ds1990_read(OneWireHost* host, iButtonProtocolData* protocol_data) { + DS1990ProtocolData* data = protocol_data; + return onewire_host_reset(host) && dallas_common_read_rom(host, &data->rom_data); +} + +bool dallas_ds1990_write_blank(OneWireHost* host, iButtonProtocolData* protocol_data) { + DS1990ProtocolData* data = protocol_data; + + return rw1990_write_v1(host, data->rom_data.bytes, sizeof(DallasCommonRomData)) || + rw1990_write_v2(host, data->rom_data.bytes, sizeof(DallasCommonRomData)) || + tm2004_write(host, data->rom_data.bytes, sizeof(DallasCommonRomData)); +} + +static bool dallas_ds1990_command_callback(uint8_t command, void* context) { + furi_assert(context); + DS1990ProtocolData* data = context; + OneWireSlave* bus = data->state.bus; + + switch(command) { + case DALLAS_COMMON_CMD_SEARCH_ROM: + dallas_common_emulate_search_rom(bus, &data->rom_data); + break; + case DALLAS_COMMON_CMD_READ_ROM: + case DS1990_CMD_READ_ROM: + dallas_common_emulate_read_rom(bus, &data->rom_data); + break; + default: + break; + } + + // No support for multiple consecutive commands + return false; +} + +void dallas_ds1990_emulate(OneWireSlave* bus, iButtonProtocolData* protocol_data) { + DS1990ProtocolData* data = protocol_data; + data->state.bus = bus; + + onewire_slave_set_reset_callback(bus, NULL, NULL); + onewire_slave_set_command_callback(bus, dallas_ds1990_command_callback, protocol_data); +} + +bool dallas_ds1990_save(FlipperFormat* ff, const iButtonProtocolData* protocol_data) { + const DS1990ProtocolData* data = protocol_data; + return dallas_common_save_rom_data(ff, &data->rom_data); +} + +bool dallas_ds1990_load( + FlipperFormat* ff, + uint32_t format_version, + iButtonProtocolData* protocol_data) { + DS1990ProtocolData* data = protocol_data; + return dallas_common_load_rom_data(ff, format_version, &data->rom_data); +} + +void dallas_ds1990_render_brief_data(FuriString* result, const iButtonProtocolData* protocol_data) { + const DS1990ProtocolData* data = protocol_data; + + for(size_t i = 0; i < sizeof(DallasCommonRomData); ++i) { + furi_string_cat_printf(result, "%02X ", data->rom_data.bytes[i]); + } +} + +void dallas_ds1990_render_error(FuriString* result, const iButtonProtocolData* protocol_data) { + const DS1990ProtocolData* data = protocol_data; + + if(!dallas_common_is_valid_crc(&data->rom_data)) { + dallas_common_render_crc_error(result, &data->rom_data); + } +} + +bool dallas_ds1990_is_data_valid(const iButtonProtocolData* protocol_data) { + const DS1990ProtocolData* data = protocol_data; + return dallas_common_is_valid_crc(&data->rom_data); +} + +void dallas_ds1990_get_editable_data( + iButtonEditableData* editable_data, + iButtonProtocolData* protocol_data) { + DS1990ProtocolData* data = protocol_data; + editable_data->ptr = data->rom_data.bytes; + editable_data->size = sizeof(DallasCommonRomData); +} + +void dallas_ds1990_apply_edits(iButtonProtocolData* protocol_data) { + DS1990ProtocolData* data = protocol_data; + dallas_common_apply_edits(&data->rom_data, DS1990_FAMILY_CODE); +} diff --git a/lib/one_wire/ibutton/protocols/dallas/protocol_ds1990.h b/lib/one_wire/ibutton/protocols/dallas/protocol_ds1990.h new file mode 100644 index 00000000000..de3da0e4cb9 --- /dev/null +++ b/lib/one_wire/ibutton/protocols/dallas/protocol_ds1990.h @@ -0,0 +1,5 @@ +#pragma once + +#include "protocol_dallas_base.h" + +extern const iButtonProtocolDallasBase ibutton_protocol_ds1990; diff --git a/lib/one_wire/ibutton/protocols/dallas/protocol_ds1992.c b/lib/one_wire/ibutton/protocols/dallas/protocol_ds1992.c new file mode 100644 index 00000000000..131bc634a00 --- /dev/null +++ b/lib/one_wire/ibutton/protocols/dallas/protocol_ds1992.c @@ -0,0 +1,218 @@ +#include "protocol_ds1992.h" + +#include +#include + +#include "dallas_common.h" + +#include "../blanks/tm2004.h" + +#define DS1992_FAMILY_CODE 0x08U +#define DS1992_FAMILY_NAME "DS1992" + +#define DS1992_SRAM_DATA_SIZE 128U +#define DS1992_SRAM_PAGE_SIZE 4U +#define DS1992_COPY_SCRATCH_TIMEOUT_US 100U + +#define DS1992_DATA_BYTE_COUNT 4U + +#define DS1992_SRAM_DATA_KEY "Sram Data" + +typedef struct { + OneWireSlave* bus; + DallasCommonCommandState command_state; +} DS1992ProtocolState; + +typedef struct { + DallasCommonRomData rom_data; + uint8_t sram_data[DS1992_SRAM_DATA_SIZE]; + DS1992ProtocolState state; +} DS1992ProtocolData; + +static bool dallas_ds1992_read(OneWireHost*, void*); +static bool dallas_ds1992_write_blank(OneWireHost*, iButtonProtocolData*); +static bool dallas_ds1992_write_copy(OneWireHost*, iButtonProtocolData*); +static void dallas_ds1992_emulate(OneWireSlave*, iButtonProtocolData*); +static bool dallas_ds1992_load(FlipperFormat*, uint32_t, iButtonProtocolData*); +static bool dallas_ds1992_save(FlipperFormat*, const iButtonProtocolData*); +static void dallas_ds1992_render_data(FuriString*, const iButtonProtocolData*); +static void dallas_ds1992_render_brief_data(FuriString*, const iButtonProtocolData*); +static void dallas_ds1992_render_error(FuriString*, const iButtonProtocolData*); +static bool dallas_ds1992_is_data_valid(const iButtonProtocolData*); +static void dallas_ds1992_get_editable_data(iButtonEditableData*, iButtonProtocolData*); +static void dallas_ds1992_apply_edits(iButtonProtocolData*); + +const iButtonProtocolDallasBase ibutton_protocol_ds1992 = { + .family_code = DS1992_FAMILY_CODE, + .features = iButtonProtocolFeatureExtData | iButtonProtocolFeatureWriteBlank | + iButtonProtocolFeatureWriteCopy, + .data_size = sizeof(DS1992ProtocolData), + .manufacturer = DALLAS_COMMON_MANUFACTURER_NAME, + .name = DS1992_FAMILY_NAME, + + .read = dallas_ds1992_read, + .write_blank = dallas_ds1992_write_blank, + .write_copy = dallas_ds1992_write_copy, + .emulate = dallas_ds1992_emulate, + .save = dallas_ds1992_save, + .load = dallas_ds1992_load, + .render_data = dallas_ds1992_render_data, + .render_brief_data = dallas_ds1992_render_brief_data, + .render_error = dallas_ds1992_render_error, + .is_valid = dallas_ds1992_is_data_valid, + .get_editable_data = dallas_ds1992_get_editable_data, + .apply_edits = dallas_ds1992_apply_edits, +}; + +bool dallas_ds1992_read(OneWireHost* host, iButtonProtocolData* protocol_data) { + DS1992ProtocolData* data = protocol_data; + return onewire_host_reset(host) && dallas_common_read_rom(host, &data->rom_data) && + dallas_common_read_mem(host, 0, data->sram_data, DS1992_SRAM_DATA_SIZE); +} + +bool dallas_ds1992_write_blank(OneWireHost* host, iButtonProtocolData* protocol_data) { + DS1992ProtocolData* data = protocol_data; + // TODO: Make this work, currently broken + return tm2004_write(host, (uint8_t*)data, sizeof(DallasCommonRomData) + DS1992_SRAM_DATA_SIZE); +} + +bool dallas_ds1992_write_copy(OneWireHost* host, iButtonProtocolData* protocol_data) { + DS1992ProtocolData* data = protocol_data; + return dallas_common_write_mem( + host, + DS1992_COPY_SCRATCH_TIMEOUT_US, + DS1992_SRAM_PAGE_SIZE, + data->sram_data, + DS1992_SRAM_DATA_SIZE); +} + +static void dallas_ds1992_reset_callback(void* context) { + furi_assert(context); + DS1992ProtocolData* data = context; + data->state.command_state = DallasCommonCommandStateIdle; +} + +static bool dallas_ds1992_command_callback(uint8_t command, void* context) { + furi_assert(context); + DS1992ProtocolData* data = context; + OneWireSlave* bus = data->state.bus; + + switch(command) { + case DALLAS_COMMON_CMD_SEARCH_ROM: + if(data->state.command_state == DallasCommonCommandStateIdle) { + data->state.command_state = DallasCommonCommandStateRomCmd; + return dallas_common_emulate_search_rom(bus, &data->rom_data); + + } else if(data->state.command_state == DallasCommonCommandStateRomCmd) { + data->state.command_state = DallasCommonCommandStateMemCmd; + dallas_common_emulate_read_mem(bus, data->sram_data, DS1992_SRAM_DATA_SIZE); + return false; + + } else { + return false; + } + + case DALLAS_COMMON_CMD_READ_ROM: + if(data->state.command_state == DallasCommonCommandStateIdle) { + data->state.command_state = DallasCommonCommandStateRomCmd; + return dallas_common_emulate_read_rom(bus, &data->rom_data); + } else { + return false; + } + + case DALLAS_COMMON_CMD_SKIP_ROM: + if(data->state.command_state == DallasCommonCommandStateIdle) { + data->state.command_state = DallasCommonCommandStateRomCmd; + return true; + } else { + return false; + } + + default: + return false; + } +} + +void dallas_ds1992_emulate(OneWireSlave* bus, iButtonProtocolData* protocol_data) { + DS1992ProtocolData* data = protocol_data; + data->state.bus = bus; + + onewire_slave_set_reset_callback(bus, dallas_ds1992_reset_callback, protocol_data); + onewire_slave_set_command_callback(bus, dallas_ds1992_command_callback, protocol_data); +} + +bool dallas_ds1992_load( + FlipperFormat* ff, + uint32_t format_version, + iButtonProtocolData* protocol_data) { + DS1992ProtocolData* data = protocol_data; + bool success = false; + + do { + if(format_version < 2) break; + if(!dallas_common_load_rom_data(ff, format_version, &data->rom_data)) break; + if(!flipper_format_read_hex( + ff, DS1992_SRAM_DATA_KEY, data->sram_data, DS1992_SRAM_DATA_SIZE)) + break; + success = true; + } while(false); + + return success; +} + +bool dallas_ds1992_save(FlipperFormat* ff, const iButtonProtocolData* protocol_data) { + const DS1992ProtocolData* data = protocol_data; + bool success = false; + + do { + if(!dallas_common_save_rom_data(ff, &data->rom_data)) break; + if(!flipper_format_write_hex( + ff, DS1992_SRAM_DATA_KEY, data->sram_data, DS1992_SRAM_DATA_SIZE)) + break; + success = true; + } while(false); + + return success; +} + +void dallas_ds1992_render_data(FuriString* result, const iButtonProtocolData* protocol_data) { + const DS1992ProtocolData* data = protocol_data; + pretty_format_bytes_hex_canonical( + result, + DS1992_DATA_BYTE_COUNT, + PRETTY_FORMAT_FONT_MONOSPACE, + data->sram_data, + DS1992_SRAM_DATA_SIZE); +} + +void dallas_ds1992_render_brief_data(FuriString* result, const iButtonProtocolData* protocol_data) { + const DS1992ProtocolData* data = protocol_data; + dallas_common_render_brief_data( + result, &data->rom_data, data->sram_data, DS1992_SRAM_DATA_SIZE); +} + +void dallas_ds1992_render_error(FuriString* result, const iButtonProtocolData* protocol_data) { + const DS1992ProtocolData* data = protocol_data; + + if(!dallas_common_is_valid_crc(&data->rom_data)) { + dallas_common_render_crc_error(result, &data->rom_data); + } +} + +bool dallas_ds1992_is_data_valid(const iButtonProtocolData* protocol_data) { + const DS1992ProtocolData* data = protocol_data; + return dallas_common_is_valid_crc(&data->rom_data); +} + +void dallas_ds1992_get_editable_data( + iButtonEditableData* editable_data, + iButtonProtocolData* protocol_data) { + DS1992ProtocolData* data = protocol_data; + editable_data->ptr = data->rom_data.bytes; + editable_data->size = sizeof(DallasCommonRomData); +} + +void dallas_ds1992_apply_edits(iButtonProtocolData* protocol_data) { + DS1992ProtocolData* data = protocol_data; + dallas_common_apply_edits(&data->rom_data, DS1992_FAMILY_CODE); +} diff --git a/lib/one_wire/ibutton/protocols/dallas/protocol_ds1992.h b/lib/one_wire/ibutton/protocols/dallas/protocol_ds1992.h new file mode 100644 index 00000000000..5a3c45f3be3 --- /dev/null +++ b/lib/one_wire/ibutton/protocols/dallas/protocol_ds1992.h @@ -0,0 +1,5 @@ +#pragma once + +#include "protocol_dallas_base.h" + +extern const iButtonProtocolDallasBase ibutton_protocol_ds1992; diff --git a/lib/one_wire/ibutton/protocols/dallas/protocol_ds1996.c b/lib/one_wire/ibutton/protocols/dallas/protocol_ds1996.c new file mode 100644 index 00000000000..e69145c58d7 --- /dev/null +++ b/lib/one_wire/ibutton/protocols/dallas/protocol_ds1996.c @@ -0,0 +1,212 @@ +#include "protocol_ds1996.h" + +#include +#include + +#include "dallas_common.h" + +#define DS1996_FAMILY_CODE 0x0CU +#define DS1996_FAMILY_NAME "DS1996" + +#define DS1996_SRAM_DATA_SIZE 8192U +#define DS1996_SRAM_PAGE_SIZE 32U +#define DS1996_COPY_SCRATCH_TIMEOUT_US 100U + +#define DS1996_DATA_BYTE_COUNT 4U + +#define DS1996_SRAM_DATA_KEY "Sram Data" + +typedef struct { + OneWireSlave* bus; + DallasCommonCommandState command_state; +} DS1996ProtocolState; + +typedef struct { + DallasCommonRomData rom_data; + uint8_t sram_data[DS1996_SRAM_DATA_SIZE]; + DS1996ProtocolState state; +} DS1996ProtocolData; + +static bool dallas_ds1996_read(OneWireHost*, void*); +static bool dallas_ds1996_write_copy(OneWireHost*, iButtonProtocolData*); +static void dallas_ds1996_emulate(OneWireSlave*, iButtonProtocolData*); +static bool dallas_ds1996_load(FlipperFormat*, uint32_t, iButtonProtocolData*); +static bool dallas_ds1996_save(FlipperFormat*, const iButtonProtocolData*); +static void dallas_ds1996_render_data(FuriString*, const iButtonProtocolData*); +static void dallas_ds1996_render_brief_data(FuriString*, const iButtonProtocolData*); +static void dallas_ds1996_render_error(FuriString*, const iButtonProtocolData*); +static bool dallas_ds1996_is_data_valid(const iButtonProtocolData*); +static void dallas_ds1996_get_editable_data(iButtonEditableData*, iButtonProtocolData*); +static void dallas_ds1996_apply_edits(iButtonProtocolData*); + +const iButtonProtocolDallasBase ibutton_protocol_ds1996 = { + .family_code = DS1996_FAMILY_CODE, + .features = iButtonProtocolFeatureExtData | iButtonProtocolFeatureWriteCopy, + .data_size = sizeof(DS1996ProtocolData), + .manufacturer = DALLAS_COMMON_MANUFACTURER_NAME, + .name = DS1996_FAMILY_NAME, + + .read = dallas_ds1996_read, + .write_blank = NULL, /* Data too big for known blanks */ + .write_copy = dallas_ds1996_write_copy, + .emulate = dallas_ds1996_emulate, + .save = dallas_ds1996_save, + .load = dallas_ds1996_load, + .render_data = dallas_ds1996_render_data, + .render_brief_data = dallas_ds1996_render_brief_data, + .render_error = dallas_ds1996_render_error, + .is_valid = dallas_ds1996_is_data_valid, + .get_editable_data = dallas_ds1996_get_editable_data, + .apply_edits = dallas_ds1996_apply_edits, +}; + +bool dallas_ds1996_read(OneWireHost* host, iButtonProtocolData* protocol_data) { + DS1996ProtocolData* data = protocol_data; + return onewire_host_reset(host) && dallas_common_read_rom(host, &data->rom_data) && + dallas_common_read_mem(host, 0, data->sram_data, DS1996_SRAM_DATA_SIZE); +} + +bool dallas_ds1996_write_copy(OneWireHost* host, iButtonProtocolData* protocol_data) { + DS1996ProtocolData* data = protocol_data; + return dallas_common_write_mem( + host, + DS1996_COPY_SCRATCH_TIMEOUT_US, + DS1996_SRAM_PAGE_SIZE, + data->sram_data, + DS1996_SRAM_DATA_SIZE); +} + +static void dallas_ds1996_reset_callback(void* context) { + furi_assert(context); + DS1996ProtocolData* data = context; + data->state.command_state = DallasCommonCommandStateIdle; +} + +static bool dallas_ds1996_command_callback(uint8_t command, void* context) { + furi_assert(context); + DS1996ProtocolData* data = context; + OneWireSlave* bus = data->state.bus; + + switch(command) { + case DALLAS_COMMON_CMD_SEARCH_ROM: + if(data->state.command_state == DallasCommonCommandStateIdle) { + data->state.command_state = DallasCommonCommandStateRomCmd; + return dallas_common_emulate_search_rom(bus, &data->rom_data); + + } else if(data->state.command_state == DallasCommonCommandStateRomCmd) { + data->state.command_state = DallasCommonCommandStateMemCmd; + dallas_common_emulate_read_mem(bus, data->sram_data, DS1996_SRAM_DATA_SIZE); + return false; + + } else { + return false; + } + + case DALLAS_COMMON_CMD_READ_ROM: + if(data->state.command_state == DallasCommonCommandStateIdle) { + data->state.command_state = DallasCommonCommandStateRomCmd; + return dallas_common_emulate_read_rom(bus, &data->rom_data); + } else { + return false; + } + + case DALLAS_COMMON_CMD_SKIP_ROM: + if(data->state.command_state == DallasCommonCommandStateIdle) { + data->state.command_state = DallasCommonCommandStateRomCmd; + return true; + } else { + return false; + } + + case DALLAS_COMMON_CMD_OVERDRIVE_SKIP_ROM: + case DALLAS_COMMON_CMD_OVERDRIVE_MATCH_ROM: + /* TODO: Overdrive mode support */ + default: + return false; + } +} + +void dallas_ds1996_emulate(OneWireSlave* bus, iButtonProtocolData* protocol_data) { + DS1996ProtocolData* data = protocol_data; + data->state.bus = bus; + + onewire_slave_set_reset_callback(bus, dallas_ds1996_reset_callback, protocol_data); + onewire_slave_set_command_callback(bus, dallas_ds1996_command_callback, protocol_data); +} + +bool dallas_ds1996_load( + FlipperFormat* ff, + uint32_t format_version, + iButtonProtocolData* protocol_data) { + DS1996ProtocolData* data = protocol_data; + bool success = false; + + do { + if(format_version < 2) break; + if(!dallas_common_load_rom_data(ff, format_version, &data->rom_data)) break; + if(!flipper_format_read_hex( + ff, DS1996_SRAM_DATA_KEY, data->sram_data, DS1996_SRAM_DATA_SIZE)) + break; + success = true; + } while(false); + + return success; +} + +bool dallas_ds1996_save(FlipperFormat* ff, const iButtonProtocolData* protocol_data) { + const DS1996ProtocolData* data = protocol_data; + bool success = false; + + do { + if(!dallas_common_save_rom_data(ff, &data->rom_data)) break; + if(!flipper_format_write_hex( + ff, DS1996_SRAM_DATA_KEY, data->sram_data, DS1996_SRAM_DATA_SIZE)) + break; + success = true; + } while(false); + + return success; +} + +void dallas_ds1996_render_data(FuriString* result, const iButtonProtocolData* protocol_data) { + const DS1996ProtocolData* data = protocol_data; + + pretty_format_bytes_hex_canonical( + result, + DS1996_DATA_BYTE_COUNT, + PRETTY_FORMAT_FONT_MONOSPACE, + data->sram_data, + DS1996_SRAM_DATA_SIZE); +} + +void dallas_ds1996_render_brief_data(FuriString* result, const iButtonProtocolData* protocol_data) { + const DS1996ProtocolData* data = protocol_data; + dallas_common_render_brief_data( + result, &data->rom_data, data->sram_data, DS1996_SRAM_DATA_SIZE); +} + +void dallas_ds1996_render_error(FuriString* result, const iButtonProtocolData* protocol_data) { + const DS1996ProtocolData* data = protocol_data; + + if(!dallas_common_is_valid_crc(&data->rom_data)) { + dallas_common_render_crc_error(result, &data->rom_data); + } +} + +bool dallas_ds1996_is_data_valid(const iButtonProtocolData* protocol_data) { + const DS1996ProtocolData* data = protocol_data; + return dallas_common_is_valid_crc(&data->rom_data); +} + +void dallas_ds1996_get_editable_data( + iButtonEditableData* editable_data, + iButtonProtocolData* protocol_data) { + DS1996ProtocolData* data = protocol_data; + editable_data->ptr = data->rom_data.bytes; + editable_data->size = sizeof(DallasCommonRomData); +} + +void dallas_ds1996_apply_edits(iButtonProtocolData* protocol_data) { + DS1996ProtocolData* data = protocol_data; + dallas_common_apply_edits(&data->rom_data, DS1996_FAMILY_CODE); +} diff --git a/lib/one_wire/ibutton/protocols/dallas/protocol_ds1996.h b/lib/one_wire/ibutton/protocols/dallas/protocol_ds1996.h new file mode 100644 index 00000000000..34546985b41 --- /dev/null +++ b/lib/one_wire/ibutton/protocols/dallas/protocol_ds1996.h @@ -0,0 +1,5 @@ +#pragma once + +#include "protocol_dallas_base.h" + +extern const iButtonProtocolDallasBase ibutton_protocol_ds1996; diff --git a/lib/one_wire/ibutton/protocols/dallas/protocol_ds_generic.c b/lib/one_wire/ibutton/protocols/dallas/protocol_ds_generic.c new file mode 100644 index 00000000000..50fd045112d --- /dev/null +++ b/lib/one_wire/ibutton/protocols/dallas/protocol_ds_generic.c @@ -0,0 +1,133 @@ +#include "protocol_ds_generic.h" + +#include +#include + +#include "dallas_common.h" + +#include "../blanks/tm2004.h" + +#define DALLAS_GENERIC_FAMILY_CODE 0x00U +#define DALLAS_GENERIC_FAMILY_NAME "DSGeneric" + +typedef struct { + OneWireSlave* bus; +} DallasGenericProtocolState; + +typedef struct { + DallasCommonRomData rom_data; + DallasGenericProtocolState state; +} DallasGenericProtocolData; + +static bool ds_generic_read(OneWireHost*, iButtonProtocolData*); +static bool ds_generic_write_blank(OneWireHost*, iButtonProtocolData*); +static void ds_generic_emulate(OneWireSlave*, iButtonProtocolData*); +static bool ds_generic_load(FlipperFormat*, uint32_t, iButtonProtocolData*); +static bool ds_generic_save(FlipperFormat*, const iButtonProtocolData*); +static void ds_generic_render_brief_data(FuriString*, const iButtonProtocolData*); +static void ds_generic_render_error(FuriString*, const iButtonProtocolData*); +static bool ds_generic_is_data_valid(const iButtonProtocolData*); +static void ds_generic_get_editable_data(iButtonEditableData*, iButtonProtocolData*); +static void ds_generic_apply_edits(iButtonProtocolData*); + +const iButtonProtocolDallasBase ibutton_protocol_ds_generic = { + .family_code = DALLAS_GENERIC_FAMILY_CODE, + .features = iButtonProtocolFeatureWriteBlank, + .data_size = sizeof(DallasGenericProtocolData), + .manufacturer = DALLAS_COMMON_MANUFACTURER_NAME, + .name = DALLAS_GENERIC_FAMILY_NAME, + + .read = ds_generic_read, + .write_blank = ds_generic_write_blank, + .write_copy = NULL, /* No data to write a copy */ + .emulate = ds_generic_emulate, + .save = ds_generic_save, + .load = ds_generic_load, + .render_data = NULL, /* No data to render */ + .render_brief_data = ds_generic_render_brief_data, + .render_error = ds_generic_render_error, + .is_valid = ds_generic_is_data_valid, + .get_editable_data = ds_generic_get_editable_data, + .apply_edits = ds_generic_apply_edits, +}; + +bool ds_generic_read(OneWireHost* host, iButtonProtocolData* protocol_data) { + DallasGenericProtocolData* data = protocol_data; + return onewire_host_reset(host) && dallas_common_read_rom(host, &data->rom_data); +} + +bool ds_generic_write_blank(OneWireHost* host, iButtonProtocolData* protocol_data) { + DallasGenericProtocolData* data = protocol_data; + return tm2004_write(host, data->rom_data.bytes, sizeof(DallasCommonRomData)); +} + +static bool ds_generic_command_callback(uint8_t command, void* context) { + furi_assert(context); + DallasGenericProtocolData* data = context; + OneWireSlave* bus = data->state.bus; + + switch(command) { + case DALLAS_COMMON_CMD_SEARCH_ROM: + dallas_common_emulate_search_rom(bus, &data->rom_data); + break; + case DALLAS_COMMON_CMD_READ_ROM: + dallas_common_emulate_read_rom(bus, &data->rom_data); + break; + default: + break; + } + + // No support for multiple consecutive commands + return false; +} + +void ds_generic_emulate(OneWireSlave* bus, iButtonProtocolData* protocol_data) { + DallasGenericProtocolData* data = protocol_data; + data->state.bus = bus; + + onewire_slave_set_reset_callback(bus, NULL, NULL); + onewire_slave_set_command_callback(bus, ds_generic_command_callback, protocol_data); +} + +bool ds_generic_save(FlipperFormat* ff, const iButtonProtocolData* protocol_data) { + const DallasGenericProtocolData* data = protocol_data; + return dallas_common_save_rom_data(ff, &data->rom_data); +} + +bool ds_generic_load( + FlipperFormat* ff, + uint32_t format_version, + iButtonProtocolData* protocol_data) { + DallasGenericProtocolData* data = protocol_data; + return dallas_common_load_rom_data(ff, format_version, &data->rom_data); +} + +void ds_generic_render_brief_data(FuriString* result, const iButtonProtocolData* protocol_data) { + const DallasGenericProtocolData* data = protocol_data; + + for(size_t i = 0; i < sizeof(DallasCommonRomData); ++i) { + furi_string_cat_printf(result, "%02X ", data->rom_data.bytes[i]); + } +} + +void ds_generic_render_error(FuriString* result, const iButtonProtocolData* protocol_data) { + UNUSED(result); + UNUSED(protocol_data); +} + +bool ds_generic_is_data_valid(const iButtonProtocolData* protocol_data) { + UNUSED(protocol_data); + return true; +} + +void ds_generic_get_editable_data( + iButtonEditableData* editable_data, + iButtonProtocolData* protocol_data) { + DallasGenericProtocolData* data = protocol_data; + editable_data->ptr = data->rom_data.bytes; + editable_data->size = sizeof(DallasCommonRomData); +} + +void ds_generic_apply_edits(iButtonProtocolData* protocol_data) { + UNUSED(protocol_data); +} diff --git a/lib/one_wire/ibutton/protocols/dallas/protocol_ds_generic.h b/lib/one_wire/ibutton/protocols/dallas/protocol_ds_generic.h new file mode 100644 index 00000000000..008f8a67b0d --- /dev/null +++ b/lib/one_wire/ibutton/protocols/dallas/protocol_ds_generic.h @@ -0,0 +1,5 @@ +#pragma once + +#include "protocol_dallas_base.h" + +extern const iButtonProtocolDallasBase ibutton_protocol_ds_generic; diff --git a/lib/one_wire/ibutton/protocols/dallas/protocol_group_dallas.c b/lib/one_wire/ibutton/protocols/dallas/protocol_group_dallas.c new file mode 100644 index 00000000000..d0ffa511b43 --- /dev/null +++ b/lib/one_wire/ibutton/protocols/dallas/protocol_group_dallas.c @@ -0,0 +1,310 @@ +#include "protocol_group_dallas.h" + +#include + +#include "protocol_group_dallas_defs.h" + +#define IBUTTON_ONEWIRE_ROM_SIZE 8U + +typedef struct { + OneWireHost* host; + OneWireSlave* bus; +} iButtonProtocolGroupDallas; + +static iButtonProtocolGroupDallas* ibutton_protocol_group_dallas_alloc() { + iButtonProtocolGroupDallas* group = malloc(sizeof(iButtonProtocolGroupDallas)); + + group->host = onewire_host_alloc(&ibutton_gpio); + group->bus = onewire_slave_alloc(&ibutton_gpio); + + return group; +} + +static void ibutton_protocol_group_dallas_free(iButtonProtocolGroupDallas* group) { + onewire_slave_free(group->bus); + onewire_host_free(group->host); + free(group); +} + +static size_t ibutton_protocol_group_dallas_get_max_data_size(iButtonProtocolGroupDallas* group) { + UNUSED(group); + size_t max_data_size = 0; + + for(iButtonProtocolLocalId i = 0; i < iButtonProtocolDSMax; ++i) { + const size_t current_rom_size = ibutton_protocols_dallas[i]->data_size; + if(current_rom_size > max_data_size) { + max_data_size = current_rom_size; + } + } + + return max_data_size; +} + +static bool ibutton_protocol_group_dallas_get_id_by_name( + iButtonProtocolGroupDallas* group, + iButtonProtocolLocalId* id, + const char* name) { + UNUSED(group); + // Handle older key files which refer to DS1990 as just "Dallas" + if(strcmp(name, "Dallas") == 0) { + *id = iButtonProtocolDS1990; + return true; + } + + for(iButtonProtocolLocalId i = 0; i < iButtonProtocolDSMax; ++i) { + if(strcmp(ibutton_protocols_dallas[i]->name, name) == 0) { + *id = i; + return true; + } + } + return false; +} + +static uint32_t ibutton_protocol_group_dallas_get_features( + iButtonProtocolGroupDallas* group, + iButtonProtocolLocalId id) { + UNUSED(group); + furi_assert(id < iButtonProtocolDSMax); + return ibutton_protocols_dallas[id]->features; +} + +static const char* ibutton_protocol_group_dallas_get_manufacturer( + iButtonProtocolGroupDallas* group, + iButtonProtocolLocalId id) { + UNUSED(group); + furi_assert(id < iButtonProtocolDSMax); + return ibutton_protocols_dallas[id]->manufacturer; +} + +static const char* ibutton_protocol_group_dallas_get_name( + iButtonProtocolGroupDallas* group, + iButtonProtocolLocalId id) { + UNUSED(group); + furi_assert(id < iButtonProtocolDSMax); + return ibutton_protocols_dallas[id]->name; +} + +static iButtonProtocolLocalId + ibutton_protocol_group_dallas_get_id_by_family_code(uint8_t family_code) { + iButtonProtocolLocalId id; + + for(id = 0; id < iButtonProtocolDSGeneric; ++id) { + if(ibutton_protocols_dallas[id]->family_code == family_code) break; + } + + return id; +} + +static bool ibutton_protocol_group_dallas_read( + iButtonProtocolGroupDallas* group, + iButtonProtocolData* data, + iButtonProtocolLocalId* id) { + bool success = false; + uint8_t rom_data[IBUTTON_ONEWIRE_ROM_SIZE]; + OneWireHost* host = group->host; + + onewire_host_start(host); + furi_delay_ms(100); + + FURI_CRITICAL_ENTER(); + + if(onewire_host_search(host, rom_data, OneWireHostSearchModeNormal)) { + /* Considering any found 1-Wire device a success. + * It can be checked later with ibutton_key_is_valid(). */ + success = true; + + /* If a 1-Wire device was found, id is guaranteed to be + * one of the known keys or DSGeneric. */ + *id = ibutton_protocol_group_dallas_get_id_by_family_code(rom_data[0]); + ibutton_protocols_dallas[*id]->read(host, data); + } + + onewire_host_reset_search(host); + onewire_host_stop(host); + + FURI_CRITICAL_EXIT(); + + return success; +} + +static bool ibutton_protocol_group_dallas_write_blank( + iButtonProtocolGroupDallas* group, + iButtonProtocolData* data, + iButtonProtocolLocalId id) { + furi_assert(id < iButtonProtocolDSMax); + const iButtonProtocolDallasBase* protocol = ibutton_protocols_dallas[id]; + furi_assert(protocol->features & iButtonProtocolFeatureWriteBlank); + + OneWireHost* host = group->host; + + onewire_host_start(host); + furi_delay_ms(100); + + FURI_CRITICAL_ENTER(); + + const bool success = protocol->write_blank(host, data); + onewire_host_stop(host); + + FURI_CRITICAL_EXIT(); + return success; +} + +static bool ibutton_protocol_group_dallas_write_copy( + iButtonProtocolGroupDallas* group, + iButtonProtocolData* data, + iButtonProtocolLocalId id) { + furi_assert(id < iButtonProtocolDSMax); + + const iButtonProtocolDallasBase* protocol = ibutton_protocols_dallas[id]; + furi_assert(protocol->features & iButtonProtocolFeatureWriteCopy); + + OneWireHost* host = group->host; + + onewire_host_start(host); + furi_delay_ms(100); + + FURI_CRITICAL_ENTER(); + + const bool success = protocol->write_copy(host, data); + onewire_host_stop(host); + + FURI_CRITICAL_EXIT(); + return success; +} + +static void ibutton_protocol_group_dallas_emulate_start( + iButtonProtocolGroupDallas* group, + iButtonProtocolData* data, + iButtonProtocolLocalId id) { + furi_assert(id < iButtonProtocolDSMax); + OneWireSlave* bus = group->bus; + ibutton_protocols_dallas[id]->emulate(bus, data); + onewire_slave_start(bus); +} + +static void ibutton_protocol_group_dallas_emulate_stop( + iButtonProtocolGroupDallas* group, + iButtonProtocolData* data, + iButtonProtocolLocalId id) { + furi_assert(id < iButtonProtocolDSMax); + UNUSED(data); + onewire_slave_stop(group->bus); +} + +static bool ibutton_protocol_group_dallas_save( + iButtonProtocolGroupDallas* group, + const iButtonProtocolData* data, + iButtonProtocolLocalId id, + FlipperFormat* ff) { + UNUSED(group); + furi_assert(id < iButtonProtocolDSMax); + return ibutton_protocols_dallas[id]->save(ff, data); +} + +static bool ibutton_protocol_group_dallas_load( + iButtonProtocolGroupDallas* group, + iButtonProtocolData* data, + iButtonProtocolLocalId id, + uint32_t version, + FlipperFormat* ff) { + UNUSED(group); + furi_assert(id < iButtonProtocolDSMax); + return ibutton_protocols_dallas[id]->load(ff, version, data); +} + +static void ibutton_protocol_group_dallas_render_data( + iButtonProtocolGroupDallas* group, + const iButtonProtocolData* data, + iButtonProtocolLocalId id, + FuriString* result) { + UNUSED(group); + furi_assert(id < iButtonProtocolDSMax); + const iButtonProtocolDallasBase* protocol = ibutton_protocols_dallas[id]; + furi_assert(protocol->render_data); + protocol->render_data(result, data); +} + +static void ibutton_protocol_group_dallas_render_brief_data( + iButtonProtocolGroupDallas* group, + const iButtonProtocolData* data, + iButtonProtocolLocalId id, + FuriString* result) { + UNUSED(group); + furi_assert(id < iButtonProtocolDSMax); + ibutton_protocols_dallas[id]->render_brief_data(result, data); +} + +static void ibutton_protocol_group_dallas_render_error( + iButtonProtocolGroupDallas* group, + const iButtonProtocolData* data, + iButtonProtocolLocalId id, + FuriString* result) { + UNUSED(group); + furi_assert(id < iButtonProtocolDSMax); + ibutton_protocols_dallas[id]->render_error(result, data); +} + +static bool ibutton_protocol_group_dallas_is_valid( + iButtonProtocolGroupDallas* group, + const iButtonProtocolData* data, + iButtonProtocolLocalId id) { + UNUSED(group); + furi_assert(id < iButtonProtocolDSMax); + return ibutton_protocols_dallas[id]->is_valid(data); +} + +static void ibutton_protocol_group_dallas_get_editable_data( + iButtonProtocolGroupDallas* group, + iButtonProtocolData* data, + iButtonProtocolLocalId id, + iButtonEditableData* editable) { + UNUSED(group); + furi_assert(id < iButtonProtocolDSMax); + ibutton_protocols_dallas[id]->get_editable_data(editable, data); +} + +static void ibutton_protocol_group_dallas_apply_edits( + iButtonProtocolGroupDallas* group, + iButtonProtocolData* data, + iButtonProtocolLocalId id) { + UNUSED(group); + furi_assert(id < iButtonProtocolDSMax); + ibutton_protocols_dallas[id]->apply_edits(data); +} + +const iButtonProtocolGroupBase ibutton_protocol_group_dallas = { + .protocol_count = iButtonProtocolDSMax, + + .alloc = (iButtonProtocolGroupAllocFunc)ibutton_protocol_group_dallas_alloc, + .free = (iButtonProtocolGroupFreeFunc)ibutton_protocol_group_dallas_free, + + .get_max_data_size = + (iButtonProtocolGropuGetSizeFunc)ibutton_protocol_group_dallas_get_max_data_size, + .get_id_by_name = (iButtonProtocolGroupGetIdFunc)ibutton_protocol_group_dallas_get_id_by_name, + .get_features = + (iButtonProtocolGroupGetFeaturesFunc)ibutton_protocol_group_dallas_get_features, + + .get_manufacturer = + (iButtonProtocolGroupGetStringFunc)ibutton_protocol_group_dallas_get_manufacturer, + .get_name = (iButtonProtocolGroupGetStringFunc)ibutton_protocol_group_dallas_get_name, + + .read = (iButtonProtocolGroupReadFunc)ibutton_protocol_group_dallas_read, + .write_blank = (iButtonProtocolGroupWriteFunc)ibutton_protocol_group_dallas_write_blank, + .write_copy = (iButtonProtocolGroupWriteFunc)ibutton_protocol_group_dallas_write_copy, + + .emulate_start = (iButtonProtocolGroupApplyFunc)ibutton_protocol_group_dallas_emulate_start, + .emulate_stop = (iButtonProtocolGroupApplyFunc)ibutton_protocol_group_dallas_emulate_stop, + + .save = (iButtonProtocolGroupSaveFunc)ibutton_protocol_group_dallas_save, + .load = (iButtonProtocolGroupLoadFunc)ibutton_protocol_group_dallas_load, + + .render_data = (iButtonProtocolGroupRenderFunc)ibutton_protocol_group_dallas_render_data, + .render_brief_data = + (iButtonProtocolGroupRenderFunc)ibutton_protocol_group_dallas_render_brief_data, + .render_error = (iButtonProtocolGroupRenderFunc)ibutton_protocol_group_dallas_render_error, + + .is_valid = (iButtonProtocolGroupIsValidFunc)ibutton_protocol_group_dallas_is_valid, + .get_editable_data = + (iButtonProtocolGroupGetDataFunc)ibutton_protocol_group_dallas_get_editable_data, + .apply_edits = (iButtonProtocolGroupApplyFunc)ibutton_protocol_group_dallas_apply_edits, +}; diff --git a/lib/one_wire/ibutton/protocols/dallas/protocol_group_dallas.h b/lib/one_wire/ibutton/protocols/dallas/protocol_group_dallas.h new file mode 100644 index 00000000000..d1293c71b81 --- /dev/null +++ b/lib/one_wire/ibutton/protocols/dallas/protocol_group_dallas.h @@ -0,0 +1,5 @@ +#pragma once + +#include "../protocol_group_base.h" + +extern const iButtonProtocolGroupBase ibutton_protocol_group_dallas; diff --git a/lib/one_wire/ibutton/protocols/dallas/protocol_group_dallas_defs.c b/lib/one_wire/ibutton/protocols/dallas/protocol_group_dallas_defs.c new file mode 100644 index 00000000000..e54c3125d73 --- /dev/null +++ b/lib/one_wire/ibutton/protocols/dallas/protocol_group_dallas_defs.c @@ -0,0 +1,16 @@ +#include "protocol_group_dallas_defs.h" + +#include "protocol_ds1990.h" +#include "protocol_ds1992.h" +#include "protocol_ds1996.h" +#include "protocol_ds_generic.h" + +const iButtonProtocolDallasBase* ibutton_protocols_dallas[] = { + [iButtonProtocolDS1990] = &ibutton_protocol_ds1990, + [iButtonProtocolDS1992] = &ibutton_protocol_ds1992, + [iButtonProtocolDS1996] = &ibutton_protocol_ds1996, + /* Add new 1-Wire protocols here */ + + /* Default catch-all 1-Wire protocol */ + [iButtonProtocolDSGeneric] = &ibutton_protocol_ds_generic, +}; diff --git a/lib/one_wire/ibutton/protocols/dallas/protocol_group_dallas_defs.h b/lib/one_wire/ibutton/protocols/dallas/protocol_group_dallas_defs.h new file mode 100644 index 00000000000..ba74c0c2361 --- /dev/null +++ b/lib/one_wire/ibutton/protocols/dallas/protocol_group_dallas_defs.h @@ -0,0 +1,16 @@ +#pragma once + +#include "protocol_dallas_base.h" + +typedef enum { + iButtonProtocolDS1990, + iButtonProtocolDS1992, + iButtonProtocolDS1996, + /* Add new 1-Wire protocols here */ + + /* Default catch-all 1-Wire protocol */ + iButtonProtocolDSGeneric, + iButtonProtocolDSMax, +} iButtonProtocolDallas; + +extern const iButtonProtocolDallasBase* ibutton_protocols_dallas[]; diff --git a/lib/one_wire/ibutton/protocols/ibutton_protocols.c b/lib/one_wire/ibutton/protocols/ibutton_protocols.c deleted file mode 100644 index b07d68b33cc..00000000000 --- a/lib/one_wire/ibutton/protocols/ibutton_protocols.c +++ /dev/null @@ -1,8 +0,0 @@ -#include "ibutton_protocols.h" -#include "protocol_cyfral.h" -#include "protocol_metakom.h" - -const ProtocolBase* ibutton_protocols[] = { - [iButtonProtocolCyfral] = &protocol_cyfral, - [iButtonProtocolMetakom] = &protocol_metakom, -}; \ No newline at end of file diff --git a/lib/one_wire/ibutton/protocols/ibutton_protocols.h b/lib/one_wire/ibutton/protocols/ibutton_protocols.h deleted file mode 100644 index cdaa3ab6fa4..00000000000 --- a/lib/one_wire/ibutton/protocols/ibutton_protocols.h +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once -#include "toolbox/protocols/protocol.h" - -typedef enum { - iButtonProtocolCyfral, - iButtonProtocolMetakom, - - iButtonProtocolMax, -} iButtonProtocol; - -extern const ProtocolBase* ibutton_protocols[]; \ No newline at end of file diff --git a/lib/one_wire/ibutton/protocols/protocol_cyfral.c b/lib/one_wire/ibutton/protocols/misc/protocol_cyfral.c similarity index 96% rename from lib/one_wire/ibutton/protocols/protocol_cyfral.c rename to lib/one_wire/ibutton/protocols/misc/protocol_cyfral.c index a5f459bc049..0cdb8766b00 100644 --- a/lib/one_wire/ibutton/protocols/protocol_cyfral.c +++ b/lib/one_wire/ibutton/protocols/misc/protocol_cyfral.c @@ -1,5 +1,6 @@ #include #include + #include "protocol_cyfral.h" #define CYFRAL_DATA_SIZE sizeof(uint16_t) @@ -324,7 +325,13 @@ static LevelDuration protocol_cyfral_encoder_yield(ProtocolCyfral* proto) { return result; } -const ProtocolBase protocol_cyfral = { +static void protocol_cyfral_render_brief_data(ProtocolCyfral* proto, FuriString* result) { + for(size_t i = 0; i < CYFRAL_DATA_SIZE; ++i) { + furi_string_cat_printf(result, "%02X ", ((uint8_t*)&proto->data)[i]); + } +} + +const ProtocolBase ibutton_protocol_misc_cyfral = { .name = "Cyfral", .manufacturer = "Cyfral", .data_size = CYFRAL_DATA_SIZE, @@ -341,4 +348,5 @@ const ProtocolBase protocol_cyfral = { .start = (ProtocolEncoderStart)protocol_cyfral_encoder_start, .yield = (ProtocolEncoderYield)protocol_cyfral_encoder_yield, }, + .render_brief_data = (ProtocolRenderData)protocol_cyfral_render_brief_data, }; diff --git a/lib/one_wire/ibutton/protocols/misc/protocol_cyfral.h b/lib/one_wire/ibutton/protocols/misc/protocol_cyfral.h new file mode 100644 index 00000000000..08b8eef8163 --- /dev/null +++ b/lib/one_wire/ibutton/protocols/misc/protocol_cyfral.h @@ -0,0 +1,4 @@ +#pragma once +#include "toolbox/protocols/protocol.h" + +extern const ProtocolBase ibutton_protocol_misc_cyfral; diff --git a/lib/one_wire/ibutton/protocols/misc/protocol_group_misc.c b/lib/one_wire/ibutton/protocols/misc/protocol_group_misc.c new file mode 100644 index 00000000000..dad1ef3cfee --- /dev/null +++ b/lib/one_wire/ibutton/protocols/misc/protocol_group_misc.c @@ -0,0 +1,295 @@ +#include "protocol_group_misc.h" + +#include +#include + +#include + +#include "protocol_group_misc_defs.h" + +#define IBUTTON_MISC_READ_TIMEOUT 100 + +#define IBUTTON_MISC_DATA_KEY_KEY_COMMON "Data" + +typedef struct { + ProtocolDict* dict; + ProtocolId emulate_id; +} iButtonProtocolGroupMisc; + +static iButtonProtocolGroupMisc* ibutton_protocol_group_misc_alloc() { + iButtonProtocolGroupMisc* group = malloc(sizeof(iButtonProtocolGroupMisc)); + + group->dict = protocol_dict_alloc(ibutton_protocols_misc, iButtonProtocolMiscMax); + group->emulate_id = PROTOCOL_NO; + + return group; +} + +static void ibutton_protocol_group_misc_free(iButtonProtocolGroupMisc* group) { + protocol_dict_free(group->dict); + free(group); +} + +static size_t ibutton_protocol_group_misc_get_max_data_size(iButtonProtocolGroupMisc* group) { + return protocol_dict_get_max_data_size(group->dict); +} + +static bool ibutton_protocol_group_misc_get_id_by_name( + iButtonProtocolGroupMisc* group, + iButtonProtocolLocalId* id, + const char* name) { + const ProtocolId found_id = protocol_dict_get_protocol_by_name(group->dict, name); + + if(found_id != PROTOCOL_NO) { + *id = found_id; + return true; + } + return false; +} + +static uint32_t ibutton_protocol_group_misc_get_features( + iButtonProtocolGroupMisc* group, + iButtonProtocolLocalId id) { + UNUSED(group); + UNUSED(id); + return 0; +} + +static const char* ibutton_protocol_group_misc_get_manufacturer( + iButtonProtocolGroupMisc* group, + iButtonProtocolLocalId id) { + return protocol_dict_get_manufacturer(group->dict, id); +} + +static const char* ibutton_protocol_group_misc_get_name( + iButtonProtocolGroupMisc* group, + iButtonProtocolLocalId id) { + return protocol_dict_get_name(group->dict, id); +} + +typedef struct { + uint32_t last_dwt_value; + FuriStreamBuffer* stream; +} iButtonReadContext; + +static void ibutton_protocols_comparator_callback(bool level, void* context) { + iButtonReadContext* read_context = context; + + uint32_t current_dwt_value = DWT->CYCCNT; + + LevelDuration data = + level_duration_make(level, current_dwt_value - read_context->last_dwt_value); + furi_stream_buffer_send(read_context->stream, &data, sizeof(LevelDuration), 0); + + read_context->last_dwt_value = current_dwt_value; +} + +static bool ibutton_protocol_group_misc_read( + iButtonProtocolGroupMisc* group, + iButtonProtocolData* data, + iButtonProtocolLocalId* id) { + bool result = false; + + protocol_dict_decoders_start(group->dict); + + furi_hal_rfid_pins_reset(); + // pulldown pull pin, we sense the signal through the analog part of the RFID schematic + furi_hal_rfid_pin_pull_pulldown(); + + iButtonReadContext read_context = { + .last_dwt_value = DWT->CYCCNT, + .stream = furi_stream_buffer_alloc(sizeof(LevelDuration) * 512, 1), + }; + + furi_hal_rfid_comp_set_callback(ibutton_protocols_comparator_callback, &read_context); + furi_hal_rfid_comp_start(); + + const uint32_t tick_start = furi_get_tick(); + + for(;;) { + LevelDuration level; + size_t ret = furi_stream_buffer_receive( + read_context.stream, &level, sizeof(LevelDuration), IBUTTON_MISC_READ_TIMEOUT); + + if((furi_get_tick() - tick_start) > IBUTTON_MISC_READ_TIMEOUT) { + break; + } + + if(ret > 0) { + ProtocolId decoded_index = protocol_dict_decoders_feed( + group->dict, level_duration_get_level(level), level_duration_get_duration(level)); + + if(decoded_index == PROTOCOL_NO) continue; + + *id = decoded_index; + + protocol_dict_get_data( + group->dict, + decoded_index, + data, + protocol_dict_get_data_size(group->dict, decoded_index)); + + result = true; + } + } + + furi_hal_rfid_comp_stop(); + furi_hal_rfid_comp_set_callback(NULL, NULL); + furi_hal_rfid_pins_reset(); + + furi_stream_buffer_free(read_context.stream); + + return result; +} + +static void ibutton_protocol_group_misc_emulate_callback(void* context) { + iButtonProtocolGroupMisc* group = context; + + const LevelDuration level_duration = + protocol_dict_encoder_yield(group->dict, group->emulate_id); + + furi_hal_ibutton_emulate_set_next(level_duration_get_duration(level_duration)); + furi_hal_ibutton_pin_write(level_duration_get_level(level_duration)); +} + +static void ibutton_protocol_group_misc_emulate_start( + iButtonProtocolGroupMisc* group, + iButtonProtocolData* data, + iButtonProtocolLocalId id) { + group->emulate_id = id; + protocol_dict_set_data(group->dict, id, data, protocol_dict_get_data_size(group->dict, id)); + protocol_dict_encoder_start(group->dict, group->emulate_id); + + furi_hal_ibutton_pin_configure(); + furi_hal_ibutton_emulate_start(0, ibutton_protocol_group_misc_emulate_callback, group); +} + +static void ibutton_protocol_group_misc_emulate_stop( + iButtonProtocolGroupMisc* group, + iButtonProtocolData* data, + iButtonProtocolLocalId id) { + UNUSED(group); + UNUSED(data); + UNUSED(id); + furi_hal_ibutton_emulate_stop(); + furi_hal_ibutton_pin_reset(); +} + +static bool ibutton_protocol_group_misc_save( + iButtonProtocolGroupMisc* group, + const iButtonProtocolData* data, + iButtonProtocolLocalId id, + FlipperFormat* ff) { + const size_t data_size = protocol_dict_get_data_size(group->dict, id); + return flipper_format_write_hex(ff, IBUTTON_MISC_DATA_KEY_KEY_COMMON, data, data_size); +} + +static bool ibutton_protocol_group_misc_load( + iButtonProtocolGroupMisc* group, + iButtonProtocolData* data, + iButtonProtocolLocalId id, + uint32_t version, + FlipperFormat* ff) { + const size_t data_size = protocol_dict_get_data_size(group->dict, id); + switch(version) { + case 1: + case 2: + return flipper_format_read_hex(ff, IBUTTON_MISC_DATA_KEY_KEY_COMMON, data, data_size); + default: + return false; + } +} + +static void ibutton_protocol_group_misc_render_data( + iButtonProtocolGroupMisc* group, + const iButtonProtocolData* data, + iButtonProtocolLocalId id, + FuriString* result) { + const size_t data_size = protocol_dict_get_data_size(group->dict, id); + protocol_dict_set_data(group->dict, id, data, data_size); + protocol_dict_render_data(group->dict, result, id); +} + +static void ibutton_protocol_group_misc_render_brief_data( + iButtonProtocolGroupMisc* group, + const iButtonProtocolData* data, + iButtonProtocolLocalId id, + FuriString* result) { + const size_t data_size = protocol_dict_get_data_size(group->dict, id); + protocol_dict_set_data(group->dict, id, data, data_size); + protocol_dict_render_brief_data(group->dict, result, id); +} + +static void ibutton_protocol_group_misc_render_error( + iButtonProtocolGroupMisc* group, + const iButtonProtocolData* data, + iButtonProtocolLocalId id, + FuriString* result) { + UNUSED(group); + UNUSED(data); + UNUSED(id); + UNUSED(result); +} + +static bool ibutton_protocol_group_misc_is_valid( + iButtonProtocolGroupMisc* group, + const iButtonProtocolData* data, + iButtonProtocolLocalId id) { + UNUSED(group); + UNUSED(data); + UNUSED(id); + return true; +} + +static void ibutton_protocol_group_misc_get_editable_data( + iButtonProtocolGroupMisc* group, + iButtonProtocolData* data, + iButtonProtocolLocalId id, + iButtonEditableData* editable) { + editable->ptr = data; + editable->size = protocol_dict_get_data_size(group->dict, id); +} + +static void ibutton_protocol_group_misc_apply_edits( + iButtonProtocolGroupMisc* group, + iButtonProtocolData* data, + iButtonProtocolLocalId id) { + const size_t data_size = protocol_dict_get_data_size(group->dict, id); + protocol_dict_set_data(group->dict, id, data, data_size); +} + +const iButtonProtocolGroupBase ibutton_protocol_group_misc = { + .protocol_count = iButtonProtocolMiscMax, + + .alloc = (iButtonProtocolGroupAllocFunc)ibutton_protocol_group_misc_alloc, + .free = (iButtonProtocolGroupFreeFunc)ibutton_protocol_group_misc_free, + + .get_max_data_size = + (iButtonProtocolGropuGetSizeFunc)ibutton_protocol_group_misc_get_max_data_size, + .get_id_by_name = (iButtonProtocolGroupGetIdFunc)ibutton_protocol_group_misc_get_id_by_name, + .get_features = (iButtonProtocolGroupGetFeaturesFunc)ibutton_protocol_group_misc_get_features, + + .get_manufacturer = + (iButtonProtocolGroupGetStringFunc)ibutton_protocol_group_misc_get_manufacturer, + .get_name = (iButtonProtocolGroupGetStringFunc)ibutton_protocol_group_misc_get_name, + + .read = (iButtonProtocolGroupReadFunc)ibutton_protocol_group_misc_read, + .write_blank = NULL, + .write_copy = NULL, + + .emulate_start = (iButtonProtocolGroupApplyFunc)ibutton_protocol_group_misc_emulate_start, + .emulate_stop = (iButtonProtocolGroupApplyFunc)ibutton_protocol_group_misc_emulate_stop, + + .save = (iButtonProtocolGroupSaveFunc)ibutton_protocol_group_misc_save, + .load = (iButtonProtocolGroupLoadFunc)ibutton_protocol_group_misc_load, + + .render_data = (iButtonProtocolGroupRenderFunc)ibutton_protocol_group_misc_render_data, + .render_brief_data = + (iButtonProtocolGroupRenderFunc)ibutton_protocol_group_misc_render_brief_data, + .render_error = (iButtonProtocolGroupRenderFunc)ibutton_protocol_group_misc_render_error, + + .is_valid = (iButtonProtocolGroupIsValidFunc)ibutton_protocol_group_misc_is_valid, + .get_editable_data = + (iButtonProtocolGroupGetDataFunc)ibutton_protocol_group_misc_get_editable_data, + .apply_edits = (iButtonProtocolGroupApplyFunc)ibutton_protocol_group_misc_apply_edits, +}; diff --git a/lib/one_wire/ibutton/protocols/misc/protocol_group_misc.h b/lib/one_wire/ibutton/protocols/misc/protocol_group_misc.h new file mode 100644 index 00000000000..7fde6e73763 --- /dev/null +++ b/lib/one_wire/ibutton/protocols/misc/protocol_group_misc.h @@ -0,0 +1,5 @@ +#pragma once + +#include "../protocol_group_base.h" + +extern const iButtonProtocolGroupBase ibutton_protocol_group_misc; diff --git a/lib/one_wire/ibutton/protocols/misc/protocol_group_misc_defs.c b/lib/one_wire/ibutton/protocols/misc/protocol_group_misc_defs.c new file mode 100644 index 00000000000..09ae0bdc7cb --- /dev/null +++ b/lib/one_wire/ibutton/protocols/misc/protocol_group_misc_defs.c @@ -0,0 +1,10 @@ +#include "protocol_group_misc_defs.h" + +#include "protocol_cyfral.h" +#include "protocol_metakom.h" + +const ProtocolBase* ibutton_protocols_misc[] = { + [iButtonProtocolMiscCyfral] = &ibutton_protocol_misc_cyfral, + [iButtonProtocolMiscMetakom] = &ibutton_protocol_misc_metakom, + /* Add new misc protocols here */ +}; diff --git a/lib/one_wire/ibutton/protocols/misc/protocol_group_misc_defs.h b/lib/one_wire/ibutton/protocols/misc/protocol_group_misc_defs.h new file mode 100644 index 00000000000..0a7f92847ec --- /dev/null +++ b/lib/one_wire/ibutton/protocols/misc/protocol_group_misc_defs.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +typedef enum { + iButtonProtocolMiscCyfral, + iButtonProtocolMiscMetakom, + iButtonProtocolMiscMax, +} iButtonProtocolMisc; + +extern const ProtocolBase* ibutton_protocols_misc[]; diff --git a/lib/one_wire/ibutton/protocols/protocol_metakom.c b/lib/one_wire/ibutton/protocols/misc/protocol_metakom.c similarity index 96% rename from lib/one_wire/ibutton/protocols/protocol_metakom.c rename to lib/one_wire/ibutton/protocols/misc/protocol_metakom.c index ff65c6678f3..a2bd2cf7ca4 100644 --- a/lib/one_wire/ibutton/protocols/protocol_metakom.c +++ b/lib/one_wire/ibutton/protocols/misc/protocol_metakom.c @@ -1,5 +1,6 @@ #include #include + #include "protocol_metakom.h" #define METAKOM_DATA_SIZE sizeof(uint32_t) @@ -300,7 +301,13 @@ static LevelDuration protocol_metakom_encoder_yield(ProtocolMetakom* proto) { return result; } -const ProtocolBase protocol_metakom = { +static void protocol_metakom_render_brief_data(ProtocolMetakom* proto, FuriString* result) { + for(size_t i = 0; i < METAKOM_DATA_SIZE; ++i) { + furi_string_cat_printf(result, "%02X ", ((uint8_t*)&proto->data)[i]); + } +} + +const ProtocolBase ibutton_protocol_misc_metakom = { .name = "Metakom", .manufacturer = "Metakom", .data_size = METAKOM_DATA_SIZE, @@ -317,4 +324,5 @@ const ProtocolBase protocol_metakom = { .start = (ProtocolEncoderStart)protocol_metakom_encoder_start, .yield = (ProtocolEncoderYield)protocol_metakom_encoder_yield, }, + .render_brief_data = (ProtocolRenderData)protocol_metakom_render_brief_data, }; diff --git a/lib/one_wire/ibutton/protocols/misc/protocol_metakom.h b/lib/one_wire/ibutton/protocols/misc/protocol_metakom.h new file mode 100644 index 00000000000..317619e3fb1 --- /dev/null +++ b/lib/one_wire/ibutton/protocols/misc/protocol_metakom.h @@ -0,0 +1,4 @@ +#pragma once +#include "toolbox/protocols/protocol.h" + +extern const ProtocolBase ibutton_protocol_misc_metakom; diff --git a/lib/one_wire/ibutton/protocols/protocol_common.h b/lib/one_wire/ibutton/protocols/protocol_common.h new file mode 100644 index 00000000000..5383158e46b --- /dev/null +++ b/lib/one_wire/ibutton/protocols/protocol_common.h @@ -0,0 +1,21 @@ +#pragma once + +#include +#include + +typedef int32_t iButtonProtocolId; + +enum { + iButtonProtocolIdInvalid = -1, +}; + +typedef enum { + iButtonProtocolFeatureExtData = (1U << 0), + iButtonProtocolFeatureWriteBlank = (1U << 1), + iButtonProtocolFeatureWriteCopy = (1U << 2), +} iButtonProtocolFeature; + +typedef struct { + uint8_t* ptr; + size_t size; +} iButtonEditableData; diff --git a/lib/one_wire/ibutton/protocols/protocol_common_i.h b/lib/one_wire/ibutton/protocols/protocol_common_i.h new file mode 100644 index 00000000000..b80c92887ff --- /dev/null +++ b/lib/one_wire/ibutton/protocols/protocol_common_i.h @@ -0,0 +1,6 @@ +#pragma once + +#include "protocol_common.h" + +typedef void iButtonProtocolData; +typedef int32_t iButtonProtocolLocalId; diff --git a/lib/one_wire/ibutton/protocols/protocol_cyfral.h b/lib/one_wire/ibutton/protocols/protocol_cyfral.h deleted file mode 100644 index 97a98e485e4..00000000000 --- a/lib/one_wire/ibutton/protocols/protocol_cyfral.h +++ /dev/null @@ -1,4 +0,0 @@ -#pragma once -#include "toolbox/protocols/protocol.h" - -extern const ProtocolBase protocol_cyfral; \ No newline at end of file diff --git a/lib/one_wire/ibutton/protocols/protocol_group_base.h b/lib/one_wire/ibutton/protocols/protocol_group_base.h new file mode 100644 index 00000000000..c8fec70fe78 --- /dev/null +++ b/lib/one_wire/ibutton/protocols/protocol_group_base.h @@ -0,0 +1,104 @@ +#pragma once + +#include +#include + +#include "protocol_common_i.h" + +typedef void iButtonProtocolGroupData; +typedef int32_t iButtonProtocolGroupId; + +typedef iButtonProtocolGroupData* (*iButtonProtocolGroupAllocFunc)(void); + +typedef void (*iButtonProtocolGroupFreeFunc)(iButtonProtocolGroupData*); + +typedef void (*iButtonProtocolGroupRenderFunc)( + iButtonProtocolGroupData*, + const iButtonProtocolData*, + iButtonProtocolLocalId, + FuriString*); + +typedef bool (*iButtonProtocolGroupIsValidFunc)( + iButtonProtocolGroupData*, + const iButtonProtocolData*, + iButtonProtocolLocalId); + +typedef void (*iButtonProtocolGroupGetDataFunc)( + iButtonProtocolGroupData*, + iButtonProtocolData*, + iButtonProtocolLocalId, + iButtonEditableData*); + +typedef void (*iButtonProtocolGroupApplyFunc)( + iButtonProtocolGroupData*, + iButtonProtocolData*, + iButtonProtocolLocalId); + +typedef size_t (*iButtonProtocolGropuGetSizeFunc)(iButtonProtocolGroupData*); + +typedef uint32_t ( + *iButtonProtocolGroupGetFeaturesFunc)(iButtonProtocolGroupData*, iButtonProtocolLocalId); + +typedef const char* ( + *iButtonProtocolGroupGetStringFunc)(iButtonProtocolGroupData*, iButtonProtocolLocalId); + +typedef bool (*iButtonProtocolGroupGetIdFunc)( + iButtonProtocolGroupData*, + iButtonProtocolLocalId*, + const char*); + +typedef bool (*iButtonProtocolGroupReadFunc)( + iButtonProtocolGroupData*, + iButtonProtocolData*, + iButtonProtocolLocalId*); + +typedef bool (*iButtonProtocolGroupWriteFunc)( + iButtonProtocolGroupData*, + iButtonProtocolData*, + iButtonProtocolLocalId); + +typedef bool (*iButtonProtocolGroupSaveFunc)( + iButtonProtocolGroupData*, + const iButtonProtocolData*, + iButtonProtocolLocalId, + FlipperFormat*); + +typedef bool (*iButtonProtocolGroupLoadFunc)( + iButtonProtocolGroupData*, + iButtonProtocolData*, + iButtonProtocolLocalId, + uint32_t, + FlipperFormat*); + +typedef struct { + const uint32_t protocol_count; + + iButtonProtocolGroupAllocFunc alloc; + iButtonProtocolGroupFreeFunc free; + + iButtonProtocolGropuGetSizeFunc get_max_data_size; + iButtonProtocolGroupGetIdFunc get_id_by_name; + iButtonProtocolGroupGetFeaturesFunc get_features; + + iButtonProtocolGroupGetStringFunc get_manufacturer; + iButtonProtocolGroupGetStringFunc get_name; + + iButtonProtocolGroupReadFunc read; + iButtonProtocolGroupWriteFunc write_blank; + iButtonProtocolGroupWriteFunc write_copy; + + iButtonProtocolGroupApplyFunc emulate_start; + iButtonProtocolGroupApplyFunc emulate_stop; + + iButtonProtocolGroupSaveFunc save; + iButtonProtocolGroupLoadFunc load; + + iButtonProtocolGroupRenderFunc render_data; + iButtonProtocolGroupRenderFunc render_brief_data; + iButtonProtocolGroupRenderFunc render_error; + + iButtonProtocolGroupIsValidFunc is_valid; + iButtonProtocolGroupGetDataFunc get_editable_data; + + iButtonProtocolGroupApplyFunc apply_edits; +} iButtonProtocolGroupBase; diff --git a/lib/one_wire/ibutton/protocols/protocol_group_defs.c b/lib/one_wire/ibutton/protocols/protocol_group_defs.c new file mode 100644 index 00000000000..40a360f0eb4 --- /dev/null +++ b/lib/one_wire/ibutton/protocols/protocol_group_defs.c @@ -0,0 +1,9 @@ +#include "protocol_group_defs.h" + +#include "dallas/protocol_group_dallas.h" +#include "misc/protocol_group_misc.h" + +const iButtonProtocolGroupBase* ibutton_protocol_groups[] = { + [iButtonProtocolGroupDallas] = &ibutton_protocol_group_dallas, + [iButtonProtocolGroupMisc] = &ibutton_protocol_group_misc, +}; diff --git a/lib/one_wire/ibutton/protocols/protocol_group_defs.h b/lib/one_wire/ibutton/protocols/protocol_group_defs.h new file mode 100644 index 00000000000..2d41e3cb86e --- /dev/null +++ b/lib/one_wire/ibutton/protocols/protocol_group_defs.h @@ -0,0 +1,11 @@ +#pragma once + +#include "protocol_group_base.h" + +typedef enum { + iButtonProtocolGroupDallas, + iButtonProtocolGroupMisc, + iButtonProtocolGroupMax +} iButtonProtocolGroup; + +extern const iButtonProtocolGroupBase* ibutton_protocol_groups[]; diff --git a/lib/one_wire/ibutton/protocols/protocol_metakom.h b/lib/one_wire/ibutton/protocols/protocol_metakom.h deleted file mode 100644 index 5e44a2a8ce5..00000000000 --- a/lib/one_wire/ibutton/protocols/protocol_metakom.h +++ /dev/null @@ -1,4 +0,0 @@ -#pragma once -#include "toolbox/protocols/protocol.h" - -extern const ProtocolBase protocol_metakom; \ No newline at end of file diff --git a/lib/one_wire/one_wire_device.c b/lib/one_wire/one_wire_device.c deleted file mode 100644 index d9b4955dbe2..00000000000 --- a/lib/one_wire/one_wire_device.c +++ /dev/null @@ -1,59 +0,0 @@ -#include -#include "maxim_crc.h" -#include "one_wire_device.h" -#include "one_wire_slave.h" -#include "one_wire_slave_i.h" - -struct OneWireDevice { - uint8_t id_storage[8]; - OneWireSlave* bus; -}; - -OneWireDevice* onewire_device_alloc( - uint8_t id_1, - uint8_t id_2, - uint8_t id_3, - uint8_t id_4, - uint8_t id_5, - uint8_t id_6, - uint8_t id_7, - uint8_t id_8) { - OneWireDevice* device = malloc(sizeof(OneWireDevice)); - device->id_storage[0] = id_1; - device->id_storage[1] = id_2; - device->id_storage[2] = id_3; - device->id_storage[3] = id_4; - device->id_storage[4] = id_5; - device->id_storage[5] = id_6; - device->id_storage[6] = id_7; - device->id_storage[7] = id_8; - device->bus = NULL; - - return device; -} - -void onewire_device_free(OneWireDevice* device) { - if(device->bus != NULL) { - onewire_slave_detach(device->bus); - } - - free(device); -} - -void onewire_device_send_id(OneWireDevice* device) { - if(device->bus != NULL) { - onewire_slave_send(device->bus, device->id_storage, 8); - } -} - -void onewire_device_attach(OneWireDevice* device, OneWireSlave* bus) { - device->bus = bus; -} - -void onewire_device_detach(OneWireDevice* device) { - device->bus = NULL; -} - -uint8_t* onewire_device_get_id_p(OneWireDevice* device) { - return device->id_storage; -} diff --git a/lib/one_wire/one_wire_device.h b/lib/one_wire/one_wire_device.h deleted file mode 100644 index fc687c7badd..00000000000 --- a/lib/one_wire/one_wire_device.h +++ /dev/null @@ -1,74 +0,0 @@ -/** - * @file one_wire_device.h - * - * 1-Wire slave library, device interface. Currently it can only emulate ID. - */ - -#pragma once -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct OneWireSlave OneWireSlave; -typedef struct OneWireDevice OneWireDevice; - -/** - * Allocate onewire device with ID - * @param id_1 - * @param id_2 - * @param id_3 - * @param id_4 - * @param id_5 - * @param id_6 - * @param id_7 - * @param id_8 - * @return OneWireDevice* - */ -OneWireDevice* onewire_device_alloc( - uint8_t id_1, - uint8_t id_2, - uint8_t id_3, - uint8_t id_4, - uint8_t id_5, - uint8_t id_6, - uint8_t id_7, - uint8_t id_8); - -/** - * Deallocate onewire device - * @param device - */ -void onewire_device_free(OneWireDevice* device); - -/** - * Send ID report, called from onewire slave - * @param device - */ -void onewire_device_send_id(OneWireDevice* device); - -/** - * Attach device to onewire slave bus - * @param device - * @param bus - */ -void onewire_device_attach(OneWireDevice* device, OneWireSlave* bus); - -/** - * Attach device from onewire slave bus - * @param device - */ -void onewire_device_detach(OneWireDevice* device); - -/** - * Get pointer to device id array - * @param device - * @return uint8_t* - */ -uint8_t* onewire_device_get_id_p(OneWireDevice* device); - -#ifdef __cplusplus -} -#endif diff --git a/lib/one_wire/one_wire_host.c b/lib/one_wire/one_wire_host.c index f3d3d3c4dcd..0a4a79f5cc1 100644 --- a/lib/one_wire/one_wire_host.c +++ b/lib/one_wire/one_wire_host.c @@ -116,6 +116,12 @@ void onewire_host_write(OneWireHost* host, uint8_t value) { } } +void onewire_host_write_bytes(OneWireHost* host, const uint8_t* buffer, uint16_t count) { + for(uint16_t i = 0; i < count; ++i) { + onewire_host_write(host, buffer[i]); + } +} + void onewire_host_skip(OneWireHost* host) { onewire_host_write(host, 0xCC); } @@ -175,10 +181,10 @@ uint8_t onewire_host_search(OneWireHost* host, uint8_t* new_addr, OneWireHostSea // issue the search command switch(mode) { - case CONDITIONAL_SEARCH: + case OneWireHostSearchModeConditional: onewire_host_write(host, 0xEC); break; - case NORMAL_SEARCH: + case OneWireHostSearchModeNormal: onewire_host_write(host, 0xF0); break; } diff --git a/lib/one_wire/one_wire_host.h b/lib/one_wire/one_wire_host.h index 9c01abc1147..dc469904ddc 100644 --- a/lib/one_wire/one_wire_host.h +++ b/lib/one_wire/one_wire_host.h @@ -14,8 +14,8 @@ extern "C" { #endif typedef enum { - CONDITIONAL_SEARCH = 0, /**< Search for alarmed device */ - NORMAL_SEARCH = 1, /**< Search all devices */ + OneWireHostSearchModeConditional = 0, /**< Search for alarmed device */ + OneWireHostSearchModeNormal = 1, /**< Search all devices */ } OneWireHostSearchMode; typedef struct OneWireHost OneWireHost; @@ -76,6 +76,14 @@ void onewire_host_write_bit(OneWireHost* host, bool value); */ void onewire_host_write(OneWireHost* host, uint8_t value); +/** + * Write many bytes + * @param host + * @param buffer + * @param count + */ +void onewire_host_write_bytes(OneWireHost* host, const uint8_t* buffer, uint16_t count); + /** * Skip ROM command * @param host diff --git a/lib/one_wire/one_wire_slave.c b/lib/one_wire/one_wire_slave.c index 4b54c4f99f7..d1676cf3b81 100644 --- a/lib/one_wire/one_wire_slave.c +++ b/lib/one_wire/one_wire_slave.c @@ -1,37 +1,42 @@ #include "one_wire_slave.h" -#include "one_wire_slave_i.h" -#include "one_wire_device.h" + #include #include -#define OWS_RESET_MIN 270 -#define OWS_RESET_MAX 960 -#define OWS_PRESENCE_TIMEOUT 20 -#define OWS_PRESENCE_MIN 100 -#define OWS_PRESENCE_MAX 480 -#define OWS_MSG_HIGH_TIMEOUT 15000 -#define OWS_SLOT_MAX 135 -#define OWS_READ_MIN 20 -#define OWS_READ_MAX 60 -#define OWS_WRITE_ZERO 30 +#define ONEWIRE_TRSTL_MIN 270 /* Minimum Reset Low time */ +#define ONEWIRE_TRSTL_MAX 1200 /* Maximum Reset Low time */ + +#define ONEWIRE_TPDH_TYP 20 /* Typical Presence Detect High time */ +#define ONEWIRE_TPDL_MIN 100 /* Minimum Presence Detect Low time */ +#define ONEWIRE_TPDL_MAX 480 /* Maximum Presence Detect Low time */ + +#define ONEWIRE_TSLOT_MIN 60 /* Minimum Read/Write Slot time */ +#define ONEWIRE_TSLOT_MAX 135 /* Maximum Read/Write Slot time */ + +#define ONEWIRE_TW1L_MAX 20 /* Maximum Master Write 1 time */ +#define ONEWIRE_TRL_TMSR_MAX 30 /* Maximum Master Read Low + Read Sample time */ + +#define ONEWIRE_TH_TIMEOUT 15000 /* Maximum time before general timeout */ typedef enum { - NO_ERROR = 0, - VERY_LONG_RESET, - VERY_SHORT_RESET, - PRESENCE_LOW_ON_LINE, - AWAIT_TIMESLOT_TIMEOUT_HIGH, - INCORRECT_ONEWIRE_CMD, - FIRST_BIT_OF_BYTE_TIMEOUT, - RESET_IN_PROGRESS + OneWireSlaveErrorNone = 0, + OneWireSlaveErrorResetInProgress, + OneWireSlaveErrorPresenceConflict, + OneWireSlaveErrorInvalidCommand, + OneWireSlaveErrorTimeout, } OneWireSlaveError; struct OneWireSlave { const GpioPin* gpio_pin; OneWireSlaveError error; - OneWireDevice* device; - OneWireSlaveResultCallback result_cb; - void* result_cb_ctx; + + OneWireSlaveResetCallback reset_callback; + OneWireSlaveCommandCallback command_callback; + OneWireSlaveResultCallback result_callback; + + void* reset_callback_context; + void* result_callback_context; + void* command_callback_context; }; /*********************** PRIVATE ***********************/ @@ -55,180 +60,94 @@ static uint32_t } static bool onewire_slave_show_presence(OneWireSlave* bus) { + // wait until the bus is high (might return immediately) + onewire_slave_wait_while_gpio_is(bus, ONEWIRE_TRSTL_MAX, false); // wait while master delay presence check - onewire_slave_wait_while_gpio_is(bus, OWS_PRESENCE_TIMEOUT, true); + furi_delay_us(ONEWIRE_TPDH_TYP); // show presence furi_hal_gpio_write(bus->gpio_pin, false); - furi_delay_us(OWS_PRESENCE_MIN); + furi_delay_us(ONEWIRE_TPDL_MIN); furi_hal_gpio_write(bus->gpio_pin, true); // somebody also can show presence - const uint32_t wait_low_time = OWS_PRESENCE_MAX - OWS_PRESENCE_MIN; + const uint32_t wait_low_time = ONEWIRE_TPDL_MAX - ONEWIRE_TPDL_MIN; // so we will wait if(onewire_slave_wait_while_gpio_is(bus, wait_low_time, false) == 0) { - bus->error = PRESENCE_LOW_ON_LINE; + bus->error = OneWireSlaveErrorPresenceConflict; return false; } return true; } -static bool onewire_slave_receive_bit(OneWireSlave* bus) { - // wait while bus is low - uint32_t time = OWS_SLOT_MAX; - time = onewire_slave_wait_while_gpio_is(bus, time, false); - if(time == 0) { - bus->error = RESET_IN_PROGRESS; - return false; - } - - // wait while bus is high - time = OWS_MSG_HIGH_TIMEOUT; - time = onewire_slave_wait_while_gpio_is(bus, time, true); - if(time == 0) { - bus->error = AWAIT_TIMESLOT_TIMEOUT_HIGH; - return false; - } - - // wait a time of zero - time = OWS_READ_MIN; - time = onewire_slave_wait_while_gpio_is(bus, time, false); - - return (time > 0); -} - -static bool onewire_slave_send_bit(OneWireSlave* bus, bool value) { - const bool write_zero = !value; - - // wait while bus is low - uint32_t time = OWS_SLOT_MAX; - time = onewire_slave_wait_while_gpio_is(bus, time, false); - if(time == 0) { - bus->error = RESET_IN_PROGRESS; - return false; - } - - // wait while bus is high - time = OWS_MSG_HIGH_TIMEOUT; - time = onewire_slave_wait_while_gpio_is(bus, time, true); - if(time == 0) { - bus->error = AWAIT_TIMESLOT_TIMEOUT_HIGH; - return false; - } - - // choose write time - if(write_zero) { - furi_hal_gpio_write(bus->gpio_pin, false); - time = OWS_WRITE_ZERO; - } else { - time = OWS_READ_MAX; - } - - // hold line for ZERO or ONE time - furi_delay_us(time); - furi_hal_gpio_write(bus->gpio_pin, true); - - return true; -} - -static void onewire_slave_cmd_search_rom(OneWireSlave* bus) { - const uint8_t key_bytes = 8; - uint8_t* key = onewire_device_get_id_p(bus->device); - - for(uint8_t i = 0; i < key_bytes; i++) { - uint8_t key_byte = key[i]; +static inline bool onewire_slave_receive_and_process_command(OneWireSlave* bus) { + /* Reset condition detected, send a presence pulse and reset protocol state */ + if(bus->error == OneWireSlaveErrorResetInProgress) { + if(onewire_slave_show_presence(bus)) { + bus->error = OneWireSlaveErrorNone; - for(uint8_t j = 0; j < 8; j++) { - bool bit = (key_byte >> j) & 0x01; + if(bus->reset_callback != NULL) { + bus->reset_callback(bus->reset_callback_context); + } - if(!onewire_slave_send_bit(bus, bit)) return; - if(!onewire_slave_send_bit(bus, !bit)) return; + return true; + } - onewire_slave_receive_bit(bus); - if(bus->error != NO_ERROR) return; + } else if(bus->error == OneWireSlaveErrorNone) { + uint8_t command; + if(!onewire_slave_receive(bus, &command, 1)) { + /* Upon failure, request an additional iteration to + choose the appropriate action by checking bus->error */ + return true; + } else if(bus->command_callback) { + return bus->command_callback(command, bus->command_callback_context); + } else { + bus->error = OneWireSlaveErrorInvalidCommand; } } -} - -static bool onewire_slave_receive_and_process_cmd(OneWireSlave* bus) { - uint8_t cmd; - onewire_slave_receive(bus, &cmd, 1); - if(bus->error == RESET_IN_PROGRESS) - return true; - else if(bus->error != NO_ERROR) - return false; - - switch(cmd) { - case 0xF0: - // SEARCH ROM - onewire_slave_cmd_search_rom(bus); - return true; - - case 0x0F: - case 0x33: - // READ ROM - onewire_device_send_id(bus->device); - return true; - - default: // Unknown command - bus->error = INCORRECT_ONEWIRE_CMD; - return false; - } + return false; } -static bool onewire_slave_bus_start(OneWireSlave* bus) { - bool result = true; +static inline bool onewire_slave_bus_start(OneWireSlave* bus) { + FURI_CRITICAL_ENTER(); + furi_hal_gpio_init(bus->gpio_pin, GpioModeOutputOpenDrain, GpioPullNo, GpioSpeedLow); - if(bus->device == NULL) { - result = false; - } else { - FURI_CRITICAL_ENTER(); - furi_hal_gpio_init(bus->gpio_pin, GpioModeOutputOpenDrain, GpioPullNo, GpioSpeedLow); - bus->error = NO_ERROR; + /* Start in Reset state in order to send a presence pulse immediately */ + bus->error = OneWireSlaveErrorResetInProgress; - if(onewire_slave_show_presence(bus)) { - // TODO think about multiple command cycles - onewire_slave_receive_and_process_cmd(bus); - result = (bus->error == NO_ERROR || bus->error == INCORRECT_ONEWIRE_CMD); + while(onewire_slave_receive_and_process_command(bus)) + ; - } else { - result = false; - } + const bool result = (bus->error == OneWireSlaveErrorNone); - furi_hal_gpio_init(bus->gpio_pin, GpioModeInterruptRiseFall, GpioPullNo, GpioSpeedLow); - FURI_CRITICAL_EXIT(); - } + furi_hal_gpio_init(bus->gpio_pin, GpioModeInterruptRiseFall, GpioPullNo, GpioSpeedLow); + FURI_CRITICAL_EXIT(); return result; } -static void exti_cb(void* context) { +static void onewire_slave_exti_callback(void* context) { OneWireSlave* bus = context; - volatile bool input_state = furi_hal_gpio_read(bus->gpio_pin); + const volatile bool input_state = furi_hal_gpio_read(bus->gpio_pin); static uint32_t pulse_start = 0; if(input_state) { - uint32_t pulse_length = + const uint32_t pulse_length = (DWT->CYCCNT - pulse_start) / furi_hal_cortex_instructions_per_microsecond(); - if(pulse_length >= OWS_RESET_MIN) { - if(pulse_length <= OWS_RESET_MAX) { - // reset cycle ok - bool result = onewire_slave_bus_start(bus); - if(result && bus->result_cb != NULL) { - bus->result_cb(bus->result_cb_ctx); - } - } else { - bus->error = VERY_LONG_RESET; + + if((pulse_length >= ONEWIRE_TRSTL_MIN) && pulse_length <= (ONEWIRE_TRSTL_MAX)) { + const bool result = onewire_slave_bus_start(bus); + + if(result && bus->result_callback != NULL) { + bus->result_callback(bus->result_callback_context); } - } else { - bus->error = VERY_SHORT_RESET; } + } else { - //FALL event pulse_start = DWT->CYCCNT; } }; @@ -237,11 +156,10 @@ static void exti_cb(void* context) { OneWireSlave* onewire_slave_alloc(const GpioPin* gpio_pin) { OneWireSlave* bus = malloc(sizeof(OneWireSlave)); + bus->gpio_pin = gpio_pin; - bus->error = NO_ERROR; - bus->device = NULL; - bus->result_cb = NULL; - bus->result_cb_ctx = NULL; + bus->error = OneWireSlaveErrorNone; + return bus; } @@ -251,7 +169,7 @@ void onewire_slave_free(OneWireSlave* bus) { } void onewire_slave_start(OneWireSlave* bus) { - furi_hal_gpio_add_int_callback(bus->gpio_pin, exti_cb, bus); + furi_hal_gpio_add_int_callback(bus->gpio_pin, onewire_slave_exti_callback, bus); furi_hal_gpio_write(bus->gpio_pin, true); furi_hal_gpio_init(bus->gpio_pin, GpioModeInterruptRiseFall, GpioPullNo, GpioSpeedLow); } @@ -262,41 +180,98 @@ void onewire_slave_stop(OneWireSlave* bus) { furi_hal_gpio_remove_int_callback(bus->gpio_pin); } -void onewire_slave_attach(OneWireSlave* bus, OneWireDevice* device) { - bus->device = device; - onewire_device_attach(device, bus); +void onewire_slave_set_reset_callback( + OneWireSlave* bus, + OneWireSlaveResetCallback callback, + void* context) { + bus->reset_callback = callback; + bus->reset_callback_context = context; } -void onewire_slave_detach(OneWireSlave* bus) { - if(bus->device != NULL) { - onewire_device_detach(bus->device); - } - bus->device = NULL; +void onewire_slave_set_command_callback( + OneWireSlave* bus, + OneWireSlaveCommandCallback callback, + void* context) { + bus->command_callback = callback; + bus->command_callback_context = context; } void onewire_slave_set_result_callback( OneWireSlave* bus, OneWireSlaveResultCallback result_cb, void* context) { - bus->result_cb = result_cb; - bus->result_cb_ctx = context; + bus->result_callback = result_cb; + bus->result_callback_context = context; +} + +bool onewire_slave_receive_bit(OneWireSlave* bus) { + // wait while bus is low + uint32_t time = ONEWIRE_TSLOT_MAX; + time = onewire_slave_wait_while_gpio_is(bus, time, false); + if(time == 0) { + bus->error = OneWireSlaveErrorResetInProgress; + return false; + } + + // wait while bus is high + time = ONEWIRE_TH_TIMEOUT; + time = onewire_slave_wait_while_gpio_is(bus, time, true); + if(time == 0) { + bus->error = OneWireSlaveErrorTimeout; + return false; + } + + // wait a time of zero + time = ONEWIRE_TW1L_MAX; + time = onewire_slave_wait_while_gpio_is(bus, time, false); + + return (time > 0); } -bool onewire_slave_send(OneWireSlave* bus, const uint8_t* address, const uint8_t data_length) { - uint8_t bytes_sent = 0; +bool onewire_slave_send_bit(OneWireSlave* bus, bool value) { + // wait while bus is low + uint32_t time = ONEWIRE_TSLOT_MAX; + time = onewire_slave_wait_while_gpio_is(bus, time, false); + if(time == 0) { + bus->error = OneWireSlaveErrorResetInProgress; + return false; + } + + // wait while bus is high + time = ONEWIRE_TH_TIMEOUT; + time = onewire_slave_wait_while_gpio_is(bus, time, true); + if(time == 0) { + bus->error = OneWireSlaveErrorTimeout; + return false; + } + + // choose write time + if(!value) { + furi_hal_gpio_write(bus->gpio_pin, false); + time = ONEWIRE_TRL_TMSR_MAX; + } else { + time = ONEWIRE_TSLOT_MIN; + } + + // hold line for ZERO or ONE time + furi_delay_us(time); + furi_hal_gpio_write(bus->gpio_pin, true); + + return true; +} +bool onewire_slave_send(OneWireSlave* bus, const uint8_t* data, size_t data_size) { furi_hal_gpio_write(bus->gpio_pin, true); + size_t bytes_sent = 0; + // bytes loop - for(; bytes_sent < data_length; ++bytes_sent) { - const uint8_t data_byte = address[bytes_sent]; + for(; bytes_sent < data_size; ++bytes_sent) { + const uint8_t data_byte = data[bytes_sent]; // bit loop for(uint8_t bit_mask = 0x01; bit_mask != 0; bit_mask <<= 1) { if(!onewire_slave_send_bit(bus, bit_mask & data_byte)) { - // if we cannot send first bit - if((bit_mask == 0x01) && (bus->error == AWAIT_TIMESLOT_TIMEOUT_HIGH)) - bus->error = FIRST_BIT_OF_BYTE_TIMEOUT; return false; } } @@ -304,19 +279,25 @@ bool onewire_slave_send(OneWireSlave* bus, const uint8_t* address, const uint8_t return true; } -bool onewire_slave_receive(OneWireSlave* bus, uint8_t* data, const uint8_t data_length) { - uint8_t bytes_received = 0; - +bool onewire_slave_receive(OneWireSlave* bus, uint8_t* data, size_t data_size) { furi_hal_gpio_write(bus->gpio_pin, true); - for(; bytes_received < data_length; ++bytes_received) { + size_t bytes_received = 0; + + for(; bytes_received < data_size; ++bytes_received) { uint8_t value = 0; for(uint8_t bit_mask = 0x01; bit_mask != 0; bit_mask <<= 1) { - if(onewire_slave_receive_bit(bus)) value |= bit_mask; + if(onewire_slave_receive_bit(bus)) { + value |= bit_mask; + } + + if(bus->error != OneWireSlaveErrorNone) { + return false; + } } data[bytes_received] = value; } - return (bytes_received != data_length); + return true; } diff --git a/lib/one_wire/one_wire_slave.h b/lib/one_wire/one_wire_slave.h index 2e5db3a1c2a..914cd9335ec 100644 --- a/lib/one_wire/one_wire_slave.h +++ b/lib/one_wire/one_wire_slave.h @@ -1,12 +1,14 @@ /** * @file one_wire_slave.h * - * 1-Wire slave library. Currently it can only emulate ID. + * 1-Wire slave library. */ #pragma once +#include #include #include + #include #ifdef __cplusplus @@ -15,7 +17,10 @@ extern "C" { typedef struct OneWireDevice OneWireDevice; typedef struct OneWireSlave OneWireSlave; + +typedef void (*OneWireSlaveResetCallback)(void* context); typedef void (*OneWireSlaveResultCallback)(void* context); +typedef bool (*OneWireSlaveCommandCallback)(uint8_t command, void* context); /** * Allocate onewire slave @@ -43,17 +48,54 @@ void onewire_slave_start(OneWireSlave* bus); void onewire_slave_stop(OneWireSlave* bus); /** - * Attach device for emulation - * @param bus - * @param device + * TODO: description comment */ -void onewire_slave_attach(OneWireSlave* bus, OneWireDevice* device); +bool onewire_slave_receive_bit(OneWireSlave* bus); /** - * Detach device from bus - * @param bus + * TODO: description comment + */ +bool onewire_slave_send_bit(OneWireSlave* bus, bool value); + +/** + * Send data + * @param bus + * @param data + * @param data_size + * @return bool + */ +bool onewire_slave_send(OneWireSlave* bus, const uint8_t* data, size_t data_size); + +/** + * Receive data + * @param bus + * @param data + * @param data_size + * @return bool */ -void onewire_slave_detach(OneWireSlave* bus); +bool onewire_slave_receive(OneWireSlave* bus, uint8_t* data, size_t data_size); + +/** + * Set a callback to be called on each reset + * @param bus + * @param callback + * @param context + */ +void onewire_slave_set_reset_callback( + OneWireSlave* bus, + OneWireSlaveResetCallback callback, + void* context); + +/** + * Set a callback to be called on each command + * @param bus + * @param callback + * @param context + */ +void onewire_slave_set_command_callback( + OneWireSlave* bus, + OneWireSlaveCommandCallback callback, + void* context); /** * Set a callback to report emulation success diff --git a/lib/one_wire/one_wire_slave_i.h b/lib/one_wire/one_wire_slave_i.h deleted file mode 100644 index 55e0762e443..00000000000 --- a/lib/one_wire/one_wire_slave_i.h +++ /dev/null @@ -1,38 +0,0 @@ -/** - * @file one_wire_slave_i.h - * - * 1-Wire slave library, internal functions - */ - -#pragma once -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct OneWireDevice OneWireDevice; -typedef struct OneWireSlave OneWireSlave; - -/** - * Send data, called from emulated device - * @param bus - * @param address - * @param data_length - * @return bool - */ -bool onewire_slave_send(OneWireSlave* bus, const uint8_t* address, const uint8_t data_length); - -/** - * Receive data, called from emulated device - * @param bus - * @param data - * @param data_length - * @return bool - */ -bool onewire_slave_receive(OneWireSlave* bus, uint8_t* data, const uint8_t data_length); - -#ifdef __cplusplus -} -#endif diff --git a/lib/toolbox/SConscript b/lib/toolbox/SConscript index 8ce45d25f7d..d7b0e7bbc0c 100644 --- a/lib/toolbox/SConscript +++ b/lib/toolbox/SConscript @@ -27,6 +27,7 @@ env.Append( File("stream/string_stream.h"), File("stream/buffered_file_stream.h"), File("protocols/protocol_dict.h"), + File("pretty_format.h"), ], ) diff --git a/lib/toolbox/pretty_format.c b/lib/toolbox/pretty_format.c new file mode 100644 index 00000000000..d5ba10381a4 --- /dev/null +++ b/lib/toolbox/pretty_format.c @@ -0,0 +1,60 @@ +#include "pretty_format.h" + +#include +#include + +#define PRETTY_FORMAT_MAX_CANONICAL_DATA_SIZE 256U + +void pretty_format_bytes_hex_canonical( + FuriString* result, + size_t num_places, + const char* line_prefix, + const uint8_t* data, + size_t data_size) { + furi_assert(data); + + bool is_truncated = false; + + if(data_size > PRETTY_FORMAT_MAX_CANONICAL_DATA_SIZE) { + data_size = PRETTY_FORMAT_MAX_CANONICAL_DATA_SIZE; + is_truncated = true; + } + + /* Only num_places byte(s) can be on a single line, therefore: */ + const size_t line_count = + data_size / num_places + (data_size % num_places != 0 ? 1 : 0) + (is_truncated ? 2 : 0); + /* Line length = Prefix length + 3 * num_places (2 hex digits + space) + 1 * num_places + + + 1 pipe character + 1 newline character */ + const size_t line_length = (line_prefix ? strlen(line_prefix) : 0) + 4 * num_places + 2; + + /* Reserve memory in adance in order to avoid unnecessary reallocs */ + furi_string_reset(result); + furi_string_reserve(result, line_count * line_length); + + for(size_t i = 0; i < data_size; i += num_places) { + if(line_prefix) { + furi_string_cat(result, line_prefix); + } + + const size_t begin_idx = i; + const size_t end_idx = MIN(i + num_places, data_size); + + for(size_t j = begin_idx; j < end_idx; j++) { + furi_string_cat_printf(result, "%02X ", data[j]); + } + + furi_string_push_back(result, '|'); + + for(size_t j = begin_idx; j < end_idx; j++) { + const char c = data[j]; + const char sep = ((j < end_idx - 1) ? ' ' : '\n'); + const char* fmt = ((j < data_size - 1) ? "%c%c" : "%c"); + furi_string_cat_printf(result, fmt, (c > 0x1f && c < 0x7f) ? c : '.', sep); + } + } + + if(is_truncated) { + furi_string_cat_printf( + result, "\n(Data is too big. Showing only the first %zu bytes.)", data_size); + } +} diff --git a/lib/toolbox/pretty_format.h b/lib/toolbox/pretty_format.h new file mode 100644 index 00000000000..860528e4cd3 --- /dev/null +++ b/lib/toolbox/pretty_format.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define PRETTY_FORMAT_FONT_BOLD "\e#" +#define PRETTY_FORMAT_FONT_MONOSPACE "\e*" + +/** + * Format a data buffer as a canonical HEX dump + * @param [out] result pointer to the output string (must be initialised) + * @param [in] num_places the number of bytes on one line (both as HEX and ASCII) + * @param [in] line_prefix if not NULL, prepend this string to each line + * @param [in] data pointer to the input data buffer + * @param [in] data_size input data size + */ +void pretty_format_bytes_hex_canonical( + FuriString* result, + size_t num_places, + const char* line_prefix, + const uint8_t* data, + size_t data_size); + +#ifdef __cplusplus +} +#endif diff --git a/lib/toolbox/stream/buffered_file_stream.c b/lib/toolbox/stream/buffered_file_stream.c index 3b20a391c60..3b485e80df9 100644 --- a/lib/toolbox/stream/buffered_file_stream.c +++ b/lib/toolbox/stream/buffered_file_stream.c @@ -95,6 +95,7 @@ FS_Error buffered_file_stream_get_error(Stream* _stream) { static void buffered_file_stream_free(BufferedFileStream* stream) { furi_assert(stream); + buffered_file_stream_sync((Stream*)stream); stream_free(stream->file_stream); stream_cache_free(stream->cache); free(stream); From 24f084d282430c13910983964066d197d97c493a Mon Sep 17 00:00:00 2001 From: andr0423 <40040215+andr0423@users.noreply.github.com> Date: Fri, 3 Mar 2023 12:50:15 +0300 Subject: [PATCH 440/824] Fixed music player path for dummy mode (#2454) Music player path changed #2453 --- applications/services/desktop/scenes/desktop_scene_main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/services/desktop/scenes/desktop_scene_main.c b/applications/services/desktop/scenes/desktop_scene_main.c index befcf399a69..b02958b66ab 100644 --- a/applications/services/desktop/scenes/desktop_scene_main.c +++ b/applications/services/desktop/scenes/desktop_scene_main.c @@ -12,7 +12,7 @@ #define TAG "DesktopSrv" -#define MUSIC_PLAYER_APP EXT_PATH("/apps/Misc/music_player.fap") +#define MUSIC_PLAYER_APP EXT_PATH("/apps/Media/music_player.fap") #define SNAKE_GAME_APP EXT_PATH("/apps/Games/snake_game.fap") #define CLOCK_APP EXT_PATH("/apps/Tools/clock.fap") From fed4c28925a398aa3ff1dcf531fb027e236d1960 Mon Sep 17 00:00:00 2001 From: Milk-Cool <43724263+Milk-Cool@users.noreply.github.com> Date: Fri, 3 Mar 2023 13:01:05 +0300 Subject: [PATCH 441/824] Fixed typo in fbt.md (#2452) Co-authored-by: hedger --- documentation/fbt.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/fbt.md b/documentation/fbt.md index 65c3ee68251..a4717463179 100644 --- a/documentation/fbt.md +++ b/documentation/fbt.md @@ -9,8 +9,8 @@ To use `fbt`, you only need `git` installed in your system. `fbt` by default downloads and unpacks a pre-built toolchain, and then modifies environment variables for itself to use it. It does not contaminate your global system's path with the toolchain. > However, if you wish to use tools supplied with the toolchain outside `fbt`, you can open an *fbt shell*, with properly configured environment. - > - On Windows, simply run `scripts/toochain/fbtenv.cmd`. - > - On Linux & MacOS, run `source scripts/toochain/fbtenv.sh` in a new shell. + > - On Windows, simply run `scripts/toolchain/fbtenv.cmd`. + > - On Linux & MacOS, run `source scripts/toolchain/fbtenv.sh` in a new shell. If your system is not supported by pre-built toolchain variants or you want to use custom versions of dependencies, you can `set FBT_NOENV=1`. `fbt` will skip toolchain & environment configuration and will expect all tools to be available on your system's `PATH`. *(this option is not available on Windows)* From 42d27d04f60977d5bf267b86eca0aee6c22a5c2d Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Fri, 3 Mar 2023 12:55:51 +0200 Subject: [PATCH 442/824] [FL-3127] Fix navigation on unsupported card types (#2440) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- applications/main/nfc/scenes/nfc_scene_read_card_success.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/applications/main/nfc/scenes/nfc_scene_read_card_success.c b/applications/main/nfc/scenes/nfc_scene_read_card_success.c index 9b2a2188e4a..ee80ee76883 100644 --- a/applications/main/nfc/scenes/nfc_scene_read_card_success.c +++ b/applications/main/nfc/scenes/nfc_scene_read_card_success.c @@ -46,6 +46,9 @@ bool nfc_scene_read_card_success_on_event(void* context, SceneManagerEvent event if(event.event == GuiButtonTypeLeft) { consumed = scene_manager_previous_scene(nfc->scene_manager); } + } else if(event.type == SceneManagerEventTypeBack) { + consumed = + scene_manager_search_and_switch_to_previous_scene(nfc->scene_manager, NfcSceneStart); } return consumed; } From 6cc5119c648b0690f915e703d7d7fc71f6f554ec Mon Sep 17 00:00:00 2001 From: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> Date: Fri, 3 Mar 2023 15:37:02 +0300 Subject: [PATCH 443/824] [FL-3117] Infrared: Fix hangups on repeated button press (#2441) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- applications/main/infrared/infrared.c | 21 +++++++++++++-------- applications/main/infrared/infrared_i.h | 1 + 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/applications/main/infrared/infrared.c b/applications/main/infrared/infrared.c index 9d78a09b6e2..4f450496d4c 100644 --- a/applications/main/infrared/infrared.c +++ b/applications/main/infrared/infrared.c @@ -3,6 +3,8 @@ #include #include +#define INFRARED_TX_MIN_INTERVAL_MS 50U + static const NotificationSequence* infrared_notification_sequences[] = { &sequence_success, &sequence_set_only_green_255, @@ -299,10 +301,13 @@ bool infrared_rename_current_remote(Infrared* infrared, const char* name) { void infrared_tx_start_signal(Infrared* infrared, InfraredSignal* signal) { if(infrared->app_state.is_transmitting) { - FURI_LOG_D(INFRARED_LOG_TAG, "Transmitter is already active"); return; - } else { - infrared->app_state.is_transmitting = true; + } + + const uint32_t time_elapsed = furi_get_tick() - infrared->app_state.last_transmit_time; + + if(time_elapsed < INFRARED_TX_MIN_INTERVAL_MS) { + return; } if(infrared_signal_is_raw(signal)) { @@ -319,6 +324,8 @@ void infrared_tx_start_signal(Infrared* infrared, InfraredSignal* signal) { infrared_worker_tx_set_get_signal_callback( infrared->worker, infrared_worker_tx_get_signal_steady_callback, infrared); infrared_worker_tx_start(infrared->worker); + + infrared->app_state.is_transmitting = true; } void infrared_tx_start_button_index(Infrared* infrared, size_t button_index) { @@ -328,26 +335,24 @@ void infrared_tx_start_button_index(Infrared* infrared, size_t button_index) { InfraredSignal* signal = infrared_remote_button_get_signal(button); infrared_tx_start_signal(infrared, signal); - infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkStartSend); } void infrared_tx_start_received(Infrared* infrared) { infrared_tx_start_signal(infrared, infrared->received_signal); - infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkStartSend); } void infrared_tx_stop(Infrared* infrared) { if(!infrared->app_state.is_transmitting) { - FURI_LOG_D(INFRARED_LOG_TAG, "Transmitter is already stopped"); return; - } else { - infrared->app_state.is_transmitting = false; } infrared_worker_tx_stop(infrared->worker); infrared_worker_tx_set_get_signal_callback(infrared->worker, NULL, NULL); infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkStop); + + infrared->app_state.is_transmitting = false; + infrared->app_state.last_transmit_time = furi_get_tick(); } void infrared_text_store_set(Infrared* infrared, uint32_t bank, const char* text, ...) { diff --git a/applications/main/infrared/infrared_i.h b/applications/main/infrared/infrared_i.h index 5b555e4bb73..9e65c2b1c9d 100644 --- a/applications/main/infrared/infrared_i.h +++ b/applications/main/infrared/infrared_i.h @@ -69,6 +69,7 @@ typedef struct { InfraredEditTarget edit_target : 8; InfraredEditMode edit_mode : 8; int32_t current_button_index; + uint32_t last_transmit_time; } InfraredAppState; struct Infrared { From 72ca6b25e9b4454927cc286d3c968f1f97771b01 Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Fri, 3 Mar 2023 19:09:13 +0400 Subject: [PATCH 444/824] [FL-3106] SubGhz: better and more verbose error handling in protocols, stricter CAME validation (#2443) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * SubGhz: add error protocol * WS: add error protocol * SubGhz: error processing * SubGhz: more stringent CAME protocol restrictions * SubGhz: fix header duration CAME protocol * SubGhz: delete comments * SubGhz: sync SubGhzProtocolStatus with FuriStatus * SubGhz: update documentation and bump api_version Co-authored-by: あく --- .../main/subghz/helpers/subghz_types.h | 1 + .../scenes/subghz_scene_receiver_info.c | 3 +- .../subghz/scenes/subghz_scene_set_type.c | 5 +- .../subghz/scenes/subghz_scene_transmitter.c | 38 +++++----- applications/main/subghz/subghz_i.c | 21 ++++-- .../helpers/weather_station_types.h | 2 +- .../protocols/acurite_592txr.c | 22 ++---- .../protocols/acurite_592txr.h | 9 +-- .../weather_station/protocols/acurite_606tx.c | 22 ++---- .../weather_station/protocols/acurite_606tx.h | 9 +-- .../protocols/acurite_609txc.c | 22 ++---- .../protocols/acurite_609txc.h | 9 +-- .../protocols/ambient_weather.c | 22 ++---- .../protocols/ambient_weather.h | 9 +-- .../protocols/auriol_hg0601a.c | 20 ++---- .../protocols/auriol_hg0601a.h | 9 +-- .../weather_station/protocols/gt_wt_02.c | 20 ++---- .../weather_station/protocols/gt_wt_02.h | 9 +-- .../weather_station/protocols/gt_wt_03.c | 20 ++---- .../weather_station/protocols/gt_wt_03.h | 9 +-- .../weather_station/protocols/infactory.c | 20 ++---- .../weather_station/protocols/infactory.h | 9 +-- .../weather_station/protocols/lacrosse_tx.c | 20 ++---- .../weather_station/protocols/lacrosse_tx.h | 9 +-- .../protocols/lacrosse_tx141thbv2.c | 21 ++---- .../protocols/lacrosse_tx141thbv2.h | 8 +-- .../weather_station/protocols/nexus_th.c | 20 ++---- .../weather_station/protocols/nexus_th.h | 9 +-- .../weather_station/protocols/oregon2.c | 24 ++++--- .../weather_station/protocols/oregon_v1.c | 20 ++---- .../weather_station/protocols/oregon_v1.h | 9 +-- .../weather_station/protocols/thermopro_tx4.c | 22 ++---- .../weather_station/protocols/thermopro_tx4.h | 9 +-- .../weather_station/protocols/tx_8300.c | 19 ++--- .../weather_station/protocols/tx_8300.h | 9 +-- .../weather_station/protocols/ws_generic.c | 57 +++++++++++++-- .../weather_station/protocols/ws_generic.h | 21 ++++-- firmware/targets/f18/api_symbols.csv | 2 +- firmware/targets/f7/api_symbols.csv | 17 ++--- lib/subghz/blocks/generic.c | 43 ++++++++++-- lib/subghz/blocks/generic.h | 21 ++++-- lib/subghz/protocols/alutech_at_4n.c | 30 ++++---- lib/subghz/protocols/alutech_at_4n.h | 11 ++- lib/subghz/protocols/ansonic.c | 43 +++++------- lib/subghz/protocols/ansonic.h | 8 ++- lib/subghz/protocols/base.c | 8 +-- lib/subghz/protocols/base.h | 8 +-- lib/subghz/protocols/bett.c | 45 +++++------- lib/subghz/protocols/bett.h | 14 ++-- lib/subghz/protocols/bin_raw.c | 48 ++++++++++--- lib/subghz/protocols/bin_raw.h | 14 ++-- lib/subghz/protocols/came.c | 69 +++++++++++++------ lib/subghz/protocols/came.h | 14 ++-- lib/subghz/protocols/came_atomo.c | 22 ++---- lib/subghz/protocols/came_atomo.h | 9 +-- lib/subghz/protocols/came_twee.c | 41 ++++------- lib/subghz/protocols/came_twee.h | 14 ++-- lib/subghz/protocols/chamberlain_code.c | 30 ++++---- lib/subghz/protocols/chamberlain_code.h | 14 ++-- lib/subghz/protocols/clemsa.c | 45 +++++------- lib/subghz/protocols/clemsa.h | 14 ++-- lib/subghz/protocols/doitrand.c | 48 ++++++------- lib/subghz/protocols/doitrand.h | 14 ++-- lib/subghz/protocols/dooya.c | 46 +++++-------- lib/subghz/protocols/dooya.h | 14 ++-- lib/subghz/protocols/faac_slh.c | 22 ++---- lib/subghz/protocols/faac_slh.h | 9 +-- lib/subghz/protocols/gate_tx.c | 46 +++++-------- lib/subghz/protocols/gate_tx.h | 14 ++-- lib/subghz/protocols/holtek.c | 46 +++++-------- lib/subghz/protocols/holtek.h | 14 ++-- lib/subghz/protocols/holtek_ht12x.c | 59 ++++++++-------- lib/subghz/protocols/holtek_ht12x.h | 14 ++-- lib/subghz/protocols/honeywell_wdb.c | 46 +++++-------- lib/subghz/protocols/honeywell_wdb.h | 18 +++-- lib/subghz/protocols/hormann.c | 46 +++++-------- lib/subghz/protocols/hormann.h | 14 ++-- lib/subghz/protocols/ido.c | 19 ++--- lib/subghz/protocols/ido.h | 9 +-- lib/subghz/protocols/intertechno_v3.c | 29 ++++---- lib/subghz/protocols/intertechno_v3.h | 12 ++-- lib/subghz/protocols/keeloq.c | 62 ++++++++--------- lib/subghz/protocols/keeloq.h | 14 ++-- lib/subghz/protocols/kia.c | 19 ++--- lib/subghz/protocols/kia.h | 9 +-- lib/subghz/protocols/kinggates_stylo_4k.c | 32 ++++----- lib/subghz/protocols/kinggates_stylo_4k.h | 8 +-- lib/subghz/protocols/linear.c | 46 +++++-------- lib/subghz/protocols/linear.h | 14 ++-- lib/subghz/protocols/linear_delta3.c | 46 +++++-------- lib/subghz/protocols/linear_delta3.h | 18 +++-- lib/subghz/protocols/magellan.c | 48 ++++++------- lib/subghz/protocols/magellan.h | 14 ++-- lib/subghz/protocols/marantec.c | 43 +++++------- lib/subghz/protocols/marantec.h | 14 ++-- lib/subghz/protocols/megacode.c | 48 ++++++------- lib/subghz/protocols/megacode.h | 14 ++-- lib/subghz/protocols/nero_radio.c | 48 ++++++------- lib/subghz/protocols/nero_radio.h | 14 ++-- lib/subghz/protocols/nero_sketch.c | 48 ++++++------- lib/subghz/protocols/nero_sketch.h | 14 ++-- lib/subghz/protocols/nice_flo.c | 31 +++++---- lib/subghz/protocols/nice_flo.h | 14 ++-- lib/subghz/protocols/nice_flor_s.c | 24 ++++--- lib/subghz/protocols/nice_flor_s.h | 9 +-- lib/subghz/protocols/phoenix_v2.c | 48 ++++++------- lib/subghz/protocols/phoenix_v2.h | 14 ++-- lib/subghz/protocols/power_smart.c | 43 +++++------- lib/subghz/protocols/power_smart.h | 14 ++-- lib/subghz/protocols/princeton.c | 62 +++++++++-------- lib/subghz/protocols/princeton.h | 14 ++-- lib/subghz/protocols/raw.c | 18 +++-- lib/subghz/protocols/raw.h | 10 +-- lib/subghz/protocols/scher_khan.c | 5 +- lib/subghz/protocols/scher_khan.h | 9 +-- lib/subghz/protocols/secplus_v1.c | 47 ++++++------- lib/subghz/protocols/secplus_v1.h | 14 ++-- lib/subghz/protocols/secplus_v2.c | 57 +++++++-------- lib/subghz/protocols/secplus_v2.h | 14 ++-- lib/subghz/protocols/smc5326.c | 62 +++++++++-------- lib/subghz/protocols/smc5326.h | 14 ++-- lib/subghz/protocols/somfy_keytis.c | 36 +++++----- lib/subghz/protocols/somfy_keytis.h | 9 +-- lib/subghz/protocols/somfy_telis.c | 22 ++---- lib/subghz/protocols/somfy_telis.h | 9 +-- lib/subghz/protocols/star_line.c | 35 ++++------ lib/subghz/protocols/star_line.h | 9 +-- lib/subghz/transmitter.c | 5 +- lib/subghz/transmitter.h | 5 +- lib/subghz/types.h | 26 ++++++- 130 files changed, 1455 insertions(+), 1507 deletions(-) diff --git a/applications/main/subghz/helpers/subghz_types.h b/applications/main/subghz/helpers/subghz_types.h index abf7053937d..2bd2f6820c2 100644 --- a/applications/main/subghz/helpers/subghz_types.h +++ b/applications/main/subghz/helpers/subghz_types.h @@ -53,6 +53,7 @@ typedef enum { SubGhzLoadKeyStateUnknown, SubGhzLoadKeyStateOK, SubGhzLoadKeyStateParseErr, + SubGhzLoadKeyStateProtocolDescriptionErr, } SubGhzLoadKeyState; /** SubGhzLock */ diff --git a/applications/main/subghz/scenes/subghz_scene_receiver_info.c b/applications/main/subghz/scenes/subghz_scene_receiver_info.c index 4733b0e1d1a..152334ad632 100644 --- a/applications/main/subghz/scenes/subghz_scene_receiver_info.c +++ b/applications/main/subghz/scenes/subghz_scene_receiver_info.c @@ -22,7 +22,9 @@ static bool subghz_scene_receiver_info_update_parser(void* context) { subghz->txrx->decoder_result = subghz_receiver_search_decoder_base_by_name( subghz->txrx->receiver, subghz_history_get_protocol_name(subghz->txrx->history, subghz->txrx->idx_menu_chosen)); + if(subghz->txrx->decoder_result) { + //todo we are trying to deserialize without checking for errors, since it is assumed that we just received this chignal subghz_protocol_decoder_base_deserialize( subghz->txrx->decoder_result, subghz_history_get_raw_data(subghz->txrx->history, subghz->txrx->idx_menu_chosen)); @@ -128,7 +130,6 @@ bool subghz_scene_receiver_info_on_event(void* context, SceneManagerEvent event) subghz, subghz_history_get_raw_data( subghz->txrx->history, subghz->txrx->idx_menu_chosen))) { - scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowOnlyRx); if(subghz->txrx->txrx_state == SubGhzTxRxStateTx) { subghz_tx_stop(subghz); } diff --git a/applications/main/subghz/scenes/subghz_scene_set_type.c b/applications/main/subghz/scenes/subghz_scene_set_type.c index eaa3ccefeff..2134377e3ad 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_type.c +++ b/applications/main/subghz/scenes/subghz_scene_set_type.c @@ -34,8 +34,9 @@ bool subghz_scene_set_type_submenu_gen_data_protocol( do { Stream* fff_data_stream = flipper_format_get_raw_stream(subghz->txrx->fff_data); stream_clean(fff_data_stream); - if(!subghz_protocol_decoder_base_serialize( - subghz->txrx->decoder_result, subghz->txrx->fff_data, subghz->txrx->preset)) { + if(subghz_protocol_decoder_base_serialize( + subghz->txrx->decoder_result, subghz->txrx->fff_data, subghz->txrx->preset) != + SubGhzProtocolStatusOk) { FURI_LOG_E(TAG, "Unable to serialize"); break; } diff --git a/applications/main/subghz/scenes/subghz_scene_transmitter.c b/applications/main/subghz/scenes/subghz_scene_transmitter.c index fbe954f0c35..c8663cc8e56 100644 --- a/applications/main/subghz/scenes/subghz_scene_transmitter.c +++ b/applications/main/subghz/scenes/subghz_scene_transmitter.c @@ -9,9 +9,8 @@ void subghz_scene_transmitter_callback(SubGhzCustomEvent event, void* context) { } bool subghz_scene_transmitter_update_data_show(void* context) { - //ToDo Fix SubGhz* subghz = context; - + bool ret = false; if(subghz->txrx->decoder_result) { FuriString* key_str; FuriString* frequency_str; @@ -22,30 +21,29 @@ bool subghz_scene_transmitter_update_data_show(void* context) { modulation_str = furi_string_alloc(); uint8_t show_button = 0; - subghz_protocol_decoder_base_deserialize( - subghz->txrx->decoder_result, subghz->txrx->fff_data); - subghz_protocol_decoder_base_get_string(subghz->txrx->decoder_result, key_str); - - if((subghz->txrx->decoder_result->protocol->flag & SubGhzProtocolFlag_Send) == - SubGhzProtocolFlag_Send) { - show_button = 1; - } + if(subghz_protocol_decoder_base_deserialize( + subghz->txrx->decoder_result, subghz->txrx->fff_data) == SubGhzProtocolStatusOk) { + subghz_protocol_decoder_base_get_string(subghz->txrx->decoder_result, key_str); - subghz_get_frequency_modulation(subghz, frequency_str, modulation_str); - subghz_view_transmitter_add_data_to_show( - subghz->subghz_transmitter, - furi_string_get_cstr(key_str), - furi_string_get_cstr(frequency_str), - furi_string_get_cstr(modulation_str), - show_button); + if((subghz->txrx->decoder_result->protocol->flag & SubGhzProtocolFlag_Send) == + SubGhzProtocolFlag_Send) { + show_button = 1; + } + subghz_get_frequency_modulation(subghz, frequency_str, modulation_str); + subghz_view_transmitter_add_data_to_show( + subghz->subghz_transmitter, + furi_string_get_cstr(key_str), + furi_string_get_cstr(frequency_str), + furi_string_get_cstr(modulation_str), + show_button); + ret = true; + } furi_string_free(frequency_str); furi_string_free(modulation_str); furi_string_free(key_str); - - return true; } - return false; + return ret; } void subghz_scene_transmitter_on_enter(void* context) { diff --git a/applications/main/subghz/subghz_i.c b/applications/main/subghz/subghz_i.c index 7de020a54ff..94713ddba6e 100644 --- a/applications/main/subghz/subghz_i.c +++ b/applications/main/subghz/subghz_i.c @@ -153,7 +153,6 @@ bool subghz_tx_start(SubGhz* subghz, FlipperFormat* flipper_format) { FURI_LOG_E(TAG, "Missing Protocol"); break; } - //ToDo FIX if(!flipper_format_insert_or_update_uint32(flipper_format, "Repeat", &repeat, 1)) { FURI_LOG_E(TAG, "Unable Repeat"); break; @@ -163,7 +162,8 @@ bool subghz_tx_start(SubGhz* subghz, FlipperFormat* flipper_format) { subghz->txrx->environment, furi_string_get_cstr(temp_str)); if(subghz->txrx->transmitter) { - if(subghz_transmitter_deserialize(subghz->txrx->transmitter, flipper_format)) { + if(subghz_transmitter_deserialize(subghz->txrx->transmitter, flipper_format) == + SubGhzProtocolStatusOk) { if(strcmp(furi_string_get_cstr(subghz->txrx->preset->name), "") != 0) { subghz_begin( subghz, @@ -186,7 +186,12 @@ bool subghz_tx_start(SubGhz* subghz, FlipperFormat* flipper_format) { //Start TX furi_hal_subghz_start_async_tx( subghz_transmitter_yield, subghz->txrx->transmitter); + } else { + subghz_dialog_message_show_only_rx(subghz); } + } else { + dialog_message_show_storage_error( + subghz->dialogs, "Error in protocol\nparameters\ndescription"); } } if(!ret) { @@ -333,8 +338,10 @@ bool subghz_key_load(SubGhz* subghz, const char* file_path, bool show_dialog) { subghz->txrx->decoder_result = subghz_receiver_search_decoder_base_by_name( subghz->txrx->receiver, furi_string_get_cstr(temp_str)); if(subghz->txrx->decoder_result) { - if(!subghz_protocol_decoder_base_deserialize( - subghz->txrx->decoder_result, subghz->txrx->fff_data)) { + SubGhzProtocolStatus status = subghz_protocol_decoder_base_deserialize( + subghz->txrx->decoder_result, subghz->txrx->fff_data); + if(status != SubGhzProtocolStatusOk) { + load_key_state = SubGhzLoadKeyStateProtocolDescriptionErr; break; } } else { @@ -355,6 +362,12 @@ bool subghz_key_load(SubGhz* subghz, const char* file_path, bool show_dialog) { dialog_message_show_storage_error(subghz->dialogs, "Cannot parse\nfile"); } return false; + case SubGhzLoadKeyStateProtocolDescriptionErr: + if(show_dialog) { + dialog_message_show_storage_error( + subghz->dialogs, "Error in protocol\nparameters\ndescription"); + } + return false; case SubGhzLoadKeyStateOK: return true; diff --git a/applications/plugins/weather_station/helpers/weather_station_types.h b/applications/plugins/weather_station/helpers/weather_station_types.h index 1f5612e2e9a..111465978b8 100644 --- a/applications/plugins/weather_station/helpers/weather_station_types.h +++ b/applications/plugins/weather_station/helpers/weather_station_types.h @@ -3,7 +3,7 @@ #include #include -#define WS_VERSION_APP "0.7" +#define WS_VERSION_APP "0.8" #define WS_DEVELOPED "SkorP" #define WS_GITHUB "https://github.com/flipperdevices/flipperzero-firmware" diff --git a/applications/plugins/weather_station/protocols/acurite_592txr.c b/applications/plugins/weather_station/protocols/acurite_592txr.c index 5384a3c9190..874f6dd33bf 100644 --- a/applications/plugins/weather_station/protocols/acurite_592txr.c +++ b/applications/plugins/weather_station/protocols/acurite_592txr.c @@ -258,7 +258,7 @@ uint8_t ws_protocol_decoder_acurite_592txr_get_hash_data(void* context) { &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } -bool ws_protocol_decoder_acurite_592txr_serialize( +SubGhzProtocolStatus ws_protocol_decoder_acurite_592txr_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset) { @@ -267,22 +267,14 @@ bool ws_protocol_decoder_acurite_592txr_serialize( return ws_block_generic_serialize(&instance->generic, flipper_format, preset); } -bool ws_protocol_decoder_acurite_592txr_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + ws_protocol_decoder_acurite_592txr_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); WSProtocolDecoderAcurite_592TXR* instance = context; - bool ret = false; - do { - if(!ws_block_generic_deserialize(&instance->generic, flipper_format)) { - break; - } - if(instance->generic.data_count_bit != - ws_protocol_acurite_592txr_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); - break; - } - ret = true; - } while(false); - return ret; + return ws_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + ws_protocol_acurite_592txr_const.min_count_bit_for_found); } void ws_protocol_decoder_acurite_592txr_get_string(void* context, FuriString* output) { diff --git a/applications/plugins/weather_station/protocols/acurite_592txr.h b/applications/plugins/weather_station/protocols/acurite_592txr.h index ac0371d89e4..1071d1cf77a 100644 --- a/applications/plugins/weather_station/protocols/acurite_592txr.h +++ b/applications/plugins/weather_station/protocols/acurite_592txr.h @@ -56,9 +56,9 @@ uint8_t ws_protocol_decoder_acurite_592txr_get_hash_data(void* context); * @param context Pointer to a WSProtocolDecoderAcurite_592TXR instance * @param flipper_format Pointer to a FlipperFormat instance * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return true On success + * @return status */ -bool ws_protocol_decoder_acurite_592txr_serialize( +SubGhzProtocolStatus ws_protocol_decoder_acurite_592txr_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset); @@ -67,9 +67,10 @@ bool ws_protocol_decoder_acurite_592txr_serialize( * Deserialize data WSProtocolDecoderAcurite_592TXR. * @param context Pointer to a WSProtocolDecoderAcurite_592TXR instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool ws_protocol_decoder_acurite_592txr_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + ws_protocol_decoder_acurite_592txr_deserialize(void* context, FlipperFormat* flipper_format); /** * Getting a textual representation of the received data. diff --git a/applications/plugins/weather_station/protocols/acurite_606tx.c b/applications/plugins/weather_station/protocols/acurite_606tx.c index 4cb5d18b8f9..e0d405c6623 100644 --- a/applications/plugins/weather_station/protocols/acurite_606tx.c +++ b/applications/plugins/weather_station/protocols/acurite_606tx.c @@ -199,7 +199,7 @@ uint8_t ws_protocol_decoder_acurite_606tx_get_hash_data(void* context) { &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } -bool ws_protocol_decoder_acurite_606tx_serialize( +SubGhzProtocolStatus ws_protocol_decoder_acurite_606tx_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset) { @@ -208,22 +208,14 @@ bool ws_protocol_decoder_acurite_606tx_serialize( return ws_block_generic_serialize(&instance->generic, flipper_format, preset); } -bool ws_protocol_decoder_acurite_606tx_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + ws_protocol_decoder_acurite_606tx_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); WSProtocolDecoderAcurite_606TX* instance = context; - bool ret = false; - do { - if(!ws_block_generic_deserialize(&instance->generic, flipper_format)) { - break; - } - if(instance->generic.data_count_bit != - ws_protocol_acurite_606tx_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); - break; - } - ret = true; - } while(false); - return ret; + return ws_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + ws_protocol_acurite_606tx_const.min_count_bit_for_found); } void ws_protocol_decoder_acurite_606tx_get_string(void* context, FuriString* output) { diff --git a/applications/plugins/weather_station/protocols/acurite_606tx.h b/applications/plugins/weather_station/protocols/acurite_606tx.h index 5bab3bcb794..15b6d1eb50d 100644 --- a/applications/plugins/weather_station/protocols/acurite_606tx.h +++ b/applications/plugins/weather_station/protocols/acurite_606tx.h @@ -56,9 +56,9 @@ uint8_t ws_protocol_decoder_acurite_606tx_get_hash_data(void* context); * @param context Pointer to a WSProtocolDecoderAcurite_606TX instance * @param flipper_format Pointer to a FlipperFormat instance * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return true On success + * @return status */ -bool ws_protocol_decoder_acurite_606tx_serialize( +SubGhzProtocolStatus ws_protocol_decoder_acurite_606tx_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset); @@ -67,9 +67,10 @@ bool ws_protocol_decoder_acurite_606tx_serialize( * Deserialize data WSProtocolDecoderAcurite_606TX. * @param context Pointer to a WSProtocolDecoderAcurite_606TX instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool ws_protocol_decoder_acurite_606tx_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + ws_protocol_decoder_acurite_606tx_deserialize(void* context, FlipperFormat* flipper_format); /** * Getting a textual representation of the received data. diff --git a/applications/plugins/weather_station/protocols/acurite_609txc.c b/applications/plugins/weather_station/protocols/acurite_609txc.c index aeb0785eb50..853b78446cb 100644 --- a/applications/plugins/weather_station/protocols/acurite_609txc.c +++ b/applications/plugins/weather_station/protocols/acurite_609txc.c @@ -199,7 +199,7 @@ uint8_t ws_protocol_decoder_acurite_609txc_get_hash_data(void* context) { &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } -bool ws_protocol_decoder_acurite_609txc_serialize( +SubGhzProtocolStatus ws_protocol_decoder_acurite_609txc_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset) { @@ -208,22 +208,14 @@ bool ws_protocol_decoder_acurite_609txc_serialize( return ws_block_generic_serialize(&instance->generic, flipper_format, preset); } -bool ws_protocol_decoder_acurite_609txc_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + ws_protocol_decoder_acurite_609txc_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); WSProtocolDecoderAcurite_609TXC* instance = context; - bool ret = false; - do { - if(!ws_block_generic_deserialize(&instance->generic, flipper_format)) { - break; - } - if(instance->generic.data_count_bit != - ws_protocol_acurite_609txc_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); - break; - } - ret = true; - } while(false); - return ret; + return ws_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + ws_protocol_acurite_609txc_const.min_count_bit_for_found); } void ws_protocol_decoder_acurite_609txc_get_string(void* context, FuriString* output) { diff --git a/applications/plugins/weather_station/protocols/acurite_609txc.h b/applications/plugins/weather_station/protocols/acurite_609txc.h index f87c20e9b70..3e3b9cee8d2 100644 --- a/applications/plugins/weather_station/protocols/acurite_609txc.h +++ b/applications/plugins/weather_station/protocols/acurite_609txc.h @@ -56,9 +56,9 @@ uint8_t ws_protocol_decoder_acurite_609txc_get_hash_data(void* context); * @param context Pointer to a WSProtocolDecoderAcurite_609TXC instance * @param flipper_format Pointer to a FlipperFormat instance * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return true On success + * @return status */ -bool ws_protocol_decoder_acurite_609txc_serialize( +SubGhzProtocolStatus ws_protocol_decoder_acurite_609txc_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset); @@ -67,9 +67,10 @@ bool ws_protocol_decoder_acurite_609txc_serialize( * Deserialize data WSProtocolDecoderAcurite_609TXC. * @param context Pointer to a WSProtocolDecoderAcurite_609TXC instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool ws_protocol_decoder_acurite_609txc_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + ws_protocol_decoder_acurite_609txc_deserialize(void* context, FlipperFormat* flipper_format); /** * Getting a textual representation of the received data. diff --git a/applications/plugins/weather_station/protocols/ambient_weather.c b/applications/plugins/weather_station/protocols/ambient_weather.c index e3c85f40ba4..588a7f82ae9 100644 --- a/applications/plugins/weather_station/protocols/ambient_weather.c +++ b/applications/plugins/weather_station/protocols/ambient_weather.c @@ -228,7 +228,7 @@ uint8_t ws_protocol_decoder_ambient_weather_get_hash_data(void* context) { &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } -bool ws_protocol_decoder_ambient_weather_serialize( +SubGhzProtocolStatus ws_protocol_decoder_ambient_weather_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset) { @@ -237,22 +237,14 @@ bool ws_protocol_decoder_ambient_weather_serialize( return ws_block_generic_serialize(&instance->generic, flipper_format, preset); } -bool ws_protocol_decoder_ambient_weather_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + ws_protocol_decoder_ambient_weather_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); WSProtocolDecoderAmbient_Weather* instance = context; - bool ret = false; - do { - if(!ws_block_generic_deserialize(&instance->generic, flipper_format)) { - break; - } - if(instance->generic.data_count_bit != - ws_protocol_ambient_weather_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); - break; - } - ret = true; - } while(false); - return ret; + return ws_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + ws_protocol_ambient_weather_const.min_count_bit_for_found); } void ws_protocol_decoder_ambient_weather_get_string(void* context, FuriString* output) { diff --git a/applications/plugins/weather_station/protocols/ambient_weather.h b/applications/plugins/weather_station/protocols/ambient_weather.h index 04cc5819c27..1694403cd1d 100644 --- a/applications/plugins/weather_station/protocols/ambient_weather.h +++ b/applications/plugins/weather_station/protocols/ambient_weather.h @@ -56,9 +56,9 @@ uint8_t ws_protocol_decoder_ambient_weather_get_hash_data(void* context); * @param context Pointer to a WSProtocolDecoderAmbient_Weather instance * @param flipper_format Pointer to a FlipperFormat instance * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return true On success + * @return status */ -bool ws_protocol_decoder_ambient_weather_serialize( +SubGhzProtocolStatus ws_protocol_decoder_ambient_weather_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset); @@ -67,9 +67,10 @@ bool ws_protocol_decoder_ambient_weather_serialize( * Deserialize data WSProtocolDecoderAmbient_Weather. * @param context Pointer to a WSProtocolDecoderAmbient_Weather instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool ws_protocol_decoder_ambient_weather_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + ws_protocol_decoder_ambient_weather_deserialize(void* context, FlipperFormat* flipper_format); /** * Getting a textual representation of the received data. diff --git a/applications/plugins/weather_station/protocols/auriol_hg0601a.c b/applications/plugins/weather_station/protocols/auriol_hg0601a.c index d5f89fc8bd5..813fe1526c8 100644 --- a/applications/plugins/weather_station/protocols/auriol_hg0601a.c +++ b/applications/plugins/weather_station/protocols/auriol_hg0601a.c @@ -210,7 +210,7 @@ uint8_t ws_protocol_decoder_auriol_th_get_hash_data(void* context) { &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } -bool ws_protocol_decoder_auriol_th_serialize( +SubGhzProtocolStatus ws_protocol_decoder_auriol_th_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset) { @@ -219,22 +219,12 @@ bool ws_protocol_decoder_auriol_th_serialize( return ws_block_generic_serialize(&instance->generic, flipper_format, preset); } -bool ws_protocol_decoder_auriol_th_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + ws_protocol_decoder_auriol_th_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); WSProtocolDecoderAuriol_TH* instance = context; - bool ret = false; - do { - if(!ws_block_generic_deserialize(&instance->generic, flipper_format)) { - break; - } - if(instance->generic.data_count_bit != - ws_protocol_auriol_th_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); - break; - } - ret = true; - } while(false); - return ret; + return ws_block_generic_deserialize_check_count_bit( + &instance->generic, flipper_format, ws_protocol_auriol_th_const.min_count_bit_for_found); } void ws_protocol_decoder_auriol_th_get_string(void* context, FuriString* output) { diff --git a/applications/plugins/weather_station/protocols/auriol_hg0601a.h b/applications/plugins/weather_station/protocols/auriol_hg0601a.h index c23007c1a70..155bb07fc46 100644 --- a/applications/plugins/weather_station/protocols/auriol_hg0601a.h +++ b/applications/plugins/weather_station/protocols/auriol_hg0601a.h @@ -56,9 +56,9 @@ uint8_t ws_protocol_decoder_auriol_th_get_hash_data(void* context); * @param context Pointer to a WSProtocolDecoderAuriol_TH instance * @param flipper_format Pointer to a FlipperFormat instance * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return true On success + * @return status */ -bool ws_protocol_decoder_auriol_th_serialize( +SubGhzProtocolStatus ws_protocol_decoder_auriol_th_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset); @@ -67,9 +67,10 @@ bool ws_protocol_decoder_auriol_th_serialize( * Deserialize data WSProtocolDecoderAuriol_TH. * @param context Pointer to a WSProtocolDecoderAuriol_TH instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool ws_protocol_decoder_auriol_th_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + ws_protocol_decoder_auriol_th_deserialize(void* context, FlipperFormat* flipper_format); /** * Getting a textual representation of the received data. diff --git a/applications/plugins/weather_station/protocols/gt_wt_02.c b/applications/plugins/weather_station/protocols/gt_wt_02.c index cbe119192ac..d333164b491 100644 --- a/applications/plugins/weather_station/protocols/gt_wt_02.c +++ b/applications/plugins/weather_station/protocols/gt_wt_02.c @@ -217,7 +217,7 @@ uint8_t ws_protocol_decoder_gt_wt_02_get_hash_data(void* context) { &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } -bool ws_protocol_decoder_gt_wt_02_serialize( +SubGhzProtocolStatus ws_protocol_decoder_gt_wt_02_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset) { @@ -226,22 +226,12 @@ bool ws_protocol_decoder_gt_wt_02_serialize( return ws_block_generic_serialize(&instance->generic, flipper_format, preset); } -bool ws_protocol_decoder_gt_wt_02_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + ws_protocol_decoder_gt_wt_02_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); WSProtocolDecoderGT_WT02* instance = context; - bool ret = false; - do { - if(!ws_block_generic_deserialize(&instance->generic, flipper_format)) { - break; - } - if(instance->generic.data_count_bit != - ws_protocol_gt_wt_02_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); - break; - } - ret = true; - } while(false); - return ret; + return ws_block_generic_deserialize_check_count_bit( + &instance->generic, flipper_format, ws_protocol_gt_wt_02_const.min_count_bit_for_found); } void ws_protocol_decoder_gt_wt_02_get_string(void* context, FuriString* output) { diff --git a/applications/plugins/weather_station/protocols/gt_wt_02.h b/applications/plugins/weather_station/protocols/gt_wt_02.h index f17d5baa07d..e13576d218d 100644 --- a/applications/plugins/weather_station/protocols/gt_wt_02.h +++ b/applications/plugins/weather_station/protocols/gt_wt_02.h @@ -56,9 +56,9 @@ uint8_t ws_protocol_decoder_gt_wt_02_get_hash_data(void* context); * @param context Pointer to a WSProtocolDecoderGT_WT02 instance * @param flipper_format Pointer to a FlipperFormat instance * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return true On success + * @return status */ -bool ws_protocol_decoder_gt_wt_02_serialize( +SubGhzProtocolStatus ws_protocol_decoder_gt_wt_02_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset); @@ -67,9 +67,10 @@ bool ws_protocol_decoder_gt_wt_02_serialize( * Deserialize data WSProtocolDecoderGT_WT02. * @param context Pointer to a WSProtocolDecoderGT_WT02 instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool ws_protocol_decoder_gt_wt_02_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + ws_protocol_decoder_gt_wt_02_deserialize(void* context, FlipperFormat* flipper_format); /** * Getting a textual representation of the received data. diff --git a/applications/plugins/weather_station/protocols/gt_wt_03.c b/applications/plugins/weather_station/protocols/gt_wt_03.c index 7831cf06950..30430b83904 100644 --- a/applications/plugins/weather_station/protocols/gt_wt_03.c +++ b/applications/plugins/weather_station/protocols/gt_wt_03.c @@ -292,7 +292,7 @@ uint8_t ws_protocol_decoder_gt_wt_03_get_hash_data(void* context) { &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } -bool ws_protocol_decoder_gt_wt_03_serialize( +SubGhzProtocolStatus ws_protocol_decoder_gt_wt_03_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset) { @@ -301,22 +301,12 @@ bool ws_protocol_decoder_gt_wt_03_serialize( return ws_block_generic_serialize(&instance->generic, flipper_format, preset); } -bool ws_protocol_decoder_gt_wt_03_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + ws_protocol_decoder_gt_wt_03_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); WSProtocolDecoderGT_WT03* instance = context; - bool ret = false; - do { - if(!ws_block_generic_deserialize(&instance->generic, flipper_format)) { - break; - } - if(instance->generic.data_count_bit != - ws_protocol_gt_wt_03_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); - break; - } - ret = true; - } while(false); - return ret; + return ws_block_generic_deserialize_check_count_bit( + &instance->generic, flipper_format, ws_protocol_gt_wt_03_const.min_count_bit_for_found); } void ws_protocol_decoder_gt_wt_03_get_string(void* context, FuriString* output) { diff --git a/applications/plugins/weather_station/protocols/gt_wt_03.h b/applications/plugins/weather_station/protocols/gt_wt_03.h index fd9536e342c..d566bb399c7 100644 --- a/applications/plugins/weather_station/protocols/gt_wt_03.h +++ b/applications/plugins/weather_station/protocols/gt_wt_03.h @@ -56,9 +56,9 @@ uint8_t ws_protocol_decoder_gt_wt_03_get_hash_data(void* context); * @param context Pointer to a WSProtocolDecoderGT_WT03 instance * @param flipper_format Pointer to a FlipperFormat instance * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return true On success + * @return status */ -bool ws_protocol_decoder_gt_wt_03_serialize( +SubGhzProtocolStatus ws_protocol_decoder_gt_wt_03_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset); @@ -67,9 +67,10 @@ bool ws_protocol_decoder_gt_wt_03_serialize( * Deserialize data WSProtocolDecoderGT_WT03. * @param context Pointer to a WSProtocolDecoderGT_WT03 instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool ws_protocol_decoder_gt_wt_03_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + ws_protocol_decoder_gt_wt_03_deserialize(void* context, FlipperFormat* flipper_format); /** * Getting a textual representation of the received data. diff --git a/applications/plugins/weather_station/protocols/infactory.c b/applications/plugins/weather_station/protocols/infactory.c index 53a656d738f..4b0e6602a53 100644 --- a/applications/plugins/weather_station/protocols/infactory.c +++ b/applications/plugins/weather_station/protocols/infactory.c @@ -248,7 +248,7 @@ uint8_t ws_protocol_decoder_infactory_get_hash_data(void* context) { &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } -bool ws_protocol_decoder_infactory_serialize( +SubGhzProtocolStatus ws_protocol_decoder_infactory_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset) { @@ -257,22 +257,12 @@ bool ws_protocol_decoder_infactory_serialize( return ws_block_generic_serialize(&instance->generic, flipper_format, preset); } -bool ws_protocol_decoder_infactory_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + ws_protocol_decoder_infactory_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); WSProtocolDecoderInfactory* instance = context; - bool ret = false; - do { - if(!ws_block_generic_deserialize(&instance->generic, flipper_format)) { - break; - } - if(instance->generic.data_count_bit != - ws_protocol_infactory_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); - break; - } - ret = true; - } while(false); - return ret; + return ws_block_generic_deserialize_check_count_bit( + &instance->generic, flipper_format, ws_protocol_infactory_const.min_count_bit_for_found); } void ws_protocol_decoder_infactory_get_string(void* context, FuriString* output) { diff --git a/applications/plugins/weather_station/protocols/infactory.h b/applications/plugins/weather_station/protocols/infactory.h index 2792ab98733..9431a067ea9 100644 --- a/applications/plugins/weather_station/protocols/infactory.h +++ b/applications/plugins/weather_station/protocols/infactory.h @@ -56,9 +56,9 @@ uint8_t ws_protocol_decoder_infactory_get_hash_data(void* context); * @param context Pointer to a WSProtocolDecoderInfactory instance * @param flipper_format Pointer to a FlipperFormat instance * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return true On success + * @return status */ -bool ws_protocol_decoder_infactory_serialize( +SubGhzProtocolStatus ws_protocol_decoder_infactory_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset); @@ -67,9 +67,10 @@ bool ws_protocol_decoder_infactory_serialize( * Deserialize data WSProtocolDecoderInfactory. * @param context Pointer to a WSProtocolDecoderInfactory instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool ws_protocol_decoder_infactory_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + ws_protocol_decoder_infactory_deserialize(void* context, FlipperFormat* flipper_format); /** * Getting a textual representation of the received data. diff --git a/applications/plugins/weather_station/protocols/lacrosse_tx.c b/applications/plugins/weather_station/protocols/lacrosse_tx.c index 8d8a24e244a..f4b850d7693 100644 --- a/applications/plugins/weather_station/protocols/lacrosse_tx.c +++ b/applications/plugins/weather_station/protocols/lacrosse_tx.c @@ -281,7 +281,7 @@ uint8_t ws_protocol_decoder_lacrosse_tx_get_hash_data(void* context) { &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } -bool ws_protocol_decoder_lacrosse_tx_serialize( +SubGhzProtocolStatus ws_protocol_decoder_lacrosse_tx_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset) { @@ -290,22 +290,12 @@ bool ws_protocol_decoder_lacrosse_tx_serialize( return ws_block_generic_serialize(&instance->generic, flipper_format, preset); } -bool ws_protocol_decoder_lacrosse_tx_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + ws_protocol_decoder_lacrosse_tx_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); WSProtocolDecoderLaCrosse_TX* instance = context; - bool ret = false; - do { - if(!ws_block_generic_deserialize(&instance->generic, flipper_format)) { - break; - } - if(instance->generic.data_count_bit != - ws_protocol_lacrosse_tx_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); - break; - } - ret = true; - } while(false); - return ret; + return ws_block_generic_deserialize_check_count_bit( + &instance->generic, flipper_format, ws_protocol_lacrosse_tx_const.min_count_bit_for_found); } void ws_protocol_decoder_lacrosse_tx_get_string(void* context, FuriString* output) { diff --git a/applications/plugins/weather_station/protocols/lacrosse_tx.h b/applications/plugins/weather_station/protocols/lacrosse_tx.h index e884556897e..151282b3a07 100644 --- a/applications/plugins/weather_station/protocols/lacrosse_tx.h +++ b/applications/plugins/weather_station/protocols/lacrosse_tx.h @@ -56,9 +56,9 @@ uint8_t ws_protocol_decoder_lacrosse_tx_get_hash_data(void* context); * @param context Pointer to a WSProtocolDecoderLaCrosse_TX instance * @param flipper_format Pointer to a FlipperFormat instance * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return true On success + * @return status */ -bool ws_protocol_decoder_lacrosse_tx_serialize( +SubGhzProtocolStatus ws_protocol_decoder_lacrosse_tx_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset); @@ -67,9 +67,10 @@ bool ws_protocol_decoder_lacrosse_tx_serialize( * Deserialize data WSProtocolDecoderLaCrosse_TX. * @param context Pointer to a WSProtocolDecoderLaCrosse_TX instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool ws_protocol_decoder_lacrosse_tx_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + ws_protocol_decoder_lacrosse_tx_deserialize(void* context, FlipperFormat* flipper_format); /** * Getting a textual representation of the received data. diff --git a/applications/plugins/weather_station/protocols/lacrosse_tx141thbv2.c b/applications/plugins/weather_station/protocols/lacrosse_tx141thbv2.c index e4b61225014..5d007b12fbc 100644 --- a/applications/plugins/weather_station/protocols/lacrosse_tx141thbv2.c +++ b/applications/plugins/weather_station/protocols/lacrosse_tx141thbv2.c @@ -247,7 +247,7 @@ uint8_t ws_protocol_decoder_lacrosse_tx141thbv2_get_hash_data(void* context) { &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } -bool ws_protocol_decoder_lacrosse_tx141thbv2_serialize( +SubGhzProtocolStatus ws_protocol_decoder_lacrosse_tx141thbv2_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset) { @@ -256,24 +256,15 @@ bool ws_protocol_decoder_lacrosse_tx141thbv2_serialize( return ws_block_generic_serialize(&instance->generic, flipper_format, preset); } -bool ws_protocol_decoder_lacrosse_tx141thbv2_deserialize( +SubGhzProtocolStatus ws_protocol_decoder_lacrosse_tx141thbv2_deserialize( void* context, FlipperFormat* flipper_format) { furi_assert(context); WSProtocolDecoderLaCrosse_TX141THBv2* instance = context; - bool ret = false; - do { - if(!ws_block_generic_deserialize(&instance->generic, flipper_format)) { - break; - } - if(instance->generic.data_count_bit != - ws_protocol_lacrosse_tx141thbv2_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); - break; - } - ret = true; - } while(false); - return ret; + return ws_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + ws_protocol_lacrosse_tx141thbv2_const.min_count_bit_for_found); } void ws_protocol_decoder_lacrosse_tx141thbv2_get_string(void* context, FuriString* output) { diff --git a/applications/plugins/weather_station/protocols/lacrosse_tx141thbv2.h b/applications/plugins/weather_station/protocols/lacrosse_tx141thbv2.h index 941b0105814..036db05481e 100644 --- a/applications/plugins/weather_station/protocols/lacrosse_tx141thbv2.h +++ b/applications/plugins/weather_station/protocols/lacrosse_tx141thbv2.h @@ -56,9 +56,9 @@ uint8_t ws_protocol_decoder_lacrosse_tx141thbv2_get_hash_data(void* context); * @param context Pointer to a WSProtocolDecoderLaCrosse_TX141THBv2 instance * @param flipper_format Pointer to a FlipperFormat instance * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return true On success + * @return status */ -bool ws_protocol_decoder_lacrosse_tx141thbv2_serialize( +SubGhzProtocolStatus ws_protocol_decoder_lacrosse_tx141thbv2_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset); @@ -67,9 +67,9 @@ bool ws_protocol_decoder_lacrosse_tx141thbv2_serialize( * Deserialize data WSProtocolDecoderLaCrosse_TX141THBv2. * @param context Pointer to a WSProtocolDecoderLaCrosse_TX141THBv2 instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool ws_protocol_decoder_lacrosse_tx141thbv2_deserialize( +SubGhzProtocolStatus ws_protocol_decoder_lacrosse_tx141thbv2_deserialize( void* context, FlipperFormat* flipper_format); diff --git a/applications/plugins/weather_station/protocols/nexus_th.c b/applications/plugins/weather_station/protocols/nexus_th.c index 38f2fe895cb..14ba8b273a6 100644 --- a/applications/plugins/weather_station/protocols/nexus_th.c +++ b/applications/plugins/weather_station/protocols/nexus_th.c @@ -216,7 +216,7 @@ uint8_t ws_protocol_decoder_nexus_th_get_hash_data(void* context) { &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } -bool ws_protocol_decoder_nexus_th_serialize( +SubGhzProtocolStatus ws_protocol_decoder_nexus_th_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset) { @@ -225,22 +225,12 @@ bool ws_protocol_decoder_nexus_th_serialize( return ws_block_generic_serialize(&instance->generic, flipper_format, preset); } -bool ws_protocol_decoder_nexus_th_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + ws_protocol_decoder_nexus_th_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); WSProtocolDecoderNexus_TH* instance = context; - bool ret = false; - do { - if(!ws_block_generic_deserialize(&instance->generic, flipper_format)) { - break; - } - if(instance->generic.data_count_bit != - ws_protocol_nexus_th_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); - break; - } - ret = true; - } while(false); - return ret; + return ws_block_generic_deserialize_check_count_bit( + &instance->generic, flipper_format, ws_protocol_nexus_th_const.min_count_bit_for_found); } void ws_protocol_decoder_nexus_th_get_string(void* context, FuriString* output) { diff --git a/applications/plugins/weather_station/protocols/nexus_th.h b/applications/plugins/weather_station/protocols/nexus_th.h index 5450f0040ee..6c2715b8511 100644 --- a/applications/plugins/weather_station/protocols/nexus_th.h +++ b/applications/plugins/weather_station/protocols/nexus_th.h @@ -56,9 +56,9 @@ uint8_t ws_protocol_decoder_nexus_th_get_hash_data(void* context); * @param context Pointer to a WSProtocolDecoderNexus_TH instance * @param flipper_format Pointer to a FlipperFormat instance * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return true On success + * @return status */ -bool ws_protocol_decoder_nexus_th_serialize( +SubGhzProtocolStatus ws_protocol_decoder_nexus_th_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset); @@ -67,9 +67,10 @@ bool ws_protocol_decoder_nexus_th_serialize( * Deserialize data WSProtocolDecoderNexus_TH. * @param context Pointer to a WSProtocolDecoderNexus_TH instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool ws_protocol_decoder_nexus_th_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + ws_protocol_decoder_nexus_th_deserialize(void* context, FlipperFormat* flipper_format); /** * Getting a textual representation of the received data. diff --git a/applications/plugins/weather_station/protocols/oregon2.c b/applications/plugins/weather_station/protocols/oregon2.c index 8ca80bbe255..313748ccfb1 100644 --- a/applications/plugins/weather_station/protocols/oregon2.c +++ b/applications/plugins/weather_station/protocols/oregon2.c @@ -302,17 +302,19 @@ uint8_t ws_protocol_decoder_oregon2_get_hash_data(void* context) { &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } -bool ws_protocol_decoder_oregon2_serialize( +SubGhzProtocolStatus ws_protocol_decoder_oregon2_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset) { furi_assert(context); WSProtocolDecoderOregon2* instance = context; - if(!ws_block_generic_serialize(&instance->generic, flipper_format, preset)) return false; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; + ret = ws_block_generic_serialize(&instance->generic, flipper_format, preset); + if(ret != SubGhzProtocolStatusOk) return ret; uint32_t temp = instance->var_bits; if(!flipper_format_write_uint32(flipper_format, "VarBits", &temp, 1)) { FURI_LOG_E(TAG, "Error adding VarBits"); - return false; + return SubGhzProtocolStatusErrorParserOthers; } if(!flipper_format_write_hex( flipper_format, @@ -320,22 +322,25 @@ bool ws_protocol_decoder_oregon2_serialize( (const uint8_t*)&instance->var_data, sizeof(instance->var_data))) { FURI_LOG_E(TAG, "Error adding VarData"); - return false; + return SubGhzProtocolStatusErrorParserOthers; } - return true; + return ret; } -bool ws_protocol_decoder_oregon2_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + ws_protocol_decoder_oregon2_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); WSProtocolDecoderOregon2* instance = context; - bool ret = false; uint32_t temp_data; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; do { - if(!ws_block_generic_deserialize(&instance->generic, flipper_format)) { + ret = ws_block_generic_deserialize(&instance->generic, flipper_format); + if(ret != SubGhzProtocolStatusOk) { break; } if(!flipper_format_read_uint32(flipper_format, "VarBits", &temp_data, 1)) { FURI_LOG_E(TAG, "Missing VarLen"); + ret = SubGhzProtocolStatusErrorParserOthers; break; } instance->var_bits = (uint8_t)temp_data; @@ -345,13 +350,14 @@ bool ws_protocol_decoder_oregon2_deserialize(void* context, FlipperFormat* flipp (uint8_t*)&instance->var_data, sizeof(instance->var_data))) { //-V1051 FURI_LOG_E(TAG, "Missing VarData"); + ret = SubGhzProtocolStatusErrorParserOthers; break; } if(instance->generic.data_count_bit != ws_oregon2_const.min_count_bit_for_found) { FURI_LOG_E(TAG, "Wrong number of bits in key: %d", instance->generic.data_count_bit); + ret = SubGhzProtocolStatusErrorValueBitCount; break; } - ret = true; } while(false); return ret; } diff --git a/applications/plugins/weather_station/protocols/oregon_v1.c b/applications/plugins/weather_station/protocols/oregon_v1.c index 1ed9da205c5..03215bbf5fa 100644 --- a/applications/plugins/weather_station/protocols/oregon_v1.c +++ b/applications/plugins/weather_station/protocols/oregon_v1.c @@ -283,7 +283,7 @@ uint8_t ws_protocol_decoder_oregon_v1_get_hash_data(void* context) { &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } -bool ws_protocol_decoder_oregon_v1_serialize( +SubGhzProtocolStatus ws_protocol_decoder_oregon_v1_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset) { @@ -292,22 +292,12 @@ bool ws_protocol_decoder_oregon_v1_serialize( return ws_block_generic_serialize(&instance->generic, flipper_format, preset); } -bool ws_protocol_decoder_oregon_v1_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + ws_protocol_decoder_oregon_v1_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); WSProtocolDecoderOregon_V1* instance = context; - bool ret = false; - do { - if(!ws_block_generic_deserialize(&instance->generic, flipper_format)) { - break; - } - if(instance->generic.data_count_bit != - ws_protocol_oregon_v1_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); - break; - } - ret = true; - } while(false); - return ret; + return ws_block_generic_deserialize_check_count_bit( + &instance->generic, flipper_format, ws_protocol_oregon_v1_const.min_count_bit_for_found); } void ws_protocol_decoder_oregon_v1_get_string(void* context, FuriString* output) { diff --git a/applications/plugins/weather_station/protocols/oregon_v1.h b/applications/plugins/weather_station/protocols/oregon_v1.h index c9aa5af48a9..48937601deb 100644 --- a/applications/plugins/weather_station/protocols/oregon_v1.h +++ b/applications/plugins/weather_station/protocols/oregon_v1.h @@ -56,9 +56,9 @@ uint8_t ws_protocol_decoder_oregon_v1_get_hash_data(void* context); * @param context Pointer to a WSProtocolDecoderOregon_V1 instance * @param flipper_format Pointer to a FlipperFormat instance * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return true On success + * @return status */ -bool ws_protocol_decoder_oregon_v1_serialize( +SubGhzProtocolStatus ws_protocol_decoder_oregon_v1_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset); @@ -67,9 +67,10 @@ bool ws_protocol_decoder_oregon_v1_serialize( * Deserialize data WSProtocolDecoderOregon_V1. * @param context Pointer to a WSProtocolDecoderOregon_V1 instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool ws_protocol_decoder_oregon_v1_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + ws_protocol_decoder_oregon_v1_deserialize(void* context, FlipperFormat* flipper_format); /** * Getting a textual representation of the received data. diff --git a/applications/plugins/weather_station/protocols/thermopro_tx4.c b/applications/plugins/weather_station/protocols/thermopro_tx4.c index 0882bc33d74..24e883e607b 100644 --- a/applications/plugins/weather_station/protocols/thermopro_tx4.c +++ b/applications/plugins/weather_station/protocols/thermopro_tx4.c @@ -211,7 +211,7 @@ uint8_t ws_protocol_decoder_thermopro_tx4_get_hash_data(void* context) { &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } -bool ws_protocol_decoder_thermopro_tx4_serialize( +SubGhzProtocolStatus ws_protocol_decoder_thermopro_tx4_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset) { @@ -220,22 +220,14 @@ bool ws_protocol_decoder_thermopro_tx4_serialize( return ws_block_generic_serialize(&instance->generic, flipper_format, preset); } -bool ws_protocol_decoder_thermopro_tx4_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + ws_protocol_decoder_thermopro_tx4_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); WSProtocolDecoderThermoPRO_TX4* instance = context; - bool ret = false; - do { - if(!ws_block_generic_deserialize(&instance->generic, flipper_format)) { - break; - } - if(instance->generic.data_count_bit != - ws_protocol_thermopro_tx4_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); - break; - } - ret = true; - } while(false); - return ret; + return ws_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + ws_protocol_thermopro_tx4_const.min_count_bit_for_found); } void ws_protocol_decoder_thermopro_tx4_get_string(void* context, FuriString* output) { diff --git a/applications/plugins/weather_station/protocols/thermopro_tx4.h b/applications/plugins/weather_station/protocols/thermopro_tx4.h index 1feae58d301..526648d1eeb 100644 --- a/applications/plugins/weather_station/protocols/thermopro_tx4.h +++ b/applications/plugins/weather_station/protocols/thermopro_tx4.h @@ -56,9 +56,9 @@ uint8_t ws_protocol_decoder_thermopro_tx4_get_hash_data(void* context); * @param context Pointer to a WSProtocolDecoderThermoPRO_TX4 instance * @param flipper_format Pointer to a FlipperFormat instance * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return true On success + * @return status */ -bool ws_protocol_decoder_thermopro_tx4_serialize( +SubGhzProtocolStatus ws_protocol_decoder_thermopro_tx4_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset); @@ -67,9 +67,10 @@ bool ws_protocol_decoder_thermopro_tx4_serialize( * Deserialize data WSProtocolDecoderThermoPRO_TX4. * @param context Pointer to a WSProtocolDecoderThermoPRO_TX4 instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool ws_protocol_decoder_thermopro_tx4_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + ws_protocol_decoder_thermopro_tx4_deserialize(void* context, FlipperFormat* flipper_format); /** * Getting a textual representation of the received data. diff --git a/applications/plugins/weather_station/protocols/tx_8300.c b/applications/plugins/weather_station/protocols/tx_8300.c index ee0412ba9cc..3a06ce49dec 100644 --- a/applications/plugins/weather_station/protocols/tx_8300.c +++ b/applications/plugins/weather_station/protocols/tx_8300.c @@ -246,7 +246,7 @@ uint8_t ws_protocol_decoder_tx_8300_get_hash_data(void* context) { &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } -bool ws_protocol_decoder_tx_8300_serialize( +SubGhzProtocolStatus ws_protocol_decoder_tx_8300_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset) { @@ -255,21 +255,12 @@ bool ws_protocol_decoder_tx_8300_serialize( return ws_block_generic_serialize(&instance->generic, flipper_format, preset); } -bool ws_protocol_decoder_tx_8300_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + ws_protocol_decoder_tx_8300_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); WSProtocolDecoderTX_8300* instance = context; - bool ret = false; - do { - if(!ws_block_generic_deserialize(&instance->generic, flipper_format)) { - break; - } - if(instance->generic.data_count_bit != ws_protocol_tx_8300_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); - break; - } - ret = true; - } while(false); - return ret; + return ws_block_generic_deserialize_check_count_bit( + &instance->generic, flipper_format, ws_protocol_tx_8300_const.min_count_bit_for_found); } void ws_protocol_decoder_tx_8300_get_string(void* context, FuriString* output) { diff --git a/applications/plugins/weather_station/protocols/tx_8300.h b/applications/plugins/weather_station/protocols/tx_8300.h index ec198e80fa9..088ccd7c034 100644 --- a/applications/plugins/weather_station/protocols/tx_8300.h +++ b/applications/plugins/weather_station/protocols/tx_8300.h @@ -56,9 +56,9 @@ uint8_t ws_protocol_decoder_tx_8300_get_hash_data(void* context); * @param context Pointer to a WSProtocolDecoderTX_8300 instance * @param flipper_format Pointer to a FlipperFormat instance * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return true On success + * @return status */ -bool ws_protocol_decoder_tx_8300_serialize( +SubGhzProtocolStatus ws_protocol_decoder_tx_8300_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset); @@ -67,9 +67,10 @@ bool ws_protocol_decoder_tx_8300_serialize( * Deserialize data WSProtocolDecoderTX_8300. * @param context Pointer to a WSProtocolDecoderTX_8300 instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool ws_protocol_decoder_tx_8300_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + ws_protocol_decoder_tx_8300_deserialize(void* context, FlipperFormat* flipper_format); /** * Getting a textual representation of the received data. diff --git a/applications/plugins/weather_station/protocols/ws_generic.c b/applications/plugins/weather_station/protocols/ws_generic.c index 8a88ed52f3d..026856a9eb5 100644 --- a/applications/plugins/weather_station/protocols/ws_generic.c +++ b/applications/plugins/weather_station/protocols/ws_generic.c @@ -21,12 +21,12 @@ void ws_block_generic_get_preset_name(const char* preset_name, FuriString* prese furi_string_set(preset_str, preset_name_temp); } -bool ws_block_generic_serialize( +SubGhzProtocolStatus ws_block_generic_serialize( WSBlockGeneric* instance, FlipperFormat* flipper_format, SubGhzRadioPreset* preset) { furi_assert(instance); - bool res = false; + SubGhzProtocolStatus res = SubGhzProtocolStatusError; FuriString* temp_str; temp_str = furi_string_alloc(); do { @@ -34,11 +34,13 @@ bool ws_block_generic_serialize( if(!flipper_format_write_header_cstr( flipper_format, WS_KEY_FILE_TYPE, WS_KEY_FILE_VERSION)) { FURI_LOG_E(TAG, "Unable to add header"); + res = SubGhzProtocolStatusErrorParserHeader; break; } if(!flipper_format_write_uint32(flipper_format, "Frequency", &preset->frequency, 1)) { FURI_LOG_E(TAG, "Unable to add Frequency"); + res = SubGhzProtocolStatusErrorParserFrequency; break; } @@ -46,34 +48,40 @@ bool ws_block_generic_serialize( if(!flipper_format_write_string_cstr( flipper_format, "Preset", furi_string_get_cstr(temp_str))) { FURI_LOG_E(TAG, "Unable to add Preset"); + res = SubGhzProtocolStatusErrorParserPreset; break; } if(!strcmp(furi_string_get_cstr(temp_str), "FuriHalSubGhzPresetCustom")) { if(!flipper_format_write_string_cstr( flipper_format, "Custom_preset_module", "CC1101")) { FURI_LOG_E(TAG, "Unable to add Custom_preset_module"); + res = SubGhzProtocolStatusErrorParserCustomPreset; break; } if(!flipper_format_write_hex( flipper_format, "Custom_preset_data", preset->data, preset->data_size)) { FURI_LOG_E(TAG, "Unable to add Custom_preset_data"); + res = SubGhzProtocolStatusErrorParserCustomPreset; break; } } if(!flipper_format_write_string_cstr(flipper_format, "Protocol", instance->protocol_name)) { FURI_LOG_E(TAG, "Unable to add Protocol"); + res = SubGhzProtocolStatusErrorParserProtocolName; break; } uint32_t temp_data = instance->id; if(!flipper_format_write_uint32(flipper_format, "Id", &temp_data, 1)) { FURI_LOG_E(TAG, "Unable to add Id"); + res = SubGhzProtocolStatusErrorParserOthers; break; } temp_data = instance->data_count_bit; if(!flipper_format_write_uint32(flipper_format, "Bit", &temp_data, 1)) { FURI_LOG_E(TAG, "Unable to add Bit"); + res = SubGhzProtocolStatusErrorParserBitCount; break; } @@ -84,18 +92,21 @@ bool ws_block_generic_serialize( if(!flipper_format_write_hex(flipper_format, "Data", key_data, sizeof(uint64_t))) { FURI_LOG_E(TAG, "Unable to add Data"); + res = SubGhzProtocolStatusErrorParserOthers; break; } temp_data = instance->battery_low; if(!flipper_format_write_uint32(flipper_format, "Batt", &temp_data, 1)) { FURI_LOG_E(TAG, "Unable to add Battery_low"); + res = SubGhzProtocolStatusErrorParserOthers; break; } temp_data = instance->humidity; if(!flipper_format_write_uint32(flipper_format, "Hum", &temp_data, 1)) { FURI_LOG_E(TAG, "Unable to add Humidity"); + res = SubGhzProtocolStatusErrorParserOthers; break; } @@ -107,52 +118,60 @@ bool ws_block_generic_serialize( temp_data = curr_ts; if(!flipper_format_write_uint32(flipper_format, "Ts", &temp_data, 1)) { FURI_LOG_E(TAG, "Unable to add timestamp"); + res = SubGhzProtocolStatusErrorParserOthers; break; } temp_data = instance->channel; if(!flipper_format_write_uint32(flipper_format, "Ch", &temp_data, 1)) { FURI_LOG_E(TAG, "Unable to add Channel"); + res = SubGhzProtocolStatusErrorParserOthers; break; } temp_data = instance->btn; if(!flipper_format_write_uint32(flipper_format, "Btn", &temp_data, 1)) { FURI_LOG_E(TAG, "Unable to add Btn"); + res = SubGhzProtocolStatusErrorParserOthers; break; } float temp = instance->temp; if(!flipper_format_write_float(flipper_format, "Temp", &temp, 1)) { FURI_LOG_E(TAG, "Unable to add Temperature"); + res = SubGhzProtocolStatusErrorParserOthers; break; } - res = true; + res = SubGhzProtocolStatusOk; } while(false); furi_string_free(temp_str); return res; } -bool ws_block_generic_deserialize(WSBlockGeneric* instance, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + ws_block_generic_deserialize(WSBlockGeneric* instance, FlipperFormat* flipper_format) { furi_assert(instance); - bool res = false; + SubGhzProtocolStatus res = SubGhzProtocolStatusError; uint32_t temp_data = 0; do { if(!flipper_format_rewind(flipper_format)) { FURI_LOG_E(TAG, "Rewind error"); + res = SubGhzProtocolStatusErrorParserOthers; break; } if(!flipper_format_read_uint32(flipper_format, "Id", (uint32_t*)&temp_data, 1)) { FURI_LOG_E(TAG, "Missing Id"); + res = SubGhzProtocolStatusErrorParserOthers; break; } instance->id = (uint32_t)temp_data; if(!flipper_format_read_uint32(flipper_format, "Bit", (uint32_t*)&temp_data, 1)) { FURI_LOG_E(TAG, "Missing Bit"); + res = SubGhzProtocolStatusErrorParserBitCount; break; } instance->data_count_bit = (uint8_t)temp_data; @@ -160,6 +179,7 @@ bool ws_block_generic_deserialize(WSBlockGeneric* instance, FlipperFormat* flipp uint8_t key_data[sizeof(uint64_t)] = {0}; if(!flipper_format_read_hex(flipper_format, "Data", key_data, sizeof(uint64_t))) { FURI_LOG_E(TAG, "Missing Data"); + res = SubGhzProtocolStatusErrorParserOthers; break; } @@ -169,30 +189,35 @@ bool ws_block_generic_deserialize(WSBlockGeneric* instance, FlipperFormat* flipp if(!flipper_format_read_uint32(flipper_format, "Batt", (uint32_t*)&temp_data, 1)) { FURI_LOG_E(TAG, "Missing Battery_low"); + res = SubGhzProtocolStatusErrorParserOthers; break; } instance->battery_low = (uint8_t)temp_data; if(!flipper_format_read_uint32(flipper_format, "Hum", (uint32_t*)&temp_data, 1)) { FURI_LOG_E(TAG, "Missing Humidity"); + res = SubGhzProtocolStatusErrorParserOthers; break; } instance->humidity = (uint8_t)temp_data; if(!flipper_format_read_uint32(flipper_format, "Ts", (uint32_t*)&temp_data, 1)) { FURI_LOG_E(TAG, "Missing timestamp"); + res = SubGhzProtocolStatusErrorParserOthers; break; } instance->timestamp = (uint32_t)temp_data; if(!flipper_format_read_uint32(flipper_format, "Ch", (uint32_t*)&temp_data, 1)) { FURI_LOG_E(TAG, "Missing Channel"); + res = SubGhzProtocolStatusErrorParserOthers; break; } instance->channel = (uint8_t)temp_data; if(!flipper_format_read_uint32(flipper_format, "Btn", (uint32_t*)&temp_data, 1)) { FURI_LOG_E(TAG, "Missing Btn"); + res = SubGhzProtocolStatusErrorParserOthers; break; } instance->btn = (uint8_t)temp_data; @@ -200,12 +225,32 @@ bool ws_block_generic_deserialize(WSBlockGeneric* instance, FlipperFormat* flipp float temp; if(!flipper_format_read_float(flipper_format, "Temp", (float*)&temp, 1)) { FURI_LOG_E(TAG, "Missing Temperature"); + res = SubGhzProtocolStatusErrorParserOthers; break; } instance->temp = temp; - res = true; + res = SubGhzProtocolStatusOk; } while(0); return res; +} + +SubGhzProtocolStatus ws_block_generic_deserialize_check_count_bit( + WSBlockGeneric* instance, + FlipperFormat* flipper_format, + uint16_t count_bit) { + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; + do { + ret = ws_block_generic_deserialize(instance, flipper_format); + if(ret != SubGhzProtocolStatusOk) { + break; + } + if(instance->data_count_bit != count_bit) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + ret = SubGhzProtocolStatusErrorValueBitCount; + break; + } + } while(false); + return ret; } \ No newline at end of file diff --git a/applications/plugins/weather_station/protocols/ws_generic.h b/applications/plugins/weather_station/protocols/ws_generic.h index 47cfa74b3a8..ff047fae654 100644 --- a/applications/plugins/weather_station/protocols/ws_generic.h +++ b/applications/plugins/weather_station/protocols/ws_generic.h @@ -48,9 +48,9 @@ void ws_block_generic_get_preset_name(const char* preset_name, FuriString* prese * @param instance Pointer to a WSBlockGeneric instance * @param flipper_format Pointer to a FlipperFormat instance * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return true On success + * @return status */ -bool ws_block_generic_serialize( +SubGhzProtocolStatus ws_block_generic_serialize( WSBlockGeneric* instance, FlipperFormat* flipper_format, SubGhzRadioPreset* preset); @@ -59,9 +59,22 @@ bool ws_block_generic_serialize( * Deserialize data WSBlockGeneric. * @param instance Pointer to a WSBlockGeneric instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool ws_block_generic_deserialize(WSBlockGeneric* instance, FlipperFormat* flipper_format); +SubGhzProtocolStatus + ws_block_generic_deserialize(WSBlockGeneric* instance, FlipperFormat* flipper_format); + +/** + * Deserialize data WSBlockGeneric. + * @param instance Pointer to a WSBlockGeneric instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param count_bit Count bit protocol + * @return status + */ +SubGhzProtocolStatus ws_block_generic_deserialize_check_count_bit( + WSBlockGeneric* instance, + FlipperFormat* flipper_format, + uint16_t count_bit); #ifdef __cplusplus } diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index f306deb8cd6..608f5b19e31 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,16.0,, +Version,+,17.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index f896dd1b319..1c35abd3bf3 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,16.0,, +Version,+,17.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -2588,9 +2588,10 @@ Function,-,strupr,char*,char* Function,-,strverscmp,int,"const char*, const char*" Function,-,strxfrm,size_t,"char*, const char*, size_t" Function,-,strxfrm_l,size_t,"char*, const char*, size_t, locale_t" -Function,+,subghz_block_generic_deserialize,_Bool,"SubGhzBlockGeneric*, FlipperFormat*" +Function,+,subghz_block_generic_deserialize,SubGhzProtocolStatus,"SubGhzBlockGeneric*, FlipperFormat*" +Function,+,subghz_block_generic_deserialize_check_count_bit,SubGhzProtocolStatus,"SubGhzBlockGeneric*, FlipperFormat*, uint16_t" Function,+,subghz_block_generic_get_preset_name,void,"const char*, FuriString*" -Function,+,subghz_block_generic_serialize,_Bool,"SubGhzBlockGeneric*, FlipperFormat*, SubGhzRadioPreset*" +Function,+,subghz_block_generic_serialize,SubGhzProtocolStatus,"SubGhzBlockGeneric*, FlipperFormat*, SubGhzRadioPreset*" Function,+,subghz_environment_alloc,SubGhzEnvironment*, Function,+,subghz_environment_free,void,SubGhzEnvironment* Function,+,subghz_environment_get_alutech_at_4n_rainbow_table_file_name,const char*,SubGhzEnvironment* @@ -2632,19 +2633,19 @@ Function,+,subghz_protocol_blocks_parity_bytes,uint8_t,"const uint8_t[], size_t" Function,+,subghz_protocol_blocks_reverse_key,uint64_t,"uint64_t, uint8_t" Function,+,subghz_protocol_blocks_set_bit_array,void,"_Bool, uint8_t[], size_t, size_t" Function,+,subghz_protocol_blocks_xor_bytes,uint8_t,"const uint8_t[], size_t" -Function,-,subghz_protocol_decoder_base_deserialize,_Bool,"SubGhzProtocolDecoderBase*, FlipperFormat*" +Function,+,subghz_protocol_decoder_base_deserialize,SubGhzProtocolStatus,"SubGhzProtocolDecoderBase*, FlipperFormat*" Function,+,subghz_protocol_decoder_base_get_hash_data,uint8_t,SubGhzProtocolDecoderBase* Function,+,subghz_protocol_decoder_base_get_string,_Bool,"SubGhzProtocolDecoderBase*, FuriString*" -Function,+,subghz_protocol_decoder_base_serialize,_Bool,"SubGhzProtocolDecoderBase*, FlipperFormat*, SubGhzRadioPreset*" +Function,+,subghz_protocol_decoder_base_serialize,SubGhzProtocolStatus,"SubGhzProtocolDecoderBase*, FlipperFormat*, SubGhzRadioPreset*" Function,-,subghz_protocol_decoder_base_set_decoder_callback,void,"SubGhzProtocolDecoderBase*, SubGhzProtocolDecoderBaseRxCallback, void*" Function,+,subghz_protocol_decoder_raw_alloc,void*,SubGhzEnvironment* -Function,+,subghz_protocol_decoder_raw_deserialize,_Bool,"void*, FlipperFormat*" +Function,+,subghz_protocol_decoder_raw_deserialize,SubGhzProtocolStatus,"void*, FlipperFormat*" Function,+,subghz_protocol_decoder_raw_feed,void,"void*, _Bool, uint32_t" Function,+,subghz_protocol_decoder_raw_free,void,void* Function,+,subghz_protocol_decoder_raw_get_string,void,"void*, FuriString*" Function,+,subghz_protocol_decoder_raw_reset,void,void* Function,+,subghz_protocol_encoder_raw_alloc,void*,SubGhzEnvironment* -Function,+,subghz_protocol_encoder_raw_deserialize,_Bool,"void*, FlipperFormat*" +Function,+,subghz_protocol_encoder_raw_deserialize,SubGhzProtocolStatus,"void*, FlipperFormat*" Function,+,subghz_protocol_encoder_raw_free,void,void* Function,+,subghz_protocol_encoder_raw_stop,void,void* Function,+,subghz_protocol_encoder_raw_yield,LevelDuration,void* @@ -2682,7 +2683,7 @@ Function,+,subghz_setting_get_preset_name,const char*,"SubGhzSetting*, size_t" Function,+,subghz_setting_load,void,"SubGhzSetting*, const char*" Function,+,subghz_setting_load_custom_preset,_Bool,"SubGhzSetting*, const char*, FlipperFormat*" Function,+,subghz_transmitter_alloc_init,SubGhzTransmitter*,"SubGhzEnvironment*, const char*" -Function,+,subghz_transmitter_deserialize,_Bool,"SubGhzTransmitter*, FlipperFormat*" +Function,+,subghz_transmitter_deserialize,SubGhzProtocolStatus,"SubGhzTransmitter*, FlipperFormat*" Function,+,subghz_transmitter_free,void,SubGhzTransmitter* Function,+,subghz_transmitter_get_protocol_instance,SubGhzProtocolEncoderBase*,SubGhzTransmitter* Function,+,subghz_transmitter_stop,_Bool,SubGhzTransmitter* diff --git a/lib/subghz/blocks/generic.c b/lib/subghz/blocks/generic.c index 3d59adc82cf..f59323da377 100644 --- a/lib/subghz/blocks/generic.c +++ b/lib/subghz/blocks/generic.c @@ -20,12 +20,12 @@ void subghz_block_generic_get_preset_name(const char* preset_name, FuriString* p furi_string_set(preset_str, preset_name_temp); } -bool subghz_block_generic_serialize( +SubGhzProtocolStatus subghz_block_generic_serialize( SubGhzBlockGeneric* instance, FlipperFormat* flipper_format, SubGhzRadioPreset* preset) { furi_assert(instance); - bool res = false; + SubGhzProtocolStatus res = SubGhzProtocolStatusError; FuriString* temp_str; temp_str = furi_string_alloc(); do { @@ -33,11 +33,13 @@ bool subghz_block_generic_serialize( if(!flipper_format_write_header_cstr( flipper_format, SUBGHZ_KEY_FILE_TYPE, SUBGHZ_KEY_FILE_VERSION)) { FURI_LOG_E(TAG, "Unable to add header"); + res = SubGhzProtocolStatusErrorParserHeader; break; } if(!flipper_format_write_uint32(flipper_format, "Frequency", &preset->frequency, 1)) { FURI_LOG_E(TAG, "Unable to add Frequency"); + res = SubGhzProtocolStatusErrorParserFrequency; break; } @@ -45,27 +47,32 @@ bool subghz_block_generic_serialize( if(!flipper_format_write_string_cstr( flipper_format, "Preset", furi_string_get_cstr(temp_str))) { FURI_LOG_E(TAG, "Unable to add Preset"); + res = SubGhzProtocolStatusErrorParserPreset; break; } if(!strcmp(furi_string_get_cstr(temp_str), "FuriHalSubGhzPresetCustom")) { if(!flipper_format_write_string_cstr( flipper_format, "Custom_preset_module", "CC1101")) { FURI_LOG_E(TAG, "Unable to add Custom_preset_module"); + res = SubGhzProtocolStatusErrorParserCustomPreset; break; } if(!flipper_format_write_hex( flipper_format, "Custom_preset_data", preset->data, preset->data_size)) { FURI_LOG_E(TAG, "Unable to add Custom_preset_data"); + res = SubGhzProtocolStatusErrorParserCustomPreset; break; } } if(!flipper_format_write_string_cstr(flipper_format, "Protocol", instance->protocol_name)) { FURI_LOG_E(TAG, "Unable to add Protocol"); + res = SubGhzProtocolStatusErrorParserProtocolName; break; } uint32_t temp = instance->data_count_bit; if(!flipper_format_write_uint32(flipper_format, "Bit", &temp, 1)) { FURI_LOG_E(TAG, "Unable to add Bit"); + res = SubGhzProtocolStatusErrorParserBitCount; break; } @@ -76,17 +83,19 @@ bool subghz_block_generic_serialize( if(!flipper_format_write_hex(flipper_format, "Key", key_data, sizeof(uint64_t))) { FURI_LOG_E(TAG, "Unable to add Key"); + res = SubGhzProtocolStatusErrorParserKey; break; } - res = true; + res = SubGhzProtocolStatusOk; } while(false); furi_string_free(temp_str); return res; } -bool subghz_block_generic_deserialize(SubGhzBlockGeneric* instance, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + subghz_block_generic_deserialize(SubGhzBlockGeneric* instance, FlipperFormat* flipper_format) { furi_assert(instance); - bool res = false; + SubGhzProtocolStatus res = SubGhzProtocolStatusError; FuriString* temp_str; temp_str = furi_string_alloc(); uint32_t temp_data = 0; @@ -94,10 +103,12 @@ bool subghz_block_generic_deserialize(SubGhzBlockGeneric* instance, FlipperForma do { if(!flipper_format_rewind(flipper_format)) { FURI_LOG_E(TAG, "Rewind error"); + res = SubGhzProtocolStatusErrorParserOthers; break; } if(!flipper_format_read_uint32(flipper_format, "Bit", (uint32_t*)&temp_data, 1)) { FURI_LOG_E(TAG, "Missing Bit"); + res = SubGhzProtocolStatusErrorParserBitCount; break; } instance->data_count_bit = (uint16_t)temp_data; @@ -105,16 +116,36 @@ bool subghz_block_generic_deserialize(SubGhzBlockGeneric* instance, FlipperForma uint8_t key_data[sizeof(uint64_t)] = {0}; if(!flipper_format_read_hex(flipper_format, "Key", key_data, sizeof(uint64_t))) { FURI_LOG_E(TAG, "Missing Key"); + res = SubGhzProtocolStatusErrorParserKey; break; } for(uint8_t i = 0; i < sizeof(uint64_t); i++) { instance->data = instance->data << 8 | key_data[i]; } - res = true; + res = SubGhzProtocolStatusOk; } while(0); furi_string_free(temp_str); return res; } + +SubGhzProtocolStatus subghz_block_generic_deserialize_check_count_bit( + SubGhzBlockGeneric* instance, + FlipperFormat* flipper_format, + uint16_t count_bit) { + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; + do { + ret = subghz_block_generic_deserialize(instance, flipper_format); + if(ret != SubGhzProtocolStatusOk) { + break; + } + if(instance->data_count_bit != count_bit) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + ret = SubGhzProtocolStatusErrorValueBitCount; + break; + } + } while(false); + return ret; +} \ No newline at end of file diff --git a/lib/subghz/blocks/generic.h b/lib/subghz/blocks/generic.h index 284df51aebb..bc26a54d9f0 100644 --- a/lib/subghz/blocks/generic.h +++ b/lib/subghz/blocks/generic.h @@ -36,9 +36,9 @@ void subghz_block_generic_get_preset_name(const char* preset_name, FuriString* p * @param instance Pointer to a SubGhzBlockGeneric instance * @param flipper_format Pointer to a FlipperFormat instance * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return true On success + * @return Status Error */ -bool subghz_block_generic_serialize( +SubGhzProtocolStatus subghz_block_generic_serialize( SubGhzBlockGeneric* instance, FlipperFormat* flipper_format, SubGhzRadioPreset* preset); @@ -47,9 +47,22 @@ bool subghz_block_generic_serialize( * Deserialize data SubGhzBlockGeneric. * @param instance Pointer to a SubGhzBlockGeneric instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return Status Error */ -bool subghz_block_generic_deserialize(SubGhzBlockGeneric* instance, FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_block_generic_deserialize(SubGhzBlockGeneric* instance, FlipperFormat* flipper_format); + +/** + * Deserialize data SubGhzBlockGeneric. + * @param instance Pointer to a SubGhzBlockGeneric instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param count_bit Count bit protocol + * @return Status Error + */ +SubGhzProtocolStatus subghz_block_generic_deserialize_check_count_bit( + SubGhzBlockGeneric* instance, + FlipperFormat* flipper_format, + uint16_t count_bit); #ifdef __cplusplus } diff --git a/lib/subghz/protocols/alutech_at_4n.c b/lib/subghz/protocols/alutech_at_4n.c index 6bcf9f25de3..e8bb87055eb 100644 --- a/lib/subghz/protocols/alutech_at_4n.c +++ b/lib/subghz/protocols/alutech_at_4n.c @@ -385,46 +385,46 @@ uint8_t subghz_protocol_decoder_alutech_at_4n_get_hash_data(void* context) { return (uint8_t)instance->crc; } -bool subghz_protocol_decoder_alutech_at_4n_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_alutech_at_4n_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset) { furi_assert(context); SubGhzProtocolDecoderAlutech_at_4n* instance = context; - bool res = subghz_block_generic_serialize(&instance->generic, flipper_format, preset); - if(res && !flipper_format_write_uint32(flipper_format, "CRC", &instance->crc, 1)) { + SubGhzProtocolStatus res = + subghz_block_generic_serialize(&instance->generic, flipper_format, preset); + if((res == SubGhzProtocolStatusOk) && + !flipper_format_write_uint32(flipper_format, "CRC", &instance->crc, 1)) { FURI_LOG_E(TAG, "Unable to add CRC"); - res = false; + res = SubGhzProtocolStatusErrorParserOthers; } return res; - - return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); } -bool subghz_protocol_decoder_alutech_at_4n_deserialize( +SubGhzProtocolStatus subghz_protocol_decoder_alutech_at_4n_deserialize( void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolDecoderAlutech_at_4n* instance = context; - bool ret = false; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; do { - if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { - break; - } - if(instance->generic.data_count_bit != - subghz_protocol_alutech_at_4n_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); + ret = subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_alutech_at_4n_const.min_count_bit_for_found); + if(ret != SubGhzProtocolStatusOk) { break; } if(!flipper_format_rewind(flipper_format)) { FURI_LOG_E(TAG, "Rewind error"); + ret = SubGhzProtocolStatusErrorParserOthers; break; } if(!flipper_format_read_uint32(flipper_format, "CRC", (uint32_t*)&instance->crc, 1)) { FURI_LOG_E(TAG, "Missing CRC"); + ret = SubGhzProtocolStatusErrorParserOthers; break; } - ret = true; } while(false); return ret; } diff --git a/lib/subghz/protocols/alutech_at_4n.h b/lib/subghz/protocols/alutech_at_4n.h index 38bac3ea6a4..189f2f0d8b4 100644 --- a/lib/subghz/protocols/alutech_at_4n.h +++ b/lib/subghz/protocols/alutech_at_4n.h @@ -49,9 +49,9 @@ uint8_t subghz_protocol_decoder_alutech_at_4n_get_hash_data(void* context); * @param context Pointer to a SubGhzProtocolDecoderAlutech_at_4n instance * @param flipper_format Pointer to a FlipperFormat instance * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return true On success + * @return status */ -bool subghz_protocol_decoder_alutech_at_4n_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_alutech_at_4n_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset); @@ -60,11 +60,10 @@ bool subghz_protocol_decoder_alutech_at_4n_serialize( * Deserialize data SubGhzProtocolDecoderAlutech_at_4n. * @param context Pointer to a SubGhzProtocolDecoderAlutech_at_4n instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool subghz_protocol_decoder_alutech_at_4n_deserialize( - void* context, - FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_decoder_alutech_at_4n_deserialize(void* context, FlipperFormat* flipper_format); /** * Getting a textual representation of the received data. diff --git a/lib/subghz/protocols/ansonic.c b/lib/subghz/protocols/ansonic.c index 81b196e369c..9a122629be7 100644 --- a/lib/subghz/protocols/ansonic.c +++ b/lib/subghz/protocols/ansonic.c @@ -136,28 +136,29 @@ static bool subghz_protocol_encoder_ansonic_get_upload(SubGhzProtocolEncoderAnso return true; } -bool subghz_protocol_encoder_ansonic_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + subghz_protocol_encoder_ansonic_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolEncoderAnsonic* instance = context; - bool res = false; + SubGhzProtocolStatus res = SubGhzProtocolStatusError; do { - if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + res = subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_ansonic_const.min_count_bit_for_found); + if(res != SubGhzProtocolStatusOk) { FURI_LOG_E(TAG, "Deserialize error"); break; } - if(instance->generic.data_count_bit != - subghz_protocol_ansonic_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); - break; - } //optional parameter parameter flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - if(!subghz_protocol_encoder_ansonic_get_upload(instance)) break; + if(!subghz_protocol_encoder_ansonic_get_upload(instance)) { + res = SubGhzProtocolStatusErrorEncoderGetUpload; + break; + } instance->encoder.is_running = true; - - res = true; } while(false); return res; @@ -301,7 +302,7 @@ uint8_t subghz_protocol_decoder_ansonic_get_hash_data(void* context) { &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } -bool subghz_protocol_decoder_ansonic_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_ansonic_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset) { @@ -310,22 +311,12 @@ bool subghz_protocol_decoder_ansonic_serialize( return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); } -bool subghz_protocol_decoder_ansonic_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + subghz_protocol_decoder_ansonic_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolDecoderAnsonic* instance = context; - bool ret = false; - do { - if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { - break; - } - if(instance->generic.data_count_bit != - subghz_protocol_ansonic_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); - break; - } - ret = true; - } while(false); - return ret; + return subghz_block_generic_deserialize_check_count_bit( + &instance->generic, flipper_format, subghz_protocol_ansonic_const.min_count_bit_for_found); } void subghz_protocol_decoder_ansonic_get_string(void* context, FuriString* output) { diff --git a/lib/subghz/protocols/ansonic.h b/lib/subghz/protocols/ansonic.h index 0170a604897..9558531877d 100644 --- a/lib/subghz/protocols/ansonic.h +++ b/lib/subghz/protocols/ansonic.h @@ -30,7 +30,8 @@ void subghz_protocol_encoder_ansonic_free(void* context); * @param flipper_format Pointer to a FlipperFormat instance * @return true On success */ -bool subghz_protocol_encoder_ansonic_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_encoder_ansonic_deserialize(void* context, FlipperFormat* flipper_format); /** * Forced transmission stop. @@ -86,7 +87,7 @@ uint8_t subghz_protocol_decoder_ansonic_get_hash_data(void* context); * @param preset The modulation on which the signal was received, SubGhzRadioPreset * @return true On success */ -bool subghz_protocol_decoder_ansonic_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_ansonic_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset); @@ -97,7 +98,8 @@ bool subghz_protocol_decoder_ansonic_serialize( * @param flipper_format Pointer to a FlipperFormat instance * @return true On success */ -bool subghz_protocol_decoder_ansonic_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_decoder_ansonic_deserialize(void* context, FlipperFormat* flipper_format); /** * Getting a textual representation of the received data. diff --git a/lib/subghz/protocols/base.c b/lib/subghz/protocols/base.c index 36f33b9a5c6..37d1a308f06 100644 --- a/lib/subghz/protocols/base.c +++ b/lib/subghz/protocols/base.c @@ -23,11 +23,11 @@ bool subghz_protocol_decoder_base_get_string( return status; } -bool subghz_protocol_decoder_base_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_base_serialize( SubGhzProtocolDecoderBase* decoder_base, FlipperFormat* flipper_format, SubGhzRadioPreset* preset) { - bool status = false; + SubGhzProtocolStatus status = SubGhzProtocolStatusError; if(decoder_base->protocol && decoder_base->protocol->decoder && decoder_base->protocol->decoder->serialize) { @@ -37,10 +37,10 @@ bool subghz_protocol_decoder_base_serialize( return status; } -bool subghz_protocol_decoder_base_deserialize( +SubGhzProtocolStatus subghz_protocol_decoder_base_deserialize( SubGhzProtocolDecoderBase* decoder_base, FlipperFormat* flipper_format) { - bool status = false; + SubGhzProtocolStatus status = SubGhzProtocolStatusError; if(decoder_base->protocol && decoder_base->protocol->decoder && decoder_base->protocol->decoder->deserialize) { diff --git a/lib/subghz/protocols/base.h b/lib/subghz/protocols/base.h index 1f3d3e1bece..1d819ab5efb 100644 --- a/lib/subghz/protocols/base.h +++ b/lib/subghz/protocols/base.h @@ -49,9 +49,9 @@ bool subghz_protocol_decoder_base_get_string( * @param decoder_base Pointer to a SubGhzProtocolDecoderBase instance * @param flipper_format Pointer to a FlipperFormat instance * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return true On success + * @return Status Error */ -bool subghz_protocol_decoder_base_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_base_serialize( SubGhzProtocolDecoderBase* decoder_base, FlipperFormat* flipper_format, SubGhzRadioPreset* preset); @@ -60,9 +60,9 @@ bool subghz_protocol_decoder_base_serialize( * Deserialize data SubGhzProtocolDecoderBase. * @param decoder_base Pointer to a SubGhzProtocolDecoderBase instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return Status Error */ -bool subghz_protocol_decoder_base_deserialize( +SubGhzProtocolStatus subghz_protocol_decoder_base_deserialize( SubGhzProtocolDecoderBase* decoder_base, FlipperFormat* flipper_format); diff --git a/lib/subghz/protocols/bett.c b/lib/subghz/protocols/bett.c index 644d80fd834..de13472ac73 100644 --- a/lib/subghz/protocols/bett.c +++ b/lib/subghz/protocols/bett.c @@ -155,31 +155,32 @@ static bool subghz_protocol_encoder_bett_get_upload(SubGhzProtocolEncoderBETT* i return true; } -bool subghz_protocol_encoder_bett_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + subghz_protocol_encoder_bett_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolEncoderBETT* instance = context; - bool res = false; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; do { - if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + ret = subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_bett_const.min_count_bit_for_found); + if(ret != SubGhzProtocolStatusOk) { FURI_LOG_E(TAG, "Deserialize error"); break; } - if(instance->generic.data_count_bit != - subghz_protocol_bett_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); - break; - } //optional parameter parameter flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - if(!subghz_protocol_encoder_bett_get_upload(instance)) break; + if(!subghz_protocol_encoder_bett_get_upload(instance)) { + ret = SubGhzProtocolStatusErrorEncoderGetUpload; + break; + } instance->encoder.is_running = true; - - res = true; } while(false); - return res; + return ret; } void subghz_protocol_encoder_bett_stop(void* context) { @@ -295,7 +296,7 @@ uint8_t subghz_protocol_decoder_bett_get_hash_data(void* context) { &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } -bool subghz_protocol_decoder_bett_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_bett_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset) { @@ -304,22 +305,12 @@ bool subghz_protocol_decoder_bett_serialize( return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); } -bool subghz_protocol_decoder_bett_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + subghz_protocol_decoder_bett_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolDecoderBETT* instance = context; - bool ret = false; - do { - if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { - break; - } - if(instance->generic.data_count_bit != - subghz_protocol_bett_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); - break; - } - ret = true; - } while(false); - return ret; + return subghz_block_generic_deserialize_check_count_bit( + &instance->generic, flipper_format, subghz_protocol_bett_const.min_count_bit_for_found); } void subghz_protocol_decoder_bett_get_string(void* context, FuriString* output) { diff --git a/lib/subghz/protocols/bett.h b/lib/subghz/protocols/bett.h index c0ce0b7f42a..0a11cbe6973 100644 --- a/lib/subghz/protocols/bett.h +++ b/lib/subghz/protocols/bett.h @@ -28,9 +28,10 @@ void subghz_protocol_encoder_bett_free(void* context); * Deserialize and generating an upload to send. * @param context Pointer to a SubGhzProtocolEncoderBETT instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool subghz_protocol_encoder_bett_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_encoder_bett_deserialize(void* context, FlipperFormat* flipper_format); /** * Forced transmission stop. @@ -84,9 +85,9 @@ uint8_t subghz_protocol_decoder_bett_get_hash_data(void* context); * @param context Pointer to a SubGhzProtocolDecoderBETT instance * @param flipper_format Pointer to a FlipperFormat instance * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return true On success + * @return status */ -bool subghz_protocol_decoder_bett_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_bett_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset); @@ -95,9 +96,10 @@ bool subghz_protocol_decoder_bett_serialize( * Deserialize data SubGhzProtocolDecoderBETT. * @param context Pointer to a SubGhzProtocolDecoderBETT instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool subghz_protocol_decoder_bett_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_decoder_bett_deserialize(void* context, FlipperFormat* flipper_format); /** * Getting a textual representation of the received data. diff --git a/lib/subghz/protocols/bin_raw.c b/lib/subghz/protocols/bin_raw.c index c3f54411090..123a4ba9d31 100644 --- a/lib/subghz/protocols/bin_raw.c +++ b/lib/subghz/protocols/bin_raw.c @@ -219,20 +219,23 @@ static bool subghz_protocol_encoder_bin_raw_get_upload(SubGhzProtocolEncoderBinR return true; } -bool subghz_protocol_encoder_bin_raw_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + subghz_protocol_encoder_bin_raw_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolEncoderBinRAW* instance = context; - bool res = false; + SubGhzProtocolStatus res = SubGhzProtocolStatusError; uint32_t temp_data = 0; do { if(!flipper_format_rewind(flipper_format)) { FURI_LOG_E(TAG, "Rewind error"); + res = SubGhzProtocolStatusErrorParserOthers; break; } if(!flipper_format_read_uint32(flipper_format, "Bit", (uint32_t*)&temp_data, 1)) { FURI_LOG_E(TAG, "Missing Bit"); + res = SubGhzProtocolStatusErrorParserBitCount; break; } @@ -240,6 +243,7 @@ bool subghz_protocol_encoder_bin_raw_deserialize(void* context, FlipperFormat* f if(!flipper_format_read_uint32(flipper_format, "TE", (uint32_t*)&instance->te, 1)) { FURI_LOG_E(TAG, "Missing TE"); + res = SubGhzProtocolStatusErrorParserTe; break; } @@ -251,11 +255,13 @@ bool subghz_protocol_encoder_bin_raw_deserialize(void* context, FlipperFormat* f while(flipper_format_read_uint32(flipper_format, "Bit_RAW", (uint32_t*)&temp_data, 1)) { if(ind >= BIN_RAW_MAX_MARKUP_COUNT) { FURI_LOG_E(TAG, "Markup overflow"); + res = SubGhzProtocolStatusErrorParserOthers; break; } byte_count += subghz_protocol_bin_raw_get_full_byte(temp_data); if(byte_count > BIN_RAW_BUF_DATA_SIZE) { FURI_LOG_E(TAG, "Receive buffer overflow"); + res = SubGhzProtocolStatusErrorParserOthers; break; } @@ -270,6 +276,7 @@ bool subghz_protocol_encoder_bin_raw_deserialize(void* context, FlipperFormat* f subghz_protocol_bin_raw_get_full_byte(temp_data))) { instance->data_markup[ind].bit_count = 0; FURI_LOG_E(TAG, "Missing Data_RAW"); + res = SubGhzProtocolStatusErrorParserOthers; break; } ind++; @@ -297,16 +304,20 @@ bool subghz_protocol_encoder_bin_raw_deserialize(void* context, FlipperFormat* f #endif if(!flipper_format_rewind(flipper_format)) { FURI_LOG_E(TAG, "Rewind error"); + res = SubGhzProtocolStatusErrorParserOthers; break; } //optional parameter parameter flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - if(!subghz_protocol_encoder_bin_raw_get_upload(instance)) break; + if(!subghz_protocol_encoder_bin_raw_get_upload(instance)) { + break; + res = SubGhzProtocolStatusErrorEncoderGetUpload; + } instance->encoder.is_running = true; - res = true; + res = SubGhzProtocolStatusOk; } while(0); return res; @@ -957,14 +968,14 @@ uint8_t subghz_protocol_decoder_bin_raw_get_hash_data(void* context) { subghz_protocol_bin_raw_get_full_byte(instance->data_markup[0].bit_count)); } -bool subghz_protocol_decoder_bin_raw_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_bin_raw_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset) { furi_assert(context); SubGhzProtocolDecoderBinRAW* instance = context; - bool res = false; + SubGhzProtocolStatus res = SubGhzProtocolStatusError; FuriString* temp_str; temp_str = furi_string_alloc(); do { @@ -972,11 +983,13 @@ bool subghz_protocol_decoder_bin_raw_serialize( if(!flipper_format_write_header_cstr( flipper_format, SUBGHZ_KEY_FILE_TYPE, SUBGHZ_KEY_FILE_VERSION)) { FURI_LOG_E(TAG, "Unable to add header"); + res = SubGhzProtocolStatusErrorParserHeader; break; } if(!flipper_format_write_uint32(flipper_format, "Frequency", &preset->frequency, 1)) { FURI_LOG_E(TAG, "Unable to add Frequency"); + res = SubGhzProtocolStatusErrorParserFrequency; break; } @@ -984,34 +997,40 @@ bool subghz_protocol_decoder_bin_raw_serialize( if(!flipper_format_write_string_cstr( flipper_format, "Preset", furi_string_get_cstr(temp_str))) { FURI_LOG_E(TAG, "Unable to add Preset"); + res = SubGhzProtocolStatusErrorParserPreset; break; } if(!strcmp(furi_string_get_cstr(temp_str), "FuriHalSubGhzPresetCustom")) { if(!flipper_format_write_string_cstr( flipper_format, "Custom_preset_module", "CC1101")) { FURI_LOG_E(TAG, "Unable to add Custom_preset_module"); + res = SubGhzProtocolStatusErrorParserCustomPreset; break; } if(!flipper_format_write_hex( flipper_format, "Custom_preset_data", preset->data, preset->data_size)) { FURI_LOG_E(TAG, "Unable to add Custom_preset_data"); + res = SubGhzProtocolStatusErrorParserCustomPreset; break; } } if(!flipper_format_write_string_cstr( flipper_format, "Protocol", instance->generic.protocol_name)) { FURI_LOG_E(TAG, "Unable to add Protocol"); + res = SubGhzProtocolStatusErrorParserProtocolName; break; } uint32_t temp = instance->generic.data_count_bit; if(!flipper_format_write_uint32(flipper_format, "Bit", &temp, 1)) { FURI_LOG_E(TAG, "Unable to add Bit"); + res = SubGhzProtocolStatusErrorParserBitCount; break; } if(!flipper_format_write_uint32(flipper_format, "TE", &instance->te, 1)) { FURI_LOG_E(TAG, "Unable to add TE"); + res = SubGhzProtocolStatusErrorParserTe; break; } @@ -1020,6 +1039,7 @@ bool subghz_protocol_decoder_bin_raw_serialize( temp = instance->data_markup[i].bit_count; if(!flipper_format_write_uint32(flipper_format, "Bit_RAW", &temp, 1)) { FURI_LOG_E(TAG, "Bit_RAW"); + res = SubGhzProtocolStatusErrorParserOthers; break; } if(!flipper_format_write_hex( @@ -1028,31 +1048,35 @@ bool subghz_protocol_decoder_bin_raw_serialize( instance->data + instance->data_markup[i].byte_bias, subghz_protocol_bin_raw_get_full_byte(instance->data_markup[i].bit_count))) { FURI_LOG_E(TAG, "Unable to add Data_RAW"); + res = SubGhzProtocolStatusErrorParserOthers; break; } i++; } - res = true; + res = SubGhzProtocolStatusOk; } while(false); furi_string_free(temp_str); return res; } -bool subghz_protocol_decoder_bin_raw_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + subghz_protocol_decoder_bin_raw_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolDecoderBinRAW* instance = context; - bool res = false; + SubGhzProtocolStatus res = SubGhzProtocolStatusError; uint32_t temp_data = 0; do { if(!flipper_format_rewind(flipper_format)) { FURI_LOG_E(TAG, "Rewind error"); + res = SubGhzProtocolStatusErrorParserOthers; break; } if(!flipper_format_read_uint32(flipper_format, "Bit", (uint32_t*)&temp_data, 1)) { FURI_LOG_E(TAG, "Missing Bit"); + res = SubGhzProtocolStatusErrorParserBitCount; break; } @@ -1060,6 +1084,7 @@ bool subghz_protocol_decoder_bin_raw_deserialize(void* context, FlipperFormat* f if(!flipper_format_read_uint32(flipper_format, "TE", (uint32_t*)&instance->te, 1)) { FURI_LOG_E(TAG, "Missing TE"); + res = SubGhzProtocolStatusErrorParserTe; break; } @@ -1071,11 +1096,13 @@ bool subghz_protocol_decoder_bin_raw_deserialize(void* context, FlipperFormat* f while(flipper_format_read_uint32(flipper_format, "Bit_RAW", (uint32_t*)&temp_data, 1)) { if(ind >= BIN_RAW_MAX_MARKUP_COUNT) { FURI_LOG_E(TAG, "Markup overflow"); + res = SubGhzProtocolStatusErrorParserOthers; break; } byte_count += subghz_protocol_bin_raw_get_full_byte(temp_data); if(byte_count > BIN_RAW_BUF_DATA_SIZE) { FURI_LOG_E(TAG, "Receive buffer overflow"); + res = SubGhzProtocolStatusErrorParserOthers; break; } @@ -1090,12 +1117,13 @@ bool subghz_protocol_decoder_bin_raw_deserialize(void* context, FlipperFormat* f subghz_protocol_bin_raw_get_full_byte(temp_data))) { instance->data_markup[ind].bit_count = 0; FURI_LOG_E(TAG, "Missing Data_RAW"); + res = SubGhzProtocolStatusErrorParserOthers; break; } ind++; } - res = true; + res = SubGhzProtocolStatusOk; } while(0); return res; diff --git a/lib/subghz/protocols/bin_raw.h b/lib/subghz/protocols/bin_raw.h index c63f86ce6f5..82775e57593 100644 --- a/lib/subghz/protocols/bin_raw.h +++ b/lib/subghz/protocols/bin_raw.h @@ -28,9 +28,10 @@ void subghz_protocol_encoder_bin_raw_free(void* context); * Deserialize and generating an upload to send. * @param context Pointer to a SubGhzProtocolEncoderBinRAW instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool subghz_protocol_encoder_bin_raw_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_encoder_bin_raw_deserialize(void* context, FlipperFormat* flipper_format); /** * Forced transmission stop. @@ -88,9 +89,9 @@ void subghz_protocol_decoder_bin_raw_data_input_rssi( * @param context Pointer to a SubGhzProtocolDecoderBinRAW instance * @param flipper_format Pointer to a FlipperFormat instance * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return true On success + * @return status */ -bool subghz_protocol_decoder_bin_raw_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_bin_raw_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset); @@ -99,9 +100,10 @@ bool subghz_protocol_decoder_bin_raw_serialize( * Deserialize data SubGhzProtocolDecoderBinRAW. * @param context Pointer to a SubGhzProtocolDecoderBinRAW instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool subghz_protocol_decoder_bin_raw_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_decoder_bin_raw_deserialize(void* context, FlipperFormat* flipper_format); /** * Getting a textual representation of the received data. diff --git a/lib/subghz/protocols/came.c b/lib/subghz/protocols/came.c index bed26d7d27c..14b2e0101cd 100644 --- a/lib/subghz/protocols/came.c +++ b/lib/subghz/protocols/came.c @@ -13,6 +13,7 @@ */ #define TAG "SubGhzProtocolCAME" +#define CAME_12_COUNT_BIT 12 #define CAME_24_COUNT_BIT 24 #define PRASTEL_COUNT_BIT 25 #define PRASTEL_NAME "Prastel" @@ -108,6 +109,7 @@ void subghz_protocol_encoder_came_free(void* context) { */ static bool subghz_protocol_encoder_came_get_upload(SubGhzProtocolEncoderCame* instance) { furi_assert(instance); + uint32_t header_te = 0; size_t index = 0; size_t size_upload = (instance->generic.data_count_bit * 2) + 2; if(size_upload > instance->encoder.size_upload) { @@ -117,13 +119,28 @@ static bool subghz_protocol_encoder_came_get_upload(SubGhzProtocolEncoderCame* i instance->encoder.size_upload = size_upload; } //Send header - instance->encoder.upload[index++] = level_duration_make( - false, - (((instance->generic.data_count_bit == CAME_24_COUNT_BIT) || - (instance->generic.data_count_bit == - subghz_protocol_came_const.min_count_bit_for_found)) ? - (uint32_t)subghz_protocol_came_const.te_short * 76 : - (uint32_t)subghz_protocol_came_const.te_short * 39)); + + switch(instance->generic.data_count_bit) { + case CAME_24_COUNT_BIT: + // CAME 24 Bit = 24320 us + header_te = 76; + break; + case CAME_12_COUNT_BIT: + case AIRFORCE_COUNT_BIT: + // CAME 12 Bit Original only! and Airforce protocol = 15040 us + header_te = 47; + break; + case PRASTEL_COUNT_BIT: + // PRASTEL = 11520 us + header_te = 36; + break; + default: + // Some wrong detected protocols, 5120 us + header_te = 16; + break; + } + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_came_const.te_short * header_te); //Send start bit instance->encoder.upload[index++] = level_duration_make(true, (uint32_t)subghz_protocol_came_const.te_short); @@ -146,30 +163,33 @@ static bool subghz_protocol_encoder_came_get_upload(SubGhzProtocolEncoderCame* i return true; } -bool subghz_protocol_encoder_came_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + subghz_protocol_encoder_came_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolEncoderCame* instance = context; - bool res = false; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; do { - if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { - FURI_LOG_E(TAG, "Deserialize error"); + ret = subghz_block_generic_deserialize(&instance->generic, flipper_format); + if(ret != SubGhzProtocolStatusOk) { break; } if((instance->generic.data_count_bit > PRASTEL_COUNT_BIT)) { FURI_LOG_E(TAG, "Wrong number of bits in key"); + ret = SubGhzProtocolStatusErrorValueBitCount; break; } //optional parameter parameter flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - if(!subghz_protocol_encoder_came_get_upload(instance)) break; + if(!subghz_protocol_encoder_came_get_upload(instance)) { + ret = SubGhzProtocolStatusErrorEncoderGetUpload; + break; + } instance->encoder.is_running = true; - - res = true; } while(false); - return res; + return ret; } void subghz_protocol_encoder_came_stop(void* context) { @@ -244,8 +264,11 @@ void subghz_protocol_decoder_came_feed(void* context, bool level, uint32_t durat if(!level) { //save interval if(duration >= (subghz_protocol_came_const.te_short * 4)) { instance->decoder.parser_step = CameDecoderStepFoundStartBit; - if(instance->decoder.decode_count_bit >= - subghz_protocol_came_const.min_count_bit_for_found) { + if((instance->decoder.decode_count_bit == + subghz_protocol_came_const.min_count_bit_for_found) || + (instance->decoder.decode_count_bit == AIRFORCE_COUNT_BIT) || + (instance->decoder.decode_count_bit == PRASTEL_COUNT_BIT) || + (instance->decoder.decode_count_bit == CAME_24_COUNT_BIT)) { instance->generic.serial = 0x0; instance->generic.btn = 0x0; @@ -294,7 +317,7 @@ uint8_t subghz_protocol_decoder_came_get_hash_data(void* context) { &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } -bool subghz_protocol_decoder_came_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_came_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset) { @@ -303,19 +326,21 @@ bool subghz_protocol_decoder_came_serialize( return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); } -bool subghz_protocol_decoder_came_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + subghz_protocol_decoder_came_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolDecoderCame* instance = context; - bool ret = false; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; do { - if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + ret = subghz_block_generic_deserialize(&instance->generic, flipper_format); + if(ret != SubGhzProtocolStatusOk) { break; } if((instance->generic.data_count_bit > PRASTEL_COUNT_BIT)) { FURI_LOG_E(TAG, "Wrong number of bits in key"); + ret = SubGhzProtocolStatusErrorValueBitCount; break; } - ret = true; } while(false); return ret; } diff --git a/lib/subghz/protocols/came.h b/lib/subghz/protocols/came.h index 253c93aaea5..fffa017ff59 100644 --- a/lib/subghz/protocols/came.h +++ b/lib/subghz/protocols/came.h @@ -28,9 +28,10 @@ void subghz_protocol_encoder_came_free(void* context); * Deserialize and generating an upload to send. * @param context Pointer to a SubGhzProtocolEncoderCame instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool subghz_protocol_encoder_came_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_encoder_came_deserialize(void* context, FlipperFormat* flipper_format); /** * Forced transmission stop. @@ -84,9 +85,9 @@ uint8_t subghz_protocol_decoder_came_get_hash_data(void* context); * @param context Pointer to a SubGhzProtocolDecoderCame instance * @param flipper_format Pointer to a FlipperFormat instance * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return true On success + * @return status */ -bool subghz_protocol_decoder_came_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_came_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset); @@ -95,9 +96,10 @@ bool subghz_protocol_decoder_came_serialize( * Deserialize data SubGhzProtocolDecoderCame. * @param context Pointer to a SubGhzProtocolDecoderCame instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool subghz_protocol_decoder_came_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_decoder_came_deserialize(void* context, FlipperFormat* flipper_format); /** * Getting a textual representation of the received data. diff --git a/lib/subghz/protocols/came_atomo.c b/lib/subghz/protocols/came_atomo.c index 2e2718485cd..9f411a66a77 100644 --- a/lib/subghz/protocols/came_atomo.c +++ b/lib/subghz/protocols/came_atomo.c @@ -298,7 +298,7 @@ uint8_t subghz_protocol_decoder_came_atomo_get_hash_data(void* context) { &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } -bool subghz_protocol_decoder_came_atomo_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_came_atomo_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset) { @@ -307,22 +307,14 @@ bool subghz_protocol_decoder_came_atomo_serialize( return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); } -bool subghz_protocol_decoder_came_atomo_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + subghz_protocol_decoder_came_atomo_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolDecoderCameAtomo* instance = context; - bool ret = false; - do { - if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { - break; - } - if(instance->generic.data_count_bit != - subghz_protocol_came_atomo_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); - break; - } - ret = true; - } while(false); - return ret; + return subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_came_atomo_const.min_count_bit_for_found); } void subghz_protocol_decoder_came_atomo_get_string(void* context, FuriString* output) { diff --git a/lib/subghz/protocols/came_atomo.h b/lib/subghz/protocols/came_atomo.h index 3781a0d8c23..0faea4f1a7c 100644 --- a/lib/subghz/protocols/came_atomo.h +++ b/lib/subghz/protocols/came_atomo.h @@ -49,9 +49,9 @@ uint8_t subghz_protocol_decoder_came_atomo_get_hash_data(void* context); * @param context Pointer to a SubGhzProtocolDecoderCameAtomo instance * @param flipper_format Pointer to a FlipperFormat instance * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return true On success + * @return status */ -bool subghz_protocol_decoder_came_atomo_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_came_atomo_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset); @@ -60,9 +60,10 @@ bool subghz_protocol_decoder_came_atomo_serialize( * Deserialize data SubGhzProtocolDecoderCameAtomo. * @param context Pointer to a SubGhzProtocolDecoderCameAtomo instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool subghz_protocol_decoder_came_atomo_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_decoder_came_atomo_deserialize(void* context, FlipperFormat* flipper_format); /** * Getting a textual representation of the received data. diff --git a/lib/subghz/protocols/came_twee.c b/lib/subghz/protocols/came_twee.c index e7eb33c4285..6fe6158139d 100644 --- a/lib/subghz/protocols/came_twee.c +++ b/lib/subghz/protocols/came_twee.c @@ -241,18 +241,17 @@ static void subghz_protocol_came_twee_remote_controller(SubGhzBlockGeneric* inst instance->cnt = data >> 6; } -bool subghz_protocol_encoder_came_twee_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + subghz_protocol_encoder_came_twee_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolEncoderCameTwee* instance = context; - bool res = false; + SubGhzProtocolStatus res = SubGhzProtocolStatusError; do { - if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { - FURI_LOG_E(TAG, "Deserialize error"); - break; - } - if(instance->generic.data_count_bit != - subghz_protocol_came_twee_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); + res = subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_came_twee_const.min_count_bit_for_found); + if(res != SubGhzProtocolStatusOk) { break; } //optional parameter parameter @@ -262,8 +261,6 @@ bool subghz_protocol_encoder_came_twee_deserialize(void* context, FlipperFormat* subghz_protocol_came_twee_remote_controller(&instance->generic); subghz_protocol_encoder_came_twee_get_upload(instance); instance->encoder.is_running = true; - - res = true; } while(false); return res; @@ -419,7 +416,7 @@ uint8_t subghz_protocol_decoder_came_twee_get_hash_data(void* context) { &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } -bool subghz_protocol_decoder_came_twee_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_came_twee_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset) { @@ -428,22 +425,14 @@ bool subghz_protocol_decoder_came_twee_serialize( return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); } -bool subghz_protocol_decoder_came_twee_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + subghz_protocol_decoder_came_twee_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolDecoderCameTwee* instance = context; - bool ret = false; - do { - if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { - break; - } - if(instance->generic.data_count_bit != - subghz_protocol_came_twee_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); - break; - } - ret = true; - } while(false); - return ret; + return subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_came_twee_const.min_count_bit_for_found); } void subghz_protocol_decoder_came_twee_get_string(void* context, FuriString* output) { diff --git a/lib/subghz/protocols/came_twee.h b/lib/subghz/protocols/came_twee.h index 359b964da7f..f26f1e8062d 100644 --- a/lib/subghz/protocols/came_twee.h +++ b/lib/subghz/protocols/came_twee.h @@ -28,9 +28,10 @@ void subghz_protocol_encoder_came_twee_free(void* context); * Deserialize and generating an upload to send. * @param context Pointer to a SubGhzProtocolEncoderCameTwee instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool subghz_protocol_encoder_came_twee_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_encoder_came_twee_deserialize(void* context, FlipperFormat* flipper_format); /** * Forced transmission stop. @@ -84,9 +85,9 @@ uint8_t subghz_protocol_decoder_came_twee_get_hash_data(void* context); * @param context Pointer to a SubGhzProtocolDecoderCameTwee instance * @param flipper_format Pointer to a FlipperFormat instance * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return true On success + * @return status */ -bool subghz_protocol_decoder_came_twee_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_came_twee_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset); @@ -95,9 +96,10 @@ bool subghz_protocol_decoder_came_twee_serialize( * Deserialize data SubGhzProtocolDecoderCameTwee. * @param context Pointer to a SubGhzProtocolDecoderCameTwee instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool subghz_protocol_decoder_came_twee_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_decoder_came_twee_deserialize(void* context, FlipperFormat* flipper_format); /** * Getting a textual representation of the received data. diff --git a/lib/subghz/protocols/chamberlain_code.c b/lib/subghz/protocols/chamberlain_code.c index 9c8e5ee4ada..be0877fb5c6 100644 --- a/lib/subghz/protocols/chamberlain_code.c +++ b/lib/subghz/protocols/chamberlain_code.c @@ -207,31 +207,35 @@ static bool return true; } -bool subghz_protocol_encoder_chamb_code_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + subghz_protocol_encoder_chamb_code_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolEncoderChamb_Code* instance = context; - bool res = false; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; do { - if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { - FURI_LOG_E(TAG, "Deserialize error"); + ret = subghz_block_generic_deserialize(&instance->generic, flipper_format); + if(ret != SubGhzProtocolStatusOk) { break; } if(instance->generic.data_count_bit > subghz_protocol_chamb_code_const.min_count_bit_for_found) { FURI_LOG_E(TAG, "Wrong number of bits in key"); + ret = SubGhzProtocolStatusErrorValueBitCount; break; } //optional parameter parameter flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - if(!subghz_protocol_encoder_chamb_code_get_upload(instance)) break; + if(!subghz_protocol_encoder_chamb_code_get_upload(instance)) { + ret = SubGhzProtocolStatusErrorEncoderGetUpload; + break; + } instance->encoder.is_running = true; - res = true; } while(false); - return res; + return ret; } void subghz_protocol_encoder_chamb_code_stop(void* context) { @@ -425,7 +429,7 @@ uint8_t subghz_protocol_decoder_chamb_code_get_hash_data(void* context) { &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } -bool subghz_protocol_decoder_chamb_code_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_chamb_code_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset) { @@ -434,20 +438,22 @@ bool subghz_protocol_decoder_chamb_code_serialize( return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); } -bool subghz_protocol_decoder_chamb_code_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + subghz_protocol_decoder_chamb_code_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolDecoderChamb_Code* instance = context; - bool ret = false; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; do { - if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + ret = subghz_block_generic_deserialize(&instance->generic, flipper_format); + if(ret != SubGhzProtocolStatusOk) { break; } if(instance->generic.data_count_bit > subghz_protocol_chamb_code_const.min_count_bit_for_found) { FURI_LOG_E(TAG, "Wrong number of bits in key"); + ret = SubGhzProtocolStatusErrorValueBitCount; break; } - ret = true; } while(false); return ret; } diff --git a/lib/subghz/protocols/chamberlain_code.h b/lib/subghz/protocols/chamberlain_code.h index f87b64d9012..c8ffed5c506 100644 --- a/lib/subghz/protocols/chamberlain_code.h +++ b/lib/subghz/protocols/chamberlain_code.h @@ -28,9 +28,10 @@ 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 + * @return status */ -bool subghz_protocol_encoder_chamb_code_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_encoder_chamb_code_deserialize(void* context, FlipperFormat* flipper_format); /** * Forced transmission stop. @@ -84,9 +85,9 @@ uint8_t subghz_protocol_decoder_chamb_code_get_hash_data(void* context); * @param context Pointer to a SubGhzProtocolDecoderChamb_Code instance * @param flipper_format Pointer to a FlipperFormat instance * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return true On success + * @return status */ -bool subghz_protocol_decoder_chamb_code_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_chamb_code_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset); @@ -95,9 +96,10 @@ bool subghz_protocol_decoder_chamb_code_serialize( * Deserialize data SubGhzProtocolDecoderChamb_Code. * @param context Pointer to a SubGhzProtocolDecoderChamb_Code instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool subghz_protocol_decoder_chamb_code_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_decoder_chamb_code_deserialize(void* context, FlipperFormat* flipper_format); /** * Getting a textual representation of the received data. diff --git a/lib/subghz/protocols/clemsa.c b/lib/subghz/protocols/clemsa.c index a2cb7a18bdc..a0547a11372 100644 --- a/lib/subghz/protocols/clemsa.c +++ b/lib/subghz/protocols/clemsa.c @@ -155,31 +155,32 @@ static bool subghz_protocol_encoder_clemsa_get_upload(SubGhzProtocolEncoderClems return true; } -bool subghz_protocol_encoder_clemsa_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + subghz_protocol_encoder_clemsa_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolEncoderClemsa* instance = context; - bool res = false; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; do { - if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { - FURI_LOG_E(TAG, "Deserialize error"); - break; - } - if(instance->generic.data_count_bit != - subghz_protocol_clemsa_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); + ret = subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_clemsa_const.min_count_bit_for_found); + if(ret != SubGhzProtocolStatusOk) { break; } //optional parameter parameter flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - if(!subghz_protocol_encoder_clemsa_get_upload(instance)) break; + if(!subghz_protocol_encoder_clemsa_get_upload(instance)) { + ret = SubGhzProtocolStatusErrorEncoderGetUpload; + break; + } instance->encoder.is_running = true; - res = true; } while(false); - return res; + return ret; } void subghz_protocol_encoder_clemsa_stop(void* context) { @@ -316,7 +317,7 @@ uint8_t subghz_protocol_decoder_clemsa_get_hash_data(void* context) { &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } -bool subghz_protocol_decoder_clemsa_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_clemsa_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset) { @@ -325,22 +326,12 @@ bool subghz_protocol_decoder_clemsa_serialize( return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); } -bool subghz_protocol_decoder_clemsa_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + subghz_protocol_decoder_clemsa_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolDecoderClemsa* instance = context; - bool ret = false; - do { - if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { - break; - } - if(instance->generic.data_count_bit != - subghz_protocol_clemsa_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); - break; - } - ret = true; - } while(false); - return ret; + return subghz_block_generic_deserialize_check_count_bit( + &instance->generic, flipper_format, subghz_protocol_clemsa_const.min_count_bit_for_found); } void subghz_protocol_decoder_clemsa_get_string(void* context, FuriString* output) { diff --git a/lib/subghz/protocols/clemsa.h b/lib/subghz/protocols/clemsa.h index 8858c1a3bac..f14cd3dace0 100644 --- a/lib/subghz/protocols/clemsa.h +++ b/lib/subghz/protocols/clemsa.h @@ -28,9 +28,10 @@ void subghz_protocol_encoder_clemsa_free(void* context); * Deserialize and generating an upload to send. * @param context Pointer to a SubGhzProtocolEncoderClemsa instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool subghz_protocol_encoder_clemsa_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_encoder_clemsa_deserialize(void* context, FlipperFormat* flipper_format); /** * Forced transmission stop. @@ -84,9 +85,9 @@ uint8_t subghz_protocol_decoder_clemsa_get_hash_data(void* context); * @param context Pointer to a SubGhzProtocolDecoderClemsa instance * @param flipper_format Pointer to a FlipperFormat instance * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return true On success + * @return status */ -bool subghz_protocol_decoder_clemsa_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_clemsa_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset); @@ -95,9 +96,10 @@ bool subghz_protocol_decoder_clemsa_serialize( * Deserialize data SubGhzProtocolDecoderClemsa. * @param context Pointer to a SubGhzProtocolDecoderClemsa instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool subghz_protocol_decoder_clemsa_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_decoder_clemsa_deserialize(void* context, FlipperFormat* flipper_format); /** * Getting a textual representation of the received data. diff --git a/lib/subghz/protocols/doitrand.c b/lib/subghz/protocols/doitrand.c index 6b31d4f2716..69b8bba4ae1 100644 --- a/lib/subghz/protocols/doitrand.c +++ b/lib/subghz/protocols/doitrand.c @@ -136,31 +136,31 @@ static bool subghz_protocol_encoder_doitrand_get_upload(SubGhzProtocolEncoderDoi return true; } -bool subghz_protocol_encoder_doitrand_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + subghz_protocol_encoder_doitrand_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolEncoderDoitrand* instance = context; - bool res = false; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; do { - if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { - FURI_LOG_E(TAG, "Deserialize error"); - break; - } - if(instance->generic.data_count_bit != - subghz_protocol_doitrand_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); + ret = subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_doitrand_const.min_count_bit_for_found); + if(ret != SubGhzProtocolStatusOk) { break; } //optional parameter parameter flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - if(!subghz_protocol_encoder_doitrand_get_upload(instance)) break; + if(!subghz_protocol_encoder_doitrand_get_upload(instance)) { + ret = SubGhzProtocolStatusErrorEncoderGetUpload; + break; + } instance->encoder.is_running = true; - - res = true; } while(false); - return res; + return ret; } void subghz_protocol_encoder_doitrand_stop(void* context) { @@ -310,7 +310,7 @@ uint8_t subghz_protocol_decoder_doitrand_get_hash_data(void* context) { &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } -bool subghz_protocol_decoder_doitrand_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_doitrand_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset) { @@ -319,22 +319,14 @@ bool subghz_protocol_decoder_doitrand_serialize( return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); } -bool subghz_protocol_decoder_doitrand_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + subghz_protocol_decoder_doitrand_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolDecoderDoitrand* instance = context; - bool ret = false; - do { - if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { - break; - } - if(instance->generic.data_count_bit != - subghz_protocol_doitrand_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); - break; - } - ret = true; - } while(false); - return ret; + return subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_doitrand_const.min_count_bit_for_found); } void subghz_protocol_decoder_doitrand_get_string(void* context, FuriString* output) { diff --git a/lib/subghz/protocols/doitrand.h b/lib/subghz/protocols/doitrand.h index 30f1fffd0dc..5dbc6678fdc 100644 --- a/lib/subghz/protocols/doitrand.h +++ b/lib/subghz/protocols/doitrand.h @@ -28,9 +28,10 @@ void subghz_protocol_encoder_doitrand_free(void* context); * Deserialize and generating an upload to send. * @param context Pointer to a SubGhzProtocolEncoderDoitrand instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool subghz_protocol_encoder_doitrand_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_encoder_doitrand_deserialize(void* context, FlipperFormat* flipper_format); /** * Forced transmission stop. @@ -84,9 +85,9 @@ uint8_t subghz_protocol_decoder_doitrand_get_hash_data(void* context); * @param context Pointer to a SubGhzProtocolDecoderDoitrand instance * @param flipper_format Pointer to a FlipperFormat instance * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return true On success + * @return status */ -bool subghz_protocol_decoder_doitrand_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_doitrand_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset); @@ -95,9 +96,10 @@ bool subghz_protocol_decoder_doitrand_serialize( * Deserialize data SubGhzProtocolDecoderDoitrand. * @param context Pointer to a SubGhzProtocolDecoderDoitrand instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool subghz_protocol_decoder_doitrand_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_decoder_doitrand_deserialize(void* context, FlipperFormat* flipper_format); /** * Getting a textual representation of the received data. diff --git a/lib/subghz/protocols/dooya.c b/lib/subghz/protocols/dooya.c index c70b6d54ee9..47e95209e54 100644 --- a/lib/subghz/protocols/dooya.c +++ b/lib/subghz/protocols/dooya.c @@ -146,31 +146,31 @@ static bool subghz_protocol_encoder_dooya_get_upload(SubGhzProtocolEncoderDooya* return true; } -bool subghz_protocol_encoder_dooya_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + subghz_protocol_encoder_dooya_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolEncoderDooya* instance = context; - bool res = false; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; do { - if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { - FURI_LOG_E(TAG, "Deserialize error"); - break; - } - if(instance->generic.data_count_bit != - subghz_protocol_dooya_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); + ret = subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_dooya_const.min_count_bit_for_found); + if(ret != SubGhzProtocolStatusOk) { break; } //optional parameter parameter flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - if(!subghz_protocol_encoder_dooya_get_upload(instance)) break; + if(!subghz_protocol_encoder_dooya_get_upload(instance)) { + ret = SubGhzProtocolStatusErrorEncoderGetUpload; + break; + } instance->encoder.is_running = true; - - res = true; } while(false); - return res; + return ret; } void subghz_protocol_encoder_dooya_stop(void* context) { @@ -354,7 +354,7 @@ uint8_t subghz_protocol_decoder_dooya_get_hash_data(void* context) { &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } -bool subghz_protocol_decoder_dooya_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_dooya_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset) { @@ -363,22 +363,12 @@ bool subghz_protocol_decoder_dooya_serialize( return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); } -bool subghz_protocol_decoder_dooya_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + subghz_protocol_decoder_dooya_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolDecoderDooya* instance = context; - bool ret = false; - do { - if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { - break; - } - if(instance->generic.data_count_bit != - subghz_protocol_dooya_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); - break; - } - ret = true; - } while(false); - return ret; + return subghz_block_generic_deserialize_check_count_bit( + &instance->generic, flipper_format, subghz_protocol_dooya_const.min_count_bit_for_found); } /** diff --git a/lib/subghz/protocols/dooya.h b/lib/subghz/protocols/dooya.h index f0cf843c0d8..ffe9d41eff7 100644 --- a/lib/subghz/protocols/dooya.h +++ b/lib/subghz/protocols/dooya.h @@ -28,9 +28,10 @@ void subghz_protocol_encoder_dooya_free(void* context); * Deserialize and generating an upload to send. * @param context Pointer to a SubGhzProtocolEncoderDooya instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool subghz_protocol_encoder_dooya_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_encoder_dooya_deserialize(void* context, FlipperFormat* flipper_format); /** * Forced transmission stop. @@ -84,9 +85,9 @@ uint8_t subghz_protocol_decoder_dooya_get_hash_data(void* context); * @param context Pointer to a SubGhzProtocolDecoderDooya instance * @param flipper_format Pointer to a FlipperFormat instance * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return true On success + * @return status */ -bool subghz_protocol_decoder_dooya_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_dooya_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset); @@ -95,9 +96,10 @@ bool subghz_protocol_decoder_dooya_serialize( * Deserialize data SubGhzProtocolDecoderDooya. * @param context Pointer to a SubGhzProtocolDecoderDooya instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool subghz_protocol_decoder_dooya_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_decoder_dooya_deserialize(void* context, FlipperFormat* flipper_format); /** * Getting a textual representation of the received data. diff --git a/lib/subghz/protocols/faac_slh.c b/lib/subghz/protocols/faac_slh.c index ad186738355..d9dd6840811 100644 --- a/lib/subghz/protocols/faac_slh.c +++ b/lib/subghz/protocols/faac_slh.c @@ -180,7 +180,7 @@ uint8_t subghz_protocol_decoder_faac_slh_get_hash_data(void* context) { &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } -bool subghz_protocol_decoder_faac_slh_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_faac_slh_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset) { @@ -189,22 +189,14 @@ bool subghz_protocol_decoder_faac_slh_serialize( return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); } -bool subghz_protocol_decoder_faac_slh_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + subghz_protocol_decoder_faac_slh_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolDecoderFaacSLH* instance = context; - bool ret = false; - do { - if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { - break; - } - if(instance->generic.data_count_bit != - subghz_protocol_faac_slh_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); - break; - } - ret = true; - } while(false); - return ret; + return subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_faac_slh_const.min_count_bit_for_found); } void subghz_protocol_decoder_faac_slh_get_string(void* context, FuriString* output) { diff --git a/lib/subghz/protocols/faac_slh.h b/lib/subghz/protocols/faac_slh.h index 52c265d20b8..9d9a9e4044e 100644 --- a/lib/subghz/protocols/faac_slh.h +++ b/lib/subghz/protocols/faac_slh.h @@ -50,9 +50,9 @@ uint8_t subghz_protocol_decoder_faac_slh_get_hash_data(void* context); * @param context Pointer to a SubGhzProtocolDecoderFaacSLH instance * @param flipper_format Pointer to a FlipperFormat instance * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return true On success + * @return status */ -bool subghz_protocol_decoder_faac_slh_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_faac_slh_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset); @@ -61,9 +61,10 @@ bool subghz_protocol_decoder_faac_slh_serialize( * Deserialize data SubGhzProtocolDecoderFaacSLH. * @param context Pointer to a SubGhzProtocolDecoderFaacSLH instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool subghz_protocol_decoder_faac_slh_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_decoder_faac_slh_deserialize(void* context, FlipperFormat* flipper_format); /** * Getting a textual representation of the received data. diff --git a/lib/subghz/protocols/gate_tx.c b/lib/subghz/protocols/gate_tx.c index 4c7c2d48458..51a424fed94 100644 --- a/lib/subghz/protocols/gate_tx.c +++ b/lib/subghz/protocols/gate_tx.c @@ -129,31 +129,31 @@ static bool subghz_protocol_encoder_gate_tx_get_upload(SubGhzProtocolEncoderGate return true; } -bool subghz_protocol_encoder_gate_tx_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + subghz_protocol_encoder_gate_tx_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolEncoderGateTx* instance = context; - bool res = false; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; do { - if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { - FURI_LOG_E(TAG, "Deserialize error"); - break; - } - if(instance->generic.data_count_bit != - subghz_protocol_gate_tx_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); + ret = subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_gate_tx_const.min_count_bit_for_found); + if(ret != SubGhzProtocolStatusOk) { break; } //optional parameter parameter flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - if(!subghz_protocol_encoder_gate_tx_get_upload(instance)) break; + if(!subghz_protocol_encoder_gate_tx_get_upload(instance)) { + ret = SubGhzProtocolStatusErrorEncoderGetUpload; + break; + } instance->encoder.is_running = true; - - res = true; } while(false); - return res; + return ret; } void subghz_protocol_encoder_gate_tx_stop(void* context) { @@ -290,7 +290,7 @@ uint8_t subghz_protocol_decoder_gate_tx_get_hash_data(void* context) { &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } -bool subghz_protocol_decoder_gate_tx_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_gate_tx_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset) { @@ -299,22 +299,12 @@ bool subghz_protocol_decoder_gate_tx_serialize( return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); } -bool subghz_protocol_decoder_gate_tx_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + subghz_protocol_decoder_gate_tx_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolDecoderGateTx* instance = context; - bool ret = false; - do { - if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { - break; - } - if(instance->generic.data_count_bit != - subghz_protocol_gate_tx_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); - break; - } - ret = true; - } while(false); - return ret; + return subghz_block_generic_deserialize_check_count_bit( + &instance->generic, flipper_format, subghz_protocol_gate_tx_const.min_count_bit_for_found); } void subghz_protocol_decoder_gate_tx_get_string(void* context, FuriString* output) { diff --git a/lib/subghz/protocols/gate_tx.h b/lib/subghz/protocols/gate_tx.h index 4bfba3597cd..a6abede0d7f 100644 --- a/lib/subghz/protocols/gate_tx.h +++ b/lib/subghz/protocols/gate_tx.h @@ -28,9 +28,10 @@ void subghz_protocol_encoder_gate_tx_free(void* context); * Deserialize and generating an upload to send. * @param context Pointer to a SubGhzProtocolEncoderGateTx instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool subghz_protocol_encoder_gate_tx_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_encoder_gate_tx_deserialize(void* context, FlipperFormat* flipper_format); /** * Forced transmission stop. @@ -84,9 +85,9 @@ uint8_t subghz_protocol_decoder_gate_tx_get_hash_data(void* context); * @param context Pointer to a SubGhzProtocolDecoderGateTx instance * @param flipper_format Pointer to a FlipperFormat instance * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return true On success + * @return status */ -bool subghz_protocol_decoder_gate_tx_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_gate_tx_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset); @@ -95,9 +96,10 @@ bool subghz_protocol_decoder_gate_tx_serialize( * Deserialize data SubGhzProtocolDecoderGateTx. * @param context Pointer to a SubGhzProtocolDecoderGateTx instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool subghz_protocol_decoder_gate_tx_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_decoder_gate_tx_deserialize(void* context, FlipperFormat* flipper_format); /** * Getting a textual representation of the received data. diff --git a/lib/subghz/protocols/holtek.c b/lib/subghz/protocols/holtek.c index 8aaad3b7177..294bd124d3c 100644 --- a/lib/subghz/protocols/holtek.c +++ b/lib/subghz/protocols/holtek.c @@ -142,31 +142,31 @@ static bool subghz_protocol_encoder_holtek_get_upload(SubGhzProtocolEncoderHolte return true; } -bool subghz_protocol_encoder_holtek_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + subghz_protocol_encoder_holtek_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolEncoderHoltek* instance = context; - bool res = false; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; do { - if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { - FURI_LOG_E(TAG, "Deserialize error"); - break; - } - if(instance->generic.data_count_bit != - subghz_protocol_holtek_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); + ret = subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_holtek_const.min_count_bit_for_found); + if(ret != SubGhzProtocolStatusOk) { break; } //optional parameter parameter flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - if(!subghz_protocol_encoder_holtek_get_upload(instance)) break; + if(!subghz_protocol_encoder_holtek_get_upload(instance)) { + ret = SubGhzProtocolStatusErrorEncoderGetUpload; + break; + } instance->encoder.is_running = true; - - res = true; } while(false); - return res; + return ret; } void subghz_protocol_encoder_holtek_stop(void* context) { @@ -322,7 +322,7 @@ uint8_t subghz_protocol_decoder_holtek_get_hash_data(void* context) { &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } -bool subghz_protocol_decoder_holtek_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_holtek_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset) { @@ -331,22 +331,12 @@ bool subghz_protocol_decoder_holtek_serialize( return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); } -bool subghz_protocol_decoder_holtek_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + subghz_protocol_decoder_holtek_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolDecoderHoltek* instance = context; - bool ret = false; - do { - if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { - break; - } - if(instance->generic.data_count_bit != - subghz_protocol_holtek_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); - break; - } - ret = true; - } while(false); - return ret; + return subghz_block_generic_deserialize_check_count_bit( + &instance->generic, flipper_format, subghz_protocol_holtek_const.min_count_bit_for_found); } void subghz_protocol_decoder_holtek_get_string(void* context, FuriString* output) { diff --git a/lib/subghz/protocols/holtek.h b/lib/subghz/protocols/holtek.h index 252a26dc7c4..19081308d3d 100644 --- a/lib/subghz/protocols/holtek.h +++ b/lib/subghz/protocols/holtek.h @@ -28,9 +28,10 @@ void subghz_protocol_encoder_holtek_free(void* context); * Deserialize and generating an upload to send. * @param context Pointer to a SubGhzProtocolEncoderHoltek instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool subghz_protocol_encoder_holtek_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_encoder_holtek_deserialize(void* context, FlipperFormat* flipper_format); /** * Forced transmission stop. @@ -84,9 +85,9 @@ uint8_t subghz_protocol_decoder_holtek_get_hash_data(void* context); * @param context Pointer to a SubGhzProtocolDecoderHoltek instance * @param flipper_format Pointer to a FlipperFormat instance * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return true On success + * @return status */ -bool subghz_protocol_decoder_holtek_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_holtek_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset); @@ -95,9 +96,10 @@ bool subghz_protocol_decoder_holtek_serialize( * Deserialize data SubGhzProtocolDecoderHoltek. * @param context Pointer to a SubGhzProtocolDecoderHoltek instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool subghz_protocol_decoder_holtek_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_decoder_holtek_deserialize(void* context, FlipperFormat* flipper_format); /** * Getting a textual representation of the received data. diff --git a/lib/subghz/protocols/holtek_ht12x.c b/lib/subghz/protocols/holtek_ht12x.c index 169387dedd0..831f824dd64 100644 --- a/lib/subghz/protocols/holtek_ht12x.c +++ b/lib/subghz/protocols/holtek_ht12x.c @@ -147,39 +147,41 @@ static bool return true; } -bool subghz_protocol_encoder_holtek_th12x_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + subghz_protocol_encoder_holtek_th12x_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolEncoderHoltek_HT12X* instance = context; - bool res = false; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; do { - if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { - FURI_LOG_E(TAG, "Deserialize error"); + ret = subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_holtek_th12x_const.min_count_bit_for_found); + if(ret != SubGhzProtocolStatusOk) { break; } if(!flipper_format_rewind(flipper_format)) { FURI_LOG_E(TAG, "Rewind error"); + ret = SubGhzProtocolStatusErrorParserOthers; break; } if(!flipper_format_read_uint32(flipper_format, "TE", (uint32_t*)&instance->te, 1)) { FURI_LOG_E(TAG, "Missing TE"); - break; - } - if(instance->generic.data_count_bit != - subghz_protocol_holtek_th12x_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); + ret = SubGhzProtocolStatusErrorParserTe; break; } //optional parameter parameter flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - if(!subghz_protocol_encoder_holtek_th12x_get_upload(instance)) break; + if(!subghz_protocol_encoder_holtek_th12x_get_upload(instance)) { + ret = SubGhzProtocolStatusErrorEncoderGetUpload; + break; + } instance->encoder.is_running = true; - - res = true; } while(false); - return res; + return ret; } void subghz_protocol_encoder_holtek_th12x_stop(void* context) { @@ -327,42 +329,45 @@ uint8_t subghz_protocol_decoder_holtek_th12x_get_hash_data(void* context) { &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } -bool subghz_protocol_decoder_holtek_th12x_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_holtek_th12x_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset) { furi_assert(context); SubGhzProtocolDecoderHoltek_HT12X* instance = context; - bool res = subghz_block_generic_serialize(&instance->generic, flipper_format, preset); - if(res && !flipper_format_write_uint32(flipper_format, "TE", &instance->te, 1)) { + SubGhzProtocolStatus ret = + subghz_block_generic_serialize(&instance->generic, flipper_format, preset); + if((ret == SubGhzProtocolStatusOk) && + !flipper_format_write_uint32(flipper_format, "TE", &instance->te, 1)) { FURI_LOG_E(TAG, "Unable to add TE"); - res = false; + ret = SubGhzProtocolStatusErrorParserTe; } - return res; + return ret; } -bool subghz_protocol_decoder_holtek_th12x_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + subghz_protocol_decoder_holtek_th12x_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolDecoderHoltek_HT12X* instance = context; - bool ret = false; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; do { - if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { - break; - } - if(instance->generic.data_count_bit != - subghz_protocol_holtek_th12x_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); + ret = subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_holtek_th12x_const.min_count_bit_for_found); + if(ret != SubGhzProtocolStatusOk) { break; } if(!flipper_format_rewind(flipper_format)) { FURI_LOG_E(TAG, "Rewind error"); + ret = SubGhzProtocolStatusErrorParserOthers; break; } if(!flipper_format_read_uint32(flipper_format, "TE", (uint32_t*)&instance->te, 1)) { FURI_LOG_E(TAG, "Missing TE"); + ret = SubGhzProtocolStatusErrorParserTe; break; } - ret = true; } while(false); return ret; } diff --git a/lib/subghz/protocols/holtek_ht12x.h b/lib/subghz/protocols/holtek_ht12x.h index 7b5c31dd739..500c061aa3c 100644 --- a/lib/subghz/protocols/holtek_ht12x.h +++ b/lib/subghz/protocols/holtek_ht12x.h @@ -28,9 +28,10 @@ void subghz_protocol_encoder_holtek_th12x_free(void* context); * Deserialize and generating an upload to send. * @param context Pointer to a SubGhzProtocolEncoderHoltek_HT12X instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool subghz_protocol_encoder_holtek_th12x_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_encoder_holtek_th12x_deserialize(void* context, FlipperFormat* flipper_format); /** * Forced transmission stop. @@ -84,9 +85,9 @@ uint8_t subghz_protocol_decoder_holtek_th12x_get_hash_data(void* context); * @param context Pointer to a SubGhzProtocolDecoderHoltek_HT12X instance * @param flipper_format Pointer to a FlipperFormat instance * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return true On success + * @return status */ -bool subghz_protocol_decoder_holtek_th12x_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_holtek_th12x_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset); @@ -95,9 +96,10 @@ bool subghz_protocol_decoder_holtek_th12x_serialize( * Deserialize data SubGhzProtocolDecoderHoltek_HT12X. * @param context Pointer to a SubGhzProtocolDecoderHoltek_HT12X instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool subghz_protocol_decoder_holtek_th12x_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_decoder_holtek_th12x_deserialize(void* context, FlipperFormat* flipper_format); /** * Getting a textual representation of the received data. diff --git a/lib/subghz/protocols/honeywell_wdb.c b/lib/subghz/protocols/honeywell_wdb.c index 3b940fc6776..7fd8d66d6f2 100644 --- a/lib/subghz/protocols/honeywell_wdb.c +++ b/lib/subghz/protocols/honeywell_wdb.c @@ -142,33 +142,32 @@ static bool subghz_protocol_encoder_honeywell_wdb_get_upload( return true; } -bool subghz_protocol_encoder_honeywell_wdb_deserialize( +SubGhzProtocolStatus subghz_protocol_encoder_honeywell_wdb_deserialize( void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolEncoderHoneywell_WDB* instance = context; - bool res = false; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; do { - if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { - FURI_LOG_E(TAG, "Deserialize error"); - break; - } - if(instance->generic.data_count_bit != - subghz_protocol_honeywell_wdb_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); + ret = subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_honeywell_wdb_const.min_count_bit_for_found); + if(ret != SubGhzProtocolStatusOk) { break; } //optional parameter parameter flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - if(!subghz_protocol_encoder_honeywell_wdb_get_upload(instance)) break; + if(!subghz_protocol_encoder_honeywell_wdb_get_upload(instance)) { + ret = SubGhzProtocolStatusErrorEncoderGetUpload; + break; + } instance->encoder.is_running = true; - - res = true; } while(false); - return res; + return ret; } void subghz_protocol_encoder_honeywell_wdb_stop(void* context) { @@ -345,7 +344,7 @@ uint8_t subghz_protocol_decoder_honeywell_wdb_get_hash_data(void* context) { &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } -bool subghz_protocol_decoder_honeywell_wdb_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_honeywell_wdb_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset) { @@ -354,24 +353,15 @@ bool subghz_protocol_decoder_honeywell_wdb_serialize( return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); } -bool subghz_protocol_decoder_honeywell_wdb_deserialize( +SubGhzProtocolStatus subghz_protocol_decoder_honeywell_wdb_deserialize( void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolDecoderHoneywell_WDB* instance = context; - bool ret = false; - do { - if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { - break; - } - if(instance->generic.data_count_bit != - subghz_protocol_honeywell_wdb_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); - break; - } - ret = true; - } while(false); - return ret; + return subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_honeywell_wdb_const.min_count_bit_for_found); } void subghz_protocol_decoder_honeywell_wdb_get_string(void* context, FuriString* output) { diff --git a/lib/subghz/protocols/honeywell_wdb.h b/lib/subghz/protocols/honeywell_wdb.h index 828631837d6..91728691b05 100644 --- a/lib/subghz/protocols/honeywell_wdb.h +++ b/lib/subghz/protocols/honeywell_wdb.h @@ -28,11 +28,10 @@ void subghz_protocol_encoder_honeywell_wdb_free(void* context); * Deserialize and generating an upload to send. * @param context Pointer to a SubGhzProtocolEncoderHoneywell_WDB instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool subghz_protocol_encoder_honeywell_wdb_deserialize( - void* context, - FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_encoder_honeywell_wdb_deserialize(void* context, FlipperFormat* flipper_format); /** * Forced transmission stop. @@ -86,9 +85,9 @@ uint8_t subghz_protocol_decoder_honeywell_wdb_get_hash_data(void* context); * @param context Pointer to a SubGhzProtocolDecoderHoneywell_WDB instance * @param flipper_format Pointer to a FlipperFormat instance * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return true On success + * @return status */ -bool subghz_protocol_decoder_honeywell_wdb_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_honeywell_wdb_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset); @@ -97,11 +96,10 @@ bool subghz_protocol_decoder_honeywell_wdb_serialize( * Deserialize data SubGhzProtocolDecoderHoneywell_WDB. * @param context Pointer to a SubGhzProtocolDecoderHoneywell_WDB instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool subghz_protocol_decoder_honeywell_wdb_deserialize( - void* context, - FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_decoder_honeywell_wdb_deserialize(void* context, FlipperFormat* flipper_format); /** * Getting a textual representation of the received data. diff --git a/lib/subghz/protocols/hormann.c b/lib/subghz/protocols/hormann.c index 67b8cdfca42..4c5c68cc449 100644 --- a/lib/subghz/protocols/hormann.c +++ b/lib/subghz/protocols/hormann.c @@ -140,31 +140,31 @@ static bool subghz_protocol_encoder_hormann_get_upload(SubGhzProtocolEncoderHorm return true; } -bool subghz_protocol_encoder_hormann_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + subghz_protocol_encoder_hormann_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolEncoderHormann* instance = context; - bool res = false; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; do { - if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { - FURI_LOG_E(TAG, "Deserialize error"); - break; - } - if(instance->generic.data_count_bit != - subghz_protocol_hormann_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); + ret = subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_hormann_const.min_count_bit_for_found); + if(ret != SubGhzProtocolStatusOk) { break; } //optional parameter parameter flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - if(!subghz_protocol_encoder_hormann_get_upload(instance)) break; + if(!subghz_protocol_encoder_hormann_get_upload(instance)) { + ret = SubGhzProtocolStatusErrorEncoderGetUpload; + break; + } instance->encoder.is_running = true; - - res = true; } while(false); - return res; + return ret; } void subghz_protocol_encoder_hormann_stop(void* context) { @@ -295,7 +295,7 @@ uint8_t subghz_protocol_decoder_hormann_get_hash_data(void* context) { &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } -bool subghz_protocol_decoder_hormann_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_hormann_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset) { @@ -304,22 +304,12 @@ bool subghz_protocol_decoder_hormann_serialize( return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); } -bool subghz_protocol_decoder_hormann_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + subghz_protocol_decoder_hormann_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolDecoderHormann* instance = context; - bool ret = false; - do { - if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { - break; - } - if(instance->generic.data_count_bit != - subghz_protocol_hormann_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); - break; - } - ret = true; - } while(false); - return ret; + return subghz_block_generic_deserialize_check_count_bit( + &instance->generic, flipper_format, subghz_protocol_hormann_const.min_count_bit_for_found); } void subghz_protocol_decoder_hormann_get_string(void* context, FuriString* output) { diff --git a/lib/subghz/protocols/hormann.h b/lib/subghz/protocols/hormann.h index 857a50041bc..8cb45aec328 100644 --- a/lib/subghz/protocols/hormann.h +++ b/lib/subghz/protocols/hormann.h @@ -28,9 +28,10 @@ void subghz_protocol_encoder_hormann_free(void* context); * Deserialize and generating an upload to send. * @param context Pointer to a SubGhzProtocolEncoderHormann instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool subghz_protocol_encoder_hormann_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_encoder_hormann_deserialize(void* context, FlipperFormat* flipper_format); /** * Forced transmission stop. @@ -84,9 +85,9 @@ uint8_t subghz_protocol_decoder_hormann_get_hash_data(void* context); * @param context Pointer to a SubGhzProtocolDecoderHormann instance * @param flipper_format Pointer to a FlipperFormat instance * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return true On success + * @return status */ -bool subghz_protocol_decoder_hormann_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_hormann_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset); @@ -95,9 +96,10 @@ bool subghz_protocol_decoder_hormann_serialize( * Deserialize data SubGhzProtocolDecoderHormann. * @param context Pointer to a SubGhzProtocolDecoderHormann instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool subghz_protocol_decoder_hormann_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_decoder_hormann_deserialize(void* context, FlipperFormat* flipper_format); /** * Getting a textual representation of the received data. diff --git a/lib/subghz/protocols/ido.c b/lib/subghz/protocols/ido.c index 31ff20e420f..90c0fb0e38c 100644 --- a/lib/subghz/protocols/ido.c +++ b/lib/subghz/protocols/ido.c @@ -179,7 +179,7 @@ uint8_t subghz_protocol_decoder_ido_get_hash_data(void* context) { &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } -bool subghz_protocol_decoder_ido_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_ido_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset) { @@ -188,21 +188,12 @@ bool subghz_protocol_decoder_ido_serialize( return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); } -bool subghz_protocol_decoder_ido_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + subghz_protocol_decoder_ido_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolDecoderIDo* instance = context; - bool ret = false; - do { - if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { - break; - } - if(instance->generic.data_count_bit != subghz_protocol_ido_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); - break; - } - ret = true; - } while(false); - return ret; + return subghz_block_generic_deserialize_check_count_bit( + &instance->generic, flipper_format, subghz_protocol_ido_const.min_count_bit_for_found); } void subghz_protocol_decoder_ido_get_string(void* context, FuriString* output) { diff --git a/lib/subghz/protocols/ido.h b/lib/subghz/protocols/ido.h index 634f6ff89cc..9493202466b 100644 --- a/lib/subghz/protocols/ido.h +++ b/lib/subghz/protocols/ido.h @@ -50,9 +50,9 @@ uint8_t subghz_protocol_decoder_ido_get_hash_data(void* context); * @param context Pointer to a SubGhzProtocolDecoderIDo instance * @param flipper_format Pointer to a FlipperFormat instance * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return true On success + * @return status */ -bool subghz_protocol_decoder_ido_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_ido_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset); @@ -61,9 +61,10 @@ bool subghz_protocol_decoder_ido_serialize( * Deserialize data SubGhzProtocolDecoderIDo. * @param context Pointer to a SubGhzProtocolDecoderIDo instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool subghz_protocol_decoder_ido_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_decoder_ido_deserialize(void* context, FlipperFormat* flipper_format); /** * Getting a textual representation of the received data. diff --git a/lib/subghz/protocols/intertechno_v3.c b/lib/subghz/protocols/intertechno_v3.c index 2c4e514cce3..7fe95299555 100644 --- a/lib/subghz/protocols/intertechno_v3.c +++ b/lib/subghz/protocols/intertechno_v3.c @@ -158,34 +158,36 @@ static bool subghz_protocol_encoder_intertechno_v3_get_upload( return true; } -bool subghz_protocol_encoder_intertechno_v3_deserialize( +SubGhzProtocolStatus subghz_protocol_encoder_intertechno_v3_deserialize( void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolEncoderIntertechno_V3* instance = context; - bool res = false; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; do { - if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { - FURI_LOG_E(TAG, "Deserialize error"); + ret = subghz_block_generic_deserialize(&instance->generic, flipper_format); + if(ret != SubGhzProtocolStatusOk) { break; } if((instance->generic.data_count_bit != subghz_protocol_intertechno_v3_const.min_count_bit_for_found) && (instance->generic.data_count_bit != INTERTECHNO_V3_DIMMING_COUNT_BIT)) { FURI_LOG_E(TAG, "Wrong number of bits in key"); + ret = SubGhzProtocolStatusErrorValueBitCount; break; } //optional parameter parameter flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - if(!subghz_protocol_encoder_intertechno_v3_get_upload(instance)) break; + if(!subghz_protocol_encoder_intertechno_v3_get_upload(instance)) { + ret = SubGhzProtocolStatusErrorEncoderGetUpload; + break; + } instance->encoder.is_running = true; - - res = true; } while(false); - return res; + return ret; } void subghz_protocol_encoder_intertechno_v3_stop(void* context) { @@ -404,7 +406,7 @@ uint8_t subghz_protocol_decoder_intertechno_v3_get_hash_data(void* context) { &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } -bool subghz_protocol_decoder_intertechno_v3_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_intertechno_v3_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset) { @@ -413,23 +415,24 @@ bool subghz_protocol_decoder_intertechno_v3_serialize( return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); } -bool subghz_protocol_decoder_intertechno_v3_deserialize( +SubGhzProtocolStatus subghz_protocol_decoder_intertechno_v3_deserialize( void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolDecoderIntertechno_V3* instance = context; - bool ret = false; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; do { - if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + ret = subghz_block_generic_deserialize(&instance->generic, flipper_format); + if(ret != SubGhzProtocolStatusOk) { break; } if((instance->generic.data_count_bit != subghz_protocol_intertechno_v3_const.min_count_bit_for_found) && (instance->generic.data_count_bit != INTERTECHNO_V3_DIMMING_COUNT_BIT)) { FURI_LOG_E(TAG, "Wrong number of bits in key"); + ret = SubGhzProtocolStatusErrorValueBitCount; break; } - ret = true; } while(false); return ret; } diff --git a/lib/subghz/protocols/intertechno_v3.h b/lib/subghz/protocols/intertechno_v3.h index ffee14b04f9..4d1c24cb6b2 100644 --- a/lib/subghz/protocols/intertechno_v3.h +++ b/lib/subghz/protocols/intertechno_v3.h @@ -28,9 +28,9 @@ void subghz_protocol_encoder_intertechno_v3_free(void* context); * Deserialize and generating an upload to send. * @param context Pointer to a SubGhzProtocolEncoderIntertechno_V3 instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return Starus error */ -bool subghz_protocol_encoder_intertechno_v3_deserialize( +SubGhzProtocolStatus subghz_protocol_encoder_intertechno_v3_deserialize( void* context, FlipperFormat* flipper_format); @@ -86,9 +86,9 @@ uint8_t subghz_protocol_decoder_intertechno_v3_get_hash_data(void* context); * @param context Pointer to a SubGhzProtocolDecoderIntertechno_V3 instance * @param flipper_format Pointer to a FlipperFormat instance * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return true On success + * @return Starus error */ -bool subghz_protocol_decoder_intertechno_v3_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_intertechno_v3_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset); @@ -97,9 +97,9 @@ bool subghz_protocol_decoder_intertechno_v3_serialize( * Deserialize data SubGhzProtocolDecoderIntertechno_V3. * @param context Pointer to a SubGhzProtocolDecoderIntertechno_V3 instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return Starus error */ -bool subghz_protocol_decoder_intertechno_v3_deserialize( +SubGhzProtocolStatus subghz_protocol_decoder_intertechno_v3_deserialize( void* context, FlipperFormat* flipper_format); diff --git a/lib/subghz/protocols/keeloq.c b/lib/subghz/protocols/keeloq.c index 4a3602fbeb3..57d1cd22d25 100644 --- a/lib/subghz/protocols/keeloq.c +++ b/lib/subghz/protocols/keeloq.c @@ -254,18 +254,17 @@ static bool return true; } -bool subghz_protocol_encoder_keeloq_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + subghz_protocol_encoder_keeloq_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolEncoderKeeloq* instance = context; - bool res = false; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; do { - if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { - FURI_LOG_E(TAG, "Deserialize error"); - break; - } - if(instance->generic.data_count_bit != - subghz_protocol_keeloq_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); + ret = subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_keeloq_const.min_count_bit_for_found); + if(ret != SubGhzProtocolStatusOk) { break; } subghz_protocol_keeloq_check_remote_controller( @@ -273,6 +272,7 @@ bool subghz_protocol_encoder_keeloq_deserialize(void* context, FlipperFormat* fl if(strcmp(instance->manufacture_name, "DoorHan") != 0) { FURI_LOG_E(TAG, "Wrong manufacturer name"); + ret = SubGhzProtocolStatusErrorParserOthers; break; } @@ -280,10 +280,13 @@ bool subghz_protocol_encoder_keeloq_deserialize(void* context, FlipperFormat* fl flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - if(!subghz_protocol_encoder_keeloq_get_upload(instance, instance->generic.btn)) break; - + if(!subghz_protocol_encoder_keeloq_get_upload(instance, instance->generic.btn)) { + ret = SubGhzProtocolStatusErrorEncoderGetUpload; + break; + } if(!flipper_format_rewind(flipper_format)) { FURI_LOG_E(TAG, "Rewind error"); + ret = SubGhzProtocolStatusErrorParserOthers; break; } uint8_t key_data[sizeof(uint64_t)] = {0}; @@ -292,15 +295,14 @@ bool subghz_protocol_encoder_keeloq_deserialize(void* context, FlipperFormat* fl } if(!flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(uint64_t))) { FURI_LOG_E(TAG, "Unable to add Key"); + ret = SubGhzProtocolStatusErrorParserKey; break; } instance->encoder.is_running = true; - - res = true; } while(false); - return res; + return ret; } void subghz_protocol_encoder_keeloq_stop(void* context) { @@ -669,7 +671,7 @@ uint8_t subghz_protocol_decoder_keeloq_get_hash_data(void* context) { &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } -bool subghz_protocol_decoder_keeloq_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_keeloq_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset) { @@ -678,34 +680,24 @@ bool subghz_protocol_decoder_keeloq_serialize( subghz_protocol_keeloq_check_remote_controller( &instance->generic, instance->keystore, &instance->manufacture_name); - bool res = subghz_block_generic_serialize(&instance->generic, flipper_format, preset); + SubGhzProtocolStatus res = + subghz_block_generic_serialize(&instance->generic, flipper_format, preset); - if(res && !flipper_format_write_string_cstr( - flipper_format, "Manufacture", instance->manufacture_name)) { + if((res == SubGhzProtocolStatusOk) && + !flipper_format_write_string_cstr( + flipper_format, "Manufacture", instance->manufacture_name)) { FURI_LOG_E(TAG, "Unable to add manufacture name"); - res = false; + res = SubGhzProtocolStatusErrorParserOthers; } return res; } -bool subghz_protocol_decoder_keeloq_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + subghz_protocol_decoder_keeloq_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolDecoderKeeloq* instance = context; - bool res = false; - do { - if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { - FURI_LOG_E(TAG, "Deserialize error"); - break; - } - if(instance->generic.data_count_bit != - subghz_protocol_keeloq_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); - break; - } - res = true; - } while(false); - - return res; + return subghz_block_generic_deserialize_check_count_bit( + &instance->generic, flipper_format, subghz_protocol_keeloq_const.min_count_bit_for_found); } void subghz_protocol_decoder_keeloq_get_string(void* context, FuriString* output) { diff --git a/lib/subghz/protocols/keeloq.h b/lib/subghz/protocols/keeloq.h index 7b1aaee237a..59cd9cf986f 100644 --- a/lib/subghz/protocols/keeloq.h +++ b/lib/subghz/protocols/keeloq.h @@ -48,9 +48,10 @@ bool subghz_protocol_keeloq_create_data( * Deserialize and generating an upload to send. * @param context Pointer to a SubGhzProtocolEncoderKeeloq instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool subghz_protocol_encoder_keeloq_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_encoder_keeloq_deserialize(void* context, FlipperFormat* flipper_format); /** * Forced transmission stop. @@ -104,9 +105,9 @@ uint8_t subghz_protocol_decoder_keeloq_get_hash_data(void* context); * @param context Pointer to a SubGhzProtocolDecoderKeeloq instance * @param flipper_format Pointer to a FlipperFormat instance * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return true On success + * @return SubGhzProtocolStatus */ -bool subghz_protocol_decoder_keeloq_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_keeloq_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset); @@ -115,9 +116,10 @@ bool subghz_protocol_decoder_keeloq_serialize( * Deserialize data SubGhzProtocolDecoderKeeloq. * @param context Pointer to a SubGhzProtocolDecoderKeeloq instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return SubGhzProtocolStatus */ -bool subghz_protocol_decoder_keeloq_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_decoder_keeloq_deserialize(void* context, FlipperFormat* flipper_format); /** * Getting a textual representation of the received data. diff --git a/lib/subghz/protocols/kia.c b/lib/subghz/protocols/kia.c index a5d9e37ef09..1d134f7babe 100644 --- a/lib/subghz/protocols/kia.c +++ b/lib/subghz/protocols/kia.c @@ -230,7 +230,7 @@ uint8_t subghz_protocol_decoder_kia_get_hash_data(void* context) { &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } -bool subghz_protocol_decoder_kia_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_kia_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset) { @@ -239,21 +239,12 @@ bool subghz_protocol_decoder_kia_serialize( return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); } -bool subghz_protocol_decoder_kia_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + subghz_protocol_decoder_kia_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolDecoderKIA* instance = context; - bool ret = false; - do { - if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { - break; - } - if(instance->generic.data_count_bit != subghz_protocol_kia_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); - break; - } - ret = true; - } while(false); - return ret; + return subghz_block_generic_deserialize_check_count_bit( + &instance->generic, flipper_format, subghz_protocol_kia_const.min_count_bit_for_found); } void subghz_protocol_decoder_kia_get_string(void* context, FuriString* output) { diff --git a/lib/subghz/protocols/kia.h b/lib/subghz/protocols/kia.h index a9afcf14984..749ff8afd27 100644 --- a/lib/subghz/protocols/kia.h +++ b/lib/subghz/protocols/kia.h @@ -50,9 +50,9 @@ uint8_t subghz_protocol_decoder_kia_get_hash_data(void* context); * @param context Pointer to a SubGhzProtocolDecoderKIA instance * @param flipper_format Pointer to a FlipperFormat instance * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return true On success + * @return status */ -bool subghz_protocol_decoder_kia_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_kia_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset); @@ -61,9 +61,10 @@ bool subghz_protocol_decoder_kia_serialize( * Deserialize data SubGhzProtocolDecoderKIA. * @param context Pointer to a SubGhzProtocolDecoderKIA instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool subghz_protocol_decoder_kia_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_decoder_kia_deserialize(void* context, FlipperFormat* flipper_format); /** * Getting a textual representation of the received data. diff --git a/lib/subghz/protocols/kinggates_stylo_4k.c b/lib/subghz/protocols/kinggates_stylo_4k.c index 2c8de0d2d21..cd027b99c7a 100644 --- a/lib/subghz/protocols/kinggates_stylo_4k.c +++ b/lib/subghz/protocols/kinggates_stylo_4k.c @@ -260,56 +260,56 @@ uint8_t subghz_protocol_decoder_kinggates_stylo_4k_get_hash_data(void* context) &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } -bool subghz_protocol_decoder_kinggates_stylo_4k_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_kinggates_stylo_4k_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset) { furi_assert(context); SubGhzProtocolDecoderKingGates_stylo_4k* instance = context; - bool res = subghz_block_generic_serialize(&instance->generic, flipper_format, preset); + SubGhzProtocolStatus ret = + subghz_block_generic_serialize(&instance->generic, flipper_format, 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->data >> (i * 8)) & 0xFF; } - if(res && !flipper_format_write_hex(flipper_format, "Data", key_data, sizeof(uint64_t))) { + if((ret == SubGhzProtocolStatusOk) && + !flipper_format_write_hex(flipper_format, "Data", key_data, sizeof(uint64_t))) { FURI_LOG_E(TAG, "Unable to add Data"); - res = false; + ret = SubGhzProtocolStatusErrorParserOthers; } - return res; - - return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); + return ret; } -bool subghz_protocol_decoder_kinggates_stylo_4k_deserialize( +SubGhzProtocolStatus subghz_protocol_decoder_kinggates_stylo_4k_deserialize( void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolDecoderKingGates_stylo_4k* instance = context; - bool ret = false; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; do { - if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { - break; - } - if(instance->generic.data_count_bit != - subghz_protocol_kinggates_stylo_4k_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); + ret = subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_kinggates_stylo_4k_const.min_count_bit_for_found); + if(ret != SubGhzProtocolStatusOk) { break; } if(!flipper_format_rewind(flipper_format)) { FURI_LOG_E(TAG, "Rewind error"); + ret = SubGhzProtocolStatusErrorParserOthers; break; } uint8_t key_data[sizeof(uint64_t)] = {0}; if(!flipper_format_read_hex(flipper_format, "Data", key_data, sizeof(uint64_t))) { FURI_LOG_E(TAG, "Missing Data"); + ret = SubGhzProtocolStatusErrorParserOthers; break; } for(uint8_t i = 0; i < sizeof(uint64_t); i++) { instance->data = instance->data << 8 | key_data[i]; } - ret = true; } while(false); return ret; } diff --git a/lib/subghz/protocols/kinggates_stylo_4k.h b/lib/subghz/protocols/kinggates_stylo_4k.h index c9f1cf380ad..64cbb904636 100644 --- a/lib/subghz/protocols/kinggates_stylo_4k.h +++ b/lib/subghz/protocols/kinggates_stylo_4k.h @@ -49,9 +49,9 @@ uint8_t subghz_protocol_decoder_kinggates_stylo_4k_get_hash_data(void* context); * @param context Pointer to a SubGhzProtocolDecoderKingGates_stylo_4k instance * @param flipper_format Pointer to a FlipperFormat instance * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return true On success + * @return status */ -bool subghz_protocol_decoder_kinggates_stylo_4k_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_kinggates_stylo_4k_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset); @@ -60,9 +60,9 @@ bool subghz_protocol_decoder_kinggates_stylo_4k_serialize( * Deserialize data SubGhzProtocolDecoderKingGates_stylo_4k. * @param context Pointer to a SubGhzProtocolDecoderKingGates_stylo_4k instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool subghz_protocol_decoder_kinggates_stylo_4k_deserialize( +SubGhzProtocolStatus subghz_protocol_decoder_kinggates_stylo_4k_deserialize( void* context, FlipperFormat* flipper_format); diff --git a/lib/subghz/protocols/linear.c b/lib/subghz/protocols/linear.c index 2fc8b20c872..8d37357966b 100644 --- a/lib/subghz/protocols/linear.c +++ b/lib/subghz/protocols/linear.c @@ -147,31 +147,31 @@ static bool subghz_protocol_encoder_linear_get_upload(SubGhzProtocolEncoderLinea return true; } -bool subghz_protocol_encoder_linear_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + subghz_protocol_encoder_linear_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolEncoderLinear* instance = context; - bool res = false; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; do { - if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { - FURI_LOG_E(TAG, "Deserialize error"); - break; - } - if(instance->generic.data_count_bit != - subghz_protocol_linear_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); + ret = subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_linear_const.min_count_bit_for_found); + if(ret != SubGhzProtocolStatusOk) { break; } //optional parameter parameter flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - if(!subghz_protocol_encoder_linear_get_upload(instance)) break; + if(!subghz_protocol_encoder_linear_get_upload(instance)) { + ret = SubGhzProtocolStatusErrorEncoderGetUpload; + break; + } instance->encoder.is_running = true; - - res = true; } while(false); - return res; + return ret; } void subghz_protocol_encoder_linear_stop(void* context) { @@ -300,7 +300,7 @@ uint8_t subghz_protocol_decoder_linear_get_hash_data(void* context) { &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } -bool subghz_protocol_decoder_linear_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_linear_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset) { @@ -309,22 +309,12 @@ bool subghz_protocol_decoder_linear_serialize( return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); } -bool subghz_protocol_decoder_linear_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + subghz_protocol_decoder_linear_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolDecoderLinear* instance = context; - bool ret = false; - do { - if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { - break; - } - if(instance->generic.data_count_bit != - subghz_protocol_linear_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); - break; - } - ret = true; - } while(false); - return ret; + return subghz_block_generic_deserialize_check_count_bit( + &instance->generic, flipper_format, subghz_protocol_linear_const.min_count_bit_for_found); } void subghz_protocol_decoder_linear_get_string(void* context, FuriString* output) { diff --git a/lib/subghz/protocols/linear.h b/lib/subghz/protocols/linear.h index 923337ac21e..b692b911c1e 100644 --- a/lib/subghz/protocols/linear.h +++ b/lib/subghz/protocols/linear.h @@ -28,9 +28,10 @@ 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 + * @return status */ -bool subghz_protocol_encoder_linear_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_encoder_linear_deserialize(void* context, FlipperFormat* flipper_format); /** * Forced transmission stop. @@ -84,9 +85,9 @@ uint8_t subghz_protocol_decoder_linear_get_hash_data(void* context); * @param context Pointer to a SubGhzProtocolDecoderLinear instance * @param flipper_format Pointer to a FlipperFormat instance * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return true On success + * @return status */ -bool subghz_protocol_decoder_linear_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_linear_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset); @@ -95,9 +96,10 @@ bool subghz_protocol_decoder_linear_serialize( * Deserialize data SubGhzProtocolDecoderLinear. * @param context Pointer to a SubGhzProtocolDecoderLinear instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool subghz_protocol_decoder_linear_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_decoder_linear_deserialize(void* context, FlipperFormat* flipper_format); /** * Getting a textual representation of the received data. diff --git a/lib/subghz/protocols/linear_delta3.c b/lib/subghz/protocols/linear_delta3.c index 869edac84e6..97ac5cc5a65 100644 --- a/lib/subghz/protocols/linear_delta3.c +++ b/lib/subghz/protocols/linear_delta3.c @@ -150,33 +150,32 @@ static bool return true; } -bool subghz_protocol_encoder_linear_delta3_deserialize( +SubGhzProtocolStatus subghz_protocol_encoder_linear_delta3_deserialize( void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolEncoderLinearDelta3* instance = context; - bool res = false; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; do { - if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { - FURI_LOG_E(TAG, "Deserialize error"); - break; - } - if(instance->generic.data_count_bit != - subghz_protocol_linear_delta3_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); + ret = subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_linear_delta3_const.min_count_bit_for_found); + if(ret != SubGhzProtocolStatusOk) { break; } //optional parameter parameter flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - if(!subghz_protocol_encoder_linear_delta3_get_upload(instance)) break; + if(!subghz_protocol_encoder_linear_delta3_get_upload(instance)) { + ret = SubGhzProtocolStatusErrorEncoderGetUpload; + break; + } instance->encoder.is_running = true; - - res = true; } while(false); - return res; + return ret; } void subghz_protocol_encoder_linear_delta3_stop(void* context) { @@ -312,7 +311,7 @@ uint8_t subghz_protocol_decoder_linear_delta3_get_hash_data(void* context) { &instance->decoder, (instance->decoder.decode_count_bit / 8)); } -bool subghz_protocol_decoder_linear_delta3_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_linear_delta3_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset) { @@ -321,24 +320,15 @@ bool subghz_protocol_decoder_linear_delta3_serialize( return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); } -bool subghz_protocol_decoder_linear_delta3_deserialize( +SubGhzProtocolStatus subghz_protocol_decoder_linear_delta3_deserialize( void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolDecoderLinearDelta3* instance = context; - bool ret = false; - do { - if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { - break; - } - if(instance->generic.data_count_bit != - subghz_protocol_linear_delta3_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); - break; - } - ret = true; - } while(false); - return ret; + return subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_linear_delta3_const.min_count_bit_for_found); } void subghz_protocol_decoder_linear_delta3_get_string(void* context, FuriString* output) { diff --git a/lib/subghz/protocols/linear_delta3.h b/lib/subghz/protocols/linear_delta3.h index 2f0a32e6827..22f6730f441 100644 --- a/lib/subghz/protocols/linear_delta3.h +++ b/lib/subghz/protocols/linear_delta3.h @@ -28,11 +28,10 @@ void subghz_protocol_encoder_linear_delta3_free(void* context); * Deserialize and generating an upload to send. * @param context Pointer to a SubGhzProtocolEncoderLinearDelta3 instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool subghz_protocol_encoder_linear_delta3_deserialize( - void* context, - FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_encoder_linear_delta3_deserialize(void* context, FlipperFormat* flipper_format); /** * Forced transmission stop. @@ -86,9 +85,9 @@ uint8_t subghz_protocol_decoder_linear_delta3_get_hash_data(void* context); * @param context Pointer to a SubGhzProtocolDecoderLinearDelta3 instance * @param flipper_format Pointer to a FlipperFormat instance * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return true On success + * @return status */ -bool subghz_protocol_decoder_linear_delta3_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_linear_delta3_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset); @@ -97,11 +96,10 @@ bool subghz_protocol_decoder_linear_delta3_serialize( * Deserialize data SubGhzProtocolDecoderLinearDelta3. * @param context Pointer to a SubGhzProtocolDecoderLinearDelta3 instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool subghz_protocol_decoder_linear_delta3_deserialize( - void* context, - FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_decoder_linear_delta3_deserialize(void* context, FlipperFormat* flipper_format); /** * Getting a textual representation of the received data. diff --git a/lib/subghz/protocols/magellan.c b/lib/subghz/protocols/magellan.c index 71497525423..1b8eccc36e3 100644 --- a/lib/subghz/protocols/magellan.c +++ b/lib/subghz/protocols/magellan.c @@ -150,31 +150,31 @@ static bool subghz_protocol_encoder_magellan_get_upload(SubGhzProtocolEncoderMag return true; } -bool subghz_protocol_encoder_magellan_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + subghz_protocol_encoder_magellan_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolEncoderMagellan* instance = context; - bool res = false; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; do { - if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { - FURI_LOG_E(TAG, "Deserialize error"); - break; - } - if(instance->generic.data_count_bit != - subghz_protocol_magellan_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); + ret = subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_magellan_const.min_count_bit_for_found); + if(ret != SubGhzProtocolStatusOk) { break; } //optional parameter parameter flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - if(!subghz_protocol_encoder_magellan_get_upload(instance)) break; + if(!subghz_protocol_encoder_magellan_get_upload(instance)) { + ret = SubGhzProtocolStatusErrorEncoderGetUpload; + break; + } instance->encoder.is_running = true; - - res = true; } while(false); - return res; + return ret; } void subghz_protocol_encoder_magellan_stop(void* context) { @@ -397,7 +397,7 @@ uint8_t subghz_protocol_decoder_magellan_get_hash_data(void* context) { &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } -bool subghz_protocol_decoder_magellan_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_magellan_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset) { @@ -406,22 +406,14 @@ bool subghz_protocol_decoder_magellan_serialize( return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); } -bool subghz_protocol_decoder_magellan_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + subghz_protocol_decoder_magellan_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolDecoderMagellan* instance = context; - bool ret = false; - do { - if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { - break; - } - if(instance->generic.data_count_bit != - subghz_protocol_magellan_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); - break; - } - ret = true; - } while(false); - return ret; + return subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_magellan_const.min_count_bit_for_found); } void subghz_protocol_decoder_magellan_get_string(void* context, FuriString* output) { diff --git a/lib/subghz/protocols/magellan.h b/lib/subghz/protocols/magellan.h index a179c9cb4b7..e0fb7ca52a0 100644 --- a/lib/subghz/protocols/magellan.h +++ b/lib/subghz/protocols/magellan.h @@ -28,9 +28,10 @@ void subghz_protocol_encoder_magellan_free(void* context); * Deserialize and generating an upload to send. * @param context Pointer to a SubGhzProtocolEncoderMagellan instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool subghz_protocol_encoder_magellan_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_encoder_magellan_deserialize(void* context, FlipperFormat* flipper_format); /** * Forced transmission stop. @@ -84,9 +85,9 @@ uint8_t subghz_protocol_decoder_magellan_get_hash_data(void* context); * @param context Pointer to a SubGhzProtocolDecoderMagellan instance * @param flipper_format Pointer to a FlipperFormat instance * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return true On success + * @return status */ -bool subghz_protocol_decoder_magellan_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_magellan_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset); @@ -95,9 +96,10 @@ bool subghz_protocol_decoder_magellan_serialize( * Deserialize data SubGhzProtocolDecoderMagellan. * @param context Pointer to a SubGhzProtocolDecoderMagellan instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool subghz_protocol_decoder_magellan_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_decoder_magellan_deserialize(void* context, FlipperFormat* flipper_format); /** * Getting a textual representation of the received data. diff --git a/lib/subghz/protocols/marantec.c b/lib/subghz/protocols/marantec.c index d557c29b0bb..fc4aa0dca4e 100644 --- a/lib/subghz/protocols/marantec.c +++ b/lib/subghz/protocols/marantec.c @@ -188,18 +188,17 @@ static void subghz_protocol_marantec_remote_controller(SubGhzBlockGeneric* insta instance->serial = ((instance->data >> 12) & 0xFFFFFF00) | ((instance->data >> 8) & 0xFF); } -bool subghz_protocol_encoder_marantec_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + subghz_protocol_encoder_marantec_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolEncoderMarantec* instance = context; - bool res = false; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; do { - if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { - FURI_LOG_E(TAG, "Deserialize error"); - break; - } - if(instance->generic.data_count_bit != - subghz_protocol_marantec_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); + ret = subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_marantec_const.min_count_bit_for_found); + if(ret != SubGhzProtocolStatusOk) { break; } //optional parameter parameter @@ -209,11 +208,9 @@ bool subghz_protocol_encoder_marantec_deserialize(void* context, FlipperFormat* subghz_protocol_marantec_remote_controller(&instance->generic); subghz_protocol_encoder_marantec_get_upload(instance); instance->encoder.is_running = true; - - res = true; } while(false); - return res; + return ret; } void subghz_protocol_encoder_marantec_stop(void* context) { @@ -346,7 +343,7 @@ uint8_t subghz_protocol_decoder_marantec_get_hash_data(void* context) { &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } -bool subghz_protocol_decoder_marantec_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_marantec_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset) { @@ -355,22 +352,14 @@ bool subghz_protocol_decoder_marantec_serialize( return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); } -bool subghz_protocol_decoder_marantec_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + subghz_protocol_decoder_marantec_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolDecoderMarantec* instance = context; - bool ret = false; - do { - if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { - break; - } - if(instance->generic.data_count_bit != - subghz_protocol_marantec_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); - break; - } - ret = true; - } while(false); - return ret; + return subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_marantec_const.min_count_bit_for_found); } void subghz_protocol_decoder_marantec_get_string(void* context, FuriString* output) { diff --git a/lib/subghz/protocols/marantec.h b/lib/subghz/protocols/marantec.h index e330ccf16b3..485c563b2fb 100644 --- a/lib/subghz/protocols/marantec.h +++ b/lib/subghz/protocols/marantec.h @@ -28,9 +28,10 @@ void subghz_protocol_encoder_marantec_free(void* context); * Deserialize and generating an upload to send. * @param context Pointer to a SubGhzProtocolEncoderMarantec instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool subghz_protocol_encoder_marantec_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_encoder_marantec_deserialize(void* context, FlipperFormat* flipper_format); /** * Forced transmission stop. @@ -84,9 +85,9 @@ uint8_t subghz_protocol_decoder_marantec_get_hash_data(void* context); * @param context Pointer to a SubGhzProtocolDecoderMarantec instance * @param flipper_format Pointer to a FlipperFormat instance * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return true On success + * @return status */ -bool subghz_protocol_decoder_marantec_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_marantec_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset); @@ -95,9 +96,10 @@ bool subghz_protocol_decoder_marantec_serialize( * Deserialize data SubGhzProtocolDecoderMarantec. * @param context Pointer to a SubGhzProtocolDecoderMarantec instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool subghz_protocol_decoder_marantec_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_decoder_marantec_deserialize(void* context, FlipperFormat* flipper_format); /** * Getting a textual representation of the received data. diff --git a/lib/subghz/protocols/megacode.c b/lib/subghz/protocols/megacode.c index 05b5b6894ad..ba58bc445b7 100644 --- a/lib/subghz/protocols/megacode.c +++ b/lib/subghz/protocols/megacode.c @@ -175,31 +175,31 @@ static bool subghz_protocol_encoder_megacode_get_upload(SubGhzProtocolEncoderMeg return true; } -bool subghz_protocol_encoder_megacode_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + subghz_protocol_encoder_megacode_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolEncoderMegaCode* instance = context; - bool res = false; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; do { - if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { - FURI_LOG_E(TAG, "Deserialize error"); - break; - } - if(instance->generic.data_count_bit != - subghz_protocol_megacode_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); + ret = subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_megacode_const.min_count_bit_for_found); + if(ret != SubGhzProtocolStatusOk) { break; } //optional parameter parameter flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - if(!subghz_protocol_encoder_megacode_get_upload(instance)) break; + if(!subghz_protocol_encoder_megacode_get_upload(instance)) { + ret = SubGhzProtocolStatusErrorEncoderGetUpload; + break; + } instance->encoder.is_running = true; - - res = true; } while(false); - return res; + return ret; } void subghz_protocol_encoder_megacode_stop(void* context) { @@ -381,7 +381,7 @@ uint8_t subghz_protocol_decoder_megacode_get_hash_data(void* context) { &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } -bool subghz_protocol_decoder_megacode_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_megacode_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset) { @@ -390,22 +390,14 @@ bool subghz_protocol_decoder_megacode_serialize( return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); } -bool subghz_protocol_decoder_megacode_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + subghz_protocol_decoder_megacode_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolDecoderMegaCode* instance = context; - bool ret = false; - do { - if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { - break; - } - if(instance->generic.data_count_bit != - subghz_protocol_megacode_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); - break; - } - ret = true; - } while(false); - return ret; + return subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_megacode_const.min_count_bit_for_found); } void subghz_protocol_decoder_megacode_get_string(void* context, FuriString* output) { diff --git a/lib/subghz/protocols/megacode.h b/lib/subghz/protocols/megacode.h index e31434fa307..616ecdf64ef 100644 --- a/lib/subghz/protocols/megacode.h +++ b/lib/subghz/protocols/megacode.h @@ -28,9 +28,10 @@ void subghz_protocol_encoder_megacode_free(void* context); * Deserialize and generating an upload to send. * @param context Pointer to a SubGhzProtocolEncoderMegaCode instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool subghz_protocol_encoder_megacode_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_encoder_megacode_deserialize(void* context, FlipperFormat* flipper_format); /** * Forced transmission stop. @@ -84,9 +85,9 @@ uint8_t subghz_protocol_decoder_megacode_get_hash_data(void* context); * @param context Pointer to a SubGhzProtocolDecoderMegaCode instance * @param flipper_format Pointer to a FlipperFormat instance * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return true On success + * @return status */ -bool subghz_protocol_decoder_megacode_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_megacode_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset); @@ -95,9 +96,10 @@ bool subghz_protocol_decoder_megacode_serialize( * Deserialize data SubGhzProtocolDecoderMegaCode. * @param context Pointer to a SubGhzProtocolDecoderMegaCode instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool subghz_protocol_decoder_megacode_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_decoder_megacode_deserialize(void* context, FlipperFormat* flipper_format); /** * Getting a textual representation of the received data. diff --git a/lib/subghz/protocols/nero_radio.c b/lib/subghz/protocols/nero_radio.c index c8126b1e152..d7731dca67b 100644 --- a/lib/subghz/protocols/nero_radio.c +++ b/lib/subghz/protocols/nero_radio.c @@ -154,31 +154,31 @@ static bool return true; } -bool subghz_protocol_encoder_nero_radio_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + subghz_protocol_encoder_nero_radio_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolEncoderNeroRadio* instance = context; - bool res = false; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; do { - if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { - FURI_LOG_E(TAG, "Deserialize error"); - break; - } - if(instance->generic.data_count_bit != - subghz_protocol_nero_radio_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); + ret = subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_nero_radio_const.min_count_bit_for_found); + if(ret != SubGhzProtocolStatusOk) { break; } //optional parameter parameter flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - if(!subghz_protocol_encoder_nero_radio_get_upload(instance)) break; + if(!subghz_protocol_encoder_nero_radio_get_upload(instance)) { + ret = SubGhzProtocolStatusErrorEncoderGetUpload; + break; + } instance->encoder.is_running = true; - - res = true; } while(false); - return res; + return ret; } void subghz_protocol_encoder_nero_radio_stop(void* context) { @@ -343,7 +343,7 @@ uint8_t subghz_protocol_decoder_nero_radio_get_hash_data(void* context) { &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } -bool subghz_protocol_decoder_nero_radio_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_nero_radio_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset) { @@ -352,22 +352,14 @@ bool subghz_protocol_decoder_nero_radio_serialize( return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); } -bool subghz_protocol_decoder_nero_radio_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + subghz_protocol_decoder_nero_radio_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolDecoderNeroRadio* instance = context; - bool ret = false; - do { - if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { - break; - } - if(instance->generic.data_count_bit != - subghz_protocol_nero_radio_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); - break; - } - ret = true; - } while(false); - return ret; + return subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_nero_radio_const.min_count_bit_for_found); } void subghz_protocol_decoder_nero_radio_get_string(void* context, FuriString* output) { diff --git a/lib/subghz/protocols/nero_radio.h b/lib/subghz/protocols/nero_radio.h index 361da6173c3..0598aee6ccd 100644 --- a/lib/subghz/protocols/nero_radio.h +++ b/lib/subghz/protocols/nero_radio.h @@ -28,9 +28,10 @@ void subghz_protocol_encoder_nero_radio_free(void* context); * Deserialize and generating an upload to send. * @param context Pointer to a SubGhzProtocolEncoderNeroRadio instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool subghz_protocol_encoder_nero_radio_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_encoder_nero_radio_deserialize(void* context, FlipperFormat* flipper_format); /** * Forced transmission stop. @@ -84,9 +85,9 @@ uint8_t subghz_protocol_decoder_nero_radio_get_hash_data(void* context); * @param context Pointer to a SubGhzProtocolDecoderNeroRadio instance * @param flipper_format Pointer to a FlipperFormat instance * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return true On success + * @return status */ -bool subghz_protocol_decoder_nero_radio_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_nero_radio_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset); @@ -95,9 +96,10 @@ bool subghz_protocol_decoder_nero_radio_serialize( * Deserialize data SubGhzProtocolDecoderNeroRadio. * @param context Pointer to a SubGhzProtocolDecoderNeroRadio instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool subghz_protocol_decoder_nero_radio_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_decoder_nero_radio_deserialize(void* context, FlipperFormat* flipper_format); /** * Getting a textual representation of the received data. diff --git a/lib/subghz/protocols/nero_sketch.c b/lib/subghz/protocols/nero_sketch.c index b124b717bdb..09cd0255ae9 100644 --- a/lib/subghz/protocols/nero_sketch.c +++ b/lib/subghz/protocols/nero_sketch.c @@ -148,31 +148,31 @@ static bool return true; } -bool subghz_protocol_encoder_nero_sketch_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + subghz_protocol_encoder_nero_sketch_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolEncoderNeroSketch* instance = context; - bool res = false; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; do { - if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { - FURI_LOG_E(TAG, "Deserialize error"); - break; - } - if(instance->generic.data_count_bit != - subghz_protocol_nero_sketch_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); + ret = subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_nero_sketch_const.min_count_bit_for_found); + if(ret != SubGhzProtocolStatusOk) { break; } //optional parameter parameter flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - if(!subghz_protocol_encoder_nero_sketch_get_upload(instance)) break; + if(!subghz_protocol_encoder_nero_sketch_get_upload(instance)) { + ret = SubGhzProtocolStatusErrorEncoderGetUpload; + break; + } instance->encoder.is_running = true; - - res = true; } while(false); - return res; + return ret; } void subghz_protocol_encoder_nero_sketch_stop(void* context) { @@ -328,7 +328,7 @@ uint8_t subghz_protocol_decoder_nero_sketch_get_hash_data(void* context) { &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } -bool subghz_protocol_decoder_nero_sketch_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_nero_sketch_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset) { @@ -337,22 +337,14 @@ bool subghz_protocol_decoder_nero_sketch_serialize( return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); } -bool subghz_protocol_decoder_nero_sketch_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + subghz_protocol_decoder_nero_sketch_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolDecoderNeroSketch* instance = context; - bool ret = false; - do { - if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { - break; - } - if(instance->generic.data_count_bit != - subghz_protocol_nero_sketch_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); - break; - } - ret = true; - } while(false); - return ret; + return subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_nero_sketch_const.min_count_bit_for_found); } void subghz_protocol_decoder_nero_sketch_get_string(void* context, FuriString* output) { diff --git a/lib/subghz/protocols/nero_sketch.h b/lib/subghz/protocols/nero_sketch.h index ac87fb00a63..b557772d20d 100644 --- a/lib/subghz/protocols/nero_sketch.h +++ b/lib/subghz/protocols/nero_sketch.h @@ -28,9 +28,10 @@ void subghz_protocol_encoder_nero_sketch_free(void* context); * Deserialize and generating an upload to send. * @param context Pointer to a SubGhzProtocolEncoderNeroSketch instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool subghz_protocol_encoder_nero_sketch_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_encoder_nero_sketch_deserialize(void* context, FlipperFormat* flipper_format); /** * Forced transmission stop. @@ -84,9 +85,9 @@ uint8_t subghz_protocol_decoder_nero_sketch_get_hash_data(void* context); * @param context Pointer to a SubGhzProtocolDecoderNeroSketch instance * @param flipper_format Pointer to a FlipperFormat instance * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return true On success + * @return status */ -bool subghz_protocol_decoder_nero_sketch_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_nero_sketch_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset); @@ -95,9 +96,10 @@ bool subghz_protocol_decoder_nero_sketch_serialize( * Deserialize data SubGhzProtocolDecoderNeroSketch. * @param context Pointer to a SubGhzProtocolDecoderNeroSketch instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool subghz_protocol_decoder_nero_sketch_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_decoder_nero_sketch_deserialize(void* context, FlipperFormat* flipper_format); /** * Getting a textual representation of the received data. diff --git a/lib/subghz/protocols/nice_flo.c b/lib/subghz/protocols/nice_flo.c index a57d5f4da54..af81d9f9056 100644 --- a/lib/subghz/protocols/nice_flo.c +++ b/lib/subghz/protocols/nice_flo.c @@ -129,13 +129,14 @@ static bool subghz_protocol_encoder_nice_flo_get_upload(SubGhzProtocolEncoderNic return true; } -bool subghz_protocol_encoder_nice_flo_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + subghz_protocol_encoder_nice_flo_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolEncoderNiceFlo* instance = context; - bool res = false; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; do { - if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { - FURI_LOG_E(TAG, "Deserialize error"); + ret = subghz_block_generic_deserialize(&instance->generic, flipper_format); + if(ret != SubGhzProtocolStatusOk) { break; } if((instance->generic.data_count_bit < @@ -143,19 +144,21 @@ bool subghz_protocol_encoder_nice_flo_deserialize(void* context, FlipperFormat* (instance->generic.data_count_bit > 2 * subghz_protocol_nice_flo_const.min_count_bit_for_found)) { FURI_LOG_E(TAG, "Wrong number of bits in key"); + ret = SubGhzProtocolStatusErrorValueBitCount; break; } //optional parameter parameter flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - if(!subghz_protocol_encoder_nice_flo_get_upload(instance)) break; + if(!subghz_protocol_encoder_nice_flo_get_upload(instance)) { + ret = SubGhzProtocolStatusErrorEncoderGetUpload; + break; + } instance->encoder.is_running = true; - - res = true; } while(false); - return res; + return ret; } void subghz_protocol_encoder_nice_flo_stop(void* context) { @@ -280,7 +283,7 @@ uint8_t subghz_protocol_decoder_nice_flo_get_hash_data(void* context) { &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } -bool subghz_protocol_decoder_nice_flo_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_nice_flo_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset) { @@ -289,12 +292,14 @@ bool subghz_protocol_decoder_nice_flo_serialize( return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); } -bool subghz_protocol_decoder_nice_flo_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + subghz_protocol_decoder_nice_flo_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolDecoderNiceFlo* instance = context; - bool ret = false; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; do { - if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + ret = subghz_block_generic_deserialize(&instance->generic, flipper_format); + if(ret != SubGhzProtocolStatusOk) { break; } if((instance->generic.data_count_bit < @@ -302,9 +307,9 @@ bool subghz_protocol_decoder_nice_flo_deserialize(void* context, FlipperFormat* (instance->generic.data_count_bit > 2 * subghz_protocol_nice_flo_const.min_count_bit_for_found)) { FURI_LOG_E(TAG, "Wrong number of bits in key"); + ret = SubGhzProtocolStatusErrorValueBitCount; break; } - ret = true; } while(false); return ret; } diff --git a/lib/subghz/protocols/nice_flo.h b/lib/subghz/protocols/nice_flo.h index e382e614618..9a4b53d1271 100644 --- a/lib/subghz/protocols/nice_flo.h +++ b/lib/subghz/protocols/nice_flo.h @@ -28,9 +28,10 @@ void subghz_protocol_encoder_nice_flo_free(void* context); * Deserialize and generating an upload to send. * @param context Pointer to a SubGhzProtocolEncoderNiceFlo instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool subghz_protocol_encoder_nice_flo_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_encoder_nice_flo_deserialize(void* context, FlipperFormat* flipper_format); /** * Forced transmission stop. @@ -84,9 +85,9 @@ uint8_t subghz_protocol_decoder_nice_flo_get_hash_data(void* context); * @param context Pointer to a SubGhzProtocolDecoderNiceFlo instance * @param flipper_format Pointer to a FlipperFormat instance * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return true On success + * @return status */ -bool subghz_protocol_decoder_nice_flo_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_nice_flo_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset); @@ -95,9 +96,10 @@ bool subghz_protocol_decoder_nice_flo_serialize( * Deserialize data SubGhzProtocolDecoderNiceFlo. * @param context Pointer to a SubGhzProtocolDecoderNiceFlo instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool subghz_protocol_decoder_nice_flo_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_decoder_nice_flo_deserialize(void* context, FlipperFormat* flipper_format); /** * Getting a textual representation of the received data. diff --git a/lib/subghz/protocols/nice_flor_s.c b/lib/subghz/protocols/nice_flor_s.c index dd5521a6442..d8f5a070a08 100644 --- a/lib/subghz/protocols/nice_flor_s.c +++ b/lib/subghz/protocols/nice_flor_s.c @@ -417,51 +417,55 @@ uint8_t subghz_protocol_decoder_nice_flor_s_get_hash_data(void* context) { &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } -bool subghz_protocol_decoder_nice_flor_s_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_nice_flor_s_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset) { furi_assert(context); SubGhzProtocolDecoderNiceFlorS* instance = context; - bool res = subghz_block_generic_serialize(&instance->generic, flipper_format, preset); + SubGhzProtocolStatus ret = + subghz_block_generic_serialize(&instance->generic, flipper_format, preset); if(instance->generic.data_count_bit == NICE_ONE_COUNT_BIT) { - if(res && + if((ret == SubGhzProtocolStatusOk) && !flipper_format_write_uint32(flipper_format, "Data", (uint32_t*)&instance->data, 1)) { FURI_LOG_E(TAG, "Unable to add Data"); - res = false; + ret = SubGhzProtocolStatusErrorParserOthers; } } - return res; + return ret; } -bool subghz_protocol_decoder_nice_flor_s_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + subghz_protocol_decoder_nice_flor_s_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolDecoderNiceFlorS* instance = context; - bool ret = false; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; do { - if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + ret = subghz_block_generic_deserialize(&instance->generic, flipper_format); + if(ret != SubGhzProtocolStatusOk) { break; } if((instance->generic.data_count_bit != subghz_protocol_nice_flor_s_const.min_count_bit_for_found) && (instance->generic.data_count_bit != NICE_ONE_COUNT_BIT)) { FURI_LOG_E(TAG, "Wrong number of bits in key"); + ret = SubGhzProtocolStatusErrorValueBitCount; break; } if(instance->generic.data_count_bit == NICE_ONE_COUNT_BIT) { if(!flipper_format_rewind(flipper_format)) { FURI_LOG_E(TAG, "Rewind error"); + ret = SubGhzProtocolStatusErrorParserOthers; break; } uint32_t temp = 0; if(!flipper_format_read_uint32(flipper_format, "Data", (uint32_t*)&temp, 1)) { FURI_LOG_E(TAG, "Missing Data"); + ret = SubGhzProtocolStatusErrorParserOthers; break; } instance->data = (uint64_t)temp; } - - ret = true; } while(false); return ret; } diff --git a/lib/subghz/protocols/nice_flor_s.h b/lib/subghz/protocols/nice_flor_s.h index 593a7915769..1ce61149cd6 100644 --- a/lib/subghz/protocols/nice_flor_s.h +++ b/lib/subghz/protocols/nice_flor_s.h @@ -50,9 +50,9 @@ uint8_t subghz_protocol_decoder_nice_flor_s_get_hash_data(void* context); * @param context Pointer to a SubGhzProtocolDecoderNiceFlorS instance * @param flipper_format Pointer to a FlipperFormat instance * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return true On success + * @return status */ -bool subghz_protocol_decoder_nice_flor_s_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_nice_flor_s_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset); @@ -61,9 +61,10 @@ bool subghz_protocol_decoder_nice_flor_s_serialize( * Deserialize data SubGhzProtocolDecoderNiceFlorS. * @param context Pointer to a SubGhzProtocolDecoderNiceFlorS instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool subghz_protocol_decoder_nice_flor_s_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_decoder_nice_flor_s_deserialize(void* context, FlipperFormat* flipper_format); /** * Getting a textual representation of the received data. diff --git a/lib/subghz/protocols/phoenix_v2.c b/lib/subghz/protocols/phoenix_v2.c index b3d6f1e98f5..4ed9766ef65 100644 --- a/lib/subghz/protocols/phoenix_v2.c +++ b/lib/subghz/protocols/phoenix_v2.c @@ -132,31 +132,31 @@ static bool return true; } -bool subghz_protocol_encoder_phoenix_v2_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + subghz_protocol_encoder_phoenix_v2_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolEncoderPhoenix_V2* instance = context; - bool res = false; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; do { - if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { - FURI_LOG_E(TAG, "Deserialize error"); - break; - } - if(instance->generic.data_count_bit != - subghz_protocol_phoenix_v2_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); + ret = subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_phoenix_v2_const.min_count_bit_for_found); + if(ret != SubGhzProtocolStatusOk) { break; } //optional parameter parameter flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - if(!subghz_protocol_encoder_phoenix_v2_get_upload(instance)) break; + if(!subghz_protocol_encoder_phoenix_v2_get_upload(instance)) { + ret = SubGhzProtocolStatusErrorEncoderGetUpload; + break; + } instance->encoder.is_running = true; - - res = true; } while(false); - return res; + return ret; } void subghz_protocol_encoder_phoenix_v2_stop(void* context) { @@ -293,7 +293,7 @@ uint8_t subghz_protocol_decoder_phoenix_v2_get_hash_data(void* context) { &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } -bool subghz_protocol_decoder_phoenix_v2_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_phoenix_v2_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset) { @@ -302,22 +302,14 @@ bool subghz_protocol_decoder_phoenix_v2_serialize( return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); } -bool subghz_protocol_decoder_phoenix_v2_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + subghz_protocol_decoder_phoenix_v2_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolDecoderPhoenix_V2* instance = context; - bool ret = false; - do { - if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { - break; - } - if(instance->generic.data_count_bit != - subghz_protocol_phoenix_v2_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); - break; - } - ret = true; - } while(false); - return ret; + return subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_phoenix_v2_const.min_count_bit_for_found); } void subghz_protocol_decoder_phoenix_v2_get_string(void* context, FuriString* output) { diff --git a/lib/subghz/protocols/phoenix_v2.h b/lib/subghz/protocols/phoenix_v2.h index 48487535e78..0724de1f095 100644 --- a/lib/subghz/protocols/phoenix_v2.h +++ b/lib/subghz/protocols/phoenix_v2.h @@ -28,9 +28,10 @@ void subghz_protocol_encoder_phoenix_v2_free(void* context); * Deserialize and generating an upload to send. * @param context Pointer to a SubGhzProtocolEncoderPhoenix_V2 instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool subghz_protocol_encoder_phoenix_v2_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_encoder_phoenix_v2_deserialize(void* context, FlipperFormat* flipper_format); /** * Forced transmission stop. @@ -84,9 +85,9 @@ uint8_t subghz_protocol_decoder_phoenix_v2_get_hash_data(void* context); * @param context Pointer to a SubGhzProtocolDecoderPhoenix_V2 instance * @param flipper_format Pointer to a FlipperFormat instance * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return true On success + * @return status */ -bool subghz_protocol_decoder_phoenix_v2_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_phoenix_v2_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset); @@ -95,9 +96,10 @@ bool subghz_protocol_decoder_phoenix_v2_serialize( * Deserialize data SubGhzProtocolDecoderPhoenix_V2. * @param context Pointer to a SubGhzProtocolDecoderPhoenix_V2 instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool subghz_protocol_decoder_phoenix_v2_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_decoder_phoenix_v2_deserialize(void* context, FlipperFormat* flipper_format); /** * Getting a textual representation of the received data. diff --git a/lib/subghz/protocols/power_smart.c b/lib/subghz/protocols/power_smart.c index 1e8d10e952e..d03282f73a1 100644 --- a/lib/subghz/protocols/power_smart.c +++ b/lib/subghz/protocols/power_smart.c @@ -192,18 +192,17 @@ static void subghz_protocol_power_smart_remote_controller(SubGhzBlockGeneric* in instance->cnt = ((instance->data >> 49) & 0x3F); } -bool subghz_protocol_encoder_power_smart_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + subghz_protocol_encoder_power_smart_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolEncoderPowerSmart* instance = context; - bool res = false; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; do { - if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { - FURI_LOG_E(TAG, "Deserialize error"); - break; - } - if(instance->generic.data_count_bit != - subghz_protocol_power_smart_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); + ret = subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_power_smart_const.min_count_bit_for_found); + if(ret != SubGhzProtocolStatusOk) { break; } //optional parameter parameter @@ -213,11 +212,9 @@ bool subghz_protocol_encoder_power_smart_deserialize(void* context, FlipperForma subghz_protocol_power_smart_remote_controller(&instance->generic); subghz_protocol_encoder_power_smart_get_upload(instance); instance->encoder.is_running = true; - - res = true; } while(false); - return res; + return ret; } void subghz_protocol_encoder_power_smart_stop(void* context) { @@ -345,7 +342,7 @@ uint8_t subghz_protocol_decoder_power_smart_get_hash_data(void* context) { &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } -bool subghz_protocol_decoder_power_smart_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_power_smart_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset) { @@ -354,22 +351,14 @@ bool subghz_protocol_decoder_power_smart_serialize( return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); } -bool subghz_protocol_decoder_power_smart_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + subghz_protocol_decoder_power_smart_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolDecoderPowerSmart* instance = context; - bool ret = false; - do { - if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { - break; - } - if(instance->generic.data_count_bit != - subghz_protocol_power_smart_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); - break; - } - ret = true; - } while(false); - return ret; + return subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_power_smart_const.min_count_bit_for_found); } void subghz_protocol_decoder_power_smart_get_string(void* context, FuriString* output) { diff --git a/lib/subghz/protocols/power_smart.h b/lib/subghz/protocols/power_smart.h index 806729f8e93..5687cf8b1ba 100644 --- a/lib/subghz/protocols/power_smart.h +++ b/lib/subghz/protocols/power_smart.h @@ -28,9 +28,10 @@ void subghz_protocol_encoder_power_smart_free(void* context); * Deserialize and generating an upload to send. * @param context Pointer to a SubGhzProtocolEncoderPowerSmart instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool subghz_protocol_encoder_power_smart_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_encoder_power_smart_deserialize(void* context, FlipperFormat* flipper_format); /** * Forced transmission stop. @@ -84,9 +85,9 @@ uint8_t subghz_protocol_decoder_power_smart_get_hash_data(void* context); * @param context Pointer to a SubGhzProtocolDecoderPowerSmart instance * @param flipper_format Pointer to a FlipperFormat instance * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return true On success + * @return status */ -bool subghz_protocol_decoder_power_smart_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_power_smart_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset); @@ -95,9 +96,10 @@ bool subghz_protocol_decoder_power_smart_serialize( * Deserialize data SubGhzProtocolDecoderPowerSmart. * @param context Pointer to a SubGhzProtocolDecoderPowerSmart instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool subghz_protocol_decoder_power_smart_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_decoder_power_smart_deserialize(void* context, FlipperFormat* flipper_format); /** * Getting a textual representation of the received data. diff --git a/lib/subghz/protocols/princeton.c b/lib/subghz/protocols/princeton.c index 7fc8f6524dd..aa15b8b41c9 100644 --- a/lib/subghz/protocols/princeton.c +++ b/lib/subghz/protocols/princeton.c @@ -141,39 +141,41 @@ static bool return true; } -bool subghz_protocol_encoder_princeton_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + subghz_protocol_encoder_princeton_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolEncoderPrinceton* instance = context; - bool res = false; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; do { - if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { - FURI_LOG_E(TAG, "Deserialize error"); + ret = subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_princeton_const.min_count_bit_for_found); + if(ret != SubGhzProtocolStatusOk) { break; } if(!flipper_format_rewind(flipper_format)) { FURI_LOG_E(TAG, "Rewind error"); + ret = SubGhzProtocolStatusErrorParserOthers; break; } if(!flipper_format_read_uint32(flipper_format, "TE", (uint32_t*)&instance->te, 1)) { FURI_LOG_E(TAG, "Missing TE"); - break; - } - if(instance->generic.data_count_bit != - subghz_protocol_princeton_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); + ret = SubGhzProtocolStatusErrorParserTe; break; } //optional parameter parameter flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - if(!subghz_protocol_encoder_princeton_get_upload(instance)) break; + if(!subghz_protocol_encoder_princeton_get_upload(instance)) { + ret = SubGhzProtocolStatusErrorEncoderGetUpload; + break; + } instance->encoder.is_running = true; - - res = true; } while(false); - return res; + return ret; } void subghz_protocol_encoder_princeton_stop(void* context) { @@ -308,46 +310,48 @@ uint8_t subghz_protocol_decoder_princeton_get_hash_data(void* context) { &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } -bool subghz_protocol_decoder_princeton_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_princeton_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset) { furi_assert(context); SubGhzProtocolDecoderPrinceton* instance = context; - bool res = subghz_block_generic_serialize(&instance->generic, flipper_format, preset); - if(res && !flipper_format_write_uint32(flipper_format, "TE", &instance->te, 1)) { + SubGhzProtocolStatus ret = + subghz_block_generic_serialize(&instance->generic, flipper_format, preset); + if((ret == SubGhzProtocolStatusOk) && + !flipper_format_write_uint32(flipper_format, "TE", &instance->te, 1)) { FURI_LOG_E(TAG, "Unable to add TE"); - res = false; + ret = SubGhzProtocolStatusErrorParserTe; } - return res; + return ret; } -bool subghz_protocol_decoder_princeton_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + subghz_protocol_decoder_princeton_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolDecoderPrinceton* instance = context; - bool res = false; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; do { - if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { - FURI_LOG_E(TAG, "Deserialize error"); - break; - } - if(instance->generic.data_count_bit != - subghz_protocol_princeton_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); + ret = subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_princeton_const.min_count_bit_for_found); + if(ret != SubGhzProtocolStatusOk) { break; } if(!flipper_format_rewind(flipper_format)) { FURI_LOG_E(TAG, "Rewind error"); + ret = SubGhzProtocolStatusErrorParserOthers; break; } if(!flipper_format_read_uint32(flipper_format, "TE", (uint32_t*)&instance->te, 1)) { FURI_LOG_E(TAG, "Missing TE"); + ret = SubGhzProtocolStatusErrorParserTe; break; } - res = true; } while(false); - return res; + return ret; } void subghz_protocol_decoder_princeton_get_string(void* context, FuriString* output) { diff --git a/lib/subghz/protocols/princeton.h b/lib/subghz/protocols/princeton.h index f63004db464..da73b6c30fc 100644 --- a/lib/subghz/protocols/princeton.h +++ b/lib/subghz/protocols/princeton.h @@ -28,9 +28,10 @@ void subghz_protocol_encoder_princeton_free(void* context); * Deserialize and generating an upload to send. * @param context Pointer to a SubGhzProtocolEncoderPrinceton instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool subghz_protocol_encoder_princeton_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_encoder_princeton_deserialize(void* context, FlipperFormat* flipper_format); /** * Forced transmission stop. @@ -84,9 +85,9 @@ uint8_t subghz_protocol_decoder_princeton_get_hash_data(void* context); * @param context Pointer to a SubGhzProtocolDecoderPrinceton instance * @param flipper_format Pointer to a FlipperFormat instance * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return true On success + * @return status */ -bool subghz_protocol_decoder_princeton_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_princeton_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset); @@ -95,9 +96,10 @@ bool subghz_protocol_decoder_princeton_serialize( * Deserialize data SubGhzProtocolDecoderPrinceton. * @param context Pointer to a SubGhzProtocolDecoderPrinceton instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool subghz_protocol_decoder_princeton_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_decoder_princeton_deserialize(void* context, FlipperFormat* flipper_format); /** * Getting a textual representation of the received data. diff --git a/lib/subghz/protocols/raw.c b/lib/subghz/protocols/raw.c index ac3492e7803..66358698ac9 100644 --- a/lib/subghz/protocols/raw.c +++ b/lib/subghz/protocols/raw.c @@ -259,12 +259,13 @@ void subghz_protocol_decoder_raw_feed(void* context, bool level, uint32_t durati } } -bool subghz_protocol_decoder_raw_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + subghz_protocol_decoder_raw_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); UNUSED(context); UNUSED(flipper_format); //ToDo stub, for backwards compatibility - return true; + return SubGhzProtocolStatusOk; } void subghz_protocol_decoder_raw_get_string(void* context, FuriString* output) { @@ -342,25 +343,32 @@ void subghz_protocol_raw_gen_fff_data(FlipperFormat* flipper_format, const char* } while(false); } -bool subghz_protocol_encoder_raw_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + subghz_protocol_encoder_raw_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolEncoderRAW* instance = context; - bool res = false; + SubGhzProtocolStatus res = SubGhzProtocolStatusError; FuriString* temp_str; temp_str = furi_string_alloc(); do { if(!flipper_format_rewind(flipper_format)) { FURI_LOG_E(TAG, "Rewind error"); + res = SubGhzProtocolStatusErrorParserOthers; break; } if(!flipper_format_read_string(flipper_format, "File_name", temp_str)) { FURI_LOG_E(TAG, "Missing File_name"); + res = SubGhzProtocolStatusErrorParserOthers; break; } furi_string_set(instance->file_name, temp_str); - res = subghz_protocol_encoder_raw_worker_init(instance); + if(!subghz_protocol_encoder_raw_worker_init(instance)) { + res = SubGhzProtocolStatusErrorEncoderGetUpload; + break; + } + res = SubGhzProtocolStatusOk; } while(false); furi_string_free(temp_str); return res; diff --git a/lib/subghz/protocols/raw.h b/lib/subghz/protocols/raw.h index 44c7faec5ff..4f67a4e2f24 100644 --- a/lib/subghz/protocols/raw.h +++ b/lib/subghz/protocols/raw.h @@ -73,9 +73,10 @@ void subghz_protocol_decoder_raw_feed(void* context, bool level, uint32_t durati * Deserialize data SubGhzProtocolDecoderRAW. * @param context Pointer to a SubGhzProtocolDecoderRAW instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool subghz_protocol_decoder_raw_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_decoder_raw_deserialize(void* context, FlipperFormat* flipper_format); /** * Getting a textual representation of the received data. @@ -132,9 +133,10 @@ void subghz_protocol_raw_gen_fff_data(FlipperFormat* flipper_format, const char* * Deserialize and generating an upload to send. * @param context Pointer to a SubGhzProtocolEncoderRAW instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool subghz_protocol_encoder_raw_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_encoder_raw_deserialize(void* context, FlipperFormat* flipper_format); /** * Getting the level and duration of the upload to be loaded into DMA. diff --git a/lib/subghz/protocols/scher_khan.c b/lib/subghz/protocols/scher_khan.c index 955104bcfd2..bf8de10c96f 100644 --- a/lib/subghz/protocols/scher_khan.c +++ b/lib/subghz/protocols/scher_khan.c @@ -248,7 +248,7 @@ uint8_t subghz_protocol_decoder_scher_khan_get_hash_data(void* context) { &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } -bool subghz_protocol_decoder_scher_khan_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_scher_khan_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset) { @@ -257,7 +257,8 @@ bool subghz_protocol_decoder_scher_khan_serialize( return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); } -bool subghz_protocol_decoder_scher_khan_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + subghz_protocol_decoder_scher_khan_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolDecoderScherKhan* instance = context; return subghz_block_generic_deserialize(&instance->generic, flipper_format); diff --git a/lib/subghz/protocols/scher_khan.h b/lib/subghz/protocols/scher_khan.h index b7e84ea1f35..58545069cf6 100644 --- a/lib/subghz/protocols/scher_khan.h +++ b/lib/subghz/protocols/scher_khan.h @@ -50,9 +50,9 @@ uint8_t subghz_protocol_decoder_scher_khan_get_hash_data(void* context); * @param context Pointer to a SubGhzProtocolDecoderScherKhan instance * @param flipper_format Pointer to a FlipperFormat instance * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return true On success + * @return status */ -bool subghz_protocol_decoder_scher_khan_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_scher_khan_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset); @@ -61,9 +61,10 @@ bool subghz_protocol_decoder_scher_khan_serialize( * Deserialize data SubGhzProtocolDecoderScherKhan. * @param context Pointer to a SubGhzProtocolDecoderScherKhan instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool subghz_protocol_decoder_scher_khan_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_decoder_scher_khan_deserialize(void* context, FlipperFormat* flipper_format); /** * Getting a textual representation of the received data. diff --git a/lib/subghz/protocols/secplus_v1.c b/lib/subghz/protocols/secplus_v1.c index a1161101194..783351c6b41 100644 --- a/lib/subghz/protocols/secplus_v1.c +++ b/lib/subghz/protocols/secplus_v1.c @@ -264,18 +264,17 @@ static bool subghz_protocol_secplus_v1_encode(SubGhzProtocolEncoderSecPlus_v1* i return true; } -bool subghz_protocol_encoder_secplus_v1_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + subghz_protocol_encoder_secplus_v1_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolEncoderSecPlus_v1* instance = context; - bool res = false; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; do { - if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { - FURI_LOG_E(TAG, "Deserialize error"); - break; - } - if(instance->generic.data_count_bit != - 2 * subghz_protocol_secplus_v1_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); + ret = subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + 2 * subghz_protocol_secplus_v1_const.min_count_bit_for_found); + if(ret != SubGhzProtocolStatusOk) { break; } //optional parameter parameter @@ -283,9 +282,12 @@ bool subghz_protocol_encoder_secplus_v1_deserialize(void* context, FlipperFormat flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); if(!subghz_protocol_secplus_v1_encode(instance)) { + ret = SubGhzProtocolStatusErrorParserOthers; break; } if(!subghz_protocol_encoder_secplus_v1_get_upload(instance)) { + ret = SubGhzProtocolStatusErrorEncoderGetUpload; + ; break; } @@ -295,15 +297,14 @@ bool subghz_protocol_encoder_secplus_v1_deserialize(void* context, FlipperFormat } if(!flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(uint64_t))) { FURI_LOG_E(TAG, "Unable to add Key"); + ret = SubGhzProtocolStatusErrorParserKey; break; } instance->encoder.is_running = true; - - res = true; } while(false); - return res; + return ret; } void subghz_protocol_encoder_secplus_v1_stop(void* context) { @@ -516,7 +517,7 @@ uint8_t subghz_protocol_decoder_secplus_v1_get_hash_data(void* context) { &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } -bool subghz_protocol_decoder_secplus_v1_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_secplus_v1_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset) { @@ -525,22 +526,14 @@ bool subghz_protocol_decoder_secplus_v1_serialize( return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); } -bool subghz_protocol_decoder_secplus_v1_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + subghz_protocol_decoder_secplus_v1_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolDecoderSecPlus_v1* instance = context; - bool ret = false; - do { - if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { - break; - } - if(instance->generic.data_count_bit != - 2 * subghz_protocol_secplus_v1_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); - break; - } - ret = true; - } while(false); - return ret; + return subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + 2 * subghz_protocol_secplus_v1_const.min_count_bit_for_found); } bool subghz_protocol_secplus_v1_check_fixed(uint32_t fixed) { diff --git a/lib/subghz/protocols/secplus_v1.h b/lib/subghz/protocols/secplus_v1.h index 99480b62b53..3490f2ca540 100644 --- a/lib/subghz/protocols/secplus_v1.h +++ b/lib/subghz/protocols/secplus_v1.h @@ -27,9 +27,10 @@ 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 + * @return status */ -bool subghz_protocol_encoder_secplus_v1_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_encoder_secplus_v1_deserialize(void* context, FlipperFormat* flipper_format); /** * Forced transmission stop. @@ -83,9 +84,9 @@ uint8_t subghz_protocol_decoder_secplus_v1_get_hash_data(void* context); * @param context Pointer to a SubGhzProtocolDecoderSecPlus_v1 instance * @param flipper_format Pointer to a FlipperFormat instance * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return true On success + * @return status */ -bool subghz_protocol_decoder_secplus_v1_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_secplus_v1_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset); @@ -94,9 +95,10 @@ bool subghz_protocol_decoder_secplus_v1_serialize( * Deserialize data SubGhzProtocolDecoderSecPlus_v1. * @param context Pointer to a SubGhzProtocolDecoderSecPlus_v1 instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool subghz_protocol_decoder_secplus_v1_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_decoder_secplus_v1_deserialize(void* context, FlipperFormat* flipper_format); /** * Validation of fixed parts SubGhzProtocolDecoderSecPlus_v1. diff --git a/lib/subghz/protocols/secplus_v2.c b/lib/subghz/protocols/secplus_v2.c index f7262bd1769..4a3815f0d31 100644 --- a/lib/subghz/protocols/secplus_v2.c +++ b/lib/subghz/protocols/secplus_v2.c @@ -503,24 +503,24 @@ static void instance->encoder.size_upload = index; } -bool subghz_protocol_encoder_secplus_v2_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + subghz_protocol_encoder_secplus_v2_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolEncoderSecPlus_v2* instance = context; - bool res = false; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; do { - if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { - FURI_LOG_E(TAG, "Deserialize error"); - break; - } - if(instance->generic.data_count_bit != - subghz_protocol_secplus_v2_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); + ret = subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_secplus_v2_const.min_count_bit_for_found); + if(ret != SubGhzProtocolStatusOk) { 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"); + ret = SubGhzProtocolStatusErrorParserOthers; break; } for(uint8_t i = 0; i < sizeof(uint64_t); i++) { @@ -541,6 +541,7 @@ bool subghz_protocol_encoder_secplus_v2_deserialize(void* context, FlipperFormat } if(!flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(uint64_t))) { FURI_LOG_E(TAG, "Unable to add Key"); + ret = SubGhzProtocolStatusErrorParserKey; break; } @@ -550,15 +551,14 @@ bool subghz_protocol_encoder_secplus_v2_deserialize(void* context, FlipperFormat 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"); + ret = SubGhzProtocolStatusErrorParserOthers; break; } instance->encoder.is_running = true; - - res = true; } while(false); - return res; + return ret; } void subghz_protocol_encoder_secplus_v2_stop(void* context) { @@ -754,58 +754,59 @@ uint8_t subghz_protocol_decoder_secplus_v2_get_hash_data(void* context) { &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } -bool subghz_protocol_decoder_secplus_v2_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_secplus_v2_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset) { furi_assert(context); SubGhzProtocolDecoderSecPlus_v2* instance = context; - bool res = subghz_block_generic_serialize(&instance->generic, flipper_format, preset); + SubGhzProtocolStatus ret = + subghz_block_generic_serialize(&instance->generic, flipper_format, 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 && + if((ret == SubGhzProtocolStatusOk) && !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; + ret = SubGhzProtocolStatusErrorParserOthers; } - return res; + return ret; } -bool subghz_protocol_decoder_secplus_v2_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + subghz_protocol_decoder_secplus_v2_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolDecoderSecPlus_v2* instance = context; - bool res = false; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; do { - if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { - FURI_LOG_E(TAG, "Deserialize error"); - break; - } - if(instance->generic.data_count_bit != - subghz_protocol_secplus_v2_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); + ret = subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_secplus_v2_const.min_count_bit_for_found); + if(ret != SubGhzProtocolStatusOk) { break; } if(!flipper_format_rewind(flipper_format)) { FURI_LOG_E(TAG, "Rewind error"); + ret = SubGhzProtocolStatusErrorParserOthers; 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, "Missing Secplus_packet_1"); + ret = SubGhzProtocolStatusErrorParserOthers; break; } for(uint8_t i = 0; i < sizeof(uint64_t); i++) { instance->secplus_packet_1 = instance->secplus_packet_1 << 8 | key_data[i]; } - res = true; } while(false); - return res; + return ret; } void subghz_protocol_decoder_secplus_v2_get_string(void* context, FuriString* output) { diff --git a/lib/subghz/protocols/secplus_v2.h b/lib/subghz/protocols/secplus_v2.h index bea8cdb5d92..0eea732af15 100644 --- a/lib/subghz/protocols/secplus_v2.h +++ b/lib/subghz/protocols/secplus_v2.h @@ -27,9 +27,10 @@ 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 + * @return status */ -bool subghz_protocol_encoder_secplus_v2_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_encoder_secplus_v2_deserialize(void* context, FlipperFormat* flipper_format); /** * Forced transmission stop. @@ -102,9 +103,9 @@ uint8_t subghz_protocol_decoder_secplus_v2_get_hash_data(void* context); * @param context Pointer to a SubGhzProtocolDecoderSecPlus_v2 instance * @param flipper_format Pointer to a FlipperFormat instance * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return true On success + * @return status */ -bool subghz_protocol_decoder_secplus_v2_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_secplus_v2_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset); @@ -113,9 +114,10 @@ bool subghz_protocol_decoder_secplus_v2_serialize( * Deserialize data SubGhzProtocolDecoderSecPlus_v2. * @param context Pointer to a SubGhzProtocolDecoderSecPlus_v2 instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool subghz_protocol_decoder_secplus_v2_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_decoder_secplus_v2_deserialize(void* context, FlipperFormat* flipper_format); /** * Getting a textual representation of the received data. diff --git a/lib/subghz/protocols/smc5326.c b/lib/subghz/protocols/smc5326.c index 9c9b5d4fd7d..bfb36b76a93 100644 --- a/lib/subghz/protocols/smc5326.c +++ b/lib/subghz/protocols/smc5326.c @@ -155,39 +155,41 @@ static bool subghz_protocol_encoder_smc5326_get_upload(SubGhzProtocolEncoderSMC5 return true; } -bool subghz_protocol_encoder_smc5326_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + subghz_protocol_encoder_smc5326_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolEncoderSMC5326* instance = context; - bool res = false; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; do { - if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { - FURI_LOG_E(TAG, "Deserialize error"); + ret = subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_smc5326_const.min_count_bit_for_found); + if(ret != SubGhzProtocolStatusOk) { break; } if(!flipper_format_rewind(flipper_format)) { FURI_LOG_E(TAG, "Rewind error"); + ret = SubGhzProtocolStatusErrorParserOthers; break; } if(!flipper_format_read_uint32(flipper_format, "TE", (uint32_t*)&instance->te, 1)) { FURI_LOG_E(TAG, "Missing TE"); - break; - } - if(instance->generic.data_count_bit != - subghz_protocol_smc5326_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); + ret = SubGhzProtocolStatusErrorParserTe; break; } //optional parameter parameter flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - if(!subghz_protocol_encoder_smc5326_get_upload(instance)) break; + if(!subghz_protocol_encoder_smc5326_get_upload(instance)) { + ret = SubGhzProtocolStatusErrorEncoderGetUpload; + break; + } instance->encoder.is_running = true; - - res = true; } while(false); - return res; + return ret; } void subghz_protocol_encoder_smc5326_stop(void* context) { @@ -313,46 +315,48 @@ uint8_t subghz_protocol_decoder_smc5326_get_hash_data(void* context) { &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } -bool subghz_protocol_decoder_smc5326_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_smc5326_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset) { furi_assert(context); SubGhzProtocolDecoderSMC5326* instance = context; - bool res = subghz_block_generic_serialize(&instance->generic, flipper_format, preset); - if(res && !flipper_format_write_uint32(flipper_format, "TE", &instance->te, 1)) { + SubGhzProtocolStatus ret = + subghz_block_generic_serialize(&instance->generic, flipper_format, preset); + if((ret == SubGhzProtocolStatusOk) && + !flipper_format_write_uint32(flipper_format, "TE", &instance->te, 1)) { FURI_LOG_E(TAG, "Unable to add TE"); - res = false; + ret = SubGhzProtocolStatusErrorParserTe; } - return res; + return ret; } -bool subghz_protocol_decoder_smc5326_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + subghz_protocol_decoder_smc5326_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolDecoderSMC5326* instance = context; - bool res = false; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; do { - if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { - FURI_LOG_E(TAG, "Deserialize error"); - break; - } - if(instance->generic.data_count_bit != - subghz_protocol_smc5326_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); + ret = subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_smc5326_const.min_count_bit_for_found); + if(ret != SubGhzProtocolStatusOk) { break; } if(!flipper_format_rewind(flipper_format)) { FURI_LOG_E(TAG, "Rewind error"); + ret = SubGhzProtocolStatusErrorParserOthers; break; } if(!flipper_format_read_uint32(flipper_format, "TE", (uint32_t*)&instance->te, 1)) { FURI_LOG_E(TAG, "Missing TE"); + ret = SubGhzProtocolStatusErrorParserTe; break; } - res = true; } while(false); - return res; + return ret; } static void subghz_protocol_smc5326_get_event_serialize(uint8_t event, FuriString* output) { diff --git a/lib/subghz/protocols/smc5326.h b/lib/subghz/protocols/smc5326.h index ddc954bd57b..911226cf9bd 100644 --- a/lib/subghz/protocols/smc5326.h +++ b/lib/subghz/protocols/smc5326.h @@ -28,9 +28,10 @@ void subghz_protocol_encoder_smc5326_free(void* context); * Deserialize and generating an upload to send. * @param context Pointer to a SubGhzProtocolEncoderSMC5326 instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool subghz_protocol_encoder_smc5326_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_encoder_smc5326_deserialize(void* context, FlipperFormat* flipper_format); /** * Forced transmission stop. @@ -84,9 +85,9 @@ uint8_t subghz_protocol_decoder_smc5326_get_hash_data(void* context); * @param context Pointer to a SubGhzProtocolDecoderSMC5326 instance * @param flipper_format Pointer to a FlipperFormat instance * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return true On success + * @return status */ -bool subghz_protocol_decoder_smc5326_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_smc5326_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset); @@ -95,9 +96,10 @@ bool subghz_protocol_decoder_smc5326_serialize( * Deserialize data SubGhzProtocolDecoderSMC5326. * @param context Pointer to a SubGhzProtocolDecoderSMC5326 instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool subghz_protocol_decoder_smc5326_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_decoder_smc5326_deserialize(void* context, FlipperFormat* flipper_format); /** * Getting a textual representation of the received data. diff --git a/lib/subghz/protocols/somfy_keytis.c b/lib/subghz/protocols/somfy_keytis.c index 00bb8a8cb1e..1e3cdc540ef 100644 --- a/lib/subghz/protocols/somfy_keytis.c +++ b/lib/subghz/protocols/somfy_keytis.c @@ -379,37 +379,39 @@ uint8_t subghz_protocol_decoder_somfy_keytis_get_hash_data(void* context) { &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } -bool subghz_protocol_decoder_somfy_keytis_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_somfy_keytis_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset) { furi_assert(context); SubGhzProtocolDecoderSomfyKeytis* instance = context; - bool res = subghz_block_generic_serialize(&instance->generic, flipper_format, preset); - if(res && !flipper_format_write_uint32( - flipper_format, "Duration_Counter", &instance->press_duration_counter, 1)) { + SubGhzProtocolStatus ret = + subghz_block_generic_serialize(&instance->generic, flipper_format, preset); + if((ret == SubGhzProtocolStatusOk) && + !flipper_format_write_uint32( + flipper_format, "Duration_Counter", &instance->press_duration_counter, 1)) { FURI_LOG_E(TAG, "Unable to add Duration_Counter"); - res = false; + ret = SubGhzProtocolStatusErrorParserOthers; } - return res; + return ret; } -bool subghz_protocol_decoder_somfy_keytis_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + subghz_protocol_decoder_somfy_keytis_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolDecoderSomfyKeytis* instance = context; - bool res = false; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; do { - if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { - FURI_LOG_E(TAG, "Deserialize error"); - break; - } - if(instance->generic.data_count_bit != - subghz_protocol_somfy_keytis_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); + ret = subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_somfy_keytis_const.min_count_bit_for_found); + if(ret != SubGhzProtocolStatusOk) { break; } if(!flipper_format_rewind(flipper_format)) { FURI_LOG_E(TAG, "Rewind error"); + ret = SubGhzProtocolStatusErrorParserOthers; break; } if(!flipper_format_read_uint32( @@ -418,12 +420,12 @@ bool subghz_protocol_decoder_somfy_keytis_deserialize(void* context, FlipperForm (uint32_t*)&instance->press_duration_counter, 1)) { FURI_LOG_E(TAG, "Missing Duration_Counter"); + ret = SubGhzProtocolStatusErrorParserOthers; break; } - res = true; } while(false); - return res; + return ret; } void subghz_protocol_decoder_somfy_keytis_get_string(void* context, FuriString* output) { diff --git a/lib/subghz/protocols/somfy_keytis.h b/lib/subghz/protocols/somfy_keytis.h index 3b595061124..15f63c43586 100644 --- a/lib/subghz/protocols/somfy_keytis.h +++ b/lib/subghz/protocols/somfy_keytis.h @@ -50,9 +50,9 @@ uint8_t subghz_protocol_decoder_somfy_keytis_get_hash_data(void* context); * @param context Pointer to a SubGhzProtocolDecoderSomfyKeytis instance * @param flipper_format Pointer to a FlipperFormat instance * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return true On success + * @return status */ -bool subghz_protocol_decoder_somfy_keytis_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_somfy_keytis_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset); @@ -61,9 +61,10 @@ bool subghz_protocol_decoder_somfy_keytis_serialize( * Deserialize data SubGhzProtocolDecoderSomfyKeytis. * @param context Pointer to a SubGhzProtocolDecoderSomfyKeytis instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool subghz_protocol_decoder_somfy_keytis_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_decoder_somfy_keytis_deserialize(void* context, FlipperFormat* flipper_format); /** * Getting a textual representation of the received data. diff --git a/lib/subghz/protocols/somfy_telis.c b/lib/subghz/protocols/somfy_telis.c index 362b1b07c4a..523bef6e79f 100644 --- a/lib/subghz/protocols/somfy_telis.c +++ b/lib/subghz/protocols/somfy_telis.c @@ -336,7 +336,7 @@ uint8_t subghz_protocol_decoder_somfy_telis_get_hash_data(void* context) { &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } -bool subghz_protocol_decoder_somfy_telis_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_somfy_telis_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset) { @@ -345,22 +345,14 @@ bool subghz_protocol_decoder_somfy_telis_serialize( return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); } -bool subghz_protocol_decoder_somfy_telis_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + subghz_protocol_decoder_somfy_telis_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolDecoderSomfyTelis* instance = context; - bool ret = false; - do { - if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { - break; - } - if(instance->generic.data_count_bit != - subghz_protocol_somfy_telis_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); - break; - } - ret = true; - } while(false); - return ret; + return subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_somfy_telis_const.min_count_bit_for_found); } void subghz_protocol_decoder_somfy_telis_get_string(void* context, FuriString* output) { diff --git a/lib/subghz/protocols/somfy_telis.h b/lib/subghz/protocols/somfy_telis.h index a6a9fa5b241..c7ddc3cab98 100644 --- a/lib/subghz/protocols/somfy_telis.h +++ b/lib/subghz/protocols/somfy_telis.h @@ -50,9 +50,9 @@ uint8_t subghz_protocol_decoder_somfy_telis_get_hash_data(void* context); * @param context Pointer to a SubGhzProtocolDecoderSomfyTelis instance * @param flipper_format Pointer to a FlipperFormat instance * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return true On success + * @return status */ -bool subghz_protocol_decoder_somfy_telis_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_somfy_telis_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset); @@ -61,9 +61,10 @@ bool subghz_protocol_decoder_somfy_telis_serialize( * Deserialize data SubGhzProtocolDecoderSomfyTelis. * @param context Pointer to a SubGhzProtocolDecoderSomfyTelis instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool subghz_protocol_decoder_somfy_telis_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_decoder_somfy_telis_deserialize(void* context, FlipperFormat* flipper_format); /** * Getting a textual representation of the received data. diff --git a/lib/subghz/protocols/star_line.c b/lib/subghz/protocols/star_line.c index 8e5e07c1f44..e3b7d4a296c 100644 --- a/lib/subghz/protocols/star_line.c +++ b/lib/subghz/protocols/star_line.c @@ -316,7 +316,7 @@ uint8_t subghz_protocol_decoder_star_line_get_hash_data(void* context) { &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } -bool subghz_protocol_decoder_star_line_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_star_line_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset) { @@ -324,34 +324,29 @@ bool subghz_protocol_decoder_star_line_serialize( SubGhzProtocolDecoderStarLine* instance = context; subghz_protocol_star_line_check_remote_controller( &instance->generic, instance->keystore, &instance->manufacture_name); - bool res = subghz_block_generic_serialize(&instance->generic, flipper_format, preset); + SubGhzProtocolStatus ret = + subghz_block_generic_serialize(&instance->generic, flipper_format, preset); - if(res && !flipper_format_write_string_cstr( - flipper_format, "Manufacture", instance->manufacture_name)) { + if((ret == SubGhzProtocolStatusOk) && + !flipper_format_write_string_cstr( + flipper_format, "Manufacture", instance->manufacture_name)) { FURI_LOG_E(TAG, "Unable to add manufacture name"); - res = false; + ret = SubGhzProtocolStatusErrorParserOthers; } - if(res && instance->generic.data_count_bit != - subghz_protocol_star_line_const.min_count_bit_for_found) { + if((ret == SubGhzProtocolStatusOk) && + instance->generic.data_count_bit != + subghz_protocol_star_line_const.min_count_bit_for_found) { FURI_LOG_E(TAG, "Wrong number of bits in key"); - res = false; + ret = SubGhzProtocolStatusErrorParserOthers; } - return res; + return ret; } -bool subghz_protocol_decoder_star_line_deserialize(void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + subghz_protocol_decoder_star_line_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolDecoderStarLine* instance = context; - bool res = false; - do { - if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { - FURI_LOG_E(TAG, "Deserialize error"); - break; - } - res = true; - } while(false); - - return res; + return subghz_block_generic_deserialize(&instance->generic, flipper_format); } void subghz_protocol_decoder_star_line_get_string(void* context, FuriString* output) { diff --git a/lib/subghz/protocols/star_line.h b/lib/subghz/protocols/star_line.h index 34c25150474..6f352f240ec 100644 --- a/lib/subghz/protocols/star_line.h +++ b/lib/subghz/protocols/star_line.h @@ -50,9 +50,9 @@ uint8_t subghz_protocol_decoder_star_line_get_hash_data(void* context); * @param context Pointer to a SubGhzProtocolDecoderStarLine instance * @param flipper_format Pointer to a FlipperFormat instance * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return true On success + * @return status */ -bool subghz_protocol_decoder_star_line_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_star_line_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset); @@ -61,9 +61,10 @@ bool subghz_protocol_decoder_star_line_serialize( * Deserialize data SubGhzProtocolDecoderStarLine. * @param context Pointer to a SubGhzProtocolDecoderStarLine instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool subghz_protocol_decoder_star_line_deserialize(void* context, FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_protocol_decoder_star_line_deserialize(void* context, FlipperFormat* flipper_format); /** * Getting a textual representation of the received data. diff --git a/lib/subghz/transmitter.c b/lib/subghz/transmitter.c index 8507ee054b8..81be143b53d 100644 --- a/lib/subghz/transmitter.c +++ b/lib/subghz/transmitter.c @@ -47,9 +47,10 @@ bool subghz_transmitter_stop(SubGhzTransmitter* instance) { return ret; } -bool subghz_transmitter_deserialize(SubGhzTransmitter* instance, FlipperFormat* flipper_format) { +SubGhzProtocolStatus + subghz_transmitter_deserialize(SubGhzTransmitter* instance, FlipperFormat* flipper_format) { furi_assert(instance); - bool ret = false; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; if(instance->protocol && instance->protocol->encoder && instance->protocol->encoder->deserialize) { ret = diff --git a/lib/subghz/transmitter.h b/lib/subghz/transmitter.h index cce98a463aa..a1c4cda08f3 100644 --- a/lib/subghz/transmitter.h +++ b/lib/subghz/transmitter.h @@ -39,9 +39,10 @@ bool subghz_transmitter_stop(SubGhzTransmitter* instance); * Deserialize and generating an upload to send. * @param instance Pointer to a SubGhzTransmitter instance * @param flipper_format Pointer to a FlipperFormat instance - * @return true On success + * @return status */ -bool subghz_transmitter_deserialize(SubGhzTransmitter* instance, FlipperFormat* flipper_format); +SubGhzProtocolStatus + subghz_transmitter_deserialize(SubGhzTransmitter* instance, FlipperFormat* flipper_format); /** * Getting the level and duration of the upload to be loaded into DMA. diff --git a/lib/subghz/types.h b/lib/subghz/types.h index 1b8ef6a1412..09eb07eeaad 100644 --- a/lib/subghz/types.h +++ b/lib/subghz/types.h @@ -29,14 +29,36 @@ typedef struct { size_t data_size; } SubGhzRadioPreset; +typedef enum { + SubGhzProtocolStatusOk = 0, + // Errors + SubGhzProtocolStatusError = (-1), ///< General unclassified error + // Serialize/De-serialize + SubGhzProtocolStatusErrorParserHeader = (-2), ///< Missing or invalid file header + SubGhzProtocolStatusErrorParserFrequency = (-3), ///< Missing `Frequency` + SubGhzProtocolStatusErrorParserPreset = (-4), ///< Missing `Preset` + SubGhzProtocolStatusErrorParserCustomPreset = (-5), ///< Missing `Custom_preset_module` + SubGhzProtocolStatusErrorParserProtocolName = (-6), ///< Missing `Protocol` name + SubGhzProtocolStatusErrorParserBitCount = (-7), ///< Missing `Bit` + SubGhzProtocolStatusErrorParserKey = (-8), ///< Missing `Key` + SubGhzProtocolStatusErrorParserTe = (-9), ///< Missing `Te` + SubGhzProtocolStatusErrorParserOthers = (-10), ///< Missing some other mandatory keys + // Invalid data + SubGhzProtocolStatusErrorValueBitCount = (-11), ///< Invalid bit count value + // Encoder issue + SubGhzProtocolStatusErrorEncoderGetUpload = (-12), ///< Payload encoder failure + // Special Values + SubGhzProtocolStatusReserved = 0x7FFFFFFF, ///< Prevents enum down-size compiler optimization. +} SubGhzProtocolStatus; + // Allocator and Deallocator typedef void* (*SubGhzAlloc)(SubGhzEnvironment* environment); typedef void (*SubGhzFree)(void* context); // Serialize and Deserialize -typedef bool ( +typedef SubGhzProtocolStatus ( *SubGhzSerialize)(void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset); -typedef bool (*SubGhzDeserialize)(void* context, FlipperFormat* flipper_format); +typedef SubGhzProtocolStatus (*SubGhzDeserialize)(void* context, FlipperFormat* flipper_format); // Decoder specific typedef void (*SubGhzDecoderFeed)(void* decoder, bool level, uint32_t duration); From 0b7d205253cb10e5f725d6113c721160c91239e4 Mon Sep 17 00:00:00 2001 From: Ethanol0001 <126507963+Ethanol0001@users.noreply.github.com> Date: Fri, 3 Mar 2023 10:15:17 -0600 Subject: [PATCH 445/824] Update clock_app.c (#2446) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- applications/plugins/clock/clock_app.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/applications/plugins/clock/clock_app.c b/applications/plugins/clock/clock_app.c index 9d87ff9501b..c938125b378 100644 --- a/applications/plugins/clock/clock_app.c +++ b/applications/plugins/clock/clock_app.c @@ -56,7 +56,7 @@ static void clock_render_callback(Canvas* canvas, void* ctx) { 31, AlignLeft, AlignCenter, - (data->datetime.hour > 12) ? "PM" : "AM"); + (data->datetime.hour > 11) ? "PM" : "AM"); } canvas_set_font(canvas, FontSecondary); @@ -133,4 +133,4 @@ int32_t clock_app(void* p) { free(clock); return 0; -} \ No newline at end of file +} From 5d4057f7228b528896578ab6cf9d9a61b80a7c58 Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Fri, 3 Mar 2023 21:07:41 +0300 Subject: [PATCH 446/824] Archive browser: update path on dir leave (#2455) --- applications/main/archive/helpers/archive_browser.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/applications/main/archive/helpers/archive_browser.c b/applications/main/archive/helpers/archive_browser.c index f5efca4698e..9a7973cb388 100644 --- a/applications/main/archive/helpers/archive_browser.c +++ b/applications/main/archive/helpers/archive_browser.c @@ -510,12 +510,16 @@ void archive_enter_dir(ArchiveBrowserView* browser, FuriString* path) { browser->view, ArchiveBrowserViewModel * model, { idx_temp = model->item_idx; }, false); furi_string_set(browser->path, path); + file_browser_worker_folder_enter(browser->worker, path, idx_temp); } void archive_leave_dir(ArchiveBrowserView* browser) { furi_assert(browser); + size_t dirname_start = furi_string_search_rchar(browser->path, '/'); + furi_string_left(browser->path, dirname_start); + file_browser_worker_folder_exit(browser->worker); } From 4ab832cc4619d238347fba9517289a7b8b90faec Mon Sep 17 00:00:00 2001 From: hedger Date: Sun, 5 Mar 2023 09:17:33 +0400 Subject: [PATCH 447/824] github: check API versions for all targets to match on gh build (#2459) * github: check API versions for all targets to match on gh build * typo fix * gh: forcing target mismatch to test pipeline * reverted API version change --- .github/workflows/build.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 689dd2037f5..46d95ede545 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -44,6 +44,16 @@ jobs: echo random_hash=$(openssl rand -base64 40 | shasum -a 256 | awk '{print $1}') >> $GITHUB_OUTPUT echo "event_type=$TYPE" >> $GITHUB_OUTPUT + - name: 'Check API versions' + run: | + set -e + N_API_HEADER_SIGNATURES=`ls -1 firmware/targets/f*/api_symbols.csv | xargs -I {} sh -c "head -n2 {} | md5sum" | sort -u | wc -l` + if [ $N_API_HEADER_SIGNATURES != 1 ] ; then + echo API versions aren\'t matching for available targets. Please update! + head -n2 firmware/targets/f*/api_symbols.csv + exit 1 + fi + - name: 'Make artifacts directory' run: | rm -rf artifacts From c0e0403b44f6fd3a2d1420eb48db663ef4d57d0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Such=C3=A1nek?= Date: Mon, 6 Mar 2023 08:44:26 +0100 Subject: [PATCH 448/824] Fix SD card CID parsing (#2463) The recent SD rewrite dropped a couple of lines from the CID parsing function resulting in zero manufacturing date displayed. Signed-off-by: Michal Suchanek --- firmware/targets/f7/fatfs/sd_spi_io.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/firmware/targets/f7/fatfs/sd_spi_io.c b/firmware/targets/f7/fatfs/sd_spi_io.c index 68903acfb65..e8e542b32ea 100644 --- a/firmware/targets/f7/fatfs/sd_spi_io.c +++ b/firmware/targets/f7/fatfs/sd_spi_io.c @@ -585,6 +585,8 @@ static SdSpiStatus sd_spi_get_cid(SD_CID* Cid) { Cid->ProdSN |= cid_data[12]; Cid->Reserved1 = (cid_data[13] & 0xF0) >> 4; Cid->ManufactYear = (cid_data[13] & 0x0F) << 4; + Cid->ManufactYear |= (cid_data[14] & 0xF0) >> 4; + Cid->ManufactMonth = (cid_data[14] & 0x0F); Cid->CID_CRC = (cid_data[15] & 0xFE) >> 1; Cid->Reserved2 = 1; From 9dd1fb64b7fbe7731dea5a93d530c3dc176f59ce Mon Sep 17 00:00:00 2001 From: GuruSteve Date: Mon, 6 Mar 2023 00:59:48 -0700 Subject: [PATCH 449/824] Fixed picopass load save file overrun (#2464) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- applications/plugins/picopass/picopass_device.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/applications/plugins/picopass/picopass_device.c b/applications/plugins/picopass/picopass_device.c index ec0bc5af8e5..53778cfb322 100644 --- a/applications/plugins/picopass/picopass_device.c +++ b/applications/plugins/picopass/picopass_device.c @@ -167,6 +167,8 @@ static bool picopass_device_load_data(PicopassDevice* dev, FuriString* path, boo } size_t app_limit = AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[0]; + // Fix for unpersonalized cards that have app_limit set to 0xFF + if(app_limit > PICOPASS_MAX_APP_LIMIT) app_limit = PICOPASS_MAX_APP_LIMIT; for(size_t i = 6; i < app_limit; i++) { furi_string_printf(temp_str, "Block %d", i); if(!flipper_format_read_hex( From eefca9f498ea346128db62d1072010aaf75db17e Mon Sep 17 00:00:00 2001 From: Eric Betts Date: Tue, 7 Mar 2023 02:53:52 -0800 Subject: [PATCH 450/824] Support reseting iCx cards (#2451) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Support reseting iCx cards * add submenu * Fix auth * switch key derivation to use same method * test system keys using both elite and standard kdf Co-authored-by: あく --- .../plugins/picopass/picopass_worker.c | 59 +++++++---- .../plugins/picopass/picopass_worker.h | 2 +- .../plugins/picopass/picopass_worker_i.h | 2 +- .../scenes/picopass_scene_card_menu.c | 13 +++ .../picopass/scenes/picopass_scene_config.h | 1 + .../picopass/scenes/picopass_scene_key_menu.c | 100 ++++++++++++++++++ .../scenes/picopass_scene_read_card.c | 4 +- .../picopass_scene_read_factory_success.c | 3 + .../scenes/picopass_scene_write_key.c | 2 +- 9 files changed, 161 insertions(+), 25 deletions(-) create mode 100644 applications/plugins/picopass/scenes/picopass_scene_key_menu.c diff --git a/applications/plugins/picopass/picopass_worker.c b/applications/plugins/picopass/picopass_worker.c index 6d904478caa..024c51120af 100644 --- a/applications/plugins/picopass/picopass_worker.c +++ b/applications/plugins/picopass/picopass_worker.c @@ -7,6 +7,9 @@ const uint8_t picopass_iclass_key[] = {0xaf, 0xa7, 0x85, 0xa7, 0xda, 0xb3, 0x33, 0x78}; const uint8_t picopass_factory_credit_key[] = {0x76, 0x65, 0x54, 0x43, 0x32, 0x21, 0x10, 0x00}; const uint8_t picopass_factory_debit_key[] = {0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87}; +const uint8_t picopass_xice_key[] = {0x20, 0x20, 0x66, 0x66, 0x66, 0x66, 0x88, 0x88}; +const uint8_t picopass_xicl_key[] = {0x20, 0x20, 0x66, 0x66, 0x66, 0x66, 0x88, 0x88}; +const uint8_t picopass_xics_key[] = {0x66, 0x66, 0x20, 0x20, 0x66, 0x66, 0x88, 0x88}; static void picopass_worker_enable_field() { furi_hal_nfc_ll_txrx_on(); @@ -192,7 +195,7 @@ static ReturnCode picopass_auth_standard(uint8_t* csn, uint8_t* div_key) { } memcpy(ccnr, rcRes.CCNR, sizeof(rcRes.CCNR)); // last 4 bytes left 0 - loclass_diversifyKey(csn, picopass_iclass_key, div_key); + loclass_iclass_calc_div_key(csn, (uint8_t*)picopass_iclass_key, div_key, false); loclass_opt_doReaderMAC(ccnr, div_key, mac); return rfalPicoPassPollerCheck(mac, &chkRes); @@ -214,7 +217,7 @@ static ReturnCode picopass_auth_factory(uint8_t* csn, uint8_t* div_key) { } memcpy(ccnr, rcRes.CCNR, sizeof(rcRes.CCNR)); // last 4 bytes left 0 - loclass_diversifyKey(csn, picopass_factory_debit_key, div_key); + loclass_iclass_calc_div_key(csn, (uint8_t*)picopass_factory_debit_key, div_key, false); loclass_opt_doReaderMAC(ccnr, div_key, mac); return rfalPicoPassPollerCheck(mac, &chkRes); @@ -224,7 +227,8 @@ static ReturnCode picopass_auth_dict( uint8_t* csn, PicopassPacs* pacs, uint8_t* div_key, - IclassEliteDictType dict_type) { + IclassEliteDictType dict_type, + bool elite) { rfalPicoPassReadCheckRes rcRes; rfalPicoPassCheckRes chkRes; @@ -269,7 +273,7 @@ static ReturnCode picopass_auth_dict( } memcpy(ccnr, rcRes.CCNR, sizeof(rcRes.CCNR)); // last 4 bytes left 0 - loclass_iclass_calc_div_key(csn, key, div_key, true); + loclass_iclass_calc_div_key(csn, key, div_key, elite); loclass_opt_doReaderMAC(ccnr, div_key, mac); err = rfalPicoPassPollerCheck(mac, &chkRes); @@ -303,22 +307,35 @@ ReturnCode picopass_auth(PicopassBlock* AA1, PicopassPacs* pacs) { return ERR_NONE; } - FURI_LOG_I(TAG, "Starting user dictionary attack"); + FURI_LOG_I(TAG, "Starting user dictionary attack [Elite KDF]"); err = picopass_auth_dict( AA1[PICOPASS_CSN_BLOCK_INDEX].data, pacs, AA1[PICOPASS_KD_BLOCK_INDEX].data, - IclassEliteDictTypeUser); + IclassEliteDictTypeUser, + true); if(err == ERR_NONE) { return ERR_NONE; } - FURI_LOG_I(TAG, "Starting system dictionary attack"); + FURI_LOG_I(TAG, "Starting system dictionary attack [Elite KDF]"); err = picopass_auth_dict( AA1[PICOPASS_CSN_BLOCK_INDEX].data, pacs, AA1[PICOPASS_KD_BLOCK_INDEX].data, - IclassEliteDictTypeFlipper); + IclassEliteDictTypeFlipper, + true); + if(err == ERR_NONE) { + return ERR_NONE; + } + + FURI_LOG_I(TAG, "Starting system dictionary attack [Standard KDF]"); + err = picopass_auth_dict( + AA1[PICOPASS_CSN_BLOCK_INDEX].data, + pacs, + AA1[PICOPASS_KD_BLOCK_INDEX].data, + IclassEliteDictTypeFlipper, + false); if(err == ERR_NONE) { return ERR_NONE; } @@ -396,7 +413,7 @@ ReturnCode picopass_write_card(PicopassBlock* AA1) { } memcpy(ccnr, rcRes.CCNR, sizeof(rcRes.CCNR)); // last 4 bytes left 0 - loclass_diversifyKey(selRes.CSN, picopass_iclass_key, div_key); + loclass_iclass_calc_div_key(selRes.CSN, (uint8_t*)picopass_iclass_key, div_key, false); loclass_opt_doReaderMAC(ccnr, div_key, mac); err = rfalPicoPassPollerCheck(mac, &chkRes); @@ -438,7 +455,7 @@ ReturnCode picopass_write_card(PicopassBlock* AA1) { return ERR_NONE; } -ReturnCode picopass_write_block(PicopassPacs* pacs, uint8_t blockNo, uint8_t* newBlock) { +ReturnCode picopass_write_block(PicopassBlock* AA1, uint8_t blockNo, uint8_t* newBlock) { rfalPicoPassIdentifyRes idRes; rfalPicoPassSelectRes selRes; rfalPicoPassReadCheckRes rcRes; @@ -446,7 +463,6 @@ ReturnCode picopass_write_block(PicopassPacs* pacs, uint8_t blockNo, uint8_t* ne ReturnCode err; - uint8_t div_key[8] = {0}; uint8_t mac[4] = {0}; uint8_t ccnr[12] = {0}; @@ -469,9 +485,12 @@ ReturnCode picopass_write_block(PicopassPacs* pacs, uint8_t blockNo, uint8_t* ne } memcpy(ccnr, rcRes.CCNR, sizeof(rcRes.CCNR)); // last 4 bytes left 0 - loclass_diversifyKey(selRes.CSN, pacs->key, div_key); - loclass_opt_doReaderMAC(ccnr, div_key, mac); + if(memcmp(selRes.CSN, AA1[PICOPASS_CSN_BLOCK_INDEX].data, PICOPASS_BLOCK_LEN) != 0) { + FURI_LOG_E(TAG, "Wrong CSN for write"); + return ERR_REQUEST; + } + loclass_opt_doReaderMAC(ccnr, AA1[PICOPASS_KD_BLOCK_INDEX].data, mac); err = rfalPicoPassPollerCheck(mac, &chkRes); if(err != ERR_NONE) { FURI_LOG_E(TAG, "rfalPicoPassPollerCheck error %d", err); @@ -489,7 +508,7 @@ ReturnCode picopass_write_block(PicopassPacs* pacs, uint8_t blockNo, uint8_t* ne newBlock[5], newBlock[6], newBlock[7]}; - loclass_doMAC_N(data, sizeof(data), div_key, mac); + loclass_doMAC_N(data, sizeof(data), AA1[PICOPASS_KD_BLOCK_INDEX].data, mac); FURI_LOG_D( TAG, "loclass_doMAC_N %d %02x%02x%02x%02x%02x%02x%02x%02x %02x%02x%02x%02x", @@ -524,8 +543,8 @@ int32_t picopass_worker_task(void* context) { picopass_worker_detect(picopass_worker); } else if(picopass_worker->state == PicopassWorkerStateWrite) { picopass_worker_write(picopass_worker); - } else if(picopass_worker->state == PicopassWorkerStateWriteStandardKey) { - picopass_worker_write_standard_key(picopass_worker); + } else if(picopass_worker->state == PicopassWorkerStateWriteKey) { + picopass_worker_write_key(picopass_worker); } picopass_worker_disable_field(ERR_NONE); @@ -633,7 +652,7 @@ void picopass_worker_write(PicopassWorker* picopass_worker) { } } -void picopass_worker_write_standard_key(PicopassWorker* picopass_worker) { +void picopass_worker_write_key(PicopassWorker* picopass_worker) { PicopassDeviceData* dev_data = picopass_worker->dev_data; PicopassBlock* AA1 = dev_data->AA1; PicopassPacs* pacs = &dev_data->pacs; @@ -646,7 +665,7 @@ void picopass_worker_write_standard_key(PicopassWorker* picopass_worker) { uint8_t* oldKey = AA1[PICOPASS_KD_BLOCK_INDEX].data; uint8_t newKey[PICOPASS_BLOCK_LEN] = {0}; - loclass_diversifyKey(csn, picopass_iclass_key, newKey); + loclass_iclass_calc_div_key(csn, pacs->key, newKey, false); if((fuses & 0x80) == 0x80) { FURI_LOG_D(TAG, "Plain write for personalized mode key change"); @@ -658,9 +677,9 @@ void picopass_worker_write_standard_key(PicopassWorker* picopass_worker) { } } - while(picopass_worker->state == PicopassWorkerStateWriteStandardKey) { + while(picopass_worker->state == PicopassWorkerStateWriteKey) { if(picopass_detect_card(1000) == ERR_NONE) { - err = picopass_write_block(pacs, PICOPASS_KD_BLOCK_INDEX, newKey); + err = picopass_write_block(AA1, PICOPASS_KD_BLOCK_INDEX, newKey); if(err != ERR_NONE) { FURI_LOG_E(TAG, "picopass_write_block error %d", err); nextState = PicopassWorkerEventFail; diff --git a/applications/plugins/picopass/picopass_worker.h b/applications/plugins/picopass/picopass_worker.h index 775212c6611..02b088b2457 100644 --- a/applications/plugins/picopass/picopass_worker.h +++ b/applications/plugins/picopass/picopass_worker.h @@ -12,7 +12,7 @@ typedef enum { // Main worker states PicopassWorkerStateDetect, PicopassWorkerStateWrite, - PicopassWorkerStateWriteStandardKey, + PicopassWorkerStateWriteKey, // Transition PicopassWorkerStateStop, } PicopassWorkerState; diff --git a/applications/plugins/picopass/picopass_worker_i.h b/applications/plugins/picopass/picopass_worker_i.h index cf55fbdf58f..f41cfce45d4 100644 --- a/applications/plugins/picopass/picopass_worker_i.h +++ b/applications/plugins/picopass/picopass_worker_i.h @@ -31,4 +31,4 @@ int32_t picopass_worker_task(void* context); void picopass_worker_detect(PicopassWorker* picopass_worker); void picopass_worker_write(PicopassWorker* picopass_worker); -void picopass_worker_write_standard_key(PicopassWorker* picopass_worker); +void picopass_worker_write_key(PicopassWorker* picopass_worker); diff --git a/applications/plugins/picopass/scenes/picopass_scene_card_menu.c b/applications/plugins/picopass/scenes/picopass_scene_card_menu.c index a424b919a74..fe63f7c86bb 100644 --- a/applications/plugins/picopass/scenes/picopass_scene_card_menu.c +++ b/applications/plugins/picopass/scenes/picopass_scene_card_menu.c @@ -3,6 +3,7 @@ enum SubmenuIndex { SubmenuIndexSave, SubmenuIndexSaveAsLF, + SubmenuIndexChangeKey, }; void picopass_scene_card_menu_submenu_callback(void* context, uint32_t index) { @@ -25,6 +26,13 @@ void picopass_scene_card_menu_on_enter(void* context) { picopass_scene_card_menu_submenu_callback, picopass); } + submenu_add_item( + submenu, + "Change Key", + SubmenuIndexChangeKey, + picopass_scene_card_menu_submenu_callback, + picopass); + submenu_set_selected_item( picopass->submenu, scene_manager_get_scene_state(picopass->scene_manager, PicopassSceneCardMenu)); @@ -49,6 +57,11 @@ bool picopass_scene_card_menu_on_event(void* context, SceneManagerEvent event) { picopass->dev->format = PicopassDeviceSaveFormatLF; scene_manager_next_scene(picopass->scene_manager, PicopassSceneSaveName); consumed = true; + } else if(event.event == SubmenuIndexChangeKey) { + scene_manager_set_scene_state( + picopass->scene_manager, PicopassSceneCardMenu, SubmenuIndexChangeKey); + scene_manager_next_scene(picopass->scene_manager, PicopassSceneKeyMenu); + consumed = true; } } else if(event.type == SceneManagerEventTypeBack) { consumed = scene_manager_search_and_switch_to_previous_scene( diff --git a/applications/plugins/picopass/scenes/picopass_scene_config.h b/applications/plugins/picopass/scenes/picopass_scene_config.h index 95700787fb4..f5a90d46e46 100644 --- a/applications/plugins/picopass/scenes/picopass_scene_config.h +++ b/applications/plugins/picopass/scenes/picopass_scene_config.h @@ -13,3 +13,4 @@ ADD_SCENE(picopass, write_card, WriteCard) ADD_SCENE(picopass, write_card_success, WriteCardSuccess) ADD_SCENE(picopass, read_factory_success, ReadFactorySuccess) ADD_SCENE(picopass, write_key, WriteKey) +ADD_SCENE(picopass, key_menu, KeyMenu) diff --git a/applications/plugins/picopass/scenes/picopass_scene_key_menu.c b/applications/plugins/picopass/scenes/picopass_scene_key_menu.c new file mode 100644 index 00000000000..b1db37f81a2 --- /dev/null +++ b/applications/plugins/picopass/scenes/picopass_scene_key_menu.c @@ -0,0 +1,100 @@ +#include "../picopass_i.h" + +enum SubmenuIndex { + SubmenuIndexWriteStandard, + SubmenuIndexWriteiCE, + SubmenuIndexWriteiCL, + SubmenuIndexWriteiCS, + SubmenuIndexWriteCustom, //TODO: user input of key +}; + +extern const uint8_t picopass_xice_key[]; +extern const uint8_t picopass_xicl_key[]; +extern const uint8_t picopass_xics_key[]; +extern const uint8_t picopass_iclass_key[]; + +void picopass_scene_key_menu_submenu_callback(void* context, uint32_t index) { + Picopass* picopass = context; + + view_dispatcher_send_custom_event(picopass->view_dispatcher, index); +} + +void picopass_scene_key_menu_on_enter(void* context) { + Picopass* picopass = context; + Submenu* submenu = picopass->submenu; + + submenu_add_item( + submenu, + "Write Standard", + SubmenuIndexWriteStandard, + picopass_scene_key_menu_submenu_callback, + picopass); + submenu_add_item( + submenu, + "Write iCE", + SubmenuIndexWriteiCE, + picopass_scene_key_menu_submenu_callback, + picopass); + submenu_add_item( + submenu, + "Write iCL", + SubmenuIndexWriteiCL, + picopass_scene_key_menu_submenu_callback, + picopass); + submenu_add_item( + submenu, + "Write iCS", + SubmenuIndexWriteiCS, + picopass_scene_key_menu_submenu_callback, + picopass); + + submenu_set_selected_item( + picopass->submenu, + scene_manager_get_scene_state(picopass->scene_manager, PicopassSceneKeyMenu)); + + view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewMenu); +} + +bool picopass_scene_key_menu_on_event(void* context, SceneManagerEvent event) { + Picopass* picopass = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubmenuIndexWriteStandard) { + scene_manager_set_scene_state( + picopass->scene_manager, PicopassSceneKeyMenu, SubmenuIndexWriteStandard); + memcpy(picopass->dev->dev_data.pacs.key, picopass_iclass_key, PICOPASS_BLOCK_LEN); + scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteKey); + consumed = true; + } else if(event.event == SubmenuIndexWriteiCE) { + scene_manager_set_scene_state( + picopass->scene_manager, PicopassSceneKeyMenu, SubmenuIndexWriteiCE); + memcpy(picopass->dev->dev_data.pacs.key, picopass_xice_key, PICOPASS_BLOCK_LEN); + scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteKey); + consumed = true; + } else if(event.event == SubmenuIndexWriteiCL) { + scene_manager_set_scene_state( + picopass->scene_manager, PicopassSceneKeyMenu, SubmenuIndexWriteiCE); + memcpy(picopass->dev->dev_data.pacs.key, picopass_xicl_key, PICOPASS_BLOCK_LEN); + scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteKey); + consumed = true; + } else if(event.event == SubmenuIndexWriteiCS) { + scene_manager_set_scene_state( + picopass->scene_manager, PicopassSceneKeyMenu, SubmenuIndexWriteiCE); + memcpy(picopass->dev->dev_data.pacs.key, picopass_xics_key, PICOPASS_BLOCK_LEN); + scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteKey); + consumed = true; + } + } else if(event.type == SceneManagerEventTypeBack) { + consumed = scene_manager_search_and_switch_to_previous_scene( + picopass->scene_manager, PicopassSceneStart); + } + + return consumed; +} + +void picopass_scene_key_menu_on_exit(void* context) { + Picopass* picopass = context; + + submenu_reset(picopass->submenu); +} diff --git a/applications/plugins/picopass/scenes/picopass_scene_read_card.c b/applications/plugins/picopass/scenes/picopass_scene_read_card.c index 90422a2e738..c62cba8ea97 100644 --- a/applications/plugins/picopass/scenes/picopass_scene_read_card.c +++ b/applications/plugins/picopass/scenes/picopass_scene_read_card.c @@ -1,7 +1,7 @@ #include "../picopass_i.h" #include -const uint8_t picopass_factory_key_check[] = {0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87}; +extern const uint8_t picopass_factory_debit_key[]; void picopass_read_card_worker_callback(PicopassWorkerEvent event, void* context) { UNUSED(event); @@ -38,7 +38,7 @@ bool picopass_scene_read_card_on_event(void* context, SceneManagerEvent event) { if(event.event == PicopassCustomEventWorkerExit) { if(memcmp( picopass->dev->dev_data.pacs.key, - picopass_factory_key_check, + picopass_factory_debit_key, PICOPASS_BLOCK_LEN) == 0) { scene_manager_next_scene(picopass->scene_manager, PicopassSceneReadFactorySuccess); } else { diff --git a/applications/plugins/picopass/scenes/picopass_scene_read_factory_success.c b/applications/plugins/picopass/scenes/picopass_scene_read_factory_success.c index 8e32d21f7b5..b98951dcfb8 100644 --- a/applications/plugins/picopass/scenes/picopass_scene_read_factory_success.c +++ b/applications/plugins/picopass/scenes/picopass_scene_read_factory_success.c @@ -1,6 +1,8 @@ #include "../picopass_i.h" #include +extern const uint8_t picopass_iclass_key[]; + void picopass_scene_read_factory_success_widget_callback( GuiButtonType result, InputType type, @@ -63,6 +65,7 @@ bool picopass_scene_read_factory_success_on_event(void* context, SceneManagerEve if(event.event == GuiButtonTypeLeft) { consumed = scene_manager_previous_scene(picopass->scene_manager); } else if(event.event == GuiButtonTypeCenter) { + memcpy(picopass->dev->dev_data.pacs.key, picopass_iclass_key, PICOPASS_BLOCK_LEN); scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteKey); consumed = true; } diff --git a/applications/plugins/picopass/scenes/picopass_scene_write_key.c b/applications/plugins/picopass/scenes/picopass_scene_write_key.c index 83d594ca2bc..0f417e1c3fe 100644 --- a/applications/plugins/picopass/scenes/picopass_scene_write_key.c +++ b/applications/plugins/picopass/scenes/picopass_scene_write_key.c @@ -20,7 +20,7 @@ void picopass_scene_write_key_on_enter(void* context) { view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewPopup); picopass_worker_start( picopass->worker, - PicopassWorkerStateWriteStandardKey, + PicopassWorkerStateWriteKey, &picopass->dev->dev_data, picopass_write_key_worker_callback, picopass); From 98193067314b230ad660762aa415c21f4bb6c781 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Tue, 7 Mar 2023 15:09:45 +0200 Subject: [PATCH 451/824] [Fl-3147] Remove ValueMutex (#2467) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Move keypad_test to furi_mutex * Move text_box_test to furi_mutex * Move snake_game to furi_mutex * Remove ValueMutex completely * Snake Game: simplify code and fix PVS warning * F18: sync API symbols Co-authored-by: あく --- applications/debug/keypad_test/keypad_test.c | 54 ++++--- .../debug/text_box_test/text_box_test.c | 30 ++-- .../debug/unit_tests/furi/furi_test.c | 6 - .../unit_tests/furi/furi_valuemutex_test.c | 41 ----- applications/plugins/snake_game/snake_game.c | 24 +-- firmware/targets/f18/api_symbols.csv | 18 ++- firmware/targets/f7/api_symbols.csv | 8 +- furi/core/valuemutex.c | 59 ------- furi/core/valuemutex.h | 149 ------------------ furi/furi.h | 1 - 10 files changed, 68 insertions(+), 322 deletions(-) delete mode 100644 applications/debug/unit_tests/furi/furi_valuemutex_test.c delete mode 100644 furi/core/valuemutex.c delete mode 100644 furi/core/valuemutex.h diff --git a/applications/debug/keypad_test/keypad_test.c b/applications/debug/keypad_test/keypad_test.c index 2470baf8d2d..9e8881defaf 100644 --- a/applications/debug/keypad_test/keypad_test.c +++ b/applications/debug/keypad_test/keypad_test.c @@ -11,6 +11,7 @@ typedef struct { uint16_t left; uint16_t right; uint16_t ok; + FuriMutex* mutex; } KeypadTestState; static void keypad_test_reset_state(KeypadTestState* state) { @@ -22,7 +23,8 @@ static void keypad_test_reset_state(KeypadTestState* state) { } static void keypad_test_render_callback(Canvas* canvas, void* ctx) { - KeypadTestState* state = (KeypadTestState*)acquire_mutex((ValueMutex*)ctx, 25); + KeypadTestState* state = ctx; + furi_mutex_acquire(state->mutex, FuriWaitForever); canvas_clear(canvas); char strings[5][20]; @@ -51,7 +53,7 @@ static void keypad_test_render_callback(Canvas* canvas, void* ctx) { canvas_draw_str(canvas, 10, 63, "[back] - reset, hold to exit"); - release_mutex((ValueMutex*)ctx, state); + furi_mutex_release(state->mutex); } static void keypad_test_input_callback(InputEvent* input_event, void* ctx) { @@ -64,17 +66,17 @@ int32_t keypad_test_app(void* p) { FuriMessageQueue* event_queue = furi_message_queue_alloc(32, sizeof(InputEvent)); furi_check(event_queue); - KeypadTestState _state = {{false, false, false, false, false}, 0, 0, 0, 0, 0}; + KeypadTestState state = {{false, false, false, false, false}, 0, 0, 0, 0, 0, NULL}; + state.mutex = furi_mutex_alloc(FuriMutexTypeNormal); - ValueMutex state_mutex; - if(!init_mutex(&state_mutex, &_state, sizeof(KeypadTestState))) { + if(!state.mutex) { FURI_LOG_E(TAG, "cannot create mutex"); return 0; } ViewPort* view_port = view_port_alloc(); - view_port_draw_callback_set(view_port, keypad_test_render_callback, &state_mutex); + view_port_draw_callback_set(view_port, keypad_test_render_callback, &state); view_port_input_callback_set(view_port, keypad_test_input_callback, event_queue); // Open GUI and register view_port @@ -83,7 +85,7 @@ int32_t keypad_test_app(void* p) { InputEvent event; while(furi_message_queue_get(event_queue, &event, FuriWaitForever) == FuriStatusOk) { - KeypadTestState* state = (KeypadTestState*)acquire_mutex_block(&state_mutex); + furi_mutex_acquire(state.mutex, FuriWaitForever); FURI_LOG_I( TAG, "key: %s type: %s", @@ -92,54 +94,54 @@ int32_t keypad_test_app(void* p) { if(event.key == InputKeyRight) { if(event.type == InputTypePress) { - state->press[0] = true; + state.press[0] = true; } else if(event.type == InputTypeRelease) { - state->press[0] = false; + state.press[0] = false; } else if(event.type == InputTypeShort) { - ++state->right; + ++state.right; } } else if(event.key == InputKeyLeft) { if(event.type == InputTypePress) { - state->press[1] = true; + state.press[1] = true; } else if(event.type == InputTypeRelease) { - state->press[1] = false; + state.press[1] = false; } else if(event.type == InputTypeShort) { - ++state->left; + ++state.left; } } else if(event.key == InputKeyUp) { if(event.type == InputTypePress) { - state->press[2] = true; + state.press[2] = true; } else if(event.type == InputTypeRelease) { - state->press[2] = false; + state.press[2] = false; } else if(event.type == InputTypeShort) { - ++state->up; + ++state.up; } } else if(event.key == InputKeyDown) { if(event.type == InputTypePress) { - state->press[3] = true; + state.press[3] = true; } else if(event.type == InputTypeRelease) { - state->press[3] = false; + state.press[3] = false; } else if(event.type == InputTypeShort) { - ++state->down; + ++state.down; } } else if(event.key == InputKeyOk) { if(event.type == InputTypePress) { - state->press[4] = true; + state.press[4] = true; } else if(event.type == InputTypeRelease) { - state->press[4] = false; + state.press[4] = false; } else if(event.type == InputTypeShort) { - ++state->ok; + ++state.ok; } } else if(event.key == InputKeyBack) { if(event.type == InputTypeLong) { - release_mutex(&state_mutex, state); + furi_mutex_release(state.mutex); break; } else if(event.type == InputTypeShort) { - keypad_test_reset_state(state); + keypad_test_reset_state(&state); } } - release_mutex(&state_mutex, state); + furi_mutex_release(state.mutex); view_port_update(view_port); } @@ -147,7 +149,7 @@ int32_t keypad_test_app(void* p) { gui_remove_view_port(gui, view_port); view_port_free(view_port); furi_message_queue_free(event_queue); - delete_mutex(&state_mutex); + furi_mutex_free(state.mutex); furi_record_close(RECORD_GUI); diff --git a/applications/debug/text_box_test/text_box_test.c b/applications/debug/text_box_test/text_box_test.c index d7194ffee41..b980f686e18 100644 --- a/applications/debug/text_box_test/text_box_test.c +++ b/applications/debug/text_box_test/text_box_test.c @@ -53,15 +53,17 @@ static void (*text_box_test_render[])(Canvas* canvas) = { typedef struct { uint32_t idx; + FuriMutex* mutex; } TextBoxTestState; static void text_box_test_render_callback(Canvas* canvas, void* ctx) { - TextBoxTestState* state = acquire_mutex((ValueMutex*)ctx, 25); + TextBoxTestState* state = ctx; + furi_mutex_acquire(state->mutex, FuriWaitForever); canvas_clear(canvas); text_box_test_render[state->idx](canvas); - release_mutex((ValueMutex*)ctx, state); + furi_mutex_release(state->mutex); } static void text_box_test_input_callback(InputEvent* input_event, void* ctx) { @@ -74,17 +76,17 @@ int32_t text_box_test_app(void* p) { FuriMessageQueue* event_queue = furi_message_queue_alloc(32, sizeof(InputEvent)); furi_check(event_queue); - TextBoxTestState _state = {.idx = 0}; + TextBoxTestState state = {.idx = 0, .mutex = NULL}; + state.mutex = furi_mutex_alloc(FuriMutexTypeNormal); - ValueMutex state_mutex; - if(!init_mutex(&state_mutex, &_state, sizeof(TextBoxTestState))) { + if(!state.mutex) { FURI_LOG_E(TAG, "Cannot create mutex"); return 0; } ViewPort* view_port = view_port_alloc(); - view_port_draw_callback_set(view_port, text_box_test_render_callback, &state_mutex); + view_port_draw_callback_set(view_port, text_box_test_render_callback, &state); view_port_input_callback_set(view_port, text_box_test_input_callback, event_queue); // Open GUI and register view_port @@ -94,24 +96,24 @@ int32_t text_box_test_app(void* p) { uint32_t test_renders_num = COUNT_OF(text_box_test_render); InputEvent event; while(furi_message_queue_get(event_queue, &event, FuriWaitForever) == FuriStatusOk) { - TextBoxTestState* state = acquire_mutex_block(&state_mutex); + furi_mutex_acquire(state.mutex, FuriWaitForever); if(event.type == InputTypeShort) { if(event.key == InputKeyRight) { - if(state->idx < test_renders_num - 1) { - state->idx++; + if(state.idx < test_renders_num - 1) { + state.idx++; } } else if(event.key == InputKeyLeft) { - if(state->idx > 0) { - state->idx--; + if(state.idx > 0) { + state.idx--; } } else if(event.key == InputKeyBack) { - release_mutex(&state_mutex, state); + furi_mutex_release(state.mutex); break; } } - release_mutex(&state_mutex, state); + furi_mutex_release(state.mutex); view_port_update(view_port); } @@ -119,7 +121,7 @@ int32_t text_box_test_app(void* p) { gui_remove_view_port(gui, view_port); view_port_free(view_port); furi_message_queue_free(event_queue); - delete_mutex(&state_mutex); + furi_mutex_free(state.mutex); furi_record_close(RECORD_GUI); diff --git a/applications/debug/unit_tests/furi/furi_test.c b/applications/debug/unit_tests/furi/furi_test.c index eed9e420597..33ec5fd019b 100644 --- a/applications/debug/unit_tests/furi/furi_test.c +++ b/applications/debug/unit_tests/furi/furi_test.c @@ -5,7 +5,6 @@ // v2 tests void test_furi_create_open(); -void test_furi_valuemutex(); void test_furi_concurrent_access(); void test_furi_pubsub(); @@ -30,10 +29,6 @@ MU_TEST(mu_test_furi_create_open) { test_furi_create_open(); } -MU_TEST(mu_test_furi_valuemutex) { - test_furi_valuemutex(); -} - MU_TEST(mu_test_furi_pubsub) { test_furi_pubsub(); } @@ -51,7 +46,6 @@ MU_TEST_SUITE(test_suite) { // v2 tests MU_RUN_TEST(mu_test_furi_create_open); - MU_RUN_TEST(mu_test_furi_valuemutex); MU_RUN_TEST(mu_test_furi_pubsub); MU_RUN_TEST(mu_test_furi_memmgr); } diff --git a/applications/debug/unit_tests/furi/furi_valuemutex_test.c b/applications/debug/unit_tests/furi/furi_valuemutex_test.c deleted file mode 100644 index 02fd47eeb31..00000000000 --- a/applications/debug/unit_tests/furi/furi_valuemutex_test.c +++ /dev/null @@ -1,41 +0,0 @@ -#include -#include -#include - -#include "../minunit.h" - -void test_furi_valuemutex() { - const int init_value = 0xdeadbeef; - const int changed_value = 0x12345678; - - int value = init_value; - bool result; - ValueMutex valuemutex; - - // init mutex case - result = init_mutex(&valuemutex, &value, sizeof(value)); - mu_assert(result, "init mutex failed"); - - // acquire mutex case - int* value_pointer = acquire_mutex(&valuemutex, 100); - mu_assert_pointers_eq(value_pointer, &value); - - // second acquire mutex case - int* value_pointer_second = acquire_mutex(&valuemutex, 100); - mu_assert_pointers_eq(value_pointer_second, NULL); - - // change value case - *value_pointer = changed_value; - mu_assert_int_eq(value, changed_value); - - // release mutex case - result = release_mutex(&valuemutex, &value); - mu_assert(result, "release mutex failed"); - - // TODO - //acquire mutex blocking case - //write mutex blocking case - //read mutex blocking case - - mu_check(delete_mutex(&valuemutex)); -} diff --git a/applications/plugins/snake_game/snake_game.c b/applications/plugins/snake_game/snake_game.c index 2815e2f372e..3cf9b6d53d0 100644 --- a/applications/plugins/snake_game/snake_game.c +++ b/applications/plugins/snake_game/snake_game.c @@ -50,6 +50,7 @@ typedef struct { Direction nextMovement; // if backward of currentMovement, ignore Point fruit; GameState state; + FuriMutex* mutex; } SnakeState; typedef enum { @@ -92,12 +93,10 @@ const NotificationSequence sequence_eat = { }; static void snake_game_render_callback(Canvas* const canvas, void* ctx) { - const SnakeState* snake_state = acquire_mutex((ValueMutex*)ctx, 25); - if(snake_state == NULL) { - return; - } + furi_assert(ctx); + const SnakeState* snake_state = ctx; - // Before the function is called, the state is set with the canvas_reset(canvas) + furi_mutex_acquire(snake_state->mutex, FuriWaitForever); // Frame canvas_draw_frame(canvas, 0, 0, 128, 64); @@ -134,7 +133,7 @@ static void snake_game_render_callback(Canvas* const canvas, void* ctx) { canvas_draw_str_aligned(canvas, 64, 41, AlignCenter, AlignBottom, buffer); } - release_mutex((ValueMutex*)ctx, snake_state); + furi_mutex_release(snake_state->mutex); } static void snake_game_input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) { @@ -324,15 +323,16 @@ int32_t snake_game_app(void* p) { SnakeState* snake_state = malloc(sizeof(SnakeState)); snake_game_init_game(snake_state); - ValueMutex state_mutex; - if(!init_mutex(&state_mutex, snake_state, sizeof(SnakeState))) { + snake_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal); + + if(!snake_state->mutex) { FURI_LOG_E("SnakeGame", "cannot create mutex\r\n"); free(snake_state); return 255; } ViewPort* view_port = view_port_alloc(); - view_port_draw_callback_set(view_port, snake_game_render_callback, &state_mutex); + view_port_draw_callback_set(view_port, snake_game_render_callback, snake_state); view_port_input_callback_set(view_port, snake_game_input_callback, event_queue); FuriTimer* timer = @@ -352,7 +352,7 @@ int32_t snake_game_app(void* p) { for(bool processing = true; processing;) { FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100); - SnakeState* snake_state = (SnakeState*)acquire_mutex_block(&state_mutex); + furi_mutex_acquire(snake_state->mutex, FuriWaitForever); if(event_status == FuriStatusOk) { // press events @@ -391,7 +391,7 @@ int32_t snake_game_app(void* p) { } view_port_update(view_port); - release_mutex(&state_mutex, snake_state); + furi_mutex_release(snake_state->mutex); } // Return backlight to normal state @@ -404,7 +404,7 @@ int32_t snake_game_app(void* p) { furi_record_close(RECORD_NOTIFICATION); view_port_free(view_port); furi_message_queue_free(event_queue); - delete_mutex(&state_mutex); + furi_mutex_free(snake_state->mutex); free(snake_state); return 0; diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index 608f5b19e31..8060d38a20b 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,17.0,, +Version,+,18.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -156,6 +156,7 @@ Header,+,lib/toolbox/manchester_decoder.h,, Header,+,lib/toolbox/manchester_encoder.h,, Header,+,lib/toolbox/md5.h,, Header,+,lib/toolbox/path.h,, +Header,+,lib/toolbox/pretty_format.h,, Header,+,lib/toolbox/protocols/protocol_dict.h,, Header,+,lib/toolbox/random_name.h,, Header,+,lib/toolbox/saved_struct.h,, @@ -439,7 +440,6 @@ Function,-,_wctomb_r,int,"_reent*, char*, wchar_t, _mbstate_t*" Function,-,a64l,long,const char* Function,+,abort,void, Function,-,abs,int,int -Function,+,acquire_mutex,void*,"ValueMutex*, uint32_t" Function,-,aligned_alloc,void*,"size_t, size_t" Function,+,aligned_free,void,void* Function,+,aligned_malloc,void*,"size_t, size_t" @@ -573,7 +573,6 @@ Function,-,ctermid,char*,char* Function,-,ctime,char*,const time_t* Function,-,ctime_r,char*,"const time_t*, char*" Function,-,cuserid,char*,char* -Function,+,delete_mutex,_Bool,ValueMutex* Function,+,dialog_ex_alloc,DialogEx*, Function,+,dialog_ex_disable_extended_events,void,DialogEx* Function,+,dialog_ex_enable_extended_events,void,DialogEx* @@ -684,6 +683,7 @@ Function,+,file_browser_worker_set_folder_callback,void,"BrowserWorker*, Browser Function,+,file_browser_worker_set_item_callback,void,"BrowserWorker*, BrowserWorkerListItemCallback" Function,+,file_browser_worker_set_list_callback,void,"BrowserWorker*, BrowserWorkerListLoadCallback" Function,+,file_browser_worker_set_long_load_callback,void,"BrowserWorker*, BrowserWorkerLongLoadCallback" +Function,+,file_info_is_dir,_Bool,const FileInfo* Function,+,file_stream_alloc,Stream*,Storage* Function,+,file_stream_close,_Bool,Stream* Function,+,file_stream_get_error,FS_Error,Stream* @@ -707,6 +707,7 @@ Function,-,flipper_application_preload_status_to_string,const char*,FlipperAppli Function,+,flipper_application_spawn,FuriThread*,"FlipperApplication*, void*" Function,+,flipper_format_buffered_file_alloc,FlipperFormat*,Storage* Function,+,flipper_format_buffered_file_close,_Bool,FlipperFormat* +Function,+,flipper_format_buffered_file_open_always,_Bool,"FlipperFormat*, const char*" Function,+,flipper_format_buffered_file_open_existing,_Bool,"FlipperFormat*, const char*" Function,+,flipper_format_delete_key,_Bool,"FlipperFormat*, const char*" Function,+,flipper_format_file_alloc,FlipperFormat*,Storage* @@ -1234,6 +1235,7 @@ Function,+,furi_thread_flags_get,uint32_t, Function,+,furi_thread_flags_set,uint32_t,"FuriThreadId, uint32_t" Function,+,furi_thread_flags_wait,uint32_t,"uint32_t, uint32_t, uint32_t" Function,+,furi_thread_free,void,FuriThread* +Function,+,furi_thread_get_appid,const char*,FuriThreadId Function,+,furi_thread_get_current,FuriThread*, Function,+,furi_thread_get_current_id,FuriThreadId, Function,+,furi_thread_get_current_priority,FuriThreadPriority, @@ -1248,6 +1250,7 @@ Function,+,furi_thread_is_suspended,_Bool,FuriThreadId Function,+,furi_thread_join,_Bool,FuriThread* Function,+,furi_thread_mark_as_service,void,FuriThread* Function,+,furi_thread_resume,void,FuriThreadId +Function,+,furi_thread_set_appid,void,"FuriThread*, const char*" Function,+,furi_thread_set_callback,void,"FuriThread*, FuriThreadCallback" Function,+,furi_thread_set_context,void,"FuriThread*, void*" Function,+,furi_thread_set_current_priority,void,FuriThreadPriority @@ -1312,7 +1315,6 @@ Function,+,icon_get_data,const uint8_t*,const Icon* Function,+,icon_get_height,uint8_t,const Icon* Function,+,icon_get_width,uint8_t,const Icon* Function,-,index,char*,"const char*, int" -Function,+,init_mutex,_Bool,"ValueMutex*, void*, size_t" Function,-,initstate,char*,"unsigned, char*, size_t" Function,+,input_get_key_name,const char*,InputKey Function,+,input_get_type_name,const char*,InputType @@ -1491,6 +1493,7 @@ Function,+,power_get_pubsub,FuriPubSub*,Power* Function,+,power_is_battery_healthy,_Bool,Power* Function,+,power_off,void,Power* Function,+,power_reboot,void,PowerBootMode +Function,+,pretty_format_bytes_hex_canonical,void,"FuriString*, size_t, const char*, const uint8_t*, size_t" Function,-,printf,int,"const char*, ..." Function,+,property_value_out,void,"PropertyValueContext*, const char*, unsigned int, ..." Function,+,protocol_dict_alloc,ProtocolDict*,"const ProtocolBase**, size_t" @@ -1534,12 +1537,10 @@ Function,+,rand,int, Function,-,rand_r,int,unsigned* Function,+,random,long, Function,-,rawmemchr,void*,"const void*, int" -Function,-,read_mutex,_Bool,"ValueMutex*, void*, size_t, uint32_t" Function,+,realloc,void*,"void*, size_t" Function,-,reallocarray,void*,"void*, size_t, size_t" Function,-,reallocf,void*,"void*, size_t" Function,-,realpath,char*,"const char*, char*" -Function,+,release_mutex,_Bool,"ValueMutex*, const void*" Function,-,remove,int,const char* Function,-,rename,int,"const char*, const char*" Function,-,renameat,int,"int, const char*, int, const char*" @@ -1617,14 +1618,18 @@ Function,-,srand48,void,long Function,-,srandom,void,unsigned Function,+,sscanf,int,"const char*, const char*, ..." Function,+,storage_common_copy,FS_Error,"Storage*, const char*, const char*" +Function,+,storage_common_exists,_Bool,"Storage*, const char*" Function,+,storage_common_fs_info,FS_Error,"Storage*, const char*, uint64_t*, uint64_t*" Function,+,storage_common_merge,FS_Error,"Storage*, const char*, const char*" +Function,+,storage_common_migrate,FS_Error,"Storage*, const char*, const char*" Function,+,storage_common_mkdir,FS_Error,"Storage*, const char*" Function,+,storage_common_remove,FS_Error,"Storage*, const char*" Function,+,storage_common_rename,FS_Error,"Storage*, const char*, const char*" +Function,+,storage_common_resolve_path_and_ensure_app_directory,void,"Storage*, FuriString*" Function,+,storage_common_stat,FS_Error,"Storage*, const char*, FileInfo*" Function,+,storage_common_timestamp,FS_Error,"Storage*, const char*, uint32_t*" Function,+,storage_dir_close,_Bool,File* +Function,+,storage_dir_exists,_Bool,"Storage*, const char*" Function,+,storage_dir_open,_Bool,"File*, const char*" Function,+,storage_dir_read,_Bool,"File*, FileInfo*, char*, uint16_t" Function,-,storage_dir_rewind,_Bool,File* @@ -1995,7 +2000,6 @@ Function,+,widget_alloc,Widget*, Function,+,widget_free,void,Widget* Function,+,widget_get_view,View*,Widget* Function,+,widget_reset,void,Widget* -Function,-,write_mutex,_Bool,"ValueMutex*, void*, size_t, uint32_t" Function,-,xPortGetFreeHeapSize,size_t, Function,-,xPortGetMinimumEverFreeHeapSize,size_t, Function,-,xPortStartScheduler,BaseType_t, diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 1c35abd3bf3..57c1a2fa464 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,17.0,, +Version,+,18.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -491,7 +491,6 @@ Function,-,acosh,double,double Function,-,acoshf,float,float Function,-,acoshl,long double,long double Function,-,acosl,long double,long double -Function,+,acquire_mutex,void*,"ValueMutex*, uint32_t" Function,-,aligned_alloc,void*,"size_t, size_t" Function,+,aligned_free,void,void* Function,+,aligned_malloc,void*,"size_t, size_t" @@ -703,7 +702,6 @@ Function,-,ctermid,char*,char* Function,-,ctime,char*,const time_t* Function,-,ctime_r,char*,"const time_t*, char*" Function,-,cuserid,char*,char* -Function,+,delete_mutex,_Bool,ValueMutex* Function,+,dialog_ex_alloc,DialogEx*, Function,+,dialog_ex_disable_extended_events,void,DialogEx* Function,+,dialog_ex_enable_extended_events,void,DialogEx* @@ -1692,7 +1690,6 @@ Function,+,infrared_worker_tx_set_get_signal_callback,void,"InfraredWorker*, Inf Function,+,infrared_worker_tx_set_signal_sent_callback,void,"InfraredWorker*, InfraredWorkerMessageSentCallback, void*" Function,+,infrared_worker_tx_start,void,InfraredWorker* Function,+,infrared_worker_tx_stop,void,InfraredWorker* -Function,+,init_mutex,_Bool,"ValueMutex*, void*, size_t" Function,-,initstate,char*,"unsigned, char*, size_t" Function,+,input_get_key_name,const char*,InputKey Function,+,input_get_type_name,const char*,InputType @@ -2164,12 +2161,10 @@ Function,+,rand,int, Function,-,rand_r,int,unsigned* Function,+,random,long, Function,-,rawmemchr,void*,"const void*, int" -Function,-,read_mutex,_Bool,"ValueMutex*, void*, size_t, uint32_t" Function,+,realloc,void*,"void*, size_t" Function,-,reallocarray,void*,"void*, size_t, size_t" Function,-,reallocf,void*,"void*, size_t" Function,-,realpath,char*,"const char*, char*" -Function,+,release_mutex,_Bool,"ValueMutex*, const void*" Function,-,remainder,double,"double, double" Function,-,remainderf,float,"float, float" Function,-,remainderl,long double,"long double, long double" @@ -2967,7 +2962,6 @@ Function,+,widget_alloc,Widget*, Function,+,widget_free,void,Widget* Function,+,widget_get_view,View*,Widget* Function,+,widget_reset,void,Widget* -Function,-,write_mutex,_Bool,"ValueMutex*, void*, size_t, uint32_t" Function,-,xPortGetFreeHeapSize,size_t, Function,-,xPortGetMinimumEverFreeHeapSize,size_t, Function,-,xPortStartScheduler,BaseType_t, diff --git a/furi/core/valuemutex.c b/furi/core/valuemutex.c deleted file mode 100644 index bf4e6130bd5..00000000000 --- a/furi/core/valuemutex.c +++ /dev/null @@ -1,59 +0,0 @@ -#include "valuemutex.h" - -#include - -bool init_mutex(ValueMutex* valuemutex, void* value, size_t size) { - // mutex without name, - // no attributes (unfortunately robust mutex is not supported by FreeRTOS), - // with dynamic memory allocation - valuemutex->mutex = furi_mutex_alloc(FuriMutexTypeNormal); - if(valuemutex->mutex == NULL) return false; - - valuemutex->value = value; - valuemutex->size = size; - - return true; -} - -bool delete_mutex(ValueMutex* valuemutex) { - if(furi_mutex_acquire(valuemutex->mutex, FuriWaitForever) == FuriStatusOk) { - furi_mutex_free(valuemutex->mutex); - return true; - } else { - return false; - } -} - -void* acquire_mutex(ValueMutex* valuemutex, uint32_t timeout) { - if(furi_mutex_acquire(valuemutex->mutex, timeout) == FuriStatusOk) { - return valuemutex->value; - } else { - return NULL; - } -} - -bool release_mutex(ValueMutex* valuemutex, const void* value) { - if(value != valuemutex->value) return false; - - if(furi_mutex_release(valuemutex->mutex) != FuriStatusOk) return false; - - return true; -} - -bool read_mutex(ValueMutex* valuemutex, void* data, size_t len, uint32_t timeout) { - void* value = acquire_mutex(valuemutex, timeout); - if(value == NULL || len > valuemutex->size) return false; - memcpy(data, value, len > 0 ? len : valuemutex->size); - if(!release_mutex(valuemutex, value)) return false; - - return true; -} - -bool write_mutex(ValueMutex* valuemutex, void* data, size_t len, uint32_t timeout) { - void* value = acquire_mutex(valuemutex, timeout); - if(value == NULL || len > valuemutex->size) return false; - memcpy(value, data, len > 0 ? len : valuemutex->size); - if(!release_mutex(valuemutex, value)) return false; - - return true; -} diff --git a/furi/core/valuemutex.h b/furi/core/valuemutex.h deleted file mode 100644 index 0d867a1df21..00000000000 --- a/furi/core/valuemutex.h +++ /dev/null @@ -1,149 +0,0 @@ -#pragma once - -#include -#include "mutex.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * == ValueMutex == - - * The most simple concept is ValueMutex. - * It is wrapper around mutex and value pointer. - * You can take and give mutex to work with value and read and write value. - */ - -typedef struct { - void* value; - size_t size; - FuriMutex* mutex; -} ValueMutex; - -/** - * Creates ValueMutex. - */ -bool init_mutex(ValueMutex* valuemutex, void* value, size_t size); - -/** - * Free resources allocated by `init_mutex`. - * This function doesn't free the memory occupied by `ValueMutex` itself. - */ -bool delete_mutex(ValueMutex* valuemutex); - -/** - * Call for work with data stored in mutex. - * @return pointer to data if success, NULL otherwise. - */ -void* acquire_mutex(ValueMutex* valuemutex, uint32_t timeout); - -/** - * Helper: infinitely wait for mutex - */ -static inline void* acquire_mutex_block(ValueMutex* valuemutex) { - return acquire_mutex(valuemutex, FuriWaitForever); -} - -/** - * With statement for value mutex, acts as lambda - * @param name a resource name, const char* - * @param function_body a (){} lambda declaration, - * executed within you parent function context. - */ -#define with_value_mutex(value_mutex, function_body) \ - { \ - void* p = acquire_mutex_block(value_mutex); \ - furi_check(p); \ - ({ void __fn__ function_body __fn__; })(p); \ - release_mutex(value_mutex, p); \ - } - -/** - * Release mutex after end of work with data. - * Call `release_mutex` and pass ValueData instance and pointer to data. - */ -bool release_mutex(ValueMutex* valuemutex, const void* value); - -/** - * Instead of take-access-give sequence you can use `read_mutex` and `write_mutex` functions. - * Both functions return true in case of success, false otherwise. - */ -bool read_mutex(ValueMutex* valuemutex, void* data, size_t len, uint32_t timeout); - -bool write_mutex(ValueMutex* valuemutex, void* data, size_t len, uint32_t timeout); - -inline static bool write_mutex_block(ValueMutex* valuemutex, void* data, size_t len) { - return write_mutex(valuemutex, data, len, FuriWaitForever); -} - -inline static bool read_mutex_block(ValueMutex* valuemutex, void* data, size_t len) { - return read_mutex(valuemutex, data, len, FuriWaitForever); -} - -#ifdef __cplusplus -} -#endif - -/* - -Usage example - -```C -// MANIFEST -// name="example-provider-app" -// stack=128 - -void provider_app(void* _p) { - // create record with mutex - uint32_t example_value = 0; - ValueMutex example_mutex; - // call `init_mutex`. - if(!init_mutex(&example_mutex, (void*)&example_value, sizeof(uint32_t))) { - printf("critical error\n"); - flapp_exit(NULL); - } - - furi_record_create("provider/example", (void*)&example_mutex); - - // we are ready to provide record to other apps - flapp_ready(); - - // get value and increment it - while(1) { - uint32_t* value = acquire_mutex(&example_mutex, OsWaitForever); - if(value != NULL) { - value++; - } - release_mutex(&example_mutex, value); - - furi_delay_ms(100); - } -} - -// MANIFEST -// name="example-consumer-app" -// stack=128 -// require="example-provider-app" -void consumer_app(void* _p) { - // this app run after flapp_ready call in all requirements app - - // open mutex value - ValueMutex* counter_mutex = furi_record_open("provider/example"); - if(counter_mutex == NULL) { - printf("critical error\n"); - flapp_exit(NULL); - } - - // continuously read value every 1s - uint32_t counter; - while(1) { - if(read_mutex(counter_mutex, &counter, sizeof(counter), OsWaitForever)) { - printf("counter value: %d\n", counter); - } - - furi_delay_ms(1000); - } -} -``` -*/ diff --git a/furi/furi.h b/furi/furi.h index 3ce83422743..cfdeb2c0f40 100644 --- a/furi/furi.h +++ b/furi/furi.h @@ -16,7 +16,6 @@ #include "core/semaphore.h" #include "core/thread.h" #include "core/timer.h" -#include "core/valuemutex.h" #include "core/string.h" #include "core/stream_buffer.h" From eb5dae1cda00d254641b29700d4e38ef4f55d04f Mon Sep 17 00:00:00 2001 From: AloneLiberty <111039319+AloneLiberty@users.noreply.github.com> Date: Tue, 7 Mar 2023 21:33:59 +0300 Subject: [PATCH 452/824] NFC: Support reading Mifare Classic key B from sector trailer, reading sector with B key where A key can't read block, Nfc Magic app not using NFC folder by default (in file select) (#2437) * NFC: Support reading Mifare Classic key B from sector trailer and reusing it for other sectors * NFC: Fix my pointer typo * NFC: Fix reading sector with B key where A key can't read block (fixes #2413) and fix Nfc Magic app not using NFC folder by default (in file select) * NFC: Fix strange bug Co-authored-by: Aleksandr Kutuzov --- .../scenes/nfc_magic_scene_file_select.c | 4 ++ firmware/targets/f7/api_symbols.csv | 2 +- lib/nfc/nfc_worker.c | 49 +++++++++++++++++++ lib/nfc/protocols/mifare_classic.c | 13 +++-- lib/nfc/protocols/mifare_classic.h | 1 + 5 files changed, 63 insertions(+), 6 deletions(-) diff --git a/applications/plugins/nfc_magic/scenes/nfc_magic_scene_file_select.c b/applications/plugins/nfc_magic/scenes/nfc_magic_scene_file_select.c index a19237ed47b..d78422eeb66 100644 --- a/applications/plugins/nfc_magic/scenes/nfc_magic_scene_file_select.c +++ b/applications/plugins/nfc_magic/scenes/nfc_magic_scene_file_select.c @@ -11,6 +11,10 @@ void nfc_magic_scene_file_select_on_enter(void* context) { // Process file_select return nfc_device_set_loading_callback(nfc_magic->nfc_dev, nfc_magic_show_loading_popup, nfc_magic); + if(!furi_string_size(nfc_magic->nfc_dev->load_path)) { + furi_string_set_str(nfc_magic->nfc_dev->load_path, NFC_APP_FOLDER); + } + if(nfc_file_select(nfc_magic->nfc_dev)) { if(nfc_magic_scene_file_select_is_file_suitable(nfc_magic->nfc_dev)) { scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneWriteConfirm); diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 57c1a2fa464..f505b943939 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1892,7 +1892,7 @@ Function,+,menu_free,void,Menu* Function,+,menu_get_view,View*,Menu* Function,+,menu_reset,void,Menu* Function,+,menu_set_selected_item,void,"Menu*, uint32_t" -Function,-,mf_classic_auth_attempt,_Bool,"FuriHalNfcTxRxContext*, MfClassicAuthContext*, uint64_t" +Function,-,mf_classic_auth_attempt,_Bool,"FuriHalNfcTxRxContext*, Crypto1*, MfClassicAuthContext*, uint64_t" Function,-,mf_classic_auth_init_context,void,"MfClassicAuthContext*, uint8_t" Function,-,mf_classic_auth_write_block,_Bool,"FuriHalNfcTxRxContext*, MfClassicBlock*, uint8_t, MfClassicKey, uint64_t" Function,-,mf_classic_authenticate,_Bool,"FuriHalNfcTxRxContext*, uint8_t, uint64_t, MfClassicKey" diff --git a/lib/nfc/nfc_worker.c b/lib/nfc/nfc_worker.c index 54bdbb24caa..062a3953436 100644 --- a/lib/nfc/nfc_worker.c +++ b/lib/nfc/nfc_worker.c @@ -569,6 +569,32 @@ void nfc_worker_emulate_mf_ultralight(NfcWorker* nfc_worker) { } } +static bool nfc_worker_mf_get_b_key_from_sector_trailer( + FuriHalNfcTxRxContext* tx_rx, + uint16_t sector, + uint64_t key, + uint64_t* found_key) { + // Some access conditions allow reading B key via A key + + uint8_t block = mf_classic_get_sector_trailer_block_num_by_sector(sector); + + Crypto1 crypto = {}; + MfClassicBlock block_tmp = {}; + MfClassicAuthContext auth_context = {.sector = sector, .key_a = MF_CLASSIC_NO_KEY, .key_b = 0}; + + furi_hal_nfc_sleep(); + + if(mf_classic_auth_attempt(tx_rx, &crypto, &auth_context, key)) { + if(mf_classic_read_block(tx_rx, &crypto, block, &block_tmp)) { + *found_key = nfc_util_bytes2num(&block_tmp.value[10], sizeof(uint8_t) * 6); + + return *found_key; + } + } + + return false; +} + static void nfc_worker_mf_classic_key_attack( NfcWorker* nfc_worker, uint64_t key, @@ -614,6 +640,16 @@ static void nfc_worker_mf_classic_key_attack( mf_classic_set_key_found(data, i, MfClassicKeyA, key); FURI_LOG_D(TAG, "Key found"); nfc_worker->callback(NfcWorkerEventFoundKeyA, nfc_worker->context); + + uint64_t found_key; + if(nfc_worker_mf_get_b_key_from_sector_trailer(tx_rx, i, key, &found_key)) { + FURI_LOG_D(TAG, "Found B key via reading sector %d", i); + mf_classic_set_key_found(data, i, MfClassicKeyB, found_key); + + if(nfc_worker->state == NfcWorkerStateMfClassicDictAttack) { + nfc_worker->callback(NfcWorkerEventFoundKeyB, nfc_worker->context); + } + } } } if(!mf_classic_is_key_found(data, i, MfClassicKeyB)) { @@ -705,6 +741,19 @@ void nfc_worker_mf_classic_dict_attack(NfcWorker* nfc_worker) { mf_classic_set_key_found(data, i, MfClassicKeyA, key); FURI_LOG_D(TAG, "Key found"); nfc_worker->callback(NfcWorkerEventFoundKeyA, nfc_worker->context); + + uint64_t found_key; + if(nfc_worker_mf_get_b_key_from_sector_trailer( + &tx_rx, i, key, &found_key)) { + FURI_LOG_D(TAG, "Found B key via reading sector %d", i); + mf_classic_set_key_found(data, i, MfClassicKeyB, found_key); + + if(nfc_worker->state == NfcWorkerStateMfClassicDictAttack) { + nfc_worker->callback(NfcWorkerEventFoundKeyB, nfc_worker->context); + } + + nfc_worker_mf_classic_key_attack(nfc_worker, found_key, &tx_rx, i + 1); + } nfc_worker_mf_classic_key_attack(nfc_worker, key, &tx_rx, i + 1); } furi_hal_nfc_sleep(); diff --git a/lib/nfc/protocols/mifare_classic.c b/lib/nfc/protocols/mifare_classic.c index e4d5e0274bb..a8a908897d3 100644 --- a/lib/nfc/protocols/mifare_classic.c +++ b/lib/nfc/protocols/mifare_classic.c @@ -541,6 +541,7 @@ bool mf_classic_authenticate_skip_activate( bool mf_classic_auth_attempt( FuriHalNfcTxRxContext* tx_rx, + Crypto1* crypto, MfClassicAuthContext* auth_ctx, uint64_t key) { furi_assert(tx_rx); @@ -549,15 +550,14 @@ bool mf_classic_auth_attempt( bool need_halt = (auth_ctx->key_a == MF_CLASSIC_NO_KEY) && (auth_ctx->key_b == MF_CLASSIC_NO_KEY); - Crypto1 crypto; if(auth_ctx->key_a == MF_CLASSIC_NO_KEY) { // Try AUTH with key A if(mf_classic_auth( tx_rx, - mf_classic_get_first_block_num_of_sector(auth_ctx->sector), + mf_classic_get_sector_trailer_block_num_by_sector(auth_ctx->sector), key, MfClassicKeyA, - &crypto, + crypto, false, 0)) { auth_ctx->key_a = key; @@ -573,10 +573,10 @@ bool mf_classic_auth_attempt( // Try AUTH with key B if(mf_classic_auth( tx_rx, - mf_classic_get_first_block_num_of_sector(auth_ctx->sector), + mf_classic_get_sector_trailer_block_num_by_sector(auth_ctx->sector), key, MfClassicKeyB, - &crypto, + crypto, false, 0)) { auth_ctx->key_b = key; @@ -671,6 +671,9 @@ void mf_classic_read_sector(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data, u do { if(blocks_read == total_blocks) break; if(!key_b_found) break; + if(key_a_found) { + furi_hal_nfc_sleep(); + } FURI_LOG_D(TAG, "Try to read blocks with key B"); key = nfc_util_bytes2num(sec_tr->key_b, sizeof(sec_tr->key_b)); if(!mf_classic_auth(tx_rx, start_block, key, MfClassicKeyB, &crypto, false, 0)) break; diff --git a/lib/nfc/protocols/mifare_classic.h b/lib/nfc/protocols/mifare_classic.h index a88781f9c8e..c03350f2e93 100644 --- a/lib/nfc/protocols/mifare_classic.h +++ b/lib/nfc/protocols/mifare_classic.h @@ -174,6 +174,7 @@ bool mf_classic_authenticate_skip_activate( bool mf_classic_auth_attempt( FuriHalNfcTxRxContext* tx_rx, + Crypto1* crypto, MfClassicAuthContext* auth_ctx, uint64_t key); From 90958a6d23ca48c68b69347505d8ef0ed24e54ca Mon Sep 17 00:00:00 2001 From: Liam Hays Date: Wed, 8 Mar 2023 04:27:21 -0700 Subject: [PATCH 453/824] More UI fixes and improvements (#2419) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Unify spelling of confirm exit/retry across apps. * Unify infrared exit/retry confirm menus? * "Keyboard Layout", not "Keyboard layout". * Make iButton read scene prompt less awkward. * "Detect Reader" in MF Classic saved menu instead of "Detect reader" * NFC menu spelling changes only. * Remove \n in strings in widget_add_string_element() calls. Co-authored-by: あく --- .../main/bad_usb/scenes/bad_usb_scene_config.c | 2 +- .../main/ibutton/scenes/ibutton_scene_exit_confirm.c | 2 +- applications/main/ibutton/scenes/ibutton_scene_read.c | 2 +- .../main/ibutton/scenes/ibutton_scene_retry_confirm.c | 2 +- .../main/infrared/scenes/infrared_scene_ask_back.c | 6 +++--- .../main/infrared/scenes/infrared_scene_ask_retry.c | 4 ++-- .../main/lfrfid/scenes/lfrfid_scene_retry_confirm.c | 2 +- .../main/nfc/scenes/nfc_scene_mf_classic_menu.c | 2 +- applications/main/nfc/scenes/nfc_scene_retry_confirm.c | 2 +- applications/main/nfc/scenes/nfc_scene_saved_menu.c | 10 +++++----- .../main/subghz/scenes/subghz_scene_need_saving.c | 4 ++-- 11 files changed, 19 insertions(+), 19 deletions(-) diff --git a/applications/main/bad_usb/scenes/bad_usb_scene_config.c b/applications/main/bad_usb/scenes/bad_usb_scene_config.c index 2a9f2f76c91..c88cae03242 100644 --- a/applications/main/bad_usb/scenes/bad_usb_scene_config.c +++ b/applications/main/bad_usb/scenes/bad_usb_scene_config.c @@ -17,7 +17,7 @@ void bad_usb_scene_config_on_enter(void* context) { submenu_add_item( submenu, - "Keyboard layout", + "Keyboard Layout", SubmenuIndexKeyboardLayout, bad_usb_scene_config_submenu_callback, bad_usb); diff --git a/applications/main/ibutton/scenes/ibutton_scene_exit_confirm.c b/applications/main/ibutton/scenes/ibutton_scene_exit_confirm.c index 2367e12177e..9029a4b7b02 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_exit_confirm.c +++ b/applications/main/ibutton/scenes/ibutton_scene_exit_confirm.c @@ -19,7 +19,7 @@ void ibutton_scene_exit_confirm_on_enter(void* context) { widget_add_button_element( widget, GuiButtonTypeRight, "Stay", ibutton_scene_exit_confirm_widget_callback, ibutton); widget_add_string_element( - widget, 64, 19, AlignCenter, AlignBottom, FontPrimary, "Exit to iButton menu?"); + widget, 64, 19, AlignCenter, AlignBottom, FontPrimary, "Exit to iButton Menu?"); widget_add_string_element( widget, 64, 31, AlignCenter, AlignBottom, FontSecondary, "All unsaved data will be lost!"); diff --git a/applications/main/ibutton/scenes/ibutton_scene_read.c b/applications/main/ibutton/scenes/ibutton_scene_read.c index 2c43b82bbb1..a840fb7b7ec 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_read.c +++ b/applications/main/ibutton/scenes/ibutton_scene_read.c @@ -13,7 +13,7 @@ void ibutton_scene_read_on_enter(void* context) { iButtonWorker* worker = ibutton->worker; popup_set_header(popup, "iButton", 95, 26, AlignCenter, AlignBottom); - popup_set_text(popup, "Waiting\nfor key ...", 95, 30, AlignCenter, AlignTop); + popup_set_text(popup, "Apply key to\nFlipper's back", 95, 30, AlignCenter, AlignTop); popup_set_icon(popup, 0, 5, &I_DolphinWait_61x59); view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewPopup); diff --git a/applications/main/ibutton/scenes/ibutton_scene_retry_confirm.c b/applications/main/ibutton/scenes/ibutton_scene_retry_confirm.c index 7f8c95b1e88..34de5b8778b 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_retry_confirm.c +++ b/applications/main/ibutton/scenes/ibutton_scene_retry_confirm.c @@ -19,7 +19,7 @@ void ibutton_scene_retry_confirm_on_enter(void* context) { widget_add_button_element( widget, GuiButtonTypeRight, "Stay", ibutton_scene_retry_confirm_widget_callback, ibutton); widget_add_string_element( - widget, 64, 19, AlignCenter, AlignBottom, FontPrimary, "Return to reading?"); + widget, 64, 19, AlignCenter, AlignBottom, FontPrimary, "Retry Reading?"); widget_add_string_element( widget, 64, 29, AlignCenter, AlignBottom, FontSecondary, "All unsaved data will be lost!"); diff --git a/applications/main/infrared/scenes/infrared_scene_ask_back.c b/applications/main/infrared/scenes/infrared_scene_ask_back.c index 493458ade32..77fc97f9871 100644 --- a/applications/main/infrared/scenes/infrared_scene_ask_back.c +++ b/applications/main/infrared/scenes/infrared_scene_ask_back.c @@ -10,13 +10,13 @@ void infrared_scene_ask_back_on_enter(void* context) { DialogEx* dialog_ex = infrared->dialog_ex; if(infrared->app_state.is_learning_new_remote) { - dialog_ex_set_header(dialog_ex, "Exit to Infrared Menu?", 64, 0, AlignCenter, AlignTop); + dialog_ex_set_header(dialog_ex, "Exit to Infrared Menu?", 64, 11, AlignCenter, AlignTop); } else { - dialog_ex_set_header(dialog_ex, "Exit to Remote Menu?", 64, 0, AlignCenter, AlignTop); + dialog_ex_set_header(dialog_ex, "Exit to Remote Menu?", 64, 11, AlignCenter, AlignTop); } dialog_ex_set_text( - dialog_ex, "All unsaved data\nwill be lost!", 64, 31, AlignCenter, AlignCenter); + dialog_ex, "All unsaved data\nwill be lost!", 64, 25, AlignCenter, AlignTop); dialog_ex_set_icon(dialog_ex, 0, 0, NULL); dialog_ex_set_left_button_text(dialog_ex, "Exit"); dialog_ex_set_center_button_text(dialog_ex, NULL); diff --git a/applications/main/infrared/scenes/infrared_scene_ask_retry.c b/applications/main/infrared/scenes/infrared_scene_ask_retry.c index c87d9e6d3f9..602e470c7c3 100644 --- a/applications/main/infrared/scenes/infrared_scene_ask_retry.c +++ b/applications/main/infrared/scenes/infrared_scene_ask_retry.c @@ -9,9 +9,9 @@ void infrared_scene_ask_retry_on_enter(void* context) { Infrared* infrared = context; DialogEx* dialog_ex = infrared->dialog_ex; - dialog_ex_set_header(dialog_ex, "Return to Reading?", 64, 0, AlignCenter, AlignTop); + dialog_ex_set_header(dialog_ex, "Retry Reading?", 64, 11, AlignCenter, AlignTop); dialog_ex_set_text( - dialog_ex, "All unsaved data\nwill be lost!", 64, 31, AlignCenter, AlignCenter); + dialog_ex, "All unsaved data\nwill be lost!", 64, 25, AlignCenter, AlignTop); dialog_ex_set_icon(dialog_ex, 0, 0, NULL); dialog_ex_set_left_button_text(dialog_ex, "Exit"); dialog_ex_set_center_button_text(dialog_ex, NULL); diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_retry_confirm.c b/applications/main/lfrfid/scenes/lfrfid_scene_retry_confirm.c index f639f0ae1aa..ddac3e8ba55 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_retry_confirm.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_retry_confirm.c @@ -7,7 +7,7 @@ void lfrfid_scene_retry_confirm_on_enter(void* context) { widget_add_button_element(widget, GuiButtonTypeLeft, "Exit", lfrfid_widget_callback, app); widget_add_button_element(widget, GuiButtonTypeRight, "Stay", lfrfid_widget_callback, app); widget_add_string_element( - widget, 64, 19, AlignCenter, AlignBottom, FontPrimary, "Return to reading?"); + widget, 64, 19, AlignCenter, AlignBottom, FontPrimary, "Retry Reading?"); widget_add_string_element( widget, 64, 29, AlignCenter, AlignBottom, FontSecondary, "All unsaved data will be lost!"); diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_menu.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_menu.c index 5fbdabe3021..67b2a85309e 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_menu.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_menu.c @@ -25,7 +25,7 @@ void nfc_scene_mf_classic_menu_on_enter(void* context) { if(!mf_classic_is_card_read(&nfc->dev->dev_data.mf_classic_data)) { submenu_add_item( submenu, - "Detect reader", + "Detect Reader", SubmenuIndexDetectReader, nfc_scene_mf_classic_menu_submenu_callback, nfc); diff --git a/applications/main/nfc/scenes/nfc_scene_retry_confirm.c b/applications/main/nfc/scenes/nfc_scene_retry_confirm.c index 366582ea80c..5f4f7985e77 100644 --- a/applications/main/nfc/scenes/nfc_scene_retry_confirm.c +++ b/applications/main/nfc/scenes/nfc_scene_retry_confirm.c @@ -14,7 +14,7 @@ void nfc_scene_retry_confirm_on_enter(void* context) { dialog_ex_set_right_button_text(dialog_ex, "Stay"); dialog_ex_set_header(dialog_ex, "Retry Reading?", 64, 11, AlignCenter, AlignTop); dialog_ex_set_text( - dialog_ex, "All unsaved data will be\nlost!", 64, 25, AlignCenter, AlignTop); + dialog_ex, "All unsaved data\nwill be lost!", 64, 25, AlignCenter, AlignTop); dialog_ex_set_context(dialog_ex, nfc); dialog_ex_set_result_callback(dialog_ex, nfc_scene_retry_confirm_dialog_callback); diff --git a/applications/main/nfc/scenes/nfc_scene_saved_menu.c b/applications/main/nfc/scenes/nfc_scene_saved_menu.c index f42e0fe9516..ba1f9653984 100644 --- a/applications/main/nfc/scenes/nfc_scene_saved_menu.c +++ b/applications/main/nfc/scenes/nfc_scene_saved_menu.c @@ -51,20 +51,20 @@ void nfc_scene_saved_menu_on_enter(void* context) { if(!mf_classic_is_card_read(&nfc->dev->dev_data.mf_classic_data)) { submenu_add_item( submenu, - "Detect reader", + "Detect Reader", SubmenuIndexDetectReader, nfc_scene_saved_menu_submenu_callback, nfc); } submenu_add_item( submenu, - "Write To Initial Card", + "Write to Initial Card", SubmenuIndexWrite, nfc_scene_saved_menu_submenu_callback, nfc); submenu_add_item( submenu, - "Update From Initial Card", + "Update from Initial Card", SubmenuIndexUpdate, nfc_scene_saved_menu_submenu_callback, nfc); @@ -75,13 +75,13 @@ void nfc_scene_saved_menu_on_enter(void* context) { !mf_ul_is_full_capture(&nfc->dev->dev_data.mf_ul_data)) { submenu_add_item( submenu, - "Unlock With Reader", + "Unlock with Reader", SubmenuIndexMfUlUnlockByReader, nfc_scene_saved_menu_submenu_callback, nfc); submenu_add_item( submenu, - "Unlock With Password", + "Unlock with Password", SubmenuIndexMfUlUnlockByPassword, nfc_scene_saved_menu_submenu_callback, nfc); diff --git a/applications/main/subghz/scenes/subghz_scene_need_saving.c b/applications/main/subghz/scenes/subghz_scene_need_saving.c index 53bffedc844..e157246aaee 100644 --- a/applications/main/subghz/scenes/subghz_scene_need_saving.c +++ b/applications/main/subghz/scenes/subghz_scene_need_saving.c @@ -16,7 +16,7 @@ void subghz_scene_need_saving_on_enter(void* context) { SubGhz* subghz = context; widget_add_string_multiline_element( - subghz->widget, 64, 13, AlignCenter, AlignCenter, FontPrimary, "Exit to Sub-GHz menu?"); + subghz->widget, 64, 13, AlignCenter, AlignCenter, FontPrimary, "Exit to Sub-GHz Menu?"); widget_add_string_multiline_element( subghz->widget, 64, @@ -24,7 +24,7 @@ void subghz_scene_need_saving_on_enter(void* context) { AlignCenter, AlignCenter, FontSecondary, - "All unsaved will be\nlost."); + "All unsaved data\nwill be lost!"); widget_add_button_element( subghz->widget, GuiButtonTypeRight, "Stay", subghz_scene_need_saving_callback, subghz); From 5be15152ebccaafe750db78aa293515779fed640 Mon Sep 17 00:00:00 2001 From: Eric Betts Date: Wed, 8 Mar 2023 03:46:30 -0800 Subject: [PATCH 454/824] PicoPass: auth cleanup (#2470) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * remove redundant auth methods * Move picopass keys to new file * CTF key * Format sources * PicoPass: add pragma once to picopass_keys.h Co-authored-by: あく --- applications/plugins/picopass/picopass_keys.c | 8 ++ applications/plugins/picopass/picopass_keys.h | 10 +++ .../plugins/picopass/picopass_worker.c | 81 ++----------------- .../plugins/picopass/picopass_worker.h | 1 + .../picopass/scenes/picopass_scene_key_menu.c | 6 +- .../scenes/picopass_scene_read_card.c | 3 +- .../picopass_scene_read_factory_success.c | 3 +- .../picopass/assets/iclass_elite_dict.txt | 2 + 8 files changed, 31 insertions(+), 83 deletions(-) create mode 100644 applications/plugins/picopass/picopass_keys.c create mode 100644 applications/plugins/picopass/picopass_keys.h diff --git a/applications/plugins/picopass/picopass_keys.c b/applications/plugins/picopass/picopass_keys.c new file mode 100644 index 00000000000..43dfc631269 --- /dev/null +++ b/applications/plugins/picopass/picopass_keys.c @@ -0,0 +1,8 @@ +#include "picopass_keys.h" + +const uint8_t picopass_iclass_key[] = {0xaf, 0xa7, 0x85, 0xa7, 0xda, 0xb3, 0x33, 0x78}; +const uint8_t picopass_factory_credit_key[] = {0x76, 0x65, 0x54, 0x43, 0x32, 0x21, 0x10, 0x00}; +const uint8_t picopass_factory_debit_key[] = {0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87}; +const uint8_t picopass_xice_key[] = {0x20, 0x20, 0x66, 0x66, 0x66, 0x66, 0x88, 0x88}; +const uint8_t picopass_xicl_key[] = {0x20, 0x20, 0x66, 0x66, 0x66, 0x66, 0x88, 0x88}; +const uint8_t picopass_xics_key[] = {0x66, 0x66, 0x20, 0x20, 0x66, 0x66, 0x88, 0x88}; diff --git a/applications/plugins/picopass/picopass_keys.h b/applications/plugins/picopass/picopass_keys.h new file mode 100644 index 00000000000..2b5dba66106 --- /dev/null +++ b/applications/plugins/picopass/picopass_keys.h @@ -0,0 +1,10 @@ +#pragma once + +#include "picopass_device.h" + +extern const uint8_t picopass_iclass_key[PICOPASS_BLOCK_LEN]; +extern const uint8_t picopass_factory_credit_key[PICOPASS_BLOCK_LEN]; +extern const uint8_t picopass_factory_debit_key[PICOPASS_BLOCK_LEN]; +extern const uint8_t picopass_xice_key[PICOPASS_BLOCK_LEN]; +extern const uint8_t picopass_xicl_key[PICOPASS_BLOCK_LEN]; +extern const uint8_t picopass_xics_key[PICOPASS_BLOCK_LEN]; diff --git a/applications/plugins/picopass/picopass_worker.c b/applications/plugins/picopass/picopass_worker.c index 024c51120af..f2e9e82b817 100644 --- a/applications/plugins/picopass/picopass_worker.c +++ b/applications/plugins/picopass/picopass_worker.c @@ -4,13 +4,6 @@ #define TAG "PicopassWorker" -const uint8_t picopass_iclass_key[] = {0xaf, 0xa7, 0x85, 0xa7, 0xda, 0xb3, 0x33, 0x78}; -const uint8_t picopass_factory_credit_key[] = {0x76, 0x65, 0x54, 0x43, 0x32, 0x21, 0x10, 0x00}; -const uint8_t picopass_factory_debit_key[] = {0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87}; -const uint8_t picopass_xice_key[] = {0x20, 0x20, 0x66, 0x66, 0x66, 0x66, 0x88, 0x88}; -const uint8_t picopass_xicl_key[] = {0x20, 0x20, 0x66, 0x66, 0x66, 0x66, 0x88, 0x88}; -const uint8_t picopass_xics_key[] = {0x66, 0x66, 0x20, 0x20, 0x66, 0x66, 0x88, 0x88}; - static void picopass_worker_enable_field() { furi_hal_nfc_ll_txrx_on(); furi_hal_nfc_exit_sleep(); @@ -179,50 +172,6 @@ ReturnCode picopass_read_preauth(PicopassBlock* AA1) { return ERR_NONE; } -static ReturnCode picopass_auth_standard(uint8_t* csn, uint8_t* div_key) { - rfalPicoPassReadCheckRes rcRes; - rfalPicoPassCheckRes chkRes; - - ReturnCode err; - - uint8_t mac[4] = {0}; - uint8_t ccnr[12] = {0}; - - err = rfalPicoPassPollerReadCheck(&rcRes); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "rfalPicoPassPollerReadCheck error %d", err); - return err; - } - memcpy(ccnr, rcRes.CCNR, sizeof(rcRes.CCNR)); // last 4 bytes left 0 - - loclass_iclass_calc_div_key(csn, (uint8_t*)picopass_iclass_key, div_key, false); - loclass_opt_doReaderMAC(ccnr, div_key, mac); - - return rfalPicoPassPollerCheck(mac, &chkRes); -} - -static ReturnCode picopass_auth_factory(uint8_t* csn, uint8_t* div_key) { - rfalPicoPassReadCheckRes rcRes; - rfalPicoPassCheckRes chkRes; - - ReturnCode err; - - uint8_t mac[4] = {0}; - uint8_t ccnr[12] = {0}; - - err = rfalPicoPassPollerReadCheck(&rcRes); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "rfalPicoPassPollerReadCheck error %d", err); - return err; - } - memcpy(ccnr, rcRes.CCNR, sizeof(rcRes.CCNR)); // last 4 bytes left 0 - - loclass_iclass_calc_div_key(csn, (uint8_t*)picopass_factory_debit_key, div_key, false); - loclass_opt_doReaderMAC(ccnr, div_key, mac); - - return rfalPicoPassPollerCheck(mac, &chkRes); -} - static ReturnCode picopass_auth_dict( uint8_t* csn, PicopassPacs* pacs, @@ -291,51 +240,35 @@ static ReturnCode picopass_auth_dict( ReturnCode picopass_auth(PicopassBlock* AA1, PicopassPacs* pacs) { ReturnCode err; - FURI_LOG_I(TAG, "Trying standard legacy key"); - err = picopass_auth_standard( - AA1[PICOPASS_CSN_BLOCK_INDEX].data, AA1[PICOPASS_KD_BLOCK_INDEX].data); - if(err == ERR_NONE) { - memcpy(pacs->key, picopass_iclass_key, PICOPASS_BLOCK_LEN); - return ERR_NONE; - } - - FURI_LOG_I(TAG, "Trying factory default key"); - err = picopass_auth_factory( - AA1[PICOPASS_CSN_BLOCK_INDEX].data, AA1[PICOPASS_KD_BLOCK_INDEX].data); - if(err == ERR_NONE) { - memcpy(pacs->key, picopass_factory_debit_key, PICOPASS_BLOCK_LEN); - return ERR_NONE; - } - - FURI_LOG_I(TAG, "Starting user dictionary attack [Elite KDF]"); + FURI_LOG_I(TAG, "Starting system dictionary attack [Standard KDF]"); err = picopass_auth_dict( AA1[PICOPASS_CSN_BLOCK_INDEX].data, pacs, AA1[PICOPASS_KD_BLOCK_INDEX].data, - IclassEliteDictTypeUser, - true); + IclassEliteDictTypeFlipper, + false); if(err == ERR_NONE) { return ERR_NONE; } - FURI_LOG_I(TAG, "Starting system dictionary attack [Elite KDF]"); + FURI_LOG_I(TAG, "Starting user dictionary attack [Elite KDF]"); err = picopass_auth_dict( AA1[PICOPASS_CSN_BLOCK_INDEX].data, pacs, AA1[PICOPASS_KD_BLOCK_INDEX].data, - IclassEliteDictTypeFlipper, + IclassEliteDictTypeUser, true); if(err == ERR_NONE) { return ERR_NONE; } - FURI_LOG_I(TAG, "Starting system dictionary attack [Standard KDF]"); + FURI_LOG_I(TAG, "Starting system dictionary attack [Elite KDF]"); err = picopass_auth_dict( AA1[PICOPASS_CSN_BLOCK_INDEX].data, pacs, AA1[PICOPASS_KD_BLOCK_INDEX].data, IclassEliteDictTypeFlipper, - false); + true); if(err == ERR_NONE) { return ERR_NONE; } diff --git a/applications/plugins/picopass/picopass_worker.h b/applications/plugins/picopass/picopass_worker.h index 02b088b2457..f5e9f3039b4 100644 --- a/applications/plugins/picopass/picopass_worker.h +++ b/applications/plugins/picopass/picopass_worker.h @@ -1,6 +1,7 @@ #pragma once #include "picopass_device.h" +#include "picopass_keys.h" typedef struct PicopassWorker PicopassWorker; diff --git a/applications/plugins/picopass/scenes/picopass_scene_key_menu.c b/applications/plugins/picopass/scenes/picopass_scene_key_menu.c index b1db37f81a2..8aac6cb2491 100644 --- a/applications/plugins/picopass/scenes/picopass_scene_key_menu.c +++ b/applications/plugins/picopass/scenes/picopass_scene_key_menu.c @@ -1,4 +1,5 @@ #include "../picopass_i.h" +#include "../picopass_keys.h" enum SubmenuIndex { SubmenuIndexWriteStandard, @@ -8,11 +9,6 @@ enum SubmenuIndex { SubmenuIndexWriteCustom, //TODO: user input of key }; -extern const uint8_t picopass_xice_key[]; -extern const uint8_t picopass_xicl_key[]; -extern const uint8_t picopass_xics_key[]; -extern const uint8_t picopass_iclass_key[]; - void picopass_scene_key_menu_submenu_callback(void* context, uint32_t index) { Picopass* picopass = context; diff --git a/applications/plugins/picopass/scenes/picopass_scene_read_card.c b/applications/plugins/picopass/scenes/picopass_scene_read_card.c index c62cba8ea97..96ec7c668b9 100644 --- a/applications/plugins/picopass/scenes/picopass_scene_read_card.c +++ b/applications/plugins/picopass/scenes/picopass_scene_read_card.c @@ -1,7 +1,6 @@ #include "../picopass_i.h" #include - -extern const uint8_t picopass_factory_debit_key[]; +#include "../picopass_keys.h" void picopass_read_card_worker_callback(PicopassWorkerEvent event, void* context) { UNUSED(event); diff --git a/applications/plugins/picopass/scenes/picopass_scene_read_factory_success.c b/applications/plugins/picopass/scenes/picopass_scene_read_factory_success.c index b98951dcfb8..bc07bb95302 100644 --- a/applications/plugins/picopass/scenes/picopass_scene_read_factory_success.c +++ b/applications/plugins/picopass/scenes/picopass_scene_read_factory_success.c @@ -1,7 +1,6 @@ #include "../picopass_i.h" #include - -extern const uint8_t picopass_iclass_key[]; +#include "../picopass_keys.h" void picopass_scene_read_factory_success_widget_callback( GuiButtonType result, diff --git a/assets/resources/apps_data/picopass/assets/iclass_elite_dict.txt b/assets/resources/apps_data/picopass/assets/iclass_elite_dict.txt index 46808ef602e..5da2a2fa80d 100644 --- a/assets/resources/apps_data/picopass/assets/iclass_elite_dict.txt +++ b/assets/resources/apps_data/picopass/assets/iclass_elite_dict.txt @@ -45,3 +45,5 @@ C1B74D7478053AE2 # default iCLASS RFIDeas 6B65797374726B72 + +5C100DF7042EAE64 From 50ef5deefc2f6b4dc1a5c89432e7a03094e9ba7d Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Thu, 9 Mar 2023 10:24:47 +0200 Subject: [PATCH 455/824] [FL-3118] Dumb mode menu freeze fix (#2456) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- applications/services/desktop/desktop.c | 2 ++ applications/services/desktop/desktop_i.h | 2 ++ applications/services/desktop/scenes/desktop_scene_main.c | 1 + 3 files changed, 5 insertions(+) diff --git a/applications/services/desktop/desktop.c b/applications/services/desktop/desktop.c index f8716e6cb91..556f4233358 100644 --- a/applications/services/desktop/desktop.c +++ b/applications/services/desktop/desktop.c @@ -144,11 +144,13 @@ void desktop_unlock(Desktop* desktop) { } void desktop_set_dummy_mode_state(Desktop* desktop, bool enabled) { + desktop->in_transition = true; view_port_enabled_set(desktop->dummy_mode_icon_viewport, enabled); desktop_main_set_dummy_mode_state(desktop->main_view, enabled); animation_manager_set_dummy_mode_state(desktop->animation_manager, enabled); desktop->settings.dummy_mode = enabled; DESKTOP_SETTINGS_SAVE(&desktop->settings); + desktop->in_transition = false; } Desktop* desktop_alloc() { diff --git a/applications/services/desktop/desktop_i.h b/applications/services/desktop/desktop_i.h index 8034bbc3ac0..2f3ec9b517d 100644 --- a/applications/services/desktop/desktop_i.h +++ b/applications/services/desktop/desktop_i.h @@ -69,6 +69,8 @@ struct Desktop { FuriPubSub* input_events_pubsub; FuriPubSubSubscription* input_events_subscription; FuriTimer* auto_lock_timer; + + bool in_transition; }; Desktop* desktop_alloc(); diff --git a/applications/services/desktop/scenes/desktop_scene_main.c b/applications/services/desktop/scenes/desktop_scene_main.c index b02958b66ab..4d1fa495052 100644 --- a/applications/services/desktop/scenes/desktop_scene_main.c +++ b/applications/services/desktop/scenes/desktop_scene_main.c @@ -79,6 +79,7 @@ static void desktop_scene_main_open_app_or_profile(Desktop* desktop, const char* void desktop_scene_main_callback(DesktopEvent event, void* context) { Desktop* desktop = (Desktop*)context; + if(desktop->in_transition) return; view_dispatcher_send_custom_event(desktop->view_dispatcher, event); } From 4fd043398a17b3965b7045c711fc5e36dbce984a Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Thu, 9 Mar 2023 18:01:53 +0300 Subject: [PATCH 456/824] Embed assets in elf file (#2466) * FBT: file_assets generator * Elf file: process manifest section externally * FBT, file_assets generator: add assets signature * Storage: assets path alias * Flipper application: assets unpacker * Apps, Storage: use '/data' alias for apps data * Storage: copy file to file * Assets: log flag, fixes * Update f18 api * Assets: asserts * Assets: fix signature_data check * App assets: example * Example assets: fix folder structure in readme * Assets: fix error handling * Assets builder: use ansii instead of utf-8, use .fapassets section instead of .fapfiles, add assets path to signature * Elf file: comment strange places * Storage: totaly optimized storage_file_copy_to_file --- .../debug/unit_tests/storage/storage_test.c | 6 +- .../examples/example_apps_assets/README.md | 58 +++ .../example_apps_assets/application.fam | 10 + .../example_apps_assets/example_apps_assets.c | 48 +++ .../files/poems/a jelly-fish.txt | 24 ++ .../files/poems/my shadow.txt | 23 ++ .../files/poems/theme in yellow.txt | 19 + .../example_apps_assets/files/test_asset.txt | 1 + .../examples/example_apps_data/README.md | 12 +- applications/services/storage/storage.h | 15 +- .../services/storage/storage_external_api.c | 21 + applications/services/storage/storage_i.h | 1 + .../services/storage/storage_processing.c | 14 +- firmware/targets/f18/api_symbols.csv | 3 +- firmware/targets/f7/api_symbols.csv | 3 +- lib/flipper_application/application_assets.c | 361 ++++++++++++++++++ lib/flipper_application/application_assets.h | 17 + lib/flipper_application/elf/elf_file.c | 116 +++--- lib/flipper_application/elf/elf_file.h | 34 +- lib/flipper_application/flipper_application.c | 78 +++- scripts/fbt/appmanifest.py | 1 + scripts/fbt_tools/fbt_extapps.py | 185 ++++++++- 22 files changed, 937 insertions(+), 113 deletions(-) create mode 100644 applications/examples/example_apps_assets/README.md create mode 100644 applications/examples/example_apps_assets/application.fam create mode 100644 applications/examples/example_apps_assets/example_apps_assets.c create mode 100644 applications/examples/example_apps_assets/files/poems/a jelly-fish.txt create mode 100644 applications/examples/example_apps_assets/files/poems/my shadow.txt create mode 100644 applications/examples/example_apps_assets/files/poems/theme in yellow.txt create mode 100644 applications/examples/example_apps_assets/files/test_asset.txt create mode 100644 lib/flipper_application/application_assets.c create mode 100644 lib/flipper_application/application_assets.h diff --git a/applications/debug/unit_tests/storage/storage_test.c b/applications/debug/unit_tests/storage/storage_test.c index 582be7902bb..f0b45c598c8 100644 --- a/applications/debug/unit_tests/storage/storage_test.c +++ b/applications/debug/unit_tests/storage/storage_test.c @@ -362,8 +362,8 @@ static size_t storage_test_apps_count = COUNT_OF(storage_test_apps); static int32_t storage_test_app(void* arg) { UNUSED(arg); Storage* storage = furi_record_open(RECORD_STORAGE); - storage_common_remove(storage, "/app/test"); - int32_t ret = storage_file_create(storage, "/app/test", "test"); + storage_common_remove(storage, "/data/test"); + int32_t ret = storage_file_create(storage, "/data/test", "test"); furi_record_close(RECORD_STORAGE); return ret; } @@ -401,7 +401,7 @@ MU_TEST(test_storage_data_path) { Storage* storage = furi_record_open(RECORD_STORAGE); File* file = storage_file_alloc(storage); - mu_check(storage_dir_open(file, "/app")); + mu_check(storage_dir_open(file, "/data")); mu_check(storage_dir_close(file)); storage_file_free(file); diff --git a/applications/examples/example_apps_assets/README.md b/applications/examples/example_apps_assets/README.md new file mode 100644 index 00000000000..a24183e88f2 --- /dev/null +++ b/applications/examples/example_apps_assets/README.md @@ -0,0 +1,58 @@ +# Apps Assets folder Example + +This example shows how to use the Apps Assets folder to store data that is not part of the application itself, but is required for its operation, and that data is provided with the application. + +## What is the Apps Assets Folder? + +The **Apps Assets** folder is a folder where external applications unpack their assets. + +The path to the current application folder is related to the `appid` of the app. The `appid` is used to identify the app in the app store and is stored in the `application.fam` file. +The Apps Assets folder is located only on the external storage, the SD card. + +For example, if the `appid` of the app is `snake_game`, the path to the Apps Assets folder will be `/ext/apps_assets/snake_game`. But using raw paths is not recommended, because the path to the Apps Assets folder can change in the future. Use the `/assets` alias instead. + +## How to get the path to the Apps Assets folder? + +You can use `/assets` alias to get the path to the current application data folder. For example, if you want to open a file `database.txt` in the Apps Assets folder, you can use the next path: `/data/database.txt`. But this way is not recommended, because even the `/assets` alias can change in the future. + +We recommend to use the `APP_ASSETS_PATH` macro to get the path to the Apps Assets folder. For example, if you want to open a file `database.txt` in the Apps Assets folder, you can use the next path: `APP_ASSETS_PATH("database.txt")`. + +## What is the difference between the Apps Assets folder and the Apps Data folder? + +The Apps Assets folder is used to store the data provided with the application. For example, if you want to create a game, you can store game levels (contant data) in the Apps Assets folder. + +The Apps Data folder is used to store data generated by the application. For example, if you want to create a game, you can save the progress of the game (user-generated data) in the Apps Data folder. + +## How to provide the data with the app? + +To provide data with an application, you need to create a folder inside your application folder (eg "files") and place the data in it. After that, you need to add `fap_file_assets="files"` to your application.fam file. + +For example, if you want to provide game levels with the application, you need to create a "levels" folder inside the "files" folder and put the game levels in it. After that, you need to add `fap_file_assets="files"` to your application.fam file. The final application folder structure will look like this: + +``` +snake_game +├── application.fam +├── snake_game.c +└── files + └── levels + ├── level1.txt + ├── level2.txt + └── level3.txt +``` + +When app is launched, the `files` folder will be unpacked to the Apps Assets folder. The final structure of the Apps Assets folder will look like this: + +``` +/assets +├── .assets.signature +└── levels + ├── level1.txt + ├── level2.txt + └── level3.txt +``` + +## When will the data be unpacked? + +The data is unpacked when the application starts, if the application is launched for the first time, or if the data within the application is updated. + +When an application is compiled, the contents of the "files" folder are hashed and stored within the application itself. When the application starts, this hash is compared to the hash stored in the `.assets.signature` file. If the hashes differ or the `.assets.signature` file does not exist, the application folder is deleted and the new data is unpacked. \ No newline at end of file diff --git a/applications/examples/example_apps_assets/application.fam b/applications/examples/example_apps_assets/application.fam new file mode 100644 index 00000000000..4f324277dba --- /dev/null +++ b/applications/examples/example_apps_assets/application.fam @@ -0,0 +1,10 @@ +App( + appid="example_apps_assets", + name="Example: Apps Assets", + apptype=FlipperAppType.EXTERNAL, + entry_point="example_apps_assets_main", + requires=["gui"], + stack_size=4 * 1024, + fap_category="Examples", + fap_file_assets="files", +) diff --git a/applications/examples/example_apps_assets/example_apps_assets.c b/applications/examples/example_apps_assets/example_apps_assets.c new file mode 100644 index 00000000000..f2d0272f0b7 --- /dev/null +++ b/applications/examples/example_apps_assets/example_apps_assets.c @@ -0,0 +1,48 @@ +#include +#include +#include +#include + +// Define log tag +#define TAG "example_apps_assets" + +static void example_apps_data_print_file_content(Storage* storage, const char* path) { + Stream* stream = file_stream_alloc(storage); + FuriString* line = furi_string_alloc(); + + FURI_LOG_I(TAG, "----------------------------------------"); + FURI_LOG_I(TAG, "File \"%s\" content:", path); + if(file_stream_open(stream, path, FSAM_READ, FSOM_OPEN_EXISTING)) { + while(stream_read_line(stream, line)) { + furi_string_replace_all(line, "\r", ""); + furi_string_replace_all(line, "\n", ""); + FURI_LOG_I(TAG, "%s", furi_string_get_cstr(line)); + } + } else { + FURI_LOG_E(TAG, "Failed to open file"); + } + FURI_LOG_I(TAG, "----------------------------------------"); + + furi_string_free(line); + file_stream_close(stream); + stream_free(stream); +} + +// Application entry point +int32_t example_apps_assets_main(void* p) { + // Mark argument as unused + UNUSED(p); + + // Open storage + Storage* storage = furi_record_open(RECORD_STORAGE); + + example_apps_data_print_file_content(storage, APP_ASSETS_PATH("test_asset.txt")); + example_apps_data_print_file_content(storage, APP_ASSETS_PATH("poems/a jelly-fish.txt")); + example_apps_data_print_file_content(storage, APP_ASSETS_PATH("poems/theme in yellow.txt")); + example_apps_data_print_file_content(storage, APP_ASSETS_PATH("poems/my shadow.txt")); + + // Close storage + furi_record_close(RECORD_STORAGE); + + return 0; +} diff --git a/applications/examples/example_apps_assets/files/poems/a jelly-fish.txt b/applications/examples/example_apps_assets/files/poems/a jelly-fish.txt new file mode 100644 index 00000000000..46a5a4dff23 --- /dev/null +++ b/applications/examples/example_apps_assets/files/poems/a jelly-fish.txt @@ -0,0 +1,24 @@ +A Jelly-Fish by Marianne Moore + +Visible, invisible, +A fluctuating charm, +An amber-colored amethyst +Inhabits it; your arm +Approaches, and +It opens and +It closes; +You have meant +To catch it, +And it shrivels; +You abandon +Your intent— +It opens, and it +Closes and you +Reach for it— +The blue +Surrounding it +Grows cloudy, and +It floats away +From you. + +source: "https://poets.org/anthology/poems-your-poetry-project-public-domain" \ No newline at end of file diff --git a/applications/examples/example_apps_assets/files/poems/my shadow.txt b/applications/examples/example_apps_assets/files/poems/my shadow.txt new file mode 100644 index 00000000000..e113e7df514 --- /dev/null +++ b/applications/examples/example_apps_assets/files/poems/my shadow.txt @@ -0,0 +1,23 @@ +My Shadow by Robert Louis Stevenson + +I have a little shadow that goes in and out with me, +And what can be the use of him is more than I can see. +He is very, very like me from the heels up to the head; +And I see him jump before me, when I jump into my bed. + +The funniest thing about him is the way he likes to grow— +Not at all like proper children, which is always very slow; +For he sometimes shoots up taller like an India-rubber ball, +And he sometimes gets so little that there’s none of him at all. + +He hasn’t got a notion of how children ought to play, +And can only make a fool of me in every sort of way. +He stays so close beside me, he’s a coward you can see; +I’d think shame to stick to nursie as that shadow sticks to me! + +One morning, very early, before the sun was up, +I rose and found the shining dew on every buttercup; +But my lazy little shadow, like an arrant sleepy-head, +Had stayed at home behind me and was fast asleep in bed. + +source: "https://poets.org/anthology/poems-your-poetry-project-public-domain" \ No newline at end of file diff --git a/applications/examples/example_apps_assets/files/poems/theme in yellow.txt b/applications/examples/example_apps_assets/files/poems/theme in yellow.txt new file mode 100644 index 00000000000..f392287bd86 --- /dev/null +++ b/applications/examples/example_apps_assets/files/poems/theme in yellow.txt @@ -0,0 +1,19 @@ +Theme in Yellow by Carl Sandburg + +I spot the hills +With yellow balls in autumn. +I light the prairie cornfields +Orange and tawny gold clusters +And I am called pumpkins. +On the last of October +When dusk is fallen +Children join hands +And circle round me +Singing ghost songs +And love to the harvest moon; +I am a jack-o'-lantern +With terrible teeth +And the children know +I am fooling. + +source: "https://poets.org/anthology/poems-your-poetry-project-public-domain" \ No newline at end of file diff --git a/applications/examples/example_apps_assets/files/test_asset.txt b/applications/examples/example_apps_assets/files/test_asset.txt new file mode 100644 index 00000000000..1adcb55ee5e --- /dev/null +++ b/applications/examples/example_apps_assets/files/test_asset.txt @@ -0,0 +1 @@ +## This is test file content \ No newline at end of file diff --git a/applications/examples/example_apps_data/README.md b/applications/examples/example_apps_data/README.md index fd866607754..c70ac055a41 100644 --- a/applications/examples/example_apps_data/README.md +++ b/applications/examples/example_apps_data/README.md @@ -9,10 +9,16 @@ The **Apps Data** folder is a folder used to store data for external apps that a The path to the current application folder is related to the `appid` of the app. The `appid` is used to identify the app in the app store and is stored in the `application.fam` file. The Apps Data folder is located only on the external storage, the SD card. -For example, if the `appid` of the app is `snake_game`, the path to the Apps Data folder will be `/ext/apps_data/snake_game`. But using raw paths is not recommended, because the path to the Apps Data folder can change in the future. Use the `/app` alias instead. +For example, if the `appid` of the app is `snake_game`, the path to the Apps Data folder will be `/ext/apps_data/snake_game`. But using raw paths is not recommended, because the path to the Apps Data folder can change in the future. Use the `/data` alias instead. ## How to get the path to the Apps Data folder? -You can use `/app` alias to get the path to the current application data folder. For example, if you want to open a file `config.txt` in the Apps Data folder, you can use the next path: `/app/config.txt`. But this way is not recommended, because even the `/app` alias can change in the future. +You can use `/data` alias to get the path to the current application data folder. For example, if you want to open a file `config.txt` in the Apps Data folder, you can use the next path: `/data/config.txt`. But this way is not recommended, because even the `/data` alias can change in the future. -We recommend to use the `APP_DATA_PATH` macro to get the path to the Apps Data folder. For example, if you want to open a file `config.txt` in the Apps Data folder, you can use the next path: `APP_DATA_PATH("config.txt")`. \ No newline at end of file +We recommend to use the `APP_DATA_PATH` macro to get the path to the Apps Data folder. For example, if you want to open a file `config.txt` in the Apps Data folder, you can use the next path: `APP_DATA_PATH("config.txt")`. + +## What is the difference between the Apps Assets folder and the Apps Data folder? + +The Apps Assets folder is used to store the data provided with the application. For example, if you want to create a game, you can store game levels (contant data) in the Apps Assets folder. + +The Apps Data folder is used to store data generated by the application. For example, if you want to create a game, you can save the progress of the game (user-generated data) in the Apps Data folder. \ No newline at end of file diff --git a/applications/services/storage/storage.h b/applications/services/storage/storage.h index e35b8164c2c..a1267575fb8 100644 --- a/applications/services/storage/storage.h +++ b/applications/services/storage/storage.h @@ -10,12 +10,14 @@ extern "C" { #define STORAGE_INT_PATH_PREFIX "/int" #define STORAGE_EXT_PATH_PREFIX "/ext" #define STORAGE_ANY_PATH_PREFIX "/any" -#define STORAGE_APP_DATA_PATH_PREFIX "/app" +#define STORAGE_APP_DATA_PATH_PREFIX "/data" +#define STORAGE_APP_ASSETS_PATH_PREFIX "/assets" #define INT_PATH(path) STORAGE_INT_PATH_PREFIX "/" path #define EXT_PATH(path) STORAGE_EXT_PATH_PREFIX "/" path #define ANY_PATH(path) STORAGE_ANY_PATH_PREFIX "/" path #define APP_DATA_PATH(path) STORAGE_APP_DATA_PATH_PREFIX "/" path +#define APP_ASSETS_PATH(path) STORAGE_APP_ASSETS_PATH_PREFIX "/" path #define RECORD_STORAGE "storage" @@ -146,6 +148,17 @@ bool storage_file_eof(File* file); */ bool storage_file_exists(Storage* storage, const char* path); +/** + * @brief Copy data from one opened file to another opened file + * Size bytes will be copied from current position of source file to current position of destination file + * + * @param source source file + * @param destination destination file + * @param size size of data to copy + * @return bool success flag + */ +bool storage_file_copy_to_file(File* source, File* destination, uint32_t size); + /******************* Dir Functions *******************/ /** Opens a directory to get objects from it diff --git a/applications/services/storage/storage_external_api.c b/applications/services/storage/storage_external_api.c index 8d8220f816b..ffc3da4bc48 100644 --- a/applications/services/storage/storage_external_api.c +++ b/applications/services/storage/storage_external_api.c @@ -9,6 +9,7 @@ #define MAX_NAME_LENGTH 256 #define MAX_EXT_LEN 16 +#define FILE_BUFFER_SIZE 512 #define TAG "StorageAPI" @@ -251,6 +252,26 @@ bool storage_file_exists(Storage* storage, const char* path) { return exist; } +bool storage_file_copy_to_file(File* source, File* destination, uint32_t size) { + uint8_t* buffer = malloc(FILE_BUFFER_SIZE); + + while(size) { + uint32_t read_size = size > FILE_BUFFER_SIZE ? FILE_BUFFER_SIZE : size; + if(storage_file_read(source, buffer, read_size) != read_size) { + break; + } + + if(storage_file_write(destination, buffer, read_size) != read_size) { + break; + } + + size -= read_size; + } + + free(buffer); + return size == 0; +} + /****************** DIR ******************/ static bool storage_dir_open_internal(File* file, const char* path) { diff --git a/applications/services/storage/storage_i.h b/applications/services/storage/storage_i.h index 85df5d92657..cb7f16e4709 100644 --- a/applications/services/storage/storage_i.h +++ b/applications/services/storage/storage_i.h @@ -13,6 +13,7 @@ extern "C" { #define STORAGE_COUNT (ST_INT + 1) #define APPS_DATA_PATH EXT_PATH("apps_data") +#define APPS_ASSETS_PATH EXT_PATH("apps_assets") typedef struct { ViewPort* view_port; diff --git a/applications/services/storage/storage_processing.c b/applications/services/storage/storage_processing.c index cab1edff58b..2a335e36655 100644 --- a/applications/services/storage/storage_processing.c +++ b/applications/services/storage/storage_processing.c @@ -454,7 +454,7 @@ void storage_process_alias( FuriString* apps_data_path_with_appsid = furi_string_alloc_set(APPS_DATA_PATH "/"); furi_string_cat(apps_data_path_with_appsid, furi_thread_get_appid(thread_id)); - // "/app" -> "/ext/apps_data/appsid" + // "/data" -> "/ext/apps_data/appsid" furi_string_replace_at( path, 0, @@ -472,6 +472,18 @@ void storage_process_alias( } furi_string_free(apps_data_path_with_appsid); + } else if(furi_string_start_with(path, STORAGE_APP_ASSETS_PATH_PREFIX)) { + FuriString* apps_assets_path_with_appsid = furi_string_alloc_set(APPS_ASSETS_PATH "/"); + furi_string_cat(apps_assets_path_with_appsid, furi_thread_get_appid(thread_id)); + + // "/assets" -> "/ext/apps_assets/appsid" + furi_string_replace_at( + path, + 0, + strlen(STORAGE_APP_ASSETS_PATH_PREFIX), + furi_string_get_cstr(apps_assets_path_with_appsid)); + + furi_string_free(apps_assets_path_with_appsid); } } diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index 8060d38a20b..07c323a1ba0 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,18.0,, +Version,+,18.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -1636,6 +1636,7 @@ Function,-,storage_dir_rewind,_Bool,File* Function,+,storage_error_get_desc,const char*,FS_Error Function,+,storage_file_alloc,File*,Storage* Function,+,storage_file_close,_Bool,File* +Function,+,storage_file_copy_to_file,_Bool,"File*, File*, uint32_t" Function,+,storage_file_eof,_Bool,File* Function,+,storage_file_exists,_Bool,"Storage*, const char*" Function,+,storage_file_free,void,File* diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index f505b943939..fcacaeee939 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,18.0,, +Version,+,18.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -2461,6 +2461,7 @@ Function,-,storage_dir_rewind,_Bool,File* Function,+,storage_error_get_desc,const char*,FS_Error Function,+,storage_file_alloc,File*,Storage* Function,+,storage_file_close,_Bool,File* +Function,+,storage_file_copy_to_file,_Bool,"File*, File*, uint32_t" Function,+,storage_file_eof,_Bool,File* Function,+,storage_file_exists,_Bool,"Storage*, const char*" Function,+,storage_file_free,void,File* diff --git a/lib/flipper_application/application_assets.c b/lib/flipper_application/application_assets.c new file mode 100644 index 00000000000..1262870d5e9 --- /dev/null +++ b/lib/flipper_application/application_assets.c @@ -0,0 +1,361 @@ +#include "application_assets.h" +#include +#include + +// #define ELF_ASSETS_DEBUG_LOG 1 + +#ifndef ELF_ASSETS_DEBUG_LOG +#undef FURI_LOG_D +#define FURI_LOG_D(...) +#undef FURI_LOG_E +#define FURI_LOG_E(...) +#endif + +#define FLIPPER_APPLICATION_ASSETS_MAGIC 0x4F4C5A44 +#define FLIPPER_APPLICATION_ASSETS_VERSION 1 +#define FLIPPER_APPLICATION_ASSETS_SIGNATURE_FILENAME ".assets.signature" + +#define BUFFER_SIZE 512 + +#define TAG "fap_assets" + +#pragma pack(push, 1) + +typedef struct { + uint32_t magic; + uint32_t version; + uint32_t dirs_count; + uint32_t files_count; +} FlipperApplicationAssetsHeader; + +#pragma pack(pop) + +typedef enum { + AssetsSignatureResultEqual, + AssetsSignatureResultNotEqual, + AssetsSignatureResultError, +} AssetsSignatureResult; + +static FuriString* flipper_application_assets_alloc_app_full_path(FuriString* app_name) { + furi_assert(app_name); + FuriString* full_path = furi_string_alloc_set(APPS_ASSETS_PATH "/"); + furi_string_cat(full_path, app_name); + return full_path; +} + +static FuriString* flipper_application_assets_alloc_signature_file_path(FuriString* app_name) { + furi_assert(app_name); + FuriString* signature_file_path = flipper_application_assets_alloc_app_full_path(app_name); + furi_string_cat(signature_file_path, "/" FLIPPER_APPLICATION_ASSETS_SIGNATURE_FILENAME); + + return signature_file_path; +} + +static uint8_t* flipper_application_assets_alloc_and_load_data(File* file, size_t* size) { + furi_assert(file); + + uint8_t* data = NULL; + uint32_t length = 0; + + // read data length + if(storage_file_read(file, &length, sizeof(length)) != sizeof(length)) { + return NULL; + } + + data = malloc(length); + + // read data + if(storage_file_read(file, (void*)data, length) != length) { + free((void*)data); + return NULL; + } + + if(size != NULL) { + *size = length; + } + + return data; +} + +static bool flipper_application_assets_process_files( + Storage* storage, + File* file, + FuriString* app_name, + uint32_t files_count) { + furi_assert(storage); + furi_assert(file); + furi_assert(app_name); + + UNUSED(storage); + + bool success = false; + uint32_t length = 0; + char* path = NULL; + FuriString* file_path = furi_string_alloc(); + File* destination = storage_file_alloc(storage); + + FuriString* full_path = flipper_application_assets_alloc_app_full_path(app_name); + + for(uint32_t i = 0; i < files_count; i++) { + path = (char*)flipper_application_assets_alloc_and_load_data(file, NULL); + + if(path == NULL) { + break; + } + + // read file size + if(storage_file_read(file, &length, sizeof(length)) != sizeof(length)) { + break; + } + + furi_string_set(file_path, full_path); + furi_string_cat(file_path, "/"); + furi_string_cat(file_path, path); + + if(!storage_file_open( + destination, furi_string_get_cstr(file_path), FSAM_WRITE, FSOM_CREATE_ALWAYS)) { + FURI_LOG_E(TAG, "Can't create file: %s", furi_string_get_cstr(file_path)); + break; + } + + // copy data to file + if(!storage_file_copy_to_file(file, destination, length)) { + FURI_LOG_E(TAG, "Can't copy data to file: %s", furi_string_get_cstr(file_path)); + break; + } + + storage_file_close(destination); + + free(path); + path = NULL; + + if(i == files_count - 1) { + success = true; + } + } + + if(path != NULL) { + free(path); + } + + storage_file_free(destination); + furi_string_free(file_path); + + return success; +} + +static bool flipper_application_assets_process_dirs( + Storage* storage, + File* file, + FuriString* app_name, + uint32_t dirs_count) { + furi_assert(storage); + furi_assert(file); + furi_assert(app_name); + + bool success = false; + FuriString* full_path = flipper_application_assets_alloc_app_full_path(app_name); + + do { + if(!storage_simply_mkdir(storage, APPS_ASSETS_PATH)) { + break; + } + + if(!storage_simply_mkdir(storage, furi_string_get_cstr(full_path))) { + break; + } + + FuriString* dir_path = furi_string_alloc(); + char* path = NULL; + + for(uint32_t i = 0; i < dirs_count; i++) { + path = (char*)flipper_application_assets_alloc_and_load_data(file, NULL); + + if(path == NULL) { + break; + } + + furi_string_set(dir_path, full_path); + furi_string_cat(dir_path, "/"); + furi_string_cat(dir_path, path); + + if(!storage_simply_mkdir(storage, furi_string_get_cstr(dir_path))) { + FURI_LOG_E(TAG, "Can't create directory: %s", furi_string_get_cstr(dir_path)); + break; + } + + free(path); + path = NULL; + + if(i == dirs_count - 1) { + success = true; + } + } + + if(path != NULL) { + free(path); + } + + furi_string_free(dir_path); + } while(false); + + furi_string_free(full_path); + + return success; +} + +static AssetsSignatureResult flipper_application_assets_process_signature( + Storage* storage, + File* file, + FuriString* app_name, + uint8_t** signature_data, + size_t* signature_data_size) { + furi_assert(storage); + furi_assert(file); + furi_assert(app_name); + furi_assert(signature_data); + furi_assert(signature_data_size); + + AssetsSignatureResult result = AssetsSignatureResultError; + File* signature_file = storage_file_alloc(storage); + FuriString* signature_file_path = + flipper_application_assets_alloc_signature_file_path(app_name); + + do { + // read signature + *signature_data = + flipper_application_assets_alloc_and_load_data(file, signature_data_size); + + if(*signature_data == NULL) { //-V547 + FURI_LOG_E(TAG, "Can't read signature"); + break; + } + + result = AssetsSignatureResultNotEqual; + + if(!storage_file_open( + signature_file, + furi_string_get_cstr(signature_file_path), + FSAM_READ_WRITE, + FSOM_OPEN_EXISTING)) { + FURI_LOG_E(TAG, "Can't open signature file"); + break; + } + + size_t signature_size = storage_file_size(signature_file); + uint8_t* signature_file_data = malloc(signature_size); + if(storage_file_read(signature_file, signature_file_data, signature_size) != + signature_size) { + FURI_LOG_E(TAG, "Can't read signature file"); + free(signature_file_data); + break; + } + + if(memcmp(*signature_data, signature_file_data, signature_size) == 0) { + FURI_LOG_D(TAG, "Assets signature is equal"); + result = AssetsSignatureResultEqual; + } + + free(signature_file_data); + } while(0); + + storage_file_free(signature_file); + furi_string_free(signature_file_path); + + return result; +} + +bool flipper_application_assets_load(File* file, const char* elf_path, size_t offset, size_t size) { + UNUSED(size); + furi_assert(file); + furi_assert(elf_path); + FlipperApplicationAssetsHeader header; + bool result = false; + Storage* storage = furi_record_open(RECORD_STORAGE); + uint8_t* signature_data = NULL; + size_t signature_data_size = 0; + FuriString* app_name = furi_string_alloc(); + path_extract_filename_no_ext(elf_path, app_name); + + FURI_LOG_D(TAG, "Loading assets for %s", furi_string_get_cstr(app_name)); + + do { + if(!storage_file_seek(file, offset, true)) { + break; + } + + // read header + if(storage_file_read(file, &header, sizeof(header)) != sizeof(header)) { + break; + } + + if(header.magic != FLIPPER_APPLICATION_ASSETS_MAGIC) { + break; + } + + if(header.version != FLIPPER_APPLICATION_ASSETS_VERSION) { + break; + } + + // process signature + AssetsSignatureResult signature_result = flipper_application_assets_process_signature( + storage, file, app_name, &signature_data, &signature_data_size); + + if(signature_result == AssetsSignatureResultError) { + FURI_LOG_E(TAG, "Assets signature error"); + break; + } else if(signature_result == AssetsSignatureResultEqual) { + FURI_LOG_D(TAG, "Assets signature equal, skip loading"); + result = true; + break; + } else { + FURI_LOG_D(TAG, "Assets signature not equal, loading"); + + // remove old assets + FuriString* full_path = flipper_application_assets_alloc_app_full_path(app_name); + storage_simply_remove_recursive(storage, furi_string_get_cstr(full_path)); + furi_string_free(full_path); + + FURI_LOG_D(TAG, "Assets removed"); + } + + // process directories + if(!flipper_application_assets_process_dirs(storage, file, app_name, header.dirs_count)) { + break; + } + + // process files + if(!flipper_application_assets_process_files(storage, file, app_name, header.files_count)) { + break; + } + + // write signature + FuriString* signature_file_path = + flipper_application_assets_alloc_signature_file_path(app_name); + File* signature_file = storage_file_alloc(storage); + + if(storage_file_open( + signature_file, + furi_string_get_cstr(signature_file_path), + FSAM_WRITE, + FSOM_CREATE_ALWAYS)) { + storage_file_write(signature_file, signature_data, signature_data_size); + } + + storage_file_free(signature_file); + furi_string_free(signature_file_path); + + result = true; + } while(false); + + if(signature_data != NULL) { + free(signature_data); + } + + furi_record_close(RECORD_STORAGE); + furi_string_free(app_name); + + FURI_LOG_D(TAG, "Assets loading %s", result ? "success" : "failed"); + + return result; +} \ No newline at end of file diff --git a/lib/flipper_application/application_assets.h b/lib/flipper_application/application_assets.h new file mode 100644 index 00000000000..83bb14fb669 --- /dev/null +++ b/lib/flipper_application/application_assets.h @@ -0,0 +1,17 @@ +/** + * @file application_assets.h + * Flipper application assets + */ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +bool flipper_application_assets_load(File* file, const char* elf_path, size_t offset, size_t size); + +#ifdef __cplusplus +} +#endif diff --git a/lib/flipper_application/elf/elf_file.c b/lib/flipper_application/elf/elf_file.c index 64d5755efa5..58e3153330a 100644 --- a/lib/flipper_application/elf/elf_file.c +++ b/lib/flipper_application/elf/elf_file.c @@ -241,7 +241,7 @@ static void elf_relocate_jmp_call(ELFFile* elf, Elf32_Addr relAddr, int type, El if(to_thumb || (symAddr & 2) || (!is_call)) { FURI_LOG_D( TAG, - "can't relocate value at %x, %s, doing trampoline", + "can't relocate value at %lx, %s, doing trampoline", relAddr, elf_reloc_type_to_str(type)); @@ -421,29 +421,11 @@ typedef enum { SectionTypeRelData = 1 << 2, SectionTypeSymTab = 1 << 3, SectionTypeStrTab = 1 << 4, - SectionTypeManifest = 1 << 5, - SectionTypeDebugLink = 1 << 6, + SectionTypeDebugLink = 1 << 5, - SectionTypeValid = SectionTypeSymTab | SectionTypeStrTab | SectionTypeManifest, + SectionTypeValid = SectionTypeSymTab | SectionTypeStrTab, } SectionType; -static bool elf_load_metadata( - ELFFile* elf, - Elf32_Shdr* section_header, - FlipperApplicationManifest* manifest) { - if(section_header->sh_size < sizeof(FlipperApplicationManifest)) { - return false; - } - - if(manifest == NULL) { - return true; - } - - return storage_file_seek(elf->fd, section_header->sh_offset, true) && - storage_file_read(elf->fd, manifest, section_header->sh_size) == - section_header->sh_size; -} - static bool elf_load_debug_link(ELFFile* elf, Elf32_Shdr* section_header) { elf->debug_link_info.debug_link_size = section_header->sh_size; elf->debug_link_info.debug_link = malloc(section_header->sh_size); @@ -478,7 +460,7 @@ static bool elf_load_section_data(ELFFile* elf, ELFSection* section, Elf32_Shdr* return false; } - FURI_LOG_D(TAG, "0x%X", section->data); + FURI_LOG_D(TAG, "0x%p", section->data); return true; } @@ -486,8 +468,7 @@ static SectionType elf_preload_section( ELFFile* elf, size_t section_idx, Elf32_Shdr* section_header, - FuriString* name_string, - FlipperApplicationManifest* manifest) { + FuriString* name_string) { const char* name = furi_string_get_cstr(name_string); #ifdef ELF_DEBUG_LOG @@ -572,16 +553,6 @@ static SectionType elf_preload_section( return SectionTypeStrTab; } - // Load manifest section - if(strcmp(name, ".fapmeta") == 0) { - FURI_LOG_D(TAG, "Found .fapmeta section"); - if(elf_load_metadata(elf, section_header, manifest)) { - return SectionTypeManifest; - } else { - return SectionTypeERROR; - } - } - // Load debug link section if(strcmp(name, ".gnu_debuglink") == 0) { FURI_LOG_D(TAG, "Found .gnu_debuglink section"); @@ -692,41 +663,12 @@ bool elf_file_open(ELFFile* elf, const char* path) { return true; } -bool elf_file_load_manifest(ELFFile* elf, FlipperApplicationManifest* manifest) { - bool result = false; - FuriString* name; - name = furi_string_alloc(); - - FURI_LOG_D(TAG, "Looking for manifest section"); - for(size_t section_idx = 1; section_idx < elf->sections_count; section_idx++) { - Elf32_Shdr section_header; - - furi_string_reset(name); - if(!elf_read_section(elf, section_idx, §ion_header, name)) { - break; - } - - if(furi_string_cmp(name, ".fapmeta") == 0) { - if(elf_load_metadata(elf, §ion_header, manifest)) { - FURI_LOG_D(TAG, "Load manifest done"); - result = true; - break; - } else { - break; - } - } - } - - furi_string_free(name); - return result; -} - -bool elf_file_load_section_table(ELFFile* elf, FlipperApplicationManifest* manifest) { +bool elf_file_load_section_table(ELFFile* elf) { SectionType loaded_sections = SectionTypeERROR; - FuriString* name; - name = furi_string_alloc(); + FuriString* name = furi_string_alloc(); FURI_LOG_D(TAG, "Scan ELF indexs..."); + // TODO: why we start from 1? for(size_t section_idx = 1; section_idx < elf->sections_count; section_idx++) { Elf32_Shdr section_header; @@ -738,8 +680,7 @@ bool elf_file_load_section_table(ELFFile* elf, FlipperApplicationManifest* manif FURI_LOG_D( TAG, "Preloading data for section #%d %s", section_idx, furi_string_get_cstr(name)); - SectionType section_type = - elf_preload_section(elf, section_idx, §ion_header, name, manifest); + SectionType section_type = elf_preload_section(elf, section_idx, §ion_header, name); loaded_sections |= section_type; if(section_type == SectionTypeERROR) { @@ -753,14 +694,49 @@ bool elf_file_load_section_table(ELFFile* elf, FlipperApplicationManifest* manif return IS_FLAGS_SET(loaded_sections, SectionTypeValid); } +ElfProcessSectionResult elf_process_section( + ELFFile* elf, + const char* name, + ElfProcessSection* process_section, + void* context) { + ElfProcessSectionResult result = ElfProcessSectionResultNotFound; + FuriString* section_name = furi_string_alloc(); + Elf32_Shdr section_header; + + // find section + // TODO: why we start from 1? + for(size_t section_idx = 1; section_idx < elf->sections_count; section_idx++) { + furi_string_reset(section_name); + if(!elf_read_section(elf, section_idx, §ion_header, section_name)) { + break; + } + + if(furi_string_cmp(section_name, name) == 0) { + result = ElfProcessSectionResultCannotProcess; + break; + } + } + + if(result != ElfProcessSectionResultNotFound) { //-V547 + if(process_section(elf->fd, section_header.sh_offset, section_header.sh_size, context)) { + result = ElfProcessSectionResultSuccess; + } else { + result = ElfProcessSectionResultCannotProcess; + } + } + + furi_string_free(section_name); + + return result; +} + ELFFileLoadStatus elf_file_load_sections(ELFFile* elf) { ELFFileLoadStatus status = ELFFileLoadStatusSuccess; ELFSectionDict_it_t it; AddressCache_init(elf->relocation_cache); - for(ELFSectionDict_it(it, elf->sections); !ELFSectionDict_end_p(it); - ELFSectionDict_next(it)) { + for(ELFSectionDict_it(it, elf->sections); !ELFSectionDict_end_p(it); ELFSectionDict_next(it)) { ELFSectionDict_itref_t* itref = ELFSectionDict_ref(it); FURI_LOG_D(TAG, "Relocating section '%s'", itref->key); if(!elf_relocate_section(elf, &itref->value)) { diff --git a/lib/flipper_application/elf/elf_file.h b/lib/flipper_application/elf/elf_file.h index 673f165ccca..f371cdb22e8 100644 --- a/lib/flipper_application/elf/elf_file.h +++ b/lib/flipper_application/elf/elf_file.h @@ -37,6 +37,14 @@ typedef enum { ELFFileLoadStatusMissingImports, } ELFFileLoadStatus; +typedef enum { + ElfProcessSectionResultNotFound, + ElfProcessSectionResultCannotProcess, + ElfProcessSectionResultSuccess, +} ElfProcessSectionResult; + +typedef bool(ElfProcessSection)(File* file, size_t offset, size_t size, void* context); + /** * @brief Allocate ELFFile instance * @param storage @@ -59,21 +67,12 @@ void elf_file_free(ELFFile* elf_file); */ bool elf_file_open(ELFFile* elf_file, const char* path); -/** - * @brief Load ELF file manifest - * @param elf - * @param manifest - * @return bool - */ -bool elf_file_load_manifest(ELFFile* elf, FlipperApplicationManifest* manifest); - /** * @brief Load ELF file section table (load stage #1) * @param elf_file - * @param manifest * @return bool */ -bool elf_file_load_section_table(ELFFile* elf_file, FlipperApplicationManifest* manifest); +bool elf_file_load_section_table(ELFFile* elf_file); /** * @brief Load and relocate ELF file sections (load stage #2) @@ -122,6 +121,21 @@ void elf_file_init_debug_info(ELFFile* elf_file, ELFDebugInfo* debug_info); */ void elf_file_clear_debug_info(ELFDebugInfo* debug_info); +/** + * @brief Process ELF file section + * + * @param elf_file + * @param name + * @param process_section + * @param context + * @return ElfProcessSectionResult + */ +ElfProcessSectionResult elf_process_section( + ELFFile* elf_file, + const char* name, + ElfProcessSection* process_section, + void* context); + #ifdef __cplusplus } #endif \ No newline at end of file diff --git a/lib/flipper_application/flipper_application.c b/lib/flipper_application/flipper_application.c index 58909218ae1..6e20c080976 100644 --- a/lib/flipper_application/flipper_application.c +++ b/lib/flipper_application/flipper_application.c @@ -1,6 +1,7 @@ #include "flipper_application.h" #include "elf/elf_file.h" #include +#include "application_assets.h" #define TAG "fapp" @@ -55,24 +56,83 @@ static FlipperApplicationPreloadStatus return FlipperApplicationPreloadStatusSuccess; } -/* Parse headers, load manifest */ -FlipperApplicationPreloadStatus - flipper_application_preload_manifest(FlipperApplication* app, const char* path) { - if(!elf_file_open(app->elf, path) || !elf_file_load_manifest(app->elf, &app->manifest)) { +static bool flipper_application_process_manifest_section( + File* file, + size_t offset, + size_t size, + void* context) { + FlipperApplicationManifest* manifest = context; + + if(size < sizeof(FlipperApplicationManifest)) { + return false; + } + + if(manifest == NULL) { + return true; + } + + return storage_file_seek(file, offset, true) && + storage_file_read(file, manifest, size) == size; +} + +// we can't use const char* as context because we will lose the const qualifier +typedef struct { + const char* path; +} FlipperApplicationPreloadAssetsContext; + +static bool flipper_application_process_assets_section( + File* file, + size_t offset, + size_t size, + void* context) { + FlipperApplicationPreloadAssetsContext* preload_context = context; + return flipper_application_assets_load(file, preload_context->path, offset, size); +} + +static FlipperApplicationPreloadStatus + flipper_application_load(FlipperApplication* app, const char* path, bool load_full) { + if(!elf_file_open(app->elf, path)) { + return FlipperApplicationPreloadStatusInvalidFile; + } + + // if we are loading full file + if(load_full) { + // load section table + if(!elf_file_load_section_table(app->elf)) { + return FlipperApplicationPreloadStatusInvalidFile; + } + + // load assets section + FlipperApplicationPreloadAssetsContext preload_context = {.path = path}; + if(elf_process_section( + app->elf, + ".fapassets", + flipper_application_process_assets_section, + &preload_context) == ElfProcessSectionResultCannotProcess) { + return FlipperApplicationPreloadStatusInvalidFile; + } + } + + // load manifest section + if(elf_process_section( + app->elf, ".fapmeta", flipper_application_process_manifest_section, &app->manifest) != + ElfProcessSectionResultSuccess) { return FlipperApplicationPreloadStatusInvalidFile; } return flipper_application_validate_manifest(app); } +/* Parse headers, load manifest */ +FlipperApplicationPreloadStatus + flipper_application_preload_manifest(FlipperApplication* app, const char* path) { + return flipper_application_load(app, path, false); +} + /* Parse headers, load full file */ FlipperApplicationPreloadStatus flipper_application_preload(FlipperApplication* app, const char* path) { - if(!elf_file_open(app->elf, path) || !elf_file_load_section_table(app->elf, &app->manifest)) { - return FlipperApplicationPreloadStatusInvalidFile; - } - - return flipper_application_validate_manifest(app); + return flipper_application_load(app, path, true); } const FlipperApplicationManifest* flipper_application_get_manifest(FlipperApplication* app) { diff --git a/scripts/fbt/appmanifest.py b/scripts/fbt/appmanifest.py index 64b9f6f39b3..eb1652b78eb 100644 --- a/scripts/fbt/appmanifest.py +++ b/scripts/fbt/appmanifest.py @@ -67,6 +67,7 @@ class Library: fap_icon_assets_symbol: Optional[str] = None fap_extbuild: List[ExternallyBuiltFile] = field(default_factory=list) fap_private_libs: List[Library] = field(default_factory=list) + fap_file_assets: Optional[str] = None # Internally used by fbt _appdir: Optional[object] = None _apppath: Optional[str] = None diff --git a/scripts/fbt_tools/fbt_extapps.py b/scripts/fbt_tools/fbt_extapps.py index 214afd8afe8..d26b1b7947b 100644 --- a/scripts/fbt_tools/fbt_extapps.py +++ b/scripts/fbt_tools/fbt_extapps.py @@ -1,5 +1,5 @@ from dataclasses import dataclass, field -from typing import Optional +from typing import Optional, TypedDict from SCons.Builder import Builder from SCons.Action import Action from SCons.Errors import UserError @@ -15,6 +15,8 @@ import pathlib import itertools import shutil +import struct +import hashlib from ansi.color import fg @@ -151,12 +153,24 @@ def BuildAppElf(env, app): app_artifacts.compact, [app_env["SDK_DEFINITION"], app_env.Value(manifest_vals)], ) + + # Add dependencies on icon files if app.fap_icon: app_env.Depends( app_artifacts.compact, app_env.File(f"{app._apppath}/{app.fap_icon}"), ) + # Add dependencies on file assets + if app.fap_file_assets: + app_env.Depends( + app_artifacts.compact, + app_env.GlobRecursive( + "*", + app._appdir.Dir(app.fap_file_assets), + ), + ) + app_artifacts.validator = app_env.ValidateAppImports(app_artifacts.compact) app_env.AlwaysBuild(app_artifacts.validator) app_env.Alias(app_alias, app_artifacts.validator) @@ -266,6 +280,159 @@ def resources_fap_dist_action(target, source, env): shutil.copy(src.path, target.path) +def generate_embed_app_metadata_emitter(target, source, env): + app = env["APP"] + + meta_file_name = source[0].path + ".meta" + target.append("#" + meta_file_name) + + if app.fap_file_assets: + files_section = source[0].path + ".files.section" + target.append("#" + files_section) + + return (target, source) + + +class File(TypedDict): + path: str + size: int + content_path: str + + +class Dir(TypedDict): + path: str + + +def prepare_app_files(target, source, env): + app = env["APP"] + + directory = app._appdir.Dir(app.fap_file_assets) + directory_path = directory.abspath + + if not directory.exists(): + raise UserError(f"File asset directory {directory} does not exist") + + file_list: list[File] = [] + directory_list: list[Dir] = [] + + for root, dirs, files in os.walk(directory_path): + for file_info in files: + file_path = os.path.join(root, file_info) + file_size = os.path.getsize(file_path) + file_list.append( + { + "path": os.path.relpath(file_path, directory_path), + "size": file_size, + "content_path": file_path, + } + ) + + for dir_info in dirs: + dir_path = os.path.join(root, dir_info) + dir_size = sum( + os.path.getsize(os.path.join(dir_path, f)) for f in os.listdir(dir_path) + ) + directory_list.append( + { + "path": os.path.relpath(dir_path, directory_path), + } + ) + + file_list.sort(key=lambda f: f["path"]) + directory_list.sort(key=lambda d: d["path"]) + + files_section = source[0].path + ".files.section" + + with open(files_section, "wb") as f: + # u32 magic + # u32 version + # u32 dirs_count + # u32 files_count + # u32 signature_size + # u8[] signature + # Dirs: + # u32 dir_name length + # u8[] dir_name + # Files: + # u32 file_name length + # u8[] file_name + # u32 file_content_size + # u8[] file_content + + # Write header magic and version + f.write(struct.pack(" Date: Thu, 9 Mar 2023 18:13:18 +0100 Subject: [PATCH 457/824] Upside down / left handed orientation support (#2462) * Add backup files to .gitignore * Added lefty support in Settings > System > hand Orient: Fixes: #1015 * Left handed mode * Fix lefthanded mode on vertical interfaces * Input: new composite sequence identifier * Gui: move input mapping from Canvas to ViewPort, properly handle input mapping on View switch in ViewDispatcher * Rpc: proper input sequencing and tagging in RpcGui * Rpc: remove magic from RpcGui Co-authored-by: MrDaGree <5050898+MrDaGree@users.noreply.github.com> Co-authored-by: Willy-JL Co-authored-by: Aleksandr Kutuzov Co-authored-by: Sergey Gavrilov --- .gitignore | 1 + applications/services/gui/canvas.c | 38 +++++++-------- applications/services/gui/gui.c | 8 +++- applications/services/gui/gui_i.h | 1 + applications/services/gui/view_dispatcher.c | 22 +++++---- applications/services/gui/view_port.c | 48 +++++++++++++------ applications/services/input/input.c | 8 ++-- applications/services/input/input.h | 10 +++- applications/services/rpc/rpc_gui.c | 21 ++++++++ .../settings/system/system_settings.c | 23 +++++++++ .../targets/furi_hal_include/furi_hal_rtc.h | 1 + 11 files changed, 133 insertions(+), 48 deletions(-) diff --git a/.gitignore b/.gitignore index 542652eb0d4..45ac911394d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +*~ *.swp *.swo *.gdb_history diff --git a/applications/services/gui/canvas.c b/applications/services/gui/canvas.c index d47bba6b2cf..9c29a39fd9f 100644 --- a/applications/services/gui/canvas.c +++ b/applications/services/gui/canvas.c @@ -4,6 +4,7 @@ #include #include +#include #include #include @@ -376,39 +377,36 @@ void canvas_set_bitmap_mode(Canvas* canvas, bool alpha) { void canvas_set_orientation(Canvas* canvas, CanvasOrientation orientation) { furi_assert(canvas); + const u8g2_cb_t* rotate_cb = NULL; + bool need_swap = false; if(canvas->orientation != orientation) { switch(orientation) { case CanvasOrientationHorizontal: - if(canvas->orientation == CanvasOrientationVertical || - canvas->orientation == CanvasOrientationVerticalFlip) { - FURI_SWAP(canvas->width, canvas->height); - } - u8g2_SetDisplayRotation(&canvas->fb, U8G2_R0); + need_swap = canvas->orientation == CanvasOrientationVertical || + canvas->orientation == CanvasOrientationVerticalFlip; + rotate_cb = U8G2_R0; break; case CanvasOrientationHorizontalFlip: - if(canvas->orientation == CanvasOrientationVertical || - canvas->orientation == CanvasOrientationVerticalFlip) { - FURI_SWAP(canvas->width, canvas->height); - } - u8g2_SetDisplayRotation(&canvas->fb, U8G2_R2); + need_swap = canvas->orientation == CanvasOrientationVertical || + canvas->orientation == CanvasOrientationVerticalFlip; + rotate_cb = U8G2_R2; break; case CanvasOrientationVertical: - if(canvas->orientation == CanvasOrientationHorizontal || - canvas->orientation == CanvasOrientationHorizontalFlip) { - FURI_SWAP(canvas->width, canvas->height); - }; - u8g2_SetDisplayRotation(&canvas->fb, U8G2_R3); + need_swap = canvas->orientation == CanvasOrientationHorizontal || + canvas->orientation == CanvasOrientationHorizontalFlip; + rotate_cb = U8G2_R3; break; case CanvasOrientationVerticalFlip: - if(canvas->orientation == CanvasOrientationHorizontal || - canvas->orientation == CanvasOrientationHorizontalFlip) { - FURI_SWAP(canvas->width, canvas->height); - } - u8g2_SetDisplayRotation(&canvas->fb, U8G2_R1); + need_swap = canvas->orientation == CanvasOrientationHorizontal || + canvas->orientation == CanvasOrientationHorizontalFlip; + rotate_cb = U8G2_R1; break; default: furi_assert(0); } + + if(need_swap) FURI_SWAP(canvas->width, canvas->height); + u8g2_SetDisplayRotation(&canvas->fb, rotate_cb); canvas->orientation = orientation; } } diff --git a/applications/services/gui/gui.c b/applications/services/gui/gui.c index 94bab140282..24b48a837dc 100644 --- a/applications/services/gui/gui.c +++ b/applications/services/gui/gui.c @@ -50,7 +50,13 @@ static void gui_redraw_status_bar(Gui* gui, bool need_attention) { uint8_t left_used = 0; uint8_t right_used = 0; uint8_t width; - canvas_set_orientation(gui->canvas, CanvasOrientationHorizontal); + + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagHandOrient)) { + canvas_set_orientation(gui->canvas, CanvasOrientationHorizontalFlip); + } else { + canvas_set_orientation(gui->canvas, CanvasOrientationHorizontal); + } + canvas_frame_set( gui->canvas, GUI_STATUS_BAR_X, GUI_STATUS_BAR_Y, GUI_DISPLAY_WIDTH, GUI_STATUS_BAR_HEIGHT); diff --git a/applications/services/gui/gui_i.h b/applications/services/gui/gui_i.h index 45061bd58e8..a5cd841202e 100644 --- a/applications/services/gui/gui_i.h +++ b/applications/services/gui/gui_i.h @@ -8,6 +8,7 @@ #include "gui.h" #include +#include #include #include #include diff --git a/applications/services/gui/view_dispatcher.c b/applications/services/gui/view_dispatcher.c index 046958749cc..920b3c13951 100644 --- a/applications/services/gui/view_dispatcher.c +++ b/applications/services/gui/view_dispatcher.c @@ -320,6 +320,13 @@ void view_dispatcher_send_custom_event(ViewDispatcher* view_dispatcher, uint32_t furi_message_queue_put(view_dispatcher->queue, &message, FuriWaitForever) == FuriStatusOk); } +static const ViewPortOrientation view_dispatcher_view_port_orientation_table[] = { + [ViewOrientationVertical] = ViewPortOrientationVertical, + [ViewOrientationVerticalFlip] = ViewPortOrientationVerticalFlip, + [ViewOrientationHorizontal] = ViewPortOrientationHorizontal, + [ViewOrientationHorizontalFlip] = ViewPortOrientationHorizontalFlip, +}; + void view_dispatcher_set_current_view(ViewDispatcher* view_dispatcher, View* view) { furi_assert(view_dispatcher); // Dispatch view exit event @@ -330,15 +337,12 @@ void view_dispatcher_set_current_view(ViewDispatcher* view_dispatcher, View* vie view_dispatcher->current_view = view; // Dispatch view enter event if(view_dispatcher->current_view) { - if(view->orientation == ViewOrientationVertical) { - view_port_set_orientation(view_dispatcher->view_port, ViewPortOrientationVertical); - } else if(view->orientation == ViewOrientationVerticalFlip) { - view_port_set_orientation(view_dispatcher->view_port, ViewPortOrientationVerticalFlip); - } else if(view->orientation == ViewOrientationHorizontal) { - view_port_set_orientation(view_dispatcher->view_port, ViewPortOrientationHorizontal); - } else if(view->orientation == ViewOrientationHorizontalFlip) { - view_port_set_orientation( - view_dispatcher->view_port, ViewPortOrientationHorizontalFlip); + ViewPortOrientation orientation = + view_dispatcher_view_port_orientation_table[view->orientation]; + if(view_port_get_orientation(view_dispatcher->view_port) != orientation) { + view_port_set_orientation(view_dispatcher->view_port, orientation); + // we just rotated input keys, now it's time to sacrifice some input + view_dispatcher->ongoing_input = 0; } view_enter(view_dispatcher->current_view); view_port_enabled_set(view_dispatcher->view_port, true); diff --git a/applications/services/gui/view_port.c b/applications/services/gui/view_port.c index 5760ed577c4..8c2ff6fe0b3 100644 --- a/applications/services/gui/view_port.c +++ b/applications/services/gui/view_port.c @@ -1,6 +1,8 @@ #include "view_port_i.h" #include +#include +#include #include "gui.h" #include "gui_i.h" @@ -48,27 +50,45 @@ static const InputKey view_port_input_mapping[ViewPortOrientationMAX][InputKeyMA InputKeyBack}, //ViewPortOrientationVerticalFlip }; +static const InputKey view_port_left_hand_input_mapping[InputKeyMAX] = + {InputKeyDown, InputKeyUp, InputKeyLeft, InputKeyRight, InputKeyOk, InputKeyBack}; + +static const CanvasOrientation view_port_orientation_mapping[ViewPortOrientationMAX] = { + [ViewPortOrientationHorizontal] = CanvasOrientationHorizontal, + [ViewPortOrientationHorizontalFlip] = CanvasOrientationHorizontalFlip, + [ViewPortOrientationVertical] = CanvasOrientationVertical, + [ViewPortOrientationVerticalFlip] = CanvasOrientationVerticalFlip, +}; + // Remaps directional pad buttons on Flipper based on ViewPort orientation static void view_port_map_input(InputEvent* event, ViewPortOrientation orientation) { furi_assert(orientation < ViewPortOrientationMAX && event->key < InputKeyMAX); + + if(event->sequence_source != INPUT_SEQUENCE_SOURCE_HARDWARE) { + return; + } + + if(orientation == ViewPortOrientationHorizontal || + orientation == ViewPortOrientationHorizontalFlip) { + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagHandOrient)) { + event->key = view_port_left_hand_input_mapping[event->key]; + } + } event->key = view_port_input_mapping[orientation][event->key]; } static void view_port_setup_canvas_orientation(const ViewPort* view_port, Canvas* canvas) { - switch(view_port->orientation) { - case ViewPortOrientationHorizontalFlip: - canvas_set_orientation(canvas, CanvasOrientationHorizontalFlip); - break; - case ViewPortOrientationVertical: - canvas_set_orientation(canvas, CanvasOrientationVertical); - break; - case ViewPortOrientationVerticalFlip: - canvas_set_orientation(canvas, CanvasOrientationVerticalFlip); - break; - default: - canvas_set_orientation(canvas, CanvasOrientationHorizontal); - break; - }; + CanvasOrientation orientation = view_port_orientation_mapping[view_port->orientation]; + + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagHandOrient)) { + if(orientation == CanvasOrientationHorizontal) { + orientation = CanvasOrientationHorizontalFlip; + } else if(orientation == CanvasOrientationHorizontalFlip) { + orientation = CanvasOrientationHorizontal; + } + } + + canvas_set_orientation(canvas, orientation); } ViewPort* view_port_alloc() { diff --git a/applications/services/input/input.c b/applications/services/input/input.c index e1e581c9f34..8da0a340033 100644 --- a/applications/services/input/input.c +++ b/applications/services/input/input.c @@ -23,7 +23,8 @@ inline static void input_timer_stop(FuriTimer* timer_id) { void input_press_timer_callback(void* arg) { InputPinState* input_pin = arg; InputEvent event; - event.sequence = input_pin->counter; + event.sequence_source = INPUT_SEQUENCE_SOURCE_HARDWARE; + event.sequence_counter = input_pin->counter; event.key = input_pin->pin->key; input_pin->press_counter++; if(input_pin->press_counter == INPUT_LONG_PRESS_COUNTS) { @@ -114,16 +115,17 @@ int32_t input_srv(void* p) { // Common state info InputEvent event; + event.sequence_source = INPUT_SEQUENCE_SOURCE_HARDWARE; event.key = input->pin_states[i].pin->key; // Short / Long / Repeat timer routine if(state) { input->counter++; input->pin_states[i].counter = input->counter; - event.sequence = input->pin_states[i].counter; + event.sequence_counter = input->pin_states[i].counter; input_timer_start(input->pin_states[i].press_timer, INPUT_PRESS_TICKS); } else { - event.sequence = input->pin_states[i].counter; + event.sequence_counter = input->pin_states[i].counter; input_timer_stop(input->pin_states[i].press_timer); if(input->pin_states[i].press_counter < INPUT_LONG_PRESS_COUNTS) { event.type = InputTypeShort; diff --git a/applications/services/input/input.h b/applications/services/input/input.h index 062dc0fa58e..a62e84569b6 100644 --- a/applications/services/input/input.h +++ b/applications/services/input/input.h @@ -12,6 +12,8 @@ extern "C" { #endif #define RECORD_INPUT_EVENTS "input_events" +#define INPUT_SEQUENCE_SOURCE_HARDWARE (0u) +#define INPUT_SEQUENCE_SOURCE_SOFTWARE (1u) /** Input Types * Some of them are physical events and some logical @@ -27,7 +29,13 @@ typedef enum { /** Input Event, dispatches with FuriPubSub */ typedef struct { - uint32_t sequence; + union { + uint32_t sequence; + struct { + uint8_t sequence_source : 2; + uint32_t sequence_counter : 30; + }; + }; InputKey key; InputType type; } InputEvent; diff --git a/applications/services/rpc/rpc_gui.c b/applications/services/rpc/rpc_gui.c index e66553d5133..c2af425e93a 100644 --- a/applications/services/rpc/rpc_gui.c +++ b/applications/services/rpc/rpc_gui.c @@ -12,6 +12,8 @@ typedef enum { #define RpcGuiWorkerFlagAny (RpcGuiWorkerFlagTransmit | RpcGuiWorkerFlagExit) +#define RPC_GUI_INPUT_RESET (0u) + typedef struct { RpcSession* session; Gui* gui; @@ -26,6 +28,9 @@ typedef struct { bool virtual_display_not_empty; bool is_streaming; + + uint32_t input_key_counter[InputKeyMAX]; + uint32_t input_counter; } RpcGuiSystem; static void @@ -194,6 +199,22 @@ static void return; } + // Event sequence shenanigans + event.sequence_source = INPUT_SEQUENCE_SOURCE_SOFTWARE; + if(event.type == InputTypePress) { + rpc_gui->input_counter++; + if(rpc_gui->input_counter == RPC_GUI_INPUT_RESET) rpc_gui->input_counter++; + rpc_gui->input_key_counter[event.key] = rpc_gui->input_counter; + } + if(rpc_gui->input_key_counter[event.key] == RPC_GUI_INPUT_RESET) { + FURI_LOG_W(TAG, "Out of sequence input event: key %d, type %d,", event.key, event.type); + } + event.sequence_counter = rpc_gui->input_key_counter[event.key]; + if(event.type == InputTypeRelease) { + rpc_gui->input_key_counter[event.key] = RPC_GUI_INPUT_RESET; + } + + // Submit event FuriPubSub* input_events = furi_record_open(RECORD_INPUT_EVENTS); furi_check(input_events); furi_pubsub_publish(input_events, &event); diff --git a/applications/settings/system/system_settings.c b/applications/settings/system/system_settings.c index 5eade21159b..cb74d7a8353 100644 --- a/applications/settings/system/system_settings.c +++ b/applications/settings/system/system_settings.c @@ -124,6 +124,23 @@ static void date_format_changed(VariableItem* item) { locale_set_date_format(date_format_value[index]); } +const char* const hand_mode[] = { + "Righty", + "Lefty", +}; + +static void hand_orient_changed(VariableItem* item) { + uint8_t index = variable_item_get_current_value_index(item); + variable_item_set_current_value_text(item, hand_mode[index]); + if(index) { + furi_hal_rtc_set_flag(FuriHalRtcFlagHandOrient); + } else { + furi_hal_rtc_reset_flag(FuriHalRtcFlagHandOrient); + } + + loader_update_menu(); +} + static uint32_t system_settings_exit(void* context) { UNUSED(context); return VIEW_NONE; @@ -145,6 +162,12 @@ SystemSettings* system_settings_alloc() { uint8_t value_index; app->var_item_list = variable_item_list_alloc(); + item = variable_item_list_add( + app->var_item_list, "Hand Orient", COUNT_OF(hand_mode), hand_orient_changed, app); + value_index = furi_hal_rtc_is_flag_set(FuriHalRtcFlagHandOrient) ? 1 : 0; + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, hand_mode[value_index]); + item = variable_item_list_add( app->var_item_list, "Units", diff --git a/firmware/targets/furi_hal_include/furi_hal_rtc.h b/firmware/targets/furi_hal_include/furi_hal_rtc.h index fe095e74980..b16b04a6849 100644 --- a/firmware/targets/furi_hal_include/furi_hal_rtc.h +++ b/firmware/targets/furi_hal_include/furi_hal_rtc.h @@ -29,6 +29,7 @@ typedef enum { FuriHalRtcFlagFactoryReset = (1 << 1), FuriHalRtcFlagLock = (1 << 2), FuriHalRtcFlagC2Update = (1 << 3), + FuriHalRtcFlagHandOrient = (1 << 4), } FuriHalRtcFlag; typedef enum { From 5b05aeea827c7740184bfcbe47e03eef6e3858f8 Mon Sep 17 00:00:00 2001 From: Leo Smith <19672114+p4p1@users.noreply.github.com> Date: Thu, 9 Mar 2023 18:42:34 +0100 Subject: [PATCH 458/824] [#1989] updated parser and added stringln, hold and release (#2448) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * updated parser and added stringln, hold and release * removed unused code as requested from PR * BadUsb: tiny change to trigger rebuild Co-authored-by: p4p1 Co-authored-by: あく --- applications/main/bad_usb/bad_usb_script.c | 279 ++++-------------- applications/main/bad_usb/bad_usb_script.h | 27 +- applications/main/bad_usb/mnemonic.c | 327 +++++++++++++++++++++ applications/main/bad_usb/mnemonic.h | 96 ++++++ 4 files changed, 508 insertions(+), 221 deletions(-) create mode 100644 applications/main/bad_usb/mnemonic.c create mode 100644 applications/main/bad_usb/mnemonic.h diff --git a/applications/main/bad_usb/bad_usb_script.c b/applications/main/bad_usb/bad_usb_script.c index beb35b894f7..12abc766aa8 100644 --- a/applications/main/bad_usb/bad_usb_script.c +++ b/applications/main/bad_usb/bad_usb_script.c @@ -6,11 +6,11 @@ #include #include #include "bad_usb_script.h" +#include "mnemonic.h" #include #define TAG "BadUSB" #define WORKER_TAG TAG "Worker" -#define FILE_BUFFER_LEN 16 #define SCRIPT_STATE_ERROR (-1) #define SCRIPT_STATE_END (-2) @@ -26,24 +26,6 @@ typedef enum { WorkerEvtDisconnect = (1 << 3), } WorkerEvtFlags; -struct BadUsbScript { - FuriHalUsbHidConfig hid_cfg; - BadUsbState st; - FuriString* file_path; - uint32_t defdelay; - uint16_t layout[128]; - uint32_t stringdelay; - FuriThread* thread; - uint8_t file_buf[FILE_BUFFER_LEN + 1]; - uint8_t buf_start; - uint8_t buf_len; - bool file_end; - FuriString* line; - - FuriString* line_prev; - uint32_t repeat_cnt; -}; - typedef struct { char* name; uint16_t keycode; @@ -112,40 +94,21 @@ static const char ducky_cmd_comment[] = {"REM"}; static const char ducky_cmd_id[] = {"ID"}; static const char ducky_cmd_delay[] = {"DELAY "}; static const char ducky_cmd_string[] = {"STRING "}; +static const char ducky_cmd_stringln[] = {"STRINGLN "}; static const char ducky_cmd_defdelay_1[] = {"DEFAULT_DELAY "}; static const char ducky_cmd_defdelay_2[] = {"DEFAULTDELAY "}; static const char ducky_cmd_stringdelay_1[] = {"STRINGDELAY "}; static const char ducky_cmd_stringdelay_2[] = {"STRING_DELAY "}; static const char ducky_cmd_repeat[] = {"REPEAT "}; static const char ducky_cmd_sysrq[] = {"SYSRQ "}; +static const char ducky_cmd_hold[] = {"HOLD "}; +static const char ducky_cmd_release[] = {"RELEASE "}; static const char ducky_cmd_altchar[] = {"ALTCHAR "}; static const char ducky_cmd_altstr_1[] = {"ALTSTRING "}; static const char ducky_cmd_altstr_2[] = {"ALTCODE "}; -static const uint8_t numpad_keys[10] = { - HID_KEYPAD_0, - HID_KEYPAD_1, - HID_KEYPAD_2, - HID_KEYPAD_3, - HID_KEYPAD_4, - HID_KEYPAD_5, - HID_KEYPAD_6, - HID_KEYPAD_7, - HID_KEYPAD_8, - HID_KEYPAD_9, -}; - -static bool ducky_get_number(const char* param, uint32_t* val) { - uint32_t value = 0; - if(sscanf(param, "%lu", &value) == 1) { - *val = value; - return true; - } - return false; -} - -static uint32_t ducky_get_command_len(const char* line) { +uint32_t ducky_get_command_len(const char* line) { uint32_t len = strlen(line); for(uint32_t i = 0; i < len; i++) { if(line[i] == ' ') return i; @@ -153,84 +116,11 @@ static uint32_t ducky_get_command_len(const char* line) { return 0; } -static bool ducky_is_line_end(const char chr) { +bool ducky_is_line_end(const char chr) { return ((chr == ' ') || (chr == '\0') || (chr == '\r') || (chr == '\n')); } -static void ducky_numlock_on() { - if((furi_hal_hid_get_led_state() & HID_KB_LED_NUM) == 0) { - furi_hal_hid_kb_press(HID_KEYBOARD_LOCK_NUM_LOCK); - furi_hal_hid_kb_release(HID_KEYBOARD_LOCK_NUM_LOCK); - } -} - -static bool ducky_numpad_press(const char num) { - if((num < '0') || (num > '9')) return false; - - uint16_t key = numpad_keys[num - '0']; - furi_hal_hid_kb_press(key); - furi_hal_hid_kb_release(key); - - return true; -} - -static bool ducky_altchar(const char* charcode) { - uint8_t i = 0; - bool state = false; - - FURI_LOG_I(WORKER_TAG, "char %s", charcode); - - furi_hal_hid_kb_press(KEY_MOD_LEFT_ALT); - - while(!ducky_is_line_end(charcode[i])) { - state = ducky_numpad_press(charcode[i]); - if(state == false) break; - i++; - } - - furi_hal_hid_kb_release(KEY_MOD_LEFT_ALT); - return state; -} - -static bool ducky_altstring(const char* param) { - uint32_t i = 0; - bool state = false; - - while(param[i] != '\0') { - if((param[i] < ' ') || (param[i] > '~')) { - i++; - continue; // Skip non-printable chars - } - - char temp_str[4]; - snprintf(temp_str, 4, "%u", param[i]); - - state = ducky_altchar(temp_str); - if(state == false) break; - i++; - } - return state; -} - -static bool ducky_string(BadUsbScript* bad_usb, const char* param) { - uint32_t i = 0; - - while(param[i] != '\0') { - uint16_t keycode = BADUSB_ASCII_TO_KEY(bad_usb, param[i]); - if(keycode != HID_KEYBOARD_NONE) { - furi_hal_hid_kb_press(keycode); - furi_hal_hid_kb_release(keycode); - if(bad_usb->stringdelay > 0) { - furi_delay_ms(bad_usb->stringdelay); - } - } - i++; - } - bad_usb->stringdelay = 0; - return true; -} - -static uint16_t ducky_get_keycode(BadUsbScript* bad_usb, const char* param, bool accept_chars) { +uint16_t ducky_get_keycode(BadUsbScript* bad_usb, const char* param, bool accept_chars) { for(size_t i = 0; i < (sizeof(ducky_keys) / sizeof(ducky_keys[0])); i++) { size_t key_cmd_len = strlen(ducky_keys[i].name); if((strncmp(param, ducky_keys[i].name, key_cmd_len) == 0) && @@ -248,119 +138,68 @@ static int32_t ducky_parse_line(BadUsbScript* bad_usb, FuriString* line, char* error, size_t error_len) { uint32_t line_len = furi_string_size(line); const char* line_tmp = furi_string_get_cstr(line); - bool state = false; + const char* ducky_cmd_table[] = { + ducky_cmd_comment, + ducky_cmd_id, + ducky_cmd_delay, + ducky_cmd_string, + ducky_cmd_defdelay_1, + ducky_cmd_defdelay_2, + ducky_cmd_stringdelay_1, + ducky_cmd_stringdelay_2, + ducky_cmd_repeat, + ducky_cmd_sysrq, + ducky_cmd_altchar, + ducky_cmd_altstr_1, + ducky_cmd_altstr_2, + ducky_cmd_stringln, + ducky_cmd_hold, + ducky_cmd_release, + NULL}; + int32_t (*fnc_ptr[])(BadUsbScript*, FuriString*, const char*, char*, size_t) = { + &ducky_fnc_noop, + &ducky_fnc_noop, + &ducky_fnc_delay, + &ducky_fnc_string, + &ducky_fnc_defdelay, + &ducky_fnc_defdelay, + &ducky_fnc_strdelay, + &ducky_fnc_strdelay, + &ducky_fnc_repeat, + &ducky_fnc_sysrq, + &ducky_fnc_altchar, + &ducky_fnc_altstring, + &ducky_fnc_altstring, + &ducky_fnc_stringln, + &ducky_fnc_hold, + &ducky_fnc_release, + NULL}; if(line_len == 0) { return SCRIPT_STATE_NEXT_LINE; // Skip empty lines } - FURI_LOG_D(WORKER_TAG, "line:%s", line_tmp); - - // General commands - if(strncmp(line_tmp, ducky_cmd_comment, strlen(ducky_cmd_comment)) == 0) { - // REM - comment line - return (0); - } else if(strncmp(line_tmp, ducky_cmd_id, strlen(ducky_cmd_id)) == 0) { - // ID - executed in ducky_script_preload - return (0); - } else if(strncmp(line_tmp, ducky_cmd_delay, strlen(ducky_cmd_delay)) == 0) { - // DELAY - line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; - uint32_t delay_val = 0; - state = ducky_get_number(line_tmp, &delay_val); - if((state) && (delay_val > 0)) { - return (int32_t)delay_val; - } - if(error != NULL) { - snprintf(error, error_len, "Invalid number %s", line_tmp); - } - return SCRIPT_STATE_ERROR; - } else if( - (strncmp(line_tmp, ducky_cmd_defdelay_1, strlen(ducky_cmd_defdelay_1)) == 0) || - (strncmp(line_tmp, ducky_cmd_defdelay_2, strlen(ducky_cmd_defdelay_2)) == 0)) { - // DEFAULT_DELAY - line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; - state = ducky_get_number(line_tmp, &bad_usb->defdelay); - if(!state && error != NULL) { - snprintf(error, error_len, "Invalid number %s", line_tmp); - } - return (state) ? (0) : SCRIPT_STATE_ERROR; - } else if( - (strncmp(line_tmp, ducky_cmd_stringdelay_1, strlen(ducky_cmd_stringdelay_1)) == 0) || - (strncmp(line_tmp, ducky_cmd_stringdelay_2, strlen(ducky_cmd_stringdelay_2)) == 0)) { - //STRINGDELAY, finally it's here - line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; - state = ducky_get_number(line_tmp, &bad_usb->stringdelay); - if((state) && (bad_usb->stringdelay > 0)) { - return state; - } + // Ducky Lang Functions + for(size_t i = 0; ducky_cmd_table[i]; i++) { + if(strncmp(line_tmp, ducky_cmd_table[i], strlen(ducky_cmd_table[i])) == 0) + return ((fnc_ptr[i])(bad_usb, line, line_tmp, error, error_len)); + } + // Special keys + modifiers + uint16_t key = ducky_get_keycode(bad_usb, line_tmp, false); + if(key == HID_KEYBOARD_NONE) { if(error != NULL) { - snprintf(error, error_len, "Invalid number %s", line_tmp); + snprintf(error, error_len, "No keycode defined for %s", line_tmp); } return SCRIPT_STATE_ERROR; - - } else if(strncmp(line_tmp, ducky_cmd_string, strlen(ducky_cmd_string)) == 0) { - // STRING - line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; - state = ducky_string(bad_usb, line_tmp); - if(!state && error != NULL) { - snprintf(error, error_len, "Invalid string %s", line_tmp); - } - return (state) ? (0) : SCRIPT_STATE_ERROR; - } else if(strncmp(line_tmp, ducky_cmd_altchar, strlen(ducky_cmd_altchar)) == 0) { - // ALTCHAR - line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; - ducky_numlock_on(); - state = ducky_altchar(line_tmp); - if(!state && error != NULL) { - snprintf(error, error_len, "Invalid altchar %s", line_tmp); - } - return (state) ? (0) : SCRIPT_STATE_ERROR; - } else if( - (strncmp(line_tmp, ducky_cmd_altstr_1, strlen(ducky_cmd_altstr_1)) == 0) || - (strncmp(line_tmp, ducky_cmd_altstr_2, strlen(ducky_cmd_altstr_2)) == 0)) { - // ALTSTRING - line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; - ducky_numlock_on(); - state = ducky_altstring(line_tmp); - if(!state && error != NULL) { - snprintf(error, error_len, "Invalid altstring %s", line_tmp); - } - return (state) ? (0) : SCRIPT_STATE_ERROR; - } else if(strncmp(line_tmp, ducky_cmd_repeat, strlen(ducky_cmd_repeat)) == 0) { - // REPEAT - line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; - state = ducky_get_number(line_tmp, &bad_usb->repeat_cnt); - if(!state && error != NULL) { - snprintf(error, error_len, "Invalid number %s", line_tmp); - } - return (state) ? (0) : SCRIPT_STATE_ERROR; - } else if(strncmp(line_tmp, ducky_cmd_sysrq, strlen(ducky_cmd_sysrq)) == 0) { - // SYSRQ + } + if((key & 0xFF00) != 0) { + // It's a modifier key line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; - uint16_t key = ducky_get_keycode(bad_usb, line_tmp, true); - furi_hal_hid_kb_press(KEY_MOD_LEFT_ALT | HID_KEYBOARD_PRINT_SCREEN); - furi_hal_hid_kb_press(key); - furi_hal_hid_kb_release_all(); - return (0); - } else { - // Special keys + modifiers - uint16_t key = ducky_get_keycode(bad_usb, line_tmp, false); - if(key == HID_KEYBOARD_NONE) { - if(error != NULL) { - snprintf(error, error_len, "No keycode defined for %s", line_tmp); - } - return SCRIPT_STATE_ERROR; - } - if((key & 0xFF00) != 0) { - // It's a modifier key - line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; - key |= ducky_get_keycode(bad_usb, line_tmp, true); - } - furi_hal_hid_kb_press(key); - furi_hal_hid_kb_release(key); - return (0); + key |= ducky_get_keycode(bad_usb, line_tmp, true); } + furi_hal_hid_kb_press(key); + furi_hal_hid_kb_release(key); + return (0); } static bool ducky_set_usb_id(BadUsbScript* bad_usb, const char* line) { diff --git a/applications/main/bad_usb/bad_usb_script.h b/applications/main/bad_usb/bad_usb_script.h index 1e4d98fe7d5..fef2deaed08 100644 --- a/applications/main/bad_usb/bad_usb_script.h +++ b/applications/main/bad_usb/bad_usb_script.h @@ -5,8 +5,9 @@ extern "C" { #endif #include +#include -typedef struct BadUsbScript BadUsbScript; +#define FILE_BUFFER_LEN 16 typedef enum { BadUsbStateInit, @@ -29,6 +30,24 @@ typedef struct { char error[64]; } BadUsbState; +typedef struct BadUsbScript { + FuriHalUsbHidConfig hid_cfg; + BadUsbState st; + FuriString* file_path; + uint32_t defdelay; + uint16_t layout[128]; + uint32_t stringdelay; + FuriThread* thread; + uint8_t file_buf[FILE_BUFFER_LEN + 1]; + uint8_t buf_start; + uint8_t buf_len; + bool file_end; + FuriString* line; + + FuriString* line_prev; + uint32_t repeat_cnt; +} BadUsbScript; + BadUsbScript* bad_usb_script_open(FuriString* file_path); void bad_usb_script_close(BadUsbScript* bad_usb); @@ -43,6 +62,12 @@ void bad_usb_script_toggle(BadUsbScript* bad_usb); BadUsbState* bad_usb_script_get_state(BadUsbScript* bad_usb); +uint16_t ducky_get_keycode(BadUsbScript* bad_usb, const char* param, bool accept_chars); + +uint32_t ducky_get_command_len(const char* line); + +bool ducky_is_line_end(const char chr); + #ifdef __cplusplus } #endif diff --git a/applications/main/bad_usb/mnemonic.c b/applications/main/bad_usb/mnemonic.c new file mode 100644 index 00000000000..f21cc98bb5b --- /dev/null +++ b/applications/main/bad_usb/mnemonic.c @@ -0,0 +1,327 @@ +#include +#include +#include "mnemonic.h" + +#define TAG "BadUSB" +#define WORKER_TAG TAG "Worker" + +#define FILE_BUFFER_LEN 16 +#define SCRIPT_STATE_ERROR (-1) +#define SCRIPT_STATE_END (-2) +#define SCRIPT_STATE_NEXT_LINE (-3) + +#define BADUSB_ASCII_TO_KEY(script, x) \ + (((uint8_t)x < 128) ? (script->layout[(uint8_t)x]) : HID_KEYBOARD_NONE) + +static const uint8_t numpad_keys[10] = { + HID_KEYPAD_0, + HID_KEYPAD_1, + HID_KEYPAD_2, + HID_KEYPAD_3, + HID_KEYPAD_4, + HID_KEYPAD_5, + HID_KEYPAD_6, + HID_KEYPAD_7, + HID_KEYPAD_8, + HID_KEYPAD_9, +}; + +static bool ducky_get_number(const char* param, uint32_t* val) { + uint32_t value = 0; + if(sscanf(param, "%lu", &value) == 1) { + *val = value; + return true; + } + return false; +} + +static void ducky_numlock_on() { + if((furi_hal_hid_get_led_state() & HID_KB_LED_NUM) == 0) { + furi_hal_hid_kb_press(HID_KEYBOARD_LOCK_NUM_LOCK); + furi_hal_hid_kb_release(HID_KEYBOARD_LOCK_NUM_LOCK); + } +} +static bool ducky_numpad_press(const char num) { + if((num < '0') || (num > '9')) return false; + + uint16_t key = numpad_keys[num - '0']; + furi_hal_hid_kb_press(key); + furi_hal_hid_kb_release(key); + + return true; +} + +static bool ducky_altchar(const char* charcode) { + uint8_t i = 0; + bool state = false; + + FURI_LOG_I(WORKER_TAG, "char %s", charcode); + + furi_hal_hid_kb_press(KEY_MOD_LEFT_ALT); + + while(!ducky_is_line_end(charcode[i])) { + state = ducky_numpad_press(charcode[i]); + if(state == false) break; + i++; + } + + furi_hal_hid_kb_release(KEY_MOD_LEFT_ALT); + return state; +} + +static bool ducky_altstring(const char* param) { + uint32_t i = 0; + bool state = false; + + while(param[i] != '\0') { + if((param[i] < ' ') || (param[i] > '~')) { + i++; + continue; // Skip non-printable chars + } + + char temp_str[4]; + snprintf(temp_str, 4, "%u", param[i]); + + state = ducky_altchar(temp_str); + if(state == false) break; + i++; + } + return state; +} + +static bool ducky_string(BadUsbScript* bad_usb, const char* param) { + uint32_t i = 0; + + while(param[i] != '\0') { + uint16_t keycode = BADUSB_ASCII_TO_KEY(bad_usb, param[i]); + if(keycode != HID_KEYBOARD_NONE) { + furi_hal_hid_kb_press(keycode); + furi_hal_hid_kb_release(keycode); + if(bad_usb->stringdelay > 0) { + furi_delay_ms(bad_usb->stringdelay); + } + } + i++; + } + bad_usb->stringdelay = 0; + return true; +} + +int32_t ducky_fnc_noop( + BadUsbScript* bad_usb, + FuriString* line, + const char* line_tmp, + char* error, + size_t error_len) { + (void)bad_usb; + (void)line; + (void)line_tmp; + (void)error; + (void)error_len; + return (0); +} + +int32_t ducky_fnc_delay( + BadUsbScript* bad_usb, + FuriString* line, + const char* line_tmp, + char* error, + size_t error_len) { + bool state = false; + (void)bad_usb; + (void)line; + + line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; + uint32_t delay_val = 0; + state = ducky_get_number(line_tmp, &delay_val); + if((state) && (delay_val > 0)) { + return (int32_t)delay_val; + } + if(error != NULL) { + snprintf(error, error_len, "Invalid number %s", line_tmp); + } + return SCRIPT_STATE_ERROR; +} + +int32_t ducky_fnc_defdelay( + BadUsbScript* bad_usb, + FuriString* line, + const char* line_tmp, + char* error, + size_t error_len) { + bool state = false; + (void)line; + + line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; + state = ducky_get_number(line_tmp, &bad_usb->defdelay); + if(!state && error != NULL) { + snprintf(error, error_len, "Invalid number %s", line_tmp); + } + return (state) ? (0) : SCRIPT_STATE_ERROR; +} + +int32_t ducky_fnc_strdelay( + BadUsbScript* bad_usb, + FuriString* line, + const char* line_tmp, + char* error, + size_t error_len) { + bool state = false; + (void)line; + + line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; + state = ducky_get_number(line_tmp, &bad_usb->stringdelay); + if((state) && (bad_usb->stringdelay > 0)) { + return state; + } + if(error != NULL) { + snprintf(error, error_len, "Invalid number %s", line_tmp); + } + return SCRIPT_STATE_ERROR; +} + +int32_t ducky_fnc_string( + BadUsbScript* bad_usb, + FuriString* line, + const char* line_tmp, + char* error, + size_t error_len) { + bool state = false; + (void)line; + + line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; + state = ducky_string(bad_usb, line_tmp); + if(!state && error != NULL) { + snprintf(error, error_len, "Invalid string %s", line_tmp); + } + return (state) ? (0) : SCRIPT_STATE_ERROR; +} + +int32_t ducky_fnc_repeat( + BadUsbScript* bad_usb, + FuriString* line, + const char* line_tmp, + char* error, + size_t error_len) { + bool state = false; + (void)line; + + line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; + state = ducky_get_number(line_tmp, &bad_usb->repeat_cnt); + if(!state && error != NULL) { + snprintf(error, error_len, "Invalid number %s", line_tmp); + } + return (state) ? (0) : SCRIPT_STATE_ERROR; +} + +int32_t ducky_fnc_sysrq( + BadUsbScript* bad_usb, + FuriString* line, + const char* line_tmp, + char* error, + size_t error_len) { + (void)error; + (void)error_len; + (void)line; + line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; + uint16_t key = ducky_get_keycode(bad_usb, line_tmp, true); + furi_hal_hid_kb_press(KEY_MOD_LEFT_ALT | HID_KEYBOARD_PRINT_SCREEN); + furi_hal_hid_kb_press(key); + furi_hal_hid_kb_release_all(); + return (0); +} + +int32_t ducky_fnc_altchar( + BadUsbScript* bad_usb, + FuriString* line, + const char* line_tmp, + char* error, + size_t error_len) { + bool state = false; + (void)bad_usb; + (void)line; + + line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; + ducky_numlock_on(); + state = ducky_altchar(line_tmp); + if(!state && error != NULL) { + snprintf(error, error_len, "Invalid altchar %s", line_tmp); + } + return (state) ? (0) : SCRIPT_STATE_ERROR; +} + +int32_t ducky_fnc_altstring( + BadUsbScript* bad_usb, + FuriString* line, + const char* line_tmp, + char* error, + size_t error_len) { + bool state = false; + (void)bad_usb; + (void)line; + + line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; + ducky_numlock_on(); + state = ducky_altstring(line_tmp); + if(!state && error != NULL) { + snprintf(error, error_len, "Invalid altstring %s", line_tmp); + } + return (state) ? (0) : SCRIPT_STATE_ERROR; +} + +int32_t ducky_fnc_stringln( + BadUsbScript* bad_usb, + FuriString* line, + const char* line_tmp, + char* error, + size_t error_len) { + bool state = false; + (void)line; + + line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; + state = ducky_string(bad_usb, line_tmp); + if(!state && error != NULL) { + snprintf(error, error_len, "Invalid string %s", line_tmp); + } + furi_hal_hid_kb_press(HID_KEYBOARD_RETURN); + furi_hal_hid_kb_release(HID_KEYBOARD_RETURN); + return (state) ? (0) : SCRIPT_STATE_ERROR; +} + +int32_t ducky_fnc_hold( + BadUsbScript* bad_usb, + FuriString* line, + const char* line_tmp, + char* error, + size_t error_len) { + (void)line; + line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; + uint16_t key = ducky_get_keycode(bad_usb, line_tmp, true); + if(key == HID_KEYBOARD_NONE) { + if(error != NULL) { + snprintf(error, error_len, "No keycode defined for %s", line_tmp); + } + return SCRIPT_STATE_ERROR; + } + furi_hal_hid_kb_press(key); + return (0); +} + +int32_t ducky_fnc_release( + BadUsbScript* bad_usb, + FuriString* line, + const char* line_tmp, + char* error, + size_t error_len) { + (void)line; + line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; + uint16_t key = ducky_get_keycode(bad_usb, line_tmp, true); + if(key == HID_KEYBOARD_NONE) { + if(error != NULL) { + snprintf(error, error_len, "No keycode defined for %s", line_tmp); + } + return SCRIPT_STATE_ERROR; + } + furi_hal_hid_kb_release(key); + return (0); +} diff --git a/applications/main/bad_usb/mnemonic.h b/applications/main/bad_usb/mnemonic.h new file mode 100644 index 00000000000..a85627c3a1b --- /dev/null +++ b/applications/main/bad_usb/mnemonic.h @@ -0,0 +1,96 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "bad_usb_script.h" + +// A no opperation function +int32_t ducky_fnc_noop( + BadUsbScript* bad_usb, + FuriString* line, + const char* line_tmp, + char* error, + size_t error_len); +// DELAY +int32_t ducky_fnc_delay( + BadUsbScript* bad_usb, + FuriString* line, + const char* line_tmp, + char* error, + size_t error_len); +// DEFAULTDELAY +int32_t ducky_fnc_defdelay( + BadUsbScript* bad_usb, + FuriString* line, + const char* line_tmp, + char* error, + size_t error_len); +// STRINGDELAY +int32_t ducky_fnc_strdelay( + BadUsbScript* bad_usb, + FuriString* line, + const char* line_tmp, + char* error, + size_t error_len); +// STRING +int32_t ducky_fnc_string( + BadUsbScript* bad_usb, + FuriString* line, + const char* line_tmp, + char* error, + size_t error_len); +// STRINGLN +int32_t ducky_fnc_stringln( + BadUsbScript* bad_usb, + FuriString* line, + const char* line_tmp, + char* error, + size_t error_len); +// REPEAT +int32_t ducky_fnc_repeat( + BadUsbScript* bad_usb, + FuriString* line, + const char* line_tmp, + char* error, + size_t error_len); +// SYSRQ +int32_t ducky_fnc_sysrq( + BadUsbScript* bad_usb, + FuriString* line, + const char* line_tmp, + char* error, + size_t error_len); +// ALTCHAR +int32_t ducky_fnc_altchar( + BadUsbScript* bad_usb, + FuriString* line, + const char* line_tmp, + char* error, + size_t error_len); +// ALTSTRING +int32_t ducky_fnc_altstring( + BadUsbScript* bad_usb, + FuriString* line, + const char* line_tmp, + char* error, + size_t error_len); +// HOLD +int32_t ducky_fnc_hold( + BadUsbScript* bad_usb, + FuriString* line, + const char* line_tmp, + char* error, + size_t error_len); +// RELEASE +int32_t ducky_fnc_release( + BadUsbScript* bad_usb, + FuriString* line, + const char* line_tmp, + char* error, + size_t error_len); + +#ifdef __cplusplus +} +#endif From 0190a161ba1cb292b894dce66fff15dcb1ed08c4 Mon Sep 17 00:00:00 2001 From: AloneLiberty <111039319+AloneLiberty@users.noreply.github.com> Date: Thu, 9 Mar 2023 20:50:25 +0300 Subject: [PATCH 459/824] NFC: Fix 0 block write possibility in Mifare Classic emulation (#2474) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- lib/nfc/protocols/mifare_classic.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/nfc/protocols/mifare_classic.c b/lib/nfc/protocols/mifare_classic.c index a8a908897d3..71242871745 100644 --- a/lib/nfc/protocols/mifare_classic.c +++ b/lib/nfc/protocols/mifare_classic.c @@ -291,6 +291,10 @@ bool mf_classic_is_allowed_access_data_block( uint8_t* sector_trailer = data->block[mf_classic_get_sector_trailer_num_by_block(block_num)].value; + if(block_num == 0 && action == MfClassicActionDataWrite) { + return false; + } + uint8_t sector_block; if(block_num <= 128) { sector_block = block_num & 0x03; From 4bd3dca16f79d66b513d6505abe30a937a9fc7e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Fri, 10 Mar 2023 20:13:11 +0900 Subject: [PATCH 460/824] Fbt: fix broken resource deployment (#2477) --- scripts/fbt_tools/fbt_extapps.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/fbt_tools/fbt_extapps.py b/scripts/fbt_tools/fbt_extapps.py index d26b1b7947b..1a2761c54af 100644 --- a/scripts/fbt_tools/fbt_extapps.py +++ b/scripts/fbt_tools/fbt_extapps.py @@ -466,7 +466,7 @@ def generate(env, **kw): generator=generate_embed_app_metadata_actions, suffix=".fap", src_suffix=".elf", - emitter=generate_embed_app_metadata_emitter, + # emitter=generate_embed_app_metadata_emitter, ), "ValidateAppImports": Builder( action=[ From 53435579b3c357a1f915dd5e3f0822da65ba59cd Mon Sep 17 00:00:00 2001 From: hedger Date: Tue, 14 Mar 2023 18:29:28 +0400 Subject: [PATCH 461/824] [FL-3097] fbt, faploader: minimal app module implementation (#2420) * fbt, faploader: minimal app module implementation * faploader, libs: moved API hashtable core to flipper_application * example: compound api * lib: flipper_application: naming fixes, doxygen comments * fbt: changed `requires` manifest field behavior for app extensions * examples: refactored plugin apps; faploader: changed new API naming; fbt: changed PLUGIN app type meaning * loader: dropped support for debug apps & plugin menus * moved applications/plugins -> applications/external * Restored x bit on chiplist_convert.py * git: fixed free-dap submodule path * pvs: updated submodule paths * examples: example_advanced_plugins.c: removed potential memory leak on errors * examples: example_plugins: refined requires * fbt: not deploying app modules for debug/sample apps; extra validation for .PLUGIN-type apps * apps: removed cdefines for external apps * fbt: moved ext app path definition * fbt: reworked fap_dist handling; f18: synced api_symbols.csv * fbt: removed resources_paths for extapps * scripts: reworked storage * scripts: reworked runfap.py & selfupdate.py to use new api * wip: fal runner * fbt: moved file packaging into separate module * scripts: storage: fixes * scripts: storage: minor fixes for new api * fbt: changed internal artifact storage details for external apps * scripts: storage: additional fixes and better error reporting; examples: using APP_DATA_PATH() * fbt, scripts: reworked launch_app to deploy plugins; moved old runfap.py to distfap.py * fbt: extra check for plugins descriptors * fbt: additional checks in emitter * fbt: better info message on SDK rebuild * scripts: removed requirements.txt * loader: removed remnants of plugins & debug menus * post-review fixes --- .github/CODEOWNERS | 4 +- .gitmodules | 4 +- .pvsoptions | 2 +- SConstruct | 27 +- applications/examples/application.fam | 1 + .../examples/example_plugins/application.fam | 31 ++ .../example_plugins/example_plugins.c | 70 +++ .../example_plugins/example_plugins_multi.c | 43 ++ .../examples/example_plugins/plugin1.c | 32 ++ .../examples/example_plugins/plugin2.c | 32 ++ .../example_plugins/plugin_interface.h | 12 + .../example_plugins_advanced/app_api.c | 25 + .../example_plugins_advanced/app_api.h | 25 + .../app_api_interface.h | 9 + .../app_api_table.cpp | 27 ++ .../app_api_table_i.h | 13 + .../example_plugins_advanced/application.fam | 24 + .../example_advanced_plugins.c | 48 ++ .../example_plugins_advanced/plugin1.c | 40 ++ .../example_plugins_advanced/plugin2.c | 40 ++ .../plugin_interface.h | 12 + applications/external/application.fam | 6 + .../clock/application.fam | 2 +- .../{plugins => external}/clock/clock.png | Bin .../{plugins => external}/clock/clock_app.c | 0 .../{plugins => external}/dap_link/README.md | 0 .../dap_link/application.fam | 2 +- .../dap_link/dap_config.h | 0 .../{plugins => external}/dap_link/dap_link.c | 0 .../{plugins => external}/dap_link/dap_link.h | 0 .../dap_link/dap_link.png | Bin .../dap_link/gui/dap_gui.c | 0 .../dap_link/gui/dap_gui.h | 0 .../dap_link/gui/dap_gui_custom_event.h | 0 .../dap_link/gui/dap_gui_i.h | 0 .../dap_link/gui/scenes/config/dap_scene.c | 0 .../dap_link/gui/scenes/config/dap_scene.h | 0 .../gui/scenes/config/dap_scene_config.h | 0 .../dap_link/gui/scenes/dap_scene_about.c | 0 .../dap_link/gui/scenes/dap_scene_config.c | 0 .../dap_link/gui/scenes/dap_scene_help.c | 0 .../dap_link/gui/scenes/dap_scene_main.c | 0 .../dap_link/gui/views/dap_main_view.c | 0 .../dap_link/gui/views/dap_main_view.h | 0 .../dap_link/icons/ActiveConnection_50x64.png | Bin .../dap_link/icons/ArrowDownEmpty_12x18.png | Bin .../dap_link/icons/ArrowDownFilled_12x18.png | Bin .../dap_link/icons/ArrowUpEmpty_12x18.png | Bin .../dap_link/icons/ArrowUpFilled_12x18.png | Bin .../dap_link/lib/free-dap | 0 .../dap_link/usb/dap_v2_usb.c | 0 .../dap_link/usb/dap_v2_usb.h | 0 .../dap_link/usb/usb_winusb.h | 0 .../hid_app/application.fam | 4 +- .../hid_app/assets/Arr_dwn_7x9.png | Bin .../hid_app/assets/Arr_up_7x9.png | Bin .../hid_app/assets/Ble_connected_15x15.png | Bin .../hid_app/assets/Ble_disconnected_15x15.png | Bin .../hid_app/assets/ButtonDown_7x4.png | Bin .../hid_app/assets/ButtonF10_5x8.png | Bin .../hid_app/assets/ButtonF11_5x8.png | Bin .../hid_app/assets/ButtonF12_5x8.png | Bin .../hid_app/assets/ButtonF1_5x8.png | Bin .../hid_app/assets/ButtonF2_5x8.png | Bin .../hid_app/assets/ButtonF3_5x8.png | Bin .../hid_app/assets/ButtonF4_5x8.png | Bin .../hid_app/assets/ButtonF5_5x8.png | Bin .../hid_app/assets/ButtonF6_5x8.png | Bin .../hid_app/assets/ButtonF7_5x8.png | Bin .../hid_app/assets/ButtonF8_5x8.png | Bin .../hid_app/assets/ButtonF9_5x8.png | Bin .../hid_app/assets/ButtonLeft_4x7.png | Bin .../hid_app/assets/ButtonRight_4x7.png | Bin .../hid_app/assets/ButtonUp_7x4.png | Bin .../hid_app/assets/Button_18x18.png | Bin .../hid_app/assets/Circles_47x47.png | Bin .../hid_app/assets/Left_mouse_icon_9x9.png | Bin .../hid_app/assets/Like_def_11x9.png | Bin .../hid_app/assets/Like_pressed_17x17.png | Bin .../hid_app/assets/Ok_btn_9x9.png | Bin .../hid_app/assets/Ok_btn_pressed_13x13.png | Bin .../hid_app/assets/Pin_arrow_down_7x9.png | Bin .../hid_app/assets/Pin_arrow_left_9x7.png | Bin .../hid_app/assets/Pin_arrow_right_9x7.png | Bin .../hid_app/assets/Pin_arrow_up_7x9.png | Bin .../hid_app/assets/Pin_back_arrow_10x8.png | Bin .../hid_app/assets/Pressed_Button_13x13.png | Bin .../hid_app/assets/Right_mouse_icon_9x9.png | Bin .../hid_app/assets/Space_65x18.png | Bin .../hid_app/assets/Voldwn_6x6.png | Bin .../hid_app/assets/Volup_8x6.png | Bin .../{plugins => external}/hid_app/hid.c | 0 .../{plugins => external}/hid_app/hid.h | 0 .../hid_app/hid_ble_10px.png | Bin .../hid_app/hid_usb_10px.png | Bin .../{plugins => external}/hid_app/views.h | 0 .../hid_app/views/hid_keyboard.c | 0 .../hid_app/views/hid_keyboard.h | 0 .../hid_app/views/hid_keynote.c | 0 .../hid_app/views/hid_keynote.h | 0 .../hid_app/views/hid_media.c | 0 .../hid_app/views/hid_media.h | 0 .../hid_app/views/hid_mouse.c | 0 .../hid_app/views/hid_mouse.h | 0 .../hid_app/views/hid_mouse_jiggler.c | 0 .../hid_app/views/hid_mouse_jiggler.h | 0 .../hid_app/views/hid_tiktok.c | 0 .../hid_app/views/hid_tiktok.h | 0 .../music_player/application.fam | 3 +- .../music_player/icons/music_10px.png | Bin .../music_player/music_player.c | 0 .../music_player/music_player_cli.c | 0 .../music_player/music_player_worker.c | 0 .../music_player/music_player_worker.h | 0 .../nfc_magic/application.fam | 0 .../nfc_magic/assets/DolphinCommon_56x48.png | Bin .../nfc_magic/assets/DolphinNice_96x59.png | Bin .../nfc_magic/assets/Loading_24.png | Bin .../nfc_magic/assets/NFC_manual_60x50.png | Bin .../nfc_magic/lib/magic/magic.c | 0 .../nfc_magic/lib/magic/magic.h | 0 .../nfc_magic/nfc_magic.c | 0 .../nfc_magic/nfc_magic.h | 0 .../nfc_magic/nfc_magic_i.h | 0 .../nfc_magic/nfc_magic_worker.c | 0 .../nfc_magic/nfc_magic_worker.h | 0 .../nfc_magic/nfc_magic_worker_i.h | 0 .../nfc_magic/scenes/nfc_magic_scene.c | 0 .../nfc_magic/scenes/nfc_magic_scene.h | 0 .../nfc_magic/scenes/nfc_magic_scene_check.c | 0 .../nfc_magic/scenes/nfc_magic_scene_config.h | 0 .../scenes/nfc_magic_scene_file_select.c | 0 .../scenes/nfc_magic_scene_magic_info.c | 0 .../scenes/nfc_magic_scene_not_magic.c | 0 .../nfc_magic/scenes/nfc_magic_scene_start.c | 0 .../scenes/nfc_magic_scene_success.c | 0 .../nfc_magic/scenes/nfc_magic_scene_wipe.c | 0 .../scenes/nfc_magic_scene_wipe_fail.c | 0 .../nfc_magic/scenes/nfc_magic_scene_write.c | 0 .../scenes/nfc_magic_scene_write_confirm.c | 0 .../scenes/nfc_magic_scene_write_fail.c | 0 .../scenes/nfc_magic_scene_wrong_card.c | 0 .../picopass/125_10px.png | Bin .../picopass/application.fam | 0 .../picopass/helpers/iclass_elite_dict.c | 0 .../picopass/helpers/iclass_elite_dict.h | 0 .../picopass/icons/DolphinMafia_115x62.png | Bin .../picopass/icons/DolphinNice_96x59.png | Bin .../picopass/icons/Nfc_10px.png | Bin .../icons/RFIDDolphinReceive_97x61.png | Bin .../picopass/icons/RFIDDolphinSend_97x61.png | Bin .../picopass/lib/loclass/optimized_cipher.c | 0 .../picopass/lib/loclass/optimized_cipher.h | 0 .../lib/loclass/optimized_cipherutils.c | 0 .../lib/loclass/optimized_cipherutils.h | 0 .../picopass/lib/loclass/optimized_elite.c | 0 .../picopass/lib/loclass/optimized_elite.h | 0 .../picopass/lib/loclass/optimized_ikeys.c | 0 .../picopass/lib/loclass/optimized_ikeys.h | 0 .../{plugins => external}/picopass/picopass.c | 0 .../{plugins => external}/picopass/picopass.h | 0 .../picopass/picopass_device.c | 0 .../picopass/picopass_device.h | 0 .../picopass/picopass_i.h | 0 .../picopass/picopass_keys.c | 0 .../picopass/picopass_keys.h | 0 .../picopass/picopass_worker.c | 0 .../picopass/picopass_worker.h | 0 .../picopass/picopass_worker_i.h | 0 .../picopass/rfal_picopass.c | 0 .../picopass/rfal_picopass.h | 0 .../picopass/scenes/picopass_scene.c | 0 .../picopass/scenes/picopass_scene.h | 0 .../scenes/picopass_scene_card_menu.c | 0 .../picopass/scenes/picopass_scene_config.h | 0 .../picopass/scenes/picopass_scene_delete.c | 0 .../scenes/picopass_scene_delete_success.c | 0 .../scenes/picopass_scene_device_info.c | 0 .../scenes/picopass_scene_file_select.c | 0 .../picopass/scenes/picopass_scene_key_menu.c | 0 .../scenes/picopass_scene_read_card.c | 0 .../scenes/picopass_scene_read_card_success.c | 0 .../picopass_scene_read_factory_success.c | 0 .../scenes/picopass_scene_save_name.c | 0 .../scenes/picopass_scene_save_success.c | 0 .../scenes/picopass_scene_saved_menu.c | 0 .../picopass/scenes/picopass_scene_start.c | 0 .../scenes/picopass_scene_write_card.c | 0 .../picopass_scene_write_card_success.c | 0 .../scenes/picopass_scene_write_key.c | 0 .../signal_generator/application.fam | 3 +- .../icons/SmallArrowDown_3x5.png | Bin .../icons/SmallArrowUp_3x5.png | Bin .../scenes/signal_gen_scene.c | 0 .../scenes/signal_gen_scene.h | 0 .../scenes/signal_gen_scene_config.h | 0 .../scenes/signal_gen_scene_mco.c | 0 .../scenes/signal_gen_scene_pwm.c | 0 .../scenes/signal_gen_scene_start.c | 0 .../signal_generator/signal_gen_10px.png | Bin .../signal_generator/signal_gen_app.c | 0 .../signal_generator/signal_gen_app_i.h | 0 .../signal_generator/views/signal_gen_pwm.c | 0 .../signal_generator/views/signal_gen_pwm.h | 0 .../snake_game/application.fam | 3 +- .../snake_game/snake_10px.png | Bin .../snake_game/snake_game.c | 0 .../spi_mem_manager/application.fam | 0 .../images/ChipLooking_64x64/frame_01.png | Bin .../images/ChipLooking_64x64/frame_02.png | Bin .../images/ChipLooking_64x64/frame_03.png | Bin .../images/ChipLooking_64x64/frame_rate | 0 .../spi_mem_manager/images/Dip8_10px.png | Bin .../spi_mem_manager/images/Dip8_32x36.png | Bin .../images/DolphinMafia_115x62.png | Bin .../images/DolphinNice_96x59.png | Bin .../images/SDQuestion_35x43.png | Bin .../images/Wiring_SPI_128x64.png | Bin .../spi_mem_manager/lib/spi/spi_mem_chip.c | 0 .../spi_mem_manager/lib/spi/spi_mem_chip.h | 0 .../lib/spi/spi_mem_chip_arr.c | 0 .../spi_mem_manager/lib/spi/spi_mem_chip_i.h | 0 .../spi_mem_manager/lib/spi/spi_mem_tools.c | 0 .../spi_mem_manager/lib/spi/spi_mem_tools.h | 0 .../spi_mem_manager/lib/spi/spi_mem_worker.c | 0 .../spi_mem_manager/lib/spi/spi_mem_worker.h | 0 .../lib/spi/spi_mem_worker_i.h | 0 .../lib/spi/spi_mem_worker_modes.c | 0 .../spi_mem_manager/scenes/spi_mem_scene.c | 0 .../spi_mem_manager/scenes/spi_mem_scene.h | 0 .../scenes/spi_mem_scene_about.c | 0 .../scenes/spi_mem_scene_chip_detect.c | 0 .../scenes/spi_mem_scene_chip_detect_fail.c | 0 .../scenes/spi_mem_scene_chip_detected.c | 0 .../scenes/spi_mem_scene_chip_error.c | 0 .../scenes/spi_mem_scene_config.h | 0 .../scenes/spi_mem_scene_delete_confirm.c | 0 .../scenes/spi_mem_scene_erase.c | 0 .../scenes/spi_mem_scene_file_info.c | 0 .../scenes/spi_mem_scene_read.c | 0 .../scenes/spi_mem_scene_read_filename.c | 0 .../scenes/spi_mem_scene_saved_file_menu.c | 0 .../scenes/spi_mem_scene_select_file.c | 0 .../scenes/spi_mem_scene_select_model.c | 0 .../scenes/spi_mem_scene_select_vendor.c | 0 .../scenes/spi_mem_scene_start.c | 0 .../scenes/spi_mem_scene_storage_error.c | 0 .../scenes/spi_mem_scene_success.c | 0 .../scenes/spi_mem_scene_verify.c | 0 .../scenes/spi_mem_scene_verify_error.c | 0 .../scenes/spi_mem_scene_wiring.c | 0 .../scenes/spi_mem_scene_write.c | 0 .../spi_mem_manager/spi_mem_app.c | 0 .../spi_mem_manager/spi_mem_app.h | 0 .../spi_mem_manager/spi_mem_app_i.h | 0 .../spi_mem_manager/spi_mem_files.c | 0 .../spi_mem_manager/spi_mem_files.h | 0 .../spi_mem_manager/tools/README.md | 0 .../spi_mem_manager/tools/chiplist/LICENSE | 0 .../tools/chiplist/chiplist.xml | 0 .../spi_mem_manager/tools/chiplist_convert.py | 0 .../views/spi_mem_view_detect.c | 0 .../views/spi_mem_view_detect.h | 0 .../views/spi_mem_view_progress.c | 0 .../views/spi_mem_view_progress.h | 0 .../weather_station/application.fam | 3 +- .../helpers/weather_station_event.h | 0 .../helpers/weather_station_types.h | 0 .../weather_station/images/Humid_10x15.png | Bin .../weather_station/images/Humid_8x13.png | Bin .../weather_station/images/Lock_7x8.png | Bin .../images/Pin_back_arrow_10x8.png | Bin .../weather_station/images/Quest_7x8.png | Bin .../images/Scanning_123x52.png | Bin .../weather_station/images/Therm_7x16.png | Bin .../weather_station/images/Timer_11x11.png | Bin .../weather_station/images/Unlock_7x8.png | Bin .../images/WarningDolphin_45x42.png | Bin .../weather_station/images/station_icon.png | Bin .../protocols/acurite_592txr.c | 0 .../protocols/acurite_592txr.h | 0 .../weather_station/protocols/acurite_606tx.c | 0 .../weather_station/protocols/acurite_606tx.h | 0 .../protocols/acurite_609txc.c | 0 .../protocols/acurite_609txc.h | 0 .../protocols/ambient_weather.c | 0 .../protocols/ambient_weather.h | 0 .../protocols/auriol_hg0601a.c | 0 .../protocols/auriol_hg0601a.h | 0 .../weather_station/protocols/gt_wt_02.c | 0 .../weather_station/protocols/gt_wt_02.h | 0 .../weather_station/protocols/gt_wt_03.c | 0 .../weather_station/protocols/gt_wt_03.h | 0 .../weather_station/protocols/infactory.c | 0 .../weather_station/protocols/infactory.h | 0 .../weather_station/protocols/lacrosse_tx.c | 0 .../weather_station/protocols/lacrosse_tx.h | 0 .../protocols/lacrosse_tx141thbv2.c | 0 .../protocols/lacrosse_tx141thbv2.h | 0 .../weather_station/protocols/nexus_th.c | 0 .../weather_station/protocols/nexus_th.h | 0 .../weather_station/protocols/oregon2.c | 0 .../weather_station/protocols/oregon2.h | 0 .../weather_station/protocols/oregon_v1.c | 0 .../weather_station/protocols/oregon_v1.h | 0 .../protocols/protocol_items.c | 0 .../protocols/protocol_items.h | 0 .../weather_station/protocols/thermopro_tx4.c | 0 .../weather_station/protocols/thermopro_tx4.h | 0 .../weather_station/protocols/tx_8300.c | 0 .../weather_station/protocols/tx_8300.h | 0 .../weather_station/protocols/ws_generic.c | 0 .../weather_station/protocols/ws_generic.h | 0 .../scenes/weather_station_receiver.c | 0 .../scenes/weather_station_scene.c | 0 .../scenes/weather_station_scene.h | 0 .../scenes/weather_station_scene_about.c | 0 .../scenes/weather_station_scene_config.h | 0 .../weather_station_scene_receiver_config.c | 0 .../weather_station_scene_receiver_info.c | 0 .../scenes/weather_station_scene_start.c | 0 .../views/weather_station_receiver.c | 0 .../views/weather_station_receiver.h | 0 .../views/weather_station_receiver_info.c | 0 .../views/weather_station_receiver_info.h | 0 .../weather_station/weather_station_10px.png | Bin .../weather_station/weather_station_app.c | 0 .../weather_station/weather_station_app_i.c | 0 .../weather_station/weather_station_app_i.h | 0 .../weather_station/weather_station_history.c | 0 .../weather_station/weather_station_history.h | 0 applications/main/fap_loader/application.fam | 1 + .../main/fap_loader/elf_cpp/elf_hashtable.cpp | 48 -- .../main/fap_loader/elf_cpp/elf_hashtable.h | 14 - .../elf_cpp/elf_hashtable_checks.hpp | 18 - .../fap_loader/elf_cpp/elf_hashtable_entry.h | 41 -- applications/main/fap_loader/fap_loader_app.c | 6 +- applications/plugins/application.fam | 9 - applications/services/applications.h | 12 - applications/services/loader/application.fam | 5 +- .../loader/firmware_api/firmware_api.cpp | 21 + .../loader/firmware_api/firmware_api.h | 5 + applications/services/loader/loader.c | 81 +--- applications/services/loader/loader_i.h | 4 - assets/.gitignore | 1 + documentation/Doxyfile | 2 +- fbt_options.py | 5 - firmware/targets/f18/api_symbols.csv | 24 +- firmware/targets/f7/api_symbols.csv | 24 +- furi/flipper.c | 2 +- lib/flipper_application/SConscript | 5 + .../api_hashtable/api_hashtable.cpp | 38 ++ .../api_hashtable/api_hashtable.h | 85 ++++ .../api_hashtable}/compilesort.hpp | 4 + .../elf/elf_api_interface.h | 12 +- lib/flipper_application/elf/elf_file.c | 33 +- lib/flipper_application/elf/elf_file.h | 22 +- lib/flipper_application/elf/elf_file_i.h | 2 + lib/flipper_application/flipper_application.c | 64 ++- lib/flipper_application/flipper_application.h | 36 +- .../plugins/composite_resolver.c | 52 +++ .../plugins/composite_resolver.h | 46 ++ .../plugins/plugin_manager.c | 153 +++++++ .../plugins/plugin_manager.h | 82 ++++ scripts/distfap.py | 71 +++ scripts/fbt/appmanifest.py | 46 +- scripts/fbt/fapassets.py | 108 +++++ scripts/fbt_tools/fbt_extapps.py | 431 ++++++++---------- scripts/fbt_tools/fbt_sdk.py | 2 +- scripts/flipper/storage.py | 344 +++++++++----- scripts/requirements.txt | 9 - scripts/runfap.py | 130 +++--- scripts/selfupdate.py | 90 +--- scripts/storage.py | 267 +++-------- site_scons/commandline.scons | 6 +- site_scons/extapps.scons | 65 ++- 376 files changed, 2039 insertions(+), 1034 deletions(-) create mode 100644 applications/examples/example_plugins/application.fam create mode 100644 applications/examples/example_plugins/example_plugins.c create mode 100644 applications/examples/example_plugins/example_plugins_multi.c create mode 100644 applications/examples/example_plugins/plugin1.c create mode 100644 applications/examples/example_plugins/plugin2.c create mode 100644 applications/examples/example_plugins/plugin_interface.h create mode 100644 applications/examples/example_plugins_advanced/app_api.c create mode 100644 applications/examples/example_plugins_advanced/app_api.h create mode 100644 applications/examples/example_plugins_advanced/app_api_interface.h create mode 100644 applications/examples/example_plugins_advanced/app_api_table.cpp create mode 100644 applications/examples/example_plugins_advanced/app_api_table_i.h create mode 100644 applications/examples/example_plugins_advanced/application.fam create mode 100644 applications/examples/example_plugins_advanced/example_advanced_plugins.c create mode 100644 applications/examples/example_plugins_advanced/plugin1.c create mode 100644 applications/examples/example_plugins_advanced/plugin2.c create mode 100644 applications/examples/example_plugins_advanced/plugin_interface.h create mode 100644 applications/external/application.fam rename applications/{plugins => external}/clock/application.fam (82%) rename applications/{plugins => external}/clock/clock.png (100%) rename applications/{plugins => external}/clock/clock_app.c (100%) rename applications/{plugins => external}/dap_link/README.md (100%) rename applications/{plugins => external}/dap_link/application.fam (92%) rename applications/{plugins => external}/dap_link/dap_config.h (100%) rename applications/{plugins => external}/dap_link/dap_link.c (100%) rename applications/{plugins => external}/dap_link/dap_link.h (100%) rename applications/{plugins => external}/dap_link/dap_link.png (100%) rename applications/{plugins => external}/dap_link/gui/dap_gui.c (100%) rename applications/{plugins => external}/dap_link/gui/dap_gui.h (100%) rename applications/{plugins => external}/dap_link/gui/dap_gui_custom_event.h (100%) rename applications/{plugins => external}/dap_link/gui/dap_gui_i.h (100%) rename applications/{plugins => external}/dap_link/gui/scenes/config/dap_scene.c (100%) rename applications/{plugins => external}/dap_link/gui/scenes/config/dap_scene.h (100%) rename applications/{plugins => external}/dap_link/gui/scenes/config/dap_scene_config.h (100%) rename applications/{plugins => external}/dap_link/gui/scenes/dap_scene_about.c (100%) rename applications/{plugins => external}/dap_link/gui/scenes/dap_scene_config.c (100%) rename applications/{plugins => external}/dap_link/gui/scenes/dap_scene_help.c (100%) rename applications/{plugins => external}/dap_link/gui/scenes/dap_scene_main.c (100%) rename applications/{plugins => external}/dap_link/gui/views/dap_main_view.c (100%) rename applications/{plugins => external}/dap_link/gui/views/dap_main_view.h (100%) rename applications/{plugins => external}/dap_link/icons/ActiveConnection_50x64.png (100%) rename applications/{plugins => external}/dap_link/icons/ArrowDownEmpty_12x18.png (100%) rename applications/{plugins => external}/dap_link/icons/ArrowDownFilled_12x18.png (100%) rename applications/{plugins => external}/dap_link/icons/ArrowUpEmpty_12x18.png (100%) rename applications/{plugins => external}/dap_link/icons/ArrowUpFilled_12x18.png (100%) rename applications/{plugins => external}/dap_link/lib/free-dap (100%) rename applications/{plugins => external}/dap_link/usb/dap_v2_usb.c (100%) rename applications/{plugins => external}/dap_link/usb/dap_v2_usb.h (100%) rename applications/{plugins => external}/dap_link/usb/usb_winusb.h (100%) rename applications/{plugins => external}/hid_app/application.fam (86%) rename applications/{plugins => external}/hid_app/assets/Arr_dwn_7x9.png (100%) rename applications/{plugins => external}/hid_app/assets/Arr_up_7x9.png (100%) rename applications/{plugins => external}/hid_app/assets/Ble_connected_15x15.png (100%) rename applications/{plugins => external}/hid_app/assets/Ble_disconnected_15x15.png (100%) rename applications/{plugins => external}/hid_app/assets/ButtonDown_7x4.png (100%) rename applications/{plugins => external}/hid_app/assets/ButtonF10_5x8.png (100%) rename applications/{plugins => external}/hid_app/assets/ButtonF11_5x8.png (100%) rename applications/{plugins => external}/hid_app/assets/ButtonF12_5x8.png (100%) rename applications/{plugins => external}/hid_app/assets/ButtonF1_5x8.png (100%) rename applications/{plugins => external}/hid_app/assets/ButtonF2_5x8.png (100%) rename applications/{plugins => external}/hid_app/assets/ButtonF3_5x8.png (100%) rename applications/{plugins => external}/hid_app/assets/ButtonF4_5x8.png (100%) rename applications/{plugins => external}/hid_app/assets/ButtonF5_5x8.png (100%) rename applications/{plugins => external}/hid_app/assets/ButtonF6_5x8.png (100%) rename applications/{plugins => external}/hid_app/assets/ButtonF7_5x8.png (100%) rename applications/{plugins => external}/hid_app/assets/ButtonF8_5x8.png (100%) rename applications/{plugins => external}/hid_app/assets/ButtonF9_5x8.png (100%) rename applications/{plugins => external}/hid_app/assets/ButtonLeft_4x7.png (100%) rename applications/{plugins => external}/hid_app/assets/ButtonRight_4x7.png (100%) rename applications/{plugins => external}/hid_app/assets/ButtonUp_7x4.png (100%) rename applications/{plugins => external}/hid_app/assets/Button_18x18.png (100%) rename applications/{plugins => external}/hid_app/assets/Circles_47x47.png (100%) rename applications/{plugins => external}/hid_app/assets/Left_mouse_icon_9x9.png (100%) rename applications/{plugins => external}/hid_app/assets/Like_def_11x9.png (100%) rename applications/{plugins => external}/hid_app/assets/Like_pressed_17x17.png (100%) rename applications/{plugins => external}/hid_app/assets/Ok_btn_9x9.png (100%) rename applications/{plugins => external}/hid_app/assets/Ok_btn_pressed_13x13.png (100%) rename applications/{plugins => external}/hid_app/assets/Pin_arrow_down_7x9.png (100%) rename applications/{plugins => external}/hid_app/assets/Pin_arrow_left_9x7.png (100%) rename applications/{plugins => external}/hid_app/assets/Pin_arrow_right_9x7.png (100%) rename applications/{plugins => external}/hid_app/assets/Pin_arrow_up_7x9.png (100%) rename applications/{plugins => external}/hid_app/assets/Pin_back_arrow_10x8.png (100%) rename applications/{plugins => external}/hid_app/assets/Pressed_Button_13x13.png (100%) rename applications/{plugins => external}/hid_app/assets/Right_mouse_icon_9x9.png (100%) rename applications/{plugins => external}/hid_app/assets/Space_65x18.png (100%) rename applications/{plugins => external}/hid_app/assets/Voldwn_6x6.png (100%) rename applications/{plugins => external}/hid_app/assets/Volup_8x6.png (100%) rename applications/{plugins => external}/hid_app/hid.c (100%) rename applications/{plugins => external}/hid_app/hid.h (100%) rename applications/{plugins => external}/hid_app/hid_ble_10px.png (100%) rename applications/{plugins => external}/hid_app/hid_usb_10px.png (100%) rename applications/{plugins => external}/hid_app/views.h (100%) rename applications/{plugins => external}/hid_app/views/hid_keyboard.c (100%) rename applications/{plugins => external}/hid_app/views/hid_keyboard.h (100%) rename applications/{plugins => external}/hid_app/views/hid_keynote.c (100%) rename applications/{plugins => external}/hid_app/views/hid_keynote.h (100%) rename applications/{plugins => external}/hid_app/views/hid_media.c (100%) rename applications/{plugins => external}/hid_app/views/hid_media.h (100%) rename applications/{plugins => external}/hid_app/views/hid_mouse.c (100%) rename applications/{plugins => external}/hid_app/views/hid_mouse.h (100%) rename applications/{plugins => external}/hid_app/views/hid_mouse_jiggler.c (100%) rename applications/{plugins => external}/hid_app/views/hid_mouse_jiggler.h (100%) rename applications/{plugins => external}/hid_app/views/hid_tiktok.c (100%) rename applications/{plugins => external}/hid_app/views/hid_tiktok.h (100%) rename applications/{plugins => external}/music_player/application.fam (87%) rename applications/{plugins => external}/music_player/icons/music_10px.png (100%) rename applications/{plugins => external}/music_player/music_player.c (100%) rename applications/{plugins => external}/music_player/music_player_cli.c (100%) rename applications/{plugins => external}/music_player/music_player_worker.c (100%) rename applications/{plugins => external}/music_player/music_player_worker.h (100%) rename applications/{plugins => external}/nfc_magic/application.fam (100%) rename applications/{plugins => external}/nfc_magic/assets/DolphinCommon_56x48.png (100%) rename applications/{plugins => external}/nfc_magic/assets/DolphinNice_96x59.png (100%) rename applications/{plugins => external}/nfc_magic/assets/Loading_24.png (100%) rename applications/{plugins => external}/nfc_magic/assets/NFC_manual_60x50.png (100%) rename applications/{plugins => external}/nfc_magic/lib/magic/magic.c (100%) rename applications/{plugins => external}/nfc_magic/lib/magic/magic.h (100%) rename applications/{plugins => external}/nfc_magic/nfc_magic.c (100%) rename applications/{plugins => external}/nfc_magic/nfc_magic.h (100%) rename applications/{plugins => external}/nfc_magic/nfc_magic_i.h (100%) rename applications/{plugins => external}/nfc_magic/nfc_magic_worker.c (100%) rename applications/{plugins => external}/nfc_magic/nfc_magic_worker.h (100%) rename applications/{plugins => external}/nfc_magic/nfc_magic_worker_i.h (100%) rename applications/{plugins => external}/nfc_magic/scenes/nfc_magic_scene.c (100%) rename applications/{plugins => external}/nfc_magic/scenes/nfc_magic_scene.h (100%) rename applications/{plugins => external}/nfc_magic/scenes/nfc_magic_scene_check.c (100%) rename applications/{plugins => external}/nfc_magic/scenes/nfc_magic_scene_config.h (100%) rename applications/{plugins => external}/nfc_magic/scenes/nfc_magic_scene_file_select.c (100%) rename applications/{plugins => external}/nfc_magic/scenes/nfc_magic_scene_magic_info.c (100%) rename applications/{plugins => external}/nfc_magic/scenes/nfc_magic_scene_not_magic.c (100%) rename applications/{plugins => external}/nfc_magic/scenes/nfc_magic_scene_start.c (100%) rename applications/{plugins => external}/nfc_magic/scenes/nfc_magic_scene_success.c (100%) rename applications/{plugins => external}/nfc_magic/scenes/nfc_magic_scene_wipe.c (100%) rename applications/{plugins => external}/nfc_magic/scenes/nfc_magic_scene_wipe_fail.c (100%) rename applications/{plugins => external}/nfc_magic/scenes/nfc_magic_scene_write.c (100%) rename applications/{plugins => external}/nfc_magic/scenes/nfc_magic_scene_write_confirm.c (100%) rename applications/{plugins => external}/nfc_magic/scenes/nfc_magic_scene_write_fail.c (100%) rename applications/{plugins => external}/nfc_magic/scenes/nfc_magic_scene_wrong_card.c (100%) rename applications/{plugins => external}/picopass/125_10px.png (100%) rename applications/{plugins => external}/picopass/application.fam (100%) rename applications/{plugins => external}/picopass/helpers/iclass_elite_dict.c (100%) rename applications/{plugins => external}/picopass/helpers/iclass_elite_dict.h (100%) rename applications/{plugins => external}/picopass/icons/DolphinMafia_115x62.png (100%) rename applications/{plugins => external}/picopass/icons/DolphinNice_96x59.png (100%) rename applications/{plugins => external}/picopass/icons/Nfc_10px.png (100%) rename applications/{plugins => external}/picopass/icons/RFIDDolphinReceive_97x61.png (100%) rename applications/{plugins => external}/picopass/icons/RFIDDolphinSend_97x61.png (100%) rename applications/{plugins => external}/picopass/lib/loclass/optimized_cipher.c (100%) rename applications/{plugins => external}/picopass/lib/loclass/optimized_cipher.h (100%) rename applications/{plugins => external}/picopass/lib/loclass/optimized_cipherutils.c (100%) rename applications/{plugins => external}/picopass/lib/loclass/optimized_cipherutils.h (100%) rename applications/{plugins => external}/picopass/lib/loclass/optimized_elite.c (100%) rename applications/{plugins => external}/picopass/lib/loclass/optimized_elite.h (100%) rename applications/{plugins => external}/picopass/lib/loclass/optimized_ikeys.c (100%) rename applications/{plugins => external}/picopass/lib/loclass/optimized_ikeys.h (100%) rename applications/{plugins => external}/picopass/picopass.c (100%) rename applications/{plugins => external}/picopass/picopass.h (100%) rename applications/{plugins => external}/picopass/picopass_device.c (100%) rename applications/{plugins => external}/picopass/picopass_device.h (100%) rename applications/{plugins => external}/picopass/picopass_i.h (100%) rename applications/{plugins => external}/picopass/picopass_keys.c (100%) rename applications/{plugins => external}/picopass/picopass_keys.h (100%) rename applications/{plugins => external}/picopass/picopass_worker.c (100%) rename applications/{plugins => external}/picopass/picopass_worker.h (100%) rename applications/{plugins => external}/picopass/picopass_worker_i.h (100%) rename applications/{plugins => external}/picopass/rfal_picopass.c (100%) rename applications/{plugins => external}/picopass/rfal_picopass.h (100%) rename applications/{plugins => external}/picopass/scenes/picopass_scene.c (100%) rename applications/{plugins => external}/picopass/scenes/picopass_scene.h (100%) rename applications/{plugins => external}/picopass/scenes/picopass_scene_card_menu.c (100%) rename applications/{plugins => external}/picopass/scenes/picopass_scene_config.h (100%) rename applications/{plugins => external}/picopass/scenes/picopass_scene_delete.c (100%) rename applications/{plugins => external}/picopass/scenes/picopass_scene_delete_success.c (100%) rename applications/{plugins => external}/picopass/scenes/picopass_scene_device_info.c (100%) rename applications/{plugins => external}/picopass/scenes/picopass_scene_file_select.c (100%) rename applications/{plugins => external}/picopass/scenes/picopass_scene_key_menu.c (100%) rename applications/{plugins => external}/picopass/scenes/picopass_scene_read_card.c (100%) rename applications/{plugins => external}/picopass/scenes/picopass_scene_read_card_success.c (100%) rename applications/{plugins => external}/picopass/scenes/picopass_scene_read_factory_success.c (100%) rename applications/{plugins => external}/picopass/scenes/picopass_scene_save_name.c (100%) rename applications/{plugins => external}/picopass/scenes/picopass_scene_save_success.c (100%) rename applications/{plugins => external}/picopass/scenes/picopass_scene_saved_menu.c (100%) rename applications/{plugins => external}/picopass/scenes/picopass_scene_start.c (100%) rename applications/{plugins => external}/picopass/scenes/picopass_scene_write_card.c (100%) rename applications/{plugins => external}/picopass/scenes/picopass_scene_write_card_success.c (100%) rename applications/{plugins => external}/picopass/scenes/picopass_scene_write_key.c (100%) rename applications/{plugins => external}/signal_generator/application.fam (78%) rename applications/{plugins => external}/signal_generator/icons/SmallArrowDown_3x5.png (100%) rename applications/{plugins => external}/signal_generator/icons/SmallArrowUp_3x5.png (100%) rename applications/{plugins => external}/signal_generator/scenes/signal_gen_scene.c (100%) rename applications/{plugins => external}/signal_generator/scenes/signal_gen_scene.h (100%) rename applications/{plugins => external}/signal_generator/scenes/signal_gen_scene_config.h (100%) rename applications/{plugins => external}/signal_generator/scenes/signal_gen_scene_mco.c (100%) rename applications/{plugins => external}/signal_generator/scenes/signal_gen_scene_pwm.c (100%) rename applications/{plugins => external}/signal_generator/scenes/signal_gen_scene_start.c (100%) rename applications/{plugins => external}/signal_generator/signal_gen_10px.png (100%) rename applications/{plugins => external}/signal_generator/signal_gen_app.c (100%) rename applications/{plugins => external}/signal_generator/signal_gen_app_i.h (100%) rename applications/{plugins => external}/signal_generator/views/signal_gen_pwm.c (100%) rename applications/{plugins => external}/signal_generator/views/signal_gen_pwm.h (100%) rename applications/{plugins => external}/snake_game/application.fam (75%) rename applications/{plugins => external}/snake_game/snake_10px.png (100%) rename applications/{plugins => external}/snake_game/snake_game.c (100%) rename applications/{plugins => external}/spi_mem_manager/application.fam (100%) rename applications/{plugins => external}/spi_mem_manager/images/ChipLooking_64x64/frame_01.png (100%) rename applications/{plugins => external}/spi_mem_manager/images/ChipLooking_64x64/frame_02.png (100%) rename applications/{plugins => external}/spi_mem_manager/images/ChipLooking_64x64/frame_03.png (100%) rename applications/{plugins => external}/spi_mem_manager/images/ChipLooking_64x64/frame_rate (100%) rename applications/{plugins => external}/spi_mem_manager/images/Dip8_10px.png (100%) rename applications/{plugins => external}/spi_mem_manager/images/Dip8_32x36.png (100%) rename applications/{plugins => external}/spi_mem_manager/images/DolphinMafia_115x62.png (100%) rename applications/{plugins => external}/spi_mem_manager/images/DolphinNice_96x59.png (100%) rename applications/{plugins => external}/spi_mem_manager/images/SDQuestion_35x43.png (100%) rename applications/{plugins => external}/spi_mem_manager/images/Wiring_SPI_128x64.png (100%) rename applications/{plugins => external}/spi_mem_manager/lib/spi/spi_mem_chip.c (100%) rename applications/{plugins => external}/spi_mem_manager/lib/spi/spi_mem_chip.h (100%) rename applications/{plugins => external}/spi_mem_manager/lib/spi/spi_mem_chip_arr.c (100%) rename applications/{plugins => external}/spi_mem_manager/lib/spi/spi_mem_chip_i.h (100%) rename applications/{plugins => external}/spi_mem_manager/lib/spi/spi_mem_tools.c (100%) rename applications/{plugins => external}/spi_mem_manager/lib/spi/spi_mem_tools.h (100%) rename applications/{plugins => external}/spi_mem_manager/lib/spi/spi_mem_worker.c (100%) rename applications/{plugins => external}/spi_mem_manager/lib/spi/spi_mem_worker.h (100%) rename applications/{plugins => external}/spi_mem_manager/lib/spi/spi_mem_worker_i.h (100%) rename applications/{plugins => external}/spi_mem_manager/lib/spi/spi_mem_worker_modes.c (100%) rename applications/{plugins => external}/spi_mem_manager/scenes/spi_mem_scene.c (100%) rename applications/{plugins => external}/spi_mem_manager/scenes/spi_mem_scene.h (100%) rename applications/{plugins => external}/spi_mem_manager/scenes/spi_mem_scene_about.c (100%) rename applications/{plugins => external}/spi_mem_manager/scenes/spi_mem_scene_chip_detect.c (100%) rename applications/{plugins => external}/spi_mem_manager/scenes/spi_mem_scene_chip_detect_fail.c (100%) rename applications/{plugins => external}/spi_mem_manager/scenes/spi_mem_scene_chip_detected.c (100%) rename applications/{plugins => external}/spi_mem_manager/scenes/spi_mem_scene_chip_error.c (100%) rename applications/{plugins => external}/spi_mem_manager/scenes/spi_mem_scene_config.h (100%) rename applications/{plugins => external}/spi_mem_manager/scenes/spi_mem_scene_delete_confirm.c (100%) rename applications/{plugins => external}/spi_mem_manager/scenes/spi_mem_scene_erase.c (100%) rename applications/{plugins => external}/spi_mem_manager/scenes/spi_mem_scene_file_info.c (100%) rename applications/{plugins => external}/spi_mem_manager/scenes/spi_mem_scene_read.c (100%) rename applications/{plugins => external}/spi_mem_manager/scenes/spi_mem_scene_read_filename.c (100%) rename applications/{plugins => external}/spi_mem_manager/scenes/spi_mem_scene_saved_file_menu.c (100%) rename applications/{plugins => external}/spi_mem_manager/scenes/spi_mem_scene_select_file.c (100%) rename applications/{plugins => external}/spi_mem_manager/scenes/spi_mem_scene_select_model.c (100%) rename applications/{plugins => external}/spi_mem_manager/scenes/spi_mem_scene_select_vendor.c (100%) rename applications/{plugins => external}/spi_mem_manager/scenes/spi_mem_scene_start.c (100%) rename applications/{plugins => external}/spi_mem_manager/scenes/spi_mem_scene_storage_error.c (100%) rename applications/{plugins => external}/spi_mem_manager/scenes/spi_mem_scene_success.c (100%) rename applications/{plugins => external}/spi_mem_manager/scenes/spi_mem_scene_verify.c (100%) rename applications/{plugins => external}/spi_mem_manager/scenes/spi_mem_scene_verify_error.c (100%) rename applications/{plugins => external}/spi_mem_manager/scenes/spi_mem_scene_wiring.c (100%) rename applications/{plugins => external}/spi_mem_manager/scenes/spi_mem_scene_write.c (100%) rename applications/{plugins => external}/spi_mem_manager/spi_mem_app.c (100%) rename applications/{plugins => external}/spi_mem_manager/spi_mem_app.h (100%) rename applications/{plugins => external}/spi_mem_manager/spi_mem_app_i.h (100%) rename applications/{plugins => external}/spi_mem_manager/spi_mem_files.c (100%) rename applications/{plugins => external}/spi_mem_manager/spi_mem_files.h (100%) rename applications/{plugins => external}/spi_mem_manager/tools/README.md (100%) rename applications/{plugins => external}/spi_mem_manager/tools/chiplist/LICENSE (100%) rename applications/{plugins => external}/spi_mem_manager/tools/chiplist/chiplist.xml (100%) rename applications/{plugins => external}/spi_mem_manager/tools/chiplist_convert.py (100%) rename applications/{plugins => external}/spi_mem_manager/views/spi_mem_view_detect.c (100%) rename applications/{plugins => external}/spi_mem_manager/views/spi_mem_view_detect.h (100%) rename applications/{plugins => external}/spi_mem_manager/views/spi_mem_view_progress.c (100%) rename applications/{plugins => external}/spi_mem_manager/views/spi_mem_view_progress.h (100%) rename applications/{plugins => external}/weather_station/application.fam (79%) rename applications/{plugins => external}/weather_station/helpers/weather_station_event.h (100%) rename applications/{plugins => external}/weather_station/helpers/weather_station_types.h (100%) rename applications/{plugins => external}/weather_station/images/Humid_10x15.png (100%) rename applications/{plugins => external}/weather_station/images/Humid_8x13.png (100%) rename applications/{plugins => external}/weather_station/images/Lock_7x8.png (100%) rename applications/{plugins => external}/weather_station/images/Pin_back_arrow_10x8.png (100%) rename applications/{plugins => external}/weather_station/images/Quest_7x8.png (100%) rename applications/{plugins => external}/weather_station/images/Scanning_123x52.png (100%) rename applications/{plugins => external}/weather_station/images/Therm_7x16.png (100%) rename applications/{plugins => external}/weather_station/images/Timer_11x11.png (100%) rename applications/{plugins => external}/weather_station/images/Unlock_7x8.png (100%) rename applications/{plugins => external}/weather_station/images/WarningDolphin_45x42.png (100%) rename applications/{plugins => external}/weather_station/images/station_icon.png (100%) rename applications/{plugins => external}/weather_station/protocols/acurite_592txr.c (100%) rename applications/{plugins => external}/weather_station/protocols/acurite_592txr.h (100%) rename applications/{plugins => external}/weather_station/protocols/acurite_606tx.c (100%) rename applications/{plugins => external}/weather_station/protocols/acurite_606tx.h (100%) rename applications/{plugins => external}/weather_station/protocols/acurite_609txc.c (100%) rename applications/{plugins => external}/weather_station/protocols/acurite_609txc.h (100%) rename applications/{plugins => external}/weather_station/protocols/ambient_weather.c (100%) rename applications/{plugins => external}/weather_station/protocols/ambient_weather.h (100%) rename applications/{plugins => external}/weather_station/protocols/auriol_hg0601a.c (100%) rename applications/{plugins => external}/weather_station/protocols/auriol_hg0601a.h (100%) rename applications/{plugins => external}/weather_station/protocols/gt_wt_02.c (100%) rename applications/{plugins => external}/weather_station/protocols/gt_wt_02.h (100%) rename applications/{plugins => external}/weather_station/protocols/gt_wt_03.c (100%) rename applications/{plugins => external}/weather_station/protocols/gt_wt_03.h (100%) rename applications/{plugins => external}/weather_station/protocols/infactory.c (100%) rename applications/{plugins => external}/weather_station/protocols/infactory.h (100%) rename applications/{plugins => external}/weather_station/protocols/lacrosse_tx.c (100%) rename applications/{plugins => external}/weather_station/protocols/lacrosse_tx.h (100%) rename applications/{plugins => external}/weather_station/protocols/lacrosse_tx141thbv2.c (100%) rename applications/{plugins => external}/weather_station/protocols/lacrosse_tx141thbv2.h (100%) rename applications/{plugins => external}/weather_station/protocols/nexus_th.c (100%) rename applications/{plugins => external}/weather_station/protocols/nexus_th.h (100%) rename applications/{plugins => external}/weather_station/protocols/oregon2.c (100%) rename applications/{plugins => external}/weather_station/protocols/oregon2.h (100%) rename applications/{plugins => external}/weather_station/protocols/oregon_v1.c (100%) rename applications/{plugins => external}/weather_station/protocols/oregon_v1.h (100%) rename applications/{plugins => external}/weather_station/protocols/protocol_items.c (100%) rename applications/{plugins => external}/weather_station/protocols/protocol_items.h (100%) rename applications/{plugins => external}/weather_station/protocols/thermopro_tx4.c (100%) rename applications/{plugins => external}/weather_station/protocols/thermopro_tx4.h (100%) rename applications/{plugins => external}/weather_station/protocols/tx_8300.c (100%) rename applications/{plugins => external}/weather_station/protocols/tx_8300.h (100%) rename applications/{plugins => external}/weather_station/protocols/ws_generic.c (100%) rename applications/{plugins => external}/weather_station/protocols/ws_generic.h (100%) rename applications/{plugins => external}/weather_station/scenes/weather_station_receiver.c (100%) rename applications/{plugins => external}/weather_station/scenes/weather_station_scene.c (100%) rename applications/{plugins => external}/weather_station/scenes/weather_station_scene.h (100%) rename applications/{plugins => external}/weather_station/scenes/weather_station_scene_about.c (100%) rename applications/{plugins => external}/weather_station/scenes/weather_station_scene_config.h (100%) rename applications/{plugins => external}/weather_station/scenes/weather_station_scene_receiver_config.c (100%) rename applications/{plugins => external}/weather_station/scenes/weather_station_scene_receiver_info.c (100%) rename applications/{plugins => external}/weather_station/scenes/weather_station_scene_start.c (100%) rename applications/{plugins => external}/weather_station/views/weather_station_receiver.c (100%) rename applications/{plugins => external}/weather_station/views/weather_station_receiver.h (100%) rename applications/{plugins => external}/weather_station/views/weather_station_receiver_info.c (100%) rename applications/{plugins => external}/weather_station/views/weather_station_receiver_info.h (100%) rename applications/{plugins => external}/weather_station/weather_station_10px.png (100%) rename applications/{plugins => external}/weather_station/weather_station_app.c (100%) rename applications/{plugins => external}/weather_station/weather_station_app_i.c (100%) rename applications/{plugins => external}/weather_station/weather_station_app_i.h (100%) rename applications/{plugins => external}/weather_station/weather_station_history.c (100%) rename applications/{plugins => external}/weather_station/weather_station_history.h (100%) delete mode 100644 applications/main/fap_loader/elf_cpp/elf_hashtable.cpp delete mode 100644 applications/main/fap_loader/elf_cpp/elf_hashtable.h delete mode 100644 applications/main/fap_loader/elf_cpp/elf_hashtable_checks.hpp delete mode 100644 applications/main/fap_loader/elf_cpp/elf_hashtable_entry.h delete mode 100644 applications/plugins/application.fam create mode 100644 applications/services/loader/firmware_api/firmware_api.cpp create mode 100644 applications/services/loader/firmware_api/firmware_api.h create mode 100644 lib/flipper_application/api_hashtable/api_hashtable.cpp create mode 100644 lib/flipper_application/api_hashtable/api_hashtable.h rename {applications/main/fap_loader/elf_cpp => lib/flipper_application/api_hashtable}/compilesort.hpp (99%) create mode 100644 lib/flipper_application/plugins/composite_resolver.c create mode 100644 lib/flipper_application/plugins/composite_resolver.h create mode 100644 lib/flipper_application/plugins/plugin_manager.c create mode 100644 lib/flipper_application/plugins/plugin_manager.h create mode 100644 scripts/distfap.py create mode 100644 scripts/fbt/fapassets.py delete mode 100644 scripts/requirements.txt diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 69f8289f9e3..0bc130243b5 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -22,8 +22,8 @@ /applications/main/subghz/ @skotopes @DrZlo13 @hedger @Skorpionm /applications/main/u2f/ @skotopes @DrZlo13 @hedger @nminaylov -/applications/plugins/bt_hid_app/ @skotopes @DrZlo13 @hedger @gornekich -/applications/plugins/picopass/ @skotopes @DrZlo13 @hedger @gornekich +/applications/external/bt_hid_app/ @skotopes @DrZlo13 @hedger @gornekich +/applications/external/picopass/ @skotopes @DrZlo13 @hedger @gornekich /applications/services/bt/ @skotopes @DrZlo13 @hedger @gornekich /applications/services/cli/ @skotopes @DrZlo13 @hedger @nminaylov diff --git a/.gitmodules b/.gitmodules index a97e0933a0c..56368cd588f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -28,6 +28,6 @@ [submodule "lib/cxxheaderparser"] path = lib/cxxheaderparser url = https://github.com/robotpy/cxxheaderparser.git -[submodule "applications/plugins/dap_link/lib/free-dap"] - path = applications/plugins/dap_link/lib/free-dap +[submodule "applications/external/dap_link/lib/free-dap"] + path = applications/external/dap_link/lib/free-dap url = https://github.com/ataradov/free-dap.git diff --git a/.pvsoptions b/.pvsoptions index ca1b2b572a4..6b22aed765e 100644 --- a/.pvsoptions +++ b/.pvsoptions @@ -1 +1 @@ ---ignore-ccache -C gccarm --rules-config .pvsconfig -e lib/fatfs -e lib/fnv1a-hash -e lib/FreeRTOS-Kernel -e lib/heatshrink -e lib/libusb_stm32 -e lib/littlefs -e lib/mbedtls -e lib/micro-ecc -e lib/microtar -e lib/mlib -e lib/qrcode -e lib/ST25RFAL002 -e lib/STM32CubeWB -e lib/u8g2 -e lib/nanopb -e */arm-none-eabi/* -e applications/plugins/dap_link/lib/free-dap +--ignore-ccache -C gccarm --rules-config .pvsconfig -e lib/fatfs -e lib/fnv1a-hash -e lib/FreeRTOS-Kernel -e lib/heatshrink -e lib/libusb_stm32 -e lib/littlefs -e lib/mbedtls -e lib/micro-ecc -e lib/microtar -e lib/mlib -e lib/qrcode -e lib/ST25RFAL002 -e lib/STM32CubeWB -e lib/u8g2 -e lib/nanopb -e */arm-none-eabi/* -e applications/external/dap_link/lib/free-dap diff --git a/SConstruct b/SConstruct index 62e37dfdcbc..090a92599bb 100644 --- a/SConstruct +++ b/SConstruct @@ -139,34 +139,33 @@ if GetOption("fullenv") or any( basic_dist = distenv.DistCommand("fw_dist", distenv["DIST_DEPENDS"]) distenv.Default(basic_dist) -dist_dir = distenv.GetProjetDirName() +dist_dir_name = distenv.GetProjetDirName() +dist_dir = distenv.Dir(f"#/dist/{dist_dir_name}") +external_apps_artifacts = firmware_env["FW_EXTAPPS"] +external_app_list = external_apps_artifacts.application_map.values() + fap_dist = [ distenv.Install( - distenv.Dir(f"#/dist/{dist_dir}/apps/debug_elf"), - list( - app_artifact.debug - for app_artifact in firmware_env["FW_EXTAPPS"].applications.values() - ), + dist_dir.Dir("debug_elf"), + list(app_artifact.debug for app_artifact in external_app_list), ), *( distenv.Install( - f"#/dist/{dist_dir}/apps/{app_artifact.app.fap_category}", - app_artifact.compact[0], + dist_dir.File(dist_entry[1]).dir, + app_artifact.compact, ) - for app_artifact in firmware_env["FW_EXTAPPS"].applications.values() + for app_artifact in external_app_list + for dist_entry in app_artifact.dist_entries ), ] Depends( fap_dist, - list( - app_artifact.validator - for app_artifact in firmware_env["FW_EXTAPPS"].applications.values() - ), + list(app_artifact.validator for app_artifact in external_app_list), ) Alias("fap_dist", fap_dist) # distenv.Default(fap_dist) -distenv.Depends(firmware_env["FW_RESOURCES"], firmware_env["FW_EXTAPPS"].resources_dist) +distenv.Depends(firmware_env["FW_RESOURCES"], external_apps_artifacts.resources_dist) # Copy all faps to device diff --git a/applications/examples/application.fam b/applications/examples/application.fam index 8556714c902..347411fac1c 100644 --- a/applications/examples/application.fam +++ b/applications/examples/application.fam @@ -1,3 +1,4 @@ +# Placeholder App( appid="example_apps", name="Example apps bundle", diff --git a/applications/examples/example_plugins/application.fam b/applications/examples/example_plugins/application.fam new file mode 100644 index 00000000000..a6e3c20781e --- /dev/null +++ b/applications/examples/example_plugins/application.fam @@ -0,0 +1,31 @@ +App( + appid="example_plugins", + name="Example: App w/plugin", + apptype=FlipperAppType.EXTERNAL, + entry_point="example_plugins_app", + stack_size=2 * 1024, + fap_category="Examples", +) + +App( + appid="example_plugins_multi", + name="Example: App w/plugins", + apptype=FlipperAppType.EXTERNAL, + entry_point="example_plugins_multi_app", + stack_size=2 * 1024, + fap_category="Examples", +) + +App( + appid="example_plugin1", + apptype=FlipperAppType.PLUGIN, + entry_point="example_plugin1_ep", + requires=["example_plugins", "example_plugins_multi"], +) + +App( + appid="example_plugin2", + apptype=FlipperAppType.PLUGIN, + entry_point="example_plugin2_ep", + requires=["example_plugins_multi"], +) diff --git a/applications/examples/example_plugins/example_plugins.c b/applications/examples/example_plugins/example_plugins.c new file mode 100644 index 00000000000..acc5903ad81 --- /dev/null +++ b/applications/examples/example_plugins/example_plugins.c @@ -0,0 +1,70 @@ +/* + * An example of a plugin host application. + * Loads a single plugin and calls its methods. + */ + +#include "plugin_interface.h" + +#include + +#include +#include +#include + +#define TAG "example_plugins" + +int32_t example_plugins_app(void* p) { + UNUSED(p); + + FURI_LOG_I(TAG, "Starting"); + + Storage* storage = furi_record_open(RECORD_STORAGE); + + FlipperApplication* app = flipper_application_alloc(storage, firmware_api_interface); + + do { + FlipperApplicationPreloadStatus preload_res = + flipper_application_preload(app, APP_DATA_PATH("plugins/example_plugin1.fal")); + + if(preload_res != FlipperApplicationPreloadStatusSuccess) { + FURI_LOG_E(TAG, "Failed to preload plugin"); + break; + } + + if(!flipper_application_is_plugin(app)) { + FURI_LOG_E(TAG, "Plugin file is not a library"); + break; + } + + FlipperApplicationLoadStatus load_status = flipper_application_map_to_memory(app); + if(load_status != FlipperApplicationLoadStatusSuccess) { + FURI_LOG_E(TAG, "Failed to load plugin file"); + break; + } + + const FlipperAppPluginDescriptor* app_descriptor = + flipper_application_plugin_get_descriptor(app); + + FURI_LOG_I( + TAG, + "Loaded plugin for appid '%s', API %lu", + app_descriptor->appid, + app_descriptor->ep_api_version); + + furi_check(app_descriptor->ep_api_version == PLUGIN_API_VERSION); + furi_check(strcmp(app_descriptor->appid, PLUGIN_APP_ID) == 0); + + const ExamplePlugin* plugin = app_descriptor->entry_point; + + FURI_LOG_I(TAG, "Plugin name: %s", plugin->name); + FURI_LOG_I(TAG, "Plugin method1: %d", plugin->method1()); + FURI_LOG_I(TAG, "Plugin method2(7,8): %d", plugin->method2(7, 8)); + FURI_LOG_I(TAG, "Plugin method2(1337,228): %d", plugin->method2(1337, 228)); + } while(false); + flipper_application_free(app); + + furi_record_close(RECORD_STORAGE); + FURI_LOG_I(TAG, "Goodbye!"); + + return 0; +} diff --git a/applications/examples/example_plugins/example_plugins_multi.c b/applications/examples/example_plugins/example_plugins_multi.c new file mode 100644 index 00000000000..12eba01c10f --- /dev/null +++ b/applications/examples/example_plugins/example_plugins_multi.c @@ -0,0 +1,43 @@ +/* + * An example of an advanced plugin host application. + * It uses PluginManager to load all plugins from a directory + */ + +#include "plugin_interface.h" + +#include +#include +#include + +#include + +#define TAG "example_plugins" + +int32_t example_plugins_multi_app(void* p) { + UNUSED(p); + + FURI_LOG_I(TAG, "Starting"); + + PluginManager* manager = + plugin_manager_alloc(PLUGIN_APP_ID, PLUGIN_API_VERSION, firmware_api_interface); + + if(plugin_manager_load_all(manager, APP_DATA_PATH("plugins")) != PluginManagerErrorNone) { + FURI_LOG_E(TAG, "Failed to load all libs"); + return 0; + } + + uint32_t plugin_count = plugin_manager_get_count(manager); + FURI_LOG_I(TAG, "Loaded %lu plugin(s)", plugin_count); + + for(uint32_t i = 0; i < plugin_count; i++) { + const ExamplePlugin* plugin = plugin_manager_get_ep(manager, i); + FURI_LOG_I(TAG, "plugin name: %s", plugin->name); + FURI_LOG_I(TAG, "plugin method1: %d", plugin->method1()); + FURI_LOG_I(TAG, "plugin method2(7,8): %d", plugin->method2(7, 8)); + } + + plugin_manager_free(manager); + FURI_LOG_I(TAG, "Goodbye!"); + + return 0; +} diff --git a/applications/examples/example_plugins/plugin1.c b/applications/examples/example_plugins/plugin1.c new file mode 100644 index 00000000000..15621935339 --- /dev/null +++ b/applications/examples/example_plugins/plugin1.c @@ -0,0 +1,32 @@ +/* A simple plugin implementing example_plugins application's plugin interface */ + +#include "plugin_interface.h" + +#include + +static int example_plugin1_method1() { + return 42; +} + +static int example_plugin1_method2(int arg1, int arg2) { + return arg1 + arg2; +} + +/* Actual implementation of app<>plugin interface */ +static const ExamplePlugin example_plugin1 = { + .name = "Demo App Plugin 1", + .method1 = &example_plugin1_method1, + .method2 = &example_plugin1_method2, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor example_plugin1_descriptor = { + .appid = PLUGIN_APP_ID, + .ep_api_version = PLUGIN_API_VERSION, + .entry_point = &example_plugin1, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* example_plugin1_ep() { + return &example_plugin1_descriptor; +} diff --git a/applications/examples/example_plugins/plugin2.c b/applications/examples/example_plugins/plugin2.c new file mode 100644 index 00000000000..0b774dad21a --- /dev/null +++ b/applications/examples/example_plugins/plugin2.c @@ -0,0 +1,32 @@ +/* Second plugin implementing example_plugins application's plugin interface */ + +#include "plugin_interface.h" + +#include + +static int example_plugin2_method1() { + return 1337; +} + +static int example_plugin2_method2(int arg1, int arg2) { + return arg1 - arg2; +} + +/* Actual implementation of app<>plugin interface */ +static const ExamplePlugin example_plugin2 = { + .name = "Demo App Plugin 2", + .method1 = &example_plugin2_method1, + .method2 = &example_plugin2_method2, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor example_plugin2_descriptor = { + .appid = PLUGIN_APP_ID, + .ep_api_version = PLUGIN_API_VERSION, + .entry_point = &example_plugin2, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* example_plugin2_ep() { + return &example_plugin2_descriptor; +} diff --git a/applications/examples/example_plugins/plugin_interface.h b/applications/examples/example_plugins/plugin_interface.h new file mode 100644 index 00000000000..e24bc47bfb1 --- /dev/null +++ b/applications/examples/example_plugins/plugin_interface.h @@ -0,0 +1,12 @@ +#pragma once + +/* Common interface between a plugin and host applicaion */ + +#define PLUGIN_APP_ID "example_plugins" +#define PLUGIN_API_VERSION 1 + +typedef struct { + const char* name; + int (*method1)(); + int (*method2)(int, int); +} ExamplePlugin; diff --git a/applications/examples/example_plugins_advanced/app_api.c b/applications/examples/example_plugins_advanced/app_api.c new file mode 100644 index 00000000000..42b3a1860dd --- /dev/null +++ b/applications/examples/example_plugins_advanced/app_api.c @@ -0,0 +1,25 @@ +#include "app_api.h" + +/* Actual implementation of app's API and its private state */ + +static uint32_t accumulator = 0; + +void app_api_accumulator_set(uint32_t value) { + accumulator = value; +} + +uint32_t app_api_accumulator_get() { + return accumulator; +} + +void app_api_accumulator_add(uint32_t value) { + accumulator += value; +} + +void app_api_accumulator_sub(uint32_t value) { + accumulator -= value; +} + +void app_api_accumulator_mul(uint32_t value) { + accumulator *= value; +} diff --git a/applications/examples/example_plugins_advanced/app_api.h b/applications/examples/example_plugins_advanced/app_api.h new file mode 100644 index 00000000000..7035b79f52b --- /dev/null +++ b/applications/examples/example_plugins_advanced/app_api.h @@ -0,0 +1,25 @@ +#pragma once + +/* + * This file contains an API that is internally implemented by the application + * It is also exposed to plugins to allow them to use the application's API. + */ +#include + +#ifdef __cplusplus +extern "C" { +#endif + +void app_api_accumulator_set(uint32_t value); + +uint32_t app_api_accumulator_get(); + +void app_api_accumulator_add(uint32_t value); + +void app_api_accumulator_sub(uint32_t value); + +void app_api_accumulator_mul(uint32_t value); + +#ifdef __cplusplus +} +#endif diff --git a/applications/examples/example_plugins_advanced/app_api_interface.h b/applications/examples/example_plugins_advanced/app_api_interface.h new file mode 100644 index 00000000000..d0db44c4aa0 --- /dev/null +++ b/applications/examples/example_plugins_advanced/app_api_interface.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +/* + * Resolver interface with private application's symbols. + * Implementation is contained in app_api_table.c + */ +extern const ElfApiInterface* const application_api_interface; \ No newline at end of file diff --git a/applications/examples/example_plugins_advanced/app_api_table.cpp b/applications/examples/example_plugins_advanced/app_api_table.cpp new file mode 100644 index 00000000000..aacfb8c181d --- /dev/null +++ b/applications/examples/example_plugins_advanced/app_api_table.cpp @@ -0,0 +1,27 @@ +#include +#include + +/* + * This file contains an implementation of a symbol table + * with private app's symbols. It is used by composite API resolver + * to load plugins that use internal application's APIs. + */ +#include "app_api_table_i.h" + +static_assert(!has_hash_collisions(app_api_table), "Detected API method hash collision!"); + +constexpr HashtableApiInterface applicaton_hashtable_api_interface{ + { + .api_version_major = 0, + .api_version_minor = 0, + /* generic resolver using pre-sorted array */ + .resolver_callback = &elf_resolve_from_hashtable, + }, + /* pointers to application's API table boundaries */ + .table_cbegin = app_api_table.cbegin(), + .table_cend = app_api_table.cend(), +}; + +/* Casting to generic resolver to use in Composite API resolver */ +extern "C" const ElfApiInterface* const application_api_interface = + &applicaton_hashtable_api_interface; diff --git a/applications/examples/example_plugins_advanced/app_api_table_i.h b/applications/examples/example_plugins_advanced/app_api_table_i.h new file mode 100644 index 00000000000..17cc8be5f93 --- /dev/null +++ b/applications/examples/example_plugins_advanced/app_api_table_i.h @@ -0,0 +1,13 @@ +#include "app_api.h" + +/* + * A list of app's private functions and objects to expose for plugins. + * It is used to generate a table of symbols for import resolver to use. + * TBD: automatically generate this table from app's header files + */ +static constexpr auto app_api_table = sort(create_array_t( + API_METHOD(app_api_accumulator_set, void, (uint32_t)), + API_METHOD(app_api_accumulator_get, uint32_t, ()), + API_METHOD(app_api_accumulator_add, void, (uint32_t)), + API_METHOD(app_api_accumulator_sub, void, (uint32_t)), + API_METHOD(app_api_accumulator_mul, void, (uint32_t)))); \ No newline at end of file diff --git a/applications/examples/example_plugins_advanced/application.fam b/applications/examples/example_plugins_advanced/application.fam new file mode 100644 index 00000000000..d40c0dde295 --- /dev/null +++ b/applications/examples/example_plugins_advanced/application.fam @@ -0,0 +1,24 @@ +App( + appid="example_advanced_plugins", + name="Example: advanced plugins", + apptype=FlipperAppType.EXTERNAL, + entry_point="example_advanced_plugins_app", + stack_size=2 * 1024, + fap_category="Examples", +) + +App( + appid="advanced_plugin1", + apptype=FlipperAppType.PLUGIN, + entry_point="advanced_plugin1_ep", + requires=["example_advanced_plugins"], + sources=["plugin1.c"], +) + +App( + appid="advanced_plugin2", + apptype=FlipperAppType.PLUGIN, + entry_point="advanced_plugin2_ep", + requires=["example_advanced_plugins"], + sources=["plugin2.c"], +) diff --git a/applications/examples/example_plugins_advanced/example_advanced_plugins.c b/applications/examples/example_plugins_advanced/example_advanced_plugins.c new file mode 100644 index 00000000000..f27b0a08416 --- /dev/null +++ b/applications/examples/example_plugins_advanced/example_advanced_plugins.c @@ -0,0 +1,48 @@ +#include "app_api.h" +#include "plugin_interface.h" +#include "app_api_interface.h" + +#include +#include +#include + +#include + +#define TAG "example_advanced_plugins" + +int32_t example_advanced_plugins_app(void* p) { + UNUSED(p); + + FURI_LOG_I(TAG, "Starting"); + + CompositeApiResolver* resolver = composite_api_resolver_alloc(); + composite_api_resolver_add(resolver, firmware_api_interface); + composite_api_resolver_add(resolver, application_api_interface); + + PluginManager* manager = plugin_manager_alloc( + PLUGIN_APP_ID, PLUGIN_API_VERSION, composite_api_resolver_get(resolver)); + + do { + if(plugin_manager_load_all(manager, APP_DATA_PATH("plugins")) != PluginManagerErrorNone) { + FURI_LOG_E(TAG, "Failed to load all libs"); + break; + } + + uint32_t plugin_count = plugin_manager_get_count(manager); + FURI_LOG_I(TAG, "Loaded libs: %lu", plugin_count); + + for(uint32_t i = 0; i < plugin_count; i++) { + const AdvancedPlugin* plugin = plugin_manager_get_ep(manager, i); + FURI_LOG_I(TAG, "plugin name: %s. Calling methods", plugin->name); + plugin->method1(228); + plugin->method2(); + FURI_LOG_I(TAG, "Accumulator: %lu", app_api_accumulator_get()); + } + } while(0); + + plugin_manager_free(manager); + composite_api_resolver_free(resolver); + FURI_LOG_I(TAG, "Goodbye!"); + + return 0; +} diff --git a/applications/examples/example_plugins_advanced/plugin1.c b/applications/examples/example_plugins_advanced/plugin1.c new file mode 100644 index 00000000000..bf0ab50b42e --- /dev/null +++ b/applications/examples/example_plugins_advanced/plugin1.c @@ -0,0 +1,40 @@ +/* + * This plugin uses both firmware's API interface and private application headers. + * It can be loaded by a plugin manager that uses CompoundApiInterface, + * which combines both interfaces. + */ + +#include "app_api.h" +#include "plugin_interface.h" + +#include +#include + +static void advanced_plugin1_method1(int arg1) { + /* This function is implemented inside host application */ + app_api_accumulator_add(arg1); +} + +static void advanced_plugin1_method2() { + /* Accumulator value is stored inside host application */ + FURI_LOG_I("TEST", "Plugin 1, accumulator: %lu", app_api_accumulator_get()); +} + +/* Actual implementation of app<>plugin interface */ +static const AdvancedPlugin advanced_plugin1 = { + .name = "Advanced Plugin 1", + .method1 = &advanced_plugin1_method1, + .method2 = &advanced_plugin1_method2, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor advanced_plugin1_descriptor = { + .appid = PLUGIN_APP_ID, + .ep_api_version = PLUGIN_API_VERSION, + .entry_point = &advanced_plugin1, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* advanced_plugin1_ep() { + return &advanced_plugin1_descriptor; +} diff --git a/applications/examples/example_plugins_advanced/plugin2.c b/applications/examples/example_plugins_advanced/plugin2.c new file mode 100644 index 00000000000..f0b2f726db3 --- /dev/null +++ b/applications/examples/example_plugins_advanced/plugin2.c @@ -0,0 +1,40 @@ +/* + * This plugin uses both firmware's API interface and private application headers. + * It can be loaded by a plugin manager that uses CompoundApiInterface, + * which combines both interfaces. + */ + +#include "app_api.h" +#include "plugin_interface.h" + +#include +#include + +static void advanced_plugin2_method1(int arg1) { + /* This function is implemented inside host application */ + app_api_accumulator_mul(arg1); +} + +static void advanced_plugin2_method2() { + /* Accumulator value is stored inside host application */ + FURI_LOG_I("TEST", "Plugin 2, accumulator: %lu", app_api_accumulator_get()); +} + +/* Actual implementation of app<>plugin interface */ +static const AdvancedPlugin advanced_plugin2 = { + .name = "Advanced Plugin 2", + .method1 = &advanced_plugin2_method1, + .method2 = &advanced_plugin2_method2, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor advanced_plugin2_descriptor = { + .appid = PLUGIN_APP_ID, + .ep_api_version = PLUGIN_API_VERSION, + .entry_point = &advanced_plugin2, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* advanced_plugin2_ep() { + return &advanced_plugin2_descriptor; +} diff --git a/applications/examples/example_plugins_advanced/plugin_interface.h b/applications/examples/example_plugins_advanced/plugin_interface.h new file mode 100644 index 00000000000..e8b5a22d647 --- /dev/null +++ b/applications/examples/example_plugins_advanced/plugin_interface.h @@ -0,0 +1,12 @@ +#pragma once + +/* Common interface between a plugin and host applicaion */ + +#define PLUGIN_APP_ID "example_plugins_advanced" +#define PLUGIN_API_VERSION 1 + +typedef struct { + const char* name; + void (*method1)(int); + void (*method2)(); +} AdvancedPlugin; diff --git a/applications/external/application.fam b/applications/external/application.fam new file mode 100644 index 00000000000..12dc1cc1ae5 --- /dev/null +++ b/applications/external/application.fam @@ -0,0 +1,6 @@ +# Placeholder +App( + appid="external_apps", + name="External apps bundle", + apptype=FlipperAppType.METAPACKAGE, +) diff --git a/applications/plugins/clock/application.fam b/applications/external/clock/application.fam similarity index 82% rename from applications/plugins/clock/application.fam rename to applications/external/clock/application.fam index 590f5dfe09d..a6a2eff3e23 100644 --- a/applications/plugins/clock/application.fam +++ b/applications/external/clock/application.fam @@ -1,7 +1,7 @@ App( appid="clock", name="Clock", - apptype=FlipperAppType.PLUGIN, + apptype=FlipperAppType.EXTERNAL, entry_point="clock_app", requires=["gui"], stack_size=2 * 1024, diff --git a/applications/plugins/clock/clock.png b/applications/external/clock/clock.png similarity index 100% rename from applications/plugins/clock/clock.png rename to applications/external/clock/clock.png diff --git a/applications/plugins/clock/clock_app.c b/applications/external/clock/clock_app.c similarity index 100% rename from applications/plugins/clock/clock_app.c rename to applications/external/clock/clock_app.c diff --git a/applications/plugins/dap_link/README.md b/applications/external/dap_link/README.md similarity index 100% rename from applications/plugins/dap_link/README.md rename to applications/external/dap_link/README.md diff --git a/applications/plugins/dap_link/application.fam b/applications/external/dap_link/application.fam similarity index 92% rename from applications/plugins/dap_link/application.fam rename to applications/external/dap_link/application.fam index 711e4833d05..0171438030f 100644 --- a/applications/plugins/dap_link/application.fam +++ b/applications/external/dap_link/application.fam @@ -1,7 +1,7 @@ App( appid="dap_link", name="DAP Link", - apptype=FlipperAppType.PLUGIN, + apptype=FlipperAppType.EXTERNAL, entry_point="dap_link_app", requires=[ "gui", diff --git a/applications/plugins/dap_link/dap_config.h b/applications/external/dap_link/dap_config.h similarity index 100% rename from applications/plugins/dap_link/dap_config.h rename to applications/external/dap_link/dap_config.h diff --git a/applications/plugins/dap_link/dap_link.c b/applications/external/dap_link/dap_link.c similarity index 100% rename from applications/plugins/dap_link/dap_link.c rename to applications/external/dap_link/dap_link.c diff --git a/applications/plugins/dap_link/dap_link.h b/applications/external/dap_link/dap_link.h similarity index 100% rename from applications/plugins/dap_link/dap_link.h rename to applications/external/dap_link/dap_link.h diff --git a/applications/plugins/dap_link/dap_link.png b/applications/external/dap_link/dap_link.png similarity index 100% rename from applications/plugins/dap_link/dap_link.png rename to applications/external/dap_link/dap_link.png diff --git a/applications/plugins/dap_link/gui/dap_gui.c b/applications/external/dap_link/gui/dap_gui.c similarity index 100% rename from applications/plugins/dap_link/gui/dap_gui.c rename to applications/external/dap_link/gui/dap_gui.c diff --git a/applications/plugins/dap_link/gui/dap_gui.h b/applications/external/dap_link/gui/dap_gui.h similarity index 100% rename from applications/plugins/dap_link/gui/dap_gui.h rename to applications/external/dap_link/gui/dap_gui.h diff --git a/applications/plugins/dap_link/gui/dap_gui_custom_event.h b/applications/external/dap_link/gui/dap_gui_custom_event.h similarity index 100% rename from applications/plugins/dap_link/gui/dap_gui_custom_event.h rename to applications/external/dap_link/gui/dap_gui_custom_event.h diff --git a/applications/plugins/dap_link/gui/dap_gui_i.h b/applications/external/dap_link/gui/dap_gui_i.h similarity index 100% rename from applications/plugins/dap_link/gui/dap_gui_i.h rename to applications/external/dap_link/gui/dap_gui_i.h diff --git a/applications/plugins/dap_link/gui/scenes/config/dap_scene.c b/applications/external/dap_link/gui/scenes/config/dap_scene.c similarity index 100% rename from applications/plugins/dap_link/gui/scenes/config/dap_scene.c rename to applications/external/dap_link/gui/scenes/config/dap_scene.c diff --git a/applications/plugins/dap_link/gui/scenes/config/dap_scene.h b/applications/external/dap_link/gui/scenes/config/dap_scene.h similarity index 100% rename from applications/plugins/dap_link/gui/scenes/config/dap_scene.h rename to applications/external/dap_link/gui/scenes/config/dap_scene.h diff --git a/applications/plugins/dap_link/gui/scenes/config/dap_scene_config.h b/applications/external/dap_link/gui/scenes/config/dap_scene_config.h similarity index 100% rename from applications/plugins/dap_link/gui/scenes/config/dap_scene_config.h rename to applications/external/dap_link/gui/scenes/config/dap_scene_config.h diff --git a/applications/plugins/dap_link/gui/scenes/dap_scene_about.c b/applications/external/dap_link/gui/scenes/dap_scene_about.c similarity index 100% rename from applications/plugins/dap_link/gui/scenes/dap_scene_about.c rename to applications/external/dap_link/gui/scenes/dap_scene_about.c diff --git a/applications/plugins/dap_link/gui/scenes/dap_scene_config.c b/applications/external/dap_link/gui/scenes/dap_scene_config.c similarity index 100% rename from applications/plugins/dap_link/gui/scenes/dap_scene_config.c rename to applications/external/dap_link/gui/scenes/dap_scene_config.c diff --git a/applications/plugins/dap_link/gui/scenes/dap_scene_help.c b/applications/external/dap_link/gui/scenes/dap_scene_help.c similarity index 100% rename from applications/plugins/dap_link/gui/scenes/dap_scene_help.c rename to applications/external/dap_link/gui/scenes/dap_scene_help.c diff --git a/applications/plugins/dap_link/gui/scenes/dap_scene_main.c b/applications/external/dap_link/gui/scenes/dap_scene_main.c similarity index 100% rename from applications/plugins/dap_link/gui/scenes/dap_scene_main.c rename to applications/external/dap_link/gui/scenes/dap_scene_main.c diff --git a/applications/plugins/dap_link/gui/views/dap_main_view.c b/applications/external/dap_link/gui/views/dap_main_view.c similarity index 100% rename from applications/plugins/dap_link/gui/views/dap_main_view.c rename to applications/external/dap_link/gui/views/dap_main_view.c diff --git a/applications/plugins/dap_link/gui/views/dap_main_view.h b/applications/external/dap_link/gui/views/dap_main_view.h similarity index 100% rename from applications/plugins/dap_link/gui/views/dap_main_view.h rename to applications/external/dap_link/gui/views/dap_main_view.h diff --git a/applications/plugins/dap_link/icons/ActiveConnection_50x64.png b/applications/external/dap_link/icons/ActiveConnection_50x64.png similarity index 100% rename from applications/plugins/dap_link/icons/ActiveConnection_50x64.png rename to applications/external/dap_link/icons/ActiveConnection_50x64.png diff --git a/applications/plugins/dap_link/icons/ArrowDownEmpty_12x18.png b/applications/external/dap_link/icons/ArrowDownEmpty_12x18.png similarity index 100% rename from applications/plugins/dap_link/icons/ArrowDownEmpty_12x18.png rename to applications/external/dap_link/icons/ArrowDownEmpty_12x18.png diff --git a/applications/plugins/dap_link/icons/ArrowDownFilled_12x18.png b/applications/external/dap_link/icons/ArrowDownFilled_12x18.png similarity index 100% rename from applications/plugins/dap_link/icons/ArrowDownFilled_12x18.png rename to applications/external/dap_link/icons/ArrowDownFilled_12x18.png diff --git a/applications/plugins/dap_link/icons/ArrowUpEmpty_12x18.png b/applications/external/dap_link/icons/ArrowUpEmpty_12x18.png similarity index 100% rename from applications/plugins/dap_link/icons/ArrowUpEmpty_12x18.png rename to applications/external/dap_link/icons/ArrowUpEmpty_12x18.png diff --git a/applications/plugins/dap_link/icons/ArrowUpFilled_12x18.png b/applications/external/dap_link/icons/ArrowUpFilled_12x18.png similarity index 100% rename from applications/plugins/dap_link/icons/ArrowUpFilled_12x18.png rename to applications/external/dap_link/icons/ArrowUpFilled_12x18.png diff --git a/applications/plugins/dap_link/lib/free-dap b/applications/external/dap_link/lib/free-dap similarity index 100% rename from applications/plugins/dap_link/lib/free-dap rename to applications/external/dap_link/lib/free-dap diff --git a/applications/plugins/dap_link/usb/dap_v2_usb.c b/applications/external/dap_link/usb/dap_v2_usb.c similarity index 100% rename from applications/plugins/dap_link/usb/dap_v2_usb.c rename to applications/external/dap_link/usb/dap_v2_usb.c diff --git a/applications/plugins/dap_link/usb/dap_v2_usb.h b/applications/external/dap_link/usb/dap_v2_usb.h similarity index 100% rename from applications/plugins/dap_link/usb/dap_v2_usb.h rename to applications/external/dap_link/usb/dap_v2_usb.h diff --git a/applications/plugins/dap_link/usb/usb_winusb.h b/applications/external/dap_link/usb/usb_winusb.h similarity index 100% rename from applications/plugins/dap_link/usb/usb_winusb.h rename to applications/external/dap_link/usb/usb_winusb.h diff --git a/applications/plugins/hid_app/application.fam b/applications/external/hid_app/application.fam similarity index 86% rename from applications/plugins/hid_app/application.fam rename to applications/external/hid_app/application.fam index b6e4e3bf8f3..a9d8305ddda 100644 --- a/applications/plugins/hid_app/application.fam +++ b/applications/external/hid_app/application.fam @@ -1,7 +1,7 @@ App( appid="hid_usb", name="Remote", - apptype=FlipperAppType.PLUGIN, + apptype=FlipperAppType.EXTERNAL, entry_point="hid_usb_app", stack_size=1 * 1024, fap_category="USB", @@ -14,7 +14,7 @@ App( App( appid="hid_ble", name="Remote", - apptype=FlipperAppType.PLUGIN, + apptype=FlipperAppType.EXTERNAL, entry_point="hid_ble_app", stack_size=1 * 1024, fap_category="Bluetooth", diff --git a/applications/plugins/hid_app/assets/Arr_dwn_7x9.png b/applications/external/hid_app/assets/Arr_dwn_7x9.png similarity index 100% rename from applications/plugins/hid_app/assets/Arr_dwn_7x9.png rename to applications/external/hid_app/assets/Arr_dwn_7x9.png diff --git a/applications/plugins/hid_app/assets/Arr_up_7x9.png b/applications/external/hid_app/assets/Arr_up_7x9.png similarity index 100% rename from applications/plugins/hid_app/assets/Arr_up_7x9.png rename to applications/external/hid_app/assets/Arr_up_7x9.png diff --git a/applications/plugins/hid_app/assets/Ble_connected_15x15.png b/applications/external/hid_app/assets/Ble_connected_15x15.png similarity index 100% rename from applications/plugins/hid_app/assets/Ble_connected_15x15.png rename to applications/external/hid_app/assets/Ble_connected_15x15.png diff --git a/applications/plugins/hid_app/assets/Ble_disconnected_15x15.png b/applications/external/hid_app/assets/Ble_disconnected_15x15.png similarity index 100% rename from applications/plugins/hid_app/assets/Ble_disconnected_15x15.png rename to applications/external/hid_app/assets/Ble_disconnected_15x15.png diff --git a/applications/plugins/hid_app/assets/ButtonDown_7x4.png b/applications/external/hid_app/assets/ButtonDown_7x4.png similarity index 100% rename from applications/plugins/hid_app/assets/ButtonDown_7x4.png rename to applications/external/hid_app/assets/ButtonDown_7x4.png diff --git a/applications/plugins/hid_app/assets/ButtonF10_5x8.png b/applications/external/hid_app/assets/ButtonF10_5x8.png similarity index 100% rename from applications/plugins/hid_app/assets/ButtonF10_5x8.png rename to applications/external/hid_app/assets/ButtonF10_5x8.png diff --git a/applications/plugins/hid_app/assets/ButtonF11_5x8.png b/applications/external/hid_app/assets/ButtonF11_5x8.png similarity index 100% rename from applications/plugins/hid_app/assets/ButtonF11_5x8.png rename to applications/external/hid_app/assets/ButtonF11_5x8.png diff --git a/applications/plugins/hid_app/assets/ButtonF12_5x8.png b/applications/external/hid_app/assets/ButtonF12_5x8.png similarity index 100% rename from applications/plugins/hid_app/assets/ButtonF12_5x8.png rename to applications/external/hid_app/assets/ButtonF12_5x8.png diff --git a/applications/plugins/hid_app/assets/ButtonF1_5x8.png b/applications/external/hid_app/assets/ButtonF1_5x8.png similarity index 100% rename from applications/plugins/hid_app/assets/ButtonF1_5x8.png rename to applications/external/hid_app/assets/ButtonF1_5x8.png diff --git a/applications/plugins/hid_app/assets/ButtonF2_5x8.png b/applications/external/hid_app/assets/ButtonF2_5x8.png similarity index 100% rename from applications/plugins/hid_app/assets/ButtonF2_5x8.png rename to applications/external/hid_app/assets/ButtonF2_5x8.png diff --git a/applications/plugins/hid_app/assets/ButtonF3_5x8.png b/applications/external/hid_app/assets/ButtonF3_5x8.png similarity index 100% rename from applications/plugins/hid_app/assets/ButtonF3_5x8.png rename to applications/external/hid_app/assets/ButtonF3_5x8.png diff --git a/applications/plugins/hid_app/assets/ButtonF4_5x8.png b/applications/external/hid_app/assets/ButtonF4_5x8.png similarity index 100% rename from applications/plugins/hid_app/assets/ButtonF4_5x8.png rename to applications/external/hid_app/assets/ButtonF4_5x8.png diff --git a/applications/plugins/hid_app/assets/ButtonF5_5x8.png b/applications/external/hid_app/assets/ButtonF5_5x8.png similarity index 100% rename from applications/plugins/hid_app/assets/ButtonF5_5x8.png rename to applications/external/hid_app/assets/ButtonF5_5x8.png diff --git a/applications/plugins/hid_app/assets/ButtonF6_5x8.png b/applications/external/hid_app/assets/ButtonF6_5x8.png similarity index 100% rename from applications/plugins/hid_app/assets/ButtonF6_5x8.png rename to applications/external/hid_app/assets/ButtonF6_5x8.png diff --git a/applications/plugins/hid_app/assets/ButtonF7_5x8.png b/applications/external/hid_app/assets/ButtonF7_5x8.png similarity index 100% rename from applications/plugins/hid_app/assets/ButtonF7_5x8.png rename to applications/external/hid_app/assets/ButtonF7_5x8.png diff --git a/applications/plugins/hid_app/assets/ButtonF8_5x8.png b/applications/external/hid_app/assets/ButtonF8_5x8.png similarity index 100% rename from applications/plugins/hid_app/assets/ButtonF8_5x8.png rename to applications/external/hid_app/assets/ButtonF8_5x8.png diff --git a/applications/plugins/hid_app/assets/ButtonF9_5x8.png b/applications/external/hid_app/assets/ButtonF9_5x8.png similarity index 100% rename from applications/plugins/hid_app/assets/ButtonF9_5x8.png rename to applications/external/hid_app/assets/ButtonF9_5x8.png diff --git a/applications/plugins/hid_app/assets/ButtonLeft_4x7.png b/applications/external/hid_app/assets/ButtonLeft_4x7.png similarity index 100% rename from applications/plugins/hid_app/assets/ButtonLeft_4x7.png rename to applications/external/hid_app/assets/ButtonLeft_4x7.png diff --git a/applications/plugins/hid_app/assets/ButtonRight_4x7.png b/applications/external/hid_app/assets/ButtonRight_4x7.png similarity index 100% rename from applications/plugins/hid_app/assets/ButtonRight_4x7.png rename to applications/external/hid_app/assets/ButtonRight_4x7.png diff --git a/applications/plugins/hid_app/assets/ButtonUp_7x4.png b/applications/external/hid_app/assets/ButtonUp_7x4.png similarity index 100% rename from applications/plugins/hid_app/assets/ButtonUp_7x4.png rename to applications/external/hid_app/assets/ButtonUp_7x4.png diff --git a/applications/plugins/hid_app/assets/Button_18x18.png b/applications/external/hid_app/assets/Button_18x18.png similarity index 100% rename from applications/plugins/hid_app/assets/Button_18x18.png rename to applications/external/hid_app/assets/Button_18x18.png diff --git a/applications/plugins/hid_app/assets/Circles_47x47.png b/applications/external/hid_app/assets/Circles_47x47.png similarity index 100% rename from applications/plugins/hid_app/assets/Circles_47x47.png rename to applications/external/hid_app/assets/Circles_47x47.png diff --git a/applications/plugins/hid_app/assets/Left_mouse_icon_9x9.png b/applications/external/hid_app/assets/Left_mouse_icon_9x9.png similarity index 100% rename from applications/plugins/hid_app/assets/Left_mouse_icon_9x9.png rename to applications/external/hid_app/assets/Left_mouse_icon_9x9.png diff --git a/applications/plugins/hid_app/assets/Like_def_11x9.png b/applications/external/hid_app/assets/Like_def_11x9.png similarity index 100% rename from applications/plugins/hid_app/assets/Like_def_11x9.png rename to applications/external/hid_app/assets/Like_def_11x9.png diff --git a/applications/plugins/hid_app/assets/Like_pressed_17x17.png b/applications/external/hid_app/assets/Like_pressed_17x17.png similarity index 100% rename from applications/plugins/hid_app/assets/Like_pressed_17x17.png rename to applications/external/hid_app/assets/Like_pressed_17x17.png diff --git a/applications/plugins/hid_app/assets/Ok_btn_9x9.png b/applications/external/hid_app/assets/Ok_btn_9x9.png similarity index 100% rename from applications/plugins/hid_app/assets/Ok_btn_9x9.png rename to applications/external/hid_app/assets/Ok_btn_9x9.png diff --git a/applications/plugins/hid_app/assets/Ok_btn_pressed_13x13.png b/applications/external/hid_app/assets/Ok_btn_pressed_13x13.png similarity index 100% rename from applications/plugins/hid_app/assets/Ok_btn_pressed_13x13.png rename to applications/external/hid_app/assets/Ok_btn_pressed_13x13.png diff --git a/applications/plugins/hid_app/assets/Pin_arrow_down_7x9.png b/applications/external/hid_app/assets/Pin_arrow_down_7x9.png similarity index 100% rename from applications/plugins/hid_app/assets/Pin_arrow_down_7x9.png rename to applications/external/hid_app/assets/Pin_arrow_down_7x9.png diff --git a/applications/plugins/hid_app/assets/Pin_arrow_left_9x7.png b/applications/external/hid_app/assets/Pin_arrow_left_9x7.png similarity index 100% rename from applications/plugins/hid_app/assets/Pin_arrow_left_9x7.png rename to applications/external/hid_app/assets/Pin_arrow_left_9x7.png diff --git a/applications/plugins/hid_app/assets/Pin_arrow_right_9x7.png b/applications/external/hid_app/assets/Pin_arrow_right_9x7.png similarity index 100% rename from applications/plugins/hid_app/assets/Pin_arrow_right_9x7.png rename to applications/external/hid_app/assets/Pin_arrow_right_9x7.png diff --git a/applications/plugins/hid_app/assets/Pin_arrow_up_7x9.png b/applications/external/hid_app/assets/Pin_arrow_up_7x9.png similarity index 100% rename from applications/plugins/hid_app/assets/Pin_arrow_up_7x9.png rename to applications/external/hid_app/assets/Pin_arrow_up_7x9.png diff --git a/applications/plugins/hid_app/assets/Pin_back_arrow_10x8.png b/applications/external/hid_app/assets/Pin_back_arrow_10x8.png similarity index 100% rename from applications/plugins/hid_app/assets/Pin_back_arrow_10x8.png rename to applications/external/hid_app/assets/Pin_back_arrow_10x8.png diff --git a/applications/plugins/hid_app/assets/Pressed_Button_13x13.png b/applications/external/hid_app/assets/Pressed_Button_13x13.png similarity index 100% rename from applications/plugins/hid_app/assets/Pressed_Button_13x13.png rename to applications/external/hid_app/assets/Pressed_Button_13x13.png diff --git a/applications/plugins/hid_app/assets/Right_mouse_icon_9x9.png b/applications/external/hid_app/assets/Right_mouse_icon_9x9.png similarity index 100% rename from applications/plugins/hid_app/assets/Right_mouse_icon_9x9.png rename to applications/external/hid_app/assets/Right_mouse_icon_9x9.png diff --git a/applications/plugins/hid_app/assets/Space_65x18.png b/applications/external/hid_app/assets/Space_65x18.png similarity index 100% rename from applications/plugins/hid_app/assets/Space_65x18.png rename to applications/external/hid_app/assets/Space_65x18.png diff --git a/applications/plugins/hid_app/assets/Voldwn_6x6.png b/applications/external/hid_app/assets/Voldwn_6x6.png similarity index 100% rename from applications/plugins/hid_app/assets/Voldwn_6x6.png rename to applications/external/hid_app/assets/Voldwn_6x6.png diff --git a/applications/plugins/hid_app/assets/Volup_8x6.png b/applications/external/hid_app/assets/Volup_8x6.png similarity index 100% rename from applications/plugins/hid_app/assets/Volup_8x6.png rename to applications/external/hid_app/assets/Volup_8x6.png diff --git a/applications/plugins/hid_app/hid.c b/applications/external/hid_app/hid.c similarity index 100% rename from applications/plugins/hid_app/hid.c rename to applications/external/hid_app/hid.c diff --git a/applications/plugins/hid_app/hid.h b/applications/external/hid_app/hid.h similarity index 100% rename from applications/plugins/hid_app/hid.h rename to applications/external/hid_app/hid.h diff --git a/applications/plugins/hid_app/hid_ble_10px.png b/applications/external/hid_app/hid_ble_10px.png similarity index 100% rename from applications/plugins/hid_app/hid_ble_10px.png rename to applications/external/hid_app/hid_ble_10px.png diff --git a/applications/plugins/hid_app/hid_usb_10px.png b/applications/external/hid_app/hid_usb_10px.png similarity index 100% rename from applications/plugins/hid_app/hid_usb_10px.png rename to applications/external/hid_app/hid_usb_10px.png diff --git a/applications/plugins/hid_app/views.h b/applications/external/hid_app/views.h similarity index 100% rename from applications/plugins/hid_app/views.h rename to applications/external/hid_app/views.h diff --git a/applications/plugins/hid_app/views/hid_keyboard.c b/applications/external/hid_app/views/hid_keyboard.c similarity index 100% rename from applications/plugins/hid_app/views/hid_keyboard.c rename to applications/external/hid_app/views/hid_keyboard.c diff --git a/applications/plugins/hid_app/views/hid_keyboard.h b/applications/external/hid_app/views/hid_keyboard.h similarity index 100% rename from applications/plugins/hid_app/views/hid_keyboard.h rename to applications/external/hid_app/views/hid_keyboard.h diff --git a/applications/plugins/hid_app/views/hid_keynote.c b/applications/external/hid_app/views/hid_keynote.c similarity index 100% rename from applications/plugins/hid_app/views/hid_keynote.c rename to applications/external/hid_app/views/hid_keynote.c diff --git a/applications/plugins/hid_app/views/hid_keynote.h b/applications/external/hid_app/views/hid_keynote.h similarity index 100% rename from applications/plugins/hid_app/views/hid_keynote.h rename to applications/external/hid_app/views/hid_keynote.h diff --git a/applications/plugins/hid_app/views/hid_media.c b/applications/external/hid_app/views/hid_media.c similarity index 100% rename from applications/plugins/hid_app/views/hid_media.c rename to applications/external/hid_app/views/hid_media.c diff --git a/applications/plugins/hid_app/views/hid_media.h b/applications/external/hid_app/views/hid_media.h similarity index 100% rename from applications/plugins/hid_app/views/hid_media.h rename to applications/external/hid_app/views/hid_media.h diff --git a/applications/plugins/hid_app/views/hid_mouse.c b/applications/external/hid_app/views/hid_mouse.c similarity index 100% rename from applications/plugins/hid_app/views/hid_mouse.c rename to applications/external/hid_app/views/hid_mouse.c diff --git a/applications/plugins/hid_app/views/hid_mouse.h b/applications/external/hid_app/views/hid_mouse.h similarity index 100% rename from applications/plugins/hid_app/views/hid_mouse.h rename to applications/external/hid_app/views/hid_mouse.h diff --git a/applications/plugins/hid_app/views/hid_mouse_jiggler.c b/applications/external/hid_app/views/hid_mouse_jiggler.c similarity index 100% rename from applications/plugins/hid_app/views/hid_mouse_jiggler.c rename to applications/external/hid_app/views/hid_mouse_jiggler.c diff --git a/applications/plugins/hid_app/views/hid_mouse_jiggler.h b/applications/external/hid_app/views/hid_mouse_jiggler.h similarity index 100% rename from applications/plugins/hid_app/views/hid_mouse_jiggler.h rename to applications/external/hid_app/views/hid_mouse_jiggler.h diff --git a/applications/plugins/hid_app/views/hid_tiktok.c b/applications/external/hid_app/views/hid_tiktok.c similarity index 100% rename from applications/plugins/hid_app/views/hid_tiktok.c rename to applications/external/hid_app/views/hid_tiktok.c diff --git a/applications/plugins/hid_app/views/hid_tiktok.h b/applications/external/hid_app/views/hid_tiktok.h similarity index 100% rename from applications/plugins/hid_app/views/hid_tiktok.h rename to applications/external/hid_app/views/hid_tiktok.h diff --git a/applications/plugins/music_player/application.fam b/applications/external/music_player/application.fam similarity index 87% rename from applications/plugins/music_player/application.fam rename to applications/external/music_player/application.fam index c51abf19449..3414c0a482d 100644 --- a/applications/plugins/music_player/application.fam +++ b/applications/external/music_player/application.fam @@ -1,9 +1,8 @@ App( appid="music_player", name="Music Player", - apptype=FlipperAppType.PLUGIN, + apptype=FlipperAppType.EXTERNAL, entry_point="music_player_app", - cdefines=["APP_MUSIC_PLAYER"], requires=[ "gui", "dialogs", diff --git a/applications/plugins/music_player/icons/music_10px.png b/applications/external/music_player/icons/music_10px.png similarity index 100% rename from applications/plugins/music_player/icons/music_10px.png rename to applications/external/music_player/icons/music_10px.png diff --git a/applications/plugins/music_player/music_player.c b/applications/external/music_player/music_player.c similarity index 100% rename from applications/plugins/music_player/music_player.c rename to applications/external/music_player/music_player.c diff --git a/applications/plugins/music_player/music_player_cli.c b/applications/external/music_player/music_player_cli.c similarity index 100% rename from applications/plugins/music_player/music_player_cli.c rename to applications/external/music_player/music_player_cli.c diff --git a/applications/plugins/music_player/music_player_worker.c b/applications/external/music_player/music_player_worker.c similarity index 100% rename from applications/plugins/music_player/music_player_worker.c rename to applications/external/music_player/music_player_worker.c diff --git a/applications/plugins/music_player/music_player_worker.h b/applications/external/music_player/music_player_worker.h similarity index 100% rename from applications/plugins/music_player/music_player_worker.h rename to applications/external/music_player/music_player_worker.h diff --git a/applications/plugins/nfc_magic/application.fam b/applications/external/nfc_magic/application.fam similarity index 100% rename from applications/plugins/nfc_magic/application.fam rename to applications/external/nfc_magic/application.fam diff --git a/applications/plugins/nfc_magic/assets/DolphinCommon_56x48.png b/applications/external/nfc_magic/assets/DolphinCommon_56x48.png similarity index 100% rename from applications/plugins/nfc_magic/assets/DolphinCommon_56x48.png rename to applications/external/nfc_magic/assets/DolphinCommon_56x48.png diff --git a/applications/plugins/nfc_magic/assets/DolphinNice_96x59.png b/applications/external/nfc_magic/assets/DolphinNice_96x59.png similarity index 100% rename from applications/plugins/nfc_magic/assets/DolphinNice_96x59.png rename to applications/external/nfc_magic/assets/DolphinNice_96x59.png diff --git a/applications/plugins/nfc_magic/assets/Loading_24.png b/applications/external/nfc_magic/assets/Loading_24.png similarity index 100% rename from applications/plugins/nfc_magic/assets/Loading_24.png rename to applications/external/nfc_magic/assets/Loading_24.png diff --git a/applications/plugins/nfc_magic/assets/NFC_manual_60x50.png b/applications/external/nfc_magic/assets/NFC_manual_60x50.png similarity index 100% rename from applications/plugins/nfc_magic/assets/NFC_manual_60x50.png rename to applications/external/nfc_magic/assets/NFC_manual_60x50.png diff --git a/applications/plugins/nfc_magic/lib/magic/magic.c b/applications/external/nfc_magic/lib/magic/magic.c similarity index 100% rename from applications/plugins/nfc_magic/lib/magic/magic.c rename to applications/external/nfc_magic/lib/magic/magic.c diff --git a/applications/plugins/nfc_magic/lib/magic/magic.h b/applications/external/nfc_magic/lib/magic/magic.h similarity index 100% rename from applications/plugins/nfc_magic/lib/magic/magic.h rename to applications/external/nfc_magic/lib/magic/magic.h diff --git a/applications/plugins/nfc_magic/nfc_magic.c b/applications/external/nfc_magic/nfc_magic.c similarity index 100% rename from applications/plugins/nfc_magic/nfc_magic.c rename to applications/external/nfc_magic/nfc_magic.c diff --git a/applications/plugins/nfc_magic/nfc_magic.h b/applications/external/nfc_magic/nfc_magic.h similarity index 100% rename from applications/plugins/nfc_magic/nfc_magic.h rename to applications/external/nfc_magic/nfc_magic.h diff --git a/applications/plugins/nfc_magic/nfc_magic_i.h b/applications/external/nfc_magic/nfc_magic_i.h similarity index 100% rename from applications/plugins/nfc_magic/nfc_magic_i.h rename to applications/external/nfc_magic/nfc_magic_i.h diff --git a/applications/plugins/nfc_magic/nfc_magic_worker.c b/applications/external/nfc_magic/nfc_magic_worker.c similarity index 100% rename from applications/plugins/nfc_magic/nfc_magic_worker.c rename to applications/external/nfc_magic/nfc_magic_worker.c diff --git a/applications/plugins/nfc_magic/nfc_magic_worker.h b/applications/external/nfc_magic/nfc_magic_worker.h similarity index 100% rename from applications/plugins/nfc_magic/nfc_magic_worker.h rename to applications/external/nfc_magic/nfc_magic_worker.h diff --git a/applications/plugins/nfc_magic/nfc_magic_worker_i.h b/applications/external/nfc_magic/nfc_magic_worker_i.h similarity index 100% rename from applications/plugins/nfc_magic/nfc_magic_worker_i.h rename to applications/external/nfc_magic/nfc_magic_worker_i.h diff --git a/applications/plugins/nfc_magic/scenes/nfc_magic_scene.c b/applications/external/nfc_magic/scenes/nfc_magic_scene.c similarity index 100% rename from applications/plugins/nfc_magic/scenes/nfc_magic_scene.c rename to applications/external/nfc_magic/scenes/nfc_magic_scene.c diff --git a/applications/plugins/nfc_magic/scenes/nfc_magic_scene.h b/applications/external/nfc_magic/scenes/nfc_magic_scene.h similarity index 100% rename from applications/plugins/nfc_magic/scenes/nfc_magic_scene.h rename to applications/external/nfc_magic/scenes/nfc_magic_scene.h diff --git a/applications/plugins/nfc_magic/scenes/nfc_magic_scene_check.c b/applications/external/nfc_magic/scenes/nfc_magic_scene_check.c similarity index 100% rename from applications/plugins/nfc_magic/scenes/nfc_magic_scene_check.c rename to applications/external/nfc_magic/scenes/nfc_magic_scene_check.c diff --git a/applications/plugins/nfc_magic/scenes/nfc_magic_scene_config.h b/applications/external/nfc_magic/scenes/nfc_magic_scene_config.h similarity index 100% rename from applications/plugins/nfc_magic/scenes/nfc_magic_scene_config.h rename to applications/external/nfc_magic/scenes/nfc_magic_scene_config.h diff --git a/applications/plugins/nfc_magic/scenes/nfc_magic_scene_file_select.c b/applications/external/nfc_magic/scenes/nfc_magic_scene_file_select.c similarity index 100% rename from applications/plugins/nfc_magic/scenes/nfc_magic_scene_file_select.c rename to applications/external/nfc_magic/scenes/nfc_magic_scene_file_select.c diff --git a/applications/plugins/nfc_magic/scenes/nfc_magic_scene_magic_info.c b/applications/external/nfc_magic/scenes/nfc_magic_scene_magic_info.c similarity index 100% rename from applications/plugins/nfc_magic/scenes/nfc_magic_scene_magic_info.c rename to applications/external/nfc_magic/scenes/nfc_magic_scene_magic_info.c diff --git a/applications/plugins/nfc_magic/scenes/nfc_magic_scene_not_magic.c b/applications/external/nfc_magic/scenes/nfc_magic_scene_not_magic.c similarity index 100% rename from applications/plugins/nfc_magic/scenes/nfc_magic_scene_not_magic.c rename to applications/external/nfc_magic/scenes/nfc_magic_scene_not_magic.c diff --git a/applications/plugins/nfc_magic/scenes/nfc_magic_scene_start.c b/applications/external/nfc_magic/scenes/nfc_magic_scene_start.c similarity index 100% rename from applications/plugins/nfc_magic/scenes/nfc_magic_scene_start.c rename to applications/external/nfc_magic/scenes/nfc_magic_scene_start.c diff --git a/applications/plugins/nfc_magic/scenes/nfc_magic_scene_success.c b/applications/external/nfc_magic/scenes/nfc_magic_scene_success.c similarity index 100% rename from applications/plugins/nfc_magic/scenes/nfc_magic_scene_success.c rename to applications/external/nfc_magic/scenes/nfc_magic_scene_success.c diff --git a/applications/plugins/nfc_magic/scenes/nfc_magic_scene_wipe.c b/applications/external/nfc_magic/scenes/nfc_magic_scene_wipe.c similarity index 100% rename from applications/plugins/nfc_magic/scenes/nfc_magic_scene_wipe.c rename to applications/external/nfc_magic/scenes/nfc_magic_scene_wipe.c diff --git a/applications/plugins/nfc_magic/scenes/nfc_magic_scene_wipe_fail.c b/applications/external/nfc_magic/scenes/nfc_magic_scene_wipe_fail.c similarity index 100% rename from applications/plugins/nfc_magic/scenes/nfc_magic_scene_wipe_fail.c rename to applications/external/nfc_magic/scenes/nfc_magic_scene_wipe_fail.c diff --git a/applications/plugins/nfc_magic/scenes/nfc_magic_scene_write.c b/applications/external/nfc_magic/scenes/nfc_magic_scene_write.c similarity index 100% rename from applications/plugins/nfc_magic/scenes/nfc_magic_scene_write.c rename to applications/external/nfc_magic/scenes/nfc_magic_scene_write.c diff --git a/applications/plugins/nfc_magic/scenes/nfc_magic_scene_write_confirm.c b/applications/external/nfc_magic/scenes/nfc_magic_scene_write_confirm.c similarity index 100% rename from applications/plugins/nfc_magic/scenes/nfc_magic_scene_write_confirm.c rename to applications/external/nfc_magic/scenes/nfc_magic_scene_write_confirm.c diff --git a/applications/plugins/nfc_magic/scenes/nfc_magic_scene_write_fail.c b/applications/external/nfc_magic/scenes/nfc_magic_scene_write_fail.c similarity index 100% rename from applications/plugins/nfc_magic/scenes/nfc_magic_scene_write_fail.c rename to applications/external/nfc_magic/scenes/nfc_magic_scene_write_fail.c diff --git a/applications/plugins/nfc_magic/scenes/nfc_magic_scene_wrong_card.c b/applications/external/nfc_magic/scenes/nfc_magic_scene_wrong_card.c similarity index 100% rename from applications/plugins/nfc_magic/scenes/nfc_magic_scene_wrong_card.c rename to applications/external/nfc_magic/scenes/nfc_magic_scene_wrong_card.c diff --git a/applications/plugins/picopass/125_10px.png b/applications/external/picopass/125_10px.png similarity index 100% rename from applications/plugins/picopass/125_10px.png rename to applications/external/picopass/125_10px.png diff --git a/applications/plugins/picopass/application.fam b/applications/external/picopass/application.fam similarity index 100% rename from applications/plugins/picopass/application.fam rename to applications/external/picopass/application.fam diff --git a/applications/plugins/picopass/helpers/iclass_elite_dict.c b/applications/external/picopass/helpers/iclass_elite_dict.c similarity index 100% rename from applications/plugins/picopass/helpers/iclass_elite_dict.c rename to applications/external/picopass/helpers/iclass_elite_dict.c diff --git a/applications/plugins/picopass/helpers/iclass_elite_dict.h b/applications/external/picopass/helpers/iclass_elite_dict.h similarity index 100% rename from applications/plugins/picopass/helpers/iclass_elite_dict.h rename to applications/external/picopass/helpers/iclass_elite_dict.h diff --git a/applications/plugins/picopass/icons/DolphinMafia_115x62.png b/applications/external/picopass/icons/DolphinMafia_115x62.png similarity index 100% rename from applications/plugins/picopass/icons/DolphinMafia_115x62.png rename to applications/external/picopass/icons/DolphinMafia_115x62.png diff --git a/applications/plugins/picopass/icons/DolphinNice_96x59.png b/applications/external/picopass/icons/DolphinNice_96x59.png similarity index 100% rename from applications/plugins/picopass/icons/DolphinNice_96x59.png rename to applications/external/picopass/icons/DolphinNice_96x59.png diff --git a/applications/plugins/picopass/icons/Nfc_10px.png b/applications/external/picopass/icons/Nfc_10px.png similarity index 100% rename from applications/plugins/picopass/icons/Nfc_10px.png rename to applications/external/picopass/icons/Nfc_10px.png diff --git a/applications/plugins/picopass/icons/RFIDDolphinReceive_97x61.png b/applications/external/picopass/icons/RFIDDolphinReceive_97x61.png similarity index 100% rename from applications/plugins/picopass/icons/RFIDDolphinReceive_97x61.png rename to applications/external/picopass/icons/RFIDDolphinReceive_97x61.png diff --git a/applications/plugins/picopass/icons/RFIDDolphinSend_97x61.png b/applications/external/picopass/icons/RFIDDolphinSend_97x61.png similarity index 100% rename from applications/plugins/picopass/icons/RFIDDolphinSend_97x61.png rename to applications/external/picopass/icons/RFIDDolphinSend_97x61.png diff --git a/applications/plugins/picopass/lib/loclass/optimized_cipher.c b/applications/external/picopass/lib/loclass/optimized_cipher.c similarity index 100% rename from applications/plugins/picopass/lib/loclass/optimized_cipher.c rename to applications/external/picopass/lib/loclass/optimized_cipher.c diff --git a/applications/plugins/picopass/lib/loclass/optimized_cipher.h b/applications/external/picopass/lib/loclass/optimized_cipher.h similarity index 100% rename from applications/plugins/picopass/lib/loclass/optimized_cipher.h rename to applications/external/picopass/lib/loclass/optimized_cipher.h diff --git a/applications/plugins/picopass/lib/loclass/optimized_cipherutils.c b/applications/external/picopass/lib/loclass/optimized_cipherutils.c similarity index 100% rename from applications/plugins/picopass/lib/loclass/optimized_cipherutils.c rename to applications/external/picopass/lib/loclass/optimized_cipherutils.c diff --git a/applications/plugins/picopass/lib/loclass/optimized_cipherutils.h b/applications/external/picopass/lib/loclass/optimized_cipherutils.h similarity index 100% rename from applications/plugins/picopass/lib/loclass/optimized_cipherutils.h rename to applications/external/picopass/lib/loclass/optimized_cipherutils.h diff --git a/applications/plugins/picopass/lib/loclass/optimized_elite.c b/applications/external/picopass/lib/loclass/optimized_elite.c similarity index 100% rename from applications/plugins/picopass/lib/loclass/optimized_elite.c rename to applications/external/picopass/lib/loclass/optimized_elite.c diff --git a/applications/plugins/picopass/lib/loclass/optimized_elite.h b/applications/external/picopass/lib/loclass/optimized_elite.h similarity index 100% rename from applications/plugins/picopass/lib/loclass/optimized_elite.h rename to applications/external/picopass/lib/loclass/optimized_elite.h diff --git a/applications/plugins/picopass/lib/loclass/optimized_ikeys.c b/applications/external/picopass/lib/loclass/optimized_ikeys.c similarity index 100% rename from applications/plugins/picopass/lib/loclass/optimized_ikeys.c rename to applications/external/picopass/lib/loclass/optimized_ikeys.c diff --git a/applications/plugins/picopass/lib/loclass/optimized_ikeys.h b/applications/external/picopass/lib/loclass/optimized_ikeys.h similarity index 100% rename from applications/plugins/picopass/lib/loclass/optimized_ikeys.h rename to applications/external/picopass/lib/loclass/optimized_ikeys.h diff --git a/applications/plugins/picopass/picopass.c b/applications/external/picopass/picopass.c similarity index 100% rename from applications/plugins/picopass/picopass.c rename to applications/external/picopass/picopass.c diff --git a/applications/plugins/picopass/picopass.h b/applications/external/picopass/picopass.h similarity index 100% rename from applications/plugins/picopass/picopass.h rename to applications/external/picopass/picopass.h diff --git a/applications/plugins/picopass/picopass_device.c b/applications/external/picopass/picopass_device.c similarity index 100% rename from applications/plugins/picopass/picopass_device.c rename to applications/external/picopass/picopass_device.c diff --git a/applications/plugins/picopass/picopass_device.h b/applications/external/picopass/picopass_device.h similarity index 100% rename from applications/plugins/picopass/picopass_device.h rename to applications/external/picopass/picopass_device.h diff --git a/applications/plugins/picopass/picopass_i.h b/applications/external/picopass/picopass_i.h similarity index 100% rename from applications/plugins/picopass/picopass_i.h rename to applications/external/picopass/picopass_i.h diff --git a/applications/plugins/picopass/picopass_keys.c b/applications/external/picopass/picopass_keys.c similarity index 100% rename from applications/plugins/picopass/picopass_keys.c rename to applications/external/picopass/picopass_keys.c diff --git a/applications/plugins/picopass/picopass_keys.h b/applications/external/picopass/picopass_keys.h similarity index 100% rename from applications/plugins/picopass/picopass_keys.h rename to applications/external/picopass/picopass_keys.h diff --git a/applications/plugins/picopass/picopass_worker.c b/applications/external/picopass/picopass_worker.c similarity index 100% rename from applications/plugins/picopass/picopass_worker.c rename to applications/external/picopass/picopass_worker.c diff --git a/applications/plugins/picopass/picopass_worker.h b/applications/external/picopass/picopass_worker.h similarity index 100% rename from applications/plugins/picopass/picopass_worker.h rename to applications/external/picopass/picopass_worker.h diff --git a/applications/plugins/picopass/picopass_worker_i.h b/applications/external/picopass/picopass_worker_i.h similarity index 100% rename from applications/plugins/picopass/picopass_worker_i.h rename to applications/external/picopass/picopass_worker_i.h diff --git a/applications/plugins/picopass/rfal_picopass.c b/applications/external/picopass/rfal_picopass.c similarity index 100% rename from applications/plugins/picopass/rfal_picopass.c rename to applications/external/picopass/rfal_picopass.c diff --git a/applications/plugins/picopass/rfal_picopass.h b/applications/external/picopass/rfal_picopass.h similarity index 100% rename from applications/plugins/picopass/rfal_picopass.h rename to applications/external/picopass/rfal_picopass.h diff --git a/applications/plugins/picopass/scenes/picopass_scene.c b/applications/external/picopass/scenes/picopass_scene.c similarity index 100% rename from applications/plugins/picopass/scenes/picopass_scene.c rename to applications/external/picopass/scenes/picopass_scene.c diff --git a/applications/plugins/picopass/scenes/picopass_scene.h b/applications/external/picopass/scenes/picopass_scene.h similarity index 100% rename from applications/plugins/picopass/scenes/picopass_scene.h rename to applications/external/picopass/scenes/picopass_scene.h diff --git a/applications/plugins/picopass/scenes/picopass_scene_card_menu.c b/applications/external/picopass/scenes/picopass_scene_card_menu.c similarity index 100% rename from applications/plugins/picopass/scenes/picopass_scene_card_menu.c rename to applications/external/picopass/scenes/picopass_scene_card_menu.c diff --git a/applications/plugins/picopass/scenes/picopass_scene_config.h b/applications/external/picopass/scenes/picopass_scene_config.h similarity index 100% rename from applications/plugins/picopass/scenes/picopass_scene_config.h rename to applications/external/picopass/scenes/picopass_scene_config.h diff --git a/applications/plugins/picopass/scenes/picopass_scene_delete.c b/applications/external/picopass/scenes/picopass_scene_delete.c similarity index 100% rename from applications/plugins/picopass/scenes/picopass_scene_delete.c rename to applications/external/picopass/scenes/picopass_scene_delete.c diff --git a/applications/plugins/picopass/scenes/picopass_scene_delete_success.c b/applications/external/picopass/scenes/picopass_scene_delete_success.c similarity index 100% rename from applications/plugins/picopass/scenes/picopass_scene_delete_success.c rename to applications/external/picopass/scenes/picopass_scene_delete_success.c diff --git a/applications/plugins/picopass/scenes/picopass_scene_device_info.c b/applications/external/picopass/scenes/picopass_scene_device_info.c similarity index 100% rename from applications/plugins/picopass/scenes/picopass_scene_device_info.c rename to applications/external/picopass/scenes/picopass_scene_device_info.c diff --git a/applications/plugins/picopass/scenes/picopass_scene_file_select.c b/applications/external/picopass/scenes/picopass_scene_file_select.c similarity index 100% rename from applications/plugins/picopass/scenes/picopass_scene_file_select.c rename to applications/external/picopass/scenes/picopass_scene_file_select.c diff --git a/applications/plugins/picopass/scenes/picopass_scene_key_menu.c b/applications/external/picopass/scenes/picopass_scene_key_menu.c similarity index 100% rename from applications/plugins/picopass/scenes/picopass_scene_key_menu.c rename to applications/external/picopass/scenes/picopass_scene_key_menu.c diff --git a/applications/plugins/picopass/scenes/picopass_scene_read_card.c b/applications/external/picopass/scenes/picopass_scene_read_card.c similarity index 100% rename from applications/plugins/picopass/scenes/picopass_scene_read_card.c rename to applications/external/picopass/scenes/picopass_scene_read_card.c diff --git a/applications/plugins/picopass/scenes/picopass_scene_read_card_success.c b/applications/external/picopass/scenes/picopass_scene_read_card_success.c similarity index 100% rename from applications/plugins/picopass/scenes/picopass_scene_read_card_success.c rename to applications/external/picopass/scenes/picopass_scene_read_card_success.c diff --git a/applications/plugins/picopass/scenes/picopass_scene_read_factory_success.c b/applications/external/picopass/scenes/picopass_scene_read_factory_success.c similarity index 100% rename from applications/plugins/picopass/scenes/picopass_scene_read_factory_success.c rename to applications/external/picopass/scenes/picopass_scene_read_factory_success.c diff --git a/applications/plugins/picopass/scenes/picopass_scene_save_name.c b/applications/external/picopass/scenes/picopass_scene_save_name.c similarity index 100% rename from applications/plugins/picopass/scenes/picopass_scene_save_name.c rename to applications/external/picopass/scenes/picopass_scene_save_name.c diff --git a/applications/plugins/picopass/scenes/picopass_scene_save_success.c b/applications/external/picopass/scenes/picopass_scene_save_success.c similarity index 100% rename from applications/plugins/picopass/scenes/picopass_scene_save_success.c rename to applications/external/picopass/scenes/picopass_scene_save_success.c diff --git a/applications/plugins/picopass/scenes/picopass_scene_saved_menu.c b/applications/external/picopass/scenes/picopass_scene_saved_menu.c similarity index 100% rename from applications/plugins/picopass/scenes/picopass_scene_saved_menu.c rename to applications/external/picopass/scenes/picopass_scene_saved_menu.c diff --git a/applications/plugins/picopass/scenes/picopass_scene_start.c b/applications/external/picopass/scenes/picopass_scene_start.c similarity index 100% rename from applications/plugins/picopass/scenes/picopass_scene_start.c rename to applications/external/picopass/scenes/picopass_scene_start.c diff --git a/applications/plugins/picopass/scenes/picopass_scene_write_card.c b/applications/external/picopass/scenes/picopass_scene_write_card.c similarity index 100% rename from applications/plugins/picopass/scenes/picopass_scene_write_card.c rename to applications/external/picopass/scenes/picopass_scene_write_card.c diff --git a/applications/plugins/picopass/scenes/picopass_scene_write_card_success.c b/applications/external/picopass/scenes/picopass_scene_write_card_success.c similarity index 100% rename from applications/plugins/picopass/scenes/picopass_scene_write_card_success.c rename to applications/external/picopass/scenes/picopass_scene_write_card_success.c diff --git a/applications/plugins/picopass/scenes/picopass_scene_write_key.c b/applications/external/picopass/scenes/picopass_scene_write_key.c similarity index 100% rename from applications/plugins/picopass/scenes/picopass_scene_write_key.c rename to applications/external/picopass/scenes/picopass_scene_write_key.c diff --git a/applications/plugins/signal_generator/application.fam b/applications/external/signal_generator/application.fam similarity index 78% rename from applications/plugins/signal_generator/application.fam rename to applications/external/signal_generator/application.fam index 60f8deffb9d..094e784cc85 100644 --- a/applications/plugins/signal_generator/application.fam +++ b/applications/external/signal_generator/application.fam @@ -1,9 +1,8 @@ App( appid="signal_generator", name="Signal Generator", - apptype=FlipperAppType.PLUGIN, + apptype=FlipperAppType.EXTERNAL, entry_point="signal_gen_app", - cdefines=["APP_SIGNAL_GEN"], requires=["gui"], stack_size=1 * 1024, order=50, diff --git a/applications/plugins/signal_generator/icons/SmallArrowDown_3x5.png b/applications/external/signal_generator/icons/SmallArrowDown_3x5.png similarity index 100% rename from applications/plugins/signal_generator/icons/SmallArrowDown_3x5.png rename to applications/external/signal_generator/icons/SmallArrowDown_3x5.png diff --git a/applications/plugins/signal_generator/icons/SmallArrowUp_3x5.png b/applications/external/signal_generator/icons/SmallArrowUp_3x5.png similarity index 100% rename from applications/plugins/signal_generator/icons/SmallArrowUp_3x5.png rename to applications/external/signal_generator/icons/SmallArrowUp_3x5.png diff --git a/applications/plugins/signal_generator/scenes/signal_gen_scene.c b/applications/external/signal_generator/scenes/signal_gen_scene.c similarity index 100% rename from applications/plugins/signal_generator/scenes/signal_gen_scene.c rename to applications/external/signal_generator/scenes/signal_gen_scene.c diff --git a/applications/plugins/signal_generator/scenes/signal_gen_scene.h b/applications/external/signal_generator/scenes/signal_gen_scene.h similarity index 100% rename from applications/plugins/signal_generator/scenes/signal_gen_scene.h rename to applications/external/signal_generator/scenes/signal_gen_scene.h diff --git a/applications/plugins/signal_generator/scenes/signal_gen_scene_config.h b/applications/external/signal_generator/scenes/signal_gen_scene_config.h similarity index 100% rename from applications/plugins/signal_generator/scenes/signal_gen_scene_config.h rename to applications/external/signal_generator/scenes/signal_gen_scene_config.h diff --git a/applications/plugins/signal_generator/scenes/signal_gen_scene_mco.c b/applications/external/signal_generator/scenes/signal_gen_scene_mco.c similarity index 100% rename from applications/plugins/signal_generator/scenes/signal_gen_scene_mco.c rename to applications/external/signal_generator/scenes/signal_gen_scene_mco.c diff --git a/applications/plugins/signal_generator/scenes/signal_gen_scene_pwm.c b/applications/external/signal_generator/scenes/signal_gen_scene_pwm.c similarity index 100% rename from applications/plugins/signal_generator/scenes/signal_gen_scene_pwm.c rename to applications/external/signal_generator/scenes/signal_gen_scene_pwm.c diff --git a/applications/plugins/signal_generator/scenes/signal_gen_scene_start.c b/applications/external/signal_generator/scenes/signal_gen_scene_start.c similarity index 100% rename from applications/plugins/signal_generator/scenes/signal_gen_scene_start.c rename to applications/external/signal_generator/scenes/signal_gen_scene_start.c diff --git a/applications/plugins/signal_generator/signal_gen_10px.png b/applications/external/signal_generator/signal_gen_10px.png similarity index 100% rename from applications/plugins/signal_generator/signal_gen_10px.png rename to applications/external/signal_generator/signal_gen_10px.png diff --git a/applications/plugins/signal_generator/signal_gen_app.c b/applications/external/signal_generator/signal_gen_app.c similarity index 100% rename from applications/plugins/signal_generator/signal_gen_app.c rename to applications/external/signal_generator/signal_gen_app.c diff --git a/applications/plugins/signal_generator/signal_gen_app_i.h b/applications/external/signal_generator/signal_gen_app_i.h similarity index 100% rename from applications/plugins/signal_generator/signal_gen_app_i.h rename to applications/external/signal_generator/signal_gen_app_i.h diff --git a/applications/plugins/signal_generator/views/signal_gen_pwm.c b/applications/external/signal_generator/views/signal_gen_pwm.c similarity index 100% rename from applications/plugins/signal_generator/views/signal_gen_pwm.c rename to applications/external/signal_generator/views/signal_gen_pwm.c diff --git a/applications/plugins/signal_generator/views/signal_gen_pwm.h b/applications/external/signal_generator/views/signal_gen_pwm.h similarity index 100% rename from applications/plugins/signal_generator/views/signal_gen_pwm.h rename to applications/external/signal_generator/views/signal_gen_pwm.h diff --git a/applications/plugins/snake_game/application.fam b/applications/external/snake_game/application.fam similarity index 75% rename from applications/plugins/snake_game/application.fam rename to applications/external/snake_game/application.fam index d55f53bb14f..c736a4ddcf4 100644 --- a/applications/plugins/snake_game/application.fam +++ b/applications/external/snake_game/application.fam @@ -1,9 +1,8 @@ App( appid="snake_game", name="Snake Game", - apptype=FlipperAppType.PLUGIN, + apptype=FlipperAppType.EXTERNAL, entry_point="snake_game_app", - cdefines=["APP_SNAKE_GAME"], requires=["gui"], stack_size=1 * 1024, order=30, diff --git a/applications/plugins/snake_game/snake_10px.png b/applications/external/snake_game/snake_10px.png similarity index 100% rename from applications/plugins/snake_game/snake_10px.png rename to applications/external/snake_game/snake_10px.png diff --git a/applications/plugins/snake_game/snake_game.c b/applications/external/snake_game/snake_game.c similarity index 100% rename from applications/plugins/snake_game/snake_game.c rename to applications/external/snake_game/snake_game.c diff --git a/applications/plugins/spi_mem_manager/application.fam b/applications/external/spi_mem_manager/application.fam similarity index 100% rename from applications/plugins/spi_mem_manager/application.fam rename to applications/external/spi_mem_manager/application.fam diff --git a/applications/plugins/spi_mem_manager/images/ChipLooking_64x64/frame_01.png b/applications/external/spi_mem_manager/images/ChipLooking_64x64/frame_01.png similarity index 100% rename from applications/plugins/spi_mem_manager/images/ChipLooking_64x64/frame_01.png rename to applications/external/spi_mem_manager/images/ChipLooking_64x64/frame_01.png diff --git a/applications/plugins/spi_mem_manager/images/ChipLooking_64x64/frame_02.png b/applications/external/spi_mem_manager/images/ChipLooking_64x64/frame_02.png similarity index 100% rename from applications/plugins/spi_mem_manager/images/ChipLooking_64x64/frame_02.png rename to applications/external/spi_mem_manager/images/ChipLooking_64x64/frame_02.png diff --git a/applications/plugins/spi_mem_manager/images/ChipLooking_64x64/frame_03.png b/applications/external/spi_mem_manager/images/ChipLooking_64x64/frame_03.png similarity index 100% rename from applications/plugins/spi_mem_manager/images/ChipLooking_64x64/frame_03.png rename to applications/external/spi_mem_manager/images/ChipLooking_64x64/frame_03.png diff --git a/applications/plugins/spi_mem_manager/images/ChipLooking_64x64/frame_rate b/applications/external/spi_mem_manager/images/ChipLooking_64x64/frame_rate similarity index 100% rename from applications/plugins/spi_mem_manager/images/ChipLooking_64x64/frame_rate rename to applications/external/spi_mem_manager/images/ChipLooking_64x64/frame_rate diff --git a/applications/plugins/spi_mem_manager/images/Dip8_10px.png b/applications/external/spi_mem_manager/images/Dip8_10px.png similarity index 100% rename from applications/plugins/spi_mem_manager/images/Dip8_10px.png rename to applications/external/spi_mem_manager/images/Dip8_10px.png diff --git a/applications/plugins/spi_mem_manager/images/Dip8_32x36.png b/applications/external/spi_mem_manager/images/Dip8_32x36.png similarity index 100% rename from applications/plugins/spi_mem_manager/images/Dip8_32x36.png rename to applications/external/spi_mem_manager/images/Dip8_32x36.png diff --git a/applications/plugins/spi_mem_manager/images/DolphinMafia_115x62.png b/applications/external/spi_mem_manager/images/DolphinMafia_115x62.png similarity index 100% rename from applications/plugins/spi_mem_manager/images/DolphinMafia_115x62.png rename to applications/external/spi_mem_manager/images/DolphinMafia_115x62.png diff --git a/applications/plugins/spi_mem_manager/images/DolphinNice_96x59.png b/applications/external/spi_mem_manager/images/DolphinNice_96x59.png similarity index 100% rename from applications/plugins/spi_mem_manager/images/DolphinNice_96x59.png rename to applications/external/spi_mem_manager/images/DolphinNice_96x59.png diff --git a/applications/plugins/spi_mem_manager/images/SDQuestion_35x43.png b/applications/external/spi_mem_manager/images/SDQuestion_35x43.png similarity index 100% rename from applications/plugins/spi_mem_manager/images/SDQuestion_35x43.png rename to applications/external/spi_mem_manager/images/SDQuestion_35x43.png diff --git a/applications/plugins/spi_mem_manager/images/Wiring_SPI_128x64.png b/applications/external/spi_mem_manager/images/Wiring_SPI_128x64.png similarity index 100% rename from applications/plugins/spi_mem_manager/images/Wiring_SPI_128x64.png rename to applications/external/spi_mem_manager/images/Wiring_SPI_128x64.png diff --git a/applications/plugins/spi_mem_manager/lib/spi/spi_mem_chip.c b/applications/external/spi_mem_manager/lib/spi/spi_mem_chip.c similarity index 100% rename from applications/plugins/spi_mem_manager/lib/spi/spi_mem_chip.c rename to applications/external/spi_mem_manager/lib/spi/spi_mem_chip.c diff --git a/applications/plugins/spi_mem_manager/lib/spi/spi_mem_chip.h b/applications/external/spi_mem_manager/lib/spi/spi_mem_chip.h similarity index 100% rename from applications/plugins/spi_mem_manager/lib/spi/spi_mem_chip.h rename to applications/external/spi_mem_manager/lib/spi/spi_mem_chip.h diff --git a/applications/plugins/spi_mem_manager/lib/spi/spi_mem_chip_arr.c b/applications/external/spi_mem_manager/lib/spi/spi_mem_chip_arr.c similarity index 100% rename from applications/plugins/spi_mem_manager/lib/spi/spi_mem_chip_arr.c rename to applications/external/spi_mem_manager/lib/spi/spi_mem_chip_arr.c diff --git a/applications/plugins/spi_mem_manager/lib/spi/spi_mem_chip_i.h b/applications/external/spi_mem_manager/lib/spi/spi_mem_chip_i.h similarity index 100% rename from applications/plugins/spi_mem_manager/lib/spi/spi_mem_chip_i.h rename to applications/external/spi_mem_manager/lib/spi/spi_mem_chip_i.h diff --git a/applications/plugins/spi_mem_manager/lib/spi/spi_mem_tools.c b/applications/external/spi_mem_manager/lib/spi/spi_mem_tools.c similarity index 100% rename from applications/plugins/spi_mem_manager/lib/spi/spi_mem_tools.c rename to applications/external/spi_mem_manager/lib/spi/spi_mem_tools.c diff --git a/applications/plugins/spi_mem_manager/lib/spi/spi_mem_tools.h b/applications/external/spi_mem_manager/lib/spi/spi_mem_tools.h similarity index 100% rename from applications/plugins/spi_mem_manager/lib/spi/spi_mem_tools.h rename to applications/external/spi_mem_manager/lib/spi/spi_mem_tools.h diff --git a/applications/plugins/spi_mem_manager/lib/spi/spi_mem_worker.c b/applications/external/spi_mem_manager/lib/spi/spi_mem_worker.c similarity index 100% rename from applications/plugins/spi_mem_manager/lib/spi/spi_mem_worker.c rename to applications/external/spi_mem_manager/lib/spi/spi_mem_worker.c diff --git a/applications/plugins/spi_mem_manager/lib/spi/spi_mem_worker.h b/applications/external/spi_mem_manager/lib/spi/spi_mem_worker.h similarity index 100% rename from applications/plugins/spi_mem_manager/lib/spi/spi_mem_worker.h rename to applications/external/spi_mem_manager/lib/spi/spi_mem_worker.h diff --git a/applications/plugins/spi_mem_manager/lib/spi/spi_mem_worker_i.h b/applications/external/spi_mem_manager/lib/spi/spi_mem_worker_i.h similarity index 100% rename from applications/plugins/spi_mem_manager/lib/spi/spi_mem_worker_i.h rename to applications/external/spi_mem_manager/lib/spi/spi_mem_worker_i.h diff --git a/applications/plugins/spi_mem_manager/lib/spi/spi_mem_worker_modes.c b/applications/external/spi_mem_manager/lib/spi/spi_mem_worker_modes.c similarity index 100% rename from applications/plugins/spi_mem_manager/lib/spi/spi_mem_worker_modes.c rename to applications/external/spi_mem_manager/lib/spi/spi_mem_worker_modes.c diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene.c b/applications/external/spi_mem_manager/scenes/spi_mem_scene.c similarity index 100% rename from applications/plugins/spi_mem_manager/scenes/spi_mem_scene.c rename to applications/external/spi_mem_manager/scenes/spi_mem_scene.c diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene.h b/applications/external/spi_mem_manager/scenes/spi_mem_scene.h similarity index 100% rename from applications/plugins/spi_mem_manager/scenes/spi_mem_scene.h rename to applications/external/spi_mem_manager/scenes/spi_mem_scene.h diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_about.c b/applications/external/spi_mem_manager/scenes/spi_mem_scene_about.c similarity index 100% rename from applications/plugins/spi_mem_manager/scenes/spi_mem_scene_about.c rename to applications/external/spi_mem_manager/scenes/spi_mem_scene_about.c diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_chip_detect.c b/applications/external/spi_mem_manager/scenes/spi_mem_scene_chip_detect.c similarity index 100% rename from applications/plugins/spi_mem_manager/scenes/spi_mem_scene_chip_detect.c rename to applications/external/spi_mem_manager/scenes/spi_mem_scene_chip_detect.c diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_chip_detect_fail.c b/applications/external/spi_mem_manager/scenes/spi_mem_scene_chip_detect_fail.c similarity index 100% rename from applications/plugins/spi_mem_manager/scenes/spi_mem_scene_chip_detect_fail.c rename to applications/external/spi_mem_manager/scenes/spi_mem_scene_chip_detect_fail.c diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_chip_detected.c b/applications/external/spi_mem_manager/scenes/spi_mem_scene_chip_detected.c similarity index 100% rename from applications/plugins/spi_mem_manager/scenes/spi_mem_scene_chip_detected.c rename to applications/external/spi_mem_manager/scenes/spi_mem_scene_chip_detected.c diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_chip_error.c b/applications/external/spi_mem_manager/scenes/spi_mem_scene_chip_error.c similarity index 100% rename from applications/plugins/spi_mem_manager/scenes/spi_mem_scene_chip_error.c rename to applications/external/spi_mem_manager/scenes/spi_mem_scene_chip_error.c diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_config.h b/applications/external/spi_mem_manager/scenes/spi_mem_scene_config.h similarity index 100% rename from applications/plugins/spi_mem_manager/scenes/spi_mem_scene_config.h rename to applications/external/spi_mem_manager/scenes/spi_mem_scene_config.h diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_delete_confirm.c b/applications/external/spi_mem_manager/scenes/spi_mem_scene_delete_confirm.c similarity index 100% rename from applications/plugins/spi_mem_manager/scenes/spi_mem_scene_delete_confirm.c rename to applications/external/spi_mem_manager/scenes/spi_mem_scene_delete_confirm.c diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_erase.c b/applications/external/spi_mem_manager/scenes/spi_mem_scene_erase.c similarity index 100% rename from applications/plugins/spi_mem_manager/scenes/spi_mem_scene_erase.c rename to applications/external/spi_mem_manager/scenes/spi_mem_scene_erase.c diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_file_info.c b/applications/external/spi_mem_manager/scenes/spi_mem_scene_file_info.c similarity index 100% rename from applications/plugins/spi_mem_manager/scenes/spi_mem_scene_file_info.c rename to applications/external/spi_mem_manager/scenes/spi_mem_scene_file_info.c diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_read.c b/applications/external/spi_mem_manager/scenes/spi_mem_scene_read.c similarity index 100% rename from applications/plugins/spi_mem_manager/scenes/spi_mem_scene_read.c rename to applications/external/spi_mem_manager/scenes/spi_mem_scene_read.c diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_read_filename.c b/applications/external/spi_mem_manager/scenes/spi_mem_scene_read_filename.c similarity index 100% rename from applications/plugins/spi_mem_manager/scenes/spi_mem_scene_read_filename.c rename to applications/external/spi_mem_manager/scenes/spi_mem_scene_read_filename.c diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_saved_file_menu.c b/applications/external/spi_mem_manager/scenes/spi_mem_scene_saved_file_menu.c similarity index 100% rename from applications/plugins/spi_mem_manager/scenes/spi_mem_scene_saved_file_menu.c rename to applications/external/spi_mem_manager/scenes/spi_mem_scene_saved_file_menu.c diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_select_file.c b/applications/external/spi_mem_manager/scenes/spi_mem_scene_select_file.c similarity index 100% rename from applications/plugins/spi_mem_manager/scenes/spi_mem_scene_select_file.c rename to applications/external/spi_mem_manager/scenes/spi_mem_scene_select_file.c diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_select_model.c b/applications/external/spi_mem_manager/scenes/spi_mem_scene_select_model.c similarity index 100% rename from applications/plugins/spi_mem_manager/scenes/spi_mem_scene_select_model.c rename to applications/external/spi_mem_manager/scenes/spi_mem_scene_select_model.c diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_select_vendor.c b/applications/external/spi_mem_manager/scenes/spi_mem_scene_select_vendor.c similarity index 100% rename from applications/plugins/spi_mem_manager/scenes/spi_mem_scene_select_vendor.c rename to applications/external/spi_mem_manager/scenes/spi_mem_scene_select_vendor.c diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_start.c b/applications/external/spi_mem_manager/scenes/spi_mem_scene_start.c similarity index 100% rename from applications/plugins/spi_mem_manager/scenes/spi_mem_scene_start.c rename to applications/external/spi_mem_manager/scenes/spi_mem_scene_start.c diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_storage_error.c b/applications/external/spi_mem_manager/scenes/spi_mem_scene_storage_error.c similarity index 100% rename from applications/plugins/spi_mem_manager/scenes/spi_mem_scene_storage_error.c rename to applications/external/spi_mem_manager/scenes/spi_mem_scene_storage_error.c diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_success.c b/applications/external/spi_mem_manager/scenes/spi_mem_scene_success.c similarity index 100% rename from applications/plugins/spi_mem_manager/scenes/spi_mem_scene_success.c rename to applications/external/spi_mem_manager/scenes/spi_mem_scene_success.c diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_verify.c b/applications/external/spi_mem_manager/scenes/spi_mem_scene_verify.c similarity index 100% rename from applications/plugins/spi_mem_manager/scenes/spi_mem_scene_verify.c rename to applications/external/spi_mem_manager/scenes/spi_mem_scene_verify.c diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_verify_error.c b/applications/external/spi_mem_manager/scenes/spi_mem_scene_verify_error.c similarity index 100% rename from applications/plugins/spi_mem_manager/scenes/spi_mem_scene_verify_error.c rename to applications/external/spi_mem_manager/scenes/spi_mem_scene_verify_error.c diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_wiring.c b/applications/external/spi_mem_manager/scenes/spi_mem_scene_wiring.c similarity index 100% rename from applications/plugins/spi_mem_manager/scenes/spi_mem_scene_wiring.c rename to applications/external/spi_mem_manager/scenes/spi_mem_scene_wiring.c diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_write.c b/applications/external/spi_mem_manager/scenes/spi_mem_scene_write.c similarity index 100% rename from applications/plugins/spi_mem_manager/scenes/spi_mem_scene_write.c rename to applications/external/spi_mem_manager/scenes/spi_mem_scene_write.c diff --git a/applications/plugins/spi_mem_manager/spi_mem_app.c b/applications/external/spi_mem_manager/spi_mem_app.c similarity index 100% rename from applications/plugins/spi_mem_manager/spi_mem_app.c rename to applications/external/spi_mem_manager/spi_mem_app.c diff --git a/applications/plugins/spi_mem_manager/spi_mem_app.h b/applications/external/spi_mem_manager/spi_mem_app.h similarity index 100% rename from applications/plugins/spi_mem_manager/spi_mem_app.h rename to applications/external/spi_mem_manager/spi_mem_app.h diff --git a/applications/plugins/spi_mem_manager/spi_mem_app_i.h b/applications/external/spi_mem_manager/spi_mem_app_i.h similarity index 100% rename from applications/plugins/spi_mem_manager/spi_mem_app_i.h rename to applications/external/spi_mem_manager/spi_mem_app_i.h diff --git a/applications/plugins/spi_mem_manager/spi_mem_files.c b/applications/external/spi_mem_manager/spi_mem_files.c similarity index 100% rename from applications/plugins/spi_mem_manager/spi_mem_files.c rename to applications/external/spi_mem_manager/spi_mem_files.c diff --git a/applications/plugins/spi_mem_manager/spi_mem_files.h b/applications/external/spi_mem_manager/spi_mem_files.h similarity index 100% rename from applications/plugins/spi_mem_manager/spi_mem_files.h rename to applications/external/spi_mem_manager/spi_mem_files.h diff --git a/applications/plugins/spi_mem_manager/tools/README.md b/applications/external/spi_mem_manager/tools/README.md similarity index 100% rename from applications/plugins/spi_mem_manager/tools/README.md rename to applications/external/spi_mem_manager/tools/README.md diff --git a/applications/plugins/spi_mem_manager/tools/chiplist/LICENSE b/applications/external/spi_mem_manager/tools/chiplist/LICENSE similarity index 100% rename from applications/plugins/spi_mem_manager/tools/chiplist/LICENSE rename to applications/external/spi_mem_manager/tools/chiplist/LICENSE diff --git a/applications/plugins/spi_mem_manager/tools/chiplist/chiplist.xml b/applications/external/spi_mem_manager/tools/chiplist/chiplist.xml similarity index 100% rename from applications/plugins/spi_mem_manager/tools/chiplist/chiplist.xml rename to applications/external/spi_mem_manager/tools/chiplist/chiplist.xml diff --git a/applications/plugins/spi_mem_manager/tools/chiplist_convert.py b/applications/external/spi_mem_manager/tools/chiplist_convert.py similarity index 100% rename from applications/plugins/spi_mem_manager/tools/chiplist_convert.py rename to applications/external/spi_mem_manager/tools/chiplist_convert.py diff --git a/applications/plugins/spi_mem_manager/views/spi_mem_view_detect.c b/applications/external/spi_mem_manager/views/spi_mem_view_detect.c similarity index 100% rename from applications/plugins/spi_mem_manager/views/spi_mem_view_detect.c rename to applications/external/spi_mem_manager/views/spi_mem_view_detect.c diff --git a/applications/plugins/spi_mem_manager/views/spi_mem_view_detect.h b/applications/external/spi_mem_manager/views/spi_mem_view_detect.h similarity index 100% rename from applications/plugins/spi_mem_manager/views/spi_mem_view_detect.h rename to applications/external/spi_mem_manager/views/spi_mem_view_detect.h diff --git a/applications/plugins/spi_mem_manager/views/spi_mem_view_progress.c b/applications/external/spi_mem_manager/views/spi_mem_view_progress.c similarity index 100% rename from applications/plugins/spi_mem_manager/views/spi_mem_view_progress.c rename to applications/external/spi_mem_manager/views/spi_mem_view_progress.c diff --git a/applications/plugins/spi_mem_manager/views/spi_mem_view_progress.h b/applications/external/spi_mem_manager/views/spi_mem_view_progress.h similarity index 100% rename from applications/plugins/spi_mem_manager/views/spi_mem_view_progress.h rename to applications/external/spi_mem_manager/views/spi_mem_view_progress.h diff --git a/applications/plugins/weather_station/application.fam b/applications/external/weather_station/application.fam similarity index 79% rename from applications/plugins/weather_station/application.fam rename to applications/external/weather_station/application.fam index 935f92573b1..8dcaa1259b8 100644 --- a/applications/plugins/weather_station/application.fam +++ b/applications/external/weather_station/application.fam @@ -1,10 +1,9 @@ App( appid="weather_station", name="Weather Station", - apptype=FlipperAppType.PLUGIN, + apptype=FlipperAppType.EXTERNAL, targets=["f7"], entry_point="weather_station_app", - cdefines=["APP_WEATHER_STATION"], requires=["gui"], stack_size=4 * 1024, order=50, diff --git a/applications/plugins/weather_station/helpers/weather_station_event.h b/applications/external/weather_station/helpers/weather_station_event.h similarity index 100% rename from applications/plugins/weather_station/helpers/weather_station_event.h rename to applications/external/weather_station/helpers/weather_station_event.h diff --git a/applications/plugins/weather_station/helpers/weather_station_types.h b/applications/external/weather_station/helpers/weather_station_types.h similarity index 100% rename from applications/plugins/weather_station/helpers/weather_station_types.h rename to applications/external/weather_station/helpers/weather_station_types.h diff --git a/applications/plugins/weather_station/images/Humid_10x15.png b/applications/external/weather_station/images/Humid_10x15.png similarity index 100% rename from applications/plugins/weather_station/images/Humid_10x15.png rename to applications/external/weather_station/images/Humid_10x15.png diff --git a/applications/plugins/weather_station/images/Humid_8x13.png b/applications/external/weather_station/images/Humid_8x13.png similarity index 100% rename from applications/plugins/weather_station/images/Humid_8x13.png rename to applications/external/weather_station/images/Humid_8x13.png diff --git a/applications/plugins/weather_station/images/Lock_7x8.png b/applications/external/weather_station/images/Lock_7x8.png similarity index 100% rename from applications/plugins/weather_station/images/Lock_7x8.png rename to applications/external/weather_station/images/Lock_7x8.png diff --git a/applications/plugins/weather_station/images/Pin_back_arrow_10x8.png b/applications/external/weather_station/images/Pin_back_arrow_10x8.png similarity index 100% rename from applications/plugins/weather_station/images/Pin_back_arrow_10x8.png rename to applications/external/weather_station/images/Pin_back_arrow_10x8.png diff --git a/applications/plugins/weather_station/images/Quest_7x8.png b/applications/external/weather_station/images/Quest_7x8.png similarity index 100% rename from applications/plugins/weather_station/images/Quest_7x8.png rename to applications/external/weather_station/images/Quest_7x8.png diff --git a/applications/plugins/weather_station/images/Scanning_123x52.png b/applications/external/weather_station/images/Scanning_123x52.png similarity index 100% rename from applications/plugins/weather_station/images/Scanning_123x52.png rename to applications/external/weather_station/images/Scanning_123x52.png diff --git a/applications/plugins/weather_station/images/Therm_7x16.png b/applications/external/weather_station/images/Therm_7x16.png similarity index 100% rename from applications/plugins/weather_station/images/Therm_7x16.png rename to applications/external/weather_station/images/Therm_7x16.png diff --git a/applications/plugins/weather_station/images/Timer_11x11.png b/applications/external/weather_station/images/Timer_11x11.png similarity index 100% rename from applications/plugins/weather_station/images/Timer_11x11.png rename to applications/external/weather_station/images/Timer_11x11.png diff --git a/applications/plugins/weather_station/images/Unlock_7x8.png b/applications/external/weather_station/images/Unlock_7x8.png similarity index 100% rename from applications/plugins/weather_station/images/Unlock_7x8.png rename to applications/external/weather_station/images/Unlock_7x8.png diff --git a/applications/plugins/weather_station/images/WarningDolphin_45x42.png b/applications/external/weather_station/images/WarningDolphin_45x42.png similarity index 100% rename from applications/plugins/weather_station/images/WarningDolphin_45x42.png rename to applications/external/weather_station/images/WarningDolphin_45x42.png diff --git a/applications/plugins/weather_station/images/station_icon.png b/applications/external/weather_station/images/station_icon.png similarity index 100% rename from applications/plugins/weather_station/images/station_icon.png rename to applications/external/weather_station/images/station_icon.png diff --git a/applications/plugins/weather_station/protocols/acurite_592txr.c b/applications/external/weather_station/protocols/acurite_592txr.c similarity index 100% rename from applications/plugins/weather_station/protocols/acurite_592txr.c rename to applications/external/weather_station/protocols/acurite_592txr.c diff --git a/applications/plugins/weather_station/protocols/acurite_592txr.h b/applications/external/weather_station/protocols/acurite_592txr.h similarity index 100% rename from applications/plugins/weather_station/protocols/acurite_592txr.h rename to applications/external/weather_station/protocols/acurite_592txr.h diff --git a/applications/plugins/weather_station/protocols/acurite_606tx.c b/applications/external/weather_station/protocols/acurite_606tx.c similarity index 100% rename from applications/plugins/weather_station/protocols/acurite_606tx.c rename to applications/external/weather_station/protocols/acurite_606tx.c diff --git a/applications/plugins/weather_station/protocols/acurite_606tx.h b/applications/external/weather_station/protocols/acurite_606tx.h similarity index 100% rename from applications/plugins/weather_station/protocols/acurite_606tx.h rename to applications/external/weather_station/protocols/acurite_606tx.h diff --git a/applications/plugins/weather_station/protocols/acurite_609txc.c b/applications/external/weather_station/protocols/acurite_609txc.c similarity index 100% rename from applications/plugins/weather_station/protocols/acurite_609txc.c rename to applications/external/weather_station/protocols/acurite_609txc.c diff --git a/applications/plugins/weather_station/protocols/acurite_609txc.h b/applications/external/weather_station/protocols/acurite_609txc.h similarity index 100% rename from applications/plugins/weather_station/protocols/acurite_609txc.h rename to applications/external/weather_station/protocols/acurite_609txc.h diff --git a/applications/plugins/weather_station/protocols/ambient_weather.c b/applications/external/weather_station/protocols/ambient_weather.c similarity index 100% rename from applications/plugins/weather_station/protocols/ambient_weather.c rename to applications/external/weather_station/protocols/ambient_weather.c diff --git a/applications/plugins/weather_station/protocols/ambient_weather.h b/applications/external/weather_station/protocols/ambient_weather.h similarity index 100% rename from applications/plugins/weather_station/protocols/ambient_weather.h rename to applications/external/weather_station/protocols/ambient_weather.h diff --git a/applications/plugins/weather_station/protocols/auriol_hg0601a.c b/applications/external/weather_station/protocols/auriol_hg0601a.c similarity index 100% rename from applications/plugins/weather_station/protocols/auriol_hg0601a.c rename to applications/external/weather_station/protocols/auriol_hg0601a.c diff --git a/applications/plugins/weather_station/protocols/auriol_hg0601a.h b/applications/external/weather_station/protocols/auriol_hg0601a.h similarity index 100% rename from applications/plugins/weather_station/protocols/auriol_hg0601a.h rename to applications/external/weather_station/protocols/auriol_hg0601a.h diff --git a/applications/plugins/weather_station/protocols/gt_wt_02.c b/applications/external/weather_station/protocols/gt_wt_02.c similarity index 100% rename from applications/plugins/weather_station/protocols/gt_wt_02.c rename to applications/external/weather_station/protocols/gt_wt_02.c diff --git a/applications/plugins/weather_station/protocols/gt_wt_02.h b/applications/external/weather_station/protocols/gt_wt_02.h similarity index 100% rename from applications/plugins/weather_station/protocols/gt_wt_02.h rename to applications/external/weather_station/protocols/gt_wt_02.h diff --git a/applications/plugins/weather_station/protocols/gt_wt_03.c b/applications/external/weather_station/protocols/gt_wt_03.c similarity index 100% rename from applications/plugins/weather_station/protocols/gt_wt_03.c rename to applications/external/weather_station/protocols/gt_wt_03.c diff --git a/applications/plugins/weather_station/protocols/gt_wt_03.h b/applications/external/weather_station/protocols/gt_wt_03.h similarity index 100% rename from applications/plugins/weather_station/protocols/gt_wt_03.h rename to applications/external/weather_station/protocols/gt_wt_03.h diff --git a/applications/plugins/weather_station/protocols/infactory.c b/applications/external/weather_station/protocols/infactory.c similarity index 100% rename from applications/plugins/weather_station/protocols/infactory.c rename to applications/external/weather_station/protocols/infactory.c diff --git a/applications/plugins/weather_station/protocols/infactory.h b/applications/external/weather_station/protocols/infactory.h similarity index 100% rename from applications/plugins/weather_station/protocols/infactory.h rename to applications/external/weather_station/protocols/infactory.h diff --git a/applications/plugins/weather_station/protocols/lacrosse_tx.c b/applications/external/weather_station/protocols/lacrosse_tx.c similarity index 100% rename from applications/plugins/weather_station/protocols/lacrosse_tx.c rename to applications/external/weather_station/protocols/lacrosse_tx.c diff --git a/applications/plugins/weather_station/protocols/lacrosse_tx.h b/applications/external/weather_station/protocols/lacrosse_tx.h similarity index 100% rename from applications/plugins/weather_station/protocols/lacrosse_tx.h rename to applications/external/weather_station/protocols/lacrosse_tx.h diff --git a/applications/plugins/weather_station/protocols/lacrosse_tx141thbv2.c b/applications/external/weather_station/protocols/lacrosse_tx141thbv2.c similarity index 100% rename from applications/plugins/weather_station/protocols/lacrosse_tx141thbv2.c rename to applications/external/weather_station/protocols/lacrosse_tx141thbv2.c diff --git a/applications/plugins/weather_station/protocols/lacrosse_tx141thbv2.h b/applications/external/weather_station/protocols/lacrosse_tx141thbv2.h similarity index 100% rename from applications/plugins/weather_station/protocols/lacrosse_tx141thbv2.h rename to applications/external/weather_station/protocols/lacrosse_tx141thbv2.h diff --git a/applications/plugins/weather_station/protocols/nexus_th.c b/applications/external/weather_station/protocols/nexus_th.c similarity index 100% rename from applications/plugins/weather_station/protocols/nexus_th.c rename to applications/external/weather_station/protocols/nexus_th.c diff --git a/applications/plugins/weather_station/protocols/nexus_th.h b/applications/external/weather_station/protocols/nexus_th.h similarity index 100% rename from applications/plugins/weather_station/protocols/nexus_th.h rename to applications/external/weather_station/protocols/nexus_th.h diff --git a/applications/plugins/weather_station/protocols/oregon2.c b/applications/external/weather_station/protocols/oregon2.c similarity index 100% rename from applications/plugins/weather_station/protocols/oregon2.c rename to applications/external/weather_station/protocols/oregon2.c diff --git a/applications/plugins/weather_station/protocols/oregon2.h b/applications/external/weather_station/protocols/oregon2.h similarity index 100% rename from applications/plugins/weather_station/protocols/oregon2.h rename to applications/external/weather_station/protocols/oregon2.h diff --git a/applications/plugins/weather_station/protocols/oregon_v1.c b/applications/external/weather_station/protocols/oregon_v1.c similarity index 100% rename from applications/plugins/weather_station/protocols/oregon_v1.c rename to applications/external/weather_station/protocols/oregon_v1.c diff --git a/applications/plugins/weather_station/protocols/oregon_v1.h b/applications/external/weather_station/protocols/oregon_v1.h similarity index 100% rename from applications/plugins/weather_station/protocols/oregon_v1.h rename to applications/external/weather_station/protocols/oregon_v1.h diff --git a/applications/plugins/weather_station/protocols/protocol_items.c b/applications/external/weather_station/protocols/protocol_items.c similarity index 100% rename from applications/plugins/weather_station/protocols/protocol_items.c rename to applications/external/weather_station/protocols/protocol_items.c diff --git a/applications/plugins/weather_station/protocols/protocol_items.h b/applications/external/weather_station/protocols/protocol_items.h similarity index 100% rename from applications/plugins/weather_station/protocols/protocol_items.h rename to applications/external/weather_station/protocols/protocol_items.h diff --git a/applications/plugins/weather_station/protocols/thermopro_tx4.c b/applications/external/weather_station/protocols/thermopro_tx4.c similarity index 100% rename from applications/plugins/weather_station/protocols/thermopro_tx4.c rename to applications/external/weather_station/protocols/thermopro_tx4.c diff --git a/applications/plugins/weather_station/protocols/thermopro_tx4.h b/applications/external/weather_station/protocols/thermopro_tx4.h similarity index 100% rename from applications/plugins/weather_station/protocols/thermopro_tx4.h rename to applications/external/weather_station/protocols/thermopro_tx4.h diff --git a/applications/plugins/weather_station/protocols/tx_8300.c b/applications/external/weather_station/protocols/tx_8300.c similarity index 100% rename from applications/plugins/weather_station/protocols/tx_8300.c rename to applications/external/weather_station/protocols/tx_8300.c diff --git a/applications/plugins/weather_station/protocols/tx_8300.h b/applications/external/weather_station/protocols/tx_8300.h similarity index 100% rename from applications/plugins/weather_station/protocols/tx_8300.h rename to applications/external/weather_station/protocols/tx_8300.h diff --git a/applications/plugins/weather_station/protocols/ws_generic.c b/applications/external/weather_station/protocols/ws_generic.c similarity index 100% rename from applications/plugins/weather_station/protocols/ws_generic.c rename to applications/external/weather_station/protocols/ws_generic.c diff --git a/applications/plugins/weather_station/protocols/ws_generic.h b/applications/external/weather_station/protocols/ws_generic.h similarity index 100% rename from applications/plugins/weather_station/protocols/ws_generic.h rename to applications/external/weather_station/protocols/ws_generic.h diff --git a/applications/plugins/weather_station/scenes/weather_station_receiver.c b/applications/external/weather_station/scenes/weather_station_receiver.c similarity index 100% rename from applications/plugins/weather_station/scenes/weather_station_receiver.c rename to applications/external/weather_station/scenes/weather_station_receiver.c diff --git a/applications/plugins/weather_station/scenes/weather_station_scene.c b/applications/external/weather_station/scenes/weather_station_scene.c similarity index 100% rename from applications/plugins/weather_station/scenes/weather_station_scene.c rename to applications/external/weather_station/scenes/weather_station_scene.c diff --git a/applications/plugins/weather_station/scenes/weather_station_scene.h b/applications/external/weather_station/scenes/weather_station_scene.h similarity index 100% rename from applications/plugins/weather_station/scenes/weather_station_scene.h rename to applications/external/weather_station/scenes/weather_station_scene.h diff --git a/applications/plugins/weather_station/scenes/weather_station_scene_about.c b/applications/external/weather_station/scenes/weather_station_scene_about.c similarity index 100% rename from applications/plugins/weather_station/scenes/weather_station_scene_about.c rename to applications/external/weather_station/scenes/weather_station_scene_about.c diff --git a/applications/plugins/weather_station/scenes/weather_station_scene_config.h b/applications/external/weather_station/scenes/weather_station_scene_config.h similarity index 100% rename from applications/plugins/weather_station/scenes/weather_station_scene_config.h rename to applications/external/weather_station/scenes/weather_station_scene_config.h diff --git a/applications/plugins/weather_station/scenes/weather_station_scene_receiver_config.c b/applications/external/weather_station/scenes/weather_station_scene_receiver_config.c similarity index 100% rename from applications/plugins/weather_station/scenes/weather_station_scene_receiver_config.c rename to applications/external/weather_station/scenes/weather_station_scene_receiver_config.c diff --git a/applications/plugins/weather_station/scenes/weather_station_scene_receiver_info.c b/applications/external/weather_station/scenes/weather_station_scene_receiver_info.c similarity index 100% rename from applications/plugins/weather_station/scenes/weather_station_scene_receiver_info.c rename to applications/external/weather_station/scenes/weather_station_scene_receiver_info.c diff --git a/applications/plugins/weather_station/scenes/weather_station_scene_start.c b/applications/external/weather_station/scenes/weather_station_scene_start.c similarity index 100% rename from applications/plugins/weather_station/scenes/weather_station_scene_start.c rename to applications/external/weather_station/scenes/weather_station_scene_start.c diff --git a/applications/plugins/weather_station/views/weather_station_receiver.c b/applications/external/weather_station/views/weather_station_receiver.c similarity index 100% rename from applications/plugins/weather_station/views/weather_station_receiver.c rename to applications/external/weather_station/views/weather_station_receiver.c diff --git a/applications/plugins/weather_station/views/weather_station_receiver.h b/applications/external/weather_station/views/weather_station_receiver.h similarity index 100% rename from applications/plugins/weather_station/views/weather_station_receiver.h rename to applications/external/weather_station/views/weather_station_receiver.h diff --git a/applications/plugins/weather_station/views/weather_station_receiver_info.c b/applications/external/weather_station/views/weather_station_receiver_info.c similarity index 100% rename from applications/plugins/weather_station/views/weather_station_receiver_info.c rename to applications/external/weather_station/views/weather_station_receiver_info.c diff --git a/applications/plugins/weather_station/views/weather_station_receiver_info.h b/applications/external/weather_station/views/weather_station_receiver_info.h similarity index 100% rename from applications/plugins/weather_station/views/weather_station_receiver_info.h rename to applications/external/weather_station/views/weather_station_receiver_info.h diff --git a/applications/plugins/weather_station/weather_station_10px.png b/applications/external/weather_station/weather_station_10px.png similarity index 100% rename from applications/plugins/weather_station/weather_station_10px.png rename to applications/external/weather_station/weather_station_10px.png diff --git a/applications/plugins/weather_station/weather_station_app.c b/applications/external/weather_station/weather_station_app.c similarity index 100% rename from applications/plugins/weather_station/weather_station_app.c rename to applications/external/weather_station/weather_station_app.c diff --git a/applications/plugins/weather_station/weather_station_app_i.c b/applications/external/weather_station/weather_station_app_i.c similarity index 100% rename from applications/plugins/weather_station/weather_station_app_i.c rename to applications/external/weather_station/weather_station_app_i.c diff --git a/applications/plugins/weather_station/weather_station_app_i.h b/applications/external/weather_station/weather_station_app_i.h similarity index 100% rename from applications/plugins/weather_station/weather_station_app_i.h rename to applications/external/weather_station/weather_station_app_i.h diff --git a/applications/plugins/weather_station/weather_station_history.c b/applications/external/weather_station/weather_station_history.c similarity index 100% rename from applications/plugins/weather_station/weather_station_history.c rename to applications/external/weather_station/weather_station_history.c diff --git a/applications/plugins/weather_station/weather_station_history.h b/applications/external/weather_station/weather_station_history.h similarity index 100% rename from applications/plugins/weather_station/weather_station_history.h rename to applications/external/weather_station/weather_station_history.h diff --git a/applications/main/fap_loader/application.fam b/applications/main/fap_loader/application.fam index 784ee9508a3..b0e67cd42e3 100644 --- a/applications/main/fap_loader/application.fam +++ b/applications/main/fap_loader/application.fam @@ -7,6 +7,7 @@ App( requires=[ "gui", "storage", + "loader", ], stack_size=int(1.5 * 1024), icon="A_Plugins_14", diff --git a/applications/main/fap_loader/elf_cpp/elf_hashtable.cpp b/applications/main/fap_loader/elf_cpp/elf_hashtable.cpp deleted file mode 100644 index e845ed008d6..00000000000 --- a/applications/main/fap_loader/elf_cpp/elf_hashtable.cpp +++ /dev/null @@ -1,48 +0,0 @@ -#include "compilesort.hpp" -#include "elf_hashtable.h" -#include "elf_hashtable_entry.h" -#include "elf_hashtable_checks.hpp" - -#include -#include - -/* Generated table */ -#include - -#define TAG "elf_hashtable" - -static_assert(!has_hash_collisions(elf_api_table), "Detected API method hash collision!"); - -/** - * Get function address by function name - * @param name function name - * @param address output for function address - * @return true if the table contains a function - */ - -bool elf_resolve_from_hashtable(const char* name, Elf32_Addr* address) { - bool result = false; - uint32_t gnu_sym_hash = elf_gnu_hash(name); - - sym_entry key = { - .hash = gnu_sym_hash, - .address = 0, - }; - - auto find_res = std::lower_bound(elf_api_table.cbegin(), elf_api_table.cend(), key); - if((find_res == elf_api_table.cend() || (find_res->hash != gnu_sym_hash))) { - FURI_LOG_W(TAG, "Can't find symbol '%s' (hash %lx)!", name, gnu_sym_hash); - result = false; - } else { - result = true; - *address = find_res->address; - } - - return result; -} - -const ElfApiInterface hashtable_api_interface = { - .api_version_major = (elf_api_version >> 16), - .api_version_minor = (elf_api_version & 0xFFFF), - .resolver_callback = &elf_resolve_from_hashtable, -}; diff --git a/applications/main/fap_loader/elf_cpp/elf_hashtable.h b/applications/main/fap_loader/elf_cpp/elf_hashtable.h deleted file mode 100644 index e574f11690c..00000000000 --- a/applications/main/fap_loader/elf_cpp/elf_hashtable.h +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once -#include - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -extern const ElfApiInterface hashtable_api_interface; - -#ifdef __cplusplus -} -#endif diff --git a/applications/main/fap_loader/elf_cpp/elf_hashtable_checks.hpp b/applications/main/fap_loader/elf_cpp/elf_hashtable_checks.hpp deleted file mode 100644 index 61ee80e916f..00000000000 --- a/applications/main/fap_loader/elf_cpp/elf_hashtable_checks.hpp +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Check for multiple entries with the same hash value at compilation time. - */ - -#pragma once -#include -#include "elf_hashtable_entry.h" - -template -constexpr bool has_hash_collisions(const std::array api_methods) { - for(std::size_t i = 0; i < (N - 1); ++i) { - if(api_methods[i].hash == api_methods[i + 1].hash) { - return true; - } - } - - return false; -} diff --git a/applications/main/fap_loader/elf_cpp/elf_hashtable_entry.h b/applications/main/fap_loader/elf_cpp/elf_hashtable_entry.h deleted file mode 100644 index 7b540fba6a7..00000000000 --- a/applications/main/fap_loader/elf_cpp/elf_hashtable_entry.h +++ /dev/null @@ -1,41 +0,0 @@ -#pragma once -#include - -#ifdef __cplusplus -extern "C" { -#endif - -struct sym_entry { - uint32_t hash; - uint32_t address; -}; - -#ifdef __cplusplus -} - -#include -#include - -#define API_METHOD(x, ret_type, args_type) \ - sym_entry { \ - .hash = elf_gnu_hash(#x), .address = (uint32_t)(static_cast(x)) \ - } - -#define API_VARIABLE(x, var_type) \ - sym_entry { \ - .hash = elf_gnu_hash(#x), .address = (uint32_t)(&(x)), \ - } - -constexpr bool operator<(const sym_entry& k1, const sym_entry& k2) { - return k1.hash < k2.hash; -} - -constexpr uint32_t elf_gnu_hash(const char* s) { - uint32_t h = 0x1505; - for(unsigned char c = *s; c != '\0'; c = *++s) { - h = (h << 5) + h + c; - } - return h; -} - -#endif diff --git a/applications/main/fap_loader/fap_loader_app.c b/applications/main/fap_loader/fap_loader_app.c index e81a3ce4c84..dcbad8e139b 100644 --- a/applications/main/fap_loader/fap_loader_app.c +++ b/applications/main/fap_loader/fap_loader_app.c @@ -7,7 +7,7 @@ #include #include #include -#include "elf_cpp/elf_hashtable.h" +#include #include "fap_loader_app.h" #define TAG "fap_loader_app" @@ -27,7 +27,7 @@ bool fap_loader_load_name_and_icon( Storage* storage, uint8_t** icon_ptr, FuriString* item_name) { - FlipperApplication* app = flipper_application_alloc(storage, &hashtable_api_interface); + FlipperApplication* app = flipper_application_alloc(storage, firmware_api_interface); FlipperApplicationPreloadStatus preload_res = flipper_application_preload_manifest(app, furi_string_get_cstr(path)); @@ -71,7 +71,7 @@ static bool fap_loader_run_selected_app(FapLoader* loader) { bool show_error = true; do { file_selected = true; - loader->app = flipper_application_alloc(loader->storage, &hashtable_api_interface); + loader->app = flipper_application_alloc(loader->storage, firmware_api_interface); size_t start = furi_get_tick(); FURI_LOG_I(TAG, "FAP Loader is loading %s", furi_string_get_cstr(loader->fap_path)); diff --git a/applications/plugins/application.fam b/applications/plugins/application.fam deleted file mode 100644 index 6d25e45aae7..00000000000 --- a/applications/plugins/application.fam +++ /dev/null @@ -1,9 +0,0 @@ -App( - appid="basic_plugins", - name="Basic applications for plug-in menu", - apptype=FlipperAppType.METAPACKAGE, - provides=[ - "music_player", - "snake_game", - ], -) diff --git a/applications/services/applications.h b/applications/services/applications.h index 871e9af54a1..85f73674232 100644 --- a/applications/services/applications.h +++ b/applications/services/applications.h @@ -39,18 +39,6 @@ extern const size_t FLIPPER_APPS_COUNT; extern const FlipperOnStartHook FLIPPER_ON_SYSTEM_START[]; extern const size_t FLIPPER_ON_SYSTEM_START_COUNT; -/* Plugins list - * Spawned by loader - */ -extern const FlipperApplication FLIPPER_PLUGINS[]; -extern const size_t FLIPPER_PLUGINS_COUNT; - -/* Debug menu apps - * Spawned by loader - */ -extern const FlipperApplication FLIPPER_DEBUG_APPS[]; -extern const size_t FLIPPER_DEBUG_APPS_COUNT; - /* System apps * Can only be spawned by loader by name */ diff --git a/applications/services/loader/application.fam b/applications/services/loader/application.fam index 91103e46e3c..49f3c414889 100644 --- a/applications/services/loader/application.fam +++ b/applications/services/loader/application.fam @@ -7,5 +7,8 @@ App( requires=["gui"], stack_size=2 * 1024, order=90, - sdk_headers=["loader.h"], + sdk_headers=[ + "loader.h", + "firmware_api/firmware_api.h", + ], ) diff --git a/applications/services/loader/firmware_api/firmware_api.cpp b/applications/services/loader/firmware_api/firmware_api.cpp new file mode 100644 index 00000000000..814dd82c927 --- /dev/null +++ b/applications/services/loader/firmware_api/firmware_api.cpp @@ -0,0 +1,21 @@ +#include "firmware_api.h" + +#include +#include + +/* Generated table */ +#include + +static_assert(!has_hash_collisions(elf_api_table), "Detected API method hash collision!"); + +constexpr HashtableApiInterface elf_api_interface{ + { + .api_version_major = (elf_api_version >> 16), + .api_version_minor = (elf_api_version & 0xFFFF), + .resolver_callback = &elf_resolve_from_hashtable, + }, + .table_cbegin = elf_api_table.cbegin(), + .table_cend = elf_api_table.cend(), +}; + +const ElfApiInterface* const firmware_api_interface = &elf_api_interface; diff --git a/applications/services/loader/firmware_api/firmware_api.h b/applications/services/loader/firmware_api/firmware_api.h new file mode 100644 index 00000000000..c73ae896085 --- /dev/null +++ b/applications/services/loader/firmware_api/firmware_api.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +extern const ElfApiInterface* const firmware_api_interface; diff --git a/applications/services/loader/loader.c b/applications/services/loader/loader.c index 5f2d8a2e739..f83d47d63dd 100644 --- a/applications/services/loader/loader.c +++ b/applications/services/loader/loader.c @@ -88,10 +88,6 @@ static FlipperApplication const* loader_find_application_by_name_in_list( const FlipperApplication* loader_find_application_by_name(const char* name) { const FlipperApplication* application = NULL; application = loader_find_application_by_name_in_list(name, FLIPPER_APPS, FLIPPER_APPS_COUNT); - if(!application) { - application = - loader_find_application_by_name_in_list(name, FLIPPER_PLUGINS, FLIPPER_PLUGINS_COUNT); - } if(!application) { application = loader_find_application_by_name_in_list( name, FLIPPER_SETTINGS_APPS, FLIPPER_SETTINGS_APPS_COUNT); @@ -100,10 +96,6 @@ const FlipperApplication* loader_find_application_by_name(const char* name) { application = loader_find_application_by_name_in_list( name, FLIPPER_SYSTEM_APPS, FLIPPER_SYSTEM_APPS_COUNT); } - if(!application) { - application = loader_find_application_by_name_in_list( - name, FLIPPER_DEBUG_APPS, FLIPPER_DEBUG_APPS_COUNT); - } return application; } @@ -160,18 +152,6 @@ static void loader_cli_list(Cli* cli, FuriString* args, Loader* instance) { for(size_t i = 0; i < FLIPPER_APPS_COUNT; i++) { printf("\t%s\r\n", FLIPPER_APPS[i].name); } - - printf("Plugins:\r\n"); - for(size_t i = 0; i < FLIPPER_PLUGINS_COUNT; i++) { - printf("\t%s\r\n", FLIPPER_PLUGINS[i].name); - } - - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - printf("Debug:\r\n"); - for(size_t i = 0; i < FLIPPER_DEBUG_APPS_COUNT; i++) { - printf("\t%s\r\n", FLIPPER_DEBUG_APPS[i].name); - } - } } static void loader_cli_info(Cli* cli, FuriString* args, Loader* instance) { @@ -341,22 +321,6 @@ static Loader* loader_alloc() { view_set_previous_callback(menu_get_view(instance->primary_menu), loader_hide_menu); view_dispatcher_add_view( instance->view_dispatcher, LoaderMenuViewPrimary, menu_get_view(instance->primary_menu)); - // Plugins menu - instance->plugins_menu = submenu_alloc(); - view_set_context(submenu_get_view(instance->plugins_menu), instance->plugins_menu); - view_set_previous_callback( - submenu_get_view(instance->plugins_menu), loader_back_to_primary_menu); - view_dispatcher_add_view( - instance->view_dispatcher, - LoaderMenuViewPlugins, - submenu_get_view(instance->plugins_menu)); - // Debug menu - instance->debug_menu = submenu_alloc(); - view_set_context(submenu_get_view(instance->debug_menu), instance->debug_menu); - view_set_previous_callback( - submenu_get_view(instance->debug_menu), loader_back_to_primary_menu); - view_dispatcher_add_view( - instance->view_dispatcher, LoaderMenuViewDebug, submenu_get_view(instance->debug_menu)); // Settings menu instance->settings_menu = submenu_alloc(); view_set_context(submenu_get_view(instance->settings_menu), instance->settings_menu); @@ -385,10 +349,6 @@ static void loader_free(Loader* instance) { menu_free(loader_instance->primary_menu); view_dispatcher_remove_view(loader_instance->view_dispatcher, LoaderMenuViewPrimary); - submenu_free(loader_instance->plugins_menu); - view_dispatcher_remove_view(loader_instance->view_dispatcher, LoaderMenuViewPlugins); - submenu_free(loader_instance->debug_menu); - view_dispatcher_remove_view(loader_instance->view_dispatcher, LoaderMenuViewDebug); submenu_free(loader_instance->settings_menu); view_dispatcher_remove_view(loader_instance->view_dispatcher, LoaderMenuViewSettings); view_dispatcher_free(loader_instance->view_dispatcher); @@ -411,24 +371,6 @@ static void loader_build_menu() { loader_menu_callback, (void*)&FLIPPER_APPS[i]); } - if(FLIPPER_PLUGINS_COUNT != 0) { - menu_add_item( - loader_instance->primary_menu, - "Plugins", - &A_Plugins_14, - i++, - loader_submenu_callback, - (void*)LoaderMenuViewPlugins); - } - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug) && (FLIPPER_DEBUG_APPS_COUNT > 0)) { - menu_add_item( - loader_instance->primary_menu, - "Debug Tools", - &A_Debug_14, - i++, - loader_submenu_callback, - (void*)LoaderMenuViewDebug); - } menu_add_item( loader_instance->primary_menu, "Settings", @@ -439,29 +381,8 @@ static void loader_build_menu() { } static void loader_build_submenu() { - FURI_LOG_I(TAG, "Building plugins menu"); - size_t i; - for(i = 0; i < FLIPPER_PLUGINS_COUNT; i++) { - submenu_add_item( - loader_instance->plugins_menu, - FLIPPER_PLUGINS[i].name, - i, - loader_menu_callback, - (void*)&FLIPPER_PLUGINS[i]); - } - - FURI_LOG_I(TAG, "Building debug menu"); - for(i = 0; i < FLIPPER_DEBUG_APPS_COUNT; i++) { - submenu_add_item( - loader_instance->debug_menu, - FLIPPER_DEBUG_APPS[i].name, - i, - loader_menu_callback, - (void*)&FLIPPER_DEBUG_APPS[i]); - } - FURI_LOG_I(TAG, "Building settings menu"); - for(i = 0; i < FLIPPER_SETTINGS_APPS_COUNT; i++) { + for(size_t i = 0; i < FLIPPER_SETTINGS_APPS_COUNT; i++) { submenu_add_item( loader_instance->settings_menu, FLIPPER_SETTINGS_APPS[i].name, diff --git a/applications/services/loader/loader_i.h b/applications/services/loader/loader_i.h index db91f806c5c..00028cd6b16 100644 --- a/applications/services/loader/loader_i.h +++ b/applications/services/loader/loader_i.h @@ -26,8 +26,6 @@ struct Loader { ViewDispatcher* view_dispatcher; Menu* primary_menu; - Submenu* plugins_menu; - Submenu* debug_menu; Submenu* settings_menu; volatile uint8_t lock_count; @@ -37,7 +35,5 @@ struct Loader { typedef enum { LoaderMenuViewPrimary, - LoaderMenuViewPlugins, - LoaderMenuViewDebug, LoaderMenuViewSettings, } LoaderMenuView; diff --git a/assets/.gitignore b/assets/.gitignore index 2695770477a..a66a6eed4cf 100644 --- a/assets/.gitignore +++ b/assets/.gitignore @@ -2,3 +2,4 @@ /resources/Manifest /resources/apps/* /resources/dolphin/* +/resources/apps_data/**/*.fal diff --git a/documentation/Doxyfile b/documentation/Doxyfile index 1824e5a5258..9611e7f1a92 100644 --- a/documentation/Doxyfile +++ b/documentation/Doxyfile @@ -938,7 +938,7 @@ EXCLUDE = \ lib/microtar \ lib/mbedtls \ lib/cxxheaderparser \ - applications/plugins/dap_link/lib/free-dap + applications/external/dap_link/lib/free-dap # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded diff --git a/fbt_options.py b/fbt_options.py index 4850389ad54..a10c64b96ab 100644 --- a/fbt_options.py +++ b/fbt_options.py @@ -71,11 +71,6 @@ "system_apps", # Settings "settings_apps", - # Stock plugins - no longer built into fw, now they're .faps - # Yet you can still build them as a part of fw - # "basic_plugins", - # Debug - # "debug_apps", ], "unit_tests": [ "basic_services", diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index 07c323a1ba0..61195aba67d 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,18.1,, +Version,+,18.2,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -28,6 +28,7 @@ Header,+,applications/services/gui/modules/widget_elements/widget_element.h,, Header,+,applications/services/gui/view_dispatcher.h,, Header,+,applications/services/gui/view_stack.h,, Header,+,applications/services/input/input.h,, +Header,+,applications/services/loader/firmware_api/firmware_api.h,, Header,+,applications/services/loader/loader.h,, Header,+,applications/services/locale/locale.h,, Header,+,applications/services/notification/notification.h,, @@ -104,7 +105,11 @@ Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_tim.h,, Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_usart.h,, Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_utils.h,, Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_wwdg.h,, +Header,+,lib/flipper_application/api_hashtable/api_hashtable.h,, +Header,+,lib/flipper_application/api_hashtable/compilesort.hpp,, Header,+,lib/flipper_application/flipper_application.h,, +Header,+,lib/flipper_application/plugins/composite_resolver.h,, +Header,+,lib/flipper_application/plugins/plugin_manager.h,, Header,+,lib/flipper_format/flipper_format.h,, Header,+,lib/flipper_format/flipper_format_i.h,, Header,+,lib/libusb_stm32/inc/hid_usage_button.h,, @@ -567,6 +572,10 @@ Function,+,cli_session_close,void,Cli* Function,+,cli_session_open,void,"Cli*, void*" Function,+,cli_write,void,"Cli*, const uint8_t*, size_t" Function,-,clock,clock_t, +Function,+,composite_api_resolver_add,void,"CompositeApiResolver*, const ElfApiInterface*" +Function,+,composite_api_resolver_alloc,CompositeApiResolver*, +Function,+,composite_api_resolver_free,void,CompositeApiResolver* +Function,+,composite_api_resolver_get,const ElfApiInterface*,CompositeApiResolver* Function,+,crc32_calc_buffer,uint32_t,"uint32_t, const void*, size_t" Function,+,crc32_calc_file,uint32_t,"File*, const FileCrcProgressCb, void*" Function,-,ctermid,char*,char* @@ -639,6 +648,7 @@ Function,+,elements_slightly_rounded_box,void,"Canvas*, uint8_t, uint8_t, uint8_ Function,+,elements_slightly_rounded_frame,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t" Function,+,elements_string_fit_width,void,"Canvas*, FuriString*, uint8_t" Function,+,elements_text_box,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, Align, Align, const char*, _Bool" +Function,+,elf_resolve_from_hashtable,_Bool,"const ElfApiInterface*, const char*, Elf32_Addr*" Function,+,empty_screen_alloc,EmptyScreen*, Function,+,empty_screen_free,void,EmptyScreen* Function,+,empty_screen_get_view,View*,EmptyScreen* @@ -696,14 +706,16 @@ Function,-,fiscanf,int,"FILE*, const char*, ..." Function,+,flipper_application_alloc,FlipperApplication*,"Storage*, const ElfApiInterface*" Function,+,flipper_application_free,void,FlipperApplication* Function,+,flipper_application_get_manifest,const FlipperApplicationManifest*,FlipperApplication* +Function,+,flipper_application_is_plugin,_Bool,FlipperApplication* Function,+,flipper_application_load_status_to_string,const char*,FlipperApplicationLoadStatus Function,+,flipper_application_manifest_is_compatible,_Bool,"const FlipperApplicationManifest*, const ElfApiInterface*" Function,+,flipper_application_manifest_is_target_compatible,_Bool,const FlipperApplicationManifest* Function,+,flipper_application_manifest_is_valid,_Bool,const FlipperApplicationManifest* Function,+,flipper_application_map_to_memory,FlipperApplicationLoadStatus,FlipperApplication* +Function,+,flipper_application_plugin_get_descriptor,const FlipperAppPluginDescriptor*,FlipperApplication* Function,+,flipper_application_preload,FlipperApplicationPreloadStatus,"FlipperApplication*, const char*" Function,+,flipper_application_preload_manifest,FlipperApplicationPreloadStatus,"FlipperApplication*, const char*" -Function,-,flipper_application_preload_status_to_string,const char*,FlipperApplicationPreloadStatus +Function,+,flipper_application_preload_status_to_string,const char*,FlipperApplicationPreloadStatus Function,+,flipper_application_spawn,FuriThread*,"FlipperApplication*, void*" Function,+,flipper_format_buffered_file_alloc,FlipperFormat*,Storage* Function,+,flipper_format_buffered_file_close,_Bool,FlipperFormat* @@ -1473,6 +1485,13 @@ Function,-,pcTaskGetName,char*,TaskHandle_t Function,-,pcTimerGetName,const char*,TimerHandle_t Function,-,pclose,int,FILE* Function,-,perror,void,const char* +Function,+,plugin_manager_alloc,PluginManager*,"const char*, uint32_t, const ElfApiInterface*" +Function,+,plugin_manager_free,void,PluginManager* +Function,+,plugin_manager_get,const FlipperAppPluginDescriptor*,"PluginManager*, uint32_t" +Function,+,plugin_manager_get_count,uint32_t,PluginManager* +Function,+,plugin_manager_get_ep,const void*,"PluginManager*, uint32_t" +Function,+,plugin_manager_load_all,PluginManagerError,"PluginManager*, const char*" +Function,+,plugin_manager_load_single,PluginManagerError,"PluginManager*, const char*" Function,-,popen,FILE*,"const char*, const char*" Function,+,popup_alloc,Popup*, Function,+,popup_disable_timeout,void,Popup* @@ -2053,6 +2072,7 @@ Variable,-,_sys_nerr,int, Variable,-,_timezone,long, Variable,-,_tzname,char*[2], Variable,+,cli_vcp,CliSession, +Variable,+,firmware_api_interface,const ElfApiInterface*, Variable,+,furi_hal_i2c_bus_external,FuriHalI2cBus, Variable,+,furi_hal_i2c_bus_power,FuriHalI2cBus, Variable,+,furi_hal_i2c_handle_external,FuriHalI2cBusHandle, diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index fcacaeee939..e46322f4b6b 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,18.1,, +Version,+,18.2,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -28,6 +28,7 @@ Header,+,applications/services/gui/modules/widget_elements/widget_element.h,, Header,+,applications/services/gui/view_dispatcher.h,, Header,+,applications/services/gui/view_stack.h,, Header,+,applications/services/input/input.h,, +Header,+,applications/services/loader/firmware_api/firmware_api.h,, Header,+,applications/services/loader/loader.h,, Header,+,applications/services/locale/locale.h,, Header,+,applications/services/notification/notification.h,, @@ -110,7 +111,11 @@ Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_tim.h,, Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_usart.h,, Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_utils.h,, Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_wwdg.h,, +Header,+,lib/flipper_application/api_hashtable/api_hashtable.h,, +Header,+,lib/flipper_application/api_hashtable/compilesort.hpp,, Header,+,lib/flipper_application/flipper_application.h,, +Header,+,lib/flipper_application/plugins/composite_resolver.h,, +Header,+,lib/flipper_application/plugins/plugin_manager.h,, Header,+,lib/flipper_format/flipper_format.h,, Header,+,lib/flipper_format/flipper_format_i.h,, Header,+,lib/infrared/encoder_decoder/infrared.h,, @@ -679,6 +684,10 @@ Function,+,cli_session_close,void,Cli* Function,+,cli_session_open,void,"Cli*, void*" Function,+,cli_write,void,"Cli*, const uint8_t*, size_t" Function,-,clock,clock_t, +Function,+,composite_api_resolver_add,void,"CompositeApiResolver*, const ElfApiInterface*" +Function,+,composite_api_resolver_alloc,CompositeApiResolver*, +Function,+,composite_api_resolver_free,void,CompositeApiResolver* +Function,+,composite_api_resolver_get,const ElfApiInterface*,CompositeApiResolver* Function,-,copysign,double,"double, double" Function,-,copysignf,float,"float, float" Function,-,copysignl,long double,"long double, long double" @@ -778,6 +787,7 @@ Function,+,elements_slightly_rounded_box,void,"Canvas*, uint8_t, uint8_t, uint8_ Function,+,elements_slightly_rounded_frame,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t" Function,+,elements_string_fit_width,void,"Canvas*, FuriString*, uint8_t" Function,+,elements_text_box,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, Align, Align, const char*, _Bool" +Function,+,elf_resolve_from_hashtable,_Bool,"const ElfApiInterface*, const char*, Elf32_Addr*" Function,+,empty_screen_alloc,EmptyScreen*, Function,+,empty_screen_free,void,EmptyScreen* Function,+,empty_screen_get_view,View*,EmptyScreen* @@ -863,14 +873,16 @@ Function,-,fiscanf,int,"FILE*, const char*, ..." Function,+,flipper_application_alloc,FlipperApplication*,"Storage*, const ElfApiInterface*" Function,+,flipper_application_free,void,FlipperApplication* Function,+,flipper_application_get_manifest,const FlipperApplicationManifest*,FlipperApplication* +Function,+,flipper_application_is_plugin,_Bool,FlipperApplication* Function,+,flipper_application_load_status_to_string,const char*,FlipperApplicationLoadStatus Function,+,flipper_application_manifest_is_compatible,_Bool,"const FlipperApplicationManifest*, const ElfApiInterface*" Function,+,flipper_application_manifest_is_target_compatible,_Bool,const FlipperApplicationManifest* Function,+,flipper_application_manifest_is_valid,_Bool,const FlipperApplicationManifest* Function,+,flipper_application_map_to_memory,FlipperApplicationLoadStatus,FlipperApplication* +Function,+,flipper_application_plugin_get_descriptor,const FlipperAppPluginDescriptor*,FlipperApplication* Function,+,flipper_application_preload,FlipperApplicationPreloadStatus,"FlipperApplication*, const char*" Function,+,flipper_application_preload_manifest,FlipperApplicationPreloadStatus,"FlipperApplication*, const char*" -Function,-,flipper_application_preload_status_to_string,const char*,FlipperApplicationPreloadStatus +Function,+,flipper_application_preload_status_to_string,const char*,FlipperApplicationPreloadStatus Function,+,flipper_application_spawn,FuriThread*,"FlipperApplication*, void*" Function,+,flipper_format_buffered_file_alloc,FlipperFormat*,Storage* Function,+,flipper_format_buffered_file_close,_Bool,FlipperFormat* @@ -2091,6 +2103,13 @@ Function,-,platformProtectST25RComm,void, Function,-,platformSetIrqCallback,void,PlatformIrqCallback Function,-,platformSpiTxRx,_Bool,"const uint8_t*, uint8_t*, uint16_t" Function,-,platformUnprotectST25RComm,void, +Function,+,plugin_manager_alloc,PluginManager*,"const char*, uint32_t, const ElfApiInterface*" +Function,+,plugin_manager_free,void,PluginManager* +Function,+,plugin_manager_get,const FlipperAppPluginDescriptor*,"PluginManager*, uint32_t" +Function,+,plugin_manager_get_count,uint32_t,PluginManager* +Function,+,plugin_manager_get_ep,const void*,"PluginManager*, uint32_t" +Function,+,plugin_manager_load_all,PluginManagerError,"PluginManager*, const char*" +Function,+,plugin_manager_load_single,PluginManagerError,"PluginManager*, const char*" Function,-,popen,FILE*,"const char*, const char*" Function,+,popup_alloc,Popup*, Function,+,popup_disable_timeout,void,Popup* @@ -3021,6 +3040,7 @@ Variable,-,_sys_nerr,int, Variable,-,_timezone,long, Variable,-,_tzname,char*[2], Variable,+,cli_vcp,CliSession, +Variable,+,firmware_api_interface,const ElfApiInterface*, Variable,+,furi_hal_i2c_bus_external,FuriHalI2cBus, Variable,+,furi_hal_i2c_bus_power,FuriHalI2cBus, Variable,+,furi_hal_i2c_handle_external,FuriHalI2cBusHandle, diff --git a/furi/flipper.c b/furi/flipper.c index f0147c06029..5c2ad81389f 100644 --- a/furi/flipper.c +++ b/furi/flipper.c @@ -33,7 +33,7 @@ void flipper_init() { FURI_LOG_I(TAG, "Boot mode %d, starting services", furi_hal_rtc_get_boot_mode()); for(size_t i = 0; i < FLIPPER_SERVICES_COUNT; i++) { - FURI_LOG_I(TAG, "Starting service %s", FLIPPER_SERVICES[i].name); + FURI_LOG_D(TAG, "Starting service %s", FLIPPER_SERVICES[i].name); FuriThread* thread = furi_thread_alloc_ex( FLIPPER_SERVICES[i].name, diff --git a/lib/flipper_application/SConscript b/lib/flipper_application/SConscript index 9fbbf95d144..d253cc82c5f 100644 --- a/lib/flipper_application/SConscript +++ b/lib/flipper_application/SConscript @@ -6,6 +6,10 @@ env.Append( ], SDK_HEADERS=[ File("flipper_application.h"), + File("plugins/plugin_manager.h"), + File("plugins/composite_resolver.h"), + File("api_hashtable/api_hashtable.h"), + File("api_hashtable/compilesort.hpp"), ], ) @@ -14,6 +18,7 @@ libenv = env.Clone(FW_LIB_NAME="flipper_application") libenv.ApplyLibFlags() sources = libenv.GlobRecursive("*.c") +sources.append(File("api_hashtable/api_hashtable.cpp")) lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources) libenv.Install("${LIB_DIST_DIR}", lib) diff --git a/lib/flipper_application/api_hashtable/api_hashtable.cpp b/lib/flipper_application/api_hashtable/api_hashtable.cpp new file mode 100644 index 00000000000..022792dce63 --- /dev/null +++ b/lib/flipper_application/api_hashtable/api_hashtable.cpp @@ -0,0 +1,38 @@ +#include "api_hashtable.h" + +#include +#include + +#define TAG "hashtable_api" + +bool elf_resolve_from_hashtable( + const ElfApiInterface* interface, + const char* name, + Elf32_Addr* address) { + const HashtableApiInterface* hashtable_interface = + static_cast(interface); + bool result = false; + uint32_t gnu_sym_hash = elf_gnu_hash(name); + + sym_entry key = { + .hash = gnu_sym_hash, + .address = 0, + }; + + auto find_res = + std::lower_bound(hashtable_interface->table_cbegin, hashtable_interface->table_cend, key); + if((find_res == hashtable_interface->table_cend || (find_res->hash != gnu_sym_hash))) { + FURI_LOG_W( + TAG, + "Can't find symbol '%s' (hash %lx) @ %p!", + name, + gnu_sym_hash, + hashtable_interface->table_cbegin); + result = false; + } else { + result = true; + *address = find_res->address; + } + + return result; +} diff --git a/lib/flipper_application/api_hashtable/api_hashtable.h b/lib/flipper_application/api_hashtable/api_hashtable.h new file mode 100644 index 00000000000..7e4b4aba1dd --- /dev/null +++ b/lib/flipper_application/api_hashtable/api_hashtable.h @@ -0,0 +1,85 @@ +#pragma once + +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Symbol table entry + */ +struct sym_entry { + uint32_t hash; + uint32_t address; +}; + +/** + * @brief Resolver for API entries using a pre-sorted table with hashes + * @param interface pointer to HashtableApiInterface + * @param name function name + * @param address output for function address + * @return true if the table contains a function + */ +bool elf_resolve_from_hashtable( + const ElfApiInterface* interface, + const char* name, + Elf32_Addr* address); + +#ifdef __cplusplus +} + +#include +#include + +/** + * @brief HashtableApiInterface is an implementation of ElfApiInterface + * that uses a hash table to resolve function addresses. + * table_cbegin and table_cend must point to a sorted array of sym_entry + */ +struct HashtableApiInterface : public ElfApiInterface { + const sym_entry *table_cbegin, *table_cend; +}; + +#define API_METHOD(x, ret_type, args_type) \ + sym_entry { \ + .hash = elf_gnu_hash(#x), .address = (uint32_t)(static_cast(x)) \ + } + +#define API_VARIABLE(x, var_type) \ + sym_entry { .hash = elf_gnu_hash(#x), .address = (uint32_t)(&(x)), } + +constexpr bool operator<(const sym_entry& k1, const sym_entry& k2) { + return k1.hash < k2.hash; +} + +/** + * @brief Calculate hash for a string using the ELF GNU hash algorithm + * @param s string to calculate hash for + * @return hash value + */ +constexpr uint32_t elf_gnu_hash(const char* s) { + uint32_t h = 0x1505; + for(unsigned char c = *s; c != '\0'; c = *++s) { + h = (h << 5) + h + c; + } + return h; +} + +/* Compile-time check for hash collisions in API table. + * Usage: static_assert(!has_hash_collisions(api_methods), "Hash collision detected"); + */ +template +constexpr bool has_hash_collisions(const std::array& api_methods) { + for(std::size_t i = 0; i < (N - 1); ++i) { + if(api_methods[i].hash == api_methods[i + 1].hash) { + return true; + } + } + + return false; +} + +#endif \ No newline at end of file diff --git a/applications/main/fap_loader/elf_cpp/compilesort.hpp b/lib/flipper_application/api_hashtable/compilesort.hpp similarity index 99% rename from applications/main/fap_loader/elf_cpp/compilesort.hpp rename to lib/flipper_application/api_hashtable/compilesort.hpp index 746611697f0..9737fd02276 100644 --- a/applications/main/fap_loader/elf_cpp/compilesort.hpp +++ b/lib/flipper_application/api_hashtable/compilesort.hpp @@ -4,6 +4,8 @@ #pragma once +#ifdef __cplusplus + #include #include @@ -109,3 +111,5 @@ constexpr auto create_array_t(const Ts&&... values) { static_assert(traits::are_same_type(), "all elements must have same type"); return std::array{static_cast(values)...}; } + +#endif diff --git a/lib/flipper_application/elf/elf_api_interface.h b/lib/flipper_application/elf/elf_api_interface.h index ca31fc4836a..f07df4edb76 100644 --- a/lib/flipper_application/elf/elf_api_interface.h +++ b/lib/flipper_application/elf/elf_api_interface.h @@ -3,10 +3,14 @@ #include #include -#define ELF_INVALID_ADDRESS 0xFFFFFFFF - -typedef struct { +/** + * @brief Interface for ELF loader to resolve symbols + */ +typedef struct ElfApiInterface { uint16_t api_version_major; uint16_t api_version_minor; - bool (*resolver_callback)(const char* name, Elf32_Addr* address); + bool (*resolver_callback)( + const struct ElfApiInterface* interface, + const char* name, + Elf32_Addr* address); } ElfApiInterface; diff --git a/lib/flipper_application/elf/elf_file.c b/lib/flipper_application/elf/elf_file.c index 58e3153330a..146afccb51a 100644 --- a/lib/flipper_application/elf/elf_file.c +++ b/lib/flipper_application/elf/elf_file.c @@ -17,6 +17,8 @@ #define FURI_LOG_D(...) #endif +#define ELF_INVALID_ADDRESS 0xFFFFFFFF + #define TRAMPOLINE_CODE_SIZE 6 /** @@ -166,7 +168,7 @@ static ELFSection* elf_section_of(ELFFile* elf, int index) { static Elf32_Addr elf_address_of(ELFFile* elf, Elf32_Sym* sym, const char* sName) { if(sym->st_shndx == SHN_UNDEF) { Elf32_Addr addr = 0; - if(elf->api_interface->resolver_callback(sName, &addr)) { + if(elf->api_interface->resolver_callback(elf->api_interface, sName, &addr)) { return addr; } } else { @@ -514,10 +516,13 @@ static SectionType elf_preload_section( section_p->sec_idx = section_idx; if(section_header->sh_type == SHT_PREINIT_ARRAY) { + furi_assert(elf->preinit_array == NULL); elf->preinit_array = section_p; } else if(section_header->sh_type == SHT_INIT_ARRAY) { + furi_assert(elf->init_array == NULL); elf->init_array = section_p; } else if(section_header->sh_type == SHT_FINI_ARRAY) { + furi_assert(elf->fini_array == NULL); elf->fini_array = section_p; } @@ -605,10 +610,17 @@ ELFFile* elf_file_alloc(Storage* storage, const ElfApiInterface* api_interface) elf->api_interface = api_interface; ELFSectionDict_init(elf->sections); AddressCache_init(elf->trampoline_cache); + elf->init_array_called = false; return elf; } void elf_file_free(ELFFile* elf) { + // furi_check(!elf->init_array_called); + if(elf->init_array_called) { + FURI_LOG_W(TAG, "Init array was called, but fini array wasn't"); + elf_file_call_section_list(elf->fini_array, true); + } + // free sections data { ELFSectionDict_it_t it; @@ -774,19 +786,26 @@ ELFFileLoadStatus elf_file_load_sections(ELFFile* elf) { return status; } -void elf_file_pre_run(ELFFile* elf) { +void elf_file_call_init(ELFFile* elf) { + furi_check(!elf->init_array_called); elf_file_call_section_list(elf->preinit_array, false); elf_file_call_section_list(elf->init_array, false); + elf->init_array_called = true; } -int32_t elf_file_run(ELFFile* elf, void* args) { - int32_t result; - result = ((int32_t(*)(void*))elf->entry)(args); - return result; +bool elf_file_is_init_complete(ELFFile* elf) { + return elf->init_array_called; +} + +void* elf_file_get_entry_point(ELFFile* elf) { + furi_check(elf->init_array_called); + return (void*)elf->entry; } -void elf_file_post_run(ELFFile* elf) { +void elf_file_call_fini(ELFFile* elf) { + furi_check(elf->init_array_called); elf_file_call_section_list(elf->fini_array, true); + elf->init_array_called = false; } const ElfApiInterface* elf_file_get_api_interface(ELFFile* elf_file) { diff --git a/lib/flipper_application/elf/elf_file.h b/lib/flipper_application/elf/elf_file.h index f371cdb22e8..631fe122f93 100644 --- a/lib/flipper_application/elf/elf_file.h +++ b/lib/flipper_application/elf/elf_file.h @@ -82,24 +82,34 @@ bool elf_file_load_section_table(ELFFile* elf_file); ELFFileLoadStatus elf_file_load_sections(ELFFile* elf_file); /** - * @brief Execute ELF file pre-run stage, call static constructors for example (load stage #3) + * @brief Execute ELF file pre-run stage, + * call static constructors for example (load stage #3) + * Must be done before invoking any code from the ELF file * @param elf */ -void elf_file_pre_run(ELFFile* elf); +void elf_file_call_init(ELFFile* elf); /** - * @brief Run ELF file (load stage #4) + * @brief Check if ELF file pre-run stage was executed and its code is runnable + * @param elf + */ +bool elf_file_is_init_complete(ELFFile* elf); + +/** + * @brief Get actual entry point for ELF file * @param elf_file * @param args * @return int32_t */ -int32_t elf_file_run(ELFFile* elf_file, void* args); +void* elf_file_get_entry_point(ELFFile* elf_file); /** - * @brief Execute ELF file post-run stage, call static destructors for example (load stage #5) + * @brief Execute ELF file post-run stage, + * call static destructors for example (load stage #5) + * Must be done if any code from the ELF file was executed * @param elf */ -void elf_file_post_run(ELFFile* elf); +void elf_file_call_fini(ELFFile* elf); /** * @brief Get ELF file API interface diff --git a/lib/flipper_application/elf/elf_file_i.h b/lib/flipper_application/elf/elf_file_i.h index 9b38540b72d..af9a1d9b4fb 100644 --- a/lib/flipper_application/elf/elf_file_i.h +++ b/lib/flipper_application/elf/elf_file_i.h @@ -45,6 +45,8 @@ struct ELFFile { ELFSection* preinit_array; ELFSection* init_array; ELFSection* fini_array; + + bool init_array_called; }; #ifdef __cplusplus diff --git a/lib/flipper_application/flipper_application.c b/lib/flipper_application/flipper_application.c index 6e20c080976..ca917cf1afb 100644 --- a/lib/flipper_application/flipper_application.c +++ b/lib/flipper_application/flipper_application.c @@ -10,6 +10,7 @@ struct FlipperApplication { FlipperApplicationManifest manifest; ELFFile* elf; FuriThread* thread; + void* ep_thread_args; }; /* For debugger access to app state */ @@ -20,9 +21,14 @@ FlipperApplication* FlipperApplication* app = malloc(sizeof(FlipperApplication)); app->elf = elf_file_alloc(storage, api_interface); app->thread = NULL; + app->ep_thread_args = NULL; return app; } +bool flipper_application_is_plugin(FlipperApplication* app) { + return app->manifest.stack_size == 0; +} + void flipper_application_free(FlipperApplication* app) { furi_assert(app); @@ -31,9 +37,16 @@ void flipper_application_free(FlipperApplication* app) { furi_thread_free(app->thread); } - last_loaded_app = NULL; + if(!flipper_application_is_plugin(app)) { + last_loaded_app = NULL; + } elf_file_clear_debug_info(&app->state); + + if(elf_file_is_init_complete(app->elf)) { + elf_file_call_fini(app->elf); + } + elf_file_free(app->elf); free(app); } @@ -140,7 +153,9 @@ const FlipperApplicationManifest* flipper_application_get_manifest(FlipperApplic } FlipperApplicationLoadStatus flipper_application_map_to_memory(FlipperApplication* app) { - last_loaded_app = app; + if(!flipper_application_is_plugin(app)) { + last_loaded_app = app; + } ELFFileLoadStatus status = elf_file_load_sections(app->elf); switch(status) { @@ -157,9 +172,15 @@ FlipperApplicationLoadStatus flipper_application_map_to_memory(FlipperApplicatio } static int32_t flipper_application_thread(void* context) { - elf_file_pre_run(last_loaded_app->elf); - int32_t result = elf_file_run(last_loaded_app->elf, context); - elf_file_post_run(last_loaded_app->elf); + furi_assert(context); + FlipperApplication* app = (FlipperApplication*)context; + + elf_file_call_init(app->elf); + + FlipperApplicationEntryPoint entry_point = elf_file_get_entry_point(app->elf); + int32_t ret_code = entry_point(app->ep_thread_args); + + elf_file_call_fini(app->elf); // wait until all notifications from RAM are completed NotificationApp* notifications = furi_record_open(RECORD_NOTIFICATION); @@ -169,17 +190,17 @@ static int32_t flipper_application_thread(void* context) { notification_message_block(notifications, &sequence_empty); furi_record_close(RECORD_NOTIFICATION); - return result; + return ret_code; } FuriThread* flipper_application_spawn(FlipperApplication* app, void* args) { furi_check(app->thread == NULL); + furi_check(!flipper_application_is_plugin(app)); + app->ep_thread_args = args; const FlipperApplicationManifest* manifest = flipper_application_get_manifest(app); - furi_check(manifest->stack_size > 0); - app->thread = furi_thread_alloc_ex( - manifest->name, manifest->stack_size, flipper_application_thread, args); + manifest->name, manifest->stack_size, flipper_application_thread, app); return app->thread; } @@ -213,3 +234,28 @@ const char* flipper_application_load_status_to_string(FlipperApplicationLoadStat } return load_status_strings[status]; } + +const FlipperAppPluginDescriptor* + flipper_application_plugin_get_descriptor(FlipperApplication* app) { + if(!flipper_application_is_plugin(app)) { + return NULL; + } + + if(!elf_file_is_init_complete(app->elf)) { + elf_file_call_init(app->elf); + } + + typedef const FlipperAppPluginDescriptor* (*get_lib_descriptor_t)(void); + get_lib_descriptor_t lib_ep = elf_file_get_entry_point(app->elf); + furi_check(lib_ep); + + const FlipperAppPluginDescriptor* lib_descriptor = lib_ep(); + + FURI_LOG_D( + TAG, + "Library for %s, API v. %lu loaded", + lib_descriptor->appid, + lib_descriptor->ep_api_version); + + return lib_descriptor; +} \ No newline at end of file diff --git a/lib/flipper_application/flipper_application.h b/lib/flipper_application/flipper_application.h index b3e5996bbd5..519cc397108 100644 --- a/lib/flipper_application/flipper_application.h +++ b/lib/flipper_application/flipper_application.h @@ -115,6 +115,40 @@ FlipperApplicationLoadStatus flipper_application_map_to_memory(FlipperApplicatio */ FuriThread* flipper_application_spawn(FlipperApplication* app, void* args); +/** + * @brief Check if application is a plugin (not a runnable standalone app) + * @param app Application pointer + * @return true if application is a plugin, false otherwise + */ +bool flipper_application_is_plugin(FlipperApplication* app); + +/** + * @brief Entry point prototype for standalone applications + */ +typedef int32_t (*FlipperApplicationEntryPoint)(void*); + +/** + * @brief An object that describes a plugin - must be returned by plugin's entry point + */ +typedef struct { + const char* appid; + const uint32_t ep_api_version; + const void* entry_point; +} FlipperAppPluginDescriptor; + +/** + * @brief Entry point prototype for plugins + */ +typedef const FlipperAppPluginDescriptor* (*FlipperApplicationPluginEntryPoint)(void); + +/** + * @brief Get plugin descriptor for preloaded plugin + * @param app Application pointer + * @return Pointer to plugin descriptor + */ +const FlipperAppPluginDescriptor* + flipper_application_plugin_get_descriptor(FlipperApplication* app); + #ifdef __cplusplus } -#endif \ No newline at end of file +#endif diff --git a/lib/flipper_application/plugins/composite_resolver.c b/lib/flipper_application/plugins/composite_resolver.c new file mode 100644 index 00000000000..1402c3ad08f --- /dev/null +++ b/lib/flipper_application/plugins/composite_resolver.c @@ -0,0 +1,52 @@ +#include "composite_resolver.h" + +#include +#include + +LIST_DEF(ElfApiInterfaceList, const ElfApiInterface*, M_POD_OPLIST) +#define M_OPL_ElfApiInterfaceList_t() LIST_OPLIST(ElfApiInterfaceList, M_POD_OPLIST) + +struct CompositeApiResolver { + ElfApiInterface api_interface; + ElfApiInterfaceList_t interfaces; +}; + +static bool composite_api_resolver_callback( + const ElfApiInterface* interface, + const char* name, + Elf32_Addr* address) { + CompositeApiResolver* resolver = (CompositeApiResolver*)interface; + for + M_EACH(interface, resolver->interfaces, ElfApiInterfaceList_t) { + if((*interface)->resolver_callback(*interface, name, address)) { + return true; + } + } + return false; +} + +CompositeApiResolver* composite_api_resolver_alloc() { + CompositeApiResolver* resolver = malloc(sizeof(CompositeApiResolver)); + resolver->api_interface.api_version_major = 0; + resolver->api_interface.api_version_minor = 0; + resolver->api_interface.resolver_callback = &composite_api_resolver_callback; + ElfApiInterfaceList_init(resolver->interfaces); + return resolver; +} + +void composite_api_resolver_free(CompositeApiResolver* resolver) { + ElfApiInterfaceList_clear(resolver->interfaces); + free(resolver); +} + +void composite_api_resolver_add(CompositeApiResolver* resolver, const ElfApiInterface* interface) { + if(ElfApiInterfaceList_empty_p(resolver->interfaces)) { + resolver->api_interface.api_version_major = interface->api_version_major; + resolver->api_interface.api_version_minor = interface->api_version_minor; + } + ElfApiInterfaceList_push_back(resolver->interfaces, interface); +} + +const ElfApiInterface* composite_api_resolver_get(CompositeApiResolver* resolver) { + return &resolver->api_interface; +} diff --git a/lib/flipper_application/plugins/composite_resolver.h b/lib/flipper_application/plugins/composite_resolver.h new file mode 100644 index 00000000000..a2d4bab25e8 --- /dev/null +++ b/lib/flipper_application/plugins/composite_resolver.h @@ -0,0 +1,46 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Composite API resolver + * Resolves API interface by calling all resolvers in order + * Uses API version from first resolver + * Note: when using hashtable resolvers, collisions between tables are not detected + * Can be cast to ElfApiInterface* + */ +typedef struct CompositeApiResolver CompositeApiResolver; + +/** + * @brief Allocate composite API resolver + * @return CompositeApiResolver* instance + */ +CompositeApiResolver* composite_api_resolver_alloc(); + +/** + * @brief Free composite API resolver + * @param resolver Instance + */ +void composite_api_resolver_free(CompositeApiResolver* resolver); + +/** + * @brief Add API resolver to composite resolver + * @param resolver Instance + * @param interface API resolver + */ +void composite_api_resolver_add(CompositeApiResolver* resolver, const ElfApiInterface* interface); + +/** + * @brief Get API interface from composite resolver + * @param resolver Instance + * @return API interface + */ +const ElfApiInterface* composite_api_resolver_get(CompositeApiResolver* resolver); + +#ifdef __cplusplus +} +#endif diff --git a/lib/flipper_application/plugins/plugin_manager.c b/lib/flipper_application/plugins/plugin_manager.c new file mode 100644 index 00000000000..101471dc5e5 --- /dev/null +++ b/lib/flipper_application/plugins/plugin_manager.c @@ -0,0 +1,153 @@ +#include "plugin_manager.h" + +#include +#include +#include + +#include +#include + +#include + +#define TAG "libmgr" + +ARRAY_DEF(FlipperApplicationList, FlipperApplication*, M_PTR_OPLIST) +#define M_OPL_FlipperApplicationList_t() ARRAY_OPLIST(FlipperApplicationList, M_PTR_OPLIST) + +struct PluginManager { + const char* application_id; + uint32_t api_version; + Storage* storage; + FlipperApplicationList_t libs; + const ElfApiInterface* api_interface; +}; + +PluginManager* plugin_manager_alloc( + const char* application_id, + uint32_t api_version, + const ElfApiInterface* api_interface) { + PluginManager* manager = malloc(sizeof(PluginManager)); + manager->application_id = application_id; + manager->api_version = api_version; + manager->api_interface = api_interface ? api_interface : firmware_api_interface; + manager->storage = furi_record_open(RECORD_STORAGE); + FlipperApplicationList_init(manager->libs); + return manager; +} + +void plugin_manager_free(PluginManager* manager) { + for + M_EACH(loaded_lib, manager->libs, FlipperApplicationList_t) { + flipper_application_free(*loaded_lib); + } + FlipperApplicationList_clear(manager->libs); + furi_record_close(RECORD_STORAGE); + free(manager); +} + +PluginManagerError plugin_manager_load_single(PluginManager* manager, const char* path) { + FlipperApplication* lib = flipper_application_alloc(manager->storage, manager->api_interface); + + PluginManagerError error = PluginManagerErrorNone; + do { + FlipperApplicationPreloadStatus preload_res = flipper_application_preload(lib, path); + + if(preload_res != FlipperApplicationPreloadStatusSuccess) { + FURI_LOG_E(TAG, "Failed to preload %s", path); + error = PluginManagerErrorLoaderError; + break; + } + + if(!flipper_application_is_plugin(lib)) { + FURI_LOG_E(TAG, "Not a plugin %s", path); + error = PluginManagerErrorLoaderError; + break; + } + + FlipperApplicationLoadStatus load_status = flipper_application_map_to_memory(lib); + if(load_status != FlipperApplicationLoadStatusSuccess) { + FURI_LOG_E(TAG, "Failed to load module_demo_plugin1.fal"); + break; + } + + const FlipperAppPluginDescriptor* app_descriptor = + flipper_application_plugin_get_descriptor(lib); + + if(!app_descriptor) { + FURI_LOG_E(TAG, "Failed to get descriptor %s", path); + error = PluginManagerErrorLoaderError; + break; + } + + if(strcmp(app_descriptor->appid, manager->application_id) != 0) { + FURI_LOG_E(TAG, "Application id mismatch %s", path); + error = PluginManagerErrorApplicationIdMismatch; + break; + } + + if(app_descriptor->ep_api_version != manager->api_version) { + FURI_LOG_E(TAG, "API version mismatch %s", path); + error = PluginManagerErrorAPIVersionMismatch; + break; + } + + FlipperApplicationList_push_back(manager->libs, lib); + } while(false); + + if(error != PluginManagerErrorNone) { + flipper_application_free(lib); + } + + return error; +} + +PluginManagerError plugin_manager_load_all(PluginManager* manager, const char* path) { + File* directory = storage_file_alloc(manager->storage); + char file_name_buffer[256]; + FuriString* file_name = furi_string_alloc(); + do { + if(!storage_dir_open(directory, path)) { + FURI_LOG_E(TAG, "Failed to open directory %s", path); + break; + } + while(true) { + if(!storage_dir_read(directory, NULL, file_name_buffer, sizeof(file_name_buffer))) { + break; + } + + furi_string_set(file_name, file_name_buffer); + if(!furi_string_end_with_str(file_name, ".fal")) { + continue; + } + + path_concat(path, file_name_buffer, file_name); + FURI_LOG_D(TAG, "Loading %s", furi_string_get_cstr(file_name)); + PluginManagerError error = + plugin_manager_load_single(manager, furi_string_get_cstr(file_name)); + + if(error != PluginManagerErrorNone) { + FURI_LOG_E(TAG, "Failed to load %s", furi_string_get_cstr(file_name)); + break; + } + } + } while(false); + storage_dir_close(directory); + storage_file_free(directory); + furi_string_free(file_name); + return PluginManagerErrorNone; +} + +uint32_t plugin_manager_get_count(PluginManager* manager) { + return FlipperApplicationList_size(manager->libs); +} + +const FlipperAppPluginDescriptor* plugin_manager_get(PluginManager* manager, uint32_t index) { + FlipperApplication* app = *FlipperApplicationList_get(manager->libs, index); + return flipper_application_plugin_get_descriptor(app); +} + +const void* plugin_manager_get_ep(PluginManager* manager, uint32_t index) { + const FlipperAppPluginDescriptor* lib_descr = plugin_manager_get(manager, index); + furi_check(lib_descr); + return lib_descr->entry_point; +} diff --git a/lib/flipper_application/plugins/plugin_manager.h b/lib/flipper_application/plugins/plugin_manager.h new file mode 100644 index 00000000000..d94c25db971 --- /dev/null +++ b/lib/flipper_application/plugins/plugin_manager.h @@ -0,0 +1,82 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Object that manages plugins for an application + * Implements mass loading of plugins and provides access to their descriptors + */ +typedef struct PluginManager PluginManager; + +typedef enum { + PluginManagerErrorNone = 0, + PluginManagerErrorLoaderError, + PluginManagerErrorApplicationIdMismatch, + PluginManagerErrorAPIVersionMismatch, +} PluginManagerError; + +/** + * @brief Allocates new PluginManager + * @param application_id Application ID filter - only plugins with matching ID will be loaded + * @param api_version Application API version filter - only plugins with matching API version + * @param api_interface Application API interface - used to resolve plugins' API imports + * If plugin uses private application's API, use CompoundApiInterface + * @return new PluginManager instance + */ +PluginManager* plugin_manager_alloc( + const char* application_id, + uint32_t api_version, + const ElfApiInterface* api_interface); + +/** + * @brief Frees PluginManager + * @param manager PluginManager instance + */ +void plugin_manager_free(PluginManager* manager); + +/** + * @brief Loads single plugin by full path + * @param manager PluginManager instance + * @param path Path to plugin + * @return Error code + */ +PluginManagerError plugin_manager_load_single(PluginManager* manager, const char* path); + +/** + * @brief Loads all plugins from specified directory + * @param manager PluginManager instance + * @param path Path to directory + * @return Error code + */ +PluginManagerError plugin_manager_load_all(PluginManager* manager, const char* path); + +/** + * @brief Returns number of loaded plugins + * @param manager PluginManager instance + * @return Number of loaded plugins + */ +uint32_t plugin_manager_get_count(PluginManager* manager); + +/** + * @brief Returns plugin descriptor by index + * @param manager PluginManager instance + * @param index Plugin index + * @return Plugin descriptor + */ +const FlipperAppPluginDescriptor* plugin_manager_get(PluginManager* manager, uint32_t index); + +/** + * @brief Returns plugin entry point by index + * @param manager PluginManager instance + * @param index Plugin index + * @return Plugin entry point + */ +const void* plugin_manager_get_ep(PluginManager* manager, uint32_t index); + +#ifdef __cplusplus +} +#endif diff --git a/scripts/distfap.py b/scripts/distfap.py new file mode 100644 index 00000000000..060fe26fff1 --- /dev/null +++ b/scripts/distfap.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python3 + +from flipper.app import App +from flipper.storage import FlipperStorage, FlipperStorageOperations +from flipper.utils.cdc import resolve_port + +import os +import posixpath + + +class Main(App): + def init(self): + self.parser.add_argument("-p", "--port", help="CDC Port", default="auto") + self.parser.add_argument( + "-n", + "--no-launch", + dest="launch_app", + action="store_false", + help="Don't launch app", + ) + + self.parser.add_argument("fap_src_path", help="App file to upload") + self.parser.add_argument( + "--fap_dst_dir", help="Upload path", default="/ext/apps", required=False + ) + self.parser.set_defaults(func=self.install) + + def install(self): + if not (port := resolve_port(self.logger, self.args.port)): + return 1 + + try: + with FlipperStorage(port) as storage: + storage_ops = FlipperStorageOperations(storage) + fap_local_path = self.args.fap_src_path + self.args.fap_dst_dir = self.args.fap_dst_dir.rstrip("/\\") + + if not os.path.isfile(fap_local_path): + self.logger.error( + f"Error: source .fap ({fap_local_path}) not found" + ) + return 2 + + fap_dst_path = posixpath.join( + self.args.fap_dst_dir, os.path.basename(fap_local_path) + ) + + self.logger.info(f'Installing "{fap_local_path}" to {fap_dst_path}') + + storage_ops.recursive_send(fap_dst_path, fap_local_path, False) + + if not self.args.launch_app: + return 0 + + storage.send_and_wait_eol( + f'loader open "Applications" {fap_dst_path}\r' + ) + + if len(result := storage.read.until(storage.CLI_EOL)): + self.logger.error(f"Unexpected response: {result.decode('ascii')}") + return 3 + return 0 + + except Exception as e: + self.logger.error(f"Error: {e}") + # raise + return 4 + + +if __name__ == "__main__": + Main()() diff --git a/scripts/fbt/appmanifest.py b/scripts/fbt/appmanifest.py index eb1652b78eb..37ddc434865 100644 --- a/scripts/fbt/appmanifest.py +++ b/scripts/fbt/appmanifest.py @@ -12,13 +12,13 @@ class FlipperAppType(Enum): SERVICE = "Service" SYSTEM = "System" APP = "App" - PLUGIN = "Plugin" DEBUG = "Debug" ARCHIVE = "Archive" SETTINGS = "Settings" STARTUP = "StartupHook" EXTERNAL = "External" METAPACKAGE = "Package" + PLUGIN = "Plugin" @dataclass @@ -69,12 +69,22 @@ class Library: fap_private_libs: List[Library] = field(default_factory=list) fap_file_assets: Optional[str] = None # Internally used by fbt + _appmanager: Optional["AppManager"] = None _appdir: Optional[object] = None _apppath: Optional[str] = None + _plugins: List["FlipperApplication"] = field(default_factory=list) def supports_hardware_target(self, target: str): return target in self.targets or "all" in self.targets + @property + def is_default_deployable(self): + return self.apptype != FlipperAppType.DEBUG and self.fap_category != "Examples" + + def __post_init__(self): + if self.apptype == FlipperAppType.PLUGIN: + self.stack_size = 0 + class AppManager: def __init__(self): @@ -94,6 +104,23 @@ def find_by_appdir(self, appdir: str): return app return None + def _validate_app_params(self, *args, **kw): + apptype = kw.get("apptype") + if apptype == FlipperAppType.PLUGIN: + if kw.get("stack_size"): + raise FlipperManifestException( + f"Plugin {kw.get('appid')} cannot have stack (did you mean FlipperAppType.EXTERNAL?)" + ) + if not kw.get("requires"): + raise FlipperManifestException( + f"Plugin {kw.get('appid')} must have 'requires' in manifest" + ) + # Harmless - cdefines for external apps are meaningless + # if apptype == FlipperAppType.EXTERNAL and kw.get("cdefines"): + # raise FlipperManifestException( + # f"External app {kw.get('appid')} must not have 'cdefines' in manifest" + # ) + def load_manifest(self, app_manifest_path: str, app_dir_node: object): if not os.path.exists(app_manifest_path): raise FlipperManifestException( @@ -105,12 +132,14 @@ def load_manifest(self, app_manifest_path: str, app_dir_node: object): def App(*args, **kw): nonlocal app_manifests + self._validate_app_params(*args, **kw) app_manifests.append( FlipperApplication( *args, **kw, _appdir=app_dir_node, _apppath=os.path.dirname(app_manifest_path), + _appmanager=self, ), ) @@ -155,7 +184,6 @@ class AppBuildset: FlipperAppType.SERVICE, FlipperAppType.SYSTEM, FlipperAppType.APP, - FlipperAppType.PLUGIN, FlipperAppType.DEBUG, FlipperAppType.ARCHIVE, FlipperAppType.SETTINGS, @@ -182,6 +210,7 @@ def __init__( self._check_conflicts() self._check_unsatisfied() # unneeded? self._check_target_match() + self._group_plugins() self.apps = sorted( list(map(self.appmgr.get, self.appnames)), key=lambda app: app.appid, @@ -260,6 +289,18 @@ def _check_target_match(self): f"Apps incompatible with target {self.hw_target}: {', '.join(incompatible)}" ) + def _group_plugins(self): + known_extensions = self.get_apps_of_type(FlipperAppType.PLUGIN, all_known=True) + for extension_app in known_extensions: + for parent_app_id in extension_app.requires: + try: + parent_app = self.appmgr.get(parent_app_id) + parent_app._plugins.append(extension_app) + except FlipperManifestException as e: + self._writer( + f"Module {extension_app.appid} has unknown parent {parent_app_id}" + ) + def get_apps_cdefs(self): cdefs = set() for app in self.apps: @@ -301,7 +342,6 @@ class ApplicationsCGenerator: FlipperAppType.SERVICE: ("FlipperApplication", "FLIPPER_SERVICES"), FlipperAppType.SYSTEM: ("FlipperApplication", "FLIPPER_SYSTEM_APPS"), FlipperAppType.APP: ("FlipperApplication", "FLIPPER_APPS"), - FlipperAppType.PLUGIN: ("FlipperApplication", "FLIPPER_PLUGINS"), FlipperAppType.DEBUG: ("FlipperApplication", "FLIPPER_DEBUG_APPS"), FlipperAppType.SETTINGS: ("FlipperApplication", "FLIPPER_SETTINGS_APPS"), FlipperAppType.STARTUP: ("FlipperOnStartHook", "FLIPPER_ON_SYSTEM_START"), diff --git a/scripts/fbt/fapassets.py b/scripts/fbt/fapassets.py new file mode 100644 index 00000000000..0649f03efe8 --- /dev/null +++ b/scripts/fbt/fapassets.py @@ -0,0 +1,108 @@ +import os +import hashlib +import struct +from typing import TypedDict + + +class File(TypedDict): + path: str + size: int + content_path: str + + +class Dir(TypedDict): + path: str + + +class FileBundler: + """ + u32 magic + u32 version + u32 dirs_count + u32 files_count + u32 signature_size + u8[] signature + Dirs: + u32 dir_name length + u8[] dir_name + Files: + u32 file_name length + u8[] file_name + u32 file_content_size + u8[] file_content + """ + + def __init__(self, directory_path: str): + self.directory_path = directory_path + self.file_list: list[File] = [] + self.directory_list: list[Dir] = [] + self._gather() + + def _gather(self): + for root, dirs, files in os.walk(self.directory_path): + for file_info in files: + file_path = os.path.join(root, file_info) + file_size = os.path.getsize(file_path) + self.file_list.append( + { + "path": os.path.relpath(file_path, self.directory_path), + "size": file_size, + "content_path": file_path, + } + ) + + for dir_info in dirs: + dir_path = os.path.join(root, dir_info) + # dir_size = sum( + # os.path.getsize(os.path.join(dir_path, f)) for f in os.listdir(dir_path) + # ) + self.directory_list.append( + { + "path": os.path.relpath(dir_path, self.directory_path), + } + ) + + self.file_list.sort(key=lambda f: f["path"]) + self.directory_list.sort(key=lambda d: d["path"]) + + def export(self, target_path: str): + self._md5_hash = hashlib.md5() + with open(target_path, "wb") as f: + # Write header magic and version + f.write(struct.pack(" FlipperExternalAppInfo + EXT_LIBS={}, + _APP_ICONS=[], ) env.AddMethod(BuildAppElf) - env.AddMethod(GetExtAppFromPath) + env.AddMethod(GetExtAppByIdOrPath) env.Append( BUILDERS={ "FapDist": Builder( @@ -466,7 +427,7 @@ def generate(env, **kw): generator=generate_embed_app_metadata_actions, suffix=".fap", src_suffix=".elf", - # emitter=generate_embed_app_metadata_emitter, + emitter=embed_app_metadata_emitter, ), "ValidateAppImports": Builder( action=[ diff --git a/scripts/fbt_tools/fbt_sdk.py b/scripts/fbt_tools/fbt_sdk.py index 3a37eacc972..324819818c0 100644 --- a/scripts/fbt_tools/fbt_sdk.py +++ b/scripts/fbt_tools/fbt_sdk.py @@ -220,7 +220,7 @@ def gen_sdk_data(sdk_cache: SdkCache): def _check_sdk_is_up2date(sdk_cache: SdkCache): if not sdk_cache.is_buildable(): raise UserError( - "SDK version is not finalized, please review changes and re-run operation" + "SDK version is not finalized, please review changes and re-run operation. See AppsOnSDCard.md for more details" ) diff --git a/scripts/flipper/storage.py b/scripts/flipper/storage.py index 9c9f529582c..47e11236d11 100644 --- a/scripts/flipper/storage.py +++ b/scripts/flipper/storage.py @@ -4,6 +4,9 @@ import time import hashlib import math +import logging +import posixpath +import enum def timing(func): @@ -25,12 +28,47 @@ def wrapper(*args, **kwargs): return wrapper +class StorageErrorCode(enum.Enum): + OK = "OK" + NOT_READY = "filesystem not ready" + EXIST = "file/dir already exist" + NOT_EXIST = "file/dir not exist" + INVALID_PARAMETER = "invalid parameter" + DENIED = "access denied" + INVALID_NAME = "invalid name/path" + INTERNAL = "internal error" + NOT_IMPLEMENTED = "function not implemented" + ALREADY_OPEN = "file is already open" + UNKNOWN = "unknown error" + + @property + def is_error(self): + return self != self.OK + + @classmethod + def from_value(cls, s: str | bytes): + if isinstance(s, bytes): + s = s.decode("ascii") + for code in cls: + if code.value == s: + return code + return cls.UNKNOWN + + +class FlipperStorageException(Exception): + def __init__(self, message): + super().__init__(f"Storage error: {message}") + + def __init__(self, path: str, error_code: StorageErrorCode): + super().__init__(f"Storage error: path '{path}': {error_code.value}") + + class BufferedRead: def __init__(self, stream): self.buffer = bytearray() self.stream = stream - def until(self, eol="\n", cut_eol=True): + def until(self, eol: str = "\n", cut_eol: bool = True): eol = eol.encode("ascii") while True: # search in buffer @@ -59,9 +97,15 @@ def __init__(self, portname: str, chunk_size: int = 8192): self.port.timeout = 2 self.port.baudrate = 115200 # Doesn't matter for VCP self.read = BufferedRead(self.port) - self.last_error = "" self.chunk_size = chunk_size + def __enter__(self): + self.start() + return self + + def __exit__(self, exc_type, exc_value, traceback): + self.stop() + def start(self): self.port.open() self.port.reset_input_buffer() @@ -71,37 +115,34 @@ def start(self): # And read buffer until we get prompt self.read.until(self.CLI_PROMPT) - def stop(self): + def stop(self) -> None: self.port.close() - def send(self, line): + def send(self, line: str) -> None: self.port.write(line.encode("ascii")) - def send_and_wait_eol(self, line): + def send_and_wait_eol(self, line: str): self.send(line) return self.read.until(self.CLI_EOL) - def send_and_wait_prompt(self, line): + def send_and_wait_prompt(self, line: str): self.send(line) return self.read.until(self.CLI_PROMPT) - def has_error(self, data): - """Is data has error""" - if data.find(b"Storage error") != -1: - return True - else: - return False + def has_error(self, data: bytes | str) -> bool: + """Is data an error message""" + return data.find(b"Storage error:") != -1 - def get_error(self, data): + def get_error(self, data: bytes) -> StorageErrorCode: """Extract error text from data and print it""" - error, error_text = data.decode("ascii").split(": ") - return error_text.strip() + _, error_text = data.decode("ascii").split(": ") + return StorageErrorCode.from_value(error_text.strip()) - def list_tree(self, path="/", level=0): + def list_tree(self, path: str = "/", level: int = 0): """List files and dirs on Flipper""" path = path.replace("//", "/") - self.send_and_wait_eol('storage list "' + path + '"\r') + self.send_and_wait_eol(f'storage list "{path}"\r') data = self.read.until(self.CLI_PROMPT) lines = data.split(b"\r\n") @@ -139,7 +180,7 @@ def list_tree(self, path="/", level=0): # Something wrong, pass pass - def walk(self, path="/"): + def walk(self, path: str = "/"): dirs = [] nondirs = [] walk_dirs = [] @@ -181,14 +222,15 @@ def walk(self, path="/"): # Something wrong, pass pass - # topdown walk, yield before recursy + # topdown walk, yield before recursing yield path, dirs, nondirs for new_path in walk_dirs: yield from self.walk(new_path) - def send_file(self, filename_from, filename_to): + def send_file(self, filename_from: str, filename_to: str): """Send file from local device to Flipper""" - self.remove(filename_to) + if self.exist_file(filename_to): + self.remove(filename_to) with open(filename_from, "rb") as file: filesize = os.fstat(file.fileno()).st_size @@ -203,9 +245,9 @@ def send_file(self, filename_from, filename_to): self.send_and_wait_eol(f'storage write_chunk "{filename_to}" {size}\r') answer = self.read.until(self.CLI_EOL) if self.has_error(answer): - self.last_error = self.get_error(answer) + last_error = self.get_error(answer) self.read.until(self.CLI_PROMPT) - return False + raise FlipperStorageException(filename_to, last_error) self.port.write(filedata) self.read.until(self.CLI_PROMPT) @@ -218,9 +260,8 @@ def send_file(self, filename_from, filename_to): ) sys.stdout.flush() print() - return True - def read_file(self, filename): + def read_file(self, filename: str): """Receive file from Flipper, and get filedata (bytes)""" buffer_size = self.chunk_size self.send_and_wait_eol( @@ -229,9 +270,10 @@ def read_file(self, filename): answer = self.read.until(self.CLI_EOL) filedata = bytearray() if self.has_error(answer): - self.last_error = self.get_error(answer) + last_error = self.get_error(answer) self.read.until(self.CLI_PROMPT) - return filedata + raise FlipperStorageException(filename, last_error) + # return filedata size = int(answer.split(b": ")[1]) read_size = 0 @@ -251,121 +293,89 @@ def read_file(self, filename): self.read.until(self.CLI_PROMPT) return filedata - def receive_file(self, filename_from, filename_to): + def receive_file(self, filename_from: str, filename_to: str): """Receive file from Flipper to local storage""" with open(filename_to, "wb") as file: data = self.read_file(filename_from) - if not data: - return False - else: - file.write(data) - return True + file.write(data) - def exist(self, path): - """Is file or dir exist on Flipper""" - self.send_and_wait_eol('storage stat "' + path + '"\r') - answer = self.read.until(self.CLI_EOL) + def exist(self, path: str): + """Does file or dir exist on Flipper""" + self.send_and_wait_eol(f'storage stat "{path}"\r') + response = self.read.until(self.CLI_EOL) self.read.until(self.CLI_PROMPT) - if self.has_error(answer): - self.last_error = self.get_error(answer) - return False - else: - return True + return not self.has_error(response) - def exist_dir(self, path): - """Is dir exist on Flipper""" - self.send_and_wait_eol('storage stat "' + path + '"\r') - answer = self.read.until(self.CLI_EOL) + def exist_dir(self, path: str): + """Does dir exist on Flipper""" + self.send_and_wait_eol(f'storage stat "{path}"\r') + response = self.read.until(self.CLI_EOL) self.read.until(self.CLI_PROMPT) - - if self.has_error(answer): - self.last_error = self.get_error(answer) - return False - else: - if answer.find(b"Directory") != -1: - return True - elif answer.find(b"Storage") != -1: - return True - else: + if self.has_error(response): + error_code = self.get_error(response) + if error_code in ( + StorageErrorCode.NOT_EXIST, + StorageErrorCode.INVALID_NAME, + ): return False + raise FlipperStorageException(path, error_code) - def exist_file(self, path): - """Is file exist on Flipper""" - self.send_and_wait_eol('storage stat "' + path + '"\r') - answer = self.read.until(self.CLI_EOL) + return True + + def exist_file(self, path: str): + """Does file exist on Flipper""" + self.send_and_wait_eol(f'storage stat "{path}"\r') + response = self.read.until(self.CLI_EOL) self.read.until(self.CLI_PROMPT) - if self.has_error(answer): - self.last_error = self.get_error(answer) - return False - else: - if answer.find(b"File, size:") != -1: - return True - else: - return False + return response.find(b"File, size:") != -1 + + def _check_no_error(self, response, path=None): + if self.has_error(response): + raise FlipperStorageException(self.get_error(response)) - def size(self, path): + def size(self, path: str): """file size on Flipper""" - self.send_and_wait_eol('storage stat "' + path + '"\r') - answer = self.read.until(self.CLI_EOL) + self.send_and_wait_eol(f'storage stat "{path}"\r') + response = self.read.until(self.CLI_EOL) self.read.until(self.CLI_PROMPT) - if self.has_error(answer): - self.last_error = self.get_error(answer) - return False - else: - if answer.find(b"File, size:") != -1: - size = int( - "".join( - ch - for ch in answer.split(b": ")[1].decode("ascii") - if ch.isdigit() - ) + self._check_no_error(response, path) + if response.find(b"File, size:") != -1: + size = int( + "".join( + ch + for ch in response.split(b": ")[1].decode("ascii") + if ch.isdigit() ) - return size - else: - self.last_error = "access denied" - return -1 + ) + return size + raise FlipperStorageException("Not a file") - def mkdir(self, path): + def mkdir(self, path: str): """Create a directory on Flipper""" - self.send_and_wait_eol('storage mkdir "' + path + '"\r') - answer = self.read.until(self.CLI_EOL) + self.send_and_wait_eol(f'storage mkdir "{path}"\r') + response = self.read.until(self.CLI_EOL) self.read.until(self.CLI_PROMPT) - - if self.has_error(answer): - self.last_error = self.get_error(answer) - return False - else: - return True + self._check_no_error(response, path) def format_ext(self): - """Create a directory on Flipper""" + """Format external storage on Flipper""" self.send_and_wait_eol("storage format /ext\r") self.send_and_wait_eol("y\r") - answer = self.read.until(self.CLI_EOL) + response = self.read.until(self.CLI_EOL) self.read.until(self.CLI_PROMPT) + self._check_no_error(response, "/ext") - if self.has_error(answer): - self.last_error = self.get_error(answer) - return False - else: - return True - - def remove(self, path): + def remove(self, path: str): """Remove file or directory on Flipper""" - self.send_and_wait_eol('storage remove "' + path + '"\r') - answer = self.read.until(self.CLI_EOL) + self.send_and_wait_eol(f'storage remove "{path}"\r') + response = self.read.until(self.CLI_EOL) self.read.until(self.CLI_PROMPT) + self._check_no_error(response, path) - if self.has_error(answer): - self.last_error = self.get_error(answer) - return False - else: - return True - - def hash_local(self, filename): + def hash_local(self, filename: str): """Hash of local file""" hash_md5 = hashlib.md5() with open(filename, "rb") as f: @@ -373,14 +383,112 @@ def hash_local(self, filename): hash_md5.update(chunk) return hash_md5.hexdigest() - def hash_flipper(self, filename): + def hash_flipper(self, filename: str): """Get hash of file on Flipper""" self.send_and_wait_eol('storage md5 "' + filename + '"\r') hash = self.read.until(self.CLI_EOL) self.read.until(self.CLI_PROMPT) + self._check_no_error(hash, filename) + return hash.decode("ascii") + + +class FlipperStorageOperations: + def __init__(self, storage): + self.storage: FlipperStorage = storage + self.logger = logging.getLogger("FStorageOps") + + def send_file_to_storage( + self, flipper_file_path: str, local_file_path: str, force: bool = False + ): + self.logger.debug( + f"* send_file_to_storage: {local_file_path}->{flipper_file_path}, {force=}" + ) + exists = self.storage.exist_file(flipper_file_path) + do_upload = not exists + if exists: + hash_local = self.storage.hash_local(local_file_path) + hash_flipper = self.storage.hash_flipper(flipper_file_path) + self.logger.debug(f"hash check: local {hash_local}, flipper {hash_flipper}") + do_upload = force or (hash_local != hash_flipper) + + if do_upload: + self.logger.info(f'Sending "{local_file_path}" to "{flipper_file_path}"') + self.storage.send_file(local_file_path, flipper_file_path) + + # make directory with exist check + def mkpath(self, flipper_dir_path: str): + path_components, dirs_to_create = flipper_dir_path.split("/"), [] + while not self.storage.exist_dir(dir_path := "/".join(path_components)): + self.logger.debug(f'"{dir_path}" does not exist, will create') + dirs_to_create.append(path_components.pop()) + for dir_to_create in reversed(dirs_to_create): + path_components.append(dir_to_create) + self.storage.mkdir("/".join(path_components)) + + # send file or folder recursively + def recursive_send(self, flipper_path: str, local_path: str, force: bool = False): + if not os.path.exists(local_path): + raise FlipperStorageException(f'"{local_path}" does not exist') + + if os.path.isdir(local_path): + # create parent dir + self.mkpath(flipper_path) + + for dirpath, dirnames, filenames in os.walk(local_path): + self.logger.debug(f'Processing directory "{os.path.normpath(dirpath)}"') + dirnames.sort() + filenames.sort() + rel_path = os.path.relpath(dirpath, local_path) + + # create subdirs + for dirname in dirnames: + flipper_dir_path = os.path.join(flipper_path, rel_path, dirname) + flipper_dir_path = os.path.normpath(flipper_dir_path).replace( + os.sep, "/" + ) + self.mkpath(flipper_dir_path) + + # send files + for filename in filenames: + flipper_file_path = os.path.join(flipper_path, rel_path, filename) + flipper_file_path = os.path.normpath(flipper_file_path).replace( + os.sep, "/" + ) + local_file_path = os.path.normpath(os.path.join(dirpath, filename)) + self.send_file_to_storage(flipper_file_path, local_file_path, force) + else: + self.mkpath(posixpath.dirname(flipper_path)) + self.send_file_to_storage(flipper_path, local_path, force) + + def recursive_receive(self, flipper_path: str, local_path: str): + if self.storage.exist_dir(flipper_path): + for dirpath, dirnames, filenames in self.storage.walk(flipper_path): + self.logger.debug( + f'Processing directory "{os.path.normpath(dirpath)}"'.replace( + os.sep, "/" + ) + ) + dirnames.sort() + filenames.sort() + + rel_path = os.path.relpath(dirpath, flipper_path) + + for dirname in dirnames: + local_dir_path = os.path.join(local_path, rel_path, dirname) + local_dir_path = os.path.normpath(local_dir_path) + os.makedirs(local_dir_path, exist_ok=True) + + for filename in filenames: + local_file_path = os.path.join(local_path, rel_path, filename) + local_file_path = os.path.normpath(local_file_path) + flipper_file_path = os.path.normpath( + os.path.join(dirpath, filename) + ).replace(os.sep, "/") + self.logger.info( + f'Receiving "{flipper_file_path}" to "{local_file_path}"' + ) + self.storage.receive_file(flipper_file_path, local_file_path) - if self.has_error(hash): - self.last_error = self.get_error(hash) - return "" else: - return hash.decode("ascii") + self.logger.info(f'Receiving "{flipper_path}" to "{local_path}"') + self.storage.receive_file(flipper_path, local_path) diff --git a/scripts/requirements.txt b/scripts/requirements.txt deleted file mode 100644 index 5b6fac5f7da..00000000000 --- a/scripts/requirements.txt +++ /dev/null @@ -1,9 +0,0 @@ -ansi==0.3.6 -black==22.6.0 -colorlog==6.7.0 -heatshrink2==0.11.0 -Pillow==9.1.1 -protobuf==3.20.1 -pyserial==3.5 -python3-protobuf==2.5.0 -SCons==4.4.0 diff --git a/scripts/runfap.py b/scripts/runfap.py index 410b3e7d2b1..f8ff607c1c3 100644 --- a/scripts/runfap.py +++ b/scripts/runfap.py @@ -1,108 +1,86 @@ #!/usr/bin/env python3 -import posixpath -from typing import final from flipper.app import App -from flipper.storage import FlipperStorage +from flipper.storage import FlipperStorage, FlipperStorageOperations from flipper.utils.cdc import resolve_port -import logging import os -import pathlib -import serial.tools.list_ports as list_ports +import posixpath +from functools import reduce +import operator class Main(App): def init(self): self.parser.add_argument("-p", "--port", help="CDC Port", default="auto") self.parser.add_argument( - "-n", - "--no-launch", - dest="launch_app", - action="store_false", - help="Don't launch app", + "--sources", + "-s", + nargs="+", + action="append", + default=[], + help="Files to send", ) - - self.parser.add_argument("fap_src_path", help="App file to upload") self.parser.add_argument( - "--fap_dst_dir", help="Upload path", default="/ext/apps", required=False + "--targets", + "-t", + nargs="+", + action="append", + default=[], + help="File destinations (must be same length as -s)", ) + self.parser.add_argument( + "--host-app", + "-a", + help="Host app to launch", + ) + self.parser.set_defaults(func=self.install) - # logging - self.logger = logging.getLogger() - - # make directory with exist check - def mkdir_on_storage(self, storage, flipper_dir_path): - if not storage.exist_dir(flipper_dir_path): - self.logger.debug(f'"{flipper_dir_path}" does not exist, creating') - if not storage.mkdir(flipper_dir_path): - self.logger.error(f"Error: {storage.last_error}") - return False - else: - self.logger.debug(f'"{flipper_dir_path}" already exists') - return True - - # send file with exist check and hash check - def send_file_to_storage(self, storage, flipper_file_path, local_file_path, force): - exists = storage.exist_file(flipper_file_path) - do_upload = not exists - if exists: - hash_local = storage.hash_local(local_file_path) - hash_flipper = storage.hash_flipper(flipper_file_path) - self.logger.debug(f"hash check: local {hash_local}, flipper {hash_flipper}") - do_upload = force or (hash_local != hash_flipper) - - if do_upload: - self.logger.info(f'Sending "{local_file_path}" to "{flipper_file_path}"') - if not storage.send_file(local_file_path, flipper_file_path): - self.logger.error(f"Error: {storage.last_error}") - return False - return True + @staticmethod + def flatten(l): + return reduce(operator.concat, l, []) def install(self): - if not (port := resolve_port(self.logger, self.args.port)): + self.args.sources = self.flatten(self.args.sources) + self.args.targets = self.flatten(self.args.targets) + + if len(self.args.sources) != len(self.args.targets): + self.logger.error( + f"Error: sources ({self.args.sources}) and targets ({self.args.targets}) must be same length" + ) return 1 - storage = FlipperStorage(port) - storage.start() + if not (port := resolve_port(self.logger, self.args.port)): + return 2 try: - fap_local_path = self.args.fap_src_path - self.args.fap_dst_dir = self.args.fap_dst_dir.rstrip("/\\") - - if not os.path.isfile(fap_local_path): - self.logger.error(f"Error: source .fap ({fap_local_path}) not found") - return -1 - - fap_dst_path = posixpath.join( - self.args.fap_dst_dir, os.path.basename(fap_local_path) - ) + with FlipperStorage(port) as storage: + storage_ops = FlipperStorageOperations(storage) + for fap_local_path, fap_dst_path in zip( + self.args.sources, self.args.targets + ): + self.logger.info(f'Installing "{fap_local_path}" to {fap_dst_path}') - self.logger.info(f'Installing "{fap_local_path}" to {fap_dst_path}') + storage_ops.recursive_send(fap_dst_path, fap_local_path, False) - if not self.mkdir_on_storage(storage, self.args.fap_dst_dir): - self.logger.error(f"Error: cannot create dir: {storage.last_error}") - return -2 + fap_host_app = self.args.targets[0] + startup_command = f'"Applications" {fap_host_app}' + if self.args.host_app: + startup_command = self.args.host_app - if not self.send_file_to_storage( - storage, fap_dst_path, fap_local_path, False - ): - self.logger.error(f"Error: upload failed: {storage.last_error}") - return -3 + self.logger.info(f"Launching app: {startup_command}") + storage.send_and_wait_eol(f"loader open {startup_command}\r") - if self.args.launch_app: - storage.send_and_wait_eol( - f'loader open "Applications" {fap_dst_path}\r' - ) - result = storage.read.until(storage.CLI_EOL) - if len(result): + if len(result := storage.read.until(storage.CLI_EOL)): self.logger.error(f"Unexpected response: {result.decode('ascii')}") - return -4 + return 3 + return 0 - return 0 - finally: - storage.stop() + except Exception as e: + self.logger.error(f"Error: {e}") + # raise + return 4 if __name__ == "__main__": diff --git a/scripts/selfupdate.py b/scripts/selfupdate.py index 1c16c5ca6a6..9bfbfefa377 100644 --- a/scripts/selfupdate.py +++ b/scripts/selfupdate.py @@ -2,7 +2,7 @@ from typing import final from flipper.app import App -from flipper.storage import FlipperStorage +from flipper.storage import FlipperStorage, FlipperStorageOperations from flipper.utils.cdc import resolve_port import logging @@ -24,89 +24,47 @@ def init(self): # logging self.logger = logging.getLogger() - # make directory with exist check - def mkdir_on_storage(self, storage, flipper_dir_path): - if not storage.exist_dir(flipper_dir_path): - self.logger.debug(f'"{flipper_dir_path}" does not exist, creating') - if not storage.mkdir(flipper_dir_path): - self.logger.error(f"Error: {storage.last_error}") - return False - else: - self.logger.debug(f'"{flipper_dir_path}" already exists') - return True - - # send file with exist check and hash check - def send_file_to_storage(self, storage, flipper_file_path, local_file_path, force): - exists = storage.exist_file(flipper_file_path) - do_upload = not exists - if exists: - hash_local = storage.hash_local(local_file_path) - hash_flipper = storage.hash_flipper(flipper_file_path) - self.logger.debug(f"hash check: local {hash_local}, flipper {hash_flipper}") - do_upload = force or (hash_local != hash_flipper) - - if do_upload: - self.logger.info(f'Sending "{local_file_path}" to "{flipper_file_path}"') - if not storage.send_file(local_file_path, flipper_file_path): - self.logger.error(f"Error: {storage.last_error}") - return False - return True - def install(self): if not (port := resolve_port(self.logger, self.args.port)): return 1 - storage = FlipperStorage(port) - storage.start() + if not os.path.isfile(self.args.manifest_path): + self.logger.error("Error: manifest not found") + return 2 - try: - if not os.path.isfile(self.args.manifest_path): - self.logger.error("Error: manifest not found") - return 2 + manifest_path = pathlib.Path(os.path.abspath(self.args.manifest_path)) + manifest_name, pkg_name = manifest_path.parts[-1], manifest_path.parts[-2] - manifest_path = pathlib.Path(os.path.abspath(self.args.manifest_path)) - manifest_name, pkg_name = manifest_path.parts[-1], manifest_path.parts[-2] + pkg_dir_name = self.args.pkg_dir_name or pkg_name + update_root = "/ext/update" + flipper_update_path = f"{update_root}/{pkg_dir_name}" - pkg_dir_name = self.args.pkg_dir_name or pkg_name - update_root = "/ext/update" - flipper_update_path = f"{update_root}/{pkg_dir_name}" + self.logger.info(f'Installing "{pkg_name}" from {flipper_update_path}') - self.logger.info(f'Installing "{pkg_name}" from {flipper_update_path}') - # if not os.path.exists(self.args.manifest_path): - # self.logger.error("Error: package not found") - if not self.mkdir_on_storage( - storage, update_root - ) or not self.mkdir_on_storage(storage, flipper_update_path): - self.logger.error(f"Error: cannot create {storage.last_error}") - return -2 - - for dirpath, dirnames, filenames in os.walk(manifest_path.parents[0]): - for fname in filenames: - self.logger.debug(f"Uploading {fname}") - local_file_path = os.path.join(dirpath, fname) - flipper_file_path = f"{flipper_update_path}/{fname}" - if not self.send_file_to_storage( - storage, flipper_file_path, local_file_path, False - ): - self.logger.error(f"Error: {storage.last_error}") - return -3 + try: + with FlipperStorage(port) as storage: + storage_ops = FlipperStorageOperations(storage) + storage_ops.mkpath(update_root) + storage_ops.mkpath(flipper_update_path) + storage_ops.recursive_send( + flipper_update_path, manifest_path.parents[0] + ) - # return -11 storage.send_and_wait_eol( f"update install {flipper_update_path}/{manifest_name}\r" ) result = storage.read.until(storage.CLI_EOL) if not b"Verifying" in result: self.logger.error(f"Unexpected response: {result.decode('ascii')}") - return -4 + return 3 result = storage.read.until(storage.CLI_EOL) if not result.startswith(b"OK"): self.logger.error(result.decode("ascii")) - return -5 - break - return 0 - finally: - storage.stop() + return 4 + return 0 + except Exception as e: + self.logger.error(e) + return 5 if __name__ == "__main__": diff --git a/scripts/storage.py b/scripts/storage.py index ee5dabd439d..84c01021a45 100755 --- a/scripts/storage.py +++ b/scripts/storage.py @@ -1,16 +1,28 @@ #!/usr/bin/env python3 from flipper.app import App -from flipper.storage import FlipperStorage +from flipper.storage import FlipperStorage, FlipperStorageOperations from flipper.utils.cdc import resolve_port -import logging import os import binascii import filecmp import tempfile +def WrapStorageOp(func): + def wrapper(*args, **kwargs): + try: + func(*args, **kwargs) + return 0 + except Exception as e: + print(f"Error: {e}") + # raise # uncomment to debug + return 1 + + return wrapper + + class Main(App): def init(self): self.parser.add_argument("-p", "--port", help="CDC Port", default="auto") @@ -71,229 +83,71 @@ def init(self): ) self.parser_stress.set_defaults(func=self.stress) - def _get_storage(self): + def _get_port(self): if not (port := resolve_port(self.logger, self.args.port)): - return None - - storage = FlipperStorage(port) - storage.start() - return storage + raise Exception("Failed to resolve port") + return port + @WrapStorageOp def mkdir(self): - if not (storage := self._get_storage()): - return 1 - self.logger.debug(f'Creating "{self.args.flipper_path}"') - if not storage.mkdir(self.args.flipper_path): - self.logger.error(f"Error: {storage.last_error}") - storage.stop() - return 0 + with FlipperStorage(self._get_port()) as storage: + storage.mkdir(self.args.flipper_path) + @WrapStorageOp def remove(self): - if not (storage := self._get_storage()): - return 1 - self.logger.debug(f'Removing "{self.args.flipper_path}"') - if not storage.remove(self.args.flipper_path): - self.logger.error(f"Error: {storage.last_error}") - storage.stop() - return 0 + with FlipperStorage(self._get_port()) as storage: + storage.remove(self.args.flipper_path) + @WrapStorageOp def receive(self): - if not (storage := self._get_storage()): - return 1 - - if storage.exist_dir(self.args.flipper_path): - for dirpath, dirnames, filenames in storage.walk(self.args.flipper_path): - self.logger.debug( - f'Processing directory "{os.path.normpath(dirpath)}"'.replace( - os.sep, "/" - ) - ) - dirnames.sort() - filenames.sort() - - rel_path = os.path.relpath(dirpath, self.args.flipper_path) - - for dirname in dirnames: - local_dir_path = os.path.join( - self.args.local_path, rel_path, dirname - ) - local_dir_path = os.path.normpath(local_dir_path) - os.makedirs(local_dir_path, exist_ok=True) - - for filename in filenames: - local_file_path = os.path.join( - self.args.local_path, rel_path, filename - ) - local_file_path = os.path.normpath(local_file_path) - flipper_file_path = os.path.normpath( - os.path.join(dirpath, filename) - ).replace(os.sep, "/") - self.logger.info( - f'Receiving "{flipper_file_path}" to "{local_file_path}"' - ) - if not storage.receive_file(flipper_file_path, local_file_path): - self.logger.error(f"Error: {storage.last_error}") - - else: - self.logger.info( - f'Receiving "{self.args.flipper_path}" to "{self.args.local_path}"' + with FlipperStorage(self._get_port()) as storage: + FlipperStorageOperations(storage).recursive_receive( + self.args.flipper_path, self.args.local_path ) - if not storage.receive_file(self.args.flipper_path, self.args.local_path): - self.logger.error(f"Error: {storage.last_error}") - storage.stop() - return 0 + @WrapStorageOp def send(self): - if not (storage := self._get_storage()): - return 1 - - self.send_to_storage( - storage, self.args.flipper_path, self.args.local_path, self.args.force - ) - storage.stop() - return 0 - - # send file or folder recursively - def send_to_storage(self, storage, flipper_path, local_path, force): - if not os.path.exists(local_path): - self.logger.error(f'Error: "{local_path}" is not exist') - - if os.path.isdir(local_path): - # create parent dir - self.mkdir_on_storage(storage, flipper_path) - - for dirpath, dirnames, filenames in os.walk(local_path): - self.logger.debug(f'Processing directory "{os.path.normpath(dirpath)}"') - dirnames.sort() - filenames.sort() - rel_path = os.path.relpath(dirpath, local_path) - - # create subdirs - for dirname in dirnames: - flipper_dir_path = os.path.join(flipper_path, rel_path, dirname) - flipper_dir_path = os.path.normpath(flipper_dir_path).replace( - os.sep, "/" - ) - self.mkdir_on_storage(storage, flipper_dir_path) - - # send files - for filename in filenames: - flipper_file_path = os.path.join(flipper_path, rel_path, filename) - flipper_file_path = os.path.normpath(flipper_file_path).replace( - os.sep, "/" - ) - local_file_path = os.path.normpath(os.path.join(dirpath, filename)) - self.send_file_to_storage( - storage, flipper_file_path, local_file_path, force - ) - else: - self.send_file_to_storage(storage, flipper_path, local_path, force) - - # make directory with exist check - def mkdir_on_storage(self, storage, flipper_dir_path): - if not storage.exist_dir(flipper_dir_path): - self.logger.debug(f'"{flipper_dir_path}" does not exist, creating') - if not storage.mkdir(flipper_dir_path): - self.logger.error(f"Error: {storage.last_error}") - else: - self.logger.debug(f'"{flipper_dir_path}" already exists') - - # send file with exist check and hash check - def send_file_to_storage(self, storage, flipper_file_path, local_file_path, force): - if not storage.exist_file(flipper_file_path): - self.logger.debug( - f'"{flipper_file_path}" does not exist, sending "{local_file_path}"' - ) - self.logger.info(f'Sending "{local_file_path}" to "{flipper_file_path}"') - if not storage.send_file(local_file_path, flipper_file_path): - self.logger.error(f"Error: {storage.last_error}") - elif force: - self.logger.debug( - f'"{flipper_file_path}" exists, but will be overwritten by "{local_file_path}"' + with FlipperStorage(self._get_port()) as storage: + FlipperStorageOperations(storage).recursive_send( + self.args.flipper_path, self.args.local_path, self.args.force ) - self.logger.info(f'Sending "{local_file_path}" to "{flipper_file_path}"') - if not storage.send_file(local_file_path, flipper_file_path): - self.logger.error(f"Error: {storage.last_error}") - else: - self.logger.debug( - f'"{flipper_file_path}" exists, compare hash with "{local_file_path}"' - ) - hash_local = storage.hash_local(local_file_path) - hash_flipper = storage.hash_flipper(flipper_file_path) - - if not hash_flipper: - self.logger.error(f"Error: {storage.last_error}") - - if hash_local == hash_flipper: - self.logger.debug( - f'"{flipper_file_path}" is equal to "{local_file_path}"' - ) - else: - self.logger.debug( - f'"{flipper_file_path}" is NOT equal to "{local_file_path}"' - ) - self.logger.info( - f'Sending "{local_file_path}" to "{flipper_file_path}"' - ) - if not storage.send_file(local_file_path, flipper_file_path): - self.logger.error(f"Error: {storage.last_error}") + @WrapStorageOp def read(self): - if not (storage := self._get_storage()): - return 1 - self.logger.debug(f'Reading "{self.args.flipper_path}"') - data = storage.read_file(self.args.flipper_path) - if not data: - self.logger.error(f"Error: {storage.last_error}") - else: + with FlipperStorage(self._get_port()) as storage: + data = storage.read_file(self.args.flipper_path) try: print("Text data:") print(data.decode()) except: print("Binary hexadecimal data:") print(binascii.hexlify(data).decode()) - storage.stop() - return 0 + @WrapStorageOp def size(self): - if not (storage := self._get_storage()): - return 1 - self.logger.debug(f'Getting size of "{self.args.flipper_path}"') - size = storage.size(self.args.flipper_path) - if size < 0: - self.logger.error(f"Error: {storage.last_error}") - else: - print(size) - storage.stop() - return 0 + with FlipperStorage(self._get_port()) as storage: + print(storage.size(self.args.flipper_path)) + @WrapStorageOp def list(self): - if not (storage := self._get_storage()): - return 1 - self.logger.debug(f'Listing "{self.args.flipper_path}"') - storage.list_tree(self.args.flipper_path) - storage.stop() - return 0 + with FlipperStorage(self._get_port()) as storage: + storage.list_tree(self.args.flipper_path) + @WrapStorageOp def format_ext(self): - if not (storage := self._get_storage()): - return 1 - self.logger.debug("Formatting /ext SD card") + with FlipperStorage(self._get_port()) as storage: + storage.format_ext() - if not storage.format_ext(): - self.logger.error(f"Error: {storage.last_error}") - storage.stop() - return 0 - + @WrapStorageOp def stress(self): self.logger.error("This test is wearing out flash memory.") - self.logger.error("Never use it with internal storage(/int)") + self.logger.error("Never use it with internal storage (/int)") if self.args.flipper_path.startswith( "/int" @@ -312,24 +166,19 @@ def stress(self): with open(send_file_name, "w") as fout: fout.write("A" * self.args.file_size) - storage = self._get_storage() - if not storage: - return 1 - - if storage.exist_file(self.args.flipper_path): - self.logger.error("File exists, remove it first") - return - while self.args.count > 0: - storage.send_file(send_file_name, self.args.flipper_path) - storage.receive_file(self.args.flipper_path, receive_file_name) - if not filecmp.cmp(receive_file_name, send_file_name): - self.logger.error("Files mismatch") - break - storage.remove(self.args.flipper_path) - os.unlink(receive_file_name) - self.args.count -= 1 - storage.stop() - return 0 + with FlipperStorage(self._get_port()) as storage: + if storage.exist_file(self.args.flipper_path): + self.logger.error("File exists, remove it first") + return + while self.args.count > 0: + storage.send_file(send_file_name, self.args.flipper_path) + storage.receive_file(self.args.flipper_path, receive_file_name) + if not filecmp.cmp(receive_file_name, send_file_name): + self.logger.error("Files mismatch") + break + storage.remove(self.args.flipper_path) + os.unlink(receive_file_name) + self.args.count -= 1 if __name__ == "__main__": diff --git a/site_scons/commandline.scons b/site_scons/commandline.scons index e3ddc59aaa7..d832a466e37 100644 --- a/site_scons/commandline.scons +++ b/site_scons/commandline.scons @@ -194,10 +194,6 @@ vars.AddVariables( "system_apps", # Settings "settings_apps", - # Plugins - # "basic_plugins", - # Debug - # "debug_apps", ), }, ), @@ -222,7 +218,7 @@ vars.AddVariables( ("applications/settings", False), ("applications/system", False), ("applications/debug", False), - ("applications/plugins", False), + ("applications/external", False), ("applications/examples", False), ("applications_user", False), ], diff --git a/site_scons/extapps.scons b/site_scons/extapps.scons index abe1a4534b9..208b7577557 100644 --- a/site_scons/extapps.scons +++ b/site_scons/extapps.scons @@ -1,7 +1,9 @@ from dataclasses import dataclass, field +from os.path import dirname + from SCons.Node import NodeList from SCons.Warnings import warn, WarningOnByDefault - +from SCons.Errors import UserError Import("ENV") @@ -12,7 +14,8 @@ appenv = ENV["APPENV"] = ENV.Clone( "fbt_extapps", "fbt_assets", "fbt_sdk", - ] + ], + RESOURCES_ROOT=ENV.Dir("#/assets/resources"), ) appenv.Replace( @@ -57,7 +60,7 @@ appenv.AppendUnique( @dataclass class FlipperExtAppBuildArtifacts: - applications: dict = field(default_factory=dict) + application_map: dict = field(default_factory=dict) resources_dist: NodeList = field(default_factory=NodeList) sdk_tree: NodeList = field(default_factory=NodeList) @@ -86,6 +89,9 @@ for app in known_extapps: appenv.BuildAppElf(app) +extapps = FlipperExtAppBuildArtifacts() +extapps.application_map = appenv["EXT_APPS"] + if incompatible_apps: warn( WarningOnByDefault, @@ -95,27 +101,60 @@ if incompatible_apps: if appenv["FORCE"]: appenv.AlwaysBuild( - list(app_artifact.compact for app_artifact in appenv["EXT_APPS"].values()) + list(app_artifact.compact for app_artifact in extapps.application_map.values()) ) Alias( - "faps", list(app_artifact.validator for app_artifact in appenv["EXT_APPS"].values()) + "faps", + list(app_artifact.validator for app_artifact in extapps.application_map.values()), ) -extapps = FlipperExtAppBuildArtifacts() -extapps.applications = appenv["EXT_APPS"] -extapps.resources_dist = appenv.FapDist(appenv.Dir("#/assets/resources/apps"), []) +extapps.resources_dist = appenv.FapDist(appenv["RESOURCES_ROOT"], []) if appsrc := appenv.subst("$APPSRC"): - app_artifacts = appenv.GetExtAppFromPath(appsrc) + deploy_sources, flipp_dist_paths, validators = [], [], [] + run_script_extra_ars = "" + + def _add_dist_targets(app_artifacts): + validators.append(app_artifacts.validator) + for _, ext_path in app_artifacts.dist_entries: + deploy_sources.append(app_artifacts.compact) + flipp_dist_paths.append(f"/ext/{ext_path}") + return app_artifacts + + def _add_host_app_to_targets(host_app): + artifacts_app_to_run = appenv["EXT_APPS"].get(host_app.appid, None) + _add_dist_targets(artifacts_app_to_run) + for plugin in host_app._plugins: + _add_dist_targets(appenv["EXT_APPS"].get(plugin.appid, None)) + + artifacts_app_to_run = appenv.GetExtAppByIdOrPath(appsrc) + if artifacts_app_to_run.app.apptype == FlipperAppType.PLUGIN: + # We deploy host app instead + host_app = appenv["APPMGR"].get(artifacts_app_to_run.app.requires[0]) + + if host_app: + if host_app.apptype == FlipperAppType.EXTERNAL: + _add_host_app_to_targets(host_app) + else: + # host app is a built-in app + run_script_extra_ars = f"-a {host_app.name}" + _add_dist_targets(artifacts_app_to_run) + else: + raise UserError("Host app is unknown") + else: + _add_host_app_to_targets(artifacts_app_to_run.app) + + # print(deploy_sources, flipp_dist_paths) appenv.PhonyTarget( "launch_app", - '${PYTHON3} "${APP_RUN_SCRIPT}" "${SOURCE}" --fap_dst_dir "/ext/apps/${FAP_CATEGORY}"', - source=app_artifacts.compact, - FAP_CATEGORY=app_artifacts.app.fap_category, + '${PYTHON3} "${APP_RUN_SCRIPT}" ${EXTRA_ARGS} -s ${SOURCES} -t ${FLIPPER_FILE_TARGETS}', + source=deploy_sources, + FLIPPER_FILE_TARGETS=flipp_dist_paths, + EXTRA_ARGS=run_script_extra_ars, ) - appenv.Alias("launch_app", app_artifacts.validator) + appenv.Alias("launch_app", validators) # SDK management From ccaa3864d54b3792b8ab0ec0a2efbd82b3c2f7ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Wed, 15 Mar 2023 00:02:27 +0900 Subject: [PATCH 462/824] Dolphin: new spring animation, weight adjust, drop winter animation. (#2489) * Dolphin: add new spring animation, drop winter animation, adjust weights * Readme: update application folder structure info --- applications/ReadMe.md | 15 ++++++++---- .../external/L1_Senpai_128x64/frame_0.png | Bin 0 -> 1756 bytes .../external/L1_Senpai_128x64/frame_1.png | Bin 0 -> 1841 bytes .../external/L1_Senpai_128x64/frame_10.png | Bin 0 -> 1846 bytes .../external/L1_Senpai_128x64/frame_11.png | Bin 0 -> 1824 bytes .../external/L1_Senpai_128x64/frame_12.png | Bin 0 -> 1826 bytes .../external/L1_Senpai_128x64/frame_13.png | Bin 0 -> 1862 bytes .../external/L1_Senpai_128x64/frame_14.png | Bin 0 -> 1815 bytes .../external/L1_Senpai_128x64/frame_15.png | Bin 0 -> 1855 bytes .../external/L1_Senpai_128x64/frame_16.png | Bin 0 -> 2009 bytes .../external/L1_Senpai_128x64/frame_17.png | Bin 0 -> 1918 bytes .../external/L1_Senpai_128x64/frame_18.png | Bin 0 -> 1686 bytes .../external/L1_Senpai_128x64/frame_19.png | Bin 0 -> 1593 bytes .../external/L1_Senpai_128x64/frame_2.png | Bin 0 -> 1879 bytes .../external/L1_Senpai_128x64/frame_20.png | Bin 0 -> 1281 bytes .../external/L1_Senpai_128x64/frame_21.png | Bin 0 -> 1318 bytes .../external/L1_Senpai_128x64/frame_22.png | Bin 0 -> 1102 bytes .../external/L1_Senpai_128x64/frame_23.png | Bin 0 -> 1537 bytes .../external/L1_Senpai_128x64/frame_24.png | Bin 0 -> 1414 bytes .../external/L1_Senpai_128x64/frame_25.png | Bin 0 -> 1486 bytes .../external/L1_Senpai_128x64/frame_26.png | Bin 0 -> 1364 bytes .../external/L1_Senpai_128x64/frame_27.png | Bin 0 -> 1325 bytes .../external/L1_Senpai_128x64/frame_28.png | Bin 0 -> 1278 bytes .../external/L1_Senpai_128x64/frame_29.png | Bin 0 -> 1179 bytes .../external/L1_Senpai_128x64/frame_3.png | Bin 0 -> 1861 bytes .../external/L1_Senpai_128x64/frame_30.png | Bin 0 -> 1198 bytes .../external/L1_Senpai_128x64/frame_31.png | Bin 0 -> 1204 bytes .../external/L1_Senpai_128x64/frame_32.png | Bin 0 -> 1248 bytes .../external/L1_Senpai_128x64/frame_33.png | Bin 0 -> 1669 bytes .../external/L1_Senpai_128x64/frame_34.png | Bin 0 -> 1767 bytes .../external/L1_Senpai_128x64/frame_35.png | Bin 0 -> 1832 bytes .../external/L1_Senpai_128x64/frame_4.png | Bin 0 -> 1769 bytes .../external/L1_Senpai_128x64/frame_5.png | Bin 0 -> 1869 bytes .../external/L1_Senpai_128x64/frame_6.png | Bin 0 -> 1893 bytes .../external/L1_Senpai_128x64/frame_7.png | Bin 0 -> 1835 bytes .../external/L1_Senpai_128x64/frame_8.png | Bin 0 -> 1772 bytes .../external/L1_Senpai_128x64/frame_9.png | Bin 0 -> 1827 bytes .../external/L1_Senpai_128x64/meta.txt | 23 ++++++++++++++++++ .../L1_Sleigh_ride_128x64/frame_0.png | Bin 1656 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_1.png | Bin 1754 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_10.png | Bin 1494 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_11.png | Bin 1637 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_12.png | Bin 1713 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_13.png | Bin 1585 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_14.png | Bin 1634 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_15.png | Bin 1771 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_16.png | Bin 1681 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_17.png | Bin 1503 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_18.png | Bin 1663 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_19.png | Bin 1661 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_2.png | Bin 1681 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_20.png | Bin 1559 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_21.png | Bin 1542 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_22.png | Bin 1736 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_23.png | Bin 1621 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_24.png | Bin 1628 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_25.png | Bin 1671 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_26.png | Bin 1636 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_27.png | Bin 1621 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_28.png | Bin 1099 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_29.png | Bin 812 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_3.png | Bin 1651 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_30.png | Bin 536 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_31.png | Bin 492 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_32.png | Bin 503 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_33.png | Bin 897 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_34.png | Bin 1490 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_35.png | Bin 1741 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_36.png | Bin 1538 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_4.png | Bin 1668 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_5.png | Bin 1555 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_6.png | Bin 1521 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_7.png | Bin 1642 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_8.png | Bin 1694 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_9.png | Bin 1605 -> 0 bytes .../external/L1_Sleigh_ride_128x64/meta.txt | 23 ------------------ assets/dolphin/external/manifest.txt | 18 +++++++------- 77 files changed, 42 insertions(+), 37 deletions(-) create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_0.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_1.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_10.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_11.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_12.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_13.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_14.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_15.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_16.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_17.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_18.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_19.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_2.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_20.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_21.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_22.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_23.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_24.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_25.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_26.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_27.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_28.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_29.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_3.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_30.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_31.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_32.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_33.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_34.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_35.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_4.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_5.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_6.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_7.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_8.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/frame_9.png create mode 100644 assets/dolphin/external/L1_Senpai_128x64/meta.txt delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_0.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_1.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_10.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_11.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_12.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_13.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_14.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_15.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_16.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_17.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_18.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_19.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_2.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_20.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_21.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_22.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_23.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_24.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_25.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_26.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_27.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_28.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_29.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_3.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_30.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_31.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_32.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_33.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_34.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_35.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_36.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_4.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_5.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_6.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_7.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_8.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_9.png delete mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/meta.txt diff --git a/applications/ReadMe.md b/applications/ReadMe.md index 6224cb45a7a..e50d8e46a5f 100644 --- a/applications/ReadMe.md +++ b/applications/ReadMe.md @@ -36,15 +36,20 @@ Applications for main Flipper menu. - `u2f` - U2F Application -## plugins +## External -Extra apps for Plugins & App Loader menus. +External applications deployed to SD Card -- `bt_hid_app` - BT Remote controller +- `clock` - Clock application +- `dap_link` - DAP Link OnChip debugger +- `hid_app` - USB/BT Remote controller - `music_player` - Music player app (demo) -- `picopass` - Picopass tool +- `nfc_magic` - NFC MFC Magic card application +- `picopass` - Picopass reader / writer +- `signal_generator` - Signal generator app: PWM and clock generator - `snake_game` - Snake game application - +- `spi_mem_manager` - SPI Memory reader / flasher +- `weather_station` - SubGHz weather station ## services diff --git a/assets/dolphin/external/L1_Senpai_128x64/frame_0.png b/assets/dolphin/external/L1_Senpai_128x64/frame_0.png new file mode 100644 index 0000000000000000000000000000000000000000..ed37723ac245322aa254e648ab48da409e7e35d8 GIT binary patch literal 1756 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Senpai_128x64/frame_10.png b/assets/dolphin/external/L1_Senpai_128x64/frame_10.png new file mode 100644 index 0000000000000000000000000000000000000000..e385018bf6531534e1d809b371b0172361c657af GIT binary patch literal 1846 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 O0000lE>hmW@WGRyB&Rwv znb;IXLd5HUl<&6h`@U@(?fXvS|AB4Wh=^X=Zv0w!5D`@VXut7m<8ihCH_pXIMEme` zlmbu`7^#8iJxa)HR^J=vW8)lv=%>;(K0rjOk$47lbBycZN%ZQeLm8I$az&zhIkan< z;h_Gvp{&D`NcYspf^#E5@FI&G$Lg=$d!vpgYK$(pRE{(T?OxswdH=ZgR`_~A10b@w z0BZLXW{suc<`3(!jS|4}UXS%_`MW|ctMo$&Y# z$C*Z`tugtCu3k4r;2`;W4##w7c9DwlesUm+ZZtjK5rE((nv*l9phxy zjfm)>ExzX6qn$5)v{bbESf%003LvZeAx~ugLwlww(40sgTu!-eG6FsM7i|K5O@8+7 zmx9WE$XeyN=m9%WQGT>K8f})`Ug<3RWQ+>&0wn9(Rxp}ePK5eP1ojeXR8U%@HG8CQD-mLSOn4X;)s=Zg-s2sjma|hD3G$W zc|5PT#wJou1GUbUAgEnFKqTz1V*4F9DiTGNiS9$N^!v6GC_6+2^8~IuP&ZbFWNn_v zoE1VkP*(j(=wUM=#$vjBvQ@(}^_6zt78tH4<{ z&fBDHk$EicRLTEd$sUFJMi56fzi2{oXd4xLjH8HSS9Gw($wWR2R@GF2`@@jR1X0$j zJ&e4*LiyFatQmE7HxE7k8CdqqAnwIh$V1fu+Dy3;Bn!_(kSi#iBa{NF@Jh-^r0~Y6 zAZk$7PTtiNS;gtU4fOJ6*Fsy7(A8+^e*MqN8w@(ywH2r$hjBkfB{$HUEtt zI4uQON?4^JIJB2(sI#s}ywwBj_-IKZxk>hH(zT)foNRM&ND*gT?ui zh<+!c??m+5C!Xw~@^bcNmJXTDSjVgZ{IhM_H{<%p0Bu*F{T{;^Mi5Q`|Lu5v8+r3n z6+d8|Q)pOu#y@0|XCSKq4L~Yb+q>ttERbzEB1U|Kq07CHBYDA_0YqwYAl&)&%p{_V zepE%UIvR|zUko5(?Vm{cJ_X1KRzw3H(L)>noqAo}5V*`%pGq278J>~CSEhuozZB{n zmRwoc9NbW|9+c_ZsgQ`mSEYofrC96iUXnzKqYcKY*N-?~m;sVcYa^O}jg0H@jRQn1 zOnZr=;&mMeR4HL-vU__3DXVx$f zY0*#(ff=%k{z+LQ5hN;yS6yYE0TFZwb0a9L0JKrWH)OIE#?jRT0+K(fq^|intUZDV zWHsQq1SZf_6k)gvNEtGICmw7Ro z^P_Xi&nwYs#1V8nmqjKmyc`DcP~ovtsbm=zwG>c+X9P%&>iCs=BlC7f0lvnbb4Zdd z+u)yyMY%*PDI^*49=7UT+QU|frZs-Gf0S5u0qMu8%7%=*-%}2TCP5106Ux$WOk{C~ z@i_xDpVcoqhqh=(0_A7kK46jbj1I@RPj8{DMa}?4ikZcZGI)kPk07*qoM6N<$f=Lf+7ytkO literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Senpai_128x64/frame_13.png b/assets/dolphin/external/L1_Senpai_128x64/frame_13.png new file mode 100644 index 0000000000000000000000000000000000000000..a996443fe46be0e95aa380d17883daf5dacbb55a GIT binary patch literal 1862 zcmV-M2f6r(P)YNI1HuJ0Q1=Y|C|2OJkDSS?n`ycFz0a5VmpNo zP&APxiV}AzZnu5k_ifu`-*;($H*DJ`BJ$F9B3BUrm z>nMh2EZZv`M>l?!05f#5N>(wd>~~A+O#w7h*@K+QxXNi`Mj`aPy{IZsMu1s& ztM0Gn|G_q&S$AvKi>WRknh%wit15sV;kPw6&xZD@JwxM0cx7$KqoRGY%KOw`*aZ5S z{Tz+AjM{!kTPz!(#f+p8fmz**xmXB9`@`B% zNO&?~BgR=0V;Mzm$}$}(8S)yRgi*dDk7ETKxq2qwC+Zx<8ZSZ2ga?L<=NQG2%G9d_ zNLUeVFSlZ}MaXR+>+CUt+U)`|Zhuv_KMO}FP*#{|9D*m`x068IAwHw_$UF41RTvdBYux$6vtD1Tcz8KOoK{o4Ui?PqI z=N`3mb=_GXZ_V$bEF>HwV_&crA^n}t_A$Q6XHkqQC5h)T+gr-V z3QBNV2=D~FGQL^`J6iy#Z`=0GdHzv|4ykl|!;$Mf!no2Q z;8&vZt+~uk7JS1MvhW6WgSt9FgbH8<%+BuhkOjP5601u>^ee2Diu$GinVK9hbE+B5 z+8nmgM|nF>*t5MsqxGTy*=+bq?%$p!9yRAmto8=lqo;BJbnA6BMX=C3dwqIu@n%{g zo6ib+#gX~ft6?6|t0sN+IN2)dnFlOcMEtS4l35`Ez8&1a?(R|fWdEE6_Cu2_PnEvC z>tw%>@(hqEL55#TTg&k+2godn{fOg8t3&Sl>ek+XuA>{6B6zo^-nzAXkj!8efUKPL z^X~GmB#cphtksPwx~BwsJ~k$!1k8i$wl@Xv5{;`Tej>i0vzCLBuWBAt^`z~4M5EUe zApW*(dw(y$X8A}75VI>VYF!x~qfOIpQ;&(}s_@|P6Va~DWBbFmgEI?R>E5$SHnPE1 zh={fqS2O&L@DnKkqk%r7{tXY?V8JG5V`d&wM6@dIKKsn}Lw_`{9=*{VK?8Z}O!m)s zZ*UKSsshl;qC-ffJoI8*6*Cf1#6MDk5gxL9&hWan8h9YWvywfefwPsMSIE5<#N7Xi zswXQ0El@2xYk+plOBtv;Y68)uUbVnN$B{}f5(*;zwYT#y;sv}=VB@`2Qu_=gvZ+_Y zy!d9bKAshgv;->9{WDgaq1QW}WyCWRI30F+J)zgft5DG24DLx_v>}!_+gLBJ z7|Fm#;;l_yS7z@lpUN1Q?SllW^D*RsS^l%{yZ_I{_gWWgpH(K!rZY;*6KEvt-lR}J zv&k-MXL|;(gKc>iWtSBoFEJv3(X1QA#t4XNB$Ug{K%l0oQh@Lj#3%_W%F@07*qoM6N<$f*OH~ Aa{vGU literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Senpai_128x64/frame_14.png b/assets/dolphin/external/L1_Senpai_128x64/frame_14.png new file mode 100644 index 0000000000000000000000000000000000000000..628d58b93a3143142a7dd78ec0a0dca53023a984 GIT binary patch literal 1815 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Senpai_128x64/frame_16.png b/assets/dolphin/external/L1_Senpai_128x64/frame_16.png new file mode 100644 index 0000000000000000000000000000000000000000..3ec3727989dd33181d644399bfecf66a2146b9d6 GIT binary patch literal 2009 zcmV;~2PXK5P)I;*U-BV`q?}SN%P|IO01b$e z#;)lq*Loxp1j#1Q;69dZ+qPv{WZO0wUk8?D5fOQM_QtP;4-tWzKc2twYvbc?2i~|B zi->IFq@xsoqQFQEBKs&|-$iZTxF3tw03ts&-O~di;*umI&|71?9#)a(lR7pdI=y-$ zQC|)3oktwh{!Ntgu!{I6m05625`-+WSUXXBukRautmq-T;C6E?8N9x_9XkD^?|Z|~ z2PA+&`n~+!5tmWuzbh?dX>R!bGvYyjr-#MTE!%i& zi_rrNsnF*cU{jgb=V~%|Ys~oabPq5(W~bj%>QQvipV|9`?4-|51GDVSBxOcM_OQA3 zMdazNH$b!j7ARb2Ih;A!QR#SO!OzeWGe}T5RP1MKYexVPep5VlM*S+cjSM05xE-9S zAv!myjE$q~tH=NF9Q022c&-&<)2Cg#tn9XaB7fa7P!Dc9p~0gKWX{h+dICA>Q<)=*vdFG(kM?&(jk8=63B*na%qB7m zGxL4~y`g83*V>M>mBuawpod=L$Px5>*s%7Zuz!ZyzY{Y^;6)Qe0jNDW~5J6k#n9s$|f8A3qeTH%EW< zI#3Y>|MFoD8(RxpB?oW|Avp%J`rrhqi+HOXZ{kAuTmfYx*xcT1hy8h>Ws5}^==QD zJ`8rkQwkB_6W>JSPZ9Y?M1H47miWZqQQk$T-irVt*F~%=bVro{|B1*K5&6C>%L?AR z17&URx(%0 z1kc8`?~bhFjsV1D{}z!y*v+?2W@B9imed1B1z~5CvO{IJq8yBh#mnVwuWf+qV5*2sx71?+o*u zlTsysXTKW-J89$y3DEvb`%|&D-L zlnD@Pyz2A}u&2-Z5&4z*8%4U#T&s5^Q5%m0c8wlmq7)Eytc21^n+|4(u(^_GBLbD?hS*_x|N2IWo0Zaj2{>y663gas$@W^S6z?||*ZSDDs z)&<#){EXVOLKeNFz8N$OFv_wbeqNab8982w1fD%ljOx$yMUWTXY)GG3@>SA7H(hl4 zp|$YZApa|&o%P4_1MP?QrF;`;CyoZMZn< zXQcqIZ6!Rj=vFR_?mp;1Mn&8~k(e?BuLi^fWLl|i^38^SR3&)xL#YFiFIpr0d{+kQ z92E_!9j{>6LX19QuhY+4;0#^jjCrHEN5ruQ(#{!ee&p{{dy)L7f{_uXPvq2*cBs8~ r3RpV>6#_(?g-}Ux00000NkvXXu0mjfjU?0v literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Senpai_128x64/frame_17.png b/assets/dolphin/external/L1_Senpai_128x64/frame_17.png new file mode 100644 index 0000000000000000000000000000000000000000..11b247eca27efdf52b168912f14183521e1c1b63 GIT binary patch literal 1918 zcmV-^2Z8vBP)OPdIS%Uoa+Gy=i1;U!*>LS72w7yYa-#m;*cZm}K#lB%TjN+~@W$$X==AroFN3cK zbO4c<3!wJQV38~jFaEF|t)~Fd>CITbcYlwN%P90;c`amDoyAvi%uff{H5OC1%^%6cjVdko#y1rU1bh{mDlk<}2aUWjyl zCF6^5`xx~_^_eO^(HYwV35W#h6WtYv#xNVoa0stXQC)nd#|-0VVVk`M8U1>HTf6c0 z&Fb;cAJF8;;7E983^X1os28E1uTJ}B8b1r$B79n`qnzElEbZ=nyk{cjb#Np^8z%Bx zJ;vVu_TQE0gEEMfMwYjoD4;c6iPXY^!^uG^m~fwqI4AY$;5 zA|qbE8Wjh~`c|AZ<5t<+NB2_e`o@HIG!UI!k7-&(?-1r$vf6Q%>j~xiOopJLqwr@X z(54VsIyS1fo$u~o$X0)5`)6`z?SYq4Gvi`4~R%;#d(hsMo%AR zOg1)*0ISNf$oRHQeAa3A?qq#}k4A%8W0~ksscec2aDbV{iDX@o@WJvx;M1B}he8Gd#R zL^Y1t`gTORBIy3rI>3J-^4?n89fAqXZDxO#HB9x&;eJn<@fS;|kcQpBm zYT+!|%-oeZ7+ueb0xZH8asnKnQk3~<-#%mwDYueIADzE4#uk;%asq273;S$$fJO43 z4fNV)g+K4apm}Gb@)nNKjd22R>{-w{?l^#_lxhFfTKjY60uY=2sD_%Rm?1A-h`Zw6>p)I8a<6+}%fl5tNBXUB z_}y;^VN0Enqd=muC=Z^^x~u6_<@Amim?<>b{@MKYz&`zo7?$~4VG$ubi~X*0I_4_q zVCI}*R7QrE&F!992SA1fsAWO?Y)7$D3pJyzDg)6=d}DGeA`><--A3_8;}}lsIG2E2EBg z7`zk5rqfwft+A;572&R{jDw;KL5)YB#2Eb?3j_zsWBsDGBWO(sbu@X z6nP?ErrTjD_^DG;P^jLqtlSZdjAd#bNAzH?g`y*?f;kfzedS3pZW1ey+Wgxf1S5+z_m; zS(-RfJMbbfdpO~L7-7@9i1k;2BxU$hNv5N4NC~mOOWP(Hc^IAJgKgIx+>!2c&(eX(CEYeoxMBIzbYFcaZ8&dcE$l)8?1ONa407*qoM6N<$ Ef|8}Dz5oCK literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Senpai_128x64/frame_18.png b/assets/dolphin/external/L1_Senpai_128x64/frame_18.png new file mode 100644 index 0000000000000000000000000000000000000000..bb15041331abe5f7a1007befcd37135b1f0b6381 GIT binary patch literal 1686 zcmV;H25I?;P)V9!Ergj2gmjfkWw1@dEpt`J3!8vQc6QU zFT5h1;7MU#zD94S`e5B9WsJ}%w$)GL=$T>fL zkWu+P8KK-6cpEFqOMBU8RHTolY_u-BC8fv?L_~MlfExh)9uX*=M%!Pt1w_Bgep(+3 z(LH_#Xg1)w*Y2aWzM+Vg6fTV+qZmcB$2i)4?lGjNjc3uUG5P%YJueG*5DBc7WR%O` zI+klUPe-)B%QpPGwQol!F*`bulWX9PuoG)&L1WcP%C&oKLKfBX$VV)Ju5(#ZDKv?h zNuLLrPAD%yr<76p)EEXCSu;NW{=xv3vR1_nd6zV}rkt1dJW0_~nQ z>79V~O@0pnCD1kM?gi2dF#uwSo7PB9MBCdeCx{-?X0wOaC^ZH|w2!v39^-nq5*eVH z-H=^#&UCYAq6p9?j~0PXKEf1oQAeTmcxH`feJ|IJIxTn@fPda=dqsiV*N{*ykoQA< z_%%(=OjvW}_iJMmpf&N1(K2VRW7X^n8GtL+qEsZS-z{n6uym_Yy|xXGgk)tDd^ILe z89*w}2iGPay^g-GbchiVs%7Hk-i-NZHj>{pR(oLp-q`E)H3dD8iPJ3OTZZ( z;&Q(ik#*7nO)|obduy#T5+aq6M@Xwb$LM4YjXzuVX#J61=$dw7LH%UHeMeqZqZMs` z^CnZ=F?ON+opTWhX#=FY$0>;DH3#r2P=j|oEmt@Tj&iCRyLx3q23Q%HtBu}z?6StF zv0RT%HGO;64?YfV9aP(&tUAG2A?LyKo`~_ivSwk*ABK2nef9n?WlOZ`Vi{RFf+fSV z9wXQ6k^IkafL&o3VrJpS>i{14J$fQ%y@Ph|4xU9jOC0dy9KYu6E1^0-(9?bs(Px(y zi*e*uGYfKSX*mg$J~w_r)|H(L_nnCTxHP{|MI>>QNhcah`+2k>dV3=%a18uGMBj+$ z7ZH7NYkL58iT)M)yEYaAAO9W{_Lts_|A#g={+ThtalQQ`e;fFNh(7)Q{Zz@LhYDBe zVL`I+3OpSHTgt-!k?Cza*$%b2v@UcsLZjP2R8H&m@${!*9kjEh@jcifN8peZMz38^ z_0i+_Dd(KI5Qo5@MD(?)(MH%I8H}FWN0jG1?3UifKITEtO6)MNXi0d~Ga6GTlmkeG zB#vIlR?IB-)?`YwIzg>(6lONBMa>S;)Pfa}HR{zbYn-0SpOo=n0BKZD_IySsK$vyp z8uid=lvAPcpaVqS=6MfJ(DL>Y!t5iPR}Tq`#qa#fg;zut+nee4z7*rh)4X^*3*dvV zWPnjo)e9{F?q!A}woz}6lTvzp7O=8By811c2qJQ=60Sw}jaAo>K1ByjhP$SJlp$s< zk|n2h4C8&YYse8+I)MaYg+~s66#9rj#&3O%3$0_#3AC|#P&>-2?*!Hsf=3I-$cH3H z6>g$=5RtvlO0+sv3#@shl-eHaJ{}}=Bu)jZDqbG6JPmymR20KSn7}h0(ix56#Zw(X zcGmX9T@$xn^h96n2-wO5R(o%yR%7X>N}z14F3Q&?wZ101~@v>wTT{)Ko)scjug!ac)!vXL6cR(%j4jh z5Q3G&3AnX3T=vsTp`|#$l|i4Dqa0l@LW{q;uA#A!)kbS)$!MAD z3;Q4jK#^X{EHx!oRJc6bQtdoEL(f(w(Arvk&=%o2!8N}WYE`c+#~BUetln#@;mrw3 zTMaGjq0Rm2U&TfheL7D9`qlc4hNat)n6dX;1tiVkEyoX40xjVMM`z!bT+i0F0ud(g gj3t5cHEmn|2Mp`eeNyah;Q#;t07*qoM6N<$f`7{!ssI20 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Senpai_128x64/frame_19.png b/assets/dolphin/external/L1_Senpai_128x64/frame_19.png new file mode 100644 index 0000000000000000000000000000000000000000..f953c8ef1950ee6ae7dd15ce03c7a56d931e8f5c GIT binary patch literal 1593 zcmV-92FCe`P)q$gGRCt{2T-%P@I1H7I01I><`v0HVhb~Z{fcBvygTwMJQgS9a zK;a~oC5k+}Nb>aPz4!LU%i`QEz#Dt2G|ZN4=Zr|=E<>E8DMC$=5?+gYU$*G=b0UK1+>G7e;7h( z3ZUBK0t4Q|xrYl$(wNeOvN4F@F;JB^S4N4mHGZ<(Y9wU60CEH%=d)(+hQzypL-4pB;AL?Qxlp2N8D&%ST;{&ToKwM^Lt1}T5*NKx5gXY zk`g4PREAusZ!XtMSeYWuoyNE3R7x4J2-U~{$I2|UEUN_XN`h-^mD@YN1$8ZsflQ&X7cv7B*Px}Ct>K#_J z!;_##;pcI`+{CC!%uY_~oAXAiOrGe$$-L9^2iU`)h|vZtk-$?6aBZ`QVCm0WFIc=Z zI#y;Od(N;>GT69k>4JhMEM_PIMllOKMJF6XD(-gSlqN&vYz&wdEV(~Yc)@Y*3`)LP znJeZD^?!2Vgev&pJ?u4FfjF*F_Io^Z1u7!Z4qOoc8`NVouF~~*epG?GNI@0F&j_*x z;MTR8vLl{9qrA*wC_4U3F;@kv)3%280jNeWzvJJyPOY9llM*o|^oK7RX_EgYv@F0Xob**FqrMJ8?>4U+d)6usv6 z0fqn{pMN*zC|MkgX~TJ=WE3h|tw6@g#j+nVLr{;5*FJ!DZLR$PpC4Y2iLe(Efy&{# zmRgZI!t%_HWi7i80Kb2H{toX)mN*&9KKT0%^**B1I{c-4SNSh^hHTnQ2>+Bq6 z!#`d{QBdCrq`t3MuHYTFG49=nFl+qi29y=lss-AQJPV)%q70G&ZX5U+Q04jApbF7J zgvT9(%R*%?)%vo0ULliZoKFz|H(Kp|-c1s&53AgfUA(V`5fOSPUI^J85e7U4jrPBr z2-^5Ke^;RV{|ThR4n{NzWGICyj8^xS-R;niB)nAs!{UkmrZ`+^4L!L#B04u-7;*4ykMQ5pe; zFy2TIUF+_8Bj_%O5&`Qtl>l&%h+!x)R3Db?#w>|FM4x)w~n|u7Q$?AcfGC+tXgGj5HQ3 rBfvFaBKs|Q^e#JYt*w7w!Giw*R39cKIu>1m00000NkvXXu0mjfTl?|s literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Senpai_128x64/frame_2.png b/assets/dolphin/external/L1_Senpai_128x64/frame_2.png new file mode 100644 index 0000000000000000000000000000000000000000..36c3b4abe1768c8cace03ee61dea063f00ada2d5 GIT binary patch literal 1879 zcmV-d2dMaoP)_^tiM?~TXV3fwpsn~3af zr=t*nyg&;LBJW9H&NE%DZ+PZX2CT{5F*cF<#_$s*c<(LqQ>lkTjN+Vu(7%y+Wl?pZSeJg1Q79a z0n{ED%#+1%=MU?#wGzPFy;0WB;&)GS>AC(pEX>Cj0bUx5rCVfpD=U)^WJrZR&qS2p zXq-txw#NE19V_ZNKpPnCewL|w-a&uPK3|A-`id~n``ltxNQ&~R>}I3#Mdalwo4_-G z1#;I>4A1OruXG$;_*n+b$jOqdvZ=D4t*kc!Xr;0baw_90Pa89Y(DU}9N}!AZv-(yl zE!I|DU#K%C;Yv9P$Gy6VK=P1^A31TKZFl0Q(D2^mk zFA0#bBH3PU#bk?+(?HhQQv|i!8_2l*RoVV596_M0G0`{#&%SR@0&Rz^Vv)i)~i%AH2_8ePkm5WwO@G|-~rDLyf#SFtLTS#tGwNj?7SABEZyA}YtK$mS7O!A$*T z$zq9IZ|=13J1zr9jql|I))RQLGuJCR&GI^)Ec84CwO0WY2>Biy)0^d|^szum=@d#!-pmEbrjSsdzpQUe(lt@QWcm6J)bq zb~E-m+J&tCT@6O+Y{{(OUm+YU`(}{d#h%MU)q(XQvkg2d&t~Bj4{`;gYotOzFQSq% z<0)pyX)>y=-Wzi@L-ykI?*>M3lP!LA=b`cJ^J*MDpNGQpM;PY}5`>R}!Q%WaB7cg=cMe1C@~yedkz?VrV{XG0vhW6Wg1UOW!rupIuYtjL`Qc4m zu=DIs!Vx8^%~4j)`$m9FO%B@16lG=DnS@~Xdz7c}jLitrize%Z0ISZ`wS4sSZwmSaCx7^($d*0`KH&)}$-lKO)za zy-M`xTVF@-Tc2LM{6a{kEMg0c6h9KtVkC2!u-&5PKG8Zy@;vVw5!|!cO9;ri7Tr%X zQvV%UUTGaZ0d{??VZ4%%jc4nPJce$2%K>!l86DVUT-R!3-TSCMdhurv-6MgXkB!NY zz^)Cl*&6}8MB{3)w5m^b=b+@P>S3y$w0(|b^m+oM-?nY<-wUvD29W?My8_Y8%+MiP zH0?IgywS6Rs*a3&Om=l1+aG>AICCQ_-Fuc~BMXe`Yb)D}s~P=9^odBoWUxxX_HTID z28*aPMEyq)DQ|V?nXi)UqCc&xCvP-I&_JF#l8q8{3->`#)c~ke(GO&*aq;MC0s-kC zmB1P^i9BO1Y&Gyef@dXrh=H?7u+j^7`m?w^6ImN*gKFE^4QR)_l!3aV77$J9RSPV1 zdo%9vFl%geVa*HFTlIt*g2 zMDKDN7qc-xJGXtm5;IBMurjF?=|5NbssgO5@C2g7$ht)q0(y`o!I54-j}^(C)Qu#2 zC=_JY#U|;t4gORtiY2V1(Bp0L!EUaH7*`{~hz_G9Kw;MsqO~J}>V8k%nJIz{rYE$e zUz;e3lT{+17&g+cfi2o7yg1BIx$Y&1Sv{VSF+7%6fIP*>VrPsTxzcJj-=kl&fjoG9 zyb1-q(m8&N^NnB0G;JG-RgsJc%F`kl{G1*w%<88c9(3=GI5TJ*I$TN5$#9sIAH?tN^?*F8!@bg3) R;+6ma002ovPDHLkV1jF*qVE6z literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Senpai_128x64/frame_20.png b/assets/dolphin/external/L1_Senpai_128x64/frame_20.png new file mode 100644 index 0000000000000000000000000000000000000000..d683b9f625f773ebfba1ae7b290f962e80c5a0f6 GIT binary patch literal 1281 zcmV+c1^)VpP)5Y=Xt`7%i<+kfE%&632<@T1h_bE0$dz70WOZ$$j*=BKtDgv6a6>u zgr$~&+IM+pm*nb*N&|b!?;(0ZIf`w^7A^Y-Axpq6%0G_dNmh#Nt-M$}S>R59U6fz? zu9u@Ci-ZK!_q^e@1V{#-&2>s#dxltMgrx+~Fuo_&aU2h;Y=cFQiEaCplAs`(499t% zr*-72b#)`=erb_;!u7p;eEOi3*UI2Hn+Mu$R@X(f<4pjn`q zA@IYXU6!yl-<#xVw}ab2D~sY=MYUHgtjAeW^CELAXE*_7Yya#SPhhUT{=3A2TQ6XZ zS}rV(TSfq@!B|_E1=>l>LQn}pJpi@dKYG>+*c#ot=fY!by*|R1fDxjVUO*{BXW2*L zA{_x{mKGro2hvDBitI}WV4<+c4IhB<##ZF!jWx^(V3GTe-dR1_J;jgC9as@Gg6L+a zf?c;T7t{rIwqgxTJb-1&H&d)+^JUg&Cgpp3ffoSzWJXP%1qre#UJC6PUIFO)+8HN| zx<}wCd$#iR2#*-R!m6y@GY1*TxdWbpKx%E8%5E%j-@!7jf?*cGP}DB~_^OuX1`8JF z5G=BQ?*QkaA;1~_KHN@&qv@M{9J7S5gy<- z{89b~bd(vqq&=IqhD$^jhv` z6I=iBK-=d?##&a6yy$lWSGl{DnLr&4bFMvEX2q+KoU7)LQ?6%w01vE=1FV$ae%^}- zyqAjFx-n3_M2#l`aFvwdxK@3&P=1X}!CwJJ0;$&t{h2rodIQGtr z)h?$^uO&kCQ?VX(08U%Is-A>Il6Sf7XR&?&VIc_MSwYsyuOV%hv(~Ou3U@r%#?>mQ r{?DM5f?~?cWI0rDl#eZCt7C>wS1=;$EP)zbW*x_N?8Gyj zPpVexP7){n{F`aZaU2KS_^^1{Ex-*Qlu}INZUUSfrIfDnC8YT5Ezpg4SgoDHO@I@@ zQ}Gq)2vACaJ1_kYsnV@ymW}{7Vl2Ifn*d3$Qt22s0XD%?;WHsKRT)_0|1N7-0j_Mb zpr?FkF7=^3z^a5ZiqIaipOyfbtA!OF%xmvy2!It@!@#V2Ta7a*z9J0)B&Y}MaR^={ zidMQsM$O(*5P%XGdjU_ydmu{&?}6!bS<<7 zvy=e%pfy6)XRYHY86uRP)yg@95}-Zg*#pAkz2~;dkquheLJ80cGi$+~73W#nTc`44*V@mg2qTdRZY@#RZtm2}o zhaHxx^_W{5L0aZg^}dJly~potNVSy^XG|KndVtvj!kxdJ%BSQ3$d&z5GFjxD_5dEW zxhK5iy>&iopFz4-n?qUGGZP@2Rd~yT4^~0~ue?AzPpf=w)uARpq~fja-_z=Q`{NQ- z>Uk@)d56XF@tk4;WV3jHIIG9N7KYc8uB}xNwSK#{c3#e_3<;1?@c?^c{pe+tSve~3 z8Y9JXeHG5DbgO_}ikCEo0DR@L#P|w}MS4RTu3D5*XaekfAFuzX9OO%me`OO(jZs z(mM!`#*U=Mm#jmDBda%faZvsb0QfUA0dBU`p9oNM*R^q}w@dk0MoTY*DpQB!40i#q z0Pr0EegVK&<=Dr?H|8ACu6!nGlqSKJlJIF>B>~<5;1j%C{3Y7RvqV@`22x$p(VCL; z)%Ld5u+p8r6J(akg1X$fow8TA&`?khcZ)mH49#q%C66}igoPwguyEEi9Jv=17 zvmPMQ>a~ZTDJzcxzL}&*s%bzShvJPOP8e@UUxRAD_i~SCvwBQ~-qkhryHbknzl7n1 zydh~6)$d2K+1gX@&6gdc7O+bd5bg5WrbXwXrgwSc6)LhRUvo~Q3PR-pRw=)|p{u{c zpB1eqz#c@J9CDr_oAM>Vdmrsf>;a-nJSw?q*X3mBZJ2BI_B;pm-PzGQACU*xrTi9> zya3PHuF-pz`u=L@?P*AW)yNG?;Zkc$)m(B24N5~GWv csIj~87qbzVAj79-wg3PC07*qoM6N<$f|ZkdMF0Q* literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Senpai_128x64/frame_22.png b/assets/dolphin/external/L1_Senpai_128x64/frame_22.png new file mode 100644 index 0000000000000000000000000000000000000000..dd241d24ae578441ddb0870387494b574909677c GIT binary patch literal 1102 zcmV-U1hM;xP)-TmB;NUUqxyH1B)8AVfAY!<; z+;zDqyd(WQ{~k9oP^t!yJZO~YA-;x(_>$Lh4yC%g0Ig1I zitZ8qBZyxUcIN)Udb8mrG(s|D-m| zEL+yIuGBu-hxGLd@vrL=MS!0G@ErhtA>z0EA=e9kY@gLnDjmfiXuE^wxURCBK8tc5&r@Jq_1h=T|f~@cmRGe zsfO^c_j3#|nw&%#U~AE}6Go2s9N}AS96krAokT?uJF#M>YbS3OvwrxjVI%@X4WHi8 zN9(Yu$LOp#xeoSBm?Gv9H8^IJgm0_u@X&SeDxxnhl)NxS`;rErFTTe36b;}}1q2XV za7x~8ZWb?n4n=FMJ{e%etdE*c^r*GFB>b!yS|@ngWA;h{SKN66>?L{)?Xs*>DkL{0w58fltrhnwVxG* zUfaFQC@u+;mQT^rt-OqDWGU!YxENR2NRjLmH0X_o$N*G0DLc<*3oNgzTP9%V05ihT z_j0b5N1iD;oVhzR%GQ`J2;snaf35iB&Uu0dZXXvxW909Zh$k(O( zO0^WikN9>h|9pUaT`EO@{AWjeJBAFPJp+4*+7W&oZl4h1b@_4ljQeK<9kG4+00gC3 UrPkqm<^TWy07*qoM6N<$f~Qyv(f|Me literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Senpai_128x64/frame_23.png b/assets/dolphin/external/L1_Senpai_128x64/frame_23.png new file mode 100644 index 0000000000000000000000000000000000000000..944bdc74e99bc530de3bbf37368532a2dedaa377 GIT binary patch literal 1537 zcmV+c2LAbpP)Mu1*` zAAPD^?QR1S=z%19rX0s{;KbYFvYUYudvPkjo8we~H^->}Z;n#|-W;a_yg4oaP)b4X zS}|%j>YoKKAG7#bjp+R8wE#Gd<3K3|Gp;ii;hjquS@O9=g}PJtBTv=3e+6V2so4Wm zjP6gY!Ez5UVxr#D-)2lY((ctf!g3GLd)8wxbF%zd#fZ+Ud8#^hX-&3!fSHy?X&lFK z`^?_|ODT7s-H?>ox451M=y{On z0<_v^fqEH^=)ANT(YaYPQ3j0?T&&Umh)L<%Vp4obIFg@)dWbdJLsWR5Z)j*)yt40sH{)FMz+S1hsTV+ODyN-nqG#uMxD~&r+|~ zjAz9y3j=#Ce1M+-{=k>To8t#c92g&AM#r!sQvo#mTNHSP@B#cj3s5lei7(*;0pSDV z7fiHcWVK%%OY9bYz21(4dmzMr^qK)MOSARDNmk%k$86D-czJ}ta)$6*#ul<tzP53*|0%XKi2m(u`)?ih4*5U&nE{6yTpy%7?bGHP5XWO`b2%$ZDu!^cEz;3wY}r zvm$*CO2=FW0Pqnl;Ar|=f~eVxm<36>B7v}m-iX$<)p*oVi=H)F$Cm`H5k9y6Q9w8& z7U;dkwZigSG_KlQITPMDj(YuepZE9Z^nTMAh?kev_!M_rz%KXcp*vIfRxi*K*hrY! z9>6+h_i^w}XeKGOu`IS{D`=fpc$CR&6xM#%XA-_w@Lp(yH#)4+rhQR(gs&N+H!6=$NhN7Ei;+v%)LptnDrm08)N{^}+}6`+o#k1_Yi@@VwoO?amsHw1Ubk zCD4qcP6HoR@SdPED*Whn$-i^HRtX<4YyrSy34aGq8FdC)SCF1(1>~ku@My5g;7+Va zlSZ^7!l%OJ;6k4v{9zW*8Z63C1gmQbJszUMr&kIt{OA^AJ`3m#wi=pB+e%~@fY$Qs z)vE|z#Tou>;HnB5w|2T1JD@QD%|VgocPTT-I3tkgaD@IpKn7!(d;1KpmF145akvNI z?N)5T-xOfQzS|OflwM|aPt&GHuO)D446X!L!E3V<$qRh@+dwTr%CyDuW(YnLvkicI n1xX2{LXp+GQ5kXiRA#>bsTpnn;ED`h00000NkvXXu0mjfG)dy~ literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Senpai_128x64/frame_24.png b/assets/dolphin/external/L1_Senpai_128x64/frame_24.png new file mode 100644 index 0000000000000000000000000000000000000000..3f445593af79067b2b2ab416f92953a1ed9c4cd5 GIT binary patch literal 1414 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Senpai_128x64/frame_29.png b/assets/dolphin/external/L1_Senpai_128x64/frame_29.png new file mode 100644 index 0000000000000000000000000000000000000000..e2767bd65540a0a0a456f6e0f8938ffd9ea77722 GIT binary patch literal 1179 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 M07*qoM6N<$f`pe5KL7v# literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Senpai_128x64/frame_31.png b/assets/dolphin/external/L1_Senpai_128x64/frame_31.png new file mode 100644 index 0000000000000000000000000000000000000000..037bdc8ed6eba76a6f3431eac40d0af8ee688ee7 GIT binary patch literal 1204 zcmV;l1WWsgP)PeYpey z0OpY(-%TF{d4KMfiYZRgjHD|SwL|jd9V;rdglg3)s*aK*(zNC(M(MHdJKW2%B&X97 z84Y=I9i9O?L+}&6mjHGR*~m#!0(z0aEBK?AF92F!p7sDeL%lk}C7_2bXBhcU)y39n z;H;BCNgEb6^>kSEdlboPhj;*$C75#@9C-(hZB8k$UiPSMd-$_<5@6K6q;;e^qwN+n8};x6 zh;R~@+Yl`q(%KWA05z<9Ge_EP4Q0MBv295L%)*gIe!@k71z|Z8Ek}T8Yw!qo?m?C# zz?spjL`rZGU?F&%&K{x^92vFIDYH2CH4>ub0Lw#?Mglb2p?3wtd*O7N}VSzOXwZ40*qRNkE=JZaV<6h7RM&Q;@AXO9Gd`(xV|J-;Fx!i&PcSKekr4+8sYrGWU32?VB9-zJF)>qp*UW4!i7!7P9mBJA~Dr65Upqb-N z%GbPCC<0i+pdLu(xRdfTq_R0YDuTF$q|^5Znaik2_hZex?a`7NeJM1`{X&N=e}?l z)w{1VNT6MZ>fby9M!ih01@PY2UF!`rpvcWe0V~GiXNQ+o^xhg~R0(kAmk=!;k4^$F zrx~C^fGf;kw0JyXy4e%?{ay$VC8v!Mm6RjLQ}C2SN-0(*5P%liogFhz5{!_;moPAr zpnK!-5qr6Ej@Ec*O3M*z8p|8q?Z zjY4}%wJe}4L2G`mFmQV1YhZQedO=x&);=r*_+=Qg46e?oViL6Abp%WgiBNl#?@b)( zd=G&6t6w}Zs$j)i2h#Z-`^(p3RKdy>B>0jF-d2DHB^Pb>PAc0`Wn{JI`!dJ~ZGneI zv0fy=cMnwCCV&uGkfp-Cowkv{cMkdAXB2=tZ+wDM%&77%uloURJdS3bDcv%Iw87&n z8THmh){c^w+rZXM)vDkwRx(CgfmVO*I{4Z40UF5llKBT9%&-Nuq}UfAGl7~HofS11 z;0qV35ALUFtF^c0D?#b9eE#LW02n85)uW*G0Z5B5Bu5Bdr`D#q=daxaQqT9jz5(7* z0C#mu)e&-Yac$HbtPikac2Il#aWcYfHHC46lIno7Djc4dLFErSJgbo4$|KLRZmSXq z0A2`)pOKeARQhms5@_E~^I?oA_$cSiA1r5aN}`ng-tc79=4W>bC1%@H#aMlv1L7 z06baGOGHIWI2vd@bxD6IIm<8+E1h!gF{=MZ5XGe03*8O)9ZP7^yi&z;=DOy&b4ed6 zyH^`m>zmsirTJ)`E#;3|;10m=X!U12-dmeOF2~{ZYtDKrJp2G33hP9U{CF1t0000< KMNUMnLSTX?97I_F literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Senpai_128x64/frame_33.png b/assets/dolphin/external/L1_Senpai_128x64/frame_33.png new file mode 100644 index 0000000000000000000000000000000000000000..e3e7799db4dd44dccb311dab62120e0203c85f71 GIT binary patch literal 1669 zcmV;027394P)j%|4n`=#|$QL4&9nzu^v|~u0;=M zbhRv#G)ZxhTwTxeJj*v~t%dx#@r{vsiPCEGrTa8`o@+;~H6_?5p&4K`EozgD3?6$P zBOh+V4@>80LC&z%u~+_DG<|Ev)NC@^2@Wm4Z2ZOu#dAF)njTxw`!aG-9!KcTjWi^~ zQUgX7V%7;bkL>^pU>PWjTQ+$98~8SY234 zPGHHg9bf`R89!3kQG-Y1xb01#u^7GbxqUco$}iJR{E7hE5p;O;gH|D$LszVYwrON- z9<{mcx0n7-0RecbY|T)fITk-YCeiUa!LfG$&Wrb zf%HSiN|F0`&vi@ZD9?NCrSY$|ex-nYB4|U}$EV>OMTQZO=pz{=zEaexrIb6@*b(3n z)+$DGDIzP}D-VwdoDUl3k*wDGRRi?Abnx!T>VxVu%h%IQXgsKGdCF>k-?+dVeLy>M zQQb-_9PyDBuXXs;aN`VjG58zOs+o<5G5LK6lH9e{ubsecY#mpmM$INsS)`Xx&hxzA z2k3drDmmIG(}`vliP`opviCm| z{4Jf81@(gi@PbJ@5~V{PW}}NlRODshRr)N$aQ?Fk>PGwSFvuIudlWwEQ0eL-a|ZZ= zn{@z3>ABj#<}KFC5)ouQ+`H!Xj2Fe5xkK`q7-c?Ru z$xowQ7Fh@3hDJ`x9T*j|uCIn^tqHG^N4AR3-~`@#u80DfuDwO1>o7eoMG} zAp{AdffMHpxgeZZbfxlpr28!eQ(9=e2bpJz&~2Y7&@=HAj_y0p6W0Kims)~KcpLj4YO~m9oR*GG P00000NkvXXu0mjfIqw{B literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Senpai_128x64/frame_34.png b/assets/dolphin/external/L1_Senpai_128x64/frame_34.png new file mode 100644 index 0000000000000000000000000000000000000000..a28aac4e0f1e76750594be887fe23ec7778094db GIT binary patch literal 1767 zcmVzK!>l%Rx%kk#J$3<$ z9d}D2B~oNj@=PwJl=Q*d;&xhr56l=ATGJ2*nlIlXfwtfqjvFq)CS!@}*JgSDO-R_mDS zK@ch?g<>R-ioL2MoYXridxwshodhz2)6T(mS3=b}YJezO=kFP}S5HjeUjO{n=5e=2 zc0Yc81qX=e+3VCT2J`f>(l54`V_1<|!(Ho$LM=y#2s(|KJC31c=UMB9pDlwk9a`*= zbN-3|Y@mp85f8lrBF698?J4Q=Wn(hukk7O}-ZP8>md#NAI}0^d?!)lJNFh%06r#Iv zBy4Lk(4aGRu4T;n&Wc2g=;u;O@k%~4tTt(@lGu&j1@+pe#OGz@bFEKQ50>X!oCYEL z+~iE>xL2jbESRd}qy_jcw_u*^=pgj~E_*mIAd<7l{}%(ONdG(1nrX zs7}$b<@g#ow@5pVbJvW`6c6VW!g8ZKvjR?*juVKo;s{Rd`WzSqjf8JL;7t+8-N z;#{v5!RTE8LyPel0rYr*C-Vep)XqLow}>2zJB@IlQOCObP1^H|rlf>N3i0G?^&!%% zvQ!(#ihM1=_M>{c-wNM{mEWbpl!!lvfb(UMvIe1&-}`&zhm`iGyj>R=TYsB%Vt zEW9OxT*2rPp%l;yudIti3U8biU0OrE)#qx4?8Wh44Ds^jiiKteTC3{zi2Re-?f`OE z$^YfMT{vlkt2n`t2!NrLae^H?KZ;d`x`GoNO97UFSLRoAu+s)$CGSqma)7@ljk67S zOL-C_$GM}J;Q)W6l>V78&NlSa<2#IV!U^1?V6r%Wrj&k9DgBjF`t8)c{3sX^Z8^eS zt=lentq$;S&iRM&{-xj@Qpx8PyKf{{%L}J~pG4(H4VkZO_=+pA@Cxk)bu~d$|2_cQ z8HUkc-y4EhT@vE>s|n(h14L?aqRch6(IB*i-WBaEW6zETb*u*mh_d1D5dKH7I&G|u z1~Q_DH~_o#y1FAsovk*#w|Fymq9zZGz2eCH*RO_IgT4~!la6}wfF%k~Kkcq$R!VsI z3n7uRh}Ebk^Cs$`$<^kfm3>}0SkW_2w$9#oR`m5EnPsyl4iNDu?Mob|c3q%KxfIxS zbcMF7ty@!Tz1f#+Ob#GJi+5q;-R19af@mHe)kiM=3`e(|K+4nlM4SNEhS}`N0W6|% zHR9K*Pju%n^3@t)S|e%w?2XYI37CF4=jY!Gz&yj8fGN8I-ehLz;4PXAn@p}23B3pz z)yEsVRSveNza1Qz$ja~@b+VBKMkBJZt-Ko1Z$ux@3A8cT&#ZmL4Idyh;TBsRfu}s` z&?B#f?6QASS8Kdcjv&DzwKp~+=m_&5sMP?hRFMZVt$A^GHNn92_ex-inT|YTEwCC; z|62Hr@WwoFwiB$30+#+-UY?1l4WvP(?QjEPpNBF~Hk1Q+Lak~+OC7J`1fKpf+KtGu z+E}Ro^F2B#zGFl-^s8Z3enXZ#vsAo-6SVYMm9i|p)$)!J&d_C$7E0tU$GjNL`BB;U z^GeKg;)<1|HYH9HBY3zBlA*#HkP#yci&_fkLDUI$MgiG3rPxi~2-#z)AhIq<((&0% z%X64Zw30&hNAjUfuE#O1<^&@;j5+~JyXX-0Idf3i?k*c^iZFvofmr%gL0+D0bp)2f zM%q@a(y^X7oB3NuI|pTY%$@I>iX-)555Sma>$waFi2a{qp&Y=uqF~ zlpep+-^vBNKG9s=6K$+i&`Sb)9H7SuS!A$M2)?)G8(E7w?tf9NempQLiU0rr002ov JPDHLkV1lcdZI%E4 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Senpai_128x64/frame_35.png b/assets/dolphin/external/L1_Senpai_128x64/frame_35.png new file mode 100644 index 0000000000000000000000000000000000000000..04f8c1a7f30fbdc51cb1c9d7ba2d0a6b5e7f87e5 GIT binary patch literal 1832 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 LNkvXXu0mjfzS&Re literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Senpai_128x64/frame_5.png b/assets/dolphin/external/L1_Senpai_128x64/frame_5.png new file mode 100644 index 0000000000000000000000000000000000000000..7a111afd03b4a20d23675dadfdf070cf0be65452 GIT binary patch literal 1869 zcmV-T2eSByP)q4TA|+B}Q9YjHJkRqu4mr_tw`xSrca#v!!aQB}Jed#Ag#>&<6N#i&nI8dg>Sy~c0)F!vwcnnrKN&2=XpjR!mx19z?s6xtC`9Gz3xRt&Sy^?`i#-Xb2hX;UOPH$ zPtVv$amLHNFvUa!^qSqEN{pQ3S-pF!iOlgl`#w?UEZ0N?F*?PJm#+$&9;%%wa&{KT zSe~5h)JJoRI8Ot;&XFRh-7bI$`&Ddz1$IRu)|hAP*KERT2>py;=1 zu9c+^4xD#`~;>n*S%I;qTY4qk7Pe~2$qC(HHi#V=2ZP7eEkn+B9USa&vyzDu3?` zu=Q~^&j?rpM9#P4HgSe9vU_*?cLTG$xoV@MlQ&vMRbrHP={9Cv+Z_Ofd`tdz;Vz?B ztP`arMpkJG@L)rn#|f(Ce>ZppHQLMDQb1PzRLMl_{SH(*VGI9E3SH#@e=nM63wp0n zD-CsbBwFDBe~8FG3+CB^-HblN-A<5R1%u7`Q$&6jk-tRbw|A@288G(~QQs<^?%2m( z2l)3mjvvA2zYNfIb@c-&MyK8TNvD9HCB_eyT)aD?=M)|)pYb=HP$^dl=m6Hh-rc<( zQlQ%fZF5#_663@>9Npf9?BoTP6yQCYL~T1m*35M$J1>(4X63QEXt|XS!(Ji)Bj_%& zGIQPetH7?MYJEDQQ#XO$v-{nS;BCezd1KdLdbINr;olo@7?x%XIO4HFa zQvsBEPZ;G<#r^Jg3Jk`Hv1WBZ+O@SMv-XDtYaY3;$bud_K;olV|8UB z2&+d{UG=O15p;|2VNkCD(6wqiNzsY&=Gu5KMQ3tVx zMowxS7!g_Ot6@=TVp;OaQt=&}fE{y36o~ekC6Ux0gm|GupK_ZQ*__YHwy!I((uoHdJKn)Ee1_MK1-6Aaeqw$LRbcV;Q*>QGhudmZaM@_^)D7F7ZkVONJt4 z+FT=!;VpKQXh!po_GgLZDj@qrRaw(f_j}sOkPu`rEzp*JO;E%g+Up)@d2G&QWO$pl z6DU9H_J)Y0_t>z_`^+{f+TF zEw=Q_7J?K;ij1H)3fOV8r!^v6sN7;<4eIsrQks389pL`}5S7jm`a^e200000NkvXX Hu0mjfzsZOo literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Senpai_128x64/frame_6.png b/assets/dolphin/external/L1_Senpai_128x64/frame_6.png new file mode 100644 index 0000000000000000000000000000000000000000..318c7eca0a6779c44ac6a7e40e50333a19f5dbb7 GIT binary patch literal 1893 zcmV-r2b%baP)Li{6-5le3coMyz)L{(k^m0R@u^ien z&2dow+fmlxNu;0D$cA$#LGU7rlwLc-UeR}=m11E z7eMWv!7N!CZvL21H3gROE=H(QdWcyM4SqJ?u5s0 zIL>rJZIAh9bu6)afHBZJ{TfrZq67aNyp+s24P@Oi7qI#9%bQGGL& zme!WtU#|Z{+kEYGk9NM8>;k;`Pv{&O98aJb>)`lV~+BfOEPyRuh zfIpKzd*h{|vLEtRDHkoU12ZRoz)C~fUZFmb=bBS#<2;>-#Oq*hapSW^;E)H(1;`VG zEmb+!~i?Q#K;u)m7!ci{*JiW(D*L$K`oaT6#zL>2QCt}Lh< zv&M@$q{p3R^_tzww-lh|32&f8CQ^K3PS0XhDwEDtiIRN$>z6`h3E`DvSwwlnRnVzl zmn@CQRk_o?@3;({HNM3OWF)X;cczWbsAI`O_aUhLDuC|-5HiL`1kf|EH)luwo?+v> zP3jhz$Kp;i7gDp1rT#_`M>fA`N^)oy63sotZUgfgZL@7LLRCP(9K~dX5~p1o{1n=Fgiyl1ytdcl#xi`$;s%F z4fWQTs~NJ2)4v<&<;@ivjV`o~EK84`&%)sjz)zL-qUb1`X@tsJB{A;6DG>m@$k3jx zm^X`6j*`(w@}n-DPD=rnfmh~N>0oDv;)+3MZvO_xoLvs^caxmMAm37+iHJcv(<6^2 z9S-mZ5&hF3=P*?2@gtn%NGBL@uY$qm{7FQ=6VYEp^xL<-*+WEh_4|m7Zq|GqX*E{WA8p_Jdj_$_=mK%^!I%A9(PsLxOcQk-yV3ZACDg4XZ#LSrL zI##ER)zKhb2BNoKS91gjuCr{ftRWBu%X(__Nn@|LGJpLmVPgX8&DT{3(}28A*4Vn$ z?`(Ki-~2iHdGx8`?OzCqltpOLv+`!NjMJW3WuI3L4pi2O^!DUgk;{u@md!pmK;(4r ze%vGZpVg-fE&6JwTZ0#^EE1@V*Vc_9hRFdq0r6hgL_*h9(DCwQ$mwu&%L({=+L(wF zNc$k0eK>$cG_Fqkh-^bgPYxnq*{UHMNyleTMl}*3{kCoU{9b^}Gsp>$vMVrqx*|Gy zo5sT?ALGrH(ZSNklbxMM_J`jNjx1zlc#k^S%mzy%BH316jp#R{kLLuM46Ra-{RbYh zLBcByQU4iu%1a%h;Xe9I_CtTTu9m!6j=+H;wI>@9bP4k!C~E*zs*IClohVJ_EOF}~ zqDX(OgsbWh)w3h&%4*<&1b2}=7?=*BC?^chFSSdvgFQE@hXuZI%Y)_7;+J; zy^dBUp_f5gDB-7E=EZ2vkIpTBj-aYTrxOo!asdQH5+it30Wwrr0ueEiVNpu~6-1rD z3xBg!fi`x`m(kBcG!;blMUwQ7w%xE2nk4#MJ4G|63so6fKcsJD914@3KH2qay7A|h z{u+=&AP438y)J?@!JZ*CqeSv#))At{iJU@dEV-@@Sb11O=D9@>qk23u9w`N6!23r| zaYSBqyvB$-2^>#5)ktXdu__eww+8bj(Ay9xT-#WSSIjW*nS3kB>q_>n@u|q+l6{at zIUhq2SmQtYzT5v?eD!o`?OkP3x13p7mO(vZS4km%Cdn>p*Y*s)4wmJe$1XEKQDVjb zv#cBCMh}^?6tQqQPlWaJc1w=M3t;3Rvj58%OUcNXNFJ(2n@m*@$+tu`2dJhpE=!v` fYHVvSEXVi{Ub^TjTGP(Q00000NkvXXu0mjfD1DE3 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Senpai_128x64/frame_7.png b/assets/dolphin/external/L1_Senpai_128x64/frame_7.png new file mode 100644 index 0000000000000000000000000000000000000000..b56b995dc61244f395c0ab2713e32e00b8909c94 GIT binary patch literal 1835 zcmV+`2h{k9P)#P5NWd)xPY-?okReW&qvVB0n#qL*toel0wR2r7SEzwvA1akc z0#Fnfse$M{O2~Uw+Z*R&;~aqKr_wb(Kt!sMcm{NHjO*b^^y;ZY8J72QMWVhO+C9y1 zQ2X0Z`tT&uJvFl6+(;0-$RfwF+G~Ap)bT_YqYEyTBaK1p%iAIEAAN6y_X8RLk8r(2s4D0G9W9tY6FD6>@2X{yQzK$Cm-TTuhd3p5ocMy(T$u;zszo9#31;*Rdl40it<*BlUX++ zqKCHlns<-ueDR~DqSeMK4Ld7LvT(d2R>)LtrBe$S*8Yq5|)Rxqo4 zIk!fPSHN>FDWfH;9Or3SNQ8xJ3ibp>RvoDf(B~{Va(>j~(7lK}nz5nv(XJzBduqx? zf=8@e3saGZfIdeTs1p@Wo|QXWO?ZZ<>Gz2md%4CUh?W;etbA42)X;5Ama#{Hl%>t% zdA&6@k#ZWSHMRsn*X095!hRRqUxA|{QB;|zAA+Udx1B)QAS#$AaOHtkqcbFH^F-#X z5YmCl^Sq>h2m?nQB{GrX?3_M>sO(*-Od3}uO7i|+FNJdD@V{E;ETTN(DnQY1)mSs* zAR<@gPW!&&GH_P;7AKIAz#4mH+E^KNtg)m=%4fY=tRPZs5B95z~gEdYj@>#H|rV892hEyhqvR>_B z;7{sspr{awSL>o{1n=P&!8_1ytdcl#xi`jZ;C? zpsby|t0}UI(|;T2<;_(KZAFqRMwz2l>z*uM8AFF$sQ^%XJ2MnA=4S_m^FZZwr%@n-2WJ$?dr4NV>rVI!YSa#j@P%5H$PSJ z1J*f(hR!qoA(K1<=>{|asbFpIo_A@GtsM~~KElxDUdWNW;LQLcH8~LO{CZ{*(M3P1 zBIu3=W9%0Lh*` z%I4sPn)RSe+m#B5D7-5rJT1jqXZMmMN*rx4R=s}2`N9m4eA+c4`vu#`UIy3W8wZG3 znD!D!#p_leP^E;S$?okDbU@02PEEaNhhDNVIRFne-hoZ@9k@U4YCp+VSZThTuJ1XULZ_ zpgcRnEv&qc#^mj-{TWXd*%$sg*b{V2)`h5%RTkeOS8{C6OFNywGXi-H$rIr-4|yRX zh}CD-Fc4|cAc@WDau1PP$-bizBr1njU1@zaBi3XeFgJoS3g8p+&wN8BTVX^Ml`)zu zko-|4t(u=hGn$I=Z`+Tp;4ZRLcZ_#@3>sbU^fMFTW0! z1jI`Xd=fOcmO5sQKpTIyDnR!=xyHOzRHK$uG%`|Yz$j&eei~+#CXywuEEQLlKbj|0 zJv1{;R}`?;I!YqLKMvK(AU>H@GF73KKbb|95ACt|l|~$aR%vArscaBl4ukl@dhAqs zlw8zmZbq;oL6u|7Mif34QJyeo#vVXx9N|jxGrtD^RV>OST1kPd&U)CYrAHN3WyDzp zRoVE;ssZT-aV6eX>h>uILz5td@d;%r7!z6CVSLV@8Y-=R)-xOlO-4(p(Q3gO_n~>W z7s4w_km;d|J+JWQ4n)$0<}|X6X3XO+a!>?wComGuWD3y8Vh01y7=bre2VYqUi{NoS zh9WRp`_abyck$I+tzBQWI7fMG6j35LJC2o#5pRnSWOy7b!?njQGst>oc0BDKV!&D- z4_*ZFWQxdP)%{wTjhxmh$zM2t<@G#-%W&f5GmS0JqN6Ivz#?2xV?&S%)!N8=Su#GZ Z{R`QooeYqW*75)V002ovPDHLkV1hqFYH$Dm literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Senpai_128x64/frame_8.png b/assets/dolphin/external/L1_Senpai_128x64/frame_8.png new file mode 100644 index 0000000000000000000000000000000000000000..6c4b8757060ecd792db21380ce0c77ab5129d93c GIT binary patch literal 1772 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 RW^(`l002ovPDHLkV1i{(Q6&HX literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Senpai_128x64/meta.txt b/assets/dolphin/external/L1_Senpai_128x64/meta.txt new file mode 100644 index 00000000000..f68f0b563ba --- /dev/null +++ b/assets/dolphin/external/L1_Senpai_128x64/meta.txt @@ -0,0 +1,23 @@ +Filetype: Flipper Animation +Version: 1 + +Width: 128 +Height: 64 +Passive frames: 16 +Active frames: 22 +Frames order: 0 1 2 3 4 5 6 7 8 9 10 11 0 12 13 14 0 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 +Active cycles: 1 +Frame rate: 2 +Duration: 3600 +Active cooldown: 7 + +Bubble slots: 1 + +Slot: 0 +X: 5 +Y: 29 +Text: SENPAI !!! +AlignH: Right +AlignV: Center +StartFrame: 28 +EndFrame: 31 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_0.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_0.png deleted file mode 100644 index 340ceb47f99ee859720fa8cd89aa51f234799bca..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1656 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 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_10.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_10.png deleted file mode 100644 index 890ea5d70da8bab182cd7d25c234a2e2a1e9d610..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1494 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 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_13.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_13.png deleted file mode 100644 index 6eda42b5d814555b5e795572face69b78fcfb0fd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1585 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 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_15.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_15.png deleted file mode 100644 index 554a177a93d8203134179860bc35ac3899407026..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1771 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 b4o3I~m)4F_96F#z00000NkvXXu0mjf;*=8M diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_17.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_17.png deleted file mode 100644 index 971e8f55b5c36b7f5e24c68d4adcd62512986417..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1503 zcmV<51t9u~P)jd3cN7lpBDK~TTc%nL2xyv)oMky*1pDDh#Ld! zR{rk!BY4vG;xfQb!#6O@Nq83~QyCz`2v3TLD)bnChaUehz;#`*#NiD*`;#!jHh&m^ z4rG;CPu+dAd@@7f+IrV`wbojXQ2~4=(8l~SNG*mLAR76+%s34d@W~Om10w;I$^oqU ztbxBQa0XI2fK{*U^W4y1!=Hr<02;vGsO6FFJ$Fxjs589F8NLEP9|l>G?mg2Ik$pZ1 zt7JPN7rNyD0KbCGW`dKQ;K`r?{Nfc@Im!g1r-f2C2O@&zC9Y z;bm3QD9oG=vZo2@HfzSc0j`J6_vtJX{xlHao+C*0GfxvEOt9*1;@wUVCIfKr905R% zY_DE>rX^zJGYBd-A5VII^3hhq1?)?qsm{)+A@i3H}G--ZN45%iSV5a0B|?3wl`;@hAO^i(HWT| z^&{(AYU~{h5TV3~@o9dL%>}Xy;I;E|3@)Rzhg03`EdRNhpM+?{^T^`w@cm3B(K#jK z@n@}nWP5rX1MtOD1<-n}AGXInQrp=|LK%1&HLA4c6O7;5f*gQCiVXfgLaMjMrTsFI zEy<$N#{mYVz=M>0S(!nO7u8$_Id_KO&ux0m`$bD!W0 z+ZiBA%cEmUiqDJ@VUFFApDELF&vbyue%7-MSp73j5SeFpq|YvweHfJ80{A!@ysaUM z9{N17)bwC7Ky)wUii-4+q9!s=6jsE0b7^bYZN#3kSN;pw4?{c()jZ8o@(h~JjF*w& z9hm@S)z)J5zg+}zkBZcLeIkk5ccI4KoVU_MTph_T}DB6~o(RoZ=Kl;5w4t_c-f6I$#!buo3ha zpDSZFs1snh(&*9}dm{c;wgT-@C*TfY)zLP5QPy0Mc6?k3BTJ9&u|0cv<+xtC-2o&e zWJS^p9dV{g_UbiuMApjr(+m)aI?aF)29U;z&gIE9I#*_#462!98Ut_x;$#`cWF5e2 z%NNlrmp4x@X6GiA#&1oy2e8oE{Y#nI{!-uT(tiNL+i1zqh+LIGS$-nyivQ$2Qw}A5 z*&M*y*QIBxFI@;#npLm0iM;br%>ddQ05wS`j#HHghQ1ZdDVGao)obI8)bsgAQDGnp ze5=BDEB1J=41X=03ZzBeifGbgLpiuy(Z00(3P<2HjwPr0*=6c?0owkH0`CBilxPwr zIcr5y8G*$J{CRdc<@*3!fisw4bj(Qq*^VI1vnukt7$CC{X<>*R+e8m`F~UkGD06@a zrP8*v{&cL8Q_2yhmz7(9XIO2|>k(mO3)q#cX0akC_zyu3=q+V8lFR@A002ovPDHLk FV1g}Xz1si) diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_18.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_18.png deleted file mode 100644 index 4026dc37364f9ff38c20c0ece6b94777fa4c8810..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1663 zcmV-_27vjAP)Fbt%F{{Jtl4+Xaj!(*t+$7X=QYg-a29wSO= zdJs`-tr6om4*KHv;3b_;MATX%{p^l-!W?$s#Toy!%zxT^dVnOs(UMlX4cS`zI&ULB z7~pK1s8$|YR!U)^E zF@PK>x>h6g?5*XK8H(rDJIAB7)_R;ulrw=h=9fX{VweHY%-7dS&`1TJoRMc>B%w+* zfcM^dj*Rui`6$%@-h0c@+*UjK>-cNoAfiU}PRrV<(S4NZcXjq%_V6?CM`kMjUAcO= z^Oka#PFD!Dj~al8-0ZgQWqYq3q{syW;i-6A%B`bd=~#pbxGneX*&SFx^u>CF-vyvs zH|?wn^=ECrfv^^@=%qZtL(T)x%;!GdmQsFZf@Mm5fBTsP{B0mnp0!9;duIlWRXd7Z z1?d$EI~1)o`cv&dl-CFz&;z3tneWNd3^pqJF3x9^7~rFGVHc1+iwuXfiTYJ>Hz)R@ zwHDU^$N>7p4>ca@e12c~F2SoU(EHy|7w{a10zeL?w}jH5*-2tmY5lj=M(d#4SMQ&S zDFMLHfq-N-I;I}i+I{FY(RUl34o_wRlNm>9fDG2LvBwe`l@rJ6F?G*8?hfnFj%YFi zL_&!^mx7GOWu{wgkrT>l=11oocY|ie5(A(obqG8ruf>j6Tk8bbP0g?03-9Yb^j;Kao)bconzR&VpQ{dklfFfozLp{zz?*)dZfr9*kx;ZE z7@x;G&3Hi#;MF9IUC!4w*E%gd36z)}XOd#kjGfIG)tUndmYvQgqW`}Q^bFjKY@M71 zgju}I)4FAuKa)|k%~v~L>i;&Ef9?FqA_n!^tPX(cm^+t^UZ+`@k@@nOY#S~CmQviA zKat`Hdio#7`%y3osdF`BcLV{;@oa=FZ+eW(PA~2#KiXKCQG0`+*ZHhlFM2X+HzTl~ z@+$S0^RR5`CCNunTl{aWPu?K9kER)~y_SPGG>hhpM!oSWPKo;UjytkkAkQQFb#DVJ zPK_e58U&s!6pge$tg)mXa_!=_DD6i&@pw{BM!#n}cZTzcDE?uHN8Oo0EdxhmWjc8BP=-NumE*~A z?{G!zU?Kb`VHq7~5ob}R1QHNd8{`=>hYT|KuHfXz`KYZlNFy zHA~6S=wXrGK4)aQoM&cdOLyI`YYE64X;D8@sct)xvAyGX0g3eAxhzDwS&5*7WoZo{ zm68V0?{)y0Xv8`ZqgYrG1X4aq`+8219o(9rX9@6tP!PjgE)W@BaTB3e9-@zw(L?7_ zzY+Oz9BX;7?w9>Iw3-XBERPs2P!jB0`R?<|Hn0*1S-TNA6^E{L{*0iv#^W&~=cCDx+Op>y0G;@#f$l=* zs{+9*2t8RI8+Z)aTJgV%jf6+-dJSatzapKF&NCaYXI!)n_zPhwl15tgi)R1;002ov JPDHLkV1gE38lnII diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_19.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_19.png deleted file mode 100644 index b8cfa50ce63ac29e44091e6a3257d7fa093d7e81..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1661 zcmV-@27>vCP)LGZ%>r!c`c za_50@#$ODu+k(Dy?OY}Fa zmUox|*vQu{n=W)sfIeU3G2(Ec8o=w9hmiSj7PFj+r`J588o=w9k$hcONC9npblw-^ zxJoItly^_ok=y%jE>19t-n%z;1=yV0JNFZu`6Z*awK~#!-(}n23Ev_;lK)rM2{Vk( zj5(q9`BejyV(0KE6QF);&J&_Vf;HZWa=isGdKIi+XJ*^BhFsUWGDV_cw^F0&aQAsV zxW5ZPmAy62Qi^tr-n<@mzHyWLHy~LNPIZEp*QqOetMzIG3tDm3OC|luFa}yr%g4@_ z=eHak$#g=#M{pgWK`XyK)2&npN(&8IaTbtcWZ!ye1L)Vck%En(eWbG!K{-H!6lw+& zu*w;IKj5XANR;ZRm(O)T$Td%Xzi}1yN1>s~Bpq7}FlyA^{871>bw8pA9LcMt^luGv zZR@+l=oPD%cG&OM+8Lba1hrQFFM=3&j^w>#u&#_2qU6uD=hQT~Wvexevl-BxpfHHcI0Y;Un z=VvN?Pk}w5nDzCVz5OHl=%u6CP7pPEmjN1PTn|zHW!AHbL@)9Y^DvRfTnAZ)L~D3^ zXE9`S(bB&Qn1RSzZSPh@BBgr5$$kyXxwH|zxg&_`RLaxZ8OAqn0e2yjR+v=b8NWy6 zTDjUedik7{2kq-Q(@Z0$89+wg$5nxA?Tef>0?+uKGqC$bY7u<~y~Z8R z@m2)t!$fIY=Qa)W9MuJ~wUjaotnssF&^p!6BgR5&jylD!Tfk^8M6`zbd-EC0qG;*x z%o&&z#r9<*SFXuCrx-vh1PSaZXGQ+B_RLVUt|2V?MQjj}Pe;3cKC?yV3^4$s0Ie*< z8Y6j`h2Dd8R2?ZSmPJHAv6<*a>t$;ZW?tl+EWOWI54LwZfIL_!{@)9<{$Ad+=Cb1>=gQ1O)i|@3M6?#j zw#ytq3(Fc4MQs#293jI38KAUqBm-CnpBX_6yP$n$V6<3dt9Gv-d;|k%AjL&1UT-bb zFELyEXq9e#@6DTG{H+Y2fr`Ow(bi)Zi95w-9MW?9yCVKL2GE#E=OcyXOt8-DvA`Y1 zKZ*e~X3}|YY_ST2SLlv0?A*M`Hgr=Lk>q;&evcZE&DPutFT-Cr{Fmp zGuqz!l*32x$i8TN1w0+9=ejePB=}_j-Pw9S5M{p(2=oq(|I6GuFiG&n0J23vF}4WH zd8_+>q;w?vhyePeAboUWHJSb0oj?_dE7Y9P=j<(Db*)DMGC-B{TYG1PD!ErT?u>qC zFw08~APd1tRLIK~cz2}f*`QuoD?9~^XZcr&titHtLBcwHRI|<%vAV`5%KTwu`KN<` z4T`GZu>w&C#EPzWgihe}_|{r)&A#c=lVxuE&^Y}*i--+o<$f!ET6=YVgy^!T;?|G_ zdnxq?8@~+DntzUG9cUGj(R&7WItJbaFAQKIlf53Bpa9#Itt)cBi9sH{_ihz|`hQ9w z!2nSuk1(@+T-}LR2sBs&%qSM!A?SeGQ4^p!&o;1!vviYofmgv%wxa%C@fRtB`mQk0 zN}cg-_(3nTrvlI5QDC5&Ad>C%If`9A1uy^xA_`?3G~RhY+Xr*$6Uo%q+*aUxGeekP z17A8N9L*RN)f~~BvpO&rNAmYoT)pnlcvcC@WE8sAwZ2AXzeQxPg_-RtgF89Dwf`4E z>;(^#Sy^YX7ep(ki0oI+yE$^o)zL)^n78FZXvZyLI0RSWNd#On^>B=^`+4 zoRwLg;|KIMQ0)Lq_ssKMA)PMUsZ)SOkJ53{+0jiQ%J%3^AOlZV{fyeX1_8?Wx0X5r z7(f*{GPYNQWpDacbzPBphnm23b`=9ipv={Oy1%s@psZvncm{f#LZ=S-d?zNLGTmt1 zt6+qz@vZYb>a%hh$>_tJFrxNsHkfht6a&aTdN%VSHjvL_jP57@pS{pQ&hs+fYmuJ2 zfP%2X`Mvk}CQuGwlw{!537V!?SOEn~C6!zCN(DQVI@x1@NMSj^GF&DjnLt81dGin1 z42JC4%-1ymdY%oiEg2rQBmE=AQM5~?R}mF_%sgI3W|&~c7S=Np@P|1YF~BTcRquZ~ z>rs6WF$l^?BwM~?GCTU+dmeLH?U~O1WdMe@qNuL|lkrg>k4-#=uv$+KMxNGt=zhBM zAOBAQI~+NKp;)AktP>C;c(sb|&lqX+)2&wnSifiHJ&XY|&as}O_mS+@<^wT1UiW7+ zzsl%Rhk;WWpb97(nK4Y(dyqzDbkGdKW`32?wcae#9KrxrHtFcKGDI)VL&O%IGouV; zbjW0ErhjFSL|A43G?|V_Mj>P@MtLnm{zdb7G%7RPo1b;Y(ey0sqwN$iN16emNS70! z%r%ZG?GmwdO^Tw-tECw2T`Wot2%LpA7^Q+;?p)`??HZ zImL7;WHbNlu&NYe;1I^$@oALHzZHD(d5<7{VCGJss4 z*Z)Tk)>(pJO^EtsoD==F;`}jQ%F@Abw)0lnu?FN%Z`=gV9(|WI4coF37C5-Vh@EpL( z*7{`w!J8{%%rl%GttrI-mO(v3tp>rYOvVt1Q4kpY&t?RqbA?>@V-S-`I|4w?Z9!)H z%=|Okh1Tmxr)Sx0i2AW9fxd?7c^f*RonWh7k}} zk{~;OngOgyqrI2e$Is0EO0f0DKs9q5!T{EynIg<$jqF^Oyo`*UfwQ_LGV-gOKg|G0 zsiBDVn+6j!sDHE#t?FBe9R*{xypjOW3zi~1$%X_6@7Q0VxAq&FJk~pS?Nq2 z6!Q0sEcDyJY9Vz+M#%O*l>w{;XNyHd=dy1__Rr{B1vaR6Iej|=WEQL!J+PDdBb>TP)(RCt{2TiK4oAPgi`|NocSC$GG&k1;rqbTd*#({LCcgCXhB zTI;>{MmUb6Nyz(jjeOVId1F0Ti!lCQ!#hL8-ck_d1W~^a_Tc3yKYG9;p*;^k984ySyKw~>R^iQ! zHhS*Q?~i2tS7U%x@?2qu^d>=C`^*K{L?gT}IQiIJy-qefem zXIvq%%fMv-iAqL5BG5aS$|xE==B_Z_os8dFk^w#%Cz8b(`n8w?d3j|7cQfJ)UNen( zB#tNzZIkUENix0b|8fx2d!KUv-qDOBpg{ZA7(#o$F?0QGApMj$0PmDLLiEm`x4n!$ zvc4>(6Ic(V{u2|Z`kBcA5LoX4M314pNSC_`X>8cd-iR7tZhf*VFasV%C zWj(2k8Opa5z(2CRRJN2s<{=t$%#^Y}ZMTuZDG{D!fD!OXTi>d{y ztS-xrg0(1HfhgA?$Q&jU+0lLPn#Q)uoyIR?0D?7$yl=J3j@8=y6F)>rULRHtV9lR! z3FB8WfJBA`M%V(4dJl4$@=7izngdw(2hq8Y0fB9uY^Sg9wifv8W9lG!JSIQkRM{2wdhOHKv zoW`TbmG$|x25o7ObREfu+(TrQ?LDJ;_s?2Jkl(EmUIMg#fIbOCuOCCwD{P(+;>0JOd^pvl)ISajio(IqbtH$Mh z?aB!<43J@hp=}^31a$5zO(BT#G-0ctc{>8R zL}ZqX-Nx4R$>q*4z?1`^3Y4CC#*%;-Uq%LpoJWM+M%RK#`#ox3{#LMMWNodQ8N+il z3*5+Arl9s$GP*YhxW8%#kcx|xKH?BFPBhw<&u4d}SIV*Mk0Dm{XP1Rp2B~OISZVaE ze$eui$H5%E`$xz8KDz#nqGI_Wd3k?T#^xoB!R6KJY3(k>01{Eeduv?QfTK~A!3ZO0 zm|4=Y<1fhpP{A|F9O5)ueQT@=Mu=pva7IFS3kHzTjN?sELu6<w1)jPY!%QbM(CW^T@n2jGR_?$l9gWW+ zoqK7S*#%@AA*%NcsCN3xGQjA>SFyrwoaG3YV}ObYeKYRJ2tOPkZ$`SROep{W002ov JPDHLkV1f!6*p~nR diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_21.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_21.png deleted file mode 100644 index d0e44937c1dc542699af72070b551bd9e9017462..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1542 zcmV+h2Ko7kP)bG@H6lRj=2)NJ9B%jhyW{`3I8MgXhfa5rPjf1f{ZT=my z;jXB#%?AU}g{BU!9f)H(ndQYAgn9t$KJECA;|Mts$4BSAsN(>D0q{xPJacF7)x{l*!e!6K z?!aB1;XCj}vlg#B(`WDL6=4|=Gd*%nu(Olj<@_r=o&4$n0Q4FjWdiBGHs`No+!a6* z_?zDV_zZ-urvqG93J5COgEv@ep!PGr3y?Z{ZJfpoJTPYSX5c~kqJ}T)ChzREtJNcD z;6b7tD)GMzV@T`q{ry$AzYPSK)3wF~GlrN!V~z+5?0V9;(-XuP!)5?!0&cK_3A~1? zo)1`M##`pRxQp`~<#{OZ1*jxh#&SL&+wseyuLoc9Z*s9us>?<4J< zXgrG_-&yAXQCRb>GX`m%<$x9S-y`=)5CfnK+=$#^8tY^pJ3YtJl0Ke$+L*gSvip(% zpwNsbJq37G$$mG%$9fp=$<7C0CxKn4&~v7-)DD_7c@Y_W&Yj@u$ySwsYL_0FYn?Vm zVKLGjg1~b;|6x0C07M(a*@5Gnyyos~YX zbn6jlOaqYrk!n_MSDC0}xR?F`0t{eA(aH5ulkI6TD_1JFqY@n?9lNPpb7Ib zK*eYg$9v^WjTYFa=V_+f*^wY9_W;$b&&=n20sr&Ji)8lfzV#kJuHkq+)A;~R2SF>c zN*fcAHkIJ6~Q)C@)hFbHo>vjhN|D5YZkz#
Cux&&y{vNJfiEN|JcYG|ZZpY3dKFj#2&MUZBo)yS3> z)UO&0SG{`C_I0piNHjElewDI;K*pjvUcq_Uo*?RcfF=g8>Wx;LvzfaJT;D4=HR61_ z2ZSdeS{Q)SDZeKL$61UpGs@i1mclJ_uYJDuvq_c61f6f{fzFMujRm7;<*$5(3_A1~=7$(`X0MzHkMSd{MB zW3T1^8AO!F>LA^@`TRzKif zOS(_dz5S@pne}JR=ek(-I6VLmQZtxs+GgxRv2y#F@{ph14Vke#N4usjBJV8R|W%Ry$03c>`-nor4 z8G*mgI6o2$w+R5mj!vG-+Q|sshLFznIA8s|D*$#G>kM42N3e8?K0UQFz>@=E7c@qo s@MO8wEaGRUd>i<}2Df3xmfk+#A2tH&C4KmzjQ{`u07*qoM6N<$f}r2zivR!s diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_22.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_22.png deleted file mode 100644 index fd800b8b936fe6d4b2dad012c12f0ddb54903236..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1736 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 Tw_x!v00000NkvXXu0mjf>uC`M diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_24.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_24.png deleted file mode 100644 index 05c5e969d7c10cb70d949d7d28c0fdf9b2c51c1e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1628 zcmV-i2BZ0jP)e#l8e)t}|xbcaIT5BZB?uaMoumZ0h@lT8Vr|DBcBnYbg~R+*)g`VpJlX31nZg{~v?Q#V`XzBVS&T(`aQ}z$ZuK85jwu zR1RR(cOk6$9}8R$sT{zn_Z+8R``-`Oz(z!kh<4GLxwp#ixU;y+9)1S?UItl_?%ktu zUKT;$DO@2xSY`Z6=qhsnqW^-8FhR%NqW>Gf1NAK{h&~;GM|Sq=A(B7Hwm*#HgQcx^ z0ntd-n89O+ECv3YU_RbfC1-?L3=mm1j)Jy)eY0bQ-v$!Z_WEAVP+^0kUbM_`ggfmZ zT5CB6=orZnWIx%%p-R+dx47SevIBHP4}p*aL@3xhYw(O!enIjjPS9GTSLH!@n#WK0 z&}m=A_-LFibj9%U8SDNE`}3pGbu^Pegvk4XK7ePaXryKH1bH}xo<;ZH68Rh{2k@em z=yPIcXW6J9&naYNTWkJVyOUKwPW~p!t>vhF=ExTr1PL02H@3I*MDv%sgX{4zC&a!P zWbM&M4|FW2fr1&6E+e_(fDz`%o@`zXk=@vC7Z5$~_v*CvT(JJXte559xYd(IO+Kx! zJ2*k)aE|M?%K@w-+$bW+AIc9Z)X%S@he{(SoM$2+2iLi2q(M%tWBl8JMUQ5mqSo1TAs*$sd_~HZdKTD{L0=#ct=RvYgFGEy}l=Tc< zjeNvFNRA3dBiv&|I1kOJjYd#pN$}cQGIUruN7UmZ<42swVj7SAIF2xO#tB}YfHP5v zjtDF9zCGj1|09v@)m!&mmRW20Oa?HMz{+}tsQYNhXtNZqPM&2vuU}r@Wa30W3; z8;I6otxc5ry#6xJddq}tUzx;g4xrVK$Wf8oTB?YG=KxwXXda$%f{O7o2A9uNBG+qI zVHEDTD~ylkqqP?Mi5RrbO3%`OoCO)i6PUM+7(L=xE7#x(U03ClbA$r3w+($FNIHQW zKz6FJf%ObmHUn+qGR_oQVm{WSrm%SFW>V z11kXK{Dwagh6b}HziJrEvEd(MW&fdaFuHWX$R=a6V%|kROr#OI|IDYR5dR0h$u3&6L z_sGzhpeubf%tCAX{ke38Rr$dTbbW}EqUnt2}eC;Q?snPD{p z>;wRjuSJWcAVS38M>E1~Wah?W)on|>4n7)ESnWOoYf`K>vrt(cqRR)WFUd%>|EbtB z5%F47Z&AFq_O8--#*XI*;}a47W{}n4s_k3U5jbYFJrW}cqt$LmCbR@ZjC=)0SV`wI z5!ET;B>cIG{-IwAy_zFjC89@xj4kgg#?LrI_(P#xsF5}^@D$^7pu?Qm#-DN!lyL;F z-4ig|=`(A7G#PetsmeAms`x5aI2%Vff|d|!V`*%l?T}h)V;>2NwtKoG%+^!(SLrvJ a2mAw+lNpPBDxqfp0000(^U_4#5#o7iWbZG?H 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 RwzmKP002ovPDHLkV1h+$`q%&f diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_29.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_29.png deleted file mode 100644 index c6caf88d94b0d9536098767bffc4c66c4b9dd2a3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 812 zcmV+{1JnG8P)?aEn`=V06RJ#06u%2);$!TqlGheHXR&I21tN1jb6=91hC-v zo+yBAPcvtl0$LG4iBuHWEC&0GB!1olSRogEyf)evfZhoDlLcrsVian7HICmc3$Vfr zXK9S2M;Z<_8Zg4lnW}9J4*@%Zi+@;UvBY>WKxa2IVJo>49hn{pIocVJq{DNH~d&cdy39L27_9Y z@YYT&sUlykKvvz0APp~AC>W{7Y*A{fDFBHxZf6n9F3HQsPF)0u>?nd6v%W++c2xmL zoc+7PHP&h)0Ki^RfFr#IP%Z%HJ&yv=68<&xQ8FF@jN<|@jtjszj{@+u)dt;%L0Z99 zHd$j^0n`ps{CN4xmI7#{u-1>4Q?}a#Vw}3uo5WE>M}V4hddi)8uvTM`BTfLlWlxk9 z#MI8m3YQZx0>Ey}BvlY&z7xUS*PQ0h#4YIrQ3zfg%HKFsiV)ZZpmqJ#D%u>@EP&hi zQ7HS(or$msK&$fs=)*Rr2)26<;NV~pfacjh3$7^O5x_Vu06}m>0T+OAmJ7hwe_410 zFpdjA7_0XIE&$`W01OjCZ~<7y1z?nzjky2}lBVM9^#IN~N<}DQO2V8rBUMDL+DR9H qB5J?()q3U$7l1-2S8d+}8s`u2Q7b?iK{5mY0000%G@RH6aA}XbjzIR33VGc9!;*7sr=HG2TEr=w+Rw}J#8?u!0IBz1p z7+|&WSC1dTotBp>1MCei;FvSvDoh3$VB5BjkB2*D#3-~le}x@C7+~ABJ#w`&^4=99 z`ND!4&8O5#wQi^I#*QLiFjdTK1ZP zfa9%pXMdacr2yTFKq4Z|1d-Z+Xl&v5OE^QIy;%{WHkw9z22u{{_v=_?1L@nhen9j| z=bnGK6^H!HG?WWn>1 z>gjy#%UFT5xA3V7t8Z&4|GeO-ct!S+$)) z>vskjCa{(VeYR1nF|6-*V*+c*4gV(40%C&Nz_OiJpjkk&&^C6Ye+_qF0(8*#_Burn ztpj9xuU)jiY->3ku?{-p@Pg>7g<)hKX$HtFH2~^>qJ1u04NJ8hVOJ+wie2rY!f9y!BQpQX z`CH;NFv0*{AM{*Lv=u13MrEuVV`VUCM4WH258pfVEx?o{iq90h*j1 zK;N?ruN9jOIj7l~yn;D+u827#K7JMKIovaB>vM?_)L(-P1itpr^J-^DgTccab+9u0 zXlMs9B6?(kO)~=j`wUK->3kxw6Uc@@owI_&3aDkrf;X;3eYHMF4)v@xo zBLZL-W*8PJ-y+L;F44Mv&kBSQ?8S0MIG<>60PI4IV$h=h-ar^t7@nCCBJI0$IwAme z;RETsR#=Xh#u*qP8ziz{i{6I@z&^-8IzP(!))*@oL4R*?ekM4k1b|e8=vX=*i4}|x zEeK`bnzOwC=>9KatVtlqnt;F0$mmvB`py_9;{2#i=yiL@K7co1rofXpi}P0kF@l8m x%B{b9tT2oLv|<@Xc<>2}5#;>{+;v|B#vk{1sswN{smTBU002ovPDHLkV1h3f3a9`8 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_30.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_30.png deleted file mode 100644 index 8710e1a0a96e97627d7f0938515217a35defa493..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 536 zcmeAS@N?(olHy`uVBq!ia0vp^4M6O`!3HERU8}DLQk(@Ik;M!Q+`=Ht$S`Y;1Oo%( zDNh&2kcv5PXWi~QtiZ#Xc=iAP)c3-nBBkcM7RL(G?yZ)pws;bD_Ka6j*4oY9H)hSh z$r^L1k$G|)3fnD0Scemq{ls_4=-(5G6>5ajkV|uSO zR9|r1$o$d!KksJV{qAxZ?YO@+F%7cq|2rp|%GFEP_I`XJxZ(GQ+qZ14-?(?F_Va!@ z8?^)P4(Ho^J@H*y(8lAxZG%<1;GU%wE(}xe-tVaR`1xPM;h*()SOcW%OzsI4uviFc z?p@BZj%ma1#~&s&utZ4kbKYS|_WHoUJvTVg<}8D!Pglo`ylFvNzjmy>`|2M*fb&SbRsVP^tleqWl& z5Vv&u=6$AiuS;`<8r*BEUtiz!Y}?Kw*BL9W#3%og+TQbUHG|AQ+igiJ<~o-$@cozk z#FOQ`g6qITd;eUHcIJW~=gUl5<}1&?^YFW}(z1>pKc-)M#2`~Q-_ErlgL#F&Wz$-L zE~XDJ-rv8cd61W3bKiqx2D!hFXSzRNYxr%>(EqRSsVAgBp9kDNL6Br*LY(r+3NZkcv5P=bX+vY{0`Z-Sz+f=~tZ3qOI3bHq_YK zGJe((W9VaTNM?Kx!C*luGeSja-n>J)Ae{+~9RXMGp9e7?G@QDsuPV0S)v`DHJ+&RO z-7X_r&4Qg+o&y}`a@-h_B>^2Rh9|=oT}B26!PR2 zN4hnf3v0P*xaEQM%kK>CC5qqH)t*&f`*-dprk=S8VxQi4Em^}bckhMm--R{x4+I{) zZ0%Ud7o diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_33.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_33.png deleted file mode 100644 index 94b79d2ed9aac30fb2e73993906569b17f80079f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 897 zcmV-{1AhF8P)cfKZZMOCo}i?TbIa*qCpZXK-ys{c{8fAxT&PwBKpa{;3xIuUfjSa9;#ss3kyp#qCG0HA!nr2mYzGx=lVn za0kHe;j87dIf8_cB&-AAnM#SEcR%-n5`{5;?@B)L6s!aVz(gaFB&c#KoZo^4E^3`RRd^5;4rKt?gXHn zNmPM|*XA8H0yUNhxDtT0>TdMz#nri$Bj9n~unTY_0O?H91idE&%bD*ad_QyiTftNy zM^2zE4tqkFae~N&-zUSD7JgYCZeYn0Ag$Khy;R~QZF|qvw&^W5t*=`PK!8m(0GC*c^`5KtrR_&s z%GTd)H9)8$P}-ilheNxiOb4(YL}x3=F>;VqL_hh%xDY@?o6M6oe2WXX`ZO?7b=|g+ zQO4WDl>;=SHGr3)b0Y9E#vaFyf+>0*z~FmMBUuJKj{A#C0RZ&>8%S@qNvzl+_}U_n z+TmD@#R9hiXy8E292(68-|{%xvcFke)&O5dpY2=pDY<`Dm`>L+?i~QY{_sUQ3wO-Y z)2E6d1kkV!5+{V6Njd2N?w0_7eaX1(oL_>ggP_yAmVAH|z<=Cux~Cz80CvyFmqhRn X`cZE4+77BK00000NkvXXu0mjf+t!%6 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_34.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_34.png deleted file mode 100644 index ce0e210f4f2202902eb5e63bb6ea0de06f4d7e92..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1490 zcmV;@1ugoCP)(^Xn1&= zKJWX!<3sQ~&(Znj)@0}XgsuH|djVcVo$v{p-vR9j@H|iI1fEulu4}LWCqUL=Rd%vi z6=X+Ex|84sZIH&z$mVt5tcmx1Lu-AWr)@}r6JTODf$9v;Gf|RsB|-Zf;08EHMV-fy zq~l@uBfw#FBf${@yzhHe{vva0_oAMm6A6wgz%3$pH{7$fZzI7G0??AKV={CD9Y}CA z1ax>{2`eeBbs`CTyWep<1au^U=1^(9XcfCfglknmmoPw2{#vik@26uD(?M zYGNt^`p`U;9|)lvDQ&D3SF4pGy9FH-Y$E#%$EyJE^@?O`stW2=Zzp8Q*y3G)=jF?; zqYy86PT0)a_y`5?fHHY4!Lkpjm3nA&3JV^T5g@zC(#e-Ha?4iLDXp+|V-0N;K>9jH zESFgAa-@__tfWNM$Pj%8EeOEX+}6pLHfX({I!&wfEu45woWbtgB?Q=sv6cdGO29dN zZR2v~sJ2Gh0;cnR6Iv+1gva?(S5Er6+Kz^S2r9Dw6UqW_1xHVRq&qn8r}uJB*P2eA z)1O`YNu@v#1gZcT=g+U4#B*4iIg&z1*}ru9wS2+~1Jy5vs#V}>2w0sJ`9Q`xetlVD zy9`I?)5eSD%z@?^R$hnPG55b8YU2+s(^Bihdk&+GfjZXp1PFHOG%j3 z2bkB;Yn0ZVIn@#8UsV8l(yFl9mz=28|B~~o$~10%Z& z=WAr}K9d!Wq{@JEvZZQsT-AQ3KM_{8F!hu1kA9Yn|HIc8#D6 zqy)|zVl3x-a}_PKkB;HiBbA`sSjnb!PX=3`3EWYENOBgDKTl&uQiG@}WOEa3tcHrj z_V~xbwZ(fRQnG0^bgZ$KUF~E_G20?R6r8f`Vv7LL2eM)!Vq;dj*6H`Iy@CYN`aEdS z2iOIz-`5ko3y(d^`JPOiI#bK&5ehKpL1LQ#G|}Py%xjiRuZ}~2Bf^doJZt0fk6jX= zl#&DdWe}IaQxc$AY=IKNl5)>I(zP*ohgpfH_IFKPb^%KQBP9)osz%2OPmbWJ8a#1a z>Qh*@HPEoxh5(4zHtmd&>(E8!=H`j6-J=jz;^!?prNs3k%RmW~(O&0Bm4!FE_l(OB zTyuW(86aA!6-1Ew2=vu_Mk{wb^{&UZWN#PQ6Y3eqm8yvK zbLC)oU(p2M8X+944x0D=F3^bJsoS%p*d;+pA3$n&>_{LYhE~Pl)h4HUl{vtiQ)?Ef zV=EMZ`w#0L-;RFHJd&a1hb0j(up{i>NCBPW*xPIb9@8UKQDqH3d0??9Xj2IDC sbaVa zMNY9&gk-#G{ zYzvVABnR^vt78t{aej&ez1IgCp}eY&Mjk36sjSS4p(`u-tBk+H?&NEI;JbmS60G_G zIl&cg1b2?)yXHpq^8Y9pMQ7w}KU+9ThZvdNhSYv%C%7A5{6X|o$x^uG7-%1F$=2Fj z#V+3khZJBPs!(_fM)NQNqz})bk0p~Ib4!l%lNsV|HISUZGQM_=&`UTT}ALTHaYwrz;h7PMW^UJ1eM9#(y^lH z0F7a_{|PWtz=DSzyhp`|qR~qYDJ?{cJkR@rZYFpF=A3vJkclqOV7!l()1{pOy3fFp zx%(MH6>EKN2B4i5v@IO(x7yPpO_R@Sfhv~lt80Rtz;{Js1X2JO!k9xkE$kGGf70g6gb1@8f0INEj4`zJAh zE7FDS^I;dzy-;aCkG|(Da)!dK!jXi<5=!ozz17wvGiO4LyOEn0U?Uh64 zQMvM30_E5Ux*|Ny0F`K_i_mrgkujolt3_ml2`nR8I~BxS60V4J7XxHGm*;RQ96hIt z_Q>Y3X|!c`E}%DJ&B~T{VT(#yfr_Qas~W{kc=z75N7S zh(^EFKAQt6duX7nqNN-395IV0Vj@f+_0Krp=?w5!FR>G{OuDe-jZt%)NPphm8~L4) zC#9sT8Q@R^b{f`$t}#*0_4e6;juDg5^>mMOy90Ql)=~;;j~1!P_eM&j{h7{y^w7ot zROx3rf=01Xa5;!(EhoD>@}o{+IZK)WBt>?@%7a#4PkWT(t@a&E5S4ePGkDjN`aM7v z-1zJBoO)HiK$WaH3zz4S?^Sx``_AiHCx1@Y>ROM)0BbRpvUyv{AiNE71a1szoG3GS z7=b>kGQktBYpsXZ!4wizYi*I@k>NAQrX}udS_<01I33Kub(Ug)4&{60B4f}t{8?o@ zBrC!IJ6@^YBK=eb=v2H1C^EYpPf7#)`3^>n(m7wFwT>~sFM^PAJSm_G?#UVFi}c}X z0X^TrxZcR8Yl$$z$5Aj+NJhC4XybXuk{mYDUus*S*DL7WT9bL5^YecMSqk({zzd{& zkMTY8@G_TY9aJZGM|$Tx4zGiyMQe(!ID*AE5+n+bj7#N2`>noL89mBoAqL=`EoD_F zi6)#ur7x8+M&1#2k8n2wtVLOQg-@=c%&Gx99DvFi;~GXls02ZDe0mN2gNQlt{|!dp zd-HD=oyH%-{?VzY-&(VEUH#}a(qEESW`HF$62W{C=zG*AYQU~^lkFQFV+ j=ym|Kh+pJz1ta_c)_1ni5SD6$00000NkvXXu0mjfMD{rX diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_36.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_36.png deleted file mode 100644 index 1aac855834c50765ba9674b304504ad9a2ed2d19..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1538 zcmV+d2L1VoP)pZ zz;5I3Uf+W!9WPx5cn`dRW3Gg|Fqz5#$8ntgS$I-LR3YR19dA0nkJeh_*{unSMp>1dA8d>;z+W%(5#G$#HbUp$ zr9iq1Mr`>}0|0zxZie>xrX-m^?8gEBe+!^Y006EdK@?~N5#q-H*TDe*4dCpsUiOND z6vs>BuKqOfO#%8Y0s#Ox6L@L^fZD?Gn{b6d`@@X@^S&LsY(Rbc$2$Pd zbZ%|3_ri$Y%WN_`_Xiu)rthZ!DQm4v9(h}6jgc7R7XxTrWKudILmqLiUZL*U0G)BsX*72wptv5z-et?qu#T&EPax zaE|rG5ywicwT{(qWqwrtQ_x!TeGA}y8WUJg58f9_+WOXbt`zMbapsem0R8t-18@WK ztmWSfD5wLetlpbKx(1i!VFKxCf$y_dV@TIeV*+lc69Y(Z0eepbR!tx+Org~nUM09J z?KQS%eh*Jz0?j@jKL`Pc(KQP9qIb+>*Oi>^2^Q_XCz!5|o<45|hz5XEFpi9k$m8=o zPUqvZxI+-QoKD(TU*s84BhY$4dhF8*Mz3sMcUb$5)C5v{)tW#&+r6#C003)_0j>mh z(x|~_7IeGWAmwb00i-j|V=!Ln_rNor_hCzAMhz`JRalvyZBgzDXiqyOEs$RXjb`2m zO-kACWdu)QC>4IAunNghlsegB0L)@;_|>2r;~8tQjFstDHDA$M>t$`gZ85!VY`3FX zGn!>mR&fLM_S$Plb@QfF&5fMqmFu4oooc2#98U1Mi3aEz`K{uU5{r3^K0YyY18Bg$Lr@?afx#%e}DJ!nhym63cwZnO+$F@Q7<;2DfS_tUdWx0Q~7p0OmOWQT3Ik-%^%WUCiYwO$mt%lTAW&S* o`8%+b5oR3$tFq?FSiuPY0nQUmgswmI@Bjb+07*qoM6N<$f>dYDyZ`_I diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_4.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_4.png deleted file mode 100644 index ef5e2fc53b5ac0d5110ccf4c1b96219f6c65516c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1668 zcmV-~27CF5P)e^kD*EFS~Lv9sEdDqpe=U;_ir zZSQKW_1=3MxUQ>xkw@?SQG579o!{EO1@L=M#3!ub4*ZO0eNxmS*s-3|vL8u;Uj{gZ z3BH+I2P7EZ7+`k<@79mtlY;c>;A)=Wn4K8gIQHqZa-czFfCwXeQbtrEb>`jQ(Kdxoy(DAe;R}A zUf(nWpqbA{>FB5#M-BY;47HdExKIyZeeQkssd)ODLp^}?xrXta_wS26xLRwywbw}I z+3a0M?zsjXkU3~g_11R6T0H!|3br!cyB^>_N1Q+UgcF#d>fMJo=LF_^>j7GOJ=12G zU?B2N;c3}~I`1^QTYF`=AN~RD+Otd$*&Hp?hYkD$aRMUWpEx1QIbN9B`dt9(REZg) z&!GBt2ZPn;Jn`Ck~>`s0`H!+ zNi*-;t(R62-PB&x))ULXz>h!WKX{MOn*W}_LnITk{eYE)k=}*&M*P0^PF{KwdJ^F8 z1Bed?YrS=fh-^^RIbB1fu|qQaROh!AWB?A25eAUVMs*?1*G?7awdGK<k<~0ljyu z2kk%Y}RWx3bEUPEc3sr~NuSf?jrrC)11Z?F zo+Qfzv(7&uc%H%n0M>m7BhgA+}ogeqD zcM04k=^P%-d^%Pi;qGPuZC@Y;Ksx*kBu1e3r3D~%X>w=mMBZUY_r1Vf3Deoa=$3#L zfsm1DJ!6#_boBIiPR*>P3Nz~t|G$9Bv$Z7)>AQexy&T2!JW4lMy-w z4u#bCNMn{kG&m|YgX-1nuP856Uu6D@G5jKkH+9}z3(+7MjTzS=WuZs+(#V@#K1Z@A z!Ytfw3s3`BFs9!GA`9Rf8I>_0Ii7wdWwMv)+DL8JbXn`AbEW4k+~xFC50FK)re@?1 zdC`W?v)Sq;Bw3a|OJ>ymBN!ly>UfhjcwF-lVE`*wc!`i~L4s^m50e?;Ca?;Tx{1Pb z4Ao6-@XCay%d>^sB|Wn{^S3d;3V6N70G;DGzbZ(15NHANF$|F1zzj2Fby-1>k%#Kz zh%nNs@ht!>`a>2(Hy1@@uB0mq&-p9Lm82K#U6zcUtuWfecpE0wI#iyeAF1^cyn3wZ z@UEk_dOZM&*&q;)fe|X?bFIzP0)#I*TM8o3?BG93*V;YHzDy8kLE#0<6az>GQo-mV zwQ{f`!y}Ak&DR27S{pJCou3)$T9Er82GHO!0}=!kS;N@`k)35VZB0>DSP=x$7?m43 z1$eYbqfpuiYvVk4ay6SY`z=V!<;k}KgUaQfS2{m4ssX^WK>Jw=0%=36x}^ogOkcp| zWuDKl*X|(Tc24yG7BbGsI>M4=1yi)_c>1U;UV@NvU_B!xRz*+XjZ?o3)Tr`qP+JBl z^Hl1%F6@8#NWsY=B~jM-bgk12AW=RyRGWVnq$ZFeVoSz;lmXGKr=M43zILCF<{yZ# z7LN1-(k4_vlNr$^Ie=bgY_OVK?fyW<>7H%NdH_wSW|-8h%<4imU$arOlh3g*8-P?7 z>U65RqH$dEg%B^3DoTdM02Zvq4b@BKL~OI>dQPv9Cyh(K51>)1R|6|`TbN;?NV!1t zP-oLo3Wkb|zl8xfyk4QA_D1cgTDDc+?^sWy;giN&z7W#8{Y_%}Ia+_SsBTC#+oQb* zeLgx;8Ambz2ihY@bFxjACa)UNE?v9!o~v+v`6(a@@6;e6+PQl*yP6Rsn=G59AV38cS7A*m7&a@-dN}xTC6^tvK|H1$f>OvNiLFK6-!Y+3OKYDT)bCx;X z>j9#MRs+pqgwC}M9oWHqXmGA_I@|+1B@if1qJ9rvAm1^D|5E78oGebH^GbL<&^87@ z4Atg%^D;P%&Z~o7>w&g2Kz8A)Sm6ZcQ>+YxqZpuKL7$9OfiPsQ2!g+^GYZtf-cH{D O00005Az&DLY{*`% zwcdMg*LAhtduy$=>$(~Sz7PM$zv;WU4F}r)5BK2#WC4+sK#5Eb(YP&!_q_^MjPVV4 z069QthH!8m+?IQPC4MU73xYQV@OEHDKXnN1p5{zHjDaTz-V^|?9KlkGpwFxM|Aff> zCMUf*Gn+_$S34&~;tAxA!YT7*6=0NyQ&sq;$gz+i|Bm%}Q-G1=vx71HpAaRxqoOt+ zy8yswVzk;}HojSTs_OdSiZ{y{yw0&pbnjxv4A2xF#nVH6*1J4A18@J|Okx$4wRxQb zXj?qbV44xyJ6`9~4yk^~;|b(&i>saedx6_WzZp-S9 zK0LLDT59m@8nD!iP=o51L@BlXXNOT0;G?lYW_4?)9YSTdp7@^Eo(fL+3J7>a8sj34 zn`CzKBmPDJY_rJMj z&_(BJ3F$p(Zygp{AGAg*!3pTSfBroHJjlTNzIMb+9Z0GJB8ODjd401Iz&bzZ&5HL^ zMTj2xO7e>d#5P*T)7%HH|EvT!4ZhY}+67eA!0N}U_&FtzCS;B0Eh4QsoD5E3uj`6! z11s=uA+r%!ElBCO++t~V?dT-ZJn&UL2pVlnYrfM(4?O$)NMS0e5%;@d_iT7b$Jb>J zfDLS|zGxXjyt|Xg3FZ#uM@hd$0W`xUWKR~kF(SJLcrP-}YE($a*U(AetO;2Ad<)u+ zgUjKWa}RD;v1*q<#~Veyx(mRVxiyluX`{x`>Htq+3h)${+EzA-p(Ixn07vPl$ypPh z(H|wp+G5g3$M0$VMbj2%>niel@9`8c+X*TQD1poWcnmro*N!8{`W>k?R+1i40BA_% zaEC%D0vhc!pHpxV4_7Jjx!-u~skwY+4L}!#POJhck6P`bV^CPcd*ocJ0koQf_J8N~ zNsZGcJX)H)$f%7~z-gGh=wRR7gTwk=tol{PrLsJ$Tt z$U>8EDN?lv=7^G%ghs_I+#UH*CxNx`v`;%gCYm(jX}ijEjBM1?K2}Eh%B#x(Bq{e` zW@LInYXN}QIqoJYlP2&SOA6qLSgyS#@v9tzrW9}5fOb2O>bZi=QKbr{04X;&R&4^{?fk+mWGmYamBQ%s$>>( zHR)Cg^YpjuPr_av$B+%Fz}tQ{Xb#1Sb$5=Z>x1u0wZIk%fFY$6DX&>6i0glrdaP6g zjOg6AQveKzDlNkCb5HwGu$*U>B0zuC25gHK@mYIrQ2+`Y;T!>yBH(+tZDdN>NaNnm zM0R%E%J^^B9UK5)Nj-HG4j$?Dw2vaHOfw3?V-j(f&CZm|)2s4&;B>CGZ0EOlV zbRw0$wC9zAaz?>8y1%U*0KkzWElQ>@?m0$viL5areLz3M3eXcA0KgT4ocvcEp;{PP zV?v*IMEsEoKrvFzt-^9v9+&!FLHe-@KrvFzSCKD`xhmqnLIEgd$@z%f9UW%{=~EP7 z)na*V7#Y#49RbEVQUP{y1dNk}ZmA;7N&!1K!s%F{2!G|LaRgS1$SeQ=002ovPDHLk FV1oG?)rtTB diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_6.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_6.png deleted file mode 100644 index 789eebdfaa25f10335d19e85b2162518cd947a34..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1521 zcmVGwk@m0LO8}3Wpm+_BUaMZ9W)) z4rI02NIm;#`DBK|wN=-6j4?*VsQ^9`Xk*fTT;|6vc}z1vH1m0z2^z`iQvWAs>nhA7 zO{xa4o?BTxGu97lk*Wc#=h{Bco#{Cj=kY#2^xy!10q~i%JUhMX(!|0Tm2p?F#a+(u z9eDp3WM#T{OiUhB7jXl0*e21!0UM^wLGFxYrm4{5e#P6yf3M1;<@f|2WJ zF#(tVohTB&1_Jn*nynck%;DX$GMvlos2lEff-o6?gVzWEbEgN1DLh1-kIyrwlMXhx zlk)+R4DhFOFten75f-r+B4cO_w5XnUbK(qA48Y+rg4TNP5h9r#)s4>;d2jJ8b-@^8 zjsZBbHG(w`&ssWmWZ$>+$>{M^pdF_eKpK=X0?)YUdjsxd?l8@H{4}6d`Bv@mWG0}e zpSPVm89;)Rh0*r}8WTu$fL1p|b&AN}8InEQ89x@jBvb;yI@3$zdPer@22Zw=)y@utDW}sKKsw876Gr4DCHZ%4T!6Y^X+0pZ zjaKbfTby$T=w6O(hx5f6AetqaJv1_AlwvE}K(&8ld~N%fMXQr729PF>7%5}O$e2_y zO*Rv7niFO-pPvq->uQiK$?z-#oDC_f0QxdF;vi2(%Y5lN!f4x^9btg0p*uc#9fRNL z2k&dGmif{(UBlA7{4NaOWwe)FyYz_2ST%sO9q^zy-TAv2fbQ`fg;Iwe7O1Y-8bEu_ zbvV2IZ-s#)pvI|Wc!e3N^U?jh!{|6xBzPXh0JvCc*a{m|*dv;@IA^*MsSuDGZ<)1g|}Lsef0w!YLcSca}pt zw*@*M|L*o5ovRL@nq{*NqWvWnsIW-2Pq*`>akcxrJ?-aU%c0sciQ;DG%Z$aHh^Y4O zaJskN(`x_?R&8K8c_s+7u{;?~H_0g;X7~8naW2CE6d6k8R6bK!(z!M8xmGwK5>(lB zUY-Fc_}*M`@a)_ccvX$}x}9wmjN1K2FaU?e2o(nCEFd*q0pg4xp{n03BVF>xpfU76 zL8pew?!Ogy!GpS@Y^240w9WMItO+j303048P;i-S_E+~_ss9Sow$~ze2mpi@2o?)O zs9Tb^=E@XgbllFuu&o9Fi2ZBWl^_+1cr6)8g6C*Hm(;haZv`uy|G@xJ>grfb7c`^r z=g~q@lbzibJbia&e5eM9819OJ;AL@y&NVhcbgVZTY^$6et^uAJ2ox)+--ESXZE=3| z6!4@#=%VvZaMsz{Mgl-m)RXb#c3?G~cL)7t8KAoGtFXdqI$sqCmt%mA1w9#eWQ6|# XYhRhvxX$)N00000NkvXXu0mjf-Ye1b diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_7.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_7.png deleted file mode 100644 index cc4f5cbfff2c3c96ad0a63df6c9a20631c04b5ab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1642 zcmV-w29^1VP)=Kfb^KBn|WR$3dY?YOH_4hAF8(nusn zDW%q03*k79^5Xa4!;N1`DYe!j-`x>U*ux5ZDB_qRAN3FHC7_}5XTD3XpJFovm4BZS6jePmcI1OK* z^&QRY{k-^(o`6Z^09Jq0z}8$XO!TrvA5uAh)vraSG!Spg-?NN@*y2SFM=7P2a*kSQ z46ofRTsTLj0`bQ7FvDGH_!0R2G02K^ug#x?Y&!xT@u-jg^k9|oufSM8a)46)FKjjw zWEeop8N6?QOC)E&1Nb95sI!*|MphA4!pIpxvcPM6j8pDq{VgC{x@qmmcukI1Em%g5 zjQQ>OSe-Juv}cwX(D*2##;1PiTzpS&<&gMmU@5U1yxwX;8dw{jiE47VnU#Z-bqCa1 zZ3aLe@EoC(zUl!nh5Q{MqF^5FT!vMR%qVTIj-!Zw3Qc_+tjW=LksJXRITva_jS1ilYI6b*1d6t74q)X3 z9NHBzlJ6lq{_5bt#C7;*g4>(glrYz zeV?sNqH<;D@TPYn!K$POF?<3TY zoS}s+D8gk96WxcFGiW)0)-U(B()dVk)31UNP*fp@;KuS2GfNiDA6b|6ek2{s<7ai` zf5)~S0j==PCUgX#wsky2)_{<4rddYQ)-@vXxgKfk6b6WRpw=gyLvBjjpy%xo*&ay# zD*Be|w~irCH=2VDzX_DIjg+s^IajXy#$pX?gVLF_b!9Ny_qy^Bd0<7lJif! z3f9hV`Hnk{HBO$N{6|7lfVHkDJfqEa3dsOmH}bvIrF=j92tZVXgh!JV&4D;j6wFL* zRxm)1laMYn=HM9W04Uj@w&ny8c;hn0?xB7ubGNah&R}7v185$nB{egn)PlFJ2zDEH zgz-z6d=H=*dY9ozw#+|xp9a2{-Nr>vT4PqXQyn0>81JaqwJ!AC6^xCH-@31*H-T1; zAls`Fx(u#h?5OdzAd>HwIsg}JWvN%o83?R_9Y%~H947onib^Zt4I8lnf&J?{$Q1V(7`BIeH_0M0tB_ z2Ox~3b1$(Rp{}U2MV!RiOK2@mZ~($cI-g~HO-3f-qa~i!KCR(N4nSBz=OcmcJw_Q{ zdq)^{6F4H#83mpbP diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_8.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_8.png deleted file mode 100644 index 1a1fe5d2d4a034e644e717e621cc315e92baa085..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1694 zcmV;P24VS$P)4r{O-HzUS&p5R;KO>M# z+Ns(`ZLk{?OfvwQ`TQ9P8p-JuXYD`c0y0zsSmRb!&&WCp{5ss*!)`2)MW_a_#v z8_F}>SjYRRgctPDckYCvwbom+Gr;>hwXh%~BX>3vr7yzhmp~)vY4AVk%d;2V?0E7_ z_x`5O0VMP3XPKHpVv7i(K|R)}a{3M|@>K)0_MHKAypjpHAtQGX6=$@R)jaEGH38Y%#$(fD-{x+~RKU1SM1Hv5MXI2*SIn|wvw0=dASXpR#Fd2Y@ z*9fi6bq^9#ctD*$#-(m#qotd#! z7&=cefFzVM0%r#F-hdmFJ4$l~?*_Cx1JC)I4tIx?jHLlU7vk+%gb6g~NH=$MjII^A z#j9{`1!Ze{s(@4nl{0|0FlPnyp1?Z}tU#0AK{|lQu^_d8J3&e+{#0v#Y=!SB!-D`( zk_QLXPg*0g56j_PNA#W|15byD9LtXUsRn>`i82E&7g3VV<8m}udTUPeoGBeoS|Fc^ zsM9~5P56HR3Dtoh$+c+TTN|Nct1GbNYb?O^wb;o5VpC~4VdShEyfMRREIq44&Sg+%*wEDJOVs+|A906QR4-2V#9$ghOd-5~Xh_MX;#R0hHFdw^L~JBxP~){h2nj5C0ARM7P` z8IsP@I+roP3{6`yrD(4P!YtMtO)sfX<6|7iepht?=`VvUMKz;VSL0crLB(lY%3w*3 zXLLSKT2qsW$WL_vm9S{7s*A|HKPWdEe3H)E8IKrUJKIrRwx|K#Z3H_rQ3uZiLDu=y z_!(#!8x4Zl^-G^KH2{Z4XHvv-dbO%wX&pp;FVifiSjU{Fs}}?r(^uC5r?-I-r+P-^ z^CLP$;W<~*7df*t;BqBhFJXYp2CUCfBSkaZlZnok^yTxdAVA?Ur&VAss{s(Dd3`{Y z)6UKeth1FGkFJZ-zNHhF7by(6j@I9iOs6^_&yxQMcov>WmXlCS|d{&BJ~r(0H<~V9t~=d zEoDwrZL3I&%F8eXcLsky6M$CLuYGfDusiQ9G0z6Oy z@=cr>kBIXvs;xNFXTaHHi!LAoFO#Iqsbl~P(mCzvs#WRMdoz%Z(XAPPBSWdtqRCs^ zg4B^{x_ZT!Br^l6U5q?03jisrHG^cSmabKud1%mTZCh2V*?a8fZwm(S44|F6vqfA6 z$eqQQNB@q>%j}~f2rjDukp7s@jnoKqEYq%O@*?kBEv@uk#uZw72?xOrMIZ{xh+04) zK_kuA)|zb_z26a;T7rzVLN&n3>a-e20VWwED~pa->B}iwT6<=_@G&qL0KKHxDPXJ& zlg1;mq_KhQ-lMj&Dt$}#bPa$gx^p0);})H2>`}4bXkbbm1fu(N4e-=Jps1LeMYMB3 z+R11TKt3oMk oNVkHS0El9jKv<2P0s#$zUs0JT=-M&wV*mgE07*qoM6N<$f;eRor2qf` diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_9.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_9.png deleted file mode 100644 index a19da7382eadaf0bc885f76da8dec3899034528c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1605 zcmV-L2DVtUtjJl073;Y!fgJw#SBa zU#+#?dvC3^_IkY<|C>YV!@uKL-bVt;&q9(vEAubn%@>jksTm&;F*NTRky(Y+@}Gh? z8HU>4`;fuOd3axGd<85Ws^_{hB0=!O0N7c(PVk~19YVS$jsF+9b%+GPF9zVP6)hbUmI%4KoKdpTq^YZW62|a^E z zY0QwpUt*AtRs^;-7NH|fK=M88%K5vDe}bchY5>ppS|zSs%NB;34&HkO>~k8f(v3Ub z2XD0Y?FgegfXLWP#UF9XlZ|~6zP8cYy8zxXQ`kv;v^0X_Glq_&j*5EV>G)VTz0?rd zHCQzU9}`8?_|koA2l7{8GQh9S$%v22jBX7_jTUjjtjx$@Nmb0V~=x7SPTIyj_4c2i}Vok(vId%tZ0OzT&r)SPgJMX=Lw?-8J)G} zN#H64Ta%B038Om_4O+1n-Kj(ml5nnF$Qpe~4Ul009ET5LVI&PmGNig<6t^=vYf~qI z5rsy^q2qY_d=K6ZM5_F=y3g!%vcN;DD&sHN1yr0R%oczd`FPKoOJXZ)7c+xWBky1U zu6U(SRqs-Nwm-sogSExv_i+*V{h5@qB*LAx+ySalk)sh@9?~oRjXI!_t?$tS6UXgr z%N<~6Zk&O{1d;S$MZWZ%=F-|LjJ~V}&~oAm88gfvIj3d#=sj(brFoAq{t5=*s49pM zBQiNN5k2!ybk0cRTQvfgceU|*@3jX(lGo3`lNFhZ43IIhcD-uRNmjMok#@<;AYAl} zQ{W=uI4j3y^+RNwX@xN~9A5)y#_)`($(kAC>4!RIvDyi;Y4h?L0COrj1E;!GuojIw z2iI{%rycZ6^YE6>kIy}tPL>=5p%c&t!TU)D(F{ulDmHJd-2LsGF39Ut>f5|QY4LLF%(Uxrk zwWyTtY2#Mc3OFaumznUb3`2#Q`KoL>8RBV%Nf9e zRv%y__CEHneFj61>DlJTW=ZS5ikuyQ#!E-H^vCOy!VIKkMG@g@BT-uUG|3)f&p zxt0cIRGnp&6O5e+jHtZJeh={DfeKUL!&&`TL(8#O&WemU}6&X;p;tIr-dH#E) zmLuSU&B(IuXHv?Fg0WiOtsH Date: Wed, 15 Mar 2023 16:01:33 +0400 Subject: [PATCH 463/824] Nfc: fixes for latest PVS-studio 7.23 (#2490) --- lib/nfc/parsers/plantain_4k_parser.c | 2 +- lib/nfc/parsers/plantain_parser.c | 2 +- lib/nfc/parsers/troika_4k_parser.c | 2 +- lib/nfc/parsers/troika_parser.c | 2 +- lib/nfc/parsers/two_cities.c | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/nfc/parsers/plantain_4k_parser.c b/lib/nfc/parsers/plantain_4k_parser.c index aed41965c9f..19da0b5ebb3 100644 --- a/lib/nfc/parsers/plantain_4k_parser.c +++ b/lib/nfc/parsers/plantain_4k_parser.c @@ -118,7 +118,7 @@ bool plantain_4k_parser_parse(NfcDeviceData* dev_data) { } furi_string_printf( - dev_data->parsed_data, "\e#Plantain\nN:%llu-\nBalance:%ld\n", card_number, balance); + dev_data->parsed_data, "\e#Plantain\nN:%llu-\nBalance:%lu\n", card_number, balance); return true; } diff --git a/lib/nfc/parsers/plantain_parser.c b/lib/nfc/parsers/plantain_parser.c index 3a1d1773256..2e4091dda10 100644 --- a/lib/nfc/parsers/plantain_parser.c +++ b/lib/nfc/parsers/plantain_parser.c @@ -91,7 +91,7 @@ bool plantain_parser_parse(NfcDeviceData* dev_data) { } furi_string_printf( - dev_data->parsed_data, "\e#Plantain\nN:%llu-\nBalance:%ld\n", card_number, balance); + dev_data->parsed_data, "\e#Plantain\nN:%llu-\nBalance:%lu\n", card_number, balance); return true; } diff --git a/lib/nfc/parsers/troika_4k_parser.c b/lib/nfc/parsers/troika_4k_parser.c index d87b4eba7b3..1f1b85a5c4b 100644 --- a/lib/nfc/parsers/troika_4k_parser.c +++ b/lib/nfc/parsers/troika_4k_parser.c @@ -99,7 +99,7 @@ bool troika_4k_parser_parse(NfcDeviceData* dev_data) { number >>= 4; furi_string_printf( - dev_data->parsed_data, "\e#Troika\nNum: %ld\nBalance: %d rur.", number, balance); + dev_data->parsed_data, "\e#Troika\nNum: %lu\nBalance: %u rur.", number, balance); return true; } diff --git a/lib/nfc/parsers/troika_parser.c b/lib/nfc/parsers/troika_parser.c index 9c16296f34c..bfd22364b70 100644 --- a/lib/nfc/parsers/troika_parser.c +++ b/lib/nfc/parsers/troika_parser.c @@ -79,7 +79,7 @@ bool troika_parser_parse(NfcDeviceData* dev_data) { number >>= 4; furi_string_printf( - dev_data->parsed_data, "\e#Troika\nNum: %ld\nBalance: %d rur.", number, balance); + dev_data->parsed_data, "\e#Troika\nNum: %lu\nBalance: %u rur.", number, balance); troika_parsed = true; } while(false); diff --git a/lib/nfc/parsers/two_cities.c b/lib/nfc/parsers/two_cities.c index 0e2ed5690c2..d6d4279ddd0 100644 --- a/lib/nfc/parsers/two_cities.c +++ b/lib/nfc/parsers/two_cities.c @@ -136,7 +136,7 @@ bool two_cities_parser_parse(NfcDeviceData* dev_data) { furi_string_printf( dev_data->parsed_data, - "\e#Troika+Plantain\nPN: %llu-\nPB: %ld rur.\nTN: %ld\nTB: %d rur.\n", + "\e#Troika+Plantain\nPN: %llu-\nPB: %lu rur.\nTN: %lu\nTB: %u rur.\n", card_number, balance, troika_number, From 3a242e5fc319f28bf7ee2aa02869744cabdc5d86 Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Wed, 15 Mar 2023 16:16:52 +0400 Subject: [PATCH 464/824] SubGhz: bugfix unable to send, new generated secplus_v2 protocol (#2488) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- lib/subghz/protocols/keeloq.c | 4 +++- lib/subghz/protocols/secplus_v2.c | 9 +++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/subghz/protocols/keeloq.c b/lib/subghz/protocols/keeloq.c index 57d1cd22d25..7748da1ee05 100644 --- a/lib/subghz/protocols/keeloq.c +++ b/lib/subghz/protocols/keeloq.c @@ -182,7 +182,9 @@ bool subghz_protocol_keeloq_create_data( instance->generic.data_count_bit = 64; bool res = subghz_protocol_keeloq_gen_data(instance, btn); if(res) { - res = subghz_block_generic_serialize(&instance->generic, flipper_format, preset); + if(subghz_block_generic_serialize(&instance->generic, flipper_format, preset) != + SubGhzProtocolStatusOk) + res = false; } return res; } diff --git a/lib/subghz/protocols/secplus_v2.c b/lib/subghz/protocols/secplus_v2.c index 4a3815f0d31..c8ecbea22e5 100644 --- a/lib/subghz/protocols/secplus_v2.c +++ b/lib/subghz/protocols/secplus_v2.c @@ -599,19 +599,20 @@ bool subghz_protocol_secplus_v2_create_data( 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, preset); + SubGhzProtocolStatus res = + subghz_block_generic_serialize(&instance->generic, flipper_format, 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 && + if((res == SubGhzProtocolStatusOk) && !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; + res = SubGhzProtocolStatusErrorParserOthers; } - return res; + return res == SubGhzProtocolStatusOk; } void* subghz_protocol_decoder_secplus_v2_alloc(SubGhzEnvironment* environment) { From e22668e19610ead06697a3b19b2ac88cdd4376d1 Mon Sep 17 00:00:00 2001 From: Eric Betts Date: Wed, 15 Mar 2023 05:35:11 -0700 Subject: [PATCH 465/824] Picopass standard KDF dictionary (#2478) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Split iclass dictionaries based on KDF * Allow cancelling during key test Co-authored-by: あく --- .../picopass/helpers/iclass_elite_dict.c | 15 +++++- .../picopass/helpers/iclass_elite_dict.h | 3 +- .../external/picopass/picopass_worker.c | 46 ++++++++---------- .../picopass/assets/iclass_elite_dict.txt | 12 ----- .../picopass/assets/iclass_standard_dict.txt | 47 +++++++++++++++++++ 5 files changed, 82 insertions(+), 41 deletions(-) create mode 100644 assets/resources/apps_data/picopass/assets/iclass_standard_dict.txt diff --git a/applications/external/picopass/helpers/iclass_elite_dict.c b/applications/external/picopass/helpers/iclass_elite_dict.c index e8c13dd1df0..f92dce0aa09 100644 --- a/applications/external/picopass/helpers/iclass_elite_dict.c +++ b/applications/external/picopass/helpers/iclass_elite_dict.c @@ -5,6 +5,7 @@ #define ICLASS_ELITE_DICT_FLIPPER_NAME APP_DATA_PATH("assets/iclass_elite_dict.txt") #define ICLASS_ELITE_DICT_USER_NAME APP_DATA_PATH("assets/iclass_elite_dict_user.txt") +#define ICLASS_STANDARD_DICT_FLIPPER_NAME APP_DATA_PATH("assets/iclass_standard_dict.txt") #define TAG "IclassEliteDict" @@ -25,6 +26,9 @@ bool iclass_elite_dict_check_presence(IclassEliteDictType dict_type) { (storage_common_stat(storage, ICLASS_ELITE_DICT_FLIPPER_NAME, NULL) == FSE_OK); } else if(dict_type == IclassEliteDictTypeUser) { dict_present = (storage_common_stat(storage, ICLASS_ELITE_DICT_USER_NAME, NULL) == FSE_OK); + } else if(dict_type == IclassStandardDictTypeFlipper) { + dict_present = + (storage_common_stat(storage, ICLASS_STANDARD_DICT_FLIPPER_NAME, NULL) == FSE_OK); } furi_record_close(RECORD_STORAGE); @@ -52,6 +56,15 @@ IclassEliteDict* iclass_elite_dict_alloc(IclassEliteDictType dict_type) { buffered_file_stream_close(dict->stream); break; } + } else if(dict_type == IclassStandardDictTypeFlipper) { + if(!buffered_file_stream_open( + dict->stream, + ICLASS_STANDARD_DICT_FLIPPER_NAME, + FSAM_READ, + FSOM_OPEN_EXISTING)) { + buffered_file_stream_close(dict->stream); + break; + } } // Read total amount of keys @@ -148,4 +161,4 @@ bool iclass_elite_dict_add_key(IclassEliteDict* dict, uint8_t* key) { furi_string_free(key_str); return key_added; -} \ No newline at end of file +} diff --git a/applications/external/picopass/helpers/iclass_elite_dict.h b/applications/external/picopass/helpers/iclass_elite_dict.h index e5ec8dfcbea..150cd1b76a3 100644 --- a/applications/external/picopass/helpers/iclass_elite_dict.h +++ b/applications/external/picopass/helpers/iclass_elite_dict.h @@ -9,6 +9,7 @@ typedef enum { IclassEliteDictTypeUser, IclassEliteDictTypeFlipper, + IclassStandardDictTypeFlipper, } IclassEliteDictType; typedef struct IclassEliteDict IclassEliteDict; @@ -25,4 +26,4 @@ bool iclass_elite_dict_get_next_key(IclassEliteDict* dict, uint8_t* key); bool iclass_elite_dict_rewind(IclassEliteDict* dict); -bool iclass_elite_dict_add_key(IclassEliteDict* dict, uint8_t* key); \ No newline at end of file +bool iclass_elite_dict_add_key(IclassEliteDict* dict, uint8_t* key); diff --git a/applications/external/picopass/picopass_worker.c b/applications/external/picopass/picopass_worker.c index f2e9e82b817..e61b67d9f34 100644 --- a/applications/external/picopass/picopass_worker.c +++ b/applications/external/picopass/picopass_worker.c @@ -172,14 +172,18 @@ ReturnCode picopass_read_preauth(PicopassBlock* AA1) { return ERR_NONE; } -static ReturnCode picopass_auth_dict( - uint8_t* csn, - PicopassPacs* pacs, - uint8_t* div_key, - IclassEliteDictType dict_type, - bool elite) { +static ReturnCode + picopass_auth_dict(PicopassWorker* picopass_worker, IclassEliteDictType dict_type) { rfalPicoPassReadCheckRes rcRes; rfalPicoPassCheckRes chkRes; + bool elite = (dict_type != IclassStandardDictTypeFlipper); + + PicopassDeviceData* dev_data = picopass_worker->dev_data; + PicopassBlock* AA1 = dev_data->AA1; + PicopassPacs* pacs = &dev_data->pacs; + + uint8_t* csn = AA1[PICOPASS_CSN_BLOCK_INDEX].data; + uint8_t* div_key = AA1[PICOPASS_KD_BLOCK_INDEX].data; ReturnCode err = ERR_PARAM; @@ -204,7 +208,8 @@ static ReturnCode picopass_auth_dict( while(iclass_elite_dict_get_next_key(dict, key)) { FURI_LOG_D( TAG, - "Try to auth with key %zu %02x%02x%02x%02x%02x%02x%02x%02x", + "Try to %s auth with key %zu %02x%02x%02x%02x%02x%02x%02x%02x", + elite ? "elite" : "standard", index++, key[0], key[1], @@ -230,6 +235,8 @@ static ReturnCode picopass_auth_dict( memcpy(pacs->key, key, PICOPASS_BLOCK_LEN); break; } + + if(picopass_worker->state != PicopassWorkerStateDetect) break; } iclass_elite_dict_free(dict); @@ -237,38 +244,23 @@ static ReturnCode picopass_auth_dict( return err; } -ReturnCode picopass_auth(PicopassBlock* AA1, PicopassPacs* pacs) { +ReturnCode picopass_auth(PicopassWorker* picopass_worker) { ReturnCode err; FURI_LOG_I(TAG, "Starting system dictionary attack [Standard KDF]"); - err = picopass_auth_dict( - AA1[PICOPASS_CSN_BLOCK_INDEX].data, - pacs, - AA1[PICOPASS_KD_BLOCK_INDEX].data, - IclassEliteDictTypeFlipper, - false); + err = picopass_auth_dict(picopass_worker, IclassStandardDictTypeFlipper); if(err == ERR_NONE) { return ERR_NONE; } FURI_LOG_I(TAG, "Starting user dictionary attack [Elite KDF]"); - err = picopass_auth_dict( - AA1[PICOPASS_CSN_BLOCK_INDEX].data, - pacs, - AA1[PICOPASS_KD_BLOCK_INDEX].data, - IclassEliteDictTypeUser, - true); + err = picopass_auth_dict(picopass_worker, IclassEliteDictTypeUser); if(err == ERR_NONE) { return ERR_NONE; } FURI_LOG_I(TAG, "Starting system dictionary attack [Elite KDF]"); - err = picopass_auth_dict( - AA1[PICOPASS_CSN_BLOCK_INDEX].data, - pacs, - AA1[PICOPASS_KD_BLOCK_INDEX].data, - IclassEliteDictTypeFlipper, - true); + err = picopass_auth_dict(picopass_worker, IclassEliteDictTypeFlipper); if(err == ERR_NONE) { return ERR_NONE; } @@ -520,7 +512,7 @@ void picopass_worker_detect(PicopassWorker* picopass_worker) { } if(nextState == PicopassWorkerEventSuccess) { - err = picopass_auth(AA1, pacs); + err = picopass_auth(picopass_worker); if(err != ERR_NONE) { FURI_LOG_E(TAG, "picopass_try_auth error %d", err); nextState = PicopassWorkerEventFail; diff --git a/assets/resources/apps_data/picopass/assets/iclass_elite_dict.txt b/assets/resources/apps_data/picopass/assets/iclass_elite_dict.txt index 5da2a2fa80d..d1189237245 100644 --- a/assets/resources/apps_data/picopass/assets/iclass_elite_dict.txt +++ b/assets/resources/apps_data/picopass/assets/iclass_elite_dict.txt @@ -1,16 +1,10 @@ ## From https://github.com/RfidResearchGroup/proxmark3/blob/master/client/dictionaries/iclass_default_keys.dic -# AA1 -AEA684A6DAB23278 # key1/Kc from PicoPass 2k documentation 7665544332211000 # SAGEM 0123456789ABCDEF -# from loclass demo file. -5b7c62c491c11b39 -# Kd from PicoPass 2k documentation -F0E1D2C3B4A59687 # PicoPass Default Exchange Key 5CBCF1DA45D5FB4F # From HID multiclassSE reader @@ -19,12 +13,6 @@ F0E1D2C3B4A59687 6EFD46EFCBB3C875 E033CA419AEE43F9 -# iCopy-x DRM keys -# iCL tags -2020666666668888 -# iCS tags reversed from the SOs -6666202066668888 - # default picopass KD / Page 0 / Book 1 FDCB5A52EA8F3090 237FF9079863DF44 diff --git a/assets/resources/apps_data/picopass/assets/iclass_standard_dict.txt b/assets/resources/apps_data/picopass/assets/iclass_standard_dict.txt new file mode 100644 index 00000000000..46808ef602e --- /dev/null +++ b/assets/resources/apps_data/picopass/assets/iclass_standard_dict.txt @@ -0,0 +1,47 @@ + +## From https://github.com/RfidResearchGroup/proxmark3/blob/master/client/dictionaries/iclass_default_keys.dic + +# AA1 +AEA684A6DAB23278 +# key1/Kc from PicoPass 2k documentation +7665544332211000 +# SAGEM +0123456789ABCDEF +# from loclass demo file. +5b7c62c491c11b39 +# Kd from PicoPass 2k documentation +F0E1D2C3B4A59687 +# PicoPass Default Exchange Key +5CBCF1DA45D5FB4F +# From HID multiclassSE reader +31ad7ebd2f282168 +# From pastebin: https://pastebin.com/uHqpjiuU +6EFD46EFCBB3C875 +E033CA419AEE43F9 + +# iCopy-x DRM keys +# iCL tags +2020666666668888 +# iCS tags reversed from the SOs +6666202066668888 + +# default picopass KD / Page 0 / Book 1 +FDCB5A52EA8F3090 +237FF9079863DF44 +5ADC25FB27181D32 +83B881F2936B2E49 +43644E61EE866BA5 +897034143D016080 +82D17B44C0122963 +4895CA7DE65E2025 +DADAD4C57BE271B7 +E41E9EDEF5719ABF +293D275EC3AF9C7F +C3C169251B8A70FB +F41DAF58B20C8B91 +28877A609EC0DD2B +66584C91EE80D5E5 +C1B74D7478053AE2 + +# default iCLASS RFIDeas +6B65797374726B72 From a69ae93871a4bf8cd9a1b5bb2cba06218199c6aa Mon Sep 17 00:00:00 2001 From: Leopold Date: Wed, 15 Mar 2023 21:52:32 +0800 Subject: [PATCH 466/824] Add new nfc apdu cli command (#2482) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- applications/main/nfc/nfc_cli.c | 68 +++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/applications/main/nfc/nfc_cli.c b/applications/main/nfc/nfc_cli.c index 23335e2993a..6e6e04ca928 100644 --- a/applications/main/nfc/nfc_cli.c +++ b/applications/main/nfc/nfc_cli.c @@ -2,6 +2,7 @@ #include #include #include +#include #include #include @@ -12,6 +13,7 @@ static void nfc_cli_print_usage() { printf("Cmd list:\r\n"); printf("\tdetect\t - detect nfc device\r\n"); printf("\temulate\t - emulate predefined nfca card\r\n"); + printf("\tapdu\t - Send APDU and print response \r\n"); if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { printf("\tfield\t - turn field on\r\n"); } @@ -98,6 +100,67 @@ static void nfc_cli_field(Cli* cli, FuriString* args) { furi_hal_nfc_sleep(); } +static void nfc_cli_apdu(Cli* cli, FuriString* args) { + UNUSED(cli); + if(furi_hal_nfc_is_busy()) { + printf("Nfc is busy\r\n"); + return; + } + + furi_hal_nfc_exit_sleep(); + FuriString* data = NULL; + data = furi_string_alloc(); + FuriHalNfcTxRxContext tx_rx = {}; + FuriHalNfcDevData dev_data = {}; + uint8_t* req_buffer = NULL; + uint8_t* resp_buffer = NULL; + size_t apdu_size = 0; + size_t resp_size = 0; + + do { + if(!args_read_string_and_trim(args, data)) { + printf( + "Use like `nfc apdu 00a404000e325041592e5359532e444446303100 00a4040008a0000003010102` \r\n"); + break; + } + + printf("detecting tag\r\n"); + if(!furi_hal_nfc_detect(&dev_data, 300)) { + printf("Failed to detect tag\r\n"); + break; + } + do { + apdu_size = furi_string_size(data) / 2; + req_buffer = malloc(apdu_size); + hex_chars_to_uint8(furi_string_get_cstr(data), req_buffer); + + memcpy(tx_rx.tx_data, req_buffer, apdu_size); + tx_rx.tx_bits = apdu_size * 8; + tx_rx.tx_rx_type = FuriHalNfcTxRxTypeDefault; + + printf("Sending APDU:%s to Tag\r\n", furi_string_get_cstr(data)); + if(!furi_hal_nfc_tx_rx(&tx_rx, 300)) { + printf("Failed to tx_rx\r\n"); + break; + } + resp_size = (tx_rx.rx_bits / 8) * 2; + resp_buffer = malloc(resp_size); + uint8_to_hex_chars(tx_rx.rx_data, resp_buffer, resp_size); + resp_buffer[resp_size] = 0; + printf("Response: %s\r\n", resp_buffer); + free(req_buffer); + free(resp_buffer); + req_buffer = NULL; + resp_buffer = NULL; + } while(args_read_string_and_trim(args, data)); + } while(false); + + free(req_buffer); + free(resp_buffer); + furi_string_free(data); + furi_hal_nfc_sleep(); +} + static void nfc_cli(Cli* cli, FuriString* args, void* context) { UNUSED(context); FuriString* cmd; @@ -117,6 +180,11 @@ static void nfc_cli(Cli* cli, FuriString* args, void* context) { break; } + if(furi_string_cmp_str(cmd, "apdu") == 0) { + nfc_cli_apdu(cli, args); + break; + } + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { if(furi_string_cmp_str(cmd, "field") == 0) { nfc_cli_field(cli, args); From c27d4d78f98869ba8ae3fdb01b322c52efe39d5e Mon Sep 17 00:00:00 2001 From: Liam Hays Date: Wed, 15 Mar 2023 08:51:15 -0600 Subject: [PATCH 467/824] Fix auto-capitalization in the keyboard when the text box is empty. (#2483) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: hedger Co-authored-by: あく --- applications/services/gui/modules/text_input.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/applications/services/gui/modules/text_input.c b/applications/services/gui/modules/text_input.c index 32607e88423..86b7bca1e0f 100644 --- a/applications/services/gui/modules/text_input.c +++ b/applications/services/gui/modules/text_input.c @@ -309,9 +309,9 @@ static void text_input_handle_ok(TextInput* text_input, TextInputModel* model, b char selected = get_selected_char(model); size_t text_length = strlen(model->text_buffer); - bool toogle_case = text_length == 0; - if(shift) toogle_case = !toogle_case; - if(toogle_case) { + bool toggle_case = text_length == 0 || model->clear_default_text; + if(shift) toggle_case = !toggle_case; + if(toggle_case) { selected = char_to_uppercase(selected); } From d8385b7f917ba10bd2ba49e5c076374de8b4c2ac Mon Sep 17 00:00:00 2001 From: hedger Date: Wed, 15 Mar 2023 19:24:56 +0400 Subject: [PATCH 468/824] gh: use shallow clones whenever possible (#2491) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * gh: use shallow clones whenever possible * gh: reverted submodule checks * gh: lint: joined linting scripts * gh: renamed linter workflow * check python linter output * gh: reworked linter * checking c linter * gh: merged submodule check & lint * gh: renamed step * gh: removed redundant `submodules: false` Co-authored-by: あく --- .github/workflows/build.yml | 6 +-- ...dules.yml => lint_and_submodule_check.yml} | 34 +++++++++++--- .github/workflows/lint_c.yml | 47 ------------------- .github/workflows/lint_python.yml | 33 ------------- .github/workflows/merge_report.yml | 2 +- .github/workflows/pvs_studio.yml | 2 +- .github/workflows/unit_tests.yml | 2 +- .github/workflows/updater_test.yml | 5 +- fbt | 4 +- fbt.cmd | 4 +- 10 files changed, 41 insertions(+), 98 deletions(-) rename .github/workflows/{check_submodules.yml => lint_and_submodule_check.yml} (56%) delete mode 100644 .github/workflows/lint_c.yml delete mode 100644 .github/workflows/lint_python.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 46d95ede545..79535c93444 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -27,7 +27,7 @@ jobs: - name: 'Checkout code' uses: actions/checkout@v3 with: - fetch-depth: 0 + fetch-depth: 1 ref: ${{ github.event.pull_request.head.sha }} - name: 'Get commit details' @@ -177,8 +177,8 @@ jobs: - name: 'Checkout code' uses: actions/checkout@v3 with: - fetch-depth: 0 - submodules: true + fetch-depth: 1 + submodules: false ref: ${{ github.event.pull_request.head.sha }} - name: 'Get commit details' diff --git a/.github/workflows/check_submodules.yml b/.github/workflows/lint_and_submodule_check.yml similarity index 56% rename from .github/workflows/check_submodules.yml rename to .github/workflows/lint_and_submodule_check.yml index 2eb2027c925..ede35793260 100644 --- a/.github/workflows/check_submodules.yml +++ b/.github/workflows/lint_and_submodule_check.yml @@ -1,4 +1,4 @@ -name: 'Check submodules branch' +name: 'Lint sources & check submodule integrity' on: push: @@ -9,9 +9,14 @@ on: - '*' pull_request: +env: + TARGETS: f7 + FBT_TOOLCHAIN_PATH: /runner/_work + SET_GH_OUTPUT: 1 + jobs: - check_protobuf: - runs-on: [self-hosted, FlipperZeroShell] + lint_sources_check_submodules: + runs-on: [self-hosted,FlipperZeroShell] steps: - name: 'Decontaminate previous build leftovers' run: | @@ -22,9 +27,10 @@ jobs: - name: 'Checkout code' uses: actions/checkout@v3 with: - fetch-depth: 0 + fetch-depth: 1 ref: ${{ github.event.pull_request.head.sha }} + - name: 'Check protobuf branch' run: | git submodule update --init @@ -36,12 +42,28 @@ jobs: BRANCHES=$(git branch -r --contains "$SUBMODULE_HASH"); COMMITS_IN_BRANCH="$(git rev-list --count dev)"; if [ $COMMITS_IN_BRANCH -lt $SUB_COMMITS_MIN ]; then - echo "name=fails::error" >> $GITHUB_OUTPUT + echo "name=fails::error" >> $GITHUB_OUTPUT; echo "::error::Error: Too low commits in $SUB_BRANCH of submodule $SUB_PATH: $COMMITS_IN_BRANCH(expected $SUB_COMMITS_MIN+)"; exit 1; fi if ! grep -q "/$SUB_BRANCH" <<< "$BRANCHES"; then - echo "name=fails::error" >> $GITHUB_OUTPUT + echo "name=fails::error" >> $GITHUB_OUTPUT; echo "::error::Error: Submodule $SUB_PATH is not on branch $SUB_BRANCH"; exit 1; fi + + - name: 'Check Python code formatting' + id: syntax_check_py + run: ./fbt lint_py 2>&1 >/dev/null || echo "errors=1" >> $GITHUB_OUTPUT + + - name: 'Check C++ code formatting' + if: always() + id: syntax_check_cpp + run: ./fbt lint 2>&1 >/dev/null || echo "errors=1" >> $GITHUB_OUTPUT + + - name: Report code formatting errors + if: ( steps.syntax_check_py.outputs.errors || steps.syntax_check_cpp.outputs.errors ) && github.event.pull_request + run: | + echo "Code formatting errors found"; + echo "Please run './fbt format' or './fbt format_py' to fix them"; + exit 1; diff --git a/.github/workflows/lint_c.yml b/.github/workflows/lint_c.yml deleted file mode 100644 index a6fd5127cda..00000000000 --- a/.github/workflows/lint_c.yml +++ /dev/null @@ -1,47 +0,0 @@ -name: 'Lint C/C++ with clang-format' - -on: - push: - branches: - - dev - - "release*" - tags: - - '*' - pull_request: - -env: - TARGETS: f7 - FBT_TOOLCHAIN_PATH: /runner/_work - SET_GH_OUTPUT: 1 - -jobs: - lint_c_cpp: - runs-on: [self-hosted,FlipperZeroShell] - steps: - - name: 'Decontaminate previous build leftovers' - run: | - if [ -d .git ]; then - git submodule status || git checkout "$(git rev-list --max-parents=0 HEAD | tail -n 1)" - fi - - - name: 'Checkout code' - uses: actions/checkout@v3 - with: - fetch-depth: 0 - ref: ${{ github.event.pull_request.head.sha }} - - - name: 'Check code formatting' - id: syntax_check - run: ./fbt lint - - - name: Report code formatting errors - if: failure() && steps.syntax_check.outputs.errors && github.event.pull_request - uses: peter-evans/create-or-update-comment@v1 - with: - issue-number: ${{ github.event.pull_request.number }} - body: | - Please fix following code formatting errors: - ``` - ${{ steps.syntax_check.outputs.errors }} - ``` - You might want to run `./fbt format` for an auto-fix. diff --git a/.github/workflows/lint_python.yml b/.github/workflows/lint_python.yml deleted file mode 100644 index 66c36064c64..00000000000 --- a/.github/workflows/lint_python.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: 'Python Lint' - -on: - push: - branches: - - dev - - "release*" - tags: - - '*' - pull_request: - -env: - FBT_TOOLCHAIN_PATH: /runner/_work - SET_GH_OUTPUT: 1 - -jobs: - lint_python: - runs-on: [self-hosted,FlipperZeroShell] - steps: - - name: 'Decontaminate previous build leftovers' - run: | - if [ -d .git ]; then - git submodule status || git checkout "$(git rev-list --max-parents=0 HEAD | tail -n 1)" - fi - - - name: 'Checkout code' - uses: actions/checkout@v3 - with: - fetch-depth: 0 - ref: ${{ github.event.pull_request.head.sha }} - - - name: 'Check code formatting' - run: ./fbt lint_py diff --git a/.github/workflows/merge_report.yml b/.github/workflows/merge_report.yml index 13fab0948a7..3b7cd234993 100644 --- a/.github/workflows/merge_report.yml +++ b/.github/workflows/merge_report.yml @@ -21,7 +21,7 @@ jobs: - name: 'Checkout code' uses: actions/checkout@v3 with: - fetch-depth: 0 + fetch-depth: 1 ref: ${{ github.event.pull_request.head.sha }} - name: 'Get commit details' diff --git a/.github/workflows/pvs_studio.yml b/.github/workflows/pvs_studio.yml index 65a8b6150e0..9105a0fd635 100644 --- a/.github/workflows/pvs_studio.yml +++ b/.github/workflows/pvs_studio.yml @@ -28,7 +28,7 @@ jobs: - name: 'Checkout code' uses: actions/checkout@v3 with: - fetch-depth: 0 + fetch-depth: 1 ref: ${{ github.event.pull_request.head.sha }} - name: 'Get commit details' diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 527e9a71ed1..bed5a470d5c 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -21,7 +21,7 @@ jobs: - name: Checkout code uses: actions/checkout@v3 with: - fetch-depth: 0 + fetch-depth: 1 ref: ${{ github.event.pull_request.head.sha }} - name: 'Get flipper from device manager (mock)' diff --git a/.github/workflows/updater_test.yml b/.github/workflows/updater_test.yml index 300440aae51..eba34e988bb 100644 --- a/.github/workflows/updater_test.yml +++ b/.github/workflows/updater_test.yml @@ -21,7 +21,8 @@ jobs: - name: Checkout code uses: actions/checkout@v3 with: - fetch-depth: 0 + fetch-depth: 1 + submodules: false ref: ${{ github.event.pull_request.head.sha }} - name: 'Get flipper from device manager (mock)' @@ -62,7 +63,7 @@ jobs: uses: actions/checkout@v3 if: failure() with: - fetch-depth: 0 + fetch-depth: 1 ref: ${{ steps.release_tag.outputs.tag }} - name: 'Flash last release' diff --git a/fbt b/fbt index f80e802b627..efe625f0340 100755 --- a/fbt +++ b/fbt @@ -25,10 +25,10 @@ fi if [ -z "$FBT_NO_SYNC" ]; then if [ ! -d "$SCRIPT_PATH/.git" ]; then - echo "\".git\" directory not found, please clone repo via \"git clone --recursive\""; + echo "\".git\" directory not found, please clone repo via \"git clone\""; exit 1; fi - git submodule update --init; + git submodule update --init --depth 1; fi $SCONS_EP $SCONS_DEFAULT_FLAGS "$@" diff --git a/fbt.cmd b/fbt.cmd index 92c734860a0..6e839c778eb 100644 --- a/fbt.cmd +++ b/fbt.cmd @@ -5,9 +5,9 @@ set SCONS_EP=python -m SCons if [%FBT_NO_SYNC%] == [] ( if exist ".git" ( - git submodule update --init + git submodule update --init --depth 1 ) else ( - echo Not in a git repo, please clone with git clone --recursive + echo Not in a git repo, please clone with "git clone" exit /b 1 ) ) From 9fbf3270286beff340d8cfc67a219606237ad7d6 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Thu, 16 Mar 2023 10:28:50 +0200 Subject: [PATCH 469/824] [FL-1799] Require the trailing slash for root paths (#2486) * Require the trailing slash * Fix the swapped storages * Fix root paths --- .../services/storage/storage_processing.c | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/applications/services/storage/storage_processing.c b/applications/services/storage/storage_processing.c index 2a335e36655..59527e76969 100644 --- a/applications/services/storage/storage_processing.c +++ b/applications/services/storage/storage_processing.c @@ -33,12 +33,22 @@ static StorageType storage_get_type_by_path(FuriString* path) { StorageType type = ST_ERROR; const char* path_cstr = furi_string_get_cstr(path); - if(memcmp(path_cstr, STORAGE_EXT_PATH_PREFIX, strlen(STORAGE_EXT_PATH_PREFIX)) == 0) { - type = ST_EXT; - } else if(memcmp(path_cstr, STORAGE_INT_PATH_PREFIX, strlen(STORAGE_INT_PATH_PREFIX)) == 0) { - type = ST_INT; - } else if(memcmp(path_cstr, STORAGE_ANY_PATH_PREFIX, strlen(STORAGE_ANY_PATH_PREFIX)) == 0) { - type = ST_ANY; + if(furi_string_size(path) == 4) { + if(memcmp(path_cstr, STORAGE_EXT_PATH_PREFIX, strlen(STORAGE_EXT_PATH_PREFIX)) == 0) { + type = ST_EXT; + } else if(memcmp(path_cstr, STORAGE_INT_PATH_PREFIX, strlen(STORAGE_INT_PATH_PREFIX)) == 0) { + type = ST_INT; + } else if(memcmp(path_cstr, STORAGE_ANY_PATH_PREFIX, strlen(STORAGE_ANY_PATH_PREFIX)) == 0) { + type = ST_ANY; + } + } else if(furi_string_size(path) > 4) { + if(memcmp(path_cstr, EXT_PATH(""), strlen(EXT_PATH(""))) == 0) { + type = ST_EXT; + } else if(memcmp(path_cstr, INT_PATH(""), strlen(INT_PATH(""))) == 0) { + type = ST_INT; + } else if(memcmp(path_cstr, ANY_PATH(""), strlen(ANY_PATH(""))) == 0) { + type = ST_ANY; + } } return type; From e90042368f941f2c167563444fdb5cf22a5da9ad Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Thu, 16 Mar 2023 10:58:07 +0200 Subject: [PATCH 470/824] [FL-3156] Mark keys as not found when they couldn't auth successfully (#2476) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Mark keys as not found when they couldn't auth successfully * Improve logging and fix the reading bug Co-authored-by: あく --- lib/nfc/nfc_worker.c | 13 +++++++++++-- lib/nfc/protocols/mifare_classic.c | 26 ++++++++++++++++++++++---- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/lib/nfc/nfc_worker.c b/lib/nfc/nfc_worker.c index 062a3953436..4561ff2af6e 100644 --- a/lib/nfc/nfc_worker.c +++ b/lib/nfc/nfc_worker.c @@ -739,7 +739,7 @@ void nfc_worker_mf_classic_dict_attack(NfcWorker* nfc_worker) { if(mf_classic_authenticate_skip_activate( &tx_rx, block_num, key, MfClassicKeyA, !deactivated, cuid)) { mf_classic_set_key_found(data, i, MfClassicKeyA, key); - FURI_LOG_D(TAG, "Key found"); + FURI_LOG_D(TAG, "Key A found"); nfc_worker->callback(NfcWorkerEventFoundKeyA, nfc_worker->context); uint64_t found_key; @@ -753,22 +753,31 @@ void nfc_worker_mf_classic_dict_attack(NfcWorker* nfc_worker) { } nfc_worker_mf_classic_key_attack(nfc_worker, found_key, &tx_rx, i + 1); + break; } nfc_worker_mf_classic_key_attack(nfc_worker, key, &tx_rx, i + 1); } furi_hal_nfc_sleep(); deactivated = true; + } else { + mf_classic_set_key_not_found(data, i, MfClassicKeyA); + is_key_a_found = false; + FURI_LOG_D(TAG, "Key %dA not found in attack", i); } if(!is_key_b_found) { is_key_b_found = mf_classic_is_key_found(data, i, MfClassicKeyB); if(mf_classic_authenticate_skip_activate( &tx_rx, block_num, key, MfClassicKeyB, !deactivated, cuid)) { - FURI_LOG_D(TAG, "Key found"); + FURI_LOG_D(TAG, "Key B found"); mf_classic_set_key_found(data, i, MfClassicKeyB, key); nfc_worker->callback(NfcWorkerEventFoundKeyB, nfc_worker->context); nfc_worker_mf_classic_key_attack(nfc_worker, key, &tx_rx, i + 1); } deactivated = true; + } else { + mf_classic_set_key_not_found(data, i, MfClassicKeyB); + is_key_b_found = false; + FURI_LOG_D(TAG, "Key %dB not found in attack", i); } if(is_key_a_found && is_key_b_found) break; if(nfc_worker->state != NfcWorkerStateMfClassicDictAttack) break; diff --git a/lib/nfc/protocols/mifare_classic.c b/lib/nfc/protocols/mifare_classic.c index 71242871745..d2d7467dce0 100644 --- a/lib/nfc/protocols/mifare_classic.c +++ b/lib/nfc/protocols/mifare_classic.c @@ -651,7 +651,12 @@ void mf_classic_read_sector(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data, u if(!key_a_found) break; FURI_LOG_D(TAG, "Try to read blocks with key A"); key = nfc_util_bytes2num(sec_tr->key_a, sizeof(sec_tr->key_a)); - if(!mf_classic_auth(tx_rx, start_block, key, MfClassicKeyA, &crypto, false, 0)) break; + if(!mf_classic_auth(tx_rx, start_block, key, MfClassicKeyA, &crypto, false, 0)) { + mf_classic_set_key_not_found(data, sec_num, MfClassicKeyA); + FURI_LOG_D(TAG, "Key %dA not found in read", sec_num); + break; + } + for(size_t i = start_block; i < start_block + total_blocks; i++) { if(!mf_classic_is_block_read(data, i)) { if(mf_classic_read_block(tx_rx, &crypto, i, &block_tmp)) { @@ -660,7 +665,11 @@ void mf_classic_read_sector(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data, u } else if(i > start_block) { // Try to re-auth to read block in case prevous block was protected from read furi_hal_nfc_sleep(); - if(!mf_classic_auth(tx_rx, i, key, MfClassicKeyA, &crypto, false, 0)) break; + if(!mf_classic_auth(tx_rx, i, key, MfClassicKeyA, &crypto, false, 0)) { + mf_classic_set_key_not_found(data, sec_num, MfClassicKeyA); + FURI_LOG_D(TAG, "Key %dA not found in read", sec_num); + break; + } if(mf_classic_read_block(tx_rx, &crypto, i, &block_tmp)) { mf_classic_set_block_read(data, i, &block_tmp); blocks_read++; @@ -680,7 +689,12 @@ void mf_classic_read_sector(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data, u } FURI_LOG_D(TAG, "Try to read blocks with key B"); key = nfc_util_bytes2num(sec_tr->key_b, sizeof(sec_tr->key_b)); - if(!mf_classic_auth(tx_rx, start_block, key, MfClassicKeyB, &crypto, false, 0)) break; + if(!mf_classic_auth(tx_rx, start_block, key, MfClassicKeyB, &crypto, false, 0)) { + mf_classic_set_key_not_found(data, sec_num, MfClassicKeyB); + FURI_LOG_D(TAG, "Key %dB not found in read", sec_num); + break; + } + for(size_t i = start_block; i < start_block + total_blocks; i++) { if(!mf_classic_is_block_read(data, i)) { if(mf_classic_read_block(tx_rx, &crypto, i, &block_tmp)) { @@ -689,7 +703,11 @@ void mf_classic_read_sector(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data, u } else if(i > start_block) { // Try to re-auth to read block in case prevous block was protected from read furi_hal_nfc_sleep(); - if(!mf_classic_auth(tx_rx, i, key, MfClassicKeyB, &crypto, false, 0)) break; + if(!mf_classic_auth(tx_rx, i, key, MfClassicKeyB, &crypto, false, 0)) { + mf_classic_set_key_not_found(data, sec_num, MfClassicKeyB); + FURI_LOG_D(TAG, "Key %dB not found in read", sec_num); + break; + } if(mf_classic_read_block(tx_rx, &crypto, i, &block_tmp)) { mf_classic_set_block_read(data, i, &block_tmp); blocks_read++; From 6aa0c08f3a47ea9750f2d28c4386fec61c55efd8 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Thu, 16 Mar 2023 11:06:11 +0200 Subject: [PATCH 471/824] [FL-3064] Skip the read when the card is not present (#2494) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- .../main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c | 3 ++- applications/main/nfc/views/dict_attack.c | 8 ++++++++ applications/main/nfc/views/dict_attack.h | 2 ++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c index b82bf5521f5..cb2f3a82d9e 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c @@ -115,7 +115,8 @@ bool nfc_scene_mf_classic_dict_attack_on_event(void* context, SceneManagerEvent consumed = true; } } else if(event.event == NfcWorkerEventAborted) { - if(state == DictAttackStateUserDictInProgress) { + if(state == DictAttackStateUserDictInProgress && + dict_attack_get_card_state(nfc->dict_attack)) { nfc_scene_mf_classic_dict_attack_prepare_view(nfc, state); consumed = true; } else { diff --git a/applications/main/nfc/views/dict_attack.c b/applications/main/nfc/views/dict_attack.c index a539e514bed..8f4bd063e8b 100644 --- a/applications/main/nfc/views/dict_attack.c +++ b/applications/main/nfc/views/dict_attack.c @@ -11,6 +11,7 @@ struct DictAttack { View* view; DictAttackCallback callback; void* context; + bool card_present; }; typedef struct { @@ -162,6 +163,7 @@ void dict_attack_set_header(DictAttack* dict_attack, const char* header) { void dict_attack_set_card_detected(DictAttack* dict_attack, MfClassicType type) { furi_assert(dict_attack); + dict_attack->card_present = true; with_view_model( dict_attack->view, DictAttackViewModel * model, @@ -175,6 +177,7 @@ void dict_attack_set_card_detected(DictAttack* dict_attack, MfClassicType type) void dict_attack_set_card_removed(DictAttack* dict_attack) { furi_assert(dict_attack); + dict_attack->card_present = false; with_view_model( dict_attack->view, DictAttackViewModel * model, @@ -182,6 +185,11 @@ void dict_attack_set_card_removed(DictAttack* dict_attack) { true); } +bool dict_attack_get_card_state(DictAttack* dict_attack) { + furi_assert(dict_attack); + return dict_attack->card_present; +} + void dict_attack_set_sector_read(DictAttack* dict_attack, uint8_t sec_read) { furi_assert(dict_attack); with_view_model( diff --git a/applications/main/nfc/views/dict_attack.h b/applications/main/nfc/views/dict_attack.h index 2839534a76e..73b98a1b827 100644 --- a/applications/main/nfc/views/dict_attack.h +++ b/applications/main/nfc/views/dict_attack.h @@ -25,6 +25,8 @@ void dict_attack_set_card_detected(DictAttack* dict_attack, MfClassicType type); void dict_attack_set_card_removed(DictAttack* dict_attack); +bool dict_attack_get_card_state(DictAttack* dict_attack); + void dict_attack_set_sector_read(DictAttack* dict_attack, uint8_t sec_read); void dict_attack_set_keys_found(DictAttack* dict_attack, uint8_t keys_found); From 771c47f809eb1e25c2bb3f55ff40780c3709e388 Mon Sep 17 00:00:00 2001 From: hedger Date: Thu, 16 Mar 2023 17:46:18 +0400 Subject: [PATCH 472/824] fbt: explicitly set dist suffix length, not depending on environment settings. See (#2497) --- scripts/version.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scripts/version.py b/scripts/version.py index 896b58a467e..880a9728130 100644 --- a/scripts/version.py +++ b/scripts/version.py @@ -9,11 +9,16 @@ class GitVersion: + REVISION_SUFFIX_LENGTH = 8 + def __init__(self, source_dir): self.source_dir = source_dir def get_version_info(self): - commit = self._exec_git("rev-parse --short HEAD") or "unknown" + commit = ( + self._exec_git(f"rev-parse --short={self.REVISION_SUFFIX_LENGTH} HEAD") + or "unknown" + ) dirty = False try: From 25fd3c3400ecaa0e3b66771a895844de8ddd52f9 Mon Sep 17 00:00:00 2001 From: Guido Giorgi Date: Thu, 16 Mar 2023 15:12:43 +0100 Subject: [PATCH 473/824] iButton: Add support for Dallas DS1971 v2 (#2492) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * iButton: Add DS1971 Support refactored for v2 * Fix requested by gsurkov * Fix DALLAS_COMMON_CMD_* use, cusotm data field to Eeprom and COPY_SCRAPTCHPAD * Fix Emulation + Memory Info + Docs * Fix docs, strings, refactor code Co-authored-by: Georgii Surkov Co-authored-by: あく --- .../file_formats/iButtonFileFormat.md | 13 +- .../ibutton/protocols/dallas/dallas_common.c | 27 +- .../ibutton/protocols/dallas/dallas_common.h | 5 +- .../protocols/dallas/protocol_ds1971.c | 270 ++++++++++++++++++ .../protocols/dallas/protocol_ds1971.h | 5 + .../protocols/dallas/protocol_ds1992.c | 3 +- .../protocols/dallas/protocol_ds1996.c | 3 +- .../dallas/protocol_group_dallas_defs.c | 2 + .../dallas/protocol_group_dallas_defs.h | 1 + 9 files changed, 311 insertions(+), 18 deletions(-) create mode 100644 lib/one_wire/ibutton/protocols/dallas/protocol_ds1971.c create mode 100644 lib/one_wire/ibutton/protocols/dallas/protocol_ds1971.h diff --git a/documentation/file_formats/iButtonFileFormat.md b/documentation/file_formats/iButtonFileFormat.md index adb493e0552..d31c297a267 100644 --- a/documentation/file_formats/iButtonFileFormat.md +++ b/documentation/file_formats/iButtonFileFormat.md @@ -24,12 +24,13 @@ Changelog: #### Format fields -| Name | Type | Description | -| --------- | ------ | -------------------------------------------- | -| Protocol | string | Currently supported: DS1990, DS1992, DS1996, DSGeneric*, Cyfral, Metakom | -| Rom Data | hex | Read-only memory data (Dallas protocols only) | -| Sram Data | hex | Static RAM data (DS1992 and DS1996 only) -| Data | hex | Key data (Cyfral & Metakom only) | +| Name | Type | Description | +| ----------- | ------ | -------------------------------------------- | +| Protocol | string | Currently supported: DS1990, DS1992, DS1996, DS1997, DSGeneric*, Cyfral, Metakom | +| Rom Data | hex | Read-only memory data (Dallas protocols only) | +| Sram Data | hex | Static RAM data (DS1992 and DS1996 only) +| Eeprom Data | hex | EEPROM data (DS1971 only) +| Data | hex | Key data (Cyfral & Metakom only) | NOTE 1: DSGeneric is a catch-all protocol for all unknown 1-Wire devices. It reads only the ROM and does not perform any checks on the read data. It can also be used if a key with a deliberately invalid family code or checksum is required. diff --git a/lib/one_wire/ibutton/protocols/dallas/dallas_common.c b/lib/one_wire/ibutton/protocols/dallas/dallas_common.c index 57a873b1d16..22b25db8482 100644 --- a/lib/one_wire/ibutton/protocols/dallas/dallas_common.c +++ b/lib/one_wire/ibutton/protocols/dallas/dallas_common.c @@ -21,6 +21,7 @@ #define BITS_IN_BYTE 8U #define BITS_IN_KBIT 1024U +#define BITS_IN_MBIT (BITS_IN_KBIT * 1024U) bool dallas_common_skip_rom(OneWireHost* host) { onewire_host_write(host, DALLAS_COMMON_CMD_SKIP_ROM); @@ -210,25 +211,35 @@ bool dallas_common_is_valid_crc(const DallasCommonRomData* rom_data) { void dallas_common_render_brief_data( FuriString* result, const DallasCommonRomData* rom_data, - const uint8_t* sram_data, - size_t sram_data_size) { + const uint8_t* mem_data, + size_t mem_size, + const char* mem_name) { for(size_t i = 0; i < sizeof(rom_data->bytes); ++i) { furi_string_cat_printf(result, "%02X ", rom_data->bytes[i]); } + const char* size_prefix = ""; + size_t mem_size_bits = mem_size * BITS_IN_BYTE; + + if(mem_size_bits >= BITS_IN_MBIT) { + size_prefix = "M"; + mem_size_bits /= BITS_IN_MBIT; + } else if(mem_size_bits >= BITS_IN_KBIT) { + size_prefix = "K"; + mem_size_bits /= BITS_IN_KBIT; + } + furi_string_cat_printf( - result, - "\nInternal SRAM: %zu Kbit\n", - (size_t)(sram_data_size * BITS_IN_BYTE / BITS_IN_KBIT)); + result, "\nInternal %s: %zu %sbit\n", mem_name, mem_size_bits, size_prefix); for(size_t i = 0; i < DALLAS_COMMON_BRIEF_HEAD_COUNT; ++i) { - furi_string_cat_printf(result, "%02X ", sram_data[i]); + furi_string_cat_printf(result, "%02X ", mem_data[i]); } furi_string_cat_printf(result, "[ . . . ]"); - for(size_t i = sram_data_size - DALLAS_COMMON_BRIEF_TAIL_COUNT; i < sram_data_size; ++i) { - furi_string_cat_printf(result, " %02X", sram_data[i]); + for(size_t i = mem_size - DALLAS_COMMON_BRIEF_TAIL_COUNT; i < mem_size; ++i) { + furi_string_cat_printf(result, " %02X", mem_data[i]); } } diff --git a/lib/one_wire/ibutton/protocols/dallas/dallas_common.h b/lib/one_wire/ibutton/protocols/dallas/dallas_common.h index 7ad13eb2cb0..7991a1f8ba7 100644 --- a/lib/one_wire/ibutton/protocols/dallas/dallas_common.h +++ b/lib/one_wire/ibutton/protocols/dallas/dallas_common.h @@ -99,8 +99,9 @@ bool dallas_common_is_valid_crc(const DallasCommonRomData* rom_data); void dallas_common_render_brief_data( FuriString* result, const DallasCommonRomData* rom_data, - const uint8_t* sram_data, - size_t sram_data_size); + const uint8_t* mem_data, + size_t mem_size, + const char* mem_name); void dallas_common_render_crc_error(FuriString* result, const DallasCommonRomData* rom_data); diff --git a/lib/one_wire/ibutton/protocols/dallas/protocol_ds1971.c b/lib/one_wire/ibutton/protocols/dallas/protocol_ds1971.c new file mode 100644 index 00000000000..eb5b330b795 --- /dev/null +++ b/lib/one_wire/ibutton/protocols/dallas/protocol_ds1971.c @@ -0,0 +1,270 @@ +#include "protocol_ds1971.h" + +#include +#include + +#include "dallas_common.h" + +#define DS1971_FAMILY_CODE 0x14U +#define DS1971_FAMILY_NAME "DS1971" + +#define DS1971_EEPROM_DATA_SIZE 32U +#define DS1971_SRAM_PAGE_SIZE 32U +#define DS1971_COPY_SCRATCH_DELAY_US 250U + +#define DS1971_DATA_BYTE_COUNT 4U + +#define DS1971_EEPROM_DATA_KEY "Eeprom Data" +#define DS1971_MEMORY_TYPE "EEPROM" + +#define DS1971_CMD_FINALIZATION 0xA5 + +typedef struct { + OneWireSlave* bus; + DallasCommonCommandState command_state; +} DS1971ProtocolState; + +typedef struct { + DallasCommonRomData rom_data; + uint8_t eeprom_data[DS1971_EEPROM_DATA_SIZE]; + DS1971ProtocolState state; +} DS1971ProtocolData; + +static bool dallas_ds1971_read(OneWireHost*, void*); +static bool dallas_ds1971_write_copy(OneWireHost*, iButtonProtocolData*); +static void dallas_ds1971_emulate(OneWireSlave*, iButtonProtocolData*); +static bool dallas_ds1971_load(FlipperFormat*, uint32_t, iButtonProtocolData*); +static bool dallas_ds1971_save(FlipperFormat*, const iButtonProtocolData*); +static void dallas_ds1971_render_data(FuriString*, const iButtonProtocolData*); +static void dallas_ds1971_render_brief_data(FuriString*, const iButtonProtocolData*); +static void dallas_ds1971_render_error(FuriString*, const iButtonProtocolData*); +static bool dallas_ds1971_is_data_valid(const iButtonProtocolData*); +static void dallas_ds1971_get_editable_data(iButtonEditableData*, iButtonProtocolData*); +static void dallas_ds1971_apply_edits(iButtonProtocolData*); +static bool + dallas_ds1971_read_mem(OneWireHost* host, uint8_t address, uint8_t* data, size_t data_size); +static bool ds1971_emulate_read_mem(OneWireSlave* bus, const uint8_t* data, size_t data_size); + +const iButtonProtocolDallasBase ibutton_protocol_ds1971 = { + .family_code = DS1971_FAMILY_CODE, + .features = iButtonProtocolFeatureExtData | iButtonProtocolFeatureWriteCopy, + .data_size = sizeof(DS1971ProtocolData), + .manufacturer = DALLAS_COMMON_MANUFACTURER_NAME, + .name = DS1971_FAMILY_NAME, + + .read = dallas_ds1971_read, + .write_blank = NULL, /* No data to write a blank */ + .write_copy = dallas_ds1971_write_copy, + .emulate = dallas_ds1971_emulate, + .save = dallas_ds1971_save, + .load = dallas_ds1971_load, + .render_data = dallas_ds1971_render_data, + .render_brief_data = dallas_ds1971_render_brief_data, + .render_error = dallas_ds1971_render_error, + .is_valid = dallas_ds1971_is_data_valid, + .get_editable_data = dallas_ds1971_get_editable_data, + .apply_edits = dallas_ds1971_apply_edits, +}; + +bool dallas_ds1971_read(OneWireHost* host, iButtonProtocolData* protocol_data) { + DS1971ProtocolData* data = protocol_data; + return onewire_host_reset(host) && dallas_common_read_rom(host, &data->rom_data) && + dallas_ds1971_read_mem(host, 0, data->eeprom_data, DS1971_EEPROM_DATA_SIZE); +} + +bool dallas_ds1971_write_copy(OneWireHost* host, iButtonProtocolData* protocol_data) { + DS1971ProtocolData* data = protocol_data; + + onewire_host_reset(host); + onewire_host_skip(host); + // Starting writing from address 0x0000 + onewire_host_write(host, DALLAS_COMMON_CMD_WRITE_SCRATCH); + onewire_host_write(host, 0x00); + // Write data to scratchpad + onewire_host_write_bytes(host, data->eeprom_data, DS1971_EEPROM_DATA_SIZE); + + // Read data from scratchpad and verify + bool pad_valid = false; + if(onewire_host_reset(host)) { + pad_valid = true; + onewire_host_skip(host); + onewire_host_write(host, DALLAS_COMMON_CMD_READ_SCRATCH); + onewire_host_write(host, 0x00); + + for(size_t i = 0; i < DS1971_EEPROM_DATA_SIZE; ++i) { + uint8_t scratch = onewire_host_read(host); + if(data->eeprom_data[i] != scratch) { + pad_valid = false; + break; + } + } + } + + // Copy scratchpad to memory and confirm + if(pad_valid) { + if(onewire_host_reset(host)) { + onewire_host_skip(host); + onewire_host_write(host, DALLAS_COMMON_CMD_COPY_SCRATCH); + onewire_host_write(host, DS1971_CMD_FINALIZATION); + + furi_delay_us(DS1971_COPY_SCRATCH_DELAY_US); + } + } + + return pad_valid; +} + +static void dallas_ds1971_reset_callback(void* context) { + furi_assert(context); + DS1971ProtocolData* data = context; + data->state.command_state = DallasCommonCommandStateIdle; +} + +static bool dallas_ds1971_command_callback(uint8_t command, void* context) { + furi_assert(context); + DS1971ProtocolData* data = context; + OneWireSlave* bus = data->state.bus; + + switch(command) { + case DALLAS_COMMON_CMD_SEARCH_ROM: + if(data->state.command_state == DallasCommonCommandStateIdle) { + data->state.command_state = DallasCommonCommandStateRomCmd; + return dallas_common_emulate_search_rom(bus, &data->rom_data); + + } else if(data->state.command_state == DallasCommonCommandStateRomCmd) { + data->state.command_state = DallasCommonCommandStateMemCmd; + ds1971_emulate_read_mem(bus, data->eeprom_data, DS1971_EEPROM_DATA_SIZE); + return false; + + } else { + return false; + } + + case DALLAS_COMMON_CMD_READ_ROM: + if(data->state.command_state == DallasCommonCommandStateIdle) { + data->state.command_state = DallasCommonCommandStateRomCmd; + return dallas_common_emulate_read_rom(bus, &data->rom_data); + } else { + return false; + } + + case DALLAS_COMMON_CMD_SKIP_ROM: + if(data->state.command_state == DallasCommonCommandStateIdle) { + data->state.command_state = DallasCommonCommandStateRomCmd; + return true; + } else { + return false; + } + + default: + return false; + } +} + +void dallas_ds1971_emulate(OneWireSlave* bus, iButtonProtocolData* protocol_data) { + DS1971ProtocolData* data = protocol_data; + data->state.bus = bus; + + onewire_slave_set_reset_callback(bus, dallas_ds1971_reset_callback, protocol_data); + onewire_slave_set_command_callback(bus, dallas_ds1971_command_callback, protocol_data); +} + +bool dallas_ds1971_load( + FlipperFormat* ff, + uint32_t format_version, + iButtonProtocolData* protocol_data) { + DS1971ProtocolData* data = protocol_data; + bool success = false; + + do { + if(format_version < 2) break; + if(!dallas_common_load_rom_data(ff, format_version, &data->rom_data)) break; + if(!flipper_format_read_hex( + ff, DS1971_EEPROM_DATA_KEY, data->eeprom_data, DS1971_EEPROM_DATA_SIZE)) + break; + success = true; + } while(false); + + return success; +} + +bool dallas_ds1971_save(FlipperFormat* ff, const iButtonProtocolData* protocol_data) { + const DS1971ProtocolData* data = protocol_data; + bool success = false; + + do { + if(!dallas_common_save_rom_data(ff, &data->rom_data)) break; + if(!flipper_format_write_hex( + ff, DS1971_EEPROM_DATA_KEY, data->eeprom_data, DS1971_EEPROM_DATA_SIZE)) + break; + success = true; + } while(false); + + return success; +} + +void dallas_ds1971_render_data(FuriString* result, const iButtonProtocolData* protocol_data) { + const DS1971ProtocolData* data = protocol_data; + pretty_format_bytes_hex_canonical( + result, + DS1971_DATA_BYTE_COUNT, + PRETTY_FORMAT_FONT_MONOSPACE, + data->eeprom_data, + DS1971_EEPROM_DATA_SIZE); +} + +void dallas_ds1971_render_brief_data(FuriString* result, const iButtonProtocolData* protocol_data) { + const DS1971ProtocolData* data = protocol_data; + dallas_common_render_brief_data( + result, &data->rom_data, data->eeprom_data, DS1971_EEPROM_DATA_SIZE, DS1971_MEMORY_TYPE); +} + +void dallas_ds1971_render_error(FuriString* result, const iButtonProtocolData* protocol_data) { + const DS1971ProtocolData* data = protocol_data; + + if(!dallas_common_is_valid_crc(&data->rom_data)) { + dallas_common_render_crc_error(result, &data->rom_data); + } +} + +bool dallas_ds1971_is_data_valid(const iButtonProtocolData* protocol_data) { + const DS1971ProtocolData* data = protocol_data; + return dallas_common_is_valid_crc(&data->rom_data); +} + +void dallas_ds1971_get_editable_data( + iButtonEditableData* editable_data, + iButtonProtocolData* protocol_data) { + DS1971ProtocolData* data = protocol_data; + editable_data->ptr = data->rom_data.bytes; + editable_data->size = sizeof(DallasCommonRomData); +} + +void dallas_ds1971_apply_edits(iButtonProtocolData* protocol_data) { + DS1971ProtocolData* data = protocol_data; + dallas_common_apply_edits(&data->rom_data, DS1971_FAMILY_CODE); +} + +bool dallas_ds1971_read_mem(OneWireHost* host, uint8_t address, uint8_t* data, size_t data_size) { + onewire_host_write(host, DALLAS_COMMON_CMD_READ_MEM); + + onewire_host_write(host, address); + onewire_host_read_bytes(host, data, (uint8_t)data_size); + + return true; +} + +bool ds1971_emulate_read_mem(OneWireSlave* bus, const uint8_t* data, size_t data_size) { + bool success = false; + + do { + uint8_t address; + if(!onewire_slave_receive(bus, &address, sizeof(address))) break; + if(address >= data_size) break; + if(!onewire_slave_send(bus, data + address, data_size - address)) break; + + success = true; + } while(false); + + return success; +} diff --git a/lib/one_wire/ibutton/protocols/dallas/protocol_ds1971.h b/lib/one_wire/ibutton/protocols/dallas/protocol_ds1971.h new file mode 100644 index 00000000000..522b612dabf --- /dev/null +++ b/lib/one_wire/ibutton/protocols/dallas/protocol_ds1971.h @@ -0,0 +1,5 @@ +#pragma once + +#include "protocol_dallas_base.h" + +extern const iButtonProtocolDallasBase ibutton_protocol_ds1971; diff --git a/lib/one_wire/ibutton/protocols/dallas/protocol_ds1992.c b/lib/one_wire/ibutton/protocols/dallas/protocol_ds1992.c index 131bc634a00..17d631259e1 100644 --- a/lib/one_wire/ibutton/protocols/dallas/protocol_ds1992.c +++ b/lib/one_wire/ibutton/protocols/dallas/protocol_ds1992.c @@ -17,6 +17,7 @@ #define DS1992_DATA_BYTE_COUNT 4U #define DS1992_SRAM_DATA_KEY "Sram Data" +#define DS1992_MEMORY_TYPE "SRAM" typedef struct { OneWireSlave* bus; @@ -188,7 +189,7 @@ void dallas_ds1992_render_data(FuriString* result, const iButtonProtocolData* pr void dallas_ds1992_render_brief_data(FuriString* result, const iButtonProtocolData* protocol_data) { const DS1992ProtocolData* data = protocol_data; dallas_common_render_brief_data( - result, &data->rom_data, data->sram_data, DS1992_SRAM_DATA_SIZE); + result, &data->rom_data, data->sram_data, DS1992_SRAM_DATA_SIZE, DS1992_MEMORY_TYPE); } void dallas_ds1992_render_error(FuriString* result, const iButtonProtocolData* protocol_data) { diff --git a/lib/one_wire/ibutton/protocols/dallas/protocol_ds1996.c b/lib/one_wire/ibutton/protocols/dallas/protocol_ds1996.c index e69145c58d7..74a5792c66d 100644 --- a/lib/one_wire/ibutton/protocols/dallas/protocol_ds1996.c +++ b/lib/one_wire/ibutton/protocols/dallas/protocol_ds1996.c @@ -15,6 +15,7 @@ #define DS1996_DATA_BYTE_COUNT 4U #define DS1996_SRAM_DATA_KEY "Sram Data" +#define DS1996_MEMORY_TYPE "SRAM" typedef struct { OneWireSlave* bus; @@ -182,7 +183,7 @@ void dallas_ds1996_render_data(FuriString* result, const iButtonProtocolData* pr void dallas_ds1996_render_brief_data(FuriString* result, const iButtonProtocolData* protocol_data) { const DS1996ProtocolData* data = protocol_data; dallas_common_render_brief_data( - result, &data->rom_data, data->sram_data, DS1996_SRAM_DATA_SIZE); + result, &data->rom_data, data->sram_data, DS1996_SRAM_DATA_SIZE, DS1996_MEMORY_TYPE); } void dallas_ds1996_render_error(FuriString* result, const iButtonProtocolData* protocol_data) { diff --git a/lib/one_wire/ibutton/protocols/dallas/protocol_group_dallas_defs.c b/lib/one_wire/ibutton/protocols/dallas/protocol_group_dallas_defs.c index e54c3125d73..b4dd51ce717 100644 --- a/lib/one_wire/ibutton/protocols/dallas/protocol_group_dallas_defs.c +++ b/lib/one_wire/ibutton/protocols/dallas/protocol_group_dallas_defs.c @@ -3,12 +3,14 @@ #include "protocol_ds1990.h" #include "protocol_ds1992.h" #include "protocol_ds1996.h" +#include "protocol_ds1971.h" #include "protocol_ds_generic.h" const iButtonProtocolDallasBase* ibutton_protocols_dallas[] = { [iButtonProtocolDS1990] = &ibutton_protocol_ds1990, [iButtonProtocolDS1992] = &ibutton_protocol_ds1992, [iButtonProtocolDS1996] = &ibutton_protocol_ds1996, + [iButtonProtocolDS1971] = &ibutton_protocol_ds1971, /* Add new 1-Wire protocols here */ /* Default catch-all 1-Wire protocol */ diff --git a/lib/one_wire/ibutton/protocols/dallas/protocol_group_dallas_defs.h b/lib/one_wire/ibutton/protocols/dallas/protocol_group_dallas_defs.h index ba74c0c2361..2ba1dd39acb 100644 --- a/lib/one_wire/ibutton/protocols/dallas/protocol_group_dallas_defs.h +++ b/lib/one_wire/ibutton/protocols/dallas/protocol_group_dallas_defs.h @@ -6,6 +6,7 @@ typedef enum { iButtonProtocolDS1990, iButtonProtocolDS1992, iButtonProtocolDS1996, + iButtonProtocolDS1971, /* Add new 1-Wire protocols here */ /* Default catch-all 1-Wire protocol */ From 7de7fa293b6eb8def92ecfecc1c1aea63a269624 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Fri, 17 Mar 2023 16:45:42 +0200 Subject: [PATCH 474/824] Optimize trailing slash check (#2503) * Optimize trailing slash check --- .../services/storage/storage_processing.c | 40 +++++++++++-------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/applications/services/storage/storage_processing.c b/applications/services/storage/storage_processing.c index 59527e76969..a3076f2711f 100644 --- a/applications/services/storage/storage_processing.c +++ b/applications/services/storage/storage_processing.c @@ -2,6 +2,17 @@ #include #include +#define STORAGE_PATH_PREFIX_LEN 4u +_Static_assert( + sizeof(STORAGE_ANY_PATH_PREFIX) == STORAGE_PATH_PREFIX_LEN + 1, + "Any path prefix len mismatch"); +_Static_assert( + sizeof(STORAGE_EXT_PATH_PREFIX) == STORAGE_PATH_PREFIX_LEN + 1, + "Ext path prefix len mismatch"); +_Static_assert( + sizeof(STORAGE_INT_PATH_PREFIX) == STORAGE_PATH_PREFIX_LEN + 1, + "Int path prefix len mismatch"); + #define FS_CALL(_storage, _fn) ret = _storage->fs_api->_fn; static bool storage_type_is_valid(StorageType type) { @@ -26,34 +37,29 @@ static StorageData* get_storage_by_file(File* file, StorageData* storages) { static const char* cstr_path_without_vfs_prefix(FuriString* path) { const char* path_cstr = furi_string_get_cstr(path); - return path_cstr + MIN(4u, strlen(path_cstr)); + return path_cstr + MIN(STORAGE_PATH_PREFIX_LEN, strlen(path_cstr)); } static StorageType storage_get_type_by_path(FuriString* path) { StorageType type = ST_ERROR; const char* path_cstr = furi_string_get_cstr(path); - if(furi_string_size(path) == 4) { - if(memcmp(path_cstr, STORAGE_EXT_PATH_PREFIX, strlen(STORAGE_EXT_PATH_PREFIX)) == 0) { - type = ST_EXT; - } else if(memcmp(path_cstr, STORAGE_INT_PATH_PREFIX, strlen(STORAGE_INT_PATH_PREFIX)) == 0) { - type = ST_INT; - } else if(memcmp(path_cstr, STORAGE_ANY_PATH_PREFIX, strlen(STORAGE_ANY_PATH_PREFIX)) == 0) { - type = ST_ANY; - } - } else if(furi_string_size(path) > 4) { - if(memcmp(path_cstr, EXT_PATH(""), strlen(EXT_PATH(""))) == 0) { - type = ST_EXT; - } else if(memcmp(path_cstr, INT_PATH(""), strlen(INT_PATH(""))) == 0) { - type = ST_INT; - } else if(memcmp(path_cstr, ANY_PATH(""), strlen(ANY_PATH(""))) == 0) { - type = ST_ANY; + if(furi_string_size(path) > STORAGE_PATH_PREFIX_LEN) { + if(path_cstr[STORAGE_PATH_PREFIX_LEN] != '/') { + return ST_ERROR; } } + if(memcmp(path_cstr, STORAGE_EXT_PATH_PREFIX, strlen(STORAGE_EXT_PATH_PREFIX)) == 0) { + type = ST_EXT; + } else if(memcmp(path_cstr, STORAGE_INT_PATH_PREFIX, strlen(STORAGE_INT_PATH_PREFIX)) == 0) { + type = ST_INT; + } else if(memcmp(path_cstr, STORAGE_ANY_PATH_PREFIX, strlen(STORAGE_ANY_PATH_PREFIX)) == 0) { + type = ST_ANY; + } + return type; } - static void storage_path_change_to_real_storage(FuriString* path, StorageType real_storage) { if(furi_string_search(path, STORAGE_ANY_PATH_PREFIX) == 0) { switch(real_storage) { From 6ec62f48f99520711b601bb89c0f614f7e6143ca Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Mon, 20 Mar 2023 05:07:17 -0700 Subject: [PATCH 475/824] [FL-3180] OTP programmer: return exit code based on error type (#2504) --- scripts/flipper/utils/programmer_openocd.py | 27 ++++++++++---- scripts/otp.py | 39 +++++++++++++-------- 2 files changed, 45 insertions(+), 21 deletions(-) diff --git a/scripts/flipper/utils/programmer_openocd.py b/scripts/flipper/utils/programmer_openocd.py index b334061035b..3d21718547e 100644 --- a/scripts/flipper/utils/programmer_openocd.py +++ b/scripts/flipper/utils/programmer_openocd.py @@ -1,6 +1,7 @@ import logging import os import typing +from enum import Enum from flipper.utils.programmer import Programmer from flipper.utils.openocd import OpenOCD @@ -8,6 +9,14 @@ from flipper.assets.obdata import OptionBytesData +class OpenOCDProgrammerResult(Enum): + Success = 0 + ErrorGeneric = 1 + ErrorAlignment = 2 + ErrorAlreadyWritten = 3 + ErrorValidation = 4 + + class OpenOCDProgrammer(Programmer): def __init__( self, @@ -199,18 +208,18 @@ def option_bytes_set(self, file_path: str) -> bool: return True - def otp_write(self, address: int, file_path: str) -> bool: + def otp_write(self, address: int, file_path: str) -> OpenOCDProgrammerResult: # Open file, check that it aligned to 8 bytes with open(file_path, "rb") as f: data = f.read() if len(data) % 8 != 0: self.logger.error(f"File {file_path} is not aligned to 8 bytes") - return False + return OpenOCDProgrammerResult.ErrorAlignment # Check that address is aligned to 8 bytes if address % 8 != 0: self.logger.error(f"Address {address} is not aligned to 8 bytes") - return False + return OpenOCDProgrammerResult.ErrorAlignment # Get size of data data_size = len(data) @@ -218,7 +227,7 @@ def otp_write(self, address: int, file_path: str) -> bool: # Check that data size is aligned to 8 bytes if data_size % 8 != 0: self.logger.error(f"Data size {data_size} is not aligned to 8 bytes") - return False + return OpenOCDProgrammerResult.ErrorAlignment self.logger.debug(f"Writing {data_size} bytes to OTP at {address:08X}") self.logger.debug(f"Data: {data.hex().upper()}") @@ -241,14 +250,14 @@ def otp_write(self, address: int, file_path: str) -> bool: self.logger.error( f"OTP memory at {address + i:08X} is not empty: {device_word:08X}" ) - raise Exception("OTP memory is not empty") + return OpenOCDProgrammerResult.ErrorAlreadyWritten if device_word != file_word: already_written = False if already_written: self.logger.info(f"OTP memory is already written with the given data") - return True + return OpenOCDProgrammerResult.Success self.reset(self.RunMode.Stop) stm32.clear_flash_errors(oocd) @@ -278,4 +287,8 @@ def otp_write(self, address: int, file_path: str) -> bool: stm32.reset(oocd, stm32.RunMode.Run) oocd.stop() - return validation_result + return ( + OpenOCDProgrammerResult.Success + if validation_result + else OpenOCDProgrammerResult.ErrorValidation + ) diff --git a/scripts/otp.py b/scripts/otp.py index 3bfe30d2dd7..cb76bdc8611 100755 --- a/scripts/otp.py +++ b/scripts/otp.py @@ -34,8 +34,16 @@ } from flipper.app import App -from flipper.cube import CubeProgrammer -from flipper.utils.programmer_openocd import OpenOCDProgrammer +from flipper.utils.programmer_openocd import OpenOCDProgrammer, OpenOCDProgrammerResult + + +class OTPException(Exception): + def __init__(self, message: str, result: OpenOCDProgrammerResult): + self.message = message + self.result = result + + def get_exit_code(self) -> int: + return int(self.result.value) class Main(App): @@ -183,13 +191,14 @@ def flash_first(self): self.args.serial, ) - if not openocd.otp_write(0x1FFF7000, filename): - raise Exception("Failed to flash OTP") + programmer_result = openocd.otp_write(0x1FFF7000, filename) + if programmer_result != OpenOCDProgrammerResult.Success: + raise OTPException("Failed to flash OTP", programmer_result) self.logger.info(f"Flashed Successfully") - except Exception as e: + except OTPException as e: self.logger.exception(e) - return 1 + return e.get_exit_code() finally: os.remove(filename) @@ -215,13 +224,14 @@ def flash_second(self): self.args.serial, ) - if not openocd.otp_write(0x1FFF7010, filename): - raise Exception("Failed to flash OTP") + programmer_result = openocd.otp_write(0x1FFF7010, filename) + if programmer_result != OpenOCDProgrammerResult.Success: + raise OTPException("Failed to flash OTP", programmer_result) self.logger.info(f"Flashed Successfully") - except Exception as e: + except OTPException as e: self.logger.exception(e) - return 1 + return e.get_exit_code() finally: os.remove(filename) @@ -249,13 +259,14 @@ def flash_all(self): self.args.serial, ) - if not openocd.otp_write(0x1FFF7000, filename): - raise Exception("Failed to flash OTP") + programmer_result = openocd.otp_write(0x1FFF7000, filename) + if programmer_result != OpenOCDProgrammerResult.Success: + raise OTPException("Failed to flash OTP", programmer_result) self.logger.info(f"Flashed Successfully") - except Exception as e: + except OTPException as e: self.logger.exception(e) - return 1 + return e.get_exit_code() finally: os.remove(filename) From f7024cff786751309f67ca3cdbf6d527b9eb5214 Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Mon, 20 Mar 2023 06:09:10 -0700 Subject: [PATCH 476/824] SD Driver: reinit sd card on error (#2493) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * SD Driver: reinit sd card on error * SD Driver: cleanup fatfs bindings * Storage: optimized glue * Storage: move fatfs initialization to appropriate subsystems, minor code cleanup * SD Driver: minor code cleanup Co-authored-by: あく --- applications/services/storage/storage_glue.c | 61 ++--- applications/services/storage/storage_glue.h | 2 +- .../services/storage/storage_processing.c | 8 +- .../services/storage/storages/storage_ext.c | 4 +- firmware/targets/f18/furi_hal/furi_hal.c | 6 - firmware/targets/f7/fatfs/fatfs.c | 47 +--- firmware/targets/f7/fatfs/fatfs.h | 50 +--- firmware/targets/f7/fatfs/ffconf.h | 2 +- firmware/targets/f7/fatfs/syscall.c | 116 --------- firmware/targets/f7/fatfs/user_diskio.c | 230 ++++++++---------- firmware/targets/f7/fatfs/user_diskio.h | 40 +-- firmware/targets/f7/furi_hal/furi_hal.c | 6 - firmware/targets/f7/src/update.c | 2 +- 13 files changed, 155 insertions(+), 419 deletions(-) delete mode 100644 firmware/targets/f7/fatfs/syscall.c diff --git a/applications/services/storage/storage_glue.c b/applications/services/storage/storage_glue.c index 5dabfa38604..63e44c9d7d7 100644 --- a/applications/services/storage/storage_glue.c +++ b/applications/services/storage/storage_glue.c @@ -73,29 +73,34 @@ uint32_t storage_data_get_timestamp(StorageData* storage) { /****************** storage glue ******************/ -bool storage_has_file(const File* file, StorageData* storage_data) { - bool result = false; +static StorageFile* storage_get_file(const File* file, StorageData* storage) { + StorageFile* storage_file_ref = NULL; StorageFileList_it_t it; - for(StorageFileList_it(it, storage_data->files); !StorageFileList_end_p(it); + for(StorageFileList_it(it, storage->files); !StorageFileList_end_p(it); StorageFileList_next(it)) { - const StorageFile* storage_file = StorageFileList_cref(it); + StorageFile* storage_file = StorageFileList_ref(it); if(storage_file->file->file_id == file->file_id) { - result = true; + storage_file_ref = storage_file; break; } } - return result; + return storage_file_ref; +} + +bool storage_has_file(const File* file, StorageData* storage) { + return storage_get_file(file, storage) != NULL; } -bool storage_path_already_open(FuriString* path, StorageFileList_t array) { +bool storage_path_already_open(FuriString* path, StorageData* storage) { bool open = false; StorageFileList_it_t it; - for(StorageFileList_it(it, array); !StorageFileList_end_p(it); StorageFileList_next(it)) { + for(StorageFileList_it(it, storage->files); !StorageFileList_end_p(it); + StorageFileList_next(it)) { const StorageFile* storage_file = StorageFileList_cref(it); if(furi_string_cmp(storage_file->path, path) == 0) { @@ -108,43 +113,15 @@ bool storage_path_already_open(FuriString* path, StorageFileList_t array) { } void storage_set_storage_file_data(const File* file, void* file_data, StorageData* storage) { - StorageFile* founded_file = NULL; - - StorageFileList_it_t it; - - for(StorageFileList_it(it, storage->files); !StorageFileList_end_p(it); - StorageFileList_next(it)) { - StorageFile* storage_file = StorageFileList_ref(it); - - if(storage_file->file->file_id == file->file_id) { - founded_file = storage_file; - break; - } - } - - furi_check(founded_file != NULL); - - founded_file->file_data = file_data; + StorageFile* storage_file_ref = storage_get_file(file, storage); + furi_check(storage_file_ref != NULL); + storage_file_ref->file_data = file_data; } void* storage_get_storage_file_data(const File* file, StorageData* storage) { - const StorageFile* founded_file = NULL; - - StorageFileList_it_t it; - - for(StorageFileList_it(it, storage->files); !StorageFileList_end_p(it); - StorageFileList_next(it)) { - const StorageFile* storage_file = StorageFileList_cref(it); - - if(storage_file->file->file_id == file->file_id) { - founded_file = storage_file; - break; - } - } - - furi_check(founded_file != NULL); - - return founded_file->file_data; + StorageFile* storage_file_ref = storage_get_file(file, storage); + furi_check(storage_file_ref != NULL); + return storage_file_ref->file_data; } void storage_push_storage_file(File* file, FuriString* path, StorageData* storage) { diff --git a/applications/services/storage/storage_glue.h b/applications/services/storage/storage_glue.h index bf0a1c69eda..f10640345d7 100644 --- a/applications/services/storage/storage_glue.h +++ b/applications/services/storage/storage_glue.h @@ -60,7 +60,7 @@ struct StorageData { }; bool storage_has_file(const File* file, StorageData* storage_data); -bool storage_path_already_open(FuriString* path, StorageFileList_t files); +bool storage_path_already_open(FuriString* path, StorageData* storage_data); void storage_set_storage_file_data(const File* file, void* file_data, StorageData* storage); void* storage_get_storage_file_data(const File* file, StorageData* storage); diff --git a/applications/services/storage/storage_processing.c b/applications/services/storage/storage_processing.c index a3076f2711f..e6b42696106 100644 --- a/applications/services/storage/storage_processing.c +++ b/applications/services/storage/storage_processing.c @@ -77,7 +77,7 @@ static void storage_path_change_to_real_storage(FuriString* path, StorageType re } } -FS_Error storage_get_data(Storage* app, FuriString* path, StorageData** storage) { +static FS_Error storage_get_data(Storage* app, FuriString* path, StorageData** storage) { StorageType type = storage_get_type_by_path(path); if(storage_type_is_valid(type)) { @@ -111,7 +111,7 @@ bool storage_process_file_open( file->error_id = storage_get_data(app, path, &storage); if(file->error_id == FSE_OK) { - if(storage_path_already_open(path, storage->files)) { + if(storage_path_already_open(path, storage)) { file->error_id = FSE_ALREADY_OPEN; } else { if(access_mode & FSAM_WRITE) { @@ -268,7 +268,7 @@ bool storage_process_dir_open(Storage* app, File* file, FuriString* path) { file->error_id = storage_get_data(app, path, &storage); if(file->error_id == FSE_OK) { - if(storage_path_already_open(path, storage->files)) { + if(storage_path_already_open(path, storage)) { file->error_id = FSE_ALREADY_OPEN; } else { storage_push_storage_file(file, path, storage); @@ -357,7 +357,7 @@ static FS_Error storage_process_common_remove(Storage* app, FuriString* path) { FS_Error ret = storage_get_data(app, path, &storage); do { - if(storage_path_already_open(path, storage->files)) { + if(storage_path_already_open(path, storage)) { ret = FSE_ALREADY_OPEN; break; } diff --git a/applications/services/storage/storages/storage_ext.c b/applications/services/storage/storages/storage_ext.c index 530c88f851f..d802d6e9f68 100644 --- a/applications/services/storage/storages/storage_ext.c +++ b/applications/services/storage/storages/storage_ext.c @@ -618,8 +618,10 @@ static const FS_Api fs_api = { }; void storage_ext_init(StorageData* storage) { + fatfs_init(); + SDData* sd_data = malloc(sizeof(SDData)); - sd_data->fs = &USERFatFS; + sd_data->fs = &fatfs_object; sd_data->path = "0:/"; sd_data->sd_was_present = true; diff --git a/firmware/targets/f18/furi_hal/furi_hal.c b/firmware/targets/f18/furi_hal/furi_hal.c index 0a68fdb696f..2c255fa0de8 100644 --- a/firmware/targets/f18/furi_hal/furi_hal.c +++ b/firmware/targets/f18/furi_hal/furi_hal.c @@ -3,8 +3,6 @@ #include -#include - #define TAG "FuriHal" void furi_hal_init_early() { @@ -74,10 +72,6 @@ void furi_hal_init() { #endif furi_hal_bt_init(); furi_hal_compress_icon_init(); - - // FatFS driver initialization - MX_FATFS_Init(); - FURI_LOG_I(TAG, "FATFS OK"); } void furi_hal_switch(void* address) { diff --git a/firmware/targets/f7/fatfs/fatfs.c b/firmware/targets/f7/fatfs/fatfs.c index 1aa5fe44bcc..2c0e77fece4 100644 --- a/firmware/targets/f7/fatfs/fatfs.c +++ b/firmware/targets/f7/fatfs/fatfs.c @@ -1,39 +1,12 @@ -/** - ****************************************************************************** - * @file fatfs.c - * @brief Code for fatfs applications - ****************************************************************************** - * @attention - * - *

© Copyright (c) 2020 STMicroelectronics. - * All rights reserved.

- * - * This software component is licensed by ST under Ultimate Liberty license - * SLA0044, the "License"; You may not use this file except in compliance with - * the License. You may obtain a copy of the License at: - * www.st.com/SLA0044 - * - ****************************************************************************** - */ - #include "fatfs.h" -uint8_t retUSER; /* Return value for USER */ -char USERPath[4]; /* USER logical drive path */ -FATFS USERFatFS; /* File system object for USER logical drive */ -FIL USERFile; /* File object for USER */ - -/* USER CODE BEGIN Variables */ - -/* USER CODE END Variables */ +/** logical drive path */ +char fatfs_path[4]; +/** File system object */ +FATFS fatfs_object; -void MX_FATFS_Init(void) { - /*## FatFS: Link the USER driver ###########################*/ - retUSER = FATFS_LinkDriver(&USER_Driver, USERPath); - - /* USER CODE BEGIN Init */ - /* additional user code for init */ - /* USER CODE END Init */ +void fatfs_init(void) { + FATFS_LinkDriver(&sd_fatfs_driver, fatfs_path); } /** @@ -42,13 +15,5 @@ void MX_FATFS_Init(void) { * @retval Time in DWORD */ DWORD get_fattime(void) { - /* USER CODE BEGIN get_fattime */ return 0; - /* USER CODE END get_fattime */ } - -/* USER CODE BEGIN Application */ - -/* USER CODE END Application */ - -/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ diff --git a/firmware/targets/f7/fatfs/fatfs.h b/firmware/targets/f7/fatfs/fatfs.h index a0775d88b3f..8376bf6cc34 100644 --- a/firmware/targets/f7/fatfs/fatfs.h +++ b/firmware/targets/f7/fatfs/fatfs.h @@ -1,49 +1,19 @@ -/** - ****************************************************************************** - * @file fatfs.h - * @brief Header for fatfs applications - ****************************************************************************** - * @attention - * - *

© Copyright (c) 2020 STMicroelectronics. - * All rights reserved.

- * - * This software component is licensed by ST under Ultimate Liberty license - * SLA0044, the "License"; You may not use this file except in compliance with - * the License. You may obtain a copy of the License at: - * www.st.com/SLA0044 - * - ****************************************************************************** - */ - -/* Define to prevent recursive inclusion -------------------------------------*/ -#ifndef __fatfs_H -#define __fatfs_H -#ifdef __cplusplus -extern "C" { -#endif +#pragma once #include "fatfs/ff.h" #include "fatfs/ff_gen_drv.h" -#include "user_diskio.h" /* defines USER_Driver as external */ - -/* USER CODE BEGIN Includes */ - -/* USER CODE END Includes */ +#include "user_diskio.h" -extern uint8_t retUSER; /* Return value for USER */ -extern char USERPath[4]; /* USER logical drive path */ -extern FATFS USERFatFS; /* File system object for USER logical drive */ -extern FIL USERFile; /* File object for USER */ +#ifdef __cplusplus +extern "C" { +#endif -void MX_FATFS_Init(void); +/** File system object */ +extern FATFS fatfs_object; -/* USER CODE BEGIN Prototypes */ +/** Init file system driver */ +void fatfs_init(void); -/* USER CODE END Prototypes */ #ifdef __cplusplus } -#endif -#endif /*__fatfs_H */ - -/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ +#endif \ No newline at end of file diff --git a/firmware/targets/f7/fatfs/ffconf.h b/firmware/targets/f7/fatfs/ffconf.h index 9410cedc86e..a44521550d1 100644 --- a/firmware/targets/f7/fatfs/ffconf.h +++ b/firmware/targets/f7/fatfs/ffconf.h @@ -164,7 +164,7 @@ /* USER CODE BEGIN Volumes */ #define _STR_VOLUME_ID 0 /* 0:Use only 0-9 for drive ID, 1:Use strings for drive ID */ -#define _VOLUME_STRS "RAM", "NAND", "CF", "SD1", "SD2", "USB1", "USB2", "USB3" +#define _VOLUME_STRS "SD" /* _STR_VOLUME_ID switches string support of volume ID. / When _STR_VOLUME_ID is set to 1, also pre-defined strings can be used as drive / number in the path name. _VOLUME_STRS defines the drive ID strings for each diff --git a/firmware/targets/f7/fatfs/syscall.c b/firmware/targets/f7/fatfs/syscall.c deleted file mode 100644 index 00eb8aedeaf..00000000000 --- a/firmware/targets/f7/fatfs/syscall.c +++ /dev/null @@ -1,116 +0,0 @@ -/*------------------------------------------------------------------------*/ -/* Sample code of OS dependent controls for FatFs */ -/* (C)ChaN, 2014 */ -/* Portions COPYRIGHT 2017 STMicroelectronics */ -/* Portions Copyright (C) 2014, ChaN, all right reserved */ -/*------------------------------------------------------------------------*/ - -/** - ****************************************************************************** - * @attention - * - * Copyright (c) 2017 STMicroelectronics. All rights reserved. - * - * This software component is licensed by ST under BSD 3-Clause license, - * the "License"; You may not use this file except in compliance with the - * License. You may obtain a copy of the License at: - * opensource.org/licenses/BSD-3-Clause - * - ****************************************************************************** -**/ - -#include "fatfs/ff.h" - -#if _FS_REENTRANT -/*------------------------------------------------------------------------*/ -/* Create a Synchronization Object */ -/*------------------------------------------------------------------------*/ -/* This function is called in f_mount() function to create a new -/ synchronization object, such as semaphore and mutex. When a 0 is returned, -/ the f_mount() function fails with FR_INT_ERR. -*/ - -int ff_cre_syncobj(/* 1:Function succeeded, 0:Could not create the sync object */ - BYTE vol, /* Corresponding volume (logical drive number) */ - _SYNC_t* sobj /* Pointer to return the created sync object */ -) { - int ret; - - //osSemaphoreDef(SEM); - //*sobj = osSemaphoreCreate(osSemaphore(SEM), 1); - *sobj = furi_mutex_alloc(FuriMutexTypeNormal); - ret = (*sobj != NULL); - - return ret; -} - -/*------------------------------------------------------------------------*/ -/* Delete a Synchronization Object */ -/*------------------------------------------------------------------------*/ -/* This function is called in f_mount() function to delete a synchronization -/ object that created with ff_cre_syncobj() function. When a 0 is returned, -/ the f_mount() function fails with FR_INT_ERR. -*/ - -int ff_del_syncobj(/* 1:Function succeeded, 0:Could not delete due to any error */ - _SYNC_t sobj /* Sync object tied to the logical drive to be deleted */ -) { - furi_mutex_free(sobj); - return 1; -} - -/*------------------------------------------------------------------------*/ -/* Request Grant to Access the Volume */ -/*------------------------------------------------------------------------*/ -/* This function is called on entering file functions to lock the volume. -/ When a 0 is returned, the file function fails with FR_TIMEOUT. -*/ - -int ff_req_grant(/* 1:Got a grant to access the volume, 0:Could not get a grant */ - _SYNC_t sobj /* Sync object to wait */ -) { - int ret = 0; - - if(furi_mutex_acquire(sobj, _FS_TIMEOUT) == FuriStatusOk) { - ret = 1; - } - - return ret; -} - -/*------------------------------------------------------------------------*/ -/* Release Grant to Access the Volume */ -/*------------------------------------------------------------------------*/ -/* This function is called on leaving file functions to unlock the volume. -*/ - -void ff_rel_grant(_SYNC_t sobj /* Sync object to be signaled */ -) { - furi_mutex_release(sobj); -} - -#endif - -#if _USE_LFN == 3 /* LFN with a working buffer on the heap */ -/*------------------------------------------------------------------------*/ -/* Allocate a memory block */ -/*------------------------------------------------------------------------*/ -/* If a NULL is returned, the file function fails with FR_NOT_ENOUGH_CORE. -*/ - -void* ff_memalloc(/* Returns pointer to the allocated memory block */ - UINT msize /* Number of bytes to allocate */ -) { - return ff_malloc(msize); /* Allocate a new memory block with POSIX API */ -} - -/*------------------------------------------------------------------------*/ -/* Free a memory block */ -/*------------------------------------------------------------------------*/ - -void ff_memfree(void* mblock /* Pointer to the memory block to free */ -) { - ff_free(mblock); /* Discard the memory block with POSIX API */ -} - -#endif diff --git a/firmware/targets/f7/fatfs/user_diskio.c b/firmware/targets/f7/fatfs/user_diskio.c index d7be09c5315..74bf26f65d7 100644 --- a/firmware/targets/f7/fatfs/user_diskio.c +++ b/firmware/targets/f7/fatfs/user_diskio.c @@ -1,50 +1,10 @@ -/* USER CODE BEGIN Header */ -/** - ****************************************************************************** - * @file user_diskio.c - * @brief This file includes a diskio driver skeleton to be completed by the user. - ****************************************************************************** - * @attention - * - *

© Copyright (c) 2020 STMicroelectronics. - * All rights reserved.

- * - * This software component is licensed by ST under Ultimate Liberty license - * SLA0044, the "License"; You may not use this file except in compliance with - * the License. You may obtain a copy of the License at: - * www.st.com/SLA0044 - * - ****************************************************************************** - */ -/* USER CODE END Header */ - -#ifdef USE_OBSOLETE_USER_CODE_SECTION_0 -/* - * Warning: the user section 0 is no more in use (starting from CubeMx version 4.16.0) - * To be suppressed in the future. - * Kept to ensure backward compatibility with previous CubeMx versions when - * migrating projects. - * User code previously added there should be copied in the new user sections before - * the section contents can be deleted. - */ -/* USER CODE BEGIN 0 */ -/* USER CODE END 0 */ -#endif - -/* USER CODE BEGIN DECL */ - -/* Includes ------------------------------------------------------------------*/ #include "user_diskio.h" #include #include "sector_cache.h" -/* Private typedef -----------------------------------------------------------*/ -/* Private define ------------------------------------------------------------*/ -/* Private variables ---------------------------------------------------------*/ -/* Disk status */ static volatile DSTATUS Stat = STA_NOINIT; -static DSTATUS User_CheckStatus(BYTE lun) { +static DSTATUS driver_check_status(BYTE lun) { UNUSED(lun); Stat = STA_NOINIT; if(sd_get_card_state() == SdSpiStatusOK) { @@ -54,32 +14,20 @@ static DSTATUS User_CheckStatus(BYTE lun) { return Stat; } -/* USER CODE END DECL */ - -/* Private function prototypes -----------------------------------------------*/ -DSTATUS USER_initialize(BYTE pdrv); -DSTATUS USER_status(BYTE pdrv); -DRESULT USER_read(BYTE pdrv, BYTE* buff, DWORD sector, UINT count); -#if _USE_WRITE == 1 -DRESULT USER_write(BYTE pdrv, const BYTE* buff, DWORD sector, UINT count); -#endif /* _USE_WRITE == 1 */ -#if _USE_IOCTL == 1 -DRESULT USER_ioctl(BYTE pdrv, BYTE cmd, void* buff); -#endif /* _USE_IOCTL == 1 */ - -Diskio_drvTypeDef USER_Driver = { - USER_initialize, - USER_status, - USER_read, -#if _USE_WRITE - USER_write, -#endif /* _USE_WRITE == 1 */ -#if _USE_IOCTL == 1 - USER_ioctl, -#endif /* _USE_IOCTL == 1 */ +static DSTATUS driver_initialize(BYTE pdrv); +static DSTATUS driver_status(BYTE pdrv); +static DRESULT driver_read(BYTE pdrv, BYTE* buff, DWORD sector, UINT count); +static DRESULT driver_write(BYTE pdrv, const BYTE* buff, DWORD sector, UINT count); +static DRESULT driver_ioctl(BYTE pdrv, BYTE cmd, void* buff); + +Diskio_drvTypeDef sd_fatfs_driver = { + driver_initialize, + driver_status, + driver_read, + driver_write, + driver_ioctl, }; -/* Private functions ---------------------------------------------------------*/ static inline bool sd_cache_get(uint32_t address, uint32_t* data) { uint8_t* cached_data = sector_cache_get(address); if(cached_data) { @@ -101,24 +49,73 @@ static inline void sd_cache_invalidate_all() { sector_cache_init(); } +static bool sd_device_read(uint32_t* buff, uint32_t sector, uint32_t count) { + bool result = false; + + furi_hal_spi_acquire(&furi_hal_spi_bus_handle_sd_fast); + furi_hal_sd_spi_handle = &furi_hal_spi_bus_handle_sd_fast; + + if(sd_read_blocks(buff, sector, count, SD_TIMEOUT_MS) == SdSpiStatusOK) { + FuriHalCortexTimer timer = furi_hal_cortex_timer_get(SD_TIMEOUT_MS * 1000); + + /* wait until the read operation is finished */ + result = true; + while(sd_get_card_state() != SdSpiStatusOK) { + if(furi_hal_cortex_timer_is_expired(timer)) { + result = false; + break; + } + } + } + + furi_hal_sd_spi_handle = NULL; + furi_hal_spi_release(&furi_hal_spi_bus_handle_sd_fast); + + return result; +} + +static bool sd_device_write(uint32_t* buff, uint32_t sector, uint32_t count) { + bool result = false; + + furi_hal_spi_acquire(&furi_hal_spi_bus_handle_sd_fast); + furi_hal_sd_spi_handle = &furi_hal_spi_bus_handle_sd_fast; + + if(sd_write_blocks(buff, sector, count, SD_TIMEOUT_MS) == SdSpiStatusOK) { + FuriHalCortexTimer timer = furi_hal_cortex_timer_get(SD_TIMEOUT_MS * 1000); + + /* wait until the Write operation is finished */ + result = true; + while(sd_get_card_state() != SdSpiStatusOK) { + if(furi_hal_cortex_timer_is_expired(timer)) { + sd_cache_invalidate_all(); + + result = false; + break; + } + } + } + + furi_hal_sd_spi_handle = NULL; + furi_hal_spi_release(&furi_hal_spi_bus_handle_sd_fast); + + return result; +} + /** * @brief Initializes a Drive * @param pdrv: Physical drive number (0..) * @retval DSTATUS: Operation status */ -DSTATUS USER_initialize(BYTE pdrv) { - /* USER CODE BEGIN INIT */ - +static DSTATUS driver_initialize(BYTE pdrv) { furi_hal_spi_acquire(&furi_hal_spi_bus_handle_sd_fast); furi_hal_sd_spi_handle = &furi_hal_spi_bus_handle_sd_fast; - DSTATUS status = User_CheckStatus(pdrv); + DSTATUS status = driver_check_status(pdrv); furi_hal_sd_spi_handle = NULL; furi_hal_spi_release(&furi_hal_spi_bus_handle_sd_fast); return status; - /* USER CODE END INIT */ } /** @@ -126,11 +123,9 @@ DSTATUS USER_initialize(BYTE pdrv) { * @param pdrv: Physical drive number (0..) * @retval DSTATUS: Operation status */ -DSTATUS USER_status(BYTE pdrv) { - /* USER CODE BEGIN STATUS */ +static DSTATUS driver_status(BYTE pdrv) { UNUSED(pdrv); return Stat; - /* USER CODE END STATUS */ } /** @@ -141,11 +136,10 @@ DSTATUS USER_status(BYTE pdrv) { * @param count: Number of sectors to read (1..128) * @retval DRESULT: Operation result */ -DRESULT USER_read(BYTE pdrv, BYTE* buff, DWORD sector, UINT count) { - /* USER CODE BEGIN READ */ +static DRESULT driver_read(BYTE pdrv, BYTE* buff, DWORD sector, UINT count) { UNUSED(pdrv); - DRESULT res = RES_ERROR; + bool result; bool single_sector = count == 1; if(single_sector) { @@ -154,32 +148,33 @@ DRESULT USER_read(BYTE pdrv, BYTE* buff, DWORD sector, UINT count) { } } - furi_hal_spi_acquire(&furi_hal_spi_bus_handle_sd_fast); - furi_hal_sd_spi_handle = &furi_hal_spi_bus_handle_sd_fast; + result = sd_device_read((uint32_t*)buff, (uint32_t)(sector), count); - if(sd_read_blocks((uint32_t*)buff, (uint32_t)(sector), count, SD_TIMEOUT_MS) == - SdSpiStatusOK) { - FuriHalCortexTimer timer = furi_hal_cortex_timer_get(SD_TIMEOUT_MS * 1000); + if(!result) { + uint8_t counter = sd_max_mount_retry_count(); - /* wait until the read operation is finished */ - res = RES_OK; - while(sd_get_card_state() != SdSpiStatusOK) { - if(furi_hal_cortex_timer_is_expired(timer)) { - res = RES_ERROR; - break; + while(result == false && counter > 0 && hal_sd_detect()) { + SdSpiStatus status; + + if((counter % 2) == 0) { + // power reset sd card + status = sd_init(true); + } else { + status = sd_init(false); } + + if(status == SdSpiStatusOK) { + result = sd_device_read((uint32_t*)buff, (uint32_t)(sector), count); + } + counter--; } } - furi_hal_sd_spi_handle = NULL; - furi_hal_spi_release(&furi_hal_spi_bus_handle_sd_fast); - - if(single_sector && res == RES_OK) { + if(single_sector && result == true) { sd_cache_put(sector, (uint32_t*)buff); } - return res; - /* USER CODE END READ */ + return result ? RES_OK : RES_ERROR; } /** @@ -190,41 +185,36 @@ DRESULT USER_read(BYTE pdrv, BYTE* buff, DWORD sector, UINT count) { * @param count: Number of sectors to write (1..128) * @retval DRESULT: Operation result */ -#if _USE_WRITE == 1 -DRESULT USER_write(BYTE pdrv, const BYTE* buff, DWORD sector, UINT count) { - /* USER CODE BEGIN WRITE */ - /* USER CODE HERE */ +static DRESULT driver_write(BYTE pdrv, const BYTE* buff, DWORD sector, UINT count) { UNUSED(pdrv); - DRESULT res = RES_ERROR; + bool result; sd_cache_invalidate_range(sector, sector + count); - furi_hal_spi_acquire(&furi_hal_spi_bus_handle_sd_fast); - furi_hal_sd_spi_handle = &furi_hal_spi_bus_handle_sd_fast; + result = sd_device_write((uint32_t*)buff, (uint32_t)(sector), count); - if(sd_write_blocks((uint32_t*)buff, (uint32_t)(sector), count, SD_TIMEOUT_MS) == - SdSpiStatusOK) { - FuriHalCortexTimer timer = furi_hal_cortex_timer_get(SD_TIMEOUT_MS * 1000); + if(!result) { + uint8_t counter = sd_max_mount_retry_count(); - /* wait until the Write operation is finished */ - res = RES_OK; - while(sd_get_card_state() != SdSpiStatusOK) { - if(furi_hal_cortex_timer_is_expired(timer)) { - sd_cache_invalidate_all(); + while(result == false && counter > 0 && hal_sd_detect()) { + SdSpiStatus status; - res = RES_ERROR; - break; + if((counter % 2) == 0) { + // power reset sd card + status = sd_init(true); + } else { + status = sd_init(false); } + + if(status == SdSpiStatusOK) { + result = sd_device_write((uint32_t*)buff, (uint32_t)(sector), count); + } + counter--; } } - furi_hal_sd_spi_handle = NULL; - furi_hal_spi_release(&furi_hal_spi_bus_handle_sd_fast); - - return res; - /* USER CODE END WRITE */ + return result ? RES_OK : RES_ERROR; } -#endif /* _USE_WRITE == 1 */ /** * @brief I/O control operation @@ -233,9 +223,7 @@ DRESULT USER_write(BYTE pdrv, const BYTE* buff, DWORD sector, UINT count) { * @param *buff: Buffer to send/receive control data * @retval DRESULT: Operation result */ -#if _USE_IOCTL == 1 -DRESULT USER_ioctl(BYTE pdrv, BYTE cmd, void* buff) { - /* USER CODE BEGIN IOCTL */ +static DRESULT driver_ioctl(BYTE pdrv, BYTE cmd, void* buff) { UNUSED(pdrv); DRESULT res = RES_ERROR; SD_CardInfo CardInfo; @@ -280,8 +268,4 @@ DRESULT USER_ioctl(BYTE pdrv, BYTE cmd, void* buff) { furi_hal_spi_release(&furi_hal_spi_bus_handle_sd_fast); return res; - /* USER CODE END IOCTL */ } -#endif /* _USE_IOCTL == 1 */ - -/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ diff --git a/firmware/targets/f7/fatfs/user_diskio.h b/firmware/targets/f7/fatfs/user_diskio.h index 12e0f27dc60..7b3f2bb9ec0 100644 --- a/firmware/targets/f7/fatfs/user_diskio.h +++ b/firmware/targets/f7/fatfs/user_diskio.h @@ -1,48 +1,14 @@ -/* USER CODE BEGIN Header */ -/** - ****************************************************************************** - * @file user_diskio.h - * @brief This file contains the common defines and functions prototypes for - * the user_diskio driver. - ****************************************************************************** - * @attention - * - *

© Copyright (c) 2020 STMicroelectronics. - * All rights reserved.

- * - * This software component is licensed by ST under Ultimate Liberty license - * SLA0044, the "License"; You may not use this file except in compliance with - * the License. You may obtain a copy of the License at: - * www.st.com/SLA0044 - * - ****************************************************************************** - */ -/* USER CODE END Header */ - -/* Define to prevent recursive inclusion -------------------------------------*/ -#ifndef __USER_DISKIO_H -#define __USER_DISKIO_H +#pragma once #ifdef __cplusplus extern "C" { #endif -/* USER CODE BEGIN 0 */ - -/* Includes ------------------------------------------------------------------*/ #include "sd_spi_io.h" #include "fatfs/ff_gen_drv.h" -/* Exported types ------------------------------------------------------------*/ -/* Exported constants --------------------------------------------------------*/ -/* Exported functions ------------------------------------------------------- */ -extern Diskio_drvTypeDef USER_Driver; -/* USER CODE END 0 */ +extern Diskio_drvTypeDef sd_fatfs_driver; #ifdef __cplusplus } -#endif - -#endif /* __USER_DISKIO_H */ - -/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ +#endif \ No newline at end of file diff --git a/firmware/targets/f7/furi_hal/furi_hal.c b/firmware/targets/f7/furi_hal/furi_hal.c index afe46c4ed3e..5840a697e44 100644 --- a/firmware/targets/f7/furi_hal/furi_hal.c +++ b/firmware/targets/f7/furi_hal/furi_hal.c @@ -4,8 +4,6 @@ #include -#include - #define TAG "FuriHal" void furi_hal_init_early() { @@ -81,10 +79,6 @@ void furi_hal_init() { furi_hal_nfc_init(); furi_hal_rfid_init(); #endif - - // FatFS driver initialization - MX_FATFS_Init(); - FURI_LOG_I(TAG, "FATFS OK"); } void furi_hal_switch(void* address) { diff --git a/firmware/targets/f7/src/update.c b/firmware/targets/f7/src/update.c index d8d26eb7cec..c1e1084c218 100644 --- a/firmware/targets/f7/src/update.c +++ b/firmware/targets/f7/src/update.c @@ -44,7 +44,7 @@ static bool flipper_update_init() { furi_hal_spi_config_init(); - MX_FATFS_Init(); + fatfs_init(); if(!hal_sd_detect()) { return false; } From 60ac2e98810fa9638f9955ea7fa6a553d1cedaa9 Mon Sep 17 00:00:00 2001 From: hedger Date: Mon, 20 Mar 2023 19:03:55 +0400 Subject: [PATCH 477/824] [FL-3161] Improved debugging experience for external apps (#2507) * debug: automated support for multiple debug symbol files * faploader: extra checks for app list state * debug: trigger BP before fap's EP if under debugger * faploader, debug: better naming * docs: info on load breakpoint * faploader: header cleanup * faploader: naming fixes * debug: less verbose; setting debug flag more often * typo fix --- applications/main/fap_loader/fap_loader_app.c | 19 ++- debug/flipperapps.py | 115 +++++++++++------- documentation/AppsOnSDCard.md | 2 + lib/flipper_application/elf/elf_file.c | 5 +- lib/flipper_application/flipper_application.c | 47 +++++-- 5 files changed, 128 insertions(+), 60 deletions(-) diff --git a/applications/main/fap_loader/fap_loader_app.c b/applications/main/fap_loader/fap_loader_app.c index dcbad8e139b..f5c7af02488 100644 --- a/applications/main/fap_loader/fap_loader_app.c +++ b/applications/main/fap_loader/fap_loader_app.c @@ -1,16 +1,17 @@ +#include "fap_loader_app.h" + #include -#include + #include +#include #include -#include #include #include #include #include #include -#include "fap_loader_app.h" -#define TAG "fap_loader_app" +#define TAG "FapLoader" struct FapLoader { FlipperApplication* app; @@ -22,6 +23,8 @@ struct FapLoader { Loading* loading; }; +volatile bool fap_loader_debug_active = false; + bool fap_loader_load_name_and_icon( FuriString* path, Storage* storage, @@ -107,6 +110,14 @@ static bool fap_loader_run_selected_app(FapLoader* loader) { FuriThread* thread = flipper_application_spawn(loader->app, NULL); + /* This flag is set by the debugger - to break on app start */ + if(fap_loader_debug_active) { + FURI_LOG_W(TAG, "Triggering BP for debugger"); + /* After hitting this, you can set breakpoints in your .fap's code + * Note that you have to toggle breakpoints that were set before */ + __asm volatile("bkpt 0"); + } + FuriString* app_name = furi_string_alloc(); path_extract_filename_no_ext(furi_string_get_cstr(loader->fap_path), app_name); furi_thread_set_appid(thread, furi_string_get_cstr(app_name)); diff --git a/debug/flipperapps.py b/debug/flipperapps.py index e815e40b17b..1dc5ebd04bb 100644 --- a/debug/flipperapps.py +++ b/debug/flipperapps.py @@ -2,7 +2,6 @@ from typing import Optional, Tuple, Dict, ClassVar import struct import posixpath -import os import zlib import gdb @@ -66,9 +65,9 @@ def get_gdb_load_command(self) -> str: def get_gdb_unload_command(self) -> str: return f"remove-symbol-file -a 0x{self.text_address:08x}" - def is_loaded_in_gdb(self, gdb_app) -> bool: - # Avoid constructing full app wrapper for comparison - return self.entry_address == int(gdb_app["state"]["entry"]) + @staticmethod + def get_gdb_app_ep(app) -> int: + return int(app["state"]["entry"]) @staticmethod def parse_debug_link_data(section_data: bytes) -> Tuple[str, int]: @@ -79,10 +78,10 @@ def parse_debug_link_data(section_data: bytes) -> Tuple[str, int]: crc32 = struct.unpack(" "AppState": + @classmethod + def from_gdb(cls, gdb_app: "AppState") -> "AppState": state = AppState(str(gdb_app["manifest"]["name"].string())) - state.entry_address = int(gdb_app["state"]["entry"]) + state.entry_address = cls.get_gdb_app_ep(gdb_app) app_state = gdb_app["state"] if debug_link_size := int(app_state["debug_link_info"]["debug_link_size"]): @@ -123,59 +122,83 @@ def invoke(self, arg, from_tty): try: global helper print(f"Set '{arg}' as debug info lookup path for Flipper external apps") - helper.attach_fw() + helper.attach_to_fw() gdb.events.stop.connect(helper.handle_stop) + gdb.events.exited.connect(helper.handle_exit) except gdb.error as e: print(f"Support for Flipper external apps debug is not available: {e}") -SetFapDebugElfRoot() - - -class FlipperAppDebugHelper: +class FlipperAppStateHelper: def __init__(self): - self.app_ptr = None self.app_type_ptr = None - self.current_app: AppState = None + self.app_list_ptr = None + self.app_list_entry_type = None + self._current_apps: list[AppState] = [] - def attach_fw(self) -> None: - self.app_ptr = gdb.lookup_global_symbol("last_loaded_app") - self.app_type_ptr = gdb.lookup_type("FlipperApplication").pointer() - self._check_app_state() - - def _check_app_state(self) -> None: - app_ptr_value = self.app_ptr.value() - if not app_ptr_value and self.current_app: - # There is an ELF loaded in GDB, but nothing is running on the device - self._unload_debug_elf() - elif app_ptr_value: - # There is an app running on the device - loaded_app = app_ptr_value.cast(self.app_type_ptr).dereference() - - if self.current_app and not self.current_app.is_loaded_in_gdb(loaded_app): - # Currently loaded ELF is not the one running on the device - self._unload_debug_elf() - - if not self.current_app: - # Load ELF for the app running on the device - self._load_debug_elf(loaded_app) - - def _unload_debug_elf(self) -> None: + def _walk_app_list(self, list_head): + while list_head: + if app := list_head["data"]: + yield app.dereference() + list_head = list_head["next"] + + def _exec_gdb_command(self, command: str) -> bool: try: - gdb.execute(self.current_app.get_gdb_unload_command()) + gdb.execute(command) + return True except gdb.error as e: - print(f"Failed to unload debug ELF: {e} (might not be an error)") - self.current_app = None + print(f"Failed to execute GDB command '{command}': {e}") + return False - def _load_debug_elf(self, app_object) -> None: - self.current_app = AppState.from_gdb(app_object) + def _sync_apps(self) -> None: + self.set_debug_mode(True) + if not (app_list := self.app_list_ptr.value()): + print("Reset app loader state") + for app in self._current_apps: + self._exec_gdb_command(app.get_gdb_unload_command()) + self._current_apps = [] + return + + loaded_apps: dict[int, gdb.Value] = dict( + (AppState.get_gdb_app_ep(app), app) + for app in self._walk_app_list(app_list[0]) + ) - if self.current_app.is_debug_available(): - gdb.execute(self.current_app.get_gdb_load_command()) + for app in self._current_apps.copy(): + if app.entry_address not in loaded_apps: + print(f"Application {app.name} is no longer loaded") + if not self._exec_gdb_command(app.get_gdb_unload_command()): + print(f"Failed to unload debug info for {app.name}") + self._current_apps.remove(app) + + for entry_point, app in loaded_apps.items(): + if entry_point not in set(app.entry_address for app in self._current_apps): + new_app_state = AppState.from_gdb(app) + print(f"New application loaded. Adding debug info") + if self._exec_gdb_command(new_app_state.get_gdb_load_command()): + self._current_apps.append(new_app_state) + else: + print(f"Failed to load debug info for {new_app_state}") + + def attach_to_fw(self) -> None: + print("Attaching to Flipper firmware") + self.app_list_ptr = gdb.lookup_global_symbol( + "flipper_application_loaded_app_list" + ) + self.app_type_ptr = gdb.lookup_type("FlipperApplication").pointer() + self.app_list_entry_type = gdb.lookup_type("struct FlipperApplicationList_s") def handle_stop(self, event) -> None: - self._check_app_state() + self._sync_apps() + def handle_exit(self, event) -> None: + self.set_debug_mode(False) -helper = FlipperAppDebugHelper() + def set_debug_mode(self, mode: bool) -> None: + gdb.execute(f"set variable fap_loader_debug_active = {int(mode)}") + + +# Init additional 'fap-set-debug-elf-root' command and set up hooks +SetFapDebugElfRoot() +helper = FlipperAppStateHelper() print("Support for Flipper external apps debug is loaded") diff --git a/documentation/AppsOnSDCard.md b/documentation/AppsOnSDCard.md index 9ab7e9b26bb..75430570699 100644 --- a/documentation/AppsOnSDCard.md +++ b/documentation/AppsOnSDCard.md @@ -32,6 +32,8 @@ Images and animated icons should follow the same [naming convention](../assets/R With it, you can debug FAPs as if they were a part of the main firmware — inspect variables, set breakpoints, step through the code, etc. +If debugging session is active, firmware will trigger a breakpoint after loading a FAP it into memory, but before running any code from it. This allows you to set breakpoints in the FAP's code. Note that any breakpoints set before the FAP is loaded may need re-setting after the FAP is actually loaded, since before loading it debugger cannot know the exact address of the FAP's code. + ### Setting up debugging environment The debugging support script looks up debugging information in the latest firmware build directory (`build/latest`). That directory is symlinked by `fbt` to the latest firmware configuration (Debug or Release) build directory when you run `./fbt` for the chosen configuration. See [fbt docs](./fbt.md#nb) for details. diff --git a/lib/flipper_application/elf/elf_file.c b/lib/flipper_application/elf/elf_file.c index 146afccb51a..0338144a9a7 100644 --- a/lib/flipper_application/elf/elf_file.c +++ b/lib/flipper_application/elf/elf_file.c @@ -830,8 +830,9 @@ void elf_file_init_debug_info(ELFFile* elf, ELFDebugInfo* debug_info) { const void* data_ptr = itref->value.data; if(data_ptr) { - debug_info->mmap_entries[mmap_entry_idx].address = (uint32_t)data_ptr; - debug_info->mmap_entries[mmap_entry_idx].name = itref->key; + ELFMemoryMapEntry* entry = &debug_info->mmap_entries[mmap_entry_idx]; + entry->address = (uint32_t)data_ptr; + entry->name = itref->key; mmap_entry_idx++; } } diff --git a/lib/flipper_application/flipper_application.c b/lib/flipper_application/flipper_application.c index ca917cf1afb..1b4f5681486 100644 --- a/lib/flipper_application/flipper_application.c +++ b/lib/flipper_application/flipper_application.c @@ -3,7 +3,9 @@ #include #include "application_assets.h" -#define TAG "fapp" +#include + +#define TAG "Fap" struct FlipperApplication { ELFDebugInfo state; @@ -13,8 +15,39 @@ struct FlipperApplication { void* ep_thread_args; }; -/* For debugger access to app state */ -FlipperApplication* last_loaded_app = NULL; +/********************** Debugger access to loader state **********************/ + +LIST_DEF(FlipperApplicationList, const FlipperApplication*, M_POD_OPLIST); + +FlipperApplicationList_t flipper_application_loaded_app_list = {0}; +static bool flipper_application_loaded_app_list_initialized = false; + +static void flipper_application_list_add_app(const FlipperApplication* app) { + furi_assert(app); + + if(!flipper_application_loaded_app_list_initialized) { + FlipperApplicationList_init(flipper_application_loaded_app_list); + flipper_application_loaded_app_list_initialized = true; + } + FlipperApplicationList_push_back(flipper_application_loaded_app_list, app); +} + +static void flipper_application_list_remove_app(const FlipperApplication* app) { + furi_assert(flipper_application_loaded_app_list_initialized); + furi_assert(app); + + FlipperApplicationList_it_t it; + for(FlipperApplicationList_it(it, flipper_application_loaded_app_list); + !FlipperApplicationList_end_p(it); + FlipperApplicationList_next(it)) { + if(*FlipperApplicationList_ref(it) == app) { + FlipperApplicationList_remove(flipper_application_loaded_app_list, it); + break; + } + } +} + +/*****************************************************************************/ FlipperApplication* flipper_application_alloc(Storage* storage, const ElfApiInterface* api_interface) { @@ -37,8 +70,8 @@ void flipper_application_free(FlipperApplication* app) { furi_thread_free(app->thread); } - if(!flipper_application_is_plugin(app)) { - last_loaded_app = NULL; + if(app->state.entry) { + flipper_application_list_remove_app(app); } elf_file_clear_debug_info(&app->state); @@ -153,14 +186,12 @@ const FlipperApplicationManifest* flipper_application_get_manifest(FlipperApplic } FlipperApplicationLoadStatus flipper_application_map_to_memory(FlipperApplication* app) { - if(!flipper_application_is_plugin(app)) { - last_loaded_app = app; - } ELFFileLoadStatus status = elf_file_load_sections(app->elf); switch(status) { case ELFFileLoadStatusSuccess: elf_file_init_debug_info(app->elf, &app->state); + flipper_application_list_add_app(app); return FlipperApplicationLoadStatusSuccess; case ELFFileLoadStatusNoFreeMemory: return FlipperApplicationLoadStatusNoFreeMemory; From 1d91a572cc15fc452272baec9cbd09a7fdf7f57a Mon Sep 17 00:00:00 2001 From: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> Date: Mon, 20 Mar 2023 18:22:40 +0300 Subject: [PATCH 478/824] [FL-3182] Fix typos in iButton (#2506) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix a typo: right shift instead of greater than * Fix a typo: proper iButton part number Co-authored-by: あく --- documentation/file_formats/iButtonFileFormat.md | 2 +- lib/one_wire/ibutton/protocols/dallas/dallas_common.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/file_formats/iButtonFileFormat.md b/documentation/file_formats/iButtonFileFormat.md index d31c297a267..63743f06372 100644 --- a/documentation/file_formats/iButtonFileFormat.md +++ b/documentation/file_formats/iButtonFileFormat.md @@ -26,7 +26,7 @@ Changelog: | Name | Type | Description | | ----------- | ------ | -------------------------------------------- | -| Protocol | string | Currently supported: DS1990, DS1992, DS1996, DS1997, DSGeneric*, Cyfral, Metakom | +| Protocol | string | Currently supported: DS1990, DS1992, DS1996, DS1971, DSGeneric*, Cyfral, Metakom | | Rom Data | hex | Read-only memory data (Dallas protocols only) | | Sram Data | hex | Static RAM data (DS1992 and DS1996 only) | Eeprom Data | hex | EEPROM data (DS1971 only) diff --git a/lib/one_wire/ibutton/protocols/dallas/dallas_common.c b/lib/one_wire/ibutton/protocols/dallas/dallas_common.c index 22b25db8482..ebf57e555fc 100644 --- a/lib/one_wire/ibutton/protocols/dallas/dallas_common.c +++ b/lib/one_wire/ibutton/protocols/dallas/dallas_common.c @@ -85,7 +85,7 @@ bool dallas_common_read_mem(OneWireHost* host, uint16_t address, uint8_t* data, onewire_host_write(host, DALLAS_COMMON_CMD_READ_MEM); onewire_host_write(host, (uint8_t)address); - onewire_host_write(host, (uint8_t)(address > BITS_IN_BYTE)); + onewire_host_write(host, (uint8_t)(address >> BITS_IN_BYTE)); onewire_host_read_bytes(host, data, (uint16_t)data_size); From 0917494a80da701dd787e2d06aa08b12e1b71099 Mon Sep 17 00:00:00 2001 From: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> Date: Mon, 20 Mar 2023 19:23:17 +0300 Subject: [PATCH 479/824] [FL-3168] Add one_wire lib to f18, separate ibutton (#2509) * Separate ibutton to its own module, add one_wire to f18 * Move onewire cli to a separate app Co-authored-by: Aleksandr Kutuzov --- applications/main/application.fam | 1 + applications/main/ibutton/ibutton_cli.c | 64 +---------------- applications/main/ibutton/ibutton_i.h | 4 +- applications/main/onewire/application.fam | 14 ++++ applications/main/onewire/onewire_cli.c | 72 +++++++++++++++++++ firmware/targets/f18/api_symbols.csv | 31 ++++++++ firmware/targets/f18/target.json | 5 +- firmware/targets/f7/api_symbols.csv | 6 +- firmware/targets/f7/target.json | 3 +- lib/SConscript | 1 + lib/ibutton/SConscript | 24 +++++++ lib/{one_wire => }/ibutton/ibutton_key.c | 0 lib/{one_wire => }/ibutton/ibutton_key.h | 0 lib/{one_wire => }/ibutton/ibutton_key_i.h | 0 .../ibutton/ibutton_protocols.c | 0 .../ibutton/ibutton_protocols.h | 0 lib/{one_wire => }/ibutton/ibutton_worker.c | 0 lib/{one_wire => }/ibutton/ibutton_worker.h | 0 lib/{one_wire => }/ibutton/ibutton_worker_i.h | 0 .../ibutton/ibutton_worker_modes.c | 0 .../ibutton/protocols/blanks/rw1990.c | 0 .../ibutton/protocols/blanks/rw1990.h | 0 .../ibutton/protocols/blanks/tm2004.c | 0 .../ibutton/protocols/blanks/tm2004.h | 0 .../ibutton/protocols/dallas/dallas_common.c | 0 .../ibutton/protocols/dallas/dallas_common.h | 0 .../protocols/dallas/protocol_dallas_base.h | 0 .../protocols/dallas/protocol_ds1971.c | 0 .../protocols/dallas/protocol_ds1971.h | 0 .../protocols/dallas/protocol_ds1990.c | 0 .../protocols/dallas/protocol_ds1990.h | 0 .../protocols/dallas/protocol_ds1992.c | 0 .../protocols/dallas/protocol_ds1992.h | 0 .../protocols/dallas/protocol_ds1996.c | 0 .../protocols/dallas/protocol_ds1996.h | 0 .../protocols/dallas/protocol_ds_generic.c | 0 .../protocols/dallas/protocol_ds_generic.h | 0 .../protocols/dallas/protocol_group_dallas.c | 0 .../protocols/dallas/protocol_group_dallas.h | 0 .../dallas/protocol_group_dallas_defs.c | 0 .../dallas/protocol_group_dallas_defs.h | 0 .../ibutton/protocols/misc/protocol_cyfral.c | 0 .../ibutton/protocols/misc/protocol_cyfral.h | 0 .../protocols/misc/protocol_group_misc.c | 0 .../protocols/misc/protocol_group_misc.h | 0 .../protocols/misc/protocol_group_misc_defs.c | 0 .../protocols/misc/protocol_group_misc_defs.h | 0 .../ibutton/protocols/misc/protocol_metakom.c | 0 .../ibutton/protocols/misc/protocol_metakom.h | 0 .../ibutton/protocols/protocol_common.h | 0 .../ibutton/protocols/protocol_common_i.h | 0 .../ibutton/protocols/protocol_group_base.h | 0 .../ibutton/protocols/protocol_group_defs.c | 0 .../ibutton/protocols/protocol_group_defs.h | 0 lib/one_wire/SConscript | 3 - 55 files changed, 156 insertions(+), 72 deletions(-) create mode 100644 applications/main/onewire/application.fam create mode 100644 applications/main/onewire/onewire_cli.c create mode 100644 lib/ibutton/SConscript rename lib/{one_wire => }/ibutton/ibutton_key.c (100%) rename lib/{one_wire => }/ibutton/ibutton_key.h (100%) rename lib/{one_wire => }/ibutton/ibutton_key_i.h (100%) rename lib/{one_wire => }/ibutton/ibutton_protocols.c (100%) rename lib/{one_wire => }/ibutton/ibutton_protocols.h (100%) rename lib/{one_wire => }/ibutton/ibutton_worker.c (100%) rename lib/{one_wire => }/ibutton/ibutton_worker.h (100%) rename lib/{one_wire => }/ibutton/ibutton_worker_i.h (100%) rename lib/{one_wire => }/ibutton/ibutton_worker_modes.c (100%) rename lib/{one_wire => }/ibutton/protocols/blanks/rw1990.c (100%) rename lib/{one_wire => }/ibutton/protocols/blanks/rw1990.h (100%) rename lib/{one_wire => }/ibutton/protocols/blanks/tm2004.c (100%) rename lib/{one_wire => }/ibutton/protocols/blanks/tm2004.h (100%) rename lib/{one_wire => }/ibutton/protocols/dallas/dallas_common.c (100%) rename lib/{one_wire => }/ibutton/protocols/dallas/dallas_common.h (100%) rename lib/{one_wire => }/ibutton/protocols/dallas/protocol_dallas_base.h (100%) rename lib/{one_wire => }/ibutton/protocols/dallas/protocol_ds1971.c (100%) rename lib/{one_wire => }/ibutton/protocols/dallas/protocol_ds1971.h (100%) rename lib/{one_wire => }/ibutton/protocols/dallas/protocol_ds1990.c (100%) rename lib/{one_wire => }/ibutton/protocols/dallas/protocol_ds1990.h (100%) rename lib/{one_wire => }/ibutton/protocols/dallas/protocol_ds1992.c (100%) rename lib/{one_wire => }/ibutton/protocols/dallas/protocol_ds1992.h (100%) rename lib/{one_wire => }/ibutton/protocols/dallas/protocol_ds1996.c (100%) rename lib/{one_wire => }/ibutton/protocols/dallas/protocol_ds1996.h (100%) rename lib/{one_wire => }/ibutton/protocols/dallas/protocol_ds_generic.c (100%) rename lib/{one_wire => }/ibutton/protocols/dallas/protocol_ds_generic.h (100%) rename lib/{one_wire => }/ibutton/protocols/dallas/protocol_group_dallas.c (100%) rename lib/{one_wire => }/ibutton/protocols/dallas/protocol_group_dallas.h (100%) rename lib/{one_wire => }/ibutton/protocols/dallas/protocol_group_dallas_defs.c (100%) rename lib/{one_wire => }/ibutton/protocols/dallas/protocol_group_dallas_defs.h (100%) rename lib/{one_wire => }/ibutton/protocols/misc/protocol_cyfral.c (100%) rename lib/{one_wire => }/ibutton/protocols/misc/protocol_cyfral.h (100%) rename lib/{one_wire => }/ibutton/protocols/misc/protocol_group_misc.c (100%) rename lib/{one_wire => }/ibutton/protocols/misc/protocol_group_misc.h (100%) rename lib/{one_wire => }/ibutton/protocols/misc/protocol_group_misc_defs.c (100%) rename lib/{one_wire => }/ibutton/protocols/misc/protocol_group_misc_defs.h (100%) rename lib/{one_wire => }/ibutton/protocols/misc/protocol_metakom.c (100%) rename lib/{one_wire => }/ibutton/protocols/misc/protocol_metakom.h (100%) rename lib/{one_wire => }/ibutton/protocols/protocol_common.h (100%) rename lib/{one_wire => }/ibutton/protocols/protocol_common_i.h (100%) rename lib/{one_wire => }/ibutton/protocols/protocol_group_base.h (100%) rename lib/{one_wire => }/ibutton/protocols/protocol_group_defs.c (100%) rename lib/{one_wire => }/ibutton/protocols/protocol_group_defs.h (100%) diff --git a/applications/main/application.fam b/applications/main/application.fam index 1fc3099059b..5c2c21d3753 100644 --- a/applications/main/application.fam +++ b/applications/main/application.fam @@ -4,6 +4,7 @@ App( apptype=FlipperAppType.METAPACKAGE, provides=[ "gpio", + "onewire", "ibutton", "infrared", "lfrfid", diff --git a/applications/main/ibutton/ibutton_cli.c b/applications/main/ibutton/ibutton_cli.c index 2b88b200769..54bc808b5ee 100644 --- a/applications/main/ibutton/ibutton_cli.c +++ b/applications/main/ibutton/ibutton_cli.c @@ -4,25 +4,20 @@ #include #include -#include - -#include -#include -#include +#include +#include +#include static void ibutton_cli(Cli* cli, FuriString* args, void* context); -static void onewire_cli(Cli* cli, FuriString* args, void* context); // app cli function void ibutton_on_system_start() { #ifdef SRV_CLI Cli* cli = furi_record_open(RECORD_CLI); cli_add_command(cli, "ikey", CliCommandFlagDefault, ibutton_cli, cli); - cli_add_command(cli, "onewire", CliCommandFlagDefault, onewire_cli, cli); furi_record_close(RECORD_CLI); #else UNUSED(ibutton_cli); - UNUSED(onewire_cli); #endif } @@ -257,56 +252,3 @@ void ibutton_cli(Cli* cli, FuriString* args, void* context) { furi_string_free(cmd); } - -static void onewire_cli_print_usage() { - printf("Usage:\r\n"); - printf("onewire search\r\n"); -}; - -static void onewire_cli_search(Cli* cli) { - UNUSED(cli); - OneWireHost* onewire = onewire_host_alloc(&ibutton_gpio); - uint8_t address[8]; - bool done = false; - - printf("Search started\r\n"); - - onewire_host_start(onewire); - furi_hal_power_enable_otg(); - - while(!done) { - if(onewire_host_search(onewire, address, OneWireHostSearchModeNormal) != 1) { - printf("Search finished\r\n"); - onewire_host_reset_search(onewire); - done = true; - } else { - printf("Found: "); - for(uint8_t i = 0; i < 8; i++) { - printf("%02X", address[i]); - } - printf("\r\n"); - } - furi_delay_ms(100); - } - - furi_hal_power_disable_otg(); - onewire_host_free(onewire); -} - -void onewire_cli(Cli* cli, FuriString* args, void* context) { - UNUSED(context); - FuriString* cmd; - cmd = furi_string_alloc(); - - if(!args_read_string_and_trim(args, cmd)) { - furi_string_free(cmd); - onewire_cli_print_usage(); - return; - } - - if(furi_string_cmp_str(cmd, "search") == 0) { - onewire_cli_search(cli); - } - - furi_string_free(cmd); -} diff --git a/applications/main/ibutton/ibutton_i.h b/applications/main/ibutton/ibutton_i.h index 8ad0b90e4a8..509279210f6 100644 --- a/applications/main/ibutton/ibutton_i.h +++ b/applications/main/ibutton/ibutton_i.h @@ -7,8 +7,8 @@ #include #include -#include -#include +#include +#include #include #include diff --git a/applications/main/onewire/application.fam b/applications/main/onewire/application.fam new file mode 100644 index 00000000000..68d4f671693 --- /dev/null +++ b/applications/main/onewire/application.fam @@ -0,0 +1,14 @@ +App( + appid="onewire", + name="1-Wire", + apptype=FlipperAppType.METAPACKAGE, + provides=["onewire_start"], +) + +App( + appid="onewire_start", + apptype=FlipperAppType.STARTUP, + entry_point="onewire_on_system_start", + requires=["onewire"], + order=60, +) diff --git a/applications/main/onewire/onewire_cli.c b/applications/main/onewire/onewire_cli.c new file mode 100644 index 00000000000..4c16fb3890e --- /dev/null +++ b/applications/main/onewire/onewire_cli.c @@ -0,0 +1,72 @@ +#include +#include + +#include +#include + +#include + +static void onewire_cli(Cli* cli, FuriString* args, void* context); + +void onewire_on_system_start() { +#ifdef SRV_CLI + Cli* cli = furi_record_open(RECORD_CLI); + cli_add_command(cli, "onewire", CliCommandFlagDefault, onewire_cli, cli); + furi_record_close(RECORD_CLI); +#else + UNUSED(onewire_cli); +#endif +} + +static void onewire_cli_print_usage() { + printf("Usage:\r\n"); + printf("onewire search\r\n"); +}; + +static void onewire_cli_search(Cli* cli) { + UNUSED(cli); + OneWireHost* onewire = onewire_host_alloc(&ibutton_gpio); + uint8_t address[8]; + bool done = false; + + printf("Search started\r\n"); + + onewire_host_start(onewire); + furi_hal_power_enable_otg(); + + while(!done) { + if(onewire_host_search(onewire, address, OneWireHostSearchModeNormal) != 1) { + printf("Search finished\r\n"); + onewire_host_reset_search(onewire); + done = true; + } else { + printf("Found: "); + for(uint8_t i = 0; i < 8; i++) { + printf("%02X", address[i]); + } + printf("\r\n"); + } + furi_delay_ms(100); + } + + furi_hal_power_disable_otg(); + onewire_host_free(onewire); +} + +void onewire_cli(Cli* cli, FuriString* args, void* context) { + UNUSED(context); + FuriString* cmd; + cmd = furi_string_alloc(); + + if(!args_read_string_and_trim(args, cmd)) { + furi_string_free(cmd); + onewire_cli_print_usage(); + return; + } + + if(furi_string_cmp_str(cmd, "search") == 0) { + onewire_cli_search(cli); + } + + furi_string_free(cmd); +} diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index 61195aba67d..7fa269c9612 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -151,6 +151,10 @@ Header,+,lib/mlib/m-list.h,, Header,+,lib/mlib/m-rbtree.h,, Header,+,lib/mlib/m-tuple.h,, Header,+,lib/mlib/m-variant.h,, +Header,+,lib/one_wire/maxim_crc.h,, +Header,+,lib/one_wire/one_wire_host.h,, +Header,+,lib/one_wire/one_wire_host_timing.h,, +Header,+,lib/one_wire/one_wire_slave.h,, Header,+,lib/print/wrappers.h,, Header,+,lib/toolbox/args.h,, Header,+,lib/toolbox/crc32_calc.h,, @@ -1394,6 +1398,7 @@ Function,+,manchester_advance,_Bool,"ManchesterState, ManchesterEvent, Mancheste Function,+,manchester_encoder_advance,_Bool,"ManchesterEncoderState*, const _Bool, ManchesterEncoderResult*" Function,+,manchester_encoder_finish,ManchesterEncoderResult,ManchesterEncoderState* Function,+,manchester_encoder_reset,void,ManchesterEncoderState* +Function,+,maxim_crc8,uint8_t,"const uint8_t*, const uint8_t, const uint8_t" Function,-,mbedtls_des3_crypt_cbc,int,"mbedtls_des3_context*, int, size_t, unsigned char[8], const unsigned char*, unsigned char*" Function,-,mbedtls_des3_crypt_ecb,int,"mbedtls_des3_context*, const unsigned char[8], unsigned char[8]" Function,-,mbedtls_des3_free,void,mbedtls_des3_context* @@ -1472,6 +1477,32 @@ Function,+,notification_message,void,"NotificationApp*, const NotificationSequen Function,+,notification_message_block,void,"NotificationApp*, const NotificationSequence*" Function,-,nrand48,long,unsigned short[3] Function,-,on_exit,int,"void (*)(int, void*), void*" +Function,+,onewire_host_alloc,OneWireHost*,const GpioPin* +Function,+,onewire_host_free,void,OneWireHost* +Function,+,onewire_host_read,uint8_t,OneWireHost* +Function,+,onewire_host_read_bit,_Bool,OneWireHost* +Function,+,onewire_host_read_bytes,void,"OneWireHost*, uint8_t*, uint16_t" +Function,+,onewire_host_reset,_Bool,OneWireHost* +Function,+,onewire_host_reset_search,void,OneWireHost* +Function,+,onewire_host_search,uint8_t,"OneWireHost*, uint8_t*, OneWireHostSearchMode" +Function,+,onewire_host_skip,void,OneWireHost* +Function,+,onewire_host_start,void,OneWireHost* +Function,+,onewire_host_stop,void,OneWireHost* +Function,+,onewire_host_target_search,void,"OneWireHost*, uint8_t" +Function,+,onewire_host_write,void,"OneWireHost*, uint8_t" +Function,+,onewire_host_write_bit,void,"OneWireHost*, _Bool" +Function,+,onewire_host_write_bytes,void,"OneWireHost*, const uint8_t*, uint16_t" +Function,+,onewire_slave_alloc,OneWireSlave*,const GpioPin* +Function,+,onewire_slave_free,void,OneWireSlave* +Function,+,onewire_slave_receive,_Bool,"OneWireSlave*, uint8_t*, size_t" +Function,+,onewire_slave_receive_bit,_Bool,OneWireSlave* +Function,+,onewire_slave_send,_Bool,"OneWireSlave*, const uint8_t*, size_t" +Function,+,onewire_slave_send_bit,_Bool,"OneWireSlave*, _Bool" +Function,+,onewire_slave_set_command_callback,void,"OneWireSlave*, OneWireSlaveCommandCallback, void*" +Function,+,onewire_slave_set_reset_callback,void,"OneWireSlave*, OneWireSlaveResetCallback, void*" +Function,+,onewire_slave_set_result_callback,void,"OneWireSlave*, OneWireSlaveResultCallback, void*" +Function,+,onewire_slave_start,void,OneWireSlave* +Function,+,onewire_slave_stop,void,OneWireSlave* Function,-,open_memstream,FILE*,"char**, size_t*" Function,+,path_append,void,"FuriString*, const char*" Function,+,path_concat,void,"const char*, const char*, FuriString*" diff --git a/firmware/targets/f18/target.json b/firmware/targets/f18/target.json index 2c3b27ab1d4..f1963fb0125 100644 --- a/firmware/targets/f18/target.json +++ b/firmware/targets/f18/target.json @@ -24,6 +24,7 @@ "usb_stm32", "appframe", "assets", + "one_wire", "misc", "flipper_application", "flipperformat", @@ -45,11 +46,11 @@ "furi_hal_subghz_configs.h" ], "excluded_modules": [ - "one_wire", "nfc", "lfrfid", "subghz", + "ibutton", "infrared", "st25rfal002" ] -} \ No newline at end of file +} diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index e46322f4b6b..c98e546727e 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -118,6 +118,9 @@ Header,+,lib/flipper_application/plugins/composite_resolver.h,, Header,+,lib/flipper_application/plugins/plugin_manager.h,, Header,+,lib/flipper_format/flipper_format.h,, Header,+,lib/flipper_format/flipper_format_i.h,, +Header,+,lib/ibutton/ibutton_key.h,, +Header,+,lib/ibutton/ibutton_protocols.h,, +Header,+,lib/ibutton/ibutton_worker.h,, Header,+,lib/infrared/encoder_decoder/infrared.h,, Header,+,lib/infrared/worker/infrared_transmit.h,, Header,+,lib/infrared/worker/infrared_worker.h,, @@ -167,9 +170,6 @@ Header,+,lib/mlib/m-rbtree.h,, Header,+,lib/mlib/m-tuple.h,, Header,+,lib/mlib/m-variant.h,, Header,+,lib/nfc/nfc_device.h,, -Header,+,lib/one_wire/ibutton/ibutton_key.h,, -Header,+,lib/one_wire/ibutton/ibutton_protocols.h,, -Header,+,lib/one_wire/ibutton/ibutton_worker.h,, Header,+,lib/one_wire/maxim_crc.h,, Header,+,lib/one_wire/one_wire_host.h,, Header,+,lib/one_wire/one_wire_host_timing.h,, diff --git a/firmware/targets/f7/target.json b/firmware/targets/f7/target.json index 49aa109bd76..14bb1cd0c21 100644 --- a/firmware/targets/f7/target.json +++ b/firmware/targets/f7/target.json @@ -35,6 +35,7 @@ "appframe", "assets", "one_wire", + "ibutton", "misc", "mbedtls", "lfrfid", @@ -42,4 +43,4 @@ "flipperformat", "toolbox" ] -} \ No newline at end of file +} diff --git a/lib/SConscript b/lib/SConscript index d1e8e8c7eea..f5d4689f1cf 100644 --- a/lib/SConscript +++ b/lib/SConscript @@ -87,6 +87,7 @@ libs = env.BuildModules( "fatfs", "flipper_format", "one_wire", + "ibutton", "infrared", "littlefs", "mbedtls", diff --git a/lib/ibutton/SConscript b/lib/ibutton/SConscript new file mode 100644 index 00000000000..238d65f7d16 --- /dev/null +++ b/lib/ibutton/SConscript @@ -0,0 +1,24 @@ +Import("env") + +env.Append( + LINT_SOURCES=[ + Dir("."), + ], + CPPPATH=[ + "#/lib/ibutton", + ], + SDK_HEADERS=[ + File("ibutton_key.h"), + File("ibutton_worker.h"), + File("ibutton_protocols.h"), + ], +) + +libenv = env.Clone(FW_LIB_NAME="ibutton") +libenv.ApplyLibFlags() + +sources = libenv.GlobRecursive("*.c*") + +lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources) +libenv.Install("${LIB_DIST_DIR}", lib) +Return("lib") diff --git a/lib/one_wire/ibutton/ibutton_key.c b/lib/ibutton/ibutton_key.c similarity index 100% rename from lib/one_wire/ibutton/ibutton_key.c rename to lib/ibutton/ibutton_key.c diff --git a/lib/one_wire/ibutton/ibutton_key.h b/lib/ibutton/ibutton_key.h similarity index 100% rename from lib/one_wire/ibutton/ibutton_key.h rename to lib/ibutton/ibutton_key.h diff --git a/lib/one_wire/ibutton/ibutton_key_i.h b/lib/ibutton/ibutton_key_i.h similarity index 100% rename from lib/one_wire/ibutton/ibutton_key_i.h rename to lib/ibutton/ibutton_key_i.h diff --git a/lib/one_wire/ibutton/ibutton_protocols.c b/lib/ibutton/ibutton_protocols.c similarity index 100% rename from lib/one_wire/ibutton/ibutton_protocols.c rename to lib/ibutton/ibutton_protocols.c diff --git a/lib/one_wire/ibutton/ibutton_protocols.h b/lib/ibutton/ibutton_protocols.h similarity index 100% rename from lib/one_wire/ibutton/ibutton_protocols.h rename to lib/ibutton/ibutton_protocols.h diff --git a/lib/one_wire/ibutton/ibutton_worker.c b/lib/ibutton/ibutton_worker.c similarity index 100% rename from lib/one_wire/ibutton/ibutton_worker.c rename to lib/ibutton/ibutton_worker.c diff --git a/lib/one_wire/ibutton/ibutton_worker.h b/lib/ibutton/ibutton_worker.h similarity index 100% rename from lib/one_wire/ibutton/ibutton_worker.h rename to lib/ibutton/ibutton_worker.h diff --git a/lib/one_wire/ibutton/ibutton_worker_i.h b/lib/ibutton/ibutton_worker_i.h similarity index 100% rename from lib/one_wire/ibutton/ibutton_worker_i.h rename to lib/ibutton/ibutton_worker_i.h diff --git a/lib/one_wire/ibutton/ibutton_worker_modes.c b/lib/ibutton/ibutton_worker_modes.c similarity index 100% rename from lib/one_wire/ibutton/ibutton_worker_modes.c rename to lib/ibutton/ibutton_worker_modes.c diff --git a/lib/one_wire/ibutton/protocols/blanks/rw1990.c b/lib/ibutton/protocols/blanks/rw1990.c similarity index 100% rename from lib/one_wire/ibutton/protocols/blanks/rw1990.c rename to lib/ibutton/protocols/blanks/rw1990.c diff --git a/lib/one_wire/ibutton/protocols/blanks/rw1990.h b/lib/ibutton/protocols/blanks/rw1990.h similarity index 100% rename from lib/one_wire/ibutton/protocols/blanks/rw1990.h rename to lib/ibutton/protocols/blanks/rw1990.h diff --git a/lib/one_wire/ibutton/protocols/blanks/tm2004.c b/lib/ibutton/protocols/blanks/tm2004.c similarity index 100% rename from lib/one_wire/ibutton/protocols/blanks/tm2004.c rename to lib/ibutton/protocols/blanks/tm2004.c diff --git a/lib/one_wire/ibutton/protocols/blanks/tm2004.h b/lib/ibutton/protocols/blanks/tm2004.h similarity index 100% rename from lib/one_wire/ibutton/protocols/blanks/tm2004.h rename to lib/ibutton/protocols/blanks/tm2004.h diff --git a/lib/one_wire/ibutton/protocols/dallas/dallas_common.c b/lib/ibutton/protocols/dallas/dallas_common.c similarity index 100% rename from lib/one_wire/ibutton/protocols/dallas/dallas_common.c rename to lib/ibutton/protocols/dallas/dallas_common.c diff --git a/lib/one_wire/ibutton/protocols/dallas/dallas_common.h b/lib/ibutton/protocols/dallas/dallas_common.h similarity index 100% rename from lib/one_wire/ibutton/protocols/dallas/dallas_common.h rename to lib/ibutton/protocols/dallas/dallas_common.h diff --git a/lib/one_wire/ibutton/protocols/dallas/protocol_dallas_base.h b/lib/ibutton/protocols/dallas/protocol_dallas_base.h similarity index 100% rename from lib/one_wire/ibutton/protocols/dallas/protocol_dallas_base.h rename to lib/ibutton/protocols/dallas/protocol_dallas_base.h diff --git a/lib/one_wire/ibutton/protocols/dallas/protocol_ds1971.c b/lib/ibutton/protocols/dallas/protocol_ds1971.c similarity index 100% rename from lib/one_wire/ibutton/protocols/dallas/protocol_ds1971.c rename to lib/ibutton/protocols/dallas/protocol_ds1971.c diff --git a/lib/one_wire/ibutton/protocols/dallas/protocol_ds1971.h b/lib/ibutton/protocols/dallas/protocol_ds1971.h similarity index 100% rename from lib/one_wire/ibutton/protocols/dallas/protocol_ds1971.h rename to lib/ibutton/protocols/dallas/protocol_ds1971.h diff --git a/lib/one_wire/ibutton/protocols/dallas/protocol_ds1990.c b/lib/ibutton/protocols/dallas/protocol_ds1990.c similarity index 100% rename from lib/one_wire/ibutton/protocols/dallas/protocol_ds1990.c rename to lib/ibutton/protocols/dallas/protocol_ds1990.c diff --git a/lib/one_wire/ibutton/protocols/dallas/protocol_ds1990.h b/lib/ibutton/protocols/dallas/protocol_ds1990.h similarity index 100% rename from lib/one_wire/ibutton/protocols/dallas/protocol_ds1990.h rename to lib/ibutton/protocols/dallas/protocol_ds1990.h diff --git a/lib/one_wire/ibutton/protocols/dallas/protocol_ds1992.c b/lib/ibutton/protocols/dallas/protocol_ds1992.c similarity index 100% rename from lib/one_wire/ibutton/protocols/dallas/protocol_ds1992.c rename to lib/ibutton/protocols/dallas/protocol_ds1992.c diff --git a/lib/one_wire/ibutton/protocols/dallas/protocol_ds1992.h b/lib/ibutton/protocols/dallas/protocol_ds1992.h similarity index 100% rename from lib/one_wire/ibutton/protocols/dallas/protocol_ds1992.h rename to lib/ibutton/protocols/dallas/protocol_ds1992.h diff --git a/lib/one_wire/ibutton/protocols/dallas/protocol_ds1996.c b/lib/ibutton/protocols/dallas/protocol_ds1996.c similarity index 100% rename from lib/one_wire/ibutton/protocols/dallas/protocol_ds1996.c rename to lib/ibutton/protocols/dallas/protocol_ds1996.c diff --git a/lib/one_wire/ibutton/protocols/dallas/protocol_ds1996.h b/lib/ibutton/protocols/dallas/protocol_ds1996.h similarity index 100% rename from lib/one_wire/ibutton/protocols/dallas/protocol_ds1996.h rename to lib/ibutton/protocols/dallas/protocol_ds1996.h diff --git a/lib/one_wire/ibutton/protocols/dallas/protocol_ds_generic.c b/lib/ibutton/protocols/dallas/protocol_ds_generic.c similarity index 100% rename from lib/one_wire/ibutton/protocols/dallas/protocol_ds_generic.c rename to lib/ibutton/protocols/dallas/protocol_ds_generic.c diff --git a/lib/one_wire/ibutton/protocols/dallas/protocol_ds_generic.h b/lib/ibutton/protocols/dallas/protocol_ds_generic.h similarity index 100% rename from lib/one_wire/ibutton/protocols/dallas/protocol_ds_generic.h rename to lib/ibutton/protocols/dallas/protocol_ds_generic.h diff --git a/lib/one_wire/ibutton/protocols/dallas/protocol_group_dallas.c b/lib/ibutton/protocols/dallas/protocol_group_dallas.c similarity index 100% rename from lib/one_wire/ibutton/protocols/dallas/protocol_group_dallas.c rename to lib/ibutton/protocols/dallas/protocol_group_dallas.c diff --git a/lib/one_wire/ibutton/protocols/dallas/protocol_group_dallas.h b/lib/ibutton/protocols/dallas/protocol_group_dallas.h similarity index 100% rename from lib/one_wire/ibutton/protocols/dallas/protocol_group_dallas.h rename to lib/ibutton/protocols/dallas/protocol_group_dallas.h diff --git a/lib/one_wire/ibutton/protocols/dallas/protocol_group_dallas_defs.c b/lib/ibutton/protocols/dallas/protocol_group_dallas_defs.c similarity index 100% rename from lib/one_wire/ibutton/protocols/dallas/protocol_group_dallas_defs.c rename to lib/ibutton/protocols/dallas/protocol_group_dallas_defs.c diff --git a/lib/one_wire/ibutton/protocols/dallas/protocol_group_dallas_defs.h b/lib/ibutton/protocols/dallas/protocol_group_dallas_defs.h similarity index 100% rename from lib/one_wire/ibutton/protocols/dallas/protocol_group_dallas_defs.h rename to lib/ibutton/protocols/dallas/protocol_group_dallas_defs.h diff --git a/lib/one_wire/ibutton/protocols/misc/protocol_cyfral.c b/lib/ibutton/protocols/misc/protocol_cyfral.c similarity index 100% rename from lib/one_wire/ibutton/protocols/misc/protocol_cyfral.c rename to lib/ibutton/protocols/misc/protocol_cyfral.c diff --git a/lib/one_wire/ibutton/protocols/misc/protocol_cyfral.h b/lib/ibutton/protocols/misc/protocol_cyfral.h similarity index 100% rename from lib/one_wire/ibutton/protocols/misc/protocol_cyfral.h rename to lib/ibutton/protocols/misc/protocol_cyfral.h diff --git a/lib/one_wire/ibutton/protocols/misc/protocol_group_misc.c b/lib/ibutton/protocols/misc/protocol_group_misc.c similarity index 100% rename from lib/one_wire/ibutton/protocols/misc/protocol_group_misc.c rename to lib/ibutton/protocols/misc/protocol_group_misc.c diff --git a/lib/one_wire/ibutton/protocols/misc/protocol_group_misc.h b/lib/ibutton/protocols/misc/protocol_group_misc.h similarity index 100% rename from lib/one_wire/ibutton/protocols/misc/protocol_group_misc.h rename to lib/ibutton/protocols/misc/protocol_group_misc.h diff --git a/lib/one_wire/ibutton/protocols/misc/protocol_group_misc_defs.c b/lib/ibutton/protocols/misc/protocol_group_misc_defs.c similarity index 100% rename from lib/one_wire/ibutton/protocols/misc/protocol_group_misc_defs.c rename to lib/ibutton/protocols/misc/protocol_group_misc_defs.c diff --git a/lib/one_wire/ibutton/protocols/misc/protocol_group_misc_defs.h b/lib/ibutton/protocols/misc/protocol_group_misc_defs.h similarity index 100% rename from lib/one_wire/ibutton/protocols/misc/protocol_group_misc_defs.h rename to lib/ibutton/protocols/misc/protocol_group_misc_defs.h diff --git a/lib/one_wire/ibutton/protocols/misc/protocol_metakom.c b/lib/ibutton/protocols/misc/protocol_metakom.c similarity index 100% rename from lib/one_wire/ibutton/protocols/misc/protocol_metakom.c rename to lib/ibutton/protocols/misc/protocol_metakom.c diff --git a/lib/one_wire/ibutton/protocols/misc/protocol_metakom.h b/lib/ibutton/protocols/misc/protocol_metakom.h similarity index 100% rename from lib/one_wire/ibutton/protocols/misc/protocol_metakom.h rename to lib/ibutton/protocols/misc/protocol_metakom.h diff --git a/lib/one_wire/ibutton/protocols/protocol_common.h b/lib/ibutton/protocols/protocol_common.h similarity index 100% rename from lib/one_wire/ibutton/protocols/protocol_common.h rename to lib/ibutton/protocols/protocol_common.h diff --git a/lib/one_wire/ibutton/protocols/protocol_common_i.h b/lib/ibutton/protocols/protocol_common_i.h similarity index 100% rename from lib/one_wire/ibutton/protocols/protocol_common_i.h rename to lib/ibutton/protocols/protocol_common_i.h diff --git a/lib/one_wire/ibutton/protocols/protocol_group_base.h b/lib/ibutton/protocols/protocol_group_base.h similarity index 100% rename from lib/one_wire/ibutton/protocols/protocol_group_base.h rename to lib/ibutton/protocols/protocol_group_base.h diff --git a/lib/one_wire/ibutton/protocols/protocol_group_defs.c b/lib/ibutton/protocols/protocol_group_defs.c similarity index 100% rename from lib/one_wire/ibutton/protocols/protocol_group_defs.c rename to lib/ibutton/protocols/protocol_group_defs.c diff --git a/lib/one_wire/ibutton/protocols/protocol_group_defs.h b/lib/ibutton/protocols/protocol_group_defs.h similarity index 100% rename from lib/one_wire/ibutton/protocols/protocol_group_defs.h rename to lib/ibutton/protocols/protocol_group_defs.h diff --git a/lib/one_wire/SConscript b/lib/one_wire/SConscript index 56d4759ebe1..8d73c9dbf01 100644 --- a/lib/one_wire/SConscript +++ b/lib/one_wire/SConscript @@ -11,9 +11,6 @@ env.Append( File("one_wire_host_timing.h"), File("one_wire_host.h"), File("one_wire_slave.h"), - File("ibutton/ibutton_key.h"), - File("ibutton/ibutton_worker.h"), - File("ibutton/ibutton_protocols.h"), File("maxim_crc.h"), ], ) From 445a1aa7b01d27b88b9cc0793e0a318d9829a3f7 Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Mon, 20 Mar 2023 20:30:57 +0400 Subject: [PATCH 480/824] SubGhz: fix Incorrect comparison in subghz_setting_get_hopper_frequency (#2518) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- lib/subghz/subghz_setting.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/subghz/subghz_setting.c b/lib/subghz/subghz_setting.c index 5d7ea0cecf8..656580043f0 100644 --- a/lib/subghz/subghz_setting.c +++ b/lib/subghz/subghz_setting.c @@ -541,7 +541,7 @@ uint32_t subghz_setting_get_frequency(SubGhzSetting* instance, size_t idx) { uint32_t subghz_setting_get_hopper_frequency(SubGhzSetting* instance, size_t idx) { furi_assert(instance); - if(idx < FrequencyList_size(instance->frequencies)) { + if(idx < FrequencyList_size(instance->hopper_frequencies)) { return *FrequencyList_get(instance->hopper_frequencies, idx); } else { return 0; From 0444a80f195b7f12b96b11e9da714c00c8a79d40 Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Mon, 20 Mar 2023 20:22:03 +0300 Subject: [PATCH 481/824] [FL-3134] BadUSB: Script interpreter refactoring (#2485) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Script command and character tables * Non-blocking stringdelay, docs update * altchar/altstring fix * Layout select UI fix * Remove debug print Co-authored-by: あく --- applications/main/bad_usb/bad_usb_app_i.h | 2 +- .../ducky_script.c} | 328 ++++++++++-------- .../ducky_script.h} | 27 +- .../bad_usb/helpers/ducky_script_commands.c | 177 ++++++++++ .../main/bad_usb/helpers/ducky_script_i.h | 69 ++++ .../bad_usb/helpers/ducky_script_keycodes.c | 79 +++++ applications/main/bad_usb/mnemonic.c | 327 ----------------- applications/main/bad_usb/mnemonic.h | 96 ----- .../bad_usb/scenes/bad_usb_scene_config.c | 2 +- .../scenes/bad_usb_scene_config_layout.c | 4 +- .../main/bad_usb/scenes/bad_usb_scene_work.c | 2 +- .../main/bad_usb/views/bad_usb_view.c | 9 +- .../main/bad_usb/views/bad_usb_view.h | 2 +- applications/services/gui/modules/submenu.c | 2 +- .../file_formats/BadUsbScriptFormat.md | 82 +++-- .../targets/f7/furi_hal/furi_hal_usb_hid.c | 3 - .../furi_hal_include/furi_hal_usb_hid.h | 5 + 17 files changed, 577 insertions(+), 639 deletions(-) rename applications/main/bad_usb/{bad_usb_script.c => helpers/ducky_script.c} (74%) rename applications/main/bad_usb/{bad_usb_script.h => helpers/ducky_script.h} (61%) create mode 100644 applications/main/bad_usb/helpers/ducky_script_commands.c create mode 100644 applications/main/bad_usb/helpers/ducky_script_i.h create mode 100644 applications/main/bad_usb/helpers/ducky_script_keycodes.c delete mode 100644 applications/main/bad_usb/mnemonic.c delete mode 100644 applications/main/bad_usb/mnemonic.h diff --git a/applications/main/bad_usb/bad_usb_app_i.h b/applications/main/bad_usb/bad_usb_app_i.h index 588c4c2d5f5..cf1c02ebc7f 100644 --- a/applications/main/bad_usb/bad_usb_app_i.h +++ b/applications/main/bad_usb/bad_usb_app_i.h @@ -2,7 +2,7 @@ #include "bad_usb_app.h" #include "scenes/bad_usb_scene.h" -#include "bad_usb_script.h" +#include "helpers/ducky_script.h" #include #include diff --git a/applications/main/bad_usb/bad_usb_script.c b/applications/main/bad_usb/helpers/ducky_script.c similarity index 74% rename from applications/main/bad_usb/bad_usb_script.c rename to applications/main/bad_usb/helpers/ducky_script.c index 12abc766aa8..0206b7d09fc 100644 --- a/applications/main/bad_usb/bad_usb_script.c +++ b/applications/main/bad_usb/helpers/ducky_script.c @@ -5,17 +5,13 @@ #include #include #include -#include "bad_usb_script.h" -#include "mnemonic.h" +#include "ducky_script.h" +#include "ducky_script_i.h" #include #define TAG "BadUSB" #define WORKER_TAG TAG "Worker" -#define SCRIPT_STATE_ERROR (-1) -#define SCRIPT_STATE_END (-2) -#define SCRIPT_STATE_NEXT_LINE (-3) - #define BADUSB_ASCII_TO_KEY(script, x) \ (((uint8_t)x < 128) ? (script->layout[(uint8_t)x]) : HID_KEYBOARD_NONE) @@ -26,87 +22,20 @@ typedef enum { WorkerEvtDisconnect = (1 << 3), } WorkerEvtFlags; -typedef struct { - char* name; - uint16_t keycode; -} DuckyKey; - -static const DuckyKey ducky_keys[] = { - {"CTRL-ALT", KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_ALT}, - {"CTRL-SHIFT", KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_SHIFT}, - {"ALT-SHIFT", KEY_MOD_LEFT_ALT | KEY_MOD_LEFT_SHIFT}, - {"ALT-GUI", KEY_MOD_LEFT_ALT | KEY_MOD_LEFT_GUI}, - {"GUI-SHIFT", KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT}, - {"GUI-CTRL", KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_CTRL}, - - {"CTRL", KEY_MOD_LEFT_CTRL}, - {"CONTROL", KEY_MOD_LEFT_CTRL}, - {"SHIFT", KEY_MOD_LEFT_SHIFT}, - {"ALT", KEY_MOD_LEFT_ALT}, - {"GUI", KEY_MOD_LEFT_GUI}, - {"WINDOWS", KEY_MOD_LEFT_GUI}, - - {"DOWNARROW", HID_KEYBOARD_DOWN_ARROW}, - {"DOWN", HID_KEYBOARD_DOWN_ARROW}, - {"LEFTARROW", HID_KEYBOARD_LEFT_ARROW}, - {"LEFT", HID_KEYBOARD_LEFT_ARROW}, - {"RIGHTARROW", HID_KEYBOARD_RIGHT_ARROW}, - {"RIGHT", HID_KEYBOARD_RIGHT_ARROW}, - {"UPARROW", HID_KEYBOARD_UP_ARROW}, - {"UP", HID_KEYBOARD_UP_ARROW}, - - {"ENTER", HID_KEYBOARD_RETURN}, - {"BREAK", HID_KEYBOARD_PAUSE}, - {"PAUSE", HID_KEYBOARD_PAUSE}, - {"CAPSLOCK", HID_KEYBOARD_CAPS_LOCK}, - {"DELETE", HID_KEYBOARD_DELETE_FORWARD}, - {"BACKSPACE", HID_KEYBOARD_DELETE}, - {"END", HID_KEYBOARD_END}, - {"ESC", HID_KEYBOARD_ESCAPE}, - {"ESCAPE", HID_KEYBOARD_ESCAPE}, - {"HOME", HID_KEYBOARD_HOME}, - {"INSERT", HID_KEYBOARD_INSERT}, - {"NUMLOCK", HID_KEYPAD_NUMLOCK}, - {"PAGEUP", HID_KEYBOARD_PAGE_UP}, - {"PAGEDOWN", HID_KEYBOARD_PAGE_DOWN}, - {"PRINTSCREEN", HID_KEYBOARD_PRINT_SCREEN}, - {"SCROLLLOCK", HID_KEYBOARD_SCROLL_LOCK}, - {"SPACE", HID_KEYBOARD_SPACEBAR}, - {"TAB", HID_KEYBOARD_TAB}, - {"MENU", HID_KEYBOARD_APPLICATION}, - {"APP", HID_KEYBOARD_APPLICATION}, - - {"F1", HID_KEYBOARD_F1}, - {"F2", HID_KEYBOARD_F2}, - {"F3", HID_KEYBOARD_F3}, - {"F4", HID_KEYBOARD_F4}, - {"F5", HID_KEYBOARD_F5}, - {"F6", HID_KEYBOARD_F6}, - {"F7", HID_KEYBOARD_F7}, - {"F8", HID_KEYBOARD_F8}, - {"F9", HID_KEYBOARD_F9}, - {"F10", HID_KEYBOARD_F10}, - {"F11", HID_KEYBOARD_F11}, - {"F12", HID_KEYBOARD_F12}, -}; - -static const char ducky_cmd_comment[] = {"REM"}; static const char ducky_cmd_id[] = {"ID"}; -static const char ducky_cmd_delay[] = {"DELAY "}; -static const char ducky_cmd_string[] = {"STRING "}; -static const char ducky_cmd_stringln[] = {"STRINGLN "}; -static const char ducky_cmd_defdelay_1[] = {"DEFAULT_DELAY "}; -static const char ducky_cmd_defdelay_2[] = {"DEFAULTDELAY "}; -static const char ducky_cmd_stringdelay_1[] = {"STRINGDELAY "}; -static const char ducky_cmd_stringdelay_2[] = {"STRING_DELAY "}; -static const char ducky_cmd_repeat[] = {"REPEAT "}; -static const char ducky_cmd_sysrq[] = {"SYSRQ "}; -static const char ducky_cmd_hold[] = {"HOLD "}; -static const char ducky_cmd_release[] = {"RELEASE "}; - -static const char ducky_cmd_altchar[] = {"ALTCHAR "}; -static const char ducky_cmd_altstr_1[] = {"ALTSTRING "}; -static const char ducky_cmd_altstr_2[] = {"ALTCODE "}; + +static const uint8_t numpad_keys[10] = { + HID_KEYPAD_0, + HID_KEYPAD_1, + HID_KEYPAD_2, + HID_KEYPAD_3, + HID_KEYPAD_4, + HID_KEYPAD_5, + HID_KEYPAD_6, + HID_KEYPAD_7, + HID_KEYPAD_8, + HID_KEYPAD_9, +}; uint32_t ducky_get_command_len(const char* line) { uint32_t len = strlen(line); @@ -121,76 +50,150 @@ bool ducky_is_line_end(const char chr) { } uint16_t ducky_get_keycode(BadUsbScript* bad_usb, const char* param, bool accept_chars) { - for(size_t i = 0; i < (sizeof(ducky_keys) / sizeof(ducky_keys[0])); i++) { - size_t key_cmd_len = strlen(ducky_keys[i].name); - if((strncmp(param, ducky_keys[i].name, key_cmd_len) == 0) && - (ducky_is_line_end(param[key_cmd_len]))) { - return ducky_keys[i].keycode; - } + uint16_t keycode = ducky_get_keycode_by_name(param); + if(keycode != HID_KEYBOARD_NONE) { + return keycode; } + if((accept_chars) && (strlen(param) > 0)) { return (BADUSB_ASCII_TO_KEY(bad_usb, param[0]) & 0xFF); } return 0; } -static int32_t - ducky_parse_line(BadUsbScript* bad_usb, FuriString* line, char* error, size_t error_len) { +bool ducky_get_number(const char* param, uint32_t* val) { + uint32_t value = 0; + if(sscanf(param, "%lu", &value) == 1) { + *val = value; + return true; + } + return false; +} + +void ducky_numlock_on() { + if((furi_hal_hid_get_led_state() & HID_KB_LED_NUM) == 0) { + furi_hal_hid_kb_press(HID_KEYBOARD_LOCK_NUM_LOCK); + furi_hal_hid_kb_release(HID_KEYBOARD_LOCK_NUM_LOCK); + } +} +bool ducky_numpad_press(const char num) { + if((num < '0') || (num > '9')) return false; + + uint16_t key = numpad_keys[num - '0']; + furi_hal_hid_kb_press(key); + furi_hal_hid_kb_release(key); + + return true; +} + +bool ducky_altchar(const char* charcode) { + uint8_t i = 0; + bool state = false; + + furi_hal_hid_kb_press(KEY_MOD_LEFT_ALT); + + while(!ducky_is_line_end(charcode[i])) { + state = ducky_numpad_press(charcode[i]); + if(state == false) break; + i++; + } + + furi_hal_hid_kb_release(KEY_MOD_LEFT_ALT); + return state; +} + +bool ducky_altstring(const char* param) { + uint32_t i = 0; + bool state = false; + + while(param[i] != '\0') { + if((param[i] < ' ') || (param[i] > '~')) { + i++; + continue; // Skip non-printable chars + } + + char temp_str[4]; + snprintf(temp_str, 4, "%u", param[i]); + + state = ducky_altchar(temp_str); + if(state == false) break; + i++; + } + return state; +} + +int32_t ducky_error(BadUsbScript* bad_usb, const char* text, ...) { + va_list args; + va_start(args, text); + + vsnprintf(bad_usb->st.error, sizeof(bad_usb->st.error), text, args); + + va_end(args); + return SCRIPT_STATE_ERROR; +} + +bool ducky_string(BadUsbScript* bad_usb, const char* param) { + uint32_t i = 0; + + while(param[i] != '\0') { + if(param[i] != '\n') { + uint16_t keycode = BADUSB_ASCII_TO_KEY(bad_usb, param[i]); + if(keycode != HID_KEYBOARD_NONE) { + furi_hal_hid_kb_press(keycode); + furi_hal_hid_kb_release(keycode); + } + } else { + furi_hal_hid_kb_press(HID_KEYBOARD_RETURN); + furi_hal_hid_kb_release(HID_KEYBOARD_RETURN); + } + i++; + } + bad_usb->stringdelay = 0; + return true; +} + +static bool ducky_string_next(BadUsbScript* bad_usb) { + if(bad_usb->string_print_pos >= furi_string_size(bad_usb->string_print)) { + return true; + } + + char print_char = furi_string_get_char(bad_usb->string_print, bad_usb->string_print_pos); + + if(print_char != '\n') { + uint16_t keycode = BADUSB_ASCII_TO_KEY(bad_usb, print_char); + if(keycode != HID_KEYBOARD_NONE) { + furi_hal_hid_kb_press(keycode); + furi_hal_hid_kb_release(keycode); + } + } else { + furi_hal_hid_kb_press(HID_KEYBOARD_RETURN); + furi_hal_hid_kb_release(HID_KEYBOARD_RETURN); + } + + bad_usb->string_print_pos++; + + return false; +} + +static int32_t ducky_parse_line(BadUsbScript* bad_usb, FuriString* line) { uint32_t line_len = furi_string_size(line); const char* line_tmp = furi_string_get_cstr(line); - const char* ducky_cmd_table[] = { - ducky_cmd_comment, - ducky_cmd_id, - ducky_cmd_delay, - ducky_cmd_string, - ducky_cmd_defdelay_1, - ducky_cmd_defdelay_2, - ducky_cmd_stringdelay_1, - ducky_cmd_stringdelay_2, - ducky_cmd_repeat, - ducky_cmd_sysrq, - ducky_cmd_altchar, - ducky_cmd_altstr_1, - ducky_cmd_altstr_2, - ducky_cmd_stringln, - ducky_cmd_hold, - ducky_cmd_release, - NULL}; - int32_t (*fnc_ptr[])(BadUsbScript*, FuriString*, const char*, char*, size_t) = { - &ducky_fnc_noop, - &ducky_fnc_noop, - &ducky_fnc_delay, - &ducky_fnc_string, - &ducky_fnc_defdelay, - &ducky_fnc_defdelay, - &ducky_fnc_strdelay, - &ducky_fnc_strdelay, - &ducky_fnc_repeat, - &ducky_fnc_sysrq, - &ducky_fnc_altchar, - &ducky_fnc_altstring, - &ducky_fnc_altstring, - &ducky_fnc_stringln, - &ducky_fnc_hold, - &ducky_fnc_release, - NULL}; if(line_len == 0) { return SCRIPT_STATE_NEXT_LINE; // Skip empty lines } FURI_LOG_D(WORKER_TAG, "line:%s", line_tmp); + // Ducky Lang Functions - for(size_t i = 0; ducky_cmd_table[i]; i++) { - if(strncmp(line_tmp, ducky_cmd_table[i], strlen(ducky_cmd_table[i])) == 0) - return ((fnc_ptr[i])(bad_usb, line, line_tmp, error, error_len)); + int32_t cmd_result = ducky_execute_cmd(bad_usb, line_tmp); + if(cmd_result != SCRIPT_STATE_CMD_UNKNOWN) { + return cmd_result; } + // Special keys + modifiers uint16_t key = ducky_get_keycode(bad_usb, line_tmp, false); if(key == HID_KEYBOARD_NONE) { - if(error != NULL) { - snprintf(error, error_len, "No keycode defined for %s", line_tmp); - } - return SCRIPT_STATE_ERROR; + return ducky_error(bad_usb, "No keycode defined for %s", line_tmp); } if((key & 0xFF00) != 0) { // It's a modifier key @@ -199,7 +202,7 @@ static int32_t } furi_hal_hid_kb_press(key); furi_hal_hid_kb_release(key); - return (0); + return 0; } static bool ducky_set_usb_id(BadUsbScript* bad_usb, const char* line) { @@ -277,8 +280,7 @@ static int32_t ducky_script_execute_next(BadUsbScript* bad_usb, File* script_fil if(bad_usb->repeat_cnt > 0) { bad_usb->repeat_cnt--; - delay_val = ducky_parse_line( - bad_usb, bad_usb->line_prev, bad_usb->st.error, sizeof(bad_usb->st.error)); + delay_val = ducky_parse_line(bad_usb, bad_usb->line_prev); if(delay_val == SCRIPT_STATE_NEXT_LINE) { // Empty line return 0; } else if(delay_val < 0) { // Script error @@ -313,10 +315,11 @@ static int32_t ducky_script_execute_next(BadUsbScript* bad_usb, File* script_fil bad_usb->buf_len = bad_usb->buf_len + bad_usb->buf_start - (i + 1); bad_usb->buf_start = i + 1; furi_string_trim(bad_usb->line); - delay_val = ducky_parse_line( - bad_usb, bad_usb->line, bad_usb->st.error, sizeof(bad_usb->st.error)); + delay_val = ducky_parse_line(bad_usb, bad_usb->line); if(delay_val == SCRIPT_STATE_NEXT_LINE) { // Empty line return 0; + } else if(delay_val == SCRIPT_STATE_STRING_START) { // Print string with delays + return delay_val; } else if(delay_val < 0) { bad_usb->st.error_line = bad_usb->st.line_cur; FURI_LOG_E(WORKER_TAG, "Unknown command at line %u", bad_usb->st.line_cur); @@ -339,10 +342,11 @@ static void bad_usb_hid_state_callback(bool state, void* context) { furi_assert(context); BadUsbScript* bad_usb = context; - if(state == true) + if(state == true) { furi_thread_flags_set(furi_thread_get_id(bad_usb->thread), WorkerEvtConnect); - else + } else { furi_thread_flags_set(furi_thread_get_id(bad_usb->thread), WorkerEvtDisconnect); + } } static uint32_t bad_usb_flags_get(uint32_t flags_mask, uint32_t timeout) { @@ -368,6 +372,7 @@ static int32_t bad_usb_worker(void* context) { File* script_file = storage_file_alloc(furi_record_open(RECORD_STORAGE)); bad_usb->line = furi_string_alloc(); bad_usb->line_prev = furi_string_alloc(); + bad_usb->string_print = furi_string_alloc(); furi_hal_hid_set_state_callback(bad_usb_hid_state_callback, bad_usb); @@ -420,6 +425,7 @@ static int32_t bad_usb_worker(void* context) { bad_usb->defdelay = 0; bad_usb->stringdelay = 0; bad_usb->repeat_cnt = 0; + bad_usb->key_hold_nb = 0; bad_usb->file_end = false; storage_file_seek(script_file, 0, true); worker_state = BadUsbStateRunning; @@ -492,12 +498,17 @@ static int32_t bad_usb_worker(void* context) { delay_val = 0; worker_state = BadUsbStateScriptError; bad_usb->st.state = worker_state; + furi_hal_hid_kb_release_all(); } else if(delay_val == SCRIPT_STATE_END) { // End of script delay_val = 0; worker_state = BadUsbStateIdle; bad_usb->st.state = BadUsbStateDone; furi_hal_hid_kb_release_all(); continue; + } else if(delay_val == SCRIPT_STATE_STRING_START) { // Start printing string with delays + delay_val = bad_usb->defdelay; + bad_usb->string_print_pos = 0; + worker_state = BadUsbStateStringDelay; } else if(delay_val > 1000) { bad_usb->st.state = BadUsbStateDelay; // Show long delays bad_usb->st.delay_remain = delay_val / 1000; @@ -505,7 +516,35 @@ static int32_t bad_usb_worker(void* context) { } else { furi_check((flags & FuriFlagError) == 0); } + } else if(worker_state == BadUsbStateStringDelay) { // State: print string with delays + uint32_t flags = furi_thread_flags_wait( + WorkerEvtEnd | WorkerEvtToggle | WorkerEvtDisconnect, + FuriFlagWaitAny, + bad_usb->stringdelay); + if(!(flags & FuriFlagError)) { + if(flags & WorkerEvtEnd) { + break; + } else if(flags & WorkerEvtToggle) { + worker_state = BadUsbStateIdle; // Stop executing script + furi_hal_hid_kb_release_all(); + } else if(flags & WorkerEvtDisconnect) { + worker_state = BadUsbStateNotConnected; // USB disconnected + furi_hal_hid_kb_release_all(); + } + bad_usb->st.state = worker_state; + continue; + } else if( + (flags == (unsigned)FuriFlagErrorTimeout) || + (flags == (unsigned)FuriFlagErrorResource)) { + bool string_end = ducky_string_next(bad_usb); + if(string_end) { + bad_usb->stringdelay = 0; + worker_state = BadUsbStateRunning; + } + } else { + furi_check((flags & FuriFlagError) == 0); + } } else if( (worker_state == BadUsbStateFileError) || (worker_state == BadUsbStateScriptError)) { // State: error @@ -524,6 +563,7 @@ static int32_t bad_usb_worker(void* context) { storage_file_free(script_file); furi_string_free(bad_usb->line); furi_string_free(bad_usb->line_prev); + furi_string_free(bad_usb->string_print); FURI_LOG_I(WORKER_TAG, "End"); diff --git a/applications/main/bad_usb/bad_usb_script.h b/applications/main/bad_usb/helpers/ducky_script.h similarity index 61% rename from applications/main/bad_usb/bad_usb_script.h rename to applications/main/bad_usb/helpers/ducky_script.h index fef2deaed08..0e616242a85 100644 --- a/applications/main/bad_usb/bad_usb_script.h +++ b/applications/main/bad_usb/helpers/ducky_script.h @@ -7,8 +7,6 @@ extern "C" { #include #include -#define FILE_BUFFER_LEN 16 - typedef enum { BadUsbStateInit, BadUsbStateNotConnected, @@ -16,6 +14,7 @@ typedef enum { BadUsbStateWillRun, BadUsbStateRunning, BadUsbStateDelay, + BadUsbStateStringDelay, BadUsbStateDone, BadUsbStateScriptError, BadUsbStateFileError, @@ -30,23 +29,7 @@ typedef struct { char error[64]; } BadUsbState; -typedef struct BadUsbScript { - FuriHalUsbHidConfig hid_cfg; - BadUsbState st; - FuriString* file_path; - uint32_t defdelay; - uint16_t layout[128]; - uint32_t stringdelay; - FuriThread* thread; - uint8_t file_buf[FILE_BUFFER_LEN + 1]; - uint8_t buf_start; - uint8_t buf_len; - bool file_end; - FuriString* line; - - FuriString* line_prev; - uint32_t repeat_cnt; -} BadUsbScript; +typedef struct BadUsbScript BadUsbScript; BadUsbScript* bad_usb_script_open(FuriString* file_path); @@ -62,12 +45,6 @@ void bad_usb_script_toggle(BadUsbScript* bad_usb); BadUsbState* bad_usb_script_get_state(BadUsbScript* bad_usb); -uint16_t ducky_get_keycode(BadUsbScript* bad_usb, const char* param, bool accept_chars); - -uint32_t ducky_get_command_len(const char* line); - -bool ducky_is_line_end(const char chr); - #ifdef __cplusplus } #endif diff --git a/applications/main/bad_usb/helpers/ducky_script_commands.c b/applications/main/bad_usb/helpers/ducky_script_commands.c new file mode 100644 index 00000000000..f3de43c1bdb --- /dev/null +++ b/applications/main/bad_usb/helpers/ducky_script_commands.c @@ -0,0 +1,177 @@ +#include +#include +#include "ducky_script.h" +#include "ducky_script_i.h" + +typedef int32_t (*DuckyCmdCallback)(BadUsbScript* bad_usb, const char* line, int32_t param); + +typedef struct { + char* name; + DuckyCmdCallback callback; + int32_t param; +} DuckyCmd; + +static int32_t ducky_fnc_delay(BadUsbScript* bad_usb, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + uint32_t delay_val = 0; + bool state = ducky_get_number(line, &delay_val); + if((state) && (delay_val > 0)) { + return (int32_t)delay_val; + } + + return ducky_error(bad_usb, "Invalid number %s", line); +} + +static int32_t ducky_fnc_defdelay(BadUsbScript* bad_usb, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + bool state = ducky_get_number(line, &bad_usb->defdelay); + if(!state) { + return ducky_error(bad_usb, "Invalid number %s", line); + } + return 0; +} + +static int32_t ducky_fnc_strdelay(BadUsbScript* bad_usb, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + bool state = ducky_get_number(line, &bad_usb->stringdelay); + if(!state) { + return ducky_error(bad_usb, "Invalid number %s", line); + } + return 0; +} + +static int32_t ducky_fnc_string(BadUsbScript* bad_usb, const char* line, int32_t param) { + line = &line[ducky_get_command_len(line) + 1]; + furi_string_set_str(bad_usb->string_print, line); + if(param == 1) { + furi_string_cat(bad_usb->string_print, "\n"); + } + + if(bad_usb->stringdelay == 0) { // stringdelay not set - run command immidiately + bool state = ducky_string(bad_usb, furi_string_get_cstr(bad_usb->string_print)); + if(!state) { + return ducky_error(bad_usb, "Invalid string %s", line); + } + } else { // stringdelay is set - run command in thread to keep handling external events + return SCRIPT_STATE_STRING_START; + } + + return 0; +} + +static int32_t ducky_fnc_repeat(BadUsbScript* bad_usb, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + bool state = ducky_get_number(line, &bad_usb->repeat_cnt); + if((!state) || (bad_usb->repeat_cnt == 0)) { + return ducky_error(bad_usb, "Invalid number %s", line); + } + return 0; +} + +static int32_t ducky_fnc_sysrq(BadUsbScript* bad_usb, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + uint16_t key = ducky_get_keycode(bad_usb, line, true); + furi_hal_hid_kb_press(KEY_MOD_LEFT_ALT | HID_KEYBOARD_PRINT_SCREEN); + furi_hal_hid_kb_press(key); + furi_hal_hid_kb_release_all(); + return 0; +} + +static int32_t ducky_fnc_altchar(BadUsbScript* bad_usb, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + ducky_numlock_on(); + bool state = ducky_altchar(line); + if(!state) { + return ducky_error(bad_usb, "Invalid altchar %s", line); + } + return 0; +} + +static int32_t ducky_fnc_altstring(BadUsbScript* bad_usb, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + ducky_numlock_on(); + bool state = ducky_altstring(line); + if(!state) { + return ducky_error(bad_usb, "Invalid altstring %s", line); + } + return 0; +} + +static int32_t ducky_fnc_hold(BadUsbScript* bad_usb, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + uint16_t key = ducky_get_keycode(bad_usb, line, true); + if(key == HID_KEYBOARD_NONE) { + return ducky_error(bad_usb, "No keycode defined for %s", line); + } + bad_usb->key_hold_nb++; + if(bad_usb->key_hold_nb > (HID_KB_MAX_KEYS - 1)) { + return ducky_error(bad_usb, "Too many keys are hold"); + } + furi_hal_hid_kb_press(key); + return 0; +} + +static int32_t ducky_fnc_release(BadUsbScript* bad_usb, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + uint16_t key = ducky_get_keycode(bad_usb, line, true); + if(key == HID_KEYBOARD_NONE) { + return ducky_error(bad_usb, "No keycode defined for %s", line); + } + if(bad_usb->key_hold_nb == 0) { + return ducky_error(bad_usb, "No keys are hold"); + } + bad_usb->key_hold_nb--; + furi_hal_hid_kb_release(key); + return 0; +} + +static const DuckyCmd ducky_commands[] = { + {"REM ", NULL, -1}, + {"ID ", NULL, -1}, + {"DELAY ", ducky_fnc_delay, -1}, + {"STRING ", ducky_fnc_string, 0}, + {"STRINGLN ", ducky_fnc_string, 1}, + {"DEFAULT_DELAY ", ducky_fnc_defdelay, -1}, + {"DEFAULTDELAY ", ducky_fnc_defdelay, -1}, + {"STRINGDELAY ", ducky_fnc_strdelay, -1}, + {"STRING_DELAY ", ducky_fnc_strdelay, -1}, + {"REPEAT ", ducky_fnc_repeat, -1}, + {"SYSRQ ", ducky_fnc_sysrq, -1}, + {"ALTCHAR ", ducky_fnc_altchar, -1}, + {"ALTSTRING ", ducky_fnc_altstring, -1}, + {"ALTCODE ", ducky_fnc_altstring, -1}, + {"HOLD ", ducky_fnc_hold, -1}, + {"RELEASE ", ducky_fnc_release, -1}, +}; + +int32_t ducky_execute_cmd(BadUsbScript* bad_usb, const char* line) { + for(size_t i = 0; i < COUNT_OF(ducky_commands); i++) { + if(strncmp(line, ducky_commands[i].name, strlen(ducky_commands[i].name)) == 0) { + if(ducky_commands[i].callback == NULL) { + return 0; + } else { + return ((ducky_commands[i].callback)(bad_usb, line, ducky_commands[i].param)); + } + } + } + + return SCRIPT_STATE_CMD_UNKNOWN; +} diff --git a/applications/main/bad_usb/helpers/ducky_script_i.h b/applications/main/bad_usb/helpers/ducky_script_i.h new file mode 100644 index 00000000000..0cda0fa2c21 --- /dev/null +++ b/applications/main/bad_usb/helpers/ducky_script_i.h @@ -0,0 +1,69 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include "ducky_script.h" + +#define SCRIPT_STATE_ERROR (-1) +#define SCRIPT_STATE_END (-2) +#define SCRIPT_STATE_NEXT_LINE (-3) +#define SCRIPT_STATE_CMD_UNKNOWN (-4) +#define SCRIPT_STATE_STRING_START (-5) + +#define FILE_BUFFER_LEN 16 + +struct BadUsbScript { + FuriHalUsbHidConfig hid_cfg; + FuriThread* thread; + BadUsbState st; + + FuriString* file_path; + uint8_t file_buf[FILE_BUFFER_LEN + 1]; + uint8_t buf_start; + uint8_t buf_len; + bool file_end; + + uint32_t defdelay; + uint32_t stringdelay; + uint16_t layout[128]; + + FuriString* line; + FuriString* line_prev; + uint32_t repeat_cnt; + uint8_t key_hold_nb; + + FuriString* string_print; + size_t string_print_pos; +}; + +uint16_t ducky_get_keycode(BadUsbScript* bad_usb, const char* param, bool accept_chars); + +uint32_t ducky_get_command_len(const char* line); + +bool ducky_is_line_end(const char chr); + +uint16_t ducky_get_keycode_by_name(const char* param); + +bool ducky_get_number(const char* param, uint32_t* val); + +void ducky_numlock_on(void); + +bool ducky_numpad_press(const char num); + +bool ducky_altchar(const char* charcode); + +bool ducky_altstring(const char* param); + +bool ducky_string(BadUsbScript* bad_usb, const char* param); + +int32_t ducky_execute_cmd(BadUsbScript* bad_usb, const char* line); + +int32_t ducky_error(BadUsbScript* bad_usb, const char* text, ...); + +#ifdef __cplusplus +} +#endif diff --git a/applications/main/bad_usb/helpers/ducky_script_keycodes.c b/applications/main/bad_usb/helpers/ducky_script_keycodes.c new file mode 100644 index 00000000000..da2fc22f79f --- /dev/null +++ b/applications/main/bad_usb/helpers/ducky_script_keycodes.c @@ -0,0 +1,79 @@ +#include +#include +#include "ducky_script_i.h" + +typedef struct { + char* name; + uint16_t keycode; +} DuckyKey; + +static const DuckyKey ducky_keys[] = { + {"CTRL-ALT", KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_ALT}, + {"CTRL-SHIFT", KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_SHIFT}, + {"ALT-SHIFT", KEY_MOD_LEFT_ALT | KEY_MOD_LEFT_SHIFT}, + {"ALT-GUI", KEY_MOD_LEFT_ALT | KEY_MOD_LEFT_GUI}, + {"GUI-SHIFT", KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT}, + {"GUI-CTRL", KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_CTRL}, + + {"CTRL", KEY_MOD_LEFT_CTRL}, + {"CONTROL", KEY_MOD_LEFT_CTRL}, + {"SHIFT", KEY_MOD_LEFT_SHIFT}, + {"ALT", KEY_MOD_LEFT_ALT}, + {"GUI", KEY_MOD_LEFT_GUI}, + {"WINDOWS", KEY_MOD_LEFT_GUI}, + + {"DOWNARROW", HID_KEYBOARD_DOWN_ARROW}, + {"DOWN", HID_KEYBOARD_DOWN_ARROW}, + {"LEFTARROW", HID_KEYBOARD_LEFT_ARROW}, + {"LEFT", HID_KEYBOARD_LEFT_ARROW}, + {"RIGHTARROW", HID_KEYBOARD_RIGHT_ARROW}, + {"RIGHT", HID_KEYBOARD_RIGHT_ARROW}, + {"UPARROW", HID_KEYBOARD_UP_ARROW}, + {"UP", HID_KEYBOARD_UP_ARROW}, + + {"ENTER", HID_KEYBOARD_RETURN}, + {"BREAK", HID_KEYBOARD_PAUSE}, + {"PAUSE", HID_KEYBOARD_PAUSE}, + {"CAPSLOCK", HID_KEYBOARD_CAPS_LOCK}, + {"DELETE", HID_KEYBOARD_DELETE_FORWARD}, + {"BACKSPACE", HID_KEYBOARD_DELETE}, + {"END", HID_KEYBOARD_END}, + {"ESC", HID_KEYBOARD_ESCAPE}, + {"ESCAPE", HID_KEYBOARD_ESCAPE}, + {"HOME", HID_KEYBOARD_HOME}, + {"INSERT", HID_KEYBOARD_INSERT}, + {"NUMLOCK", HID_KEYPAD_NUMLOCK}, + {"PAGEUP", HID_KEYBOARD_PAGE_UP}, + {"PAGEDOWN", HID_KEYBOARD_PAGE_DOWN}, + {"PRINTSCREEN", HID_KEYBOARD_PRINT_SCREEN}, + {"SCROLLLOCK", HID_KEYBOARD_SCROLL_LOCK}, + {"SPACE", HID_KEYBOARD_SPACEBAR}, + {"TAB", HID_KEYBOARD_TAB}, + {"MENU", HID_KEYBOARD_APPLICATION}, + {"APP", HID_KEYBOARD_APPLICATION}, + + {"F1", HID_KEYBOARD_F1}, + {"F2", HID_KEYBOARD_F2}, + {"F3", HID_KEYBOARD_F3}, + {"F4", HID_KEYBOARD_F4}, + {"F5", HID_KEYBOARD_F5}, + {"F6", HID_KEYBOARD_F6}, + {"F7", HID_KEYBOARD_F7}, + {"F8", HID_KEYBOARD_F8}, + {"F9", HID_KEYBOARD_F9}, + {"F10", HID_KEYBOARD_F10}, + {"F11", HID_KEYBOARD_F11}, + {"F12", HID_KEYBOARD_F12}, +}; + +uint16_t ducky_get_keycode_by_name(const char* param) { + for(size_t i = 0; i < COUNT_OF(ducky_keys); i++) { + size_t key_cmd_len = strlen(ducky_keys[i].name); + if((strncmp(param, ducky_keys[i].name, key_cmd_len) == 0) && + (ducky_is_line_end(param[key_cmd_len]))) { + return ducky_keys[i].keycode; + } + } + + return HID_KEYBOARD_NONE; +} diff --git a/applications/main/bad_usb/mnemonic.c b/applications/main/bad_usb/mnemonic.c deleted file mode 100644 index f21cc98bb5b..00000000000 --- a/applications/main/bad_usb/mnemonic.c +++ /dev/null @@ -1,327 +0,0 @@ -#include -#include -#include "mnemonic.h" - -#define TAG "BadUSB" -#define WORKER_TAG TAG "Worker" - -#define FILE_BUFFER_LEN 16 -#define SCRIPT_STATE_ERROR (-1) -#define SCRIPT_STATE_END (-2) -#define SCRIPT_STATE_NEXT_LINE (-3) - -#define BADUSB_ASCII_TO_KEY(script, x) \ - (((uint8_t)x < 128) ? (script->layout[(uint8_t)x]) : HID_KEYBOARD_NONE) - -static const uint8_t numpad_keys[10] = { - HID_KEYPAD_0, - HID_KEYPAD_1, - HID_KEYPAD_2, - HID_KEYPAD_3, - HID_KEYPAD_4, - HID_KEYPAD_5, - HID_KEYPAD_6, - HID_KEYPAD_7, - HID_KEYPAD_8, - HID_KEYPAD_9, -}; - -static bool ducky_get_number(const char* param, uint32_t* val) { - uint32_t value = 0; - if(sscanf(param, "%lu", &value) == 1) { - *val = value; - return true; - } - return false; -} - -static void ducky_numlock_on() { - if((furi_hal_hid_get_led_state() & HID_KB_LED_NUM) == 0) { - furi_hal_hid_kb_press(HID_KEYBOARD_LOCK_NUM_LOCK); - furi_hal_hid_kb_release(HID_KEYBOARD_LOCK_NUM_LOCK); - } -} -static bool ducky_numpad_press(const char num) { - if((num < '0') || (num > '9')) return false; - - uint16_t key = numpad_keys[num - '0']; - furi_hal_hid_kb_press(key); - furi_hal_hid_kb_release(key); - - return true; -} - -static bool ducky_altchar(const char* charcode) { - uint8_t i = 0; - bool state = false; - - FURI_LOG_I(WORKER_TAG, "char %s", charcode); - - furi_hal_hid_kb_press(KEY_MOD_LEFT_ALT); - - while(!ducky_is_line_end(charcode[i])) { - state = ducky_numpad_press(charcode[i]); - if(state == false) break; - i++; - } - - furi_hal_hid_kb_release(KEY_MOD_LEFT_ALT); - return state; -} - -static bool ducky_altstring(const char* param) { - uint32_t i = 0; - bool state = false; - - while(param[i] != '\0') { - if((param[i] < ' ') || (param[i] > '~')) { - i++; - continue; // Skip non-printable chars - } - - char temp_str[4]; - snprintf(temp_str, 4, "%u", param[i]); - - state = ducky_altchar(temp_str); - if(state == false) break; - i++; - } - return state; -} - -static bool ducky_string(BadUsbScript* bad_usb, const char* param) { - uint32_t i = 0; - - while(param[i] != '\0') { - uint16_t keycode = BADUSB_ASCII_TO_KEY(bad_usb, param[i]); - if(keycode != HID_KEYBOARD_NONE) { - furi_hal_hid_kb_press(keycode); - furi_hal_hid_kb_release(keycode); - if(bad_usb->stringdelay > 0) { - furi_delay_ms(bad_usb->stringdelay); - } - } - i++; - } - bad_usb->stringdelay = 0; - return true; -} - -int32_t ducky_fnc_noop( - BadUsbScript* bad_usb, - FuriString* line, - const char* line_tmp, - char* error, - size_t error_len) { - (void)bad_usb; - (void)line; - (void)line_tmp; - (void)error; - (void)error_len; - return (0); -} - -int32_t ducky_fnc_delay( - BadUsbScript* bad_usb, - FuriString* line, - const char* line_tmp, - char* error, - size_t error_len) { - bool state = false; - (void)bad_usb; - (void)line; - - line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; - uint32_t delay_val = 0; - state = ducky_get_number(line_tmp, &delay_val); - if((state) && (delay_val > 0)) { - return (int32_t)delay_val; - } - if(error != NULL) { - snprintf(error, error_len, "Invalid number %s", line_tmp); - } - return SCRIPT_STATE_ERROR; -} - -int32_t ducky_fnc_defdelay( - BadUsbScript* bad_usb, - FuriString* line, - const char* line_tmp, - char* error, - size_t error_len) { - bool state = false; - (void)line; - - line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; - state = ducky_get_number(line_tmp, &bad_usb->defdelay); - if(!state && error != NULL) { - snprintf(error, error_len, "Invalid number %s", line_tmp); - } - return (state) ? (0) : SCRIPT_STATE_ERROR; -} - -int32_t ducky_fnc_strdelay( - BadUsbScript* bad_usb, - FuriString* line, - const char* line_tmp, - char* error, - size_t error_len) { - bool state = false; - (void)line; - - line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; - state = ducky_get_number(line_tmp, &bad_usb->stringdelay); - if((state) && (bad_usb->stringdelay > 0)) { - return state; - } - if(error != NULL) { - snprintf(error, error_len, "Invalid number %s", line_tmp); - } - return SCRIPT_STATE_ERROR; -} - -int32_t ducky_fnc_string( - BadUsbScript* bad_usb, - FuriString* line, - const char* line_tmp, - char* error, - size_t error_len) { - bool state = false; - (void)line; - - line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; - state = ducky_string(bad_usb, line_tmp); - if(!state && error != NULL) { - snprintf(error, error_len, "Invalid string %s", line_tmp); - } - return (state) ? (0) : SCRIPT_STATE_ERROR; -} - -int32_t ducky_fnc_repeat( - BadUsbScript* bad_usb, - FuriString* line, - const char* line_tmp, - char* error, - size_t error_len) { - bool state = false; - (void)line; - - line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; - state = ducky_get_number(line_tmp, &bad_usb->repeat_cnt); - if(!state && error != NULL) { - snprintf(error, error_len, "Invalid number %s", line_tmp); - } - return (state) ? (0) : SCRIPT_STATE_ERROR; -} - -int32_t ducky_fnc_sysrq( - BadUsbScript* bad_usb, - FuriString* line, - const char* line_tmp, - char* error, - size_t error_len) { - (void)error; - (void)error_len; - (void)line; - line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; - uint16_t key = ducky_get_keycode(bad_usb, line_tmp, true); - furi_hal_hid_kb_press(KEY_MOD_LEFT_ALT | HID_KEYBOARD_PRINT_SCREEN); - furi_hal_hid_kb_press(key); - furi_hal_hid_kb_release_all(); - return (0); -} - -int32_t ducky_fnc_altchar( - BadUsbScript* bad_usb, - FuriString* line, - const char* line_tmp, - char* error, - size_t error_len) { - bool state = false; - (void)bad_usb; - (void)line; - - line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; - ducky_numlock_on(); - state = ducky_altchar(line_tmp); - if(!state && error != NULL) { - snprintf(error, error_len, "Invalid altchar %s", line_tmp); - } - return (state) ? (0) : SCRIPT_STATE_ERROR; -} - -int32_t ducky_fnc_altstring( - BadUsbScript* bad_usb, - FuriString* line, - const char* line_tmp, - char* error, - size_t error_len) { - bool state = false; - (void)bad_usb; - (void)line; - - line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; - ducky_numlock_on(); - state = ducky_altstring(line_tmp); - if(!state && error != NULL) { - snprintf(error, error_len, "Invalid altstring %s", line_tmp); - } - return (state) ? (0) : SCRIPT_STATE_ERROR; -} - -int32_t ducky_fnc_stringln( - BadUsbScript* bad_usb, - FuriString* line, - const char* line_tmp, - char* error, - size_t error_len) { - bool state = false; - (void)line; - - line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; - state = ducky_string(bad_usb, line_tmp); - if(!state && error != NULL) { - snprintf(error, error_len, "Invalid string %s", line_tmp); - } - furi_hal_hid_kb_press(HID_KEYBOARD_RETURN); - furi_hal_hid_kb_release(HID_KEYBOARD_RETURN); - return (state) ? (0) : SCRIPT_STATE_ERROR; -} - -int32_t ducky_fnc_hold( - BadUsbScript* bad_usb, - FuriString* line, - const char* line_tmp, - char* error, - size_t error_len) { - (void)line; - line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; - uint16_t key = ducky_get_keycode(bad_usb, line_tmp, true); - if(key == HID_KEYBOARD_NONE) { - if(error != NULL) { - snprintf(error, error_len, "No keycode defined for %s", line_tmp); - } - return SCRIPT_STATE_ERROR; - } - furi_hal_hid_kb_press(key); - return (0); -} - -int32_t ducky_fnc_release( - BadUsbScript* bad_usb, - FuriString* line, - const char* line_tmp, - char* error, - size_t error_len) { - (void)line; - line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; - uint16_t key = ducky_get_keycode(bad_usb, line_tmp, true); - if(key == HID_KEYBOARD_NONE) { - if(error != NULL) { - snprintf(error, error_len, "No keycode defined for %s", line_tmp); - } - return SCRIPT_STATE_ERROR; - } - furi_hal_hid_kb_release(key); - return (0); -} diff --git a/applications/main/bad_usb/mnemonic.h b/applications/main/bad_usb/mnemonic.h deleted file mode 100644 index a85627c3a1b..00000000000 --- a/applications/main/bad_usb/mnemonic.h +++ /dev/null @@ -1,96 +0,0 @@ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#include "bad_usb_script.h" - -// A no opperation function -int32_t ducky_fnc_noop( - BadUsbScript* bad_usb, - FuriString* line, - const char* line_tmp, - char* error, - size_t error_len); -// DELAY -int32_t ducky_fnc_delay( - BadUsbScript* bad_usb, - FuriString* line, - const char* line_tmp, - char* error, - size_t error_len); -// DEFAULTDELAY -int32_t ducky_fnc_defdelay( - BadUsbScript* bad_usb, - FuriString* line, - const char* line_tmp, - char* error, - size_t error_len); -// STRINGDELAY -int32_t ducky_fnc_strdelay( - BadUsbScript* bad_usb, - FuriString* line, - const char* line_tmp, - char* error, - size_t error_len); -// STRING -int32_t ducky_fnc_string( - BadUsbScript* bad_usb, - FuriString* line, - const char* line_tmp, - char* error, - size_t error_len); -// STRINGLN -int32_t ducky_fnc_stringln( - BadUsbScript* bad_usb, - FuriString* line, - const char* line_tmp, - char* error, - size_t error_len); -// REPEAT -int32_t ducky_fnc_repeat( - BadUsbScript* bad_usb, - FuriString* line, - const char* line_tmp, - char* error, - size_t error_len); -// SYSRQ -int32_t ducky_fnc_sysrq( - BadUsbScript* bad_usb, - FuriString* line, - const char* line_tmp, - char* error, - size_t error_len); -// ALTCHAR -int32_t ducky_fnc_altchar( - BadUsbScript* bad_usb, - FuriString* line, - const char* line_tmp, - char* error, - size_t error_len); -// ALTSTRING -int32_t ducky_fnc_altstring( - BadUsbScript* bad_usb, - FuriString* line, - const char* line_tmp, - char* error, - size_t error_len); -// HOLD -int32_t ducky_fnc_hold( - BadUsbScript* bad_usb, - FuriString* line, - const char* line_tmp, - char* error, - size_t error_len); -// RELEASE -int32_t ducky_fnc_release( - BadUsbScript* bad_usb, - FuriString* line, - const char* line_tmp, - char* error, - size_t error_len); - -#ifdef __cplusplus -} -#endif diff --git a/applications/main/bad_usb/scenes/bad_usb_scene_config.c b/applications/main/bad_usb/scenes/bad_usb_scene_config.c index c88cae03242..5477c5e8072 100644 --- a/applications/main/bad_usb/scenes/bad_usb_scene_config.c +++ b/applications/main/bad_usb/scenes/bad_usb_scene_config.c @@ -17,7 +17,7 @@ void bad_usb_scene_config_on_enter(void* context) { submenu_add_item( submenu, - "Keyboard Layout", + "Keyboard Layout (global)", SubmenuIndexKeyboardLayout, bad_usb_scene_config_submenu_callback, bad_usb); diff --git a/applications/main/bad_usb/scenes/bad_usb_scene_config_layout.c b/applications/main/bad_usb/scenes/bad_usb_scene_config_layout.c index 7708ed1d834..5d70b801b25 100644 --- a/applications/main/bad_usb/scenes/bad_usb_scene_config_layout.c +++ b/applications/main/bad_usb/scenes/bad_usb_scene_config_layout.c @@ -33,8 +33,10 @@ void bad_usb_scene_config_layout_on_enter(void* context) { if(bad_usb_layout_select(bad_usb)) { bad_usb_script_set_keyboard_layout(bad_usb->bad_usb_script, bad_usb->keyboard_layout); + scene_manager_search_and_switch_to_previous_scene(bad_usb->scene_manager, BadUsbSceneWork); + } else { + scene_manager_previous_scene(bad_usb->scene_manager); } - scene_manager_previous_scene(bad_usb->scene_manager); } bool bad_usb_scene_config_layout_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/main/bad_usb/scenes/bad_usb_scene_work.c b/applications/main/bad_usb/scenes/bad_usb_scene_work.c index 6f2b8269356..afc2e6f6f13 100644 --- a/applications/main/bad_usb/scenes/bad_usb_scene_work.c +++ b/applications/main/bad_usb/scenes/bad_usb_scene_work.c @@ -1,4 +1,4 @@ -#include "../bad_usb_script.h" +#include "../helpers/ducky_script.h" #include "../bad_usb_app_i.h" #include "../views/bad_usb_view.h" #include diff --git a/applications/main/bad_usb/views/bad_usb_view.c b/applications/main/bad_usb/views/bad_usb_view.c index 9ee9dc341c6..874d677c88e 100644 --- a/applications/main/bad_usb/views/bad_usb_view.c +++ b/applications/main/bad_usb/views/bad_usb_view.c @@ -1,5 +1,5 @@ #include "bad_usb_view.h" -#include "../bad_usb_script.h" +#include "../helpers/ducky_script.h" #include #include #include @@ -79,7 +79,12 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) { canvas_draw_str_aligned( canvas, 127, 46, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); furi_string_reset(disp_str); - canvas_draw_str_aligned(canvas, 127, 56, AlignRight, AlignBottom, model->state.error); + + furi_string_set_str(disp_str, model->state.error); + elements_string_fit_width(canvas, disp_str, canvas_width(canvas)); + canvas_draw_str_aligned( + canvas, 127, 56, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); + furi_string_reset(disp_str); } else if(model->state.state == BadUsbStateIdle) { canvas_draw_icon(canvas, 4, 26, &I_Smile_18x18); canvas_set_font(canvas, FontBigNumbers); diff --git a/applications/main/bad_usb/views/bad_usb_view.h b/applications/main/bad_usb/views/bad_usb_view.h index 2fc01688aaf..6210835f016 100644 --- a/applications/main/bad_usb/views/bad_usb_view.h +++ b/applications/main/bad_usb/views/bad_usb_view.h @@ -1,7 +1,7 @@ #pragma once #include -#include "../bad_usb_script.h" +#include "../helpers/ducky_script.h" typedef struct BadUsb BadUsb; typedef void (*BadUsbButtonCallback)(InputKey key, void* context); diff --git a/applications/services/gui/modules/submenu.c b/applications/services/gui/modules/submenu.c index 00e4d68b5ac..9d81c30b688 100644 --- a/applications/services/gui/modules/submenu.c +++ b/applications/services/gui/modules/submenu.c @@ -98,7 +98,7 @@ static void submenu_view_draw_callback(Canvas* canvas, void* _model) { FuriString* disp_str; disp_str = furi_string_alloc_set(SubmenuItemArray_cref(it)->label); - elements_string_fit_width(canvas, disp_str, item_width - 20); + elements_string_fit_width(canvas, disp_str, item_width - (6 * 2)); canvas_draw_str( canvas, diff --git a/documentation/file_formats/BadUsbScriptFormat.md b/documentation/file_formats/BadUsbScriptFormat.md index 94dee59439c..8373bf6882c 100644 --- a/documentation/file_formats/BadUsbScriptFormat.md +++ b/documentation/file_formats/BadUsbScriptFormat.md @@ -11,18 +11,18 @@ BadUsb app can execute only text scrips from `.txt` files, no compilation is req ## Comment line Just a single comment line. The interpreter will ignore all text after the REM command. -|Command|Parameters|Notes| -|-|-|-| -|REM|Comment text|| +| Command | Parameters | Notes | +| ------- | ------------ | ----- | +| REM | Comment text | | ## Delay Pause script execution by a defined time. -|Command|Parameters|Notes| -|-|-|-| -|DELAY|Delay value in ms|Single delay| -|DEFAULT_DELAY|Delay value in ms|Add delay before every next command| -|DEFAULTDELAY|Delay value in ms|Same as DEFAULT_DELAY| +| Command | Parameters | Notes | +| ------------- | ----------------- | ----------------------------------- | +| DELAY | Delay value in ms | Single delay | +| DEFAULT_DELAY | Delay value in ms | Add delay before every next command | +| DEFAULTDELAY | Delay value in ms | Same as DEFAULT_DELAY | ## Special keys @@ -56,32 +56,42 @@ Pause script execution by a defined time. ## Modifier keys Can be combined with a special key command or a single character. -|Command|Notes| -|-|-| -|CONTROL / CTRL|| -|SHIFT|| -|ALT|| -|WINDOWS / GUI|| -|CTRL-ALT|CTRL+ALT| -|CTRL-SHIFT|CTRL+SHIFT| -|ALT-SHIFT|ALT+SHIFT| -|ALT-GUI|ALT+WIN| -|GUI-SHIFT|WIN+SHIFT| -|GUI-CTRL|WIN+CTRL| +| Command | Notes | +| -------------- | ---------- | +| CONTROL / CTRL | | +| SHIFT | | +| ALT | | +| WINDOWS / GUI | | +| CTRL-ALT | CTRL+ALT | +| CTRL-SHIFT | CTRL+SHIFT | +| ALT-SHIFT | ALT+SHIFT | +| ALT-GUI | ALT+WIN | +| GUI-SHIFT | WIN+SHIFT | +| GUI-CTRL | WIN+CTRL | + +## Key hold and release + +Up to 5 keys can be hold simultaneously. +| Command | Parameters | Notes | +| ------- | ------------------------------- | ----------------------------------------- | +| HOLD | Special key or single character | Press and hold key untill RELEASE command | +| RELEASE | Special key or single character | Release key | + ## String -| Command | Parameters | Notes | -| ------- | ----------- | ----------------- | -| STRING | Text string | Print text string | +| Command | Parameters | Notes | +| ------- | ----------- | ----------------- | +| STRING | Text string | Print text string | +| STRINGLN | Text string | Print text string and press enter after it | ## String delay Delay between keypresses. -|Command|Parameters|Notes| -|-|-|-| -|STRING_DELAY|Delay value in ms|Applied once to next appearing string| -|STRINGDELAY|Delay value in ms|Same as STRING_DELAY| +| Command | Parameters | Notes | +| ------------ | ----------------- | --------------------------------------------- | +| STRING_DELAY | Delay value in ms | Applied once to next appearing STRING command | +| STRINGDELAY | Delay value in ms | Same as STRING_DELAY | ## Repeat @@ -91,19 +101,19 @@ Delay between keypresses. ## ALT+Numpad input -On Windows and some Linux systems, you can print characters by pressing `ALT` key and entering its code on Numpad. -|Command|Parameters|Notes| -|-|-|-| -|ALTCHAR|Character code|Print single character| -|ALTSTRING|Text string|Print text string using ALT+Numpad method| -|ALTCODE|Text string|Same as ALTSTRING, presents in some Duckyscript implementations| +On Windows and some Linux systems, you can print characters by holding `ALT` key and entering its code on Numpad. +| Command | Parameters | Notes | +| --------- | -------------- | --------------------------------------------------------------- | +| ALTCHAR | Character code | Print single character | +| ALTSTRING | Text string | Print text string using ALT+Numpad method | +| ALTCODE | Text string | Same as ALTSTRING, presents in some Duckyscript implementations | ## SysRq Send [SysRq command](https://en.wikipedia.org/wiki/Magic_SysRq_key) -|Command|Parameters|Notes| -|-|-|-| -|SYSRQ|Single character|| +| Command | Parameters | Notes | +| ------- | ---------------- | ----- | +| SYSRQ | Single character | | ## USB device ID diff --git a/firmware/targets/f7/furi_hal/furi_hal_usb_hid.c b/firmware/targets/f7/furi_hal/furi_hal_usb_hid.c index a3bc842274a..5cb7fd2983a 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_usb_hid.c +++ b/firmware/targets/f7/furi_hal/furi_hal_usb_hid.c @@ -11,9 +11,6 @@ #define HID_EP_OUT 0x01 #define HID_EP_SZ 0x10 -#define HID_KB_MAX_KEYS 6 -#define HID_CONSUMER_MAX_KEYS 2 - #define HID_INTERVAL 2 #define HID_VID_DEFAULT 0x046D diff --git a/firmware/targets/furi_hal_include/furi_hal_usb_hid.h b/firmware/targets/furi_hal_include/furi_hal_usb_hid.h index a9f094814c1..13e83ef6752 100644 --- a/firmware/targets/furi_hal_include/furi_hal_usb_hid.h +++ b/firmware/targets/furi_hal_include/furi_hal_usb_hid.h @@ -9,6 +9,11 @@ extern "C" { #endif +/** Max number of simultaneously pressed keys (keyboard) */ +#define HID_KB_MAX_KEYS 6 +/** Max number of simultaneously pressed keys (consumer control) */ +#define HID_CONSUMER_MAX_KEYS 2 + #define HID_KEYBOARD_NONE 0x00 /** HID keyboard modifier keys */ From fd8607398dbdb986888ee0b7517d9ef5c79e06e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Tue, 21 Mar 2023 18:55:20 +0900 Subject: [PATCH 482/824] Github: unshallow on decontamination (#2521) * Github: unshallow on decontamination * Github: fix syntax * Github: decontaminate without full tree * Github: update decontaminate action in all workflows --- .github/workflows/build.yml | 7 ++++++- .github/workflows/lint_and_submodule_check.yml | 7 ++++++- .github/workflows/merge_report.yml | 7 ++++++- .github/workflows/pvs_studio.yml | 7 ++++++- .github/workflows/unit_tests.yml | 9 +++++++-- .github/workflows/updater_test.yml | 7 ++++++- 6 files changed, 37 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 79535c93444..6ab2490ce6f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,7 +21,12 @@ jobs: - name: 'Decontaminate previous build leftovers' run: | if [ -d .git ]; then - git submodule status || git checkout "$(git rev-list --max-parents=0 HEAD | tail -n 1)" + git submodule status || ( + git ls-files --stage | egrep '^160000' | awk '{print $4}' | while read submodule + do + git rm -rf --cached "$submodule" + done + ) fi - name: 'Checkout code' diff --git a/.github/workflows/lint_and_submodule_check.yml b/.github/workflows/lint_and_submodule_check.yml index ede35793260..46cca5c0dca 100644 --- a/.github/workflows/lint_and_submodule_check.yml +++ b/.github/workflows/lint_and_submodule_check.yml @@ -21,7 +21,12 @@ jobs: - name: 'Decontaminate previous build leftovers' run: | if [ -d .git ]; then - git submodule status || git checkout "$(git rev-list --max-parents=0 HEAD | tail -n 1)" + git submodule status || ( + git ls-files --stage | egrep '^160000' | awk '{print $4}' | while read submodule + do + git rm -rf --cached "$submodule" + done + ) fi - name: 'Checkout code' diff --git a/.github/workflows/merge_report.yml b/.github/workflows/merge_report.yml index 3b7cd234993..e88346edf53 100644 --- a/.github/workflows/merge_report.yml +++ b/.github/workflows/merge_report.yml @@ -15,7 +15,12 @@ jobs: - name: 'Decontaminate previous build leftovers' run: | if [ -d .git ]; then - git submodule status || git checkout "$(git rev-list --max-parents=0 HEAD | tail -n 1)" + git submodule status || ( + git ls-files --stage | egrep '^160000' | awk '{print $4}' | while read submodule + do + git rm -rf --cached "$submodule" + done + ) fi - name: 'Checkout code' diff --git a/.github/workflows/pvs_studio.yml b/.github/workflows/pvs_studio.yml index 9105a0fd635..65ffd19546a 100644 --- a/.github/workflows/pvs_studio.yml +++ b/.github/workflows/pvs_studio.yml @@ -22,7 +22,12 @@ jobs: - name: 'Decontaminate previous build leftovers' run: | if [ -d .git ]; then - git submodule status || git checkout "$(git rev-list --max-parents=0 HEAD | tail -n 1)" + git submodule status || ( + git ls-files --stage | egrep '^160000' | awk '{print $4}' | while read submodule + do + git rm -rf --cached "$submodule" + done + ) fi - name: 'Checkout code' diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index bed5a470d5c..6f044ebcaef 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -15,7 +15,12 @@ jobs: - name: 'Decontaminate previous build leftovers' run: | if [ -d .git ]; then - git submodule status || git checkout "$(git rev-list --max-parents=0 HEAD | tail -n 1)" + git submodule status || ( + git ls-files --stage | egrep '^160000' | awk '{print $4}' | while read submodule + do + git rm -rf --cached "$submodule" + done + ) fi - name: Checkout code @@ -32,7 +37,7 @@ jobs: - name: 'Flash unit tests firmware' id: flashing if: success() - run: | + run: | ./fbt flash OPENOCD_ADAPTER_SERIAL=2A0906016415303030303032 FIRMWARE_APP_SET=unit_tests FORCE=1 - name: 'Wait for flipper and format ext' diff --git a/.github/workflows/updater_test.yml b/.github/workflows/updater_test.yml index eba34e988bb..c04d526fc78 100644 --- a/.github/workflows/updater_test.yml +++ b/.github/workflows/updater_test.yml @@ -15,7 +15,12 @@ jobs: - name: 'Decontaminate previous build leftovers' run: | if [ -d .git ]; then - git submodule status || git checkout "$(git rev-list --max-parents=0 HEAD | tail -n 1)" + git submodule status || ( + git ls-files --stage | egrep '^160000' | awk '{print $4}' | while read submodule + do + git rm -rf --cached "$submodule" + done + ) fi - name: Checkout code From bf70f4b71a6d75204613fb0ce19c84598e9f0457 Mon Sep 17 00:00:00 2001 From: AloneLiberty <111039319+AloneLiberty@users.noreply.github.com> Date: Tue, 21 Mar 2023 15:03:14 +0300 Subject: [PATCH 483/824] NFC: Fixed writing gen1a magic tags with invalid BCC (#2511) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- applications/external/nfc_magic/lib/magic/magic.c | 5 ++--- applications/external/nfc_magic/nfc_magic_worker.c | 8 +++++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/applications/external/nfc_magic/lib/magic/magic.c b/applications/external/nfc_magic/lib/magic/magic.c index a922bc7a8f6..9a71daaa0ff 100644 --- a/applications/external/nfc_magic/lib/magic/magic.c +++ b/applications/external/nfc_magic/lib/magic/magic.c @@ -6,8 +6,7 @@ #define MAGIC_CMD_WUPA (0x40) #define MAGIC_CMD_WIPE (0x41) -#define MAGIC_CMD_READ (0x43) -#define MAGIC_CMD_WRITE (0x43) +#define MAGIC_CMD_ACCESS (0x43) #define MAGIC_MIFARE_READ_CMD (0x30) #define MAGIC_MIFARE_WRITE_CMD (0xA0) @@ -70,7 +69,7 @@ bool magic_data_access_cmd() { FuriHalNfcReturn ret = 0; do { - tx_data[0] = MAGIC_CMD_WRITE; + tx_data[0] = MAGIC_CMD_ACCESS; ret = furi_hal_nfc_ll_txrx_bits( tx_data, 8, diff --git a/applications/external/nfc_magic/nfc_magic_worker.c b/applications/external/nfc_magic/nfc_magic_worker.c index 523c794f7dd..92eb793a71d 100644 --- a/applications/external/nfc_magic/nfc_magic_worker.c +++ b/applications/external/nfc_magic/nfc_magic_worker.c @@ -85,15 +85,17 @@ void nfc_magic_worker_write(NfcMagicWorker* nfc_magic_worker) { card_found_notified = true; } furi_hal_nfc_sleep(); - if(!magic_wupa()) { - FURI_LOG_E(TAG, "Not Magic card"); + FURI_LOG_E(TAG, "No card response to WUPA (not a magic card)"); nfc_magic_worker->callback( NfcMagicWorkerEventWrongCard, nfc_magic_worker->context); break; } + furi_hal_nfc_sleep(); + } + if(magic_wupa()) { if(!magic_data_access_cmd()) { - FURI_LOG_E(TAG, "Not Magic card"); + FURI_LOG_E(TAG, "No card response to data access command (not a magic card)"); nfc_magic_worker->callback( NfcMagicWorkerEventWrongCard, nfc_magic_worker->context); break; From ce50b09b286835d24d5d4d1aeca048260f1205a6 Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Tue, 21 Mar 2023 15:29:54 +0300 Subject: [PATCH 484/824] Remove hmac_sha256 from public API (#2519) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- {lib/toolbox => applications/main/u2f}/hmac_sha256.c | 0 {lib/toolbox => applications/main/u2f}/hmac_sha256.h | 0 applications/main/u2f/u2f.c | 2 +- firmware/targets/f18/api_symbols.csv | 7 ++----- firmware/targets/f7/api_symbols.csv | 7 ++----- lib/toolbox/SConscript | 2 +- 6 files changed, 6 insertions(+), 12 deletions(-) rename {lib/toolbox => applications/main/u2f}/hmac_sha256.c (100%) rename {lib/toolbox => applications/main/u2f}/hmac_sha256.h (100%) diff --git a/lib/toolbox/hmac_sha256.c b/applications/main/u2f/hmac_sha256.c similarity index 100% rename from lib/toolbox/hmac_sha256.c rename to applications/main/u2f/hmac_sha256.c diff --git a/lib/toolbox/hmac_sha256.h b/applications/main/u2f/hmac_sha256.h similarity index 100% rename from lib/toolbox/hmac_sha256.h rename to applications/main/u2f/hmac_sha256.h diff --git a/applications/main/u2f/u2f.c b/applications/main/u2f/u2f.c index 767733ce65b..0ed5ebb2966 100644 --- a/applications/main/u2f/u2f.c +++ b/applications/main/u2f/u2f.c @@ -7,7 +7,7 @@ #include // for lfs_tobe32 #include "toolbox/sha256.h" -#include "toolbox/hmac_sha256.h" +#include "hmac_sha256.h" #include "micro-ecc/uECC.h" #define TAG "U2F" diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index 7fa269c9612..b6be56f60d1 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,18.2,, +Version,+,19.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -160,7 +160,6 @@ Header,+,lib/toolbox/args.h,, Header,+,lib/toolbox/crc32_calc.h,, Header,+,lib/toolbox/dir_walk.h,, Header,+,lib/toolbox/float_tools.h,, -Header,+,lib/toolbox/hmac_sha256.h,, Header,+,lib/toolbox/manchester_decoder.h,, Header,+,lib/toolbox/manchester_encoder.h,, Header,+,lib/toolbox/md5.h,, @@ -169,6 +168,7 @@ Header,+,lib/toolbox/pretty_format.h,, Header,+,lib/toolbox/protocols/protocol_dict.h,, Header,+,lib/toolbox/random_name.h,, Header,+,lib/toolbox/saved_struct.h,, +Header,+,lib/toolbox/sha256.h,, Header,+,lib/toolbox/stream/buffered_file_stream.h,, Header,+,lib/toolbox/stream/file_stream.h,, Header,+,lib/toolbox/stream/stream.h,, @@ -1316,9 +1316,6 @@ Function,+,gui_view_port_send_to_front,void,"Gui*, ViewPort*" Function,+,hal_sd_detect,_Bool, Function,+,hal_sd_detect_init,void, Function,+,hal_sd_detect_set_low,void, -Function,+,hmac_sha256_finish,void,"const hmac_sha256_context*, const uint8_t*, uint8_t*" -Function,+,hmac_sha256_init,void,"hmac_sha256_context*, const uint8_t*" -Function,+,hmac_sha256_update,void,"const hmac_sha256_context*, const uint8_t*, unsigned" Function,+,icon_animation_alloc,IconAnimation*,const Icon* Function,+,icon_animation_free,void,IconAnimation* Function,+,icon_animation_get_height,uint8_t,const IconAnimation* diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index c98e546727e..e6de39b1d6c 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,18.2,, +Version,+,19.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -192,7 +192,6 @@ Header,+,lib/toolbox/args.h,, Header,+,lib/toolbox/crc32_calc.h,, Header,+,lib/toolbox/dir_walk.h,, Header,+,lib/toolbox/float_tools.h,, -Header,+,lib/toolbox/hmac_sha256.h,, Header,+,lib/toolbox/manchester_decoder.h,, Header,+,lib/toolbox/manchester_encoder.h,, Header,+,lib/toolbox/md5.h,, @@ -201,6 +200,7 @@ Header,+,lib/toolbox/pretty_format.h,, Header,+,lib/toolbox/protocols/protocol_dict.h,, Header,+,lib/toolbox/random_name.h,, Header,+,lib/toolbox/saved_struct.h,, +Header,+,lib/toolbox/sha256.h,, Header,+,lib/toolbox/stream/buffered_file_stream.h,, Header,+,lib/toolbox/stream/file_stream.h,, Header,+,lib/toolbox/stream/stream.h,, @@ -1604,9 +1604,6 @@ Function,+,gui_view_port_send_to_front,void,"Gui*, ViewPort*" Function,+,hal_sd_detect,_Bool, Function,+,hal_sd_detect_init,void, Function,+,hal_sd_detect_set_low,void, -Function,+,hmac_sha256_finish,void,"const hmac_sha256_context*, const uint8_t*, uint8_t*" -Function,+,hmac_sha256_init,void,"hmac_sha256_context*, const uint8_t*" -Function,+,hmac_sha256_update,void,"const hmac_sha256_context*, const uint8_t*, unsigned" Function,-,hypot,double,"double, double" Function,-,hypotf,float,"float, float" Function,-,hypotl,long double,"long double, long double" diff --git a/lib/toolbox/SConscript b/lib/toolbox/SConscript index d7b0e7bbc0c..fad4c558414 100644 --- a/lib/toolbox/SConscript +++ b/lib/toolbox/SConscript @@ -12,7 +12,7 @@ env.Append( File("manchester_encoder.h"), File("path.h"), File("random_name.h"), - File("hmac_sha256.h"), + File("sha256.h"), File("crc32_calc.h"), File("dir_walk.h"), File("md5.h"), From 6089e9210f67712284e14c147123938cd7be9bd9 Mon Sep 17 00:00:00 2001 From: Sam Edwards Date: Tue, 21 Mar 2023 08:53:07 -0600 Subject: [PATCH 485/824] BadUSB: implement boot protocol (#2496) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * BadUSB: remove unused out EP * BadUSB: do not use iad for a single interface * BadUSB: implement the boot protocol * BadUSB: implement SET_PROTOCOL * Improve HID report descriptor readability * CODEOWNERS update Co-authored-by: nminaylov Co-authored-by: あく --- .github/CODEOWNERS | 3 + .../targets/f7/furi_hal/furi_hal_usb_hid.c | 247 ++++++++++-------- 2 files changed, 136 insertions(+), 114 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 0bc130243b5..c1684aa99b3 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -44,6 +44,9 @@ /applications/examples/example_thermo/ @skotopes @DrZlo13 @hedger @gsurkov +# Firmware targets +/firmware/ @skotopes @DrZlo13 @hedger @nminaylov + # Assets /assets/resources/infrared/ @skotopes @DrZlo13 @hedger @gsurkov diff --git a/firmware/targets/f7/furi_hal/furi_hal_usb_hid.c b/firmware/targets/f7/furi_hal/furi_hal_usb_hid.c index 5cb7fd2983a..d27613410bd 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_usb_hid.c +++ b/firmware/targets/f7/furi_hal/furi_hal_usb_hid.c @@ -8,7 +8,6 @@ #include "usb_hid.h" #define HID_EP_IN 0x81 -#define HID_EP_OUT 0x01 #define HID_EP_SZ 0x10 #define HID_INTERVAL 2 @@ -16,17 +15,15 @@ #define HID_VID_DEFAULT 0x046D #define HID_PID_DEFAULT 0xC529 -struct HidIadDescriptor { - struct usb_iad_descriptor hid_iad; +struct HidIntfDescriptor { struct usb_interface_descriptor hid; struct usb_hid_descriptor hid_desc; struct usb_endpoint_descriptor hid_ep_in; - struct usb_endpoint_descriptor hid_ep_out; }; struct HidConfigDescriptor { struct usb_config_descriptor config; - struct HidIadDescriptor iad_0; + struct HidIntfDescriptor intf_0; } __attribute__((packed)); enum HidReportId { @@ -35,78 +32,98 @@ enum HidReportId { ReportIdConsumer = 3, }; -/* HID report: keyboard+mouse */ +/* HID report descriptor: keyboard + mouse + consumer control */ static const uint8_t hid_report_desc[] = { + // clang-format off HID_USAGE_PAGE(HID_PAGE_DESKTOP), HID_USAGE(HID_DESKTOP_KEYBOARD), HID_COLLECTION(HID_APPLICATION_COLLECTION), - HID_REPORT_ID(ReportIdKeyboard), - HID_USAGE_PAGE(HID_DESKTOP_KEYPAD), - HID_USAGE_MINIMUM(HID_KEYBOARD_L_CTRL), - HID_USAGE_MAXIMUM(HID_KEYBOARD_R_GUI), - HID_LOGICAL_MINIMUM(0), - HID_LOGICAL_MAXIMUM(1), - HID_REPORT_SIZE(1), - HID_REPORT_COUNT(8), - HID_INPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), - HID_REPORT_COUNT(1), - HID_REPORT_SIZE(8), - HID_INPUT(HID_IOF_CONSTANT | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), - HID_USAGE_PAGE(HID_PAGE_LED), - HID_REPORT_COUNT(8), - HID_REPORT_SIZE(1), - HID_USAGE_MINIMUM(1), - HID_USAGE_MAXIMUM(8), - HID_OUTPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), - HID_REPORT_COUNT(HID_KB_MAX_KEYS), - HID_REPORT_SIZE(8), - HID_LOGICAL_MINIMUM(0), - HID_LOGICAL_MAXIMUM(101), - HID_USAGE_PAGE(HID_DESKTOP_KEYPAD), - HID_USAGE_MINIMUM(0), - HID_USAGE_MAXIMUM(101), - HID_INPUT(HID_IOF_DATA | HID_IOF_ARRAY | HID_IOF_ABSOLUTE), + HID_REPORT_ID(ReportIdKeyboard), + // Keyboard report + HID_USAGE_PAGE(HID_DESKTOP_KEYPAD), + HID_USAGE_MINIMUM(HID_KEYBOARD_L_CTRL), + HID_USAGE_MAXIMUM(HID_KEYBOARD_R_GUI), + HID_LOGICAL_MINIMUM(0), + HID_LOGICAL_MAXIMUM(1), + HID_REPORT_SIZE(1), + HID_REPORT_COUNT(8), + // Input - Modifier keys byte + HID_INPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), + + HID_REPORT_COUNT(1), + HID_REPORT_SIZE(8), + // Input - Reserved byte + HID_INPUT(HID_IOF_CONSTANT | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), + + HID_USAGE_PAGE(HID_PAGE_LED), + HID_REPORT_COUNT(8), + HID_REPORT_SIZE(1), + HID_USAGE_MINIMUM(1), + HID_USAGE_MAXIMUM(8), + // Output - LEDs + HID_OUTPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), + + HID_REPORT_COUNT(HID_KB_MAX_KEYS), + HID_REPORT_SIZE(8), + HID_LOGICAL_MINIMUM(0), + HID_LOGICAL_MAXIMUM(101), + HID_USAGE_PAGE(HID_DESKTOP_KEYPAD), + HID_USAGE_MINIMUM(0), + HID_USAGE_MAXIMUM(101), + // Input - Key codes + HID_INPUT(HID_IOF_DATA | HID_IOF_ARRAY | HID_IOF_ABSOLUTE), HID_END_COLLECTION, + HID_USAGE_PAGE(HID_PAGE_DESKTOP), HID_USAGE(HID_DESKTOP_MOUSE), HID_COLLECTION(HID_APPLICATION_COLLECTION), - HID_USAGE(HID_DESKTOP_POINTER), - HID_COLLECTION(HID_PHYSICAL_COLLECTION), - HID_REPORT_ID(ReportIdMouse), - HID_USAGE_PAGE(HID_PAGE_BUTTON), - HID_USAGE_MINIMUM(1), - HID_USAGE_MAXIMUM(3), - HID_LOGICAL_MINIMUM(0), - HID_LOGICAL_MAXIMUM(1), - HID_REPORT_COUNT(3), - HID_REPORT_SIZE(1), - HID_INPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), - HID_REPORT_SIZE(1), - HID_REPORT_COUNT(5), - HID_INPUT(HID_IOF_CONSTANT | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), - HID_USAGE_PAGE(HID_PAGE_DESKTOP), - HID_USAGE(HID_DESKTOP_X), - HID_USAGE(HID_DESKTOP_Y), - HID_USAGE(HID_DESKTOP_WHEEL), - HID_LOGICAL_MINIMUM(-127), - HID_LOGICAL_MAXIMUM(127), - HID_REPORT_SIZE(8), - HID_REPORT_COUNT(3), - HID_INPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_RELATIVE), - HID_END_COLLECTION, + HID_USAGE(HID_DESKTOP_POINTER), + HID_COLLECTION(HID_PHYSICAL_COLLECTION), + HID_REPORT_ID(ReportIdMouse), + // Mouse report + HID_USAGE_PAGE(HID_PAGE_BUTTON), + HID_USAGE_MINIMUM(1), + HID_USAGE_MAXIMUM(3), + HID_LOGICAL_MINIMUM(0), + HID_LOGICAL_MAXIMUM(1), + HID_REPORT_COUNT(3), + HID_REPORT_SIZE(1), + // Input - Mouse keys + HID_INPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), + + HID_REPORT_SIZE(1), + HID_REPORT_COUNT(5), + // Input - Mouse keys padding + HID_INPUT(HID_IOF_CONSTANT | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), + + HID_USAGE_PAGE(HID_PAGE_DESKTOP), + HID_USAGE(HID_DESKTOP_X), + HID_USAGE(HID_DESKTOP_Y), + HID_USAGE(HID_DESKTOP_WHEEL), + HID_LOGICAL_MINIMUM(-127), + HID_LOGICAL_MAXIMUM(127), + HID_REPORT_SIZE(8), + HID_REPORT_COUNT(3), + // Input - Mouse movement data (x, y, scroll) + HID_INPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_RELATIVE), + HID_END_COLLECTION, HID_END_COLLECTION, + HID_USAGE_PAGE(HID_PAGE_CONSUMER), HID_USAGE(HID_CONSUMER_CONTROL), HID_COLLECTION(HID_APPLICATION_COLLECTION), - HID_REPORT_ID(ReportIdConsumer), - HID_LOGICAL_MINIMUM(0), - HID_RI_LOGICAL_MAXIMUM(16, 0x3FF), - HID_USAGE_MINIMUM(0), - HID_RI_USAGE_MAXIMUM(16, 0x3FF), - HID_REPORT_COUNT(HID_CONSUMER_MAX_KEYS), - HID_REPORT_SIZE(16), - HID_INPUT(HID_IOF_DATA | HID_IOF_ARRAY | HID_IOF_ABSOLUTE), + HID_REPORT_ID(ReportIdConsumer), + // Consumer report + HID_LOGICAL_MINIMUM(0), + HID_RI_LOGICAL_MAXIMUM(16, 0x3FF), + HID_USAGE_MINIMUM(0), + HID_RI_USAGE_MAXIMUM(16, 0x3FF), + HID_REPORT_COUNT(HID_CONSUMER_MAX_KEYS), + HID_REPORT_SIZE(16), + // Input - Consumer control keys + HID_INPUT(HID_IOF_DATA | HID_IOF_ARRAY | HID_IOF_ABSOLUTE), HID_END_COLLECTION, + // clang-format on }; /* Device descriptor */ @@ -114,9 +131,9 @@ static struct usb_device_descriptor hid_device_desc = { .bLength = sizeof(struct usb_device_descriptor), .bDescriptorType = USB_DTYPE_DEVICE, .bcdUSB = VERSION_BCD(2, 0, 0), - .bDeviceClass = USB_CLASS_IAD, - .bDeviceSubClass = USB_SUBCLASS_IAD, - .bDeviceProtocol = USB_PROTO_IAD, + .bDeviceClass = USB_CLASS_PER_INTERFACE, + .bDeviceSubClass = USB_SUBCLASS_NONE, + .bDeviceProtocol = USB_PROTO_NONE, .bMaxPacketSize0 = USB_EP0_SIZE, .idVendor = HID_VID_DEFAULT, .idProduct = HID_PID_DEFAULT, @@ -140,29 +157,18 @@ static const struct HidConfigDescriptor hid_cfg_desc = { .bmAttributes = USB_CFG_ATTR_RESERVED | USB_CFG_ATTR_SELFPOWERED, .bMaxPower = USB_CFG_POWER_MA(100), }, - .iad_0 = + .intf_0 = { - .hid_iad = - { - .bLength = sizeof(struct usb_iad_descriptor), - .bDescriptorType = USB_DTYPE_INTERFASEASSOC, - .bFirstInterface = 0, - .bInterfaceCount = 1, - .bFunctionClass = USB_CLASS_PER_INTERFACE, - .bFunctionSubClass = USB_SUBCLASS_NONE, - .bFunctionProtocol = USB_PROTO_NONE, - .iFunction = NO_DESCRIPTOR, - }, .hid = { .bLength = sizeof(struct usb_interface_descriptor), .bDescriptorType = USB_DTYPE_INTERFACE, .bInterfaceNumber = 0, .bAlternateSetting = 0, - .bNumEndpoints = 2, + .bNumEndpoints = 1, .bInterfaceClass = USB_CLASS_HID, - .bInterfaceSubClass = USB_HID_SUBCLASS_NONBOOT, - .bInterfaceProtocol = USB_HID_PROTO_NONBOOT, + .bInterfaceSubClass = USB_HID_SUBCLASS_BOOT, + .bInterfaceProtocol = USB_HID_PROTO_KEYBOARD, .iInterface = NO_DESCRIPTOR, }, .hid_desc = @@ -184,15 +190,6 @@ static const struct HidConfigDescriptor hid_cfg_desc = { .wMaxPacketSize = HID_EP_SZ, .bInterval = HID_INTERVAL, }, - .hid_ep_out = - { - .bLength = sizeof(struct usb_endpoint_descriptor), - .bDescriptorType = USB_DTYPE_ENDPOINT, - .bEndpointAddress = HID_EP_OUT, - .bmAttributes = USB_EPTYPE_INTERRUPT, - .wMaxPacketSize = HID_EP_SZ, - .bInterval = HID_INTERVAL, - }, }, }; @@ -206,9 +203,11 @@ struct HidReportMouse { struct HidReportKB { uint8_t report_id; - uint8_t mods; - uint8_t reserved; - uint8_t btn[HID_KB_MAX_KEYS]; + struct { + uint8_t mods; + uint8_t reserved; + uint8_t btn[HID_KB_MAX_KEYS]; + } boot; } __attribute__((packed)); struct HidReportConsumer { @@ -256,6 +255,7 @@ static bool hid_connected = false; static HidStateCallback callback; static void* cb_ctx; static uint8_t led_state; +static bool boot_protocol = false; bool furi_hal_hid_is_connected() { return hid_connected; @@ -280,31 +280,31 @@ void furi_hal_hid_set_state_callback(HidStateCallback cb, void* ctx) { bool furi_hal_hid_kb_press(uint16_t button) { for(uint8_t key_nb = 0; key_nb < HID_KB_MAX_KEYS; key_nb++) { - if(hid_report.keyboard.btn[key_nb] == 0) { - hid_report.keyboard.btn[key_nb] = button & 0xFF; + if(hid_report.keyboard.boot.btn[key_nb] == 0) { + hid_report.keyboard.boot.btn[key_nb] = button & 0xFF; break; } } - hid_report.keyboard.mods |= (button >> 8); + hid_report.keyboard.boot.mods |= (button >> 8); return hid_send_report(ReportIdKeyboard); } bool furi_hal_hid_kb_release(uint16_t button) { for(uint8_t key_nb = 0; key_nb < HID_KB_MAX_KEYS; key_nb++) { - if(hid_report.keyboard.btn[key_nb] == (button & 0xFF)) { - hid_report.keyboard.btn[key_nb] = 0; + if(hid_report.keyboard.boot.btn[key_nb] == (button & 0xFF)) { + hid_report.keyboard.boot.btn[key_nb] = 0; break; } } - hid_report.keyboard.mods &= ~(button >> 8); + hid_report.keyboard.boot.mods &= ~(button >> 8); return hid_send_report(ReportIdKeyboard); } bool furi_hal_hid_kb_release_all() { for(uint8_t key_nb = 0; key_nb < HID_KB_MAX_KEYS; key_nb++) { - hid_report.keyboard.btn[key_nb] = 0; + hid_report.keyboard.boot.btn[key_nb] = 0; } - hid_report.keyboard.mods = 0; + hid_report.keyboard.boot.mods = 0; return hid_send_report(ReportIdKeyboard); } @@ -434,27 +434,35 @@ static void hid_on_suspend(usbd_device* dev) { static bool hid_send_report(uint8_t report_id) { if((hid_semaphore == NULL) || (hid_connected == false)) return false; + if((boot_protocol == true) && (report_id != ReportIdKeyboard)) return false; furi_check(furi_semaphore_acquire(hid_semaphore, FuriWaitForever) == FuriStatusOk); - if(hid_connected == true) { + if(hid_connected == false) { + return false; + } + if(boot_protocol == true) { + usbd_ep_write( + usb_dev, HID_EP_IN, &hid_report.keyboard.boot, sizeof(hid_report.keyboard.boot)); + } else { if(report_id == ReportIdKeyboard) usbd_ep_write(usb_dev, HID_EP_IN, &hid_report.keyboard, sizeof(hid_report.keyboard)); else if(report_id == ReportIdMouse) usbd_ep_write(usb_dev, HID_EP_IN, &hid_report.mouse, sizeof(hid_report.mouse)); else if(report_id == ReportIdConsumer) usbd_ep_write(usb_dev, HID_EP_IN, &hid_report.consumer, sizeof(hid_report.consumer)); - return true; } - return false; + return true; } static void hid_txrx_ep_callback(usbd_device* dev, uint8_t event, uint8_t ep) { UNUSED(dev); if(event == usbd_evt_eptx) { furi_semaphore_release(hid_semaphore); + } else if(boot_protocol == true) { + usbd_ep_read(usb_dev, ep, &led_state, sizeof(led_state)); } else { struct HidReportLED leds; - usbd_ep_read(usb_dev, ep, &leds, 2); + usbd_ep_read(usb_dev, ep, &leds, sizeof(leds)); led_state = leds.led_state; } } @@ -464,18 +472,15 @@ static usbd_respond hid_ep_config(usbd_device* dev, uint8_t cfg) { switch(cfg) { case 0: /* deconfiguring device */ - usbd_ep_deconfig(dev, HID_EP_OUT); usbd_ep_deconfig(dev, HID_EP_IN); - usbd_reg_endpoint(dev, HID_EP_OUT, 0); usbd_reg_endpoint(dev, HID_EP_IN, 0); return usbd_ack; case 1: /* configuring device */ usbd_ep_config(dev, HID_EP_IN, USB_EPTYPE_INTERRUPT, HID_EP_SZ); - usbd_ep_config(dev, HID_EP_OUT, USB_EPTYPE_INTERRUPT, HID_EP_SZ); usbd_reg_endpoint(dev, HID_EP_IN, hid_txrx_ep_callback); - usbd_reg_endpoint(dev, HID_EP_OUT, hid_txrx_ep_callback); usbd_ep_write(dev, HID_EP_IN, 0, 0); + boot_protocol = false; /* BIOS will SET_PROTOCOL if it wants this */ return usbd_ack; default: return usbd_fail; @@ -493,8 +498,21 @@ static usbd_respond hid_control(usbd_device* dev, usbd_ctlreq* req, usbd_rqc_cal case USB_HID_SETIDLE: return usbd_ack; case USB_HID_GETREPORT: - dev->status.data_ptr = &hid_report; - dev->status.data_count = sizeof(hid_report); + if(boot_protocol == true) { + dev->status.data_ptr = &hid_report.keyboard.boot; + dev->status.data_count = sizeof(hid_report.keyboard.boot); + } else { + dev->status.data_ptr = &hid_report; + dev->status.data_count = sizeof(hid_report); + } + return usbd_ack; + case USB_HID_SETPROTOCOL: + if(req->wValue == 0) + boot_protocol = true; + else if(req->wValue == 1) + boot_protocol = false; + else + return usbd_fail; return usbd_ack; default: return usbd_fail; @@ -505,10 +523,11 @@ static usbd_respond hid_control(usbd_device* dev, usbd_ctlreq* req, usbd_rqc_cal req->wIndex == 0 && req->bRequest == USB_STD_GET_DESCRIPTOR) { switch(req->wValue >> 8) { case USB_DTYPE_HID: - dev->status.data_ptr = (uint8_t*)&(hid_cfg_desc.iad_0.hid_desc); - dev->status.data_count = sizeof(hid_cfg_desc.iad_0.hid_desc); + dev->status.data_ptr = (uint8_t*)&(hid_cfg_desc.intf_0.hid_desc); + dev->status.data_count = sizeof(hid_cfg_desc.intf_0.hid_desc); return usbd_ack; case USB_DTYPE_HID_REPORT: + boot_protocol = false; /* BIOS does not read this */ dev->status.data_ptr = (uint8_t*)hid_report_desc; dev->status.data_count = sizeof(hid_report_desc); return usbd_ack; From 204b50381a0975b72af8b0be33b6371e69c024a0 Mon Sep 17 00:00:00 2001 From: Shukai Ni Date: Wed, 22 Mar 2023 05:47:47 -0400 Subject: [PATCH 486/824] Correct FAP default upload path in AppsOnSDCard.md (#2524) Since the fap's source code is in `applications_user`, the documentation should also point to `applications_user` as the parent directory --- documentation/AppsOnSDCard.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/AppsOnSDCard.md b/documentation/AppsOnSDCard.md index 75430570699..212df5b1bf0 100644 --- a/documentation/AppsOnSDCard.md +++ b/documentation/AppsOnSDCard.md @@ -13,7 +13,7 @@ FAPs are created and developed the same way as internal applications that are pa To build your application as a FAP, create a folder with your app's source code in `applications_user`, then write its code the way you'd do when creating a regular built-in application. Then configure its `application.fam` manifest, and set its _apptype_ to FlipperAppType.EXTERNAL. See [Application Manifests](./AppManifests.md#application-definition) for more details. - To build your application, run `./fbt fap_{APPID}`, where APPID is your application's ID in its manifest. -- To build your app and upload it over USB to run on Flipper, use `./fbt launch_app APPSRC=applications/path/to/app`. This command is configured in the default [VS Code profile](../.vscode/ReadMe.md) as a "Launch App on Flipper" build action (Ctrl+Shift+B menu). +- To build your app and upload it over USB to run on Flipper, use `./fbt launch_app APPSRC=applications_user/path/to/app`. This command is configured in the default [VS Code profile](../.vscode/ReadMe.md) as a "Launch App on Flipper" build action (Ctrl+Shift+B menu). - To build all FAPs, run `./fbt faps` or `./fbt fap_dist`. ## FAP assets From acc32f66e843a13996ae16c6287de6cbcf98be70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Wed, 22 Mar 2023 19:48:41 +0900 Subject: [PATCH 487/824] Github: force cleanup tree on decontaminate (#2526) --- .github/workflows/build.yml | 2 +- .github/workflows/lint_and_submodule_check.yml | 2 +- .github/workflows/merge_report.yml | 2 +- .github/workflows/pvs_studio.yml | 2 +- .github/workflows/unit_tests.yml | 2 +- .github/workflows/updater_test.yml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6ab2490ce6f..898a1291c6d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,7 +24,7 @@ jobs: git submodule status || ( git ls-files --stage | egrep '^160000' | awk '{print $4}' | while read submodule do - git rm -rf --cached "$submodule" + git rm -rf "$submodule" done ) fi diff --git a/.github/workflows/lint_and_submodule_check.yml b/.github/workflows/lint_and_submodule_check.yml index 46cca5c0dca..10b3c1d9aac 100644 --- a/.github/workflows/lint_and_submodule_check.yml +++ b/.github/workflows/lint_and_submodule_check.yml @@ -24,7 +24,7 @@ jobs: git submodule status || ( git ls-files --stage | egrep '^160000' | awk '{print $4}' | while read submodule do - git rm -rf --cached "$submodule" + git rm -rf "$submodule" done ) fi diff --git a/.github/workflows/merge_report.yml b/.github/workflows/merge_report.yml index e88346edf53..a382733dfce 100644 --- a/.github/workflows/merge_report.yml +++ b/.github/workflows/merge_report.yml @@ -18,7 +18,7 @@ jobs: git submodule status || ( git ls-files --stage | egrep '^160000' | awk '{print $4}' | while read submodule do - git rm -rf --cached "$submodule" + git rm -rf "$submodule" done ) fi diff --git a/.github/workflows/pvs_studio.yml b/.github/workflows/pvs_studio.yml index 65ffd19546a..d11e268da49 100644 --- a/.github/workflows/pvs_studio.yml +++ b/.github/workflows/pvs_studio.yml @@ -25,7 +25,7 @@ jobs: git submodule status || ( git ls-files --stage | egrep '^160000' | awk '{print $4}' | while read submodule do - git rm -rf --cached "$submodule" + git rm -rf "$submodule" done ) fi diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 6f044ebcaef..0ec531064c7 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -18,7 +18,7 @@ jobs: git submodule status || ( git ls-files --stage | egrep '^160000' | awk '{print $4}' | while read submodule do - git rm -rf --cached "$submodule" + git rm -rf "$submodule" done ) fi diff --git a/.github/workflows/updater_test.yml b/.github/workflows/updater_test.yml index c04d526fc78..e1e655b0034 100644 --- a/.github/workflows/updater_test.yml +++ b/.github/workflows/updater_test.yml @@ -18,7 +18,7 @@ jobs: git submodule status || ( git ls-files --stage | egrep '^160000' | awk '{print $4}' | while read submodule do - git rm -rf --cached "$submodule" + git rm -rf "$submodule" done ) fi From 973287b09b2e001503d91d94e05d9fe04bfafd6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Wed, 22 Mar 2023 20:26:40 +0900 Subject: [PATCH 488/824] Github: wipe workspace before checkout (#2527) * Github: wipe workspace before checkout * Github: allow find to fail * Github: limit maxdepth for find --- .github/workflows/build.yml | 21 ++++--------------- .../workflows/lint_and_submodule_check.yml | 12 ++--------- .github/workflows/merge_report.yml | 12 ++--------- .github/workflows/pvs_studio.yml | 12 ++--------- .github/workflows/unit_tests.yml | 12 ++--------- .github/workflows/updater_test.yml | 20 ++++-------------- 6 files changed, 16 insertions(+), 73 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 898a1291c6d..56e50d5f408 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,16 +18,8 @@ jobs: main: runs-on: [self-hosted,FlipperZeroShell] steps: - - name: 'Decontaminate previous build leftovers' - run: | - if [ -d .git ]; then - git submodule status || ( - git ls-files --stage | egrep '^160000' | awk '{print $4}' | while read submodule - do - git rm -rf "$submodule" - done - ) - fi + - name: 'Wipe workspace' + run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; - name: 'Checkout code' uses: actions/checkout@v3 @@ -171,13 +163,8 @@ jobs: if: ${{ !startsWith(github.ref, 'refs/tags') }} runs-on: [self-hosted,FlipperZeroShell] steps: - - name: 'Decontaminate previous build leftovers' - run: | - if [ -d .git ] - then - git submodule status \ - || git checkout "$(git rev-list --max-parents=0 HEAD | tail -n 1)" - fi + - name: 'Wipe workspace' + run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; - name: 'Checkout code' uses: actions/checkout@v3 diff --git a/.github/workflows/lint_and_submodule_check.yml b/.github/workflows/lint_and_submodule_check.yml index 10b3c1d9aac..cecfd12481e 100644 --- a/.github/workflows/lint_and_submodule_check.yml +++ b/.github/workflows/lint_and_submodule_check.yml @@ -18,16 +18,8 @@ jobs: lint_sources_check_submodules: runs-on: [self-hosted,FlipperZeroShell] steps: - - name: 'Decontaminate previous build leftovers' - run: | - if [ -d .git ]; then - git submodule status || ( - git ls-files --stage | egrep '^160000' | awk '{print $4}' | while read submodule - do - git rm -rf "$submodule" - done - ) - fi + - name: 'Wipe workspace' + run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; - name: 'Checkout code' uses: actions/checkout@v3 diff --git a/.github/workflows/merge_report.yml b/.github/workflows/merge_report.yml index a382733dfce..71515e1c5c9 100644 --- a/.github/workflows/merge_report.yml +++ b/.github/workflows/merge_report.yml @@ -12,16 +12,8 @@ jobs: merge_report: runs-on: [self-hosted,FlipperZeroShell] steps: - - name: 'Decontaminate previous build leftovers' - run: | - if [ -d .git ]; then - git submodule status || ( - git ls-files --stage | egrep '^160000' | awk '{print $4}' | while read submodule - do - git rm -rf "$submodule" - done - ) - fi + - name: 'Wipe workspace' + run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; - name: 'Checkout code' uses: actions/checkout@v3 diff --git a/.github/workflows/pvs_studio.yml b/.github/workflows/pvs_studio.yml index d11e268da49..6dbf84edb88 100644 --- a/.github/workflows/pvs_studio.yml +++ b/.github/workflows/pvs_studio.yml @@ -19,16 +19,8 @@ jobs: if: ${{ !github.event.pull_request.head.repo.fork }} runs-on: [self-hosted, FlipperZeroShell] steps: - - name: 'Decontaminate previous build leftovers' - run: | - if [ -d .git ]; then - git submodule status || ( - git ls-files --stage | egrep '^160000' | awk '{print $4}' | while read submodule - do - git rm -rf "$submodule" - done - ) - fi + - name: 'Wipe workspace' + run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; - name: 'Checkout code' uses: actions/checkout@v3 diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 0ec531064c7..6a824fac314 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -12,16 +12,8 @@ jobs: run_units_on_bench: runs-on: [self-hosted, FlipperZeroUnitTest] steps: - - name: 'Decontaminate previous build leftovers' - run: | - if [ -d .git ]; then - git submodule status || ( - git ls-files --stage | egrep '^160000' | awk '{print $4}' | while read submodule - do - git rm -rf "$submodule" - done - ) - fi + - name: 'Wipe workspace' + run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; - name: Checkout code uses: actions/checkout@v3 diff --git a/.github/workflows/updater_test.yml b/.github/workflows/updater_test.yml index e1e655b0034..2861529d838 100644 --- a/.github/workflows/updater_test.yml +++ b/.github/workflows/updater_test.yml @@ -12,16 +12,8 @@ jobs: test_updater_on_bench: runs-on: [self-hosted, FlipperZeroUpdaterTest] steps: - - name: 'Decontaminate previous build leftovers' - run: | - if [ -d .git ]; then - git submodule status || ( - git ls-files --stage | egrep '^160000' | awk '{print $4}' | while read submodule - do - git rm -rf "$submodule" - done - ) - fi + - name: 'Wipe workspace' + run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; - name: Checkout code uses: actions/checkout@v3 @@ -57,12 +49,8 @@ jobs: run: | echo "tag=$(git tag -l --sort=-version:refname | grep -v "rc\|RC" | head -1)" >> $GITHUB_OUTPUT - - name: 'Decontaminate previous build leftovers' - if: failure() - run: | - if [ -d .git ]; then - git submodule status || git checkout "$(git rev-list --max-parents=0 HEAD | tail -n 1)" - fi + - name: 'Wipe workspace' + run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; - name: 'Checkout latest release' uses: actions/checkout@v3 From 1f236ede0e4d13f2ca0c031bd7c0e2017540d68c Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Wed, 22 Mar 2023 17:41:14 +0300 Subject: [PATCH 489/824] [#2501] Disable UART IRQs by default (#2523) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- firmware/targets/f7/furi_hal/furi_hal_uart.c | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/firmware/targets/f7/furi_hal/furi_hal_uart.c b/firmware/targets/f7/furi_hal/furi_hal_uart.c index 54232e67fa8..71b5c7ba044 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_uart.c +++ b/firmware/targets/f7/furi_hal/furi_hal_uart.c @@ -44,7 +44,8 @@ static void furi_hal_usart_init(uint32_t baud) { while(!LL_USART_IsActiveFlag_TEACK(USART1) || !LL_USART_IsActiveFlag_REACK(USART1)) ; - LL_USART_EnableIT_RXNE_RXFNE(USART1); + LL_USART_DisableIT_ERROR(USART1); + NVIC_SetPriority(USART1_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 5, 0)); } @@ -79,8 +80,8 @@ static void furi_hal_lpuart_init(uint32_t baud) { ; furi_hal_uart_set_br(FuriHalUartIdLPUART1, baud); + LL_LPUART_DisableIT_ERROR(LPUART1); - LL_LPUART_EnableIT_RXNE_RXFNE(LPUART1); NVIC_SetPriority(LPUART1_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 5, 0)); } @@ -190,19 +191,25 @@ void furi_hal_uart_set_irq_cb( void (*cb)(UartIrqEvent ev, uint8_t data, void* ctx), void* ctx) { if(cb == NULL) { - if(ch == FuriHalUartIdUSART1) + if(ch == FuriHalUartIdUSART1) { NVIC_DisableIRQ(USART1_IRQn); - else if(ch == FuriHalUartIdLPUART1) + LL_USART_DisableIT_RXNE_RXFNE(USART1); + } else if(ch == FuriHalUartIdLPUART1) { NVIC_DisableIRQ(LPUART1_IRQn); + LL_LPUART_DisableIT_RXNE_RXFNE(LPUART1); + } irq_cb[ch] = cb; irq_ctx[ch] = ctx; } else { irq_ctx[ch] = ctx; irq_cb[ch] = cb; - if(ch == FuriHalUartIdUSART1) + if(ch == FuriHalUartIdUSART1) { NVIC_EnableIRQ(USART1_IRQn); - else if(ch == FuriHalUartIdLPUART1) + LL_USART_EnableIT_RXNE_RXFNE(USART1); + } else if(ch == FuriHalUartIdLPUART1) { NVIC_EnableIRQ(LPUART1_IRQn); + LL_LPUART_EnableIT_RXNE_RXFNE(LPUART1); + } } } From 8b224ecb15d0899c3e67edd693a18f58d9029b57 Mon Sep 17 00:00:00 2001 From: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> Date: Wed, 22 Mar 2023 17:54:06 +0300 Subject: [PATCH 490/824] [FL-3179] 1-Wire Overdrive Mode (#2522) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Separate ibutton to its own module, add one_wire to f18 * Move onewire cli to a separate app * Add definitions for normal and overdrive timings * Update api definitions * Add rough overdrive timings definition for onewire emulation * Remove one_wire_host_timing.h * Add rough overdrive timings for onewire host * Improve overdrive mode * Working overdrive mode from flipper to flipper * Update thermometer example app * Turn on otg power when running thermometer example app * Implement reset overdrive switching * Always exit out of overdrive mode * Improve overdrive timings * Fix typos * Fix reset behaviour * Use overdrive mode everywhere in DS1996 * Improve comments * Bump API version Co-authored-by: あく --- .../examples/example_thermo/example_thermo.c | 13 +- firmware/targets/f18/api_symbols.csv | 8 +- firmware/targets/f7/api_symbols.csv | 8 +- lib/ibutton/protocols/dallas/dallas_common.h | 4 +- .../protocols/dallas/protocol_dallas_base.h | 4 +- .../protocols/dallas/protocol_ds1971.c | 18 +- .../protocols/dallas/protocol_ds1990.c | 10 +- .../protocols/dallas/protocol_ds1992.c | 10 +- .../protocols/dallas/protocol_ds1996.c | 62 ++++-- .../protocols/dallas/protocol_ds_generic.c | 10 +- lib/one_wire/SConscript | 1 - lib/one_wire/one_wire_host.c | 85 ++++++-- lib/one_wire/one_wire_host.h | 89 +++++---- lib/one_wire/one_wire_host_timing.h | 30 --- lib/one_wire/one_wire_slave.c | 188 +++++++++++------- lib/one_wire/one_wire_slave.h | 84 +++++--- 16 files changed, 397 insertions(+), 227 deletions(-) delete mode 100644 lib/one_wire/one_wire_host_timing.h diff --git a/applications/examples/example_thermo/example_thermo.c b/applications/examples/example_thermo/example_thermo.c index b3bc7cd9906..4241cb59d94 100644 --- a/applications/examples/example_thermo/example_thermo.c +++ b/applications/examples/example_thermo/example_thermo.c @@ -19,9 +19,12 @@ #include #include +#include + #define UPDATE_PERIOD_MS 1000UL #define TEXT_STORE_SIZE 64U +#define DS18B20_CMD_SKIP_ROM 0xccU #define DS18B20_CMD_CONVERT 0x44U #define DS18B20_CMD_READ_SCRATCHPAD 0xbeU @@ -92,7 +95,7 @@ static void example_thermo_request_temperature(ExampleThermoContext* context) { /* After the reset, a ROM operation must follow. If there is only one device connected, the "Skip ROM" command is most appropriate (it can also be used to address all of the connected devices in some cases).*/ - onewire_host_skip(onewire); + onewire_host_write(onewire, DS18B20_CMD_SKIP_ROM); /* After the ROM operation, a device-specific command is issued. In this case, it's a request to start measuring the temperature. */ onewire_host_write(onewire, DS18B20_CMD_CONVERT); @@ -133,7 +136,7 @@ static void example_thermo_read_temperature(ExampleThermoContext* context) { /* After the reset, a ROM operation must follow. If there is only one device connected, the "Skip ROM" command is most appropriate (it can also be used to address all of the connected devices in some cases).*/ - onewire_host_skip(onewire); + onewire_host_write(onewire, DS18B20_CMD_SKIP_ROM); /* After the ROM operation, a device-specific command is issued. This time, it will be the "Read Scratchpad" command which will @@ -267,6 +270,9 @@ static void example_thermo_input_callback(InputEvent* event, void* ctx) { /* Starts the reader thread and handles the input */ static void example_thermo_run(ExampleThermoContext* context) { + /* Enable power on external pins */ + furi_hal_power_enable_otg(); + /* Configure the hardware in host mode */ onewire_host_start(context->onewire); @@ -299,6 +305,9 @@ static void example_thermo_run(ExampleThermoContext* context) { /* Reset the hardware */ onewire_host_stop(context->onewire); + + /* Disable power on external pins */ + furi_hal_power_disable_otg(); } /******************** Initialisation & startup *****************************/ diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index b6be56f60d1..e6fae33ee4f 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,19.0,, +Version,+,20.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -153,7 +153,6 @@ Header,+,lib/mlib/m-tuple.h,, Header,+,lib/mlib/m-variant.h,, Header,+,lib/one_wire/maxim_crc.h,, Header,+,lib/one_wire/one_wire_host.h,, -Header,+,lib/one_wire/one_wire_host_timing.h,, Header,+,lib/one_wire/one_wire_slave.h,, Header,+,lib/print/wrappers.h,, Header,+,lib/toolbox/args.h,, @@ -1481,8 +1480,8 @@ Function,+,onewire_host_read_bit,_Bool,OneWireHost* Function,+,onewire_host_read_bytes,void,"OneWireHost*, uint8_t*, uint16_t" Function,+,onewire_host_reset,_Bool,OneWireHost* Function,+,onewire_host_reset_search,void,OneWireHost* -Function,+,onewire_host_search,uint8_t,"OneWireHost*, uint8_t*, OneWireHostSearchMode" -Function,+,onewire_host_skip,void,OneWireHost* +Function,+,onewire_host_search,_Bool,"OneWireHost*, uint8_t*, OneWireHostSearchMode" +Function,+,onewire_host_set_overdrive,void,"OneWireHost*, _Bool" Function,+,onewire_host_start,void,OneWireHost* Function,+,onewire_host_stop,void,OneWireHost* Function,+,onewire_host_target_search,void,"OneWireHost*, uint8_t" @@ -1496,6 +1495,7 @@ Function,+,onewire_slave_receive_bit,_Bool,OneWireSlave* Function,+,onewire_slave_send,_Bool,"OneWireSlave*, const uint8_t*, size_t" Function,+,onewire_slave_send_bit,_Bool,"OneWireSlave*, _Bool" Function,+,onewire_slave_set_command_callback,void,"OneWireSlave*, OneWireSlaveCommandCallback, void*" +Function,+,onewire_slave_set_overdrive,void,"OneWireSlave*, _Bool" Function,+,onewire_slave_set_reset_callback,void,"OneWireSlave*, OneWireSlaveResetCallback, void*" Function,+,onewire_slave_set_result_callback,void,"OneWireSlave*, OneWireSlaveResultCallback, void*" Function,+,onewire_slave_start,void,OneWireSlave* diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index e6de39b1d6c..7ac9a245993 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,19.0,, +Version,+,20.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -172,7 +172,6 @@ Header,+,lib/mlib/m-variant.h,, Header,+,lib/nfc/nfc_device.h,, Header,+,lib/one_wire/maxim_crc.h,, Header,+,lib/one_wire/one_wire_host.h,, -Header,+,lib/one_wire/one_wire_host_timing.h,, Header,+,lib/one_wire/one_wire_slave.h,, Header,+,lib/print/wrappers.h,, Header,+,lib/subghz/blocks/const.h,, @@ -2062,8 +2061,8 @@ Function,+,onewire_host_read_bit,_Bool,OneWireHost* Function,+,onewire_host_read_bytes,void,"OneWireHost*, uint8_t*, uint16_t" Function,+,onewire_host_reset,_Bool,OneWireHost* Function,+,onewire_host_reset_search,void,OneWireHost* -Function,+,onewire_host_search,uint8_t,"OneWireHost*, uint8_t*, OneWireHostSearchMode" -Function,+,onewire_host_skip,void,OneWireHost* +Function,+,onewire_host_search,_Bool,"OneWireHost*, uint8_t*, OneWireHostSearchMode" +Function,+,onewire_host_set_overdrive,void,"OneWireHost*, _Bool" Function,+,onewire_host_start,void,OneWireHost* Function,+,onewire_host_stop,void,OneWireHost* Function,+,onewire_host_target_search,void,"OneWireHost*, uint8_t" @@ -2077,6 +2076,7 @@ Function,+,onewire_slave_receive_bit,_Bool,OneWireSlave* Function,+,onewire_slave_send,_Bool,"OneWireSlave*, const uint8_t*, size_t" Function,+,onewire_slave_send_bit,_Bool,"OneWireSlave*, _Bool" Function,+,onewire_slave_set_command_callback,void,"OneWireSlave*, OneWireSlaveCommandCallback, void*" +Function,+,onewire_slave_set_overdrive,void,"OneWireSlave*, _Bool" Function,+,onewire_slave_set_reset_callback,void,"OneWireSlave*, OneWireSlaveResetCallback, void*" Function,+,onewire_slave_set_result_callback,void,"OneWireSlave*, OneWireSlaveResultCallback, void*" Function,+,onewire_slave_start,void,OneWireSlave* diff --git a/lib/ibutton/protocols/dallas/dallas_common.h b/lib/ibutton/protocols/dallas/dallas_common.h index 7991a1f8ba7..6f5ff7cc010 100644 --- a/lib/ibutton/protocols/dallas/dallas_common.h +++ b/lib/ibutton/protocols/dallas/dallas_common.h @@ -1,10 +1,10 @@ #pragma once -#include - #include #include +#include + #define DALLAS_COMMON_MANUFACTURER_NAME "Dallas" #define DALLAS_COMMON_CMD_READ_ROM 0x33U diff --git a/lib/ibutton/protocols/dallas/protocol_dallas_base.h b/lib/ibutton/protocols/dallas/protocol_dallas_base.h index b4edb2b20da..55e10993605 100644 --- a/lib/ibutton/protocols/dallas/protocol_dallas_base.h +++ b/lib/ibutton/protocols/dallas/protocol_dallas_base.h @@ -2,11 +2,11 @@ #include "../protocol_common_i.h" -#include - #include #include +#include + typedef bool (*iButtonProtocolDallasReadWriteFunc)(OneWireHost*, iButtonProtocolData*); typedef void (*iButtonProtocolDallasEmulateFunc)(OneWireSlave*, iButtonProtocolData*); typedef bool (*iButtonProtocolDallasSaveFunc)(FlipperFormat*, const iButtonProtocolData*); diff --git a/lib/ibutton/protocols/dallas/protocol_ds1971.c b/lib/ibutton/protocols/dallas/protocol_ds1971.c index eb5b330b795..a806acb22ee 100644 --- a/lib/ibutton/protocols/dallas/protocol_ds1971.c +++ b/lib/ibutton/protocols/dallas/protocol_ds1971.c @@ -53,7 +53,7 @@ const iButtonProtocolDallasBase ibutton_protocol_ds1971 = { .name = DS1971_FAMILY_NAME, .read = dallas_ds1971_read, - .write_blank = NULL, /* No data to write a blank */ + .write_blank = NULL, // TODO: Implement writing to blank .write_copy = dallas_ds1971_write_copy, .emulate = dallas_ds1971_emulate, .save = dallas_ds1971_save, @@ -76,7 +76,7 @@ bool dallas_ds1971_write_copy(OneWireHost* host, iButtonProtocolData* protocol_d DS1971ProtocolData* data = protocol_data; onewire_host_reset(host); - onewire_host_skip(host); + onewire_host_write(host, DALLAS_COMMON_CMD_SKIP_ROM); // Starting writing from address 0x0000 onewire_host_write(host, DALLAS_COMMON_CMD_WRITE_SCRATCH); onewire_host_write(host, 0x00); @@ -87,7 +87,7 @@ bool dallas_ds1971_write_copy(OneWireHost* host, iButtonProtocolData* protocol_d bool pad_valid = false; if(onewire_host_reset(host)) { pad_valid = true; - onewire_host_skip(host); + onewire_host_write(host, DALLAS_COMMON_CMD_SKIP_ROM); onewire_host_write(host, DALLAS_COMMON_CMD_READ_SCRATCH); onewire_host_write(host, 0x00); @@ -103,7 +103,7 @@ bool dallas_ds1971_write_copy(OneWireHost* host, iButtonProtocolData* protocol_d // Copy scratchpad to memory and confirm if(pad_valid) { if(onewire_host_reset(host)) { - onewire_host_skip(host); + onewire_host_write(host, DALLAS_COMMON_CMD_SKIP_ROM); onewire_host_write(host, DALLAS_COMMON_CMD_COPY_SCRATCH); onewire_host_write(host, DS1971_CMD_FINALIZATION); @@ -114,10 +114,16 @@ bool dallas_ds1971_write_copy(OneWireHost* host, iButtonProtocolData* protocol_d return pad_valid; } -static void dallas_ds1971_reset_callback(void* context) { +static bool dallas_ds1971_reset_callback(bool is_short, void* context) { furi_assert(context); DS1971ProtocolData* data = context; - data->state.command_state = DallasCommonCommandStateIdle; + + if(!is_short) { + data->state.command_state = DallasCommonCommandStateIdle; + onewire_slave_set_overdrive(data->state.bus, is_short); + } + + return !is_short; } static bool dallas_ds1971_command_callback(uint8_t command, void* context) { diff --git a/lib/ibutton/protocols/dallas/protocol_ds1990.c b/lib/ibutton/protocols/dallas/protocol_ds1990.c index 0d9c937ee4d..86d39f1bd87 100644 --- a/lib/ibutton/protocols/dallas/protocol_ds1990.c +++ b/lib/ibutton/protocols/dallas/protocol_ds1990.c @@ -67,6 +67,14 @@ bool dallas_ds1990_write_blank(OneWireHost* host, iButtonProtocolData* protocol_ tm2004_write(host, data->rom_data.bytes, sizeof(DallasCommonRomData)); } +static bool dallas_ds1990_reset_callback(bool is_short, void* context) { + DS1990ProtocolData* data = context; + if(!is_short) { + onewire_slave_set_overdrive(data->state.bus, is_short); + } + return !is_short; +} + static bool dallas_ds1990_command_callback(uint8_t command, void* context) { furi_assert(context); DS1990ProtocolData* data = context; @@ -92,7 +100,7 @@ void dallas_ds1990_emulate(OneWireSlave* bus, iButtonProtocolData* protocol_data DS1990ProtocolData* data = protocol_data; data->state.bus = bus; - onewire_slave_set_reset_callback(bus, NULL, NULL); + onewire_slave_set_reset_callback(bus, dallas_ds1990_reset_callback, protocol_data); onewire_slave_set_command_callback(bus, dallas_ds1990_command_callback, protocol_data); } diff --git a/lib/ibutton/protocols/dallas/protocol_ds1992.c b/lib/ibutton/protocols/dallas/protocol_ds1992.c index 17d631259e1..0b4d4b34f23 100644 --- a/lib/ibutton/protocols/dallas/protocol_ds1992.c +++ b/lib/ibutton/protocols/dallas/protocol_ds1992.c @@ -87,10 +87,16 @@ bool dallas_ds1992_write_copy(OneWireHost* host, iButtonProtocolData* protocol_d DS1992_SRAM_DATA_SIZE); } -static void dallas_ds1992_reset_callback(void* context) { +static bool dallas_ds1992_reset_callback(bool is_short, void* context) { furi_assert(context); DS1992ProtocolData* data = context; - data->state.command_state = DallasCommonCommandStateIdle; + + if(!is_short) { + data->state.command_state = DallasCommonCommandStateIdle; + onewire_slave_set_overdrive(data->state.bus, is_short); + } + + return !is_short; } static bool dallas_ds1992_command_callback(uint8_t command, void* context) { diff --git a/lib/ibutton/protocols/dallas/protocol_ds1996.c b/lib/ibutton/protocols/dallas/protocol_ds1996.c index 74a5792c66d..5358b63e267 100644 --- a/lib/ibutton/protocols/dallas/protocol_ds1996.c +++ b/lib/ibutton/protocols/dallas/protocol_ds1996.c @@ -63,24 +63,54 @@ const iButtonProtocolDallasBase ibutton_protocol_ds1996 = { bool dallas_ds1996_read(OneWireHost* host, iButtonProtocolData* protocol_data) { DS1996ProtocolData* data = protocol_data; - return onewire_host_reset(host) && dallas_common_read_rom(host, &data->rom_data) && - dallas_common_read_mem(host, 0, data->sram_data, DS1996_SRAM_DATA_SIZE); + bool success = false; + + do { + if(!onewire_host_reset(host)) break; + if(!dallas_common_read_rom(host, &data->rom_data)) break; + if(!onewire_host_reset(host)) break; + + onewire_host_write(host, DALLAS_COMMON_CMD_OVERDRIVE_SKIP_ROM); + onewire_host_set_overdrive(host, true); + + if(!dallas_common_read_mem(host, 0, data->sram_data, DS1996_SRAM_DATA_SIZE)) break; + success = true; + } while(false); + + onewire_host_set_overdrive(host, false); + return success; } bool dallas_ds1996_write_copy(OneWireHost* host, iButtonProtocolData* protocol_data) { DS1996ProtocolData* data = protocol_data; - return dallas_common_write_mem( - host, - DS1996_COPY_SCRATCH_TIMEOUT_US, - DS1996_SRAM_PAGE_SIZE, - data->sram_data, - DS1996_SRAM_DATA_SIZE); + bool success = false; + + do { + if(!onewire_host_reset(host)) break; + + onewire_host_write(host, DALLAS_COMMON_CMD_OVERDRIVE_SKIP_ROM); + onewire_host_set_overdrive(host, true); + + if(!dallas_common_write_mem( + host, + DS1996_COPY_SCRATCH_TIMEOUT_US, + DS1996_SRAM_PAGE_SIZE, + data->sram_data, + DS1996_SRAM_DATA_SIZE)) + break; + success = true; + } while(false); + + onewire_host_set_overdrive(host, false); + return success; } -static void dallas_ds1996_reset_callback(void* context) { +static bool dallas_ds1996_reset_callback(bool is_short, void* context) { furi_assert(context); DS1996ProtocolData* data = context; data->state.command_state = DallasCommonCommandStateIdle; + onewire_slave_set_overdrive(data->state.bus, is_short); + return true; } static bool dallas_ds1996_command_callback(uint8_t command, void* context) { @@ -96,8 +126,7 @@ static bool dallas_ds1996_command_callback(uint8_t command, void* context) { } else if(data->state.command_state == DallasCommonCommandStateRomCmd) { data->state.command_state = DallasCommonCommandStateMemCmd; - dallas_common_emulate_read_mem(bus, data->sram_data, DS1996_SRAM_DATA_SIZE); - return false; + return dallas_common_emulate_read_mem(bus, data->sram_data, DS1996_SRAM_DATA_SIZE); } else { return false; @@ -120,8 +149,17 @@ static bool dallas_ds1996_command_callback(uint8_t command, void* context) { } case DALLAS_COMMON_CMD_OVERDRIVE_SKIP_ROM: + if(data->state.command_state == DallasCommonCommandStateIdle) { + data->state.command_state = DallasCommonCommandStateRomCmd; + onewire_slave_set_overdrive(bus, true); + return true; + } else { + return false; + } + + case DALLAS_COMMON_CMD_MATCH_ROM: case DALLAS_COMMON_CMD_OVERDRIVE_MATCH_ROM: - /* TODO: Overdrive mode support */ + /* TODO: Match ROM command support */ default: return false; } diff --git a/lib/ibutton/protocols/dallas/protocol_ds_generic.c b/lib/ibutton/protocols/dallas/protocol_ds_generic.c index 50fd045112d..af355f46127 100644 --- a/lib/ibutton/protocols/dallas/protocol_ds_generic.c +++ b/lib/ibutton/protocols/dallas/protocol_ds_generic.c @@ -61,6 +61,14 @@ bool ds_generic_write_blank(OneWireHost* host, iButtonProtocolData* protocol_dat return tm2004_write(host, data->rom_data.bytes, sizeof(DallasCommonRomData)); } +static bool ds_generic_reset_callback(bool is_short, void* context) { + DallasGenericProtocolData* data = context; + if(!is_short) { + onewire_slave_set_overdrive(data->state.bus, is_short); + } + return !is_short; +} + static bool ds_generic_command_callback(uint8_t command, void* context) { furi_assert(context); DallasGenericProtocolData* data = context; @@ -85,7 +93,7 @@ void ds_generic_emulate(OneWireSlave* bus, iButtonProtocolData* protocol_data) { DallasGenericProtocolData* data = protocol_data; data->state.bus = bus; - onewire_slave_set_reset_callback(bus, NULL, NULL); + onewire_slave_set_reset_callback(bus, ds_generic_reset_callback, NULL); onewire_slave_set_command_callback(bus, ds_generic_command_callback, protocol_data); } diff --git a/lib/one_wire/SConscript b/lib/one_wire/SConscript index 8d73c9dbf01..2dde9153df9 100644 --- a/lib/one_wire/SConscript +++ b/lib/one_wire/SConscript @@ -8,7 +8,6 @@ env.Append( "#/lib/one_wire", ], SDK_HEADERS=[ - File("one_wire_host_timing.h"), File("one_wire_host.h"), File("one_wire_slave.h"), File("maxim_crc.h"), diff --git a/lib/one_wire/one_wire_host.c b/lib/one_wire/one_wire_host.c index 0a4a79f5cc1..678812105c3 100644 --- a/lib/one_wire/one_wire_host.c +++ b/lib/one_wire/one_wire_host.c @@ -1,10 +1,54 @@ #include +/** + * Timings based on Application Note 126: + * https://www.analog.com/media/en/technical-documentation/tech-articles/1wire-communication-through-software--maxim-integrated.pdf + */ + #include "one_wire_host.h" -#include "one_wire_host_timing.h" + +typedef struct { + uint16_t a; + uint16_t b; + uint16_t c; + uint16_t d; + uint16_t e; + uint16_t f; + uint16_t g; + uint16_t h; + uint16_t i; + uint16_t j; +} OneWireHostTimings; + +static const OneWireHostTimings onewire_host_timings_normal = { + .a = 9, + .b = 64, + .c = 64, + .d = 14, + .e = 9, + .f = 55, + .g = 0, + .h = 480, + .i = 70, + .j = 410, +}; + +static const OneWireHostTimings onewire_host_timings_overdrive = { + .a = 1, + .b = 8, + .c = 8, + .d = 3, + .e = 1, + .f = 7, + .g = 3, + .h = 70, + .i = 9, + .j = 40, +}; struct OneWireHost { const GpioPin* gpio_pin; + const OneWireHostTimings* timings; unsigned char saved_rom[8]; /** < global search state */ uint8_t last_discrepancy; uint8_t last_family_discrepancy; @@ -15,6 +59,7 @@ OneWireHost* onewire_host_alloc(const GpioPin* gpio_pin) { OneWireHost* host = malloc(sizeof(OneWireHost)); host->gpio_pin = gpio_pin; onewire_host_reset_search(host); + onewire_host_set_overdrive(host, false); return host; } @@ -27,6 +72,8 @@ bool onewire_host_reset(OneWireHost* host) { uint8_t r; uint8_t retries = 125; + const OneWireHostTimings* timings = host->timings; + // wait until the gpio is high furi_hal_gpio_write(host->gpio_pin, true); do { @@ -35,19 +82,19 @@ bool onewire_host_reset(OneWireHost* host) { } while(!furi_hal_gpio_read(host->gpio_pin)); // pre delay - furi_delay_us(OWH_RESET_DELAY_PRE); + furi_delay_us(timings->g); // drive low furi_hal_gpio_write(host->gpio_pin, false); - furi_delay_us(OWH_RESET_DRIVE); + furi_delay_us(timings->h); // release furi_hal_gpio_write(host->gpio_pin, true); - furi_delay_us(OWH_RESET_RELEASE); + furi_delay_us(timings->i); // read and post delay r = !furi_hal_gpio_read(host->gpio_pin); - furi_delay_us(OWH_RESET_DELAY_POST); + furi_delay_us(timings->j); return r; } @@ -55,17 +102,19 @@ bool onewire_host_reset(OneWireHost* host) { bool onewire_host_read_bit(OneWireHost* host) { bool result; + const OneWireHostTimings* timings = host->timings; + // drive low furi_hal_gpio_write(host->gpio_pin, false); - furi_delay_us(OWH_READ_DRIVE); + furi_delay_us(timings->a); // release furi_hal_gpio_write(host->gpio_pin, true); - furi_delay_us(OWH_READ_RELEASE); + furi_delay_us(timings->e); // read and post delay result = furi_hal_gpio_read(host->gpio_pin); - furi_delay_us(OWH_READ_DELAY_POST); + furi_delay_us(timings->f); return result; } @@ -89,22 +138,24 @@ void onewire_host_read_bytes(OneWireHost* host, uint8_t* buffer, uint16_t count) } void onewire_host_write_bit(OneWireHost* host, bool value) { + const OneWireHostTimings* timings = host->timings; + if(value) { // drive low furi_hal_gpio_write(host->gpio_pin, false); - furi_delay_us(OWH_WRITE_1_DRIVE); + furi_delay_us(timings->a); // release furi_hal_gpio_write(host->gpio_pin, true); - furi_delay_us(OWH_WRITE_1_RELEASE); + furi_delay_us(timings->b); } else { // drive low furi_hal_gpio_write(host->gpio_pin, false); - furi_delay_us(OWH_WRITE_0_DRIVE); + furi_delay_us(timings->c); // release furi_hal_gpio_write(host->gpio_pin, true); - furi_delay_us(OWH_WRITE_0_RELEASE); + furi_delay_us(timings->d); } } @@ -122,10 +173,6 @@ void onewire_host_write_bytes(OneWireHost* host, const uint8_t* buffer, uint16_t } } -void onewire_host_skip(OneWireHost* host) { - onewire_host_write(host, 0xCC); -} - void onewire_host_start(OneWireHost* host) { furi_hal_gpio_write(host->gpio_pin, true); furi_hal_gpio_init(host->gpio_pin, GpioModeOutputOpenDrain, GpioPullNo, GpioSpeedLow); @@ -154,7 +201,7 @@ void onewire_host_target_search(OneWireHost* host, uint8_t family_code) { host->last_device_flag = false; } -uint8_t onewire_host_search(OneWireHost* host, uint8_t* new_addr, OneWireHostSearchMode mode) { +bool onewire_host_search(OneWireHost* host, uint8_t* new_addr, OneWireHostSearchMode mode) { uint8_t id_bit_number; uint8_t last_zero, rom_byte_number, search_result; uint8_t id_bit, cmp_id_bit; @@ -268,3 +315,7 @@ uint8_t onewire_host_search(OneWireHost* host, uint8_t* new_addr, OneWireHostSea return search_result; } + +void onewire_host_set_overdrive(OneWireHost* host, bool set) { + host->timings = set ? &onewire_host_timings_overdrive : &onewire_host_timings_normal; +} diff --git a/lib/one_wire/one_wire_host.h b/lib/one_wire/one_wire_host.h index dc469904ddc..9f9bd4ffd75 100644 --- a/lib/one_wire/one_wire_host.h +++ b/lib/one_wire/one_wire_host.h @@ -15,114 +15,115 @@ extern "C" { typedef enum { OneWireHostSearchModeConditional = 0, /**< Search for alarmed device */ - OneWireHostSearchModeNormal = 1, /**< Search all devices */ + OneWireHostSearchModeNormal = 1, /**< Search for all devices */ } OneWireHostSearchMode; typedef struct OneWireHost OneWireHost; /** - * Allocate onewire host bus - * @param pin - * @return OneWireHost* + * Allocate OneWireHost instance + * @param [in] gpio_pin connection pin + * @return pointer to OneWireHost instance */ OneWireHost* onewire_host_alloc(const GpioPin* gpio_pin); /** - * Deallocate onewire host bus - * @param host + * Destroy OneWireHost instance, free resources + * @param [in] host pointer to OneWireHost instance */ void onewire_host_free(OneWireHost* host); /** - * Reset bus - * @param host - * @return bool + * Reset the 1-Wire bus + * @param [in] host pointer to OneWireHost instance + * @return true if presence was detected, false otherwise */ bool onewire_host_reset(OneWireHost* host); /** * Read one bit - * @param host - * @return bool + * @param [in] host pointer to OneWireHost instance + * @return received bit value */ bool onewire_host_read_bit(OneWireHost* host); /** * Read one byte - * @param host - * @return uint8_t + * @param [in] host pointer to OneWireHost instance + * @return received byte value */ uint8_t onewire_host_read(OneWireHost* host); /** - * Read many bytes - * @param host - * @param buffer - * @param count + * Read one or more bytes + * @param [in] host pointer to OneWireHost instance + * @param [out] buffer received data buffer + * @param [in] count number of bytes to read */ void onewire_host_read_bytes(OneWireHost* host, uint8_t* buffer, uint16_t count); /** * Write one bit - * @param host - * @param value + * @param [in] host pointer to OneWireHost instance + * @param value bit value to write */ void onewire_host_write_bit(OneWireHost* host, bool value); /** * Write one byte - * @param host - * @param value + * @param [in] host pointer to OneWireHost instance + * @param value byte value to write */ void onewire_host_write(OneWireHost* host, uint8_t value); /** - * Write many bytes - * @param host - * @param buffer - * @param count + * Write one or more bytes + * @param [in] host pointer to OneWireHost instance + * @param [in] buffer pointer to the data to write + * @param [in] count size of the data to write */ void onewire_host_write_bytes(OneWireHost* host, const uint8_t* buffer, uint16_t count); -/** - * Skip ROM command - * @param host - */ -void onewire_host_skip(OneWireHost* host); - /** * Start working with the bus - * @param host + * @param [in] host pointer to OneWireHost instance */ void onewire_host_start(OneWireHost* host); /** * Stop working with the bus - * @param host + * @param [in] host pointer to OneWireHost instance */ void onewire_host_stop(OneWireHost* host); /** - * - * @param host + * Reset previous search results + * @param [in] host pointer to OneWireHost instance */ void onewire_host_reset_search(OneWireHost* host); /** - * - * @param host - * @param family_code + * Set the family code to search for + * @param [in] host pointer to OneWireHost instance + * @param [in] family_code device family code */ void onewire_host_target_search(OneWireHost* host, uint8_t family_code); /** - * - * @param host - * @param newAddr - * @param mode - * @return uint8_t + * Search for devices on the 1-Wire bus + * @param [in] host pointer to OneWireHost instance + * @param [out] new_addr pointer to the buffer to contain the unique ROM of the found device + * @param [in] mode search mode + * @return true on success, false otherwise + */ +bool onewire_host_search(OneWireHost* host, uint8_t* new_addr, OneWireHostSearchMode mode); + +/** + * Enable overdrive mode + * @param [in] host pointer to OneWireHost instance + * @param [in] set true to turn overdrive on, false to turn it off */ -uint8_t onewire_host_search(OneWireHost* host, uint8_t* new_addr, OneWireHostSearchMode mode); +void onewire_host_set_overdrive(OneWireHost* host, bool set); #ifdef __cplusplus } diff --git a/lib/one_wire/one_wire_host_timing.h b/lib/one_wire/one_wire_host_timing.h deleted file mode 100644 index f95dd3561e1..00000000000 --- a/lib/one_wire/one_wire_host_timing.h +++ /dev/null @@ -1,30 +0,0 @@ -/** - * @file one_wire_host_timing.h - * - * 1-Wire library, timing list - */ - -#pragma once - -#define OWH_TIMING_A 9 -#define OWH_TIMING_B 64 -#define OWH_TIMING_C 64 -#define OWH_TIMING_D 14 -#define OWH_TIMING_E 9 -#define OWH_TIMING_F 55 -#define OWH_TIMING_G 0 -#define OWH_TIMING_H 480 -#define OWH_TIMING_I 70 -#define OWH_TIMING_J 410 - -#define OWH_WRITE_1_DRIVE OWH_TIMING_A -#define OWH_WRITE_1_RELEASE OWH_TIMING_B -#define OWH_WRITE_0_DRIVE OWH_TIMING_C -#define OWH_WRITE_0_RELEASE OWH_TIMING_D -#define OWH_READ_DRIVE 3 -#define OWH_READ_RELEASE OWH_TIMING_E -#define OWH_READ_DELAY_POST OWH_TIMING_F -#define OWH_RESET_DELAY_PRE OWH_TIMING_G -#define OWH_RESET_DRIVE OWH_TIMING_H -#define OWH_RESET_RELEASE OWH_TIMING_I -#define OWH_RESET_DELAY_POST OWH_TIMING_J diff --git a/lib/one_wire/one_wire_slave.c b/lib/one_wire/one_wire_slave.c index d1676cf3b81..733b36e30e2 100644 --- a/lib/one_wire/one_wire_slave.c +++ b/lib/one_wire/one_wire_slave.c @@ -3,20 +3,7 @@ #include #include -#define ONEWIRE_TRSTL_MIN 270 /* Minimum Reset Low time */ -#define ONEWIRE_TRSTL_MAX 1200 /* Maximum Reset Low time */ - -#define ONEWIRE_TPDH_TYP 20 /* Typical Presence Detect High time */ -#define ONEWIRE_TPDL_MIN 100 /* Minimum Presence Detect Low time */ -#define ONEWIRE_TPDL_MAX 480 /* Maximum Presence Detect Low time */ - -#define ONEWIRE_TSLOT_MIN 60 /* Minimum Read/Write Slot time */ -#define ONEWIRE_TSLOT_MAX 135 /* Maximum Read/Write Slot time */ - -#define ONEWIRE_TW1L_MAX 20 /* Maximum Master Write 1 time */ -#define ONEWIRE_TRL_TMSR_MAX 30 /* Maximum Master Read Low + Read Sample time */ - -#define ONEWIRE_TH_TIMEOUT 15000 /* Maximum time before general timeout */ +#define TH_TIMEOUT_MAX 15000 /* Maximum time before general timeout */ typedef enum { OneWireSlaveErrorNone = 0, @@ -26,10 +13,29 @@ typedef enum { OneWireSlaveErrorTimeout, } OneWireSlaveError; +typedef struct { + uint16_t trstl_min; /* Minimum Reset Low time */ + uint16_t trstl_max; /* Maximum Reset Low time */ + + uint16_t tpdh_typ; /* Typical Presence Detect High time */ + uint16_t tpdl_min; /* Minimum Presence Detect Low time */ + uint16_t tpdl_max; /* Maximum Presence Detect Low time */ + + uint16_t tslot_min; /* Minimum Read/Write Slot time */ + uint16_t tslot_max; /* Maximum Read/Write Slot time */ + + uint16_t tw1l_max; /* Maximum Master Write 1 time */ + uint16_t trl_tmsr_max; /* Maximum Master Read Low + Read Sample time */ +} OneWireSlaveTimings; + struct OneWireSlave { const GpioPin* gpio_pin; + const OneWireSlaveTimings* timings; OneWireSlaveError error; + bool is_first_reset; + bool is_short_reset; + OneWireSlaveResetCallback reset_callback; OneWireSlaveCommandCallback command_callback; OneWireSlaveResultCallback result_callback; @@ -39,42 +45,72 @@ struct OneWireSlave { void* command_callback_context; }; +static const OneWireSlaveTimings onewire_slave_timings_normal = { + .trstl_min = 270, + .trstl_max = 1200, + + .tpdh_typ = 20, + .tpdl_min = 100, + .tpdl_max = 480, + + .tslot_min = 60, + .tslot_max = 135, + + .tw1l_max = 20, + .trl_tmsr_max = 30, +}; + +static const OneWireSlaveTimings onewire_slave_timings_overdrive = { + .trstl_min = 48, + .trstl_max = 80, + + .tpdh_typ = 0, + .tpdl_min = 8, + .tpdl_max = 24, + + .tslot_min = 6, + .tslot_max = 16, + + .tw1l_max = 2, + .trl_tmsr_max = 3, +}; + /*********************** PRIVATE ***********************/ -static uint32_t - onewire_slave_wait_while_gpio_is(OneWireSlave* bus, uint32_t time, const bool pin_value) { - uint32_t start = DWT->CYCCNT; - uint32_t time_ticks = time * furi_hal_cortex_instructions_per_microsecond(); - uint32_t time_captured; +static bool + onewire_slave_wait_while_gpio_is(OneWireSlave* bus, uint32_t time_us, const bool pin_value) { + const uint32_t time_start = DWT->CYCCNT; + const uint32_t time_ticks = time_us * furi_hal_cortex_instructions_per_microsecond(); + + uint32_t time_elapsed; do { //-V1044 - time_captured = DWT->CYCCNT; + time_elapsed = DWT->CYCCNT - time_start; if(furi_hal_gpio_read(bus->gpio_pin) != pin_value) { - uint32_t remaining_time = time_ticks - (time_captured - start); - remaining_time /= furi_hal_cortex_instructions_per_microsecond(); - return remaining_time; + return time_ticks >= time_elapsed; } - } while((time_captured - start) < time_ticks); + } while(time_elapsed < time_ticks); - return 0; + return false; } -static bool onewire_slave_show_presence(OneWireSlave* bus) { +static inline bool onewire_slave_show_presence(OneWireSlave* bus) { + const OneWireSlaveTimings* timings = bus->timings; // wait until the bus is high (might return immediately) - onewire_slave_wait_while_gpio_is(bus, ONEWIRE_TRSTL_MAX, false); + onewire_slave_wait_while_gpio_is(bus, timings->trstl_max, false); // wait while master delay presence check - furi_delay_us(ONEWIRE_TPDH_TYP); + furi_delay_us(timings->tpdh_typ); // show presence furi_hal_gpio_write(bus->gpio_pin, false); - furi_delay_us(ONEWIRE_TPDL_MIN); + furi_delay_us(timings->tpdl_min); furi_hal_gpio_write(bus->gpio_pin, true); // somebody also can show presence - const uint32_t wait_low_time = ONEWIRE_TPDL_MAX - ONEWIRE_TPDL_MIN; + const uint32_t wait_low_time = timings->tpdl_max - timings->tpdl_min; // so we will wait - if(onewire_slave_wait_while_gpio_is(bus, wait_low_time, false) == 0) { + if(!onewire_slave_wait_while_gpio_is(bus, wait_low_time, false)) { bus->error = OneWireSlaveErrorPresenceConflict; return false; } @@ -85,27 +121,36 @@ static bool onewire_slave_show_presence(OneWireSlave* bus) { static inline bool onewire_slave_receive_and_process_command(OneWireSlave* bus) { /* Reset condition detected, send a presence pulse and reset protocol state */ if(bus->error == OneWireSlaveErrorResetInProgress) { - if(onewire_slave_show_presence(bus)) { - bus->error = OneWireSlaveErrorNone; + if(!bus->is_first_reset) { + /* Guess the reset type */ + bus->is_short_reset = onewire_slave_wait_while_gpio_is( + bus, + onewire_slave_timings_overdrive.trstl_max - + onewire_slave_timings_overdrive.tslot_max, + false); + } else { + bus->is_first_reset = false; + } - if(bus->reset_callback != NULL) { - bus->reset_callback(bus->reset_callback_context); - } + furi_assert(bus->reset_callback); - return true; + if(bus->reset_callback(bus->is_short_reset, bus->reset_callback_context)) { + if(onewire_slave_show_presence(bus)) { + bus->error = OneWireSlaveErrorNone; + return true; + } } } else if(bus->error == OneWireSlaveErrorNone) { uint8_t command; - if(!onewire_slave_receive(bus, &command, 1)) { - /* Upon failure, request an additional iteration to - choose the appropriate action by checking bus->error */ - return true; - } else if(bus->command_callback) { - return bus->command_callback(command, bus->command_callback_context); - } else { - bus->error = OneWireSlaveErrorInvalidCommand; + if(onewire_slave_receive(bus, &command, sizeof(command))) { + furi_assert(bus->command_callback); + if(bus->command_callback(command, bus->command_callback_context)) { + return true; + } } + + return (bus->error == OneWireSlaveErrorResetInProgress); } return false; @@ -115,9 +160,6 @@ static inline bool onewire_slave_bus_start(OneWireSlave* bus) { FURI_CRITICAL_ENTER(); furi_hal_gpio_init(bus->gpio_pin, GpioModeOutputOpenDrain, GpioPullNo, GpioSpeedLow); - /* Start in Reset state in order to send a presence pulse immediately */ - bus->error = OneWireSlaveErrorResetInProgress; - while(onewire_slave_receive_and_process_command(bus)) ; @@ -139,7 +181,15 @@ static void onewire_slave_exti_callback(void* context) { const uint32_t pulse_length = (DWT->CYCCNT - pulse_start) / furi_hal_cortex_instructions_per_microsecond(); - if((pulse_length >= ONEWIRE_TRSTL_MIN) && pulse_length <= (ONEWIRE_TRSTL_MAX)) { + if((pulse_length >= onewire_slave_timings_overdrive.trstl_min) && + (pulse_length <= onewire_slave_timings_normal.trstl_max)) { + /* Start in reset state in order to send a presence pulse immediately */ + bus->error = OneWireSlaveErrorResetInProgress; + /* Determine reset type (chooses speed mode if supported by the emulated device) */ + bus->is_short_reset = pulse_length <= onewire_slave_timings_overdrive.trstl_max; + /* Initial reset allows going directly into overdrive mode */ + bus->is_first_reset = true; + const bool result = onewire_slave_bus_start(bus); if(result && bus->result_callback != NULL) { @@ -158,6 +208,7 @@ OneWireSlave* onewire_slave_alloc(const GpioPin* gpio_pin) { OneWireSlave* bus = malloc(sizeof(OneWireSlave)); bus->gpio_pin = gpio_pin; + bus->timings = &onewire_slave_timings_normal; bus->error = OneWireSlaveErrorNone; return bus; @@ -205,52 +256,45 @@ void onewire_slave_set_result_callback( } bool onewire_slave_receive_bit(OneWireSlave* bus) { + const OneWireSlaveTimings* timings = bus->timings; // wait while bus is low - uint32_t time = ONEWIRE_TSLOT_MAX; - time = onewire_slave_wait_while_gpio_is(bus, time, false); - if(time == 0) { + if(!onewire_slave_wait_while_gpio_is(bus, timings->tslot_max, false)) { bus->error = OneWireSlaveErrorResetInProgress; return false; } // wait while bus is high - time = ONEWIRE_TH_TIMEOUT; - time = onewire_slave_wait_while_gpio_is(bus, time, true); - if(time == 0) { + if(!onewire_slave_wait_while_gpio_is(bus, TH_TIMEOUT_MAX, true)) { bus->error = OneWireSlaveErrorTimeout; return false; } // wait a time of zero - time = ONEWIRE_TW1L_MAX; - time = onewire_slave_wait_while_gpio_is(bus, time, false); - - return (time > 0); + return onewire_slave_wait_while_gpio_is(bus, timings->tw1l_max, false); } bool onewire_slave_send_bit(OneWireSlave* bus, bool value) { + const OneWireSlaveTimings* timings = bus->timings; // wait while bus is low - uint32_t time = ONEWIRE_TSLOT_MAX; - time = onewire_slave_wait_while_gpio_is(bus, time, false); - if(time == 0) { + if(!onewire_slave_wait_while_gpio_is(bus, timings->tslot_max, false)) { bus->error = OneWireSlaveErrorResetInProgress; return false; } // wait while bus is high - time = ONEWIRE_TH_TIMEOUT; - time = onewire_slave_wait_while_gpio_is(bus, time, true); - if(time == 0) { + if(!onewire_slave_wait_while_gpio_is(bus, TH_TIMEOUT_MAX, true)) { bus->error = OneWireSlaveErrorTimeout; return false; } // choose write time + uint32_t time; + if(!value) { furi_hal_gpio_write(bus->gpio_pin, false); - time = ONEWIRE_TRL_TMSR_MAX; + time = timings->trl_tmsr_max; } else { - time = ONEWIRE_TSLOT_MIN; + time = timings->tslot_min; } // hold line for ZERO or ONE time @@ -301,3 +345,13 @@ bool onewire_slave_receive(OneWireSlave* bus, uint8_t* data, size_t data_size) { } return true; } + +void onewire_slave_set_overdrive(OneWireSlave* bus, bool set) { + const OneWireSlaveTimings* new_timings = set ? &onewire_slave_timings_overdrive : + &onewire_slave_timings_normal; + if(bus->timings != new_timings) { + /* Prevent erroneous reset by waiting for the previous time slot to finish */ + onewire_slave_wait_while_gpio_is(bus, bus->timings->tslot_max, false); + bus->timings = new_timings; + } +} diff --git a/lib/one_wire/one_wire_slave.h b/lib/one_wire/one_wire_slave.h index 914cd9335ec..21114b912cc 100644 --- a/lib/one_wire/one_wire_slave.h +++ b/lib/one_wire/one_wire_slave.h @@ -18,68 +18,85 @@ extern "C" { typedef struct OneWireDevice OneWireDevice; typedef struct OneWireSlave OneWireSlave; -typedef void (*OneWireSlaveResetCallback)(void* context); -typedef void (*OneWireSlaveResultCallback)(void* context); +typedef bool (*OneWireSlaveResetCallback)(bool is_short, void* context); typedef bool (*OneWireSlaveCommandCallback)(uint8_t command, void* context); +typedef void (*OneWireSlaveResultCallback)(void* context); /** - * Allocate onewire slave - * @param gpio_pin - * @return OneWireSlave* + * Allocate OneWireSlave instance + * @param [in] gpio_pin connection pin + * @return pointer to OneWireSlave instance */ OneWireSlave* onewire_slave_alloc(const GpioPin* gpio_pin); /** - * Free onewire slave - * @param bus + * Destroy OneWireSlave instance, free resources + * @param [in] bus pointer to OneWireSlave instance */ void onewire_slave_free(OneWireSlave* bus); /** * Start working with the bus - * @param bus + * @param [in] bus pointer to OneWireSlave instance */ void onewire_slave_start(OneWireSlave* bus); /** * Stop working with the bus - * @param bus + * @param [in] bus pointer to OneWireSlave instance */ void onewire_slave_stop(OneWireSlave* bus); /** - * TODO: description comment + * Receive one bit + * @param [in] bus pointer to OneWireSlave instance + * @return received bit value */ bool onewire_slave_receive_bit(OneWireSlave* bus); /** - * TODO: description comment + * Send one bit + * @param [in] bus pointer to OneWireSlave instance + * @param [in] value bit value to send + * @return true on success, false on failure */ bool onewire_slave_send_bit(OneWireSlave* bus, bool value); /** - * Send data - * @param bus - * @param data - * @param data_size - * @return bool + * Send one or more bytes of data + * @param [in] bus pointer to OneWireSlave instance + * @param [in] data pointer to the data to send + * @param [in] data_size size of the data to send + * @return true on success, false on failure */ bool onewire_slave_send(OneWireSlave* bus, const uint8_t* data, size_t data_size); /** - * Receive data - * @param bus - * @param data - * @param data_size - * @return bool + * Receive one or more bytes of data + * @param [in] bus pointer to OneWireSlave instance + * @param [out] data pointer to the receive buffer + * @param [in] data_size number of bytes to receive + * @return true on success, false on failure */ bool onewire_slave_receive(OneWireSlave* bus, uint8_t* data, size_t data_size); /** - * Set a callback to be called on each reset - * @param bus - * @param callback - * @param context + * Enable overdrive mode + * @param [in] bus pointer to OneWireSlave instance + * @param [in] set true to turn overdrive on, false to turn it off + */ +void onewire_slave_set_overdrive(OneWireSlave* bus, bool set); + +/** + * Set a callback function to be called on each reset. + * The return value of the callback determines whether the emulated device + * supports the short reset (passed as the is_short parameter). + * In most applications, it should also call onewire_slave_set_overdrive() + * to set the appropriate speed mode. + * + * @param [in] bus pointer to OneWireSlave instance + * @param [in] callback pointer to a callback function + * @param [in] context additional parameter to be passed to the callback */ void onewire_slave_set_reset_callback( OneWireSlave* bus, @@ -87,10 +104,13 @@ void onewire_slave_set_reset_callback( void* context); /** - * Set a callback to be called on each command - * @param bus - * @param callback - * @param context + * Set a callback function to be called on each command. + * The return value of the callback determines whether further operation + * is possible. As a rule of thumb, return true unless a critical error happened. + * + * @param [in] bus pointer to OneWireSlave instance + * @param [in] callback pointer to a callback function + * @param [in] context additional parameter to be passed to the callback */ void onewire_slave_set_command_callback( OneWireSlave* bus, @@ -99,9 +119,9 @@ void onewire_slave_set_command_callback( /** * Set a callback to report emulation success - * @param bus - * @param result_cb - * @param context + * @param [in] bus pointer to OneWireSlave instance + * @param [in] result_cb pointer to a callback function + * @param [in] context additional parameter to be passed to the callback */ void onewire_slave_set_result_callback( OneWireSlave* bus, From 7bf0a4786c6987b756683f6ee0af866833a8b4cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Thu, 23 Mar 2023 02:00:48 +0900 Subject: [PATCH 491/824] [FL-3152] Screen streaming improvements (#2498) * Rpc: reserve some bandwidth when screen streaming * Move furi_hal_compress to toolbox/comporess * Lib: heatshrink as external submodule, compile warnings fixes, better buffer management * Lib: cleanup compressor definitions * Rpc: add canvas orientation support * Format Sources --- .gitmodules | 3 + applications/services/gui/canvas.c | 9 +- applications/services/gui/canvas_i.h | 2 + applications/services/gui/gui.c | 1 + applications/services/gui/gui.h | 6 +- applications/services/rpc/rpc_gui.c | 26 +- assets/protobuf | 2 +- firmware/targets/f18/api_symbols.csv | 13 +- firmware/targets/f18/furi_hal/furi_hal.c | 34 +- .../targets/f18/furi_hal/furi_hal_resources.c | 4 + firmware/targets/f7/api_symbols.csv | 7 - firmware/targets/f7/furi_hal/furi_hal.c | 26 - .../targets/f7/furi_hal/furi_hal_ibutton.c | 3 + .../targets/f7/furi_hal/furi_hal_resources.c | 4 + firmware/targets/f7/src/dfu.c | 7 +- firmware/targets/f7/src/recovery.c | 6 +- firmware/targets/furi_hal_include/furi_hal.h | 1 - .../furi_hal_include/furi_hal_compress.h | 87 --- lib/err.h | 4 + lib/heatshrink | 1 + lib/heatshrink/heatshrink_common.h | 20 - lib/heatshrink/heatshrink_config.h | 28 - lib/heatshrink/heatshrink_decoder.c | 364 ----------- lib/heatshrink/heatshrink_decoder.h | 100 --- lib/heatshrink/heatshrink_encoder.c | 602 ------------------ lib/heatshrink/heatshrink_encoder.h | 109 ---- lib/misc.scons | 7 +- .../toolbox/compress.c | 127 ++-- lib/toolbox/compress.h | 96 +++ 29 files changed, 242 insertions(+), 1457 deletions(-) delete mode 100644 firmware/targets/furi_hal_include/furi_hal_compress.h create mode 100644 lib/err.h create mode 160000 lib/heatshrink delete mode 100644 lib/heatshrink/heatshrink_common.h delete mode 100644 lib/heatshrink/heatshrink_config.h delete mode 100644 lib/heatshrink/heatshrink_decoder.c delete mode 100644 lib/heatshrink/heatshrink_decoder.h delete mode 100644 lib/heatshrink/heatshrink_encoder.c delete mode 100644 lib/heatshrink/heatshrink_encoder.h rename firmware/targets/f7/furi_hal/furi_hal_compress.c => lib/toolbox/compress.c (67%) create mode 100644 lib/toolbox/compress.h diff --git a/.gitmodules b/.gitmodules index 56368cd588f..3a15177bd18 100644 --- a/.gitmodules +++ b/.gitmodules @@ -31,3 +31,6 @@ [submodule "applications/external/dap_link/lib/free-dap"] path = applications/external/dap_link/lib/free-dap url = https://github.com/ataradov/free-dap.git +[submodule "lib/heatshrink"] + path = lib/heatshrink + url = https://github.com/flipperdevices/heatshrink.git diff --git a/applications/services/gui/canvas.c b/applications/services/gui/canvas.c index 9c29a39fd9f..40797c08674 100644 --- a/applications/services/gui/canvas.c +++ b/applications/services/gui/canvas.c @@ -17,6 +17,7 @@ const CanvasFontParameters canvas_font_params[FontTotalNumber] = { Canvas* canvas_init() { Canvas* canvas = malloc(sizeof(Canvas)); + canvas->compress_icon = compress_icon_alloc(); // Setup u8g2 u8g2_Setup_st756x_flipper(&canvas->fb, U8G2_R0, u8x8_hw_spi_stm32, u8g2_gpio_and_delay_stm32); @@ -35,6 +36,7 @@ Canvas* canvas_init() { void canvas_free(Canvas* canvas) { furi_assert(canvas); + compress_icon_free(canvas->compress_icon); free(canvas); } @@ -218,7 +220,7 @@ void canvas_draw_bitmap( x += canvas->offset_x; y += canvas->offset_y; uint8_t* bitmap_data = NULL; - furi_hal_compress_icon_decode(compressed_bitmap_data, &bitmap_data); + compress_icon_decode(canvas->compress_icon, compressed_bitmap_data, &bitmap_data); u8g2_DrawXBM(&canvas->fb, x, y, width, height, bitmap_data); } @@ -233,7 +235,8 @@ void canvas_draw_icon_animation( x += canvas->offset_x; y += canvas->offset_y; uint8_t* icon_data = NULL; - furi_hal_compress_icon_decode(icon_animation_get_data(icon_animation), &icon_data); + compress_icon_decode( + canvas->compress_icon, icon_animation_get_data(icon_animation), &icon_data); u8g2_DrawXBM( &canvas->fb, x, @@ -250,7 +253,7 @@ void canvas_draw_icon(Canvas* canvas, uint8_t x, uint8_t y, const Icon* icon) { x += canvas->offset_x; y += canvas->offset_y; uint8_t* icon_data = NULL; - furi_hal_compress_icon_decode(icon_get_data(icon), &icon_data); + compress_icon_decode(canvas->compress_icon, icon_get_data(icon), &icon_data); u8g2_DrawXBM(&canvas->fb, x, y, icon_get_width(icon), icon_get_height(icon), icon_data); } diff --git a/applications/services/gui/canvas_i.h b/applications/services/gui/canvas_i.h index 12cabfa7d9c..39e7021bc90 100644 --- a/applications/services/gui/canvas_i.h +++ b/applications/services/gui/canvas_i.h @@ -7,6 +7,7 @@ #include "canvas.h" #include +#include /** Canvas structure */ @@ -17,6 +18,7 @@ struct Canvas { uint8_t offset_y; uint8_t width; uint8_t height; + CompressIcon* compress_icon; }; /** Allocate memory and initialize canvas diff --git a/applications/services/gui/gui.c b/applications/services/gui/gui.c index 24b48a837dc..392011620ab 100644 --- a/applications/services/gui/gui.c +++ b/applications/services/gui/gui.c @@ -250,6 +250,7 @@ static void gui_redraw(Gui* gui) { p->callback( canvas_get_buffer(gui->canvas), canvas_get_buffer_size(gui->canvas), + canvas_get_orientation(gui->canvas), p->context); } } while(false); diff --git a/applications/services/gui/gui.h b/applications/services/gui/gui.h index d7d73f27b69..1b5987edace 100644 --- a/applications/services/gui/gui.h +++ b/applications/services/gui/gui.h @@ -27,7 +27,11 @@ typedef enum { } GuiLayer; /** Gui Canvas Commit Callback */ -typedef void (*GuiCanvasCommitCallback)(uint8_t* data, size_t size, void* context); +typedef void (*GuiCanvasCommitCallback)( + uint8_t* data, + size_t size, + CanvasOrientation orientation, + void* context); #define RECORD_GUI "gui" diff --git a/applications/services/rpc/rpc_gui.c b/applications/services/rpc/rpc_gui.c index c2af425e93a..0c70702cf4e 100644 --- a/applications/services/rpc/rpc_gui.c +++ b/applications/services/rpc/rpc_gui.c @@ -33,8 +33,18 @@ typedef struct { uint32_t input_counter; } RpcGuiSystem; -static void - rpc_system_gui_screen_stream_frame_callback(uint8_t* data, size_t size, void* context) { +static const PB_Gui_ScreenOrientation rpc_system_gui_screen_orientation_map[] = { + [CanvasOrientationHorizontal] = PB_Gui_ScreenOrientation_HORIZONTAL, + [CanvasOrientationHorizontalFlip] = PB_Gui_ScreenOrientation_HORIZONTAL_FLIP, + [CanvasOrientationVertical] = PB_Gui_ScreenOrientation_VERTICAL, + [CanvasOrientationVerticalFlip] = PB_Gui_ScreenOrientation_VERTICAL_FLIP, +}; + +static void rpc_system_gui_screen_stream_frame_callback( + uint8_t* data, + size_t size, + CanvasOrientation orientation, + void* context) { furi_assert(data); furi_assert(context); @@ -44,6 +54,8 @@ static void furi_assert(size == rpc_gui->transmit_frame->content.gui_screen_frame.data->size); memcpy(buffer, data, size); + rpc_gui->transmit_frame->content.gui_screen_frame.orientation = + rpc_system_gui_screen_orientation_map[orientation]; furi_thread_flags_set(furi_thread_get_id(rpc_gui->transmit_thread), RpcGuiWorkerFlagTransmit); } @@ -53,12 +65,22 @@ static int32_t rpc_system_gui_screen_stream_frame_transmit_thread(void* context) RpcGuiSystem* rpc_gui = (RpcGuiSystem*)context; + uint32_t transmit_time = 0; while(true) { uint32_t flags = furi_thread_flags_wait(RpcGuiWorkerFlagAny, FuriFlagWaitAny, FuriWaitForever); + if(flags & RpcGuiWorkerFlagTransmit) { + transmit_time = furi_get_tick(); rpc_send(rpc_gui->session, rpc_gui->transmit_frame); + transmit_time = furi_get_tick() - transmit_time; + + // Guaranteed bandwidth reserve + uint32_t extra_delay = transmit_time / 20; + if(extra_delay > 500) extra_delay = 500; + if(extra_delay) furi_delay_tick(extra_delay); } + if(flags & RpcGuiWorkerFlagExit) { break; } diff --git a/assets/protobuf b/assets/protobuf index 64606602370..1f6b4a08c5d 160000 --- a/assets/protobuf +++ b/assets/protobuf @@ -1 +1 @@ -Subproject commit 6460660237005d02d5c223835659b40e373bade9 +Subproject commit 1f6b4a08c5d05c2b17926a3ba79f60109638932f diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index e6fae33ee4f..40e23a747f0 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -57,7 +57,6 @@ Header,+,firmware/targets/furi_hal_include/furi_hal.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_bt.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_bt_hid.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_bt_serial.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_compress.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_cortex.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_crypto.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_debug.h,, @@ -876,12 +875,12 @@ Function,-,furi_hal_clock_resume_tick,void, Function,-,furi_hal_clock_suspend_tick,void, Function,-,furi_hal_clock_switch_to_hsi,void, Function,-,furi_hal_clock_switch_to_pll,void, -Function,-,furi_hal_compress_alloc,FuriHalCompress*,uint16_t -Function,-,furi_hal_compress_decode,_Bool,"FuriHalCompress*, uint8_t*, size_t, uint8_t*, size_t, size_t*" -Function,-,furi_hal_compress_encode,_Bool,"FuriHalCompress*, uint8_t*, size_t, uint8_t*, size_t, size_t*" -Function,-,furi_hal_compress_free,void,FuriHalCompress* -Function,-,furi_hal_compress_icon_decode,void,"const uint8_t*, uint8_t**" -Function,-,furi_hal_compress_icon_init,void, +Function,-,compress_alloc,Compress*,uint16_t +Function,-,compress_decode,_Bool,"Compress*, uint8_t*, size_t, uint8_t*, size_t, size_t*" +Function,-,compress_encode,_Bool,"Compress*, uint8_t*, size_t, uint8_t*, size_t, size_t*" +Function,-,compress_free,void,Compress* +Function,-,compress_icon_decode,void,"const uint8_t*, uint8_t**" +Function,-,compress_icon_init,void, Function,+,furi_hal_console_disable,void, Function,+,furi_hal_console_enable,void, Function,+,furi_hal_console_init,void, diff --git a/firmware/targets/f18/furi_hal/furi_hal.c b/firmware/targets/f18/furi_hal/furi_hal.c index 2c255fa0de8..4064dd64726 100644 --- a/firmware/targets/f18/furi_hal/furi_hal.c +++ b/firmware/targets/f18/furi_hal/furi_hal.c @@ -1,5 +1,6 @@ #include #include +#include #include @@ -7,29 +8,20 @@ void furi_hal_init_early() { furi_hal_cortex_init_early(); - furi_hal_clock_init_early(); - furi_hal_resources_init_early(); - furi_hal_os_init(); - furi_hal_spi_config_init_early(); - furi_hal_i2c_init_early(); furi_hal_light_init(); - furi_hal_rtc_init_early(); } void furi_hal_deinit_early() { furi_hal_rtc_deinit_early(); - furi_hal_i2c_deinit_early(); furi_hal_spi_config_deinit_early(); - furi_hal_resources_deinit_early(); - furi_hal_clock_deinit_early(); } @@ -38,40 +30,24 @@ void furi_hal_init() { furi_hal_clock_init(); furi_hal_console_init(); furi_hal_rtc_init(); - furi_hal_interrupt_init(); - furi_hal_flash_init(); - furi_hal_resources_init(); - FURI_LOG_I(TAG, "GPIO OK"); - furi_hal_version_init(); - furi_hal_spi_config_init(); furi_hal_spi_dma_init(); - furi_hal_speaker_init(); - FURI_LOG_I(TAG, "Speaker OK"); - furi_hal_crypto_init(); - - // USB -#ifndef FURI_RAM_EXEC - furi_hal_usb_init(); - FURI_LOG_I(TAG, "USB OK"); -#endif - furi_hal_i2c_init(); - - // High Level furi_hal_power_init(); furi_hal_light_init(); + furi_hal_bt_init(); + furi_hal_memory_init(); + #ifndef FURI_RAM_EXEC + furi_hal_usb_init(); furi_hal_vibro_init(); #endif - furi_hal_bt_init(); - furi_hal_compress_icon_init(); } void furi_hal_switch(void* address) { diff --git a/firmware/targets/f18/furi_hal/furi_hal_resources.c b/firmware/targets/f18/furi_hal/furi_hal_resources.c index 41cc80bfba1..abb258cb114 100644 --- a/firmware/targets/f18/furi_hal/furi_hal_resources.c +++ b/firmware/targets/f18/furi_hal/furi_hal_resources.c @@ -4,6 +4,8 @@ #include #include +#define TAG "FuriHalResources" + const GpioPin vibro_gpio = {.port = GPIOA, .pin = LL_GPIO_PIN_8}; const GpioPin ibutton_gpio = {.port = GPIOB, .pin = LL_GPIO_PIN_14}; @@ -198,6 +200,8 @@ void furi_hal_resources_init() { NVIC_SetPriority(EXTI15_10_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 5, 0)); NVIC_EnableIRQ(EXTI15_10_IRQn); + + FURI_LOG_I(TAG, "Init OK"); } int32_t furi_hal_resources_get_ext_pin_number(const GpioPin* gpio) { diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 7ac9a245993..8b1d29b1ce4 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -62,7 +62,6 @@ Header,+,firmware/targets/furi_hal_include/furi_hal.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_bt.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_bt_hid.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_bt_serial.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_compress.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_cortex.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_crypto.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_debug.h,, @@ -1057,12 +1056,6 @@ Function,-,furi_hal_clock_resume_tick,void, Function,-,furi_hal_clock_suspend_tick,void, Function,-,furi_hal_clock_switch_to_hsi,void, Function,-,furi_hal_clock_switch_to_pll,void, -Function,-,furi_hal_compress_alloc,FuriHalCompress*,uint16_t -Function,-,furi_hal_compress_decode,_Bool,"FuriHalCompress*, uint8_t*, size_t, uint8_t*, size_t, size_t*" -Function,-,furi_hal_compress_encode,_Bool,"FuriHalCompress*, uint8_t*, size_t, uint8_t*, size_t, size_t*" -Function,-,furi_hal_compress_free,void,FuriHalCompress* -Function,-,furi_hal_compress_icon_decode,void,"const uint8_t*, uint8_t**" -Function,-,furi_hal_compress_icon_init,void, Function,+,furi_hal_console_disable,void, Function,+,furi_hal_console_enable,void, Function,+,furi_hal_console_init,void, diff --git a/firmware/targets/f7/furi_hal/furi_hal.c b/firmware/targets/f7/furi_hal/furi_hal.c index 5840a697e44..1b710bb9637 100644 --- a/firmware/targets/f7/furi_hal/furi_hal.c +++ b/firmware/targets/f7/furi_hal/furi_hal.c @@ -8,29 +8,20 @@ void furi_hal_init_early() { furi_hal_cortex_init_early(); - furi_hal_clock_init_early(); - furi_hal_resources_init_early(); - furi_hal_os_init(); - furi_hal_spi_config_init_early(); - furi_hal_i2c_init_early(); furi_hal_light_init(); - furi_hal_rtc_init_early(); } void furi_hal_deinit_early() { furi_hal_rtc_deinit_early(); - furi_hal_i2c_deinit_early(); furi_hal_spi_config_deinit_early(); - furi_hal_resources_deinit_early(); - furi_hal_clock_deinit_early(); } @@ -39,41 +30,24 @@ void furi_hal_init() { furi_hal_clock_init(); furi_hal_console_init(); furi_hal_rtc_init(); - furi_hal_interrupt_init(); - furi_hal_flash_init(); - furi_hal_resources_init(); - FURI_LOG_I(TAG, "GPIO OK"); - furi_hal_version_init(); furi_hal_region_init(); - furi_hal_spi_config_init(); furi_hal_spi_dma_init(); - furi_hal_ibutton_init(); - FURI_LOG_I(TAG, "iButton OK"); furi_hal_speaker_init(); - FURI_LOG_I(TAG, "Speaker OK"); - furi_hal_crypto_init(); - furi_hal_i2c_init(); - - // High Level furi_hal_power_init(); furi_hal_light_init(); - furi_hal_bt_init(); furi_hal_memory_init(); - furi_hal_compress_icon_init(); #ifndef FURI_RAM_EXEC - // USB furi_hal_usb_init(); - FURI_LOG_I(TAG, "USB OK"); furi_hal_vibro_init(); furi_hal_subghz_init(); furi_hal_nfc_init(); diff --git a/firmware/targets/f7/furi_hal/furi_hal_ibutton.c b/firmware/targets/f7/furi_hal/furi_hal_ibutton.c index c05cd69a825..f19fd0a0ef0 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_ibutton.c +++ b/firmware/targets/f7/furi_hal/furi_hal_ibutton.c @@ -7,6 +7,7 @@ #include +#define TAG "FuriHalIbutton" #define FURI_HAL_IBUTTON_TIMER TIM1 #define FURI_HAL_IBUTTON_TIMER_IRQ FuriHalInterruptIdTim1UpTim16 @@ -33,6 +34,8 @@ static void furi_hal_ibutton_emulate_isr() { void furi_hal_ibutton_init() { furi_hal_ibutton = malloc(sizeof(FuriHalIbutton)); furi_hal_ibutton->state = FuriHalIbuttonStateIdle; + + FURI_LOG_I(TAG, "Init OK"); } void furi_hal_ibutton_emulate_start( diff --git a/firmware/targets/f7/furi_hal/furi_hal_resources.c b/firmware/targets/f7/furi_hal/furi_hal_resources.c index c0eb9ee67c1..d0d85cb2d6c 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_resources.c +++ b/firmware/targets/f7/furi_hal/furi_hal_resources.c @@ -4,6 +4,8 @@ #include #include +#define TAG "FuriHalResources" + const GpioPin vibro_gpio = {.port = VIBRO_GPIO_Port, .pin = VIBRO_Pin}; const GpioPin ibutton_gpio = {.port = iBTN_GPIO_Port, .pin = iBTN_Pin}; @@ -190,6 +192,8 @@ void furi_hal_resources_init() { NVIC_SetPriority(EXTI15_10_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 5, 0)); NVIC_EnableIRQ(EXTI15_10_IRQn); + + FURI_LOG_I(TAG, "Init OK"); } int32_t furi_hal_resources_get_ext_pin_number(const GpioPin* gpio) { diff --git a/firmware/targets/f7/src/dfu.c b/firmware/targets/f7/src/dfu.c index f32ac2ac48d..b060bc8d268 100644 --- a/firmware/targets/f7/src/dfu.c +++ b/firmware/targets/f7/src/dfu.c @@ -4,10 +4,11 @@ #include #include #include +#include void flipper_boot_dfu_show_splash() { // Initialize - furi_hal_compress_icon_init(); + CompressIcon* compress_icon = compress_icon_alloc(); u8g2_t* fb = malloc(sizeof(u8g2_t)); memset(fb, 0, sizeof(u8g2_t)); @@ -15,13 +16,15 @@ void flipper_boot_dfu_show_splash() { u8g2_InitDisplay(fb); u8g2_SetDrawColor(fb, 0x01); uint8_t* splash_data = NULL; - furi_hal_compress_icon_decode(icon_get_data(&I_DFU_128x50), &splash_data); + compress_icon_decode(compress_icon, icon_get_data(&I_DFU_128x50), &splash_data); u8g2_DrawXBM(fb, 0, 64 - 50, 128, 50, splash_data); u8g2_SetFont(fb, u8g2_font_helvB08_tr); u8g2_DrawStr(fb, 2, 8, "Update & Recovery Mode"); u8g2_DrawStr(fb, 2, 21, "DFU Started"); u8g2_SetPowerSave(fb, 0); u8g2_SendBuffer(fb); + + compress_icon_free(compress_icon); } void flipper_boot_dfu_exec() { diff --git a/firmware/targets/f7/src/recovery.c b/firmware/targets/f7/src/recovery.c index db538b0d511..d037e8118f6 100644 --- a/firmware/targets/f7/src/recovery.c +++ b/firmware/targets/f7/src/recovery.c @@ -4,6 +4,7 @@ #include #include #include +#include #define COUNTER_VALUE (136U) @@ -27,9 +28,9 @@ void flipper_boot_recovery_exec() { u8g2_Setup_st756x_flipper(fb, U8G2_R0, u8x8_hw_spi_stm32, u8g2_gpio_and_delay_stm32); u8g2_InitDisplay(fb); - furi_hal_compress_icon_init(); + CompressIcon* compress_icon = compress_icon_alloc(); uint8_t* splash_data = NULL; - furi_hal_compress_icon_decode(icon_get_data(&I_Erase_pin_128x64), &splash_data); + compress_icon_decode(compress_icon, icon_get_data(&I_Erase_pin_128x64), &splash_data); u8g2_ClearBuffer(fb); u8g2_SetDrawColor(fb, 0x01); @@ -38,6 +39,7 @@ void flipper_boot_recovery_exec() { u8g2_DrawXBM(fb, 0, 0, 128, 64, splash_data); u8g2_SendBuffer(fb); u8g2_SetPowerSave(fb, 0); + compress_icon_free(compress_icon); size_t counter = COUNTER_VALUE; while(counter) { diff --git a/firmware/targets/furi_hal_include/furi_hal.h b/firmware/targets/furi_hal_include/furi_hal.h index ad4340dd409..2eb4688d428 100644 --- a/firmware/targets/furi_hal_include/furi_hal.h +++ b/firmware/targets/furi_hal_include/furi_hal.h @@ -33,7 +33,6 @@ struct STOP_EXTERNING_ME {}; #include #include #include -#include #include #include #include diff --git a/firmware/targets/furi_hal_include/furi_hal_compress.h b/firmware/targets/furi_hal_include/furi_hal_compress.h deleted file mode 100644 index f80aee5162f..00000000000 --- a/firmware/targets/furi_hal_include/furi_hal_compress.h +++ /dev/null @@ -1,87 +0,0 @@ -/** - * @file furi_hal_compress.h - * LZSS based compression HAL API - */ -#pragma once - -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/** Defines encoder and decoder window size */ -#define FURI_HAL_COMPRESS_EXP_BUFF_SIZE_LOG (8) - -/** Defines encoder and decoder lookahead buffer size */ -#define FURI_HAL_COMPRESS_LOOKAHEAD_BUFF_SIZE_LOG (4) - -/** FuriHalCompress control structure */ -typedef struct FuriHalCompress FuriHalCompress; - -/** Initialize icon decoder - */ -void furi_hal_compress_icon_init(); - -/** Icon decoder - * - * @param icon_data pointer to icon data - * @param decoded_buff pointer to decoded buffer - */ -void furi_hal_compress_icon_decode(const uint8_t* icon_data, uint8_t** decoded_buff); - -/** Allocate encoder and decoder - * - * @param compress_buff_size size of decoder and encoder buffer to allocate - * - * @return FuriHalCompress instance - */ -FuriHalCompress* furi_hal_compress_alloc(uint16_t compress_buff_size); - -/** Free encoder and decoder - * - * @param compress FuriHalCompress instance - */ -void furi_hal_compress_free(FuriHalCompress* compress); - -/** Encode data - * - * @param compress FuriHalCompress instance - * @param data_in pointer to input data - * @param data_in_size size of input data - * @param data_out maximum size of output data - * @param data_res_size pointer to result output data size - * - * @return true on success - */ -bool furi_hal_compress_encode( - FuriHalCompress* compress, - uint8_t* data_in, - size_t data_in_size, - uint8_t* data_out, - size_t data_out_size, - size_t* data_res_size); - -/** Decode data - * - * @param compress FuriHalCompress instance - * @param data_in pointer to input data - * @param data_in_size size of input data - * @param data_out maximum size of output data - * @param data_res_size pointer to result output data size - * - * @return true on success - */ -bool furi_hal_compress_decode( - FuriHalCompress* compress, - uint8_t* data_in, - size_t data_in_size, - uint8_t* data_out, - size_t data_out_size, - size_t* data_res_size); - -#ifdef __cplusplus -} -#endif diff --git a/lib/err.h b/lib/err.h new file mode 100644 index 00000000000..a0e93874e6c --- /dev/null +++ b/lib/err.h @@ -0,0 +1,4 @@ +#pragma once +#include + +#define err(...) FURI_LOG_E("Heatshrink", "Error: %d-%s", __VA_ARGS__) \ No newline at end of file diff --git a/lib/heatshrink b/lib/heatshrink new file mode 160000 index 00000000000..7398ccc9165 --- /dev/null +++ b/lib/heatshrink @@ -0,0 +1 @@ +Subproject commit 7398ccc91652a33483245200cfa1a83b073bc206 diff --git a/lib/heatshrink/heatshrink_common.h b/lib/heatshrink/heatshrink_common.h deleted file mode 100644 index 243f447029d..00000000000 --- a/lib/heatshrink/heatshrink_common.h +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef HEATSHRINK_H -#define HEATSHRINK_H - -#define HEATSHRINK_AUTHOR "Scott Vokes " -#define HEATSHRINK_URL "https://github.com/atomicobject/heatshrink" - -/* Version 0.4.1 */ -#define HEATSHRINK_VERSION_MAJOR 0 -#define HEATSHRINK_VERSION_MINOR 4 -#define HEATSHRINK_VERSION_PATCH 1 - -#define HEATSHRINK_MIN_WINDOW_BITS 4 -#define HEATSHRINK_MAX_WINDOW_BITS 15 - -#define HEATSHRINK_MIN_LOOKAHEAD_BITS 3 - -#define HEATSHRINK_LITERAL_MARKER 0x01 -#define HEATSHRINK_BACKREF_MARKER 0x00 - -#endif diff --git a/lib/heatshrink/heatshrink_config.h b/lib/heatshrink/heatshrink_config.h deleted file mode 100644 index 7f2373c0d47..00000000000 --- a/lib/heatshrink/heatshrink_config.h +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef HEATSHRINK_CONFIG_H -#define HEATSHRINK_CONFIG_H - -#include - -/* Should functionality assuming dynamic allocation be used? */ -#ifndef HEATSHRINK_DYNAMIC_ALLOC -#define HEATSHRINK_DYNAMIC_ALLOC 1 -#endif - -#if HEATSHRINK_DYNAMIC_ALLOC - /* Optional replacement of malloc/free */ - #define HEATSHRINK_MALLOC(SZ) malloc(SZ) - #define HEATSHRINK_FREE(P, SZ) free(P) -#else - /* Required parameters for static configuration */ - #define HEATSHRINK_STATIC_INPUT_BUFFER_SIZE 1024 - #define HEATSHRINK_STATIC_WINDOW_BITS 8 - #define HEATSHRINK_STATIC_LOOKAHEAD_BITS 4 -#endif - -/* Turn on logging for debugging. */ -#define HEATSHRINK_DEBUGGING_LOGS 0 - -/* Use indexing for faster compression. (This requires additional space.) */ -#define HEATSHRINK_USE_INDEX 1 - -#endif diff --git a/lib/heatshrink/heatshrink_decoder.c b/lib/heatshrink/heatshrink_decoder.c deleted file mode 100644 index 28782836757..00000000000 --- a/lib/heatshrink/heatshrink_decoder.c +++ /dev/null @@ -1,364 +0,0 @@ -#include -#include -#include "heatshrink_decoder.h" - -/* States for the polling state machine. */ -typedef enum { - HSDS_TAG_BIT, /* tag bit */ - HSDS_YIELD_LITERAL, /* ready to yield literal byte */ - HSDS_BACKREF_INDEX_MSB, /* most significant byte of index */ - HSDS_BACKREF_INDEX_LSB, /* least significant byte of index */ - HSDS_BACKREF_COUNT_MSB, /* most significant byte of count */ - HSDS_BACKREF_COUNT_LSB, /* least significant byte of count */ - HSDS_YIELD_BACKREF, /* ready to yield back-reference */ -} HSD_state; - -#if HEATSHRINK_DEBUGGING_LOGS -#include -#include -#include -#define LOG(...) fprintf(stderr, __VA_ARGS__) -#define ASSERT(X) assert(X) -static const char *state_names[] = { - "tag_bit", - "yield_literal", - "backref_index_msb", - "backref_index_lsb", - "backref_count_msb", - "backref_count_lsb", - "yield_backref", -}; -#else -#define LOG(...) /* no-op */ -#define ASSERT(X) /* no-op */ -#endif - -typedef struct { - uint8_t *buf; /* output buffer */ - size_t buf_size; /* buffer size */ - size_t *output_size; /* bytes pushed to buffer, so far */ -} output_info; - -#define NO_BITS ((uint16_t)-1) - -/* Forward references. */ -static uint16_t get_bits(heatshrink_decoder *hsd, uint8_t count); -static void push_byte(heatshrink_decoder *hsd, output_info *oi, uint8_t byte); - -#if HEATSHRINK_DYNAMIC_ALLOC -heatshrink_decoder *heatshrink_decoder_alloc(uint8_t* buffer, - uint16_t input_buffer_size, - uint8_t window_sz2, - uint8_t lookahead_sz2) { - if ((window_sz2 < HEATSHRINK_MIN_WINDOW_BITS) || - (window_sz2 > HEATSHRINK_MAX_WINDOW_BITS) || - (input_buffer_size == 0) || - (lookahead_sz2 < HEATSHRINK_MIN_LOOKAHEAD_BITS) || - (lookahead_sz2 >= window_sz2)) { - return NULL; - } - size_t sz = sizeof(heatshrink_decoder); - heatshrink_decoder *hsd = HEATSHRINK_MALLOC(sz); - if (hsd == NULL) { return NULL; } - hsd->input_buffer_size = input_buffer_size; - hsd->window_sz2 = window_sz2; - hsd->lookahead_sz2 = lookahead_sz2; - hsd->buffers = buffer; - heatshrink_decoder_reset(hsd); - LOG("-- allocated decoder with buffer size of %zu (%zu + %u + %u)\n", - sz, sizeof(heatshrink_decoder), (1 << window_sz2), input_buffer_size); - return hsd; -} - -void heatshrink_decoder_free(heatshrink_decoder *hsd) { - size_t sz = sizeof(heatshrink_decoder); - HEATSHRINK_FREE(hsd, sz); - (void)sz; /* may not be used by free */ -} -#endif - -void heatshrink_decoder_reset(heatshrink_decoder *hsd) { - hsd->state = HSDS_TAG_BIT; - hsd->input_size = 0; - hsd->input_index = 0; - hsd->bit_index = 0x00; - hsd->current_byte = 0x00; - hsd->output_count = 0; - hsd->output_index = 0; - hsd->head_index = 0; -} - -/* Copy SIZE bytes into the decoder's input buffer, if it will fit. */ -HSD_sink_res heatshrink_decoder_sink(heatshrink_decoder *hsd, - uint8_t *in_buf, size_t size, size_t *input_size) { - if ((hsd == NULL) || (in_buf == NULL) || (input_size == NULL)) { - return HSDR_SINK_ERROR_NULL; - } - - size_t rem = HEATSHRINK_DECODER_INPUT_BUFFER_SIZE(hsd) - hsd->input_size; - if (rem == 0) { - *input_size = 0; - return HSDR_SINK_FULL; - } - - size = rem < size ? rem : size; - LOG("-- sinking %zd bytes\n", size); - /* copy into input buffer (at head of buffers) */ - memcpy(&hsd->buffers[hsd->input_size], in_buf, size); - hsd->input_size += size; - *input_size = size; - return HSDR_SINK_OK; -} - - -/***************** - * Decompression * - *****************/ - -#define BACKREF_COUNT_BITS(HSD) (HEATSHRINK_DECODER_LOOKAHEAD_BITS(HSD)) -#define BACKREF_INDEX_BITS(HSD) (HEATSHRINK_DECODER_WINDOW_BITS(HSD)) - -// States -static HSD_state st_tag_bit(heatshrink_decoder *hsd); -static HSD_state st_yield_literal(heatshrink_decoder *hsd, - output_info *oi); -static HSD_state st_backref_index_msb(heatshrink_decoder *hsd); -static HSD_state st_backref_index_lsb(heatshrink_decoder *hsd); -static HSD_state st_backref_count_msb(heatshrink_decoder *hsd); -static HSD_state st_backref_count_lsb(heatshrink_decoder *hsd); -static HSD_state st_yield_backref(heatshrink_decoder *hsd, - output_info *oi); - -HSD_poll_res heatshrink_decoder_poll(heatshrink_decoder *hsd, - uint8_t *out_buf, size_t out_buf_size, size_t *output_size) { - if ((hsd == NULL) || (out_buf == NULL) || (output_size == NULL)) { - return HSDR_POLL_ERROR_NULL; - } - *output_size = 0; - - output_info oi; - oi.buf = out_buf; - oi.buf_size = out_buf_size; - oi.output_size = output_size; - - while (1) { - LOG("-- poll, state is %d (%s), input_size %d\n", - hsd->state, state_names[hsd->state], hsd->input_size); - uint8_t in_state = hsd->state; - switch (in_state) { - case HSDS_TAG_BIT: - hsd->state = st_tag_bit(hsd); - break; - case HSDS_YIELD_LITERAL: - hsd->state = st_yield_literal(hsd, &oi); - break; - case HSDS_BACKREF_INDEX_MSB: - hsd->state = st_backref_index_msb(hsd); - break; - case HSDS_BACKREF_INDEX_LSB: - hsd->state = st_backref_index_lsb(hsd); - break; - case HSDS_BACKREF_COUNT_MSB: - hsd->state = st_backref_count_msb(hsd); - break; - case HSDS_BACKREF_COUNT_LSB: - hsd->state = st_backref_count_lsb(hsd); - break; - case HSDS_YIELD_BACKREF: - hsd->state = st_yield_backref(hsd, &oi); - break; - default: - return HSDR_POLL_ERROR_UNKNOWN; - } - - /* If the current state cannot advance, check if input or output - * buffer are exhausted. */ - if (hsd->state == in_state) { - if (*output_size == out_buf_size) { return HSDR_POLL_MORE; } - return HSDR_POLL_EMPTY; - } - } -} - -static HSD_state st_tag_bit(heatshrink_decoder *hsd) { - uint32_t bits = get_bits(hsd, 1); // get tag bit - if (bits == NO_BITS) { - return HSDS_TAG_BIT; - } else if (bits) { - return HSDS_YIELD_LITERAL; - } else if (HEATSHRINK_DECODER_WINDOW_BITS(hsd) > 8) { - return HSDS_BACKREF_INDEX_MSB; - } else { - hsd->output_index = 0; - return HSDS_BACKREF_INDEX_LSB; - } -} - -static HSD_state st_yield_literal(heatshrink_decoder *hsd, - output_info *oi) { - /* Emit a repeated section from the window buffer, and add it (again) - * to the window buffer. (Note that the repetition can include - * itself.)*/ - if (*oi->output_size < oi->buf_size) { - uint16_t byte = get_bits(hsd, 8); - if (byte == NO_BITS) { return HSDS_YIELD_LITERAL; } /* out of input */ - uint8_t *buf = &hsd->buffers[HEATSHRINK_DECODER_INPUT_BUFFER_SIZE(hsd)]; - uint16_t mask = (1 << HEATSHRINK_DECODER_WINDOW_BITS(hsd)) - 1; - uint8_t c = byte & 0xFF; - LOG("-- emitting literal byte 0x%02x ('%c')\n", c, isprint(c) ? c : '.'); - buf[hsd->head_index++ & mask] = c; - push_byte(hsd, oi, c); - return HSDS_TAG_BIT; - } else { - return HSDS_YIELD_LITERAL; - } -} - -static HSD_state st_backref_index_msb(heatshrink_decoder *hsd) { - uint8_t bit_ct = BACKREF_INDEX_BITS(hsd); - ASSERT(bit_ct > 8); - uint16_t bits = get_bits(hsd, bit_ct - 8); - LOG("-- backref index (msb), got 0x%04x (+1)\n", bits); - if (bits == NO_BITS) { return HSDS_BACKREF_INDEX_MSB; } - hsd->output_index = bits << 8; - return HSDS_BACKREF_INDEX_LSB; -} - -static HSD_state st_backref_index_lsb(heatshrink_decoder *hsd) { - uint8_t bit_ct = BACKREF_INDEX_BITS(hsd); - uint16_t bits = get_bits(hsd, bit_ct < 8 ? bit_ct : 8); - LOG("-- backref index (lsb), got 0x%04x (+1)\n", bits); - if (bits == NO_BITS) { return HSDS_BACKREF_INDEX_LSB; } - hsd->output_index |= bits; - hsd->output_index++; - uint8_t br_bit_ct = BACKREF_COUNT_BITS(hsd); - hsd->output_count = 0; - return (br_bit_ct > 8) ? HSDS_BACKREF_COUNT_MSB : HSDS_BACKREF_COUNT_LSB; -} - -static HSD_state st_backref_count_msb(heatshrink_decoder *hsd) { - uint8_t br_bit_ct = BACKREF_COUNT_BITS(hsd); - ASSERT(br_bit_ct > 8); - uint16_t bits = get_bits(hsd, br_bit_ct - 8); - LOG("-- backref count (msb), got 0x%04x (+1)\n", bits); - if (bits == NO_BITS) { return HSDS_BACKREF_COUNT_MSB; } - hsd->output_count = bits << 8; - return HSDS_BACKREF_COUNT_LSB; -} - -static HSD_state st_backref_count_lsb(heatshrink_decoder *hsd) { - uint8_t br_bit_ct = BACKREF_COUNT_BITS(hsd); - uint16_t bits = get_bits(hsd, br_bit_ct < 8 ? br_bit_ct : 8); - LOG("-- backref count (lsb), got 0x%04x (+1)\n", bits); - if (bits == NO_BITS) { return HSDS_BACKREF_COUNT_LSB; } - hsd->output_count |= bits; - hsd->output_count++; - return HSDS_YIELD_BACKREF; -} - -static HSD_state st_yield_backref(heatshrink_decoder *hsd, - output_info *oi) { - size_t count = oi->buf_size - *oi->output_size; - if (count > 0) { - size_t i = 0; - if (hsd->output_count < count) count = hsd->output_count; - uint8_t *buf = &hsd->buffers[HEATSHRINK_DECODER_INPUT_BUFFER_SIZE(hsd)]; - uint16_t mask = (1 << HEATSHRINK_DECODER_WINDOW_BITS(hsd)) - 1; - uint16_t neg_offset = hsd->output_index; - LOG("-- emitting %zu bytes from -%u bytes back\n", count, neg_offset); - ASSERT(neg_offset <= mask + 1); - ASSERT(count <= (size_t)(1 << BACKREF_COUNT_BITS(hsd))); - - for (i=0; ihead_index - neg_offset) & mask]; - push_byte(hsd, oi, c); - buf[hsd->head_index & mask] = c; - hsd->head_index++; - LOG(" -- ++ 0x%02x\n", c); - } - hsd->output_count -= count; - if (hsd->output_count == 0) { return HSDS_TAG_BIT; } - } - return HSDS_YIELD_BACKREF; -} - -/* Get the next COUNT bits from the input buffer, saving incremental progress. - * Returns NO_BITS on end of input, or if more than 15 bits are requested. */ -static uint16_t get_bits(heatshrink_decoder *hsd, uint8_t count) { - uint16_t accumulator = 0; - int i = 0; - if (count > 15) { return NO_BITS; } - LOG("-- popping %u bit(s)\n", count); - - /* If we aren't able to get COUNT bits, suspend immediately, because we - * don't track how many bits of COUNT we've accumulated before suspend. */ - if (hsd->input_size == 0) { - if (hsd->bit_index < (1 << (count - 1))) { return NO_BITS; } - } - - for (i = 0; i < count; i++) { - if (hsd->bit_index == 0x00) { - if (hsd->input_size == 0) { - LOG(" -- out of bits, suspending w/ accumulator of %u (0x%02x)\n", - accumulator, accumulator); - return NO_BITS; - } - hsd->current_byte = hsd->buffers[hsd->input_index++]; - LOG(" -- pulled byte 0x%02x\n", hsd->current_byte); - if (hsd->input_index == hsd->input_size) { - hsd->input_index = 0; /* input is exhausted */ - hsd->input_size = 0; - } - hsd->bit_index = 0x80; - } - accumulator <<= 1; - if (hsd->current_byte & hsd->bit_index) { - accumulator |= 0x01; - if (0) { - LOG(" -- got 1, accumulator 0x%04x, bit_index 0x%02x\n", - accumulator, hsd->bit_index); - } - } else { - if (0) { - LOG(" -- got 0, accumulator 0x%04x, bit_index 0x%02x\n", - accumulator, hsd->bit_index); - } - } - hsd->bit_index >>= 1; - } - - if (count > 1) { LOG(" -- accumulated %08x\n", accumulator); } - return accumulator; -} - -HSD_finish_res heatshrink_decoder_finish(heatshrink_decoder *hsd) { - if (hsd == NULL) { return HSDR_FINISH_ERROR_NULL; } - switch (hsd->state) { - case HSDS_TAG_BIT: - return hsd->input_size == 0 ? HSDR_FINISH_DONE : HSDR_FINISH_MORE; - - /* If we want to finish with no input, but are in these states, it's - * because the 0-bit padding to the last byte looks like a backref - * marker bit followed by all 0s for index and count bits. */ - case HSDS_BACKREF_INDEX_LSB: - case HSDS_BACKREF_INDEX_MSB: - case HSDS_BACKREF_COUNT_LSB: - case HSDS_BACKREF_COUNT_MSB: - return hsd->input_size == 0 ? HSDR_FINISH_DONE : HSDR_FINISH_MORE; - - /* If the output stream is padded with 0xFFs (possibly due to being in - * flash memory), also explicitly check the input size rather than - * uselessly returning MORE but yielding 0 bytes when polling. */ - case HSDS_YIELD_LITERAL: - return hsd->input_size == 0 ? HSDR_FINISH_DONE : HSDR_FINISH_MORE; - - default: - return HSDR_FINISH_MORE; - } -} - -static void push_byte(heatshrink_decoder *hsd, output_info *oi, uint8_t byte) { - LOG(" -- pushing byte: 0x%02x ('%c')\n", byte, isprint(byte) ? byte : '.'); - oi->buf[(*oi->output_size)++] = byte; - (void)hsd; -} diff --git a/lib/heatshrink/heatshrink_decoder.h b/lib/heatshrink/heatshrink_decoder.h deleted file mode 100644 index 687b0806b4b..00000000000 --- a/lib/heatshrink/heatshrink_decoder.h +++ /dev/null @@ -1,100 +0,0 @@ -#ifndef HEATSHRINK_DECODER_H -#define HEATSHRINK_DECODER_H - -#include -#include -#include "heatshrink_common.h" -#include "heatshrink_config.h" - -typedef enum { - HSDR_SINK_OK, /* data sunk, ready to poll */ - HSDR_SINK_FULL, /* out of space in internal buffer */ - HSDR_SINK_ERROR_NULL=-1, /* NULL argument */ -} HSD_sink_res; - -typedef enum { - HSDR_POLL_EMPTY, /* input exhausted */ - HSDR_POLL_MORE, /* more data remaining, call again w/ fresh output buffer */ - HSDR_POLL_ERROR_NULL=-1, /* NULL arguments */ - HSDR_POLL_ERROR_UNKNOWN=-2, -} HSD_poll_res; - -typedef enum { - HSDR_FINISH_DONE, /* output is done */ - HSDR_FINISH_MORE, /* more output remains */ - HSDR_FINISH_ERROR_NULL=-1, /* NULL arguments */ -} HSD_finish_res; - -#if HEATSHRINK_DYNAMIC_ALLOC -#define HEATSHRINK_DECODER_INPUT_BUFFER_SIZE(BUF) \ - ((BUF)->input_buffer_size) -#define HEATSHRINK_DECODER_WINDOW_BITS(BUF) \ - ((BUF)->window_sz2) -#define HEATSHRINK_DECODER_LOOKAHEAD_BITS(BUF) \ - ((BUF)->lookahead_sz2) -#else -#define HEATSHRINK_DECODER_INPUT_BUFFER_SIZE(_) \ - HEATSHRINK_STATIC_INPUT_BUFFER_SIZE -#define HEATSHRINK_DECODER_WINDOW_BITS(_) \ - (HEATSHRINK_STATIC_WINDOW_BITS) -#define HEATSHRINK_DECODER_LOOKAHEAD_BITS(BUF) \ - (HEATSHRINK_STATIC_LOOKAHEAD_BITS) -#endif - -typedef struct { - uint16_t input_size; /* bytes in input buffer */ - uint16_t input_index; /* offset to next unprocessed input byte */ - uint16_t output_count; /* how many bytes to output */ - uint16_t output_index; /* index for bytes to output */ - uint16_t head_index; /* head of window buffer */ - uint8_t state; /* current state machine node */ - uint8_t current_byte; /* current byte of input */ - uint8_t bit_index; /* current bit index */ - -#if HEATSHRINK_DYNAMIC_ALLOC - /* Fields that are only used if dynamically allocated. */ - uint8_t window_sz2; /* window buffer bits */ - uint8_t lookahead_sz2; /* lookahead bits */ - uint16_t input_buffer_size; /* input buffer size */ - - /* Input buffer, then expansion window buffer */ - uint8_t* buffers; -#else - /* Input buffer, then expansion window buffer */ - uint8_t buffers[(1 << HEATSHRINK_DECODER_WINDOW_BITS(_)) - + HEATSHRINK_DECODER_INPUT_BUFFER_SIZE(_)]; -#endif -} heatshrink_decoder; - -#if HEATSHRINK_DYNAMIC_ALLOC -/* Allocate a decoder with an input buffer of INPUT_BUFFER_SIZE bytes, - * an expansion buffer size of 2^WINDOW_SZ2, and a lookahead - * size of 2^lookahead_sz2. (The window buffer and lookahead sizes - * must match the settings used when the data was compressed.) - * Returns NULL on error. */ -heatshrink_decoder *heatshrink_decoder_alloc(uint8_t* buffer, uint16_t input_buffer_size, - uint8_t expansion_buffer_sz2, uint8_t lookahead_sz2); - -/* Free a decoder. */ -void heatshrink_decoder_free(heatshrink_decoder *hsd); -#endif - -/* Reset a decoder. */ -void heatshrink_decoder_reset(heatshrink_decoder *hsd); - -/* Sink at most SIZE bytes from IN_BUF into the decoder. *INPUT_SIZE is set to - * indicate how many bytes were actually sunk (in case a buffer was filled). */ -HSD_sink_res heatshrink_decoder_sink(heatshrink_decoder *hsd, - uint8_t *in_buf, size_t size, size_t *input_size); - -/* Poll for output from the decoder, copying at most OUT_BUF_SIZE bytes into - * OUT_BUF (setting *OUTPUT_SIZE to the actual amount copied). */ -HSD_poll_res heatshrink_decoder_poll(heatshrink_decoder *hsd, - uint8_t *out_buf, size_t out_buf_size, size_t *output_size); - -/* Notify the dencoder that the input stream is finished. - * If the return value is HSDR_FINISH_MORE, there is still more output, so - * call heatshrink_decoder_poll and repeat. */ -HSD_finish_res heatshrink_decoder_finish(heatshrink_decoder *hsd); - -#endif diff --git a/lib/heatshrink/heatshrink_encoder.c b/lib/heatshrink/heatshrink_encoder.c deleted file mode 100644 index 98f27dff872..00000000000 --- a/lib/heatshrink/heatshrink_encoder.c +++ /dev/null @@ -1,602 +0,0 @@ -#include -#include -#include -#include "heatshrink_encoder.h" - -typedef enum { - HSES_NOT_FULL, /* input buffer not full enough */ - HSES_FILLED, /* buffer is full */ - HSES_SEARCH, /* searching for patterns */ - HSES_YIELD_TAG_BIT, /* yield tag bit */ - HSES_YIELD_LITERAL, /* emit literal byte */ - HSES_YIELD_BR_INDEX, /* yielding backref index */ - HSES_YIELD_BR_LENGTH, /* yielding backref length */ - HSES_SAVE_BACKLOG, /* copying buffer to backlog */ - HSES_FLUSH_BITS, /* flush bit buffer */ - HSES_DONE, /* done */ -} HSE_state; - -#if HEATSHRINK_DEBUGGING_LOGS -#include -#include -#include -#define LOG(...) fprintf(stderr, __VA_ARGS__) -#define ASSERT(X) assert(X) -static const char *state_names[] = { - "not_full", - "filled", - "search", - "yield_tag_bit", - "yield_literal", - "yield_br_index", - "yield_br_length", - "save_backlog", - "flush_bits", - "done", -}; -#else -#define LOG(...) /* no-op */ -#define ASSERT(X) /* no-op */ -#endif - -// Encoder flags -enum { - FLAG_IS_FINISHING = 0x01, -}; - -typedef struct { - uint8_t *buf; /* output buffer */ - size_t buf_size; /* buffer size */ - size_t *output_size; /* bytes pushed to buffer, so far */ -} output_info; - -#define MATCH_NOT_FOUND ((uint16_t)-1) - -static uint16_t get_input_offset(heatshrink_encoder *hse); -static uint16_t get_input_buffer_size(heatshrink_encoder *hse); -static uint16_t get_lookahead_size(heatshrink_encoder *hse); -static void add_tag_bit(heatshrink_encoder *hse, output_info *oi, uint8_t tag); -static int can_take_byte(output_info *oi); -static int is_finishing(heatshrink_encoder *hse); -static void save_backlog(heatshrink_encoder *hse); - -/* Push COUNT (max 8) bits to the output buffer, which has room. */ -static void push_bits(heatshrink_encoder *hse, uint8_t count, uint8_t bits, - output_info *oi); -static uint8_t push_outgoing_bits(heatshrink_encoder *hse, output_info *oi); -static void push_literal_byte(heatshrink_encoder *hse, output_info *oi); - -#if HEATSHRINK_DYNAMIC_ALLOC -heatshrink_encoder *heatshrink_encoder_alloc(uint8_t* buffer, uint8_t window_sz2, - uint8_t lookahead_sz2) { - if ((window_sz2 < HEATSHRINK_MIN_WINDOW_BITS) || - (window_sz2 > HEATSHRINK_MAX_WINDOW_BITS) || - (lookahead_sz2 < HEATSHRINK_MIN_LOOKAHEAD_BITS) || - (lookahead_sz2 >= window_sz2)) { - return NULL; - } - - /* Note: 2 * the window size is used because the buffer needs to fit - * (1 << window_sz2) bytes for the current input, and an additional - * (1 << window_sz2) bytes for the previous buffer of input, which - * will be scanned for useful backreferences. */ - size_t buf_sz = (2 << window_sz2); - - heatshrink_encoder *hse = HEATSHRINK_MALLOC(sizeof(*hse)); - if (hse == NULL) { return NULL; } - hse->window_sz2 = window_sz2; - hse->lookahead_sz2 = lookahead_sz2; - hse->buffer = buffer; - heatshrink_encoder_reset(hse); - -#if HEATSHRINK_USE_INDEX - size_t index_sz = buf_sz*sizeof(uint16_t); - hse->search_index = HEATSHRINK_MALLOC(index_sz + sizeof(struct hs_index)); - if (hse->search_index == NULL) { - HEATSHRINK_FREE(hse, sizeof(*hse) + buf_sz); - return NULL; - } - hse->search_index->size = index_sz; -#endif - - LOG("-- allocated encoder with buffer size of %zu (%u byte input size)\n", - buf_sz, get_input_buffer_size(hse)); - return hse; -} - -void heatshrink_encoder_free(heatshrink_encoder *hse) { -#if HEATSHRINK_USE_INDEX - size_t index_sz = sizeof(struct hs_index) + hse->search_index->size; - HEATSHRINK_FREE(hse->search_index, index_sz); - (void)index_sz; -#endif - HEATSHRINK_FREE(hse, sizeof(heatshrink_encoder)); -} -#endif - -void heatshrink_encoder_reset(heatshrink_encoder *hse) { - hse->input_size = 0; - hse->state = HSES_NOT_FULL; - hse->match_scan_index = 0; - hse->flags = 0; - hse->bit_index = 0x80; - hse->current_byte = 0x00; - hse->match_length = 0; - - hse->outgoing_bits = 0x0000; - hse->outgoing_bits_count = 0; - - #ifdef LOOP_DETECT - hse->loop_detect = (uint32_t)-1; - #endif -} - -HSE_sink_res heatshrink_encoder_sink(heatshrink_encoder *hse, - uint8_t *in_buf, size_t size, size_t *input_size) { - if ((hse == NULL) || (in_buf == NULL) || (input_size == NULL)) { - return HSER_SINK_ERROR_NULL; - } - - /* Sinking more content after saying the content is done, tsk tsk */ - if (is_finishing(hse)) { return HSER_SINK_ERROR_MISUSE; } - - /* Sinking more content before processing is done */ - if (hse->state != HSES_NOT_FULL) { return HSER_SINK_ERROR_MISUSE; } - - uint16_t write_offset = get_input_offset(hse) + hse->input_size; - uint16_t ibs = get_input_buffer_size(hse); - uint16_t rem = ibs - hse->input_size; - uint16_t cp_sz = rem < size ? rem : size; - - memcpy(&hse->buffer[write_offset], in_buf, cp_sz); - *input_size = cp_sz; - hse->input_size += cp_sz; - - LOG("-- sunk %u bytes (of %zu) into encoder at %d, input buffer now has %u\n", - cp_sz, size, write_offset, hse->input_size); - if (cp_sz == rem) { - LOG("-- internal buffer is now full\n"); - hse->state = HSES_FILLED; - } - - return HSER_SINK_OK; -} - - -/*************** - * Compression * - ***************/ - -static uint16_t find_longest_match(heatshrink_encoder *hse, uint16_t start, - uint16_t end, const uint16_t maxlen, uint16_t *match_length); -static void do_indexing(heatshrink_encoder *hse); - -static HSE_state st_step_search(heatshrink_encoder *hse); -static HSE_state st_yield_tag_bit(heatshrink_encoder *hse, - output_info *oi); -static HSE_state st_yield_literal(heatshrink_encoder *hse, - output_info *oi); -static HSE_state st_yield_br_index(heatshrink_encoder *hse, - output_info *oi); -static HSE_state st_yield_br_length(heatshrink_encoder *hse, - output_info *oi); -static HSE_state st_save_backlog(heatshrink_encoder *hse); -static HSE_state st_flush_bit_buffer(heatshrink_encoder *hse, - output_info *oi); - -HSE_poll_res heatshrink_encoder_poll(heatshrink_encoder *hse, - uint8_t *out_buf, size_t out_buf_size, size_t *output_size) { - if ((hse == NULL) || (out_buf == NULL) || (output_size == NULL)) { - return HSER_POLL_ERROR_NULL; - } - if (out_buf_size == 0) { - LOG("-- MISUSE: output buffer size is 0\n"); - return HSER_POLL_ERROR_MISUSE; - } - *output_size = 0; - - output_info oi; - oi.buf = out_buf; - oi.buf_size = out_buf_size; - oi.output_size = output_size; - - while (1) { - LOG("-- polling, state %u (%s), flags 0x%02x\n", - hse->state, state_names[hse->state], hse->flags); - - uint8_t in_state = hse->state; - switch (in_state) { - case HSES_NOT_FULL: - return HSER_POLL_EMPTY; - case HSES_FILLED: - do_indexing(hse); - hse->state = HSES_SEARCH; - break; - case HSES_SEARCH: - hse->state = st_step_search(hse); - break; - case HSES_YIELD_TAG_BIT: - hse->state = st_yield_tag_bit(hse, &oi); - break; - case HSES_YIELD_LITERAL: - hse->state = st_yield_literal(hse, &oi); - break; - case HSES_YIELD_BR_INDEX: - hse->state = st_yield_br_index(hse, &oi); - break; - case HSES_YIELD_BR_LENGTH: - hse->state = st_yield_br_length(hse, &oi); - break; - case HSES_SAVE_BACKLOG: - hse->state = st_save_backlog(hse); - break; - case HSES_FLUSH_BITS: - hse->state = st_flush_bit_buffer(hse, &oi); - /* fall through */ - case HSES_DONE: - return HSER_POLL_EMPTY; - default: - LOG("-- bad state %s\n", state_names[hse->state]); - return HSER_POLL_ERROR_MISUSE; - } - - if (hse->state == in_state) { - /* Check if output buffer is exhausted. */ - if (*output_size == out_buf_size) return HSER_POLL_MORE; - } - } -} - -HSE_finish_res heatshrink_encoder_finish(heatshrink_encoder *hse) { - if (hse == NULL) { return HSER_FINISH_ERROR_NULL; } - LOG("-- setting is_finishing flag\n"); - hse->flags |= FLAG_IS_FINISHING; - if (hse->state == HSES_NOT_FULL) { hse->state = HSES_FILLED; } - return hse->state == HSES_DONE ? HSER_FINISH_DONE : HSER_FINISH_MORE; -} - -static HSE_state st_step_search(heatshrink_encoder *hse) { - uint16_t window_length = get_input_buffer_size(hse); - uint16_t lookahead_sz = get_lookahead_size(hse); - uint16_t msi = hse->match_scan_index; - LOG("## step_search, scan @ +%d (%d/%d), input size %d\n", - msi, hse->input_size + msi, 2*window_length, hse->input_size); - - bool fin = is_finishing(hse); - if (msi > hse->input_size - (fin ? 1 : lookahead_sz)) { - /* Current search buffer is exhausted, copy it into the - * backlog and await more input. */ - LOG("-- end of search @ %d\n", msi); - return fin ? HSES_FLUSH_BITS : HSES_SAVE_BACKLOG; - } - - uint16_t input_offset = get_input_offset(hse); - uint16_t end = input_offset + msi; - uint16_t start = end - window_length; - - uint16_t max_possible = lookahead_sz; - if (hse->input_size - msi < lookahead_sz) { - max_possible = hse->input_size - msi; - } - - uint16_t match_length = 0; - uint16_t match_pos = find_longest_match(hse, - start, end, max_possible, &match_length); - - if (match_pos == MATCH_NOT_FOUND) { - LOG("ss Match not found\n"); - hse->match_scan_index++; - hse->match_length = 0; - return HSES_YIELD_TAG_BIT; - } else { - LOG("ss Found match of %d bytes at %d\n", match_length, match_pos); - hse->match_pos = match_pos; - hse->match_length = match_length; - ASSERT(match_pos <= 1 << HEATSHRINK_ENCODER_WINDOW_BITS(hse) /*window_length*/); - - return HSES_YIELD_TAG_BIT; - } -} - -static HSE_state st_yield_tag_bit(heatshrink_encoder *hse, - output_info *oi) { - if (can_take_byte(oi)) { - if (hse->match_length == 0) { - add_tag_bit(hse, oi, HEATSHRINK_LITERAL_MARKER); - return HSES_YIELD_LITERAL; - } else { - add_tag_bit(hse, oi, HEATSHRINK_BACKREF_MARKER); - hse->outgoing_bits = hse->match_pos - 1; - hse->outgoing_bits_count = HEATSHRINK_ENCODER_WINDOW_BITS(hse); - return HSES_YIELD_BR_INDEX; - } - } else { - return HSES_YIELD_TAG_BIT; /* output is full, continue */ - } -} - -static HSE_state st_yield_literal(heatshrink_encoder *hse, - output_info *oi) { - if (can_take_byte(oi)) { - push_literal_byte(hse, oi); - return HSES_SEARCH; - } else { - return HSES_YIELD_LITERAL; - } -} - -static HSE_state st_yield_br_index(heatshrink_encoder *hse, - output_info *oi) { - if (can_take_byte(oi)) { - LOG("-- yielding backref index %u\n", hse->match_pos); - if (push_outgoing_bits(hse, oi) > 0) { - return HSES_YIELD_BR_INDEX; /* continue */ - } else { - hse->outgoing_bits = hse->match_length - 1; - hse->outgoing_bits_count = HEATSHRINK_ENCODER_LOOKAHEAD_BITS(hse); - return HSES_YIELD_BR_LENGTH; /* done */ - } - } else { - return HSES_YIELD_BR_INDEX; /* continue */ - } -} - -static HSE_state st_yield_br_length(heatshrink_encoder *hse, - output_info *oi) { - if (can_take_byte(oi)) { - LOG("-- yielding backref length %u\n", hse->match_length); - if (push_outgoing_bits(hse, oi) > 0) { - return HSES_YIELD_BR_LENGTH; - } else { - hse->match_scan_index += hse->match_length; - hse->match_length = 0; - return HSES_SEARCH; - } - } else { - return HSES_YIELD_BR_LENGTH; - } -} - -static HSE_state st_save_backlog(heatshrink_encoder *hse) { - LOG("-- saving backlog\n"); - save_backlog(hse); - return HSES_NOT_FULL; -} - -static HSE_state st_flush_bit_buffer(heatshrink_encoder *hse, - output_info *oi) { - if (hse->bit_index == 0x80) { - LOG("-- done!\n"); - return HSES_DONE; - } else if (can_take_byte(oi)) { - LOG("-- flushing remaining byte (bit_index == 0x%02x)\n", hse->bit_index); - oi->buf[(*oi->output_size)++] = hse->current_byte; - LOG("-- done!\n"); - return HSES_DONE; - } else { - return HSES_FLUSH_BITS; - } -} - -static void add_tag_bit(heatshrink_encoder *hse, output_info *oi, uint8_t tag) { - LOG("-- adding tag bit: %d\n", tag); - push_bits(hse, 1, tag, oi); -} - -static uint16_t get_input_offset(heatshrink_encoder *hse) { - return get_input_buffer_size(hse); -} - -static uint16_t get_input_buffer_size(heatshrink_encoder *hse) { - return (1 << HEATSHRINK_ENCODER_WINDOW_BITS(hse)); - (void)hse; -} - -static uint16_t get_lookahead_size(heatshrink_encoder *hse) { - return (1 << HEATSHRINK_ENCODER_LOOKAHEAD_BITS(hse)); - (void)hse; -} - -static void do_indexing(heatshrink_encoder *hse) { -#if HEATSHRINK_USE_INDEX - /* Build an index array I that contains flattened linked lists - * for the previous instances of every byte in the buffer. - * - * For example, if buf[200] == 'x', then index[200] will either - * be an offset i such that buf[i] == 'x', or a negative offset - * to indicate end-of-list. This significantly speeds up matching, - * while only using sizeof(uint16_t)*sizeof(buffer) bytes of RAM. - * - * Future optimization options: - * 1. Since any negative value represents end-of-list, the other - * 15 bits could be used to improve the index dynamically. - * - * 2. Likewise, the last lookahead_sz bytes of the index will - * not be usable, so temporary data could be stored there to - * dynamically improve the index. - * */ - struct hs_index *hsi = HEATSHRINK_ENCODER_INDEX(hse); - int16_t last[256]; - memset(last, 0xFF, sizeof(last)); - - uint8_t * const data = hse->buffer; - int16_t * const index = hsi->index; - - const uint16_t input_offset = get_input_offset(hse); - const uint16_t end = input_offset + hse->input_size; - - for (uint16_t i=0; iflags & FLAG_IS_FINISHING; -} - -static int can_take_byte(output_info *oi) { - return *oi->output_size < oi->buf_size; -} - -/* Return the longest match for the bytes at buf[end:end+maxlen] between - * buf[start] and buf[end-1]. If no match is found, return -1. */ -static uint16_t find_longest_match(heatshrink_encoder *hse, uint16_t start, - uint16_t end, const uint16_t maxlen, uint16_t *match_length) { - LOG("-- scanning for match of buf[%u:%u] between buf[%u:%u] (max %u bytes)\n", - end, end + maxlen, start, end + maxlen - 1, maxlen); - uint8_t *buf = hse->buffer; - - uint16_t match_maxlen = 0; - uint16_t match_index = MATCH_NOT_FOUND; - - uint16_t len = 0; - uint8_t * const needlepoint = &buf[end]; -#if HEATSHRINK_USE_INDEX - struct hs_index *hsi = HEATSHRINK_ENCODER_INDEX(hse); - int16_t pos = hsi->index[end]; - - while (pos - (int16_t)start >= 0) { - uint8_t * const pospoint = &buf[pos]; - len = 0; - - /* Only check matches that will potentially beat the current maxlen. - * This is redundant with the index if match_maxlen is 0, but the - * added branch overhead to check if it == 0 seems to be worse. */ - if (pospoint[match_maxlen] != needlepoint[match_maxlen]) { - pos = hsi->index[pos]; - continue; - } - - for (len = 1; len < maxlen; len++) { - if (pospoint[len] != needlepoint[len]) break; - } - - if (len > match_maxlen) { - match_maxlen = len; - match_index = pos; - if (len == maxlen) { break; } /* won't find better */ - } - pos = hsi->index[pos]; - } -#else - for (int16_t pos=end - 1; pos - (int16_t)start >= 0; pos--) { - uint8_t * const pospoint = &buf[pos]; - if ((pospoint[match_maxlen] == needlepoint[match_maxlen]) - && (*pospoint == *needlepoint)) { - for (len=1; len cmp buf[%d] == 0x%02x against %02x (start %u)\n", - pos + len, pospoint[len], needlepoint[len], start); - } - if (pospoint[len] != needlepoint[len]) { break; } - } - if (len > match_maxlen) { - match_maxlen = len; - match_index = pos; - if (len == maxlen) { break; } /* don't keep searching */ - } - } - } -#endif - - const size_t break_even_point = - (1 + HEATSHRINK_ENCODER_WINDOW_BITS(hse) + - HEATSHRINK_ENCODER_LOOKAHEAD_BITS(hse)); - - /* Instead of comparing break_even_point against 8*match_maxlen, - * compare match_maxlen against break_even_point/8 to avoid - * overflow. Since MIN_WINDOW_BITS and MIN_LOOKAHEAD_BITS are 4 and - * 3, respectively, break_even_point/8 will always be at least 1. */ - if (match_maxlen > (break_even_point / 8)) { - LOG("-- best match: %u bytes at -%u\n", - match_maxlen, end - match_index); - *match_length = match_maxlen; - return end - match_index; - } - LOG("-- none found\n"); - return MATCH_NOT_FOUND; -} - -static uint8_t push_outgoing_bits(heatshrink_encoder *hse, output_info *oi) { - uint8_t count = 0; - uint8_t bits = 0; - if (hse->outgoing_bits_count > 8) { - count = 8; - bits = hse->outgoing_bits >> (hse->outgoing_bits_count - 8); - } else { - count = hse->outgoing_bits_count; - bits = hse->outgoing_bits; - } - - if (count > 0) { - LOG("-- pushing %d outgoing bits: 0x%02x\n", count, bits); - push_bits(hse, count, bits, oi); - hse->outgoing_bits_count -= count; - } - return count; -} - -/* Push COUNT (max 8) bits to the output buffer, which has room. - * Bytes are set from the lowest bits, up. */ -static void push_bits(heatshrink_encoder *hse, uint8_t count, uint8_t bits, - output_info *oi) { - ASSERT(count <= 8); - LOG("++ push_bits: %d bits, input of 0x%02x\n", count, bits); - - /* If adding a whole byte and at the start of a new output byte, - * just push it through whole and skip the bit IO loop. */ - if (count == 8 && hse->bit_index == 0x80) { - oi->buf[(*oi->output_size)++] = bits; - } else { - for (int i=count - 1; i>=0; i--) { - bool bit = bits & (1 << i); - if (bit) { hse->current_byte |= hse->bit_index; } - if (0) { - LOG(" -- setting bit %d at bit index 0x%02x, byte => 0x%02x\n", - bit ? 1 : 0, hse->bit_index, hse->current_byte); - } - hse->bit_index >>= 1; - if (hse->bit_index == 0x00) { - hse->bit_index = 0x80; - LOG(" > pushing byte 0x%02x\n", hse->current_byte); - oi->buf[(*oi->output_size)++] = hse->current_byte; - hse->current_byte = 0x00; - } - } - } -} - -static void push_literal_byte(heatshrink_encoder *hse, output_info *oi) { - uint16_t processed_offset = hse->match_scan_index - 1; - uint16_t input_offset = get_input_offset(hse) + processed_offset; - uint8_t c = hse->buffer[input_offset]; - LOG("-- yielded literal byte 0x%02x ('%c') from +%d\n", - c, isprint(c) ? c : '.', input_offset); - push_bits(hse, 8, c, oi); -} - -static void save_backlog(heatshrink_encoder *hse) { - size_t input_buf_sz = get_input_buffer_size(hse); - - uint16_t msi = hse->match_scan_index; - - /* Copy processed data to beginning of buffer, so it can be - * used for future matches. Don't bother checking whether the - * input is less than the maximum size, because if it isn't, - * we're done anyway. */ - uint16_t rem = input_buf_sz - msi; // unprocessed bytes - uint16_t shift_sz = input_buf_sz + rem; - - memmove(&hse->buffer[0], - &hse->buffer[input_buf_sz - rem], - shift_sz); - - hse->match_scan_index = 0; - hse->input_size -= input_buf_sz - rem; -} diff --git a/lib/heatshrink/heatshrink_encoder.h b/lib/heatshrink/heatshrink_encoder.h deleted file mode 100644 index e2ccb44c7c2..00000000000 --- a/lib/heatshrink/heatshrink_encoder.h +++ /dev/null @@ -1,109 +0,0 @@ -#ifndef HEATSHRINK_ENCODER_H -#define HEATSHRINK_ENCODER_H - -#include -#include -#include "heatshrink_common.h" -#include "heatshrink_config.h" - -typedef enum { - HSER_SINK_OK, /* data sunk into input buffer */ - HSER_SINK_ERROR_NULL=-1, /* NULL argument */ - HSER_SINK_ERROR_MISUSE=-2, /* API misuse */ -} HSE_sink_res; - -typedef enum { - HSER_POLL_EMPTY, /* input exhausted */ - HSER_POLL_MORE, /* poll again for more output */ - HSER_POLL_ERROR_NULL=-1, /* NULL argument */ - HSER_POLL_ERROR_MISUSE=-2, /* API misuse */ -} HSE_poll_res; - -typedef enum { - HSER_FINISH_DONE, /* encoding is complete */ - HSER_FINISH_MORE, /* more output remaining; use poll */ - HSER_FINISH_ERROR_NULL=-1, /* NULL argument */ -} HSE_finish_res; - -#if HEATSHRINK_DYNAMIC_ALLOC -#define HEATSHRINK_ENCODER_WINDOW_BITS(HSE) \ - ((HSE)->window_sz2) -#define HEATSHRINK_ENCODER_LOOKAHEAD_BITS(HSE) \ - ((HSE)->lookahead_sz2) -#define HEATSHRINK_ENCODER_INDEX(HSE) \ - ((HSE)->search_index) -struct hs_index { - uint16_t size; - int16_t index[]; -}; -#else -#define HEATSHRINK_ENCODER_WINDOW_BITS(_) \ - (HEATSHRINK_STATIC_WINDOW_BITS) -#define HEATSHRINK_ENCODER_LOOKAHEAD_BITS(_) \ - (HEATSHRINK_STATIC_LOOKAHEAD_BITS) -#define HEATSHRINK_ENCODER_INDEX(HSE) \ - (&(HSE)->search_index) -struct hs_index { - uint16_t size; - int16_t index[2 << HEATSHRINK_STATIC_WINDOW_BITS]; -}; -#endif - -typedef struct { - uint16_t input_size; /* bytes in input buffer */ - uint16_t match_scan_index; - uint16_t match_length; - uint16_t match_pos; - uint16_t outgoing_bits; /* enqueued outgoing bits */ - uint8_t outgoing_bits_count; - uint8_t flags; - uint8_t state; /* current state machine node */ - uint8_t current_byte; /* current byte of output */ - uint8_t bit_index; /* current bit index */ -#if HEATSHRINK_DYNAMIC_ALLOC - uint8_t window_sz2; /* 2^n size of window */ - uint8_t lookahead_sz2; /* 2^n size of lookahead */ -#if HEATSHRINK_USE_INDEX - struct hs_index *search_index; -#endif - /* input buffer and / sliding window for expansion */ - uint8_t* buffer; -#else - #if HEATSHRINK_USE_INDEX - struct hs_index search_index; - #endif - /* input buffer and / sliding window for expansion */ - uint8_t buffer[2 << HEATSHRINK_ENCODER_WINDOW_BITS(_)]; -#endif -} heatshrink_encoder; - -#if HEATSHRINK_DYNAMIC_ALLOC -/* Allocate a new encoder struct and its buffers. - * Returns NULL on error. */ -heatshrink_encoder *heatshrink_encoder_alloc(uint8_t* buffer, uint8_t window_sz2, - uint8_t lookahead_sz2); - -/* Free an encoder. */ -void heatshrink_encoder_free(heatshrink_encoder *hse); -#endif - -/* Reset an encoder. */ -void heatshrink_encoder_reset(heatshrink_encoder *hse); - -/* Sink up to SIZE bytes from IN_BUF into the encoder. - * INPUT_SIZE is set to the number of bytes actually sunk (in case a - * buffer was filled.). */ -HSE_sink_res heatshrink_encoder_sink(heatshrink_encoder *hse, - uint8_t *in_buf, size_t size, size_t *input_size); - -/* Poll for output from the encoder, copying at most OUT_BUF_SIZE bytes into - * OUT_BUF (setting *OUTPUT_SIZE to the actual amount copied). */ -HSE_poll_res heatshrink_encoder_poll(heatshrink_encoder *hse, - uint8_t *out_buf, size_t out_buf_size, size_t *output_size); - -/* Notify the encoder that the input stream is finished. - * If the return value is HSER_FINISH_MORE, there is still more output, so - * call heatshrink_encoder_poll and repeat. */ -HSE_finish_res heatshrink_encoder_finish(heatshrink_encoder *hse); - -#endif diff --git a/lib/misc.scons b/lib/misc.scons index 49b6b61d97b..b479851b126 100644 --- a/lib/misc.scons +++ b/lib/misc.scons @@ -36,7 +36,6 @@ for lib in libs_recurse: sources += libenv.GlobRecursive("*.c*", lib) libs_plain = [ - "heatshrink", "nanopb", ] @@ -47,6 +46,12 @@ for lib in libs_plain: source=True, ) +sources += Glob( + "heatshrink/heatshrink_*.c*", + exclude=GLOB_FILE_EXCLUSION, + source=True, +) + lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources) libenv.Install("${LIB_DIST_DIR}", lib) Return("lib") diff --git a/firmware/targets/f7/furi_hal/furi_hal_compress.c b/lib/toolbox/compress.c similarity index 67% rename from firmware/targets/f7/furi_hal/furi_hal_compress.c rename to lib/toolbox/compress.c index 7e31dbbf7f0..0d5e1c654d7 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_compress.c +++ b/lib/toolbox/compress.c @@ -1,115 +1,112 @@ -#include +#include "compress.h" #include #include #include -#define TAG "FuriHalCompress" +/** Defines encoder and decoder window size */ +#define COMPRESS_EXP_BUFF_SIZE_LOG (8u) -#define FURI_HAL_COMPRESS_ICON_ENCODED_BUFF_SIZE (2 * 512) -#define FURI_HAL_COMPRESS_ICON_DECODED_BUFF_SIZE (1024) +/** Defines encoder and decoder lookahead buffer size */ +#define COMPRESS_LOOKAHEAD_BUFF_SIZE_LOG (4u) -#define FURI_HAL_COMPRESS_EXP_BUFF_SIZE (1 << FURI_HAL_COMPRESS_EXP_BUFF_SIZE_LOG) +/** Buffer sizes for input and output data */ +#define COMPRESS_ICON_ENCODED_BUFF_SIZE (1024u) +#define COMPRESS_ICON_DECODED_BUFF_SIZE (1024u) typedef struct { uint8_t is_compressed; uint8_t reserved; uint16_t compressed_buff_size; -} FuriHalCompressHeader; +} CompressHeader; -typedef struct { - heatshrink_decoder* decoder; - uint8_t - compress_buff[FURI_HAL_COMPRESS_EXP_BUFF_SIZE + FURI_HAL_COMPRESS_ICON_ENCODED_BUFF_SIZE]; - uint8_t decoded_buff[FURI_HAL_COMPRESS_ICON_DECODED_BUFF_SIZE]; -} FuriHalCompressIcon; +_Static_assert(sizeof(CompressHeader) == 4, "Incorrect CompressHeader size"); -struct FuriHalCompress { - heatshrink_encoder* encoder; +struct CompressIcon { heatshrink_decoder* decoder; - uint8_t* compress_buff; - uint16_t compress_buff_size; + uint8_t decoded_buff[COMPRESS_ICON_DECODED_BUFF_SIZE]; }; -static FuriHalCompressIcon* icon_decoder; +CompressIcon* compress_icon_alloc() { + CompressIcon* instance = malloc(sizeof(CompressIcon)); + instance->decoder = heatshrink_decoder_alloc( + COMPRESS_ICON_ENCODED_BUFF_SIZE, + COMPRESS_EXP_BUFF_SIZE_LOG, + COMPRESS_LOOKAHEAD_BUFF_SIZE_LOG); + heatshrink_decoder_reset(instance->decoder); + memset(instance->decoded_buff, 0, sizeof(instance->decoded_buff)); -static void furi_hal_compress_reset(FuriHalCompress* compress) { - furi_assert(compress); - heatshrink_encoder_reset(compress->encoder); - heatshrink_decoder_reset(compress->decoder); - memset(compress->compress_buff, 0, compress->compress_buff_size); + return instance; } -void furi_hal_compress_icon_init() { - icon_decoder = malloc(sizeof(FuriHalCompressIcon)); - icon_decoder->decoder = heatshrink_decoder_alloc( - icon_decoder->compress_buff, - FURI_HAL_COMPRESS_ICON_ENCODED_BUFF_SIZE, - FURI_HAL_COMPRESS_EXP_BUFF_SIZE_LOG, - FURI_HAL_COMPRESS_LOOKAHEAD_BUFF_SIZE_LOG); - heatshrink_decoder_reset(icon_decoder->decoder); - memset(icon_decoder->decoded_buff, 0, sizeof(icon_decoder->decoded_buff)); - FURI_LOG_I(TAG, "Init OK"); +void compress_icon_free(CompressIcon* instance) { + furi_assert(instance); + heatshrink_decoder_free(instance->decoder); + free(instance); } -void furi_hal_compress_icon_decode(const uint8_t* icon_data, uint8_t** decoded_buff) { +void compress_icon_decode(CompressIcon* instance, const uint8_t* icon_data, uint8_t** decoded_buff) { + furi_assert(instance); furi_assert(icon_data); furi_assert(decoded_buff); - FuriHalCompressHeader* header = (FuriHalCompressHeader*)icon_data; + CompressHeader* header = (CompressHeader*)icon_data; if(header->is_compressed) { size_t data_processed = 0; heatshrink_decoder_sink( - icon_decoder->decoder, - (uint8_t*)&icon_data[4], + instance->decoder, + (uint8_t*)&icon_data[sizeof(CompressHeader)], header->compressed_buff_size, &data_processed); while(1) { HSD_poll_res res = heatshrink_decoder_poll( - icon_decoder->decoder, - icon_decoder->decoded_buff, - sizeof(icon_decoder->decoded_buff), + instance->decoder, + instance->decoded_buff, + sizeof(instance->decoded_buff), &data_processed); furi_assert((res == HSDR_POLL_EMPTY) || (res == HSDR_POLL_MORE)); if(res != HSDR_POLL_MORE) { break; } } - heatshrink_decoder_reset(icon_decoder->decoder); - memset(icon_decoder->compress_buff, 0, sizeof(icon_decoder->compress_buff)); - *decoded_buff = icon_decoder->decoded_buff; + heatshrink_decoder_reset(instance->decoder); + *decoded_buff = instance->decoded_buff; } else { *decoded_buff = (uint8_t*)&icon_data[1]; } } -FuriHalCompress* furi_hal_compress_alloc(uint16_t compress_buff_size) { - FuriHalCompress* compress = malloc(sizeof(FuriHalCompress)); - compress->compress_buff = malloc(compress_buff_size + FURI_HAL_COMPRESS_EXP_BUFF_SIZE); - compress->encoder = heatshrink_encoder_alloc( - compress->compress_buff, - FURI_HAL_COMPRESS_EXP_BUFF_SIZE_LOG, - FURI_HAL_COMPRESS_LOOKAHEAD_BUFF_SIZE_LOG); +struct Compress { + heatshrink_encoder* encoder; + heatshrink_decoder* decoder; +}; + +static void compress_reset(Compress* compress) { + furi_assert(compress); + heatshrink_encoder_reset(compress->encoder); + heatshrink_decoder_reset(compress->decoder); +} + +Compress* compress_alloc(uint16_t compress_buff_size) { + Compress* compress = malloc(sizeof(Compress)); + compress->encoder = + heatshrink_encoder_alloc(COMPRESS_EXP_BUFF_SIZE_LOG, COMPRESS_LOOKAHEAD_BUFF_SIZE_LOG); compress->decoder = heatshrink_decoder_alloc( - compress->compress_buff, - compress_buff_size, - FURI_HAL_COMPRESS_EXP_BUFF_SIZE_LOG, - FURI_HAL_COMPRESS_LOOKAHEAD_BUFF_SIZE_LOG); + compress_buff_size, COMPRESS_EXP_BUFF_SIZE_LOG, COMPRESS_LOOKAHEAD_BUFF_SIZE_LOG); return compress; } -void furi_hal_compress_free(FuriHalCompress* compress) { +void compress_free(Compress* compress) { furi_assert(compress); heatshrink_encoder_free(compress->encoder); heatshrink_decoder_free(compress->decoder); - free(compress->compress_buff); free(compress); } -bool furi_hal_compress_encode( - FuriHalCompress* compress, +bool compress_encode( + Compress* compress, uint8_t* data_in, size_t data_in_size, uint8_t* data_out, @@ -126,7 +123,7 @@ bool furi_hal_compress_encode( HSE_finish_res finish_res; bool encode_failed = false; size_t sunk = 0; - size_t res_buff_size = sizeof(FuriHalCompressHeader); + size_t res_buff_size = sizeof(CompressHeader); // Sink data to encoding buffer while((sunk < data_in_size) && !encode_failed) { @@ -174,7 +171,7 @@ bool furi_hal_compress_encode( bool result = true; // Write encoded data to output buffer if compression is efficient. Else - write header and original data if(!encode_failed && (res_buff_size < data_in_size + 1)) { - FuriHalCompressHeader header = { + CompressHeader header = { .is_compressed = 0x01, .reserved = 0x00, .compressed_buff_size = res_buff_size}; memcpy(data_out, &header, sizeof(header)); *data_res_size = res_buff_size; @@ -186,13 +183,13 @@ bool furi_hal_compress_encode( *data_res_size = 0; result = false; } - furi_hal_compress_reset(compress); + compress_reset(compress); return result; } -bool furi_hal_compress_decode( - FuriHalCompress* compress, +bool compress_decode( + Compress* compress, uint8_t* data_in, size_t data_in_size, uint8_t* data_out, @@ -212,11 +209,11 @@ bool furi_hal_compress_decode( size_t res_buff_size = 0; size_t poll_size = 0; - FuriHalCompressHeader* header = (FuriHalCompressHeader*)data_in; + CompressHeader* header = (CompressHeader*)data_in; if(header->is_compressed) { // Sink data to decoding buffer size_t compressed_size = header->compressed_buff_size; - size_t sunk = sizeof(FuriHalCompressHeader); + size_t sunk = sizeof(CompressHeader); while(sunk < compressed_size && !decode_failed) { sink_res = heatshrink_decoder_sink( compress->decoder, &data_in[sunk], compressed_size - sunk, &sink_size); @@ -258,7 +255,7 @@ bool furi_hal_compress_decode( } else { result = false; } - furi_hal_compress_reset(compress); + compress_reset(compress); return result; } diff --git a/lib/toolbox/compress.h b/lib/toolbox/compress.h new file mode 100644 index 00000000000..a18551d7f9a --- /dev/null +++ b/lib/toolbox/compress.h @@ -0,0 +1,96 @@ +/** + * @file compress.h + * LZSS based compression HAL API + */ +#pragma once + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** Compress Icon control structure */ +typedef struct CompressIcon CompressIcon; + +/** Initialize icon compressor + * + * @return Compress Icon instance + */ +CompressIcon* compress_icon_alloc(); + +/** Free icon compressor + * + * @param instance The Compress Icon instance + */ +void compress_icon_free(CompressIcon* instance); + +/** Decompress icon + * + * @warning decoded_buff pointer set by this function is valid till next + * `compress_icon_decode` or `compress_icon_free` call + * + * @param instance The Compress Icon instance + * @param icon_data pointer to icon data + * @param[in] decoded_buff pointer to decoded buffer pointer + */ +void compress_icon_decode(CompressIcon* instance, const uint8_t* icon_data, uint8_t** decoded_buff); + +/** Compress control structure */ +typedef struct Compress Compress; + +/** Allocate encoder and decoder + * + * @param compress_buff_size size of decoder and encoder buffer to allocate + * + * @return Compress instance + */ +Compress* compress_alloc(uint16_t compress_buff_size); + +/** Free encoder and decoder + * + * @param compress Compress instance + */ +void compress_free(Compress* compress); + +/** Encode data + * + * @param compress Compress instance + * @param data_in pointer to input data + * @param data_in_size size of input data + * @param data_out maximum size of output data + * @param data_res_size pointer to result output data size + * + * @return true on success + */ +bool compress_encode( + Compress* compress, + uint8_t* data_in, + size_t data_in_size, + uint8_t* data_out, + size_t data_out_size, + size_t* data_res_size); + +/** Decode data + * + * @param compress Compress instance + * @param data_in pointer to input data + * @param data_in_size size of input data + * @param data_out maximum size of output data + * @param data_res_size pointer to result output data size + * + * @return true on success + */ +bool compress_decode( + Compress* compress, + uint8_t* data_in, + size_t data_in_size, + uint8_t* data_out, + size_t data_out_size, + size_t* data_res_size); + +#ifdef __cplusplus +} +#endif From 9a14699aa062590744acd8d3fb65476308c26a42 Mon Sep 17 00:00:00 2001 From: Shukai Ni Date: Wed, 22 Mar 2023 16:19:07 -0400 Subject: [PATCH 492/824] Fix relative links in .md files (#2528) Fixed some incorrect relative link, which caused 404 error when viewing in GitHub. --- documentation/UnitTests.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/documentation/UnitTests.md b/documentation/UnitTests.md index d38d4c4b197..4717daa8ca0 100644 --- a/documentation/UnitTests.md +++ b/documentation/UnitTests.md @@ -17,11 +17,11 @@ To run the unit tests, follow these steps: 1. Compile the firmware with the tests enabled: `./fbt FIRMWARE_APP_SET=unit_tests`. 2. Flash the firmware using your preferred method. -3. Copy the [assets/unit_tests](assets/unit_tests) folder to the root of your Flipper Zero's SD card. +3. Copy the [assets/unit_tests](/assets/unit_tests) folder to the root of your Flipper Zero's SD card. 4. Launch the CLI session and run the `unit_tests` command. **NOTE:** To run a particular test (and skip all others), specify its name as the command argument. -See [test_index.c](applications/debug/unit_tests/test_index.c) for the complete list of test names. +See [test_index.c](/applications/debug/unit_tests/test_index.c) for the complete list of test names. ## Adding unit tests @@ -29,7 +29,7 @@ See [test_index.c](applications/debug/unit_tests/test_index.c) for the complete #### Entry point -The common entry point for all tests is the [unit_tests](applications/debug/unit_tests) application. Test-specific code is placed into an arbitrarily named subdirectory and is then called from the [test_index.c](applications/debug/unit_tests/test_index.c) source file. +The common entry point for all tests is the [unit_tests](/applications/debug/unit_tests) application. Test-specific code is placed into an arbitrarily named subdirectory and is then called from the [test_index.c](/applications/debug/unit_tests/test_index.c) source file. #### Test assets @@ -42,10 +42,10 @@ Some unit tests require external data in order to function. These files (commonl Each infrared protocol has a corresponding set of unit tests, so it makes sense to implement one when adding support for a new protocol. To add unit tests for your protocol, follow these steps: -1. Create a file named `test_.irtest` in the [assets](assets/unit_tests/infrared) directory. +1. Create a file named `test_.irtest` in the [assets](/assets/unit_tests/infrared) directory. 2. Fill it with the test data (more on it below). -3. Add the test code to [infrared_test.c](applications/debug/unit_tests/infrared/infrared_test.c). -4. Update the [assets](assets/unit_tests/infrared) on your Flipper Zero and run the tests to see if they pass. +3. Add the test code to [infrared_test.c](/applications/debug/unit_tests/infrared/infrared_test.c). +4. Update the [assets](/assets/unit_tests/infrared) on your Flipper Zero and run the tests to see if they pass. ##### Test data format From fad24efdf076725468e8793d6d3d592e768dcd2a Mon Sep 17 00:00:00 2001 From: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> Date: Fri, 24 Mar 2023 03:26:43 +0300 Subject: [PATCH 493/824] [FL-3188] Fix crash when emulating a DSGeneric key (#2530) --- lib/ibutton/protocols/dallas/protocol_ds_generic.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/ibutton/protocols/dallas/protocol_ds_generic.c b/lib/ibutton/protocols/dallas/protocol_ds_generic.c index af355f46127..6c698bb8924 100644 --- a/lib/ibutton/protocols/dallas/protocol_ds_generic.c +++ b/lib/ibutton/protocols/dallas/protocol_ds_generic.c @@ -62,6 +62,7 @@ bool ds_generic_write_blank(OneWireHost* host, iButtonProtocolData* protocol_dat } static bool ds_generic_reset_callback(bool is_short, void* context) { + furi_assert(context); DallasGenericProtocolData* data = context; if(!is_short) { onewire_slave_set_overdrive(data->state.bus, is_short); @@ -93,7 +94,7 @@ void ds_generic_emulate(OneWireSlave* bus, iButtonProtocolData* protocol_data) { DallasGenericProtocolData* data = protocol_data; data->state.bus = bus; - onewire_slave_set_reset_callback(bus, ds_generic_reset_callback, NULL); + onewire_slave_set_reset_callback(bus, ds_generic_reset_callback, protocol_data); onewire_slave_set_command_callback(bus, ds_generic_command_callback, protocol_data); } From ae9659d32dec51fc6e1d2a382b8119043f3fa012 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Mon, 27 Mar 2023 10:28:13 +0300 Subject: [PATCH 494/824] [FL-3193] Additional checks before invalidating the key (#2533) --- lib/nfc/nfc_worker.c | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/lib/nfc/nfc_worker.c b/lib/nfc/nfc_worker.c index 4561ff2af6e..c2b89c71aee 100644 --- a/lib/nfc/nfc_worker.c +++ b/lib/nfc/nfc_worker.c @@ -638,7 +638,8 @@ static void nfc_worker_mf_classic_key_attack( (uint32_t)key); if(mf_classic_authenticate(tx_rx, block_num, key, MfClassicKeyA)) { mf_classic_set_key_found(data, i, MfClassicKeyA, key); - FURI_LOG_D(TAG, "Key found"); + FURI_LOG_D( + TAG, "Key A found: %04lx%08lx", (uint32_t)(key >> 32), (uint32_t)key); nfc_worker->callback(NfcWorkerEventFoundKeyA, nfc_worker->context); uint64_t found_key; @@ -661,7 +662,8 @@ static void nfc_worker_mf_classic_key_attack( (uint32_t)key); if(mf_classic_authenticate(tx_rx, block_num, key, MfClassicKeyB)) { mf_classic_set_key_found(data, i, MfClassicKeyB, key); - FURI_LOG_D(TAG, "Key found"); + FURI_LOG_D( + TAG, "Key B found: %04lx%08lx", (uint32_t)(key >> 32), (uint32_t)key); nfc_worker->callback(NfcWorkerEventFoundKeyB, nfc_worker->context); } } @@ -760,9 +762,13 @@ void nfc_worker_mf_classic_dict_attack(NfcWorker* nfc_worker) { furi_hal_nfc_sleep(); deactivated = true; } else { - mf_classic_set_key_not_found(data, i, MfClassicKeyA); - is_key_a_found = false; - FURI_LOG_D(TAG, "Key %dA not found in attack", i); + // If the key A is marked as found and matches the searching key, invalidate it + if(mf_classic_is_key_found(data, i, MfClassicKeyA) && + data->block[i].value[0] == key) { + mf_classic_set_key_not_found(data, i, MfClassicKeyA); + is_key_a_found = false; + FURI_LOG_D(TAG, "Key %dA not found in attack", i); + } } if(!is_key_b_found) { is_key_b_found = mf_classic_is_key_found(data, i, MfClassicKeyB); @@ -775,9 +781,13 @@ void nfc_worker_mf_classic_dict_attack(NfcWorker* nfc_worker) { } deactivated = true; } else { - mf_classic_set_key_not_found(data, i, MfClassicKeyB); - is_key_b_found = false; - FURI_LOG_D(TAG, "Key %dB not found in attack", i); + // If the key B is marked as found and matches the searching key, invalidate it + if(mf_classic_is_key_found(data, i, MfClassicKeyB) && + data->block[i].value[10] == key) { + mf_classic_set_key_not_found(data, i, MfClassicKeyB); + is_key_b_found = false; + FURI_LOG_D(TAG, "Key %dB not found in attack", i); + } } if(is_key_a_found && is_key_b_found) break; if(nfc_worker->state != NfcWorkerStateMfClassicDictAttack) break; From 27341fc1934ee309ee2ba1a2a42322449a12d7f4 Mon Sep 17 00:00:00 2001 From: Ikko Eltociear Ashimine Date: Mon, 27 Mar 2023 16:39:24 +0900 Subject: [PATCH 495/824] Fix typo in fbt.md (#2539) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit availabe -> available Co-authored-by: あく --- documentation/fbt.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/fbt.md b/documentation/fbt.md index a4717463179..65729c5c842 100644 --- a/documentation/fbt.md +++ b/documentation/fbt.md @@ -72,7 +72,7 @@ To run cleanup (think of `make clean`) for specified targets, add the `-c` optio - `get_stlink` - output serial numbers for attached STLink probes. Used for specifying an adapter with `OPENOCD_ADAPTER_SERIAL=...`. - `lint`, `format` - run clang-format on the C source code to check and reformat it according to the `.clang-format` specs. - `lint_py`, `format_py` - run [black](https://black.readthedocs.io/en/stable/index.html) on the Python source code, build system files & application manifests. -- `firmware_pvs` - generate a PVS Studio report for the firmware. Requires PVS Studio to be availabe on your system's `PATH`. +- `firmware_pvs` - generate a PVS Studio report for the firmware. Requires PVS Studio to be available on your system's `PATH`. - `cli` - start a Flipper CLI session over USB. ### Firmware targets From 3617ad33e4f7d29cdf99ebb00b48865f323d2aea Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Mon, 27 Mar 2023 23:31:21 -0700 Subject: [PATCH 496/824] View Model: recursive mutex (#2532) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- applications/services/gui/view.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/services/gui/view.c b/applications/services/gui/view.c index 50c05a406fa..4d84cac507d 100644 --- a/applications/services/gui/view.c +++ b/applications/services/gui/view.c @@ -81,7 +81,7 @@ void view_allocate_model(View* view, ViewModelType type, size_t size) { view->model = malloc(size); } else if(view->model_type == ViewModelTypeLocking) { ViewModelLocking* model = malloc(sizeof(ViewModelLocking)); - model->mutex = furi_mutex_alloc(FuriMutexTypeNormal); + model->mutex = furi_mutex_alloc(FuriMutexTypeRecursive); furi_check(model->mutex); model->data = malloc(size); view->model = model; From 8b2dfea925835012f2e6b4a6eba4ce82fce7ca6c Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Tue, 28 Mar 2023 00:34:49 -0700 Subject: [PATCH 497/824] Improved thread lifecycle (#2534) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Core, Thread: mark thread to join from prvDeleteTCB * USB HAL: move vars to MEM2 * Core, Thread: cleanup sources * Cli: add magic delays on rx pipe error, prevent cli from consuming processor time * Furi: update thread documentation Co-authored-by: あく --- applications/services/cli/cli.c | 2 ++ firmware/targets/f7/furi_hal/furi_hal_usb.c | 5 ++- firmware/targets/f7/inc/FreeRTOSConfig.h | 5 +++ furi/core/thread.c | 35 ++++++++++++++------- furi/core/thread.h | 5 +++ furi/flipper.c | 4 +-- 6 files changed, 39 insertions(+), 17 deletions(-) diff --git a/applications/services/cli/cli.c b/applications/services/cli/cli.c index b68505c51b9..ad3bbd6650d 100644 --- a/applications/services/cli/cli.c +++ b/applications/services/cli/cli.c @@ -37,9 +37,11 @@ char cli_getc(Cli* cli) { if(cli->session != NULL) { if(cli->session->rx((uint8_t*)&c, 1, FuriWaitForever) == 0) { cli_reset(cli); + furi_delay_tick(10); } } else { cli_reset(cli); + furi_delay_tick(10); } return c; } diff --git a/firmware/targets/f7/furi_hal/furi_hal_usb.c b/firmware/targets/f7/furi_hal/furi_hal_usb.c index fc679114fa6..011add95323 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_usb.c +++ b/firmware/targets/f7/furi_hal/furi_hal_usb.c @@ -73,12 +73,11 @@ typedef enum { #define USB_SRV_ALL_EVENTS (UsbEventReset | UsbEventRequest | UsbEventMessage) PLACE_IN_SECTION("MB_MEM2") static UsbSrv usb = {0}; +PLACE_IN_SECTION("MB_MEM2") static uint32_t ubuf[0x20]; +PLACE_IN_SECTION("MB_MEM2") usbd_device udev; static const struct usb_string_descriptor dev_lang_desc = USB_ARRAY_DESC(USB_LANGID_ENG_US); -static uint32_t ubuf[0x20]; -usbd_device udev; - static int32_t furi_hal_usb_thread(void* context); static usbd_respond usb_descriptor_get(usbd_ctlreq* req, void** address, uint16_t* length); static void reset_evt(usbd_device* dev, uint8_t event, uint8_t ep); diff --git a/firmware/targets/f7/inc/FreeRTOSConfig.h b/firmware/targets/f7/inc/FreeRTOSConfig.h index 69ef9406bb2..9486f501c7d 100644 --- a/firmware/targets/f7/inc/FreeRTOSConfig.h +++ b/firmware/targets/f7/inc/FreeRTOSConfig.h @@ -58,6 +58,7 @@ extern uint32_t SystemCoreClock; #define configTIMER_SERVICE_TASK_NAME "TimersSrv" #define configIDLE_TASK_NAME "(-_-)" +#define configIDLE_TASK_STACK_DEPTH 128 /* Set the following definitions to 1 to include the API function, or zero to exclude the API function. */ @@ -138,3 +139,7 @@ standard names. */ #define traceTASK_SWITCHED_IN() \ extern void furi_hal_mpu_set_stack_protection(uint32_t* stack); \ furi_hal_mpu_set_stack_protection((uint32_t*)pxCurrentTCB->pxStack) + +#define portCLEAN_UP_TCB(pxTCB) \ + extern void furi_thread_cleanup_tcb_event(TaskHandle_t task); \ + furi_thread_cleanup_tcb_event(pxTCB) diff --git a/furi/core/thread.c b/furi/core/thread.c index b45651c29d1..d78070d61d5 100644 --- a/furi/core/thread.c +++ b/furi/core/thread.c @@ -24,7 +24,6 @@ struct FuriThreadStdout { }; struct FuriThread { - bool is_service; FuriThreadState state; int32_t ret; @@ -37,14 +36,19 @@ struct FuriThread { char* name; char* appid; - configSTACK_DEPTH_TYPE stack_size; FuriThreadPriority priority; TaskHandle_t task_handle; - bool heap_trace_enabled; size_t heap_size; FuriThreadStdout output; + + // Keep all non-alignable byte types in one place, + // this ensures that the size of this structure is minimal + bool is_service; + bool heap_trace_enabled; + + configSTACK_DEPTH_TYPE stack_size; }; static size_t __furi_thread_stdout_write(FuriThread* thread, const char* data, size_t size); @@ -107,14 +111,8 @@ static void furi_thread_body(void* context) { // flush stdout __furi_thread_stdout_flush(thread); - // from here we can't use thread pointer furi_thread_set_state(thread, FuriThreadStateStopped); - // clear thread local storage - furi_assert(pvTaskGetThreadLocalStoragePointer(NULL, 0) != NULL); - vTaskSetThreadLocalStoragePointer(NULL, 0, NULL); - - thread->task_handle = NULL; vTaskDelete(NULL); furi_thread_catch(); } @@ -249,11 +247,11 @@ void furi_thread_start(FuriThread* thread) { furi_assert(thread); furi_assert(thread->callback); furi_assert(thread->state == FuriThreadStateStopped); - furi_assert(thread->stack_size > 0 && thread->stack_size < 0xFFFF * 4); + furi_assert(thread->stack_size > 0 && thread->stack_size < (UINT16_MAX * sizeof(StackType_t))); furi_thread_set_state(thread, FuriThreadStateStarting); - uint32_t stack = thread->stack_size / 4; + uint32_t stack = thread->stack_size / sizeof(StackType_t); UBaseType_t priority = thread->priority ? thread->priority : FuriThreadPriorityNormal; if(thread->is_service) { thread->task_handle = xTaskCreateStatic( @@ -273,12 +271,25 @@ void furi_thread_start(FuriThread* thread) { furi_check(thread->task_handle); } +void furi_thread_cleanup_tcb_event(TaskHandle_t task) { + FuriThread* thread = pvTaskGetThreadLocalStoragePointer(task, 0); + if(thread) { + // clear thread local storage + vTaskSetThreadLocalStoragePointer(task, 0, NULL); + + thread->task_handle = NULL; + } +} + bool furi_thread_join(FuriThread* thread) { furi_assert(thread); furi_check(furi_thread_get_current() != thread); - // Wait for thread to stop + // !!! IMPORTANT NOTICE !!! + // + // If your thread exited, but your app stuck here: some other thread uses + // all cpu time, which delays kernel from releasing task handle while(thread->task_handle) { furi_delay_ms(10); } diff --git a/furi/core/thread.h b/furi/core/thread.h index 8f43984197b..b11a225b58b 100644 --- a/furi/core/thread.h +++ b/furi/core/thread.h @@ -75,6 +75,8 @@ FuriThread* furi_thread_alloc_ex( void* context); /** Release FuriThread + * + * @warning see furi_thread_join * * @param thread FuriThread instance */ @@ -173,6 +175,9 @@ FuriThreadState furi_thread_get_state(FuriThread* thread); void furi_thread_start(FuriThread* thread); /** Join FuriThread + * + * @warning Use this method only when CPU is not busy(Idle task receives + * control), otherwise it will wait forever. * * @param thread FuriThread instance * diff --git a/furi/flipper.c b/furi/flipper.c index 5c2ad81389f..8806ce27fb3 100644 --- a/furi/flipper.c +++ b/furi/flipper.c @@ -54,8 +54,8 @@ void vApplicationGetIdleTaskMemory( StackType_t** stack_ptr, uint32_t* stack_size) { *tcb_ptr = memmgr_alloc_from_pool(sizeof(StaticTask_t)); - *stack_ptr = memmgr_alloc_from_pool(sizeof(StackType_t) * configMINIMAL_STACK_SIZE); - *stack_size = configMINIMAL_STACK_SIZE; + *stack_ptr = memmgr_alloc_from_pool(sizeof(StackType_t) * configIDLE_TASK_STACK_DEPTH); + *stack_size = configIDLE_TASK_STACK_DEPTH; } void vApplicationGetTimerTaskMemory( From 0161d49d80111328b4f27803528858872c5a4aea Mon Sep 17 00:00:00 2001 From: Eric Betts Date: Tue, 28 Mar 2023 01:21:14 -0700 Subject: [PATCH 498/824] Elite progress (#2481) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * WIP: builds * can read standard * Test standard picopass dictiony during attack * correctly save diversified key * read card on success * more logs * update file location * Call setup methods * backbutton and attempt at skip * fixed skip * remove found key state * rename dictionary attack * move notification * center button back to start menu * wait for card * Picopass: proper integer formatting * Picopass: even more proper integer formatting * remove nextState Co-authored-by: あく --- applications/external/picopass/picopass.c | 9 + .../external/picopass/picopass_device.h | 9 + applications/external/picopass/picopass_i.h | 4 + .../external/picopass/picopass_worker.c | 145 ++++++++- .../external/picopass/picopass_worker.h | 7 +- .../picopass/scenes/picopass_scene_config.h | 1 + .../scenes/picopass_scene_elite_dict_attack.c | 170 +++++++++++ .../scenes/picopass_scene_read_card_success.c | 22 ++ .../picopass/scenes/picopass_scene_start.c | 15 +- .../external/picopass/views/dict_attack.c | 281 ++++++++++++++++++ .../external/picopass/views/dict_attack.h | 44 +++ 11 files changed, 693 insertions(+), 14 deletions(-) create mode 100644 applications/external/picopass/scenes/picopass_scene_elite_dict_attack.c create mode 100644 applications/external/picopass/views/dict_attack.c create mode 100644 applications/external/picopass/views/dict_attack.h diff --git a/applications/external/picopass/picopass.c b/applications/external/picopass/picopass.c index 5d1cee70edd..6737d8077d1 100644 --- a/applications/external/picopass/picopass.c +++ b/applications/external/picopass/picopass.c @@ -73,6 +73,12 @@ Picopass* picopass_alloc() { view_dispatcher_add_view( picopass->view_dispatcher, PicopassViewWidget, widget_get_view(picopass->widget)); + picopass->dict_attack = dict_attack_alloc(); + view_dispatcher_add_view( + picopass->view_dispatcher, + PicopassViewDictAttack, + dict_attack_get_view(picopass->dict_attack)); + return picopass; } @@ -103,6 +109,9 @@ void picopass_free(Picopass* picopass) { view_dispatcher_remove_view(picopass->view_dispatcher, PicopassViewWidget); widget_free(picopass->widget); + view_dispatcher_remove_view(picopass->view_dispatcher, PicopassViewDictAttack); + dict_attack_free(picopass->dict_attack); + // Worker picopass_worker_stop(picopass->worker); picopass_worker_free(picopass->worker); diff --git a/applications/external/picopass/picopass_device.h b/applications/external/picopass/picopass_device.h index d7d0977df4c..7fc35ebda15 100644 --- a/applications/external/picopass/picopass_device.h +++ b/applications/external/picopass/picopass_device.h @@ -27,8 +27,16 @@ #define PICOPASS_APP_EXTENSION ".picopass" #define PICOPASS_APP_SHADOW_EXTENSION ".pas" +#define PICOPASS_DICT_KEY_BATCH_SIZE 10 + typedef void (*PicopassLoadingCallback)(void* context, bool state); +typedef struct { + IclassEliteDict* dict; + IclassEliteDictType type; + uint8_t current_sector; +} IclassEliteDictAttackData; + typedef enum { PicopassDeviceEncryptionUnknown = 0, PicopassDeviceEncryptionNone = 0x14, @@ -69,6 +77,7 @@ typedef struct { typedef struct { PicopassBlock AA1[PICOPASS_MAX_APP_LIMIT]; PicopassPacs pacs; + IclassEliteDictAttackData iclass_elite_dict_attack_data; } PicopassDeviceData; typedef struct { diff --git a/applications/external/picopass/picopass_i.h b/applications/external/picopass/picopass_i.h index 54533e82364..9147cfa0cfe 100644 --- a/applications/external/picopass/picopass_i.h +++ b/applications/external/picopass/picopass_i.h @@ -21,6 +21,7 @@ #include #include "scenes/picopass_scene.h" +#include "views/dict_attack.h" #include #include @@ -36,6 +37,7 @@ enum PicopassCustomEvent { PicopassCustomEventWorkerExit, PicopassCustomEventByteInputDone, PicopassCustomEventTextInputDone, + PicopassCustomEventDictAttackSkip, }; typedef enum { @@ -60,6 +62,7 @@ struct Picopass { Loading* loading; TextInput* text_input; Widget* widget; + DictAttack* dict_attack; }; typedef enum { @@ -68,6 +71,7 @@ typedef enum { PicopassViewLoading, PicopassViewTextInput, PicopassViewWidget, + PicopassViewDictAttack, } PicopassView; Picopass* picopass_alloc(); diff --git a/applications/external/picopass/picopass_worker.c b/applications/external/picopass/picopass_worker.c index e61b67d9f34..174413bae36 100644 --- a/applications/external/picopass/picopass_worker.c +++ b/applications/external/picopass/picopass_worker.c @@ -23,7 +23,7 @@ PicopassWorker* picopass_worker_alloc() { // Worker thread attributes picopass_worker->thread = - furi_thread_alloc_ex("PicopassWorker", 8192, picopass_worker_task, picopass_worker); + furi_thread_alloc_ex("PicopassWorker", 8 * 1024, picopass_worker_task, picopass_worker); picopass_worker->callback = NULL; picopass_worker->context = NULL; @@ -66,14 +66,12 @@ void picopass_worker_start( void picopass_worker_stop(PicopassWorker* picopass_worker) { furi_assert(picopass_worker); - if(picopass_worker->state == PicopassWorkerStateBroken || - picopass_worker->state == PicopassWorkerStateReady) { - return; - } - picopass_worker_disable_field(ERR_NONE); + furi_assert(picopass_worker->thread); - picopass_worker_change_state(picopass_worker, PicopassWorkerStateStop); - furi_thread_join(picopass_worker->thread); + if(furi_thread_get_state(picopass_worker->thread) != FuriThreadStateStopped) { + picopass_worker_change_state(picopass_worker, PicopassWorkerStateStop); + furi_thread_join(picopass_worker->thread); + } } void picopass_worker_change_state(PicopassWorker* picopass_worker, PicopassWorkerState state) { @@ -460,6 +458,132 @@ ReturnCode picopass_write_block(PicopassBlock* AA1, uint8_t blockNo, uint8_t* ne return ERR_NONE; } +void picopass_worker_elite_dict_attack(PicopassWorker* picopass_worker) { + furi_assert(picopass_worker); + furi_assert(picopass_worker->callback); + + picopass_device_data_clear(picopass_worker->dev_data); + PicopassDeviceData* dev_data = picopass_worker->dev_data; + PicopassBlock* AA1 = dev_data->AA1; + PicopassPacs* pacs = &dev_data->pacs; + + for(size_t i = 0; i < PICOPASS_MAX_APP_LIMIT; i++) { + memset(AA1[i].data, 0, sizeof(AA1[i].data)); + } + memset(pacs, 0, sizeof(PicopassPacs)); + + IclassEliteDictAttackData* dict_attack_data = + &picopass_worker->dev_data->iclass_elite_dict_attack_data; + bool elite = (dict_attack_data->type != IclassStandardDictTypeFlipper); + + rfalPicoPassReadCheckRes rcRes; + rfalPicoPassCheckRes chkRes; + + ReturnCode err; + uint8_t mac[4] = {0}; + uint8_t ccnr[12] = {0}; + + size_t index = 0; + uint8_t key[PICOPASS_BLOCK_LEN] = {0}; + + // Load dictionary + IclassEliteDict* dict = dict_attack_data->dict; + if(!dict) { + FURI_LOG_E(TAG, "Dictionary not found"); + picopass_worker->callback(PicopassWorkerEventNoDictFound, picopass_worker->context); + return; + } + + do { + if(picopass_detect_card(1000) == ERR_NONE) { + picopass_worker->callback(PicopassWorkerEventCardDetected, picopass_worker->context); + + // Process first found device + err = picopass_read_preauth(AA1); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "picopass_read_preauth error %d", err); + picopass_worker->callback(PicopassWorkerEventAborted, picopass_worker->context); + return; + } + + // Thank you proxmark! + pacs->legacy = picopass_is_memset(AA1[5].data, 0xFF, 8); + pacs->se_enabled = (memcmp(AA1[5].data, "\xff\xff\xff\x00\x06\xff\xff\xff", 8) == 0); + if(pacs->se_enabled) { + FURI_LOG_D(TAG, "SE enabled"); + picopass_worker->callback(PicopassWorkerEventAborted, picopass_worker->context); + return; + } + + break; + } else { + picopass_worker->callback(PicopassWorkerEventNoCardDetected, picopass_worker->context); + } + if(picopass_worker->state != PicopassWorkerStateEliteDictAttack) break; + + furi_delay_ms(100); + } while(true); + + FURI_LOG_D( + TAG, "Start Dictionary attack, Key Count %lu", iclass_elite_dict_get_total_keys(dict)); + while(iclass_elite_dict_get_next_key(dict, key)) { + FURI_LOG_T(TAG, "Key %zu", index); + if(++index % PICOPASS_DICT_KEY_BATCH_SIZE == 0) { + picopass_worker->callback( + PicopassWorkerEventNewDictKeyBatch, picopass_worker->context); + } + + err = rfalPicoPassPollerReadCheck(&rcRes); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "rfalPicoPassPollerReadCheck error %d", err); + break; + } + memcpy(ccnr, rcRes.CCNR, sizeof(rcRes.CCNR)); // last 4 bytes left 0 + + uint8_t* csn = AA1[PICOPASS_CSN_BLOCK_INDEX].data; + uint8_t* div_key = AA1[PICOPASS_KD_BLOCK_INDEX].data; + + loclass_iclass_calc_div_key(csn, key, div_key, elite); + loclass_opt_doReaderMAC(ccnr, div_key, mac); + + err = rfalPicoPassPollerCheck(mac, &chkRes); + if(err == ERR_NONE) { + FURI_LOG_I(TAG, "Found key"); + memcpy(pacs->key, key, PICOPASS_BLOCK_LEN); + err = picopass_read_card(AA1); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "picopass_read_card error %d", err); + picopass_worker->callback(PicopassWorkerEventFail, picopass_worker->context); + break; + } + + err = picopass_device_parse_credential(AA1, pacs); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "picopass_device_parse_credential error %d", err); + picopass_worker->callback(PicopassWorkerEventFail, picopass_worker->context); + break; + } + + err = picopass_device_parse_wiegand(pacs->credential, &pacs->record); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "picopass_device_parse_wiegand error %d", err); + picopass_worker->callback(PicopassWorkerEventFail, picopass_worker->context); + break; + } + picopass_worker->callback(PicopassWorkerEventSuccess, picopass_worker->context); + break; + } + + if(picopass_worker->state != PicopassWorkerStateEliteDictAttack) break; + } + FURI_LOG_D(TAG, "Dictionary complete"); + if(picopass_worker->state == PicopassWorkerStateEliteDictAttack) { + picopass_worker->callback(PicopassWorkerEventSuccess, picopass_worker->context); + } else { + picopass_worker->callback(PicopassWorkerEventAborted, picopass_worker->context); + } +} + int32_t picopass_worker_task(void* context) { PicopassWorker* picopass_worker = context; @@ -470,9 +594,12 @@ int32_t picopass_worker_task(void* context) { picopass_worker_write(picopass_worker); } else if(picopass_worker->state == PicopassWorkerStateWriteKey) { picopass_worker_write_key(picopass_worker); + } else if(picopass_worker->state == PicopassWorkerStateEliteDictAttack) { + picopass_worker_elite_dict_attack(picopass_worker); + } else { + FURI_LOG_W(TAG, "Unknown state %d", picopass_worker->state); } picopass_worker_disable_field(ERR_NONE); - picopass_worker_change_state(picopass_worker, PicopassWorkerStateReady); return 0; diff --git a/applications/external/picopass/picopass_worker.h b/applications/external/picopass/picopass_worker.h index f5e9f3039b4..e9d37481b19 100644 --- a/applications/external/picopass/picopass_worker.h +++ b/applications/external/picopass/picopass_worker.h @@ -14,6 +14,7 @@ typedef enum { PicopassWorkerStateDetect, PicopassWorkerStateWrite, PicopassWorkerStateWriteKey, + PicopassWorkerStateEliteDictAttack, // Transition PicopassWorkerStateStop, } PicopassWorkerState; @@ -27,8 +28,10 @@ typedef enum { PicopassWorkerEventFail, PicopassWorkerEventNoCardDetected, PicopassWorkerEventSeEnabled, - - PicopassWorkerEventStartReading, + PicopassWorkerEventAborted, + PicopassWorkerEventCardDetected, + PicopassWorkerEventNewDictKeyBatch, + PicopassWorkerEventNoDictFound, } PicopassWorkerEvent; typedef void (*PicopassWorkerCallback)(PicopassWorkerEvent event, void* context); diff --git a/applications/external/picopass/scenes/picopass_scene_config.h b/applications/external/picopass/scenes/picopass_scene_config.h index f5a90d46e46..8ea97049856 100644 --- a/applications/external/picopass/scenes/picopass_scene_config.h +++ b/applications/external/picopass/scenes/picopass_scene_config.h @@ -14,3 +14,4 @@ ADD_SCENE(picopass, write_card_success, WriteCardSuccess) ADD_SCENE(picopass, read_factory_success, ReadFactorySuccess) ADD_SCENE(picopass, write_key, WriteKey) ADD_SCENE(picopass, key_menu, KeyMenu) +ADD_SCENE(picopass, elite_dict_attack, EliteDictAttack) diff --git a/applications/external/picopass/scenes/picopass_scene_elite_dict_attack.c b/applications/external/picopass/scenes/picopass_scene_elite_dict_attack.c new file mode 100644 index 00000000000..c76a8ffae59 --- /dev/null +++ b/applications/external/picopass/scenes/picopass_scene_elite_dict_attack.c @@ -0,0 +1,170 @@ +#include "../picopass_i.h" +#include + +#define TAG "IclassEliteDictAttack" + +typedef enum { + DictAttackStateIdle, + DictAttackStateUserDictInProgress, + DictAttackStateFlipperDictInProgress, + DictAttackStateStandardDictInProgress, +} DictAttackState; + +void picopass_dict_attack_worker_callback(PicopassWorkerEvent event, void* context) { + furi_assert(context); + Picopass* picopass = context; + view_dispatcher_send_custom_event(picopass->view_dispatcher, event); +} + +void picopass_dict_attack_result_callback(void* context) { + furi_assert(context); + Picopass* picopass = context; + view_dispatcher_send_custom_event( + picopass->view_dispatcher, PicopassCustomEventDictAttackSkip); +} + +static void + picopass_scene_elite_dict_attack_prepare_view(Picopass* picopass, DictAttackState state) { + IclassEliteDictAttackData* dict_attack_data = + &picopass->dev->dev_data.iclass_elite_dict_attack_data; + PicopassWorkerState worker_state = PicopassWorkerStateReady; + IclassEliteDict* dict = NULL; + + // Identify scene state + if(state == DictAttackStateIdle) { + if(iclass_elite_dict_check_presence(IclassEliteDictTypeUser)) { + FURI_LOG_D(TAG, "Starting with user dictionary"); + state = DictAttackStateUserDictInProgress; + } else { + FURI_LOG_D(TAG, "Starting with standard dictionary"); + state = DictAttackStateStandardDictInProgress; + } + } else if(state == DictAttackStateUserDictInProgress) { + FURI_LOG_D(TAG, "Moving from user dictionary to standard dictionary"); + state = DictAttackStateStandardDictInProgress; + } else if(state == DictAttackStateStandardDictInProgress) { + FURI_LOG_D(TAG, "Moving from standard dictionary to elite dictionary"); + state = DictAttackStateFlipperDictInProgress; + } + + // Setup view + if(state == DictAttackStateUserDictInProgress) { + worker_state = PicopassWorkerStateEliteDictAttack; + dict_attack_set_header(picopass->dict_attack, "Elite User Dictionary"); + dict_attack_data->type = IclassEliteDictTypeUser; + dict = iclass_elite_dict_alloc(IclassEliteDictTypeUser); + + // If failed to load user dictionary - try the system dictionary + if(!dict) { + FURI_LOG_E(TAG, "User dictionary not found"); + state = DictAttackStateStandardDictInProgress; + } + } + if(state == DictAttackStateStandardDictInProgress) { + worker_state = PicopassWorkerStateEliteDictAttack; + dict_attack_set_header(picopass->dict_attack, "Standard System Dictionary"); + dict_attack_data->type = IclassStandardDictTypeFlipper; + dict = iclass_elite_dict_alloc(IclassStandardDictTypeFlipper); + + if(!dict) { + FURI_LOG_E(TAG, "Flipper standard dictionary not found"); + state = DictAttackStateFlipperDictInProgress; + } + } + if(state == DictAttackStateFlipperDictInProgress) { + worker_state = PicopassWorkerStateEliteDictAttack; + dict_attack_set_header(picopass->dict_attack, "Elite System Dictionary"); + dict_attack_data->type = IclassEliteDictTypeFlipper; + dict = iclass_elite_dict_alloc(IclassEliteDictTypeFlipper); + if(!dict) { + FURI_LOG_E(TAG, "Flipper Elite dictionary not found"); + // Pass through to let the worker handle the failure + } + } + // Free previous dictionary + if(dict_attack_data->dict) { + iclass_elite_dict_free(dict_attack_data->dict); + } + dict_attack_data->dict = dict; + scene_manager_set_scene_state(picopass->scene_manager, PicopassSceneEliteDictAttack, state); + dict_attack_set_callback( + picopass->dict_attack, picopass_dict_attack_result_callback, picopass); + dict_attack_set_current_sector(picopass->dict_attack, 0); + dict_attack_set_card_detected(picopass->dict_attack); + dict_attack_set_total_dict_keys( + picopass->dict_attack, dict ? iclass_elite_dict_get_total_keys(dict) : 0); + picopass_worker_start( + picopass->worker, + worker_state, + &picopass->dev->dev_data, + picopass_dict_attack_worker_callback, + picopass); +} + +void picopass_scene_elite_dict_attack_on_enter(void* context) { + Picopass* picopass = context; + picopass_scene_elite_dict_attack_prepare_view(picopass, DictAttackStateIdle); + view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewDictAttack); + picopass_blink_start(picopass); + notification_message(picopass->notifications, &sequence_display_backlight_enforce_on); +} + +bool picopass_scene_elite_dict_attack_on_event(void* context, SceneManagerEvent event) { + Picopass* picopass = context; + bool consumed = false; + + uint32_t state = + scene_manager_get_scene_state(picopass->scene_manager, PicopassSceneEliteDictAttack); + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == PicopassWorkerEventSuccess || + event.event == PicopassWorkerEventAborted) { + if(state == DictAttackStateUserDictInProgress || + state == DictAttackStateStandardDictInProgress) { + picopass_worker_stop(picopass->worker); + picopass_scene_elite_dict_attack_prepare_view(picopass, state); + consumed = true; + } else { + scene_manager_next_scene(picopass->scene_manager, PicopassSceneReadCardSuccess); + consumed = true; + } + } else if(event.event == PicopassWorkerEventCardDetected) { + dict_attack_set_card_detected(picopass->dict_attack); + consumed = true; + } else if(event.event == PicopassWorkerEventNoCardDetected) { + dict_attack_set_card_removed(picopass->dict_attack); + consumed = true; + } else if(event.event == PicopassWorkerEventNewDictKeyBatch) { + dict_attack_inc_current_dict_key(picopass->dict_attack, PICOPASS_DICT_KEY_BATCH_SIZE); + consumed = true; + } else if(event.event == PicopassCustomEventDictAttackSkip) { + if(state == DictAttackStateUserDictInProgress) { + picopass_worker_stop(picopass->worker); + consumed = true; + } else if(state == DictAttackStateFlipperDictInProgress) { + picopass_worker_stop(picopass->worker); + consumed = true; + } else if(state == DictAttackStateStandardDictInProgress) { + picopass_worker_stop(picopass->worker); + consumed = true; + } + } + } else if(event.type == SceneManagerEventTypeBack) { + consumed = scene_manager_previous_scene(picopass->scene_manager); + } + return consumed; +} + +void picopass_scene_elite_dict_attack_on_exit(void* context) { + Picopass* picopass = context; + IclassEliteDictAttackData* dict_attack_data = + &picopass->dev->dev_data.iclass_elite_dict_attack_data; + // Stop worker + picopass_worker_stop(picopass->worker); + if(dict_attack_data->dict) { + iclass_elite_dict_free(dict_attack_data->dict); + dict_attack_data->dict = NULL; + } + dict_attack_reset(picopass->dict_attack); + picopass_blink_stop(picopass); + notification_message(picopass->notifications, &sequence_display_backlight_enforce_auto); +} diff --git a/applications/external/picopass/scenes/picopass_scene_read_card_success.c b/applications/external/picopass/scenes/picopass_scene_read_card_success.c index f078d460a29..198b21d986e 100644 --- a/applications/external/picopass/scenes/picopass_scene_read_card_success.c +++ b/applications/external/picopass/scenes/picopass_scene_read_card_success.c @@ -47,8 +47,21 @@ void picopass_scene_read_card_success_on_enter(void* context) { if(pacs->se_enabled) { furi_string_cat_printf(credential_str, "SE enabled"); } + + widget_add_button_element( + widget, + GuiButtonTypeCenter, + "Menu", + picopass_scene_read_card_success_widget_callback, + picopass); } else if(empty) { furi_string_cat_printf(wiegand_str, "Empty"); + widget_add_button_element( + widget, + GuiButtonTypeCenter, + "Menu", + picopass_scene_read_card_success_widget_callback, + picopass); } else if(pacs->record.bitLength == 0 || pacs->record.bitLength == 255) { // Neither of these are valid. Indicates the block was all 0x00 or all 0xff furi_string_cat_printf(wiegand_str, "Invalid PACS"); @@ -56,6 +69,12 @@ void picopass_scene_read_card_success_on_enter(void* context) { if(pacs->se_enabled) { furi_string_cat_printf(credential_str, "SE enabled"); } + widget_add_button_element( + widget, + GuiButtonTypeCenter, + "Menu", + picopass_scene_read_card_success_widget_callback, + picopass); } else { size_t bytesLength = 1 + pacs->record.bitLength / 8; furi_string_set(credential_str, ""); @@ -137,6 +156,9 @@ bool picopass_scene_read_card_success_on_event(void* context, SceneManagerEvent picopass_device_set_name(picopass->dev, ""); scene_manager_next_scene(picopass->scene_manager, PicopassSceneCardMenu); consumed = true; + } else if(event.event == GuiButtonTypeCenter) { + consumed = scene_manager_search_and_switch_to_another_scene( + picopass->scene_manager, PicopassSceneStart); } } return consumed; diff --git a/applications/external/picopass/scenes/picopass_scene_start.c b/applications/external/picopass/scenes/picopass_scene_start.c index d33a1d26492..8f7b627aaf3 100644 --- a/applications/external/picopass/scenes/picopass_scene_start.c +++ b/applications/external/picopass/scenes/picopass_scene_start.c @@ -1,10 +1,8 @@ #include "../picopass_i.h" enum SubmenuIndex { SubmenuIndexRead, - SubmenuIndexRunScript, + SubmenuIndexEliteDictAttack, SubmenuIndexSaved, - SubmenuIndexAddManually, - SubmenuIndexDebug, }; void picopass_scene_start_submenu_callback(void* context, uint32_t index) { @@ -17,6 +15,12 @@ void picopass_scene_start_on_enter(void* context) { Submenu* submenu = picopass->submenu; submenu_add_item( submenu, "Read Card", SubmenuIndexRead, picopass_scene_start_submenu_callback, picopass); + submenu_add_item( + submenu, + "Elite Dict. Attack", + SubmenuIndexEliteDictAttack, + picopass_scene_start_submenu_callback, + picopass); submenu_add_item( submenu, "Saved", SubmenuIndexSaved, picopass_scene_start_submenu_callback, picopass); @@ -43,6 +47,11 @@ bool picopass_scene_start_on_event(void* context, SceneManagerEvent event) { picopass->scene_manager, PicopassSceneStart, SubmenuIndexSaved); scene_manager_next_scene(picopass->scene_manager, PicopassSceneFileSelect); consumed = true; + } else if(event.event == SubmenuIndexEliteDictAttack) { + scene_manager_set_scene_state( + picopass->scene_manager, PicopassSceneStart, SubmenuIndexEliteDictAttack); + scene_manager_next_scene(picopass->scene_manager, PicopassSceneEliteDictAttack); + consumed = true; } } diff --git a/applications/external/picopass/views/dict_attack.c b/applications/external/picopass/views/dict_attack.c new file mode 100644 index 00000000000..fb7335f6c3d --- /dev/null +++ b/applications/external/picopass/views/dict_attack.c @@ -0,0 +1,281 @@ +#include "dict_attack.h" + +#include + +typedef enum { + DictAttackStateRead, + DictAttackStateCardRemoved, +} DictAttackState; + +struct DictAttack { + View* view; + DictAttackCallback callback; + void* context; +}; + +typedef struct { + DictAttackState state; + MfClassicType type; + FuriString* header; + uint8_t sectors_total; + uint8_t sectors_read; + uint8_t sector_current; + uint8_t keys_total; + uint8_t keys_found; + uint16_t dict_keys_total; + uint16_t dict_keys_current; + bool is_key_attack; + uint8_t key_attack_current_sector; +} DictAttackViewModel; + +static void dict_attack_draw_callback(Canvas* canvas, void* model) { + DictAttackViewModel* m = model; + if(m->state == DictAttackStateCardRemoved) { + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 64, 4, AlignCenter, AlignTop, "Lost the tag!"); + canvas_set_font(canvas, FontSecondary); + elements_multiline_text_aligned( + canvas, 64, 23, AlignCenter, AlignTop, "Make sure the tag is\npositioned correctly."); + } else if(m->state == DictAttackStateRead) { + char draw_str[32] = {}; + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned( + canvas, 64, 0, AlignCenter, AlignTop, furi_string_get_cstr(m->header)); + if(m->is_key_attack) { + snprintf( + draw_str, + sizeof(draw_str), + "Reuse key check for sector: %d", + m->key_attack_current_sector); + } else { + snprintf(draw_str, sizeof(draw_str), "Unlocking sector: %d", m->sector_current); + } + canvas_draw_str_aligned(canvas, 0, 10, AlignLeft, AlignTop, draw_str); + float dict_progress = m->dict_keys_total == 0 ? + 0 : + (float)(m->dict_keys_current) / (float)(m->dict_keys_total); + float progress = m->sectors_total == 0 ? 0 : + ((float)(m->sector_current) + dict_progress) / + (float)(m->sectors_total); + if(progress > 1.0) { + progress = 1.0; + } + if(m->dict_keys_current == 0) { + // Cause when people see 0 they think it's broken + snprintf(draw_str, sizeof(draw_str), "%d/%d", 1, m->dict_keys_total); + } else { + snprintf( + draw_str, sizeof(draw_str), "%d/%d", m->dict_keys_current, m->dict_keys_total); + } + elements_progress_bar_with_text(canvas, 0, 20, 128, dict_progress, draw_str); + canvas_set_font(canvas, FontSecondary); + snprintf(draw_str, sizeof(draw_str), "Keys found: %d/%d", m->keys_found, m->keys_total); + canvas_draw_str_aligned(canvas, 0, 33, AlignLeft, AlignTop, draw_str); + snprintf( + draw_str, sizeof(draw_str), "Sectors Read: %d/%d", m->sectors_read, m->sectors_total); + canvas_draw_str_aligned(canvas, 0, 43, AlignLeft, AlignTop, draw_str); + } + elements_button_center(canvas, "Skip"); +} + +static bool dict_attack_input_callback(InputEvent* event, void* context) { + DictAttack* dict_attack = context; + bool consumed = false; + if(event->type == InputTypeShort && event->key == InputKeyOk) { + if(dict_attack->callback) { + dict_attack->callback(dict_attack->context); + } + consumed = true; + } + return consumed; +} + +DictAttack* dict_attack_alloc() { + DictAttack* dict_attack = malloc(sizeof(DictAttack)); + dict_attack->view = view_alloc(); + view_allocate_model(dict_attack->view, ViewModelTypeLocking, sizeof(DictAttackViewModel)); + view_set_draw_callback(dict_attack->view, dict_attack_draw_callback); + view_set_input_callback(dict_attack->view, dict_attack_input_callback); + view_set_context(dict_attack->view, dict_attack); + with_view_model( + dict_attack->view, + DictAttackViewModel * model, + { model->header = furi_string_alloc(); }, + false); + return dict_attack; +} + +void dict_attack_free(DictAttack* dict_attack) { + furi_assert(dict_attack); + with_view_model( + dict_attack->view, + DictAttackViewModel * model, + { furi_string_free(model->header); }, + false); + view_free(dict_attack->view); + free(dict_attack); +} + +void dict_attack_reset(DictAttack* dict_attack) { + furi_assert(dict_attack); + with_view_model( + dict_attack->view, + DictAttackViewModel * model, + { + model->state = DictAttackStateRead; + model->type = MfClassicType1k; + model->sectors_total = 1; + model->sectors_read = 0; + model->sector_current = 0; + model->keys_total = 0; + model->keys_found = 0; + model->dict_keys_total = 0; + model->dict_keys_current = 0; + model->is_key_attack = false; + furi_string_reset(model->header); + }, + false); +} + +View* dict_attack_get_view(DictAttack* dict_attack) { + furi_assert(dict_attack); + return dict_attack->view; +} + +void dict_attack_set_callback(DictAttack* dict_attack, DictAttackCallback callback, void* context) { + furi_assert(dict_attack); + furi_assert(callback); + dict_attack->callback = callback; + dict_attack->context = context; +} + +void dict_attack_set_header(DictAttack* dict_attack, const char* header) { + furi_assert(dict_attack); + furi_assert(header); + + with_view_model( + dict_attack->view, + DictAttackViewModel * model, + { furi_string_set(model->header, header); }, + true); +} + +void dict_attack_set_card_detected(DictAttack* dict_attack) { + furi_assert(dict_attack); + with_view_model( + dict_attack->view, + DictAttackViewModel * model, + { + model->state = DictAttackStateRead; + model->sectors_total = 1; + model->keys_total = model->sectors_total; + }, + true); +} + +void dict_attack_set_card_removed(DictAttack* dict_attack) { + furi_assert(dict_attack); + with_view_model( + dict_attack->view, + DictAttackViewModel * model, + { model->state = DictAttackStateCardRemoved; }, + true); +} + +void dict_attack_set_sector_read(DictAttack* dict_attack, uint8_t sec_read) { + furi_assert(dict_attack); + with_view_model( + dict_attack->view, DictAttackViewModel * model, { model->sectors_read = sec_read; }, true); +} + +void dict_attack_set_keys_found(DictAttack* dict_attack, uint8_t keys_found) { + furi_assert(dict_attack); + with_view_model( + dict_attack->view, DictAttackViewModel * model, { model->keys_found = keys_found; }, true); +} + +void dict_attack_set_current_sector(DictAttack* dict_attack, uint8_t curr_sec) { + furi_assert(dict_attack); + with_view_model( + dict_attack->view, + DictAttackViewModel * model, + { + model->sector_current = curr_sec; + model->dict_keys_current = 0; + }, + true); +} + +void dict_attack_inc_current_sector(DictAttack* dict_attack) { + furi_assert(dict_attack); + with_view_model( + dict_attack->view, + DictAttackViewModel * model, + { + if(model->sector_current < model->sectors_total) { + model->sector_current++; + model->dict_keys_current = 0; + } + }, + true); +} + +void dict_attack_inc_keys_found(DictAttack* dict_attack) { + furi_assert(dict_attack); + with_view_model( + dict_attack->view, + DictAttackViewModel * model, + { + if(model->keys_found < model->keys_total) { + model->keys_found++; + } + }, + true); +} + +void dict_attack_set_total_dict_keys(DictAttack* dict_attack, uint16_t dict_keys_total) { + furi_assert(dict_attack); + with_view_model( + dict_attack->view, + DictAttackViewModel * model, + { model->dict_keys_total = dict_keys_total; }, + true); +} + +void dict_attack_inc_current_dict_key(DictAttack* dict_attack, uint16_t keys_tried) { + furi_assert(dict_attack); + with_view_model( + dict_attack->view, + DictAttackViewModel * model, + { + if(model->dict_keys_current + keys_tried < model->dict_keys_total) { + model->dict_keys_current += keys_tried; + } + }, + true); +} + +void dict_attack_set_key_attack(DictAttack* dict_attack, bool is_key_attack, uint8_t sector) { + furi_assert(dict_attack); + with_view_model( + dict_attack->view, + DictAttackViewModel * model, + { + model->is_key_attack = is_key_attack; + model->key_attack_current_sector = sector; + }, + true); +} + +void dict_attack_inc_key_attack_current_sector(DictAttack* dict_attack) { + furi_assert(dict_attack); + with_view_model( + dict_attack->view, + DictAttackViewModel * model, + { + if(model->key_attack_current_sector < model->sectors_total) { + model->key_attack_current_sector++; + } + }, + true); +} diff --git a/applications/external/picopass/views/dict_attack.h b/applications/external/picopass/views/dict_attack.h new file mode 100644 index 00000000000..bdfa3e95200 --- /dev/null +++ b/applications/external/picopass/views/dict_attack.h @@ -0,0 +1,44 @@ +#pragma once +#include +#include +#include + +#include + +typedef struct DictAttack DictAttack; + +typedef void (*DictAttackCallback)(void* context); + +DictAttack* dict_attack_alloc(); + +void dict_attack_free(DictAttack* dict_attack); + +void dict_attack_reset(DictAttack* dict_attack); + +View* dict_attack_get_view(DictAttack* dict_attack); + +void dict_attack_set_callback(DictAttack* dict_attack, DictAttackCallback callback, void* context); + +void dict_attack_set_header(DictAttack* dict_attack, const char* header); + +void dict_attack_set_card_detected(DictAttack* dict_attack); + +void dict_attack_set_card_removed(DictAttack* dict_attack); + +void dict_attack_set_sector_read(DictAttack* dict_attack, uint8_t sec_read); + +void dict_attack_set_keys_found(DictAttack* dict_attack, uint8_t keys_found); + +void dict_attack_set_current_sector(DictAttack* dict_attack, uint8_t curr_sec); + +void dict_attack_inc_current_sector(DictAttack* dict_attack); + +void dict_attack_inc_keys_found(DictAttack* dict_attack); + +void dict_attack_set_total_dict_keys(DictAttack* dict_attack, uint16_t dict_keys_total); + +void dict_attack_inc_current_dict_key(DictAttack* dict_attack, uint16_t keys_tried); + +void dict_attack_set_key_attack(DictAttack* dict_attack, bool is_key_attack, uint8_t sector); + +void dict_attack_inc_key_attack_current_sector(DictAttack* dict_attack); From ae3a3d6336a290c423c17a3798dd86085710a073 Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Fri, 31 Mar 2023 00:15:15 -0700 Subject: [PATCH 499/824] RPC: increase max message size (#2543) * RPC: increase max message size * RPC: do not use magic numbers --- applications/services/rpc/rpc.c | 2 +- applications/services/rpc/rpc.h | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/applications/services/rpc/rpc.c b/applications/services/rpc/rpc.c index 63a71ad06d6..57a4ec9aa2d 100644 --- a/applications/services/rpc/rpc.c +++ b/applications/services/rpc/rpc.c @@ -244,7 +244,7 @@ static int32_t rpc_session_worker(void* context) { .callback = rpc_pb_stream_read, .state = session, .errmsg = NULL, - .bytes_left = RPC_MAX_MESSAGE_SIZE, /* max incoming message size */ + .bytes_left = SIZE_MAX, }; bool message_decode_failed = false; diff --git a/applications/services/rpc/rpc.h b/applications/services/rpc/rpc.h index 21836d9a472..ec290e083af 100644 --- a/applications/services/rpc/rpc.h +++ b/applications/services/rpc/rpc.h @@ -10,7 +10,6 @@ extern "C" { #endif #define RPC_BUFFER_SIZE (1024) -#define RPC_MAX_MESSAGE_SIZE (1536) #define RECORD_RPC "rpc" From f192ccce2c3e4f6798632db01e45d63fcff94ed6 Mon Sep 17 00:00:00 2001 From: hedger Date: Sat, 1 Apr 2023 17:50:30 +0400 Subject: [PATCH 500/824] FatFS: use rtc for timestamping (#2555) * fatfs: use rtc * fatfs: removed duplicate get_fattime declaration * pvs: fixed warnings for fatfs timestamp * fatfs: fixed seconds packing * FuriHal: critical section around RTC datetime access * FatFS: remove unused configration defines, update documentation * FuriHal: checks instead of assets in RTC --------- Co-authored-by: Aleksandr Kutuzov --- firmware/targets/f7/fatfs/fatfs.c | 16 ++++++++++------ firmware/targets/f7/fatfs/ffconf.h | 5 +---- firmware/targets/f7/furi_hal/furi_hal_rtc.c | 6 ++++++ lib/fatfs/diskio.h | 1 - 4 files changed, 17 insertions(+), 11 deletions(-) diff --git a/firmware/targets/f7/fatfs/fatfs.c b/firmware/targets/f7/fatfs/fatfs.c index 2c0e77fece4..5a8912cbdb7 100644 --- a/firmware/targets/f7/fatfs/fatfs.c +++ b/firmware/targets/f7/fatfs/fatfs.c @@ -1,4 +1,5 @@ #include "fatfs.h" +#include "furi_hal_rtc.h" /** logical drive path */ char fatfs_path[4]; @@ -9,11 +10,14 @@ void fatfs_init(void) { FATFS_LinkDriver(&sd_fatfs_driver, fatfs_path); } -/** - * @brief Gets Time from RTC - * @param None - * @retval Time in DWORD +/** Gets Time from RTC + * + * @return Time in DWORD (toasters per square washing machine) */ -DWORD get_fattime(void) { - return 0; +DWORD get_fattime() { + FuriHalRtcDateTime furi_time; + furi_hal_rtc_get_datetime(&furi_time); + + return ((uint32_t)(furi_time.year - 1980) << 25) | furi_time.month << 21 | + furi_time.day << 16 | furi_time.hour << 11 | furi_time.minute << 5 | furi_time.second; } diff --git a/firmware/targets/f7/fatfs/ffconf.h b/firmware/targets/f7/fatfs/ffconf.h index a44521550d1..8408a1ec175 100644 --- a/firmware/targets/f7/fatfs/ffconf.h +++ b/firmware/targets/f7/fatfs/ffconf.h @@ -219,10 +219,7 @@ / When enable exFAT, also LFN needs to be enabled. (_USE_LFN >= 1) / Note that enabling exFAT discards C89 compatibility. */ -#define _FS_NORTC 1 -#define _NORTC_MON 7 -#define _NORTC_MDAY 20 -#define _NORTC_YEAR 2021 +#define _FS_NORTC 0 /* The option _FS_NORTC switches timestamp functiton. If the system does not have / any RTC function or valid timestamp is not needed, set _FS_NORTC = 1 to disable / the timestamp function. All objects modified by FatFs will have a fixed timestamp diff --git a/firmware/targets/f7/furi_hal/furi_hal_rtc.c b/firmware/targets/f7/furi_hal/furi_hal_rtc.c index e011406ad89..84e7fe3959c 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_rtc.c +++ b/firmware/targets/f7/furi_hal/furi_hal_rtc.c @@ -281,8 +281,10 @@ FuriHalRtcLocaleDateFormat furi_hal_rtc_get_locale_dateformat() { } void furi_hal_rtc_set_datetime(FuriHalRtcDateTime* datetime) { + furi_check(!FURI_IS_IRQ_MODE()); furi_assert(datetime); + FURI_CRITICAL_ENTER(); /* Disable write protection */ LL_RTC_DisableWriteProtection(RTC); @@ -319,13 +321,17 @@ void furi_hal_rtc_set_datetime(FuriHalRtcDateTime* datetime) { /* Enable write protection */ LL_RTC_EnableWriteProtection(RTC); + FURI_CRITICAL_EXIT(); } void furi_hal_rtc_get_datetime(FuriHalRtcDateTime* datetime) { + furi_check(!FURI_IS_IRQ_MODE()); furi_assert(datetime); + FURI_CRITICAL_ENTER(); uint32_t time = LL_RTC_TIME_Get(RTC); // 0x00HHMMSS uint32_t date = LL_RTC_DATE_Get(RTC); // 0xWWDDMMYY + FURI_CRITICAL_EXIT(); datetime->second = __LL_RTC_CONVERT_BCD2BIN((time >> 0) & 0xFF); datetime->minute = __LL_RTC_CONVERT_BCD2BIN((time >> 8) & 0xFF); diff --git a/lib/fatfs/diskio.h b/lib/fatfs/diskio.h index 5b61e570ba9..0ccc9d461d0 100644 --- a/lib/fatfs/diskio.h +++ b/lib/fatfs/diskio.h @@ -37,7 +37,6 @@ DSTATUS disk_status (BYTE pdrv); DRESULT disk_read (BYTE pdrv, BYTE* buff, DWORD sector, UINT count); DRESULT disk_write (BYTE pdrv, const BYTE* buff, DWORD sector, UINT count); DRESULT disk_ioctl (BYTE pdrv, BYTE cmd, void* buff); -DWORD get_fattime (void); /* Disk Status Bits (DSTATUS) */ From f98ac4c48a267b0c536b76d8638432c8f7aeb677 Mon Sep 17 00:00:00 2001 From: Eric Betts Date: Mon, 3 Apr 2023 20:21:43 -0700 Subject: [PATCH 501/824] Add more detail to saved info screen (#2548) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add more detail to saved info screen * PR feedback * Format sources and add pvs temp files to gitignore Co-authored-by: あく --- .gitignore | 1 + .../scenes/picopass_scene_device_info.c | 54 ++++++++++++++----- 2 files changed, 41 insertions(+), 14 deletions(-) diff --git a/.gitignore b/.gitignore index 45ac911394d..81e985db7a0 100644 --- a/.gitignore +++ b/.gitignore @@ -60,5 +60,6 @@ openocd.log # PVS Studio temporary files .PVS-Studio/ PVS-Studio.log +*.PVS-Studio.* .gdbinit diff --git a/applications/external/picopass/scenes/picopass_scene_device_info.c b/applications/external/picopass/scenes/picopass_scene_device_info.c index 046e9c8e450..41caeabf5ad 100644 --- a/applications/external/picopass/scenes/picopass_scene_device_info.c +++ b/applications/external/picopass/scenes/picopass_scene_device_info.c @@ -14,43 +14,69 @@ void picopass_scene_device_info_widget_callback( void picopass_scene_device_info_on_enter(void* context) { Picopass* picopass = context; - FuriString* credential_str; - FuriString* wiegand_str; - credential_str = furi_string_alloc(); - wiegand_str = furi_string_alloc(); + FuriString* csn_str = furi_string_alloc_set("CSN:"); + FuriString* credential_str = furi_string_alloc(); + FuriString* wiegand_str = furi_string_alloc(); + FuriString* sio_str = furi_string_alloc(); DOLPHIN_DEED(DolphinDeedNfcReadSuccess); // Setup view + PicopassBlock* AA1 = picopass->dev->dev_data.AA1; PicopassPacs* pacs = &picopass->dev->dev_data.pacs; Widget* widget = picopass->widget; - size_t bytesLength = 1 + pacs->record.bitLength / 8; - furi_string_set(credential_str, ""); - for(uint8_t i = PICOPASS_BLOCK_LEN - bytesLength; i < PICOPASS_BLOCK_LEN; i++) { - furi_string_cat_printf(credential_str, " %02X", pacs->credential[i]); + uint8_t csn[PICOPASS_BLOCK_LEN] = {0}; + memcpy(csn, AA1[PICOPASS_CSN_BLOCK_INDEX].data, PICOPASS_BLOCK_LEN); + for(uint8_t i = 0; i < PICOPASS_BLOCK_LEN; i++) { + furi_string_cat_printf(csn_str, "%02X ", csn[i]); } - if(pacs->record.valid) { - furi_string_cat_printf( - wiegand_str, "FC: %u CN: %u", pacs->record.FacilityCode, pacs->record.CardNumber); + if(pacs->record.bitLength == 0 || pacs->record.bitLength == 255) { + // Neither of these are valid. Indicates the block was all 0x00 or all 0xff + furi_string_cat_printf(wiegand_str, "Invalid PACS"); } else { - furi_string_cat_printf(wiegand_str, "%d bits", pacs->record.bitLength); + size_t bytesLength = pacs->record.bitLength / 8; + if(pacs->record.bitLength % 8 > 0) { + // Add extra byte if there are bits remaining + bytesLength++; + } + furi_string_set(credential_str, ""); + for(uint8_t i = PICOPASS_BLOCK_LEN - bytesLength; i < PICOPASS_BLOCK_LEN; i++) { + furi_string_cat_printf(credential_str, " %02X", pacs->credential[i]); + } + + if(pacs->record.valid) { + furi_string_cat_printf( + wiegand_str, "FC: %u CN: %u", pacs->record.FacilityCode, pacs->record.CardNumber); + } else { + furi_string_cat_printf(wiegand_str, "%d bits", pacs->record.bitLength); + } + + if(pacs->sio) { + furi_string_cat_printf(sio_str, "+SIO"); + } } widget_add_string_element( - widget, 64, 12, AlignCenter, AlignCenter, FontPrimary, furi_string_get_cstr(wiegand_str)); + widget, 64, 5, AlignCenter, AlignCenter, FontSecondary, furi_string_get_cstr(csn_str)); + widget_add_string_element( + widget, 64, 20, AlignCenter, AlignCenter, FontPrimary, furi_string_get_cstr(wiegand_str)); widget_add_string_element( widget, 64, - 32, + 36, AlignCenter, AlignCenter, FontSecondary, furi_string_get_cstr(credential_str)); + widget_add_string_element( + widget, 64, 46, AlignCenter, AlignCenter, FontSecondary, furi_string_get_cstr(sio_str)); + furi_string_free(csn_str); furi_string_free(credential_str); furi_string_free(wiegand_str); + furi_string_free(sio_str); widget_add_button_element( picopass->widget, From efc52ab46901b69f887cebbd9a9a8764b1cc11f7 Mon Sep 17 00:00:00 2001 From: Leo Smith <19672114+p4p1@users.noreply.github.com> Date: Tue, 4 Apr 2023 05:40:19 +0200 Subject: [PATCH 502/824] BdUSBadded WAIT_FOR_BUTTON_PRESS functionality (#2544) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: p4p1 Co-authored-by: あく --- .../main/bad_usb/helpers/ducky_script.c | 26 +++++++++++++++++++ .../main/bad_usb/helpers/ducky_script.h | 1 + .../bad_usb/helpers/ducky_script_commands.c | 12 +++++++++ .../main/bad_usb/helpers/ducky_script_i.h | 1 + .../main/bad_usb/views/bad_usb_view.c | 2 ++ .../file_formats/BadUsbScriptFormat.md | 7 +++++ 6 files changed, 49 insertions(+) diff --git a/applications/main/bad_usb/helpers/ducky_script.c b/applications/main/bad_usb/helpers/ducky_script.c index 0206b7d09fc..47d8a7e0517 100644 --- a/applications/main/bad_usb/helpers/ducky_script.c +++ b/applications/main/bad_usb/helpers/ducky_script.c @@ -283,6 +283,10 @@ static int32_t ducky_script_execute_next(BadUsbScript* bad_usb, File* script_fil delay_val = ducky_parse_line(bad_usb, bad_usb->line_prev); if(delay_val == SCRIPT_STATE_NEXT_LINE) { // Empty line return 0; + } else if(delay_val == SCRIPT_STATE_STRING_START) { // Print string with delays + return delay_val; + } else if(delay_val == SCRIPT_STATE_WAIT_FOR_BTN) { // wait for button + return delay_val; } else if(delay_val < 0) { // Script error bad_usb->st.error_line = bad_usb->st.line_cur - 1; FURI_LOG_E(WORKER_TAG, "Unknown command at line %u", bad_usb->st.line_cur - 1U); @@ -320,6 +324,8 @@ static int32_t ducky_script_execute_next(BadUsbScript* bad_usb, File* script_fil return 0; } else if(delay_val == SCRIPT_STATE_STRING_START) { // Print string with delays return delay_val; + } else if(delay_val == SCRIPT_STATE_WAIT_FOR_BTN) { // wait for button + return delay_val; } else if(delay_val < 0) { bad_usb->st.error_line = bad_usb->st.line_cur; FURI_LOG_E(WORKER_TAG, "Unknown command at line %u", bad_usb->st.line_cur); @@ -509,6 +515,9 @@ static int32_t bad_usb_worker(void* context) { delay_val = bad_usb->defdelay; bad_usb->string_print_pos = 0; worker_state = BadUsbStateStringDelay; + } else if(delay_val == SCRIPT_STATE_WAIT_FOR_BTN) { // set state to wait for user input + worker_state = BadUsbStateWaitForBtn; + bad_usb->st.state = BadUsbStateWaitForBtn; // Show long delays } else if(delay_val > 1000) { bad_usb->st.state = BadUsbStateDelay; // Show long delays bad_usb->st.delay_remain = delay_val / 1000; @@ -516,6 +525,23 @@ static int32_t bad_usb_worker(void* context) { } else { furi_check((flags & FuriFlagError) == 0); } + } else if(worker_state == BadUsbStateWaitForBtn) { // State: Wait for button Press + uint16_t delay_cur = (delay_val > 1000) ? (1000) : (delay_val); + uint32_t flags = furi_thread_flags_wait( + WorkerEvtEnd | WorkerEvtToggle | WorkerEvtDisconnect, FuriFlagWaitAny, delay_cur); + if(!(flags & FuriFlagError)) { + if(flags & WorkerEvtEnd) { + break; + } else if(flags & WorkerEvtToggle) { + delay_val = 0; + worker_state = BadUsbStateRunning; + } else if(flags & WorkerEvtDisconnect) { + worker_state = BadUsbStateNotConnected; // USB disconnected + furi_hal_hid_kb_release_all(); + } + bad_usb->st.state = worker_state; + continue; + } } else if(worker_state == BadUsbStateStringDelay) { // State: print string with delays uint32_t flags = furi_thread_flags_wait( WorkerEvtEnd | WorkerEvtToggle | WorkerEvtDisconnect, diff --git a/applications/main/bad_usb/helpers/ducky_script.h b/applications/main/bad_usb/helpers/ducky_script.h index 0e616242a85..cff7239420e 100644 --- a/applications/main/bad_usb/helpers/ducky_script.h +++ b/applications/main/bad_usb/helpers/ducky_script.h @@ -15,6 +15,7 @@ typedef enum { BadUsbStateRunning, BadUsbStateDelay, BadUsbStateStringDelay, + BadUsbStateWaitForBtn, BadUsbStateDone, BadUsbStateScriptError, BadUsbStateFileError, diff --git a/applications/main/bad_usb/helpers/ducky_script_commands.c b/applications/main/bad_usb/helpers/ducky_script_commands.c index f3de43c1bdb..1498c9a73fc 100644 --- a/applications/main/bad_usb/helpers/ducky_script_commands.c +++ b/applications/main/bad_usb/helpers/ducky_script_commands.c @@ -143,6 +143,14 @@ static int32_t ducky_fnc_release(BadUsbScript* bad_usb, const char* line, int32_ return 0; } +static int32_t ducky_fnc_waitforbutton(BadUsbScript* bad_usb, const char* line, int32_t param) { + UNUSED(param); + UNUSED(bad_usb); + UNUSED(line); + + return SCRIPT_STATE_WAIT_FOR_BTN; +} + static const DuckyCmd ducky_commands[] = { {"REM ", NULL, -1}, {"ID ", NULL, -1}, @@ -160,8 +168,12 @@ static const DuckyCmd ducky_commands[] = { {"ALTCODE ", ducky_fnc_altstring, -1}, {"HOLD ", ducky_fnc_hold, -1}, {"RELEASE ", ducky_fnc_release, -1}, + {"WAIT_FOR_BUTTON_PRESS", ducky_fnc_waitforbutton, -1}, }; +#define TAG "BadUSB" +#define WORKER_TAG TAG "Worker" + int32_t ducky_execute_cmd(BadUsbScript* bad_usb, const char* line) { for(size_t i = 0; i < COUNT_OF(ducky_commands); i++) { if(strncmp(line, ducky_commands[i].name, strlen(ducky_commands[i].name)) == 0) { diff --git a/applications/main/bad_usb/helpers/ducky_script_i.h b/applications/main/bad_usb/helpers/ducky_script_i.h index 0cda0fa2c21..84c7ef9de6c 100644 --- a/applications/main/bad_usb/helpers/ducky_script_i.h +++ b/applications/main/bad_usb/helpers/ducky_script_i.h @@ -13,6 +13,7 @@ extern "C" { #define SCRIPT_STATE_NEXT_LINE (-3) #define SCRIPT_STATE_CMD_UNKNOWN (-4) #define SCRIPT_STATE_STRING_START (-5) +#define SCRIPT_STATE_WAIT_FOR_BTN (-6) #define FILE_BUFFER_LEN 16 diff --git a/applications/main/bad_usb/views/bad_usb_view.c b/applications/main/bad_usb/views/bad_usb_view.c index 874d677c88e..0ab4365b7b1 100644 --- a/applications/main/bad_usb/views/bad_usb_view.c +++ b/applications/main/bad_usb/views/bad_usb_view.c @@ -51,6 +51,8 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) { elements_button_left(canvas, "Config"); } else if((model->state.state == BadUsbStateRunning) || (model->state.state == BadUsbStateDelay)) { elements_button_center(canvas, "Stop"); + } else if(model->state.state == BadUsbStateWaitForBtn) { + elements_button_center(canvas, "Press to continue"); } else if(model->state.state == BadUsbStateWillRun) { elements_button_center(canvas, "Cancel"); } diff --git a/documentation/file_formats/BadUsbScriptFormat.md b/documentation/file_formats/BadUsbScriptFormat.md index 8373bf6882c..9cb848e2b70 100644 --- a/documentation/file_formats/BadUsbScriptFormat.md +++ b/documentation/file_formats/BadUsbScriptFormat.md @@ -77,6 +77,13 @@ Up to 5 keys can be hold simultaneously. | HOLD | Special key or single character | Press and hold key untill RELEASE command | | RELEASE | Special key or single character | Release key | +## Wait for button press + +Will wait indefinitely for a button to be pressed +| Command | Parameters | Notes | +| --------------------- | ------------ | --------------------------------------------------------------------- | +| WAIT_FOR_BUTTON_PRESS | None | Will wait for the user to press a button to continue script execution | + ## String From 494002505e38c50db1eda4b85702c4c6fadd6d30 Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Tue, 4 Apr 2023 08:37:54 +0400 Subject: [PATCH 503/824] WS: fix protocol TX141TH-BV2 (#2559) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- .../protocols/lacrosse_tx141thbv2.c | 145 ++++++++++-------- 1 file changed, 78 insertions(+), 67 deletions(-) diff --git a/applications/external/weather_station/protocols/lacrosse_tx141thbv2.c b/applications/external/weather_station/protocols/lacrosse_tx141thbv2.c index 5d007b12fbc..33a61cee06d 100644 --- a/applications/external/weather_station/protocols/lacrosse_tx141thbv2.c +++ b/applications/external/weather_station/protocols/lacrosse_tx141thbv2.c @@ -2,11 +2,15 @@ #define TAG "WSProtocolLaCrosse_TX141THBv2" +#define LACROSSE_TX141TH_BV2_BIT_COUNT 41 + /* * Help * https://github.com/merbanan/rtl_433/blob/master/src/devices/lacrosse_tx141x.c * - * iiii iiii | bkcc tttt | tttt tttt | hhhh hhhh | cccc cccc | u + * iiii iiii | bkcc tttt | tttt tttt | hhhh hhhh | cccc cccc | u - 41 bit + * or + * iiii iiii | bkcc tttt | tttt tttt | hhhh hhhh | cccc cccc | -40 bit * - i: identification; changes on battery switch * - c: lfsr_digest8_reflect; * - u: unknown; @@ -17,10 +21,10 @@ */ static const SubGhzBlockConst ws_protocol_lacrosse_tx141thbv2_const = { - .te_short = 250, - .te_long = 500, + .te_short = 208, + .te_long = 417, .te_delta = 120, - .min_count_bit_for_found = 41, + .min_count_bit_for_found = 40, }; struct WSProtocolDecoderLaCrosse_TX141THBv2 { @@ -102,14 +106,14 @@ void ws_protocol_decoder_lacrosse_tx141thbv2_reset(void* context) { static bool ws_protocol_lacrosse_tx141thbv2_check_crc(WSProtocolDecoderLaCrosse_TX141THBv2* instance) { if(!instance->decoder.decode_data) return false; - uint8_t msg[] = { - instance->decoder.decode_data >> 33, - instance->decoder.decode_data >> 25, - instance->decoder.decode_data >> 17, - instance->decoder.decode_data >> 9}; + uint64_t data = instance->decoder.decode_data; + if(instance->decoder.decode_count_bit == LACROSSE_TX141TH_BV2_BIT_COUNT) { + data >>= 1; + } + uint8_t msg[] = {data >> 32, data >> 24, data >> 16, data >> 8}; uint8_t crc = subghz_protocol_blocks_lfsr_digest8_reflect(msg, 4, 0x31, 0xF4); - return (crc == ((instance->decoder.decode_data >> 1) & 0xFF)); + return (crc == (data & 0xFF)); } /** @@ -117,14 +121,43 @@ static bool * @param instance Pointer to a WSBlockGeneric* instance */ static void ws_protocol_lacrosse_tx141thbv2_remote_controller(WSBlockGeneric* instance) { - instance->id = instance->data >> 33; - instance->battery_low = (instance->data >> 32) & 1; - instance->btn = (instance->data >> 31) & 1; - instance->channel = ((instance->data >> 29) & 0x03) + 1; - instance->temp = ((float)((instance->data >> 17) & 0x0FFF) - 500.0f) / 10.0f; - instance->humidity = (instance->data >> 9) & 0xFF; + uint64_t data = instance->data; + if(instance->data_count_bit == LACROSSE_TX141TH_BV2_BIT_COUNT) { + data >>= 1; + } + instance->id = data >> 32; + instance->battery_low = (data >> 31) & 1; + instance->btn = (data >> 30) & 1; + instance->channel = ((data >> 28) & 0x03) + 1; + instance->temp = ((float)((data >> 16) & 0x0FFF) - 500.0f) / 10.0f; + instance->humidity = (data >> 8) & 0xFF; } +/** + * Analysis of received data + * @param instance Pointer to a WSBlockGeneric* instance + */ +static bool ws_protocol_decoder_lacrosse_tx141thbv2_add_bit( + WSProtocolDecoderLaCrosse_TX141THBv2* instance, + uint32_t te_last, + uint32_t te_current) { + furi_assert(instance); + bool ret = false; + if(DURATION_DIFF( + te_last + te_current, + ws_protocol_lacrosse_tx141thbv2_const.te_short + + ws_protocol_lacrosse_tx141thbv2_const.te_long) < + ws_protocol_lacrosse_tx141thbv2_const.te_delta * 2) { + if(te_last > te_current) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + } else { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + } + ret = true; + } + + return ret; +} void ws_protocol_decoder_lacrosse_tx141thbv2_feed(void* context, bool level, uint32_t duration) { furi_assert(context); WSProtocolDecoderLaCrosse_TX141THBv2* instance = context; @@ -132,7 +165,7 @@ void ws_protocol_decoder_lacrosse_tx141thbv2_feed(void* context, bool level, uin switch(instance->decoder.parser_step) { case LaCrosse_TX141THBv2DecoderStepReset: if((level) && - (DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_short * 3) < + (DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_short * 4) < ws_protocol_lacrosse_tx141thbv2_const.te_delta * 2)) { instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepCheckPreambule; instance->decoder.te_last = duration; @@ -146,33 +179,17 @@ void ws_protocol_decoder_lacrosse_tx141thbv2_feed(void* context, bool level, uin } else { if((DURATION_DIFF( instance->decoder.te_last, - ws_protocol_lacrosse_tx141thbv2_const.te_short * 3) < + ws_protocol_lacrosse_tx141thbv2_const.te_short * 4) < ws_protocol_lacrosse_tx141thbv2_const.te_delta * 2) && - (DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_short * 3) < + (DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_short * 4) < ws_protocol_lacrosse_tx141thbv2_const.te_delta * 2)) { //Found preambule instance->header_count++; } else if(instance->header_count == 4) { - if((DURATION_DIFF( - instance->decoder.te_last, - ws_protocol_lacrosse_tx141thbv2_const.te_short) < - ws_protocol_lacrosse_tx141thbv2_const.te_delta) && - (DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_long) < - ws_protocol_lacrosse_tx141thbv2_const.te_delta)) { - instance->decoder.decode_data = 0; - instance->decoder.decode_count_bit = 0; - subghz_protocol_blocks_add_bit(&instance->decoder, 0); - instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepSaveDuration; - } else if( - (DURATION_DIFF( - instance->decoder.te_last, - ws_protocol_lacrosse_tx141thbv2_const.te_long) < - ws_protocol_lacrosse_tx141thbv2_const.te_delta) && - (DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_short) < - ws_protocol_lacrosse_tx141thbv2_const.te_delta)) { - instance->decoder.decode_data = 0; - instance->decoder.decode_count_bit = 0; - subghz_protocol_blocks_add_bit(&instance->decoder, 1); + if(ws_protocol_decoder_lacrosse_tx141thbv2_add_bit( + instance, instance->decoder.te_last, duration)) { + instance->decoder.decode_data = instance->decoder.decode_data & 1; + instance->decoder.decode_count_bit = 1; instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepSaveDuration; } else { instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepReset; @@ -198,37 +215,31 @@ void ws_protocol_decoder_lacrosse_tx141thbv2_feed(void* context, bool level, uin instance->decoder.te_last, ws_protocol_lacrosse_tx141thbv2_const.te_short * 3) < ws_protocol_lacrosse_tx141thbv2_const.te_delta * 2) && - (DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_short * 3) < + (DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_short * 4) < ws_protocol_lacrosse_tx141thbv2_const.te_delta * 2))) { + FURI_LOG_E( + "WS", + "%llX %d", + instance->decoder.decode_data, + instance->decoder.decode_count_bit); if((instance->decoder.decode_count_bit == - ws_protocol_lacrosse_tx141thbv2_const.min_count_bit_for_found) && - ws_protocol_lacrosse_tx141thbv2_check_crc(instance)) { - instance->generic.data = instance->decoder.decode_data; - instance->generic.data_count_bit = instance->decoder.decode_count_bit; - ws_protocol_lacrosse_tx141thbv2_remote_controller(&instance->generic); - if(instance->base.callback) - instance->base.callback(&instance->base, instance->base.context); + ws_protocol_lacrosse_tx141thbv2_const.min_count_bit_for_found) || + (instance->decoder.decode_count_bit == LACROSSE_TX141TH_BV2_BIT_COUNT)) { + if(ws_protocol_lacrosse_tx141thbv2_check_crc(instance)) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + ws_protocol_lacrosse_tx141thbv2_remote_controller(&instance->generic); + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->header_count = 1; + instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepCheckPreambule; + break; } - instance->decoder.decode_data = 0; - instance->decoder.decode_count_bit = 0; - instance->header_count = 1; - instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepCheckPreambule; - break; - } else if( - (DURATION_DIFF( - instance->decoder.te_last, ws_protocol_lacrosse_tx141thbv2_const.te_short) < - ws_protocol_lacrosse_tx141thbv2_const.te_delta) && - (DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_long) < - ws_protocol_lacrosse_tx141thbv2_const.te_delta)) { - subghz_protocol_blocks_add_bit(&instance->decoder, 0); - instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepSaveDuration; - } else if( - (DURATION_DIFF( - instance->decoder.te_last, ws_protocol_lacrosse_tx141thbv2_const.te_long) < - ws_protocol_lacrosse_tx141thbv2_const.te_delta) && - (DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_short) < - ws_protocol_lacrosse_tx141thbv2_const.te_delta)) { - subghz_protocol_blocks_add_bit(&instance->decoder, 1); + } else if(ws_protocol_decoder_lacrosse_tx141thbv2_add_bit( + instance, instance->decoder.te_last, duration)) { instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepSaveDuration; } else { instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepReset; From 4c488bd9704cb6af32bc9bcb523c3104c0a4a6a0 Mon Sep 17 00:00:00 2001 From: Anton Chistyakov Date: Wed, 5 Apr 2023 07:16:20 +0300 Subject: [PATCH 504/824] Fixing parsing troika number (#2536) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- lib/nfc/parsers/troika_4k_parser.c | 5 +++-- lib/nfc/parsers/troika_parser.c | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/nfc/parsers/troika_4k_parser.c b/lib/nfc/parsers/troika_4k_parser.c index 1f1b85a5c4b..d248feb17c3 100644 --- a/lib/nfc/parsers/troika_4k_parser.c +++ b/lib/nfc/parsers/troika_4k_parser.c @@ -90,13 +90,14 @@ bool troika_4k_parser_parse(NfcDeviceData* dev_data) { uint8_t* temp_ptr = &data->block[8 * 4 + 1].value[5]; uint16_t balance = ((temp_ptr[0] << 8) | temp_ptr[1]) / 25; - temp_ptr = &data->block[8 * 4].value[3]; + temp_ptr = &data->block[8 * 4].value[2]; uint32_t number = 0; - for(size_t i = 0; i < 4; i++) { + for(size_t i = 1; i < 5; i++) { number <<= 8; number |= temp_ptr[i]; } number >>= 4; + number |= (temp_ptr[0] & 0xf) << 28; furi_string_printf( dev_data->parsed_data, "\e#Troika\nNum: %lu\nBalance: %u rur.", number, balance); diff --git a/lib/nfc/parsers/troika_parser.c b/lib/nfc/parsers/troika_parser.c index bfd22364b70..6d8ae98f3cb 100644 --- a/lib/nfc/parsers/troika_parser.c +++ b/lib/nfc/parsers/troika_parser.c @@ -70,13 +70,14 @@ bool troika_parser_parse(NfcDeviceData* dev_data) { // Parse data uint8_t* temp_ptr = &data->block[8 * 4 + 1].value[5]; uint16_t balance = ((temp_ptr[0] << 8) | temp_ptr[1]) / 25; - temp_ptr = &data->block[8 * 4].value[3]; + temp_ptr = &data->block[8 * 4].value[2]; uint32_t number = 0; - for(size_t i = 0; i < 4; i++) { + for(size_t i = 1; i < 5; i++) { number <<= 8; number |= temp_ptr[i]; } number >>= 4; + number |= (temp_ptr[0] & 0xf) << 28; furi_string_printf( dev_data->parsed_data, "\e#Troika\nNum: %lu\nBalance: %u rur.", number, balance); From 2a26680acbfc2c526b48bac57b65132a0a1a94bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Thu, 6 Apr 2023 10:19:39 +0800 Subject: [PATCH 505/824] Furi: more gpio checks in HAL (#2549) * Furi: more gpio checks in HAL * Nfc: do not spawn service thread if it is already spawned Co-authored-by: Sergey Gavrilov --- firmware/targets/f7/furi_hal/furi_hal_gpio.c | 6 ++--- lib/ST25RFAL002/platform.c | 23 +++++++++++--------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/firmware/targets/f7/furi_hal/furi_hal_gpio.c b/firmware/targets/f7/furi_hal/furi_hal_gpio.c index e8318afcf59..afd482f2f55 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_gpio.c +++ b/firmware/targets/f7/furi_hal/furi_hal_gpio.c @@ -53,8 +53,8 @@ void furi_hal_gpio_init( const GpioPull pull, const GpioSpeed speed) { // we cannot set alternate mode in this function - furi_assert(mode != GpioModeAltFunctionPushPull); - furi_assert(mode != GpioModeAltFunctionOpenDrain); + furi_check(mode != GpioModeAltFunctionPushPull); + furi_check(mode != GpioModeAltFunctionOpenDrain); furi_hal_gpio_init_ex(gpio, mode, pull, speed, GpioAltFnUnused); } @@ -178,7 +178,7 @@ void furi_hal_gpio_add_int_callback(const GpioPin* gpio, GpioExtiCallback cb, vo FURI_CRITICAL_ENTER(); uint8_t pin_num = furi_hal_gpio_get_pin_num(gpio); - furi_assert(gpio_interrupt[pin_num].callback == NULL); + furi_check(gpio_interrupt[pin_num].callback == NULL); gpio_interrupt[pin_num].callback = cb; gpio_interrupt[pin_num].context = ctx; gpio_interrupt[pin_num].ready = true; diff --git a/lib/ST25RFAL002/platform.c b/lib/ST25RFAL002/platform.c index 754e2565013..5fe65ef880e 100644 --- a/lib/ST25RFAL002/platform.c +++ b/lib/ST25RFAL002/platform.c @@ -45,16 +45,19 @@ void platformDisableIrqCallback() { void platformSetIrqCallback(PlatformIrqCallback callback) { rfal_platform.callback = callback; - rfal_platform.thread = - furi_thread_alloc_ex("RfalIrqDriver", 1024, rfal_platform_irq_thread, NULL); - furi_thread_mark_as_service(rfal_platform.thread); - furi_thread_set_priority(rfal_platform.thread, FuriThreadPriorityIsr); - furi_thread_start(rfal_platform.thread); - - furi_hal_gpio_add_int_callback(&gpio_nfc_irq_rfid_pull, nfc_isr, NULL); - // Disable interrupt callback as the pin is shared between 2 apps - // It is enabled in rfalLowPowerModeStop() - furi_hal_gpio_disable_int_callback(&gpio_nfc_irq_rfid_pull); + + if(!rfal_platform.thread) { + rfal_platform.thread = + furi_thread_alloc_ex("RfalIrqDriver", 1024, rfal_platform_irq_thread, NULL); + furi_thread_mark_as_service(rfal_platform.thread); + furi_thread_set_priority(rfal_platform.thread, FuriThreadPriorityIsr); + furi_thread_start(rfal_platform.thread); + + furi_hal_gpio_add_int_callback(&gpio_nfc_irq_rfid_pull, nfc_isr, NULL); + // Disable interrupt callback as the pin is shared between 2 apps + // It is enabled in rfalLowPowerModeStop() + furi_hal_gpio_disable_int_callback(&gpio_nfc_irq_rfid_pull); + } } bool platformSpiTxRx(const uint8_t* txBuf, uint8_t* rxBuf, uint16_t len) { From 8a021ae48c809abc554ad38e480b887760753eda Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Wed, 5 Apr 2023 19:26:33 -0700 Subject: [PATCH 506/824] [FL-3224] SD Driver: do not cache sd status. (#2560) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * SD Driver: do not cache sd status. * SD Driver: fix status getter --------- Co-authored-by: あく --- firmware/targets/f7/fatfs/user_diskio.c | 36 ++++++++++++------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/firmware/targets/f7/fatfs/user_diskio.c b/firmware/targets/f7/fatfs/user_diskio.c index 74bf26f65d7..6663d119cdb 100644 --- a/firmware/targets/f7/fatfs/user_diskio.c +++ b/firmware/targets/f7/fatfs/user_diskio.c @@ -2,16 +2,14 @@ #include #include "sector_cache.h" -static volatile DSTATUS Stat = STA_NOINIT; - static DSTATUS driver_check_status(BYTE lun) { UNUSED(lun); - Stat = STA_NOINIT; - if(sd_get_card_state() == SdSpiStatusOK) { - Stat &= ~STA_NOINIT; + DSTATUS status = 0; + if(sd_get_card_state() != SdSpiStatusOK) { + status = STA_NOINIT; } - return Stat; + return status; } static DSTATUS driver_initialize(BYTE pdrv); @@ -107,6 +105,16 @@ static bool sd_device_write(uint32_t* buff, uint32_t sector, uint32_t count) { * @retval DSTATUS: Operation status */ static DSTATUS driver_initialize(BYTE pdrv) { + UNUSED(pdrv); + return RES_OK; +} + +/** + * @brief Gets Disk Status + * @param pdrv: Physical drive number (0..) + * @retval DSTATUS: Operation status + */ +static DSTATUS driver_status(BYTE pdrv) { furi_hal_spi_acquire(&furi_hal_spi_bus_handle_sd_fast); furi_hal_sd_spi_handle = &furi_hal_spi_bus_handle_sd_fast; @@ -118,16 +126,6 @@ static DSTATUS driver_initialize(BYTE pdrv) { return status; } -/** - * @brief Gets Disk Status - * @param pdrv: Physical drive number (0..) - * @retval DSTATUS: Operation status - */ -static DSTATUS driver_status(BYTE pdrv) { - UNUSED(pdrv); - return Stat; -} - /** * @brief Reads Sector(s) * @param pdrv: Physical drive number (0..) @@ -224,15 +222,15 @@ static DRESULT driver_write(BYTE pdrv, const BYTE* buff, DWORD sector, UINT coun * @retval DRESULT: Operation result */ static DRESULT driver_ioctl(BYTE pdrv, BYTE cmd, void* buff) { - UNUSED(pdrv); DRESULT res = RES_ERROR; SD_CardInfo CardInfo; - if(Stat & STA_NOINIT) return RES_NOTRDY; - furi_hal_spi_acquire(&furi_hal_spi_bus_handle_sd_fast); furi_hal_sd_spi_handle = &furi_hal_spi_bus_handle_sd_fast; + DSTATUS status = driver_check_status(pdrv); + if(status & STA_NOINIT) return RES_NOTRDY; + switch(cmd) { /* Make sure that no pending write process */ case CTRL_SYNC: From a91d3198396bf9623d0b1181ef841811bf718c5f Mon Sep 17 00:00:00 2001 From: hedger Date: Thu, 6 Apr 2023 06:44:37 +0400 Subject: [PATCH 507/824] [FL-3162] Moved ufbt to fbt codebase (#2520) * scripts: moved ufbt code * ufbt: fixed tool path * ufbt: fixed linter/formatter target descriptions * scripts: ufbt: cleanup * fbt: moved fap launch target to tools; ufbt fixes * fbt: fixed missing headers from SDK * ufbt: removed debug output * ufbt: moved project template to main codebase * ufbt: fixed vscode_dist * ufbt: path naming changes * fbt: error message for older ufbt versions * ufbt: docs fixes * ufbt: fixed build dir location * fbt: fixes for extapps objcopy * fbt: extapps: removed extra debug output; fixed formatting * ufbt: handle launch target for multiple known apps * ufbt: dropping wrapper; linter fixes * ufbt: fixed boostrap path * ufbt: renamed entrypoint * ufbt: updated vscode config * ufbt: moved sconsign db location * ufbt: fixed sconsign path * fbt: SDK builders rework * fbt: reworked sdk packaging * ufbt: additional checks and state processing * ufbt: fixed sdk state file location * dist: not packaging pycache * dump commit json content * Github: more workflow debug prints * Github: fix incorrect commit meta extraction in get_env.py * ufbt, fbt: changed SConsEnvironmentError->StopError * fbtenv: no longer needs SCRIPT_PATH pre-set * ufbt: fixed sdk state check * scripts: exception fixes for storage.py * scripts: fbtenv: added FBT_TOOLCHAIN_PATH for on Windows for compat * ufbt: app template: creating .gitkeep for images folder * ufbt: app template: fixed .gitkeep creation * docs: formatting fixes for AppManifests; added link to ufbt * fbt: added link to PyPI for old ufbt versions * sdk: fixed dir component paths Co-authored-by: Aleksandr Kutuzov --- .../loader/firmware_api/firmware_api.cpp | 2 +- documentation/AppManifests.md | 12 +- documentation/fbt.md | 2 + firmware.scons | 4 +- scripts/fbt_tools/fbt_assets.py | 4 +- scripts/fbt_tools/fbt_extapps.py | 78 +++- scripts/fbt_tools/fbt_sdk.py | 54 +-- scripts/fbt_tools/fbt_tweaks.py | 7 + scripts/flipper/storage.py | 18 +- scripts/get_env.py | 11 +- scripts/sconsdist.py | 212 +++++++--- scripts/serial_cli.py | 2 +- scripts/toolchain/fbtenv.cmd | 6 +- scripts/toolchain/fbtenv.sh | 34 +- scripts/ufbt/SConstruct | 393 ++++++++++++++++++ scripts/ufbt/commandline.scons | 90 ++++ scripts/ufbt/project_template/.clang-format | 191 +++++++++ scripts/ufbt/project_template/.editorconfig | 13 + scripts/ufbt/project_template/.gitignore | 4 + .../.vscode/c_cpp_properties.json | 14 + .../project_template/.vscode/extensions.json | 18 + .../ufbt/project_template/.vscode/launch.json | 98 +++++ .../project_template/.vscode/settings.json | 20 + .../ufbt/project_template/.vscode/tasks.json | 54 +++ .../app_template/${FBT_APPID}.c | 12 + .../app_template/${FBT_APPID}.png | Bin 0 -> 220 bytes .../app_template/application.fam | 17 + scripts/ufbt/site_init.py | 36 ++ scripts/ufbt/site_tools/ufbt_help.py | 53 +++ scripts/ufbt/site_tools/ufbt_state.py | 117 ++++++ scripts/ufbt/update.scons | 37 ++ site_scons/extapps.scons | 81 +--- 32 files changed, 1495 insertions(+), 199 deletions(-) create mode 100644 scripts/ufbt/SConstruct create mode 100644 scripts/ufbt/commandline.scons create mode 100644 scripts/ufbt/project_template/.clang-format create mode 100644 scripts/ufbt/project_template/.editorconfig create mode 100644 scripts/ufbt/project_template/.gitignore create mode 100644 scripts/ufbt/project_template/.vscode/c_cpp_properties.json create mode 100644 scripts/ufbt/project_template/.vscode/extensions.json create mode 100644 scripts/ufbt/project_template/.vscode/launch.json create mode 100644 scripts/ufbt/project_template/.vscode/settings.json create mode 100644 scripts/ufbt/project_template/.vscode/tasks.json create mode 100644 scripts/ufbt/project_template/app_template/${FBT_APPID}.c create mode 100644 scripts/ufbt/project_template/app_template/${FBT_APPID}.png create mode 100644 scripts/ufbt/project_template/app_template/application.fam create mode 100644 scripts/ufbt/site_init.py create mode 100644 scripts/ufbt/site_tools/ufbt_help.py create mode 100644 scripts/ufbt/site_tools/ufbt_state.py create mode 100644 scripts/ufbt/update.scons diff --git a/applications/services/loader/firmware_api/firmware_api.cpp b/applications/services/loader/firmware_api/firmware_api.cpp index 814dd82c927..52e86efc269 100644 --- a/applications/services/loader/firmware_api/firmware_api.cpp +++ b/applications/services/loader/firmware_api/firmware_api.cpp @@ -4,7 +4,7 @@ #include /* Generated table */ -#include +#include static_assert(!has_hash_collisions(elf_api_table), "Detected API method hash collision!"); diff --git a/documentation/AppManifests.md b/documentation/AppManifests.md index 195fe925688..99f6386b2c9 100644 --- a/documentation/AppManifests.md +++ b/documentation/AppManifests.md @@ -75,12 +75,12 @@ Example for building an app from Rust sources: Library sources must be placed in a subfolder of the `lib` folder within the application's source folder. Each library is defined as a call to the `Lib()` function, accepting the following parameters: - - **name**: name of the library's folder. Required. - - **fap_include_paths**: list of the library's relative paths to add to the parent fap's include path list. The default value is `["."]`, meaning the library's source root. - - **sources**: list of filename masks to be used for gathering include files for this library. Paths are relative to the library's source root. The default value is `["*.c*"]`. - - **cflags**: list of additional compiler flags to be used for building this library. The default value is `[]`. - - **cdefines**: list of additional preprocessor definitions to be used for building this library. The default value is `[]`. - - **cincludes**: list of additional include paths to be used for building this library. Paths are relative to the application's root. This can be used for providing external search paths for this library's code — for configuration headers. The default value is `[]`. + - **name**: name of the library's folder. Required. + - **fap_include_paths**: list of the library's relative paths to add to the parent fap's include path list. The default value is `["."]`, meaning the library's source root. + - **sources**: list of filename masks to be used for gathering include files for this library. Paths are relative to the library's source root. The default value is `["*.c*"]`. + - **cflags**: list of additional compiler flags to be used for building this library. The default value is `[]`. + - **cdefines**: list of additional preprocessor definitions to be used for building this library. The default value is `[]`. + - **cincludes**: list of additional include paths to be used for building this library. Paths are relative to the application's root. This can be used for providing external search paths for this library's code — for configuration headers. The default value is `[]`. Example for building an app with a private library: diff --git a/documentation/fbt.md b/documentation/fbt.md index 65729c5c842..14d63e9cea8 100644 --- a/documentation/fbt.md +++ b/documentation/fbt.md @@ -3,6 +3,8 @@ FBT is the entry point for firmware-related commands and utilities. It is invoked by `./fbt` in the firmware project root directory. Internally, it is a wrapper around [scons](https://scons.org/) build system. +If you don't need all features of `fbt` - like building the whole firmware - and only want to build and debug a single application, you can use [ufbt](https://pypi.org/project/ufbt/). + ## Environment To use `fbt`, you only need `git` installed in your system. diff --git a/firmware.scons b/firmware.scons index a094765af7b..c7fdc639252 100644 --- a/firmware.scons +++ b/firmware.scons @@ -68,7 +68,7 @@ env = ENV.Clone( ], }, }, - SDK_APISYMS=None, + FW_API_TABLE=None, _APP_ICONS=None, ) @@ -241,7 +241,7 @@ Depends( [ fwenv["FW_VERSION_JSON"], fwenv["FW_ASSETS_HEADERS"], - fwenv["SDK_APISYMS"], + fwenv["FW_API_TABLE"], fwenv["_APP_ICONS"], ], ) diff --git a/scripts/fbt_tools/fbt_assets.py b/scripts/fbt_tools/fbt_assets.py index d2a58f3fb82..e4c567993e7 100644 --- a/scripts/fbt_tools/fbt_assets.py +++ b/scripts/fbt_tools/fbt_assets.py @@ -1,6 +1,6 @@ from SCons.Builder import Builder from SCons.Action import Action -from SCons.Errors import SConsEnvironmentError +from SCons.Errors import StopError import os import subprocess @@ -90,7 +90,7 @@ def proto_ver_generator(target, source, env): source_dir=src_dir, ) except (subprocess.CalledProcessError, EnvironmentError) as e: - raise SConsEnvironmentError("Git: describe failed") + raise StopError("Git: describe failed") git_major, git_minor = git_describe.split(".") version_file_data = ( diff --git a/scripts/fbt_tools/fbt_extapps.py b/scripts/fbt_tools/fbt_extapps.py index ef0c2d30121..4ac1c68737c 100644 --- a/scripts/fbt_tools/fbt_extapps.py +++ b/scripts/fbt_tools/fbt_extapps.py @@ -21,6 +21,10 @@ from fbt.util import extract_abs_dir_path +_FAP_META_SECTION = ".fapmeta" +_FAP_FILEASSETS_SECTION = ".fapassets" + + @dataclass class FlipperExternalAppInfo: app: FlipperApplication @@ -234,6 +238,8 @@ def BuildAppElf(env, app): def prepare_app_metadata(target, source, env): + metadata_node = next(filter(lambda t: t.name.endswith(_FAP_META_SECTION), target)) + sdk_cache = SdkCache(env["SDK_DEFINITION"].path, load_version_only=True) if not sdk_cache.is_buildable(): @@ -242,8 +248,7 @@ def prepare_app_metadata(target, source, env): ) app = env["APP"] - meta_file_name = source[0].path + ".meta" - with open(meta_file_name, "wb") as f: + with open(metadata_node.abspath, "wb") as f: f.write( assemble_manifest_data( app_manifest=app, @@ -337,24 +342,26 @@ def embed_app_metadata_emitter(target, source, env): if app.apptype == FlipperAppType.PLUGIN: target[0].name = target[0].name.replace(".fap", ".fal") - meta_file_name = source[0].path + ".meta" - target.append("#" + meta_file_name) + target.append(env.File(source[0].abspath + _FAP_META_SECTION)) if app.fap_file_assets: - files_section = source[0].path + ".files.section" - target.append("#" + files_section) + target.append(env.File(source[0].abspath + _FAP_FILEASSETS_SECTION)) return (target, source) def prepare_app_files(target, source, env): + files_section_node = next( + filter(lambda t: t.name.endswith(_FAP_FILEASSETS_SECTION), target) + ) + app = env["APP"] - directory = app._appdir.Dir(app.fap_file_assets) + directory = env.Dir(app._apppath).Dir(app.fap_file_assets) if not directory.exists(): raise UserError(f"File asset directory {directory} does not exist") bundler = FileBundler(directory.abspath) - bundler.export(source[0].path + ".files.section") + bundler.export(files_section_node.abspath) def generate_embed_app_metadata_actions(source, target, env, for_signature): @@ -367,15 +374,15 @@ def generate_embed_app_metadata_actions(source, target, env, for_signature): objcopy_str = ( "${OBJCOPY} " "--remove-section .ARM.attributes " - "--add-section .fapmeta=${SOURCE}.meta " + "--add-section ${_FAP_META_SECTION}=${SOURCE}${_FAP_META_SECTION} " ) if app.fap_file_assets: actions.append(Action(prepare_app_files, "$APPFILE_COMSTR")) - objcopy_str += "--add-section .fapassets=${SOURCE}.files.section " + objcopy_str += "--add-section ${_FAP_FILEASSETS_SECTION}=${SOURCE}${_FAP_FILEASSETS_SECTION} " objcopy_str += ( - "--set-section-flags .fapmeta=contents,noload,readonly,data " + "--set-section-flags ${_FAP_META_SECTION}=contents,noload,readonly,data " "--strip-debug --strip-unneeded " "--add-gnu-debuglink=${SOURCE} " "${SOURCES} ${TARGET}" @@ -391,6 +398,51 @@ def generate_embed_app_metadata_actions(source, target, env, for_signature): return Action(actions) +def AddAppLaunchTarget(env, appname, launch_target_name): + deploy_sources, flipp_dist_paths, validators = [], [], [] + run_script_extra_ars = "" + + def _add_dist_targets(app_artifacts): + validators.append(app_artifacts.validator) + for _, ext_path in app_artifacts.dist_entries: + deploy_sources.append(app_artifacts.compact) + flipp_dist_paths.append(f"/ext/{ext_path}") + return app_artifacts + + def _add_host_app_to_targets(host_app): + artifacts_app_to_run = env["EXT_APPS"].get(host_app.appid, None) + _add_dist_targets(artifacts_app_to_run) + for plugin in host_app._plugins: + _add_dist_targets(env["EXT_APPS"].get(plugin.appid, None)) + + artifacts_app_to_run = env.GetExtAppByIdOrPath(appname) + if artifacts_app_to_run.app.apptype == FlipperAppType.PLUGIN: + # We deploy host app instead + host_app = env["APPMGR"].get(artifacts_app_to_run.app.requires[0]) + + if host_app: + if host_app.apptype == FlipperAppType.EXTERNAL: + _add_host_app_to_targets(host_app) + else: + # host app is a built-in app + run_script_extra_ars = f"-a {host_app.name}" + _add_dist_targets(artifacts_app_to_run) + else: + raise UserError("Host app is unknown") + else: + _add_host_app_to_targets(artifacts_app_to_run.app) + + # print(deploy_sources, flipp_dist_paths) + env.PhonyTarget( + launch_target_name, + '${PYTHON3} "${APP_RUN_SCRIPT}" ${EXTRA_ARGS} -s ${SOURCES} -t ${FLIPPER_FILE_TARGETS}', + source=deploy_sources, + FLIPPER_FILE_TARGETS=flipp_dist_paths, + EXTRA_ARGS=run_script_extra_ars, + ) + env.Alias(launch_target_name, validators) + + def generate(env, **kw): env.SetDefault( EXT_APPS_WORK_DIR="${FBT_FAP_DEBUG_ELF_ROOT}", @@ -410,10 +462,14 @@ def generate(env, **kw): EXT_APPS={}, # appid -> FlipperExternalAppInfo EXT_LIBS={}, _APP_ICONS=[], + _FAP_META_SECTION=_FAP_META_SECTION, + _FAP_FILEASSETS_SECTION=_FAP_FILEASSETS_SECTION, ) env.AddMethod(BuildAppElf) env.AddMethod(GetExtAppByIdOrPath) + env.AddMethod(AddAppLaunchTarget) + env.Append( BUILDERS={ "FapDist": Builder( diff --git a/scripts/fbt_tools/fbt_sdk.py b/scripts/fbt_tools/fbt_sdk.py index 324819818c0..90d0831eb42 100644 --- a/scripts/fbt_tools/fbt_sdk.py +++ b/scripts/fbt_tools/fbt_sdk.py @@ -38,13 +38,13 @@ def ProcessSdkDepends(env, filename): return depends -def prebuild_sdk_emitter(target, source, env): +def api_amalgam_emitter(target, source, env): target.append(env.ChangeFileExtension(target[0], ".d")) target.append(env.ChangeFileExtension(target[0], ".i.c")) return target, source -def prebuild_sdk_create_origin_file(target, source, env): +def api_amalgam_gen_origin_header(target, source, env): mega_file = env.subst("${TARGET}.c", target=target[0]) with open(mega_file, "wt") as sdk_c: sdk_c.write( @@ -87,6 +87,7 @@ def _wrap_scons_vars(self, vars: str): class SdkTreeBuilder: SDK_DIR_SUBST = "SDK_ROOT_DIR" SDK_APP_EP_SUBST = "SDK_APP_EP_SUBST" + HEADER_EXTENSIONS = [".h", ".hpp"] def __init__(self, env, target, source) -> None: self.env = env @@ -111,7 +112,10 @@ def _parse_sdk_depends(self): lines = LogicalLines(deps_f).readlines() _, depends = lines[0].split(":", 1) self.header_depends = list( - filter(lambda fname: fname.endswith(".h"), depends.split()), + filter( + lambda fname: any(map(fname.endswith, self.HEADER_EXTENSIONS)), + depends.split(), + ), ) self.header_depends.append(self.sdk_env.subst("${LINKER_SCRIPT_PATH}")) self.header_depends.append(self.sdk_env.subst("${SDK_DEFINITION}")) @@ -180,12 +184,12 @@ def deploy_action(self): self._generate_sdk_meta() -def deploy_sdk_tree_action(target, source, env): +def deploy_sdk_header_tree_action(target, source, env): sdk_tree = SdkTreeBuilder(env, target, source) return sdk_tree.deploy_action() -def deploy_sdk_tree_emitter(target, source, env): +def deploy_sdk_header_tree_emitter(target, source, env): sdk_tree = SdkTreeBuilder(env, target, source) return sdk_tree.emitter(target, source, env) @@ -224,7 +228,7 @@ def _check_sdk_is_up2date(sdk_cache: SdkCache): ) -def validate_sdk_cache(source, target, env): +def validate_api_cache(source, target, env): # print(f"Generating SDK for {source[0]} to {target[0]}") current_sdk = SdkCollector() current_sdk.process_source_file_for_sdk(source[0].path) @@ -237,7 +241,7 @@ def validate_sdk_cache(source, target, env): _check_sdk_is_up2date(sdk_cache) -def generate_sdk_symbols(source, target, env): +def generate_api_table(source, target, env): sdk_cache = SdkCache(source[0].path) _check_sdk_is_up2date(sdk_cache) @@ -249,11 +253,11 @@ def generate_sdk_symbols(source, target, env): def generate(env, **kw): if not env["VERBOSE"]: env.SetDefault( - SDK_PREGEN_COMSTR="\tPREGEN\t${TARGET}", - SDK_COMSTR="\tSDKSRC\t${TARGET}", + SDK_AMALGAMATE_HEADER_COMSTR="\tAPIPREP\t${TARGET}", + SDK_AMALGAMATE_PP_COMSTR="\tAPIPP\t${TARGET}", SDKSYM_UPDATER_COMSTR="\tSDKCHK\t${TARGET}", - SDKSYM_GENERATOR_COMSTR="\tSDKSYM\t${TARGET}", - SDKDEPLOY_COMSTR="\tSDKTREE\t${TARGET}", + APITABLE_GENERATOR_COMSTR="\tAPITBL\t${TARGET}", + SDKTREE_COMSTR="\tSDKTREE\t${TARGET}", ) # Filtering out things cxxheaderparser cannot handle @@ -274,40 +278,40 @@ def generate(env, **kw): env.AddMethod(ProcessSdkDepends) env.Append( BUILDERS={ - "SDKPrebuilder": Builder( - emitter=prebuild_sdk_emitter, + "ApiAmalgamator": Builder( + emitter=api_amalgam_emitter, action=[ Action( - prebuild_sdk_create_origin_file, - "$SDK_PREGEN_COMSTR", + api_amalgam_gen_origin_header, + "$SDK_AMALGAMATE_HEADER_COMSTR", ), Action( "$CC -o $TARGET -E -P $CCFLAGS $_CCCOMCOM $SDK_PP_FLAGS -MMD ${TARGET}.c", - "$SDK_COMSTR", + "$SDK_AMALGAMATE_PP_COMSTR", ), ], suffix=".i", ), - "SDKTree": Builder( + "SDKHeaderTreeExtractor": Builder( action=Action( - deploy_sdk_tree_action, - "$SDKDEPLOY_COMSTR", + deploy_sdk_header_tree_action, + "$SDKTREE_COMSTR", ), - emitter=deploy_sdk_tree_emitter, + emitter=deploy_sdk_header_tree_emitter, src_suffix=".d", ), - "SDKSymUpdater": Builder( + "ApiTableValidator": Builder( action=Action( - validate_sdk_cache, + validate_api_cache, "$SDKSYM_UPDATER_COMSTR", ), suffix=".csv", src_suffix=".i", ), - "SDKSymGenerator": Builder( + "ApiSymbolTable": Builder( action=Action( - generate_sdk_symbols, - "$SDKSYM_GENERATOR_COMSTR", + generate_api_table, + "$APITABLE_GENERATOR_COMSTR", ), suffix=".h", src_suffix=".csv", diff --git a/scripts/fbt_tools/fbt_tweaks.py b/scripts/fbt_tools/fbt_tweaks.py index a903d4033b5..700f66d2389 100644 --- a/scripts/fbt_tools/fbt_tweaks.py +++ b/scripts/fbt_tools/fbt_tweaks.py @@ -1,4 +1,6 @@ import SCons.Warnings as Warnings +from SCons.Errors import UserError + # from SCons.Script.Main import find_deepest_user_frame @@ -36,6 +38,11 @@ def fbt_warning(e): def generate(env): + if env.get("UFBT_WORK_DIR"): + raise UserError( + "You're trying to use a new format SDK on a legacy ufbt version. " + "Please update ufbt to a version from PyPI: https://pypi.org/project/ufbt/" + ) Warnings._warningOut = fbt_warning diff --git a/scripts/flipper/storage.py b/scripts/flipper/storage.py index 47e11236d11..7b56ee0d0ef 100644 --- a/scripts/flipper/storage.py +++ b/scripts/flipper/storage.py @@ -56,11 +56,11 @@ def from_value(cls, s: str | bytes): class FlipperStorageException(Exception): - def __init__(self, message): - super().__init__(f"Storage error: {message}") - - def __init__(self, path: str, error_code: StorageErrorCode): - super().__init__(f"Storage error: path '{path}': {error_code.value}") + @staticmethod + def from_error_code(path: str, error_code: StorageErrorCode): + return FlipperStorageException( + f"Storage error: path '{path}': {error_code.value}" + ) class BufferedRead: @@ -247,7 +247,9 @@ def send_file(self, filename_from: str, filename_to: str): if self.has_error(answer): last_error = self.get_error(answer) self.read.until(self.CLI_PROMPT) - raise FlipperStorageException(filename_to, last_error) + raise FlipperStorageException.from_error_code( + filename_to, last_error + ) self.port.write(filedata) self.read.until(self.CLI_PROMPT) @@ -319,7 +321,7 @@ def exist_dir(self, path: str): StorageErrorCode.INVALID_NAME, ): return False - raise FlipperStorageException(path, error_code) + raise FlipperStorageException.from_error_code(path, error_code) return True @@ -333,7 +335,7 @@ def exist_file(self, path: str): def _check_no_error(self, response, path=None): if self.has_error(response): - raise FlipperStorageException(self.get_error(response)) + raise FlipperStorageException.from_error_code(self.get_error(response)) def size(self, path: str): """file size on Flipper""" diff --git a/scripts/get_env.py b/scripts/get_env.py index e2da6eda5c7..f661f38d625 100644 --- a/scripts/get_env.py +++ b/scripts/get_env.py @@ -31,9 +31,10 @@ def parse_args(): def get_commit_json(event): context = ssl._create_unverified_context() - with urllib.request.urlopen( - event["pull_request"]["_links"]["commits"]["href"], context=context - ) as commit_file: + commit_url = event["pull_request"]["base"]["repo"]["commits_url"].replace( + "{/sha}", f"/{event['after']}" + ) + with urllib.request.urlopen(commit_url, context=context) as commit_file: commit_json = json.loads(commit_file.read().decode("utf-8")) return commit_json @@ -43,8 +44,8 @@ def get_details(event, args): current_time = datetime.datetime.utcnow().date() if args.type == "pull": commit_json = get_commit_json(event) - data["commit_comment"] = shlex.quote(commit_json[-1]["commit"]["message"]) - data["commit_hash"] = commit_json[-1]["sha"] + data["commit_comment"] = shlex.quote(commit_json["commit"]["message"]) + data["commit_hash"] = commit_json["sha"] ref = event["pull_request"]["head"]["ref"] data["pull_id"] = event["pull_request"]["number"] data["pull_name"] = shlex.quote(event["pull_request"]["title"]) diff --git a/scripts/sconsdist.py b/scripts/sconsdist.py index b8f1d72b260..c2cfecd4e55 100644 --- a/scripts/sconsdist.py +++ b/scripts/sconsdist.py @@ -1,13 +1,15 @@ #!/usr/bin/env python3 -from flipper.app import App -from os.path import join, exists, relpath -from os import makedirs, walk -from update import Main as UpdateMain +import json import shutil -import zipfile import tarfile +import zipfile +from os import makedirs, walk +from os.path import exists, join, relpath, basename, split + from ansi.color import fg +from flipper.app import App +from update import Main as UpdateMain class ProjectDir: @@ -54,12 +56,19 @@ def get_project_file_name(self, project: ProjectDir, filetype: str) -> str: if project_name == "firmware" and filetype != "elf": project_name = "full" - return self.get_dist_file_name(project_name, filetype) + dist_target_path = self.get_dist_file_name(project_name, filetype) + self.note_dist_component( + project_name, filetype, self.get_dist_path(dist_target_path) + ) + return dist_target_path + + def note_dist_component(self, component: str, extension: str, srcpath: str) -> None: + self._dist_components[f"{component}.{extension}"] = srcpath def get_dist_file_name(self, dist_artifact_type: str, filetype: str) -> str: return f"{self.DIST_FILE_PREFIX}{self.target}-{dist_artifact_type}-{self.args.suffix}.{filetype}" - def get_dist_file_path(self, filename: str) -> str: + def get_dist_path(self, filename: str) -> str: return join(self.output_dir_path, filename) def copy_single_project(self, project: ProjectDir) -> None: @@ -69,17 +78,15 @@ def copy_single_project(self, project: ProjectDir) -> None: if exists(src_file := join(obj_directory, f"{project.project}.{filetype}")): shutil.copyfile( src_file, - self.get_dist_file_path( - self.get_project_file_name(project, filetype) - ), + self.get_dist_path(self.get_project_file_name(project, filetype)), ) - for foldertype in ("sdk", "lib"): + for foldertype in ("sdk_headers", "lib"): if exists(sdk_folder := join(obj_directory, foldertype)): - self.package_zip(foldertype, sdk_folder) + self.note_dist_component(foldertype, "dir", sdk_folder) def package_zip(self, foldertype, sdk_folder): with zipfile.ZipFile( - self.get_dist_file_path(self.get_dist_file_name(foldertype, "zip")), + self.get_dist_path(self.get_dist_file_name(foldertype, "zip")), "w", zipfile.ZIP_DEFLATED, ) as zf: @@ -94,7 +101,8 @@ def package_zip(self, foldertype, sdk_folder): ) def copy(self) -> int: - self.projects = dict( + self._dist_components: dict[str, str] = dict() + self.projects: dict[str, ProjectDir] = dict( map( lambda pd: (pd.project, pd), map(ProjectDir, self.args.project), @@ -122,12 +130,18 @@ def copy(self) -> int: try: shutil.rmtree(self.output_dir_path) except Exception as ex: - pass + self.logger.warn(f"Failed to clean output directory: {ex}") if not exists(self.output_dir_path): + self.logger.debug(f"Creating output directory {self.output_dir_path}") makedirs(self.output_dir_path) + for folder in ("debug", "scripts"): + if exists(folder): + self.note_dist_component(folder, "dir", folder) + for project in self.projects.values(): + self.logger.debug(f"Copying {project.project} for {project.target}") self.copy_single_project(project) self.logger.info( @@ -137,58 +151,132 @@ def copy(self) -> int: ) if self.args.version: - bundle_dir_name = f"{self.target}-update-{self.args.suffix}"[ - : self.DIST_FOLDER_MAX_NAME_LENGTH - ] - bundle_dir = join(self.output_dir_path, bundle_dir_name) - bundle_args = [ - "generate", - "-d", - bundle_dir, - "-v", - self.args.version, - "-t", - self.target, - "--dfu", - self.get_dist_file_path( - self.get_project_file_name(self.projects["firmware"], "dfu") - ), - "--stage", - self.get_dist_file_path( - self.get_project_file_name(self.projects["updater"], "bin") + if bundle_result := self.bundle_update_package(): + return bundle_result + + required_components = ("firmware.elf", "full.bin", "update.dir") + if all( + map( + lambda c: c in self._dist_components, + required_components, + ) + ): + self.bundle_sdk() + + return 0 + + def bundle_sdk(self): + self.logger.info("Bundling SDK") + components_paths = dict() + + sdk_components_keys = ( + "full.bin", + "firmware.elf", + "update.dir", + "sdk_headers.dir", + "lib.dir", + "debug.dir", + "scripts.dir", + ) + + with zipfile.ZipFile( + self.get_dist_path(self.get_dist_file_name("sdk", "zip")), + "w", + zipfile.ZIP_DEFLATED, + ) as zf: + for component_key in sdk_components_keys: + component_path = self._dist_components.get(component_key) + components_paths[component_key] = basename(component_path) + + if component_key.endswith(".dir"): + for root, dirnames, files in walk(component_path): + if "__pycache__" in dirnames: + dirnames.remove("__pycache__") + for file in files: + zf.write( + join(root, file), + join( + components_paths[component_key], + relpath( + join(root, file), + component_path, + ), + ), + ) + else: + zf.write(component_path, basename(component_path)) + + zf.writestr( + "components.json", + json.dumps( + { + "meta": { + "hw_target": self.target, + "flavor": self.flavor, + "version": self.args.version, + }, + "components": components_paths, + } ), - ] - if self.args.resources: - bundle_args.extend( - ( - "-r", - self.args.resources, - ) - ) - bundle_args.extend(self.other_args) + ) - if (bundle_result := UpdateMain(no_exit=True)(bundle_args)) == 0: - self.logger.info( - fg.boldgreen( - f"Use this directory to self-update your Flipper:\n\t{bundle_dir}" - ) + def bundle_update_package(self): + self.logger.debug( + f"Generating update bundle with version {self.args.version} for {self.target}" + ) + bundle_dir_name = f"{self.target}-update-{self.args.suffix}"[ + : self.DIST_FOLDER_MAX_NAME_LENGTH + ] + bundle_dir = self.get_dist_path(bundle_dir_name) + bundle_args = [ + "generate", + "-d", + bundle_dir, + "-v", + self.args.version, + "-t", + self.target, + "--dfu", + self.get_dist_path( + self.get_project_file_name(self.projects["firmware"], "dfu") + ), + "--stage", + self.get_dist_path( + self.get_project_file_name(self.projects["updater"], "bin") + ), + ] + if self.args.resources: + bundle_args.extend( + ( + "-r", + self.args.resources, ) + ) + bundle_args.extend(self.other_args) - # Create tgz archive - with tarfile.open( - join( - self.output_dir_path, - f"{self.DIST_FILE_PREFIX}{bundle_dir_name}.tgz", - ), - "w:gz", - compresslevel=9, - format=tarfile.USTAR_FORMAT, - ) as tar: - tar.add(bundle_dir, arcname=bundle_dir_name) - - return bundle_result + if (bundle_result := UpdateMain(no_exit=True)(bundle_args)) == 0: + self.note_dist_component("update", "dir", bundle_dir) + self.logger.info( + fg.boldgreen( + f"Use this directory to self-update your Flipper:\n\t{bundle_dir}" + ) + ) - return 0 + # Create tgz archive + with tarfile.open( + join( + self.output_dir_path, + bundle_tgz := f"{self.DIST_FILE_PREFIX}{bundle_dir_name}.tgz", + ), + "w:gz", + compresslevel=9, + format=tarfile.USTAR_FORMAT, + ) as tar: + self.note_dist_component( + "update", "tgz", self.get_dist_path(bundle_tgz) + ) + tar.add(bundle_dir, arcname=bundle_dir_name) + return bundle_result if __name__ == "__main__": diff --git a/scripts/serial_cli.py b/scripts/serial_cli.py index 441bc7cc8e1..390b1f2638c 100644 --- a/scripts/serial_cli.py +++ b/scripts/serial_cli.py @@ -8,7 +8,7 @@ def main(): logger = logging.getLogger() if not (port := resolve_port(logger, "auto")): - logger.error("Is Flipper connected over USB and isn't in DFU mode?") + logger.error("Is Flipper connected over USB and is it not in DFU mode?") return 1 subprocess.call( [ diff --git a/scripts/toolchain/fbtenv.cmd b/scripts/toolchain/fbtenv.cmd index 8587f6d0e0a..9d45b7e9d7e 100644 --- a/scripts/toolchain/fbtenv.cmd +++ b/scripts/toolchain/fbtenv.cmd @@ -15,10 +15,12 @@ if not ["%FBT_NOENV%"] == [""] ( set "FLIPPER_TOOLCHAIN_VERSION=21" -if ["%FBT_TOOLCHAIN_ROOT%"] == [""] ( - set "FBT_TOOLCHAIN_ROOT=%FBT_ROOT%\toolchain\x86_64-windows" +if ["%FBT_TOOLCHAIN_PATH%"] == [""] ( + set "FBT_TOOLCHAIN_PATH=%FBT_ROOT%" ) +set "FBT_TOOLCHAIN_ROOT=%FBT_TOOLCHAIN_PATH%\toolchain\x86_64-windows" + set "FBT_TOOLCHAIN_VERSION_FILE=%FBT_TOOLCHAIN_ROOT%\VERSION" if not exist "%FBT_TOOLCHAIN_ROOT%" ( diff --git a/scripts/toolchain/fbtenv.sh b/scripts/toolchain/fbtenv.sh index 8f05c23ca82..57a50281edb 100755 --- a/scripts/toolchain/fbtenv.sh +++ b/scripts/toolchain/fbtenv.sh @@ -4,9 +4,15 @@ # public variables DEFAULT_SCRIPT_PATH="$(pwd -P)"; -SCRIPT_PATH="${SCRIPT_PATH:-$DEFAULT_SCRIPT_PATH}"; FBT_TOOLCHAIN_VERSION="${FBT_TOOLCHAIN_VERSION:-"21"}"; -FBT_TOOLCHAIN_PATH="${FBT_TOOLCHAIN_PATH:-$SCRIPT_PATH}"; + +if [ -z ${FBT_TOOLCHAIN_PATH+x} ] ; then + FBT_TOOLCHAIN_PATH_WAS_SET=0; +else + FBT_TOOLCHAIN_PATH_WAS_SET=1; +fi + +FBT_TOOLCHAIN_PATH="${FBT_TOOLCHAIN_PATH:-$DEFAULT_SCRIPT_PATH}"; FBT_VERBOSE="${FBT_VERBOSE:-""}"; fbtenv_show_usage() @@ -60,7 +66,6 @@ fbtenv_restore_env() unset SAVED_PYTHONPATH; unset SAVED_PYTHONHOME; - unset SCRIPT_PATH; unset FBT_TOOLCHAIN_VERSION; unset FBT_TOOLCHAIN_PATH; } @@ -104,13 +109,14 @@ fbtenv_set_shell_prompt() return 0; # all other shells } -fbtenv_check_script_path() +fbtenv_check_env_vars() { - if [ ! -x "$SCRIPT_PATH/fbt" ] && [ ! -x "$SCRIPT_PATH/ufbt" ] ; then - echo "Please source this script from [u]fbt root directory, or specify 'SCRIPT_PATH' variable manually"; + # Return error if FBT_TOOLCHAIN_PATH is not set before script is sourced or if fbt executable is not in DEFAULT_SCRIPT_PATH + if [ "$FBT_TOOLCHAIN_PATH_WAS_SET" -eq 0 ] && [ ! -x "$DEFAULT_SCRIPT_PATH/fbt" ] && [ ! -x "$DEFAULT_SCRIPT_PATH/ufbt" ] ; then + echo "Please source this script from [u]fbt root directory, or specify 'FBT_TOOLCHAIN_PATH' variable manually"; echo "Example:"; - printf "\tSCRIPT_PATH=lang/c/flipperzero-firmware source lang/c/flipperzero-firmware/scripts/fbtenv.sh\n"; - echo "If current directory is right, type 'unset SCRIPT_PATH' and try again" + printf "\tFBT_TOOLCHAIN_PATH=lang/c/flipperzero-firmware source lang/c/flipperzero-firmware/scripts/fbtenv.sh\n"; + echo "If current directory is right, type 'unset FBT_TOOLCHAIN_PATH' and try again" return 1; fi return 0; @@ -207,7 +213,7 @@ fbtenv_show_unpack_percentage() fbtenv_unpack_toolchain() { - echo "Unpacking toolchain:"; + echo "Unpacking toolchain to '$FBT_TOOLCHAIN_PATH/toolchain':"; tar -xvf "$FBT_TOOLCHAIN_PATH/toolchain/$TOOLCHAIN_TAR" -C "$FBT_TOOLCHAIN_PATH/toolchain" 2>&1 | fbtenv_show_unpack_percentage; mkdir -p "$FBT_TOOLCHAIN_PATH/toolchain" || return 1; mv "$FBT_TOOLCHAIN_PATH/toolchain/$TOOLCHAIN_DIR" "$TOOLCHAIN_ARCH_DIR" || return 1; @@ -215,7 +221,7 @@ fbtenv_unpack_toolchain() return 0; } -fbtenv_clearing() +fbtenv_cleanup() { printf "Cleaning up.."; if [ -n "${FBT_TOOLCHAIN_PATH:-""}" ]; then @@ -270,14 +276,14 @@ fbtenv_download_toolchain() fbtenv_check_tar || return 1; TOOLCHAIN_TAR="$(basename "$TOOLCHAIN_URL")"; TOOLCHAIN_DIR="$(echo "$TOOLCHAIN_TAR" | sed "s/-$FBT_TOOLCHAIN_VERSION.tar.gz//g")"; - trap fbtenv_clearing 2; # trap will be restored in fbtenv_clearing + trap fbtenv_cleanup 2; # trap will be restored in fbtenv_cleanup if ! fbtenv_check_downloaded_toolchain; then fbtenv_curl_wget_check || return 1; fbtenv_download_toolchain_tar || return 1; fi fbtenv_remove_old_tooclhain; fbtenv_unpack_toolchain || return 1; - fbtenv_clearing; + fbtenv_cleanup; return 0; } @@ -296,8 +302,8 @@ fbtenv_main() fbtenv_restore_env; return 0; fi - fbtenv_check_if_sourced_multiple_times; # many source it's just a warning - fbtenv_check_script_path || return 1; + fbtenv_check_if_sourced_multiple_times; + fbtenv_check_env_vars || return 1; fbtenv_check_download_toolchain || return 1; fbtenv_set_shell_prompt; fbtenv_print_version; diff --git a/scripts/ufbt/SConstruct b/scripts/ufbt/SConstruct new file mode 100644 index 00000000000..a82189c147e --- /dev/null +++ b/scripts/ufbt/SConstruct @@ -0,0 +1,393 @@ +from SCons.Platform import TempFileMunge +from SCons.Node import FS +from SCons.Errors import UserError + +import os +import multiprocessing +import pathlib + +SetOption("num_jobs", multiprocessing.cpu_count()) +SetOption("max_drift", 1) +# SetOption("silent", False) + +ufbt_state_dir = Dir(os.environ.get("UFBT_STATE_DIR", "#.ufbt")) +ufbt_script_dir = Dir(os.environ.get("UFBT_SCRIPT_DIR")) + +ufbt_current_sdk_dir = ufbt_state_dir.Dir("current") + +SConsignFile(ufbt_state_dir.File(".sconsign.dblite").abspath) + +ufbt_variables = SConscript("commandline.scons") + +forward_os_env = { + # Import PATH from OS env - scons doesn't do that by default + "PATH": os.environ["PATH"], +} + +# Proxying environment to child processes & scripts +variables_to_forward = [ + # CI/CD variables + "WORKFLOW_BRANCH_OR_TAG", + "DIST_SUFFIX", + # Python & other tools + "HOME", + "APPDATA", + "PYTHONHOME", + "PYTHONNOUSERSITE", + "TMP", + "TEMP", + # Colors for tools + "TERM", +] + +if proxy_env := GetOption("proxy_env"): + variables_to_forward.extend(proxy_env.split(",")) + +for env_value_name in variables_to_forward: + if environ_value := os.environ.get(env_value_name, None): + forward_os_env[env_value_name] = environ_value + +# Core environment init - loads SDK state, sets up paths, etc. +core_env = Environment( + variables=ufbt_variables, + ENV=forward_os_env, + UFBT_STATE_DIR=ufbt_state_dir, + UFBT_CURRENT_SDK_DIR=ufbt_current_sdk_dir, + UFBT_SCRIPT_DIR=ufbt_script_dir, + toolpath=[ufbt_current_sdk_dir.Dir("scripts/ufbt/site_tools")], + tools=[ + "ufbt_state", + ("ufbt_help", {"vars": ufbt_variables}), + ], +) + +if "update" in BUILD_TARGETS: + SConscript( + "update.scons", + exports={"core_env": core_env}, + ) + +if "purge" in BUILD_TARGETS: + core_env.Execute(Delete(ufbt_state_dir)) + print("uFBT state purged") + Exit(0) + +# Now we can import stuff bundled with SDK - it was added to sys.path by ufbt_state + +from fbt.util import ( + tempfile_arg_esc_func, + single_quote, + extract_abs_dir, + extract_abs_dir_path, + wrap_tempfile, + path_as_posix, +) +from fbt.appmanifest import FlipperAppType +from fbt.sdk.cache import SdkCache + +# Base environment with all tools loaded from SDK +env = core_env.Clone( + toolpath=[core_env["FBT_SCRIPT_DIR"].Dir("fbt_tools")], + tools=[ + "fbt_tweaks", + ( + "crosscc", + { + "toolchain_prefix": "arm-none-eabi-", + "versions": (" 10.3",), + }, + ), + "fwbin", + "python3", + "sconsrecursiveglob", + "sconsmodular", + "ccache", + "fbt_apps", + "fbt_extapps", + "fbt_assets", + ("compilation_db", {"COMPILATIONDB_COMSTR": "\tCDB\t${TARGET}"}), + ], + FBT_FAP_DEBUG_ELF_ROOT=ufbt_state_dir.Dir("build"), + TEMPFILE=TempFileMunge, + MAXLINELENGTH=2048, + PROGSUFFIX=".elf", + TEMPFILEARGESCFUNC=tempfile_arg_esc_func, + SINGLEQUOTEFUNC=single_quote, + ABSPATHGETTERFUNC=extract_abs_dir_path, + APPS=[], + UFBT_API_VERSION=SdkCache( + core_env.subst("$SDK_DEFINITION"), load_version_only=True + ).version, + APPCHECK_COMSTR="\tAPPCHK\t${SOURCE}\n\t\tTarget: ${TARGET_HW}, API: ${UFBT_API_VERSION}", +) + +wrap_tempfile(env, "LINKCOM") +wrap_tempfile(env, "ARCOM") + +# print(env.Dump()) + +# Dist env + +dist_env = env.Clone( + tools=[ + "fbt_dist", + "fbt_debugopts", + "openocd", + "blackmagic", + "jflash", + "textfile", + ], + ENV=os.environ, + OPENOCD_OPTS=[ + "-f", + "interface/stlink.cfg", + "-c", + "transport select hla_swd", + "-f", + "${FBT_DEBUG_DIR}/stm32wbx.cfg", + "-c", + "stm32wbx.cpu configure -rtos auto", + ], +) + +openocd_target = dist_env.OpenOCDFlash( + dist_env["UFBT_STATE_DIR"].File("flash"), + dist_env["FW_BIN"], + OPENOCD_COMMAND=[ + "-c", + "program ${SOURCE.posix} reset exit 0x08000000", + ], +) +dist_env.Alias("firmware_flash", openocd_target) +dist_env.Alias("flash", openocd_target) +if env["FORCE"]: + env.AlwaysBuild(openocd_target) + +firmware_debug = dist_env.PhonyTarget( + "debug", + "${GDBPYCOM}", + source=dist_env["FW_ELF"], + GDBOPTS="${GDBOPTS_BASE}", + GDBREMOTE="${OPENOCD_GDB_PIPE}", + FBT_FAP_DEBUG_ELF_ROOT=path_as_posix(dist_env.subst("$FBT_FAP_DEBUG_ELF_ROOT")), +) + +dist_env.PhonyTarget( + "blackmagic", + "${GDBPYCOM}", + source=dist_env["FW_ELF"], + GDBOPTS="${GDBOPTS_BASE} ${GDBOPTS_BLACKMAGIC}", + GDBREMOTE="${BLACKMAGIC_ADDR}", + FBT_FAP_DEBUG_ELF_ROOT=path_as_posix(dist_env.subst("$FBT_FAP_DEBUG_ELF_ROOT")), +) + +dist_env.PhonyTarget( + "flash_blackmagic", + "$GDB $GDBOPTS $SOURCES $GDBFLASH", + source=dist_env["FW_ELF"], + GDBOPTS="${GDBOPTS_BASE} ${GDBOPTS_BLACKMAGIC}", + GDBREMOTE="${BLACKMAGIC_ADDR}", + GDBFLASH=[ + "-ex", + "load", + "-ex", + "quit", + ], +) + +flash_usb_full = dist_env.UsbInstall( + dist_env["UFBT_STATE_DIR"].File("usbinstall"), + [], +) +dist_env.AlwaysBuild(flash_usb_full) +dist_env.Alias("flash_usb", flash_usb_full) +dist_env.Alias("flash_usb_full", flash_usb_full) + +# App build environment + +appenv = env.Clone( + CCCOM=env["CCCOM"].replace("$CFLAGS", "$CFLAGS_APP $CFLAGS"), + CXXCOM=env["CXXCOM"].replace("$CXXFLAGS", "$CXXFLAGS_APP $CXXFLAGS"), + LINKCOM=env["LINKCOM"].replace("$LINKFLAGS", "$LINKFLAGS_APP $LINKFLAGS"), + COMPILATIONDB_USE_ABSPATH=True, +) + + +original_app_dir = Dir(appenv.subst("$UFBT_APP_DIR")) +app_mount_point = Dir("#/app/") +app_mount_point.addRepository(original_app_dir) + +appenv.LoadAppManifest(app_mount_point) +appenv.PrepareApplicationsBuild() + +####################### + +apps_artifacts = appenv["EXT_APPS"] + +apps_to_build_as_faps = [ + FlipperAppType.PLUGIN, + FlipperAppType.EXTERNAL, +] + +known_extapps = [ + app + for apptype in apps_to_build_as_faps + for app in appenv["APPBUILD"].get_apps_of_type(apptype, True) +] +for app in known_extapps: + app_artifacts = appenv.BuildAppElf(app) + app_src_dir = extract_abs_dir(app_artifacts.app._appdir) + app_artifacts.installer = [ + appenv.Install(app_src_dir.Dir("dist"), app_artifacts.compact), + appenv.Install(app_src_dir.Dir("dist").Dir("debug"), app_artifacts.debug), + ] + +if appenv["FORCE"]: + appenv.AlwaysBuild([extapp.compact for extapp in apps_artifacts.values()]) + +# Final steps - target aliases + +install_and_check = [ + (extapp.installer, extapp.validator) for extapp in apps_artifacts.values() +] +Alias( + "faps", + install_and_check, +) +Default(install_and_check) + +# Compilation database + +fwcdb = appenv.CompilationDatabase( + original_app_dir.Dir(".vscode").File("compile_commands.json") +) + +AlwaysBuild(fwcdb) +Precious(fwcdb) +NoClean(fwcdb) +if len(apps_artifacts): + Default(fwcdb) + + +# launch handler +runnable_apps = appenv["APPBUILD"].get_apps_of_type(FlipperAppType.EXTERNAL, True) + +app_to_launch = None +if len(runnable_apps) == 1: + app_to_launch = runnable_apps[0].appid +elif len(runnable_apps) > 1: + # more than 1 app - try to find one with matching id + app_to_launch = appenv.subst("$APPID") + + +def ambiguous_app_call(**kw): + raise UserError( + f"More than one app is runnable: {', '.join(app.appid for app in runnable_apps)}. Please specify an app with APPID=..." + ) + + +if app_to_launch: + appenv.AddAppLaunchTarget(app_to_launch, "launch") +else: + dist_env.PhonyTarget("launch", Action(ambiguous_app_call, None)) + +# cli handler + +appenv.PhonyTarget( + "cli", + '${PYTHON3} "${FBT_SCRIPT_DIR}/serial_cli.py"', +) + +# Linter + +dist_env.PhonyTarget( + "lint", + "${PYTHON3} ${FBT_SCRIPT_DIR}/lint.py check ${LINT_SOURCES}", + source=original_app_dir.File(".clang-format"), + LINT_SOURCES=[original_app_dir], +) + +dist_env.PhonyTarget( + "format", + "${PYTHON3} ${FBT_SCRIPT_DIR}/lint.py format ${LINT_SOURCES}", + source=original_app_dir.File(".clang-format"), + LINT_SOURCES=[original_app_dir], +) + + +# Prepare vscode environment +def _path_as_posix(path): + return pathlib.Path(path).as_posix() + + +vscode_dist = [] +project_template_dir = dist_env["UFBT_SCRIPT_ROOT"].Dir("project_template") +for template_file in project_template_dir.Dir(".vscode").glob("*"): + vscode_dist.append( + dist_env.Substfile( + original_app_dir.Dir(".vscode").File(template_file.name), + template_file, + SUBST_DICT={ + "@UFBT_VSCODE_PATH_SEP@": os.path.pathsep, + "@UFBT_TOOLCHAIN_ARM_TOOLCHAIN_DIR@": pathlib.Path( + dist_env.WhereIs("arm-none-eabi-gcc") + ).parent.as_posix(), + "@UFBT_TOOLCHAIN_GCC@": _path_as_posix( + dist_env.WhereIs("arm-none-eabi-gcc") + ), + "@UFBT_TOOLCHAIN_GDB_PY@": _path_as_posix( + dist_env.WhereIs("arm-none-eabi-gdb-py") + ), + "@UFBT_TOOLCHAIN_OPENOCD@": _path_as_posix(dist_env.WhereIs("openocd")), + "@UFBT_APP_DIR@": _path_as_posix(original_app_dir.abspath), + "@UFBT_ROOT_DIR@": _path_as_posix(Dir("#").abspath), + "@UFBT_DEBUG_DIR@": dist_env["FBT_DEBUG_DIR"], + "@UFBT_DEBUG_ELF_DIR@": _path_as_posix( + dist_env["FBT_FAP_DEBUG_ELF_ROOT"].abspath + ), + "@UFBT_FIRMWARE_ELF@": _path_as_posix(dist_env["FW_ELF"].abspath), + }, + ) + ) + +for config_file in project_template_dir.glob(".*"): + if isinstance(config_file, FS.Dir): + continue + vscode_dist.append(dist_env.Install(original_app_dir, config_file)) + +dist_env.Precious(vscode_dist) +dist_env.NoClean(vscode_dist) +dist_env.Alias("vscode_dist", vscode_dist) + + +# Creating app from base template + +dist_env.SetDefault(FBT_APPID=appenv.subst("$APPID") or "template") +app_template_dist = [] +for template_file in project_template_dir.Dir("app_template").glob("*"): + dist_file_name = dist_env.subst(template_file.name) + if template_file.name.endswith(".png"): + app_template_dist.append( + dist_env.InstallAs(original_app_dir.File(dist_file_name), template_file) + ) + else: + app_template_dist.append( + dist_env.Substfile( + original_app_dir.File(dist_file_name), + template_file, + SUBST_DICT={ + "@FBT_APPID@": dist_env.subst("$FBT_APPID"), + }, + ) + ) + +AddPostAction( + app_template_dist[-1], + [ + Mkdir(original_app_dir.Dir("images")), + Touch(original_app_dir.Dir("images").File(".gitkeep")), + ], +) +dist_env.Precious(app_template_dist) +dist_env.NoClean(app_template_dist) +dist_env.Alias("create", app_template_dist) diff --git a/scripts/ufbt/commandline.scons b/scripts/ufbt/commandline.scons new file mode 100644 index 00000000000..9af5e9bce91 --- /dev/null +++ b/scripts/ufbt/commandline.scons @@ -0,0 +1,90 @@ +AddOption( + "--proxy-env", + action="store", + dest="proxy_env", + default="", + help="Comma-separated list of additional environment variables to pass to child SCons processes", +) + +AddOption( + "--channel", + action="store", + dest="sdk_channel", + choices=["dev", "rc", "release"], + default="", + help="Release channel to use for SDK", +) + +AddOption( + "--branch", + action="store", + dest="sdk_branch", + help="Custom main repo branch to use for SDK", +) + +AddOption( + "--hw-target", + action="store", + dest="sdk_target", + help="SDK Hardware target", +) + +vars = Variables("ufbt_options.py", ARGUMENTS) + +vars.AddVariables( + BoolVariable( + "VERBOSE", + help="Print full commands", + default=False, + ), + BoolVariable( + "FORCE", + help="Force target action (for supported targets)", + default=False, + ), + # These 2 are inherited from SDK + # BoolVariable( + # "DEBUG", + # help="Enable debug build", + # default=True, + # ), + # BoolVariable( + # "COMPACT", + # help="Optimize for size", + # default=False, + # ), + PathVariable( + "OTHER_ELF", + help="Path to prebuilt ELF file to debug", + validator=PathVariable.PathAccept, + default="", + ), + ( + "OPENOCD_OPTS", + "Options to pass to OpenOCD", + "", + ), + ( + "BLACKMAGIC", + "Blackmagic probe location", + "auto", + ), + ( + "OPENOCD_ADAPTER_SERIAL", + "OpenOCD adapter serial number", + "auto", + ), + ( + "APPID", + "Application id", + "", + ), + PathVariable( + "UFBT_APP_DIR", + help="Application dir to work with", + validator=PathVariable.PathIsDir, + default="", + ), +) + +Return("vars") diff --git a/scripts/ufbt/project_template/.clang-format b/scripts/ufbt/project_template/.clang-format new file mode 100644 index 00000000000..4b76f7fa43b --- /dev/null +++ b/scripts/ufbt/project_template/.clang-format @@ -0,0 +1,191 @@ +--- +Language: Cpp +AccessModifierOffset: -4 +AlignAfterOpenBracket: AlwaysBreak +AlignArrayOfStructures: None +AlignConsecutiveMacros: None +AlignConsecutiveAssignments: None +AlignConsecutiveBitFields: None +AlignConsecutiveDeclarations: None +AlignEscapedNewlines: Left +AlignOperands: Align +AlignTrailingComments: false +AllowAllArgumentsOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortEnumsOnASingleLine: true +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortLambdasOnASingleLine: All +AllowShortIfStatementsOnASingleLine: WithoutElse +AllowShortLoopsOnASingleLine: true +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: Yes +AttributeMacros: + - __capability +BinPackArguments: false +BinPackParameters: false +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: Never + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + BeforeLambdaBody: false + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeConceptDeclarations: true +BreakBeforeBraces: Attach +BreakBeforeInheritanceComma: false +BreakInheritanceList: BeforeColon +BreakBeforeTernaryOperators: false +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeComma +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: false +ColumnLimit: 99 +CommentPragmas: '^ IWYU pragma:' +QualifierAlignment: Leave +CompactNamespaces: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DeriveLineEnding: true +DerivePointerAlignment: false +DisableFormat: false +EmptyLineAfterAccessModifier: Never +EmptyLineBeforeAccessModifier: LogicalBlock +ExperimentalAutoDetectBinPacking: false +PackConstructorInitializers: BinPack +BasedOnStyle: '' +ConstructorInitializerAllOnOneLineOrOnePerLine: false +AllowAllConstructorInitializersOnNextLine: true +FixNamespaceComments: false +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IfMacros: + - KJ_IF_MAYBE +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '.*' + Priority: 1 + SortPriority: 0 + CaseSensitive: false + - Regex: '^(<|"(gtest|gmock|isl|json)/)' + Priority: 3 + SortPriority: 0 + CaseSensitive: false + - Regex: '.*' + Priority: 1 + SortPriority: 0 + CaseSensitive: false +IncludeIsMainRegex: '(Test)?$' +IncludeIsMainSourceRegex: '' +IndentAccessModifiers: false +IndentCaseLabels: false +IndentCaseBlocks: false +IndentGotoLabels: true +IndentPPDirectives: None +IndentExternBlock: AfterExternBlock +IndentRequires: false +IndentWidth: 4 +IndentWrappedFunctionNames: true +InsertTrailingCommas: None +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +LambdaBodyIndentation: Signature +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 4 +ObjCBreakBeforeNestedBlockParam: true +ObjCSpaceAfterProperty: true +ObjCSpaceBeforeProtocolList: true +PenaltyBreakAssignment: 10 +PenaltyBreakBeforeFirstCallParameter: 30 +PenaltyBreakComment: 10 +PenaltyBreakFirstLessLess: 0 +PenaltyBreakOpenParenthesis: 0 +PenaltyBreakString: 10 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 100 +PenaltyReturnTypeOnItsOwnLine: 60 +PenaltyIndentedWhitespace: 0 +PointerAlignment: Left +PPIndentWidth: -1 +ReferenceAlignment: Pointer +ReflowComments: false +RemoveBracesLLVM: false +SeparateDefinitionBlocks: Leave +ShortNamespaceLines: 1 +SortIncludes: Never +SortJavaStaticImport: Before +SortUsingDeclarations: false +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCaseColon: false +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: Never +SpaceBeforeParensOptions: + AfterControlStatements: false + AfterForeachMacros: false + AfterFunctionDefinitionName: false + AfterFunctionDeclarationName: false + AfterIfMacros: false + AfterOverloadedOperator: false + BeforeNonEmptyParentheses: false +SpaceAroundPointerQualifiers: Default +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: Never +SpacesInConditionalStatement: false +SpacesInContainerLiterals: false +SpacesInCStyleCastParentheses: false +SpacesInLineCommentPrefix: + Minimum: 1 + Maximum: -1 +SpacesInParentheses: false +SpacesInSquareBrackets: false +SpaceBeforeSquareBrackets: false +BitFieldColonSpacing: Both +Standard: c++03 +StatementAttributeLikeMacros: + - Q_EMIT +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 4 +UseCRLF: false +UseTab: Never +WhitespaceSensitiveMacros: + - STRINGIZE + - PP_STRINGIZE + - BOOST_PP_STRINGIZE + - NS_SWIFT_NAME + - CF_SWIFT_NAME +... + diff --git a/scripts/ufbt/project_template/.editorconfig b/scripts/ufbt/project_template/.editorconfig new file mode 100644 index 00000000000..a31ef8e753a --- /dev/null +++ b/scripts/ufbt/project_template/.editorconfig @@ -0,0 +1,13 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 + +[*.{cpp,h,c,py,sh}] +indent_style = space +indent_size = 4 + +[{Makefile,*.mk}] +indent_size = tab diff --git a/scripts/ufbt/project_template/.gitignore b/scripts/ufbt/project_template/.gitignore new file mode 100644 index 00000000000..e2a15a10a84 --- /dev/null +++ b/scripts/ufbt/project_template/.gitignore @@ -0,0 +1,4 @@ +dist/* +.vscode +.clang-format +.editorconfig \ No newline at end of file diff --git a/scripts/ufbt/project_template/.vscode/c_cpp_properties.json b/scripts/ufbt/project_template/.vscode/c_cpp_properties.json new file mode 100644 index 00000000000..922a9091b60 --- /dev/null +++ b/scripts/ufbt/project_template/.vscode/c_cpp_properties.json @@ -0,0 +1,14 @@ +{ + "configurations": [ + { + "name": "main", + "compilerPath": "@UFBT_TOOLCHAIN_GCC@", + "intelliSenseMode": "gcc-arm", + "compileCommands": "${workspaceFolder}/.vscode/compile_commands.json", + "configurationProvider": "ms-vscode.cpptools", + "cStandard": "gnu17", + "cppStandard": "c++17" + }, + ], + "version": 4 +} \ No newline at end of file diff --git a/scripts/ufbt/project_template/.vscode/extensions.json b/scripts/ufbt/project_template/.vscode/extensions.json new file mode 100644 index 00000000000..35f90700a3f --- /dev/null +++ b/scripts/ufbt/project_template/.vscode/extensions.json @@ -0,0 +1,18 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. + // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp + // List of extensions which should be recommended for users of this workspace. + "recommendations": [ + "ms-python.black-formatter", + "ms-vscode.cpptools", + "amiralizadeh9480.cpp-helper", + "marus25.cortex-debug", + "zxh404.vscode-proto3", + "augustocdias.tasks-shell-input" + ], + // List of extensions recommended by VS Code that should not be recommended for users of this workspace. + "unwantedRecommendations": [ + "twxs.cmake", + "ms-vscode.cmake-tools" + ] +} \ No newline at end of file diff --git a/scripts/ufbt/project_template/.vscode/launch.json b/scripts/ufbt/project_template/.vscode/launch.json new file mode 100644 index 00000000000..d9c98dcc13e --- /dev/null +++ b/scripts/ufbt/project_template/.vscode/launch.json @@ -0,0 +1,98 @@ +{ + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "inputs": [ + // { + // "id": "BLACKMAGIC", + // "type": "command", + // "command": "shellCommand.execute", + // "args": { + // "useSingleResult": true, + // "env": { + // "PATH": "${workspaceFolder};${env:PATH}" + // }, + // "command": "./fbt get_blackmagic", + // "description": "Get Blackmagic device", + // } + // }, + ], + "configurations": [ + { + "name": "Attach FW (ST-Link)", + "cwd": "${workspaceFolder}", + "executable": "@UFBT_FIRMWARE_ELF@", + "request": "attach", + "type": "cortex-debug", + "servertype": "openocd", + "device": "stlink", + "svdFile": "@UFBT_DEBUG_DIR@/STM32WB55_CM4.svd", + "rtos": "FreeRTOS", + "configFiles": [ + "interface/stlink.cfg", + "@UFBT_DEBUG_DIR@/stm32wbx.cfg" + ], + "postAttachCommands": [ + "source @UFBT_DEBUG_DIR@/flipperapps.py", + "fap-set-debug-elf-root @UFBT_DEBUG_ELF_DIR@" + ], + // "showDevDebugOutput": "raw", + }, + { + "name": "Attach FW (DAP)", + "cwd": "${workspaceFolder}", + "executable": "@UFBT_FIRMWARE_ELF@", + "request": "attach", + "type": "cortex-debug", + "servertype": "openocd", + "device": "cmsis-dap", + "svdFile": "@UFBT_DEBUG_DIR@/STM32WB55_CM4.svd", + "rtos": "FreeRTOS", + "configFiles": [ + "interface/cmsis-dap.cfg", + "@UFBT_DEBUG_DIR@/stm32wbx.cfg" + ], + "postAttachCommands": [ + "source @UFBT_DEBUG_DIR@/flipperapps.py", + "fap-set-debug-elf-root @UFBT_DEBUG_ELF_DIR@" + ], + // "showDevDebugOutput": "raw", + }, + // { + // "name": "Attach FW (blackmagic)", + // "cwd": "${workspaceFolder}", + // "executable": "@UFBT_FIRMWARE_ELF@", + // "request": "attach", + // "type": "cortex-debug", + // "servertype": "external", + // "gdbTarget": "${input:BLACKMAGIC}", + // "svdFile": "@UFBT_DEBUG_DIR@/STM32WB55_CM4.svd", + // "rtos": "FreeRTOS", + // "postAttachCommands": [ + // "monitor swdp_scan", + // "attach 1", + // "set confirm off", + // "set mem inaccessible-by-default off", + // "source @UFBT_DEBUG_DIR@/flipperapps.py", + // "fap-set-debug-elf-root @UFBT_DEBUG_ELF_DIR@" + // ] + // // "showDevDebugOutput": "raw", + // }, + { + "name": "Attach FW (JLink)", + "cwd": "${workspaceFolder}", + "executable": "@UFBT_FIRMWARE_ELF@", + "request": "attach", + "type": "cortex-debug", + "servertype": "jlink", + "interface": "swd", + "device": "STM32WB55RG", + "svdFile": "@UFBT_DEBUG_DIR@/STM32WB55_CM4.svd", + "rtos": "FreeRTOS", + "postAttachCommands": [ + "source @UFBT_DEBUG_DIR@/flipperapps.py", + "fap-set-debug-elf-root @UFBT_DEBUG_ELF_DIR@" + ] + // "showDevDebugOutput": "raw", + }, + ] +} \ No newline at end of file diff --git a/scripts/ufbt/project_template/.vscode/settings.json b/scripts/ufbt/project_template/.vscode/settings.json new file mode 100644 index 00000000000..33cd3f035e1 --- /dev/null +++ b/scripts/ufbt/project_template/.vscode/settings.json @@ -0,0 +1,20 @@ +{ + "cortex-debug.enableTelemetry": false, + "cortex-debug.variableUseNaturalFormat": false, + "cortex-debug.showRTOS": true, + "cortex-debug.armToolchainPath": "@UFBT_TOOLCHAIN_ARM_TOOLCHAIN_DIR@", + "cortex-debug.openocdPath": "@UFBT_TOOLCHAIN_OPENOCD@", + "cortex-debug.gdbPath": "@UFBT_TOOLCHAIN_GDB_PY@", + "editor.formatOnSave": true, + "files.associations": { + "*.scons": "python", + "SConscript": "python", + "SConstruct": "python", + "*.fam": "python" + }, + "cortex-debug.registerUseNaturalFormat": false, + "python.analysis.typeCheckingMode": "off", + "[python]": { + "editor.defaultFormatter": "ms-python.black-formatter" + } +} \ No newline at end of file diff --git a/scripts/ufbt/project_template/.vscode/tasks.json b/scripts/ufbt/project_template/.vscode/tasks.json new file mode 100644 index 00000000000..6343bba7bea --- /dev/null +++ b/scripts/ufbt/project_template/.vscode/tasks.json @@ -0,0 +1,54 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "options": { + "env": { + "PATH": "${workspaceFolder}@UFBT_VSCODE_PATH_SEP@${env:PATH}@UFBT_VSCODE_PATH_SEP@@UFBT_ROOT_DIR@" + } + }, + "tasks": [ + { + "label": "Launch App on Flipper", + "group": "build", + "type": "shell", + "command": "ufbt launch" + }, + { + "label": "Build", + "group": "build", + "type": "shell", + "command": "ufbt" + }, + { + "label": "Flash FW (ST-Link)", + "group": "build", + "type": "shell", + "command": "ufbt FORCE=1 flash" + }, + // { + // "label": "[NOTIMPL] Flash FW (blackmagic)", + // "group": "build", + // "type": "shell", + // "command": "ufbt flash_blackmagic" + // }, + // { + // "label": "[NOTIMPL] Flash FW (JLink)", + // "group": "build", + // "type": "shell", + // "command": "ufbt FORCE=1 jflash" + // }, + { + "label": "Flash FW (USB, with resources)", + "group": "build", + "type": "shell", + "command": "ufbt FORCE=1 flash_usb" + }, + { + "label": "Update uFBT SDK", + "group": "build", + "type": "shell", + "command": "ufbt update" + } + ] +} \ No newline at end of file diff --git a/scripts/ufbt/project_template/app_template/${FBT_APPID}.c b/scripts/ufbt/project_template/app_template/${FBT_APPID}.c new file mode 100644 index 00000000000..9b8113cb502 --- /dev/null +++ b/scripts/ufbt/project_template/app_template/${FBT_APPID}.c @@ -0,0 +1,12 @@ +#include + +/* generated by fbt from .png files in images folder */ +#include <@FBT_APPID@_icons.h> + +int32_t @FBT_APPID@_app(void* p) { + UNUSED(p); + FURI_LOG_I("TEST", "Hello world"); + FURI_LOG_I("TEST", "I'm @FBT_APPID@!"); + + return 0; +} diff --git a/scripts/ufbt/project_template/app_template/${FBT_APPID}.png b/scripts/ufbt/project_template/app_template/${FBT_APPID}.png new file mode 100644 index 0000000000000000000000000000000000000000..59e6c185f60aa7d0af126f96c9c9ff2ebf7d8ced GIT binary patch literal 220 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V6Od#Ih?TfNTy1#`a7G79fieh^2rSsBHlgT!d=@GlC70l-MxqJdpDDba4!kkSy&Ds2Gx}>HG7H0_ ovK6i!oilCimkTR6j_o_`>A%fhS~xer_cF)|Pgg&ebxsLQ00l-kGXMYp literal 0 HcmV?d00001 diff --git a/scripts/ufbt/project_template/app_template/application.fam b/scripts/ufbt/project_template/app_template/application.fam new file mode 100644 index 00000000000..31fadb207a8 --- /dev/null +++ b/scripts/ufbt/project_template/app_template/application.fam @@ -0,0 +1,17 @@ +# For details & more options, see documentation/AppManifests.md in firmware repo + +App( + appid="@FBT_APPID@", # Must be unique + name="App @FBT_APPID@", # Displayed in menus + apptype=FlipperAppType.EXTERNAL, + entry_point="@FBT_APPID@_app", + stack_size=2 * 1024, + fap_category="Misc", + # Optional values + # fap_version=(0, 1), # (major, minor) + fap_icon="@FBT_APPID@.png", # 10x10 1-bit PNG + # fap_description="A simple app", + # fap_author="J. Doe", + # fap_weburl="https://github.com/user/@FBT_APPID@", + fap_icon_assets="images", # Image assets to compile for this application +) diff --git a/scripts/ufbt/site_init.py b/scripts/ufbt/site_init.py new file mode 100644 index 00000000000..557085ede85 --- /dev/null +++ b/scripts/ufbt/site_init.py @@ -0,0 +1,36 @@ +from SCons.Script import GetBuildFailures +import SCons.Errors + +import atexit +from ansi.color import fg, fx + + +def bf_to_str(bf): + """Convert an element of GetBuildFailures() to a string + in a useful way.""" + + if bf is None: # unknown targets product None in list + return "(unknown tgt)" + elif isinstance(bf, SCons.Errors.StopError): + return fg.yellow(str(bf)) + elif bf.node: + return fg.yellow(str(bf.node)) + ": " + bf.errstr + elif bf.filename: + return fg.yellow(bf.filename) + ": " + bf.errstr + return fg.yellow("unknown failure: ") + bf.errstr + + +def display_build_status(): + """Display the build status. Called by atexit. + Here you could do all kinds of complicated things.""" + bf = GetBuildFailures() + if bf: + # bf is normally a list of build failures; if an element is None, + # it's because of a target that scons doesn't know anything about. + failures_message = "\n".join([bf_to_str(x) for x in bf if x is not None]) + print() + print(fg.brightred(fx.bold("*" * 10 + " FBT ERRORS " + "*" * 10))) + print(failures_message) + + +atexit.register(display_build_status) diff --git a/scripts/ufbt/site_tools/ufbt_help.py b/scripts/ufbt/site_tools/ufbt_help.py new file mode 100644 index 00000000000..da6ff6e51f4 --- /dev/null +++ b/scripts/ufbt/site_tools/ufbt_help.py @@ -0,0 +1,53 @@ +targets_help = """Configuration variables: +""" + +tail_help = """ + +TASKS: + (* - not supported yet) + + launch: + Upload and start application over USB + vscode_dist: + Configure application in current directory for development in VSCode. + create: + Copy application template to current directory. Set APPID=myapp to create an app with id 'myapp'. + +Building: + faps: + Build all FAP apps + fap_{APPID}, launch APPSRC={APPID}: + Build FAP app with appid={APPID}; upload & start it over USB + +Flashing & debugging: + flash, flash_blackmagic, *jflash: + Flash firmware to target using debug probe + flash_usb, flash_usb_full: + Install firmware using self-update package + debug, debug_other, blackmagic: + Start GDB + +Other: + cli: + Open a Flipper CLI session over USB + lint: + run linter for C code + format: + reformat C code + +How to create a new application: + 1. Create a new directory for your application and cd into it. + 2. Run `ufbt vscode_dist create APPID=myapp` + 3. In VSCode, open the folder and start editing. + 4. Run `ufbt launch` to build and upload your application. +""" + + +def generate(env, **kw): + vars = kw["vars"] + basic_help = vars.GenerateHelpText(env) + env.Help(targets_help + basic_help + tail_help) + + +def exists(env): + return True diff --git a/scripts/ufbt/site_tools/ufbt_state.py b/scripts/ufbt/site_tools/ufbt_state.py new file mode 100644 index 00000000000..6ba8c69628b --- /dev/null +++ b/scripts/ufbt/site_tools/ufbt_state.py @@ -0,0 +1,117 @@ +from SCons.Errors import StopError +from SCons.Warnings import warn, WarningOnByDefault + +import json +import os +import sys +import pathlib +from functools import reduce + + +def _load_sdk_data(sdk_root): + split_vars = { + "cc_args", + "cpp_args", + "linker_args", + "linker_libs", + } + subst_vars = split_vars | { + "sdk_symbols", + } + sdk_data = {} + with open(os.path.join(sdk_root, "sdk.opts")) as f: + sdk_json_data = json.load(f) + replacements = { + sdk_json_data["app_ep_subst"]: "${APP_ENTRY}", + sdk_json_data["sdk_path_subst"]: sdk_root.replace("\\", "/"), + sdk_json_data["map_file_subst"]: "${TARGET}", + } + + def do_value_substs(src_value): + if isinstance(src_value, str): + return reduce( + lambda acc, kv: acc.replace(*kv), replacements.items(), src_value + ) + elif isinstance(src_value, list): + return [do_value_substs(v) for v in src_value] + else: + return src_value + + for key, value in sdk_json_data.items(): + if key in split_vars: + value = value.split() + if key in subst_vars: + value = do_value_substs(value) + sdk_data[key] = value + + return sdk_data + + +def _load_state_file(state_dir_node, filename: str) -> dict: + state_path = os.path.join(state_dir_node.abspath, filename) + if not os.path.exists(state_path): + raise StopError(f"State file {state_path} not found") + + with open(state_path, "r") as f: + return json.load(f) + + +def generate(env, **kw): + sdk_current_sdk_dir_node = env["UFBT_CURRENT_SDK_DIR"] + + sdk_components_filename = kw.get("SDK_COMPONENTS", "components.json") + ufbt_state_filename = kw.get("UFBT_STATE", "ufbt_state.json") + + sdk_state = _load_state_file(sdk_current_sdk_dir_node, sdk_components_filename) + ufbt_state = _load_state_file(sdk_current_sdk_dir_node, ufbt_state_filename) + + if not (sdk_components := sdk_state.get("components", {})): + raise StopError("SDK state file doesn't contain components data") + + sdk_data = _load_sdk_data( + sdk_current_sdk_dir_node.Dir(sdk_components["sdk_headers.dir"]).abspath + ) + + if not sdk_state["meta"]["hw_target"].endswith(sdk_data["hardware"]): + raise StopError("SDK state file doesn't match hardware target") + + if sdk_state["meta"]["version"] != ufbt_state["version"]: + warn( + WarningOnByDefault, + f"Version mismatch: SDK state vs uFBT: {sdk_state['meta']['version']} vs {ufbt_state['version']}", + ) + + scripts_dir = sdk_current_sdk_dir_node.Dir(sdk_components["scripts.dir"]) + env.SetDefault( + # Paths + SDK_DEFINITION=env.File(sdk_data["sdk_symbols"]), + FBT_DEBUG_DIR=pathlib.Path( + sdk_current_sdk_dir_node.Dir(sdk_components["debug.dir"]).abspath + ).as_posix(), + FBT_SCRIPT_DIR=scripts_dir, + LIBPATH=sdk_current_sdk_dir_node.Dir(sdk_components["lib.dir"]), + FW_ELF=sdk_current_sdk_dir_node.File(sdk_components["firmware.elf"]), + FW_BIN=sdk_current_sdk_dir_node.File(sdk_components["full.bin"]), + UPDATE_BUNDLE_DIR=sdk_current_sdk_dir_node.Dir(sdk_components["update.dir"]), + SVD_FILE="${FBT_DEBUG_DIR}/STM32WB55_CM4.svd", + # Build variables + ROOT_DIR=env.Dir("#"), + FIRMWARE_BUILD_CFG="firmware", + TARGET_HW=int(sdk_data["hardware"]), + CFLAGS_APP=sdk_data["cc_args"], + CXXFLAGS_APP=sdk_data["cpp_args"], + LINKFLAGS_APP=sdk_data["linker_args"], + LIBS=sdk_data["linker_libs"], + # ufbt state + # UFBT_STATE_DIR=ufbt_state_dir_node, + # UFBT_CURRENT_SDK_DIR=sdk_current_sdk_dir_node, + UFBT_STATE=ufbt_state, + UFBT_BOOTSTRAP_SCRIPT="${UFBT_SCRIPT_DIR}/bootstrap.py", + UFBT_SCRIPT_ROOT=scripts_dir.Dir("ufbt"), + ) + + sys.path.insert(0, env["FBT_SCRIPT_DIR"].abspath) + + +def exists(env): + return True diff --git a/scripts/ufbt/update.scons b/scripts/ufbt/update.scons new file mode 100644 index 00000000000..9658e0bb20f --- /dev/null +++ b/scripts/ufbt/update.scons @@ -0,0 +1,37 @@ +from SCons.Errors import StopError + +Import("core_env") + +update_env = core_env.Clone( + toolpath=[core_env["FBT_SCRIPT_DIR"].Dir("fbt_tools")], + tools=["python3"], +) +print("Updating SDK...") +ufbt_state = update_env["UFBT_STATE"] + +update_args = [ + "--ufbt-dir", + f'"{update_env["UFBT_STATE_DIR"]}"', +] + +if branch_name := GetOption("sdk_branch"): + update_args.extend(["--branch", branch_name]) +elif channel_name := GetOption("sdk_channel"): + update_args.extend(["--channel", channel_name]) +elif branch_name := ufbt_state.get("branch", None): + update_args.extend(["--branch", branch_name]) +elif channel_name := ufbt_state.get("channel", None): + update_args.extend(["--channel", channel_name]) +else: + raise StopError("No branch or channel specified for SDK update") + +if hw_target := GetOption("sdk_target"): + update_args.extend(["--hw-target", hw_target]) +else: + update_args.extend(["--hw-target", ufbt_state["hw_target"]]) + +update_env.Replace(UPDATE_ARGS=update_args) +result = update_env.Execute( + update_env.subst('$PYTHON3 "$UFBT_BOOTSTRAP_SCRIPT" $UPDATE_ARGS'), +) +Exit(result) diff --git a/site_scons/extapps.scons b/site_scons/extapps.scons index 208b7577557..798b85ea105 100644 --- a/site_scons/extapps.scons +++ b/site_scons/extapps.scons @@ -112,86 +112,47 @@ Alias( extapps.resources_dist = appenv.FapDist(appenv["RESOURCES_ROOT"], []) + if appsrc := appenv.subst("$APPSRC"): - deploy_sources, flipp_dist_paths, validators = [], [], [] - run_script_extra_ars = "" - - def _add_dist_targets(app_artifacts): - validators.append(app_artifacts.validator) - for _, ext_path in app_artifacts.dist_entries: - deploy_sources.append(app_artifacts.compact) - flipp_dist_paths.append(f"/ext/{ext_path}") - return app_artifacts - - def _add_host_app_to_targets(host_app): - artifacts_app_to_run = appenv["EXT_APPS"].get(host_app.appid, None) - _add_dist_targets(artifacts_app_to_run) - for plugin in host_app._plugins: - _add_dist_targets(appenv["EXT_APPS"].get(plugin.appid, None)) - - artifacts_app_to_run = appenv.GetExtAppByIdOrPath(appsrc) - if artifacts_app_to_run.app.apptype == FlipperAppType.PLUGIN: - # We deploy host app instead - host_app = appenv["APPMGR"].get(artifacts_app_to_run.app.requires[0]) - - if host_app: - if host_app.apptype == FlipperAppType.EXTERNAL: - _add_host_app_to_targets(host_app) - else: - # host app is a built-in app - run_script_extra_ars = f"-a {host_app.name}" - _add_dist_targets(artifacts_app_to_run) - else: - raise UserError("Host app is unknown") - else: - _add_host_app_to_targets(artifacts_app_to_run.app) - - # print(deploy_sources, flipp_dist_paths) - appenv.PhonyTarget( - "launch_app", - '${PYTHON3} "${APP_RUN_SCRIPT}" ${EXTRA_ARGS} -s ${SOURCES} -t ${FLIPPER_FILE_TARGETS}', - source=deploy_sources, - FLIPPER_FILE_TARGETS=flipp_dist_paths, - EXTRA_ARGS=run_script_extra_ars, - ) - appenv.Alias("launch_app", validators) + appenv.AddAppLaunchTarget(appsrc, "launch_app") + # SDK management -sdk_origin_path = "${BUILD_DIR}/sdk_origin" -sdk_source = appenv.SDKPrebuilder( - sdk_origin_path, +amalgamated_api = "${BUILD_DIR}/sdk_origin" +sdk_source = appenv.ApiAmalgamator( + amalgamated_api, # Deps on root SDK headers and generated files (appenv["SDK_HEADERS"], appenv["FW_ASSETS_HEADERS"]), ) # Extra deps on headers included in deeper levels # Available on second and subsequent builds -Depends(sdk_source, appenv.ProcessSdkDepends(f"{sdk_origin_path}.d")) +Depends(sdk_source, appenv.ProcessSdkDepends(f"{amalgamated_api}.d")) -appenv["SDK_DIR"] = appenv.Dir("${BUILD_DIR}/sdk") -sdk_tree = appenv.SDKTree(appenv["SDK_DIR"], sdk_origin_path) +appenv["SDK_DIR"] = appenv.Dir("${BUILD_DIR}/sdk_headers") +sdk_header_tree = appenv.SDKHeaderTreeExtractor(appenv["SDK_DIR"], amalgamated_api) # AlwaysBuild(sdk_tree) -Alias("sdk_tree", sdk_tree) -extapps.sdk_tree = sdk_tree +Alias("sdk_tree", sdk_header_tree) +extapps.sdk_tree = sdk_header_tree -sdk_apicheck = appenv.SDKSymUpdater(appenv["SDK_DEFINITION"], sdk_origin_path) -Precious(sdk_apicheck) -NoClean(sdk_apicheck) -AlwaysBuild(sdk_apicheck) -Alias("sdk_check", sdk_apicheck) +api_check = appenv.ApiTableValidator(appenv["SDK_DEFINITION"], amalgamated_api) +Precious(api_check) +NoClean(api_check) +AlwaysBuild(api_check) +Alias("api_check", api_check) -sdk_apisyms = appenv.SDKSymGenerator( - "${BUILD_DIR}/assets/compiled/symbols.h", appenv["SDK_DEFINITION"] +firmware_apitable = appenv.ApiSymbolTable( + "${BUILD_DIR}/assets/compiled/firmware_api_table.h", appenv["SDK_DEFINITION"] ) -Alias("api_syms", sdk_apisyms) +Alias("api_table", firmware_apitable) ENV.Replace( - SDK_APISYMS=sdk_apisyms, + FW_API_TABLE=firmware_apitable, _APP_ICONS=appenv["_APP_ICONS"], ) if appenv["FORCE"]: - appenv.AlwaysBuild(sdk_source, sdk_tree, sdk_apicheck, sdk_apisyms) + appenv.AlwaysBuild(sdk_source, sdk_header_tree, api_check, firmware_apitable) Return("extapps") From b4ceb55fd20e66709311a652a336286839c5d672 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Thu, 6 Apr 2023 06:36:12 +0300 Subject: [PATCH 508/824] [FL-2524] Graphics cleanup and icon rotation (#2561) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Canvas with rotation * Full icon rotation, cleanup of unused resources * F18 API update * Bitmap draw cleanup * More cleaning up * Migrate recovery and DFU to canvas * Make the internal draw function static * Remove all calls to u8g2_DrawXBM Co-authored-by: あく --- .../dap_link/gui/views/dap_main_view.c | 8 +- .../dap_link/icons/ArrowDownEmpty_12x18.png | Bin 160 -> 0 bytes .../dap_link/icons/ArrowDownFilled_12x18.png | Bin 168 -> 0 bytes applications/main/gpio/views/gpio_usb_uart.c | 4 +- applications/services/desktop/desktop.c | 4 +- .../desktop/views/desktop_view_pin_input.c | 13 +- applications/services/gui/canvas.c | 136 +++++++++++++++++- applications/services/gui/canvas.h | 32 +++++ applications/services/gui/canvas_i.h | 19 +++ assets/icons/GPIO/ArrowDownEmpty_14x15.png | Bin 654 -> 0 bytes assets/icons/GPIO/ArrowDownFilled_14x15.png | Bin 669 -> 0 bytes assets/icons/PIN/Pin_arrow_down_7x9.png | Bin 3607 -> 0 bytes assets/icons/PIN/Pin_arrow_left_9x7.png | Bin 3603 -> 0 bytes assets/icons/PIN/Pin_arrow_right_9x7.png | Bin 3602 -> 0 bytes assets/icons/PIN/Pin_back_full_40x8.png | Bin 3641 -> 0 bytes assets/icons/StatusBar/Lock_8x8.png | Bin 303 -> 0 bytes firmware/targets/f18/api_symbols.csv | 9 +- firmware/targets/f7/api_symbols.csv | 3 +- firmware/targets/f7/src/dfu.c | 31 ++-- firmware/targets/f7/src/recovery.c | 45 +++--- 20 files changed, 237 insertions(+), 67 deletions(-) delete mode 100644 applications/external/dap_link/icons/ArrowDownEmpty_12x18.png delete mode 100644 applications/external/dap_link/icons/ArrowDownFilled_12x18.png delete mode 100644 assets/icons/GPIO/ArrowDownEmpty_14x15.png delete mode 100644 assets/icons/GPIO/ArrowDownFilled_14x15.png delete mode 100644 assets/icons/PIN/Pin_arrow_down_7x9.png delete mode 100644 assets/icons/PIN/Pin_arrow_left_9x7.png delete mode 100644 assets/icons/PIN/Pin_arrow_right_9x7.png delete mode 100644 assets/icons/PIN/Pin_back_full_40x8.png delete mode 100644 assets/icons/StatusBar/Lock_8x8.png diff --git a/applications/external/dap_link/gui/views/dap_main_view.c b/applications/external/dap_link/gui/views/dap_main_view.c index c5c8f9dff02..f54c5e3d582 100644 --- a/applications/external/dap_link/gui/views/dap_main_view.c +++ b/applications/external/dap_link/gui/views/dap_main_view.c @@ -51,10 +51,10 @@ static void dap_main_view_draw_callback(Canvas* canvas, void* _model) { canvas_set_color(canvas, ColorBlack); if(model->dap_active) { canvas_draw_icon(canvas, 14, 16, &I_ArrowUpFilled_12x18); - canvas_draw_icon(canvas, 28, 16, &I_ArrowDownFilled_12x18); + canvas_draw_icon_ex(canvas, 28, 16, &I_ArrowUpFilled_12x18, IconRotation180); } else { canvas_draw_icon(canvas, 14, 16, &I_ArrowUpEmpty_12x18); - canvas_draw_icon(canvas, 28, 16, &I_ArrowDownEmpty_12x18); + canvas_draw_icon_ex(canvas, 28, 16, &I_ArrowUpEmpty_12x18, IconRotation180); } switch(model->mode) { @@ -76,9 +76,9 @@ static void dap_main_view_draw_callback(Canvas* canvas, void* _model) { } if(model->rx_active) { - canvas_draw_icon(canvas, 101, 16, &I_ArrowDownFilled_12x18); + canvas_draw_icon_ex(canvas, 101, 16, &I_ArrowUpFilled_12x18, IconRotation180); } else { - canvas_draw_icon(canvas, 101, 16, &I_ArrowDownEmpty_12x18); + canvas_draw_icon_ex(canvas, 101, 16, &I_ArrowUpEmpty_12x18, IconRotation180); } canvas_draw_str_aligned(canvas, 100, 38, AlignCenter, AlignTop, "UART"); diff --git a/applications/external/dap_link/icons/ArrowDownEmpty_12x18.png b/applications/external/dap_link/icons/ArrowDownEmpty_12x18.png deleted file mode 100644 index 6007f74ab0e89fc751f503a8a5e4e44021c0106c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 160 zcmeAS@N?(olHy`uVBq!ia0vp^JU}eO!3HGrSK5O(jKx9jP7LeL$-D$|qC8z3Lo_D7 zo$SbWK!Jlr{PF+&Hnv;TJQ&v7?g&r!Fz9A{(LGbM+u3CI@thML?q)j=*|$VqJ$T$$ ztuvzCPsSl~7YFluKK0CllcIM`vf0BqQzUWaj&{}5`5zhLf9t51+o=lhbp(50(A=anC2ZSBvNHg_RnWm*uWGEIRgogS6BGwsp^g6qa`c R{s7v>;OXk;vd$@?2>@rkKMeo? diff --git a/applications/main/gpio/views/gpio_usb_uart.c b/applications/main/gpio/views/gpio_usb_uart.c index 837f2e3ec58..3234309a294 100644 --- a/applications/main/gpio/views/gpio_usb_uart.c +++ b/applications/main/gpio/views/gpio_usb_uart.c @@ -80,9 +80,9 @@ static void gpio_usb_uart_draw_callback(Canvas* canvas, void* _model) { canvas_draw_icon(canvas, 48, 14, &I_ArrowUpEmpty_14x15); if(model->rx_active) - canvas_draw_icon(canvas, 48, 34, &I_ArrowDownFilled_14x15); + canvas_draw_icon_ex(canvas, 48, 34, &I_ArrowUpFilled_14x15, IconRotation180); else - canvas_draw_icon(canvas, 48, 34, &I_ArrowDownEmpty_14x15); + canvas_draw_icon_ex(canvas, 48, 34, &I_ArrowUpEmpty_14x15, IconRotation180); } static bool gpio_usb_uart_input_callback(InputEvent* event, void* context) { diff --git a/applications/services/desktop/desktop.c b/applications/services/desktop/desktop.c index 556f4233358..41470ed3a17 100644 --- a/applications/services/desktop/desktop.c +++ b/applications/services/desktop/desktop.c @@ -37,7 +37,7 @@ static void desktop_loader_callback(const void* message, void* context) { static void desktop_lock_icon_draw_callback(Canvas* canvas, void* context) { UNUSED(context); furi_assert(canvas); - canvas_draw_icon(canvas, 0, 0, &I_Lock_8x8); + canvas_draw_icon(canvas, 0, 0, &I_Lock_7x8); } static void desktop_dummy_mode_icon_draw_callback(Canvas* canvas, void* context) { @@ -230,7 +230,7 @@ Desktop* desktop_alloc() { // Lock icon desktop->lock_icon_viewport = view_port_alloc(); - view_port_set_width(desktop->lock_icon_viewport, icon_get_width(&I_Lock_8x8)); + view_port_set_width(desktop->lock_icon_viewport, icon_get_width(&I_Lock_7x8)); view_port_draw_callback_set( desktop->lock_icon_viewport, desktop_lock_icon_draw_callback, desktop); view_port_enabled_set(desktop->lock_icon_viewport, false); diff --git a/applications/services/desktop/views/desktop_view_pin_input.c b/applications/services/desktop/views/desktop_view_pin_input.c index b86bf2929bf..d3dadd7d713 100644 --- a/applications/services/desktop/views/desktop_view_pin_input.c +++ b/applications/services/desktop/views/desktop_view_pin_input.c @@ -115,16 +115,18 @@ static void desktop_view_pin_input_draw_cells(Canvas* canvas, DesktopViewPinInpu } else { switch(model->pin.data[i]) { case InputKeyDown: - canvas_draw_icon(canvas, x + 3, y + 2, &I_Pin_arrow_down_7x9); + canvas_draw_icon_ex( + canvas, x + 3, y + 2, &I_Pin_arrow_up_7x9, IconRotation180); break; case InputKeyUp: - canvas_draw_icon(canvas, x + 3, y + 2, &I_Pin_arrow_up_7x9); + canvas_draw_icon_ex(canvas, x + 3, y + 2, &I_Pin_arrow_up_7x9, IconRotation0); break; case InputKeyLeft: - canvas_draw_icon(canvas, x + 2, y + 3, &I_Pin_arrow_left_9x7); + canvas_draw_icon_ex( + canvas, x + 2, y + 3, &I_Pin_arrow_up_7x9, IconRotation270); break; case InputKeyRight: - canvas_draw_icon(canvas, x + 2, y + 3, &I_Pin_arrow_right_9x7); + canvas_draw_icon_ex(canvas, x + 2, y + 3, &I_Pin_arrow_up_7x9, IconRotation90); break; default: furi_assert(0); @@ -147,7 +149,8 @@ static void desktop_view_pin_input_draw(Canvas* canvas, void* context) { desktop_view_pin_input_draw_cells(canvas, model); if((model->pin.length > 0) && !model->locked_input) { - canvas_draw_icon(canvas, 4, 53, &I_Pin_back_full_40x8); + canvas_draw_icon(canvas, 4, 53, &I_Pin_back_arrow_10x8); + canvas_draw_str(canvas, 16, 60, "= clear"); } if(model->button_label && ((model->pin.length >= MIN_PIN_SIZE) || model->locked_input)) { diff --git a/applications/services/gui/canvas.c b/applications/services/gui/canvas.c index 40797c08674..6e35dcffb01 100644 --- a/applications/services/gui/canvas.c +++ b/applications/services/gui/canvas.c @@ -221,7 +221,7 @@ void canvas_draw_bitmap( y += canvas->offset_y; uint8_t* bitmap_data = NULL; compress_icon_decode(canvas->compress_icon, compressed_bitmap_data, &bitmap_data); - u8g2_DrawXBM(&canvas->fb, x, y, width, height, bitmap_data); + canvas_draw_u8g2_bitmap(&canvas->fb, x, y, width, height, bitmap_data, IconRotation0); } void canvas_draw_icon_animation( @@ -237,13 +237,138 @@ void canvas_draw_icon_animation( uint8_t* icon_data = NULL; compress_icon_decode( canvas->compress_icon, icon_animation_get_data(icon_animation), &icon_data); - u8g2_DrawXBM( + canvas_draw_u8g2_bitmap( &canvas->fb, x, y, icon_animation_get_width(icon_animation), icon_animation_get_height(icon_animation), - icon_data); + icon_data, + IconRotation0); +} + +static void canvas_draw_u8g2_bitmap_int( + u8g2_t* u8g2, + u8g2_uint_t x, + u8g2_uint_t y, + u8g2_uint_t w, + u8g2_uint_t h, + bool mirror, + bool rotation, + const uint8_t* bitmap) { + u8g2_uint_t blen; + blen = w; + blen += 7; + blen >>= 3; + + if(rotation && !mirror) { + x += w + 1; + } else if(mirror && !rotation) { + y += h - 1; + } + + while(h > 0) { + const uint8_t* b = bitmap; + uint16_t len = w; + uint16_t x0 = x; + uint16_t y0 = y; + uint8_t mask; + uint8_t color = u8g2->draw_color; + uint8_t ncolor = (color == 0 ? 1 : 0); + mask = 1; + + while(len > 0) { + if(u8x8_pgm_read(b) & mask) { + u8g2->draw_color = color; + u8g2_DrawHVLine(u8g2, x0, y0, 1, 0); + } else if(u8g2->bitmap_transparency == 0) { + u8g2->draw_color = ncolor; + u8g2_DrawHVLine(u8g2, x0, y0, 1, 0); + } + + if(rotation) { + y0++; + } else { + x0++; + } + + mask <<= 1; + if(mask == 0) { + mask = 1; + b++; + } + len--; + } + + u8g2->draw_color = color; + bitmap += blen; + + if(mirror) { + if(rotation) { + x++; + } else { + y--; + } + } else { + if(rotation) { + x--; + } else { + y++; + } + } + h--; + } +} + +void canvas_draw_u8g2_bitmap( + u8g2_t* u8g2, + u8g2_uint_t x, + u8g2_uint_t y, + u8g2_uint_t w, + u8g2_uint_t h, + const uint8_t* bitmap, + IconRotation rotation) { + u8g2_uint_t blen; + blen = w; + blen += 7; + blen >>= 3; +#ifdef U8G2_WITH_INTERSECTION + if(u8g2_IsIntersection(u8g2, x, y, x + w, y + h) == 0) return; +#endif /* U8G2_WITH_INTERSECTION */ + + switch(rotation) { + case IconRotation0: + canvas_draw_u8g2_bitmap_int(u8g2, x, y, w, h, 0, 0, bitmap); + break; + case IconRotation90: + canvas_draw_u8g2_bitmap_int(u8g2, x, y, w, h, 0, 1, bitmap); + break; + case IconRotation180: + canvas_draw_u8g2_bitmap_int(u8g2, x, y, w, h, 1, 0, bitmap); + break; + case IconRotation270: + canvas_draw_u8g2_bitmap_int(u8g2, x, y, w, h, 1, 1, bitmap); + break; + default: + break; + } +} + +void canvas_draw_icon_ex( + Canvas* canvas, + uint8_t x, + uint8_t y, + const Icon* icon, + IconRotation rotation) { + furi_assert(canvas); + furi_assert(icon); + + x += canvas->offset_x; + y += canvas->offset_y; + uint8_t* icon_data = NULL; + compress_icon_decode(canvas->compress_icon, icon_get_data(icon), &icon_data); + canvas_draw_u8g2_bitmap( + &canvas->fb, x, y, icon_get_width(icon), icon_get_height(icon), icon_data, rotation); } void canvas_draw_icon(Canvas* canvas, uint8_t x, uint8_t y, const Icon* icon) { @@ -254,7 +379,8 @@ void canvas_draw_icon(Canvas* canvas, uint8_t x, uint8_t y, const Icon* icon) { y += canvas->offset_y; uint8_t* icon_data = NULL; compress_icon_decode(canvas->compress_icon, icon_get_data(icon), &icon_data); - u8g2_DrawXBM(&canvas->fb, x, y, icon_get_width(icon), icon_get_height(icon), icon_data); + canvas_draw_u8g2_bitmap( + &canvas->fb, x, y, icon_get_width(icon), icon_get_height(icon), icon_data, IconRotation0); } void canvas_draw_dot(Canvas* canvas, uint8_t x, uint8_t y) { @@ -364,7 +490,7 @@ void canvas_draw_xbm( furi_assert(canvas); x += canvas->offset_x; y += canvas->offset_y; - u8g2_DrawXBM(&canvas->fb, x, y, w, h, bitmap); + canvas_draw_u8g2_bitmap(&canvas->fb, x, y, w, h, bitmap, IconRotation0); } void canvas_draw_glyph(Canvas* canvas, uint8_t x, uint8_t y, uint16_t ch) { diff --git a/applications/services/gui/canvas.h b/applications/services/gui/canvas.h index f8fe2c1db56..f34d02bfbac 100644 --- a/applications/services/gui/canvas.h +++ b/applications/services/gui/canvas.h @@ -64,6 +64,22 @@ typedef struct { uint8_t descender; } CanvasFontParameters; +/** Icon flip */ +typedef enum { + IconFlipNone, + IconFlipHorizontal, + IconFlipVertical, + IconFlipBoth, +} IconFlip; + +/** Icon rotation */ +typedef enum { + IconRotation0, + IconRotation90, + IconRotation180, + IconRotation270, +} IconRotation; + /** Canvas anonymous structure */ typedef struct Canvas Canvas; @@ -217,6 +233,22 @@ void canvas_draw_bitmap( uint8_t height, const uint8_t* compressed_bitmap_data); +/** Draw icon at position defined by x,y with rotation and flip. + * + * @param canvas Canvas instance + * @param x x coordinate + * @param y y coordinate + * @param icon Icon instance + * @param flip IconFlip + * @param rotation IconRotation + */ +void canvas_draw_icon_ex( + Canvas* canvas, + uint8_t x, + uint8_t y, + const Icon* icon, + IconRotation rotation); + /** Draw animation at position defined by x,y. * * @param canvas Canvas instance diff --git a/applications/services/gui/canvas_i.h b/applications/services/gui/canvas_i.h index 39e7021bc90..f3b8f17ad79 100644 --- a/applications/services/gui/canvas_i.h +++ b/applications/services/gui/canvas_i.h @@ -78,3 +78,22 @@ void canvas_set_orientation(Canvas* canvas, CanvasOrientation orientation); * @return CanvasOrientation */ CanvasOrientation canvas_get_orientation(const Canvas* canvas); + +/** Draw a u8g2 bitmap + * + * @param canvas Canvas instance + * @param x x coordinate + * @param y y coordinate + * @param width width + * @param height height + * @param bitmap bitmap + * @param rotation rotation + */ +void canvas_draw_u8g2_bitmap( + u8g2_t* u8g2, + uint8_t x, + uint8_t y, + uint8_t width, + uint8_t height, + const uint8_t* bitmap, + uint8_t rotation); diff --git a/assets/icons/GPIO/ArrowDownEmpty_14x15.png b/assets/icons/GPIO/ArrowDownEmpty_14x15.png deleted file mode 100644 index 8c6d54f9cbee1fa08f30d7d8d309fcf0eae24dc3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 654 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 QX&eA7&1_6djNPvM5BL~PlmGw# diff --git a/assets/icons/PIN/Pin_arrow_left_9x7.png b/assets/icons/PIN/Pin_arrow_left_9x7.png deleted file mode 100644 index fb4ded78fde8d1bf4f053ba0b353e7acf14f031a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3603 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 diff --git a/assets/icons/PIN/Pin_back_full_40x8.png b/assets/icons/PIN/Pin_back_full_40x8.png deleted file mode 100644 index cd1301512db1c06700fec26d03231c63b9be75d3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3641 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 Cv0zgG diff --git a/assets/icons/StatusBar/Lock_8x8.png b/assets/icons/StatusBar/Lock_8x8.png deleted file mode 100644 index 01fb0eb6bf7506d19f5c6e039df0ef0d3272e424..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 303 zcmeAS@N?(olHy`uVBq!ia0vp^93afZ3?z3ZhDiV^mUKs7M+SzC{oH>NS%G|oWRD45bDP46hOx7_4S6Fo+k-*%fF5l;8~T332`ZAIM?=k_T^V zeFCx=OM?7@862M7NCR<_yxm zF*LUVA`?Sx0|P4qgWh8dPf;}F=BH$)Rbpx|Hi2kZk~Z%-Py>UftDnm{r-UW|;$Ka1 diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index 40e23a747f0..7713beb93b2 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,20.0,, +Version,+,20.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -539,6 +539,7 @@ Function,+,canvas_draw_frame,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t" Function,+,canvas_draw_glyph,void,"Canvas*, uint8_t, uint8_t, uint16_t" Function,+,canvas_draw_icon,void,"Canvas*, uint8_t, uint8_t, const Icon*" Function,+,canvas_draw_icon_animation,void,"Canvas*, uint8_t, uint8_t, IconAnimation*" +Function,+,canvas_draw_icon_ex,void,"Canvas*, uint8_t, uint8_t, const Icon*, IconRotation" Function,+,canvas_draw_line,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t" Function,+,canvas_draw_rbox,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t" Function,+,canvas_draw_rframe,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t" @@ -875,12 +876,6 @@ Function,-,furi_hal_clock_resume_tick,void, Function,-,furi_hal_clock_suspend_tick,void, Function,-,furi_hal_clock_switch_to_hsi,void, Function,-,furi_hal_clock_switch_to_pll,void, -Function,-,compress_alloc,Compress*,uint16_t -Function,-,compress_decode,_Bool,"Compress*, uint8_t*, size_t, uint8_t*, size_t, size_t*" -Function,-,compress_encode,_Bool,"Compress*, uint8_t*, size_t, uint8_t*, size_t, size_t*" -Function,-,compress_free,void,Compress* -Function,-,compress_icon_decode,void,"const uint8_t*, uint8_t**" -Function,-,compress_icon_init,void, Function,+,furi_hal_console_disable,void, Function,+,furi_hal_console_enable,void, Function,+,furi_hal_console_init,void, diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 8b1d29b1ce4..fea7926801f 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,20.0,, +Version,+,20.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -621,6 +621,7 @@ Function,+,canvas_draw_frame,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t" Function,+,canvas_draw_glyph,void,"Canvas*, uint8_t, uint8_t, uint16_t" Function,+,canvas_draw_icon,void,"Canvas*, uint8_t, uint8_t, const Icon*" Function,+,canvas_draw_icon_animation,void,"Canvas*, uint8_t, uint8_t, IconAnimation*" +Function,+,canvas_draw_icon_ex,void,"Canvas*, uint8_t, uint8_t, const Icon*, IconRotation" Function,+,canvas_draw_line,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t" Function,+,canvas_draw_rbox,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t" Function,+,canvas_draw_rframe,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t" diff --git a/firmware/targets/f7/src/dfu.c b/firmware/targets/f7/src/dfu.c index b060bc8d268..7e094b4c47e 100644 --- a/firmware/targets/f7/src/dfu.c +++ b/firmware/targets/f7/src/dfu.c @@ -2,29 +2,24 @@ #include #include #include -#include #include #include +#include +#include void flipper_boot_dfu_show_splash() { // Initialize - CompressIcon* compress_icon = compress_icon_alloc(); - - u8g2_t* fb = malloc(sizeof(u8g2_t)); - memset(fb, 0, sizeof(u8g2_t)); - u8g2_Setup_st756x_flipper(fb, U8G2_R0, u8x8_hw_spi_stm32, u8g2_gpio_and_delay_stm32); - u8g2_InitDisplay(fb); - u8g2_SetDrawColor(fb, 0x01); - uint8_t* splash_data = NULL; - compress_icon_decode(compress_icon, icon_get_data(&I_DFU_128x50), &splash_data); - u8g2_DrawXBM(fb, 0, 64 - 50, 128, 50, splash_data); - u8g2_SetFont(fb, u8g2_font_helvB08_tr); - u8g2_DrawStr(fb, 2, 8, "Update & Recovery Mode"); - u8g2_DrawStr(fb, 2, 21, "DFU Started"); - u8g2_SetPowerSave(fb, 0); - u8g2_SendBuffer(fb); - - compress_icon_free(compress_icon); + Canvas* canvas = canvas_init(); + + canvas_set_color(canvas, ColorBlack); + canvas_set_font(canvas, FontPrimary); + + canvas_draw_icon(canvas, 0, 64 - 50, &I_DFU_128x50); + canvas_draw_str(canvas, 2, 8, "Update & Recovery Mode"); + canvas_draw_str(canvas, 2, 21, "DFU Started"); + canvas_commit(canvas); + + canvas_free(canvas); } void flipper_boot_dfu_exec() { diff --git a/firmware/targets/f7/src/recovery.c b/firmware/targets/f7/src/recovery.c index d037e8118f6..49d780d478f 100644 --- a/firmware/targets/f7/src/recovery.c +++ b/firmware/targets/f7/src/recovery.c @@ -2,44 +2,43 @@ #include #include #include -#include #include #include +#include +#include #define COUNTER_VALUE (136U) -static void flipper_boot_recovery_draw_splash(u8g2_t* fb, size_t progress) { +static void flipper_boot_recovery_draw_progress(Canvas* canvas, size_t progress) { if(progress < COUNTER_VALUE) { // Fill the progress bar while the progress is going down - u8g2_SetDrawColor(fb, 0x01); - u8g2_DrawRFrame(fb, 59, 41, 69, 8, 2); + canvas_draw_rframe(canvas, 59, 41, 69, 8, 2); size_t width = (COUNTER_VALUE - progress) * 68 / COUNTER_VALUE; - u8g2_DrawBox(fb, 60, 42, width, 6); + canvas_draw_box(canvas, 60, 42, width, 6); } else { - u8g2_SetDrawColor(fb, 0x00); - u8g2_DrawRBox(fb, 59, 41, 69, 8, 2); + canvas_draw_rframe(canvas, 59, 41, 69, 8, 2); + canvas_set_color(canvas, ColorWhite); + canvas_draw_box(canvas, 60, 42, 67, 6); + canvas_set_color(canvas, ColorBlack); } - u8g2_SendBuffer(fb); + canvas_commit(canvas); } -void flipper_boot_recovery_exec() { - u8g2_t* fb = malloc(sizeof(u8g2_t)); - u8g2_Setup_st756x_flipper(fb, U8G2_R0, u8x8_hw_spi_stm32, u8g2_gpio_and_delay_stm32); - u8g2_InitDisplay(fb); +void flipper_boot_recovery_draw_splash(Canvas* canvas) { + canvas_set_color(canvas, ColorBlack); + canvas_set_font(canvas, FontPrimary); + + canvas_draw_icon(canvas, 0, 0, &I_Erase_pin_128x64); - CompressIcon* compress_icon = compress_icon_alloc(); - uint8_t* splash_data = NULL; - compress_icon_decode(compress_icon, icon_get_data(&I_Erase_pin_128x64), &splash_data); + canvas_commit(canvas); +} - u8g2_ClearBuffer(fb); - u8g2_SetDrawColor(fb, 0x01); +void flipper_boot_recovery_exec() { + Canvas* canvas = canvas_init(); - // Draw the recovery picture - u8g2_DrawXBM(fb, 0, 0, 128, 64, splash_data); - u8g2_SendBuffer(fb); - u8g2_SetPowerSave(fb, 0); - compress_icon_free(compress_icon); + // Show recovery splashscreen + flipper_boot_recovery_draw_splash(canvas); size_t counter = COUNTER_VALUE; while(counter) { @@ -53,7 +52,7 @@ void flipper_boot_recovery_exec() { counter = COUNTER_VALUE; } - flipper_boot_recovery_draw_splash(fb, counter); + flipper_boot_recovery_draw_progress(canvas, counter); } if(!counter) { From d1ad9242165143215ec9267914d23e58f837374c Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Thu, 6 Apr 2023 08:13:30 +0400 Subject: [PATCH 509/824] [AVR_ISP]: add AVR ISP Programmer FAP (#2475) * [AVR_ISP]: add AVR ISP Programmer FAP * [AVR_ISP]: add auto detect AVR chip * [AVR_ISP]: fix auto detect chip * [AVR_ISP]: fix fast write flash * AVR_ISP: auto set SPI speed * AVR_ISP: add clock 4Mhz on &gpio_ext_pa4 * AVR_ISP: fix "[CRASH][ISR 4] NULL pointer dereference" with no AVR chip connected * AVR_ISP: add AVR ISP Reader * AVR_ISP: add read and check I32HEX file * AVR_ISP: add write eerom, flash, fuse, lock byte * AVR_ISP: add gui Reader, Writer * Github: unshallow on decontamination * AVR_ISP: move to external * API: fix api_symbols * AVR_ISP: add wiring scene * GUI: model mutex FuriMutexTypeNormal -> FuriMutexTypeRecursive * AVR_ISP: add chip_detect view * AVR_ISP: refactoring gui ISP Programmer * AVR_ISP: add gui "Dump AVR" * AVR_ISP: add gui "Flash AVR" * AVR_ISP: fix navigation gui * GUI: model mutex FuriMutexTypeRecursive -> FuriMutexTypeNormal * AVR_ISP: fix conflicts * AVR_ISP: fix build * AVR_ISP: delete images * AVR_ISP: add images * AVR_ISP: fix gui * AVR_ISP: fix stuck in navigation * AVR_ISP: changing the Fuse bit recording logic * AVR_ISP: fix read/write chips with memory greater than 64Kb * AVR_ISP: fix auto set speed SPI * AVR_ISP: fix gui * ISP: switching on +5 volts to an external GPIO Co-authored-by: Aleksandr Kutuzov --- .../avr_isp_programmer/application.fam | 17 + .../avr_isp_programmer/avr_app_icon_10x10.png | Bin 0 -> 3614 bytes .../external/avr_isp_programmer/avr_isp_app.c | 179 +++ .../avr_isp_programmer/avr_isp_app_i.c | 31 + .../avr_isp_programmer/avr_isp_app_i.h | 44 + .../avr_isp_programmer/helpers/avr_isp.c | 490 +++++++ .../avr_isp_programmer/helpers/avr_isp.h | 70 + .../helpers/avr_isp_event.h | 23 + .../helpers/avr_isp_types.h | 32 + .../helpers/avr_isp_worker.c | 266 ++++ .../helpers/avr_isp_worker.h | 49 + .../helpers/avr_isp_worker_rw.c | 1145 +++++++++++++++++ .../helpers/avr_isp_worker_rw.h | 99 ++ .../helpers/flipper_i32hex_file.c | 321 +++++ .../helpers/flipper_i32hex_file.h | 55 + .../images/avr_app_icon_10x10.png | Bin 0 -> 3614 bytes .../avr_isp_programmer/images/avr_wiring.png | Bin 0 -> 4513 bytes .../images/chif_not_found_83x37.png | Bin 0 -> 3742 bytes .../images/chip_error_70x22.png | Bin 0 -> 3688 bytes .../images/chip_long_70x22.png | Bin 0 -> 3656 bytes .../images/chip_not_found_83x37.png | Bin 0 -> 3779 bytes .../images/dolphin_nice_96x59.png | Bin 0 -> 2459 bytes .../images/isp_active_128x53.png | Bin 0 -> 3961 bytes .../images/link_waiting_77x56.png | Bin 0 -> 3883 bytes .../lib/driver/avr_isp_chip_arr.c | 386 ++++++ .../lib/driver/avr_isp_chip_arr.h | 33 + .../lib/driver/avr_isp_prog.c | 633 +++++++++ .../lib/driver/avr_isp_prog.h | 16 + .../lib/driver/avr_isp_prog_cmd.h | 97 ++ .../lib/driver/avr_isp_spi_sw.c | 73 ++ .../lib/driver/avr_isp_spi_sw.h | 24 + .../avr_isp_programmer/lib/driver/clock.png | Bin 0 -> 3649 bytes .../avr_isp_programmer/scenes/avr_isp_scene.c | 30 + .../avr_isp_programmer/scenes/avr_isp_scene.h | 29 + .../scenes/avr_isp_scene_about.c | 99 ++ .../scenes/avr_isp_scene_chip_detect.c | 72 ++ .../scenes/avr_isp_scene_config.h | 10 + .../scenes/avr_isp_scene_input_name.c | 89 ++ .../scenes/avr_isp_scene_load.c | 22 + .../scenes/avr_isp_scene_programmer.c | 28 + .../scenes/avr_isp_scene_reader.c | 64 + .../scenes/avr_isp_scene_start.c | 75 ++ .../scenes/avr_isp_scene_success.c | 44 + .../scenes/avr_isp_scene_wiring.c | 21 + .../scenes/avr_isp_scene_writer.c | 69 + .../views/avr_isp_view_chip_detect.c | 213 +++ .../views/avr_isp_view_chip_detect.h | 32 + .../views/avr_isp_view_programmer.c | 134 ++ .../views/avr_isp_view_programmer.h | 27 + .../views/avr_isp_view_reader.c | 215 ++++ .../views/avr_isp_view_reader.h | 35 + .../views/avr_isp_view_writer.c | 268 ++++ .../views/avr_isp_view_writer.h | 37 + firmware/targets/f18/api_symbols.csv | 6 + firmware/targets/f7/api_symbols.csv | 6 + lib/toolbox/SConscript | 1 + 56 files changed, 5709 insertions(+) create mode 100644 applications/external/avr_isp_programmer/application.fam create mode 100644 applications/external/avr_isp_programmer/avr_app_icon_10x10.png create mode 100644 applications/external/avr_isp_programmer/avr_isp_app.c create mode 100644 applications/external/avr_isp_programmer/avr_isp_app_i.c create mode 100644 applications/external/avr_isp_programmer/avr_isp_app_i.h create mode 100644 applications/external/avr_isp_programmer/helpers/avr_isp.c create mode 100644 applications/external/avr_isp_programmer/helpers/avr_isp.h create mode 100644 applications/external/avr_isp_programmer/helpers/avr_isp_event.h create mode 100644 applications/external/avr_isp_programmer/helpers/avr_isp_types.h create mode 100644 applications/external/avr_isp_programmer/helpers/avr_isp_worker.c create mode 100644 applications/external/avr_isp_programmer/helpers/avr_isp_worker.h create mode 100644 applications/external/avr_isp_programmer/helpers/avr_isp_worker_rw.c create mode 100644 applications/external/avr_isp_programmer/helpers/avr_isp_worker_rw.h create mode 100644 applications/external/avr_isp_programmer/helpers/flipper_i32hex_file.c create mode 100644 applications/external/avr_isp_programmer/helpers/flipper_i32hex_file.h create mode 100644 applications/external/avr_isp_programmer/images/avr_app_icon_10x10.png create mode 100644 applications/external/avr_isp_programmer/images/avr_wiring.png create mode 100644 applications/external/avr_isp_programmer/images/chif_not_found_83x37.png create mode 100644 applications/external/avr_isp_programmer/images/chip_error_70x22.png create mode 100644 applications/external/avr_isp_programmer/images/chip_long_70x22.png create mode 100644 applications/external/avr_isp_programmer/images/chip_not_found_83x37.png create mode 100644 applications/external/avr_isp_programmer/images/dolphin_nice_96x59.png create mode 100644 applications/external/avr_isp_programmer/images/isp_active_128x53.png create mode 100644 applications/external/avr_isp_programmer/images/link_waiting_77x56.png create mode 100644 applications/external/avr_isp_programmer/lib/driver/avr_isp_chip_arr.c create mode 100644 applications/external/avr_isp_programmer/lib/driver/avr_isp_chip_arr.h create mode 100644 applications/external/avr_isp_programmer/lib/driver/avr_isp_prog.c create mode 100644 applications/external/avr_isp_programmer/lib/driver/avr_isp_prog.h create mode 100644 applications/external/avr_isp_programmer/lib/driver/avr_isp_prog_cmd.h create mode 100644 applications/external/avr_isp_programmer/lib/driver/avr_isp_spi_sw.c create mode 100644 applications/external/avr_isp_programmer/lib/driver/avr_isp_spi_sw.h create mode 100644 applications/external/avr_isp_programmer/lib/driver/clock.png create mode 100644 applications/external/avr_isp_programmer/scenes/avr_isp_scene.c create mode 100644 applications/external/avr_isp_programmer/scenes/avr_isp_scene.h create mode 100644 applications/external/avr_isp_programmer/scenes/avr_isp_scene_about.c create mode 100644 applications/external/avr_isp_programmer/scenes/avr_isp_scene_chip_detect.c create mode 100644 applications/external/avr_isp_programmer/scenes/avr_isp_scene_config.h create mode 100644 applications/external/avr_isp_programmer/scenes/avr_isp_scene_input_name.c create mode 100644 applications/external/avr_isp_programmer/scenes/avr_isp_scene_load.c create mode 100644 applications/external/avr_isp_programmer/scenes/avr_isp_scene_programmer.c create mode 100644 applications/external/avr_isp_programmer/scenes/avr_isp_scene_reader.c create mode 100644 applications/external/avr_isp_programmer/scenes/avr_isp_scene_start.c create mode 100644 applications/external/avr_isp_programmer/scenes/avr_isp_scene_success.c create mode 100644 applications/external/avr_isp_programmer/scenes/avr_isp_scene_wiring.c create mode 100644 applications/external/avr_isp_programmer/scenes/avr_isp_scene_writer.c create mode 100644 applications/external/avr_isp_programmer/views/avr_isp_view_chip_detect.c create mode 100644 applications/external/avr_isp_programmer/views/avr_isp_view_chip_detect.h create mode 100644 applications/external/avr_isp_programmer/views/avr_isp_view_programmer.c create mode 100644 applications/external/avr_isp_programmer/views/avr_isp_view_programmer.h create mode 100644 applications/external/avr_isp_programmer/views/avr_isp_view_reader.c create mode 100644 applications/external/avr_isp_programmer/views/avr_isp_view_reader.h create mode 100644 applications/external/avr_isp_programmer/views/avr_isp_view_writer.c create mode 100644 applications/external/avr_isp_programmer/views/avr_isp_view_writer.h diff --git a/applications/external/avr_isp_programmer/application.fam b/applications/external/avr_isp_programmer/application.fam new file mode 100644 index 00000000000..19556d03d3a --- /dev/null +++ b/applications/external/avr_isp_programmer/application.fam @@ -0,0 +1,17 @@ +App( + appid="avr_isp", + name="AVR Flasher", + apptype=FlipperAppType.EXTERNAL, + entry_point="avr_isp_app", + requires=["gui"], + stack_size=4 * 1024, + order=20, + fap_icon="avr_app_icon_10x10.png", + fap_category="GPIO", + fap_icon_assets="images", + fap_private_libs=[ + Lib( + name="driver", + ), + ], +) diff --git a/applications/external/avr_isp_programmer/avr_app_icon_10x10.png b/applications/external/avr_isp_programmer/avr_app_icon_10x10.png new file mode 100644 index 0000000000000000000000000000000000000000..533787fe3569f70196d3f71e8373102dfd3967a0 GIT binary patch literal 3614 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 Vpqb^9w@m;7?5&;gRaoD1{{|C}E&c!i literal 0 HcmV?d00001 diff --git a/applications/external/avr_isp_programmer/avr_isp_app.c b/applications/external/avr_isp_programmer/avr_isp_app.c new file mode 100644 index 00000000000..740dc3610a6 --- /dev/null +++ b/applications/external/avr_isp_programmer/avr_isp_app.c @@ -0,0 +1,179 @@ +#include "avr_isp_app_i.h" + +static bool avr_isp_app_custom_event_callback(void* context, uint32_t event) { + furi_assert(context); + AvrIspApp* app = context; + return scene_manager_handle_custom_event(app->scene_manager, event); +} + +static bool avr_isp_app_back_event_callback(void* context) { + furi_assert(context); + AvrIspApp* app = context; + return scene_manager_handle_back_event(app->scene_manager); +} + +static void avr_isp_app_tick_event_callback(void* context) { + furi_assert(context); + AvrIspApp* app = context; + scene_manager_handle_tick_event(app->scene_manager); +} + +AvrIspApp* avr_isp_app_alloc() { + AvrIspApp* app = malloc(sizeof(AvrIspApp)); + + app->file_path = furi_string_alloc(); + furi_string_set(app->file_path, STORAGE_APP_DATA_PATH_PREFIX); + app->error = AvrIspErrorNoError; + + // GUI + app->gui = furi_record_open(RECORD_GUI); + + // View Dispatcher + app->view_dispatcher = view_dispatcher_alloc(); + app->scene_manager = scene_manager_alloc(&avr_isp_scene_handlers, app); + view_dispatcher_enable_queue(app->view_dispatcher); + + view_dispatcher_set_event_callback_context(app->view_dispatcher, app); + view_dispatcher_set_custom_event_callback( + app->view_dispatcher, avr_isp_app_custom_event_callback); + view_dispatcher_set_navigation_event_callback( + app->view_dispatcher, avr_isp_app_back_event_callback); + view_dispatcher_set_tick_event_callback( + app->view_dispatcher, avr_isp_app_tick_event_callback, 100); + + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + + // Open Notification record + app->notifications = furi_record_open(RECORD_NOTIFICATION); + + // SubMenu + app->submenu = submenu_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, AvrIspViewSubmenu, submenu_get_view(app->submenu)); + + // Widget + app->widget = widget_alloc(); + view_dispatcher_add_view(app->view_dispatcher, AvrIspViewWidget, widget_get_view(app->widget)); + + // Text Input + app->text_input = text_input_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, AvrIspViewTextInput, text_input_get_view(app->text_input)); + + // Popup + app->popup = popup_alloc(); + view_dispatcher_add_view(app->view_dispatcher, AvrIspViewPopup, popup_get_view(app->popup)); + + //Dialog + app->dialogs = furi_record_open(RECORD_DIALOGS); + + // Programmer view + app->avr_isp_programmer_view = avr_isp_programmer_view_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + AvrIspViewProgrammer, + avr_isp_programmer_view_get_view(app->avr_isp_programmer_view)); + + // Reader view + app->avr_isp_reader_view = avr_isp_reader_view_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + AvrIspViewReader, + avr_isp_reader_view_get_view(app->avr_isp_reader_view)); + + // Writer view + app->avr_isp_writer_view = avr_isp_writer_view_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + AvrIspViewWriter, + avr_isp_writer_view_get_view(app->avr_isp_writer_view)); + + // Chip detect view + app->avr_isp_chip_detect_view = avr_isp_chip_detect_view_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + AvrIspViewChipDetect, + avr_isp_chip_detect_view_get_view(app->avr_isp_chip_detect_view)); + + // Enable 5v power, multiple attempts to avoid issues with power chip protection false triggering + uint8_t attempts = 0; + while(!furi_hal_power_is_otg_enabled() && attempts++ < 5) { + furi_hal_power_enable_otg(); + furi_delay_ms(10); + } + + scene_manager_next_scene(app->scene_manager, AvrIspSceneStart); + + return app; +} //-V773 + +void avr_isp_app_free(AvrIspApp* app) { + furi_assert(app); + + // Disable 5v power + if(furi_hal_power_is_otg_enabled()) { + furi_hal_power_disable_otg(); + } + + // Submenu + view_dispatcher_remove_view(app->view_dispatcher, AvrIspViewSubmenu); + submenu_free(app->submenu); + + // Widget + view_dispatcher_remove_view(app->view_dispatcher, AvrIspViewWidget); + widget_free(app->widget); + + // TextInput + view_dispatcher_remove_view(app->view_dispatcher, AvrIspViewTextInput); + text_input_free(app->text_input); + + // Popup + view_dispatcher_remove_view(app->view_dispatcher, AvrIspViewPopup); + popup_free(app->popup); + + //Dialog + furi_record_close(RECORD_DIALOGS); + + // Programmer view + view_dispatcher_remove_view(app->view_dispatcher, AvrIspViewProgrammer); + avr_isp_programmer_view_free(app->avr_isp_programmer_view); + + // Reader view + view_dispatcher_remove_view(app->view_dispatcher, AvrIspViewReader); + avr_isp_reader_view_free(app->avr_isp_reader_view); + + // Writer view + view_dispatcher_remove_view(app->view_dispatcher, AvrIspViewWriter); + avr_isp_writer_view_free(app->avr_isp_writer_view); + + // Chip detect view + view_dispatcher_remove_view(app->view_dispatcher, AvrIspViewChipDetect); + avr_isp_chip_detect_view_free(app->avr_isp_chip_detect_view); + + // View dispatcher + view_dispatcher_free(app->view_dispatcher); + scene_manager_free(app->scene_manager); + + // Notifications + furi_record_close(RECORD_NOTIFICATION); + app->notifications = NULL; + + // Close records + furi_record_close(RECORD_GUI); + + // Path strings + furi_string_free(app->file_path); + + free(app); +} + +int32_t avr_isp_app(void* p) { + UNUSED(p); + AvrIspApp* avr_isp_app = avr_isp_app_alloc(); + + view_dispatcher_run(avr_isp_app->view_dispatcher); + + avr_isp_app_free(avr_isp_app); + + return 0; +} diff --git a/applications/external/avr_isp_programmer/avr_isp_app_i.c b/applications/external/avr_isp_programmer/avr_isp_app_i.c new file mode 100644 index 00000000000..7a7fa6d7f18 --- /dev/null +++ b/applications/external/avr_isp_programmer/avr_isp_app_i.c @@ -0,0 +1,31 @@ +#include "avr_isp_app_i.h" +#include +#include + +#define TAG "AvrIsp" + +bool avr_isp_load_from_file(AvrIspApp* app) { + furi_assert(app); + + FuriString* file_path = furi_string_alloc(); + FuriString* file_name = furi_string_alloc(); + + DialogsFileBrowserOptions browser_options; + dialog_file_browser_set_basic_options( + &browser_options, AVR_ISP_APP_EXTENSION, &I_avr_app_icon_10x10); + browser_options.base_path = STORAGE_APP_DATA_PATH_PREFIX; + + // Input events and views are managed by file_select + bool res = dialog_file_browser_show(app->dialogs, file_path, app->file_path, &browser_options); + + if(res) { + path_extract_dirname(furi_string_get_cstr(file_path), app->file_path); + path_extract_filename(file_path, file_name, true); + strncpy(app->file_name_tmp, furi_string_get_cstr(file_name), AVR_ISP_MAX_LEN_NAME); + } + + furi_string_free(file_name); + furi_string_free(file_path); + + return res; +} diff --git a/applications/external/avr_isp_programmer/avr_isp_app_i.h b/applications/external/avr_isp_programmer/avr_isp_app_i.h new file mode 100644 index 00000000000..17c69f8f2e2 --- /dev/null +++ b/applications/external/avr_isp_programmer/avr_isp_app_i.h @@ -0,0 +1,44 @@ +#pragma once + +#include "helpers/avr_isp_types.h" +#include + +#include "scenes/avr_isp_scene.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "views/avr_isp_view_programmer.h" +#include "views/avr_isp_view_reader.h" +#include "views/avr_isp_view_writer.h" +#include "views/avr_isp_view_chip_detect.h" + +#define AVR_ISP_MAX_LEN_NAME 64 + +typedef struct { + Gui* gui; + ViewDispatcher* view_dispatcher; + SceneManager* scene_manager; + NotificationApp* notifications; + DialogsApp* dialogs; + Popup* popup; + Submenu* submenu; + Widget* widget; + TextInput* text_input; + FuriString* file_path; + char file_name_tmp[AVR_ISP_MAX_LEN_NAME]; + AvrIspProgrammerView* avr_isp_programmer_view; + AvrIspReaderView* avr_isp_reader_view; + AvrIspWriterView* avr_isp_writer_view; + AvrIspChipDetectView* avr_isp_chip_detect_view; + AvrIspError error; +} AvrIspApp; + +bool avr_isp_load_from_file(AvrIspApp* app); \ No newline at end of file diff --git a/applications/external/avr_isp_programmer/helpers/avr_isp.c b/applications/external/avr_isp_programmer/helpers/avr_isp.c new file mode 100644 index 00000000000..76e0a80b061 --- /dev/null +++ b/applications/external/avr_isp_programmer/helpers/avr_isp.c @@ -0,0 +1,490 @@ +#include "avr_isp.h" +#include "../lib/driver/avr_isp_prog_cmd.h" +#include "../lib/driver/avr_isp_spi_sw.h" + +#include + +#define AVR_ISP_PROG_TX_RX_BUF_SIZE 320 +#define TAG "AvrIsp" + +struct AvrIsp { + AvrIspSpiSw* spi; + bool pmode; + AvrIspCallback callback; + void* context; +}; + +AvrIsp* avr_isp_alloc(void) { + AvrIsp* instance = malloc(sizeof(AvrIsp)); + return instance; +} + +void avr_isp_free(AvrIsp* instance) { + furi_assert(instance); + + if(instance->spi) avr_isp_end_pmode(instance); + free(instance); +} + +void avr_isp_set_tx_callback(AvrIsp* instance, AvrIspCallback callback, void* context) { + furi_assert(instance); + furi_assert(context); + + instance->callback = callback; + instance->context = context; +} + +uint8_t avr_isp_spi_transaction( + AvrIsp* instance, + uint8_t cmd, + uint8_t addr_hi, + uint8_t addr_lo, + uint8_t data) { + furi_assert(instance); + + avr_isp_spi_sw_txrx(instance->spi, cmd); + avr_isp_spi_sw_txrx(instance->spi, addr_hi); + avr_isp_spi_sw_txrx(instance->spi, addr_lo); + return avr_isp_spi_sw_txrx(instance->spi, data); +} + +static bool avr_isp_set_pmode(AvrIsp* instance, uint8_t a, uint8_t b, uint8_t c, uint8_t d) { + furi_assert(instance); + + uint8_t res = 0; + avr_isp_spi_sw_txrx(instance->spi, a); + avr_isp_spi_sw_txrx(instance->spi, b); + res = avr_isp_spi_sw_txrx(instance->spi, c); + avr_isp_spi_sw_txrx(instance->spi, d); + return res == 0x53; +} + +void avr_isp_end_pmode(AvrIsp* instance) { + furi_assert(instance); + + if(instance->pmode) { + avr_isp_spi_sw_res_set(instance->spi, true); + // We're about to take the target out of reset + // so configure SPI pins as input + if(instance->spi) avr_isp_spi_sw_free(instance->spi); + instance->spi = NULL; + } + + instance->pmode = false; +} + +static bool avr_isp_start_pmode(AvrIsp* instance, AvrIspSpiSwSpeed spi_speed) { + furi_assert(instance); + + // Reset target before driving PIN_SCK or PIN_MOSI + + // SPI.begin() will configure SS as output, + // so SPI master mode is selected. + // We have defined RESET as pin 10, + // which for many arduino's is not the SS pin. + // So we have to configure RESET as output here, + // (reset_target() first sets the correct level) + if(instance->spi) avr_isp_spi_sw_free(instance->spi); + instance->spi = avr_isp_spi_sw_init(spi_speed); + + avr_isp_spi_sw_res_set(instance->spi, false); + // See avr datasheets, chapter "SERIAL_PRG Programming Algorithm": + + // Pulse RESET after PIN_SCK is low: + avr_isp_spi_sw_sck_set(instance->spi, false); + + // discharge PIN_SCK, value arbitrally chosen + furi_delay_ms(20); + avr_isp_spi_sw_res_set(instance->spi, true); + + // Pulse must be minimum 2 target CPU speed cycles + // so 100 usec is ok for CPU speeds above 20KHz + furi_delay_ms(1); + + avr_isp_spi_sw_res_set(instance->spi, false); + + // Send the enable programming command: + // datasheet: must be > 20 msec + furi_delay_ms(50); + if(avr_isp_set_pmode(instance, AVR_ISP_SET_PMODE)) { + instance->pmode = true; + return true; + } + return false; +} + +bool avr_isp_auto_set_spi_speed_start_pmode(AvrIsp* instance) { + furi_assert(instance); + + AvrIspSpiSwSpeed spi_speed[] = { + AvrIspSpiSwSpeed1Mhz, + AvrIspSpiSwSpeed400Khz, + AvrIspSpiSwSpeed250Khz, + AvrIspSpiSwSpeed125Khz, + AvrIspSpiSwSpeed60Khz, + AvrIspSpiSwSpeed40Khz, + AvrIspSpiSwSpeed20Khz, + AvrIspSpiSwSpeed10Khz, + AvrIspSpiSwSpeed5Khz, + AvrIspSpiSwSpeed1Khz, + }; + for(uint8_t i = 0; i < COUNT_OF(spi_speed); i++) { + if(avr_isp_start_pmode(instance, spi_speed[i])) { + AvrIspSignature sig = avr_isp_read_signature(instance); + AvrIspSignature sig_examination = avr_isp_read_signature(instance); //-V656 + uint8_t y = 0; + while(y < 8) { + if(memcmp((uint8_t*)&sig, (uint8_t*)&sig_examination, sizeof(AvrIspSignature)) != + 0) + break; + sig_examination = avr_isp_read_signature(instance); + y++; + } + if(y == 8) { + if(spi_speed[i] > AvrIspSpiSwSpeed1Mhz) { + if(i < (COUNT_OF(spi_speed) - 1)) { + avr_isp_end_pmode(instance); + i++; + return avr_isp_start_pmode(instance, spi_speed[i]); + } + } + return true; + } + } + } + return false; +} + +static void avr_isp_commit(AvrIsp* instance, uint16_t addr, uint8_t data) { + furi_assert(instance); + + avr_isp_spi_transaction(instance, AVR_ISP_COMMIT(addr)); + /* polling flash */ + if(data == 0xFF) { + furi_delay_ms(5); + } else { + /* polling flash */ + uint32_t starttime = furi_get_tick(); + while((furi_get_tick() - starttime) < 30) { + if(avr_isp_spi_transaction(instance, AVR_ISP_READ_FLASH_HI(addr)) != 0xFF) { + break; + }; + } + } +} + +static uint16_t avr_isp_current_page(AvrIsp* instance, uint32_t addr, uint16_t page_size) { + furi_assert(instance); + + uint16_t page = 0; + switch(page_size) { + case 32: + page = addr & 0xFFFFFFF0; + break; + case 64: + page = addr & 0xFFFFFFE0; + break; + case 128: + page = addr & 0xFFFFFFC0; + break; + case 256: + page = addr & 0xFFFFFF80; + break; + + default: + page = addr; + break; + } + + return page; +} + +static bool avr_isp_flash_write_pages( + AvrIsp* instance, + uint16_t addr, + uint16_t page_size, + uint8_t* data, + uint32_t data_size) { + furi_assert(instance); + + size_t x = 0; + uint16_t page = avr_isp_current_page(instance, addr, page_size); + + while(x < data_size) { + if(page != avr_isp_current_page(instance, addr, page_size)) { + avr_isp_commit(instance, page, data[x - 1]); + page = avr_isp_current_page(instance, addr, page_size); + } + avr_isp_spi_transaction(instance, AVR_ISP_WRITE_FLASH_LO(addr, data[x++])); + avr_isp_spi_transaction(instance, AVR_ISP_WRITE_FLASH_HI(addr, data[x++])); + addr++; + } + avr_isp_commit(instance, page, data[x - 1]); + return true; +} + +bool avr_isp_erase_chip(AvrIsp* instance) { + furi_assert(instance); + + bool ret = false; + if(!instance->pmode) avr_isp_auto_set_spi_speed_start_pmode(instance); + if(instance->pmode) { + avr_isp_spi_transaction(instance, AVR_ISP_ERASE_CHIP); + furi_delay_ms(100); + avr_isp_end_pmode(instance); + ret = true; + } + return ret; +} + +static bool + avr_isp_eeprom_write(AvrIsp* instance, uint16_t addr, uint8_t* data, uint32_t data_size) { + furi_assert(instance); + + for(uint16_t i = 0; i < data_size; i++) { + avr_isp_spi_transaction(instance, AVR_ISP_WRITE_EEPROM(addr, data[i])); + furi_delay_ms(10); + addr++; + } + return true; +} + +bool avr_isp_write_page( + AvrIsp* instance, + uint32_t mem_type, + uint32_t mem_size, + uint16_t addr, + uint16_t page_size, + uint8_t* data, + uint32_t data_size) { + furi_assert(instance); + + bool ret = false; + switch(mem_type) { + case STK_SET_FLASH_TYPE: + if((addr + data_size / 2) <= mem_size) { + ret = avr_isp_flash_write_pages(instance, addr, page_size, data, data_size); + } + break; + + case STK_SET_EEPROM_TYPE: + if((addr + data_size) <= mem_size) { + ret = avr_isp_eeprom_write(instance, addr, data, data_size); + } + break; + + default: + furi_crash(TAG " Incorrect mem type."); + break; + } + + return ret; +} + +static bool avr_isp_flash_read_page( + AvrIsp* instance, + uint16_t addr, + uint16_t page_size, + uint8_t* data, + uint32_t data_size) { + furi_assert(instance); + + if(page_size > data_size) return false; + for(uint16_t i = 0; i < page_size; i += 2) { + data[i] = avr_isp_spi_transaction(instance, AVR_ISP_READ_FLASH_LO(addr)); + data[i + 1] = avr_isp_spi_transaction(instance, AVR_ISP_READ_FLASH_HI(addr)); + addr++; + } + return true; +} + +static bool avr_isp_eeprom_read_page( + AvrIsp* instance, + uint16_t addr, + uint16_t page_size, + uint8_t* data, + uint32_t data_size) { + furi_assert(instance); + + if(page_size > data_size) return false; + for(uint16_t i = 0; i < page_size; i++) { + data[i] = avr_isp_spi_transaction(instance, AVR_ISP_READ_EEPROM(addr)); + addr++; + } + return true; +} + +bool avr_isp_read_page( + AvrIsp* instance, + uint32_t mem_type, + uint16_t addr, + uint16_t page_size, + uint8_t* data, + uint32_t data_size) { + furi_assert(instance); + + bool res = false; + if(mem_type == STK_SET_FLASH_TYPE) + res = avr_isp_flash_read_page(instance, addr, page_size, data, data_size); + if(mem_type == STK_SET_EEPROM_TYPE) + res = avr_isp_eeprom_read_page(instance, addr, page_size, data, data_size); + + return res; +} + +AvrIspSignature avr_isp_read_signature(AvrIsp* instance) { + furi_assert(instance); + + AvrIspSignature signature; + signature.vendor = avr_isp_spi_transaction(instance, AVR_ISP_READ_VENDOR); + signature.part_family = avr_isp_spi_transaction(instance, AVR_ISP_READ_PART_FAMILY); + signature.part_number = avr_isp_spi_transaction(instance, AVR_ISP_READ_PART_NUMBER); + return signature; +} + +uint8_t avr_isp_read_lock_byte(AvrIsp* instance) { + furi_assert(instance); + + uint8_t data = 0; + uint32_t starttime = furi_get_tick(); + while((furi_get_tick() - starttime) < 300) { + data = avr_isp_spi_transaction(instance, AVR_ISP_READ_LOCK_BYTE); + if(avr_isp_spi_transaction(instance, AVR_ISP_READ_LOCK_BYTE) == data) { + break; + }; + data = 0x00; + } + return data; +} + +bool avr_isp_write_lock_byte(AvrIsp* instance, uint8_t lock) { + furi_assert(instance); + + bool ret = false; + if(avr_isp_read_lock_byte(instance) == lock) { + ret = true; + } else { + avr_isp_spi_transaction(instance, AVR_ISP_WRITE_LOCK_BYTE(lock)); + /* polling lock byte */ + uint32_t starttime = furi_get_tick(); + while((furi_get_tick() - starttime) < 30) { + if(avr_isp_spi_transaction(instance, AVR_ISP_READ_LOCK_BYTE) == lock) { + ret = true; + break; + }; + } + } + return ret; +} + +uint8_t avr_isp_read_fuse_low(AvrIsp* instance) { + furi_assert(instance); + + uint8_t data = 0; + uint32_t starttime = furi_get_tick(); + while((furi_get_tick() - starttime) < 300) { + data = avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_LOW); + if(avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_LOW) == data) { + break; + }; + data = 0x00; + } + return data; +} + +bool avr_isp_write_fuse_low(AvrIsp* instance, uint8_t lfuse) { + furi_assert(instance); + + bool ret = false; + if(avr_isp_read_fuse_low(instance) == lfuse) { + ret = true; + } else { + avr_isp_spi_transaction(instance, AVR_ISP_WRITE_FUSE_LOW(lfuse)); + /* polling fuse */ + uint32_t starttime = furi_get_tick(); + while((furi_get_tick() - starttime) < 30) { + if(avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_LOW) == lfuse) { + ret = true; + break; + }; + } + } + return ret; +} + +uint8_t avr_isp_read_fuse_high(AvrIsp* instance) { + furi_assert(instance); + + uint8_t data = 0; + uint32_t starttime = furi_get_tick(); + while((furi_get_tick() - starttime) < 300) { + data = avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_HIGH); + if(avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_HIGH) == data) { + break; + }; + data = 0x00; + } + return data; +} + +bool avr_isp_write_fuse_high(AvrIsp* instance, uint8_t hfuse) { + furi_assert(instance); + + bool ret = false; + if(avr_isp_read_fuse_high(instance) == hfuse) { + ret = true; + } else { + avr_isp_spi_transaction(instance, AVR_ISP_WRITE_FUSE_HIGH(hfuse)); + /* polling fuse */ + uint32_t starttime = furi_get_tick(); + while((furi_get_tick() - starttime) < 30) { + if(avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_HIGH) == hfuse) { + ret = true; + break; + }; + } + } + return ret; +} + +uint8_t avr_isp_read_fuse_extended(AvrIsp* instance) { + furi_assert(instance); + + uint8_t data = 0; + uint32_t starttime = furi_get_tick(); + while((furi_get_tick() - starttime) < 300) { + data = avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_EXTENDED); + if(avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_EXTENDED) == data) { + break; + }; + data = 0x00; + } + return data; +} + +bool avr_isp_write_fuse_extended(AvrIsp* instance, uint8_t efuse) { + furi_assert(instance); + + bool ret = false; + if(avr_isp_read_fuse_extended(instance) == efuse) { + ret = true; + } else { + avr_isp_spi_transaction(instance, AVR_ISP_WRITE_FUSE_EXTENDED(efuse)); + /* polling fuse */ + uint32_t starttime = furi_get_tick(); + while((furi_get_tick() - starttime) < 30) { + if(avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_EXTENDED) == efuse) { + ret = true; + break; + }; + } + } + return ret; +} + +void avr_isp_write_extended_addr(AvrIsp* instance, uint8_t extended_addr) { + furi_assert(instance); + + avr_isp_spi_transaction(instance, AVR_ISP_EXTENDED_ADDR(extended_addr)); + furi_delay_ms(10); +} \ No newline at end of file diff --git a/applications/external/avr_isp_programmer/helpers/avr_isp.h b/applications/external/avr_isp_programmer/helpers/avr_isp.h new file mode 100644 index 00000000000..476fc3d6427 --- /dev/null +++ b/applications/external/avr_isp_programmer/helpers/avr_isp.h @@ -0,0 +1,70 @@ +#pragma once + +#include + +typedef struct AvrIsp AvrIsp; +typedef void (*AvrIspCallback)(void* context); + +struct AvrIspSignature { + uint8_t vendor; + uint8_t part_family; + uint8_t part_number; +}; + +typedef struct AvrIspSignature AvrIspSignature; + +AvrIsp* avr_isp_alloc(void); + +void avr_isp_free(AvrIsp* instance); + +void avr_isp_set_tx_callback(AvrIsp* instance, AvrIspCallback callback, void* context); + +bool avr_isp_auto_set_spi_speed_start_pmode(AvrIsp* instance); + +AvrIspSignature avr_isp_read_signature(AvrIsp* instance); + +void avr_isp_end_pmode(AvrIsp* instance); + +bool avr_isp_erase_chip(AvrIsp* instance); + +uint8_t avr_isp_spi_transaction( + AvrIsp* instance, + uint8_t cmd, + uint8_t addr_hi, + uint8_t addr_lo, + uint8_t data); + +bool avr_isp_read_page( + AvrIsp* instance, + uint32_t memtype, + uint16_t addr, + uint16_t page_size, + uint8_t* data, + uint32_t data_size); + +bool avr_isp_write_page( + AvrIsp* instance, + uint32_t mem_type, + uint32_t mem_size, + uint16_t addr, + uint16_t page_size, + uint8_t* data, + uint32_t data_size); + +uint8_t avr_isp_read_lock_byte(AvrIsp* instance); + +bool avr_isp_write_lock_byte(AvrIsp* instance, uint8_t lock); + +uint8_t avr_isp_read_fuse_low(AvrIsp* instance); + +bool avr_isp_write_fuse_low(AvrIsp* instance, uint8_t lfuse); + +uint8_t avr_isp_read_fuse_high(AvrIsp* instance); + +bool avr_isp_write_fuse_high(AvrIsp* instance, uint8_t hfuse); + +uint8_t avr_isp_read_fuse_extended(AvrIsp* instance); + +bool avr_isp_write_fuse_extended(AvrIsp* instance, uint8_t efuse); + +void avr_isp_write_extended_addr(AvrIsp* instance, uint8_t extended_addr); \ No newline at end of file diff --git a/applications/external/avr_isp_programmer/helpers/avr_isp_event.h b/applications/external/avr_isp_programmer/helpers/avr_isp_event.h new file mode 100644 index 00000000000..c704b5b356a --- /dev/null +++ b/applications/external/avr_isp_programmer/helpers/avr_isp_event.h @@ -0,0 +1,23 @@ +#pragma once + +typedef enum { + //SubmenuIndex + SubmenuIndexAvrIspProgrammer = 10, + SubmenuIndexAvrIspReader, + SubmenuIndexAvrIspWriter, + SubmenuIndexAvrIsWiring, + SubmenuIndexAvrIspAbout, + + //AvrIspCustomEvent + AvrIspCustomEventSceneChipDetectOk = 100, + AvrIspCustomEventSceneReadingOk, + AvrIspCustomEventSceneWritingOk, + AvrIspCustomEventSceneErrorVerification, + AvrIspCustomEventSceneErrorReading, + AvrIspCustomEventSceneErrorWriting, + AvrIspCustomEventSceneErrorWritingFuse, + AvrIspCustomEventSceneInputName, + AvrIspCustomEventSceneSuccess, + AvrIspCustomEventSceneExit, + AvrIspCustomEventSceneExitStartMenu, +} AvrIspCustomEvent; diff --git a/applications/external/avr_isp_programmer/helpers/avr_isp_types.h b/applications/external/avr_isp_programmer/helpers/avr_isp_types.h new file mode 100644 index 00000000000..5e174ec3b9c --- /dev/null +++ b/applications/external/avr_isp_programmer/helpers/avr_isp_types.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include + +#define AVR_ISP_VERSION_APP "0.1" +#define AVR_ISP_DEVELOPED "SkorP" +#define AVR_ISP_GITHUB "https://github.com/flipperdevices/flipperzero-firmware" + +#define AVR_ISP_APP_FILE_VERSION 1 +#define AVR_ISP_APP_FILE_TYPE "Flipper Dump AVR" +#define AVR_ISP_APP_EXTENSION ".avr" + +typedef enum { + //AvrIspViewVariableItemList, + AvrIspViewSubmenu, + AvrIspViewProgrammer, + AvrIspViewReader, + AvrIspViewWriter, + AvrIspViewWidget, + AvrIspViewPopup, + AvrIspViewTextInput, + AvrIspViewChipDetect, +} AvrIspView; + +typedef enum { + AvrIspErrorNoError, + AvrIspErrorReading, + AvrIspErrorWriting, + AvrIspErrorVerification, + AvrIspErrorWritingFuse, +} AvrIspError; \ No newline at end of file diff --git a/applications/external/avr_isp_programmer/helpers/avr_isp_worker.c b/applications/external/avr_isp_programmer/helpers/avr_isp_worker.c new file mode 100644 index 00000000000..dfe1f43c2c3 --- /dev/null +++ b/applications/external/avr_isp_programmer/helpers/avr_isp_worker.c @@ -0,0 +1,266 @@ +#include "avr_isp_worker.h" +#include +#include "../lib/driver/avr_isp_prog.h" +#include "../lib/driver/avr_isp_prog_cmd.h" +#include "../lib/driver/avr_isp_chip_arr.h" + +#include + +#define TAG "AvrIspWorker" + +typedef enum { + AvrIspWorkerEvtStop = (1 << 0), + + AvrIspWorkerEvtRx = (1 << 1), + AvrIspWorkerEvtTxCoplete = (1 << 2), + AvrIspWorkerEvtTx = (1 << 3), + AvrIspWorkerEvtState = (1 << 4), + + //AvrIspWorkerEvtCfg = (1 << 5), + +} AvrIspWorkerEvt; + +struct AvrIspWorker { + FuriThread* thread; + volatile bool worker_running; + uint8_t connect_usb; + AvrIspWorkerCallback callback; + void* context; +}; + +#define AVR_ISP_WORKER_PROG_ALL_EVENTS (AvrIspWorkerEvtStop) +#define AVR_ISP_WORKER_ALL_EVENTS \ + (AvrIspWorkerEvtTx | AvrIspWorkerEvtTxCoplete | AvrIspWorkerEvtRx | AvrIspWorkerEvtStop | \ + AvrIspWorkerEvtState) + +//########################/* VCP CDC */############################################# +#include "usb_cdc.h" +#include +#include +#include + +#define AVR_ISP_VCP_CDC_CH 1 +#define AVR_ISP_VCP_CDC_PKT_LEN CDC_DATA_SZ +#define AVR_ISP_VCP_UART_RX_BUF_SIZE (AVR_ISP_VCP_CDC_PKT_LEN * 5) + +static void vcp_on_cdc_tx_complete(void* context); +static void vcp_on_cdc_rx(void* context); +static void vcp_state_callback(void* context, uint8_t state); +static void vcp_on_cdc_control_line(void* context, uint8_t state); +static void vcp_on_line_config(void* context, struct usb_cdc_line_coding* config); + +static const CdcCallbacks cdc_cb = { + vcp_on_cdc_tx_complete, + vcp_on_cdc_rx, + vcp_state_callback, + vcp_on_cdc_control_line, + vcp_on_line_config, +}; + +/* VCP callbacks */ + +static void vcp_on_cdc_tx_complete(void* context) { + furi_assert(context); + AvrIspWorker* instance = context; + furi_thread_flags_set(furi_thread_get_id(instance->thread), AvrIspWorkerEvtTxCoplete); +} + +static void vcp_on_cdc_rx(void* context) { + furi_assert(context); + AvrIspWorker* instance = context; + furi_thread_flags_set(furi_thread_get_id(instance->thread), AvrIspWorkerEvtRx); +} + +static void vcp_state_callback(void* context, uint8_t state) { + UNUSED(context); + + AvrIspWorker* instance = context; + instance->connect_usb = state; + furi_thread_flags_set(furi_thread_get_id(instance->thread), AvrIspWorkerEvtState); +} + +static void vcp_on_cdc_control_line(void* context, uint8_t state) { + UNUSED(context); + UNUSED(state); +} + +static void vcp_on_line_config(void* context, struct usb_cdc_line_coding* config) { + UNUSED(context); + UNUSED(config); +} + +static void avr_isp_worker_vcp_cdc_init(void* context) { + furi_hal_usb_unlock(); + Cli* cli = furi_record_open(RECORD_CLI); + //close cli + cli_session_close(cli); + //disable callbacks VCP_CDC=0 + furi_hal_cdc_set_callbacks(0, NULL, NULL); + //set 2 cdc + furi_check(furi_hal_usb_set_config(&usb_cdc_dual, NULL) == true); + //open cli VCP_CDC=0 + cli_session_open(cli, &cli_vcp); + furi_record_close(RECORD_CLI); + + furi_hal_cdc_set_callbacks(AVR_ISP_VCP_CDC_CH, (CdcCallbacks*)&cdc_cb, context); +} + +static void avr_isp_worker_vcp_cdc_deinit(void) { + //disable callbacks AVR_ISP_VCP_CDC_CH + furi_hal_cdc_set_callbacks(AVR_ISP_VCP_CDC_CH, NULL, NULL); + + Cli* cli = furi_record_open(RECORD_CLI); + //close cli + cli_session_close(cli); + furi_hal_usb_unlock(); + //set 1 cdc + furi_check(furi_hal_usb_set_config(&usb_cdc_single, NULL) == true); + //open cli VCP_CDC=0 + cli_session_open(cli, &cli_vcp); + furi_record_close(RECORD_CLI); +} + +//################################################################################# + +static int32_t avr_isp_worker_prog_thread(void* context) { + AvrIspProg* prog = context; + FURI_LOG_D(TAG, "AvrIspProgWorker Start"); + while(1) { + if(furi_thread_flags_get() & AvrIspWorkerEvtStop) break; + avr_isp_prog_avrisp(prog); + } + FURI_LOG_D(TAG, "AvrIspProgWorker Stop"); + return 0; +} + +static void avr_isp_worker_prog_tx_data(void* context) { + furi_assert(context); + AvrIspWorker* instance = context; + furi_thread_flags_set(furi_thread_get_id(instance->thread), AvrIspWorkerEvtTx); +} + +/** Worker thread + * + * @param context + * @return exit code + */ +static int32_t avr_isp_worker_thread(void* context) { + AvrIspWorker* instance = context; + avr_isp_worker_vcp_cdc_init(instance); + + /* start PWM on &gpio_ext_pa4 */ + furi_hal_pwm_start(FuriHalPwmOutputIdLptim2PA4, 4000000, 50); + + AvrIspProg* prog = avr_isp_prog_init(); + avr_isp_prog_set_tx_callback(prog, avr_isp_worker_prog_tx_data, instance); + + uint8_t buf[AVR_ISP_VCP_UART_RX_BUF_SIZE]; + size_t len = 0; + + FuriThread* prog_thread = + furi_thread_alloc_ex("AvrIspProgWorker", 1024, avr_isp_worker_prog_thread, prog); + furi_thread_start(prog_thread); + + FURI_LOG_D(TAG, "Start"); + + while(instance->worker_running) { + uint32_t events = + furi_thread_flags_wait(AVR_ISP_WORKER_ALL_EVENTS, FuriFlagWaitAny, FuriWaitForever); + + if(events & AvrIspWorkerEvtRx) { + if(avr_isp_prog_spaces_rx(prog) >= AVR_ISP_VCP_CDC_PKT_LEN) { + len = furi_hal_cdc_receive(AVR_ISP_VCP_CDC_CH, buf, AVR_ISP_VCP_CDC_PKT_LEN); + // for(uint8_t i = 0; i < len; i++) { + // FURI_LOG_I(TAG, "--> %X", buf[i]); + // } + avr_isp_prog_rx(prog, buf, len); + } else { + furi_thread_flags_set(furi_thread_get_id(instance->thread), AvrIspWorkerEvtRx); + } + } + + if((events & AvrIspWorkerEvtTxCoplete) || (events & AvrIspWorkerEvtTx)) { + len = avr_isp_prog_tx(prog, buf, AVR_ISP_VCP_CDC_PKT_LEN); + + // for(uint8_t i = 0; i < len; i++) { + // FURI_LOG_I(TAG, "<-- %X", buf[i]); + // } + + if(len > 0) furi_hal_cdc_send(AVR_ISP_VCP_CDC_CH, buf, len); + } + + if(events & AvrIspWorkerEvtStop) { + break; + } + + if(events & AvrIspWorkerEvtState) { + if(instance->callback) + instance->callback(instance->context, (bool)instance->connect_usb); + } + } + + FURI_LOG_D(TAG, "Stop"); + + furi_thread_flags_set(furi_thread_get_id(prog_thread), AvrIspWorkerEvtStop); + avr_isp_prog_exit(prog); + furi_delay_ms(10); + furi_thread_join(prog_thread); + furi_thread_free(prog_thread); + + avr_isp_prog_free(prog); + furi_hal_pwm_stop(FuriHalPwmOutputIdLptim2PA4); + avr_isp_worker_vcp_cdc_deinit(); + return 0; +} + +AvrIspWorker* avr_isp_worker_alloc(void* context) { + furi_assert(context); + UNUSED(context); + AvrIspWorker* instance = malloc(sizeof(AvrIspWorker)); + + instance->thread = furi_thread_alloc_ex("AvrIspWorker", 2048, avr_isp_worker_thread, instance); + return instance; +} + +void avr_isp_worker_free(AvrIspWorker* instance) { + furi_assert(instance); + + furi_check(!instance->worker_running); + furi_thread_free(instance->thread); + free(instance); +} + +void avr_isp_worker_set_callback( + AvrIspWorker* instance, + AvrIspWorkerCallback callback, + void* context) { + furi_assert(instance); + + instance->callback = callback; + instance->context = context; +} + +void avr_isp_worker_start(AvrIspWorker* instance) { + furi_assert(instance); + furi_assert(!instance->worker_running); + + instance->worker_running = true; + + furi_thread_start(instance->thread); +} + +void avr_isp_worker_stop(AvrIspWorker* instance) { + furi_assert(instance); + furi_assert(instance->worker_running); + + instance->worker_running = false; + furi_thread_flags_set(furi_thread_get_id(instance->thread), AvrIspWorkerEvtStop); + + furi_thread_join(instance->thread); +} + +bool avr_isp_worker_is_running(AvrIspWorker* instance) { + furi_assert(instance); + + return instance->worker_running; +} diff --git a/applications/external/avr_isp_programmer/helpers/avr_isp_worker.h b/applications/external/avr_isp_programmer/helpers/avr_isp_worker.h new file mode 100644 index 00000000000..cd9897dff3f --- /dev/null +++ b/applications/external/avr_isp_programmer/helpers/avr_isp_worker.h @@ -0,0 +1,49 @@ +#pragma once + +#include + +typedef struct AvrIspWorker AvrIspWorker; + +typedef void (*AvrIspWorkerCallback)(void* context, bool connect_usb); + +/** Allocate AvrIspWorker + * + * @param context AvrIsp* context + * @return AvrIspWorker* + */ +AvrIspWorker* avr_isp_worker_alloc(void* context); + +/** Free AvrIspWorker + * + * @param instance AvrIspWorker instance + */ +void avr_isp_worker_free(AvrIspWorker* instance); + +/** Callback AvrIspWorker + * + * @param instance AvrIspWorker instance + * @param callback AvrIspWorkerOverrunCallback callback + * @param context + */ +void avr_isp_worker_set_callback( + AvrIspWorker* instance, + AvrIspWorkerCallback callback, + void* context); + +/** Start AvrIspWorker + * + * @param instance AvrIspWorker instance + */ +void avr_isp_worker_start(AvrIspWorker* instance); + +/** Stop AvrIspWorker + * + * @param instance AvrIspWorker instance + */ +void avr_isp_worker_stop(AvrIspWorker* instance); + +/** Check if worker is running + * @param instance AvrIspWorker instance + * @return bool - true if running + */ +bool avr_isp_worker_is_running(AvrIspWorker* instance); diff --git a/applications/external/avr_isp_programmer/helpers/avr_isp_worker_rw.c b/applications/external/avr_isp_programmer/helpers/avr_isp_worker_rw.c new file mode 100644 index 00000000000..fc8d3b09fe0 --- /dev/null +++ b/applications/external/avr_isp_programmer/helpers/avr_isp_worker_rw.c @@ -0,0 +1,1145 @@ +#include "avr_isp_worker_rw.h" +#include +#include "avr_isp_types.h" +#include "avr_isp.h" +#include "../lib/driver/avr_isp_prog_cmd.h" +#include "../lib/driver/avr_isp_chip_arr.h" + +#include "flipper_i32hex_file.h" +#include + +#include + +#define TAG "AvrIspWorkerRW" + +#define NAME_PATERN_FLASH_FILE "flash.hex" +#define NAME_PATERN_EEPROM_FILE "eeprom.hex" + +struct AvrIspWorkerRW { + AvrIsp* avr_isp; + FuriThread* thread; + volatile bool worker_running; + + uint32_t chip_arr_ind; + bool chip_detect; + uint8_t lfuse; + uint8_t hfuse; + uint8_t efuse; + uint8_t lock; + float progress_flash; + float progress_eeprom; + const char* file_path; + const char* file_name; + AvrIspSignature signature; + AvrIspWorkerRWCallback callback; + void* context; + + AvrIspWorkerRWStatusCallback callback_status; + void* context_status; +}; + +typedef enum { + AvrIspWorkerRWEvtStop = (1 << 0), + + AvrIspWorkerRWEvtReading = (1 << 1), + AvrIspWorkerRWEvtVerification = (1 << 2), + AvrIspWorkerRWEvtWriting = (1 << 3), + AvrIspWorkerRWEvtWritingFuse = (1 << 4), + +} AvrIspWorkerRWEvt; +#define AVR_ISP_WORKER_ALL_EVENTS \ + (AvrIspWorkerRWEvtWritingFuse | AvrIspWorkerRWEvtWriting | AvrIspWorkerRWEvtVerification | \ + AvrIspWorkerRWEvtReading | AvrIspWorkerRWEvtStop) + +/** Worker thread + * + * @param context + * @return exit code + */ +static int32_t avr_isp_worker_rw_thread(void* context) { + AvrIspWorkerRW* instance = context; + + /* start PWM on &gpio_ext_pa4 */ + furi_hal_pwm_start(FuriHalPwmOutputIdLptim2PA4, 4000000, 50); + + FURI_LOG_D(TAG, "Start"); + + while(1) { + uint32_t events = + furi_thread_flags_wait(AVR_ISP_WORKER_ALL_EVENTS, FuriFlagWaitAny, FuriWaitForever); + + if(events & AvrIspWorkerRWEvtStop) { + break; + } + + if(events & AvrIspWorkerRWEvtWritingFuse) { + if(avr_isp_worker_rw_write_fuse(instance, instance->file_path, instance->file_name)) { + if(instance->callback_status) + instance->callback_status( + instance->context_status, AvrIspWorkerRWStatusEndWritingFuse); + } else { + if(instance->callback_status) + instance->callback_status( + instance->context_status, AvrIspWorkerRWStatusErrorWritingFuse); + } + } + + if(events & AvrIspWorkerRWEvtWriting) { + if(avr_isp_worker_rw_write_dump(instance, instance->file_path, instance->file_name)) { + if(instance->callback_status) + instance->callback_status( + instance->context_status, AvrIspWorkerRWStatusEndWriting); + } else { + if(instance->callback_status) + instance->callback_status( + instance->context_status, AvrIspWorkerRWStatusErrorWriting); + } + } + + if(events & AvrIspWorkerRWEvtVerification) { + if(avr_isp_worker_rw_verification(instance, instance->file_path, instance->file_name)) { + if(instance->callback_status) + instance->callback_status( + instance->context_status, AvrIspWorkerRWStatusEndVerification); + } else { + if(instance->callback_status) + instance->callback_status( + instance->context_status, AvrIspWorkerRWStatusErrorVerification); + } + } + + if(events & AvrIspWorkerRWEvtReading) { + if(avr_isp_worker_rw_read_dump(instance, instance->file_path, instance->file_name)) { + if(instance->callback_status) + instance->callback_status( + instance->context_status, AvrIspWorkerRWStatusEndReading); + } else { + if(instance->callback_status) + instance->callback_status( + instance->context_status, AvrIspWorkerRWStatusErrorReading); + } + } + } + FURI_LOG_D(TAG, "Stop"); + + furi_hal_pwm_stop(FuriHalPwmOutputIdLptim2PA4); + + return 0; +} + +bool avr_isp_worker_rw_detect_chip(AvrIspWorkerRW* instance) { + furi_assert(instance); + + FURI_LOG_D(TAG, "Detecting AVR chip"); + + instance->chip_detect = false; + instance->chip_arr_ind = avr_isp_chip_arr_size + 1; + + /* start PWM on &gpio_ext_pa4 */ + furi_hal_pwm_start(FuriHalPwmOutputIdLptim2PA4, 4000000, 50); + + do { + if(!avr_isp_auto_set_spi_speed_start_pmode(instance->avr_isp)) { + FURI_LOG_E(TAG, "Well, I managed to enter the mod program"); + break; + } + instance->signature = avr_isp_read_signature(instance->avr_isp); + + if(instance->signature.vendor != 0x1E) { + //No detect chip + } else { + for(uint32_t ind = 0; ind < avr_isp_chip_arr_size; ind++) { + if(avr_isp_chip_arr[ind].avrarch != F_AVR8) continue; + if(avr_isp_chip_arr[ind].sigs[1] == instance->signature.part_family) { + if(avr_isp_chip_arr[ind].sigs[2] == instance->signature.part_number) { + FURI_LOG_D(TAG, "Detect AVR chip = \"%s\"", avr_isp_chip_arr[ind].name); + FURI_LOG_D( + TAG, + "Signature = 0x%02X 0x%02X 0x%02X", + instance->signature.vendor, + instance->signature.part_family, + instance->signature.part_number); + + switch(avr_isp_chip_arr[ind].nfuses) { + case 1: + instance->lfuse = avr_isp_read_fuse_low(instance->avr_isp); + FURI_LOG_D(TAG, "Lfuse = %02X", instance->lfuse); + break; + case 2: + instance->lfuse = avr_isp_read_fuse_low(instance->avr_isp); + instance->hfuse = avr_isp_read_fuse_high(instance->avr_isp); + FURI_LOG_D( + TAG, "Lfuse = %02X Hfuse = %02X", instance->lfuse, instance->hfuse); + break; + case 3: + instance->lfuse = avr_isp_read_fuse_low(instance->avr_isp); + instance->hfuse = avr_isp_read_fuse_high(instance->avr_isp); + instance->efuse = avr_isp_read_fuse_extended(instance->avr_isp); + FURI_LOG_D( + TAG, + "Lfuse = %02X Hfuse = %02X Efuse = %02X", + instance->lfuse, + instance->hfuse, + instance->efuse); + break; + default: + break; + } + if(avr_isp_chip_arr[ind].nlocks == 1) { + instance->lock = avr_isp_read_lock_byte(instance->avr_isp); + FURI_LOG_D(TAG, "Lock = %02X", instance->lock); + } + instance->chip_detect = true; + instance->chip_arr_ind = ind; + break; + } + } + } + } + avr_isp_end_pmode(instance->avr_isp); + + furi_hal_pwm_stop(FuriHalPwmOutputIdLptim2PA4); + + } while(0); + if(instance->callback) { + if(instance->chip_arr_ind > avr_isp_chip_arr_size) { + instance->callback(instance->context, "No detect", instance->chip_detect, 0); + } else if(instance->chip_arr_ind < avr_isp_chip_arr_size) { + instance->callback( + instance->context, + avr_isp_chip_arr[instance->chip_arr_ind].name, + instance->chip_detect, + avr_isp_chip_arr[instance->chip_arr_ind].flashsize); + } else { + instance->callback(instance->context, "Unknown", instance->chip_detect, 0); + } + } + + return instance->chip_detect; +} + +AvrIspWorkerRW* avr_isp_worker_rw_alloc(void* context) { + furi_assert(context); + UNUSED(context); + + AvrIspWorkerRW* instance = malloc(sizeof(AvrIspWorkerRW)); + instance->avr_isp = avr_isp_alloc(); + + instance->thread = + furi_thread_alloc_ex("AvrIspWorkerRW", 4096, avr_isp_worker_rw_thread, instance); + + instance->chip_detect = false; + instance->lfuse = 0; + instance->hfuse = 0; + instance->efuse = 0; + instance->lock = 0; + instance->progress_flash = 0.0f; + instance->progress_eeprom = 0.0f; + + return instance; +} + +void avr_isp_worker_rw_free(AvrIspWorkerRW* instance) { + furi_assert(instance); + + avr_isp_free(instance->avr_isp); + + furi_check(!instance->worker_running); + furi_thread_free(instance->thread); + + free(instance); +} + +void avr_isp_worker_rw_start(AvrIspWorkerRW* instance) { + furi_assert(instance); + furi_assert(!instance->worker_running); + + instance->worker_running = true; + + furi_thread_start(instance->thread); +} + +void avr_isp_worker_rw_stop(AvrIspWorkerRW* instance) { + furi_assert(instance); + furi_assert(instance->worker_running); + + instance->worker_running = false; + furi_thread_flags_set(furi_thread_get_id(instance->thread), AvrIspWorkerRWEvtStop); + + furi_thread_join(instance->thread); +} + +bool avr_isp_worker_rw_is_running(AvrIspWorkerRW* instance) { + furi_assert(instance); + + return instance->worker_running; +} + +void avr_isp_worker_rw_set_callback( + AvrIspWorkerRW* instance, + AvrIspWorkerRWCallback callback, + void* context) { + furi_assert(instance); + + instance->callback = callback; + instance->context = context; +} + +void avr_isp_worker_rw_set_callback_status( + AvrIspWorkerRW* instance, + AvrIspWorkerRWStatusCallback callback_status, + void* context_status) { + furi_assert(instance); + + instance->callback_status = callback_status; + instance->context_status = context_status; +} + +float avr_isp_worker_rw_get_progress_flash(AvrIspWorkerRW* instance) { + furi_assert(instance); + + return instance->progress_flash; +} + +float avr_isp_worker_rw_get_progress_eeprom(AvrIspWorkerRW* instance) { + furi_assert(instance); + + return instance->progress_eeprom; +} + +static void avr_isp_worker_rw_get_dump_flash(AvrIspWorkerRW* instance, const char* file_path) { + furi_assert(instance); + furi_check(instance->avr_isp); + + FURI_LOG_D(TAG, "Dump FLASH %s", file_path); + + FlipperI32HexFile* flipper_hex_flash = flipper_i32hex_file_open_write( + file_path, avr_isp_chip_arr[instance->chip_arr_ind].flashoffset); + + uint8_t data[272] = {0}; + bool send_extended_addr = ((avr_isp_chip_arr[instance->chip_arr_ind].flashsize / 2) > 0x10000); + uint8_t extended_addr = 0; + + for(int32_t i = avr_isp_chip_arr[instance->chip_arr_ind].flashoffset; + i < avr_isp_chip_arr[instance->chip_arr_ind].flashsize / 2; + i += avr_isp_chip_arr[instance->chip_arr_ind].pagesize / 2) { + if(send_extended_addr) { + if(extended_addr <= ((i >> 16) & 0xFF)) { + avr_isp_write_extended_addr(instance->avr_isp, extended_addr); + extended_addr = ((i >> 16) & 0xFF) + 1; + } + } + avr_isp_read_page( + instance->avr_isp, + STK_SET_FLASH_TYPE, + (uint16_t)i, + avr_isp_chip_arr[instance->chip_arr_ind].pagesize, + data, + sizeof(data)); + flipper_i32hex_file_bin_to_i32hex_set_data( + flipper_hex_flash, data, avr_isp_chip_arr[instance->chip_arr_ind].pagesize); + FURI_LOG_D(TAG, "%s", flipper_i32hex_file_get_string(flipper_hex_flash)); + instance->progress_flash = + (float)(i) / ((float)avr_isp_chip_arr[instance->chip_arr_ind].flashsize / 2.0f); + } + flipper_i32hex_file_bin_to_i32hex_set_end_line(flipper_hex_flash); + FURI_LOG_D(TAG, "%s", flipper_i32hex_file_get_string(flipper_hex_flash)); + flipper_i32hex_file_close(flipper_hex_flash); + instance->progress_flash = 1.0f; +} + +static void avr_isp_worker_rw_get_dump_eeprom(AvrIspWorkerRW* instance, const char* file_path) { + furi_assert(instance); + furi_check(instance->avr_isp); + + FURI_LOG_D(TAG, "Dump EEPROM %s", file_path); + + FlipperI32HexFile* flipper_hex_eeprom = flipper_i32hex_file_open_write( + file_path, avr_isp_chip_arr[instance->chip_arr_ind].eepromoffset); + + int32_t size_data = 32; + uint8_t data[256] = {0}; + + if(size_data > avr_isp_chip_arr[instance->chip_arr_ind].eepromsize) + size_data = avr_isp_chip_arr[instance->chip_arr_ind].eepromsize; + + for(int32_t i = avr_isp_chip_arr[instance->chip_arr_ind].eepromoffset; + i < avr_isp_chip_arr[instance->chip_arr_ind].eepromsize; + i += size_data) { + avr_isp_read_page( + instance->avr_isp, STK_SET_EEPROM_TYPE, (uint16_t)i, size_data, data, sizeof(data)); + flipper_i32hex_file_bin_to_i32hex_set_data(flipper_hex_eeprom, data, size_data); + FURI_LOG_D(TAG, "%s", flipper_i32hex_file_get_string(flipper_hex_eeprom)); + instance->progress_eeprom = + (float)(i) / ((float)avr_isp_chip_arr[instance->chip_arr_ind].eepromsize); + } + flipper_i32hex_file_bin_to_i32hex_set_end_line(flipper_hex_eeprom); + FURI_LOG_D(TAG, "%s", flipper_i32hex_file_get_string(flipper_hex_eeprom)); + flipper_i32hex_file_close(flipper_hex_eeprom); + instance->progress_eeprom = 1.0f; +} + +bool avr_isp_worker_rw_read_dump( + AvrIspWorkerRW* instance, + const char* file_path, + const char* file_name) { + furi_assert(instance); + furi_assert(file_path); + furi_assert(file_name); + + FURI_LOG_D(TAG, "Read dump chip"); + + instance->progress_flash = 0.0f; + instance->progress_eeprom = 0.0f; + bool ret = false; + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* flipper_format = flipper_format_file_alloc(storage); + FuriString* file_path_name = furi_string_alloc(); + + if(!avr_isp_worker_rw_detect_chip(instance)) { + FURI_LOG_E(TAG, "No detect AVR chip"); + } else { + do { + furi_string_printf( + file_path_name, "%s/%s%s", file_path, file_name, AVR_ISP_APP_EXTENSION); + if(!flipper_format_file_open_always( + flipper_format, furi_string_get_cstr(file_path_name))) { + FURI_LOG_E(TAG, "flipper_format_file_open_always"); + break; + } + if(!flipper_format_write_header_cstr( + flipper_format, AVR_ISP_APP_FILE_TYPE, AVR_ISP_APP_FILE_VERSION)) { + FURI_LOG_E(TAG, "flipper_format_write_header_cstr"); + break; + } + if(!flipper_format_write_string_cstr( + flipper_format, "Chip name", avr_isp_chip_arr[instance->chip_arr_ind].name)) { + FURI_LOG_E(TAG, "Chip name"); + break; + } + if(!flipper_format_write_hex( + flipper_format, + "Signature", + (uint8_t*)&instance->signature, + sizeof(AvrIspSignature))) { + FURI_LOG_E(TAG, "Unable to add Signature"); + break; + } + if(avr_isp_chip_arr[instance->chip_arr_ind].nfuses > 0) { + if(!flipper_format_write_hex(flipper_format, "Lfuse", &instance->lfuse, 1)) { + FURI_LOG_E(TAG, "Unable to add Lfuse"); + break; + } + } + if(avr_isp_chip_arr[instance->chip_arr_ind].nfuses > 1) { + if(!flipper_format_write_hex(flipper_format, "Hfuse", &instance->hfuse, 1)) { + FURI_LOG_E(TAG, "Unable to add Hfuse"); + break; + } + } + if(avr_isp_chip_arr[instance->chip_arr_ind].nfuses > 2) { + if(!flipper_format_write_hex(flipper_format, "Efuse", &instance->efuse, 1)) { + FURI_LOG_E(TAG, "Unable to add Efuse"); + break; + } + } + if(avr_isp_chip_arr[instance->chip_arr_ind].nlocks == 1) { + if(!flipper_format_write_hex(flipper_format, "Lock", &instance->lock, 1)) { + FURI_LOG_E(TAG, "Unable to add Lock"); + break; + } + } + furi_string_printf(file_path_name, "%s_%s", file_name, NAME_PATERN_FLASH_FILE); + if(!flipper_format_write_string_cstr( + flipper_format, "Dump_flash", furi_string_get_cstr(file_path_name))) { + FURI_LOG_E(TAG, "Unable to add Dump_flash"); + break; + } + + if(avr_isp_chip_arr[instance->chip_arr_ind].eepromsize > 0) { + furi_string_printf(file_path_name, "%s_%s", file_name, NAME_PATERN_EEPROM_FILE); + if(avr_isp_chip_arr[instance->chip_arr_ind].eepromsize > 0) { + if(!flipper_format_write_string_cstr( + flipper_format, "Dump_eeprom", furi_string_get_cstr(file_path_name))) { + FURI_LOG_E(TAG, "Unable to add Dump_eeprom"); + break; + } + } + } + ret = true; + } while(false); + } + + flipper_format_free(flipper_format); + furi_record_close(RECORD_STORAGE); + + if(ret) { + if(avr_isp_auto_set_spi_speed_start_pmode(instance->avr_isp)) { + //Dump flash + furi_string_printf( + file_path_name, "%s/%s_%s", file_path, file_name, NAME_PATERN_FLASH_FILE); + avr_isp_worker_rw_get_dump_flash(instance, furi_string_get_cstr(file_path_name)); + //Dump eeprom + if(avr_isp_chip_arr[instance->chip_arr_ind].eepromsize > 0) { + furi_string_printf( + file_path_name, "%s/%s_%s", file_path, file_name, NAME_PATERN_EEPROM_FILE); + avr_isp_worker_rw_get_dump_eeprom(instance, furi_string_get_cstr(file_path_name)); + } + + avr_isp_end_pmode(instance->avr_isp); + } + } + + furi_string_free(file_path_name); + + return true; +} + +void avr_isp_worker_rw_read_dump_start( + AvrIspWorkerRW* instance, + const char* file_path, + const char* file_name) { + furi_assert(instance); + + instance->file_path = file_path; + instance->file_name = file_name; + furi_thread_flags_set(furi_thread_get_id(instance->thread), AvrIspWorkerRWEvtReading); +} + +static bool avr_isp_worker_rw_verification_flash(AvrIspWorkerRW* instance, const char* file_path) { + furi_assert(instance); + furi_assert(file_path); + + FURI_LOG_D(TAG, "Verification flash %s", file_path); + + instance->progress_flash = 0.0; + bool ret = true; + + FlipperI32HexFile* flipper_hex_flash = flipper_i32hex_file_open_read(file_path); + + uint8_t data_read_flash[272] = {0}; + uint8_t data_read_hex[272] = {0}; + + uint32_t addr = avr_isp_chip_arr[instance->chip_arr_ind].flashoffset; + bool send_extended_addr = ((avr_isp_chip_arr[instance->chip_arr_ind].flashsize / 2) > 0x10000); + uint8_t extended_addr = 0; + + FlipperI32HexFileRet flipper_hex_ret = flipper_i32hex_file_i32hex_to_bin_get_data( + flipper_hex_flash, data_read_hex, sizeof(data_read_hex)); + + while(((flipper_hex_ret.status == FlipperI32HexFileStatusData) || + (flipper_hex_ret.status == FlipperI32HexFileStatusUdateAddr)) && + ret) { + switch(flipper_hex_ret.status) { + case FlipperI32HexFileStatusData: + + if(send_extended_addr) { + if(extended_addr <= ((addr >> 16) & 0xFF)) { + avr_isp_write_extended_addr(instance->avr_isp, extended_addr); + extended_addr = ((addr >> 16) & 0xFF) + 1; + } + } + + avr_isp_read_page( + instance->avr_isp, + STK_SET_FLASH_TYPE, + (uint16_t)addr, + flipper_hex_ret.data_size, + data_read_flash, + sizeof(data_read_flash)); + + if(memcmp(data_read_hex, data_read_flash, flipper_hex_ret.data_size) != 0) { + ret = false; + + FURI_LOG_E(TAG, "Verification flash error"); + FURI_LOG_E(TAG, "Addr: 0x%04lX", addr); + for(uint32_t i = 0; i < flipper_hex_ret.data_size; i++) { + FURI_LOG_RAW_E("%02X ", data_read_hex[i]); + } + FURI_LOG_RAW_E("\r\n"); + for(uint32_t i = 0; i < flipper_hex_ret.data_size; i++) { + FURI_LOG_RAW_E("%02X ", data_read_flash[i]); + } + FURI_LOG_RAW_E("\r\n"); + } + + addr += flipper_hex_ret.data_size / 2; + instance->progress_flash = + (float)(addr) / ((float)avr_isp_chip_arr[instance->chip_arr_ind].flashsize / 2.0f); + break; + + case FlipperI32HexFileStatusUdateAddr: + addr = (data_read_hex[0] << 24 | data_read_hex[1] << 16) / 2; + break; + + default: + furi_crash(TAG " Incorrect status."); + break; + } + + flipper_hex_ret = flipper_i32hex_file_i32hex_to_bin_get_data( + flipper_hex_flash, data_read_hex, sizeof(data_read_hex)); + } + + flipper_i32hex_file_close(flipper_hex_flash); + instance->progress_flash = 1.0f; + + return ret; +} + +static bool + avr_isp_worker_rw_verification_eeprom(AvrIspWorkerRW* instance, const char* file_path) { + furi_assert(instance); + furi_assert(file_path); + + FURI_LOG_D(TAG, "Verification eeprom %s", file_path); + + instance->progress_eeprom = 0.0; + bool ret = true; + + FlipperI32HexFile* flipper_hex_eeprom = flipper_i32hex_file_open_read(file_path); + + uint8_t data_read_eeprom[272] = {0}; + uint8_t data_read_hex[272] = {0}; + + uint32_t addr = avr_isp_chip_arr[instance->chip_arr_ind].eepromoffset; + + FlipperI32HexFileRet flipper_hex_ret = flipper_i32hex_file_i32hex_to_bin_get_data( + flipper_hex_eeprom, data_read_hex, sizeof(data_read_hex)); + + while(((flipper_hex_ret.status == FlipperI32HexFileStatusData) || + (flipper_hex_ret.status == FlipperI32HexFileStatusUdateAddr)) && + ret) { + switch(flipper_hex_ret.status) { + case FlipperI32HexFileStatusData: + avr_isp_read_page( + instance->avr_isp, + STK_SET_EEPROM_TYPE, + (uint16_t)addr, + flipper_hex_ret.data_size, + data_read_eeprom, + sizeof(data_read_eeprom)); + + if(memcmp(data_read_hex, data_read_eeprom, flipper_hex_ret.data_size) != 0) { + ret = false; + FURI_LOG_E(TAG, "Verification eeprom error"); + FURI_LOG_E(TAG, "Addr: 0x%04lX", addr); + for(uint32_t i = 0; i < flipper_hex_ret.data_size; i++) { + FURI_LOG_RAW_E("%02X ", data_read_hex[i]); + } + FURI_LOG_RAW_E("\r\n"); + for(uint32_t i = 0; i < flipper_hex_ret.data_size; i++) { + FURI_LOG_RAW_E("%02X ", data_read_eeprom[i]); + } + FURI_LOG_RAW_E("\r\n"); + } + + addr += flipper_hex_ret.data_size; + instance->progress_eeprom = + (float)(addr) / ((float)avr_isp_chip_arr[instance->chip_arr_ind].eepromsize); + break; + + case FlipperI32HexFileStatusUdateAddr: + addr = (data_read_hex[0] << 24 | data_read_hex[1] << 16); + break; + + default: + furi_crash(TAG " Incorrect status."); + break; + } + + flipper_hex_ret = flipper_i32hex_file_i32hex_to_bin_get_data( + flipper_hex_eeprom, data_read_hex, sizeof(data_read_hex)); + } + + flipper_i32hex_file_close(flipper_hex_eeprom); + instance->progress_eeprom = 1.0f; + + return ret; +} + +bool avr_isp_worker_rw_verification( + AvrIspWorkerRW* instance, + const char* file_path, + const char* file_name) { + furi_assert(instance); + furi_assert(file_path); + furi_assert(file_name); + + FURI_LOG_D(TAG, "Verification chip"); + + instance->progress_flash = 0.0f; + instance->progress_eeprom = 0.0f; + FuriString* file_path_name = furi_string_alloc(); + + bool ret = false; + + if(avr_isp_auto_set_spi_speed_start_pmode(instance->avr_isp)) { + do { + furi_string_printf( + file_path_name, "%s/%s_%s", file_path, file_name, NAME_PATERN_FLASH_FILE); + if(!avr_isp_worker_rw_verification_flash( + instance, furi_string_get_cstr(file_path_name))) + break; + + if(avr_isp_chip_arr[instance->chip_arr_ind].eepromsize > 0) { + furi_string_printf( + file_path_name, "%s/%s_%s", file_path, file_name, NAME_PATERN_EEPROM_FILE); + + if(!avr_isp_worker_rw_verification_eeprom( + instance, furi_string_get_cstr(file_path_name))) + break; + } + ret = true; + } while(false); + avr_isp_end_pmode(instance->avr_isp); + furi_string_free(file_path_name); + } + return ret; +} + +void avr_isp_worker_rw_verification_start( + AvrIspWorkerRW* instance, + const char* file_path, + const char* file_name) { + furi_assert(instance); + + instance->file_path = file_path; + instance->file_name = file_name; + furi_thread_flags_set(furi_thread_get_id(instance->thread), AvrIspWorkerRWEvtVerification); +} + +static void avr_isp_worker_rw_write_flash(AvrIspWorkerRW* instance, const char* file_path) { + furi_assert(instance); + furi_check(instance->avr_isp); + + instance->progress_flash = 0.0; + + FURI_LOG_D(TAG, "Write Flash %s", file_path); + + uint8_t data[288] = {0}; + + FlipperI32HexFile* flipper_hex_flash = flipper_i32hex_file_open_read(file_path); + + uint32_t addr = avr_isp_chip_arr[instance->chip_arr_ind].flashoffset; + bool send_extended_addr = ((avr_isp_chip_arr[instance->chip_arr_ind].flashsize / 2) > 0x10000); + uint8_t extended_addr = 0; + + FlipperI32HexFileRet flipper_hex_ret = + flipper_i32hex_file_i32hex_to_bin_get_data(flipper_hex_flash, data, sizeof(data)); + + while((flipper_hex_ret.status == FlipperI32HexFileStatusData) || + (flipper_hex_ret.status == FlipperI32HexFileStatusUdateAddr)) { + switch(flipper_hex_ret.status) { + case FlipperI32HexFileStatusData: + + if(send_extended_addr) { + if(extended_addr <= ((addr >> 16) & 0xFF)) { + avr_isp_write_extended_addr(instance->avr_isp, extended_addr); + extended_addr = ((addr >> 16) & 0xFF) + 1; + } + } + + if(!avr_isp_write_page( + instance->avr_isp, + STK_SET_FLASH_TYPE, + avr_isp_chip_arr[instance->chip_arr_ind].flashsize, + (uint16_t)addr, + avr_isp_chip_arr[instance->chip_arr_ind].pagesize, + data, + flipper_hex_ret.data_size)) { + break; + } + addr += flipper_hex_ret.data_size / 2; + instance->progress_flash = + (float)(addr) / ((float)avr_isp_chip_arr[instance->chip_arr_ind].flashsize / 2.0f); + break; + + case FlipperI32HexFileStatusUdateAddr: + addr = (data[0] << 24 | data[1] << 16) / 2; + break; + + default: + furi_crash(TAG " Incorrect status."); + break; + } + + flipper_hex_ret = + flipper_i32hex_file_i32hex_to_bin_get_data(flipper_hex_flash, data, sizeof(data)); + } + + flipper_i32hex_file_close(flipper_hex_flash); + instance->progress_flash = 1.0f; +} + +static void avr_isp_worker_rw_write_eeprom(AvrIspWorkerRW* instance, const char* file_path) { + furi_assert(instance); + furi_check(instance->avr_isp); + + instance->progress_eeprom = 0.0; + uint8_t data[288] = {0}; + + FURI_LOG_D(TAG, "Write EEPROM %s", file_path); + + FlipperI32HexFile* flipper_hex_eeprom_read = flipper_i32hex_file_open_read(file_path); + + uint32_t addr = avr_isp_chip_arr[instance->chip_arr_ind].eepromoffset; + FlipperI32HexFileRet flipper_hex_ret = + flipper_i32hex_file_i32hex_to_bin_get_data(flipper_hex_eeprom_read, data, sizeof(data)); + + while((flipper_hex_ret.status == FlipperI32HexFileStatusData) || + (flipper_hex_ret.status == FlipperI32HexFileStatusUdateAddr)) { + switch(flipper_hex_ret.status) { + case FlipperI32HexFileStatusData: + if(!avr_isp_write_page( + instance->avr_isp, + STK_SET_EEPROM_TYPE, + avr_isp_chip_arr[instance->chip_arr_ind].eepromsize, + (uint16_t)addr, + avr_isp_chip_arr[instance->chip_arr_ind].eeprompagesize, + data, + flipper_hex_ret.data_size)) { + break; + } + addr += flipper_hex_ret.data_size; + instance->progress_eeprom = + (float)(addr) / ((float)avr_isp_chip_arr[instance->chip_arr_ind].eepromsize); + break; + + case FlipperI32HexFileStatusUdateAddr: + addr = data[0] << 24 | data[1] << 16; + break; + + default: + furi_crash(TAG " Incorrect status."); + break; + } + + flipper_hex_ret = flipper_i32hex_file_i32hex_to_bin_get_data( + flipper_hex_eeprom_read, data, sizeof(data)); + } + + flipper_i32hex_file_close(flipper_hex_eeprom_read); + instance->progress_eeprom = 1.0f; +} + +bool avr_isp_worker_rw_write_dump( + AvrIspWorkerRW* instance, + const char* file_path, + const char* file_name) { + furi_assert(instance); + furi_assert(file_path); + furi_assert(file_name); + + FURI_LOG_D(TAG, "Write dump chip"); + + instance->progress_flash = 0.0f; + instance->progress_eeprom = 0.0f; + bool ret = false; + + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* flipper_format = flipper_format_file_alloc(storage); + FuriString* file_path_name = furi_string_alloc(); + + FuriString* temp_str_1 = furi_string_alloc(); + FuriString* temp_str_2 = furi_string_alloc(); + uint32_t temp_data32; + + if(!avr_isp_worker_rw_detect_chip(instance)) { + FURI_LOG_E(TAG, "No detect AVR chip"); + } else { + //upload file with description + do { + furi_string_printf( + file_path_name, "%s/%s%s", file_path, file_name, AVR_ISP_APP_EXTENSION); + if(!flipper_format_file_open_existing( + flipper_format, furi_string_get_cstr(file_path_name))) { + FURI_LOG_E(TAG, "Error open file %s", furi_string_get_cstr(file_path_name)); + break; + } + + if(!flipper_format_read_header(flipper_format, temp_str_1, &temp_data32)) { + FURI_LOG_E(TAG, "Missing or incorrect header"); + break; + } + + if((!strcmp(furi_string_get_cstr(temp_str_1), AVR_ISP_APP_FILE_TYPE)) && + temp_data32 == AVR_ISP_APP_FILE_VERSION) { + } else { + FURI_LOG_E(TAG, "Type or version mismatch"); + break; + } + + AvrIspSignature sig_read = {0}; + + if(!flipper_format_read_hex( + flipper_format, "Signature", (uint8_t*)&sig_read, sizeof(AvrIspSignature))) { + FURI_LOG_E(TAG, "Missing Signature"); + break; + } + + if(memcmp( + (uint8_t*)&instance->signature, (uint8_t*)&sig_read, sizeof(AvrIspSignature)) != + 0) { + FURI_LOG_E( + TAG, + "Wrong chip. Connected (%02X %02X %02X), read from file (%02X %02X %02X)", + instance->signature.vendor, + instance->signature.part_family, + instance->signature.part_number, + sig_read.vendor, + sig_read.part_family, + sig_read.part_number); + break; + } + + if(!flipper_format_read_string(flipper_format, "Dump_flash", temp_str_1)) { + FURI_LOG_E(TAG, "Missing Dump_flash"); + break; + } + + //may not be + flipper_format_read_string(flipper_format, "Dump_eeprom", temp_str_2); + ret = true; + } while(false); + } + flipper_format_free(flipper_format); + furi_record_close(RECORD_STORAGE); + + if(ret) { + do { + //checking .hex files for errors + + furi_string_printf( + file_path_name, "%s/%s", file_path, furi_string_get_cstr(temp_str_1)); + + FURI_LOG_D(TAG, "Check flash file"); + FlipperI32HexFile* flipper_hex_flash_read = + flipper_i32hex_file_open_read(furi_string_get_cstr(file_path_name)); + if(flipper_i32hex_file_check(flipper_hex_flash_read)) { + FURI_LOG_D(TAG, "Check flash file: OK"); + } else { + FURI_LOG_E(TAG, "Check flash file: Error"); + ret = false; + } + flipper_i32hex_file_close(flipper_hex_flash_read); + + if(furi_string_size(temp_str_2) > 4) { + furi_string_printf( + file_path_name, "%s/%s", file_path, furi_string_get_cstr(temp_str_2)); + + FURI_LOG_D(TAG, "Check eeprom file"); + FlipperI32HexFile* flipper_hex_eeprom_read = + flipper_i32hex_file_open_read(furi_string_get_cstr(file_path_name)); + if(flipper_i32hex_file_check(flipper_hex_eeprom_read)) { + FURI_LOG_D(TAG, "Check eeprom file: OK"); + } else { + FURI_LOG_E(TAG, "Check eeprom file: Error"); + ret = false; + } + flipper_i32hex_file_close(flipper_hex_eeprom_read); + } + + if(!ret) break; + ret = false; + + //erase chip + FURI_LOG_D(TAG, "Erase chip"); + if(!avr_isp_erase_chip(instance->avr_isp)) { + FURI_LOG_E(TAG, "Erase chip: Error"); + break; + } + + if(!avr_isp_auto_set_spi_speed_start_pmode(instance->avr_isp)) { + FURI_LOG_E(TAG, "Well, I managed to enter the mod program"); + break; + } + + //write flash + furi_string_printf( + file_path_name, "%s/%s", file_path, furi_string_get_cstr(temp_str_1)); + avr_isp_worker_rw_write_flash(instance, furi_string_get_cstr(file_path_name)); + + //write eeprom + if(furi_string_size(temp_str_2) > 4) { + furi_string_printf( + file_path_name, "%s/%s", file_path, furi_string_get_cstr(temp_str_2)); + avr_isp_worker_rw_write_eeprom(instance, furi_string_get_cstr(file_path_name)); + } + ret = true; + avr_isp_end_pmode(instance->avr_isp); + } while(false); + } + + furi_string_free(file_path_name); + furi_string_free(temp_str_1); + furi_string_free(temp_str_2); + + return ret; +} + +void avr_isp_worker_rw_write_dump_start( + AvrIspWorkerRW* instance, + const char* file_path, + const char* file_name) { + furi_assert(instance); + + instance->file_path = file_path; + instance->file_name = file_name; + furi_thread_flags_set(furi_thread_get_id(instance->thread), AvrIspWorkerRWEvtWriting); +} + +bool avr_isp_worker_rw_write_fuse( + AvrIspWorkerRW* instance, + const char* file_path, + const char* file_name) { + furi_assert(instance); + furi_assert(file_path); + furi_assert(file_name); + + FURI_LOG_D(TAG, "Write fuse chip"); + + bool ret = false; + uint8_t lfuse; + uint8_t hfuse; + uint8_t efuse; + uint8_t lock; + + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* flipper_format = flipper_format_file_alloc(storage); + FuriString* temp_str = furi_string_alloc(); + + uint32_t temp_data32; + + if(!avr_isp_worker_rw_detect_chip(instance)) { + FURI_LOG_E(TAG, "No detect AVR chip"); + } else { + //upload file with description + do { + furi_string_printf(temp_str, "%s/%s%s", file_path, file_name, AVR_ISP_APP_EXTENSION); + if(!flipper_format_file_open_existing(flipper_format, furi_string_get_cstr(temp_str))) { + FURI_LOG_E(TAG, "Error open file %s", furi_string_get_cstr(temp_str)); + break; + } + + if(!flipper_format_read_header(flipper_format, temp_str, &temp_data32)) { + FURI_LOG_E(TAG, "Missing or incorrect header"); + break; + } + + if((!strcmp(furi_string_get_cstr(temp_str), AVR_ISP_APP_FILE_TYPE)) && + temp_data32 == AVR_ISP_APP_FILE_VERSION) { + } else { + FURI_LOG_E(TAG, "Type or version mismatch"); + break; + } + + AvrIspSignature sig_read = {0}; + + if(!flipper_format_read_hex( + flipper_format, "Signature", (uint8_t*)&sig_read, sizeof(AvrIspSignature))) { + FURI_LOG_E(TAG, "Missing Signature"); + break; + } + + if(memcmp( + (uint8_t*)&instance->signature, (uint8_t*)&sig_read, sizeof(AvrIspSignature)) != + 0) { + FURI_LOG_E( + TAG, + "Wrong chip. Connected (%02X %02X %02X), read from file (%02X %02X %02X)", + instance->signature.vendor, + instance->signature.part_family, + instance->signature.part_number, + sig_read.vendor, + sig_read.part_family, + sig_read.part_number); + break; + } + + if(avr_isp_chip_arr[instance->chip_arr_ind].nfuses > 0) { + if(!flipper_format_read_hex(flipper_format, "Lfuse", &lfuse, 1)) { + FURI_LOG_E(TAG, "Missing Lfuse"); + break; + } + } + if(avr_isp_chip_arr[instance->chip_arr_ind].nfuses > 1) { + if(!flipper_format_read_hex(flipper_format, "Hfuse", &hfuse, 1)) { + FURI_LOG_E(TAG, "Missing Hfuse"); + break; + } + } + if(avr_isp_chip_arr[instance->chip_arr_ind].nfuses > 2) { + if(!flipper_format_read_hex(flipper_format, "Efuse", &efuse, 1)) { + FURI_LOG_E(TAG, "Missing Efuse"); + break; + } + } + if(avr_isp_chip_arr[instance->chip_arr_ind].nlocks == 1) { + if(!flipper_format_read_hex(flipper_format, "Lock", &lock, 1)) { + FURI_LOG_E(TAG, "Missing Lock"); + break; + } + } + + if(!avr_isp_auto_set_spi_speed_start_pmode(instance->avr_isp)) { + FURI_LOG_E(TAG, "Well, I managed to enter the mod program"); + break; + } + + ret = true; + + if(avr_isp_chip_arr[instance->chip_arr_ind].nfuses > 0) { + if(instance->lfuse != lfuse) { + if(!avr_isp_write_fuse_low(instance->avr_isp, lfuse)) { + FURI_LOG_E(TAG, "Write Lfuse: error"); + ret = false; + } + } + } + if(avr_isp_chip_arr[instance->chip_arr_ind].nfuses > 1) { + if(instance->hfuse != hfuse) { + if(!avr_isp_write_fuse_high(instance->avr_isp, hfuse)) { + FURI_LOG_E(TAG, "Write Hfuse: error"); + ret = false; + } + } + } + if(avr_isp_chip_arr[instance->chip_arr_ind].nfuses > 2) { + if(instance->efuse != efuse) { + if(!avr_isp_write_fuse_extended(instance->avr_isp, efuse)) { + FURI_LOG_E(TAG, "Write Efuse: error"); + ret = false; + } + } + } + + if(avr_isp_chip_arr[instance->chip_arr_ind].nlocks == 1) { + FURI_LOG_D(TAG, "Write lock byte"); + if(instance->lock != lock) { + if(!avr_isp_write_lock_byte(instance->avr_isp, lock)) { + FURI_LOG_E(TAG, "Write Lock byte: error"); + ret = false; + } + } + } + avr_isp_end_pmode(instance->avr_isp); + } while(false); + } + + flipper_format_free(flipper_format); + furi_record_close(RECORD_STORAGE); + furi_string_free(temp_str); + return ret; +} + +void avr_isp_worker_rw_write_fuse_start( + AvrIspWorkerRW* instance, + const char* file_path, + const char* file_name) { + furi_assert(instance); + + instance->file_path = file_path; + instance->file_name = file_name; + furi_thread_flags_set(furi_thread_get_id(instance->thread), AvrIspWorkerRWEvtWritingFuse); +} \ No newline at end of file diff --git a/applications/external/avr_isp_programmer/helpers/avr_isp_worker_rw.h b/applications/external/avr_isp_programmer/helpers/avr_isp_worker_rw.h new file mode 100644 index 00000000000..2c52a8700d5 --- /dev/null +++ b/applications/external/avr_isp_programmer/helpers/avr_isp_worker_rw.h @@ -0,0 +1,99 @@ +#pragma once + +#include + +typedef struct AvrIspWorkerRW AvrIspWorkerRW; + +typedef void (*AvrIspWorkerRWCallback)( + void* context, + const char* name, + bool detect_chip, + uint32_t flash_size); + +typedef enum { + AvrIspWorkerRWStatusILDE = 0, + AvrIspWorkerRWStatusEndReading = 1, + AvrIspWorkerRWStatusEndVerification = 2, + AvrIspWorkerRWStatusEndWriting = 3, + AvrIspWorkerRWStatusEndWritingFuse = 4, + + AvrIspWorkerRWStatusErrorReading = (-1), + AvrIspWorkerRWStatusErrorVerification = (-2), + AvrIspWorkerRWStatusErrorWriting = (-3), + AvrIspWorkerRWStatusErrorWritingFuse = (-4), + + AvrIspWorkerRWStatusReserved = 0x7FFFFFFF, ///< Prevents enum down-size compiler optimization. +} AvrIspWorkerRWStatus; + +typedef void (*AvrIspWorkerRWStatusCallback)(void* context, AvrIspWorkerRWStatus status); + +AvrIspWorkerRW* avr_isp_worker_rw_alloc(void* context); + +void avr_isp_worker_rw_free(AvrIspWorkerRW* instance); + +void avr_isp_worker_rw_start(AvrIspWorkerRW* instance); + +void avr_isp_worker_rw_stop(AvrIspWorkerRW* instance); + +bool avr_isp_worker_rw_is_running(AvrIspWorkerRW* instance); + +void avr_isp_worker_rw_set_callback( + AvrIspWorkerRW* instance, + AvrIspWorkerRWCallback callback, + void* context); + +void avr_isp_worker_rw_set_callback_status( + AvrIspWorkerRW* instance, + AvrIspWorkerRWStatusCallback callback_status, + void* context_status); + +bool avr_isp_worker_rw_detect_chip(AvrIspWorkerRW* instance); + +float avr_isp_worker_rw_get_progress_flash(AvrIspWorkerRW* instance); + +float avr_isp_worker_rw_get_progress_eeprom(AvrIspWorkerRW* instance); + +bool avr_isp_worker_rw_read_dump( + AvrIspWorkerRW* instance, + const char* file_path, + const char* file_name); + +void avr_isp_worker_rw_read_dump_start( + AvrIspWorkerRW* instance, + const char* file_path, + const char* file_name); + +bool avr_isp_worker_rw_verification( + AvrIspWorkerRW* instance, + const char* file_path, + const char* file_name); + +void avr_isp_worker_rw_verification_start( + AvrIspWorkerRW* instance, + const char* file_path, + const char* file_name); + +bool avr_isp_worker_rw_check_hex( + AvrIspWorkerRW* instance, + const char* file_path, + const char* file_name); + +bool avr_isp_worker_rw_write_dump( + AvrIspWorkerRW* instance, + const char* file_path, + const char* file_name); + +void avr_isp_worker_rw_write_dump_start( + AvrIspWorkerRW* instance, + const char* file_path, + const char* file_name); + +bool avr_isp_worker_rw_write_fuse( + AvrIspWorkerRW* instance, + const char* file_path, + const char* file_name); + +void avr_isp_worker_rw_write_fuse_start( + AvrIspWorkerRW* instance, + const char* file_path, + const char* file_name); \ No newline at end of file diff --git a/applications/external/avr_isp_programmer/helpers/flipper_i32hex_file.c b/applications/external/avr_isp_programmer/helpers/flipper_i32hex_file.c new file mode 100644 index 00000000000..a8c20a0bd94 --- /dev/null +++ b/applications/external/avr_isp_programmer/helpers/flipper_i32hex_file.c @@ -0,0 +1,321 @@ +#include "flipper_i32hex_file.h" +#include +#include +#include +#include +#include + +//https://en.wikipedia.org/wiki/Intel_HEX + +#define TAG "FlipperI32HexFile" + +#define COUNT_BYTE_PAYLOAD 32 //how much payload will be used + +#define I32HEX_TYPE_DATA 0x00 +#define I32HEX_TYPE_END_OF_FILE 0x01 +#define I32HEX_TYPE_EXT_LINEAR_ADDR 0x04 +#define I32HEX_TYPE_START_LINEAR_ADDR 0x05 + +struct FlipperI32HexFile { + uint32_t addr; + uint32_t addr_last; + Storage* storage; + Stream* stream; + FuriString* str_data; + FlipperI32HexFileStatus file_open; +}; + +FlipperI32HexFile* flipper_i32hex_file_open_write(const char* name, uint32_t start_addr) { + furi_assert(name); + + FlipperI32HexFile* instance = malloc(sizeof(FlipperI32HexFile)); + instance->addr = start_addr; + instance->addr_last = 0; + instance->storage = furi_record_open(RECORD_STORAGE); + instance->stream = file_stream_alloc(instance->storage); + + if(file_stream_open(instance->stream, name, FSAM_WRITE, FSOM_CREATE_ALWAYS)) { + instance->file_open = FlipperI32HexFileStatusOpenFileWrite; + FURI_LOG_D(TAG, "Open write file %s", name); + } else { + FURI_LOG_E(TAG, "Failed to open file %s", name); + instance->file_open = FlipperI32HexFileStatusErrorNoOpenFile; + } + instance->str_data = furi_string_alloc(instance->storage); + + return instance; +} + +FlipperI32HexFile* flipper_i32hex_file_open_read(const char* name) { + furi_assert(name); + + FlipperI32HexFile* instance = malloc(sizeof(FlipperI32HexFile)); + instance->addr = 0; + instance->addr_last = 0; + instance->storage = furi_record_open(RECORD_STORAGE); + instance->stream = file_stream_alloc(instance->storage); + + if(file_stream_open(instance->stream, name, FSAM_READ, FSOM_OPEN_EXISTING)) { + instance->file_open = FlipperI32HexFileStatusOpenFileRead; + FURI_LOG_D(TAG, "Open read file %s", name); + } else { + FURI_LOG_E(TAG, "Failed to open file %s", name); + instance->file_open = FlipperI32HexFileStatusErrorNoOpenFile; + } + instance->str_data = furi_string_alloc(instance->storage); + + return instance; +} + +void flipper_i32hex_file_close(FlipperI32HexFile* instance) { + furi_assert(instance); + + furi_string_free(instance->str_data); + file_stream_close(instance->stream); + stream_free(instance->stream); + furi_record_close(RECORD_STORAGE); +} + +FlipperI32HexFileRet flipper_i32hex_file_bin_to_i32hex_set_data( + FlipperI32HexFile* instance, + uint8_t* data, + uint32_t data_size) { + furi_assert(instance); + furi_assert(data); + + FlipperI32HexFileRet ret = {.status = FlipperI32HexFileStatusOK, .data_size = 0}; + if(instance->file_open != FlipperI32HexFileStatusOpenFileWrite) { + ret.status = FlipperI32HexFileStatusErrorFileWrite; + } + uint8_t count_byte = 0; + uint32_t ind = 0; + uint8_t crc = 0; + + furi_string_reset(instance->str_data); + + if((instance->addr_last & 0xFF0000) < (instance->addr & 0xFF0000)) { + crc = 0x02 + 0x04 + ((instance->addr >> 24) & 0xFF) + ((instance->addr >> 16) & 0xFF); + crc = 0x01 + ~crc; + //I32HEX_TYPE_EXT_LINEAR_ADDR + furi_string_cat_printf( + instance->str_data, ":02000004%04lX%02X\r\n", (instance->addr >> 16), crc); + instance->addr_last = instance->addr; + } + + while(ind < data_size) { + if((ind + COUNT_BYTE_PAYLOAD) > data_size) { + count_byte = data_size - ind; + } else { + count_byte = COUNT_BYTE_PAYLOAD; + } + //I32HEX_TYPE_DATA + furi_string_cat_printf( + instance->str_data, ":%02X%04lX00", count_byte, (instance->addr & 0xFFFF)); + crc = count_byte + ((instance->addr >> 8) & 0xFF) + (instance->addr & 0xFF); + + for(uint32_t i = 0; i < count_byte; i++) { + furi_string_cat_printf(instance->str_data, "%02X", *data); + crc += *data++; + } + crc = 0x01 + ~crc; + furi_string_cat_printf(instance->str_data, "%02X\r\n", crc); + + ind += count_byte; + instance->addr += count_byte; + } + if(instance->file_open) stream_write_string(instance->stream, instance->str_data); + return ret; +} + +FlipperI32HexFileRet flipper_i32hex_file_bin_to_i32hex_set_end_line(FlipperI32HexFile* instance) { + furi_assert(instance); + + FlipperI32HexFileRet ret = {.status = FlipperI32HexFileStatusOK, .data_size = 0}; + if(instance->file_open != FlipperI32HexFileStatusOpenFileWrite) { + ret.status = FlipperI32HexFileStatusErrorFileWrite; + } + furi_string_reset(instance->str_data); + //I32HEX_TYPE_END_OF_FILE + furi_string_cat_printf(instance->str_data, ":00000001FF\r\n"); + if(instance->file_open) stream_write_string(instance->stream, instance->str_data); + return ret; +} + +void flipper_i32hex_file_bin_to_i32hex_set_addr(FlipperI32HexFile* instance, uint32_t addr) { + furi_assert(instance); + + instance->addr = addr; +} + +const char* flipper_i32hex_file_get_string(FlipperI32HexFile* instance) { + furi_assert(instance); + + return furi_string_get_cstr(instance->str_data); +} + +static FlipperI32HexFileRet flipper_i32hex_file_parse_line( + FlipperI32HexFile* instance, + const char* str, + uint8_t* data, + uint32_t data_size) { + furi_assert(instance); + furi_assert(data); + + char* str1; + uint32_t data_wrire_ind = 0; + uint32_t data_len = 0; + FlipperI32HexFileRet ret = {.status = FlipperI32HexFileStatusErrorData, .data_size = 0}; + + //Search for start of data I32HEX + str1 = strstr(str, ":"); + do { + if(str1 == NULL) { + ret.status = FlipperI32HexFileStatusErrorData; + break; + } + str1++; + if(!hex_char_to_uint8(*str1, str1[1], data + data_wrire_ind)) { + ret.status = FlipperI32HexFileStatusErrorData; + break; + } + str1++; + if(++data_wrire_ind > data_size) { + ret.status = FlipperI32HexFileStatusErrorOverflow; + break; + } + data_len = 5 + data[0]; // +5 bytes per header and crc + while(data_len > data_wrire_ind) { + str1++; + if(!hex_char_to_uint8(*str1, str1[1], data + data_wrire_ind)) { + ret.status = FlipperI32HexFileStatusErrorData; + break; + } + str1++; + if(++data_wrire_ind > data_size) { + ret.status = FlipperI32HexFileStatusErrorOverflow; + break; + } + } + ret.status = FlipperI32HexFileStatusOK; + ret.data_size = data_wrire_ind; + + } while(0); + return ret; +} + +static bool flipper_i32hex_file_check_data(uint8_t* data, uint32_t data_size) { + furi_assert(data); + + uint8_t crc = 0; + uint32_t data_read_ind = 0; + if(data[0] > data_size) return false; + while(data_read_ind < data_size - 1) { + crc += data[data_read_ind++]; + } + return data[data_size - 1] == ((1 + ~crc) & 0xFF); +} + +static FlipperI32HexFileRet flipper_i32hex_file_parse( + FlipperI32HexFile* instance, + const char* str, + uint8_t* data, + uint32_t data_size) { + furi_assert(instance); + furi_assert(data); + + FlipperI32HexFileRet ret = flipper_i32hex_file_parse_line(instance, str, data, data_size); + + if((ret.status == FlipperI32HexFileStatusOK) && (ret.data_size > 4)) { + switch(data[3]) { + case I32HEX_TYPE_DATA: + if(flipper_i32hex_file_check_data(data, ret.data_size)) { + ret.data_size -= 5; + memcpy(data, data + 4, ret.data_size); + ret.status = FlipperI32HexFileStatusData; + } else { + ret.status = FlipperI32HexFileStatusErrorCrc; + ret.data_size = 0; + } + break; + case I32HEX_TYPE_END_OF_FILE: + if(flipper_i32hex_file_check_data(data, ret.data_size)) { + ret.status = FlipperI32HexFileStatusEofFile; + ret.data_size = 0; + } else { + ret.status = FlipperI32HexFileStatusErrorCrc; + ret.data_size = 0; + } + break; + case I32HEX_TYPE_EXT_LINEAR_ADDR: + if(flipper_i32hex_file_check_data(data, ret.data_size)) { + data[0] = data[4]; + data[1] = data[5]; + data[3] = 0; + data[4] = 0; + ret.status = FlipperI32HexFileStatusUdateAddr; + ret.data_size = 4; + } else { + ret.status = FlipperI32HexFileStatusErrorCrc; + ret.data_size = 0; + } + break; + case I32HEX_TYPE_START_LINEAR_ADDR: + ret.status = FlipperI32HexFileStatusErrorUnsupportedCommand; + ret.data_size = 0; + break; + default: + ret.status = FlipperI32HexFileStatusErrorUnsupportedCommand; + ret.data_size = 0; + break; + } + } else { + ret.status = FlipperI32HexFileStatusErrorData; + ret.data_size = 0; + } + return ret; +} + +bool flipper_i32hex_file_check(FlipperI32HexFile* instance) { + furi_assert(instance); + + uint32_t data_size = 280; + uint8_t data[280] = {0}; + bool ret = true; + + if(instance->file_open != FlipperI32HexFileStatusOpenFileRead) { + FURI_LOG_E(TAG, "File is not open"); + ret = false; + } else { + stream_rewind(instance->stream); + + while(stream_read_line(instance->stream, instance->str_data)) { + FlipperI32HexFileRet parse_ret = flipper_i32hex_file_parse( + instance, furi_string_get_cstr(instance->str_data), data, data_size); + + if(parse_ret.status < 0) { + ret = false; + } + } + stream_rewind(instance->stream); + } + return ret; +} + +FlipperI32HexFileRet flipper_i32hex_file_i32hex_to_bin_get_data( + FlipperI32HexFile* instance, + uint8_t* data, + uint32_t data_size) { + furi_assert(instance); + furi_assert(data); + + FlipperI32HexFileRet ret = {.status = FlipperI32HexFileStatusOK, .data_size = 0}; + if(instance->file_open != FlipperI32HexFileStatusOpenFileRead) { + ret.status = FlipperI32HexFileStatusErrorFileRead; + } else { + stream_read_line(instance->stream, instance->str_data); + ret = flipper_i32hex_file_parse( + instance, furi_string_get_cstr(instance->str_data), data, data_size); + } + + return ret; +} \ No newline at end of file diff --git a/applications/external/avr_isp_programmer/helpers/flipper_i32hex_file.h b/applications/external/avr_isp_programmer/helpers/flipper_i32hex_file.h new file mode 100644 index 00000000000..765b94beb59 --- /dev/null +++ b/applications/external/avr_isp_programmer/helpers/flipper_i32hex_file.h @@ -0,0 +1,55 @@ +#pragma once + +#include + +typedef struct FlipperI32HexFile FlipperI32HexFile; + +typedef enum { + FlipperI32HexFileStatusOK = 0, + FlipperI32HexFileStatusData = 2, + FlipperI32HexFileStatusUdateAddr = 3, + FlipperI32HexFileStatusEofFile = 4, + FlipperI32HexFileStatusOpenFileWrite = 5, + FlipperI32HexFileStatusOpenFileRead = 6, + + // Errors + FlipperI32HexFileStatusErrorCrc = (-1), + FlipperI32HexFileStatusErrorOverflow = (-2), + FlipperI32HexFileStatusErrorData = (-3), + FlipperI32HexFileStatusErrorUnsupportedCommand = (-4), + FlipperI32HexFileStatusErrorNoOpenFile = (-5), + FlipperI32HexFileStatusErrorFileWrite = (-6), + FlipperI32HexFileStatusErrorFileRead = (-7), + + FlipperI32HexFileStatusReserved = + 0x7FFFFFFF, ///< Prevents enum down-size compiler optimization. +} FlipperI32HexFileStatus; + +typedef struct { + FlipperI32HexFileStatus status; + uint32_t data_size; +} FlipperI32HexFileRet; + +FlipperI32HexFile* flipper_i32hex_file_open_write(const char* name, uint32_t start_addr); + +FlipperI32HexFile* flipper_i32hex_file_open_read(const char* name); + +void flipper_i32hex_file_close(FlipperI32HexFile* instance); + +FlipperI32HexFileRet flipper_i32hex_file_bin_to_i32hex_set_data( + FlipperI32HexFile* instance, + uint8_t* data, + uint32_t data_size); + +FlipperI32HexFileRet flipper_i32hex_file_bin_to_i32hex_set_end_line(FlipperI32HexFile* instance); + +const char* flipper_i32hex_file_get_string(FlipperI32HexFile* instance); + +void flipper_i32hex_file_bin_to_i32hex_set_addr(FlipperI32HexFile* instance, uint32_t addr); + +bool flipper_i32hex_file_check(FlipperI32HexFile* instance); + +FlipperI32HexFileRet flipper_i32hex_file_i32hex_to_bin_get_data( + FlipperI32HexFile* instance, + uint8_t* data, + uint32_t data_size); \ No newline at end of file diff --git a/applications/external/avr_isp_programmer/images/avr_app_icon_10x10.png b/applications/external/avr_isp_programmer/images/avr_app_icon_10x10.png new file mode 100644 index 0000000000000000000000000000000000000000..533787fe3569f70196d3f71e8373102dfd3967a0 GIT binary patch literal 3614 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 Vpqb^9w@m;7?5&;gRaoD1{{|C}E&c!i literal 0 HcmV?d00001 diff --git a/applications/external/avr_isp_programmer/images/avr_wiring.png b/applications/external/avr_isp_programmer/images/avr_wiring.png new file mode 100644 index 0000000000000000000000000000000000000000..957012405127ce4504ef8cd4e805b1fc31963026 GIT binary patch literal 4513 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 literal 0 HcmV?d00001 diff --git a/applications/external/avr_isp_programmer/images/chif_not_found_83x37.png b/applications/external/avr_isp_programmer/images/chif_not_found_83x37.png new file mode 100644 index 0000000000000000000000000000000000000000..b03bf3567ade9b2e4ce2d29328032ebd40c89401 GIT binary patch literal 3742 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 literal 0 HcmV?d00001 diff --git a/applications/external/avr_isp_programmer/images/chip_error_70x22.png b/applications/external/avr_isp_programmer/images/chip_error_70x22.png new file mode 100644 index 0000000000000000000000000000000000000000..16f81178c0ec857591db3fe40830a5955db54e73 GIT binary patch literal 3688 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 literal 0 HcmV?d00001 diff --git a/applications/external/avr_isp_programmer/images/chip_long_70x22.png b/applications/external/avr_isp_programmer/images/chip_long_70x22.png new file mode 100644 index 0000000000000000000000000000000000000000..3edfff82de952d6f49a012154a23104e3fd935fa GIT binary patch literal 3656 zcmaJ@c{o&iA3q}dR#`&2V+e^^Eq5kknHjqwjEuAxGh<8|Gse_dB9bj#lCnlxWLHrn zlr5AHrR=gLb@5(HlHizZSI7I`1!2T>3I?-iX0kb^3h_#CiziP*F zmKOy%W8=f+k~DSH#AIz_)o%95JJs*7un|q@1!evQM^}VLhV*UTjnvQs+zN~M<>S81RuB0NO({6*Z{AbYhtY!na38Ire=Gt3|jLFr0}2z{9k z3$FkmCrO^4?ZSFshjeL2hhaj6^a;Js&xAL@US8uHlbuCuGXNOnhIMV|Ld%uI4+@7f zH*W2l74kVQk#l-E-n&f3>=BSN-S4)*-l~no&C6ANeUlRty|ztQ5AsX5&<%RSi8{CS zQ{Tdj*Or$)JRQ@BKpcy(5?cAt@M_UMcTeXPu?t><9}}(CDkV18RNsJ`Y`m&SI&$Mq zJN*;z8J89ix!^eLmHp56b#GF~Ms!yNO-2lW`zK8VLX!0Ik5L4_+G)v>xOHR805D(8 zs(-63Dj4n)IoiqFoHJdw%Gn2md)r*`2Y};v4G8gNxoL|i0N`^Xbnct0EY|PVtrOl; zzkRS?V$IX=0#>7`0V|6Yr-tw0c>$Phl#DvUSMR$?a`eOyWE|Sy}L>1GcR@CaPg?7ekfL_GPIf3nx46NbK7l|NO zYt?xSXB#T!sO6KSgRKDK{91I475r*MnG@!%_AJ{Gy-gTPA|K zstY>M8a0tM(KvyeP?=Dh_YlwWGV{N);xeY~{PLu&(xmL9{-iK14PowjJHvS>|0Z#V zLE;f?$;}GqdrmR=yYx?IpxPr9Z0vGNZe4q$?4#(j%((Z7`(($^wY?6huid)arma4u zeiB^dNlHb_N4CV$wUsh=i|nQ=@pj)!v%jnKCSIw92s46zNt;TSNoTo|bSiYt$|t=P zzh-+)^O}kdlvq%Bw{W;n!gay5jhI+)+$FTs(iQ14ULf{1rO34~>(Cb$6&HHJ!Tgv) zdOnM2dMC_%Jx>4%uSQ6lx7cbO)v}@|c5Kg@a_Ms!$`j91AYjl-rI143 zT$P*Ec-}L=yxFwur^myy?OA!lLA6ug_k=>%iR;Yoc}rH3B;j&N4dDUFj@`!34g6Wg zs?e5!Kb&yK8qILIXo=b8)a;)64B&%fKyXunayd8N}4#^Hh+3)C$_y4GPQBhE-bbqo}c%Za`SrJO6 zdnwW@pO-eyCf6p1J_-G89U~$Y(Xhy5 zMUGeOYTMt$$a2YiV?|e_R|P~a#KyMgxyO**u%QG8h z@(1qC8qP9iV+L=$(!a4k+Z`G3y0I1a!D+I~RN}@pnD0n&m?O?Hg8pbq9ZG>Fxs|-X zUzy7*Tqe&cntV0k+!!|*H#QnZ47;CrWmH$$TG{5<$jUwuHG(^*zDeB--s}SM!uJW# z1>+*jBRsaPt^}V|dzN5|9-w_K>zgsZlv8CcZ=QI*k~$dD zQHR1ly?ZS}{z#5*43pG~iivWIHcep1l9apPsRq2RL0rHH{yRPeKb%R2JEHFC*&67W z6hclK_ZvOYe`4AU@pgaJL&_rAoU+@4g6NbQ`ki_@vNp32GnO?bF&?6r25mjY4!YUV zuo#u6PypGfi%v1Kk9GL<>c7lob@CN1?VI1l+m|37)S%ix2Sd9IyJCBBM|Ji(%vtKf2ty_Ee>COTUo;|z$2z@Tg4kynx~`(q2$2+0-n&-9Pp zXWEKsQDqy?{o*U3d#{PS@GZYwyxm<-yaIdo6Y+@ldmWK7I?c`dS$o_|R7z3yf%chK zBH$7F-$J*kPs4`>!paJo5`Rxay4+|F?KfYL@!|ZV^ znsG}l4Xf1*Ciq4iuYY;I{*i$17YSGK$*9mTgYRdKIg+66Bag`6qq9^@YY$XMR^X~`KQn$@L(6;7(SFdBc!#)1{7y8S?H+nWe!t?^HLDU*^Hu-%o&k@V z<#m%6PX}BDTnRniJ+xJu)$(Q2(zwFum6TQHu@VQS|4fTux8S;nx^%_+s<%C=-58>C z;=2Q1tfX6hdAgA`$J3KClyd#;dh?h%8y_?=y(~7eyjKd{f96t1@uWA`B21>y@v|MdAc$@KZoOIg>lLc<{6 z20aIERfJ4YIz~>)u;!k~a!0!@Hshxb)*S3OI{%nEUp6qg%k8mS#y#{2=4b9_3c@m`)*$u{a3TC5HFLt*n>Pc{lORJ#z&T7JH~G@>vR#?e~u zXshnyY0Z|@IM$q4G@CK+!wtpsn0jms_RbBSJ6XreS?C(HS{9Cq?A%CNN|eEEPfSm2 zicRS)X3Z!*xNOfsG3Oe0f+{9n+F0YFfjK_qcW1bZ}v z#e|TzFpxkdo6iOSW79x3nc_?1g1l&Sh93qzSN#kOVo)()HvQY3q^PIEC}ez1RK!DRm<>lg5MrT8_229nuOI0Uwp)ej(n@c*Gq=0E5Ft~2dF z@%~TY0AdiE26d(duugL*{N8!1Z@FTlaU2?%%i<7OtW!S0{(xN-(9(Sx95Xm>NsTk}XA2)`-f!R1^ti z%NnATUACkyzSj~r+i%=^yT9)r_kPdoob!2}&+B==pZD{8ocpU1bMk{A-O~I03dAUg~cAT!eT*87K7?_o&o^=gBeaVg43)ldUbReV-p>6 z+lJvNBM5TD#D+*GsA?z)Nm@rMWe>3&@J zgXnAR>*GNWyg$^ee(v0Q_R(mjcqya2TcA!*G|5uOK`%tK0CRB9r|_1h=J6?rNvN<2 z6Oa@vCoB1FD)Rtq!6?)baGk(QfXDxxh#*jhPp^X=h}xF;ib*}m6LOWOj-7DSMleJg zFbRyqV0_fKQU{)?vOW<)OP}e0XQU(Z$0x*Z@h{FJ15OB6tS=k@B znhHppFS?+9J5nk+qrvS|Y8k3Z1z{HICaC2r;Nk)~sNQ8IcSKsBw2PEx0%-_HmDdi{ zmH4#u1^}`WWVqEXZTfeKmv(jO$5n`*(fay|e;e%XKDjmBUBom2fN^$k&z2^%e1C`` ze+Yf+{-Jq3&(k7V7gl4bWfCUOfUMa;mnG&-Z_Ki9Rt*eHPfhh(H(}gJ?Jk$MXborT zTsF`D9*o*pUHSBKLM2rDRHy~t+NXv$%eFZOx^D?xbszp5Z?RD+vb~}B4%}qrUPaW9 zo^+7%jl-o~U((J2$6#(9etoESn>;b5xz1}erUyJeXT%efpp}2hgZI0Qnk123H?ax; zi`9(!_v(VYA)evm-JIXt76oW`j@2<_#@ErI}m%L>(aY^tzazfZG{ z|3Llj;d+scv-(#tDoqU-NsKT#lidSvHgMUAu2_a=(Ebq=19iA-@wgY$E7 zG*jXSNiefsy(UzM&$Lx=FG*=In#cVbQ8`XfE;V9Jsos)LDpm#57A@@nwn@;lggag% zfSfHA7tyR;h^Uk1FA@w}0qwAj$Qldy?a(p@^n1}~*s6Sk{a4NJi@YVX;c4-*S?Ou1lrE%KBYj5orz!0Nv26VPco4}&x}VxAn;6iW2ycmggKEo$EX_;@jIbj>@x|1?jq$`;`;h2Fc!K z0*Kq1pd+mjQyEi@Q#w-$Q%Z|&!Wr%+z7N-&Ce$6<&sob)OHS)f^HWl^O`RX4IgaFK z6ZYuEpTLx4S2#X$h|1rqdm#>0Up&@TC{OK-=l z#h2tSyvrO>u}GQlmS~!~eEL3teKdK_zDFsxx$^H~pQA<6f~fOg2LRw(LxdDCFc%8e8Fj_%cbVdI!==XLhqA`oC`CKeREQ9q7@kC zM-|fY83f~p!LFMz{H~3*jrQ1w4p~pmOx84mL_Fln{WX=m#fl;?gz7b^KIt5|bWx)^ zWmB;_7F}47jlk+y>$sFVF5RXY3rwc?uH9wZ3C*bIB`*bE8{|U{C{EFuktFoyRxvujS zH9iq15Ux2y=M$&O%}X*$4t=ODsm|MzS7n!ISCsjI*7*3hinfY^O8Ljr{rp3v74(YB zB$~S%t@3qg<9uRm;^h~YZ)~Ck#G(eoixf{N2Kzl_Nh6OVN7K6Q&KqBTy__@)r4hR& zyZdz}EB1CAZt}`-N`GfTlcQDng)c?N#@K{)K$49h=?cvwt+i9u>=oZr^8R_Ne z4RiTJkLLB~z2>8a@4eBzcR15k$M0=pEB2GabdRyfy*n`PvEpERtbHi$*^DyO1DfDc z^6_zH4ySOHv><2n-H3H>(r6N8FseQ3dghHmU1e)!hkYX>^Gw7T_KNa0c{^~s2gnOK z#6#na2{jFM+qJ$HcuDD1oH25U^1W4p1%LVQR)F-G6x$dqsumFy;Sy;a$BZWK?|~=lae9Waeq*>FxFps@lt`>w-hyzF(EP;B$onhJ;e;j z?rK<$$dfIANNFOIOl+g=j^6%{sia1}?Da#7dpU>VgaaBB8)#r?kA6>dKlY@?LAymu z4Se9OUlVHd0#sh>Y6(|ha=#ExsDIQDD5FtasINL>+7U@bnMXS3 z-jufw-88tnaBq7~szGY}Rz*&vjf<8d@pEnQIYb%CH(*G3QfBv&$m9IQsOQ%zH0XWy zMRP96rNOnTfq3uG)Aj9P_0M>`zlk^tPe)w-HvDn!lsysZI`)k8BQit5NG9f5sq~Os zvdoo!^Mg(yb*tJLA!PYa5gs>t2cUh3@UQLRij@ub4!&&lFGVgrLu#m0_5om=^C zHUv%XR3EAiufSG4c!hdiL&NGSaJR~d=eh~EMdqn2ZLcHadms=SN94#?@G3Oh1n7#b9Z}T|Hi22!`IQk4U3^)B<|{>Tm!6^2yI@2vtjQNX^Y+0Gwx(u4u0LD+Son=h%cuQ{`9GG{t~9f|5QcH0{6Ul_h}u8xzn)H7_c${!Kly_K*MFM-`1pBmp0 zDHi!H^QaL5F=5QwoZZO7c9XkRGv&7KZ*`Q)$wGtI`o4Ya>PhLzPF_q-d_}*Mv-!2| zoBX4p=7#2jFWdZe;HQ_5ug}$UhB=B055^?yr!Il6sBe4z{$$1JZQgpKd}87@A8*Ri z#)97MFz$DyJll-Oc4AQ391@EHn35Up6p$62M58!TNaO(DHVO&=c-6fKL^cs`i}Ya7 zA*7uch(DdlMFRlJ*q=%A@TRaq?i8Ar4;s8s{R#~7BBQ}BdUzO~iKTdYSq8EwPJwm= zk3eq^1Q~2>1VZ^Exde0yn*{QwpZD=Y`lG>r@FKbP&NdVb`XhwxjRyZIiikf3!ZKJC zkO2g)jHBVvdC1VBhLJ< zbX*S&_GGh}NGO!U;XpV#5C)3|g(DCMC`=owt*yn4(DDoLVUzr|eEgJuGTNQGcK^vV6NCdtDrOgruFgrt5e*bLH$WgC>#RYsp@AS9{;~X>GZ#&{n(C_ z|JVDE#D0VTCI#w9@nc+Id2r8;s=SkmiNvxfBsPOZU@*@AY~(Rd2AkpM$zX!Cbs%t% zI-ca=<+HPM_zwskkF@gfW0QP5C{{Q$m`eij@**R#aG0(RLeCTnLtx=>Gn^?5ql+Gkhgj{durKe6P0(DGLuh=0XGxniI@XZv4g{d0>uKs)B&!^?I49)F4tcjj5# z;nuIe&HaG@_>b8V%((0J_IA#|y%Dapi|uIVv<*yG!mPPoofXcM;6GT?H!fZ$Da!Y0 zX$xBW8d*vV%UrQ2w_S|TxOtIRHavm;WE;HqbtZlw`-)`BDNO$x^HQ?ae{z4hYpFiu z)R;}Yp)`=zVQ8c)ec zcxR&Vca72o=5Oxhj3gMkE?xvC3`#Q!CbTNRQJuLXiZ2-YXq)`z<@{E5emj{!l`7OZ tqM<8NsC&lo-2MW+xr4Uu%d$`&en1N&q_;IOR>G|Xurjm5m153@{U2Vwjb;D< literal 0 HcmV?d00001 diff --git a/applications/external/avr_isp_programmer/images/dolphin_nice_96x59.png b/applications/external/avr_isp_programmer/images/dolphin_nice_96x59.png new file mode 100644 index 0000000000000000000000000000000000000000..a299d3630239b4486e249cc501872bed5996df3b GIT binary patch literal 2459 zcmbVO3s4i+8V(M(gEFORwSrA`4O0uPn|M|5y* zB*aMDxC&7(gP9JN;POOi-9khrC>Z9YJs2U!LnVcQEEC0fDtKo&ILlzb30%M}3J^;~ zv7RzcsilOs4Mq@tD*&R;!LMSk2A~{(`HK9|hQBqEX)3sQr9Je6SZU*F-^fD-p+~Hs; zHLkO%v?>ZoxEv+F#whudr%615FkA0DYR0tMEo}3OOY#xecLWe>xV?u5KtSmC^ z7)Fmj6gjfKstiEV-*Cxbbb+&rRWuI_rBJ)ybs_f1Rn&f2>q3pYwI^|J(hdn{j{0EZIm_F zpIyIWLsRUgOItR-dUbVd|6Zo=_BU_Tj4|{{jxO#=JH4o8er(5{!nZD_j4}MH&zh~9 zVLC~y(0-D6GO0ghZD8BYzP?o{>22~lT6^d@X{SwQ8vrNY-PPIMajIwC)`s14Ep72@ zeq7YOzM`?U{+W)ocXBr`eSOcpk?Rxc=ou5&)fWW|pD};-Z0mvk9}=&`Rb&y<77W~a z(>6YM;6Y5aIU~JKZ}mQZynKHiSTQ#Bczn@&jTiN^?vPJ(jhm7cXLx0oum5P$`TceG zU+wR;OO^)8CVlnM)5p$CO&e94KJt>HccCaHGusmW_b`T6m| z-R6V6Db1pErTot?^d22ojm+2>_)FbD`_+WbDGMx9f@hO27maS2`csiV(D&Fs`PS2& zvrq18du_&zXID(!KIxsU$)iuTYuZ?zmYiP&n&i@Be{IdbS-jA2c0QAlu5NXQv_0K< z3Hvs4eeu6B7yD&CNT~gIkMV&UkRU=V!iQ(+_(O&u^ah$+s{_yn(yBYeD40HeU{xGsIT6W Zfq!wOp!QjS=r55&&8K)tZJ>! zX9!|urpns_+3bLGhpWpJa7G1iR=7U<4q#?(qy>Qh$3$rnPo_4eDBe*|l7 zt*?E0IVl%{I3HrfzVWH??Kkt>Bi(nnZ@7%i#u{xs%!_xbh~O&bT^Ien|%u6t7Zn-j(gUnSv0WUO%}G04p`rhWCnG zY)p@^iEhU3vhKD~_HlseZgR&P04`wVAh`BQ-BvCDz-EUimFr7>YdEZ2&vB$-|40Mx zmb1nUv|Mu|S_sYK#ysNVe4->2tr*c+E~VrQeXl2_R&VVQkw6oGG}=8E(54CgByeRl zDAtB>v+K8U9U@2%MS)yy;bmjE#L~hyq#KOc58jpozljpImNAQ0H-_8X!h!9KrB<|k z_8}vk3}3{bZUYdZTM@NJ@WhY`Ywh=ZPchX6ni4k*@ALM!(c$T_qS+ZeK2IdHqcw8o zdWt;+hhlXwt+4vfhdEW7FT)@$P3Xs`l(`dJJ08oF@D;a6l%FkOtGT)6+WnZpelWzK zo?C;Rfd&(f>Ko(D@s=Nr3&2O@)D8@BYjU&Qux?b4NhmOTBLCvRkLJTJ2zVskSXp-9 zVC*5NP*4=6SyS%dO$_I(}mMxRqYvwdUm z@kfY+wMLN?#WN0b9wv!14nImY&l7)lTf7wq(}XXi&ZP;aQSI7?>s5sq+ z!4BIuIUJhIo2)Pot+O9roT_aB^SX*x`YTI&@fSy22~lsBf805E)laD=bz7?Dwsuir z4ickks%l?pvzq9x%Q=XXr$vvl-pRyW!YfO0g#N-LdJT>!bIMKar%|?;pP5%@P~)%}BB0-Ds^FwxM2hX&pE+kcXgiwElP_wajan;%6nW)J=G0&r zuPFITsaY>CFtg05`C|cfb3czzm3(p>!+c$bwO*@xQ?;a^t;1if zG3T4~Fu8;zLdwLA`08G*2mOYB7z##vwm416O_5v3Ef3^5)T*ilt@n_EG{Ld*@6;wSZnp8}m%X3(&s-=XVLptQ* z?arOAG%U?5Jw8xVT9bbuzuGdvvN&si)Kvbp>P=PQGx747j~v5gRphE`1d@vw>DlYD zrlo|sgljLZ{jsgh$sai=P%L#$D%kglk1*;iYAn6$?vn1c*WZ%op(K2_Q1?gGsj5RA zCz?GoZ8P2(k;F*VzG16Tw{Mz-c0f{eAQ_S^qiuE5rt~%M^Amx6Ynd698I6kt!;h9U zmOPgtNAA5ESBr8$NebGZ0cv;JAzvkt2!YSzW@am;nuUANu9-CiJ{c^pJyyBVS% z;<#^fBk-#9s~BC>F!6iE;G%wXcD25Uer#xI=uAVYv`5>Yai!AhbE#eNU7iBrXM#Tu z^l%bp3AdYq`4qwPk9AkV{%a znlIE|=(a%I9p3iiGw~*u&5j@;N@W_9%P+^b7FQ!DGbeecg2YmxZRcqLIbDt4!t+H7 zAqSOF$$I8dmZuW`r7xsZAR2vqH%`ERdbbRs&6P1#?_khn~!FovP9GUz+{9rstz7@CqB*_T_kOhP(}JensxW?oZ<&1&I%II-u+eQ&30sRan{Ms#kZC1!*QB- zm+$Q^9&9`~ai=Ob!pvSp3O`#{atT?Xh0V8(#3zNt&DCz*?tSj_vtue*jsnR=DYGd86#l`XC;a1QpDeC@HyDPdbSe(l zgjHdxAH33fUQ5h>)75!e7xxhN4fhkLvD7#El<;AL(z_%XRQp}+&;DV@+VyRnH!p|n zKz0`W?)}6~lg-L?-LjiS^Bc*VMPG3nk%&<-0 zbaZiiVf9w0ci_ud;Fi(wF~PfPS`GoGtGG9wL-V2U5=blE(V0n^*McEGMx2N5R|Ua?u5HlLtuj{xo@^N|O`lWhC_G<5l(K<(XSoco+TC5;ue{5Q8M+ASwLe?oA zByv*MXM27tAJgsDEuST}bAP9!OiUCSywSh#p{qBwHz#E!CE*qMYVP)z`UUYv!!3<1 zM_<12SA}2rc6M{Ific36T7EDtXf=Hmd|h$ZUH$OID6hDdM=@P0$o0suBePaK|(w=hS!Qppg)o(;sG zOk<$|Kug!3MsW2a(!nl7k|#x5X1V5-4A|36TgG190%k$O5IsDN1AU0LftEPeKrdIM zn~bgwSj!*9A|Mm#1h7B(GQ}6=uPyTzFN!7asi899zf9;}+A{wM3U6@+jG_7v!I}`b ziYp8T18X87L^lG$Mb(|)stiWJ5O64*b!)1?HBksv6dVcu`;uWf^l@`X*eMIcmI7An306gt6Qh2kswivdgYb@lP2(LJdY z@E#+9|u4GM?A_OkkAXkqccP08ectbOS=#Q(qt3hN`e%SS;1`3Ykcu|H8Wc7klcr*u8-u(^#IdL?2H-qMM-)l??tXYn12jV^RMt z-``lb-^ZfyTP&0n40Nxz|EJf#RICBo6aN`r*5;q_CsJ55@535yKkHgm)`!7y#vEtB zT6cGMa|iE@vZQ@<8%x_=VCEUj6aYYeCRlx(|InYQ3j$4GuJG99-9E$yK6D9-^!kkir((8ExON>}J~Ocpw}BbH6#MyjJ@7P6)WtmSMmDP-fQf-6whZkY`fn1t B&F}yK literal 0 HcmV?d00001 diff --git a/applications/external/avr_isp_programmer/images/link_waiting_77x56.png b/applications/external/avr_isp_programmer/images/link_waiting_77x56.png new file mode 100644 index 0000000000000000000000000000000000000000..d7d32aed59a19e24a7e1f2216c806872610a0af5 GIT binary patch literal 3883 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 + +//https://github.com/avrdudes/avrdude/blob/master/src/avrintel.c + +const AvrIspChipArr avr_isp_chip_arr[] = { // Value of -1 typically means unknown + //{mcu_name, mcuid, family, {sig, na, ture}, flstart, flsize, pgsiz, nb, bootsz, eestart, eesize, ep, rambeg, ramsiz, nf, nl, ni}, // Source + {"ATtiny4", 0, F_AVR8L, {0x1E, 0x8F, 0x0A}, 0, 0x00200, 0x010, 0, 0, 0, 0, 0, 0x0040, 0x0020, 1, 1, 10}, // atdf, avr-gcc 12.2.0, avrdude, boot size (manual) + {"ATtiny5", 1, F_AVR8L, {0x1E, 0x8F, 0x09}, 0, 0x00200, 0x010, 0, 0, 0, 0, 0, 0x0040, 0x0020, 1, 1, 11}, // atdf, avr-gcc 12.2.0, avrdude, boot size (manual) + {"ATtiny9", 2, F_AVR8L, {0x1E, 0x90, 0x08}, 0, 0x00400, 0x010, 0, 0, 0, 0, 0, 0x0040, 0x0020, 1, 1, 10}, // atdf, avr-gcc 12.2.0, avrdude, boot size (manual) + {"ATtiny10", 3, F_AVR8L, {0x1E, 0x90, 0x03}, 0, 0x00400, 0x010, 0, 0, 0, 0, 0, 0x0040, 0x0020, 1, 1, 11}, // atdf, avr-gcc 12.2.0, avrdude, boot size (manual) + {"ATtiny20", 4, F_AVR8L, {0x1E, 0x91, 0x0F}, 0, 0x00800, 0x020, 0, 0, 0, 0, 0, 0x0040, 0x0080, 1, 1, 17}, // atdf, avr-gcc 12.2.0, avrdude, boot size (manual) + {"ATtiny40", 5, F_AVR8L, {0x1E, 0x92, 0x0E}, 0, 0x01000, 0x040, 0, 0, 0, 0, 0, 0x0040, 0x0100, 1, 1, 18}, // atdf, avr-gcc 12.2.0, avrdude, boot size (manual) + {"ATtiny102", 6, F_AVR8L, {0x1E, 0x90, 0x0C}, 0, 0x00400, 0x010, 0, 0, 0, 0, 0, 0x0040, 0x0020, 1, 1, 16}, // atdf, avrdude, boot size (manual) + {"ATtiny104", 7, F_AVR8L, {0x1E, 0x90, 0x0B}, 0, 0x00400, 0x010, 0, 0, 0, 0, 0, 0x0040, 0x0020, 1, 1, 16}, // atdf, avrdude, boot size (manual) + + {"ATtiny11", 8, F_AVR8, {0x1E, 0x90, 0x04}, 0, 0x00400, 0x001, 0, 0, 0, 0x0040, 1, 0x0060, 0x0020, 1, 1, 5}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny12", 9, F_AVR8, {0x1E, 0x90, 0x05}, 0, 0x00400, 0x001, 0, 0, 0, 0x0040, 2, 0x0060, 0x0020, 1, 1, 6}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny13", 10, F_AVR8, {0x1E, 0x90, 0x07}, 0, 0x00400, 0x020, 0, 0, 0, 0x0040, 4, 0x0060, 0x0040, 2, 1, 10}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny13A", 11, F_AVR8, {0x1E, 0x90, 0x07}, 0, 0x00400, 0x020, 0, 0, 0, 0x0040, 4, 0x0060, 0x0040, 2, 1, 10}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny15", 12, F_AVR8, {0x1E, 0x90, 0x06}, 0, 0x00400, 0x001, 0, 0, 0, 0x0040, 2, 0x0060, 0x0020, 1, 1, 9}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny22", 13, F_AVR8, {0x1E, 0x91, 0x06}, 0, 0x00800, -1, 0, 0, -1, -1, -1, 0x0060, 0x0080, 1, 1, 3}, // avr-gcc 12.2.0, boot size (manual) + {"ATtiny24", 14, F_AVR8, {0x1E, 0x91, 0x0B}, 0, 0x00800, 0x020, 0, 0, 0, 0x0080, 4, 0x0060, 0x0080, 3, 1, 17}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny24A", 15, F_AVR8, {0x1E, 0x91, 0x0B}, 0, 0x00800, 0x020, 0, 0, 0, 0x0080, 4, 0x0060, 0x0080, 3, 1, 17}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny25", 16, F_AVR8, {0x1E, 0x91, 0x08}, 0, 0x00800, 0x020, 0, 0, 0, 0x0080, 4, 0x0060, 0x0080, 3, 1, 15}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny26", 17, F_AVR8, {0x1E, 0x91, 0x09}, 0, 0x00800, 0x020, 0, 0, 0, 0x0080, 4, 0x0060, 0x0080, 2, 1, 12}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny28", 18, F_AVR8, {0x1E, 0x91, 0x07}, 0, 0x00800, 0x002, 0, 0, 0, 0, 0, 0x0060, 0x0020, 1, 1, 6}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny43U", 19, F_AVR8, {0x1E, 0x92, 0x0C}, 0, 0x01000, 0x040, 0, 0, 0, 0x0040, 4, 0x0060, 0x0100, 3, 1, 16}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny44", 20, F_AVR8, {0x1E, 0x92, 0x07}, 0, 0x01000, 0x040, 0, 0, 0, 0x0100, 4, 0x0060, 0x0100, 3, 1, 17}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny44A", 21, F_AVR8, {0x1E, 0x92, 0x07}, 0, 0x01000, 0x040, 0, 0, 0, 0x0100, 4, 0x0060, 0x0100, 3, 1, 17}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny45", 22, F_AVR8, {0x1E, 0x92, 0x06}, 0, 0x01000, 0x040, 0, 0, 0, 0x0100, 4, 0x0060, 0x0100, 3, 1, 15}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny48", 23, F_AVR8, {0x1E, 0x92, 0x09}, 0, 0x01000, 0x040, 0, 0, 0, 0x0040, 4, 0x0100, 0x0100, 3, 1, 20}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny84", 24, F_AVR8, {0x1E, 0x93, 0x0C}, 0, 0x02000, 0x040, 0, 0, 0, 0x0200, 4, 0x0060, 0x0200, 3, 1, 17}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny84A", 25, F_AVR8, {0x1E, 0x93, 0x0C}, 0, 0x02000, 0x040, 0, 0, 0, 0x0200, 4, 0x0060, 0x0200, 3, 1, 17}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny85", 26, F_AVR8, {0x1E, 0x93, 0x0B}, 0, 0x02000, 0x040, 0, 0, 0, 0x0200, 4, 0x0060, 0x0200, 3, 1, 15}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny87", 27, F_AVR8, {0x1E, 0x93, 0x87}, 0, 0x02000, 0x080, 0, 0, 0, 0x0200, 4, 0x0100, 0x0200, 3, 1, 20}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny88", 28, F_AVR8, {0x1E, 0x93, 0x11}, 0, 0x02000, 0x040, 0, 0, 0, 0x0040, 4, 0x0100, 0x0200, 3, 1, 20}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny167", 29, F_AVR8, {0x1E, 0x94, 0x87}, 0, 0x04000, 0x080, 0, 0, 0, 0x0200, 4, 0x0100, 0x0200, 3, 1, 20}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny261", 30, F_AVR8, {0x1E, 0x91, 0x0C}, 0, 0x00800, 0x020, 0, 0, 0, 0x0080, 4, 0x0060, 0x0080, 3, 1, 19}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny261A", 31, F_AVR8, {0x1E, 0x91, 0x0C}, 0, 0x00800, 0x020, 0, 0, 0, 0x0080, 4, 0x0060, 0x0080, 3, 1, 19}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny441", 32, F_AVR8, {0x1E, 0x92, 0x15}, 0, 0x01000, 0x010, 0, 0, 0, 0x0100, 4, 0x0100, 0x0100, 3, 1, 30}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny461", 33, F_AVR8, {0x1E, 0x92, 0x08}, 0, 0x01000, 0x040, 0, 0, 0, 0x0100, 4, 0x0060, 0x0100, 3, 1, 19}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny461A", 34, F_AVR8, {0x1E, 0x92, 0x08}, 0, 0x01000, 0x040, 0, 0, 0, 0x0100, 4, 0x0060, 0x0100, 3, 1, 19}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny828", 35, F_AVR8, {0x1E, 0x93, 0x14}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0100, 4, 0x0100, 0x0200, 3, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny828R", 36, F_AVR8, {0x1E, 0x93, 0x14}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0100, 4, 0x0100, 0x0200, 3, 1, 26}, // avrdude, from ATtiny828 + {"ATtiny841", 37, F_AVR8, {0x1E, 0x93, 0x15}, 0, 0x02000, 0x010, 0, 0, 0, 0x0200, 4, 0x0100, 0x0200, 3, 1, 30}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny861", 38, F_AVR8, {0x1E, 0x93, 0x0D}, 0, 0x02000, 0x040, 0, 0, 0, 0x0200, 4, 0x0060, 0x0200, 3, 1, 19}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny861A", 39, F_AVR8, {0x1E, 0x93, 0x0D}, 0, 0x02000, 0x040, 0, 0, 0, 0x0200, 4, 0x0060, 0x0200, 3, 1, 19}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny1634", 40, F_AVR8, {0x1E, 0x94, 0x12}, 0, 0x04000, 0x020, 0, 0, 0, 0x0100, 4, 0x0100, 0x0400, 3, 1, 28}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny1634R", 41, F_AVR8, {0x1E, 0x94, 0x12}, 0, 0x04000, 0x020, 0, 0, 0, 0x0100, 4, 0x0100, 0x0400, 3, 1, 28}, // avrdude, from ATtiny1634 + {"ATtiny2313", 42, F_AVR8, {0x1E, 0x91, 0x0A}, 0, 0x00800, 0x020, 0, 0, 0, 0x0080, 4, 0x0060, 0x0080, 3, 1, 19}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny2313A", 43, F_AVR8, {0x1E, 0x91, 0x0A}, 0, 0x00800, 0x020, 0, 0, 0, 0x0080, 4, 0x0060, 0x0080, 3, 1, 21}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny4313", 44, F_AVR8, {0x1E, 0x92, 0x0D}, 0, 0x01000, 0x040, 0, 0, 0, 0x0100, 4, 0x0060, 0x0100, 3, 1, 21}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega8", 45, F_AVR8, {0x1E, 0x93, 0x07}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0200, 4, 0x0060, 0x0400, 2, 1, 19}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega8A", 46, F_AVR8, {0x1E, 0x93, 0x07}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0200, 4, 0x0060, 0x0400, 2, 1, 19}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega8HVA", 47, F_AVR8, {0x1E, 0x93, 0x10}, 0, 0x02000, 0x080, 0, 0, 0, 0x0100, 4, 0x0100, 0x0200, 1, 1, 21}, // atdf, avr-gcc 12.2.0 + {"ATmega8U2", 48, F_AVR8, {0x1E, 0x93, 0x89}, 0, 0x02000, 0x080, 4, 0x0200, 0, 0x0200, 4, 0x0100, 0x0200, 3, 1, 29}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega16", 49, F_AVR8, {0x1E, 0x94, 0x03}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0060, 0x0400, 2, 1, 21}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega16A", 50, F_AVR8, {0x1E, 0x94, 0x03}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0060, 0x0400, 2, 1, 21}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega16HVA", 51, F_AVR8, {0x1E, 0x94, 0x0C}, 0, 0x04000, 0x080, 0, 0, 0, 0x0100, 4, 0x0100, 0x0200, 1, 1, 21}, // atdf, avr-gcc 12.2.0 + {"ATmega16HVB", 52, F_AVR8, {0x1E, 0x94, 0x0D}, 0, 0x04000, 0x080, 4, 0x0200, 0, 0x0200, 4, 0x0100, 0x0400, 2, 1, 29}, // atdf, avr-gcc 12.2.0 + {"ATmega16HVBrevB", 53, F_AVR8, {0x1E, 0x94, 0x0D}, 0, 0x04000, 0x080, 4, 0x0200, 0, 0x0200, 4, 0x0100, 0x0400, 2, 1, 29}, // atdf, avr-gcc 12.2.0 + {"ATmega16M1", 54, F_AVR8, {0x1E, 0x94, 0x84}, 0, 0x04000, 0x080, 4, 0x0200, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 31}, // atdf, avr-gcc 12.2.0 + {"ATmega16HVA2", 55, F_AVR8, {0x1E, 0x94, 0x0E}, 0, 0x04000, 0x080, -1, -1, -1, -1, -1, 0x0100, 0x0400, 2, 1, 22}, // avr-gcc 12.2.0 + {"ATmega16U2", 56, F_AVR8, {0x1E, 0x94, 0x89}, 0, 0x04000, 0x080, 4, 0x0200, 0, 0x0200, 4, 0x0100, 0x0200, 3, 1, 29}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega16U4", 57, F_AVR8, {0x1E, 0x94, 0x88}, 0, 0x04000, 0x080, 4, 0x0200, 0, 0x0200, 4, 0x0100, 0x0500, 3, 1, 43}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega32", 58, F_AVR8, {0x1E, 0x95, 0x02}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0060, 0x0800, 2, 1, 21}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega32A", 59, F_AVR8, {0x1E, 0x95, 0x02}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0060, 0x0800, 2, 1, 21}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega32HVB", 60, F_AVR8, {0x1E, 0x95, 0x10}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 2, 1, 29}, // atdf, avr-gcc 12.2.0 + {"ATmega32HVBrevB", 61, F_AVR8, {0x1E, 0x95, 0x10}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 2, 1, 29}, // atdf, avr-gcc 12.2.0 + {"ATmega32C1", 62, F_AVR8, {0x1E, 0x95, 0x86}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 31}, // atdf, avr-gcc 12.2.0 + {"ATmega32M1", 63, F_AVR8, {0x1E, 0x95, 0x84}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega32U2", 64, F_AVR8, {0x1E, 0x95, 0x8A}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0400, 3, 1, 29}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega32U4", 65, F_AVR8, {0x1E, 0x95, 0x87}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0a00, 3, 1, 43}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega32U6", 66, F_AVR8, {0x1E, 0x95, 0x88}, 0, 0x08000, 0x080, 4, 0x0200, -1, -1, -1, 0x0100, 0x0a00, 3, 1, 38}, // avr-gcc 12.2.0, boot size (manual) + {"ATmega48", 67, F_AVR8, {0x1E, 0x92, 0x05}, 0, 0x01000, 0x040, 0, 0, 0, 0x0100, 4, 0x0100, 0x0200, 3, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega48A", 68, F_AVR8, {0x1E, 0x92, 0x05}, 0, 0x01000, 0x040, 0, 0, 0, 0x0100, 4, 0x0100, 0x0200, 3, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega48P", 69, F_AVR8, {0x1E, 0x92, 0x0A}, 0, 0x01000, 0x040, 0, 0, 0, 0x0100, 4, 0x0100, 0x0200, 3, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega48PA", 70, F_AVR8, {0x1E, 0x92, 0x0A}, 0, 0x01000, 0x040, 0, 0, 0, 0x0100, 4, 0x0100, 0x0200, 3, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega48PB", 71, F_AVR8, {0x1E, 0x92, 0x10}, 0, 0x01000, 0x040, 0, 0, 0, 0x0100, 4, 0x0100, 0x0200, 3, 1, 27}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega64", 72, F_AVR8, {0x1E, 0x96, 0x02}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 35}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega64A", 73, F_AVR8, {0x1E, 0x96, 0x02}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 35}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega64HVE", 74, F_AVR8, {0x1E, 0x96, 0x10}, 0, 0x10000, 0x080, 4, 0x0400, -1, -1, -1, 0x0100, 0x1000, 2, 1, 25}, // avr-gcc 12.2.0, boot size (manual) + {"ATmega64C1", 75, F_AVR8, {0x1E, 0x96, 0x86}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 31}, // atdf, avr-gcc 12.2.0 + {"ATmega64M1", 76, F_AVR8, {0x1E, 0x96, 0x84}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega64HVE2", 77, F_AVR8, {0x1E, 0x96, 0x10}, 0, 0x10000, 0x080, 4, 0x0400, 0, 0x0400, 4, 0x0100, 0x1000, 2, 1, 25}, // atdf, avr-gcc 12.2.0 + {"ATmega64RFR2", 78, F_AVR8, {0x1E, 0xA6, 0x02}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0200, 0x2000, 3, 1, 77}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega88", 79, F_AVR8, {0x1E, 0x93, 0x0A}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega88A", 80, F_AVR8, {0x1E, 0x93, 0x0A}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega88P", 81, F_AVR8, {0x1E, 0x93, 0x0F}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega88PA", 82, F_AVR8, {0x1E, 0x93, 0x0F}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega88PB", 83, F_AVR8, {0x1E, 0x93, 0x16}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 27}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega103", 84, F_AVR8, {0x1E, 0x97, 0x01}, 0, 0x20000, 0x100, 0, 0, 0, 0x1000, 1, 0x0060, 0x0fa0, 1, 1, 24}, // avr-gcc 12.2.0, avrdude, boot size (manual) + {"ATmega128", 85, F_AVR8, {0x1E, 0x97, 0x02}, 0, 0x20000, 0x100, 4, 0x0400, 0, 0x1000, 8, 0x0100, 0x1000, 3, 1, 35}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega128A", 86, F_AVR8, {0x1E, 0x97, 0x02}, 0, 0x20000, 0x100, 4, 0x0400, 0, 0x1000, 8, 0x0100, 0x1000, 3, 1, 35}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega128RFA1", 87, F_AVR8, {0x1E, 0xA7, 0x01}, 0, 0x20000, 0x100, 4, 0x0400, 0, 0x1000, 8, 0x0200, 0x4000, 3, 1, 72}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega128RFR2", 88, F_AVR8, {0x1E, 0xA7, 0x02}, 0, 0x20000, 0x100, 4, 0x0400, 0, 0x1000, 8, 0x0200, 0x4000, 3, 1, 77}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega161", 89, F_AVR8, {0x1E, 0x94, 0x01}, 0, 0x04000, 0x080, 1, 0x0400, 0, 0x0200, 1, 0x0060, 0x0400, 1, 1, 21}, // avr-gcc 12.2.0, avrdude, boot size (manual) + {"ATmega162", 90, F_AVR8, {0x1E, 0x94, 0x04}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 28}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega163", 91, F_AVR8, {0x1E, 0x94, 0x02}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 1, 0x0060, 0x0400, 2, 1, 18}, // avr-gcc 12.2.0, avrdude, boot size (manual) + {"ATmega164A", 92, F_AVR8, {0x1E, 0x94, 0x0F}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega164P", 93, F_AVR8, {0x1E, 0x94, 0x0A}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega164PA", 94, F_AVR8, {0x1E, 0x94, 0x0A}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega165", 95, F_AVR8, {0x1E, 0x94, 0x10}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 22}, // avr-gcc 12.2.0, avrdude, boot size (manual) + {"ATmega165A", 96, F_AVR8, {0x1E, 0x94, 0x10}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 22}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega165P", 97, F_AVR8, {0x1E, 0x94, 0x07}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 22}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega165PA", 98, F_AVR8, {0x1E, 0x94, 0x07}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 22}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega168", 99, F_AVR8, {0x1E, 0x94, 0x06}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega168A", 100, F_AVR8, {0x1E, 0x94, 0x06}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega168P", 101, F_AVR8, {0x1E, 0x94, 0x0B}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega168PA", 102, F_AVR8, {0x1E, 0x94, 0x0B}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega168PB", 103, F_AVR8, {0x1E, 0x94, 0x15}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 27}, // atdf, avr-gcc 7.3.0, avrdude + {"ATmega169", 104, F_AVR8, {0x1E, 0x94, 0x05}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 23}, // avr-gcc 12.2.0, avrdude, boot size (manual) + {"ATmega169A", 105, F_AVR8, {0x1E, 0x94, 0x11}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 23}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega169P", 106, F_AVR8, {0x1E, 0x94, 0x05}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 23}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega169PA", 107, F_AVR8, {0x1E, 0x94, 0x05}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 23}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega256RFR2", 108, F_AVR8, {0x1E, 0xA8, 0x02}, 0, 0x40000, 0x100, 4, 0x0400, 0, 0x2000, 8, 0x0200, 0x8000, 3, 1, 77}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega323", 109, F_AVR8, {0x1E, 0x95, 0x01}, 0, 0x08000, 0x080, 4, 0x0200, -1, -1, -1, 0x0060, 0x0800, 2, 1, 21}, // avr-gcc 12.2.0, boot size (manual) + {"ATmega324A", 110, F_AVR8, {0x1E, 0x95, 0x15}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega324P", 111, F_AVR8, {0x1E, 0x95, 0x08}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega324PA", 112, F_AVR8, {0x1E, 0x95, 0x11}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega324PB", 113, F_AVR8, {0x1E, 0x95, 0x17}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 51}, // atdf, avrdude + {"ATmega325", 114, F_AVR8, {0x1E, 0x95, 0x05}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 22}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega325A", 115, F_AVR8, {0x1E, 0x95, 0x05}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 22}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega325P", 116, F_AVR8, {0x1E, 0x95, 0x0D}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 22}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega325PA", 117, F_AVR8, {0x1E, 0x95, 0x0D}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 22}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega328", 118, F_AVR8, {0x1E, 0x95, 0x14}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega328P", 119, F_AVR8, {0x1E, 0x95, 0x0F}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega328PB", 120, F_AVR8, {0x1E, 0x95, 0x16}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 45}, // atdf, avr-gcc 7.3.0, avrdude + {"ATmega329", 121, F_AVR8, {0x1E, 0x95, 0x03}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 23}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega329A", 122, F_AVR8, {0x1E, 0x95, 0x03}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 23}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega329P", 123, F_AVR8, {0x1E, 0x95, 0x0B}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 23}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega329PA", 124, F_AVR8, {0x1E, 0x95, 0x0B}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 23}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega406", 125, F_AVR8, {0x1E, 0x95, 0x07}, 0, 0x0a000, 0x080, 4, 0x0200, 0, 0x0200, 4, 0x0100, 0x0800, 2, 1, 23}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega640", 126, F_AVR8, {0x1E, 0x96, 0x08}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x1000, 8, 0x0200, 0x2000, 3, 1, 57}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega644", 127, F_AVR8, {0x1E, 0x96, 0x09}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 28}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega644A", 128, F_AVR8, {0x1E, 0x96, 0x09}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega644P", 129, F_AVR8, {0x1E, 0x96, 0x0A}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega644PA", 130, F_AVR8, {0x1E, 0x96, 0x0A}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega644RFR2", 131, F_AVR8, {0x1E, 0xA6, 0x03}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0200, 0x2000, 3, 1, 77}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega645", 132, F_AVR8, {0x1E, 0x96, 0x05}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 22}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega645A", 133, F_AVR8, {0x1E, 0x96, 0x05}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 22}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega645P", 134, F_AVR8, {0x1E, 0x96, 0x0D}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 22}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega649", 135, F_AVR8, {0x1E, 0x96, 0x03}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 23}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega649A", 136, F_AVR8, {0x1E, 0x96, 0x03}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 23}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega649P", 137, F_AVR8, {0x1E, 0x96, 0x0B}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 23}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega1280", 138, F_AVR8, {0x1E, 0x97, 0x03}, 0, 0x20000, 0x100, 4, 0x0400, 0, 0x1000, 8, 0x0200, 0x2000, 3, 1, 57}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega1281", 139, F_AVR8, {0x1E, 0x97, 0x04}, 0, 0x20000, 0x100, 4, 0x0400, 0, 0x1000, 8, 0x0200, 0x2000, 3, 1, 57}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega1284", 140, F_AVR8, {0x1E, 0x97, 0x06}, 0, 0x20000, 0x100, 4, 0x0400, 0, 0x1000, 8, 0x0100, 0x4000, 3, 1, 35}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega1284P", 141, F_AVR8, {0x1E, 0x97, 0x05}, 0, 0x20000, 0x100, 4, 0x0400, 0, 0x1000, 8, 0x0100, 0x4000, 3, 1, 35}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega1284RFR2", 142, F_AVR8, {0x1E, 0xA7, 0x03}, 0, 0x20000, 0x100, 4, 0x0400, 0, 0x1000, 8, 0x0200, 0x4000, 3, 1, 77}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega2560", 143, F_AVR8, {0x1E, 0x98, 0x01}, 0, 0x40000, 0x100, 4, 0x0400, 0, 0x1000, 8, 0x0200, 0x2000, 3, 1, 57}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega2561", 144, F_AVR8, {0x1E, 0x98, 0x02}, 0, 0x40000, 0x100, 4, 0x0400, 0, 0x1000, 8, 0x0200, 0x2000, 3, 1, 57}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega2564RFR2", 145, F_AVR8, {0x1E, 0xA8, 0x03}, 0, 0x40000, 0x100, 4, 0x0400, 0, 0x2000, 8, 0x0200, 0x8000, 3, 1, 77}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega3250", 146, F_AVR8, {0x1E, 0x95, 0x06}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 25}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega3250A", 147, F_AVR8, {0x1E, 0x95, 0x06}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 25}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega3250P", 148, F_AVR8, {0x1E, 0x95, 0x0E}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 25}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega3250PA", 149, F_AVR8, {0x1E, 0x95, 0x0E}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 25}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega3290", 150, F_AVR8, {0x1E, 0x95, 0x04}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 25}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega3290A", 151, F_AVR8, {0x1E, 0x95, 0x04}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 25}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega3290P", 152, F_AVR8, {0x1E, 0x95, 0x0C}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 25}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega3290PA", 153, F_AVR8, {0x1E, 0x95, 0x0C}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 25}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega6450", 154, F_AVR8, {0x1E, 0x96, 0x06}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 25}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega6450A", 155, F_AVR8, {0x1E, 0x96, 0x06}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 25}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega6450P", 156, F_AVR8, {0x1E, 0x96, 0x0E}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 25}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega6490", 157, F_AVR8, {0x1E, 0x96, 0x04}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 25}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega6490A", 158, F_AVR8, {0x1E, 0x96, 0x04}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 25}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega6490P", 159, F_AVR8, {0x1E, 0x96, 0x0C}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 25}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega8515", 160, F_AVR8, {0x1E, 0x93, 0x06}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0200, 4, 0x0060, 0x0200, 2, 1, 17}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega8535", 161, F_AVR8, {0x1E, 0x93, 0x08}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0200, 4, 0x0060, 0x0200, 2, 1, 21}, // atdf, avr-gcc 12.2.0, avrdude + {"AT43USB320", 162, F_AVR8, {0xff, -1, -1}, 0, 0x10000, -1, -1, -1, -1, -1, -1, 0x0060, 0x0200, -1, -1, 0}, // avr-gcc 12.2.0 + {"AT43USB355", 163, F_AVR8, {0xff, -1, -1}, 0, 0x06000, -1, -1, -1, -1, -1, -1, 0x0060, 0x0400, -1, -1, 0}, // avr-gcc 12.2.0 + {"AT76C711", 164, F_AVR8, {0xff, -1, -1}, 0, 0x04000, -1, -1, -1, -1, -1, -1, 0x0060, 0x07a0, -1, -1, 0}, // avr-gcc 12.2.0 + {"AT86RF401", 165, F_AVR8, {0x1E, 0x91, 0x81}, 0, 0x00800, -1, -1, -1, -1, -1, -1, 0x0060, 0x0080, 0, 1, 3}, // avr-gcc 12.2.0 + {"AT90PWM1", 166, F_AVR8, {0x1E, 0x93, 0x83}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0200, 3, 1, 32}, // atdf, avr-gcc 12.2.0 + {"AT90PWM2", 167, F_AVR8, {0x1E, 0x93, 0x81}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0200, 3, 1, 32}, // avr-gcc 12.2.0, avrdude, boot size (manual) + {"AT90PWM2B", 168, F_AVR8, {0x1E, 0x93, 0x83}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0200, 3, 1, 32}, // atdf, avr-gcc 12.2.0, avrdude + {"AT90PWM3", 169, F_AVR8, {0x1E, 0x93, 0x81}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0200, 3, 1, 32}, // atdf, avr-gcc 12.2.0, avrdude + {"AT90PWM3B", 170, F_AVR8, {0x1E, 0x93, 0x83}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0200, 3, 1, 32}, // atdf, avr-gcc 12.2.0, avrdude + {"AT90CAN32", 171, F_AVR8, {0x1E, 0x95, 0x81}, 0, 0x08000, 0x100, 4, 0x0400, 0, 0x0400, 8, 0x0100, 0x0800, 3, 1, 37}, // atdf, avr-gcc 12.2.0, avrdude + {"AT90CAN64", 172, F_AVR8, {0x1E, 0x96, 0x81}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 37}, // atdf, avr-gcc 12.2.0, avrdude + {"AT90PWM81", 173, F_AVR8, {0x1E, 0x93, 0x88}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0100, 3, 1, 20}, // atdf, avr-gcc 12.2.0 + {"AT90USB82", 174, F_AVR8, {0x1E, 0x93, 0x82}, 0, 0x02000, 0x080, 4, 0x0200, 0, 0x0200, 4, 0x0100, 0x0200, 3, 1, 29}, // atdf, avr-gcc 12.2.0, avrdude + {"AT90SCR100", 175, F_AVR8, {0x1E, 0x96, 0xC1}, 0, 0x10000, 0x100, 4, 0x0200, -1, -1, -1, 0x0100, 0x1000, 3, 1, 38}, // avr-gcc 12.2.0, boot size (manual) + {"AT90CAN128", 176, F_AVR8, {0x1E, 0x97, 0x81}, 0, 0x20000, 0x100, 4, 0x0400, 0, 0x1000, 8, 0x0100, 0x1000, 3, 1, 37}, // atdf, avr-gcc 12.2.0, avrdude + {"AT90PWM161", 177, F_AVR8, {0x1E, 0x94, 0x8B}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 20}, // atdf, avr-gcc 12.2.0 + {"AT90USB162", 178, F_AVR8, {0x1E, 0x94, 0x82}, 0, 0x04000, 0x080, 4, 0x0200, 0, 0x0200, 4, 0x0100, 0x0200, 3, 1, 29}, // atdf, avr-gcc 12.2.0, avrdude + {"AT90PWM216", 179, F_AVR8, {0x1E, 0x94, 0x83}, 0, 0x04000, 0x080, 4, 0x0200, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 32}, // atdf, avr-gcc 12.2.0, avrdude + {"AT90PWM316", 180, F_AVR8, {0x1E, 0x94, 0x83}, 0, 0x04000, 0x080, 4, 0x0200, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 32}, // atdf, avr-gcc 12.2.0, avrdude + {"AT90USB646", 181, F_AVR8, {0x1E, 0x96, 0x82}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 38}, // atdf, avr-gcc 12.2.0, avrdude + {"AT90USB647", 182, F_AVR8, {0x1E, 0x96, 0x82}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 38}, // atdf, avr-gcc 12.2.0, avrdude + {"AT90S1200", 183, F_AVR8, {0x1E, 0x90, 0x01}, 0, 0x00400, 0x001, 0, 0, 0, 0x0040, 1, 0x0060, 0x0020, 1, 1, 4}, // avr-gcc 12.2.0, avrdude, boot size (manual) + {"AT90USB1286", 184, F_AVR8, {0x1E, 0x97, 0x82}, 0, 0x20000, 0x100, 4, 0x0400, 0, 0x1000, 8, 0x0100, 0x2000, 3, 1, 38}, // atdf, avr-gcc 12.2.0, avrdude + {"AT90USB1287", 185, F_AVR8, {0x1E, 0x97, 0x82}, 0, 0x20000, 0x100, 4, 0x0400, 0, 0x1000, 8, 0x0100, 0x2000, 3, 1, 38}, // atdf, avr-gcc 12.2.0, avrdude + {"AT90S2313", 186, F_AVR8, {0x1E, 0x91, 0x01}, 0, 0x00800, 0x001, 0, 0, 0, 0x0080, 1, 0x0060, 0x0080, 1, 1, 11}, // avr-gcc 12.2.0, avrdude, boot size (manual) + {"AT90S2323", 187, F_AVR8, {0x1E, 0x91, 0x02}, 0, 0x00800, -1, 0, 0, -1, -1, -1, 0x0060, 0x0080, 1, 1, 3}, // avr-gcc 12.2.0, boot size (manual) + {"AT90S2333", 188, F_AVR8, {0x1E, 0x91, 0x05}, 0, 0x00800, 0x001, 0, 0, 0, 0x0080, 1, 0x0060, 0x0080, -1, -1, 14}, // avr-gcc 12.2.0, avrdude, boot size (manual) + {"AT90S2343", 189, F_AVR8, {0x1E, 0x91, 0x03}, 0, 0x00800, 0x001, 0, 0, 0, 0x0080, 1, 0x0060, 0x0080, 1, 1, 3}, // avr-gcc 12.2.0, avrdude, boot size (manual) + {"AT90S4414", 190, F_AVR8, {0x1E, 0x92, 0x01}, 0, 0x01000, 0x001, 0, 0, 0, 0x0100, 1, 0x0060, 0x0100, 1, 1, 13}, // avr-gcc 12.2.0, avrdude, boot size (manual) + {"AT90S4433", 191, F_AVR8, {0x1E, 0x92, 0x03}, 0, 0x01000, 0x001, 0, 0, 0, 0x0100, 1, 0x0060, 0x0080, 1, 1, 14}, // avr-gcc 12.2.0, avrdude, boot size (manual) + {"AT90S4434", 192, F_AVR8, {0x1E, 0x92, 0x02}, 0, 0x01000, 0x001, 0, 0, 0, 0x0100, 1, 0x0060, 0x0100, 1, 1, 17}, // avr-gcc 12.2.0, avrdude, boot size (manual) + {"AT90S8515", 193, F_AVR8, {0x1E, 0x93, 0x01}, 0, 0x02000, 0x001, 0, 0, 0, 0x0200, 1, 0x0060, 0x0200, 1, 1, 13}, // avr-gcc 12.2.0, avrdude, boot size (manual) + {"AT90C8534", 194, F_AVR8, {0xff, -1, -1}, 0, 0x02000, -1, -1, -1, -1, -1, -1, 0x0060, 0x0100, -1, -1, 0}, // avr-gcc 12.2.0 + {"AT90S8535", 195, F_AVR8, {0x1E, 0x93, 0x03}, 0, 0x02000, 0x001, 0, 0, 0, 0x0200, 1, 0x0060, 0x0200, 1, 1, 17}, // avr-gcc 12.2.0, avrdude, boot size (manual) + {"AT94K", 196, F_AVR8, {0xff, -1, -1}, 0, 0x08000, -1, -1, -1, -1, -1, -1, 0x0060, 0x0fa0, -1, -1, 0}, // avr-gcc 12.2.0 + {"ATA5272", 197, F_AVR8, {0x1E, 0x93, 0x87}, 0, 0x02000, 0x080, 0, 0, 0, 0x0200, 4, 0x0100, 0x0200, 3, 1, 37}, // atdf, avr-gcc 12.2.0 + {"ATA5505", 198, F_AVR8, {0x1E, 0x94, 0x87}, 0, 0x04000, 0x080, 0, 0, 0, 0x0200, 4, 0x0100, 0x0200, 3, 1, 20}, // atdf, avr-gcc 12.2.0 + {"ATA5700M322", 199, F_AVR8, {0x1E, 0x95, 0x67}, 0x08000, 0x08000, 0x040, 0, 0, 0, 0x0880, 16, 0x0200, 0x0400, 1, 1, 51}, // atdf + {"ATA5702M322", 200, F_AVR8, {0x1E, 0x95, 0x69}, 0x08000, 0x08000, 0x040, 0, 0, 0, 0x0880, 16, 0x0200, 0x0400, 1, 1, 51}, // atdf, avr-gcc 12.2.0 + {"ATA5781", 201, F_AVR8, {0x1E, 0x95, 0x64}, -1, -1, -1, 0, 0, 0, 0x0400, 16, 0x0200, 0x0400, 1, 1, 42}, // atdf + {"ATA5782", 202, F_AVR8, {0x1E, 0x95, 0x65}, 0x08000, 0x05000, 0x040, 1, 0x5000, 0, 0x0400, 16, 0x0200, 0x0400, 1, 1, 42}, // atdf, avr-gcc 12.2.0 + {"ATA5783", 203, F_AVR8, {0x1E, 0x95, 0x66}, -1, -1, -1, 0, 0, 0, 0x0400, 16, 0x0200, 0x0400, 1, 1, 42}, // atdf + {"ATA5787", 204, F_AVR8, {0x1E, 0x94, 0x6C}, 0x08000, 0x05200, 0x040, 0, 0, 0, 0x0400, 16, 0x0200, 0x0800, 1, 1, 44}, // atdf + {"ATA5790", 205, F_AVR8, {0x1E, 0x94, 0x61}, 0, 0x04000, 0x080, 1, 0x0800, 0, 0x0800, 16, 0x0100, 0x0200, 1, 1, 30}, // atdf, avr-gcc 12.2.0 + {"ATA5790N", 206, F_AVR8, {0x1E, 0x94, 0x62}, 0, 0x04000, 0x080, 1, 0x0800, 0, 0x0800, 16, 0x0100, 0x0200, 1, 1, 31}, // atdf, avr-gcc 12.2.0 + {"ATA5791", 207, F_AVR8, {0x1E, 0x94, 0x62}, 0, 0x04000, 0x080, 1, 0x0800, 0, 0x0800, 16, 0x0100, 0x0200, 1, 1, 31}, // atdf, avr-gcc 7.3.0 + {"ATA5795", 208, F_AVR8, {0x1E, 0x93, 0x61}, 0, 0x02000, 0x040, 1, 0x0800, 0, 0x0800, 16, 0x0100, 0x0200, 1, 1, 23}, // atdf, avr-gcc 12.2.0 + {"ATA5831", 209, F_AVR8, {0x1E, 0x95, 0x61}, 0x08000, 0x05000, 0x040, 1, 0x5000, 0, 0x0400, 16, 0x0200, 0x0400, 1, 1, 42}, // atdf, avr-gcc 12.2.0 + {"ATA5832", 210, F_AVR8, {0x1E, 0x95, 0x62}, -1, -1, -1, 0, 0, 0, 0x0400, 16, 0x0200, 0x0400, 1, 1, 42}, // atdf + {"ATA5833", 211, F_AVR8, {0x1E, 0x95, 0x63}, -1, -1, -1, 0, 0, 0, 0x0400, 16, 0x0200, 0x0400, 1, 1, 42}, // atdf + {"ATA5835", 212, F_AVR8, {0x1E, 0x94, 0x6B}, 0x08000, 0x05200, 0x040, 0, 0, 0, 0x0400, 16, 0x0200, 0x0800, 1, 1, 44}, // atdf + {"ATA6285", 213, F_AVR8, {0x1E, 0x93, 0x82}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0140, 4, 0x0100, 0x0200, 2, 1, 27}, // atdf, avr-gcc 12.2.0 + {"ATA6286", 214, F_AVR8, {0x1E, 0x93, 0x82}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0140, 4, 0x0100, 0x0200, 2, 1, 27}, // atdf, avr-gcc 12.2.0 + {"ATA6289", 215, F_AVR8, {0x1E, 0x93, 0x82}, 0, 0x02000, 0x040, 4, 0x0100, -1, -1, -1, 0x0100, 0x0200, 2, 1, 27}, // avr-gcc 12.2.0, boot size (manual) + {"ATA6612C", 216, F_AVR8, {0x1E, 0x93, 0x0A}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 26}, // atdf, avr-gcc 12.2.0 + {"ATA6613C", 217, F_AVR8, {0x1E, 0x94, 0x06}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 26}, // atdf, avr-gcc 12.2.0 + {"ATA6614Q", 218, F_AVR8, {0x1E, 0x95, 0x0F}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 26}, // atdf, avr-gcc 12.2.0 + {"ATA6616C", 219, F_AVR8, {0x1E, 0x93, 0x87}, 0, 0x02000, 0x080, 0, 0, 0, 0x0200, 4, 0x0100, 0x0200, 3, 1, 20}, // atdf, avr-gcc 12.2.0 + {"ATA6617C", 220, F_AVR8, {0x1E, 0x94, 0x87}, 0, 0x04000, 0x080, 0, 0, 0, 0x0200, 4, 0x0100, 0x0200, 3, 1, 20}, // atdf, avr-gcc 12.2.0 + {"ATA8210", 221, F_AVR8, {0x1E, 0x95, 0x65}, 0x08000, 0x05000, 0x040, 1, 0x5000, 0, 0x0400, 16, 0x0200, 0x0400, 1, 1, 42}, // atdf, avr-gcc 7.3.0 + {"ATA8215", 222, F_AVR8, {0x1E, 0x95, 0x64}, -1, -1, -1, 0, 0, 0, 0x0400, 16, 0x0200, 0x0400, 1, 1, 42}, // atdf + {"ATA8510", 223, F_AVR8, {0x1E, 0x95, 0x61}, 0x08000, 0x05000, 0x040, 1, 0x5000, 0, 0x0400, 16, 0x0200, 0x0400, 1, 1, 42}, // atdf, avr-gcc 7.3.0 + {"ATA8515", 224, F_AVR8, {0x1E, 0x95, 0x63}, -1, -1, -1, 0, 0, 0, 0x0400, 16, 0x0200, 0x0400, 1, 1, 42}, // atdf + {"ATA664251", 225, F_AVR8, {0x1E, 0x94, 0x87}, 0, 0x04000, 0x080, 0, 0, 0, 0x0200, 4, 0x0100, 0x0200, 3, 1, 20}, // atdf, avr-gcc 12.2.0 + {"M3000", 226, F_AVR8, {0xff, -1, -1}, 0, 0x10000, -1, -1, -1, -1, -1, -1, 0x1000, 0x1000, -1, -1, 0}, // avr-gcc 12.2.0 + {"LGT8F88P", 227, F_AVR8, {0x1E, 0x93, 0x0F}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 26}, // avrdude, from ATmega88 + {"LGT8F168P", 228, F_AVR8, {0x1E, 0x94, 0x0B}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 26}, // avrdude, from ATmega168P + {"LGT8F328P", 229, F_AVR8, {0x1E, 0x95, 0x0F}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 26}, // avrdude, from ATmega328P + + {"ATxmega8E5", 230, F_XMEGA, {0x1E, 0x93, 0x41}, 0, 0x02800, 0x080, 1, 0x0800, 0, 0x0200, 32, 0x2000, 0x0400, 7, 1, 43}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega16A4", 231, F_XMEGA, {0x1E, 0x94, 0x41}, 0, 0x05000, 0x100, 1, 0x1000, 0, 0x0400, 32, 0x2000, 0x0800, 6, 1, 94}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega16A4U", 232, F_XMEGA, {0x1E, 0x94, 0x41}, 0, 0x05000, 0x100, 1, 0x1000, 0, 0x0400, 32, 0x2000, 0x0800, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega16C4", 233, F_XMEGA, {0x1E, 0x94, 0x43}, 0, 0x05000, 0x100, 1, 0x1000, 0, 0x0400, 32, 0x2000, 0x0800, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega16D4", 234, F_XMEGA, {0x1E, 0x94, 0x42}, 0, 0x05000, 0x100, 1, 0x1000, 0, 0x0400, 32, 0x2000, 0x0800, 6, 1, 91}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega16E5", 235, F_XMEGA, {0x1E, 0x94, 0x45}, 0, 0x05000, 0x080, 1, 0x1000, 0, 0x0200, 32, 0x2000, 0x0800, 7, 1, 43}, // atdf, avr-gcc 7.3.0, avrdude + {"ATxmega32C3", 236, F_XMEGA, {0x1E, 0x95, 0x49}, 0, 0x09000, 0x100, 1, 0x1000, 0, 0x0400, 32, 0x2000, 0x1000, 6, 1, 127}, // atdf, avr-gcc 12.2.0 + {"ATxmega32D3", 237, F_XMEGA, {0x1E, 0x95, 0x4A}, 0, 0x09000, 0x100, 1, 0x1000, 0, 0x0400, 32, 0x2000, 0x1000, 6, 1, 114}, // atdf, avr-gcc 12.2.0 + {"ATxmega32A4", 238, F_XMEGA, {0x1E, 0x95, 0x41}, 0, 0x09000, 0x100, 1, 0x1000, 0, 0x0400, 32, 0x2000, 0x1000, 6, 1, 94}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega32A4U", 239, F_XMEGA, {0x1E, 0x95, 0x41}, 0, 0x09000, 0x100, 1, 0x1000, 0, 0x0400, 32, 0x2000, 0x1000, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega32C4", 240, F_XMEGA, {0x1E, 0x95, 0x44}, 0, 0x09000, 0x100, 1, 0x1000, 0, 0x0400, 32, 0x2000, 0x1000, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega32D4", 241, F_XMEGA, {0x1E, 0x95, 0x42}, 0, 0x09000, 0x100, 1, 0x1000, 0, 0x0400, 32, 0x2000, 0x1000, 6, 1, 91}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega32E5", 242, F_XMEGA, {0x1E, 0x95, 0x4C}, 0, 0x09000, 0x080, 1, 0x1000, 0, 0x0400, 32, 0x2000, 0x1000, 7, 1, 43}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega64A1", 243, F_XMEGA, {0x1E, 0x96, 0x4E}, 0, 0x11000, 0x100, 1, 0x1000, 0, 0x0800, 32, 0x2000, 0x1000, 6, 1, 125}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega64A1U", 244, F_XMEGA, {0x1E, 0x96, 0x4E}, 0, 0x11000, 0x100, 1, 0x1000, 0, 0x0800, 32, 0x2000, 0x1000, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega64B1", 245, F_XMEGA, {0x1E, 0x96, 0x52}, 0, 0x11000, 0x100, 1, 0x1000, 0, 0x0800, 32, 0x2000, 0x1000, 6, 1, 81}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega64A3", 246, F_XMEGA, {0x1E, 0x96, 0x42}, 0, 0x11000, 0x100, 1, 0x1000, 0, 0x0800, 32, 0x2000, 0x1000, 6, 1, 122}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega64A3U", 247, F_XMEGA, {0x1E, 0x96, 0x42}, 0, 0x11000, 0x100, 1, 0x1000, 0, 0x0800, 32, 0x2000, 0x1000, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega64B3", 248, F_XMEGA, {0x1E, 0x96, 0x51}, 0, 0x11000, 0x100, 1, 0x1000, 0, 0x0800, 32, 0x2000, 0x1000, 6, 1, 54}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega64C3", 249, F_XMEGA, {0x1E, 0x96, 0x49}, 0, 0x11000, 0x100, 1, 0x1000, 0, 0x0800, 32, 0x2000, 0x1000, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega64D3", 250, F_XMEGA, {0x1E, 0x96, 0x4A}, 0, 0x11000, 0x100, 1, 0x1000, 0, 0x0800, 32, 0x2000, 0x1000, 6, 1, 114}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega64A4", 251, F_XMEGA, {0x1E, 0x96, 0x46}, 0, 0x11000, 0x100, -1, -1, 0, 0x0800, 32, -1, -1, -1, -1, 0}, // avrdude + {"ATxmega64A4U", 252, F_XMEGA, {0x1E, 0x96, 0x46}, 0, 0x11000, 0x100, 1, 0x1000, 0, 0x0800, 32, 0x2000, 0x1000, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega64D4", 253, F_XMEGA, {0x1E, 0x96, 0x47}, 0, 0x11000, 0x100, 1, 0x1000, 0, 0x0800, 32, 0x2000, 0x1000, 6, 1, 91}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega128A1", 254, F_XMEGA, {0x1E, 0x97, 0x4C}, 0, 0x22000, 0x200, 1, 0x2000, 0, 0x0800, 32, 0x2000, 0x2000, 6, 1, 125}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega128A1revD", 255, F_XMEGA, {0x1E, 0x97, 0x41}, 0, 0x22000, 0x200, -1, -1, 0, 0x0800, 32, -1, -1, -1, -1, 0}, // avrdude + {"ATxmega128A1U", 256, F_XMEGA, {0x1E, 0x97, 0x4C}, 0, 0x22000, 0x200, 1, 0x2000, 0, 0x0800, 32, 0x2000, 0x2000, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega128B1", 257, F_XMEGA, {0x1E, 0x97, 0x4D}, 0, 0x22000, 0x100, 1, 0x2000, 0, 0x0800, 32, 0x2000, 0x2000, 6, 1, 81}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega128A3", 258, F_XMEGA, {0x1E, 0x97, 0x42}, 0, 0x22000, 0x200, 1, 0x2000, 0, 0x0800, 32, 0x2000, 0x2000, 6, 1, 122}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega128A3U", 259, F_XMEGA, {0x1E, 0x97, 0x42}, 0, 0x22000, 0x200, 1, 0x2000, 0, 0x0800, 32, 0x2000, 0x2000, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega128B3", 260, F_XMEGA, {0x1E, 0x97, 0x4B}, 0, 0x22000, 0x100, 1, 0x2000, 0, 0x0800, 32, 0x2000, 0x2000, 6, 1, 54}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega128C3", 261, F_XMEGA, {0x1E, 0x97, 0x52}, 0, 0x22000, 0x200, 1, 0x2000, 0, 0x0800, 32, 0x2000, 0x2000, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega128D3", 262, F_XMEGA, {0x1E, 0x97, 0x48}, 0, 0x22000, 0x200, 1, 0x2000, 0, 0x0800, 32, 0x2000, 0x2000, 6, 1, 114}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega128A4", 263, F_XMEGA, {0x1E, 0x97, 0x46}, 0, 0x22000, 0x200, -1, -1, 0, 0x0800, 32, -1, -1, -1, -1, 0}, // avrdude + {"ATxmega128A4U", 264, F_XMEGA, {0x1E, 0x97, 0x46}, 0, 0x22000, 0x100, 1, 0x2000, 0, 0x0800, 32, 0x2000, 0x2000, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega128D4", 265, F_XMEGA, {0x1E, 0x97, 0x47}, 0, 0x22000, 0x100, 1, 0x2000, 0, 0x0800, 32, 0x2000, 0x2000, 6, 1, 91}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega192A1", 266, F_XMEGA, {0x1E, 0x97, 0x4E}, 0, 0x32000, 0x200, -1, -1, 0, 0x0800, 32, -1, -1, -1, -1, 0}, // avrdude + {"ATxmega192A3", 267, F_XMEGA, {0x1E, 0x97, 0x44}, 0, 0x32000, 0x200, 1, 0x2000, 0, 0x0800, 32, 0x2000, 0x4000, 6, 1, 122}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega192A3U", 268, F_XMEGA, {0x1E, 0x97, 0x44}, 0, 0x32000, 0x200, 1, 0x2000, 0, 0x0800, 32, 0x2000, 0x4000, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega192C3", 269, F_XMEGA, {0x1E, 0x97, 0x51}, 0, 0x32000, 0x200, 1, 0x2000, 0, 0x0800, 32, 0x2000, 0x4000, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega192D3", 270, F_XMEGA, {0x1E, 0x97, 0x49}, 0, 0x32000, 0x200, 1, 0x2000, 0, 0x0800, 32, 0x2000, 0x4000, 6, 1, 114}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega256A1", 271, F_XMEGA, {0x1E, 0x98, 0x46}, 0, 0x42000, 0x200, -1, -1, 0, 0x1000, 32, -1, -1, -1, -1, 0}, // avrdude + {"ATxmega256A3", 272, F_XMEGA, {0x1E, 0x98, 0x42}, 0, 0x42000, 0x200, 1, 0x2000, 0, 0x1000, 32, 0x2000, 0x4000, 6, 1, 122}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega256A3B", 273, F_XMEGA, {0x1E, 0x98, 0x43}, 0, 0x42000, 0x200, 1, 0x2000, 0, 0x1000, 32, 0x2000, 0x4000, 6, 1, 122}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega256A3BU", 274, F_XMEGA, {0x1E, 0x98, 0x43}, 0, 0x42000, 0x200, 1, 0x2000, 0, 0x1000, 32, 0x2000, 0x4000, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega256A3U", 275, F_XMEGA, {0x1E, 0x98, 0x42}, 0, 0x42000, 0x200, 1, 0x2000, 0, 0x1000, 32, 0x2000, 0x4000, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega256C3", 276, F_XMEGA, {0x1E, 0x98, 0x46}, 0, 0x42000, 0x200, 1, 0x2000, 0, 0x1000, 32, 0x2000, 0x4000, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega256D3", 277, F_XMEGA, {0x1E, 0x98, 0x44}, 0, 0x42000, 0x200, 1, 0x2000, 0, 0x1000, 32, 0x2000, 0x4000, 6, 1, 114}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega384C3", 278, F_XMEGA, {0x1E, 0x98, 0x45}, 0, 0x62000, 0x200, 1, 0x2000, 0, 0x1000, 32, 0x2000, 0x8000, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude + {"ATxmega384D3", 279, F_XMEGA, {0x1E, 0x98, 0x47}, 0, 0x62000, 0x200, 1, 0x2000, 0, 0x1000, 32, 0x2000, 0x8000, 6, 1, 114}, // atdf, avr-gcc 12.2.0, avrdude + + {"ATtiny202", 280, F_AVR8X, {0x1E, 0x91, 0x23}, 0, 0x00800, 0x040, 1, 0, 0x01400, 0x0040, 32, 0x3f80, 0x0080, 10, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny204", 281, F_AVR8X, {0x1E, 0x91, 0x22}, 0, 0x00800, 0x040, 1, 0, 0x01400, 0x0040, 32, 0x3f80, 0x0080, 10, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny212", 282, F_AVR8X, {0x1E, 0x91, 0x21}, 0, 0x00800, 0x040, 1, 0, 0x01400, 0x0040, 32, 0x3f80, 0x0080, 10, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny214", 283, F_AVR8X, {0x1E, 0x91, 0x20}, 0, 0x00800, 0x040, 1, 0, 0x01400, 0x0040, 32, 0x3f80, 0x0080, 10, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny402", 284, F_AVR8X, {0x1E, 0x92, 0x27}, 0, 0x01000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3f00, 0x0100, 10, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny404", 285, F_AVR8X, {0x1E, 0x92, 0x26}, 0, 0x01000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3f00, 0x0100, 10, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny406", 286, F_AVR8X, {0x1E, 0x92, 0x25}, 0, 0x01000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3f00, 0x0100, 10, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny412", 287, F_AVR8X, {0x1E, 0x92, 0x23}, 0, 0x01000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3f00, 0x0100, 10, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny414", 288, F_AVR8X, {0x1E, 0x92, 0x22}, 0, 0x01000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3f00, 0x0100, 10, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny416", 289, F_AVR8X, {0x1E, 0x92, 0x21}, 0, 0x01000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3f00, 0x0100, 10, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny416auto", 290, F_AVR8X, {0x1E, 0x92, 0x28}, 0, 0x01000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3f00, 0x0100, 10, 1, 26}, // atdf + {"ATtiny417", 291, F_AVR8X, {0x1E, 0x92, 0x20}, 0, 0x01000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3f00, 0x0100, 10, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny424", 292, F_AVR8X, {0x1E, 0x92, 0x2C}, 0, 0x01000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3e00, 0x0200, 10, 1, 30}, // atdf, avrdude + {"ATtiny426", 293, F_AVR8X, {0x1E, 0x92, 0x2B}, 0, 0x01000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3e00, 0x0200, 10, 1, 30}, // atdf, avrdude + {"ATtiny427", 294, F_AVR8X, {0x1E, 0x92, 0x2A}, 0, 0x01000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3e00, 0x0200, 10, 1, 30}, // atdf, avrdude + {"ATtiny804", 295, F_AVR8X, {0x1E, 0x93, 0x25}, 0, 0x02000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3e00, 0x0200, 10, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny806", 296, F_AVR8X, {0x1E, 0x93, 0x24}, 0, 0x02000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3e00, 0x0200, 10, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny807", 297, F_AVR8X, {0x1E, 0x93, 0x23}, 0, 0x02000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3e00, 0x0200, 10, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny814", 298, F_AVR8X, {0x1E, 0x93, 0x22}, 0, 0x02000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3e00, 0x0200, 10, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny816", 299, F_AVR8X, {0x1E, 0x93, 0x21}, 0, 0x02000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3e00, 0x0200, 10, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny817", 300, F_AVR8X, {0x1E, 0x93, 0x20}, 0, 0x02000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3e00, 0x0200, 10, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny824", 301, F_AVR8X, {0x1E, 0x93, 0x29}, 0, 0x02000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3c00, 0x0400, 10, 1, 30}, // atdf, avrdude + {"ATtiny826", 302, F_AVR8X, {0x1E, 0x93, 0x28}, 0, 0x02000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3c00, 0x0400, 10, 1, 30}, // atdf, avrdude + {"ATtiny827", 303, F_AVR8X, {0x1E, 0x93, 0x27}, 0, 0x02000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3c00, 0x0400, 10, 1, 30}, // atdf, avrdude + {"ATtiny1604", 304, F_AVR8X, {0x1E, 0x94, 0x25}, 0, 0x04000, 0x040, 1, 0, 0x01400, 0x0100, 32, 0x3c00, 0x0400, 10, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny1606", 305, F_AVR8X, {0x1E, 0x94, 0x24}, 0, 0x04000, 0x040, 1, 0, 0x01400, 0x0100, 32, 0x3c00, 0x0400, 10, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny1607", 306, F_AVR8X, {0x1E, 0x94, 0x23}, 0, 0x04000, 0x040, 1, 0, 0x01400, 0x0100, 32, 0x3c00, 0x0400, 10, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny1614", 307, F_AVR8X, {0x1E, 0x94, 0x22}, 0, 0x04000, 0x040, 1, 0, 0x01400, 0x0100, 32, 0x3800, 0x0800, 10, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny1616", 308, F_AVR8X, {0x1E, 0x94, 0x21}, 0, 0x04000, 0x040, 1, 0, 0x01400, 0x0100, 32, 0x3800, 0x0800, 10, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny1617", 309, F_AVR8X, {0x1E, 0x94, 0x20}, 0, 0x04000, 0x040, 1, 0, 0x01400, 0x0100, 32, 0x3800, 0x0800, 10, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny1624", 310, F_AVR8X, {0x1E, 0x94, 0x2A}, 0, 0x04000, 0x040, 1, 0, 0x01400, 0x0100, 32, 0x3800, 0x0800, 10, 1, 30}, // atdf, avrdude + {"ATtiny1626", 311, F_AVR8X, {0x1E, 0x94, 0x29}, 0, 0x04000, 0x040, 1, 0, 0x01400, 0x0100, 32, 0x3800, 0x0800, 10, 1, 30}, // atdf, avrdude + {"ATtiny1627", 312, F_AVR8X, {0x1E, 0x94, 0x28}, 0, 0x04000, 0x040, 1, 0, 0x01400, 0x0100, 32, 0x3800, 0x0800, 10, 1, 30}, // atdf, avrdude + {"ATtiny3214", 313, F_AVR8X, {0x1E, 0x95, 0x20}, 0, 0x08000, 0x080, 1, 0, 0x01400, 0x0100, 64, 0x3800, 0x0800, 10, 1, 31}, // avr-gcc 12.2.0 + {"ATtiny3216", 314, F_AVR8X, {0x1E, 0x95, 0x21}, 0, 0x08000, 0x080, 1, 0, 0x01400, 0x0100, 64, 0x3800, 0x0800, 10, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny3217", 315, F_AVR8X, {0x1E, 0x95, 0x22}, 0, 0x08000, 0x080, 1, 0, 0x01400, 0x0100, 64, 0x3800, 0x0800, 10, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude + {"ATtiny3224", 316, F_AVR8X, {0x1E, 0x95, 0x28}, 0, 0x08000, 0x080, 1, 0, 0x01400, 0x0100, 64, 0x3400, 0x0c00, 10, 1, 30}, // atdf, avrdude + {"ATtiny3226", 317, F_AVR8X, {0x1E, 0x95, 0x27}, 0, 0x08000, 0x080, 1, 0, 0x01400, 0x0100, 64, 0x3400, 0x0c00, 10, 1, 30}, // atdf, avrdude + {"ATtiny3227", 318, F_AVR8X, {0x1E, 0x95, 0x26}, 0, 0x08000, 0x080, 1, 0, 0x01400, 0x0100, 64, 0x3400, 0x0c00, 10, 1, 30}, // atdf, avrdude + {"ATmega808", 319, F_AVR8X, {0x1E, 0x93, 0x26}, 0, 0x02000, 0x040, 1, 0, 0x01400, 0x0100, 32, 0x3c00, 0x0400, 10, 1, 36}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega809", 320, F_AVR8X, {0x1E, 0x93, 0x2A}, 0, 0x02000, 0x040, 1, 0, 0x01400, 0x0100, 32, 0x3c00, 0x0400, 10, 1, 40}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega1608", 321, F_AVR8X, {0x1E, 0x94, 0x27}, 0, 0x04000, 0x040, 1, 0, 0x01400, 0x0100, 32, 0x3800, 0x0800, 10, 1, 36}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega1609", 322, F_AVR8X, {0x1E, 0x94, 0x26}, 0, 0x04000, 0x040, 1, 0, 0x01400, 0x0100, 32, 0x3800, 0x0800, 10, 1, 40}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega3208", 323, F_AVR8X, {0x1E, 0x95, 0x30}, 0, 0x08000, 0x080, 1, 0, 0x01400, 0x0100, 64, 0x3000, 0x1000, 10, 1, 36}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega3209", 324, F_AVR8X, {0x1E, 0x95, 0x31}, 0, 0x08000, 0x080, 1, 0, 0x01400, 0x0100, 64, 0x3000, 0x1000, 10, 1, 40}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega4808", 325, F_AVR8X, {0x1E, 0x96, 0x50}, 0, 0x0c000, 0x080, 1, 0, 0x01400, 0x0100, 64, 0x2800, 0x1800, 10, 1, 36}, // atdf, avr-gcc 12.2.0, avrdude + {"ATmega4809", 326, F_AVR8X, {0x1E, 0x96, 0x51}, 0, 0x0c000, 0x080, 1, 0, 0x01400, 0x0100, 64, 0x2800, 0x1800, 10, 1, 40}, // atdf, avr-gcc 12.2.0, avrdude + {"AVR8EA28", 327, F_AVR8X, {0x1E, 0x93, 0x2C}, 0, 0x02000, 0x040, 1, 0, 0x01400, 0x0200, 8, -1, -1, -1, -1, 0}, // avrdude + {"AVR8EA32", 328, F_AVR8X, {0x1E, 0x93, 0x2B}, 0, 0x02000, 0x040, 1, 0, 0x01400, 0x0200, 8, -1, -1, -1, -1, 0}, // avrdude + {"AVR16DD14", 329, F_AVR8X, {0x1E, 0x94, 0x34}, 0, 0x04000, 0x200, 1, 0, 0x01400, 0x0100, 1, 0x7800, 0x0800, 16, 4, 36}, // atdf, avrdude + {"AVR16DD20", 330, F_AVR8X, {0x1E, 0x94, 0x33}, 0, 0x04000, 0x200, 1, 0, 0x01400, 0x0100, 1, 0x7800, 0x0800, 16, 4, 36}, // atdf, avrdude + {"AVR16DD28", 331, F_AVR8X, {0x1E, 0x94, 0x32}, 0, 0x04000, 0x200, 1, 0, 0x01400, 0x0100, 1, 0x7800, 0x0800, 16, 4, 36}, // atdf, avrdude + {"AVR16EA28", 332, F_AVR8X, {0x1E, 0x94, 0x37}, 0, 0x04000, 0x040, 1, 0, 0x01400, 0x0200, 8, -1, -1, -1, -1, 0}, // avrdude + {"AVR16DD32", 333, F_AVR8X, {0x1E, 0x94, 0x31}, 0, 0x04000, 0x200, 1, 0, 0x01400, 0x0100, 1, 0x7800, 0x0800, 16, 4, 36}, // atdf, avrdude + {"AVR16EA32", 334, F_AVR8X, {0x1E, 0x94, 0x36}, 0, 0x04000, 0x040, 1, 0, 0x01400, 0x0200, 8, -1, -1, -1, -1, 0}, // avrdude + {"AVR16EA48", 335, F_AVR8X, {0x1E, 0x94, 0x35}, 0, 0x04000, 0x040, 1, 0, 0x01400, 0x0200, 8, -1, -1, -1, -1, 0}, // avrdude + {"AVR32DD14", 336, F_AVR8X, {0x1E, 0x95, 0x3B}, 0, 0x08000, 0x200, 1, 0, 0x01400, 0x0100, 1, 0x7000, 0x1000, 16, 4, 36}, // atdf, avrdude + {"AVR32DD20", 337, F_AVR8X, {0x1E, 0x95, 0x3A}, 0, 0x08000, 0x200, 1, 0, 0x01400, 0x0100, 1, 0x7000, 0x1000, 16, 4, 36}, // atdf, avrdude + {"AVR32DA28", 338, F_AVR8X, {0x1E, 0x95, 0x34}, 0, 0x08000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x7000, 0x1000, 16, 4, 41}, // atdf, avrdude + {"AVR32DB28", 339, F_AVR8X, {0x1E, 0x95, 0x37}, 0, 0x08000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x7000, 0x1000, 16, 4, 42}, // atdf, avrdude + {"AVR32DD28", 340, F_AVR8X, {0x1E, 0x95, 0x39}, 0, 0x08000, 0x200, 1, 0, 0x01400, 0x0100, 1, 0x7000, 0x1000, 16, 4, 36}, // atdf, avrdude + {"AVR32EA28", 341, F_AVR8X, {0x1E, 0x95, 0x3E}, 0, 0x08000, 0x040, 1, 0, 0x01400, 0x0200, 8, -1, -1, -1, -1, 0}, // avrdude + {"AVR32DA32", 342, F_AVR8X, {0x1E, 0x95, 0x33}, 0, 0x08000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x7000, 0x1000, 16, 4, 44}, // atdf, avrdude + {"AVR32DB32", 343, F_AVR8X, {0x1E, 0x95, 0x36}, 0, 0x08000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x7000, 0x1000, 16, 4, 44}, // atdf, avrdude + {"AVR32DD32", 344, F_AVR8X, {0x1E, 0x95, 0x38}, 0, 0x08000, 0x200, 1, 0, 0x01400, 0x0100, 1, 0x7000, 0x1000, 16, 4, 36}, // atdf, avrdude + {"AVR32EA32", 345, F_AVR8X, {0x1E, 0x95, 0x3D}, 0, 0x08000, 0x040, 1, 0, 0x01400, 0x0200, 8, -1, -1, -1, -1, 0}, // avrdude + {"AVR32DA48", 346, F_AVR8X, {0x1E, 0x95, 0x32}, 0, 0x08000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x7000, 0x1000, 16, 4, 58}, // atdf, avrdude + {"AVR32DB48", 347, F_AVR8X, {0x1E, 0x95, 0x35}, 0, 0x08000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x7000, 0x1000, 16, 4, 61}, // atdf, avrdude + {"AVR32EA48", 348, F_AVR8X, {0x1E, 0x95, 0x3C}, 0, 0x08000, 0x040, 1, 0, 0x01400, 0x0200, 8, -1, -1, -1, -1, 0}, // avrdude + {"AVR64DD14", 349, F_AVR8X, {0x1E, 0x96, 0x1D}, 0, 0x10000, 0x200, 1, 0, 0x01400, 0x0100, 1, 0x6000, 0x2000, 16, 4, 36}, // atdf, avrdude + {"AVR64DD20", 350, F_AVR8X, {0x1E, 0x96, 0x1C}, 0, 0x10000, 0x200, 1, 0, 0x01400, 0x0100, 1, 0x6000, 0x2000, 16, 4, 36}, // atdf, avrdude + {"AVR64DA28", 351, F_AVR8X, {0x1E, 0x96, 0x15}, 0, 0x10000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x6000, 0x2000, 16, 4, 41}, // atdf, avrdude + {"AVR64DB28", 352, F_AVR8X, {0x1E, 0x96, 0x19}, 0, 0x10000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x6000, 0x2000, 16, 4, 42}, // atdf, avrdude + {"AVR64DD28", 353, F_AVR8X, {0x1E, 0x96, 0x1B}, 0, 0x10000, 0x200, 1, 0, 0x01400, 0x0100, 1, 0x6000, 0x2000, 16, 4, 36}, // atdf, avrdude + {"AVR64EA28", 354, F_AVR8X, {0x1E, 0x96, 0x20}, 0, 0x10000, 0x080, 1, 0, 0x01400, 0x0200, 8, 0x6800, 0x1800, 16, 4, 37}, // atdf, avrdude + {"AVR64DA32", 355, F_AVR8X, {0x1E, 0x96, 0x14}, 0, 0x10000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x6000, 0x2000, 16, 4, 44}, // atdf, avrdude + {"AVR64DB32", 356, F_AVR8X, {0x1E, 0x96, 0x18}, 0, 0x10000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x6000, 0x2000, 16, 4, 44}, // atdf, avrdude + {"AVR64DD32", 357, F_AVR8X, {0x1E, 0x96, 0x1A}, 0, 0x10000, 0x200, 1, 0, 0x01400, 0x0100, 1, 0x6000, 0x2000, 16, 4, 36}, // atdf, avrdude + {"AVR64EA32", 358, F_AVR8X, {0x1E, 0x96, 0x1F}, 0, 0x10000, 0x080, 1, 0, 0x01400, 0x0200, 8, 0x6800, 0x1800, 16, 4, 37}, // atdf, avrdude + {"AVR64DA48", 359, F_AVR8X, {0x1E, 0x96, 0x13}, 0, 0x10000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x6000, 0x2000, 16, 4, 58}, // atdf, avrdude + {"AVR64DB48", 360, F_AVR8X, {0x1E, 0x96, 0x17}, 0, 0x10000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x6000, 0x2000, 16, 4, 61}, // atdf, avrdude + {"AVR64EA48", 361, F_AVR8X, {0x1E, 0x96, 0x1E}, 0, 0x10000, 0x080, 1, 0, 0x01400, 0x0200, 8, 0x6800, 0x1800, 16, 4, 45}, // atdf, avrdude + {"AVR64DA64", 362, F_AVR8X, {0x1E, 0x96, 0x12}, 0, 0x10000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x6000, 0x2000, 16, 4, 64}, // atdf, avrdude + {"AVR64DB64", 363, F_AVR8X, {0x1E, 0x96, 0x16}, 0, 0x10000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x6000, 0x2000, 16, 4, 65}, // atdf, avrdude + {"AVR128DA28", 364, F_AVR8X, {0x1E, 0x97, 0x0A}, 0, 0x20000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x4000, 0x4000, 16, 4, 41}, // atdf, avrdude + {"AVR128DB28", 365, F_AVR8X, {0x1E, 0x97, 0x0E}, 0, 0x20000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x4000, 0x4000, 16, 4, 42}, // atdf, avrdude + {"AVR128DA32", 366, F_AVR8X, {0x1E, 0x97, 0x09}, 0, 0x20000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x4000, 0x4000, 16, 4, 44}, // atdf, avrdude + {"AVR128DB32", 367, F_AVR8X, {0x1E, 0x97, 0x0D}, 0, 0x20000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x4000, 0x4000, 16, 4, 44}, // atdf, avrdude + {"AVR128DA48", 368, F_AVR8X, {0x1E, 0x97, 0x08}, 0, 0x20000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x4000, 0x4000, 16, 4, 58}, // atdf, avrdude + {"AVR128DB48", 369, F_AVR8X, {0x1E, 0x97, 0x0C}, 0, 0x20000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x4000, 0x4000, 16, 4, 61}, // atdf, avrdude + {"AVR128DA64", 370, F_AVR8X, {0x1E, 0x97, 0x07}, 0, 0x20000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x4000, 0x4000, 16, 4, 64}, // atdf, avrdude + {"AVR128DB64", 371, F_AVR8X, {0x1E, 0x97, 0x0B}, 0, 0x20000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x4000, 0x4000, 16, 4, 65}, // atdf, avrdude +}; + +const size_t avr_isp_chip_arr_size = COUNT_OF(avr_isp_chip_arr); \ No newline at end of file diff --git a/applications/external/avr_isp_programmer/lib/driver/avr_isp_chip_arr.h b/applications/external/avr_isp_programmer/lib/driver/avr_isp_chip_arr.h new file mode 100644 index 00000000000..66f16a7b96a --- /dev/null +++ b/applications/external/avr_isp_programmer/lib/driver/avr_isp_chip_arr.h @@ -0,0 +1,33 @@ +#pragma once + +#include + +#define F_AVR8L 1 // TPI programming, ATtiny(4|5|9|10|20|40|102|104) +#define F_AVR8 2 // ISP programming with SPI, "classic" AVRs +#define F_XMEGA 4 // PDI programming, ATxmega family +#define F_AVR8X 8 // UPDI programming, newer 8-bit MCUs + +struct AvrIspChipArr { // Value of -1 typically means unknown + const char* name; // Name of part + uint16_t mcuid; // ID of MCU in 0..2039 + uint8_t avrarch; // F_AVR8L, F_AVR8, F_XMEGA or F_AVR8X + uint8_t sigs[3]; // Signature bytes + int32_t flashoffset; // Flash offset + int32_t flashsize; // Flash size + int16_t pagesize; // Flash page size + int8_t nboots; // Number of supported boot sectors + int16_t bootsize; // Size of (smallest) boot sector + int32_t eepromoffset; // EEPROM offset + int32_t eepromsize; // EEPROM size + int32_t eeprompagesize; // EEPROM page size + int32_t sramstart; // SRAM offset + int32_t sramsize; // SRAM size + int8_t nfuses; // Number of fuse bytes + int8_t nlocks; // Number of lock bytes + uint8_t ninterrupts; // Number of vectors in interrupt vector table +}; + +typedef struct AvrIspChipArr AvrIspChipArr; + +extern const AvrIspChipArr avr_isp_chip_arr[]; +extern const size_t avr_isp_chip_arr_size; \ No newline at end of file diff --git a/applications/external/avr_isp_programmer/lib/driver/avr_isp_prog.c b/applications/external/avr_isp_programmer/lib/driver/avr_isp_prog.c new file mode 100644 index 00000000000..b457e4c27b7 --- /dev/null +++ b/applications/external/avr_isp_programmer/lib/driver/avr_isp_prog.c @@ -0,0 +1,633 @@ +#include "avr_isp_prog.h" +#include "avr_isp_prog_cmd.h" + +#include + +#define AVR_ISP_PROG_TX_RX_BUF_SIZE 320 +#define TAG "AvrIspProg" + +struct AvrIspProgSignature { + uint8_t vendor; + uint8_t part_family; + uint8_t part_number; +}; + +typedef struct AvrIspProgSignature AvrIspProgSignature; + +struct AvrIspProgCfgDevice { + uint8_t devicecode; + uint8_t revision; + uint8_t progtype; + uint8_t parmode; + uint8_t polling; + uint8_t selftimed; + uint8_t lockbytes; + uint8_t fusebytes; + uint8_t flashpoll; + uint16_t eeprompoll; + uint16_t pagesize; + uint16_t eepromsize; + uint32_t flashsize; +}; + +typedef struct AvrIspProgCfgDevice AvrIspProgCfgDevice; + +struct AvrIspProg { + AvrIspSpiSw* spi; + AvrIspProgCfgDevice* cfg; + FuriStreamBuffer* stream_rx; + FuriStreamBuffer* stream_tx; + + uint16_t error; + uint16_t addr; + bool pmode; + bool exit; + bool rst_active_high; + uint8_t buff[AVR_ISP_PROG_TX_RX_BUF_SIZE]; + + AvrIspProgCallback callback; + void* context; +}; + +static void avr_isp_prog_end_pmode(AvrIspProg* instance); + +AvrIspProg* avr_isp_prog_init(void) { + AvrIspProg* instance = malloc(sizeof(AvrIspProg)); + instance->cfg = malloc(sizeof(AvrIspProgCfgDevice)); + instance->stream_rx = + furi_stream_buffer_alloc(sizeof(int8_t) * AVR_ISP_PROG_TX_RX_BUF_SIZE, sizeof(int8_t)); + instance->stream_tx = + furi_stream_buffer_alloc(sizeof(int8_t) * AVR_ISP_PROG_TX_RX_BUF_SIZE, sizeof(int8_t)); + instance->rst_active_high = false; + instance->exit = false; + return instance; +} + +void avr_isp_prog_free(AvrIspProg* instance) { + furi_assert(instance); + if(instance->spi) avr_isp_prog_end_pmode(instance); + furi_stream_buffer_free(instance->stream_tx); + furi_stream_buffer_free(instance->stream_rx); + free(instance->cfg); + free(instance); +} + +size_t avr_isp_prog_spaces_rx(AvrIspProg* instance) { + return furi_stream_buffer_spaces_available(instance->stream_rx); +} + +bool avr_isp_prog_rx(AvrIspProg* instance, uint8_t* data, size_t len) { + furi_assert(instance); + furi_assert(data); + furi_assert(len != 0); + size_t ret = furi_stream_buffer_send(instance->stream_rx, data, sizeof(uint8_t) * len, 0); + return ret == sizeof(uint8_t) * len; +} + +size_t avr_isp_prog_tx(AvrIspProg* instance, uint8_t* data, size_t max_len) { + furi_assert(instance); + return furi_stream_buffer_receive(instance->stream_tx, data, sizeof(int8_t) * max_len, 0); +} + +void avr_isp_prog_exit(AvrIspProg* instance) { + furi_assert(instance); + instance->exit = true; +} + +void avr_isp_prog_set_tx_callback(AvrIspProg* instance, AvrIspProgCallback callback, void* context) { + furi_assert(instance); + furi_assert(context); + instance->callback = callback; + instance->context = context; +} + +static void avr_isp_prog_tx_ch(AvrIspProg* instance, uint8_t data) { + furi_assert(instance); + furi_stream_buffer_send(instance->stream_tx, &data, sizeof(uint8_t), FuriWaitForever); +} + +static uint8_t avr_isp_prog_getch(AvrIspProg* instance) { + furi_assert(instance); + uint8_t data[1] = {0}; + while(furi_stream_buffer_receive(instance->stream_rx, &data, sizeof(int8_t), 30) == 0) { + if(instance->exit) break; + }; + return data[0]; +} + +static void avr_isp_prog_fill(AvrIspProg* instance, size_t len) { + furi_assert(instance); + for(size_t x = 0; x < len; x++) { + instance->buff[x] = avr_isp_prog_getch(instance); + } +} + +static void avr_isp_prog_reset_target(AvrIspProg* instance, bool reset) { + furi_assert(instance); + avr_isp_spi_sw_res_set(instance->spi, (reset == instance->rst_active_high) ? true : false); +} + +static uint8_t avr_isp_prog_spi_transaction( + AvrIspProg* instance, + uint8_t cmd, + uint8_t addr_hi, + uint8_t addr_lo, + uint8_t data) { + furi_assert(instance); + + avr_isp_spi_sw_txrx(instance->spi, cmd); + avr_isp_spi_sw_txrx(instance->spi, addr_hi); + avr_isp_spi_sw_txrx(instance->spi, addr_lo); + return avr_isp_spi_sw_txrx(instance->spi, data); +} + +static void avr_isp_prog_empty_reply(AvrIspProg* instance) { + furi_assert(instance); + if(avr_isp_prog_getch(instance) == CRC_EOP) { + avr_isp_prog_tx_ch(instance, STK_INSYNC); + avr_isp_prog_tx_ch(instance, STK_OK); + } else { + instance->error++; + avr_isp_prog_tx_ch(instance, STK_NOSYNC); + } +} + +static void avr_isp_prog_breply(AvrIspProg* instance, uint8_t data) { + furi_assert(instance); + if(avr_isp_prog_getch(instance) == CRC_EOP) { + avr_isp_prog_tx_ch(instance, STK_INSYNC); + avr_isp_prog_tx_ch(instance, data); + avr_isp_prog_tx_ch(instance, STK_OK); + } else { + instance->error++; + avr_isp_prog_tx_ch(instance, STK_NOSYNC); + } +} + +static void avr_isp_prog_get_version(AvrIspProg* instance, uint8_t data) { + furi_assert(instance); + switch(data) { + case STK_HW_VER: + avr_isp_prog_breply(instance, AVR_ISP_HWVER); + break; + case STK_SW_MAJOR: + avr_isp_prog_breply(instance, AVR_ISP_SWMAJ); + break; + case STK_SW_MINOR: + avr_isp_prog_breply(instance, AVR_ISP_SWMIN); + break; + case AVP_ISP_CONNECT_TYPE: + avr_isp_prog_breply(instance, AVP_ISP_SERIAL_CONNECT_TYPE); + break; + default: + avr_isp_prog_breply(instance, AVR_ISP_RESP_0); + } +} + +static void avr_isp_prog_set_cfg(AvrIspProg* instance) { + furi_assert(instance); + // call this after reading cfg packet into buff[] + instance->cfg->devicecode = instance->buff[0]; + instance->cfg->revision = instance->buff[1]; + instance->cfg->progtype = instance->buff[2]; + instance->cfg->parmode = instance->buff[3]; + instance->cfg->polling = instance->buff[4]; + instance->cfg->selftimed = instance->buff[5]; + instance->cfg->lockbytes = instance->buff[6]; + instance->cfg->fusebytes = instance->buff[7]; + instance->cfg->flashpoll = instance->buff[8]; + // ignore (instance->buff[9] == instance->buff[8]) //FLASH polling value. Same as flashpoll + instance->cfg->eeprompoll = instance->buff[10] << 8 | instance->buff[11]; + instance->cfg->pagesize = instance->buff[12] << 8 | instance->buff[13]; + instance->cfg->eepromsize = instance->buff[14] << 8 | instance->buff[15]; + instance->cfg->flashsize = instance->buff[16] << 24 | instance->buff[17] << 16 | + instance->buff[18] << 8 | instance->buff[19]; + + // avr devices have active low reset, at89sx are active high + instance->rst_active_high = (instance->cfg->devicecode >= 0xe0); +} +static bool + avr_isp_prog_set_pmode(AvrIspProg* instance, uint8_t a, uint8_t b, uint8_t c, uint8_t d) { + furi_assert(instance); + uint8_t res = 0; + avr_isp_spi_sw_txrx(instance->spi, a); + avr_isp_spi_sw_txrx(instance->spi, b); + res = avr_isp_spi_sw_txrx(instance->spi, c); + avr_isp_spi_sw_txrx(instance->spi, d); + return res == 0x53; +} + +static void avr_isp_prog_end_pmode(AvrIspProg* instance) { + furi_assert(instance); + if(instance->pmode) { + avr_isp_prog_reset_target(instance, false); + // We're about to take the target out of reset + // so configure SPI pins as input + + if(instance->spi) avr_isp_spi_sw_free(instance->spi); + instance->spi = NULL; + } + + instance->pmode = false; +} + +static bool avr_isp_prog_start_pmode(AvrIspProg* instance, AvrIspSpiSwSpeed spi_speed) { + furi_assert(instance); + // Reset target before driving PIN_SCK or PIN_MOSI + + // SPI.begin() will configure SS as output, + // so SPI master mode is selected. + // We have defined RESET as pin 10, + // which for many arduino's is not the SS pin. + // So we have to configure RESET as output here, + // (reset_target() first sets the correct level) + if(instance->spi) avr_isp_spi_sw_free(instance->spi); + instance->spi = avr_isp_spi_sw_init(spi_speed); + + avr_isp_prog_reset_target(instance, true); + // See avr datasheets, chapter "SERIAL_PRG Programming Algorithm": + + // Pulse RESET after PIN_SCK is low: + avr_isp_spi_sw_sck_set(instance->spi, false); + + // discharge PIN_SCK, value arbitrally chosen + furi_delay_ms(20); + avr_isp_prog_reset_target(instance, false); + + // Pulse must be minimum 2 target CPU speed cycles + // so 100 usec is ok for CPU speeds above 20KHz + furi_delay_ms(1); + + avr_isp_prog_reset_target(instance, true); + + // Send the enable programming command: + // datasheet: must be > 20 msec + furi_delay_ms(50); + if(avr_isp_prog_set_pmode(instance, AVR_ISP_SET_PMODE)) { + instance->pmode = true; + return true; + } + return false; +} + +static AvrIspProgSignature avr_isp_prog_check_signature(AvrIspProg* instance) { + furi_assert(instance); + AvrIspProgSignature signature; + signature.vendor = avr_isp_prog_spi_transaction(instance, AVR_ISP_READ_VENDOR); + signature.part_family = avr_isp_prog_spi_transaction(instance, AVR_ISP_READ_PART_FAMILY); + signature.part_number = avr_isp_prog_spi_transaction(instance, AVR_ISP_READ_PART_NUMBER); + return signature; +} + +static bool avr_isp_prog_auto_set_spi_speed_start_pmode(AvrIspProg* instance) { + AvrIspSpiSwSpeed spi_speed[] = { + AvrIspSpiSwSpeed1Mhz, + AvrIspSpiSwSpeed400Khz, + AvrIspSpiSwSpeed250Khz, + AvrIspSpiSwSpeed125Khz, + AvrIspSpiSwSpeed60Khz, + AvrIspSpiSwSpeed40Khz, + AvrIspSpiSwSpeed20Khz, + AvrIspSpiSwSpeed10Khz, + AvrIspSpiSwSpeed5Khz, + AvrIspSpiSwSpeed1Khz, + }; + for(uint8_t i = 0; i < COUNT_OF(spi_speed); i++) { + if(avr_isp_prog_start_pmode(instance, spi_speed[i])) { + AvrIspProgSignature sig = avr_isp_prog_check_signature(instance); + AvrIspProgSignature sig_examination = avr_isp_prog_check_signature(instance); //-V656 + uint8_t y = 0; + while(y < 8) { + if(memcmp( + (uint8_t*)&sig, (uint8_t*)&sig_examination, sizeof(AvrIspProgSignature)) != + 0) + break; + sig_examination = avr_isp_prog_check_signature(instance); + y++; + } + if(y == 8) { + if(spi_speed[i] > AvrIspSpiSwSpeed1Mhz) { + if(i < (COUNT_OF(spi_speed) - 1)) { + avr_isp_prog_end_pmode(instance); + i++; + return avr_isp_prog_start_pmode(instance, spi_speed[i]); + } + } + return true; + } + } + } + return false; +} + +static void avr_isp_prog_universal(AvrIspProg* instance) { + furi_assert(instance); + uint8_t data; + + avr_isp_prog_fill(instance, 4); + data = avr_isp_prog_spi_transaction( + instance, instance->buff[0], instance->buff[1], instance->buff[2], instance->buff[3]); + avr_isp_prog_breply(instance, data); +} + +static void avr_isp_prog_commit(AvrIspProg* instance, uint16_t addr, uint8_t data) { + furi_assert(instance); + avr_isp_prog_spi_transaction(instance, AVR_ISP_COMMIT(addr)); + /* polling flash */ + if(data == 0xFF) { + furi_delay_ms(5); + } else { + /* polling flash */ + uint32_t starttime = furi_get_tick(); + while((furi_get_tick() - starttime) < 30) { + if(avr_isp_prog_spi_transaction(instance, AVR_ISP_READ_FLASH_HI(addr)) != 0xFF) { + break; + }; + } + } +} + +static uint16_t avr_isp_prog_current_page(AvrIspProg* instance) { + furi_assert(instance); + uint16_t page = 0; + switch(instance->cfg->pagesize) { + case 32: + page = instance->addr & 0xFFFFFFF0; + break; + case 64: + page = instance->addr & 0xFFFFFFE0; + break; + case 128: + page = instance->addr & 0xFFFFFFC0; + break; + case 256: + page = instance->addr & 0xFFFFFF80; + break; + + default: + page = instance->addr; + break; + } + + return page; +} + +static uint8_t avr_isp_prog_write_flash_pages(AvrIspProg* instance, size_t length) { + furi_assert(instance); + size_t x = 0; + uint16_t page = avr_isp_prog_current_page(instance); + while(x < length) { + if(page != avr_isp_prog_current_page(instance)) { + --x; + avr_isp_prog_commit(instance, page, instance->buff[x++]); + page = avr_isp_prog_current_page(instance); + } + avr_isp_prog_spi_transaction( + instance, AVR_ISP_WRITE_FLASH_LO(instance->addr, instance->buff[x++])); + + avr_isp_prog_spi_transaction( + instance, AVR_ISP_WRITE_FLASH_HI(instance->addr, instance->buff[x++])); + instance->addr++; + } + + avr_isp_prog_commit(instance, page, instance->buff[--x]); + return STK_OK; +} + +static void avr_isp_prog_write_flash(AvrIspProg* instance, size_t length) { + furi_assert(instance); + avr_isp_prog_fill(instance, length); + if(avr_isp_prog_getch(instance) == CRC_EOP) { + avr_isp_prog_tx_ch(instance, STK_INSYNC); + avr_isp_prog_tx_ch(instance, avr_isp_prog_write_flash_pages(instance, length)); + } else { + instance->error++; + avr_isp_prog_tx_ch(instance, STK_NOSYNC); + } +} + +// write (length) bytes, (start) is a byte address +static uint8_t + avr_isp_prog_write_eeprom_chunk(AvrIspProg* instance, uint16_t start, uint16_t length) { + furi_assert(instance); + // this writes byte-by-byte, + // page writing may be faster (4 bytes at a time) + avr_isp_prog_fill(instance, length); + for(uint16_t x = 0; x < length; x++) { + uint16_t addr = start + x; + avr_isp_prog_spi_transaction(instance, AVR_ISP_WRITE_EEPROM(addr, instance->buff[x])); + furi_delay_ms(10); + } + return STK_OK; +} + +static uint8_t avr_isp_prog_write_eeprom(AvrIspProg* instance, size_t length) { + furi_assert(instance); + // here is a word address, get the byte address + uint16_t start = instance->addr * 2; + uint16_t remaining = length; + if(length > instance->cfg->eepromsize) { + instance->error++; + return STK_FAILED; + } + while(remaining > AVR_ISP_EECHUNK) { + avr_isp_prog_write_eeprom_chunk(instance, start, AVR_ISP_EECHUNK); + start += AVR_ISP_EECHUNK; + remaining -= AVR_ISP_EECHUNK; + } + avr_isp_prog_write_eeprom_chunk(instance, start, remaining); + return STK_OK; +} + +static void avr_isp_prog_program_page(AvrIspProg* instance) { + furi_assert(instance); + uint8_t result = STK_FAILED; + uint16_t length = avr_isp_prog_getch(instance) << 8 | avr_isp_prog_getch(instance); + uint8_t memtype = avr_isp_prog_getch(instance); + // flash memory @addr, (length) bytes + if(memtype == STK_SET_FLASH_TYPE) { + avr_isp_prog_write_flash(instance, length); + return; + } + if(memtype == STK_SET_EEPROM_TYPE) { + result = avr_isp_prog_write_eeprom(instance, length); + if(avr_isp_prog_getch(instance) == CRC_EOP) { + avr_isp_prog_tx_ch(instance, STK_INSYNC); + avr_isp_prog_tx_ch(instance, result); + + } else { + instance->error++; + avr_isp_prog_tx_ch(instance, STK_NOSYNC); + } + return; + } + avr_isp_prog_tx_ch(instance, STK_FAILED); + return; +} + +static uint8_t avr_isp_prog_flash_read_page(AvrIspProg* instance, uint16_t length) { + furi_assert(instance); + for(uint16_t x = 0; x < length; x += 2) { + avr_isp_prog_tx_ch( + instance, + avr_isp_prog_spi_transaction(instance, AVR_ISP_READ_FLASH_LO(instance->addr))); + avr_isp_prog_tx_ch( + instance, + avr_isp_prog_spi_transaction(instance, AVR_ISP_READ_FLASH_HI(instance->addr))); + instance->addr++; + } + return STK_OK; +} + +static uint8_t avr_isp_prog_eeprom_read_page(AvrIspProg* instance, uint16_t length) { + furi_assert(instance); + // here again we have a word address + uint16_t start = instance->addr * 2; + for(uint16_t x = 0; x < length; x++) { + uint16_t addr = start + x; + avr_isp_prog_tx_ch( + instance, avr_isp_prog_spi_transaction(instance, AVR_ISP_READ_EEPROM(addr))); + } + return STK_OK; +} + +static void avr_isp_prog_read_page(AvrIspProg* instance) { + furi_assert(instance); + uint8_t result = STK_FAILED; + uint16_t length = avr_isp_prog_getch(instance) << 8 | avr_isp_prog_getch(instance); + uint8_t memtype = avr_isp_prog_getch(instance); + if(avr_isp_prog_getch(instance) != CRC_EOP) { + instance->error++; + avr_isp_prog_tx_ch(instance, STK_NOSYNC); + return; + } + avr_isp_prog_tx_ch(instance, STK_INSYNC); + if(memtype == STK_SET_FLASH_TYPE) result = avr_isp_prog_flash_read_page(instance, length); + if(memtype == STK_SET_EEPROM_TYPE) result = avr_isp_prog_eeprom_read_page(instance, length); + avr_isp_prog_tx_ch(instance, result); +} + +static void avr_isp_prog_read_signature(AvrIspProg* instance) { + furi_assert(instance); + if(avr_isp_prog_getch(instance) != CRC_EOP) { + instance->error++; + avr_isp_prog_tx_ch(instance, STK_NOSYNC); + return; + } + avr_isp_prog_tx_ch(instance, STK_INSYNC); + + avr_isp_prog_tx_ch(instance, avr_isp_prog_spi_transaction(instance, AVR_ISP_READ_VENDOR)); + avr_isp_prog_tx_ch(instance, avr_isp_prog_spi_transaction(instance, AVR_ISP_READ_PART_FAMILY)); + avr_isp_prog_tx_ch(instance, avr_isp_prog_spi_transaction(instance, AVR_ISP_READ_PART_NUMBER)); + + avr_isp_prog_tx_ch(instance, STK_OK); +} + +void avr_isp_prog_avrisp(AvrIspProg* instance) { + furi_assert(instance); + uint8_t ch = avr_isp_prog_getch(instance); + + switch(ch) { + case STK_GET_SYNC: + FURI_LOG_D(TAG, "cmd STK_GET_SYNC"); + instance->error = 0; + avr_isp_prog_empty_reply(instance); + break; + case STK_GET_SIGN_ON: + FURI_LOG_D(TAG, "cmd STK_GET_SIGN_ON"); + if(avr_isp_prog_getch(instance) == CRC_EOP) { + avr_isp_prog_tx_ch(instance, STK_INSYNC); + + avr_isp_prog_tx_ch(instance, 'A'); + avr_isp_prog_tx_ch(instance, 'V'); + avr_isp_prog_tx_ch(instance, 'R'); + avr_isp_prog_tx_ch(instance, ' '); + avr_isp_prog_tx_ch(instance, 'I'); + avr_isp_prog_tx_ch(instance, 'S'); + avr_isp_prog_tx_ch(instance, 'P'); + + avr_isp_prog_tx_ch(instance, STK_OK); + } else { + instance->error++; + avr_isp_prog_tx_ch(instance, STK_NOSYNC); + } + break; + case STK_GET_PARAMETER: + FURI_LOG_D(TAG, "cmd STK_GET_PARAMETER"); + avr_isp_prog_get_version(instance, avr_isp_prog_getch(instance)); + break; + case STK_SET_DEVICE: + FURI_LOG_D(TAG, "cmd STK_SET_DEVICE"); + avr_isp_prog_fill(instance, 20); + avr_isp_prog_set_cfg(instance); + avr_isp_prog_empty_reply(instance); + break; + case STK_SET_DEVICE_EXT: // ignore for now + FURI_LOG_D(TAG, "cmd STK_SET_DEVICE_EXT"); + avr_isp_prog_fill(instance, 5); + avr_isp_prog_empty_reply(instance); + break; + case STK_ENTER_PROGMODE: + FURI_LOG_D(TAG, "cmd STK_ENTER_PROGMODE"); + if(!instance->pmode) avr_isp_prog_auto_set_spi_speed_start_pmode(instance); + avr_isp_prog_empty_reply(instance); + break; + case STK_LOAD_ADDRESS: + FURI_LOG_D(TAG, "cmd STK_LOAD_ADDRESS"); + instance->addr = avr_isp_prog_getch(instance) | avr_isp_prog_getch(instance) << 8; + avr_isp_prog_empty_reply(instance); + break; + case STK_PROG_FLASH: // ignore for now + FURI_LOG_D(TAG, "cmd STK_PROG_FLASH"); + avr_isp_prog_getch(instance); + avr_isp_prog_getch(instance); + avr_isp_prog_empty_reply(instance); + break; + case STK_PROG_DATA: // ignore for now + FURI_LOG_D(TAG, "cmd STK_PROG_DATA"); + avr_isp_prog_getch(instance); + avr_isp_prog_empty_reply(instance); + break; + case STK_PROG_PAGE: + FURI_LOG_D(TAG, "cmd STK_PROG_PAGE"); + avr_isp_prog_program_page(instance); + break; + case STK_READ_PAGE: + FURI_LOG_D(TAG, "cmd STK_READ_PAGE"); + avr_isp_prog_read_page(instance); + break; + case STK_UNIVERSAL: + FURI_LOG_D(TAG, "cmd STK_UNIVERSAL"); + avr_isp_prog_universal(instance); + break; + case STK_LEAVE_PROGMODE: + FURI_LOG_D(TAG, "cmd STK_LEAVE_PROGMODE"); + instance->error = 0; + if(instance->pmode) avr_isp_prog_end_pmode(instance); + avr_isp_prog_empty_reply(instance); + break; + case STK_READ_SIGN: + FURI_LOG_D(TAG, "cmd STK_READ_SIGN"); + avr_isp_prog_read_signature(instance); + break; + // expecting a command, not CRC_EOP + // this is how we can get back in sync + case CRC_EOP: + FURI_LOG_D(TAG, "cmd CRC_EOP"); + instance->error++; + avr_isp_prog_tx_ch(instance, STK_NOSYNC); + break; + // anything else we will return STK_UNKNOWN + default: + FURI_LOG_D(TAG, "cmd STK_ERROR_CMD"); + instance->error++; + if(avr_isp_prog_getch(instance) == CRC_EOP) + avr_isp_prog_tx_ch(instance, STK_UNKNOWN); + else + avr_isp_prog_tx_ch(instance, STK_NOSYNC); + } + + if(instance->callback) { + instance->callback(instance->context); + } +} diff --git a/applications/external/avr_isp_programmer/lib/driver/avr_isp_prog.h b/applications/external/avr_isp_programmer/lib/driver/avr_isp_prog.h new file mode 100644 index 00000000000..2c15ab06636 --- /dev/null +++ b/applications/external/avr_isp_programmer/lib/driver/avr_isp_prog.h @@ -0,0 +1,16 @@ +#pragma once + +#include "avr_isp_spi_sw.h" +#include + +typedef struct AvrIspProg AvrIspProg; +typedef void (*AvrIspProgCallback)(void* context); + +AvrIspProg* avr_isp_prog_init(void); +void avr_isp_prog_free(AvrIspProg* instance); +size_t avr_isp_prog_spaces_rx(AvrIspProg* instance) ; +bool avr_isp_prog_rx(AvrIspProg* instance, uint8_t* data, size_t len); +size_t avr_isp_prog_tx(AvrIspProg* instance, uint8_t* data, size_t max_len); +void avr_isp_prog_avrisp(AvrIspProg* instance); +void avr_isp_prog_exit(AvrIspProg* instance); +void avr_isp_prog_set_tx_callback(AvrIspProg* instance, AvrIspProgCallback callback, void* context); diff --git a/applications/external/avr_isp_programmer/lib/driver/avr_isp_prog_cmd.h b/applications/external/avr_isp_programmer/lib/driver/avr_isp_prog_cmd.h new file mode 100644 index 00000000000..f8b07203e66 --- /dev/null +++ b/applications/external/avr_isp_programmer/lib/driver/avr_isp_prog_cmd.h @@ -0,0 +1,97 @@ +#pragma once + +// http://ww1.microchip.com/downloads/en/appnotes/atmel-0943-in-system-programming_applicationnote_avr910.pdf +// AVR ISP Definitions +#define AVR_ISP_HWVER 0X02 +#define AVR_ISP_SWMAJ 0X01 +#define AVR_ISP_SWMIN 0X12 +#define AVP_ISP_SERIAL_CONNECT_TYPE 0X53 +#define AVP_ISP_CONNECT_TYPE 0x93 +#define AVR_ISP_RESP_0 0X00 + +#define AVR_ISP_SET_PMODE 0xAC, 0x53, 0x00, 0x00 +#define AVR_ISP_READ_VENDOR 0x30, 0x00, 0x00, 0x00 +#define AVR_ISP_READ_PART_FAMILY 0x30, 0x00, 0x01, 0x00 +#define AVR_ISP_READ_PART_NUMBER 0x30, 0x00, 0x02, 0x00 +#define AVR_ISP_ERASE_CHIP \ + 0xAC, 0x80, 0x00, 0x00 //Erase Chip, Wait N ms, Release RESET to end the erase. +//The only way to end a Chip Erase cycle is by temporarily releasing the Reset line + +#define AVR_ISP_EXTENDED_ADDR(data) 0x4D, 0x00, data, 0x00 +#define AVR_ISP_WRITE_FLASH_LO(add, data) 0x40, (add >> 8) & 0xFF, add & 0xFF, data +#define AVR_ISP_WRITE_FLASH_HI(add, data) 0x48, (add >> 8) & 0xFF, add & 0xFF, data +#define AVR_ISP_READ_FLASH_LO(add) 0x20, (add >> 8) & 0xFF, add & 0xFF, 0x00 +#define AVR_ISP_READ_FLASH_HI(add) 0x28, (add >> 8) & 0xFF, add & 0xFF, 0x00 + +#define AVR_ISP_WRITE_EEPROM(add, data) \ + 0xC0, (add >> 8) & 0xFF, add & 0xFF, data //Send cmd, Wait N ms +#define AVR_ISP_READ_EEPROM(add) 0xA0, (add >> 8) & 0xFF, add & 0xFF, 0xFF + +#define AVR_ISP_COMMIT(add) \ + 0x4C, (add >> 8) & 0xFF, add & 0xFF, 0x00 //Send cmd, polling read last addr page + +#define AVR_ISP_OSCCAL(add) 0x38, 0x00, add, 0x00 + +#define AVR_ISP_WRITE_LOCK_BYTE(data) 0xAC, 0xE0, 0x00, data //Send cmd, Wait N ms +#define AVR_ISP_READ_LOCK_BYTE 0x58, 0x00, 0x00, 0x00 +#define AVR_ISP_WRITE_FUSE_LOW(data) 0xAC, 0xA0, 0x00, data //Send cmd, Wait N ms +#define AVR_ISP_READ_FUSE_LOW 0x50, 0x00, 0x00, 0x00 +#define AVR_ISP_WRITE_FUSE_HIGH(data) 0xAC, 0xA8, 0x00, data //Send cmd, Wait N ms +#define AVR_ISP_READ_FUSE_HIGH 0x58, 0x08, 0x00, 0x00 +#define AVR_ISP_WRITE_FUSE_EXTENDED(data) 0xAC, 0xA4, 0x00, data //Send cmd, Wait N ms (~write) +#define AVR_ISP_READ_FUSE_EXTENDED 0x50, 0x08, 0x00, 0x00 + +#define AVR_ISP_EECHUNK 0x20 + +// https://www.microchip.com/content/dam/mchp/documents/OTH/ApplicationNotes/ApplicationNotes/doc2525.pdf +// STK Definitions +#define STK_OK 0x10 +#define STK_FAILED 0x11 +#define STK_UNKNOWN 0x12 +#define STK_INSYNC 0x14 +#define STK_NOSYNC 0x15 +#define CRC_EOP 0x20 + +#define STK_GET_SYNC 0x30 +#define STK_GET_SIGN_ON 0x31 +#define STK_SET_PARAMETER 0x40 +#define STK_GET_PARAMETER 0x41 +#define STK_SET_DEVICE 0x42 +#define STK_SET_DEVICE_EXT 0x45 +#define STK_ENTER_PROGMODE 0x50 +#define STK_LEAVE_PROGMODE 0x51 +#define STK_CHIP_ERASE 0x52 +#define STK_CHECK_AUTOINC 0x53 +#define STK_LOAD_ADDRESS 0x55 +#define STK_UNIVERSAL 0x56 +#define STK_UNIVERSAL_MULTI 0x57 +#define STK_PROG_FLASH 0x60 +#define STK_PROG_DATA 0x61 +#define STK_PROG_FUSE 0x62 +#define STK_PROG_FUSE_EXT 0x65 +#define STK_PROG_LOCK 0x63 +#define STK_PROG_PAGE 0x64 +#define STK_READ_FLASH 0x70 +#define STK_READ_DATA 0x71 +#define STK_READ_FUSE 0x72 +#define STK_READ_LOCK 0x73 +#define STK_READ_PAGE 0x74 +#define STK_READ_SIGN 0x75 +#define STK_READ_OSCCAL 0x76 +#define STK_READ_FUSE_EXT 0x77 +#define STK_READ_OSCCAL_EXT 0x78 +#define STK_HW_VER 0x80 +#define STK_SW_MAJOR 0x81 +#define STK_SW_MINOR 0x82 +#define STK_LEDS 0x83 +#define STK_VTARGET 0x84 +#define STK_VADJUST 0x85 +#define STK_OSC_PSCALE 0x86 +#define STK_OSC_CMATCH 0x87 +#define STK_SCK_DURATION 0x89 +#define STK_BUFSIZEL 0x90 +#define STK_BUFSIZEH 0x91 +#define STK_STK500_TOPCARD_DETECT 0x98 + +#define STK_SET_EEPROM_TYPE 0X45 +#define STK_SET_FLASH_TYPE 0X46 diff --git a/applications/external/avr_isp_programmer/lib/driver/avr_isp_spi_sw.c b/applications/external/avr_isp_programmer/lib/driver/avr_isp_spi_sw.c new file mode 100644 index 00000000000..f60850c841a --- /dev/null +++ b/applications/external/avr_isp_programmer/lib/driver/avr_isp_spi_sw.c @@ -0,0 +1,73 @@ +#include "avr_isp_spi_sw.h" + +#include + +#define AVR_ISP_SPI_SW_MISO &gpio_ext_pa6 +#define AVR_ISP_SPI_SW_MOSI &gpio_ext_pa7 +#define AVR_ISP_SPI_SW_SCK &gpio_ext_pb3 +#define AVR_ISP_RESET &gpio_ext_pb2 + +struct AvrIspSpiSw { + AvrIspSpiSwSpeed speed_wait_time; + const GpioPin* miso; + const GpioPin* mosi; + const GpioPin* sck; + const GpioPin* res; +}; + +AvrIspSpiSw* avr_isp_spi_sw_init(AvrIspSpiSwSpeed speed) { + AvrIspSpiSw* instance = malloc(sizeof(AvrIspSpiSw)); + instance->speed_wait_time = speed; + + instance->miso = AVR_ISP_SPI_SW_MISO; + instance->mosi = AVR_ISP_SPI_SW_MOSI; + instance->sck = AVR_ISP_SPI_SW_SCK; + instance->res = AVR_ISP_RESET; + + furi_hal_gpio_init(instance->miso, GpioModeInput, GpioPullNo, GpioSpeedVeryHigh); + furi_hal_gpio_write(instance->mosi, false); + furi_hal_gpio_init(instance->mosi, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); + furi_hal_gpio_write(instance->sck, false); + furi_hal_gpio_init(instance->sck, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); + furi_hal_gpio_init(instance->res, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); + + return instance; +} + +void avr_isp_spi_sw_free(AvrIspSpiSw* instance) { + furi_assert(instance); + furi_hal_gpio_init(instance->res, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_init(instance->miso, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_init(instance->mosi, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_init(instance->sck, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + + free(instance); +} + +uint8_t avr_isp_spi_sw_txrx(AvrIspSpiSw* instance, uint8_t data) { + furi_assert(instance); + for(uint8_t i = 0; i < 8; ++i) { + furi_hal_gpio_write(instance->mosi, (data & 0x80) ? true : false); + + furi_hal_gpio_write(instance->sck, true); + if(instance->speed_wait_time != AvrIspSpiSwSpeed1Mhz) + furi_delay_us(instance->speed_wait_time - 1); + + data = (data << 1) | furi_hal_gpio_read(instance->miso); //-V792 + + furi_hal_gpio_write(instance->sck, false); + if(instance->speed_wait_time != AvrIspSpiSwSpeed1Mhz) + furi_delay_us(instance->speed_wait_time - 1); + } + return data; +} + +void avr_isp_spi_sw_res_set(AvrIspSpiSw* instance, bool state) { + furi_assert(instance); + furi_hal_gpio_write(instance->res, state); +} + +void avr_isp_spi_sw_sck_set(AvrIspSpiSw* instance, bool state) { + furi_assert(instance); + furi_hal_gpio_write(instance->sck, state); +} \ No newline at end of file diff --git a/applications/external/avr_isp_programmer/lib/driver/avr_isp_spi_sw.h b/applications/external/avr_isp_programmer/lib/driver/avr_isp_spi_sw.h new file mode 100644 index 00000000000..44de5ff79cb --- /dev/null +++ b/applications/external/avr_isp_programmer/lib/driver/avr_isp_spi_sw.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +typedef enum { + AvrIspSpiSwSpeed1Mhz = 0, + AvrIspSpiSwSpeed400Khz = 1, + AvrIspSpiSwSpeed250Khz = 2, + AvrIspSpiSwSpeed125Khz = 4, + AvrIspSpiSwSpeed60Khz = 8, + AvrIspSpiSwSpeed40Khz = 12, + AvrIspSpiSwSpeed20Khz = 24, + AvrIspSpiSwSpeed10Khz = 48, + AvrIspSpiSwSpeed5Khz = 96, + AvrIspSpiSwSpeed1Khz = 480, +} AvrIspSpiSwSpeed; + +typedef struct AvrIspSpiSw AvrIspSpiSw; + +AvrIspSpiSw* avr_isp_spi_sw_init(AvrIspSpiSwSpeed speed); +void avr_isp_spi_sw_free(AvrIspSpiSw* instance); +uint8_t avr_isp_spi_sw_txrx(AvrIspSpiSw* instance, uint8_t data); +void avr_isp_spi_sw_res_set(AvrIspSpiSw* instance, bool state); +void avr_isp_spi_sw_sck_set(AvrIspSpiSw* instance, bool state); \ No newline at end of file diff --git a/applications/external/avr_isp_programmer/lib/driver/clock.png b/applications/external/avr_isp_programmer/lib/driver/clock.png new file mode 100644 index 0000000000000000000000000000000000000000..93a59fe681d26795d0db342a0c55ba42fc2c7529 GIT binary patch literal 3649 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 LTALIbyM_M?u*zuP literal 0 HcmV?d00001 diff --git a/applications/external/avr_isp_programmer/scenes/avr_isp_scene.c b/applications/external/avr_isp_programmer/scenes/avr_isp_scene.c new file mode 100644 index 00000000000..4af125aeeaa --- /dev/null +++ b/applications/external/avr_isp_programmer/scenes/avr_isp_scene.c @@ -0,0 +1,30 @@ +#include "../avr_isp_app_i.h" + +// Generate scene on_enter handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, +void (*const avr_isp_scene_on_enter_handlers[])(void*) = { +#include "avr_isp_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 avr_isp_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = { +#include "avr_isp_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 avr_isp_scene_on_exit_handlers[])(void* context) = { +#include "avr_isp_scene_config.h" +}; +#undef ADD_SCENE + +// Initialize scene handlers configuration structure +const SceneManagerHandlers avr_isp_scene_handlers = { + .on_enter_handlers = avr_isp_scene_on_enter_handlers, + .on_event_handlers = avr_isp_scene_on_event_handlers, + .on_exit_handlers = avr_isp_scene_on_exit_handlers, + .scene_num = AvrIspSceneNum, +}; diff --git a/applications/external/avr_isp_programmer/scenes/avr_isp_scene.h b/applications/external/avr_isp_programmer/scenes/avr_isp_scene.h new file mode 100644 index 00000000000..658ee74326f --- /dev/null +++ b/applications/external/avr_isp_programmer/scenes/avr_isp_scene.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +// Generate scene id and total number +#define ADD_SCENE(prefix, name, id) AvrIspScene##id, +typedef enum { +#include "avr_isp_scene_config.h" + AvrIspSceneNum, +} AvrIspScene; +#undef ADD_SCENE + +extern const SceneManagerHandlers avr_isp_scene_handlers; + +// Generate scene on_enter handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); +#include "avr_isp_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 "avr_isp_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 "avr_isp_scene_config.h" +#undef ADD_SCENE diff --git a/applications/external/avr_isp_programmer/scenes/avr_isp_scene_about.c b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_about.c new file mode 100644 index 00000000000..e5f530fec89 --- /dev/null +++ b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_about.c @@ -0,0 +1,99 @@ +#include "../avr_isp_app_i.h" +#include "../helpers/avr_isp_types.h" + +void avr_isp_scene_about_widget_callback(GuiButtonType result, InputType type, void* context) { + furi_assert(context); + + AvrIspApp* app = context; + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(app->view_dispatcher, result); + } +} + +void avr_isp_scene_about_on_enter(void* context) { + furi_assert(context); + + AvrIspApp* app = context; + FuriString* temp_str = furi_string_alloc(); + furi_string_printf(temp_str, "\e#%s\n", "Information"); + + furi_string_cat_printf(temp_str, "Version: %s\n", AVR_ISP_VERSION_APP); + furi_string_cat_printf(temp_str, "Developed by: %s\n", AVR_ISP_DEVELOPED); + furi_string_cat_printf(temp_str, "Github: %s\n\n", AVR_ISP_GITHUB); + + furi_string_cat_printf(temp_str, "\e#%s\n", "Description"); + furi_string_cat_printf( + temp_str, + "This application is an AVR in-system programmer based on stk500mk1. It is compatible with AVR-based" + " microcontrollers including Arduino. You can also use it to repair the chip if you accidentally" + " corrupt the bootloader.\n\n"); + + furi_string_cat_printf(temp_str, "\e#%s\n", "What it can do:"); + furi_string_cat_printf(temp_str, "- Create a dump of your chip on an SD card\n"); + furi_string_cat_printf(temp_str, "- Flash your chip firmware from the SD card\n"); + furi_string_cat_printf(temp_str, "- Act as a wired USB ISP using avrdude software\n\n"); + + furi_string_cat_printf(temp_str, "\e#%s\n", "Supported chip series:"); + furi_string_cat_printf( + temp_str, + "Example command for avrdude flashing: avrdude.exe -p m328p -c stk500v1 -P COMxx -U flash:r:" + "X:\\sketch_sample.hex" + ":i\n"); + furi_string_cat_printf( + temp_str, + "Where: " + "-p m328p" + " brand of your chip, " + "-P COMxx" + " com port number in the system when " + "ISP Programmer" + " is enabled\n\n"); + + furi_string_cat_printf(temp_str, "\e#%s\n", "Info"); + furi_string_cat_printf( + temp_str, + "ATtinyXXXX\nATmegaXXXX\nAT43Uxxx\nAT76C711\nAT86RF401\nAT90xxxxx\nAT94K\n" + "ATAxxxxx\nATA664251\nM3000\nLGT8F88P\nLGT8F168P\nLGT8F328P\n"); + + furi_string_cat_printf( + temp_str, "For a more detailed list of supported chips, see AVRDude help\n"); + + widget_add_text_box_element( + app->widget, + 0, + 0, + 128, + 14, + AlignCenter, + AlignBottom, + "\e#\e! \e!\n", + false); + widget_add_text_box_element( + app->widget, + 0, + 2, + 128, + 14, + AlignCenter, + AlignBottom, + "\e#\e! ISP Programmer \e!\n", + false); + widget_add_text_scroll_element(app->widget, 0, 16, 128, 50, furi_string_get_cstr(temp_str)); + furi_string_free(temp_str); + + view_dispatcher_switch_to_view(app->view_dispatcher, AvrIspViewWidget); +} + +bool avr_isp_scene_about_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + UNUSED(event); + return false; +} + +void avr_isp_scene_about_on_exit(void* context) { + furi_assert(context); + + AvrIspApp* app = context; + // Clear views + widget_reset(app->widget); +} diff --git a/applications/external/avr_isp_programmer/scenes/avr_isp_scene_chip_detect.c b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_chip_detect.c new file mode 100644 index 00000000000..79c23939074 --- /dev/null +++ b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_chip_detect.c @@ -0,0 +1,72 @@ +#include "../avr_isp_app_i.h" + +void avr_isp_scene_chip_detect_callback(AvrIspCustomEvent event, void* context) { + furi_assert(context); + + AvrIspApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, event); +} + +void avr_isp_scene_chip_detect_on_enter(void* context) { + furi_assert(context); + + AvrIspApp* app = context; + switch(app->error) { + case AvrIspErrorReading: + case AvrIspErrorWriting: + case AvrIspErrorWritingFuse: + avr_isp_chip_detect_set_state( + app->avr_isp_chip_detect_view, AvrIspChipDetectViewStateErrorOccured); + break; + case AvrIspErrorVerification: + avr_isp_chip_detect_set_state( + app->avr_isp_chip_detect_view, AvrIspChipDetectViewStateErrorVerification); + break; + + default: + avr_isp_chip_detect_set_state( + app->avr_isp_chip_detect_view, AvrIspChipDetectViewStateNoDetect); + break; + } + app->error = AvrIspErrorNoError; + avr_isp_chip_detect_view_set_callback( + app->avr_isp_chip_detect_view, avr_isp_scene_chip_detect_callback, app); + + view_dispatcher_switch_to_view(app->view_dispatcher, AvrIspViewChipDetect); +} + +bool avr_isp_scene_chip_detect_on_event(void* context, SceneManagerEvent event) { + furi_assert(context); + + AvrIspApp* app = context; + bool consumed = false; + if(event.type == SceneManagerEventTypeCustom) { + switch(event.event) { + case AvrIspCustomEventSceneChipDetectOk: + + if(scene_manager_get_scene_state(app->scene_manager, AvrIspSceneChipDetect) == + AvrIspViewProgrammer) { + scene_manager_next_scene(app->scene_manager, AvrIspSceneProgrammer); + } else if( + scene_manager_get_scene_state(app->scene_manager, AvrIspSceneChipDetect) == + AvrIspViewReader) { + scene_manager_next_scene(app->scene_manager, AvrIspSceneInputName); + } else if( + scene_manager_get_scene_state(app->scene_manager, AvrIspSceneChipDetect) == + AvrIspViewWriter) { + scene_manager_next_scene(app->scene_manager, AvrIspSceneLoad); + } + + consumed = true; + break; + default: + break; + } + } else if(event.type == SceneManagerEventTypeTick) { + } + return consumed; +} + +void avr_isp_scene_chip_detect_on_exit(void* context) { + UNUSED(context); +} diff --git a/applications/external/avr_isp_programmer/scenes/avr_isp_scene_config.h b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_config.h new file mode 100644 index 00000000000..6f22511dbe2 --- /dev/null +++ b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_config.h @@ -0,0 +1,10 @@ +ADD_SCENE(avr_isp, start, Start) +ADD_SCENE(avr_isp, about, About) +ADD_SCENE(avr_isp, programmer, Programmer) +ADD_SCENE(avr_isp, reader, Reader) +ADD_SCENE(avr_isp, input_name, InputName) +ADD_SCENE(avr_isp, load, Load) +ADD_SCENE(avr_isp, writer, Writer) +ADD_SCENE(avr_isp, wiring, Wiring) +ADD_SCENE(avr_isp, chip_detect, ChipDetect) +ADD_SCENE(avr_isp, success, Success) diff --git a/applications/external/avr_isp_programmer/scenes/avr_isp_scene_input_name.c b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_input_name.c new file mode 100644 index 00000000000..3394f4362a6 --- /dev/null +++ b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_input_name.c @@ -0,0 +1,89 @@ +#include "../avr_isp_app_i.h" +#include + +#define MAX_TEXT_INPUT_LEN 22 + +void avr_isp_scene_input_name_text_callback(void* context) { + furi_assert(context); + + AvrIspApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, AvrIspCustomEventSceneInputName); +} + +void avr_isp_scene_input_name_get_timefilename(FuriString* name) { + FuriHalRtcDateTime datetime = {0}; + furi_hal_rtc_get_datetime(&datetime); + furi_string_printf( + name, + "AVR_dump-%.4d%.2d%.2d-%.2d%.2d%.2d", + datetime.year, + datetime.month, + datetime.day, + datetime.hour, + datetime.minute, + datetime.second); +} + +void avr_isp_scene_input_name_on_enter(void* context) { + furi_assert(context); + + AvrIspApp* app = context; + // Setup view + TextInput* text_input = app->text_input; + bool dev_name_empty = false; + + FuriString* file_name = furi_string_alloc(); + + avr_isp_scene_input_name_get_timefilename(file_name); + furi_string_set(app->file_path, STORAGE_APP_DATA_PATH_PREFIX); + //highlighting the entire filename by default + dev_name_empty = true; + + strncpy(app->file_name_tmp, furi_string_get_cstr(file_name), AVR_ISP_MAX_LEN_NAME); + text_input_set_header_text(text_input, "Name dump"); + text_input_set_result_callback( + text_input, + avr_isp_scene_input_name_text_callback, + app, + app->file_name_tmp, + MAX_TEXT_INPUT_LEN, // buffer size + dev_name_empty); + + ValidatorIsFile* validator_is_file = + validator_is_file_alloc_init(STORAGE_APP_DATA_PATH_PREFIX, AVR_ISP_APP_EXTENSION, ""); + text_input_set_validator(text_input, validator_is_file_callback, validator_is_file); + + furi_string_free(file_name); + + view_dispatcher_switch_to_view(app->view_dispatcher, AvrIspViewTextInput); +} + +bool avr_isp_scene_input_name_on_event(void* context, SceneManagerEvent event) { + furi_assert(context); + + AvrIspApp* app = context; + if(event.type == SceneManagerEventTypeBack) { + scene_manager_previous_scene(app->scene_manager); + return true; + } else if(event.type == SceneManagerEventTypeCustom) { + if(event.event == AvrIspCustomEventSceneInputName) { + if(strcmp(app->file_name_tmp, "") != 0) { + scene_manager_next_scene(app->scene_manager, AvrIspSceneReader); + } else { + } + } + } + return false; +} + +void avr_isp_scene_input_name_on_exit(void* context) { + furi_assert(context); + + AvrIspApp* app = context; + // Clear validator + void* validator_context = text_input_get_validator_callback_context(app->text_input); + text_input_set_validator(app->text_input, NULL, NULL); + validator_is_file_free(validator_context); + // Clear view + text_input_reset(app->text_input); +} diff --git a/applications/external/avr_isp_programmer/scenes/avr_isp_scene_load.c b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_load.c new file mode 100644 index 00000000000..e8890e37381 --- /dev/null +++ b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_load.c @@ -0,0 +1,22 @@ +#include "../avr_isp_app_i.h" + +void avr_isp_scene_load_on_enter(void* context) { + furi_assert(context); + + AvrIspApp* app = context; + if(avr_isp_load_from_file(app)) { + scene_manager_next_scene(app->scene_manager, AvrIspSceneWriter); + } else { + scene_manager_previous_scene(app->scene_manager); + } +} + +bool avr_isp_scene_load_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + UNUSED(event); + return false; +} + +void avr_isp_scene_load_on_exit(void* context) { + UNUSED(context); +} diff --git a/applications/external/avr_isp_programmer/scenes/avr_isp_scene_programmer.c b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_programmer.c new file mode 100644 index 00000000000..0915e1e8a2a --- /dev/null +++ b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_programmer.c @@ -0,0 +1,28 @@ +#include "../avr_isp_app_i.h" + +void avr_isp_scene_programmer_callback(AvrIspCustomEvent event, void* context) { + furi_assert(context); + + AvrIspApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, event); +} + +void avr_isp_scene_programmer_on_enter(void* context) { + furi_assert(context); + + AvrIspApp* app = context; + avr_isp_programmer_view_set_callback( + app->avr_isp_programmer_view, avr_isp_scene_programmer_callback, app); + + view_dispatcher_switch_to_view(app->view_dispatcher, AvrIspViewProgrammer); +} + +bool avr_isp_scene_programmer_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + UNUSED(event); + return false; +} + +void avr_isp_scene_programmer_on_exit(void* context) { + UNUSED(context); +} diff --git a/applications/external/avr_isp_programmer/scenes/avr_isp_scene_reader.c b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_reader.c new file mode 100644 index 00000000000..8dcb4759700 --- /dev/null +++ b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_reader.c @@ -0,0 +1,64 @@ +#include "../avr_isp_app_i.h" + +void avr_isp_scene_reader_callback(AvrIspCustomEvent event, void* context) { + furi_assert(context); + + AvrIspApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, event); +} + +void avr_isp_scene_reader_on_enter(void* context) { + furi_assert(context); + + AvrIspApp* app = context; + avr_isp_reader_set_file_path( + app->avr_isp_reader_view, furi_string_get_cstr(app->file_path), app->file_name_tmp); + avr_isp_reader_view_set_callback(app->avr_isp_reader_view, avr_isp_scene_reader_callback, app); + + view_dispatcher_switch_to_view(app->view_dispatcher, AvrIspViewReader); +} + +bool avr_isp_scene_reader_on_event(void* context, SceneManagerEvent event) { + furi_assert(context); + + AvrIspApp* app = context; + UNUSED(app); + bool consumed = false; + if(event.type == SceneManagerEventTypeBack) { + //do not handle exit on "Back" + consumed = true; + } else if(event.type == SceneManagerEventTypeCustom) { + switch(event.event) { + case AvrIspCustomEventSceneReadingOk: + scene_manager_next_scene(app->scene_manager, AvrIspSceneSuccess); + consumed = true; + break; + case AvrIspCustomEventSceneExit: + scene_manager_search_and_switch_to_previous_scene( + app->scene_manager, AvrIspSceneChipDetect); + consumed = true; + break; + case AvrIspCustomEventSceneErrorVerification: + app->error = AvrIspErrorVerification; + scene_manager_search_and_switch_to_previous_scene( + app->scene_manager, AvrIspSceneChipDetect); + consumed = true; + break; + case AvrIspCustomEventSceneErrorReading: + app->error = AvrIspErrorReading; + scene_manager_search_and_switch_to_previous_scene( + app->scene_manager, AvrIspSceneChipDetect); + consumed = true; + break; + default: + break; + } + } else if(event.type == SceneManagerEventTypeTick) { + avr_isp_reader_update_progress(app->avr_isp_reader_view); + } + return consumed; +} + +void avr_isp_scene_reader_on_exit(void* context) { + UNUSED(context); +} diff --git a/applications/external/avr_isp_programmer/scenes/avr_isp_scene_start.c b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_start.c new file mode 100644 index 00000000000..b00bfefce2f --- /dev/null +++ b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_start.c @@ -0,0 +1,75 @@ +#include "../avr_isp_app_i.h" + +void avr_isp_scene_start_submenu_callback(void* context, uint32_t index) { + furi_assert(context); + AvrIspApp* app = context; + + view_dispatcher_send_custom_event(app->view_dispatcher, index); +} + +void avr_isp_scene_start_on_enter(void* context) { + furi_assert(context); + + AvrIspApp* app = context; + Submenu* submenu = app->submenu; + submenu_add_item( + submenu, "Dump AVR", SubmenuIndexAvrIspReader, avr_isp_scene_start_submenu_callback, app); + submenu_add_item( + submenu, "Flash AVR", SubmenuIndexAvrIspWriter, avr_isp_scene_start_submenu_callback, app); + submenu_add_item( + submenu, + "ISP Programmer", + SubmenuIndexAvrIspProgrammer, + avr_isp_scene_start_submenu_callback, + app); + submenu_add_item( + submenu, "Wiring", SubmenuIndexAvrIsWiring, avr_isp_scene_start_submenu_callback, app); + submenu_add_item( + submenu, "About", SubmenuIndexAvrIspAbout, avr_isp_scene_start_submenu_callback, app); + + submenu_set_selected_item( + submenu, scene_manager_get_scene_state(app->scene_manager, AvrIspSceneStart)); + + view_dispatcher_switch_to_view(app->view_dispatcher, AvrIspViewSubmenu); +} + +bool avr_isp_scene_start_on_event(void* context, SceneManagerEvent event) { + furi_assert(context); + + AvrIspApp* app = context; + bool consumed = false; + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubmenuIndexAvrIspAbout) { + scene_manager_next_scene(app->scene_manager, AvrIspSceneAbout); + consumed = true; + } else if(event.event == SubmenuIndexAvrIspProgrammer) { + scene_manager_set_scene_state( + app->scene_manager, AvrIspSceneChipDetect, AvrIspViewProgrammer); + scene_manager_next_scene(app->scene_manager, AvrIspSceneChipDetect); + consumed = true; + } else if(event.event == SubmenuIndexAvrIspReader) { + scene_manager_set_scene_state( + app->scene_manager, AvrIspSceneChipDetect, AvrIspViewReader); + scene_manager_next_scene(app->scene_manager, AvrIspSceneChipDetect); + consumed = true; + } else if(event.event == SubmenuIndexAvrIspWriter) { + scene_manager_set_scene_state( + app->scene_manager, AvrIspSceneChipDetect, AvrIspViewWriter); + scene_manager_next_scene(app->scene_manager, AvrIspSceneChipDetect); + consumed = true; + } else if(event.event == SubmenuIndexAvrIsWiring) { + scene_manager_next_scene(app->scene_manager, AvrIspSceneWiring); + consumed = true; + } + scene_manager_set_scene_state(app->scene_manager, AvrIspSceneStart, event.event); + } + + return consumed; +} + +void avr_isp_scene_start_on_exit(void* context) { + furi_assert(context); + + AvrIspApp* app = context; + submenu_reset(app->submenu); +} diff --git a/applications/external/avr_isp_programmer/scenes/avr_isp_scene_success.c b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_success.c new file mode 100644 index 00000000000..a88ed28aa83 --- /dev/null +++ b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_success.c @@ -0,0 +1,44 @@ +#include "../avr_isp_app_i.h" + +void avr_isp_scene_success_popup_callback(void* context) { + furi_assert(context); + + AvrIspApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, AvrIspCustomEventSceneSuccess); +} + +void avr_isp_scene_success_on_enter(void* context) { + furi_assert(context); + + AvrIspApp* app = context; + Popup* popup = app->popup; + popup_set_icon(popup, 32, 5, &I_dolphin_nice_96x59); + popup_set_header(popup, "Success!", 8, 22, AlignLeft, AlignBottom); + popup_set_timeout(popup, 1500); + popup_set_context(popup, app); + popup_set_callback(popup, avr_isp_scene_success_popup_callback); + popup_enable_timeout(popup); + view_dispatcher_switch_to_view(app->view_dispatcher, AvrIspViewPopup); +} + +bool avr_isp_scene_success_on_event(void* context, SceneManagerEvent event) { + furi_assert(context); + + AvrIspApp* app = context; + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == AvrIspCustomEventSceneSuccess) { + scene_manager_search_and_switch_to_previous_scene( + app->scene_manager, AvrIspSceneStart); + return true; + } + } + return false; +} + +void avr_isp_scene_success_on_exit(void* context) { + furi_assert(context); + + AvrIspApp* app = context; + Popup* popup = app->popup; + popup_reset(popup); +} diff --git a/applications/external/avr_isp_programmer/scenes/avr_isp_scene_wiring.c b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_wiring.c new file mode 100644 index 00000000000..787ed56732e --- /dev/null +++ b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_wiring.c @@ -0,0 +1,21 @@ +#include "../avr_isp_app_i.h" + +void avr_isp_scene_wiring_on_enter(void* context) { + furi_assert(context); + + AvrIspApp* app = context; + widget_add_icon_element(app->widget, 0, 0, &I_avr_wiring); + view_dispatcher_switch_to_view(app->view_dispatcher, AvrIspViewWidget); +} + +bool avr_isp_scene_wiring_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + UNUSED(event); + return false; +} +void avr_isp_scene_wiring_on_exit(void* context) { + furi_assert(context); + + AvrIspApp* app = context; + widget_reset(app->widget); +} diff --git a/applications/external/avr_isp_programmer/scenes/avr_isp_scene_writer.c b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_writer.c new file mode 100644 index 00000000000..39c944fd5fc --- /dev/null +++ b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_writer.c @@ -0,0 +1,69 @@ +#include "../avr_isp_app_i.h" + +void avr_isp_scene_writer_callback(AvrIspCustomEvent event, void* context) { + furi_assert(context); + + AvrIspApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, event); +} + +void avr_isp_scene_writer_on_enter(void* context) { + furi_assert(context); + + AvrIspApp* app = context; + avr_isp_writer_set_file_path( + app->avr_isp_writer_view, furi_string_get_cstr(app->file_path), app->file_name_tmp); + avr_isp_writer_view_set_callback(app->avr_isp_writer_view, avr_isp_scene_writer_callback, app); + view_dispatcher_switch_to_view(app->view_dispatcher, AvrIspViewWriter); +} + +bool avr_isp_scene_writer_on_event(void* context, SceneManagerEvent event) { + furi_assert(context); + + AvrIspApp* app = context; + bool consumed = false; + if(event.type == SceneManagerEventTypeBack) { + //do not handle exit on "Back" + consumed = true; + } else if(event.type == SceneManagerEventTypeCustom) { + switch(event.event) { + case AvrIspCustomEventSceneExitStartMenu: + scene_manager_search_and_switch_to_previous_scene( + app->scene_manager, AvrIspSceneStart); + consumed = true; + break; + case AvrIspCustomEventSceneExit: + scene_manager_search_and_switch_to_previous_scene( + app->scene_manager, AvrIspSceneChipDetect); + consumed = true; + break; + case AvrIspCustomEventSceneErrorVerification: + app->error = AvrIspErrorVerification; + scene_manager_search_and_switch_to_previous_scene( + app->scene_manager, AvrIspSceneChipDetect); + consumed = true; + break; + case AvrIspCustomEventSceneErrorWriting: + app->error = AvrIspErrorWriting; + scene_manager_search_and_switch_to_previous_scene( + app->scene_manager, AvrIspSceneChipDetect); + consumed = true; + break; + case AvrIspCustomEventSceneErrorWritingFuse: + app->error = AvrIspErrorWritingFuse; + scene_manager_search_and_switch_to_previous_scene( + app->scene_manager, AvrIspSceneChipDetect); + consumed = true; + break; + default: + break; + } + } else if(event.type == SceneManagerEventTypeTick) { + avr_isp_writer_update_progress(app->avr_isp_writer_view); + } + return consumed; +} + +void avr_isp_scene_writer_on_exit(void* context) { + UNUSED(context); +} diff --git a/applications/external/avr_isp_programmer/views/avr_isp_view_chip_detect.c b/applications/external/avr_isp_programmer/views/avr_isp_view_chip_detect.c new file mode 100644 index 00000000000..fdcf71c36c1 --- /dev/null +++ b/applications/external/avr_isp_programmer/views/avr_isp_view_chip_detect.c @@ -0,0 +1,213 @@ +#include "avr_isp_view_chip_detect.h" +#include +#include + +#include "../helpers/avr_isp_worker_rw.h" + +struct AvrIspChipDetectView { + View* view; + AvrIspWorkerRW* avr_isp_worker_rw; + AvrIspChipDetectViewCallback callback; + void* context; +}; + +typedef struct { + uint16_t idx; + const char* name_chip; + uint32_t flash_size; + AvrIspChipDetectViewState state; +} AvrIspChipDetectViewModel; + +void avr_isp_chip_detect_view_set_callback( + AvrIspChipDetectView* instance, + AvrIspChipDetectViewCallback callback, + void* context) { + furi_assert(instance); + furi_assert(callback); + + instance->callback = callback; + instance->context = context; +} + +void avr_isp_chip_detect_set_state(AvrIspChipDetectView* instance, AvrIspChipDetectViewState state) { + furi_assert(instance); + + with_view_model( + instance->view, AvrIspChipDetectViewModel * model, { model->state = state; }, true); +} + +void avr_isp_chip_detect_view_draw(Canvas* canvas, AvrIspChipDetectViewModel* model) { + canvas_clear(canvas); + + char str_buf[64] = {0}; + canvas_set_font(canvas, FontPrimary); + + switch(model->state) { + case AvrIspChipDetectViewStateDetected: + canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, "AVR chip detected!"); + canvas_draw_icon(canvas, 29, 14, &I_chip_long_70x22); + canvas_set_font(canvas, FontSecondary); + snprintf(str_buf, sizeof(str_buf), "%ld Kb", model->flash_size / 1024); + canvas_draw_str_aligned(canvas, 64, 25, AlignCenter, AlignCenter, str_buf); + canvas_draw_str_aligned(canvas, 64, 45, AlignCenter, AlignCenter, model->name_chip); + elements_button_right(canvas, "Next"); + break; + case AvrIspChipDetectViewStateErrorOccured: + canvas_draw_str_aligned( + canvas, 64, 5, AlignCenter, AlignCenter, "Error occured, try again!"); + canvas_draw_icon(canvas, 29, 14, &I_chip_error_70x22); + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned( + canvas, 64, 45, AlignCenter, AlignCenter, "Check the wiring and retry"); + break; + case AvrIspChipDetectViewStateErrorVerification: + canvas_draw_str_aligned( + canvas, 64, 5, AlignCenter, AlignCenter, "Data verification failed"); + canvas_draw_icon(canvas, 29, 14, &I_chip_error_70x22); + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned( + canvas, 64, 45, AlignCenter, AlignCenter, "Try to restart the process"); + break; + + default: + //AvrIspChipDetectViewStateNoDetect + canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, "AVR chip not found!"); + canvas_draw_icon(canvas, 29, 12, &I_chif_not_found_83x37); + + break; + } + canvas_set_font(canvas, FontSecondary); + elements_button_left(canvas, "Retry"); +} + +bool avr_isp_chip_detect_view_input(InputEvent* event, void* context) { + furi_assert(context); + + AvrIspChipDetectView* instance = context; + + if(event->type == InputTypeShort) { + if(event->key == InputKeyBack) { + return false; + } else if(event->key == InputKeyRight) { + with_view_model( + instance->view, + AvrIspChipDetectViewModel * model, + { + if(model->state == AvrIspChipDetectViewStateDetected) { + if(instance->callback) + instance->callback( + AvrIspCustomEventSceneChipDetectOk, instance->context); + } + }, + false); + + } else if(event->key == InputKeyLeft) { + bool detect_chip = false; + with_view_model( + instance->view, + AvrIspChipDetectViewModel * model, + { + if(model->state != AvrIspChipDetectViewStateDetecting) { + model->state = AvrIspChipDetectViewStateDetecting; + detect_chip = true; + } + }, + false); + if(detect_chip) avr_isp_worker_rw_detect_chip(instance->avr_isp_worker_rw); + } + } else { + return false; + } + + return true; +} + +static void avr_isp_chip_detect_detect_chip_callback( + void* context, + const char* name, + bool detect_chip, + uint32_t flash_size) { + furi_assert(context); + + AvrIspChipDetectView* instance = context; + with_view_model( + instance->view, + AvrIspChipDetectViewModel * model, + { + model->name_chip = name; + model->flash_size = flash_size; + if(detect_chip) { + model->state = AvrIspChipDetectViewStateDetected; + } else { + model->state = AvrIspChipDetectViewStateNoDetect; + } + }, + true); +} +void avr_isp_chip_detect_view_enter(void* context) { + furi_assert(context); + + AvrIspChipDetectView* instance = context; + bool detect_chip = false; + with_view_model( + instance->view, + AvrIspChipDetectViewModel * model, + { + if(model->state == AvrIspChipDetectViewStateNoDetect || + model->state == AvrIspChipDetectViewStateDetected) { + detect_chip = true; + } + }, + false); + + //Start avr_isp_worker_rw + instance->avr_isp_worker_rw = avr_isp_worker_rw_alloc(instance->context); + + avr_isp_worker_rw_set_callback( + instance->avr_isp_worker_rw, avr_isp_chip_detect_detect_chip_callback, instance); + + if(detect_chip) avr_isp_worker_rw_detect_chip(instance->avr_isp_worker_rw); +} + +void avr_isp_chip_detect_view_exit(void* context) { + furi_assert(context); + + AvrIspChipDetectView* instance = context; + + avr_isp_worker_rw_set_callback(instance->avr_isp_worker_rw, NULL, NULL); + avr_isp_worker_rw_free(instance->avr_isp_worker_rw); +} + +AvrIspChipDetectView* avr_isp_chip_detect_view_alloc() { + AvrIspChipDetectView* instance = malloc(sizeof(AvrIspChipDetectView)); + + // View allocation and configuration + instance->view = view_alloc(); + + view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(AvrIspChipDetectViewModel)); + view_set_context(instance->view, instance); + view_set_draw_callback(instance->view, (ViewDrawCallback)avr_isp_chip_detect_view_draw); + view_set_input_callback(instance->view, avr_isp_chip_detect_view_input); + view_set_enter_callback(instance->view, avr_isp_chip_detect_view_enter); + view_set_exit_callback(instance->view, avr_isp_chip_detect_view_exit); + + with_view_model( + instance->view, + AvrIspChipDetectViewModel * model, + { model->state = AvrIspChipDetectViewStateNoDetect; }, + false); + return instance; +} + +void avr_isp_chip_detect_view_free(AvrIspChipDetectView* instance) { + furi_assert(instance); + + view_free(instance->view); + free(instance); +} + +View* avr_isp_chip_detect_view_get_view(AvrIspChipDetectView* instance) { + furi_assert(instance); + + return instance->view; +} diff --git a/applications/external/avr_isp_programmer/views/avr_isp_view_chip_detect.h b/applications/external/avr_isp_programmer/views/avr_isp_view_chip_detect.h new file mode 100644 index 00000000000..37f2ae23369 --- /dev/null +++ b/applications/external/avr_isp_programmer/views/avr_isp_view_chip_detect.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include "../helpers/avr_isp_types.h" +#include "../helpers/avr_isp_event.h" + +typedef struct AvrIspChipDetectView AvrIspChipDetectView; + +typedef void (*AvrIspChipDetectViewCallback)(AvrIspCustomEvent event, void* context); + +typedef enum { + AvrIspChipDetectViewStateNoDetect, + AvrIspChipDetectViewStateDetecting, + AvrIspChipDetectViewStateDetected, + AvrIspChipDetectViewStateErrorOccured, + AvrIspChipDetectViewStateErrorVerification, +} AvrIspChipDetectViewState; + +void avr_isp_chip_detect_view_set_callback( + AvrIspChipDetectView* instance, + AvrIspChipDetectViewCallback callback, + void* context); + +void avr_isp_chip_detect_set_state(AvrIspChipDetectView* instance, AvrIspChipDetectViewState state); + +AvrIspChipDetectView* avr_isp_chip_detect_view_alloc(); + +void avr_isp_chip_detect_view_free(AvrIspChipDetectView* instance); + +View* avr_isp_chip_detect_view_get_view(AvrIspChipDetectView* instance); + +void avr_isp_chip_detect_view_exit(void* context); diff --git a/applications/external/avr_isp_programmer/views/avr_isp_view_programmer.c b/applications/external/avr_isp_programmer/views/avr_isp_view_programmer.c new file mode 100644 index 00000000000..34e18770b2f --- /dev/null +++ b/applications/external/avr_isp_programmer/views/avr_isp_view_programmer.c @@ -0,0 +1,134 @@ +#include "avr_isp_view_programmer.h" +#include + +#include "../helpers/avr_isp_worker.h" +#include + +struct AvrIspProgrammerView { + View* view; + AvrIspWorker* worker; + AvrIspProgrammerViewCallback callback; + void* context; +}; + +typedef struct { + AvrIspProgrammerViewStatus status; +} AvrIspProgrammerViewModel; + +void avr_isp_programmer_view_set_callback( + AvrIspProgrammerView* instance, + AvrIspProgrammerViewCallback callback, + void* context) { + furi_assert(instance); + furi_assert(callback); + + instance->callback = callback; + instance->context = context; +} + +void avr_isp_programmer_view_draw(Canvas* canvas, AvrIspProgrammerViewModel* model) { + canvas_clear(canvas); + + if(model->status == AvrIspProgrammerViewStatusUSBConnect) { + canvas_set_font(canvas, FontPrimary); + canvas_draw_icon(canvas, 0, 0, &I_isp_active_128x53); + elements_multiline_text(canvas, 45, 10, "ISP mode active"); + } else { + canvas_set_font(canvas, FontSecondary); + canvas_draw_icon(canvas, 51, 6, &I_link_waiting_77x56); + elements_multiline_text(canvas, 0, 25, "Waiting for\nsoftware\nconnection"); + } +} + +bool avr_isp_programmer_view_input(InputEvent* event, void* context) { + furi_assert(context); + UNUSED(context); + + if(event->key == InputKeyBack || event->type != InputTypeShort) { + return false; + } + + return true; +} + +static void avr_isp_programmer_usb_connect_callback(void* context, bool status_connect) { + furi_assert(context); + AvrIspProgrammerView* instance = context; + + with_view_model( + instance->view, + AvrIspProgrammerViewModel * model, + { + if(status_connect) { + model->status = AvrIspProgrammerViewStatusUSBConnect; + } else { + model->status = AvrIspProgrammerViewStatusNoUSBConnect; + } + }, + true); +} + +void avr_isp_programmer_view_enter(void* context) { + furi_assert(context); + + AvrIspProgrammerView* instance = context; + with_view_model( + instance->view, + AvrIspProgrammerViewModel * model, + { model->status = AvrIspProgrammerViewStatusNoUSBConnect; }, + true); + + //Start worker + instance->worker = avr_isp_worker_alloc(instance->context); + + avr_isp_worker_set_callback( + instance->worker, avr_isp_programmer_usb_connect_callback, instance); + + avr_isp_worker_start(instance->worker); +} + +void avr_isp_programmer_view_exit(void* context) { + furi_assert(context); + + AvrIspProgrammerView* instance = context; + //Stop worker + if(avr_isp_worker_is_running(instance->worker)) { + avr_isp_worker_stop(instance->worker); + } + avr_isp_worker_set_callback(instance->worker, NULL, NULL); + avr_isp_worker_free(instance->worker); +} + +AvrIspProgrammerView* avr_isp_programmer_view_alloc() { + AvrIspProgrammerView* instance = malloc(sizeof(AvrIspProgrammerView)); + + // View allocation and configuration + instance->view = view_alloc(); + + view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(AvrIspProgrammerViewModel)); + view_set_context(instance->view, instance); + view_set_draw_callback(instance->view, (ViewDrawCallback)avr_isp_programmer_view_draw); + view_set_input_callback(instance->view, avr_isp_programmer_view_input); + view_set_enter_callback(instance->view, avr_isp_programmer_view_enter); + view_set_exit_callback(instance->view, avr_isp_programmer_view_exit); + + with_view_model( + instance->view, + AvrIspProgrammerViewModel * model, + { model->status = AvrIspProgrammerViewStatusNoUSBConnect; }, + false); + return instance; +} + +void avr_isp_programmer_view_free(AvrIspProgrammerView* instance) { + furi_assert(instance); + + view_free(instance->view); + free(instance); +} + +View* avr_isp_programmer_view_get_view(AvrIspProgrammerView* instance) { + furi_assert(instance); + + return instance->view; +} diff --git a/applications/external/avr_isp_programmer/views/avr_isp_view_programmer.h b/applications/external/avr_isp_programmer/views/avr_isp_view_programmer.h new file mode 100644 index 00000000000..9f005b026a2 --- /dev/null +++ b/applications/external/avr_isp_programmer/views/avr_isp_view_programmer.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include "../helpers/avr_isp_types.h" +#include "../helpers/avr_isp_event.h" + +typedef struct AvrIspProgrammerView AvrIspProgrammerView; + +typedef void (*AvrIspProgrammerViewCallback)(AvrIspCustomEvent event, void* context); + +typedef enum { + AvrIspProgrammerViewStatusNoUSBConnect, + AvrIspProgrammerViewStatusUSBConnect, +} AvrIspProgrammerViewStatus; + +void avr_isp_programmer_view_set_callback( + AvrIspProgrammerView* instance, + AvrIspProgrammerViewCallback callback, + void* context); + +AvrIspProgrammerView* avr_isp_programmer_view_alloc(); + +void avr_isp_programmer_view_free(AvrIspProgrammerView* instance); + +View* avr_isp_programmer_view_get_view(AvrIspProgrammerView* instance); + +void avr_isp_programmer_view_exit(void* context); diff --git a/applications/external/avr_isp_programmer/views/avr_isp_view_reader.c b/applications/external/avr_isp_programmer/views/avr_isp_view_reader.c new file mode 100644 index 00000000000..92d15bd7f8b --- /dev/null +++ b/applications/external/avr_isp_programmer/views/avr_isp_view_reader.c @@ -0,0 +1,215 @@ +#include "avr_isp_view_reader.h" +#include + +#include "../helpers/avr_isp_worker_rw.h" + +struct AvrIspReaderView { + View* view; + AvrIspWorkerRW* avr_isp_worker_rw; + const char* file_path; + const char* file_name; + AvrIspReaderViewCallback callback; + void* context; +}; + +typedef struct { + AvrIspReaderViewStatus status; + float progress_flash; + float progress_eeprom; +} AvrIspReaderViewModel; + +void avr_isp_reader_update_progress(AvrIspReaderView* instance) { + with_view_model( + instance->view, + AvrIspReaderViewModel * model, + { + model->progress_flash = + avr_isp_worker_rw_get_progress_flash(instance->avr_isp_worker_rw); + model->progress_eeprom = + avr_isp_worker_rw_get_progress_eeprom(instance->avr_isp_worker_rw); + }, + true); +} + +void avr_isp_reader_view_set_callback( + AvrIspReaderView* instance, + AvrIspReaderViewCallback callback, + void* context) { + furi_assert(instance); + furi_assert(callback); + + instance->callback = callback; + instance->context = context; +} + +void avr_isp_reader_set_file_path( + AvrIspReaderView* instance, + const char* file_path, + const char* file_name) { + furi_assert(instance); + + instance->file_path = file_path; + instance->file_name = file_name; +} + +void avr_isp_reader_view_draw(Canvas* canvas, AvrIspReaderViewModel* model) { + canvas_clear(canvas); + char str_buf[64] = {0}; + + canvas_set_font(canvas, FontPrimary); + switch(model->status) { + case AvrIspReaderViewStatusIDLE: + canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, "Press start to dump"); + canvas_set_font(canvas, FontSecondary); + elements_button_center(canvas, "Start"); + break; + case AvrIspReaderViewStatusReading: + canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, "Reading dump"); + break; + case AvrIspReaderViewStatusVerification: + canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, "Verifyng dump"); + break; + + default: + break; + } + + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 0, 27, "Flash"); + snprintf(str_buf, sizeof(str_buf), "%d%%", (uint8_t)(model->progress_flash * 100)); + elements_progress_bar_with_text(canvas, 44, 17, 84, model->progress_flash, str_buf); + canvas_draw_str(canvas, 0, 43, "EEPROM"); + snprintf(str_buf, sizeof(str_buf), "%d%%", (uint8_t)(model->progress_eeprom * 100)); + elements_progress_bar_with_text(canvas, 44, 34, 84, model->progress_eeprom, str_buf); +} + +bool avr_isp_reader_view_input(InputEvent* event, void* context) { + furi_assert(context); + AvrIspReaderView* instance = context; + + bool ret = true; + if(event->key == InputKeyBack && event->type == InputTypeShort) { + with_view_model( + instance->view, + AvrIspReaderViewModel * model, + { + if(model->status == AvrIspReaderViewStatusIDLE) { + if(instance->callback) + instance->callback(AvrIspCustomEventSceneExit, instance->context); + ret = false; + } + }, + false); + } else if(event->key == InputKeyOk && event->type == InputTypeShort) { + with_view_model( + instance->view, + AvrIspReaderViewModel * model, + { + if(model->status == AvrIspReaderViewStatusIDLE) { + model->status = AvrIspReaderViewStatusReading; + avr_isp_worker_rw_read_dump_start( + instance->avr_isp_worker_rw, instance->file_path, instance->file_name); + } + }, + false); + } + return ret; +} + +static void avr_isp_reader_callback_status(void* context, AvrIspWorkerRWStatus status) { + furi_assert(context); + AvrIspReaderView* instance = context; + + with_view_model( + instance->view, + AvrIspReaderViewModel * model, + { + switch(status) { + case AvrIspWorkerRWStatusEndReading: + model->status = AvrIspReaderViewStatusVerification; + avr_isp_worker_rw_verification_start( + instance->avr_isp_worker_rw, instance->file_path, instance->file_name); + model->status = AvrIspReaderViewStatusVerification; + break; + case AvrIspWorkerRWStatusEndVerification: + if(instance->callback) + instance->callback(AvrIspCustomEventSceneReadingOk, instance->context); + break; + case AvrIspWorkerRWStatusErrorVerification: + if(instance->callback) + instance->callback(AvrIspCustomEventSceneErrorVerification, instance->context); + break; + + default: + //AvrIspWorkerRWStatusErrorReading; + if(instance->callback) + instance->callback(AvrIspCustomEventSceneErrorReading, instance->context); + break; + } + }, + true); +} + +void avr_isp_reader_view_enter(void* context) { + furi_assert(context); + AvrIspReaderView* instance = context; + + with_view_model( + instance->view, + AvrIspReaderViewModel * model, + { + model->status = AvrIspReaderViewStatusIDLE; + model->progress_flash = 0.0f; + model->progress_eeprom = 0.0f; + }, + true); + + //Start avr_isp_worker_rw + instance->avr_isp_worker_rw = avr_isp_worker_rw_alloc(instance->context); + + avr_isp_worker_rw_set_callback_status( + instance->avr_isp_worker_rw, avr_isp_reader_callback_status, instance); + + avr_isp_worker_rw_start(instance->avr_isp_worker_rw); +} + +void avr_isp_reader_view_exit(void* context) { + furi_assert(context); + + AvrIspReaderView* instance = context; + //Stop avr_isp_worker_rw + if(avr_isp_worker_rw_is_running(instance->avr_isp_worker_rw)) { + avr_isp_worker_rw_stop(instance->avr_isp_worker_rw); + } + + avr_isp_worker_rw_free(instance->avr_isp_worker_rw); +} + +AvrIspReaderView* avr_isp_reader_view_alloc() { + AvrIspReaderView* instance = malloc(sizeof(AvrIspReaderView)); + + // View allocation and configuration + instance->view = view_alloc(); + + view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(AvrIspReaderViewModel)); + view_set_context(instance->view, instance); + view_set_draw_callback(instance->view, (ViewDrawCallback)avr_isp_reader_view_draw); + view_set_input_callback(instance->view, avr_isp_reader_view_input); + view_set_enter_callback(instance->view, avr_isp_reader_view_enter); + view_set_exit_callback(instance->view, avr_isp_reader_view_exit); + + return instance; +} + +void avr_isp_reader_view_free(AvrIspReaderView* instance) { + furi_assert(instance); + + view_free(instance->view); + free(instance); +} + +View* avr_isp_reader_view_get_view(AvrIspReaderView* instance) { + furi_assert(instance); + + return instance->view; +} diff --git a/applications/external/avr_isp_programmer/views/avr_isp_view_reader.h b/applications/external/avr_isp_programmer/views/avr_isp_view_reader.h new file mode 100644 index 00000000000..44a43994815 --- /dev/null +++ b/applications/external/avr_isp_programmer/views/avr_isp_view_reader.h @@ -0,0 +1,35 @@ +#pragma once + +#include +#include "../helpers/avr_isp_types.h" +#include "../helpers/avr_isp_event.h" + +typedef struct AvrIspReaderView AvrIspReaderView; + +typedef void (*AvrIspReaderViewCallback)(AvrIspCustomEvent event, void* context); + +typedef enum { + AvrIspReaderViewStatusIDLE, + AvrIspReaderViewStatusReading, + AvrIspReaderViewStatusVerification, +} AvrIspReaderViewStatus; + +void avr_isp_reader_update_progress(AvrIspReaderView* instance); + +void avr_isp_reader_set_file_path( + AvrIspReaderView* instance, + const char* file_path, + const char* file_name); + +void avr_isp_reader_view_set_callback( + AvrIspReaderView* instance, + AvrIspReaderViewCallback callback, + void* context); + +AvrIspReaderView* avr_isp_reader_view_alloc(); + +void avr_isp_reader_view_free(AvrIspReaderView* instance); + +View* avr_isp_reader_view_get_view(AvrIspReaderView* instance); + +void avr_isp_reader_view_exit(void* context); diff --git a/applications/external/avr_isp_programmer/views/avr_isp_view_writer.c b/applications/external/avr_isp_programmer/views/avr_isp_view_writer.c new file mode 100644 index 00000000000..a06b7853555 --- /dev/null +++ b/applications/external/avr_isp_programmer/views/avr_isp_view_writer.c @@ -0,0 +1,268 @@ +#include "avr_isp_view_writer.h" +#include + +#include "../helpers/avr_isp_worker_rw.h" +#include + +struct AvrIspWriterView { + View* view; + AvrIspWorkerRW* avr_isp_worker_rw; + const char* file_path; + const char* file_name; + AvrIspWriterViewCallback callback; + void* context; +}; + +typedef struct { + AvrIspWriterViewStatus status; + float progress_flash; + float progress_eeprom; +} AvrIspWriterViewModel; + +void avr_isp_writer_update_progress(AvrIspWriterView* instance) { + with_view_model( + instance->view, + AvrIspWriterViewModel * model, + { + model->progress_flash = + avr_isp_worker_rw_get_progress_flash(instance->avr_isp_worker_rw); + model->progress_eeprom = + avr_isp_worker_rw_get_progress_eeprom(instance->avr_isp_worker_rw); + }, + true); +} + +void avr_isp_writer_view_set_callback( + AvrIspWriterView* instance, + AvrIspWriterViewCallback callback, + void* context) { + furi_assert(instance); + furi_assert(callback); + + instance->callback = callback; + instance->context = context; +} + +void avr_isp_writer_set_file_path( + AvrIspWriterView* instance, + const char* file_path, + const char* file_name) { + furi_assert(instance); + + instance->file_path = file_path; + instance->file_name = file_name; +} + +void avr_isp_writer_view_draw(Canvas* canvas, AvrIspWriterViewModel* model) { + canvas_clear(canvas); + char str_flash[32] = {0}; + char str_eeprom[32] = {0}; + + canvas_set_font(canvas, FontPrimary); + + switch(model->status) { + case AvrIspWriterViewStatusIDLE: + canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, "Press start to write"); + canvas_set_font(canvas, FontSecondary); + elements_button_center(canvas, "Start"); + snprintf(str_flash, sizeof(str_flash), "%d%%", (uint8_t)(model->progress_flash * 100)); + snprintf(str_eeprom, sizeof(str_eeprom), "%d%%", (uint8_t)(model->progress_eeprom * 100)); + break; + case AvrIspWriterViewStatusWriting: + if(float_is_equal(model->progress_flash, 0.f)) { + canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, "Verifying firmware"); + snprintf(str_flash, sizeof(str_flash), "***"); + snprintf(str_eeprom, sizeof(str_eeprom), "***"); + } else { + canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, "Writing dump"); + snprintf(str_flash, sizeof(str_flash), "%d%%", (uint8_t)(model->progress_flash * 100)); + snprintf( + str_eeprom, sizeof(str_eeprom), "%d%%", (uint8_t)(model->progress_eeprom * 100)); + } + break; + case AvrIspWriterViewStatusVerification: + canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, "Verifying dump"); + snprintf(str_flash, sizeof(str_flash), "%d%%", (uint8_t)(model->progress_flash * 100)); + snprintf(str_eeprom, sizeof(str_eeprom), "%d%%", (uint8_t)(model->progress_eeprom * 100)); + break; + case AvrIspWriterViewStatusWritingFuse: + canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, "Writing fuse"); + snprintf(str_flash, sizeof(str_flash), "%d%%", (uint8_t)(model->progress_flash * 100)); + snprintf(str_eeprom, sizeof(str_eeprom), "%d%%", (uint8_t)(model->progress_eeprom * 100)); + break; + case AvrIspWriterViewStatusWritingFuseOk: + canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, "Done!"); + snprintf(str_flash, sizeof(str_flash), "%d%%", (uint8_t)(model->progress_flash * 100)); + snprintf(str_eeprom, sizeof(str_eeprom), "%d%%", (uint8_t)(model->progress_eeprom * 100)); + canvas_set_font(canvas, FontSecondary); + elements_button_center(canvas, "Reflash"); + elements_button_right(canvas, "Exit"); + break; + + default: + break; + } + + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 0, 27, "Flash"); + // snprintf(str_buf, sizeof(str_buf), "%d%%", (uint8_t)(model->progress_flash * 100)); + elements_progress_bar_with_text(canvas, 44, 17, 84, model->progress_flash, str_flash); + canvas_draw_str(canvas, 0, 43, "EEPROM"); + // snprintf(str_buf, sizeof(str_buf), "%d%%", (uint8_t)(model->progress_eeprom * 100)); + elements_progress_bar_with_text(canvas, 44, 34, 84, model->progress_eeprom, str_eeprom); +} + +bool avr_isp_writer_view_input(InputEvent* event, void* context) { + furi_assert(context); + AvrIspWriterView* instance = context; + + bool ret = true; + if(event->key == InputKeyBack && event->type == InputTypeShort) { + with_view_model( + instance->view, + AvrIspWriterViewModel * model, + { + if((model->status == AvrIspWriterViewStatusIDLE) || + (model->status == AvrIspWriterViewStatusWritingFuseOk)) { + if(instance->callback) + instance->callback(AvrIspCustomEventSceneExit, instance->context); + ret = false; + } + }, + false); + } else if(event->key == InputKeyOk && event->type == InputTypeShort) { + with_view_model( + instance->view, + AvrIspWriterViewModel * model, + { + if((model->status == AvrIspWriterViewStatusIDLE) || + (model->status == AvrIspWriterViewStatusWritingFuseOk)) { + model->status = AvrIspWriterViewStatusWriting; + + avr_isp_worker_rw_write_dump_start( + instance->avr_isp_worker_rw, instance->file_path, instance->file_name); + } + }, + false); + } else if(event->key == InputKeyRight && event->type == InputTypeShort) { + with_view_model( + instance->view, + AvrIspWriterViewModel * model, + { + if((model->status == AvrIspWriterViewStatusIDLE) || + (model->status == AvrIspWriterViewStatusWritingFuseOk)) { + if(instance->callback) + instance->callback(AvrIspCustomEventSceneExitStartMenu, instance->context); + ret = false; + } + }, + false); + } + return ret; +} + +static void avr_isp_writer_callback_status(void* context, AvrIspWorkerRWStatus status) { + furi_assert(context); + + AvrIspWriterView* instance = context; + with_view_model( + instance->view, + AvrIspWriterViewModel * model, + { + switch(status) { + case AvrIspWorkerRWStatusEndWriting: + model->status = AvrIspWriterViewStatusVerification; + avr_isp_worker_rw_verification_start( + instance->avr_isp_worker_rw, instance->file_path, instance->file_name); + model->status = AvrIspWriterViewStatusVerification; + break; + case AvrIspWorkerRWStatusErrorVerification: + if(instance->callback) + instance->callback(AvrIspCustomEventSceneErrorVerification, instance->context); + break; + case AvrIspWorkerRWStatusEndVerification: + avr_isp_worker_rw_write_fuse_start( + instance->avr_isp_worker_rw, instance->file_path, instance->file_name); + model->status = AvrIspWriterViewStatusWritingFuse; + break; + case AvrIspWorkerRWStatusErrorWritingFuse: + if(instance->callback) + instance->callback(AvrIspCustomEventSceneErrorWritingFuse, instance->context); + break; + case AvrIspWorkerRWStatusEndWritingFuse: + model->status = AvrIspWriterViewStatusWritingFuseOk; + break; + + default: + //AvrIspWorkerRWStatusErrorWriting; + if(instance->callback) + instance->callback(AvrIspCustomEventSceneErrorWriting, instance->context); + break; + } + }, + true); +} + +void avr_isp_writer_view_enter(void* context) { + furi_assert(context); + + AvrIspWriterView* instance = context; + with_view_model( + instance->view, + AvrIspWriterViewModel * model, + { + model->status = AvrIspWriterViewStatusIDLE; + model->progress_flash = 0.0f; + model->progress_eeprom = 0.0f; + }, + true); + + //Start avr_isp_worker_rw + instance->avr_isp_worker_rw = avr_isp_worker_rw_alloc(instance->context); + + avr_isp_worker_rw_set_callback_status( + instance->avr_isp_worker_rw, avr_isp_writer_callback_status, instance); + + avr_isp_worker_rw_start(instance->avr_isp_worker_rw); +} + +void avr_isp_writer_view_exit(void* context) { + furi_assert(context); + AvrIspWriterView* instance = context; + + //Stop avr_isp_worker_rw + if(avr_isp_worker_rw_is_running(instance->avr_isp_worker_rw)) { + avr_isp_worker_rw_stop(instance->avr_isp_worker_rw); + } + + avr_isp_worker_rw_free(instance->avr_isp_worker_rw); +} + +AvrIspWriterView* avr_isp_writer_view_alloc() { + AvrIspWriterView* instance = malloc(sizeof(AvrIspWriterView)); + + // View allocation and configuration + instance->view = view_alloc(); + + view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(AvrIspWriterViewModel)); + view_set_context(instance->view, instance); + view_set_draw_callback(instance->view, (ViewDrawCallback)avr_isp_writer_view_draw); + view_set_input_callback(instance->view, avr_isp_writer_view_input); + view_set_enter_callback(instance->view, avr_isp_writer_view_enter); + view_set_exit_callback(instance->view, avr_isp_writer_view_exit); + + return instance; +} + +void avr_isp_writer_view_free(AvrIspWriterView* instance) { + furi_assert(instance); + + view_free(instance->view); + free(instance); +} + +View* avr_isp_writer_view_get_view(AvrIspWriterView* instance) { + furi_assert(instance); + + return instance->view; +} diff --git a/applications/external/avr_isp_programmer/views/avr_isp_view_writer.h b/applications/external/avr_isp_programmer/views/avr_isp_view_writer.h new file mode 100644 index 00000000000..1ff72838765 --- /dev/null +++ b/applications/external/avr_isp_programmer/views/avr_isp_view_writer.h @@ -0,0 +1,37 @@ +#pragma once + +#include +#include "../helpers/avr_isp_types.h" +#include "../helpers/avr_isp_event.h" + +typedef struct AvrIspWriterView AvrIspWriterView; + +typedef void (*AvrIspWriterViewCallback)(AvrIspCustomEvent event, void* context); + +typedef enum { + AvrIspWriterViewStatusIDLE, + AvrIspWriterViewStatusWriting, + AvrIspWriterViewStatusVerification, + AvrIspWriterViewStatusWritingFuse, + AvrIspWriterViewStatusWritingFuseOk, +} AvrIspWriterViewStatus; + +void avr_isp_writer_update_progress(AvrIspWriterView* instance); + +void avr_isp_writer_set_file_path( + AvrIspWriterView* instance, + const char* file_path, + const char* file_name); + +void avr_isp_writer_view_set_callback( + AvrIspWriterView* instance, + AvrIspWriterViewCallback callback, + void* context); + +AvrIspWriterView* avr_isp_writer_view_alloc(); + +void avr_isp_writer_view_free(AvrIspWriterView* instance); + +View* avr_isp_writer_view_get_view(AvrIspWriterView* instance); + +void avr_isp_writer_view_exit(void* context); diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index 7713beb93b2..3806ac47b07 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -158,6 +158,7 @@ Header,+,lib/toolbox/args.h,, Header,+,lib/toolbox/crc32_calc.h,, Header,+,lib/toolbox/dir_walk.h,, Header,+,lib/toolbox/float_tools.h,, +Header,+,lib/toolbox/hex.h,, Header,+,lib/toolbox/manchester_decoder.h,, Header,+,lib/toolbox/manchester_encoder.h,, Header,+,lib/toolbox/md5.h,, @@ -1309,6 +1310,10 @@ Function,+,gui_view_port_send_to_front,void,"Gui*, ViewPort*" Function,+,hal_sd_detect,_Bool, Function,+,hal_sd_detect_init,void, Function,+,hal_sd_detect_set_low,void, +Function,+,hex_char_to_hex_nibble,_Bool,"char, uint8_t*" +Function,+,hex_char_to_uint8,_Bool,"char, char, uint8_t*" +Function,+,hex_chars_to_uint64,_Bool,"const char*, uint64_t*" +Function,+,hex_chars_to_uint8,_Bool,"const char*, uint8_t*" Function,+,icon_animation_alloc,IconAnimation*,const Icon* Function,+,icon_animation_free,void,IconAnimation* Function,+,icon_animation_get_height,uint8_t,const IconAnimation* @@ -1870,6 +1875,7 @@ Function,+,uECC_sign,int,"const uint8_t*, const uint8_t*, unsigned, uint8_t*, uE Function,-,uECC_sign_deterministic,int,"const uint8_t*, const uint8_t*, unsigned, const uECC_HashContext*, uint8_t*, uECC_Curve" Function,-,uECC_valid_public_key,int,"const uint8_t*, uECC_Curve" Function,-,uECC_verify,int,"const uint8_t*, const uint8_t*, unsigned, const uint8_t*, uECC_Curve" +Function,+,uint8_to_hex_chars,void,"const uint8_t*, uint8_t*, int" Function,-,ulTaskGenericNotifyTake,uint32_t,"UBaseType_t, BaseType_t, TickType_t" Function,-,ulTaskGenericNotifyValueClear,uint32_t,"TaskHandle_t, UBaseType_t, uint32_t" Function,-,ulTaskGetIdleRunTimeCounter,uint32_t, diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index fea7926801f..efc20b4a74b 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -190,6 +190,7 @@ Header,+,lib/toolbox/args.h,, Header,+,lib/toolbox/crc32_calc.h,, Header,+,lib/toolbox/dir_walk.h,, Header,+,lib/toolbox/float_tools.h,, +Header,+,lib/toolbox/hex.h,, Header,+,lib/toolbox/manchester_decoder.h,, Header,+,lib/toolbox/manchester_encoder.h,, Header,+,lib/toolbox/md5.h,, @@ -1597,6 +1598,10 @@ Function,+,gui_view_port_send_to_front,void,"Gui*, ViewPort*" Function,+,hal_sd_detect,_Bool, Function,+,hal_sd_detect_init,void, Function,+,hal_sd_detect_set_low,void, +Function,+,hex_char_to_hex_nibble,_Bool,"char, uint8_t*" +Function,+,hex_char_to_uint8,_Bool,"char, char, uint8_t*" +Function,+,hex_chars_to_uint64,_Bool,"const char*, uint64_t*" +Function,+,hex_chars_to_uint8,_Bool,"const char*, uint8_t*" Function,-,hypot,double,"double, double" Function,-,hypotf,float,"float, float" Function,-,hypotl,long double,"long double, long double" @@ -2801,6 +2806,7 @@ Function,+,uECC_sign,int,"const uint8_t*, const uint8_t*, unsigned, uint8_t*, uE Function,-,uECC_sign_deterministic,int,"const uint8_t*, const uint8_t*, unsigned, const uECC_HashContext*, uint8_t*, uECC_Curve" Function,-,uECC_valid_public_key,int,"const uint8_t*, uECC_Curve" Function,-,uECC_verify,int,"const uint8_t*, const uint8_t*, unsigned, const uint8_t*, uECC_Curve" +Function,+,uint8_to_hex_chars,void,"const uint8_t*, uint8_t*, int" Function,-,ulTaskGenericNotifyTake,uint32_t,"UBaseType_t, BaseType_t, TickType_t" Function,-,ulTaskGenericNotifyValueClear,uint32_t,"TaskHandle_t, UBaseType_t, uint32_t" Function,-,ulTaskGetIdleRunTimeCounter,uint32_t, diff --git a/lib/toolbox/SConscript b/lib/toolbox/SConscript index fad4c558414..bb06c2db4b2 100644 --- a/lib/toolbox/SConscript +++ b/lib/toolbox/SConscript @@ -28,6 +28,7 @@ env.Append( File("stream/buffered_file_stream.h"), File("protocols/protocol_dict.h"), File("pretty_format.h"), + File("hex.h"), ], ) From 0d8518d31ded15a2f1ae6ef559f39c8a8abf286e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Thu, 6 Apr 2023 17:06:19 +0800 Subject: [PATCH 510/824] [FL-3232] FuriHal: fix gpio naming and add explicit pulls for vibro, speaker and ir_tx (#2565) * FuriHal: fix gpio naming and add explicit pulls for vibro, speaker and ir_tx * Github: workflow event debug print * Github: proper PR head commit SHA extraction in get_env.py --- .github/workflows/build.yml | 4 ++-- .github/workflows/merge_report.yml | 2 +- .github/workflows/pvs_studio.yml | 2 +- applications/debug/accessor/accessor_app.cpp | 2 +- .../examples/example_thermo/README.md | 4 ++-- .../examples/example_thermo/example_thermo.c | 4 ++-- applications/main/onewire/onewire_cli.c | 2 +- firmware/targets/f18/api_symbols.csv | 8 +++---- .../targets/f18/furi_hal/furi_hal_resources.c | 20 ++++++++++-------- .../targets/f18/furi_hal/furi_hal_resources.h | 6 +++--- firmware/targets/f7/api_symbols.csv | 8 +++---- .../targets/f7/furi_hal/furi_hal_ibutton.c | 10 ++++----- .../targets/f7/furi_hal/furi_hal_infrared.c | 4 ++-- firmware/targets/f7/furi_hal/furi_hal_power.c | 4 ++-- .../targets/f7/furi_hal/furi_hal_resources.c | 21 +++++++++++-------- .../targets/f7/furi_hal/furi_hal_resources.h | 6 +++--- .../targets/f7/furi_hal/furi_hal_speaker.c | 2 +- firmware/targets/f7/furi_hal/furi_hal_vibro.c | 6 +++--- .../protocols/dallas/protocol_group_dallas.c | 4 ++-- scripts/get_env.py | 2 +- 20 files changed, 63 insertions(+), 58 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 56e50d5f408..5f6f50a9d0d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -37,7 +37,7 @@ jobs: else TYPE="other" fi - python3 scripts/get_env.py "--event_file=${{ github.event_path }}" "--type=$TYPE" + python3 scripts/get_env.py "--event_file=${{ github.event_path }}" "--type=$TYPE" || cat "${{ github.event_path }}" echo random_hash=$(openssl rand -base64 40 | shasum -a 256 | awk '{print $1}') >> $GITHUB_OUTPUT echo "event_type=$TYPE" >> $GITHUB_OUTPUT @@ -182,7 +182,7 @@ jobs: else TYPE="other" fi - python3 scripts/get_env.py "--event_file=${{ github.event_path }}" "--type=$TYPE" + python3 scripts/get_env.py "--event_file=${{ github.event_path }}" "--type=$TYPE" || cat "${{ github.event_path }}" - name: 'Build the firmware' run: | diff --git a/.github/workflows/merge_report.yml b/.github/workflows/merge_report.yml index 71515e1c5c9..5b7d5fcbf7c 100644 --- a/.github/workflows/merge_report.yml +++ b/.github/workflows/merge_report.yml @@ -30,7 +30,7 @@ jobs: else TYPE="other" fi - python3 scripts/get_env.py "--event_file=${{ github.event_path }}" "--type=$TYPE" + python3 scripts/get_env.py "--event_file=${{ github.event_path }}" "--type=$TYPE" || cat "${{ github.event_path }}" - name: 'Check ticket and report' run: | diff --git a/.github/workflows/pvs_studio.yml b/.github/workflows/pvs_studio.yml index 6dbf84edb88..b8c4d7a367d 100644 --- a/.github/workflows/pvs_studio.yml +++ b/.github/workflows/pvs_studio.yml @@ -38,7 +38,7 @@ jobs: else TYPE="other" fi - python3 scripts/get_env.py "--event_file=${{ github.event_path }}" "--type=$TYPE" + python3 scripts/get_env.py "--event_file=${{ github.event_path }}" "--type=$TYPE" || cat "${{ github.event_path }}" - name: 'Supply PVS credentials' run: | diff --git a/applications/debug/accessor/accessor_app.cpp b/applications/debug/accessor/accessor_app.cpp index 337437d0eba..2e40b3c35d7 100644 --- a/applications/debug/accessor/accessor_app.cpp +++ b/applications/debug/accessor/accessor_app.cpp @@ -34,7 +34,7 @@ void AccessorApp::run(void) { AccessorApp::AccessorApp() : text_store{0} { notification = static_cast(furi_record_open(RECORD_NOTIFICATION)); - onewire_host = onewire_host_alloc(&ibutton_gpio); + onewire_host = onewire_host_alloc(&gpio_ibutton); furi_hal_power_enable_otg(); } diff --git a/applications/examples/example_thermo/README.md b/applications/examples/example_thermo/README.md index fa00264dc15..08240a1f8fa 100644 --- a/applications/examples/example_thermo/README.md +++ b/applications/examples/example_thermo/README.md @@ -33,10 +33,10 @@ It is possible to use other GPIO pin as a 1-Wire data pin. In order to change it - gpio_ext_pa4 - gpio_ext_pa6 - gpio_ext_pa7 - - ibutton_gpio + - gpio_ibutton */ -#define THERMO_GPIO_PIN (ibutton_gpio) +#define THERMO_GPIO_PIN (gpio_ibutton) ``` Do not forget about the external pull-up resistor as these pins do not have one built-in. diff --git a/applications/examples/example_thermo/example_thermo.c b/applications/examples/example_thermo/example_thermo.c index 4241cb59d94..5cb8863a476 100644 --- a/applications/examples/example_thermo/example_thermo.c +++ b/applications/examples/example_thermo/example_thermo.c @@ -43,10 +43,10 @@ - gpio_ext_pa4 - gpio_ext_pa6 - gpio_ext_pa7 - - ibutton_gpio + - gpio_ibutton */ -#define THERMO_GPIO_PIN (ibutton_gpio) +#define THERMO_GPIO_PIN (gpio_ibutton) /* Flags which the reader thread responds to */ typedef enum { diff --git a/applications/main/onewire/onewire_cli.c b/applications/main/onewire/onewire_cli.c index 4c16fb3890e..5f6cdc670bd 100644 --- a/applications/main/onewire/onewire_cli.c +++ b/applications/main/onewire/onewire_cli.c @@ -25,7 +25,7 @@ static void onewire_cli_print_usage() { static void onewire_cli_search(Cli* cli) { UNUSED(cli); - OneWireHost* onewire = onewire_host_alloc(&ibutton_gpio); + OneWireHost* onewire = onewire_host_alloc(&gpio_ibutton); uint8_t address[8]; bool done = false; diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index 3806ac47b07..5d5b7bbcf2c 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,20.1,, +Version,+,21.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -2161,7 +2161,7 @@ Variable,+,gpio_usart_rx,const GpioPin, Variable,+,gpio_usart_tx,const GpioPin, Variable,+,gpio_usb_dm,const GpioPin, Variable,+,gpio_usb_dp,const GpioPin, -Variable,+,ibutton_gpio,const GpioPin, +Variable,+,gpio_ibutton,const GpioPin, Variable,+,input_pins,const InputPin[], Variable,+,input_pins_count,const size_t, Variable,+,message_blink_set_color_blue,const NotificationMessage, @@ -2309,7 +2309,7 @@ Variable,+,message_red_255,const NotificationMessage, Variable,+,message_sound_off,const NotificationMessage, Variable,+,message_vibro_off,const NotificationMessage, Variable,+,message_vibro_on,const NotificationMessage, -Variable,+,periph_power,const GpioPin, +Variable,+,gpio_periph_power,const GpioPin, Variable,+,sequence_audiovisual_alert,const NotificationSequence, Variable,+,sequence_blink_blue_10,const NotificationSequence, Variable,+,sequence_blink_blue_100,const NotificationSequence, @@ -2364,4 +2364,4 @@ Variable,+,usb_cdc_single,FuriHalUsbInterface, Variable,+,usb_hid,FuriHalUsbInterface, Variable,+,usb_hid_u2f,FuriHalUsbInterface, Variable,+,usbd_devfs,const usbd_driver, -Variable,+,vibro_gpio,const GpioPin, +Variable,+,gpio_vibro,const GpioPin, diff --git a/firmware/targets/f18/furi_hal/furi_hal_resources.c b/firmware/targets/f18/furi_hal/furi_hal_resources.c index abb258cb114..19bc9f99840 100644 --- a/firmware/targets/f18/furi_hal/furi_hal_resources.c +++ b/firmware/targets/f18/furi_hal/furi_hal_resources.c @@ -6,8 +6,8 @@ #define TAG "FuriHalResources" -const GpioPin vibro_gpio = {.port = GPIOA, .pin = LL_GPIO_PIN_8}; -const GpioPin ibutton_gpio = {.port = GPIOB, .pin = LL_GPIO_PIN_14}; +const GpioPin gpio_vibro = {.port = GPIOA, .pin = LL_GPIO_PIN_8}; +const GpioPin gpio_ibutton = {.port = GPIOB, .pin = LL_GPIO_PIN_14}; const GpioPin gpio_display_cs = {.port = GPIOC, .pin = LL_GPIO_PIN_11}; const GpioPin gpio_display_rst_n = {.port = GPIOB, .pin = LL_GPIO_PIN_0}; @@ -57,7 +57,7 @@ const GpioPin gpio_i2c_power_scl = {.port = GPIOA, .pin = LL_GPIO_PIN_9}; const GpioPin gpio_speaker = {.port = GPIOB, .pin = LL_GPIO_PIN_8}; -const GpioPin periph_power = {.port = GPIOA, .pin = LL_GPIO_PIN_3}; +const GpioPin gpio_periph_power = {.port = GPIOA, .pin = LL_GPIO_PIN_3}; const GpioPin gpio_usb_dm = {.port = GPIOA, .pin = LL_GPIO_PIN_11}; const GpioPin gpio_usb_dp = {.port = GPIOA, .pin = LL_GPIO_PIN_12}; @@ -118,8 +118,8 @@ void furi_hal_resources_init_early() { furi_hal_resources_init_input_pins(GpioModeInput); // SD Card stepdown control - furi_hal_gpio_write(&periph_power, 1); - furi_hal_gpio_init(&periph_power, GpioModeOutputOpenDrain, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_write(&gpio_periph_power, 1); + furi_hal_gpio_init(&gpio_periph_power, GpioModeOutputOpenDrain, GpioPullNo, GpioSpeedLow); // Display pins furi_hal_gpio_write(&gpio_display_rst_n, 1); @@ -165,6 +165,10 @@ void furi_hal_resources_init() { // Button pins furi_hal_resources_init_input_pins(GpioModeInterruptRiseFall); + // Explicit pulls pins + furi_hal_gpio_init(&gpio_speaker, GpioModeAnalog, GpioPullDown, GpioSpeedLow); + furi_hal_gpio_init(&gpio_vibro, GpioModeAnalog, GpioPullDown, GpioSpeedLow); + // Display pins furi_hal_gpio_init(&gpio_display_rst_n, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); furi_hal_gpio_write(&gpio_display_rst_n, 0); @@ -176,9 +180,7 @@ void furi_hal_resources_init() { furi_hal_gpio_init(&gpio_sdcard_cd, GpioModeInput, GpioPullNo, GpioSpeedLow); furi_hal_gpio_write(&gpio_sdcard_cd, 0); - furi_hal_gpio_init(&vibro_gpio, GpioModeAnalog, GpioPullNo, GpioSpeedLow); - - furi_hal_gpio_init(&ibutton_gpio, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_init(&gpio_ibutton, GpioModeAnalog, GpioPullNo, GpioSpeedLow); NVIC_SetPriority(EXTI0_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 5, 0)); NVIC_EnableIRQ(EXTI0_IRQn); @@ -222,7 +224,7 @@ int32_t furi_hal_resources_get_ext_pin_number(const GpioPin* gpio) { return 15; else if(gpio == &gpio_ext_pc0) return 16; - else if(gpio == &ibutton_gpio) + else if(gpio == &gpio_ibutton) return 17; else return -1; diff --git a/firmware/targets/f18/furi_hal/furi_hal_resources.h b/firmware/targets/f18/furi_hal/furi_hal_resources.h index a24afbf9410..3c4708d15f9 100644 --- a/firmware/targets/f18/furi_hal/furi_hal_resources.h +++ b/firmware/targets/f18/furi_hal/furi_hal_resources.h @@ -50,8 +50,8 @@ extern const size_t input_pins_count; extern const GpioPinRecord gpio_pins[]; extern const size_t gpio_pins_count; -extern const GpioPin vibro_gpio; -extern const GpioPin ibutton_gpio; +extern const GpioPin gpio_vibro; +extern const GpioPin gpio_ibutton; extern const GpioPin gpio_display_cs; extern const GpioPin gpio_display_rst_n; @@ -100,7 +100,7 @@ extern const GpioPin gpio_i2c_power_scl; extern const GpioPin gpio_speaker; -extern const GpioPin periph_power; +extern const GpioPin gpio_periph_power; extern const GpioPin gpio_usb_dm; extern const GpioPin gpio_usb_dp; diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index efc20b4a74b..89984352a2d 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,20.1,, +Version,+,21.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -3100,7 +3100,7 @@ Variable,+,gpio_usart_rx,const GpioPin, Variable,+,gpio_usart_tx,const GpioPin, Variable,+,gpio_usb_dm,const GpioPin, Variable,+,gpio_usb_dp,const GpioPin, -Variable,+,ibutton_gpio,const GpioPin, +Variable,+,gpio_ibutton,const GpioPin, Variable,+,input_pins,const InputPin[], Variable,+,input_pins_count,const size_t, Variable,+,lfrfid_protocols,const ProtocolBase*[], @@ -3249,7 +3249,7 @@ Variable,+,message_red_255,const NotificationMessage, Variable,+,message_sound_off,const NotificationMessage, Variable,+,message_vibro_off,const NotificationMessage, Variable,+,message_vibro_on,const NotificationMessage, -Variable,+,periph_power,const GpioPin, +Variable,+,gpio_periph_power,const GpioPin, Variable,+,sequence_audiovisual_alert,const NotificationSequence, Variable,+,sequence_blink_blue_10,const NotificationSequence, Variable,+,sequence_blink_blue_100,const NotificationSequence, @@ -3307,4 +3307,4 @@ Variable,+,usb_cdc_single,FuriHalUsbInterface, Variable,+,usb_hid,FuriHalUsbInterface, Variable,+,usb_hid_u2f,FuriHalUsbInterface, Variable,+,usbd_devfs,const usbd_driver, -Variable,+,vibro_gpio,const GpioPin, +Variable,+,gpio_vibro,const GpioPin, diff --git a/firmware/targets/f7/furi_hal/furi_hal_ibutton.c b/firmware/targets/f7/furi_hal/furi_hal_ibutton.c index f19fd0a0ef0..c8041c9f2cd 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_ibutton.c +++ b/firmware/targets/f7/furi_hal/furi_hal_ibutton.c @@ -93,15 +93,15 @@ void furi_hal_ibutton_emulate_stop() { } void furi_hal_ibutton_pin_configure() { - furi_hal_gpio_write(&ibutton_gpio, true); - furi_hal_gpio_init(&ibutton_gpio, GpioModeOutputOpenDrain, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_write(&gpio_ibutton, true); + furi_hal_gpio_init(&gpio_ibutton, GpioModeOutputOpenDrain, GpioPullNo, GpioSpeedLow); } void furi_hal_ibutton_pin_reset() { - furi_hal_gpio_write(&ibutton_gpio, true); - furi_hal_gpio_init(&ibutton_gpio, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_write(&gpio_ibutton, true); + furi_hal_gpio_init(&gpio_ibutton, GpioModeAnalog, GpioPullNo, GpioSpeedLow); } void furi_hal_ibutton_pin_write(const bool state) { - furi_hal_gpio_write(&ibutton_gpio, state); + furi_hal_gpio_write(&gpio_ibutton, state); } diff --git a/firmware/targets/f7/furi_hal/furi_hal_infrared.c b/firmware/targets/f7/furi_hal/furi_hal_infrared.c index c1d24f8039c..2598e5fa314 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_infrared.c +++ b/firmware/targets/f7/furi_hal/furi_hal_infrared.c @@ -17,7 +17,7 @@ #if INFRARED_TX_DEBUG == 1 #define gpio_infrared_tx gpio_infrared_tx_debug -const GpioPin gpio_infrared_tx_debug = {.port = GPIOA, .pin = GPIO_PIN_7}; +const GpioPin gpio_infrared_tx_debug = {.port = GPIOA, .pin = GpioModeAnalog}; #endif #define INFRARED_TIM_TX_DMA_BUFFER_SIZE 200 @@ -567,7 +567,7 @@ static void furi_hal_infrared_async_tx_free_resources(void) { (furi_hal_infrared_state == InfraredStateIdle) || (furi_hal_infrared_state == InfraredStateAsyncTxStopped)); - furi_hal_gpio_init(&gpio_infrared_tx, GpioModeOutputOpenDrain, GpioPullDown, GpioSpeedLow); + furi_hal_gpio_init(&gpio_infrared_tx, GpioModeAnalog, GpioPullDown, GpioSpeedLow); furi_hal_interrupt_set_isr(IR_DMA_CH1_IRQ, NULL, NULL); furi_hal_interrupt_set_isr(IR_DMA_CH2_IRQ, NULL, NULL); LL_TIM_DeInit(TIM1); diff --git a/firmware/targets/f7/furi_hal/furi_hal_power.c b/firmware/targets/f7/furi_hal/furi_hal_power.c index dd7c34ae735..fd601ec7ec8 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_power.c +++ b/firmware/targets/f7/furi_hal/furi_hal_power.c @@ -440,11 +440,11 @@ float furi_hal_power_get_usb_voltage() { } void furi_hal_power_enable_external_3_3v() { - furi_hal_gpio_write(&periph_power, 1); + furi_hal_gpio_write(&gpio_periph_power, 1); } void furi_hal_power_disable_external_3_3v() { - furi_hal_gpio_write(&periph_power, 0); + furi_hal_gpio_write(&gpio_periph_power, 0); } void furi_hal_power_suppress_charge_enter() { diff --git a/firmware/targets/f7/furi_hal/furi_hal_resources.c b/firmware/targets/f7/furi_hal/furi_hal_resources.c index d0d85cb2d6c..df9530079c6 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_resources.c +++ b/firmware/targets/f7/furi_hal/furi_hal_resources.c @@ -6,8 +6,8 @@ #define TAG "FuriHalResources" -const GpioPin vibro_gpio = {.port = VIBRO_GPIO_Port, .pin = VIBRO_Pin}; -const GpioPin ibutton_gpio = {.port = iBTN_GPIO_Port, .pin = iBTN_Pin}; +const GpioPin gpio_vibro = {.port = VIBRO_GPIO_Port, .pin = VIBRO_Pin}; +const GpioPin gpio_ibutton = {.port = iBTN_GPIO_Port, .pin = iBTN_Pin}; const GpioPin gpio_cc1101_g0 = {.port = CC1101_G0_GPIO_Port, .pin = CC1101_G0_Pin}; const GpioPin gpio_rf_sw_0 = {.port = RF_SW_0_GPIO_Port, .pin = RF_SW_0_Pin}; @@ -59,7 +59,7 @@ const GpioPin gpio_i2c_power_scl = {.port = GPIOA, .pin = LL_GPIO_PIN_9}; const GpioPin gpio_speaker = {.port = GPIOB, .pin = LL_GPIO_PIN_8}; -const GpioPin periph_power = {.port = GPIOA, .pin = LL_GPIO_PIN_3}; +const GpioPin gpio_periph_power = {.port = GPIOA, .pin = LL_GPIO_PIN_3}; const GpioPin gpio_usb_dm = {.port = GPIOA, .pin = LL_GPIO_PIN_11}; const GpioPin gpio_usb_dp = {.port = GPIOA, .pin = LL_GPIO_PIN_12}; @@ -106,8 +106,8 @@ void furi_hal_resources_init_early() { furi_hal_resources_init_input_pins(GpioModeInput); // SD Card stepdown control - furi_hal_gpio_write(&periph_power, 1); - furi_hal_gpio_init(&periph_power, GpioModeOutputOpenDrain, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_write(&gpio_periph_power, 1); + furi_hal_gpio_init(&gpio_periph_power, GpioModeOutputOpenDrain, GpioPullNo, GpioSpeedLow); // Display pins furi_hal_gpio_write(&gpio_display_rst_n, 1); @@ -153,6 +153,11 @@ void furi_hal_resources_init() { // Button pins furi_hal_resources_init_input_pins(GpioModeInterruptRiseFall); + // Explicit pulls pins + furi_hal_gpio_init(&gpio_infrared_tx, GpioModeAnalog, GpioPullDown, GpioSpeedLow); + furi_hal_gpio_init(&gpio_speaker, GpioModeAnalog, GpioPullDown, GpioSpeedLow); + furi_hal_gpio_init(&gpio_vibro, GpioModeAnalog, GpioPullDown, GpioSpeedLow); + // Display pins furi_hal_gpio_init(&gpio_display_rst_n, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); furi_hal_gpio_write(&gpio_display_rst_n, 0); @@ -164,9 +169,7 @@ void furi_hal_resources_init() { furi_hal_gpio_init(&gpio_sdcard_cd, GpioModeInput, GpioPullNo, GpioSpeedLow); furi_hal_gpio_write(&gpio_sdcard_cd, 0); - furi_hal_gpio_init(&vibro_gpio, GpioModeAnalog, GpioPullNo, GpioSpeedLow); - - furi_hal_gpio_init(&ibutton_gpio, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_init(&gpio_ibutton, GpioModeAnalog, GpioPullNo, GpioSpeedLow); furi_hal_gpio_init(&gpio_nfc_irq_rfid_pull, GpioModeInterruptRise, GpioPullNo, GpioSpeedLow); @@ -213,7 +216,7 @@ int32_t furi_hal_resources_get_ext_pin_number(const GpioPin* gpio) { return 15; else if(gpio == &gpio_ext_pc0) return 16; - else if(gpio == &ibutton_gpio) + else if(gpio == &gpio_ibutton) return 17; else return -1; diff --git a/firmware/targets/f7/furi_hal/furi_hal_resources.h b/firmware/targets/f7/furi_hal/furi_hal_resources.h index 04b99a84e19..f2991730010 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_resources.h +++ b/firmware/targets/f7/furi_hal/furi_hal_resources.h @@ -50,8 +50,8 @@ extern const size_t input_pins_count; extern const GpioPinRecord gpio_pins[]; extern const size_t gpio_pins_count; -extern const GpioPin vibro_gpio; -extern const GpioPin ibutton_gpio; +extern const GpioPin gpio_vibro; +extern const GpioPin gpio_ibutton; extern const GpioPin gpio_cc1101_g0; extern const GpioPin gpio_rf_sw_0; @@ -102,7 +102,7 @@ extern const GpioPin gpio_i2c_power_scl; extern const GpioPin gpio_speaker; -extern const GpioPin periph_power; +extern const GpioPin gpio_periph_power; extern const GpioPin gpio_usb_dm; extern const GpioPin gpio_usb_dp; diff --git a/firmware/targets/f7/furi_hal/furi_hal_speaker.c b/firmware/targets/f7/furi_hal/furi_hal_speaker.c index c4a0bdd1e8e..5421509cca5 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_speaker.c +++ b/firmware/targets/f7/furi_hal/furi_hal_speaker.c @@ -52,7 +52,7 @@ void furi_hal_speaker_release() { furi_check(furi_hal_speaker_is_mine()); furi_hal_speaker_stop(); - furi_hal_gpio_init(&gpio_speaker, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_init(&gpio_speaker, GpioModeAnalog, GpioPullDown, GpioSpeedLow); furi_hal_power_insomnia_exit(); furi_check(furi_mutex_release(furi_hal_speaker_mutex) == FuriStatusOk); diff --git a/firmware/targets/f7/furi_hal/furi_hal_vibro.c b/firmware/targets/f7/furi_hal/furi_hal_vibro.c index 4315ea63759..f4678467750 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_vibro.c +++ b/firmware/targets/f7/furi_hal/furi_hal_vibro.c @@ -4,11 +4,11 @@ #define TAG "FuriHalVibro" void furi_hal_vibro_init() { - furi_hal_gpio_init(&vibro_gpio, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); - furi_hal_gpio_write(&vibro_gpio, false); + furi_hal_gpio_init(&gpio_vibro, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_write(&gpio_vibro, false); FURI_LOG_I(TAG, "Init OK"); } void furi_hal_vibro_on(bool value) { - furi_hal_gpio_write(&vibro_gpio, value); + furi_hal_gpio_write(&gpio_vibro, value); } diff --git a/lib/ibutton/protocols/dallas/protocol_group_dallas.c b/lib/ibutton/protocols/dallas/protocol_group_dallas.c index d0ffa511b43..63ec97855ab 100644 --- a/lib/ibutton/protocols/dallas/protocol_group_dallas.c +++ b/lib/ibutton/protocols/dallas/protocol_group_dallas.c @@ -14,8 +14,8 @@ typedef struct { static iButtonProtocolGroupDallas* ibutton_protocol_group_dallas_alloc() { iButtonProtocolGroupDallas* group = malloc(sizeof(iButtonProtocolGroupDallas)); - group->host = onewire_host_alloc(&ibutton_gpio); - group->bus = onewire_slave_alloc(&ibutton_gpio); + group->host = onewire_host_alloc(&gpio_ibutton); + group->bus = onewire_slave_alloc(&gpio_ibutton); return group; } diff --git a/scripts/get_env.py b/scripts/get_env.py index f661f38d625..92f9243c2de 100644 --- a/scripts/get_env.py +++ b/scripts/get_env.py @@ -32,7 +32,7 @@ def parse_args(): def get_commit_json(event): context = ssl._create_unverified_context() commit_url = event["pull_request"]["base"]["repo"]["commits_url"].replace( - "{/sha}", f"/{event['after']}" + "{/sha}", f"/{event['pull_request']['head']['sha']}" ) with urllib.request.urlopen(commit_url, context=context) as commit_file: commit_json = json.loads(commit_file.read().decode("utf-8")) From 89161a7a1e14d6365626e76068035acc45954934 Mon Sep 17 00:00:00 2001 From: hedger Date: Thu, 6 Apr 2023 23:37:12 +0400 Subject: [PATCH 511/824] scripts: sconsdist: added stub file artifact for older ufbt (#2568) * scripts: sconsdist: added stub file artifact for older ufbt * scripts: sconsdist: not creating dummy SDK archive --- scripts/sconsdist.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/scripts/sconsdist.py b/scripts/sconsdist.py index c2cfecd4e55..2e28ebef453 100644 --- a/scripts/sconsdist.py +++ b/scripts/sconsdist.py @@ -84,21 +84,16 @@ def copy_single_project(self, project: ProjectDir) -> None: if exists(sdk_folder := join(obj_directory, foldertype)): self.note_dist_component(foldertype, "dir", sdk_folder) - def package_zip(self, foldertype, sdk_folder): + # TODO: remove this after everyone migrates to new uFBT + self.create_zip_stub("lib") + + def create_zip_stub(self, foldertype): with zipfile.ZipFile( self.get_dist_path(self.get_dist_file_name(foldertype, "zip")), "w", zipfile.ZIP_DEFLATED, - ) as zf: - for root, _, files in walk(sdk_folder): - for file in files: - zf.write( - join(root, file), - relpath( - join(root, file), - sdk_folder, - ), - ) + ) as _: + pass def copy(self) -> int: self._dist_components: dict[str, str] = dict() From 6cc5f30c84e6991af1fa7318922a6aab02ce1a67 Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Fri, 7 Apr 2023 07:02:29 +0400 Subject: [PATCH 512/824] Fix gpio state isp programmer (#2567) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ISP: fix state gpio ISP Programmer * WS: delete string debug Co-authored-by: あく --- applications/external/avr_isp_programmer/helpers/avr_isp.c | 1 + .../external/avr_isp_programmer/helpers/avr_isp_worker_rw.c | 5 +++-- .../external/avr_isp_programmer/lib/driver/avr_isp_prog.c | 1 + .../external/avr_isp_programmer/lib/driver/avr_isp_spi_sw.c | 2 -- .../external/weather_station/protocols/lacrosse_tx141thbv2.c | 5 ----- 5 files changed, 5 insertions(+), 9 deletions(-) diff --git a/applications/external/avr_isp_programmer/helpers/avr_isp.c b/applications/external/avr_isp_programmer/helpers/avr_isp.c index 76e0a80b061..51b4f8846a1 100644 --- a/applications/external/avr_isp_programmer/helpers/avr_isp.c +++ b/applications/external/avr_isp_programmer/helpers/avr_isp.c @@ -152,6 +152,7 @@ bool avr_isp_auto_set_spi_speed_start_pmode(AvrIsp* instance) { } } } + if(instance->spi) avr_isp_spi_sw_free(instance->spi); return false; } diff --git a/applications/external/avr_isp_programmer/helpers/avr_isp_worker_rw.c b/applications/external/avr_isp_programmer/helpers/avr_isp_worker_rw.c index fc8d3b09fe0..0ee5cefa1de 100644 --- a/applications/external/avr_isp_programmer/helpers/avr_isp_worker_rw.c +++ b/applications/external/avr_isp_programmer/helpers/avr_isp_worker_rw.c @@ -198,9 +198,10 @@ bool avr_isp_worker_rw_detect_chip(AvrIspWorkerRW* instance) { } avr_isp_end_pmode(instance->avr_isp); - furi_hal_pwm_stop(FuriHalPwmOutputIdLptim2PA4); - } while(0); + + furi_hal_pwm_stop(FuriHalPwmOutputIdLptim2PA4); + if(instance->callback) { if(instance->chip_arr_ind > avr_isp_chip_arr_size) { instance->callback(instance->context, "No detect", instance->chip_detect, 0); diff --git a/applications/external/avr_isp_programmer/lib/driver/avr_isp_prog.c b/applications/external/avr_isp_programmer/lib/driver/avr_isp_prog.c index b457e4c27b7..b3c81f3b13a 100644 --- a/applications/external/avr_isp_programmer/lib/driver/avr_isp_prog.c +++ b/applications/external/avr_isp_programmer/lib/driver/avr_isp_prog.c @@ -317,6 +317,7 @@ static bool avr_isp_prog_auto_set_spi_speed_start_pmode(AvrIspProg* instance) { } } } + if(instance->spi) avr_isp_spi_sw_free(instance->spi); return false; } diff --git a/applications/external/avr_isp_programmer/lib/driver/avr_isp_spi_sw.c b/applications/external/avr_isp_programmer/lib/driver/avr_isp_spi_sw.c index f60850c841a..c6d9d54c892 100644 --- a/applications/external/avr_isp_programmer/lib/driver/avr_isp_spi_sw.c +++ b/applications/external/avr_isp_programmer/lib/driver/avr_isp_spi_sw.c @@ -18,7 +18,6 @@ struct AvrIspSpiSw { AvrIspSpiSw* avr_isp_spi_sw_init(AvrIspSpiSwSpeed speed) { AvrIspSpiSw* instance = malloc(sizeof(AvrIspSpiSw)); instance->speed_wait_time = speed; - instance->miso = AVR_ISP_SPI_SW_MISO; instance->mosi = AVR_ISP_SPI_SW_MOSI; instance->sck = AVR_ISP_SPI_SW_SCK; @@ -40,7 +39,6 @@ void avr_isp_spi_sw_free(AvrIspSpiSw* instance) { furi_hal_gpio_init(instance->miso, GpioModeAnalog, GpioPullNo, GpioSpeedLow); furi_hal_gpio_init(instance->mosi, GpioModeAnalog, GpioPullNo, GpioSpeedLow); furi_hal_gpio_init(instance->sck, GpioModeAnalog, GpioPullNo, GpioSpeedLow); - free(instance); } diff --git a/applications/external/weather_station/protocols/lacrosse_tx141thbv2.c b/applications/external/weather_station/protocols/lacrosse_tx141thbv2.c index 33a61cee06d..f2fddd40c7a 100644 --- a/applications/external/weather_station/protocols/lacrosse_tx141thbv2.c +++ b/applications/external/weather_station/protocols/lacrosse_tx141thbv2.c @@ -217,11 +217,6 @@ void ws_protocol_decoder_lacrosse_tx141thbv2_feed(void* context, bool level, uin ws_protocol_lacrosse_tx141thbv2_const.te_delta * 2) && (DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_short * 4) < ws_protocol_lacrosse_tx141thbv2_const.te_delta * 2))) { - FURI_LOG_E( - "WS", - "%llX %d", - instance->decoder.decode_data, - instance->decoder.decode_count_bit); if((instance->decoder.decode_count_bit == ws_protocol_lacrosse_tx141thbv2_const.min_count_bit_for_found) || (instance->decoder.decode_count_bit == LACROSSE_TX141TH_BV2_BIT_COUNT)) { From b9ccb274a77bf1f8aac56afbab7a3cc98fb6cf8f Mon Sep 17 00:00:00 2001 From: hedger Date: Mon, 10 Apr 2023 18:46:22 +0400 Subject: [PATCH 513/824] ufbt: project & debugging updates (#2572) * ufbt: removed warning in "channel=dev" update mode * ufbt: removed API version warning; added get_blackmagic & get_apiversion targets * ufbt: updater project template to include blackmagic & jlink targets * ufbt: project template: fixes & updates * ufbt: project template: added config update shortcut * sdk: using fixed names for file components --- .gitignore | 2 +- scripts/sconsdist.py | 6 +- scripts/ufbt/SConstruct | 22 +++++++ .../ufbt/project_template/.vscode/launch.json | 63 +++++++++---------- .../ufbt/project_template/.vscode/tasks.json | 36 +++++++---- .../app_template/application.fam | 2 +- scripts/ufbt/site_tools/ufbt_state.py | 6 -- site_scons/extapps.scons | 2 +- 8 files changed, 83 insertions(+), 56 deletions(-) diff --git a/.gitignore b/.gitignore index 81e985db7a0..89e129acea0 100644 --- a/.gitignore +++ b/.gitignore @@ -30,7 +30,7 @@ bindings/ Brewfile.lock.json # Visual Studio Code -.vscode/ +/.vscode/ # Kate .kateproject diff --git a/scripts/sconsdist.py b/scripts/sconsdist.py index 2e28ebef453..af2554d0ad6 100644 --- a/scripts/sconsdist.py +++ b/scripts/sconsdist.py @@ -181,9 +181,9 @@ def bundle_sdk(self): ) as zf: for component_key in sdk_components_keys: component_path = self._dist_components.get(component_key) - components_paths[component_key] = basename(component_path) if component_key.endswith(".dir"): + components_paths[component_key] = basename(component_path) for root, dirnames, files in walk(component_path): if "__pycache__" in dirnames: dirnames.remove("__pycache__") @@ -199,7 +199,9 @@ def bundle_sdk(self): ), ) else: - zf.write(component_path, basename(component_path)) + # We use fixed names for files to avoid having to regenerate VSCode project + components_paths[component_key] = component_key + zf.write(component_path, component_key) zf.writestr( "components.json", diff --git a/scripts/ufbt/SConstruct b/scripts/ufbt/SConstruct index a82189c147e..7228e2f510c 100644 --- a/scripts/ufbt/SConstruct +++ b/scripts/ufbt/SConstruct @@ -163,6 +163,18 @@ dist_env.Alias("flash", openocd_target) if env["FORCE"]: env.AlwaysBuild(openocd_target) + +firmware_jflash = dist_env.JFlash( + dist_env["UFBT_STATE_DIR"].File("jflash"), + dist_env["FW_BIN"], + JFLASHADDR="0x20000000", +) +dist_env.Alias("firmware_jflash", firmware_jflash) +dist_env.Alias("jflash", firmware_jflash) +if env["FORCE"]: + env.AlwaysBuild(firmware_jflash) + + firmware_debug = dist_env.PhonyTarget( "debug", "${GDBPYCOM}", @@ -391,3 +403,13 @@ AddPostAction( dist_env.Precious(app_template_dist) dist_env.NoClean(app_template_dist) dist_env.Alias("create", app_template_dist) + +dist_env.PhonyTarget( + "get_blackmagic", + "@echo $( ${BLACKMAGIC_ADDR} $)", +) + +dist_env.PhonyTarget( + "get_apiversion", + "@echo $( ${UFBT_API_VERSION} $)", +) diff --git a/scripts/ufbt/project_template/.vscode/launch.json b/scripts/ufbt/project_template/.vscode/launch.json index d9c98dcc13e..697de9a4988 100644 --- a/scripts/ufbt/project_template/.vscode/launch.json +++ b/scripts/ufbt/project_template/.vscode/launch.json @@ -2,19 +2,16 @@ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "inputs": [ - // { - // "id": "BLACKMAGIC", - // "type": "command", - // "command": "shellCommand.execute", - // "args": { - // "useSingleResult": true, - // "env": { - // "PATH": "${workspaceFolder};${env:PATH}" - // }, - // "command": "./fbt get_blackmagic", - // "description": "Get Blackmagic device", - // } - // }, + { + "id": "BLACKMAGIC", + "type": "command", + "command": "shellCommand.execute", + "args": { + "description": "Get Blackmagic device", + "useSingleResult": true, + "command": "ufbt -s get_blackmagic", + } + }, ], "configurations": [ { @@ -57,26 +54,26 @@ ], // "showDevDebugOutput": "raw", }, - // { - // "name": "Attach FW (blackmagic)", - // "cwd": "${workspaceFolder}", - // "executable": "@UFBT_FIRMWARE_ELF@", - // "request": "attach", - // "type": "cortex-debug", - // "servertype": "external", - // "gdbTarget": "${input:BLACKMAGIC}", - // "svdFile": "@UFBT_DEBUG_DIR@/STM32WB55_CM4.svd", - // "rtos": "FreeRTOS", - // "postAttachCommands": [ - // "monitor swdp_scan", - // "attach 1", - // "set confirm off", - // "set mem inaccessible-by-default off", - // "source @UFBT_DEBUG_DIR@/flipperapps.py", - // "fap-set-debug-elf-root @UFBT_DEBUG_ELF_DIR@" - // ] - // // "showDevDebugOutput": "raw", - // }, + { + "name": "Attach FW (blackmagic)", + "cwd": "${workspaceFolder}", + "executable": "@UFBT_FIRMWARE_ELF@", + "request": "attach", + "type": "cortex-debug", + "servertype": "external", + "gdbTarget": "${input:BLACKMAGIC}", + "svdFile": "@UFBT_DEBUG_DIR@/STM32WB55_CM4.svd", + "rtos": "FreeRTOS", + "postAttachCommands": [ + "monitor swdp_scan", + "attach 1", + "set confirm off", + "set mem inaccessible-by-default off", + "source @UFBT_DEBUG_DIR@/flipperapps.py", + "fap-set-debug-elf-root @UFBT_DEBUG_ELF_DIR@" + ] + // "showDevDebugOutput": "raw", + }, { "name": "Attach FW (JLink)", "cwd": "${workspaceFolder}", diff --git a/scripts/ufbt/project_template/.vscode/tasks.json b/scripts/ufbt/project_template/.vscode/tasks.json index 6343bba7bea..4b3f4bda563 100644 --- a/scripts/ufbt/project_template/.vscode/tasks.json +++ b/scripts/ufbt/project_template/.vscode/tasks.json @@ -20,24 +20,30 @@ "type": "shell", "command": "ufbt" }, + { + "label": "Clean", + "group": "build", + "type": "shell", + "command": "ufbt -c" + }, { "label": "Flash FW (ST-Link)", "group": "build", "type": "shell", "command": "ufbt FORCE=1 flash" }, - // { - // "label": "[NOTIMPL] Flash FW (blackmagic)", - // "group": "build", - // "type": "shell", - // "command": "ufbt flash_blackmagic" - // }, - // { - // "label": "[NOTIMPL] Flash FW (JLink)", - // "group": "build", - // "type": "shell", - // "command": "ufbt FORCE=1 jflash" - // }, + { + "label": "Flash FW (blackmagic)", + "group": "build", + "type": "shell", + "command": "ufbt flash_blackmagic" + }, + { + "label": "Flash FW (JLink)", + "group": "build", + "type": "shell", + "command": "ufbt FORCE=1 jflash" + }, { "label": "Flash FW (USB, with resources)", "group": "build", @@ -49,6 +55,12 @@ "group": "build", "type": "shell", "command": "ufbt update" + }, + { + "label": "Update VSCode config for current SDK", + "group": "build", + "type": "shell", + "command": "ufbt vscode_dist" } ] } \ No newline at end of file diff --git a/scripts/ufbt/project_template/app_template/application.fam b/scripts/ufbt/project_template/app_template/application.fam index 31fadb207a8..37a4ce66552 100644 --- a/scripts/ufbt/project_template/app_template/application.fam +++ b/scripts/ufbt/project_template/app_template/application.fam @@ -6,7 +6,7 @@ App( apptype=FlipperAppType.EXTERNAL, entry_point="@FBT_APPID@_app", stack_size=2 * 1024, - fap_category="Misc", + fap_category="Examples", # Optional values # fap_version=(0, 1), # (major, minor) fap_icon="@FBT_APPID@.png", # 10x10 1-bit PNG diff --git a/scripts/ufbt/site_tools/ufbt_state.py b/scripts/ufbt/site_tools/ufbt_state.py index 6ba8c69628b..76c6e9acfad 100644 --- a/scripts/ufbt/site_tools/ufbt_state.py +++ b/scripts/ufbt/site_tools/ufbt_state.py @@ -75,12 +75,6 @@ def generate(env, **kw): if not sdk_state["meta"]["hw_target"].endswith(sdk_data["hardware"]): raise StopError("SDK state file doesn't match hardware target") - if sdk_state["meta"]["version"] != ufbt_state["version"]: - warn( - WarningOnByDefault, - f"Version mismatch: SDK state vs uFBT: {sdk_state['meta']['version']} vs {ufbt_state['version']}", - ) - scripts_dir = sdk_current_sdk_dir_node.Dir(sdk_components["scripts.dir"]) env.SetDefault( # Paths diff --git a/site_scons/extapps.scons b/site_scons/extapps.scons index 798b85ea105..89ee492429f 100644 --- a/site_scons/extapps.scons +++ b/site_scons/extapps.scons @@ -1,5 +1,4 @@ from dataclasses import dataclass, field -from os.path import dirname from SCons.Node import NodeList from SCons.Warnings import warn, WarningOnByDefault @@ -131,6 +130,7 @@ Depends(sdk_source, appenv.ProcessSdkDepends(f"{amalgamated_api}.d")) appenv["SDK_DIR"] = appenv.Dir("${BUILD_DIR}/sdk_headers") sdk_header_tree = appenv.SDKHeaderTreeExtractor(appenv["SDK_DIR"], amalgamated_api) +Depends(sdk_header_tree, appenv["SDK_DEFINITION"]) # AlwaysBuild(sdk_tree) Alias("sdk_tree", sdk_header_tree) extapps.sdk_tree = sdk_header_tree From 7ac7b708840d29daf8f358f629119f61b859aaa0 Mon Sep 17 00:00:00 2001 From: gornekich Date: Mon, 10 Apr 2023 19:51:55 +0400 Subject: [PATCH 514/824] [FL-3241] NFC disable EMV support (#2571) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * nfc: remove read emv from extra actions * nfc: remove read emv Co-authored-by: あく --- applications/main/nfc/scenes/nfc_scene_read.c | 5 -- .../nfc/scenes/nfc_scene_read_card_type.c | 12 --- lib/nfc/nfc_device.h | 1 - lib/nfc/nfc_worker.c | 80 ------------------- lib/nfc/nfc_worker.h | 1 - lib/nfc/nfc_worker_i.h | 1 - 6 files changed, 100 deletions(-) diff --git a/applications/main/nfc/scenes/nfc_scene_read.c b/applications/main/nfc/scenes/nfc_scene_read.c index 4252883b255..938f2da6754 100644 --- a/applications/main/nfc/scenes/nfc_scene_read.c +++ b/applications/main/nfc/scenes/nfc_scene_read.c @@ -85,11 +85,6 @@ bool nfc_scene_read_on_event(void* context, SceneManagerEvent event) { scene_manager_next_scene(nfc->scene_manager, NfcSceneMfDesfireReadSuccess); DOLPHIN_DEED(DolphinDeedNfcReadSuccess); consumed = true; - } else if(event.event == NfcWorkerEventReadBankCard) { - notification_message(nfc->notifications, &sequence_success); - scene_manager_next_scene(nfc->scene_manager, NfcSceneEmvReadSuccess); - DOLPHIN_DEED(DolphinDeedNfcReadSuccess); - consumed = true; } else if(event.event == NfcWorkerEventReadMfClassicDictAttackRequired) { if(mf_classic_dict_check_presence(MfClassicDictTypeSystem)) { scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicDictAttack); diff --git a/applications/main/nfc/scenes/nfc_scene_read_card_type.c b/applications/main/nfc/scenes/nfc_scene_read_card_type.c index 94262aa1e04..8023026c3da 100644 --- a/applications/main/nfc/scenes/nfc_scene_read_card_type.c +++ b/applications/main/nfc/scenes/nfc_scene_read_card_type.c @@ -5,7 +5,6 @@ enum SubmenuIndex { SubmenuIndexReadMifareClassic, SubmenuIndexReadMifareDesfire, SubmenuIndexReadMfUltralight, - SubmenuIndexReadEMV, SubmenuIndexReadNFCA, }; @@ -37,12 +36,6 @@ void nfc_scene_read_card_type_on_enter(void* context) { SubmenuIndexReadMfUltralight, nfc_scene_read_card_type_submenu_callback, nfc); - submenu_add_item( - submenu, - "Read EMV card", - SubmenuIndexReadEMV, - nfc_scene_read_card_type_submenu_callback, - nfc); submenu_add_item( submenu, "Read NFC-A data", @@ -75,11 +68,6 @@ bool nfc_scene_read_card_type_on_event(void* context, SceneManagerEvent event) { scene_manager_next_scene(nfc->scene_manager, NfcSceneRead); consumed = true; } - if(event.event == SubmenuIndexReadEMV) { - nfc->dev->dev_data.read_mode = NfcReadModeEMV; - scene_manager_next_scene(nfc->scene_manager, NfcSceneRead); - consumed = true; - } if(event.event == SubmenuIndexReadNFCA) { nfc->dev->dev_data.read_mode = NfcReadModeNFCA; scene_manager_next_scene(nfc->scene_manager, NfcSceneRead); diff --git a/lib/nfc/nfc_device.h b/lib/nfc/nfc_device.h index 8b2e6e5ba30..df37ec3df53 100644 --- a/lib/nfc/nfc_device.h +++ b/lib/nfc/nfc_device.h @@ -56,7 +56,6 @@ typedef enum { NfcReadModeMfClassic, NfcReadModeMfUltralight, NfcReadModeMfDesfire, - NfcReadModeEMV, NfcReadModeNFCA, } NfcReadMode; diff --git a/lib/nfc/nfc_worker.c b/lib/nfc/nfc_worker.c index c2b89c71aee..28a1f682797 100644 --- a/lib/nfc/nfc_worker.c +++ b/lib/nfc/nfc_worker.c @@ -229,69 +229,6 @@ static bool nfc_worker_read_mf_desfire(NfcWorker* nfc_worker, FuriHalNfcTxRxCont return read_success; } -static bool nfc_worker_read_bank_card(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { - bool read_success = false; - EmvApplication emv_app = {}; - EmvData* result = &nfc_worker->dev_data->emv_data; - - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - reader_analyzer_prepare_tx_rx(nfc_worker->reader_analyzer, tx_rx, false); - reader_analyzer_start(nfc_worker->reader_analyzer, ReaderAnalyzerModeDebugLog); - } - - // Bank cards require strong field to start application. If we find AID, try at least several - // times to start EMV application - uint8_t start_application_attempts = 0; - while(start_application_attempts < 3) { - if(nfc_worker->state != NfcWorkerStateRead) break; - start_application_attempts++; - if(!furi_hal_nfc_detect(&nfc_worker->dev_data->nfc_data, 300)) break; - if(emv_read_bank_card(tx_rx, &emv_app)) { - FURI_LOG_D(TAG, "Bank card number read from %d attempt", start_application_attempts); - break; - } else if(emv_app.aid_len && !emv_app.app_started) { - FURI_LOG_D( - TAG, - "AID found but failed to start EMV app from %d attempt", - start_application_attempts); - furi_hal_nfc_sleep(); - continue; - } else { - FURI_LOG_D(TAG, "Failed to find AID"); - break; - } - } - // Copy data - if(emv_app.aid_len) { - result->aid_len = emv_app.aid_len; - memcpy(result->aid, emv_app.aid, result->aid_len); - read_success = true; - } - if(emv_app.card_number_len) { - result->number_len = emv_app.card_number_len; - memcpy(result->number, emv_app.card_number, result->number_len); - } - if(emv_app.name_found) { - memcpy(result->name, emv_app.name, sizeof(emv_app.name)); - } - if(emv_app.exp_month) { - result->exp_mon = emv_app.exp_month; - result->exp_year = emv_app.exp_year; - } - if(emv_app.country_code) { - result->country_code = emv_app.country_code; - } - if(emv_app.currency_code) { - result->currency_code = emv_app.currency_code; - } - - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - reader_analyzer_stop(nfc_worker->reader_analyzer); - } - - return read_success; -} - static bool nfc_worker_read_nfca(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data; @@ -315,14 +252,6 @@ static bool nfc_worker_read_nfca(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* t nfc_worker->dev_data->protocol = NfcDeviceProtocolUnknown; } card_read = true; - } else if(nfc_data->interface == FuriHalNfcInterfaceIsoDep) { - FURI_LOG_I(TAG, "ISO14443-4 card detected"); - nfc_worker->dev_data->protocol = NfcDeviceProtocolEMV; - if(!nfc_worker_read_bank_card(nfc_worker, tx_rx)) { - FURI_LOG_I(TAG, "Unknown card. Save UID"); - nfc_worker->dev_data->protocol = NfcDeviceProtocolUnknown; - } - card_read = true; } else { nfc_worker->dev_data->protocol = NfcDeviceProtocolUnknown; card_read = true; @@ -358,9 +287,6 @@ void nfc_worker_read(NfcWorker* nfc_worker) { } else if(dev_data->protocol == NfcDeviceProtocolMifareDesfire) { event = NfcWorkerEventReadMfDesfire; break; - } else if(dev_data->protocol == NfcDeviceProtocolEMV) { - event = NfcWorkerEventReadBankCard; - break; } else if(dev_data->protocol == NfcDeviceProtocolUnknown) { event = NfcWorkerEventReadUidNfcA; break; @@ -444,12 +370,6 @@ void nfc_worker_read_type(NfcWorker* nfc_worker) { event = NfcWorkerEventReadMfDesfire; break; } - } else if(read_mode == NfcReadModeEMV) { - nfc_worker->dev_data->protocol = NfcDeviceProtocolEMV; - if(nfc_worker_read_bank_card(nfc_worker, &tx_rx)) { - event = NfcWorkerEventReadBankCard; - break; - } } else if(read_mode == NfcReadModeNFCA) { nfc_worker->dev_data->protocol = NfcDeviceProtocolUnknown; event = NfcWorkerEventReadUidNfcA; diff --git a/lib/nfc/nfc_worker.h b/lib/nfc/nfc_worker.h index ce542828ab1..8e993fc6aa3 100644 --- a/lib/nfc/nfc_worker.h +++ b/lib/nfc/nfc_worker.h @@ -39,7 +39,6 @@ typedef enum { NfcWorkerEventReadMfClassicDone, NfcWorkerEventReadMfClassicLoadKeyCache, NfcWorkerEventReadMfClassicDictAttackRequired, - NfcWorkerEventReadBankCard, // Nfc worker common events NfcWorkerEventSuccess, diff --git a/lib/nfc/nfc_worker_i.h b/lib/nfc/nfc_worker_i.h index 9733426abf2..701ecb90c63 100644 --- a/lib/nfc/nfc_worker_i.h +++ b/lib/nfc/nfc_worker_i.h @@ -6,7 +6,6 @@ #include #include -#include #include #include #include From 33e8bae78bc19821d662edd131a956580ac8bdb2 Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Wed, 12 Apr 2023 10:07:05 +0400 Subject: [PATCH 515/824] Bugfix: ISP Programmer and SubGhz (#2574) * AVR_ISP: fix NULL pointer dereference * SubGhz: double back with a blocked transmission in this region * SubGhz: fix speaker, when a transmission is blocked in this region * SubGhz: fix speaker * SubGhz: return region * AVR Flasher: cleanup code Co-authored-by: Aleksandr Kutuzov --- applications/external/avr_isp_programmer/helpers/avr_isp.c | 7 ++++++- .../external/avr_isp_programmer/lib/driver/avr_isp_prog.c | 7 ++++++- applications/main/subghz/scenes/subghz_scene_read_raw.c | 6 +++++- applications/main/subghz/scenes/subghz_scene_transmitter.c | 4 +--- applications/main/subghz/subghz_i.c | 7 +++++-- 5 files changed, 23 insertions(+), 8 deletions(-) diff --git a/applications/external/avr_isp_programmer/helpers/avr_isp.c b/applications/external/avr_isp_programmer/helpers/avr_isp.c index 51b4f8846a1..283c17bfd48 100644 --- a/applications/external/avr_isp_programmer/helpers/avr_isp.c +++ b/applications/external/avr_isp_programmer/helpers/avr_isp.c @@ -152,7 +152,12 @@ bool avr_isp_auto_set_spi_speed_start_pmode(AvrIsp* instance) { } } } - if(instance->spi) avr_isp_spi_sw_free(instance->spi); + + if(instance->spi) { + avr_isp_spi_sw_free(instance->spi); + instance->spi = NULL; + } + return false; } diff --git a/applications/external/avr_isp_programmer/lib/driver/avr_isp_prog.c b/applications/external/avr_isp_programmer/lib/driver/avr_isp_prog.c index b3c81f3b13a..bbb6d47393f 100644 --- a/applications/external/avr_isp_programmer/lib/driver/avr_isp_prog.c +++ b/applications/external/avr_isp_programmer/lib/driver/avr_isp_prog.c @@ -317,7 +317,12 @@ static bool avr_isp_prog_auto_set_spi_speed_start_pmode(AvrIspProg* instance) { } } } - if(instance->spi) avr_isp_spi_sw_free(instance->spi); + + if(instance->spi) { + avr_isp_spi_sw_free(instance->spi); + instance->spi = NULL; + } + return false; } diff --git a/applications/main/subghz/scenes/subghz_scene_read_raw.c b/applications/main/subghz/scenes/subghz_scene_read_raw.c index 96acc90eeee..09440b32bb7 100644 --- a/applications/main/subghz/scenes/subghz_scene_read_raw.c +++ b/applications/main/subghz/scenes/subghz_scene_read_raw.c @@ -230,7 +230,11 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { (subghz->txrx->txrx_state == SubGhzTxRxStateSleep)) { if(!subghz_tx_start(subghz, subghz->txrx->fff_data)) { subghz->txrx->rx_key_state = SubGhzRxKeyStateBack; - scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowOnlyRx); + subghz_read_raw_set_status( + subghz->subghz_read_raw, + SubGhzReadRAWStatusIDLE, + "", + subghz->txrx->raw_threshold_rssi); } else { if(scene_manager_has_previous_scene( subghz->scene_manager, SubGhzSceneSaved) || diff --git a/applications/main/subghz/scenes/subghz_scene_transmitter.c b/applications/main/subghz/scenes/subghz_scene_transmitter.c index c8663cc8e56..712e50071c1 100644 --- a/applications/main/subghz/scenes/subghz_scene_transmitter.c +++ b/applications/main/subghz/scenes/subghz_scene_transmitter.c @@ -70,9 +70,7 @@ bool subghz_scene_transmitter_on_event(void* context, SceneManagerEvent event) { } if((subghz->txrx->txrx_state == SubGhzTxRxStateIDLE) || (subghz->txrx->txrx_state == SubGhzTxRxStateSleep)) { - if(!subghz_tx_start(subghz, subghz->txrx->fff_data)) { - scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowOnlyRx); - } else { + if(subghz_tx_start(subghz, subghz->txrx->fff_data)) { subghz->state_notifications = SubGhzNotificationStateTx; subghz_scene_transmitter_update_data_show(subghz); DOLPHIN_DEED(DolphinDeedSubGhzSend); diff --git a/applications/main/subghz/subghz_i.c b/applications/main/subghz/subghz_i.c index 94713ddba6e..18d87c76b5a 100644 --- a/applications/main/subghz/subghz_i.c +++ b/applications/main/subghz/subghz_i.c @@ -105,9 +105,11 @@ static bool subghz_tx(SubGhz* subghz, uint32_t frequency) { furi_hal_subghz_set_frequency_and_path(frequency); furi_hal_gpio_write(&gpio_cc1101_g0, false); furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); - subghz_speaker_on(subghz); bool ret = furi_hal_subghz_tx(); - subghz->txrx->txrx_state = SubGhzTxRxStateTx; + if(ret) { + subghz_speaker_on(subghz); + subghz->txrx->txrx_state = SubGhzTxRxStateTx; + } return ret; } @@ -115,6 +117,7 @@ void subghz_idle(SubGhz* subghz) { furi_assert(subghz); furi_assert(subghz->txrx->txrx_state != SubGhzTxRxStateSleep); furi_hal_subghz_idle(); + subghz_speaker_off(subghz); subghz->txrx->txrx_state = SubGhzTxRxStateIDLE; } From 5d7bdca835e83c6925d7802774ac1ffe03766368 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Wed, 12 Apr 2023 19:45:13 +0800 Subject: [PATCH 516/824] FuriHal: pwr pulls for some pins (#2579) --- firmware/targets/f18/furi_hal/furi_hal_resources.c | 5 +++-- firmware/targets/f7/furi_hal/furi_hal_resources.c | 9 +++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/firmware/targets/f18/furi_hal/furi_hal_resources.c b/firmware/targets/f18/furi_hal/furi_hal_resources.c index 19bc9f99840..4a7015d139a 100644 --- a/firmware/targets/f18/furi_hal/furi_hal_resources.c +++ b/firmware/targets/f18/furi_hal/furi_hal_resources.c @@ -166,8 +166,9 @@ void furi_hal_resources_init() { furi_hal_resources_init_input_pins(GpioModeInterruptRiseFall); // Explicit pulls pins - furi_hal_gpio_init(&gpio_speaker, GpioModeAnalog, GpioPullDown, GpioSpeedLow); - furi_hal_gpio_init(&gpio_vibro, GpioModeAnalog, GpioPullDown, GpioSpeedLow); + LL_PWR_EnablePUPDCfg(); + LL_PWR_EnableGPIOPullDown(LL_PWR_GPIO_B, LL_PWR_GPIO_BIT_8); // gpio_speaker + LL_PWR_EnableGPIOPullDown(LL_PWR_GPIO_A, LL_PWR_GPIO_BIT_8); // gpio_vibro // Display pins furi_hal_gpio_init(&gpio_display_rst_n, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); diff --git a/firmware/targets/f7/furi_hal/furi_hal_resources.c b/firmware/targets/f7/furi_hal/furi_hal_resources.c index df9530079c6..912912b4a4a 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_resources.c +++ b/firmware/targets/f7/furi_hal/furi_hal_resources.c @@ -153,10 +153,11 @@ void furi_hal_resources_init() { // Button pins furi_hal_resources_init_input_pins(GpioModeInterruptRiseFall); - // Explicit pulls pins - furi_hal_gpio_init(&gpio_infrared_tx, GpioModeAnalog, GpioPullDown, GpioSpeedLow); - furi_hal_gpio_init(&gpio_speaker, GpioModeAnalog, GpioPullDown, GpioSpeedLow); - furi_hal_gpio_init(&gpio_vibro, GpioModeAnalog, GpioPullDown, GpioSpeedLow); + // Explicit, surviving reset, pulls + LL_PWR_EnablePUPDCfg(); + LL_PWR_EnableGPIOPullDown(LL_PWR_GPIO_B, LL_PWR_GPIO_BIT_9); // gpio_infrared_tx + LL_PWR_EnableGPIOPullDown(LL_PWR_GPIO_B, LL_PWR_GPIO_BIT_8); // gpio_speaker + LL_PWR_EnableGPIOPullDown(LL_PWR_GPIO_A, LL_PWR_GPIO_BIT_8); // gpio_vibro // Display pins furi_hal_gpio_init(&gpio_display_rst_n, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); From 37fb330b362dba3b01b7c6ca6a200564211dc1b7 Mon Sep 17 00:00:00 2001 From: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> Date: Thu, 13 Apr 2023 17:47:38 +0300 Subject: [PATCH 517/824] [FL-3226] Deep Sleep Idle (#2569) * Improve RNG error handling * Sync RTC shadow registers on Stop mode exit * Implement working STOP2 mode * Fix formatting * FuriHal: disable SWD pins if debug is disabled * Power: cleanup battery info view, handle zero current report from gauge * Fbt: add command line argument for extra global defines * FuriHal: cleanup debug defines in power and os, drop deep_insomnia counter. * Add a setting to disable deep sleep * Clean up furi_hal_power * FuriHal,FapLoader,Debug: implement debug in stop mode, workaround resume in stop * FuriHal: document OS and power subsystems debugging * Furi: enable debug interface on crash --------- Co-authored-by: Aleksandr Kutuzov --- applications/main/fap_loader/fap_loader_app.c | 5 +- .../power_settings_app/views/battery_info.c | 51 +++++++------- .../settings/system/system_settings.c | 21 ++++++ debug/flipperapps.py | 3 +- documentation/FuriHalDebuging.md | 26 +++++++ documentation/fbt.md | 1 + firmware/targets/f18/api_symbols.csv | 13 ++-- .../targets/f18/furi_hal/furi_hal_resources.c | 3 + .../targets/f18/furi_hal/furi_hal_resources.h | 3 + firmware/targets/f7/api_symbols.csv | 13 ++-- firmware/targets/f7/ble_glue/ble_glue.c | 6 -- firmware/targets/f7/furi_hal/furi_hal_clock.c | 9 ++- firmware/targets/f7/furi_hal/furi_hal_debug.c | 21 ++++++ firmware/targets/f7/furi_hal/furi_hal_os.c | 30 ++++++-- firmware/targets/f7/furi_hal/furi_hal_power.c | 69 +++++++++---------- .../targets/f7/furi_hal/furi_hal_random.c | 42 ++++++----- .../targets/f7/furi_hal/furi_hal_resources.c | 3 + .../targets/f7/furi_hal/furi_hal_resources.h | 3 + firmware/targets/f7/furi_hal/furi_hal_rtc.c | 15 ++-- .../targets/furi_hal_include/furi_hal_debug.h | 3 + .../targets/furi_hal_include/furi_hal_power.h | 6 -- .../targets/furi_hal_include/furi_hal_rtc.h | 4 ++ furi/core/check.c | 3 + furi/core/core_defines.h | 4 ++ site_scons/cc.scons | 1 + site_scons/commandline.scons | 8 +++ 26 files changed, 246 insertions(+), 120 deletions(-) create mode 100644 documentation/FuriHalDebuging.md diff --git a/applications/main/fap_loader/fap_loader_app.c b/applications/main/fap_loader/fap_loader_app.c index f5c7af02488..7af5244ae49 100644 --- a/applications/main/fap_loader/fap_loader_app.c +++ b/applications/main/fap_loader/fap_loader_app.c @@ -1,6 +1,7 @@ #include "fap_loader_app.h" #include +#include #include #include @@ -23,8 +24,6 @@ struct FapLoader { Loading* loading; }; -volatile bool fap_loader_debug_active = false; - bool fap_loader_load_name_and_icon( FuriString* path, Storage* storage, @@ -111,7 +110,7 @@ static bool fap_loader_run_selected_app(FapLoader* loader) { FuriThread* thread = flipper_application_spawn(loader->app, NULL); /* This flag is set by the debugger - to break on app start */ - if(fap_loader_debug_active) { + if(furi_hal_debug_is_gdb_session_active()) { FURI_LOG_W(TAG, "Triggering BP for debugger"); /* After hitting this, you can set breakpoints in your .fap's code * Note that you have to toggle breakpoints that were set before */ diff --git a/applications/settings/power_settings_app/views/battery_info.c b/applications/settings/power_settings_app/views/battery_info.c index 7394fd3c5a6..0956cae4f5a 100644 --- a/applications/settings/power_settings_app/views/battery_info.c +++ b/applications/settings/power_settings_app/views/battery_info.c @@ -4,8 +4,8 @@ #include #include -#define LOW_CHARGE_THRESHOLD 10 -#define HIGH_DRAIN_CURRENT_THRESHOLD 100 +#define LOW_CHARGE_THRESHOLD (10) +#define HIGH_DRAIN_CURRENT_THRESHOLD (-100) struct BatteryInfo { View* view; @@ -25,14 +25,13 @@ static void draw_battery(Canvas* canvas, BatteryInfoModel* data, int x, int y) { char header[20] = {}; char value[20] = {}; - int32_t drain_current = data->gauge_current * (-1000); - uint32_t charge_current = data->gauge_current * 1000; + int32_t current = 1000.0f * data->gauge_current; // Draw battery canvas_draw_icon(canvas, x, y, &I_BatteryBody_52x28); - if(charge_current > 0) { + if(current > 0) { canvas_draw_icon(canvas, x + 16, y + 7, &I_FaceCharging_29x14); - } else if(drain_current > HIGH_DRAIN_CURRENT_THRESHOLD) { + } else if(current < HIGH_DRAIN_CURRENT_THRESHOLD) { canvas_draw_icon(canvas, x + 16, y + 7, &I_FaceConfused_29x14); } else if(data->charge < LOW_CHARGE_THRESHOLD) { canvas_draw_icon(canvas, x + 16, y + 7, &I_FaceNopower_29x14); @@ -44,7 +43,7 @@ static void draw_battery(Canvas* canvas, BatteryInfoModel* data, int x, int y) { elements_bubble(canvas, 53, 0, 71, 39); // Set text - if(charge_current > 0) { + if(current > 0) { snprintf(emote, sizeof(emote), "%s", "Yummy!"); snprintf(header, sizeof(header), "%s", "Charging at"); snprintf( @@ -53,34 +52,36 @@ static void draw_battery(Canvas* canvas, BatteryInfoModel* data, int x, int y) { "%lu.%luV %lumA", (uint32_t)(data->vbus_voltage), (uint32_t)(data->vbus_voltage * 10) % 10, - charge_current); - } else if(drain_current > 0) { + current); + } else if(current < 0) { snprintf( emote, sizeof(emote), "%s", - drain_current > HIGH_DRAIN_CURRENT_THRESHOLD ? "Oh no!" : "Om-nom-nom!"); + current < HIGH_DRAIN_CURRENT_THRESHOLD ? "Oh no!" : "Om-nom-nom!"); snprintf(header, sizeof(header), "%s", "Consumption is"); snprintf( value, sizeof(value), "%ld %s", - drain_current, - drain_current > HIGH_DRAIN_CURRENT_THRESHOLD ? "mA!" : "mA"); - } else if(drain_current != 0) { - snprintf(header, 20, "..."); - } else if(data->charge_voltage_limit < 4.2) { - // Non-default battery charging limit, mention it - snprintf(emote, sizeof(emote), "Charged!"); - snprintf(header, sizeof(header), "Limited to"); - snprintf( - value, - sizeof(value), - "%lu.%luV", - (uint32_t)(data->charge_voltage_limit), - (uint32_t)(data->charge_voltage_limit * 10) % 10); + ABS(current), + current < HIGH_DRAIN_CURRENT_THRESHOLD ? "mA!" : "mA"); + } else if(data->vbus_voltage > 0) { + if(data->charge_voltage_limit < 4.2) { + // Non-default battery charging limit, mention it + snprintf(emote, sizeof(emote), "Charged!"); + snprintf(header, sizeof(header), "Limited to"); + snprintf( + value, + sizeof(value), + "%lu.%luV", + (uint32_t)(data->charge_voltage_limit), + (uint32_t)(data->charge_voltage_limit * 10) % 10); + } else { + snprintf(header, sizeof(header), "Charged!"); + } } else { - snprintf(header, sizeof(header), "Charged!"); + snprintf(header, sizeof(header), "Napping..."); } canvas_draw_str_aligned(canvas, 92, y + 3, AlignCenter, AlignCenter, emote); diff --git a/applications/settings/system/system_settings.c b/applications/settings/system/system_settings.c index cb74d7a8353..597710a53f0 100644 --- a/applications/settings/system/system_settings.c +++ b/applications/settings/system/system_settings.c @@ -141,6 +141,21 @@ static void hand_orient_changed(VariableItem* item) { loader_update_menu(); } +const char* const sleep_method[] = { + "Default", + "Legacy", +}; + +static void sleep_method_changed(VariableItem* item) { + uint8_t index = variable_item_get_current_value_index(item); + variable_item_set_current_value_text(item, sleep_method[index]); + if(index) { + furi_hal_rtc_set_flag(FuriHalRtcFlagLegacySleep); + } else { + furi_hal_rtc_reset_flag(FuriHalRtcFlagLegacySleep); + } +} + static uint32_t system_settings_exit(void* context) { UNUSED(context); return VIEW_NONE; @@ -218,6 +233,12 @@ SystemSettings* system_settings_alloc() { variable_item_set_current_value_index(item, value_index); variable_item_set_current_value_text(item, heap_trace_mode_text[value_index]); + item = variable_item_list_add( + app->var_item_list, "Sleep Method", COUNT_OF(sleep_method), sleep_method_changed, app); + value_index = furi_hal_rtc_is_flag_set(FuriHalRtcFlagLegacySleep) ? 1 : 0; + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, sleep_method[value_index]); + view_set_previous_callback( variable_item_list_get_view(app->var_item_list), system_settings_exit); view_dispatcher_add_view( diff --git a/debug/flipperapps.py b/debug/flipperapps.py index 1dc5ebd04bb..90582c1e44a 100644 --- a/debug/flipperapps.py +++ b/debug/flipperapps.py @@ -135,6 +135,7 @@ def __init__(self): self.app_list_ptr = None self.app_list_entry_type = None self._current_apps: list[AppState] = [] + self.set_debug_mode(True) def _walk_app_list(self, list_head): while list_head: @@ -195,7 +196,7 @@ def handle_exit(self, event) -> None: self.set_debug_mode(False) def set_debug_mode(self, mode: bool) -> None: - gdb.execute(f"set variable fap_loader_debug_active = {int(mode)}") + gdb.execute(f"set variable furi_hal_debug_gdb_session_active = {int(mode)}") # Init additional 'fap-set-debug-elf-root' command and set up hooks diff --git a/documentation/FuriHalDebuging.md b/documentation/FuriHalDebuging.md new file mode 100644 index 00000000000..8ff770163b2 --- /dev/null +++ b/documentation/FuriHalDebuging.md @@ -0,0 +1,26 @@ +# Furi HAL Debugging + +Some Furi subsystem got additional debugging features that can be enabled by adding additional defines to firmware compilation. +Usually they are used for low level tracing and profiling or signal redirection/duplication. + + +## FuriHalOs + +`--extra-define=FURI_HAL_OS_DEBUG` enables tick, tick suppression, idle and time flow. + +There are 3 signals that will be exposed to external GPIO pins: + +- `AWAKE` - `PA7` - High when system is busy with computations, low when sleeping. Can be used to track transitions to sleep mode. +- `TICK` - `PA6` - Flipped on system tick, only flips when no tick suppression in progress. Can be used to track tick skew and abnormal task scheduling. +- `SECOND` - `PA4` - Flipped each second. Can be used for tracing RT issue: time flow disturbance means system doesn't conforms Hard RT. + + + +## FuriHalPower + +`--extra-define=FURI_HAL_POWER_DEBUG` enables power subsystem mode transitions tracing. + +There are 2 signals that will be exposed to external GPIO pins: + +- `WFI` - `PB2` - Light sleep (wait for interrupt) used. Basically this is lightest and most non-breaking things power save mode. All function and debug should work correctly in this mode. +- `STOP` - `PC3` - STOP mode used. Platform deep sleep mode. Extremely fragile mode where most of the silicon is disabled or in unusable state. Debugging MCU in this mode is nearly impossible. \ No newline at end of file diff --git a/documentation/fbt.md b/documentation/fbt.md index 14d63e9cea8..23b2e2b55f1 100644 --- a/documentation/fbt.md +++ b/documentation/fbt.md @@ -105,6 +105,7 @@ To run cleanup (think of `make clean`) for specified targets, add the `-c` optio - `--options optionfile.py` (default value `fbt_options.py`) - load a file with multiple configuration values - `--extra-int-apps=app1,app2,appN` - force listed apps to be built as internal with the `firmware` target - `--extra-ext-apps=app1,app2,appN` - force listed apps to be built as external with the `firmware_extapps` target +- `--extra-define=A --extra-define=B=C ` - extra global defines that will be passed to the C/C++ compiler, can be specified multiple times - `--proxy-env=VAR1,VAR2` - additional environment variables to expose to subprocesses spawned by `fbt`. By default, `fbt` sanitizes the execution environment and doesn't forward all inherited environment variables. You can find the list of variables that are always forwarded in the `environ.scons` file. ## Configuration diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index 5d5b7bbcf2c..493f5963474 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,21.0,, +Version,+,22.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -901,6 +901,7 @@ Function,+,furi_hal_crypto_verify_enclave,_Bool,"uint8_t*, uint8_t*" Function,+,furi_hal_crypto_verify_key,_Bool,uint8_t Function,+,furi_hal_debug_disable,void, Function,+,furi_hal_debug_enable,void, +Function,+,furi_hal_debug_is_gdb_session_active,_Bool, Function,-,furi_hal_deinit_early,void, Function,-,furi_hal_flash_erase,void,uint8_t Function,-,furi_hal_flash_get_base,size_t, @@ -983,7 +984,6 @@ Function,-,furi_hal_os_init,void, Function,+,furi_hal_os_tick,void, Function,+,furi_hal_power_check_otg_status,void, Function,+,furi_hal_power_debug_get,void,"PropertyValueCallback, void*" -Function,+,furi_hal_power_deep_sleep_available,_Bool, Function,+,furi_hal_power_disable_external_3_3v,void, Function,+,furi_hal_power_disable_otg,void, Function,+,furi_hal_power_enable_external_3_3v,void, @@ -1059,6 +1059,7 @@ Function,+,furi_hal_rtc_set_locale_units,void,FuriHalRtcLocaleUnits Function,+,furi_hal_rtc_set_log_level,void,uint8_t Function,+,furi_hal_rtc_set_pin_fails,void,uint32_t Function,+,furi_hal_rtc_set_register,void,"FuriHalRtcRegister, uint32_t" +Function,+,furi_hal_rtc_sync_shadow,void, Function,+,furi_hal_rtc_validate_datetime,_Bool,FuriHalRtcDateTime* Function,+,furi_hal_speaker_acquire,_Bool,uint32_t Function,-,furi_hal_speaker_deinit,void, @@ -2149,6 +2150,8 @@ Variable,+,gpio_ext_pd0,const GpioPin, Variable,+,gpio_ext_pe4,const GpioPin, Variable,+,gpio_i2c_power_scl,const GpioPin, Variable,+,gpio_i2c_power_sda,const GpioPin, +Variable,+,gpio_ibutton,const GpioPin, +Variable,+,gpio_periph_power,const GpioPin, Variable,+,gpio_pins,const GpioPinRecord[], Variable,+,gpio_pins_count,const size_t, Variable,+,gpio_sdcard_cd,const GpioPin, @@ -2157,11 +2160,13 @@ Variable,+,gpio_speaker,const GpioPin, Variable,+,gpio_spi_d_miso,const GpioPin, Variable,+,gpio_spi_d_mosi,const GpioPin, Variable,+,gpio_spi_d_sck,const GpioPin, +Variable,+,gpio_swclk,const GpioPin, +Variable,+,gpio_swdio,const GpioPin, Variable,+,gpio_usart_rx,const GpioPin, Variable,+,gpio_usart_tx,const GpioPin, Variable,+,gpio_usb_dm,const GpioPin, Variable,+,gpio_usb_dp,const GpioPin, -Variable,+,gpio_ibutton,const GpioPin, +Variable,+,gpio_vibro,const GpioPin, Variable,+,input_pins,const InputPin[], Variable,+,input_pins_count,const size_t, Variable,+,message_blink_set_color_blue,const NotificationMessage, @@ -2309,7 +2314,6 @@ Variable,+,message_red_255,const NotificationMessage, Variable,+,message_sound_off,const NotificationMessage, Variable,+,message_vibro_off,const NotificationMessage, Variable,+,message_vibro_on,const NotificationMessage, -Variable,+,gpio_periph_power,const GpioPin, Variable,+,sequence_audiovisual_alert,const NotificationSequence, Variable,+,sequence_blink_blue_10,const NotificationSequence, Variable,+,sequence_blink_blue_100,const NotificationSequence, @@ -2364,4 +2368,3 @@ Variable,+,usb_cdc_single,FuriHalUsbInterface, Variable,+,usb_hid,FuriHalUsbInterface, Variable,+,usb_hid_u2f,FuriHalUsbInterface, Variable,+,usbd_devfs,const usbd_driver, -Variable,+,gpio_vibro,const GpioPin, diff --git a/firmware/targets/f18/furi_hal/furi_hal_resources.c b/firmware/targets/f18/furi_hal/furi_hal_resources.c index 4a7015d139a..6db483dbcbb 100644 --- a/firmware/targets/f18/furi_hal/furi_hal_resources.c +++ b/firmware/targets/f18/furi_hal/furi_hal_resources.c @@ -6,6 +6,9 @@ #define TAG "FuriHalResources" +const GpioPin gpio_swdio = {.port = GPIOA, .pin = LL_GPIO_PIN_13}; +const GpioPin gpio_swclk = {.port = GPIOA, .pin = LL_GPIO_PIN_14}; + const GpioPin gpio_vibro = {.port = GPIOA, .pin = LL_GPIO_PIN_8}; const GpioPin gpio_ibutton = {.port = GPIOB, .pin = LL_GPIO_PIN_14}; diff --git a/firmware/targets/f18/furi_hal/furi_hal_resources.h b/firmware/targets/f18/furi_hal/furi_hal_resources.h index 3c4708d15f9..7d2caab3682 100644 --- a/firmware/targets/f18/furi_hal/furi_hal_resources.h +++ b/firmware/targets/f18/furi_hal/furi_hal_resources.h @@ -50,6 +50,9 @@ extern const size_t input_pins_count; extern const GpioPinRecord gpio_pins[]; extern const size_t gpio_pins_count; +extern const GpioPin gpio_swdio; +extern const GpioPin gpio_swclk; + extern const GpioPin gpio_vibro; extern const GpioPin gpio_ibutton; diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 89984352a2d..3ef57813b4a 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,21.0,, +Version,+,22.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -1082,6 +1082,7 @@ Function,+,furi_hal_crypto_verify_enclave,_Bool,"uint8_t*, uint8_t*" Function,+,furi_hal_crypto_verify_key,_Bool,uint8_t Function,+,furi_hal_debug_disable,void, Function,+,furi_hal_debug_enable,void, +Function,+,furi_hal_debug_is_gdb_session_active,_Bool, Function,-,furi_hal_deinit_early,void, Function,-,furi_hal_flash_erase,void,uint8_t Function,-,furi_hal_flash_get_base,size_t, @@ -1212,7 +1213,6 @@ Function,-,furi_hal_os_init,void, Function,+,furi_hal_os_tick,void, Function,+,furi_hal_power_check_otg_status,void, Function,+,furi_hal_power_debug_get,void,"PropertyValueCallback, void*" -Function,+,furi_hal_power_deep_sleep_available,_Bool, Function,+,furi_hal_power_disable_external_3_3v,void, Function,+,furi_hal_power_disable_otg,void, Function,+,furi_hal_power_enable_external_3_3v,void, @@ -1313,6 +1313,7 @@ Function,+,furi_hal_rtc_set_locale_units,void,FuriHalRtcLocaleUnits Function,+,furi_hal_rtc_set_log_level,void,uint8_t Function,+,furi_hal_rtc_set_pin_fails,void,uint32_t Function,+,furi_hal_rtc_set_register,void,"FuriHalRtcRegister, uint32_t" +Function,+,furi_hal_rtc_sync_shadow,void, Function,+,furi_hal_rtc_validate_datetime,_Bool,FuriHalRtcDateTime* Function,+,furi_hal_speaker_acquire,_Bool,uint32_t Function,-,furi_hal_speaker_deinit,void, @@ -3076,10 +3077,12 @@ Variable,+,gpio_ext_pc1,const GpioPin, Variable,+,gpio_ext_pc3,const GpioPin, Variable,+,gpio_i2c_power_scl,const GpioPin, Variable,+,gpio_i2c_power_sda,const GpioPin, +Variable,+,gpio_ibutton,const GpioPin, Variable,+,gpio_infrared_rx,const GpioPin, Variable,+,gpio_infrared_tx,const GpioPin, Variable,+,gpio_nfc_cs,const GpioPin, Variable,+,gpio_nfc_irq_rfid_pull,const GpioPin, +Variable,+,gpio_periph_power,const GpioPin, Variable,+,gpio_pins,const GpioPinRecord[], Variable,+,gpio_pins_count,const size_t, Variable,+,gpio_rf_sw_0,const GpioPin, @@ -3096,11 +3099,13 @@ Variable,+,gpio_spi_r_miso,const GpioPin, Variable,+,gpio_spi_r_mosi,const GpioPin, Variable,+,gpio_spi_r_sck,const GpioPin, Variable,+,gpio_subghz_cs,const GpioPin, +Variable,+,gpio_swclk,const GpioPin, +Variable,+,gpio_swdio,const GpioPin, Variable,+,gpio_usart_rx,const GpioPin, Variable,+,gpio_usart_tx,const GpioPin, Variable,+,gpio_usb_dm,const GpioPin, Variable,+,gpio_usb_dp,const GpioPin, -Variable,+,gpio_ibutton,const GpioPin, +Variable,+,gpio_vibro,const GpioPin, Variable,+,input_pins,const InputPin[], Variable,+,input_pins_count,const size_t, Variable,+,lfrfid_protocols,const ProtocolBase*[], @@ -3249,7 +3254,6 @@ Variable,+,message_red_255,const NotificationMessage, Variable,+,message_sound_off,const NotificationMessage, Variable,+,message_vibro_off,const NotificationMessage, Variable,+,message_vibro_on,const NotificationMessage, -Variable,+,gpio_periph_power,const GpioPin, Variable,+,sequence_audiovisual_alert,const NotificationSequence, Variable,+,sequence_blink_blue_10,const NotificationSequence, Variable,+,sequence_blink_blue_100,const NotificationSequence, @@ -3307,4 +3311,3 @@ Variable,+,usb_cdc_single,FuriHalUsbInterface, Variable,+,usb_hid,FuriHalUsbInterface, Variable,+,usb_hid_u2f,FuriHalUsbInterface, Variable,+,usbd_devfs,const usbd_driver, -Variable,+,gpio_vibro,const GpioPin, diff --git a/firmware/targets/f7/ble_glue/ble_glue.c b/firmware/targets/f7/ble_glue/ble_glue.c index 83562c73eef..a2f2f1a94b0 100644 --- a/firmware/targets/f7/ble_glue/ble_glue.c +++ b/firmware/targets/f7/ble_glue/ble_glue.c @@ -58,12 +58,6 @@ void ble_glue_init() { ble_glue = malloc(sizeof(BleGlue)); ble_glue->status = BleGlueStatusStartup; - // Configure the system Power Mode - // Select HSI as system clock source after Wake Up from Stop mode - LL_RCC_SetClkAfterWakeFromStop(LL_RCC_STOP_WAKEUPCLOCK_HSI); - /* Initialize the CPU2 reset value before starting CPU2 with C2BOOT */ - LL_C2_PWR_SetPowerMode(LL_PWR_MODE_SHUTDOWN); - #ifdef BLE_GLUE_DEBUG APPD_Init(); #endif diff --git a/firmware/targets/f7/furi_hal/furi_hal_clock.c b/firmware/targets/f7/furi_hal/furi_hal_clock.c index cf19451ec7b..d85524ce44e 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_clock.c +++ b/firmware/targets/f7/furi_hal/furi_hal_clock.c @@ -63,6 +63,10 @@ void furi_hal_clock_init() { LL_RCC_HSI_Enable(); while(!HS_CLOCK_IS_READY()) ; + /* Select HSI as system clock source after Wake Up from Stop mode + * Must be set before enabling CSS */ + LL_RCC_SetClkAfterWakeFromStop(LL_RCC_STOP_WAKEUPCLOCK_HSI); + LL_RCC_HSE_EnableCSS(); /* LSE and LSI1 configuration and activation */ @@ -215,11 +219,14 @@ void furi_hal_clock_switch_to_hsi() { void furi_hal_clock_switch_to_pll() { LL_RCC_HSE_Enable(); LL_RCC_PLL_Enable(); + LL_RCC_PLLSAI1_Enable(); while(!LL_RCC_HSE_IsReady()) ; while(!LL_RCC_PLL_IsReady()) ; + while(!LL_RCC_PLLSAI1_IsReady()) + ; LL_FLASH_SetLatency(LL_FLASH_LATENCY_3); @@ -296,4 +303,4 @@ void furi_hal_clock_mco_disable() { LL_RCC_MSI_Disable(); while(LL_RCC_MSI_IsReady() != 0) ; -} \ No newline at end of file +} diff --git a/firmware/targets/f7/furi_hal/furi_hal_debug.c b/firmware/targets/f7/furi_hal/furi_hal_debug.c index 3b5dfe622e0..3dc03ea69d7 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_debug.c +++ b/firmware/targets/f7/furi_hal/furi_hal_debug.c @@ -3,12 +3,26 @@ #include #include +#include +#include + +volatile bool furi_hal_debug_gdb_session_active = false; + void furi_hal_debug_enable() { // Low power mode debug LL_DBGMCU_EnableDBGSleepMode(); LL_DBGMCU_EnableDBGStopMode(); LL_DBGMCU_EnableDBGStandbyMode(); LL_EXTI_EnableIT_32_63(LL_EXTI_LINE_48); + // SWD GPIO + furi_hal_gpio_init_ex( + &gpio_swdio, + GpioModeAltFunctionPushPull, + GpioPullUp, + GpioSpeedVeryHigh, + GpioAltFn0JTMS_SWDIO); + furi_hal_gpio_init_ex( + &gpio_swclk, GpioModeAltFunctionPushPull, GpioPullDown, GpioSpeedLow, GpioAltFn0JTCK_SWCLK); } void furi_hal_debug_disable() { @@ -17,4 +31,11 @@ void furi_hal_debug_disable() { LL_DBGMCU_DisableDBGStopMode(); LL_DBGMCU_DisableDBGStandbyMode(); LL_EXTI_DisableIT_32_63(LL_EXTI_LINE_48); + // SWD GPIO + furi_hal_gpio_init_simple(&gpio_swdio, GpioModeAnalog); + furi_hal_gpio_init_simple(&gpio_swclk, GpioModeAnalog); } + +bool furi_hal_debug_is_gdb_session_active() { + return furi_hal_debug_gdb_session_active; +} \ No newline at end of file diff --git a/firmware/targets/f7/furi_hal/furi_hal_os.c b/firmware/targets/f7/furi_hal/furi_hal_os.c index ee9743e62c6..3fc1fbea848 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_os.c +++ b/firmware/targets/f7/furi_hal/furi_hal_os.c @@ -28,11 +28,24 @@ // Arbitrary (but small) number for better tick consistency #define FURI_HAL_OS_EXTRA_CNT 3 +#ifndef FURI_HAL_OS_DEBUG_AWAKE_GPIO +#define FURI_HAL_OS_DEBUG_AWAKE_GPIO (&gpio_ext_pa7) +#endif + +#ifndef FURI_HAL_OS_DEBUG_TICK_GPIO +#define FURI_HAL_OS_DEBUG_TICK_GPIO (&gpio_ext_pa6) +#endif + +#ifndef FURI_HAL_OS_DEBUG_SECOND_GPIO +#define FURI_HAL_OS_DEBUG_SECOND_GPIO (&gpio_ext_pa4) +#endif + #ifdef FURI_HAL_OS_DEBUG #include void furi_hal_os_timer_callback() { - furi_hal_gpio_write(&gpio_ext_pa4, !furi_hal_gpio_read(&gpio_ext_pa4)); + furi_hal_gpio_write( + FURI_HAL_OS_DEBUG_SECOND_GPIO, !furi_hal_gpio_read(FURI_HAL_OS_DEBUG_SECOND_GPIO)); } #endif @@ -44,9 +57,11 @@ void furi_hal_os_init() { furi_hal_idle_timer_init(); #ifdef FURI_HAL_OS_DEBUG - furi_hal_gpio_init_simple(&gpio_ext_pa7, GpioModeOutputPushPull); - furi_hal_gpio_init_simple(&gpio_ext_pa6, GpioModeOutputPushPull); - furi_hal_gpio_init_simple(&gpio_ext_pa4, GpioModeOutputPushPull); + furi_hal_gpio_init_simple(FURI_HAL_OS_DEBUG_AWAKE_GPIO, GpioModeOutputPushPull); + furi_hal_gpio_init_simple(FURI_HAL_OS_DEBUG_TICK_GPIO, GpioModeOutputPushPull); + furi_hal_gpio_init_simple(FURI_HAL_OS_DEBUG_SECOND_GPIO, GpioModeOutputPushPull); + furi_hal_gpio_write(FURI_HAL_OS_DEBUG_AWAKE_GPIO, 1); + FuriTimer* second_timer = furi_timer_alloc(furi_hal_os_timer_callback, FuriTimerTypePeriodic, NULL); furi_timer_start(second_timer, FURI_HAL_OS_TICK_HZ); @@ -58,7 +73,8 @@ void furi_hal_os_init() { void furi_hal_os_tick() { if(xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED) { #ifdef FURI_HAL_OS_DEBUG - furi_hal_gpio_write(&gpio_ext_pa6, !furi_hal_gpio_read(&gpio_ext_pa6)); + furi_hal_gpio_write( + FURI_HAL_OS_DEBUG_TICK_GPIO, !furi_hal_gpio_read(FURI_HAL_OS_DEBUG_TICK_GPIO)); #endif xPortSysTickHandler(); } @@ -121,14 +137,14 @@ static inline uint32_t furi_hal_os_sleep(TickType_t expected_idle_ticks) { furi_hal_idle_timer_start(FURI_HAL_OS_TICKS_TO_IDLE_CNT(expected_idle_ticks)); #ifdef FURI_HAL_OS_DEBUG - furi_hal_gpio_write(&gpio_ext_pa7, 0); + furi_hal_gpio_write(FURI_HAL_OS_DEBUG_AWAKE_GPIO, 0); #endif // Go to sleep mode furi_hal_power_sleep(); #ifdef FURI_HAL_OS_DEBUG - furi_hal_gpio_write(&gpio_ext_pa7, 1); + furi_hal_gpio_write(FURI_HAL_OS_DEBUG_AWAKE_GPIO, 1); #endif // Calculate how much time we spent in the sleep diff --git a/firmware/targets/f7/furi_hal/furi_hal_power.c b/firmware/targets/f7/furi_hal/furi_hal_power.c index fd601ec7ec8..9a87cef15c3 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_power.c +++ b/firmware/targets/f7/furi_hal/furi_hal_power.c @@ -4,6 +4,8 @@ #include #include #include +#include +#include #include #include @@ -19,15 +21,16 @@ #define TAG "FuriHalPower" -#ifdef FURI_HAL_POWER_DEEP_SLEEP_ENABLED -#define FURI_HAL_POWER_DEEP_INSOMNIA 0 -#else -#define FURI_HAL_POWER_DEEP_INSOMNIA 1 +#ifndef FURI_HAL_POWER_DEBUG_WFI_GPIO +#define FURI_HAL_POWER_DEBUG_WFI_GPIO (&gpio_ext_pb2) +#endif + +#ifndef FURI_HAL_POWER_DEBUG_STOP_GPIO +#define FURI_HAL_POWER_DEBUG_STOP_GPIO (&gpio_ext_pc3) #endif typedef struct { volatile uint8_t insomnia; - volatile uint8_t deep_insomnia; volatile uint8_t suppress_charge; uint8_t gauge_initialized; @@ -36,7 +39,6 @@ typedef struct { static volatile FuriHalPower furi_hal_power = { .insomnia = 0, - .deep_insomnia = FURI_HAL_POWER_DEEP_INSOMNIA, .suppress_charge = 0, }; @@ -79,19 +81,23 @@ const ParamCEDV cedv = { }; void furi_hal_power_init() { +#ifdef FURI_HAL_POWER_DEBUG + furi_hal_gpio_init_simple(FURI_HAL_POWER_DEBUG_WFI_GPIO, GpioModeOutputPushPull); + furi_hal_gpio_init_simple(FURI_HAL_POWER_DEBUG_STOP_GPIO, GpioModeOutputPushPull); + furi_hal_gpio_write(FURI_HAL_POWER_DEBUG_WFI_GPIO, 0); + furi_hal_gpio_write(FURI_HAL_POWER_DEBUG_STOP_GPIO, 0); +#endif + LL_PWR_SetRegulVoltageScaling(LL_PWR_REGU_VOLTAGE_SCALE1); LL_PWR_SMPS_SetMode(LL_PWR_SMPS_STEP_DOWN); + LL_PWR_SetPowerMode(LL_PWR_MODE_STOP2); + LL_C2_PWR_SetPowerMode(LL_PWR_MODE_STOP2); furi_hal_i2c_acquire(&furi_hal_i2c_handle_power); bq27220_init(&furi_hal_i2c_handle_power, &cedv); bq25896_init(&furi_hal_i2c_handle_power); furi_hal_i2c_release(&furi_hal_i2c_handle_power); -#ifdef FURI_HAL_OS_DEBUG - furi_hal_gpio_init_simple(&gpio_ext_pb2, GpioModeOutputPushPull); - furi_hal_gpio_init_simple(&gpio_ext_pc3, GpioModeOutputPushPull); -#endif - FURI_LOG_I(TAG, "Init OK"); } @@ -140,11 +146,12 @@ bool furi_hal_power_sleep_available() { return furi_hal_power.insomnia == 0; } -bool furi_hal_power_deep_sleep_available() { - return furi_hal_bt_is_alive() && furi_hal_power.deep_insomnia == 0; +static inline bool furi_hal_power_deep_sleep_available() { + return furi_hal_bt_is_alive() && !furi_hal_rtc_is_flag_set(FuriHalRtcFlagLegacySleep) && + !furi_hal_debug_is_gdb_session_active(); } -void furi_hal_power_light_sleep() { +static inline void furi_hal_power_light_sleep() { __WFI(); } @@ -152,17 +159,15 @@ static inline void furi_hal_power_suspend_aux_periphs() { // Disable USART furi_hal_uart_suspend(FuriHalUartIdUSART1); furi_hal_uart_suspend(FuriHalUartIdLPUART1); - // TODO: Disable USB } static inline void furi_hal_power_resume_aux_periphs() { // Re-enable USART furi_hal_uart_resume(FuriHalUartIdUSART1); furi_hal_uart_resume(FuriHalUartIdLPUART1); - // TODO: Re-enable USB } -void furi_hal_power_deep_sleep() { +static inline void furi_hal_power_deep_sleep() { furi_hal_power_suspend_aux_periphs(); while(LL_HSEM_1StepLock(HSEM, CFG_HW_RCC_SEMID)) @@ -187,8 +192,6 @@ void furi_hal_power_deep_sleep() { LL_HSEM_ReleaseLock(HSEM, CFG_HW_RCC_SEMID, 0); // Prepare deep sleep - LL_PWR_SetPowerMode(LL_PWR_MODE_STOP2); - LL_C2_PWR_SetPowerMode(LL_PWR_MODE_STOP2); LL_LPM_EnableDeepSleep(); #if defined(__CC_ARM) @@ -200,13 +203,6 @@ void furi_hal_power_deep_sleep() { LL_LPM_EnableSleep(); - // Make sure that values differ to prevent disaster on wfi - LL_PWR_SetPowerMode(LL_PWR_MODE_STOP0); - LL_C2_PWR_SetPowerMode(LL_PWR_MODE_SHUTDOWN); - - LL_PWR_ClearFlag_C1STOP_C1STB(); - LL_PWR_ClearFlag_C2STOP_C2STB(); - /* Release ENTRY_STOP_MODE semaphore */ LL_HSEM_ReleaseLock(HSEM, CFG_HW_ENTRY_STOP_MODE_SEMID, 0); @@ -220,28 +216,25 @@ void furi_hal_power_deep_sleep() { LL_HSEM_ReleaseLock(HSEM, CFG_HW_RCC_SEMID, 0); furi_hal_power_resume_aux_periphs(); + furi_hal_rtc_sync_shadow(); } void furi_hal_power_sleep() { if(furi_hal_power_deep_sleep_available()) { -#ifdef FURI_HAL_OS_DEBUG - furi_hal_gpio_write(&gpio_ext_pc3, 1); +#ifdef FURI_HAL_POWER_DEBUG + furi_hal_gpio_write(FURI_HAL_POWER_DEBUG_STOP_GPIO, 1); #endif - furi_hal_power_deep_sleep(); - -#ifdef FURI_HAL_OS_DEBUG - furi_hal_gpio_write(&gpio_ext_pc3, 0); +#ifdef FURI_HAL_POWER_DEBUG + furi_hal_gpio_write(FURI_HAL_POWER_DEBUG_STOP_GPIO, 0); #endif } else { -#ifdef FURI_HAL_OS_DEBUG - furi_hal_gpio_write(&gpio_ext_pb2, 1); +#ifdef FURI_HAL_POWER_DEBUG + furi_hal_gpio_write(FURI_HAL_POWER_DEBUG_WFI_GPIO, 1); #endif - furi_hal_power_light_sleep(); - -#ifdef FURI_HAL_OS_DEBUG - furi_hal_gpio_write(&gpio_ext_pb2, 0); +#ifdef FURI_HAL_POWER_DEBUG + furi_hal_gpio_write(FURI_HAL_POWER_DEBUG_WFI_GPIO, 0); #endif } } diff --git a/firmware/targets/f7/furi_hal/furi_hal_random.c b/firmware/targets/f7/furi_hal/furi_hal_random.c index f36407cc16a..d3461c4d12e 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_random.c +++ b/firmware/targets/f7/furi_hal/furi_hal_random.c @@ -9,19 +9,35 @@ #define TAG "FuriHalRandom" +static uint32_t furi_hal_random_read_rng() { + while(LL_RNG_IsActiveFlag_CECS(RNG) && LL_RNG_IsActiveFlag_SECS(RNG) && + !LL_RNG_IsActiveFlag_DRDY(RNG)) { + /* Error handling as described in RM0434, pg. 582-583 */ + if(LL_RNG_IsActiveFlag_CECS(RNG)) { + /* Clock error occurred */ + LL_RNG_ClearFlag_CEIS(RNG); + } + + if(LL_RNG_IsActiveFlag_SECS(RNG)) { + /* Noise source error occurred */ + LL_RNG_ClearFlag_SEIS(RNG); + + for(uint32_t i = 0; i < 12; ++i) { + const volatile uint32_t discard = LL_RNG_ReadRandData32(RNG); + UNUSED(discard); + } + } + } + + return LL_RNG_ReadRandData32(RNG); +} + uint32_t furi_hal_random_get() { while(LL_HSEM_1StepLock(HSEM, CFG_HW_RNG_SEMID)) ; LL_RNG_Enable(RNG); - while(!LL_RNG_IsActiveFlag_DRDY(RNG)) - ; - - if((LL_RNG_IsActiveFlag_CECS(RNG)) || (LL_RNG_IsActiveFlag_SECS(RNG))) { - furi_crash("TRNG error"); - } - - uint32_t random_val = LL_RNG_ReadRandData32(RNG); + const uint32_t random_val = furi_hal_random_read_rng(); LL_RNG_Disable(RNG); LL_HSEM_ReleaseLock(HSEM, CFG_HW_RNG_SEMID, 0); @@ -35,15 +51,7 @@ void furi_hal_random_fill_buf(uint8_t* buf, uint32_t len) { LL_RNG_Enable(RNG); for(uint32_t i = 0; i < len; i += 4) { - while(!LL_RNG_IsActiveFlag_DRDY(RNG)) - ; - - if((LL_RNG_IsActiveFlag_CECS(RNG)) || (LL_RNG_IsActiveFlag_SECS(RNG))) { - furi_crash("TRNG error"); - } - - uint32_t random_val = LL_RNG_ReadRandData32(RNG); - + const uint32_t random_val = furi_hal_random_read_rng(); uint8_t len_cur = ((i + 4) < len) ? (4) : (len - i); memcpy(&buf[i], &random_val, len_cur); } diff --git a/firmware/targets/f7/furi_hal/furi_hal_resources.c b/firmware/targets/f7/furi_hal/furi_hal_resources.c index 912912b4a4a..abfd977e52c 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_resources.c +++ b/firmware/targets/f7/furi_hal/furi_hal_resources.c @@ -6,6 +6,9 @@ #define TAG "FuriHalResources" +const GpioPin gpio_swdio = {.port = GPIOA, .pin = LL_GPIO_PIN_13}; +const GpioPin gpio_swclk = {.port = GPIOA, .pin = LL_GPIO_PIN_14}; + const GpioPin gpio_vibro = {.port = VIBRO_GPIO_Port, .pin = VIBRO_Pin}; const GpioPin gpio_ibutton = {.port = iBTN_GPIO_Port, .pin = iBTN_Pin}; diff --git a/firmware/targets/f7/furi_hal/furi_hal_resources.h b/firmware/targets/f7/furi_hal/furi_hal_resources.h index f2991730010..6e585c51822 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_resources.h +++ b/firmware/targets/f7/furi_hal/furi_hal_resources.h @@ -50,6 +50,9 @@ extern const size_t input_pins_count; extern const GpioPinRecord gpio_pins[]; extern const size_t gpio_pins_count; +extern const GpioPin gpio_swdio; +extern const GpioPin gpio_swclk; + extern const GpioPin gpio_vibro; extern const GpioPin gpio_ibutton; diff --git a/firmware/targets/f7/furi_hal/furi_hal_rtc.c b/firmware/targets/f7/furi_hal/furi_hal_rtc.c index 84e7fe3959c..7bd45c35d71 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_rtc.c +++ b/firmware/targets/f7/furi_hal/furi_hal_rtc.c @@ -165,6 +165,14 @@ void furi_hal_rtc_init() { FURI_LOG_I(TAG, "Init OK"); } +void furi_hal_rtc_sync_shadow() { + if(!LL_RTC_IsShadowRegBypassEnabled(RTC)) { + LL_RTC_ClearFlag_RS(RTC); + while(!LL_RTC_IsActiveFlag_RS(RTC)) { + }; + } +} + uint32_t furi_hal_rtc_get_register(FuriHalRtcRegister reg) { return LL_RTC_BAK_GetRegister(RTC, reg); } @@ -312,12 +320,7 @@ void furi_hal_rtc_set_datetime(FuriHalRtcDateTime* datetime) { /* Exit Initialization mode */ LL_RTC_DisableInitMode(RTC); - /* If RTC_CR_BYPSHAD bit = 0, wait for synchro else this check is not needed */ - if(!LL_RTC_IsShadowRegBypassEnabled(RTC)) { - LL_RTC_ClearFlag_RS(RTC); - while(!LL_RTC_IsActiveFlag_RS(RTC)) { - }; - } + furi_hal_rtc_sync_shadow(); /* Enable write protection */ LL_RTC_EnableWriteProtection(RTC); diff --git a/firmware/targets/furi_hal_include/furi_hal_debug.h b/firmware/targets/furi_hal_include/furi_hal_debug.h index 88397bbbafb..befbb4f40cb 100644 --- a/firmware/targets/furi_hal_include/furi_hal_debug.h +++ b/firmware/targets/furi_hal_include/furi_hal_debug.h @@ -18,6 +18,9 @@ void furi_hal_debug_enable(); /** Disable MCU debug */ void furi_hal_debug_disable(); +/** Check if GDB debug session is active */ +bool furi_hal_debug_is_gdb_session_active(); + #ifdef __cplusplus } #endif diff --git a/firmware/targets/furi_hal_include/furi_hal_power.h b/firmware/targets/furi_hal_include/furi_hal_power.h index 462e20e41db..00182fa2861 100644 --- a/firmware/targets/furi_hal_include/furi_hal_power.h +++ b/firmware/targets/furi_hal_include/furi_hal_power.h @@ -58,12 +58,6 @@ void furi_hal_power_insomnia_exit(); */ bool furi_hal_power_sleep_available(); -/** Check if deep sleep availble - * - * @return true if available - */ -bool furi_hal_power_deep_sleep_available(); - /** Go to sleep */ void furi_hal_power_sleep(); diff --git a/firmware/targets/furi_hal_include/furi_hal_rtc.h b/firmware/targets/furi_hal_include/furi_hal_rtc.h index b16b04a6849..0d9f46f01ba 100644 --- a/firmware/targets/furi_hal_include/furi_hal_rtc.h +++ b/firmware/targets/furi_hal_include/furi_hal_rtc.h @@ -30,6 +30,7 @@ typedef enum { FuriHalRtcFlagLock = (1 << 2), FuriHalRtcFlagC2Update = (1 << 3), FuriHalRtcFlagHandOrient = (1 << 4), + FuriHalRtcFlagLegacySleep = (1 << 5), } FuriHalRtcFlag; typedef enum { @@ -85,6 +86,9 @@ void furi_hal_rtc_deinit_early(); /** Initialize RTC subsystem */ void furi_hal_rtc_init(); +/** Force sync shadow registers */ +void furi_hal_rtc_sync_shadow(); + /** Get RTC register content * * @param[in] reg The register identifier diff --git a/furi/core/check.c b/furi/core/check.c index 910527ceed8..64f9f72f1b9 100644 --- a/furi/core/check.c +++ b/furi/core/check.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -117,6 +118,8 @@ FURI_NORETURN void __furi_crash() { if(debug) { furi_hal_console_puts("\r\nSystem halted. Connect debugger for more info\r\n"); furi_hal_console_puts("\033[0m\r\n"); + furi_hal_debug_enable(); + RESTORE_REGISTERS_AND_HALT_MCU(true); } else { furi_hal_rtc_set_fault_data((uint32_t)__furi_check_message); diff --git a/furi/core/core_defines.h b/furi/core/core_defines.h index 03a364abd0f..830bb191c2e 100644 --- a/furi/core/core_defines.h +++ b/furi/core/core_defines.h @@ -24,6 +24,10 @@ extern "C" { }) #endif +#ifndef ABS +#define ABS(a) ({ (a) < 0 ? -(a) : (a); }) +#endif + #ifndef ROUND_UP_TO #define ROUND_UP_TO(a, b) \ ({ \ diff --git a/site_scons/cc.scons b/site_scons/cc.scons index 1eb6a33768e..55ab72ba654 100644 --- a/site_scons/cc.scons +++ b/site_scons/cc.scons @@ -36,6 +36,7 @@ ENV.AppendUnique( ], CPPDEFINES=[ "_GNU_SOURCE", + *GetOption("extra_defines"), ], LINKFLAGS=[ "-mcpu=cortex-m4", diff --git a/site_scons/commandline.scons b/site_scons/commandline.scons index d832a466e37..2e948662738 100644 --- a/site_scons/commandline.scons +++ b/site_scons/commandline.scons @@ -26,6 +26,14 @@ AddOption( help="List of applications to add to firmware's built-ins. Also see FIRMWARE_APP_SET and FIRMWARE_APPS", ) +AddOption( + "--extra-define", + action="append", + dest="extra_defines", + default=[], + help="Extra global define that will be passed to C/C++ compiler, can be specified multiple times", +) + AddOption( "--extra-ext-apps", action="store", From de02a0a25ad1d9b2637bf16bdb78b97b2a38f6ab Mon Sep 17 00:00:00 2001 From: Eric Betts Date: Sun, 16 Apr 2023 22:36:15 -0700 Subject: [PATCH 518/824] [#2589] Correctly aborts when correct key is found (#2590) --- applications/external/picopass/picopass_worker.c | 5 ++++- .../picopass/scenes/picopass_scene_elite_dict_attack.c | 6 ++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/applications/external/picopass/picopass_worker.c b/applications/external/picopass/picopass_worker.c index 174413bae36..e671552c5cb 100644 --- a/applications/external/picopass/picopass_worker.c +++ b/applications/external/picopass/picopass_worker.c @@ -570,7 +570,7 @@ void picopass_worker_elite_dict_attack(PicopassWorker* picopass_worker) { picopass_worker->callback(PicopassWorkerEventFail, picopass_worker->context); break; } - picopass_worker->callback(PicopassWorkerEventSuccess, picopass_worker->context); + picopass_worker->callback(PicopassWorkerEventAborted, picopass_worker->context); break; } @@ -596,6 +596,9 @@ int32_t picopass_worker_task(void* context) { picopass_worker_write_key(picopass_worker); } else if(picopass_worker->state == PicopassWorkerStateEliteDictAttack) { picopass_worker_elite_dict_attack(picopass_worker); + } else if(picopass_worker->state == PicopassWorkerStateStop) { + FURI_LOG_D(TAG, "Worker state stop"); + // no-op } else { FURI_LOG_W(TAG, "Unknown state %d", picopass_worker->state); } diff --git a/applications/external/picopass/scenes/picopass_scene_elite_dict_attack.c b/applications/external/picopass/scenes/picopass_scene_elite_dict_attack.c index c76a8ffae59..e6191d5baf4 100644 --- a/applications/external/picopass/scenes/picopass_scene_elite_dict_attack.c +++ b/applications/external/picopass/scenes/picopass_scene_elite_dict_attack.c @@ -116,8 +116,7 @@ bool picopass_scene_elite_dict_attack_on_event(void* context, SceneManagerEvent uint32_t state = scene_manager_get_scene_state(picopass->scene_manager, PicopassSceneEliteDictAttack); if(event.type == SceneManagerEventTypeCustom) { - if(event.event == PicopassWorkerEventSuccess || - event.event == PicopassWorkerEventAborted) { + if(event.event == PicopassWorkerEventSuccess) { if(state == DictAttackStateUserDictInProgress || state == DictAttackStateStandardDictInProgress) { picopass_worker_stop(picopass->worker); @@ -127,6 +126,9 @@ bool picopass_scene_elite_dict_attack_on_event(void* context, SceneManagerEvent scene_manager_next_scene(picopass->scene_manager, PicopassSceneReadCardSuccess); consumed = true; } + } else if(event.event == PicopassWorkerEventAborted) { + scene_manager_next_scene(picopass->scene_manager, PicopassSceneReadCardSuccess); + consumed = true; } else if(event.event == PicopassWorkerEventCardDetected) { dict_attack_set_card_detected(picopass->dict_attack); consumed = true; From f68c3b2a653a2f5fc35c3bc90c8447f3d5d97398 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Tue, 18 Apr 2023 20:38:35 +0900 Subject: [PATCH 519/824] [FL-3264] Various stop mode fixes (#2584) * BleGlue: log hci_cmd_resp invocation * BleGlue: increase BleHciDriver stack size * ble hid app: increase stack * ble: comment unnecessary hci reset * BleGlue: stricter checks in communication with core2, cleanup code * Furi: enter insomnia when executing from RAM --------- Co-authored-by: gornekich --- firmware/targets/f7/ble_glue/app_debug.c | 2 +- firmware/targets/f7/ble_glue/ble_app.c | 33 ++++++++++-------------- firmware/targets/f7/ble_glue/gap.c | 2 -- firmware/targets/f7/src/main.c | 3 +++ 4 files changed, 18 insertions(+), 22 deletions(-) diff --git a/firmware/targets/f7/ble_glue/app_debug.c b/firmware/targets/f7/ble_glue/app_debug.c index d84588540a0..78e789ac307 100644 --- a/firmware/targets/f7/ble_glue/app_debug.c +++ b/firmware/targets/f7/ble_glue/app_debug.c @@ -68,7 +68,7 @@ static const APPD_GpioConfig_t aGpioConfigList[GPIO_CFG_NBR_OF_FEATURES] = { {GPIOA, LL_GPIO_PIN_0, 0, 0}, /* END_OF_CONNECTION_EVENT - Set on Entry / Reset on Exit */ {GPIOA, LL_GPIO_PIN_0, 0, 0}, /* TIMER_SERVER_CALLBACK - Toggle on Entry */ {GPIOA, LL_GPIO_PIN_4, 1, 0}, /* PES_ACTIVITY - Set on Entry / Reset on Exit */ - {GPIOB, LL_GPIO_PIN_2, 1, 0}, /* MB_BLE_SEND_EVT - Set on Entry / Reset on Exit */ + {GPIOC, LL_GPIO_PIN_0, 1, 0}, /* MB_BLE_SEND_EVT - Set on Entry / Reset on Exit */ /* From v1.3.0 */ {GPIOA, LL_GPIO_PIN_0, 0, 0}, /* BLE_NO_DELAY - Set on Entry / Reset on Exit */ {GPIOA, LL_GPIO_PIN_0, 0, 0}, /* BLE_STACK_STORE_NVM_CB - Set on Entry / Reset on Exit */ diff --git a/firmware/targets/f7/ble_glue/ble_app.c b/firmware/targets/f7/ble_glue/ble_app.c index 30be3c7ceca..4fc4d521be1 100644 --- a/firmware/targets/f7/ble_glue/ble_app.c +++ b/firmware/targets/f7/ble_glue/ble_app.c @@ -137,38 +137,33 @@ static int32_t ble_app_hci_thread(void* arg) { // Called by WPAN lib void hci_notify_asynch_evt(void* pdata) { UNUSED(pdata); - if(ble_app) { - FuriThreadId thread_id = furi_thread_get_id(ble_app->thread); - furi_assert(thread_id); - furi_thread_flags_set(thread_id, BLE_APP_FLAG_HCI_EVENT); - } + furi_check(ble_app); + FuriThreadId thread_id = furi_thread_get_id(ble_app->thread); + furi_assert(thread_id); + furi_thread_flags_set(thread_id, BLE_APP_FLAG_HCI_EVENT); } void hci_cmd_resp_release(uint32_t flag) { UNUSED(flag); - if(ble_app) { - furi_semaphore_release(ble_app->hci_sem); - } + furi_check(ble_app); + furi_check(furi_semaphore_release(ble_app->hci_sem) == FuriStatusOk); } void hci_cmd_resp_wait(uint32_t timeout) { - UNUSED(timeout); - if(ble_app) { - furi_semaphore_acquire(ble_app->hci_sem, FuriWaitForever); - } + furi_check(ble_app); + furi_check(furi_semaphore_acquire(ble_app->hci_sem, timeout) == FuriStatusOk); } static void ble_app_hci_event_handler(void* pPayload) { SVCCTL_UserEvtFlowStatus_t svctl_return_status; tHCI_UserEvtRxParam* pParam = (tHCI_UserEvtRxParam*)pPayload; - if(ble_app) { - svctl_return_status = SVCCTL_UserEvtRx((void*)&(pParam->pckt->evtserial)); - if(svctl_return_status != SVCCTL_UserEvtFlowDisable) { - pParam->status = HCI_TL_UserEventFlow_Enable; - } else { - pParam->status = HCI_TL_UserEventFlow_Disable; - } + furi_check(ble_app); + svctl_return_status = SVCCTL_UserEvtRx((void*)&(pParam->pckt->evtserial)); + if(svctl_return_status != SVCCTL_UserEvtFlowDisable) { + pParam->status = HCI_TL_UserEventFlow_Enable; + } else { + pParam->status = HCI_TL_UserEventFlow_Disable; } } diff --git a/firmware/targets/f7/ble_glue/gap.c b/firmware/targets/f7/ble_glue/gap.c index 8ef037d6b72..cbb2a203bd1 100644 --- a/firmware/targets/f7/ble_glue/gap.c +++ b/firmware/targets/f7/ble_glue/gap.c @@ -289,8 +289,6 @@ static void gap_init_svc(Gap* gap) { tBleStatus status; uint32_t srd_bd_addr[2]; - // HCI Reset to synchronise BLE Stack - hci_reset(); // Configure mac address aci_hal_write_config_data( CONFIG_DATA_PUBADDR_OFFSET, CONFIG_DATA_PUBADDR_LEN, gap->config->mac_address); diff --git a/firmware/targets/f7/src/main.c b/firmware/targets/f7/src/main.c index 1f2b5d6e42a..2c353f52b20 100644 --- a/firmware/targets/f7/src/main.c +++ b/firmware/targets/f7/src/main.c @@ -29,6 +29,8 @@ int main() { FuriThread* main_thread = furi_thread_alloc_ex("Init", 4096, init_task, NULL); #ifdef FURI_RAM_EXEC + // Prevent entering sleep mode when executed from RAM + furi_hal_power_insomnia_enter(); furi_thread_start(main_thread); #else furi_hal_light_sequence("RGB"); @@ -44,6 +46,7 @@ int main() { furi_hal_power_reset(); } else if(boot_mode == FuriHalRtcBootModeUpdate) { furi_hal_light_sequence("rgb BR"); + // Do update flipper_boot_update_exec(); // if things go nice, we shouldn't reach this point. // But if we do, abandon to avoid bootloops From 2c7eb53caceea63999aed09d691271a3d1ee58bc Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Wed, 19 Apr 2023 11:30:26 +0300 Subject: [PATCH 520/824] [FL-2505] Active RPC session icon (#2583) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Active RPC session icon * Add RpcOwner, don't show the RPC icon when the session was started from BLE * Fix rpc_test and f18 api * Bump API version Co-authored-by: あく --- applications/debug/unit_tests/rpc/rpc_test.c | 4 +-- applications/services/bt/bt_service/bt.c | 2 +- applications/services/rpc/rpc.c | 9 ++++++- applications/services/rpc/rpc.h | 18 ++++++++++++- applications/services/rpc/rpc_cli.c | 2 +- applications/services/rpc/rpc_gui.c | 26 ++++++++++++++++++- assets/icons/StatusBar/Rpc_active_7x8.png | Bin 0 -> 3607 bytes firmware/targets/f18/api_symbols.csv | 5 ++-- firmware/targets/f7/api_symbols.csv | 5 ++-- 9 files changed, 60 insertions(+), 11 deletions(-) create mode 100644 assets/icons/StatusBar/Rpc_active_7x8.png diff --git a/applications/debug/unit_tests/rpc/rpc_test.c b/applications/debug/unit_tests/rpc/rpc_test.c index 329f3b7412e..167266a8433 100644 --- a/applications/debug/unit_tests/rpc/rpc_test.c +++ b/applications/debug/unit_tests/rpc/rpc_test.c @@ -84,7 +84,7 @@ static void test_rpc_setup(void) { rpc = furi_record_open(RECORD_RPC); for(int i = 0; !(rpc_session[0].session) && (i < 10000); ++i) { - rpc_session[0].session = rpc_session_open(rpc); + rpc_session[0].session = rpc_session_open(rpc, RpcOwnerUnknown); furi_delay_tick(1); } furi_check(rpc_session[0].session); @@ -104,7 +104,7 @@ static void test_rpc_setup_second_session(void) { furi_check(!(rpc_session[1].session)); for(int i = 0; !(rpc_session[1].session) && (i < 10000); ++i) { - rpc_session[1].session = rpc_session_open(rpc); + rpc_session[1].session = rpc_session_open(rpc, RpcOwnerUnknown); furi_delay_tick(1); } furi_check(rpc_session[1].session); diff --git a/applications/services/bt/bt_service/bt.c b/applications/services/bt/bt_service/bt.c index 16b60231b24..2dcea34856c 100644 --- a/applications/services/bt/bt_service/bt.c +++ b/applications/services/bt/bt_service/bt.c @@ -225,7 +225,7 @@ static bool bt_on_gap_event_callback(GapEvent event, void* context) { furi_event_flag_clear(bt->rpc_event, BT_RPC_EVENT_DISCONNECTED); if(bt->profile == BtProfileSerial) { // Open RPC session - bt->rpc_session = rpc_session_open(bt->rpc); + bt->rpc_session = rpc_session_open(bt->rpc, RpcOwnerBle); if(bt->rpc_session) { FURI_LOG_I(TAG, "Open RPC connection"); rpc_session_set_send_bytes_callback(bt->rpc_session, bt_rpc_send_bytes_callback); diff --git a/applications/services/rpc/rpc.c b/applications/services/rpc/rpc.c index 57a4ec9aa2d..5b09e9b5178 100644 --- a/applications/services/rpc/rpc.c +++ b/applications/services/rpc/rpc.c @@ -76,6 +76,7 @@ struct RpcSession { RpcBufferIsEmptyCallback buffer_is_empty_callback; RpcSessionClosedCallback closed_callback; RpcSessionTerminatedCallback terminated_callback; + RpcOwner owner; void* context; }; @@ -83,6 +84,11 @@ struct Rpc { FuriMutex* busy_mutex; }; +RpcOwner rpc_session_get_owner(RpcSession* session) { + furi_assert(session); + return session->owner; +} + static void rpc_close_session_process(const PB_Main* request, void* context) { furi_assert(request); furi_assert(context); @@ -348,7 +354,7 @@ static void rpc_session_free_callback(FuriThreadState thread_state, void* contex } } -RpcSession* rpc_session_open(Rpc* rpc) { +RpcSession* rpc_session_open(Rpc* rpc, RpcOwner owner) { furi_assert(rpc); RpcSession* session = malloc(sizeof(RpcSession)); @@ -357,6 +363,7 @@ RpcSession* rpc_session_open(Rpc* rpc) { session->rpc = rpc; session->terminate = false; session->decode_error = false; + session->owner = owner; RpcHandlerDict_init(session->handlers); session->decoded_message = malloc(sizeof(PB_Main)); diff --git a/applications/services/rpc/rpc.h b/applications/services/rpc/rpc.h index ec290e083af..d11fdc16244 100644 --- a/applications/services/rpc/rpc.h +++ b/applications/services/rpc/rpc.h @@ -30,6 +30,21 @@ typedef void (*RpcSessionClosedCallback)(void* context); * and all operations were finished */ typedef void (*RpcSessionTerminatedCallback)(void* context); +/** RPC owner */ +typedef enum { + RpcOwnerUnknown = 0, + RpcOwnerBle, + RpcOwnerUsb, + RpcOwnerCount, +} RpcOwner; + +/** Get RPC session owner + * + * @param session pointer to RpcSession descriptor + * @return session owner + */ +RpcOwner rpc_session_get_owner(RpcSession* session); + /** Open RPC session * * USAGE: @@ -44,10 +59,11 @@ typedef void (*RpcSessionTerminatedCallback)(void* context); * * * @param rpc instance + * @param owner owner of session * @return pointer to RpcSession descriptor, or * NULL if RPC is busy and can't open session now */ -RpcSession* rpc_session_open(Rpc* rpc); +RpcSession* rpc_session_open(Rpc* rpc, RpcOwner owner); /** Close RPC session * It is guaranteed that no callbacks will be called diff --git a/applications/services/rpc/rpc_cli.c b/applications/services/rpc/rpc_cli.c index d14b8eee2fa..f1c139b5f93 100644 --- a/applications/services/rpc/rpc_cli.c +++ b/applications/services/rpc/rpc_cli.c @@ -47,7 +47,7 @@ void rpc_cli_command_start_session(Cli* cli, FuriString* args, void* context) { FURI_LOG_D(TAG, "Free memory %lu", mem_before); furi_hal_usb_lock(); - RpcSession* rpc_session = rpc_session_open(rpc); + RpcSession* rpc_session = rpc_session_open(rpc, RpcOwnerUsb); if(rpc_session == NULL) { printf("Session start error\r\n"); furi_hal_usb_unlock(); diff --git a/applications/services/rpc/rpc_gui.c b/applications/services/rpc/rpc_gui.c index 0c70702cf4e..9ba20a83208 100644 --- a/applications/services/rpc/rpc_gui.c +++ b/applications/services/rpc/rpc_gui.c @@ -2,6 +2,7 @@ #include "rpc_i.h" #include "gui.pb.h" #include +#include #define TAG "RpcGui" @@ -31,6 +32,8 @@ typedef struct { uint32_t input_key_counter[InputKeyMAX]; uint32_t input_counter; + + ViewPort* rpc_session_active_viewport; } RpcGuiSystem; static const PB_Gui_ScreenOrientation rpc_system_gui_screen_orientation_map[] = { @@ -352,6 +355,12 @@ static void rpc_system_gui_virtual_display_frame_process(const PB_Main* request, (void)session; } +static void rpc_active_session_icon_draw_callback(Canvas* canvas, void* context) { + UNUSED(context); + furi_assert(canvas); + canvas_draw_icon(canvas, 0, 0, &I_Rpc_active_7x8); +} + void* rpc_system_gui_alloc(RpcSession* session) { furi_assert(session); @@ -359,6 +368,18 @@ void* rpc_system_gui_alloc(RpcSession* session) { rpc_gui->gui = furi_record_open(RECORD_GUI); rpc_gui->session = session; + // Active session icon + rpc_gui->rpc_session_active_viewport = view_port_alloc(); + view_port_set_width(rpc_gui->rpc_session_active_viewport, icon_get_width(&I_Rpc_active_7x8)); + view_port_draw_callback_set( + rpc_gui->rpc_session_active_viewport, rpc_active_session_icon_draw_callback, session); + if(rpc_session_get_owner(rpc_gui->session) != RpcOwnerBle) { + view_port_enabled_set(rpc_gui->rpc_session_active_viewport, true); + } else { + view_port_enabled_set(rpc_gui->rpc_session_active_viewport, false); + } + gui_add_view_port(rpc_gui->gui, rpc_gui->rpc_session_active_viewport, GuiLayerStatusBarLeft); + RpcHandler rpc_handler = { .message_handler = NULL, .decode_submessage = NULL, @@ -399,6 +420,9 @@ void rpc_system_gui_free(void* context) { rpc_gui->virtual_display_not_empty = false; } + gui_remove_view_port(rpc_gui->gui, rpc_gui->rpc_session_active_viewport); + view_port_free(rpc_gui->rpc_session_active_viewport); + if(rpc_gui->is_streaming) { rpc_gui->is_streaming = false; // Remove GUI framebuffer callback @@ -415,4 +439,4 @@ void rpc_system_gui_free(void* context) { } furi_record_close(RECORD_GUI); free(rpc_gui); -} +} \ No newline at end of file diff --git a/assets/icons/StatusBar/Rpc_active_7x8.png b/assets/icons/StatusBar/Rpc_active_7x8.png new file mode 100644 index 0000000000000000000000000000000000000000..f643a82aa1d2efab26d7e8976bce73124c5c64d2 GIT binary patch literal 3607 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 TX-MlFYYS&#YHd=yAa5I#)A literal 0 HcmV?d00001 diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index 493f5963474..a37ef02cc60 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,22.0,, +Version,+,23.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -1596,7 +1596,8 @@ Function,-,rindex,char*,"const char*, int" Function,+,rpc_session_close,void,RpcSession* Function,+,rpc_session_feed,size_t,"RpcSession*, uint8_t*, size_t, TickType_t" Function,+,rpc_session_get_available_size,size_t,RpcSession* -Function,+,rpc_session_open,RpcSession*,Rpc* +Function,+,rpc_session_get_owner,RpcOwner,RpcSession* +Function,+,rpc_session_open,RpcSession*,"Rpc*, RpcOwner" Function,+,rpc_session_set_buffer_is_empty_callback,void,"RpcSession*, RpcBufferIsEmptyCallback" Function,+,rpc_session_set_close_callback,void,"RpcSession*, RpcSessionClosedCallback" Function,+,rpc_session_set_context,void,"RpcSession*, void*" diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 3ef57813b4a..43ede425e12 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,22.0,, +Version,+,23.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -2373,7 +2373,8 @@ Function,-,roundl,long double,long double Function,+,rpc_session_close,void,RpcSession* Function,+,rpc_session_feed,size_t,"RpcSession*, uint8_t*, size_t, TickType_t" Function,+,rpc_session_get_available_size,size_t,RpcSession* -Function,+,rpc_session_open,RpcSession*,Rpc* +Function,+,rpc_session_get_owner,RpcOwner,RpcSession* +Function,+,rpc_session_open,RpcSession*,"Rpc*, RpcOwner" Function,+,rpc_session_set_buffer_is_empty_callback,void,"RpcSession*, RpcBufferIsEmptyCallback" Function,+,rpc_session_set_close_callback,void,"RpcSession*, RpcSessionClosedCallback" Function,+,rpc_session_set_context,void,"RpcSession*, void*" From 74fe003f8bd6e69230979d02a90853f0ea9f9750 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Wed, 19 Apr 2023 12:33:23 +0300 Subject: [PATCH 521/824] [FL-3171] Introduce stealth mode and auto-selective lock (#2576) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Introduce stealth mode and auto-selective lock * Stealth mode status bar icon * Review fixes * Fix icon disappearing after reboot * Support overriding stealth mode * FuriHal: correct reserved space size in RTC SystemReg Co-authored-by: あく --- applications/services/desktop/desktop.c | 29 +++++++++++++ applications/services/desktop/desktop_i.h | 2 + .../desktop/scenes/desktop_scene_lock_menu.c | 12 ++++++ .../services/desktop/views/desktop_events.h | 2 + .../desktop/views/desktop_view_lock_menu.c | 38 +++++++++++++---- .../desktop/views/desktop_view_lock_menu.h | 2 + .../services/notification/notification_app.c | 27 ++++++++---- .../notification_settings_app.c | 39 ++++++++++++------ assets/icons/StatusBar/Muted_8x8.png | Bin 0 -> 3626 bytes .../targets/furi_hal_include/furi_hal_rtc.h | 1 + 10 files changed, 122 insertions(+), 30 deletions(-) create mode 100644 assets/icons/StatusBar/Muted_8x8.png diff --git a/applications/services/desktop/desktop.c b/applications/services/desktop/desktop.c index 41470ed3a17..bdb73009952 100644 --- a/applications/services/desktop/desktop.c +++ b/applications/services/desktop/desktop.c @@ -46,6 +46,12 @@ static void desktop_dummy_mode_icon_draw_callback(Canvas* canvas, void* context) canvas_draw_icon(canvas, 0, 0, &I_GameMode_11x8); } +static void desktop_stealth_mode_icon_draw_callback(Canvas* canvas, void* context) { + UNUSED(context); + furi_assert(canvas); + canvas_draw_icon(canvas, 0, 0, &I_Muted_8x8); +} + static bool desktop_custom_event_callback(void* context, uint32_t event) { furi_assert(context); Desktop* desktop = (Desktop*)context; @@ -153,6 +159,17 @@ void desktop_set_dummy_mode_state(Desktop* desktop, bool enabled) { desktop->in_transition = false; } +void desktop_set_stealth_mode_state(Desktop* desktop, bool enabled) { + desktop->in_transition = true; + if(enabled) { + furi_hal_rtc_set_flag(FuriHalRtcFlagStealthMode); + } else { + furi_hal_rtc_reset_flag(FuriHalRtcFlagStealthMode); + } + view_port_enabled_set(desktop->stealth_mode_icon_viewport, enabled); + desktop->in_transition = false; +} + Desktop* desktop_alloc() { Desktop* desktop = malloc(sizeof(Desktop)); @@ -244,6 +261,18 @@ Desktop* desktop_alloc() { view_port_enabled_set(desktop->dummy_mode_icon_viewport, false); gui_add_view_port(desktop->gui, desktop->dummy_mode_icon_viewport, GuiLayerStatusBarLeft); + // Stealth mode icon + desktop->stealth_mode_icon_viewport = view_port_alloc(); + view_port_set_width(desktop->stealth_mode_icon_viewport, icon_get_width(&I_Muted_8x8)); + view_port_draw_callback_set( + desktop->stealth_mode_icon_viewport, desktop_stealth_mode_icon_draw_callback, desktop); + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagStealthMode)) { + view_port_enabled_set(desktop->stealth_mode_icon_viewport, true); + } else { + view_port_enabled_set(desktop->stealth_mode_icon_viewport, false); + } + gui_add_view_port(desktop->gui, desktop->stealth_mode_icon_viewport, GuiLayerStatusBarLeft); + // Special case: autostart application is already running desktop->loader = furi_record_open(RECORD_LOADER); if(loader_is_locked(desktop->loader) && diff --git a/applications/services/desktop/desktop_i.h b/applications/services/desktop/desktop_i.h index 2f3ec9b517d..ede6bbcc31c 100644 --- a/applications/services/desktop/desktop_i.h +++ b/applications/services/desktop/desktop_i.h @@ -59,6 +59,7 @@ struct Desktop { ViewPort* lock_icon_viewport; ViewPort* dummy_mode_icon_viewport; + ViewPort* stealth_mode_icon_viewport; AnimationManager* animation_manager; @@ -79,3 +80,4 @@ void desktop_free(Desktop* desktop); void desktop_lock(Desktop* desktop); void desktop_unlock(Desktop* desktop); void desktop_set_dummy_mode_state(Desktop* desktop, bool enabled); +void desktop_set_stealth_mode_state(Desktop* desktop, bool enabled); diff --git a/applications/services/desktop/scenes/desktop_scene_lock_menu.c b/applications/services/desktop/scenes/desktop_scene_lock_menu.c index 365fe17023c..bfaa8a036a6 100644 --- a/applications/services/desktop/scenes/desktop_scene_lock_menu.c +++ b/applications/services/desktop/scenes/desktop_scene_lock_menu.c @@ -27,6 +27,8 @@ void desktop_scene_lock_menu_on_enter(void* context) { desktop_lock_menu_set_callback(desktop->lock_menu, desktop_scene_lock_menu_callback, desktop); desktop_lock_menu_set_pin_state(desktop->lock_menu, desktop->settings.pin_code.length > 0); desktop_lock_menu_set_dummy_mode_state(desktop->lock_menu, desktop->settings.dummy_mode); + desktop_lock_menu_set_stealth_mode_state( + desktop->lock_menu, furi_hal_rtc_is_flag_set(FuriHalRtcFlagStealthMode)); desktop_lock_menu_set_idx(desktop->lock_menu, 0); view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewIdLockMenu); @@ -78,6 +80,16 @@ bool desktop_scene_lock_menu_on_event(void* context, SceneManagerEvent event) { scene_manager_search_and_switch_to_previous_scene( desktop->scene_manager, DesktopSceneMain); break; + case DesktopLockMenuEventStealthModeOn: + desktop_set_stealth_mode_state(desktop, true); + scene_manager_search_and_switch_to_previous_scene( + desktop->scene_manager, DesktopSceneMain); + break; + case DesktopLockMenuEventStealthModeOff: + desktop_set_stealth_mode_state(desktop, false); + scene_manager_search_and_switch_to_previous_scene( + desktop->scene_manager, DesktopSceneMain); + break; default: break; } diff --git a/applications/services/desktop/views/desktop_events.h b/applications/services/desktop/views/desktop_events.h index 666d179b83d..983e8443853 100644 --- a/applications/services/desktop/views/desktop_events.h +++ b/applications/services/desktop/views/desktop_events.h @@ -34,6 +34,8 @@ typedef enum { DesktopLockMenuEventPinLock, DesktopLockMenuEventDummyModeOn, DesktopLockMenuEventDummyModeOff, + DesktopLockMenuEventStealthModeOn, + DesktopLockMenuEventStealthModeOff, DesktopAnimationEventCheckAnimation, DesktopAnimationEventNewIdleAnimation, diff --git a/applications/services/desktop/views/desktop_view_lock_menu.c b/applications/services/desktop/views/desktop_view_lock_menu.c index 52570f8ca2b..8b25a890f16 100644 --- a/applications/services/desktop/views/desktop_view_lock_menu.c +++ b/applications/services/desktop/views/desktop_view_lock_menu.c @@ -7,7 +7,7 @@ typedef enum { DesktopLockMenuIndexLock, - DesktopLockMenuIndexPinLock, + DesktopLockMenuIndexStealth, DesktopLockMenuIndexDummy, DesktopLockMenuIndexTotalCount @@ -39,6 +39,14 @@ void desktop_lock_menu_set_dummy_mode_state(DesktopLockMenuView* lock_menu, bool true); } +void desktop_lock_menu_set_stealth_mode_state(DesktopLockMenuView* lock_menu, bool stealth_mode) { + with_view_model( + lock_menu->view, + DesktopLockMenuViewModel * model, + { model->stealth_mode = stealth_mode; }, + true); +} + void desktop_lock_menu_set_idx(DesktopLockMenuView* lock_menu, uint8_t idx) { furi_assert(idx < DesktopLockMenuIndexTotalCount); with_view_model( @@ -58,11 +66,11 @@ void desktop_lock_menu_draw_callback(Canvas* canvas, void* model) { if(i == DesktopLockMenuIndexLock) { str = "Lock"; - } else if(i == DesktopLockMenuIndexPinLock) { - if(m->pin_is_set) { - str = "Lock with PIN"; + } else if(i == DesktopLockMenuIndexStealth) { + if(m->stealth_mode) { + str = "Sound Mode"; } else { - str = "Set PIN"; + str = "Stealth Mode"; } } else if(i == DesktopLockMenuIndexDummy) { //-V547 if(m->dummy_mode) { @@ -93,6 +101,8 @@ bool desktop_lock_menu_input_callback(InputEvent* event, void* context) { uint8_t idx = 0; bool consumed = false; bool dummy_mode = false; + bool stealth_mode = false; + bool pin_is_set = false; bool update = false; with_view_model( @@ -120,14 +130,24 @@ bool desktop_lock_menu_input_callback(InputEvent* event, void* context) { } idx = model->idx; dummy_mode = model->dummy_mode; + stealth_mode = model->stealth_mode; + pin_is_set = model->pin_is_set; }, update); if(event->key == InputKeyOk) { - if((idx == DesktopLockMenuIndexLock) && (event->type == InputTypeShort)) { - lock_menu->callback(DesktopLockMenuEventLock, lock_menu->context); - } else if((idx == DesktopLockMenuIndexPinLock) && (event->type == InputTypeShort)) { - lock_menu->callback(DesktopLockMenuEventPinLock, lock_menu->context); + if((idx == DesktopLockMenuIndexLock)) { + if((pin_is_set) && (event->type == InputTypeShort)) { + lock_menu->callback(DesktopLockMenuEventPinLock, lock_menu->context); + } else if((pin_is_set == false) && (event->type == InputTypeShort)) { + lock_menu->callback(DesktopLockMenuEventLock, lock_menu->context); + } + } else if(idx == DesktopLockMenuIndexStealth) { + if((stealth_mode == false) && (event->type == InputTypeShort)) { + lock_menu->callback(DesktopLockMenuEventStealthModeOn, lock_menu->context); + } else if((stealth_mode == true) && (event->type == InputTypeShort)) { + lock_menu->callback(DesktopLockMenuEventStealthModeOff, lock_menu->context); + } } else if(idx == DesktopLockMenuIndexDummy) { if((dummy_mode == false) && (event->type == InputTypeShort)) { lock_menu->callback(DesktopLockMenuEventDummyModeOn, lock_menu->context); diff --git a/applications/services/desktop/views/desktop_view_lock_menu.h b/applications/services/desktop/views/desktop_view_lock_menu.h index 812aa9f99e8..03ce6fa8084 100644 --- a/applications/services/desktop/views/desktop_view_lock_menu.h +++ b/applications/services/desktop/views/desktop_view_lock_menu.h @@ -19,6 +19,7 @@ typedef struct { uint8_t idx; bool pin_is_set; bool dummy_mode; + bool stealth_mode; } DesktopLockMenuViewModel; void desktop_lock_menu_set_callback( @@ -29,6 +30,7 @@ void desktop_lock_menu_set_callback( View* desktop_lock_menu_get_view(DesktopLockMenuView* lock_menu); void desktop_lock_menu_set_pin_state(DesktopLockMenuView* lock_menu, bool pin_is_set); void desktop_lock_menu_set_dummy_mode_state(DesktopLockMenuView* lock_menu, bool dummy_mode); +void desktop_lock_menu_set_stealth_mode_state(DesktopLockMenuView* lock_menu, bool stealth_mode); void desktop_lock_menu_set_idx(DesktopLockMenuView* lock_menu, uint8_t idx); DesktopLockMenuView* desktop_lock_menu_alloc(); void desktop_lock_menu_free(DesktopLockMenuView* lock_menu); diff --git a/applications/services/notification/notification_app.c b/applications/services/notification/notification_app.c index 2e170f54766..f91a73f321d 100644 --- a/applications/services/notification/notification_app.c +++ b/applications/services/notification/notification_app.c @@ -20,9 +20,9 @@ static const uint8_t reset_sound_mask = 1 << 4; static const uint8_t reset_display_mask = 1 << 5; static const uint8_t reset_blink_mask = 1 << 6; -void notification_vibro_on(); +void notification_vibro_on(bool force); void notification_vibro_off(); -void notification_sound_on(float freq, float volume); +void notification_sound_on(float freq, float volume, bool force); void notification_sound_off(); uint8_t notification_settings_get_display_brightness(NotificationApp* app, uint8_t value); @@ -141,17 +141,21 @@ uint32_t notification_settings_display_off_delay_ticks(NotificationApp* app) { } // generics -void notification_vibro_on() { - furi_hal_vibro_on(true); +void notification_vibro_on(bool force) { + if(!furi_hal_rtc_is_flag_set(FuriHalRtcFlagStealthMode) || force) { + furi_hal_vibro_on(true); + } } void notification_vibro_off() { furi_hal_vibro_on(false); } -void notification_sound_on(float freq, float volume) { - if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(30)) { - furi_hal_speaker_start(freq, volume); +void notification_sound_on(float freq, float volume, bool force) { + if(!furi_hal_rtc_is_flag_set(FuriHalRtcFlagStealthMode) || force) { + if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(30)) { + furi_hal_speaker_start(freq, volume); + } } } @@ -174,6 +178,8 @@ void notification_process_notification_message( NotificationApp* app, NotificationAppMessage* message) { uint32_t notification_message_index = 0; + bool force_volume = false; + bool force_vibro = false; const NotificationMessage* notification_message; notification_message = (*message->sequence)[notification_message_index]; @@ -269,7 +275,7 @@ void notification_process_notification_message( break; case NotificationMessageTypeVibro: if(notification_message->data.vibro.on) { - if(vibro_setting) notification_vibro_on(); + if(vibro_setting) notification_vibro_on(force_vibro); } else { notification_vibro_off(); } @@ -278,7 +284,8 @@ void notification_process_notification_message( case NotificationMessageTypeSoundOn: notification_sound_on( notification_message->data.sound.frequency, - notification_message->data.sound.volume * speaker_volume_setting); + notification_message->data.sound.volume * speaker_volume_setting, + force_volume); reset_mask |= reset_sound_mask; break; case NotificationMessageTypeSoundOff: @@ -307,9 +314,11 @@ void notification_process_notification_message( break; case NotificationMessageTypeForceSpeakerVolumeSetting: speaker_volume_setting = notification_message->data.forced_settings.speaker_volume; + force_volume = true; break; case NotificationMessageTypeForceVibroSetting: vibro_setting = notification_message->data.forced_settings.vibro; + force_vibro = true; break; case NotificationMessageTypeForceDisplayBrightnessSetting: display_brightness_setting = diff --git a/applications/settings/notification_settings/notification_settings_app.c b/applications/settings/notification_settings/notification_settings_app.c index d462163ad7e..8efbc5e0844 100644 --- a/applications/settings/notification_settings/notification_settings_app.c +++ b/applications/settings/notification_settings/notification_settings_app.c @@ -157,18 +157,33 @@ static NotificationAppSettings* alloc_settings() { variable_item_set_current_value_index(item, value_index); variable_item_set_current_value_text(item, backlight_text[value_index]); - item = variable_item_list_add( - app->variable_item_list, "Volume", VOLUME_COUNT, volume_changed, app); - value_index = - value_index_float(app->notification->settings.speaker_volume, volume_value, VOLUME_COUNT); - variable_item_set_current_value_index(item, value_index); - variable_item_set_current_value_text(item, volume_text[value_index]); - - item = - variable_item_list_add(app->variable_item_list, "Vibro", VIBRO_COUNT, vibro_changed, app); - value_index = value_index_bool(app->notification->settings.vibro_on, vibro_value, VIBRO_COUNT); - variable_item_set_current_value_index(item, value_index); - variable_item_set_current_value_text(item, vibro_text[value_index]); + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagStealthMode)) { + item = variable_item_list_add(app->variable_item_list, "Volume", 1, NULL, app); + value_index = 0; + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, "Stealth"); + } else { + item = variable_item_list_add( + app->variable_item_list, "Volume", VOLUME_COUNT, volume_changed, app); + value_index = value_index_float( + app->notification->settings.speaker_volume, volume_value, VOLUME_COUNT); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, volume_text[value_index]); + } + + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagStealthMode)) { + item = variable_item_list_add(app->variable_item_list, "Vibro", 1, NULL, app); + value_index = 0; + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, "Stealth"); + } else { + item = variable_item_list_add( + app->variable_item_list, "Vibro", VIBRO_COUNT, vibro_changed, app); + value_index = + value_index_bool(app->notification->settings.vibro_on, vibro_value, VIBRO_COUNT); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, vibro_text[value_index]); + } app->view_dispatcher = view_dispatcher_alloc(); view_dispatcher_enable_queue(app->view_dispatcher); diff --git a/assets/icons/StatusBar/Muted_8x8.png b/assets/icons/StatusBar/Muted_8x8.png new file mode 100644 index 0000000000000000000000000000000000000000..fee4e09f5e68ce3fd468d2aae8ffea79a1fcd74a GIT binary patch literal 3626 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 Date: Wed, 19 Apr 2023 12:47:01 +0300 Subject: [PATCH 522/824] [FL-3089] Raw RFID documentation (#2592) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- documentation/LFRFIDRaw.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 documentation/LFRFIDRaw.md diff --git a/documentation/LFRFIDRaw.md b/documentation/LFRFIDRaw.md new file mode 100644 index 00000000000..5a8cbde60d7 --- /dev/null +++ b/documentation/LFRFIDRaw.md @@ -0,0 +1,23 @@ +# Reading RAW RFID data + +Flipper Zero has the option to read RAW data from 125 kHz cards that allows you to record the card's data and save it, similar to how a dictaphone records sound. + +To use this function, you need to activate the Debug mode on your Flipper Zero by doing the following: + +1. Go to **Main Menu** → **Settings** → **System**. + +2. Set **Debug** to **ON**. + +Once the Debug mode is activated on your Flipper Zero, you can read RAW data from 125 kHz RFID cards: + +1. Go to **Main Menu** → **125 kHz RFID** → **Extra Actions**. + +2. Select **RAW RFID** data and name the raw file. + +3. Read instructions and press **OK**. + +4. Apply the card to Flipper Zero's back. + +5. Once the reading is finished, press **OK**. + +Two files with data (with ASK and PSK modulations) will be saved in the `lfrfid` folder on the microSD card. Now, you can share it and the card's photo with developers by creating an issue on GitHub. From 39325036607d16effddfb5016cb7ffbd5b38ac49 Mon Sep 17 00:00:00 2001 From: hedger Date: Wed, 19 Apr 2023 15:08:13 +0400 Subject: [PATCH 523/824] [FL-3243] github: testing SDK with ufbt action (#2581) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * github: testing SDK with ufbt action * github: also build apps with ufbt * github: fixed dir lookup for ufbt * ufbt: checks for compatibility on app discovery * github: Conditional app skip for ufbt * github: fixed app build flow with ufbt * extra debug * github: lint: message capture * github: testing different output capture method for linters * shorter version of status check * github: updated comment actions to suppress warnings * Reverted formatting changes Co-authored-by: あく --- .github/workflows/build.yml | 44 ++++++++++++++++--- .../workflows/lint_and_submodule_check.yml | 34 +++++++++++--- scripts/ufbt/SConstruct | 14 ++++++ 3 files changed, 82 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5f6f50a9d0d..dfeb8d83f28 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -139,7 +139,7 @@ jobs: - name: 'Find Previous Comment' if: ${{ !github.event.pull_request.head.repo.fork && github.event.pull_request }} - uses: peter-evans/find-comment@v1 + uses: peter-evans/find-comment@v2 id: fc with: issue-number: ${{ github.event.pull_request.number }} @@ -148,7 +148,7 @@ jobs: - name: 'Create or update comment' if: ${{ !github.event.pull_request.head.repo.fork && github.event.pull_request}} - uses: peter-evans/create-or-update-comment@v1 + uses: peter-evans/create-or-update-comment@v3 with: comment-id: ${{ steps.fc.outputs.comment-id }} issue-number: ${{ github.event.pull_request.number }} @@ -162,6 +162,9 @@ jobs: compact: if: ${{ !startsWith(github.ref, 'refs/tags') }} runs-on: [self-hosted,FlipperZeroShell] + strategy: + matrix: + target: [f7, f18] steps: - name: 'Wipe workspace' run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; @@ -185,9 +188,40 @@ jobs: python3 scripts/get_env.py "--event_file=${{ github.event_path }}" "--type=$TYPE" || cat "${{ github.event_path }}" - name: 'Build the firmware' + id: build-fw run: | set -e - for TARGET in ${TARGETS}; do - TARGET="$(echo "${TARGET}" | sed 's/f//')"; \ - ./fbt TARGET_HW=$TARGET DEBUG=0 COMPACT=1 fap_dist updater_package + TARGET="$(echo '${{ matrix.target }}' | sed 's/f//')"; \ + ./fbt TARGET_HW=$TARGET DEBUG=0 COMPACT=1 fap_dist updater_package + echo "sdk-file=$(ls dist/${{ matrix.target }}-*/flipper-z-${{ matrix.target }}-sdk-*.zip)" >> $GITHUB_OUTPUT + + - name: Deploy uFBT with SDK + uses: flipperdevices/flipperzero-ufbt-action@v0.1.0 + with: + task: setup + sdk-file: ${{ steps.build-fw.outputs.sdk-file }} + + - name: Build test app with SDK + run: | + mkdir testapp + cd testapp + ufbt create APPID=testapp + ufbt + + - name: Build example & external apps with uFBT + run: | + for appdir in 'applications/external' 'applications/examples'; do + for app in $(find "$appdir" -maxdepth 1 -mindepth 1 -type d); do + pushd $app + TARGETS_FAM=$(grep "targets" application.fam || echo "${{ matrix.target }}") + if ! grep -q "${{ matrix.target }}" <<< $TARGETS_FAM ; then + echo Skipping unsupported app: $app + popd + continue + fi + echo Building $app + ufbt + popd + done done + diff --git a/.github/workflows/lint_and_submodule_check.yml b/.github/workflows/lint_and_submodule_check.yml index cecfd12481e..999111cc95c 100644 --- a/.github/workflows/lint_and_submodule_check.yml +++ b/.github/workflows/lint_and_submodule_check.yml @@ -40,7 +40,7 @@ jobs: COMMITS_IN_BRANCH="$(git rev-list --count dev)"; if [ $COMMITS_IN_BRANCH -lt $SUB_COMMITS_MIN ]; then echo "name=fails::error" >> $GITHUB_OUTPUT; - echo "::error::Error: Too low commits in $SUB_BRANCH of submodule $SUB_PATH: $COMMITS_IN_BRANCH(expected $SUB_COMMITS_MIN+)"; + echo "::error::Error: Too few commits in $SUB_BRANCH of submodule $SUB_PATH: $COMMITS_IN_BRANCH(expected $SUB_COMMITS_MIN+)"; exit 1; fi if ! grep -q "/$SUB_BRANCH" <<< "$BRANCHES"; then @@ -51,12 +51,36 @@ jobs: - name: 'Check Python code formatting' id: syntax_check_py - run: ./fbt lint_py 2>&1 >/dev/null || echo "errors=1" >> $GITHUB_OUTPUT - + run: | + set +e; + ./fbt -s lint_py 2>&1 | tee lint-py.log; + if [ "${PIPESTATUS[0]}" -ne 0 ]; then + # Save multiline output + echo "errors=1" >> $GITHUB_OUTPUT; + printf "Python Lint errors:\n\`\`\`\n" >> $GITHUB_STEP_SUMMARY; + echo "$(cat lint-py.log)" >> $GITHUB_STEP_SUMMARY; + printf "\n\`\`\`\n" >> $GITHUB_STEP_SUMMARY; + exit 1; + else + echo "Python Lint: all good ✨" >> $GITHUB_STEP_SUMMARY; + fi + - name: 'Check C++ code formatting' - if: always() id: syntax_check_cpp - run: ./fbt lint 2>&1 >/dev/null || echo "errors=1" >> $GITHUB_OUTPUT + if: always() + run: | + set +e; + ./fbt -s lint 2>&1 | tee lint-cpp.log; + if [ "${PIPESTATUS[0]}" -ne 0 ]; then + # Save multiline output + echo "errors=1" >> $GITHUB_OUTPUT; + printf "C Lint errors:\n\`\`\`\n" >> $GITHUB_STEP_SUMMARY; + echo "$(cat lint-cpp.log)" >> $GITHUB_STEP_SUMMARY; + printf "\n\`\`\`\n" >> $GITHUB_STEP_SUMMARY; + exit 1; + else + echo "C Lint: all good ✨" >> $GITHUB_STEP_SUMMARY; + fi - name: Report code formatting errors if: ( steps.syntax_check_py.outputs.errors || steps.syntax_check_cpp.outputs.errors ) && github.event.pull_request diff --git a/scripts/ufbt/SConstruct b/scripts/ufbt/SConstruct index 7228e2f510c..ce7c8b9780a 100644 --- a/scripts/ufbt/SConstruct +++ b/scripts/ufbt/SConstruct @@ -1,6 +1,8 @@ from SCons.Platform import TempFileMunge from SCons.Node import FS from SCons.Errors import UserError +from SCons.Warnings import warn, WarningOnByDefault + import os import multiprocessing @@ -246,7 +248,12 @@ known_extapps = [ for apptype in apps_to_build_as_faps for app in appenv["APPBUILD"].get_apps_of_type(apptype, True) ] +incompatible_apps = [] for app in known_extapps: + if not app.supports_hardware_target(appenv.subst("f${TARGET_HW}")): + incompatible_apps.append(app) + continue + app_artifacts = appenv.BuildAppElf(app) app_src_dir = extract_abs_dir(app_artifacts.app._appdir) app_artifacts.installer = [ @@ -254,6 +261,13 @@ for app in known_extapps: appenv.Install(app_src_dir.Dir("dist").Dir("debug"), app_artifacts.debug), ] +if len(incompatible_apps): + print( + "WARNING: The following apps are not compatible with the current target hardware and will not be built: {}".format( + ", ".join([app.name for app in incompatible_apps]) + ) + ) + if appenv["FORCE"]: appenv.AlwaysBuild([extapp.compact for extapp in apps_artifacts.values()]) From 4d015a1106f2577b47c66c5dd7edcccc58caa2e5 Mon Sep 17 00:00:00 2001 From: hedger Date: Thu, 20 Apr 2023 16:57:51 +0400 Subject: [PATCH 524/824] [FL-3271] cubewb: updated to v1.16.0 (#2595) * cubewb: updated project to v1.16.0 * hal: updated api_symbols for f18 * FuriHal: add missing enterprise sleep and insomnia * FuriHal: slightly more paranoic sleep mode Co-authored-by: Aleksandr Kutuzov --- fbt_options.py | 2 +- firmware/targets/f18/api_symbols.csv | 40 +++++++-------- firmware/targets/f7/api_symbols.csv | 38 +++++++------- firmware/targets/f7/ble_glue/app_common.h | 1 + firmware/targets/f7/ble_glue/app_conf.h | 2 + firmware/targets/f7/ble_glue/app_debug.c | 3 +- firmware/targets/f7/ble_glue/ble_app.c | 10 +++- firmware/targets/f7/ble_glue/ble_const.h | 4 ++ firmware/targets/f7/ble_glue/ble_glue.c | 2 + firmware/targets/f7/ble_glue/compiler.h | 10 +++- firmware/targets/f7/ble_glue/gap.c | 51 +++++++++---------- firmware/targets/f7/furi_hal/furi_hal_flash.c | 3 ++ firmware/targets/f7/furi_hal/furi_hal_power.c | 29 +++++++++-- lib/STM32CubeWB | 2 +- scripts/ob.data | 4 +- 15 files changed, 124 insertions(+), 77 deletions(-) diff --git a/fbt_options.py b/fbt_options.py index a10c64b96ab..25e84afde57 100644 --- a/fbt_options.py +++ b/fbt_options.py @@ -20,7 +20,7 @@ COPRO_OB_DATA = "scripts/ob.data" # Must match lib/STM32CubeWB version -COPRO_CUBE_VERSION = "1.13.3" +COPRO_CUBE_VERSION = "1.16.0" COPRO_CUBE_DIR = "lib/STM32CubeWB" diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index a37ef02cc60..4ee3d8636b3 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -176,17 +176,17 @@ Header,+,lib/toolbox/tar/tar_archive.h,, Header,+,lib/toolbox/value_index.h,, Header,+,lib/toolbox/version.h,, Function,-,LL_ADC_CommonDeInit,ErrorStatus,ADC_Common_TypeDef* -Function,-,LL_ADC_CommonInit,ErrorStatus,"ADC_Common_TypeDef*, LL_ADC_CommonInitTypeDef*" +Function,-,LL_ADC_CommonInit,ErrorStatus,"ADC_Common_TypeDef*, const LL_ADC_CommonInitTypeDef*" Function,-,LL_ADC_CommonStructInit,void,LL_ADC_CommonInitTypeDef* Function,-,LL_ADC_DeInit,ErrorStatus,ADC_TypeDef* -Function,-,LL_ADC_INJ_Init,ErrorStatus,"ADC_TypeDef*, LL_ADC_INJ_InitTypeDef*" +Function,-,LL_ADC_INJ_Init,ErrorStatus,"ADC_TypeDef*, const LL_ADC_INJ_InitTypeDef*" Function,-,LL_ADC_INJ_StructInit,void,LL_ADC_INJ_InitTypeDef* -Function,-,LL_ADC_Init,ErrorStatus,"ADC_TypeDef*, LL_ADC_InitTypeDef*" -Function,-,LL_ADC_REG_Init,ErrorStatus,"ADC_TypeDef*, LL_ADC_REG_InitTypeDef*" +Function,-,LL_ADC_Init,ErrorStatus,"ADC_TypeDef*, const LL_ADC_InitTypeDef*" +Function,-,LL_ADC_REG_Init,ErrorStatus,"ADC_TypeDef*, const LL_ADC_REG_InitTypeDef*" Function,-,LL_ADC_REG_StructInit,void,LL_ADC_REG_InitTypeDef* Function,-,LL_ADC_StructInit,void,LL_ADC_InitTypeDef* Function,-,LL_COMP_DeInit,ErrorStatus,COMP_TypeDef* -Function,+,LL_COMP_Init,ErrorStatus,"COMP_TypeDef*, LL_COMP_InitTypeDef*" +Function,+,LL_COMP_Init,ErrorStatus,"COMP_TypeDef*, const LL_COMP_InitTypeDef*" Function,-,LL_COMP_StructInit,void,LL_COMP_InitTypeDef* Function,-,LL_CRC_DeInit,ErrorStatus,CRC_TypeDef* Function,-,LL_CRS_DeInit,ErrorStatus, @@ -199,16 +199,16 @@ Function,-,LL_EXTI_StructInit,void,LL_EXTI_InitTypeDef* Function,-,LL_GPIO_DeInit,ErrorStatus,GPIO_TypeDef* Function,+,LL_GPIO_Init,ErrorStatus,"GPIO_TypeDef*, LL_GPIO_InitTypeDef*" Function,-,LL_GPIO_StructInit,void,LL_GPIO_InitTypeDef* -Function,-,LL_I2C_DeInit,ErrorStatus,I2C_TypeDef* -Function,+,LL_I2C_Init,ErrorStatus,"I2C_TypeDef*, LL_I2C_InitTypeDef*" +Function,-,LL_I2C_DeInit,ErrorStatus,const I2C_TypeDef* +Function,+,LL_I2C_Init,ErrorStatus,"I2C_TypeDef*, const LL_I2C_InitTypeDef*" Function,-,LL_I2C_StructInit,void,LL_I2C_InitTypeDef* Function,-,LL_Init1msTick,void,uint32_t Function,+,LL_LPTIM_DeInit,ErrorStatus,LPTIM_TypeDef* Function,-,LL_LPTIM_Disable,void,LPTIM_TypeDef* -Function,+,LL_LPTIM_Init,ErrorStatus,"LPTIM_TypeDef*, LL_LPTIM_InitTypeDef*" +Function,+,LL_LPTIM_Init,ErrorStatus,"LPTIM_TypeDef*, const LL_LPTIM_InitTypeDef*" Function,-,LL_LPTIM_StructInit,void,LL_LPTIM_InitTypeDef* -Function,-,LL_LPUART_DeInit,ErrorStatus,USART_TypeDef* -Function,+,LL_LPUART_Init,ErrorStatus,"USART_TypeDef*, LL_LPUART_InitTypeDef*" +Function,-,LL_LPUART_DeInit,ErrorStatus,const USART_TypeDef* +Function,+,LL_LPUART_Init,ErrorStatus,"USART_TypeDef*, const LL_LPUART_InitTypeDef*" Function,-,LL_LPUART_StructInit,void,LL_LPUART_InitTypeDef* Function,-,LL_PKA_DeInit,ErrorStatus,PKA_TypeDef* Function,-,LL_PKA_Init,ErrorStatus,"PKA_TypeDef*, LL_PKA_InitTypeDef*" @@ -253,23 +253,23 @@ Function,+,LL_SPI_Init,ErrorStatus,"SPI_TypeDef*, LL_SPI_InitTypeDef*" Function,-,LL_SPI_StructInit,void,LL_SPI_InitTypeDef* Function,-,LL_SetFlashLatency,ErrorStatus,uint32_t Function,+,LL_SetSystemCoreClock,void,uint32_t -Function,-,LL_TIM_BDTR_Init,ErrorStatus,"TIM_TypeDef*, LL_TIM_BDTR_InitTypeDef*" +Function,-,LL_TIM_BDTR_Init,ErrorStatus,"TIM_TypeDef*, const LL_TIM_BDTR_InitTypeDef*" Function,-,LL_TIM_BDTR_StructInit,void,LL_TIM_BDTR_InitTypeDef* -Function,+,LL_TIM_DeInit,ErrorStatus,TIM_TypeDef* -Function,-,LL_TIM_ENCODER_Init,ErrorStatus,"TIM_TypeDef*, LL_TIM_ENCODER_InitTypeDef*" +Function,-,LL_TIM_DeInit,ErrorStatus,TIM_TypeDef* +Function,-,LL_TIM_ENCODER_Init,ErrorStatus,"TIM_TypeDef*, const LL_TIM_ENCODER_InitTypeDef*" Function,-,LL_TIM_ENCODER_StructInit,void,LL_TIM_ENCODER_InitTypeDef* -Function,-,LL_TIM_HALLSENSOR_Init,ErrorStatus,"TIM_TypeDef*, LL_TIM_HALLSENSOR_InitTypeDef*" +Function,-,LL_TIM_HALLSENSOR_Init,ErrorStatus,"TIM_TypeDef*, const LL_TIM_HALLSENSOR_InitTypeDef*" Function,-,LL_TIM_HALLSENSOR_StructInit,void,LL_TIM_HALLSENSOR_InitTypeDef* -Function,-,LL_TIM_IC_Init,ErrorStatus,"TIM_TypeDef*, uint32_t, LL_TIM_IC_InitTypeDef*" +Function,-,LL_TIM_IC_Init,ErrorStatus,"TIM_TypeDef*, uint32_t, const LL_TIM_IC_InitTypeDef*" Function,-,LL_TIM_IC_StructInit,void,LL_TIM_IC_InitTypeDef* -Function,+,LL_TIM_Init,ErrorStatus,"TIM_TypeDef*, LL_TIM_InitTypeDef*" -Function,+,LL_TIM_OC_Init,ErrorStatus,"TIM_TypeDef*, uint32_t, LL_TIM_OC_InitTypeDef*" +Function,+,LL_TIM_Init,ErrorStatus,"TIM_TypeDef*, const LL_TIM_InitTypeDef*" +Function,+,LL_TIM_OC_Init,ErrorStatus,"TIM_TypeDef*, uint32_t, const LL_TIM_OC_InitTypeDef*" Function,-,LL_TIM_OC_StructInit,void,LL_TIM_OC_InitTypeDef* Function,-,LL_TIM_StructInit,void,LL_TIM_InitTypeDef* -Function,-,LL_USART_ClockInit,ErrorStatus,"USART_TypeDef*, LL_USART_ClockInitTypeDef*" +Function,-,LL_USART_ClockInit,ErrorStatus,"USART_TypeDef*, const LL_USART_ClockInitTypeDef*" Function,-,LL_USART_ClockStructInit,void,LL_USART_ClockInitTypeDef* -Function,-,LL_USART_DeInit,ErrorStatus,USART_TypeDef* -Function,+,LL_USART_Init,ErrorStatus,"USART_TypeDef*, LL_USART_InitTypeDef*" +Function,-,LL_USART_DeInit,ErrorStatus,const USART_TypeDef* +Function,+,LL_USART_Init,ErrorStatus,"USART_TypeDef*, const LL_USART_InitTypeDef*" Function,-,LL_USART_StructInit,void,LL_USART_InitTypeDef* Function,-,LL_mDelay,void,uint32_t Function,-,SystemCoreClockUpdate,void, diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 43ede425e12..a2d70e7f86e 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -208,17 +208,17 @@ Header,+,lib/toolbox/tar/tar_archive.h,, Header,+,lib/toolbox/value_index.h,, Header,+,lib/toolbox/version.h,, Function,-,LL_ADC_CommonDeInit,ErrorStatus,ADC_Common_TypeDef* -Function,-,LL_ADC_CommonInit,ErrorStatus,"ADC_Common_TypeDef*, LL_ADC_CommonInitTypeDef*" +Function,-,LL_ADC_CommonInit,ErrorStatus,"ADC_Common_TypeDef*, const LL_ADC_CommonInitTypeDef*" Function,-,LL_ADC_CommonStructInit,void,LL_ADC_CommonInitTypeDef* Function,-,LL_ADC_DeInit,ErrorStatus,ADC_TypeDef* -Function,-,LL_ADC_INJ_Init,ErrorStatus,"ADC_TypeDef*, LL_ADC_INJ_InitTypeDef*" +Function,-,LL_ADC_INJ_Init,ErrorStatus,"ADC_TypeDef*, const LL_ADC_INJ_InitTypeDef*" Function,-,LL_ADC_INJ_StructInit,void,LL_ADC_INJ_InitTypeDef* -Function,-,LL_ADC_Init,ErrorStatus,"ADC_TypeDef*, LL_ADC_InitTypeDef*" -Function,-,LL_ADC_REG_Init,ErrorStatus,"ADC_TypeDef*, LL_ADC_REG_InitTypeDef*" +Function,-,LL_ADC_Init,ErrorStatus,"ADC_TypeDef*, const LL_ADC_InitTypeDef*" +Function,-,LL_ADC_REG_Init,ErrorStatus,"ADC_TypeDef*, const LL_ADC_REG_InitTypeDef*" Function,-,LL_ADC_REG_StructInit,void,LL_ADC_REG_InitTypeDef* Function,-,LL_ADC_StructInit,void,LL_ADC_InitTypeDef* Function,-,LL_COMP_DeInit,ErrorStatus,COMP_TypeDef* -Function,+,LL_COMP_Init,ErrorStatus,"COMP_TypeDef*, LL_COMP_InitTypeDef*" +Function,+,LL_COMP_Init,ErrorStatus,"COMP_TypeDef*, const LL_COMP_InitTypeDef*" Function,-,LL_COMP_StructInit,void,LL_COMP_InitTypeDef* Function,-,LL_CRC_DeInit,ErrorStatus,CRC_TypeDef* Function,-,LL_CRS_DeInit,ErrorStatus, @@ -231,16 +231,16 @@ Function,-,LL_EXTI_StructInit,void,LL_EXTI_InitTypeDef* Function,-,LL_GPIO_DeInit,ErrorStatus,GPIO_TypeDef* Function,+,LL_GPIO_Init,ErrorStatus,"GPIO_TypeDef*, LL_GPIO_InitTypeDef*" Function,-,LL_GPIO_StructInit,void,LL_GPIO_InitTypeDef* -Function,-,LL_I2C_DeInit,ErrorStatus,I2C_TypeDef* -Function,+,LL_I2C_Init,ErrorStatus,"I2C_TypeDef*, LL_I2C_InitTypeDef*" +Function,-,LL_I2C_DeInit,ErrorStatus,const I2C_TypeDef* +Function,+,LL_I2C_Init,ErrorStatus,"I2C_TypeDef*, const LL_I2C_InitTypeDef*" Function,-,LL_I2C_StructInit,void,LL_I2C_InitTypeDef* Function,-,LL_Init1msTick,void,uint32_t Function,+,LL_LPTIM_DeInit,ErrorStatus,LPTIM_TypeDef* Function,-,LL_LPTIM_Disable,void,LPTIM_TypeDef* -Function,+,LL_LPTIM_Init,ErrorStatus,"LPTIM_TypeDef*, LL_LPTIM_InitTypeDef*" +Function,+,LL_LPTIM_Init,ErrorStatus,"LPTIM_TypeDef*, const LL_LPTIM_InitTypeDef*" Function,-,LL_LPTIM_StructInit,void,LL_LPTIM_InitTypeDef* -Function,-,LL_LPUART_DeInit,ErrorStatus,USART_TypeDef* -Function,+,LL_LPUART_Init,ErrorStatus,"USART_TypeDef*, LL_LPUART_InitTypeDef*" +Function,-,LL_LPUART_DeInit,ErrorStatus,const USART_TypeDef* +Function,+,LL_LPUART_Init,ErrorStatus,"USART_TypeDef*, const LL_LPUART_InitTypeDef*" Function,-,LL_LPUART_StructInit,void,LL_LPUART_InitTypeDef* Function,-,LL_PKA_DeInit,ErrorStatus,PKA_TypeDef* Function,-,LL_PKA_Init,ErrorStatus,"PKA_TypeDef*, LL_PKA_InitTypeDef*" @@ -285,23 +285,23 @@ Function,+,LL_SPI_Init,ErrorStatus,"SPI_TypeDef*, LL_SPI_InitTypeDef*" Function,-,LL_SPI_StructInit,void,LL_SPI_InitTypeDef* Function,-,LL_SetFlashLatency,ErrorStatus,uint32_t Function,+,LL_SetSystemCoreClock,void,uint32_t -Function,-,LL_TIM_BDTR_Init,ErrorStatus,"TIM_TypeDef*, LL_TIM_BDTR_InitTypeDef*" +Function,-,LL_TIM_BDTR_Init,ErrorStatus,"TIM_TypeDef*, const LL_TIM_BDTR_InitTypeDef*" Function,-,LL_TIM_BDTR_StructInit,void,LL_TIM_BDTR_InitTypeDef* Function,+,LL_TIM_DeInit,ErrorStatus,TIM_TypeDef* -Function,-,LL_TIM_ENCODER_Init,ErrorStatus,"TIM_TypeDef*, LL_TIM_ENCODER_InitTypeDef*" +Function,-,LL_TIM_ENCODER_Init,ErrorStatus,"TIM_TypeDef*, const LL_TIM_ENCODER_InitTypeDef*" Function,-,LL_TIM_ENCODER_StructInit,void,LL_TIM_ENCODER_InitTypeDef* -Function,-,LL_TIM_HALLSENSOR_Init,ErrorStatus,"TIM_TypeDef*, LL_TIM_HALLSENSOR_InitTypeDef*" +Function,-,LL_TIM_HALLSENSOR_Init,ErrorStatus,"TIM_TypeDef*, const LL_TIM_HALLSENSOR_InitTypeDef*" Function,-,LL_TIM_HALLSENSOR_StructInit,void,LL_TIM_HALLSENSOR_InitTypeDef* -Function,-,LL_TIM_IC_Init,ErrorStatus,"TIM_TypeDef*, uint32_t, LL_TIM_IC_InitTypeDef*" +Function,-,LL_TIM_IC_Init,ErrorStatus,"TIM_TypeDef*, uint32_t, const LL_TIM_IC_InitTypeDef*" Function,-,LL_TIM_IC_StructInit,void,LL_TIM_IC_InitTypeDef* -Function,+,LL_TIM_Init,ErrorStatus,"TIM_TypeDef*, LL_TIM_InitTypeDef*" -Function,+,LL_TIM_OC_Init,ErrorStatus,"TIM_TypeDef*, uint32_t, LL_TIM_OC_InitTypeDef*" +Function,+,LL_TIM_Init,ErrorStatus,"TIM_TypeDef*, const LL_TIM_InitTypeDef*" +Function,+,LL_TIM_OC_Init,ErrorStatus,"TIM_TypeDef*, uint32_t, const LL_TIM_OC_InitTypeDef*" Function,-,LL_TIM_OC_StructInit,void,LL_TIM_OC_InitTypeDef* Function,-,LL_TIM_StructInit,void,LL_TIM_InitTypeDef* -Function,-,LL_USART_ClockInit,ErrorStatus,"USART_TypeDef*, LL_USART_ClockInitTypeDef*" +Function,-,LL_USART_ClockInit,ErrorStatus,"USART_TypeDef*, const LL_USART_ClockInitTypeDef*" Function,-,LL_USART_ClockStructInit,void,LL_USART_ClockInitTypeDef* -Function,-,LL_USART_DeInit,ErrorStatus,USART_TypeDef* -Function,+,LL_USART_Init,ErrorStatus,"USART_TypeDef*, LL_USART_InitTypeDef*" +Function,-,LL_USART_DeInit,ErrorStatus,const USART_TypeDef* +Function,+,LL_USART_Init,ErrorStatus,"USART_TypeDef*, const LL_USART_InitTypeDef*" Function,-,LL_USART_StructInit,void,LL_USART_InitTypeDef* Function,-,LL_mDelay,void,uint32_t Function,-,SystemCoreClockUpdate,void, diff --git a/firmware/targets/f7/ble_glue/app_common.h b/firmware/targets/f7/ble_glue/app_common.h index 214c85acd23..8eaf2308593 100644 --- a/firmware/targets/f7/ble_glue/app_common.h +++ b/firmware/targets/f7/ble_glue/app_common.h @@ -33,6 +33,7 @@ extern "C" { #include #include +#include #include "app_conf.h" diff --git a/firmware/targets/f7/ble_glue/app_conf.h b/firmware/targets/f7/ble_glue/app_conf.h index aaa755a3622..ee5115cfed3 100644 --- a/firmware/targets/f7/ble_glue/app_conf.h +++ b/firmware/targets/f7/ble_glue/app_conf.h @@ -8,6 +8,8 @@ #define CFG_TX_POWER (0x19) /* +0dBm */ +#define CFG_IDENTITY_ADDRESS GAP_PUBLIC_ADDR + /** * Define Advertising parameters */ diff --git a/firmware/targets/f7/ble_glue/app_debug.c b/firmware/targets/f7/ble_glue/app_debug.c index 78e789ac307..b443bee21f0 100644 --- a/firmware/targets/f7/ble_glue/app_debug.c +++ b/firmware/targets/f7/ble_glue/app_debug.c @@ -33,7 +33,8 @@ PLACE_IN_SECTION("MB_MEM2") ALIGN(4) static SHCI_C2_DEBUG_TracesConfig_t APPD_TracesConfig = {0, 0, 0, 0}; PLACE_IN_SECTION("MB_MEM2") ALIGN(4) -static SHCI_C2_DEBUG_GeneralConfig_t APPD_GeneralConfig = {BLE_DTB_CFG, SYS_DBG_CFG1, {0, 0}}; +static SHCI_C2_DEBUG_GeneralConfig_t APPD_GeneralConfig = + {BLE_DTB_CFG, SYS_DBG_CFG1, {0, 0}, 0, 0, 0, 0, 0}; /** * THE DEBUG ON GPIO FOR CPU2 IS INTENDED TO BE USED ONLY ON REQUEST FROM ST SUPPORT diff --git a/firmware/targets/f7/ble_glue/ble_app.c b/firmware/targets/f7/ble_glue/ble_app.c index 4fc4d521be1..cc6065b9755 100644 --- a/firmware/targets/f7/ble_glue/ble_app.c +++ b/firmware/targets/f7/ble_glue/ble_app.c @@ -18,8 +18,8 @@ PLACE_IN_SECTION("MB_MEM1") ALIGN(4) static TL_CmdPacket_t ble_app_cmd_buffer; PLACE_IN_SECTION("MB_MEM2") ALIGN(4) static uint32_t ble_app_nvm[BLE_NVM_SRAM_SIZE]; _Static_assert( - sizeof(SHCI_C2_Ble_Init_Cmd_Packet_t) == 49, - "Ble stack config structure size mismatch"); + sizeof(SHCI_C2_Ble_Init_Cmd_Packet_t) == 58, + "Ble stack config structure size mismatch (check new config options - last updated for v.1.16.0)"); typedef struct { FuriMutex* hci_mtx; @@ -88,6 +88,12 @@ bool ble_app_init() { .min_tx_power = 0, .max_tx_power = 0, .rx_model_config = 1, + /* New stack (13.3->16.0)*/ + .max_adv_set_nbr = 1, // Only used if SHCI_C2_BLE_INIT_OPTIONS_EXT_ADV is set + .max_adv_data_len = 31, // Only used if SHCI_C2_BLE_INIT_OPTIONS_EXT_ADV is set + .tx_path_compens = 0, // RF TX Path Compensation, * 0.1 dB + .rx_path_compens = 0, // RF RX Path Compensation, * 0.1 dB + .ble_core_version = 11, // BLE Core Version: 11(5.2), 12(5.3) }}; status = SHCI_C2_BLE_Init(&ble_init_cmd_packet); if(status) { diff --git a/firmware/targets/f7/ble_glue/ble_const.h b/firmware/targets/f7/ble_glue/ble_const.h index 0e4c8b398d9..85f734b62c2 100644 --- a/firmware/targets/f7/ble_glue/ble_const.h +++ b/firmware/targets/f7/ble_glue/ble_const.h @@ -23,6 +23,7 @@ #include #include #include "osal.h" +#include "compiler.h" /* Default BLE variant */ #ifndef BASIC_FEATURES @@ -34,6 +35,9 @@ #ifndef LL_ONLY #define LL_ONLY 0 #endif +#ifndef LL_ONLY_BASIC +#define LL_ONLY_BASIC 0 +#endif #ifndef BEACON_ONLY #define BEACON_ONLY 0 #endif diff --git a/firmware/targets/f7/ble_glue/ble_glue.c b/firmware/targets/f7/ble_glue/ble_glue.c index a2f2f1a94b0..c73bbd86601 100644 --- a/firmware/targets/f7/ble_glue/ble_glue.c +++ b/firmware/targets/f7/ble_glue/ble_glue.c @@ -403,7 +403,9 @@ void shci_cmd_resp_release(uint32_t flag) { void shci_cmd_resp_wait(uint32_t timeout) { UNUSED(timeout); if(ble_glue) { + furi_hal_power_insomnia_enter(); furi_semaphore_acquire(ble_glue->shci_sem, FuriWaitForever); + furi_hal_power_insomnia_exit(); } } diff --git a/firmware/targets/f7/ble_glue/compiler.h b/firmware/targets/f7/ble_glue/compiler.h index 1c39628197d..98a93d71264 100644 --- a/firmware/targets/f7/ble_glue/compiler.h +++ b/firmware/targets/f7/ble_glue/compiler.h @@ -5,7 +5,7 @@ ***************************************************************************** * @attention * - * Copyright (c) 2018-2022 STMicroelectronics. + * Copyright (c) 2018-2023 STMicroelectronics. * All rights reserved. * * This software is licensed under terms that can be found in the LICENSE file @@ -18,6 +18,14 @@ #ifndef COMPILER_H__ #define COMPILER_H__ +#ifndef __PACKED_STRUCT +#define __PACKED_STRUCT PACKED(struct) +#endif + +#ifndef __PACKED_UNION +#define __PACKED_UNION PACKED(union) +#endif + /** * @brief This is the section dedicated to IAR toolchain */ diff --git a/firmware/targets/f7/ble_glue/gap.c b/firmware/targets/f7/ble_glue/gap.c index cbb2a203bd1..f0a9ced3cb7 100644 --- a/firmware/targets/f7/ble_glue/gap.c +++ b/firmware/targets/f7/ble_glue/gap.c @@ -1,5 +1,6 @@ #include "gap.h" +#include "app_common.h" #include #include @@ -85,7 +86,7 @@ static void gap_verify_connection_parameters(Gap* gap) { SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification(void* pckt) { hci_event_pckt* event_pckt; evt_le_meta_event* meta_evt; - evt_blue_aci* blue_evt; + evt_blecore_aci* blue_evt; hci_le_phy_update_complete_event_rp0* evt_le_phy_update_complete; uint8_t tx_phy; uint8_t rx_phy; @@ -97,7 +98,7 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification(void* pckt) { furi_mutex_acquire(gap->state_mutex, FuriWaitForever); } switch(event_pckt->evt) { - case EVT_DISCONN_COMPLETE: { + case HCI_DISCONNECTION_COMPLETE_EVT_CODE: { hci_disconnection_complete_event_rp0* disconnection_complete_event = (hci_disconnection_complete_event_rp0*)event_pckt->data; if(disconnection_complete_event->Connection_Handle == gap->service.connection_handle) { @@ -106,6 +107,8 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification(void* pckt) { FURI_LOG_I( TAG, "Disconnect from client. Reason: %02X", disconnection_complete_event->Reason); } + // Enterprise sleep + furi_delay_us(666 + 666); if(gap->enable_adv) { // Restart advertising gap_advertise_start(GapStateAdvFast); @@ -114,10 +117,10 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification(void* pckt) { gap->on_event_cb(event, gap->context); } break; - case EVT_LE_META_EVENT: + case HCI_LE_META_EVT_CODE: meta_evt = (evt_le_meta_event*)event_pckt->data; switch(meta_evt->subevent) { - case EVT_LE_CONN_UPDATE_COMPLETE: { + case HCI_LE_CONNECTION_UPDATE_COMPLETE_SUBEVT_CODE: { hci_le_connection_update_complete_event_rp0* event = (hci_le_connection_update_complete_event_rp0*)meta_evt->data; gap->connection_params.conn_interval = event->Conn_Interval; @@ -128,7 +131,7 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification(void* pckt) { break; } - case EVT_LE_PHY_UPDATE_COMPLETE: + case HCI_LE_PHY_UPDATE_COMPLETE_SUBEVT_CODE: evt_le_phy_update_complete = (hci_le_phy_update_complete_event_rp0*)meta_evt->data; if(evt_le_phy_update_complete->Status) { FURI_LOG_E( @@ -144,7 +147,7 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification(void* pckt) { } break; - case EVT_LE_CONN_COMPLETE: { + case HCI_LE_CONNECTION_COMPLETE_SUBEVT_CODE: { hci_le_connection_complete_event_rp0* event = (hci_le_connection_complete_event_rp0*)meta_evt->data; gap->connection_params.conn_interval = event->Conn_Interval; @@ -168,16 +171,16 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification(void* pckt) { } break; - case EVT_VENDOR: - blue_evt = (evt_blue_aci*)event_pckt->data; + case HCI_VENDOR_SPECIFIC_DEBUG_EVT_CODE: + blue_evt = (evt_blecore_aci*)event_pckt->data; switch(blue_evt->ecode) { aci_gap_pairing_complete_event_rp0* pairing_complete; - case EVT_BLUE_GAP_LIMITED_DISCOVERABLE: + case ACI_GAP_LIMITED_DISCOVERABLE_VSEVT_CODE: FURI_LOG_I(TAG, "Limited discoverable event"); break; - case EVT_BLUE_GAP_PASS_KEY_REQUEST: { + case ACI_GAP_PASS_KEY_REQ_VSEVT_CODE: { // Generate random PIN code uint32_t pin = rand() % 999999; //-V1064 aci_gap_pass_key_resp(gap->service.connection_handle, pin); @@ -190,7 +193,7 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification(void* pckt) { gap->on_event_cb(event, gap->context); } break; - case EVT_BLUE_ATT_EXCHANGE_MTU_RESP: { + case ACI_ATT_EXCHANGE_MTU_RESP_VSEVT_CODE: { aci_att_exchange_mtu_resp_event_rp0* pr = (void*)blue_evt->data; FURI_LOG_I(TAG, "Rx MTU size: %d", pr->Server_RX_MTU); // Set maximum packet size given header size is 3 bytes @@ -199,32 +202,28 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification(void* pckt) { gap->on_event_cb(event, gap->context); } break; - case EVT_BLUE_GAP_AUTHORIZATION_REQUEST: + case ACI_GAP_AUTHORIZATION_REQ_VSEVT_CODE: FURI_LOG_D(TAG, "Authorization request event"); break; - case EVT_BLUE_GAP_SLAVE_SECURITY_INITIATED: + case ACI_GAP_SLAVE_SECURITY_INITIATED_VSEVT_CODE: FURI_LOG_D(TAG, "Slave security initiated"); break; - case EVT_BLUE_GAP_BOND_LOST: + case ACI_GAP_BOND_LOST_VSEVT_CODE: FURI_LOG_D(TAG, "Bond lost event. Start rebonding"); aci_gap_allow_rebond(gap->service.connection_handle); break; - case EVT_BLUE_GAP_DEVICE_FOUND: - FURI_LOG_D(TAG, "Device found event"); - break; - - case EVT_BLUE_GAP_ADDR_NOT_RESOLVED: + case ACI_GAP_ADDR_NOT_RESOLVED_VSEVT_CODE: FURI_LOG_D(TAG, "Address not resolved event"); break; - case EVT_BLUE_GAP_KEYPRESS_NOTIFICATION: + case ACI_GAP_KEYPRESS_NOTIFICATION_VSEVT_CODE: FURI_LOG_D(TAG, "Key press notification event"); break; - case EVT_BLUE_GAP_NUMERIC_COMPARISON_VALUE: { + case ACI_GAP_NUMERIC_COMPARISON_VALUE_VSEVT_CODE: { uint32_t pin = ((aci_gap_numeric_comparison_value_event_rp0*)(blue_evt->data))->Numeric_Value; FURI_LOG_I(TAG, "Verify numeric comparison: %06lu", pin); @@ -234,7 +233,7 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification(void* pckt) { break; } - case EVT_BLUE_GAP_PAIRING_CMPLT: + case ACI_GAP_PAIRING_COMPLETE_VSEVT_CODE: pairing_complete = (aci_gap_pairing_complete_event_rp0*)blue_evt->data; if(pairing_complete->Status) { FURI_LOG_E( @@ -249,11 +248,11 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification(void* pckt) { } break; - case EVT_BLUE_GAP_PROCEDURE_COMPLETE: + case ACI_L2CAP_CONNECTION_UPDATE_RESP_VSEVT_CODE: FURI_LOG_D(TAG, "Procedure complete event"); break; - case EVT_BLUE_L2CAP_CONNECTION_UPDATE_RESP: { + case ACI_L2CAP_CONNECTION_UPDATE_REQ_VSEVT_CODE: { uint16_t result = ((aci_l2cap_connection_update_resp_event_rp0*)(blue_evt->data))->Result; if(result == 0) { @@ -362,7 +361,7 @@ static void gap_init_svc(Gap* gap) { CFG_ENCRYPTION_KEY_SIZE_MAX, CFG_USED_FIXED_PIN, 0, - PUBLIC_ADDR); + CFG_IDENTITY_ADDRESS); // Configure whitelist aci_gap_configure_whitelist(); } @@ -397,7 +396,7 @@ static void gap_advertise_start(GapState new_state) { ADV_IND, min_interval, max_interval, - PUBLIC_ADDR, + CFG_IDENTITY_ADDRESS, 0, strlen(gap->service.adv_name), (uint8_t*)gap->service.adv_name, diff --git a/firmware/targets/f7/furi_hal/furi_hal_flash.c b/firmware/targets/f7/furi_hal/furi_hal_flash.c index fc021d96921..d2dbff55f30 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_flash.c +++ b/firmware/targets/f7/furi_hal/furi_hal_flash.c @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -114,6 +115,7 @@ static void furi_hal_flash_lock(void) { } static void furi_hal_flash_begin_with_core2(bool erase_flag) { + furi_hal_power_insomnia_enter(); /* Take flash controller ownership */ while(LL_HSEM_1StepLock(HSEM, CFG_HW_FLASH_SEMID) != 0) { furi_thread_yield(); @@ -188,6 +190,7 @@ static void furi_hal_flash_end_with_core2(bool erase_flag) { /* Release flash controller ownership */ LL_HSEM_ReleaseLock(HSEM, CFG_HW_FLASH_SEMID, 0); + furi_hal_power_insomnia_exit(); } static void furi_hal_flash_end(bool erase_flag) { diff --git a/firmware/targets/f7/furi_hal/furi_hal_power.c b/firmware/targets/f7/furi_hal/furi_hal_power.c index 9a87cef15c3..3e4e3f48b60 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_power.c +++ b/firmware/targets/f7/furi_hal/furi_hal_power.c @@ -29,6 +29,14 @@ #define FURI_HAL_POWER_DEBUG_STOP_GPIO (&gpio_ext_pc3) #endif +#ifndef FURI_HAL_POWER_DEBUG_ABNORMAL_GPIO +#define FURI_HAL_POWER_DEBUG_ABNORMAL_GPIO (&gpio_ext_pb3) +#endif + +#ifndef FURI_HAL_POWER_STOP_MODE +#define FURI_HAL_POWER_STOP_MODE (LL_PWR_MODE_STOP2) +#endif + typedef struct { volatile uint8_t insomnia; volatile uint8_t suppress_charge; @@ -84,14 +92,16 @@ void furi_hal_power_init() { #ifdef FURI_HAL_POWER_DEBUG furi_hal_gpio_init_simple(FURI_HAL_POWER_DEBUG_WFI_GPIO, GpioModeOutputPushPull); furi_hal_gpio_init_simple(FURI_HAL_POWER_DEBUG_STOP_GPIO, GpioModeOutputPushPull); + furi_hal_gpio_init_simple(FURI_HAL_POWER_DEBUG_ABNORMAL_GPIO, GpioModeOutputPushPull); furi_hal_gpio_write(FURI_HAL_POWER_DEBUG_WFI_GPIO, 0); furi_hal_gpio_write(FURI_HAL_POWER_DEBUG_STOP_GPIO, 0); + furi_hal_gpio_write(FURI_HAL_POWER_DEBUG_ABNORMAL_GPIO, 0); #endif LL_PWR_SetRegulVoltageScaling(LL_PWR_REGU_VOLTAGE_SCALE1); LL_PWR_SMPS_SetMode(LL_PWR_SMPS_STEP_DOWN); - LL_PWR_SetPowerMode(LL_PWR_MODE_STOP2); - LL_C2_PWR_SetPowerMode(LL_PWR_MODE_STOP2); + LL_PWR_SetPowerMode(FURI_HAL_POWER_STOP_MODE); + LL_C2_PWR_SetPowerMode(FURI_HAL_POWER_STOP_MODE); furi_hal_i2c_acquire(&furi_hal_i2c_handle_power); bq27220_init(&furi_hal_i2c_handle_power, &cedv); @@ -148,7 +158,9 @@ bool furi_hal_power_sleep_available() { static inline bool furi_hal_power_deep_sleep_available() { return furi_hal_bt_is_alive() && !furi_hal_rtc_is_flag_set(FuriHalRtcFlagLegacySleep) && - !furi_hal_debug_is_gdb_session_active(); + !furi_hal_debug_is_gdb_session_active() && !LL_PWR_IsActiveFlag_CRPE() && + !LL_PWR_IsActiveFlag_CRP() && !LL_PWR_IsActiveFlag_BLEA() && + !LL_PWR_IsActiveFlag_BLEWU(); } static inline void furi_hal_power_light_sleep() { @@ -199,7 +211,16 @@ static inline void furi_hal_power_deep_sleep() { __force_stores(); #endif - __WFI(); + bool should_abort_sleep = LL_PWR_IsActiveFlag_CRPE() || LL_PWR_IsActiveFlag_CRP() || + LL_PWR_IsActiveFlag_BLEA() || LL_PWR_IsActiveFlag_BLEWU(); + + if(should_abort_sleep) { +#ifdef FURI_HAL_POWER_DEBUG + furi_hal_gpio_write(FURI_HAL_POWER_DEBUG_ABNORMAL_GPIO, 1); +#endif + } else { + __WFI(); + } LL_LPM_EnableSleep(); diff --git a/lib/STM32CubeWB b/lib/STM32CubeWB index a9e29b431f6..06b8133fa29 160000 --- a/lib/STM32CubeWB +++ b/lib/STM32CubeWB @@ -1 +1 @@ -Subproject commit a9e29b431f6dac95b6fc860a717834f35b7f78e5 +Subproject commit 06b8133fa295474507b55b1a5695d4b8bf804ed6 diff --git a/scripts/ob.data b/scripts/ob.data index 5276a5103bf..605faccbfc6 100644 --- a/scripts/ob.data +++ b/scripts/ob.data @@ -14,7 +14,7 @@ IWDGSTOP:0x1:rw IWDGSW:0x1:rw IPCCDBA:0x0:rw ESE:0x1:r -SFSA:0xD7:r +SFSA:0xD5:r FSD:0x0:r DDS:0x1:r C2OPT:0x1:r @@ -22,7 +22,7 @@ NBRSD:0x0:r SNBRSA:0xD:r BRSD:0x0:r SBRSA:0x12:r -SBRV:0x35C00:r +SBRV:0x35400:r PCROP1A_STRT:0x1FF:r PCROP1A_END:0x0:r PCROP_RDP:0x1:rw From 1ef70c0bb4db8c4b7f537b39195fcba419af023b Mon Sep 17 00:00:00 2001 From: hedger Date: Mon, 24 Apr 2023 11:19:36 +0400 Subject: [PATCH 525/824] [FL-3280] cubewb: downgraded to v1.15.0 (#2605) * cubewb: downgraded to v1.15.0 * hal: updated f18 symbols to match LL * hal: flash: use furi_hal_cortex_timer for timeouts * scripts: fixed cube version validation from config file * hal: flash: added 3 seconds timeout when waiting for C2 to unlock flash controller. On timeout, triggers furi_check * nfc: fixed missing interrupt setup on multiple platformSetIrqCallback() invocations * hal: gpio: don't trigger furi_check on furi_hal_gpio_add_int_callback() with same parameters * Reverted NFC fixes - will be in a separate PR * scripts: storage: fixed exception handler for paths --- fbt_options.py | 2 +- firmware/targets/f18/api_symbols.csv | 4 +-- firmware/targets/f7/api_symbols.csv | 4 +-- firmware/targets/f7/ble_glue/ble_app.c | 4 +-- firmware/targets/f7/furi_hal/furi_hal_flash.c | 30 ++++++++++++------- lib/STM32CubeWB | 2 +- scripts/fbt_tools/fbt_dist.py | 4 ++- scripts/flipper/assets/copro.py | 4 +-- scripts/flipper/storage.py | 4 ++- 9 files changed, 35 insertions(+), 23 deletions(-) diff --git a/fbt_options.py b/fbt_options.py index 25e84afde57..4fd7ef496ea 100644 --- a/fbt_options.py +++ b/fbt_options.py @@ -20,7 +20,7 @@ COPRO_OB_DATA = "scripts/ob.data" # Must match lib/STM32CubeWB version -COPRO_CUBE_VERSION = "1.16.0" +COPRO_CUBE_VERSION = "1.15.0" COPRO_CUBE_DIR = "lib/STM32CubeWB" diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index 4ee3d8636b3..eb2d6f43fa0 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -199,8 +199,8 @@ Function,-,LL_EXTI_StructInit,void,LL_EXTI_InitTypeDef* Function,-,LL_GPIO_DeInit,ErrorStatus,GPIO_TypeDef* Function,+,LL_GPIO_Init,ErrorStatus,"GPIO_TypeDef*, LL_GPIO_InitTypeDef*" Function,-,LL_GPIO_StructInit,void,LL_GPIO_InitTypeDef* -Function,-,LL_I2C_DeInit,ErrorStatus,const I2C_TypeDef* -Function,+,LL_I2C_Init,ErrorStatus,"I2C_TypeDef*, const LL_I2C_InitTypeDef*" +Function,-,LL_I2C_DeInit,ErrorStatus,I2C_TypeDef* +Function,+,LL_I2C_Init,ErrorStatus,"I2C_TypeDef*, LL_I2C_InitTypeDef*" Function,-,LL_I2C_StructInit,void,LL_I2C_InitTypeDef* Function,-,LL_Init1msTick,void,uint32_t Function,+,LL_LPTIM_DeInit,ErrorStatus,LPTIM_TypeDef* diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index a2d70e7f86e..d0c6b36ad54 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -231,8 +231,8 @@ Function,-,LL_EXTI_StructInit,void,LL_EXTI_InitTypeDef* Function,-,LL_GPIO_DeInit,ErrorStatus,GPIO_TypeDef* Function,+,LL_GPIO_Init,ErrorStatus,"GPIO_TypeDef*, LL_GPIO_InitTypeDef*" Function,-,LL_GPIO_StructInit,void,LL_GPIO_InitTypeDef* -Function,-,LL_I2C_DeInit,ErrorStatus,const I2C_TypeDef* -Function,+,LL_I2C_Init,ErrorStatus,"I2C_TypeDef*, const LL_I2C_InitTypeDef*" +Function,-,LL_I2C_DeInit,ErrorStatus,I2C_TypeDef* +Function,+,LL_I2C_Init,ErrorStatus,"I2C_TypeDef*, LL_I2C_InitTypeDef*" Function,-,LL_I2C_StructInit,void,LL_I2C_InitTypeDef* Function,-,LL_Init1msTick,void,uint32_t Function,+,LL_LPTIM_DeInit,ErrorStatus,LPTIM_TypeDef* diff --git a/firmware/targets/f7/ble_glue/ble_app.c b/firmware/targets/f7/ble_glue/ble_app.c index cc6065b9755..a325830cfc5 100644 --- a/firmware/targets/f7/ble_glue/ble_app.c +++ b/firmware/targets/f7/ble_glue/ble_app.c @@ -18,8 +18,8 @@ PLACE_IN_SECTION("MB_MEM1") ALIGN(4) static TL_CmdPacket_t ble_app_cmd_buffer; PLACE_IN_SECTION("MB_MEM2") ALIGN(4) static uint32_t ble_app_nvm[BLE_NVM_SRAM_SIZE]; _Static_assert( - sizeof(SHCI_C2_Ble_Init_Cmd_Packet_t) == 58, - "Ble stack config structure size mismatch (check new config options - last updated for v.1.16.0)"); + sizeof(SHCI_C2_Ble_Init_Cmd_Packet_t) == 57, + "Ble stack config structure size mismatch (check new config options - last updated for v.1.15.0)"); typedef struct { FuriMutex* hci_mtx; diff --git a/firmware/targets/f7/furi_hal/furi_hal_flash.c b/firmware/targets/f7/furi_hal/furi_hal_flash.c index d2dbff55f30..464d88d9593 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_flash.c +++ b/firmware/targets/f7/furi_hal/furi_hal_flash.c @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -26,6 +27,16 @@ #define FURI_HAL_FLASH_OPT_KEY2 0x4C5D6E7F #define FURI_HAL_FLASH_OB_TOTAL_WORDS (0x80 / (sizeof(uint32_t) * 2)) +/* lib/STM32CubeWB/Projects/P-NUCLEO-WB55.Nucleo/Applications/BLE/BLE_RfWithFlash/Core/Src/flash_driver.c + * ProcessSingleFlashOperation, quote: + > In most BLE application, the flash should not be blocked by the CPU2 longer than FLASH_TIMEOUT_VALUE (1000ms) + > However, it could be that for some marginal application, this time is longer. + > ... there is no other way than waiting the operation to be completed. + > If for any reason this test is never passed, this means there is a failure in the system and there is no other + > way to recover than applying a device reset. + */ +#define FURI_HAL_FLASH_C2_LOCK_TIMEOUT_MS 3000u /* 3 seconds */ + #define IS_ADDR_ALIGNED_64BITS(__VALUE__) (((__VALUE__)&0x7U) == (0x00UL)) #define IS_FLASH_PROGRAM_ADDRESS(__VALUE__) \ (((__VALUE__) >= FLASH_BASE) && ((__VALUE__) <= (FLASH_BASE + FLASH_SIZE - 8UL)) && \ @@ -131,9 +142,11 @@ static void furi_hal_flash_begin_with_core2(bool erase_flag) { for(volatile uint32_t i = 0; i < 35; i++) ; + FuriHalCortexTimer timer = furi_hal_cortex_timer_get(FURI_HAL_FLASH_C2_LOCK_TIMEOUT_MS * 1000); while(true) { /* Wait till flash controller become usable */ while(LL_FLASH_IsActiveFlag_OperationSuspended()) { + furi_check(!furi_hal_cortex_timer_is_expired(timer)); furi_thread_yield(); }; @@ -143,6 +156,7 @@ static void furi_hal_flash_begin_with_core2(bool erase_flag) { /* Actually we already have mutex for it, but specification is specification */ if(LL_HSEM_IsSemaphoreLocked(HSEM, CFG_HW_BLOCK_FLASH_REQ_BY_CPU1_SEMID)) { taskEXIT_CRITICAL(); + furi_check(!furi_hal_cortex_timer_is_expired(timer)); furi_thread_yield(); continue; } @@ -150,6 +164,7 @@ static void furi_hal_flash_begin_with_core2(bool erase_flag) { /* Take sempahopre and prevent core2 from anything funky */ if(LL_HSEM_1StepLock(HSEM, CFG_HW_BLOCK_FLASH_REQ_BY_CPU2_SEMID) != 0) { taskEXIT_CRITICAL(); + furi_check(!furi_hal_cortex_timer_is_expired(timer)); furi_thread_yield(); continue; } @@ -231,17 +246,13 @@ static void furi_hal_flush_cache(void) { bool furi_hal_flash_wait_last_operation(uint32_t timeout) { uint32_t error = 0; - uint32_t countdown = 0; /* Wait for the FLASH operation to complete by polling on BUSY flag to be reset. Even if the FLASH operation fails, the BUSY flag will be reset and an error flag will be set */ - countdown = timeout; + FuriHalCortexTimer timer = furi_hal_cortex_timer_get(timeout * 1000); while(READ_BIT(FLASH->SR, FLASH_SR_BSY)) { - if(LL_SYSTICK_IsActiveCounterFlag()) { - countdown--; - } - if(countdown == 0) { + if(furi_hal_cortex_timer_is_expired(timer)) { return false; } } @@ -264,12 +275,9 @@ bool furi_hal_flash_wait_last_operation(uint32_t timeout) { CLEAR_BIT(FLASH->SR, error); /* Wait for control register to be written */ - countdown = timeout; + timer = furi_hal_cortex_timer_get(timeout * 1000); while(READ_BIT(FLASH->SR, FLASH_SR_CFGBSY)) { - if(LL_SYSTICK_IsActiveCounterFlag()) { - countdown--; - } - if(countdown == 0) { + if(furi_hal_cortex_timer_is_expired(timer)) { return false; } } diff --git a/lib/STM32CubeWB b/lib/STM32CubeWB index 06b8133fa29..c4cec8ae57a 160000 --- a/lib/STM32CubeWB +++ b/lib/STM32CubeWB @@ -1 +1 @@ -Subproject commit 06b8133fa295474507b55b1a5695d4b8bf804ed6 +Subproject commit c4cec8ae57a79e949a184cd0b4117a008a0a25a7 diff --git a/scripts/fbt_tools/fbt_dist.py b/scripts/fbt_tools/fbt_dist.py index f0b4434864a..d2808419c7a 100644 --- a/scripts/fbt_tools/fbt_dist.py +++ b/scripts/fbt_tools/fbt_dist.py @@ -112,6 +112,8 @@ def DistCommand(env, name, source, **kw): def generate(env): + if not env["VERBOSE"]: + env.SetDefault(COPROCOMSTR="\tCOPRO\t${TARGET}") env.AddMethod(AddFwProject) env.AddMethod(DistCommand) env.AddMethod(AddOpenOCDFlashTarget) @@ -147,7 +149,7 @@ def generate(env): '--stack_file="${COPRO_STACK_BIN}" ' "--stack_addr=${COPRO_STACK_ADDR} ", ], - "\tCOPRO\t${TARGET}", + "$COPROCOMSTR", ) ), } diff --git a/scripts/flipper/assets/copro.py b/scripts/flipper/assets/copro.py index b61ac032964..e0375b51f17 100644 --- a/scripts/flipper/assets/copro.py +++ b/scripts/flipper/assets/copro.py @@ -34,7 +34,7 @@ def __init__(self, mcu): self.mcu_copro = None self.logger = logging.getLogger(self.__class__.__name__) - def loadCubeInfo(self, cube_dir, cube_version): + def loadCubeInfo(self, cube_dir, reference_cube_version): if not os.path.isdir(cube_dir): raise Exception(f'"{cube_dir}" doesn\'t exists') self.cube_dir = cube_dir @@ -50,7 +50,7 @@ def loadCubeInfo(self, cube_dir, cube_version): if not cube_version or not cube_version.startswith("FW.WB"): raise Exception(f"Incorrect Cube package or version info") cube_version = cube_version.replace("FW.WB.", "", 1) - if cube_version != cube_version: + if cube_version != reference_cube_version: raise Exception(f"Unsupported cube version") self.version = cube_version diff --git a/scripts/flipper/storage.py b/scripts/flipper/storage.py index 7b56ee0d0ef..cff32ceb1d7 100644 --- a/scripts/flipper/storage.py +++ b/scripts/flipper/storage.py @@ -335,7 +335,9 @@ def exist_file(self, path: str): def _check_no_error(self, response, path=None): if self.has_error(response): - raise FlipperStorageException.from_error_code(self.get_error(response)) + raise FlipperStorageException.from_error_code( + path, self.get_error(response) + ) def size(self, path: str): """file size on Flipper""" From d70ba2b74064e24586adea56231b569ff66d71df Mon Sep 17 00:00:00 2001 From: hedger Date: Tue, 25 Apr 2023 20:33:13 +0400 Subject: [PATCH 526/824] [FL-3286] Don't reboot on crash in debug builds (#2613) * furi: never reboot on furi_crash in debug builds * furi: crash info: added registers * furi: check and assert optimization, split registers and stack info dump * furi: macro uppercase Co-authored-by: SG --- furi/core/check.c | 31 ++++++++++++++++++++++++++++++- furi/core/check.h | 24 ++++++++++++++---------- 2 files changed, 44 insertions(+), 11 deletions(-) diff --git a/furi/core/check.c b/furi/core/check.c index 64f9f72f1b9..f5390639d6f 100644 --- a/furi/core/check.c +++ b/furi/core/check.c @@ -14,7 +14,7 @@ #include PLACE_IN_SECTION("MB_MEM2") const char* __furi_check_message = NULL; -PLACE_IN_SECTION("MB_MEM2") uint32_t __furi_check_registers[12] = {0}; +PLACE_IN_SECTION("MB_MEM2") uint32_t __furi_check_registers[13] = {0}; /** Load r12 value to __furi_check_message and store registers to __furi_check_registers */ #define GET_MESSAGE_AND_STORE_REGISTERS() \ @@ -22,6 +22,7 @@ PLACE_IN_SECTION("MB_MEM2") uint32_t __furi_check_registers[12] = {0}; "str r12, [r11] \n" \ "ldr r12, =__furi_check_registers \n" \ "stm r12, {r0-r11} \n" \ + "str lr, [r12, #48] \n" \ : \ : \ : "memory"); @@ -62,6 +63,25 @@ static void __furi_put_uint32_as_text(uint32_t data) { furi_hal_console_puts(tmp_str); } +static void __furi_put_uint32_as_hex(uint32_t data) { + char tmp_str[] = "0xFFFFFFFF"; + itoa(data, tmp_str, 16); + furi_hal_console_puts(tmp_str); +} + +static void __furi_print_register_info() { + // Print registers + for(uint8_t i = 0; i < 12; i++) { + furi_hal_console_puts("\r\n\tr"); + __furi_put_uint32_as_text(i); + furi_hal_console_puts(" : "); + __furi_put_uint32_as_hex(__furi_check_registers[i]); + } + + furi_hal_console_puts("\r\n\tlr : "); + __furi_put_uint32_as_hex(__furi_check_registers[12]); +} + static void __furi_print_stack_info() { furi_hal_console_puts("\r\n\tstack watermark: "); __furi_put_uint32_as_text(uxTaskGetStackHighWaterMark(NULL) * 4); @@ -101,32 +121,41 @@ FURI_NORETURN void __furi_crash() { if(__furi_check_message == NULL) { __furi_check_message = "Fatal Error"; + } else if(__furi_check_message == (void*)__FURI_ASSERT_MESSAGE_FLAG) { + __furi_check_message = "furi_assert failed"; + } else if(__furi_check_message == (void*)__FURI_CHECK_MESSAGE_FLAG) { + __furi_check_message = "furi_check failed"; } furi_hal_console_puts("\r\n\033[0;31m[CRASH]"); __furi_print_name(isr); furi_hal_console_puts(__furi_check_message); + __furi_print_register_info(); if(!isr) { __furi_print_stack_info(); } __furi_print_heap_info(); +#ifndef FURI_DEBUG // Check if debug enabled by DAP // https://developer.arm.com/documentation/ddi0403/d/Debug-Architecture/ARMv7-M-Debug/Debug-register-support-in-the-SCS/Debug-Halting-Control-and-Status-Register--DHCSR?lang=en bool debug = CoreDebug->DHCSR & CoreDebug_DHCSR_C_DEBUGEN_Msk; if(debug) { +#endif furi_hal_console_puts("\r\nSystem halted. Connect debugger for more info\r\n"); furi_hal_console_puts("\033[0m\r\n"); furi_hal_debug_enable(); RESTORE_REGISTERS_AND_HALT_MCU(true); +#ifndef FURI_DEBUG } else { furi_hal_rtc_set_fault_data((uint32_t)__furi_check_message); furi_hal_console_puts("\r\nRebooting system.\r\n"); furi_hal_console_puts("\033[0m\r\n"); furi_hal_power_reset(); } +#endif __builtin_unreachable(); } diff --git a/furi/core/check.h b/furi/core/check.h index 192c5260e8e..a507fc1ead5 100644 --- a/furi/core/check.h +++ b/furi/core/check.h @@ -21,6 +21,10 @@ extern "C" { #define FURI_NORETURN noreturn #endif +// Flags instead of pointers will save ~4 bytes on furi_assert and furi_check calls. +#define __FURI_ASSERT_MESSAGE_FLAG (0x01) +#define __FURI_CHECK_MESSAGE_FLAG (0x02) + /** Crash system */ FURI_NORETURN void __furi_crash(); @@ -44,20 +48,20 @@ FURI_NORETURN void __furi_halt(); } while(0) /** Check condition and crash if check failed */ -#define furi_check(__e) \ - do { \ - if(!(__e)) { \ - furi_crash("furi_check failed\r\n"); \ - } \ +#define furi_check(__e) \ + do { \ + if(!(__e)) { \ + furi_crash(__FURI_ASSERT_MESSAGE_FLAG); \ + } \ } while(0) /** Only in debug build: Assert condition and crash if assert failed */ #ifdef FURI_DEBUG -#define furi_assert(__e) \ - do { \ - if(!(__e)) { \ - furi_crash("furi_assert failed\r\n"); \ - } \ +#define furi_assert(__e) \ + do { \ + if(!(__e)) { \ + furi_crash(__FURI_CHECK_MESSAGE_FLAG); \ + } \ } while(0) #else #define furi_assert(__e) \ From 0ec8fc4c55178e9ad7daa014a9268dcfbb8da573 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Wed, 26 Apr 2023 05:11:42 +0900 Subject: [PATCH 527/824] FuriHal: use proper divider for core2 when transition to sleep, remove extra stop mode transition checks, cleanup code. Furi: proper assert and check messages. (#2615) --- firmware/targets/f7/ble_glue/ble_app.c | 2 +- firmware/targets/f7/furi_hal/furi_hal_bt.c | 8 ++----- firmware/targets/f7/furi_hal/furi_hal_clock.c | 10 ++++++++- firmware/targets/f7/furi_hal/furi_hal_power.c | 21 ++----------------- furi/core/check.h | 20 +++++++++--------- 5 files changed, 24 insertions(+), 37 deletions(-) diff --git a/firmware/targets/f7/ble_glue/ble_app.c b/firmware/targets/f7/ble_glue/ble_app.c index a325830cfc5..37d8f7cd04b 100644 --- a/firmware/targets/f7/ble_glue/ble_app.c +++ b/firmware/targets/f7/ble_glue/ble_app.c @@ -88,7 +88,7 @@ bool ble_app_init() { .min_tx_power = 0, .max_tx_power = 0, .rx_model_config = 1, - /* New stack (13.3->16.0)*/ + /* New stack (13.3->15.0) */ .max_adv_set_nbr = 1, // Only used if SHCI_C2_BLE_INIT_OPTIONS_EXT_ADV is set .max_adv_data_len = 31, // Only used if SHCI_C2_BLE_INIT_OPTIONS_EXT_ADV is set .tx_path_compens = 0, // RF TX Path Compensation, * 0.1 dB diff --git a/firmware/targets/f7/furi_hal/furi_hal_bt.c b/firmware/targets/f7/furi_hal/furi_hal_bt.c index 0857fe4ee56..b08c9ea27bb 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_bt.c +++ b/firmware/targets/f7/furi_hal/furi_hal_bt.c @@ -84,9 +84,7 @@ void furi_hal_bt_init() { } // Explicitly tell that we are in charge of CLK48 domain - if(!LL_HSEM_IsSemaphoreLocked(HSEM, CFG_HW_CLK48_CONFIG_SEMID)) { - furi_check(LL_HSEM_1StepLock(HSEM, CFG_HW_CLK48_CONFIG_SEMID) == 0); - } + furi_check(LL_HSEM_1StepLock(HSEM, CFG_HW_CLK48_CONFIG_SEMID) == 0); // Start Core2 ble_glue_init(); @@ -129,9 +127,7 @@ bool furi_hal_bt_start_radio_stack() { furi_mutex_acquire(furi_hal_bt_core2_mtx, FuriWaitForever); // Explicitly tell that we are in charge of CLK48 domain - if(!LL_HSEM_IsSemaphoreLocked(HSEM, CFG_HW_CLK48_CONFIG_SEMID)) { - furi_check(LL_HSEM_1StepLock(HSEM, CFG_HW_CLK48_CONFIG_SEMID) == 0); - } + furi_check(LL_HSEM_1StepLock(HSEM, CFG_HW_CLK48_CONFIG_SEMID) == 0); do { // Wait until C2 is started or timeout diff --git a/firmware/targets/f7/furi_hal/furi_hal_clock.c b/firmware/targets/f7/furi_hal/furi_hal_clock.c index d85524ce44e..a4df4877eaf 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_clock.c +++ b/firmware/targets/f7/furi_hal/furi_hal_clock.c @@ -213,7 +213,11 @@ void furi_hal_clock_switch_to_hsi() { while(LL_RCC_GetSysClkSource() != LL_RCC_SYS_CLKSOURCE_STATUS_HSI) ; - LL_FLASH_SetLatency(LL_FLASH_LATENCY_1); + LL_C2_RCC_SetAHBPrescaler(LL_RCC_SYSCLK_DIV_1); + + LL_FLASH_SetLatency(LL_FLASH_LATENCY_0); + while(LL_FLASH_GetLatency() != LL_FLASH_LATENCY_0) + ; } void furi_hal_clock_switch_to_pll() { @@ -228,7 +232,11 @@ void furi_hal_clock_switch_to_pll() { while(!LL_RCC_PLLSAI1_IsReady()) ; + LL_C2_RCC_SetAHBPrescaler(LL_RCC_SYSCLK_DIV_2); + LL_FLASH_SetLatency(LL_FLASH_LATENCY_3); + while(LL_FLASH_GetLatency() != LL_FLASH_LATENCY_3) + ; LL_RCC_SetSysClkSource(LL_RCC_SYS_CLKSOURCE_PLL); LL_RCC_SetSMPSClockSource(LL_RCC_SMPS_CLKSOURCE_HSE); diff --git a/firmware/targets/f7/furi_hal/furi_hal_power.c b/firmware/targets/f7/furi_hal/furi_hal_power.c index 3e4e3f48b60..7d9334c2c74 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_power.c +++ b/firmware/targets/f7/furi_hal/furi_hal_power.c @@ -29,10 +29,6 @@ #define FURI_HAL_POWER_DEBUG_STOP_GPIO (&gpio_ext_pc3) #endif -#ifndef FURI_HAL_POWER_DEBUG_ABNORMAL_GPIO -#define FURI_HAL_POWER_DEBUG_ABNORMAL_GPIO (&gpio_ext_pb3) -#endif - #ifndef FURI_HAL_POWER_STOP_MODE #define FURI_HAL_POWER_STOP_MODE (LL_PWR_MODE_STOP2) #endif @@ -92,10 +88,8 @@ void furi_hal_power_init() { #ifdef FURI_HAL_POWER_DEBUG furi_hal_gpio_init_simple(FURI_HAL_POWER_DEBUG_WFI_GPIO, GpioModeOutputPushPull); furi_hal_gpio_init_simple(FURI_HAL_POWER_DEBUG_STOP_GPIO, GpioModeOutputPushPull); - furi_hal_gpio_init_simple(FURI_HAL_POWER_DEBUG_ABNORMAL_GPIO, GpioModeOutputPushPull); furi_hal_gpio_write(FURI_HAL_POWER_DEBUG_WFI_GPIO, 0); furi_hal_gpio_write(FURI_HAL_POWER_DEBUG_STOP_GPIO, 0); - furi_hal_gpio_write(FURI_HAL_POWER_DEBUG_ABNORMAL_GPIO, 0); #endif LL_PWR_SetRegulVoltageScaling(LL_PWR_REGU_VOLTAGE_SCALE1); @@ -158,9 +152,7 @@ bool furi_hal_power_sleep_available() { static inline bool furi_hal_power_deep_sleep_available() { return furi_hal_bt_is_alive() && !furi_hal_rtc_is_flag_set(FuriHalRtcFlagLegacySleep) && - !furi_hal_debug_is_gdb_session_active() && !LL_PWR_IsActiveFlag_CRPE() && - !LL_PWR_IsActiveFlag_CRP() && !LL_PWR_IsActiveFlag_BLEA() && - !LL_PWR_IsActiveFlag_BLEWU(); + !furi_hal_debug_is_gdb_session_active(); } static inline void furi_hal_power_light_sleep() { @@ -211,16 +203,7 @@ static inline void furi_hal_power_deep_sleep() { __force_stores(); #endif - bool should_abort_sleep = LL_PWR_IsActiveFlag_CRPE() || LL_PWR_IsActiveFlag_CRP() || - LL_PWR_IsActiveFlag_BLEA() || LL_PWR_IsActiveFlag_BLEWU(); - - if(should_abort_sleep) { -#ifdef FURI_HAL_POWER_DEBUG - furi_hal_gpio_write(FURI_HAL_POWER_DEBUG_ABNORMAL_GPIO, 1); -#endif - } else { - __WFI(); - } + __WFI(); LL_LPM_EnableSleep(); diff --git a/furi/core/check.h b/furi/core/check.h index a507fc1ead5..ea83f2219c2 100644 --- a/furi/core/check.h +++ b/furi/core/check.h @@ -48,21 +48,21 @@ FURI_NORETURN void __furi_halt(); } while(0) /** Check condition and crash if check failed */ -#define furi_check(__e) \ - do { \ - if(!(__e)) { \ - furi_crash(__FURI_ASSERT_MESSAGE_FLAG); \ - } \ - } while(0) - -/** Only in debug build: Assert condition and crash if assert failed */ -#ifdef FURI_DEBUG -#define furi_assert(__e) \ +#define furi_check(__e) \ do { \ if(!(__e)) { \ furi_crash(__FURI_CHECK_MESSAGE_FLAG); \ } \ } while(0) + +/** Only in debug build: Assert condition and crash if assert failed */ +#ifdef FURI_DEBUG +#define furi_assert(__e) \ + do { \ + if(!(__e)) { \ + furi_crash(__FURI_ASSERT_MESSAGE_FLAG); \ + } \ + } while(0) #else #define furi_assert(__e) \ do { \ From 408edb3e99751b5374571340c9517027845a65fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Thu, 27 Apr 2023 23:01:13 +0900 Subject: [PATCH 528/824] Keep HSI16 working in stop mode. (#2621) --- firmware/targets/f7/furi_hal/furi_hal_clock.c | 3 ++- firmware/targets/f7/furi_hal/furi_hal_power.c | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/firmware/targets/f7/furi_hal/furi_hal_clock.c b/firmware/targets/f7/furi_hal/furi_hal_clock.c index a4df4877eaf..a76fbfbca76 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_clock.c +++ b/firmware/targets/f7/furi_hal/furi_hal_clock.c @@ -144,6 +144,7 @@ void furi_hal_clock_init() { LL_RCC_SetRNGClockSource(LL_RCC_RNG_CLKSOURCE_CLK48); LL_RCC_SetUSBClockSource(LL_RCC_USB_CLKSOURCE_PLLSAI1); LL_RCC_SetCLK48ClockSource(LL_RCC_CLK48_CLKSOURCE_PLLSAI1); + LL_RCC_HSI_EnableInStopMode(); // Ensure that MR is capable of work in STOP0 LL_RCC_SetSMPSClockSource(LL_RCC_SMPS_CLKSOURCE_HSE); LL_RCC_SetSMPSPrescaler(LL_RCC_SMPS_DIV_1); LL_RCC_SetRFWKPClockSource(LL_RCC_RFWKP_CLKSOURCE_LSE); @@ -207,8 +208,8 @@ void furi_hal_clock_switch_to_hsi() { while(!LL_RCC_HSI_IsReady()) ; - LL_RCC_SetSysClkSource(LL_RCC_SYS_CLKSOURCE_HSI); LL_RCC_SetSMPSClockSource(LL_RCC_SMPS_CLKSOURCE_HSI); + LL_RCC_SetSysClkSource(LL_RCC_SYS_CLKSOURCE_HSI); while(LL_RCC_GetSysClkSource() != LL_RCC_SYS_CLKSOURCE_STATUS_HSI) ; diff --git a/firmware/targets/f7/furi_hal/furi_hal_power.c b/firmware/targets/f7/furi_hal/furi_hal_power.c index 7d9334c2c74..e380de7fa5e 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_power.c +++ b/firmware/targets/f7/furi_hal/furi_hal_power.c @@ -94,6 +94,7 @@ void furi_hal_power_init() { LL_PWR_SetRegulVoltageScaling(LL_PWR_REGU_VOLTAGE_SCALE1); LL_PWR_SMPS_SetMode(LL_PWR_SMPS_STEP_DOWN); + LL_PWR_SetPowerMode(FURI_HAL_POWER_STOP_MODE); LL_C2_PWR_SetPowerMode(FURI_HAL_POWER_STOP_MODE); From e42aec68c533e6120f0e776e8bdc5d7d89e99c8b Mon Sep 17 00:00:00 2001 From: Max Andreev Date: Fri, 28 Apr 2023 17:25:20 +0300 Subject: [PATCH 529/824] Disable ci/cd on release* branches (#2624) --- .github/workflows/build.yml | 7 +++---- .github/workflows/lint_and_submodule_check.yml | 5 ++--- .github/workflows/merge_report.yml | 2 +- .github/workflows/pvs_studio.yml | 3 +-- .github/workflows/unit_tests.yml | 2 +- .github/workflows/updater_test.yml | 6 +++--- 6 files changed, 11 insertions(+), 14 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index dfeb8d83f28..0934eec76d5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,7 +4,6 @@ on: push: branches: - dev - - "release*" tags: - '*' pull_request: @@ -19,7 +18,7 @@ jobs: runs-on: [self-hosted,FlipperZeroShell] steps: - name: 'Wipe workspace' - run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; + run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; - name: 'Checkout code' uses: actions/checkout@v3 @@ -167,7 +166,7 @@ jobs: target: [f7, f18] steps: - name: 'Wipe workspace' - run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; + run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; - name: 'Checkout code' uses: actions/checkout@v3 @@ -207,7 +206,7 @@ jobs: cd testapp ufbt create APPID=testapp ufbt - + - name: Build example & external apps with uFBT run: | for appdir in 'applications/external' 'applications/examples'; do diff --git a/.github/workflows/lint_and_submodule_check.yml b/.github/workflows/lint_and_submodule_check.yml index 999111cc95c..22ca7d893fb 100644 --- a/.github/workflows/lint_and_submodule_check.yml +++ b/.github/workflows/lint_and_submodule_check.yml @@ -4,7 +4,6 @@ on: push: branches: - dev - - "release*" tags: - '*' pull_request: @@ -19,7 +18,7 @@ jobs: runs-on: [self-hosted,FlipperZeroShell] steps: - name: 'Wipe workspace' - run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; + run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; - name: 'Checkout code' uses: actions/checkout@v3 @@ -64,7 +63,7 @@ jobs: else echo "Python Lint: all good ✨" >> $GITHUB_STEP_SUMMARY; fi - + - name: 'Check C++ code formatting' id: syntax_check_cpp if: always() diff --git a/.github/workflows/merge_report.yml b/.github/workflows/merge_report.yml index 5b7d5fcbf7c..02016666659 100644 --- a/.github/workflows/merge_report.yml +++ b/.github/workflows/merge_report.yml @@ -13,7 +13,7 @@ jobs: runs-on: [self-hosted,FlipperZeroShell] steps: - name: 'Wipe workspace' - run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; + run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; - name: 'Checkout code' uses: actions/checkout@v3 diff --git a/.github/workflows/pvs_studio.yml b/.github/workflows/pvs_studio.yml index b8c4d7a367d..cb5b50278a6 100644 --- a/.github/workflows/pvs_studio.yml +++ b/.github/workflows/pvs_studio.yml @@ -4,7 +4,6 @@ on: push: branches: - dev - - "release*" tags: - '*' pull_request: @@ -20,7 +19,7 @@ jobs: runs-on: [self-hosted, FlipperZeroShell] steps: - name: 'Wipe workspace' - run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; + run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; - name: 'Checkout code' uses: actions/checkout@v3 diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 6a824fac314..81f0e0d050e 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -13,7 +13,7 @@ jobs: runs-on: [self-hosted, FlipperZeroUnitTest] steps: - name: 'Wipe workspace' - run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; + run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; - name: Checkout code uses: actions/checkout@v3 diff --git a/.github/workflows/updater_test.yml b/.github/workflows/updater_test.yml index 2861529d838..bd837297974 100644 --- a/.github/workflows/updater_test.yml +++ b/.github/workflows/updater_test.yml @@ -13,13 +13,13 @@ jobs: runs-on: [self-hosted, FlipperZeroUpdaterTest] steps: - name: 'Wipe workspace' - run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; + run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; - name: Checkout code uses: actions/checkout@v3 with: fetch-depth: 1 - submodules: false + submodules: false ref: ${{ github.event.pull_request.head.sha }} - name: 'Get flipper from device manager (mock)' @@ -50,7 +50,7 @@ jobs: echo "tag=$(git tag -l --sort=-version:refname | grep -v "rc\|RC" | head -1)" >> $GITHUB_OUTPUT - name: 'Wipe workspace' - run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; + run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; - name: 'Checkout latest release' uses: actions/checkout@v3 From 238005890ec6a619157ac75eb60bd02dfe6c736e Mon Sep 17 00:00:00 2001 From: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> Date: Wed, 3 May 2023 00:56:25 +0300 Subject: [PATCH 530/824] [FL-3294] Fix TERMINFO on Linux systems (#2630) * Fix TERMINFO on Linux systems * Set TERMINFO_DIRS only on Linux * Unset TERMINFO_DIRS if it was not set before --- scripts/toolchain/fbtenv.sh | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/scripts/toolchain/fbtenv.sh b/scripts/toolchain/fbtenv.sh index 57a50281edb..143dce74b91 100755 --- a/scripts/toolchain/fbtenv.sh +++ b/scripts/toolchain/fbtenv.sh @@ -56,6 +56,16 @@ fbtenv_restore_env() unset SSL_CERT_FILE; unset REQUESTS_CA_BUNDLE; fi + + if [ "$SYS_TYPE" = "Linux" ]; then + if [ -n "$SAVED_TERMINFO_DIRS" ]; then + export TERMINFO_DIRS="$SAVED_TERMINFO_DIRS"; + else + unset TERMINFO_DIRS; + fi + unset SAVED_TERMINFO_DIRS; + fi + export PYTHONNOUSERSITE="$SAVED_PYTHONNOUSERSITE"; export PYTHONPATH="$SAVED_PYTHONPATH"; export PYTHONHOME="$SAVED_PYTHONHOME"; @@ -325,6 +335,11 @@ fbtenv_main() export PYTHONNOUSERSITE=1; export PYTHONPATH=; export PYTHONHOME=; + + if [ "$SYS_TYPE" = "Linux" ]; then + export SAVED_TERMINFO_DIRS="${TERMINFO_DIRS:-""}"; + export TERMINFO_DIRS="$TOOLCHAIN_ARCH_DIR/ncurses/share/terminfo"; + fi } fbtenv_main "${1:-""}"; From 23c946ef50e07950199637776d2f520351ee7ffc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Wed, 3 May 2023 11:08:41 +0900 Subject: [PATCH 531/824] Move gauge calibration to separate header, add f18 calibration (#2622) --- firmware/targets/f18/api_symbols.csv | 1 + .../f18/furi_hal/furi_hal_power_calibration.h | 37 ++++++++++++++++++ firmware/targets/f7/api_symbols.csv | 1 + firmware/targets/f7/furi_hal/furi_hal_power.c | 38 +------------------ .../f7/furi_hal/furi_hal_power_calibration.h | 37 ++++++++++++++++++ 5 files changed, 77 insertions(+), 37 deletions(-) create mode 100644 firmware/targets/f18/furi_hal/furi_hal_power_calibration.h create mode 100644 firmware/targets/f7/furi_hal/furi_hal_power_calibration.h diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index eb2d6f43fa0..25f36ca0254 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -36,6 +36,7 @@ Header,+,applications/services/notification/notification_messages.h,, Header,+,applications/services/power/power_service/power.h,, Header,+,applications/services/rpc/rpc_app.h,, Header,+,applications/services/storage/storage.h,, +Header,-,firmware/targets/f18/furi_hal/furi_hal_power_calibration.h,, Header,+,firmware/targets/f18/furi_hal/furi_hal_resources.h,, Header,+,firmware/targets/f18/furi_hal/furi_hal_spi_config.h,, Header,+,firmware/targets/f18/furi_hal/furi_hal_target_hw.h,, diff --git a/firmware/targets/f18/furi_hal/furi_hal_power_calibration.h b/firmware/targets/f18/furi_hal/furi_hal_power_calibration.h new file mode 100644 index 00000000000..e97e1657dba --- /dev/null +++ b/firmware/targets/f18/furi_hal/furi_hal_power_calibration.h @@ -0,0 +1,37 @@ +const ParamCEDV cedv = { + .cedv_conf.gauge_conf = + { + .CCT = 1, + .CSYNC = 0, + .EDV_CMP = 0, + .SC = 1, + .FIXED_EDV0 = 1, + .FCC_LIM = 1, + .FC_FOR_VDQ = 1, + .IGNORE_SD = 1, + .SME0 = 0, + }, + .full_charge_cap = 1300, + .design_cap = 1300, + .EDV0 = 3300, + .EDV1 = 3321, + .EDV2 = 3355, + .EMF = 3679, + .C0 = 430, + .C1 = 0, + .R1 = 408, + .R0 = 334, + .T0 = 4626, + .TC = 11, + .DOD0 = 4044, + .DOD10 = 3905, + .DOD20 = 3807, + .DOD30 = 3718, + .DOD40 = 3642, + .DOD50 = 3585, + .DOD60 = 3546, + .DOD70 = 3514, + .DOD80 = 3477, + .DOD90 = 3411, + .DOD100 = 3299, +}; diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index d0c6b36ad54..951e92aae13 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -47,6 +47,7 @@ Header,+,firmware/targets/f7/furi_hal/furi_hal_idle_timer.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_interrupt.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_nfc.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_os.h,, +Header,-,firmware/targets/f7/furi_hal/furi_hal_power_calibration.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_pwm.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_resources.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_rfid.h,, diff --git a/firmware/targets/f7/furi_hal/furi_hal_power.c b/firmware/targets/f7/furi_hal/furi_hal_power.c index e380de7fa5e..ec405f1080d 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_power.c +++ b/firmware/targets/f7/furi_hal/furi_hal_power.c @@ -46,43 +46,7 @@ static volatile FuriHalPower furi_hal_power = { .suppress_charge = 0, }; -const ParamCEDV cedv = { - .cedv_conf.gauge_conf = - { - .CCT = 1, - .CSYNC = 0, - .EDV_CMP = 0, - .SC = 1, - .FIXED_EDV0 = 1, - .FCC_LIM = 1, - .FC_FOR_VDQ = 1, - .IGNORE_SD = 1, - .SME0 = 0, - }, - .full_charge_cap = 2101, - .design_cap = 2101, - .EDV0 = 3300, - .EDV1 = 3321, - .EDV2 = 3355, - .EMF = 3679, - .C0 = 430, - .C1 = 0, - .R1 = 408, - .R0 = 334, - .T0 = 4626, - .TC = 11, - .DOD0 = 4044, - .DOD10 = 3905, - .DOD20 = 3807, - .DOD30 = 3718, - .DOD40 = 3642, - .DOD50 = 3585, - .DOD60 = 3546, - .DOD70 = 3514, - .DOD80 = 3477, - .DOD90 = 3411, - .DOD100 = 3299, -}; +#include void furi_hal_power_init() { #ifdef FURI_HAL_POWER_DEBUG diff --git a/firmware/targets/f7/furi_hal/furi_hal_power_calibration.h b/firmware/targets/f7/furi_hal/furi_hal_power_calibration.h new file mode 100644 index 00000000000..5eb0f938b2e --- /dev/null +++ b/firmware/targets/f7/furi_hal/furi_hal_power_calibration.h @@ -0,0 +1,37 @@ +const ParamCEDV cedv = { + .cedv_conf.gauge_conf = + { + .CCT = 1, + .CSYNC = 0, + .EDV_CMP = 0, + .SC = 1, + .FIXED_EDV0 = 1, + .FCC_LIM = 1, + .FC_FOR_VDQ = 1, + .IGNORE_SD = 1, + .SME0 = 0, + }, + .full_charge_cap = 2101, + .design_cap = 2101, + .EDV0 = 3300, + .EDV1 = 3321, + .EDV2 = 3355, + .EMF = 3679, + .C0 = 430, + .C1 = 0, + .R1 = 408, + .R0 = 334, + .T0 = 4626, + .TC = 11, + .DOD0 = 4044, + .DOD10 = 3905, + .DOD20 = 3807, + .DOD30 = 3718, + .DOD40 = 3642, + .DOD50 = 3585, + .DOD60 = 3546, + .DOD70 = 3514, + .DOD80 = 3477, + .DOD90 = 3411, + .DOD100 = 3299, +}; From 5c09bc5b2d25da5fa6fc71e82eb316ed9db87c1f Mon Sep 17 00:00:00 2001 From: Lewis Westbury Date: Wed, 3 May 2023 04:33:30 +0100 Subject: [PATCH 532/824] Gui: relax some asserts in view (#2623) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Remove assertion preventing replacement of view input callback * Gui: relax some asserts in view Co-authored-by: あく --- applications/services/gui/view.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/applications/services/gui/view.c b/applications/services/gui/view.c index 4d84cac507d..07ae072a171 100644 --- a/applications/services/gui/view.c +++ b/applications/services/gui/view.c @@ -19,19 +19,16 @@ void view_tie_icon_animation(View* view, IconAnimation* icon_animation) { void view_set_draw_callback(View* view, ViewDrawCallback callback) { furi_assert(view); - furi_assert(view->draw_callback == NULL); view->draw_callback = callback; } void view_set_input_callback(View* view, ViewInputCallback callback) { furi_assert(view); - furi_assert(view->input_callback == NULL); view->input_callback = callback; } void view_set_custom_callback(View* view, ViewCustomCallback callback) { furi_assert(view); - furi_assert(callback); view->custom_callback = callback; } @@ -62,7 +59,6 @@ void view_set_update_callback_context(View* view, void* context) { void view_set_context(View* view, void* context) { furi_assert(view); - furi_assert(context); view->context = context; } From c5b460b416e24fa887ec93672e56b71109c91a79 Mon Sep 17 00:00:00 2001 From: hedger Date: Wed, 3 May 2023 06:58:59 +0300 Subject: [PATCH 533/824] [FL-3260] Added API version to device info (#2611) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * hal: device_info: added API version to "firmware.api.major" & "firmware.api.minor" * FuriHal: bump device info version Co-authored-by: あく --- .../loader/firmware_api/firmware_api.cpp | 7 +++++++ firmware/targets/f18/api_symbols.csv | 3 ++- firmware/targets/f7/api_symbols.csv | 3 ++- firmware/targets/f7/furi_hal/furi_hal_info.c | 16 ++++++++++++++-- .../targets/furi_hal_include/furi_hal_info.h | 2 ++ furi/core/common_defines.h | 4 ++++ 6 files changed, 31 insertions(+), 4 deletions(-) diff --git a/applications/services/loader/firmware_api/firmware_api.cpp b/applications/services/loader/firmware_api/firmware_api.cpp index 52e86efc269..6651bf11247 100644 --- a/applications/services/loader/firmware_api/firmware_api.cpp +++ b/applications/services/loader/firmware_api/firmware_api.cpp @@ -6,6 +6,8 @@ /* Generated table */ #include +#include + static_assert(!has_hash_collisions(elf_api_table), "Detected API method hash collision!"); constexpr HashtableApiInterface elf_api_interface{ @@ -19,3 +21,8 @@ constexpr HashtableApiInterface elf_api_interface{ }; const ElfApiInterface* const firmware_api_interface = &elf_api_interface; + +extern "C" void furi_hal_info_get_api_version(uint16_t* major, uint16_t* minor) { + *major = elf_api_interface.api_version_major; + *minor = elf_api_interface.api_version_minor; +} \ No newline at end of file diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index 25f36ca0254..bdd5d6b61fe 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,23.0,, +Version,+,23.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -960,6 +960,7 @@ Function,+,furi_hal_i2c_write_mem,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, Function,+,furi_hal_i2c_write_reg_16,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint16_t, uint32_t" Function,+,furi_hal_i2c_write_reg_8,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint8_t, uint32_t" Function,+,furi_hal_info_get,void,"PropertyValueCallback, char, void*" +Function,+,furi_hal_info_get_api_version,void,"uint16_t*, uint16_t*" Function,-,furi_hal_init,void, Function,-,furi_hal_init_early,void, Function,-,furi_hal_interrupt_init,void, diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 951e92aae13..9f4ec4261c1 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,23.0,, +Version,+,23.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -1148,6 +1148,7 @@ Function,+,furi_hal_ibutton_pin_configure,void, Function,+,furi_hal_ibutton_pin_reset,void, Function,+,furi_hal_ibutton_pin_write,void,const _Bool Function,+,furi_hal_info_get,void,"PropertyValueCallback, char, void*" +Function,+,furi_hal_info_get_api_version,void,"uint16_t*, uint16_t*" Function,+,furi_hal_infrared_async_rx_set_capture_isr_callback,void,"FuriHalInfraredRxCaptureCallback, void*" Function,+,furi_hal_infrared_async_rx_set_timeout,void,uint32_t Function,+,furi_hal_infrared_async_rx_set_timeout_isr_callback,void,"FuriHalInfraredRxTimeoutCallback, void*" diff --git a/firmware/targets/f7/furi_hal/furi_hal_info.c b/firmware/targets/f7/furi_hal/furi_hal_info.c index c984ef4d560..4c034ff3521 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_info.c +++ b/firmware/targets/f7/furi_hal/furi_hal_info.c @@ -8,6 +8,11 @@ #include #include +FURI_WEAK void furi_hal_info_get_api_version(uint16_t* major, uint16_t* minor) { + *major = 0; + *minor = 0; +} + void furi_hal_info_get(PropertyValueCallback out, char sep, void* context) { FuriString* key = furi_string_alloc(); FuriString* value = furi_string_alloc(); @@ -18,10 +23,10 @@ void furi_hal_info_get(PropertyValueCallback out, char sep, void* context) { // Device Info version if(sep == '.') { property_value_out(&property_context, NULL, 2, "format", "major", "3"); - property_value_out(&property_context, NULL, 2, "format", "minor", "0"); + property_value_out(&property_context, NULL, 2, "format", "minor", "1"); } else { property_value_out(&property_context, NULL, 3, "device", "info", "major", "2"); - property_value_out(&property_context, NULL, 3, "device", "info", "minor", "0"); + property_value_out(&property_context, NULL, 3, "device", "info", "minor", "1"); } // Model name @@ -161,6 +166,13 @@ void furi_hal_info_get(PropertyValueCallback out, char sep, void* context) { version_get_builddate(firmware_version)); property_value_out( &property_context, "%d", 2, "firmware", "target", version_get_target(firmware_version)); + + uint16_t api_version_major, api_version_minor; + furi_hal_info_get_api_version(&api_version_major, &api_version_minor); + property_value_out( + &property_context, "%d", 3, "firmware", "api", "major", api_version_major); + property_value_out( + &property_context, "%d", 3, "firmware", "api", "minor", api_version_minor); } if(furi_hal_bt_is_alive()) { diff --git a/firmware/targets/furi_hal_include/furi_hal_info.h b/firmware/targets/furi_hal_include/furi_hal_info.h index fa3267f5d47..7e8b0e1fb1a 100644 --- a/firmware/targets/furi_hal_include/furi_hal_info.h +++ b/firmware/targets/furi_hal_include/furi_hal_info.h @@ -14,6 +14,8 @@ extern "C" { #endif +void furi_hal_info_get_api_version(uint16_t* major, uint16_t* minor); + /** Get device information * * @param[in] callback callback to provide with new data diff --git a/furi/core/common_defines.h b/furi/core/common_defines.h index 1ec847d4594..d7bfaf2076d 100644 --- a/furi/core/common_defines.h +++ b/furi/core/common_defines.h @@ -15,6 +15,10 @@ extern "C" { #define FURI_WARN_UNUSED __attribute__((warn_unused_result)) #endif +#ifndef FURI_WEAK +#define FURI_WEAK __attribute__((weak)) +#endif + #ifndef FURI_IS_IRQ_MASKED #define FURI_IS_IRQ_MASKED() (__get_PRIMASK() != 0U) #endif From dfbacd1a4753d64d76b1ac85da8f5bc9a46ecbd5 Mon Sep 17 00:00:00 2001 From: Eric Betts Date: Tue, 2 May 2023 21:05:24 -0700 Subject: [PATCH 534/824] [#2612] Remove spaces in CSN(#2616) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- .../external/picopass/scenes/picopass_scene_read_card_success.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/external/picopass/scenes/picopass_scene_read_card_success.c b/applications/external/picopass/scenes/picopass_scene_read_card_success.c index 198b21d986e..cc18ac066a0 100644 --- a/applications/external/picopass/scenes/picopass_scene_read_card_success.c +++ b/applications/external/picopass/scenes/picopass_scene_read_card_success.c @@ -34,7 +34,7 @@ void picopass_scene_read_card_success_on_enter(void* context) { uint8_t csn[PICOPASS_BLOCK_LEN] = {0}; memcpy(csn, AA1[PICOPASS_CSN_BLOCK_INDEX].data, PICOPASS_BLOCK_LEN); for(uint8_t i = 0; i < PICOPASS_BLOCK_LEN; i++) { - furi_string_cat_printf(csn_str, "%02X ", csn[i]); + furi_string_cat_printf(csn_str, "%02X", csn[i]); } bool no_key = picopass_is_memset(pacs->key, 0x00, PICOPASS_BLOCK_LEN); From 59386f9fa9e8b3f9b03823c99405484dddaec071 Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Wed, 3 May 2023 08:15:47 +0400 Subject: [PATCH 535/824] WS: add protocol "Wendox W6726" (#2604) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * WS: add protocol "Wendox" * WS: add bat status * WS: add CRC, refactoring * WS: description added * WS: fix name file * WeatherStation: cleanup alien symbols Co-authored-by: あく --- .../protocols/protocol_items.c | 1 + .../protocols/protocol_items.h | 1 + .../weather_station/protocols/wendox_w6726.c | 299 ++++++++++++++++++ .../weather_station/protocols/wendox_w6726.h | 80 +++++ 4 files changed, 381 insertions(+) create mode 100644 applications/external/weather_station/protocols/wendox_w6726.c create mode 100644 applications/external/weather_station/protocols/wendox_w6726.h diff --git a/applications/external/weather_station/protocols/protocol_items.c b/applications/external/weather_station/protocols/protocol_items.c index 2c9d751c7ef..cd4bae76dc2 100644 --- a/applications/external/weather_station/protocols/protocol_items.c +++ b/applications/external/weather_station/protocols/protocol_items.c @@ -16,6 +16,7 @@ const SubGhzProtocol* weather_station_protocol_registry_items[] = { &ws_protocol_auriol_th, &ws_protocol_oregon_v1, &ws_protocol_tx_8300, + &ws_protocol_wendox_w6726, }; const SubGhzProtocolRegistry weather_station_protocol_registry = { diff --git a/applications/external/weather_station/protocols/protocol_items.h b/applications/external/weather_station/protocols/protocol_items.h index f9e443abcff..0398c11f256 100644 --- a/applications/external/weather_station/protocols/protocol_items.h +++ b/applications/external/weather_station/protocols/protocol_items.h @@ -16,5 +16,6 @@ #include "auriol_hg0601a.h" #include "oregon_v1.h" #include "tx_8300.h" +#include "wendox_w6726.h" extern const SubGhzProtocolRegistry weather_station_protocol_registry; diff --git a/applications/external/weather_station/protocols/wendox_w6726.c b/applications/external/weather_station/protocols/wendox_w6726.c new file mode 100644 index 00000000000..2fbe961f725 --- /dev/null +++ b/applications/external/weather_station/protocols/wendox_w6726.c @@ -0,0 +1,299 @@ +#include "wendox_w6726.h" + +#define TAG "WSProtocolWendoxW6726" + +/* + * Wendox W6726 + * + * Temperature -50C to +70C + * _ _ _ __ _ + * _| |___| |___| |___ ... | |_| |__...._______________ + * preamble data guard time + * + * 3 reps every 3 minutes + * in the first message 11 bytes of the preamble in the rest by 7 + * + * bit 0: 1955-hi, 5865-lo + * bit 1: 5865-hi, 1955-lo + * guard time: 12*1955+(lo last bit) + * data: 29 bit + * + * IIIII | ZTTTTTTTTT | uuuuuuuBuu | CCCC + * + * I: identification; + * Z: temperature sign; + * T: temperature sign dependent +12C; + * B: battery low; flag to indicate low battery voltage; + * C: CRC4 (polynomial = 0x9, start_data = 0xD); + * u: unknown; + */ + +static const SubGhzBlockConst ws_protocol_wendox_w6726_const = { + .te_short = 1955, + .te_long = 5865, + .te_delta = 300, + .min_count_bit_for_found = 29, +}; + +struct WSProtocolDecoderWendoxW6726 { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + WSBlockGeneric generic; + + uint16_t header_count; +}; + +struct WSProtocolEncoderWendoxW6726 { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + WSBlockGeneric generic; +}; + +typedef enum { + WendoxW6726DecoderStepReset = 0, + WendoxW6726DecoderStepCheckPreambule, + WendoxW6726DecoderStepSaveDuration, + WendoxW6726DecoderStepCheckDuration, +} WendoxW6726DecoderStep; + +const SubGhzProtocolDecoder ws_protocol_wendox_w6726_decoder = { + .alloc = ws_protocol_decoder_wendox_w6726_alloc, + .free = ws_protocol_decoder_wendox_w6726_free, + + .feed = ws_protocol_decoder_wendox_w6726_feed, + .reset = ws_protocol_decoder_wendox_w6726_reset, + + .get_hash_data = ws_protocol_decoder_wendox_w6726_get_hash_data, + .serialize = ws_protocol_decoder_wendox_w6726_serialize, + .deserialize = ws_protocol_decoder_wendox_w6726_deserialize, + .get_string = ws_protocol_decoder_wendox_w6726_get_string, +}; + +const SubGhzProtocolEncoder ws_protocol_wendox_w6726_encoder = { + .alloc = NULL, + .free = NULL, + + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol ws_protocol_wendox_w6726 = { + .name = WS_PROTOCOL_WENDOX_W6726_NAME, + .type = SubGhzProtocolWeatherStation, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable, + + .decoder = &ws_protocol_wendox_w6726_decoder, + .encoder = &ws_protocol_wendox_w6726_encoder, +}; + +void* ws_protocol_decoder_wendox_w6726_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + WSProtocolDecoderWendoxW6726* instance = malloc(sizeof(WSProtocolDecoderWendoxW6726)); + instance->base.protocol = &ws_protocol_wendox_w6726; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void ws_protocol_decoder_wendox_w6726_free(void* context) { + furi_assert(context); + WSProtocolDecoderWendoxW6726* instance = context; + free(instance); +} + +void ws_protocol_decoder_wendox_w6726_reset(void* context) { + furi_assert(context); + WSProtocolDecoderWendoxW6726* instance = context; + instance->decoder.parser_step = WendoxW6726DecoderStepReset; +} + +static bool ws_protocol_wendox_w6726_check(WSProtocolDecoderWendoxW6726* instance) { + if(!instance->decoder.decode_data) return false; + uint8_t msg[] = { + instance->decoder.decode_data >> 28, + instance->decoder.decode_data >> 20, + instance->decoder.decode_data >> 12, + instance->decoder.decode_data >> 4}; + + uint8_t crc = subghz_protocol_blocks_crc4(msg, 4, 0x9, 0xD); + return (crc == (instance->decoder.decode_data & 0x0F)); +} + +/** + * Analysis of received data + * @param instance Pointer to a WSBlockGeneric* instance + */ +static void ws_protocol_wendox_w6726_remote_controller(WSBlockGeneric* instance) { + instance->id = (instance->data >> 24) & 0xFF; + instance->battery_low = (instance->data >> 6) & 1; + instance->channel = WS_NO_CHANNEL; + + if(((instance->data >> 23) & 1)) { + instance->temp = (float)(((instance->data >> 14) & 0x1FF) + 12) / 10.0f; + } else { + instance->temp = (float)((~(instance->data >> 14) & 0x1FF) + 1 - 12) / -10.0f; + } + + if(instance->temp < -50.0f) { + instance->temp = -50.0f; + } else if(instance->temp > 70.0f) { + instance->temp = 70.0f; + } + + instance->btn = WS_NO_BTN; + instance->humidity = WS_NO_HUMIDITY; +} + +void ws_protocol_decoder_wendox_w6726_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + WSProtocolDecoderWendoxW6726* instance = context; + + switch(instance->decoder.parser_step) { + case WendoxW6726DecoderStepReset: + if((level) && (DURATION_DIFF(duration, ws_protocol_wendox_w6726_const.te_short) < + ws_protocol_wendox_w6726_const.te_delta)) { + instance->decoder.parser_step = WendoxW6726DecoderStepCheckPreambule; + instance->decoder.te_last = duration; + instance->header_count = 0; + } + break; + + case WendoxW6726DecoderStepCheckPreambule: + if(level) { + instance->decoder.te_last = duration; + } else { + if((DURATION_DIFF(instance->decoder.te_last, ws_protocol_wendox_w6726_const.te_short) < + ws_protocol_wendox_w6726_const.te_delta * 1) && + (DURATION_DIFF(duration, ws_protocol_wendox_w6726_const.te_long) < + ws_protocol_wendox_w6726_const.te_delta * 2)) { + instance->header_count++; + } else if((instance->header_count > 4) && (instance->header_count < 12)) { + if((DURATION_DIFF( + instance->decoder.te_last, ws_protocol_wendox_w6726_const.te_long) < + ws_protocol_wendox_w6726_const.te_delta * 2) && + (DURATION_DIFF(duration, ws_protocol_wendox_w6726_const.te_short) < + ws_protocol_wendox_w6726_const.te_delta)) { + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = WendoxW6726DecoderStepSaveDuration; + } else { + instance->decoder.parser_step = WendoxW6726DecoderStepReset; + } + + } else { + instance->decoder.parser_step = WendoxW6726DecoderStepReset; + } + } + break; + + case WendoxW6726DecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = WendoxW6726DecoderStepCheckDuration; + } else { + instance->decoder.parser_step = WendoxW6726DecoderStepReset; + } + break; + + case WendoxW6726DecoderStepCheckDuration: + if(!level) { + if(duration > + ws_protocol_wendox_w6726_const.te_short + ws_protocol_wendox_w6726_const.te_long) { + if(DURATION_DIFF( + instance->decoder.te_last, ws_protocol_wendox_w6726_const.te_short) < + ws_protocol_wendox_w6726_const.te_delta) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = WendoxW6726DecoderStepSaveDuration; + } else if( + DURATION_DIFF( + instance->decoder.te_last, ws_protocol_wendox_w6726_const.te_long) < + ws_protocol_wendox_w6726_const.te_delta * 2) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = WendoxW6726DecoderStepSaveDuration; + } else { + instance->decoder.parser_step = WendoxW6726DecoderStepReset; + } + if((instance->decoder.decode_count_bit == + ws_protocol_wendox_w6726_const.min_count_bit_for_found) && + ws_protocol_wendox_w6726_check(instance)) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + ws_protocol_wendox_w6726_remote_controller(&instance->generic); + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + + instance->decoder.parser_step = WendoxW6726DecoderStepReset; + } else if( + (DURATION_DIFF(instance->decoder.te_last, ws_protocol_wendox_w6726_const.te_short) < + ws_protocol_wendox_w6726_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_wendox_w6726_const.te_long) < + ws_protocol_wendox_w6726_const.te_delta * 3)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = WendoxW6726DecoderStepSaveDuration; + } else if( + (DURATION_DIFF(instance->decoder.te_last, ws_protocol_wendox_w6726_const.te_long) < + ws_protocol_wendox_w6726_const.te_delta * 2) && + (DURATION_DIFF(duration, ws_protocol_wendox_w6726_const.te_short) < + ws_protocol_wendox_w6726_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = WendoxW6726DecoderStepSaveDuration; + } else { + instance->decoder.parser_step = WendoxW6726DecoderStepReset; + } + } else { + instance->decoder.parser_step = WendoxW6726DecoderStepReset; + } + break; + } +} + +uint8_t ws_protocol_decoder_wendox_w6726_get_hash_data(void* context) { + furi_assert(context); + WSProtocolDecoderWendoxW6726* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +SubGhzProtocolStatus ws_protocol_decoder_wendox_w6726_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + WSProtocolDecoderWendoxW6726* instance = context; + return ws_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +SubGhzProtocolStatus + ws_protocol_decoder_wendox_w6726_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + WSProtocolDecoderWendoxW6726* instance = context; + return ws_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + ws_protocol_wendox_w6726_const.min_count_bit_for_found); +} + +void ws_protocol_decoder_wendox_w6726_get_string(void* context, FuriString* output) { + furi_assert(context); + WSProtocolDecoderWendoxW6726* instance = context; + furi_string_printf( + output, + "%s %dbit\r\n" + "Key:0x%lX%08lX\r\n" + "Sn:0x%lX Ch:%d Bat:%d\r\n" + "Temp:%3.1f C Hum:%d%%", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data >> 32), + (uint32_t)(instance->generic.data), + instance->generic.id, + instance->generic.channel, + instance->generic.battery_low, + (double)instance->generic.temp, + instance->generic.humidity); +} diff --git a/applications/external/weather_station/protocols/wendox_w6726.h b/applications/external/weather_station/protocols/wendox_w6726.h new file mode 100644 index 00000000000..236777a1c86 --- /dev/null +++ b/applications/external/weather_station/protocols/wendox_w6726.h @@ -0,0 +1,80 @@ +#pragma once + +#include + +#include +#include +#include +#include "ws_generic.h" +#include + +#define WS_PROTOCOL_WENDOX_W6726_NAME "Wendox W6726" + +typedef struct WSProtocolDecoderWendoxW6726 WSProtocolDecoderWendoxW6726; +typedef struct WSProtocolEncoderWendoxW6726 WSProtocolEncoderWendoxW6726; + +extern const SubGhzProtocolDecoder ws_protocol_wendox_w6726_decoder; +extern const SubGhzProtocolEncoder ws_protocol_wendox_w6726_encoder; +extern const SubGhzProtocol ws_protocol_wendox_w6726; + +/** + * Allocate WSProtocolDecoderWendoxW6726. + * @param environment Pointer to a SubGhzEnvironment instance + * @return WSProtocolDecoderWendoxW6726* pointer to a WSProtocolDecoderWendoxW6726 instance + */ +void* ws_protocol_decoder_wendox_w6726_alloc(SubGhzEnvironment* environment); + +/** + * Free WSProtocolDecoderWendoxW6726. + * @param context Pointer to a WSProtocolDecoderWendoxW6726 instance + */ +void ws_protocol_decoder_wendox_w6726_free(void* context); + +/** + * Reset decoder WSProtocolDecoderWendoxW6726. + * @param context Pointer to a WSProtocolDecoderWendoxW6726 instance + */ +void ws_protocol_decoder_wendox_w6726_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a WSProtocolDecoderWendoxW6726 instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void ws_protocol_decoder_wendox_w6726_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a WSProtocolDecoderWendoxW6726 instance + * @return hash Hash sum + */ +uint8_t ws_protocol_decoder_wendox_w6726_get_hash_data(void* context); + +/** + * Serialize data WSProtocolDecoderWendoxW6726. + * @param context Pointer to a WSProtocolDecoderWendoxW6726 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return status + */ +SubGhzProtocolStatus ws_protocol_decoder_wendox_w6726_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data WSProtocolDecoderWendoxW6726. + * @param context Pointer to a WSProtocolDecoderWendoxW6726 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + ws_protocol_decoder_wendox_w6726_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a WSProtocolDecoderWendoxW6726 instance + * @param output Resulting text + */ +void ws_protocol_decoder_wendox_w6726_get_string(void* context, FuriString* output); From 015ab4a0242c9c9c7ff182afd42b81cbecee758c Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Wed, 3 May 2023 07:39:14 +0300 Subject: [PATCH 536/824] [#2591] BadUSB: command parser fix (#2607) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- .../bad_usb/helpers/ducky_script_commands.c | 41 +++++++++++-------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/applications/main/bad_usb/helpers/ducky_script_commands.c b/applications/main/bad_usb/helpers/ducky_script_commands.c index 1498c9a73fc..d073b4c8d62 100644 --- a/applications/main/bad_usb/helpers/ducky_script_commands.c +++ b/applications/main/bad_usb/helpers/ducky_script_commands.c @@ -152,22 +152,22 @@ static int32_t ducky_fnc_waitforbutton(BadUsbScript* bad_usb, const char* line, } static const DuckyCmd ducky_commands[] = { - {"REM ", NULL, -1}, - {"ID ", NULL, -1}, - {"DELAY ", ducky_fnc_delay, -1}, - {"STRING ", ducky_fnc_string, 0}, - {"STRINGLN ", ducky_fnc_string, 1}, - {"DEFAULT_DELAY ", ducky_fnc_defdelay, -1}, - {"DEFAULTDELAY ", ducky_fnc_defdelay, -1}, - {"STRINGDELAY ", ducky_fnc_strdelay, -1}, - {"STRING_DELAY ", ducky_fnc_strdelay, -1}, - {"REPEAT ", ducky_fnc_repeat, -1}, - {"SYSRQ ", ducky_fnc_sysrq, -1}, - {"ALTCHAR ", ducky_fnc_altchar, -1}, - {"ALTSTRING ", ducky_fnc_altstring, -1}, - {"ALTCODE ", ducky_fnc_altstring, -1}, - {"HOLD ", ducky_fnc_hold, -1}, - {"RELEASE ", ducky_fnc_release, -1}, + {"REM", NULL, -1}, + {"ID", NULL, -1}, + {"DELAY", ducky_fnc_delay, -1}, + {"STRING", ducky_fnc_string, 0}, + {"STRINGLN", ducky_fnc_string, 1}, + {"DEFAULT_DELAY", ducky_fnc_defdelay, -1}, + {"DEFAULTDELAY", ducky_fnc_defdelay, -1}, + {"STRINGDELAY", ducky_fnc_strdelay, -1}, + {"STRING_DELAY", ducky_fnc_strdelay, -1}, + {"REPEAT", ducky_fnc_repeat, -1}, + {"SYSRQ", ducky_fnc_sysrq, -1}, + {"ALTCHAR", ducky_fnc_altchar, -1}, + {"ALTSTRING", ducky_fnc_altstring, -1}, + {"ALTCODE", ducky_fnc_altstring, -1}, + {"HOLD", ducky_fnc_hold, -1}, + {"RELEASE", ducky_fnc_release, -1}, {"WAIT_FOR_BUTTON_PRESS", ducky_fnc_waitforbutton, -1}, }; @@ -175,8 +175,15 @@ static const DuckyCmd ducky_commands[] = { #define WORKER_TAG TAG "Worker" int32_t ducky_execute_cmd(BadUsbScript* bad_usb, const char* line) { + size_t cmd_word_len = strcspn(line, " "); for(size_t i = 0; i < COUNT_OF(ducky_commands); i++) { - if(strncmp(line, ducky_commands[i].name, strlen(ducky_commands[i].name)) == 0) { + size_t cmd_compare_len = strlen(ducky_commands[i].name); + + if(cmd_compare_len != cmd_word_len) { + continue; + } + + if(strncmp(line, ducky_commands[i].name, cmd_compare_len) == 0) { if(ducky_commands[i].callback == NULL) { return 0; } else { From c3ececcf969576f2099fd45be80d5e4f213dc9b5 Mon Sep 17 00:00:00 2001 From: hedger Date: Wed, 3 May 2023 08:48:49 +0300 Subject: [PATCH 537/824] [FL-3174] Dolphin builder in ufbt; minor ufbt/fbt improvements (#2601) * ufbt: added "dolphin_ext" target (expects "external" subfolder in cwd with dolphin assets); cleaned up unused code * ufbt: codestyle fixes * scripts: fixed style according to ruff linter * scripts: additional cleanup & codestyle fixes * github: pass target hw code when installing local SDK with ufbt * ufbt: added error message for missing folder in dolphin builder * scripts: more linter fixes * sdk: added flipper_format_stream; ufbt: support for --extra-define * fbt: reduced amount of global defines * scripts, fbt: rearranged imports Co-authored-by: Aleksandr Kutuzov --- .github/workflows/build.yml | 2 + assets/SConscript | 4 +- firmware.scons | 4 +- firmware/targets/f18/api_symbols.csv | 8 +++- firmware/targets/f7/api_symbols.csv | 8 +++- lib/flipper_format/SConscript | 1 + lib/freertos.scons | 3 -- lib/libusb_stm32.scons | 8 ++-- lib/littlefs.scons | 8 ++-- lib/misc.scons | 4 +- lib/toolbox/SConscript | 4 +- scripts/assets.py | 32 ++++++------- scripts/distfap.py | 6 +-- scripts/fbt/appmanifest.py | 10 ++--- scripts/fbt/elfmanifest.py | 4 +- scripts/fbt/fapassets.py | 2 +- scripts/fbt/sdk/cache.py | 17 +++---- scripts/fbt/util.py | 9 ++-- scripts/fbt/version.py | 2 +- scripts/fbt_tools/ccache.py | 2 +- scripts/fbt_tools/crosscc.py | 14 +++--- scripts/fbt_tools/fbt_apps.py | 13 +++--- scripts/fbt_tools/fbt_assets.py | 14 +++--- scripts/fbt_tools/fbt_debugopts.py | 2 - scripts/fbt_tools/fbt_dist.py | 3 +- scripts/fbt_tools/fbt_extapps.py | 18 +++----- scripts/fbt_tools/fbt_hwtarget.py | 2 - scripts/fbt_tools/fbt_sdk.py | 19 ++++---- scripts/fbt_tools/fbt_tweaks.py | 12 +++-- scripts/fbt_tools/fbt_version.py | 2 +- scripts/fbt_tools/fwbin.py | 4 +- scripts/fbt_tools/gdb.py | 4 -- scripts/fbt_tools/objdump.py | 2 +- scripts/fbt_tools/openocd.py | 4 +- scripts/fbt_tools/pvsstudio.py | 13 +++--- scripts/fbt_tools/sconsmodular.py | 3 +- scripts/fbt_tools/sconsrecursiveglob.py | 2 +- scripts/fbt_tools/strip.py | 2 +- scripts/flash.py | 50 ++++++++++----------- scripts/flipper/app.py | 2 +- scripts/flipper/assets/copro.py | 8 ++-- scripts/flipper/assets/coprobin.py | 3 +- scripts/flipper/assets/dolphin.py | 12 +++-- scripts/flipper/assets/icon.py | 12 ++--- scripts/flipper/assets/manifest.py | 5 +-- scripts/flipper/assets/obdata.py | 4 +- scripts/flipper/cube.py | 8 ++-- scripts/flipper/storage.py | 4 +- scripts/flipper/utils/__init__.py | 1 - scripts/flipper/utils/programmer_openocd.py | 10 ++--- scripts/flipper/utils/stm32wb55.py | 6 +-- scripts/flipper/utils/templite.py | 4 +- scripts/fwsize.py | 7 +-- scripts/get_env.py | 10 ++--- scripts/lint.py | 11 +++-- scripts/merge_report_qa.py | 3 +- scripts/meta.py | 3 +- scripts/ob.py | 4 +- scripts/otp.py | 37 +++++++-------- scripts/program.py | 14 +++--- scripts/runfap.py | 12 +++-- scripts/sconsdist.py | 2 +- scripts/selfupdate.py | 12 +++-- scripts/serial_cli.py | 5 ++- scripts/storage.py | 12 ++--- scripts/ufbt/SConstruct | 37 ++++++++++----- scripts/ufbt/commandline.scons | 32 ++++--------- scripts/ufbt/site_init.py | 6 +-- scripts/ufbt/site_tools/ufbt_state.py | 7 ++- scripts/ufbt/update.scons | 37 --------------- scripts/update.py | 20 ++++----- scripts/version.py | 8 ++-- site_scons/extapps.scons | 4 +- 73 files changed, 306 insertions(+), 377 deletions(-) delete mode 100644 scripts/ufbt/update.scons diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0934eec76d5..d69318530bb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -193,12 +193,14 @@ jobs: TARGET="$(echo '${{ matrix.target }}' | sed 's/f//')"; \ ./fbt TARGET_HW=$TARGET DEBUG=0 COMPACT=1 fap_dist updater_package echo "sdk-file=$(ls dist/${{ matrix.target }}-*/flipper-z-${{ matrix.target }}-sdk-*.zip)" >> $GITHUB_OUTPUT + echo "hw-target-code=$TARGET" >> $GITHUB_OUTPUT - name: Deploy uFBT with SDK uses: flipperdevices/flipperzero-ufbt-action@v0.1.0 with: task: setup sdk-file: ${{ steps.build-fw.outputs.sdk-file }} + sdk-hw-target: ${{ steps.build-fw.outputs.hw-target-code }} - name: Build test app with SDK run: | diff --git a/assets/SConscript b/assets/SConscript index 21437aa301b..9bd273626d1 100644 --- a/assets/SConscript +++ b/assets/SConscript @@ -1,7 +1,7 @@ -Import("env") - from fbt.version import get_git_commit_unix_timestamp +Import("env") + assetsenv = env.Clone( tools=["fbt_assets"], FW_LIB_NAME="assets", diff --git a/firmware.scons b/firmware.scons index c7fdc639252..c4699689913 100644 --- a/firmware.scons +++ b/firmware.scons @@ -1,5 +1,3 @@ -Import("ENV", "fw_build_meta") - from SCons.Errors import UserError from SCons.Node import FS @@ -10,6 +8,8 @@ from fbt_extra.util import ( link_elf_dir_as_latest, ) +Import("ENV", "fw_build_meta") + # Building initial C environment for libs env = ENV.Clone( tools=[ diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index bdd5d6b61fe..f84bf074e16 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,23.1,, +Version,+,23.3,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -112,6 +112,7 @@ Header,+,lib/flipper_application/plugins/composite_resolver.h,, Header,+,lib/flipper_application/plugins/plugin_manager.h,, Header,+,lib/flipper_format/flipper_format.h,, Header,+,lib/flipper_format/flipper_format_i.h,, +Header,+,lib/flipper_format/flipper_format_stream.h,, Header,+,lib/libusb_stm32/inc/hid_usage_button.h,, Header,+,lib/libusb_stm32/inc/hid_usage_consumer.h,, Header,+,lib/libusb_stm32/inc/hid_usage_desktop.h,, @@ -755,6 +756,11 @@ Function,+,flipper_format_read_uint32,_Bool,"FlipperFormat*, const char*, uint32 Function,+,flipper_format_rewind,_Bool,FlipperFormat* Function,+,flipper_format_seek_to_end,_Bool,FlipperFormat* Function,+,flipper_format_set_strict_mode,void,"FlipperFormat*, _Bool" +Function,+,flipper_format_stream_delete_key_and_write,_Bool,"Stream*, FlipperStreamWriteData*, _Bool" +Function,+,flipper_format_stream_get_value_count,_Bool,"Stream*, const char*, uint32_t*, _Bool" +Function,+,flipper_format_stream_read_value_line,_Bool,"Stream*, const char*, FlipperStreamValue, void*, size_t, _Bool" +Function,+,flipper_format_stream_write_comment_cstr,_Bool,"Stream*, const char*" +Function,+,flipper_format_stream_write_value_line,_Bool,"Stream*, FlipperStreamWriteData*" Function,+,flipper_format_string_alloc,FlipperFormat*, Function,+,flipper_format_update_bool,_Bool,"FlipperFormat*, const char*, const _Bool*, const uint16_t" Function,+,flipper_format_update_float,_Bool,"FlipperFormat*, const char*, const float*, const uint16_t" diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 9f4ec4261c1..c176b2d7e1e 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,23.1,, +Version,+,23.3,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -118,6 +118,7 @@ Header,+,lib/flipper_application/plugins/composite_resolver.h,, Header,+,lib/flipper_application/plugins/plugin_manager.h,, Header,+,lib/flipper_format/flipper_format.h,, Header,+,lib/flipper_format/flipper_format_i.h,, +Header,+,lib/flipper_format/flipper_format_stream.h,, Header,+,lib/ibutton/ibutton_key.h,, Header,+,lib/ibutton/ibutton_protocols.h,, Header,+,lib/ibutton/ibutton_worker.h,, @@ -918,6 +919,11 @@ Function,+,flipper_format_read_uint32,_Bool,"FlipperFormat*, const char*, uint32 Function,+,flipper_format_rewind,_Bool,FlipperFormat* Function,+,flipper_format_seek_to_end,_Bool,FlipperFormat* Function,+,flipper_format_set_strict_mode,void,"FlipperFormat*, _Bool" +Function,+,flipper_format_stream_delete_key_and_write,_Bool,"Stream*, FlipperStreamWriteData*, _Bool" +Function,+,flipper_format_stream_get_value_count,_Bool,"Stream*, const char*, uint32_t*, _Bool" +Function,+,flipper_format_stream_read_value_line,_Bool,"Stream*, const char*, FlipperStreamValue, void*, size_t, _Bool" +Function,+,flipper_format_stream_write_comment_cstr,_Bool,"Stream*, const char*" +Function,+,flipper_format_stream_write_value_line,_Bool,"Stream*, FlipperStreamWriteData*" Function,+,flipper_format_string_alloc,FlipperFormat*, Function,+,flipper_format_update_bool,_Bool,"FlipperFormat*, const char*, const _Bool*, const uint16_t" Function,+,flipper_format_update_float,_Bool,"FlipperFormat*, const char*, const float*, const uint16_t" diff --git a/lib/flipper_format/SConscript b/lib/flipper_format/SConscript index 353da803570..9c9e8b6f338 100644 --- a/lib/flipper_format/SConscript +++ b/lib/flipper_format/SConscript @@ -7,6 +7,7 @@ env.Append( SDK_HEADERS=[ File("flipper_format.h"), File("flipper_format_i.h"), + File("flipper_format_stream.h"), ], ) diff --git a/lib/freertos.scons b/lib/freertos.scons index 1c5a5bf54fb..cb0006e560f 100644 --- a/lib/freertos.scons +++ b/lib/freertos.scons @@ -7,9 +7,6 @@ env.Append( "#/lib/FreeRTOS-Kernel/portable/GCC/ARM_CM4F", "#/lib/FreeRTOS-glue", ], - CPPDEFINES=[ - "HAVE_FREERTOS", - ], ) diff --git a/lib/libusb_stm32.scons b/lib/libusb_stm32.scons index 4838b7c509a..ccc5de24f09 100644 --- a/lib/libusb_stm32.scons +++ b/lib/libusb_stm32.scons @@ -4,9 +4,6 @@ env.Append( CPPPATH=[ "#/lib/libusb_stm32/inc", ], - CPPDEFINES=[ - ("USB_PMASIZE", "0x400"), - ], SDK_HEADERS=env.GlobRecursive( "*.h", Dir("libusb_stm32/inc"), @@ -16,6 +13,11 @@ env.Append( libenv = env.Clone(FW_LIB_NAME="usb_stm32") libenv.ApplyLibFlags() +libenv.Append( + CPPDEFINES=[ + ("USB_PMASIZE", "0x400"), + ], +) sources = [ diff --git a/lib/littlefs.scons b/lib/littlefs.scons index 792142c32f0..3d68e07bae8 100644 --- a/lib/littlefs.scons +++ b/lib/littlefs.scons @@ -4,14 +4,16 @@ env.Append( CPPPATH=[ "#/lib/littlefs", ], - CPPDEFINES=[ - ("LFS_CONFIG", "lfs_config.h"), - ], ) libenv = env.Clone(FW_LIB_NAME="littlefs") libenv.ApplyLibFlags() +libenv.Append( + CPPDEFINES=[ + ("LFS_CONFIG", "lfs_config.h"), + ], +) sources = Glob("littlefs/*.c", source=True) diff --git a/lib/misc.scons b/lib/misc.scons index b479851b126..1ff6e2fb0ce 100644 --- a/lib/misc.scons +++ b/lib/misc.scons @@ -1,7 +1,7 @@ -Import("env") - from fbt.util import GLOB_FILE_EXCLUSION +Import("env") + env.Append( CPPPATH=[ "#/lib/digital_signal", diff --git a/lib/toolbox/SConscript b/lib/toolbox/SConscript index bb06c2db4b2..4e158e30ea7 100644 --- a/lib/toolbox/SConscript +++ b/lib/toolbox/SConscript @@ -1,8 +1,8 @@ -Import("env") - from fbt.version import get_fast_git_version_id +Import("env") + env.Append( CPPPATH=[ "#/lib/toolbox", diff --git a/scripts/assets.py b/scripts/assets.py index 75bebcfb454..c0fa9100b71 100755 --- a/scripts/assets.py +++ b/scripts/assets.py @@ -1,10 +1,10 @@ #!/usr/bin/env python3 +import os + from flipper.app import App from flipper.assets.icon import file2image -import os - ICONS_SUPPORTED_FORMATS = ["png"] ICONS_TEMPLATE_H_HEADER = """#pragma once @@ -127,7 +127,7 @@ def icons(self): if not filenames: continue if "frame_rate" in filenames: - self.logger.debug(f"Folder contains animation") + self.logger.debug("Folder contains animation") icon_name = "A_" + os.path.split(dirpath)[1].replace("-", "_") width = height = None frame_count = 0 @@ -186,7 +186,7 @@ def icons(self): icons_c.write("\n") icons.append((icon_name, width, height, 0, 1)) # Create array of images: - self.logger.debug(f"Finalizing source file") + self.logger.debug("Finalizing source file") for name, width, height, frame_rate, frame_count in icons: icons_c.write( ICONS_TEMPLATE_C_ICONS.format( @@ -201,7 +201,7 @@ def icons(self): icons_c.close() # Create Public Header - self.logger.debug(f"Creating header") + self.logger.debug("Creating header") icons_h = open( os.path.join(self.args.output_directory, f"{self.args.filename}.h"), "w", @@ -211,7 +211,7 @@ def icons(self): for name, width, height, frame_rate, frame_count in icons: icons_h.write(ICONS_TEMPLATE_H_ICON_NAME.format(name=name)) icons_h.close() - self.logger.debug(f"Done") + self.logger.debug("Done") return 0 def manifest(self): @@ -232,7 +232,7 @@ def manifest(self): new_manifest = Manifest(self.args.timestamp) new_manifest.create(directory_path) - self.logger.info(f"Comparing new manifest with existing") + self.logger.info("Comparing new manifest with existing") only_in_old, changed, only_in_new = Manifest.compare(old_manifest, new_manifest) for record in only_in_old: self.logger.info(f"Only in old: {record}") @@ -246,38 +246,38 @@ def manifest(self): else: self.logger.info("Manifest is up-to-date!") - self.logger.info(f"Complete") + self.logger.info("Complete") return 0 def copro(self): from flipper.assets.copro import Copro - self.logger.info(f"Bundling coprocessor binaries") + self.logger.info("Bundling coprocessor binaries") copro = Copro(self.args.mcu) - self.logger.info(f"Loading CUBE info") + self.logger.info("Loading CUBE info") copro.loadCubeInfo(self.args.cube_dir, self.args.cube_ver) - self.logger.info(f"Bundling") + self.logger.info("Bundling") copro.bundle( self.args.output_dir, self.args.stack_file, self.args.stack_type, self.args.stack_addr, ) - self.logger.info(f"Complete") + self.logger.info("Complete") return 0 def dolphin(self): from flipper.assets.dolphin import Dolphin - self.logger.info(f"Processing Dolphin sources") + self.logger.info("Processing Dolphin sources") dolphin = Dolphin() - self.logger.info(f"Loading data") + self.logger.info("Loading data") dolphin.load(self.args.input_directory) - self.logger.info(f"Packing") + self.logger.info("Packing") dolphin.pack(self.args.output_directory, self.args.symbol_name) - self.logger.info(f"Complete") + self.logger.info("Complete") return 0 diff --git a/scripts/distfap.py b/scripts/distfap.py index 060fe26fff1..d330988b51e 100644 --- a/scripts/distfap.py +++ b/scripts/distfap.py @@ -1,12 +1,12 @@ #!/usr/bin/env python3 +import os +import posixpath + from flipper.app import App from flipper.storage import FlipperStorage, FlipperStorageOperations from flipper.utils.cdc import resolve_port -import os -import posixpath - class Main(App): def init(self): diff --git a/scripts/fbt/appmanifest.py b/scripts/fbt/appmanifest.py index 37ddc434865..5e41f4c0e30 100644 --- a/scripts/fbt/appmanifest.py +++ b/scripts/fbt/appmanifest.py @@ -1,7 +1,7 @@ +import os from dataclasses import dataclass, field -from typing import List, Optional, Tuple, Callable from enum import Enum -import os +from typing import Callable, List, Optional, Tuple class FlipperManifestException(Exception): @@ -93,7 +93,7 @@ def __init__(self): def get(self, appname: str): try: return self.known_apps[appname] - except KeyError as _: + except KeyError: raise FlipperManifestException( f"Missing application manifest for '{appname}'" ) @@ -223,6 +223,7 @@ def _check_if_app_target_supported(self, app_name: str): return self.appmgr.get(app_name).supports_hardware_target(self.hw_target) def _get_app_depends(self, app_name: str) -> List[str]: + app_def = self.appmgr.get(app_name) # Skip app if its target is not supported by the target we are building for if not self._check_if_app_target_supported(app_name): self._writer( @@ -230,7 +231,6 @@ def _get_app_depends(self, app_name: str) -> List[str]: ) return [] - app_def = self.appmgr.get(app_name) return list( filter( self._check_if_app_target_supported, @@ -296,7 +296,7 @@ def _group_plugins(self): try: parent_app = self.appmgr.get(parent_app_id) parent_app._plugins.append(extension_app) - except FlipperManifestException as e: + except FlipperManifestException: self._writer( f"Module {extension_app.appid} has unknown parent {parent_app_id}" ) diff --git a/scripts/fbt/elfmanifest.py b/scripts/fbt/elfmanifest.py index 17bceddf4e3..333888e140d 100644 --- a/scripts/fbt/elfmanifest.py +++ b/scripts/fbt/elfmanifest.py @@ -1,12 +1,10 @@ -from dataclasses import dataclass import os - import struct from dataclasses import dataclass, field -from .appmanifest import FlipperApplication from flipper.assets.icon import file2image +from .appmanifest import FlipperApplication _MANIFEST_MAGIC = 0x52474448 diff --git a/scripts/fbt/fapassets.py b/scripts/fbt/fapassets.py index 0649f03efe8..9902fd79a16 100644 --- a/scripts/fbt/fapassets.py +++ b/scripts/fbt/fapassets.py @@ -1,5 +1,5 @@ -import os import hashlib +import os import struct from typing import TypedDict diff --git a/scripts/fbt/sdk/cache.py b/scripts/fbt/sdk/cache.py index 756c3782755..b6f6edbe512 100644 --- a/scripts/fbt/sdk/cache.py +++ b/scripts/fbt/sdk/cache.py @@ -1,20 +1,13 @@ -import operator -import os import csv import operator - -from enum import Enum, auto -from typing import Set, ClassVar, Any +import os from dataclasses import dataclass +from enum import Enum, auto +from typing import Any, ClassVar, Set from ansi.color import fg -from . import ( - ApiEntries, - ApiEntryFunction, - ApiEntryVariable, - ApiHeader, -) +from . import ApiEntries, ApiEntryFunction, ApiEntryVariable, ApiHeader @dataclass(frozen=True) @@ -137,7 +130,7 @@ def save(self) -> None: f"API version is still WIP: {self.version}. Review the changes and re-run command." ) ) - print(f"CSV file entries to mark up:") + print("CSV file entries to mark up:") print( fg.yellow( "\n".join( diff --git a/scripts/fbt/util.py b/scripts/fbt/util.py index 7bdaea031fe..ae850a8c3c0 100644 --- a/scripts/fbt/util.py +++ b/scripts/fbt/util.py @@ -1,10 +1,9 @@ -import SCons -from SCons.Subst import quote_spaces -from SCons.Errors import StopError - -import re import os +import re +import SCons +from SCons.Errors import StopError +from SCons.Subst import quote_spaces WINPATHSEP_RE = re.compile(r"\\([^\"'\\]|$)") diff --git a/scripts/fbt/version.py b/scripts/fbt/version.py index e7fe2edaf64..09f48c8eb8a 100644 --- a/scripts/fbt/version.py +++ b/scripts/fbt/version.py @@ -1,5 +1,5 @@ -import subprocess import datetime +import subprocess from functools import cache diff --git a/scripts/fbt_tools/ccache.py b/scripts/fbt_tools/ccache.py index e88886ade37..63577ab781f 100644 --- a/scripts/fbt_tools/ccache.py +++ b/scripts/fbt_tools/ccache.py @@ -3,7 +3,7 @@ def exists(): def generate(env): - if ccache := env.WhereIs("ccache"): + if env.WhereIs("ccache"): env["CCACHE"] = "ccache" env["CC_NOCACHE"] = env["CC"] env["CC"] = "$CCACHE $CC_NOCACHE" diff --git a/scripts/fbt_tools/crosscc.py b/scripts/fbt_tools/crosscc.py index dd5cd531961..d0631ca33ab 100644 --- a/scripts/fbt_tools/crosscc.py +++ b/scripts/fbt_tools/crosscc.py @@ -1,15 +1,11 @@ -from SCons.Errors import StopError -from SCons.Tool import asm -from SCons.Tool import gcc -from SCons.Tool import gxx -from SCons.Tool import ar -from SCons.Tool import gnulink -import strip +import subprocess + import gdb import objdump - +import strip from SCons.Action import _subproc -import subprocess +from SCons.Errors import StopError +from SCons.Tool import ar, asm, gcc, gnulink, gxx def prefix_commands(env, command_prefix, cmd_list): diff --git a/scripts/fbt_tools/fbt_apps.py b/scripts/fbt_tools/fbt_apps.py index 9dbe307203c..053a695037c 100644 --- a/scripts/fbt_tools/fbt_apps.py +++ b/scripts/fbt_tools/fbt_apps.py @@ -1,15 +1,14 @@ -from SCons.Builder import Builder -from SCons.Action import Action -from SCons.Errors import StopError -from SCons.Warnings import warn, WarningOnByDefault from ansi.color import fg - from fbt.appmanifest import ( - FlipperAppType, - AppManager, ApplicationsCGenerator, + AppManager, + FlipperAppType, FlipperManifestException, ) +from SCons.Action import Action +from SCons.Builder import Builder +from SCons.Errors import StopError +from SCons.Warnings import WarningOnByDefault, warn # Adding objects for application management to env # AppManager env["APPMGR"] - loads all manifests; manages list of known apps diff --git a/scripts/fbt_tools/fbt_assets.py b/scripts/fbt_tools/fbt_assets.py index e4c567993e7..68617c2549b 100644 --- a/scripts/fbt_tools/fbt_assets.py +++ b/scripts/fbt_tools/fbt_assets.py @@ -1,10 +1,10 @@ -from SCons.Builder import Builder -from SCons.Action import Action -from SCons.Errors import StopError - import os import subprocess + from ansi.color import fg +from SCons.Action import Action +from SCons.Builder import Builder +from SCons.Errors import StopError def icons_emitter(target, source, env): @@ -76,11 +76,11 @@ def proto_ver_generator(target, source, env): target_file = target[0] src_dir = source[0].dir.abspath try: - git_fetch = _invoke_git( + _invoke_git( ["fetch", "--tags"], source_dir=src_dir, ) - except (subprocess.CalledProcessError, EnvironmentError) as e: + except (subprocess.CalledProcessError, EnvironmentError): # Not great, not terrible print(fg.boldred("Git: fetch failed")) @@ -89,7 +89,7 @@ def proto_ver_generator(target, source, env): ["describe", "--tags", "--abbrev=0"], source_dir=src_dir, ) - except (subprocess.CalledProcessError, EnvironmentError) as e: + except (subprocess.CalledProcessError, EnvironmentError): raise StopError("Git: describe failed") git_major, git_minor = git_describe.split(".") diff --git a/scripts/fbt_tools/fbt_debugopts.py b/scripts/fbt_tools/fbt_debugopts.py index 9abe59893eb..33cc0c0763b 100644 --- a/scripts/fbt_tools/fbt_debugopts.py +++ b/scripts/fbt_tools/fbt_debugopts.py @@ -1,5 +1,3 @@ -from re import search - from SCons.Errors import UserError diff --git a/scripts/fbt_tools/fbt_dist.py b/scripts/fbt_tools/fbt_dist.py index d2808419c7a..a43d62e9dc9 100644 --- a/scripts/fbt_tools/fbt_dist.py +++ b/scripts/fbt_tools/fbt_dist.py @@ -1,6 +1,5 @@ -from SCons.Builder import Builder from SCons.Action import Action -from SCons.Script import Mkdir +from SCons.Builder import Builder from SCons.Defaults import Touch diff --git a/scripts/fbt_tools/fbt_extapps.py b/scripts/fbt_tools/fbt_extapps.py index 4ac1c68737c..1a1bad29e71 100644 --- a/scripts/fbt_tools/fbt_extapps.py +++ b/scripts/fbt_tools/fbt_extapps.py @@ -3,23 +3,19 @@ import pathlib import shutil from dataclasses import dataclass, field -from typing import Optional, TypedDict - -from ansi.color import fg +from typing import Optional import SCons.Warnings -from SCons.Action import Action -from SCons.Builder import Builder -from SCons.Errors import UserError -from SCons.Node import NodeList -from SCons.Node.FS import File, Entry - +from ansi.color import fg from fbt.appmanifest import FlipperApplication, FlipperAppType, FlipperManifestException from fbt.elfmanifest import assemble_manifest_data from fbt.fapassets import FileBundler from fbt.sdk.cache import SdkCache from fbt.util import extract_abs_dir_path - +from SCons.Action import Action +from SCons.Builder import Builder +from SCons.Errors import UserError +from SCons.Node.FS import Entry, File _FAP_META_SECTION = ".fapmeta" _FAP_FILEASSETS_SECTION = ".fapassets" @@ -289,7 +285,7 @@ def GetExtAppByIdOrPath(env, app_dir): try: # Maybe user passed an appid? app = appmgr.get(app_dir) - except FlipperManifestException as _: + except FlipperManifestException: # Look up path components in known app dirs for dir_part in reversed(pathlib.Path(app_dir).parts): if app := appmgr.find_by_appdir(dir_part): diff --git a/scripts/fbt_tools/fbt_hwtarget.py b/scripts/fbt_tools/fbt_hwtarget.py index b4e1e58ac1f..1831a6984da 100644 --- a/scripts/fbt_tools/fbt_hwtarget.py +++ b/scripts/fbt_tools/fbt_hwtarget.py @@ -1,5 +1,3 @@ -from SCons.Builder import Builder -from SCons.Action import Action import json diff --git a/scripts/fbt_tools/fbt_sdk.py b/scripts/fbt_tools/fbt_sdk.py index 90d0831eb42..2f7d62388df 100644 --- a/scripts/fbt_tools/fbt_sdk.py +++ b/scripts/fbt_tools/fbt_sdk.py @@ -1,21 +1,20 @@ +import json +import os.path +import pathlib +import posixpath import shutil -from SCons.Builder import Builder + +from fbt.sdk.cache import SdkCache +from fbt.sdk.collector import SdkCollector +from fbt.util import path_as_posix from SCons.Action import Action +from SCons.Builder import Builder from SCons.Errors import UserError # from SCons.Scanner import C from SCons.Script import Entry from SCons.Util import LogicalLines -import os.path -import posixpath -import pathlib -import json - -from fbt.sdk.collector import SdkCollector -from fbt.sdk.cache import SdkCache -from fbt.util import path_as_posix - def ProcessSdkDepends(env, filename): try: diff --git a/scripts/fbt_tools/fbt_tweaks.py b/scripts/fbt_tools/fbt_tweaks.py index 700f66d2389..68ac9d7d17c 100644 --- a/scripts/fbt_tools/fbt_tweaks.py +++ b/scripts/fbt_tools/fbt_tweaks.py @@ -1,15 +1,13 @@ +import os +import sys +import traceback + import SCons.Warnings as Warnings +from ansi.color import fg from SCons.Errors import UserError - # from SCons.Script.Main import find_deepest_user_frame -from ansi.color import fg, bg, fx - -import traceback -import sys -import os - def find_deepest_user_frame(tb): tb.reverse() diff --git a/scripts/fbt_tools/fbt_version.py b/scripts/fbt_tools/fbt_version.py index 87497ca5f72..8469e181a32 100644 --- a/scripts/fbt_tools/fbt_version.py +++ b/scripts/fbt_tools/fbt_version.py @@ -1,5 +1,5 @@ -from SCons.Builder import Builder from SCons.Action import Action +from SCons.Builder import Builder def version_emitter(target, source, env): diff --git a/scripts/fbt_tools/fwbin.py b/scripts/fbt_tools/fwbin.py index f510c2a60c8..06a435b6db1 100644 --- a/scripts/fbt_tools/fwbin.py +++ b/scripts/fbt_tools/fwbin.py @@ -1,6 +1,6 @@ -from SCons.Builder import Builder -from SCons.Action import Action import SCons +from SCons.Action import Action +from SCons.Builder import Builder __OBJCOPY_ARM_BIN = "arm-none-eabi-objcopy" __NM_ARM_BIN = "arm-none-eabi-nm" diff --git a/scripts/fbt_tools/gdb.py b/scripts/fbt_tools/gdb.py index 38256a0f81d..ea29e9c92e8 100644 --- a/scripts/fbt_tools/gdb.py +++ b/scripts/fbt_tools/gdb.py @@ -1,7 +1,3 @@ -from SCons.Builder import Builder -from SCons.Action import Action - - def generate(env): env.SetDefault( GDB="gdb", diff --git a/scripts/fbt_tools/objdump.py b/scripts/fbt_tools/objdump.py index f5fa938a7bd..31f8176484b 100644 --- a/scripts/fbt_tools/objdump.py +++ b/scripts/fbt_tools/objdump.py @@ -1,5 +1,5 @@ -from SCons.Builder import Builder from SCons.Action import Action +from SCons.Builder import Builder def generate(env): diff --git a/scripts/fbt_tools/openocd.py b/scripts/fbt_tools/openocd.py index dcf0bf925aa..157d798f40d 100644 --- a/scripts/fbt_tools/openocd.py +++ b/scripts/fbt_tools/openocd.py @@ -1,7 +1,7 @@ -from SCons.Builder import Builder +import SCons from SCons.Action import Action +from SCons.Builder import Builder from SCons.Defaults import Touch -import SCons __OPENOCD_BIN = "openocd" diff --git a/scripts/fbt_tools/pvsstudio.py b/scripts/fbt_tools/pvsstudio.py index 593559a33f0..211f46aee8e 100644 --- a/scripts/fbt_tools/pvsstudio.py +++ b/scripts/fbt_tools/pvsstudio.py @@ -1,11 +1,12 @@ -from SCons.Builder import Builder -from SCons.Action import Action -from SCons.Script import Delete, Mkdir, GetBuildFailures, Flatten -import multiprocessing -import webbrowser import atexit -import sys +import multiprocessing import subprocess +import sys +import webbrowser + +from SCons.Action import Action +from SCons.Builder import Builder +from SCons.Script import Delete, Flatten, GetBuildFailures, Mkdir __no_browser = False diff --git a/scripts/fbt_tools/sconsmodular.py b/scripts/fbt_tools/sconsmodular.py index 57ae8f055c3..4dc2079a6e3 100644 --- a/scripts/fbt_tools/sconsmodular.py +++ b/scripts/fbt_tools/sconsmodular.py @@ -1,5 +1,6 @@ -import posixpath import os +import posixpath + from SCons.Errors import UserError diff --git a/scripts/fbt_tools/sconsrecursiveglob.py b/scripts/fbt_tools/sconsrecursiveglob.py index fbcee965b1a..7dbde531b30 100644 --- a/scripts/fbt_tools/sconsrecursiveglob.py +++ b/scripts/fbt_tools/sconsrecursiveglob.py @@ -1,6 +1,6 @@ import SCons -from SCons.Script import Flatten from fbt.util import GLOB_FILE_EXCLUSION +from SCons.Script import Flatten def GlobRecursive(env, pattern, node=".", exclude=[]): diff --git a/scripts/fbt_tools/strip.py b/scripts/fbt_tools/strip.py index 053956f22e0..ee14fc185a3 100644 --- a/scripts/fbt_tools/strip.py +++ b/scripts/fbt_tools/strip.py @@ -1,5 +1,5 @@ -from SCons.Builder import Builder from SCons.Action import Action +from SCons.Builder import Builder def generate(env): diff --git a/scripts/flash.py b/scripts/flash.py index fb05b8b0b85..6189dc1a28e 100755 --- a/scripts/flash.py +++ b/scripts/flash.py @@ -1,13 +1,9 @@ #!/usr/bin/env python3 -import logging -import argparse -import sys -import os from flipper.app import App -from flipper.cube import CubeProgrammer from flipper.assets.coprobin import CoproBinary +from flipper.cube import CubeProgrammer STATEMENT = "AGREE_TO_LOSE_FLIPPER_FEATURES_THAT_USE_CRYPTO_ENCLAVE" @@ -94,59 +90,59 @@ def _getCubeParams(self): } def wipe(self): - self.logger.info(f"Wiping flash") + self.logger.info("Wiping flash") cp = CubeProgrammer(self._getCubeParams()) - self.logger.info(f"Setting RDP to 0xBB") + self.logger.info("Setting RDP to 0xBB") cp.setOptionBytes({"RDP": ("0xBB", "rw")}) - self.logger.info(f"Verifying RDP") + self.logger.info("Verifying RDP") r = cp.checkOptionBytes({"RDP": ("0xBB", "rw")}) - assert r == True + assert r is True self.logger.info(f"Result: {r}") - self.logger.info(f"Setting RDP to 0xAA") + self.logger.info("Setting RDP to 0xAA") cp.setOptionBytes({"RDP": ("0xAA", "rw")}) - self.logger.info(f"Verifying RDP") + self.logger.info("Verifying RDP") r = cp.checkOptionBytes({"RDP": ("0xAA", "rw")}) - assert r == True + assert r is True self.logger.info(f"Result: {r}") - self.logger.info(f"Complete") + self.logger.info("Complete") return 0 def core1bootloader(self): - self.logger.info(f"Flashing bootloader") + self.logger.info("Flashing bootloader") cp = CubeProgrammer(self._getCubeParams()) cp.flashBin("0x08000000", self.args.bootloader) - self.logger.info(f"Complete") + self.logger.info("Complete") cp.resetTarget() return 0 def core1firmware(self): - self.logger.info(f"Flashing firmware") + self.logger.info("Flashing firmware") cp = CubeProgrammer(self._getCubeParams()) cp.flashBin("0x08008000", self.args.firmware) - self.logger.info(f"Complete") + self.logger.info("Complete") cp.resetTarget() return 0 def core1(self): - self.logger.info(f"Flashing bootloader") + self.logger.info("Flashing bootloader") cp = CubeProgrammer(self._getCubeParams()) cp.flashBin("0x08000000", self.args.bootloader) - self.logger.info(f"Flashing firmware") + self.logger.info("Flashing firmware") cp.flashBin("0x08008000", self.args.firmware) cp.resetTarget() - self.logger.info(f"Complete") + self.logger.info("Complete") return 0 def core2fus(self): if self.args.statement != STATEMENT: self.logger.error( - f"PLEASE DON'T. THIS FEATURE INTENDED ONLY FOR FACTORY FLASHING" + "PLEASE DON'T. THIS FEATURE INTENDED ONLY FOR FACTORY FLASHING" ) return 1 - self.logger.info(f"Flashing Firmware Update Service") + self.logger.info("Flashing Firmware Update Service") cp = CubeProgrammer(self._getCubeParams()) cp.flashCore2(self.args.fus_address, self.args.fus) - self.logger.info(f"Complete") + self.logger.info("Complete") return 0 def core2radio(self): @@ -163,15 +159,15 @@ def core2radio(self): f"Radio address not provided, guessed as 0x{radio_address:X}" ) if radio_address > 0x080E0000: - self.logger.error(f"I KNOW WHAT YOU DID LAST SUMMER") + self.logger.error("I KNOW WHAT YOU DID LAST SUMMER") return 1 cp = CubeProgrammer(self._getCubeParams()) - self.logger.info(f"Removing Current Radio Stack") + self.logger.info("Removing Current Radio Stack") cp.deleteCore2RadioStack() - self.logger.info(f"Flashing Radio Stack") + self.logger.info("Flashing Radio Stack") cp.flashCore2(radio_address, self.args.radio) - self.logger.info(f"Complete") + self.logger.info("Complete") return 0 diff --git a/scripts/flipper/app.py b/scripts/flipper/app.py index 30630a5f9f6..405c4c39907 100644 --- a/scripts/flipper/app.py +++ b/scripts/flipper/app.py @@ -44,7 +44,7 @@ def __call__(self, args=None, skip_logger_init=False): if isinstance(return_code, int): return self._exit(return_code) else: - self.logger.error(f"Missing return code") + self.logger.error("Missing return code") return self._exit(255) def _exit(self, code): diff --git a/scripts/flipper/assets/copro.py b/scripts/flipper/assets/copro.py index e0375b51f17..ee13a9b5ea6 100644 --- a/scripts/flipper/assets/copro.py +++ b/scripts/flipper/assets/copro.py @@ -6,7 +6,7 @@ import posixpath import os -from flipper.utils import * +from flipper.utils import file_sha256, timestamp from flipper.assets.coprobin import CoproBinary, get_stack_type @@ -45,13 +45,13 @@ def loadCubeInfo(self, cube_dir, reference_cube_version): cube_manifest = ET.parse(cube_manifest_file) cube_package = cube_manifest.find("PackDescription") if not cube_package: - raise Exception(f"Unknown Cube manifest format") + raise Exception("Unknown Cube manifest format") cube_version = cube_package.get("Patch") or cube_package.get("Release") if not cube_version or not cube_version.startswith("FW.WB"): - raise Exception(f"Incorrect Cube package or version info") + raise Exception("Incorrect Cube package or version info") cube_version = cube_version.replace("FW.WB.", "", 1) if cube_version != reference_cube_version: - raise Exception(f"Unsupported cube version") + raise Exception("Unsupported cube version") self.version = cube_version def _getFileName(self, name): diff --git a/scripts/flipper/assets/coprobin.py b/scripts/flipper/assets/coprobin.py index 64f0b8c8718..7c5bdb3dc4d 100644 --- a/scripts/flipper/assets/coprobin.py +++ b/scripts/flipper/assets/coprobin.py @@ -1,6 +1,7 @@ import struct import math -import os, os.path +import os +import os.path import sys diff --git a/scripts/flipper/assets/dolphin.py b/scripts/flipper/assets/dolphin.py index cbd1320b653..ebe9fd88986 100644 --- a/scripts/flipper/assets/dolphin.py +++ b/scripts/flipper/assets/dolphin.py @@ -1,13 +1,11 @@ import multiprocessing import logging import os -import sys -import shutil from collections import Counter -from flipper.utils.fff import * -from flipper.utils.templite import * -from .icon import * +from flipper.utils.fff import FlipperFormatFile +from flipper.utils.templite import Templite +from .icon import ImageTools, file2image def _convert_image_to_bm(pair: set): @@ -121,7 +119,7 @@ def load(self, animation_directory: str): self.meta["Passive frames"] + self.meta["Active frames"] == ordered_frames_count ) - except EOFError as e: + except EOFError: raise Exception("Invalid meta file: too short") except AssertionError as e: self.logger.exception(e) @@ -158,7 +156,7 @@ def load(self, animation_directory: str): except AssertionError as e: self.logger.exception(e) self.logger.error( - f"Animation {self.name} bubble slot {bubble_slot} got incorrect data: {bubble}" + f"Animation {self.name} bubble slot {bubble['Slot']} got incorrect data: {bubble}" ) raise Exception("Meta file is invalid: incorrect bubble data") except EOFError: diff --git a/scripts/flipper/assets/icon.py b/scripts/flipper/assets/icon.py index f0dae25bea5..d5d2a585ee6 100644 --- a/scripts/flipper/assets/icon.py +++ b/scripts/flipper/assets/icon.py @@ -1,9 +1,6 @@ import logging -import argparse import subprocess import io -import os -import sys ICONS_SUPPORTED_FORMATS = ["png"] @@ -36,11 +33,8 @@ class ImageTools: @staticmethod def is_processing_slow(): try: - from PIL import Image, ImageOps - import heatshrink2 - return False - except ImportError as e: + except ImportError: return True def __init__(self): @@ -52,7 +46,7 @@ def png2xbm(self, file): try: from PIL import Image, ImageOps - except ImportError as e: + except ImportError: self.__pil_unavailable = True self.logger.info("pillow module is missing, using convert cli util") return self.png2xbm(file) @@ -72,7 +66,7 @@ def xbm2hs(self, data): try: import heatshrink2 - except ImportError as e: + except ImportError: self.__hs2_unavailable = True self.logger.info("heatshrink2 module is missing, using heatshrink cli util") return self.xbm2hs(data) diff --git a/scripts/flipper/assets/manifest.py b/scripts/flipper/assets/manifest.py index a8f6855a476..a9bbb8dacd8 100644 --- a/scripts/flipper/assets/manifest.py +++ b/scripts/flipper/assets/manifest.py @@ -1,11 +1,10 @@ -import datetime import logging import os import posixpath from pathlib import Path -from flipper.utils import * -from flipper.utils.fstree import * +from flipper.utils import timestamp, file_md5 +from flipper.utils.fstree import FsNode, compare_fs_trees MANIFEST_VERSION = 0 diff --git a/scripts/flipper/assets/obdata.py b/scripts/flipper/assets/obdata.py index 0f7f5c19281..eb35d0e1745 100644 --- a/scripts/flipper/assets/obdata.py +++ b/scripts/flipper/assets/obdata.py @@ -1,7 +1,5 @@ #!/usr/bin/env python3 -import logging -import struct from enum import Enum from dataclasses import dataclass @@ -181,7 +179,7 @@ def __init__(self, obfname): def gen_values(self): obref = ObReferenceValuesGenerator() - converted_refs = list(obref.apply(ob) for ob in self.obs) + list(obref.apply(ob) for ob in self.obs) return obref diff --git a/scripts/flipper/cube.py b/scripts/flipper/cube.py index 38aa54a8548..e4f9876df11 100644 --- a/scripts/flipper/cube.py +++ b/scripts/flipper/cube.py @@ -14,7 +14,7 @@ def __init__(self, config={}): if "port" in config and config["port"]: connect.append(f"port={config['port']}") else: - connect.append(f"port=swd") + connect.append("port=swd") if "serial" in config and config["serial"]: connect.append(f"sn={config['serial']}") self.params.append("-c " + " ".join(connect)) @@ -43,20 +43,20 @@ def _execute(self, args): return output.decode() def getVersion(self): - output = self._execute(["--version"]) + self._execute(["--version"]) def checkOptionBytes(self, option_bytes): output = self._execute(["-ob displ"]) ob_correct = True for line in output.split("\n"): line = line.strip() - if not ":" in line: + if ":" not in line: self.logger.debug(f"Skipping line: {line}") continue key, data = line.split(":", 1) key = key.strip() data = data.strip() - if not key in option_bytes.keys(): + if key not in option_bytes.keys(): self.logger.debug(f"Skipping key: {key}") continue self.logger.debug(f"Processing key: {key} {data}") diff --git a/scripts/flipper/storage.py b/scripts/flipper/storage.py index cff32ceb1d7..9f6f52156fd 100644 --- a/scripts/flipper/storage.py +++ b/scripts/flipper/storage.py @@ -151,7 +151,7 @@ def list_tree(self, path: str = "/", level: int = 0): try: # TODO: better decoding, considering non-ascii characters line = line.decode("ascii") - except: + except Exception: continue line = line.strip() @@ -194,7 +194,7 @@ def walk(self, path: str = "/"): try: # TODO: better decoding, considering non-ascii characters line = line.decode("ascii") - except: + except Exception: continue line = line.strip() diff --git a/scripts/flipper/utils/__init__.py b/scripts/flipper/utils/__init__.py index 62bf98a25e2..6b4ebbd52be 100644 --- a/scripts/flipper/utils/__init__.py +++ b/scripts/flipper/utils/__init__.py @@ -1,6 +1,5 @@ import datetime import hashlib -import os def timestamp(): diff --git a/scripts/flipper/utils/programmer_openocd.py b/scripts/flipper/utils/programmer_openocd.py index 3d21718547e..5a8029f379b 100644 --- a/scripts/flipper/utils/programmer_openocd.py +++ b/scripts/flipper/utils/programmer_openocd.py @@ -31,13 +31,13 @@ def __init__( config["interface"] = interface config["target"] = "target/stm32wbx.cfg" - if not serial is None: + if serial is not None: if interface == "interface/cmsis-dap.cfg": config["serial"] = f"cmsis_dap_serial {serial}" elif "stlink" in interface: config["serial"] = f"stlink_serial {serial}" - if not port_base is None: + if port_base is not None: config["port_base"] = port_base self.openocd = OpenOCD(config) @@ -59,7 +59,7 @@ def flash(self, address: int, file_path: str, verify: bool = True) -> bool: raise Exception(f"File {file_path} not found") self.openocd.start() - self.openocd.send_tcl(f"init") + self.openocd.send_tcl("init") self.openocd.send_tcl( f"program {file_path} 0x{address:08x}{' verify' if verify else ''} reset exit" ) @@ -196,7 +196,7 @@ def option_bytes_set(self, file_path: str) -> bool: if ob_need_to_apply: stm32.option_bytes_apply(self.openocd) else: - self.logger.info(f"Option Bytes are already correct") + self.logger.info("Option Bytes are already correct") # Load Option Bytes # That will reset and also lock the Option Bytes and the Flash @@ -256,7 +256,7 @@ def otp_write(self, address: int, file_path: str) -> OpenOCDProgrammerResult: already_written = False if already_written: - self.logger.info(f"OTP memory is already written with the given data") + self.logger.info("OTP memory is already written with the given data") return OpenOCDProgrammerResult.Success self.reset(self.RunMode.Stop) diff --git a/scripts/flipper/utils/stm32wb55.py b/scripts/flipper/utils/stm32wb55.py index 910b0d7d69e..52a5ec4e3eb 100644 --- a/scripts/flipper/utils/stm32wb55.py +++ b/scripts/flipper/utils/stm32wb55.py @@ -123,7 +123,7 @@ def reset(self, oocd: OpenOCD, mode: RunMode): def clear_flash_errors(self, oocd: OpenOCD): # Errata 2.2.9: Flash OPTVERR flag is always set after system reset # And also clear all other flash error flags - self.logger.debug(f"Resetting flash errors") + self.logger.debug("Resetting flash errors") self.FLASH_SR.load(oocd) self.FLASH_SR.OP_ERR = 1 self.FLASH_SR.PROG_ERR = 1 @@ -218,7 +218,7 @@ def flash_lock(self, oocd: OpenOCD): raise Exception("Flash lock failed") def option_bytes_apply(self, oocd: OpenOCD): - self.logger.debug(f"Applying Option Bytes") + self.logger.debug("Applying Option Bytes") self.FLASH_CR.load(oocd) self.FLASH_CR.OPT_STRT = 1 @@ -228,7 +228,7 @@ def option_bytes_apply(self, oocd: OpenOCD): self.flash_wait_for_operation(oocd) def option_bytes_load(self, oocd: OpenOCD): - self.logger.debug(f"Loading Option Bytes") + self.logger.debug("Loading Option Bytes") self.FLASH_CR.load(oocd) self.FLASH_CR.OBL_LAUNCH = 1 self.FLASH_CR.store(oocd) diff --git a/scripts/flipper/utils/templite.py b/scripts/flipper/utils/templite.py index 2d958bd77ab..1d19585cd8f 100644 --- a/scripts/flipper/utils/templite.py +++ b/scripts/flipper/utils/templite.py @@ -77,8 +77,8 @@ def processControl(self): return lines = self.block.splitlines() - margin = min(len(l) - len(l.lstrip()) for l in lines if l.strip()) - self.block = "\n".join("\t" * self.offset + l[margin:] for l in lines) + margin = min(len(line) - len(line.lstrip()) for line in lines if line.strip()) + self.block = "\n".join("\t" * self.offset + line[margin:] for line in lines) self.blocks.append(self.block) if self.block.endswith(":"): self.offset += 1 diff --git a/scripts/fwsize.py b/scripts/fwsize.py index 445c290498f..75a82569226 100644 --- a/scripts/fwsize.py +++ b/scripts/fwsize.py @@ -1,10 +1,11 @@ #!/usr/bin/env python3 -from flipper.app import App -import subprocess -import os import math +import os +import subprocess + from ansi.color import fg +from flipper.app import App class Main(App): diff --git a/scripts/get_env.py b/scripts/get_env.py index 92f9243c2de..5403bafeb28 100644 --- a/scripts/get_env.py +++ b/scripts/get_env.py @@ -1,14 +1,14 @@ #!/usr/bin/env python3 -import ssl +import argparse +import datetime import json import os -import shlex +import random import re +import shlex +import ssl import string -import random -import argparse -import datetime import urllib.request diff --git a/scripts/lint.py b/scripts/lint.py index 58f2d69f559..8530209bec0 100755 --- a/scripts/lint.py +++ b/scripts/lint.py @@ -1,14 +1,13 @@ #!/usr/bin/env python3 +import multiprocessing import os import re import shutil import subprocess -import multiprocessing from flipper.app import App - SOURCE_CODE_FILE_EXTENSIONS = [".h", ".c", ".cpp", ".cxx", ".hpp"] SOURCE_CODE_FILE_PATTERN = r"^[0-9A-Za-z_]+\.[a-z]+$" SOURCE_CODE_DIR_PATTERN = r"^[0-9A-Za-z_]+$" @@ -59,7 +58,7 @@ def _check_folders(self, folders: list): show_message = True if show_message: self.logger.warning( - f"Folders are not renamed automatically, please fix it by yourself" + "Folders are not renamed automatically, please fix it by yourself" ) def _find_sources(self, folders: list): @@ -70,7 +69,7 @@ def _find_sources(self, folders: list): for filename in filenames: ext = os.path.splitext(filename.lower())[1] - if not ext in SOURCE_CODE_FILE_EXTENSIONS: + if ext not in SOURCE_CODE_FILE_EXTENSIONS: continue output.append(os.path.join(dirpath, filename)) return output @@ -80,7 +79,7 @@ def _format_source(task): try: subprocess.check_call(task) return True - except subprocess.CalledProcessError as e: + except subprocess.CalledProcessError: return False def _format_sources(self, sources: list, dry_run: bool = False): @@ -144,7 +143,7 @@ def _apply_file_naming_convention(self, sources: list, dry_run: bool = False): def _apply_file_permissions(self, sources: list, dry_run: bool = False): execute_permissions = 0o111 - pattern = re.compile(SOURCE_CODE_FILE_PATTERN) + re.compile(SOURCE_CODE_FILE_PATTERN) good = [] bad = [] # Check sources for unexpected execute permissions diff --git a/scripts/merge_report_qa.py b/scripts/merge_report_qa.py index caa74240854..a33327e6b7a 100755 --- a/scripts/merge_report_qa.py +++ b/scripts/merge_report_qa.py @@ -1,9 +1,10 @@ #!/usr/bin/env python3 +import argparse import os import re import sys -import argparse + from slack_sdk import WebClient from slack_sdk.errors import SlackApiError diff --git a/scripts/meta.py b/scripts/meta.py index ae2f213b760..f47ef65fb0e 100755 --- a/scripts/meta.py +++ b/scripts/meta.py @@ -1,8 +1,9 @@ #!/usr/bin/env python3 -from flipper.app import App import json +from flipper.app import App + class Main(App): def init(self): diff --git a/scripts/ob.py b/scripts/ob.py index 178fe16a77e..7010bdec58b 100755 --- a/scripts/ob.py +++ b/scripts/ob.py @@ -44,7 +44,7 @@ def _add_args(self, parser): ) def check(self): - self.logger.info(f"Checking Option Bytes") + self.logger.info("Checking Option Bytes") # OpenOCD openocd = OpenOCDProgrammer( @@ -60,7 +60,7 @@ def check(self): return return_code def set(self): - self.logger.info(f"Setting Option Bytes") + self.logger.info("Setting Option Bytes") # OpenOCD openocd = OpenOCDProgrammer( diff --git a/scripts/otp.py b/scripts/otp.py index cb76bdc8611..19b8c4df42e 100755 --- a/scripts/otp.py +++ b/scripts/otp.py @@ -1,13 +1,13 @@ #!/usr/bin/env python3 +import datetime import logging -import argparse -import subprocess import os -import sys import re import struct -import datetime + +from flipper.app import App +from flipper.utils.programmer_openocd import OpenOCDProgrammer, OpenOCDProgrammerResult OTP_MAGIC = 0xBABE OTP_VERSION = 0x02 @@ -33,9 +33,6 @@ "mgg": 0x02, } -from flipper.app import App -from flipper.utils.programmer_openocd import OpenOCDProgrammer, OpenOCDProgrammerResult - class OTPException(Exception): def __init__(self, message: str, result: OpenOCDProgrammerResult): @@ -158,7 +155,7 @@ def _packSecond(self): ) def generate_all(self): - self.logger.info(f"Generating OTP") + self.logger.info("Generating OTP") self._processFirstArgs() self._processSecondArgs() with open(f"{self.args.file}_first.bin", "wb") as file: @@ -172,18 +169,18 @@ def generate_all(self): return 0 def flash_first(self): - self.logger.info(f"Flashing first block of OTP") + self.logger.info("Flashing first block of OTP") self._processFirstArgs() filename = f"otp_unknown_first_{self.timestamp}.bin" try: - self.logger.info(f"Packing binary data") + self.logger.info("Packing binary data") with open(filename, "wb") as file: file.write(self._packFirst()) - self.logger.info(f"Flashing OTP") + self.logger.info("Flashing OTP") openocd = OpenOCDProgrammer( self.args.interface, @@ -195,7 +192,7 @@ def flash_first(self): if programmer_result != OpenOCDProgrammerResult.Success: raise OTPException("Failed to flash OTP", programmer_result) - self.logger.info(f"Flashed Successfully") + self.logger.info("Flashed Successfully") except OTPException as e: self.logger.exception(e) return e.get_exit_code() @@ -205,18 +202,18 @@ def flash_first(self): return 0 def flash_second(self): - self.logger.info(f"Flashing second block of OTP") + self.logger.info("Flashing second block of OTP") self._processSecondArgs() filename = f"otp_{self.args.name}_second_{self.timestamp}.bin" try: - self.logger.info(f"Packing binary data") + self.logger.info("Packing binary data") with open(filename, "wb") as file: file.write(self._packSecond()) - self.logger.info(f"Flashing OTP") + self.logger.info("Flashing OTP") openocd = OpenOCDProgrammer( self.args.interface, @@ -228,7 +225,7 @@ def flash_second(self): if programmer_result != OpenOCDProgrammerResult.Success: raise OTPException("Failed to flash OTP", programmer_result) - self.logger.info(f"Flashed Successfully") + self.logger.info("Flashed Successfully") except OTPException as e: self.logger.exception(e) return e.get_exit_code() @@ -238,7 +235,7 @@ def flash_second(self): return 0 def flash_all(self): - self.logger.info(f"Flashing OTP") + self.logger.info("Flashing OTP") self._processFirstArgs() self._processSecondArgs() @@ -246,12 +243,12 @@ def flash_all(self): filename = f"otp_{self.args.name}_whole_{self.timestamp}.bin" try: - self.logger.info(f"Packing binary data") + self.logger.info("Packing binary data") with open(filename, "wb") as file: file.write(self._packFirst()) file.write(self._packSecond()) - self.logger.info(f"Flashing OTP") + self.logger.info("Flashing OTP") openocd = OpenOCDProgrammer( self.args.interface, @@ -263,7 +260,7 @@ def flash_all(self): if programmer_result != OpenOCDProgrammerResult.Success: raise OTPException("Failed to flash OTP", programmer_result) - self.logger.info(f"Flashed Successfully") + self.logger.info("Flashed Successfully") except OTPException as e: self.logger.exception(e) return e.get_exit_code() diff --git a/scripts/program.py b/scripts/program.py index c140a9024e9..f3e7e3e2d19 100755 --- a/scripts/program.py +++ b/scripts/program.py @@ -1,13 +1,13 @@ #!/usr/bin/env python3 -import typing -import subprocess import logging -import time import os import socket - +import subprocess +import time +import typing from abc import ABC, abstractmethod from dataclasses import dataclass + from flipper.app import App @@ -223,7 +223,7 @@ def _valid_ip(self, address): try: socket.inet_aton(address) return True - except: + except Exception: return False def set_serial(self, serial: str): @@ -415,12 +415,12 @@ def flash(self): if len(interfaces) == 0: interfaces = [p for p in network_programmers if p.get_name() == i_name] else: - self.logger.info(f"Probing for interfaces...") + self.logger.info("Probing for interfaces...") interfaces = self._search_interface(self.args.serial) if len(interfaces) == 0: # Probe network blackmagic - self.logger.info(f"Probing for network interfaces...") + self.logger.info("Probing for network interfaces...") interfaces = self._search_network_interface(self.args.serial) if len(interfaces) == 0: diff --git a/scripts/runfap.py b/scripts/runfap.py index f8ff607c1c3..a240acf1212 100644 --- a/scripts/runfap.py +++ b/scripts/runfap.py @@ -1,14 +1,12 @@ #!/usr/bin/env python3 +import operator +from functools import reduce + from flipper.app import App from flipper.storage import FlipperStorage, FlipperStorageOperations from flipper.utils.cdc import resolve_port -import os -import posixpath -from functools import reduce -import operator - class Main(App): def init(self): @@ -38,8 +36,8 @@ def init(self): self.parser.set_defaults(func=self.install) @staticmethod - def flatten(l): - return reduce(operator.concat, l, []) + def flatten(item_list): + return reduce(operator.concat, item_list, []) def install(self): self.args.sources = self.flatten(self.args.sources) diff --git a/scripts/sconsdist.py b/scripts/sconsdist.py index af2554d0ad6..1657feab93b 100644 --- a/scripts/sconsdist.py +++ b/scripts/sconsdist.py @@ -5,7 +5,7 @@ import tarfile import zipfile from os import makedirs, walk -from os.path import exists, join, relpath, basename, split +from os.path import basename, exists, join, relpath from ansi.color import fg from flipper.app import App diff --git a/scripts/selfupdate.py b/scripts/selfupdate.py index 9bfbfefa377..1ce0b83763f 100644 --- a/scripts/selfupdate.py +++ b/scripts/selfupdate.py @@ -1,14 +1,12 @@ #!/usr/bin/env python3 -from typing import final -from flipper.app import App -from flipper.storage import FlipperStorage, FlipperStorageOperations -from flipper.utils.cdc import resolve_port - import logging import os import pathlib -import serial.tools.list_ports as list_ports + +from flipper.app import App +from flipper.storage import FlipperStorage, FlipperStorageOperations +from flipper.utils.cdc import resolve_port class Main(App): @@ -54,7 +52,7 @@ def install(self): f"update install {flipper_update_path}/{manifest_name}\r" ) result = storage.read.until(storage.CLI_EOL) - if not b"Verifying" in result: + if b"Verifying" not in result: self.logger.error(f"Unexpected response: {result.decode('ascii')}") return 3 result = storage.read.until(storage.CLI_EOL) diff --git a/scripts/serial_cli.py b/scripts/serial_cli.py index 390b1f2638c..2fa37d7512a 100644 --- a/scripts/serial_cli.py +++ b/scripts/serial_cli.py @@ -1,9 +1,10 @@ import logging -import subprocess -from flipper.utils.cdc import resolve_port import os +import subprocess import sys +from flipper.utils.cdc import resolve_port + def main(): logger = logging.getLogger() diff --git a/scripts/storage.py b/scripts/storage.py index 84c01021a45..e04eaa7e1f0 100755 --- a/scripts/storage.py +++ b/scripts/storage.py @@ -1,14 +1,14 @@ #!/usr/bin/env python3 -from flipper.app import App -from flipper.storage import FlipperStorage, FlipperStorageOperations -from flipper.utils.cdc import resolve_port - -import os import binascii import filecmp +import os import tempfile +from flipper.app import App +from flipper.storage import FlipperStorage, FlipperStorageOperations +from flipper.utils.cdc import resolve_port + def WrapStorageOp(func): def wrapper(*args, **kwargs): @@ -122,7 +122,7 @@ def read(self): try: print("Text data:") print(data.decode()) - except: + except Exception: print("Binary hexadecimal data:") print(binascii.hexlify(data).decode()) diff --git a/scripts/ufbt/SConstruct b/scripts/ufbt/SConstruct index ce7c8b9780a..fdb51981be8 100644 --- a/scripts/ufbt/SConstruct +++ b/scripts/ufbt/SConstruct @@ -1,7 +1,6 @@ from SCons.Platform import TempFileMunge from SCons.Node import FS from SCons.Errors import UserError -from SCons.Warnings import warn, WarningOnByDefault import os @@ -14,6 +13,7 @@ SetOption("max_drift", 1) ufbt_state_dir = Dir(os.environ.get("UFBT_STATE_DIR", "#.ufbt")) ufbt_script_dir = Dir(os.environ.get("UFBT_SCRIPT_DIR")) +ufbt_build_dir = ufbt_state_dir.Dir("build") ufbt_current_sdk_dir = ufbt_state_dir.Dir("current") @@ -63,16 +63,7 @@ core_env = Environment( ], ) -if "update" in BUILD_TARGETS: - SConscript( - "update.scons", - exports={"core_env": core_env}, - ) - -if "purge" in BUILD_TARGETS: - core_env.Execute(Delete(ufbt_state_dir)) - print("uFBT state purged") - Exit(0) +core_env.Append(CPPDEFINES=GetOption("extra_defines")) # Now we can import stuff bundled with SDK - it was added to sys.path by ufbt_state @@ -109,7 +100,7 @@ env = core_env.Clone( "fbt_assets", ("compilation_db", {"COMPILATIONDB_COMSTR": "\tCDB\t${TARGET}"}), ], - FBT_FAP_DEBUG_ELF_ROOT=ufbt_state_dir.Dir("build"), + FBT_FAP_DEBUG_ELF_ROOT=ufbt_build_dir, TEMPFILE=TempFileMunge, MAXLINELENGTH=2048, PROGSUFFIX=".elf", @@ -427,3 +418,25 @@ dist_env.PhonyTarget( "get_apiversion", "@echo $( ${UFBT_API_VERSION} $)", ) + +# Dolphin animation builder. Expects "external" directory in current dir +# with animation sources & manifests. Builds & uploads them to connected Flipper +dolphin_src_dir = original_app_dir.Dir("external") +if dolphin_src_dir.exists(): + dolphin_dir = ufbt_build_dir.Dir("dolphin") + dolphin_external = dist_env.DolphinExtBuilder( + ufbt_build_dir.Dir("dolphin"), + original_app_dir, + DOLPHIN_RES_TYPE="external", + ) + dist_env.PhonyTarget( + "dolphin_ext", + '${PYTHON3} ${FBT_SCRIPT_DIR}/storage.py send "${SOURCE}" /ext/dolphin', + source=ufbt_build_dir.Dir("dolphin"), + ) +else: + + def missing_dolphin_folder(**kw): + raise UserError(f"Dolphin folder not found: {dolphin_src_dir}") + + dist_env.PhonyTarget("dolphin_ext", Action(missing_dolphin_folder, None)) diff --git a/scripts/ufbt/commandline.scons b/scripts/ufbt/commandline.scons index 9af5e9bce91..a9b91bbca97 100644 --- a/scripts/ufbt/commandline.scons +++ b/scripts/ufbt/commandline.scons @@ -1,32 +1,18 @@ AddOption( - "--proxy-env", - action="store", - dest="proxy_env", - default="", - help="Comma-separated list of additional environment variables to pass to child SCons processes", + "--extra-define", + action="append", + dest="extra_defines", + default=[], + help="Extra global define that will be passed to C/C++ compiler, can be specified multiple times", ) AddOption( - "--channel", + "--proxy-env", action="store", - dest="sdk_channel", - choices=["dev", "rc", "release"], + dest="proxy_env", default="", - help="Release channel to use for SDK", -) - -AddOption( - "--branch", - action="store", - dest="sdk_branch", - help="Custom main repo branch to use for SDK", -) - -AddOption( - "--hw-target", - action="store", - dest="sdk_target", - help="SDK Hardware target", + help="Comma-separated list of additional environment variables to pass to " + "child SCons processes", ) vars = Variables("ufbt_options.py", ARGUMENTS) diff --git a/scripts/ufbt/site_init.py b/scripts/ufbt/site_init.py index 557085ede85..8e38a36e96a 100644 --- a/scripts/ufbt/site_init.py +++ b/scripts/ufbt/site_init.py @@ -1,8 +1,8 @@ -from SCons.Script import GetBuildFailures -import SCons.Errors - import atexit + +import SCons.Errors from ansi.color import fg, fx +from SCons.Script import GetBuildFailures def bf_to_str(bf): diff --git a/scripts/ufbt/site_tools/ufbt_state.py b/scripts/ufbt/site_tools/ufbt_state.py index 76c6e9acfad..47f4afec4ef 100644 --- a/scripts/ufbt/site_tools/ufbt_state.py +++ b/scripts/ufbt/site_tools/ufbt_state.py @@ -1,12 +1,11 @@ -from SCons.Errors import StopError -from SCons.Warnings import warn, WarningOnByDefault - import json import os -import sys import pathlib +import sys from functools import reduce +from SCons.Errors import StopError + def _load_sdk_data(sdk_root): split_vars = { diff --git a/scripts/ufbt/update.scons b/scripts/ufbt/update.scons deleted file mode 100644 index 9658e0bb20f..00000000000 --- a/scripts/ufbt/update.scons +++ /dev/null @@ -1,37 +0,0 @@ -from SCons.Errors import StopError - -Import("core_env") - -update_env = core_env.Clone( - toolpath=[core_env["FBT_SCRIPT_DIR"].Dir("fbt_tools")], - tools=["python3"], -) -print("Updating SDK...") -ufbt_state = update_env["UFBT_STATE"] - -update_args = [ - "--ufbt-dir", - f'"{update_env["UFBT_STATE_DIR"]}"', -] - -if branch_name := GetOption("sdk_branch"): - update_args.extend(["--branch", branch_name]) -elif channel_name := GetOption("sdk_channel"): - update_args.extend(["--channel", channel_name]) -elif branch_name := ufbt_state.get("branch", None): - update_args.extend(["--branch", branch_name]) -elif channel_name := ufbt_state.get("channel", None): - update_args.extend(["--channel", channel_name]) -else: - raise StopError("No branch or channel specified for SDK update") - -if hw_target := GetOption("sdk_target"): - update_args.extend(["--hw-target", hw_target]) -else: - update_args.extend(["--hw-target", ufbt_state["hw_target"]]) - -update_env.Replace(UPDATE_ARGS=update_args) -result = update_env.Execute( - update_env.subst('$PYTHON3 "$UFBT_BOOTSTRAP_SCRIPT" $UPDATE_ARGS'), -) -Exit(result) diff --git a/scripts/update.py b/scripts/update.py index 2b015726053..0f3ee6ea8b9 100755 --- a/scripts/update.py +++ b/scripts/update.py @@ -1,16 +1,16 @@ #!/usr/bin/env python3 -from flipper.app import App -from flipper.utils.fff import FlipperFormatFile -from flipper.assets.coprobin import CoproBinary, get_stack_type -from flipper.assets.obdata import OptionBytesData, ObReferenceValues -from os.path import basename, join, exists +import math import os import shutil -import zlib import tarfile -import math +import zlib +from os.path import exists, join +from flipper.app import App +from flipper.assets.coprobin import CoproBinary, get_stack_type +from flipper.assets.obdata import ObReferenceValues, OptionBytesData +from flipper.utils.fff import FlipperFormatFile from slideshow import Main as SlideshowMain @@ -267,9 +267,9 @@ def crc(fileName): @staticmethod def batch(iterable, n=1): - l = len(iterable) - for ndx in range(0, l, n): - yield iterable[ndx : min(ndx + n, l)] + iterable_len = len(iterable) + for ndx in range(0, iterable_len, n): + yield iterable[ndx : min(ndx + n, iterable_len)] if __name__ == "__main__": diff --git a/scripts/version.py b/scripts/version.py index 880a9728130..71d201abb6c 100644 --- a/scripts/version.py +++ b/scripts/version.py @@ -1,12 +1,12 @@ #!/usb/bin/env python3 -from flipper.app import App - -import subprocess -import os import json +import os +import subprocess from datetime import date, datetime +from flipper.app import App + class GitVersion: REVISION_SUFFIX_LENGTH = 8 diff --git a/site_scons/extapps.scons b/site_scons/extapps.scons index 89ee492429f..6db0e538dfd 100644 --- a/site_scons/extapps.scons +++ b/site_scons/extapps.scons @@ -1,12 +1,12 @@ from dataclasses import dataclass, field +from fbt.appmanifest import FlipperAppType from SCons.Node import NodeList from SCons.Warnings import warn, WarningOnByDefault -from SCons.Errors import UserError + Import("ENV") -from fbt.appmanifest import FlipperAppType appenv = ENV["APPENV"] = ENV.Clone( tools=[ From 71e85ac36723663f1d153d46f682245d8ed4a58d Mon Sep 17 00:00:00 2001 From: Raymond Lucke Date: Wed, 3 May 2023 02:38:09 -0400 Subject: [PATCH 538/824] Add HID mouse auto-clicker. (#2627) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add HID mouse auto-clicker. * Add click rate adjustment to HID auto-clicker. * Fix formatting. * HidRemote: modify jiggler/clicker event filter and allow repeat to change click rate --------- Co-authored-by: あく --- applications/external/hid_app/hid.c | 22 ++ applications/external/hid_app/hid.h | 2 + applications/external/hid_app/views.h | 1 + .../hid_app/views/hid_mouse_clicker.c | 214 ++++++++++++++++++ .../hid_app/views/hid_mouse_clicker.h | 14 ++ .../hid_app/views/hid_mouse_jiggler.c | 2 +- 6 files changed, 254 insertions(+), 1 deletion(-) create mode 100644 applications/external/hid_app/views/hid_mouse_clicker.c create mode 100644 applications/external/hid_app/views/hid_mouse_clicker.h diff --git a/applications/external/hid_app/hid.c b/applications/external/hid_app/hid.c index 949ff63b3ed..a4f64589d76 100644 --- a/applications/external/hid_app/hid.c +++ b/applications/external/hid_app/hid.c @@ -11,6 +11,7 @@ enum HidDebugSubmenuIndex { HidSubmenuIndexMedia, HidSubmenuIndexTikTok, HidSubmenuIndexMouse, + HidSubmenuIndexMouseClicker, HidSubmenuIndexMouseJiggler, }; @@ -32,6 +33,9 @@ static void hid_submenu_callback(void* context, uint32_t index) { } else if(index == HidSubmenuIndexTikTok) { app->view_id = BtHidViewTikTok; view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewTikTok); + } else if(index == HidSubmenuIndexMouseClicker) { + app->view_id = HidViewMouseClicker; + view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMouseClicker); } else if(index == HidSubmenuIndexMouseJiggler) { app->view_id = HidViewMouseJiggler; view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMouseJiggler); @@ -53,6 +57,7 @@ static void bt_hid_connection_status_changed_callback(BtStatus status, void* con hid_keyboard_set_connected_status(hid->hid_keyboard, connected); hid_media_set_connected_status(hid->hid_media, connected); hid_mouse_set_connected_status(hid->hid_mouse, connected); + hid_mouse_clicker_set_connected_status(hid->hid_mouse_clicker, connected); hid_mouse_jiggler_set_connected_status(hid->hid_mouse_jiggler, connected); hid_tiktok_set_connected_status(hid->hid_tiktok, connected); } @@ -114,6 +119,12 @@ Hid* hid_alloc(HidTransport transport) { hid_submenu_callback, app); } + submenu_add_item( + app->device_type_submenu, + "Mouse Clicker", + HidSubmenuIndexMouseClicker, + hid_submenu_callback, + app); submenu_add_item( app->device_type_submenu, "Mouse Jiggler", @@ -172,6 +183,15 @@ Hid* hid_app_alloc_view(void* context) { view_dispatcher_add_view( app->view_dispatcher, HidViewMouse, hid_mouse_get_view(app->hid_mouse)); + // Mouse clicker view + app->hid_mouse_clicker = hid_mouse_clicker_alloc(app); + view_set_previous_callback( + hid_mouse_clicker_get_view(app->hid_mouse_clicker), hid_exit_confirm_view); + view_dispatcher_add_view( + app->view_dispatcher, + HidViewMouseClicker, + hid_mouse_clicker_get_view(app->hid_mouse_clicker)); + // Mouse jiggler view app->hid_mouse_jiggler = hid_mouse_jiggler_alloc(app); view_set_previous_callback( @@ -205,6 +225,8 @@ void hid_free(Hid* app) { hid_media_free(app->hid_media); view_dispatcher_remove_view(app->view_dispatcher, HidViewMouse); hid_mouse_free(app->hid_mouse); + view_dispatcher_remove_view(app->view_dispatcher, HidViewMouseClicker); + hid_mouse_clicker_free(app->hid_mouse_clicker); view_dispatcher_remove_view(app->view_dispatcher, HidViewMouseJiggler); hid_mouse_jiggler_free(app->hid_mouse_jiggler); view_dispatcher_remove_view(app->view_dispatcher, BtHidViewTikTok); diff --git a/applications/external/hid_app/hid.h b/applications/external/hid_app/hid.h index 8ed1664a340..49d8b4e045d 100644 --- a/applications/external/hid_app/hid.h +++ b/applications/external/hid_app/hid.h @@ -20,6 +20,7 @@ #include "views/hid_keyboard.h" #include "views/hid_media.h" #include "views/hid_mouse.h" +#include "views/hid_mouse_clicker.h" #include "views/hid_mouse_jiggler.h" #include "views/hid_tiktok.h" @@ -43,6 +44,7 @@ struct Hid { HidKeyboard* hid_keyboard; HidMedia* hid_media; HidMouse* hid_mouse; + HidMouseClicker* hid_mouse_clicker; HidMouseJiggler* hid_mouse_jiggler; HidTikTok* hid_tiktok; diff --git a/applications/external/hid_app/views.h b/applications/external/hid_app/views.h index 2a44832e12e..1bea3355e0d 100644 --- a/applications/external/hid_app/views.h +++ b/applications/external/hid_app/views.h @@ -4,6 +4,7 @@ typedef enum { HidViewKeyboard, HidViewMedia, HidViewMouse, + HidViewMouseClicker, HidViewMouseJiggler, BtHidViewTikTok, HidViewExitConfirm, diff --git a/applications/external/hid_app/views/hid_mouse_clicker.c b/applications/external/hid_app/views/hid_mouse_clicker.c new file mode 100644 index 00000000000..d85affc4338 --- /dev/null +++ b/applications/external/hid_app/views/hid_mouse_clicker.c @@ -0,0 +1,214 @@ +#include "hid_mouse_clicker.h" +#include +#include "../hid.h" + +#include "hid_icons.h" + +#define TAG "HidMouseClicker" +#define DEFAULT_CLICK_RATE 1 +#define MAXIMUM_CLICK_RATE 60 + +struct HidMouseClicker { + View* view; + Hid* hid; + FuriTimer* timer; +}; + +typedef struct { + bool connected; + bool running; + int rate; + HidTransport transport; +} HidMouseClickerModel; + +static void hid_mouse_clicker_start_or_restart_timer(void* context) { + furi_assert(context); + HidMouseClicker* hid_mouse_clicker = context; + + if(furi_timer_is_running(hid_mouse_clicker->timer)) { + furi_timer_stop(hid_mouse_clicker->timer); + } + + with_view_model( + hid_mouse_clicker->view, + HidMouseClickerModel * model, + { + furi_timer_start( + hid_mouse_clicker->timer, furi_kernel_get_tick_frequency() / model->rate); + }, + true); +} + +static void hid_mouse_clicker_draw_callback(Canvas* canvas, void* context) { + furi_assert(context); + HidMouseClickerModel* model = context; + + // Header + if(model->transport == HidTransportBle) { + if(model->connected) { + canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); + } else { + canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); + } + } + + canvas_set_font(canvas, FontPrimary); + elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Mouse Clicker"); + + // Ok + canvas_draw_icon(canvas, 63, 25, &I_Space_65x18); + if(model->running) { + canvas_set_font(canvas, FontPrimary); + + FuriString* rate_label = furi_string_alloc(); + furi_string_printf(rate_label, "%d clicks/s\n\nUp / Down", model->rate); + elements_multiline_text(canvas, AlignLeft, 35, furi_string_get_cstr(rate_label)); + canvas_set_font(canvas, FontSecondary); + furi_string_free(rate_label); + + elements_slightly_rounded_box(canvas, 66, 27, 60, 13); + canvas_set_color(canvas, ColorWhite); + } else { + canvas_set_font(canvas, FontPrimary); + elements_multiline_text(canvas, AlignLeft, 35, "Press Start\nto start\nclicking"); + canvas_set_font(canvas, FontSecondary); + } + canvas_draw_icon(canvas, 74, 29, &I_Ok_btn_9x9); + if(model->running) { + elements_multiline_text_aligned(canvas, 91, 36, AlignLeft, AlignBottom, "Stop"); + } else { + elements_multiline_text_aligned(canvas, 91, 36, AlignLeft, AlignBottom, "Start"); + } + canvas_set_color(canvas, ColorBlack); + + // Back + canvas_draw_icon(canvas, 74, 49, &I_Pin_back_arrow_10x8); + elements_multiline_text_aligned(canvas, 91, 57, AlignLeft, AlignBottom, "Quit"); +} + +static void hid_mouse_clicker_timer_callback(void* context) { + furi_assert(context); + HidMouseClicker* hid_mouse_clicker = context; + with_view_model( + hid_mouse_clicker->view, + HidMouseClickerModel * model, + { + if(model->running) { + hid_hal_mouse_press(hid_mouse_clicker->hid, HID_MOUSE_BTN_LEFT); + hid_hal_mouse_release(hid_mouse_clicker->hid, HID_MOUSE_BTN_LEFT); + } + }, + false); +} + +static void hid_mouse_clicker_enter_callback(void* context) { + hid_mouse_clicker_start_or_restart_timer(context); +} + +static void hid_mouse_clicker_exit_callback(void* context) { + furi_assert(context); + HidMouseClicker* hid_mouse_clicker = context; + furi_timer_stop(hid_mouse_clicker->timer); +} + +static bool hid_mouse_clicker_input_callback(InputEvent* event, void* context) { + furi_assert(context); + HidMouseClicker* hid_mouse_clicker = context; + + bool consumed = false; + bool rate_changed = false; + + if(event->type != InputTypeShort && event->type != InputTypeRepeat) { + return false; + } + + with_view_model( + hid_mouse_clicker->view, + HidMouseClickerModel * model, + { + switch(event->key) { + case InputKeyOk: + model->running = !model->running; + consumed = true; + break; + case InputKeyUp: + if(model->rate < MAXIMUM_CLICK_RATE) { + model->rate++; + } + rate_changed = true; + consumed = true; + break; + case InputKeyDown: + if(model->rate > 1) { + model->rate--; + } + rate_changed = true; + consumed = true; + break; + default: + consumed = true; + break; + } + }, + true); + + if(rate_changed) { + hid_mouse_clicker_start_or_restart_timer(context); + } + + return consumed; +} + +HidMouseClicker* hid_mouse_clicker_alloc(Hid* hid) { + HidMouseClicker* hid_mouse_clicker = malloc(sizeof(HidMouseClicker)); + + hid_mouse_clicker->view = view_alloc(); + view_set_context(hid_mouse_clicker->view, hid_mouse_clicker); + view_allocate_model( + hid_mouse_clicker->view, ViewModelTypeLocking, sizeof(HidMouseClickerModel)); + view_set_draw_callback(hid_mouse_clicker->view, hid_mouse_clicker_draw_callback); + view_set_input_callback(hid_mouse_clicker->view, hid_mouse_clicker_input_callback); + view_set_enter_callback(hid_mouse_clicker->view, hid_mouse_clicker_enter_callback); + view_set_exit_callback(hid_mouse_clicker->view, hid_mouse_clicker_exit_callback); + + hid_mouse_clicker->hid = hid; + + hid_mouse_clicker->timer = furi_timer_alloc( + hid_mouse_clicker_timer_callback, FuriTimerTypePeriodic, hid_mouse_clicker); + + with_view_model( + hid_mouse_clicker->view, + HidMouseClickerModel * model, + { + model->transport = hid->transport; + model->rate = DEFAULT_CLICK_RATE; + }, + true); + + return hid_mouse_clicker; +} + +void hid_mouse_clicker_free(HidMouseClicker* hid_mouse_clicker) { + furi_assert(hid_mouse_clicker); + + furi_timer_stop(hid_mouse_clicker->timer); + furi_timer_free(hid_mouse_clicker->timer); + + view_free(hid_mouse_clicker->view); + + free(hid_mouse_clicker); +} + +View* hid_mouse_clicker_get_view(HidMouseClicker* hid_mouse_clicker) { + furi_assert(hid_mouse_clicker); + return hid_mouse_clicker->view; +} + +void hid_mouse_clicker_set_connected_status(HidMouseClicker* hid_mouse_clicker, bool connected) { + furi_assert(hid_mouse_clicker); + with_view_model( + hid_mouse_clicker->view, + HidMouseClickerModel * model, + { model->connected = connected; }, + true); +} diff --git a/applications/external/hid_app/views/hid_mouse_clicker.h b/applications/external/hid_app/views/hid_mouse_clicker.h new file mode 100644 index 00000000000..d72847baa70 --- /dev/null +++ b/applications/external/hid_app/views/hid_mouse_clicker.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +typedef struct Hid Hid; +typedef struct HidMouseClicker HidMouseClicker; + +HidMouseClicker* hid_mouse_clicker_alloc(Hid* bt_hid); + +void hid_mouse_clicker_free(HidMouseClicker* hid_mouse_clicker); + +View* hid_mouse_clicker_get_view(HidMouseClicker* hid_mouse_clicker); + +void hid_mouse_clicker_set_connected_status(HidMouseClicker* hid_mouse_clicker, bool connected); diff --git a/applications/external/hid_app/views/hid_mouse_jiggler.c b/applications/external/hid_app/views/hid_mouse_jiggler.c index d8f1f892867..15547eb26b5 100644 --- a/applications/external/hid_app/views/hid_mouse_jiggler.c +++ b/applications/external/hid_app/views/hid_mouse_jiggler.c @@ -95,7 +95,7 @@ static bool hid_mouse_jiggler_input_callback(InputEvent* event, void* context) { bool consumed = false; - if(event->key == InputKeyOk) { + if(event->type == InputTypeShort && event->key == InputKeyOk) { with_view_model( hid_mouse_jiggler->view, HidMouseJigglerModel * model, From d2ca67d261217f445abaf9b3cb0926250d1385a9 Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Thu, 4 May 2023 07:04:26 +0400 Subject: [PATCH 539/824] [FL-3242] SubGhz: refactoring app (#2554) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * SubGhz: add SubGhzThresholdRssi * SubGhz: remove direct reading of subghz-txrx-txrx_state * SubGhz: remove direct reading subghz->txrx->hopper_state * SubGhz: remove direct reading subghz->lock * SubGhz: check load type file * SubGhz: remove direct reading subghz->txrx->rx_key_state * SubGhz: remove direct reading subghz->txrx->speaker_state * SubGhz: refactoring subghz_scene_set_type.c * SubGhz: moving "txrx" entity to a separate file * SubGhz: show error tx start * SubGhz: refactoring RPC * SubGhz: value get optimizations * SubGhz: fix name file * SubGhz: add function description * SubGhz: fix double back with a blocked transmission in this region and speacker, when a transmission is blocked in this region * SubGhz: correct spelling * SubGhz: better naming * SubGhz: simplify includes Co-authored-by: SG Co-authored-by: あく --- .../views/weather_station_receiver.c | 6 +- .../main/subghz/helpers/subghz_custom_event.h | 5 +- .../subghz_frequency_analyzer_worker.c | 2 +- .../subghz/helpers/subghz_threshold_rssi.c | 60 ++ .../subghz/helpers/subghz_threshold_rssi.h | 43 ++ .../main/subghz/helpers/subghz_txrx.c | 521 ++++++++++++++++++ .../main/subghz/helpers/subghz_txrx.h | 290 ++++++++++ .../helpers/subghz_txrx_create_potocol_key.c | 164 ++++++ .../helpers/subghz_txrx_create_potocol_key.h | 96 ++++ .../main/subghz/helpers/subghz_txrx_i.h | 27 + .../main/subghz/helpers/subghz_types.h | 7 + .../main/subghz/scenes/subghz_scene_delete.c | 4 +- .../subghz/scenes/subghz_scene_delete_raw.c | 2 +- .../subghz/scenes/subghz_scene_need_saving.c | 18 +- .../subghz/scenes/subghz_scene_read_raw.c | 229 +++----- .../subghz/scenes/subghz_scene_receiver.c | 122 ++-- .../scenes/subghz_scene_receiver_config.c | 133 +++-- .../scenes/subghz_scene_receiver_info.c | 112 ++-- .../subghz/scenes/subghz_scene_region_info.c | 3 +- .../main/subghz/scenes/subghz_scene_rpc.c | 20 +- .../subghz/scenes/subghz_scene_save_name.c | 16 +- .../subghz/scenes/subghz_scene_save_success.c | 4 +- .../main/subghz/scenes/subghz_scene_saved.c | 4 +- .../subghz/scenes/subghz_scene_set_type.c | 255 ++------- .../subghz/scenes/subghz_scene_show_error.c | 11 +- .../main/subghz/scenes/subghz_scene_start.c | 2 +- .../subghz/scenes/subghz_scene_transmitter.c | 46 +- applications/main/subghz/subghz.c | 95 +--- applications/main/subghz/subghz_i.c | 403 +++----------- applications/main/subghz/subghz_i.h | 76 +-- applications/main/subghz/views/receiver.c | 23 +- applications/main/subghz/views/receiver.h | 2 +- .../main/subghz/views/subghz_read_raw.c | 8 +- .../main/subghz/views/subghz_read_raw.h | 2 +- applications/main/subghz/views/transmitter.c | 6 +- applications/main/subghz/views/transmitter.h | 2 +- lib/subghz/environment.c | 2 + 37 files changed, 1704 insertions(+), 1117 deletions(-) create mode 100644 applications/main/subghz/helpers/subghz_threshold_rssi.c create mode 100644 applications/main/subghz/helpers/subghz_threshold_rssi.h create mode 100644 applications/main/subghz/helpers/subghz_txrx.c create mode 100644 applications/main/subghz/helpers/subghz_txrx.h create mode 100644 applications/main/subghz/helpers/subghz_txrx_create_potocol_key.c create mode 100644 applications/main/subghz/helpers/subghz_txrx_create_potocol_key.h create mode 100644 applications/main/subghz/helpers/subghz_txrx_i.h diff --git a/applications/external/weather_station/views/weather_station_receiver.c b/applications/external/weather_station/views/weather_station_receiver.c index f8e2e328899..c4ce1627ec2 100644 --- a/applications/external/weather_station/views/weather_station_receiver.c +++ b/applications/external/weather_station/views/weather_station_receiver.c @@ -12,7 +12,7 @@ #define MENU_ITEMS 4u #define UNLOCK_CNT 3 -#define SUBGHZ_RAW_TRESHOLD_MIN -90.0f +#define SUBGHZ_RAW_THRESHOLD_MIN -90.0f typedef struct { FuriString* item_str; uint8_t type; @@ -69,10 +69,10 @@ void ws_view_receiver_set_rssi(WSReceiver* instance, float rssi) { instance->view, WSReceiverModel * model, { - if(rssi < SUBGHZ_RAW_TRESHOLD_MIN) { + if(rssi < SUBGHZ_RAW_THRESHOLD_MIN) { model->u_rssi = 0; } else { - model->u_rssi = (uint8_t)(rssi - SUBGHZ_RAW_TRESHOLD_MIN); + model->u_rssi = (uint8_t)(rssi - SUBGHZ_RAW_THRESHOLD_MIN); } }, true); diff --git a/applications/main/subghz/helpers/subghz_custom_event.h b/applications/main/subghz/helpers/subghz_custom_event.h index 765c9e251c5..285b4a60f91 100644 --- a/applications/main/subghz/helpers/subghz_custom_event.h +++ b/applications/main/subghz/helpers/subghz_custom_event.h @@ -6,14 +6,13 @@ typedef enum { SubGhzCustomEventManagerSetRAW, //SubmenuIndex - SubmenuIndexPricenton, + SubmenuIndexPricenton_433, + SubmenuIndexPricenton_315, SubmenuIndexNiceFlo12bit, SubmenuIndexNiceFlo24bit, SubmenuIndexCAME12bit, SubmenuIndexCAME24bit, SubmenuIndexCAMETwee, - SubmenuIndexNeroSketch, - SubmenuIndexNeroRadio, SubmenuIndexGateTX, SubmenuIndexDoorHan_315_00, SubmenuIndexDoorHan_433_92, diff --git a/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.c b/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.c index 5d1a80a395c..4a4445faad8 100644 --- a/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.c +++ b/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.c @@ -261,7 +261,7 @@ SubGhzFrequencyAnalyzerWorker* subghz_frequency_analyzer_worker_alloc(void* cont instance->thread = furi_thread_alloc_ex( "SubGhzFAWorker", 2048, subghz_frequency_analyzer_worker_thread, instance); SubGhz* subghz = context; - instance->setting = subghz->setting; + instance->setting = subghz_txrx_get_setting(subghz->txrx); return instance; } diff --git a/applications/main/subghz/helpers/subghz_threshold_rssi.c b/applications/main/subghz/helpers/subghz_threshold_rssi.c new file mode 100644 index 00000000000..04a06bc1736 --- /dev/null +++ b/applications/main/subghz/helpers/subghz_threshold_rssi.c @@ -0,0 +1,60 @@ +#include "subghz_threshold_rssi.h" +#include +#include "../subghz_i.h" + +#define TAG "SubGhzThresholdRssi" +#define THRESHOLD_RSSI_LOW_COUNT 10 + +struct SubGhzThresholdRssi { + float threshold_rssi; + uint8_t threshold_rssi_low_count; +}; + +SubGhzThresholdRssi* subghz_threshold_rssi_alloc(void) { + SubGhzThresholdRssi* instance = malloc(sizeof(SubGhzThresholdRssi)); + instance->threshold_rssi = SUBGHZ_RAW_THRESHOLD_MIN; + instance->threshold_rssi_low_count = THRESHOLD_RSSI_LOW_COUNT; + return instance; +} + +void subghz_threshold_rssi_free(SubGhzThresholdRssi* instance) { + furi_assert(instance); + free(instance); +} + +void subghz_threshold_rssi_set(SubGhzThresholdRssi* instance, float rssi) { + furi_assert(instance); + instance->threshold_rssi = rssi; +} + +float subghz_threshold_rssi_get(SubGhzThresholdRssi* instance) { + furi_assert(instance); + return instance->threshold_rssi; +} + +SubGhzThresholdRssiData subghz_threshold_get_rssi_data(SubGhzThresholdRssi* instance) { + furi_assert(instance); + float rssi = furi_hal_subghz_get_rssi(); + SubGhzThresholdRssiData ret = {.rssi = rssi, .is_above = false}; + + if(float_is_equal(instance->threshold_rssi, SUBGHZ_RAW_THRESHOLD_MIN)) { + ret.is_above = true; + } else { + if(rssi < instance->threshold_rssi) { + instance->threshold_rssi_low_count++; + if(instance->threshold_rssi_low_count > THRESHOLD_RSSI_LOW_COUNT) { + instance->threshold_rssi_low_count = THRESHOLD_RSSI_LOW_COUNT; + } + ret.is_above = false; + } else { + instance->threshold_rssi_low_count = 0; + } + + if(instance->threshold_rssi_low_count == THRESHOLD_RSSI_LOW_COUNT) { + ret.is_above = false; + } else { + ret.is_above = true; + } + } + return ret; +} diff --git a/applications/main/subghz/helpers/subghz_threshold_rssi.h b/applications/main/subghz/helpers/subghz_threshold_rssi.h new file mode 100644 index 00000000000..e28092acbc0 --- /dev/null +++ b/applications/main/subghz/helpers/subghz_threshold_rssi.h @@ -0,0 +1,43 @@ +#pragma once + +#include + +typedef struct { + float rssi; /**< Current RSSI */ + bool is_above; /**< Exceeded threshold level */ +} SubGhzThresholdRssiData; + +typedef struct SubGhzThresholdRssi SubGhzThresholdRssi; + +/** Allocate SubGhzThresholdRssi + * + * @return SubGhzThresholdRssi* + */ +SubGhzThresholdRssi* subghz_threshold_rssi_alloc(void); + +/** Free SubGhzThresholdRssi + * + * @param instance Pointer to a SubGhzThresholdRssi + */ +void subghz_threshold_rssi_free(SubGhzThresholdRssi* instance); + +/** Set threshold + * + * @param instance Pointer to a SubGhzThresholdRssi + * @param rssi RSSI threshold + */ +void subghz_threshold_rssi_set(SubGhzThresholdRssi* instance, float rssi); + +/** Get threshold + * + * @param instance Pointer to a SubGhzThresholdRssi + * @return float RSSI threshold + */ +float subghz_threshold_rssi_get(SubGhzThresholdRssi* instance); + +/** Check threshold + * + * @param instance Pointer to a SubGhzThresholdRssi + * @return SubGhzThresholdRssiData + */ +SubGhzThresholdRssiData subghz_threshold_get_rssi_data(SubGhzThresholdRssi* instance); diff --git a/applications/main/subghz/helpers/subghz_txrx.c b/applications/main/subghz/helpers/subghz_txrx.c new file mode 100644 index 00000000000..1517cb99892 --- /dev/null +++ b/applications/main/subghz/helpers/subghz_txrx.c @@ -0,0 +1,521 @@ +#include "subghz_txrx_i.h" + +#include + +#define TAG "SubGhz" + +SubGhzTxRx* subghz_txrx_alloc() { + SubGhzTxRx* instance = malloc(sizeof(SubGhzTxRx)); + instance->setting = subghz_setting_alloc(); + subghz_setting_load(instance->setting, EXT_PATH("subghz/assets/setting_user")); + + instance->preset = malloc(sizeof(SubGhzRadioPreset)); + instance->preset->name = furi_string_alloc(); + subghz_txrx_set_preset( + instance, "AM650", subghz_setting_get_default_frequency(instance->setting), NULL, 0); + + instance->txrx_state = SubGhzTxRxStateSleep; + + subghz_txrx_hopper_set_state(instance, SubGhzHopperStateOFF); + subghz_txrx_speaker_set_state(instance, SubGhzSpeakerStateDisable); + + instance->worker = subghz_worker_alloc(); + instance->fff_data = flipper_format_string_alloc(); + + instance->environment = subghz_environment_alloc(); + instance->is_database_loaded = subghz_environment_load_keystore( + instance->environment, EXT_PATH("subghz/assets/keeloq_mfcodes")); + subghz_environment_load_keystore( + instance->environment, EXT_PATH("subghz/assets/keeloq_mfcodes_user")); + subghz_environment_set_came_atomo_rainbow_table_file_name( + instance->environment, EXT_PATH("subghz/assets/came_atomo")); + subghz_environment_set_alutech_at_4n_rainbow_table_file_name( + instance->environment, EXT_PATH("subghz/assets/alutech_at_4n")); + subghz_environment_set_nice_flor_s_rainbow_table_file_name( + instance->environment, EXT_PATH("subghz/assets/nice_flor_s")); + subghz_environment_set_protocol_registry( + instance->environment, (void*)&subghz_protocol_registry); + instance->receiver = subghz_receiver_alloc_init(instance->environment); + + subghz_worker_set_overrun_callback( + instance->worker, (SubGhzWorkerOverrunCallback)subghz_receiver_reset); + subghz_worker_set_pair_callback( + instance->worker, (SubGhzWorkerPairCallback)subghz_receiver_decode); + subghz_worker_set_context(instance->worker, instance->receiver); + + return instance; +} + +void subghz_txrx_free(SubGhzTxRx* instance) { + furi_assert(instance); + + subghz_worker_free(instance->worker); + subghz_receiver_free(instance->receiver); + subghz_environment_free(instance->environment); + flipper_format_free(instance->fff_data); + furi_string_free(instance->preset->name); + subghz_setting_free(instance->setting); + free(instance->preset); + free(instance); +} + +bool subghz_txrx_is_database_loaded(SubGhzTxRx* instance) { + furi_assert(instance); + return instance->is_database_loaded; +} + +void subghz_txrx_set_preset( + SubGhzTxRx* instance, + const char* preset_name, + uint32_t frequency, + uint8_t* preset_data, + size_t preset_data_size) { + furi_assert(instance); + furi_string_set(instance->preset->name, preset_name); + SubGhzRadioPreset* preset = instance->preset; + preset->frequency = frequency; + preset->data = preset_data; + preset->data_size = preset_data_size; +} + +const char* subghz_txrx_get_preset_name(SubGhzTxRx* instance, const char* preset) { + UNUSED(instance); + const char* preset_name = ""; + if(!strcmp(preset, "FuriHalSubGhzPresetOok270Async")) { + preset_name = "AM270"; + } else if(!strcmp(preset, "FuriHalSubGhzPresetOok650Async")) { + preset_name = "AM650"; + } else if(!strcmp(preset, "FuriHalSubGhzPreset2FSKDev238Async")) { + preset_name = "FM238"; + } else if(!strcmp(preset, "FuriHalSubGhzPreset2FSKDev476Async")) { + preset_name = "FM476"; + } else if(!strcmp(preset, "FuriHalSubGhzPresetCustom")) { + preset_name = "CUSTOM"; + } else { + FURI_LOG_E(TAG, "Unknown preset"); + } + return preset_name; +} + +SubGhzRadioPreset subghz_txrx_get_preset(SubGhzTxRx* instance) { + furi_assert(instance); + return *instance->preset; +} + +void subghz_txrx_get_frequency_and_modulation( + SubGhzTxRx* instance, + FuriString* frequency, + FuriString* modulation) { + furi_assert(instance); + SubGhzRadioPreset* preset = instance->preset; + if(frequency != NULL) { + furi_string_printf( + frequency, + "%03ld.%02ld", + preset->frequency / 1000000 % 1000, + preset->frequency / 10000 % 100); + } + if(modulation != NULL) { + furi_string_printf(modulation, "%.2s", furi_string_get_cstr(preset->name)); + } +} + +static void subghz_txrx_begin(SubGhzTxRx* instance, uint8_t* preset_data) { + furi_assert(instance); + furi_hal_subghz_reset(); + furi_hal_subghz_idle(); + furi_hal_subghz_load_custom_preset(preset_data); + furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow); + instance->txrx_state = SubGhzTxRxStateIDLE; +} + +static uint32_t subghz_txrx_rx(SubGhzTxRx* instance, uint32_t frequency) { + furi_assert(instance); + if(!furi_hal_subghz_is_frequency_valid(frequency)) { + furi_crash("SubGhz: Incorrect RX frequency."); + } + furi_assert( + instance->txrx_state != SubGhzTxRxStateRx && instance->txrx_state != SubGhzTxRxStateSleep); + + furi_hal_subghz_idle(); + uint32_t value = furi_hal_subghz_set_frequency_and_path(frequency); + furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow); + furi_hal_subghz_flush_rx(); + subghz_txrx_speaker_on(instance); + furi_hal_subghz_rx(); + + furi_hal_subghz_start_async_rx(subghz_worker_rx_callback, instance->worker); + subghz_worker_start(instance->worker); + instance->txrx_state = SubGhzTxRxStateRx; + return value; +} + +static void subghz_txrx_idle(SubGhzTxRx* instance) { + furi_assert(instance); + furi_assert(instance->txrx_state != SubGhzTxRxStateSleep); + furi_hal_subghz_idle(); + subghz_txrx_speaker_off(instance); + instance->txrx_state = SubGhzTxRxStateIDLE; +} + +static void subghz_txrx_rx_end(SubGhzTxRx* instance) { + furi_assert(instance); + furi_assert(instance->txrx_state == SubGhzTxRxStateRx); + + if(subghz_worker_is_running(instance->worker)) { + subghz_worker_stop(instance->worker); + furi_hal_subghz_stop_async_rx(); + } + furi_hal_subghz_idle(); + subghz_txrx_speaker_off(instance); + instance->txrx_state = SubGhzTxRxStateIDLE; +} + +void subghz_txrx_sleep(SubGhzTxRx* instance) { + furi_assert(instance); + furi_hal_subghz_sleep(); + instance->txrx_state = SubGhzTxRxStateSleep; +} + +static bool subghz_txrx_tx(SubGhzTxRx* instance, uint32_t frequency) { + furi_assert(instance); + if(!furi_hal_subghz_is_frequency_valid(frequency)) { + furi_crash("SubGhz: Incorrect TX frequency."); + } + furi_assert(instance->txrx_state != SubGhzTxRxStateSleep); + furi_hal_subghz_idle(); + furi_hal_subghz_set_frequency_and_path(frequency); + furi_hal_gpio_write(&gpio_cc1101_g0, false); + furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); + bool ret = furi_hal_subghz_tx(); + if(ret) { + subghz_txrx_speaker_on(instance); + instance->txrx_state = SubGhzTxRxStateTx; + } + + return ret; +} + +SubGhzTxRxStartTxState subghz_txrx_tx_start(SubGhzTxRx* instance, FlipperFormat* flipper_format) { + furi_assert(instance); + furi_assert(flipper_format); + + subghz_txrx_stop(instance); + + SubGhzTxRxStartTxState ret = SubGhzTxRxStartTxStateErrorParserOthers; + FuriString* temp_str = furi_string_alloc(); + uint32_t repeat = 200; + do { + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + if(!flipper_format_read_string(flipper_format, "Protocol", temp_str)) { + FURI_LOG_E(TAG, "Missing Protocol"); + break; + } + if(!flipper_format_insert_or_update_uint32(flipper_format, "Repeat", &repeat, 1)) { + FURI_LOG_E(TAG, "Unable Repeat"); + break; + } + ret = SubGhzTxRxStartTxStateOk; + + SubGhzRadioPreset* preset = instance->preset; + instance->transmitter = + subghz_transmitter_alloc_init(instance->environment, furi_string_get_cstr(temp_str)); + + if(instance->transmitter) { + if(subghz_transmitter_deserialize(instance->transmitter, flipper_format) == + SubGhzProtocolStatusOk) { + if(strcmp(furi_string_get_cstr(preset->name), "") != 0) { + subghz_txrx_begin( + instance, + subghz_setting_get_preset_data_by_name( + instance->setting, furi_string_get_cstr(preset->name))); + if(preset->frequency) { + if(!subghz_txrx_tx(instance, preset->frequency)) { + FURI_LOG_E(TAG, "Only Rx"); + ret = SubGhzTxRxStartTxStateErrorOnlyRx; + } + } else { + ret = SubGhzTxRxStartTxStateErrorParserOthers; + } + + } else { + FURI_LOG_E( + TAG, "Unknown name preset \" %s \"", furi_string_get_cstr(preset->name)); + ret = SubGhzTxRxStartTxStateErrorParserOthers; + } + + if(ret == SubGhzTxRxStartTxStateOk) { + //Start TX + furi_hal_subghz_start_async_tx( + subghz_transmitter_yield, instance->transmitter); + } + } else { + ret = SubGhzTxRxStartTxStateErrorParserOthers; + } + } else { + ret = SubGhzTxRxStartTxStateErrorParserOthers; + } + if(ret != SubGhzTxRxStartTxStateOk) { + subghz_transmitter_free(instance->transmitter); + if(instance->txrx_state != SubGhzTxRxStateIDLE) { + subghz_txrx_idle(instance); + } + } + + } while(false); + furi_string_free(temp_str); + return ret; +} + +void subghz_txrx_rx_start(SubGhzTxRx* instance) { + furi_assert(instance); + subghz_txrx_stop(instance); + subghz_txrx_begin( + instance, + subghz_setting_get_preset_data_by_name( + subghz_txrx_get_setting(instance), furi_string_get_cstr(instance->preset->name))); + subghz_txrx_rx(instance, instance->preset->frequency); +} + +void subghz_txrx_set_need_save_callback( + SubGhzTxRx* instance, + SubGhzTxRxNeedSaveCallback callback, + void* context) { + furi_assert(instance); + instance->need_save_callback = callback; + instance->need_save_context = context; +} + +static void subghz_txrx_tx_stop(SubGhzTxRx* instance) { + furi_assert(instance); + furi_assert(instance->txrx_state == SubGhzTxRxStateTx); + //Stop TX + furi_hal_subghz_stop_async_tx(); + subghz_transmitter_stop(instance->transmitter); + subghz_transmitter_free(instance->transmitter); + + //if protocol dynamic then we save the last upload + if(instance->decoder_result->protocol->type == SubGhzProtocolTypeDynamic) { + if(instance->need_save_callback) { + instance->need_save_callback(instance->need_save_context); + } + } + subghz_txrx_idle(instance); + subghz_txrx_speaker_off(instance); + //Todo: Show message + // notification_message(notifications, &sequence_reset_red); +} + +FlipperFormat* subghz_txrx_get_fff_data(SubGhzTxRx* instance) { + furi_assert(instance); + return instance->fff_data; +} + +SubGhzSetting* subghz_txrx_get_setting(SubGhzTxRx* instance) { + furi_assert(instance); + return instance->setting; +} + +void subghz_txrx_stop(SubGhzTxRx* instance) { + furi_assert(instance); + + switch(instance->txrx_state) { + case SubGhzTxRxStateTx: + subghz_txrx_tx_stop(instance); + subghz_txrx_speaker_unmute(instance); + break; + case SubGhzTxRxStateRx: + subghz_txrx_rx_end(instance); + subghz_txrx_speaker_mute(instance); + break; + + default: + break; + } +} + +void subghz_txrx_hopper_update(SubGhzTxRx* instance) { + furi_assert(instance); + + switch(instance->hopper_state) { + case SubGhzHopperStateOFF: + case SubGhzHopperStatePause: + return; + case SubGhzHopperStateRSSITimeOut: + if(instance->hopper_timeout != 0) { + instance->hopper_timeout--; + return; + } + break; + default: + break; + } + float rssi = -127.0f; + if(instance->hopper_state != SubGhzHopperStateRSSITimeOut) { + // See RSSI Calculation timings in CC1101 17.3 RSSI + rssi = furi_hal_subghz_get_rssi(); + + // Stay if RSSI is high enough + if(rssi > -90.0f) { + instance->hopper_timeout = 10; + instance->hopper_state = SubGhzHopperStateRSSITimeOut; + return; + } + } else { + instance->hopper_state = SubGhzHopperStateRunnig; + } + // Select next frequency + if(instance->hopper_idx_frequency < + subghz_setting_get_hopper_frequency_count(instance->setting) - 1) { + instance->hopper_idx_frequency++; + } else { + instance->hopper_idx_frequency = 0; + } + + if(instance->txrx_state == SubGhzTxRxStateRx) { + subghz_txrx_rx_end(instance); + }; + if(instance->txrx_state == SubGhzTxRxStateIDLE) { + subghz_receiver_reset(instance->receiver); + instance->preset->frequency = + subghz_setting_get_hopper_frequency(instance->setting, instance->hopper_idx_frequency); + subghz_txrx_rx(instance, instance->preset->frequency); + } +} + +SubGhzHopperState subghz_txrx_hopper_get_state(SubGhzTxRx* instance) { + furi_assert(instance); + return instance->hopper_state; +} + +void subghz_txrx_hopper_set_state(SubGhzTxRx* instance, SubGhzHopperState state) { + furi_assert(instance); + instance->hopper_state = state; +} + +void subghz_txrx_hopper_unpause(SubGhzTxRx* instance) { + furi_assert(instance); + if(instance->hopper_state == SubGhzHopperStatePause) { + instance->hopper_state = SubGhzHopperStateRunnig; + } +} + +void subghz_txrx_hopper_pause(SubGhzTxRx* instance) { + furi_assert(instance); + if(instance->hopper_state == SubGhzHopperStateRunnig) { + instance->hopper_state = SubGhzHopperStatePause; + } +} + +void subghz_txrx_speaker_on(SubGhzTxRx* instance) { + furi_assert(instance); + if(instance->speaker_state == SubGhzSpeakerStateEnable) { + if(furi_hal_speaker_acquire(30)) { + furi_hal_subghz_set_async_mirror_pin(&gpio_speaker); + } else { + instance->speaker_state = SubGhzSpeakerStateDisable; + } + } +} + +void subghz_txrx_speaker_off(SubGhzTxRx* instance) { + furi_assert(instance); + if(instance->speaker_state != SubGhzSpeakerStateDisable) { + if(furi_hal_speaker_is_mine()) { + furi_hal_subghz_set_async_mirror_pin(NULL); + furi_hal_speaker_release(); + if(instance->speaker_state == SubGhzSpeakerStateShutdown) + instance->speaker_state = SubGhzSpeakerStateDisable; + } + } +} + +void subghz_txrx_speaker_mute(SubGhzTxRx* instance) { + furi_assert(instance); + if(instance->speaker_state == SubGhzSpeakerStateEnable) { + if(furi_hal_speaker_is_mine()) { + furi_hal_subghz_set_async_mirror_pin(NULL); + } + } +} + +void subghz_txrx_speaker_unmute(SubGhzTxRx* instance) { + furi_assert(instance); + if(instance->speaker_state == SubGhzSpeakerStateEnable) { + if(furi_hal_speaker_is_mine()) { + furi_hal_subghz_set_async_mirror_pin(&gpio_speaker); + } + } +} + +void subghz_txrx_speaker_set_state(SubGhzTxRx* instance, SubGhzSpeakerState state) { + furi_assert(instance); + instance->speaker_state = state; +} + +SubGhzSpeakerState subghz_txrx_speaker_get_state(SubGhzTxRx* instance) { + furi_assert(instance); + return instance->speaker_state; +} + +bool subghz_txrx_load_decoder_by_name_protocol(SubGhzTxRx* instance, const char* name_protocol) { + furi_assert(instance); + furi_assert(name_protocol); + bool res = false; + instance->decoder_result = + subghz_receiver_search_decoder_base_by_name(instance->receiver, name_protocol); + if(instance->decoder_result) { + res = true; + } + return res; +} + +SubGhzProtocolDecoderBase* subghz_txrx_get_decoder(SubGhzTxRx* instance) { + furi_assert(instance); + return instance->decoder_result; +} + +bool subghz_txrx_protocol_is_serializable(SubGhzTxRx* instance) { + furi_assert(instance); + return ( + (instance->decoder_result->protocol->flag & SubGhzProtocolFlag_Save) == + SubGhzProtocolFlag_Save); +} + +bool subghz_txrx_protocol_is_transmittable(SubGhzTxRx* instance, bool check_type) { + furi_assert(instance); + const SubGhzProtocol* protocol = instance->decoder_result->protocol; + if(check_type) { + return ( + ((protocol->flag & SubGhzProtocolFlag_Send) == SubGhzProtocolFlag_Send) && + protocol->encoder->deserialize && protocol->type == SubGhzProtocolTypeStatic); + } + return ( + ((protocol->flag & SubGhzProtocolFlag_Send) == SubGhzProtocolFlag_Send) && + protocol->encoder->deserialize); +} + +void subghz_txrx_receiver_set_filter(SubGhzTxRx* instance, SubGhzProtocolFlag filter) { + furi_assert(instance); + subghz_receiver_set_filter(instance->receiver, filter); +} + +void subghz_txrx_set_rx_calback( + SubGhzTxRx* instance, + SubGhzReceiverCallback callback, + void* context) { + subghz_receiver_set_rx_callback(instance->receiver, callback, context); +} + +void subghz_txrx_set_raw_file_encoder_worker_callback_end( + SubGhzTxRx* instance, + SubGhzProtocolEncoderRAWCallbackEnd callback, + void* context) { + subghz_protocol_raw_file_encoder_worker_set_callback_end( + (SubGhzProtocolEncoderRAW*)subghz_transmitter_get_protocol_instance(instance->transmitter), + callback, + context); +} diff --git a/applications/main/subghz/helpers/subghz_txrx.h b/applications/main/subghz/helpers/subghz_txrx.h new file mode 100644 index 00000000000..0f2daf05d49 --- /dev/null +++ b/applications/main/subghz/helpers/subghz_txrx.h @@ -0,0 +1,290 @@ +#pragma once + +#include "subghz_types.h" + +#include +#include +#include +#include +#include + +typedef struct SubGhzTxRx SubGhzTxRx; + +typedef void (*SubGhzTxRxNeedSaveCallback)(void* context); + +typedef enum { + SubGhzTxRxStartTxStateOk, + SubGhzTxRxStartTxStateErrorOnlyRx, + SubGhzTxRxStartTxStateErrorParserOthers, +} SubGhzTxRxStartTxState; + +/** + * Allocate SubGhzTxRx + * + * @return SubGhzTxRx* pointer to SubGhzTxRx + */ +SubGhzTxRx* subghz_txrx_alloc(); + +/** + * Free SubGhzTxRx + * + * @param instance Pointer to a SubGhzTxRx + */ +void subghz_txrx_free(SubGhzTxRx* instance); + +/** + * Check if the database is loaded + * + * @param instance Pointer to a SubGhzTxRx + * @return bool True if the database is loaded + */ +bool subghz_txrx_is_database_loaded(SubGhzTxRx* instance); + +/** + * Set preset + * + * @param instance Pointer to a SubGhzTxRx + * @param preset_name Name of preset + * @param frequency Frequency in Hz + * @param preset_data Data of preset + * @param preset_data_size Size of preset data + */ +void subghz_txrx_set_preset( + SubGhzTxRx* instance, + const char* preset_name, + uint32_t frequency, + uint8_t* preset_data, + size_t preset_data_size); + +/** + * Get name of preset + * + * @param instance Pointer to a SubGhzTxRx + * @param preset String of preset + * @return const char* Name of preset + */ +const char* subghz_txrx_get_preset_name(SubGhzTxRx* instance, const char* preset); + +/** + * Get of preset + * + * @param instance Pointer to a SubGhzTxRx + * @return SubGhzRadioPreset Preset + */ +SubGhzRadioPreset subghz_txrx_get_preset(SubGhzTxRx* instance); + +/** + * Get string frequency and modulation + * + * @param instance Pointer to a SubGhzTxRx + * @param frequency Pointer to a string frequency + * @param modulation Pointer to a string modulation + */ +void subghz_txrx_get_frequency_and_modulation( + SubGhzTxRx* instance, + FuriString* frequency, + FuriString* modulation); + +/** + * Start TX CC1101 + * + * @param instance Pointer to a SubGhzTxRx + * @param flipper_format Pointer to a FlipperFormat + * @return SubGhzTxRxStartTxState + */ +SubGhzTxRxStartTxState subghz_txrx_tx_start(SubGhzTxRx* instance, FlipperFormat* flipper_format); + +/** + * Start RX CC1101 + * + * @param instance Pointer to a SubGhzTxRx + */ +void subghz_txrx_rx_start(SubGhzTxRx* instance); + +/** + * Stop TX/RX CC1101 + * + * @param instance Pointer to a SubGhzTxRx + */ +void subghz_txrx_stop(SubGhzTxRx* instance); + +/** + * Set sleep mode CC1101 + * + * @param instance Pointer to a SubGhzTxRx + */ +void subghz_txrx_sleep(SubGhzTxRx* instance); + +/** + * Update frequency CC1101 in automatic mode (hopper) + * + * @param instance Pointer to a SubGhzTxRx + */ +void subghz_txrx_hopper_update(SubGhzTxRx* instance); + +/** + * Get state hopper + * + * @param instance Pointer to a SubGhzTxRx + * @return SubGhzHopperState + */ +SubGhzHopperState subghz_txrx_hopper_get_state(SubGhzTxRx* instance); + +/** + * Set state hopper + * + * @param instance Pointer to a SubGhzTxRx + * @param state State hopper + */ +void subghz_txrx_hopper_set_state(SubGhzTxRx* instance, SubGhzHopperState state); + +/** + * Unpause hopper + * + * @param instance Pointer to a SubGhzTxRx + */ +void subghz_txrx_hopper_unpause(SubGhzTxRx* instance); + +/** + * Set pause hopper + * + * @param instance Pointer to a SubGhzTxRx + */ +void subghz_txrx_hopper_pause(SubGhzTxRx* instance); + +/** + * Speaker on + * + * @param instance Pointer to a SubGhzTxRx + */ +void subghz_txrx_speaker_on(SubGhzTxRx* instance); + +/** + * Speaker off + * + * @param instance Pointer to a SubGhzTxRx + */ +void subghz_txrx_speaker_off(SubGhzTxRx* instance); + +/** + * Speaker mute + * + * @param instance Pointer to a SubGhzTxRx + */ +void subghz_txrx_speaker_mute(SubGhzTxRx* instance); + +/** + * Speaker unmute + * + * @param instance Pointer to a SubGhzTxRx + */ +void subghz_txrx_speaker_unmute(SubGhzTxRx* instance); + +/** + * Set state speaker + * + * @param instance Pointer to a SubGhzTxRx + * @param state State speaker + */ +void subghz_txrx_speaker_set_state(SubGhzTxRx* instance, SubGhzSpeakerState state); + +/** + * Get state speaker + * + * @param instance Pointer to a SubGhzTxRx + * @return SubGhzSpeakerState + */ +SubGhzSpeakerState subghz_txrx_speaker_get_state(SubGhzTxRx* instance); + +/** + * load decoder by name protocol + * + * @param instance Pointer to a SubGhzTxRx + * @param name_protocol Name protocol + * @return bool True if the decoder is loaded + */ +bool subghz_txrx_load_decoder_by_name_protocol(SubGhzTxRx* instance, const char* name_protocol); + +/** + * Get decoder + * + * @param instance Pointer to a SubGhzTxRx + * @return SubGhzProtocolDecoderBase* Pointer to a SubGhzProtocolDecoderBase + */ +SubGhzProtocolDecoderBase* subghz_txrx_get_decoder(SubGhzTxRx* instance); + +/** + * Set callback for save data + * + * @param instance Pointer to a SubGhzTxRx + * @param callback Callback for save data + * @param context Context for callback + */ +void subghz_txrx_set_need_save_callback( + SubGhzTxRx* instance, + SubGhzTxRxNeedSaveCallback callback, + void* context); + +/** + * Get pointer to a load data key + * + * @param instance Pointer to a SubGhzTxRx + * @return FlipperFormat* + */ +FlipperFormat* subghz_txrx_get_fff_data(SubGhzTxRx* instance); + +/** + * Get pointer to a SugGhzSetting + * + * @param instance Pointer to a SubGhzTxRx + * @return SubGhzSetting* + */ +SubGhzSetting* subghz_txrx_get_setting(SubGhzTxRx* instance); + +/** + * Is it possible to save this protocol + * + * @param instance Pointer to a SubGhzTxRx + * @return bool True if it is possible to save this protocol + */ +bool subghz_txrx_protocol_is_serializable(SubGhzTxRx* instance); + +/** + * Is it possible to send this protocol + * + * @param instance Pointer to a SubGhzTxRx + * @return bool True if it is possible to send this protocol + */ +bool subghz_txrx_protocol_is_transmittable(SubGhzTxRx* instance, bool check_type); + +/** + * Set filter, what types of decoder to use + * + * @param instance Pointer to a SubGhzTxRx + * @param filter Filter + */ +void subghz_txrx_receiver_set_filter(SubGhzTxRx* instance, SubGhzProtocolFlag filter); + +/** + * Set callback for receive data + * + * @param instance Pointer to a SubGhzTxRx + * @param callback Callback for receive data + * @param context Context for callback + */ +void subghz_txrx_set_rx_calback( + SubGhzTxRx* instance, + SubGhzReceiverCallback callback, + void* context); + +/** + * Set callback for Raw decoder, end of data transfer + * + * @param instance Pointer to a SubGhzTxRx + * @param callback Callback for Raw decoder, end of data transfer + * @param context Context for callback + */ +void subghz_txrx_set_raw_file_encoder_worker_callback_end( + SubGhzTxRx* instance, + SubGhzProtocolEncoderRAWCallbackEnd callback, + void* context); diff --git a/applications/main/subghz/helpers/subghz_txrx_create_potocol_key.c b/applications/main/subghz/helpers/subghz_txrx_create_potocol_key.c new file mode 100644 index 00000000000..41e4f7c4efd --- /dev/null +++ b/applications/main/subghz/helpers/subghz_txrx_create_potocol_key.c @@ -0,0 +1,164 @@ +#include "subghz_txrx_i.h" +#include "subghz_txrx_create_potocol_key.h" +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define TAG "SubGhzCreateProtocolKey" + +bool subghz_txrx_gen_data_protocol( + void* context, + const char* preset_name, + uint32_t frequency, + const char* protocol_name, + uint64_t key, + uint32_t bit) { + furi_assert(context); + SubGhzTxRx* instance = context; + + bool res = false; + + subghz_txrx_set_preset(instance, preset_name, frequency, NULL, 0); + instance->decoder_result = + subghz_receiver_search_decoder_base_by_name(instance->receiver, protocol_name); + + if(instance->decoder_result == NULL) { + //TODO: Error + // furi_string_set(error_str, "Protocol not\nfound!"); + // scene_manager_next_scene(scene_manager, SubGhzSceneShowErrorSub); + FURI_LOG_E(TAG, "Protocol not found!"); + return false; + } + + do { + Stream* fff_data_stream = flipper_format_get_raw_stream(instance->fff_data); + stream_clean(fff_data_stream); + if(subghz_protocol_decoder_base_serialize( + instance->decoder_result, instance->fff_data, instance->preset) != + SubGhzProtocolStatusOk) { + FURI_LOG_E(TAG, "Unable to serialize"); + break; + } + if(!flipper_format_update_uint32(instance->fff_data, "Bit", &bit, 1)) { + FURI_LOG_E(TAG, "Unable to update Bit"); + break; + } + + uint8_t key_data[sizeof(uint64_t)] = {0}; + for(size_t i = 0; i < sizeof(uint64_t); i++) { + key_data[sizeof(uint64_t) - i - 1] = (key >> (i * 8)) & 0xFF; + } + if(!flipper_format_update_hex(instance->fff_data, "Key", key_data, sizeof(uint64_t))) { + FURI_LOG_E(TAG, "Unable to update Key"); + break; + } + res = true; + } while(false); + return res; +} + +bool subghz_txrx_gen_data_protocol_and_te( + SubGhzTxRx* instance, + const char* preset_name, + uint32_t frequency, + const char* protocol_name, + uint64_t key, + uint32_t bit, + uint32_t te) { + furi_assert(instance); + bool ret = false; + if(subghz_txrx_gen_data_protocol(instance, preset_name, frequency, protocol_name, key, bit)) { + if(!flipper_format_update_uint32(instance->fff_data, "TE", (uint32_t*)&te, 1)) { + FURI_LOG_E(TAG, "Unable to update Te"); + } else { + ret = true; + } + } + return ret; +} + +bool subghz_txrx_gen_keelog_protocol( + SubGhzTxRx* instance, + const char* name_preset, + uint32_t frequency, + const char* name_sysmem, + uint32_t serial, + uint8_t btn, + uint16_t cnt) { + furi_assert(instance); + + bool ret = false; + serial &= 0x0FFFFFFF; + instance->transmitter = + subghz_transmitter_alloc_init(instance->environment, SUBGHZ_PROTOCOL_KEELOQ_NAME); + subghz_txrx_set_preset(instance, name_preset, frequency, NULL, 0); + if(instance->transmitter) { + subghz_protocol_keeloq_create_data( + subghz_transmitter_get_protocol_instance(instance->transmitter), + instance->fff_data, + serial, + btn, + cnt, + name_sysmem, + instance->preset); + ret = true; + } + subghz_transmitter_free(instance->transmitter); + return ret; +} + +bool subghz_txrx_gen_secplus_v2_protocol( + SubGhzTxRx* instance, + const char* name_preset, + uint32_t frequency, + uint32_t serial, + uint8_t btn, + uint32_t cnt) { + furi_assert(instance); + + bool ret = false; + instance->transmitter = + subghz_transmitter_alloc_init(instance->environment, SUBGHZ_PROTOCOL_SECPLUS_V2_NAME); + subghz_txrx_set_preset(instance, name_preset, frequency, NULL, 0); + if(instance->transmitter) { + subghz_protocol_secplus_v2_create_data( + subghz_transmitter_get_protocol_instance(instance->transmitter), + instance->fff_data, + serial, + btn, + cnt, + instance->preset); + ret = true; + } + return ret; +} + +bool subghz_txrx_gen_secplus_v1_protocol( + SubGhzTxRx* instance, + const char* name_preset, + uint32_t frequency) { + furi_assert(instance); + + bool ret = false; + uint32_t serial = (uint32_t)rand(); + while(!subghz_protocol_secplus_v1_check_fixed(serial)) { + serial = (uint32_t)rand(); + } + if(subghz_txrx_gen_data_protocol( + instance, + name_preset, + frequency, + SUBGHZ_PROTOCOL_SECPLUS_V1_NAME, + (uint64_t)serial << 32 | 0xE6000000, + 42)) { + ret = true; + } + return ret; +} \ No newline at end of file diff --git a/applications/main/subghz/helpers/subghz_txrx_create_potocol_key.h b/applications/main/subghz/helpers/subghz_txrx_create_potocol_key.h new file mode 100644 index 00000000000..5eed93034f6 --- /dev/null +++ b/applications/main/subghz/helpers/subghz_txrx_create_potocol_key.h @@ -0,0 +1,96 @@ +#pragma once +#include "subghz_types.h" +#include "subghz_txrx.h" + +/** + * Generate data for protocol + * + * @param instance Pointer to a SubGhzTxRx + * @param preset_name Name of preset + * @param frequency Frequency in Hz + * @param protocol_name Name of protocol + * @param key Key + * @param bit Bit + * @return bool True if success + */ +bool subghz_txrx_gen_data_protocol( + void* context, + const char* preset_name, + uint32_t frequency, + const char* protocol_name, + uint64_t key, + uint32_t bit); + +/** + * Generate data for protocol and te + * + * @param instance Pointer to a SubGhzTxRx + * @param preset_name Name of preset + * @param frequency Frequency in Hz + * @param protocol_name Name of protocol + * @param key Key + * @param bit Bit + * @param te Te + * @return bool True if success + */ +bool subghz_txrx_gen_data_protocol_and_te( + SubGhzTxRx* instance, + const char* preset_name, + uint32_t frequency, + const char* protocol_name, + uint64_t key, + uint32_t bit, + uint32_t te); + +/** + * Generate data Keeloq protocol + * + * @param instance Pointer to a SubGhzTxRx + * @param name_preset Name of preset + * @param frequency Frequency in Hz + * @param name_sysmem Name of Keeloq sysmem + * @param serial Serial number + * @param btn Button + * @param cnt Counter + * @return bool True if success + */ +bool subghz_txrx_gen_keelog_protocol( + SubGhzTxRx* instance, + const char* name_preset, + uint32_t frequency, + const char* name_sysmem, + uint32_t serial, + uint8_t btn, + uint16_t cnt); + +/** + * Generate data SecPlus v2 protocol + * + * @param instance Pointer to a SubGhzTxRx + * @param name_preset Name of preset + * @param frequency Frequency in Hz + * @param serial Serial number + * @param btn Button + * @param cnt Counter + * @return bool True if success + */ +bool subghz_txrx_gen_secplus_v2_protocol( + SubGhzTxRx* instance, + const char* name_preset, + uint32_t frequency, + uint32_t serial, + uint8_t btn, + uint32_t cnt); + +/** + * Generate data SecPlus v1 protocol + * + * @param instance Pointer to a SubGhzTxRx + * @param name_preset Name of preset + * @param frequency Frequency in Hz + * @return bool True if success + */ +bool subghz_txrx_gen_secplus_v1_protocol( + SubGhzTxRx* instance, + const char* name_preset, + uint32_t frequency); \ No newline at end of file diff --git a/applications/main/subghz/helpers/subghz_txrx_i.h b/applications/main/subghz/helpers/subghz_txrx_i.h new file mode 100644 index 00000000000..bd0ad8b7be3 --- /dev/null +++ b/applications/main/subghz/helpers/subghz_txrx_i.h @@ -0,0 +1,27 @@ +#pragma once + +#include "subghz_txrx.h" + +struct SubGhzTxRx { + SubGhzWorker* worker; + + SubGhzEnvironment* environment; + SubGhzReceiver* receiver; + SubGhzTransmitter* transmitter; + SubGhzProtocolDecoderBase* decoder_result; + FlipperFormat* fff_data; + + SubGhzRadioPreset* preset; + SubGhzSetting* setting; + + uint8_t hopper_timeout; + uint8_t hopper_idx_frequency; + bool is_database_loaded; + SubGhzHopperState hopper_state; + + SubGhzTxRxState txrx_state; + SubGhzSpeakerState speaker_state; + + SubGhzTxRxNeedSaveCallback need_save_callback; + void* need_save_context; +}; diff --git a/applications/main/subghz/helpers/subghz_types.h b/applications/main/subghz/helpers/subghz_types.h index 2bd2f6820c2..46bf940f469 100644 --- a/applications/main/subghz/helpers/subghz_types.h +++ b/applications/main/subghz/helpers/subghz_types.h @@ -77,3 +77,10 @@ typedef enum { SubGhzViewIdTestCarrier, SubGhzViewIdTestPacket, } SubGhzViewId; + +/** SubGhz load type file */ +typedef enum { + SubGhzLoadTypeFileNoLoad, + SubGhzLoadTypeFileKey, + SubGhzLoadTypeFileRaw, +} SubGhzLoadTypeFile; diff --git a/applications/main/subghz/scenes/subghz_scene_delete.c b/applications/main/subghz/scenes/subghz_scene_delete.c index 94814b14324..0d14cd23a3f 100644 --- a/applications/main/subghz/scenes/subghz_scene_delete.c +++ b/applications/main/subghz/scenes/subghz_scene_delete.c @@ -19,7 +19,7 @@ void subghz_scene_delete_on_enter(void* context) { modulation_str = furi_string_alloc(); text = furi_string_alloc(); - subghz_get_frequency_modulation(subghz, frequency_str, modulation_str); + subghz_txrx_get_frequency_and_modulation(subghz->txrx, frequency_str, modulation_str); widget_add_string_element( subghz->widget, 78, @@ -37,7 +37,7 @@ void subghz_scene_delete_on_enter(void* context) { AlignTop, FontSecondary, furi_string_get_cstr(modulation_str)); - subghz_protocol_decoder_base_get_string(subghz->txrx->decoder_result, text); + subghz_protocol_decoder_base_get_string(subghz_txrx_get_decoder(subghz->txrx), text); widget_add_string_multiline_element( subghz->widget, 0, 0, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(text)); diff --git a/applications/main/subghz/scenes/subghz_scene_delete_raw.c b/applications/main/subghz/scenes/subghz_scene_delete_raw.c index fa4fc6f6424..8dff442a870 100644 --- a/applications/main/subghz/scenes/subghz_scene_delete_raw.c +++ b/applications/main/subghz/scenes/subghz_scene_delete_raw.c @@ -33,7 +33,7 @@ void subghz_scene_delete_raw_on_enter(void* context) { widget_add_string_element( subghz->widget, 38, 25, AlignLeft, AlignTop, FontSecondary, "RAW signal"); - subghz_get_frequency_modulation(subghz, frequency_str, modulation_str); + subghz_txrx_get_frequency_and_modulation(subghz->txrx, frequency_str, modulation_str); widget_add_string_element( subghz->widget, 35, diff --git a/applications/main/subghz/scenes/subghz_scene_need_saving.c b/applications/main/subghz/scenes/subghz_scene_need_saving.c index e157246aaee..f29f26309c6 100644 --- a/applications/main/subghz/scenes/subghz_scene_need_saving.c +++ b/applications/main/subghz/scenes/subghz_scene_need_saving.c @@ -37,27 +37,23 @@ void subghz_scene_need_saving_on_enter(void* context) { bool subghz_scene_need_saving_on_event(void* context, SceneManagerEvent event) { SubGhz* subghz = context; if(event.type == SceneManagerEventTypeBack) { - subghz->txrx->rx_key_state = SubGhzRxKeyStateBack; + subghz_rx_key_state_set(subghz, SubGhzRxKeyStateBack); scene_manager_previous_scene(subghz->scene_manager); return true; } else if(event.type == SceneManagerEventTypeCustom) { if(event.event == SubGhzCustomEventSceneStay) { - subghz->txrx->rx_key_state = SubGhzRxKeyStateBack; + subghz_rx_key_state_set(subghz, SubGhzRxKeyStateBack); scene_manager_previous_scene(subghz->scene_manager); return true; } else if(event.event == SubGhzCustomEventSceneExit) { - if(subghz->txrx->rx_key_state == SubGhzRxKeyStateExit) { - subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE; - subghz_preset_init( - subghz, - "AM650", - subghz_setting_get_default_frequency(subghz->setting), - NULL, - 0); + SubGhzRxKeyState state = subghz_rx_key_state_get(subghz); + subghz_rx_key_state_set(subghz, SubGhzRxKeyStateIDLE); + + if(state == SubGhzRxKeyStateExit) { + subghz_set_default_preset(subghz); scene_manager_search_and_switch_to_previous_scene( subghz->scene_manager, SubGhzSceneStart); } else { - subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE; scene_manager_previous_scene(subghz->scene_manager); } diff --git a/applications/main/subghz/scenes/subghz_scene_read_raw.c b/applications/main/subghz/scenes/subghz_scene_read_raw.c index 09440b32bb7..6e576a86183 100644 --- a/applications/main/subghz/scenes/subghz_scene_read_raw.c +++ b/applications/main/subghz/scenes/subghz_scene_read_raw.c @@ -3,11 +3,9 @@ #include #include #include -#include #define RAW_FILE_NAME "Raw_signal_" #define TAG "SubGhzSceneReadRAW" -#define RAW_THRESHOLD_RSSI_LOW_COUNT 10 bool subghz_scene_read_raw_update_filename(SubGhz* subghz) { bool ret = false; @@ -15,12 +13,13 @@ bool subghz_scene_read_raw_update_filename(SubGhz* subghz) { FuriString* temp_str; temp_str = furi_string_alloc(); do { - if(!flipper_format_rewind(subghz->txrx->fff_data)) { + FlipperFormat* fff_data = subghz_txrx_get_fff_data(subghz->txrx); + if(!flipper_format_rewind(fff_data)) { FURI_LOG_E(TAG, "Rewind error"); break; } - if(!flipper_format_read_string(subghz->txrx->fff_data, "File_name", temp_str)) { + if(!flipper_format_read_string(fff_data, "File_name", temp_str)) { FURI_LOG_E(TAG, "Missing File_name"); break; } @@ -38,13 +37,10 @@ static void subghz_scene_read_raw_update_statusbar(void* context) { furi_assert(context); SubGhz* subghz = context; - FuriString* frequency_str; - FuriString* modulation_str; + FuriString* frequency_str = furi_string_alloc(); + FuriString* modulation_str = furi_string_alloc(); - frequency_str = furi_string_alloc(); - modulation_str = furi_string_alloc(); - - subghz_get_frequency_modulation(subghz, frequency_str, modulation_str); + subghz_txrx_get_frequency_and_modulation(subghz->txrx, frequency_str, modulation_str); subghz_read_raw_add_data_statusbar( subghz->subghz_read_raw, furi_string_get_cstr(frequency_str), @@ -69,13 +65,13 @@ void subghz_scene_read_raw_callback_end_tx(void* context) { void subghz_scene_read_raw_on_enter(void* context) { SubGhz* subghz = context; - FuriString* file_name; - file_name = furi_string_alloc(); + FuriString* file_name = furi_string_alloc(); - switch(subghz->txrx->rx_key_state) { + float threshold_rssi = subghz_threshold_rssi_get(subghz->threshold_rssi); + switch(subghz_rx_key_state_get(subghz)) { case SubGhzRxKeyStateBack: subghz_read_raw_set_status( - subghz->subghz_read_raw, SubGhzReadRAWStatusIDLE, "", subghz->txrx->raw_threshold_rssi); + subghz->subghz_read_raw, SubGhzReadRAWStatusIDLE, "", threshold_rssi); break; case SubGhzRxKeyStateRAWLoad: path_extract_filename(subghz->file_path, file_name, true); @@ -83,8 +79,7 @@ void subghz_scene_read_raw_on_enter(void* context) { subghz->subghz_read_raw, SubGhzReadRAWStatusLoadKeyTX, furi_string_get_cstr(file_name), - subghz->txrx->raw_threshold_rssi); - subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE; + threshold_rssi); break; case SubGhzRxKeyStateRAWSave: path_extract_filename(subghz->file_path, file_name, true); @@ -92,66 +87,51 @@ void subghz_scene_read_raw_on_enter(void* context) { subghz->subghz_read_raw, SubGhzReadRAWStatusSaveKey, furi_string_get_cstr(file_name), - subghz->txrx->raw_threshold_rssi); - subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE; + threshold_rssi); break; default: subghz_read_raw_set_status( - subghz->subghz_read_raw, - SubGhzReadRAWStatusStart, - "", - subghz->txrx->raw_threshold_rssi); - subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE; + subghz->subghz_read_raw, SubGhzReadRAWStatusStart, "", threshold_rssi); break; } + + if(subghz_rx_key_state_get(subghz) != SubGhzRxKeyStateBack) { + subghz_rx_key_state_set(subghz, SubGhzRxKeyStateIDLE); + } furi_string_free(file_name); subghz_scene_read_raw_update_statusbar(subghz); //set callback view raw subghz_read_raw_set_callback(subghz->subghz_read_raw, subghz_scene_read_raw_callback, subghz); - subghz->txrx->decoder_result = subghz_receiver_search_decoder_base_by_name( - subghz->txrx->receiver, SUBGHZ_PROTOCOL_RAW_NAME); - furi_assert(subghz->txrx->decoder_result); + furi_check(subghz_txrx_load_decoder_by_name_protocol(subghz->txrx, SUBGHZ_PROTOCOL_RAW_NAME)); //set filter RAW feed - subghz_receiver_set_filter(subghz->txrx->receiver, SubGhzProtocolFlag_RAW); + subghz_txrx_receiver_set_filter(subghz->txrx, SubGhzProtocolFlag_RAW); view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdReadRAW); } bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { SubGhz* subghz = context; bool consumed = false; + SubGhzProtocolDecoderRAW* decoder_raw = + (SubGhzProtocolDecoderRAW*)subghz_txrx_get_decoder(subghz->txrx); if(event.type == SceneManagerEventTypeCustom) { switch(event.event) { case SubGhzCustomEventViewReadRAWBack: - //Stop TX - if(subghz->txrx->txrx_state == SubGhzTxRxStateTx) { - subghz_tx_stop(subghz); - subghz_sleep(subghz); - } - //Stop RX - if(subghz->txrx->txrx_state == SubGhzTxRxStateRx) { - subghz_rx_end(subghz); - subghz_sleep(subghz); - }; + + subghz_txrx_stop(subghz->txrx); //Stop save file - subghz_protocol_raw_save_to_file_stop( - (SubGhzProtocolDecoderRAW*)subghz->txrx->decoder_result); + subghz_protocol_raw_save_to_file_stop(decoder_raw); subghz->state_notifications = SubGhzNotificationStateIDLE; //needed save? - if((subghz->txrx->rx_key_state == SubGhzRxKeyStateAddKey) || - (subghz->txrx->rx_key_state == SubGhzRxKeyStateBack)) { - subghz->txrx->rx_key_state = SubGhzRxKeyStateExit; + if((subghz_rx_key_state_get(subghz) == SubGhzRxKeyStateAddKey) || + (subghz_rx_key_state_get(subghz) == SubGhzRxKeyStateBack)) { + subghz_rx_key_state_set(subghz, SubGhzRxKeyStateExit); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneNeedSaving); } else { //Restore default setting - subghz_preset_init( - subghz, - "AM650", - subghz_setting_get_default_frequency(subghz->setting), - NULL, - 0); + subghz_set_default_preset(subghz); if(!scene_manager_search_and_switch_to_previous_scene( subghz->scene_manager, SubGhzSceneSaved)) { if(!scene_manager_search_and_switch_to_previous_scene( @@ -165,16 +145,7 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { break; case SubGhzCustomEventViewReadRAWTXRXStop: - //Stop TX - if(subghz->txrx->txrx_state == SubGhzTxRxStateTx) { - subghz_tx_stop(subghz); - subghz_sleep(subghz); - } - //Stop RX - if(subghz->txrx->txrx_state == SubGhzTxRxStateRx) { - subghz_rx_end(subghz); - subghz_sleep(subghz); - }; + subghz_txrx_stop(subghz->txrx); subghz->state_notifications = SubGhzNotificationStateIDLE; consumed = true; break; @@ -187,13 +158,13 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { break; case SubGhzCustomEventViewReadRAWErase: - if(subghz->txrx->rx_key_state == SubGhzRxKeyStateAddKey) { + if(subghz_rx_key_state_get(subghz) == SubGhzRxKeyStateAddKey) { if(subghz_scene_read_raw_update_filename(subghz)) { furi_string_set(subghz->file_path_tmp, subghz->file_path); subghz_delete_file(subghz); } } - subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE; + subghz_rx_key_state_set(subghz, SubGhzRxKeyStateIDLE); notification_message(subghz->notifications, &sequence_reset_rgb); consumed = true; break; @@ -203,7 +174,7 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { if(subghz_scene_read_raw_update_filename(subghz)) { scene_manager_set_scene_state( subghz->scene_manager, SubGhzSceneReadRAW, SubGhzCustomEventManagerSet); - subghz->txrx->rx_key_state = SubGhzRxKeyStateRAWLoad; + subghz_rx_key_state_set(subghz, SubGhzRxKeyStateRAWLoad); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneMoreRAW); consumed = true; } else { @@ -223,33 +194,22 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { if(subghz_file_available(subghz) && subghz_scene_read_raw_update_filename(subghz)) { //start send subghz->state_notifications = SubGhzNotificationStateIDLE; - if(subghz->txrx->txrx_state == SubGhzTxRxStateRx) { - subghz_rx_end(subghz); - } - if((subghz->txrx->txrx_state == SubGhzTxRxStateIDLE) || - (subghz->txrx->txrx_state == SubGhzTxRxStateSleep)) { - if(!subghz_tx_start(subghz, subghz->txrx->fff_data)) { - subghz->txrx->rx_key_state = SubGhzRxKeyStateBack; - subghz_read_raw_set_status( - subghz->subghz_read_raw, - SubGhzReadRAWStatusIDLE, - "", - subghz->txrx->raw_threshold_rssi); - } else { - if(scene_manager_has_previous_scene( - subghz->scene_manager, SubGhzSceneSaved) || - !scene_manager_has_previous_scene( - subghz->scene_manager, SubGhzSceneStart)) { - DOLPHIN_DEED(DolphinDeedSubGhzSend); - } - // set callback end tx - subghz_protocol_raw_file_encoder_worker_set_callback_end( - (SubGhzProtocolEncoderRAW*)subghz_transmitter_get_protocol_instance( - subghz->txrx->transmitter), - subghz_scene_read_raw_callback_end_tx, - subghz); - subghz->state_notifications = SubGhzNotificationStateTx; + if(!subghz_tx_start(subghz, subghz_txrx_get_fff_data(subghz->txrx))) { + subghz_rx_key_state_set(subghz, SubGhzRxKeyStateBack); + subghz_read_raw_set_status( + subghz->subghz_read_raw, + SubGhzReadRAWStatusIDLE, + "", + subghz_threshold_rssi_get(subghz->threshold_rssi)); + } else { + if(scene_manager_has_previous_scene(subghz->scene_manager, SubGhzSceneSaved) || + !scene_manager_has_previous_scene(subghz->scene_manager, SubGhzSceneStart)) { + DOLPHIN_DEED(DolphinDeedSubGhzSend); } + // set callback end tx + subghz_txrx_set_raw_file_encoder_worker_callback_end( + subghz->txrx, subghz_scene_read_raw_callback_end_tx, subghz); + subghz->state_notifications = SubGhzNotificationStateTx; } } else { if(!scene_manager_search_and_switch_to_previous_scene( @@ -263,33 +223,22 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { case SubGhzCustomEventViewReadRAWSendStop: subghz->state_notifications = SubGhzNotificationStateIDLE; - if(subghz->txrx->txrx_state == SubGhzTxRxStateTx) { - subghz_speaker_unmute(subghz); - subghz_tx_stop(subghz); - subghz_sleep(subghz); - } + subghz_txrx_stop(subghz->txrx); subghz_read_raw_stop_send(subghz->subghz_read_raw); consumed = true; break; case SubGhzCustomEventViewReadRAWIDLE: - if(subghz->txrx->txrx_state == SubGhzTxRxStateRx) { - subghz_rx_end(subghz); - subghz_sleep(subghz); - }; - - size_t spl_count = subghz_protocol_raw_get_sample_write( - (SubGhzProtocolDecoderRAW*)subghz->txrx->decoder_result); + subghz_txrx_stop(subghz->txrx); + size_t spl_count = subghz_protocol_raw_get_sample_write(decoder_raw); - subghz_protocol_raw_save_to_file_stop( - (SubGhzProtocolDecoderRAW*)subghz->txrx->decoder_result); + subghz_protocol_raw_save_to_file_stop(decoder_raw); - FuriString* temp_str; - temp_str = furi_string_alloc(); + FuriString* temp_str = furi_string_alloc(); furi_string_printf( temp_str, "%s/%s%s", SUBGHZ_RAW_FOLDER, RAW_FILE_NAME, SUBGHZ_APP_EXTENSION); subghz_protocol_raw_gen_fff_data( - subghz->txrx->fff_data, furi_string_get_cstr(temp_str)); + subghz_txrx_get_fff_data(subghz->txrx), furi_string_get_cstr(temp_str)); furi_string_free(temp_str); if(spl_count > 0) { @@ -299,32 +248,21 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { } subghz->state_notifications = SubGhzNotificationStateIDLE; - subghz->txrx->rx_key_state = SubGhzRxKeyStateAddKey; + subghz_rx_key_state_set(subghz, SubGhzRxKeyStateAddKey); consumed = true; break; case SubGhzCustomEventViewReadRAWREC: - if(subghz->txrx->rx_key_state != SubGhzRxKeyStateIDLE) { + if(subghz_rx_key_state_get(subghz) != SubGhzRxKeyStateIDLE) { scene_manager_next_scene(subghz->scene_manager, SubGhzSceneNeedSaving); } else { - subghz->txrx->raw_threshold_rssi_low_count = RAW_THRESHOLD_RSSI_LOW_COUNT; - if(subghz_protocol_raw_save_to_file_init( - (SubGhzProtocolDecoderRAW*)subghz->txrx->decoder_result, - RAW_FILE_NAME, - subghz->txrx->preset)) { + SubGhzRadioPreset preset = subghz_txrx_get_preset(subghz->txrx); + if(subghz_protocol_raw_save_to_file_init(decoder_raw, RAW_FILE_NAME, &preset)) { DOLPHIN_DEED(DolphinDeedSubGhzRawRec); - if((subghz->txrx->txrx_state == SubGhzTxRxStateIDLE) || - (subghz->txrx->txrx_state == SubGhzTxRxStateSleep)) { - subghz_begin( - subghz, - subghz_setting_get_preset_data_by_name( - subghz->setting, - furi_string_get_cstr(subghz->txrx->preset->name))); - subghz_rx(subghz, subghz->txrx->preset->frequency); - } + subghz_txrx_rx_start(subghz->txrx); subghz->state_notifications = SubGhzNotificationStateRx; - subghz->txrx->rx_key_state = SubGhzRxKeyStateAddKey; + subghz_rx_key_state_set(subghz, SubGhzRxKeyStateAddKey); } else { furi_string_set(subghz->error_str, "Function requires\nan SD card."); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError); @@ -337,7 +275,7 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { if(subghz_file_available(subghz) && subghz_scene_read_raw_update_filename(subghz)) { scene_manager_set_scene_state( subghz->scene_manager, SubGhzSceneReadRAW, SubGhzCustomEventManagerSetRAW); - subghz->txrx->rx_key_state = SubGhzRxKeyStateBack; + subghz_rx_key_state_set(subghz, SubGhzRxKeyStateBack); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveName); } else { if(!scene_manager_search_and_switch_to_previous_scene( @@ -356,41 +294,15 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { switch(subghz->state_notifications) { case SubGhzNotificationStateRx: notification_message(subghz->notifications, &sequence_blink_cyan_10); - subghz_read_raw_update_sample_write( - subghz->subghz_read_raw, - subghz_protocol_raw_get_sample_write( - (SubGhzProtocolDecoderRAW*)subghz->txrx->decoder_result)); - - float rssi = furi_hal_subghz_get_rssi(); - if(float_is_equal(subghz->txrx->raw_threshold_rssi, SUBGHZ_RAW_TRESHOLD_MIN)) { - subghz_read_raw_add_data_rssi(subghz->subghz_read_raw, rssi, true); - subghz_protocol_raw_save_to_file_pause( - (SubGhzProtocolDecoderRAW*)subghz->txrx->decoder_result, false); - } else { - if(rssi < subghz->txrx->raw_threshold_rssi) { - subghz->txrx->raw_threshold_rssi_low_count++; - if(subghz->txrx->raw_threshold_rssi_low_count > RAW_THRESHOLD_RSSI_LOW_COUNT) { - subghz->txrx->raw_threshold_rssi_low_count = RAW_THRESHOLD_RSSI_LOW_COUNT; - } - subghz_read_raw_add_data_rssi(subghz->subghz_read_raw, rssi, false); - } else { - subghz->txrx->raw_threshold_rssi_low_count = 0; - } - - if(subghz->txrx->raw_threshold_rssi_low_count == RAW_THRESHOLD_RSSI_LOW_COUNT) { - subghz_read_raw_add_data_rssi(subghz->subghz_read_raw, rssi, false); - subghz_protocol_raw_save_to_file_pause( - (SubGhzProtocolDecoderRAW*)subghz->txrx->decoder_result, true); - subghz_speaker_mute(subghz); - } else { - subghz_read_raw_add_data_rssi(subghz->subghz_read_raw, rssi, true); - subghz_protocol_raw_save_to_file_pause( - (SubGhzProtocolDecoderRAW*)subghz->txrx->decoder_result, false); - subghz_speaker_unmute(subghz); - } - } + subghz_read_raw_update_sample_write( + subghz->subghz_read_raw, subghz_protocol_raw_get_sample_write(decoder_raw)); + SubGhzThresholdRssiData ret_rssi = + subghz_threshold_get_rssi_data(subghz->threshold_rssi); + subghz_read_raw_add_data_rssi( + subghz->subghz_read_raw, ret_rssi.rssi, ret_rssi.is_above); + subghz_protocol_raw_save_to_file_pause(decoder_raw, !ret_rssi.is_above); break; case SubGhzNotificationStateTx: notification_message(subghz->notifications, &sequence_blink_magenta_10); @@ -407,13 +319,10 @@ void subghz_scene_read_raw_on_exit(void* context) { SubGhz* subghz = context; //Stop CC1101 - if(subghz->txrx->txrx_state == SubGhzTxRxStateRx) { - subghz_rx_end(subghz); - subghz_sleep(subghz); - }; + subghz_txrx_stop(subghz->txrx); subghz->state_notifications = SubGhzNotificationStateIDLE; notification_message(subghz->notifications, &sequence_reset_rgb); //filter restoration - subghz_receiver_set_filter(subghz->txrx->receiver, subghz->txrx->filter); + subghz_txrx_receiver_set_filter(subghz->txrx, subghz->filter); } diff --git a/applications/main/subghz/scenes/subghz_scene_receiver.c b/applications/main/subghz/scenes/subghz_scene_receiver.c index 93c369092e9..dcc22b91cc7 100644 --- a/applications/main/subghz/scenes/subghz_scene_receiver.c +++ b/applications/main/subghz/scenes/subghz_scene_receiver.c @@ -35,16 +35,12 @@ static const NotificationSequence subghs_sequence_rx_locked = { static void subghz_scene_receiver_update_statusbar(void* context) { SubGhz* subghz = context; - FuriString* history_stat_str; - history_stat_str = furi_string_alloc(); - if(!subghz_history_get_text_space_left(subghz->txrx->history, history_stat_str)) { - FuriString* frequency_str; - FuriString* modulation_str; + FuriString* history_stat_str = furi_string_alloc(); + if(!subghz_history_get_text_space_left(subghz->history, history_stat_str)) { + FuriString* frequency_str = furi_string_alloc(); + FuriString* modulation_str = furi_string_alloc(); - frequency_str = furi_string_alloc(); - modulation_str = furi_string_alloc(); - - subghz_get_frequency_modulation(subghz, frequency_str, modulation_str); + subghz_txrx_get_frequency_and_modulation(subghz->txrx, frequency_str, modulation_str); subghz_view_receiver_add_data_statusbar( subghz->subghz_receiver, @@ -74,80 +70,68 @@ static void subghz_scene_add_to_history_callback( void* context) { furi_assert(context); SubGhz* subghz = context; - FuriString* str_buff; - str_buff = furi_string_alloc(); + SubGhzHistory* history = subghz->history; + FuriString* str_buff = furi_string_alloc(); + + SubGhzRadioPreset preset = subghz_txrx_get_preset(subghz->txrx); - if(subghz_history_add_to_history(subghz->txrx->history, decoder_base, subghz->txrx->preset)) { + if(subghz_history_add_to_history(history, decoder_base, &preset)) { furi_string_reset(str_buff); subghz->state_notifications = SubGhzNotificationStateRxDone; - - subghz_history_get_text_item_menu( - subghz->txrx->history, str_buff, subghz_history_get_item(subghz->txrx->history) - 1); + uint16_t item_history = subghz_history_get_item(history); + subghz_history_get_text_item_menu(history, str_buff, item_history - 1); subghz_view_receiver_add_item_to_menu( subghz->subghz_receiver, furi_string_get_cstr(str_buff), - subghz_history_get_type_protocol( - subghz->txrx->history, subghz_history_get_item(subghz->txrx->history) - 1)); + subghz_history_get_type_protocol(history, item_history - 1)); subghz_scene_receiver_update_statusbar(subghz); } subghz_receiver_reset(receiver); furi_string_free(str_buff); - subghz->txrx->rx_key_state = SubGhzRxKeyStateAddKey; + subghz_rx_key_state_set(subghz, SubGhzRxKeyStateAddKey); } void subghz_scene_receiver_on_enter(void* context) { SubGhz* subghz = context; + SubGhzHistory* history = subghz->history; FuriString* str_buff; str_buff = furi_string_alloc(); - if(subghz->txrx->rx_key_state == SubGhzRxKeyStateIDLE) { - subghz_preset_init( - subghz, "AM650", subghz_setting_get_default_frequency(subghz->setting), NULL, 0); - subghz_history_reset(subghz->txrx->history); - subghz->txrx->rx_key_state = SubGhzRxKeyStateStart; + if(subghz_rx_key_state_get(subghz) == SubGhzRxKeyStateIDLE) { + subghz_set_default_preset(subghz); + subghz_history_reset(history); + subghz_rx_key_state_set(subghz, SubGhzRxKeyStateStart); } - subghz_view_receiver_set_lock(subghz->subghz_receiver, subghz->lock); + subghz_view_receiver_set_lock(subghz->subghz_receiver, subghz_is_locked(subghz)); //Load history to receiver subghz_view_receiver_exit(subghz->subghz_receiver); - for(uint8_t i = 0; i < subghz_history_get_item(subghz->txrx->history); i++) { + for(uint8_t i = 0; i < subghz_history_get_item(history); i++) { furi_string_reset(str_buff); - subghz_history_get_text_item_menu(subghz->txrx->history, str_buff, i); + subghz_history_get_text_item_menu(history, str_buff, i); subghz_view_receiver_add_item_to_menu( subghz->subghz_receiver, furi_string_get_cstr(str_buff), - subghz_history_get_type_protocol(subghz->txrx->history, i)); - subghz->txrx->rx_key_state = SubGhzRxKeyStateAddKey; + subghz_history_get_type_protocol(history, i)); + subghz_rx_key_state_set(subghz, SubGhzRxKeyStateAddKey); } furi_string_free(str_buff); subghz_scene_receiver_update_statusbar(subghz); subghz_view_receiver_set_callback( subghz->subghz_receiver, subghz_scene_receiver_callback, subghz); - subghz_receiver_set_rx_callback( - subghz->txrx->receiver, subghz_scene_add_to_history_callback, subghz); + subghz_txrx_set_rx_calback(subghz->txrx, subghz_scene_add_to_history_callback, subghz); subghz->state_notifications = SubGhzNotificationStateRx; - if(subghz->txrx->txrx_state == SubGhzTxRxStateRx) { - subghz_rx_end(subghz); - }; - if((subghz->txrx->txrx_state == SubGhzTxRxStateIDLE) || - (subghz->txrx->txrx_state == SubGhzTxRxStateSleep)) { - subghz_begin( - subghz, - subghz_setting_get_preset_data_by_name( - subghz->setting, furi_string_get_cstr(subghz->txrx->preset->name))); - subghz_rx(subghz, subghz->txrx->preset->frequency); - } - subghz_view_receiver_set_idx_menu(subghz->subghz_receiver, subghz->txrx->idx_menu_chosen); + subghz_txrx_rx_start(subghz->txrx); + subghz_view_receiver_set_idx_menu(subghz->subghz_receiver, subghz->idx_menu_chosen); //to use a universal decoder, we are looking for a link to it - subghz->txrx->decoder_result = subghz_receiver_search_decoder_base_by_name( - subghz->txrx->receiver, SUBGHZ_PROTOCOL_BIN_RAW_NAME); - furi_assert(subghz->txrx->decoder_result); + furi_check( + subghz_txrx_load_decoder_by_name_protocol(subghz->txrx, SUBGHZ_PROTOCOL_BIN_RAW_NAME)); view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdReceiver); } @@ -160,41 +144,31 @@ bool subghz_scene_receiver_on_event(void* context, SceneManagerEvent event) { case SubGhzCustomEventViewReceiverBack: // Stop CC1101 Rx subghz->state_notifications = SubGhzNotificationStateIDLE; - if(subghz->txrx->txrx_state == SubGhzTxRxStateRx) { - subghz_rx_end(subghz); - subghz_sleep(subghz); - }; - subghz->txrx->hopper_state = SubGhzHopperStateOFF; - subghz->txrx->idx_menu_chosen = 0; - subghz_receiver_set_rx_callback(subghz->txrx->receiver, NULL, subghz); - - if(subghz->txrx->rx_key_state == SubGhzRxKeyStateAddKey) { - subghz->txrx->rx_key_state = SubGhzRxKeyStateExit; + subghz_txrx_stop(subghz->txrx); + subghz_txrx_hopper_set_state(subghz->txrx, SubGhzHopperStateOFF); + subghz->idx_menu_chosen = 0; + subghz_txrx_set_rx_calback(subghz->txrx, NULL, subghz); + + if(subghz_rx_key_state_get(subghz) == SubGhzRxKeyStateAddKey) { + subghz_rx_key_state_set(subghz, SubGhzRxKeyStateExit); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneNeedSaving); } else { - subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE; - subghz_preset_init( - subghz, - "AM650", - subghz_setting_get_default_frequency(subghz->setting), - NULL, - 0); + subghz_rx_key_state_set(subghz, SubGhzRxKeyStateIDLE); + subghz_set_default_preset(subghz); scene_manager_search_and_switch_to_previous_scene( subghz->scene_manager, SubGhzSceneStart); } consumed = true; break; case SubGhzCustomEventViewReceiverOK: - subghz->txrx->idx_menu_chosen = - subghz_view_receiver_get_idx_menu(subghz->subghz_receiver); + subghz->idx_menu_chosen = subghz_view_receiver_get_idx_menu(subghz->subghz_receiver); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReceiverInfo); DOLPHIN_DEED(DolphinDeedSubGhzReceiverInfo); consumed = true; break; case SubGhzCustomEventViewReceiverConfig: subghz->state_notifications = SubGhzNotificationStateIDLE; - subghz->txrx->idx_menu_chosen = - subghz_view_receiver_get_idx_menu(subghz->subghz_receiver); + subghz->idx_menu_chosen = subghz_view_receiver_get_idx_menu(subghz->subghz_receiver); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReceiverConfig); consumed = true; break; @@ -203,30 +177,30 @@ bool subghz_scene_receiver_on_event(void* context, SceneManagerEvent event) { consumed = true; break; case SubGhzCustomEventViewReceiverUnlock: - subghz->lock = SubGhzLockOff; + subghz_unlock(subghz); consumed = true; break; default: break; } } else if(event.type == SceneManagerEventTypeTick) { - if(subghz->txrx->hopper_state != SubGhzHopperStateOFF) { - subghz_hopper_update(subghz); + if(subghz_txrx_hopper_get_state(subghz->txrx) != SubGhzHopperStateOFF) { + subghz_txrx_hopper_update(subghz->txrx); subghz_scene_receiver_update_statusbar(subghz); } - //get RSSI - float rssi = furi_hal_subghz_get_rssi(); - subghz_receiver_rssi(subghz->subghz_receiver, rssi); + SubGhzThresholdRssiData ret_rssi = subghz_threshold_get_rssi_data(subghz->threshold_rssi); + + subghz_receiver_rssi(subghz->subghz_receiver, ret_rssi.rssi); subghz_protocol_decoder_bin_raw_data_input_rssi( - (SubGhzProtocolDecoderBinRAW*)subghz->txrx->decoder_result, rssi); + (SubGhzProtocolDecoderBinRAW*)subghz_txrx_get_decoder(subghz->txrx), ret_rssi.rssi); switch(subghz->state_notifications) { case SubGhzNotificationStateRx: notification_message(subghz->notifications, &sequence_blink_cyan_10); break; case SubGhzNotificationStateRxDone: - if(subghz->lock != SubGhzLockOn) { + if(!subghz_is_locked(subghz)) { notification_message(subghz->notifications, &subghs_sequence_rx); } else { notification_message(subghz->notifications, &subghs_sequence_rx_locked); diff --git a/applications/main/subghz/scenes/subghz_scene_receiver_config.c b/applications/main/subghz/scenes/subghz_scene_receiver_config.c index 895e4334285..55a8f6b44a5 100644 --- a/applications/main/subghz/scenes/subghz_scene_receiver_config.c +++ b/applications/main/subghz/scenes/subghz_scene_receiver_config.c @@ -72,13 +72,15 @@ const uint32_t bin_raw_value[BIN_RAW_COUNT] = { uint8_t subghz_scene_receiver_config_next_frequency(const uint32_t value, void* context) { furi_assert(context); SubGhz* subghz = context; + SubGhzSetting* setting = subghz_txrx_get_setting(subghz->txrx); + uint8_t index = 0; - for(uint8_t i = 0; i < subghz_setting_get_frequency_count(subghz->setting); i++) { - if(value == subghz_setting_get_frequency(subghz->setting, i)) { + for(uint8_t i = 0; i < subghz_setting_get_frequency_count(setting); i++) { + if(value == subghz_setting_get_frequency(setting, i)) { index = i; break; } else { - index = subghz_setting_get_frequency_default_index(subghz->setting); + index = subghz_setting_get_frequency_default_index(setting); } } return index; @@ -87,13 +89,15 @@ uint8_t subghz_scene_receiver_config_next_frequency(const uint32_t value, void* uint8_t subghz_scene_receiver_config_next_preset(const char* preset_name, void* context) { furi_assert(context); SubGhz* subghz = context; + SubGhzSetting* setting = subghz_txrx_get_setting(subghz->txrx); + uint8_t index = 0; - for(uint8_t i = 0; i < subghz_setting_get_preset_count(subghz->setting); i++) { - if(!strcmp(subghz_setting_get_preset_name(subghz->setting, i), preset_name)) { + for(uint8_t i = 0; i < subghz_setting_get_preset_count(setting); i++) { + if(!strcmp(subghz_setting_get_preset_name(setting, i), preset_name)) { index = i; break; } else { - // index = subghz_setting_get_frequency_default_index(subghz->setting); + // index = subghz_setting_get_frequency_default_index(subghz_txrx_get_setting(subghz->txrx)); } } return index; @@ -122,70 +126,84 @@ uint8_t subghz_scene_receiver_config_hopper_value_index( static void subghz_scene_receiver_config_set_frequency(VariableItem* item) { SubGhz* subghz = variable_item_get_context(item); uint8_t index = variable_item_get_current_value_index(item); + SubGhzSetting* setting = subghz_txrx_get_setting(subghz->txrx); - if(subghz->txrx->hopper_state == SubGhzHopperStateOFF) { + if(subghz_txrx_hopper_get_state(subghz->txrx) == SubGhzHopperStateOFF) { char text_buf[10] = {0}; + uint32_t frequency = subghz_setting_get_frequency(setting, index); + SubGhzRadioPreset preset = subghz_txrx_get_preset(subghz->txrx); + snprintf( text_buf, sizeof(text_buf), "%lu.%02lu", - subghz_setting_get_frequency(subghz->setting, index) / 1000000, - (subghz_setting_get_frequency(subghz->setting, index) % 1000000) / 10000); + frequency / 1000000, + (frequency % 1000000) / 10000); variable_item_set_current_value_text(item, text_buf); - subghz->txrx->preset->frequency = subghz_setting_get_frequency(subghz->setting, index); + subghz_txrx_set_preset( + subghz->txrx, + furi_string_get_cstr(preset.name), + frequency, + preset.data, + preset.data_size); } else { variable_item_set_current_value_index( - item, subghz_setting_get_frequency_default_index(subghz->setting)); + item, subghz_setting_get_frequency_default_index(setting)); } } static void subghz_scene_receiver_config_set_preset(VariableItem* item) { SubGhz* subghz = variable_item_get_context(item); uint8_t index = variable_item_get_current_value_index(item); - variable_item_set_current_value_text( - item, subghz_setting_get_preset_name(subghz->setting, index)); - subghz_preset_init( - subghz, - subghz_setting_get_preset_name(subghz->setting, index), - subghz->txrx->preset->frequency, - subghz_setting_get_preset_data(subghz->setting, index), - subghz_setting_get_preset_data_size(subghz->setting, index)); + SubGhzSetting* setting = subghz_txrx_get_setting(subghz->txrx); + + variable_item_set_current_value_text(item, subghz_setting_get_preset_name(setting, index)); + + SubGhzRadioPreset preset = subghz_txrx_get_preset(subghz->txrx); + subghz_txrx_set_preset( + subghz->txrx, + subghz_setting_get_preset_name(setting, index), + preset.frequency, + subghz_setting_get_preset_data(setting, index), + subghz_setting_get_preset_data_size(setting, index)); } static void subghz_scene_receiver_config_set_hopping_running(VariableItem* item) { SubGhz* subghz = variable_item_get_context(item); uint8_t index = variable_item_get_current_value_index(item); + SubGhzSetting* setting = subghz_txrx_get_setting(subghz->txrx); + VariableItem* frequency_item = (VariableItem*)scene_manager_get_scene_state( + subghz->scene_manager, SubGhzSceneReceiverConfig); variable_item_set_current_value_text(item, hopping_text[index]); if(hopping_value[index] == SubGhzHopperStateOFF) { char text_buf[10] = {0}; + uint32_t frequency = subghz_setting_get_default_frequency(setting); + SubGhzRadioPreset preset = subghz_txrx_get_preset(subghz->txrx); + snprintf( text_buf, sizeof(text_buf), "%lu.%02lu", - subghz_setting_get_default_frequency(subghz->setting) / 1000000, - (subghz_setting_get_default_frequency(subghz->setting) % 1000000) / 10000); - variable_item_set_current_value_text( - (VariableItem*)scene_manager_get_scene_state( - subghz->scene_manager, SubGhzSceneReceiverConfig), - text_buf); - subghz->txrx->preset->frequency = subghz_setting_get_default_frequency(subghz->setting); + frequency / 1000000, + (frequency % 1000000) / 10000); + variable_item_set_current_value_text(frequency_item, text_buf); + + subghz_txrx_set_preset( + subghz->txrx, + furi_string_get_cstr(preset.name), + frequency, + preset.data, + preset.data_size); variable_item_set_current_value_index( - (VariableItem*)scene_manager_get_scene_state( - subghz->scene_manager, SubGhzSceneReceiverConfig), - subghz_setting_get_frequency_default_index(subghz->setting)); + frequency_item, subghz_setting_get_frequency_default_index(setting)); } else { - variable_item_set_current_value_text( - (VariableItem*)scene_manager_get_scene_state( - subghz->scene_manager, SubGhzSceneReceiverConfig), - " -----"); + variable_item_set_current_value_text(frequency_item, " -----"); variable_item_set_current_value_index( - (VariableItem*)scene_manager_get_scene_state( - subghz->scene_manager, SubGhzSceneReceiverConfig), - subghz_setting_get_frequency_default_index(subghz->setting)); + frequency_item, subghz_setting_get_frequency_default_index(setting)); } - subghz->txrx->hopper_state = hopping_value[index]; + subghz_txrx_hopper_set_state(subghz->txrx, hopping_value[index]); } static void subghz_scene_receiver_config_set_speaker(VariableItem* item) { @@ -193,7 +211,7 @@ static void subghz_scene_receiver_config_set_speaker(VariableItem* item) { uint8_t index = variable_item_get_current_value_index(item); variable_item_set_current_value_text(item, speaker_text[index]); - subghz->txrx->speaker_state = speaker_value[index]; + subghz_txrx_speaker_set_state(subghz->txrx, speaker_value[index]); } static void subghz_scene_receiver_config_set_bin_raw(VariableItem* item) { @@ -201,8 +219,8 @@ static void subghz_scene_receiver_config_set_bin_raw(VariableItem* item) { uint8_t index = variable_item_get_current_value_index(item); variable_item_set_current_value_text(item, bin_raw_text[index]); - subghz->txrx->filter = bin_raw_value[index]; - subghz_receiver_set_filter(subghz->txrx->receiver, subghz->txrx->filter); + subghz->filter = bin_raw_value[index]; + subghz_txrx_receiver_set_filter(subghz->txrx, subghz->filter); } static void subghz_scene_receiver_config_set_raw_threshold_rssi(VariableItem* item) { @@ -210,7 +228,7 @@ static void subghz_scene_receiver_config_set_raw_threshold_rssi(VariableItem* it uint8_t index = variable_item_get_current_value_index(item); variable_item_set_current_value_text(item, raw_theshold_rssi_text[index]); - subghz->txrx->raw_threshold_rssi = raw_theshold_rssi_value[index]; + subghz_threshold_rssi_set(subghz->threshold_rssi, raw_theshold_rssi_value[index]); } static void subghz_scene_receiver_config_var_list_enter_callback(void* context, uint32_t index) { @@ -226,25 +244,27 @@ void subghz_scene_receiver_config_on_enter(void* context) { SubGhz* subghz = context; VariableItem* item; uint8_t value_index; + SubGhzSetting* setting = subghz_txrx_get_setting(subghz->txrx); + SubGhzRadioPreset preset = subghz_txrx_get_preset(subghz->txrx); item = variable_item_list_add( subghz->variable_item_list, "Frequency:", - subghz_setting_get_frequency_count(subghz->setting), + subghz_setting_get_frequency_count(setting), subghz_scene_receiver_config_set_frequency, subghz); - value_index = - subghz_scene_receiver_config_next_frequency(subghz->txrx->preset->frequency, subghz); + value_index = subghz_scene_receiver_config_next_frequency(preset.frequency, subghz); scene_manager_set_scene_state( subghz->scene_manager, SubGhzSceneReceiverConfig, (uint32_t)item); variable_item_set_current_value_index(item, value_index); char text_buf[10] = {0}; + uint32_t frequency = subghz_setting_get_frequency(setting, value_index); snprintf( text_buf, sizeof(text_buf), "%lu.%02lu", - subghz_setting_get_frequency(subghz->setting, value_index) / 1000000, - (subghz_setting_get_frequency(subghz->setting, value_index) % 1000000) / 10000); + frequency / 1000000, + (frequency % 1000000) / 10000); variable_item_set_current_value_text(item, text_buf); if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAW) != @@ -256,7 +276,7 @@ void subghz_scene_receiver_config_on_enter(void* context) { subghz_scene_receiver_config_set_hopping_running, subghz); value_index = subghz_scene_receiver_config_hopper_value_index( - subghz->txrx->hopper_state, hopping_value, HOPPING_COUNT, subghz); + subghz_txrx_hopper_get_state(subghz->txrx), hopping_value, HOPPING_COUNT, subghz); variable_item_set_current_value_index(item, value_index); variable_item_set_current_value_text(item, hopping_text[value_index]); } @@ -264,14 +284,14 @@ void subghz_scene_receiver_config_on_enter(void* context) { item = variable_item_list_add( subghz->variable_item_list, "Modulation:", - subghz_setting_get_preset_count(subghz->setting), + subghz_setting_get_preset_count(setting), subghz_scene_receiver_config_set_preset, subghz); - value_index = subghz_scene_receiver_config_next_preset( - furi_string_get_cstr(subghz->txrx->preset->name), subghz); + value_index = + subghz_scene_receiver_config_next_preset(furi_string_get_cstr(preset.name), subghz); variable_item_set_current_value_index(item, value_index); variable_item_set_current_value_text( - item, subghz_setting_get_preset_name(subghz->setting, value_index)); + item, subghz_setting_get_preset_name(setting, value_index)); if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAW) != SubGhzCustomEventManagerSet) { @@ -281,7 +301,7 @@ void subghz_scene_receiver_config_on_enter(void* context) { BIN_RAW_COUNT, subghz_scene_receiver_config_set_bin_raw, subghz); - value_index = value_index_uint32(subghz->txrx->filter, bin_raw_value, BIN_RAW_COUNT); + value_index = value_index_uint32(subghz->filter, bin_raw_value, BIN_RAW_COUNT); variable_item_set_current_value_index(item, value_index); variable_item_set_current_value_text(item, bin_raw_text[value_index]); } @@ -292,7 +312,8 @@ void subghz_scene_receiver_config_on_enter(void* context) { SPEAKER_COUNT, subghz_scene_receiver_config_set_speaker, subghz); - value_index = value_index_uint32(subghz->txrx->speaker_state, speaker_value, SPEAKER_COUNT); + value_index = value_index_uint32( + subghz_txrx_speaker_get_state(subghz->txrx), speaker_value, SPEAKER_COUNT); variable_item_set_current_value_index(item, value_index); variable_item_set_current_value_text(item, speaker_text[value_index]); @@ -313,7 +334,9 @@ void subghz_scene_receiver_config_on_enter(void* context) { subghz_scene_receiver_config_set_raw_threshold_rssi, subghz); value_index = value_index_float( - subghz->txrx->raw_threshold_rssi, raw_theshold_rssi_value, RAW_THRESHOLD_RSSI_COUNT); + subghz_threshold_rssi_get(subghz->threshold_rssi), + raw_theshold_rssi_value, + RAW_THRESHOLD_RSSI_COUNT); variable_item_set_current_value_index(item, value_index); variable_item_set_current_value_text(item, raw_theshold_rssi_text[value_index]); } @@ -326,7 +349,7 @@ bool subghz_scene_receiver_config_on_event(void* context, SceneManagerEvent even if(event.type == SceneManagerEventTypeCustom) { if(event.event == SubGhzCustomEventSceneSettingLock) { - subghz->lock = SubGhzLockOn; + subghz_lock(subghz); scene_manager_previous_scene(subghz->scene_manager); consumed = true; } diff --git a/applications/main/subghz/scenes/subghz_scene_receiver_info.c b/applications/main/subghz/scenes/subghz_scene_receiver_info.c index 152334ad632..9b57165e763 100644 --- a/applications/main/subghz/scenes/subghz_scene_receiver_info.c +++ b/applications/main/subghz/scenes/subghz_scene_receiver_info.c @@ -19,20 +19,19 @@ void subghz_scene_receiver_info_callback(GuiButtonType result, InputType type, v static bool subghz_scene_receiver_info_update_parser(void* context) { SubGhz* subghz = context; - subghz->txrx->decoder_result = subghz_receiver_search_decoder_base_by_name( - subghz->txrx->receiver, - subghz_history_get_protocol_name(subghz->txrx->history, subghz->txrx->idx_menu_chosen)); - if(subghz->txrx->decoder_result) { + if(subghz_txrx_load_decoder_by_name_protocol( + subghz->txrx, + subghz_history_get_protocol_name(subghz->history, subghz->idx_menu_chosen))) { //todo we are trying to deserialize without checking for errors, since it is assumed that we just received this chignal subghz_protocol_decoder_base_deserialize( - subghz->txrx->decoder_result, - subghz_history_get_raw_data(subghz->txrx->history, subghz->txrx->idx_menu_chosen)); + subghz_txrx_get_decoder(subghz->txrx), + subghz_history_get_raw_data(subghz->history, subghz->idx_menu_chosen)); SubGhzRadioPreset* preset = - subghz_history_get_radio_preset(subghz->txrx->history, subghz->txrx->idx_menu_chosen); - subghz_preset_init( - subghz, + subghz_history_get_radio_preset(subghz->history, subghz->idx_menu_chosen); + subghz_txrx_set_preset( + subghz->txrx, furi_string_get_cstr(preset->name), preset->frequency, preset->data, @@ -47,15 +46,11 @@ void subghz_scene_receiver_info_on_enter(void* context) { SubGhz* subghz = context; if(subghz_scene_receiver_info_update_parser(subghz)) { - FuriString* frequency_str; - FuriString* modulation_str; - FuriString* text; + FuriString* frequency_str = furi_string_alloc(); + FuriString* modulation_str = furi_string_alloc(); + FuriString* text = furi_string_alloc(); - frequency_str = furi_string_alloc(); - modulation_str = furi_string_alloc(); - text = furi_string_alloc(); - - subghz_get_frequency_modulation(subghz, frequency_str, modulation_str); + subghz_txrx_get_frequency_and_modulation(subghz->txrx, frequency_str, modulation_str); widget_add_string_element( subghz->widget, 78, @@ -73,7 +68,7 @@ void subghz_scene_receiver_info_on_enter(void* context) { AlignTop, FontSecondary, furi_string_get_cstr(modulation_str)); - subghz_protocol_decoder_base_get_string(subghz->txrx->decoder_result, text); + subghz_protocol_decoder_base_get_string(subghz_txrx_get_decoder(subghz->txrx), text); widget_add_string_multiline_element( subghz->widget, 0, 0, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(text)); @@ -81,8 +76,7 @@ void subghz_scene_receiver_info_on_enter(void* context) { furi_string_free(modulation_str); furi_string_free(text); - if((subghz->txrx->decoder_result->protocol->flag & SubGhzProtocolFlag_Save) == - SubGhzProtocolFlag_Save) { + if(subghz_txrx_protocol_is_serializable(subghz->txrx)) { widget_add_button_element( subghz->widget, GuiButtonTypeRight, @@ -90,10 +84,7 @@ void subghz_scene_receiver_info_on_enter(void* context) { subghz_scene_receiver_info_callback, subghz); } - if(((subghz->txrx->decoder_result->protocol->flag & SubGhzProtocolFlag_Send) == - SubGhzProtocolFlag_Send) && - subghz->txrx->decoder_result->protocol->encoder->deserialize && - subghz->txrx->decoder_result->protocol->type == SubGhzProtocolTypeStatic) { + if(subghz_txrx_protocol_is_transmittable(subghz->txrx, true)) { widget_add_button_element( subghz->widget, GuiButtonTypeCenter, @@ -114,82 +105,49 @@ bool subghz_scene_receiver_info_on_event(void* context, SceneManagerEvent event) SubGhz* subghz = context; if(event.type == SceneManagerEventTypeCustom) { if(event.event == SubGhzCustomEventSceneReceiverInfoTxStart) { - //CC1101 Stop RX -> Start TX - if(subghz->txrx->hopper_state != SubGhzHopperStateOFF) { - subghz->txrx->hopper_state = SubGhzHopperStatePause; - } - if(subghz->txrx->txrx_state == SubGhzTxRxStateRx) { - subghz_rx_end(subghz); - } if(!subghz_scene_receiver_info_update_parser(subghz)) { return false; } - if(subghz->txrx->txrx_state == SubGhzTxRxStateIDLE || - subghz->txrx->txrx_state == SubGhzTxRxStateSleep) { - if(!subghz_tx_start( - subghz, - subghz_history_get_raw_data( - subghz->txrx->history, subghz->txrx->idx_menu_chosen))) { - if(subghz->txrx->txrx_state == SubGhzTxRxStateTx) { - subghz_tx_stop(subghz); - } - if(subghz->txrx->txrx_state == SubGhzTxRxStateIDLE) { - subghz_begin( - subghz, - subghz_setting_get_preset_data_by_name( - subghz->setting, - furi_string_get_cstr(subghz->txrx->preset->name))); - subghz_rx(subghz, subghz->txrx->preset->frequency); - } - if(subghz->txrx->hopper_state == SubGhzHopperStatePause) { - subghz->txrx->hopper_state = SubGhzHopperStateRunnig; - } - subghz->state_notifications = SubGhzNotificationStateRx; - } else { - subghz->state_notifications = SubGhzNotificationStateTx; - } + //CC1101 Stop RX -> Start TX + subghz_txrx_hopper_pause(subghz->txrx); + if(!subghz_tx_start( + subghz, + subghz_history_get_raw_data(subghz->history, subghz->idx_menu_chosen))) { + subghz_txrx_rx_start(subghz->txrx); + subghz_txrx_hopper_unpause(subghz->txrx); + subghz->state_notifications = SubGhzNotificationStateRx; + } else { + subghz->state_notifications = SubGhzNotificationStateTx; } return true; } else if(event.event == SubGhzCustomEventSceneReceiverInfoTxStop) { //CC1101 Stop Tx -> Start RX subghz->state_notifications = SubGhzNotificationStateIDLE; - if(subghz->txrx->txrx_state == SubGhzTxRxStateTx) { - subghz_tx_stop(subghz); - } - if(subghz->txrx->txrx_state == SubGhzTxRxStateIDLE) { - subghz_begin( - subghz, - subghz_setting_get_preset_data_by_name( - subghz->setting, furi_string_get_cstr(subghz->txrx->preset->name))); - subghz_rx(subghz, subghz->txrx->preset->frequency); - } - if(subghz->txrx->hopper_state == SubGhzHopperStatePause) { - subghz->txrx->hopper_state = SubGhzHopperStateRunnig; - } + + subghz_txrx_rx_start(subghz->txrx); + + subghz_txrx_hopper_unpause(subghz->txrx); subghz->state_notifications = SubGhzNotificationStateRx; return true; } else if(event.event == SubGhzCustomEventSceneReceiverInfoSave) { //CC1101 Stop RX -> Save subghz->state_notifications = SubGhzNotificationStateIDLE; - subghz->txrx->hopper_state = SubGhzHopperStateOFF; - if(subghz->txrx->txrx_state == SubGhzTxRxStateRx) { - subghz_rx_end(subghz); - subghz_sleep(subghz); - } + subghz_txrx_hopper_set_state(subghz->txrx, SubGhzHopperStateOFF); + + subghz_txrx_stop(subghz->txrx); if(!subghz_scene_receiver_info_update_parser(subghz)) { return false; } - if((subghz->txrx->decoder_result->protocol->flag & SubGhzProtocolFlag_Save) == - SubGhzProtocolFlag_Save) { + if(subghz_txrx_protocol_is_serializable(subghz->txrx)) { subghz_file_name_clear(subghz); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveName); } return true; } } else if(event.type == SceneManagerEventTypeTick) { - if(subghz->txrx->hopper_state != SubGhzHopperStateOFF) { - subghz_hopper_update(subghz); + if(subghz_txrx_hopper_get_state(subghz->txrx) != SubGhzHopperStateOFF) { + subghz_txrx_hopper_update(subghz->txrx); } switch(subghz->state_notifications) { case SubGhzNotificationStateTx: diff --git a/applications/main/subghz/scenes/subghz_scene_region_info.c b/applications/main/subghz/scenes/subghz_scene_region_info.c index 82486314d90..b98394af072 100644 --- a/applications/main/subghz/scenes/subghz_scene_region_info.c +++ b/applications/main/subghz/scenes/subghz_scene_region_info.c @@ -5,8 +5,7 @@ void subghz_scene_region_info_on_enter(void* context) { SubGhz* subghz = context; const FuriHalRegion* const region = furi_hal_region_get(); - FuriString* buffer; - buffer = furi_string_alloc(); + FuriString* buffer = furi_string_alloc(); if(region) { furi_string_cat_printf(buffer, "Region: %s, bands:\n", region->country_code); for(uint16_t i = 0; i < region->bands_count; ++i) { diff --git a/applications/main/subghz/scenes/subghz_scene_rpc.c b/applications/main/subghz/scenes/subghz_scene_rpc.c index a1c0e41fd65..aa6f132d761 100644 --- a/applications/main/subghz/scenes/subghz_scene_rpc.c +++ b/applications/main/subghz/scenes/subghz_scene_rpc.c @@ -3,6 +3,7 @@ typedef enum { SubGhzRpcStateIdle, SubGhzRpcStateLoaded, + SubGhzRpcStateTx, } SubGhzRpcState; void subghz_scene_rpc_on_enter(void* context) { @@ -38,9 +39,9 @@ bool subghz_scene_rpc_on_event(void* context, SceneManagerEvent event) { view_dispatcher_stop(subghz->view_dispatcher); } else if(event.event == SubGhzCustomEventSceneRpcButtonPress) { bool result = false; - if((subghz->txrx->txrx_state == SubGhzTxRxStateSleep) && - (state == SubGhzRpcStateLoaded)) { - result = subghz_tx_start(subghz, subghz->txrx->fff_data); + if((state == SubGhzRpcStateLoaded)) { + result = subghz_tx_start(subghz, subghz_txrx_get_fff_data(subghz->txrx)); + state = SubGhzRpcStateTx; if(result) subghz_blink_start(subghz); } if(!result) { @@ -52,10 +53,10 @@ bool subghz_scene_rpc_on_event(void* context, SceneManagerEvent event) { rpc_system_app_confirm(subghz->rpc_ctx, RpcAppEventButtonPress, result); } else if(event.event == SubGhzCustomEventSceneRpcButtonRelease) { bool result = false; - if(subghz->txrx->txrx_state == SubGhzTxRxStateTx) { + if(state == SubGhzRpcStateTx) { + subghz_txrx_stop(subghz->txrx); subghz_blink_stop(subghz); - subghz_tx_stop(subghz); - subghz_sleep(subghz); + state = SubGhzRpcStateIdle; result = true; } rpc_system_app_confirm(subghz->rpc_ctx, RpcAppEventButtonRelease, result); @@ -93,10 +94,9 @@ bool subghz_scene_rpc_on_event(void* context, SceneManagerEvent event) { void subghz_scene_rpc_on_exit(void* context) { SubGhz* subghz = context; - - if(subghz->txrx->txrx_state == SubGhzTxRxStateTx) { - subghz_tx_stop(subghz); - subghz_sleep(subghz); + SubGhzRpcState state = scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneRpc); + if(state != SubGhzRpcStateIdle) { + subghz_txrx_stop(subghz->txrx); subghz_blink_stop(subghz); } diff --git a/applications/main/subghz/scenes/subghz_scene_save_name.c b/applications/main/subghz/scenes/subghz_scene_save_name.c index 255ba228bcc..2a292a1ef39 100644 --- a/applications/main/subghz/scenes/subghz_scene_save_name.c +++ b/applications/main/subghz/scenes/subghz_scene_save_name.c @@ -35,10 +35,8 @@ void subghz_scene_save_name_on_enter(void* context) { TextInput* text_input = subghz->text_input; bool dev_name_empty = false; - FuriString* file_name; - FuriString* dir_name; - file_name = furi_string_alloc(); - dir_name = furi_string_alloc(); + FuriString* file_name = furi_string_alloc(); + FuriString* dir_name = furi_string_alloc(); if(!subghz_path_is_file(subghz->file_path)) { char file_name_buf[SUBGHZ_MAX_LEN_NAME] = {0}; @@ -69,7 +67,7 @@ void subghz_scene_save_name_on_enter(void* context) { subghz_scene_save_name_text_input_callback, subghz, subghz->file_name_tmp, - MAX_TEXT_INPUT_LEN, // buffer size + MAX_TEXT_INPUT_LEN, dev_name_empty); ValidatorIsFile* validator_is_file = validator_is_file_alloc_init( @@ -106,7 +104,7 @@ bool subghz_scene_save_name_on_event(void* context, SceneManagerEvent event) { SubGhzCustomEventManagerNoSet) { subghz_save_protocol_to_file( subghz, - subghz->txrx->fff_data, + subghz_txrx_get_fff_data(subghz->txrx), furi_string_get_cstr(subghz->file_path)); scene_manager_set_scene_state( subghz->scene_manager, @@ -115,8 +113,7 @@ bool subghz_scene_save_name_on_event(void* context, SceneManagerEvent event) { } else { subghz_save_protocol_to_file( subghz, - subghz_history_get_raw_data( - subghz->txrx->history, subghz->txrx->idx_menu_chosen), + subghz_history_get_raw_data(subghz->history, subghz->idx_menu_chosen), furi_string_get_cstr(subghz->file_path)); } } @@ -124,7 +121,8 @@ bool subghz_scene_save_name_on_event(void* context, SceneManagerEvent event) { if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAW) != SubGhzCustomEventManagerNoSet) { subghz_protocol_raw_gen_fff_data( - subghz->txrx->fff_data, furi_string_get_cstr(subghz->file_path)); + subghz_txrx_get_fff_data(subghz->txrx), + furi_string_get_cstr(subghz->file_path)); scene_manager_set_scene_state( subghz->scene_manager, SubGhzSceneReadRAW, SubGhzCustomEventManagerNoSet); } else { diff --git a/applications/main/subghz/scenes/subghz_scene_save_success.c b/applications/main/subghz/scenes/subghz_scene_save_success.c index 2977975f7ff..40ade5a5350 100644 --- a/applications/main/subghz/scenes/subghz_scene_save_success.c +++ b/applications/main/subghz/scenes/subghz_scene_save_success.c @@ -26,10 +26,10 @@ bool subghz_scene_save_success_on_event(void* context, SceneManagerEvent event) if(event.event == SubGhzCustomEventSceneSaveSuccess) { if(!scene_manager_search_and_switch_to_previous_scene( subghz->scene_manager, SubGhzSceneReceiver)) { - subghz->txrx->rx_key_state = SubGhzRxKeyStateRAWSave; + subghz_rx_key_state_set(subghz, SubGhzRxKeyStateRAWSave); if(!scene_manager_search_and_switch_to_previous_scene( subghz->scene_manager, SubGhzSceneReadRAW)) { - subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE; + subghz_rx_key_state_set(subghz, SubGhzRxKeyStateIDLE); if(!scene_manager_search_and_switch_to_previous_scene( subghz->scene_manager, SubGhzSceneSaved)) { scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaved); diff --git a/applications/main/subghz/scenes/subghz_scene_saved.c b/applications/main/subghz/scenes/subghz_scene_saved.c index 62ade3508e7..8b198e3395e 100644 --- a/applications/main/subghz/scenes/subghz_scene_saved.c +++ b/applications/main/subghz/scenes/subghz_scene_saved.c @@ -4,8 +4,8 @@ void subghz_scene_saved_on_enter(void* context) { SubGhz* subghz = context; if(subghz_load_protocol_from_file(subghz)) { - if((!strcmp(subghz->txrx->decoder_result->protocol->name, "RAW"))) { - subghz->txrx->rx_key_state = SubGhzRxKeyStateRAWLoad; + if(subghz_get_load_type_file(subghz) == SubGhzLoadTypeFileRaw) { + subghz_rx_key_state_set(subghz, SubGhzRxKeyStateRAWLoad); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReadRAW); } else { scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSavedMenu); diff --git a/applications/main/subghz/scenes/subghz_scene_set_type.c b/applications/main/subghz/scenes/subghz_scene_set_type.c index 2134377e3ad..32e0d658848 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_type.c +++ b/applications/main/subghz/scenes/subghz_scene_set_type.c @@ -1,63 +1,10 @@ #include "../subghz_i.h" -#include -#include -#include +#include "../helpers/subghz_txrx_create_potocol_key.h" #include -#include -#include #include #define TAG "SubGhzSetType" -bool subghz_scene_set_type_submenu_gen_data_protocol( - void* context, - const char* protocol_name, - uint64_t key, - uint32_t bit, - uint32_t frequency, - const char* preset_name) { - furi_assert(context); - SubGhz* subghz = context; - - bool res = false; - - subghz_preset_init(subghz, preset_name, frequency, NULL, 0); - subghz->txrx->decoder_result = - subghz_receiver_search_decoder_base_by_name(subghz->txrx->receiver, protocol_name); - - if(subghz->txrx->decoder_result == NULL) { - furi_string_set(subghz->error_str, "Protocol not\nfound!"); - scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowErrorSub); - return false; - } - - do { - Stream* fff_data_stream = flipper_format_get_raw_stream(subghz->txrx->fff_data); - stream_clean(fff_data_stream); - if(subghz_protocol_decoder_base_serialize( - subghz->txrx->decoder_result, subghz->txrx->fff_data, subghz->txrx->preset) != - SubGhzProtocolStatusOk) { - FURI_LOG_E(TAG, "Unable to serialize"); - break; - } - if(!flipper_format_update_uint32(subghz->txrx->fff_data, "Bit", &bit, 1)) { - FURI_LOG_E(TAG, "Unable to update Bit"); - break; - } - - uint8_t key_data[sizeof(uint64_t)] = {0}; - for(size_t i = 0; i < sizeof(uint64_t); i++) { - key_data[sizeof(uint64_t) - i - 1] = (key >> (i * 8)) & 0xFF; - } - if(!flipper_format_update_hex(subghz->txrx->fff_data, "Key", key_data, sizeof(uint64_t))) { - FURI_LOG_E(TAG, "Unable to update Key"); - break; - } - res = true; - } while(false); - return res; -} - void subghz_scene_set_type_submenu_callback(void* context, uint32_t index) { SubGhz* subghz = context; view_dispatcher_send_custom_event(subghz->view_dispatcher, index); @@ -69,7 +16,13 @@ void subghz_scene_set_type_on_enter(void* context) { submenu_add_item( subghz->submenu, "Princeton_433", - SubmenuIndexPricenton, + SubmenuIndexPricenton_433, + subghz_scene_set_type_submenu_callback, + subghz); + submenu_add_item( + subghz->submenu, + "Princeton_315", + SubmenuIndexPricenton_315, subghz_scene_set_type_submenu_callback, subghz); submenu_add_item( @@ -108,10 +61,6 @@ void subghz_scene_set_type_on_enter(void* context) { SubmenuIndexCAMETwee, subghz_scene_set_type_submenu_callback, subghz); - // submenu_add_item( - // subghz->submenu, "Nero Sketch", SubmenuIndexNeroSketch, subghz_scene_set_type_submenu_callback, subghz); - // submenu_add_item( - // subghz->submenu, "Nero Radio", SubmenuIndexNeroRadio, subghz_scene_set_type_submenu_callback, subghz); submenu_add_item( subghz->submenu, "Gate TX_433", @@ -172,94 +121,59 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { bool generated_protocol = false; if(event.type == SceneManagerEventTypeCustom) { - //ToDo Fix - uint32_t key = subghz_random_serial(); + uint32_t key = (uint32_t)rand(); switch(event.event) { - case SubmenuIndexPricenton: + case SubmenuIndexPricenton_433: key = (key & 0x00FFFFF0) | 0x4; //btn 0x1, 0x2, 0x4, 0x8 - if(subghz_scene_set_type_submenu_gen_data_protocol( - subghz, SUBGHZ_PROTOCOL_PRINCETON_NAME, key, 24, 433920000, "AM650")) { - uint32_t te = 400; - flipper_format_update_uint32(subghz->txrx->fff_data, "TE", (uint32_t*)&te, 1); - generated_protocol = true; - } + generated_protocol = subghz_txrx_gen_data_protocol_and_te( + subghz->txrx, "AM650", 433920000, SUBGHZ_PROTOCOL_PRINCETON_NAME, key, 24, 400); + break; + case SubmenuIndexPricenton_315: + key = (key & 0x00FFFFF0) | 0x4; //btn 0x1, 0x2, 0x4, 0x8 + generated_protocol = subghz_txrx_gen_data_protocol_and_te( + subghz->txrx, "AM650", 315000000, SUBGHZ_PROTOCOL_PRINCETON_NAME, key, 24, 400); break; case SubmenuIndexNiceFlo12bit: key = (key & 0x0000FFF0) | 0x1; //btn 0x1, 0x2, 0x4 - if(subghz_scene_set_type_submenu_gen_data_protocol( - subghz, SUBGHZ_PROTOCOL_NICE_FLO_NAME, key, 12, 433920000, "AM650")) { - generated_protocol = true; - } + generated_protocol = subghz_txrx_gen_data_protocol( + subghz->txrx, "AM650", 433920000, SUBGHZ_PROTOCOL_NICE_FLO_NAME, key, 12); break; case SubmenuIndexNiceFlo24bit: key = (key & 0x00FFFFF0) | 0x4; //btn 0x1, 0x2, 0x4, 0x8 - if(subghz_scene_set_type_submenu_gen_data_protocol( - subghz, SUBGHZ_PROTOCOL_NICE_FLO_NAME, key, 24, 433920000, "AM650")) { - generated_protocol = true; - } + generated_protocol = subghz_txrx_gen_data_protocol( + subghz->txrx, "AM650", 433920000, SUBGHZ_PROTOCOL_NICE_FLO_NAME, key, 24); break; case SubmenuIndexCAME12bit: key = (key & 0x0000FFF0) | 0x1; //btn 0x1, 0x2, 0x4 - if(subghz_scene_set_type_submenu_gen_data_protocol( - subghz, SUBGHZ_PROTOCOL_CAME_NAME, key, 12, 433920000, "AM650")) { - generated_protocol = true; - } + generated_protocol = subghz_txrx_gen_data_protocol( + subghz->txrx, "AM650", 433920000, SUBGHZ_PROTOCOL_CAME_NAME, key, 12); break; case SubmenuIndexCAME24bit: key = (key & 0x00FFFFF0) | 0x4; //btn 0x1, 0x2, 0x4, 0x8 - if(subghz_scene_set_type_submenu_gen_data_protocol( - subghz, SUBGHZ_PROTOCOL_CAME_NAME, key, 24, 433920000, "AM650")) { - generated_protocol = true; - } + generated_protocol = subghz_txrx_gen_data_protocol( + subghz->txrx, "AM650", 433920000, SUBGHZ_PROTOCOL_CAME_NAME, key, 24); break; case SubmenuIndexLinear_300_00: key = (key & 0x3FF); - if(subghz_scene_set_type_submenu_gen_data_protocol( - subghz, SUBGHZ_PROTOCOL_LINEAR_NAME, key, 10, 300000000, "AM650")) { - generated_protocol = true; - } + generated_protocol = subghz_txrx_gen_data_protocol( + subghz->txrx, "AM650", 300000000, SUBGHZ_PROTOCOL_LINEAR_NAME, key, 10); break; case SubmenuIndexCAMETwee: key = (key & 0x0FFFFFF0); key = 0x003FFF7200000000 | (key ^ 0xE0E0E0EE); - if(subghz_scene_set_type_submenu_gen_data_protocol( - subghz, SUBGHZ_PROTOCOL_CAME_TWEE_NAME, key, 54, 433920000, "AM650")) { - generated_protocol = true; - } + + generated_protocol = subghz_txrx_gen_data_protocol( + subghz->txrx, "AM650", 433920000, SUBGHZ_PROTOCOL_CAME_TWEE_NAME, key, 54); break; - // case SubmenuIndexNeroSketch: - // /* code */ - // break; - // case SubmenuIndexNeroRadio: - // /* code */ - // break; case SubmenuIndexGateTX: key = (key & 0x00F0FF00) | 0xF << 16 | 0x40; //btn 0xF, 0xC, 0xA, 0x6 (?) uint64_t rev_key = subghz_protocol_blocks_reverse_key(key, 24); - if(subghz_scene_set_type_submenu_gen_data_protocol( - subghz, SUBGHZ_PROTOCOL_GATE_TX_NAME, rev_key, 24, 433920000, "AM650")) { - generated_protocol = true; - } + generated_protocol = subghz_txrx_gen_data_protocol( + subghz->txrx, "AM650", 433920000, SUBGHZ_PROTOCOL_GATE_TX_NAME, rev_key, 24); break; case SubmenuIndexDoorHan_433_92: - subghz->txrx->transmitter = subghz_transmitter_alloc_init( - subghz->txrx->environment, SUBGHZ_PROTOCOL_KEELOQ_NAME); - subghz_preset_init( - subghz, "AM650", subghz_setting_get_default_frequency(subghz->setting), NULL, 0); - if(subghz->txrx->transmitter) { - subghz_protocol_keeloq_create_data( - subghz_transmitter_get_protocol_instance(subghz->txrx->transmitter), - subghz->txrx->fff_data, - key & 0x0FFFFFFF, - 0x2, - 0x0003, - "DoorHan", - subghz->txrx->preset); - generated_protocol = true; - } else { - generated_protocol = false; - } - subghz_transmitter_free(subghz->txrx->transmitter); + generated_protocol = subghz_txrx_gen_keelog_protocol( + subghz->txrx, "AM650", 433920000, "DoorHan", key, 0x2, 0x0003); if(!generated_protocol) { furi_string_set( subghz->error_str, "Function requires\nan SD card with\nfresh databases."); @@ -267,23 +181,8 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { } break; case SubmenuIndexDoorHan_315_00: - subghz->txrx->transmitter = subghz_transmitter_alloc_init( - subghz->txrx->environment, SUBGHZ_PROTOCOL_KEELOQ_NAME); - subghz_preset_init(subghz, "AM650", 315000000, NULL, 0); - if(subghz->txrx->transmitter) { - subghz_protocol_keeloq_create_data( - subghz_transmitter_get_protocol_instance(subghz->txrx->transmitter), - subghz->txrx->fff_data, - key & 0x0FFFFFFF, - 0x2, - 0x0003, - "DoorHan", - subghz->txrx->preset); - generated_protocol = true; - } else { - generated_protocol = false; - } - subghz_transmitter_free(subghz->txrx->transmitter); + generated_protocol = subghz_txrx_gen_keelog_protocol( + subghz->txrx, "AM650", 315000000, "DoorHan", key, 0x2, 0x0003); if(!generated_protocol) { furi_string_set( subghz->error_str, "Function requires\nan SD card with\nfresh databases."); @@ -291,86 +190,24 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { } break; case SubmenuIndexLiftMaster_315_00: - while(!subghz_protocol_secplus_v1_check_fixed(key)) { - key = subghz_random_serial(); - } - if(subghz_scene_set_type_submenu_gen_data_protocol( - subghz, - SUBGHZ_PROTOCOL_SECPLUS_V1_NAME, - (uint64_t)key << 32 | 0xE6000000, - 42, - 315000000, - "AM650")) { - generated_protocol = true; - } + generated_protocol = + subghz_txrx_gen_secplus_v1_protocol(subghz->txrx, "AM650", 315000000); break; case SubmenuIndexLiftMaster_390_00: - while(!subghz_protocol_secplus_v1_check_fixed(key)) { - key = subghz_random_serial(); - } - if(subghz_scene_set_type_submenu_gen_data_protocol( - subghz, - SUBGHZ_PROTOCOL_SECPLUS_V1_NAME, - (uint64_t)key << 32 | 0xE6000000, - 42, - 390000000, - "AM650")) { - generated_protocol = true; - } + generated_protocol = + subghz_txrx_gen_secplus_v1_protocol(subghz->txrx, "AM650", 390000000); break; case SubmenuIndexSecPlus_v2_310_00: - subghz->txrx->transmitter = subghz_transmitter_alloc_init( - subghz->txrx->environment, SUBGHZ_PROTOCOL_SECPLUS_V2_NAME); - subghz_preset_init(subghz, "AM650", 310000000, NULL, 0); - if(subghz->txrx->transmitter) { - subghz_protocol_secplus_v2_create_data( - subghz_transmitter_get_protocol_instance(subghz->txrx->transmitter), - subghz->txrx->fff_data, - key, - 0x68, - 0xE500000, - subghz->txrx->preset); - generated_protocol = true; - } else { - generated_protocol = false; - } - subghz_transmitter_free(subghz->txrx->transmitter); + generated_protocol = subghz_txrx_gen_secplus_v2_protocol( + subghz->txrx, "AM650", 310000000, key, 0x68, 0xE500000); break; case SubmenuIndexSecPlus_v2_315_00: - subghz->txrx->transmitter = subghz_transmitter_alloc_init( - subghz->txrx->environment, SUBGHZ_PROTOCOL_SECPLUS_V2_NAME); - subghz_preset_init(subghz, "AM650", 315000000, NULL, 0); - if(subghz->txrx->transmitter) { - subghz_protocol_secplus_v2_create_data( - subghz_transmitter_get_protocol_instance(subghz->txrx->transmitter), - subghz->txrx->fff_data, - key, - 0x68, - 0xE500000, - subghz->txrx->preset); - generated_protocol = true; - } else { - generated_protocol = false; - } - subghz_transmitter_free(subghz->txrx->transmitter); + generated_protocol = subghz_txrx_gen_secplus_v2_protocol( + subghz->txrx, "AM650", 315000000, key, 0x68, 0xE500000); break; case SubmenuIndexSecPlus_v2_390_00: - subghz->txrx->transmitter = subghz_transmitter_alloc_init( - subghz->txrx->environment, SUBGHZ_PROTOCOL_SECPLUS_V2_NAME); - subghz_preset_init(subghz, "AM650", 390000000, NULL, 0); - if(subghz->txrx->transmitter) { - subghz_protocol_secplus_v2_create_data( - subghz_transmitter_get_protocol_instance(subghz->txrx->transmitter), - subghz->txrx->fff_data, - key, - 0x68, - 0xE500000, - subghz->txrx->preset); - generated_protocol = true; - } else { - generated_protocol = false; - } - subghz_transmitter_free(subghz->txrx->transmitter); + generated_protocol = subghz_txrx_gen_secplus_v2_protocol( + subghz->txrx, "AM650", 390000000, key, 0x68, 0xE500000); break; default: return false; diff --git a/applications/main/subghz/scenes/subghz_scene_show_error.c b/applications/main/subghz/scenes/subghz_scene_show_error.c index eadfb21146a..d52eca9b6cf 100644 --- a/applications/main/subghz/scenes/subghz_scene_show_error.c +++ b/applications/main/subghz/scenes/subghz_scene_show_error.c @@ -50,9 +50,10 @@ void subghz_scene_show_error_on_enter(void* context) { bool subghz_scene_show_error_on_event(void* context, SceneManagerEvent event) { SubGhz* subghz = context; + SubGhzCustomEvent scene_state = + scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneShowError); if(event.type == SceneManagerEventTypeBack) { - if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneShowError) == - SubGhzCustomEventManagerSet) { + if(scene_state == SubGhzCustomEventManagerSet) { return false; } else { scene_manager_search_and_switch_to_previous_scene( @@ -61,14 +62,12 @@ bool subghz_scene_show_error_on_event(void* context, SceneManagerEvent event) { return true; } else if(event.type == SceneManagerEventTypeCustom) { if(event.event == SubGhzCustomEventSceneShowErrorOk) { - if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneShowError) == - SubGhzCustomEventManagerSet) { + if(scene_state == SubGhzCustomEventManagerSet) { scene_manager_next_scene(subghz->scene_manager, SubGhzSceneStart); } return true; } else if(event.event == SubGhzCustomEventSceneShowErrorBack) { - if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneShowError) == - SubGhzCustomEventManagerSet) { + if(scene_state == SubGhzCustomEventManagerSet) { //exit app if(!scene_manager_previous_scene(subghz->scene_manager)) { scene_manager_stop(subghz->scene_manager); diff --git a/applications/main/subghz/scenes/subghz_scene_start.c b/applications/main/subghz/scenes/subghz_scene_start.c index a50f73a810f..a41e4b06f4a 100644 --- a/applications/main/subghz/scenes/subghz_scene_start.c +++ b/applications/main/subghz/scenes/subghz_scene_start.c @@ -70,7 +70,7 @@ bool subghz_scene_start_on_event(void* context, SceneManagerEvent event) { if(event.event == SubmenuIndexReadRAW) { scene_manager_set_scene_state( subghz->scene_manager, SubGhzSceneStart, SubmenuIndexReadRAW); - subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE; + subghz_rx_key_state_set(subghz, SubGhzRxKeyStateIDLE); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReadRAW); return true; } else if(event.event == SubmenuIndexRead) { diff --git a/applications/main/subghz/scenes/subghz_scene_transmitter.c b/applications/main/subghz/scenes/subghz_scene_transmitter.c index 712e50071c1..1c193c1794d 100644 --- a/applications/main/subghz/scenes/subghz_scene_transmitter.c +++ b/applications/main/subghz/scenes/subghz_scene_transmitter.c @@ -11,32 +11,24 @@ void subghz_scene_transmitter_callback(SubGhzCustomEvent event, void* context) { bool subghz_scene_transmitter_update_data_show(void* context) { SubGhz* subghz = context; bool ret = false; - if(subghz->txrx->decoder_result) { - FuriString* key_str; - FuriString* frequency_str; - FuriString* modulation_str; + SubGhzProtocolDecoderBase* decoder = subghz_txrx_get_decoder(subghz->txrx); - key_str = furi_string_alloc(); - frequency_str = furi_string_alloc(); - modulation_str = furi_string_alloc(); - uint8_t show_button = 0; + if(decoder) { + FuriString* key_str = furi_string_alloc(); + FuriString* frequency_str = furi_string_alloc(); + FuriString* modulation_str = furi_string_alloc(); if(subghz_protocol_decoder_base_deserialize( - subghz->txrx->decoder_result, subghz->txrx->fff_data) == SubGhzProtocolStatusOk) { - subghz_protocol_decoder_base_get_string(subghz->txrx->decoder_result, key_str); + decoder, subghz_txrx_get_fff_data(subghz->txrx)) == SubGhzProtocolStatusOk) { + subghz_protocol_decoder_base_get_string(decoder, key_str); - if((subghz->txrx->decoder_result->protocol->flag & SubGhzProtocolFlag_Send) == - SubGhzProtocolFlag_Send) { - show_button = 1; - } - - subghz_get_frequency_modulation(subghz, frequency_str, modulation_str); + subghz_txrx_get_frequency_and_modulation(subghz->txrx, frequency_str, modulation_str); subghz_view_transmitter_add_data_to_show( subghz->subghz_transmitter, furi_string_get_cstr(key_str), furi_string_get_cstr(frequency_str), furi_string_get_cstr(modulation_str), - show_button); + subghz_txrx_protocol_is_transmittable(subghz->txrx, false)); ret = true; } furi_string_free(frequency_str); @@ -65,24 +57,16 @@ bool subghz_scene_transmitter_on_event(void* context, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { if(event.event == SubGhzCustomEventViewTransmitterSendStart) { subghz->state_notifications = SubGhzNotificationStateIDLE; - if(subghz->txrx->txrx_state == SubGhzTxRxStateRx) { - subghz_rx_end(subghz); - } - if((subghz->txrx->txrx_state == SubGhzTxRxStateIDLE) || - (subghz->txrx->txrx_state == SubGhzTxRxStateSleep)) { - if(subghz_tx_start(subghz, subghz->txrx->fff_data)) { - subghz->state_notifications = SubGhzNotificationStateTx; - subghz_scene_transmitter_update_data_show(subghz); - DOLPHIN_DEED(DolphinDeedSubGhzSend); - } + + if(subghz_tx_start(subghz, subghz_txrx_get_fff_data(subghz->txrx))) { + subghz->state_notifications = SubGhzNotificationStateTx; + subghz_scene_transmitter_update_data_show(subghz); + DOLPHIN_DEED(DolphinDeedSubGhzSend); } return true; } else if(event.event == SubGhzCustomEventViewTransmitterSendStop) { subghz->state_notifications = SubGhzNotificationStateIDLE; - if(subghz->txrx->txrx_state == SubGhzTxRxStateTx) { - subghz_tx_stop(subghz); - subghz_sleep(subghz); - } + subghz_txrx_stop(subghz->txrx); return true; } else if(event.event == SubGhzCustomEventViewTransmitterBack) { subghz->state_notifications = SubGhzNotificationStateIDLE; diff --git a/applications/main/subghz/subghz.c b/applications/main/subghz/subghz.c index 25233fe21cf..09963584afc 100644 --- a/applications/main/subghz/subghz.c +++ b/applications/main/subghz/subghz.c @@ -1,9 +1,6 @@ /* Abandon hope, all ye who enter here. */ -#include "subghz/types.h" #include "subghz_i.h" -#include -#include bool subghz_custom_event_callback(void* context, uint32_t event) { furi_assert(context); @@ -49,16 +46,6 @@ static void subghz_rpc_command_callback(RpcAppSystemEvent event, void* context) } } -void subghz_blink_start(SubGhz* instance) { - furi_assert(instance); - notification_message(instance->notifications, &sequence_blink_start_magenta); -} - -void subghz_blink_stop(SubGhz* instance) { - furi_assert(instance); - notification_message(instance->notifications, &sequence_blink_stop); -} - SubGhz* subghz_alloc() { SubGhz* subghz = malloc(sizeof(SubGhz)); @@ -163,45 +150,18 @@ SubGhz* subghz_alloc() { SubGhzViewIdStatic, subghz_test_static_get_view(subghz->subghz_test_static)); - //init setting - subghz->setting = subghz_setting_alloc(); - subghz_setting_load(subghz->setting, EXT_PATH("subghz/assets/setting_user")); - - //init Worker & Protocol & History & KeyBoard - subghz->lock = SubGhzLockOff; - subghz->txrx = malloc(sizeof(SubGhzTxRx)); - subghz->txrx->preset = malloc(sizeof(SubGhzRadioPreset)); - subghz->txrx->preset->name = furi_string_alloc(); - subghz_preset_init( - subghz, "AM650", subghz_setting_get_default_frequency(subghz->setting), NULL, 0); - - subghz->txrx->txrx_state = SubGhzTxRxStateSleep; - subghz->txrx->hopper_state = SubGhzHopperStateOFF; - subghz->txrx->speaker_state = SubGhzSpeakerStateDisable; - subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE; - subghz->txrx->raw_threshold_rssi = SUBGHZ_RAW_TRESHOLD_MIN; - subghz->txrx->history = subghz_history_alloc(); - subghz->txrx->worker = subghz_worker_alloc(); - subghz->txrx->fff_data = flipper_format_string_alloc(); - - subghz->txrx->environment = subghz_environment_alloc(); - subghz_environment_set_came_atomo_rainbow_table_file_name( - subghz->txrx->environment, EXT_PATH("subghz/assets/came_atomo")); - subghz_environment_set_alutech_at_4n_rainbow_table_file_name( - subghz->txrx->environment, EXT_PATH("subghz/assets/alutech_at_4n")); - subghz_environment_set_nice_flor_s_rainbow_table_file_name( - subghz->txrx->environment, EXT_PATH("subghz/assets/nice_flor_s")); - subghz_environment_set_protocol_registry( - subghz->txrx->environment, (void*)&subghz_protocol_registry); - subghz->txrx->receiver = subghz_receiver_alloc_init(subghz->txrx->environment); - subghz->txrx->filter = SubGhzProtocolFlag_Decodable; - subghz_receiver_set_filter(subghz->txrx->receiver, subghz->txrx->filter); - - subghz_worker_set_overrun_callback( - subghz->txrx->worker, (SubGhzWorkerOverrunCallback)subghz_receiver_reset); - subghz_worker_set_pair_callback( - subghz->txrx->worker, (SubGhzWorkerPairCallback)subghz_receiver_decode); - subghz_worker_set_context(subghz->txrx->worker, subghz->txrx->receiver); + //init threshold rssi + subghz->threshold_rssi = subghz_threshold_rssi_alloc(); + + subghz_unlock(subghz); + subghz_rx_key_state_set(subghz, SubGhzRxKeyStateIDLE); + subghz->history = subghz_history_alloc(); + subghz->filter = SubGhzProtocolFlag_Decodable; + + //init TxRx & History & KeyBoard + subghz->txrx = subghz_txrx_alloc(); + subghz_txrx_receiver_set_filter(subghz->txrx, subghz->filter); + subghz_txrx_set_need_save_callback(subghz->txrx, subghz_save_to_file, subghz); //Init Error_str subghz->error_str = furi_string_alloc(); @@ -219,7 +179,9 @@ void subghz_free(SubGhz* subghz) { subghz->rpc_ctx = NULL; } - subghz_speaker_off(subghz); + subghz_txrx_speaker_off(subghz->txrx); + subghz_txrx_stop(subghz->txrx); + subghz_txrx_sleep(subghz->txrx); // Packet Test view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdTestPacket); @@ -282,18 +244,14 @@ void subghz_free(SubGhz* subghz) { furi_record_close(RECORD_GUI); subghz->gui = NULL; - //setting - subghz_setting_free(subghz->setting); + // threshold rssi + subghz_threshold_rssi_free(subghz->threshold_rssi); //Worker & Protocol & History - subghz_receiver_free(subghz->txrx->receiver); - subghz_environment_free(subghz->txrx->environment); - subghz_worker_free(subghz->txrx->worker); - flipper_format_free(subghz->txrx->fff_data); - subghz_history_free(subghz->txrx->history); - furi_string_free(subghz->txrx->preset->name); - free(subghz->txrx->preset); - free(subghz->txrx); + subghz_history_free(subghz->history); + + //TxRx + subghz_txrx_free(subghz->txrx); //Error string furi_string_free(subghz->error_str); @@ -319,11 +277,6 @@ int32_t subghz_app(void* p) { return 1; } - //Load database - bool load_database = subghz_environment_load_keystore( - subghz->txrx->environment, EXT_PATH("subghz/assets/keeloq_mfcodes")); - subghz_environment_load_keystore( - subghz->txrx->environment, EXT_PATH("subghz/assets/keeloq_mfcodes_user")); // Check argument and run corresponding scene if(p && strlen(p)) { uint32_t rpc_ctx = 0; @@ -340,9 +293,9 @@ int32_t subghz_app(void* p) { if(subghz_key_load(subghz, p, true)) { furi_string_set(subghz->file_path, (const char*)p); - if((!strcmp(subghz->txrx->decoder_result->protocol->name, "RAW"))) { + if(subghz_get_load_type_file(subghz) == SubGhzLoadTypeFileRaw) { //Load Raw TX - subghz->txrx->rx_key_state = SubGhzRxKeyStateRAWLoad; + subghz_rx_key_state_set(subghz, SubGhzRxKeyStateRAWLoad); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReadRAW); } else { //Load transmitter TX @@ -358,7 +311,7 @@ int32_t subghz_app(void* p) { view_dispatcher_attach_to_gui( subghz->view_dispatcher, subghz->gui, ViewDispatcherTypeFullscreen); furi_string_set(subghz->file_path, SUBGHZ_APP_FOLDER); - if(load_database) { + if(subghz_txrx_is_database_loaded(subghz->txrx)) { scene_manager_next_scene(subghz->scene_manager, SubGhzSceneStart); } else { scene_manager_set_scene_state( diff --git a/applications/main/subghz/subghz_i.c b/applications/main/subghz/subghz_i.c index 18d87c76b5a..8036ed5f7ac 100644 --- a/applications/main/subghz/subghz_i.c +++ b/applications/main/subghz/subghz_i.c @@ -18,214 +18,42 @@ #define TAG "SubGhz" -void subghz_preset_init( - void* context, - const char* preset_name, - uint32_t frequency, - uint8_t* preset_data, - size_t preset_data_size) { - furi_assert(context); - SubGhz* subghz = context; - furi_string_set(subghz->txrx->preset->name, preset_name); - subghz->txrx->preset->frequency = frequency; - subghz->txrx->preset->data = preset_data; - subghz->txrx->preset->data_size = preset_data_size; -} - -bool subghz_set_preset(SubGhz* subghz, const char* preset) { - if(!strcmp(preset, "FuriHalSubGhzPresetOok270Async")) { - furi_string_set(subghz->txrx->preset->name, "AM270"); - } else if(!strcmp(preset, "FuriHalSubGhzPresetOok650Async")) { - furi_string_set(subghz->txrx->preset->name, "AM650"); - } else if(!strcmp(preset, "FuriHalSubGhzPreset2FSKDev238Async")) { - furi_string_set(subghz->txrx->preset->name, "FM238"); - } else if(!strcmp(preset, "FuriHalSubGhzPreset2FSKDev476Async")) { - furi_string_set(subghz->txrx->preset->name, "FM476"); - } else if(!strcmp(preset, "FuriHalSubGhzPresetCustom")) { - furi_string_set(subghz->txrx->preset->name, "CUSTOM"); - } else { - FURI_LOG_E(TAG, "Unknown preset"); - return false; - } - return true; -} - -void subghz_get_frequency_modulation(SubGhz* subghz, FuriString* frequency, FuriString* modulation) { +void subghz_set_default_preset(SubGhz* subghz) { furi_assert(subghz); - if(frequency != NULL) { - furi_string_printf( - frequency, - "%03ld.%02ld", - subghz->txrx->preset->frequency / 1000000 % 1000, - subghz->txrx->preset->frequency / 10000 % 100); - } - if(modulation != NULL) { - furi_string_printf(modulation, "%.2s", furi_string_get_cstr(subghz->txrx->preset->name)); - } + subghz_txrx_set_preset( + subghz->txrx, + "AM650", + subghz_setting_get_default_frequency(subghz_txrx_get_setting(subghz->txrx)), + NULL, + 0); } -void subghz_begin(SubGhz* subghz, uint8_t* preset_data) { +void subghz_blink_start(SubGhz* subghz) { furi_assert(subghz); - furi_hal_subghz_reset(); - furi_hal_subghz_idle(); - furi_hal_subghz_load_custom_preset(preset_data); - furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow); - subghz->txrx->txrx_state = SubGhzTxRxStateIDLE; + notification_message(subghz->notifications, &sequence_blink_stop); + notification_message(subghz->notifications, &sequence_blink_start_magenta); } -uint32_t subghz_rx(SubGhz* subghz, uint32_t frequency) { +void subghz_blink_stop(SubGhz* subghz) { furi_assert(subghz); - if(!furi_hal_subghz_is_frequency_valid(frequency)) { - furi_crash("SubGhz: Incorrect RX frequency."); - } - furi_assert( - subghz->txrx->txrx_state != SubGhzTxRxStateRx && - subghz->txrx->txrx_state != SubGhzTxRxStateSleep); - - furi_hal_subghz_idle(); - uint32_t value = furi_hal_subghz_set_frequency_and_path(frequency); - furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow); - furi_hal_subghz_flush_rx(); - subghz_speaker_on(subghz); - furi_hal_subghz_rx(); - - furi_hal_subghz_start_async_rx(subghz_worker_rx_callback, subghz->txrx->worker); - subghz_worker_start(subghz->txrx->worker); - subghz->txrx->txrx_state = SubGhzTxRxStateRx; - return value; -} - -static bool subghz_tx(SubGhz* subghz, uint32_t frequency) { - furi_assert(subghz); - if(!furi_hal_subghz_is_frequency_valid(frequency)) { - furi_crash("SubGhz: Incorrect TX frequency."); - } - furi_assert(subghz->txrx->txrx_state != SubGhzTxRxStateSleep); - furi_hal_subghz_idle(); - furi_hal_subghz_set_frequency_and_path(frequency); - furi_hal_gpio_write(&gpio_cc1101_g0, false); - furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); - bool ret = furi_hal_subghz_tx(); - if(ret) { - subghz_speaker_on(subghz); - subghz->txrx->txrx_state = SubGhzTxRxStateTx; - } - return ret; -} - -void subghz_idle(SubGhz* subghz) { - furi_assert(subghz); - furi_assert(subghz->txrx->txrx_state != SubGhzTxRxStateSleep); - furi_hal_subghz_idle(); - subghz_speaker_off(subghz); - subghz->txrx->txrx_state = SubGhzTxRxStateIDLE; -} - -void subghz_rx_end(SubGhz* subghz) { - furi_assert(subghz); - furi_assert(subghz->txrx->txrx_state == SubGhzTxRxStateRx); - - if(subghz_worker_is_running(subghz->txrx->worker)) { - subghz_worker_stop(subghz->txrx->worker); - furi_hal_subghz_stop_async_rx(); - } - furi_hal_subghz_idle(); - subghz_speaker_off(subghz); - subghz->txrx->txrx_state = SubGhzTxRxStateIDLE; -} - -void subghz_sleep(SubGhz* subghz) { - furi_assert(subghz); - furi_hal_subghz_sleep(); - subghz->txrx->txrx_state = SubGhzTxRxStateSleep; + notification_message(subghz->notifications, &sequence_blink_stop); } bool subghz_tx_start(SubGhz* subghz, FlipperFormat* flipper_format) { - furi_assert(subghz); - - bool ret = false; - FuriString* temp_str; - temp_str = furi_string_alloc(); - uint32_t repeat = 200; - do { - if(!flipper_format_rewind(flipper_format)) { - FURI_LOG_E(TAG, "Rewind error"); - break; - } - if(!flipper_format_read_string(flipper_format, "Protocol", temp_str)) { - FURI_LOG_E(TAG, "Missing Protocol"); - break; - } - if(!flipper_format_insert_or_update_uint32(flipper_format, "Repeat", &repeat, 1)) { - FURI_LOG_E(TAG, "Unable Repeat"); - break; - } - - subghz->txrx->transmitter = subghz_transmitter_alloc_init( - subghz->txrx->environment, furi_string_get_cstr(temp_str)); - - if(subghz->txrx->transmitter) { - if(subghz_transmitter_deserialize(subghz->txrx->transmitter, flipper_format) == - SubGhzProtocolStatusOk) { - if(strcmp(furi_string_get_cstr(subghz->txrx->preset->name), "") != 0) { - subghz_begin( - subghz, - subghz_setting_get_preset_data_by_name( - subghz->setting, furi_string_get_cstr(subghz->txrx->preset->name))); - } else { - FURI_LOG_E( - TAG, - "Unknown name preset \" %s \"", - furi_string_get_cstr(subghz->txrx->preset->name)); - subghz_begin( - subghz, subghz_setting_get_preset_data_by_name(subghz->setting, "AM650")); - } - if(subghz->txrx->preset->frequency) { - ret = subghz_tx(subghz, subghz->txrx->preset->frequency); - } else { - ret = subghz_tx(subghz, 433920000); - } - if(ret) { - //Start TX - furi_hal_subghz_start_async_tx( - subghz_transmitter_yield, subghz->txrx->transmitter); - } else { - subghz_dialog_message_show_only_rx(subghz); - } - } else { - dialog_message_show_storage_error( - subghz->dialogs, "Error in protocol\nparameters\ndescription"); - } - } - if(!ret) { - subghz_transmitter_free(subghz->txrx->transmitter); - if(subghz->txrx->txrx_state != SubGhzTxRxStateSleep) { - subghz_idle(subghz); - } - } - - } while(false); - furi_string_free(temp_str); - return ret; -} + switch(subghz_txrx_tx_start(subghz->txrx, flipper_format)) { + case SubGhzTxRxStartTxStateErrorParserOthers: + dialog_message_show_storage_error( + subghz->dialogs, "Error in protocol\nparameters\ndescription"); + break; + case SubGhzTxRxStartTxStateErrorOnlyRx: + subghz_dialog_message_show_only_rx(subghz); + break; -void subghz_tx_stop(SubGhz* subghz) { - furi_assert(subghz); - furi_assert(subghz->txrx->txrx_state == SubGhzTxRxStateTx); - //Stop TX - furi_hal_subghz_stop_async_tx(); - subghz_transmitter_stop(subghz->txrx->transmitter); - subghz_transmitter_free(subghz->txrx->transmitter); - - //if protocol dynamic then we save the last upload - if((subghz->txrx->decoder_result->protocol->type == SubGhzProtocolTypeDynamic) && - (subghz_path_is_file(subghz->file_path))) { - subghz_save_protocol_to_file( - subghz, subghz->txrx->fff_data, furi_string_get_cstr(subghz->file_path)); + default: + return true; + break; } - subghz_idle(subghz); - subghz_speaker_off(subghz); - notification_message(subghz->notifications, &sequence_reset_red); + return false; } void subghz_dialog_message_show_only_rx(SubGhz* subghz) { @@ -254,11 +82,11 @@ bool subghz_key_load(SubGhz* subghz, const char* file_path, bool show_dialog) { Storage* storage = furi_record_open(RECORD_STORAGE); FlipperFormat* fff_data_file = flipper_format_file_alloc(storage); - Stream* fff_data_stream = flipper_format_get_raw_stream(subghz->txrx->fff_data); + Stream* fff_data_stream = + flipper_format_get_raw_stream(subghz_txrx_get_fff_data(subghz->txrx)); SubGhzLoadKeyState load_key_state = SubGhzLoadKeyStateParseErr; - FuriString* temp_str; - temp_str = furi_string_alloc(); + FuriString* temp_str = furi_string_alloc(); uint32_t temp_data32; do { @@ -281,6 +109,7 @@ bool subghz_key_load(SubGhz* subghz, const char* file_path, bool show_dialog) { break; } + //Load frequency if(!flipper_format_read_uint32(fff_data_file, "Frequency", &temp_data32, 1)) { FURI_LOG_E(TAG, "Missing Frequency"); break; @@ -291,58 +120,61 @@ bool subghz_key_load(SubGhz* subghz, const char* file_path, bool show_dialog) { break; } - subghz->txrx->preset->frequency = temp_data32; - + //Load preset if(!flipper_format_read_string(fff_data_file, "Preset", temp_str)) { FURI_LOG_E(TAG, "Missing Preset"); break; } - if(!subghz_set_preset(subghz, furi_string_get_cstr(temp_str))) { + furi_string_set_str( + temp_str, subghz_txrx_get_preset_name(subghz->txrx, furi_string_get_cstr(temp_str))); + if(!strcmp(furi_string_get_cstr(temp_str), "")) { break; } + SubGhzSetting* setting = subghz_txrx_get_setting(subghz->txrx); - if(!strcmp(furi_string_get_cstr(temp_str), "FuriHalSubGhzPresetCustom")) { + if(!strcmp(furi_string_get_cstr(temp_str), "CUSTOM")) { //Todo add Custom_preset_module //delete preset if it already exists - subghz_setting_delete_custom_preset( - subghz->setting, furi_string_get_cstr(subghz->txrx->preset->name)); + subghz_setting_delete_custom_preset(setting, furi_string_get_cstr(temp_str)); //load custom preset from file if(!subghz_setting_load_custom_preset( - subghz->setting, - furi_string_get_cstr(subghz->txrx->preset->name), - fff_data_file)) { + setting, furi_string_get_cstr(temp_str), fff_data_file)) { FURI_LOG_E(TAG, "Missing Custom preset"); break; } } - size_t preset_index = subghz_setting_get_inx_preset_by_name( - subghz->setting, furi_string_get_cstr(subghz->txrx->preset->name)); - subghz_preset_init( - subghz, - furi_string_get_cstr(subghz->txrx->preset->name), - subghz->txrx->preset->frequency, - subghz_setting_get_preset_data(subghz->setting, preset_index), - subghz_setting_get_preset_data_size(subghz->setting, preset_index)); - + size_t preset_index = + subghz_setting_get_inx_preset_by_name(setting, furi_string_get_cstr(temp_str)); + subghz_txrx_set_preset( + subghz->txrx, + furi_string_get_cstr(temp_str), + temp_data32, + subghz_setting_get_preset_data(setting, preset_index), + subghz_setting_get_preset_data_size(setting, preset_index)); + + //Load protocol if(!flipper_format_read_string(fff_data_file, "Protocol", temp_str)) { FURI_LOG_E(TAG, "Missing Protocol"); break; } + + FlipperFormat* fff_data = subghz_txrx_get_fff_data(subghz->txrx); if(!strcmp(furi_string_get_cstr(temp_str), "RAW")) { //if RAW - subghz_protocol_raw_gen_fff_data(subghz->txrx->fff_data, file_path); + subghz->load_type_file = SubGhzLoadTypeFileRaw; + subghz_protocol_raw_gen_fff_data(fff_data, file_path); } else { + subghz->load_type_file = SubGhzLoadTypeFileKey; stream_copy_full( flipper_format_get_raw_stream(fff_data_file), - flipper_format_get_raw_stream(subghz->txrx->fff_data)); + flipper_format_get_raw_stream(fff_data)); } - subghz->txrx->decoder_result = subghz_receiver_search_decoder_base_by_name( - subghz->txrx->receiver, furi_string_get_cstr(temp_str)); - if(subghz->txrx->decoder_result) { + if(subghz_txrx_load_decoder_by_name_protocol( + subghz->txrx, furi_string_get_cstr(temp_str))) { SubGhzProtocolStatus status = subghz_protocol_decoder_base_deserialize( - subghz->txrx->decoder_result, subghz->txrx->fff_data); + subghz_txrx_get_decoder(subghz->txrx), fff_data); if(status != SubGhzProtocolStatusOk) { load_key_state = SubGhzLoadKeyStateProtocolDescriptionErr; break; @@ -381,17 +213,18 @@ bool subghz_key_load(SubGhz* subghz, const char* file_path, bool show_dialog) { } } +SubGhzLoadTypeFile subghz_get_load_type_file(SubGhz* subghz) { + furi_assert(subghz); + return subghz->load_type_file; +} + bool subghz_get_next_name_file(SubGhz* subghz, uint8_t max_len) { furi_assert(subghz); Storage* storage = furi_record_open(RECORD_STORAGE); - FuriString* temp_str; - FuriString* file_name; - FuriString* file_path; - - temp_str = furi_string_alloc(); - file_name = furi_string_alloc(); - file_path = furi_string_alloc(); + FuriString* temp_str = furi_string_alloc(); + FuriString* file_name = furi_string_alloc(); + FuriString* file_path = furi_string_alloc(); bool res = false; @@ -438,8 +271,7 @@ bool subghz_save_protocol_to_file( Stream* flipper_format_stream = flipper_format_get_raw_stream(flipper_format); bool saved = false; - FuriString* file_dir; - file_dir = furi_string_alloc(); + FuriString* file_dir = furi_string_alloc(); path_extract_dirname(dev_file_name, file_dir); do { @@ -467,11 +299,21 @@ bool subghz_save_protocol_to_file( return saved; } +void subghz_save_to_file(void* context) { + furi_assert(context); + SubGhz* subghz = context; + if(subghz_path_is_file(subghz->file_path)) { + subghz_save_protocol_to_file( + subghz, + subghz_txrx_get_fff_data(subghz->txrx), + furi_string_get_cstr(subghz->file_path)); + } +} + bool subghz_load_protocol_from_file(SubGhz* subghz) { furi_assert(subghz); - FuriString* file_path; - file_path = furi_string_alloc(); + FuriString* file_path = furi_string_alloc(); DialogsFileBrowserOptions browser_options; dialog_file_browser_set_basic_options(&browser_options, SUBGHZ_APP_EXTENSION, &I_sub1_10px); @@ -551,92 +393,27 @@ bool subghz_path_is_file(FuriString* path) { return furi_string_end_with(path, SUBGHZ_APP_EXTENSION); } -uint32_t subghz_random_serial(void) { - return (uint32_t)rand(); -} - -void subghz_hopper_update(SubGhz* subghz) { +void subghz_lock(SubGhz* subghz) { furi_assert(subghz); - - switch(subghz->txrx->hopper_state) { - case SubGhzHopperStateOFF: - case SubGhzHopperStatePause: - return; - case SubGhzHopperStateRSSITimeOut: - if(subghz->txrx->hopper_timeout != 0) { - subghz->txrx->hopper_timeout--; - return; - } - break; - default: - break; - } - float rssi = -127.0f; - if(subghz->txrx->hopper_state != SubGhzHopperStateRSSITimeOut) { - // See RSSI Calculation timings in CC1101 17.3 RSSI - rssi = furi_hal_subghz_get_rssi(); - - // Stay if RSSI is high enough - if(rssi > -90.0f) { - subghz->txrx->hopper_timeout = 10; - subghz->txrx->hopper_state = SubGhzHopperStateRSSITimeOut; - return; - } - } else { - subghz->txrx->hopper_state = SubGhzHopperStateRunnig; - } - // Select next frequency - if(subghz->txrx->hopper_idx_frequency < - subghz_setting_get_hopper_frequency_count(subghz->setting) - 1) { - subghz->txrx->hopper_idx_frequency++; - } else { - subghz->txrx->hopper_idx_frequency = 0; - } - - if(subghz->txrx->txrx_state == SubGhzTxRxStateRx) { - subghz_rx_end(subghz); - }; - if(subghz->txrx->txrx_state == SubGhzTxRxStateIDLE) { - subghz_receiver_reset(subghz->txrx->receiver); - subghz->txrx->preset->frequency = subghz_setting_get_hopper_frequency( - subghz->setting, subghz->txrx->hopper_idx_frequency); - subghz_rx(subghz, subghz->txrx->preset->frequency); - } + subghz->lock = SubGhzLockOn; } -void subghz_speaker_on(SubGhz* subghz) { - if(subghz->txrx->speaker_state == SubGhzSpeakerStateEnable) { - if(furi_hal_speaker_acquire(30)) { - furi_hal_subghz_set_async_mirror_pin(&gpio_speaker); - } else { - subghz->txrx->speaker_state = SubGhzSpeakerStateDisable; - } - } +void subghz_unlock(SubGhz* subghz) { + furi_assert(subghz); + subghz->lock = SubGhzLockOff; } -void subghz_speaker_off(SubGhz* subghz) { - if(subghz->txrx->speaker_state != SubGhzSpeakerStateDisable) { - if(furi_hal_speaker_is_mine()) { - furi_hal_subghz_set_async_mirror_pin(NULL); - furi_hal_speaker_release(); - if(subghz->txrx->speaker_state == SubGhzSpeakerStateShutdown) - subghz->txrx->speaker_state = SubGhzSpeakerStateDisable; - } - } +bool subghz_is_locked(SubGhz* subghz) { + furi_assert(subghz); + return (subghz->lock == SubGhzLockOn); } -void subghz_speaker_mute(SubGhz* subghz) { - if(subghz->txrx->speaker_state == SubGhzSpeakerStateEnable) { - if(furi_hal_speaker_is_mine()) { - furi_hal_subghz_set_async_mirror_pin(NULL); - } - } +void subghz_rx_key_state_set(SubGhz* subghz, SubGhzRxKeyState state) { + furi_assert(subghz); + subghz->rx_key_state = state; } -void subghz_speaker_unmute(SubGhz* subghz) { - if(subghz->txrx->speaker_state == SubGhzSpeakerStateEnable) { - if(furi_hal_speaker_is_mine()) { - furi_hal_subghz_set_async_mirror_pin(&gpio_speaker); - } - } +SubGhzRxKeyState subghz_rx_key_state_get(SubGhz* subghz) { + furi_assert(subghz); + return subghz->rx_key_state; } diff --git a/applications/main/subghz/subghz_i.h b/applications/main/subghz/subghz_i.h index 65480c6fd00..fc3404c07e7 100644 --- a/applications/main/subghz/subghz_i.h +++ b/applications/main/subghz/subghz_i.h @@ -25,10 +25,6 @@ #include #include -#include -#include -#include -#include #include "subghz_history.h" @@ -37,33 +33,11 @@ #include "rpc/rpc_app.h" -#define SUBGHZ_MAX_LEN_NAME 64 - -struct SubGhzTxRx { - SubGhzWorker* worker; - - SubGhzEnvironment* environment; - SubGhzReceiver* receiver; - SubGhzTransmitter* transmitter; - SubGhzProtocolFlag filter; - SubGhzProtocolDecoderBase* decoder_result; - FlipperFormat* fff_data; - - SubGhzRadioPreset* preset; - SubGhzHistory* history; - uint16_t idx_menu_chosen; - SubGhzTxRxState txrx_state; - SubGhzHopperState hopper_state; - SubGhzSpeakerState speaker_state; - uint8_t hopper_timeout; - uint8_t hopper_idx_frequency; - SubGhzRxKeyState rx_key_state; +#include "helpers/subghz_threshold_rssi.h" - float raw_threshold_rssi; - uint8_t raw_threshold_rssi_low_count; -}; +#include "helpers/subghz_txrx.h" -typedef struct SubGhzTxRx SubGhzTxRx; +#define SUBGHZ_MAX_LEN_NAME 64 struct SubGhz { Gui* gui; @@ -93,47 +67,43 @@ struct SubGhz { SubGhzTestStatic* subghz_test_static; SubGhzTestCarrier* subghz_test_carrier; SubGhzTestPacket* subghz_test_packet; + + SubGhzProtocolFlag filter; FuriString* error_str; - SubGhzSetting* setting; SubGhzLock lock; - + SubGhzThresholdRssi* threshold_rssi; + SubGhzRxKeyState rx_key_state; + SubGhzHistory* history; + uint16_t idx_menu_chosen; + SubGhzLoadTypeFile load_type_file; void* rpc_ctx; }; -void subghz_preset_init( - void* context, - const char* preset_name, - uint32_t frequency, - uint8_t* preset_data, - size_t preset_data_size); -bool subghz_set_preset(SubGhz* subghz, const char* preset); -void subghz_get_frequency_modulation(SubGhz* subghz, FuriString* frequency, FuriString* modulation); -void subghz_begin(SubGhz* subghz, uint8_t* preset_data); -uint32_t subghz_rx(SubGhz* subghz, uint32_t frequency); -void subghz_rx_end(SubGhz* subghz); -void subghz_sleep(SubGhz* subghz); - -void subghz_blink_start(SubGhz* instance); -void subghz_blink_stop(SubGhz* instance); +void subghz_set_default_preset(SubGhz* subghz); +void subghz_blink_start(SubGhz* subghz); +void subghz_blink_stop(SubGhz* subghz); bool subghz_tx_start(SubGhz* subghz, FlipperFormat* flipper_format); -void subghz_tx_stop(SubGhz* subghz); void subghz_dialog_message_show_only_rx(SubGhz* subghz); + bool subghz_key_load(SubGhz* subghz, const char* file_path, bool show_dialog); bool subghz_get_next_name_file(SubGhz* subghz, uint8_t max_len); bool subghz_save_protocol_to_file( SubGhz* subghz, FlipperFormat* flipper_format, const char* dev_file_name); +void subghz_save_to_file(void* context); bool subghz_load_protocol_from_file(SubGhz* subghz); bool subghz_rename_file(SubGhz* subghz); bool subghz_file_available(SubGhz* subghz); bool subghz_delete_file(SubGhz* subghz); void subghz_file_name_clear(SubGhz* subghz); bool subghz_path_is_file(FuriString* path); -uint32_t subghz_random_serial(void); -void subghz_hopper_update(SubGhz* subghz); -void subghz_speaker_on(SubGhz* subghz); -void subghz_speaker_off(SubGhz* subghz); -void subghz_speaker_mute(SubGhz* subghz); -void subghz_speaker_unmute(SubGhz* subghz); +SubGhzLoadTypeFile subghz_get_load_type_file(SubGhz* subghz); + +void subghz_lock(SubGhz* subghz); +void subghz_unlock(SubGhz* subghz); +bool subghz_is_locked(SubGhz* subghz); + +void subghz_rx_key_state_set(SubGhz* subghz, SubGhzRxKeyState state); +SubGhzRxKeyState subghz_rx_key_state_get(SubGhz* subghz); diff --git a/applications/main/subghz/views/receiver.c b/applications/main/subghz/views/receiver.c index acc39e25810..f84ddfed086 100644 --- a/applications/main/subghz/views/receiver.c +++ b/applications/main/subghz/views/receiver.c @@ -12,7 +12,7 @@ #define MENU_ITEMS 4u #define UNLOCK_CNT 3 -#define SUBGHZ_RAW_TRESHOLD_MIN -90.0f +#define SUBGHZ_RAW_THRESHOLD_MIN -90.0f typedef struct { FuriString* item_str; @@ -44,7 +44,7 @@ typedef enum { } SubGhzViewReceiverBarShow; struct SubGhzViewReceiver { - SubGhzLock lock; + bool lock; uint8_t lock_count; FuriTimer* timer; View* view; @@ -70,20 +70,21 @@ void subghz_receiver_rssi(SubGhzViewReceiver* instance, float rssi) { instance->view, SubGhzViewReceiverModel * model, { - if(rssi < SUBGHZ_RAW_TRESHOLD_MIN) { + if(rssi < SUBGHZ_RAW_THRESHOLD_MIN) { model->u_rssi = 0; } else { - model->u_rssi = (uint8_t)(rssi - SUBGHZ_RAW_TRESHOLD_MIN); + model->u_rssi = (uint8_t)(rssi - SUBGHZ_RAW_THRESHOLD_MIN); } }, true); } -void subghz_view_receiver_set_lock(SubGhzViewReceiver* subghz_receiver, SubGhzLock lock) { +void subghz_view_receiver_set_lock(SubGhzViewReceiver* subghz_receiver, bool lock) { furi_assert(subghz_receiver); subghz_receiver->lock_count = 0; - if(lock == SubGhzLockOn) { - subghz_receiver->lock = lock; + + if(lock == true) { + subghz_receiver->lock = true; with_view_model( subghz_receiver->view, SubGhzViewReceiverModel * model, @@ -280,7 +281,7 @@ static void subghz_view_receiver_timer_callback(void* context) { subghz_receiver->callback( SubGhzCustomEventViewReceiverOffDisplay, subghz_receiver->context); } else { - subghz_receiver->lock = SubGhzLockOff; + subghz_receiver->lock = false; subghz_receiver->callback(SubGhzCustomEventViewReceiverUnlock, subghz_receiver->context); } subghz_receiver->lock_count = 0; @@ -290,7 +291,7 @@ bool subghz_view_receiver_input(InputEvent* event, void* context) { furi_assert(context); SubGhzViewReceiver* subghz_receiver = context; - if(subghz_receiver->lock == SubGhzLockOn) { + if(subghz_receiver->lock == true) { with_view_model( subghz_receiver->view, SubGhzViewReceiverModel * model, @@ -310,7 +311,7 @@ bool subghz_view_receiver_input(InputEvent* event, void* context) { SubGhzViewReceiverModel * model, { model->bar_show = SubGhzViewReceiverBarShowUnlock; }, true); - //subghz_receiver->lock = SubGhzLockOff; + //subghz_receiver->lock = false; furi_timer_start(subghz_receiver->timer, pdMS_TO_TICKS(650)); } @@ -394,7 +395,7 @@ SubGhzViewReceiver* subghz_view_receiver_alloc() { // View allocation and configuration subghz_receiver->view = view_alloc(); - subghz_receiver->lock = SubGhzLockOff; + subghz_receiver->lock = false; subghz_receiver->lock_count = 0; view_allocate_model( subghz_receiver->view, ViewModelTypeLocking, sizeof(SubGhzViewReceiverModel)); diff --git a/applications/main/subghz/views/receiver.h b/applications/main/subghz/views/receiver.h index 9b12ccfee89..5119105e93b 100644 --- a/applications/main/subghz/views/receiver.h +++ b/applications/main/subghz/views/receiver.h @@ -10,7 +10,7 @@ typedef void (*SubGhzViewReceiverCallback)(SubGhzCustomEvent event, void* contex void subghz_receiver_rssi(SubGhzViewReceiver* instance, float rssi); -void subghz_view_receiver_set_lock(SubGhzViewReceiver* subghz_receiver, SubGhzLock keyboard); +void subghz_view_receiver_set_lock(SubGhzViewReceiver* subghz_receiver, bool keyboard); void subghz_view_receiver_set_callback( SubGhzViewReceiver* subghz_receiver, diff --git a/applications/main/subghz/views/subghz_read_raw.c b/applications/main/subghz/views/subghz_read_raw.c index 87c8a308284..2ff598b6058 100644 --- a/applications/main/subghz/views/subghz_read_raw.c +++ b/applications/main/subghz/views/subghz_read_raw.c @@ -60,10 +60,10 @@ void subghz_read_raw_add_data_rssi(SubGhzReadRAW* instance, float rssi, bool tra furi_assert(instance); uint8_t u_rssi = 0; - if(rssi < SUBGHZ_RAW_TRESHOLD_MIN) { + if(rssi < SUBGHZ_RAW_THRESHOLD_MIN) { u_rssi = 0; } else { - u_rssi = (uint8_t)((rssi - SUBGHZ_RAW_TRESHOLD_MIN) / 2.7); + u_rssi = (uint8_t)((rssi - SUBGHZ_RAW_THRESHOLD_MIN) / 2.7); } with_view_model( @@ -261,9 +261,9 @@ void subghz_read_raw_draw_threshold_rssi(Canvas* canvas, SubGhzReadRAWModel* mod uint8_t x = 118; uint8_t y = 48; - if(model->raw_threshold_rssi > SUBGHZ_RAW_TRESHOLD_MIN) { + if(model->raw_threshold_rssi > SUBGHZ_RAW_THRESHOLD_MIN) { uint8_t x = 118; - y -= (uint8_t)((model->raw_threshold_rssi - SUBGHZ_RAW_TRESHOLD_MIN) / 2.7); + y -= (uint8_t)((model->raw_threshold_rssi - SUBGHZ_RAW_THRESHOLD_MIN) / 2.7); uint8_t width = 3; for(uint8_t i = 0; i < x; i += width * 2) { diff --git a/applications/main/subghz/views/subghz_read_raw.h b/applications/main/subghz/views/subghz_read_raw.h index bc871192395..31aa9db6fdd 100644 --- a/applications/main/subghz/views/subghz_read_raw.h +++ b/applications/main/subghz/views/subghz_read_raw.h @@ -3,7 +3,7 @@ #include #include "../helpers/subghz_custom_event.h" -#define SUBGHZ_RAW_TRESHOLD_MIN -90.0f +#define SUBGHZ_RAW_THRESHOLD_MIN -90.0f typedef struct SubGhzReadRAW SubGhzReadRAW; diff --git a/applications/main/subghz/views/transmitter.c b/applications/main/subghz/views/transmitter.c index 4a13460a375..86dc17a38f7 100644 --- a/applications/main/subghz/views/transmitter.c +++ b/applications/main/subghz/views/transmitter.c @@ -14,7 +14,7 @@ typedef struct { FuriString* frequency_str; FuriString* preset_str; FuriString* key_str; - uint8_t show_button; + bool show_button; } SubGhzViewTransmitterModel; void subghz_view_transmitter_set_callback( @@ -32,7 +32,7 @@ void subghz_view_transmitter_add_data_to_show( const char* key_str, const char* frequency_str, const char* preset_str, - uint8_t show_button) { + bool show_button) { furi_assert(subghz_transmitter); with_view_model( subghz_transmitter->view, @@ -104,7 +104,7 @@ bool subghz_view_transmitter_input(InputEvent* event, void* context) { furi_string_reset(model->frequency_str); furi_string_reset(model->preset_str); furi_string_reset(model->key_str); - model->show_button = 0; + model->show_button = false; }, false); return false; diff --git a/applications/main/subghz/views/transmitter.h b/applications/main/subghz/views/transmitter.h index 64bcbd1afae..06aae7c6bf7 100644 --- a/applications/main/subghz/views/transmitter.h +++ b/applications/main/subghz/views/transmitter.h @@ -23,4 +23,4 @@ void subghz_view_transmitter_add_data_to_show( const char* key_str, const char* frequency_str, const char* preset_str, - uint8_t show_button); + bool show_button); diff --git a/lib/subghz/environment.c b/lib/subghz/environment.c index b39b259d416..5ded243c416 100644 --- a/lib/subghz/environment.c +++ b/lib/subghz/environment.c @@ -16,6 +16,7 @@ SubGhzEnvironment* subghz_environment_alloc() { instance->protocol_registry = NULL; instance->came_atomo_rainbow_table_file_name = NULL; instance->nice_flor_s_rainbow_table_file_name = NULL; + instance->alutech_at_4n_rainbow_table_file_name = NULL; return instance; } @@ -26,6 +27,7 @@ void subghz_environment_free(SubGhzEnvironment* instance) { instance->protocol_registry = NULL; instance->came_atomo_rainbow_table_file_name = NULL; instance->nice_flor_s_rainbow_table_file_name = NULL; + instance->alutech_at_4n_rainbow_table_file_name = NULL; subghz_keystore_free(instance->keystore); free(instance); From a7d1ec03e878c062ba07132474720d0d54d26b97 Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Wed, 3 May 2023 20:48:13 -0700 Subject: [PATCH 540/824] [FL-3270] Loader refactoring, part 1 (#2593) * Loader: menu part * Settings: remove unused loader api * Desktop: get loader from record_open * CLI: remove unneeded loader api * gitignore: ignore .old files * Loader: now really a service * Loader: working service prototype * Loader: cli, system start hooks * CI/CD: make happy * Loader: autorun * Loader: lock and unlock * Loader: rearrange code * Gui, module menu: fix memleak * Updater test: add timeout * added update timeouts and max run duration * Github: revert updater test workflow changes * Loader: less missleading message in info cli command Co-authored-by: doomwastaken Co-authored-by: Aleksandr Kutuzov --- .gitignore | 1 + applications/services/cli/cli_commands.c | 2 - .../desktop/scenes/desktop_scene_main.c | 8 +- applications/services/gui/modules/menu.c | 2 + applications/services/loader/application.fam | 9 + applications/services/loader/loader.c | 553 +++++++----------- applications/services/loader/loader.h | 13 +- applications/services/loader/loader_cli.c | 117 ++++ applications/services/loader/loader_i.h | 79 ++- applications/services/loader/loader_menu.c | 187 ++++++ applications/services/loader/loader_menu.h | 30 + .../settings/system/system_settings.c | 3 - firmware/targets/f18/api_symbols.csv | 7 +- firmware/targets/f7/api_symbols.csv | 7 +- 14 files changed, 615 insertions(+), 403 deletions(-) create mode 100644 applications/services/loader/loader_cli.c create mode 100644 applications/services/loader/loader_menu.c create mode 100644 applications/services/loader/loader_menu.h diff --git a/.gitignore b/.gitignore index 89e129acea0..bf17a94e28d 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ *.swp *.swo *.gdb_history +*.old # LSP diff --git a/applications/services/cli/cli_commands.c b/applications/services/cli/cli_commands.c index 0f042f6c48d..3f94deebcfb 100644 --- a/applications/services/cli/cli_commands.c +++ b/applications/services/cli/cli_commands.c @@ -220,11 +220,9 @@ void cli_command_sysctl_debug(Cli* cli, FuriString* args, void* context) { UNUSED(context); if(!furi_string_cmp(args, "0")) { furi_hal_rtc_reset_flag(FuriHalRtcFlagDebug); - loader_update_menu(); printf("Debug disabled."); } else if(!furi_string_cmp(args, "1")) { furi_hal_rtc_set_flag(FuriHalRtcFlagDebug); - loader_update_menu(); printf("Debug enabled."); } else { cli_print_usage("sysctl debug", "<1|0>", furi_string_get_cstr(args)); diff --git a/applications/services/desktop/scenes/desktop_scene_main.c b/applications/services/desktop/scenes/desktop_scene_main.c index 4d1fa495052..053ac56f1e7 100644 --- a/applications/services/desktop/scenes/desktop_scene_main.c +++ b/applications/services/desktop/scenes/desktop_scene_main.c @@ -106,10 +106,12 @@ bool desktop_scene_main_on_event(void* context, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { switch(event.event) { - case DesktopMainEventOpenMenu: - loader_show_menu(); + case DesktopMainEventOpenMenu: { + Loader* loader = furi_record_open(RECORD_LOADER); + loader_show_menu(loader); + furi_record_close(RECORD_LOADER); consumed = true; - break; + } break; case DesktopMainEventOpenLockMenu: scene_manager_next_scene(desktop->scene_manager, DesktopSceneLockMenu); diff --git a/applications/services/gui/modules/menu.c b/applications/services/gui/modules/menu.c index 3e3b6c2e481..afae8b8fa24 100644 --- a/applications/services/gui/modules/menu.c +++ b/applications/services/gui/modules/menu.c @@ -154,6 +154,8 @@ Menu* menu_alloc() { void menu_free(Menu* menu) { furi_assert(menu); menu_reset(menu); + with_view_model( + menu->view, MenuModel * model, { MenuItemArray_clear(model->items); }, false); view_free(menu->view); free(menu); } diff --git a/applications/services/loader/application.fam b/applications/services/loader/application.fam index 49f3c414889..f4d006e076a 100644 --- a/applications/services/loader/application.fam +++ b/applications/services/loader/application.fam @@ -5,6 +5,7 @@ App( entry_point="loader_srv", cdefines=["SRV_LOADER"], requires=["gui"], + provides=["loader_start"], stack_size=2 * 1024, order=90, sdk_headers=[ @@ -12,3 +13,11 @@ App( "firmware_api/firmware_api.h", ], ) + +App( + appid="loader_start", + apptype=FlipperAppType.STARTUP, + entry_point="loader_on_system_start", + requires=["loader"], + order=90, +) diff --git a/applications/services/loader/loader.c b/applications/services/loader/loader.c index f83d47d63dd..be16e5091f1 100644 --- a/applications/services/loader/loader.c +++ b/applications/services/loader/loader.c @@ -1,76 +1,114 @@ -#include "applications.h" -#include -#include "loader/loader.h" +#include "loader.h" #include "loader_i.h" +#include "loader_menu.h" +#include +#include + +#define TAG "Loader" +#define LOADER_MAGIC_THREAD_VALUE 0xDEADBEEF +// api + +LoaderStatus loader_start(Loader* loader, const char* name, const char* args) { + LoaderMessage message; + LoaderMessageLoaderStatusResult result; + + message.type = LoaderMessageTypeStartByName; + message.start.name = name; + message.start.args = args; + message.api_lock = api_lock_alloc_locked(); + message.status_value = &result; + furi_message_queue_put(loader->queue, &message, FuriWaitForever); + api_lock_wait_unlock_and_free(message.api_lock); + return result.value; +} -#define TAG "LoaderSrv" - -#define LOADER_THREAD_FLAG_SHOW_MENU (1 << 0) -#define LOADER_THREAD_FLAG_ALL (LOADER_THREAD_FLAG_SHOW_MENU) - -static Loader* loader_instance = NULL; +bool loader_lock(Loader* loader) { + LoaderMessage message; + LoaderMessageBoolResult result; + message.type = LoaderMessageTypeLock; + message.api_lock = api_lock_alloc_locked(); + message.bool_value = &result; + furi_message_queue_put(loader->queue, &message, FuriWaitForever); + api_lock_wait_unlock_and_free(message.api_lock); + return result.value; +} -static bool - loader_start_application(const FlipperApplication* application, const char* arguments) { - loader_instance->application = application; +void loader_unlock(Loader* loader) { + LoaderMessage message; + message.type = LoaderMessageTypeUnlock; + furi_message_queue_put(loader->queue, &message, FuriWaitForever); +} - furi_assert(loader_instance->application_arguments == NULL); - if(arguments && strlen(arguments) > 0) { - loader_instance->application_arguments = strdup(arguments); - } +bool loader_is_locked(Loader* loader) { + LoaderMessage message; + LoaderMessageBoolResult result; + message.type = LoaderMessageTypeIsLocked; + message.api_lock = api_lock_alloc_locked(); + message.bool_value = &result; + furi_message_queue_put(loader->queue, &message, FuriWaitForever); + api_lock_wait_unlock_and_free(message.api_lock); + return result.value; +} - FURI_LOG_I(TAG, "Starting: %s", loader_instance->application->name); +void loader_show_menu(Loader* loader) { + LoaderMessage message; + message.type = LoaderMessageTypeShowMenu; + furi_message_queue_put(loader->queue, &message, FuriWaitForever); +} - FuriHalRtcHeapTrackMode mode = furi_hal_rtc_get_heap_track_mode(); - if(mode > FuriHalRtcHeapTrackModeNone) { - furi_thread_enable_heap_trace(loader_instance->application_thread); - } else { - furi_thread_disable_heap_trace(loader_instance->application_thread); - } +FuriPubSub* loader_get_pubsub(Loader* loader) { + furi_assert(loader); + // it's safe to return pubsub without locking + // because it's never freed and loader is never exited + // also the loader instance cannot be obtained until the pubsub is created + return loader->pubsub; +} - furi_thread_set_name(loader_instance->application_thread, loader_instance->application->name); - furi_thread_set_appid( - loader_instance->application_thread, loader_instance->application->appid); - furi_thread_set_stack_size( - loader_instance->application_thread, loader_instance->application->stack_size); - furi_thread_set_context( - loader_instance->application_thread, loader_instance->application_arguments); - furi_thread_set_callback( - loader_instance->application_thread, loader_instance->application->app); +// callbacks - furi_thread_start(loader_instance->application_thread); +static void loader_menu_closed_callback(void* context) { + Loader* loader = context; + LoaderMessage message; + message.type = LoaderMessageTypeMenuClosed; + furi_message_queue_put(loader->queue, &message, FuriWaitForever); +} - return true; +static void loader_menu_click_callback(const char* name, void* context) { + Loader* loader = context; + loader_start(loader, name, NULL); } -static void loader_menu_callback(void* _ctx, uint32_t index) { - UNUSED(index); - const FlipperApplication* application = _ctx; +static void loader_thread_state_callback(FuriThreadState thread_state, void* context) { + furi_assert(context); - furi_assert(application->app); - furi_assert(application->name); + Loader* loader = context; + LoaderEvent event; - if(!loader_lock(loader_instance)) { - FURI_LOG_E(TAG, "Loader is locked"); - return; - } + if(thread_state == FuriThreadStateRunning) { + event.type = LoaderEventTypeApplicationStarted; + furi_pubsub_publish(loader->pubsub, &event); + } else if(thread_state == FuriThreadStateStopped) { + LoaderMessage message; + message.type = LoaderMessageTypeAppClosed; + furi_message_queue_put(loader->queue, &message, FuriWaitForever); - loader_start_application(application, NULL); + event.type = LoaderEventTypeApplicationStopped; + furi_pubsub_publish(loader->pubsub, &event); + } } -static void loader_submenu_callback(void* context, uint32_t index) { - UNUSED(index); - uint32_t view_id = (uint32_t)context; - view_dispatcher_switch_to_view(loader_instance->view_dispatcher, view_id); -} +// implementation -static void loader_cli_print_usage() { - printf("Usage:\r\n"); - printf("loader \r\n"); - printf("Cmd list:\r\n"); - printf("\tlist\t - List available applications\r\n"); - printf("\topen \t - Open application by name\r\n"); - printf("\tinfo\t - Show loader state\r\n"); +static Loader* loader_alloc() { + Loader* loader = malloc(sizeof(Loader)); + loader->pubsub = furi_pubsub_alloc(); + loader->queue = furi_message_queue_alloc(1, sizeof(LoaderMessage)); + loader->loader_menu = NULL; + loader->app.args = NULL; + loader->app.name = NULL; + loader->app.thread = NULL; + loader->app.insomniac = false; + return loader; } static FlipperApplication const* loader_find_application_by_name_in_list( @@ -85,7 +123,7 @@ static FlipperApplication const* loader_find_application_by_name_in_list( return NULL; } -const FlipperApplication* loader_find_application_by_name(const char* name) { +static const FlipperApplication* loader_find_application_by_name(const char* name) { const FlipperApplication* application = NULL; application = loader_find_application_by_name_in_list(name, FLIPPER_APPS, FLIPPER_APPS_COUNT); if(!application) { @@ -100,346 +138,167 @@ const FlipperApplication* loader_find_application_by_name(const char* name) { return application; } -static void loader_cli_open(Cli* cli, FuriString* args, Loader* instance) { - UNUSED(cli); - if(loader_is_locked(instance)) { - if(instance->application) { - furi_assert(instance->application->name); - printf("Can't start, %s application is running", instance->application->name); - } else { - printf("Can't start, furi application is running"); - } - return; - } - - FuriString* application_name; - application_name = furi_string_alloc(); - - do { - if(!args_read_probably_quoted_string_and_trim(args, application_name)) { - printf("No application provided\r\n"); - break; - } +static void + loader_start_internal_app(Loader* loader, const FlipperApplication* app, const char* args) { + FURI_LOG_I(TAG, "Starting %s", app->name); - const FlipperApplication* application = - loader_find_application_by_name(furi_string_get_cstr(application_name)); - if(!application) { - printf("%s doesn't exists\r\n", furi_string_get_cstr(application_name)); - break; - } + // store args + furi_assert(loader->app.args == NULL); + if(args && strlen(args) > 0) { + loader->app.args = strdup(args); + } - furi_string_trim(args); - if(!loader_start_application(application, furi_string_get_cstr(args))) { - printf("Can't start, furi application is running"); - return; - } else { - // We must to increment lock counter to keep balance - // TODO: rewrite whole thing, it's complex as hell - FURI_CRITICAL_ENTER(); - instance->lock_count++; - FURI_CRITICAL_EXIT(); - } - } while(false); + // store name + furi_assert(loader->app.name == NULL); + loader->app.name = strdup(app->name); - furi_string_free(application_name); -} + // setup app thread + loader->app.thread = + furi_thread_alloc_ex(app->name, app->stack_size, app->app, loader->app.args); + furi_thread_set_appid(loader->app.thread, app->appid); -static void loader_cli_list(Cli* cli, FuriString* args, Loader* instance) { - UNUSED(cli); - UNUSED(args); - UNUSED(instance); - printf("Applications:\r\n"); - for(size_t i = 0; i < FLIPPER_APPS_COUNT; i++) { - printf("\t%s\r\n", FLIPPER_APPS[i].name); + // setup heap trace + FuriHalRtcHeapTrackMode mode = furi_hal_rtc_get_heap_track_mode(); + if(mode > FuriHalRtcHeapTrackModeNone) { + furi_thread_enable_heap_trace(loader->app.thread); + } else { + furi_thread_disable_heap_trace(loader->app.thread); } -} -static void loader_cli_info(Cli* cli, FuriString* args, Loader* instance) { - UNUSED(cli); - UNUSED(args); - if(!loader_is_locked(instance)) { - printf("No application is running\r\n"); + // setup insomnia + if(!(app->flags & FlipperApplicationFlagInsomniaSafe)) { + furi_hal_power_insomnia_enter(); + loader->app.insomniac = true; } else { - printf("Running application: "); - if(instance->application) { - furi_assert(instance->application->name); - printf("%s\r\n", instance->application->name); - } else { - printf("unknown\r\n"); - } + loader->app.insomniac = false; } -} -static void loader_cli(Cli* cli, FuriString* args, void* _ctx) { - furi_assert(_ctx); - Loader* instance = _ctx; + // setup app thread callbacks + furi_thread_set_state_context(loader->app.thread, loader); + furi_thread_set_state_callback(loader->app.thread, loader_thread_state_callback); - FuriString* cmd; - cmd = furi_string_alloc(); - - do { - if(!args_read_string_and_trim(args, cmd)) { - loader_cli_print_usage(); - break; - } - - if(furi_string_cmp_str(cmd, "list") == 0) { - loader_cli_list(cli, args, instance); - break; - } - - if(furi_string_cmp_str(cmd, "open") == 0) { - loader_cli_open(cli, args, instance); - break; - } - - if(furi_string_cmp_str(cmd, "info") == 0) { - loader_cli_info(cli, args, instance); - break; - } - - loader_cli_print_usage(); - } while(false); - - furi_string_free(cmd); + // start app thread + furi_thread_start(loader->app.thread); } -LoaderStatus loader_start(Loader* instance, const char* name, const char* args) { - UNUSED(instance); - furi_assert(name); +// process messages - const FlipperApplication* application = loader_find_application_by_name(name); - - if(!application) { - FURI_LOG_E(TAG, "Can't find application with name %s", name); - return LoaderStatusErrorUnknownApp; +static void loader_do_menu_show(Loader* loader) { + if(!loader->loader_menu) { + loader->loader_menu = loader_menu_alloc(); + loader_menu_set_closed_callback(loader->loader_menu, loader_menu_closed_callback, loader); + loader_menu_set_click_callback(loader->loader_menu, loader_menu_click_callback, loader); + loader_menu_start(loader->loader_menu); } - - if(!loader_lock(loader_instance)) { - FURI_LOG_E(TAG, "Loader is locked"); - return LoaderStatusErrorAppStarted; - } - - if(!loader_start_application(application, args)) { - return LoaderStatusErrorInternal; - } - - return LoaderStatusOk; } -bool loader_lock(Loader* instance) { - FURI_CRITICAL_ENTER(); - bool result = false; - if(instance->lock_count == 0) { - instance->lock_count++; - result = true; +static void loader_do_menu_closed(Loader* loader) { + if(loader->loader_menu) { + loader_menu_stop(loader->loader_menu); + loader_menu_free(loader->loader_menu); + loader->loader_menu = NULL; } - FURI_CRITICAL_EXIT(); - return result; } -void loader_unlock(Loader* instance) { - FURI_CRITICAL_ENTER(); - if(instance->lock_count > 0) instance->lock_count--; - FURI_CRITICAL_EXIT(); +static bool loader_do_is_locked(Loader* loader) { + return loader->app.thread != NULL; } -bool loader_is_locked(const Loader* instance) { - return instance->lock_count > 0; -} - -static void loader_thread_state_callback(FuriThreadState thread_state, void* context) { - furi_assert(context); - - Loader* instance = context; - LoaderEvent event; - - if(thread_state == FuriThreadStateRunning) { - event.type = LoaderEventTypeApplicationStarted; - furi_pubsub_publish(loader_instance->pubsub, &event); - - if(!(loader_instance->application->flags & FlipperApplicationFlagInsomniaSafe)) { - furi_hal_power_insomnia_enter(); - } - } else if(thread_state == FuriThreadStateStopped) { - FURI_LOG_I(TAG, "Application stopped. Free heap: %zu", memmgr_get_free_heap()); - - if(loader_instance->application_arguments) { - free(loader_instance->application_arguments); - loader_instance->application_arguments = NULL; - } - - if(!(loader_instance->application->flags & FlipperApplicationFlagInsomniaSafe)) { - furi_hal_power_insomnia_exit(); - } - loader_unlock(instance); - - event.type = LoaderEventTypeApplicationStopped; - furi_pubsub_publish(loader_instance->pubsub, &event); +static LoaderStatus loader_do_start_by_name(Loader* loader, const char* name, const char* args) { + if(loader_do_is_locked(loader)) { + return LoaderStatusErrorAppStarted; } -} -static uint32_t loader_hide_menu(void* context) { - UNUSED(context); - return VIEW_NONE; -} + const FlipperApplication* app = loader_find_application_by_name(name); -static uint32_t loader_back_to_primary_menu(void* context) { - furi_assert(context); - Submenu* submenu = context; - submenu_set_selected_item(submenu, 0); - return LoaderMenuViewPrimary; -} + if(!app) { + return LoaderStatusErrorUnknownApp; + } -static Loader* loader_alloc() { - Loader* instance = malloc(sizeof(Loader)); - - instance->application_thread = furi_thread_alloc(); - - furi_thread_set_state_context(instance->application_thread, instance); - furi_thread_set_state_callback(instance->application_thread, loader_thread_state_callback); - - instance->pubsub = furi_pubsub_alloc(); - -#ifdef SRV_CLI - instance->cli = furi_record_open(RECORD_CLI); - cli_add_command( - instance->cli, RECORD_LOADER, CliCommandFlagParallelSafe, loader_cli, instance); -#else - UNUSED(loader_cli); -#endif - - instance->loader_thread = furi_thread_get_current_id(); - - // Gui - instance->gui = furi_record_open(RECORD_GUI); - instance->view_dispatcher = view_dispatcher_alloc(); - view_dispatcher_attach_to_gui( - instance->view_dispatcher, instance->gui, ViewDispatcherTypeFullscreen); - // Primary menu - instance->primary_menu = menu_alloc(); - view_set_previous_callback(menu_get_view(instance->primary_menu), loader_hide_menu); - view_dispatcher_add_view( - instance->view_dispatcher, LoaderMenuViewPrimary, menu_get_view(instance->primary_menu)); - // Settings menu - instance->settings_menu = submenu_alloc(); - view_set_context(submenu_get_view(instance->settings_menu), instance->settings_menu); - view_set_previous_callback( - submenu_get_view(instance->settings_menu), loader_back_to_primary_menu); - view_dispatcher_add_view( - instance->view_dispatcher, - LoaderMenuViewSettings, - submenu_get_view(instance->settings_menu)); - - view_dispatcher_enable_queue(instance->view_dispatcher); - - return instance; + loader_start_internal_app(loader, app, args); + return LoaderStatusOk; } -static void loader_free(Loader* instance) { - furi_assert(instance); - - if(instance->cli) { - furi_record_close(RECORD_CLI); +static bool loader_do_lock(Loader* loader) { + if(loader->app.thread) { + return false; } - furi_pubsub_free(instance->pubsub); - - furi_thread_free(instance->application_thread); - - menu_free(loader_instance->primary_menu); - view_dispatcher_remove_view(loader_instance->view_dispatcher, LoaderMenuViewPrimary); - submenu_free(loader_instance->settings_menu); - view_dispatcher_remove_view(loader_instance->view_dispatcher, LoaderMenuViewSettings); - view_dispatcher_free(loader_instance->view_dispatcher); - - furi_record_close(RECORD_GUI); + loader->app.thread = (FuriThread*)LOADER_MAGIC_THREAD_VALUE; + return true; +} - free(instance); - instance = NULL; +static void loader_do_unlock(Loader* loader) { + furi_assert(loader->app.thread == (FuriThread*)LOADER_MAGIC_THREAD_VALUE); + loader->app.thread = NULL; } -static void loader_build_menu() { - FURI_LOG_I(TAG, "Building main menu"); - size_t i; - for(i = 0; i < FLIPPER_APPS_COUNT; i++) { - menu_add_item( - loader_instance->primary_menu, - FLIPPER_APPS[i].name, - FLIPPER_APPS[i].icon, - i, - loader_menu_callback, - (void*)&FLIPPER_APPS[i]); +static void loader_do_app_closed(Loader* loader) { + furi_assert(loader->app.thread); + FURI_LOG_I(TAG, "Application stopped. Free heap: %zu", memmgr_get_free_heap()); + if(loader->app.args) { + free(loader->app.args); + loader->app.args = NULL; } - menu_add_item( - loader_instance->primary_menu, - "Settings", - &A_Settings_14, - i++, - loader_submenu_callback, - (void*)LoaderMenuViewSettings); -} -static void loader_build_submenu() { - FURI_LOG_I(TAG, "Building settings menu"); - for(size_t i = 0; i < FLIPPER_SETTINGS_APPS_COUNT; i++) { - submenu_add_item( - loader_instance->settings_menu, - FLIPPER_SETTINGS_APPS[i].name, - i, - loader_menu_callback, - (void*)&FLIPPER_SETTINGS_APPS[i]); + if(loader->app.insomniac) { + furi_hal_power_insomnia_exit(); } -} -void loader_show_menu() { - furi_assert(loader_instance); - furi_thread_flags_set(loader_instance->loader_thread, LOADER_THREAD_FLAG_SHOW_MENU); -} + free(loader->app.name); + loader->app.name = NULL; -void loader_update_menu() { - menu_reset(loader_instance->primary_menu); - loader_build_menu(); + furi_thread_free(loader->app.thread); + loader->app.thread = NULL; } +// app + int32_t loader_srv(void* p) { UNUSED(p); + Loader* loader = loader_alloc(); + furi_record_create(RECORD_LOADER, loader); + FURI_LOG_I(TAG, "Executing system start hooks"); for(size_t i = 0; i < FLIPPER_ON_SYSTEM_START_COUNT; i++) { FLIPPER_ON_SYSTEM_START[i](); } - FURI_LOG_I(TAG, "Starting"); - loader_instance = loader_alloc(); - - loader_build_menu(); - loader_build_submenu(); - - FURI_LOG_I(TAG, "Started"); - - furi_record_create(RECORD_LOADER, loader_instance); - if(FLIPPER_AUTORUN_APP_NAME && strlen(FLIPPER_AUTORUN_APP_NAME)) { - loader_start(loader_instance, FLIPPER_AUTORUN_APP_NAME, NULL); + loader_do_start_by_name(loader, FLIPPER_AUTORUN_APP_NAME, NULL); } - while(1) { - uint32_t flags = - furi_thread_flags_wait(LOADER_THREAD_FLAG_ALL, FuriFlagWaitAny, FuriWaitForever); - if(flags & LOADER_THREAD_FLAG_SHOW_MENU) { - menu_set_selected_item(loader_instance->primary_menu, 0); - view_dispatcher_switch_to_view( - loader_instance->view_dispatcher, LoaderMenuViewPrimary); - view_dispatcher_run(loader_instance->view_dispatcher); + LoaderMessage message; + while(true) { + if(furi_message_queue_get(loader->queue, &message, FuriWaitForever) == FuriStatusOk) { + switch(message.type) { + case LoaderMessageTypeStartByName: + message.status_value->value = + loader_do_start_by_name(loader, message.start.name, message.start.args); + api_lock_unlock(message.api_lock); + break; + case LoaderMessageTypeShowMenu: + loader_do_menu_show(loader); + break; + case LoaderMessageTypeMenuClosed: + loader_do_menu_closed(loader); + break; + case LoaderMessageTypeIsLocked: + message.bool_value->value = loader_do_is_locked(loader); + api_lock_unlock(message.api_lock); + break; + case LoaderMessageTypeAppClosed: + loader_do_app_closed(loader); + break; + case LoaderMessageTypeLock: + message.bool_value->value = loader_do_lock(loader); + api_lock_unlock(message.api_lock); + break; + case LoaderMessageTypeUnlock: + loader_do_unlock(loader); + } } } - furi_record_destroy(RECORD_LOADER); - loader_free(loader_instance); - return 0; -} - -FuriPubSub* loader_get_pubsub(Loader* instance) { - return instance->pubsub; -} +} \ No newline at end of file diff --git a/applications/services/loader/loader.h b/applications/services/loader/loader.h index 8dbc4fc358f..e3a691b768c 100644 --- a/applications/services/loader/loader.h +++ b/applications/services/loader/loader.h @@ -1,7 +1,5 @@ #pragma once - -#include -#include +#include #ifdef __cplusplus extern "C" { @@ -43,17 +41,14 @@ bool loader_lock(Loader* instance); void loader_unlock(Loader* instance); /** Get loader lock status */ -bool loader_is_locked(const Loader* instance); - -/** Show primary loader */ -void loader_show_menu(); +bool loader_is_locked(Loader* instance); /** Show primary loader */ -void loader_update_menu(); +void loader_show_menu(Loader* instance); /** Show primary loader */ FuriPubSub* loader_get_pubsub(Loader* instance); #ifdef __cplusplus } -#endif +#endif \ No newline at end of file diff --git a/applications/services/loader/loader_cli.c b/applications/services/loader/loader_cli.c new file mode 100644 index 00000000000..2d460221578 --- /dev/null +++ b/applications/services/loader/loader_cli.c @@ -0,0 +1,117 @@ +#include +#include +#include +#include +#include "loader.h" + +static void loader_cli_print_usage() { + printf("Usage:\r\n"); + printf("loader \r\n"); + printf("Cmd list:\r\n"); + printf("\tlist\t - List available applications\r\n"); + printf("\topen \t - Open application by name\r\n"); + printf("\tinfo\t - Show loader state\r\n"); +} + +static void loader_cli_list() { + printf("Applications:\r\n"); + for(size_t i = 0; i < FLIPPER_APPS_COUNT; i++) { + printf("\t%s\r\n", FLIPPER_APPS[i].name); + } + printf("Settings:\r\n"); + for(size_t i = 0; i < FLIPPER_SETTINGS_APPS_COUNT; i++) { + printf("\t%s\r\n", FLIPPER_SETTINGS_APPS[i].name); + } +} + +static void loader_cli_info(Loader* loader) { + if(!loader_is_locked(loader)) { + printf("No application is running\r\n"); + } else { + // TODO: print application name ??? + printf("Application is running\r\n"); + } +} + +static void loader_cli_open(FuriString* args, Loader* loader) { + FuriString* app_name = furi_string_alloc(); + + do { + if(!args_read_probably_quoted_string_and_trim(args, app_name)) { + printf("No application provided\r\n"); + break; + } + furi_string_trim(args); + + const char* args_str = furi_string_get_cstr(args); + if(strlen(args_str) == 0) { + args_str = NULL; + } + + const char* app_name_str = furi_string_get_cstr(app_name); + + LoaderStatus status = loader_start(loader, app_name_str, args_str); + + switch(status) { + case LoaderStatusOk: + break; + case LoaderStatusErrorAppStarted: + printf("Can't start, application is running"); + break; + case LoaderStatusErrorUnknownApp: + printf("%s doesn't exists\r\n", app_name_str); + break; + case LoaderStatusErrorInternal: + printf("Internal error\r\n"); + break; + } + } while(false); + + furi_string_free(app_name); +} + +static void loader_cli(Cli* cli, FuriString* args, void* context) { + UNUSED(cli); + UNUSED(context); + Loader* loader = furi_record_open(RECORD_LOADER); + + FuriString* cmd; + cmd = furi_string_alloc(); + + do { + if(!args_read_string_and_trim(args, cmd)) { + loader_cli_print_usage(); + break; + } + + if(furi_string_cmp_str(cmd, "list") == 0) { + loader_cli_list(); + break; + } + + if(furi_string_cmp_str(cmd, "open") == 0) { + loader_cli_open(args, loader); + break; + } + + if(furi_string_cmp_str(cmd, "info") == 0) { + loader_cli_info(loader); + break; + } + + loader_cli_print_usage(); + } while(false); + + furi_string_free(cmd); + furi_record_close(RECORD_LOADER); +} + +void loader_on_system_start() { +#ifdef SRV_CLI + Cli* cli = furi_record_open(RECORD_CLI); + cli_add_command(cli, RECORD_LOADER, CliCommandFlagParallelSafe, loader_cli, NULL); + furi_record_close(RECORD_CLI); +#else + UNUSED(loader_cli); +#endif +} \ No newline at end of file diff --git a/applications/services/loader/loader_i.h b/applications/services/loader/loader_i.h index 00028cd6b16..2e3f10dad02 100644 --- a/applications/services/loader/loader_i.h +++ b/applications/services/loader/loader_i.h @@ -1,39 +1,56 @@ -#include "loader.h" - +#pragma once #include -#include -#include -#include -#include - -#include - -#include -#include +#include +#include "loader.h" +#include "loader_menu.h" -#include -#include +typedef struct { + char* args; + char* name; + FuriThread* thread; + bool insomniac; +} LoaderAppData; struct Loader { - FuriThreadId loader_thread; - - const FlipperApplication* application; - FuriThread* application_thread; - char* application_arguments; - - Cli* cli; - Gui* gui; - - ViewDispatcher* view_dispatcher; - Menu* primary_menu; - Submenu* settings_menu; - - volatile uint8_t lock_count; - FuriPubSub* pubsub; + FuriMessageQueue* queue; + LoaderMenu* loader_menu; + LoaderAppData app; }; typedef enum { - LoaderMenuViewPrimary, - LoaderMenuViewSettings, -} LoaderMenuView; + LoaderMessageTypeStartByName, + LoaderMessageTypeAppClosed, + LoaderMessageTypeShowMenu, + LoaderMessageTypeMenuClosed, + LoaderMessageTypeLock, + LoaderMessageTypeUnlock, + LoaderMessageTypeIsLocked, +} LoaderMessageType; + +typedef struct { + const char* name; + const char* args; +} LoaderMessageStartByName; + +typedef struct { + LoaderStatus value; +} LoaderMessageLoaderStatusResult; + +typedef struct { + bool value; +} LoaderMessageBoolResult; + +typedef struct { + FuriApiLock api_lock; + LoaderMessageType type; + + union { + LoaderMessageStartByName start; + }; + + union { + LoaderMessageLoaderStatusResult* status_value; + LoaderMessageBoolResult* bool_value; + }; +} LoaderMessage; diff --git a/applications/services/loader/loader_menu.c b/applications/services/loader/loader_menu.c new file mode 100644 index 00000000000..ec853661fbb --- /dev/null +++ b/applications/services/loader/loader_menu.c @@ -0,0 +1,187 @@ +#include +#include +#include +#include +#include +#include + +#include "loader_menu.h" + +#define TAG "LoaderMenu" + +struct LoaderMenu { + Gui* gui; + ViewDispatcher* view_dispatcher; + Menu* primary_menu; + Submenu* settings_menu; + + void (*closed_callback)(void*); + void* closed_callback_context; + + void (*click_callback)(const char*, void*); + void* click_callback_context; + + FuriThread* thread; +}; + +typedef enum { + LoaderMenuViewPrimary, + LoaderMenuViewSettings, +} LoaderMenuView; + +static int32_t loader_menu_thread(void* p); + +LoaderMenu* loader_menu_alloc() { + LoaderMenu* loader_menu = malloc(sizeof(LoaderMenu)); + loader_menu->gui = furi_record_open(RECORD_GUI); + loader_menu->view_dispatcher = view_dispatcher_alloc(); + loader_menu->primary_menu = menu_alloc(); + loader_menu->settings_menu = submenu_alloc(); + loader_menu->thread = NULL; + return loader_menu; +} + +void loader_menu_free(LoaderMenu* loader_menu) { + furi_assert(loader_menu); + // check if thread is running + furi_assert(!loader_menu->thread); + + submenu_free(loader_menu->settings_menu); + menu_free(loader_menu->primary_menu); + view_dispatcher_free(loader_menu->view_dispatcher); + furi_record_close(RECORD_GUI); + free(loader_menu); +} + +void loader_menu_start(LoaderMenu* loader_menu) { + furi_assert(loader_menu); + furi_assert(!loader_menu->thread); + loader_menu->thread = furi_thread_alloc_ex(TAG, 1024, loader_menu_thread, loader_menu); + furi_thread_start(loader_menu->thread); +} + +void loader_menu_stop(LoaderMenu* loader_menu) { + furi_assert(loader_menu); + furi_assert(loader_menu->thread); + view_dispatcher_stop(loader_menu->view_dispatcher); + furi_thread_join(loader_menu->thread); + furi_thread_free(loader_menu->thread); + loader_menu->thread = NULL; +} + +void loader_menu_set_closed_callback( + LoaderMenu* loader_menu, + void (*callback)(void*), + void* context) { + loader_menu->closed_callback = callback; + loader_menu->closed_callback_context = context; +} + +void loader_menu_set_click_callback( + LoaderMenu* loader_menu, + void (*callback)(const char*, void*), + void* context) { + loader_menu->click_callback = callback; + loader_menu->click_callback_context = context; +} + +static void loader_menu_callback(void* context, uint32_t index) { + LoaderMenu* loader_menu = context; + const char* name = FLIPPER_APPS[index].name; + if(loader_menu->click_callback) { + loader_menu->click_callback(name, loader_menu->click_callback_context); + } +} + +static void loader_menu_settings_menu_callback(void* context, uint32_t index) { + LoaderMenu* loader_menu = context; + const char* name = FLIPPER_SETTINGS_APPS[index].name; + if(loader_menu->click_callback) { + loader_menu->click_callback(name, loader_menu->click_callback_context); + } +} + +static void loader_menu_switch_to_settings(void* context, uint32_t index) { + UNUSED(index); + LoaderMenu* loader_menu = context; + view_dispatcher_switch_to_view(loader_menu->view_dispatcher, LoaderMenuViewSettings); +} + +static uint32_t loader_menu_switch_to_primary(void* context) { + UNUSED(context); + return LoaderMenuViewPrimary; +} + +static uint32_t loader_menu_exit(void* context) { + UNUSED(context); + return VIEW_NONE; +} + +static void loader_menu_build_menu(LoaderMenu* loader_menu) { + size_t i; + for(i = 0; i < FLIPPER_APPS_COUNT; i++) { + menu_add_item( + loader_menu->primary_menu, + FLIPPER_APPS[i].name, + FLIPPER_APPS[i].icon, + i, + loader_menu_callback, + (void*)loader_menu); + } + menu_add_item( + loader_menu->primary_menu, + "Settings", + &A_Settings_14, + i++, + loader_menu_switch_to_settings, + loader_menu); +}; + +static void loader_menu_build_submenu(LoaderMenu* loader_menu) { + for(size_t i = 0; i < FLIPPER_SETTINGS_APPS_COUNT; i++) { + submenu_add_item( + loader_menu->settings_menu, + FLIPPER_SETTINGS_APPS[i].name, + i, + loader_menu_settings_menu_callback, + loader_menu); + } +} + +static int32_t loader_menu_thread(void* p) { + LoaderMenu* loader_menu = p; + furi_assert(loader_menu); + + loader_menu_build_menu(loader_menu); + loader_menu_build_submenu(loader_menu); + + view_dispatcher_attach_to_gui( + loader_menu->view_dispatcher, loader_menu->gui, ViewDispatcherTypeFullscreen); + + // Primary menu + View* primary_view = menu_get_view(loader_menu->primary_menu); + view_set_context(primary_view, loader_menu->primary_menu); + view_set_previous_callback(primary_view, loader_menu_exit); + view_dispatcher_add_view(loader_menu->view_dispatcher, LoaderMenuViewPrimary, primary_view); + + // Settings menu + View* settings_view = submenu_get_view(loader_menu->settings_menu); + view_set_context(settings_view, loader_menu->settings_menu); + view_set_previous_callback(settings_view, loader_menu_switch_to_primary); + view_dispatcher_add_view(loader_menu->view_dispatcher, LoaderMenuViewSettings, settings_view); + + view_dispatcher_enable_queue(loader_menu->view_dispatcher); + view_dispatcher_switch_to_view(loader_menu->view_dispatcher, LoaderMenuViewPrimary); + + // run view dispatcher + view_dispatcher_run(loader_menu->view_dispatcher); + + view_dispatcher_remove_view(loader_menu->view_dispatcher, LoaderMenuViewPrimary); + view_dispatcher_remove_view(loader_menu->view_dispatcher, LoaderMenuViewSettings); + + if(loader_menu->closed_callback) { + loader_menu->closed_callback(loader_menu->closed_callback_context); + } + + return 0; +} \ No newline at end of file diff --git a/applications/services/loader/loader_menu.h b/applications/services/loader/loader_menu.h new file mode 100644 index 00000000000..7405b87be77 --- /dev/null +++ b/applications/services/loader/loader_menu.h @@ -0,0 +1,30 @@ +#pragma once +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct LoaderMenu LoaderMenu; + +LoaderMenu* loader_menu_alloc(); + +void loader_menu_free(LoaderMenu* loader_menu); + +void loader_menu_start(LoaderMenu* loader_menu); + +void loader_menu_stop(LoaderMenu* loader_menu); + +void loader_menu_set_closed_callback( + LoaderMenu* loader_menu, + void (*callback)(void*), + void* context); + +void loader_menu_set_click_callback( + LoaderMenu* loader_menu, + void (*callback)(const char*, void*), + void* context); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/applications/settings/system/system_settings.c b/applications/settings/system/system_settings.c index 597710a53f0..dd3c0dc6bd2 100644 --- a/applications/settings/system/system_settings.c +++ b/applications/settings/system/system_settings.c @@ -43,7 +43,6 @@ static void debug_changed(VariableItem* item) { } else { furi_hal_rtc_reset_flag(FuriHalRtcFlagDebug); } - loader_update_menu(); } const char* const heap_trace_mode_text[] = { @@ -137,8 +136,6 @@ static void hand_orient_changed(VariableItem* item) { } else { furi_hal_rtc_reset_flag(FuriHalRtcFlagHandOrient); } - - loader_update_menu(); } const char* const sleep_method[] = { diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index f84bf074e16..bc6844d350b 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,23.3,, +Version,+,24.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -1375,12 +1375,11 @@ Function,-,ldiv,ldiv_t,"long, long" Function,-,llabs,long long,long long Function,-,lldiv,lldiv_t,"long long, long long" Function,+,loader_get_pubsub,FuriPubSub*,Loader* -Function,+,loader_is_locked,_Bool,const Loader* +Function,+,loader_is_locked,_Bool,Loader* Function,+,loader_lock,_Bool,Loader* -Function,+,loader_show_menu,void, +Function,+,loader_show_menu,void,Loader* Function,+,loader_start,LoaderStatus,"Loader*, const char*, const char*" Function,+,loader_unlock,void,Loader* -Function,+,loader_update_menu,void, Function,+,loading_alloc,Loading*, Function,+,loading_free,void,Loading* Function,+,loading_get_view,View*,Loading* diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index c176b2d7e1e..1c8424fe8c9 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,23.3,, +Version,+,24.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -1795,12 +1795,11 @@ Function,-,llround,long long int,double Function,-,llroundf,long long int,float Function,-,llroundl,long long int,long double Function,+,loader_get_pubsub,FuriPubSub*,Loader* -Function,+,loader_is_locked,_Bool,const Loader* +Function,+,loader_is_locked,_Bool,Loader* Function,+,loader_lock,_Bool,Loader* -Function,+,loader_show_menu,void, +Function,+,loader_show_menu,void,Loader* Function,+,loader_start,LoaderStatus,"Loader*, const char*, const char*" Function,+,loader_unlock,void,Loader* -Function,+,loader_update_menu,void, Function,+,loading_alloc,Loading*, Function,+,loading_free,void,Loading* Function,+,loading_get_view,View*,Loading* From 914129a0d90a75952662f4c717d1d98ffe90fccf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Fri, 5 May 2023 21:40:55 +0900 Subject: [PATCH 541/824] [FL-3289] Various Furi/FuriHal bug fixes and improvements (#2637) * Furi: properly handle thread free before TCB scrapping, add furi_free - more invasive version of free to memmgr. FuriHal: add DWT comparator api to cortex. Updater, RPC: refactor various thread shanenigans. Code cleanup. * Rollback free macros and related changes --- applications/services/loader/loader.c | 1 + applications/services/rpc/rpc.c | 48 +++++++------ .../services/storage/storage_external_api.c | 1 + .../services/storage/storages/storage_ext.c | 1 + .../power_settings_app/views/battery_info.c | 4 +- applications/system/updater/cli/updater_cli.c | 26 ++----- .../updater/util/update_task_worker_flasher.c | 6 +- firmware/targets/f18/api_symbols.csv | 7 +- firmware/targets/f7/api_symbols.csv | 7 +- .../targets/f7/furi_hal/furi_hal_cortex.c | 72 ++++++++++++++++++- .../furi_hal_include/furi_hal_cortex.h | 47 ++++++++++++ furi/core/thread.c | 28 ++++---- furi/core/thread.h | 8 +-- furi/core/timer.c | 10 +++ furi/core/timer.h | 4 ++ 15 files changed, 202 insertions(+), 68 deletions(-) diff --git a/applications/services/loader/loader.c b/applications/services/loader/loader.c index be16e5091f1..f385efdf95a 100644 --- a/applications/services/loader/loader.c +++ b/applications/services/loader/loader.c @@ -248,6 +248,7 @@ static void loader_do_app_closed(Loader* loader) { free(loader->app.name); loader->app.name = NULL; + furi_thread_join(loader->app.thread); furi_thread_free(loader->app.thread); loader->app.thread = NULL; } diff --git a/applications/services/rpc/rpc.c b/applications/services/rpc/rpc.c index 5b09e9b5178..a759a12a9c3 100644 --- a/applications/services/rpc/rpc.c +++ b/applications/services/rpc/rpc.c @@ -326,31 +326,35 @@ static int32_t rpc_session_worker(void* context) { return 0; } -static void rpc_session_free_callback(FuriThreadState thread_state, void* context) { - furi_assert(context); - +static void rpc_session_thread_pending_callback(void* context, uint32_t arg) { + UNUSED(arg); RpcSession* session = (RpcSession*)context; - if(thread_state == FuriThreadStateStopped) { - for(size_t i = 0; i < COUNT_OF(rpc_systems); ++i) { - if(rpc_systems[i].free) { - rpc_systems[i].free(session->system_contexts[i]); - } - } - free(session->system_contexts); - free(session->decoded_message); - RpcHandlerDict_clear(session->handlers); - furi_stream_buffer_free(session->stream); - - furi_mutex_acquire(session->callbacks_mutex, FuriWaitForever); - if(session->terminated_callback) { - session->terminated_callback(session->context); + for(size_t i = 0; i < COUNT_OF(rpc_systems); ++i) { + if(rpc_systems[i].free) { + (rpc_systems[i].free)(session->system_contexts[i]); } - furi_mutex_release(session->callbacks_mutex); + } + free(session->system_contexts); + free(session->decoded_message); + RpcHandlerDict_clear(session->handlers); + furi_stream_buffer_free(session->stream); - furi_mutex_free(session->callbacks_mutex); - furi_thread_free(session->thread); - free(session); + furi_mutex_acquire(session->callbacks_mutex, FuriWaitForever); + if(session->terminated_callback) { + session->terminated_callback(session->context); + } + furi_mutex_release(session->callbacks_mutex); + + furi_mutex_free(session->callbacks_mutex); + furi_thread_join(session->thread); + furi_thread_free(session->thread); + free(session); +} + +static void rpc_session_thread_state_callback(FuriThreadState thread_state, void* context) { + if(thread_state == FuriThreadStateStopped) { + furi_timer_pending_callback(rpc_session_thread_pending_callback, context, 0); } } @@ -385,7 +389,7 @@ RpcSession* rpc_session_open(Rpc* rpc, RpcOwner owner) { session->thread = furi_thread_alloc_ex("RpcSessionWorker", 3072, rpc_session_worker, session); furi_thread_set_state_context(session->thread, session); - furi_thread_set_state_callback(session->thread, rpc_session_free_callback); + furi_thread_set_state_callback(session->thread, rpc_session_thread_state_callback); furi_thread_start(session->thread); diff --git a/applications/services/storage/storage_external_api.c b/applications/services/storage/storage_external_api.c index ffc3da4bc48..bf474bc9d0b 100644 --- a/applications/services/storage/storage_external_api.c +++ b/applications/services/storage/storage_external_api.c @@ -803,6 +803,7 @@ void storage_file_free(File* file) { } FuriPubSub* storage_get_pubsub(Storage* storage) { + furi_assert(storage); return storage->pubsub; } diff --git a/applications/services/storage/storages/storage_ext.c b/applications/services/storage/storages/storage_ext.c index d802d6e9f68..15a355dc25a 100644 --- a/applications/services/storage/storages/storage_ext.c +++ b/applications/services/storage/storages/storage_ext.c @@ -337,6 +337,7 @@ static bool storage_ext_file_close(void* ctx, File* file) { file->internal_error_id = f_close(file_data); file->error_id = storage_ext_parse_error(file->internal_error_id); free(file_data); + storage_set_storage_file_data(file, NULL, storage); return (file->error_id == FSE_OK); } diff --git a/applications/settings/power_settings_app/views/battery_info.c b/applications/settings/power_settings_app/views/battery_info.c index 0956cae4f5a..d56dfc62859 100644 --- a/applications/settings/power_settings_app/views/battery_info.c +++ b/applications/settings/power_settings_app/views/battery_info.c @@ -53,7 +53,9 @@ static void draw_battery(Canvas* canvas, BatteryInfoModel* data, int x, int y) { (uint32_t)(data->vbus_voltage), (uint32_t)(data->vbus_voltage * 10) % 10, current); - } else if(current < 0) { + } else if(current < -5) { + // Often gauge reports anything in the range 1~5ma as 5ma + // That brings confusion, so we'll treat it as Napping snprintf( emote, sizeof(emote), diff --git a/applications/system/updater/cli/updater_cli.c b/applications/system/updater/cli/updater_cli.c index 2bf6dab26e4..659c431f701 100644 --- a/applications/system/updater/cli/updater_cli.c +++ b/applications/system/updater/cli/updater_cli.c @@ -85,22 +85,10 @@ static void updater_cli_ep(Cli* cli, FuriString* args, void* context) { updater_cli_help(args); } -static int32_t updater_spawner_thread_worker(void* arg) { +static void updater_start_app(void* context, uint32_t arg) { + UNUSED(context); UNUSED(arg); - Loader* loader = furi_record_open(RECORD_LOADER); - loader_start(loader, "UpdaterApp", NULL); - furi_record_close(RECORD_LOADER); - return 0; -} -static void updater_spawner_thread_cleanup(FuriThreadState state, void* context) { - FuriThread* thread = context; - if(state == FuriThreadStateStopped) { - furi_thread_free(thread); - } -} - -static void updater_start_app() { FuriHalRtcBootMode mode = furi_hal_rtc_get_boot_mode(); if((mode != FuriHalRtcBootModePreUpdate) && (mode != FuriHalRtcBootModePostUpdate)) { return; @@ -110,11 +98,9 @@ static void updater_start_app() { * inside loader process, at startup. * So, accessing its record would cause a deadlock */ - FuriThread* thread = - furi_thread_alloc_ex("UpdateAppSpawner", 768, updater_spawner_thread_worker, NULL); - furi_thread_set_state_callback(thread, updater_spawner_thread_cleanup); - furi_thread_set_state_context(thread, thread); - furi_thread_start(thread); + Loader* loader = furi_record_open(RECORD_LOADER); + loader_start(loader, "UpdaterApp", NULL); + furi_record_close(RECORD_LOADER); } void updater_on_system_start() { @@ -126,7 +112,7 @@ void updater_on_system_start() { UNUSED(updater_cli_ep); #endif #ifndef FURI_RAM_EXEC - updater_start_app(); + furi_timer_pending_callback(updater_start_app, NULL, 0); #else UNUSED(updater_start_app); #endif diff --git a/applications/system/updater/util/update_task_worker_flasher.c b/applications/system/updater/util/update_task_worker_flasher.c index 63024ced9cf..5d247746464 100644 --- a/applications/system/updater/util/update_task_worker_flasher.c +++ b/applications/system/updater/util/update_task_worker_flasher.c @@ -346,7 +346,11 @@ int32_t update_task_worker_flash_writer(void* context) { furi_hal_rtc_set_boot_mode(FuriHalRtcBootModePostUpdate); // Format LFS before restoring backup on next boot furi_hal_rtc_set_flag(FuriHalRtcFlagFactoryReset); - +#ifdef FURI_NDEBUG + // Production + furi_hal_rtc_set_log_level(FuriLogLevelDefault); + furi_hal_rtc_reset_flag(FuriHalRtcFlagDebug); +#endif update_task_set_progress(update_task, UpdateTaskStageCompleted, 100); success = true; } while(false); diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index bc6844d350b..f83f6405be8 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,24.0,, +Version,+,26.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -892,6 +892,8 @@ Function,+,furi_hal_console_puts,void,const char* Function,+,furi_hal_console_set_tx_callback,void,"FuriHalConsoleTxCallback, void*" Function,+,furi_hal_console_tx,void,"const uint8_t*, size_t" Function,+,furi_hal_console_tx_with_new_line,void,"const uint8_t*, size_t" +Function,+,furi_hal_cortex_comp_enable,void,"FuriHalCortexComp, FuriHalCortexCompFunction, uint32_t, uint32_t, FuriHalCortexCompSize" +Function,+,furi_hal_cortex_comp_reset,void,FuriHalCortexComp Function,+,furi_hal_cortex_delay_us,void,uint32_t Function,-,furi_hal_cortex_init_early,void, Function,+,furi_hal_cortex_instructions_per_microsecond,uint32_t, @@ -1278,7 +1280,7 @@ Function,+,furi_thread_set_priority,void,"FuriThread*, FuriThreadPriority" Function,+,furi_thread_set_stack_size,void,"FuriThread*, size_t" Function,+,furi_thread_set_state_callback,void,"FuriThread*, FuriThreadStateCallback" Function,+,furi_thread_set_state_context,void,"FuriThread*, void*" -Function,+,furi_thread_set_stdout_callback,_Bool,FuriThreadStdoutWriteCallback +Function,+,furi_thread_set_stdout_callback,void,FuriThreadStdoutWriteCallback Function,+,furi_thread_start,void,FuriThread* Function,+,furi_thread_stdout_flush,int32_t, Function,+,furi_thread_stdout_write,size_t,"const char*, size_t" @@ -1287,6 +1289,7 @@ Function,+,furi_thread_yield,void, Function,+,furi_timer_alloc,FuriTimer*,"FuriTimerCallback, FuriTimerType, void*" Function,+,furi_timer_free,void,FuriTimer* Function,+,furi_timer_is_running,uint32_t,FuriTimer* +Function,+,furi_timer_pending_callback,void,"FuriTimerPendigCallback, void*, uint32_t" Function,+,furi_timer_start,FuriStatus,"FuriTimer*, uint32_t" Function,+,furi_timer_stop,FuriStatus,FuriTimer* Function,-,fwrite,size_t,"const void*, size_t, size_t, FILE*" diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 1c8424fe8c9..8a4e06303f7 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,24.0,, +Version,+,26.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -1073,6 +1073,8 @@ Function,+,furi_hal_console_puts,void,const char* Function,+,furi_hal_console_set_tx_callback,void,"FuriHalConsoleTxCallback, void*" Function,+,furi_hal_console_tx,void,"const uint8_t*, size_t" Function,+,furi_hal_console_tx_with_new_line,void,"const uint8_t*, size_t" +Function,+,furi_hal_cortex_comp_enable,void,"FuriHalCortexComp, FuriHalCortexCompFunction, uint32_t, uint32_t, FuriHalCortexCompSize" +Function,+,furi_hal_cortex_comp_reset,void,FuriHalCortexComp Function,+,furi_hal_cortex_delay_us,void,uint32_t Function,-,furi_hal_cortex_init_early,void, Function,+,furi_hal_cortex_instructions_per_microsecond,uint32_t, @@ -1562,7 +1564,7 @@ Function,+,furi_thread_set_priority,void,"FuriThread*, FuriThreadPriority" Function,+,furi_thread_set_stack_size,void,"FuriThread*, size_t" Function,+,furi_thread_set_state_callback,void,"FuriThread*, FuriThreadStateCallback" Function,+,furi_thread_set_state_context,void,"FuriThread*, void*" -Function,+,furi_thread_set_stdout_callback,_Bool,FuriThreadStdoutWriteCallback +Function,+,furi_thread_set_stdout_callback,void,FuriThreadStdoutWriteCallback Function,+,furi_thread_start,void,FuriThread* Function,+,furi_thread_stdout_flush,int32_t, Function,+,furi_thread_stdout_write,size_t,"const char*, size_t" @@ -1571,6 +1573,7 @@ Function,+,furi_thread_yield,void, Function,+,furi_timer_alloc,FuriTimer*,"FuriTimerCallback, FuriTimerType, void*" Function,+,furi_timer_free,void,FuriTimer* Function,+,furi_timer_is_running,uint32_t,FuriTimer* +Function,+,furi_timer_pending_callback,void,"FuriTimerPendigCallback, void*, uint32_t" Function,+,furi_timer_start,FuriStatus,"FuriTimer*, uint32_t" Function,+,furi_timer_stop,FuriStatus,FuriTimer* Function,-,fwrite,size_t,"const void*, size_t, size_t, FILE*" diff --git a/firmware/targets/f7/furi_hal/furi_hal_cortex.c b/firmware/targets/f7/furi_hal/furi_hal_cortex.c index d0bce503817..3fbe384e3c8 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_cortex.c +++ b/firmware/targets/f7/furi_hal/furi_hal_cortex.c @@ -1,11 +1,12 @@ #include +#include #include #define FURI_HAL_CORTEX_INSTRUCTIONS_PER_MICROSECOND (SystemCoreClock / 1000000) void furi_hal_cortex_init_early() { - CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; + CoreDebug->DEMCR |= (CoreDebug_DEMCR_TRCENA_Msk | CoreDebug_DEMCR_MON_EN_Msk); DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; DWT->CYCCNT = 0U; @@ -38,4 +39,71 @@ bool furi_hal_cortex_timer_is_expired(FuriHalCortexTimer cortex_timer) { void furi_hal_cortex_timer_wait(FuriHalCortexTimer cortex_timer) { while(!furi_hal_cortex_timer_is_expired(cortex_timer)) ; -} \ No newline at end of file +} + +// Duck ST +#undef COMP0 +#undef COMP1 +#undef COMP2 +#undef COMP3 + +void furi_hal_cortex_comp_enable( + FuriHalCortexComp comp, + FuriHalCortexCompFunction function, + uint32_t value, + uint32_t mask, + FuriHalCortexCompSize size) { + uint32_t function_reg = (uint32_t)function | ((uint32_t)size << 10); + + switch(comp) { + case FuriHalCortexComp0: + (DWT->COMP0) = value; + (DWT->MASK0) = mask; + (DWT->FUNCTION0) = function_reg; + break; + case FuriHalCortexComp1: + (DWT->COMP1) = value; + (DWT->MASK1) = mask; + (DWT->FUNCTION1) = function_reg; + break; + case FuriHalCortexComp2: + (DWT->COMP2) = value; + (DWT->MASK2) = mask; + (DWT->FUNCTION2) = function_reg; + break; + case FuriHalCortexComp3: + (DWT->COMP3) = value; + (DWT->MASK3) = mask; + (DWT->FUNCTION3) = function_reg; + break; + default: + furi_crash("Invalid parameter"); + } +} + +void furi_hal_cortex_comp_reset(FuriHalCortexComp comp) { + switch(comp) { + case FuriHalCortexComp0: + (DWT->COMP0) = 0; + (DWT->MASK0) = 0; + (DWT->FUNCTION0) = 0; + break; + case FuriHalCortexComp1: + (DWT->COMP1) = 0; + (DWT->MASK1) = 0; + (DWT->FUNCTION1) = 0; + break; + case FuriHalCortexComp2: + (DWT->COMP2) = 0; + (DWT->MASK2) = 0; + (DWT->FUNCTION2) = 0; + break; + case FuriHalCortexComp3: + (DWT->COMP3) = 0; + (DWT->MASK3) = 0; + (DWT->FUNCTION3) = 0; + break; + default: + furi_crash("Invalid parameter"); + } +} diff --git a/firmware/targets/furi_hal_include/furi_hal_cortex.h b/firmware/targets/furi_hal_include/furi_hal_cortex.h index 91596ffe3f2..ebabbabfd8f 100644 --- a/firmware/targets/furi_hal_include/furi_hal_cortex.h +++ b/firmware/targets/furi_hal_include/furi_hal_cortex.h @@ -56,6 +56,53 @@ bool furi_hal_cortex_timer_is_expired(FuriHalCortexTimer cortex_timer); */ void furi_hal_cortex_timer_wait(FuriHalCortexTimer cortex_timer); +typedef enum { + FuriHalCortexComp0, + FuriHalCortexComp1, + FuriHalCortexComp2, + FuriHalCortexComp3, +} FuriHalCortexComp; + +typedef enum { + FuriHalCortexCompSizeWord = 0b10, + FuriHalCortexCompSizeHalfWord = 0b01, + FuriHalCortexCompSizeByte = 0b00, +} FuriHalCortexCompSize; + +typedef enum { + FuriHalCortexCompFunctionPC = 0b100, + FuriHalCortexCompFunctionRead = 0b101, + FuriHalCortexCompFunctionWrite = 0b110, + FuriHalCortexCompFunctionReadWrite = 0b110, +} FuriHalCortexCompFunction; + +/** Enable DWT comparator + * + * Allows to programmatically set instruction/data breakpoints. + * + * More details on how it works can be found in armv7m official documentation: + * https://developer.arm.com/documentation/ddi0403/d/Debug-Architecture/ARMv7-M-Debug/The-Data-Watchpoint-and-Trace-unit/The-DWT-comparators + * https://developer.arm.com/documentation/ddi0403/d/Debug-Architecture/ARMv7-M-Debug/The-Data-Watchpoint-and-Trace-unit/Comparator-Function-registers--DWT-FUNCTIONn + * + * @param[in] comp The Comparator + * @param[in] function The Comparator Function to use + * @param[in] value The value + * @param[in] mask The mask + * @param[in] size The size + */ +void furi_hal_cortex_comp_enable( + FuriHalCortexComp comp, + FuriHalCortexCompFunction function, + uint32_t value, + uint32_t mask, + FuriHalCortexCompSize size); + +/** Reset DWT comparator + * + * @param[in] comp The Comparator + */ +void furi_hal_cortex_comp_reset(FuriHalCortexComp comp); + #ifdef __cplusplus } #endif diff --git a/furi/core/thread.c b/furi/core/thread.c index d78070d61d5..facbcb41179 100644 --- a/furi/core/thread.c +++ b/furi/core/thread.c @@ -164,10 +164,13 @@ FuriThread* furi_thread_alloc_ex( void furi_thread_free(FuriThread* thread) { furi_assert(thread); + + // Ensure that use join before free furi_assert(thread->state == FuriThreadStateStopped); + furi_assert(thread->task_handle == NULL); - if(thread->name) free((void*)thread->name); - if(thread->appid) free((void*)thread->appid); + if(thread->name) free(thread->name); + if(thread->appid) free(thread->appid); furi_string_free(thread->output.buffer); free(thread); @@ -176,14 +179,14 @@ void furi_thread_free(FuriThread* thread) { void furi_thread_set_name(FuriThread* thread, const char* name) { furi_assert(thread); furi_assert(thread->state == FuriThreadStateStopped); - if(thread->name) free((void*)thread->name); + if(thread->name) free(thread->name); thread->name = name ? strdup(name) : NULL; } void furi_thread_set_appid(FuriThread* thread, const char* appid) { furi_assert(thread); furi_assert(thread->state == FuriThreadStateStopped); - if(thread->appid) free((void*)thread->appid); + if(thread->appid) free(thread->appid); thread->appid = appid ? strdup(appid) : NULL; } @@ -276,7 +279,7 @@ void furi_thread_cleanup_tcb_event(TaskHandle_t task) { if(thread) { // clear thread local storage vTaskSetThreadLocalStoragePointer(task, 0, NULL); - + furi_assert(thread->task_handle == task); thread->task_handle = NULL; } } @@ -332,7 +335,6 @@ FuriThreadId furi_thread_get_current_id() { FuriThread* furi_thread_get_current() { FuriThread* thread = pvTaskGetThreadLocalStoragePointer(NULL, 0); - furi_assert(thread != NULL); return thread; } @@ -579,24 +581,22 @@ static int32_t __furi_thread_stdout_flush(FuriThread* thread) { return 0; } -bool furi_thread_set_stdout_callback(FuriThreadStdoutWriteCallback callback) { +void furi_thread_set_stdout_callback(FuriThreadStdoutWriteCallback callback) { FuriThread* thread = furi_thread_get_current(); - + furi_assert(thread); __furi_thread_stdout_flush(thread); thread->output.write_callback = callback; - - return true; } FuriThreadStdoutWriteCallback furi_thread_get_stdout_callback() { FuriThread* thread = furi_thread_get_current(); - + furi_assert(thread); return thread->output.write_callback; } size_t furi_thread_stdout_write(const char* data, size_t size) { FuriThread* thread = furi_thread_get_current(); - + furi_assert(thread); if(size == 0 || data == NULL) { return __furi_thread_stdout_flush(thread); } else { @@ -619,7 +619,9 @@ size_t furi_thread_stdout_write(const char* data, size_t size) { } int32_t furi_thread_stdout_flush() { - return __furi_thread_stdout_flush(furi_thread_get_current()); + FuriThread* thread = furi_thread_get_current(); + furi_assert(thread); + return __furi_thread_stdout_flush(thread); } void furi_thread_suspend(FuriThreadId thread_id) { diff --git a/furi/core/thread.h b/furi/core/thread.h index b11a225b58b..022894ee8f6 100644 --- a/furi/core/thread.h +++ b/furi/core/thread.h @@ -233,7 +233,7 @@ FuriThreadId furi_thread_get_current_id(); /** Get FuriThread instance for current thread * - * @return FuriThread* + * @return pointer to FuriThread or NULL if this thread doesn't belongs to Furi */ FuriThread* furi_thread_get_current(); @@ -288,12 +288,10 @@ uint32_t furi_thread_get_stack_space(FuriThreadId thread_id); FuriThreadStdoutWriteCallback furi_thread_get_stdout_callback(); /** Set STDOUT callback for thread - * + * * @param callback callback or NULL to clear - * - * @return true on success, otherwise fail */ -bool furi_thread_set_stdout_callback(FuriThreadStdoutWriteCallback callback); +void furi_thread_set_stdout_callback(FuriThreadStdoutWriteCallback callback); /** Write data to buffered STDOUT * diff --git a/furi/core/timer.c b/furi/core/timer.c index 4b6ccecba5f..7743ffe7018 100644 --- a/furi/core/timer.c +++ b/furi/core/timer.c @@ -124,3 +124,13 @@ uint32_t furi_timer_is_running(FuriTimer* instance) { /* Return 0: not running, 1: running */ return (uint32_t)xTimerIsTimerActive(hTimer); } + +void furi_timer_pending_callback(FuriTimerPendigCallback callback, void* context, uint32_t arg) { + BaseType_t ret = pdFAIL; + if(furi_kernel_is_irq_or_masked()) { + ret = xTimerPendFunctionCallFromISR(callback, context, arg, NULL); + } else { + ret = xTimerPendFunctionCall(callback, context, arg, FuriWaitForever); + } + furi_check(ret == pdPASS); +} \ No newline at end of file diff --git a/furi/core/timer.h b/furi/core/timer.h index e79c1868d90..3f43de5fd9f 100644 --- a/furi/core/timer.h +++ b/furi/core/timer.h @@ -56,6 +56,10 @@ FuriStatus furi_timer_stop(FuriTimer* instance); */ uint32_t furi_timer_is_running(FuriTimer* instance); +typedef void (*FuriTimerPendigCallback)(void* context, uint32_t arg); + +void furi_timer_pending_callback(FuriTimerPendigCallback callback, void* context, uint32_t arg); + #ifdef __cplusplus } #endif From 954780ed24c63adfdda153d88eddd4abb3a28c53 Mon Sep 17 00:00:00 2001 From: Perry Fraser Date: Sun, 7 May 2023 14:40:38 -0400 Subject: [PATCH 542/824] Fix storage.py exist_dir logic (#2639) * Fix storage.py exist_dir logic. This method would return true if the given path existed as either a directory or a file, which is what the plain exist method is for. --- scripts/flipper/storage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/flipper/storage.py b/scripts/flipper/storage.py index 9f6f52156fd..4640262bce9 100644 --- a/scripts/flipper/storage.py +++ b/scripts/flipper/storage.py @@ -323,7 +323,7 @@ def exist_dir(self, path: str): return False raise FlipperStorageException.from_error_code(path, error_code) - return True + return response == b"Directory" or response.startswith(b"Storage") def exist_file(self, path: str): """Does file exist on Flipper""" From f7dd77795a700ec737505709573fc3f2bdc3a9ca Mon Sep 17 00:00:00 2001 From: hedger Date: Mon, 8 May 2023 14:01:52 +0300 Subject: [PATCH 543/824] ufbt: deploying sample ufbt automation for new apps; added `source "ufbt -s env"` for toolchain access (#2648) --- SConstruct | 6 +++ documentation/fbt.md | 1 + scripts/fbt_tools/fbt_apps.py | 4 +- scripts/fbt_tools/fbt_help.py | 3 ++ scripts/ufbt/SConstruct | 11 ++++- .../app_template/.github/workflows/build.yml | 41 +++++++++++++++++++ scripts/ufbt/site_tools/ufbt_help.py | 3 ++ 7 files changed, 66 insertions(+), 3 deletions(-) create mode 100644 scripts/ufbt/project_template/app_template/.github/workflows/build.yml diff --git a/SConstruct b/SConstruct index 090a92599bb..12f1166eb95 100644 --- a/SConstruct +++ b/SConstruct @@ -335,3 +335,9 @@ vscode_dist = distenv.Install("#.vscode", distenv.Glob("#.vscode/example/*")) distenv.Precious(vscode_dist) distenv.NoClean(vscode_dist) distenv.Alias("vscode_dist", vscode_dist) + +# Configure shell with build tools +distenv.PhonyTarget( + "env", + "@echo $( ${FBT_SCRIPT_DIR}/toolchain/fbtenv.sh $)", +) diff --git a/documentation/fbt.md b/documentation/fbt.md index 23b2e2b55f1..d9eb8f4aabb 100644 --- a/documentation/fbt.md +++ b/documentation/fbt.md @@ -13,6 +13,7 @@ To use `fbt`, you only need `git` installed in your system. > However, if you wish to use tools supplied with the toolchain outside `fbt`, you can open an *fbt shell*, with properly configured environment. > - On Windows, simply run `scripts/toolchain/fbtenv.cmd`. > - On Linux & MacOS, run `source scripts/toolchain/fbtenv.sh` in a new shell. + > - You can also type ```. `./fbt -s env` ``` in your shell. (Keep the "." at the beginning.) If your system is not supported by pre-built toolchain variants or you want to use custom versions of dependencies, you can `set FBT_NOENV=1`. `fbt` will skip toolchain & environment configuration and will expect all tools to be available on your system's `PATH`. *(this option is not available on Windows)* diff --git a/scripts/fbt_tools/fbt_apps.py b/scripts/fbt_tools/fbt_apps.py index 053a695037c..cbb3bf726ad 100644 --- a/scripts/fbt_tools/fbt_apps.py +++ b/scripts/fbt_tools/fbt_apps.py @@ -9,6 +9,7 @@ from SCons.Builder import Builder from SCons.Errors import StopError from SCons.Warnings import WarningOnByDefault, warn +from SCons.Script import GetOption # Adding objects for application management to env # AppManager env["APPMGR"] - loads all manifests; manages list of known apps @@ -28,7 +29,8 @@ def LoadAppManifest(env, entry): env["APPMGR"].load_manifest(app_manifest_file_path, entry) env.Append(PY_LINT_SOURCES=[app_manifest_file_path]) except FlipperManifestException as e: - warn(WarningOnByDefault, str(e)) + if not GetOption("silent"): + warn(WarningOnByDefault, str(e)) def PrepareApplicationsBuild(env): diff --git a/scripts/fbt_tools/fbt_help.py b/scripts/fbt_tools/fbt_help.py index afdb36665c7..c7452af9884 100644 --- a/scripts/fbt_tools/fbt_help.py +++ b/scripts/fbt_tools/fbt_help.py @@ -34,6 +34,9 @@ firmware_pvs: generate a PVS-Studio report +How to open a shell with toolchain environment and other build tools: + In your shell, type "source `./fbt -s env`". You can also use "." instead of "source". + For more targets & info, see documentation/fbt.md """ diff --git a/scripts/ufbt/SConstruct b/scripts/ufbt/SConstruct index fdb51981be8..3f623ebc82d 100644 --- a/scripts/ufbt/SConstruct +++ b/scripts/ufbt/SConstruct @@ -380,8 +380,9 @@ dist_env.Alias("vscode_dist", vscode_dist) # Creating app from base template dist_env.SetDefault(FBT_APPID=appenv.subst("$APPID") or "template") +app_template_dir = project_template_dir.Dir("app_template") app_template_dist = [] -for template_file in project_template_dir.Dir("app_template").glob("*"): +for template_file in app_template_dir.glob("*"): dist_file_name = dist_env.subst(template_file.name) if template_file.name.endswith(".png"): app_template_dist.append( @@ -397,12 +398,13 @@ for template_file in project_template_dir.Dir("app_template").glob("*"): }, ) ) - AddPostAction( app_template_dist[-1], [ Mkdir(original_app_dir.Dir("images")), Touch(original_app_dir.Dir("images").File(".gitkeep")), + # scons' glob ignores .dot directories, so we need to copy .github manually + Copy(original_app_dir.Dir(".github"), app_template_dir.Dir(".github")), ], ) dist_env.Precious(app_template_dist) @@ -440,3 +442,8 @@ else: raise UserError(f"Dolphin folder not found: {dolphin_src_dir}") dist_env.PhonyTarget("dolphin_ext", Action(missing_dolphin_folder, None)) + +dist_env.PhonyTarget( + "env", + "@echo $( ${FBT_SCRIPT_DIR}/toolchain/fbtenv.sh $)", +) diff --git a/scripts/ufbt/project_template/app_template/.github/workflows/build.yml b/scripts/ufbt/project_template/app_template/.github/workflows/build.yml new file mode 100644 index 00000000000..0834f83798f --- /dev/null +++ b/scripts/ufbt/project_template/app_template/.github/workflows/build.yml @@ -0,0 +1,41 @@ +name: "FAP: Build for multiple SDK sources" +# This will build your app for dev and release channels on GitHub. +# It will also build your app every day to make sure it's up to date with the latest SDK changes. +# See https://github.com/marketplace/actions/build-flipper-application-package-fap for more information + +on: + push: + ## put your main branch name under "braches" + #branches: + # - master + pull_request: + schedule: + # do a build every day + - cron: "1 1 * * *" + +jobs: + ufbt-build: + runs-on: ubuntu-latest + strategy: + matrix: + include: + - name: dev channel + sdk-channel: dev + - name: release channel + sdk-channel: release + # You can add unofficial channels here. See ufbt action docs for more info. + name: 'ufbt: Build for ${{ matrix.name }}' + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Build with ufbt + uses: flipperdevices/flipperzero-ufbt-action@v0.1.1 + id: build-app + with: + sdk-channel: ${{ matrix.sdk-channel }} + - name: Upload app artifacts + uses: actions/upload-artifact@v3 + with: + # See ufbt action docs for other output variables + name: ${{ github.event.repository.name }}-${{ steps.build-app.outputs.suffix }} + path: ${{ steps.build-app.outputs.fap-artifacts }} diff --git a/scripts/ufbt/site_tools/ufbt_help.py b/scripts/ufbt/site_tools/ufbt_help.py index da6ff6e51f4..3f13edcdbba 100644 --- a/scripts/ufbt/site_tools/ufbt_help.py +++ b/scripts/ufbt/site_tools/ufbt_help.py @@ -40,6 +40,9 @@ 2. Run `ufbt vscode_dist create APPID=myapp` 3. In VSCode, open the folder and start editing. 4. Run `ufbt launch` to build and upload your application. + +How to open a shell with toolchain environment and other build tools: + In your shell, type "source `ufbt -s env`". You can also use "." instead of "source". """ From 538f96f0ac43576383620b0faa0686ca35207b49 Mon Sep 17 00:00:00 2001 From: hedger Date: Mon, 8 May 2023 14:11:23 +0300 Subject: [PATCH 544/824] [FL-3300] API version in UI (#2649) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * desktop, about: replaced commit# with API version * scripts: storage: added approx speed for file uploads * scripts: selfupdate: use folder name by default * vscode: fixed broken get_blackmagic shell integration on *nix Co-authored-by: あく --- .vscode/example/launch.json | 7 +++--- .vscode/extensions.json | 5 +++-- .../desktop/views/desktop_view_debug.c | 7 ++++-- applications/settings/about/about.c | 8 +++++-- scripts/flipper/storage.py | 22 +++++++++++-------- scripts/selfupdate.py | 2 +- .../project_template/.vscode/extensions.json | 3 ++- 7 files changed, 33 insertions(+), 21 deletions(-) diff --git a/.vscode/example/launch.json b/.vscode/example/launch.json index f7a9f8269d3..4cab026fa2b 100644 --- a/.vscode/example/launch.json +++ b/.vscode/example/launch.json @@ -11,11 +11,10 @@ "args": { "useSingleResult": true, "env": { - "PATH": "${workspaceFolder};${env:PATH}", - "FBT_QUIET": 1 + "PATH": "${workspaceFolder}${command:extension.commandvariable.envListSep}${env:PATH}" }, - "command": "fbt get_blackmagic", - "description": "Get Blackmagic device", + "command": "fbt -s get_blackmagic", + "description": "Get Blackmagic device" } } ], diff --git a/.vscode/extensions.json b/.vscode/extensions.json index b5791a91e12..ead935b08b4 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -8,11 +8,12 @@ "amiralizadeh9480.cpp-helper", "marus25.cortex-debug", "zxh404.vscode-proto3", - "augustocdias.tasks-shell-input" + "augustocdias.tasks-shell-input", + "rioj7.command-variable" ], // List of extensions recommended by VS Code that should not be recommended for users of this workspace. "unwantedRecommendations": [ "twxs.cmake", "ms-vscode.cmake-tools" ] -} +} \ No newline at end of file diff --git a/applications/services/desktop/views/desktop_view_debug.c b/applications/services/desktop/views/desktop_view_debug.c index e679cf636e1..7a16c08479a 100644 --- a/applications/services/desktop/views/desktop_view_debug.c +++ b/applications/services/desktop/views/desktop_view_debug.c @@ -65,13 +65,16 @@ void desktop_debug_render(Canvas* canvas, void* model) { version_get_builddate(ver)); canvas_draw_str(canvas, 0, 30 + STATUS_BAR_Y_SHIFT, buffer); + uint16_t api_major, api_minor; + furi_hal_info_get_api_version(&api_major, &api_minor); snprintf( buffer, sizeof(buffer), - "%s%s [%s] %s", + "%s%s [%d.%d] %s", version_get_dirty_flag(ver) ? "[!] " : "", version_get_githash(ver), - version_get_gitbranchnum(ver), + api_major, + api_minor, c2_ver ? c2_ver->StackTypeString : ""); canvas_draw_str(canvas, 0, 40 + STATUS_BAR_Y_SHIFT, buffer); diff --git a/applications/settings/about/about.c b/applications/settings/about/about.c index 61c72496680..68810330688 100644 --- a/applications/settings/about/about.c +++ b/applications/settings/about/about.c @@ -7,6 +7,7 @@ #include #include #include +#include typedef DialogMessageButton (*AboutDialogScreen)(DialogsApp* dialogs, DialogMessage* message); @@ -134,14 +135,17 @@ static DialogMessageButton fw_version_screen(DialogsApp* dialogs, DialogMessage* if(!ver) { //-V1051 furi_string_cat_printf(buffer, "No info\n"); } else { + uint16_t api_major, api_minor; + furi_hal_info_get_api_version(&api_major, &api_minor); furi_string_cat_printf( buffer, - "%s [%s]\n%s%s [%s] %s\n[%d] %s", + "%s [%s]\n%s%s [%d.%d] %s\n[%d] %s", version_get_version(ver), version_get_builddate(ver), version_get_dirty_flag(ver) ? "[!] " : "", version_get_githash(ver), - version_get_gitbranchnum(ver), + api_major, + api_minor, c2_ver ? c2_ver->StackTypeString : "", version_get_target(ver), version_get_gitbranch(ver)); diff --git a/scripts/flipper/storage.py b/scripts/flipper/storage.py index 4640262bce9..f4d622bfe42 100644 --- a/scripts/flipper/storage.py +++ b/scripts/flipper/storage.py @@ -1,12 +1,13 @@ -import os -import sys -import serial -import time +import enum import hashlib -import math import logging +import math +import os import posixpath -import enum +import sys +import time + +import serial def timing(func): @@ -236,6 +237,7 @@ def send_file(self, filename_from: str, filename_to: str): filesize = os.fstat(file.fileno()).st_size buffer_size = self.chunk_size + start_time = time.time() while True: filedata = file.read(buffer_size) size = len(filedata) @@ -254,11 +256,13 @@ def send_file(self, filename_from: str, filename_to: str): self.port.write(filedata) self.read.until(self.CLI_PROMPT) - percent = str(math.ceil(file.tell() / filesize * 100)) + ftell = file.tell() + percent = str(math.ceil(ftell / filesize * 100)) total_chunks = str(math.ceil(filesize / buffer_size)) - current_chunk = str(math.ceil(file.tell() / buffer_size)) + current_chunk = str(math.ceil(ftell / buffer_size)) + approx_speed = ftell / (time.time() - start_time + 0.0001) sys.stdout.write( - f"\r{percent}%, chunk {current_chunk} of {total_chunks}" + f"\r{percent}%, chunk {current_chunk} of {total_chunks} @ {approx_speed/1024:.2f} kb/s" ) sys.stdout.flush() print() diff --git a/scripts/selfupdate.py b/scripts/selfupdate.py index 1ce0b83763f..d222bf24916 100644 --- a/scripts/selfupdate.py +++ b/scripts/selfupdate.py @@ -15,7 +15,7 @@ def init(self): self.parser.add_argument("manifest_path", help="Manifest path") self.parser.add_argument( - "--pkg_dir_name", help="Update dir name", default="pcbundle", required=False + "--pkg_dir_name", help="Update dir name", default=None, required=False ) self.parser.set_defaults(func=self.install) diff --git a/scripts/ufbt/project_template/.vscode/extensions.json b/scripts/ufbt/project_template/.vscode/extensions.json index 35f90700a3f..ead935b08b4 100644 --- a/scripts/ufbt/project_template/.vscode/extensions.json +++ b/scripts/ufbt/project_template/.vscode/extensions.json @@ -8,7 +8,8 @@ "amiralizadeh9480.cpp-helper", "marus25.cortex-debug", "zxh404.vscode-proto3", - "augustocdias.tasks-shell-input" + "augustocdias.tasks-shell-input", + "rioj7.command-variable" ], // List of extensions recommended by VS Code that should not be recommended for users of this workspace. "unwantedRecommendations": [ From 268b88be0d8fbdaca359c3e49411f4f5c699f64b Mon Sep 17 00:00:00 2001 From: hedger Date: Tue, 9 May 2023 00:30:33 +0300 Subject: [PATCH 545/824] [FL-3285] Removed STM32CubeWB module (#2608) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * libs: removed STM32CubeWB module; split cube into 3 submodules * fixed f18 version * fbt: options: fixed expected stack version * pvs: updated for new paths * fbt: ep: multithreaded submodule update * libs: stm32cubewb: fixed duplicate include path; renamed to stm32wb; codeowners: updated paths; docs: updated paths * pvs: updated paths * libs: added cmsis_core from ARM sources, v.5.4.0, from https://github.com/ARM-software/CMSIS_5/tree/develop/CMSIS/Core/Include * Updated stm32wb_copro structure * PVS: exclude cmsis core from analysis --------- Co-authored-by: あく --- .github/CODEOWNERS | 2 +- .gitmodules | 12 +- .pvsoptions | 2 +- documentation/Doxyfile | 4 +- documentation/OTA.md | 2 +- fbt | 3 +- fbt.cmd | 2 +- fbt_options.py | 11 +- firmware/SConscript | 2 +- firmware/targets/f18/api_symbols.csv | 58 +- firmware/targets/f18/target.json | 4 +- firmware/targets/f7/api_symbols.csv | 58 +- firmware/targets/f7/furi_hal/furi_hal_flash.c | 2 +- firmware/targets/f7/target.json | 4 +- lib/ReadMe.md | 4 +- lib/SConscript | 2 +- lib/STM32CubeWB | 1 - lib/STM32CubeWB.scons | 72 - lib/cmsis_core/cmsis_armcc.h | 894 +++++++ lib/cmsis_core/cmsis_armclang.h | 1510 +++++++++++ lib/cmsis_core/cmsis_armclang_ltm.h | 1934 ++++++++++++++ lib/cmsis_core/cmsis_compiler.h | 303 +++ lib/cmsis_core/cmsis_gcc.h | 2217 +++++++++++++++++ lib/cmsis_core/cmsis_iccarm.h | 1008 ++++++++ lib/cmsis_core/cmsis_tiarmclang.h | 1510 +++++++++++ lib/cmsis_core/cmsis_version.h | 39 + lib/cmsis_core/core_cm4.h | 2170 ++++++++++++++++ lib/cmsis_core/mpu_armv7.h | 275 ++ lib/stm32wb.scons | 70 + lib/stm32wb_cmsis | 1 + lib/stm32wb_copro | 1 + lib/stm32wb_hal | 1 + scripts/assets.py | 25 +- scripts/flipper/assets/copro.py | 43 +- scripts/flipper/assets/coprobin.py | 2 +- 35 files changed, 12059 insertions(+), 189 deletions(-) delete mode 160000 lib/STM32CubeWB delete mode 100644 lib/STM32CubeWB.scons create mode 100644 lib/cmsis_core/cmsis_armcc.h create mode 100644 lib/cmsis_core/cmsis_armclang.h create mode 100644 lib/cmsis_core/cmsis_armclang_ltm.h create mode 100644 lib/cmsis_core/cmsis_compiler.h create mode 100644 lib/cmsis_core/cmsis_gcc.h create mode 100644 lib/cmsis_core/cmsis_iccarm.h create mode 100644 lib/cmsis_core/cmsis_tiarmclang.h create mode 100644 lib/cmsis_core/cmsis_version.h create mode 100644 lib/cmsis_core/core_cm4.h create mode 100644 lib/cmsis_core/mpu_armv7.h create mode 100644 lib/stm32wb.scons create mode 160000 lib/stm32wb_cmsis create mode 160000 lib/stm32wb_copro create mode 160000 lib/stm32wb_hal diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index c1684aa99b3..bed54dfa045 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -56,7 +56,7 @@ # Lib /lib/ST25RFAL002/ @skotopes @DrZlo13 @hedger @gornekich -/lib/STM32CubeWB/ @skotopes @DrZlo13 @hedger @gornekich +/lib/stm32wb_copro/ @skotopes @DrZlo13 @hedger @gornekich /lib/digital_signal/ @skotopes @DrZlo13 @hedger @gornekich /lib/infrared/ @skotopes @DrZlo13 @hedger @gsurkov /lib/lfrfid/ @skotopes @DrZlo13 @hedger @nminaylov diff --git a/.gitmodules b/.gitmodules index 3a15177bd18..671e7e2c416 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +1,6 @@ [submodule "lib/mlib"] path = lib/mlib url = https://github.com/P-p-H-d/mlib.git -[submodule "lib/STM32CubeWB"] - path = lib/STM32CubeWB - url = https://github.com/Flipper-Zero/STM32CubeWB.git [submodule "lib/littlefs"] path = lib/littlefs url = https://github.com/littlefs-project/littlefs.git @@ -34,3 +31,12 @@ [submodule "lib/heatshrink"] path = lib/heatshrink url = https://github.com/flipperdevices/heatshrink.git +[submodule "lib/st_cmsis_device_wb"] + path = lib/stm32wb_cmsis + url = https://github.com/STMicroelectronics/cmsis_device_wb +[submodule "lib/stm32wbxx_hal_driver"] + path = lib/stm32wb_hal + url = https://github.com/STMicroelectronics/stm32wbxx_hal_driver +[submodule "lib/stm32wb_copro"] + path = lib/stm32wb_copro + url = https://github.com/flipperdevices/stm32wb_copro.git diff --git a/.pvsoptions b/.pvsoptions index 6b22aed765e..b25d4633e21 100644 --- a/.pvsoptions +++ b/.pvsoptions @@ -1 +1 @@ ---ignore-ccache -C gccarm --rules-config .pvsconfig -e lib/fatfs -e lib/fnv1a-hash -e lib/FreeRTOS-Kernel -e lib/heatshrink -e lib/libusb_stm32 -e lib/littlefs -e lib/mbedtls -e lib/micro-ecc -e lib/microtar -e lib/mlib -e lib/qrcode -e lib/ST25RFAL002 -e lib/STM32CubeWB -e lib/u8g2 -e lib/nanopb -e */arm-none-eabi/* -e applications/external/dap_link/lib/free-dap +--ignore-ccache -C gccarm --rules-config .pvsconfig -e lib/cmsis_core -e lib/fatfs -e lib/fnv1a-hash -e lib/FreeRTOS-Kernel -e lib/heatshrink -e lib/libusb_stm32 -e lib/littlefs -e lib/mbedtls -e lib/micro-ecc -e lib/microtar -e lib/mlib -e lib/qrcode -e lib/ST25RFAL002 -e lib/stm32wb_cmsis -e lib/stm32wb_copro -e lib/stm32wb_hal -e lib/u8g2 -e lib/nanopb -e */arm-none-eabi/* -e applications/external/dap_link/lib/free-dap diff --git a/documentation/Doxyfile b/documentation/Doxyfile index 9611e7f1a92..bb43ce8a773 100644 --- a/documentation/Doxyfile +++ b/documentation/Doxyfile @@ -929,7 +929,9 @@ RECURSIVE = YES EXCLUDE = \ lib/mlib \ - lib/STM32CubeWB \ + lib/stm32wb_cmsis \ + lib/stm32wb_copro \ + lib/stm32wb_hal_driver \ lib/littlefs \ lib/nanopb \ assets/protobuf \ diff --git a/documentation/OTA.md b/documentation/OTA.md index 9d09c0f7c08..799548f4d52 100644 --- a/documentation/OTA.md +++ b/documentation/OTA.md @@ -133,7 +133,7 @@ For example, to build a package only for installing BLE FULL stack: scripts/update.py generate \ -t f7 -d r13.3_full -v "BLE FULL 13.3" \ --stage dist/f7/flipper-z-f7-updater-*.bin \ - --radio lib/STM32CubeWB/Projects/STM32WB_Copro_Wireless_Binaries/STM32WB5x/stm32wb5x_BLE_Stack_full_fw.bin \ + --radio lib/stm32wb_copro/firmware/stm32wb5x_BLE_Stack_full_fw.bin \ --radiotype ble_full ``` diff --git a/fbt b/fbt index efe625f0340..ef41cc056b6 100755 --- a/fbt +++ b/fbt @@ -5,6 +5,7 @@ set -eu; # private variables +N_GIT_THREADS="$(getconf _NPROCESSORS_ONLN)"; SCRIPT_PATH="$(cd "$(dirname "$0")" && pwd -P)"; SCONS_DEFAULT_FLAGS="--warn=target-not-built"; SCONS_EP="python3 -m SCons"; @@ -28,7 +29,7 @@ if [ -z "$FBT_NO_SYNC" ]; then echo "\".git\" directory not found, please clone repo via \"git clone\""; exit 1; fi - git submodule update --init --depth 1; + git submodule update --init --depth 1 --jobs "$N_GIT_THREADS"; fi $SCONS_EP $SCONS_DEFAULT_FLAGS "$@" diff --git a/fbt.cmd b/fbt.cmd index 6e839c778eb..03e4ec3d094 100644 --- a/fbt.cmd +++ b/fbt.cmd @@ -5,7 +5,7 @@ set SCONS_EP=python -m SCons if [%FBT_NO_SYNC%] == [] ( if exist ".git" ( - git submodule update --init --depth 1 + git submodule update --init --depth 1 --jobs %NUMBER_OF_PROCESSORS% ) else ( echo Not in a git repo, please clone with "git clone" exit /b 1 diff --git a/fbt_options.py b/fbt_options.py index 4fd7ef496ea..d05b882a0ca 100644 --- a/fbt_options.py +++ b/fbt_options.py @@ -19,10 +19,10 @@ # Coprocessor firmware COPRO_OB_DATA = "scripts/ob.data" -# Must match lib/STM32CubeWB version +# Must match lib/stm32wb_copro version COPRO_CUBE_VERSION = "1.15.0" -COPRO_CUBE_DIR = "lib/STM32CubeWB" +COPRO_CUBE_DIR = "lib/stm32wb_copro" # Default radio stack COPRO_STACK_BIN = "stm32wb5x_BLE_Stack_light_fw.bin" @@ -33,12 +33,7 @@ COPRO_STACK_ADDR = "0x0" # If you override COPRO_CUBE_DIR on commandline, override this as well -COPRO_STACK_BIN_DIR = posixpath.join( - COPRO_CUBE_DIR, - "Projects", - "STM32WB_Copro_Wireless_Binaries", - "STM32WB5x", -) +COPRO_STACK_BIN_DIR = posixpath.join(COPRO_CUBE_DIR, "firmware") # Supported toolchain versions FBT_TOOLCHAIN_VERSIONS = (" 10.3.",) diff --git a/firmware/SConscript b/firmware/SConscript index fa96b0adf09..8d8789e7235 100644 --- a/firmware/SConscript +++ b/firmware/SConscript @@ -7,7 +7,7 @@ env.Append( libenv = env.Clone(FW_LIB_NAME="flipper${TARGET_HW}") libenv.Append( CPPPATH=[ - "#/lib/STM32CubeWB/Middlewares/ST/STM32_WPAN/interface/patterns/ble_thread/tl", + "#/lib/stm32wb_copro/wpan/interface/patterns/ble_thread/tl", ] ) libenv.ApplyLibFlags() diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index f83f6405be8..8db084e2fd5 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -78,33 +78,6 @@ Header,+,firmware/targets/furi_hal_include/furi_hal_usb_hid.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_usb_hid_u2f.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_version.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_vibro.h,, -Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_adc.h,, -Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_bus.h,, -Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_comp.h,, -Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_cortex.h,, -Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_crc.h,, -Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_crs.h,, -Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_dma.h,, -Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_dmamux.h,, -Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_exti.h,, -Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_gpio.h,, -Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_hsem.h,, -Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_i2c.h,, -Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_ipcc.h,, -Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_iwdg.h,, -Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_lptim.h,, -Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_lpuart.h,, -Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_pka.h,, -Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_pwr.h,, -Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_rcc.h,, -Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_rng.h,, -Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_rtc.h,, -Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_spi.h,, -Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_system.h,, -Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_tim.h,, -Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_usart.h,, -Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_utils.h,, -Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_wwdg.h,, Header,+,lib/flipper_application/api_hashtable/api_hashtable.h,, Header,+,lib/flipper_application/api_hashtable/compilesort.hpp,, Header,+,lib/flipper_application/flipper_application.h,, @@ -156,6 +129,33 @@ Header,+,lib/one_wire/maxim_crc.h,, Header,+,lib/one_wire/one_wire_host.h,, Header,+,lib/one_wire/one_wire_slave.h,, Header,+,lib/print/wrappers.h,, +Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_adc.h,, +Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_bus.h,, +Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_comp.h,, +Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_cortex.h,, +Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_crc.h,, +Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_crs.h,, +Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_dma.h,, +Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_dmamux.h,, +Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_exti.h,, +Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_gpio.h,, +Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_hsem.h,, +Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_i2c.h,, +Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_ipcc.h,, +Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_iwdg.h,, +Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_lptim.h,, +Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_lpuart.h,, +Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_pka.h,, +Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_pwr.h,, +Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_rcc.h,, +Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_rng.h,, +Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_rtc.h,, +Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_spi.h,, +Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_system.h,, +Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_tim.h,, +Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_usart.h,, +Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_utils.h,, +Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_wwdg.h,, Header,+,lib/toolbox/args.h,, Header,+,lib/toolbox/crc32_calc.h,, Header,+,lib/toolbox/dir_walk.h,, @@ -201,8 +201,8 @@ Function,-,LL_EXTI_StructInit,void,LL_EXTI_InitTypeDef* Function,-,LL_GPIO_DeInit,ErrorStatus,GPIO_TypeDef* Function,+,LL_GPIO_Init,ErrorStatus,"GPIO_TypeDef*, LL_GPIO_InitTypeDef*" Function,-,LL_GPIO_StructInit,void,LL_GPIO_InitTypeDef* -Function,-,LL_I2C_DeInit,ErrorStatus,I2C_TypeDef* -Function,+,LL_I2C_Init,ErrorStatus,"I2C_TypeDef*, LL_I2C_InitTypeDef*" +Function,-,LL_I2C_DeInit,ErrorStatus,const I2C_TypeDef* +Function,+,LL_I2C_Init,ErrorStatus,"I2C_TypeDef*, const LL_I2C_InitTypeDef*" Function,-,LL_I2C_StructInit,void,LL_I2C_InitTypeDef* Function,-,LL_Init1msTick,void,uint32_t Function,+,LL_LPTIM_DeInit,ErrorStatus,LPTIM_TypeDef* diff --git a/firmware/targets/f18/target.json b/firmware/targets/f18/target.json index f1963fb0125..14d395d2227 100644 --- a/firmware/targets/f18/target.json +++ b/firmware/targets/f18/target.json @@ -14,7 +14,7 @@ "flipper18", "furi", "freertos", - "stm32cubewb", + "stm32wb", "hwdrivers", "fatfs", "littlefs", @@ -53,4 +53,4 @@ "infrared", "st25rfal002" ] -} +} \ No newline at end of file diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 8a4e06303f7..64383560873 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -84,33 +84,6 @@ Header,+,firmware/targets/furi_hal_include/furi_hal_usb_hid.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_usb_hid_u2f.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_version.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_vibro.h,, -Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_adc.h,, -Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_bus.h,, -Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_comp.h,, -Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_cortex.h,, -Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_crc.h,, -Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_crs.h,, -Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_dma.h,, -Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_dmamux.h,, -Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_exti.h,, -Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_gpio.h,, -Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_hsem.h,, -Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_i2c.h,, -Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_ipcc.h,, -Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_iwdg.h,, -Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_lptim.h,, -Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_lpuart.h,, -Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_pka.h,, -Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_pwr.h,, -Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_rcc.h,, -Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_rng.h,, -Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_rtc.h,, -Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_spi.h,, -Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_system.h,, -Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_tim.h,, -Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_usart.h,, -Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_utils.h,, -Header,+,lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/stm32wbxx_ll_wwdg.h,, Header,+,lib/flipper_application/api_hashtable/api_hashtable.h,, Header,+,lib/flipper_application/api_hashtable/compilesort.hpp,, Header,+,lib/flipper_application/flipper_application.h,, @@ -175,6 +148,33 @@ Header,+,lib/one_wire/maxim_crc.h,, Header,+,lib/one_wire/one_wire_host.h,, Header,+,lib/one_wire/one_wire_slave.h,, Header,+,lib/print/wrappers.h,, +Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_adc.h,, +Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_bus.h,, +Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_comp.h,, +Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_cortex.h,, +Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_crc.h,, +Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_crs.h,, +Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_dma.h,, +Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_dmamux.h,, +Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_exti.h,, +Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_gpio.h,, +Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_hsem.h,, +Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_i2c.h,, +Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_ipcc.h,, +Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_iwdg.h,, +Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_lptim.h,, +Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_lpuart.h,, +Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_pka.h,, +Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_pwr.h,, +Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_rcc.h,, +Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_rng.h,, +Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_rtc.h,, +Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_spi.h,, +Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_system.h,, +Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_tim.h,, +Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_usart.h,, +Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_utils.h,, +Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_wwdg.h,, Header,+,lib/subghz/blocks/const.h,, Header,+,lib/subghz/blocks/decoder.h,, Header,+,lib/subghz/blocks/encoder.h,, @@ -233,8 +233,8 @@ Function,-,LL_EXTI_StructInit,void,LL_EXTI_InitTypeDef* Function,-,LL_GPIO_DeInit,ErrorStatus,GPIO_TypeDef* Function,+,LL_GPIO_Init,ErrorStatus,"GPIO_TypeDef*, LL_GPIO_InitTypeDef*" Function,-,LL_GPIO_StructInit,void,LL_GPIO_InitTypeDef* -Function,-,LL_I2C_DeInit,ErrorStatus,I2C_TypeDef* -Function,+,LL_I2C_Init,ErrorStatus,"I2C_TypeDef*, LL_I2C_InitTypeDef*" +Function,-,LL_I2C_DeInit,ErrorStatus,const I2C_TypeDef* +Function,+,LL_I2C_Init,ErrorStatus,"I2C_TypeDef*, const LL_I2C_InitTypeDef*" Function,-,LL_I2C_StructInit,void,LL_I2C_InitTypeDef* Function,-,LL_Init1msTick,void,uint32_t Function,+,LL_LPTIM_DeInit,ErrorStatus,LPTIM_TypeDef* diff --git a/firmware/targets/f7/furi_hal/furi_hal_flash.c b/firmware/targets/f7/furi_hal/furi_hal_flash.c index 464d88d9593..94d269345ba 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_flash.c +++ b/firmware/targets/f7/furi_hal/furi_hal_flash.c @@ -27,7 +27,7 @@ #define FURI_HAL_FLASH_OPT_KEY2 0x4C5D6E7F #define FURI_HAL_FLASH_OB_TOTAL_WORDS (0x80 / (sizeof(uint32_t) * 2)) -/* lib/STM32CubeWB/Projects/P-NUCLEO-WB55.Nucleo/Applications/BLE/BLE_RfWithFlash/Core/Src/flash_driver.c +/* STM32CubeWB/Projects/P-NUCLEO-WB55.Nucleo/Applications/BLE/BLE_RfWithFlash/Core/Src/flash_driver.c * ProcessSingleFlashOperation, quote: > In most BLE application, the flash should not be blocked by the CPU2 longer than FLASH_TIMEOUT_VALUE (1000ms) > However, it could be that for some marginal application, this time is longer. diff --git a/firmware/targets/f7/target.json b/firmware/targets/f7/target.json index 14bb1cd0c21..4d97ae7d74b 100644 --- a/firmware/targets/f7/target.json +++ b/firmware/targets/f7/target.json @@ -20,7 +20,7 @@ "flipper7", "furi", "freertos", - "stm32cubewb", + "stm32wb", "hwdrivers", "fatfs", "littlefs", @@ -43,4 +43,4 @@ "flipperformat", "toolbox" ] -} +} \ No newline at end of file diff --git a/lib/ReadMe.md b/lib/ReadMe.md index 82dcb74c7aa..884a0b8c083 100644 --- a/lib/ReadMe.md +++ b/lib/ReadMe.md @@ -19,7 +19,9 @@ - `one_wire` - One wire library - `qrcode` - Qr code generator library - `ST25RFAL002` - ST253916 driver and NFC hal -- `STM32CubeWB` - STM32WB series cube package +- `stm32wb_cmsis` - STM32WB series CMSIS component +- `stm32wb_copro` - STM32WB Coprocessor fimrware + WPAN library +- `stm32wb_hal_driver` - STM32WB series HAL - `subghz` - SubGhz library - `toolbox` - Toolbox of things that we are using but don't place in core - `u8g2` - Graphics library that we use to draw GUI diff --git a/lib/SConscript b/lib/SConscript index f5d4689f1cf..b8a36c87280 100644 --- a/lib/SConscript +++ b/lib/SConscript @@ -76,7 +76,7 @@ env.Append( libs = env.BuildModules( [ - "STM32CubeWB", + "stm32wb", "freertos", "print", "microtar", diff --git a/lib/STM32CubeWB b/lib/STM32CubeWB deleted file mode 160000 index c4cec8ae57a..00000000000 --- a/lib/STM32CubeWB +++ /dev/null @@ -1 +0,0 @@ -Subproject commit c4cec8ae57a79e949a184cd0b4117a008a0a25a7 diff --git a/lib/STM32CubeWB.scons b/lib/STM32CubeWB.scons deleted file mode 100644 index b0e55f82eda..00000000000 --- a/lib/STM32CubeWB.scons +++ /dev/null @@ -1,72 +0,0 @@ -Import("env") - -env.Append( - CPPPATH=[ - "#/lib/STM32CubeWB/Drivers/CMSIS/Device/ST/STM32WBxx/Include", - "#/lib/STM32CubeWB/Drivers/CMSIS/Include", - "#/lib/STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc", - "#/lib/STM32CubeWB/Middlewares/ST/STM32_WPAN", - ], - CPPDEFINES=[ - "STM32WB", - "STM32WB55xx", - "USE_FULL_ASSERT", - "USE_FULL_LL_DRIVER", - ], - SDK_HEADERS=env.GlobRecursive( - "*_ll_*.h", - Dir("STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Inc/"), - exclude="*usb.h", - ), -) - -if env["RAM_EXEC"]: - env.Append( - CPPDEFINES=[ - "VECT_TAB_SRAM", - ], - ) - - -libenv = env.Clone(FW_LIB_NAME="stm32cubewb") -libenv.Append( - CPPPATH=[ - "#/lib/STM32CubeWB/Middlewares/ST/STM32_WPAN/ble", - "#/lib/STM32CubeWB/Middlewares/ST/STM32_WPAN/ble/core", - "#/lib/STM32CubeWB/Middlewares/ST/STM32_WPAN/interface/patterns/ble_thread", - "#/lib/STM32CubeWB/Middlewares/ST/STM32_WPAN/interface/patterns/ble_thread/shci", - "#/lib/STM32CubeWB/Middlewares/ST/STM32_WPAN/interface/patterns/ble_thread/tl", - "#/lib/STM32CubeWB/Middlewares/ST/STM32_WPAN/utilities", - ] -) -libenv.ApplyLibFlags() - -sources = libenv.GlobRecursive( - "*_ll_*.c", "STM32CubeWB/Drivers/STM32WBxx_HAL_Driver/Src/", exclude="*usb.c" -) -sources += Glob( - "STM32CubeWB/Middlewares/ST/STM32_WPAN/interface/patterns/ble_thread/shci/*.c", - source=True, -) -sources += Glob( - "STM32CubeWB/Middlewares/ST/STM32_WPAN/interface/patterns/ble_thread/tl/*_tl*.c", - source=True, -) -sources += [ - "STM32CubeWB/Middlewares/ST/STM32_WPAN/interface/patterns/ble_thread/tl/tl_mbox.c", - "STM32CubeWB/Middlewares/ST/STM32_WPAN/ble/svc/Src/svc_ctl.c", - "STM32CubeWB/Middlewares/ST/STM32_WPAN/ble/core/auto/ble_gap_aci.c", - "STM32CubeWB/Middlewares/ST/STM32_WPAN/ble/core/auto/ble_gatt_aci.c", - "STM32CubeWB/Middlewares/ST/STM32_WPAN/ble/core/auto/ble_hal_aci.c", - "STM32CubeWB/Middlewares/ST/STM32_WPAN/ble/core/auto/ble_hci_le.c", - "STM32CubeWB/Middlewares/ST/STM32_WPAN/ble/core/auto/ble_l2cap_aci.c", - "STM32CubeWB/Middlewares/ST/STM32_WPAN/ble/core/template/osal.c", - "STM32CubeWB/Middlewares/ST/STM32_WPAN/utilities/dbg_trace.c", - "STM32CubeWB/Middlewares/ST/STM32_WPAN/utilities/otp.c", - "STM32CubeWB/Middlewares/ST/STM32_WPAN/utilities/stm_list.c", -] - - -lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources) -libenv.Install("${LIB_DIST_DIR}", lib) -Return("lib") diff --git a/lib/cmsis_core/cmsis_armcc.h b/lib/cmsis_core/cmsis_armcc.h new file mode 100644 index 00000000000..2edff5af552 --- /dev/null +++ b/lib/cmsis_core/cmsis_armcc.h @@ -0,0 +1,894 @@ +/**************************************************************************//** + * @file cmsis_armcc.h + * @brief CMSIS compiler ARMCC (Arm Compiler 5) header file + * @version V5.4.0 + * @date 20. January 2023 + ******************************************************************************/ +/* + * Copyright (c) 2009-2023 Arm Limited. All rights reserved. + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an AS IS BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __CMSIS_ARMCC_H +#define __CMSIS_ARMCC_H + + +#if defined(__ARMCC_VERSION) && (__ARMCC_VERSION < 400677) + #error "Please use Arm Compiler Toolchain V4.0.677 or later!" +#endif + +/* CMSIS compiler control architecture macros */ +#if ((defined (__TARGET_ARCH_6_M ) && (__TARGET_ARCH_6_M == 1)) || \ + (defined (__TARGET_ARCH_6S_M ) && (__TARGET_ARCH_6S_M == 1)) ) + #define __ARM_ARCH_6M__ 1 +#endif + +#if (defined (__TARGET_ARCH_7_M ) && (__TARGET_ARCH_7_M == 1)) + #define __ARM_ARCH_7M__ 1 +#endif + +#if (defined (__TARGET_ARCH_7E_M) && (__TARGET_ARCH_7E_M == 1)) + #define __ARM_ARCH_7EM__ 1 +#endif + + /* __ARM_ARCH_8M_BASE__ not applicable */ + /* __ARM_ARCH_8M_MAIN__ not applicable */ + /* __ARM_ARCH_8_1M_MAIN__ not applicable */ + +/* CMSIS compiler control DSP macros */ +#if ((defined (__ARM_ARCH_7EM__) && (__ARM_ARCH_7EM__ == 1)) ) + #define __ARM_FEATURE_DSP 1 +#endif + +/* CMSIS compiler specific defines */ +#ifndef __ASM + #define __ASM __asm +#endif +#ifndef __INLINE + #define __INLINE __inline +#endif +#ifndef __STATIC_INLINE + #define __STATIC_INLINE static __inline +#endif +#ifndef __STATIC_FORCEINLINE + #define __STATIC_FORCEINLINE static __forceinline +#endif +#ifndef __NO_RETURN + #define __NO_RETURN __declspec(noreturn) +#endif +#ifndef __USED + #define __USED __attribute__((used)) +#endif +#ifndef __WEAK + #define __WEAK __attribute__((weak)) +#endif +#ifndef __PACKED + #define __PACKED __attribute__((packed)) +#endif +#ifndef __PACKED_STRUCT + #define __PACKED_STRUCT __packed struct +#endif +#ifndef __PACKED_UNION + #define __PACKED_UNION __packed union +#endif +#ifndef __UNALIGNED_UINT32 /* deprecated */ + #define __UNALIGNED_UINT32(x) (*((__packed uint32_t *)(x))) +#endif +#ifndef __UNALIGNED_UINT16_WRITE + #define __UNALIGNED_UINT16_WRITE(addr, val) ((*((__packed uint16_t *)(addr))) = (val)) +#endif +#ifndef __UNALIGNED_UINT16_READ + #define __UNALIGNED_UINT16_READ(addr) (*((const __packed uint16_t *)(addr))) +#endif +#ifndef __UNALIGNED_UINT32_WRITE + #define __UNALIGNED_UINT32_WRITE(addr, val) ((*((__packed uint32_t *)(addr))) = (val)) +#endif +#ifndef __UNALIGNED_UINT32_READ + #define __UNALIGNED_UINT32_READ(addr) (*((const __packed uint32_t *)(addr))) +#endif +#ifndef __ALIGNED + #define __ALIGNED(x) __attribute__((aligned(x))) +#endif +#ifndef __RESTRICT + #define __RESTRICT __restrict +#endif +#ifndef __COMPILER_BARRIER + #define __COMPILER_BARRIER() __memory_changed() +#endif +#ifndef __NO_INIT + #define __NO_INIT __attribute__ ((section (".bss.noinit"), zero_init)) +#endif +#ifndef __ALIAS + #define __ALIAS(x) __attribute__ ((alias(x))) +#endif + +/* ######################### Startup and Lowlevel Init ######################## */ + +#ifndef __PROGRAM_START +#define __PROGRAM_START __main +#endif + +#ifndef __INITIAL_SP +#define __INITIAL_SP Image$$ARM_LIB_STACK$$ZI$$Limit +#endif + +#ifndef __STACK_LIMIT +#define __STACK_LIMIT Image$$ARM_LIB_STACK$$ZI$$Base +#endif + +#ifndef __VECTOR_TABLE +#define __VECTOR_TABLE __Vectors +#endif + +#ifndef __VECTOR_TABLE_ATTRIBUTE +#define __VECTOR_TABLE_ATTRIBUTE __attribute__((used, section("RESET"))) +#endif + +/* ########################## Core Instruction Access ######################### */ +/** \defgroup CMSIS_Core_InstructionInterface CMSIS Core Instruction Interface + Access to dedicated instructions + @{ +*/ + +/** + \brief No Operation + \details No Operation does nothing. This instruction can be used for code alignment purposes. + */ +#define __NOP __nop + + +/** + \brief Wait For Interrupt + \details Wait For Interrupt is a hint instruction that suspends execution until one of a number of events occurs. + */ +#define __WFI __wfi + + +/** + \brief Wait For Event + \details Wait For Event is a hint instruction that permits the processor to enter + a low-power state until one of a number of events occurs. + */ +#define __WFE __wfe + + +/** + \brief Send Event + \details Send Event is a hint instruction. It causes an event to be signaled to the CPU. + */ +#define __SEV __sev + + +/** + \brief Instruction Synchronization Barrier + \details Instruction Synchronization Barrier flushes the pipeline in the processor, + so that all instructions following the ISB are fetched from cache or memory, + after the instruction has been completed. + */ +#define __ISB() __isb(0xF) + +/** + \brief Data Synchronization Barrier + \details Acts as a special kind of Data Memory Barrier. + It completes when all explicit memory accesses before this instruction complete. + */ +#define __DSB() __dsb(0xF) + +/** + \brief Data Memory Barrier + \details Ensures the apparent order of the explicit memory operations before + and after the instruction, without ensuring their completion. + */ +#define __DMB() __dmb(0xF) + + +/** + \brief Reverse byte order (32 bit) + \details Reverses the byte order in unsigned integer value. For example, 0x12345678 becomes 0x78563412. + \param [in] value Value to reverse + \return Reversed value + */ +#define __REV __rev + + +/** + \brief Reverse byte order (16 bit) + \details Reverses the byte order within each halfword of a word. For example, 0x12345678 becomes 0x34127856. + \param [in] value Value to reverse + \return Reversed value + */ +#ifndef __NO_EMBEDDED_ASM +__attribute__((section(".rev16_text"))) __STATIC_INLINE __ASM uint32_t __REV16(uint32_t value) +{ + rev16 r0, r0 + bx lr +} +#endif + + +/** + \brief Reverse byte order (16 bit) + \details Reverses the byte order in a 16-bit value and returns the signed 16-bit result. For example, 0x0080 becomes 0x8000. + \param [in] value Value to reverse + \return Reversed value + */ +#ifndef __NO_EMBEDDED_ASM +__attribute__((section(".revsh_text"))) __STATIC_INLINE __ASM int16_t __REVSH(int16_t value) +{ + revsh r0, r0 + bx lr +} +#endif + + +/** + \brief Rotate Right in unsigned value (32 bit) + \details Rotate Right (immediate) provides the value of the contents of a register rotated by a variable number of bits. + \param [in] op1 Value to rotate + \param [in] op2 Number of Bits to rotate + \return Rotated value + */ +#define __ROR __ror + + +/** + \brief Breakpoint + \details Causes the processor to enter Debug state. + Debug tools can use this to investigate system state when the instruction at a particular address is reached. + \param [in] value is ignored by the processor. + If required, a debugger can use it to store additional information about the breakpoint. + */ +#define __BKPT(value) __breakpoint(value) + + +/** + \brief Reverse bit order of value + \details Reverses the bit order of the given value. + \param [in] value Value to reverse + \return Reversed value + */ +#if ((defined (__ARM_ARCH_7M__ ) && (__ARM_ARCH_7M__ == 1)) || \ + (defined (__ARM_ARCH_7EM__) && (__ARM_ARCH_7EM__ == 1)) ) + #define __RBIT __rbit +#else +__attribute__((always_inline)) __STATIC_INLINE uint32_t __RBIT(uint32_t value) +{ + uint32_t result; + uint32_t s = (4U /*sizeof(v)*/ * 8U) - 1U; /* extra shift needed at end */ + + result = value; /* r will be reversed bits of v; first get LSB of v */ + for (value >>= 1U; value != 0U; value >>= 1U) + { + result <<= 1U; + result |= value & 1U; + s--; + } + result <<= s; /* shift when v's highest bits are zero */ + return result; +} +#endif + + +/** + \brief Count leading zeros + \details Counts the number of leading zeros of a data value. + \param [in] value Value to count the leading zeros + \return number of leading zeros in value + */ +#define __CLZ __clz + + +#if ((defined (__ARM_ARCH_7M__ ) && (__ARM_ARCH_7M__ == 1)) || \ + (defined (__ARM_ARCH_7EM__) && (__ARM_ARCH_7EM__ == 1)) ) + +/** + \brief LDR Exclusive (8 bit) + \details Executes a exclusive LDR instruction for 8 bit value. + \param [in] ptr Pointer to data + \return value of type uint8_t at (*ptr) + */ +#if defined(__ARMCC_VERSION) && (__ARMCC_VERSION < 5060020) + #define __LDREXB(ptr) ((uint8_t ) __ldrex(ptr)) +#else + #define __LDREXB(ptr) _Pragma("push") _Pragma("diag_suppress 3731") ((uint8_t ) __ldrex(ptr)) _Pragma("pop") +#endif + + +/** + \brief LDR Exclusive (16 bit) + \details Executes a exclusive LDR instruction for 16 bit values. + \param [in] ptr Pointer to data + \return value of type uint16_t at (*ptr) + */ +#if defined(__ARMCC_VERSION) && (__ARMCC_VERSION < 5060020) + #define __LDREXH(ptr) ((uint16_t) __ldrex(ptr)) +#else + #define __LDREXH(ptr) _Pragma("push") _Pragma("diag_suppress 3731") ((uint16_t) __ldrex(ptr)) _Pragma("pop") +#endif + + +/** + \brief LDR Exclusive (32 bit) + \details Executes a exclusive LDR instruction for 32 bit values. + \param [in] ptr Pointer to data + \return value of type uint32_t at (*ptr) + */ +#if defined(__ARMCC_VERSION) && (__ARMCC_VERSION < 5060020) + #define __LDREXW(ptr) ((uint32_t ) __ldrex(ptr)) +#else + #define __LDREXW(ptr) _Pragma("push") _Pragma("diag_suppress 3731") ((uint32_t ) __ldrex(ptr)) _Pragma("pop") +#endif + + +/** + \brief STR Exclusive (8 bit) + \details Executes a exclusive STR instruction for 8 bit values. + \param [in] value Value to store + \param [in] ptr Pointer to location + \return 0 Function succeeded + \return 1 Function failed + */ +#if defined(__ARMCC_VERSION) && (__ARMCC_VERSION < 5060020) + #define __STREXB(value, ptr) __strex(value, ptr) +#else + #define __STREXB(value, ptr) _Pragma("push") _Pragma("diag_suppress 3731") __strex(value, ptr) _Pragma("pop") +#endif + + +/** + \brief STR Exclusive (16 bit) + \details Executes a exclusive STR instruction for 16 bit values. + \param [in] value Value to store + \param [in] ptr Pointer to location + \return 0 Function succeeded + \return 1 Function failed + */ +#if defined(__ARMCC_VERSION) && (__ARMCC_VERSION < 5060020) + #define __STREXH(value, ptr) __strex(value, ptr) +#else + #define __STREXH(value, ptr) _Pragma("push") _Pragma("diag_suppress 3731") __strex(value, ptr) _Pragma("pop") +#endif + + +/** + \brief STR Exclusive (32 bit) + \details Executes a exclusive STR instruction for 32 bit values. + \param [in] value Value to store + \param [in] ptr Pointer to location + \return 0 Function succeeded + \return 1 Function failed + */ +#if defined(__ARMCC_VERSION) && (__ARMCC_VERSION < 5060020) + #define __STREXW(value, ptr) __strex(value, ptr) +#else + #define __STREXW(value, ptr) _Pragma("push") _Pragma("diag_suppress 3731") __strex(value, ptr) _Pragma("pop") +#endif + + +/** + \brief Remove the exclusive lock + \details Removes the exclusive lock which is created by LDREX. + */ +#define __CLREX __clrex + + +/** + \brief Signed Saturate + \details Saturates a signed value. + \param [in] value Value to be saturated + \param [in] sat Bit position to saturate to (1..32) + \return Saturated value + */ +#define __SSAT __ssat + + +/** + \brief Unsigned Saturate + \details Saturates an unsigned value. + \param [in] value Value to be saturated + \param [in] sat Bit position to saturate to (0..31) + \return Saturated value + */ +#define __USAT __usat + + +/** + \brief Rotate Right with Extend (32 bit) + \details Moves each bit of a bitstring right by one bit. + The carry input is shifted in at the left end of the bitstring. + \param [in] value Value to rotate + \return Rotated value + */ +#ifndef __NO_EMBEDDED_ASM +__attribute__((section(".rrx_text"))) __STATIC_INLINE __ASM uint32_t __RRX(uint32_t value) +{ + rrx r0, r0 + bx lr +} +#endif + + +/** + \brief LDRT Unprivileged (8 bit) + \details Executes a Unprivileged LDRT instruction for 8 bit value. + \param [in] ptr Pointer to data + \return value of type uint8_t at (*ptr) + */ +#define __LDRBT(ptr) ((uint8_t ) __ldrt(ptr)) + + +/** + \brief LDRT Unprivileged (16 bit) + \details Executes a Unprivileged LDRT instruction for 16 bit values. + \param [in] ptr Pointer to data + \return value of type uint16_t at (*ptr) + */ +#define __LDRHT(ptr) ((uint16_t) __ldrt(ptr)) + + +/** + \brief LDRT Unprivileged (32 bit) + \details Executes a Unprivileged LDRT instruction for 32 bit values. + \param [in] ptr Pointer to data + \return value of type uint32_t at (*ptr) + */ +#define __LDRT(ptr) ((uint32_t ) __ldrt(ptr)) + + +/** + \brief STRT Unprivileged (8 bit) + \details Executes a Unprivileged STRT instruction for 8 bit values. + \param [in] value Value to store + \param [in] ptr Pointer to location + */ +#define __STRBT(value, ptr) __strt(value, ptr) + + +/** + \brief STRT Unprivileged (16 bit) + \details Executes a Unprivileged STRT instruction for 16 bit values. + \param [in] value Value to store + \param [in] ptr Pointer to location + */ +#define __STRHT(value, ptr) __strt(value, ptr) + + +/** + \brief STRT Unprivileged (32 bit) + \details Executes a Unprivileged STRT instruction for 32 bit values. + \param [in] value Value to store + \param [in] ptr Pointer to location + */ +#define __STRT(value, ptr) __strt(value, ptr) + +#else /* ((defined (__ARM_ARCH_7M__ ) && (__ARM_ARCH_7M__ == 1)) || \ + (defined (__ARM_ARCH_7EM__) && (__ARM_ARCH_7EM__ == 1)) ) */ + +/** + \brief Signed Saturate + \details Saturates a signed value. + \param [in] value Value to be saturated + \param [in] sat Bit position to saturate to (1..32) + \return Saturated value + */ +__attribute__((always_inline)) __STATIC_INLINE int32_t __SSAT(int32_t val, uint32_t sat) +{ + if ((sat >= 1U) && (sat <= 32U)) + { + const int32_t max = (int32_t)((1U << (sat - 1U)) - 1U); + const int32_t min = -1 - max ; + if (val > max) + { + return max; + } + else if (val < min) + { + return min; + } + } + return val; +} + +/** + \brief Unsigned Saturate + \details Saturates an unsigned value. + \param [in] value Value to be saturated + \param [in] sat Bit position to saturate to (0..31) + \return Saturated value + */ +__attribute__((always_inline)) __STATIC_INLINE uint32_t __USAT(int32_t val, uint32_t sat) +{ + if (sat <= 31U) + { + const uint32_t max = ((1U << sat) - 1U); + if (val > (int32_t)max) + { + return max; + } + else if (val < 0) + { + return 0U; + } + } + return (uint32_t)val; +} + +#endif /* ((defined (__ARM_ARCH_7M__ ) && (__ARM_ARCH_7M__ == 1)) || \ + (defined (__ARM_ARCH_7EM__) && (__ARM_ARCH_7EM__ == 1)) ) */ + +/*@}*/ /* end of group CMSIS_Core_InstructionInterface */ + + +/* ########################### Core Function Access ########################### */ +/** \ingroup CMSIS_Core_FunctionInterface + \defgroup CMSIS_Core_RegAccFunctions CMSIS Core Register Access Functions + @{ + */ + +/** + \brief Enable IRQ Interrupts + \details Enables IRQ interrupts by clearing special-purpose register PRIMASK. + Can only be executed in Privileged modes. + */ +/* intrinsic void __enable_irq(); */ + + +/** + \brief Disable IRQ Interrupts + \details Disables IRQ interrupts by setting special-purpose register PRIMASK. + Can only be executed in Privileged modes. + */ +/* intrinsic void __disable_irq(); */ + +/** + \brief Get Control Register + \details Returns the content of the Control Register. + \return Control Register value + */ +__STATIC_INLINE uint32_t __get_CONTROL(void) +{ + register uint32_t __regControl __ASM("control"); + return(__regControl); +} + + +/** + \brief Set Control Register + \details Writes the given value to the Control Register. + \param [in] control Control Register value to set + */ +__STATIC_INLINE void __set_CONTROL(uint32_t control) +{ + register uint32_t __regControl __ASM("control"); + __regControl = control; + __ISB(); +} + + +/** + \brief Get IPSR Register + \details Returns the content of the IPSR Register. + \return IPSR Register value + */ +__STATIC_INLINE uint32_t __get_IPSR(void) +{ + register uint32_t __regIPSR __ASM("ipsr"); + return(__regIPSR); +} + + +/** + \brief Get APSR Register + \details Returns the content of the APSR Register. + \return APSR Register value + */ +__STATIC_INLINE uint32_t __get_APSR(void) +{ + register uint32_t __regAPSR __ASM("apsr"); + return(__regAPSR); +} + + +/** + \brief Get xPSR Register + \details Returns the content of the xPSR Register. + \return xPSR Register value + */ +__STATIC_INLINE uint32_t __get_xPSR(void) +{ + register uint32_t __regXPSR __ASM("xpsr"); + return(__regXPSR); +} + + +/** + \brief Get Process Stack Pointer + \details Returns the current value of the Process Stack Pointer (PSP). + \return PSP Register value + */ +__STATIC_INLINE uint32_t __get_PSP(void) +{ + register uint32_t __regProcessStackPointer __ASM("psp"); + return(__regProcessStackPointer); +} + + +/** + \brief Set Process Stack Pointer + \details Assigns the given value to the Process Stack Pointer (PSP). + \param [in] topOfProcStack Process Stack Pointer value to set + */ +__STATIC_INLINE void __set_PSP(uint32_t topOfProcStack) +{ + register uint32_t __regProcessStackPointer __ASM("psp"); + __regProcessStackPointer = topOfProcStack; +} + + +/** + \brief Get Main Stack Pointer + \details Returns the current value of the Main Stack Pointer (MSP). + \return MSP Register value + */ +__STATIC_INLINE uint32_t __get_MSP(void) +{ + register uint32_t __regMainStackPointer __ASM("msp"); + return(__regMainStackPointer); +} + + +/** + \brief Set Main Stack Pointer + \details Assigns the given value to the Main Stack Pointer (MSP). + \param [in] topOfMainStack Main Stack Pointer value to set + */ +__STATIC_INLINE void __set_MSP(uint32_t topOfMainStack) +{ + register uint32_t __regMainStackPointer __ASM("msp"); + __regMainStackPointer = topOfMainStack; +} + + +/** + \brief Get Priority Mask + \details Returns the current state of the priority mask bit from the Priority Mask Register. + \return Priority Mask value + */ +__STATIC_INLINE uint32_t __get_PRIMASK(void) +{ + register uint32_t __regPriMask __ASM("primask"); + return(__regPriMask); +} + + +/** + \brief Set Priority Mask + \details Assigns the given value to the Priority Mask Register. + \param [in] priMask Priority Mask + */ +__STATIC_INLINE void __set_PRIMASK(uint32_t priMask) +{ + register uint32_t __regPriMask __ASM("primask"); + __regPriMask = (priMask); +} + + +#if ((defined (__ARM_ARCH_7M__ ) && (__ARM_ARCH_7M__ == 1)) || \ + (defined (__ARM_ARCH_7EM__) && (__ARM_ARCH_7EM__ == 1)) ) + +/** + \brief Enable FIQ + \details Enables FIQ interrupts by clearing special-purpose register FAULTMASK. + Can only be executed in Privileged modes. + */ +#define __enable_fault_irq __enable_fiq + + +/** + \brief Disable FIQ + \details Disables FIQ interrupts by setting special-purpose register FAULTMASK. + Can only be executed in Privileged modes. + */ +#define __disable_fault_irq __disable_fiq + + +/** + \brief Get Base Priority + \details Returns the current value of the Base Priority register. + \return Base Priority register value + */ +__STATIC_INLINE uint32_t __get_BASEPRI(void) +{ + register uint32_t __regBasePri __ASM("basepri"); + return(__regBasePri); +} + + +/** + \brief Set Base Priority + \details Assigns the given value to the Base Priority register. + \param [in] basePri Base Priority value to set + */ +__STATIC_INLINE void __set_BASEPRI(uint32_t basePri) +{ + register uint32_t __regBasePri __ASM("basepri"); + __regBasePri = (basePri & 0xFFU); +} + + +/** + \brief Set Base Priority with condition + \details Assigns the given value to the Base Priority register only if BASEPRI masking is disabled, + or the new value increases the BASEPRI priority level. + \param [in] basePri Base Priority value to set + */ +__STATIC_INLINE void __set_BASEPRI_MAX(uint32_t basePri) +{ + register uint32_t __regBasePriMax __ASM("basepri_max"); + __regBasePriMax = (basePri & 0xFFU); +} + + +/** + \brief Get Fault Mask + \details Returns the current value of the Fault Mask register. + \return Fault Mask register value + */ +__STATIC_INLINE uint32_t __get_FAULTMASK(void) +{ + register uint32_t __regFaultMask __ASM("faultmask"); + return(__regFaultMask); +} + + +/** + \brief Set Fault Mask + \details Assigns the given value to the Fault Mask register. + \param [in] faultMask Fault Mask value to set + */ +__STATIC_INLINE void __set_FAULTMASK(uint32_t faultMask) +{ + register uint32_t __regFaultMask __ASM("faultmask"); + __regFaultMask = (faultMask & (uint32_t)1U); +} + +#endif /* ((defined (__ARM_ARCH_7M__ ) && (__ARM_ARCH_7M__ == 1)) || \ + (defined (__ARM_ARCH_7EM__) && (__ARM_ARCH_7EM__ == 1)) ) */ + + +/** + \brief Get FPSCR + \details Returns the current value of the Floating Point Status/Control register. + \return Floating Point Status/Control register value + */ +__STATIC_INLINE uint32_t __get_FPSCR(void) +{ +#if ((defined (__FPU_PRESENT) && (__FPU_PRESENT == 1U)) && \ + (defined (__FPU_USED ) && (__FPU_USED == 1U)) ) + register uint32_t __regfpscr __ASM("fpscr"); + return(__regfpscr); +#else + return(0U); +#endif +} + + +/** + \brief Set FPSCR + \details Assigns the given value to the Floating Point Status/Control register. + \param [in] fpscr Floating Point Status/Control value to set + */ +__STATIC_INLINE void __set_FPSCR(uint32_t fpscr) +{ +#if ((defined (__FPU_PRESENT) && (__FPU_PRESENT == 1U)) && \ + (defined (__FPU_USED ) && (__FPU_USED == 1U)) ) + register uint32_t __regfpscr __ASM("fpscr"); + __regfpscr = (fpscr); +#else + (void)fpscr; +#endif +} + + +/*@} end of CMSIS_Core_RegAccFunctions */ + + +/* ################### Compiler specific Intrinsics ########################### */ +/** \defgroup CMSIS_SIMD_intrinsics CMSIS SIMD Intrinsics + Access to dedicated SIMD instructions + @{ +*/ + +#if ((defined (__ARM_ARCH_7EM__) && (__ARM_ARCH_7EM__ == 1)) ) + +#define __SADD8 __sadd8 +#define __QADD8 __qadd8 +#define __SHADD8 __shadd8 +#define __UADD8 __uadd8 +#define __UQADD8 __uqadd8 +#define __UHADD8 __uhadd8 +#define __SSUB8 __ssub8 +#define __QSUB8 __qsub8 +#define __SHSUB8 __shsub8 +#define __USUB8 __usub8 +#define __UQSUB8 __uqsub8 +#define __UHSUB8 __uhsub8 +#define __SADD16 __sadd16 +#define __QADD16 __qadd16 +#define __SHADD16 __shadd16 +#define __UADD16 __uadd16 +#define __UQADD16 __uqadd16 +#define __UHADD16 __uhadd16 +#define __SSUB16 __ssub16 +#define __QSUB16 __qsub16 +#define __SHSUB16 __shsub16 +#define __USUB16 __usub16 +#define __UQSUB16 __uqsub16 +#define __UHSUB16 __uhsub16 +#define __SASX __sasx +#define __QASX __qasx +#define __SHASX __shasx +#define __UASX __uasx +#define __UQASX __uqasx +#define __UHASX __uhasx +#define __SSAX __ssax +#define __QSAX __qsax +#define __SHSAX __shsax +#define __USAX __usax +#define __UQSAX __uqsax +#define __UHSAX __uhsax +#define __USAD8 __usad8 +#define __USADA8 __usada8 +#define __SSAT16 __ssat16 +#define __USAT16 __usat16 +#define __UXTB16 __uxtb16 +#define __UXTAB16 __uxtab16 +#define __SXTB16 __sxtb16 +#define __SXTAB16 __sxtab16 +#define __SMUAD __smuad +#define __SMUADX __smuadx +#define __SMLAD __smlad +#define __SMLADX __smladx +#define __SMLALD __smlald +#define __SMLALDX __smlaldx +#define __SMUSD __smusd +#define __SMUSDX __smusdx +#define __SMLSD __smlsd +#define __SMLSDX __smlsdx +#define __SMLSLD __smlsld +#define __SMLSLDX __smlsldx +#define __SEL __sel +#define __QADD __qadd +#define __QSUB __qsub + +#define __PKHBT(ARG1,ARG2,ARG3) ( ((((uint32_t)(ARG1)) ) & 0x0000FFFFUL) | \ + ((((uint32_t)(ARG2)) << (ARG3)) & 0xFFFF0000UL) ) + +#define __PKHTB(ARG1,ARG2,ARG3) ( ((((uint32_t)(ARG1)) ) & 0xFFFF0000UL) | \ + ((((uint32_t)(ARG2)) >> (ARG3)) & 0x0000FFFFUL) ) + +#define __SMMLA(ARG1,ARG2,ARG3) ( (int32_t)((((int64_t)(ARG1) * (ARG2)) + \ + ((int64_t)(ARG3) << 32U) ) >> 32U)) + +#define __SXTB16_RORn(ARG1, ARG2) __SXTB16(__ROR(ARG1, ARG2)) + +#define __SXTAB16_RORn(ARG1, ARG2, ARG3) __SXTAB16(ARG1, __ROR(ARG2, ARG3)) + +#endif /* ((defined (__ARM_ARCH_7EM__) && (__ARM_ARCH_7EM__ == 1)) ) */ +/*@} end of group CMSIS_SIMD_intrinsics */ + + +#endif /* __CMSIS_ARMCC_H */ diff --git a/lib/cmsis_core/cmsis_armclang.h b/lib/cmsis_core/cmsis_armclang.h new file mode 100644 index 00000000000..139923dab27 --- /dev/null +++ b/lib/cmsis_core/cmsis_armclang.h @@ -0,0 +1,1510 @@ +/**************************************************************************//** + * @file cmsis_armclang.h + * @brief CMSIS compiler armclang (Arm Compiler 6) header file + * @version V5.5.0 + * @date 20. January 2023 + ******************************************************************************/ +/* + * Copyright (c) 2009-2023 Arm Limited. All rights reserved. + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an AS IS BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/*lint -esym(9058, IRQn)*/ /* disable MISRA 2012 Rule 2.4 for IRQn */ + +#ifndef __CMSIS_ARMCLANG_H +#define __CMSIS_ARMCLANG_H + +#pragma clang system_header /* treat file as system include file */ + +/* CMSIS compiler specific defines */ +#ifndef __ASM + #define __ASM __asm +#endif +#ifndef __INLINE + #define __INLINE __inline +#endif +#ifndef __STATIC_INLINE + #define __STATIC_INLINE static __inline +#endif +#ifndef __STATIC_FORCEINLINE + #define __STATIC_FORCEINLINE __attribute__((always_inline)) static __inline +#endif +#ifndef __NO_RETURN + #define __NO_RETURN __attribute__((__noreturn__)) +#endif +#ifndef __USED + #define __USED __attribute__((used)) +#endif +#ifndef __WEAK + #define __WEAK __attribute__((weak)) +#endif +#ifndef __PACKED + #define __PACKED __attribute__((packed, aligned(1))) +#endif +#ifndef __PACKED_STRUCT + #define __PACKED_STRUCT struct __attribute__((packed, aligned(1))) +#endif +#ifndef __PACKED_UNION + #define __PACKED_UNION union __attribute__((packed, aligned(1))) +#endif +#ifndef __UNALIGNED_UINT32 /* deprecated */ + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wpacked" +/*lint -esym(9058, T_UINT32)*/ /* disable MISRA 2012 Rule 2.4 for T_UINT32 */ + struct __attribute__((packed)) T_UINT32 { uint32_t v; }; + #pragma clang diagnostic pop + #define __UNALIGNED_UINT32(x) (((struct T_UINT32 *)(x))->v) +#endif +#ifndef __UNALIGNED_UINT16_WRITE + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wpacked" +/*lint -esym(9058, T_UINT16_WRITE)*/ /* disable MISRA 2012 Rule 2.4 for T_UINT16_WRITE */ + __PACKED_STRUCT T_UINT16_WRITE { uint16_t v; }; + #pragma clang diagnostic pop + #define __UNALIGNED_UINT16_WRITE(addr, val) (void)((((struct T_UINT16_WRITE *)(void *)(addr))->v) = (val)) +#endif +#ifndef __UNALIGNED_UINT16_READ + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wpacked" +/*lint -esym(9058, T_UINT16_READ)*/ /* disable MISRA 2012 Rule 2.4 for T_UINT16_READ */ + __PACKED_STRUCT T_UINT16_READ { uint16_t v; }; + #pragma clang diagnostic pop + #define __UNALIGNED_UINT16_READ(addr) (((const struct T_UINT16_READ *)(const void *)(addr))->v) +#endif +#ifndef __UNALIGNED_UINT32_WRITE + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wpacked" +/*lint -esym(9058, T_UINT32_WRITE)*/ /* disable MISRA 2012 Rule 2.4 for T_UINT32_WRITE */ + __PACKED_STRUCT T_UINT32_WRITE { uint32_t v; }; + #pragma clang diagnostic pop + #define __UNALIGNED_UINT32_WRITE(addr, val) (void)((((struct T_UINT32_WRITE *)(void *)(addr))->v) = (val)) +#endif +#ifndef __UNALIGNED_UINT32_READ + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wpacked" +/*lint -esym(9058, T_UINT32_READ)*/ /* disable MISRA 2012 Rule 2.4 for T_UINT32_READ */ + __PACKED_STRUCT T_UINT32_READ { uint32_t v; }; + #pragma clang diagnostic pop + #define __UNALIGNED_UINT32_READ(addr) (((const struct T_UINT32_READ *)(const void *)(addr))->v) +#endif +#ifndef __ALIGNED + #define __ALIGNED(x) __attribute__((aligned(x))) +#endif +#ifndef __RESTRICT + #define __RESTRICT __restrict +#endif +#ifndef __COMPILER_BARRIER + #define __COMPILER_BARRIER() __ASM volatile("":::"memory") +#endif +#ifndef __NO_INIT + #define __NO_INIT __attribute__ ((section (".bss.noinit"))) +#endif +#ifndef __ALIAS + #define __ALIAS(x) __attribute__ ((alias(x))) +#endif + + +/* ######################### Startup and Lowlevel Init ######################## */ + +#ifndef __PROGRAM_START +#define __PROGRAM_START __main +#endif + +#ifndef __INITIAL_SP +#define __INITIAL_SP Image$$ARM_LIB_STACK$$ZI$$Limit +#endif + +#ifndef __STACK_LIMIT +#define __STACK_LIMIT Image$$ARM_LIB_STACK$$ZI$$Base +#endif + +#ifndef __VECTOR_TABLE +#define __VECTOR_TABLE __Vectors +#endif + +#ifndef __VECTOR_TABLE_ATTRIBUTE +#define __VECTOR_TABLE_ATTRIBUTE __attribute__((used, section("RESET"))) +#endif + +#if defined (__ARM_FEATURE_CMSE) && (__ARM_FEATURE_CMSE == 3U) +#ifndef __STACK_SEAL +#define __STACK_SEAL Image$$STACKSEAL$$ZI$$Base +#endif + +#ifndef __TZ_STACK_SEAL_SIZE +#define __TZ_STACK_SEAL_SIZE 8U +#endif + +#ifndef __TZ_STACK_SEAL_VALUE +#define __TZ_STACK_SEAL_VALUE 0xFEF5EDA5FEF5EDA5ULL +#endif + + +__STATIC_FORCEINLINE void __TZ_set_STACKSEAL_S (uint32_t* stackTop) { + *((uint64_t *)stackTop) = __TZ_STACK_SEAL_VALUE; +} +#endif + + +/* ########################## Core Instruction Access ######################### */ +/** \defgroup CMSIS_Core_InstructionInterface CMSIS Core Instruction Interface + Access to dedicated instructions + @{ +*/ + +/* Define macros for porting to both thumb1 and thumb2. + * For thumb1, use low register (r0-r7), specified by constraint "l" + * Otherwise, use general registers, specified by constraint "r" */ +#if defined (__thumb__) && !defined (__thumb2__) +#define __CMSIS_GCC_OUT_REG(r) "=l" (r) +#define __CMSIS_GCC_RW_REG(r) "+l" (r) +#define __CMSIS_GCC_USE_REG(r) "l" (r) +#else +#define __CMSIS_GCC_OUT_REG(r) "=r" (r) +#define __CMSIS_GCC_RW_REG(r) "+r" (r) +#define __CMSIS_GCC_USE_REG(r) "r" (r) +#endif + +/** + \brief No Operation + \details No Operation does nothing. This instruction can be used for code alignment purposes. + */ +#define __NOP __builtin_arm_nop + +/** + \brief Wait For Interrupt + \details Wait For Interrupt is a hint instruction that suspends execution until one of a number of events occurs. + */ +#define __WFI __builtin_arm_wfi + + +/** + \brief Wait For Event + \details Wait For Event is a hint instruction that permits the processor to enter + a low-power state until one of a number of events occurs. + */ +#define __WFE __builtin_arm_wfe + + +/** + \brief Send Event + \details Send Event is a hint instruction. It causes an event to be signaled to the CPU. + */ +#define __SEV __builtin_arm_sev + + +/** + \brief Instruction Synchronization Barrier + \details Instruction Synchronization Barrier flushes the pipeline in the processor, + so that all instructions following the ISB are fetched from cache or memory, + after the instruction has been completed. + */ +#define __ISB() __builtin_arm_isb(0xF) + +/** + \brief Data Synchronization Barrier + \details Acts as a special kind of Data Memory Barrier. + It completes when all explicit memory accesses before this instruction complete. + */ +#define __DSB() __builtin_arm_dsb(0xF) + + +/** + \brief Data Memory Barrier + \details Ensures the apparent order of the explicit memory operations before + and after the instruction, without ensuring their completion. + */ +#define __DMB() __builtin_arm_dmb(0xF) + + +/** + \brief Reverse byte order (32 bit) + \details Reverses the byte order in unsigned integer value. For example, 0x12345678 becomes 0x78563412. + \param [in] value Value to reverse + \return Reversed value + */ +#define __REV(value) __builtin_bswap32(value) + + +/** + \brief Reverse byte order (16 bit) + \details Reverses the byte order within each halfword of a word. For example, 0x12345678 becomes 0x34127856. + \param [in] value Value to reverse + \return Reversed value + */ +#define __REV16(value) __ROR(__REV(value), 16) + + +/** + \brief Reverse byte order (16 bit) + \details Reverses the byte order in a 16-bit value and returns the signed 16-bit result. For example, 0x0080 becomes 0x8000. + \param [in] value Value to reverse + \return Reversed value + */ +#define __REVSH(value) (int16_t)__builtin_bswap16(value) + + +/** + \brief Rotate Right in unsigned value (32 bit) + \details Rotate Right (immediate) provides the value of the contents of a register rotated by a variable number of bits. + \param [in] op1 Value to rotate + \param [in] op2 Number of Bits to rotate + \return Rotated value + */ +__STATIC_FORCEINLINE uint32_t __ROR(uint32_t op1, uint32_t op2) +{ + op2 %= 32U; + if (op2 == 0U) + { + return op1; + } + return (op1 >> op2) | (op1 << (32U - op2)); +} + + +/** + \brief Breakpoint + \details Causes the processor to enter Debug state. + Debug tools can use this to investigate system state when the instruction at a particular address is reached. + \param [in] value is ignored by the processor. + If required, a debugger can use it to store additional information about the breakpoint. + */ +#define __BKPT(value) __ASM volatile ("bkpt "#value) + + +/** + \brief Reverse bit order of value + \details Reverses the bit order of the given value. + \param [in] value Value to reverse + \return Reversed value + */ +#define __RBIT __builtin_arm_rbit + +/** + \brief Count leading zeros + \details Counts the number of leading zeros of a data value. + \param [in] value Value to count the leading zeros + \return number of leading zeros in value + */ +__STATIC_FORCEINLINE uint8_t __CLZ(uint32_t value) +{ + /* Even though __builtin_clz produces a CLZ instruction on ARM, formally + __builtin_clz(0) is undefined behaviour, so handle this case specially. + This guarantees ARM-compatible results if happening to compile on a non-ARM + target, and ensures the compiler doesn't decide to activate any + optimisations using the logic "value was passed to __builtin_clz, so it + is non-zero". + ARM Compiler 6.10 and possibly earlier will optimise this test away, leaving a + single CLZ instruction. + */ + if (value == 0U) + { + return 32U; + } + return __builtin_clz(value); +} + + +#if ((defined (__ARM_ARCH_7M__ ) && (__ARM_ARCH_7M__ == 1)) || \ + (defined (__ARM_ARCH_7EM__ ) && (__ARM_ARCH_7EM__ == 1)) || \ + (defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) || \ + (defined (__ARM_ARCH_8M_BASE__ ) && (__ARM_ARCH_8M_BASE__ == 1)) || \ + (defined (__ARM_ARCH_8_1M_MAIN__) && (__ARM_ARCH_8_1M_MAIN__ == 1)) ) + +/** + \brief LDR Exclusive (8 bit) + \details Executes a exclusive LDR instruction for 8 bit value. + \param [in] ptr Pointer to data + \return value of type uint8_t at (*ptr) + */ +#define __LDREXB (uint8_t)__builtin_arm_ldrex + + +/** + \brief LDR Exclusive (16 bit) + \details Executes a exclusive LDR instruction for 16 bit values. + \param [in] ptr Pointer to data + \return value of type uint16_t at (*ptr) + */ +#define __LDREXH (uint16_t)__builtin_arm_ldrex + + +/** + \brief LDR Exclusive (32 bit) + \details Executes a exclusive LDR instruction for 32 bit values. + \param [in] ptr Pointer to data + \return value of type uint32_t at (*ptr) + */ +#define __LDREXW (uint32_t)__builtin_arm_ldrex + + +/** + \brief STR Exclusive (8 bit) + \details Executes a exclusive STR instruction for 8 bit values. + \param [in] value Value to store + \param [in] ptr Pointer to location + \return 0 Function succeeded + \return 1 Function failed + */ +#define __STREXB (uint32_t)__builtin_arm_strex + + +/** + \brief STR Exclusive (16 bit) + \details Executes a exclusive STR instruction for 16 bit values. + \param [in] value Value to store + \param [in] ptr Pointer to location + \return 0 Function succeeded + \return 1 Function failed + */ +#define __STREXH (uint32_t)__builtin_arm_strex + + +/** + \brief STR Exclusive (32 bit) + \details Executes a exclusive STR instruction for 32 bit values. + \param [in] value Value to store + \param [in] ptr Pointer to location + \return 0 Function succeeded + \return 1 Function failed + */ +#define __STREXW (uint32_t)__builtin_arm_strex + + +/** + \brief Remove the exclusive lock + \details Removes the exclusive lock which is created by LDREX. + */ +#define __CLREX __builtin_arm_clrex + +#endif /* ((defined (__ARM_ARCH_7M__ ) && (__ARM_ARCH_7M__ == 1)) || \ + (defined (__ARM_ARCH_7EM__ ) && (__ARM_ARCH_7EM__ == 1)) || \ + (defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) || \ + (defined (__ARM_ARCH_8M_BASE__ ) && (__ARM_ARCH_8M_BASE__ == 1)) || \ + (defined (__ARM_ARCH_8_1M_MAIN__) && (__ARM_ARCH_8_1M_MAIN__ == 1)) ) */ + + +#if ((defined (__ARM_ARCH_7M__ ) && (__ARM_ARCH_7M__ == 1)) || \ + (defined (__ARM_ARCH_7EM__ ) && (__ARM_ARCH_7EM__ == 1)) || \ + (defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) || \ + (defined (__ARM_ARCH_8_1M_MAIN__) && (__ARM_ARCH_8_1M_MAIN__ == 1)) ) + +/** + \brief Signed Saturate + \details Saturates a signed value. + \param [in] value Value to be saturated + \param [in] sat Bit position to saturate to (1..32) + \return Saturated value + */ +#define __SSAT __builtin_arm_ssat + + +/** + \brief Unsigned Saturate + \details Saturates an unsigned value. + \param [in] value Value to be saturated + \param [in] sat Bit position to saturate to (0..31) + \return Saturated value + */ +#define __USAT __builtin_arm_usat + + +/** + \brief Rotate Right with Extend (32 bit) + \details Moves each bit of a bitstring right by one bit. + The carry input is shifted in at the left end of the bitstring. + \param [in] value Value to rotate + \return Rotated value + */ +__STATIC_FORCEINLINE uint32_t __RRX(uint32_t value) +{ + uint32_t result; + + __ASM volatile ("rrx %0, %1" : __CMSIS_GCC_OUT_REG (result) : __CMSIS_GCC_USE_REG (value) ); + return(result); +} + + +/** + \brief LDRT Unprivileged (8 bit) + \details Executes a Unprivileged LDRT instruction for 8 bit value. + \param [in] ptr Pointer to data + \return value of type uint8_t at (*ptr) + */ +__STATIC_FORCEINLINE uint8_t __LDRBT(volatile uint8_t *ptr) +{ + uint32_t result; + + __ASM volatile ("ldrbt %0, %1" : "=r" (result) : "Q" (*ptr) ); + return ((uint8_t) result); /* Add explicit type cast here */ +} + + +/** + \brief LDRT Unprivileged (16 bit) + \details Executes a Unprivileged LDRT instruction for 16 bit values. + \param [in] ptr Pointer to data + \return value of type uint16_t at (*ptr) + */ +__STATIC_FORCEINLINE uint16_t __LDRHT(volatile uint16_t *ptr) +{ + uint32_t result; + + __ASM volatile ("ldrht %0, %1" : "=r" (result) : "Q" (*ptr) ); + return ((uint16_t) result); /* Add explicit type cast here */ +} + + +/** + \brief LDRT Unprivileged (32 bit) + \details Executes a Unprivileged LDRT instruction for 32 bit values. + \param [in] ptr Pointer to data + \return value of type uint32_t at (*ptr) + */ +__STATIC_FORCEINLINE uint32_t __LDRT(volatile uint32_t *ptr) +{ + uint32_t result; + + __ASM volatile ("ldrt %0, %1" : "=r" (result) : "Q" (*ptr) ); + return(result); +} + + +/** + \brief STRT Unprivileged (8 bit) + \details Executes a Unprivileged STRT instruction for 8 bit values. + \param [in] value Value to store + \param [in] ptr Pointer to location + */ +__STATIC_FORCEINLINE void __STRBT(uint8_t value, volatile uint8_t *ptr) +{ + __ASM volatile ("strbt %1, %0" : "=Q" (*ptr) : "r" ((uint32_t)value) ); +} + + +/** + \brief STRT Unprivileged (16 bit) + \details Executes a Unprivileged STRT instruction for 16 bit values. + \param [in] value Value to store + \param [in] ptr Pointer to location + */ +__STATIC_FORCEINLINE void __STRHT(uint16_t value, volatile uint16_t *ptr) +{ + __ASM volatile ("strht %1, %0" : "=Q" (*ptr) : "r" ((uint32_t)value) ); +} + + +/** + \brief STRT Unprivileged (32 bit) + \details Executes a Unprivileged STRT instruction for 32 bit values. + \param [in] value Value to store + \param [in] ptr Pointer to location + */ +__STATIC_FORCEINLINE void __STRT(uint32_t value, volatile uint32_t *ptr) +{ + __ASM volatile ("strt %1, %0" : "=Q" (*ptr) : "r" (value) ); +} + +#else /* ((defined (__ARM_ARCH_7M__ ) && (__ARM_ARCH_7M__ == 1)) || \ + (defined (__ARM_ARCH_7EM__ ) && (__ARM_ARCH_7EM__ == 1)) || \ + (defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) || \ + (defined (__ARM_ARCH_8_1M_MAIN__) && (__ARM_ARCH_8_1M_MAIN__ == 1)) ) */ + +/** + \brief Signed Saturate + \details Saturates a signed value. + \param [in] value Value to be saturated + \param [in] sat Bit position to saturate to (1..32) + \return Saturated value + */ +__STATIC_FORCEINLINE int32_t __SSAT(int32_t val, uint32_t sat) +{ + if ((sat >= 1U) && (sat <= 32U)) + { + const int32_t max = (int32_t)((1U << (sat - 1U)) - 1U); + const int32_t min = -1 - max ; + if (val > max) + { + return max; + } + else if (val < min) + { + return min; + } + } + return val; +} + +/** + \brief Unsigned Saturate + \details Saturates an unsigned value. + \param [in] value Value to be saturated + \param [in] sat Bit position to saturate to (0..31) + \return Saturated value + */ +__STATIC_FORCEINLINE uint32_t __USAT(int32_t val, uint32_t sat) +{ + if (sat <= 31U) + { + const uint32_t max = ((1U << sat) - 1U); + if (val > (int32_t)max) + { + return max; + } + else if (val < 0) + { + return 0U; + } + } + return (uint32_t)val; +} + +#endif /* ((defined (__ARM_ARCH_7M__ ) && (__ARM_ARCH_7M__ == 1)) || \ + (defined (__ARM_ARCH_7EM__ ) && (__ARM_ARCH_7EM__ == 1)) || \ + (defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) || \ + (defined (__ARM_ARCH_8_1M_MAIN__) && (__ARM_ARCH_8_1M_MAIN__ == 1)) ) */ + + +#if ((defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) || \ + (defined (__ARM_ARCH_8M_BASE__ ) && (__ARM_ARCH_8M_BASE__ == 1)) || \ + (defined (__ARM_ARCH_8_1M_MAIN__) && (__ARM_ARCH_8_1M_MAIN__ == 1)) ) + +/** + \brief Load-Acquire (8 bit) + \details Executes a LDAB instruction for 8 bit value. + \param [in] ptr Pointer to data + \return value of type uint8_t at (*ptr) + */ +__STATIC_FORCEINLINE uint8_t __LDAB(volatile uint8_t *ptr) +{ + uint32_t result; + + __ASM volatile ("ldab %0, %1" : "=r" (result) : "Q" (*ptr) : "memory" ); + return ((uint8_t) result); +} + + +/** + \brief Load-Acquire (16 bit) + \details Executes a LDAH instruction for 16 bit values. + \param [in] ptr Pointer to data + \return value of type uint16_t at (*ptr) + */ +__STATIC_FORCEINLINE uint16_t __LDAH(volatile uint16_t *ptr) +{ + uint32_t result; + + __ASM volatile ("ldah %0, %1" : "=r" (result) : "Q" (*ptr) : "memory" ); + return ((uint16_t) result); +} + + +/** + \brief Load-Acquire (32 bit) + \details Executes a LDA instruction for 32 bit values. + \param [in] ptr Pointer to data + \return value of type uint32_t at (*ptr) + */ +__STATIC_FORCEINLINE uint32_t __LDA(volatile uint32_t *ptr) +{ + uint32_t result; + + __ASM volatile ("lda %0, %1" : "=r" (result) : "Q" (*ptr) : "memory" ); + return(result); +} + + +/** + \brief Store-Release (8 bit) + \details Executes a STLB instruction for 8 bit values. + \param [in] value Value to store + \param [in] ptr Pointer to location + */ +__STATIC_FORCEINLINE void __STLB(uint8_t value, volatile uint8_t *ptr) +{ + __ASM volatile ("stlb %1, %0" : "=Q" (*ptr) : "r" ((uint32_t)value) : "memory" ); +} + + +/** + \brief Store-Release (16 bit) + \details Executes a STLH instruction for 16 bit values. + \param [in] value Value to store + \param [in] ptr Pointer to location + */ +__STATIC_FORCEINLINE void __STLH(uint16_t value, volatile uint16_t *ptr) +{ + __ASM volatile ("stlh %1, %0" : "=Q" (*ptr) : "r" ((uint32_t)value) : "memory" ); +} + + +/** + \brief Store-Release (32 bit) + \details Executes a STL instruction for 32 bit values. + \param [in] value Value to store + \param [in] ptr Pointer to location + */ +__STATIC_FORCEINLINE void __STL(uint32_t value, volatile uint32_t *ptr) +{ + __ASM volatile ("stl %1, %0" : "=Q" (*ptr) : "r" ((uint32_t)value) : "memory" ); +} + + +/** + \brief Load-Acquire Exclusive (8 bit) + \details Executes a LDAB exclusive instruction for 8 bit value. + \param [in] ptr Pointer to data + \return value of type uint8_t at (*ptr) + */ +#define __LDAEXB (uint8_t)__builtin_arm_ldaex + + +/** + \brief Load-Acquire Exclusive (16 bit) + \details Executes a LDAH exclusive instruction for 16 bit values. + \param [in] ptr Pointer to data + \return value of type uint16_t at (*ptr) + */ +#define __LDAEXH (uint16_t)__builtin_arm_ldaex + + +/** + \brief Load-Acquire Exclusive (32 bit) + \details Executes a LDA exclusive instruction for 32 bit values. + \param [in] ptr Pointer to data + \return value of type uint32_t at (*ptr) + */ +#define __LDAEX (uint32_t)__builtin_arm_ldaex + + +/** + \brief Store-Release Exclusive (8 bit) + \details Executes a STLB exclusive instruction for 8 bit values. + \param [in] value Value to store + \param [in] ptr Pointer to location + \return 0 Function succeeded + \return 1 Function failed + */ +#define __STLEXB (uint32_t)__builtin_arm_stlex + + +/** + \brief Store-Release Exclusive (16 bit) + \details Executes a STLH exclusive instruction for 16 bit values. + \param [in] value Value to store + \param [in] ptr Pointer to location + \return 0 Function succeeded + \return 1 Function failed + */ +#define __STLEXH (uint32_t)__builtin_arm_stlex + + +/** + \brief Store-Release Exclusive (32 bit) + \details Executes a STL exclusive instruction for 32 bit values. + \param [in] value Value to store + \param [in] ptr Pointer to location + \return 0 Function succeeded + \return 1 Function failed + */ +#define __STLEX (uint32_t)__builtin_arm_stlex + +#endif /* ((defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) || \ + (defined (__ARM_ARCH_8M_BASE__ ) && (__ARM_ARCH_8M_BASE__ == 1)) || \ + (defined (__ARM_ARCH_8_1M_MAIN__) && (__ARM_ARCH_8_1M_MAIN__ == 1)) ) */ + +/** @}*/ /* end of group CMSIS_Core_InstructionInterface */ + + +/* ########################### Core Function Access ########################### */ +/** \ingroup CMSIS_Core_FunctionInterface + \defgroup CMSIS_Core_RegAccFunctions CMSIS Core Register Access Functions + @{ + */ + +/** + \brief Enable IRQ Interrupts + \details Enables IRQ interrupts by clearing special-purpose register PRIMASK. + Can only be executed in Privileged modes. + */ +#ifndef __ARM_COMPAT_H +__STATIC_FORCEINLINE void __enable_irq(void) +{ + __ASM volatile ("cpsie i" : : : "memory"); +} +#endif + + +/** + \brief Disable IRQ Interrupts + \details Disables IRQ interrupts by setting special-purpose register PRIMASK. + Can only be executed in Privileged modes. + */ +#ifndef __ARM_COMPAT_H +__STATIC_FORCEINLINE void __disable_irq(void) +{ + __ASM volatile ("cpsid i" : : : "memory"); +} +#endif + + +/** + \brief Get Control Register + \details Returns the content of the Control Register. + \return Control Register value + */ +__STATIC_FORCEINLINE uint32_t __get_CONTROL(void) +{ + uint32_t result; + + __ASM volatile ("MRS %0, control" : "=r" (result) ); + return(result); +} + + +#if (defined (__ARM_FEATURE_CMSE ) && (__ARM_FEATURE_CMSE == 3)) +/** + \brief Get Control Register (non-secure) + \details Returns the content of the non-secure Control Register when in secure mode. + \return non-secure Control Register value + */ +__STATIC_FORCEINLINE uint32_t __TZ_get_CONTROL_NS(void) +{ + uint32_t result; + + __ASM volatile ("MRS %0, control_ns" : "=r" (result) ); + return(result); +} +#endif + + +/** + \brief Set Control Register + \details Writes the given value to the Control Register. + \param [in] control Control Register value to set + */ +__STATIC_FORCEINLINE void __set_CONTROL(uint32_t control) +{ + __ASM volatile ("MSR control, %0" : : "r" (control) : "memory"); + __ISB(); +} + + +#if (defined (__ARM_FEATURE_CMSE ) && (__ARM_FEATURE_CMSE == 3)) +/** + \brief Set Control Register (non-secure) + \details Writes the given value to the non-secure Control Register when in secure state. + \param [in] control Control Register value to set + */ +__STATIC_FORCEINLINE void __TZ_set_CONTROL_NS(uint32_t control) +{ + __ASM volatile ("MSR control_ns, %0" : : "r" (control) : "memory"); + __ISB(); +} +#endif + + +/** + \brief Get IPSR Register + \details Returns the content of the IPSR Register. + \return IPSR Register value + */ +__STATIC_FORCEINLINE uint32_t __get_IPSR(void) +{ + uint32_t result; + + __ASM volatile ("MRS %0, ipsr" : "=r" (result) ); + return(result); +} + + +/** + \brief Get APSR Register + \details Returns the content of the APSR Register. + \return APSR Register value + */ +__STATIC_FORCEINLINE uint32_t __get_APSR(void) +{ + uint32_t result; + + __ASM volatile ("MRS %0, apsr" : "=r" (result) ); + return(result); +} + + +/** + \brief Get xPSR Register + \details Returns the content of the xPSR Register. + \return xPSR Register value + */ +__STATIC_FORCEINLINE uint32_t __get_xPSR(void) +{ + uint32_t result; + + __ASM volatile ("MRS %0, xpsr" : "=r" (result) ); + return(result); +} + + +/** + \brief Get Process Stack Pointer + \details Returns the current value of the Process Stack Pointer (PSP). + \return PSP Register value + */ +__STATIC_FORCEINLINE uint32_t __get_PSP(void) +{ + uint32_t result; + + __ASM volatile ("MRS %0, psp" : "=r" (result) ); + return(result); +} + + +#if (defined (__ARM_FEATURE_CMSE ) && (__ARM_FEATURE_CMSE == 3)) +/** + \brief Get Process Stack Pointer (non-secure) + \details Returns the current value of the non-secure Process Stack Pointer (PSP) when in secure state. + \return PSP Register value + */ +__STATIC_FORCEINLINE uint32_t __TZ_get_PSP_NS(void) +{ + uint32_t result; + + __ASM volatile ("MRS %0, psp_ns" : "=r" (result) ); + return(result); +} +#endif + + +/** + \brief Set Process Stack Pointer + \details Assigns the given value to the Process Stack Pointer (PSP). + \param [in] topOfProcStack Process Stack Pointer value to set + */ +__STATIC_FORCEINLINE void __set_PSP(uint32_t topOfProcStack) +{ + __ASM volatile ("MSR psp, %0" : : "r" (topOfProcStack) : ); +} + + +#if (defined (__ARM_FEATURE_CMSE ) && (__ARM_FEATURE_CMSE == 3)) +/** + \brief Set Process Stack Pointer (non-secure) + \details Assigns the given value to the non-secure Process Stack Pointer (PSP) when in secure state. + \param [in] topOfProcStack Process Stack Pointer value to set + */ +__STATIC_FORCEINLINE void __TZ_set_PSP_NS(uint32_t topOfProcStack) +{ + __ASM volatile ("MSR psp_ns, %0" : : "r" (topOfProcStack) : ); +} +#endif + + +/** + \brief Get Main Stack Pointer + \details Returns the current value of the Main Stack Pointer (MSP). + \return MSP Register value + */ +__STATIC_FORCEINLINE uint32_t __get_MSP(void) +{ + uint32_t result; + + __ASM volatile ("MRS %0, msp" : "=r" (result) ); + return(result); +} + + +#if (defined (__ARM_FEATURE_CMSE ) && (__ARM_FEATURE_CMSE == 3)) +/** + \brief Get Main Stack Pointer (non-secure) + \details Returns the current value of the non-secure Main Stack Pointer (MSP) when in secure state. + \return MSP Register value + */ +__STATIC_FORCEINLINE uint32_t __TZ_get_MSP_NS(void) +{ + uint32_t result; + + __ASM volatile ("MRS %0, msp_ns" : "=r" (result) ); + return(result); +} +#endif + + +/** + \brief Set Main Stack Pointer + \details Assigns the given value to the Main Stack Pointer (MSP). + \param [in] topOfMainStack Main Stack Pointer value to set + */ +__STATIC_FORCEINLINE void __set_MSP(uint32_t topOfMainStack) +{ + __ASM volatile ("MSR msp, %0" : : "r" (topOfMainStack) : ); +} + + +#if (defined (__ARM_FEATURE_CMSE ) && (__ARM_FEATURE_CMSE == 3)) +/** + \brief Set Main Stack Pointer (non-secure) + \details Assigns the given value to the non-secure Main Stack Pointer (MSP) when in secure state. + \param [in] topOfMainStack Main Stack Pointer value to set + */ +__STATIC_FORCEINLINE void __TZ_set_MSP_NS(uint32_t topOfMainStack) +{ + __ASM volatile ("MSR msp_ns, %0" : : "r" (topOfMainStack) : ); +} +#endif + + +#if (defined (__ARM_FEATURE_CMSE ) && (__ARM_FEATURE_CMSE == 3)) +/** + \brief Get Stack Pointer (non-secure) + \details Returns the current value of the non-secure Stack Pointer (SP) when in secure state. + \return SP Register value + */ +__STATIC_FORCEINLINE uint32_t __TZ_get_SP_NS(void) +{ + uint32_t result; + + __ASM volatile ("MRS %0, sp_ns" : "=r" (result) ); + return(result); +} + + +/** + \brief Set Stack Pointer (non-secure) + \details Assigns the given value to the non-secure Stack Pointer (SP) when in secure state. + \param [in] topOfStack Stack Pointer value to set + */ +__STATIC_FORCEINLINE void __TZ_set_SP_NS(uint32_t topOfStack) +{ + __ASM volatile ("MSR sp_ns, %0" : : "r" (topOfStack) : ); +} +#endif + + +/** + \brief Get Priority Mask + \details Returns the current state of the priority mask bit from the Priority Mask Register. + \return Priority Mask value + */ +__STATIC_FORCEINLINE uint32_t __get_PRIMASK(void) +{ + uint32_t result; + + __ASM volatile ("MRS %0, primask" : "=r" (result) ); + return(result); +} + + +#if (defined (__ARM_FEATURE_CMSE ) && (__ARM_FEATURE_CMSE == 3)) +/** + \brief Get Priority Mask (non-secure) + \details Returns the current state of the non-secure priority mask bit from the Priority Mask Register when in secure state. + \return Priority Mask value + */ +__STATIC_FORCEINLINE uint32_t __TZ_get_PRIMASK_NS(void) +{ + uint32_t result; + + __ASM volatile ("MRS %0, primask_ns" : "=r" (result) ); + return(result); +} +#endif + + +/** + \brief Set Priority Mask + \details Assigns the given value to the Priority Mask Register. + \param [in] priMask Priority Mask + */ +__STATIC_FORCEINLINE void __set_PRIMASK(uint32_t priMask) +{ + __ASM volatile ("MSR primask, %0" : : "r" (priMask) : "memory"); +} + + +#if (defined (__ARM_FEATURE_CMSE ) && (__ARM_FEATURE_CMSE == 3)) +/** + \brief Set Priority Mask (non-secure) + \details Assigns the given value to the non-secure Priority Mask Register when in secure state. + \param [in] priMask Priority Mask + */ +__STATIC_FORCEINLINE void __TZ_set_PRIMASK_NS(uint32_t priMask) +{ + __ASM volatile ("MSR primask_ns, %0" : : "r" (priMask) : "memory"); +} +#endif + + +#if ((defined (__ARM_ARCH_7M__ ) && (__ARM_ARCH_7M__ == 1)) || \ + (defined (__ARM_ARCH_7EM__ ) && (__ARM_ARCH_7EM__ == 1)) || \ + (defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) || \ + (defined (__ARM_ARCH_8_1M_MAIN__) && (__ARM_ARCH_8_1M_MAIN__ == 1)) ) +/** + \brief Enable FIQ + \details Enables FIQ interrupts by clearing special-purpose register FAULTMASK. + Can only be executed in Privileged modes. + */ +__STATIC_FORCEINLINE void __enable_fault_irq(void) +{ + __ASM volatile ("cpsie f" : : : "memory"); +} + + +/** + \brief Disable FIQ + \details Disables FIQ interrupts by setting special-purpose register FAULTMASK. + Can only be executed in Privileged modes. + */ +__STATIC_FORCEINLINE void __disable_fault_irq(void) +{ + __ASM volatile ("cpsid f" : : : "memory"); +} + + +/** + \brief Get Base Priority + \details Returns the current value of the Base Priority register. + \return Base Priority register value + */ +__STATIC_FORCEINLINE uint32_t __get_BASEPRI(void) +{ + uint32_t result; + + __ASM volatile ("MRS %0, basepri" : "=r" (result) ); + return(result); +} + + +#if (defined (__ARM_FEATURE_CMSE ) && (__ARM_FEATURE_CMSE == 3)) +/** + \brief Get Base Priority (non-secure) + \details Returns the current value of the non-secure Base Priority register when in secure state. + \return Base Priority register value + */ +__STATIC_FORCEINLINE uint32_t __TZ_get_BASEPRI_NS(void) +{ + uint32_t result; + + __ASM volatile ("MRS %0, basepri_ns" : "=r" (result) ); + return(result); +} +#endif + + +/** + \brief Set Base Priority + \details Assigns the given value to the Base Priority register. + \param [in] basePri Base Priority value to set + */ +__STATIC_FORCEINLINE void __set_BASEPRI(uint32_t basePri) +{ + __ASM volatile ("MSR basepri, %0" : : "r" (basePri) : "memory"); +} + + +#if (defined (__ARM_FEATURE_CMSE ) && (__ARM_FEATURE_CMSE == 3)) +/** + \brief Set Base Priority (non-secure) + \details Assigns the given value to the non-secure Base Priority register when in secure state. + \param [in] basePri Base Priority value to set + */ +__STATIC_FORCEINLINE void __TZ_set_BASEPRI_NS(uint32_t basePri) +{ + __ASM volatile ("MSR basepri_ns, %0" : : "r" (basePri) : "memory"); +} +#endif + + +/** + \brief Set Base Priority with condition + \details Assigns the given value to the Base Priority register only if BASEPRI masking is disabled, + or the new value increases the BASEPRI priority level. + \param [in] basePri Base Priority value to set + */ +__STATIC_FORCEINLINE void __set_BASEPRI_MAX(uint32_t basePri) +{ + __ASM volatile ("MSR basepri_max, %0" : : "r" (basePri) : "memory"); +} + + +/** + \brief Get Fault Mask + \details Returns the current value of the Fault Mask register. + \return Fault Mask register value + */ +__STATIC_FORCEINLINE uint32_t __get_FAULTMASK(void) +{ + uint32_t result; + + __ASM volatile ("MRS %0, faultmask" : "=r" (result) ); + return(result); +} + + +#if (defined (__ARM_FEATURE_CMSE ) && (__ARM_FEATURE_CMSE == 3)) +/** + \brief Get Fault Mask (non-secure) + \details Returns the current value of the non-secure Fault Mask register when in secure state. + \return Fault Mask register value + */ +__STATIC_FORCEINLINE uint32_t __TZ_get_FAULTMASK_NS(void) +{ + uint32_t result; + + __ASM volatile ("MRS %0, faultmask_ns" : "=r" (result) ); + return(result); +} +#endif + + +/** + \brief Set Fault Mask + \details Assigns the given value to the Fault Mask register. + \param [in] faultMask Fault Mask value to set + */ +__STATIC_FORCEINLINE void __set_FAULTMASK(uint32_t faultMask) +{ + __ASM volatile ("MSR faultmask, %0" : : "r" (faultMask) : "memory"); +} + + +#if (defined (__ARM_FEATURE_CMSE ) && (__ARM_FEATURE_CMSE == 3)) +/** + \brief Set Fault Mask (non-secure) + \details Assigns the given value to the non-secure Fault Mask register when in secure state. + \param [in] faultMask Fault Mask value to set + */ +__STATIC_FORCEINLINE void __TZ_set_FAULTMASK_NS(uint32_t faultMask) +{ + __ASM volatile ("MSR faultmask_ns, %0" : : "r" (faultMask) : "memory"); +} +#endif + +#endif /* ((defined (__ARM_ARCH_7M__ ) && (__ARM_ARCH_7M__ == 1)) || \ + (defined (__ARM_ARCH_7EM__ ) && (__ARM_ARCH_7EM__ == 1)) || \ + (defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) || \ + (defined (__ARM_ARCH_8_1M_MAIN__) && (__ARM_ARCH_8_1M_MAIN__ == 1)) ) */ + + +#if ((defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) || \ + (defined (__ARM_ARCH_8M_BASE__ ) && (__ARM_ARCH_8M_BASE__ == 1)) || \ + (defined (__ARM_ARCH_8_1M_MAIN__) && (__ARM_ARCH_8_1M_MAIN__ == 1)) ) + +/** + \brief Get Process Stack Pointer Limit + Devices without ARMv8-M Main Extensions (i.e. Cortex-M23) lack the non-secure + Stack Pointer Limit register hence zero is returned always in non-secure + mode. + + \details Returns the current value of the Process Stack Pointer Limit (PSPLIM). + \return PSPLIM Register value + */ +__STATIC_FORCEINLINE uint32_t __get_PSPLIM(void) +{ +#if (!((defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) || \ + (defined (__ARM_ARCH_8_1M_MAIN__ ) && (__ARM_ARCH_8_1M_MAIN__ == 1)) ) && \ + (!defined (__ARM_FEATURE_CMSE) || (__ARM_FEATURE_CMSE < 3))) + // without main extensions, the non-secure PSPLIM is RAZ/WI + return 0U; +#else + uint32_t result; + __ASM volatile ("MRS %0, psplim" : "=r" (result) ); + return result; +#endif +} + +#if (defined (__ARM_FEATURE_CMSE) && (__ARM_FEATURE_CMSE == 3)) +/** + \brief Get Process Stack Pointer Limit (non-secure) + Devices without ARMv8-M Main Extensions (i.e. Cortex-M23) lack the non-secure + Stack Pointer Limit register hence zero is returned always in non-secure + mode. + + \details Returns the current value of the non-secure Process Stack Pointer Limit (PSPLIM) when in secure state. + \return PSPLIM Register value + */ +__STATIC_FORCEINLINE uint32_t __TZ_get_PSPLIM_NS(void) +{ +#if (!((defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) || \ + (defined (__ARM_ARCH_8_1M_MAIN__ ) && (__ARM_ARCH_8_1M_MAIN__ == 1)) ) ) + // without main extensions, the non-secure PSPLIM is RAZ/WI + return 0U; +#else + uint32_t result; + __ASM volatile ("MRS %0, psplim_ns" : "=r" (result) ); + return result; +#endif +} +#endif + + +/** + \brief Set Process Stack Pointer Limit + Devices without ARMv8-M Main Extensions (i.e. Cortex-M23) lack the non-secure + Stack Pointer Limit register hence the write is silently ignored in non-secure + mode. + + \details Assigns the given value to the Process Stack Pointer Limit (PSPLIM). + \param [in] ProcStackPtrLimit Process Stack Pointer Limit value to set + */ +__STATIC_FORCEINLINE void __set_PSPLIM(uint32_t ProcStackPtrLimit) +{ +#if (!((defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) || \ + (defined (__ARM_ARCH_8_1M_MAIN__ ) && (__ARM_ARCH_8_1M_MAIN__ == 1)) ) && \ + (!defined (__ARM_FEATURE_CMSE) || (__ARM_FEATURE_CMSE < 3))) + // without main extensions, the non-secure PSPLIM is RAZ/WI + (void)ProcStackPtrLimit; +#else + __ASM volatile ("MSR psplim, %0" : : "r" (ProcStackPtrLimit)); +#endif +} + + +#if (defined (__ARM_FEATURE_CMSE ) && (__ARM_FEATURE_CMSE == 3)) +/** + \brief Set Process Stack Pointer (non-secure) + Devices without ARMv8-M Main Extensions (i.e. Cortex-M23) lack the non-secure + Stack Pointer Limit register hence the write is silently ignored in non-secure + mode. + + \details Assigns the given value to the non-secure Process Stack Pointer Limit (PSPLIM) when in secure state. + \param [in] ProcStackPtrLimit Process Stack Pointer Limit value to set + */ +__STATIC_FORCEINLINE void __TZ_set_PSPLIM_NS(uint32_t ProcStackPtrLimit) +{ +#if (!((defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) || \ + (defined (__ARM_ARCH_8_1M_MAIN__ ) && (__ARM_ARCH_8_1M_MAIN__ == 1)) ) ) + // without main extensions, the non-secure PSPLIM is RAZ/WI + (void)ProcStackPtrLimit; +#else + __ASM volatile ("MSR psplim_ns, %0\n" : : "r" (ProcStackPtrLimit)); +#endif +} +#endif + + +/** + \brief Get Main Stack Pointer Limit + Devices without ARMv8-M Main Extensions (i.e. Cortex-M23) lack the non-secure + Stack Pointer Limit register hence zero is returned always. + + \details Returns the current value of the Main Stack Pointer Limit (MSPLIM). + \return MSPLIM Register value + */ +__STATIC_FORCEINLINE uint32_t __get_MSPLIM(void) +{ +#if (!((defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) || \ + (defined (__ARM_ARCH_8_1M_MAIN__ ) && (__ARM_ARCH_8_1M_MAIN__ == 1)) ) && \ + (!defined (__ARM_FEATURE_CMSE) || (__ARM_FEATURE_CMSE < 3))) + // without main extensions, the non-secure MSPLIM is RAZ/WI + return 0U; +#else + uint32_t result; + __ASM volatile ("MRS %0, msplim" : "=r" (result) ); + return result; +#endif +} + + +#if (defined (__ARM_FEATURE_CMSE ) && (__ARM_FEATURE_CMSE == 3)) +/** + \brief Get Main Stack Pointer Limit (non-secure) + Devices without ARMv8-M Main Extensions (i.e. Cortex-M23) lack the non-secure + Stack Pointer Limit register hence zero is returned always. + + \details Returns the current value of the non-secure Main Stack Pointer Limit(MSPLIM) when in secure state. + \return MSPLIM Register value + */ +__STATIC_FORCEINLINE uint32_t __TZ_get_MSPLIM_NS(void) +{ +#if (!((defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) || \ + (defined (__ARM_ARCH_8_1M_MAIN__ ) && (__ARM_ARCH_8_1M_MAIN__ == 1)) ) ) + // without main extensions, the non-secure MSPLIM is RAZ/WI + return 0U; +#else + uint32_t result; + __ASM volatile ("MRS %0, msplim_ns" : "=r" (result) ); + return result; +#endif +} +#endif + + +/** + \brief Set Main Stack Pointer Limit + Devices without ARMv8-M Main Extensions (i.e. Cortex-M23) lack the non-secure + Stack Pointer Limit register hence the write is silently ignored. + + \details Assigns the given value to the Main Stack Pointer Limit (MSPLIM). + \param [in] MainStackPtrLimit Main Stack Pointer Limit value to set + */ +__STATIC_FORCEINLINE void __set_MSPLIM(uint32_t MainStackPtrLimit) +{ +#if (!((defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) || \ + (defined (__ARM_ARCH_8_1M_MAIN__ ) && (__ARM_ARCH_8_1M_MAIN__ == 1)) ) && \ + (!defined (__ARM_FEATURE_CMSE) || (__ARM_FEATURE_CMSE < 3))) + // without main extensions, the non-secure MSPLIM is RAZ/WI + (void)MainStackPtrLimit; +#else + __ASM volatile ("MSR msplim, %0" : : "r" (MainStackPtrLimit)); +#endif +} + + +#if (defined (__ARM_FEATURE_CMSE ) && (__ARM_FEATURE_CMSE == 3)) +/** + \brief Set Main Stack Pointer Limit (non-secure) + Devices without ARMv8-M Main Extensions (i.e. Cortex-M23) lack the non-secure + Stack Pointer Limit register hence the write is silently ignored. + + \details Assigns the given value to the non-secure Main Stack Pointer Limit (MSPLIM) when in secure state. + \param [in] MainStackPtrLimit Main Stack Pointer value to set + */ +__STATIC_FORCEINLINE void __TZ_set_MSPLIM_NS(uint32_t MainStackPtrLimit) +{ +#if (!((defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) || \ + (defined (__ARM_ARCH_8_1M_MAIN__ ) && (__ARM_ARCH_8_1M_MAIN__ == 1)) ) ) + // without main extensions, the non-secure MSPLIM is RAZ/WI + (void)MainStackPtrLimit; +#else + __ASM volatile ("MSR msplim_ns, %0" : : "r" (MainStackPtrLimit)); +#endif +} +#endif + +#endif /* ((defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) || \ + (defined (__ARM_ARCH_8M_BASE__ ) && (__ARM_ARCH_8M_BASE__ == 1)) || \ + (defined (__ARM_ARCH_8_1M_MAIN__) && (__ARM_ARCH_8_1M_MAIN__ == 1)) ) */ + +/** + \brief Get FPSCR + \details Returns the current value of the Floating Point Status/Control register. + \return Floating Point Status/Control register value + */ +#if ((defined (__FPU_PRESENT) && (__FPU_PRESENT == 1U)) && \ + (defined (__FPU_USED ) && (__FPU_USED == 1U)) ) +#define __get_FPSCR (uint32_t)__builtin_arm_get_fpscr +#else +#define __get_FPSCR() ((uint32_t)0U) +#endif + +/** + \brief Set FPSCR + \details Assigns the given value to the Floating Point Status/Control register. + \param [in] fpscr Floating Point Status/Control value to set + */ +#if ((defined (__FPU_PRESENT) && (__FPU_PRESENT == 1U)) && \ + (defined (__FPU_USED ) && (__FPU_USED == 1U)) ) +#define __set_FPSCR __builtin_arm_set_fpscr +#else +#define __set_FPSCR(fpscr) ((void)(fpscr)) +#endif + + +/** @} end of CMSIS_Core_RegAccFunctions */ + + +/* ################### Compiler specific Intrinsics ########################### */ +/** \defgroup CMSIS_SIMD_intrinsics CMSIS SIMD Intrinsics + Access to dedicated SIMD instructions + @{ +*/ + +#if (defined (__ARM_FEATURE_DSP) && (__ARM_FEATURE_DSP == 1)) + +#define __SADD8 __builtin_arm_sadd8 +#define __QADD8 __builtin_arm_qadd8 +#define __SHADD8 __builtin_arm_shadd8 +#define __UADD8 __builtin_arm_uadd8 +#define __UQADD8 __builtin_arm_uqadd8 +#define __UHADD8 __builtin_arm_uhadd8 +#define __SSUB8 __builtin_arm_ssub8 +#define __QSUB8 __builtin_arm_qsub8 +#define __SHSUB8 __builtin_arm_shsub8 +#define __USUB8 __builtin_arm_usub8 +#define __UQSUB8 __builtin_arm_uqsub8 +#define __UHSUB8 __builtin_arm_uhsub8 +#define __SADD16 __builtin_arm_sadd16 +#define __QADD16 __builtin_arm_qadd16 +#define __SHADD16 __builtin_arm_shadd16 +#define __UADD16 __builtin_arm_uadd16 +#define __UQADD16 __builtin_arm_uqadd16 +#define __UHADD16 __builtin_arm_uhadd16 +#define __SSUB16 __builtin_arm_ssub16 +#define __QSUB16 __builtin_arm_qsub16 +#define __SHSUB16 __builtin_arm_shsub16 +#define __USUB16 __builtin_arm_usub16 +#define __UQSUB16 __builtin_arm_uqsub16 +#define __UHSUB16 __builtin_arm_uhsub16 +#define __SASX __builtin_arm_sasx +#define __QASX __builtin_arm_qasx +#define __SHASX __builtin_arm_shasx +#define __UASX __builtin_arm_uasx +#define __UQASX __builtin_arm_uqasx +#define __UHASX __builtin_arm_uhasx +#define __SSAX __builtin_arm_ssax +#define __QSAX __builtin_arm_qsax +#define __SHSAX __builtin_arm_shsax +#define __USAX __builtin_arm_usax +#define __UQSAX __builtin_arm_uqsax +#define __UHSAX __builtin_arm_uhsax +#define __USAD8 __builtin_arm_usad8 +#define __USADA8 __builtin_arm_usada8 +#define __SSAT16 __builtin_arm_ssat16 +#define __USAT16 __builtin_arm_usat16 +#define __UXTB16 __builtin_arm_uxtb16 +#define __UXTAB16 __builtin_arm_uxtab16 +#define __SXTB16 __builtin_arm_sxtb16 +#define __SXTAB16 __builtin_arm_sxtab16 +#define __SMUAD __builtin_arm_smuad +#define __SMUADX __builtin_arm_smuadx +#define __SMLAD __builtin_arm_smlad +#define __SMLADX __builtin_arm_smladx +#define __SMLALD __builtin_arm_smlald +#define __SMLALDX __builtin_arm_smlaldx +#define __SMUSD __builtin_arm_smusd +#define __SMUSDX __builtin_arm_smusdx +#define __SMLSD __builtin_arm_smlsd +#define __SMLSDX __builtin_arm_smlsdx +#define __SMLSLD __builtin_arm_smlsld +#define __SMLSLDX __builtin_arm_smlsldx +#define __SEL __builtin_arm_sel +#define __QADD __builtin_arm_qadd +#define __QSUB __builtin_arm_qsub + +#define __PKHBT(ARG1,ARG2,ARG3) ( ((((uint32_t)(ARG1)) ) & 0x0000FFFFUL) | \ + ((((uint32_t)(ARG2)) << (ARG3)) & 0xFFFF0000UL) ) + +#define __PKHTB(ARG1,ARG2,ARG3) ( ((((uint32_t)(ARG1)) ) & 0xFFFF0000UL) | \ + ((((uint32_t)(ARG2)) >> (ARG3)) & 0x0000FFFFUL) ) + +#define __SXTB16_RORn(ARG1, ARG2) __SXTB16(__ROR(ARG1, ARG2)) + +#define __SXTAB16_RORn(ARG1, ARG2, ARG3) __SXTAB16(ARG1, __ROR(ARG2, ARG3)) + +__STATIC_FORCEINLINE int32_t __SMMLA (int32_t op1, int32_t op2, int32_t op3) +{ + int32_t result; + + __ASM volatile ("smmla %0, %1, %2, %3" : "=r" (result): "r" (op1), "r" (op2), "r" (op3) ); + return(result); +} + +#endif /* (__ARM_FEATURE_DSP == 1) */ +/** @} end of group CMSIS_SIMD_intrinsics */ + + +#endif /* __CMSIS_ARMCLANG_H */ diff --git a/lib/cmsis_core/cmsis_armclang_ltm.h b/lib/cmsis_core/cmsis_armclang_ltm.h new file mode 100644 index 00000000000..477136e8483 --- /dev/null +++ b/lib/cmsis_core/cmsis_armclang_ltm.h @@ -0,0 +1,1934 @@ +/**************************************************************************//** + * @file cmsis_armclang_ltm.h + * @brief CMSIS compiler armclang (Arm Compiler 6) header file + * @version V1.6.0 + * @date 20. January 2023 + ******************************************************************************/ +/* + * Copyright (c) 2018-2023 Arm Limited. All rights reserved. + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an AS IS BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/*lint -esym(9058, IRQn)*/ /* disable MISRA 2012 Rule 2.4 for IRQn */ + +#ifndef __CMSIS_ARMCLANG_H +#define __CMSIS_ARMCLANG_H + +#pragma clang system_header /* treat file as system include file */ + +/* CMSIS compiler specific defines */ +#ifndef __ASM + #define __ASM __asm +#endif +#ifndef __INLINE + #define __INLINE __inline +#endif +#ifndef __STATIC_INLINE + #define __STATIC_INLINE static __inline +#endif +#ifndef __STATIC_FORCEINLINE + #define __STATIC_FORCEINLINE __attribute__((always_inline)) static __inline +#endif +#ifndef __NO_RETURN + #define __NO_RETURN __attribute__((__noreturn__)) +#endif +#ifndef __USED + #define __USED __attribute__((used)) +#endif +#ifndef __WEAK + #define __WEAK __attribute__((weak)) +#endif +#ifndef __PACKED + #define __PACKED __attribute__((packed, aligned(1))) +#endif +#ifndef __PACKED_STRUCT + #define __PACKED_STRUCT struct __attribute__((packed, aligned(1))) +#endif +#ifndef __PACKED_UNION + #define __PACKED_UNION union __attribute__((packed, aligned(1))) +#endif +#ifndef __UNALIGNED_UINT32 /* deprecated */ + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wpacked" +/*lint -esym(9058, T_UINT32)*/ /* disable MISRA 2012 Rule 2.4 for T_UINT32 */ + struct __attribute__((packed)) T_UINT32 { uint32_t v; }; + #pragma clang diagnostic pop + #define __UNALIGNED_UINT32(x) (((struct T_UINT32 *)(x))->v) +#endif +#ifndef __UNALIGNED_UINT16_WRITE + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wpacked" +/*lint -esym(9058, T_UINT16_WRITE)*/ /* disable MISRA 2012 Rule 2.4 for T_UINT16_WRITE */ + __PACKED_STRUCT T_UINT16_WRITE { uint16_t v; }; + #pragma clang diagnostic pop + #define __UNALIGNED_UINT16_WRITE(addr, val) (void)((((struct T_UINT16_WRITE *)(void *)(addr))->v) = (val)) +#endif +#ifndef __UNALIGNED_UINT16_READ + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wpacked" +/*lint -esym(9058, T_UINT16_READ)*/ /* disable MISRA 2012 Rule 2.4 for T_UINT16_READ */ + __PACKED_STRUCT T_UINT16_READ { uint16_t v; }; + #pragma clang diagnostic pop + #define __UNALIGNED_UINT16_READ(addr) (((const struct T_UINT16_READ *)(const void *)(addr))->v) +#endif +#ifndef __UNALIGNED_UINT32_WRITE + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wpacked" +/*lint -esym(9058, T_UINT32_WRITE)*/ /* disable MISRA 2012 Rule 2.4 for T_UINT32_WRITE */ + __PACKED_STRUCT T_UINT32_WRITE { uint32_t v; }; + #pragma clang diagnostic pop + #define __UNALIGNED_UINT32_WRITE(addr, val) (void)((((struct T_UINT32_WRITE *)(void *)(addr))->v) = (val)) +#endif +#ifndef __UNALIGNED_UINT32_READ + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wpacked" +/*lint -esym(9058, T_UINT32_READ)*/ /* disable MISRA 2012 Rule 2.4 for T_UINT32_READ */ + __PACKED_STRUCT T_UINT32_READ { uint32_t v; }; + #pragma clang diagnostic pop + #define __UNALIGNED_UINT32_READ(addr) (((const struct T_UINT32_READ *)(const void *)(addr))->v) +#endif +#ifndef __ALIGNED + #define __ALIGNED(x) __attribute__((aligned(x))) +#endif +#ifndef __RESTRICT + #define __RESTRICT __restrict +#endif +#ifndef __COMPILER_BARRIER + #define __COMPILER_BARRIER() __ASM volatile("":::"memory") +#endif +#ifndef __NO_INIT + #define __NO_INIT __attribute__ ((section (".bss.noinit"))) +#endif +#ifndef __ALIAS + #define __ALIAS(x) __attribute__ ((alias(x))) +#endif + +/* ######################### Startup and Lowlevel Init ######################## */ + +#ifndef __PROGRAM_START +#define __PROGRAM_START __main +#endif + +#ifndef __INITIAL_SP +#define __INITIAL_SP Image$$ARM_LIB_STACK$$ZI$$Limit +#endif + +#ifndef __STACK_LIMIT +#define __STACK_LIMIT Image$$ARM_LIB_STACK$$ZI$$Base +#endif + +#ifndef __VECTOR_TABLE +#define __VECTOR_TABLE __Vectors +#endif + +#ifndef __VECTOR_TABLE_ATTRIBUTE +#define __VECTOR_TABLE_ATTRIBUTE __attribute__((used, section("RESET"))) +#endif + +#if defined (__ARM_FEATURE_CMSE) && (__ARM_FEATURE_CMSE == 3U) +#ifndef __STACK_SEAL +#define __STACK_SEAL Image$$STACKSEAL$$ZI$$Base +#endif + +#ifndef __TZ_STACK_SEAL_SIZE +#define __TZ_STACK_SEAL_SIZE 8U +#endif + +#ifndef __TZ_STACK_SEAL_VALUE +#define __TZ_STACK_SEAL_VALUE 0xFEF5EDA5FEF5EDA5ULL +#endif + + +__STATIC_FORCEINLINE void __TZ_set_STACKSEAL_S (uint32_t* stackTop) { + *((uint64_t *)stackTop) = __TZ_STACK_SEAL_VALUE; +} +#endif + + +/* ########################## Core Instruction Access ######################### */ +/** \defgroup CMSIS_Core_InstructionInterface CMSIS Core Instruction Interface + Access to dedicated instructions + @{ +*/ + +/* Define macros for porting to both thumb1 and thumb2. + * For thumb1, use low register (r0-r7), specified by constraint "l" + * Otherwise, use general registers, specified by constraint "r" */ +#if defined (__thumb__) && !defined (__thumb2__) +#define __CMSIS_GCC_OUT_REG(r) "=l" (r) +#define __CMSIS_GCC_USE_REG(r) "l" (r) +#else +#define __CMSIS_GCC_OUT_REG(r) "=r" (r) +#define __CMSIS_GCC_USE_REG(r) "r" (r) +#endif + +/** + \brief No Operation + \details No Operation does nothing. This instruction can be used for code alignment purposes. + */ +#define __NOP __builtin_arm_nop + +/** + \brief Wait For Interrupt + \details Wait For Interrupt is a hint instruction that suspends execution until one of a number of events occurs. + */ +#define __WFI __builtin_arm_wfi + + +/** + \brief Wait For Event + \details Wait For Event is a hint instruction that permits the processor to enter + a low-power state until one of a number of events occurs. + */ +#define __WFE __builtin_arm_wfe + + +/** + \brief Send Event + \details Send Event is a hint instruction. It causes an event to be signaled to the CPU. + */ +#define __SEV __builtin_arm_sev + + +/** + \brief Instruction Synchronization Barrier + \details Instruction Synchronization Barrier flushes the pipeline in the processor, + so that all instructions following the ISB are fetched from cache or memory, + after the instruction has been completed. + */ +#define __ISB() __builtin_arm_isb(0xF) + +/** + \brief Data Synchronization Barrier + \details Acts as a special kind of Data Memory Barrier. + It completes when all explicit memory accesses before this instruction complete. + */ +#define __DSB() __builtin_arm_dsb(0xF) + + +/** + \brief Data Memory Barrier + \details Ensures the apparent order of the explicit memory operations before + and after the instruction, without ensuring their completion. + */ +#define __DMB() __builtin_arm_dmb(0xF) + + +/** + \brief Reverse byte order (32 bit) + \details Reverses the byte order in unsigned integer value. For example, 0x12345678 becomes 0x78563412. + \param [in] value Value to reverse + \return Reversed value + */ +#define __REV(value) __builtin_bswap32(value) + + +/** + \brief Reverse byte order (16 bit) + \details Reverses the byte order within each halfword of a word. For example, 0x12345678 becomes 0x34127856. + \param [in] value Value to reverse + \return Reversed value + */ +#define __REV16(value) __ROR(__REV(value), 16) + + +/** + \brief Reverse byte order (16 bit) + \details Reverses the byte order in a 16-bit value and returns the signed 16-bit result. For example, 0x0080 becomes 0x8000. + \param [in] value Value to reverse + \return Reversed value + */ +#define __REVSH(value) (int16_t)__builtin_bswap16(value) + + +/** + \brief Rotate Right in unsigned value (32 bit) + \details Rotate Right (immediate) provides the value of the contents of a register rotated by a variable number of bits. + \param [in] op1 Value to rotate + \param [in] op2 Number of Bits to rotate + \return Rotated value + */ +__STATIC_FORCEINLINE uint32_t __ROR(uint32_t op1, uint32_t op2) +{ + op2 %= 32U; + if (op2 == 0U) + { + return op1; + } + return (op1 >> op2) | (op1 << (32U - op2)); +} + + +/** + \brief Breakpoint + \details Causes the processor to enter Debug state. + Debug tools can use this to investigate system state when the instruction at a particular address is reached. + \param [in] value is ignored by the processor. + If required, a debugger can use it to store additional information about the breakpoint. + */ +#define __BKPT(value) __ASM volatile ("bkpt "#value) + + +/** + \brief Reverse bit order of value + \details Reverses the bit order of the given value. + \param [in] value Value to reverse + \return Reversed value + */ +#define __RBIT __builtin_arm_rbit + +/** + \brief Count leading zeros + \details Counts the number of leading zeros of a data value. + \param [in] value Value to count the leading zeros + \return number of leading zeros in value + */ +__STATIC_FORCEINLINE uint8_t __CLZ(uint32_t value) +{ + /* Even though __builtin_clz produces a CLZ instruction on ARM, formally + __builtin_clz(0) is undefined behaviour, so handle this case specially. + This guarantees ARM-compatible results if happening to compile on a non-ARM + target, and ensures the compiler doesn't decide to activate any + optimisations using the logic "value was passed to __builtin_clz, so it + is non-zero". + ARM Compiler 6.10 and possibly earlier will optimise this test away, leaving a + single CLZ instruction. + */ + if (value == 0U) + { + return 32U; + } + return __builtin_clz(value); +} + + +#if ((defined (__ARM_ARCH_7M__ ) && (__ARM_ARCH_7M__ == 1)) || \ + (defined (__ARM_ARCH_7EM__ ) && (__ARM_ARCH_7EM__ == 1)) || \ + (defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) || \ + (defined (__ARM_ARCH_8M_BASE__ ) && (__ARM_ARCH_8M_BASE__ == 1)) ) +/** + \brief LDR Exclusive (8 bit) + \details Executes a exclusive LDR instruction for 8 bit value. + \param [in] ptr Pointer to data + \return value of type uint8_t at (*ptr) + */ +#define __LDREXB (uint8_t)__builtin_arm_ldrex + + +/** + \brief LDR Exclusive (16 bit) + \details Executes a exclusive LDR instruction for 16 bit values. + \param [in] ptr Pointer to data + \return value of type uint16_t at (*ptr) + */ +#define __LDREXH (uint16_t)__builtin_arm_ldrex + + +/** + \brief LDR Exclusive (32 bit) + \details Executes a exclusive LDR instruction for 32 bit values. + \param [in] ptr Pointer to data + \return value of type uint32_t at (*ptr) + */ +#define __LDREXW (uint32_t)__builtin_arm_ldrex + + +/** + \brief STR Exclusive (8 bit) + \details Executes a exclusive STR instruction for 8 bit values. + \param [in] value Value to store + \param [in] ptr Pointer to location + \return 0 Function succeeded + \return 1 Function failed + */ +#define __STREXB (uint32_t)__builtin_arm_strex + + +/** + \brief STR Exclusive (16 bit) + \details Executes a exclusive STR instruction for 16 bit values. + \param [in] value Value to store + \param [in] ptr Pointer to location + \return 0 Function succeeded + \return 1 Function failed + */ +#define __STREXH (uint32_t)__builtin_arm_strex + + +/** + \brief STR Exclusive (32 bit) + \details Executes a exclusive STR instruction for 32 bit values. + \param [in] value Value to store + \param [in] ptr Pointer to location + \return 0 Function succeeded + \return 1 Function failed + */ +#define __STREXW (uint32_t)__builtin_arm_strex + + +/** + \brief Remove the exclusive lock + \details Removes the exclusive lock which is created by LDREX. + */ +#define __CLREX __builtin_arm_clrex + +#endif /* ((defined (__ARM_ARCH_7M__ ) && (__ARM_ARCH_7M__ == 1)) || \ + (defined (__ARM_ARCH_7EM__ ) && (__ARM_ARCH_7EM__ == 1)) || \ + (defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) || \ + (defined (__ARM_ARCH_8M_BASE__ ) && (__ARM_ARCH_8M_BASE__ == 1)) ) */ + + +#if ((defined (__ARM_ARCH_7M__ ) && (__ARM_ARCH_7M__ == 1)) || \ + (defined (__ARM_ARCH_7EM__ ) && (__ARM_ARCH_7EM__ == 1)) || \ + (defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) ) + +/** + \brief Signed Saturate + \details Saturates a signed value. + \param [in] value Value to be saturated + \param [in] sat Bit position to saturate to (1..32) + \return Saturated value + */ +#define __SSAT __builtin_arm_ssat + + +/** + \brief Unsigned Saturate + \details Saturates an unsigned value. + \param [in] value Value to be saturated + \param [in] sat Bit position to saturate to (0..31) + \return Saturated value + */ +#define __USAT __builtin_arm_usat + + +/** + \brief Rotate Right with Extend (32 bit) + \details Moves each bit of a bitstring right by one bit. + The carry input is shifted in at the left end of the bitstring. + \param [in] value Value to rotate + \return Rotated value + */ +__STATIC_FORCEINLINE uint32_t __RRX(uint32_t value) +{ + uint32_t result; + + __ASM volatile ("rrx %0, %1" : __CMSIS_GCC_OUT_REG (result) : __CMSIS_GCC_USE_REG (value) ); + return(result); +} + + +/** + \brief LDRT Unprivileged (8 bit) + \details Executes a Unprivileged LDRT instruction for 8 bit value. + \param [in] ptr Pointer to data + \return value of type uint8_t at (*ptr) + */ +__STATIC_FORCEINLINE uint8_t __LDRBT(volatile uint8_t *ptr) +{ + uint32_t result; + + __ASM volatile ("ldrbt %0, %1" : "=r" (result) : "Q" (*ptr) ); + return ((uint8_t) result); /* Add explicit type cast here */ +} + + +/** + \brief LDRT Unprivileged (16 bit) + \details Executes a Unprivileged LDRT instruction for 16 bit values. + \param [in] ptr Pointer to data + \return value of type uint16_t at (*ptr) + */ +__STATIC_FORCEINLINE uint16_t __LDRHT(volatile uint16_t *ptr) +{ + uint32_t result; + + __ASM volatile ("ldrht %0, %1" : "=r" (result) : "Q" (*ptr) ); + return ((uint16_t) result); /* Add explicit type cast here */ +} + + +/** + \brief LDRT Unprivileged (32 bit) + \details Executes a Unprivileged LDRT instruction for 32 bit values. + \param [in] ptr Pointer to data + \return value of type uint32_t at (*ptr) + */ +__STATIC_FORCEINLINE uint32_t __LDRT(volatile uint32_t *ptr) +{ + uint32_t result; + + __ASM volatile ("ldrt %0, %1" : "=r" (result) : "Q" (*ptr) ); + return(result); +} + + +/** + \brief STRT Unprivileged (8 bit) + \details Executes a Unprivileged STRT instruction for 8 bit values. + \param [in] value Value to store + \param [in] ptr Pointer to location + */ +__STATIC_FORCEINLINE void __STRBT(uint8_t value, volatile uint8_t *ptr) +{ + __ASM volatile ("strbt %1, %0" : "=Q" (*ptr) : "r" ((uint32_t)value) ); +} + + +/** + \brief STRT Unprivileged (16 bit) + \details Executes a Unprivileged STRT instruction for 16 bit values. + \param [in] value Value to store + \param [in] ptr Pointer to location + */ +__STATIC_FORCEINLINE void __STRHT(uint16_t value, volatile uint16_t *ptr) +{ + __ASM volatile ("strht %1, %0" : "=Q" (*ptr) : "r" ((uint32_t)value) ); +} + + +/** + \brief STRT Unprivileged (32 bit) + \details Executes a Unprivileged STRT instruction for 32 bit values. + \param [in] value Value to store + \param [in] ptr Pointer to location + */ +__STATIC_FORCEINLINE void __STRT(uint32_t value, volatile uint32_t *ptr) +{ + __ASM volatile ("strt %1, %0" : "=Q" (*ptr) : "r" (value) ); +} + +#else /* ((defined (__ARM_ARCH_7M__ ) && (__ARM_ARCH_7M__ == 1)) || \ + (defined (__ARM_ARCH_7EM__ ) && (__ARM_ARCH_7EM__ == 1)) || \ + (defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) ) */ + +/** + \brief Signed Saturate + \details Saturates a signed value. + \param [in] value Value to be saturated + \param [in] sat Bit position to saturate to (1..32) + \return Saturated value + */ +__STATIC_FORCEINLINE int32_t __SSAT(int32_t val, uint32_t sat) +{ + if ((sat >= 1U) && (sat <= 32U)) + { + const int32_t max = (int32_t)((1U << (sat - 1U)) - 1U); + const int32_t min = -1 - max ; + if (val > max) + { + return max; + } + else if (val < min) + { + return min; + } + } + return val; +} + +/** + \brief Unsigned Saturate + \details Saturates an unsigned value. + \param [in] value Value to be saturated + \param [in] sat Bit position to saturate to (0..31) + \return Saturated value + */ +__STATIC_FORCEINLINE uint32_t __USAT(int32_t val, uint32_t sat) +{ + if (sat <= 31U) + { + const uint32_t max = ((1U << sat) - 1U); + if (val > (int32_t)max) + { + return max; + } + else if (val < 0) + { + return 0U; + } + } + return (uint32_t)val; +} + +#endif /* ((defined (__ARM_ARCH_7M__ ) && (__ARM_ARCH_7M__ == 1)) || \ + (defined (__ARM_ARCH_7EM__ ) && (__ARM_ARCH_7EM__ == 1)) || \ + (defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) ) */ + + +#if ((defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) || \ + (defined (__ARM_ARCH_8M_BASE__ ) && (__ARM_ARCH_8M_BASE__ == 1)) ) +/** + \brief Load-Acquire (8 bit) + \details Executes a LDAB instruction for 8 bit value. + \param [in] ptr Pointer to data + \return value of type uint8_t at (*ptr) + */ +__STATIC_FORCEINLINE uint8_t __LDAB(volatile uint8_t *ptr) +{ + uint32_t result; + + __ASM volatile ("ldab %0, %1" : "=r" (result) : "Q" (*ptr) : "memory" ); + return ((uint8_t) result); +} + + +/** + \brief Load-Acquire (16 bit) + \details Executes a LDAH instruction for 16 bit values. + \param [in] ptr Pointer to data + \return value of type uint16_t at (*ptr) + */ +__STATIC_FORCEINLINE uint16_t __LDAH(volatile uint16_t *ptr) +{ + uint32_t result; + + __ASM volatile ("ldah %0, %1" : "=r" (result) : "Q" (*ptr) : "memory" ); + return ((uint16_t) result); +} + + +/** + \brief Load-Acquire (32 bit) + \details Executes a LDA instruction for 32 bit values. + \param [in] ptr Pointer to data + \return value of type uint32_t at (*ptr) + */ +__STATIC_FORCEINLINE uint32_t __LDA(volatile uint32_t *ptr) +{ + uint32_t result; + + __ASM volatile ("lda %0, %1" : "=r" (result) : "Q" (*ptr) : "memory" ); + return(result); +} + + +/** + \brief Store-Release (8 bit) + \details Executes a STLB instruction for 8 bit values. + \param [in] value Value to store + \param [in] ptr Pointer to location + */ +__STATIC_FORCEINLINE void __STLB(uint8_t value, volatile uint8_t *ptr) +{ + __ASM volatile ("stlb %1, %0" : "=Q" (*ptr) : "r" ((uint32_t)value) : "memory" ); +} + + +/** + \brief Store-Release (16 bit) + \details Executes a STLH instruction for 16 bit values. + \param [in] value Value to store + \param [in] ptr Pointer to location + */ +__STATIC_FORCEINLINE void __STLH(uint16_t value, volatile uint16_t *ptr) +{ + __ASM volatile ("stlh %1, %0" : "=Q" (*ptr) : "r" ((uint32_t)value) : "memory" ); +} + + +/** + \brief Store-Release (32 bit) + \details Executes a STL instruction for 32 bit values. + \param [in] value Value to store + \param [in] ptr Pointer to location + */ +__STATIC_FORCEINLINE void __STL(uint32_t value, volatile uint32_t *ptr) +{ + __ASM volatile ("stl %1, %0" : "=Q" (*ptr) : "r" ((uint32_t)value) : "memory" ); +} + + +/** + \brief Load-Acquire Exclusive (8 bit) + \details Executes a LDAB exclusive instruction for 8 bit value. + \param [in] ptr Pointer to data + \return value of type uint8_t at (*ptr) + */ +#define __LDAEXB (uint8_t)__builtin_arm_ldaex + + +/** + \brief Load-Acquire Exclusive (16 bit) + \details Executes a LDAH exclusive instruction for 16 bit values. + \param [in] ptr Pointer to data + \return value of type uint16_t at (*ptr) + */ +#define __LDAEXH (uint16_t)__builtin_arm_ldaex + + +/** + \brief Load-Acquire Exclusive (32 bit) + \details Executes a LDA exclusive instruction for 32 bit values. + \param [in] ptr Pointer to data + \return value of type uint32_t at (*ptr) + */ +#define __LDAEX (uint32_t)__builtin_arm_ldaex + + +/** + \brief Store-Release Exclusive (8 bit) + \details Executes a STLB exclusive instruction for 8 bit values. + \param [in] value Value to store + \param [in] ptr Pointer to location + \return 0 Function succeeded + \return 1 Function failed + */ +#define __STLEXB (uint32_t)__builtin_arm_stlex + + +/** + \brief Store-Release Exclusive (16 bit) + \details Executes a STLH exclusive instruction for 16 bit values. + \param [in] value Value to store + \param [in] ptr Pointer to location + \return 0 Function succeeded + \return 1 Function failed + */ +#define __STLEXH (uint32_t)__builtin_arm_stlex + + +/** + \brief Store-Release Exclusive (32 bit) + \details Executes a STL exclusive instruction for 32 bit values. + \param [in] value Value to store + \param [in] ptr Pointer to location + \return 0 Function succeeded + \return 1 Function failed + */ +#define __STLEX (uint32_t)__builtin_arm_stlex + +#endif /* ((defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) || \ + (defined (__ARM_ARCH_8M_BASE__ ) && (__ARM_ARCH_8M_BASE__ == 1)) ) */ + +/*@}*/ /* end of group CMSIS_Core_InstructionInterface */ + + +/* ########################### Core Function Access ########################### */ +/** \ingroup CMSIS_Core_FunctionInterface + \defgroup CMSIS_Core_RegAccFunctions CMSIS Core Register Access Functions + @{ + */ + +/** + \brief Enable IRQ Interrupts + \details Enables IRQ interrupts by clearing special-purpose register PRIMASK. + Can only be executed in Privileged modes. + */ +#ifndef __ARM_COMPAT_H +__STATIC_FORCEINLINE void __enable_irq(void) +{ + __ASM volatile ("cpsie i" : : : "memory"); +} +#endif + + +/** + \brief Disable IRQ Interrupts + \details Disables IRQ interrupts by setting special-purpose register PRIMASK. + Can only be executed in Privileged modes. + */ +#ifndef __ARM_COMPAT_H +__STATIC_FORCEINLINE void __disable_irq(void) +{ + __ASM volatile ("cpsid i" : : : "memory"); +} +#endif + + +/** + \brief Get Control Register + \details Returns the content of the Control Register. + \return Control Register value + */ +__STATIC_FORCEINLINE uint32_t __get_CONTROL(void) +{ + uint32_t result; + + __ASM volatile ("MRS %0, control" : "=r" (result) ); + return(result); +} + + +#if (defined (__ARM_FEATURE_CMSE ) && (__ARM_FEATURE_CMSE == 3)) +/** + \brief Get Control Register (non-secure) + \details Returns the content of the non-secure Control Register when in secure mode. + \return non-secure Control Register value + */ +__STATIC_FORCEINLINE uint32_t __TZ_get_CONTROL_NS(void) +{ + uint32_t result; + + __ASM volatile ("MRS %0, control_ns" : "=r" (result) ); + return(result); +} +#endif + + +/** + \brief Set Control Register + \details Writes the given value to the Control Register. + \param [in] control Control Register value to set + */ +__STATIC_FORCEINLINE void __set_CONTROL(uint32_t control) +{ + __ASM volatile ("MSR control, %0" : : "r" (control) : "memory"); + __ISB(); +} + + +#if (defined (__ARM_FEATURE_CMSE ) && (__ARM_FEATURE_CMSE == 3)) +/** + \brief Set Control Register (non-secure) + \details Writes the given value to the non-secure Control Register when in secure state. + \param [in] control Control Register value to set + */ +__STATIC_FORCEINLINE void __TZ_set_CONTROL_NS(uint32_t control) +{ + __ASM volatile ("MSR control_ns, %0" : : "r" (control) : "memory"); + __ISB(); +} +#endif + + +/** + \brief Get IPSR Register + \details Returns the content of the IPSR Register. + \return IPSR Register value + */ +__STATIC_FORCEINLINE uint32_t __get_IPSR(void) +{ + uint32_t result; + + __ASM volatile ("MRS %0, ipsr" : "=r" (result) ); + return(result); +} + + +/** + \brief Get APSR Register + \details Returns the content of the APSR Register. + \return APSR Register value + */ +__STATIC_FORCEINLINE uint32_t __get_APSR(void) +{ + uint32_t result; + + __ASM volatile ("MRS %0, apsr" : "=r" (result) ); + return(result); +} + + +/** + \brief Get xPSR Register + \details Returns the content of the xPSR Register. + \return xPSR Register value + */ +__STATIC_FORCEINLINE uint32_t __get_xPSR(void) +{ + uint32_t result; + + __ASM volatile ("MRS %0, xpsr" : "=r" (result) ); + return(result); +} + + +/** + \brief Get Process Stack Pointer + \details Returns the current value of the Process Stack Pointer (PSP). + \return PSP Register value + */ +__STATIC_FORCEINLINE uint32_t __get_PSP(void) +{ + uint32_t result; + + __ASM volatile ("MRS %0, psp" : "=r" (result) ); + return(result); +} + + +#if (defined (__ARM_FEATURE_CMSE ) && (__ARM_FEATURE_CMSE == 3)) +/** + \brief Get Process Stack Pointer (non-secure) + \details Returns the current value of the non-secure Process Stack Pointer (PSP) when in secure state. + \return PSP Register value + */ +__STATIC_FORCEINLINE uint32_t __TZ_get_PSP_NS(void) +{ + uint32_t result; + + __ASM volatile ("MRS %0, psp_ns" : "=r" (result) ); + return(result); +} +#endif + + +/** + \brief Set Process Stack Pointer + \details Assigns the given value to the Process Stack Pointer (PSP). + \param [in] topOfProcStack Process Stack Pointer value to set + */ +__STATIC_FORCEINLINE void __set_PSP(uint32_t topOfProcStack) +{ + __ASM volatile ("MSR psp, %0" : : "r" (topOfProcStack) : ); +} + + +#if (defined (__ARM_FEATURE_CMSE ) && (__ARM_FEATURE_CMSE == 3)) +/** + \brief Set Process Stack Pointer (non-secure) + \details Assigns the given value to the non-secure Process Stack Pointer (PSP) when in secure state. + \param [in] topOfProcStack Process Stack Pointer value to set + */ +__STATIC_FORCEINLINE void __TZ_set_PSP_NS(uint32_t topOfProcStack) +{ + __ASM volatile ("MSR psp_ns, %0" : : "r" (topOfProcStack) : ); +} +#endif + + +/** + \brief Get Main Stack Pointer + \details Returns the current value of the Main Stack Pointer (MSP). + \return MSP Register value + */ +__STATIC_FORCEINLINE uint32_t __get_MSP(void) +{ + uint32_t result; + + __ASM volatile ("MRS %0, msp" : "=r" (result) ); + return(result); +} + + +#if (defined (__ARM_FEATURE_CMSE ) && (__ARM_FEATURE_CMSE == 3)) +/** + \brief Get Main Stack Pointer (non-secure) + \details Returns the current value of the non-secure Main Stack Pointer (MSP) when in secure state. + \return MSP Register value + */ +__STATIC_FORCEINLINE uint32_t __TZ_get_MSP_NS(void) +{ + uint32_t result; + + __ASM volatile ("MRS %0, msp_ns" : "=r" (result) ); + return(result); +} +#endif + + +/** + \brief Set Main Stack Pointer + \details Assigns the given value to the Main Stack Pointer (MSP). + \param [in] topOfMainStack Main Stack Pointer value to set + */ +__STATIC_FORCEINLINE void __set_MSP(uint32_t topOfMainStack) +{ + __ASM volatile ("MSR msp, %0" : : "r" (topOfMainStack) : ); +} + + +#if (defined (__ARM_FEATURE_CMSE ) && (__ARM_FEATURE_CMSE == 3)) +/** + \brief Set Main Stack Pointer (non-secure) + \details Assigns the given value to the non-secure Main Stack Pointer (MSP) when in secure state. + \param [in] topOfMainStack Main Stack Pointer value to set + */ +__STATIC_FORCEINLINE void __TZ_set_MSP_NS(uint32_t topOfMainStack) +{ + __ASM volatile ("MSR msp_ns, %0" : : "r" (topOfMainStack) : ); +} +#endif + + +#if (defined (__ARM_FEATURE_CMSE ) && (__ARM_FEATURE_CMSE == 3)) +/** + \brief Get Stack Pointer (non-secure) + \details Returns the current value of the non-secure Stack Pointer (SP) when in secure state. + \return SP Register value + */ +__STATIC_FORCEINLINE uint32_t __TZ_get_SP_NS(void) +{ + uint32_t result; + + __ASM volatile ("MRS %0, sp_ns" : "=r" (result) ); + return(result); +} + + +/** + \brief Set Stack Pointer (non-secure) + \details Assigns the given value to the non-secure Stack Pointer (SP) when in secure state. + \param [in] topOfStack Stack Pointer value to set + */ +__STATIC_FORCEINLINE void __TZ_set_SP_NS(uint32_t topOfStack) +{ + __ASM volatile ("MSR sp_ns, %0" : : "r" (topOfStack) : ); +} +#endif + + +/** + \brief Get Priority Mask + \details Returns the current state of the priority mask bit from the Priority Mask Register. + \return Priority Mask value + */ +__STATIC_FORCEINLINE uint32_t __get_PRIMASK(void) +{ + uint32_t result; + + __ASM volatile ("MRS %0, primask" : "=r" (result) ); + return(result); +} + + +#if (defined (__ARM_FEATURE_CMSE ) && (__ARM_FEATURE_CMSE == 3)) +/** + \brief Get Priority Mask (non-secure) + \details Returns the current state of the non-secure priority mask bit from the Priority Mask Register when in secure state. + \return Priority Mask value + */ +__STATIC_FORCEINLINE uint32_t __TZ_get_PRIMASK_NS(void) +{ + uint32_t result; + + __ASM volatile ("MRS %0, primask_ns" : "=r" (result) ); + return(result); +} +#endif + + +/** + \brief Set Priority Mask + \details Assigns the given value to the Priority Mask Register. + \param [in] priMask Priority Mask + */ +__STATIC_FORCEINLINE void __set_PRIMASK(uint32_t priMask) +{ + __ASM volatile ("MSR primask, %0" : : "r" (priMask) : "memory"); +} + + +#if (defined (__ARM_FEATURE_CMSE ) && (__ARM_FEATURE_CMSE == 3)) +/** + \brief Set Priority Mask (non-secure) + \details Assigns the given value to the non-secure Priority Mask Register when in secure state. + \param [in] priMask Priority Mask + */ +__STATIC_FORCEINLINE void __TZ_set_PRIMASK_NS(uint32_t priMask) +{ + __ASM volatile ("MSR primask_ns, %0" : : "r" (priMask) : "memory"); +} +#endif + + +#if ((defined (__ARM_ARCH_7M__ ) && (__ARM_ARCH_7M__ == 1)) || \ + (defined (__ARM_ARCH_7EM__ ) && (__ARM_ARCH_7EM__ == 1)) || \ + (defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) ) +/** + \brief Enable FIQ + \details Enables FIQ interrupts by clearing special-purpose register FAULTMASK. + Can only be executed in Privileged modes. + */ +__STATIC_FORCEINLINE void __enable_fault_irq(void) +{ + __ASM volatile ("cpsie f" : : : "memory"); +} + + +/** + \brief Disable FIQ + \details Disables FIQ interrupts by setting special-purpose register FAULTMASK. + Can only be executed in Privileged modes. + */ +__STATIC_FORCEINLINE void __disable_fault_irq(void) +{ + __ASM volatile ("cpsid f" : : : "memory"); +} + + +/** + \brief Get Base Priority + \details Returns the current value of the Base Priority register. + \return Base Priority register value + */ +__STATIC_FORCEINLINE uint32_t __get_BASEPRI(void) +{ + uint32_t result; + + __ASM volatile ("MRS %0, basepri" : "=r" (result) ); + return(result); +} + + +#if (defined (__ARM_FEATURE_CMSE ) && (__ARM_FEATURE_CMSE == 3)) +/** + \brief Get Base Priority (non-secure) + \details Returns the current value of the non-secure Base Priority register when in secure state. + \return Base Priority register value + */ +__STATIC_FORCEINLINE uint32_t __TZ_get_BASEPRI_NS(void) +{ + uint32_t result; + + __ASM volatile ("MRS %0, basepri_ns" : "=r" (result) ); + return(result); +} +#endif + + +/** + \brief Set Base Priority + \details Assigns the given value to the Base Priority register. + \param [in] basePri Base Priority value to set + */ +__STATIC_FORCEINLINE void __set_BASEPRI(uint32_t basePri) +{ + __ASM volatile ("MSR basepri, %0" : : "r" (basePri) : "memory"); +} + + +#if (defined (__ARM_FEATURE_CMSE ) && (__ARM_FEATURE_CMSE == 3)) +/** + \brief Set Base Priority (non-secure) + \details Assigns the given value to the non-secure Base Priority register when in secure state. + \param [in] basePri Base Priority value to set + */ +__STATIC_FORCEINLINE void __TZ_set_BASEPRI_NS(uint32_t basePri) +{ + __ASM volatile ("MSR basepri_ns, %0" : : "r" (basePri) : "memory"); +} +#endif + + +/** + \brief Set Base Priority with condition + \details Assigns the given value to the Base Priority register only if BASEPRI masking is disabled, + or the new value increases the BASEPRI priority level. + \param [in] basePri Base Priority value to set + */ +__STATIC_FORCEINLINE void __set_BASEPRI_MAX(uint32_t basePri) +{ + __ASM volatile ("MSR basepri_max, %0" : : "r" (basePri) : "memory"); +} + + +/** + \brief Get Fault Mask + \details Returns the current value of the Fault Mask register. + \return Fault Mask register value + */ +__STATIC_FORCEINLINE uint32_t __get_FAULTMASK(void) +{ + uint32_t result; + + __ASM volatile ("MRS %0, faultmask" : "=r" (result) ); + return(result); +} + + +#if (defined (__ARM_FEATURE_CMSE ) && (__ARM_FEATURE_CMSE == 3)) +/** + \brief Get Fault Mask (non-secure) + \details Returns the current value of the non-secure Fault Mask register when in secure state. + \return Fault Mask register value + */ +__STATIC_FORCEINLINE uint32_t __TZ_get_FAULTMASK_NS(void) +{ + uint32_t result; + + __ASM volatile ("MRS %0, faultmask_ns" : "=r" (result) ); + return(result); +} +#endif + + +/** + \brief Set Fault Mask + \details Assigns the given value to the Fault Mask register. + \param [in] faultMask Fault Mask value to set + */ +__STATIC_FORCEINLINE void __set_FAULTMASK(uint32_t faultMask) +{ + __ASM volatile ("MSR faultmask, %0" : : "r" (faultMask) : "memory"); +} + + +#if (defined (__ARM_FEATURE_CMSE ) && (__ARM_FEATURE_CMSE == 3)) +/** + \brief Set Fault Mask (non-secure) + \details Assigns the given value to the non-secure Fault Mask register when in secure state. + \param [in] faultMask Fault Mask value to set + */ +__STATIC_FORCEINLINE void __TZ_set_FAULTMASK_NS(uint32_t faultMask) +{ + __ASM volatile ("MSR faultmask_ns, %0" : : "r" (faultMask) : "memory"); +} +#endif + +#endif /* ((defined (__ARM_ARCH_7M__ ) && (__ARM_ARCH_7M__ == 1)) || \ + (defined (__ARM_ARCH_7EM__ ) && (__ARM_ARCH_7EM__ == 1)) || \ + (defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) ) */ + + +#if ((defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) || \ + (defined (__ARM_ARCH_8M_BASE__ ) && (__ARM_ARCH_8M_BASE__ == 1)) ) + +/** + \brief Get Process Stack Pointer Limit + Devices without ARMv8-M Main Extensions (i.e. Cortex-M23) lack the non-secure + Stack Pointer Limit register hence zero is returned always in non-secure + mode. + + \details Returns the current value of the Process Stack Pointer Limit (PSPLIM). + \return PSPLIM Register value + */ +__STATIC_FORCEINLINE uint32_t __get_PSPLIM(void) +{ +#if (!(defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) && \ + (!defined (__ARM_FEATURE_CMSE) || (__ARM_FEATURE_CMSE < 3))) + // without main extensions, the non-secure PSPLIM is RAZ/WI + return 0U; +#else + uint32_t result; + __ASM volatile ("MRS %0, psplim" : "=r" (result) ); + return result; +#endif +} + +#if (defined (__ARM_FEATURE_CMSE) && (__ARM_FEATURE_CMSE == 3)) +/** + \brief Get Process Stack Pointer Limit (non-secure) + Devices without ARMv8-M Main Extensions (i.e. Cortex-M23) lack the non-secure + Stack Pointer Limit register hence zero is returned always in non-secure + mode. + + \details Returns the current value of the non-secure Process Stack Pointer Limit (PSPLIM) when in secure state. + \return PSPLIM Register value + */ +__STATIC_FORCEINLINE uint32_t __TZ_get_PSPLIM_NS(void) +{ +#if (!(defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1))) + // without main extensions, the non-secure PSPLIM is RAZ/WI + return 0U; +#else + uint32_t result; + __ASM volatile ("MRS %0, psplim_ns" : "=r" (result) ); + return result; +#endif +} +#endif + + +/** + \brief Set Process Stack Pointer Limit + Devices without ARMv8-M Main Extensions (i.e. Cortex-M23) lack the non-secure + Stack Pointer Limit register hence the write is silently ignored in non-secure + mode. + + \details Assigns the given value to the Process Stack Pointer Limit (PSPLIM). + \param [in] ProcStackPtrLimit Process Stack Pointer Limit value to set + */ +__STATIC_FORCEINLINE void __set_PSPLIM(uint32_t ProcStackPtrLimit) +{ +#if (!(defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) && \ + (!defined (__ARM_FEATURE_CMSE) || (__ARM_FEATURE_CMSE < 3))) + // without main extensions, the non-secure PSPLIM is RAZ/WI + (void)ProcStackPtrLimit; +#else + __ASM volatile ("MSR psplim, %0" : : "r" (ProcStackPtrLimit)); +#endif +} + + +#if (defined (__ARM_FEATURE_CMSE ) && (__ARM_FEATURE_CMSE == 3)) +/** + \brief Set Process Stack Pointer (non-secure) + Devices without ARMv8-M Main Extensions (i.e. Cortex-M23) lack the non-secure + Stack Pointer Limit register hence the write is silently ignored in non-secure + mode. + + \details Assigns the given value to the non-secure Process Stack Pointer Limit (PSPLIM) when in secure state. + \param [in] ProcStackPtrLimit Process Stack Pointer Limit value to set + */ +__STATIC_FORCEINLINE void __TZ_set_PSPLIM_NS(uint32_t ProcStackPtrLimit) +{ +#if (!(defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1))) + // without main extensions, the non-secure PSPLIM is RAZ/WI + (void)ProcStackPtrLimit; +#else + __ASM volatile ("MSR psplim_ns, %0\n" : : "r" (ProcStackPtrLimit)); +#endif +} +#endif + + +/** + \brief Get Main Stack Pointer Limit + Devices without ARMv8-M Main Extensions (i.e. Cortex-M23) lack the non-secure + Stack Pointer Limit register hence zero is returned always. + + \details Returns the current value of the Main Stack Pointer Limit (MSPLIM). + \return MSPLIM Register value + */ +__STATIC_FORCEINLINE uint32_t __get_MSPLIM(void) +{ +#if (!(defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) && \ + (!defined (__ARM_FEATURE_CMSE) || (__ARM_FEATURE_CMSE < 3))) + // without main extensions, the non-secure MSPLIM is RAZ/WI + return 0U; +#else + uint32_t result; + __ASM volatile ("MRS %0, msplim" : "=r" (result) ); + return result; +#endif +} + + +#if (defined (__ARM_FEATURE_CMSE ) && (__ARM_FEATURE_CMSE == 3)) +/** + \brief Get Main Stack Pointer Limit (non-secure) + Devices without ARMv8-M Main Extensions (i.e. Cortex-M23) lack the non-secure + Stack Pointer Limit register hence zero is returned always. + + \details Returns the current value of the non-secure Main Stack Pointer Limit(MSPLIM) when in secure state. + \return MSPLIM Register value + */ +__STATIC_FORCEINLINE uint32_t __TZ_get_MSPLIM_NS(void) +{ +#if (!(defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1))) + // without main extensions, the non-secure MSPLIM is RAZ/WI + return 0U; +#else + uint32_t result; + __ASM volatile ("MRS %0, msplim_ns" : "=r" (result) ); + return result; +#endif +} +#endif + + +/** + \brief Set Main Stack Pointer Limit + Devices without ARMv8-M Main Extensions (i.e. Cortex-M23) lack the non-secure + Stack Pointer Limit register hence the write is silently ignored. + + \details Assigns the given value to the Main Stack Pointer Limit (MSPLIM). + \param [in] MainStackPtrLimit Main Stack Pointer Limit value to set + */ +__STATIC_FORCEINLINE void __set_MSPLIM(uint32_t MainStackPtrLimit) +{ +#if (!(defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) && \ + (!defined (__ARM_FEATURE_CMSE) || (__ARM_FEATURE_CMSE < 3))) + // without main extensions, the non-secure MSPLIM is RAZ/WI + (void)MainStackPtrLimit; +#else + __ASM volatile ("MSR msplim, %0" : : "r" (MainStackPtrLimit)); +#endif +} + + +#if (defined (__ARM_FEATURE_CMSE ) && (__ARM_FEATURE_CMSE == 3)) +/** + \brief Set Main Stack Pointer Limit (non-secure) + Devices without ARMv8-M Main Extensions (i.e. Cortex-M23) lack the non-secure + Stack Pointer Limit register hence the write is silently ignored. + + \details Assigns the given value to the non-secure Main Stack Pointer Limit (MSPLIM) when in secure state. + \param [in] MainStackPtrLimit Main Stack Pointer value to set + */ +__STATIC_FORCEINLINE void __TZ_set_MSPLIM_NS(uint32_t MainStackPtrLimit) +{ +#if (!(defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1))) + // without main extensions, the non-secure MSPLIM is RAZ/WI + (void)MainStackPtrLimit; +#else + __ASM volatile ("MSR msplim_ns, %0" : : "r" (MainStackPtrLimit)); +#endif +} +#endif + +#endif /* ((defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) || \ + (defined (__ARM_ARCH_8M_BASE__ ) && (__ARM_ARCH_8M_BASE__ == 1)) ) */ + +/** + \brief Get FPSCR + \details Returns the current value of the Floating Point Status/Control register. + \return Floating Point Status/Control register value + */ +#if ((defined (__FPU_PRESENT) && (__FPU_PRESENT == 1U)) && \ + (defined (__FPU_USED ) && (__FPU_USED == 1U)) ) +#define __get_FPSCR (uint32_t)__builtin_arm_get_fpscr +#else +#define __get_FPSCR() ((uint32_t)0U) +#endif + +/** + \brief Set FPSCR + \details Assigns the given value to the Floating Point Status/Control register. + \param [in] fpscr Floating Point Status/Control value to set + */ +#if ((defined (__FPU_PRESENT) && (__FPU_PRESENT == 1U)) && \ + (defined (__FPU_USED ) && (__FPU_USED == 1U)) ) +#define __set_FPSCR __builtin_arm_set_fpscr +#else +#define __set_FPSCR(x) ((void)(x)) +#endif + + +/*@} end of CMSIS_Core_RegAccFunctions */ + + +/* ################### Compiler specific Intrinsics ########################### */ +/** \defgroup CMSIS_SIMD_intrinsics CMSIS SIMD Intrinsics + Access to dedicated SIMD instructions + @{ +*/ + +#if (defined (__ARM_FEATURE_DSP) && (__ARM_FEATURE_DSP == 1)) + +__STATIC_FORCEINLINE uint32_t __SADD8(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM volatile ("sadd8 %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __QADD8(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM volatile ("qadd8 %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __SHADD8(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM volatile ("shadd8 %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __UADD8(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM volatile ("uadd8 %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __UQADD8(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM volatile ("uqadd8 %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __UHADD8(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM volatile ("uhadd8 %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + + +__STATIC_FORCEINLINE uint32_t __SSUB8(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM volatile ("ssub8 %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __QSUB8(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM volatile ("qsub8 %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __SHSUB8(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM volatile ("shsub8 %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __USUB8(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM volatile ("usub8 %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __UQSUB8(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM volatile ("uqsub8 %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __UHSUB8(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM volatile ("uhsub8 %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + + +__STATIC_FORCEINLINE uint32_t __SADD16(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM volatile ("sadd16 %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __QADD16(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM volatile ("qadd16 %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __SHADD16(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM volatile ("shadd16 %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __UADD16(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM volatile ("uadd16 %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __UQADD16(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM volatile ("uqadd16 %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __UHADD16(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM volatile ("uhadd16 %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __SSUB16(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM volatile ("ssub16 %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __QSUB16(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM volatile ("qsub16 %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __SHSUB16(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM volatile ("shsub16 %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __USUB16(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM volatile ("usub16 %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __UQSUB16(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM volatile ("uqsub16 %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __UHSUB16(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM volatile ("uhsub16 %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __SASX(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM volatile ("sasx %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __QASX(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM volatile ("qasx %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __SHASX(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM volatile ("shasx %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __UASX(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM volatile ("uasx %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __UQASX(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM volatile ("uqasx %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __UHASX(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM volatile ("uhasx %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __SSAX(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM volatile ("ssax %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __QSAX(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM volatile ("qsax %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __SHSAX(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM volatile ("shsax %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __USAX(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM volatile ("usax %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __UQSAX(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM volatile ("uqsax %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __UHSAX(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM volatile ("uhsax %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __USAD8(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM volatile ("usad8 %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __USADA8(uint32_t op1, uint32_t op2, uint32_t op3) +{ + uint32_t result; + + __ASM volatile ("usada8 %0, %1, %2, %3" : "=r" (result) : "r" (op1), "r" (op2), "r" (op3) ); + return(result); +} + +#define __SSAT16(ARG1,ARG2) \ +({ \ + int32_t __RES, __ARG1 = (ARG1); \ + __ASM ("ssat16 %0, %1, %2" : "=r" (__RES) : "I" (ARG2), "r" (__ARG1) ); \ + __RES; \ + }) + +#define __USAT16(ARG1,ARG2) \ +({ \ + uint32_t __RES, __ARG1 = (ARG1); \ + __ASM ("usat16 %0, %1, %2" : "=r" (__RES) : "I" (ARG2), "r" (__ARG1) ); \ + __RES; \ + }) + +__STATIC_FORCEINLINE uint32_t __UXTB16(uint32_t op1) +{ + uint32_t result; + + __ASM volatile ("uxtb16 %0, %1" : "=r" (result) : "r" (op1)); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __UXTAB16(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM volatile ("uxtab16 %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __SXTB16(uint32_t op1) +{ + uint32_t result; + + __ASM volatile ("sxtb16 %0, %1" : "=r" (result) : "r" (op1)); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __SXTAB16(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM volatile ("sxtab16 %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __SMUAD (uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM volatile ("smuad %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __SMUADX (uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM volatile ("smuadx %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __SMLAD (uint32_t op1, uint32_t op2, uint32_t op3) +{ + uint32_t result; + + __ASM volatile ("smlad %0, %1, %2, %3" : "=r" (result) : "r" (op1), "r" (op2), "r" (op3) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __SMLADX (uint32_t op1, uint32_t op2, uint32_t op3) +{ + uint32_t result; + + __ASM volatile ("smladx %0, %1, %2, %3" : "=r" (result) : "r" (op1), "r" (op2), "r" (op3) ); + return(result); +} + +__STATIC_FORCEINLINE uint64_t __SMLALD (uint32_t op1, uint32_t op2, uint64_t acc) +{ + union llreg_u{ + uint32_t w32[2]; + uint64_t w64; + } llr; + llr.w64 = acc; + +#ifndef __ARMEB__ /* Little endian */ + __ASM volatile ("smlald %0, %1, %2, %3" : "=r" (llr.w32[0]), "=r" (llr.w32[1]): "r" (op1), "r" (op2) , "0" (llr.w32[0]), "1" (llr.w32[1]) ); +#else /* Big endian */ + __ASM volatile ("smlald %0, %1, %2, %3" : "=r" (llr.w32[1]), "=r" (llr.w32[0]): "r" (op1), "r" (op2) , "0" (llr.w32[1]), "1" (llr.w32[0]) ); +#endif + + return(llr.w64); +} + +__STATIC_FORCEINLINE uint64_t __SMLALDX (uint32_t op1, uint32_t op2, uint64_t acc) +{ + union llreg_u{ + uint32_t w32[2]; + uint64_t w64; + } llr; + llr.w64 = acc; + +#ifndef __ARMEB__ /* Little endian */ + __ASM volatile ("smlaldx %0, %1, %2, %3" : "=r" (llr.w32[0]), "=r" (llr.w32[1]): "r" (op1), "r" (op2) , "0" (llr.w32[0]), "1" (llr.w32[1]) ); +#else /* Big endian */ + __ASM volatile ("smlaldx %0, %1, %2, %3" : "=r" (llr.w32[1]), "=r" (llr.w32[0]): "r" (op1), "r" (op2) , "0" (llr.w32[1]), "1" (llr.w32[0]) ); +#endif + + return(llr.w64); +} + +__STATIC_FORCEINLINE uint32_t __SMUSD (uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM volatile ("smusd %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __SMUSDX (uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM volatile ("smusdx %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __SMLSD (uint32_t op1, uint32_t op2, uint32_t op3) +{ + uint32_t result; + + __ASM volatile ("smlsd %0, %1, %2, %3" : "=r" (result) : "r" (op1), "r" (op2), "r" (op3) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __SMLSDX (uint32_t op1, uint32_t op2, uint32_t op3) +{ + uint32_t result; + + __ASM volatile ("smlsdx %0, %1, %2, %3" : "=r" (result) : "r" (op1), "r" (op2), "r" (op3) ); + return(result); +} + +__STATIC_FORCEINLINE uint64_t __SMLSLD (uint32_t op1, uint32_t op2, uint64_t acc) +{ + union llreg_u{ + uint32_t w32[2]; + uint64_t w64; + } llr; + llr.w64 = acc; + +#ifndef __ARMEB__ /* Little endian */ + __ASM volatile ("smlsld %0, %1, %2, %3" : "=r" (llr.w32[0]), "=r" (llr.w32[1]): "r" (op1), "r" (op2) , "0" (llr.w32[0]), "1" (llr.w32[1]) ); +#else /* Big endian */ + __ASM volatile ("smlsld %0, %1, %2, %3" : "=r" (llr.w32[1]), "=r" (llr.w32[0]): "r" (op1), "r" (op2) , "0" (llr.w32[1]), "1" (llr.w32[0]) ); +#endif + + return(llr.w64); +} + +__STATIC_FORCEINLINE uint64_t __SMLSLDX (uint32_t op1, uint32_t op2, uint64_t acc) +{ + union llreg_u{ + uint32_t w32[2]; + uint64_t w64; + } llr; + llr.w64 = acc; + +#ifndef __ARMEB__ /* Little endian */ + __ASM volatile ("smlsldx %0, %1, %2, %3" : "=r" (llr.w32[0]), "=r" (llr.w32[1]): "r" (op1), "r" (op2) , "0" (llr.w32[0]), "1" (llr.w32[1]) ); +#else /* Big endian */ + __ASM volatile ("smlsldx %0, %1, %2, %3" : "=r" (llr.w32[1]), "=r" (llr.w32[0]): "r" (op1), "r" (op2) , "0" (llr.w32[1]), "1" (llr.w32[0]) ); +#endif + + return(llr.w64); +} + +__STATIC_FORCEINLINE uint32_t __SEL (uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM volatile ("sel %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE int32_t __QADD( int32_t op1, int32_t op2) +{ + int32_t result; + + __ASM volatile ("qadd %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE int32_t __QSUB( int32_t op1, int32_t op2) +{ + int32_t result; + + __ASM volatile ("qsub %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +#define __PKHBT(ARG1,ARG2,ARG3) ( ((((uint32_t)(ARG1)) ) & 0x0000FFFFUL) | \ + ((((uint32_t)(ARG2)) << (ARG3)) & 0xFFFF0000UL) ) + +#define __PKHTB(ARG1,ARG2,ARG3) ( ((((uint32_t)(ARG1)) ) & 0xFFFF0000UL) | \ + ((((uint32_t)(ARG2)) >> (ARG3)) & 0x0000FFFFUL) ) + +#define __SXTB16_RORn(ARG1, ARG2) __SXTB16(__ROR(ARG1, ARG2)) + +#define __SXTAB16_RORn(ARG1, ARG2, ARG3) __SXTAB16(ARG1, __ROR(ARG2, ARG3)) + +__STATIC_FORCEINLINE int32_t __SMMLA (int32_t op1, int32_t op2, int32_t op3) +{ + int32_t result; + + __ASM volatile ("smmla %0, %1, %2, %3" : "=r" (result): "r" (op1), "r" (op2), "r" (op3) ); + return(result); +} + +#endif /* (__ARM_FEATURE_DSP == 1) */ +/*@} end of group CMSIS_SIMD_intrinsics */ + + +#endif /* __CMSIS_ARMCLANG_H */ diff --git a/lib/cmsis_core/cmsis_compiler.h b/lib/cmsis_core/cmsis_compiler.h new file mode 100644 index 00000000000..192f9b7c931 --- /dev/null +++ b/lib/cmsis_core/cmsis_compiler.h @@ -0,0 +1,303 @@ +/**************************************************************************//** + * @file cmsis_compiler.h + * @brief CMSIS compiler generic header file + * @version V5.3.0 + * @date 04. April 2023 + ******************************************************************************/ +/* + * Copyright (c) 2009-2023 Arm Limited. All rights reserved. + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an AS IS BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __CMSIS_COMPILER_H +#define __CMSIS_COMPILER_H + +#include + +/* + * Arm Compiler 4/5 + */ +#if defined ( __CC_ARM ) + #include "cmsis_armcc.h" + + +/* + * Arm Compiler 6.6 LTM (armclang) + */ +#elif defined (__ARMCC_VERSION) && (__ARMCC_VERSION >= 6010050) && (__ARMCC_VERSION < 6100100) + #include "cmsis_armclang_ltm.h" + + /* + * Arm Compiler above 6.10.1 (armclang) + */ +#elif defined (__ARMCC_VERSION) && (__ARMCC_VERSION >= 6100100) + #include "cmsis_armclang.h" + +/* + * TI Arm Clang Compiler (tiarmclang) + */ +#elif defined (__ti__) + #include "cmsis_tiarmclang.h" + +/* + * GNU Compiler + */ +#elif defined ( __GNUC__ ) + #include "cmsis_gcc.h" + + +/* + * IAR Compiler + */ +#elif defined ( __ICCARM__ ) + #include + + +/* + * TI Arm Compiler (armcl) + */ +#elif defined ( __TI_ARM__ ) + #include + + #ifndef __ASM + #define __ASM __asm + #endif + #ifndef __INLINE + #define __INLINE inline + #endif + #ifndef __STATIC_INLINE + #define __STATIC_INLINE static inline + #endif + #ifndef __STATIC_FORCEINLINE + #define __STATIC_FORCEINLINE __STATIC_INLINE + #endif + #ifndef __NO_RETURN + #define __NO_RETURN __attribute__((noreturn)) + #endif + #ifndef __USED + #define __USED __attribute__((used)) + #endif + #ifndef __WEAK + #define __WEAK __attribute__((weak)) + #endif + #ifndef __PACKED + #define __PACKED __attribute__((packed)) + #endif + #ifndef __PACKED_STRUCT + #define __PACKED_STRUCT struct __attribute__((packed)) + #endif + #ifndef __PACKED_UNION + #define __PACKED_UNION union __attribute__((packed)) + #endif + #ifndef __UNALIGNED_UINT32 /* deprecated */ + struct __attribute__((packed)) T_UINT32 { uint32_t v; }; + #define __UNALIGNED_UINT32(x) (((struct T_UINT32 *)(x))->v) + #endif + #ifndef __UNALIGNED_UINT16_WRITE + __PACKED_STRUCT T_UINT16_WRITE { uint16_t v; }; + #define __UNALIGNED_UINT16_WRITE(addr, val) (void)((((struct T_UINT16_WRITE *)(void*)(addr))->v) = (val)) + #endif + #ifndef __UNALIGNED_UINT16_READ + __PACKED_STRUCT T_UINT16_READ { uint16_t v; }; + #define __UNALIGNED_UINT16_READ(addr) (((const struct T_UINT16_READ *)(const void *)(addr))->v) + #endif + #ifndef __UNALIGNED_UINT32_WRITE + __PACKED_STRUCT T_UINT32_WRITE { uint32_t v; }; + #define __UNALIGNED_UINT32_WRITE(addr, val) (void)((((struct T_UINT32_WRITE *)(void *)(addr))->v) = (val)) + #endif + #ifndef __UNALIGNED_UINT32_READ + __PACKED_STRUCT T_UINT32_READ { uint32_t v; }; + #define __UNALIGNED_UINT32_READ(addr) (((const struct T_UINT32_READ *)(const void *)(addr))->v) + #endif + #ifndef __ALIGNED + #define __ALIGNED(x) __attribute__((aligned(x))) + #endif + #ifndef __RESTRICT + #define __RESTRICT __restrict + #endif + #ifndef __COMPILER_BARRIER + #warning No compiler specific solution for __COMPILER_BARRIER. __COMPILER_BARRIER is ignored. + #define __COMPILER_BARRIER() (void)0 + #endif + #ifndef __NO_INIT + #define __NO_INIT __attribute__ ((section (".bss.noinit"))) + #endif + #ifndef __ALIAS + #define __ALIAS(x) __attribute__ ((alias(x))) + #endif + +/* + * TASKING Compiler + */ +#elif defined ( __TASKING__ ) + /* + * The CMSIS functions have been implemented as intrinsics in the compiler. + * Please use "carm -?i" to get an up to date list of all intrinsics, + * Including the CMSIS ones. + */ + + #ifndef __ASM + #define __ASM __asm + #endif + #ifndef __INLINE + #define __INLINE inline + #endif + #ifndef __STATIC_INLINE + #define __STATIC_INLINE static inline + #endif + #ifndef __STATIC_FORCEINLINE + #define __STATIC_FORCEINLINE __STATIC_INLINE + #endif + #ifndef __NO_RETURN + #define __NO_RETURN __attribute__((noreturn)) + #endif + #ifndef __USED + #define __USED __attribute__((used)) + #endif + #ifndef __WEAK + #define __WEAK __attribute__((weak)) + #endif + #ifndef __PACKED + #define __PACKED __packed__ + #endif + #ifndef __PACKED_STRUCT + #define __PACKED_STRUCT struct __packed__ + #endif + #ifndef __PACKED_UNION + #define __PACKED_UNION union __packed__ + #endif + #ifndef __UNALIGNED_UINT32 /* deprecated */ + struct __packed__ T_UINT32 { uint32_t v; }; + #define __UNALIGNED_UINT32(x) (((struct T_UINT32 *)(x))->v) + #endif + #ifndef __UNALIGNED_UINT16_WRITE + __PACKED_STRUCT T_UINT16_WRITE { uint16_t v; }; + #define __UNALIGNED_UINT16_WRITE(addr, val) (void)((((struct T_UINT16_WRITE *)(void *)(addr))->v) = (val)) + #endif + #ifndef __UNALIGNED_UINT16_READ + __PACKED_STRUCT T_UINT16_READ { uint16_t v; }; + #define __UNALIGNED_UINT16_READ(addr) (((const struct T_UINT16_READ *)(const void *)(addr))->v) + #endif + #ifndef __UNALIGNED_UINT32_WRITE + __PACKED_STRUCT T_UINT32_WRITE { uint32_t v; }; + #define __UNALIGNED_UINT32_WRITE(addr, val) (void)((((struct T_UINT32_WRITE *)(void *)(addr))->v) = (val)) + #endif + #ifndef __UNALIGNED_UINT32_READ + __PACKED_STRUCT T_UINT32_READ { uint32_t v; }; + #define __UNALIGNED_UINT32_READ(addr) (((const struct T_UINT32_READ *)(const void *)(addr))->v) + #endif + #ifndef __ALIGNED + #define __ALIGNED(x) __align(x) + #endif + #ifndef __RESTRICT + #warning No compiler specific solution for __RESTRICT. __RESTRICT is ignored. + #define __RESTRICT + #endif + #ifndef __COMPILER_BARRIER + #warning No compiler specific solution for __COMPILER_BARRIER. __COMPILER_BARRIER is ignored. + #define __COMPILER_BARRIER() (void)0 + #endif + #ifndef __NO_INIT + #define __NO_INIT __attribute__ ((section (".bss.noinit"))) + #endif + #ifndef __ALIAS + #define __ALIAS(x) __attribute__ ((alias(x))) + #endif + +/* + * COSMIC Compiler + */ +#elif defined ( __CSMC__ ) + #include + + #ifndef __ASM + #define __ASM _asm + #endif + #ifndef __INLINE + #define __INLINE inline + #endif + #ifndef __STATIC_INLINE + #define __STATIC_INLINE static inline + #endif + #ifndef __STATIC_FORCEINLINE + #define __STATIC_FORCEINLINE __STATIC_INLINE + #endif + #ifndef __NO_RETURN + // NO RETURN is automatically detected hence no warning here + #define __NO_RETURN + #endif + #ifndef __USED + #warning No compiler specific solution for __USED. __USED is ignored. + #define __USED + #endif + #ifndef __WEAK + #define __WEAK __weak + #endif + #ifndef __PACKED + #define __PACKED @packed + #endif + #ifndef __PACKED_STRUCT + #define __PACKED_STRUCT @packed struct + #endif + #ifndef __PACKED_UNION + #define __PACKED_UNION @packed union + #endif + #ifndef __UNALIGNED_UINT32 /* deprecated */ + @packed struct T_UINT32 { uint32_t v; }; + #define __UNALIGNED_UINT32(x) (((struct T_UINT32 *)(x))->v) + #endif + #ifndef __UNALIGNED_UINT16_WRITE + __PACKED_STRUCT T_UINT16_WRITE { uint16_t v; }; + #define __UNALIGNED_UINT16_WRITE(addr, val) (void)((((struct T_UINT16_WRITE *)(void *)(addr))->v) = (val)) + #endif + #ifndef __UNALIGNED_UINT16_READ + __PACKED_STRUCT T_UINT16_READ { uint16_t v; }; + #define __UNALIGNED_UINT16_READ(addr) (((const struct T_UINT16_READ *)(const void *)(addr))->v) + #endif + #ifndef __UNALIGNED_UINT32_WRITE + __PACKED_STRUCT T_UINT32_WRITE { uint32_t v; }; + #define __UNALIGNED_UINT32_WRITE(addr, val) (void)((((struct T_UINT32_WRITE *)(void *)(addr))->v) = (val)) + #endif + #ifndef __UNALIGNED_UINT32_READ + __PACKED_STRUCT T_UINT32_READ { uint32_t v; }; + #define __UNALIGNED_UINT32_READ(addr) (((const struct T_UINT32_READ *)(const void *)(addr))->v) + #endif + #ifndef __ALIGNED + #warning No compiler specific solution for __ALIGNED. __ALIGNED is ignored. + #define __ALIGNED(x) + #endif + #ifndef __RESTRICT + #warning No compiler specific solution for __RESTRICT. __RESTRICT is ignored. + #define __RESTRICT + #endif + #ifndef __COMPILER_BARRIER + #warning No compiler specific solution for __COMPILER_BARRIER. __COMPILER_BARRIER is ignored. + #define __COMPILER_BARRIER() (void)0 + #endif + #ifndef __NO_INIT + #define __NO_INIT __attribute__ ((section (".bss.noinit"))) + #endif + #ifndef __ALIAS + #define __ALIAS(x) __attribute__ ((alias(x))) + #endif + +#else + #error Unknown compiler. +#endif + + +#endif /* __CMSIS_COMPILER_H */ + diff --git a/lib/cmsis_core/cmsis_gcc.h b/lib/cmsis_core/cmsis_gcc.h new file mode 100644 index 00000000000..4f0762d6dc4 --- /dev/null +++ b/lib/cmsis_core/cmsis_gcc.h @@ -0,0 +1,2217 @@ +/**************************************************************************//** + * @file cmsis_gcc.h + * @brief CMSIS compiler GCC header file + * @version V5.4.2 + * @date 17. December 2022 + ******************************************************************************/ +/* + * Copyright (c) 2009-2021 Arm Limited. All rights reserved. + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an AS IS BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __CMSIS_GCC_H +#define __CMSIS_GCC_H + +/* ignore some GCC warnings */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wsign-conversion" +#pragma GCC diagnostic ignored "-Wconversion" +#pragma GCC diagnostic ignored "-Wunused-parameter" + +/* Fallback for __has_builtin */ +#ifndef __has_builtin + #define __has_builtin(x) (0) +#endif + +/* CMSIS compiler specific defines */ +#ifndef __ASM + #define __ASM __asm +#endif +#ifndef __INLINE + #define __INLINE inline +#endif +#ifndef __STATIC_INLINE + #define __STATIC_INLINE static inline +#endif +#ifndef __STATIC_FORCEINLINE + #define __STATIC_FORCEINLINE __attribute__((always_inline)) static inline +#endif +#ifndef __NO_RETURN + #define __NO_RETURN __attribute__((__noreturn__)) +#endif +#ifndef __USED + #define __USED __attribute__((used)) +#endif +#ifndef __WEAK + #define __WEAK __attribute__((weak)) +#endif +#ifndef __PACKED + #define __PACKED __attribute__((packed, aligned(1))) +#endif +#ifndef __PACKED_STRUCT + #define __PACKED_STRUCT struct __attribute__((packed, aligned(1))) +#endif +#ifndef __PACKED_UNION + #define __PACKED_UNION union __attribute__((packed, aligned(1))) +#endif +#ifndef __UNALIGNED_UINT32 /* deprecated */ + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wpacked" + #pragma GCC diagnostic ignored "-Wattributes" + struct __attribute__((packed)) T_UINT32 { uint32_t v; }; + #pragma GCC diagnostic pop + #define __UNALIGNED_UINT32(x) (((struct T_UINT32 *)(x))->v) +#endif +#ifndef __UNALIGNED_UINT16_WRITE + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wpacked" + #pragma GCC diagnostic ignored "-Wattributes" + __PACKED_STRUCT T_UINT16_WRITE { uint16_t v; }; + #pragma GCC diagnostic pop + #define __UNALIGNED_UINT16_WRITE(addr, val) (void)((((struct T_UINT16_WRITE *)(void *)(addr))->v) = (val)) +#endif +#ifndef __UNALIGNED_UINT16_READ + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wpacked" + #pragma GCC diagnostic ignored "-Wattributes" + __PACKED_STRUCT T_UINT16_READ { uint16_t v; }; + #pragma GCC diagnostic pop + #define __UNALIGNED_UINT16_READ(addr) (((const struct T_UINT16_READ *)(const void *)(addr))->v) +#endif +#ifndef __UNALIGNED_UINT32_WRITE + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wpacked" + #pragma GCC diagnostic ignored "-Wattributes" + __PACKED_STRUCT T_UINT32_WRITE { uint32_t v; }; + #pragma GCC diagnostic pop + #define __UNALIGNED_UINT32_WRITE(addr, val) (void)((((struct T_UINT32_WRITE *)(void *)(addr))->v) = (val)) +#endif +#ifndef __UNALIGNED_UINT32_READ + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wpacked" + #pragma GCC diagnostic ignored "-Wattributes" + __PACKED_STRUCT T_UINT32_READ { uint32_t v; }; + #pragma GCC diagnostic pop + #define __UNALIGNED_UINT32_READ(addr) (((const struct T_UINT32_READ *)(const void *)(addr))->v) +#endif +#ifndef __ALIGNED + #define __ALIGNED(x) __attribute__((aligned(x))) +#endif +#ifndef __RESTRICT + #define __RESTRICT __restrict +#endif +#ifndef __COMPILER_BARRIER + #define __COMPILER_BARRIER() __ASM volatile("":::"memory") +#endif +#ifndef __NO_INIT + #define __NO_INIT __attribute__ ((section (".bss.noinit"))) +#endif +#ifndef __ALIAS + #define __ALIAS(x) __attribute__ ((alias(x))) +#endif + +/* ######################### Startup and Lowlevel Init ######################## */ + +#ifndef __PROGRAM_START + +/** + \brief Initializes data and bss sections + \details This default implementations initialized all data and additional bss + sections relying on .copy.table and .zero.table specified properly + in the used linker script. + + */ +__STATIC_FORCEINLINE __NO_RETURN void __cmsis_start(void) +{ + extern void _start(void) __NO_RETURN; + + typedef struct __copy_table { + uint32_t const* src; + uint32_t* dest; + uint32_t wlen; + } __copy_table_t; + + typedef struct __zero_table { + uint32_t* dest; + uint32_t wlen; + } __zero_table_t; + + extern const __copy_table_t __copy_table_start__; + extern const __copy_table_t __copy_table_end__; + extern const __zero_table_t __zero_table_start__; + extern const __zero_table_t __zero_table_end__; + + for (__copy_table_t const* pTable = &__copy_table_start__; pTable < &__copy_table_end__; ++pTable) { + for(uint32_t i=0u; iwlen; ++i) { + pTable->dest[i] = pTable->src[i]; + } + } + + for (__zero_table_t const* pTable = &__zero_table_start__; pTable < &__zero_table_end__; ++pTable) { + for(uint32_t i=0u; iwlen; ++i) { + pTable->dest[i] = 0u; + } + } + + _start(); +} + +#define __PROGRAM_START __cmsis_start +#endif + +#ifndef __INITIAL_SP +#define __INITIAL_SP __StackTop +#endif + +#ifndef __STACK_LIMIT +#define __STACK_LIMIT __StackLimit +#endif + +#ifndef __VECTOR_TABLE +#define __VECTOR_TABLE __Vectors +#endif + +#ifndef __VECTOR_TABLE_ATTRIBUTE +#define __VECTOR_TABLE_ATTRIBUTE __attribute__((used, section(".vectors"))) +#endif + +#if defined (__ARM_FEATURE_CMSE) && (__ARM_FEATURE_CMSE == 3U) +#ifndef __STACK_SEAL +#define __STACK_SEAL __StackSeal +#endif + +#ifndef __TZ_STACK_SEAL_SIZE +#define __TZ_STACK_SEAL_SIZE 8U +#endif + +#ifndef __TZ_STACK_SEAL_VALUE +#define __TZ_STACK_SEAL_VALUE 0xFEF5EDA5FEF5EDA5ULL +#endif + + +__STATIC_FORCEINLINE void __TZ_set_STACKSEAL_S (uint32_t* stackTop) { + *((uint64_t *)stackTop) = __TZ_STACK_SEAL_VALUE; +} +#endif + + +/* ########################## Core Instruction Access ######################### */ +/** \defgroup CMSIS_Core_InstructionInterface CMSIS Core Instruction Interface + Access to dedicated instructions + @{ +*/ + +/* Define macros for porting to both thumb1 and thumb2. + * For thumb1, use low register (r0-r7), specified by constraint "l" + * Otherwise, use general registers, specified by constraint "r" */ +#if defined (__thumb__) && !defined (__thumb2__) +#define __CMSIS_GCC_OUT_REG(r) "=l" (r) +#define __CMSIS_GCC_RW_REG(r) "+l" (r) +#define __CMSIS_GCC_USE_REG(r) "l" (r) +#else +#define __CMSIS_GCC_OUT_REG(r) "=r" (r) +#define __CMSIS_GCC_RW_REG(r) "+r" (r) +#define __CMSIS_GCC_USE_REG(r) "r" (r) +#endif + +/** + \brief No Operation + \details No Operation does nothing. This instruction can be used for code alignment purposes. + */ +#define __NOP() __ASM volatile ("nop") + +/** + \brief Wait For Interrupt + \details Wait For Interrupt is a hint instruction that suspends execution until one of a number of events occurs. + */ +#define __WFI() __ASM volatile ("wfi":::"memory") + + +/** + \brief Wait For Event + \details Wait For Event is a hint instruction that permits the processor to enter + a low-power state until one of a number of events occurs. + */ +#define __WFE() __ASM volatile ("wfe":::"memory") + + +/** + \brief Send Event + \details Send Event is a hint instruction. It causes an event to be signaled to the CPU. + */ +#define __SEV() __ASM volatile ("sev") + + +/** + \brief Instruction Synchronization Barrier + \details Instruction Synchronization Barrier flushes the pipeline in the processor, + so that all instructions following the ISB are fetched from cache or memory, + after the instruction has been completed. + */ +__STATIC_FORCEINLINE void __ISB(void) +{ + __ASM volatile ("isb 0xF":::"memory"); +} + + +/** + \brief Data Synchronization Barrier + \details Acts as a special kind of Data Memory Barrier. + It completes when all explicit memory accesses before this instruction complete. + */ +__STATIC_FORCEINLINE void __DSB(void) +{ + __ASM volatile ("dsb 0xF":::"memory"); +} + + +/** + \brief Data Memory Barrier + \details Ensures the apparent order of the explicit memory operations before + and after the instruction, without ensuring their completion. + */ +__STATIC_FORCEINLINE void __DMB(void) +{ + __ASM volatile ("dmb 0xF":::"memory"); +} + + +/** + \brief Reverse byte order (32 bit) + \details Reverses the byte order in unsigned integer value. For example, 0x12345678 becomes 0x78563412. + \param [in] value Value to reverse + \return Reversed value + */ +__STATIC_FORCEINLINE uint32_t __REV(uint32_t value) +{ +#if (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5) + return __builtin_bswap32(value); +#else + uint32_t result; + + __ASM ("rev %0, %1" : __CMSIS_GCC_OUT_REG (result) : __CMSIS_GCC_USE_REG (value) ); + return result; +#endif +} + + +/** + \brief Reverse byte order (16 bit) + \details Reverses the byte order within each halfword of a word. For example, 0x12345678 becomes 0x34127856. + \param [in] value Value to reverse + \return Reversed value + */ +__STATIC_FORCEINLINE uint32_t __REV16(uint32_t value) +{ + uint32_t result; + + __ASM ("rev16 %0, %1" : __CMSIS_GCC_OUT_REG (result) : __CMSIS_GCC_USE_REG (value) ); + return result; +} + + +/** + \brief Reverse byte order (16 bit) + \details Reverses the byte order in a 16-bit value and returns the signed 16-bit result. For example, 0x0080 becomes 0x8000. + \param [in] value Value to reverse + \return Reversed value + */ +__STATIC_FORCEINLINE int16_t __REVSH(int16_t value) +{ +#if (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8) + return (int16_t)__builtin_bswap16(value); +#else + int16_t result; + + __ASM ("revsh %0, %1" : __CMSIS_GCC_OUT_REG (result) : __CMSIS_GCC_USE_REG (value) ); + return result; +#endif +} + + +/** + \brief Rotate Right in unsigned value (32 bit) + \details Rotate Right (immediate) provides the value of the contents of a register rotated by a variable number of bits. + \param [in] op1 Value to rotate + \param [in] op2 Number of Bits to rotate + \return Rotated value + */ +__STATIC_FORCEINLINE uint32_t __ROR(uint32_t op1, uint32_t op2) +{ + op2 %= 32U; + if (op2 == 0U) + { + return op1; + } + return (op1 >> op2) | (op1 << (32U - op2)); +} + + +/** + \brief Breakpoint + \details Causes the processor to enter Debug state. + Debug tools can use this to investigate system state when the instruction at a particular address is reached. + \param [in] value is ignored by the processor. + If required, a debugger can use it to store additional information about the breakpoint. + */ +#define __BKPT(value) __ASM volatile ("bkpt "#value) + + +/** + \brief Reverse bit order of value + \details Reverses the bit order of the given value. + \param [in] value Value to reverse + \return Reversed value + */ +__STATIC_FORCEINLINE uint32_t __RBIT(uint32_t value) +{ + uint32_t result; + +#if ((defined (__ARM_ARCH_7M__ ) && (__ARM_ARCH_7M__ == 1)) || \ + (defined (__ARM_ARCH_7EM__ ) && (__ARM_ARCH_7EM__ == 1)) || \ + (defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) ) + __ASM ("rbit %0, %1" : "=r" (result) : "r" (value) ); +#else + uint32_t s = (4U /*sizeof(v)*/ * 8U) - 1U; /* extra shift needed at end */ + + result = value; /* r will be reversed bits of v; first get LSB of v */ + for (value >>= 1U; value != 0U; value >>= 1U) + { + result <<= 1U; + result |= value & 1U; + s--; + } + result <<= s; /* shift when v's highest bits are zero */ +#endif + return result; +} + + +/** + \brief Count leading zeros + \details Counts the number of leading zeros of a data value. + \param [in] value Value to count the leading zeros + \return number of leading zeros in value + */ +__STATIC_FORCEINLINE uint8_t __CLZ(uint32_t value) +{ + /* Even though __builtin_clz produces a CLZ instruction on ARM, formally + __builtin_clz(0) is undefined behaviour, so handle this case specially. + This guarantees ARM-compatible results if happening to compile on a non-ARM + target, and ensures the compiler doesn't decide to activate any + optimisations using the logic "value was passed to __builtin_clz, so it + is non-zero". + ARM GCC 7.3 and possibly earlier will optimise this test away, leaving a + single CLZ instruction. + */ + if (value == 0U) + { + return 32U; + } + return __builtin_clz(value); +} + + +#if ((defined (__ARM_ARCH_7M__ ) && (__ARM_ARCH_7M__ == 1)) || \ + (defined (__ARM_ARCH_7EM__ ) && (__ARM_ARCH_7EM__ == 1)) || \ + (defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) || \ + (defined (__ARM_ARCH_8M_BASE__ ) && (__ARM_ARCH_8M_BASE__ == 1)) ) +/** + \brief LDR Exclusive (8 bit) + \details Executes a exclusive LDR instruction for 8 bit value. + \param [in] ptr Pointer to data + \return value of type uint8_t at (*ptr) + */ +__STATIC_FORCEINLINE uint8_t __LDREXB(volatile uint8_t *addr) +{ + uint32_t result; + +#if (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8) + __ASM volatile ("ldrexb %0, %1" : "=r" (result) : "Q" (*addr) ); +#else + /* Prior to GCC 4.8, "Q" will be expanded to [rx, #0] which is not + accepted by assembler. So has to use following less efficient pattern. + */ + __ASM volatile ("ldrexb %0, [%1]" : "=r" (result) : "r" (addr) : "memory" ); +#endif + return ((uint8_t) result); /* Add explicit type cast here */ +} + + +/** + \brief LDR Exclusive (16 bit) + \details Executes a exclusive LDR instruction for 16 bit values. + \param [in] ptr Pointer to data + \return value of type uint16_t at (*ptr) + */ +__STATIC_FORCEINLINE uint16_t __LDREXH(volatile uint16_t *addr) +{ + uint32_t result; + +#if (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8) + __ASM volatile ("ldrexh %0, %1" : "=r" (result) : "Q" (*addr) ); +#else + /* Prior to GCC 4.8, "Q" will be expanded to [rx, #0] which is not + accepted by assembler. So has to use following less efficient pattern. + */ + __ASM volatile ("ldrexh %0, [%1]" : "=r" (result) : "r" (addr) : "memory" ); +#endif + return ((uint16_t) result); /* Add explicit type cast here */ +} + + +/** + \brief LDR Exclusive (32 bit) + \details Executes a exclusive LDR instruction for 32 bit values. + \param [in] ptr Pointer to data + \return value of type uint32_t at (*ptr) + */ +__STATIC_FORCEINLINE uint32_t __LDREXW(volatile uint32_t *addr) +{ + uint32_t result; + + __ASM volatile ("ldrex %0, %1" : "=r" (result) : "Q" (*addr) ); + return(result); +} + + +/** + \brief STR Exclusive (8 bit) + \details Executes a exclusive STR instruction for 8 bit values. + \param [in] value Value to store + \param [in] ptr Pointer to location + \return 0 Function succeeded + \return 1 Function failed + */ +__STATIC_FORCEINLINE uint32_t __STREXB(uint8_t value, volatile uint8_t *addr) +{ + uint32_t result; + + __ASM volatile ("strexb %0, %2, %1" : "=&r" (result), "=Q" (*addr) : "r" ((uint32_t)value) ); + return(result); +} + + +/** + \brief STR Exclusive (16 bit) + \details Executes a exclusive STR instruction for 16 bit values. + \param [in] value Value to store + \param [in] ptr Pointer to location + \return 0 Function succeeded + \return 1 Function failed + */ +__STATIC_FORCEINLINE uint32_t __STREXH(uint16_t value, volatile uint16_t *addr) +{ + uint32_t result; + + __ASM volatile ("strexh %0, %2, %1" : "=&r" (result), "=Q" (*addr) : "r" ((uint32_t)value) ); + return(result); +} + + +/** + \brief STR Exclusive (32 bit) + \details Executes a exclusive STR instruction for 32 bit values. + \param [in] value Value to store + \param [in] ptr Pointer to location + \return 0 Function succeeded + \return 1 Function failed + */ +__STATIC_FORCEINLINE uint32_t __STREXW(uint32_t value, volatile uint32_t *addr) +{ + uint32_t result; + + __ASM volatile ("strex %0, %2, %1" : "=&r" (result), "=Q" (*addr) : "r" (value) ); + return(result); +} + + +/** + \brief Remove the exclusive lock + \details Removes the exclusive lock which is created by LDREX. + */ +__STATIC_FORCEINLINE void __CLREX(void) +{ + __ASM volatile ("clrex" ::: "memory"); +} + +#endif /* ((defined (__ARM_ARCH_7M__ ) && (__ARM_ARCH_7M__ == 1)) || \ + (defined (__ARM_ARCH_7EM__ ) && (__ARM_ARCH_7EM__ == 1)) || \ + (defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) || \ + (defined (__ARM_ARCH_8M_BASE__ ) && (__ARM_ARCH_8M_BASE__ == 1)) ) */ + + +#if ((defined (__ARM_ARCH_7M__ ) && (__ARM_ARCH_7M__ == 1)) || \ + (defined (__ARM_ARCH_7EM__ ) && (__ARM_ARCH_7EM__ == 1)) || \ + (defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) ) +/** + \brief Signed Saturate + \details Saturates a signed value. + \param [in] ARG1 Value to be saturated + \param [in] ARG2 Bit position to saturate to (1..32) + \return Saturated value + */ +#define __SSAT(ARG1, ARG2) \ +__extension__ \ +({ \ + int32_t __RES, __ARG1 = (ARG1); \ + __ASM volatile ("ssat %0, %1, %2" : "=r" (__RES) : "I" (ARG2), "r" (__ARG1) : "cc" ); \ + __RES; \ + }) + + +/** + \brief Unsigned Saturate + \details Saturates an unsigned value. + \param [in] ARG1 Value to be saturated + \param [in] ARG2 Bit position to saturate to (0..31) + \return Saturated value + */ +#define __USAT(ARG1, ARG2) \ +__extension__ \ +({ \ + uint32_t __RES, __ARG1 = (ARG1); \ + __ASM volatile ("usat %0, %1, %2" : "=r" (__RES) : "I" (ARG2), "r" (__ARG1) : "cc" ); \ + __RES; \ + }) + + +/** + \brief Rotate Right with Extend (32 bit) + \details Moves each bit of a bitstring right by one bit. + The carry input is shifted in at the left end of the bitstring. + \param [in] value Value to rotate + \return Rotated value + */ +__STATIC_FORCEINLINE uint32_t __RRX(uint32_t value) +{ + uint32_t result; + + __ASM volatile ("rrx %0, %1" : __CMSIS_GCC_OUT_REG (result) : __CMSIS_GCC_USE_REG (value) ); + return(result); +} + + +/** + \brief LDRT Unprivileged (8 bit) + \details Executes a Unprivileged LDRT instruction for 8 bit value. + \param [in] ptr Pointer to data + \return value of type uint8_t at (*ptr) + */ +__STATIC_FORCEINLINE uint8_t __LDRBT(volatile uint8_t *ptr) +{ + uint32_t result; + +#if (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8) + __ASM volatile ("ldrbt %0, %1" : "=r" (result) : "Q" (*ptr) ); +#else + /* Prior to GCC 4.8, "Q" will be expanded to [rx, #0] which is not + accepted by assembler. So has to use following less efficient pattern. + */ + __ASM volatile ("ldrbt %0, [%1]" : "=r" (result) : "r" (ptr) : "memory" ); +#endif + return ((uint8_t) result); /* Add explicit type cast here */ +} + + +/** + \brief LDRT Unprivileged (16 bit) + \details Executes a Unprivileged LDRT instruction for 16 bit values. + \param [in] ptr Pointer to data + \return value of type uint16_t at (*ptr) + */ +__STATIC_FORCEINLINE uint16_t __LDRHT(volatile uint16_t *ptr) +{ + uint32_t result; + +#if (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8) + __ASM volatile ("ldrht %0, %1" : "=r" (result) : "Q" (*ptr) ); +#else + /* Prior to GCC 4.8, "Q" will be expanded to [rx, #0] which is not + accepted by assembler. So has to use following less efficient pattern. + */ + __ASM volatile ("ldrht %0, [%1]" : "=r" (result) : "r" (ptr) : "memory" ); +#endif + return ((uint16_t) result); /* Add explicit type cast here */ +} + + +/** + \brief LDRT Unprivileged (32 bit) + \details Executes a Unprivileged LDRT instruction for 32 bit values. + \param [in] ptr Pointer to data + \return value of type uint32_t at (*ptr) + */ +__STATIC_FORCEINLINE uint32_t __LDRT(volatile uint32_t *ptr) +{ + uint32_t result; + + __ASM volatile ("ldrt %0, %1" : "=r" (result) : "Q" (*ptr) ); + return(result); +} + + +/** + \brief STRT Unprivileged (8 bit) + \details Executes a Unprivileged STRT instruction for 8 bit values. + \param [in] value Value to store + \param [in] ptr Pointer to location + */ +__STATIC_FORCEINLINE void __STRBT(uint8_t value, volatile uint8_t *ptr) +{ + __ASM volatile ("strbt %1, %0" : "=Q" (*ptr) : "r" ((uint32_t)value) ); +} + + +/** + \brief STRT Unprivileged (16 bit) + \details Executes a Unprivileged STRT instruction for 16 bit values. + \param [in] value Value to store + \param [in] ptr Pointer to location + */ +__STATIC_FORCEINLINE void __STRHT(uint16_t value, volatile uint16_t *ptr) +{ + __ASM volatile ("strht %1, %0" : "=Q" (*ptr) : "r" ((uint32_t)value) ); +} + + +/** + \brief STRT Unprivileged (32 bit) + \details Executes a Unprivileged STRT instruction for 32 bit values. + \param [in] value Value to store + \param [in] ptr Pointer to location + */ +__STATIC_FORCEINLINE void __STRT(uint32_t value, volatile uint32_t *ptr) +{ + __ASM volatile ("strt %1, %0" : "=Q" (*ptr) : "r" (value) ); +} + +#else /* ((defined (__ARM_ARCH_7M__ ) && (__ARM_ARCH_7M__ == 1)) || \ + (defined (__ARM_ARCH_7EM__ ) && (__ARM_ARCH_7EM__ == 1)) || \ + (defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) ) */ + +/** + \brief Signed Saturate + \details Saturates a signed value. + \param [in] value Value to be saturated + \param [in] sat Bit position to saturate to (1..32) + \return Saturated value + */ +__STATIC_FORCEINLINE int32_t __SSAT(int32_t val, uint32_t sat) +{ + if ((sat >= 1U) && (sat <= 32U)) + { + const int32_t max = (int32_t)((1U << (sat - 1U)) - 1U); + const int32_t min = -1 - max ; + if (val > max) + { + return max; + } + else if (val < min) + { + return min; + } + } + return val; +} + +/** + \brief Unsigned Saturate + \details Saturates an unsigned value. + \param [in] value Value to be saturated + \param [in] sat Bit position to saturate to (0..31) + \return Saturated value + */ +__STATIC_FORCEINLINE uint32_t __USAT(int32_t val, uint32_t sat) +{ + if (sat <= 31U) + { + const uint32_t max = ((1U << sat) - 1U); + if (val > (int32_t)max) + { + return max; + } + else if (val < 0) + { + return 0U; + } + } + return (uint32_t)val; +} + +#endif /* ((defined (__ARM_ARCH_7M__ ) && (__ARM_ARCH_7M__ == 1)) || \ + (defined (__ARM_ARCH_7EM__ ) && (__ARM_ARCH_7EM__ == 1)) || \ + (defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) ) */ + + +#if ((defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) || \ + (defined (__ARM_ARCH_8M_BASE__ ) && (__ARM_ARCH_8M_BASE__ == 1)) ) +/** + \brief Load-Acquire (8 bit) + \details Executes a LDAB instruction for 8 bit value. + \param [in] ptr Pointer to data + \return value of type uint8_t at (*ptr) + */ +__STATIC_FORCEINLINE uint8_t __LDAB(volatile uint8_t *ptr) +{ + uint32_t result; + + __ASM volatile ("ldab %0, %1" : "=r" (result) : "Q" (*ptr) : "memory" ); + return ((uint8_t) result); +} + + +/** + \brief Load-Acquire (16 bit) + \details Executes a LDAH instruction for 16 bit values. + \param [in] ptr Pointer to data + \return value of type uint16_t at (*ptr) + */ +__STATIC_FORCEINLINE uint16_t __LDAH(volatile uint16_t *ptr) +{ + uint32_t result; + + __ASM volatile ("ldah %0, %1" : "=r" (result) : "Q" (*ptr) : "memory" ); + return ((uint16_t) result); +} + + +/** + \brief Load-Acquire (32 bit) + \details Executes a LDA instruction for 32 bit values. + \param [in] ptr Pointer to data + \return value of type uint32_t at (*ptr) + */ +__STATIC_FORCEINLINE uint32_t __LDA(volatile uint32_t *ptr) +{ + uint32_t result; + + __ASM volatile ("lda %0, %1" : "=r" (result) : "Q" (*ptr) : "memory" ); + return(result); +} + + +/** + \brief Store-Release (8 bit) + \details Executes a STLB instruction for 8 bit values. + \param [in] value Value to store + \param [in] ptr Pointer to location + */ +__STATIC_FORCEINLINE void __STLB(uint8_t value, volatile uint8_t *ptr) +{ + __ASM volatile ("stlb %1, %0" : "=Q" (*ptr) : "r" ((uint32_t)value) : "memory" ); +} + + +/** + \brief Store-Release (16 bit) + \details Executes a STLH instruction for 16 bit values. + \param [in] value Value to store + \param [in] ptr Pointer to location + */ +__STATIC_FORCEINLINE void __STLH(uint16_t value, volatile uint16_t *ptr) +{ + __ASM volatile ("stlh %1, %0" : "=Q" (*ptr) : "r" ((uint32_t)value) : "memory" ); +} + + +/** + \brief Store-Release (32 bit) + \details Executes a STL instruction for 32 bit values. + \param [in] value Value to store + \param [in] ptr Pointer to location + */ +__STATIC_FORCEINLINE void __STL(uint32_t value, volatile uint32_t *ptr) +{ + __ASM volatile ("stl %1, %0" : "=Q" (*ptr) : "r" ((uint32_t)value) : "memory" ); +} + + +/** + \brief Load-Acquire Exclusive (8 bit) + \details Executes a LDAB exclusive instruction for 8 bit value. + \param [in] ptr Pointer to data + \return value of type uint8_t at (*ptr) + */ +__STATIC_FORCEINLINE uint8_t __LDAEXB(volatile uint8_t *ptr) +{ + uint32_t result; + + __ASM volatile ("ldaexb %0, %1" : "=r" (result) : "Q" (*ptr) : "memory" ); + return ((uint8_t) result); +} + + +/** + \brief Load-Acquire Exclusive (16 bit) + \details Executes a LDAH exclusive instruction for 16 bit values. + \param [in] ptr Pointer to data + \return value of type uint16_t at (*ptr) + */ +__STATIC_FORCEINLINE uint16_t __LDAEXH(volatile uint16_t *ptr) +{ + uint32_t result; + + __ASM volatile ("ldaexh %0, %1" : "=r" (result) : "Q" (*ptr) : "memory" ); + return ((uint16_t) result); +} + + +/** + \brief Load-Acquire Exclusive (32 bit) + \details Executes a LDA exclusive instruction for 32 bit values. + \param [in] ptr Pointer to data + \return value of type uint32_t at (*ptr) + */ +__STATIC_FORCEINLINE uint32_t __LDAEX(volatile uint32_t *ptr) +{ + uint32_t result; + + __ASM volatile ("ldaex %0, %1" : "=r" (result) : "Q" (*ptr) : "memory" ); + return(result); +} + + +/** + \brief Store-Release Exclusive (8 bit) + \details Executes a STLB exclusive instruction for 8 bit values. + \param [in] value Value to store + \param [in] ptr Pointer to location + \return 0 Function succeeded + \return 1 Function failed + */ +__STATIC_FORCEINLINE uint32_t __STLEXB(uint8_t value, volatile uint8_t *ptr) +{ + uint32_t result; + + __ASM volatile ("stlexb %0, %2, %1" : "=&r" (result), "=Q" (*ptr) : "r" ((uint32_t)value) : "memory" ); + return(result); +} + + +/** + \brief Store-Release Exclusive (16 bit) + \details Executes a STLH exclusive instruction for 16 bit values. + \param [in] value Value to store + \param [in] ptr Pointer to location + \return 0 Function succeeded + \return 1 Function failed + */ +__STATIC_FORCEINLINE uint32_t __STLEXH(uint16_t value, volatile uint16_t *ptr) +{ + uint32_t result; + + __ASM volatile ("stlexh %0, %2, %1" : "=&r" (result), "=Q" (*ptr) : "r" ((uint32_t)value) : "memory" ); + return(result); +} + + +/** + \brief Store-Release Exclusive (32 bit) + \details Executes a STL exclusive instruction for 32 bit values. + \param [in] value Value to store + \param [in] ptr Pointer to location + \return 0 Function succeeded + \return 1 Function failed + */ +__STATIC_FORCEINLINE uint32_t __STLEX(uint32_t value, volatile uint32_t *ptr) +{ + uint32_t result; + + __ASM volatile ("stlex %0, %2, %1" : "=&r" (result), "=Q" (*ptr) : "r" ((uint32_t)value) : "memory" ); + return(result); +} + +#endif /* ((defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) || \ + (defined (__ARM_ARCH_8M_BASE__ ) && (__ARM_ARCH_8M_BASE__ == 1)) ) */ + +/*@}*/ /* end of group CMSIS_Core_InstructionInterface */ + + +/* ########################### Core Function Access ########################### */ +/** \ingroup CMSIS_Core_FunctionInterface + \defgroup CMSIS_Core_RegAccFunctions CMSIS Core Register Access Functions + @{ + */ + +/** + \brief Enable IRQ Interrupts + \details Enables IRQ interrupts by clearing special-purpose register PRIMASK. + Can only be executed in Privileged modes. + */ +__STATIC_FORCEINLINE void __enable_irq(void) +{ + __ASM volatile ("cpsie i" : : : "memory"); +} + + +/** + \brief Disable IRQ Interrupts + \details Disables IRQ interrupts by setting special-purpose register PRIMASK. + Can only be executed in Privileged modes. + */ +__STATIC_FORCEINLINE void __disable_irq(void) +{ + __ASM volatile ("cpsid i" : : : "memory"); +} + + +/** + \brief Get Control Register + \details Returns the content of the Control Register. + \return Control Register value + */ +__STATIC_FORCEINLINE uint32_t __get_CONTROL(void) +{ + uint32_t result; + + __ASM volatile ("MRS %0, control" : "=r" (result) ); + return(result); +} + + +#if (defined (__ARM_FEATURE_CMSE ) && (__ARM_FEATURE_CMSE == 3)) +/** + \brief Get Control Register (non-secure) + \details Returns the content of the non-secure Control Register when in secure mode. + \return non-secure Control Register value + */ +__STATIC_FORCEINLINE uint32_t __TZ_get_CONTROL_NS(void) +{ + uint32_t result; + + __ASM volatile ("MRS %0, control_ns" : "=r" (result) ); + return(result); +} +#endif + + +/** + \brief Set Control Register + \details Writes the given value to the Control Register. + \param [in] control Control Register value to set + */ +__STATIC_FORCEINLINE void __set_CONTROL(uint32_t control) +{ + __ASM volatile ("MSR control, %0" : : "r" (control) : "memory"); + __ISB(); +} + + +#if (defined (__ARM_FEATURE_CMSE ) && (__ARM_FEATURE_CMSE == 3)) +/** + \brief Set Control Register (non-secure) + \details Writes the given value to the non-secure Control Register when in secure state. + \param [in] control Control Register value to set + */ +__STATIC_FORCEINLINE void __TZ_set_CONTROL_NS(uint32_t control) +{ + __ASM volatile ("MSR control_ns, %0" : : "r" (control) : "memory"); + __ISB(); +} +#endif + + +/** + \brief Get IPSR Register + \details Returns the content of the IPSR Register. + \return IPSR Register value + */ +__STATIC_FORCEINLINE uint32_t __get_IPSR(void) +{ + uint32_t result; + + __ASM volatile ("MRS %0, ipsr" : "=r" (result) ); + return(result); +} + + +/** + \brief Get APSR Register + \details Returns the content of the APSR Register. + \return APSR Register value + */ +__STATIC_FORCEINLINE uint32_t __get_APSR(void) +{ + uint32_t result; + + __ASM volatile ("MRS %0, apsr" : "=r" (result) ); + return(result); +} + + +/** + \brief Get xPSR Register + \details Returns the content of the xPSR Register. + \return xPSR Register value + */ +__STATIC_FORCEINLINE uint32_t __get_xPSR(void) +{ + uint32_t result; + + __ASM volatile ("MRS %0, xpsr" : "=r" (result) ); + return(result); +} + + +/** + \brief Get Process Stack Pointer + \details Returns the current value of the Process Stack Pointer (PSP). + \return PSP Register value + */ +__STATIC_FORCEINLINE uint32_t __get_PSP(void) +{ + uint32_t result; + + __ASM volatile ("MRS %0, psp" : "=r" (result) ); + return(result); +} + + +#if (defined (__ARM_FEATURE_CMSE ) && (__ARM_FEATURE_CMSE == 3)) +/** + \brief Get Process Stack Pointer (non-secure) + \details Returns the current value of the non-secure Process Stack Pointer (PSP) when in secure state. + \return PSP Register value + */ +__STATIC_FORCEINLINE uint32_t __TZ_get_PSP_NS(void) +{ + uint32_t result; + + __ASM volatile ("MRS %0, psp_ns" : "=r" (result) ); + return(result); +} +#endif + + +/** + \brief Set Process Stack Pointer + \details Assigns the given value to the Process Stack Pointer (PSP). + \param [in] topOfProcStack Process Stack Pointer value to set + */ +__STATIC_FORCEINLINE void __set_PSP(uint32_t topOfProcStack) +{ + __ASM volatile ("MSR psp, %0" : : "r" (topOfProcStack) : ); +} + + +#if (defined (__ARM_FEATURE_CMSE ) && (__ARM_FEATURE_CMSE == 3)) +/** + \brief Set Process Stack Pointer (non-secure) + \details Assigns the given value to the non-secure Process Stack Pointer (PSP) when in secure state. + \param [in] topOfProcStack Process Stack Pointer value to set + */ +__STATIC_FORCEINLINE void __TZ_set_PSP_NS(uint32_t topOfProcStack) +{ + __ASM volatile ("MSR psp_ns, %0" : : "r" (topOfProcStack) : ); +} +#endif + + +/** + \brief Get Main Stack Pointer + \details Returns the current value of the Main Stack Pointer (MSP). + \return MSP Register value + */ +__STATIC_FORCEINLINE uint32_t __get_MSP(void) +{ + uint32_t result; + + __ASM volatile ("MRS %0, msp" : "=r" (result) ); + return(result); +} + + +#if (defined (__ARM_FEATURE_CMSE ) && (__ARM_FEATURE_CMSE == 3)) +/** + \brief Get Main Stack Pointer (non-secure) + \details Returns the current value of the non-secure Main Stack Pointer (MSP) when in secure state. + \return MSP Register value + */ +__STATIC_FORCEINLINE uint32_t __TZ_get_MSP_NS(void) +{ + uint32_t result; + + __ASM volatile ("MRS %0, msp_ns" : "=r" (result) ); + return(result); +} +#endif + + +/** + \brief Set Main Stack Pointer + \details Assigns the given value to the Main Stack Pointer (MSP). + \param [in] topOfMainStack Main Stack Pointer value to set + */ +__STATIC_FORCEINLINE void __set_MSP(uint32_t topOfMainStack) +{ + __ASM volatile ("MSR msp, %0" : : "r" (topOfMainStack) : ); +} + + +#if (defined (__ARM_FEATURE_CMSE ) && (__ARM_FEATURE_CMSE == 3)) +/** + \brief Set Main Stack Pointer (non-secure) + \details Assigns the given value to the non-secure Main Stack Pointer (MSP) when in secure state. + \param [in] topOfMainStack Main Stack Pointer value to set + */ +__STATIC_FORCEINLINE void __TZ_set_MSP_NS(uint32_t topOfMainStack) +{ + __ASM volatile ("MSR msp_ns, %0" : : "r" (topOfMainStack) : ); +} +#endif + + +#if (defined (__ARM_FEATURE_CMSE ) && (__ARM_FEATURE_CMSE == 3)) +/** + \brief Get Stack Pointer (non-secure) + \details Returns the current value of the non-secure Stack Pointer (SP) when in secure state. + \return SP Register value + */ +__STATIC_FORCEINLINE uint32_t __TZ_get_SP_NS(void) +{ + uint32_t result; + + __ASM volatile ("MRS %0, sp_ns" : "=r" (result) ); + return(result); +} + + +/** + \brief Set Stack Pointer (non-secure) + \details Assigns the given value to the non-secure Stack Pointer (SP) when in secure state. + \param [in] topOfStack Stack Pointer value to set + */ +__STATIC_FORCEINLINE void __TZ_set_SP_NS(uint32_t topOfStack) +{ + __ASM volatile ("MSR sp_ns, %0" : : "r" (topOfStack) : ); +} +#endif + + +/** + \brief Get Priority Mask + \details Returns the current state of the priority mask bit from the Priority Mask Register. + \return Priority Mask value + */ +__STATIC_FORCEINLINE uint32_t __get_PRIMASK(void) +{ + uint32_t result; + + __ASM volatile ("MRS %0, primask" : "=r" (result) ); + return(result); +} + + +#if (defined (__ARM_FEATURE_CMSE ) && (__ARM_FEATURE_CMSE == 3)) +/** + \brief Get Priority Mask (non-secure) + \details Returns the current state of the non-secure priority mask bit from the Priority Mask Register when in secure state. + \return Priority Mask value + */ +__STATIC_FORCEINLINE uint32_t __TZ_get_PRIMASK_NS(void) +{ + uint32_t result; + + __ASM volatile ("MRS %0, primask_ns" : "=r" (result) ); + return(result); +} +#endif + + +/** + \brief Set Priority Mask + \details Assigns the given value to the Priority Mask Register. + \param [in] priMask Priority Mask + */ +__STATIC_FORCEINLINE void __set_PRIMASK(uint32_t priMask) +{ + __ASM volatile ("MSR primask, %0" : : "r" (priMask) : "memory"); +} + + +#if (defined (__ARM_FEATURE_CMSE ) && (__ARM_FEATURE_CMSE == 3)) +/** + \brief Set Priority Mask (non-secure) + \details Assigns the given value to the non-secure Priority Mask Register when in secure state. + \param [in] priMask Priority Mask + */ +__STATIC_FORCEINLINE void __TZ_set_PRIMASK_NS(uint32_t priMask) +{ + __ASM volatile ("MSR primask_ns, %0" : : "r" (priMask) : "memory"); +} +#endif + + +#if ((defined (__ARM_ARCH_7M__ ) && (__ARM_ARCH_7M__ == 1)) || \ + (defined (__ARM_ARCH_7EM__ ) && (__ARM_ARCH_7EM__ == 1)) || \ + (defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) ) +/** + \brief Enable FIQ + \details Enables FIQ interrupts by clearing special-purpose register FAULTMASK. + Can only be executed in Privileged modes. + */ +__STATIC_FORCEINLINE void __enable_fault_irq(void) +{ + __ASM volatile ("cpsie f" : : : "memory"); +} + + +/** + \brief Disable FIQ + \details Disables FIQ interrupts by setting special-purpose register FAULTMASK. + Can only be executed in Privileged modes. + */ +__STATIC_FORCEINLINE void __disable_fault_irq(void) +{ + __ASM volatile ("cpsid f" : : : "memory"); +} + + +/** + \brief Get Base Priority + \details Returns the current value of the Base Priority register. + \return Base Priority register value + */ +__STATIC_FORCEINLINE uint32_t __get_BASEPRI(void) +{ + uint32_t result; + + __ASM volatile ("MRS %0, basepri" : "=r" (result) ); + return(result); +} + + +#if (defined (__ARM_FEATURE_CMSE ) && (__ARM_FEATURE_CMSE == 3)) +/** + \brief Get Base Priority (non-secure) + \details Returns the current value of the non-secure Base Priority register when in secure state. + \return Base Priority register value + */ +__STATIC_FORCEINLINE uint32_t __TZ_get_BASEPRI_NS(void) +{ + uint32_t result; + + __ASM volatile ("MRS %0, basepri_ns" : "=r" (result) ); + return(result); +} +#endif + + +/** + \brief Set Base Priority + \details Assigns the given value to the Base Priority register. + \param [in] basePri Base Priority value to set + */ +__STATIC_FORCEINLINE void __set_BASEPRI(uint32_t basePri) +{ + __ASM volatile ("MSR basepri, %0" : : "r" (basePri) : "memory"); +} + + +#if (defined (__ARM_FEATURE_CMSE ) && (__ARM_FEATURE_CMSE == 3)) +/** + \brief Set Base Priority (non-secure) + \details Assigns the given value to the non-secure Base Priority register when in secure state. + \param [in] basePri Base Priority value to set + */ +__STATIC_FORCEINLINE void __TZ_set_BASEPRI_NS(uint32_t basePri) +{ + __ASM volatile ("MSR basepri_ns, %0" : : "r" (basePri) : "memory"); +} +#endif + + +/** + \brief Set Base Priority with condition + \details Assigns the given value to the Base Priority register only if BASEPRI masking is disabled, + or the new value increases the BASEPRI priority level. + \param [in] basePri Base Priority value to set + */ +__STATIC_FORCEINLINE void __set_BASEPRI_MAX(uint32_t basePri) +{ + __ASM volatile ("MSR basepri_max, %0" : : "r" (basePri) : "memory"); +} + + +/** + \brief Get Fault Mask + \details Returns the current value of the Fault Mask register. + \return Fault Mask register value + */ +__STATIC_FORCEINLINE uint32_t __get_FAULTMASK(void) +{ + uint32_t result; + + __ASM volatile ("MRS %0, faultmask" : "=r" (result) ); + return(result); +} + + +#if (defined (__ARM_FEATURE_CMSE ) && (__ARM_FEATURE_CMSE == 3)) +/** + \brief Get Fault Mask (non-secure) + \details Returns the current value of the non-secure Fault Mask register when in secure state. + \return Fault Mask register value + */ +__STATIC_FORCEINLINE uint32_t __TZ_get_FAULTMASK_NS(void) +{ + uint32_t result; + + __ASM volatile ("MRS %0, faultmask_ns" : "=r" (result) ); + return(result); +} +#endif + + +/** + \brief Set Fault Mask + \details Assigns the given value to the Fault Mask register. + \param [in] faultMask Fault Mask value to set + */ +__STATIC_FORCEINLINE void __set_FAULTMASK(uint32_t faultMask) +{ + __ASM volatile ("MSR faultmask, %0" : : "r" (faultMask) : "memory"); +} + + +#if (defined (__ARM_FEATURE_CMSE ) && (__ARM_FEATURE_CMSE == 3)) +/** + \brief Set Fault Mask (non-secure) + \details Assigns the given value to the non-secure Fault Mask register when in secure state. + \param [in] faultMask Fault Mask value to set + */ +__STATIC_FORCEINLINE void __TZ_set_FAULTMASK_NS(uint32_t faultMask) +{ + __ASM volatile ("MSR faultmask_ns, %0" : : "r" (faultMask) : "memory"); +} +#endif + +#endif /* ((defined (__ARM_ARCH_7M__ ) && (__ARM_ARCH_7M__ == 1)) || \ + (defined (__ARM_ARCH_7EM__ ) && (__ARM_ARCH_7EM__ == 1)) || \ + (defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) ) */ + + +#if ((defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) || \ + (defined (__ARM_ARCH_8M_BASE__ ) && (__ARM_ARCH_8M_BASE__ == 1)) ) + +/** + \brief Get Process Stack Pointer Limit + Devices without ARMv8-M Main Extensions (i.e. Cortex-M23) lack the non-secure + Stack Pointer Limit register hence zero is returned always in non-secure + mode. + + \details Returns the current value of the Process Stack Pointer Limit (PSPLIM). + \return PSPLIM Register value + */ +__STATIC_FORCEINLINE uint32_t __get_PSPLIM(void) +{ +#if (!(defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) && \ + (!defined (__ARM_FEATURE_CMSE) || (__ARM_FEATURE_CMSE < 3))) + // without main extensions, the non-secure PSPLIM is RAZ/WI + return 0U; +#else + uint32_t result; + __ASM volatile ("MRS %0, psplim" : "=r" (result) ); + return result; +#endif +} + +#if (defined (__ARM_FEATURE_CMSE) && (__ARM_FEATURE_CMSE == 3)) +/** + \brief Get Process Stack Pointer Limit (non-secure) + Devices without ARMv8-M Main Extensions (i.e. Cortex-M23) lack the non-secure + Stack Pointer Limit register hence zero is returned always. + + \details Returns the current value of the non-secure Process Stack Pointer Limit (PSPLIM) when in secure state. + \return PSPLIM Register value + */ +__STATIC_FORCEINLINE uint32_t __TZ_get_PSPLIM_NS(void) +{ +#if (!(defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1))) + // without main extensions, the non-secure PSPLIM is RAZ/WI + return 0U; +#else + uint32_t result; + __ASM volatile ("MRS %0, psplim_ns" : "=r" (result) ); + return result; +#endif +} +#endif + + +/** + \brief Set Process Stack Pointer Limit + Devices without ARMv8-M Main Extensions (i.e. Cortex-M23) lack the non-secure + Stack Pointer Limit register hence the write is silently ignored in non-secure + mode. + + \details Assigns the given value to the Process Stack Pointer Limit (PSPLIM). + \param [in] ProcStackPtrLimit Process Stack Pointer Limit value to set + */ +__STATIC_FORCEINLINE void __set_PSPLIM(uint32_t ProcStackPtrLimit) +{ +#if (!(defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) && \ + (!defined (__ARM_FEATURE_CMSE) || (__ARM_FEATURE_CMSE < 3))) + // without main extensions, the non-secure PSPLIM is RAZ/WI + (void)ProcStackPtrLimit; +#else + __ASM volatile ("MSR psplim, %0" : : "r" (ProcStackPtrLimit)); +#endif +} + + +#if (defined (__ARM_FEATURE_CMSE ) && (__ARM_FEATURE_CMSE == 3)) +/** + \brief Set Process Stack Pointer (non-secure) + Devices without ARMv8-M Main Extensions (i.e. Cortex-M23) lack the non-secure + Stack Pointer Limit register hence the write is silently ignored. + + \details Assigns the given value to the non-secure Process Stack Pointer Limit (PSPLIM) when in secure state. + \param [in] ProcStackPtrLimit Process Stack Pointer Limit value to set + */ +__STATIC_FORCEINLINE void __TZ_set_PSPLIM_NS(uint32_t ProcStackPtrLimit) +{ +#if (!(defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1))) + // without main extensions, the non-secure PSPLIM is RAZ/WI + (void)ProcStackPtrLimit; +#else + __ASM volatile ("MSR psplim_ns, %0\n" : : "r" (ProcStackPtrLimit)); +#endif +} +#endif + + +/** + \brief Get Main Stack Pointer Limit + Devices without ARMv8-M Main Extensions (i.e. Cortex-M23) lack the non-secure + Stack Pointer Limit register hence zero is returned always in non-secure + mode. + + \details Returns the current value of the Main Stack Pointer Limit (MSPLIM). + \return MSPLIM Register value + */ +__STATIC_FORCEINLINE uint32_t __get_MSPLIM(void) +{ +#if (!(defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) && \ + (!defined (__ARM_FEATURE_CMSE) || (__ARM_FEATURE_CMSE < 3))) + // without main extensions, the non-secure MSPLIM is RAZ/WI + return 0U; +#else + uint32_t result; + __ASM volatile ("MRS %0, msplim" : "=r" (result) ); + return result; +#endif +} + + +#if (defined (__ARM_FEATURE_CMSE ) && (__ARM_FEATURE_CMSE == 3)) +/** + \brief Get Main Stack Pointer Limit (non-secure) + Devices without ARMv8-M Main Extensions (i.e. Cortex-M23) lack the non-secure + Stack Pointer Limit register hence zero is returned always. + + \details Returns the current value of the non-secure Main Stack Pointer Limit(MSPLIM) when in secure state. + \return MSPLIM Register value + */ +__STATIC_FORCEINLINE uint32_t __TZ_get_MSPLIM_NS(void) +{ +#if (!(defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1))) + // without main extensions, the non-secure MSPLIM is RAZ/WI + return 0U; +#else + uint32_t result; + __ASM volatile ("MRS %0, msplim_ns" : "=r" (result) ); + return result; +#endif +} +#endif + + +/** + \brief Set Main Stack Pointer Limit + Devices without ARMv8-M Main Extensions (i.e. Cortex-M23) lack the non-secure + Stack Pointer Limit register hence the write is silently ignored in non-secure + mode. + + \details Assigns the given value to the Main Stack Pointer Limit (MSPLIM). + \param [in] MainStackPtrLimit Main Stack Pointer Limit value to set + */ +__STATIC_FORCEINLINE void __set_MSPLIM(uint32_t MainStackPtrLimit) +{ +#if (!(defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) && \ + (!defined (__ARM_FEATURE_CMSE) || (__ARM_FEATURE_CMSE < 3))) + // without main extensions, the non-secure MSPLIM is RAZ/WI + (void)MainStackPtrLimit; +#else + __ASM volatile ("MSR msplim, %0" : : "r" (MainStackPtrLimit)); +#endif +} + + +#if (defined (__ARM_FEATURE_CMSE ) && (__ARM_FEATURE_CMSE == 3)) +/** + \brief Set Main Stack Pointer Limit (non-secure) + Devices without ARMv8-M Main Extensions (i.e. Cortex-M23) lack the non-secure + Stack Pointer Limit register hence the write is silently ignored. + + \details Assigns the given value to the non-secure Main Stack Pointer Limit (MSPLIM) when in secure state. + \param [in] MainStackPtrLimit Main Stack Pointer value to set + */ +__STATIC_FORCEINLINE void __TZ_set_MSPLIM_NS(uint32_t MainStackPtrLimit) +{ +#if (!(defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1))) + // without main extensions, the non-secure MSPLIM is RAZ/WI + (void)MainStackPtrLimit; +#else + __ASM volatile ("MSR msplim_ns, %0" : : "r" (MainStackPtrLimit)); +#endif +} +#endif + +#endif /* ((defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) || \ + (defined (__ARM_ARCH_8M_BASE__ ) && (__ARM_ARCH_8M_BASE__ == 1)) ) */ + + +/** + \brief Get FPSCR + \details Returns the current value of the Floating Point Status/Control register. + \return Floating Point Status/Control register value + */ +__STATIC_FORCEINLINE uint32_t __get_FPSCR(void) +{ +#if ((defined (__FPU_PRESENT) && (__FPU_PRESENT == 1U)) && \ + (defined (__FPU_USED ) && (__FPU_USED == 1U)) ) +#if __has_builtin(__builtin_arm_get_fpscr) +// Re-enable using built-in when GCC has been fixed +// || (__GNUC__ > 7) || (__GNUC__ == 7 && __GNUC_MINOR__ >= 2) + /* see https://gcc.gnu.org/ml/gcc-patches/2017-04/msg00443.html */ + return __builtin_arm_get_fpscr(); +#else + uint32_t result; + + __ASM volatile ("VMRS %0, fpscr" : "=r" (result) ); + return(result); +#endif +#else + return(0U); +#endif +} + + +/** + \brief Set FPSCR + \details Assigns the given value to the Floating Point Status/Control register. + \param [in] fpscr Floating Point Status/Control value to set + */ +__STATIC_FORCEINLINE void __set_FPSCR(uint32_t fpscr) +{ +#if ((defined (__FPU_PRESENT) && (__FPU_PRESENT == 1U)) && \ + (defined (__FPU_USED ) && (__FPU_USED == 1U)) ) +#if __has_builtin(__builtin_arm_set_fpscr) +// Re-enable using built-in when GCC has been fixed +// || (__GNUC__ > 7) || (__GNUC__ == 7 && __GNUC_MINOR__ >= 2) + /* see https://gcc.gnu.org/ml/gcc-patches/2017-04/msg00443.html */ + __builtin_arm_set_fpscr(fpscr); +#else + __ASM volatile ("VMSR fpscr, %0" : : "r" (fpscr) : "vfpcc", "memory"); +#endif +#else + (void)fpscr; +#endif +} + + +/*@} end of CMSIS_Core_RegAccFunctions */ + + +/* ################### Compiler specific Intrinsics ########################### */ +/** \defgroup CMSIS_SIMD_intrinsics CMSIS SIMD Intrinsics + Access to dedicated SIMD instructions + @{ +*/ + +#if (defined (__ARM_FEATURE_DSP) && (__ARM_FEATURE_DSP == 1)) + +__STATIC_FORCEINLINE uint32_t __SADD8(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM volatile ("sadd8 %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __QADD8(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM ("qadd8 %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __SHADD8(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM ("shadd8 %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __UADD8(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM volatile ("uadd8 %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __UQADD8(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM ("uqadd8 %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __UHADD8(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM ("uhadd8 %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + + +__STATIC_FORCEINLINE uint32_t __SSUB8(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM volatile ("ssub8 %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __QSUB8(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM ("qsub8 %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __SHSUB8(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM ("shsub8 %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __USUB8(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM volatile ("usub8 %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __UQSUB8(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM ("uqsub8 %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __UHSUB8(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM ("uhsub8 %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + + +__STATIC_FORCEINLINE uint32_t __SADD16(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM volatile ("sadd16 %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __QADD16(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM ("qadd16 %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __SHADD16(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM ("shadd16 %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __UADD16(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM volatile ("uadd16 %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __UQADD16(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM ("uqadd16 %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __UHADD16(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM ("uhadd16 %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __SSUB16(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM volatile ("ssub16 %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __QSUB16(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM ("qsub16 %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __SHSUB16(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM ("shsub16 %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __USUB16(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM volatile ("usub16 %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __UQSUB16(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM ("uqsub16 %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __UHSUB16(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM ("uhsub16 %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __SASX(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM volatile ("sasx %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __QASX(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM ("qasx %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __SHASX(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM ("shasx %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __UASX(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM volatile ("uasx %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __UQASX(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM ("uqasx %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __UHASX(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM ("uhasx %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __SSAX(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM volatile ("ssax %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __QSAX(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM ("qsax %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __SHSAX(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM ("shsax %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __USAX(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM volatile ("usax %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __UQSAX(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM ("uqsax %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __UHSAX(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM ("uhsax %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __USAD8(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM ("usad8 %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __USADA8(uint32_t op1, uint32_t op2, uint32_t op3) +{ + uint32_t result; + + __ASM ("usada8 %0, %1, %2, %3" : "=r" (result) : "r" (op1), "r" (op2), "r" (op3) ); + return(result); +} + +#define __SSAT16(ARG1, ARG2) \ +__extension__ \ +({ \ + int32_t __RES, __ARG1 = (ARG1); \ + __ASM volatile ("ssat16 %0, %1, %2" : "=r" (__RES) : "I" (ARG2), "r" (__ARG1) : "cc" ); \ + __RES; \ + }) + +#define __USAT16(ARG1, ARG2) \ +__extension__ \ +({ \ + uint32_t __RES, __ARG1 = (ARG1); \ + __ASM volatile ("usat16 %0, %1, %2" : "=r" (__RES) : "I" (ARG2), "r" (__ARG1) : "cc" ); \ + __RES; \ + }) + +__STATIC_FORCEINLINE uint32_t __UXTB16(uint32_t op1) +{ + uint32_t result; + + __ASM ("uxtb16 %0, %1" : "=r" (result) : "r" (op1)); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __UXTAB16(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM ("uxtab16 %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __SXTB16(uint32_t op1) +{ + uint32_t result; + + __ASM ("sxtb16 %0, %1" : "=r" (result) : "r" (op1)); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __SXTB16_RORn(uint32_t op1, uint32_t rotate) +{ + uint32_t result; + if (__builtin_constant_p(rotate) && ((rotate == 8U) || (rotate == 16U) || (rotate == 24U))) { + __ASM volatile ("sxtb16 %0, %1, ROR %2" : "=r" (result) : "r" (op1), "i" (rotate) ); + } else { + result = __SXTB16(__ROR(op1, rotate)) ; + } + return result; +} + +__STATIC_FORCEINLINE uint32_t __SXTAB16(uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM ("sxtab16 %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __SXTAB16_RORn(uint32_t op1, uint32_t op2, uint32_t rotate) +{ + uint32_t result; + if (__builtin_constant_p(rotate) && ((rotate == 8U) || (rotate == 16U) || (rotate == 24U))) { + __ASM volatile ("sxtab16 %0, %1, %2, ROR %3" : "=r" (result) : "r" (op1) , "r" (op2) , "i" (rotate)); + } else { + result = __SXTAB16(op1, __ROR(op2, rotate)); + } + return result; +} + + +__STATIC_FORCEINLINE uint32_t __SMUAD (uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM volatile ("smuad %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __SMUADX (uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM volatile ("smuadx %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __SMLAD (uint32_t op1, uint32_t op2, uint32_t op3) +{ + uint32_t result; + + __ASM volatile ("smlad %0, %1, %2, %3" : "=r" (result) : "r" (op1), "r" (op2), "r" (op3) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __SMLADX (uint32_t op1, uint32_t op2, uint32_t op3) +{ + uint32_t result; + + __ASM volatile ("smladx %0, %1, %2, %3" : "=r" (result) : "r" (op1), "r" (op2), "r" (op3) ); + return(result); +} + +__STATIC_FORCEINLINE uint64_t __SMLALD (uint32_t op1, uint32_t op2, uint64_t acc) +{ + union llreg_u{ + uint32_t w32[2]; + uint64_t w64; + } llr; + llr.w64 = acc; + +#ifndef __ARMEB__ /* Little endian */ + __ASM volatile ("smlald %0, %1, %2, %3" : "=r" (llr.w32[0]), "=r" (llr.w32[1]): "r" (op1), "r" (op2) , "0" (llr.w32[0]), "1" (llr.w32[1]) ); +#else /* Big endian */ + __ASM volatile ("smlald %0, %1, %2, %3" : "=r" (llr.w32[1]), "=r" (llr.w32[0]): "r" (op1), "r" (op2) , "0" (llr.w32[1]), "1" (llr.w32[0]) ); +#endif + + return(llr.w64); +} + +__STATIC_FORCEINLINE uint64_t __SMLALDX (uint32_t op1, uint32_t op2, uint64_t acc) +{ + union llreg_u{ + uint32_t w32[2]; + uint64_t w64; + } llr; + llr.w64 = acc; + +#ifndef __ARMEB__ /* Little endian */ + __ASM volatile ("smlaldx %0, %1, %2, %3" : "=r" (llr.w32[0]), "=r" (llr.w32[1]): "r" (op1), "r" (op2) , "0" (llr.w32[0]), "1" (llr.w32[1]) ); +#else /* Big endian */ + __ASM volatile ("smlaldx %0, %1, %2, %3" : "=r" (llr.w32[1]), "=r" (llr.w32[0]): "r" (op1), "r" (op2) , "0" (llr.w32[1]), "1" (llr.w32[0]) ); +#endif + + return(llr.w64); +} + +__STATIC_FORCEINLINE uint32_t __SMUSD (uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM volatile ("smusd %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __SMUSDX (uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM volatile ("smusdx %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __SMLSD (uint32_t op1, uint32_t op2, uint32_t op3) +{ + uint32_t result; + + __ASM volatile ("smlsd %0, %1, %2, %3" : "=r" (result) : "r" (op1), "r" (op2), "r" (op3) ); + return(result); +} + +__STATIC_FORCEINLINE uint32_t __SMLSDX (uint32_t op1, uint32_t op2, uint32_t op3) +{ + uint32_t result; + + __ASM volatile ("smlsdx %0, %1, %2, %3" : "=r" (result) : "r" (op1), "r" (op2), "r" (op3) ); + return(result); +} + +__STATIC_FORCEINLINE uint64_t __SMLSLD (uint32_t op1, uint32_t op2, uint64_t acc) +{ + union llreg_u{ + uint32_t w32[2]; + uint64_t w64; + } llr; + llr.w64 = acc; + +#ifndef __ARMEB__ /* Little endian */ + __ASM volatile ("smlsld %0, %1, %2, %3" : "=r" (llr.w32[0]), "=r" (llr.w32[1]): "r" (op1), "r" (op2) , "0" (llr.w32[0]), "1" (llr.w32[1]) ); +#else /* Big endian */ + __ASM volatile ("smlsld %0, %1, %2, %3" : "=r" (llr.w32[1]), "=r" (llr.w32[0]): "r" (op1), "r" (op2) , "0" (llr.w32[1]), "1" (llr.w32[0]) ); +#endif + + return(llr.w64); +} + +__STATIC_FORCEINLINE uint64_t __SMLSLDX (uint32_t op1, uint32_t op2, uint64_t acc) +{ + union llreg_u{ + uint32_t w32[2]; + uint64_t w64; + } llr; + llr.w64 = acc; + +#ifndef __ARMEB__ /* Little endian */ + __ASM volatile ("smlsldx %0, %1, %2, %3" : "=r" (llr.w32[0]), "=r" (llr.w32[1]): "r" (op1), "r" (op2) , "0" (llr.w32[0]), "1" (llr.w32[1]) ); +#else /* Big endian */ + __ASM volatile ("smlsldx %0, %1, %2, %3" : "=r" (llr.w32[1]), "=r" (llr.w32[0]): "r" (op1), "r" (op2) , "0" (llr.w32[1]), "1" (llr.w32[0]) ); +#endif + + return(llr.w64); +} + +__STATIC_FORCEINLINE uint32_t __SEL (uint32_t op1, uint32_t op2) +{ + uint32_t result; + + __ASM volatile ("sel %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE int32_t __QADD( int32_t op1, int32_t op2) +{ + int32_t result; + + __ASM volatile ("qadd %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + +__STATIC_FORCEINLINE int32_t __QSUB( int32_t op1, int32_t op2) +{ + int32_t result; + + __ASM volatile ("qsub %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + + +#define __PKHBT(ARG1,ARG2,ARG3) \ +__extension__ \ +({ \ + uint32_t __RES, __ARG1 = (ARG1), __ARG2 = (ARG2); \ + __ASM ("pkhbt %0, %1, %2, lsl %3" : "=r" (__RES) : "r" (__ARG1), "r" (__ARG2), "I" (ARG3) ); \ + __RES; \ + }) + +#define __PKHTB(ARG1,ARG2,ARG3) \ +__extension__ \ +({ \ + uint32_t __RES, __ARG1 = (ARG1), __ARG2 = (ARG2); \ + if (ARG3 == 0) \ + __ASM ("pkhtb %0, %1, %2" : "=r" (__RES) : "r" (__ARG1), "r" (__ARG2) ); \ + else \ + __ASM ("pkhtb %0, %1, %2, asr %3" : "=r" (__RES) : "r" (__ARG1), "r" (__ARG2), "I" (ARG3) ); \ + __RES; \ + }) + + +__STATIC_FORCEINLINE int32_t __SMMLA (int32_t op1, int32_t op2, int32_t op3) +{ + int32_t result; + + __ASM ("smmla %0, %1, %2, %3" : "=r" (result): "r" (op1), "r" (op2), "r" (op3) ); + return(result); +} + +#endif /* (__ARM_FEATURE_DSP == 1) */ +/*@} end of group CMSIS_SIMD_intrinsics */ + + +#pragma GCC diagnostic pop + +#endif /* __CMSIS_GCC_H */ diff --git a/lib/cmsis_core/cmsis_iccarm.h b/lib/cmsis_core/cmsis_iccarm.h new file mode 100644 index 00000000000..47d6a859e51 --- /dev/null +++ b/lib/cmsis_core/cmsis_iccarm.h @@ -0,0 +1,1008 @@ +/**************************************************************************//** + * @file cmsis_iccarm.h + * @brief CMSIS compiler ICCARM (IAR Compiler for Arm) header file + * @version V5.4.0 + * @date 20. January 2023 + ******************************************************************************/ + +//------------------------------------------------------------------------------ +// +// Copyright (c) 2017-2021 IAR Systems +// Copyright (c) 2017-2023 Arm Limited. All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License") +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//------------------------------------------------------------------------------ + + +#ifndef __CMSIS_ICCARM_H__ +#define __CMSIS_ICCARM_H__ + +#ifndef __ICCARM__ + #error This file should only be compiled by ICCARM +#endif + +#pragma system_include + +#define __IAR_FT _Pragma("inline=forced") __intrinsic + +#if (__VER__ >= 8000000) + #define __ICCARM_V8 1 +#else + #define __ICCARM_V8 0 +#endif + +#ifndef __ALIGNED + #if __ICCARM_V8 + #define __ALIGNED(x) __attribute__((aligned(x))) + #elif (__VER__ >= 7080000) + /* Needs IAR language extensions */ + #define __ALIGNED(x) __attribute__((aligned(x))) + #else + #warning No compiler specific solution for __ALIGNED.__ALIGNED is ignored. + #define __ALIGNED(x) + #endif +#endif + + +/* Define compiler macros for CPU architecture, used in CMSIS 5. + */ +#if __ARM_ARCH_6M__ || __ARM_ARCH_7M__ || __ARM_ARCH_7EM__ || __ARM_ARCH_8M_BASE__ || __ARM_ARCH_8M_MAIN__ +/* Macros already defined */ +#else + #if defined(__ARM8M_MAINLINE__) || defined(__ARM8EM_MAINLINE__) + #define __ARM_ARCH_8M_MAIN__ 1 + #elif defined(__ARM8M_BASELINE__) + #define __ARM_ARCH_8M_BASE__ 1 + #elif defined(__ARM_ARCH_PROFILE) && __ARM_ARCH_PROFILE == 'M' + #if __ARM_ARCH == 6 + #define __ARM_ARCH_6M__ 1 + #elif __ARM_ARCH == 7 + #if __ARM_FEATURE_DSP + #define __ARM_ARCH_7EM__ 1 + #else + #define __ARM_ARCH_7M__ 1 + #endif + #endif /* __ARM_ARCH */ + #endif /* __ARM_ARCH_PROFILE == 'M' */ +#endif + +/* Alternativ core deduction for older ICCARM's */ +#if !defined(__ARM_ARCH_6M__) && !defined(__ARM_ARCH_7M__) && !defined(__ARM_ARCH_7EM__) && \ + !defined(__ARM_ARCH_8M_BASE__) && !defined(__ARM_ARCH_8M_MAIN__) + #if defined(__ARM6M__) && (__CORE__ == __ARM6M__) + #define __ARM_ARCH_6M__ 1 + #elif defined(__ARM7M__) && (__CORE__ == __ARM7M__) + #define __ARM_ARCH_7M__ 1 + #elif defined(__ARM7EM__) && (__CORE__ == __ARM7EM__) + #define __ARM_ARCH_7EM__ 1 + #elif defined(__ARM8M_BASELINE__) && (__CORE == __ARM8M_BASELINE__) + #define __ARM_ARCH_8M_BASE__ 1 + #elif defined(__ARM8M_MAINLINE__) && (__CORE == __ARM8M_MAINLINE__) + #define __ARM_ARCH_8M_MAIN__ 1 + #elif defined(__ARM8EM_MAINLINE__) && (__CORE == __ARM8EM_MAINLINE__) + #define __ARM_ARCH_8M_MAIN__ 1 + #else + #error "Unknown target." + #endif +#endif + + + +#if defined(__ARM_ARCH_6M__) && __ARM_ARCH_6M__==1 + #define __IAR_M0_FAMILY 1 +#elif defined(__ARM_ARCH_8M_BASE__) && __ARM_ARCH_8M_BASE__==1 + #define __IAR_M0_FAMILY 1 +#else + #define __IAR_M0_FAMILY 0 +#endif + +#ifndef __NO_INIT + #define __NO_INIT __attribute__ ((section (".noinit"))) +#endif +#ifndef __ALIAS + #define __ALIAS(x) __attribute__ ((alias(x))) +#endif + +#ifndef __ASM + #define __ASM __asm +#endif + +#ifndef __COMPILER_BARRIER + #define __COMPILER_BARRIER() __ASM volatile("":::"memory") +#endif + +#ifndef __INLINE + #define __INLINE inline +#endif + +#ifndef __NO_RETURN + #if __ICCARM_V8 + #define __NO_RETURN __attribute__((__noreturn__)) + #else + #define __NO_RETURN _Pragma("object_attribute=__noreturn") + #endif +#endif + +#ifndef __PACKED + #if __ICCARM_V8 + #define __PACKED __attribute__((packed, aligned(1))) + #else + /* Needs IAR language extensions */ + #define __PACKED __packed + #endif +#endif + +#ifndef __PACKED_STRUCT + #if __ICCARM_V8 + #define __PACKED_STRUCT struct __attribute__((packed, aligned(1))) + #else + /* Needs IAR language extensions */ + #define __PACKED_STRUCT __packed struct + #endif +#endif + +#ifndef __PACKED_UNION + #if __ICCARM_V8 + #define __PACKED_UNION union __attribute__((packed, aligned(1))) + #else + /* Needs IAR language extensions */ + #define __PACKED_UNION __packed union + #endif +#endif + +#ifndef __RESTRICT + #if __ICCARM_V8 + #define __RESTRICT __restrict + #else + /* Needs IAR language extensions */ + #define __RESTRICT restrict + #endif +#endif + +#ifndef __STATIC_INLINE + #define __STATIC_INLINE static inline +#endif + +#ifndef __FORCEINLINE + #define __FORCEINLINE _Pragma("inline=forced") +#endif + +#ifndef __STATIC_FORCEINLINE + #define __STATIC_FORCEINLINE __FORCEINLINE __STATIC_INLINE +#endif + +#ifndef __UNALIGNED_UINT16_READ +#pragma language=save +#pragma language=extended +__IAR_FT uint16_t __iar_uint16_read(void const *ptr) +{ + return *(__packed uint16_t*)(ptr); +} +#pragma language=restore +#define __UNALIGNED_UINT16_READ(PTR) __iar_uint16_read(PTR) +#endif + + +#ifndef __UNALIGNED_UINT16_WRITE +#pragma language=save +#pragma language=extended +__IAR_FT void __iar_uint16_write(void const *ptr, uint16_t val) +{ + *(__packed uint16_t*)(ptr) = val;; +} +#pragma language=restore +#define __UNALIGNED_UINT16_WRITE(PTR,VAL) __iar_uint16_write(PTR,VAL) +#endif + +#ifndef __UNALIGNED_UINT32_READ +#pragma language=save +#pragma language=extended +__IAR_FT uint32_t __iar_uint32_read(void const *ptr) +{ + return *(__packed uint32_t*)(ptr); +} +#pragma language=restore +#define __UNALIGNED_UINT32_READ(PTR) __iar_uint32_read(PTR) +#endif + +#ifndef __UNALIGNED_UINT32_WRITE +#pragma language=save +#pragma language=extended +__IAR_FT void __iar_uint32_write(void const *ptr, uint32_t val) +{ + *(__packed uint32_t*)(ptr) = val;; +} +#pragma language=restore +#define __UNALIGNED_UINT32_WRITE(PTR,VAL) __iar_uint32_write(PTR,VAL) +#endif + +#ifndef __UNALIGNED_UINT32 /* deprecated */ +#pragma language=save +#pragma language=extended +__packed struct __iar_u32 { uint32_t v; }; +#pragma language=restore +#define __UNALIGNED_UINT32(PTR) (((struct __iar_u32 *)(PTR))->v) +#endif + +#ifndef __USED + #if __ICCARM_V8 + #define __USED __attribute__((used)) + #else + #define __USED _Pragma("__root") + #endif +#endif + +#undef __WEAK /* undo the definition from DLib_Defaults.h */ +#ifndef __WEAK + #if __ICCARM_V8 + #define __WEAK __attribute__((weak)) + #else + #define __WEAK _Pragma("__weak") + #endif +#endif + +#ifndef __PROGRAM_START +#define __PROGRAM_START __iar_program_start +#endif + +#ifndef __INITIAL_SP +#define __INITIAL_SP CSTACK$$Limit +#endif + +#ifndef __STACK_LIMIT +#define __STACK_LIMIT CSTACK$$Base +#endif + +#ifndef __VECTOR_TABLE +#define __VECTOR_TABLE __vector_table +#endif + +#ifndef __VECTOR_TABLE_ATTRIBUTE +#define __VECTOR_TABLE_ATTRIBUTE @".intvec" +#endif + +#if defined (__ARM_FEATURE_CMSE) && (__ARM_FEATURE_CMSE == 3U) +#ifndef __STACK_SEAL +#define __STACK_SEAL STACKSEAL$$Base +#endif + +#ifndef __TZ_STACK_SEAL_SIZE +#define __TZ_STACK_SEAL_SIZE 8U +#endif + +#ifndef __TZ_STACK_SEAL_VALUE +#define __TZ_STACK_SEAL_VALUE 0xFEF5EDA5FEF5EDA5ULL +#endif + +__STATIC_FORCEINLINE void __TZ_set_STACKSEAL_S (uint32_t* stackTop) { + *((uint64_t *)stackTop) = __TZ_STACK_SEAL_VALUE; +} +#endif + +#ifndef __ICCARM_INTRINSICS_VERSION__ + #define __ICCARM_INTRINSICS_VERSION__ 0 +#endif + +#if __ICCARM_INTRINSICS_VERSION__ == 2 + + #if defined(__CLZ) + #undef __CLZ + #endif + #if defined(__REVSH) + #undef __REVSH + #endif + #if defined(__RBIT) + #undef __RBIT + #endif + #if defined(__SSAT) + #undef __SSAT + #endif + #if defined(__USAT) + #undef __USAT + #endif + + #include "iccarm_builtin.h" + + #define __disable_fault_irq __iar_builtin_disable_fiq + #define __disable_irq __iar_builtin_disable_interrupt + #define __enable_fault_irq __iar_builtin_enable_fiq + #define __enable_irq __iar_builtin_enable_interrupt + #define __arm_rsr __iar_builtin_rsr + #define __arm_wsr __iar_builtin_wsr + + + #define __get_APSR() (__arm_rsr("APSR")) + #define __get_BASEPRI() (__arm_rsr("BASEPRI")) + #define __get_CONTROL() (__arm_rsr("CONTROL")) + #define __get_FAULTMASK() (__arm_rsr("FAULTMASK")) + + #if ((defined (__FPU_PRESENT) && (__FPU_PRESENT == 1U)) && \ + (defined (__FPU_USED ) && (__FPU_USED == 1U)) ) + #define __get_FPSCR() (__arm_rsr("FPSCR")) + #define __set_FPSCR(VALUE) (__arm_wsr("FPSCR", (VALUE))) + #else + #define __get_FPSCR() ( 0 ) + #define __set_FPSCR(VALUE) ((void)VALUE) + #endif + + #define __get_IPSR() (__arm_rsr("IPSR")) + #define __get_MSP() (__arm_rsr("MSP")) + #if (!(defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) && \ + (!defined (__ARM_FEATURE_CMSE) || (__ARM_FEATURE_CMSE < 3))) + // without main extensions, the non-secure MSPLIM is RAZ/WI + #define __get_MSPLIM() (0U) + #else + #define __get_MSPLIM() (__arm_rsr("MSPLIM")) + #endif + #define __get_PRIMASK() (__arm_rsr("PRIMASK")) + #define __get_PSP() (__arm_rsr("PSP")) + + #if (!(defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) && \ + (!defined (__ARM_FEATURE_CMSE) || (__ARM_FEATURE_CMSE < 3))) + // without main extensions, the non-secure PSPLIM is RAZ/WI + #define __get_PSPLIM() (0U) + #else + #define __get_PSPLIM() (__arm_rsr("PSPLIM")) + #endif + + #define __get_xPSR() (__arm_rsr("xPSR")) + + #define __set_BASEPRI(VALUE) (__arm_wsr("BASEPRI", (VALUE))) + #define __set_BASEPRI_MAX(VALUE) (__arm_wsr("BASEPRI_MAX", (VALUE))) + +__STATIC_FORCEINLINE void __set_CONTROL(uint32_t control) +{ + __arm_wsr("CONTROL", control); + __iar_builtin_ISB(); +} + + #define __set_FAULTMASK(VALUE) (__arm_wsr("FAULTMASK", (VALUE))) + #define __set_MSP(VALUE) (__arm_wsr("MSP", (VALUE))) + + #if (!(defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) && \ + (!defined (__ARM_FEATURE_CMSE) || (__ARM_FEATURE_CMSE < 3))) + // without main extensions, the non-secure MSPLIM is RAZ/WI + #define __set_MSPLIM(VALUE) ((void)(VALUE)) + #else + #define __set_MSPLIM(VALUE) (__arm_wsr("MSPLIM", (VALUE))) + #endif + #define __set_PRIMASK(VALUE) (__arm_wsr("PRIMASK", (VALUE))) + #define __set_PSP(VALUE) (__arm_wsr("PSP", (VALUE))) + #if (!(defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) && \ + (!defined (__ARM_FEATURE_CMSE) || (__ARM_FEATURE_CMSE < 3))) + // without main extensions, the non-secure PSPLIM is RAZ/WI + #define __set_PSPLIM(VALUE) ((void)(VALUE)) + #else + #define __set_PSPLIM(VALUE) (__arm_wsr("PSPLIM", (VALUE))) + #endif + + #define __TZ_get_CONTROL_NS() (__arm_rsr("CONTROL_NS")) + +__STATIC_FORCEINLINE void __TZ_set_CONTROL_NS(uint32_t control) +{ + __arm_wsr("CONTROL_NS", control); + __iar_builtin_ISB(); +} + + #define __TZ_get_PSP_NS() (__arm_rsr("PSP_NS")) + #define __TZ_set_PSP_NS(VALUE) (__arm_wsr("PSP_NS", (VALUE))) + #define __TZ_get_MSP_NS() (__arm_rsr("MSP_NS")) + #define __TZ_set_MSP_NS(VALUE) (__arm_wsr("MSP_NS", (VALUE))) + #define __TZ_get_SP_NS() (__arm_rsr("SP_NS")) + #define __TZ_set_SP_NS(VALUE) (__arm_wsr("SP_NS", (VALUE))) + #define __TZ_get_PRIMASK_NS() (__arm_rsr("PRIMASK_NS")) + #define __TZ_set_PRIMASK_NS(VALUE) (__arm_wsr("PRIMASK_NS", (VALUE))) + #define __TZ_get_BASEPRI_NS() (__arm_rsr("BASEPRI_NS")) + #define __TZ_set_BASEPRI_NS(VALUE) (__arm_wsr("BASEPRI_NS", (VALUE))) + #define __TZ_get_FAULTMASK_NS() (__arm_rsr("FAULTMASK_NS")) + #define __TZ_set_FAULTMASK_NS(VALUE)(__arm_wsr("FAULTMASK_NS", (VALUE))) + + #if (!(defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) && \ + (!defined (__ARM_FEATURE_CMSE) || (__ARM_FEATURE_CMSE < 3))) + // without main extensions, the non-secure PSPLIM is RAZ/WI + #define __TZ_get_PSPLIM_NS() (0U) + #define __TZ_set_PSPLIM_NS(VALUE) ((void)(VALUE)) + #else + #define __TZ_get_PSPLIM_NS() (__arm_rsr("PSPLIM_NS")) + #define __TZ_set_PSPLIM_NS(VALUE) (__arm_wsr("PSPLIM_NS", (VALUE))) + #endif + + #define __TZ_get_MSPLIM_NS() (__arm_rsr("MSPLIM_NS")) + #define __TZ_set_MSPLIM_NS(VALUE) (__arm_wsr("MSPLIM_NS", (VALUE))) + + #define __NOP __iar_builtin_no_operation + + #define __CLZ __iar_builtin_CLZ + #define __CLREX __iar_builtin_CLREX + + #define __DMB __iar_builtin_DMB + #define __DSB __iar_builtin_DSB + #define __ISB __iar_builtin_ISB + + #define __LDREXB __iar_builtin_LDREXB + #define __LDREXH __iar_builtin_LDREXH + #define __LDREXW __iar_builtin_LDREX + + #define __RBIT __iar_builtin_RBIT + #define __REV __iar_builtin_REV + #define __REV16 __iar_builtin_REV16 + + __IAR_FT int16_t __REVSH(int16_t val) + { + return (int16_t) __iar_builtin_REVSH(val); + } + + #define __ROR __iar_builtin_ROR + #define __RRX __iar_builtin_RRX + + #define __SEV __iar_builtin_SEV + + #if !__IAR_M0_FAMILY + #define __SSAT __iar_builtin_SSAT + #endif + + #define __STREXB __iar_builtin_STREXB + #define __STREXH __iar_builtin_STREXH + #define __STREXW __iar_builtin_STREX + + #if !__IAR_M0_FAMILY + #define __USAT __iar_builtin_USAT + #endif + + #define __WFE __iar_builtin_WFE + #define __WFI __iar_builtin_WFI + + #if __ARM_MEDIA__ + #define __SADD8 __iar_builtin_SADD8 + #define __QADD8 __iar_builtin_QADD8 + #define __SHADD8 __iar_builtin_SHADD8 + #define __UADD8 __iar_builtin_UADD8 + #define __UQADD8 __iar_builtin_UQADD8 + #define __UHADD8 __iar_builtin_UHADD8 + #define __SSUB8 __iar_builtin_SSUB8 + #define __QSUB8 __iar_builtin_QSUB8 + #define __SHSUB8 __iar_builtin_SHSUB8 + #define __USUB8 __iar_builtin_USUB8 + #define __UQSUB8 __iar_builtin_UQSUB8 + #define __UHSUB8 __iar_builtin_UHSUB8 + #define __SADD16 __iar_builtin_SADD16 + #define __QADD16 __iar_builtin_QADD16 + #define __SHADD16 __iar_builtin_SHADD16 + #define __UADD16 __iar_builtin_UADD16 + #define __UQADD16 __iar_builtin_UQADD16 + #define __UHADD16 __iar_builtin_UHADD16 + #define __SSUB16 __iar_builtin_SSUB16 + #define __QSUB16 __iar_builtin_QSUB16 + #define __SHSUB16 __iar_builtin_SHSUB16 + #define __USUB16 __iar_builtin_USUB16 + #define __UQSUB16 __iar_builtin_UQSUB16 + #define __UHSUB16 __iar_builtin_UHSUB16 + #define __SASX __iar_builtin_SASX + #define __QASX __iar_builtin_QASX + #define __SHASX __iar_builtin_SHASX + #define __UASX __iar_builtin_UASX + #define __UQASX __iar_builtin_UQASX + #define __UHASX __iar_builtin_UHASX + #define __SSAX __iar_builtin_SSAX + #define __QSAX __iar_builtin_QSAX + #define __SHSAX __iar_builtin_SHSAX + #define __USAX __iar_builtin_USAX + #define __UQSAX __iar_builtin_UQSAX + #define __UHSAX __iar_builtin_UHSAX + #define __USAD8 __iar_builtin_USAD8 + #define __USADA8 __iar_builtin_USADA8 + #define __SSAT16 __iar_builtin_SSAT16 + #define __USAT16 __iar_builtin_USAT16 + #define __UXTB16 __iar_builtin_UXTB16 + #define __UXTAB16 __iar_builtin_UXTAB16 + #define __SXTB16 __iar_builtin_SXTB16 + #define __SXTAB16 __iar_builtin_SXTAB16 + #define __SMUAD __iar_builtin_SMUAD + #define __SMUADX __iar_builtin_SMUADX + #define __SMMLA __iar_builtin_SMMLA + #define __SMLAD __iar_builtin_SMLAD + #define __SMLADX __iar_builtin_SMLADX + #define __SMLALD __iar_builtin_SMLALD + #define __SMLALDX __iar_builtin_SMLALDX + #define __SMUSD __iar_builtin_SMUSD + #define __SMUSDX __iar_builtin_SMUSDX + #define __SMLSD __iar_builtin_SMLSD + #define __SMLSDX __iar_builtin_SMLSDX + #define __SMLSLD __iar_builtin_SMLSLD + #define __SMLSLDX __iar_builtin_SMLSLDX + #define __SEL __iar_builtin_SEL + #define __QADD __iar_builtin_QADD + #define __QSUB __iar_builtin_QSUB + #define __PKHBT __iar_builtin_PKHBT + #define __PKHTB __iar_builtin_PKHTB + #endif + +#else /* __ICCARM_INTRINSICS_VERSION__ == 2 */ + + #if __IAR_M0_FAMILY + /* Avoid clash between intrinsics.h and arm_math.h when compiling for Cortex-M0. */ + #define __CLZ __cmsis_iar_clz_not_active + #define __SSAT __cmsis_iar_ssat_not_active + #define __USAT __cmsis_iar_usat_not_active + #define __RBIT __cmsis_iar_rbit_not_active + #define __get_APSR __cmsis_iar_get_APSR_not_active + #endif + + + #if (!((defined (__FPU_PRESENT) && (__FPU_PRESENT == 1U)) && \ + (defined (__FPU_USED ) && (__FPU_USED == 1U)) )) + #define __get_FPSCR __cmsis_iar_get_FPSR_not_active + #define __set_FPSCR __cmsis_iar_set_FPSR_not_active + #endif + + #ifdef __INTRINSICS_INCLUDED + #error intrinsics.h is already included previously! + #endif + + #include + + #if __IAR_M0_FAMILY + /* Avoid clash between intrinsics.h and arm_math.h when compiling for Cortex-M0. */ + #undef __CLZ + #undef __SSAT + #undef __USAT + #undef __RBIT + #undef __get_APSR + + __STATIC_INLINE uint8_t __CLZ(uint32_t data) + { + if (data == 0U) { return 32U; } + + uint32_t count = 0U; + uint32_t mask = 0x80000000U; + + while ((data & mask) == 0U) + { + count += 1U; + mask = mask >> 1U; + } + return count; + } + + __STATIC_INLINE uint32_t __RBIT(uint32_t v) + { + uint8_t sc = 31U; + uint32_t r = v; + for (v >>= 1U; v; v >>= 1U) + { + r <<= 1U; + r |= v & 1U; + sc--; + } + return (r << sc); + } + + __STATIC_INLINE uint32_t __get_APSR(void) + { + uint32_t res; + __asm("MRS %0,APSR" : "=r" (res)); + return res; + } + + #endif + + #if (!((defined (__FPU_PRESENT) && (__FPU_PRESENT == 1U)) && \ + (defined (__FPU_USED ) && (__FPU_USED == 1U)) )) + #undef __get_FPSCR + #undef __set_FPSCR + #define __get_FPSCR() (0) + #define __set_FPSCR(VALUE) ((void)VALUE) + #endif + + #pragma diag_suppress=Pe940 + #pragma diag_suppress=Pe177 + + #define __enable_irq __enable_interrupt + #define __disable_irq __disable_interrupt + #define __NOP __no_operation + + #define __get_xPSR __get_PSR + + #if (!defined(__ARM_ARCH_6M__) || __ARM_ARCH_6M__==0) + + __IAR_FT uint32_t __LDREXW(uint32_t volatile *ptr) + { + return __LDREX((unsigned long *)ptr); + } + + __IAR_FT uint32_t __STREXW(uint32_t value, uint32_t volatile *ptr) + { + return __STREX(value, (unsigned long *)ptr); + } + #endif + + + /* __CORTEX_M is defined in core_cm0.h, core_cm3.h and core_cm4.h. */ + #if (__CORTEX_M >= 0x03) + + __IAR_FT uint32_t __RRX(uint32_t value) + { + uint32_t result; + __ASM volatile("RRX %0, %1" : "=r"(result) : "r" (value)); + return(result); + } + + __IAR_FT void __set_BASEPRI_MAX(uint32_t value) + { + __asm volatile("MSR BASEPRI_MAX,%0"::"r" (value)); + } + + + #define __enable_fault_irq __enable_fiq + #define __disable_fault_irq __disable_fiq + + + #endif /* (__CORTEX_M >= 0x03) */ + + __IAR_FT uint32_t __ROR(uint32_t op1, uint32_t op2) + { + return (op1 >> op2) | (op1 << ((sizeof(op1)*8)-op2)); + } + + #if ((defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) || \ + (defined (__ARM_ARCH_8M_BASE__ ) && (__ARM_ARCH_8M_BASE__ == 1)) ) + + __IAR_FT uint32_t __get_MSPLIM(void) + { + uint32_t res; + #if (!(defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) && \ + (!defined (__ARM_FEATURE_CMSE ) || (__ARM_FEATURE_CMSE < 3))) + // without main extensions, the non-secure MSPLIM is RAZ/WI + res = 0U; + #else + __asm volatile("MRS %0,MSPLIM" : "=r" (res)); + #endif + return res; + } + + __IAR_FT void __set_MSPLIM(uint32_t value) + { + #if (!(defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) && \ + (!defined (__ARM_FEATURE_CMSE ) || (__ARM_FEATURE_CMSE < 3))) + // without main extensions, the non-secure MSPLIM is RAZ/WI + (void)value; + #else + __asm volatile("MSR MSPLIM,%0" :: "r" (value)); + #endif + } + + __IAR_FT uint32_t __get_PSPLIM(void) + { + uint32_t res; + #if (!(defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) && \ + (!defined (__ARM_FEATURE_CMSE ) || (__ARM_FEATURE_CMSE < 3))) + // without main extensions, the non-secure PSPLIM is RAZ/WI + res = 0U; + #else + __asm volatile("MRS %0,PSPLIM" : "=r" (res)); + #endif + return res; + } + + __IAR_FT void __set_PSPLIM(uint32_t value) + { + #if (!(defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) && \ + (!defined (__ARM_FEATURE_CMSE ) || (__ARM_FEATURE_CMSE < 3))) + // without main extensions, the non-secure PSPLIM is RAZ/WI + (void)value; + #else + __asm volatile("MSR PSPLIM,%0" :: "r" (value)); + #endif + } + + __IAR_FT uint32_t __TZ_get_CONTROL_NS(void) + { + uint32_t res; + __asm volatile("MRS %0,CONTROL_NS" : "=r" (res)); + return res; + } + + __IAR_FT void __TZ_set_CONTROL_NS(uint32_t value) + { + __asm volatile("MSR CONTROL_NS,%0" :: "r" (value)); + __iar_builtin_ISB(); + } + + __IAR_FT uint32_t __TZ_get_PSP_NS(void) + { + uint32_t res; + __asm volatile("MRS %0,PSP_NS" : "=r" (res)); + return res; + } + + __IAR_FT void __TZ_set_PSP_NS(uint32_t value) + { + __asm volatile("MSR PSP_NS,%0" :: "r" (value)); + } + + __IAR_FT uint32_t __TZ_get_MSP_NS(void) + { + uint32_t res; + __asm volatile("MRS %0,MSP_NS" : "=r" (res)); + return res; + } + + __IAR_FT void __TZ_set_MSP_NS(uint32_t value) + { + __asm volatile("MSR MSP_NS,%0" :: "r" (value)); + } + + __IAR_FT uint32_t __TZ_get_SP_NS(void) + { + uint32_t res; + __asm volatile("MRS %0,SP_NS" : "=r" (res)); + return res; + } + __IAR_FT void __TZ_set_SP_NS(uint32_t value) + { + __asm volatile("MSR SP_NS,%0" :: "r" (value)); + } + + __IAR_FT uint32_t __TZ_get_PRIMASK_NS(void) + { + uint32_t res; + __asm volatile("MRS %0,PRIMASK_NS" : "=r" (res)); + return res; + } + + __IAR_FT void __TZ_set_PRIMASK_NS(uint32_t value) + { + __asm volatile("MSR PRIMASK_NS,%0" :: "r" (value)); + } + + __IAR_FT uint32_t __TZ_get_BASEPRI_NS(void) + { + uint32_t res; + __asm volatile("MRS %0,BASEPRI_NS" : "=r" (res)); + return res; + } + + __IAR_FT void __TZ_set_BASEPRI_NS(uint32_t value) + { + __asm volatile("MSR BASEPRI_NS,%0" :: "r" (value)); + } + + __IAR_FT uint32_t __TZ_get_FAULTMASK_NS(void) + { + uint32_t res; + __asm volatile("MRS %0,FAULTMASK_NS" : "=r" (res)); + return res; + } + + __IAR_FT void __TZ_set_FAULTMASK_NS(uint32_t value) + { + __asm volatile("MSR FAULTMASK_NS,%0" :: "r" (value)); + } + + __IAR_FT uint32_t __TZ_get_PSPLIM_NS(void) + { + uint32_t res; + #if (!(defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) && \ + (!defined (__ARM_FEATURE_CMSE ) || (__ARM_FEATURE_CMSE < 3))) + // without main extensions, the non-secure PSPLIM is RAZ/WI + res = 0U; + #else + __asm volatile("MRS %0,PSPLIM_NS" : "=r" (res)); + #endif + return res; + } + + __IAR_FT void __TZ_set_PSPLIM_NS(uint32_t value) + { + #if (!(defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) && \ + (!defined (__ARM_FEATURE_CMSE ) || (__ARM_FEATURE_CMSE < 3))) + // without main extensions, the non-secure PSPLIM is RAZ/WI + (void)value; + #else + __asm volatile("MSR PSPLIM_NS,%0" :: "r" (value)); + #endif + } + + __IAR_FT uint32_t __TZ_get_MSPLIM_NS(void) + { + uint32_t res; + __asm volatile("MRS %0,MSPLIM_NS" : "=r" (res)); + return res; + } + + __IAR_FT void __TZ_set_MSPLIM_NS(uint32_t value) + { + __asm volatile("MSR MSPLIM_NS,%0" :: "r" (value)); + } + + #endif /* __ARM_ARCH_8M_MAIN__ or __ARM_ARCH_8M_BASE__ */ + +#endif /* __ICCARM_INTRINSICS_VERSION__ == 2 */ + +#define __BKPT(value) __asm volatile ("BKPT %0" : : "i"(value)) + +#if __IAR_M0_FAMILY + __STATIC_INLINE int32_t __SSAT(int32_t val, uint32_t sat) + { + if ((sat >= 1U) && (sat <= 32U)) + { + const int32_t max = (int32_t)((1U << (sat - 1U)) - 1U); + const int32_t min = -1 - max ; + if (val > max) + { + return max; + } + else if (val < min) + { + return min; + } + } + return val; + } + + __STATIC_INLINE uint32_t __USAT(int32_t val, uint32_t sat) + { + if (sat <= 31U) + { + const uint32_t max = ((1U << sat) - 1U); + if (val > (int32_t)max) + { + return max; + } + else if (val < 0) + { + return 0U; + } + } + return (uint32_t)val; + } +#endif + +#if (__CORTEX_M >= 0x03) /* __CORTEX_M is defined in core_cm0.h, core_cm3.h and core_cm4.h. */ + + __IAR_FT uint8_t __LDRBT(volatile uint8_t *addr) + { + uint32_t res; + __ASM volatile ("LDRBT %0, [%1]" : "=r" (res) : "r" (addr) : "memory"); + return ((uint8_t)res); + } + + __IAR_FT uint16_t __LDRHT(volatile uint16_t *addr) + { + uint32_t res; + __ASM volatile ("LDRHT %0, [%1]" : "=r" (res) : "r" (addr) : "memory"); + return ((uint16_t)res); + } + + __IAR_FT uint32_t __LDRT(volatile uint32_t *addr) + { + uint32_t res; + __ASM volatile ("LDRT %0, [%1]" : "=r" (res) : "r" (addr) : "memory"); + return res; + } + + __IAR_FT void __STRBT(uint8_t value, volatile uint8_t *addr) + { + __ASM volatile ("STRBT %1, [%0]" : : "r" (addr), "r" ((uint32_t)value) : "memory"); + } + + __IAR_FT void __STRHT(uint16_t value, volatile uint16_t *addr) + { + __ASM volatile ("STRHT %1, [%0]" : : "r" (addr), "r" ((uint32_t)value) : "memory"); + } + + __IAR_FT void __STRT(uint32_t value, volatile uint32_t *addr) + { + __ASM volatile ("STRT %1, [%0]" : : "r" (addr), "r" (value) : "memory"); + } + +#endif /* (__CORTEX_M >= 0x03) */ + +#if ((defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) || \ + (defined (__ARM_ARCH_8M_BASE__ ) && (__ARM_ARCH_8M_BASE__ == 1)) ) + + + __IAR_FT uint8_t __LDAB(volatile uint8_t *ptr) + { + uint32_t res; + __ASM volatile ("LDAB %0, [%1]" : "=r" (res) : "r" (ptr) : "memory"); + return ((uint8_t)res); + } + + __IAR_FT uint16_t __LDAH(volatile uint16_t *ptr) + { + uint32_t res; + __ASM volatile ("LDAH %0, [%1]" : "=r" (res) : "r" (ptr) : "memory"); + return ((uint16_t)res); + } + + __IAR_FT uint32_t __LDA(volatile uint32_t *ptr) + { + uint32_t res; + __ASM volatile ("LDA %0, [%1]" : "=r" (res) : "r" (ptr) : "memory"); + return res; + } + + __IAR_FT void __STLB(uint8_t value, volatile uint8_t *ptr) + { + __ASM volatile ("STLB %1, [%0]" :: "r" (ptr), "r" (value) : "memory"); + } + + __IAR_FT void __STLH(uint16_t value, volatile uint16_t *ptr) + { + __ASM volatile ("STLH %1, [%0]" :: "r" (ptr), "r" (value) : "memory"); + } + + __IAR_FT void __STL(uint32_t value, volatile uint32_t *ptr) + { + __ASM volatile ("STL %1, [%0]" :: "r" (ptr), "r" (value) : "memory"); + } + + __IAR_FT uint8_t __LDAEXB(volatile uint8_t *ptr) + { + uint32_t res; + __ASM volatile ("LDAEXB %0, [%1]" : "=r" (res) : "r" (ptr) : "memory"); + return ((uint8_t)res); + } + + __IAR_FT uint16_t __LDAEXH(volatile uint16_t *ptr) + { + uint32_t res; + __ASM volatile ("LDAEXH %0, [%1]" : "=r" (res) : "r" (ptr) : "memory"); + return ((uint16_t)res); + } + + __IAR_FT uint32_t __LDAEX(volatile uint32_t *ptr) + { + uint32_t res; + __ASM volatile ("LDAEX %0, [%1]" : "=r" (res) : "r" (ptr) : "memory"); + return res; + } + + __IAR_FT uint32_t __STLEXB(uint8_t value, volatile uint8_t *ptr) + { + uint32_t res; + __ASM volatile ("STLEXB %0, %2, [%1]" : "=r" (res) : "r" (ptr), "r" (value) : "memory"); + return res; + } + + __IAR_FT uint32_t __STLEXH(uint16_t value, volatile uint16_t *ptr) + { + uint32_t res; + __ASM volatile ("STLEXH %0, %2, [%1]" : "=r" (res) : "r" (ptr), "r" (value) : "memory"); + return res; + } + + __IAR_FT uint32_t __STLEX(uint32_t value, volatile uint32_t *ptr) + { + uint32_t res; + __ASM volatile ("STLEX %0, %2, [%1]" : "=r" (res) : "r" (ptr), "r" (value) : "memory"); + return res; + } + +#endif /* __ARM_ARCH_8M_MAIN__ or __ARM_ARCH_8M_BASE__ */ + +#undef __IAR_FT +#undef __IAR_M0_FAMILY +#undef __ICCARM_V8 + +#pragma diag_default=Pe940 +#pragma diag_default=Pe177 + +#define __SXTB16_RORn(ARG1, ARG2) __SXTB16(__ROR(ARG1, ARG2)) + +#define __SXTAB16_RORn(ARG1, ARG2, ARG3) __SXTAB16(ARG1, __ROR(ARG2, ARG3)) + +#endif /* __CMSIS_ICCARM_H__ */ diff --git a/lib/cmsis_core/cmsis_tiarmclang.h b/lib/cmsis_core/cmsis_tiarmclang.h new file mode 100644 index 00000000000..4d799c277c8 --- /dev/null +++ b/lib/cmsis_core/cmsis_tiarmclang.h @@ -0,0 +1,1510 @@ +/**************************************************************************//** + * @file cmsis_tiarmclang.h + * @brief CMSIS compiler tiarmclang header file + * @version V1.0.0 + * @date 04. April 2023 + ******************************************************************************/ +/* + * Copyright (c) 2023 Arm Limited. All rights reserved. + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an AS IS BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/*lint -esym(9058, IRQn)*/ /* disable MISRA 2012 Rule 2.4 for IRQn */ + +#ifndef __CMSIS_TIARMCLANG_H +#define __CMSIS_TIARMCLANG_H + +#pragma clang system_header /* treat file as system include file */ + +/* CMSIS compiler specific defines */ +#ifndef __ASM + #define __ASM __asm +#endif +#ifndef __INLINE + #define __INLINE __inline +#endif +#ifndef __STATIC_INLINE + #define __STATIC_INLINE static __inline +#endif +#ifndef __STATIC_FORCEINLINE + #define __STATIC_FORCEINLINE __attribute__((always_inline)) static __inline +#endif +#ifndef __NO_RETURN + #define __NO_RETURN __attribute__((__noreturn__)) +#endif +#ifndef __USED + #define __USED __attribute__((used)) +#endif +#ifndef __WEAK + #define __WEAK __attribute__((weak)) +#endif +#ifndef __PACKED + #define __PACKED __attribute__((packed, aligned(1))) +#endif +#ifndef __PACKED_STRUCT + #define __PACKED_STRUCT struct __attribute__((packed, aligned(1))) +#endif +#ifndef __PACKED_UNION + #define __PACKED_UNION union __attribute__((packed, aligned(1))) +#endif +#ifndef __UNALIGNED_UINT32 /* deprecated */ + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wpacked" +/*lint -esym(9058, T_UINT32)*/ /* disable MISRA 2012 Rule 2.4 for T_UINT32 */ + struct __attribute__((packed)) T_UINT32 { uint32_t v; }; + #pragma clang diagnostic pop + #define __UNALIGNED_UINT32(x) (((struct T_UINT32 *)(x))->v) +#endif +#ifndef __UNALIGNED_UINT16_WRITE + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wpacked" +/*lint -esym(9058, T_UINT16_WRITE)*/ /* disable MISRA 2012 Rule 2.4 for T_UINT16_WRITE */ + __PACKED_STRUCT T_UINT16_WRITE { uint16_t v; }; + #pragma clang diagnostic pop + #define __UNALIGNED_UINT16_WRITE(addr, val) (void)((((struct T_UINT16_WRITE *)(void *)(addr))->v) = (val)) +#endif +#ifndef __UNALIGNED_UINT16_READ + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wpacked" +/*lint -esym(9058, T_UINT16_READ)*/ /* disable MISRA 2012 Rule 2.4 for T_UINT16_READ */ + __PACKED_STRUCT T_UINT16_READ { uint16_t v; }; + #pragma clang diagnostic pop + #define __UNALIGNED_UINT16_READ(addr) (((const struct T_UINT16_READ *)(const void *)(addr))->v) +#endif +#ifndef __UNALIGNED_UINT32_WRITE + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wpacked" +/*lint -esym(9058, T_UINT32_WRITE)*/ /* disable MISRA 2012 Rule 2.4 for T_UINT32_WRITE */ + __PACKED_STRUCT T_UINT32_WRITE { uint32_t v; }; + #pragma clang diagnostic pop + #define __UNALIGNED_UINT32_WRITE(addr, val) (void)((((struct T_UINT32_WRITE *)(void *)(addr))->v) = (val)) +#endif +#ifndef __UNALIGNED_UINT32_READ + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wpacked" +/*lint -esym(9058, T_UINT32_READ)*/ /* disable MISRA 2012 Rule 2.4 for T_UINT32_READ */ + __PACKED_STRUCT T_UINT32_READ { uint32_t v; }; + #pragma clang diagnostic pop + #define __UNALIGNED_UINT32_READ(addr) (((const struct T_UINT32_READ *)(const void *)(addr))->v) +#endif +#ifndef __ALIGNED + #define __ALIGNED(x) __attribute__((aligned(x))) +#endif +#ifndef __RESTRICT + #define __RESTRICT __restrict +#endif +#ifndef __COMPILER_BARRIER + #define __COMPILER_BARRIER() __ASM volatile("":::"memory") +#endif +#ifndef __NO_INIT + #define __NO_INIT __attribute__ ((section (".bss.noinit"))) +#endif +#ifndef __ALIAS + #define __ALIAS(x) __attribute__ ((alias(x))) +#endif + + +/* ######################### Startup and Lowlevel Init ######################## */ + +#ifndef __PROGRAM_START +#define __PROGRAM_START _c_int00 +#endif + +#ifndef __INITIAL_SP +#define __INITIAL_SP __STACK_END +#endif + +#ifndef __STACK_LIMIT +#define __STACK_LIMIT __STACK_SIZE +#endif + +#ifndef __VECTOR_TABLE +#define __VECTOR_TABLE __Vectors +#endif + +#ifndef __VECTOR_TABLE_ATTRIBUTE +#define __VECTOR_TABLE_ATTRIBUTE __attribute__((used, section(".intvecs"))) +#endif + +#if defined (__ARM_FEATURE_CMSE) && (__ARM_FEATURE_CMSE == 3U) +#ifndef __STACK_SEAL +#define __STACK_SEAL Image$$STACKSEAL$$ZI$$Base +#endif + +#ifndef __TZ_STACK_SEAL_SIZE +#define __TZ_STACK_SEAL_SIZE 8U +#endif + +#ifndef __TZ_STACK_SEAL_VALUE +#define __TZ_STACK_SEAL_VALUE 0xFEF5EDA5FEF5EDA5ULL +#endif + + +__STATIC_FORCEINLINE void __TZ_set_STACKSEAL_S (uint32_t* stackTop) { + *((uint64_t *)stackTop) = __TZ_STACK_SEAL_VALUE; +} +#endif + + +/* ########################## Core Instruction Access ######################### */ +/** \defgroup CMSIS_Core_InstructionInterface CMSIS Core Instruction Interface + Access to dedicated instructions + @{ +*/ + +/* Define macros for porting to both thumb1 and thumb2. + * For thumb1, use low register (r0-r7), specified by constraint "l" + * Otherwise, use general registers, specified by constraint "r" */ +#if defined (__thumb__) && !defined (__thumb2__) +#define __CMSIS_GCC_OUT_REG(r) "=l" (r) +#define __CMSIS_GCC_RW_REG(r) "+l" (r) +#define __CMSIS_GCC_USE_REG(r) "l" (r) +#else +#define __CMSIS_GCC_OUT_REG(r) "=r" (r) +#define __CMSIS_GCC_RW_REG(r) "+r" (r) +#define __CMSIS_GCC_USE_REG(r) "r" (r) +#endif + +/** + \brief No Operation + \details No Operation does nothing. This instruction can be used for code alignment purposes. + */ +#define __NOP __builtin_arm_nop + +/** + \brief Wait For Interrupt + \details Wait For Interrupt is a hint instruction that suspends execution until one of a number of events occurs. + */ +#define __WFI __builtin_arm_wfi + + +/** + \brief Wait For Event + \details Wait For Event is a hint instruction that permits the processor to enter + a low-power state until one of a number of events occurs. + */ +#define __WFE __builtin_arm_wfe + + +/** + \brief Send Event + \details Send Event is a hint instruction. It causes an event to be signaled to the CPU. + */ +#define __SEV __builtin_arm_sev + + +/** + \brief Instruction Synchronization Barrier + \details Instruction Synchronization Barrier flushes the pipeline in the processor, + so that all instructions following the ISB are fetched from cache or memory, + after the instruction has been completed. + */ +#define __ISB() __builtin_arm_isb(0xF) + +/** + \brief Data Synchronization Barrier + \details Acts as a special kind of Data Memory Barrier. + It completes when all explicit memory accesses before this instruction complete. + */ +#define __DSB() __builtin_arm_dsb(0xF) + + +/** + \brief Data Memory Barrier + \details Ensures the apparent order of the explicit memory operations before + and after the instruction, without ensuring their completion. + */ +#define __DMB() __builtin_arm_dmb(0xF) + + +/** + \brief Reverse byte order (32 bit) + \details Reverses the byte order in unsigned integer value. For example, 0x12345678 becomes 0x78563412. + \param [in] value Value to reverse + \return Reversed value + */ +#define __REV(value) __builtin_bswap32(value) + + +/** + \brief Reverse byte order (16 bit) + \details Reverses the byte order within each halfword of a word. For example, 0x12345678 becomes 0x34127856. + \param [in] value Value to reverse + \return Reversed value + */ +#define __REV16(value) __ROR(__REV(value), 16) + + +/** + \brief Reverse byte order (16 bit) + \details Reverses the byte order in a 16-bit value and returns the signed 16-bit result. For example, 0x0080 becomes 0x8000. + \param [in] value Value to reverse + \return Reversed value + */ +#define __REVSH(value) (int16_t)__builtin_bswap16(value) + + +/** + \brief Rotate Right in unsigned value (32 bit) + \details Rotate Right (immediate) provides the value of the contents of a register rotated by a variable number of bits. + \param [in] op1 Value to rotate + \param [in] op2 Number of Bits to rotate + \return Rotated value + */ +__STATIC_FORCEINLINE uint32_t __ROR(uint32_t op1, uint32_t op2) +{ + op2 %= 32U; + if (op2 == 0U) + { + return op1; + } + return (op1 >> op2) | (op1 << (32U - op2)); +} + + +/** + \brief Breakpoint + \details Causes the processor to enter Debug state. + Debug tools can use this to investigate system state when the instruction at a particular address is reached. + \param [in] value is ignored by the processor. + If required, a debugger can use it to store additional information about the breakpoint. + */ +#define __BKPT(value) __ASM volatile ("bkpt "#value) + + +/** + \brief Reverse bit order of value + \details Reverses the bit order of the given value. + \param [in] value Value to reverse + \return Reversed value + */ +#define __RBIT __builtin_arm_rbit + +/** + \brief Count leading zeros + \details Counts the number of leading zeros of a data value. + \param [in] value Value to count the leading zeros + \return number of leading zeros in value + */ +__STATIC_FORCEINLINE uint8_t __CLZ(uint32_t value) +{ + /* Even though __builtin_clz produces a CLZ instruction on ARM, formally + __builtin_clz(0) is undefined behaviour, so handle this case specially. + This guarantees ARM-compatible results if happening to compile on a non-ARM + target, and ensures the compiler doesn't decide to activate any + optimisations using the logic "value was passed to __builtin_clz, so it + is non-zero". + ARM Compiler 6.10 and possibly earlier will optimise this test away, leaving a + single CLZ instruction. + */ + if (value == 0U) + { + return 32U; + } + return __builtin_clz(value); +} + + +#if ((defined (__ARM_ARCH_7M__ ) && (__ARM_ARCH_7M__ == 1)) || \ + (defined (__ARM_ARCH_7EM__ ) && (__ARM_ARCH_7EM__ == 1)) || \ + (defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) || \ + (defined (__ARM_ARCH_8M_BASE__ ) && (__ARM_ARCH_8M_BASE__ == 1)) || \ + (defined (__ARM_ARCH_8_1M_MAIN__) && (__ARM_ARCH_8_1M_MAIN__ == 1)) ) + +/** + \brief LDR Exclusive (8 bit) + \details Executes a exclusive LDR instruction for 8 bit value. + \param [in] ptr Pointer to data + \return value of type uint8_t at (*ptr) + */ +#define __LDREXB (uint8_t)__builtin_arm_ldrex + + +/** + \brief LDR Exclusive (16 bit) + \details Executes a exclusive LDR instruction for 16 bit values. + \param [in] ptr Pointer to data + \return value of type uint16_t at (*ptr) + */ +#define __LDREXH (uint16_t)__builtin_arm_ldrex + + +/** + \brief LDR Exclusive (32 bit) + \details Executes a exclusive LDR instruction for 32 bit values. + \param [in] ptr Pointer to data + \return value of type uint32_t at (*ptr) + */ +#define __LDREXW (uint32_t)__builtin_arm_ldrex + + +/** + \brief STR Exclusive (8 bit) + \details Executes a exclusive STR instruction for 8 bit values. + \param [in] value Value to store + \param [in] ptr Pointer to location + \return 0 Function succeeded + \return 1 Function failed + */ +#define __STREXB (uint32_t)__builtin_arm_strex + + +/** + \brief STR Exclusive (16 bit) + \details Executes a exclusive STR instruction for 16 bit values. + \param [in] value Value to store + \param [in] ptr Pointer to location + \return 0 Function succeeded + \return 1 Function failed + */ +#define __STREXH (uint32_t)__builtin_arm_strex + + +/** + \brief STR Exclusive (32 bit) + \details Executes a exclusive STR instruction for 32 bit values. + \param [in] value Value to store + \param [in] ptr Pointer to location + \return 0 Function succeeded + \return 1 Function failed + */ +#define __STREXW (uint32_t)__builtin_arm_strex + + +/** + \brief Remove the exclusive lock + \details Removes the exclusive lock which is created by LDREX. + */ +#define __CLREX __builtin_arm_clrex + +#endif /* ((defined (__ARM_ARCH_7M__ ) && (__ARM_ARCH_7M__ == 1)) || \ + (defined (__ARM_ARCH_7EM__ ) && (__ARM_ARCH_7EM__ == 1)) || \ + (defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) || \ + (defined (__ARM_ARCH_8M_BASE__ ) && (__ARM_ARCH_8M_BASE__ == 1)) || \ + (defined (__ARM_ARCH_8_1M_MAIN__) && (__ARM_ARCH_8_1M_MAIN__ == 1)) ) */ + + +#if ((defined (__ARM_ARCH_7M__ ) && (__ARM_ARCH_7M__ == 1)) || \ + (defined (__ARM_ARCH_7EM__ ) && (__ARM_ARCH_7EM__ == 1)) || \ + (defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) || \ + (defined (__ARM_ARCH_8_1M_MAIN__) && (__ARM_ARCH_8_1M_MAIN__ == 1)) ) + +/** + \brief Signed Saturate + \details Saturates a signed value. + \param [in] value Value to be saturated + \param [in] sat Bit position to saturate to (1..32) + \return Saturated value + */ +#define __SSAT __builtin_arm_ssat + + +/** + \brief Unsigned Saturate + \details Saturates an unsigned value. + \param [in] value Value to be saturated + \param [in] sat Bit position to saturate to (0..31) + \return Saturated value + */ +#define __USAT __builtin_arm_usat + + +/** + \brief Rotate Right with Extend (32 bit) + \details Moves each bit of a bitstring right by one bit. + The carry input is shifted in at the left end of the bitstring. + \param [in] value Value to rotate + \return Rotated value + */ +__STATIC_FORCEINLINE uint32_t __RRX(uint32_t value) +{ + uint32_t result; + + __ASM volatile ("rrx %0, %1" : __CMSIS_GCC_OUT_REG (result) : __CMSIS_GCC_USE_REG (value) ); + return(result); +} + + +/** + \brief LDRT Unprivileged (8 bit) + \details Executes a Unprivileged LDRT instruction for 8 bit value. + \param [in] ptr Pointer to data + \return value of type uint8_t at (*ptr) + */ +__STATIC_FORCEINLINE uint8_t __LDRBT(volatile uint8_t *ptr) +{ + uint32_t result; + + __ASM volatile ("ldrbt %0, %1" : "=r" (result) : "Q" (*ptr) ); + return ((uint8_t) result); /* Add explicit type cast here */ +} + + +/** + \brief LDRT Unprivileged (16 bit) + \details Executes a Unprivileged LDRT instruction for 16 bit values. + \param [in] ptr Pointer to data + \return value of type uint16_t at (*ptr) + */ +__STATIC_FORCEINLINE uint16_t __LDRHT(volatile uint16_t *ptr) +{ + uint32_t result; + + __ASM volatile ("ldrht %0, %1" : "=r" (result) : "Q" (*ptr) ); + return ((uint16_t) result); /* Add explicit type cast here */ +} + + +/** + \brief LDRT Unprivileged (32 bit) + \details Executes a Unprivileged LDRT instruction for 32 bit values. + \param [in] ptr Pointer to data + \return value of type uint32_t at (*ptr) + */ +__STATIC_FORCEINLINE uint32_t __LDRT(volatile uint32_t *ptr) +{ + uint32_t result; + + __ASM volatile ("ldrt %0, %1" : "=r" (result) : "Q" (*ptr) ); + return(result); +} + + +/** + \brief STRT Unprivileged (8 bit) + \details Executes a Unprivileged STRT instruction for 8 bit values. + \param [in] value Value to store + \param [in] ptr Pointer to location + */ +__STATIC_FORCEINLINE void __STRBT(uint8_t value, volatile uint8_t *ptr) +{ + __ASM volatile ("strbt %1, %0" : "=Q" (*ptr) : "r" ((uint32_t)value) ); +} + + +/** + \brief STRT Unprivileged (16 bit) + \details Executes a Unprivileged STRT instruction for 16 bit values. + \param [in] value Value to store + \param [in] ptr Pointer to location + */ +__STATIC_FORCEINLINE void __STRHT(uint16_t value, volatile uint16_t *ptr) +{ + __ASM volatile ("strht %1, %0" : "=Q" (*ptr) : "r" ((uint32_t)value) ); +} + + +/** + \brief STRT Unprivileged (32 bit) + \details Executes a Unprivileged STRT instruction for 32 bit values. + \param [in] value Value to store + \param [in] ptr Pointer to location + */ +__STATIC_FORCEINLINE void __STRT(uint32_t value, volatile uint32_t *ptr) +{ + __ASM volatile ("strt %1, %0" : "=Q" (*ptr) : "r" (value) ); +} + +#else /* ((defined (__ARM_ARCH_7M__ ) && (__ARM_ARCH_7M__ == 1)) || \ + (defined (__ARM_ARCH_7EM__ ) && (__ARM_ARCH_7EM__ == 1)) || \ + (defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) || \ + (defined (__ARM_ARCH_8_1M_MAIN__) && (__ARM_ARCH_8_1M_MAIN__ == 1)) ) */ + +/** + \brief Signed Saturate + \details Saturates a signed value. + \param [in] value Value to be saturated + \param [in] sat Bit position to saturate to (1..32) + \return Saturated value + */ +__STATIC_FORCEINLINE int32_t __SSAT(int32_t val, uint32_t sat) +{ + if ((sat >= 1U) && (sat <= 32U)) + { + const int32_t max = (int32_t)((1U << (sat - 1U)) - 1U); + const int32_t min = -1 - max ; + if (val > max) + { + return max; + } + else if (val < min) + { + return min; + } + } + return val; +} + +/** + \brief Unsigned Saturate + \details Saturates an unsigned value. + \param [in] value Value to be saturated + \param [in] sat Bit position to saturate to (0..31) + \return Saturated value + */ +__STATIC_FORCEINLINE uint32_t __USAT(int32_t val, uint32_t sat) +{ + if (sat <= 31U) + { + const uint32_t max = ((1U << sat) - 1U); + if (val > (int32_t)max) + { + return max; + } + else if (val < 0) + { + return 0U; + } + } + return (uint32_t)val; +} + +#endif /* ((defined (__ARM_ARCH_7M__ ) && (__ARM_ARCH_7M__ == 1)) || \ + (defined (__ARM_ARCH_7EM__ ) && (__ARM_ARCH_7EM__ == 1)) || \ + (defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) || \ + (defined (__ARM_ARCH_8_1M_MAIN__) && (__ARM_ARCH_8_1M_MAIN__ == 1)) ) */ + + +#if ((defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) || \ + (defined (__ARM_ARCH_8M_BASE__ ) && (__ARM_ARCH_8M_BASE__ == 1)) || \ + (defined (__ARM_ARCH_8_1M_MAIN__) && (__ARM_ARCH_8_1M_MAIN__ == 1)) ) + +/** + \brief Load-Acquire (8 bit) + \details Executes a LDAB instruction for 8 bit value. + \param [in] ptr Pointer to data + \return value of type uint8_t at (*ptr) + */ +__STATIC_FORCEINLINE uint8_t __LDAB(volatile uint8_t *ptr) +{ + uint32_t result; + + __ASM volatile ("ldab %0, %1" : "=r" (result) : "Q" (*ptr) : "memory" ); + return ((uint8_t) result); +} + + +/** + \brief Load-Acquire (16 bit) + \details Executes a LDAH instruction for 16 bit values. + \param [in] ptr Pointer to data + \return value of type uint16_t at (*ptr) + */ +__STATIC_FORCEINLINE uint16_t __LDAH(volatile uint16_t *ptr) +{ + uint32_t result; + + __ASM volatile ("ldah %0, %1" : "=r" (result) : "Q" (*ptr) : "memory" ); + return ((uint16_t) result); +} + + +/** + \brief Load-Acquire (32 bit) + \details Executes a LDA instruction for 32 bit values. + \param [in] ptr Pointer to data + \return value of type uint32_t at (*ptr) + */ +__STATIC_FORCEINLINE uint32_t __LDA(volatile uint32_t *ptr) +{ + uint32_t result; + + __ASM volatile ("lda %0, %1" : "=r" (result) : "Q" (*ptr) : "memory" ); + return(result); +} + + +/** + \brief Store-Release (8 bit) + \details Executes a STLB instruction for 8 bit values. + \param [in] value Value to store + \param [in] ptr Pointer to location + */ +__STATIC_FORCEINLINE void __STLB(uint8_t value, volatile uint8_t *ptr) +{ + __ASM volatile ("stlb %1, %0" : "=Q" (*ptr) : "r" ((uint32_t)value) : "memory" ); +} + + +/** + \brief Store-Release (16 bit) + \details Executes a STLH instruction for 16 bit values. + \param [in] value Value to store + \param [in] ptr Pointer to location + */ +__STATIC_FORCEINLINE void __STLH(uint16_t value, volatile uint16_t *ptr) +{ + __ASM volatile ("stlh %1, %0" : "=Q" (*ptr) : "r" ((uint32_t)value) : "memory" ); +} + + +/** + \brief Store-Release (32 bit) + \details Executes a STL instruction for 32 bit values. + \param [in] value Value to store + \param [in] ptr Pointer to location + */ +__STATIC_FORCEINLINE void __STL(uint32_t value, volatile uint32_t *ptr) +{ + __ASM volatile ("stl %1, %0" : "=Q" (*ptr) : "r" ((uint32_t)value) : "memory" ); +} + + +/** + \brief Load-Acquire Exclusive (8 bit) + \details Executes a LDAB exclusive instruction for 8 bit value. + \param [in] ptr Pointer to data + \return value of type uint8_t at (*ptr) + */ +#define __LDAEXB (uint8_t)__builtin_arm_ldaex + + +/** + \brief Load-Acquire Exclusive (16 bit) + \details Executes a LDAH exclusive instruction for 16 bit values. + \param [in] ptr Pointer to data + \return value of type uint16_t at (*ptr) + */ +#define __LDAEXH (uint16_t)__builtin_arm_ldaex + + +/** + \brief Load-Acquire Exclusive (32 bit) + \details Executes a LDA exclusive instruction for 32 bit values. + \param [in] ptr Pointer to data + \return value of type uint32_t at (*ptr) + */ +#define __LDAEX (uint32_t)__builtin_arm_ldaex + + +/** + \brief Store-Release Exclusive (8 bit) + \details Executes a STLB exclusive instruction for 8 bit values. + \param [in] value Value to store + \param [in] ptr Pointer to location + \return 0 Function succeeded + \return 1 Function failed + */ +#define __STLEXB (uint32_t)__builtin_arm_stlex + + +/** + \brief Store-Release Exclusive (16 bit) + \details Executes a STLH exclusive instruction for 16 bit values. + \param [in] value Value to store + \param [in] ptr Pointer to location + \return 0 Function succeeded + \return 1 Function failed + */ +#define __STLEXH (uint32_t)__builtin_arm_stlex + + +/** + \brief Store-Release Exclusive (32 bit) + \details Executes a STL exclusive instruction for 32 bit values. + \param [in] value Value to store + \param [in] ptr Pointer to location + \return 0 Function succeeded + \return 1 Function failed + */ +#define __STLEX (uint32_t)__builtin_arm_stlex + +#endif /* ((defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) || \ + (defined (__ARM_ARCH_8M_BASE__ ) && (__ARM_ARCH_8M_BASE__ == 1)) || \ + (defined (__ARM_ARCH_8_1M_MAIN__) && (__ARM_ARCH_8_1M_MAIN__ == 1)) ) */ + +/** @}*/ /* end of group CMSIS_Core_InstructionInterface */ + + +/* ########################### Core Function Access ########################### */ +/** \ingroup CMSIS_Core_FunctionInterface + \defgroup CMSIS_Core_RegAccFunctions CMSIS Core Register Access Functions + @{ + */ + +/** + \brief Enable IRQ Interrupts + \details Enables IRQ interrupts by clearing special-purpose register PRIMASK. + Can only be executed in Privileged modes. + */ +#ifndef __ARM_COMPAT_H +__STATIC_FORCEINLINE void __enable_irq(void) +{ + __ASM volatile ("cpsie i" : : : "memory"); +} +#endif + + +/** + \brief Disable IRQ Interrupts + \details Disables IRQ interrupts by setting special-purpose register PRIMASK. + Can only be executed in Privileged modes. + */ +#ifndef __ARM_COMPAT_H +__STATIC_FORCEINLINE void __disable_irq(void) +{ + __ASM volatile ("cpsid i" : : : "memory"); +} +#endif + + +/** + \brief Get Control Register + \details Returns the content of the Control Register. + \return Control Register value + */ +__STATIC_FORCEINLINE uint32_t __get_CONTROL(void) +{ + uint32_t result; + + __ASM volatile ("MRS %0, control" : "=r" (result) ); + return(result); +} + + +#if (defined (__ARM_FEATURE_CMSE ) && (__ARM_FEATURE_CMSE == 3)) +/** + \brief Get Control Register (non-secure) + \details Returns the content of the non-secure Control Register when in secure mode. + \return non-secure Control Register value + */ +__STATIC_FORCEINLINE uint32_t __TZ_get_CONTROL_NS(void) +{ + uint32_t result; + + __ASM volatile ("MRS %0, control_ns" : "=r" (result) ); + return(result); +} +#endif + + +/** + \brief Set Control Register + \details Writes the given value to the Control Register. + \param [in] control Control Register value to set + */ +__STATIC_FORCEINLINE void __set_CONTROL(uint32_t control) +{ + __ASM volatile ("MSR control, %0" : : "r" (control) : "memory"); + __ISB(); +} + + +#if (defined (__ARM_FEATURE_CMSE ) && (__ARM_FEATURE_CMSE == 3)) +/** + \brief Set Control Register (non-secure) + \details Writes the given value to the non-secure Control Register when in secure state. + \param [in] control Control Register value to set + */ +__STATIC_FORCEINLINE void __TZ_set_CONTROL_NS(uint32_t control) +{ + __ASM volatile ("MSR control_ns, %0" : : "r" (control) : "memory"); + __ISB(); +} +#endif + + +/** + \brief Get IPSR Register + \details Returns the content of the IPSR Register. + \return IPSR Register value + */ +__STATIC_FORCEINLINE uint32_t __get_IPSR(void) +{ + uint32_t result; + + __ASM volatile ("MRS %0, ipsr" : "=r" (result) ); + return(result); +} + + +/** + \brief Get APSR Register + \details Returns the content of the APSR Register. + \return APSR Register value + */ +__STATIC_FORCEINLINE uint32_t __get_APSR(void) +{ + uint32_t result; + + __ASM volatile ("MRS %0, apsr" : "=r" (result) ); + return(result); +} + + +/** + \brief Get xPSR Register + \details Returns the content of the xPSR Register. + \return xPSR Register value + */ +__STATIC_FORCEINLINE uint32_t __get_xPSR(void) +{ + uint32_t result; + + __ASM volatile ("MRS %0, xpsr" : "=r" (result) ); + return(result); +} + + +/** + \brief Get Process Stack Pointer + \details Returns the current value of the Process Stack Pointer (PSP). + \return PSP Register value + */ +__STATIC_FORCEINLINE uint32_t __get_PSP(void) +{ + uint32_t result; + + __ASM volatile ("MRS %0, psp" : "=r" (result) ); + return(result); +} + + +#if (defined (__ARM_FEATURE_CMSE ) && (__ARM_FEATURE_CMSE == 3)) +/** + \brief Get Process Stack Pointer (non-secure) + \details Returns the current value of the non-secure Process Stack Pointer (PSP) when in secure state. + \return PSP Register value + */ +__STATIC_FORCEINLINE uint32_t __TZ_get_PSP_NS(void) +{ + uint32_t result; + + __ASM volatile ("MRS %0, psp_ns" : "=r" (result) ); + return(result); +} +#endif + + +/** + \brief Set Process Stack Pointer + \details Assigns the given value to the Process Stack Pointer (PSP). + \param [in] topOfProcStack Process Stack Pointer value to set + */ +__STATIC_FORCEINLINE void __set_PSP(uint32_t topOfProcStack) +{ + __ASM volatile ("MSR psp, %0" : : "r" (topOfProcStack) : ); +} + + +#if (defined (__ARM_FEATURE_CMSE ) && (__ARM_FEATURE_CMSE == 3)) +/** + \brief Set Process Stack Pointer (non-secure) + \details Assigns the given value to the non-secure Process Stack Pointer (PSP) when in secure state. + \param [in] topOfProcStack Process Stack Pointer value to set + */ +__STATIC_FORCEINLINE void __TZ_set_PSP_NS(uint32_t topOfProcStack) +{ + __ASM volatile ("MSR psp_ns, %0" : : "r" (topOfProcStack) : ); +} +#endif + + +/** + \brief Get Main Stack Pointer + \details Returns the current value of the Main Stack Pointer (MSP). + \return MSP Register value + */ +__STATIC_FORCEINLINE uint32_t __get_MSP(void) +{ + uint32_t result; + + __ASM volatile ("MRS %0, msp" : "=r" (result) ); + return(result); +} + + +#if (defined (__ARM_FEATURE_CMSE ) && (__ARM_FEATURE_CMSE == 3)) +/** + \brief Get Main Stack Pointer (non-secure) + \details Returns the current value of the non-secure Main Stack Pointer (MSP) when in secure state. + \return MSP Register value + */ +__STATIC_FORCEINLINE uint32_t __TZ_get_MSP_NS(void) +{ + uint32_t result; + + __ASM volatile ("MRS %0, msp_ns" : "=r" (result) ); + return(result); +} +#endif + + +/** + \brief Set Main Stack Pointer + \details Assigns the given value to the Main Stack Pointer (MSP). + \param [in] topOfMainStack Main Stack Pointer value to set + */ +__STATIC_FORCEINLINE void __set_MSP(uint32_t topOfMainStack) +{ + __ASM volatile ("MSR msp, %0" : : "r" (topOfMainStack) : ); +} + + +#if (defined (__ARM_FEATURE_CMSE ) && (__ARM_FEATURE_CMSE == 3)) +/** + \brief Set Main Stack Pointer (non-secure) + \details Assigns the given value to the non-secure Main Stack Pointer (MSP) when in secure state. + \param [in] topOfMainStack Main Stack Pointer value to set + */ +__STATIC_FORCEINLINE void __TZ_set_MSP_NS(uint32_t topOfMainStack) +{ + __ASM volatile ("MSR msp_ns, %0" : : "r" (topOfMainStack) : ); +} +#endif + + +#if (defined (__ARM_FEATURE_CMSE ) && (__ARM_FEATURE_CMSE == 3)) +/** + \brief Get Stack Pointer (non-secure) + \details Returns the current value of the non-secure Stack Pointer (SP) when in secure state. + \return SP Register value + */ +__STATIC_FORCEINLINE uint32_t __TZ_get_SP_NS(void) +{ + uint32_t result; + + __ASM volatile ("MRS %0, sp_ns" : "=r" (result) ); + return(result); +} + + +/** + \brief Set Stack Pointer (non-secure) + \details Assigns the given value to the non-secure Stack Pointer (SP) when in secure state. + \param [in] topOfStack Stack Pointer value to set + */ +__STATIC_FORCEINLINE void __TZ_set_SP_NS(uint32_t topOfStack) +{ + __ASM volatile ("MSR sp_ns, %0" : : "r" (topOfStack) : ); +} +#endif + + +/** + \brief Get Priority Mask + \details Returns the current state of the priority mask bit from the Priority Mask Register. + \return Priority Mask value + */ +__STATIC_FORCEINLINE uint32_t __get_PRIMASK(void) +{ + uint32_t result; + + __ASM volatile ("MRS %0, primask" : "=r" (result) ); + return(result); +} + + +#if (defined (__ARM_FEATURE_CMSE ) && (__ARM_FEATURE_CMSE == 3)) +/** + \brief Get Priority Mask (non-secure) + \details Returns the current state of the non-secure priority mask bit from the Priority Mask Register when in secure state. + \return Priority Mask value + */ +__STATIC_FORCEINLINE uint32_t __TZ_get_PRIMASK_NS(void) +{ + uint32_t result; + + __ASM volatile ("MRS %0, primask_ns" : "=r" (result) ); + return(result); +} +#endif + + +/** + \brief Set Priority Mask + \details Assigns the given value to the Priority Mask Register. + \param [in] priMask Priority Mask + */ +__STATIC_FORCEINLINE void __set_PRIMASK(uint32_t priMask) +{ + __ASM volatile ("MSR primask, %0" : : "r" (priMask) : "memory"); +} + + +#if (defined (__ARM_FEATURE_CMSE ) && (__ARM_FEATURE_CMSE == 3)) +/** + \brief Set Priority Mask (non-secure) + \details Assigns the given value to the non-secure Priority Mask Register when in secure state. + \param [in] priMask Priority Mask + */ +__STATIC_FORCEINLINE void __TZ_set_PRIMASK_NS(uint32_t priMask) +{ + __ASM volatile ("MSR primask_ns, %0" : : "r" (priMask) : "memory"); +} +#endif + + +#if ((defined (__ARM_ARCH_7M__ ) && (__ARM_ARCH_7M__ == 1)) || \ + (defined (__ARM_ARCH_7EM__ ) && (__ARM_ARCH_7EM__ == 1)) || \ + (defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) || \ + (defined (__ARM_ARCH_8_1M_MAIN__) && (__ARM_ARCH_8_1M_MAIN__ == 1)) ) +/** + \brief Enable FIQ + \details Enables FIQ interrupts by clearing special-purpose register FAULTMASK. + Can only be executed in Privileged modes. + */ +__STATIC_FORCEINLINE void __enable_fault_irq(void) +{ + __ASM volatile ("cpsie f" : : : "memory"); +} + + +/** + \brief Disable FIQ + \details Disables FIQ interrupts by setting special-purpose register FAULTMASK. + Can only be executed in Privileged modes. + */ +__STATIC_FORCEINLINE void __disable_fault_irq(void) +{ + __ASM volatile ("cpsid f" : : : "memory"); +} + + +/** + \brief Get Base Priority + \details Returns the current value of the Base Priority register. + \return Base Priority register value + */ +__STATIC_FORCEINLINE uint32_t __get_BASEPRI(void) +{ + uint32_t result; + + __ASM volatile ("MRS %0, basepri" : "=r" (result) ); + return(result); +} + + +#if (defined (__ARM_FEATURE_CMSE ) && (__ARM_FEATURE_CMSE == 3)) +/** + \brief Get Base Priority (non-secure) + \details Returns the current value of the non-secure Base Priority register when in secure state. + \return Base Priority register value + */ +__STATIC_FORCEINLINE uint32_t __TZ_get_BASEPRI_NS(void) +{ + uint32_t result; + + __ASM volatile ("MRS %0, basepri_ns" : "=r" (result) ); + return(result); +} +#endif + + +/** + \brief Set Base Priority + \details Assigns the given value to the Base Priority register. + \param [in] basePri Base Priority value to set + */ +__STATIC_FORCEINLINE void __set_BASEPRI(uint32_t basePri) +{ + __ASM volatile ("MSR basepri, %0" : : "r" (basePri) : "memory"); +} + + +#if (defined (__ARM_FEATURE_CMSE ) && (__ARM_FEATURE_CMSE == 3)) +/** + \brief Set Base Priority (non-secure) + \details Assigns the given value to the non-secure Base Priority register when in secure state. + \param [in] basePri Base Priority value to set + */ +__STATIC_FORCEINLINE void __TZ_set_BASEPRI_NS(uint32_t basePri) +{ + __ASM volatile ("MSR basepri_ns, %0" : : "r" (basePri) : "memory"); +} +#endif + + +/** + \brief Set Base Priority with condition + \details Assigns the given value to the Base Priority register only if BASEPRI masking is disabled, + or the new value increases the BASEPRI priority level. + \param [in] basePri Base Priority value to set + */ +__STATIC_FORCEINLINE void __set_BASEPRI_MAX(uint32_t basePri) +{ + __ASM volatile ("MSR basepri_max, %0" : : "r" (basePri) : "memory"); +} + + +/** + \brief Get Fault Mask + \details Returns the current value of the Fault Mask register. + \return Fault Mask register value + */ +__STATIC_FORCEINLINE uint32_t __get_FAULTMASK(void) +{ + uint32_t result; + + __ASM volatile ("MRS %0, faultmask" : "=r" (result) ); + return(result); +} + + +#if (defined (__ARM_FEATURE_CMSE ) && (__ARM_FEATURE_CMSE == 3)) +/** + \brief Get Fault Mask (non-secure) + \details Returns the current value of the non-secure Fault Mask register when in secure state. + \return Fault Mask register value + */ +__STATIC_FORCEINLINE uint32_t __TZ_get_FAULTMASK_NS(void) +{ + uint32_t result; + + __ASM volatile ("MRS %0, faultmask_ns" : "=r" (result) ); + return(result); +} +#endif + + +/** + \brief Set Fault Mask + \details Assigns the given value to the Fault Mask register. + \param [in] faultMask Fault Mask value to set + */ +__STATIC_FORCEINLINE void __set_FAULTMASK(uint32_t faultMask) +{ + __ASM volatile ("MSR faultmask, %0" : : "r" (faultMask) : "memory"); +} + + +#if (defined (__ARM_FEATURE_CMSE ) && (__ARM_FEATURE_CMSE == 3)) +/** + \brief Set Fault Mask (non-secure) + \details Assigns the given value to the non-secure Fault Mask register when in secure state. + \param [in] faultMask Fault Mask value to set + */ +__STATIC_FORCEINLINE void __TZ_set_FAULTMASK_NS(uint32_t faultMask) +{ + __ASM volatile ("MSR faultmask_ns, %0" : : "r" (faultMask) : "memory"); +} +#endif + +#endif /* ((defined (__ARM_ARCH_7M__ ) && (__ARM_ARCH_7M__ == 1)) || \ + (defined (__ARM_ARCH_7EM__ ) && (__ARM_ARCH_7EM__ == 1)) || \ + (defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) || \ + (defined (__ARM_ARCH_8_1M_MAIN__) && (__ARM_ARCH_8_1M_MAIN__ == 1)) ) */ + + +#if ((defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) || \ + (defined (__ARM_ARCH_8M_BASE__ ) && (__ARM_ARCH_8M_BASE__ == 1)) || \ + (defined (__ARM_ARCH_8_1M_MAIN__) && (__ARM_ARCH_8_1M_MAIN__ == 1)) ) + +/** + \brief Get Process Stack Pointer Limit + Devices without ARMv8-M Main Extensions (i.e. Cortex-M23) lack the non-secure + Stack Pointer Limit register hence zero is returned always in non-secure + mode. + + \details Returns the current value of the Process Stack Pointer Limit (PSPLIM). + \return PSPLIM Register value + */ +__STATIC_FORCEINLINE uint32_t __get_PSPLIM(void) +{ +#if (!((defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) || \ + (defined (__ARM_ARCH_8_1M_MAIN__ ) && (__ARM_ARCH_8_1M_MAIN__ == 1)) ) && \ + (!defined (__ARM_FEATURE_CMSE) || (__ARM_FEATURE_CMSE < 3))) + // without main extensions, the non-secure PSPLIM is RAZ/WI + return 0U; +#else + uint32_t result; + __ASM volatile ("MRS %0, psplim" : "=r" (result) ); + return result; +#endif +} + +#if (defined (__ARM_FEATURE_CMSE) && (__ARM_FEATURE_CMSE == 3)) +/** + \brief Get Process Stack Pointer Limit (non-secure) + Devices without ARMv8-M Main Extensions (i.e. Cortex-M23) lack the non-secure + Stack Pointer Limit register hence zero is returned always in non-secure + mode. + + \details Returns the current value of the non-secure Process Stack Pointer Limit (PSPLIM) when in secure state. + \return PSPLIM Register value + */ +__STATIC_FORCEINLINE uint32_t __TZ_get_PSPLIM_NS(void) +{ +#if (!((defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) || \ + (defined (__ARM_ARCH_8_1M_MAIN__ ) && (__ARM_ARCH_8_1M_MAIN__ == 1)) ) ) + // without main extensions, the non-secure PSPLIM is RAZ/WI + return 0U; +#else + uint32_t result; + __ASM volatile ("MRS %0, psplim_ns" : "=r" (result) ); + return result; +#endif +} +#endif + + +/** + \brief Set Process Stack Pointer Limit + Devices without ARMv8-M Main Extensions (i.e. Cortex-M23) lack the non-secure + Stack Pointer Limit register hence the write is silently ignored in non-secure + mode. + + \details Assigns the given value to the Process Stack Pointer Limit (PSPLIM). + \param [in] ProcStackPtrLimit Process Stack Pointer Limit value to set + */ +__STATIC_FORCEINLINE void __set_PSPLIM(uint32_t ProcStackPtrLimit) +{ +#if (!((defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) || \ + (defined (__ARM_ARCH_8_1M_MAIN__ ) && (__ARM_ARCH_8_1M_MAIN__ == 1)) ) && \ + (!defined (__ARM_FEATURE_CMSE) || (__ARM_FEATURE_CMSE < 3))) + // without main extensions, the non-secure PSPLIM is RAZ/WI + (void)ProcStackPtrLimit; +#else + __ASM volatile ("MSR psplim, %0" : : "r" (ProcStackPtrLimit)); +#endif +} + + +#if (defined (__ARM_FEATURE_CMSE ) && (__ARM_FEATURE_CMSE == 3)) +/** + \brief Set Process Stack Pointer (non-secure) + Devices without ARMv8-M Main Extensions (i.e. Cortex-M23) lack the non-secure + Stack Pointer Limit register hence the write is silently ignored in non-secure + mode. + + \details Assigns the given value to the non-secure Process Stack Pointer Limit (PSPLIM) when in secure state. + \param [in] ProcStackPtrLimit Process Stack Pointer Limit value to set + */ +__STATIC_FORCEINLINE void __TZ_set_PSPLIM_NS(uint32_t ProcStackPtrLimit) +{ +#if (!((defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) || \ + (defined (__ARM_ARCH_8_1M_MAIN__ ) && (__ARM_ARCH_8_1M_MAIN__ == 1)) ) ) + // without main extensions, the non-secure PSPLIM is RAZ/WI + (void)ProcStackPtrLimit; +#else + __ASM volatile ("MSR psplim_ns, %0\n" : : "r" (ProcStackPtrLimit)); +#endif +} +#endif + + +/** + \brief Get Main Stack Pointer Limit + Devices without ARMv8-M Main Extensions (i.e. Cortex-M23) lack the non-secure + Stack Pointer Limit register hence zero is returned always. + + \details Returns the current value of the Main Stack Pointer Limit (MSPLIM). + \return MSPLIM Register value + */ +__STATIC_FORCEINLINE uint32_t __get_MSPLIM(void) +{ +#if (!((defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) || \ + (defined (__ARM_ARCH_8_1M_MAIN__ ) && (__ARM_ARCH_8_1M_MAIN__ == 1)) ) && \ + (!defined (__ARM_FEATURE_CMSE) || (__ARM_FEATURE_CMSE < 3))) + // without main extensions, the non-secure MSPLIM is RAZ/WI + return 0U; +#else + uint32_t result; + __ASM volatile ("MRS %0, msplim" : "=r" (result) ); + return result; +#endif +} + + +#if (defined (__ARM_FEATURE_CMSE ) && (__ARM_FEATURE_CMSE == 3)) +/** + \brief Get Main Stack Pointer Limit (non-secure) + Devices without ARMv8-M Main Extensions (i.e. Cortex-M23) lack the non-secure + Stack Pointer Limit register hence zero is returned always. + + \details Returns the current value of the non-secure Main Stack Pointer Limit(MSPLIM) when in secure state. + \return MSPLIM Register value + */ +__STATIC_FORCEINLINE uint32_t __TZ_get_MSPLIM_NS(void) +{ +#if (!((defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) || \ + (defined (__ARM_ARCH_8_1M_MAIN__ ) && (__ARM_ARCH_8_1M_MAIN__ == 1)) ) ) + // without main extensions, the non-secure MSPLIM is RAZ/WI + return 0U; +#else + uint32_t result; + __ASM volatile ("MRS %0, msplim_ns" : "=r" (result) ); + return result; +#endif +} +#endif + + +/** + \brief Set Main Stack Pointer Limit + Devices without ARMv8-M Main Extensions (i.e. Cortex-M23) lack the non-secure + Stack Pointer Limit register hence the write is silently ignored. + + \details Assigns the given value to the Main Stack Pointer Limit (MSPLIM). + \param [in] MainStackPtrLimit Main Stack Pointer Limit value to set + */ +__STATIC_FORCEINLINE void __set_MSPLIM(uint32_t MainStackPtrLimit) +{ +#if (!((defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) || \ + (defined (__ARM_ARCH_8_1M_MAIN__ ) && (__ARM_ARCH_8_1M_MAIN__ == 1)) ) && \ + (!defined (__ARM_FEATURE_CMSE) || (__ARM_FEATURE_CMSE < 3))) + // without main extensions, the non-secure MSPLIM is RAZ/WI + (void)MainStackPtrLimit; +#else + __ASM volatile ("MSR msplim, %0" : : "r" (MainStackPtrLimit)); +#endif +} + + +#if (defined (__ARM_FEATURE_CMSE ) && (__ARM_FEATURE_CMSE == 3)) +/** + \brief Set Main Stack Pointer Limit (non-secure) + Devices without ARMv8-M Main Extensions (i.e. Cortex-M23) lack the non-secure + Stack Pointer Limit register hence the write is silently ignored. + + \details Assigns the given value to the non-secure Main Stack Pointer Limit (MSPLIM) when in secure state. + \param [in] MainStackPtrLimit Main Stack Pointer value to set + */ +__STATIC_FORCEINLINE void __TZ_set_MSPLIM_NS(uint32_t MainStackPtrLimit) +{ +#if (!((defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) || \ + (defined (__ARM_ARCH_8_1M_MAIN__ ) && (__ARM_ARCH_8_1M_MAIN__ == 1)) ) ) + // without main extensions, the non-secure MSPLIM is RAZ/WI + (void)MainStackPtrLimit; +#else + __ASM volatile ("MSR msplim_ns, %0" : : "r" (MainStackPtrLimit)); +#endif +} +#endif + +#endif /* ((defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) || \ + (defined (__ARM_ARCH_8M_BASE__ ) && (__ARM_ARCH_8M_BASE__ == 1)) || \ + (defined (__ARM_ARCH_8_1M_MAIN__) && (__ARM_ARCH_8_1M_MAIN__ == 1)) ) */ + +/** + \brief Get FPSCR + \details Returns the current value of the Floating Point Status/Control register. + \return Floating Point Status/Control register value + */ +#if ((defined (__FPU_PRESENT) && (__FPU_PRESENT == 1U)) && \ + (defined (__FPU_USED ) && (__FPU_USED == 1U)) ) +#define __get_FPSCR (uint32_t)__builtin_arm_get_fpscr +#else +#define __get_FPSCR() ((uint32_t)0U) +#endif + +/** + \brief Set FPSCR + \details Assigns the given value to the Floating Point Status/Control register. + \param [in] fpscr Floating Point Status/Control value to set + */ +#if ((defined (__FPU_PRESENT) && (__FPU_PRESENT == 1U)) && \ + (defined (__FPU_USED ) && (__FPU_USED == 1U)) ) +#define __set_FPSCR __builtin_arm_set_fpscr +#else +#define __set_FPSCR(fpscr) ((void)(fpscr)) +#endif + + +/** @} end of CMSIS_Core_RegAccFunctions */ + + +/* ################### Compiler specific Intrinsics ########################### */ +/** \defgroup CMSIS_SIMD_intrinsics CMSIS SIMD Intrinsics + Access to dedicated SIMD instructions + @{ +*/ + +#if (defined (__ARM_FEATURE_DSP) && (__ARM_FEATURE_DSP == 1)) + +#define __SADD8 __builtin_arm_sadd8 +#define __QADD8 __builtin_arm_qadd8 +#define __SHADD8 __builtin_arm_shadd8 +#define __UADD8 __builtin_arm_uadd8 +#define __UQADD8 __builtin_arm_uqadd8 +#define __UHADD8 __builtin_arm_uhadd8 +#define __SSUB8 __builtin_arm_ssub8 +#define __QSUB8 __builtin_arm_qsub8 +#define __SHSUB8 __builtin_arm_shsub8 +#define __USUB8 __builtin_arm_usub8 +#define __UQSUB8 __builtin_arm_uqsub8 +#define __UHSUB8 __builtin_arm_uhsub8 +#define __SADD16 __builtin_arm_sadd16 +#define __QADD16 __builtin_arm_qadd16 +#define __SHADD16 __builtin_arm_shadd16 +#define __UADD16 __builtin_arm_uadd16 +#define __UQADD16 __builtin_arm_uqadd16 +#define __UHADD16 __builtin_arm_uhadd16 +#define __SSUB16 __builtin_arm_ssub16 +#define __QSUB16 __builtin_arm_qsub16 +#define __SHSUB16 __builtin_arm_shsub16 +#define __USUB16 __builtin_arm_usub16 +#define __UQSUB16 __builtin_arm_uqsub16 +#define __UHSUB16 __builtin_arm_uhsub16 +#define __SASX __builtin_arm_sasx +#define __QASX __builtin_arm_qasx +#define __SHASX __builtin_arm_shasx +#define __UASX __builtin_arm_uasx +#define __UQASX __builtin_arm_uqasx +#define __UHASX __builtin_arm_uhasx +#define __SSAX __builtin_arm_ssax +#define __QSAX __builtin_arm_qsax +#define __SHSAX __builtin_arm_shsax +#define __USAX __builtin_arm_usax +#define __UQSAX __builtin_arm_uqsax +#define __UHSAX __builtin_arm_uhsax +#define __USAD8 __builtin_arm_usad8 +#define __USADA8 __builtin_arm_usada8 +#define __SSAT16 __builtin_arm_ssat16 +#define __USAT16 __builtin_arm_usat16 +#define __UXTB16 __builtin_arm_uxtb16 +#define __UXTAB16 __builtin_arm_uxtab16 +#define __SXTB16 __builtin_arm_sxtb16 +#define __SXTAB16 __builtin_arm_sxtab16 +#define __SMUAD __builtin_arm_smuad +#define __SMUADX __builtin_arm_smuadx +#define __SMLAD __builtin_arm_smlad +#define __SMLADX __builtin_arm_smladx +#define __SMLALD __builtin_arm_smlald +#define __SMLALDX __builtin_arm_smlaldx +#define __SMUSD __builtin_arm_smusd +#define __SMUSDX __builtin_arm_smusdx +#define __SMLSD __builtin_arm_smlsd +#define __SMLSDX __builtin_arm_smlsdx +#define __SMLSLD __builtin_arm_smlsld +#define __SMLSLDX __builtin_arm_smlsldx +#define __SEL __builtin_arm_sel +#define __QADD __builtin_arm_qadd +#define __QSUB __builtin_arm_qsub + +#define __PKHBT(ARG1,ARG2,ARG3) ( ((((uint32_t)(ARG1)) ) & 0x0000FFFFUL) | \ + ((((uint32_t)(ARG2)) << (ARG3)) & 0xFFFF0000UL) ) + +#define __PKHTB(ARG1,ARG2,ARG3) ( ((((uint32_t)(ARG1)) ) & 0xFFFF0000UL) | \ + ((((uint32_t)(ARG2)) >> (ARG3)) & 0x0000FFFFUL) ) + +#define __SXTB16_RORn(ARG1, ARG2) __SXTB16(__ROR(ARG1, ARG2)) + +#define __SXTAB16_RORn(ARG1, ARG2, ARG3) __SXTAB16(ARG1, __ROR(ARG2, ARG3)) + +__STATIC_FORCEINLINE int32_t __SMMLA (int32_t op1, int32_t op2, int32_t op3) +{ + int32_t result; + + __ASM volatile ("smmla %0, %1, %2, %3" : "=r" (result): "r" (op1), "r" (op2), "r" (op3) ); + return(result); +} + +#endif /* (__ARM_FEATURE_DSP == 1) */ +/** @} end of group CMSIS_SIMD_intrinsics */ + + +#endif /* __CMSIS_TIARMCLANG_H */ diff --git a/lib/cmsis_core/cmsis_version.h b/lib/cmsis_core/cmsis_version.h new file mode 100644 index 00000000000..8b4765f186e --- /dev/null +++ b/lib/cmsis_core/cmsis_version.h @@ -0,0 +1,39 @@ +/**************************************************************************//** + * @file cmsis_version.h + * @brief CMSIS Core(M) Version definitions + * @version V5.0.5 + * @date 02. February 2022 + ******************************************************************************/ +/* + * Copyright (c) 2009-2022 ARM Limited. All rights reserved. + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an AS IS BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#if defined ( __ICCARM__ ) + #pragma system_include /* treat file as system include file for MISRA check */ +#elif defined (__clang__) + #pragma clang system_header /* treat file as system include file */ +#endif + +#ifndef __CMSIS_VERSION_H +#define __CMSIS_VERSION_H + +/* CMSIS Version definitions */ +#define __CM_CMSIS_VERSION_MAIN ( 5U) /*!< [31:16] CMSIS Core(M) main version */ +#define __CM_CMSIS_VERSION_SUB ( 6U) /*!< [15:0] CMSIS Core(M) sub version */ +#define __CM_CMSIS_VERSION ((__CM_CMSIS_VERSION_MAIN << 16U) | \ + __CM_CMSIS_VERSION_SUB ) /*!< CMSIS Core(M) version number */ +#endif diff --git a/lib/cmsis_core/core_cm4.h b/lib/cmsis_core/core_cm4.h new file mode 100644 index 00000000000..711c11326c9 --- /dev/null +++ b/lib/cmsis_core/core_cm4.h @@ -0,0 +1,2170 @@ +/**************************************************************************//** + * @file core_cm4.h + * @brief CMSIS Cortex-M4 Core Peripheral Access Layer Header File + * @version V5.2.0 + * @date 04. April 2023 + ******************************************************************************/ +/* + * Copyright (c) 2009-2023 Arm Limited. All rights reserved. + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an AS IS BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#if defined ( __ICCARM__ ) + #pragma system_include /* treat file as system include file for MISRA check */ +#elif defined (__clang__) + #pragma clang system_header /* treat file as system include file */ +#endif + +#ifndef __CORE_CM4_H_GENERIC +#define __CORE_CM4_H_GENERIC + +#include + +#ifdef __cplusplus + extern "C" { +#endif + +/** + \page CMSIS_MISRA_Exceptions MISRA-C:2004 Compliance Exceptions + CMSIS violates the following MISRA-C:2004 rules: + + \li Required Rule 8.5, object/function definition in header file.
+ Function definitions in header files are used to allow 'inlining'. + + \li Required Rule 18.4, declaration of union type or object of union type: '{...}'.
+ Unions are used for effective representation of core registers. + + \li Advisory Rule 19.7, Function-like macro defined.
+ Function-like macros are used to allow more efficient code. + */ + + +/******************************************************************************* + * CMSIS definitions + ******************************************************************************/ +/** + \ingroup Cortex_M4 + @{ + */ + +#include "cmsis_version.h" + +/* CMSIS CM4 definitions */ +#define __CM4_CMSIS_VERSION_MAIN (__CM_CMSIS_VERSION_MAIN) /*!< \deprecated [31:16] CMSIS HAL main version */ +#define __CM4_CMSIS_VERSION_SUB (__CM_CMSIS_VERSION_SUB) /*!< \deprecated [15:0] CMSIS HAL sub version */ +#define __CM4_CMSIS_VERSION ((__CM4_CMSIS_VERSION_MAIN << 16U) | \ + __CM4_CMSIS_VERSION_SUB ) /*!< \deprecated CMSIS HAL version number */ + +#define __CORTEX_M (4U) /*!< Cortex-M Core */ + +/** __FPU_USED indicates whether an FPU is used or not. + For this, __FPU_PRESENT has to be checked prior to making use of FPU specific registers and functions. +*/ +#if defined ( __CC_ARM ) + #if defined __TARGET_FPU_VFP + #if defined (__FPU_PRESENT) && (__FPU_PRESENT == 1U) + #define __FPU_USED 1U + #else + #error "Compiler generates FPU instructions for a device without an FPU (check __FPU_PRESENT)" + #define __FPU_USED 0U + #endif + #else + #define __FPU_USED 0U + #endif + +#elif defined (__ARMCC_VERSION) && (__ARMCC_VERSION >= 6010050) + #if defined __ARM_FP + #if defined (__FPU_PRESENT) && (__FPU_PRESENT == 1U) + #define __FPU_USED 1U + #else + #warning "Compiler generates FPU instructions for a device without an FPU (check __FPU_PRESENT)" + #define __FPU_USED 0U + #endif + #else + #define __FPU_USED 0U + #endif + +#elif defined (__ti__) + #if defined (__ARM_FP) + #if defined (__FPU_PRESENT) && (__FPU_PRESENT == 1U) + #define __FPU_USED 1U + #else + #warning "Compiler generates FPU instructions for a device without an FPU (check __FPU_PRESENT)" + #define __FPU_USED 0U + #endif + #else + #define __FPU_USED 0U + #endif + +#elif defined ( __GNUC__ ) + #if defined (__VFP_FP__) && !defined(__SOFTFP__) + #if defined (__FPU_PRESENT) && (__FPU_PRESENT == 1U) + #define __FPU_USED 1U + #else + #error "Compiler generates FPU instructions for a device without an FPU (check __FPU_PRESENT)" + #define __FPU_USED 0U + #endif + #else + #define __FPU_USED 0U + #endif + +#elif defined ( __ICCARM__ ) + #if defined __ARMVFP__ + #if defined (__FPU_PRESENT) && (__FPU_PRESENT == 1U) + #define __FPU_USED 1U + #else + #error "Compiler generates FPU instructions for a device without an FPU (check __FPU_PRESENT)" + #define __FPU_USED 0U + #endif + #else + #define __FPU_USED 0U + #endif + +#elif defined ( __TI_ARM__ ) + #if defined __TI_VFP_SUPPORT__ + #if defined (__FPU_PRESENT) && (__FPU_PRESENT == 1U) + #define __FPU_USED 1U + #else + #error "Compiler generates FPU instructions for a device without an FPU (check __FPU_PRESENT)" + #define __FPU_USED 0U + #endif + #else + #define __FPU_USED 0U + #endif + +#elif defined ( __TASKING__ ) + #if defined __FPU_VFP__ + #if defined (__FPU_PRESENT) && (__FPU_PRESENT == 1U) + #define __FPU_USED 1U + #else + #error "Compiler generates FPU instructions for a device without an FPU (check __FPU_PRESENT)" + #define __FPU_USED 0U + #endif + #else + #define __FPU_USED 0U + #endif + +#elif defined ( __CSMC__ ) + #if ( __CSMC__ & 0x400U) + #if defined (__FPU_PRESENT) && (__FPU_PRESENT == 1U) + #define __FPU_USED 1U + #else + #error "Compiler generates FPU instructions for a device without an FPU (check __FPU_PRESENT)" + #define __FPU_USED 0U + #endif + #else + #define __FPU_USED 0U + #endif + +#endif + +#include "cmsis_compiler.h" /* CMSIS compiler specific defines */ + + +#ifdef __cplusplus +} +#endif + +#endif /* __CORE_CM4_H_GENERIC */ + +#ifndef __CMSIS_GENERIC + +#ifndef __CORE_CM4_H_DEPENDANT +#define __CORE_CM4_H_DEPENDANT + +#ifdef __cplusplus + extern "C" { +#endif + +/* check device defines and use defaults */ +#if defined __CHECK_DEVICE_DEFINES + #ifndef __CM4_REV + #define __CM4_REV 0x0000U + #warning "__CM4_REV not defined in device header file; using default!" + #endif + + #ifndef __FPU_PRESENT + #define __FPU_PRESENT 0U + #warning "__FPU_PRESENT not defined in device header file; using default!" + #endif + + #ifndef __MPU_PRESENT + #define __MPU_PRESENT 0U + #warning "__MPU_PRESENT not defined in device header file; using default!" + #endif + + #ifndef __VTOR_PRESENT + #define __VTOR_PRESENT 1U + #warning "__VTOR_PRESENT not defined in device header file; using default!" + #endif + + #ifndef __NVIC_PRIO_BITS + #define __NVIC_PRIO_BITS 3U + #warning "__NVIC_PRIO_BITS not defined in device header file; using default!" + #endif + + #ifndef __Vendor_SysTickConfig + #define __Vendor_SysTickConfig 0U + #warning "__Vendor_SysTickConfig not defined in device header file; using default!" + #endif +#endif + +/* IO definitions (access restrictions to peripheral registers) */ +/** + \defgroup CMSIS_glob_defs CMSIS Global Defines + + IO Type Qualifiers are used + \li to specify the access to peripheral variables. + \li for automatic generation of peripheral register debug information. +*/ +#ifdef __cplusplus + #define __I volatile /*!< Defines 'read only' permissions */ +#else + #define __I volatile const /*!< Defines 'read only' permissions */ +#endif +#define __O volatile /*!< Defines 'write only' permissions */ +#define __IO volatile /*!< Defines 'read / write' permissions */ + +/* following defines should be used for structure members */ +#define __IM volatile const /*! Defines 'read only' structure member permissions */ +#define __OM volatile /*! Defines 'write only' structure member permissions */ +#define __IOM volatile /*! Defines 'read / write' structure member permissions */ + +/*@} end of group Cortex_M4 */ + + + +/******************************************************************************* + * Register Abstraction + Core Register contain: + - Core Register + - Core NVIC Register + - Core SCB Register + - Core SysTick Register + - Core Debug Register + - Core MPU Register + - Core FPU Register + ******************************************************************************/ +/** + \defgroup CMSIS_core_register Defines and Type Definitions + \brief Type definitions and defines for Cortex-M processor based devices. +*/ + +/** + \ingroup CMSIS_core_register + \defgroup CMSIS_CORE Status and Control Registers + \brief Core Register type definitions. + @{ + */ + +/** + \brief Union type to access the Application Program Status Register (APSR). + */ +typedef union +{ + struct + { + uint32_t _reserved0:16; /*!< bit: 0..15 Reserved */ + uint32_t GE:4; /*!< bit: 16..19 Greater than or Equal flags */ + uint32_t _reserved1:7; /*!< bit: 20..26 Reserved */ + uint32_t Q:1; /*!< bit: 27 Saturation condition flag */ + uint32_t V:1; /*!< bit: 28 Overflow condition code flag */ + uint32_t C:1; /*!< bit: 29 Carry condition code flag */ + uint32_t Z:1; /*!< bit: 30 Zero condition code flag */ + uint32_t N:1; /*!< bit: 31 Negative condition code flag */ + } b; /*!< Structure used for bit access */ + uint32_t w; /*!< Type used for word access */ +} APSR_Type; + +/* APSR Register Definitions */ +#define APSR_N_Pos 31U /*!< APSR: N Position */ +#define APSR_N_Msk (1UL << APSR_N_Pos) /*!< APSR: N Mask */ + +#define APSR_Z_Pos 30U /*!< APSR: Z Position */ +#define APSR_Z_Msk (1UL << APSR_Z_Pos) /*!< APSR: Z Mask */ + +#define APSR_C_Pos 29U /*!< APSR: C Position */ +#define APSR_C_Msk (1UL << APSR_C_Pos) /*!< APSR: C Mask */ + +#define APSR_V_Pos 28U /*!< APSR: V Position */ +#define APSR_V_Msk (1UL << APSR_V_Pos) /*!< APSR: V Mask */ + +#define APSR_Q_Pos 27U /*!< APSR: Q Position */ +#define APSR_Q_Msk (1UL << APSR_Q_Pos) /*!< APSR: Q Mask */ + +#define APSR_GE_Pos 16U /*!< APSR: GE Position */ +#define APSR_GE_Msk (0xFUL << APSR_GE_Pos) /*!< APSR: GE Mask */ + + +/** + \brief Union type to access the Interrupt Program Status Register (IPSR). + */ +typedef union +{ + struct + { + uint32_t ISR:9; /*!< bit: 0.. 8 Exception number */ + uint32_t _reserved0:23; /*!< bit: 9..31 Reserved */ + } b; /*!< Structure used for bit access */ + uint32_t w; /*!< Type used for word access */ +} IPSR_Type; + +/* IPSR Register Definitions */ +#define IPSR_ISR_Pos 0U /*!< IPSR: ISR Position */ +#define IPSR_ISR_Msk (0x1FFUL /*<< IPSR_ISR_Pos*/) /*!< IPSR: ISR Mask */ + + +/** + \brief Union type to access the Special-Purpose Program Status Registers (xPSR). + */ +typedef union +{ + struct + { + uint32_t ISR:9; /*!< bit: 0.. 8 Exception number */ + uint32_t _reserved0:1; /*!< bit: 9 Reserved */ + uint32_t ICI_IT_1:6; /*!< bit: 10..15 ICI/IT part 1 */ + uint32_t GE:4; /*!< bit: 16..19 Greater than or Equal flags */ + uint32_t _reserved1:4; /*!< bit: 20..23 Reserved */ + uint32_t T:1; /*!< bit: 24 Thumb bit */ + uint32_t ICI_IT_2:2; /*!< bit: 25..26 ICI/IT part 2 */ + uint32_t Q:1; /*!< bit: 27 Saturation condition flag */ + uint32_t V:1; /*!< bit: 28 Overflow condition code flag */ + uint32_t C:1; /*!< bit: 29 Carry condition code flag */ + uint32_t Z:1; /*!< bit: 30 Zero condition code flag */ + uint32_t N:1; /*!< bit: 31 Negative condition code flag */ + } b; /*!< Structure used for bit access */ + uint32_t w; /*!< Type used for word access */ +} xPSR_Type; + +/* xPSR Register Definitions */ +#define xPSR_N_Pos 31U /*!< xPSR: N Position */ +#define xPSR_N_Msk (1UL << xPSR_N_Pos) /*!< xPSR: N Mask */ + +#define xPSR_Z_Pos 30U /*!< xPSR: Z Position */ +#define xPSR_Z_Msk (1UL << xPSR_Z_Pos) /*!< xPSR: Z Mask */ + +#define xPSR_C_Pos 29U /*!< xPSR: C Position */ +#define xPSR_C_Msk (1UL << xPSR_C_Pos) /*!< xPSR: C Mask */ + +#define xPSR_V_Pos 28U /*!< xPSR: V Position */ +#define xPSR_V_Msk (1UL << xPSR_V_Pos) /*!< xPSR: V Mask */ + +#define xPSR_Q_Pos 27U /*!< xPSR: Q Position */ +#define xPSR_Q_Msk (1UL << xPSR_Q_Pos) /*!< xPSR: Q Mask */ + +#define xPSR_ICI_IT_2_Pos 25U /*!< xPSR: ICI/IT part 2 Position */ +#define xPSR_ICI_IT_2_Msk (3UL << xPSR_ICI_IT_2_Pos) /*!< xPSR: ICI/IT part 2 Mask */ + +#define xPSR_T_Pos 24U /*!< xPSR: T Position */ +#define xPSR_T_Msk (1UL << xPSR_T_Pos) /*!< xPSR: T Mask */ + +#define xPSR_GE_Pos 16U /*!< xPSR: GE Position */ +#define xPSR_GE_Msk (0xFUL << xPSR_GE_Pos) /*!< xPSR: GE Mask */ + +#define xPSR_ICI_IT_1_Pos 10U /*!< xPSR: ICI/IT part 1 Position */ +#define xPSR_ICI_IT_1_Msk (0x3FUL << xPSR_ICI_IT_1_Pos) /*!< xPSR: ICI/IT part 1 Mask */ + +#define xPSR_ISR_Pos 0U /*!< xPSR: ISR Position */ +#define xPSR_ISR_Msk (0x1FFUL /*<< xPSR_ISR_Pos*/) /*!< xPSR: ISR Mask */ + + +/** + \brief Union type to access the Control Registers (CONTROL). + */ +typedef union +{ + struct + { + uint32_t nPRIV:1; /*!< bit: 0 Execution privilege in Thread mode */ + uint32_t SPSEL:1; /*!< bit: 1 Stack to be used */ + uint32_t FPCA:1; /*!< bit: 2 FP extension active flag */ + uint32_t _reserved0:29; /*!< bit: 3..31 Reserved */ + } b; /*!< Structure used for bit access */ + uint32_t w; /*!< Type used for word access */ +} CONTROL_Type; + +/* CONTROL Register Definitions */ +#define CONTROL_FPCA_Pos 2U /*!< CONTROL: FPCA Position */ +#define CONTROL_FPCA_Msk (1UL << CONTROL_FPCA_Pos) /*!< CONTROL: FPCA Mask */ + +#define CONTROL_SPSEL_Pos 1U /*!< CONTROL: SPSEL Position */ +#define CONTROL_SPSEL_Msk (1UL << CONTROL_SPSEL_Pos) /*!< CONTROL: SPSEL Mask */ + +#define CONTROL_nPRIV_Pos 0U /*!< CONTROL: nPRIV Position */ +#define CONTROL_nPRIV_Msk (1UL /*<< CONTROL_nPRIV_Pos*/) /*!< CONTROL: nPRIV Mask */ + +/*@} end of group CMSIS_CORE */ + + +/** + \ingroup CMSIS_core_register + \defgroup CMSIS_NVIC Nested Vectored Interrupt Controller (NVIC) + \brief Type definitions for the NVIC Registers + @{ + */ + +/** + \brief Structure type to access the Nested Vectored Interrupt Controller (NVIC). + */ +typedef struct +{ + __IOM uint32_t ISER[8U]; /*!< Offset: 0x000 (R/W) Interrupt Set Enable Register */ + uint32_t RESERVED0[24U]; + __IOM uint32_t ICER[8U]; /*!< Offset: 0x080 (R/W) Interrupt Clear Enable Register */ + uint32_t RESERVED1[24U]; + __IOM uint32_t ISPR[8U]; /*!< Offset: 0x100 (R/W) Interrupt Set Pending Register */ + uint32_t RESERVED2[24U]; + __IOM uint32_t ICPR[8U]; /*!< Offset: 0x180 (R/W) Interrupt Clear Pending Register */ + uint32_t RESERVED3[24U]; + __IOM uint32_t IABR[8U]; /*!< Offset: 0x200 (R/W) Interrupt Active bit Register */ + uint32_t RESERVED4[56U]; + __IOM uint8_t IP[240U]; /*!< Offset: 0x300 (R/W) Interrupt Priority Register (8Bit wide) */ + uint32_t RESERVED5[644U]; + __OM uint32_t STIR; /*!< Offset: 0xE00 ( /W) Software Trigger Interrupt Register */ +} NVIC_Type; + +/* Software Triggered Interrupt Register Definitions */ +#define NVIC_STIR_INTID_Pos 0U /*!< STIR: INTLINESNUM Position */ +#define NVIC_STIR_INTID_Msk (0x1FFUL /*<< NVIC_STIR_INTID_Pos*/) /*!< STIR: INTLINESNUM Mask */ + +/*@} end of group CMSIS_NVIC */ + + +/** + \ingroup CMSIS_core_register + \defgroup CMSIS_SCB System Control Block (SCB) + \brief Type definitions for the System Control Block Registers + @{ + */ + +/** + \brief Structure type to access the System Control Block (SCB). + */ +typedef struct +{ + __IM uint32_t CPUID; /*!< Offset: 0x000 (R/ ) CPUID Base Register */ + __IOM uint32_t ICSR; /*!< Offset: 0x004 (R/W) Interrupt Control and State Register */ + __IOM uint32_t VTOR; /*!< Offset: 0x008 (R/W) Vector Table Offset Register */ + __IOM uint32_t AIRCR; /*!< Offset: 0x00C (R/W) Application Interrupt and Reset Control Register */ + __IOM uint32_t SCR; /*!< Offset: 0x010 (R/W) System Control Register */ + __IOM uint32_t CCR; /*!< Offset: 0x014 (R/W) Configuration Control Register */ + __IOM uint8_t SHP[12U]; /*!< Offset: 0x018 (R/W) System Handlers Priority Registers (4-7, 8-11, 12-15) */ + __IOM uint32_t SHCSR; /*!< Offset: 0x024 (R/W) System Handler Control and State Register */ + __IOM uint32_t CFSR; /*!< Offset: 0x028 (R/W) Configurable Fault Status Register */ + __IOM uint32_t HFSR; /*!< Offset: 0x02C (R/W) HardFault Status Register */ + __IOM uint32_t DFSR; /*!< Offset: 0x030 (R/W) Debug Fault Status Register */ + __IOM uint32_t MMFAR; /*!< Offset: 0x034 (R/W) MemManage Fault Address Register */ + __IOM uint32_t BFAR; /*!< Offset: 0x038 (R/W) BusFault Address Register */ + __IOM uint32_t AFSR; /*!< Offset: 0x03C (R/W) Auxiliary Fault Status Register */ + __IM uint32_t PFR[2U]; /*!< Offset: 0x040 (R/ ) Processor Feature Register */ + __IM uint32_t DFR; /*!< Offset: 0x048 (R/ ) Debug Feature Register */ + __IM uint32_t ADR; /*!< Offset: 0x04C (R/ ) Auxiliary Feature Register */ + __IM uint32_t MMFR[4U]; /*!< Offset: 0x050 (R/ ) Memory Model Feature Register */ + __IM uint32_t ISAR[5U]; /*!< Offset: 0x060 (R/ ) Instruction Set Attributes Register */ + uint32_t RESERVED0[5U]; + __IOM uint32_t CPACR; /*!< Offset: 0x088 (R/W) Coprocessor Access Control Register */ +} SCB_Type; + +/* SCB CPUID Register Definitions */ +#define SCB_CPUID_IMPLEMENTER_Pos 24U /*!< SCB CPUID: IMPLEMENTER Position */ +#define SCB_CPUID_IMPLEMENTER_Msk (0xFFUL << SCB_CPUID_IMPLEMENTER_Pos) /*!< SCB CPUID: IMPLEMENTER Mask */ + +#define SCB_CPUID_VARIANT_Pos 20U /*!< SCB CPUID: VARIANT Position */ +#define SCB_CPUID_VARIANT_Msk (0xFUL << SCB_CPUID_VARIANT_Pos) /*!< SCB CPUID: VARIANT Mask */ + +#define SCB_CPUID_ARCHITECTURE_Pos 16U /*!< SCB CPUID: ARCHITECTURE Position */ +#define SCB_CPUID_ARCHITECTURE_Msk (0xFUL << SCB_CPUID_ARCHITECTURE_Pos) /*!< SCB CPUID: ARCHITECTURE Mask */ + +#define SCB_CPUID_PARTNO_Pos 4U /*!< SCB CPUID: PARTNO Position */ +#define SCB_CPUID_PARTNO_Msk (0xFFFUL << SCB_CPUID_PARTNO_Pos) /*!< SCB CPUID: PARTNO Mask */ + +#define SCB_CPUID_REVISION_Pos 0U /*!< SCB CPUID: REVISION Position */ +#define SCB_CPUID_REVISION_Msk (0xFUL /*<< SCB_CPUID_REVISION_Pos*/) /*!< SCB CPUID: REVISION Mask */ + +/* SCB Interrupt Control State Register Definitions */ +#define SCB_ICSR_NMIPENDSET_Pos 31U /*!< SCB ICSR: NMIPENDSET Position */ +#define SCB_ICSR_NMIPENDSET_Msk (1UL << SCB_ICSR_NMIPENDSET_Pos) /*!< SCB ICSR: NMIPENDSET Mask */ + +#define SCB_ICSR_PENDSVSET_Pos 28U /*!< SCB ICSR: PENDSVSET Position */ +#define SCB_ICSR_PENDSVSET_Msk (1UL << SCB_ICSR_PENDSVSET_Pos) /*!< SCB ICSR: PENDSVSET Mask */ + +#define SCB_ICSR_PENDSVCLR_Pos 27U /*!< SCB ICSR: PENDSVCLR Position */ +#define SCB_ICSR_PENDSVCLR_Msk (1UL << SCB_ICSR_PENDSVCLR_Pos) /*!< SCB ICSR: PENDSVCLR Mask */ + +#define SCB_ICSR_PENDSTSET_Pos 26U /*!< SCB ICSR: PENDSTSET Position */ +#define SCB_ICSR_PENDSTSET_Msk (1UL << SCB_ICSR_PENDSTSET_Pos) /*!< SCB ICSR: PENDSTSET Mask */ + +#define SCB_ICSR_PENDSTCLR_Pos 25U /*!< SCB ICSR: PENDSTCLR Position */ +#define SCB_ICSR_PENDSTCLR_Msk (1UL << SCB_ICSR_PENDSTCLR_Pos) /*!< SCB ICSR: PENDSTCLR Mask */ + +#define SCB_ICSR_ISRPREEMPT_Pos 23U /*!< SCB ICSR: ISRPREEMPT Position */ +#define SCB_ICSR_ISRPREEMPT_Msk (1UL << SCB_ICSR_ISRPREEMPT_Pos) /*!< SCB ICSR: ISRPREEMPT Mask */ + +#define SCB_ICSR_ISRPENDING_Pos 22U /*!< SCB ICSR: ISRPENDING Position */ +#define SCB_ICSR_ISRPENDING_Msk (1UL << SCB_ICSR_ISRPENDING_Pos) /*!< SCB ICSR: ISRPENDING Mask */ + +#define SCB_ICSR_VECTPENDING_Pos 12U /*!< SCB ICSR: VECTPENDING Position */ +#define SCB_ICSR_VECTPENDING_Msk (0x1FFUL << SCB_ICSR_VECTPENDING_Pos) /*!< SCB ICSR: VECTPENDING Mask */ + +#define SCB_ICSR_RETTOBASE_Pos 11U /*!< SCB ICSR: RETTOBASE Position */ +#define SCB_ICSR_RETTOBASE_Msk (1UL << SCB_ICSR_RETTOBASE_Pos) /*!< SCB ICSR: RETTOBASE Mask */ + +#define SCB_ICSR_VECTACTIVE_Pos 0U /*!< SCB ICSR: VECTACTIVE Position */ +#define SCB_ICSR_VECTACTIVE_Msk (0x1FFUL /*<< SCB_ICSR_VECTACTIVE_Pos*/) /*!< SCB ICSR: VECTACTIVE Mask */ + +/* SCB Vector Table Offset Register Definitions */ +#define SCB_VTOR_TBLOFF_Pos 7U /*!< SCB VTOR: TBLOFF Position */ +#define SCB_VTOR_TBLOFF_Msk (0x1FFFFFFUL << SCB_VTOR_TBLOFF_Pos) /*!< SCB VTOR: TBLOFF Mask */ + +/* SCB Application Interrupt and Reset Control Register Definitions */ +#define SCB_AIRCR_VECTKEY_Pos 16U /*!< SCB AIRCR: VECTKEY Position */ +#define SCB_AIRCR_VECTKEY_Msk (0xFFFFUL << SCB_AIRCR_VECTKEY_Pos) /*!< SCB AIRCR: VECTKEY Mask */ + +#define SCB_AIRCR_VECTKEYSTAT_Pos 16U /*!< SCB AIRCR: VECTKEYSTAT Position */ +#define SCB_AIRCR_VECTKEYSTAT_Msk (0xFFFFUL << SCB_AIRCR_VECTKEYSTAT_Pos) /*!< SCB AIRCR: VECTKEYSTAT Mask */ + +#define SCB_AIRCR_ENDIANESS_Pos 15U /*!< SCB AIRCR: ENDIANESS Position */ +#define SCB_AIRCR_ENDIANESS_Msk (1UL << SCB_AIRCR_ENDIANESS_Pos) /*!< SCB AIRCR: ENDIANESS Mask */ + +#define SCB_AIRCR_PRIGROUP_Pos 8U /*!< SCB AIRCR: PRIGROUP Position */ +#define SCB_AIRCR_PRIGROUP_Msk (7UL << SCB_AIRCR_PRIGROUP_Pos) /*!< SCB AIRCR: PRIGROUP Mask */ + +#define SCB_AIRCR_SYSRESETREQ_Pos 2U /*!< SCB AIRCR: SYSRESETREQ Position */ +#define SCB_AIRCR_SYSRESETREQ_Msk (1UL << SCB_AIRCR_SYSRESETREQ_Pos) /*!< SCB AIRCR: SYSRESETREQ Mask */ + +#define SCB_AIRCR_VECTCLRACTIVE_Pos 1U /*!< SCB AIRCR: VECTCLRACTIVE Position */ +#define SCB_AIRCR_VECTCLRACTIVE_Msk (1UL << SCB_AIRCR_VECTCLRACTIVE_Pos) /*!< SCB AIRCR: VECTCLRACTIVE Mask */ + +#define SCB_AIRCR_VECTRESET_Pos 0U /*!< SCB AIRCR: VECTRESET Position */ +#define SCB_AIRCR_VECTRESET_Msk (1UL /*<< SCB_AIRCR_VECTRESET_Pos*/) /*!< SCB AIRCR: VECTRESET Mask */ + +/* SCB System Control Register Definitions */ +#define SCB_SCR_SEVONPEND_Pos 4U /*!< SCB SCR: SEVONPEND Position */ +#define SCB_SCR_SEVONPEND_Msk (1UL << SCB_SCR_SEVONPEND_Pos) /*!< SCB SCR: SEVONPEND Mask */ + +#define SCB_SCR_SLEEPDEEP_Pos 2U /*!< SCB SCR: SLEEPDEEP Position */ +#define SCB_SCR_SLEEPDEEP_Msk (1UL << SCB_SCR_SLEEPDEEP_Pos) /*!< SCB SCR: SLEEPDEEP Mask */ + +#define SCB_SCR_SLEEPONEXIT_Pos 1U /*!< SCB SCR: SLEEPONEXIT Position */ +#define SCB_SCR_SLEEPONEXIT_Msk (1UL << SCB_SCR_SLEEPONEXIT_Pos) /*!< SCB SCR: SLEEPONEXIT Mask */ + +/* SCB Configuration Control Register Definitions */ +#define SCB_CCR_STKALIGN_Pos 9U /*!< SCB CCR: STKALIGN Position */ +#define SCB_CCR_STKALIGN_Msk (1UL << SCB_CCR_STKALIGN_Pos) /*!< SCB CCR: STKALIGN Mask */ + +#define SCB_CCR_BFHFNMIGN_Pos 8U /*!< SCB CCR: BFHFNMIGN Position */ +#define SCB_CCR_BFHFNMIGN_Msk (1UL << SCB_CCR_BFHFNMIGN_Pos) /*!< SCB CCR: BFHFNMIGN Mask */ + +#define SCB_CCR_DIV_0_TRP_Pos 4U /*!< SCB CCR: DIV_0_TRP Position */ +#define SCB_CCR_DIV_0_TRP_Msk (1UL << SCB_CCR_DIV_0_TRP_Pos) /*!< SCB CCR: DIV_0_TRP Mask */ + +#define SCB_CCR_UNALIGN_TRP_Pos 3U /*!< SCB CCR: UNALIGN_TRP Position */ +#define SCB_CCR_UNALIGN_TRP_Msk (1UL << SCB_CCR_UNALIGN_TRP_Pos) /*!< SCB CCR: UNALIGN_TRP Mask */ + +#define SCB_CCR_USERSETMPEND_Pos 1U /*!< SCB CCR: USERSETMPEND Position */ +#define SCB_CCR_USERSETMPEND_Msk (1UL << SCB_CCR_USERSETMPEND_Pos) /*!< SCB CCR: USERSETMPEND Mask */ + +#define SCB_CCR_NONBASETHRDENA_Pos 0U /*!< SCB CCR: NONBASETHRDENA Position */ +#define SCB_CCR_NONBASETHRDENA_Msk (1UL /*<< SCB_CCR_NONBASETHRDENA_Pos*/) /*!< SCB CCR: NONBASETHRDENA Mask */ + +/* SCB System Handler Control and State Register Definitions */ +#define SCB_SHCSR_USGFAULTENA_Pos 18U /*!< SCB SHCSR: USGFAULTENA Position */ +#define SCB_SHCSR_USGFAULTENA_Msk (1UL << SCB_SHCSR_USGFAULTENA_Pos) /*!< SCB SHCSR: USGFAULTENA Mask */ + +#define SCB_SHCSR_BUSFAULTENA_Pos 17U /*!< SCB SHCSR: BUSFAULTENA Position */ +#define SCB_SHCSR_BUSFAULTENA_Msk (1UL << SCB_SHCSR_BUSFAULTENA_Pos) /*!< SCB SHCSR: BUSFAULTENA Mask */ + +#define SCB_SHCSR_MEMFAULTENA_Pos 16U /*!< SCB SHCSR: MEMFAULTENA Position */ +#define SCB_SHCSR_MEMFAULTENA_Msk (1UL << SCB_SHCSR_MEMFAULTENA_Pos) /*!< SCB SHCSR: MEMFAULTENA Mask */ + +#define SCB_SHCSR_SVCALLPENDED_Pos 15U /*!< SCB SHCSR: SVCALLPENDED Position */ +#define SCB_SHCSR_SVCALLPENDED_Msk (1UL << SCB_SHCSR_SVCALLPENDED_Pos) /*!< SCB SHCSR: SVCALLPENDED Mask */ + +#define SCB_SHCSR_BUSFAULTPENDED_Pos 14U /*!< SCB SHCSR: BUSFAULTPENDED Position */ +#define SCB_SHCSR_BUSFAULTPENDED_Msk (1UL << SCB_SHCSR_BUSFAULTPENDED_Pos) /*!< SCB SHCSR: BUSFAULTPENDED Mask */ + +#define SCB_SHCSR_MEMFAULTPENDED_Pos 13U /*!< SCB SHCSR: MEMFAULTPENDED Position */ +#define SCB_SHCSR_MEMFAULTPENDED_Msk (1UL << SCB_SHCSR_MEMFAULTPENDED_Pos) /*!< SCB SHCSR: MEMFAULTPENDED Mask */ + +#define SCB_SHCSR_USGFAULTPENDED_Pos 12U /*!< SCB SHCSR: USGFAULTPENDED Position */ +#define SCB_SHCSR_USGFAULTPENDED_Msk (1UL << SCB_SHCSR_USGFAULTPENDED_Pos) /*!< SCB SHCSR: USGFAULTPENDED Mask */ + +#define SCB_SHCSR_SYSTICKACT_Pos 11U /*!< SCB SHCSR: SYSTICKACT Position */ +#define SCB_SHCSR_SYSTICKACT_Msk (1UL << SCB_SHCSR_SYSTICKACT_Pos) /*!< SCB SHCSR: SYSTICKACT Mask */ + +#define SCB_SHCSR_PENDSVACT_Pos 10U /*!< SCB SHCSR: PENDSVACT Position */ +#define SCB_SHCSR_PENDSVACT_Msk (1UL << SCB_SHCSR_PENDSVACT_Pos) /*!< SCB SHCSR: PENDSVACT Mask */ + +#define SCB_SHCSR_MONITORACT_Pos 8U /*!< SCB SHCSR: MONITORACT Position */ +#define SCB_SHCSR_MONITORACT_Msk (1UL << SCB_SHCSR_MONITORACT_Pos) /*!< SCB SHCSR: MONITORACT Mask */ + +#define SCB_SHCSR_SVCALLACT_Pos 7U /*!< SCB SHCSR: SVCALLACT Position */ +#define SCB_SHCSR_SVCALLACT_Msk (1UL << SCB_SHCSR_SVCALLACT_Pos) /*!< SCB SHCSR: SVCALLACT Mask */ + +#define SCB_SHCSR_USGFAULTACT_Pos 3U /*!< SCB SHCSR: USGFAULTACT Position */ +#define SCB_SHCSR_USGFAULTACT_Msk (1UL << SCB_SHCSR_USGFAULTACT_Pos) /*!< SCB SHCSR: USGFAULTACT Mask */ + +#define SCB_SHCSR_BUSFAULTACT_Pos 1U /*!< SCB SHCSR: BUSFAULTACT Position */ +#define SCB_SHCSR_BUSFAULTACT_Msk (1UL << SCB_SHCSR_BUSFAULTACT_Pos) /*!< SCB SHCSR: BUSFAULTACT Mask */ + +#define SCB_SHCSR_MEMFAULTACT_Pos 0U /*!< SCB SHCSR: MEMFAULTACT Position */ +#define SCB_SHCSR_MEMFAULTACT_Msk (1UL /*<< SCB_SHCSR_MEMFAULTACT_Pos*/) /*!< SCB SHCSR: MEMFAULTACT Mask */ + +/* SCB Configurable Fault Status Register Definitions */ +#define SCB_CFSR_USGFAULTSR_Pos 16U /*!< SCB CFSR: Usage Fault Status Register Position */ +#define SCB_CFSR_USGFAULTSR_Msk (0xFFFFUL << SCB_CFSR_USGFAULTSR_Pos) /*!< SCB CFSR: Usage Fault Status Register Mask */ + +#define SCB_CFSR_BUSFAULTSR_Pos 8U /*!< SCB CFSR: Bus Fault Status Register Position */ +#define SCB_CFSR_BUSFAULTSR_Msk (0xFFUL << SCB_CFSR_BUSFAULTSR_Pos) /*!< SCB CFSR: Bus Fault Status Register Mask */ + +#define SCB_CFSR_MEMFAULTSR_Pos 0U /*!< SCB CFSR: Memory Manage Fault Status Register Position */ +#define SCB_CFSR_MEMFAULTSR_Msk (0xFFUL /*<< SCB_CFSR_MEMFAULTSR_Pos*/) /*!< SCB CFSR: Memory Manage Fault Status Register Mask */ + +/* MemManage Fault Status Register (part of SCB Configurable Fault Status Register) */ +#define SCB_CFSR_MMARVALID_Pos (SCB_CFSR_MEMFAULTSR_Pos + 7U) /*!< SCB CFSR (MMFSR): MMARVALID Position */ +#define SCB_CFSR_MMARVALID_Msk (1UL << SCB_CFSR_MMARVALID_Pos) /*!< SCB CFSR (MMFSR): MMARVALID Mask */ + +#define SCB_CFSR_MLSPERR_Pos (SCB_CFSR_MEMFAULTSR_Pos + 5U) /*!< SCB CFSR (MMFSR): MLSPERR Position */ +#define SCB_CFSR_MLSPERR_Msk (1UL << SCB_CFSR_MLSPERR_Pos) /*!< SCB CFSR (MMFSR): MLSPERR Mask */ + +#define SCB_CFSR_MSTKERR_Pos (SCB_CFSR_MEMFAULTSR_Pos + 4U) /*!< SCB CFSR (MMFSR): MSTKERR Position */ +#define SCB_CFSR_MSTKERR_Msk (1UL << SCB_CFSR_MSTKERR_Pos) /*!< SCB CFSR (MMFSR): MSTKERR Mask */ + +#define SCB_CFSR_MUNSTKERR_Pos (SCB_CFSR_MEMFAULTSR_Pos + 3U) /*!< SCB CFSR (MMFSR): MUNSTKERR Position */ +#define SCB_CFSR_MUNSTKERR_Msk (1UL << SCB_CFSR_MUNSTKERR_Pos) /*!< SCB CFSR (MMFSR): MUNSTKERR Mask */ + +#define SCB_CFSR_DACCVIOL_Pos (SCB_CFSR_MEMFAULTSR_Pos + 1U) /*!< SCB CFSR (MMFSR): DACCVIOL Position */ +#define SCB_CFSR_DACCVIOL_Msk (1UL << SCB_CFSR_DACCVIOL_Pos) /*!< SCB CFSR (MMFSR): DACCVIOL Mask */ + +#define SCB_CFSR_IACCVIOL_Pos (SCB_CFSR_MEMFAULTSR_Pos + 0U) /*!< SCB CFSR (MMFSR): IACCVIOL Position */ +#define SCB_CFSR_IACCVIOL_Msk (1UL /*<< SCB_CFSR_IACCVIOL_Pos*/) /*!< SCB CFSR (MMFSR): IACCVIOL Mask */ + +/* BusFault Status Register (part of SCB Configurable Fault Status Register) */ +#define SCB_CFSR_BFARVALID_Pos (SCB_CFSR_BUSFAULTSR_Pos + 7U) /*!< SCB CFSR (BFSR): BFARVALID Position */ +#define SCB_CFSR_BFARVALID_Msk (1UL << SCB_CFSR_BFARVALID_Pos) /*!< SCB CFSR (BFSR): BFARVALID Mask */ + +#define SCB_CFSR_LSPERR_Pos (SCB_CFSR_BUSFAULTSR_Pos + 5U) /*!< SCB CFSR (BFSR): LSPERR Position */ +#define SCB_CFSR_LSPERR_Msk (1UL << SCB_CFSR_LSPERR_Pos) /*!< SCB CFSR (BFSR): LSPERR Mask */ + +#define SCB_CFSR_STKERR_Pos (SCB_CFSR_BUSFAULTSR_Pos + 4U) /*!< SCB CFSR (BFSR): STKERR Position */ +#define SCB_CFSR_STKERR_Msk (1UL << SCB_CFSR_STKERR_Pos) /*!< SCB CFSR (BFSR): STKERR Mask */ + +#define SCB_CFSR_UNSTKERR_Pos (SCB_CFSR_BUSFAULTSR_Pos + 3U) /*!< SCB CFSR (BFSR): UNSTKERR Position */ +#define SCB_CFSR_UNSTKERR_Msk (1UL << SCB_CFSR_UNSTKERR_Pos) /*!< SCB CFSR (BFSR): UNSTKERR Mask */ + +#define SCB_CFSR_IMPRECISERR_Pos (SCB_CFSR_BUSFAULTSR_Pos + 2U) /*!< SCB CFSR (BFSR): IMPRECISERR Position */ +#define SCB_CFSR_IMPRECISERR_Msk (1UL << SCB_CFSR_IMPRECISERR_Pos) /*!< SCB CFSR (BFSR): IMPRECISERR Mask */ + +#define SCB_CFSR_PRECISERR_Pos (SCB_CFSR_BUSFAULTSR_Pos + 1U) /*!< SCB CFSR (BFSR): PRECISERR Position */ +#define SCB_CFSR_PRECISERR_Msk (1UL << SCB_CFSR_PRECISERR_Pos) /*!< SCB CFSR (BFSR): PRECISERR Mask */ + +#define SCB_CFSR_IBUSERR_Pos (SCB_CFSR_BUSFAULTSR_Pos + 0U) /*!< SCB CFSR (BFSR): IBUSERR Position */ +#define SCB_CFSR_IBUSERR_Msk (1UL << SCB_CFSR_IBUSERR_Pos) /*!< SCB CFSR (BFSR): IBUSERR Mask */ + +/* UsageFault Status Register (part of SCB Configurable Fault Status Register) */ +#define SCB_CFSR_DIVBYZERO_Pos (SCB_CFSR_USGFAULTSR_Pos + 9U) /*!< SCB CFSR (UFSR): DIVBYZERO Position */ +#define SCB_CFSR_DIVBYZERO_Msk (1UL << SCB_CFSR_DIVBYZERO_Pos) /*!< SCB CFSR (UFSR): DIVBYZERO Mask */ + +#define SCB_CFSR_UNALIGNED_Pos (SCB_CFSR_USGFAULTSR_Pos + 8U) /*!< SCB CFSR (UFSR): UNALIGNED Position */ +#define SCB_CFSR_UNALIGNED_Msk (1UL << SCB_CFSR_UNALIGNED_Pos) /*!< SCB CFSR (UFSR): UNALIGNED Mask */ + +#define SCB_CFSR_NOCP_Pos (SCB_CFSR_USGFAULTSR_Pos + 3U) /*!< SCB CFSR (UFSR): NOCP Position */ +#define SCB_CFSR_NOCP_Msk (1UL << SCB_CFSR_NOCP_Pos) /*!< SCB CFSR (UFSR): NOCP Mask */ + +#define SCB_CFSR_INVPC_Pos (SCB_CFSR_USGFAULTSR_Pos + 2U) /*!< SCB CFSR (UFSR): INVPC Position */ +#define SCB_CFSR_INVPC_Msk (1UL << SCB_CFSR_INVPC_Pos) /*!< SCB CFSR (UFSR): INVPC Mask */ + +#define SCB_CFSR_INVSTATE_Pos (SCB_CFSR_USGFAULTSR_Pos + 1U) /*!< SCB CFSR (UFSR): INVSTATE Position */ +#define SCB_CFSR_INVSTATE_Msk (1UL << SCB_CFSR_INVSTATE_Pos) /*!< SCB CFSR (UFSR): INVSTATE Mask */ + +#define SCB_CFSR_UNDEFINSTR_Pos (SCB_CFSR_USGFAULTSR_Pos + 0U) /*!< SCB CFSR (UFSR): UNDEFINSTR Position */ +#define SCB_CFSR_UNDEFINSTR_Msk (1UL << SCB_CFSR_UNDEFINSTR_Pos) /*!< SCB CFSR (UFSR): UNDEFINSTR Mask */ + +/* SCB Hard Fault Status Register Definitions */ +#define SCB_HFSR_DEBUGEVT_Pos 31U /*!< SCB HFSR: DEBUGEVT Position */ +#define SCB_HFSR_DEBUGEVT_Msk (1UL << SCB_HFSR_DEBUGEVT_Pos) /*!< SCB HFSR: DEBUGEVT Mask */ + +#define SCB_HFSR_FORCED_Pos 30U /*!< SCB HFSR: FORCED Position */ +#define SCB_HFSR_FORCED_Msk (1UL << SCB_HFSR_FORCED_Pos) /*!< SCB HFSR: FORCED Mask */ + +#define SCB_HFSR_VECTTBL_Pos 1U /*!< SCB HFSR: VECTTBL Position */ +#define SCB_HFSR_VECTTBL_Msk (1UL << SCB_HFSR_VECTTBL_Pos) /*!< SCB HFSR: VECTTBL Mask */ + +/* SCB Debug Fault Status Register Definitions */ +#define SCB_DFSR_EXTERNAL_Pos 4U /*!< SCB DFSR: EXTERNAL Position */ +#define SCB_DFSR_EXTERNAL_Msk (1UL << SCB_DFSR_EXTERNAL_Pos) /*!< SCB DFSR: EXTERNAL Mask */ + +#define SCB_DFSR_VCATCH_Pos 3U /*!< SCB DFSR: VCATCH Position */ +#define SCB_DFSR_VCATCH_Msk (1UL << SCB_DFSR_VCATCH_Pos) /*!< SCB DFSR: VCATCH Mask */ + +#define SCB_DFSR_DWTTRAP_Pos 2U /*!< SCB DFSR: DWTTRAP Position */ +#define SCB_DFSR_DWTTRAP_Msk (1UL << SCB_DFSR_DWTTRAP_Pos) /*!< SCB DFSR: DWTTRAP Mask */ + +#define SCB_DFSR_BKPT_Pos 1U /*!< SCB DFSR: BKPT Position */ +#define SCB_DFSR_BKPT_Msk (1UL << SCB_DFSR_BKPT_Pos) /*!< SCB DFSR: BKPT Mask */ + +#define SCB_DFSR_HALTED_Pos 0U /*!< SCB DFSR: HALTED Position */ +#define SCB_DFSR_HALTED_Msk (1UL /*<< SCB_DFSR_HALTED_Pos*/) /*!< SCB DFSR: HALTED Mask */ + +/*@} end of group CMSIS_SCB */ + + +/** + \ingroup CMSIS_core_register + \defgroup CMSIS_SCnSCB System Controls not in SCB (SCnSCB) + \brief Type definitions for the System Control and ID Register not in the SCB + @{ + */ + +/** + \brief Structure type to access the System Control and ID Register not in the SCB. + */ +typedef struct +{ + uint32_t RESERVED0[1U]; + __IM uint32_t ICTR; /*!< Offset: 0x004 (R/ ) Interrupt Controller Type Register */ + __IOM uint32_t ACTLR; /*!< Offset: 0x008 (R/W) Auxiliary Control Register */ +} SCnSCB_Type; + +/* Interrupt Controller Type Register Definitions */ +#define SCnSCB_ICTR_INTLINESNUM_Pos 0U /*!< ICTR: INTLINESNUM Position */ +#define SCnSCB_ICTR_INTLINESNUM_Msk (0xFUL /*<< SCnSCB_ICTR_INTLINESNUM_Pos*/) /*!< ICTR: INTLINESNUM Mask */ + +/* Auxiliary Control Register Definitions */ +#define SCnSCB_ACTLR_DISOOFP_Pos 9U /*!< ACTLR: DISOOFP Position */ +#define SCnSCB_ACTLR_DISOOFP_Msk (1UL << SCnSCB_ACTLR_DISOOFP_Pos) /*!< ACTLR: DISOOFP Mask */ + +#define SCnSCB_ACTLR_DISFPCA_Pos 8U /*!< ACTLR: DISFPCA Position */ +#define SCnSCB_ACTLR_DISFPCA_Msk (1UL << SCnSCB_ACTLR_DISFPCA_Pos) /*!< ACTLR: DISFPCA Mask */ + +#define SCnSCB_ACTLR_DISFOLD_Pos 2U /*!< ACTLR: DISFOLD Position */ +#define SCnSCB_ACTLR_DISFOLD_Msk (1UL << SCnSCB_ACTLR_DISFOLD_Pos) /*!< ACTLR: DISFOLD Mask */ + +#define SCnSCB_ACTLR_DISDEFWBUF_Pos 1U /*!< ACTLR: DISDEFWBUF Position */ +#define SCnSCB_ACTLR_DISDEFWBUF_Msk (1UL << SCnSCB_ACTLR_DISDEFWBUF_Pos) /*!< ACTLR: DISDEFWBUF Mask */ + +#define SCnSCB_ACTLR_DISMCYCINT_Pos 0U /*!< ACTLR: DISMCYCINT Position */ +#define SCnSCB_ACTLR_DISMCYCINT_Msk (1UL /*<< SCnSCB_ACTLR_DISMCYCINT_Pos*/) /*!< ACTLR: DISMCYCINT Mask */ + +/*@} end of group CMSIS_SCnotSCB */ + + +/** + \ingroup CMSIS_core_register + \defgroup CMSIS_SysTick System Tick Timer (SysTick) + \brief Type definitions for the System Timer Registers. + @{ + */ + +/** + \brief Structure type to access the System Timer (SysTick). + */ +typedef struct +{ + __IOM uint32_t CTRL; /*!< Offset: 0x000 (R/W) SysTick Control and Status Register */ + __IOM uint32_t LOAD; /*!< Offset: 0x004 (R/W) SysTick Reload Value Register */ + __IOM uint32_t VAL; /*!< Offset: 0x008 (R/W) SysTick Current Value Register */ + __IM uint32_t CALIB; /*!< Offset: 0x00C (R/ ) SysTick Calibration Register */ +} SysTick_Type; + +/* SysTick Control / Status Register Definitions */ +#define SysTick_CTRL_COUNTFLAG_Pos 16U /*!< SysTick CTRL: COUNTFLAG Position */ +#define SysTick_CTRL_COUNTFLAG_Msk (1UL << SysTick_CTRL_COUNTFLAG_Pos) /*!< SysTick CTRL: COUNTFLAG Mask */ + +#define SysTick_CTRL_CLKSOURCE_Pos 2U /*!< SysTick CTRL: CLKSOURCE Position */ +#define SysTick_CTRL_CLKSOURCE_Msk (1UL << SysTick_CTRL_CLKSOURCE_Pos) /*!< SysTick CTRL: CLKSOURCE Mask */ + +#define SysTick_CTRL_TICKINT_Pos 1U /*!< SysTick CTRL: TICKINT Position */ +#define SysTick_CTRL_TICKINT_Msk (1UL << SysTick_CTRL_TICKINT_Pos) /*!< SysTick CTRL: TICKINT Mask */ + +#define SysTick_CTRL_ENABLE_Pos 0U /*!< SysTick CTRL: ENABLE Position */ +#define SysTick_CTRL_ENABLE_Msk (1UL /*<< SysTick_CTRL_ENABLE_Pos*/) /*!< SysTick CTRL: ENABLE Mask */ + +/* SysTick Reload Register Definitions */ +#define SysTick_LOAD_RELOAD_Pos 0U /*!< SysTick LOAD: RELOAD Position */ +#define SysTick_LOAD_RELOAD_Msk (0xFFFFFFUL /*<< SysTick_LOAD_RELOAD_Pos*/) /*!< SysTick LOAD: RELOAD Mask */ + +/* SysTick Current Register Definitions */ +#define SysTick_VAL_CURRENT_Pos 0U /*!< SysTick VAL: CURRENT Position */ +#define SysTick_VAL_CURRENT_Msk (0xFFFFFFUL /*<< SysTick_VAL_CURRENT_Pos*/) /*!< SysTick VAL: CURRENT Mask */ + +/* SysTick Calibration Register Definitions */ +#define SysTick_CALIB_NOREF_Pos 31U /*!< SysTick CALIB: NOREF Position */ +#define SysTick_CALIB_NOREF_Msk (1UL << SysTick_CALIB_NOREF_Pos) /*!< SysTick CALIB: NOREF Mask */ + +#define SysTick_CALIB_SKEW_Pos 30U /*!< SysTick CALIB: SKEW Position */ +#define SysTick_CALIB_SKEW_Msk (1UL << SysTick_CALIB_SKEW_Pos) /*!< SysTick CALIB: SKEW Mask */ + +#define SysTick_CALIB_TENMS_Pos 0U /*!< SysTick CALIB: TENMS Position */ +#define SysTick_CALIB_TENMS_Msk (0xFFFFFFUL /*<< SysTick_CALIB_TENMS_Pos*/) /*!< SysTick CALIB: TENMS Mask */ + +/*@} end of group CMSIS_SysTick */ + + +/** + \ingroup CMSIS_core_register + \defgroup CMSIS_ITM Instrumentation Trace Macrocell (ITM) + \brief Type definitions for the Instrumentation Trace Macrocell (ITM) + @{ + */ + +/** + \brief Structure type to access the Instrumentation Trace Macrocell Register (ITM). + */ +typedef struct +{ + __OM union + { + __OM uint8_t u8; /*!< Offset: 0x000 ( /W) ITM Stimulus Port 8-bit */ + __OM uint16_t u16; /*!< Offset: 0x000 ( /W) ITM Stimulus Port 16-bit */ + __OM uint32_t u32; /*!< Offset: 0x000 ( /W) ITM Stimulus Port 32-bit */ + } PORT [32U]; /*!< Offset: 0x000 ( /W) ITM Stimulus Port Registers */ + uint32_t RESERVED0[864U]; + __IOM uint32_t TER; /*!< Offset: 0xE00 (R/W) ITM Trace Enable Register */ + uint32_t RESERVED1[15U]; + __IOM uint32_t TPR; /*!< Offset: 0xE40 (R/W) ITM Trace Privilege Register */ + uint32_t RESERVED2[15U]; + __IOM uint32_t TCR; /*!< Offset: 0xE80 (R/W) ITM Trace Control Register */ + uint32_t RESERVED3[32U]; + uint32_t RESERVED4[43U]; + __OM uint32_t LAR; /*!< Offset: 0xFB0 ( /W) ITM Lock Access Register */ + __IM uint32_t LSR; /*!< Offset: 0xFB4 (R/ ) ITM Lock Status Register */ + uint32_t RESERVED5[6U]; + __IM uint32_t PID4; /*!< Offset: 0xFD0 (R/ ) ITM Peripheral Identification Register #4 */ + __IM uint32_t PID5; /*!< Offset: 0xFD4 (R/ ) ITM Peripheral Identification Register #5 */ + __IM uint32_t PID6; /*!< Offset: 0xFD8 (R/ ) ITM Peripheral Identification Register #6 */ + __IM uint32_t PID7; /*!< Offset: 0xFDC (R/ ) ITM Peripheral Identification Register #7 */ + __IM uint32_t PID0; /*!< Offset: 0xFE0 (R/ ) ITM Peripheral Identification Register #0 */ + __IM uint32_t PID1; /*!< Offset: 0xFE4 (R/ ) ITM Peripheral Identification Register #1 */ + __IM uint32_t PID2; /*!< Offset: 0xFE8 (R/ ) ITM Peripheral Identification Register #2 */ + __IM uint32_t PID3; /*!< Offset: 0xFEC (R/ ) ITM Peripheral Identification Register #3 */ + __IM uint32_t CID0; /*!< Offset: 0xFF0 (R/ ) ITM Component Identification Register #0 */ + __IM uint32_t CID1; /*!< Offset: 0xFF4 (R/ ) ITM Component Identification Register #1 */ + __IM uint32_t CID2; /*!< Offset: 0xFF8 (R/ ) ITM Component Identification Register #2 */ + __IM uint32_t CID3; /*!< Offset: 0xFFC (R/ ) ITM Component Identification Register #3 */ +} ITM_Type; + +/* ITM Trace Privilege Register Definitions */ +#define ITM_TPR_PRIVMASK_Pos 0U /*!< ITM TPR: PRIVMASK Position */ +#define ITM_TPR_PRIVMASK_Msk (0xFFFFFFFFUL /*<< ITM_TPR_PRIVMASK_Pos*/) /*!< ITM TPR: PRIVMASK Mask */ + +/* ITM Trace Control Register Definitions */ +#define ITM_TCR_BUSY_Pos 23U /*!< ITM TCR: BUSY Position */ +#define ITM_TCR_BUSY_Msk (1UL << ITM_TCR_BUSY_Pos) /*!< ITM TCR: BUSY Mask */ + +#define ITM_TCR_TRACEBUSID_Pos 16U /*!< ITM TCR: ATBID Position */ +#define ITM_TCR_TRACEBUSID_Msk (0x7FUL << ITM_TCR_TRACEBUSID_Pos) /*!< ITM TCR: ATBID Mask */ + +#define ITM_TCR_GTSFREQ_Pos 10U /*!< ITM TCR: Global timestamp frequency Position */ +#define ITM_TCR_GTSFREQ_Msk (3UL << ITM_TCR_GTSFREQ_Pos) /*!< ITM TCR: Global timestamp frequency Mask */ + +#define ITM_TCR_TSPRESCALE_Pos 8U /*!< ITM TCR: TSPrescale Position */ +#define ITM_TCR_TSPRESCALE_Msk (3UL << ITM_TCR_TSPRESCALE_Pos) /*!< ITM TCR: TSPrescale Mask */ + +#define ITM_TCR_SWOENA_Pos 4U /*!< ITM TCR: SWOENA Position */ +#define ITM_TCR_SWOENA_Msk (1UL << ITM_TCR_SWOENA_Pos) /*!< ITM TCR: SWOENA Mask */ + +#define ITM_TCR_DWTENA_Pos 3U /*!< ITM TCR: DWTENA Position */ +#define ITM_TCR_DWTENA_Msk (1UL << ITM_TCR_DWTENA_Pos) /*!< ITM TCR: DWTENA Mask */ + +#define ITM_TCR_SYNCENA_Pos 2U /*!< ITM TCR: SYNCENA Position */ +#define ITM_TCR_SYNCENA_Msk (1UL << ITM_TCR_SYNCENA_Pos) /*!< ITM TCR: SYNCENA Mask */ + +#define ITM_TCR_TSENA_Pos 1U /*!< ITM TCR: TSENA Position */ +#define ITM_TCR_TSENA_Msk (1UL << ITM_TCR_TSENA_Pos) /*!< ITM TCR: TSENA Mask */ + +#define ITM_TCR_ITMENA_Pos 0U /*!< ITM TCR: ITM Enable bit Position */ +#define ITM_TCR_ITMENA_Msk (1UL /*<< ITM_TCR_ITMENA_Pos*/) /*!< ITM TCR: ITM Enable bit Mask */ + +/* ITM Lock Status Register Definitions */ +#define ITM_LSR_BYTEACC_Pos 2U /*!< ITM LSR: ByteAcc Position */ +#define ITM_LSR_BYTEACC_Msk (1UL << ITM_LSR_BYTEACC_Pos) /*!< ITM LSR: ByteAcc Mask */ + +#define ITM_LSR_ACCESS_Pos 1U /*!< ITM LSR: Access Position */ +#define ITM_LSR_ACCESS_Msk (1UL << ITM_LSR_ACCESS_Pos) /*!< ITM LSR: Access Mask */ + +#define ITM_LSR_PRESENT_Pos 0U /*!< ITM LSR: Present Position */ +#define ITM_LSR_PRESENT_Msk (1UL /*<< ITM_LSR_PRESENT_Pos*/) /*!< ITM LSR: Present Mask */ + +/*@}*/ /* end of group CMSIS_ITM */ + + +/** + \ingroup CMSIS_core_register + \defgroup CMSIS_DWT Data Watchpoint and Trace (DWT) + \brief Type definitions for the Data Watchpoint and Trace (DWT) + @{ + */ + +/** + \brief Structure type to access the Data Watchpoint and Trace Register (DWT). + */ +typedef struct +{ + __IOM uint32_t CTRL; /*!< Offset: 0x000 (R/W) Control Register */ + __IOM uint32_t CYCCNT; /*!< Offset: 0x004 (R/W) Cycle Count Register */ + __IOM uint32_t CPICNT; /*!< Offset: 0x008 (R/W) CPI Count Register */ + __IOM uint32_t EXCCNT; /*!< Offset: 0x00C (R/W) Exception Overhead Count Register */ + __IOM uint32_t SLEEPCNT; /*!< Offset: 0x010 (R/W) Sleep Count Register */ + __IOM uint32_t LSUCNT; /*!< Offset: 0x014 (R/W) LSU Count Register */ + __IOM uint32_t FOLDCNT; /*!< Offset: 0x018 (R/W) Folded-instruction Count Register */ + __IM uint32_t PCSR; /*!< Offset: 0x01C (R/ ) Program Counter Sample Register */ + __IOM uint32_t COMP0; /*!< Offset: 0x020 (R/W) Comparator Register 0 */ + __IOM uint32_t MASK0; /*!< Offset: 0x024 (R/W) Mask Register 0 */ + __IOM uint32_t FUNCTION0; /*!< Offset: 0x028 (R/W) Function Register 0 */ + uint32_t RESERVED0[1U]; + __IOM uint32_t COMP1; /*!< Offset: 0x030 (R/W) Comparator Register 1 */ + __IOM uint32_t MASK1; /*!< Offset: 0x034 (R/W) Mask Register 1 */ + __IOM uint32_t FUNCTION1; /*!< Offset: 0x038 (R/W) Function Register 1 */ + uint32_t RESERVED1[1U]; + __IOM uint32_t COMP2; /*!< Offset: 0x040 (R/W) Comparator Register 2 */ + __IOM uint32_t MASK2; /*!< Offset: 0x044 (R/W) Mask Register 2 */ + __IOM uint32_t FUNCTION2; /*!< Offset: 0x048 (R/W) Function Register 2 */ + uint32_t RESERVED2[1U]; + __IOM uint32_t COMP3; /*!< Offset: 0x050 (R/W) Comparator Register 3 */ + __IOM uint32_t MASK3; /*!< Offset: 0x054 (R/W) Mask Register 3 */ + __IOM uint32_t FUNCTION3; /*!< Offset: 0x058 (R/W) Function Register 3 */ +} DWT_Type; + +/* DWT Control Register Definitions */ +#define DWT_CTRL_NUMCOMP_Pos 28U /*!< DWT CTRL: NUMCOMP Position */ +#define DWT_CTRL_NUMCOMP_Msk (0xFUL << DWT_CTRL_NUMCOMP_Pos) /*!< DWT CTRL: NUMCOMP Mask */ + +#define DWT_CTRL_NOTRCPKT_Pos 27U /*!< DWT CTRL: NOTRCPKT Position */ +#define DWT_CTRL_NOTRCPKT_Msk (0x1UL << DWT_CTRL_NOTRCPKT_Pos) /*!< DWT CTRL: NOTRCPKT Mask */ + +#define DWT_CTRL_NOEXTTRIG_Pos 26U /*!< DWT CTRL: NOEXTTRIG Position */ +#define DWT_CTRL_NOEXTTRIG_Msk (0x1UL << DWT_CTRL_NOEXTTRIG_Pos) /*!< DWT CTRL: NOEXTTRIG Mask */ + +#define DWT_CTRL_NOCYCCNT_Pos 25U /*!< DWT CTRL: NOCYCCNT Position */ +#define DWT_CTRL_NOCYCCNT_Msk (0x1UL << DWT_CTRL_NOCYCCNT_Pos) /*!< DWT CTRL: NOCYCCNT Mask */ + +#define DWT_CTRL_NOPRFCNT_Pos 24U /*!< DWT CTRL: NOPRFCNT Position */ +#define DWT_CTRL_NOPRFCNT_Msk (0x1UL << DWT_CTRL_NOPRFCNT_Pos) /*!< DWT CTRL: NOPRFCNT Mask */ + +#define DWT_CTRL_CYCEVTENA_Pos 22U /*!< DWT CTRL: CYCEVTENA Position */ +#define DWT_CTRL_CYCEVTENA_Msk (0x1UL << DWT_CTRL_CYCEVTENA_Pos) /*!< DWT CTRL: CYCEVTENA Mask */ + +#define DWT_CTRL_FOLDEVTENA_Pos 21U /*!< DWT CTRL: FOLDEVTENA Position */ +#define DWT_CTRL_FOLDEVTENA_Msk (0x1UL << DWT_CTRL_FOLDEVTENA_Pos) /*!< DWT CTRL: FOLDEVTENA Mask */ + +#define DWT_CTRL_LSUEVTENA_Pos 20U /*!< DWT CTRL: LSUEVTENA Position */ +#define DWT_CTRL_LSUEVTENA_Msk (0x1UL << DWT_CTRL_LSUEVTENA_Pos) /*!< DWT CTRL: LSUEVTENA Mask */ + +#define DWT_CTRL_SLEEPEVTENA_Pos 19U /*!< DWT CTRL: SLEEPEVTENA Position */ +#define DWT_CTRL_SLEEPEVTENA_Msk (0x1UL << DWT_CTRL_SLEEPEVTENA_Pos) /*!< DWT CTRL: SLEEPEVTENA Mask */ + +#define DWT_CTRL_EXCEVTENA_Pos 18U /*!< DWT CTRL: EXCEVTENA Position */ +#define DWT_CTRL_EXCEVTENA_Msk (0x1UL << DWT_CTRL_EXCEVTENA_Pos) /*!< DWT CTRL: EXCEVTENA Mask */ + +#define DWT_CTRL_CPIEVTENA_Pos 17U /*!< DWT CTRL: CPIEVTENA Position */ +#define DWT_CTRL_CPIEVTENA_Msk (0x1UL << DWT_CTRL_CPIEVTENA_Pos) /*!< DWT CTRL: CPIEVTENA Mask */ + +#define DWT_CTRL_EXCTRCENA_Pos 16U /*!< DWT CTRL: EXCTRCENA Position */ +#define DWT_CTRL_EXCTRCENA_Msk (0x1UL << DWT_CTRL_EXCTRCENA_Pos) /*!< DWT CTRL: EXCTRCENA Mask */ + +#define DWT_CTRL_PCSAMPLENA_Pos 12U /*!< DWT CTRL: PCSAMPLENA Position */ +#define DWT_CTRL_PCSAMPLENA_Msk (0x1UL << DWT_CTRL_PCSAMPLENA_Pos) /*!< DWT CTRL: PCSAMPLENA Mask */ + +#define DWT_CTRL_SYNCTAP_Pos 10U /*!< DWT CTRL: SYNCTAP Position */ +#define DWT_CTRL_SYNCTAP_Msk (0x3UL << DWT_CTRL_SYNCTAP_Pos) /*!< DWT CTRL: SYNCTAP Mask */ + +#define DWT_CTRL_CYCTAP_Pos 9U /*!< DWT CTRL: CYCTAP Position */ +#define DWT_CTRL_CYCTAP_Msk (0x1UL << DWT_CTRL_CYCTAP_Pos) /*!< DWT CTRL: CYCTAP Mask */ + +#define DWT_CTRL_POSTINIT_Pos 5U /*!< DWT CTRL: POSTINIT Position */ +#define DWT_CTRL_POSTINIT_Msk (0xFUL << DWT_CTRL_POSTINIT_Pos) /*!< DWT CTRL: POSTINIT Mask */ + +#define DWT_CTRL_POSTPRESET_Pos 1U /*!< DWT CTRL: POSTPRESET Position */ +#define DWT_CTRL_POSTPRESET_Msk (0xFUL << DWT_CTRL_POSTPRESET_Pos) /*!< DWT CTRL: POSTPRESET Mask */ + +#define DWT_CTRL_CYCCNTENA_Pos 0U /*!< DWT CTRL: CYCCNTENA Position */ +#define DWT_CTRL_CYCCNTENA_Msk (0x1UL /*<< DWT_CTRL_CYCCNTENA_Pos*/) /*!< DWT CTRL: CYCCNTENA Mask */ + +/* DWT CPI Count Register Definitions */ +#define DWT_CPICNT_CPICNT_Pos 0U /*!< DWT CPICNT: CPICNT Position */ +#define DWT_CPICNT_CPICNT_Msk (0xFFUL /*<< DWT_CPICNT_CPICNT_Pos*/) /*!< DWT CPICNT: CPICNT Mask */ + +/* DWT Exception Overhead Count Register Definitions */ +#define DWT_EXCCNT_EXCCNT_Pos 0U /*!< DWT EXCCNT: EXCCNT Position */ +#define DWT_EXCCNT_EXCCNT_Msk (0xFFUL /*<< DWT_EXCCNT_EXCCNT_Pos*/) /*!< DWT EXCCNT: EXCCNT Mask */ + +/* DWT Sleep Count Register Definitions */ +#define DWT_SLEEPCNT_SLEEPCNT_Pos 0U /*!< DWT SLEEPCNT: SLEEPCNT Position */ +#define DWT_SLEEPCNT_SLEEPCNT_Msk (0xFFUL /*<< DWT_SLEEPCNT_SLEEPCNT_Pos*/) /*!< DWT SLEEPCNT: SLEEPCNT Mask */ + +/* DWT LSU Count Register Definitions */ +#define DWT_LSUCNT_LSUCNT_Pos 0U /*!< DWT LSUCNT: LSUCNT Position */ +#define DWT_LSUCNT_LSUCNT_Msk (0xFFUL /*<< DWT_LSUCNT_LSUCNT_Pos*/) /*!< DWT LSUCNT: LSUCNT Mask */ + +/* DWT Folded-instruction Count Register Definitions */ +#define DWT_FOLDCNT_FOLDCNT_Pos 0U /*!< DWT FOLDCNT: FOLDCNT Position */ +#define DWT_FOLDCNT_FOLDCNT_Msk (0xFFUL /*<< DWT_FOLDCNT_FOLDCNT_Pos*/) /*!< DWT FOLDCNT: FOLDCNT Mask */ + +/* DWT Comparator Mask Register Definitions */ +#define DWT_MASK_MASK_Pos 0U /*!< DWT MASK: MASK Position */ +#define DWT_MASK_MASK_Msk (0x1FUL /*<< DWT_MASK_MASK_Pos*/) /*!< DWT MASK: MASK Mask */ + +/* DWT Comparator Function Register Definitions */ +#define DWT_FUNCTION_MATCHED_Pos 24U /*!< DWT FUNCTION: MATCHED Position */ +#define DWT_FUNCTION_MATCHED_Msk (0x1UL << DWT_FUNCTION_MATCHED_Pos) /*!< DWT FUNCTION: MATCHED Mask */ + +#define DWT_FUNCTION_DATAVADDR1_Pos 16U /*!< DWT FUNCTION: DATAVADDR1 Position */ +#define DWT_FUNCTION_DATAVADDR1_Msk (0xFUL << DWT_FUNCTION_DATAVADDR1_Pos) /*!< DWT FUNCTION: DATAVADDR1 Mask */ + +#define DWT_FUNCTION_DATAVADDR0_Pos 12U /*!< DWT FUNCTION: DATAVADDR0 Position */ +#define DWT_FUNCTION_DATAVADDR0_Msk (0xFUL << DWT_FUNCTION_DATAVADDR0_Pos) /*!< DWT FUNCTION: DATAVADDR0 Mask */ + +#define DWT_FUNCTION_DATAVSIZE_Pos 10U /*!< DWT FUNCTION: DATAVSIZE Position */ +#define DWT_FUNCTION_DATAVSIZE_Msk (0x3UL << DWT_FUNCTION_DATAVSIZE_Pos) /*!< DWT FUNCTION: DATAVSIZE Mask */ + +#define DWT_FUNCTION_LNK1ENA_Pos 9U /*!< DWT FUNCTION: LNK1ENA Position */ +#define DWT_FUNCTION_LNK1ENA_Msk (0x1UL << DWT_FUNCTION_LNK1ENA_Pos) /*!< DWT FUNCTION: LNK1ENA Mask */ + +#define DWT_FUNCTION_DATAVMATCH_Pos 8U /*!< DWT FUNCTION: DATAVMATCH Position */ +#define DWT_FUNCTION_DATAVMATCH_Msk (0x1UL << DWT_FUNCTION_DATAVMATCH_Pos) /*!< DWT FUNCTION: DATAVMATCH Mask */ + +#define DWT_FUNCTION_CYCMATCH_Pos 7U /*!< DWT FUNCTION: CYCMATCH Position */ +#define DWT_FUNCTION_CYCMATCH_Msk (0x1UL << DWT_FUNCTION_CYCMATCH_Pos) /*!< DWT FUNCTION: CYCMATCH Mask */ + +#define DWT_FUNCTION_EMITRANGE_Pos 5U /*!< DWT FUNCTION: EMITRANGE Position */ +#define DWT_FUNCTION_EMITRANGE_Msk (0x1UL << DWT_FUNCTION_EMITRANGE_Pos) /*!< DWT FUNCTION: EMITRANGE Mask */ + +#define DWT_FUNCTION_FUNCTION_Pos 0U /*!< DWT FUNCTION: FUNCTION Position */ +#define DWT_FUNCTION_FUNCTION_Msk (0xFUL /*<< DWT_FUNCTION_FUNCTION_Pos*/) /*!< DWT FUNCTION: FUNCTION Mask */ + +/*@}*/ /* end of group CMSIS_DWT */ + + +/** + \ingroup CMSIS_core_register + \defgroup CMSIS_TPI Trace Port Interface (TPI) + \brief Type definitions for the Trace Port Interface (TPI) + @{ + */ + +/** + \brief Structure type to access the Trace Port Interface Register (TPI). + */ +typedef struct +{ + __IM uint32_t SSPSR; /*!< Offset: 0x000 (R/ ) Supported Parallel Port Size Register */ + __IOM uint32_t CSPSR; /*!< Offset: 0x004 (R/W) Current Parallel Port Size Register */ + uint32_t RESERVED0[2U]; + __IOM uint32_t ACPR; /*!< Offset: 0x010 (R/W) Asynchronous Clock Prescaler Register */ + uint32_t RESERVED1[55U]; + __IOM uint32_t SPPR; /*!< Offset: 0x0F0 (R/W) Selected Pin Protocol Register */ + uint32_t RESERVED2[131U]; + __IM uint32_t FFSR; /*!< Offset: 0x300 (R/ ) Formatter and Flush Status Register */ + __IOM uint32_t FFCR; /*!< Offset: 0x304 (R/W) Formatter and Flush Control Register */ + __IM uint32_t FSCR; /*!< Offset: 0x308 (R/ ) Formatter Synchronization Counter Register */ + uint32_t RESERVED3[759U]; + __IM uint32_t TRIGGER; /*!< Offset: 0xEE8 (R/ ) TRIGGER Register */ + __IM uint32_t FIFO0; /*!< Offset: 0xEEC (R/ ) Integration ETM Data */ + __IM uint32_t ITATBCTR2; /*!< Offset: 0xEF0 (R/ ) ITATBCTR2 */ + uint32_t RESERVED4[1U]; + __IM uint32_t ITATBCTR0; /*!< Offset: 0xEF8 (R/ ) ITATBCTR0 */ + __IM uint32_t FIFO1; /*!< Offset: 0xEFC (R/ ) Integration ITM Data */ + __IOM uint32_t ITCTRL; /*!< Offset: 0xF00 (R/W) Integration Mode Control */ + uint32_t RESERVED5[39U]; + __IOM uint32_t CLAIMSET; /*!< Offset: 0xFA0 (R/W) Claim tag set */ + __IOM uint32_t CLAIMCLR; /*!< Offset: 0xFA4 (R/W) Claim tag clear */ + uint32_t RESERVED7[8U]; + __IM uint32_t DEVID; /*!< Offset: 0xFC8 (R/ ) TPIU_DEVID */ + __IM uint32_t DEVTYPE; /*!< Offset: 0xFCC (R/ ) TPIU_DEVTYPE */ +} TPI_Type; + +/* TPI Asynchronous Clock Prescaler Register Definitions */ +#define TPI_ACPR_PRESCALER_Pos 0U /*!< TPI ACPR: PRESCALER Position */ +#define TPI_ACPR_PRESCALER_Msk (0x1FFFUL /*<< TPI_ACPR_PRESCALER_Pos*/) /*!< TPI ACPR: PRESCALER Mask */ + +/* TPI Selected Pin Protocol Register Definitions */ +#define TPI_SPPR_TXMODE_Pos 0U /*!< TPI SPPR: TXMODE Position */ +#define TPI_SPPR_TXMODE_Msk (0x3UL /*<< TPI_SPPR_TXMODE_Pos*/) /*!< TPI SPPR: TXMODE Mask */ + +/* TPI Formatter and Flush Status Register Definitions */ +#define TPI_FFSR_FtNonStop_Pos 3U /*!< TPI FFSR: FtNonStop Position */ +#define TPI_FFSR_FtNonStop_Msk (0x1UL << TPI_FFSR_FtNonStop_Pos) /*!< TPI FFSR: FtNonStop Mask */ + +#define TPI_FFSR_TCPresent_Pos 2U /*!< TPI FFSR: TCPresent Position */ +#define TPI_FFSR_TCPresent_Msk (0x1UL << TPI_FFSR_TCPresent_Pos) /*!< TPI FFSR: TCPresent Mask */ + +#define TPI_FFSR_FtStopped_Pos 1U /*!< TPI FFSR: FtStopped Position */ +#define TPI_FFSR_FtStopped_Msk (0x1UL << TPI_FFSR_FtStopped_Pos) /*!< TPI FFSR: FtStopped Mask */ + +#define TPI_FFSR_FlInProg_Pos 0U /*!< TPI FFSR: FlInProg Position */ +#define TPI_FFSR_FlInProg_Msk (0x1UL /*<< TPI_FFSR_FlInProg_Pos*/) /*!< TPI FFSR: FlInProg Mask */ + +/* TPI Formatter and Flush Control Register Definitions */ +#define TPI_FFCR_TrigIn_Pos 8U /*!< TPI FFCR: TrigIn Position */ +#define TPI_FFCR_TrigIn_Msk (0x1UL << TPI_FFCR_TrigIn_Pos) /*!< TPI FFCR: TrigIn Mask */ + +#define TPI_FFCR_EnFCont_Pos 1U /*!< TPI FFCR: EnFCont Position */ +#define TPI_FFCR_EnFCont_Msk (0x1UL << TPI_FFCR_EnFCont_Pos) /*!< TPI FFCR: EnFCont Mask */ + +/* TPI TRIGGER Register Definitions */ +#define TPI_TRIGGER_TRIGGER_Pos 0U /*!< TPI TRIGGER: TRIGGER Position */ +#define TPI_TRIGGER_TRIGGER_Msk (0x1UL /*<< TPI_TRIGGER_TRIGGER_Pos*/) /*!< TPI TRIGGER: TRIGGER Mask */ + +/* TPI Integration ETM Data Register Definitions (FIFO0) */ +#define TPI_FIFO0_ITM_ATVALID_Pos 29U /*!< TPI FIFO0: ITM_ATVALID Position */ +#define TPI_FIFO0_ITM_ATVALID_Msk (0x1UL << TPI_FIFO0_ITM_ATVALID_Pos) /*!< TPI FIFO0: ITM_ATVALID Mask */ + +#define TPI_FIFO0_ITM_bytecount_Pos 27U /*!< TPI FIFO0: ITM_bytecount Position */ +#define TPI_FIFO0_ITM_bytecount_Msk (0x3UL << TPI_FIFO0_ITM_bytecount_Pos) /*!< TPI FIFO0: ITM_bytecount Mask */ + +#define TPI_FIFO0_ETM_ATVALID_Pos 26U /*!< TPI FIFO0: ETM_ATVALID Position */ +#define TPI_FIFO0_ETM_ATVALID_Msk (0x1UL << TPI_FIFO0_ETM_ATVALID_Pos) /*!< TPI FIFO0: ETM_ATVALID Mask */ + +#define TPI_FIFO0_ETM_bytecount_Pos 24U /*!< TPI FIFO0: ETM_bytecount Position */ +#define TPI_FIFO0_ETM_bytecount_Msk (0x3UL << TPI_FIFO0_ETM_bytecount_Pos) /*!< TPI FIFO0: ETM_bytecount Mask */ + +#define TPI_FIFO0_ETM2_Pos 16U /*!< TPI FIFO0: ETM2 Position */ +#define TPI_FIFO0_ETM2_Msk (0xFFUL << TPI_FIFO0_ETM2_Pos) /*!< TPI FIFO0: ETM2 Mask */ + +#define TPI_FIFO0_ETM1_Pos 8U /*!< TPI FIFO0: ETM1 Position */ +#define TPI_FIFO0_ETM1_Msk (0xFFUL << TPI_FIFO0_ETM1_Pos) /*!< TPI FIFO0: ETM1 Mask */ + +#define TPI_FIFO0_ETM0_Pos 0U /*!< TPI FIFO0: ETM0 Position */ +#define TPI_FIFO0_ETM0_Msk (0xFFUL /*<< TPI_FIFO0_ETM0_Pos*/) /*!< TPI FIFO0: ETM0 Mask */ + +/* TPI ITATBCTR2 Register Definitions */ +#define TPI_ITATBCTR2_ATREADY2_Pos 0U /*!< TPI ITATBCTR2: ATREADY2 Position */ +#define TPI_ITATBCTR2_ATREADY2_Msk (0x1UL /*<< TPI_ITATBCTR2_ATREADY2_Pos*/) /*!< TPI ITATBCTR2: ATREADY2 Mask */ + +#define TPI_ITATBCTR2_ATREADY1_Pos 0U /*!< TPI ITATBCTR2: ATREADY1 Position */ +#define TPI_ITATBCTR2_ATREADY1_Msk (0x1UL /*<< TPI_ITATBCTR2_ATREADY1_Pos*/) /*!< TPI ITATBCTR2: ATREADY1 Mask */ + +/* TPI Integration ITM Data Register Definitions (FIFO1) */ +#define TPI_FIFO1_ITM_ATVALID_Pos 29U /*!< TPI FIFO1: ITM_ATVALID Position */ +#define TPI_FIFO1_ITM_ATVALID_Msk (0x1UL << TPI_FIFO1_ITM_ATVALID_Pos) /*!< TPI FIFO1: ITM_ATVALID Mask */ + +#define TPI_FIFO1_ITM_bytecount_Pos 27U /*!< TPI FIFO1: ITM_bytecount Position */ +#define TPI_FIFO1_ITM_bytecount_Msk (0x3UL << TPI_FIFO1_ITM_bytecount_Pos) /*!< TPI FIFO1: ITM_bytecount Mask */ + +#define TPI_FIFO1_ETM_ATVALID_Pos 26U /*!< TPI FIFO1: ETM_ATVALID Position */ +#define TPI_FIFO1_ETM_ATVALID_Msk (0x1UL << TPI_FIFO1_ETM_ATVALID_Pos) /*!< TPI FIFO1: ETM_ATVALID Mask */ + +#define TPI_FIFO1_ETM_bytecount_Pos 24U /*!< TPI FIFO1: ETM_bytecount Position */ +#define TPI_FIFO1_ETM_bytecount_Msk (0x3UL << TPI_FIFO1_ETM_bytecount_Pos) /*!< TPI FIFO1: ETM_bytecount Mask */ + +#define TPI_FIFO1_ITM2_Pos 16U /*!< TPI FIFO1: ITM2 Position */ +#define TPI_FIFO1_ITM2_Msk (0xFFUL << TPI_FIFO1_ITM2_Pos) /*!< TPI FIFO1: ITM2 Mask */ + +#define TPI_FIFO1_ITM1_Pos 8U /*!< TPI FIFO1: ITM1 Position */ +#define TPI_FIFO1_ITM1_Msk (0xFFUL << TPI_FIFO1_ITM1_Pos) /*!< TPI FIFO1: ITM1 Mask */ + +#define TPI_FIFO1_ITM0_Pos 0U /*!< TPI FIFO1: ITM0 Position */ +#define TPI_FIFO1_ITM0_Msk (0xFFUL /*<< TPI_FIFO1_ITM0_Pos*/) /*!< TPI FIFO1: ITM0 Mask */ + +/* TPI ITATBCTR0 Register Definitions */ +#define TPI_ITATBCTR0_ATREADY2_Pos 0U /*!< TPI ITATBCTR0: ATREADY2 Position */ +#define TPI_ITATBCTR0_ATREADY2_Msk (0x1UL /*<< TPI_ITATBCTR0_ATREADY2_Pos*/) /*!< TPI ITATBCTR0: ATREADY2 Mask */ + +#define TPI_ITATBCTR0_ATREADY1_Pos 0U /*!< TPI ITATBCTR0: ATREADY1 Position */ +#define TPI_ITATBCTR0_ATREADY1_Msk (0x1UL /*<< TPI_ITATBCTR0_ATREADY1_Pos*/) /*!< TPI ITATBCTR0: ATREADY1 Mask */ + +/* TPI Integration Mode Control Register Definitions */ +#define TPI_ITCTRL_Mode_Pos 0U /*!< TPI ITCTRL: Mode Position */ +#define TPI_ITCTRL_Mode_Msk (0x3UL /*<< TPI_ITCTRL_Mode_Pos*/) /*!< TPI ITCTRL: Mode Mask */ + +/* TPI DEVID Register Definitions */ +#define TPI_DEVID_NRZVALID_Pos 11U /*!< TPI DEVID: NRZVALID Position */ +#define TPI_DEVID_NRZVALID_Msk (0x1UL << TPI_DEVID_NRZVALID_Pos) /*!< TPI DEVID: NRZVALID Mask */ + +#define TPI_DEVID_MANCVALID_Pos 10U /*!< TPI DEVID: MANCVALID Position */ +#define TPI_DEVID_MANCVALID_Msk (0x1UL << TPI_DEVID_MANCVALID_Pos) /*!< TPI DEVID: MANCVALID Mask */ + +#define TPI_DEVID_PTINVALID_Pos 9U /*!< TPI DEVID: PTINVALID Position */ +#define TPI_DEVID_PTINVALID_Msk (0x1UL << TPI_DEVID_PTINVALID_Pos) /*!< TPI DEVID: PTINVALID Mask */ + +#define TPI_DEVID_MinBufSz_Pos 6U /*!< TPI DEVID: MinBufSz Position */ +#define TPI_DEVID_MinBufSz_Msk (0x7UL << TPI_DEVID_MinBufSz_Pos) /*!< TPI DEVID: MinBufSz Mask */ + +#define TPI_DEVID_AsynClkIn_Pos 5U /*!< TPI DEVID: AsynClkIn Position */ +#define TPI_DEVID_AsynClkIn_Msk (0x1UL << TPI_DEVID_AsynClkIn_Pos) /*!< TPI DEVID: AsynClkIn Mask */ + +#define TPI_DEVID_NrTraceInput_Pos 0U /*!< TPI DEVID: NrTraceInput Position */ +#define TPI_DEVID_NrTraceInput_Msk (0x1FUL /*<< TPI_DEVID_NrTraceInput_Pos*/) /*!< TPI DEVID: NrTraceInput Mask */ + +/* TPI DEVTYPE Register Definitions */ +#define TPI_DEVTYPE_SubType_Pos 4U /*!< TPI DEVTYPE: SubType Position */ +#define TPI_DEVTYPE_SubType_Msk (0xFUL /*<< TPI_DEVTYPE_SubType_Pos*/) /*!< TPI DEVTYPE: SubType Mask */ + +#define TPI_DEVTYPE_MajorType_Pos 0U /*!< TPI DEVTYPE: MajorType Position */ +#define TPI_DEVTYPE_MajorType_Msk (0xFUL << TPI_DEVTYPE_MajorType_Pos) /*!< TPI DEVTYPE: MajorType Mask */ + +/*@}*/ /* end of group CMSIS_TPI */ + + +#if defined (__MPU_PRESENT) && (__MPU_PRESENT == 1U) +/** + \ingroup CMSIS_core_register + \defgroup CMSIS_MPU Memory Protection Unit (MPU) + \brief Type definitions for the Memory Protection Unit (MPU) + @{ + */ + +/** + \brief Structure type to access the Memory Protection Unit (MPU). + */ +typedef struct +{ + __IM uint32_t TYPE; /*!< Offset: 0x000 (R/ ) MPU Type Register */ + __IOM uint32_t CTRL; /*!< Offset: 0x004 (R/W) MPU Control Register */ + __IOM uint32_t RNR; /*!< Offset: 0x008 (R/W) MPU Region RNRber Register */ + __IOM uint32_t RBAR; /*!< Offset: 0x00C (R/W) MPU Region Base Address Register */ + __IOM uint32_t RASR; /*!< Offset: 0x010 (R/W) MPU Region Attribute and Size Register */ + __IOM uint32_t RBAR_A1; /*!< Offset: 0x014 (R/W) MPU Alias 1 Region Base Address Register */ + __IOM uint32_t RASR_A1; /*!< Offset: 0x018 (R/W) MPU Alias 1 Region Attribute and Size Register */ + __IOM uint32_t RBAR_A2; /*!< Offset: 0x01C (R/W) MPU Alias 2 Region Base Address Register */ + __IOM uint32_t RASR_A2; /*!< Offset: 0x020 (R/W) MPU Alias 2 Region Attribute and Size Register */ + __IOM uint32_t RBAR_A3; /*!< Offset: 0x024 (R/W) MPU Alias 3 Region Base Address Register */ + __IOM uint32_t RASR_A3; /*!< Offset: 0x028 (R/W) MPU Alias 3 Region Attribute and Size Register */ +} MPU_Type; + +#define MPU_TYPE_RALIASES 4U + +/* MPU Type Register Definitions */ +#define MPU_TYPE_IREGION_Pos 16U /*!< MPU TYPE: IREGION Position */ +#define MPU_TYPE_IREGION_Msk (0xFFUL << MPU_TYPE_IREGION_Pos) /*!< MPU TYPE: IREGION Mask */ + +#define MPU_TYPE_DREGION_Pos 8U /*!< MPU TYPE: DREGION Position */ +#define MPU_TYPE_DREGION_Msk (0xFFUL << MPU_TYPE_DREGION_Pos) /*!< MPU TYPE: DREGION Mask */ + +#define MPU_TYPE_SEPARATE_Pos 0U /*!< MPU TYPE: SEPARATE Position */ +#define MPU_TYPE_SEPARATE_Msk (1UL /*<< MPU_TYPE_SEPARATE_Pos*/) /*!< MPU TYPE: SEPARATE Mask */ + +/* MPU Control Register Definitions */ +#define MPU_CTRL_PRIVDEFENA_Pos 2U /*!< MPU CTRL: PRIVDEFENA Position */ +#define MPU_CTRL_PRIVDEFENA_Msk (1UL << MPU_CTRL_PRIVDEFENA_Pos) /*!< MPU CTRL: PRIVDEFENA Mask */ + +#define MPU_CTRL_HFNMIENA_Pos 1U /*!< MPU CTRL: HFNMIENA Position */ +#define MPU_CTRL_HFNMIENA_Msk (1UL << MPU_CTRL_HFNMIENA_Pos) /*!< MPU CTRL: HFNMIENA Mask */ + +#define MPU_CTRL_ENABLE_Pos 0U /*!< MPU CTRL: ENABLE Position */ +#define MPU_CTRL_ENABLE_Msk (1UL /*<< MPU_CTRL_ENABLE_Pos*/) /*!< MPU CTRL: ENABLE Mask */ + +/* MPU Region Number Register Definitions */ +#define MPU_RNR_REGION_Pos 0U /*!< MPU RNR: REGION Position */ +#define MPU_RNR_REGION_Msk (0xFFUL /*<< MPU_RNR_REGION_Pos*/) /*!< MPU RNR: REGION Mask */ + +/* MPU Region Base Address Register Definitions */ +#define MPU_RBAR_ADDR_Pos 5U /*!< MPU RBAR: ADDR Position */ +#define MPU_RBAR_ADDR_Msk (0x7FFFFFFUL << MPU_RBAR_ADDR_Pos) /*!< MPU RBAR: ADDR Mask */ + +#define MPU_RBAR_VALID_Pos 4U /*!< MPU RBAR: VALID Position */ +#define MPU_RBAR_VALID_Msk (1UL << MPU_RBAR_VALID_Pos) /*!< MPU RBAR: VALID Mask */ + +#define MPU_RBAR_REGION_Pos 0U /*!< MPU RBAR: REGION Position */ +#define MPU_RBAR_REGION_Msk (0xFUL /*<< MPU_RBAR_REGION_Pos*/) /*!< MPU RBAR: REGION Mask */ + +/* MPU Region Attribute and Size Register Definitions */ +#define MPU_RASR_ATTRS_Pos 16U /*!< MPU RASR: MPU Region Attribute field Position */ +#define MPU_RASR_ATTRS_Msk (0xFFFFUL << MPU_RASR_ATTRS_Pos) /*!< MPU RASR: MPU Region Attribute field Mask */ + +#define MPU_RASR_XN_Pos 28U /*!< MPU RASR: ATTRS.XN Position */ +#define MPU_RASR_XN_Msk (1UL << MPU_RASR_XN_Pos) /*!< MPU RASR: ATTRS.XN Mask */ + +#define MPU_RASR_AP_Pos 24U /*!< MPU RASR: ATTRS.AP Position */ +#define MPU_RASR_AP_Msk (0x7UL << MPU_RASR_AP_Pos) /*!< MPU RASR: ATTRS.AP Mask */ + +#define MPU_RASR_TEX_Pos 19U /*!< MPU RASR: ATTRS.TEX Position */ +#define MPU_RASR_TEX_Msk (0x7UL << MPU_RASR_TEX_Pos) /*!< MPU RASR: ATTRS.TEX Mask */ + +#define MPU_RASR_S_Pos 18U /*!< MPU RASR: ATTRS.S Position */ +#define MPU_RASR_S_Msk (1UL << MPU_RASR_S_Pos) /*!< MPU RASR: ATTRS.S Mask */ + +#define MPU_RASR_C_Pos 17U /*!< MPU RASR: ATTRS.C Position */ +#define MPU_RASR_C_Msk (1UL << MPU_RASR_C_Pos) /*!< MPU RASR: ATTRS.C Mask */ + +#define MPU_RASR_B_Pos 16U /*!< MPU RASR: ATTRS.B Position */ +#define MPU_RASR_B_Msk (1UL << MPU_RASR_B_Pos) /*!< MPU RASR: ATTRS.B Mask */ + +#define MPU_RASR_SRD_Pos 8U /*!< MPU RASR: Sub-Region Disable Position */ +#define MPU_RASR_SRD_Msk (0xFFUL << MPU_RASR_SRD_Pos) /*!< MPU RASR: Sub-Region Disable Mask */ + +#define MPU_RASR_SIZE_Pos 1U /*!< MPU RASR: Region Size Field Position */ +#define MPU_RASR_SIZE_Msk (0x1FUL << MPU_RASR_SIZE_Pos) /*!< MPU RASR: Region Size Field Mask */ + +#define MPU_RASR_ENABLE_Pos 0U /*!< MPU RASR: Region enable bit Position */ +#define MPU_RASR_ENABLE_Msk (1UL /*<< MPU_RASR_ENABLE_Pos*/) /*!< MPU RASR: Region enable bit Disable Mask */ + +/*@} end of group CMSIS_MPU */ +#endif /* defined (__MPU_PRESENT) && (__MPU_PRESENT == 1U) */ + + +/** + \ingroup CMSIS_core_register + \defgroup CMSIS_FPU Floating Point Unit (FPU) + \brief Type definitions for the Floating Point Unit (FPU) + @{ + */ + +/** + \brief Structure type to access the Floating Point Unit (FPU). + */ +typedef struct +{ + uint32_t RESERVED0[1U]; + __IOM uint32_t FPCCR; /*!< Offset: 0x004 (R/W) Floating-Point Context Control Register */ + __IOM uint32_t FPCAR; /*!< Offset: 0x008 (R/W) Floating-Point Context Address Register */ + __IOM uint32_t FPDSCR; /*!< Offset: 0x00C (R/W) Floating-Point Default Status Control Register */ + __IM uint32_t MVFR0; /*!< Offset: 0x010 (R/ ) Media and FP Feature Register 0 */ + __IM uint32_t MVFR1; /*!< Offset: 0x014 (R/ ) Media and FP Feature Register 1 */ + __IM uint32_t MVFR2; /*!< Offset: 0x018 (R/ ) Media and FP Feature Register 2 */ +} FPU_Type; + +/* Floating-Point Context Control Register Definitions */ +#define FPU_FPCCR_ASPEN_Pos 31U /*!< FPCCR: ASPEN bit Position */ +#define FPU_FPCCR_ASPEN_Msk (1UL << FPU_FPCCR_ASPEN_Pos) /*!< FPCCR: ASPEN bit Mask */ + +#define FPU_FPCCR_LSPEN_Pos 30U /*!< FPCCR: LSPEN Position */ +#define FPU_FPCCR_LSPEN_Msk (1UL << FPU_FPCCR_LSPEN_Pos) /*!< FPCCR: LSPEN bit Mask */ + +#define FPU_FPCCR_MONRDY_Pos 8U /*!< FPCCR: MONRDY Position */ +#define FPU_FPCCR_MONRDY_Msk (1UL << FPU_FPCCR_MONRDY_Pos) /*!< FPCCR: MONRDY bit Mask */ + +#define FPU_FPCCR_BFRDY_Pos 6U /*!< FPCCR: BFRDY Position */ +#define FPU_FPCCR_BFRDY_Msk (1UL << FPU_FPCCR_BFRDY_Pos) /*!< FPCCR: BFRDY bit Mask */ + +#define FPU_FPCCR_MMRDY_Pos 5U /*!< FPCCR: MMRDY Position */ +#define FPU_FPCCR_MMRDY_Msk (1UL << FPU_FPCCR_MMRDY_Pos) /*!< FPCCR: MMRDY bit Mask */ + +#define FPU_FPCCR_HFRDY_Pos 4U /*!< FPCCR: HFRDY Position */ +#define FPU_FPCCR_HFRDY_Msk (1UL << FPU_FPCCR_HFRDY_Pos) /*!< FPCCR: HFRDY bit Mask */ + +#define FPU_FPCCR_THREAD_Pos 3U /*!< FPCCR: processor mode bit Position */ +#define FPU_FPCCR_THREAD_Msk (1UL << FPU_FPCCR_THREAD_Pos) /*!< FPCCR: processor mode active bit Mask */ + +#define FPU_FPCCR_USER_Pos 1U /*!< FPCCR: privilege level bit Position */ +#define FPU_FPCCR_USER_Msk (1UL << FPU_FPCCR_USER_Pos) /*!< FPCCR: privilege level bit Mask */ + +#define FPU_FPCCR_LSPACT_Pos 0U /*!< FPCCR: Lazy state preservation active bit Position */ +#define FPU_FPCCR_LSPACT_Msk (1UL /*<< FPU_FPCCR_LSPACT_Pos*/) /*!< FPCCR: Lazy state preservation active bit Mask */ + +/* Floating-Point Context Address Register Definitions */ +#define FPU_FPCAR_ADDRESS_Pos 3U /*!< FPCAR: ADDRESS bit Position */ +#define FPU_FPCAR_ADDRESS_Msk (0x1FFFFFFFUL << FPU_FPCAR_ADDRESS_Pos) /*!< FPCAR: ADDRESS bit Mask */ + +/* Floating-Point Default Status Control Register Definitions */ +#define FPU_FPDSCR_AHP_Pos 26U /*!< FPDSCR: AHP bit Position */ +#define FPU_FPDSCR_AHP_Msk (1UL << FPU_FPDSCR_AHP_Pos) /*!< FPDSCR: AHP bit Mask */ + +#define FPU_FPDSCR_DN_Pos 25U /*!< FPDSCR: DN bit Position */ +#define FPU_FPDSCR_DN_Msk (1UL << FPU_FPDSCR_DN_Pos) /*!< FPDSCR: DN bit Mask */ + +#define FPU_FPDSCR_FZ_Pos 24U /*!< FPDSCR: FZ bit Position */ +#define FPU_FPDSCR_FZ_Msk (1UL << FPU_FPDSCR_FZ_Pos) /*!< FPDSCR: FZ bit Mask */ + +#define FPU_FPDSCR_RMode_Pos 22U /*!< FPDSCR: RMode bit Position */ +#define FPU_FPDSCR_RMode_Msk (3UL << FPU_FPDSCR_RMode_Pos) /*!< FPDSCR: RMode bit Mask */ + +/* Media and FP Feature Register 0 Definitions */ +#define FPU_MVFR0_FP_rounding_modes_Pos 28U /*!< MVFR0: FP rounding modes bits Position */ +#define FPU_MVFR0_FP_rounding_modes_Msk (0xFUL << FPU_MVFR0_FP_rounding_modes_Pos) /*!< MVFR0: FP rounding modes bits Mask */ + +#define FPU_MVFR0_Short_vectors_Pos 24U /*!< MVFR0: Short vectors bits Position */ +#define FPU_MVFR0_Short_vectors_Msk (0xFUL << FPU_MVFR0_Short_vectors_Pos) /*!< MVFR0: Short vectors bits Mask */ + +#define FPU_MVFR0_Square_root_Pos 20U /*!< MVFR0: Square root bits Position */ +#define FPU_MVFR0_Square_root_Msk (0xFUL << FPU_MVFR0_Square_root_Pos) /*!< MVFR0: Square root bits Mask */ + +#define FPU_MVFR0_Divide_Pos 16U /*!< MVFR0: Divide bits Position */ +#define FPU_MVFR0_Divide_Msk (0xFUL << FPU_MVFR0_Divide_Pos) /*!< MVFR0: Divide bits Mask */ + +#define FPU_MVFR0_FP_excep_trapping_Pos 12U /*!< MVFR0: FP exception trapping bits Position */ +#define FPU_MVFR0_FP_excep_trapping_Msk (0xFUL << FPU_MVFR0_FP_excep_trapping_Pos) /*!< MVFR0: FP exception trapping bits Mask */ + +#define FPU_MVFR0_Double_precision_Pos 8U /*!< MVFR0: Double-precision bits Position */ +#define FPU_MVFR0_Double_precision_Msk (0xFUL << FPU_MVFR0_Double_precision_Pos) /*!< MVFR0: Double-precision bits Mask */ + +#define FPU_MVFR0_Single_precision_Pos 4U /*!< MVFR0: Single-precision bits Position */ +#define FPU_MVFR0_Single_precision_Msk (0xFUL << FPU_MVFR0_Single_precision_Pos) /*!< MVFR0: Single-precision bits Mask */ + +#define FPU_MVFR0_A_SIMD_registers_Pos 0U /*!< MVFR0: A_SIMD registers bits Position */ +#define FPU_MVFR0_A_SIMD_registers_Msk (0xFUL /*<< FPU_MVFR0_A_SIMD_registers_Pos*/) /*!< MVFR0: A_SIMD registers bits Mask */ + +/* Media and FP Feature Register 1 Definitions */ +#define FPU_MVFR1_FP_fused_MAC_Pos 28U /*!< MVFR1: FP fused MAC bits Position */ +#define FPU_MVFR1_FP_fused_MAC_Msk (0xFUL << FPU_MVFR1_FP_fused_MAC_Pos) /*!< MVFR1: FP fused MAC bits Mask */ + +#define FPU_MVFR1_FP_HPFP_Pos 24U /*!< MVFR1: FP HPFP bits Position */ +#define FPU_MVFR1_FP_HPFP_Msk (0xFUL << FPU_MVFR1_FP_HPFP_Pos) /*!< MVFR1: FP HPFP bits Mask */ + +#define FPU_MVFR1_D_NaN_mode_Pos 4U /*!< MVFR1: D_NaN mode bits Position */ +#define FPU_MVFR1_D_NaN_mode_Msk (0xFUL << FPU_MVFR1_D_NaN_mode_Pos) /*!< MVFR1: D_NaN mode bits Mask */ + +#define FPU_MVFR1_FtZ_mode_Pos 0U /*!< MVFR1: FtZ mode bits Position */ +#define FPU_MVFR1_FtZ_mode_Msk (0xFUL /*<< FPU_MVFR1_FtZ_mode_Pos*/) /*!< MVFR1: FtZ mode bits Mask */ + +/* Media and FP Feature Register 2 Definitions */ + +#define FPU_MVFR2_VFP_Misc_Pos 4U /*!< MVFR2: VFP Misc bits Position */ +#define FPU_MVFR2_VFP_Misc_Msk (0xFUL << FPU_MVFR2_VFP_Misc_Pos) /*!< MVFR2: VFP Misc bits Mask */ + +/*@} end of group CMSIS_FPU */ + + +/** + \ingroup CMSIS_core_register + \defgroup CMSIS_CoreDebug Core Debug Registers (CoreDebug) + \brief Type definitions for the Core Debug Registers + @{ + */ + +/** + \brief Structure type to access the Core Debug Register (CoreDebug). + */ +typedef struct +{ + __IOM uint32_t DHCSR; /*!< Offset: 0x000 (R/W) Debug Halting Control and Status Register */ + __OM uint32_t DCRSR; /*!< Offset: 0x004 ( /W) Debug Core Register Selector Register */ + __IOM uint32_t DCRDR; /*!< Offset: 0x008 (R/W) Debug Core Register Data Register */ + __IOM uint32_t DEMCR; /*!< Offset: 0x00C (R/W) Debug Exception and Monitor Control Register */ +} CoreDebug_Type; + +/* Debug Halting Control and Status Register Definitions */ +#define CoreDebug_DHCSR_DBGKEY_Pos 16U /*!< CoreDebug DHCSR: DBGKEY Position */ +#define CoreDebug_DHCSR_DBGKEY_Msk (0xFFFFUL << CoreDebug_DHCSR_DBGKEY_Pos) /*!< CoreDebug DHCSR: DBGKEY Mask */ + +#define CoreDebug_DHCSR_S_RESET_ST_Pos 25U /*!< CoreDebug DHCSR: S_RESET_ST Position */ +#define CoreDebug_DHCSR_S_RESET_ST_Msk (1UL << CoreDebug_DHCSR_S_RESET_ST_Pos) /*!< CoreDebug DHCSR: S_RESET_ST Mask */ + +#define CoreDebug_DHCSR_S_RETIRE_ST_Pos 24U /*!< CoreDebug DHCSR: S_RETIRE_ST Position */ +#define CoreDebug_DHCSR_S_RETIRE_ST_Msk (1UL << CoreDebug_DHCSR_S_RETIRE_ST_Pos) /*!< CoreDebug DHCSR: S_RETIRE_ST Mask */ + +#define CoreDebug_DHCSR_S_LOCKUP_Pos 19U /*!< CoreDebug DHCSR: S_LOCKUP Position */ +#define CoreDebug_DHCSR_S_LOCKUP_Msk (1UL << CoreDebug_DHCSR_S_LOCKUP_Pos) /*!< CoreDebug DHCSR: S_LOCKUP Mask */ + +#define CoreDebug_DHCSR_S_SLEEP_Pos 18U /*!< CoreDebug DHCSR: S_SLEEP Position */ +#define CoreDebug_DHCSR_S_SLEEP_Msk (1UL << CoreDebug_DHCSR_S_SLEEP_Pos) /*!< CoreDebug DHCSR: S_SLEEP Mask */ + +#define CoreDebug_DHCSR_S_HALT_Pos 17U /*!< CoreDebug DHCSR: S_HALT Position */ +#define CoreDebug_DHCSR_S_HALT_Msk (1UL << CoreDebug_DHCSR_S_HALT_Pos) /*!< CoreDebug DHCSR: S_HALT Mask */ + +#define CoreDebug_DHCSR_S_REGRDY_Pos 16U /*!< CoreDebug DHCSR: S_REGRDY Position */ +#define CoreDebug_DHCSR_S_REGRDY_Msk (1UL << CoreDebug_DHCSR_S_REGRDY_Pos) /*!< CoreDebug DHCSR: S_REGRDY Mask */ + +#define CoreDebug_DHCSR_C_SNAPSTALL_Pos 5U /*!< CoreDebug DHCSR: C_SNAPSTALL Position */ +#define CoreDebug_DHCSR_C_SNAPSTALL_Msk (1UL << CoreDebug_DHCSR_C_SNAPSTALL_Pos) /*!< CoreDebug DHCSR: C_SNAPSTALL Mask */ + +#define CoreDebug_DHCSR_C_MASKINTS_Pos 3U /*!< CoreDebug DHCSR: C_MASKINTS Position */ +#define CoreDebug_DHCSR_C_MASKINTS_Msk (1UL << CoreDebug_DHCSR_C_MASKINTS_Pos) /*!< CoreDebug DHCSR: C_MASKINTS Mask */ + +#define CoreDebug_DHCSR_C_STEP_Pos 2U /*!< CoreDebug DHCSR: C_STEP Position */ +#define CoreDebug_DHCSR_C_STEP_Msk (1UL << CoreDebug_DHCSR_C_STEP_Pos) /*!< CoreDebug DHCSR: C_STEP Mask */ + +#define CoreDebug_DHCSR_C_HALT_Pos 1U /*!< CoreDebug DHCSR: C_HALT Position */ +#define CoreDebug_DHCSR_C_HALT_Msk (1UL << CoreDebug_DHCSR_C_HALT_Pos) /*!< CoreDebug DHCSR: C_HALT Mask */ + +#define CoreDebug_DHCSR_C_DEBUGEN_Pos 0U /*!< CoreDebug DHCSR: C_DEBUGEN Position */ +#define CoreDebug_DHCSR_C_DEBUGEN_Msk (1UL /*<< CoreDebug_DHCSR_C_DEBUGEN_Pos*/) /*!< CoreDebug DHCSR: C_DEBUGEN Mask */ + +/* Debug Core Register Selector Register Definitions */ +#define CoreDebug_DCRSR_REGWnR_Pos 16U /*!< CoreDebug DCRSR: REGWnR Position */ +#define CoreDebug_DCRSR_REGWnR_Msk (1UL << CoreDebug_DCRSR_REGWnR_Pos) /*!< CoreDebug DCRSR: REGWnR Mask */ + +#define CoreDebug_DCRSR_REGSEL_Pos 0U /*!< CoreDebug DCRSR: REGSEL Position */ +#define CoreDebug_DCRSR_REGSEL_Msk (0x1FUL /*<< CoreDebug_DCRSR_REGSEL_Pos*/) /*!< CoreDebug DCRSR: REGSEL Mask */ + +/* Debug Exception and Monitor Control Register Definitions */ +#define CoreDebug_DEMCR_TRCENA_Pos 24U /*!< CoreDebug DEMCR: TRCENA Position */ +#define CoreDebug_DEMCR_TRCENA_Msk (1UL << CoreDebug_DEMCR_TRCENA_Pos) /*!< CoreDebug DEMCR: TRCENA Mask */ + +#define CoreDebug_DEMCR_MON_REQ_Pos 19U /*!< CoreDebug DEMCR: MON_REQ Position */ +#define CoreDebug_DEMCR_MON_REQ_Msk (1UL << CoreDebug_DEMCR_MON_REQ_Pos) /*!< CoreDebug DEMCR: MON_REQ Mask */ + +#define CoreDebug_DEMCR_MON_STEP_Pos 18U /*!< CoreDebug DEMCR: MON_STEP Position */ +#define CoreDebug_DEMCR_MON_STEP_Msk (1UL << CoreDebug_DEMCR_MON_STEP_Pos) /*!< CoreDebug DEMCR: MON_STEP Mask */ + +#define CoreDebug_DEMCR_MON_PEND_Pos 17U /*!< CoreDebug DEMCR: MON_PEND Position */ +#define CoreDebug_DEMCR_MON_PEND_Msk (1UL << CoreDebug_DEMCR_MON_PEND_Pos) /*!< CoreDebug DEMCR: MON_PEND Mask */ + +#define CoreDebug_DEMCR_MON_EN_Pos 16U /*!< CoreDebug DEMCR: MON_EN Position */ +#define CoreDebug_DEMCR_MON_EN_Msk (1UL << CoreDebug_DEMCR_MON_EN_Pos) /*!< CoreDebug DEMCR: MON_EN Mask */ + +#define CoreDebug_DEMCR_VC_HARDERR_Pos 10U /*!< CoreDebug DEMCR: VC_HARDERR Position */ +#define CoreDebug_DEMCR_VC_HARDERR_Msk (1UL << CoreDebug_DEMCR_VC_HARDERR_Pos) /*!< CoreDebug DEMCR: VC_HARDERR Mask */ + +#define CoreDebug_DEMCR_VC_INTERR_Pos 9U /*!< CoreDebug DEMCR: VC_INTERR Position */ +#define CoreDebug_DEMCR_VC_INTERR_Msk (1UL << CoreDebug_DEMCR_VC_INTERR_Pos) /*!< CoreDebug DEMCR: VC_INTERR Mask */ + +#define CoreDebug_DEMCR_VC_BUSERR_Pos 8U /*!< CoreDebug DEMCR: VC_BUSERR Position */ +#define CoreDebug_DEMCR_VC_BUSERR_Msk (1UL << CoreDebug_DEMCR_VC_BUSERR_Pos) /*!< CoreDebug DEMCR: VC_BUSERR Mask */ + +#define CoreDebug_DEMCR_VC_STATERR_Pos 7U /*!< CoreDebug DEMCR: VC_STATERR Position */ +#define CoreDebug_DEMCR_VC_STATERR_Msk (1UL << CoreDebug_DEMCR_VC_STATERR_Pos) /*!< CoreDebug DEMCR: VC_STATERR Mask */ + +#define CoreDebug_DEMCR_VC_CHKERR_Pos 6U /*!< CoreDebug DEMCR: VC_CHKERR Position */ +#define CoreDebug_DEMCR_VC_CHKERR_Msk (1UL << CoreDebug_DEMCR_VC_CHKERR_Pos) /*!< CoreDebug DEMCR: VC_CHKERR Mask */ + +#define CoreDebug_DEMCR_VC_NOCPERR_Pos 5U /*!< CoreDebug DEMCR: VC_NOCPERR Position */ +#define CoreDebug_DEMCR_VC_NOCPERR_Msk (1UL << CoreDebug_DEMCR_VC_NOCPERR_Pos) /*!< CoreDebug DEMCR: VC_NOCPERR Mask */ + +#define CoreDebug_DEMCR_VC_MMERR_Pos 4U /*!< CoreDebug DEMCR: VC_MMERR Position */ +#define CoreDebug_DEMCR_VC_MMERR_Msk (1UL << CoreDebug_DEMCR_VC_MMERR_Pos) /*!< CoreDebug DEMCR: VC_MMERR Mask */ + +#define CoreDebug_DEMCR_VC_CORERESET_Pos 0U /*!< CoreDebug DEMCR: VC_CORERESET Position */ +#define CoreDebug_DEMCR_VC_CORERESET_Msk (1UL /*<< CoreDebug_DEMCR_VC_CORERESET_Pos*/) /*!< CoreDebug DEMCR: VC_CORERESET Mask */ + +/*@} end of group CMSIS_CoreDebug */ + + +/** + \ingroup CMSIS_core_register + \defgroup CMSIS_core_bitfield Core register bit field macros + \brief Macros for use with bit field definitions (xxx_Pos, xxx_Msk). + @{ + */ + +/** + \brief Mask and shift a bit field value for use in a register bit range. + \param[in] field Name of the register bit field. + \param[in] value Value of the bit field. This parameter is interpreted as an uint32_t type. + \return Masked and shifted value. +*/ +#define _VAL2FLD(field, value) (((uint32_t)(value) << field ## _Pos) & field ## _Msk) + +/** + \brief Mask and shift a register value to extract a bit filed value. + \param[in] field Name of the register bit field. + \param[in] value Value of register. This parameter is interpreted as an uint32_t type. + \return Masked and shifted bit field value. +*/ +#define _FLD2VAL(field, value) (((uint32_t)(value) & field ## _Msk) >> field ## _Pos) + +/*@} end of group CMSIS_core_bitfield */ + + +/** + \ingroup CMSIS_core_register + \defgroup CMSIS_core_base Core Definitions + \brief Definitions for base addresses, unions, and structures. + @{ + */ + +/* Memory mapping of Core Hardware */ +#define SCS_BASE (0xE000E000UL) /*!< System Control Space Base Address */ +#define ITM_BASE (0xE0000000UL) /*!< ITM Base Address */ +#define DWT_BASE (0xE0001000UL) /*!< DWT Base Address */ +#define TPI_BASE (0xE0040000UL) /*!< TPI Base Address */ +#define CoreDebug_BASE (0xE000EDF0UL) /*!< Core Debug Base Address */ +#define SysTick_BASE (SCS_BASE + 0x0010UL) /*!< SysTick Base Address */ +#define NVIC_BASE (SCS_BASE + 0x0100UL) /*!< NVIC Base Address */ +#define SCB_BASE (SCS_BASE + 0x0D00UL) /*!< System Control Block Base Address */ + +#define SCnSCB ((SCnSCB_Type *) SCS_BASE ) /*!< System control Register not in SCB */ +#define SCB ((SCB_Type *) SCB_BASE ) /*!< SCB configuration struct */ +#define SysTick ((SysTick_Type *) SysTick_BASE ) /*!< SysTick configuration struct */ +#define NVIC ((NVIC_Type *) NVIC_BASE ) /*!< NVIC configuration struct */ +#define ITM ((ITM_Type *) ITM_BASE ) /*!< ITM configuration struct */ +#define DWT ((DWT_Type *) DWT_BASE ) /*!< DWT configuration struct */ +#define TPI ((TPI_Type *) TPI_BASE ) /*!< TPI configuration struct */ +#define CoreDebug ((CoreDebug_Type *) CoreDebug_BASE) /*!< Core Debug configuration struct */ + +#if defined (__MPU_PRESENT) && (__MPU_PRESENT == 1U) + #define MPU_BASE (SCS_BASE + 0x0D90UL) /*!< Memory Protection Unit */ + #define MPU ((MPU_Type *) MPU_BASE ) /*!< Memory Protection Unit */ +#endif + +#define FPU_BASE (SCS_BASE + 0x0F30UL) /*!< Floating Point Unit */ +#define FPU ((FPU_Type *) FPU_BASE ) /*!< Floating Point Unit */ + +/*@} */ + + +/** + \ingroup CMSIS_core_register + \defgroup CMSIS_register_aliases Backwards Compatibility Aliases + \brief Register alias definitions for backwards compatibility. + @{ + */ + +/* Capitalize ITM_TCR Register Definitions */ + +/* ITM Trace Control Register Definitions */ +#define ITM_TCR_TraceBusID_Pos (ITM_TCR_TRACEBUSID_Pos) /*!< \deprecated ITM_TCR_TraceBusID_Pos */ +#define ITM_TCR_TraceBusID_Msk (ITM_TCR_TRACEBUSID_Msk) /*!< \deprecated ITM_TCR_TraceBusID_Msk */ + +#define ITM_TCR_TSPrescale_Pos (ITM_TCR_TSPRESCALE_Pos) /*!< \deprecated ITM_TCR_TSPrescale_Pos */ +#define ITM_TCR_TSPrescale_Msk (ITM_TCR_TSPRESCALE_Msk) /*!< \deprecated ITM_TCR_TSPrescale_Msk */ + +/* ITM Lock Status Register Definitions */ +#define ITM_LSR_ByteAcc_Pos (ITM_LSR_BYTEACC_Pos) /*!< \deprecated ITM_LSR_ByteAcc_Pos */ +#define ITM_LSR_ByteAcc_Msk (ITM_LSR_BYTEACC_Msk) /*!< \deprecated ITM_LSR_ByteAcc_Msk */ + +#define ITM_LSR_Access_Pos (ITM_LSR_ACCESS_Pos) /*!< \deprecated ITM_LSR_Access_Pos */ +#define ITM_LSR_Access_Msk (ITM_LSR_ACCESS_Msk) /*!< \deprecated ITM_LSR_Access_Msk */ + +#define ITM_LSR_Present_Pos (ITM_LSR_PRESENT_Pos) /*!< \deprecated ITM_LSR_Present_Pos */ +#define ITM_LSR_Present_Msk (ITM_LSR_PRESENT_Msk) /*!< \deprecated ITM_LSR_Present_Msk */ + +/*@} */ + + + +/******************************************************************************* + * Hardware Abstraction Layer + Core Function Interface contains: + - Core NVIC Functions + - Core SysTick Functions + - Core Debug Functions + - Core Register Access Functions + ******************************************************************************/ +/** + \defgroup CMSIS_Core_FunctionInterface Functions and Instructions Reference +*/ + + + +/* ########################## NVIC functions #################################### */ +/** + \ingroup CMSIS_Core_FunctionInterface + \defgroup CMSIS_Core_NVICFunctions NVIC Functions + \brief Functions that manage interrupts and exceptions via the NVIC. + @{ + */ + +#ifdef CMSIS_NVIC_VIRTUAL + #ifndef CMSIS_NVIC_VIRTUAL_HEADER_FILE + #define CMSIS_NVIC_VIRTUAL_HEADER_FILE "cmsis_nvic_virtual.h" + #endif + #include CMSIS_NVIC_VIRTUAL_HEADER_FILE +#else + #define NVIC_SetPriorityGrouping __NVIC_SetPriorityGrouping + #define NVIC_GetPriorityGrouping __NVIC_GetPriorityGrouping + #define NVIC_EnableIRQ __NVIC_EnableIRQ + #define NVIC_GetEnableIRQ __NVIC_GetEnableIRQ + #define NVIC_DisableIRQ __NVIC_DisableIRQ + #define NVIC_GetPendingIRQ __NVIC_GetPendingIRQ + #define NVIC_SetPendingIRQ __NVIC_SetPendingIRQ + #define NVIC_ClearPendingIRQ __NVIC_ClearPendingIRQ + #define NVIC_GetActive __NVIC_GetActive + #define NVIC_SetPriority __NVIC_SetPriority + #define NVIC_GetPriority __NVIC_GetPriority + #define NVIC_SystemReset __NVIC_SystemReset +#endif /* CMSIS_NVIC_VIRTUAL */ + +#ifdef CMSIS_VECTAB_VIRTUAL + #ifndef CMSIS_VECTAB_VIRTUAL_HEADER_FILE + #define CMSIS_VECTAB_VIRTUAL_HEADER_FILE "cmsis_vectab_virtual.h" + #endif + #include CMSIS_VECTAB_VIRTUAL_HEADER_FILE +#else + #define NVIC_SetVector __NVIC_SetVector + #define NVIC_GetVector __NVIC_GetVector +#endif /* (CMSIS_VECTAB_VIRTUAL) */ + +#define NVIC_USER_IRQ_OFFSET 16 + + +/* The following EXC_RETURN values are saved the LR on exception entry */ +#define EXC_RETURN_HANDLER (0xFFFFFFF1UL) /* return to Handler mode, uses MSP after return */ +#define EXC_RETURN_THREAD_MSP (0xFFFFFFF9UL) /* return to Thread mode, uses MSP after return */ +#define EXC_RETURN_THREAD_PSP (0xFFFFFFFDUL) /* return to Thread mode, uses PSP after return */ +#define EXC_RETURN_HANDLER_FPU (0xFFFFFFE1UL) /* return to Handler mode, uses MSP after return, restore floating-point state */ +#define EXC_RETURN_THREAD_MSP_FPU (0xFFFFFFE9UL) /* return to Thread mode, uses MSP after return, restore floating-point state */ +#define EXC_RETURN_THREAD_PSP_FPU (0xFFFFFFEDUL) /* return to Thread mode, uses PSP after return, restore floating-point state */ + + +/** + \brief Set Priority Grouping + \details Sets the priority grouping field using the required unlock sequence. + The parameter PriorityGroup is assigned to the field SCB->AIRCR [10:8] PRIGROUP field. + Only values from 0..7 are used. + In case of a conflict between priority grouping and available + priority bits (__NVIC_PRIO_BITS), the smallest possible priority group is set. + \param [in] PriorityGroup Priority grouping field. + */ +__STATIC_INLINE void __NVIC_SetPriorityGrouping(uint32_t PriorityGroup) +{ + uint32_t reg_value; + uint32_t PriorityGroupTmp = (PriorityGroup & (uint32_t)0x07UL); /* only values 0..7 are used */ + + reg_value = SCB->AIRCR; /* read old register configuration */ + reg_value &= ~((uint32_t)(SCB_AIRCR_VECTKEY_Msk | SCB_AIRCR_PRIGROUP_Msk)); /* clear bits to change */ + reg_value = (reg_value | + ((uint32_t)0x5FAUL << SCB_AIRCR_VECTKEY_Pos) | + (PriorityGroupTmp << SCB_AIRCR_PRIGROUP_Pos) ); /* Insert write key and priority group */ + SCB->AIRCR = reg_value; +} + + +/** + \brief Get Priority Grouping + \details Reads the priority grouping field from the NVIC Interrupt Controller. + \return Priority grouping field (SCB->AIRCR [10:8] PRIGROUP field). + */ +__STATIC_INLINE uint32_t __NVIC_GetPriorityGrouping(void) +{ + return ((uint32_t)((SCB->AIRCR & SCB_AIRCR_PRIGROUP_Msk) >> SCB_AIRCR_PRIGROUP_Pos)); +} + + +/** + \brief Enable Interrupt + \details Enables a device specific interrupt in the NVIC interrupt controller. + \param [in] IRQn Device specific interrupt number. + \note IRQn must not be negative. + */ +__STATIC_INLINE void __NVIC_EnableIRQ(IRQn_Type IRQn) +{ + if ((int32_t)(IRQn) >= 0) + { + __COMPILER_BARRIER(); + NVIC->ISER[(((uint32_t)IRQn) >> 5UL)] = (uint32_t)(1UL << (((uint32_t)IRQn) & 0x1FUL)); + __COMPILER_BARRIER(); + } +} + + +/** + \brief Get Interrupt Enable status + \details Returns a device specific interrupt enable status from the NVIC interrupt controller. + \param [in] IRQn Device specific interrupt number. + \return 0 Interrupt is not enabled. + \return 1 Interrupt is enabled. + \note IRQn must not be negative. + */ +__STATIC_INLINE uint32_t __NVIC_GetEnableIRQ(IRQn_Type IRQn) +{ + if ((int32_t)(IRQn) >= 0) + { + return((uint32_t)(((NVIC->ISER[(((uint32_t)IRQn) >> 5UL)] & (1UL << (((uint32_t)IRQn) & 0x1FUL))) != 0UL) ? 1UL : 0UL)); + } + else + { + return(0U); + } +} + + +/** + \brief Disable Interrupt + \details Disables a device specific interrupt in the NVIC interrupt controller. + \param [in] IRQn Device specific interrupt number. + \note IRQn must not be negative. + */ +__STATIC_INLINE void __NVIC_DisableIRQ(IRQn_Type IRQn) +{ + if ((int32_t)(IRQn) >= 0) + { + NVIC->ICER[(((uint32_t)IRQn) >> 5UL)] = (uint32_t)(1UL << (((uint32_t)IRQn) & 0x1FUL)); + __DSB(); + __ISB(); + } +} + + +/** + \brief Get Pending Interrupt + \details Reads the NVIC pending register and returns the pending bit for the specified device specific interrupt. + \param [in] IRQn Device specific interrupt number. + \return 0 Interrupt status is not pending. + \return 1 Interrupt status is pending. + \note IRQn must not be negative. + */ +__STATIC_INLINE uint32_t __NVIC_GetPendingIRQ(IRQn_Type IRQn) +{ + if ((int32_t)(IRQn) >= 0) + { + return((uint32_t)(((NVIC->ISPR[(((uint32_t)IRQn) >> 5UL)] & (1UL << (((uint32_t)IRQn) & 0x1FUL))) != 0UL) ? 1UL : 0UL)); + } + else + { + return(0U); + } +} + + +/** + \brief Set Pending Interrupt + \details Sets the pending bit of a device specific interrupt in the NVIC pending register. + \param [in] IRQn Device specific interrupt number. + \note IRQn must not be negative. + */ +__STATIC_INLINE void __NVIC_SetPendingIRQ(IRQn_Type IRQn) +{ + if ((int32_t)(IRQn) >= 0) + { + NVIC->ISPR[(((uint32_t)IRQn) >> 5UL)] = (uint32_t)(1UL << (((uint32_t)IRQn) & 0x1FUL)); + } +} + + +/** + \brief Clear Pending Interrupt + \details Clears the pending bit of a device specific interrupt in the NVIC pending register. + \param [in] IRQn Device specific interrupt number. + \note IRQn must not be negative. + */ +__STATIC_INLINE void __NVIC_ClearPendingIRQ(IRQn_Type IRQn) +{ + if ((int32_t)(IRQn) >= 0) + { + NVIC->ICPR[(((uint32_t)IRQn) >> 5UL)] = (uint32_t)(1UL << (((uint32_t)IRQn) & 0x1FUL)); + } +} + + +/** + \brief Get Active Interrupt + \details Reads the active register in the NVIC and returns the active bit for the device specific interrupt. + \param [in] IRQn Device specific interrupt number. + \return 0 Interrupt status is not active. + \return 1 Interrupt status is active. + \note IRQn must not be negative. + */ +__STATIC_INLINE uint32_t __NVIC_GetActive(IRQn_Type IRQn) +{ + if ((int32_t)(IRQn) >= 0) + { + return((uint32_t)(((NVIC->IABR[(((uint32_t)IRQn) >> 5UL)] & (1UL << (((uint32_t)IRQn) & 0x1FUL))) != 0UL) ? 1UL : 0UL)); + } + else + { + return(0U); + } +} + + +/** + \brief Set Interrupt Priority + \details Sets the priority of a device specific interrupt or a processor exception. + The interrupt number can be positive to specify a device specific interrupt, + or negative to specify a processor exception. + \param [in] IRQn Interrupt number. + \param [in] priority Priority to set. + \note The priority cannot be set for every processor exception. + */ +__STATIC_INLINE void __NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority) +{ + if ((int32_t)(IRQn) >= 0) + { + NVIC->IP[((uint32_t)IRQn)] = (uint8_t)((priority << (8U - __NVIC_PRIO_BITS)) & (uint32_t)0xFFUL); + } + else + { + SCB->SHP[(((uint32_t)IRQn) & 0xFUL)-4UL] = (uint8_t)((priority << (8U - __NVIC_PRIO_BITS)) & (uint32_t)0xFFUL); + } +} + + +/** + \brief Get Interrupt Priority + \details Reads the priority of a device specific interrupt or a processor exception. + The interrupt number can be positive to specify a device specific interrupt, + or negative to specify a processor exception. + \param [in] IRQn Interrupt number. + \return Interrupt Priority. + Value is aligned automatically to the implemented priority bits of the microcontroller. + */ +__STATIC_INLINE uint32_t __NVIC_GetPriority(IRQn_Type IRQn) +{ + + if ((int32_t)(IRQn) >= 0) + { + return(((uint32_t)NVIC->IP[((uint32_t)IRQn)] >> (8U - __NVIC_PRIO_BITS))); + } + else + { + return(((uint32_t)SCB->SHP[(((uint32_t)IRQn) & 0xFUL)-4UL] >> (8U - __NVIC_PRIO_BITS))); + } +} + + +/** + \brief Encode Priority + \details Encodes the priority for an interrupt with the given priority group, + preemptive priority value, and subpriority value. + In case of a conflict between priority grouping and available + priority bits (__NVIC_PRIO_BITS), the smallest possible priority group is set. + \param [in] PriorityGroup Used priority group. + \param [in] PreemptPriority Preemptive priority value (starting from 0). + \param [in] SubPriority Subpriority value (starting from 0). + \return Encoded priority. Value can be used in the function \ref NVIC_SetPriority(). + */ +__STATIC_INLINE uint32_t NVIC_EncodePriority (uint32_t PriorityGroup, uint32_t PreemptPriority, uint32_t SubPriority) +{ + uint32_t PriorityGroupTmp = (PriorityGroup & (uint32_t)0x07UL); /* only values 0..7 are used */ + uint32_t PreemptPriorityBits; + uint32_t SubPriorityBits; + + PreemptPriorityBits = ((7UL - PriorityGroupTmp) > (uint32_t)(__NVIC_PRIO_BITS)) ? (uint32_t)(__NVIC_PRIO_BITS) : (uint32_t)(7UL - PriorityGroupTmp); + SubPriorityBits = ((PriorityGroupTmp + (uint32_t)(__NVIC_PRIO_BITS)) < (uint32_t)7UL) ? (uint32_t)0UL : (uint32_t)((PriorityGroupTmp - 7UL) + (uint32_t)(__NVIC_PRIO_BITS)); + + return ( + ((PreemptPriority & (uint32_t)((1UL << (PreemptPriorityBits)) - 1UL)) << SubPriorityBits) | + ((SubPriority & (uint32_t)((1UL << (SubPriorityBits )) - 1UL))) + ); +} + + +/** + \brief Decode Priority + \details Decodes an interrupt priority value with a given priority group to + preemptive priority value and subpriority value. + In case of a conflict between priority grouping and available + priority bits (__NVIC_PRIO_BITS) the smallest possible priority group is set. + \param [in] Priority Priority value, which can be retrieved with the function \ref NVIC_GetPriority(). + \param [in] PriorityGroup Used priority group. + \param [out] pPreemptPriority Preemptive priority value (starting from 0). + \param [out] pSubPriority Subpriority value (starting from 0). + */ +__STATIC_INLINE void NVIC_DecodePriority (uint32_t Priority, uint32_t PriorityGroup, uint32_t* const pPreemptPriority, uint32_t* const pSubPriority) +{ + uint32_t PriorityGroupTmp = (PriorityGroup & (uint32_t)0x07UL); /* only values 0..7 are used */ + uint32_t PreemptPriorityBits; + uint32_t SubPriorityBits; + + PreemptPriorityBits = ((7UL - PriorityGroupTmp) > (uint32_t)(__NVIC_PRIO_BITS)) ? (uint32_t)(__NVIC_PRIO_BITS) : (uint32_t)(7UL - PriorityGroupTmp); + SubPriorityBits = ((PriorityGroupTmp + (uint32_t)(__NVIC_PRIO_BITS)) < (uint32_t)7UL) ? (uint32_t)0UL : (uint32_t)((PriorityGroupTmp - 7UL) + (uint32_t)(__NVIC_PRIO_BITS)); + + *pPreemptPriority = (Priority >> SubPriorityBits) & (uint32_t)((1UL << (PreemptPriorityBits)) - 1UL); + *pSubPriority = (Priority ) & (uint32_t)((1UL << (SubPriorityBits )) - 1UL); +} + + +/** + \brief Set Interrupt Vector + \details Sets an interrupt vector in SRAM based interrupt vector table. + The interrupt number can be positive to specify a device specific interrupt, + or negative to specify a processor exception. + VTOR must been relocated to SRAM before. + \param [in] IRQn Interrupt number + \param [in] vector Address of interrupt handler function + */ +__STATIC_INLINE void __NVIC_SetVector(IRQn_Type IRQn, uint32_t vector) +{ + uint32_t *vectors = (uint32_t *)SCB->VTOR; + vectors[(int32_t)IRQn + NVIC_USER_IRQ_OFFSET] = vector; + /* ARM Application Note 321 states that the M4 does not require the architectural barrier */ +} + + +/** + \brief Get Interrupt Vector + \details Reads an interrupt vector from interrupt vector table. + The interrupt number can be positive to specify a device specific interrupt, + or negative to specify a processor exception. + \param [in] IRQn Interrupt number. + \return Address of interrupt handler function + */ +__STATIC_INLINE uint32_t __NVIC_GetVector(IRQn_Type IRQn) +{ + uint32_t *vectors = (uint32_t *)SCB->VTOR; + return vectors[(int32_t)IRQn + NVIC_USER_IRQ_OFFSET]; +} + + +/** + \brief System Reset + \details Initiates a system reset request to reset the MCU. + */ +__NO_RETURN __STATIC_INLINE void __NVIC_SystemReset(void) +{ + __DSB(); /* Ensure all outstanding memory accesses included + buffered write are completed before reset */ + SCB->AIRCR = (uint32_t)((0x5FAUL << SCB_AIRCR_VECTKEY_Pos) | + (SCB->AIRCR & SCB_AIRCR_PRIGROUP_Msk) | + SCB_AIRCR_SYSRESETREQ_Msk ); /* Keep priority group unchanged */ + __DSB(); /* Ensure completion of memory access */ + + for(;;) /* wait until reset */ + { + __NOP(); + } +} + +/*@} end of CMSIS_Core_NVICFunctions */ + + +/* ########################## MPU functions #################################### */ + +#if defined (__MPU_PRESENT) && (__MPU_PRESENT == 1U) + +#include "mpu_armv7.h" + +#endif + + +/* ########################## FPU functions #################################### */ +/** + \ingroup CMSIS_Core_FunctionInterface + \defgroup CMSIS_Core_FpuFunctions FPU Functions + \brief Function that provides FPU type. + @{ + */ + +/** + \brief get FPU type + \details returns the FPU type + \returns + - \b 0: No FPU + - \b 1: Single precision FPU + - \b 2: Double + Single precision FPU + */ +__STATIC_INLINE uint32_t SCB_GetFPUType(void) +{ + uint32_t mvfr0; + + mvfr0 = FPU->MVFR0; + if ((mvfr0 & (FPU_MVFR0_Single_precision_Msk | FPU_MVFR0_Double_precision_Msk)) == 0x020U) + { + return 1U; /* Single precision FPU */ + } + else + { + return 0U; /* No FPU */ + } +} + + +/*@} end of CMSIS_Core_FpuFunctions */ + + + +/* ################################## SysTick function ############################################ */ +/** + \ingroup CMSIS_Core_FunctionInterface + \defgroup CMSIS_Core_SysTickFunctions SysTick Functions + \brief Functions that configure the System. + @{ + */ + +#if defined (__Vendor_SysTickConfig) && (__Vendor_SysTickConfig == 0U) + +/** + \brief System Tick Configuration + \details Initializes the System Timer and its interrupt, and starts the System Tick Timer. + Counter is in free running mode to generate periodic interrupts. + \param [in] ticks Number of ticks between two interrupts. + \return 0 Function succeeded. + \return 1 Function failed. + \note When the variable __Vendor_SysTickConfig is set to 1, then the + function SysTick_Config is not included. In this case, the file device.h + must contain a vendor-specific implementation of this function. + */ +__STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks) +{ + if ((ticks - 1UL) > SysTick_LOAD_RELOAD_Msk) + { + return (1UL); /* Reload value impossible */ + } + + SysTick->LOAD = (uint32_t)(ticks - 1UL); /* set reload register */ + NVIC_SetPriority (SysTick_IRQn, (1UL << __NVIC_PRIO_BITS) - 1UL); /* set Priority for Systick Interrupt */ + SysTick->VAL = 0UL; /* Load the SysTick Counter Value */ + SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | + SysTick_CTRL_TICKINT_Msk | + SysTick_CTRL_ENABLE_Msk; /* Enable SysTick IRQ and SysTick Timer */ + return (0UL); /* Function successful */ +} + +#endif + +/*@} end of CMSIS_Core_SysTickFunctions */ + + + +/* ##################################### Debug In/Output function ########################################### */ +/** + \ingroup CMSIS_Core_FunctionInterface + \defgroup CMSIS_core_DebugFunctions ITM Functions + \brief Functions that access the ITM debug interface. + @{ + */ + +extern volatile int32_t ITM_RxBuffer; /*!< External variable to receive characters. */ +#define ITM_RXBUFFER_EMPTY ((int32_t)0x5AA55AA5U) /*!< Value identifying \ref ITM_RxBuffer is ready for next character. */ + + +/** + \brief ITM Send Character + \details Transmits a character via the ITM channel 0, and + \li Just returns when no debugger is connected that has booked the output. + \li Is blocking when a debugger is connected, but the previous character sent has not been transmitted. + \param [in] ch Character to transmit. + \returns Character to transmit. + */ +__STATIC_INLINE uint32_t ITM_SendChar (uint32_t ch) +{ + if (((ITM->TCR & ITM_TCR_ITMENA_Msk) != 0UL) && /* ITM enabled */ + ((ITM->TER & 1UL ) != 0UL) ) /* ITM Port #0 enabled */ + { + while (ITM->PORT[0U].u32 == 0UL) + { + __NOP(); + } + ITM->PORT[0U].u8 = (uint8_t)ch; + } + return (ch); +} + + +/** + \brief ITM Receive Character + \details Inputs a character via the external variable \ref ITM_RxBuffer. + \return Received character. + \return -1 No character pending. + */ +__STATIC_INLINE int32_t ITM_ReceiveChar (void) +{ + int32_t ch = -1; /* no character available */ + + if (ITM_RxBuffer != ITM_RXBUFFER_EMPTY) + { + ch = ITM_RxBuffer; + ITM_RxBuffer = ITM_RXBUFFER_EMPTY; /* ready for next character */ + } + + return (ch); +} + + +/** + \brief ITM Check Character + \details Checks whether a character is pending for reading in the variable \ref ITM_RxBuffer. + \return 0 No character available. + \return 1 Character available. + */ +__STATIC_INLINE int32_t ITM_CheckChar (void) +{ + + if (ITM_RxBuffer == ITM_RXBUFFER_EMPTY) + { + return (0); /* no character available */ + } + else + { + return (1); /* character available */ + } +} + +/*@} end of CMSIS_core_DebugFunctions */ + + + + +#ifdef __cplusplus +} +#endif + +#endif /* __CORE_CM4_H_DEPENDANT */ + +#endif /* __CMSIS_GENERIC */ diff --git a/lib/cmsis_core/mpu_armv7.h b/lib/cmsis_core/mpu_armv7.h new file mode 100644 index 00000000000..d9eedf81a64 --- /dev/null +++ b/lib/cmsis_core/mpu_armv7.h @@ -0,0 +1,275 @@ +/****************************************************************************** + * @file mpu_armv7.h + * @brief CMSIS MPU API for Armv7-M MPU + * @version V5.1.2 + * @date 25. May 2020 + ******************************************************************************/ +/* + * Copyright (c) 2017-2020 Arm Limited. All rights reserved. + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an AS IS BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#if defined ( __ICCARM__ ) + #pragma system_include /* treat file as system include file for MISRA check */ +#elif defined (__clang__) + #pragma clang system_header /* treat file as system include file */ +#endif + +#ifndef ARM_MPU_ARMV7_H +#define ARM_MPU_ARMV7_H + +#define ARM_MPU_REGION_SIZE_32B ((uint8_t)0x04U) ///!< MPU Region Size 32 Bytes +#define ARM_MPU_REGION_SIZE_64B ((uint8_t)0x05U) ///!< MPU Region Size 64 Bytes +#define ARM_MPU_REGION_SIZE_128B ((uint8_t)0x06U) ///!< MPU Region Size 128 Bytes +#define ARM_MPU_REGION_SIZE_256B ((uint8_t)0x07U) ///!< MPU Region Size 256 Bytes +#define ARM_MPU_REGION_SIZE_512B ((uint8_t)0x08U) ///!< MPU Region Size 512 Bytes +#define ARM_MPU_REGION_SIZE_1KB ((uint8_t)0x09U) ///!< MPU Region Size 1 KByte +#define ARM_MPU_REGION_SIZE_2KB ((uint8_t)0x0AU) ///!< MPU Region Size 2 KBytes +#define ARM_MPU_REGION_SIZE_4KB ((uint8_t)0x0BU) ///!< MPU Region Size 4 KBytes +#define ARM_MPU_REGION_SIZE_8KB ((uint8_t)0x0CU) ///!< MPU Region Size 8 KBytes +#define ARM_MPU_REGION_SIZE_16KB ((uint8_t)0x0DU) ///!< MPU Region Size 16 KBytes +#define ARM_MPU_REGION_SIZE_32KB ((uint8_t)0x0EU) ///!< MPU Region Size 32 KBytes +#define ARM_MPU_REGION_SIZE_64KB ((uint8_t)0x0FU) ///!< MPU Region Size 64 KBytes +#define ARM_MPU_REGION_SIZE_128KB ((uint8_t)0x10U) ///!< MPU Region Size 128 KBytes +#define ARM_MPU_REGION_SIZE_256KB ((uint8_t)0x11U) ///!< MPU Region Size 256 KBytes +#define ARM_MPU_REGION_SIZE_512KB ((uint8_t)0x12U) ///!< MPU Region Size 512 KBytes +#define ARM_MPU_REGION_SIZE_1MB ((uint8_t)0x13U) ///!< MPU Region Size 1 MByte +#define ARM_MPU_REGION_SIZE_2MB ((uint8_t)0x14U) ///!< MPU Region Size 2 MBytes +#define ARM_MPU_REGION_SIZE_4MB ((uint8_t)0x15U) ///!< MPU Region Size 4 MBytes +#define ARM_MPU_REGION_SIZE_8MB ((uint8_t)0x16U) ///!< MPU Region Size 8 MBytes +#define ARM_MPU_REGION_SIZE_16MB ((uint8_t)0x17U) ///!< MPU Region Size 16 MBytes +#define ARM_MPU_REGION_SIZE_32MB ((uint8_t)0x18U) ///!< MPU Region Size 32 MBytes +#define ARM_MPU_REGION_SIZE_64MB ((uint8_t)0x19U) ///!< MPU Region Size 64 MBytes +#define ARM_MPU_REGION_SIZE_128MB ((uint8_t)0x1AU) ///!< MPU Region Size 128 MBytes +#define ARM_MPU_REGION_SIZE_256MB ((uint8_t)0x1BU) ///!< MPU Region Size 256 MBytes +#define ARM_MPU_REGION_SIZE_512MB ((uint8_t)0x1CU) ///!< MPU Region Size 512 MBytes +#define ARM_MPU_REGION_SIZE_1GB ((uint8_t)0x1DU) ///!< MPU Region Size 1 GByte +#define ARM_MPU_REGION_SIZE_2GB ((uint8_t)0x1EU) ///!< MPU Region Size 2 GBytes +#define ARM_MPU_REGION_SIZE_4GB ((uint8_t)0x1FU) ///!< MPU Region Size 4 GBytes + +#define ARM_MPU_AP_NONE 0U ///!< MPU Access Permission no access +#define ARM_MPU_AP_PRIV 1U ///!< MPU Access Permission privileged access only +#define ARM_MPU_AP_URO 2U ///!< MPU Access Permission unprivileged access read-only +#define ARM_MPU_AP_FULL 3U ///!< MPU Access Permission full access +#define ARM_MPU_AP_PRO 5U ///!< MPU Access Permission privileged access read-only +#define ARM_MPU_AP_RO 6U ///!< MPU Access Permission read-only access + +/** MPU Region Base Address Register Value +* +* \param Region The region to be configured, number 0 to 15. +* \param BaseAddress The base address for the region. +*/ +#define ARM_MPU_RBAR(Region, BaseAddress) \ + (((BaseAddress) & MPU_RBAR_ADDR_Msk) | \ + ((Region) & MPU_RBAR_REGION_Msk) | \ + (MPU_RBAR_VALID_Msk)) + +/** +* MPU Memory Access Attributes +* +* \param TypeExtField Type extension field, allows you to configure memory access type, for example strongly ordered, peripheral. +* \param IsShareable Region is shareable between multiple bus masters. +* \param IsCacheable Region is cacheable, i.e. its value may be kept in cache. +* \param IsBufferable Region is bufferable, i.e. using write-back caching. Cacheable but non-bufferable regions use write-through policy. +*/ +#define ARM_MPU_ACCESS_(TypeExtField, IsShareable, IsCacheable, IsBufferable) \ + ((((TypeExtField) << MPU_RASR_TEX_Pos) & MPU_RASR_TEX_Msk) | \ + (((IsShareable) << MPU_RASR_S_Pos) & MPU_RASR_S_Msk) | \ + (((IsCacheable) << MPU_RASR_C_Pos) & MPU_RASR_C_Msk) | \ + (((IsBufferable) << MPU_RASR_B_Pos) & MPU_RASR_B_Msk)) + +/** +* MPU Region Attribute and Size Register Value +* +* \param DisableExec Instruction access disable bit, 1= disable instruction fetches. +* \param AccessPermission Data access permissions, allows you to configure read/write access for User and Privileged mode. +* \param AccessAttributes Memory access attribution, see \ref ARM_MPU_ACCESS_. +* \param SubRegionDisable Sub-region disable field. +* \param Size Region size of the region to be configured, for example 4K, 8K. +*/ +#define ARM_MPU_RASR_EX(DisableExec, AccessPermission, AccessAttributes, SubRegionDisable, Size) \ + ((((DisableExec) << MPU_RASR_XN_Pos) & MPU_RASR_XN_Msk) | \ + (((AccessPermission) << MPU_RASR_AP_Pos) & MPU_RASR_AP_Msk) | \ + (((AccessAttributes) & (MPU_RASR_TEX_Msk | MPU_RASR_S_Msk | MPU_RASR_C_Msk | MPU_RASR_B_Msk))) | \ + (((SubRegionDisable) << MPU_RASR_SRD_Pos) & MPU_RASR_SRD_Msk) | \ + (((Size) << MPU_RASR_SIZE_Pos) & MPU_RASR_SIZE_Msk) | \ + (((MPU_RASR_ENABLE_Msk)))) + +/** +* MPU Region Attribute and Size Register Value +* +* \param DisableExec Instruction access disable bit, 1= disable instruction fetches. +* \param AccessPermission Data access permissions, allows you to configure read/write access for User and Privileged mode. +* \param TypeExtField Type extension field, allows you to configure memory access type, for example strongly ordered, peripheral. +* \param IsShareable Region is shareable between multiple bus masters. +* \param IsCacheable Region is cacheable, i.e. its value may be kept in cache. +* \param IsBufferable Region is bufferable, i.e. using write-back caching. Cacheable but non-bufferable regions use write-through policy. +* \param SubRegionDisable Sub-region disable field. +* \param Size Region size of the region to be configured, for example 4K, 8K. +*/ +#define ARM_MPU_RASR(DisableExec, AccessPermission, TypeExtField, IsShareable, IsCacheable, IsBufferable, SubRegionDisable, Size) \ + ARM_MPU_RASR_EX(DisableExec, AccessPermission, ARM_MPU_ACCESS_(TypeExtField, IsShareable, IsCacheable, IsBufferable), SubRegionDisable, Size) + +/** +* MPU Memory Access Attribute for strongly ordered memory. +* - TEX: 000b +* - Shareable +* - Non-cacheable +* - Non-bufferable +*/ +#define ARM_MPU_ACCESS_ORDERED ARM_MPU_ACCESS_(0U, 1U, 0U, 0U) + +/** +* MPU Memory Access Attribute for device memory. +* - TEX: 000b (if shareable) or 010b (if non-shareable) +* - Shareable or non-shareable +* - Non-cacheable +* - Bufferable (if shareable) or non-bufferable (if non-shareable) +* +* \param IsShareable Configures the device memory as shareable or non-shareable. +*/ +#define ARM_MPU_ACCESS_DEVICE(IsShareable) ((IsShareable) ? ARM_MPU_ACCESS_(0U, 1U, 0U, 1U) : ARM_MPU_ACCESS_(2U, 0U, 0U, 0U)) + +/** +* MPU Memory Access Attribute for normal memory. +* - TEX: 1BBb (reflecting outer cacheability rules) +* - Shareable or non-shareable +* - Cacheable or non-cacheable (reflecting inner cacheability rules) +* - Bufferable or non-bufferable (reflecting inner cacheability rules) +* +* \param OuterCp Configures the outer cache policy. +* \param InnerCp Configures the inner cache policy. +* \param IsShareable Configures the memory as shareable or non-shareable. +*/ +#define ARM_MPU_ACCESS_NORMAL(OuterCp, InnerCp, IsShareable) ARM_MPU_ACCESS_((4U | (OuterCp)), IsShareable, ((InnerCp) >> 1U), ((InnerCp) & 1U)) + +/** +* MPU Memory Access Attribute non-cacheable policy. +*/ +#define ARM_MPU_CACHEP_NOCACHE 0U + +/** +* MPU Memory Access Attribute write-back, write and read allocate policy. +*/ +#define ARM_MPU_CACHEP_WB_WRA 1U + +/** +* MPU Memory Access Attribute write-through, no write allocate policy. +*/ +#define ARM_MPU_CACHEP_WT_NWA 2U + +/** +* MPU Memory Access Attribute write-back, no write allocate policy. +*/ +#define ARM_MPU_CACHEP_WB_NWA 3U + + +/** +* Struct for a single MPU Region +*/ +typedef struct { + uint32_t RBAR; //!< The region base address register value (RBAR) + uint32_t RASR; //!< The region attribute and size register value (RASR) \ref MPU_RASR +} ARM_MPU_Region_t; + +/** Enable the MPU. +* \param MPU_Control Default access permissions for unconfigured regions. +*/ +__STATIC_INLINE void ARM_MPU_Enable(uint32_t MPU_Control) +{ + __DMB(); + MPU->CTRL = MPU_Control | MPU_CTRL_ENABLE_Msk; +#ifdef SCB_SHCSR_MEMFAULTENA_Msk + SCB->SHCSR |= SCB_SHCSR_MEMFAULTENA_Msk; +#endif + __DSB(); + __ISB(); +} + +/** Disable the MPU. +*/ +__STATIC_INLINE void ARM_MPU_Disable(void) +{ + __DMB(); +#ifdef SCB_SHCSR_MEMFAULTENA_Msk + SCB->SHCSR &= ~SCB_SHCSR_MEMFAULTENA_Msk; +#endif + MPU->CTRL &= ~MPU_CTRL_ENABLE_Msk; + __DSB(); + __ISB(); +} + +/** Clear and disable the given MPU region. +* \param rnr Region number to be cleared. +*/ +__STATIC_INLINE void ARM_MPU_ClrRegion(uint32_t rnr) +{ + MPU->RNR = rnr; + MPU->RASR = 0U; +} + +/** Configure an MPU region. +* \param rbar Value for RBAR register. +* \param rasr Value for RASR register. +*/ +__STATIC_INLINE void ARM_MPU_SetRegion(uint32_t rbar, uint32_t rasr) +{ + MPU->RBAR = rbar; + MPU->RASR = rasr; +} + +/** Configure the given MPU region. +* \param rnr Region number to be configured. +* \param rbar Value for RBAR register. +* \param rasr Value for RASR register. +*/ +__STATIC_INLINE void ARM_MPU_SetRegionEx(uint32_t rnr, uint32_t rbar, uint32_t rasr) +{ + MPU->RNR = rnr; + MPU->RBAR = rbar; + MPU->RASR = rasr; +} + +/** Memcpy with strictly ordered memory access, e.g. used by code in ARM_MPU_Load(). +* \param dst Destination data is copied to. +* \param src Source data is copied from. +* \param len Amount of data words to be copied. +*/ +__STATIC_INLINE void ARM_MPU_OrderedMemcpy(volatile uint32_t* dst, const uint32_t* __RESTRICT src, uint32_t len) +{ + uint32_t i; + for (i = 0U; i < len; ++i) + { + dst[i] = src[i]; + } +} + +/** Load the given number of MPU regions from a table. +* \param table Pointer to the MPU configuration table. +* \param cnt Amount of regions to be configured. +*/ +__STATIC_INLINE void ARM_MPU_Load(ARM_MPU_Region_t const* table, uint32_t cnt) +{ + const uint32_t rowWordSize = sizeof(ARM_MPU_Region_t)/4U; + while (cnt > MPU_TYPE_RALIASES) { + ARM_MPU_OrderedMemcpy(&(MPU->RBAR), &(table->RBAR), MPU_TYPE_RALIASES*rowWordSize); + table += MPU_TYPE_RALIASES; + cnt -= MPU_TYPE_RALIASES; + } + ARM_MPU_OrderedMemcpy(&(MPU->RBAR), &(table->RBAR), cnt*rowWordSize); +} + +#endif diff --git a/lib/stm32wb.scons b/lib/stm32wb.scons new file mode 100644 index 00000000000..9c184872bb8 --- /dev/null +++ b/lib/stm32wb.scons @@ -0,0 +1,70 @@ +Import("env") + +env.Append( + CPPPATH=[ + "#/lib/cmsis_core", + "#/lib/stm32wb_cmsis/Include", + "#/lib/stm32wb_hal/Inc", + "#/lib/stm32wb_copro/wpan", + ], + CPPDEFINES=[ + "STM32WB", + "STM32WB55xx", + "USE_FULL_ASSERT", + "USE_FULL_LL_DRIVER", + ], + SDK_HEADERS=env.GlobRecursive( + "*_ll_*.h", + Dir("stm32wb_hal/Inc"), + exclude="*usb.h", + ), +) + +if env["RAM_EXEC"]: + env.Append( + CPPDEFINES=[ + "VECT_TAB_SRAM", + ], + ) + + +libenv = env.Clone(FW_LIB_NAME="stm32wb") +libenv.Append( + CPPPATH=[ + "#/lib/stm32wb_copro/wpan/ble", + "#/lib/stm32wb_copro/wpan/ble/core", + "#/lib/stm32wb_copro/wpan/interface/patterns/ble_thread", + "#/lib/stm32wb_copro/wpan/interface/patterns/ble_thread/shci", + "#/lib/stm32wb_copro/wpan/interface/patterns/ble_thread/tl", + "#/lib/stm32wb_copro/wpan/utilities", + ] +) +libenv.ApplyLibFlags() + +sources = libenv.GlobRecursive("*_ll_*.c", "stm32wb_hal/Src/", exclude="*usb.c") +sources += Glob( + "stm32wb_copro/wpan/interface/patterns/ble_thread/shci/*.c", + source=True, +) +sources += Glob( + "stm32wb_copro/wpan/interface/patterns/ble_thread/tl/*_tl*.c", + source=True, +) +sources += [ + "stm32wb_copro/wpan/interface/patterns/ble_thread/tl/tl_mbox.c", + "stm32wb_copro/wpan/ble/svc/Src/svc_ctl.c", + "stm32wb_copro/wpan/ble/core/auto/ble_gap_aci.c", + "stm32wb_copro/wpan/ble/core/auto/ble_gatt_aci.c", + "stm32wb_copro/wpan/ble/core/auto/ble_hal_aci.c", + "stm32wb_copro/wpan/ble/core/auto/ble_hci_le.c", + "stm32wb_copro/wpan/ble/core/auto/ble_l2cap_aci.c", + "stm32wb_copro/wpan/ble/core/template/osal.c", + "stm32wb_copro/wpan/utilities/dbg_trace.c", + "stm32wb_copro/wpan/utilities/otp.c", + "stm32wb_copro/wpan/utilities/stm_list.c", +] + + +lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources) +libenv.Install("${LIB_DIST_DIR}", lib) +Return("lib") diff --git a/lib/stm32wb_cmsis b/lib/stm32wb_cmsis new file mode 160000 index 00000000000..d1b860584df --- /dev/null +++ b/lib/stm32wb_cmsis @@ -0,0 +1 @@ +Subproject commit d1b860584dfe24d40d455ae624ed14600dfa93c9 diff --git a/lib/stm32wb_copro b/lib/stm32wb_copro new file mode 160000 index 00000000000..6c9c54f0566 --- /dev/null +++ b/lib/stm32wb_copro @@ -0,0 +1 @@ +Subproject commit 6c9c54f05669b2c4d436df58bb691d3b0d7c86df diff --git a/lib/stm32wb_hal b/lib/stm32wb_hal new file mode 160000 index 00000000000..cfd0dd258cb --- /dev/null +++ b/lib/stm32wb_hal @@ -0,0 +1 @@ +Subproject commit cfd0dd258cb031c95b2b2d6d04c19f9f625fe3e8 diff --git a/scripts/assets.py b/scripts/assets.py index c0fa9100b71..bd8b38ae6d4 100755 --- a/scripts/assets.py +++ b/scripts/assets.py @@ -60,7 +60,6 @@ def init(self): ) self.parser_copro.add_argument("cube_dir", help="Path to Cube folder") self.parser_copro.add_argument("output_dir", help="Path to output folder") - self.parser_copro.add_argument("mcu", help="MCU series as in copro folder") self.parser_copro.add_argument( "--cube_ver", dest="cube_ver", help="Cube version", required=True ) @@ -254,16 +253,20 @@ def copro(self): from flipper.assets.copro import Copro self.logger.info("Bundling coprocessor binaries") - copro = Copro(self.args.mcu) - self.logger.info("Loading CUBE info") - copro.loadCubeInfo(self.args.cube_dir, self.args.cube_ver) - self.logger.info("Bundling") - copro.bundle( - self.args.output_dir, - self.args.stack_file, - self.args.stack_type, - self.args.stack_addr, - ) + copro = Copro() + try: + self.logger.info("Loading CUBE info") + copro.loadCubeInfo(self.args.cube_dir, self.args.cube_ver) + self.logger.info("Bundling") + copro.bundle( + self.args.output_dir, + self.args.stack_file, + self.args.stack_type, + self.args.stack_addr, + ) + except Exception as e: + self.logger.error(f"Failed to bundle: {e}") + return 1 self.logger.info("Complete") return 0 diff --git a/scripts/flipper/assets/copro.py b/scripts/flipper/assets/copro.py index ee13a9b5ea6..f176e3b2e82 100644 --- a/scripts/flipper/assets/copro.py +++ b/scripts/flipper/assets/copro.py @@ -1,16 +1,14 @@ -import logging import json -from io import BytesIO -import tarfile -import xml.etree.ElementTree as ET -import posixpath +import logging import os +import posixpath +import tarfile +from io import BytesIO -from flipper.utils import file_sha256, timestamp from flipper.assets.coprobin import CoproBinary, get_stack_type +from flipper.utils import file_sha256, timestamp - -CUBE_COPRO_PATH = "Projects/STM32WB_Copro_Wireless_Binaries" +CUBE_COPRO_PATH = "firmware" MANIFEST_TEMPLATE = { "manifest": {"version": 0, "timestamp": 0}, @@ -27,8 +25,7 @@ class Copro: COPRO_TAR_DIR = "core2_firmware" - def __init__(self, mcu): - self.mcu = mcu + def __init__(self): self.version = None self.cube_dir = None self.mcu_copro = None @@ -38,20 +35,24 @@ def loadCubeInfo(self, cube_dir, reference_cube_version): if not os.path.isdir(cube_dir): raise Exception(f'"{cube_dir}" doesn\'t exists') self.cube_dir = cube_dir - self.mcu_copro = os.path.join(self.cube_dir, CUBE_COPRO_PATH, self.mcu) + self.mcu_copro = os.path.join(self.cube_dir, CUBE_COPRO_PATH) if not os.path.isdir(self.mcu_copro): raise Exception(f'"{self.mcu_copro}" doesn\'t exists') - cube_manifest_file = os.path.join(self.cube_dir, "package.xml") - cube_manifest = ET.parse(cube_manifest_file) - cube_package = cube_manifest.find("PackDescription") - if not cube_package: - raise Exception("Unknown Cube manifest format") - cube_version = cube_package.get("Patch") or cube_package.get("Release") - if not cube_version or not cube_version.startswith("FW.WB"): - raise Exception("Incorrect Cube package or version info") - cube_version = cube_version.replace("FW.WB.", "", 1) + try: + cube_manifest_file = os.path.join(self.cube_dir, "VERSION") + with open(cube_manifest_file, "r") as cube_manifest: + cube_version = cube_manifest.read().strip() + except IOError: + raise Exception(f"Failed to read version from {cube_manifest_file}") + + if not cube_version.startswith("v"): + raise Exception(f"Invalid cube version: {cube_version}") + cube_version = cube_version[1:] + if cube_version != reference_cube_version: - raise Exception("Unsupported cube version") + raise Exception( + f"Unsupported cube version: {cube_version}, expecting {reference_cube_version}" + ) self.version = cube_version def _getFileName(self, name): diff --git a/scripts/flipper/assets/coprobin.py b/scripts/flipper/assets/coprobin.py index 7c5bdb3dc4d..75bf76d766d 100644 --- a/scripts/flipper/assets/coprobin.py +++ b/scripts/flipper/assets/coprobin.py @@ -5,7 +5,7 @@ import sys -# From STM32CubeWB\Middlewares\ST\STM32_WPAN\interface\patterns\ble_thread\shci\shci.h +# From lib/stm32wb_copro/wpan/interface/patterns/ble_thread/shci/shci.h __STACK_TYPE_CODES = { "BLE_FULL": 0x01, "BLE_HCI": 0x02, From 241b4ef6e4096005290aedd1bc35e56398c579dd Mon Sep 17 00:00:00 2001 From: hedger Date: Tue, 9 May 2023 01:31:39 +0300 Subject: [PATCH 546/824] [FL-3299] furi_crash: added C2 status; added fw-version gdb command (#2638) * furi_crash: added C2 status * debug: Added "fw-version" gdb command; vscode: updated configuration to use new command * debug: added fw-info command to debug_other session * Toolbox: versioned structure for Version * debug: fw-version: no longer needs an ELF file loaded * debug: flipperversion: removed unused variable * debug_other: print running fw version Co-authored-by: Aleksandr Kutuzov --- .vscode/example/launch.json | 17 ++- SConstruct | 16 ++- debug/flipperversion.py | 109 ++++++++++++++++++ firmware/targets/f18/api_symbols.csv | 3 +- firmware/targets/f7/api_symbols.csv | 3 +- firmware/targets/f7/furi_hal/furi_hal_bt.c | 11 ++ .../targets/furi_hal_include/furi_hal_bt.h | 13 +++ furi/core/check.c | 16 +++ lib/toolbox/version.c | 20 +++- scripts/fbt_tools/fbt_debugopts.py | 8 +- .../ufbt/project_template/.vscode/launch.json | 8 ++ scripts/version.py | 3 - 12 files changed, 205 insertions(+), 22 deletions(-) create mode 100644 debug/flipperversion.py diff --git a/.vscode/example/launch.json b/.vscode/example/launch.json index 4cab026fa2b..b0bb9e2455b 100644 --- a/.vscode/example/launch.json +++ b/.vscode/example/launch.json @@ -36,6 +36,8 @@ "./debug/stm32wbx.cfg", ], "postAttachCommands": [ + "source debug/flipperversion.py", + "fw-version", // "compare-sections", "source debug/flipperapps.py", "fap-set-debug-elf-root build/latest/.extapps", @@ -59,6 +61,8 @@ "attach 1", "set confirm off", "set mem inaccessible-by-default off", + "source debug/flipperversion.py", + "fw-version", "source debug/flipperapps.py", "fap-set-debug-elf-root build/latest/.extapps", // "compare-sections", @@ -77,6 +81,8 @@ "svdFile": "./debug/STM32WB55_CM4.svd", "rtos": "FreeRTOS", "postAttachCommands": [ + "source debug/flipperversion.py", + "fw-version", "source debug/flipperapps.py", "fap-set-debug-elf-root build/latest/.extapps", ] @@ -97,20 +103,13 @@ "./debug/stm32wbx.cfg", ], "postAttachCommands": [ + "source debug/flipperversion.py", + "fw-version", "source debug/flipperapps.py", "fap-set-debug-elf-root build/latest/.extapps", ], // "showDevDebugOutput": "raw", }, - { - "name": "fbt debug", - "type": "python", - "request": "launch", - "program": "./lib/scons/scripts/scons.py", - "args": [ - "plugin_dist" - ] - }, { "name": "python debug", "type": "python", diff --git a/SConstruct b/SConstruct index 12f1166eb95..e2568287dff 100644 --- a/SConstruct +++ b/SConstruct @@ -239,19 +239,31 @@ distenv.PhonyTarget( ) # Debug alien elf +debug_other_opts = [ + "-ex", + "source ${FBT_DEBUG_DIR}/PyCortexMDebug/PyCortexMDebug.py", + # "-ex", + # "source ${FBT_DEBUG_DIR}/FreeRTOS/FreeRTOS.py", + "-ex", + "source ${FBT_DEBUG_DIR}/flipperversion.py", + "-ex", + "fw-version", +] + distenv.PhonyTarget( "debug_other", "${GDBPYCOM}", GDBOPTS="${GDBOPTS_BASE}", GDBREMOTE="${OPENOCD_GDB_PIPE}", - GDBPYOPTS='-ex "source ${FBT_DEBUG_DIR}/PyCortexMDebug/PyCortexMDebug.py" ', + GDBPYOPTS=debug_other_opts, ) distenv.PhonyTarget( "debug_other_blackmagic", "${GDBPYCOM}", GDBOPTS="${GDBOPTS_BASE} ${GDBOPTS_BLACKMAGIC}", - GDBREMOTE="$${BLACKMAGIC_ADDR}", + GDBREMOTE="${BLACKMAGIC_ADDR}", + GDBPYOPTS=debug_other_opts, ) diff --git a/debug/flipperversion.py b/debug/flipperversion.py new file mode 100644 index 00000000000..4ac3bd200d1 --- /dev/null +++ b/debug/flipperversion.py @@ -0,0 +1,109 @@ +from dataclasses import dataclass, field +from typing import Dict, Optional + +import gdb + + +# Must match FuriHalRtcRegisterVersion index in FuriHalRtcRegister enum +RTC_BACKUP_VERSION_REGISTER_IDX = 0x2 + +RTC_BASE = 0x40002800 +RTC_BACKUP_BASE = RTC_BASE + 0x50 + +VERSION_REGISTER_ADDRESS = RTC_BACKUP_BASE + RTC_BACKUP_VERSION_REGISTER_IDX * 4 + +VERSION_STRUCT_MAGIC = 0xBE40 + + +@dataclass +class VersionData: + git_hash: str + git_branch: str + build_date: str + version: str + target: int + build_is_dirty: bool + extra: Optional[Dict[str, str]] = field(default_factory=dict) + + +class VersionLoader: + def __init__(self, version_ptr): + self.version_ptr = version_ptr + self._cstr_type = gdb.lookup_type("char").pointer() + self._uint_type = gdb.lookup_type("unsigned int") + + version_signature = version_ptr.dereference().cast(self._uint_type) + is_versioned = (version_signature & (0xFFFF)) == VERSION_STRUCT_MAGIC + if is_versioned: + self._version_data = self.load_versioned( + major=version_signature >> 16 & 0xFF, + minor=version_signature >> 24 & 0xFF, + ) + else: + self._version_data = self.load_unversioned() + + @property + def version(self) -> VersionData: + return self._version_data + + def load_versioned(self, major, minor): + if major != 1: + raise ValueError("Unsupported version struct major version") + + # Struct version 1.0 + extra_data = int(self.version_ptr[5].cast(self._uint_type)) + return VersionData( + git_hash=self.version_ptr[1].cast(self._cstr_type).string(), + git_branch=self.version_ptr[2].cast(self._cstr_type).string(), + build_date=self.version_ptr[3].cast(self._cstr_type).string(), + version=self.version_ptr[4].cast(self._cstr_type).string(), + target=extra_data & 0xF, + build_is_dirty=bool((extra_data >> 8) & 0xF), + ) + + def load_unversioned(self): + """Parse an early version of the version struct.""" + extra_data = int(self.version_ptr[5].cast(self._uint_type)) + return VersionData( + git_hash=self.version_ptr[0].cast(self._cstr_type).string(), + git_branch=self.version_ptr[1].cast(self._cstr_type).string(), + # branch number is #2, but we don't care about it + build_date=self.version_ptr[3].cast(self._cstr_type).string(), + version=self.version_ptr[4].cast(self._cstr_type).string(), + target=extra_data & 0xF, + build_is_dirty=bool((extra_data >> 8) & 0xF), + ) + + +class FlipperFwVersion(gdb.Command): + """Print the version of Flipper's firmware.""" + + def __init__(self): + super(FlipperFwVersion, self).__init__("fw-version", gdb.COMMAND_USER) + + def invoke(self, arg, from_tty): + void_ptr_type = gdb.lookup_type("void").pointer().pointer() + version_ptr_ptr = gdb.Value(VERSION_REGISTER_ADDRESS).cast(void_ptr_type) + + if not version_ptr_ptr: + print("RTC version register is NULL") + return + + version_ptr = version_ptr_ptr.dereference() + if not version_ptr: + print("Pointer to version struct is NULL") + return + + version_struct = version_ptr.cast(void_ptr_type) + + v = VersionLoader(version_struct) + print("Firmware version on attached Flipper:") + print(f"\tVersion: {v.version.version}") + print(f"\tBuilt on: {v.version.build_date}") + print(f"\tGit branch: {v.version.git_branch}") + print(f"\tGit commit: {v.version.git_hash}") + print(f"\tDirty: {v.version.build_is_dirty}") + print(f"\tHW Target: {v.version.target}") + + +FlipperFwVersion() diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index 8db084e2fd5..7a892e1ccfa 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,26.0,, +Version,+,26.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -823,6 +823,7 @@ Function,+,furi_hal_bt_change_app,_Bool,"FuriHalBtProfile, GapEventCallback, voi Function,+,furi_hal_bt_clear_white_list,_Bool, Function,+,furi_hal_bt_dump_state,void,FuriString* Function,+,furi_hal_bt_ensure_c2_mode,_Bool,BleGlueC2Mode +Function,-,furi_hal_bt_get_hardfault_info,const FuriHalBtHardfaultInfo*, Function,+,furi_hal_bt_get_key_storage_buff,void,"uint8_t**, uint16_t*" Function,+,furi_hal_bt_get_radio_stack,FuriHalBtStack, Function,+,furi_hal_bt_get_rssi,float, diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 64383560873..6cb421a1bde 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,26.0,, +Version,+,26.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -1004,6 +1004,7 @@ Function,+,furi_hal_bt_change_app,_Bool,"FuriHalBtProfile, GapEventCallback, voi Function,+,furi_hal_bt_clear_white_list,_Bool, Function,+,furi_hal_bt_dump_state,void,FuriString* Function,+,furi_hal_bt_ensure_c2_mode,_Bool,BleGlueC2Mode +Function,-,furi_hal_bt_get_hardfault_info,const FuriHalBtHardfaultInfo*, Function,+,furi_hal_bt_get_key_storage_buff,void,"uint8_t**, uint16_t*" Function,+,furi_hal_bt_get_radio_stack,FuriHalBtStack, Function,+,furi_hal_bt_get_rssi,float, diff --git a/firmware/targets/f7/furi_hal/furi_hal_bt.c b/firmware/targets/f7/furi_hal/furi_hal_bt.c index b08c9ea27bb..048a8b3090b 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_bt.c +++ b/firmware/targets/f7/furi_hal/furi_hal_bt.c @@ -19,6 +19,8 @@ /* Time, in ms, to wait for mode transition before crashing */ #define C2_MODE_SWITCH_TIMEOUT 10000 +#define FURI_HAL_BT_HARDFAULT_INFO_MAGIC 0x1170FD0F + FuriMutex* furi_hal_bt_core2_mtx = NULL; static FuriHalBtStack furi_hal_bt_stack = FuriHalBtStackUnknown; @@ -440,3 +442,12 @@ bool furi_hal_bt_ensure_c2_mode(BleGlueC2Mode mode) { FURI_LOG_E(TAG, "Failed to switch C2 mode: %d", fw_start_res); return false; } + +const FuriHalBtHardfaultInfo* furi_hal_bt_get_hardfault_info() { + /* AN5289, 4.8.2 */ + const FuriHalBtHardfaultInfo* info = (FuriHalBtHardfaultInfo*)(SRAM2A_BASE); + if(info->magic != FURI_HAL_BT_HARDFAULT_INFO_MAGIC) { + return NULL; + } + return info; +} diff --git a/firmware/targets/furi_hal_include/furi_hal_bt.h b/firmware/targets/furi_hal_include/furi_hal_bt.h index 196b2edb3ad..6ba38cb5e63 100644 --- a/firmware/targets/furi_hal_include/furi_hal_bt.h +++ b/firmware/targets/furi_hal_include/furi_hal_bt.h @@ -224,6 +224,19 @@ uint32_t furi_hal_bt_get_transmitted_packets(); */ bool furi_hal_bt_ensure_c2_mode(BleGlueC2Mode mode); +typedef struct { + uint32_t magic; + uint32_t source_pc; + uint32_t source_lr; + uint32_t source_sp; +} FuriHalBtHardfaultInfo; + +/** Get hardfault info + * + * @return hardfault info. NULL if no hardfault + */ +const FuriHalBtHardfaultInfo* furi_hal_bt_get_hardfault_info(); + #ifdef __cplusplus } #endif diff --git a/furi/core/check.c b/furi/core/check.c index f5390639d6f..478f3aacccf 100644 --- a/furi/core/check.c +++ b/furi/core/check.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -87,6 +88,20 @@ static void __furi_print_stack_info() { __furi_put_uint32_as_text(uxTaskGetStackHighWaterMark(NULL) * 4); } +static void __furi_print_bt_stack_info() { + const FuriHalBtHardfaultInfo* fault_info = furi_hal_bt_get_hardfault_info(); + if(fault_info == NULL) { + furi_hal_console_puts("\r\n\tcore2: not faulted"); + } else { + furi_hal_console_puts("\r\n\tcore2: hardfaulted.\r\n\tPC: "); + __furi_put_uint32_as_hex(fault_info->source_pc); + furi_hal_console_puts("\r\n\tLR: "); + __furi_put_uint32_as_hex(fault_info->source_lr); + furi_hal_console_puts("\r\n\tSP: "); + __furi_put_uint32_as_hex(fault_info->source_sp); + } +} + static void __furi_print_heap_info() { furi_hal_console_puts("\r\n\t heap total: "); __furi_put_uint32_as_text(xPortGetTotalHeapSize()); @@ -136,6 +151,7 @@ FURI_NORETURN void __furi_crash() { __furi_print_stack_info(); } __furi_print_heap_info(); + __furi_print_bt_stack_info(); #ifndef FURI_DEBUG // Check if debug enabled by DAP diff --git a/lib/toolbox/version.c b/lib/toolbox/version.c index c6c10b4107e..6ba68e364d9 100644 --- a/lib/toolbox/version.c +++ b/lib/toolbox/version.c @@ -1,23 +1,34 @@ #include "version.h" - +#include /* This header is autogenerated by build system */ #include "version.inc.h" +#define VERSION_MAGIC (0xBE40u) +#define VERSION_MAJOR (0x1u) +#define VERSION_MINOR (0x0u) + struct Version { + // Header + const uint16_t magic; + const uint8_t major; + const uint8_t minor; + // Payload const char* git_hash; const char* git_branch; - const char* git_branch_num; const char* build_date; const char* version; + // Payload bits and pieces const uint8_t target; const bool build_is_dirty; }; /* version of current running firmware (bootloader/flipper) */ static const Version version = { + .magic = VERSION_MAGIC, + .major = VERSION_MAJOR, + .minor = VERSION_MINOR, .git_hash = GIT_COMMIT, .git_branch = GIT_BRANCH, - .git_branch_num = GIT_BRANCH_NUM, .build_date = BUILD_DATE, .version = VERSION #ifdef FURI_RAM_EXEC @@ -41,7 +52,8 @@ const char* version_get_gitbranch(const Version* v) { } const char* version_get_gitbranchnum(const Version* v) { - return v ? v->git_branch_num : version.git_branch_num; + UNUSED(v); + return "0"; } const char* version_get_builddate(const Version* v) { diff --git a/scripts/fbt_tools/fbt_debugopts.py b/scripts/fbt_tools/fbt_debugopts.py index 33cc0c0763b..58e73e9c9e9 100644 --- a/scripts/fbt_tools/fbt_debugopts.py +++ b/scripts/fbt_tools/fbt_debugopts.py @@ -39,10 +39,10 @@ def generate(env, **kw): "|openocd -c 'gdb_port pipe; log_output ${FBT_DEBUG_DIR}/openocd.log' ${[SINGLEQUOTEFUNC(OPENOCD_OPTS)]}" ], GDBOPTS_BASE=[ - "-ex", - "target extended-remote ${GDBREMOTE}", "-ex", "source ${FBT_DEBUG_DIR}/gdbinit", + "-ex", + "target extended-remote ${GDBREMOTE}", ], GDBOPTS_BLACKMAGIC=[ "-q", @@ -61,6 +61,8 @@ def generate(env, **kw): "-ex", "source ${FBT_DEBUG_DIR}/flipperapps.py", "-ex", + "source ${FBT_DEBUG_DIR}/flipperversion.py", + "-ex", "fap-set-debug-elf-root ${FBT_FAP_DEBUG_ELF_ROOT}", "-ex", "source ${FBT_DEBUG_DIR}/PyCortexMDebug/PyCortexMDebug.py", @@ -68,6 +70,8 @@ def generate(env, **kw): "svd_load ${SVD_FILE}", "-ex", "compare-sections", + "-ex", + "fw-version", ], JFLASHPROJECT="${FBT_DEBUG_DIR}/fw.jflash", ) diff --git a/scripts/ufbt/project_template/.vscode/launch.json b/scripts/ufbt/project_template/.vscode/launch.json index 697de9a4988..3269bab5754 100644 --- a/scripts/ufbt/project_template/.vscode/launch.json +++ b/scripts/ufbt/project_template/.vscode/launch.json @@ -29,6 +29,8 @@ "@UFBT_DEBUG_DIR@/stm32wbx.cfg" ], "postAttachCommands": [ + "source @UFBT_DEBUG_DIR@/flipperversion.py", + "fw-version", "source @UFBT_DEBUG_DIR@/flipperapps.py", "fap-set-debug-elf-root @UFBT_DEBUG_ELF_DIR@" ], @@ -49,6 +51,8 @@ "@UFBT_DEBUG_DIR@/stm32wbx.cfg" ], "postAttachCommands": [ + "source @UFBT_DEBUG_DIR@/flipperversion.py", + "fw-version", "source @UFBT_DEBUG_DIR@/flipperapps.py", "fap-set-debug-elf-root @UFBT_DEBUG_ELF_DIR@" ], @@ -69,6 +73,8 @@ "attach 1", "set confirm off", "set mem inaccessible-by-default off", + "source @UFBT_DEBUG_DIR@/flipperversion.py", + "fw-version", "source @UFBT_DEBUG_DIR@/flipperapps.py", "fap-set-debug-elf-root @UFBT_DEBUG_ELF_DIR@" ] @@ -86,6 +92,8 @@ "svdFile": "@UFBT_DEBUG_DIR@/STM32WB55_CM4.svd", "rtos": "FreeRTOS", "postAttachCommands": [ + "source @UFBT_DEBUG_DIR@/flipperversion.py", + "fw-version", "source @UFBT_DEBUG_DIR@/flipperapps.py", "fap-set-debug-elf-root @UFBT_DEBUG_ELF_DIR@" ] diff --git a/scripts/version.py b/scripts/version.py index 71d201abb6c..3d68b2e98d4 100644 --- a/scripts/version.py +++ b/scripts/version.py @@ -35,8 +35,6 @@ def get_version_info(self): or "unknown" ) - branch_num = self._exec_git("rev-list --count HEAD") or "n/a" - try: version = self._exec_git("describe --tags --abbrev=0 --exact-match") except subprocess.CalledProcessError: @@ -45,7 +43,6 @@ def get_version_info(self): return { "GIT_COMMIT": commit, "GIT_BRANCH": branch, - "GIT_BRANCH_NUM": branch_num, "VERSION": version, "BUILD_DIRTY": dirty and 1 or 0, } From f57f0efc4837f2c33e881f58477bb0f27884576b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Tue, 9 May 2023 08:31:44 +0900 Subject: [PATCH 547/824] Debug: revert cortex debug to lxml and drop DWT (#2651) * Debug: revert cortex debug to lxml * Debug: update PyCortexMDebug readme * fbt: moved "debug" dir to "scripts" subfolder * ufbt: added missing debug_other & debug_other_blackmagic targets; github: fixed script bundling * lint: fixed formatting on debug scripts * vscode: updated configuration for debug dir changes --------- Co-authored-by: hedger Co-authored-by: hedger --- .github/workflows/build.yml | 2 +- .vscode/example/launch.json | 31 +- debug/PyCortexMDebug/README.md | 84 --- debug/PyCortexMDebug/cmdebug/dwt_gdb.py | 160 ----- debug/PyCortexMDebug/cmdebug/x2d.py | 586 ------------------ {debug => scripts/debug}/FreeRTOS/FreeRTOS.py | 1 - .../debug}/FreeRTOS/FreeRTOSgdb/EventGroup.py | 0 .../FreeRTOS/FreeRTOSgdb/GDBCommands.py | 1 - .../FreeRTOS/FreeRTOSgdb/HandleRegistry.py | 1 - .../debug}/FreeRTOS/FreeRTOSgdb/List.py | 2 - .../debug}/FreeRTOS/FreeRTOSgdb/QueueTools.py | 1 - .../debug}/FreeRTOS/FreeRTOSgdb/Task.py | 1 - .../debug}/FreeRTOS/FreeRTOSgdb/Types.py | 0 .../debug}/FreeRTOS/FreeRTOSgdb/__init__.py | 0 {debug => scripts/debug}/FreeRTOS/LICENSE | 0 {debug => scripts/debug}/FreeRTOS/README.md | 0 .../debug}/PyCortexMDebug/LICENSE | 0 .../debug}/PyCortexMDebug/PyCortexMDebug.py | 2 - scripts/debug/PyCortexMDebug/README.md | 35 ++ .../debug}/PyCortexMDebug/cmdebug/__init__.py | 0 .../debug}/PyCortexMDebug/cmdebug/svd.py | 70 +-- .../debug}/PyCortexMDebug/cmdebug/svd_gdb.py | 0 {debug => scripts/debug}/STM32WB55_CM4.svd | 0 {debug => scripts/debug}/flipperapps.py | 0 {debug => scripts/debug}/flipperversion.py | 0 {debug => scripts/debug}/fw.jflash | 0 {debug => scripts/debug}/gdbinit | 0 {debug => scripts/debug}/stm32wbx.cfg | 0 scripts/fbt_tools/fbt_debugopts.py | 2 +- scripts/sconsdist.py | 1 - scripts/ufbt/SConstruct | 27 + scripts/ufbt/site_tools/ufbt_state.py | 4 +- 32 files changed, 113 insertions(+), 898 deletions(-) delete mode 100644 debug/PyCortexMDebug/README.md delete mode 100755 debug/PyCortexMDebug/cmdebug/dwt_gdb.py delete mode 100644 debug/PyCortexMDebug/cmdebug/x2d.py rename {debug => scripts/debug}/FreeRTOS/FreeRTOS.py (99%) rename {debug => scripts/debug}/FreeRTOS/FreeRTOSgdb/EventGroup.py (100%) rename {debug => scripts/debug}/FreeRTOS/FreeRTOSgdb/GDBCommands.py (99%) rename {debug => scripts/debug}/FreeRTOS/FreeRTOSgdb/HandleRegistry.py (99%) rename {debug => scripts/debug}/FreeRTOS/FreeRTOSgdb/List.py (99%) rename {debug => scripts/debug}/FreeRTOS/FreeRTOSgdb/QueueTools.py (99%) rename {debug => scripts/debug}/FreeRTOS/FreeRTOSgdb/Task.py (99%) rename {debug => scripts/debug}/FreeRTOS/FreeRTOSgdb/Types.py (100%) rename {debug => scripts/debug}/FreeRTOS/FreeRTOSgdb/__init__.py (100%) rename {debug => scripts/debug}/FreeRTOS/LICENSE (100%) rename {debug => scripts/debug}/FreeRTOS/README.md (100%) rename {debug => scripts/debug}/PyCortexMDebug/LICENSE (100%) rename {debug => scripts/debug}/PyCortexMDebug/PyCortexMDebug.py (96%) create mode 100644 scripts/debug/PyCortexMDebug/README.md rename {debug => scripts/debug}/PyCortexMDebug/cmdebug/__init__.py (100%) rename {debug => scripts/debug}/PyCortexMDebug/cmdebug/svd.py (89%) rename {debug => scripts/debug}/PyCortexMDebug/cmdebug/svd_gdb.py (100%) rename {debug => scripts/debug}/STM32WB55_CM4.svd (100%) rename {debug => scripts/debug}/flipperapps.py (100%) rename {debug => scripts/debug}/flipperversion.py (100%) rename {debug => scripts/debug}/fw.jflash (100%) rename {debug => scripts/debug}/gdbinit (100%) rename {debug => scripts/debug}/stm32wbx.cfg (100%) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d69318530bb..8358d1706aa 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -60,7 +60,7 @@ jobs: - name: 'Bundle scripts' if: ${{ !github.event.pull_request.head.repo.fork }} run: | - tar czpf artifacts/flipper-z-any-scripts-${SUFFIX}.tgz scripts debug + tar czpf artifacts/flipper-z-any-scripts-${SUFFIX}.tgz scripts - name: 'Build the firmware' run: | diff --git a/.vscode/example/launch.json b/.vscode/example/launch.json index b0bb9e2455b..f4b7c6e4838 100644 --- a/.vscode/example/launch.json +++ b/.vscode/example/launch.json @@ -27,22 +27,21 @@ "type": "cortex-debug", "servertype": "openocd", "device": "stlink", - "svdFile": "./debug/STM32WB55_CM4.svd", + "svdFile": "./scripts/debug/STM32WB55_CM4.svd", // If you're debugging early in the boot process, before OS scheduler is running, // you have to comment out the following line. "rtos": "FreeRTOS", "configFiles": [ "interface/stlink.cfg", - "./debug/stm32wbx.cfg", + "./scripts/debug/stm32wbx.cfg", ], "postAttachCommands": [ - "source debug/flipperversion.py", + "source scripts/debug/flipperversion.py", "fw-version", // "compare-sections", - "source debug/flipperapps.py", + "source scripts/debug/flipperapps.py", "fap-set-debug-elf-root build/latest/.extapps", - // "source debug/FreeRTOS/FreeRTOS.py", - // "svd_load debug/STM32WB55_CM4.svd" + // "source scripts/debug/FreeRTOS/FreeRTOS.py", ] // "showDevDebugOutput": "raw", }, @@ -54,16 +53,16 @@ "type": "cortex-debug", "servertype": "external", "gdbTarget": "${input:BLACKMAGIC}", - "svdFile": "./debug/STM32WB55_CM4.svd", + "svdFile": "./scripts/debug/STM32WB55_CM4.svd", "rtos": "FreeRTOS", "postAttachCommands": [ "monitor swdp_scan", "attach 1", "set confirm off", "set mem inaccessible-by-default off", - "source debug/flipperversion.py", + "source scripts/debug/flipperversion.py", "fw-version", - "source debug/flipperapps.py", + "source scripts/debug/flipperapps.py", "fap-set-debug-elf-root build/latest/.extapps", // "compare-sections", ] @@ -78,12 +77,12 @@ "servertype": "jlink", "interface": "swd", "device": "STM32WB55RG", - "svdFile": "./debug/STM32WB55_CM4.svd", + "svdFile": "./scripts/debug/STM32WB55_CM4.svd", "rtos": "FreeRTOS", "postAttachCommands": [ - "source debug/flipperversion.py", + "source scripts/debug/flipperversion.py", "fw-version", - "source debug/flipperapps.py", + "source scripts/debug/flipperapps.py", "fap-set-debug-elf-root build/latest/.extapps", ] // "showDevDebugOutput": "raw", @@ -96,16 +95,16 @@ "type": "cortex-debug", "servertype": "openocd", "device": "cmsis-dap", - "svdFile": "./debug/STM32WB55_CM4.svd", + "svdFile": "./scripts/debug/STM32WB55_CM4.svd", "rtos": "FreeRTOS", "configFiles": [ "interface/cmsis-dap.cfg", - "./debug/stm32wbx.cfg", + "./scripts/debug/stm32wbx.cfg", ], "postAttachCommands": [ - "source debug/flipperversion.py", + "source scripts/debug/flipperversion.py", "fw-version", - "source debug/flipperapps.py", + "source scripts/debug/flipperapps.py", "fap-set-debug-elf-root build/latest/.extapps", ], // "showDevDebugOutput": "raw", diff --git a/debug/PyCortexMDebug/README.md b/debug/PyCortexMDebug/README.md deleted file mode 100644 index 32c76e765d2..00000000000 --- a/debug/PyCortexMDebug/README.md +++ /dev/null @@ -1,84 +0,0 @@ -PyCortexMDebug -============== - -*A set of GDB/Python-based utilities to make life debugging ARM Cortex-M processors a bit easier* - -It will consist of several modules which will hopefully become integrated as they evolve. Presently, there is only one: - -## SVD -ARM defines an SVD (System View Description) file format in its CMSIS -standard as a means for Cortex-M-based chip manufacturers to provide a -common description of peripherals, registers, and register fields. You -can download SVD files for different manufacturers -[here](http://www.arm.com/products/processors/cortex-m/cortex-microcontroller-software-interface-standard.php). - -My implementation so far has only tested STM32 chips but should hold for others. If others are like those from ST, -expect plenty of errors in the file. Like GPIOA having a register named GPIOB_OSPEEDR and lots of 16-bit registers -that are listed as 32! - -The implementation consists of two components -- An xml parser module (pysvd) and a GDB file (gdb_svd). -I haven't yet worked out a perfect workflow for this, though it's quite easy to use when -you already tend to have a GDB initialization file for starting up OpenOCD and the like. -However your workflow works, just make sure to, in GDB: - - source gdb_svd.py - svd_load [your_svd_file].svd - -These files can be huge so it might take a second or two. Anyways, after that, you can do - - svd - -to list available peripherals with descriptions. Or you can do - - svd [some_peripheral_name] - -to see all of the registers (with their values) for a given peripheral. For more details, run - - svd [some_peripheral_name] [some_register_name] - -to see all of the field values with descriptions. - -You can add format modifiers like: - -* `svd/x` will display values in hex -* `svd/o` will display values in octal -* `svd/t` or `svd/b` will display values in binary -* `svd/a` will display values in hex and try to resolve symbols from the values - -All field values are displayed at the correct lengths as provided by the SVD files. -Also, tab completion exists for nearly everything! When in doubt, run `svd help`. - -### TODO - -Enable writing to registers and individual fields - -### Bugs - -There are probably a few. All planning, writing, and testing of this was done in an afternoon. There may be -some oddities in working with non-STM32 parts. I'll play with this when I start working with other -controllers again. If something's giving you trouble, describe the problem and it shall be fixed. - -## DWT -The ARM Data Watchpoint and Trace Unit (DWT) offers data watchpoints and a series of gated cycle counters. For now, -I only support the raw cycle counter but facilities are in place to make use of others. As this is independent of the -specific device under test, commands are simple and you can configure a clock speed to get real time values from -counters. - - dwt configclk 48000000 - -will set the current core clock speed. Then - - dwt cyccnt reset - dwt cyccnt enable - -will reset and start the cycle counter. At any point - - dwt cycnt - -will then indicate the number of cycles and amount of time that has passed. - -## ITM/ETM support - -This is not implemented yet. I want to have more complete support for some of the nicer debug and trace features -on Cortex-M processors. Parts of this will probably be dependent on OpenOCD and possibly on specific interfaces. -I'll try to avoid this where possible but can't make any promises. diff --git a/debug/PyCortexMDebug/cmdebug/dwt_gdb.py b/debug/PyCortexMDebug/cmdebug/dwt_gdb.py deleted file mode 100755 index dd7ccd2073b..00000000000 --- a/debug/PyCortexMDebug/cmdebug/dwt_gdb.py +++ /dev/null @@ -1,160 +0,0 @@ -#!/usr/bin/env python -""" -This file is part of PyCortexMDebug - -PyCortexMDebug is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -PyCortexMDebug is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with PyCortexMDebug. If not, see . -""" - -import gdb -import struct - -DWT_CTRL = 0xE0001000 -DWT_CYCCNT = 0xE0001004 -DWT_CPICNT = 0xE0001008 -DWT_EXTCNT = 0xE000100C -DWT_SLEEPCNT = 0xE0001010 -DWT_LSUCNT = 0xE0001014 -DWT_FOLDCNT = 0xE0001018 -DWT_PCSR = 0xE000101C - -prefix = "dwt : " - - -class DWT(gdb.Command): - clk = None - is_init = False - - def __init__(self): - gdb.Command.__init__(self, "dwt", gdb.COMMAND_DATA) - - @staticmethod - def read(address, bits=32): - """Read from memory (using print) and return an integer""" - value = gdb.selected_inferior().read_memory(address, bits / 8) - return struct.unpack_from(" 1: - if s[1][:2] == "en": - self.cyccnt_en() - elif s[1][0] == "r": - self.cyccnt_reset() - elif s[1][0] == "d": - self.cyccnt_dis() - gdb.write( - prefix - + "CYCCNT ({}): ".format("ON" if (self.read(DWT_CTRL) & 1) else "OFF") - + self.cycles_str(self.read(DWT_CYCCNT)) - ) - elif s[0] == "reset": - if len(s) > 1: - if s[1] == "cyccnt": - self.cyccnt_reset() - gdb.write(prefix + "CYCCNT reset\n") - if s[1] == "counters": - self.cyccnt_reset() - gdb.write(prefix + "CYCCNT reset\n") - else: - self.cyccnt_reset() - gdb.write(prefix + "CYCCNT reset\n") - else: - # Reset everything - self.cyccnt_reset() - gdb.write(prefix + "CYCCNT reset\n") - elif s[0] == "configclk": - if len(s) == 2: - try: - self.clk = float(s[1]) - except: - self.print_help() - else: - self.print_help() - else: - # Try to figure out what stupid went on here - gdb.write(args) - self.print_help() - - @staticmethod - def complete(text, word): - text = str(text).lower() - s = text.split(" ") - - commands = ["configclk", "reset", "cyccnt"] - reset_commands = ["counters", "cyccnt"] - cyccnt_commands = ["enable", "reset", "disable"] - - if len(s) == 1: - return filter(lambda x: x.startswith(s[0]), commands) - - if len(s) == 2: - if s[0] == "reset": - return filter(lambda x: x.startswith(s[1]), reset_commands) - if s[0] == "cyccnt": - return filter(lambda x: x.startswith(s[1]), cyccnt_commands) - - def cycles_str(self, cycles): - if self.clk: - return "%d cycles, %.3es\n" % (cycles, cycles * 1.0 / self.clk) - else: - return "%d cycles" - - def cyccnt_en(self): - self.write(DWT_CTRL, self.read(DWT_CTRL) | 1) - - def cyccnt_dis(self): - self.write(DWT_CTRL, self.read(DWT_CTRL) & 0xFFFFFFFE) - - def cyccnt_reset(self, value=0): - self.write(DWT_CYCCNT, value) - - def cpicnt_reset(self, value=0): - self.write(DWT_CPICNT, value & 0xFF) - - @staticmethod - def print_help(): - gdb.write("Usage:\n") - gdb.write("=========\n") - gdb.write("dwt:\n") - gdb.write("\tList available peripherals\n") - gdb.write("dwt configclk [Hz]:\n") - gdb.write("\tSet clock for rendering time values in seconds\n") - gdb.write("dwt reset:\n") - gdb.write("\tReset everything in DWT\n") - gdb.write("dwt reset counters:\n") - gdb.write("\tReset all DWT counters\n") - gdb.write("dwt cyccnt\n") - gdb.write("\tDisplay the cycle count\n") - gdb.write("\td(default):decimal, x: hex, o: octal, b: binary\n") - return - - -# Registers our class to GDB when sourced: -DWT() diff --git a/debug/PyCortexMDebug/cmdebug/x2d.py b/debug/PyCortexMDebug/cmdebug/x2d.py deleted file mode 100644 index fc3f185db20..00000000000 --- a/debug/PyCortexMDebug/cmdebug/x2d.py +++ /dev/null @@ -1,586 +0,0 @@ -#!/usr/bin/env python -"Makes working with XML feel like you are working with JSON" - -try: - from defusedexpat import pyexpat as expat -except ImportError: - from xml.parsers import expat - -from xml.sax.saxutils import XMLGenerator -from xml.sax.xmlreader import AttributesImpl - -try: # pragma no cover - from cStringIO import StringIO -except ImportError: # pragma no cover - try: - from StringIO import StringIO - except ImportError: - from io import StringIO - -from inspect import isgenerator - - -class ObjectDict(dict): - def __getattr__(self, name): - if name in self: - return self[name] - else: - raise AttributeError("No such attribute: " + name) - - -try: # pragma no cover - _basestring = basestring -except NameError: # pragma no cover - _basestring = str -try: # pragma no cover - _unicode = unicode -except NameError: # pragma no cover - _unicode = str - -__author__ = "Martin Blech" -__version__ = "0.12.0" -__license__ = "MIT" - - -class ParsingInterrupted(Exception): - pass - - -class _DictSAXHandler(object): - def __init__( - self, - item_depth=0, - item_callback=lambda *args: True, - xml_attribs=True, - attr_prefix="@", - cdata_key="#text", - force_cdata=False, - cdata_separator="", - postprocessor=None, - dict_constructor=ObjectDict, - strip_whitespace=True, - namespace_separator=":", - namespaces=None, - force_list=None, - comment_key="#comment", - ): - self.path = [] - self.stack = [] - self.data = [] - self.item = None - self.item_depth = item_depth - self.xml_attribs = xml_attribs - self.item_callback = item_callback - self.attr_prefix = attr_prefix - self.cdata_key = cdata_key - self.force_cdata = force_cdata - self.cdata_separator = cdata_separator - self.postprocessor = postprocessor - self.dict_constructor = dict_constructor - self.strip_whitespace = strip_whitespace - self.namespace_separator = namespace_separator - self.namespaces = namespaces - self.namespace_declarations = ObjectDict() - self.force_list = force_list - self.comment_key = comment_key - - def _build_name(self, full_name): - if self.namespaces is None: - return full_name - i = full_name.rfind(self.namespace_separator) - if i == -1: - return full_name - namespace, name = full_name[:i], full_name[i + 1 :] - try: - short_namespace = self.namespaces[namespace] - except KeyError: - short_namespace = namespace - if not short_namespace: - return name - else: - return self.namespace_separator.join((short_namespace, name)) - - def _attrs_to_dict(self, attrs): - if isinstance(attrs, dict): - return attrs - return self.dict_constructor(zip(attrs[0::2], attrs[1::2])) - - def startNamespaceDecl(self, prefix, uri): - self.namespace_declarations[prefix or ""] = uri - - def startElement(self, full_name, attrs): - name = self._build_name(full_name) - attrs = self._attrs_to_dict(attrs) - if attrs and self.namespace_declarations: - attrs["xmlns"] = self.namespace_declarations - self.namespace_declarations = ObjectDict() - self.path.append((name, attrs or None)) - if len(self.path) > self.item_depth: - self.stack.append((self.item, self.data)) - if self.xml_attribs: - attr_entries = [] - for key, value in attrs.items(): - key = self.attr_prefix + self._build_name(key) - if self.postprocessor: - entry = self.postprocessor(self.path, key, value) - else: - entry = (key, value) - if entry: - attr_entries.append(entry) - attrs = self.dict_constructor(attr_entries) - else: - attrs = None - self.item = attrs or None - self.data = [] - - def endElement(self, full_name): - name = self._build_name(full_name) - if len(self.path) == self.item_depth: - item = self.item - if item is None: - item = None if not self.data else self.cdata_separator.join(self.data) - - should_continue = self.item_callback(self.path, item) - if not should_continue: - raise ParsingInterrupted() - if len(self.stack): - data = None if not self.data else self.cdata_separator.join(self.data) - item = self.item - self.item, self.data = self.stack.pop() - if self.strip_whitespace and data: - data = data.strip() or None - if data and self.force_cdata and item is None: - item = self.dict_constructor() - if item is not None: - if data: - self.push_data(item, self.cdata_key, data) - self.item = self.push_data(self.item, name, item) - else: - self.item = self.push_data(self.item, name, data) - else: - self.item = None - self.data = [] - self.path.pop() - - def characters(self, data): - if not self.data: - self.data = [data] - else: - self.data.append(data) - - def comments(self, data): - if self.strip_whitespace: - data = data.strip() - self.item = self.push_data(self.item, self.comment_key, data) - - def push_data(self, item, key, data): - if self.postprocessor is not None: - result = self.postprocessor(self.path, key, data) - if result is None: - return item - key, data = result - if item is None: - item = self.dict_constructor() - try: - value = item[key] - if isinstance(value, list): - value.append(data) - else: - item[key] = [value, data] - except KeyError: - if self._should_force_list(key, data): - item[key] = [data] - else: - item[key] = data - return item - - def _should_force_list(self, key, value): - if not self.force_list: - return False - if isinstance(self.force_list, bool): - return self.force_list - try: - return key in self.force_list - except TypeError: - return self.force_list(self.path[:-1], key, value) - - -def parse( - xml_input, - encoding=None, - expat=expat, - process_namespaces=False, - namespace_separator=":", - disable_entities=True, - process_comments=False, - **kwargs -): - """Parse the given XML input and convert it into a dictionary. - - `xml_input` can either be a `string`, a file-like object, or a generator of strings. - - If `xml_attribs` is `True`, element attributes are put in the dictionary - among regular child elements, using `@` as a prefix to avoid collisions. If - set to `False`, they are just ignored. - - Simple example:: - - >>> import xmltodict - >>> doc = xmltodict.parse(\"\"\" - ...
- ... 1 - ... 2 - ... - ... \"\"\") - >>> doc['a']['@prop'] - u'x' - >>> doc['a']['b'] - [u'1', u'2'] - - If `item_depth` is `0`, the function returns a dictionary for the root - element (default behavior). Otherwise, it calls `item_callback` every time - an item at the specified depth is found and returns `None` in the end - (streaming mode). - - The callback function receives two parameters: the `path` from the document - root to the item (name-attribs pairs), and the `item` (dict). If the - callback's return value is false-ish, parsing will be stopped with the - :class:`ParsingInterrupted` exception. - - Streaming example:: - - >>> def handle(path, item): - ... print('path:%s item:%s' % (path, item)) - ... return True - ... - >>> xmltodict.parse(\"\"\" - ... - ... 1 - ... 2 - ... \"\"\", item_depth=2, item_callback=handle) - path:[(u'a', {u'prop': u'x'}), (u'b', None)] item:1 - path:[(u'a', {u'prop': u'x'}), (u'b', None)] item:2 - - The optional argument `postprocessor` is a function that takes `path`, - `key` and `value` as positional arguments and returns a new `(key, value)` - pair where both `key` and `value` may have changed. Usage example:: - - >>> def postprocessor(path, key, value): - ... try: - ... return key + ':int', int(value) - ... except (ValueError, TypeError): - ... return key, value - >>> xmltodict.parse('12x', - ... postprocessor=postprocessor) - ObjectDict([(u'a', ObjectDict([(u'b:int', [1, 2]), (u'b', u'x')]))]) - - You can pass an alternate version of `expat` (such as `defusedexpat`) by - using the `expat` parameter. E.g: - - >>> import defusedexpat - >>> xmltodict.parse('hello', expat=defusedexpat.pyexpat) - ObjectDict([(u'a', u'hello')]) - - You can use the force_list argument to force lists to be created even - when there is only a single child of a given level of hierarchy. The - force_list argument is a tuple of keys. If the key for a given level - of hierarchy is in the force_list argument, that level of hierarchy - will have a list as a child (even if there is only one sub-element). - The index_keys operation takes precedence over this. This is applied - after any user-supplied postprocessor has already run. - - For example, given this input: - - - host1 - Linux - - - em0 - 10.0.0.1 - - - - - - If called with force_list=('interface',), it will produce - this dictionary: - {'servers': - {'server': - {'name': 'host1', - 'os': 'Linux'}, - 'interfaces': - {'interface': - [ {'name': 'em0', 'ip_address': '10.0.0.1' } ] } } } - - `force_list` can also be a callable that receives `path`, `key` and - `value`. This is helpful in cases where the logic that decides whether - a list should be forced is more complex. - - - If `process_comment` is `True` then comment will be added with comment_key - (default=`'#comment'`) to then tag which contains comment - - For example, given this input: - - - - - - 1 - - 2 - - - - If called with process_comment=True, it will produce - this dictionary: - 'a': { - 'b': { - '#comment': 'b comment', - 'c': { - - '#comment': 'c comment', - '#text': '1', - }, - 'd': '2', - }, - } - """ - handler = _DictSAXHandler(namespace_separator=namespace_separator, **kwargs) - if isinstance(xml_input, _unicode): - if not encoding: - encoding = "utf-8" - xml_input = xml_input.encode(encoding) - if not process_namespaces: - namespace_separator = None - parser = expat.ParserCreate(encoding, namespace_separator) - try: - parser.ordered_attributes = True - except AttributeError: - # Jython's expat does not support ordered_attributes - pass - parser.StartNamespaceDeclHandler = handler.startNamespaceDecl - parser.StartElementHandler = handler.startElement - parser.EndElementHandler = handler.endElement - parser.CharacterDataHandler = handler.characters - if process_comments: - parser.CommentHandler = handler.comments - parser.buffer_text = True - if disable_entities: - try: - # Attempt to disable DTD in Jython's expat parser (Xerces-J). - feature = "http://apache.org/xml/features/disallow-doctype-decl" - parser._reader.setFeature(feature, True) - except AttributeError: - # For CPython / expat parser. - # Anything not handled ends up here and entities aren't expanded. - parser.DefaultHandler = lambda x: None - # Expects an integer return; zero means failure -> expat.ExpatError. - parser.ExternalEntityRefHandler = lambda *x: 1 - if hasattr(xml_input, "read"): - parser.ParseFile(xml_input) - elif isgenerator(xml_input): - for chunk in xml_input: - parser.Parse(chunk, False) - parser.Parse(b"", True) - else: - parser.Parse(xml_input, True) - return handler.item - - -def _process_namespace(name, namespaces, ns_sep=":", attr_prefix="@"): - if not namespaces: - return name - try: - ns, name = name.rsplit(ns_sep, 1) - except ValueError: - pass - else: - ns_res = namespaces.get(ns.strip(attr_prefix)) - name = ( - "{}{}{}{}".format( - attr_prefix if ns.startswith(attr_prefix) else "", ns_res, ns_sep, name - ) - if ns_res - else name - ) - return name - - -def _emit( - key, - value, - content_handler, - attr_prefix="@", - cdata_key="#text", - depth=0, - preprocessor=None, - pretty=False, - newl="\n", - indent="\t", - namespace_separator=":", - namespaces=None, - full_document=True, - expand_iter=None, -): - key = _process_namespace(key, namespaces, namespace_separator, attr_prefix) - if preprocessor is not None: - result = preprocessor(key, value) - if result is None: - return - key, value = result - if ( - not hasattr(value, "__iter__") - or isinstance(value, _basestring) - or isinstance(value, dict) - ): - value = [value] - for index, v in enumerate(value): - if full_document and depth == 0 and index > 0: - raise ValueError("document with multiple roots") - if v is None: - v = ObjectDict() - elif isinstance(v, bool): - if v: - v = _unicode("true") - else: - v = _unicode("false") - elif not isinstance(v, dict): - if ( - expand_iter - and hasattr(v, "__iter__") - and not isinstance(v, _basestring) - ): - v = ObjectDict(((expand_iter, v),)) - else: - v = _unicode(v) - if isinstance(v, _basestring): - v = ObjectDict(((cdata_key, v),)) - cdata = None - attrs = ObjectDict() - children = [] - for ik, iv in v.items(): - if ik == cdata_key: - cdata = iv - continue - if ik.startswith(attr_prefix): - ik = _process_namespace( - ik, namespaces, namespace_separator, attr_prefix - ) - if ik == "@xmlns" and isinstance(iv, dict): - for k, v in iv.items(): - attr = "xmlns{}".format(":{}".format(k) if k else "") - attrs[attr] = _unicode(v) - continue - if not isinstance(iv, _unicode): - iv = _unicode(iv) - attrs[ik[len(attr_prefix) :]] = iv - continue - children.append((ik, iv)) - if pretty: - content_handler.ignorableWhitespace(depth * indent) - content_handler.startElement(key, AttributesImpl(attrs)) - if pretty and children: - content_handler.ignorableWhitespace(newl) - for child_key, child_value in children: - _emit( - child_key, - child_value, - content_handler, - attr_prefix, - cdata_key, - depth + 1, - preprocessor, - pretty, - newl, - indent, - namespaces=namespaces, - namespace_separator=namespace_separator, - expand_iter=expand_iter, - ) - if cdata is not None: - content_handler.characters(cdata) - if pretty and children: - content_handler.ignorableWhitespace(depth * indent) - content_handler.endElement(key) - if pretty and depth: - content_handler.ignorableWhitespace(newl) - - -def unparse( - input_dict, - output=None, - encoding="utf-8", - full_document=True, - short_empty_elements=False, - **kwargs -): - """Emit an XML document for the given `input_dict` (reverse of `parse`). - - The resulting XML document is returned as a string, but if `output` (a - file-like object) is specified, it is written there instead. - - Dictionary keys prefixed with `attr_prefix` (default=`'@'`) are interpreted - as XML node attributes, whereas keys equal to `cdata_key` - (default=`'#text'`) are treated as character data. - - The `pretty` parameter (default=`False`) enables pretty-printing. In this - mode, lines are terminated with `'\n'` and indented with `'\t'`, but this - can be customized with the `newl` and `indent` parameters. - - """ - if full_document and len(input_dict) != 1: - raise ValueError("Document must have exactly one root.") - must_return = False - if output is None: - output = StringIO() - must_return = True - if short_empty_elements: - content_handler = XMLGenerator(output, encoding, True) - else: - content_handler = XMLGenerator(output, encoding) - if full_document: - content_handler.startDocument() - for key, value in input_dict.items(): - _emit(key, value, content_handler, full_document=full_document, **kwargs) - if full_document: - content_handler.endDocument() - if must_return: - value = output.getvalue() - try: # pragma no cover - value = value.decode(encoding) - except AttributeError: # pragma no cover - pass - return value - - -if __name__ == "__main__": # pragma: no cover - import sys - import marshal - - try: - stdin = sys.stdin.buffer - stdout = sys.stdout.buffer - except AttributeError: - stdin = sys.stdin - stdout = sys.stdout - - (item_depth,) = sys.argv[1:] - item_depth = int(item_depth) - - def handle_item(path, item): - marshal.dump((path, item), stdout) - return True - - try: - root = parse( - stdin, - item_depth=item_depth, - item_callback=handle_item, - dict_constructor=dict, - ) - if item_depth == 0: - handle_item([], root) - except KeyboardInterrupt: - pass diff --git a/debug/FreeRTOS/FreeRTOS.py b/scripts/debug/FreeRTOS/FreeRTOS.py similarity index 99% rename from debug/FreeRTOS/FreeRTOS.py rename to scripts/debug/FreeRTOS/FreeRTOS.py index 036e18f31db..0eb7e5f8d5b 100644 --- a/debug/FreeRTOS/FreeRTOS.py +++ b/scripts/debug/FreeRTOS/FreeRTOS.py @@ -29,7 +29,6 @@ class Scheduler: def __init__(self): - self._blocked = ListInspector("xSuspendedTaskList") self._delayed1 = ListInspector("xDelayedTaskList1") self._delayed2 = ListInspector("xDelayedTaskList2") diff --git a/debug/FreeRTOS/FreeRTOSgdb/EventGroup.py b/scripts/debug/FreeRTOS/FreeRTOSgdb/EventGroup.py similarity index 100% rename from debug/FreeRTOS/FreeRTOSgdb/EventGroup.py rename to scripts/debug/FreeRTOS/FreeRTOSgdb/EventGroup.py diff --git a/debug/FreeRTOS/FreeRTOSgdb/GDBCommands.py b/scripts/debug/FreeRTOS/FreeRTOSgdb/GDBCommands.py similarity index 99% rename from debug/FreeRTOS/FreeRTOSgdb/GDBCommands.py rename to scripts/debug/FreeRTOS/FreeRTOSgdb/GDBCommands.py index ba811e3e131..5564502ecc9 100644 --- a/debug/FreeRTOS/FreeRTOSgdb/GDBCommands.py +++ b/scripts/debug/FreeRTOS/FreeRTOSgdb/GDBCommands.py @@ -61,7 +61,6 @@ def PrintQueueInfo(self, q): if maxCount == 0: print(outputFmt % (q.GetName(), q.GetQueueMessagesWaiting(), "", "")) else: - for i in range(0, maxCount): txName = "" if i < len(sendList): diff --git a/debug/FreeRTOS/FreeRTOSgdb/HandleRegistry.py b/scripts/debug/FreeRTOS/FreeRTOSgdb/HandleRegistry.py similarity index 99% rename from debug/FreeRTOS/FreeRTOSgdb/HandleRegistry.py rename to scripts/debug/FreeRTOS/FreeRTOSgdb/HandleRegistry.py index 1682c917665..c13457017b3 100644 --- a/debug/FreeRTOS/FreeRTOSgdb/HandleRegistry.py +++ b/scripts/debug/FreeRTOS/FreeRTOSgdb/HandleRegistry.py @@ -48,7 +48,6 @@ def PrintRegistry(self): print("%d: %3s %16s" % (i, h, name)) def FilterBy(self, qMode): - """Retrieve a List of Mutex Queue Handles""" resp = [] for i in range(self._minIndex, self._maxIndex): diff --git a/debug/FreeRTOS/FreeRTOSgdb/List.py b/scripts/debug/FreeRTOS/FreeRTOSgdb/List.py similarity index 99% rename from debug/FreeRTOS/FreeRTOSgdb/List.py rename to scripts/debug/FreeRTOS/FreeRTOSgdb/List.py index 62aa9dc9ab8..575bdc525a3 100644 --- a/debug/FreeRTOS/FreeRTOSgdb/List.py +++ b/scripts/debug/FreeRTOS/FreeRTOSgdb/List.py @@ -56,7 +56,6 @@ def GetElements(self, CastTypeStr=None, startElem=1): of some of the TCB Task lists. """ if self._list != None: - CastType = None if CastTypeStr != None: if type(CastTypeStr) == str: @@ -73,7 +72,6 @@ def GetElements(self, CastTypeStr=None, startElem=1): index = self._list["pxIndex"] if numElems > 0 and numElems < 200: - if startElem == 0: curr = index else: diff --git a/debug/FreeRTOS/FreeRTOSgdb/QueueTools.py b/scripts/debug/FreeRTOS/FreeRTOSgdb/QueueTools.py similarity index 99% rename from debug/FreeRTOS/FreeRTOSgdb/QueueTools.py rename to scripts/debug/FreeRTOS/FreeRTOSgdb/QueueTools.py index 49a780db3e7..a35f0894f1c 100644 --- a/debug/FreeRTOS/FreeRTOSgdb/QueueTools.py +++ b/scripts/debug/FreeRTOS/FreeRTOSgdb/QueueTools.py @@ -47,7 +47,6 @@ def IsValid(qType): class QueueInspector: - QueueType = gdb.lookup_type("Queue_t") def __init__(self, handle): diff --git a/debug/FreeRTOS/FreeRTOSgdb/Task.py b/scripts/debug/FreeRTOS/FreeRTOSgdb/Task.py similarity index 99% rename from debug/FreeRTOS/FreeRTOSgdb/Task.py rename to scripts/debug/FreeRTOS/FreeRTOSgdb/Task.py index 04da3bbcd41..d3078fdc922 100644 --- a/debug/FreeRTOS/FreeRTOSgdb/Task.py +++ b/scripts/debug/FreeRTOS/FreeRTOSgdb/Task.py @@ -11,7 +11,6 @@ class TaskInspector: - TCBType = gdb.lookup_type("TCB_t") def __init__(self, handle): diff --git a/debug/FreeRTOS/FreeRTOSgdb/Types.py b/scripts/debug/FreeRTOS/FreeRTOSgdb/Types.py similarity index 100% rename from debug/FreeRTOS/FreeRTOSgdb/Types.py rename to scripts/debug/FreeRTOS/FreeRTOSgdb/Types.py diff --git a/debug/FreeRTOS/FreeRTOSgdb/__init__.py b/scripts/debug/FreeRTOS/FreeRTOSgdb/__init__.py similarity index 100% rename from debug/FreeRTOS/FreeRTOSgdb/__init__.py rename to scripts/debug/FreeRTOS/FreeRTOSgdb/__init__.py diff --git a/debug/FreeRTOS/LICENSE b/scripts/debug/FreeRTOS/LICENSE similarity index 100% rename from debug/FreeRTOS/LICENSE rename to scripts/debug/FreeRTOS/LICENSE diff --git a/debug/FreeRTOS/README.md b/scripts/debug/FreeRTOS/README.md similarity index 100% rename from debug/FreeRTOS/README.md rename to scripts/debug/FreeRTOS/README.md diff --git a/debug/PyCortexMDebug/LICENSE b/scripts/debug/PyCortexMDebug/LICENSE similarity index 100% rename from debug/PyCortexMDebug/LICENSE rename to scripts/debug/PyCortexMDebug/LICENSE diff --git a/debug/PyCortexMDebug/PyCortexMDebug.py b/scripts/debug/PyCortexMDebug/PyCortexMDebug.py similarity index 96% rename from debug/PyCortexMDebug/PyCortexMDebug.py rename to scripts/debug/PyCortexMDebug/PyCortexMDebug.py index 6533535c32c..fd322b5a62c 100644 --- a/debug/PyCortexMDebug/PyCortexMDebug.py +++ b/scripts/debug/PyCortexMDebug/PyCortexMDebug.py @@ -28,7 +28,5 @@ sys.path.append(directory) from cmdebug.svd_gdb import LoadSVD -from cmdebug.dwt_gdb import DWT -DWT() LoadSVD() diff --git a/scripts/debug/PyCortexMDebug/README.md b/scripts/debug/PyCortexMDebug/README.md new file mode 100644 index 00000000000..0a5764b02de --- /dev/null +++ b/scripts/debug/PyCortexMDebug/README.md @@ -0,0 +1,35 @@ +PyCortexMDebug +============== + +## SVD + +ARM defines an SVD (System View Description) file format in its CMSIS standard as a means for Cortex-M-based chip manufacturers to provide a common description of peripherals, registers, and register fields. You can download SVD files for different manufacturers [here](http://www.arm.com/products/processors/cortex-m/cortex-microcontroller-software-interface-standard.php). + +The implementation consists of two components -- An lxml-based parser module (pysvd) and a GDB file (gdb_svd). I haven't yet worked out a perfect workflow for this, though it's quite easy to use when you already tend to have a GDB initialization file for starting up OpenOCD and the like. However your workflow works, just make sure to, in GDB: + + source gdb_svd.py + svd_load [your_svd_file].svd + +These files can be huge so it might take a second or two. Anyways, after that, you can do + + svd + +to list available peripherals with descriptions. Or you can do + + svd [some_peripheral_name] + +to see all of the registers (with their values) for a given peripheral. For more details, run + + svd [some_peripheral_name] [some_register_name] + +to see all of the field values with descriptions. + +You can add format modifiers like: + +* `svd/x` will display values in hex +* `svd/o` will display values in octal +* `svd/t` or `svd/b` will display values in binary +* `svd/a` will display values in hex and try to resolve symbols from the values + +All field values are displayed at the correct lengths as provided by the SVD files. +Also, tab completion exists for nearly everything! When in doubt, run `svd help`. diff --git a/debug/PyCortexMDebug/cmdebug/__init__.py b/scripts/debug/PyCortexMDebug/cmdebug/__init__.py similarity index 100% rename from debug/PyCortexMDebug/cmdebug/__init__.py rename to scripts/debug/PyCortexMDebug/cmdebug/__init__.py diff --git a/debug/PyCortexMDebug/cmdebug/svd.py b/scripts/debug/PyCortexMDebug/cmdebug/svd.py similarity index 89% rename from debug/PyCortexMDebug/cmdebug/svd.py rename to scripts/debug/PyCortexMDebug/cmdebug/svd.py index f4a884bb4a5..a25e69bfb90 100755 --- a/debug/PyCortexMDebug/cmdebug/svd.py +++ b/scripts/debug/PyCortexMDebug/cmdebug/svd.py @@ -16,15 +16,14 @@ along with PyCortexMDebug. If not, see . """ -from collections import OrderedDict -from . import x2d - -import traceback -import warnings -import pickle +import lxml.objectify as objectify import sys +from collections import OrderedDict import os +import pickle +import traceback import re +import warnings class SmartDict: @@ -127,31 +126,26 @@ class SVDFile: def __init__(self, fname): """ + Args: fname: Filename for the SVD file """ + f = objectify.parse(os.path.expanduser(fname)) + root = f.getroot() + periph = root.peripherals.getchildren() self.peripherals = SmartDict() self.base_address = 0 - xml_file_name = os.path.expanduser(fname) - pickle_file_name = xml_file_name + ".pickle" - root = None - if os.path.exists(pickle_file_name): - print("Loading pickled SVD") - root = pickle.load(open(pickle_file_name, "rb")) - else: - print("Loading XML SVD and pickling it") - root = x2d.parse(open(xml_file_name, "rb")) - pickle.dump(root, open(pickle_file_name, "wb"), pickle.HIGHEST_PROTOCOL) - print("Processing SVD tree") # XML elements - for p in root["device"]["peripherals"]["peripheral"]: + for p in periph: try: - self.peripherals[p["name"]] = SVDPeripheral(p, self) + if p.tag == "peripheral": + self.peripherals[str(p.name)] = SVDPeripheral(p, self) + else: + # This is some other tag + pass except SVDNonFatalError as e: - # print(e) - pass - print("SVD Ready") + print(e) def add_register(parent, node): @@ -271,11 +265,11 @@ def __init__(self, svd_elem, parent): self.parent_base_address = parent.base_address # Look for a base address, as it is required - if "baseAddress" not in svd_elem: + if not hasattr(svd_elem, "baseAddress"): raise SVDNonFatalError("Periph without base address") self.base_address = int(str(svd_elem.baseAddress), 0) - if "@derivedFrom" in svd_elem: - derived_from = svd_elem["@derivedFrom"] + if "derivedFrom" in svd_elem.attrib: + derived_from = svd_elem.attrib["derivedFrom"] try: self.name = str(svd_elem.name) except AttributeError: @@ -301,14 +295,16 @@ def copier(a): self.clusters = SmartDict() if hasattr(svd_elem, "registers"): - if "register" in svd_elem.registers: - for r in svd_elem.registers.register: - if isinstance(r, x2d.ObjectDict): - add_register(self, r) - if "cluster" in svd_elem.registers: - for c in svd_elem.registers.cluster: - if isinstance(c, x2d.ObjectDict): - add_cluster(self, c) + registers = [ + r + for r in svd_elem.registers.getchildren() + if r.tag in ["cluster", "register"] + ] + for r in registers: + if r.tag == "cluster": + add_cluster(self, r) + elif r.tag == "register": + add_register(self, r) def refactor_parent(self, parent): self.parent_base_address = parent.base_address @@ -342,11 +338,11 @@ def __init__(self, svd_elem, parent): else: self.size = 0x20 self.fields = SmartDict() - if "fields" in svd_elem: + if hasattr(svd_elem, "fields"): # Filter fields to only consider those of tag "field" - for f in svd_elem.fields.field: - if isinstance(f, x2d.ObjectDict): - self.fields[str(f.name)] = SVDPeripheralRegisterField(f, self) + fields = [f for f in svd_elem.fields.getchildren() if f.tag == "field"] + for f in fields: + self.fields[str(f.name)] = SVDPeripheralRegisterField(f, self) def refactor_parent(self, parent): self.parent_base_address = parent.base_address diff --git a/debug/PyCortexMDebug/cmdebug/svd_gdb.py b/scripts/debug/PyCortexMDebug/cmdebug/svd_gdb.py similarity index 100% rename from debug/PyCortexMDebug/cmdebug/svd_gdb.py rename to scripts/debug/PyCortexMDebug/cmdebug/svd_gdb.py diff --git a/debug/STM32WB55_CM4.svd b/scripts/debug/STM32WB55_CM4.svd similarity index 100% rename from debug/STM32WB55_CM4.svd rename to scripts/debug/STM32WB55_CM4.svd diff --git a/debug/flipperapps.py b/scripts/debug/flipperapps.py similarity index 100% rename from debug/flipperapps.py rename to scripts/debug/flipperapps.py diff --git a/debug/flipperversion.py b/scripts/debug/flipperversion.py similarity index 100% rename from debug/flipperversion.py rename to scripts/debug/flipperversion.py diff --git a/debug/fw.jflash b/scripts/debug/fw.jflash similarity index 100% rename from debug/fw.jflash rename to scripts/debug/fw.jflash diff --git a/debug/gdbinit b/scripts/debug/gdbinit similarity index 100% rename from debug/gdbinit rename to scripts/debug/gdbinit diff --git a/debug/stm32wbx.cfg b/scripts/debug/stm32wbx.cfg similarity index 100% rename from debug/stm32wbx.cfg rename to scripts/debug/stm32wbx.cfg diff --git a/scripts/fbt_tools/fbt_debugopts.py b/scripts/fbt_tools/fbt_debugopts.py index 58e73e9c9e9..d46ecd8f32c 100644 --- a/scripts/fbt_tools/fbt_debugopts.py +++ b/scripts/fbt_tools/fbt_debugopts.py @@ -18,7 +18,7 @@ def GetDevices(env): def generate(env, **kw): env.AddMethod(GetDevices) env.SetDefault( - FBT_DEBUG_DIR="${ROOT_DIR}/debug", + FBT_DEBUG_DIR="${FBT_SCRIPT_DIR}/debug", ) if (adapter_serial := env.subst("$OPENOCD_ADAPTER_SERIAL")) != "auto": diff --git a/scripts/sconsdist.py b/scripts/sconsdist.py index 1657feab93b..d2d1d2f49c9 100644 --- a/scripts/sconsdist.py +++ b/scripts/sconsdist.py @@ -170,7 +170,6 @@ def bundle_sdk(self): "update.dir", "sdk_headers.dir", "lib.dir", - "debug.dir", "scripts.dir", ) diff --git a/scripts/ufbt/SConstruct b/scripts/ufbt/SConstruct index 3f623ebc82d..4dd1fb5b904 100644 --- a/scripts/ufbt/SConstruct +++ b/scripts/ufbt/SConstruct @@ -186,6 +186,33 @@ dist_env.PhonyTarget( FBT_FAP_DEBUG_ELF_ROOT=path_as_posix(dist_env.subst("$FBT_FAP_DEBUG_ELF_ROOT")), ) +# Debug alien elf +debug_other_opts = [ + "-ex", + "source ${FBT_DEBUG_DIR}/PyCortexMDebug/PyCortexMDebug.py", + "-ex", + "source ${FBT_DEBUG_DIR}/flipperversion.py", + "-ex", + "fw-version", +] + +dist_env.PhonyTarget( + "debug_other", + "${GDBPYCOM}", + GDBOPTS="${GDBOPTS_BASE}", + GDBREMOTE="${OPENOCD_GDB_PIPE}", + GDBPYOPTS=debug_other_opts, +) + +dist_env.PhonyTarget( + "debug_other_blackmagic", + "${GDBPYCOM}", + GDBOPTS="${GDBOPTS_BASE} ${GDBOPTS_BLACKMAGIC}", + GDBREMOTE="${BLACKMAGIC_ADDR}", + GDBPYOPTS=debug_other_opts, +) + + dist_env.PhonyTarget( "flash_blackmagic", "$GDB $GDBOPTS $SOURCES $GDBFLASH", diff --git a/scripts/ufbt/site_tools/ufbt_state.py b/scripts/ufbt/site_tools/ufbt_state.py index 47f4afec4ef..0038b66a31f 100644 --- a/scripts/ufbt/site_tools/ufbt_state.py +++ b/scripts/ufbt/site_tools/ufbt_state.py @@ -78,10 +78,8 @@ def generate(env, **kw): env.SetDefault( # Paths SDK_DEFINITION=env.File(sdk_data["sdk_symbols"]), - FBT_DEBUG_DIR=pathlib.Path( - sdk_current_sdk_dir_node.Dir(sdk_components["debug.dir"]).abspath - ).as_posix(), FBT_SCRIPT_DIR=scripts_dir, + FBT_DEBUG_DIR=scripts_dir.Dir("debug"), LIBPATH=sdk_current_sdk_dir_node.Dir(sdk_components["lib.dir"]), FW_ELF=sdk_current_sdk_dir_node.File(sdk_components["firmware.elf"]), FW_BIN=sdk_current_sdk_dir_node.File(sdk_components["full.bin"]), From e1c6e78b2ecdfe6da19c04d4e16b95c7f8247e9f Mon Sep 17 00:00:00 2001 From: "g3gg0.de" Date: Tue, 9 May 2023 02:55:17 +0200 Subject: [PATCH 548/824] added DigitalSequence and PulseReader (#2070) * added DigitalSequence to chain multiple DigitalSignals added PulseReader for hardware assisted digital signal sampling * added send_time option to start a signal at a specific DWT->CYCCNT value * fixed linter errors and undone function renaming * fixed renaming * flagged functions in api_symbols.csv * allow gpio field to stay uninitialized in digital_signal_prepare_arr() * fix test cases to match (expected) implementation * pulse_reader: build as static library Signed-off-by: g3gg0.de * fix starting level detection in pulse_reader * added unit test for pulse_reader * change pulse reader test timings to 1, 10 and 100 ms * fine tuned timings for pulse_reader test * pulse_reader_stop now deinits GPIO as recommended by @gornekich * ran format_py * pulse_reader: remove from API, allow to link with faps Signed-off-by: g3gg0.de * remove unit test for pulse_reader again * pulse_reader: add call to set GPIO pull direction * make structures private, add C implementation of digital_signal_update_dma() * digital_signal/pulse_reader: allow parameters for free to be NULL * digital_signal: show unoptimized and optimized code for digital_signal_update_dma() next to each other * pulse_reader: further optimize assembly code * digital_signal: reduce code complexity of digital_signal_update_dma() by only reconfiguring DMA2 * digital_signal: remove assembly code, limiting the performance but increasing portability * added recovery if the timer already expired * digital_signal: fix memory leak * digital_signal: keep lock until all DMA transfers have finished * DigitalSequence: fix issues with concatenation of same levels and spurious bit flips * DigitalSignal: use cyclic DMA buffer for sequences * update api_symbols.csv * Update api_symbols.csv for f18 target * Patches from @gornekich to fix linter warnings. * Remove some redundant if checks * Remove some magic numbers and reformat. * Remove forced terminating edge. Signed-off-by: g3gg0.de Co-authored-by: gornekich Co-authored-by: Tiernan Messmer Co-authored-by: Aleksandr Kutuzov --- .../unit_tests/nfc/nfc_nfca_signal_long.nfc | 2 +- .../unit_tests/nfc/nfc_nfca_signal_short.nfc | 2 +- firmware/targets/f18/api_symbols.csv | 12 +- firmware/targets/f7/api_symbols.csv | 22 +- firmware/targets/f7/target.json | 1 + lib/SConscript | 3 + lib/digital_signal/digital_signal.c | 640 +++++++++++++++--- lib/digital_signal/digital_signal.h | 40 +- lib/pulse_reader/SConscript | 27 + lib/pulse_reader/pulse_reader.c | 233 +++++++ lib/pulse_reader/pulse_reader.h | 122 ++++ 11 files changed, 1020 insertions(+), 84 deletions(-) create mode 100644 lib/pulse_reader/SConscript create mode 100644 lib/pulse_reader/pulse_reader.c create mode 100644 lib/pulse_reader/pulse_reader.h diff --git a/assets/unit_tests/nfc/nfc_nfca_signal_long.nfc b/assets/unit_tests/nfc/nfc_nfca_signal_long.nfc index fae69cb5cc4..dd6a2ff8e67 100644 --- a/assets/unit_tests/nfc/nfc_nfca_signal_long.nfc +++ b/assets/unit_tests/nfc/nfc_nfca_signal_long.nfc @@ -3,4 +3,4 @@ Version: 1 Data length: 18 Plain data: f1 99 41 43 a1 2f 23 01 de f3 c5 8d 91 4b 1e 50 4a c9 Timings length: 1304 -Timings: 37 37 36 37 37 37 36 339 37 37 36 37 37 37 36 641 37 37 37 36 37 37 37 338 37 37 37 36 37 37 37 339 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 339 37 36 37 37 37 36 37 339 37 36 37 37 37 36 37 339 37 37 36 37 37 37 36 641 37 37 37 36 37 37 37 36 37 37 37 36 37 37 37 640 37 37 37 37 36 37 37 339 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 339 37 36 37 37 37 36 37 641 37 37 36 37 37 37 36 339 37 37 36 37 37 37 36 37 37 37 36 37 37 37 37 640 37 37 37 36 37 37 37 36 37 37 37 36 37 37 37 641 36 37 37 37 36 37 37 339 37 36 37 37 37 36 37 339 37 36 37 37 37 36 37 339 37 37 36 37 37 37 36 339 37 37 36 37 37 37 36 37 37 37 37 36 37 37 37 640 37 37 37 36 37 37 37 339 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 339 37 36 37 37 37 36 37 641 37 36 37 37 37 37 36 339 37 37 36 37 37 37 36 339 37 37 36 37 37 37 37 338 37 37 37 36 37 37 37 36 37 37 37 36 37 37 37 641 36 37 37 37 36 37 37 339 36 37 37 37 37 36 37 37 37 36 37 37 37 36 37 641 37 37 36 37 37 37 36 339 37 37 36 37 37 37 36 339 37 37 37 36 37 37 37 338 37 37 37 36 37 37 37 36 37 37 37 36 37 37 37 641 36 37 37 37 36 37 37 37 36 37 37 37 37 36 37 641 37 36 37 37 37 36 37 37 37 36 37 37 37 37 36 339 37 37 36 37 37 37 36 339 37 37 37 36 37 37 37 338 37 37 37 36 37 37 37 641 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 641 37 36 37 37 37 36 37 339 37 36 37 37 37 36 37 339 37 37 36 37 37 37 36 37 37 37 36 37 37 37 36 339 37 37 37 36 37 37 37 640 37 37 37 36 37 37 37 339 36 37 37 37 36 37 37 339 36 37 37 37 37 36 37 37 37 36 37 37 37 36 37 641 37 36 37 37 37 37 36 339 37 37 36 37 37 37 36 339 37 37 37 36 37 37 37 36 37 37 37 36 37 37 37 641 36 37 37 37 36 37 37 339 36 37 37 37 36 37 37 339 37 36 37 37 37 36 37 339 37 36 37 37 37 36 37 339 37 37 36 37 37 37 36 339 37 37 36 37 37 37 36 339 37 37 37 36 37 37 37 338 37 37 37 36 37 37 37 339 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 339 36 37 37 37 37 36 37 339 37 36 37 37 37 36 37 339 37 37 36 37 37 37 36 641 37 37 36 37 37 37 36 37 37 37 37 36 37 37 37 338 37 37 37 36 37 37 37 641 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 339 37 36 37 37 37 36 37 641 37 36 37 37 37 37 36 339 37 37 36 37 37 37 36 37 37 37 36 37 37 37 36 339 37 37 37 36 37 37 37 338 37 37 37 36 37 37 37 339 36 37 37 37 36 37 37 641 37 36 37 37 37 36 37 37 37 36 37 37 37 36 37 641 37 37 36 37 37 37 36 37 37 37 36 37 37 37 36 641 37 37 37 36 37 37 37 338 37 37 37 36 37 37 37 339 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 339 36 37 37 37 37 36 37 641 37 36 37 37 37 36 37 37 37 36 37 37 37 37 36 641 37 37 36 37 37 37 36 37 37 37 37 36 37 37 37 338 37 37 37 36 37 37 37 641 36 37 37 37 36 37 37 339 36 37 37 37 36 37 37 339 37 36 37 37 37 36 37 37 37 36 37 37 37 36 37 641 37 37 36 37 37 37 36 37 37 37 36 37 37 37 36 641 37 37 37 36 37 37 37 338 37 37 37 37 36 37 37 339 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 641 37 36 37 37 37 36 37 339 37 37 36 37 37 37 36 37 37 37 36 37 37 37 36 641 37 37 37 36 37 37 37 36 37 37 37 36 37 37 37 338 37 37 37 36 37 37 37 641 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 641 37 36 37 37 37 36 37 339 37 37 36 37 37 37 36 37 37 37 36 37 37 37 36 641 37 37 37 36 37 37 37 338 37 37 37 36 37 37 37 339 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 339 37 36 37 37 37 36 37 339 37 36 37 37 37 36 37 339 37 37 36 37 37 37 36 641 37 37 36 37 37 37 37 338 37 37 37 36 37 37 37 338 37 37 37 36 37 37 37 339 36 37 37 37 36 37 37 339 36 37 37 37 37 36 37 339 37 36 37 37 37 36 37 339 37 36 37 37 37 37 36 339 37 37 36 37 37 37 36 37 37 37 36 37 37 37 36 641 37 37 37 36 37 37 37 36 37 37 37 36 37 37 37 641 36 37 37 37 36 37 37 339 37 36 37 37 37 36 37 339 37 36 37 37 37 36 37 37 37 36 37 37 37 36 37 641 37 37 36 37 37 37 36 37 37 37 36 37 37 37 37 640 37 37 37 36 37 37 37 339 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 641 37 36 37 37 37 36 37 339 37 36 37 37 37 36 37 37 37 37 36 37 37 37 36 641 37 37 36 37 37 37 36 339 37 37 37 36 37 37 37 36 37 37 37 36 37 37 37 641 36 37 37 37 36 37 37 339 36 37 37 37 36 37 37 37 37 36 37 37 37 36 37 339 37 36 37 37 37 36 37 641 37 37 36 37 37 37 36 0 +Timings: 37 37 36 37 37 37 36 339 37 37 36 37 37 37 36 641 37 37 37 36 37 37 37 338 37 37 37 36 37 37 37 339 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 339 37 36 37 37 37 36 37 339 37 36 37 37 37 36 37 339 37 37 36 37 37 37 36 641 37 37 37 36 37 37 37 36 37 37 37 36 37 37 37 640 37 37 37 37 36 37 37 339 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 339 37 36 37 37 37 36 37 641 37 37 36 37 37 37 36 339 37 37 36 37 37 37 36 37 37 37 36 37 37 37 37 640 37 37 37 36 37 37 37 36 37 37 37 36 37 37 37 641 36 37 37 37 36 37 37 339 37 36 37 37 37 36 37 339 37 36 37 37 37 36 37 339 37 37 36 37 37 37 36 339 37 37 36 37 37 37 36 37 37 37 37 36 37 37 37 640 37 37 37 36 37 37 37 339 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 339 37 36 37 37 37 36 37 641 37 36 37 37 37 37 36 339 37 37 36 37 37 37 36 339 37 37 36 37 37 37 37 338 37 37 37 36 37 37 37 36 37 37 37 36 37 37 37 641 36 37 37 37 36 37 37 339 36 37 37 37 37 36 37 37 37 36 37 37 37 36 37 641 37 37 36 37 37 37 36 339 37 37 36 37 37 37 36 339 37 37 37 36 37 37 37 338 37 37 37 36 37 37 37 36 37 37 37 36 37 37 37 641 36 37 37 37 36 37 37 37 36 37 37 37 37 36 37 641 37 36 37 37 37 36 37 37 37 36 37 37 37 37 36 339 37 37 36 37 37 37 36 339 37 37 37 36 37 37 37 338 37 37 37 36 37 37 37 641 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 641 37 36 37 37 37 36 37 339 37 36 37 37 37 36 37 339 37 37 36 37 37 37 36 37 37 37 36 37 37 37 36 339 37 37 37 36 37 37 37 640 37 37 37 36 37 37 37 339 36 37 37 37 36 37 37 339 36 37 37 37 37 36 37 37 37 36 37 37 37 36 37 641 37 36 37 37 37 37 36 339 37 37 36 37 37 37 36 339 37 37 37 36 37 37 37 36 37 37 37 36 37 37 37 641 36 37 37 37 36 37 37 339 36 37 37 37 36 37 37 339 37 36 37 37 37 36 37 339 37 36 37 37 37 36 37 339 37 37 36 37 37 37 36 339 37 37 36 37 37 37 36 339 37 37 37 36 37 37 37 338 37 37 37 36 37 37 37 339 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 339 36 37 37 37 37 36 37 339 37 36 37 37 37 36 37 339 37 37 36 37 37 37 36 641 37 37 36 37 37 37 36 37 37 37 37 36 37 37 37 338 37 37 37 36 37 37 37 641 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 339 37 36 37 37 37 36 37 641 37 36 37 37 37 37 36 339 37 37 36 37 37 37 36 37 37 37 36 37 37 37 36 339 37 37 37 36 37 37 37 338 37 37 37 36 37 37 37 339 36 37 37 37 36 37 37 641 37 36 37 37 37 36 37 37 37 36 37 37 37 36 37 641 37 37 36 37 37 37 36 37 37 37 36 37 37 37 36 641 37 37 37 36 37 37 37 338 37 37 37 36 37 37 37 339 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 339 36 37 37 37 37 36 37 641 37 36 37 37 37 36 37 37 37 36 37 37 37 37 36 641 37 37 36 37 37 37 36 37 37 37 37 36 37 37 37 338 37 37 37 36 37 37 37 641 36 37 37 37 36 37 37 339 36 37 37 37 36 37 37 339 37 36 37 37 37 36 37 37 37 36 37 37 37 36 37 641 37 37 36 37 37 37 36 37 37 37 36 37 37 37 36 641 37 37 37 36 37 37 37 338 37 37 37 37 36 37 37 339 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 641 37 36 37 37 37 36 37 339 37 37 36 37 37 37 36 37 37 37 36 37 37 37 36 641 37 37 37 36 37 37 37 36 37 37 37 36 37 37 37 338 37 37 37 36 37 37 37 641 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 641 37 36 37 37 37 36 37 339 37 37 36 37 37 37 36 37 37 37 36 37 37 37 36 641 37 37 37 36 37 37 37 338 37 37 37 36 37 37 37 339 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 339 37 36 37 37 37 36 37 339 37 36 37 37 37 36 37 339 37 37 36 37 37 37 36 641 37 37 36 37 37 37 37 338 37 37 37 36 37 37 37 338 37 37 37 36 37 37 37 339 36 37 37 37 36 37 37 339 36 37 37 37 37 36 37 339 37 36 37 37 37 36 37 339 37 36 37 37 37 37 36 339 37 37 36 37 37 37 36 37 37 37 36 37 37 37 36 641 37 37 37 36 37 37 37 36 37 37 37 36 37 37 37 641 36 37 37 37 36 37 37 339 37 36 37 37 37 36 37 339 37 36 37 37 37 36 37 37 37 36 37 37 37 36 37 641 37 37 36 37 37 37 36 37 37 37 36 37 37 37 37 640 37 37 37 36 37 37 37 339 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 641 37 36 37 37 37 36 37 339 37 36 37 37 37 36 37 37 37 37 36 37 37 37 36 641 37 37 36 37 37 37 36 339 37 37 37 36 37 37 37 36 37 37 37 36 37 37 37 641 36 37 37 37 36 37 37 339 36 37 37 37 36 37 37 37 37 36 37 37 37 36 37 339 37 36 37 37 37 36 37 641 37 37 36 37 37 37 36 37 diff --git a/assets/unit_tests/nfc/nfc_nfca_signal_short.nfc b/assets/unit_tests/nfc/nfc_nfca_signal_short.nfc index 3b7e2d9e973..f447fca26ef 100644 --- a/assets/unit_tests/nfc/nfc_nfca_signal_short.nfc +++ b/assets/unit_tests/nfc/nfc_nfca_signal_short.nfc @@ -3,4 +3,4 @@ Version: 1 Data length: 4 Plain data: 14 d8 a0 c9 Timings length: 296 -Timings: 37 37 36 37 37 37 36 641 37 37 36 37 37 37 37 338 37 37 37 36 37 37 37 36 37 37 37 36 37 37 37 641 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 641 37 36 37 37 37 36 37 339 37 36 37 37 37 37 36 339 37 37 36 37 37 37 36 339 37 37 37 36 37 37 37 338 37 37 37 36 37 37 37 338 37 37 37 37 36 37 37 339 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 339 37 36 37 37 37 36 37 641 37 37 36 37 37 37 36 37 37 37 36 37 37 37 36 339 37 37 36 37 37 37 37 640 37 37 37 36 37 37 37 339 36 37 37 37 36 37 37 339 36 37 37 37 36 37 37 339 37 36 37 37 37 36 37 339 37 36 37 37 37 36 37 339 37 37 36 37 37 37 36 37 37 37 36 37 37 37 36 641 37 37 37 36 37 37 37 36 37 37 37 36 37 37 37 641 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 641 37 36 37 37 37 36 37 339 37 36 37 37 37 37 36 37 37 37 36 37 37 37 36 641 37 37 36 37 37 37 37 338 37 37 37 36 37 37 37 36 37 37 37 36 37 37 37 339 36 37 37 37 36 37 37 641 36 37 37 37 37 36 37 0 +Timings: 37 37 36 37 37 37 36 641 37 37 36 37 37 37 37 338 37 37 37 36 37 37 37 36 37 37 37 36 37 37 37 641 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 641 37 36 37 37 37 36 37 339 37 36 37 37 37 37 36 339 37 37 36 37 37 37 36 339 37 37 37 36 37 37 37 338 37 37 37 36 37 37 37 338 37 37 37 37 36 37 37 339 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 339 37 36 37 37 37 36 37 641 37 37 36 37 37 37 36 37 37 37 36 37 37 37 36 339 37 37 36 37 37 37 37 640 37 37 37 36 37 37 37 339 36 37 37 37 36 37 37 339 36 37 37 37 36 37 37 339 37 36 37 37 37 36 37 339 37 36 37 37 37 36 37 339 37 37 36 37 37 37 36 37 37 37 36 37 37 37 36 641 37 37 37 36 37 37 37 36 37 37 37 36 37 37 37 641 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 641 37 36 37 37 37 36 37 339 37 36 37 37 37 37 36 37 37 37 36 37 37 37 36 641 37 37 36 37 37 37 37 338 37 37 37 36 37 37 37 36 37 37 37 36 37 37 37 339 36 37 37 37 36 37 37 641 36 37 37 37 37 36 37 37 diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index 7a892e1ccfa..828c771662c 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,26.1,, +Version,+,26.2,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -156,6 +156,7 @@ Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_tim.h,, Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_usart.h,, Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_utils.h,, Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_wwdg.h,, +Header,+,lib/pulse_reader/pulse_reader.h,, Header,+,lib/toolbox/args.h,, Header,+,lib/toolbox/crc32_calc.h,, Header,+,lib/toolbox/dir_walk.h,, @@ -1575,6 +1576,15 @@ Function,+,protocol_dict_render_brief_data,void,"ProtocolDict*, FuriString*, siz Function,+,protocol_dict_render_data,void,"ProtocolDict*, FuriString*, size_t" Function,+,protocol_dict_set_data,void,"ProtocolDict*, size_t, const uint8_t*, size_t" Function,-,pselect,int,"int, fd_set*, fd_set*, fd_set*, const timespec*, const sigset_t*" +Function,-,pulse_reader_alloc,PulseReader*,"const GpioPin*, uint32_t" +Function,-,pulse_reader_free,void,PulseReader* +Function,-,pulse_reader_receive,uint32_t,"PulseReader*, int" +Function,-,pulse_reader_samples,uint32_t,PulseReader* +Function,-,pulse_reader_set_bittime,void,"PulseReader*, uint32_t" +Function,-,pulse_reader_set_pull,void,"PulseReader*, GpioPull" +Function,-,pulse_reader_set_timebase,void,"PulseReader*, PulseReaderUnit" +Function,-,pulse_reader_start,void,PulseReader* +Function,-,pulse_reader_stop,void,PulseReader* Function,-,putc,int,"int, FILE*" Function,-,putc_unlocked,int,"int, FILE*" Function,-,putchar,int,int diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 6cb421a1bde..d73a1c7bc05 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,26.1,, +Version,+,26.2,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -175,6 +175,7 @@ Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_tim.h,, Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_usart.h,, Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_utils.h,, Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_wwdg.h,, +Header,+,lib/pulse_reader/pulse_reader.h,, Header,+,lib/subghz/blocks/const.h,, Header,+,lib/subghz/blocks/decoder.h,, Header,+,lib/subghz/blocks/encoder.h,, @@ -738,6 +739,16 @@ Function,+,dialog_message_set_text,void,"DialogMessage*, const char*, uint8_t, u Function,+,dialog_message_show,DialogMessageButton,"DialogsApp*, const DialogMessage*" Function,+,dialog_message_show_storage_error,void,"DialogsApp*, const char*" Function,-,difftime,double,"time_t, time_t" +Function,-,digital_sequence_add,void,"DigitalSequence*, uint8_t" +Function,-,digital_sequence_alloc,DigitalSequence*,"uint32_t, const GpioPin*" +Function,-,digital_sequence_clear,void,DigitalSequence* +Function,-,digital_sequence_free,void,DigitalSequence* +Function,-,digital_sequence_send,_Bool,DigitalSequence* +Function,-,digital_sequence_set_sendtime,void,"DigitalSequence*, uint32_t" +Function,-,digital_sequence_set_signal,void,"DigitalSequence*, uint8_t, DigitalSignal*" +Function,-,digital_sequence_timebase_correction,void,"DigitalSequence*, float" +Function,-,digital_signal_add,void,"DigitalSignal*, uint32_t" +Function,-,digital_signal_add_pulse,void,"DigitalSignal*, uint32_t, _Bool" Function,-,digital_signal_alloc,DigitalSignal*,uint32_t Function,-,digital_signal_append,_Bool,"DigitalSignal*, DigitalSignal*" Function,-,digital_signal_free,void,DigitalSignal* @@ -2168,6 +2179,15 @@ Function,+,protocol_dict_render_brief_data,void,"ProtocolDict*, FuriString*, siz Function,+,protocol_dict_render_data,void,"ProtocolDict*, FuriString*, size_t" Function,+,protocol_dict_set_data,void,"ProtocolDict*, size_t, const uint8_t*, size_t" Function,-,pselect,int,"int, fd_set*, fd_set*, fd_set*, const timespec*, const sigset_t*" +Function,-,pulse_reader_alloc,PulseReader*,"const GpioPin*, uint32_t" +Function,-,pulse_reader_free,void,PulseReader* +Function,-,pulse_reader_receive,uint32_t,"PulseReader*, int" +Function,-,pulse_reader_samples,uint32_t,PulseReader* +Function,-,pulse_reader_set_bittime,void,"PulseReader*, uint32_t" +Function,-,pulse_reader_set_pull,void,"PulseReader*, GpioPull" +Function,-,pulse_reader_set_timebase,void,"PulseReader*, PulseReaderUnit" +Function,-,pulse_reader_start,void,PulseReader* +Function,-,pulse_reader_stop,void,PulseReader* Function,-,putc,int,"int, FILE*" Function,-,putc_unlocked,int,"int, FILE*" Function,-,putchar,int,int diff --git a/firmware/targets/f7/target.json b/firmware/targets/f7/target.json index 4d97ae7d74b..c503644536e 100644 --- a/firmware/targets/f7/target.json +++ b/firmware/targets/f7/target.json @@ -28,6 +28,7 @@ "flipperformat", "toolbox", "nfc", + "pulse_reader", "microtar", "usb_stm32", "st25rfal002", diff --git a/lib/SConscript b/lib/SConscript index b8a36c87280..8727746d818 100644 --- a/lib/SConscript +++ b/lib/SConscript @@ -4,6 +4,7 @@ env.Append( LINT_SOURCES=[ Dir("app-scened-template"), Dir("digital_signal"), + Dir("pulse_reader"), Dir("drivers"), Dir("flipper_format"), Dir("infrared"), @@ -14,6 +15,7 @@ env.Append( Dir("u8g2"), Dir("update_util"), Dir("print"), + Dir("pulse_reader"), ], ) @@ -93,6 +95,7 @@ libs = env.BuildModules( "mbedtls", "subghz", "nfc", + "pulse_reader", "appframe", "misc", "lfrfid", diff --git a/lib/digital_signal/digital_signal.c b/lib/digital_signal/digital_signal.c index 46ca307a7fb..51a87d22a5b 100644 --- a/lib/digital_signal/digital_signal.c +++ b/lib/digital_signal/digital_signal.c @@ -1,23 +1,98 @@ #include "digital_signal.h" #include +#include +#include +#include + #include #include -#include -#pragma GCC optimize("O3,unroll-loops,Ofast") +/* must be on bank B */ +#define DEBUG_OUTPUT gpio_ext_pb3 + +struct ReloadBuffer { + uint32_t* buffer; /* DMA ringbuffer */ + uint32_t size; /* maximum entry count of the ring buffer */ + uint32_t write_pos; /* current buffer write index */ + uint32_t read_pos; /* current buffer read index */ + bool dma_active; +}; + +struct DigitalSequence { + uint8_t signals_size; + bool bake; + uint32_t sequence_used; + uint32_t sequence_size; + DigitalSignal** signals; + uint8_t* sequence; + const GpioPin* gpio; + uint32_t send_time; + bool send_time_active; + LL_DMA_InitTypeDef dma_config_gpio; + LL_DMA_InitTypeDef dma_config_timer; + uint32_t* gpio_buff; + struct ReloadBuffer* dma_buffer; +}; + +struct DigitalSignalInternals { + uint64_t factor; + uint32_t reload_reg_entries; + uint32_t reload_reg_remainder; + uint32_t gpio_buff[2]; + const GpioPin* gpio; + LL_DMA_InitTypeDef dma_config_gpio; + LL_DMA_InitTypeDef dma_config_timer; +}; + +#define TAG "DigitalSignal" #define F_TIM (64000000.0) -#define T_TIM 1562 //15.625 ns *100 -#define T_TIM_DIV2 781 //15.625 ns / 2 *100 +#define T_TIM 1562 /* 15.625 ns *100 */ +#define T_TIM_DIV2 781 /* 15.625 ns / 2 *100 */ + +/* maximum entry count of the sequence dma ring buffer */ +#define SEQUENCE_DMA_RINGBUFFER_SIZE 32 +/* maximum number of DigitalSignals in a sequence */ +#define SEQUENCE_SIGNALS_SIZE 32 +/* + * if sequence size runs out from the initial value passed to digital_sequence_alloc + * the size will be increased by this amount and reallocated + */ +#define SEQUENCE_SIZE_REALLOCATE_INCREMENT 256 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(uint32_t)); - signal->reload_reg_buff = malloc(max_edges_cnt * sizeof(uint32_t)); + signal->edge_timings = malloc(signal->edges_max_cnt * sizeof(uint32_t)); signal->edge_cnt = 0; + signal->reload_reg_buff = malloc(signal->edges_max_cnt * sizeof(uint32_t)); + + signal->internals = malloc(sizeof(DigitalSignalInternals)); + DigitalSignalInternals* internals = signal->internals; + + internals->factor = 1024 * 1024; + + internals->dma_config_gpio.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH; + internals->dma_config_gpio.Mode = LL_DMA_MODE_CIRCULAR; + internals->dma_config_gpio.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT; + internals->dma_config_gpio.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT; + internals->dma_config_gpio.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_WORD; + internals->dma_config_gpio.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_WORD; + internals->dma_config_gpio.NbData = 2; + internals->dma_config_gpio.PeriphRequest = LL_DMAMUX_REQ_TIM2_UP; + internals->dma_config_gpio.Priority = LL_DMA_PRIORITY_VERYHIGH; + + internals->dma_config_timer.PeriphOrM2MSrcAddress = (uint32_t) & (TIM2->ARR); + internals->dma_config_timer.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH; + internals->dma_config_timer.Mode = LL_DMA_MODE_NORMAL; + internals->dma_config_timer.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT; + internals->dma_config_timer.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT; + internals->dma_config_timer.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_WORD; + internals->dma_config_timer.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_WORD; + internals->dma_config_timer.PeriphRequest = LL_DMAMUX_REQ_TIM2_UP; + internals->dma_config_timer.Priority = LL_DMA_PRIORITY_HIGH; return signal; } @@ -27,6 +102,7 @@ void digital_signal_free(DigitalSignal* signal) { free(signal->edge_timings); free(signal->reload_reg_buff); + free(signal->internals); free(signal); } @@ -37,7 +113,10 @@ bool digital_signal_append(DigitalSignal* signal_a, DigitalSignal* signal_b) { if(signal_a->edges_max_cnt < signal_a->edge_cnt + signal_b->edge_cnt) { return false; } - + /* in case there are no edges in our target signal, the signal to append makes the rules */ + if(!signal_a->edge_cnt) { + signal_a->start_level = signal_b->start_level; + } bool end_level = signal_a->start_level; if(signal_a->edge_cnt) { end_level = signal_a->start_level ^ !(signal_a->edge_cnt % 2); @@ -72,6 +151,32 @@ uint32_t digital_signal_get_edges_cnt(DigitalSignal* signal) { return signal->edge_cnt; } +void digital_signal_add(DigitalSignal* signal, uint32_t ticks) { + furi_assert(signal); + furi_assert(signal->edge_cnt < signal->edges_max_cnt); + + signal->edge_timings[signal->edge_cnt++] = ticks; +} + +void digital_signal_add_pulse(DigitalSignal* signal, uint32_t ticks, bool level) { + furi_assert(signal); + furi_assert(signal->edge_cnt < signal->edges_max_cnt); + + /* virgin signal? add it as the only level */ + if(signal->edge_cnt == 0) { + signal->start_level = level; + signal->edge_timings[signal->edge_cnt++] = ticks; + } else { + bool end_level = signal->start_level ^ !(signal->edge_cnt % 2); + + if(level != end_level) { + signal->edge_timings[signal->edge_cnt++] = ticks; + } else { + signal->edge_timings[signal->edge_cnt - 1] += ticks; + } + } +} + uint32_t digital_signal_get_edge(DigitalSignal* signal, uint32_t edge_num) { furi_assert(signal); furi_assert(edge_num < signal->edge_cnt); @@ -80,94 +185,473 @@ uint32_t digital_signal_get_edge(DigitalSignal* signal, uint32_t edge_num) { } void digital_signal_prepare_arr(DigitalSignal* signal) { - uint32_t t_signal_rest = signal->edge_timings[0]; - uint32_t r_count_tick_arr = 0; - uint32_t r_rest_div = 0; + furi_assert(signal); + + DigitalSignalInternals* internals = signal->internals; + + /* set up signal polarities */ + if(internals->gpio) { + uint32_t bit_set = internals->gpio->pin; + uint32_t bit_reset = internals->gpio->pin << 16; - for(size_t i = 0; i < signal->edge_cnt - 1; i++) { - r_count_tick_arr = t_signal_rest / T_TIM; - r_rest_div = t_signal_rest % T_TIM; - t_signal_rest = signal->edge_timings[i + 1] + r_rest_div; +#ifdef DEBUG_OUTPUT + bit_set |= DEBUG_OUTPUT.pin; + bit_reset |= DEBUG_OUTPUT.pin << 16; +#endif - if(r_rest_div < T_TIM_DIV2) { - signal->reload_reg_buff[i] = r_count_tick_arr - 1; + if(signal->start_level) { + internals->gpio_buff[0] = bit_set; + internals->gpio_buff[1] = bit_reset; } else { - signal->reload_reg_buff[i] = r_count_tick_arr; - t_signal_rest -= T_TIM; + internals->gpio_buff[0] = bit_reset; + internals->gpio_buff[1] = bit_set; } } -} -void digital_signal_send(DigitalSignal* signal, const GpioPin* gpio) { - furi_assert(signal); - furi_assert(gpio); + /* set up edge timings */ + internals->reload_reg_entries = 0; - // Configure gpio as output - furi_hal_gpio_init(gpio, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); + for(size_t pos = 0; pos < signal->edge_cnt; pos++) { + uint32_t edge_scaled = (internals->factor * signal->edge_timings[pos]) / (1024 * 1024); + uint32_t pulse_duration = edge_scaled + internals->reload_reg_remainder; + if(pulse_duration < 10 || pulse_duration > 10000000) { + FURI_LOG_D( + TAG, + "[prepare] pulse_duration out of range: %lu = %lu * %llu", + pulse_duration, + signal->edge_timings[pos], + internals->factor); + pulse_duration = 100; + } + uint32_t pulse_ticks = (pulse_duration + T_TIM_DIV2) / T_TIM; + internals->reload_reg_remainder = pulse_duration - (pulse_ticks * T_TIM); - // 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); + if(pulse_ticks > 1) { + signal->reload_reg_buff[internals->reload_reg_entries++] = pulse_ticks - 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); +static void digital_signal_stop_dma() { + LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_1); + LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_2); + LL_DMA_ClearFlag_TC1(DMA1); + LL_DMA_ClearFlag_TC2(DMA1); +} + +static void digital_signal_stop_timer() { + LL_TIM_DisableCounter(TIM2); + LL_TIM_DisableUpdateEvent(TIM2); + LL_TIM_DisableDMAReq_UPDATE(TIM2); +} + +static void digital_signal_setup_timer() { + digital_signal_stop_timer(); - // 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_SetAutoReload(TIM2, 0xFFFFFFFF); LL_TIM_SetCounter(TIM2, 0); +} + +static void digital_signal_start_timer() { + LL_TIM_EnableCounter(TIM2); LL_TIM_EnableUpdateEvent(TIM2); LL_TIM_EnableDMAReq_UPDATE(TIM2); + LL_TIM_GenerateEvent_UPDATE(TIM2); +} - // Start transactions - LL_TIM_GenerateEvent_UPDATE(TIM2); // Do we really need it? - LL_TIM_EnableCounter(TIM2); +static bool digital_signal_setup_dma(DigitalSignal* signal) { + furi_assert(signal); + DigitalSignalInternals* internals = signal->internals; - while(!LL_DMA_IsActiveFlag_TC2(DMA1)) - ; + if(!signal->internals->reload_reg_entries) { + return false; + } + digital_signal_stop_dma(); - 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); + internals->dma_config_gpio.MemoryOrM2MDstAddress = (uint32_t)internals->gpio_buff; + internals->dma_config_gpio.PeriphOrM2MSrcAddress = (uint32_t) & (internals->gpio->port->BSRR); + internals->dma_config_timer.MemoryOrM2MDstAddress = (uint32_t)signal->reload_reg_buff; + internals->dma_config_timer.NbData = signal->internals->reload_reg_entries; + + /* set up DMA channel 1 and 2 for GPIO and timer copy operations */ + LL_DMA_Init(DMA1, LL_DMA_CHANNEL_1, &internals->dma_config_gpio); + LL_DMA_Init(DMA1, LL_DMA_CHANNEL_2, &internals->dma_config_timer); + + /* enable both DMA channels */ + LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_1); + LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_2); + + return true; +} + +void digital_signal_send(DigitalSignal* signal, const GpioPin* gpio) { + furi_assert(signal); + + if(!signal->edge_cnt) { + return; + } + + /* Configure gpio as output */ + signal->internals->gpio = gpio; + furi_hal_gpio_init( + signal->internals->gpio, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); + + digital_signal_prepare_arr(signal); + + digital_signal_setup_dma(signal); + digital_signal_setup_timer(); + digital_signal_start_timer(); + + while(!LL_DMA_IsActiveFlag_TC2(DMA1)) { + } + + digital_signal_stop_timer(); + digital_signal_stop_dma(); +} + +static void digital_sequence_alloc_signals(DigitalSequence* sequence, uint32_t size) { + sequence->signals_size = size; + sequence->signals = malloc(sequence->signals_size * sizeof(DigitalSignal*)); +} + +static void digital_sequence_alloc_sequence(DigitalSequence* sequence, uint32_t size) { + sequence->sequence_used = 0; + sequence->sequence_size = size; + sequence->sequence = malloc(sequence->sequence_size); + sequence->send_time = 0; + sequence->send_time_active = false; +} + +DigitalSequence* digital_sequence_alloc(uint32_t size, const GpioPin* gpio) { + furi_assert(gpio); + + DigitalSequence* sequence = malloc(sizeof(DigitalSequence)); + + sequence->gpio = gpio; + sequence->bake = false; + + sequence->dma_buffer = malloc(sizeof(struct ReloadBuffer)); + sequence->dma_buffer->size = SEQUENCE_DMA_RINGBUFFER_SIZE; + sequence->dma_buffer->buffer = malloc(sequence->dma_buffer->size * sizeof(uint32_t)); + + sequence->dma_config_gpio.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH; + sequence->dma_config_gpio.Mode = LL_DMA_MODE_CIRCULAR; + sequence->dma_config_gpio.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT; + sequence->dma_config_gpio.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT; + sequence->dma_config_gpio.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_WORD; + sequence->dma_config_gpio.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_WORD; + sequence->dma_config_gpio.NbData = 2; + sequence->dma_config_gpio.PeriphRequest = LL_DMAMUX_REQ_TIM2_UP; + sequence->dma_config_gpio.Priority = LL_DMA_PRIORITY_VERYHIGH; + + sequence->dma_config_timer.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH; + sequence->dma_config_timer.Mode = LL_DMA_MODE_CIRCULAR; + sequence->dma_config_timer.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT; + sequence->dma_config_timer.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT; + sequence->dma_config_timer.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_WORD; + sequence->dma_config_timer.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_WORD; + sequence->dma_config_timer.PeriphOrM2MSrcAddress = (uint32_t) & (TIM2->ARR); + sequence->dma_config_timer.MemoryOrM2MDstAddress = (uint32_t)sequence->dma_buffer->buffer; + sequence->dma_config_timer.NbData = sequence->dma_buffer->size; + sequence->dma_config_timer.PeriphRequest = LL_DMAMUX_REQ_TIM2_UP; + sequence->dma_config_timer.Priority = LL_DMA_PRIORITY_HIGH; + + digital_sequence_alloc_signals(sequence, SEQUENCE_SIGNALS_SIZE); + digital_sequence_alloc_sequence(sequence, size); + + return sequence; +} + +void digital_sequence_free(DigitalSequence* sequence) { + furi_assert(sequence); + + free(sequence->signals); + free(sequence->sequence); + free(sequence->dma_buffer->buffer); + free(sequence->dma_buffer); + free(sequence); +} + +void digital_sequence_set_signal( + DigitalSequence* sequence, + uint8_t signal_index, + DigitalSignal* signal) { + furi_assert(sequence); + furi_assert(signal); + furi_assert(signal_index < sequence->signals_size); + + sequence->signals[signal_index] = signal; + signal->internals->gpio = sequence->gpio; + signal->internals->reload_reg_remainder = 0; + + digital_signal_prepare_arr(signal); +} + +void digital_sequence_set_sendtime(DigitalSequence* sequence, uint32_t send_time) { + furi_assert(sequence); + + sequence->send_time = send_time; + sequence->send_time_active = true; +} + +void digital_sequence_add(DigitalSequence* sequence, uint8_t signal_index) { + furi_assert(sequence); + furi_assert(signal_index < sequence->signals_size); + + if(sequence->sequence_used >= sequence->sequence_size) { + sequence->sequence_size += SEQUENCE_SIZE_REALLOCATE_INCREMENT; + sequence->sequence = realloc(sequence->sequence, sequence->sequence_size); //-V701 + furi_assert(sequence->sequence); + } + + sequence->sequence[sequence->sequence_used++] = signal_index; +} + +static bool digital_sequence_setup_dma(DigitalSequence* sequence) { + furi_assert(sequence); + + digital_signal_stop_dma(); + + sequence->dma_config_gpio.MemoryOrM2MDstAddress = (uint32_t)sequence->gpio_buff; + sequence->dma_config_gpio.PeriphOrM2MSrcAddress = (uint32_t) & (sequence->gpio->port->BSRR); + + /* set up DMA channel 1 and 2 for GPIO and timer copy operations */ + LL_DMA_Init(DMA1, LL_DMA_CHANNEL_1, &sequence->dma_config_gpio); + LL_DMA_Init(DMA1, LL_DMA_CHANNEL_2, &sequence->dma_config_timer); + + /* enable both DMA channels */ + LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_1); + LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_2); + + return true; +} + +static DigitalSignal* digital_sequence_bake(DigitalSequence* sequence) { + furi_assert(sequence); + + uint32_t edges = 0; + + for(uint32_t pos = 0; pos < sequence->sequence_used; pos++) { + uint8_t signal_index = sequence->sequence[pos]; + DigitalSignal* sig = sequence->signals[signal_index]; + + edges += sig->edge_cnt; + } + + DigitalSignal* ret = digital_signal_alloc(edges); + + for(uint32_t pos = 0; pos < sequence->sequence_used; pos++) { + uint8_t signal_index = sequence->sequence[pos]; + DigitalSignal* sig = sequence->signals[signal_index]; + + digital_signal_append(ret, sig); + } + + return ret; +} + +static void digital_sequence_update_pos(DigitalSequence* sequence) { + struct ReloadBuffer* dma_buffer = sequence->dma_buffer; + + dma_buffer->read_pos = dma_buffer->size - LL_DMA_GetDataLength(DMA1, LL_DMA_CHANNEL_2); +} + +static const uint32_t wait_ms = 10; +static const uint32_t wait_ticks = wait_ms * 1000 * 64; + +static void digital_sequence_finish(DigitalSequence* sequence) { + struct ReloadBuffer* dma_buffer = sequence->dma_buffer; + + if(dma_buffer->dma_active) { + uint32_t prev_timer = DWT->CYCCNT; + uint32_t end_pos = (dma_buffer->write_pos + 1) % dma_buffer->size; + do { + uint32_t last_pos = dma_buffer->read_pos; + + digital_sequence_update_pos(sequence); + + /* we are finished, when the DMA transferred the 0xFFFFFFFF-timer which is the current write_pos */ + if(dma_buffer->read_pos == end_pos) { + break; + } + + if(last_pos != dma_buffer->read_pos) { //-V547 + prev_timer = DWT->CYCCNT; + } + if(DWT->CYCCNT - prev_timer > wait_ticks) { + FURI_LOG_D( + TAG, + "[SEQ] hung %lu ms in finish (ARR 0x%08lx, read %lu, write %lu)", + wait_ms, + TIM2->ARR, + dma_buffer->read_pos, + dma_buffer->write_pos); + break; + } + } while(1); + } + + digital_signal_stop_timer(); + digital_signal_stop_dma(); +} + +static void digital_sequence_queue_pulse(DigitalSequence* sequence, uint32_t length) { + struct ReloadBuffer* dma_buffer = sequence->dma_buffer; + + if(dma_buffer->dma_active) { + uint32_t prev_timer = DWT->CYCCNT; + uint32_t end_pos = (dma_buffer->write_pos + 1) % dma_buffer->size; + do { + uint32_t last_pos = dma_buffer->read_pos; + digital_sequence_update_pos(sequence); + + if(dma_buffer->read_pos != end_pos) { + break; + } + + if(last_pos != dma_buffer->read_pos) { //-V547 + prev_timer = DWT->CYCCNT; + } + if(DWT->CYCCNT - prev_timer > wait_ticks) { + FURI_LOG_D( + TAG, + "[SEQ] hung %lu ms in queue (ARR 0x%08lx, read %lu, write %lu)", + wait_ms, + TIM2->ARR, + dma_buffer->read_pos, + dma_buffer->write_pos); + break; + } + } while(1); + } + + dma_buffer->buffer[dma_buffer->write_pos] = length; + dma_buffer->write_pos = (dma_buffer->write_pos + 1) % dma_buffer->size; + dma_buffer->buffer[dma_buffer->write_pos] = 0xFFFFFFFF; +} + +bool digital_sequence_send(DigitalSequence* sequence) { + furi_assert(sequence); + + struct ReloadBuffer* dma_buffer = sequence->dma_buffer; + + furi_hal_gpio_init(sequence->gpio, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); +#ifdef DEBUG_OUTPUT + furi_hal_gpio_init(&DEBUG_OUTPUT, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); +#endif + + if(sequence->bake) { + DigitalSignal* sig = digital_sequence_bake(sequence); + + digital_signal_send(sig, sequence->gpio); + digital_signal_free(sig); + return true; + } + + int32_t remainder = 0; + bool traded_first = false; + + FURI_CRITICAL_ENTER(); + + dma_buffer->dma_active = false; + dma_buffer->buffer[0] = 0xFFFFFFFF; + dma_buffer->read_pos = 0; + dma_buffer->write_pos = 0; + + for(uint32_t seq_pos = 0; seq_pos < sequence->sequence_used; seq_pos++) { + uint8_t signal_index = sequence->sequence[seq_pos]; + DigitalSignal* sig = sequence->signals[signal_index]; + bool last_signal = ((seq_pos + 1) == sequence->sequence_used); + + /* all signals are prepared and we can re-use the GPIO buffer from the fist signal */ + if(seq_pos == 0) { + sequence->gpio_buff = sig->internals->gpio_buff; + } + + for(uint32_t pulse_pos = 0; pulse_pos < sig->internals->reload_reg_entries; pulse_pos++) { + if(traded_first) { + traded_first = false; + continue; + } + uint32_t pulse_length = 0; + bool last_pulse = ((pulse_pos + 1) == sig->internals->reload_reg_entries); + + pulse_length = sig->reload_reg_buff[pulse_pos]; + + /* when we are too late more than half a tick, make the first edge temporarily longer */ + if(remainder >= T_TIM_DIV2) { + remainder -= T_TIM; + pulse_length += 1; + } + remainder += sig->internals->reload_reg_remainder; + + /* last pulse in that signal and have a next signal? */ + if(last_pulse) { + if((seq_pos + 1) < sequence->sequence_used) { + DigitalSignal* sig_next = sequence->signals[sequence->sequence[seq_pos + 1]]; + + /* when a signal ends with the same level as the next signal begins, let the fist signal generate the whole pulse */ + /* beware, we do not want the level after the last edge, but the last level before that edge */ + bool end_level = sig->start_level ^ ((sig->edge_cnt % 2) == 0); + + /* take from the next, add it to the current if they have the same level */ + if(end_level == sig_next->start_level) { + pulse_length += sig_next->reload_reg_buff[0]; + traded_first = true; + } + } + } + + digital_sequence_queue_pulse(sequence, pulse_length); + + /* start transmission when buffer was filled enough */ + bool start_send = sequence->dma_buffer->write_pos >= (sequence->dma_buffer->size - 4); + + /* or it was the last pulse */ + if(last_pulse && last_signal) { + start_send = true; + } + + /* start transmission */ + if(start_send && !dma_buffer->dma_active) { + digital_sequence_setup_dma(sequence); + digital_signal_setup_timer(); + + /* if the send time is specified, wait till the core timer passed beyond that time */ + if(sequence->send_time_active) { + sequence->send_time_active = false; + while(sequence->send_time - DWT->CYCCNT < 0x80000000) { + } + } + digital_signal_start_timer(); + dma_buffer->dma_active = true; + } + } + } + + /* wait until last dma transaction was finished */ + digital_sequence_finish(sequence); + FURI_CRITICAL_EXIT(); + + return true; +} + +void digital_sequence_clear(DigitalSequence* sequence) { + furi_assert(sequence); + + sequence->sequence_used = 0; +} + +void digital_sequence_timebase_correction(DigitalSequence* sequence, float factor) { + for(uint32_t sig_pos = 0; sig_pos < sequence->signals_size; sig_pos++) { + DigitalSignal* signal = sequence->signals[sig_pos]; + + if(signal) { + signal->internals->factor = (uint32_t)(1024 * 1024 * factor); + digital_signal_prepare_arr(signal); + } + } } diff --git a/lib/digital_signal/digital_signal.h b/lib/digital_signal/digital_signal.h index 90905d74b79..404d02605e7 100644 --- a/lib/digital_signal/digital_signal.h +++ b/lib/digital_signal/digital_signal.h @@ -10,18 +10,35 @@ extern "C" { #endif -typedef struct { +/* helper for easier signal generation */ +#define DIGITAL_SIGNAL_MS(x) ((x)*100000000UL) +#define DIGITAL_SIGNAL_US(x) ((x)*100000UL) +#define DIGITAL_SIGNAL_NS(x) ((x)*100UL) +#define DIGITAL_SIGNAL_PS(x) ((x) / 10UL) + +/* using an anonymous type for the internals */ +typedef struct DigitalSignalInternals DigitalSignalInternals; + +/* and a public one for accessing user-side fields */ +typedef struct DigitalSignal { bool start_level; uint32_t edge_cnt; uint32_t edges_max_cnt; uint32_t* edge_timings; - uint32_t* reload_reg_buff; + uint32_t* reload_reg_buff; /* internal, but used by unit tests */ + DigitalSignalInternals* internals; } DigitalSignal; +typedef struct DigitalSequence DigitalSequence; + DigitalSignal* digital_signal_alloc(uint32_t max_edges_cnt); void digital_signal_free(DigitalSignal* signal); +void digital_signal_add(DigitalSignal* signal, uint32_t ticks); + +void digital_signal_add_pulse(DigitalSignal* signal, uint32_t ticks, bool level); + bool digital_signal_append(DigitalSignal* signal_a, DigitalSignal* signal_b); void digital_signal_prepare_arr(DigitalSignal* signal); @@ -34,6 +51,25 @@ uint32_t digital_signal_get_edge(DigitalSignal* signal, uint32_t edge_num); void digital_signal_send(DigitalSignal* signal, const GpioPin* gpio); +DigitalSequence* digital_sequence_alloc(uint32_t size, const GpioPin* gpio); + +void digital_sequence_free(DigitalSequence* sequence); + +void digital_sequence_set_signal( + DigitalSequence* sequence, + uint8_t signal_index, + DigitalSignal* signal); + +void digital_sequence_set_sendtime(DigitalSequence* sequence, uint32_t send_time); + +void digital_sequence_add(DigitalSequence* sequence, uint8_t signal_index); + +bool digital_sequence_send(DigitalSequence* sequence); + +void digital_sequence_clear(DigitalSequence* sequence); + +void digital_sequence_timebase_correction(DigitalSequence* sequence, float factor); + #ifdef __cplusplus } #endif diff --git a/lib/pulse_reader/SConscript b/lib/pulse_reader/SConscript new file mode 100644 index 00000000000..f00851a20d5 --- /dev/null +++ b/lib/pulse_reader/SConscript @@ -0,0 +1,27 @@ +Import("env") + +env.Append( + CPPPATH=[ + "#/lib/pulse_reader", + ], + SDK_HEADERS=[ + File("pulse_reader.h"), + ], +) + +libenv = env.Clone(FW_LIB_NAME="pulse_reader") +libenv.ApplyLibFlags() + +libenv.AppendUnique( + CCFLAGS=[ + # Required for lib to be linkable with .faps + "-mword-relocations", + "-mlong-calls", + ], +) + +sources = libenv.GlobRecursive("*.c*") + +lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources) +libenv.Install("${LIB_DIST_DIR}", lib) +Return("lib") diff --git a/lib/pulse_reader/pulse_reader.c b/lib/pulse_reader/pulse_reader.c new file mode 100644 index 00000000000..74d99fe801c --- /dev/null +++ b/lib/pulse_reader/pulse_reader.c @@ -0,0 +1,233 @@ +#include "pulse_reader.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + +struct PulseReader { + uint32_t* timer_buffer; + uint32_t* gpio_buffer; + uint32_t size; + uint32_t pos; + uint32_t timer_value; + uint32_t gpio_value; + uint32_t gpio_mask; + uint32_t unit_multiplier; + uint32_t unit_divider; + uint32_t bit_time; + uint32_t dma_channel; + const GpioPin* gpio; + GpioPull pull; + LL_DMA_InitTypeDef dma_config_timer; + LL_DMA_InitTypeDef dma_config_gpio; +}; + +#define GPIO_PIN_MAP(pin, prefix) \ + (((pin) == (LL_GPIO_PIN_0)) ? prefix##0 : \ + ((pin) == (LL_GPIO_PIN_1)) ? prefix##1 : \ + ((pin) == (LL_GPIO_PIN_2)) ? prefix##2 : \ + ((pin) == (LL_GPIO_PIN_3)) ? prefix##3 : \ + ((pin) == (LL_GPIO_PIN_4)) ? prefix##4 : \ + ((pin) == (LL_GPIO_PIN_5)) ? prefix##5 : \ + ((pin) == (LL_GPIO_PIN_6)) ? prefix##6 : \ + ((pin) == (LL_GPIO_PIN_7)) ? prefix##7 : \ + ((pin) == (LL_GPIO_PIN_8)) ? prefix##8 : \ + ((pin) == (LL_GPIO_PIN_9)) ? prefix##9 : \ + ((pin) == (LL_GPIO_PIN_10)) ? prefix##10 : \ + ((pin) == (LL_GPIO_PIN_11)) ? prefix##11 : \ + ((pin) == (LL_GPIO_PIN_12)) ? prefix##12 : \ + ((pin) == (LL_GPIO_PIN_13)) ? prefix##13 : \ + ((pin) == (LL_GPIO_PIN_14)) ? prefix##14 : \ + prefix##15) + +#define GET_DMAMUX_EXTI_LINE(pin) GPIO_PIN_MAP(pin, LL_DMAMUX_REQ_GEN_EXTI_LINE) + +PulseReader* pulse_reader_alloc(const GpioPin* gpio, uint32_t size) { + PulseReader* signal = malloc(sizeof(PulseReader)); + signal->timer_buffer = malloc(size * sizeof(uint32_t)); + signal->gpio_buffer = malloc(size * sizeof(uint32_t)); + signal->dma_channel = LL_DMA_CHANNEL_4; + signal->gpio = gpio; + signal->pull = GpioPullNo; + signal->size = size; + signal->timer_value = 0; + signal->pos = 0; + + pulse_reader_set_timebase(signal, PulseReaderUnit64MHz); + pulse_reader_set_bittime(signal, 1); + + signal->dma_config_timer.Direction = LL_DMA_DIRECTION_PERIPH_TO_MEMORY; + signal->dma_config_timer.PeriphOrM2MSrcAddress = (uint32_t) & (TIM2->CNT); + signal->dma_config_timer.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT; + signal->dma_config_timer.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_WORD; + signal->dma_config_timer.MemoryOrM2MDstAddress = (uint32_t)signal->timer_buffer; + signal->dma_config_timer.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT; + signal->dma_config_timer.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_WORD; + signal->dma_config_timer.Mode = LL_DMA_MODE_CIRCULAR; + signal->dma_config_timer.PeriphRequest = + LL_DMAMUX_REQ_GENERATOR0; /* executes LL_DMA_SetPeriphRequest */ + signal->dma_config_timer.Priority = LL_DMA_PRIORITY_VERYHIGH; + + signal->dma_config_gpio.Direction = LL_DMA_DIRECTION_PERIPH_TO_MEMORY; + signal->dma_config_gpio.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT; + signal->dma_config_gpio.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_WORD; + signal->dma_config_gpio.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT; + signal->dma_config_gpio.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_WORD; + signal->dma_config_gpio.Mode = LL_DMA_MODE_CIRCULAR; + signal->dma_config_gpio.PeriphRequest = + LL_DMAMUX_REQ_GENERATOR0; /* executes LL_DMA_SetPeriphRequest */ + signal->dma_config_gpio.Priority = LL_DMA_PRIORITY_VERYHIGH; + + return signal; +} + +void pulse_reader_set_timebase(PulseReader* signal, PulseReaderUnit unit) { + switch(unit) { + case PulseReaderUnit64MHz: + signal->unit_multiplier = 1; + signal->unit_divider = 1; + break; + case PulseReaderUnitPicosecond: + signal->unit_multiplier = 15625; + signal->unit_divider = 1; + break; + case PulseReaderUnitNanosecond: + signal->unit_multiplier = 15625; + signal->unit_divider = 1000; + break; + case PulseReaderUnitMicrosecond: + signal->unit_multiplier = 15625; + signal->unit_divider = 1000000; + break; + } +} + +void pulse_reader_set_bittime(PulseReader* signal, uint32_t bit_time) { + signal->bit_time = bit_time; +} + +void pulse_reader_set_pull(PulseReader* signal, GpioPull pull) { + signal->pull = pull; +} + +void pulse_reader_free(PulseReader* signal) { + furi_assert(signal); + + free(signal->timer_buffer); + free(signal->gpio_buffer); + free(signal); +} + +uint32_t pulse_reader_samples(PulseReader* signal) { + uint32_t dma_pos = signal->size - (uint32_t)LL_DMA_GetDataLength(DMA1, signal->dma_channel); + + return ((signal->pos + signal->size) - dma_pos) % signal->size; +} + +void pulse_reader_stop(PulseReader* signal) { + LL_DMA_DisableChannel(DMA1, signal->dma_channel); + LL_DMA_DisableChannel(DMA1, signal->dma_channel + 1); + LL_DMAMUX_DisableRequestGen(NULL, LL_DMAMUX_REQ_GEN_0); + LL_TIM_DisableCounter(TIM2); + furi_hal_gpio_init_simple(signal->gpio, GpioModeAnalog); +} + +void pulse_reader_start(PulseReader* signal) { + /* configure DMA to read from a timer peripheral */ + signal->dma_config_timer.NbData = signal->size; + + signal->dma_config_gpio.PeriphOrM2MSrcAddress = (uint32_t) & (signal->gpio->port->IDR); + signal->dma_config_gpio.MemoryOrM2MDstAddress = (uint32_t)signal->gpio_buffer; + signal->dma_config_gpio.NbData = signal->size; + + /* start counter */ + 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, 0xFFFFFFFF); + LL_TIM_SetCounter(TIM2, 0); + LL_TIM_EnableCounter(TIM2); + + /* generator 0 gets fed by EXTI_LINEn */ + LL_DMAMUX_SetRequestSignalID( + NULL, LL_DMAMUX_REQ_GEN_0, GET_DMAMUX_EXTI_LINE(signal->gpio->pin)); + /* trigger on rising edge of the interrupt */ + LL_DMAMUX_SetRequestGenPolarity(NULL, LL_DMAMUX_REQ_GEN_0, LL_DMAMUX_REQ_GEN_POL_RISING); + /* now enable request generation again */ + LL_DMAMUX_EnableRequestGen(NULL, LL_DMAMUX_REQ_GEN_0); + + /* we need the EXTI to be configured as interrupt generating line, but no ISR registered */ + furi_hal_gpio_init_ex( + signal->gpio, GpioModeInterruptRiseFall, signal->pull, GpioSpeedVeryHigh, GpioAltFnUnused); + + /* capture current timer */ + signal->pos = 0; + signal->timer_value = TIM2->CNT; + signal->gpio_mask = signal->gpio->pin; + signal->gpio_value = signal->gpio->port->IDR & signal->gpio_mask; + + /* now set up DMA with these settings */ + LL_DMA_Init(DMA1, signal->dma_channel, &signal->dma_config_timer); + LL_DMA_Init(DMA1, signal->dma_channel + 1, &signal->dma_config_gpio); + LL_DMA_EnableChannel(DMA1, signal->dma_channel); + LL_DMA_EnableChannel(DMA1, signal->dma_channel + 1); +} + +uint32_t pulse_reader_receive(PulseReader* signal, int timeout_us) { + uint32_t start_time = DWT->CYCCNT; + uint32_t timeout_ticks = timeout_us * (F_TIM2 / 1000000); + + do { + /* get the DMA's next write position by reading "remaining length" register */ + uint32_t dma_pos = + signal->size - (uint32_t)LL_DMA_GetDataLength(DMA1, signal->dma_channel); + + /* the DMA has advanced in the ringbuffer */ + if(dma_pos != signal->pos) { + uint32_t delta = signal->timer_buffer[signal->pos] - signal->timer_value; + uint32_t last_gpio_value = signal->gpio_value; + + signal->gpio_value = signal->gpio_buffer[signal->pos]; + + /* check if the GPIO really toggled. if not, we lost an edge :( */ + if(((last_gpio_value ^ signal->gpio_value) & signal->gpio_mask) != signal->gpio_mask) { + signal->gpio_value ^= signal->gpio_mask; + return PULSE_READER_LOST_EDGE; + } + signal->timer_value = signal->timer_buffer[signal->pos]; + + signal->pos++; + signal->pos %= signal->size; + + uint32_t delta_unit = 0; + + /* probably larger values, so choose a wider data type */ + if(signal->unit_divider > 1) { + delta_unit = + (uint32_t)((uint64_t)delta * (uint64_t)signal->unit_multiplier / signal->unit_divider); + } else { + delta_unit = delta * signal->unit_multiplier; + } + + /* if to be scaled to bit times, save a few instructions. should be faster */ + if(signal->bit_time > 1) { + return (delta_unit + signal->bit_time / 2) / signal->bit_time; + } + + return delta_unit; + } + + /* check for timeout */ + uint32_t elapsed = DWT->CYCCNT - start_time; + + if(elapsed > timeout_ticks) { + return PULSE_READER_NO_EDGE; + } + } while(true); +} diff --git a/lib/pulse_reader/pulse_reader.h b/lib/pulse_reader/pulse_reader.h new file mode 100644 index 00000000000..62c5f2fa46d --- /dev/null +++ b/lib/pulse_reader/pulse_reader.h @@ -0,0 +1,122 @@ +#pragma once + +#include +#include +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define PULSE_READER_NO_EDGE (0xFFFFFFFFUL) +#define PULSE_READER_LOST_EDGE (0xFFFFFFFEUL) +#define F_TIM2 (64000000UL) + +/** + * unit of the edge durations to return + */ +typedef enum { + PulseReaderUnit64MHz, + PulseReaderUnitPicosecond, + PulseReaderUnitNanosecond, + PulseReaderUnitMicrosecond, +} PulseReaderUnit; + +/* using an anonymous type */ +typedef struct PulseReader PulseReader; + +/** Allocate a PulseReader object + * + * Allocates memory for a ringbuffer and initalizes the object + * + * @param[in] gpio the GPIO to use. will get configured as input. + * @param[in] size number of edges to buffer + */ +PulseReader* pulse_reader_alloc(const GpioPin* gpio, uint32_t size); + +/** Free a PulseReader object + * + * Frees all memory of the given object + * + * @param[in] signal previously allocated PulseReader object. + */ +void pulse_reader_free(PulseReader* signal); + +/** Start signal capturing + * + * Initializes DMA1, TIM2 and DMAMUX_REQ_GEN_0 to automatically capture timer values. + * Ensure that interrupts are always enabled, as the used EXTI line is handled as one. + * + * @param[in] signal previously allocated PulseReader object. + */ +void pulse_reader_start(PulseReader* signal); + +/** Stop signal capturing + * + * Frees DMA1, TIM2 and DMAMUX_REQ_GEN_0 + * + * @param[in] signal previously allocated PulseReader object. + */ +void pulse_reader_stop(PulseReader* signal); + +/** Recevie a sample from ringbuffer + * + * Waits for the specified time until a new edge gets detected. + * If not configured otherwise, the pulse duration will be in picosecond resolution. + * If a bittime was configured, the return value will contain the properly rounded + * number of bit times measured. + * + * @param[in] signal previously allocated PulseReader object. + * @param[in] timeout_us time to wait for a signal [µs] + * + * @returns the scaled value of the pulse duration + */ +uint32_t pulse_reader_receive(PulseReader* signal, int timeout_us); + +/** Get available samples + * + * Get the number of available samples in the ringbuffer + * + * @param[in] signal previously allocated PulseReader object. + * + * @returns the number of samples in buffer + */ +uint32_t pulse_reader_samples(PulseReader* signal); + +/** Set timebase + * + * Set the timebase to be used when returning pulse duration. + * + * @param[in] signal previously allocated PulseReader object. + * @param[in] unit PulseReaderUnit64MHz or PulseReaderUnitPicosecond + */ +void pulse_reader_set_timebase(PulseReader* signal, PulseReaderUnit unit); + +/** Set bit time + * + * Set the number of timebase units per bit. + * When set, the pulse_reader_receive() will return an already rounded + * bit count value instead of the raw duration. + * + * Set to 1 to return duration again. + * + * @param[in] signal previously allocated PulseReader object. + * @param[in] bit_time + */ +void pulse_reader_set_bittime(PulseReader* signal, uint32_t bit_time); + +/** Set GPIO pull direction + * + * Some GPIOs need pulldown, others don't. By default the + * pull direction is GpioPullNo. + * + * @param[in] signal previously allocated PulseReader object. + * @param[in] pull GPIO pull direction + */ +void pulse_reader_set_pull(PulseReader* signal, GpioPull pull); + +#ifdef __cplusplus +} +#endif From d5403a089cb8f364f18e656ae4e9d577495cb9ce Mon Sep 17 00:00:00 2001 From: Nathan Nye Date: Mon, 8 May 2023 21:45:59 -0400 Subject: [PATCH 549/824] Add Mfkey32 application (#2517) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add Mfkey32 application * Fine tune RAM requirements, use FZ notification service API * Fix PVS security warnings, fix issue with cracking keys on the FZ * Prefer on-device or Flipper mobile app (seamless) * Mfkey32: change app category to NFC * Mfkey32: set target to f7 only * Silence PVS false positives * Correct PVS codes Co-authored-by: あく --- applications/external/mfkey32/application.fam | 17 + .../external/mfkey32/images/mfkey.png | Bin 0 -> 9060 bytes applications/external/mfkey32/mfkey.png | Bin 0 -> 9060 bytes applications/external/mfkey32/mfkey32.c | 1349 +++++++++++++++++ .../nfc/scenes/nfc_scene_mfkey_complete.c | 4 +- 5 files changed, 1368 insertions(+), 2 deletions(-) create mode 100644 applications/external/mfkey32/application.fam create mode 100644 applications/external/mfkey32/images/mfkey.png create mode 100644 applications/external/mfkey32/mfkey.png create mode 100644 applications/external/mfkey32/mfkey32.c diff --git a/applications/external/mfkey32/application.fam b/applications/external/mfkey32/application.fam new file mode 100644 index 00000000000..9a9cbf581ab --- /dev/null +++ b/applications/external/mfkey32/application.fam @@ -0,0 +1,17 @@ +App( + appid="mfkey32", + name="Mfkey32", + apptype=FlipperAppType.EXTERNAL, + targets=["f7"], + entry_point="mfkey32_main", + requires=[ + "gui", + "storage", + ], + stack_size=1 * 1024, + fap_icon="mfkey.png", + fap_category="Nfc", + fap_author="noproto", + fap_icon_assets="images", + fap_weburl="https://github.com/noproto/FlipperMfkey", +) diff --git a/applications/external/mfkey32/images/mfkey.png b/applications/external/mfkey32/images/mfkey.png new file mode 100644 index 0000000000000000000000000000000000000000..52ab29efb92bf5aa7c790b8043d2d07a7e74704d GIT binary patch literal 9060 zcmeHLc|4SB`ya`gt&*%Y21OdPF9tD_coPmO>|@kR(yI zB1+a0Qo0o*glK}z+_2)bH zofwBpEttwGMFgchm(ef6MoJ!5%BERtYs?BBRG*a@G-?C$&T4 z>l-HDO-E)QRNbuTd}d~^O`ni%^(}aH_>BXK7S&h=H?U4t(jOEkSbeJ&2NVh9yho`S3uecDk^ zGsl~HI*z?%loB30T)tUQvKza5#=NTPPM2ij)d9)Tu{aUjQEKhPhK{((+>GuQWQCANmXV3iq%wW4RXAVlDYK~Py`b~rCH;a``1xuH zu;N0?^P;mU0ej<%U3fJNq~nBeSM+#DVEG=KGmTWRvURONsP6n@*xh~cVwgIKj z3H*{079-s?6d|p8_xKJejmM)o7v(+TwZsRye@fn|+K_5%tM;YyF|0*VzsqwnVif$0f^Ex-Ry>Y*2b$jwh1mA4y_dt(Yq0MWG!l zQD8=mzzecWIMJof`+j4ncC#46MW7_Y@;pt(`$k_;Y9X+}xSHAG>nrL^1QOn69=}nf^by)#38K@bPdb^ja zU4DFmb{$aOL^>`};g;}J=)fHL#=MT)TJd9hx2=C0qVjrI{H5F=V`R4>T4~|J55^K# zS~`h#ZXrW9`^%)O>ejj3*H>g?68D|(PPwcQ13D={ih9{HSlT!M3#iNuo+5}TPZ6%S z1--2Goa++35v-@off(1(xJp-~B4zJ3su|iAJ{LgDX88#ZEAB5lUtz6!!#O0U=$F3m zv6X3sC3}vGt|79Wo}go6A*!1nBurDn$X4}LilvvVc7-XIrkgrRIUA_esN>bC>l^$Y zjYA9fMrqHQol_WSNUUUI)=L8eu=6Nf-dK+QQ)&Z&uE(U>BWb!n=D(J?HU-#dQJJX zQu*N9H_!85=c-FA-YtXGEVh=i+;`WY5fp`8_l6a$G~>`3tCEB{^`-&AACDYR@!K{u zjg8$?#c7B2p2|XY1f4r`-AcYl$atk1-lpfhIKiScyTj|?^qGpRif%i2D=SW}PfzL1 zpy}PU%7p>f4!vH4&&VVfjRr);89vyhe431X5#8&g*%oXsZ51XVdqH5uG41383hHFc zs%*+dzX^TuRqZNAcU+bDfD*rcw6|ErLBeZ;Q(?9cS<1i=Eg2nNLRBj1&_8c}&V5nN z;nT|T%7RCmowlDR9L&mYGilr@IN+ST)m_=_A~n@L#ZTaMgWOEaB4yo%j-!wT=wEnJerb6OScXPK^54z&?-3mEo zEnU70zXSzTYpS!^n!9(8-3wyR}`3UTtl(ntI zh^q9XW=-f>`eK2L@kG)UwxV#F%As)BL(sH|w;o4ow~0{n4#7jm=YDQjGI8y%$l$i@<1+#wt@pThrQSJ8WWeT=ejysE-J(2vPP$7AA z7t#PjEQ_{4@noYyVd_GvOwtD*)Qn(eXC?U)^nCzjFnP9UHc_}g)lPq+Mnkr0CcSV} zu3azNGwDMnvE!;~w&D7FF_-QHM!pi_&Gc3Ti6}y^Z1Vc0&_K*fLl9}vm~85jbk4m* zb9VJ(HI=lV#2f9Pg-_MH@>mB-w357%XQeEQTnjhNNl66U4;{wXoKqbW^CQ>?=l~hVsW)}qN)C|duida%|Evc4^S=B*=YjEOwwGID8a0gQmld8Eu5TMsd+x5HVmZ@w ziI^~u4Lz$#vKtdz#54z3Np~#FUvtp96BhGO25GID*R$?%yKHD+#{BK08#bGmOH3Yn zkh!^Pb8YVCrcS?mM=~D9C1&cpuiwslI^zUeDCg#ej>}bU{P7*p%hlNIbCzOdb;6qx z(P4~R$D|nlS{smP8fWymUw51TaqS)Zq(yFJNOt(+-^v^96hOPRk!b6ZtIS4qU)lN? z7i4y~$q0U0@tV@ZB_8g_b3H_!l*jzY$ZUGnz*G?x)9`|;r$RQDBw7TYJ*m=(Jex*| zn`0{Y79OPS@FMKIZv%panjC4kcvUmeg_0O=+~w65R+c%cZ8QVd%o*HyY@hV8(XPDx zWWuAV`QXvuM`oXRJ`WB)7S)PL6TOu!W#sEd5)asGMz&0^-*G-)^>Qyr$)zXP%6zz2 zWMYdwtn~Fc17%$a0|e*N+-VmBd}yIc6#sL z6nd%Q;m6_|q)Eu4jYLE`D|zrv)?E8NjVbjE3yY5;oy6Fx#>uy~I4YyJLh^&o!cpjg z_{1uo6Rd}?pDnD9n@C|zPnm6Gr<&%tHRs%CcNDa>9XKplo@V(XN~~!8AyQX%$~5B* zQaJFj+)=8kivtkr)%zz z$d^?n9!pgoc;%QfF;Ll@le@UDjQ5I?n9)RjMpi7KzaLy5%X6Ge3pnqbq5t4bXj&_5 zc%iwxG1>jxifW7vDHiQ3@#zD~>Dsdfc(;7E`Qz4g7a|T~1}`6ybGf&wHwE*`liQ6l4oAm{WMG@s9wP+7(TzUQ`oY1 zvtZp-d#RbTTH9TcZ=qg#T0CM~eoE08n@kESNza?K{MeWLRXCzngUV;jrfu)wWC)8R zMix%^#&s^7AIm)_r}R{rtfpQMy5`szWQ0K4|?)7vFus?R867(PxIA#s%=kCR7qxUnwFJa z_crVOZTvl=J!;E+eIb>DAg_G37nmpB(o6q6+QQax)ltiObZ_pqeKUKZ~?StM+0=L_bfEmCs3R=3!k-TGmNa=AFEK*lTDQN@z=DPof(`B+@; z&>r8nrB437e%)Pl4l*k@f{S&mkLlRHsA8Tc#f#|74cCr*u9|Z-ERf4!lqU$BW@W}Y z*<0rCZn&)-Hfjosf$N~|pGwKY>yM9T-6))Z=-;oNY=3Qwk`oMrOl?J3W(GwDg>+)^ zlWUJS?Q@K6k2STr0iIIpNh8$yJ*e9Hw)J?1W$W4_M;lyMX+vAzT`+rzkOu0t}W}?MSzI9alzUxKIQF*!2m0}Rt)Ln+Fv5;%yc}`{>!H@53ANr)h(fVmi z_z9|j#{T1bw}05Ukaaoj?3seiLb-H}fjx140v|e$0uJ)=W^oBY29PCQ0&veChC#qf5Z(a;h?A8Kn8@bP!Dt<{4jf7f zVg?`~+r+{892$dQYi#m`0(dfj?C0@(2{2e-V4zMQN{7wygducwbzyKM42gsS2q-t0 z#iIm4SzI+f#WII6olE5~eR)ha3(V)Fc(DC>1`r4!2Y(?4q*PK6F!;p)|9Od?%cH@_ zzylhH2LQm3a5x?cM?#Uhu1NZ5H=S7J+=if+)T)gbp0$e_llI8DpOvzR@Uv>Ct z^kn+@E@|-5-y><%uQ*?Sj`tFVMupM6={|rXT!0zz8$ACr%vX)y#KV{T&JiAy@lW7< z$?xERcYjR&YkHSlUz!GjF`Meo_mOOD0O5y6ps}e;8e!=cL1SR4SQ-{e#nZ7+G!{#T z;&H&mgHFeI;NU0(4vqSbip=8jC@dBE)-2iVWD^i9S%hz=@bl& zip8Pm=p`!tv=NMK$OaIk4*c7QjW>nIU~_y7AeKy)f6zC9J=2G7$D{C*hQQ*`SUeUe zTr?hw`4Zg{pCg^a1=a+g69Lyj;Fr{CRDub>NC9e-=|l0P!+cquO9K8Z5^R`3bZ;kP zfSt9hidgpMo6!09LjHsjfHVM8Lg6n$z^5g~rEz|Fd;9Z;9av1jDDs=g>6^>|f&&78 z!(j;i^&72}BN^!lN4lKY$w!bO{8IUbHqh;5erA+ zD0m<+49i&~|U;5&I(gYa%w~@b; z-@nuKce?&k2L2NG@9O$HU4JP9e+m3|b^Y7YCI0P`8Jz{Zkpu!C#G)SBy#hX_iTRp2 zb3ve0YxzF`(CIVsz@P|^Y()|o5m1m**Icz`(|KTsNH#XIm+wqSB#VRKqJnFuKD${< z+lqOd=k9Vf5imS?kD?ArPBsfDKavwMx2f2_Do?p$?=KNlw!ZwhcI`XYiHy&no*dYF T2_+RDfDS|^SsNGcaF6&O#taN} literal 0 HcmV?d00001 diff --git a/applications/external/mfkey32/mfkey.png b/applications/external/mfkey32/mfkey.png new file mode 100644 index 0000000000000000000000000000000000000000..52ab29efb92bf5aa7c790b8043d2d07a7e74704d GIT binary patch literal 9060 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 T2_+RDfDS|^SsNGcaF6&O#taN} literal 0 HcmV?d00001 diff --git a/applications/external/mfkey32/mfkey32.c b/applications/external/mfkey32/mfkey32.c new file mode 100644 index 00000000000..2934d837aa1 --- /dev/null +++ b/applications/external/mfkey32/mfkey32.c @@ -0,0 +1,1349 @@ +#pragma GCC optimize("O3") +#pragma GCC optimize("-funroll-all-loops") + +// TODO: Add keys to top of the user dictionary, not the bottom +// TODO: More efficient dictionary bruteforce by scanning through hardcoded very common keys and previously found dictionary keys first? +// (a cache for napi_key_already_found_for_nonce) + +#include +#include +#include "time.h" +#include +#include +#include +#include +#include "mfkey32_icons.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MF_CLASSIC_DICT_FLIPPER_PATH EXT_PATH("nfc/assets/mf_classic_dict.nfc") +#define MF_CLASSIC_DICT_USER_PATH EXT_PATH("nfc/assets/mf_classic_dict_user.nfc") +#define MF_CLASSIC_NONCE_PATH EXT_PATH("nfc/.mfkey32.log") +#define TAG "Mfkey32" +#define NFC_MF_CLASSIC_KEY_LEN (13) + +#define MIN_RAM 115632 +#define LF_POLY_ODD (0x29CE5C) +#define LF_POLY_EVEN (0x870804) +#define CONST_M1_1 (LF_POLY_EVEN << 1 | 1) +#define CONST_M2_1 (LF_POLY_ODD << 1) +#define CONST_M1_2 (LF_POLY_ODD) +#define CONST_M2_2 (LF_POLY_EVEN << 1 | 1) +#define BIT(x, n) ((x) >> (n)&1) +#define BEBIT(x, n) BIT(x, (n) ^ 24) +#define SWAPENDIAN(x) \ + (x = (x >> 8 & 0xff00ff) | (x & 0xff00ff) << 8, x = x >> 16 | x << 16) //-V1003 +//#define SIZEOF(arr) sizeof(arr) / sizeof(*arr) + +static int eta_round_time = 56; +static int eta_total_time = 900; +// MSB_LIMIT: Chunk size (out of 256) +static int MSB_LIMIT = 16; + +struct Crypto1State { + uint32_t odd, even; +}; +struct Crypto1Params { + uint64_t key; + uint32_t nr0_enc, uid_xor_nt0, uid_xor_nt1, nr1_enc, p64b, ar1_enc; +}; +struct Msb { + int tail; + uint32_t states[768]; +}; + +typedef enum { + EventTypeTick, + EventTypeKey, +} EventType; + +typedef struct { + EventType type; + InputEvent input; +} PluginEvent; + +typedef enum { + MissingNonces, + ZeroNonces, +} MfkeyError; + +typedef enum { + Ready, + Initializing, + DictionaryAttack, + MfkeyAttack, + Complete, + Error, + Help, +} MfkeyState; + +// TODO: Can we eliminate any of the members of this struct? +typedef struct { + FuriMutex* mutex; + MfkeyError err; + MfkeyState mfkey_state; + int cracked; + int unique_cracked; + int num_completed; + int total; + int dict_count; + int search; + int eta_timestamp; + int eta_total; + int eta_round; + bool is_thread_running; + bool close_thread_please; + FuriThread* mfkeythread; +} ProgramState; + +// TODO: Merge this with Crypto1Params? +typedef struct { + uint32_t uid; // serial number + uint32_t nt0; // tag challenge first + uint32_t nt1; // tag challenge second + uint32_t nr0_enc; // first encrypted reader challenge + uint32_t ar0_enc; // first encrypted reader response + uint32_t nr1_enc; // second encrypted reader challenge + uint32_t ar1_enc; // second encrypted reader response +} MfClassicNonce; + +typedef struct { + Stream* stream; + uint32_t total_nonces; + MfClassicNonce* remaining_nonce_array; + size_t remaining_nonces; +} MfClassicNonceArray; + +struct MfClassicDict { + Stream* stream; + uint32_t total_keys; +}; + +static const uint8_t table[256] = { + 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, + 4, 4, 5, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, + 4, 5, 4, 5, 5, 6, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, + 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, + 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, + 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, + 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, + 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, + 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8}; +static const uint8_t lookup1[256] = { + 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, + 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, + 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, 8, 8, 24, 24, 8, 24, 8, 8, + 8, 24, 8, 8, 24, 24, 24, 24, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, + 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, + 0, 16, 0, 0, 16, 16, 16, 16, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, + 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, + 0, 16, 0, 0, 16, 16, 16, 16, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, + 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, 0, 0, 16, 16, 0, 16, 0, 0, + 0, 16, 0, 0, 16, 16, 16, 16, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, + 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24}; +static const uint8_t lookup2[256] = { + 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, + 4, 4, 4, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, + 2, 2, 6, 6, 6, 6, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 2, 2, 6, 6, 2, 6, 2, + 2, 2, 6, 2, 2, 6, 6, 6, 6, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 0, 0, 4, 4, + 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 2, + 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, + 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, + 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, + 2, 6, 2, 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6}; + +uint32_t prng_successor(uint32_t x, uint32_t n) { + SWAPENDIAN(x); + while(n--) x = x >> 1 | (x >> 16 ^ x >> 18 ^ x >> 19 ^ x >> 21) << 31; + return SWAPENDIAN(x); +} + +static inline int filter(uint32_t const x) { + uint32_t f; + f = lookup1[x & 0xff] | lookup2[(x >> 8) & 0xff]; + f |= 0x0d938 >> (x >> 16 & 0xf) & 1; + return BIT(0xEC57E80A, f); +} + +static inline uint8_t evenparity32(uint32_t x) { + if((table[x & 0xff] + table[(x >> 8) & 0xff] + table[(x >> 16) & 0xff] + table[x >> 24]) % 2 == + 0) { + return 0; + } else { + return 1; + } + //return ((table[x & 0xff] + table[(x >> 8) & 0xff] + table[(x >> 16) & 0xff] + table[x >> 24]) % 2) & 0xFF; +} + +static inline void update_contribution(unsigned int data[], int item, int mask1, int mask2) { + int p = data[item] >> 25; + p = p << 1 | evenparity32(data[item] & mask1); + p = p << 1 | evenparity32(data[item] & mask2); + data[item] = p << 24 | (data[item] & 0xffffff); +} + +void crypto1_get_lfsr(struct Crypto1State* state, uint64_t* lfsr) { + int i; + for(*lfsr = 0, i = 23; i >= 0; --i) { + *lfsr = *lfsr << 1 | BIT(state->odd, i ^ 3); + *lfsr = *lfsr << 1 | BIT(state->even, i ^ 3); + } +} + +static inline uint32_t crypt_word(struct Crypto1State* s) { + // "in" and "x" are always 0 (last iteration) + uint32_t res_ret = 0; + uint32_t feedin, t; + for(int i = 0; i <= 31; i++) { + res_ret |= (filter(s->odd) << (24 ^ i)); //-V629 + feedin = LF_POLY_EVEN & s->even; + feedin ^= LF_POLY_ODD & s->odd; + s->even = s->even << 1 | (evenparity32(feedin)); + t = s->odd, s->odd = s->even, s->even = t; + } + return res_ret; +} + +static inline void crypt_word_noret(struct Crypto1State* s, uint32_t in, int x) { + uint8_t ret; + uint32_t feedin, t, next_in; + for(int i = 0; i <= 31; i++) { + next_in = BEBIT(in, i); + ret = filter(s->odd); + feedin = ret & (!!x); + feedin ^= LF_POLY_EVEN & s->even; + feedin ^= LF_POLY_ODD & s->odd; + feedin ^= !!next_in; + s->even = s->even << 1 | (evenparity32(feedin)); + t = s->odd, s->odd = s->even, s->even = t; + } + return; +} + +static inline void rollback_word_noret(struct Crypto1State* s, uint32_t in, int x) { + uint8_t ret; + uint32_t feedin, t, next_in; + for(int i = 31; i >= 0; i--) { + next_in = BEBIT(in, i); + s->odd &= 0xffffff; + t = s->odd, s->odd = s->even, s->even = t; + ret = filter(s->odd); + feedin = ret & (!!x); + feedin ^= s->even & 1; + feedin ^= LF_POLY_EVEN & (s->even >>= 1); + feedin ^= LF_POLY_ODD & s->odd; + feedin ^= !!next_in; + s->even |= (evenparity32(feedin)) << 23; + } + return; +} + +int key_already_found_for_nonce( + uint64_t* keyarray, + int keyarray_size, + uint32_t uid_xor_nt1, + uint32_t nr1_enc, + uint32_t p64b, + uint32_t ar1_enc) { + for(int k = 0; k < keyarray_size; k++) { + struct Crypto1State temp = {0, 0}; + + for(int i = 0; i < 24; i++) { + (&temp)->odd |= (BIT(keyarray[k], 2 * i + 1) << (i ^ 3)); + (&temp)->even |= (BIT(keyarray[k], 2 * i) << (i ^ 3)); + } + + crypt_word_noret(&temp, uid_xor_nt1, 0); + crypt_word_noret(&temp, nr1_enc, 1); + + if(ar1_enc == (crypt_word(&temp) ^ p64b)) { + return 1; + } + } + return 0; +} + +int check_state(struct Crypto1State* t, struct Crypto1Params* p) { + if(!(t->odd | t->even)) return 0; + rollback_word_noret(t, 0, 0); + rollback_word_noret(t, p->nr0_enc, 1); + rollback_word_noret(t, p->uid_xor_nt0, 0); + struct Crypto1State temp = {t->odd, t->even}; + crypt_word_noret(t, p->uid_xor_nt1, 0); + crypt_word_noret(t, p->nr1_enc, 1); + if(p->ar1_enc == (crypt_word(t) ^ p->p64b)) { + crypto1_get_lfsr(&temp, &(p->key)); + return 1; + } + return 0; +} + +static inline int state_loop(unsigned int* states_buffer, int xks, int m1, int m2) { + int states_tail = 0; + int round = 0, s = 0, xks_bit = 0; + + for(round = 1; round <= 12; round++) { + xks_bit = BIT(xks, round); + + for(s = 0; s <= states_tail; s++) { + states_buffer[s] <<= 1; + + if((filter(states_buffer[s]) ^ filter(states_buffer[s] | 1)) != 0) { + states_buffer[s] |= filter(states_buffer[s]) ^ xks_bit; + if(round > 4) { + update_contribution(states_buffer, s, m1, m2); + } + } else if(filter(states_buffer[s]) == xks_bit) { + // TODO: Refactor + if(round > 4) { + states_buffer[++states_tail] = states_buffer[s + 1]; + states_buffer[s + 1] = states_buffer[s] | 1; + update_contribution(states_buffer, s, m1, m2); + s++; + update_contribution(states_buffer, s, m1, m2); + } else { + states_buffer[++states_tail] = states_buffer[++s]; + states_buffer[s] = states_buffer[s - 1] | 1; + } + } else { + states_buffer[s--] = states_buffer[states_tail--]; + } + } + } + + return states_tail; +} + +int binsearch(unsigned int data[], int start, int stop) { + int mid, val = data[stop] & 0xff000000; + while(start != stop) { + mid = (stop - start) >> 1; + if((data[start + mid] ^ 0x80000000) > (val ^ 0x80000000)) + stop = start + mid; + else + start += mid + 1; + } + return start; +} +void quicksort(unsigned int array[], int low, int high) { + //if (SIZEOF(array) == 0) + // return; + if(low >= high) return; + int middle = low + (high - low) / 2; + unsigned int pivot = array[middle]; + int i = low, j = high; + while(i <= j) { + while(array[i] < pivot) { + i++; + } + while(array[j] > pivot) { + j--; + } + if(i <= j) { // swap + int temp = array[i]; + array[i] = array[j]; + array[j] = temp; + i++; + j--; + } + } + if(low < j) { + quicksort(array, low, j); + } + if(high > i) { + quicksort(array, i, high); + } +} +int extend_table(unsigned int data[], int tbl, int end, int bit, int m1, int m2) { + for(data[tbl] <<= 1; tbl <= end; data[++tbl] <<= 1) { + if((filter(data[tbl]) ^ filter(data[tbl] | 1)) != 0) { + data[tbl] |= filter(data[tbl]) ^ bit; + update_contribution(data, tbl, m1, m2); + } else if(filter(data[tbl]) == bit) { + data[++end] = data[tbl + 1]; + data[tbl + 1] = data[tbl] | 1; + update_contribution(data, tbl, m1, m2); + tbl++; + update_contribution(data, tbl, m1, m2); + } else { + data[tbl--] = data[end--]; + } + } + return end; +} + +int old_recover( + unsigned int odd[], + int o_head, + int o_tail, + int oks, + unsigned int even[], + int e_head, + int e_tail, + int eks, + int rem, + int s, + struct Crypto1Params* p, + int first_run) { + int o, e, i; + if(rem == -1) { + for(e = e_head; e <= e_tail; ++e) { + even[e] = (even[e] << 1) ^ evenparity32(even[e] & LF_POLY_EVEN); + for(o = o_head; o <= o_tail; ++o, ++s) { + struct Crypto1State temp = {0, 0}; + temp.even = odd[o]; + temp.odd = even[e] ^ evenparity32(odd[o] & LF_POLY_ODD); + if(check_state(&temp, p)) { + return -1; + } + } + } + return s; + } + if(first_run == 0) { + for(i = 0; (i < 4) && (rem-- != 0); i++) { + oks >>= 1; + eks >>= 1; + o_tail = extend_table( + odd, o_head, o_tail, oks & 1, LF_POLY_EVEN << 1 | 1, LF_POLY_ODD << 1); + if(o_head > o_tail) return s; + e_tail = + extend_table(even, e_head, e_tail, eks & 1, LF_POLY_ODD, LF_POLY_EVEN << 1 | 1); + if(e_head > e_tail) return s; + } + } + first_run = 0; + quicksort(odd, o_head, o_tail); + quicksort(even, e_head, e_tail); + while(o_tail >= o_head && e_tail >= e_head) { + if(((odd[o_tail] ^ even[e_tail]) >> 24) == 0) { + o_tail = binsearch(odd, o_head, o = o_tail); + e_tail = binsearch(even, e_head, e = e_tail); + s = old_recover(odd, o_tail--, o, oks, even, e_tail--, e, eks, rem, s, p, first_run); + if(s == -1) { + break; + } + } else if((odd[o_tail] ^ 0x80000000) > (even[e_tail] ^ 0x80000000)) { + o_tail = binsearch(odd, o_head, o_tail) - 1; + } else { + e_tail = binsearch(even, e_head, e_tail) - 1; + } + } + return s; +} + +static inline int sync_state(ProgramState* program_state) { + int ts = furi_hal_rtc_get_timestamp(); + program_state->eta_round = program_state->eta_round - (ts - program_state->eta_timestamp); + program_state->eta_total = program_state->eta_total - (ts - program_state->eta_timestamp); + program_state->eta_timestamp = ts; + if(program_state->close_thread_please) { + return 1; + } + return 0; +} + +int calculate_msb_tables( + int oks, + int eks, + int msb_round, + struct Crypto1Params* p, + unsigned int* states_buffer, + struct Msb* odd_msbs, + struct Msb* even_msbs, + unsigned int* temp_states_odd, + unsigned int* temp_states_even, + ProgramState* program_state) { + //FURI_LOG_I(TAG, "MSB GO %i", msb_iter); // DEBUG + unsigned int msb_head = (MSB_LIMIT * msb_round); // msb_iter ranges from 0 to (256/MSB_LIMIT)-1 + unsigned int msb_tail = (MSB_LIMIT * (msb_round + 1)); + int states_tail = 0, tail = 0; + int i = 0, j = 0, semi_state = 0, found = 0; + unsigned int msb = 0; + // TODO: Why is this necessary? + memset(odd_msbs, 0, MSB_LIMIT * sizeof(struct Msb)); + memset(even_msbs, 0, MSB_LIMIT * sizeof(struct Msb)); + + for(semi_state = 1 << 20; semi_state >= 0; semi_state--) { + if(semi_state % 32768 == 0) { + if(sync_state(program_state) == 1) { + return 0; + } + } + + if(filter(semi_state) == (oks & 1)) { //-V547 + states_buffer[0] = semi_state; + states_tail = state_loop(states_buffer, oks, CONST_M1_1, CONST_M2_1); + + for(i = states_tail; i >= 0; i--) { + msb = states_buffer[i] >> 24; + if((msb >= msb_head) && (msb < msb_tail)) { + found = 0; + for(j = 0; j < odd_msbs[msb - msb_head].tail - 1; j++) { + if(odd_msbs[msb - msb_head].states[j] == states_buffer[i]) { + found = 1; + break; + } + } + + if(!found) { + tail = odd_msbs[msb - msb_head].tail++; + odd_msbs[msb - msb_head].states[tail] = states_buffer[i]; + } + } + } + } + + if(filter(semi_state) == (eks & 1)) { //-V547 + states_buffer[0] = semi_state; + states_tail = state_loop(states_buffer, eks, CONST_M1_2, CONST_M2_2); + + for(i = 0; i <= states_tail; i++) { + msb = states_buffer[i] >> 24; + if((msb >= msb_head) && (msb < msb_tail)) { + found = 0; + + for(j = 0; j < even_msbs[msb - msb_head].tail; j++) { + if(even_msbs[msb - msb_head].states[j] == states_buffer[i]) { + found = 1; + break; + } + } + + if(!found) { + tail = even_msbs[msb - msb_head].tail++; + even_msbs[msb - msb_head].states[tail] = states_buffer[i]; + } + } + } + } + } + + oks >>= 12; + eks >>= 12; + + for(i = 0; i < MSB_LIMIT; i++) { + if(sync_state(program_state) == 1) { + return 0; + } + // TODO: Why is this necessary? + memset(temp_states_even, 0, sizeof(unsigned int) * (1280)); + memset(temp_states_odd, 0, sizeof(unsigned int) * (1280)); + memcpy(temp_states_odd, odd_msbs[i].states, odd_msbs[i].tail * sizeof(unsigned int)); + memcpy(temp_states_even, even_msbs[i].states, even_msbs[i].tail * sizeof(unsigned int)); + int res = old_recover( + temp_states_odd, + 0, + odd_msbs[i].tail, + oks, + temp_states_even, + 0, + even_msbs[i].tail, + eks, + 3, + 0, + p, + 1); + if(res == -1) { + return 1; + } + //odd_msbs[i].tail = 0; + //even_msbs[i].tail = 0; + } + + return 0; +} + +bool recover(struct Crypto1Params* p, int ks2, ProgramState* program_state) { + bool found = false; + unsigned int* states_buffer = malloc(sizeof(unsigned int) * (2 << 9)); + struct Msb* odd_msbs = (struct Msb*)malloc(MSB_LIMIT * sizeof(struct Msb)); + struct Msb* even_msbs = (struct Msb*)malloc(MSB_LIMIT * sizeof(struct Msb)); + unsigned int* temp_states_odd = malloc(sizeof(unsigned int) * (1280)); + unsigned int* temp_states_even = malloc(sizeof(unsigned int) * (1280)); + int oks = 0, eks = 0; + int i = 0, msb = 0; + for(i = 31; i >= 0; i -= 2) { + oks = oks << 1 | BEBIT(ks2, i); + } + for(i = 30; i >= 0; i -= 2) { + eks = eks << 1 | BEBIT(ks2, i); + } + int bench_start = furi_hal_rtc_get_timestamp(); + program_state->eta_total = eta_total_time; + program_state->eta_timestamp = bench_start; + for(msb = 0; msb <= ((256 / MSB_LIMIT) - 1); msb++) { + program_state->search = msb; + program_state->eta_round = eta_round_time; + program_state->eta_total = eta_total_time - (eta_round_time * msb); + if(calculate_msb_tables( + oks, + eks, + msb, + p, + states_buffer, + odd_msbs, + even_msbs, + temp_states_odd, + temp_states_even, + program_state)) { + int bench_stop = furi_hal_rtc_get_timestamp(); + FURI_LOG_I(TAG, "Cracked in %i seconds", bench_stop - bench_start); + found = true; + break; + } + if(program_state->close_thread_please) { + break; + } + } + free(states_buffer); + free(odd_msbs); + free(even_msbs); + free(temp_states_odd); + free(temp_states_even); + return found; +} + +bool napi_mf_classic_dict_check_presence(MfClassicDictType dict_type) { + Storage* storage = furi_record_open(RECORD_STORAGE); + + bool dict_present = false; + if(dict_type == MfClassicDictTypeSystem) { + dict_present = storage_common_stat(storage, MF_CLASSIC_DICT_FLIPPER_PATH, NULL) == FSE_OK; + } else if(dict_type == MfClassicDictTypeUser) { + dict_present = storage_common_stat(storage, MF_CLASSIC_DICT_USER_PATH, NULL) == FSE_OK; + } + + furi_record_close(RECORD_STORAGE); + + return dict_present; +} + +MfClassicDict* napi_mf_classic_dict_alloc(MfClassicDictType dict_type) { + MfClassicDict* dict = malloc(sizeof(MfClassicDict)); + Storage* storage = furi_record_open(RECORD_STORAGE); + dict->stream = buffered_file_stream_alloc(storage); + furi_record_close(RECORD_STORAGE); + + bool dict_loaded = false; + do { + if(dict_type == MfClassicDictTypeSystem) { + if(!buffered_file_stream_open( + dict->stream, + MF_CLASSIC_DICT_FLIPPER_PATH, + FSAM_READ_WRITE, + FSOM_OPEN_EXISTING)) { + buffered_file_stream_close(dict->stream); + break; + } + } else if(dict_type == MfClassicDictTypeUser) { + if(!buffered_file_stream_open( + dict->stream, MF_CLASSIC_DICT_USER_PATH, FSAM_READ_WRITE, FSOM_OPEN_ALWAYS)) { + buffered_file_stream_close(dict->stream); + break; + } + } + + // Check for newline ending + if(!stream_eof(dict->stream)) { + if(!stream_seek(dict->stream, -1, StreamOffsetFromEnd)) break; + uint8_t last_char = 0; + if(stream_read(dict->stream, &last_char, 1) != 1) break; + if(last_char != '\n') { + FURI_LOG_D(TAG, "Adding new line ending"); + if(stream_write_char(dict->stream, '\n') != 1) break; + } + if(!stream_rewind(dict->stream)) break; + } + + // Read total amount of keys + FuriString* next_line; + next_line = furi_string_alloc(); + while(true) { + if(!stream_read_line(dict->stream, next_line)) { + FURI_LOG_T(TAG, "No keys left in dict"); + break; + } + FURI_LOG_T( + TAG, + "Read line: %s, len: %zu", + furi_string_get_cstr(next_line), + furi_string_size(next_line)); + if(furi_string_get_char(next_line, 0) == '#') continue; + if(furi_string_size(next_line) != NFC_MF_CLASSIC_KEY_LEN) continue; + dict->total_keys++; + } + furi_string_free(next_line); + stream_rewind(dict->stream); + + dict_loaded = true; + FURI_LOG_I(TAG, "Loaded dictionary with %lu keys", dict->total_keys); + } while(false); + + if(!dict_loaded) { + buffered_file_stream_close(dict->stream); + free(dict); + dict = NULL; + } + + return dict; +} + +bool napi_mf_classic_dict_add_key_str(MfClassicDict* dict, FuriString* key) { + furi_assert(dict); + furi_assert(dict->stream); + FURI_LOG_I(TAG, "Saving key: %s", furi_string_get_cstr(key)); + + furi_string_cat_printf(key, "\n"); + + bool key_added = false; + do { + if(!stream_seek(dict->stream, 0, StreamOffsetFromEnd)) break; + if(!stream_insert_string(dict->stream, key)) break; + dict->total_keys++; + key_added = true; + } while(false); + + furi_string_left(key, 12); + return key_added; +} + +void napi_mf_classic_dict_free(MfClassicDict* dict) { + furi_assert(dict); + furi_assert(dict->stream); + + buffered_file_stream_close(dict->stream); + stream_free(dict->stream); + free(dict); +} + +static void napi_mf_classic_dict_int_to_str(uint8_t* key_int, FuriString* key_str) { + furi_string_reset(key_str); + for(size_t i = 0; i < 6; i++) { + furi_string_cat_printf(key_str, "%02X", key_int[i]); + } +} + +static void napi_mf_classic_dict_str_to_int(FuriString* key_str, uint64_t* key_int) { + uint8_t key_byte_tmp; + + *key_int = 0ULL; + for(uint8_t i = 0; i < 12; i += 2) { + args_char_to_hex( + furi_string_get_char(key_str, i), furi_string_get_char(key_str, i + 1), &key_byte_tmp); + *key_int |= (uint64_t)key_byte_tmp << (8 * (5 - i / 2)); + } +} + +uint32_t napi_mf_classic_dict_get_total_keys(MfClassicDict* dict) { + furi_assert(dict); + + return dict->total_keys; +} + +bool napi_mf_classic_dict_rewind(MfClassicDict* dict) { + furi_assert(dict); + furi_assert(dict->stream); + + return stream_rewind(dict->stream); +} + +bool napi_mf_classic_dict_get_next_key_str(MfClassicDict* dict, FuriString* key) { + furi_assert(dict); + furi_assert(dict->stream); + + bool key_read = false; + furi_string_reset(key); + while(!key_read) { + if(!stream_read_line(dict->stream, key)) break; + if(furi_string_get_char(key, 0) == '#') continue; + if(furi_string_size(key) != NFC_MF_CLASSIC_KEY_LEN) continue; + furi_string_left(key, 12); + key_read = true; + } + + return key_read; +} + +bool napi_mf_classic_dict_get_next_key(MfClassicDict* dict, uint64_t* key) { + furi_assert(dict); + furi_assert(dict->stream); + + FuriString* temp_key; + temp_key = furi_string_alloc(); + bool key_read = napi_mf_classic_dict_get_next_key_str(dict, temp_key); + if(key_read) { + napi_mf_classic_dict_str_to_int(temp_key, key); + } + furi_string_free(temp_key); + return key_read; +} + +bool napi_mf_classic_dict_is_key_present_str(MfClassicDict* dict, FuriString* key) { + furi_assert(dict); + furi_assert(dict->stream); + + FuriString* next_line; + next_line = furi_string_alloc(); + + bool key_found = false; + stream_rewind(dict->stream); + while(!key_found) { //-V654 + if(!stream_read_line(dict->stream, next_line)) break; + if(furi_string_get_char(next_line, 0) == '#') continue; + if(furi_string_size(next_line) != NFC_MF_CLASSIC_KEY_LEN) continue; + furi_string_left(next_line, 12); + if(!furi_string_equal(key, next_line)) continue; + key_found = true; + } + + furi_string_free(next_line); + return key_found; +} + +bool napi_mf_classic_dict_is_key_present(MfClassicDict* dict, uint8_t* key) { + FuriString* temp_key; + + temp_key = furi_string_alloc(); + napi_mf_classic_dict_int_to_str(key, temp_key); + bool key_found = napi_mf_classic_dict_is_key_present_str(dict, temp_key); + furi_string_free(temp_key); + return key_found; +} + +bool napi_key_already_found_for_nonce( + MfClassicDict* dict, + uint32_t uid_xor_nt1, + uint32_t nr1_enc, + uint32_t p64b, + uint32_t ar1_enc) { + bool found = false; + uint64_t k = 0; + napi_mf_classic_dict_rewind(dict); + while(napi_mf_classic_dict_get_next_key(dict, &k)) { + struct Crypto1State temp = {0, 0}; + int i; + for(i = 0; i < 24; i++) { + (&temp)->odd |= (BIT(k, 2 * i + 1) << (i ^ 3)); + (&temp)->even |= (BIT(k, 2 * i) << (i ^ 3)); + } + crypt_word_noret(&temp, uid_xor_nt1, 0); + crypt_word_noret(&temp, nr1_enc, 1); + if(ar1_enc == (crypt_word(&temp) ^ p64b)) { + found = true; + break; + } + } + return found; +} + +bool napi_mf_classic_nonces_check_presence() { + Storage* storage = furi_record_open(RECORD_STORAGE); + + bool nonces_present = storage_common_stat(storage, MF_CLASSIC_NONCE_PATH, NULL) == FSE_OK; + + furi_record_close(RECORD_STORAGE); + + return nonces_present; +} + +MfClassicNonceArray* napi_mf_classic_nonce_array_alloc( + MfClassicDict* system_dict, + bool system_dict_exists, + MfClassicDict* user_dict, + ProgramState* program_state) { + MfClassicNonceArray* nonce_array = malloc(sizeof(MfClassicNonceArray)); + MfClassicNonce* remaining_nonce_array_init = malloc(sizeof(MfClassicNonce) * 1); + nonce_array->remaining_nonce_array = remaining_nonce_array_init; + Storage* storage = furi_record_open(RECORD_STORAGE); + nonce_array->stream = buffered_file_stream_alloc(storage); + furi_record_close(RECORD_STORAGE); + + bool array_loaded = false; + do { + // https://github.com/flipperdevices/flipperzero-firmware/blob/5134f44c09d39344a8747655c0d59864bb574b96/applications/services/storage/filesystem_api_defines.h#L8-L22 + if(!buffered_file_stream_open( + nonce_array->stream, MF_CLASSIC_NONCE_PATH, FSAM_READ_WRITE, FSOM_OPEN_EXISTING)) { + buffered_file_stream_close(nonce_array->stream); + break; + } + + // Check for newline ending + if(!stream_eof(nonce_array->stream)) { + if(!stream_seek(nonce_array->stream, -1, StreamOffsetFromEnd)) break; + uint8_t last_char = 0; + if(stream_read(nonce_array->stream, &last_char, 1) != 1) break; + if(last_char != '\n') { + FURI_LOG_D(TAG, "Adding new line ending"); + if(stream_write_char(nonce_array->stream, '\n') != 1) break; + } + if(!stream_rewind(nonce_array->stream)) break; + } + + // Read total amount of nonces + FuriString* next_line; + next_line = furi_string_alloc(); + while(!(program_state->close_thread_please)) { + if(!stream_read_line(nonce_array->stream, next_line)) { + FURI_LOG_T(TAG, "No nonces left"); + break; + } + FURI_LOG_T( + TAG, + "Read line: %s, len: %zu", + furi_string_get_cstr(next_line), + furi_string_size(next_line)); + if(!furi_string_start_with_str(next_line, "Sec")) continue; + const char* next_line_cstr = furi_string_get_cstr(next_line); + MfClassicNonce res = {0}; + int i = 0; + char* endptr; + for(i = 0; i <= 17; i++) { + if(i != 0) { + next_line_cstr = strchr(next_line_cstr, ' '); + if(next_line_cstr) { + next_line_cstr++; + } else { + break; + } + } + unsigned long value = strtoul(next_line_cstr, &endptr, 16); + switch(i) { + case 5: + res.uid = value; + break; + case 7: + res.nt0 = value; + break; + case 9: + res.nr0_enc = value; + break; + case 11: + res.ar0_enc = value; + break; + case 13: + res.nt1 = value; + break; + case 15: + res.nr1_enc = value; + break; + case 17: + res.ar1_enc = value; + break; + default: + break; // Do nothing + } + next_line_cstr = endptr; + } + (program_state->total)++; + uint32_t p64b = prng_successor(res.nt1, 64); + if((system_dict_exists && + napi_key_already_found_for_nonce( + system_dict, res.uid ^ res.nt1, res.nr1_enc, p64b, res.ar1_enc)) || + (napi_key_already_found_for_nonce( + user_dict, res.uid ^ res.nt1, res.nr1_enc, p64b, res.ar1_enc))) { + (program_state->cracked)++; + (program_state->num_completed)++; + continue; + } + FURI_LOG_I(TAG, "No key found for %8lx %8lx", res.uid, res.ar1_enc); + // TODO: Refactor + nonce_array->remaining_nonce_array = realloc( //-V701 + nonce_array->remaining_nonce_array, + sizeof(MfClassicNonce) * ((nonce_array->remaining_nonces) + 1)); + nonce_array->remaining_nonces++; + nonce_array->remaining_nonce_array[(nonce_array->remaining_nonces) - 1] = res; + nonce_array->total_nonces++; + } + furi_string_free(next_line); + buffered_file_stream_close(nonce_array->stream); + + array_loaded = true; + FURI_LOG_I(TAG, "Loaded %lu nonces", nonce_array->total_nonces); + } while(false); + + if(!array_loaded) { + free(nonce_array); + nonce_array = NULL; + } + + return nonce_array; +} + +void napi_mf_classic_nonce_array_free(MfClassicNonceArray* nonce_array) { + furi_assert(nonce_array); + furi_assert(nonce_array->stream); + + buffered_file_stream_close(nonce_array->stream); + stream_free(nonce_array->stream); + free(nonce_array); +} + +static void finished_beep() { + // Beep to indicate completion + NotificationApp* notification = furi_record_open("notification"); + notification_message(notification, &sequence_audiovisual_alert); + notification_message(notification, &sequence_display_backlight_on); + furi_record_close("notification"); +} + +void mfkey32(ProgramState* program_state) { + uint64_t found_key; // recovered key + size_t keyarray_size = 0; + uint64_t* keyarray = malloc(sizeof(uint64_t) * 1); + uint32_t i = 0, j = 0; + // Check for nonces + if(!napi_mf_classic_nonces_check_presence()) { + program_state->err = MissingNonces; + program_state->mfkey_state = Error; + free(keyarray); + return; + } + // Read dictionaries (optional) + MfClassicDict* system_dict = {0}; + bool system_dict_exists = napi_mf_classic_dict_check_presence(MfClassicDictTypeSystem); + MfClassicDict* user_dict = {0}; + bool user_dict_exists = napi_mf_classic_dict_check_presence(MfClassicDictTypeUser); + uint32_t total_dict_keys = 0; + if(system_dict_exists) { + system_dict = napi_mf_classic_dict_alloc(MfClassicDictTypeSystem); + total_dict_keys += napi_mf_classic_dict_get_total_keys(system_dict); + } + user_dict = napi_mf_classic_dict_alloc(MfClassicDictTypeUser); + if(user_dict_exists) { + total_dict_keys += napi_mf_classic_dict_get_total_keys(user_dict); + } + user_dict_exists = true; + program_state->dict_count = total_dict_keys; + program_state->mfkey_state = DictionaryAttack; + // Read nonces + MfClassicNonceArray* nonce_arr; + nonce_arr = napi_mf_classic_nonce_array_alloc( + system_dict, system_dict_exists, user_dict, program_state); + if(system_dict_exists) { + napi_mf_classic_dict_free(system_dict); + } + if(nonce_arr->total_nonces == 0) { + // Nothing to crack + program_state->err = ZeroNonces; + program_state->mfkey_state = Error; + napi_mf_classic_nonce_array_free(nonce_arr); + napi_mf_classic_dict_free(user_dict); + free(keyarray); + return; + } + if(memmgr_get_free_heap() < MIN_RAM) { + // System has less than the guaranteed amount of RAM (140 KB) - adjust some parameters to run anyway at half speed + eta_round_time *= 2; + eta_total_time *= 2; + MSB_LIMIT /= 2; + } + program_state->mfkey_state = MfkeyAttack; + // TODO: Work backwards on this array and free memory + for(i = 0; i < nonce_arr->total_nonces; i++) { + MfClassicNonce next_nonce = nonce_arr->remaining_nonce_array[i]; + uint32_t p64 = prng_successor(next_nonce.nt0, 64); + uint32_t p64b = prng_successor(next_nonce.nt1, 64); + if(key_already_found_for_nonce( + keyarray, + keyarray_size, + next_nonce.uid ^ next_nonce.nt1, + next_nonce.nr1_enc, + p64b, + next_nonce.ar1_enc)) { + nonce_arr->remaining_nonces--; + (program_state->cracked)++; + (program_state->num_completed)++; + continue; + } + FURI_LOG_I(TAG, "Cracking %8lx %8lx", next_nonce.uid, next_nonce.ar1_enc); + struct Crypto1Params p = { + 0, + next_nonce.nr0_enc, + next_nonce.uid ^ next_nonce.nt0, + next_nonce.uid ^ next_nonce.nt1, + next_nonce.nr1_enc, + p64b, + next_nonce.ar1_enc}; + if(!recover(&p, next_nonce.ar0_enc ^ p64, program_state)) { + if(program_state->close_thread_please) { + break; + } + // No key found in recover() + (program_state->num_completed)++; + continue; + } + (program_state->cracked)++; + (program_state->num_completed)++; + found_key = p.key; + bool already_found = false; + for(j = 0; j < keyarray_size; j++) { + if(keyarray[j] == found_key) { + already_found = true; + break; + } + } + if(already_found == false) { + // New key + keyarray = realloc(keyarray, sizeof(uint64_t) * (keyarray_size + 1)); //-V701 + keyarray_size += 1; + keyarray[keyarray_size - 1] = found_key; + (program_state->unique_cracked)++; + } + } + // TODO: Update display to show all keys were found + // TODO: Prepend found key(s) to user dictionary file + //FURI_LOG_I(TAG, "Unique keys found:"); + for(i = 0; i < keyarray_size; i++) { + //FURI_LOG_I(TAG, "%012" PRIx64, keyarray[i]); + FuriString* temp_key = furi_string_alloc(); + furi_string_cat_printf(temp_key, "%012" PRIX64, keyarray[i]); + napi_mf_classic_dict_add_key_str(user_dict, temp_key); + furi_string_free(temp_key); + } + if(keyarray_size > 0) { + // TODO: Should we use DolphinDeedNfcMfcAdd? + DOLPHIN_DEED(DolphinDeedNfcMfcAdd); + } + napi_mf_classic_nonce_array_free(nonce_arr); + napi_mf_classic_dict_free(user_dict); + free(keyarray); + //FURI_LOG_I(TAG, "mfkey32 function completed normally"); // DEBUG + program_state->mfkey_state = Complete; + // No need to alert the user if they asked it to stop + if(!(program_state->close_thread_please)) { + finished_beep(); + } + return; +} + +// Screen is 128x64 px +static void render_callback(Canvas* const canvas, void* ctx) { + furi_assert(ctx); + ProgramState* program_state = ctx; + furi_mutex_acquire(program_state->mutex, FuriWaitForever); + char draw_str[44] = {}; + canvas_clear(canvas); + canvas_draw_frame(canvas, 0, 0, 128, 64); + canvas_draw_frame(canvas, 0, 15, 128, 64); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 5, 4, AlignLeft, AlignTop, "Mfkey32"); + canvas_draw_icon(canvas, 114, 4, &I_mfkey); + if(program_state->is_thread_running && program_state->mfkey_state == MfkeyAttack) { + float eta_round = (float)1 - ((float)program_state->eta_round / (float)eta_round_time); + float eta_total = (float)1 - ((float)program_state->eta_total / (float)eta_total_time); + float progress = (float)program_state->num_completed / (float)program_state->total; + if(eta_round < 0) { + // Round ETA miscalculated + eta_round = 1; + program_state->eta_round = 0; + } + if(eta_total < 0) { + // Total ETA miscalculated + eta_total = 1; + program_state->eta_total = 0; + } + canvas_set_font(canvas, FontSecondary); + snprintf( + draw_str, + sizeof(draw_str), + "Cracking: %d/%d - in prog.", + program_state->num_completed, + program_state->total); + elements_progress_bar_with_text(canvas, 5, 18, 118, progress, draw_str); + snprintf( + draw_str, + sizeof(draw_str), + "Round: %d/%d - ETA %02d Sec", + (program_state->search) + 1, // Zero indexed + 256 / MSB_LIMIT, + program_state->eta_round); + elements_progress_bar_with_text(canvas, 5, 31, 118, eta_round, draw_str); + snprintf(draw_str, sizeof(draw_str), "Total ETA %03d Sec", program_state->eta_total); + elements_progress_bar_with_text(canvas, 5, 44, 118, eta_total, draw_str); + } else if(program_state->is_thread_running && program_state->mfkey_state == DictionaryAttack) { + canvas_set_font(canvas, FontSecondary); + snprintf( + draw_str, sizeof(draw_str), "Dict solves: %d (in progress)", program_state->cracked); + canvas_draw_str_aligned(canvas, 10, 18, AlignLeft, AlignTop, draw_str); + snprintf(draw_str, sizeof(draw_str), "Keys in dict: %d", program_state->dict_count); + canvas_draw_str_aligned(canvas, 26, 28, AlignLeft, AlignTop, draw_str); + } else if(program_state->mfkey_state == Complete) { + // TODO: Scrollable list view to see cracked keys if user presses down + elements_progress_bar_with_text(canvas, 5, 18, 118, 1, draw_str); + canvas_set_font(canvas, FontSecondary); + snprintf(draw_str, sizeof(draw_str), "Complete"); + canvas_draw_str_aligned(canvas, 40, 31, AlignLeft, AlignTop, draw_str); + snprintf( + draw_str, + sizeof(draw_str), + "Keys added to user dict: %d", + program_state->unique_cracked); + canvas_draw_str_aligned(canvas, 10, 41, AlignLeft, AlignTop, draw_str); + } else if(program_state->mfkey_state == Ready) { + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned(canvas, 50, 30, AlignLeft, AlignTop, "Ready"); + elements_button_center(canvas, "Start"); + elements_button_right(canvas, "Help"); + } else if(program_state->mfkey_state == Help) { + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned(canvas, 7, 20, AlignLeft, AlignTop, "Collect nonces using"); + canvas_draw_str_aligned(canvas, 7, 30, AlignLeft, AlignTop, "Detect Reader."); + canvas_draw_str_aligned(canvas, 7, 40, AlignLeft, AlignTop, "Developers: noproto, AG"); + canvas_draw_str_aligned(canvas, 7, 50, AlignLeft, AlignTop, "Thanks: bettse"); + } else if(program_state->mfkey_state == Error) { + canvas_draw_str_aligned(canvas, 50, 25, AlignLeft, AlignTop, "Error"); + canvas_set_font(canvas, FontSecondary); + if(program_state->err == MissingNonces) { + canvas_draw_str_aligned(canvas, 25, 36, AlignLeft, AlignTop, "No nonces found"); + } else if(program_state->err == ZeroNonces) { + canvas_draw_str_aligned(canvas, 15, 36, AlignLeft, AlignTop, "Nonces already cracked"); + } else { + // Unhandled error + } + } else { + // Unhandled program state + } + furi_mutex_release(program_state->mutex); +} + +static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) { + furi_assert(event_queue); + + PluginEvent event = {.type = EventTypeKey, .input = *input_event}; + furi_message_queue_put(event_queue, &event, FuriWaitForever); +} + +static void mfkey32_state_init(ProgramState* program_state) { + program_state->is_thread_running = false; + program_state->mfkey_state = Ready; + program_state->cracked = 0; + program_state->unique_cracked = 0; + program_state->num_completed = 0; + program_state->total = 0; + program_state->dict_count = 0; +} + +// Entrypoint for worker thread +static int32_t mfkey32_worker_thread(void* ctx) { + ProgramState* program_state = ctx; + program_state->is_thread_running = true; + program_state->mfkey_state = Initializing; + //FURI_LOG_I(TAG, "Hello from the mfkey32 worker thread"); // DEBUG + mfkey32(program_state); + program_state->is_thread_running = false; + return 0; +} + +void start_mfkey32_thread(ProgramState* program_state) { + if(!program_state->is_thread_running) { + furi_thread_start(program_state->mfkeythread); + } +} + +int32_t mfkey32_main() { + FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent)); + + ProgramState* program_state = malloc(sizeof(ProgramState)); + + mfkey32_state_init(program_state); + + program_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal); + if(!program_state->mutex) { + FURI_LOG_E(TAG, "cannot create mutex\r\n"); + free(program_state); + return 255; + } + + // Set system callbacks + ViewPort* view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, render_callback, program_state); + view_port_input_callback_set(view_port, input_callback, event_queue); + + // Open GUI and register view_port + Gui* gui = furi_record_open(RECORD_GUI); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); + + program_state->mfkeythread = furi_thread_alloc(); + furi_thread_set_name(program_state->mfkeythread, "Mfkey32 Worker"); + furi_thread_set_stack_size(program_state->mfkeythread, 2048); + furi_thread_set_context(program_state->mfkeythread, program_state); + furi_thread_set_callback(program_state->mfkeythread, mfkey32_worker_thread); + + PluginEvent event; + for(bool main_loop = true; main_loop;) { + FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100); + + furi_mutex_acquire(program_state->mutex, FuriWaitForever); + + if(event_status == FuriStatusOk) { + // press events + if(event.type == EventTypeKey) { + if(event.input.type == InputTypePress) { + switch(event.input.key) { + case InputKeyUp: + break; + case InputKeyDown: + break; + case InputKeyRight: + if(!program_state->is_thread_running && + program_state->mfkey_state == Ready) { + program_state->mfkey_state = Help; + view_port_update(view_port); + } + break; + case InputKeyLeft: + break; + case InputKeyOk: + if(!program_state->is_thread_running && + program_state->mfkey_state == Ready) { + start_mfkey32_thread(program_state); + view_port_update(view_port); + } + break; + case InputKeyBack: + if(!program_state->is_thread_running && + program_state->mfkey_state == Help) { + program_state->mfkey_state = Ready; + view_port_update(view_port); + } else { + program_state->close_thread_please = true; + if(program_state->is_thread_running && program_state->mfkeythread) { + // Wait until thread is finished + furi_thread_join(program_state->mfkeythread); + } + program_state->close_thread_please = false; + main_loop = false; + } + break; + default: + break; + } + } + } + } + + view_port_update(view_port); + furi_mutex_release(program_state->mutex); + } + + furi_thread_free(program_state->mfkeythread); + view_port_enabled_set(view_port, false); + gui_remove_view_port(gui, view_port); + furi_record_close("gui"); + view_port_free(view_port); + furi_message_queue_free(event_queue); + furi_mutex_free(program_state->mutex); + free(program_state); + + return 0; +} diff --git a/applications/main/nfc/scenes/nfc_scene_mfkey_complete.c b/applications/main/nfc/scenes/nfc_scene_mfkey_complete.c index 3c4f9dba19e..04515f24ff6 100644 --- a/applications/main/nfc/scenes/nfc_scene_mfkey_complete.c +++ b/applications/main/nfc/scenes/nfc_scene_mfkey_complete.c @@ -18,7 +18,7 @@ void nfc_scene_mfkey_complete_on_enter(void* context) { AlignCenter, AlignCenter, FontSecondary, - "Now use mfkey32v2\nto extract keys"); + "Now use Mfkey32\nto extract keys"); widget_add_button_element( nfc->widget, GuiButtonTypeCenter, "OK", nfc_scene_mfkey_complete_callback, nfc); @@ -46,4 +46,4 @@ void nfc_scene_mfkey_complete_on_exit(void* context) { Nfc* nfc = context; widget_reset(nfc->widget); -} \ No newline at end of file +} From eebc6241b7aae4ca55aa76c1b37c1f924e72a58f Mon Sep 17 00:00:00 2001 From: hedger Date: Tue, 9 May 2023 07:06:44 +0300 Subject: [PATCH 550/824] [FL-3302] ble: attempt to handle hardfaulted c2 (#2653) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ble: attempt to handle hardfaulted c2 * MfKey32: nicer macros * FuriHal: slightly different core2 hardfault message * Update ReadMe Co-authored-by: あく --- ReadMe.md | 1 - applications/external/mfkey32/mfkey32.c | 2 +- firmware/targets/f7/ble_glue/ble_glue.c | 20 +++++++++ lib/ReadMe.md | 57 +++++++++++++++---------- lib/stm32wb.scons | 1 + 5 files changed, 56 insertions(+), 25 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index 411f5b41de1..b60d66fbdd8 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -99,7 +99,6 @@ Make sure your Flipper is on, and your firmware is functioning. Connect your Fli - `applications` - applications and services used in firmware - `assets` - assets used by applications and services - `furi` - Furi Core: OS-level primitives and helpers -- `debug` - debug tool: GDB plugins, an SVD file, etc. - `documentation` - documentation generation system configs and input files - `firmware` - firmware source code - `lib` - our and 3rd party libraries, drivers, etc. diff --git a/applications/external/mfkey32/mfkey32.c b/applications/external/mfkey32/mfkey32.c index 2934d837aa1..d4b2d3e4ab0 100644 --- a/applications/external/mfkey32/mfkey32.c +++ b/applications/external/mfkey32/mfkey32.c @@ -41,7 +41,7 @@ #define BIT(x, n) ((x) >> (n)&1) #define BEBIT(x, n) BIT(x, (n) ^ 24) #define SWAPENDIAN(x) \ - (x = (x >> 8 & 0xff00ff) | (x & 0xff00ff) << 8, x = x >> 16 | x << 16) //-V1003 + ((x) = ((x) >> 8 & 0xff00ff) | ((x)&0xff00ff) << 8, (x) = (x) >> 16 | (x) << 16) //#define SIZEOF(arr) sizeof(arr) / sizeof(*arr) static int eta_round_time = 56; diff --git a/firmware/targets/f7/ble_glue/ble_glue.c b/firmware/targets/f7/ble_glue/ble_glue.c index c73bbd86601..6b527cfcacd 100644 --- a/firmware/targets/f7/ble_glue/ble_glue.c +++ b/firmware/targets/f7/ble_glue/ble_glue.c @@ -54,6 +54,26 @@ void ble_glue_set_key_storage_changed_callback( ble_glue->context = context; } +/////////////////////////////////////////////////////////////////////////////// + +/* TL hook to catch hardfaults */ + +int32_t ble_glue_TL_SYS_SendCmd(uint8_t* buffer, uint16_t size) { + if(furi_hal_bt_get_hardfault_info()) { + furi_crash("ST(R) Copro(R) HardFault"); + } + + return TL_SYS_SendCmd(buffer, size); +} + +void shci_register_io_bus(tSHciIO* fops) { + /* Register IO bus services */ + fops->Init = TL_SYS_Init; + fops->Send = ble_glue_TL_SYS_SendCmd; +} + +/////////////////////////////////////////////////////////////////////////////// + void ble_glue_init() { ble_glue = malloc(sizeof(BleGlue)); ble_glue->status = BleGlueStatusStartup; diff --git a/lib/ReadMe.md b/lib/ReadMe.md index 884a0b8c083..93236b2677f 100644 --- a/lib/ReadMe.md +++ b/lib/ReadMe.md @@ -1,27 +1,38 @@ # Structure -- `app-scened-template` - Scened template app library -- `app-template` - Template app library +- `FreeRTOS-Kernel` - FreeRTOS kernel source code +- `FreeRTOS-glue` - Extra glue to hold together FreeRTOS kernel and flipper firmware +- `ST25RFAL002` - ST25R3916 Driver and protocol stack +- `app-scened-template` - C++ app library - `callback-connector` - Callback connector library -- `drivers` - Drivers that we wrote -- `fatfs` - External storage file system +- `cmsis_core` - CMSIS Core package, contain cortex-m core headers +- `cxxheaderparser` - C++ headers parser, used by SDK bundler +- `digital_signal` - Digital signal library: used by NFC for software implemented protocols +- `drivers` - Various flipper drivers +- `fatfs` - FatFS file system driver +- `flipper_application` - Flipper application library, used for FAPs - `flipper_format` - Flipper File Format library -- `fnv1a-hash` - Fnv1a hash library -- `heatshrink` - Image compression library -- `infrared` - Infrared library -- `libusb_stm32` - STM32 USB library -- `littlefs` - Internal storage file system -- `micro-ecc` - Elliptic Curve Crytography library -- `microtar` - TAR archive support library -- `mlib` - Algorithms and containers -- `nanopb` - Nano Protobuf library -- `nfc` - Nfc library -- `one_wire` - One wire library -- `qrcode` - Qr code generator library -- `ST25RFAL002` - ST253916 driver and NFC hal -- `stm32wb_cmsis` - STM32WB series CMSIS component -- `stm32wb_copro` - STM32WB Coprocessor fimrware + WPAN library -- `stm32wb_hal_driver` - STM32WB series HAL -- `subghz` - SubGhz library -- `toolbox` - Toolbox of things that we are using but don't place in core -- `u8g2` - Graphics library that we use to draw GUI +- `fnv1a-hash` - FNV-1a hash library +- `heatshrink` - Heatshrink compression library +- `ibutton` - ibutton library, used by iButton application +- `infrared` - Infrared library, used by Infrared application +- `lfrfid` - LF-RFID library, used by LF RFID application +- `libusb_stm32` - LibUSB for STM32 series MCU +- `littlefs` - LittleFS file system driver, used by internal storage +- `mbedtls` - MbedTLS cryptography library +- `micro-ecc` - MicroECC cryptography library +- `microtar` - MicroTAR library +- `mlib` - M-Lib C containers library +- `nanopb` - NanoPB library, protobuf implementation for MCU +- `nfc` - NFC library, used by NFC application +- `one_wire` - OneWire library, used by iButton application +- `print` - Tiny printf implementation +- `pulse_reader` - Pulse Reader library used by NFC for software implemented protocols +- `qrcode` - QR-Code library +- `stm32wb_cmsis` - STM32WB series CMSIS headers, extends CMSIS Core +- `stm32wb_copro` - STM32WB Copro library: contains WPAN and radio co-processor firmware +- `stm32wb_hal` - STM32WB HAL library, extends STM32WB CMSIS and provides HAL +- `subghz` - Subghz library, used by SubGhz application +- `toolbox` - Toolbox library, contains various things that is used by flipper firmware +- `u8g2` - u8g2 graphics library, used by GUI subsystem +- `update_util` - update utilities library, used by updater \ No newline at end of file diff --git a/lib/stm32wb.scons b/lib/stm32wb.scons index 9c184872bb8..7cb2fa8bdd6 100644 --- a/lib/stm32wb.scons +++ b/lib/stm32wb.scons @@ -48,6 +48,7 @@ sources += Glob( ) sources += Glob( "stm32wb_copro/wpan/interface/patterns/ble_thread/tl/*_tl*.c", + exclude="stm32wb_copro/wpan/interface/patterns/ble_thread/tl/shci_tl_if.c", source=True, ) sources += [ From 9914aa40bd589d767b850d81b0e4eda28de46be9 Mon Sep 17 00:00:00 2001 From: hedger Date: Thu, 11 May 2023 05:25:06 +0300 Subject: [PATCH 551/824] [FL-3302] Part 2 of hooking C2 IPC (#2662) --- firmware/targets/f7/ble_glue/ble_glue.c | 15 +++++++++++++++ lib/stm32wb.scons | 5 ++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/firmware/targets/f7/ble_glue/ble_glue.c b/firmware/targets/f7/ble_glue/ble_glue.c index 6b527cfcacd..1cb5501d97e 100644 --- a/firmware/targets/f7/ble_glue/ble_glue.c +++ b/firmware/targets/f7/ble_glue/ble_glue.c @@ -2,6 +2,7 @@ #include "app_common.h" #include "ble_app.h" #include +#include #include #include @@ -72,6 +73,20 @@ void shci_register_io_bus(tSHciIO* fops) { fops->Send = ble_glue_TL_SYS_SendCmd; } +static int32_t ble_glue_TL_BLE_SendCmd(uint8_t* buffer, uint16_t size) { + if(furi_hal_bt_get_hardfault_info()) { + furi_crash("ST(R) Copro(R) HardFault"); + } + + return TL_BLE_SendCmd(buffer, size); +} + +void hci_register_io_bus(tHciIO* fops) { + /* Register IO bus services */ + fops->Init = TL_BLE_Init; + fops->Send = ble_glue_TL_BLE_SendCmd; +} + /////////////////////////////////////////////////////////////////////////////// void ble_glue_init() { diff --git a/lib/stm32wb.scons b/lib/stm32wb.scons index 7cb2fa8bdd6..94a1c7075aa 100644 --- a/lib/stm32wb.scons +++ b/lib/stm32wb.scons @@ -48,7 +48,10 @@ sources += Glob( ) sources += Glob( "stm32wb_copro/wpan/interface/patterns/ble_thread/tl/*_tl*.c", - exclude="stm32wb_copro/wpan/interface/patterns/ble_thread/tl/shci_tl_if.c", + exclude=[ + "stm32wb_copro/wpan/interface/patterns/ble_thread/tl/hci_tl_if.c", + "stm32wb_copro/wpan/interface/patterns/ble_thread/tl/shci_tl_if.c", + ], source=True, ) sources += [ From 9862876f06c967554c0979c035e1ed194f83f97d Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 11 May 2023 06:10:20 +0300 Subject: [PATCH 552/824] SubGHz: Fix typos (#2661) Co-authored-by: hedger --- applications/debug/unit_tests/subghz/subghz_test.c | 8 ++++---- ...te_potocol_key.c => subghz_txrx_create_protocol_key.c} | 4 ++-- ...te_potocol_key.h => subghz_txrx_create_protocol_key.h} | 2 +- applications/main/subghz/scenes/subghz_scene_set_type.c | 6 +++--- lib/subghz/protocols/keeloq_common.c | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) rename applications/main/subghz/helpers/{subghz_txrx_create_potocol_key.c => subghz_txrx_create_protocol_key.c} (98%) rename applications/main/subghz/helpers/{subghz_txrx_create_potocol_key.h => subghz_txrx_create_protocol_key.h} (98%) diff --git a/applications/debug/unit_tests/subghz/subghz_test.c b/applications/debug/unit_tests/subghz/subghz_test.c index c7e9c96f1d1..f1ab926538f 100644 --- a/applications/debug/unit_tests/subghz/subghz_test.c +++ b/applications/debug/unit_tests/subghz/subghz_test.c @@ -407,7 +407,7 @@ MU_TEST(subghz_decoder_ido_test) { "Test decoder " SUBGHZ_PROTOCOL_IDO_NAME " error\r\n"); } -MU_TEST(subghz_decoder_keelog_test) { +MU_TEST(subghz_decoder_keeloq_test) { mu_assert( subghz_decoder_test( EXT_PATH("unit_tests/subghz/doorhan_raw.sub"), SUBGHZ_PROTOCOL_KEELOQ_NAME), @@ -676,7 +676,7 @@ MU_TEST(subghz_encoder_nice_flo_test) { "Test encoder " SUBGHZ_PROTOCOL_NICE_FLO_NAME " error\r\n"); } -MU_TEST(subghz_encoder_keelog_test) { +MU_TEST(subghz_encoder_keeloq_test) { mu_assert( subghz_encoder_test(EXT_PATH("unit_tests/subghz/doorhan.sub")), "Test encoder " SUBGHZ_PROTOCOL_KEELOQ_NAME " error\r\n"); @@ -813,7 +813,7 @@ MU_TEST_SUITE(subghz) { MU_RUN_TEST(subghz_decoder_gate_tx_test); MU_RUN_TEST(subghz_decoder_hormann_hsm_test); MU_RUN_TEST(subghz_decoder_ido_test); - MU_RUN_TEST(subghz_decoder_keelog_test); + MU_RUN_TEST(subghz_decoder_keeloq_test); MU_RUN_TEST(subghz_decoder_kia_seed_test); MU_RUN_TEST(subghz_decoder_nero_radio_test); MU_RUN_TEST(subghz_decoder_nero_sketch_test); @@ -852,7 +852,7 @@ MU_TEST_SUITE(subghz) { MU_RUN_TEST(subghz_encoder_came_twee_test); MU_RUN_TEST(subghz_encoder_gate_tx_test); MU_RUN_TEST(subghz_encoder_nice_flo_test); - MU_RUN_TEST(subghz_encoder_keelog_test); + MU_RUN_TEST(subghz_encoder_keeloq_test); MU_RUN_TEST(subghz_encoder_linear_test); MU_RUN_TEST(subghz_encoder_linear_delta3_test); MU_RUN_TEST(subghz_encoder_megacode_test); diff --git a/applications/main/subghz/helpers/subghz_txrx_create_potocol_key.c b/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.c similarity index 98% rename from applications/main/subghz/helpers/subghz_txrx_create_potocol_key.c rename to applications/main/subghz/helpers/subghz_txrx_create_protocol_key.c index 41e4f7c4efd..06a855c23c2 100644 --- a/applications/main/subghz/helpers/subghz_txrx_create_potocol_key.c +++ b/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.c @@ -1,5 +1,5 @@ #include "subghz_txrx_i.h" -#include "subghz_txrx_create_potocol_key.h" +#include "subghz_txrx_create_protocol_key.h" #include #include #include @@ -84,7 +84,7 @@ bool subghz_txrx_gen_data_protocol_and_te( return ret; } -bool subghz_txrx_gen_keelog_protocol( +bool subghz_txrx_gen_keeloq_protocol( SubGhzTxRx* instance, const char* name_preset, uint32_t frequency, diff --git a/applications/main/subghz/helpers/subghz_txrx_create_potocol_key.h b/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.h similarity index 98% rename from applications/main/subghz/helpers/subghz_txrx_create_potocol_key.h rename to applications/main/subghz/helpers/subghz_txrx_create_protocol_key.h index 5eed93034f6..514a5733c79 100644 --- a/applications/main/subghz/helpers/subghz_txrx_create_potocol_key.h +++ b/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.h @@ -54,7 +54,7 @@ bool subghz_txrx_gen_data_protocol_and_te( * @param cnt Counter * @return bool True if success */ -bool subghz_txrx_gen_keelog_protocol( +bool subghz_txrx_gen_keeloq_protocol( SubGhzTxRx* instance, const char* name_preset, uint32_t frequency, diff --git a/applications/main/subghz/scenes/subghz_scene_set_type.c b/applications/main/subghz/scenes/subghz_scene_set_type.c index 32e0d658848..d0571f1b13e 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_type.c +++ b/applications/main/subghz/scenes/subghz_scene_set_type.c @@ -1,5 +1,5 @@ #include "../subghz_i.h" -#include "../helpers/subghz_txrx_create_potocol_key.h" +#include "../helpers/subghz_txrx_create_protocol_key.h" #include #include @@ -172,7 +172,7 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { subghz->txrx, "AM650", 433920000, SUBGHZ_PROTOCOL_GATE_TX_NAME, rev_key, 24); break; case SubmenuIndexDoorHan_433_92: - generated_protocol = subghz_txrx_gen_keelog_protocol( + generated_protocol = subghz_txrx_gen_keeloq_protocol( subghz->txrx, "AM650", 433920000, "DoorHan", key, 0x2, 0x0003); if(!generated_protocol) { furi_string_set( @@ -181,7 +181,7 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { } break; case SubmenuIndexDoorHan_315_00: - generated_protocol = subghz_txrx_gen_keelog_protocol( + generated_protocol = subghz_txrx_gen_keeloq_protocol( subghz->txrx, "AM650", 315000000, "DoorHan", key, 0x2, 0x0003); if(!generated_protocol) { furi_string_set( diff --git a/lib/subghz/protocols/keeloq_common.c b/lib/subghz/protocols/keeloq_common.c index ddbf1c9174c..041494f90a3 100644 --- a/lib/subghz/protocols/keeloq_common.c +++ b/lib/subghz/protocols/keeloq_common.c @@ -23,7 +23,7 @@ inline uint32_t subghz_protocol_keeloq_common_encrypt(const uint32_t data, const } /** Simple Learning Decrypt - * @param data - keelog encrypt data + * @param data - keeloq encrypt data * @param key - manufacture (64bit) * @return 0xBSSSCCCC, B(4bit) key, S(10bit) serial&0x3FF, C(16bit) counter */ From aa8a369e2a175fff226a84c75126eac8d24d625e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Thu, 11 May 2023 18:50:17 +0900 Subject: [PATCH 553/824] Rpc: add desktop service. Desktop: refactor locking routine. (#2665) * Rpc: add desktop service * Protobuf: sync to latest release. Desktop: rewrite PIN locking, finalize locking API * Desktop: cleanup code a little bit --- applications/services/desktop/desktop.c | 41 ++++- applications/services/desktop/desktop.h | 6 + .../services/desktop/desktop_settings.h | 3 +- applications/services/desktop/helpers/pin.c | 74 +++++++++ applications/services/desktop/helpers/pin.h | 11 ++ .../services/desktop/helpers/pin_lock.c | 140 ------------------ .../services/desktop/helpers/pin_lock.h | 21 --- .../desktop/scenes/desktop_scene_lock_menu.c | 19 +-- .../desktop/scenes/desktop_scene_locked.c | 4 +- .../desktop/scenes/desktop_scene_pin_input.c | 8 +- .../services/desktop/views/desktop_events.h | 1 - .../desktop/views/desktop_view_lock_menu.c | 14 +- .../desktop/views/desktop_view_lock_menu.h | 2 - .../views/desktop_view_pin_setup_done.c | 79 ---------- .../views/desktop_view_pin_setup_done.h | 15 -- applications/services/rpc/rpc.c | 4 + applications/services/rpc/rpc_desktop.c | 73 +++++++++ applications/services/rpc/rpc_i.h | 3 + .../scenes/desktop_settings_scene_pin_auth.c | 4 +- .../scenes/desktop_settings_scene_pin_error.c | 2 +- .../scenes/desktop_settings_scene_pin_setup.c | 4 +- assets/protobuf | 2 +- 22 files changed, 222 insertions(+), 308 deletions(-) create mode 100644 applications/services/desktop/helpers/pin.c create mode 100644 applications/services/desktop/helpers/pin.h delete mode 100644 applications/services/desktop/helpers/pin_lock.c delete mode 100644 applications/services/desktop/helpers/pin_lock.h delete mode 100644 applications/services/desktop/views/desktop_view_pin_setup_done.c delete mode 100644 applications/services/desktop/views/desktop_view_pin_setup_done.h create mode 100644 applications/services/rpc/rpc_desktop.c diff --git a/applications/services/desktop/desktop.c b/applications/services/desktop/desktop.c index bdb73009952..28d09cc0d9d 100644 --- a/applications/services/desktop/desktop.c +++ b/applications/services/desktop/desktop.c @@ -6,6 +6,8 @@ #include #include #include +#include +#include #include "animations/animation_manager.h" #include "desktop/scenes/desktop_scene.h" @@ -14,7 +16,7 @@ #include "desktop/views/desktop_view_pin_input.h" #include "desktop/views/desktop_view_pin_timeout.h" #include "desktop_i.h" -#include "helpers/pin_lock.h" +#include "helpers/pin.h" #include "helpers/slideshow_filename.h" #define TAG "Desktop" @@ -132,6 +134,15 @@ static void desktop_auto_lock_inhibit(Desktop* desktop) { } void desktop_lock(Desktop* desktop) { + furi_hal_rtc_set_flag(FuriHalRtcFlagLock); + furi_hal_rtc_set_pin_fails(0); + + if(desktop->settings.pin_code.length) { + Cli* cli = furi_record_open(RECORD_CLI); + cli_session_close(cli); + furi_record_close(RECORD_CLI); + } + desktop_auto_lock_inhibit(desktop); scene_manager_set_scene_state( desktop->scene_manager, DesktopSceneLocked, SCENE_LOCKED_FIRST_ENTER); @@ -147,6 +158,13 @@ void desktop_unlock(Desktop* desktop) { desktop_view_locked_unlock(desktop->locked_view); scene_manager_search_and_switch_to_previous_scene(desktop->scene_manager, DesktopSceneMain); desktop_auto_lock_arm(desktop); + furi_hal_rtc_reset_flag(FuriHalRtcFlagLock); + + if(desktop->settings.pin_code.length) { + Cli* cli = furi_record_open(RECORD_CLI); + cli_session_open(cli, &cli_vcp); + furi_record_close(RECORD_CLI); + } } void desktop_set_dummy_mode_state(Desktop* desktop, bool enabled) { @@ -290,11 +308,14 @@ Desktop* desktop_alloc() { desktop->auto_lock_timer = furi_timer_alloc(desktop_auto_lock_timer_callback, FuriTimerTypeOnce, desktop); + furi_record_create(RECORD_DESKTOP, desktop); + return desktop; } void desktop_free(Desktop* desktop) { furi_assert(desktop); + furi_check(furi_record_destroy(RECORD_DESKTOP)); furi_pubsub_unsubscribe( loader_get_pubsub(desktop->loader), desktop->app_start_stop_subscription); @@ -352,6 +373,16 @@ static bool desktop_check_file_flag(const char* flag_path) { return exists; } +bool desktop_api_is_locked(Desktop* instance) { + furi_assert(instance); + return furi_hal_rtc_is_flag_set(FuriHalRtcFlagLock); +} + +void desktop_api_unlock(Desktop* instance) { + furi_assert(instance); + view_dispatcher_send_custom_event(instance->view_dispatcher, DesktopLockedEventUnlocked); +} + int32_t desktop_srv(void* p) { UNUSED(p); @@ -375,14 +406,12 @@ int32_t desktop_srv(void* p) { scene_manager_next_scene(desktop->scene_manager, DesktopSceneMain); - desktop_pin_lock_init(&desktop->settings); - - if(!desktop_pin_lock_is_locked()) { + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagLock)) { + desktop_lock(desktop); + } else { if(!loader_is_locked(desktop->loader)) { desktop_auto_lock_arm(desktop); } - } else { - desktop_lock(desktop); } if(desktop_check_file_flag(SLIDESHOW_FS_PATH)) { diff --git a/applications/services/desktop/desktop.h b/applications/services/desktop/desktop.h index f5608207d0a..5b12647b8a5 100644 --- a/applications/services/desktop/desktop.h +++ b/applications/services/desktop/desktop.h @@ -1,3 +1,9 @@ #pragma once typedef struct Desktop Desktop; + +#define RECORD_DESKTOP "desktop" + +bool desktop_api_is_locked(Desktop* instance); + +void desktop_api_unlock(Desktop* instance); diff --git a/applications/services/desktop/desktop_settings.h b/applications/services/desktop/desktop_settings.h index e502c35f23b..5d1b6126feb 100644 --- a/applications/services/desktop/desktop_settings.h +++ b/applications/services/desktop/desktop_settings.h @@ -8,7 +8,7 @@ #include #include -#define DESKTOP_SETTINGS_VER (6) +#define DESKTOP_SETTINGS_VER (7) #define DESKTOP_SETTINGS_PATH INT_PATH(DESKTOP_SETTINGS_FILE_NAME) #define DESKTOP_SETTINGS_MAGIC (0x17) @@ -52,7 +52,6 @@ typedef struct { FavoriteApp favorite_primary; FavoriteApp favorite_secondary; PinCode pin_code; - uint8_t is_locked; uint32_t auto_lock_delay_ms; uint8_t dummy_mode; } DesktopSettings; diff --git a/applications/services/desktop/helpers/pin.c b/applications/services/desktop/helpers/pin.c new file mode 100644 index 00000000000..8a79a1fb8b6 --- /dev/null +++ b/applications/services/desktop/helpers/pin.c @@ -0,0 +1,74 @@ +#include "pin.h" + +#include +#include +#include +#include +#include +#include + +#include "../desktop_i.h" + +static const NotificationSequence sequence_pin_fail = { + &message_display_backlight_on, + + &message_red_255, + &message_vibro_on, + &message_delay_100, + &message_vibro_off, + &message_red_0, + + &message_delay_250, + + &message_red_255, + &message_vibro_on, + &message_delay_100, + &message_vibro_off, + &message_red_0, + NULL, +}; + +static const uint8_t desktop_helpers_fails_timeout[] = { + 0, + 0, + 0, + 0, + 30, + 60, + 90, + 120, + 150, + 180, + /* +60 for every next fail */ +}; + +void desktop_pin_lock_error_notify() { + NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); + notification_message(notification, &sequence_pin_fail); + furi_record_close(RECORD_NOTIFICATION); +} + +uint32_t desktop_pin_lock_get_fail_timeout() { + uint32_t pin_fails = furi_hal_rtc_get_pin_fails(); + uint32_t pin_timeout = 0; + uint32_t max_index = COUNT_OF(desktop_helpers_fails_timeout) - 1; + if(pin_fails <= max_index) { + pin_timeout = desktop_helpers_fails_timeout[pin_fails]; + } else { + pin_timeout = desktop_helpers_fails_timeout[max_index] + (pin_fails - max_index) * 60; + } + + return pin_timeout; +} + +bool desktop_pin_compare(const PinCode* pin_code1, const PinCode* pin_code2) { + furi_assert(pin_code1); + furi_assert(pin_code2); + bool result = false; + + if(pin_code1->length == pin_code2->length) { + result = !memcmp(pin_code1->data, pin_code2->data, pin_code1->length); + } + + return result; +} diff --git a/applications/services/desktop/helpers/pin.h b/applications/services/desktop/helpers/pin.h new file mode 100644 index 00000000000..e5410723e5c --- /dev/null +++ b/applications/services/desktop/helpers/pin.h @@ -0,0 +1,11 @@ +#pragma once +#include +#include +#include "../desktop.h" +#include + +void desktop_pin_lock_error_notify(); + +uint32_t desktop_pin_lock_get_fail_timeout(); + +bool desktop_pin_compare(const PinCode* pin_code1, const PinCode* pin_code2); diff --git a/applications/services/desktop/helpers/pin_lock.c b/applications/services/desktop/helpers/pin_lock.c deleted file mode 100644 index 22fcabe7d50..00000000000 --- a/applications/services/desktop/helpers/pin_lock.c +++ /dev/null @@ -1,140 +0,0 @@ - -#include -#include -#include -#include -#include -#include - -#include "../helpers/pin_lock.h" -#include "../desktop_i.h" -#include -#include - -static const NotificationSequence sequence_pin_fail = { - &message_display_backlight_on, - - &message_red_255, - &message_vibro_on, - &message_delay_100, - &message_vibro_off, - &message_red_0, - - &message_delay_250, - - &message_red_255, - &message_vibro_on, - &message_delay_100, - &message_vibro_off, - &message_red_0, - NULL, -}; - -static const uint8_t desktop_helpers_fails_timeout[] = { - 0, - 0, - 0, - 0, - 30, - 60, - 90, - 120, - 150, - 180, - /* +60 for every next fail */ -}; - -void desktop_pin_lock_error_notify() { - NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); - notification_message(notification, &sequence_pin_fail); - furi_record_close(RECORD_NOTIFICATION); -} - -uint32_t desktop_pin_lock_get_fail_timeout() { - uint32_t pin_fails = furi_hal_rtc_get_pin_fails(); - uint32_t pin_timeout = 0; - uint32_t max_index = COUNT_OF(desktop_helpers_fails_timeout) - 1; - if(pin_fails <= max_index) { - pin_timeout = desktop_helpers_fails_timeout[pin_fails]; - } else { - pin_timeout = desktop_helpers_fails_timeout[max_index] + (pin_fails - max_index) * 60; - } - - return pin_timeout; -} - -void desktop_pin_lock(DesktopSettings* settings) { - furi_assert(settings); - - furi_hal_rtc_set_pin_fails(0); - furi_hal_rtc_set_flag(FuriHalRtcFlagLock); - Cli* cli = furi_record_open(RECORD_CLI); - cli_session_close(cli); - furi_record_close(RECORD_CLI); - settings->is_locked = 1; - DESKTOP_SETTINGS_SAVE(settings); -} - -void desktop_pin_unlock(DesktopSettings* settings) { - furi_assert(settings); - - furi_hal_rtc_reset_flag(FuriHalRtcFlagLock); - Cli* cli = furi_record_open(RECORD_CLI); - cli_session_open(cli, &cli_vcp); - furi_record_close(RECORD_CLI); - settings->is_locked = 0; - DESKTOP_SETTINGS_SAVE(settings); -} - -void desktop_pin_lock_init(DesktopSettings* settings) { - furi_assert(settings); - - if(settings->pin_code.length > 0) { - if(settings->is_locked == 1) { - furi_hal_rtc_set_flag(FuriHalRtcFlagLock); - } else { - if(desktop_pin_lock_is_locked()) { - settings->is_locked = 1; - DESKTOP_SETTINGS_SAVE(settings); - } - } - } else { - furi_hal_rtc_set_pin_fails(0); - furi_hal_rtc_reset_flag(FuriHalRtcFlagLock); - } - - if(desktop_pin_lock_is_locked()) { - Cli* cli = furi_record_open(RECORD_CLI); - cli_session_close(cli); - furi_record_close(RECORD_CLI); - } -} - -bool desktop_pin_lock_verify(const PinCode* pin_set, const PinCode* pin_entered) { - bool result = false; - if(desktop_pins_are_equal(pin_set, pin_entered)) { - furi_hal_rtc_set_pin_fails(0); - result = true; - } else { - uint32_t pin_fails = furi_hal_rtc_get_pin_fails(); - furi_hal_rtc_set_pin_fails(pin_fails + 1); - result = false; - } - return result; -} - -bool desktop_pin_lock_is_locked() { - return furi_hal_rtc_is_flag_set(FuriHalRtcFlagLock); -} - -bool desktop_pins_are_equal(const PinCode* pin_code1, const PinCode* pin_code2) { - furi_assert(pin_code1); - furi_assert(pin_code2); - bool result = false; - - if(pin_code1->length == pin_code2->length) { - result = !memcmp(pin_code1->data, pin_code2->data, pin_code1->length); - } - - return result; -} diff --git a/applications/services/desktop/helpers/pin_lock.h b/applications/services/desktop/helpers/pin_lock.h deleted file mode 100644 index 028ae6d22f8..00000000000 --- a/applications/services/desktop/helpers/pin_lock.h +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once -#include -#include -#include "../desktop.h" -#include - -void desktop_pin_lock_error_notify(); - -uint32_t desktop_pin_lock_get_fail_timeout(); - -void desktop_pin_lock(DesktopSettings* settings); - -void desktop_pin_unlock(DesktopSettings* settings); - -bool desktop_pin_lock_is_locked(); - -void desktop_pin_lock_init(DesktopSettings* settings); - -bool desktop_pin_lock_verify(const PinCode* pin_set, const PinCode* pin_entered); - -bool desktop_pins_are_equal(const PinCode* pin_code1, const PinCode* pin_code2); diff --git a/applications/services/desktop/scenes/desktop_scene_lock_menu.c b/applications/services/desktop/scenes/desktop_scene_lock_menu.c index bfaa8a036a6..105b2b37a24 100644 --- a/applications/services/desktop/scenes/desktop_scene_lock_menu.c +++ b/applications/services/desktop/scenes/desktop_scene_lock_menu.c @@ -10,7 +10,7 @@ #include "../views/desktop_view_lock_menu.h" #include "desktop_scene_i.h" #include "desktop_scene.h" -#include "../helpers/pin_lock.h" +#include "../helpers/pin.h" #define TAG "DesktopSceneLock" @@ -25,7 +25,6 @@ void desktop_scene_lock_menu_on_enter(void* context) { DESKTOP_SETTINGS_LOAD(&desktop->settings); scene_manager_set_scene_state(desktop->scene_manager, DesktopSceneLockMenu, 0); desktop_lock_menu_set_callback(desktop->lock_menu, desktop_scene_lock_menu_callback, desktop); - desktop_lock_menu_set_pin_state(desktop->lock_menu, desktop->settings.pin_code.length > 0); desktop_lock_menu_set_dummy_mode_state(desktop->lock_menu, desktop->settings.dummy_mode); desktop_lock_menu_set_stealth_mode_state( desktop->lock_menu, furi_hal_rtc_is_flag_set(FuriHalRtcFlagStealthMode)); @@ -44,7 +43,6 @@ bool desktop_scene_lock_menu_on_event(void* context, SceneManagerEvent event) { if(check_pin_changed) { DESKTOP_SETTINGS_LOAD(&desktop->settings); if(desktop->settings.pin_code.length > 0) { - desktop_lock_menu_set_pin_state(desktop->lock_menu, true); scene_manager_set_scene_state(desktop->scene_manager, DesktopSceneLockMenu, 0); } } @@ -55,21 +53,6 @@ bool desktop_scene_lock_menu_on_event(void* context, SceneManagerEvent event) { desktop_lock(desktop); consumed = true; break; - case DesktopLockMenuEventPinLock: - if(desktop->settings.pin_code.length > 0) { - desktop_pin_lock(&desktop->settings); - desktop_lock(desktop); - } else { - LoaderStatus status = - loader_start(desktop->loader, "Desktop", DESKTOP_SETTINGS_RUN_PIN_SETUP_ARG); - if(status == LoaderStatusOk) { - scene_manager_set_scene_state(desktop->scene_manager, DesktopSceneLockMenu, 1); - } else { - FURI_LOG_E(TAG, "Unable to start desktop settings"); - } - } - consumed = true; - break; case DesktopLockMenuEventDummyModeOn: desktop_set_dummy_mode_state(desktop, true); scene_manager_search_and_switch_to_previous_scene( diff --git a/applications/services/desktop/scenes/desktop_scene_locked.c b/applications/services/desktop/scenes/desktop_scene_locked.c index af19efc7479..f64ef83716e 100644 --- a/applications/services/desktop/scenes/desktop_scene_locked.c +++ b/applications/services/desktop/scenes/desktop_scene_locked.c @@ -7,7 +7,7 @@ #include "../desktop.h" #include "../desktop_i.h" -#include "../helpers/pin_lock.h" +#include "../helpers/pin.h" #include "../animations/animation_manager.h" #include "../views/desktop_events.h" #include "../views/desktop_view_pin_input.h" @@ -45,7 +45,7 @@ void desktop_scene_locked_on_enter(void* context) { bool switch_to_timeout_scene = false; uint32_t state = scene_manager_get_scene_state(desktop->scene_manager, DesktopSceneLocked); if(state == SCENE_LOCKED_FIRST_ENTER) { - bool pin_locked = desktop_pin_lock_is_locked(); + bool pin_locked = desktop->settings.pin_code.length > 0; view_port_enabled_set(desktop->lock_icon_viewport, true); Gui* gui = furi_record_open(RECORD_GUI); gui_set_lockdown(gui, true); diff --git a/applications/services/desktop/scenes/desktop_scene_pin_input.c b/applications/services/desktop/scenes/desktop_scene_pin_input.c index 9392309e662..157acebf540 100644 --- a/applications/services/desktop/scenes/desktop_scene_pin_input.c +++ b/applications/services/desktop/scenes/desktop_scene_pin_input.c @@ -12,7 +12,7 @@ #include "../animations/animation_manager.h" #include "../views/desktop_events.h" #include "../views/desktop_view_pin_input.h" -#include "../helpers/pin_lock.h" +#include "../helpers/pin.h" #include "desktop_scene.h" #include "desktop_scene_i.h" @@ -54,9 +54,12 @@ static void desktop_scene_pin_input_back_callback(void* context) { static void desktop_scene_pin_input_done_callback(const PinCode* pin_code, void* context) { Desktop* desktop = (Desktop*)context; - if(desktop_pin_lock_verify(&desktop->settings.pin_code, pin_code)) { + if(desktop_pin_compare(&desktop->settings.pin_code, pin_code)) { + furi_hal_rtc_set_pin_fails(0); view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopPinInputEventUnlocked); } else { + uint32_t pin_fails = furi_hal_rtc_get_pin_fails(); + furi_hal_rtc_set_pin_fails(pin_fails + 1); view_dispatcher_send_custom_event( desktop->view_dispatcher, DesktopPinInputEventUnlockFailed); } @@ -126,7 +129,6 @@ bool desktop_scene_pin_input_on_event(void* context, SceneManagerEvent event) { consumed = true; break; case DesktopPinInputEventUnlocked: - desktop_pin_unlock(&desktop->settings); desktop_unlock(desktop); consumed = true; break; diff --git a/applications/services/desktop/views/desktop_events.h b/applications/services/desktop/views/desktop_events.h index 983e8443853..e366885fe65 100644 --- a/applications/services/desktop/views/desktop_events.h +++ b/applications/services/desktop/views/desktop_events.h @@ -31,7 +31,6 @@ typedef enum { DesktopDebugEventExit, DesktopLockMenuEventLock, - DesktopLockMenuEventPinLock, DesktopLockMenuEventDummyModeOn, DesktopLockMenuEventDummyModeOff, DesktopLockMenuEventStealthModeOn, diff --git a/applications/services/desktop/views/desktop_view_lock_menu.c b/applications/services/desktop/views/desktop_view_lock_menu.c index 8b25a890f16..f4790ebb8cf 100644 --- a/applications/services/desktop/views/desktop_view_lock_menu.c +++ b/applications/services/desktop/views/desktop_view_lock_menu.c @@ -23,14 +23,6 @@ void desktop_lock_menu_set_callback( lock_menu->context = context; } -void desktop_lock_menu_set_pin_state(DesktopLockMenuView* lock_menu, bool pin_is_set) { - with_view_model( - lock_menu->view, - DesktopLockMenuViewModel * model, - { model->pin_is_set = pin_is_set; }, - true); -} - void desktop_lock_menu_set_dummy_mode_state(DesktopLockMenuView* lock_menu, bool dummy_mode) { with_view_model( lock_menu->view, @@ -102,7 +94,6 @@ bool desktop_lock_menu_input_callback(InputEvent* event, void* context) { bool consumed = false; bool dummy_mode = false; bool stealth_mode = false; - bool pin_is_set = false; bool update = false; with_view_model( @@ -131,15 +122,12 @@ bool desktop_lock_menu_input_callback(InputEvent* event, void* context) { idx = model->idx; dummy_mode = model->dummy_mode; stealth_mode = model->stealth_mode; - pin_is_set = model->pin_is_set; }, update); if(event->key == InputKeyOk) { if((idx == DesktopLockMenuIndexLock)) { - if((pin_is_set) && (event->type == InputTypeShort)) { - lock_menu->callback(DesktopLockMenuEventPinLock, lock_menu->context); - } else if((pin_is_set == false) && (event->type == InputTypeShort)) { + if((event->type == InputTypeShort)) { lock_menu->callback(DesktopLockMenuEventLock, lock_menu->context); } } else if(idx == DesktopLockMenuIndexStealth) { diff --git a/applications/services/desktop/views/desktop_view_lock_menu.h b/applications/services/desktop/views/desktop_view_lock_menu.h index 03ce6fa8084..8ac3a727338 100644 --- a/applications/services/desktop/views/desktop_view_lock_menu.h +++ b/applications/services/desktop/views/desktop_view_lock_menu.h @@ -17,7 +17,6 @@ struct DesktopLockMenuView { typedef struct { uint8_t idx; - bool pin_is_set; bool dummy_mode; bool stealth_mode; } DesktopLockMenuViewModel; @@ -28,7 +27,6 @@ void desktop_lock_menu_set_callback( void* context); View* desktop_lock_menu_get_view(DesktopLockMenuView* lock_menu); -void desktop_lock_menu_set_pin_state(DesktopLockMenuView* lock_menu, bool pin_is_set); void desktop_lock_menu_set_dummy_mode_state(DesktopLockMenuView* lock_menu, bool dummy_mode); void desktop_lock_menu_set_stealth_mode_state(DesktopLockMenuView* lock_menu, bool stealth_mode); void desktop_lock_menu_set_idx(DesktopLockMenuView* lock_menu, uint8_t idx); diff --git a/applications/services/desktop/views/desktop_view_pin_setup_done.c b/applications/services/desktop/views/desktop_view_pin_setup_done.c deleted file mode 100644 index 561b12861e1..00000000000 --- a/applications/services/desktop/views/desktop_view_pin_setup_done.c +++ /dev/null @@ -1,79 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../desktop_i.h" -#include "desktop_view_pin_setup_done.h" - -struct DesktopViewPinSetupDone { - View* view; - DesktopViewPinSetupDoneDoneCallback callback; - void* context; -}; - -static void desktop_view_pin_done_draw(Canvas* canvas, void* model) { - furi_assert(canvas); - UNUSED(model); - - canvas_set_font(canvas, FontPrimary); - elements_multiline_text_aligned( - canvas, 64, 0, AlignCenter, AlignTop, "Prepare to use\narrows as\nPIN symbols"); - - canvas_set_font(canvas, FontSecondary); - elements_multiline_text(canvas, 58, 24, "Prepare to use\narrows as\nPIN symbols"); - - canvas_draw_icon(canvas, 16, 18, &I_Pin_attention_dpad_29x29); - elements_button_right(canvas, "Next"); -} - -static bool desktop_view_pin_done_input(InputEvent* event, void* context) { - furi_assert(event); - furi_assert(context); - - DesktopViewPinSetupDone* instance = context; - bool consumed = false; - - if((event->key == InputKeyRight) && (event->type == InputTypeShort)) { - instance->callback(instance->context); - consumed = true; - } - - return consumed; -} - -void desktop_view_pin_done_set_callback( - DesktopViewPinSetupDone* instance, - DesktopViewPinSetupDoneDoneCallback callback, - void* context) { - furi_assert(instance); - furi_assert(callback); - instance->callback = callback; - instance->context = context; -} - -DesktopViewPinSetupDone* desktop_view_pin_done_alloc() { - DesktopViewPinSetupDone* view = malloc(sizeof(DesktopViewPinSetupDone)); - view->view = view_alloc(); - view_set_context(view->view, view); - view_set_draw_callback(view->view, desktop_view_pin_done_draw); - view_set_input_callback(view->view, desktop_view_pin_done_input); - - return view; -} - -void desktop_view_pin_done_free(DesktopViewPinSetupDone* instance) { - furi_assert(instance); - - view_free(instance->view); - free(instance); -} - -View* desktop_view_pin_done_get_view(DesktopViewPinSetupDone* instance) { - furi_assert(instance); - return instance->view; -} diff --git a/applications/services/desktop/views/desktop_view_pin_setup_done.h b/applications/services/desktop/views/desktop_view_pin_setup_done.h deleted file mode 100644 index b55677dc587..00000000000 --- a/applications/services/desktop/views/desktop_view_pin_setup_done.h +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once - -#include - -typedef struct DesktopViewPinSetupDone DesktopViewPinSetupDone; - -typedef void (*DesktopViewPinSetupDoneDoneCallback)(void*); - -void desktop_view_pin_done_set_callback( - DesktopViewPinSetupDone* instance, - DesktopViewPinSetupDoneDoneCallback callback, - void* context); -DesktopViewPinSetupDone* desktop_view_pin_done_alloc(); -void desktop_view_pin_done_free(DesktopViewPinSetupDone* instance); -View* desktop_view_pin_done_get_view(DesktopViewPinSetupDone* instance); diff --git a/applications/services/rpc/rpc.c b/applications/services/rpc/rpc.c index a759a12a9c3..b3ed4417cc9 100644 --- a/applications/services/rpc/rpc.c +++ b/applications/services/rpc/rpc.c @@ -57,6 +57,10 @@ static RpcSystemCallbacks rpc_systems[] = { .alloc = rpc_system_property_alloc, .free = NULL, }, + { + .alloc = rpc_desktop_alloc, + .free = rpc_desktop_free, + }, }; struct RpcSession { diff --git a/applications/services/rpc/rpc_desktop.c b/applications/services/rpc/rpc_desktop.c new file mode 100644 index 00000000000..dbf9796ec56 --- /dev/null +++ b/applications/services/rpc/rpc_desktop.c @@ -0,0 +1,73 @@ +#include "flipper.pb.h" +#include "rpc_i.h" +#include +#include "desktop.pb.h" + +#define TAG "RpcDesktop" + +typedef struct { + RpcSession* session; + Desktop* desktop; +} RpcDesktop; + +static void rpc_desktop_on_is_locked_request(const PB_Main* request, void* context) { + furi_assert(request); + furi_assert(context); + furi_assert(request->which_content == PB_Main_desktop_is_locked_request_tag); + + FURI_LOG_D(TAG, "IsLockedRequest"); + RpcDesktop* rpc_desktop = context; + RpcSession* session = rpc_desktop->session; + + PB_CommandStatus ret = desktop_api_is_locked(rpc_desktop->desktop) ? PB_CommandStatus_OK : + PB_CommandStatus_ERROR; + + rpc_send_and_release_empty(session, request->command_id, ret); +} + +static void rpc_desktop_on_unlock_request(const PB_Main* request, void* context) { + furi_assert(request); + furi_assert(context); + furi_assert(request->which_content == PB_Main_desktop_unlock_request_tag); + + FURI_LOG_D(TAG, "UnlockRequest"); + RpcDesktop* rpc_desktop = context; + RpcSession* session = rpc_desktop->session; + + desktop_api_unlock(rpc_desktop->desktop); + + rpc_send_and_release_empty(session, request->command_id, PB_CommandStatus_OK); +} + +void* rpc_desktop_alloc(RpcSession* session) { + furi_assert(session); + + RpcDesktop* rpc_desktop = malloc(sizeof(RpcDesktop)); + rpc_desktop->desktop = furi_record_open(RECORD_DESKTOP); + rpc_desktop->session = session; + + RpcHandler rpc_handler = { + .message_handler = NULL, + .decode_submessage = NULL, + .context = rpc_desktop, + }; + + rpc_handler.message_handler = rpc_desktop_on_is_locked_request; + rpc_add_handler(session, PB_Main_desktop_is_locked_request_tag, &rpc_handler); + + rpc_handler.message_handler = rpc_desktop_on_unlock_request; + rpc_add_handler(session, PB_Main_desktop_unlock_request_tag, &rpc_handler); + + return rpc_desktop; +} + +void rpc_desktop_free(void* context) { + furi_assert(context); + RpcDesktop* rpc_desktop = context; + + furi_assert(rpc_desktop->desktop); + furi_record_close(RECORD_DESKTOP); + + rpc_desktop->session = NULL; + free(rpc_desktop); +} \ No newline at end of file diff --git a/applications/services/rpc/rpc_i.h b/applications/services/rpc/rpc_i.h index 91a176da87b..16e5e594d16 100644 --- a/applications/services/rpc/rpc_i.h +++ b/applications/services/rpc/rpc_i.h @@ -36,6 +36,9 @@ void* rpc_system_gpio_alloc(RpcSession* session); void rpc_system_gpio_free(void* ctx); void* rpc_system_property_alloc(RpcSession* session); +void* rpc_desktop_alloc(RpcSession* session); +void rpc_desktop_free(void* ctx); + void rpc_debug_print_message(const PB_Main* message); void rpc_debug_print_data(const char* prefix, uint8_t* buffer, size_t size); diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_auth.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_auth.c index 5fed235cecb..be2ee48259b 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_auth.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_auth.c @@ -1,7 +1,7 @@ #include #include #include -#include +#include #include "../desktop_settings_app.h" #include #include @@ -18,7 +18,7 @@ static void pin_auth_done_callback(const PinCode* pin_code, void* context) { DesktopSettingsApp* app = context; app->pincode_buffer = *pin_code; - if(desktop_pins_are_equal(&app->settings.pin_code, pin_code)) { + if(desktop_pin_compare(&app->settings.pin_code, pin_code)) { view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EVENT_PINS_EQUAL); } else { view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EVENT_PINS_DIFFERENT); diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_error.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_error.c index dd1e8579572..508992cee78 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_error.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_error.c @@ -6,7 +6,7 @@ #include #include "desktop_settings_scene.h" #include "desktop_settings_scene_i.h" -#include +#include #include "../desktop_settings_app.h" #define SCENE_EVENT_EXIT (0U) diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup.c index bf0f48ae6ed..1603aa33722 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup.c @@ -7,7 +7,7 @@ #include #include "desktop_settings_scene.h" #include "desktop_settings_scene_i.h" -#include +#include #define SCENE_EVENT_EXIT (0U) #define SCENE_EVENT_1ST_PIN_ENTERED (1U) @@ -25,7 +25,7 @@ static void pin_setup_done_callback(const PinCode* pin_code, void* context) { view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EVENT_1ST_PIN_ENTERED); } else { app->pincode_buffer_filled = false; - if(desktop_pins_are_equal(&app->pincode_buffer, pin_code)) { + if(desktop_pin_compare(&app->pincode_buffer, pin_code)) { view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EVENT_PINS_EQUAL); } else { view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EVENT_PINS_DIFFERENT); diff --git a/assets/protobuf b/assets/protobuf index 1f6b4a08c5d..a13c5ddd039 160000 --- a/assets/protobuf +++ b/assets/protobuf @@ -1 +1 @@ -Subproject commit 1f6b4a08c5d05c2b17926a3ba79f60109638932f +Subproject commit a13c5ddd0397511bd4c6de4afdd1031a5b6f5bca From c496962f950e2d38384b86d7ae5b590f744e4a79 Mon Sep 17 00:00:00 2001 From: end-me-please <90796271+end-me-please@users.noreply.github.com> Date: Sun, 14 May 2023 08:12:30 +0200 Subject: [PATCH 554/824] fix typo in FuriHalDebuging.md (#2667) --- documentation/FuriHalDebuging.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/FuriHalDebuging.md b/documentation/FuriHalDebuging.md index 8ff770163b2..e7f2d8f2abe 100644 --- a/documentation/FuriHalDebuging.md +++ b/documentation/FuriHalDebuging.md @@ -1,6 +1,6 @@ # Furi HAL Debugging -Some Furi subsystem got additional debugging features that can be enabled by adding additional defines to firmware compilation. +Some Furi subsystems got additional debugging features that can be enabled by adding additional defines to firmware compilation. Usually they are used for low level tracing and profiling or signal redirection/duplication. @@ -23,4 +23,4 @@ There are 3 signals that will be exposed to external GPIO pins: There are 2 signals that will be exposed to external GPIO pins: - `WFI` - `PB2` - Light sleep (wait for interrupt) used. Basically this is lightest and most non-breaking things power save mode. All function and debug should work correctly in this mode. -- `STOP` - `PC3` - STOP mode used. Platform deep sleep mode. Extremely fragile mode where most of the silicon is disabled or in unusable state. Debugging MCU in this mode is nearly impossible. \ No newline at end of file +- `STOP` - `PC3` - STOP mode used. Platform deep sleep mode. Extremely fragile mode where most of the silicon is disabled or in unusable state. Debugging MCU in this mode is nearly impossible. From 8d1f5b04b32a55141359ba6053a895a56c008c4c Mon Sep 17 00:00:00 2001 From: hedger Date: Sun, 14 May 2023 14:49:52 +0300 Subject: [PATCH 555/824] [FL-3317] fbt: allow strings for fap_version field in app manifests (#2672) --- documentation/AppManifests.md | 2 +- scripts/debug/flipperapps.py | 5 ++++- scripts/fbt/appmanifest.py | 9 ++++++++- .../ufbt/project_template/app_template/application.fam | 2 +- 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/documentation/AppManifests.md b/documentation/AppManifests.md index 99f6386b2c9..b48a6b8eddc 100644 --- a/documentation/AppManifests.md +++ b/documentation/AppManifests.md @@ -47,7 +47,7 @@ Only two parameters are mandatory: **_appid_** and **_apptype_**. Others are opt The following parameters are used only for [FAPs](./AppsOnSDCard.md): - **sources**: list of strings, file name masks used for gathering sources within the app folder. The default value of `["*.c*"]` includes C and C++ source files. Applications cannot use the `"lib"` folder for their own source code, as it is reserved for **fap_private_libs**. -- **fap_version**: tuple, 2 numbers in the form of (x,y): application version to be embedded within .fap file. The default value is (0,1), meaning version "0.1". +- **fap_version**: string, application version. The default value is "0.1". You can also use a tuple of 2 numbers in the form of (x,y) to specify the version. It is also possible to add more dot-separated parts to the version, like patch number, but only major and minor version numbers are stored in the built .fap. - **fap_icon**: name of a `.png` file, 1-bit color depth, 10x10px, to be embedded within `.fap` file. - **fap_libs**: list of extra libraries to link the application against. Provides access to extra functions that are not exported as a part of main firmware at the expense of increased `.fap` file size and RAM consumption. - **fap_category**: string, may be empty. App subcategory, also determines the path of the FAP within the apps folder in the file system. diff --git a/scripts/debug/flipperapps.py b/scripts/debug/flipperapps.py index 90582c1e44a..608c30412ff 100644 --- a/scripts/debug/flipperapps.py +++ b/scripts/debug/flipperapps.py @@ -196,7 +196,10 @@ def handle_exit(self, event) -> None: self.set_debug_mode(False) def set_debug_mode(self, mode: bool) -> None: - gdb.execute(f"set variable furi_hal_debug_gdb_session_active = {int(mode)}") + try: + gdb.execute(f"set variable furi_hal_debug_gdb_session_active = {int(mode)}") + except gdb.error as e: + print(f"Failed to set debug mode: {e}") # Init additional 'fap-set-debug-elf-root' command and set up hooks diff --git a/scripts/fbt/appmanifest.py b/scripts/fbt/appmanifest.py index 5e41f4c0e30..ed1654e3663 100644 --- a/scripts/fbt/appmanifest.py +++ b/scripts/fbt/appmanifest.py @@ -56,7 +56,7 @@ class Library: # .fap-specific sources: List[str] = field(default_factory=lambda: ["*.c*"]) - fap_version: Tuple[int] = field(default_factory=lambda: (0, 1)) + fap_version: str | Tuple[int] = "0.1" fap_icon: Optional[str] = None fap_libs: List[str] = field(default_factory=list) fap_category: str = "" @@ -84,6 +84,13 @@ def is_default_deployable(self): def __post_init__(self): if self.apptype == FlipperAppType.PLUGIN: self.stack_size = 0 + if isinstance(self.fap_version, str): + try: + self.fap_version = tuple(int(v) for v in self.fap_version.split(".")) + except ValueError: + raise FlipperManifestException( + f"Invalid version string '{self.fap_version}'. Must be in the form 'major.minor'" + ) class AppManager: diff --git a/scripts/ufbt/project_template/app_template/application.fam b/scripts/ufbt/project_template/app_template/application.fam index 37a4ce66552..a2d23ef4660 100644 --- a/scripts/ufbt/project_template/app_template/application.fam +++ b/scripts/ufbt/project_template/app_template/application.fam @@ -8,7 +8,7 @@ App( stack_size=2 * 1024, fap_category="Examples", # Optional values - # fap_version=(0, 1), # (major, minor) + # fap_version="0.1", fap_icon="@FBT_APPID@.png", # 10x10 1-bit PNG # fap_description="A simple app", # fap_author="J. Doe", From 341cd5e860a67b70b46a87d79d705caa1d457155 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Sun, 14 May 2023 21:04:03 +0900 Subject: [PATCH 556/824] [FL-3312] fix PIN retry count reset on reboot (#2671) --- applications/services/desktop/desktop.c | 2 +- applications/services/desktop/scenes/desktop_scene_pin_input.c | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/applications/services/desktop/desktop.c b/applications/services/desktop/desktop.c index 28d09cc0d9d..77999dfcc08 100644 --- a/applications/services/desktop/desktop.c +++ b/applications/services/desktop/desktop.c @@ -135,7 +135,6 @@ static void desktop_auto_lock_inhibit(Desktop* desktop) { void desktop_lock(Desktop* desktop) { furi_hal_rtc_set_flag(FuriHalRtcFlagLock); - furi_hal_rtc_set_pin_fails(0); if(desktop->settings.pin_code.length) { Cli* cli = furi_record_open(RECORD_CLI); @@ -159,6 +158,7 @@ void desktop_unlock(Desktop* desktop) { scene_manager_search_and_switch_to_previous_scene(desktop->scene_manager, DesktopSceneMain); desktop_auto_lock_arm(desktop); furi_hal_rtc_reset_flag(FuriHalRtcFlagLock); + furi_hal_rtc_set_pin_fails(0); if(desktop->settings.pin_code.length) { Cli* cli = furi_record_open(RECORD_CLI); diff --git a/applications/services/desktop/scenes/desktop_scene_pin_input.c b/applications/services/desktop/scenes/desktop_scene_pin_input.c index 157acebf540..e062c1b97d2 100644 --- a/applications/services/desktop/scenes/desktop_scene_pin_input.c +++ b/applications/services/desktop/scenes/desktop_scene_pin_input.c @@ -55,7 +55,6 @@ static void desktop_scene_pin_input_back_callback(void* context) { static void desktop_scene_pin_input_done_callback(const PinCode* pin_code, void* context) { Desktop* desktop = (Desktop*)context; if(desktop_pin_compare(&desktop->settings.pin_code, pin_code)) { - furi_hal_rtc_set_pin_fails(0); view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopPinInputEventUnlocked); } else { uint32_t pin_fails = furi_hal_rtc_get_pin_fails(); From 9dedcd07b604762a5e8d880567fe9147b2fb2f60 Mon Sep 17 00:00:00 2001 From: hedger Date: Mon, 15 May 2023 15:55:22 +0400 Subject: [PATCH 557/824] api: added lib/nfc/protocols/nfc_util.h (#2674) --- firmware/targets/f18/api_symbols.csv | 2 +- firmware/targets/f7/api_symbols.csv | 10 ++++++++-- lib/nfc/SConscript | 1 + lib/nfc/protocols/nfc_util.h | 8 ++++++++ 4 files changed, 18 insertions(+), 3 deletions(-) diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index 828c771662c..ee1ae115423 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,26.2,, +Version,+,26.3,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index d73a1c7bc05..e2bedffb7d2 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,26.2,, +Version,+,26.3,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -144,10 +144,12 @@ Header,+,lib/mlib/m-rbtree.h,, Header,+,lib/mlib/m-tuple.h,, Header,+,lib/mlib/m-variant.h,, Header,+,lib/nfc/nfc_device.h,, +Header,+,lib/nfc/protocols/nfc_util.h,, Header,+,lib/one_wire/maxim_crc.h,, Header,+,lib/one_wire/one_wire_host.h,, Header,+,lib/one_wire/one_wire_slave.h,, Header,+,lib/print/wrappers.h,, +Header,+,lib/pulse_reader/pulse_reader.h,, Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_adc.h,, Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_bus.h,, Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_comp.h,, @@ -175,7 +177,6 @@ Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_tim.h,, Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_usart.h,, Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_utils.h,, Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_wwdg.h,, -Header,+,lib/pulse_reader/pulse_reader.h,, Header,+,lib/subghz/blocks/const.h,, Header,+,lib/subghz/blocks/decoder.h,, Header,+,lib/subghz/blocks/encoder.h,, @@ -2064,6 +2065,11 @@ Function,+,nfc_device_save_shadow,_Bool,"NfcDevice*, const char*" Function,+,nfc_device_set_loading_callback,void,"NfcDevice*, NfcLoadingCallback, void*" Function,+,nfc_device_set_name,void,"NfcDevice*, const char*" Function,+,nfc_file_select,_Bool,NfcDevice* +Function,+,nfc_util_bytes2num,uint64_t,"const uint8_t*, uint8_t" +Function,+,nfc_util_even_parity32,uint8_t,uint32_t +Function,+,nfc_util_num2bytes,void,"uint64_t, uint8_t, uint8_t*" +Function,+,nfc_util_odd_parity,void,"const uint8_t*, uint8_t*, uint8_t" +Function,+,nfc_util_odd_parity8,uint8_t,uint8_t Function,-,nfca_append_crc16,void,"uint8_t*, uint16_t" Function,-,nfca_emulation_handler,_Bool,"uint8_t*, uint16_t, uint8_t*, uint16_t*" Function,-,nfca_get_crc16,uint16_t,"uint8_t*, uint16_t" diff --git a/lib/nfc/SConscript b/lib/nfc/SConscript index b086298de2a..b8551db8427 100644 --- a/lib/nfc/SConscript +++ b/lib/nfc/SConscript @@ -6,6 +6,7 @@ env.Append( ], SDK_HEADERS=[ File("nfc_device.h"), + File("protocols/nfc_util.h"), ], ) diff --git a/lib/nfc/protocols/nfc_util.h b/lib/nfc/protocols/nfc_util.h index 04fa7622b93..a9d5a3f8ab0 100644 --- a/lib/nfc/protocols/nfc_util.h +++ b/lib/nfc/protocols/nfc_util.h @@ -2,6 +2,10 @@ #include +#ifdef __cplusplus +extern "C" { +#endif + void nfc_util_num2bytes(uint64_t src, uint8_t len, uint8_t* dest); uint64_t nfc_util_bytes2num(const uint8_t* src, uint8_t len); @@ -11,3 +15,7 @@ uint8_t nfc_util_even_parity32(uint32_t data); uint8_t nfc_util_odd_parity8(uint8_t data); void nfc_util_odd_parity(const uint8_t* src, uint8_t* dst, uint8_t len); + +#ifdef __cplusplus +} +#endif From d062ce73d71789bcf511ebf2a75bf36501d7bf98 Mon Sep 17 00:00:00 2001 From: Max Andreev Date: Fri, 19 May 2023 12:32:30 +0300 Subject: [PATCH 558/824] Add new indexer (#2681) --- .github/workflows/build.yml | 17 +++++------------ .github/workflows/reindex.yml | 3 ++- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8358d1706aa..ca603a64a7c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -123,18 +123,11 @@ jobs: - name: 'Upload artifacts to update server' if: ${{ !github.event.pull_request.head.repo.fork }} run: | - mkdir -p ~/.ssh - ssh-keyscan -p ${{ secrets.RSYNC_DEPLOY_PORT }} -H ${{ secrets.RSYNC_DEPLOY_HOST }} > ~/.ssh/known_hosts - echo "${{ secrets.RSYNC_DEPLOY_KEY }}" > deploy_key; - chmod 600 ./deploy_key; - rsync -avzP --delete --mkpath \ - -e 'ssh -p ${{ secrets.RSYNC_DEPLOY_PORT }} -i ./deploy_key' \ - artifacts/ ${{ secrets.RSYNC_DEPLOY_USER }}@${{ secrets.RSYNC_DEPLOY_HOST }}:"${{ secrets.RSYNC_DEPLOY_BASE_PATH }}${BRANCH_NAME}/"; - rm ./deploy_key; - - - name: 'Trigger update server reindex' - if: ${{ !github.event.pull_request.head.repo.fork }} - run: curl -X POST -F 'key=${{ secrets.REINDEX_KEY }}' ${{ secrets.REINDEX_URL }} + FILES=$(for CUR in $(ls artifacts/); do echo "-F files=@artifacts/$CUR"; done) + curl --fail -L -H "Token: ${{ secrets.INDEXER_TOKEN }}" \ + -F "branch=${BRANCH_NAME}" \ + ${FILES[@]} \ + "${{ secrets.INDEXER_URL }}"/firmware/uploadfiles - name: 'Find Previous Comment' if: ${{ !github.event.pull_request.head.repo.fork && github.event.pull_request }} diff --git a/.github/workflows/reindex.yml b/.github/workflows/reindex.yml index ea850e7051b..5645f609bad 100644 --- a/.github/workflows/reindex.yml +++ b/.github/workflows/reindex.yml @@ -11,4 +11,5 @@ jobs: steps: - name: Trigger reindex run: | - curl -X POST -F 'key=${{ secrets.REINDEX_KEY }}' ${{ secrets.REINDEX_URL }} + curl --fail -L -H "Token: ${{ secrets.INDEXER_TOKEN }}" \ + "${{ secrets.INDEXER_URL }}"/firmware/reindex From d9fc408d96b5d012966e5039dced6e9c613ea1a4 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 21 May 2023 20:50:38 +0300 Subject: [PATCH 559/824] fbt: Use union for old py (#2685) --- scripts/fbt/appmanifest.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/fbt/appmanifest.py b/scripts/fbt/appmanifest.py index ed1654e3663..eb265cee864 100644 --- a/scripts/fbt/appmanifest.py +++ b/scripts/fbt/appmanifest.py @@ -1,7 +1,7 @@ import os from dataclasses import dataclass, field from enum import Enum -from typing import Callable, List, Optional, Tuple +from typing import Callable, List, Optional, Tuple, Union class FlipperManifestException(Exception): @@ -56,7 +56,7 @@ class Library: # .fap-specific sources: List[str] = field(default_factory=lambda: ["*.c*"]) - fap_version: str | Tuple[int] = "0.1" + fap_version: Union[str, Tuple[int]] = "0.1" fap_icon: Optional[str] = None fap_libs: List[str] = field(default_factory=list) fap_category: str = "" From 5f1ac6e1b14595e8727497b9227ad239d60f2d86 Mon Sep 17 00:00:00 2001 From: Yukai Li Date: Mon, 22 May 2023 03:14:18 -0600 Subject: [PATCH 560/824] fbt: Fix tar uid overflow when packaging (#2689) * fbt: Fix tar uid overflow when packaging * Fix trailing spaces --- scripts/sconsdist.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/scripts/sconsdist.py b/scripts/sconsdist.py index d2d1d2f49c9..46178113655 100644 --- a/scripts/sconsdist.py +++ b/scripts/sconsdist.py @@ -271,7 +271,13 @@ def bundle_update_package(self): self.note_dist_component( "update", "tgz", self.get_dist_path(bundle_tgz) ) - tar.add(bundle_dir, arcname=bundle_dir_name) + + # Strip uid and gid in case of overflow + def tar_filter(tarinfo): + tarinfo.uid = tarinfo.gid = 0 + return tarinfo + + tar.add(bundle_dir, arcname=bundle_dir_name, filter=tar_filter) return bundle_result From a821a2fcc0eaa72a96f2a48c0424287e7f688e06 Mon Sep 17 00:00:00 2001 From: hedger Date: Tue, 23 May 2023 14:51:21 +0400 Subject: [PATCH 561/824] [FL-3328] Removed user-specific data from tar artifacts (#2691) --- scripts/sconsdist.py | 2 ++ scripts/update.py | 3 +++ 2 files changed, 5 insertions(+) diff --git a/scripts/sconsdist.py b/scripts/sconsdist.py index 46178113655..23f9526a0b5 100644 --- a/scripts/sconsdist.py +++ b/scripts/sconsdist.py @@ -275,6 +275,8 @@ def bundle_update_package(self): # Strip uid and gid in case of overflow def tar_filter(tarinfo): tarinfo.uid = tarinfo.gid = 0 + tarinfo.mtime = 0 + tarinfo.uname = tarinfo.gname = "furippa" return tarinfo tar.add(bundle_dir, arcname=bundle_dir_name, filter=tar_filter) diff --git a/scripts/update.py b/scripts/update.py index 0f3ee6ea8b9..9f0d95d94ee 100755 --- a/scripts/update.py +++ b/scripts/update.py @@ -211,6 +211,9 @@ def _tar_filter(self, tarinfo: tarfile.TarInfo): f"Cannot package resource: name '{tarinfo.name}' too long" ) raise ValueError("Resource name too long") + tarinfo.gid = tarinfo.uid = 0 + tarinfo.mtime = 0 + tarinfo.uname = tarinfo.gname = "furippa" return tarinfo def package_resources(self, srcdir: str, dst_name: str): From 711f0fef405fefc51d08fdafd942423a6aba9bcc Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Tue, 23 May 2023 07:59:32 -0700 Subject: [PATCH 562/824] [FL-3327] Storage: common_rename is now POSIX compliant (#2693) * Storage: common_rename is now POSIX compliant * storage: check for success on storage_common_remove in file rename --------- Co-authored-by: hedger --- applications/services/storage/storage.h | 2 +- applications/services/storage/storage_external_api.c | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/applications/services/storage/storage.h b/applications/services/storage/storage.h index a1267575fb8..dccf29592c6 100644 --- a/applications/services/storage/storage.h +++ b/applications/services/storage/storage.h @@ -226,7 +226,7 @@ FS_Error storage_common_stat(Storage* storage, const char* path, FileInfo* filei */ FS_Error storage_common_remove(Storage* storage, const char* path); -/** Renames file/directory, file/directory must not be open +/** Renames file/directory, file/directory must not be open. Will overwrite existing file. * @param app pointer to the api * @param old_path old path * @param new_path new path diff --git a/applications/services/storage/storage_external_api.c b/applications/services/storage/storage_external_api.c index bf474bc9d0b..549397c87de 100644 --- a/applications/services/storage/storage_external_api.c +++ b/applications/services/storage/storage_external_api.c @@ -422,7 +422,16 @@ FS_Error storage_common_remove(Storage* storage, const char* path) { } FS_Error storage_common_rename(Storage* storage, const char* old_path, const char* new_path) { - FS_Error error = storage_common_copy(storage, old_path, new_path); + FS_Error error; + + if(storage_file_exists(storage, new_path)) { + error = storage_common_remove(storage, new_path); + if(error != FSE_OK) { + return error; + } + } + + error = storage_common_copy(storage, old_path, new_path); if(error == FSE_OK) { if(!storage_simply_remove_recursive(storage, old_path)) { error = FSE_INTERNAL; From 3217f286f03da119398586daf94c0723d28b872a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Wed, 24 May 2023 00:17:12 +0900 Subject: [PATCH 563/824] Services: remove deallocator for persistent services (#2692) Co-authored-by: hedger --- applications/services/desktop/desktop.c | 55 +------------------ applications/services/dolphin/dolphin.c | 11 +--- applications/services/dolphin/dolphin_i.h | 2 - .../services/power/power_service/power.c | 26 +-------- 4 files changed, 4 insertions(+), 90 deletions(-) diff --git a/applications/services/desktop/desktop.c b/applications/services/desktop/desktop.c index 77999dfcc08..36589aed4cf 100644 --- a/applications/services/desktop/desktop.c +++ b/applications/services/desktop/desktop.c @@ -313,58 +313,6 @@ Desktop* desktop_alloc() { return desktop; } -void desktop_free(Desktop* desktop) { - furi_assert(desktop); - furi_check(furi_record_destroy(RECORD_DESKTOP)); - - furi_pubsub_unsubscribe( - loader_get_pubsub(desktop->loader), desktop->app_start_stop_subscription); - - if(desktop->input_events_subscription) { - furi_pubsub_unsubscribe(desktop->input_events_pubsub, desktop->input_events_subscription); - desktop->input_events_subscription = NULL; - } - - desktop->loader = NULL; - desktop->input_events_pubsub = NULL; - furi_record_close(RECORD_LOADER); - furi_record_close(RECORD_NOTIFICATION); - furi_record_close(RECORD_INPUT_EVENTS); - - view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewIdMain); - view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewIdLockMenu); - view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewIdLocked); - view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewIdDebug); - view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewIdHwMismatch); - view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewIdPinInput); - view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewIdPinTimeout); - - view_dispatcher_free(desktop->view_dispatcher); - scene_manager_free(desktop->scene_manager); - - animation_manager_free(desktop->animation_manager); - view_stack_free(desktop->main_view_stack); - desktop_main_free(desktop->main_view); - view_stack_free(desktop->locked_view_stack); - desktop_view_locked_free(desktop->locked_view); - desktop_lock_menu_free(desktop->lock_menu); - desktop_view_locked_free(desktop->locked_view); - desktop_debug_free(desktop->debug_view); - popup_free(desktop->hw_mismatch_popup); - desktop_view_pin_timeout_free(desktop->pin_timeout_view); - - furi_record_close(RECORD_GUI); - desktop->gui = NULL; - - furi_thread_free(desktop->scene_thread); - - furi_record_close("menu"); - - furi_timer_free(desktop->auto_lock_timer); - - free(desktop); -} - static bool desktop_check_file_flag(const char* flag_path) { Storage* storage = furi_record_open(RECORD_STORAGE); bool exists = storage_common_stat(storage, flag_path, NULL) == FSE_OK; @@ -427,7 +375,8 @@ int32_t desktop_srv(void* p) { } view_dispatcher_run(desktop->view_dispatcher); - desktop_free(desktop); + + furi_crash("That was unexpected"); return 0; } diff --git a/applications/services/dolphin/dolphin.c b/applications/services/dolphin/dolphin.c index dd8b7105f09..93a9b3095b8 100644 --- a/applications/services/dolphin/dolphin.c +++ b/applications/services/dolphin/dolphin.c @@ -89,15 +89,6 @@ Dolphin* dolphin_alloc() { return dolphin; } -void dolphin_free(Dolphin* dolphin) { - furi_assert(dolphin); - - dolphin_state_free(dolphin->state); - furi_message_queue_free(dolphin->event_queue); - - free(dolphin); -} - void dolphin_event_send_async(Dolphin* dolphin, DolphinEvent* event) { furi_assert(dolphin); furi_assert(event); @@ -204,7 +195,7 @@ int32_t dolphin_srv(void* p) { } } - dolphin_free(dolphin); + furi_crash("That was unexpected"); return 0; } diff --git a/applications/services/dolphin/dolphin_i.h b/applications/services/dolphin/dolphin_i.h index 4bb0df08ee6..ceeff1e1a9a 100644 --- a/applications/services/dolphin/dolphin_i.h +++ b/applications/services/dolphin/dolphin_i.h @@ -37,8 +37,6 @@ struct Dolphin { Dolphin* dolphin_alloc(); -void dolphin_free(Dolphin* dolphin); - void dolphin_event_send_async(Dolphin* dolphin, DolphinEvent* event); void dolphin_event_send_wait(Dolphin* dolphin, DolphinEvent* event); diff --git a/applications/services/power/power_service/power.c b/applications/services/power/power_service/power.c index 56dbd0f87cf..72dc8f3f1e7 100644 --- a/applications/services/power/power_service/power.c +++ b/applications/services/power/power_service/power.c @@ -85,30 +85,6 @@ Power* power_alloc() { return power; } -void power_free(Power* power) { - furi_assert(power); - - // Gui - view_dispatcher_remove_view(power->view_dispatcher, PowerViewOff); - power_off_free(power->power_off); - view_dispatcher_remove_view(power->view_dispatcher, PowerViewUnplugUsb); - power_unplug_usb_free(power->power_unplug_usb); - - view_port_free(power->battery_view_port); - - // State - furi_mutex_free(power->api_mtx); - - // FuriPubSub - furi_pubsub_free(power->event_pubsub); - - // Records - furi_record_close(RECORD_NOTIFICATION); - furi_record_close(RECORD_GUI); - - free(power); -} - static void power_check_charging_state(Power* power) { if(furi_hal_power_is_charging()) { if((power->info.charge == 100) || (furi_hal_power_is_charging_done())) { @@ -252,7 +228,7 @@ int32_t power_srv(void* p) { furi_delay_ms(1000); } - power_free(power); + furi_crash("That was unexpected"); return 0; } From 88f0b635774afe76f15bb72a469e22d4449d7e35 Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Thu, 25 May 2023 06:44:32 -0700 Subject: [PATCH 564/824] Storage, common_rename: check that old path is exists (#2698) * Storage, common_rename: check that old path is exists * Storage, common_rename: return correct status --- .../services/storage/storage_external_api.c | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/applications/services/storage/storage_external_api.c b/applications/services/storage/storage_external_api.c index 549397c87de..5fcaa59216b 100644 --- a/applications/services/storage/storage_external_api.c +++ b/applications/services/storage/storage_external_api.c @@ -424,19 +424,25 @@ FS_Error storage_common_remove(Storage* storage, const char* path) { FS_Error storage_common_rename(Storage* storage, const char* old_path, const char* new_path) { FS_Error error; - if(storage_file_exists(storage, new_path)) { - error = storage_common_remove(storage, new_path); + do { + if(!storage_common_exists(storage, old_path)) { + error = FSE_INVALID_NAME; + break; + } + + if(storage_file_exists(storage, new_path)) { + storage_common_remove(storage, new_path); + } + + error = storage_common_copy(storage, old_path, new_path); if(error != FSE_OK) { - return error; + break; } - } - error = storage_common_copy(storage, old_path, new_path); - if(error == FSE_OK) { if(!storage_simply_remove_recursive(storage, old_path)) { error = FSE_INTERNAL; } - } + } while(false); return error; } From 080324f7e0b28283fd4da5b24b1efb41a0c9af0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Thu, 25 May 2023 23:22:31 +0900 Subject: [PATCH 565/824] [FL-3315] Desktop,Rpc: desktop status subscription (#2696) * Desktop,Rpc: desktop status subscription * Desktop,RPC: properly handle unsubscribe Co-authored-by: Sergey Gavrilov --- applications/services/desktop/desktop.c | 13 +++++ applications/services/desktop/desktop.h | 8 +++ applications/services/desktop/desktop_i.h | 2 + applications/services/rpc/rpc_desktop.c | 64 +++++++++++++++++++++++ assets/protobuf | 2 +- 5 files changed, 88 insertions(+), 1 deletion(-) diff --git a/applications/services/desktop/desktop.c b/applications/services/desktop/desktop.c index 36589aed4cf..e1da649408b 100644 --- a/applications/services/desktop/desktop.c +++ b/applications/services/desktop/desktop.c @@ -147,6 +147,9 @@ void desktop_lock(Desktop* desktop) { desktop->scene_manager, DesktopSceneLocked, SCENE_LOCKED_FIRST_ENTER); scene_manager_next_scene(desktop->scene_manager, DesktopSceneLocked); notification_message(desktop->notification, &sequence_display_backlight_off_delay_1000); + + DesktopStatus status = {.locked = true}; + furi_pubsub_publish(desktop->status_pubsub, &status); } void desktop_unlock(Desktop* desktop) { @@ -165,6 +168,9 @@ void desktop_unlock(Desktop* desktop) { cli_session_open(cli, &cli_vcp); furi_record_close(RECORD_CLI); } + + DesktopStatus status = {.locked = false}; + furi_pubsub_publish(desktop->status_pubsub, &status); } void desktop_set_dummy_mode_state(Desktop* desktop, bool enabled) { @@ -308,6 +314,8 @@ Desktop* desktop_alloc() { desktop->auto_lock_timer = furi_timer_alloc(desktop_auto_lock_timer_callback, FuriTimerTypeOnce, desktop); + desktop->status_pubsub = furi_pubsub_alloc(); + furi_record_create(RECORD_DESKTOP, desktop); return desktop; @@ -331,6 +339,11 @@ void desktop_api_unlock(Desktop* instance) { view_dispatcher_send_custom_event(instance->view_dispatcher, DesktopLockedEventUnlocked); } +FuriPubSub* desktop_api_get_status_pubsub(Desktop* instance) { + furi_assert(instance); + return instance->status_pubsub; +} + int32_t desktop_srv(void* p) { UNUSED(p); diff --git a/applications/services/desktop/desktop.h b/applications/services/desktop/desktop.h index 5b12647b8a5..4eab24fcc50 100644 --- a/applications/services/desktop/desktop.h +++ b/applications/services/desktop/desktop.h @@ -1,5 +1,7 @@ #pragma once +#include + typedef struct Desktop Desktop; #define RECORD_DESKTOP "desktop" @@ -7,3 +9,9 @@ typedef struct Desktop Desktop; bool desktop_api_is_locked(Desktop* instance); void desktop_api_unlock(Desktop* instance); + +typedef struct { + bool locked; +} DesktopStatus; + +FuriPubSub* desktop_api_get_status_pubsub(Desktop* instance); diff --git a/applications/services/desktop/desktop_i.h b/applications/services/desktop/desktop_i.h index ede6bbcc31c..0b3d568016b 100644 --- a/applications/services/desktop/desktop_i.h +++ b/applications/services/desktop/desktop_i.h @@ -71,6 +71,8 @@ struct Desktop { FuriPubSubSubscription* input_events_subscription; FuriTimer* auto_lock_timer; + FuriPubSub* status_pubsub; + bool in_transition; }; diff --git a/applications/services/rpc/rpc_desktop.c b/applications/services/rpc/rpc_desktop.c index dbf9796ec56..0d72b43d551 100644 --- a/applications/services/rpc/rpc_desktop.c +++ b/applications/services/rpc/rpc_desktop.c @@ -8,6 +8,8 @@ typedef struct { RpcSession* session; Desktop* desktop; + FuriPubSub* status_pubsub; + FuriPubSubSubscription* status_subscription; } RpcDesktop; static void rpc_desktop_on_is_locked_request(const PB_Main* request, void* context) { @@ -39,11 +41,63 @@ static void rpc_desktop_on_unlock_request(const PB_Main* request, void* context) rpc_send_and_release_empty(session, request->command_id, PB_CommandStatus_OK); } +static void rpc_desktop_on_desktop_pubsub(const void* message, void* context) { + RpcDesktop* rpc_desktop = context; + RpcSession* session = rpc_desktop->session; + const DesktopStatus* status = message; + + PB_Main rpc_message = { + .command_id = 0, + .command_status = PB_CommandStatus_OK, + .has_next = false, + .which_content = PB_Main_desktop_status_tag, + .content.desktop_status.locked = status->locked, + }; + rpc_send_and_release(session, &rpc_message); +} + +static void rpc_desktop_on_status_subscribe_request(const PB_Main* request, void* context) { + furi_assert(request); + furi_assert(context); + furi_assert(request->which_content == PB_Main_desktop_status_subscribe_request_tag); + + FURI_LOG_D(TAG, "StatusSubscribeRequest"); + RpcDesktop* rpc_desktop = context; + RpcSession* session = rpc_desktop->session; + + if(rpc_desktop->status_subscription) { + rpc_send_and_release_empty(session, request->command_id, PB_CommandStatus_ERROR); + } else { + rpc_desktop->status_subscription = furi_pubsub_subscribe( + rpc_desktop->status_pubsub, rpc_desktop_on_desktop_pubsub, rpc_desktop); + rpc_send_and_release_empty(session, request->command_id, PB_CommandStatus_OK); + } +} + +static void rpc_desktop_on_status_unsubscribe_request(const PB_Main* request, void* context) { + furi_assert(request); + furi_assert(context); + furi_assert(request->which_content == PB_Main_desktop_status_unsubscribe_request_tag); + + FURI_LOG_D(TAG, "StatusUnsubscribeRequest"); + RpcDesktop* rpc_desktop = context; + RpcSession* session = rpc_desktop->session; + + if(rpc_desktop->status_subscription) { + furi_pubsub_unsubscribe(rpc_desktop->status_pubsub, rpc_desktop->status_subscription); + rpc_desktop->status_subscription = NULL; + rpc_send_and_release_empty(session, request->command_id, PB_CommandStatus_OK); + } else { + rpc_send_and_release_empty(session, request->command_id, PB_CommandStatus_ERROR); + } +} + void* rpc_desktop_alloc(RpcSession* session) { furi_assert(session); RpcDesktop* rpc_desktop = malloc(sizeof(RpcDesktop)); rpc_desktop->desktop = furi_record_open(RECORD_DESKTOP); + rpc_desktop->status_pubsub = desktop_api_get_status_pubsub(rpc_desktop->desktop); rpc_desktop->session = session; RpcHandler rpc_handler = { @@ -58,6 +112,12 @@ void* rpc_desktop_alloc(RpcSession* session) { rpc_handler.message_handler = rpc_desktop_on_unlock_request; rpc_add_handler(session, PB_Main_desktop_unlock_request_tag, &rpc_handler); + rpc_handler.message_handler = rpc_desktop_on_status_subscribe_request; + rpc_add_handler(session, PB_Main_desktop_status_subscribe_request_tag, &rpc_handler); + + rpc_handler.message_handler = rpc_desktop_on_status_unsubscribe_request; + rpc_add_handler(session, PB_Main_desktop_status_unsubscribe_request_tag, &rpc_handler); + return rpc_desktop; } @@ -65,6 +125,10 @@ void rpc_desktop_free(void* context) { furi_assert(context); RpcDesktop* rpc_desktop = context; + if(rpc_desktop->status_subscription) { + furi_pubsub_unsubscribe(rpc_desktop->status_pubsub, rpc_desktop->status_subscription); + } + furi_assert(rpc_desktop->desktop); furi_record_close(RECORD_DESKTOP); diff --git a/assets/protobuf b/assets/protobuf index a13c5ddd039..f71c4b7f750 160000 --- a/assets/protobuf +++ b/assets/protobuf @@ -1 +1 @@ -Subproject commit a13c5ddd0397511bd4c6de4afdd1031a5b6f5bca +Subproject commit f71c4b7f750f2539a1fed08925d8da3abdc80ff9 From faa14cfa1c7f48481d0165776ff320993853fa68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Legrelle?= Date: Thu, 25 May 2023 16:30:07 +0200 Subject: [PATCH 566/824] :sparkles: Add fr-FR-mac key layout (#2666) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- .../resources/badusb/assets/layouts/fr-FR-mac.kl | Bin 0 -> 256 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 assets/resources/badusb/assets/layouts/fr-FR-mac.kl diff --git a/assets/resources/badusb/assets/layouts/fr-FR-mac.kl b/assets/resources/badusb/assets/layouts/fr-FR-mac.kl new file mode 100644 index 0000000000000000000000000000000000000000..0906936547cd3c8accd9632bac82cb4193a24c25 GIT binary patch literal 256 zcmaLLM{dGU007a^2*c-7jOjgKAVDOih=6?{?tg}?*<^Na;Jp*y9N*W!`r*KahgW`G zvn8kCYGsczPfNdC`{Bl|xjXkB{IulBi;9;$9}G>b+c4NP+OloOuBmr3`wpx*a_q#Z zGgmHLIyaAHEaHW;H-;qCX%J` Date: Thu, 25 May 2023 17:38:56 +0300 Subject: [PATCH 567/824] [FL-3322] Infrared: respect carrier frequency and duty cycle settings (#2677) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Make infrared_worker respect carrier frequency and duty cycle * Update comments Co-authored-by: あく --- applications/main/infrared/infrared.c | 3 +- firmware/targets/f18/api_symbols.csv | 2 +- firmware/targets/f7/api_symbols.csv | 4 +-- .../targets/f7/furi_hal/furi_hal_infrared.c | 14 +++----- lib/infrared/worker/infrared_worker.c | 32 ++++++++++++------- lib/infrared/worker/infrared_worker.h | 10 ++++-- 6 files changed, 38 insertions(+), 27 deletions(-) diff --git a/applications/main/infrared/infrared.c b/applications/main/infrared/infrared.c index 4f450496d4c..685dd57ec05 100644 --- a/applications/main/infrared/infrared.c +++ b/applications/main/infrared/infrared.c @@ -312,7 +312,8 @@ void infrared_tx_start_signal(Infrared* infrared, InfraredSignal* signal) { if(infrared_signal_is_raw(signal)) { InfraredRawSignal* raw = infrared_signal_get_raw_signal(signal); - infrared_worker_set_raw_signal(infrared->worker, raw->timings, raw->timings_size); + infrared_worker_set_raw_signal( + infrared->worker, raw->timings, raw->timings_size, raw->frequency, raw->duty_cycle); } else { InfraredMessage* message = infrared_signal_get_message(signal); infrared_worker_set_decoded_signal(infrared->worker, message); diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index ee1ae115423..b7abdfc0502 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,26.3,, +Version,+,27.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index e2bedffb7d2..15c19091e9e 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,26.3,, +Version,+,27.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -1715,7 +1715,7 @@ Function,+,infrared_worker_rx_set_received_signal_callback,void,"InfraredWorker* Function,+,infrared_worker_rx_start,void,InfraredWorker* Function,+,infrared_worker_rx_stop,void,InfraredWorker* Function,+,infrared_worker_set_decoded_signal,void,"InfraredWorker*, const InfraredMessage*" -Function,+,infrared_worker_set_raw_signal,void,"InfraredWorker*, const uint32_t*, size_t" +Function,+,infrared_worker_set_raw_signal,void,"InfraredWorker*, const uint32_t*, size_t, uint32_t, float" Function,+,infrared_worker_signal_is_decoded,_Bool,const InfraredWorkerSignal* Function,+,infrared_worker_tx_get_signal_steady_callback,InfraredWorkerGetSignalResponse,"void*, InfraredWorker*" Function,+,infrared_worker_tx_set_get_signal_callback,void,"InfraredWorker*, InfraredWorkerGetSignalCallback, void*" diff --git a/firmware/targets/f7/furi_hal/furi_hal_infrared.c b/firmware/targets/f7/furi_hal/furi_hal_infrared.c index 2598e5fa314..7b4f1708474 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_infrared.c +++ b/firmware/targets/f7/furi_hal/furi_hal_infrared.c @@ -1,7 +1,6 @@ #include #include #include "stm32wbxx_ll_dma.h" -#include "sys/_stdint.h" #include #include @@ -13,11 +12,10 @@ #include #include -#define INFRARED_TX_DEBUG 0 +// #define INFRARED_TX_DEBUG -#if INFRARED_TX_DEBUG == 1 -#define gpio_infrared_tx gpio_infrared_tx_debug -const GpioPin gpio_infrared_tx_debug = {.port = GPIOA, .pin = GpioModeAnalog}; +#if defined INFRARED_TX_DEBUG +#define gpio_infrared_tx gpio_ext_pa7 #endif #define INFRARED_TIM_TX_DMA_BUFFER_SIZE 200 @@ -330,8 +328,6 @@ static void furi_hal_infrared_tx_dma_isr() { } static void furi_hal_infrared_configure_tim_pwm_tx(uint32_t freq, float duty_cycle) { - /* LL_DBGMCU_APB2_GRP1_FreezePeriph(LL_DBGMCU_APB2_GRP1_TIM1_STOP); */ - LL_TIM_DisableCounter(TIM1); LL_TIM_SetRepetitionCounter(TIM1, 0); LL_TIM_SetCounter(TIM1, 0); @@ -340,7 +336,7 @@ static void furi_hal_infrared_configure_tim_pwm_tx(uint32_t freq, float duty_cyc LL_TIM_EnableARRPreload(TIM1); LL_TIM_SetAutoReload( TIM1, __LL_TIM_CALC_ARR(SystemCoreClock, LL_TIM_GetPrescaler(TIM1), freq)); -#if INFRARED_TX_DEBUG == 1 +#if defined INFRARED_TX_DEBUG LL_TIM_OC_SetCompareCH1(TIM1, ((LL_TIM_GetAutoReload(TIM1) + 1) * (1 - duty_cycle))); LL_TIM_OC_EnablePreload(TIM1, LL_TIM_CHANNEL_CH1); /* LL_TIM_OCMODE_PWM2 set by DMA */ @@ -370,7 +366,7 @@ static void furi_hal_infrared_configure_tim_pwm_tx(uint32_t freq, float duty_cyc static void furi_hal_infrared_configure_tim_cmgr2_dma_tx(void) { LL_DMA_InitTypeDef dma_config = {0}; -#if INFRARED_TX_DEBUG == 1 +#if defined INFRARED_TX_DEBUG dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (TIM1->CCMR1); #else dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (TIM1->CCMR2); diff --git a/lib/infrared/worker/infrared_worker.c b/lib/infrared/worker/infrared_worker.c index 5add1413e90..46effd420dc 100644 --- a/lib/infrared/worker/infrared_worker.c +++ b/lib/infrared/worker/infrared_worker.c @@ -40,8 +40,12 @@ struct InfraredWorkerSignal { size_t timings_cnt; union { InfraredMessage message; - /* +1 is for pause we add at the beginning */ - uint32_t timings[MAX_TIMINGS_AMOUNT + 1]; + struct { + /* +1 is for pause we add at the beginning */ + uint32_t timings[MAX_TIMINGS_AMOUNT + 1]; + uint32_t frequency; + float duty_cycle; + } raw; }; }; @@ -146,7 +150,7 @@ static void } if(instance->signal.timings_cnt < MAX_TIMINGS_AMOUNT) { - instance->signal.timings[instance->signal.timings_cnt] = duration; + instance->signal.raw.timings[instance->signal.timings_cnt] = duration; ++instance->signal.timings_cnt; } else { uint32_t flags_set = furi_thread_flags_set( @@ -300,7 +304,7 @@ void infrared_worker_get_raw_signal( furi_assert(timings); furi_assert(timings_cnt); - *timings = signal->timings; + *timings = signal->raw.timings; *timings_cnt = signal->timings_cnt; } @@ -390,8 +394,8 @@ static bool infrared_get_new_signal(InfraredWorker* instance) { infrared_get_protocol_duty_cycle(instance->signal.message.protocol); } else { furi_assert(instance->signal.timings_cnt > 1); - new_tx_frequency = INFRARED_COMMON_CARRIER_FREQUENCY; - new_tx_duty_cycle = INFRARED_COMMON_DUTY_CYCLE; + new_tx_frequency = instance->signal.raw.frequency; + new_tx_duty_cycle = instance->signal.raw.duty_cycle; } instance->tx.tx_raw_cnt = 0; @@ -426,7 +430,7 @@ static bool infrared_worker_tx_fill_buffer(InfraredWorker* instance) { if(instance->signal.decoded) { status = infrared_encode(instance->infrared_encoder, &timing.duration, &timing.level); } else { - timing.duration = instance->signal.timings[instance->tx.tx_raw_cnt]; + timing.duration = instance->signal.raw.timings[instance->tx.tx_raw_cnt]; /* raw always starts from Mark, but we fill it with space delay at start */ timing.level = (instance->tx.tx_raw_cnt % 2); ++instance->tx.tx_raw_cnt; @@ -597,15 +601,21 @@ void infrared_worker_set_decoded_signal(InfraredWorker* instance, const Infrared void infrared_worker_set_raw_signal( InfraredWorker* instance, const uint32_t* timings, - size_t timings_cnt) { + size_t timings_cnt, + uint32_t frequency, + float duty_cycle) { furi_assert(instance); furi_assert(timings); furi_assert(timings_cnt > 0); - size_t max_copy_num = COUNT_OF(instance->signal.timings) - 1; + furi_assert((frequency <= INFRARED_MAX_FREQUENCY) && (frequency >= INFRARED_MIN_FREQUENCY)); + furi_assert((duty_cycle < 1.0f) && (duty_cycle > 0.0f)); + size_t max_copy_num = COUNT_OF(instance->signal.raw.timings) - 1; furi_check(timings_cnt <= max_copy_num); - instance->signal.timings[0] = INFRARED_RAW_TX_TIMING_DELAY_US; - memcpy(&instance->signal.timings[1], timings, timings_cnt * sizeof(uint32_t)); + instance->signal.raw.frequency = frequency; + instance->signal.raw.duty_cycle = duty_cycle; + instance->signal.raw.timings[0] = INFRARED_RAW_TX_TIMING_DELAY_US; + memcpy(&instance->signal.raw.timings[1], timings, timings_cnt * sizeof(uint32_t)); instance->signal.decoded = false; instance->signal.timings_cnt = timings_cnt + 1; } diff --git a/lib/infrared/worker/infrared_worker.h b/lib/infrared/worker/infrared_worker.h index 1a8cd9a76c4..e0e86198309 100644 --- a/lib/infrared/worker/infrared_worker.h +++ b/lib/infrared/worker/infrared_worker.h @@ -130,9 +130,9 @@ void infrared_worker_tx_set_signal_sent_callback( /** Callback to pass to infrared_worker_tx_set_get_signal_callback() if signal * is steady and will not be changed between infrared_worker start and stop. * Before starting transmission, desired steady signal must be set with - * infrared_worker_make_decoded_signal() or infrared_worker_make_raw_signal(). + * infrared_worker_set_decoded_signal() or infrared_worker_set_raw_signal(). * - * This function should not be implicitly called. + * This function should not be called directly. * * @param[in] context - context * @param[out] instance - InfraredWorker instance @@ -172,11 +172,15 @@ void infrared_worker_set_decoded_signal(InfraredWorker* instance, const Infrared * @param[out] instance - InfraredWorker instance * @param[in] timings - array of raw timings * @param[in] timings_cnt - size of array of raw timings + * @param[in] frequency - carrier frequency in Hertz + * @param[in] duty_cycle - carrier duty cycle (0.0 - 1.0) */ void infrared_worker_set_raw_signal( InfraredWorker* instance, const uint32_t* timings, - size_t timings_cnt); + size_t timings_cnt, + uint32_t frequency, + float duty_cycle); #ifdef __cplusplus } From 12dc5b186f49dde06b1d11effacd08ba3f55612b Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Thu, 25 May 2023 17:50:13 +0300 Subject: [PATCH 568/824] USB HID report timeout (#2682) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- firmware/targets/f7/furi_hal/furi_hal_usb_hid.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/firmware/targets/f7/furi_hal/furi_hal_usb_hid.c b/firmware/targets/f7/furi_hal/furi_hal_usb_hid.c index d27613410bd..334aa010264 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_usb_hid.c +++ b/firmware/targets/f7/furi_hal/furi_hal_usb_hid.c @@ -436,7 +436,11 @@ static bool hid_send_report(uint8_t report_id) { if((hid_semaphore == NULL) || (hid_connected == false)) return false; if((boot_protocol == true) && (report_id != ReportIdKeyboard)) return false; - furi_check(furi_semaphore_acquire(hid_semaphore, FuriWaitForever) == FuriStatusOk); + FuriStatus status = furi_semaphore_acquire(hid_semaphore, HID_INTERVAL * 2); + if(status == FuriStatusErrorTimeout) { + return false; + } + furi_check(status == FuriStatusOk); if(hid_connected == false) { return false; } From a472ff7a0fbff7c42682e1aa9e2afdfaa3732e4d Mon Sep 17 00:00:00 2001 From: minchogaydarov <134236905+minchogaydarov@users.noreply.github.com> Date: Thu, 25 May 2023 16:00:13 +0100 Subject: [PATCH 569/824] Add Airwell Prime DCI Series and match file style (#2686) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- assets/resources/infrared/assets/ac.ir | 41 ++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/assets/resources/infrared/assets/ac.ir b/assets/resources/infrared/assets/ac.ir index 96a2a0f38eb..cfa62f6a244 100644 --- a/assets/resources/infrared/assets/ac.ir +++ b/assets/resources/infrared/assets/ac.ir @@ -297,8 +297,8 @@ type: raw frequency: 38000 duty_cycle: 0.330000 data: 2320 634 837 637 838 637 838 640 835 642 832 1378 836 645 826 670 809 667 808 1406 806 672 803 674 802 1412 802 1412 800 676 801 675 802 1412 802 674 802 1413 801 1412 801 1413 802 1412 802 50937 2285 671 801 1411 802 51225 2280 696 775 1412 801 51212 2283 671 775 1412 802 -# Model: Daikin FTXM20M. # +# Model: Daikin FTXM20M. name: Off type: raw frequency: 38000 @@ -334,8 +334,8 @@ type: raw frequency: 38000 duty_cycle: 0.330000 data: 503 365 500 364 501 366 499 365 500 364 502 25049 3535 1660 504 1228 503 390 474 391 473 393 471 1261 469 397 468 397 469 397 469 397 469 1264 468 398 468 1264 468 1264 468 398 468 1265 467 1265 467 1265 467 1265 467 1265 467 399 467 399 467 1266 466 399 467 400 466 400 466 400 466 423 443 423 443 401 465 423 442 424 442 424 442 1290 442 424 442 1290 442 424 442 424 441 424 442 1290 442 1291 441 425 441 424 442 425 441 425 441 1291 441 425 440 425 441 425 441 425 441 425 441 425 440 425 441 425 441 425 441 425 441 426 440 1292 440 1292 440 1292 440 426 440 426 440 1292 440 1293 439 1293 439 35480 3503 1696 467 1264 468 398 468 398 467 398 468 1265 467 398 467 399 467 399 466 399 467 1265 467 399 467 1266 466 1267 465 400 466 1290 442 1290 442 1290 442 1290 442 1290 442 424 442 424 442 1290 442 424 441 424 442 424 442 424 442 424 442 424 441 424 442 425 441 424 442 425 441 425 441 1291 441 425 441 425 441 425 441 425 440 1292 440 425 441 425 440 426 440 426 440 426 440 426 440 426 440 426 440 426 440 426 439 427 439 426 440 426 440 1293 439 427 439 427 439 427 438 427 439 428 438 1294 438 428 437 428 438 1295 437 1319 413 453 413 35480 3503 1696 468 1265 467 398 468 398 468 398 468 1265 467 398 468 399 466 399 467 399 467 1266 466 399 466 1267 465 1290 442 401 465 1290 442 1290 442 1290 442 1290 442 1290 442 424 442 424 441 1291 441 424 442 424 442 424 442 424 442 424 441 424 442 425 441 424 442 424 441 425 441 425 441 425 440 425 441 425 441 425 441 425 441 425 441 425 441 1292 440 426 440 426 440 1292 440 426 440 426 440 1293 439 426 440 426 440 1293 439 1293 439 1293 439 427 439 1294 438 427 438 427 439 427 438 428 438 428 438 428 438 428 438 453 413 429 437 453 413 1319 413 1320 412 1320 412 1320 412 454 412 1320 412 454 412 1321 411 1321 411 1321 411 1321 411 1321 411 455 410 455 411 455 411 455 410 456 410 456 410 456 410 481 384 481 385 482 383 482 383 483 358 507 359 1374 358 1374 358 508 358 508 358 508 358 509 357 509 357 535 331 535 331 535 330 535 330 536 330 1403 329 1403 329 563 302 564 301 564 302 564 301 565 301 591 274 619 246 593 273 620 245 620 245 621 245 673 189 -# Model: Mitsubishi SRK63HE. # +# Model: Mitsubishi SRK63HE. name: Off type: raw frequency: 38000 @@ -371,3 +371,40 @@ type: raw frequency: 38000 duty_cycle: 0.330000 data: 3234 1525 463 333 462 1127 465 332 462 333 436 1153 518 307 488 1073 518 307 488 308 434 1131 459 1155 435 1156 434 362 433 1159 432 363 432 1159 432 1159 432 1159 433 363 432 363 432 363 433 363 432 1159 433 1159 432 363 432 1159 433 1159 432 363 432 363 432 1159 432 363 433 363 432 1159 432 363 432 363 432 1159 432 1159 432 363 432 1160 432 1160 431 1160 432 1160 431 1160 431 1160 431 364 431 1160 431 1160 431 1160 431 364 431 364 431 364 431 364 431 1160 431 364 431 364 431 364 431 1160 432 1160 431 1160 432 364 431 1160 431 1160 431 1161 431 1161 430 364 431 364 431 364 431 1160 432 364 431 364 431 364 432 364 431 1161 431 1161 431 364 431 364 431 1161 430 364 432 364 431 1161 430 365 431 365 431 1161 430 1161 430 365 431 1161 430 1161 430 365 430 +# +# Model: Airwell Prime DCI Series +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3078 3852 2004 888 1054 1824 1045 894 1051 865 2062 861 1078 860 1080 865 1046 894 1015 1883 1945 899 1013 901 1013 901 1012 902 1012 900 1042 927 986 902 1012 927 987 901 1012 902 1012 927 986 903 1011 902 1012 931 1011 931 1011 904 1010 933 1009 928 985 928 986 1885 1944 927 3017 3943 1943 927 985 1915 984 929 985 929 1943 957 984 929 985 928 985 929 985 1886 1943 899 984 930 983 930 984 957 986 929 985 929 985 930 984 930 983 930 984 930 1013 930 984 959 983 931 982 931 983 930 984 930 984 930 984 960 1011 931 984 1918 1939 929 3016 3917 1940 930 982 1916 954 959 954 959 1913 957 955 933 981 959 928 1015 954 1916 1913 959 953 960 957 986 927 1015 928 1015 953 961 927 987 927 986 927 987 927 986 927 1016 927 987 927 1016 926 1044 928 987 926 1015 928 988 926 987 926 988 926 1946 1883 987 3974 +# +name: Dh +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3060 3870 1026 888 1984 886 1026 888 1053 888 1026 887 1055 858 1055 858 1052 860 1024 891 1044 1854 1016 897 1975 1853 1975 925 1015 898 1015 898 1016 897 1016 898 1015 898 1016 898 1015 898 1015 898 1015 899 1015 899 1014 899 1014 899 1016 927 1014 900 1014 899 1014 1856 1974 926 3048 3883 1015 898 1975 897 1014 899 1015 899 1014 900 1014 899 1015 900 1013 900 1014 899 1014 1857 1014 900 1973 1855 1973 899 1012 901 1013 902 1011 926 987 927 987 926 987 927 987 955 987 926 988 926 987 927 1015 927 987 927 987 926 988 926 1016 927 1015 1884 1945 925 3020 3911 986 928 1946 925 986 927 986 928 986 928 986 927 987 927 987 928 986 928 986 1884 987 956 1946 1882 1946 925 986 928 986 927 986 928 985 928 986 928 986 928 986 928 985 928 985 929 959 954 984 930 984 987 931 955 959 956 958 955 959 1968 1891 980 3982 +# +name: Cool_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3054 3879 1977 892 1020 1846 1083 838 1073 840 2031 859 1051 864 1047 864 1077 896 1014 927 986 928 986 928 986 1885 1943 927 985 929 1013 928 986 929 985 928 985 928 985 928 985 929 985 929 985 957 985 928 1015 928 1015 928 985 929 984 929 985 929 985 1886 1943 927 3017 3914 1943 928 984 1886 985 958 984 929 1972 957 984 958 984 929 984 930 984 929 984 930 984 930 983 1887 1942 929 983 930 984 930 984 930 983 959 984 930 984 930 983 930 984 930 984 930 984 930 983 931 983 931 983 959 984 931 983 931 983 1888 1941 930 3014 3943 1914 931 981 1915 955 959 955 959 1913 958 955 959 954 959 955 959 955 959 955 988 955 960 953 1917 1941 959 953 960 953 961 953 961 953 961 927 987 927 987 927 987 926 987 927 1017 926 988 926 988 925 988 925 988 926 988 925 1018 925 1972 1857 1014 3946 +# +name: Cool_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3080 3850 2007 863 1049 1851 1048 888 1054 887 2012 886 1026 887 1025 888 1050 865 1045 1854 2029 871 1041 872 1040 873 1042 899 1043 899 1043 872 1015 898 1016 899 1041 872 1042 872 1015 926 1017 898 1016 927 1016 926 1016 899 1014 898 1015 926 1016 899 1015 898 1015 1856 1973 897 3048 3910 1947 898 1015 1884 1015 925 988 899 2003 953 988 900 1014 954 988 926 987 1857 1972 896 988 899 1014 900 1014 900 1013 927 987 929 1014 929 1013 926 987 927 986 927 987 927 987 927 987 927 986 927 987 927 986 985 958 928 986 927 986 927 1012 1860 1943 927 3018 3914 1943 955 986 1914 986 929 984 928 1944 955 957 930 984 957 985 956 958 1913 1915 956 957 957 956 957 931 1012 957 930 983 958 955 931 982 958 956 960 983 958 956 958 955 958 930 984 930 984 930 984 930 984 930 1013 930 984 930 985 928 1942 1887 983 3977 +# +name: Heat_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3083 3873 2012 1843 2013 1873 1054 888 2011 887 1025 887 1025 887 1052 890 1048 1852 1017 896 1018 895 1018 896 1976 895 1017 898 1016 898 1015 898 1016 897 1016 897 1016 897 1017 897 1016 897 1017 900 1043 899 1014 897 1016 897 1016 898 1016 898 1015 898 1015 1857 1972 896 3048 3911 1947 1853 1975 1854 1015 898 1973 897 1016 927 1015 926 987 926 1016 1857 1014 926 987 899 1015 901 1970 926 987 927 987 926 988 955 988 1013 958 900 1014 926 987 900 1014 900 1014 900 1013 927 986 927 987 927 1016 955 987 927 987 955 987 1884 1946 928 3045 3912 1974 1883 1974 1883 987 927 1974 926 986 927 987 928 987 956 987 1942 986 956 986 928 986 928 1944 926 986 928 985 929 984 929 985 928 986 956 987 928 986 958 984 929 984 930 984 930 983 929 985 958 984 930 984 957 957 956 957 1887 1971 956 4003 +# +name: Heat_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3108 3851 2062 1793 2006 1821 1103 839 2031 859 1085 829 1081 833 1079 836 1045 1911 1973 897 1015 898 1016 899 1041 871 1016 899 1014 898 1015 899 1015 899 1014 899 1041 872 1015 899 1041 872 1041 872 1015 899 1015 899 1041 873 1014 899 1041 873 1014 899 1014 1883 1975 900 3045 3886 1997 1856 1945 1857 1012 927 1945 900 1013 901 1012 901 1013 901 1012 1859 1999 901 1012 930 1012 903 1011 903 1010 903 1011 902 1012 960 1011 928 986 932 1010 903 1011 928 1015 928 985 929 985 928 1014 928 985 929 985 929 984 929 985 928 986 1915 1971 928 3017 3915 1942 1885 1943 1885 985 930 1971 929 984 930 983 930 984 930 984 1887 1942 929 983 960 983 931 982 931 983 932 981 958 985 958 956 958 984 959 954 931 983 932 981 959 955 932 982 959 954 960 982 961 983 933 955 988 955 985 929 1943 1915 958 4003 From 77bb997b0b4bc73a53624af3d1b9391856f70c98 Mon Sep 17 00:00:00 2001 From: Yukai Li Date: Thu, 25 May 2023 10:16:41 -0600 Subject: [PATCH 570/824] desktop: Refactor favorites settings and allow app browser in selection (#2687) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * desktop: Refactor favorites settings and allow app browser in selection * desktop: Gate app browser entry add, just in case * Desktop: simplify favorite application selection * Desktop: refactor favorite application opening routine and cleanup code * Desktop: handle exit from external application selection Co-authored-by: hedger Co-authored-by: あく --- .../services/desktop/desktop_settings.h | 2 - .../desktop/scenes/desktop_scene_main.c | 49 ++++------ .../scenes/desktop_settings_scene_favorite.c | 94 +++++++++---------- 3 files changed, 61 insertions(+), 84 deletions(-) diff --git a/applications/services/desktop/desktop_settings.h b/applications/services/desktop/desktop_settings.h index 5d1b6126feb..7ab39094d16 100644 --- a/applications/services/desktop/desktop_settings.h +++ b/applications/services/desktop/desktop_settings.h @@ -36,8 +36,6 @@ #define MIN_PIN_SIZE 4 #define MAX_APP_LENGTH 128 -#define FAP_LOADER_APP_NAME "Applications" - typedef struct { InputKey data[MAX_PIN_SIZE]; uint8_t length; diff --git a/applications/services/desktop/scenes/desktop_scene_main.c b/applications/services/desktop/scenes/desktop_scene_main.c index 053ac56f1e7..d19b5560f94 100644 --- a/applications/services/desktop/scenes/desktop_scene_main.c +++ b/applications/services/desktop/scenes/desktop_scene_main.c @@ -16,6 +16,8 @@ #define SNAKE_GAME_APP EXT_PATH("/apps/Games/snake_game.fap") #define CLOCK_APP EXT_PATH("/apps/Tools/clock.fap") +#define FAP_LOADER_APP_NAME "Applications" + static void desktop_scene_main_new_idle_animation_callback(void* context) { furi_assert(context); Desktop* desktop = context; @@ -77,6 +79,21 @@ static void desktop_scene_main_open_app_or_profile(Desktop* desktop, const char* } while(false); } +static void desktop_scene_main_start_favorite(Desktop* desktop, FavoriteApp* application) { + LoaderStatus status = LoaderStatusErrorInternal; + if(application->is_external) { + status = loader_start(desktop->loader, FAP_LOADER_APP_NAME, application->name_or_path); + } else if(strlen(application->name_or_path) > 0) { + status = loader_start(desktop->loader, application->name_or_path, NULL); + } else { + status = loader_start(desktop->loader, FAP_LOADER_APP_NAME, NULL); + } + + if(status != LoaderStatusOk) { + FURI_LOG_E(TAG, "loader_start failed: %d", status); + } +} + void desktop_scene_main_callback(DesktopEvent event, void* context) { Desktop* desktop = (Desktop*)context; if(desktop->in_transition) return; @@ -141,40 +158,12 @@ bool desktop_scene_main_on_event(void* context, SceneManagerEvent event) { case DesktopMainEventOpenFavoritePrimary: DESKTOP_SETTINGS_LOAD(&desktop->settings); - if(desktop->settings.favorite_primary.is_external) { - LoaderStatus status = loader_start( - desktop->loader, - FAP_LOADER_APP_NAME, - desktop->settings.favorite_primary.name_or_path); - if(status != LoaderStatusOk) { - FURI_LOG_E(TAG, "loader_start failed: %d", status); - } - } else { - LoaderStatus status = loader_start( - desktop->loader, desktop->settings.favorite_primary.name_or_path, NULL); - if(status != LoaderStatusOk) { - FURI_LOG_E(TAG, "loader_start failed: %d", status); - } - } + desktop_scene_main_start_favorite(desktop, &desktop->settings.favorite_primary); consumed = true; break; case DesktopMainEventOpenFavoriteSecondary: DESKTOP_SETTINGS_LOAD(&desktop->settings); - if(desktop->settings.favorite_secondary.is_external) { - LoaderStatus status = loader_start( - desktop->loader, - FAP_LOADER_APP_NAME, - desktop->settings.favorite_secondary.name_or_path); - if(status != LoaderStatusOk) { - FURI_LOG_E(TAG, "loader_start failed: %d", status); - } - } else { - LoaderStatus status = loader_start( - desktop->loader, desktop->settings.favorite_secondary.name_or_path, NULL); - if(status != LoaderStatusOk) { - FURI_LOG_E(TAG, "loader_start failed: %d", status); - } - } + desktop_scene_main_start_favorite(desktop, &desktop->settings.favorite_secondary); consumed = true; break; case DesktopAnimationEventCheckAnimation: diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c index 94c5ee9f049..4b5c4792123 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c @@ -5,6 +5,9 @@ #include #include +#define EXTERNAL_APPLICATION_NAME ("[External Application]") +#define EXTERNAL_APPLICATION_INDEX (FLIPPER_APPS_COUNT + 1) + static bool favorite_fap_selector_item_callback( FuriString* file_path, void* context, @@ -44,6 +47,8 @@ void desktop_settings_scene_favorite_on_enter(void* context) { uint32_t primary_favorite = scene_manager_get_scene_state(app->scene_manager, DesktopSettingsAppSceneFavorite); uint32_t pre_select_item = 0; + FavoriteApp* curr_favorite_app = primary_favorite ? &app->settings.favorite_primary : + &app->settings.favorite_secondary; for(size_t i = 0; i < FLIPPER_APPS_COUNT; i++) { submenu_add_item( @@ -53,21 +58,25 @@ void desktop_settings_scene_favorite_on_enter(void* context) { desktop_settings_scene_favorite_submenu_callback, app); - if(primary_favorite) { // Select favorite item in submenu - if((app->settings.favorite_primary.is_external && - !strcmp(FLIPPER_APPS[i].name, FAP_LOADER_APP_NAME)) || - (!strcmp(FLIPPER_APPS[i].name, app->settings.favorite_primary.name_or_path))) { - pre_select_item = i; - } - } else { - if((app->settings.favorite_secondary.is_external && - !strcmp(FLIPPER_APPS[i].name, FAP_LOADER_APP_NAME)) || - (!strcmp(FLIPPER_APPS[i].name, app->settings.favorite_secondary.name_or_path))) { - pre_select_item = i; - } + // Select favorite item in submenu + if(!curr_favorite_app->is_external && + !strcmp(FLIPPER_APPS[i].name, curr_favorite_app->name_or_path)) { + pre_select_item = i; } } +#ifdef APP_FAP_LOADER + submenu_add_item( + submenu, + EXTERNAL_APPLICATION_NAME, + EXTERNAL_APPLICATION_INDEX, + desktop_settings_scene_favorite_submenu_callback, + app); + if(curr_favorite_app->is_external) { + pre_select_item = EXTERNAL_APPLICATION_INDEX; + } +#endif + submenu_set_header( submenu, primary_favorite ? "Primary favorite app:" : "Secondary favorite app:"); submenu_set_selected_item(submenu, pre_select_item); // If set during loop, visual glitch. @@ -82,23 +91,11 @@ bool desktop_settings_scene_favorite_on_event(void* context, SceneManagerEvent e uint32_t primary_favorite = scene_manager_get_scene_state(app->scene_manager, DesktopSettingsAppSceneFavorite); + FavoriteApp* curr_favorite_app = primary_favorite ? &app->settings.favorite_primary : + &app->settings.favorite_secondary; if(event.type == SceneManagerEventTypeCustom) { - if(strcmp(FLIPPER_APPS[event.event].name, FAP_LOADER_APP_NAME) != 0) { - if(primary_favorite) { - app->settings.favorite_primary.is_external = false; - strncpy( - app->settings.favorite_primary.name_or_path, - FLIPPER_APPS[event.event].name, - MAX_APP_LENGTH); - } else { - app->settings.favorite_secondary.is_external = false; - strncpy( - app->settings.favorite_secondary.name_or_path, - FLIPPER_APPS[event.event].name, - MAX_APP_LENGTH); - } - } else { + if(event.event == EXTERNAL_APPLICATION_INDEX) { const DialogsFileBrowserOptions browser_options = { .extension = ".fap", .icon = &I_unknown_10px, @@ -109,36 +106,29 @@ bool desktop_settings_scene_favorite_on_event(void* context, SceneManagerEvent e .base_path = EXT_PATH("apps"), }; - if(primary_favorite) { // Select favorite fap in file browser - if(favorite_fap_selector_file_exists( - app->settings.favorite_primary.name_or_path)) { - furi_string_set_str(temp_path, app->settings.favorite_primary.name_or_path); - } - } else { - if(favorite_fap_selector_file_exists( - app->settings.favorite_secondary.name_or_path)) { - furi_string_set_str(temp_path, app->settings.favorite_secondary.name_or_path); - } + // Select favorite fap in file browser + if(favorite_fap_selector_file_exists(curr_favorite_app->name_or_path)) { + furi_string_set_str(temp_path, curr_favorite_app->name_or_path); } - submenu_reset(app->submenu); if(dialog_file_browser_show(app->dialogs, temp_path, temp_path, &browser_options)) { - if(primary_favorite) { - app->settings.favorite_primary.is_external = true; - strncpy( - app->settings.favorite_primary.name_or_path, - furi_string_get_cstr(temp_path), - MAX_APP_LENGTH); - } else { - app->settings.favorite_secondary.is_external = true; - strncpy( - app->settings.favorite_secondary.name_or_path, - furi_string_get_cstr(temp_path), - MAX_APP_LENGTH); - } + submenu_reset(app->submenu); // Prevent menu from being shown when we exiting scene + curr_favorite_app->is_external = true; + strncpy( + curr_favorite_app->name_or_path, + furi_string_get_cstr(temp_path), + MAX_APP_LENGTH); + consumed = true; } + } else { + curr_favorite_app->is_external = false; + strncpy( + curr_favorite_app->name_or_path, FLIPPER_APPS[event.event].name, MAX_APP_LENGTH); + consumed = true; } - scene_manager_previous_scene(app->scene_manager); + if(consumed) { + scene_manager_previous_scene(app->scene_manager); + }; consumed = true; } From 490447bbd482126063bce3e5378198365b580963 Mon Sep 17 00:00:00 2001 From: Avery <30564701+nullableVoidPtr@users.noreply.github.com> Date: Fri, 26 May 2023 03:01:02 +1000 Subject: [PATCH 571/824] NFC: Add support for Gen4 "ultimate card" in Magic app (#2238) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * NFC: gen4 gtu detect in magic app * NFC: more support for GTU card * NFC: Fix Gen1 in Magic * Allow double UIDs for MFClassic on GTU cards * NFC: Small magic app tweaks * nfc magic: notify card event on wiping * nfc magic: fix power consumption * nfc magic: disable i2c writing and fix wipe loop * NfcMagic: correct formatting in printf * NfcMagic: correct formatting in printf, proper version * nfc_magic: rework card found notification and gen4 wiping Co-authored-by: あく --- .../nfc_magic/lib/magic/classic_gen1.c | 175 ++++++++ .../nfc_magic/lib/magic/classic_gen1.h | 13 + .../external/nfc_magic/lib/magic/common.c | 33 ++ .../external/nfc_magic/lib/magic/common.h | 19 + .../external/nfc_magic/lib/magic/gen4.c | 199 ++++++++++ .../external/nfc_magic/lib/magic/gen4.h | 48 +++ .../external/nfc_magic/lib/magic/magic.c | 52 +-- .../external/nfc_magic/lib/magic/magic.h | 15 - .../external/nfc_magic/lib/magic/types.c | 23 ++ .../external/nfc_magic/lib/magic/types.h | 5 + applications/external/nfc_magic/nfc_magic.c | 22 +- applications/external/nfc_magic/nfc_magic.h | 2 + applications/external/nfc_magic/nfc_magic_i.h | 20 +- .../external/nfc_magic/nfc_magic_worker.c | 372 +++++++++++++++--- .../external/nfc_magic/nfc_magic_worker.h | 4 + .../external/nfc_magic/nfc_magic_worker_i.h | 5 + .../scenes/nfc_magic_scene_actions.c | 50 +++ .../nfc_magic/scenes/nfc_magic_scene_check.c | 4 +- .../nfc_magic/scenes/nfc_magic_scene_config.h | 6 + .../scenes/nfc_magic_scene_file_select.c | 60 ++- .../scenes/nfc_magic_scene_gen4_actions.c | 70 ++++ .../scenes/nfc_magic_scene_key_input.c | 45 +++ .../scenes/nfc_magic_scene_magic_info.c | 14 + .../scenes/nfc_magic_scene_new_key_input.c | 45 +++ .../nfc_magic/scenes/nfc_magic_scene_rekey.c | 95 +++++ .../scenes/nfc_magic_scene_rekey_fail.c | 50 +++ .../nfc_magic/scenes/nfc_magic_scene_start.c | 25 +- .../nfc_magic/scenes/nfc_magic_scene_wipe.c | 11 +- .../nfc_magic/scenes/nfc_magic_scene_write.c | 11 +- .../scenes/nfc_magic_scene_wrong_card.c | 2 +- 30 files changed, 1345 insertions(+), 150 deletions(-) create mode 100644 applications/external/nfc_magic/lib/magic/classic_gen1.c create mode 100644 applications/external/nfc_magic/lib/magic/classic_gen1.h create mode 100644 applications/external/nfc_magic/lib/magic/common.c create mode 100644 applications/external/nfc_magic/lib/magic/common.h create mode 100644 applications/external/nfc_magic/lib/magic/gen4.c create mode 100644 applications/external/nfc_magic/lib/magic/gen4.h delete mode 100644 applications/external/nfc_magic/lib/magic/magic.h create mode 100644 applications/external/nfc_magic/lib/magic/types.c create mode 100644 applications/external/nfc_magic/lib/magic/types.h create mode 100644 applications/external/nfc_magic/scenes/nfc_magic_scene_actions.c create mode 100644 applications/external/nfc_magic/scenes/nfc_magic_scene_gen4_actions.c create mode 100644 applications/external/nfc_magic/scenes/nfc_magic_scene_key_input.c create mode 100644 applications/external/nfc_magic/scenes/nfc_magic_scene_new_key_input.c create mode 100644 applications/external/nfc_magic/scenes/nfc_magic_scene_rekey.c create mode 100644 applications/external/nfc_magic/scenes/nfc_magic_scene_rekey_fail.c diff --git a/applications/external/nfc_magic/lib/magic/classic_gen1.c b/applications/external/nfc_magic/lib/magic/classic_gen1.c new file mode 100644 index 00000000000..ebd2b08057a --- /dev/null +++ b/applications/external/nfc_magic/lib/magic/classic_gen1.c @@ -0,0 +1,175 @@ +#include "classic_gen1.h" + +#include + +#define TAG "Magic" + +#define MAGIC_CMD_WUPA (0x40) +#define MAGIC_CMD_WIPE (0x41) +#define MAGIC_CMD_ACCESS (0x43) + +#define MAGIC_MIFARE_READ_CMD (0x30) +#define MAGIC_MIFARE_WRITE_CMD (0xA0) + +#define MAGIC_ACK (0x0A) + +#define MAGIC_BUFFER_SIZE (32) + +bool magic_gen1_wupa() { + bool magic_activated = false; + uint8_t tx_data[MAGIC_BUFFER_SIZE] = {}; + uint8_t rx_data[MAGIC_BUFFER_SIZE] = {}; + uint16_t rx_len = 0; + FuriHalNfcReturn ret = 0; + + do { + // Start communication + tx_data[0] = MAGIC_CMD_WUPA; + ret = furi_hal_nfc_ll_txrx_bits( + tx_data, + 7, + rx_data, + sizeof(rx_data), + &rx_len, + FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_TX_MANUAL | FURI_HAL_NFC_LL_TXRX_FLAGS_AGC_ON | + FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_RX_KEEP, + furi_hal_nfc_ll_ms2fc(20)); + if(ret != FuriHalNfcReturnIncompleteByte) break; + if(rx_len != 4) break; + if(rx_data[0] != MAGIC_ACK) break; + magic_activated = true; + } while(false); + + return magic_activated; +} + +bool magic_gen1_data_access_cmd() { + bool write_cmd_success = false; + uint8_t tx_data[MAGIC_BUFFER_SIZE] = {}; + uint8_t rx_data[MAGIC_BUFFER_SIZE] = {}; + uint16_t rx_len = 0; + FuriHalNfcReturn ret = 0; + + do { + tx_data[0] = MAGIC_CMD_ACCESS; + ret = furi_hal_nfc_ll_txrx_bits( + tx_data, + 8, + rx_data, + sizeof(rx_data), + &rx_len, + FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_TX_MANUAL | FURI_HAL_NFC_LL_TXRX_FLAGS_AGC_ON | + FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_RX_KEEP, + furi_hal_nfc_ll_ms2fc(20)); + if(ret != FuriHalNfcReturnIncompleteByte) break; + if(rx_len != 4) break; + if(rx_data[0] != MAGIC_ACK) break; + + write_cmd_success = true; + } while(false); + + return write_cmd_success; +} + +bool magic_gen1_read_block(uint8_t block_num, MfClassicBlock* data) { + furi_assert(data); + + bool read_success = false; + + uint8_t tx_data[MAGIC_BUFFER_SIZE] = {}; + uint8_t rx_data[MAGIC_BUFFER_SIZE] = {}; + uint16_t rx_len = 0; + FuriHalNfcReturn ret = 0; + + do { + tx_data[0] = MAGIC_MIFARE_READ_CMD; + tx_data[1] = block_num; + ret = furi_hal_nfc_ll_txrx_bits( + tx_data, + 2 * 8, + rx_data, + sizeof(rx_data), + &rx_len, + FURI_HAL_NFC_LL_TXRX_FLAGS_AGC_ON, + furi_hal_nfc_ll_ms2fc(20)); + + if(ret != FuriHalNfcReturnOk) break; + if(rx_len != 16 * 8) break; + memcpy(data->value, rx_data, sizeof(data->value)); + read_success = true; + } while(false); + + return read_success; +} + +bool magic_gen1_write_blk(uint8_t block_num, MfClassicBlock* data) { + furi_assert(data); + + bool write_success = false; + uint8_t tx_data[MAGIC_BUFFER_SIZE] = {}; + uint8_t rx_data[MAGIC_BUFFER_SIZE] = {}; + uint16_t rx_len = 0; + FuriHalNfcReturn ret = 0; + + do { + tx_data[0] = MAGIC_MIFARE_WRITE_CMD; + tx_data[1] = block_num; + ret = furi_hal_nfc_ll_txrx_bits( + tx_data, + 2 * 8, + rx_data, + sizeof(rx_data), + &rx_len, + FURI_HAL_NFC_LL_TXRX_FLAGS_AGC_ON | FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_RX_KEEP, + furi_hal_nfc_ll_ms2fc(20)); + if(ret != FuriHalNfcReturnIncompleteByte) break; + if(rx_len != 4) break; + if(rx_data[0] != MAGIC_ACK) break; + + memcpy(tx_data, data->value, sizeof(data->value)); + ret = furi_hal_nfc_ll_txrx_bits( + tx_data, + 16 * 8, + rx_data, + sizeof(rx_data), + &rx_len, + FURI_HAL_NFC_LL_TXRX_FLAGS_AGC_ON | FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_RX_KEEP, + furi_hal_nfc_ll_ms2fc(20)); + if(ret != FuriHalNfcReturnIncompleteByte) break; + if(rx_len != 4) break; + if(rx_data[0] != MAGIC_ACK) break; + + write_success = true; + } while(false); + + return write_success; +} + +bool magic_gen1_wipe() { + bool wipe_success = false; + uint8_t tx_data[MAGIC_BUFFER_SIZE] = {}; + uint8_t rx_data[MAGIC_BUFFER_SIZE] = {}; + uint16_t rx_len = 0; + FuriHalNfcReturn ret = 0; + + do { + tx_data[0] = MAGIC_CMD_WIPE; + ret = furi_hal_nfc_ll_txrx_bits( + tx_data, + 8, + rx_data, + sizeof(rx_data), + &rx_len, + FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_TX_MANUAL | FURI_HAL_NFC_LL_TXRX_FLAGS_AGC_ON | + FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_RX_KEEP, + furi_hal_nfc_ll_ms2fc(2000)); + + if(ret != FuriHalNfcReturnIncompleteByte) break; + if(rx_len != 4) break; + if(rx_data[0] != MAGIC_ACK) break; + + wipe_success = true; + } while(false); + + return wipe_success; +} \ No newline at end of file diff --git a/applications/external/nfc_magic/lib/magic/classic_gen1.h b/applications/external/nfc_magic/lib/magic/classic_gen1.h new file mode 100644 index 00000000000..6d4ff6dcd9a --- /dev/null +++ b/applications/external/nfc_magic/lib/magic/classic_gen1.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +bool magic_gen1_wupa(); + +bool magic_gen1_read_block(uint8_t block_num, MfClassicBlock* data); + +bool magic_gen1_data_access_cmd(); + +bool magic_gen1_write_blk(uint8_t block_num, MfClassicBlock* data); + +bool magic_gen1_wipe(); \ No newline at end of file diff --git a/applications/external/nfc_magic/lib/magic/common.c b/applications/external/nfc_magic/lib/magic/common.c new file mode 100644 index 00000000000..0ea3cb218dd --- /dev/null +++ b/applications/external/nfc_magic/lib/magic/common.c @@ -0,0 +1,33 @@ +#include "common.h" + +#include + +#define REQA (0x26) +#define CL1_PREFIX (0x93) +#define SELECT (0x70) + +#define MAGIC_BUFFER_SIZE (32) + +bool magic_activate() { + FuriHalNfcReturn ret = 0; + + // Setup nfc poller + furi_hal_nfc_exit_sleep(); + furi_hal_nfc_ll_txrx_on(); + furi_hal_nfc_ll_poll(); + ret = furi_hal_nfc_ll_set_mode( + FuriHalNfcModePollNfca, FuriHalNfcBitrate106, FuriHalNfcBitrate106); + if(ret != FuriHalNfcReturnOk) return false; + + furi_hal_nfc_ll_set_fdt_listen(FURI_HAL_NFC_LL_FDT_LISTEN_NFCA_POLLER); + furi_hal_nfc_ll_set_fdt_poll(FURI_HAL_NFC_LL_FDT_POLL_NFCA_POLLER); + furi_hal_nfc_ll_set_error_handling(FuriHalNfcErrorHandlingNfc); + furi_hal_nfc_ll_set_guard_time(FURI_HAL_NFC_LL_GT_NFCA); + + return true; +} + +void magic_deactivate() { + furi_hal_nfc_ll_txrx_off(); + furi_hal_nfc_sleep(); +} \ No newline at end of file diff --git a/applications/external/nfc_magic/lib/magic/common.h b/applications/external/nfc_magic/lib/magic/common.h new file mode 100644 index 00000000000..bef166c8f22 --- /dev/null +++ b/applications/external/nfc_magic/lib/magic/common.h @@ -0,0 +1,19 @@ +#pragma once + +#include +#include + +typedef enum { + MagicTypeClassicGen1, + MagicTypeClassicDirectWrite, + MagicTypeClassicAPDU, + MagicTypeUltralightGen1, + MagicTypeUltralightDirectWrite, + MagicTypeUltralightC_Gen1, + MagicTypeUltralightC_DirectWrite, + MagicTypeGen4, +} MagicType; + +bool magic_activate(); + +void magic_deactivate(); \ No newline at end of file diff --git a/applications/external/nfc_magic/lib/magic/gen4.c b/applications/external/nfc_magic/lib/magic/gen4.c new file mode 100644 index 00000000000..31be649a04e --- /dev/null +++ b/applications/external/nfc_magic/lib/magic/gen4.c @@ -0,0 +1,199 @@ +#include "gen4.h" + +#include +#include + +#define TAG "Magic" + +#define MAGIC_CMD_PREFIX (0xCF) + +#define MAGIC_CMD_GET_CFG (0xC6) +#define MAGIC_CMD_WRITE (0xCD) +#define MAGIC_CMD_READ (0xCE) +#define MAGIC_CMD_SET_CFG (0xF0) +#define MAGIC_CMD_FUSE_CFG (0xF1) +#define MAGIC_CMD_SET_PWD (0xFE) + +#define MAGIC_BUFFER_SIZE (40) + +const uint8_t MAGIC_DEFAULT_CONFIG[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x09, 0x78, 0x00, 0x91, 0x02, 0xDA, 0xBC, 0x19, 0x10, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x04, 0x00, 0x08, 0x00 +}; + +const uint8_t MAGIC_DEFAULT_BLOCK0[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x04, 0x08, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +const uint8_t MAGIC_EMPTY_BLOCK[16] = { 0 }; + +const uint8_t MAGIC_DEFAULT_SECTOR_TRAILER[] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x07, 0x80, 0x69, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF +}; + +static bool magic_gen4_is_block_num_trailer(uint8_t n) { + n++; + if (n < 32 * 4) { + return (n % 4 == 0); + } + + return (n % 16 == 0); +} + +bool magic_gen4_get_cfg(uint32_t pwd, uint8_t* config) { + bool is_valid_config_len = false; + uint8_t tx_data[MAGIC_BUFFER_SIZE] = {}; + uint8_t rx_data[MAGIC_BUFFER_SIZE] = {}; + uint16_t rx_len = 0; + FuriHalNfcReturn ret = 0; + + do { + // Start communication + tx_data[0] = MAGIC_CMD_PREFIX; + tx_data[1] = (uint8_t)(pwd >> 24); + tx_data[2] = (uint8_t)(pwd >> 16); + tx_data[3] = (uint8_t)(pwd >> 8); + tx_data[4] = (uint8_t)pwd; + tx_data[5] = MAGIC_CMD_GET_CFG; + ret = furi_hal_nfc_ll_txrx( + tx_data, + 6, + rx_data, + sizeof(rx_data), + &rx_len, + FURI_HAL_NFC_TXRX_DEFAULT, + furi_hal_nfc_ll_ms2fc(20)); + if(ret != FuriHalNfcReturnOk) break; + if(rx_len != 30 && rx_len != 32) break; + memcpy(config, rx_data, rx_len); + is_valid_config_len = true; + } while(false); + + return is_valid_config_len; +} + +bool magic_gen4_set_cfg(uint32_t pwd, const uint8_t* config, uint8_t config_length, bool fuse) { + bool write_success = false; + uint8_t tx_data[MAGIC_BUFFER_SIZE] = {}; + uint8_t rx_data[MAGIC_BUFFER_SIZE] = {}; + uint16_t rx_len = 0; + FuriHalNfcReturn ret = 0; + + do { + // Start communication + tx_data[0] = MAGIC_CMD_PREFIX; + tx_data[1] = (uint8_t)(pwd >> 24); + tx_data[2] = (uint8_t)(pwd >> 16); + tx_data[3] = (uint8_t)(pwd >> 8); + tx_data[4] = (uint8_t)pwd; + tx_data[5] = fuse ? MAGIC_CMD_FUSE_CFG : MAGIC_CMD_SET_CFG; + memcpy(tx_data + 6, config, config_length); + ret = furi_hal_nfc_ll_txrx( + tx_data, + 6 + config_length, + rx_data, + sizeof(rx_data), + &rx_len, + FURI_HAL_NFC_TXRX_DEFAULT, + furi_hal_nfc_ll_ms2fc(20)); + if(ret != FuriHalNfcReturnOk) break; + if(rx_len != 2) break; + write_success = true; + } while(false); + + return write_success; +} + +bool magic_gen4_set_pwd(uint32_t old_pwd, uint32_t new_pwd) { + bool change_success = false; + uint8_t tx_data[MAGIC_BUFFER_SIZE] = {}; + uint8_t rx_data[MAGIC_BUFFER_SIZE] = {}; + uint16_t rx_len = 0; + FuriHalNfcReturn ret = 0; + + do { + // Start communication + tx_data[0] = MAGIC_CMD_PREFIX; + tx_data[1] = (uint8_t)(old_pwd >> 24); + tx_data[2] = (uint8_t)(old_pwd >> 16); + tx_data[3] = (uint8_t)(old_pwd >> 8); + tx_data[4] = (uint8_t)old_pwd; + tx_data[5] = MAGIC_CMD_SET_PWD; + tx_data[6] = (uint8_t)(new_pwd >> 24); + tx_data[7] = (uint8_t)(new_pwd >> 16); + tx_data[8] = (uint8_t)(new_pwd >> 8); + tx_data[9] = (uint8_t)new_pwd; + ret = furi_hal_nfc_ll_txrx( + tx_data, + 10, + rx_data, + sizeof(rx_data), + &rx_len, + FURI_HAL_NFC_TXRX_DEFAULT, + furi_hal_nfc_ll_ms2fc(20)); + FURI_LOG_I(TAG, "ret %d, len %d", ret, rx_len); + if(ret != FuriHalNfcReturnOk) break; + if(rx_len != 2) break; + change_success = true; + } while(false); + + return change_success; +} + +bool magic_gen4_write_blk(uint32_t pwd, uint8_t block_num, const uint8_t* data) { + bool write_success = false; + uint8_t tx_data[MAGIC_BUFFER_SIZE] = {}; + uint8_t rx_data[MAGIC_BUFFER_SIZE] = {}; + uint16_t rx_len = 0; + FuriHalNfcReturn ret = 0; + + do { + // Start communication + tx_data[0] = MAGIC_CMD_PREFIX; + tx_data[1] = (uint8_t)(pwd >> 24); + tx_data[2] = (uint8_t)(pwd >> 16); + tx_data[3] = (uint8_t)(pwd >> 8); + tx_data[4] = (uint8_t)pwd; + tx_data[5] = MAGIC_CMD_WRITE; + tx_data[6] = block_num; + memcpy(tx_data + 7, data, 16); + ret = furi_hal_nfc_ll_txrx( + tx_data, + 23, + rx_data, + sizeof(rx_data), + &rx_len, + FURI_HAL_NFC_TXRX_DEFAULT, + furi_hal_nfc_ll_ms2fc(200)); + if(ret != FuriHalNfcReturnOk) break; + if(rx_len != 2) break; + write_success = true; + } while(false); + + return write_success; +} + +bool magic_gen4_wipe(uint32_t pwd) { + if(!magic_gen4_set_cfg(pwd, MAGIC_DEFAULT_CONFIG, sizeof(MAGIC_DEFAULT_CONFIG), false)) { + FURI_LOG_E(TAG, "Set config failed"); + return false; + } + if(!magic_gen4_write_blk(pwd, 0, MAGIC_DEFAULT_BLOCK0)) { + FURI_LOG_E(TAG, "Block 0 write failed"); + return false; + } + for(size_t i = 1; i < 64; i++) { + const uint8_t* block = magic_gen4_is_block_num_trailer(i) ? MAGIC_DEFAULT_SECTOR_TRAILER : MAGIC_EMPTY_BLOCK; + if(!magic_gen4_write_blk(pwd, i, block)) { + FURI_LOG_E(TAG, "Block %d write failed", i); + return false; + } + } + for(size_t i = 65; i < 256; i++) { + if(!magic_gen4_write_blk(pwd, i, MAGIC_EMPTY_BLOCK)) { + FURI_LOG_E(TAG, "Block %d write failed", i); + return false; + } + } + + return true; +} \ No newline at end of file diff --git a/applications/external/nfc_magic/lib/magic/gen4.h b/applications/external/nfc_magic/lib/magic/gen4.h new file mode 100644 index 00000000000..c515af820b0 --- /dev/null +++ b/applications/external/nfc_magic/lib/magic/gen4.h @@ -0,0 +1,48 @@ +#pragma once + +#include + +#define MAGIC_GEN4_DEFAULT_PWD 0x00000000 +#define MAGIC_GEN4_CONFIG_LEN 32 + +#define NFCID1_SINGLE_SIZE 4 +#define NFCID1_DOUBLE_SIZE 7 +#define NFCID1_TRIPLE_SIZE 10 + +typedef enum { + MagicGen4UIDLengthSingle = 0x00, + MagicGen4UIDLengthDouble = 0x01, + MagicGen4UIDLengthTriple = 0x02 +} MagicGen4UIDLength; + +typedef enum { + MagicGen4UltralightModeUL_EV1 = 0x00, + MagicGen4UltralightModeNTAG = 0x01, + MagicGen4UltralightModeUL_C = 0x02, + MagicGen4UltralightModeUL = 0x03 +} MagicGen4UltralightMode; + +typedef enum { + // for writing original (shadow) data + MagicGen4ShadowModePreWrite = 0x00, + // written data can be read once before restored to original + MagicGen4ShadowModeRestore = 0x01, + // written data is discarded + MagicGen4ShadowModeIgnore = 0x02, + // apparently for UL? + MagicGen4ShadowModeHighSpeedIgnore = 0x03 +} MagicGen4ShadowMode; + +bool magic_gen4_get_cfg(uint32_t pwd, uint8_t* config); + +bool magic_gen4_set_cfg(uint32_t pwd, const uint8_t* config, uint8_t config_length, bool fuse); + +bool magic_gen4_set_pwd(uint32_t old_pwd, uint32_t new_pwd); + +bool magic_gen4_read_blk(uint32_t pwd, uint8_t block_num, uint8_t* data); + +bool magic_gen4_write_blk(uint32_t pwd, uint8_t block_num, const uint8_t* data); + +bool magic_gen4_wipe(uint32_t pwd); + +void magic_gen4_deactivate(); diff --git a/applications/external/nfc_magic/lib/magic/magic.c b/applications/external/nfc_magic/lib/magic/magic.c index 9a71daaa0ff..ebd2b08057a 100644 --- a/applications/external/nfc_magic/lib/magic/magic.c +++ b/applications/external/nfc_magic/lib/magic/magic.c @@ -1,4 +1,4 @@ -#include "magic.h" +#include "classic_gen1.h" #include @@ -15,7 +15,7 @@ #define MAGIC_BUFFER_SIZE (32) -bool magic_wupa() { +bool magic_gen1_wupa() { bool magic_activated = false; uint8_t tx_data[MAGIC_BUFFER_SIZE] = {}; uint8_t rx_data[MAGIC_BUFFER_SIZE] = {}; @@ -23,19 +23,6 @@ bool magic_wupa() { FuriHalNfcReturn ret = 0; do { - // Setup nfc poller - furi_hal_nfc_exit_sleep(); - furi_hal_nfc_ll_txrx_on(); - furi_hal_nfc_ll_poll(); - ret = furi_hal_nfc_ll_set_mode( - FuriHalNfcModePollNfca, FuriHalNfcBitrate106, FuriHalNfcBitrate106); - if(ret != FuriHalNfcReturnOk) break; - - furi_hal_nfc_ll_set_fdt_listen(FURI_HAL_NFC_LL_FDT_LISTEN_NFCA_POLLER); - furi_hal_nfc_ll_set_fdt_poll(FURI_HAL_NFC_LL_FDT_POLL_NFCA_POLLER); - furi_hal_nfc_ll_set_error_handling(FuriHalNfcErrorHandlingNfc); - furi_hal_nfc_ll_set_guard_time(FURI_HAL_NFC_LL_GT_NFCA); - // Start communication tx_data[0] = MAGIC_CMD_WUPA; ret = furi_hal_nfc_ll_txrx_bits( @@ -53,15 +40,10 @@ bool magic_wupa() { magic_activated = true; } while(false); - if(!magic_activated) { - furi_hal_nfc_ll_txrx_off(); - furi_hal_nfc_start_sleep(); - } - return magic_activated; } -bool magic_data_access_cmd() { +bool magic_gen1_data_access_cmd() { bool write_cmd_success = false; uint8_t tx_data[MAGIC_BUFFER_SIZE] = {}; uint8_t rx_data[MAGIC_BUFFER_SIZE] = {}; @@ -86,15 +68,10 @@ bool magic_data_access_cmd() { write_cmd_success = true; } while(false); - if(!write_cmd_success) { - furi_hal_nfc_ll_txrx_off(); - furi_hal_nfc_start_sleep(); - } - return write_cmd_success; } -bool magic_read_block(uint8_t block_num, MfClassicBlock* data) { +bool magic_gen1_read_block(uint8_t block_num, MfClassicBlock* data) { furi_assert(data); bool read_success = false; @@ -122,15 +99,10 @@ bool magic_read_block(uint8_t block_num, MfClassicBlock* data) { read_success = true; } while(false); - if(!read_success) { - furi_hal_nfc_ll_txrx_off(); - furi_hal_nfc_start_sleep(); - } - return read_success; } -bool magic_write_blk(uint8_t block_num, MfClassicBlock* data) { +bool magic_gen1_write_blk(uint8_t block_num, MfClassicBlock* data) { furi_assert(data); bool write_success = false; @@ -170,15 +142,10 @@ bool magic_write_blk(uint8_t block_num, MfClassicBlock* data) { write_success = true; } while(false); - if(!write_success) { - furi_hal_nfc_ll_txrx_off(); - furi_hal_nfc_start_sleep(); - } - return write_success; } -bool magic_wipe() { +bool magic_gen1_wipe() { bool wipe_success = false; uint8_t tx_data[MAGIC_BUFFER_SIZE] = {}; uint8_t rx_data[MAGIC_BUFFER_SIZE] = {}; @@ -205,9 +172,4 @@ bool magic_wipe() { } while(false); return wipe_success; -} - -void magic_deactivate() { - furi_hal_nfc_ll_txrx_off(); - furi_hal_nfc_sleep(); -} +} \ No newline at end of file diff --git a/applications/external/nfc_magic/lib/magic/magic.h b/applications/external/nfc_magic/lib/magic/magic.h deleted file mode 100644 index 64c60a0a705..00000000000 --- a/applications/external/nfc_magic/lib/magic/magic.h +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once - -#include - -bool magic_wupa(); - -bool magic_read_block(uint8_t block_num, MfClassicBlock* data); - -bool magic_data_access_cmd(); - -bool magic_write_blk(uint8_t block_num, MfClassicBlock* data); - -bool magic_wipe(); - -void magic_deactivate(); diff --git a/applications/external/nfc_magic/lib/magic/types.c b/applications/external/nfc_magic/lib/magic/types.c new file mode 100644 index 00000000000..77c6c0a4eff --- /dev/null +++ b/applications/external/nfc_magic/lib/magic/types.c @@ -0,0 +1,23 @@ +#include "types.h" + +const char* nfc_magic_type(MagicType type) { + if(type == MagicTypeClassicGen1) { + return "Classic Gen 1A/B"; + } else if(type == MagicTypeClassicDirectWrite) { + return "Classic DirectWrite"; + } else if(type == MagicTypeClassicAPDU) { + return "Classic APDU"; + } else if(type == MagicTypeUltralightGen1) { + return "Ultralight Gen 1"; + } else if(type == MagicTypeUltralightDirectWrite) { + return "Ultralight DirectWrite"; + } else if(type == MagicTypeUltralightC_Gen1) { + return "Ultralight-C Gen 1"; + } else if(type == MagicTypeUltralightC_DirectWrite) { + return "Ultralight-C DirectWrite"; + } else if(type == MagicTypeGen4) { + return "Gen 4 GTU"; + } else { + return "Unknown"; + } +} diff --git a/applications/external/nfc_magic/lib/magic/types.h b/applications/external/nfc_magic/lib/magic/types.h new file mode 100644 index 00000000000..dbf5540637c --- /dev/null +++ b/applications/external/nfc_magic/lib/magic/types.h @@ -0,0 +1,5 @@ +#pragma once + +#include "common.h" + +const char* nfc_magic_type(MagicType type); \ No newline at end of file diff --git a/applications/external/nfc_magic/nfc_magic.c b/applications/external/nfc_magic/nfc_magic.c index 1805f35ed05..68c9a65b5fd 100644 --- a/applications/external/nfc_magic/nfc_magic.c +++ b/applications/external/nfc_magic/nfc_magic.c @@ -48,8 +48,9 @@ NfcMagic* nfc_magic_alloc() { nfc_magic->view_dispatcher, nfc_magic_tick_event_callback, 100); // Nfc device - nfc_magic->nfc_dev = nfc_device_alloc(); - furi_string_set(nfc_magic->nfc_dev->folder, NFC_APP_FOLDER); + nfc_magic->dev = malloc(sizeof(NfcMagicDevice)); + nfc_magic->source_dev = nfc_device_alloc(); + furi_string_set(nfc_magic->source_dev->folder, NFC_APP_FOLDER); // Open GUI record nfc_magic->gui = furi_record_open(RECORD_GUI); @@ -81,6 +82,13 @@ NfcMagic* nfc_magic_alloc() { NfcMagicViewTextInput, text_input_get_view(nfc_magic->text_input)); + // Byte Input + nfc_magic->byte_input = byte_input_alloc(); + view_dispatcher_add_view( + nfc_magic->view_dispatcher, + NfcMagicViewByteInput, + byte_input_get_view(nfc_magic->byte_input)); + // Custom Widget nfc_magic->widget = widget_alloc(); view_dispatcher_add_view( @@ -93,7 +101,8 @@ void nfc_magic_free(NfcMagic* nfc_magic) { furi_assert(nfc_magic); // Nfc device - nfc_device_free(nfc_magic->nfc_dev); + free(nfc_magic->dev); + nfc_device_free(nfc_magic->source_dev); // Submenu view_dispatcher_remove_view(nfc_magic->view_dispatcher, NfcMagicViewMenu); @@ -107,10 +116,14 @@ void nfc_magic_free(NfcMagic* nfc_magic) { view_dispatcher_remove_view(nfc_magic->view_dispatcher, NfcMagicViewLoading); loading_free(nfc_magic->loading); - // TextInput + // Text Input view_dispatcher_remove_view(nfc_magic->view_dispatcher, NfcMagicViewTextInput); text_input_free(nfc_magic->text_input); + // Byte Input + view_dispatcher_remove_view(nfc_magic->view_dispatcher, NfcMagicViewByteInput); + byte_input_free(nfc_magic->byte_input); + // Custom Widget view_dispatcher_remove_view(nfc_magic->view_dispatcher, NfcMagicViewWidget); widget_free(nfc_magic->widget); @@ -164,6 +177,7 @@ int32_t nfc_magic_app(void* p) { view_dispatcher_run(nfc_magic->view_dispatcher); + magic_deactivate(); nfc_magic_free(nfc_magic); return 0; diff --git a/applications/external/nfc_magic/nfc_magic.h b/applications/external/nfc_magic/nfc_magic.h index 1abf1371ed0..f9cf395d826 100644 --- a/applications/external/nfc_magic/nfc_magic.h +++ b/applications/external/nfc_magic/nfc_magic.h @@ -1,3 +1,5 @@ #pragma once +typedef struct NfcMagicDevice NfcMagicDevice; + typedef struct NfcMagic NfcMagic; diff --git a/applications/external/nfc_magic/nfc_magic_i.h b/applications/external/nfc_magic/nfc_magic_i.h index 378912e5b09..4d6b89103d4 100644 --- a/applications/external/nfc_magic/nfc_magic_i.h +++ b/applications/external/nfc_magic/nfc_magic_i.h @@ -3,7 +3,10 @@ #include "nfc_magic.h" #include "nfc_magic_worker.h" -#include "lib/magic/magic.h" +#include "lib/magic/common.h" +#include "lib/magic/types.h" +#include "lib/magic/classic_gen1.h" +#include "lib/magic/gen4.h" #include #include @@ -15,6 +18,7 @@ #include #include #include +#include #include #include @@ -39,14 +43,22 @@ enum NfcMagicCustomEvent { NfcMagicCustomEventTextInputDone, }; +struct NfcMagicDevice { + MagicType type; + uint32_t cuid; + uint32_t password; +}; + struct NfcMagic { NfcMagicWorker* worker; ViewDispatcher* view_dispatcher; Gui* gui; NotificationApp* notifications; SceneManager* scene_manager; - // NfcMagicDevice* dev; - NfcDevice* nfc_dev; + struct NfcMagicDevice* dev; + NfcDevice* source_dev; + + uint32_t new_password; FuriString* text_box_store; @@ -55,6 +67,7 @@ struct NfcMagic { Popup* popup; Loading* loading; TextInput* text_input; + ByteInput* byte_input; Widget* widget; }; @@ -63,6 +76,7 @@ typedef enum { NfcMagicViewPopup, NfcMagicViewLoading, NfcMagicViewTextInput, + NfcMagicViewByteInput, NfcMagicViewWidget, } NfcMagicView; diff --git a/applications/external/nfc_magic/nfc_magic_worker.c b/applications/external/nfc_magic/nfc_magic_worker.c index 92eb793a71d..dc22b5d3ec6 100644 --- a/applications/external/nfc_magic/nfc_magic_worker.c +++ b/applications/external/nfc_magic/nfc_magic_worker.c @@ -1,6 +1,9 @@ #include "nfc_magic_worker_i.h" -#include "lib/magic/magic.h" +#include "nfc_magic_i.h" +#include "lib/magic/common.h" +#include "lib/magic/classic_gen1.h" +#include "lib/magic/gen4.h" #define TAG "NfcMagicWorker" @@ -43,15 +46,20 @@ void nfc_magic_worker_stop(NfcMagicWorker* nfc_magic_worker) { void nfc_magic_worker_start( NfcMagicWorker* nfc_magic_worker, NfcMagicWorkerState state, + NfcMagicDevice* magic_dev, NfcDeviceData* dev_data, + uint32_t new_password, NfcMagicWorkerCallback callback, void* context) { furi_assert(nfc_magic_worker); + furi_assert(magic_dev); furi_assert(dev_data); nfc_magic_worker->callback = callback; nfc_magic_worker->context = context; + nfc_magic_worker->magic_dev = magic_dev; nfc_magic_worker->dev_data = dev_data; + nfc_magic_worker->new_password = new_password; nfc_magic_worker_change_state(nfc_magic_worker, state); furi_thread_start(nfc_magic_worker->thread); } @@ -63,6 +71,8 @@ int32_t nfc_magic_worker_task(void* context) { nfc_magic_worker_check(nfc_magic_worker); } else if(nfc_magic_worker->state == NfcMagicWorkerStateWrite) { nfc_magic_worker_write(nfc_magic_worker); + } else if(nfc_magic_worker->state == NfcMagicWorkerStateRekey) { + nfc_magic_worker_rekey(nfc_magic_worker); } else if(nfc_magic_worker->state == NfcMagicWorkerStateWipe) { nfc_magic_worker_wipe(nfc_magic_worker); } @@ -74,59 +84,245 @@ int32_t nfc_magic_worker_task(void* context) { void nfc_magic_worker_write(NfcMagicWorker* nfc_magic_worker) { bool card_found_notified = false; + bool done = false; FuriHalNfcDevData nfc_data = {}; - MfClassicData* src_data = &nfc_magic_worker->dev_data->mf_classic_data; + NfcMagicDevice* magic_dev = nfc_magic_worker->magic_dev; + NfcDeviceData* dev_data = nfc_magic_worker->dev_data; + NfcProtocol dev_protocol = dev_data->protocol; while(nfc_magic_worker->state == NfcMagicWorkerStateWrite) { - if(furi_hal_nfc_detect(&nfc_data, 200)) { - if(!card_found_notified) { - nfc_magic_worker->callback( - NfcMagicWorkerEventCardDetected, nfc_magic_worker->context); - card_found_notified = true; - } - furi_hal_nfc_sleep(); - if(!magic_wupa()) { - FURI_LOG_E(TAG, "No card response to WUPA (not a magic card)"); - nfc_magic_worker->callback( - NfcMagicWorkerEventWrongCard, nfc_magic_worker->context); - break; - } - furi_hal_nfc_sleep(); - } - if(magic_wupa()) { - if(!magic_data_access_cmd()) { - FURI_LOG_E(TAG, "No card response to data access command (not a magic card)"); - nfc_magic_worker->callback( - NfcMagicWorkerEventWrongCard, nfc_magic_worker->context); - break; - } - for(size_t i = 0; i < 64; i++) { - FURI_LOG_D(TAG, "Writing block %d", i); - if(!magic_write_blk(i, &src_data->block[i])) { - FURI_LOG_E(TAG, "Failed to write %d block", i); - nfc_magic_worker->callback(NfcMagicWorkerEventFail, nfc_magic_worker->context); + do { + if(furi_hal_nfc_detect(&nfc_data, 200)) { + if(nfc_data.cuid != magic_dev->cuid) break; + if(!card_found_notified) { + nfc_magic_worker->callback( + NfcMagicWorkerEventCardDetected, nfc_magic_worker->context); + card_found_notified = true; + } + furi_hal_nfc_sleep(); + + magic_activate(); + if(magic_dev->type == MagicTypeClassicGen1) { + if(dev_protocol != NfcDeviceProtocolMifareClassic) break; + MfClassicData* mfc_data = &dev_data->mf_classic_data; + + if(mfc_data->type != MfClassicType1k) break; + if(!magic_gen1_wupa()) { + FURI_LOG_E(TAG, "Not Magic card"); + nfc_magic_worker->callback( + NfcMagicWorkerEventWrongCard, nfc_magic_worker->context); + done = true; + break; + } + if(!magic_gen1_data_access_cmd()) { + FURI_LOG_E(TAG, "Not Magic card"); + nfc_magic_worker->callback( + NfcMagicWorkerEventWrongCard, nfc_magic_worker->context); + done = true; + break; + } + for(size_t i = 0; i < 64; i++) { + FURI_LOG_D(TAG, "Writing block %d", i); + if(!magic_gen1_write_blk(i, &mfc_data->block[i])) { + FURI_LOG_E(TAG, "Failed to write %d block", i); + nfc_magic_worker->callback( + NfcMagicWorkerEventFail, nfc_magic_worker->context); + done = true; + break; + } + } + + nfc_magic_worker->callback( + NfcMagicWorkerEventSuccess, nfc_magic_worker->context); + done = true; + break; + } else if(magic_dev->type == MagicTypeGen4) { + uint8_t gen4_config[28]; + uint32_t password = magic_dev->password; + + uint32_t cuid; + if(dev_protocol == NfcDeviceProtocolMifareClassic) { + gen4_config[0] = 0x00; + gen4_config[27] = 0x00; + } else if(dev_protocol == NfcDeviceProtocolMifareUl) { + MfUltralightData* mf_ul_data = &dev_data->mf_ul_data; + gen4_config[0] = 0x01; + switch(mf_ul_data->type) { + case MfUltralightTypeUL11: + case MfUltralightTypeUL21: + // UL-C? + // UL? + default: + gen4_config[27] = MagicGen4UltralightModeUL_EV1; + break; + case MfUltralightTypeNTAG203: + case MfUltralightTypeNTAG213: + case MfUltralightTypeNTAG215: + case MfUltralightTypeNTAG216: + case MfUltralightTypeNTAGI2C1K: + case MfUltralightTypeNTAGI2C2K: + case MfUltralightTypeNTAGI2CPlus1K: + case MfUltralightTypeNTAGI2CPlus2K: + gen4_config[27] = MagicGen4UltralightModeNTAG; + break; + } + } + + if(dev_data->nfc_data.uid_len == 4) { + gen4_config[1] = MagicGen4UIDLengthSingle; + } else if(dev_data->nfc_data.uid_len == 7) { + gen4_config[1] = MagicGen4UIDLengthDouble; + } else { + FURI_LOG_E(TAG, "Unexpected UID length %d", dev_data->nfc_data.uid_len); + nfc_magic_worker->callback( + NfcMagicWorkerEventFail, nfc_magic_worker->context); + done = true; + break; + } + + gen4_config[2] = (uint8_t)(password >> 24); + gen4_config[3] = (uint8_t)(password >> 16); + gen4_config[4] = (uint8_t)(password >> 8); + gen4_config[5] = (uint8_t)password; + + if(dev_protocol == NfcDeviceProtocolMifareUl) { + gen4_config[6] = MagicGen4ShadowModeHighSpeedIgnore; + } else { + gen4_config[6] = MagicGen4ShadowModeIgnore; + } + gen4_config[7] = 0x00; + memset(gen4_config + 8, 0, 16); + gen4_config[24] = dev_data->nfc_data.atqa[0]; + gen4_config[25] = dev_data->nfc_data.atqa[1]; + gen4_config[26] = dev_data->nfc_data.sak; + + furi_hal_nfc_activate_nfca(200, &cuid); + if(!magic_gen4_set_cfg(password, gen4_config, sizeof(gen4_config), false)) { + nfc_magic_worker->callback( + NfcMagicWorkerEventFail, nfc_magic_worker->context); + done = true; + break; + } + if(dev_protocol == NfcDeviceProtocolMifareClassic) { + MfClassicData* mfc_data = &dev_data->mf_classic_data; + size_t block_count = 64; + if(mfc_data->type == MfClassicType4k) block_count = 256; + for(size_t i = 0; i < block_count; i++) { + FURI_LOG_D(TAG, "Writing block %d", i); + if(!magic_gen4_write_blk(password, i, mfc_data->block[i].value)) { + FURI_LOG_E(TAG, "Failed to write %d block", i); + nfc_magic_worker->callback( + NfcMagicWorkerEventFail, nfc_magic_worker->context); + done = true; + break; + } + } + } else if(dev_protocol == NfcDeviceProtocolMifareUl) { + MfUltralightData* mf_ul_data = &dev_data->mf_ul_data; + for(size_t i = 0; (i * 4) < mf_ul_data->data_read; i++) { + size_t data_offset = i * 4; + FURI_LOG_D( + TAG, + "Writing page %zu (%zu/%u)", + i, + data_offset, + mf_ul_data->data_read); + uint8_t* block = mf_ul_data->data + data_offset; + if(!magic_gen4_write_blk(password, i, block)) { + FURI_LOG_E(TAG, "Failed to write %zu page", i); + nfc_magic_worker->callback( + NfcMagicWorkerEventFail, nfc_magic_worker->context); + done = true; + break; + } + } + + uint8_t buffer[16] = {0}; + + for(size_t i = 0; i < 8; i++) { + memcpy(buffer, &mf_ul_data->signature[i * 4], 4); //-V1086 + if(!magic_gen4_write_blk(password, 0xF2 + i, buffer)) { + FURI_LOG_E(TAG, "Failed to write signature block %d", i); + nfc_magic_worker->callback( + NfcMagicWorkerEventFail, nfc_magic_worker->context); + done = true; + break; + } + } + + buffer[0] = mf_ul_data->version.header; + buffer[1] = mf_ul_data->version.vendor_id; + buffer[2] = mf_ul_data->version.prod_type; + buffer[3] = mf_ul_data->version.prod_subtype; + if(!magic_gen4_write_blk(password, 0xFA, buffer)) { + FURI_LOG_E(TAG, "Failed to write version block 0"); + nfc_magic_worker->callback( + NfcMagicWorkerEventFail, nfc_magic_worker->context); + done = true; + break; + } + + buffer[0] = mf_ul_data->version.prod_ver_major; + buffer[1] = mf_ul_data->version.prod_ver_minor; + buffer[2] = mf_ul_data->version.storage_size; + buffer[3] = mf_ul_data->version.protocol_type; + if(!magic_gen4_write_blk(password, 0xFB, buffer)) { + FURI_LOG_E(TAG, "Failed to write version block 1"); + nfc_magic_worker->callback( + NfcMagicWorkerEventFail, nfc_magic_worker->context); + done = true; + break; + } + } + + nfc_magic_worker->callback( + NfcMagicWorkerEventSuccess, nfc_magic_worker->context); + done = true; break; } } - nfc_magic_worker->callback(NfcMagicWorkerEventSuccess, nfc_magic_worker->context); - break; - } else { - if(card_found_notified) { - nfc_magic_worker->callback( - NfcMagicWorkerEventNoCardDetected, nfc_magic_worker->context); - card_found_notified = false; - } + } while(false); + + if(done) break; + + if(card_found_notified) { + nfc_magic_worker->callback( + NfcMagicWorkerEventNoCardDetected, nfc_magic_worker->context); + card_found_notified = false; } + furi_delay_ms(300); } magic_deactivate(); } void nfc_magic_worker_check(NfcMagicWorker* nfc_magic_worker) { + NfcMagicDevice* magic_dev = nfc_magic_worker->magic_dev; bool card_found_notified = false; + uint8_t gen4_config[MAGIC_GEN4_CONFIG_LEN]; while(nfc_magic_worker->state == NfcMagicWorkerStateCheck) { - if(magic_wupa()) { + magic_activate(); + if(magic_gen1_wupa()) { + magic_dev->type = MagicTypeClassicGen1; + if(!card_found_notified) { + nfc_magic_worker->callback( + NfcMagicWorkerEventCardDetected, nfc_magic_worker->context); + card_found_notified = true; + } + + furi_hal_nfc_activate_nfca(200, &magic_dev->cuid); + nfc_magic_worker->callback(NfcMagicWorkerEventSuccess, nfc_magic_worker->context); + break; + } + + magic_deactivate(); + furi_delay_ms(300); + magic_activate(); + + furi_hal_nfc_activate_nfca(200, &magic_dev->cuid); + if(magic_gen4_get_cfg(magic_dev->password, gen4_config)) { + magic_dev->type = MagicTypeGen4; if(!card_found_notified) { nfc_magic_worker->callback( NfcMagicWorkerEventCardDetected, nfc_magic_worker->context); @@ -135,12 +331,56 @@ void nfc_magic_worker_check(NfcMagicWorker* nfc_magic_worker) { nfc_magic_worker->callback(NfcMagicWorkerEventSuccess, nfc_magic_worker->context); break; - } else { + } + + if(card_found_notified) { + nfc_magic_worker->callback( + NfcMagicWorkerEventNoCardDetected, nfc_magic_worker->context); + card_found_notified = false; + } + + magic_deactivate(); + furi_delay_ms(300); + } + + magic_deactivate(); +} + +void nfc_magic_worker_rekey(NfcMagicWorker* nfc_magic_worker) { + NfcMagicDevice* magic_dev = nfc_magic_worker->magic_dev; + bool card_found_notified = false; + + if(magic_dev->type != MagicTypeGen4) { + nfc_magic_worker->callback(NfcMagicWorkerEventCardDetected, nfc_magic_worker->context); + return; + } + + while(nfc_magic_worker->state == NfcMagicWorkerStateRekey) { + magic_activate(); + uint32_t cuid; + furi_hal_nfc_activate_nfca(200, &cuid); + if(cuid != magic_dev->cuid) { if(card_found_notified) { nfc_magic_worker->callback( NfcMagicWorkerEventNoCardDetected, nfc_magic_worker->context); card_found_notified = false; } + continue; + } + + nfc_magic_worker->callback(NfcMagicWorkerEventCardDetected, nfc_magic_worker->context); + card_found_notified = true; + + if(magic_gen4_set_pwd(magic_dev->password, nfc_magic_worker->new_password)) { + magic_dev->password = nfc_magic_worker->new_password; + nfc_magic_worker->callback(NfcMagicWorkerEventSuccess, nfc_magic_worker->context); + break; + } + + if(card_found_notified) { //-V547 + nfc_magic_worker->callback( + NfcMagicWorkerEventNoCardDetected, nfc_magic_worker->context); + card_found_notified = false; } furi_delay_ms(300); } @@ -148,6 +388,10 @@ void nfc_magic_worker_check(NfcMagicWorker* nfc_magic_worker) { } void nfc_magic_worker_wipe(NfcMagicWorker* nfc_magic_worker) { + NfcMagicDevice* magic_dev = nfc_magic_worker->magic_dev; + bool card_found_notified = false; + bool card_wiped = false; + MfClassicBlock block; memset(&block, 0, sizeof(MfClassicBlock)); block.value[0] = 0x01; @@ -159,14 +403,48 @@ void nfc_magic_worker_wipe(NfcMagicWorker* nfc_magic_worker) { block.value[6] = 0x04; while(nfc_magic_worker->state == NfcMagicWorkerStateWipe) { - magic_deactivate(); - furi_delay_ms(300); - if(!magic_wupa()) continue; - if(!magic_wipe()) continue; - if(!magic_data_access_cmd()) continue; - if(!magic_write_blk(0, &block)) continue; - nfc_magic_worker->callback(NfcMagicWorkerEventSuccess, nfc_magic_worker->context); - break; + do { + magic_deactivate(); + furi_delay_ms(300); + if(!magic_activate()) break; + if(magic_dev->type == MagicTypeClassicGen1) { + if(!magic_gen1_wupa()) break; + if(!card_found_notified) { + nfc_magic_worker->callback( + NfcMagicWorkerEventCardDetected, nfc_magic_worker->context); + card_found_notified = true; + } + + if(!magic_gen1_wipe()) break; + if(!magic_gen1_data_access_cmd()) break; + if(!magic_gen1_write_blk(0, &block)) break; + + card_wiped = true; + nfc_magic_worker->callback(NfcMagicWorkerEventSuccess, nfc_magic_worker->context); + } else if(magic_dev->type == MagicTypeGen4) { + uint32_t cuid; + if(!furi_hal_nfc_activate_nfca(200, &cuid)) break; + if(cuid != magic_dev->cuid) break; + if(!card_found_notified) { + nfc_magic_worker->callback( + NfcMagicWorkerEventCardDetected, nfc_magic_worker->context); + card_found_notified = true; + } + + if(!magic_gen4_wipe(magic_dev->password)) break; + + card_wiped = true; + nfc_magic_worker->callback(NfcMagicWorkerEventSuccess, nfc_magic_worker->context); + } + } while(false); + + if(card_wiped) break; + + if(card_found_notified) { + nfc_magic_worker->callback( + NfcMagicWorkerEventNoCardDetected, nfc_magic_worker->context); + card_found_notified = false; + } } magic_deactivate(); } diff --git a/applications/external/nfc_magic/nfc_magic_worker.h b/applications/external/nfc_magic/nfc_magic_worker.h index 9d29bb3a8be..51ff4ee438d 100644 --- a/applications/external/nfc_magic/nfc_magic_worker.h +++ b/applications/external/nfc_magic/nfc_magic_worker.h @@ -1,6 +1,7 @@ #pragma once #include +#include "nfc_magic.h" typedef struct NfcMagicWorker NfcMagicWorker; @@ -9,6 +10,7 @@ typedef enum { NfcMagicWorkerStateCheck, NfcMagicWorkerStateWrite, + NfcMagicWorkerStateRekey, NfcMagicWorkerStateWipe, NfcMagicWorkerStateStop, @@ -33,6 +35,8 @@ void nfc_magic_worker_stop(NfcMagicWorker* nfc_magic_worker); void nfc_magic_worker_start( NfcMagicWorker* nfc_magic_worker, NfcMagicWorkerState state, + NfcMagicDevice* magic_dev, NfcDeviceData* dev_data, + uint32_t new_password, NfcMagicWorkerCallback callback, void* context); diff --git a/applications/external/nfc_magic/nfc_magic_worker_i.h b/applications/external/nfc_magic/nfc_magic_worker_i.h index 0cde2e7125b..a354f804763 100644 --- a/applications/external/nfc_magic/nfc_magic_worker_i.h +++ b/applications/external/nfc_magic/nfc_magic_worker_i.h @@ -3,11 +3,14 @@ #include #include "nfc_magic_worker.h" +#include "lib/magic/common.h" struct NfcMagicWorker { FuriThread* thread; + NfcMagicDevice* magic_dev; NfcDeviceData* dev_data; + uint32_t new_password; NfcMagicWorkerCallback callback; void* context; @@ -21,4 +24,6 @@ void nfc_magic_worker_check(NfcMagicWorker* nfc_magic_worker); void nfc_magic_worker_write(NfcMagicWorker* nfc_magic_worker); +void nfc_magic_worker_rekey(NfcMagicWorker* nfc_magic_worker); + void nfc_magic_worker_wipe(NfcMagicWorker* nfc_magic_worker); diff --git a/applications/external/nfc_magic/scenes/nfc_magic_scene_actions.c b/applications/external/nfc_magic/scenes/nfc_magic_scene_actions.c new file mode 100644 index 00000000000..675262a9b2f --- /dev/null +++ b/applications/external/nfc_magic/scenes/nfc_magic_scene_actions.c @@ -0,0 +1,50 @@ +#include "../nfc_magic_i.h" +enum SubmenuIndex { + SubmenuIndexWrite, + SubmenuIndexWipe, +}; + +void nfc_magic_scene_actions_submenu_callback(void* context, uint32_t index) { + NfcMagic* nfc_magic = context; + view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, index); +} + +void nfc_magic_scene_actions_on_enter(void* context) { + NfcMagic* nfc_magic = context; + + Submenu* submenu = nfc_magic->submenu; + submenu_add_item( + submenu, "Write", SubmenuIndexWrite, nfc_magic_scene_actions_submenu_callback, nfc_magic); + submenu_add_item( + submenu, "Wipe", SubmenuIndexWipe, nfc_magic_scene_actions_submenu_callback, nfc_magic); + + submenu_set_selected_item( + submenu, scene_manager_get_scene_state(nfc_magic->scene_manager, NfcMagicSceneActions)); + view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewMenu); +} + +bool nfc_magic_scene_actions_on_event(void* context, SceneManagerEvent event) { + NfcMagic* nfc_magic = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubmenuIndexWrite) { + scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneFileSelect); + consumed = true; + } else if(event.event == SubmenuIndexWipe) { + scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneWipe); + consumed = true; + } + scene_manager_set_scene_state(nfc_magic->scene_manager, NfcMagicSceneActions, event.event); + } else if(event.type == SceneManagerEventTypeBack) { + consumed = scene_manager_search_and_switch_to_previous_scene( + nfc_magic->scene_manager, NfcMagicSceneStart); + } + + return consumed; +} + +void nfc_magic_scene_actions_on_exit(void* context) { + NfcMagic* nfc_magic = context; + submenu_reset(nfc_magic->submenu); +} diff --git a/applications/external/nfc_magic/scenes/nfc_magic_scene_check.c b/applications/external/nfc_magic/scenes/nfc_magic_scene_check.c index d5179724283..90b43d7d3a2 100644 --- a/applications/external/nfc_magic/scenes/nfc_magic_scene_check.c +++ b/applications/external/nfc_magic/scenes/nfc_magic_scene_check.c @@ -42,7 +42,9 @@ void nfc_magic_scene_check_on_enter(void* context) { nfc_magic_worker_start( nfc_magic->worker, NfcMagicWorkerStateCheck, - &nfc_magic->nfc_dev->dev_data, + nfc_magic->dev, + &nfc_magic->source_dev->dev_data, + nfc_magic->new_password, nfc_magic_check_worker_callback, nfc_magic); nfc_magic_blink_start(nfc_magic); diff --git a/applications/external/nfc_magic/scenes/nfc_magic_scene_config.h b/applications/external/nfc_magic/scenes/nfc_magic_scene_config.h index 557e26914e6..2f9860d96f2 100644 --- a/applications/external/nfc_magic/scenes/nfc_magic_scene_config.h +++ b/applications/external/nfc_magic/scenes/nfc_magic_scene_config.h @@ -1,4 +1,8 @@ ADD_SCENE(nfc_magic, start, Start) +ADD_SCENE(nfc_magic, key_input, KeyInput) +ADD_SCENE(nfc_magic, actions, Actions) +ADD_SCENE(nfc_magic, gen4_actions, Gen4Actions) +ADD_SCENE(nfc_magic, new_key_input, NewKeyInput) ADD_SCENE(nfc_magic, file_select, FileSelect) ADD_SCENE(nfc_magic, write_confirm, WriteConfirm) ADD_SCENE(nfc_magic, wrong_card, WrongCard) @@ -8,5 +12,7 @@ ADD_SCENE(nfc_magic, success, Success) ADD_SCENE(nfc_magic, check, Check) ADD_SCENE(nfc_magic, not_magic, NotMagic) ADD_SCENE(nfc_magic, magic_info, MagicInfo) +ADD_SCENE(nfc_magic, rekey, Rekey) +ADD_SCENE(nfc_magic, rekey_fail, RekeyFail) ADD_SCENE(nfc_magic, wipe, Wipe) ADD_SCENE(nfc_magic, wipe_fail, WipeFail) diff --git a/applications/external/nfc_magic/scenes/nfc_magic_scene_file_select.c b/applications/external/nfc_magic/scenes/nfc_magic_scene_file_select.c index d78422eeb66..baa6bcccc2d 100644 --- a/applications/external/nfc_magic/scenes/nfc_magic_scene_file_select.c +++ b/applications/external/nfc_magic/scenes/nfc_magic_scene_file_select.c @@ -1,22 +1,60 @@ #include "../nfc_magic_i.h" -static bool nfc_magic_scene_file_select_is_file_suitable(NfcDevice* nfc_dev) { - return (nfc_dev->format == NfcDeviceSaveFormatMifareClassic) && - (nfc_dev->dev_data.mf_classic_data.type == MfClassicType1k) && - (nfc_dev->dev_data.nfc_data.uid_len == 4); +static bool nfc_magic_scene_file_select_is_file_suitable(NfcMagic* nfc_magic) { + NfcDevice* nfc_dev = nfc_magic->source_dev; + if(nfc_dev->format == NfcDeviceSaveFormatMifareClassic) { + switch(nfc_magic->dev->type) { + case MagicTypeClassicGen1: + case MagicTypeClassicDirectWrite: + case MagicTypeClassicAPDU: + if((nfc_dev->dev_data.mf_classic_data.type != MfClassicType1k) || + (nfc_dev->dev_data.nfc_data.uid_len != 4)) { + return false; + } + return true; + + case MagicTypeGen4: + return true; + default: + return false; + } + } else if( + (nfc_dev->format == NfcDeviceSaveFormatMifareUl) && + (nfc_dev->dev_data.nfc_data.uid_len == 7)) { + switch(nfc_magic->dev->type) { + case MagicTypeUltralightGen1: + case MagicTypeUltralightDirectWrite: + case MagicTypeUltralightC_Gen1: + case MagicTypeUltralightC_DirectWrite: + case MagicTypeGen4: + switch(nfc_dev->dev_data.mf_ul_data.type) { + case MfUltralightTypeNTAGI2C1K: + case MfUltralightTypeNTAGI2C2K: + case MfUltralightTypeNTAGI2CPlus1K: + case MfUltralightTypeNTAGI2CPlus2K: + return false; + default: + return true; + } + default: + return false; + } + } + + return false; } void nfc_magic_scene_file_select_on_enter(void* context) { NfcMagic* nfc_magic = context; // Process file_select return - nfc_device_set_loading_callback(nfc_magic->nfc_dev, nfc_magic_show_loading_popup, nfc_magic); + nfc_device_set_loading_callback( + nfc_magic->source_dev, nfc_magic_show_loading_popup, nfc_magic); - if(!furi_string_size(nfc_magic->nfc_dev->load_path)) { - furi_string_set_str(nfc_magic->nfc_dev->load_path, NFC_APP_FOLDER); + if(!furi_string_size(nfc_magic->source_dev->load_path)) { + furi_string_set_str(nfc_magic->source_dev->load_path, NFC_APP_FOLDER); } - - if(nfc_file_select(nfc_magic->nfc_dev)) { - if(nfc_magic_scene_file_select_is_file_suitable(nfc_magic->nfc_dev)) { + if(nfc_file_select(nfc_magic->source_dev)) { + if(nfc_magic_scene_file_select_is_file_suitable(nfc_magic)) { scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneWriteConfirm); } else { scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneWrongCard); @@ -34,5 +72,5 @@ bool nfc_magic_scene_file_select_on_event(void* context, SceneManagerEvent event void nfc_magic_scene_file_select_on_exit(void* context) { NfcMagic* nfc_magic = context; - nfc_device_set_loading_callback(nfc_magic->nfc_dev, NULL, nfc_magic); + nfc_device_set_loading_callback(nfc_magic->source_dev, NULL, nfc_magic); } diff --git a/applications/external/nfc_magic/scenes/nfc_magic_scene_gen4_actions.c b/applications/external/nfc_magic/scenes/nfc_magic_scene_gen4_actions.c new file mode 100644 index 00000000000..ceaa33e29f0 --- /dev/null +++ b/applications/external/nfc_magic/scenes/nfc_magic_scene_gen4_actions.c @@ -0,0 +1,70 @@ +#include "../nfc_magic_i.h" +enum SubmenuIndex { + SubmenuIndexWrite, + SubmenuIndexChangePassword, + SubmenuIndexWipe, +}; + +void nfc_magic_scene_gen4_actions_submenu_callback(void* context, uint32_t index) { + NfcMagic* nfc_magic = context; + view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, index); +} + +void nfc_magic_scene_gen4_actions_on_enter(void* context) { + NfcMagic* nfc_magic = context; + + Submenu* submenu = nfc_magic->submenu; + submenu_add_item( + submenu, + "Write", + SubmenuIndexWrite, + nfc_magic_scene_gen4_actions_submenu_callback, + nfc_magic); + submenu_add_item( + submenu, + "Change password", + SubmenuIndexChangePassword, + nfc_magic_scene_gen4_actions_submenu_callback, + nfc_magic); + submenu_add_item( + submenu, + "Wipe", + SubmenuIndexWipe, + nfc_magic_scene_gen4_actions_submenu_callback, + nfc_magic); + + submenu_set_selected_item( + submenu, + scene_manager_get_scene_state(nfc_magic->scene_manager, NfcMagicSceneGen4Actions)); + view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewMenu); +} + +bool nfc_magic_scene_gen4_actions_on_event(void* context, SceneManagerEvent event) { + NfcMagic* nfc_magic = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubmenuIndexWrite) { + scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneFileSelect); + consumed = true; + } else if(event.event == SubmenuIndexChangePassword) { + scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneNewKeyInput); + consumed = true; + } else if(event.event == SubmenuIndexWipe) { + scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneWipe); + consumed = true; + } + scene_manager_set_scene_state( + nfc_magic->scene_manager, NfcMagicSceneGen4Actions, event.event); + } else if(event.type == SceneManagerEventTypeBack) { + consumed = scene_manager_search_and_switch_to_previous_scene( + nfc_magic->scene_manager, NfcMagicSceneStart); + } + + return consumed; +} + +void nfc_magic_scene_gen4_actions_on_exit(void* context) { + NfcMagic* nfc_magic = context; + submenu_reset(nfc_magic->submenu); +} diff --git a/applications/external/nfc_magic/scenes/nfc_magic_scene_key_input.c b/applications/external/nfc_magic/scenes/nfc_magic_scene_key_input.c new file mode 100644 index 00000000000..58b487a09bb --- /dev/null +++ b/applications/external/nfc_magic/scenes/nfc_magic_scene_key_input.c @@ -0,0 +1,45 @@ +#include "../nfc_magic_i.h" + +void nfc_magic_scene_key_input_byte_input_callback(void* context) { + NfcMagic* nfc_magic = context; + + view_dispatcher_send_custom_event( + nfc_magic->view_dispatcher, NfcMagicCustomEventByteInputDone); +} + +void nfc_magic_scene_key_input_on_enter(void* context) { + NfcMagic* nfc_magic = context; + + // Setup view + ByteInput* byte_input = nfc_magic->byte_input; + byte_input_set_header_text(byte_input, "Enter the password in hex"); + byte_input_set_result_callback( + byte_input, + nfc_magic_scene_key_input_byte_input_callback, + NULL, + nfc_magic, + (uint8_t*)&nfc_magic->dev->password, + 4); + view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewByteInput); +} + +bool nfc_magic_scene_key_input_on_event(void* context, SceneManagerEvent event) { + NfcMagic* nfc_magic = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcMagicCustomEventByteInputDone) { + scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneCheck); + consumed = true; + } + } + return consumed; +} + +void nfc_magic_scene_key_input_on_exit(void* context) { + NfcMagic* nfc_magic = context; + + // Clear view + byte_input_set_result_callback(nfc_magic->byte_input, NULL, NULL, NULL, NULL, 0); + byte_input_set_header_text(nfc_magic->byte_input, ""); +} diff --git a/applications/external/nfc_magic/scenes/nfc_magic_scene_magic_info.c b/applications/external/nfc_magic/scenes/nfc_magic_scene_magic_info.c index e9b226b3abb..c147ac4383f 100644 --- a/applications/external/nfc_magic/scenes/nfc_magic_scene_magic_info.c +++ b/applications/external/nfc_magic/scenes/nfc_magic_scene_magic_info.c @@ -1,4 +1,5 @@ #include "../nfc_magic_i.h" +#include "../lib/magic/types.h" void nfc_magic_scene_magic_info_widget_callback( GuiButtonType result, @@ -13,14 +14,18 @@ void nfc_magic_scene_magic_info_widget_callback( void nfc_magic_scene_magic_info_on_enter(void* context) { NfcMagic* nfc_magic = context; Widget* widget = nfc_magic->widget; + const char* card_type = nfc_magic_type(nfc_magic->dev->type); notification_message(nfc_magic->notifications, &sequence_success); widget_add_icon_element(widget, 73, 17, &I_DolphinCommon_56x48); widget_add_string_element( widget, 3, 4, AlignLeft, AlignTop, FontPrimary, "Magic card detected"); + widget_add_string_element(widget, 3, 17, AlignLeft, AlignTop, FontSecondary, card_type); widget_add_button_element( widget, GuiButtonTypeLeft, "Retry", nfc_magic_scene_magic_info_widget_callback, nfc_magic); + widget_add_button_element( + widget, GuiButtonTypeRight, "More", nfc_magic_scene_magic_info_widget_callback, nfc_magic); // Setup and start worker view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewWidget); @@ -33,6 +38,15 @@ bool nfc_magic_scene_magic_info_on_event(void* context, SceneManagerEvent event) if(event.type == SceneManagerEventTypeCustom) { if(event.event == GuiButtonTypeLeft) { consumed = scene_manager_previous_scene(nfc_magic->scene_manager); + } else if(event.event == GuiButtonTypeRight) { + MagicType type = nfc_magic->dev->type; + if(type == MagicTypeGen4) { + scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneGen4Actions); + consumed = true; + } else { + scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneActions); + consumed = true; + } } } return consumed; diff --git a/applications/external/nfc_magic/scenes/nfc_magic_scene_new_key_input.c b/applications/external/nfc_magic/scenes/nfc_magic_scene_new_key_input.c new file mode 100644 index 00000000000..b5247f6c555 --- /dev/null +++ b/applications/external/nfc_magic/scenes/nfc_magic_scene_new_key_input.c @@ -0,0 +1,45 @@ +#include "../nfc_magic_i.h" + +void nfc_magic_scene_new_key_input_byte_input_callback(void* context) { + NfcMagic* nfc_magic = context; + + view_dispatcher_send_custom_event( + nfc_magic->view_dispatcher, NfcMagicCustomEventByteInputDone); +} + +void nfc_magic_scene_new_key_input_on_enter(void* context) { + NfcMagic* nfc_magic = context; + + // Setup view + ByteInput* byte_input = nfc_magic->byte_input; + byte_input_set_header_text(byte_input, "Enter the password in hex"); + byte_input_set_result_callback( + byte_input, + nfc_magic_scene_new_key_input_byte_input_callback, + NULL, + nfc_magic, + (uint8_t*)&nfc_magic->new_password, + 4); + view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewByteInput); +} + +bool nfc_magic_scene_new_key_input_on_event(void* context, SceneManagerEvent event) { + NfcMagic* nfc_magic = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcMagicCustomEventByteInputDone) { + scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneRekey); + consumed = true; + } + } + return consumed; +} + +void nfc_magic_scene_new_key_input_on_exit(void* context) { + NfcMagic* nfc_magic = context; + + // Clear view + byte_input_set_result_callback(nfc_magic->byte_input, NULL, NULL, NULL, NULL, 0); + byte_input_set_header_text(nfc_magic->byte_input, ""); +} diff --git a/applications/external/nfc_magic/scenes/nfc_magic_scene_rekey.c b/applications/external/nfc_magic/scenes/nfc_magic_scene_rekey.c new file mode 100644 index 00000000000..259dc78eaa5 --- /dev/null +++ b/applications/external/nfc_magic/scenes/nfc_magic_scene_rekey.c @@ -0,0 +1,95 @@ +#include "../nfc_magic_i.h" + +enum { + NfcMagicSceneRekeyStateCardSearch, + NfcMagicSceneRekeyStateCardFound, +}; + +bool nfc_magic_rekey_worker_callback(NfcMagicWorkerEvent event, void* context) { + furi_assert(context); + + NfcMagic* nfc_magic = context; + view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, event); + + return true; +} + +static void nfc_magic_scene_rekey_setup_view(NfcMagic* nfc_magic) { + Popup* popup = nfc_magic->popup; + popup_reset(popup); + uint32_t state = scene_manager_get_scene_state(nfc_magic->scene_manager, NfcMagicSceneRekey); + + if(state == NfcMagicSceneRekeyStateCardSearch) { + popup_set_text( + nfc_magic->popup, + "Apply the\nsame card\nto the back", + 128, + 32, + AlignRight, + AlignCenter); + popup_set_icon(nfc_magic->popup, 0, 8, &I_NFC_manual_60x50); + } else { + popup_set_icon(popup, 12, 23, &I_Loading_24); + popup_set_header(popup, "Writing\nDon't move...", 52, 32, AlignLeft, AlignCenter); + } + + view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewPopup); +} + +void nfc_magic_scene_rekey_on_enter(void* context) { + NfcMagic* nfc_magic = context; + + scene_manager_set_scene_state( + nfc_magic->scene_manager, NfcMagicSceneRekey, NfcMagicSceneRekeyStateCardSearch); + nfc_magic_scene_rekey_setup_view(nfc_magic); + + // Setup and start worker + nfc_magic_worker_start( + nfc_magic->worker, + NfcMagicWorkerStateRekey, + nfc_magic->dev, + &nfc_magic->source_dev->dev_data, + nfc_magic->new_password, + nfc_magic_rekey_worker_callback, + nfc_magic); + nfc_magic_blink_start(nfc_magic); +} + +bool nfc_magic_scene_rekey_on_event(void* context, SceneManagerEvent event) { + NfcMagic* nfc_magic = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcMagicWorkerEventSuccess) { + nfc_magic->dev->password = nfc_magic->new_password; + scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneSuccess); + consumed = true; + } else if(event.event == NfcMagicWorkerEventFail) { + scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneRekeyFail); + consumed = true; + } else if(event.event == NfcMagicWorkerEventCardDetected) { + scene_manager_set_scene_state( + nfc_magic->scene_manager, NfcMagicSceneRekey, NfcMagicSceneRekeyStateCardFound); + nfc_magic_scene_rekey_setup_view(nfc_magic); + consumed = true; + } else if(event.event == NfcMagicWorkerEventNoCardDetected) { + scene_manager_set_scene_state( + nfc_magic->scene_manager, NfcMagicSceneRekey, NfcMagicSceneRekeyStateCardSearch); + nfc_magic_scene_rekey_setup_view(nfc_magic); + consumed = true; + } + } + return consumed; +} + +void nfc_magic_scene_rekey_on_exit(void* context) { + NfcMagic* nfc_magic = context; + + nfc_magic_worker_stop(nfc_magic->worker); + scene_manager_set_scene_state( + nfc_magic->scene_manager, NfcMagicSceneRekey, NfcMagicSceneRekeyStateCardSearch); + // Clear view + popup_reset(nfc_magic->popup); + + nfc_magic_blink_stop(nfc_magic); +} diff --git a/applications/external/nfc_magic/scenes/nfc_magic_scene_rekey_fail.c b/applications/external/nfc_magic/scenes/nfc_magic_scene_rekey_fail.c new file mode 100644 index 00000000000..d30ee57bcfc --- /dev/null +++ b/applications/external/nfc_magic/scenes/nfc_magic_scene_rekey_fail.c @@ -0,0 +1,50 @@ +#include "../nfc_magic_i.h" + +void nfc_magic_scene_rekey_fail_widget_callback( + GuiButtonType result, + InputType type, + void* context) { + NfcMagic* nfc_magic = context; + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, result); + } +} + +void nfc_magic_scene_rekey_fail_on_enter(void* context) { + NfcMagic* nfc_magic = context; + Widget* widget = nfc_magic->widget; + + notification_message(nfc_magic->notifications, &sequence_error); + + widget_add_icon_element(widget, 72, 17, &I_DolphinCommon_56x48); + widget_add_string_element( + widget, 7, 4, AlignLeft, AlignTop, FontPrimary, "Can't change password!"); + + widget_add_button_element( + widget, GuiButtonTypeLeft, "Finish", nfc_magic_scene_rekey_fail_widget_callback, nfc_magic); + + // Setup and start worker + view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewWidget); +} + +bool nfc_magic_scene_rekey_fail_on_event(void* context, SceneManagerEvent event) { + NfcMagic* nfc_magic = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == GuiButtonTypeLeft) { + consumed = scene_manager_search_and_switch_to_previous_scene( + nfc_magic->scene_manager, NfcMagicSceneStart); + } + } else if(event.type == SceneManagerEventTypeBack) { + consumed = scene_manager_search_and_switch_to_previous_scene( + nfc_magic->scene_manager, NfcMagicSceneStart); + } + return consumed; +} + +void nfc_magic_scene_rekey_fail_on_exit(void* context) { + NfcMagic* nfc_magic = context; + + widget_reset(nfc_magic->widget); +} diff --git a/applications/external/nfc_magic/scenes/nfc_magic_scene_start.c b/applications/external/nfc_magic/scenes/nfc_magic_scene_start.c index a70eb8accfe..b5861629e43 100644 --- a/applications/external/nfc_magic/scenes/nfc_magic_scene_start.c +++ b/applications/external/nfc_magic/scenes/nfc_magic_scene_start.c @@ -1,8 +1,7 @@ #include "../nfc_magic_i.h" enum SubmenuIndex { SubmenuIndexCheck, - SubmenuIndexWriteGen1A, - SubmenuIndexWipe, + SubmenuIndexAuthenticateGen4, }; void nfc_magic_scene_start_submenu_callback(void* context, uint32_t index) { @@ -22,12 +21,10 @@ void nfc_magic_scene_start_on_enter(void* context) { nfc_magic); submenu_add_item( submenu, - "Write Gen1A", - SubmenuIndexWriteGen1A, + "Authenticate Gen4", + SubmenuIndexAuthenticateGen4, nfc_magic_scene_start_submenu_callback, nfc_magic); - submenu_add_item( - submenu, "Wipe", SubmenuIndexWipe, nfc_magic_scene_start_submenu_callback, nfc_magic); submenu_set_selected_item( submenu, scene_manager_get_scene_state(nfc_magic->scene_manager, NfcMagicSceneStart)); @@ -40,23 +37,13 @@ bool nfc_magic_scene_start_on_event(void* context, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { if(event.event == SubmenuIndexCheck) { + nfc_magic->dev->password = MAGIC_GEN4_DEFAULT_PWD; scene_manager_set_scene_state( nfc_magic->scene_manager, NfcMagicSceneStart, SubmenuIndexCheck); scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneCheck); consumed = true; - } else if(event.event == SubmenuIndexWriteGen1A) { - // Explicitly save state in each branch so that the - // correct option is reselected if the user cancels - // loading a file. - scene_manager_set_scene_state( - nfc_magic->scene_manager, NfcMagicSceneStart, SubmenuIndexWriteGen1A); - scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneFileSelect); - consumed = true; - } else if(event.event == SubmenuIndexWipe) { - scene_manager_set_scene_state( - nfc_magic->scene_manager, NfcMagicSceneStart, SubmenuIndexWipe); - scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneWipe); - consumed = true; + } else if(event.event == SubmenuIndexAuthenticateGen4) { + scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneKeyInput); } } diff --git a/applications/external/nfc_magic/scenes/nfc_magic_scene_wipe.c b/applications/external/nfc_magic/scenes/nfc_magic_scene_wipe.c index 1ca194286ad..29640f89c6b 100644 --- a/applications/external/nfc_magic/scenes/nfc_magic_scene_wipe.c +++ b/applications/external/nfc_magic/scenes/nfc_magic_scene_wipe.c @@ -22,7 +22,12 @@ static void nfc_magic_scene_wipe_setup_view(NfcMagic* nfc_magic) { if(state == NfcMagicSceneWipeStateCardSearch) { popup_set_icon(nfc_magic->popup, 0, 8, &I_NFC_manual_60x50); popup_set_text( - nfc_magic->popup, "Apply card to\nthe back", 128, 32, AlignRight, AlignCenter); + nfc_magic->popup, + "Apply the\nsame card\nto the back", + 128, + 32, + AlignRight, + AlignCenter); } else { popup_set_icon(popup, 12, 23, &I_Loading_24); popup_set_header(popup, "Wiping\nDon't move...", 52, 32, AlignLeft, AlignCenter); @@ -42,7 +47,9 @@ void nfc_magic_scene_wipe_on_enter(void* context) { nfc_magic_worker_start( nfc_magic->worker, NfcMagicWorkerStateWipe, - &nfc_magic->nfc_dev->dev_data, + nfc_magic->dev, + &nfc_magic->source_dev->dev_data, + nfc_magic->new_password, nfc_magic_wipe_worker_callback, nfc_magic); nfc_magic_blink_start(nfc_magic); diff --git a/applications/external/nfc_magic/scenes/nfc_magic_scene_write.c b/applications/external/nfc_magic/scenes/nfc_magic_scene_write.c index c3e6f962a34..45c54557f15 100644 --- a/applications/external/nfc_magic/scenes/nfc_magic_scene_write.c +++ b/applications/external/nfc_magic/scenes/nfc_magic_scene_write.c @@ -21,7 +21,12 @@ static void nfc_magic_scene_write_setup_view(NfcMagic* nfc_magic) { if(state == NfcMagicSceneWriteStateCardSearch) { popup_set_text( - nfc_magic->popup, "Apply card to\nthe back", 128, 32, AlignRight, AlignCenter); + nfc_magic->popup, + "Apply the\nsame card\nto the back", + 128, + 32, + AlignRight, + AlignCenter); popup_set_icon(nfc_magic->popup, 0, 8, &I_NFC_manual_60x50); } else { popup_set_icon(popup, 12, 23, &I_Loading_24); @@ -42,7 +47,9 @@ void nfc_magic_scene_write_on_enter(void* context) { nfc_magic_worker_start( nfc_magic->worker, NfcMagicWorkerStateWrite, - &nfc_magic->nfc_dev->dev_data, + nfc_magic->dev, + &nfc_magic->source_dev->dev_data, + nfc_magic->new_password, nfc_magic_write_worker_callback, nfc_magic); nfc_magic_blink_start(nfc_magic); diff --git a/applications/external/nfc_magic/scenes/nfc_magic_scene_wrong_card.c b/applications/external/nfc_magic/scenes/nfc_magic_scene_wrong_card.c index 4b80896932d..857d50c1f7d 100644 --- a/applications/external/nfc_magic/scenes/nfc_magic_scene_wrong_card.c +++ b/applications/external/nfc_magic/scenes/nfc_magic_scene_wrong_card.c @@ -26,7 +26,7 @@ void nfc_magic_scene_wrong_card_on_enter(void* context) { AlignLeft, AlignTop, FontSecondary, - "Writing is supported\nonly for 4 bytes UID\nMifare Classic 1k"); + "Writing this file is\nnot supported for\nthis magic card."); widget_add_button_element( widget, GuiButtonTypeLeft, "Retry", nfc_magic_scene_wrong_card_widget_callback, nfc_magic); From 5a7cd203cdfeef9dd26fe14e89990ab0ef482a9c Mon Sep 17 00:00:00 2001 From: Yukai Li Date: Fri, 26 May 2023 06:50:59 -0600 Subject: [PATCH 572/824] nfc: Fix MFUL tearing flags read (#2669) Co-authored-by: gornekich --- lib/nfc/protocols/mifare_ultralight.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/nfc/protocols/mifare_ultralight.c b/lib/nfc/protocols/mifare_ultralight.c index 0e28c0074f1..6960bd2a180 100644 --- a/lib/nfc/protocols/mifare_ultralight.c +++ b/lib/nfc/protocols/mifare_ultralight.c @@ -702,7 +702,7 @@ bool mf_ultralight_read_tearing_flags(FuriHalNfcTxRxContext* tx_rx, MfUltralight FURI_LOG_D(TAG, "Reading tearing flags"); for(size_t i = 0; i < 3; i++) { tx_rx->tx_data[0] = MF_UL_CHECK_TEARING; - tx_rx->rx_data[1] = i; + tx_rx->tx_data[1] = i; tx_rx->tx_bits = 16; tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; if(!furi_hal_nfc_tx_rx(tx_rx, 50)) { From 4f054ed53595d901b5fa755eb3402ae1e0231fd1 Mon Sep 17 00:00:00 2001 From: hedger Date: Fri, 26 May 2023 16:57:24 +0400 Subject: [PATCH 573/824] api: added toolbox/api_lock.h (#2702) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- firmware/targets/f18/api_symbols.csv | 3 ++- firmware/targets/f7/api_symbols.csv | 1 + lib/toolbox/SConscript | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index b7abdfc0502..68248a6d2ba 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -129,6 +129,7 @@ Header,+,lib/one_wire/maxim_crc.h,, Header,+,lib/one_wire/one_wire_host.h,, Header,+,lib/one_wire/one_wire_slave.h,, Header,+,lib/print/wrappers.h,, +Header,+,lib/pulse_reader/pulse_reader.h,, Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_adc.h,, Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_bus.h,, Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_comp.h,, @@ -156,7 +157,7 @@ Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_tim.h,, Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_usart.h,, Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_utils.h,, Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_wwdg.h,, -Header,+,lib/pulse_reader/pulse_reader.h,, +Header,+,lib/toolbox/api_lock.h,, Header,+,lib/toolbox/args.h,, Header,+,lib/toolbox/crc32_calc.h,, Header,+,lib/toolbox/dir_walk.h,, diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 15c19091e9e..aebfc9072be 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -190,6 +190,7 @@ Header,+,lib/subghz/subghz_setting.h,, Header,+,lib/subghz/subghz_tx_rx_worker.h,, Header,+,lib/subghz/subghz_worker.h,, Header,+,lib/subghz/transmitter.h,, +Header,+,lib/toolbox/api_lock.h,, Header,+,lib/toolbox/args.h,, Header,+,lib/toolbox/crc32_calc.h,, Header,+,lib/toolbox/dir_walk.h,, diff --git a/lib/toolbox/SConscript b/lib/toolbox/SConscript index 4e158e30ea7..6084969c4c5 100644 --- a/lib/toolbox/SConscript +++ b/lib/toolbox/SConscript @@ -8,6 +8,7 @@ env.Append( "#/lib/toolbox", ], SDK_HEADERS=[ + File("api_lock.h"), File("manchester_decoder.h"), File("manchester_encoder.h"), File("path.h"), From cce0485e75f39c3a7ef504054f15b819deeb7e0a Mon Sep 17 00:00:00 2001 From: technobulb <84107091+technobulb@users.noreply.github.com> Date: Fri, 26 May 2023 09:12:21 -0400 Subject: [PATCH 574/824] Update ac.ir (#2701) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- assets/resources/infrared/assets/ac.ir | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/assets/resources/infrared/assets/ac.ir b/assets/resources/infrared/assets/ac.ir index cfa62f6a244..e06c95f715b 100644 --- a/assets/resources/infrared/assets/ac.ir +++ b/assets/resources/infrared/assets/ac.ir @@ -408,3 +408,29 @@ type: raw frequency: 38000 duty_cycle: 0.330000 data: 3108 3851 2062 1793 2006 1821 1103 839 2031 859 1085 829 1081 833 1079 836 1045 1911 1973 897 1015 898 1016 899 1041 871 1016 899 1014 898 1015 899 1015 899 1014 899 1041 872 1015 899 1041 872 1041 872 1015 899 1015 899 1041 873 1014 899 1041 873 1014 899 1014 1883 1975 900 3045 3886 1997 1856 1945 1857 1012 927 1945 900 1013 901 1012 901 1013 901 1012 1859 1999 901 1012 930 1012 903 1011 903 1010 903 1011 902 1012 960 1011 928 986 932 1010 903 1011 928 1015 928 985 929 985 928 1014 928 985 929 985 929 984 929 985 928 986 1915 1971 928 3017 3915 1942 1885 1943 1885 985 930 1971 929 984 930 983 930 984 930 984 1887 1942 929 983 960 983 931 982 931 983 932 981 958 985 958 956 958 984 959 954 931 983 932 981 959 955 932 982 959 954 960 982 961 983 933 955 988 955 985 929 1943 1915 958 4003 +# +# Model: Danby DAC060EB7WDB +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4402 4442 527 1629 529 549 529 1628 530 550 529 548 531 549 530 550 529 1630 528 549 530 549 529 549 530 1630 527 1629 529 1630 528 548 530 550 529 547 531 1628 530 1629 529 1628 530 1628 529 1628 530 1628 530 549 530 1628 530 1628 530 1627 531 1628 530 1627 531 1631 527 1629 529 1628 530 1627 531 1628 530 1628 530 1629 529 1627 531 1627 531 1628 529 1627 531 1627 531 1629 529 1627 530 549 529 549 530 549 530 1628 529 1627 530 5234 4401 4440 530 550 528 1628 529 549 530 1628 529 1629 528 1626 531 1628 529 550 529 1628 530 1627 531 1629 529 549 529 548 530 549 530 1628 529 1628 530 1627 530 547 532 547 532 547 531 548 531 548 530 548 531 1626 531 549 530 547 531 547 531 548 530 548 530 549 530 548 531 546 532 578 500 547 532 548 531 548 531 548 530 548 531 548 530 548 531 547 531 547 532 547 531 1627 531 1627 530 1626 532 547 531 547 532 +# +name: Dh +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4454 4387 586 1573 585 494 584 1571 586 494 584 493 585 524 554 493 531 1626 586 1573 585 494 585 492 586 492 586 494 585 493 585 493 586 1571 586 493 585 1572 531 1627 530 549 584 494 586 1571 587 1572 529 1628 529 1628 530 1627 530 1626 532 1627 531 1626 532 1628 529 1627 531 1626 531 1627 531 1627 587 1569 532 1625 533 1626 532 1626 532 1627 531 1627 530 548 531 1626 532 1627 531 548 531 1627 531 548 531 546 532 547 531 5233 4401 4443 530 548 530 1627 530 547 531 1627 531 1626 531 1626 531 1627 530 549 530 548 530 1627 531 1627 531 1627 530 1627 531 1627 531 1626 531 548 530 1627 530 547 532 547 532 1627 531 1627 532 546 531 547 531 548 530 548 530 548 531 548 531 548 530 548 530 549 530 548 531 548 531 548 530 547 532 549 529 548 530 548 531 547 532 548 530 548 531 1627 530 548 530 548 531 1626 533 546 531 1627 530 1627 532 1626 530 +# +name: Cool_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4492 4354 619 1537 619 459 621 1537 622 455 623 456 623 457 622 456 623 1535 622 1536 622 458 621 456 623 1536 621 1535 623 456 622 456 623 457 621 456 531 1627 621 1536 622 456 622 457 622 455 623 458 621 458 621 1537 621 1536 620 1539 618 1538 531 1628 531 1627 530 1628 530 1628 530 1627 531 1628 530 1628 530 1628 531 1627 530 1629 529 1628 529 1628 530 549 530 1627 531 1628 530 1628 530 1627 586 494 530 1627 530 549 530 5232 4400 4443 586 492 587 1571 587 493 585 1572 530 1628 586 1572 586 1572 586 492 587 493 586 1572 586 1571 586 492 587 493 585 1572 531 1627 585 1573 585 1572 585 492 586 494 585 1572 586 1571 531 1627 531 1628 530 1627 587 491 531 548 587 492 530 548 530 547 532 548 531 547 532 547 531 548 531 547 532 547 531 548 588 491 530 547 589 490 531 547 532 1626 532 548 531 548 531 547 532 547 532 1627 531 548 531 1626 532 +# +name: Cool_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4401 4441 528 1629 529 550 528 1628 529 551 528 550 528 551 527 551 528 1629 529 1629 529 550 529 1630 528 551 528 549 530 550 529 551 528 549 529 550 529 1629 529 1628 530 549 530 1629 529 550 529 1628 529 1629 529 1631 527 1628 530 1628 529 1629 528 1628 530 1629 529 1629 529 1629 529 1629 528 1629 529 1629 529 1630 528 1629 529 1629 529 1628 529 1629 528 551 528 1629 529 550 529 550 530 548 529 1631 527 551 528 1629 529 5235 4402 4439 530 550 528 1629 529 549 530 1628 529 1629 529 1628 530 1629 529 549 530 550 529 1628 530 552 526 1628 529 1628 530 1628 530 1627 531 1628 529 1629 528 551 528 550 529 1628 530 550 528 1628 529 549 529 550 528 550 529 549 530 548 530 551 528 550 528 578 500 550 529 550 529 551 527 549 530 549 529 549 529 550 528 548 530 550 528 549 529 1629 528 550 529 1630 528 1628 530 1628 530 549 530 1628 529 549 529 +# From 5f52382098855e05b4239966cbe274551abdf538 Mon Sep 17 00:00:00 2001 From: Yukai Li Date: Fri, 26 May 2023 07:19:10 -0600 Subject: [PATCH 575/824] nfc: Mifare Ultralight C detection (#2668) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * nfc: Add Mifare Ultralight C detection * nfc: Add display name for MFUL C and hide menu items MFUL C unlock and emulation currently not supported, so hide from menu if current card is MFUL C * nfc: Also check response when probing 3DES auth * nfc: Hide emulate option in saved menu for MFUL if not supported * nfc: Remove unlock options from saved menu if Ultralight C Co-authored-by: gornekich Co-authored-by: あく --- .../nfc/scenes/nfc_scene_mf_ultralight_menu.c | 16 ++++---- .../main/nfc/scenes/nfc_scene_saved_menu.c | 4 +- firmware/targets/f7/api_symbols.csv | 1 + lib/nfc/nfc_types.c | 2 + lib/nfc/protocols/mifare_ultralight.c | 40 ++++++++++++++++--- lib/nfc/protocols/mifare_ultralight.h | 7 +++- 6 files changed, 56 insertions(+), 14 deletions(-) diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_menu.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_menu.c index c511e9dcbfe..e7a494d273a 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_menu.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_menu.c @@ -19,7 +19,7 @@ void nfc_scene_mf_ultralight_menu_on_enter(void* context) { Submenu* submenu = nfc->submenu; MfUltralightData* data = &nfc->dev->dev_data.mf_ul_data; - if(!mf_ul_is_full_capture(data)) { + if(!mf_ul_is_full_capture(data) && data->type != MfUltralightTypeULC) { submenu_add_item( submenu, "Unlock", @@ -29,12 +29,14 @@ void nfc_scene_mf_ultralight_menu_on_enter(void* context) { } submenu_add_item( submenu, "Save", SubmenuIndexSave, nfc_scene_mf_ultralight_menu_submenu_callback, nfc); - submenu_add_item( - submenu, - "Emulate", - SubmenuIndexEmulate, - nfc_scene_mf_ultralight_menu_submenu_callback, - nfc); + if(mf_ul_emulation_supported(data)) { + submenu_add_item( + submenu, + "Emulate", + SubmenuIndexEmulate, + nfc_scene_mf_ultralight_menu_submenu_callback, + nfc); + } submenu_add_item( submenu, "Info", SubmenuIndexInfo, nfc_scene_mf_ultralight_menu_submenu_callback, nfc); diff --git a/applications/main/nfc/scenes/nfc_scene_saved_menu.c b/applications/main/nfc/scenes/nfc_scene_saved_menu.c index ba1f9653984..e45dc4eb7dd 100644 --- a/applications/main/nfc/scenes/nfc_scene_saved_menu.c +++ b/applications/main/nfc/scenes/nfc_scene_saved_menu.c @@ -42,7 +42,8 @@ void nfc_scene_saved_menu_on_enter(void* context) { nfc); } } else if( - nfc->dev->format == NfcDeviceSaveFormatMifareUl || + (nfc->dev->format == NfcDeviceSaveFormatMifareUl && + mf_ul_emulation_supported(&nfc->dev->dev_data.mf_ul_data)) || nfc->dev->format == NfcDeviceSaveFormatMifareClassic) { submenu_add_item( submenu, "Emulate", SubmenuIndexEmulate, nfc_scene_saved_menu_submenu_callback, nfc); @@ -72,6 +73,7 @@ void nfc_scene_saved_menu_on_enter(void* context) { submenu_add_item( submenu, "Info", SubmenuIndexInfo, nfc_scene_saved_menu_submenu_callback, nfc); if(nfc->dev->format == NfcDeviceSaveFormatMifareUl && + nfc->dev->dev_data.mf_ul_data.type != MfUltralightTypeULC && !mf_ul_is_full_capture(&nfc->dev->dev_data.mf_ul_data)) { submenu_add_item( submenu, diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index aebfc9072be..ccbaa531765 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -2013,6 +2013,7 @@ Function,-,mf_df_prepare_read_records,uint16_t,"uint8_t*, uint8_t, uint32_t, uin Function,-,mf_df_prepare_select_application,uint16_t,"uint8_t*, uint8_t[3]" Function,-,mf_df_read_card,_Bool,"FuriHalNfcTxRxContext*, MifareDesfireData*" Function,-,mf_ul_check_card_type,_Bool,"uint8_t, uint8_t, uint8_t" +Function,-,mf_ul_emulation_supported,_Bool,MfUltralightData* Function,-,mf_ul_is_full_capture,_Bool,MfUltralightData* Function,-,mf_ul_prepare_emulation,void,"MfUltralightEmulator*, MfUltralightData*" Function,-,mf_ul_prepare_emulation_response,_Bool,"uint8_t*, uint16_t, uint8_t*, uint16_t*, uint32_t*, void*" diff --git a/lib/nfc/nfc_types.c b/lib/nfc/nfc_types.c index 02ca85580db..96b92640f0e 100644 --- a/lib/nfc/nfc_types.c +++ b/lib/nfc/nfc_types.c @@ -45,6 +45,8 @@ const char* nfc_mf_ul_type(MfUltralightType type, bool full_name) { return "NTAG I2C Plus 2K"; } else if(type == MfUltralightTypeNTAG203) { return "NTAG203"; + } else if(type == MfUltralightTypeULC) { + return "Mifare Ultralight C"; } else if(type == MfUltralightTypeUL11 && full_name) { return "Mifare Ultralight 11"; } else if(type == MfUltralightTypeUL21 && full_name) { diff --git a/lib/nfc/protocols/mifare_ultralight.c b/lib/nfc/protocols/mifare_ultralight.c index 6960bd2a180..266b6e2e2e4 100644 --- a/lib/nfc/protocols/mifare_ultralight.c +++ b/lib/nfc/protocols/mifare_ultralight.c @@ -79,6 +79,8 @@ static MfUltralightFeatures mf_ul_get_features(MfUltralightType type) { MfUltralightSupportSectorSelect; case MfUltralightTypeNTAG203: return MfUltralightSupportCompatWrite | MfUltralightSupportCounterInMemory; + case MfUltralightTypeULC: + return MfUltralightSupportCompatWrite | MfUltralightSupport3DesAuth; default: // Assumed original MFUL 512-bit return MfUltralightSupportCompatWrite; @@ -95,6 +97,11 @@ static void mf_ul_set_version_ntag203(MfUltralightReader* reader, MfUltralightDa reader->pages_to_read = 42; } +static void mf_ul_set_version_ulc(MfUltralightReader* reader, MfUltralightData* data) { + data->type = MfUltralightTypeULC; + reader->pages_to_read = 48; +} + bool mf_ultralight_read_version( FuriHalNfcTxRxContext* tx_rx, MfUltralightReader* reader, @@ -175,7 +182,7 @@ bool mf_ultralight_authenticate(FuriHalNfcTxRxContext* tx_rx, uint32_t key, uint do { FURI_LOG_D(TAG, "Authenticating"); - tx_rx->tx_data[0] = MF_UL_AUTH; + tx_rx->tx_data[0] = MF_UL_PWD_AUTH; nfc_util_num2bytes(key, 4, &tx_rx->tx_data[1]); tx_rx->tx_bits = 40; tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; @@ -716,6 +723,21 @@ bool mf_ultralight_read_tearing_flags(FuriHalNfcTxRxContext* tx_rx, MfUltralight return flag_read == 2; } +static bool mf_ul_probe_3des_auth(FuriHalNfcTxRxContext* tx_rx) { + tx_rx->tx_data[0] = MF_UL_AUTHENTICATE_1; + tx_rx->tx_data[1] = 0; + tx_rx->tx_bits = 16; + tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; + bool rc = furi_hal_nfc_tx_rx(tx_rx, 50) && tx_rx->rx_bits == 9 * 8 && + tx_rx->rx_data[0] == 0xAF; + + // Reset just in case, we're not going to finish authenticating and need to if tag doesn't support auth + furi_hal_nfc_sleep(); + furi_hal_nfc_activate_nfca(300, NULL); + + return rc; +} + bool mf_ul_read_card( FuriHalNfcTxRxContext* tx_rx, MfUltralightReader* reader, @@ -733,16 +755,20 @@ bool mf_ul_read_card( mf_ultralight_read_signature(tx_rx, data); } } else { - // No GET_VERSION command, check for NTAG203 by reading last page (41) uint8_t dummy[16]; - if(mf_ultralight_read_pages_direct(tx_rx, 41, dummy)) { + // No GET_VERSION command, check if AUTHENTICATE command available (detect UL C). + if(mf_ul_probe_3des_auth(tx_rx)) { + mf_ul_set_version_ulc(reader, data); + } else if(mf_ultralight_read_pages_direct(tx_rx, 41, dummy)) { + // No AUTHENTICATE, check for NTAG203 by reading last page (41) mf_ul_set_version_ntag203(reader, data); - reader->supported_features = mf_ul_get_features(data->type); } else { // We're really an original Mifare Ultralight, reset tag for safety furi_hal_nfc_sleep(); furi_hal_nfc_activate_nfca(300, NULL); } + + reader->supported_features = mf_ul_get_features(data->type); } card_read = mf_ultralight_read_pages(tx_rx, reader, data); @@ -1228,6 +1254,10 @@ static void mf_ul_emulate_write( emulator->data_changed = true; } +bool mf_ul_emulation_supported(MfUltralightData* data) { + return data->type != MfUltralightTypeULC; +} + void mf_ul_reset_emulation(MfUltralightEmulator* emulator, bool is_power_cycle) { emulator->comp_write_cmd_started = false; emulator->sector_select_cmd_started = false; @@ -1732,7 +1762,7 @@ bool mf_ul_prepare_emulation_response( } } } - } else if(cmd == MF_UL_AUTH) { + } else if(cmd == MF_UL_PWD_AUTH) { if(emulator->supported_features & MfUltralightSupportAuth) { if(buff_rx_len == (1 + 4) * 8) { // Record password sent by PCD diff --git a/lib/nfc/protocols/mifare_ultralight.h b/lib/nfc/protocols/mifare_ultralight.h index 4ab22e89cb8..d444fa7983e 100644 --- a/lib/nfc/protocols/mifare_ultralight.h +++ b/lib/nfc/protocols/mifare_ultralight.h @@ -16,7 +16,8 @@ #define MF_UL_COMP_WRITE (0xA0) #define MF_UL_READ_CNT (0x39) #define MF_UL_INC_CNT (0xA5) -#define MF_UL_AUTH (0x1B) +#define MF_UL_AUTHENTICATE_1 (0x1A) +#define MF_UL_PWD_AUTH (0x1B) #define MF_UL_READ_SIG (0x3C) #define MF_UL_CHECK_TEARING (0x3E) #define MF_UL_READ_VCSL (0x4B) @@ -41,6 +42,7 @@ typedef enum { typedef enum { MfUltralightTypeUnknown, MfUltralightTypeNTAG203, + MfUltralightTypeULC, // Below have config pages and GET_VERSION support MfUltralightTypeUL11, MfUltralightTypeUL21, @@ -77,6 +79,7 @@ typedef enum { MfUltralightSupportAsciiMirror = 1 << 11, // NTAG203 counter that's in memory rather than through a command MfUltralightSupportCounterInMemory = 1 << 12, + MfUltralightSupport3DesAuth = 1 << 13, } MfUltralightFeatures; typedef enum { @@ -237,6 +240,8 @@ bool mf_ul_read_card( MfUltralightReader* reader, MfUltralightData* data); +bool mf_ul_emulation_supported(MfUltralightData* data); + void mf_ul_reset_emulation(MfUltralightEmulator* emulator, bool is_power_cycle); void mf_ul_prepare_emulation(MfUltralightEmulator* emulator, MfUltralightData* data); From 44426c7612f4dff67f9e2faf8bc3cd3d15613814 Mon Sep 17 00:00:00 2001 From: Sebastian Mauer Date: Mon, 29 May 2023 10:19:42 +0200 Subject: [PATCH 576/824] [LRFID] Add support for Nexkey/Nexwatch (#2680) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [LRFID] Add support for Nexkey/Nexwatch * Update protocol_nexwatch.c: Remove unnecessary check Co-authored-by: SG Co-authored-by: あく --- lib/lfrfid/protocols/lfrfid_protocols.c | 4 +- lib/lfrfid/protocols/lfrfid_protocols.h | 3 +- lib/lfrfid/protocols/protocol_nexwatch.c | 323 +++++++++++++++++++++++ lib/lfrfid/protocols/protocol_nexwatch.h | 4 + 4 files changed, 332 insertions(+), 2 deletions(-) create mode 100644 lib/lfrfid/protocols/protocol_nexwatch.c create mode 100644 lib/lfrfid/protocols/protocol_nexwatch.h diff --git a/lib/lfrfid/protocols/lfrfid_protocols.c b/lib/lfrfid/protocols/lfrfid_protocols.c index 2c1f0ad97cd..f07218d7f30 100644 --- a/lib/lfrfid/protocols/lfrfid_protocols.c +++ b/lib/lfrfid/protocols/lfrfid_protocols.c @@ -16,6 +16,7 @@ #include "protocol_pac_stanley.h" #include "protocol_keri.h" #include "protocol_gallagher.h" +#include "protocol_nexwatch.h" const ProtocolBase* lfrfid_protocols[] = { [LFRFIDProtocolEM4100] = &protocol_em4100, @@ -35,4 +36,5 @@ const ProtocolBase* lfrfid_protocols[] = { [LFRFIDProtocolPACStanley] = &protocol_pac_stanley, [LFRFIDProtocolKeri] = &protocol_keri, [LFRFIDProtocolGallagher] = &protocol_gallagher, -}; \ No newline at end of file + [LFRFIDProtocolNexwatch] = &protocol_nexwatch, +}; diff --git a/lib/lfrfid/protocols/lfrfid_protocols.h b/lib/lfrfid/protocols/lfrfid_protocols.h index 848f003a31e..0cb7cbc8440 100644 --- a/lib/lfrfid/protocols/lfrfid_protocols.h +++ b/lib/lfrfid/protocols/lfrfid_protocols.h @@ -25,6 +25,7 @@ typedef enum { LFRFIDProtocolPACStanley, LFRFIDProtocolKeri, LFRFIDProtocolGallagher, + LFRFIDProtocolNexwatch, LFRFIDProtocolMax, } LFRFIDProtocol; @@ -39,4 +40,4 @@ typedef struct { union { LFRFIDT5577 t5577; }; -} LFRFIDWriteRequest; \ No newline at end of file +} LFRFIDWriteRequest; diff --git a/lib/lfrfid/protocols/protocol_nexwatch.c b/lib/lfrfid/protocols/protocol_nexwatch.c new file mode 100644 index 00000000000..3bbbb42f503 --- /dev/null +++ b/lib/lfrfid/protocols/protocol_nexwatch.c @@ -0,0 +1,323 @@ +#include +#include +#include +#include "lfrfid_protocols.h" + +#define NEXWATCH_PREAMBLE_BIT_SIZE (8) +#define NEXWATCH_PREAMBLE_DATA_SIZE (1) + +#define NEXWATCH_ENCODED_BIT_SIZE (96) +#define NEXWATCH_ENCODED_DATA_SIZE ((NEXWATCH_ENCODED_BIT_SIZE) / 8) + +#define NEXWATCH_DECODED_BIT_SIZE (NEXWATCH_DECODED_DATA_SIZE * 8) +#define NEXWATCH_DECODED_DATA_SIZE (8) + +#define NEXWATCH_US_PER_BIT (255) +#define NEXWATCH_ENCODER_PULSES_PER_BIT (16) + +typedef struct { + uint8_t magic; + char desc[13]; + uint8_t chk; +} ProtocolNexwatchMagic; + +ProtocolNexwatchMagic magic_items[] = { + {0xBE, "Quadrakey", 0}, + {0x88, "Nexkey", 0}, + {0x86, "Honeywell", 0}}; + +typedef struct { + uint8_t data_index; + uint8_t bit_clock_index; + bool last_bit; + bool current_polarity; + bool pulse_phase; +} ProtocolNexwatchEncoder; + +typedef struct { + uint8_t encoded_data[NEXWATCH_ENCODED_DATA_SIZE]; + uint8_t negative_encoded_data[NEXWATCH_ENCODED_DATA_SIZE]; + uint8_t corrupted_encoded_data[NEXWATCH_ENCODED_DATA_SIZE]; + uint8_t corrupted_negative_encoded_data[NEXWATCH_ENCODED_DATA_SIZE]; + + uint8_t data[NEXWATCH_DECODED_DATA_SIZE]; + ProtocolNexwatchEncoder encoder; +} ProtocolNexwatch; + +ProtocolNexwatch* protocol_nexwatch_alloc(void) { + ProtocolNexwatch* protocol = malloc(sizeof(ProtocolNexwatch)); + return protocol; +}; + +void protocol_nexwatch_free(ProtocolNexwatch* protocol) { + free(protocol); +}; + +uint8_t* protocol_nexwatch_get_data(ProtocolNexwatch* protocol) { + return protocol->data; +}; + +void protocol_nexwatch_decoder_start(ProtocolNexwatch* protocol) { + memset(protocol->encoded_data, 0, NEXWATCH_ENCODED_DATA_SIZE); + memset(protocol->negative_encoded_data, 0, NEXWATCH_ENCODED_DATA_SIZE); + memset(protocol->corrupted_encoded_data, 0, NEXWATCH_ENCODED_DATA_SIZE); + memset(protocol->corrupted_negative_encoded_data, 0, NEXWATCH_ENCODED_DATA_SIZE); +}; + +static bool protocol_nexwatch_check_preamble(uint8_t* data, size_t bit_index) { + // 01010110 + if(bit_lib_get_bits(data, bit_index, 8) != 0b01010110) return false; + return true; +} + +static uint8_t protocol_nexwatch_parity_swap(uint8_t parity) { + uint8_t a = (((parity >> 3) & 1)); + a |= (((parity >> 1) & 1) << 1); + a |= (((parity >> 2) & 1) << 2); + a |= ((parity & 1) << 3); + return a; +} + +static uint8_t protocol_nexwatch_parity(const uint8_t hexid[5]) { + uint8_t p = 0; + for(uint8_t i = 0; i < 5; i++) { + p ^= ((hexid[i]) & 0xF0) >> 4; + p ^= ((hexid[i]) & 0x0F); + } + return protocol_nexwatch_parity_swap(p); +} + +static uint8_t protocol_nexwatch_checksum(uint8_t magic, uint32_t id, uint8_t parity) { + uint8_t a = ((id >> 24) & 0xFF); + a -= ((id >> 16) & 0xFF); + a -= ((id >> 8) & 0xFF); + a -= (id & 0xFF); + a -= magic; + a -= (bit_lib_reverse_8_fast(parity) >> 4); + return bit_lib_reverse_8_fast(a); +} + +static bool protocol_nexwatch_can_be_decoded(uint8_t* data) { + if(!protocol_nexwatch_check_preamble(data, 0)) return false; + + // Check for reserved word (32-bit) + if(bit_lib_get_bits_32(data, 8, 32) != 0) { + return false; + } + + uint8_t parity = bit_lib_get_bits(data, 76, 4); + + // parity check + // from 32b hex id, 4b mode + uint8_t hex[5] = {0}; + for(uint8_t i = 0; i < 5; i++) { + hex[i] = bit_lib_get_bits(data, 40 + (i * 8), 8); + } + //mode is only 4 bits. + hex[4] &= 0xf0; + uint8_t calc_parity = protocol_nexwatch_parity(hex); + + if(calc_parity != parity) { + return false; + } + + return true; +} + +static bool protocol_nexwatch_decoder_feed_internal(bool polarity, uint32_t time, uint8_t* data) { + time += (NEXWATCH_US_PER_BIT / 2); + + size_t bit_count = (time / NEXWATCH_US_PER_BIT); + bool result = false; + + if(bit_count < NEXWATCH_ENCODED_BIT_SIZE) { + for(size_t i = 0; i < bit_count; i++) { + bit_lib_push_bit(data, NEXWATCH_ENCODED_DATA_SIZE, polarity); + if(protocol_nexwatch_can_be_decoded(data)) { + result = true; + break; + } + } + } + + return result; +} + +static void protocol_nexwatch_descramble(uint32_t* id, uint32_t* scrambled) { + // 255 = Not used/Unknown other values are the bit offset in the ID/FC values + const uint8_t hex_2_id[] = {31, 27, 23, 19, 15, 11, 7, 3, 30, 26, 22, 18, 14, 10, 6, 2, + 29, 25, 21, 17, 13, 9, 5, 1, 28, 24, 20, 16, 12, 8, 4, 0}; + + *id = 0; + for(uint8_t idx = 0; idx < 32; idx++) { + bool bit_state = (*scrambled >> hex_2_id[idx]) & 1; + *id |= (bit_state << (31 - idx)); + } +} + +static void protocol_nexwatch_decoder_save(uint8_t* data_to, const uint8_t* data_from) { + uint32_t id = bit_lib_get_bits_32(data_from, 40, 32); + data_to[4] = (uint8_t)id; + data_to[3] = (uint8_t)(id >>= 8); + data_to[2] = (uint8_t)(id >>= 8); + data_to[1] = (uint8_t)(id >>= 8); + data_to[0] = (uint8_t)(id >>= 8); + uint32_t check = bit_lib_get_bits_32(data_from, 72, 24); + data_to[7] = (uint8_t)check; + data_to[6] = (uint8_t)(check >>= 8); + data_to[5] = (uint8_t)(check >>= 8); +} + +bool protocol_nexwatch_decoder_feed(ProtocolNexwatch* protocol, bool level, uint32_t duration) { + bool result = false; + + if(duration > (NEXWATCH_US_PER_BIT / 2)) { + if(protocol_nexwatch_decoder_feed_internal(level, duration, protocol->encoded_data)) { + protocol_nexwatch_decoder_save(protocol->data, protocol->encoded_data); + result = true; + return result; + } + + if(protocol_nexwatch_decoder_feed_internal( + !level, duration, protocol->negative_encoded_data)) { + protocol_nexwatch_decoder_save(protocol->data, protocol->negative_encoded_data); + result = true; + return result; + } + } + + if(duration > (NEXWATCH_US_PER_BIT / 4)) { + // Try to decode wrong phase synced data + if(level) { + duration += 120; + } else { + if(duration > 120) { + duration -= 120; + } + } + + if(protocol_nexwatch_decoder_feed_internal( + level, duration, protocol->corrupted_encoded_data)) { + protocol_nexwatch_decoder_save(protocol->data, protocol->corrupted_encoded_data); + + result = true; + return result; + } + + if(protocol_nexwatch_decoder_feed_internal( + !level, duration, protocol->corrupted_negative_encoded_data)) { + protocol_nexwatch_decoder_save( + protocol->data, protocol->corrupted_negative_encoded_data); + + result = true; + return result; + } + } + + return result; +}; + +bool protocol_nexwatch_encoder_start(ProtocolNexwatch* protocol) { + memset(protocol->encoded_data, 0, NEXWATCH_ENCODED_DATA_SIZE); + *(uint32_t*)&protocol->encoded_data[0] = 0b00000000000000000000000001010110; + bit_lib_copy_bits(protocol->encoded_data, 32, 32, protocol->data, 0); + bit_lib_copy_bits(protocol->encoded_data, 64, 32, protocol->data, 32); + + protocol->encoder.last_bit = + bit_lib_get_bit(protocol->encoded_data, NEXWATCH_ENCODED_BIT_SIZE - 1); + protocol->encoder.data_index = 0; + protocol->encoder.current_polarity = true; + protocol->encoder.pulse_phase = true; + protocol->encoder.bit_clock_index = 0; + + return true; +}; + +LevelDuration protocol_nexwatch_encoder_yield(ProtocolNexwatch* protocol) { + LevelDuration level_duration; + ProtocolNexwatchEncoder* encoder = &protocol->encoder; + + if(encoder->pulse_phase) { + level_duration = level_duration_make(encoder->current_polarity, 1); + encoder->pulse_phase = false; + } else { + level_duration = level_duration_make(!encoder->current_polarity, 1); + encoder->pulse_phase = true; + + encoder->bit_clock_index++; + if(encoder->bit_clock_index >= NEXWATCH_ENCODER_PULSES_PER_BIT) { + encoder->bit_clock_index = 0; + + bool current_bit = bit_lib_get_bit(protocol->encoded_data, encoder->data_index); + + if(current_bit != encoder->last_bit) { + encoder->current_polarity = !encoder->current_polarity; + } + + encoder->last_bit = current_bit; + + bit_lib_increment_index(encoder->data_index, NEXWATCH_ENCODED_BIT_SIZE); + } + } + + return level_duration; +}; + +void protocol_nexwatch_render_data(ProtocolNexwatch* protocol, FuriString* result) { + uint32_t id = 0; + uint32_t scrambled = bit_lib_get_bits_32(protocol->data, 8, 32); + protocol_nexwatch_descramble(&id, &scrambled); + + uint8_t m_idx; + uint8_t mode = bit_lib_get_bits(protocol->data, 40, 4); + uint8_t parity = bit_lib_get_bits(protocol->data, 44, 4); + uint8_t chk = bit_lib_get_bits(protocol->data, 48, 8); + for(m_idx = 0; m_idx < 3; m_idx++) { + magic_items[m_idx].chk = protocol_nexwatch_checksum(magic_items[m_idx].magic, id, parity); + if(magic_items[m_idx].chk == chk) { + break; + } + } + furi_string_printf(result, "ID: %lu, M:%u\r\nType: %s\r\n", id, mode, magic_items[m_idx].desc); +} + +bool protocol_nexwatch_write_data(ProtocolNexwatch* protocol, void* data) { + LFRFIDWriteRequest* request = (LFRFIDWriteRequest*)data; + bool result = false; + + protocol_nexwatch_encoder_start(protocol); + if(request->write_type == LFRFIDWriteTypeT5577) { + request->t5577.block[0] = LFRFID_T5577_MODULATION_PSK1 | LFRFID_T5577_BITRATE_RF_32 | + (3 << LFRFID_T5577_MAXBLOCK_SHIFT); + request->t5577.block[1] = bit_lib_get_bits_32(protocol->encoded_data, 0, 32); + request->t5577.block[2] = bit_lib_get_bits_32(protocol->encoded_data, 32, 32); + request->t5577.block[3] = bit_lib_get_bits_32(protocol->encoded_data, 64, 32); + request->t5577.blocks_to_write = 4; + result = true; + } + return result; +}; + +const ProtocolBase protocol_nexwatch = { + .name = "Nexwatch", + .manufacturer = "Honeywell", + .data_size = NEXWATCH_DECODED_DATA_SIZE, + .features = LFRFIDFeaturePSK, + .validate_count = 6, + .alloc = (ProtocolAlloc)protocol_nexwatch_alloc, + .free = (ProtocolFree)protocol_nexwatch_free, + .get_data = (ProtocolGetData)protocol_nexwatch_get_data, + .decoder = + { + .start = (ProtocolDecoderStart)protocol_nexwatch_decoder_start, + .feed = (ProtocolDecoderFeed)protocol_nexwatch_decoder_feed, + }, + .encoder = + { + .start = (ProtocolEncoderStart)protocol_nexwatch_encoder_start, + .yield = (ProtocolEncoderYield)protocol_nexwatch_encoder_yield, + }, + .render_data = (ProtocolRenderData)protocol_nexwatch_render_data, + .render_brief_data = (ProtocolRenderData)protocol_nexwatch_render_data, + .write_data = (ProtocolWriteData)protocol_nexwatch_write_data, +}; diff --git a/lib/lfrfid/protocols/protocol_nexwatch.h b/lib/lfrfid/protocols/protocol_nexwatch.h new file mode 100644 index 00000000000..0872ca7dcdd --- /dev/null +++ b/lib/lfrfid/protocols/protocol_nexwatch.h @@ -0,0 +1,4 @@ +#pragma once +#include + +extern const ProtocolBase protocol_nexwatch; From f9390e0cbdf62e4b1c6309adff4940e655633b66 Mon Sep 17 00:00:00 2001 From: minchogaydarov <134236905+minchogaydarov@users.noreply.github.com> Date: Mon, 29 May 2023 10:04:38 +0100 Subject: [PATCH 577/824] Add Carrier 42QHB12D8S (#2707) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- assets/resources/infrared/assets/ac.ir | 36 ++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/assets/resources/infrared/assets/ac.ir b/assets/resources/infrared/assets/ac.ir index e06c95f715b..142c49243c3 100644 --- a/assets/resources/infrared/assets/ac.ir +++ b/assets/resources/infrared/assets/ac.ir @@ -434,3 +434,39 @@ frequency: 38000 duty_cycle: 0.330000 data: 4401 4441 528 1629 529 550 528 1628 529 551 528 550 528 551 527 551 528 1629 529 1629 529 550 529 1630 528 551 528 549 530 550 529 551 528 549 529 550 529 1629 529 1628 530 549 530 1629 529 550 529 1628 529 1629 529 1631 527 1628 530 1628 529 1629 528 1628 530 1629 529 1629 529 1629 529 1629 528 1629 529 1629 529 1630 528 1629 529 1629 529 1628 529 1629 528 551 528 1629 529 550 529 550 530 548 529 1631 527 551 528 1629 529 5235 4402 4439 530 550 528 1629 529 549 530 1628 529 1629 529 1628 530 1629 529 549 530 550 529 1628 530 552 526 1628 529 1628 530 1628 530 1627 531 1628 529 1629 528 551 528 550 529 1628 530 550 528 1628 529 549 529 550 528 550 529 549 530 548 530 551 528 550 528 578 500 550 529 550 529 551 527 549 530 549 529 549 529 550 528 548 530 550 528 549 529 1629 528 550 529 1630 528 1628 530 1628 530 549 530 1628 529 549 529 # +# Model: Carrier 42QHB12D8S +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4467 4363 599 1556 599 478 599 1556 599 1558 597 505 572 505 572 1583 571 505 572 506 624 1531 653 423 651 426 624 1530 622 1532 572 505 572 1583 571 506 571 1584 571 1583 571 1584 571 1584 571 507 570 1585 570 1585 570 1585 570 508 569 508 569 508 592 485 570 1585 593 484 570 508 569 1586 569 1586 592 1563 593 485 569 508 592 485 592 485 570 508 569 508 570 508 569 508 569 1586 569 1586 569 1586 569 1586 569 1586 569 5187 4461 4371 568 1586 569 508 593 1562 593 1563 569 509 591 486 591 1563 569 508 592 486 592 1563 592 485 592 486 591 1563 592 1563 591 486 592 1564 591 486 592 1563 593 1563 591 1563 592 1564 592 485 592 1563 592 1563 592 1564 591 486 591 486 592 485 592 485 592 1563 592 486 591 486 592 1564 591 1563 592 1563 592 486 592 485 592 486 591 486 592 485 592 486 591 486 591 486 591 1563 592 1563 593 1563 591 1564 591 1563 591 +# +name: Dh +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4440 4390 571 1583 572 505 572 1583 596 1559 571 505 572 505 572 1583 596 481 596 481 597 1559 625 451 654 423 625 1529 595 1560 596 480 572 1583 572 505 596 482 594 483 571 1583 571 1584 571 1584 571 1584 571 1585 569 1585 593 1562 594 1561 593 485 569 508 593 484 592 485 593 484 592 485 592 1563 569 508 592 1562 592 485 570 1586 592 485 592 486 569 1586 592 485 569 1585 570 508 569 1586 569 508 569 1585 570 1586 569 5186 4438 4393 569 1585 593 485 592 1562 569 1585 569 508 569 508 569 1585 591 486 593 484 591 1563 591 486 591 486 590 1564 569 1585 569 508 593 1562 592 486 592 485 569 508 591 1563 592 1562 569 1586 569 1586 591 1563 592 1563 590 1564 591 1563 592 486 569 508 569 508 569 508 569 508 592 486 592 1563 568 508 593 1563 591 486 592 1563 569 508 592 486 592 1563 591 486 592 1563 592 485 592 1563 592 486 592 1563 591 1563 592 +# +name: Cool_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4465 4365 598 1557 598 479 598 1557 598 1557 598 479 598 479 598 1556 599 479 598 507 593 1562 652 424 624 452 595 1559 595 1560 594 483 571 1584 594 1561 593 484 593 1562 593 1562 593 1562 593 1562 593 1562 593 1563 592 485 592 1562 593 484 593 485 592 485 592 485 592 485 592 485 592 485 592 485 592 485 592 485 592 485 592 485 592 485 592 485 592 1562 592 1563 592 1562 593 1562 592 1563 592 1563 592 1562 592 1563 592 5165 4439 4394 569 1586 592 485 569 1586 569 1586 569 508 569 508 593 1562 592 485 570 508 592 1563 592 485 594 484 569 1586 569 1586 569 508 569 1586 592 1563 569 508 592 1563 569 1586 592 1563 592 1563 569 1586 569 1585 569 508 569 1586 569 508 569 508 569 508 569 508 592 485 569 508 592 485 569 508 593 485 569 508 569 508 569 508 593 484 570 508 569 1586 593 1562 570 1586 569 1586 592 1563 569 1586 592 1563 592 1563 592 +# +name: Cool_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4465 4364 599 1556 599 479 598 1556 599 1556 599 478 599 479 598 1559 596 505 572 506 571 1584 653 424 624 452 571 1583 572 1583 571 506 571 1583 571 1584 571 506 571 1584 571 1585 570 1585 570 1585 592 1563 592 1562 593 485 592 1563 592 485 593 485 592 485 592 485 592 485 592 485 593 485 592 1563 592 485 592 1563 592 485 592 485 592 485 592 485 592 1563 592 485 592 1563 592 485 592 1563 592 1563 592 1563 592 1563 592 5164 4460 4372 592 1563 592 485 593 1563 592 1563 592 486 591 486 591 1563 592 485 592 485 592 1563 592 485 592 485 592 1563 592 1563 592 485 592 1563 592 1563 592 485 592 1563 592 1563 592 1563 592 1563 592 1563 592 1564 591 486 591 1563 591 485 592 485 592 485 592 485 592 485 592 485 592 485 592 1563 592 485 592 1563 591 486 591 485 592 486 591 485 592 1563 592 485 592 1563 592 485 592 1564 591 1563 592 1563 592 1563 592 +# +name: Heat_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4440 4391 572 1583 572 505 572 1584 571 1583 572 505 572 505 572 1583 596 481 573 505 596 1559 626 451 655 422 625 1529 596 1559 572 505 572 1583 572 1582 572 505 572 1583 572 1583 571 1584 571 1584 571 1585 570 1584 594 484 593 1562 592 485 592 485 592 485 593 485 593 484 593 484 593 1562 593 484 593 1562 593 1563 592 1562 592 1563 592 485 593 484 592 485 593 1562 593 484 593 485 592 485 592 485 592 1562 593 1563 592 5163 4462 4370 592 1563 592 485 592 1563 592 1563 592 485 592 485 592 1563 592 485 592 485 593 1562 593 485 593 485 592 1563 592 1563 592 485 592 1562 593 1563 592 484 593 1563 592 1563 592 1563 592 1563 592 1563 592 1563 592 485 592 1563 592 485 592 485 592 485 592 485 592 485 593 485 592 1563 592 485 592 1563 592 1563 592 1563 592 1563 592 485 592 485 592 485 592 1563 591 485 592 486 591 485 592 485 593 1563 591 1563 593 +# +name: Heat_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4467 4390 571 1583 572 505 595 1560 572 1583 572 505 572 505 596 1559 596 482 596 481 597 1559 626 451 655 422 625 1529 596 1559 596 481 572 1582 573 1583 571 505 572 1583 595 1560 594 1561 592 1562 594 1561 593 1562 593 484 593 1563 592 485 592 485 592 485 592 485 593 484 593 485 592 485 592 1562 593 485 592 1563 592 1562 593 1562 594 483 593 485 593 1562 592 485 593 1561 593 484 593 484 593 484 593 1562 593 1562 592 5163 4462 4370 592 1563 593 484 592 1563 592 1563 592 485 592 485 593 1562 593 484 593 485 592 1562 593 484 593 485 592 1562 593 1562 593 485 592 1563 592 1563 592 485 592 1563 592 1562 593 1562 593 1563 592 1563 592 1562 592 485 593 1562 592 485 592 485 592 485 592 485 592 485 592 485 592 485 592 1563 592 485 592 1563 591 1563 592 1563 593 485 592 485 592 1563 592 485 592 1563 591 485 593 485 592 485 592 1563 592 1563 591 From 66961dab0657b8d1a7d1196f8b89b1021969ca59 Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Mon, 29 May 2023 12:21:18 +0300 Subject: [PATCH 578/824] BadUSB: script execution pause (#2700) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- .../main/bad_usb/helpers/ducky_script.c | 96 ++++++++++++++----- .../main/bad_usb/helpers/ducky_script.h | 5 +- .../main/bad_usb/scenes/bad_usb_scene_work.c | 5 +- .../main/bad_usb/views/bad_usb_view.c | 82 ++++++++++++---- 4 files changed, 142 insertions(+), 46 deletions(-) diff --git a/applications/main/bad_usb/helpers/ducky_script.c b/applications/main/bad_usb/helpers/ducky_script.c index 47d8a7e0517..5a834ad0afe 100644 --- a/applications/main/bad_usb/helpers/ducky_script.c +++ b/applications/main/bad_usb/helpers/ducky_script.c @@ -16,10 +16,11 @@ (((uint8_t)x < 128) ? (script->layout[(uint8_t)x]) : HID_KEYBOARD_NONE) typedef enum { - WorkerEvtToggle = (1 << 0), - WorkerEvtEnd = (1 << 1), - WorkerEvtConnect = (1 << 2), - WorkerEvtDisconnect = (1 << 3), + WorkerEvtStartStop = (1 << 0), + WorkerEvtPauseResume = (1 << 1), + WorkerEvtEnd = (1 << 2), + WorkerEvtConnect = (1 << 3), + WorkerEvtDisconnect = (1 << 4), } WorkerEvtFlags; static const char ducky_cmd_id[] = {"ID"}; @@ -372,6 +373,7 @@ static int32_t bad_usb_worker(void* context) { BadUsbScript* bad_usb = context; BadUsbWorkerState worker_state = BadUsbStateInit; + BadUsbWorkerState pause_state = BadUsbStateRunning; int32_t delay_val = 0; FURI_LOG_I(WORKER_TAG, "Init"); @@ -406,24 +408,24 @@ static int32_t bad_usb_worker(void* context) { } else if(worker_state == BadUsbStateNotConnected) { // State: USB not connected uint32_t flags = bad_usb_flags_get( - WorkerEvtEnd | WorkerEvtConnect | WorkerEvtToggle, FuriWaitForever); + WorkerEvtEnd | WorkerEvtConnect | WorkerEvtStartStop, FuriWaitForever); if(flags & WorkerEvtEnd) { break; } else if(flags & WorkerEvtConnect) { worker_state = BadUsbStateIdle; // Ready to run - } else if(flags & WorkerEvtToggle) { + } else if(flags & WorkerEvtStartStop) { worker_state = BadUsbStateWillRun; // Will run when USB is connected } bad_usb->st.state = worker_state; } else if(worker_state == BadUsbStateIdle) { // State: ready to start uint32_t flags = bad_usb_flags_get( - WorkerEvtEnd | WorkerEvtToggle | WorkerEvtDisconnect, FuriWaitForever); + WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtDisconnect, FuriWaitForever); if(flags & WorkerEvtEnd) { break; - } else if(flags & WorkerEvtToggle) { // Start executing script + } else if(flags & WorkerEvtStartStop) { // Start executing script DOLPHIN_DEED(DolphinDeedBadUsbPlayScript); delay_val = 0; bad_usb->buf_len = 0; @@ -442,7 +444,7 @@ static int32_t bad_usb_worker(void* context) { } else if(worker_state == BadUsbStateWillRun) { // State: start on connection uint32_t flags = bad_usb_flags_get( - WorkerEvtEnd | WorkerEvtConnect | WorkerEvtToggle, FuriWaitForever); + WorkerEvtEnd | WorkerEvtConnect | WorkerEvtStartStop, FuriWaitForever); if(flags & WorkerEvtEnd) { break; @@ -458,17 +460,17 @@ static int32_t bad_usb_worker(void* context) { storage_file_seek(script_file, 0, true); // extra time for PC to recognize Flipper as keyboard flags = furi_thread_flags_wait( - WorkerEvtEnd | WorkerEvtDisconnect | WorkerEvtToggle, + WorkerEvtEnd | WorkerEvtDisconnect | WorkerEvtStartStop, FuriFlagWaitAny | FuriFlagNoClear, 1500); if(flags == (unsigned)FuriFlagErrorTimeout) { // If nothing happened - start script execution worker_state = BadUsbStateRunning; - } else if(flags & WorkerEvtToggle) { + } else if(flags & WorkerEvtStartStop) { worker_state = BadUsbStateIdle; - furi_thread_flags_clear(WorkerEvtToggle); + furi_thread_flags_clear(WorkerEvtStartStop); } - } else if(flags & WorkerEvtToggle) { // Cancel scheduled execution + } else if(flags & WorkerEvtStartStop) { // Cancel scheduled execution worker_state = BadUsbStateNotConnected; } bad_usb->st.state = worker_state; @@ -476,18 +478,23 @@ static int32_t bad_usb_worker(void* context) { } else if(worker_state == BadUsbStateRunning) { // State: running uint16_t delay_cur = (delay_val > 1000) ? (1000) : (delay_val); uint32_t flags = furi_thread_flags_wait( - WorkerEvtEnd | WorkerEvtToggle | WorkerEvtDisconnect, FuriFlagWaitAny, delay_cur); + WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtPauseResume | WorkerEvtDisconnect, + FuriFlagWaitAny, + delay_cur); delay_val -= delay_cur; if(!(flags & FuriFlagError)) { if(flags & WorkerEvtEnd) { break; - } else if(flags & WorkerEvtToggle) { + } else if(flags & WorkerEvtStartStop) { worker_state = BadUsbStateIdle; // Stop executing script furi_hal_hid_kb_release_all(); } else if(flags & WorkerEvtDisconnect) { worker_state = BadUsbStateNotConnected; // USB disconnected furi_hal_hid_kb_release_all(); + } else if(flags & WorkerEvtPauseResume) { + pause_state = BadUsbStateRunning; + worker_state = BadUsbStatePaused; // Pause } bad_usb->st.state = worker_state; continue; @@ -526,13 +533,13 @@ static int32_t bad_usb_worker(void* context) { furi_check((flags & FuriFlagError) == 0); } } else if(worker_state == BadUsbStateWaitForBtn) { // State: Wait for button Press - uint16_t delay_cur = (delay_val > 1000) ? (1000) : (delay_val); - uint32_t flags = furi_thread_flags_wait( - WorkerEvtEnd | WorkerEvtToggle | WorkerEvtDisconnect, FuriFlagWaitAny, delay_cur); + uint32_t flags = bad_usb_flags_get( + WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtPauseResume | WorkerEvtDisconnect, + FuriWaitForever); if(!(flags & FuriFlagError)) { if(flags & WorkerEvtEnd) { break; - } else if(flags & WorkerEvtToggle) { + } else if(flags & WorkerEvtStartStop) { delay_val = 0; worker_state = BadUsbStateRunning; } else if(flags & WorkerEvtDisconnect) { @@ -542,21 +549,55 @@ static int32_t bad_usb_worker(void* context) { bad_usb->st.state = worker_state; continue; } + } else if(worker_state == BadUsbStatePaused) { // State: Paused + uint32_t flags = bad_usb_flags_get( + WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtPauseResume | WorkerEvtDisconnect, + FuriWaitForever); + if(!(flags & FuriFlagError)) { + if(flags & WorkerEvtEnd) { + break; + } else if(flags & WorkerEvtStartStop) { + worker_state = BadUsbStateIdle; // Stop executing script + bad_usb->st.state = worker_state; + furi_hal_hid_kb_release_all(); + } else if(flags & WorkerEvtDisconnect) { + worker_state = BadUsbStateNotConnected; // USB disconnected + bad_usb->st.state = worker_state; + furi_hal_hid_kb_release_all(); + } else if(flags & WorkerEvtPauseResume) { + if(pause_state == BadUsbStateRunning) { + if(delay_val > 0) { + bad_usb->st.state = BadUsbStateDelay; + bad_usb->st.delay_remain = delay_val / 1000; + } else { + bad_usb->st.state = BadUsbStateRunning; + delay_val = 0; + } + worker_state = BadUsbStateRunning; // Resume + } else if(pause_state == BadUsbStateStringDelay) { + bad_usb->st.state = BadUsbStateRunning; + worker_state = BadUsbStateStringDelay; // Resume + } + } + continue; + } } else if(worker_state == BadUsbStateStringDelay) { // State: print string with delays - uint32_t flags = furi_thread_flags_wait( - WorkerEvtEnd | WorkerEvtToggle | WorkerEvtDisconnect, - FuriFlagWaitAny, + uint32_t flags = bad_usb_flags_get( + WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtPauseResume | WorkerEvtDisconnect, bad_usb->stringdelay); if(!(flags & FuriFlagError)) { if(flags & WorkerEvtEnd) { break; - } else if(flags & WorkerEvtToggle) { + } else if(flags & WorkerEvtStartStop) { worker_state = BadUsbStateIdle; // Stop executing script furi_hal_hid_kb_release_all(); } else if(flags & WorkerEvtDisconnect) { worker_state = BadUsbStateNotConnected; // USB disconnected furi_hal_hid_kb_release_all(); + } else if(flags & WorkerEvtPauseResume) { + pause_state = BadUsbStateStringDelay; + worker_state = BadUsbStatePaused; // Pause } bad_usb->st.state = worker_state; continue; @@ -651,9 +692,14 @@ void bad_usb_script_set_keyboard_layout(BadUsbScript* bad_usb, FuriString* layou storage_file_free(layout_file); } -void bad_usb_script_toggle(BadUsbScript* bad_usb) { +void bad_usb_script_start_stop(BadUsbScript* bad_usb) { + furi_assert(bad_usb); + furi_thread_flags_set(furi_thread_get_id(bad_usb->thread), WorkerEvtStartStop); +} + +void bad_usb_script_pause_resume(BadUsbScript* bad_usb) { furi_assert(bad_usb); - furi_thread_flags_set(furi_thread_get_id(bad_usb->thread), WorkerEvtToggle); + furi_thread_flags_set(furi_thread_get_id(bad_usb->thread), WorkerEvtPauseResume); } BadUsbState* bad_usb_script_get_state(BadUsbScript* bad_usb) { diff --git a/applications/main/bad_usb/helpers/ducky_script.h b/applications/main/bad_usb/helpers/ducky_script.h index cff7239420e..c8705dbdd10 100644 --- a/applications/main/bad_usb/helpers/ducky_script.h +++ b/applications/main/bad_usb/helpers/ducky_script.h @@ -16,6 +16,7 @@ typedef enum { BadUsbStateDelay, BadUsbStateStringDelay, BadUsbStateWaitForBtn, + BadUsbStatePaused, BadUsbStateDone, BadUsbStateScriptError, BadUsbStateFileError, @@ -42,7 +43,9 @@ void bad_usb_script_start(BadUsbScript* bad_usb); void bad_usb_script_stop(BadUsbScript* bad_usb); -void bad_usb_script_toggle(BadUsbScript* bad_usb); +void bad_usb_script_start_stop(BadUsbScript* bad_usb); + +void bad_usb_script_pause_resume(BadUsbScript* bad_usb); BadUsbState* bad_usb_script_get_state(BadUsbScript* bad_usb); diff --git a/applications/main/bad_usb/scenes/bad_usb_scene_work.c b/applications/main/bad_usb/scenes/bad_usb_scene_work.c index afc2e6f6f13..ad33a124d2a 100644 --- a/applications/main/bad_usb/scenes/bad_usb_scene_work.c +++ b/applications/main/bad_usb/scenes/bad_usb_scene_work.c @@ -21,7 +21,10 @@ bool bad_usb_scene_work_on_event(void* context, SceneManagerEvent event) { } consumed = true; } else if(event.event == InputKeyOk) { - bad_usb_script_toggle(app->bad_usb_script); + bad_usb_script_start_stop(app->bad_usb_script); + consumed = true; + } else if(event.event == InputKeyRight) { + bad_usb_script_pause_resume(app->bad_usb_script); consumed = true; } } else if(event.type == SceneManagerEventTypeTick) { diff --git a/applications/main/bad_usb/views/bad_usb_view.c b/applications/main/bad_usb/views/bad_usb_view.c index 0ab4365b7b1..fa75b50d038 100644 --- a/applications/main/bad_usb/views/bad_usb_view.c +++ b/applications/main/bad_usb/views/bad_usb_view.c @@ -16,6 +16,7 @@ typedef struct { char file_name[MAX_NAME_LEN]; char layout[MAX_NAME_LEN]; BadUsbState state; + bool pause_wait; uint8_t anim_frame; } BadUsbModel; @@ -31,11 +32,7 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) { if(strlen(model->layout) == 0) { furi_string_set(disp_str, "(default)"); } else { - furi_string_reset(disp_str); - furi_string_push_back(disp_str, '('); - for(size_t i = 0; i < strlen(model->layout); i++) - furi_string_push_back(disp_str, model->layout[i]); - furi_string_push_back(disp_str, ')'); + furi_string_printf(disp_str, "(%s)", model->layout); } elements_string_fit_width(canvas, disp_str, 128 - 2); canvas_draw_str( @@ -45,34 +42,42 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) { canvas_draw_icon(canvas, 22, 24, &I_UsbTree_48x22); - if((model->state.state == BadUsbStateIdle) || (model->state.state == BadUsbStateDone) || - (model->state.state == BadUsbStateNotConnected)) { + BadUsbWorkerState state = model->state.state; + + if((state == BadUsbStateIdle) || (state == BadUsbStateDone) || + (state == BadUsbStateNotConnected)) { elements_button_center(canvas, "Run"); elements_button_left(canvas, "Config"); - } else if((model->state.state == BadUsbStateRunning) || (model->state.state == BadUsbStateDelay)) { + } else if((state == BadUsbStateRunning) || (state == BadUsbStateDelay)) { elements_button_center(canvas, "Stop"); - } else if(model->state.state == BadUsbStateWaitForBtn) { + if(!model->pause_wait) { + elements_button_right(canvas, "Pause"); + } + } else if(state == BadUsbStatePaused) { + elements_button_center(canvas, "End"); + elements_button_right(canvas, "Resume"); + } else if(state == BadUsbStateWaitForBtn) { elements_button_center(canvas, "Press to continue"); - } else if(model->state.state == BadUsbStateWillRun) { + } else if(state == BadUsbStateWillRun) { elements_button_center(canvas, "Cancel"); } - if(model->state.state == BadUsbStateNotConnected) { + if(state == BadUsbStateNotConnected) { canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18); canvas_set_font(canvas, FontPrimary); canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "Connect"); canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "to USB"); - } else if(model->state.state == BadUsbStateWillRun) { + } else if(state == BadUsbStateWillRun) { canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18); canvas_set_font(canvas, FontPrimary); canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "Will run"); canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "on connect"); - } else if(model->state.state == BadUsbStateFileError) { + } else if(state == BadUsbStateFileError) { canvas_draw_icon(canvas, 4, 26, &I_Error_18x18); canvas_set_font(canvas, FontPrimary); canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "File"); canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "ERROR"); - } else if(model->state.state == BadUsbStateScriptError) { + } else if(state == BadUsbStateScriptError) { canvas_draw_icon(canvas, 4, 26, &I_Error_18x18); canvas_set_font(canvas, FontPrimary); canvas_draw_str_aligned(canvas, 127, 33, AlignRight, AlignBottom, "ERROR:"); @@ -87,12 +92,12 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) { canvas_draw_str_aligned( canvas, 127, 56, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); furi_string_reset(disp_str); - } else if(model->state.state == BadUsbStateIdle) { + } else if(state == BadUsbStateIdle) { canvas_draw_icon(canvas, 4, 26, &I_Smile_18x18); canvas_set_font(canvas, FontBigNumbers); canvas_draw_str_aligned(canvas, 114, 40, AlignRight, AlignBottom, "0"); canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14); - } else if(model->state.state == BadUsbStateRunning) { + } else if(state == BadUsbStateRunning) { if(model->anim_frame == 0) { canvas_draw_icon(canvas, 4, 23, &I_EviSmile1_18x21); } else { @@ -105,13 +110,13 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) { canvas, 114, 40, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); furi_string_reset(disp_str); canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14); - } else if(model->state.state == BadUsbStateDone) { + } else if(state == BadUsbStateDone) { canvas_draw_icon(canvas, 4, 23, &I_EviSmile1_18x21); canvas_set_font(canvas, FontBigNumbers); canvas_draw_str_aligned(canvas, 114, 40, AlignRight, AlignBottom, "100"); furi_string_reset(disp_str); canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14); - } else if(model->state.state == BadUsbStateDelay) { + } else if(state == BadUsbStateDelay) { if(model->anim_frame == 0) { canvas_draw_icon(canvas, 4, 23, &I_EviWaiting1_18x21); } else { @@ -129,6 +134,22 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) { canvas_draw_str_aligned( canvas, 127, 50, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); furi_string_reset(disp_str); + } else if((state == BadUsbStatePaused) || (state == BadUsbStateWaitForBtn)) { + if(model->anim_frame == 0) { + canvas_draw_icon(canvas, 4, 23, &I_EviWaiting1_18x21); + } else { + canvas_draw_icon(canvas, 4, 23, &I_EviWaiting2_18x21); + } + canvas_set_font(canvas, FontBigNumbers); + furi_string_printf( + disp_str, "%u", ((model->state.line_cur - 1) * 100) / model->state.line_nb); + canvas_draw_str_aligned( + canvas, 114, 40, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); + furi_string_reset(disp_str); + canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14); + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned(canvas, 127, 50, AlignRight, AlignBottom, "Paused"); + furi_string_reset(disp_str); } else { canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18); } @@ -142,7 +163,27 @@ static bool bad_usb_input_callback(InputEvent* event, void* context) { bool consumed = false; if(event->type == InputTypeShort) { - if((event->key == InputKeyLeft) || (event->key == InputKeyOk)) { + if(event->key == InputKeyLeft) { + consumed = true; + furi_assert(bad_usb->callback); + bad_usb->callback(event->key, bad_usb->context); + } else if(event->key == InputKeyOk) { + with_view_model( + bad_usb->view, BadUsbModel * model, { model->pause_wait = false; }, true); + consumed = true; + furi_assert(bad_usb->callback); + bad_usb->callback(event->key, bad_usb->context); + } else if(event->key == InputKeyRight) { + with_view_model( + bad_usb->view, + BadUsbModel * model, + { + if((model->state.state == BadUsbStateRunning) || + (model->state.state == BadUsbStateDelay)) { + model->pause_wait = true; + } + }, + true); consumed = true; furi_assert(bad_usb->callback); bad_usb->callback(event->key, bad_usb->context); @@ -215,6 +256,9 @@ void bad_usb_set_state(BadUsb* bad_usb, BadUsbState* st) { { memcpy(&(model->state), st, sizeof(BadUsbState)); model->anim_frame ^= 1; + if(model->state.state == BadUsbStatePaused) { + model->pause_wait = false; + } }, true); } From 363f555ed74ab79aea0a6700779b22197bf1305d Mon Sep 17 00:00:00 2001 From: micolous Date: Mon, 29 May 2023 21:55:55 +1000 Subject: [PATCH 579/824] Implement support for reading Opal card (Sydney, Australia) (#2683) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Implement support for reading Opal card (Sydney, Australia) * stub_parser_verify_read: used UNUSED macro * furi_hal_rtc: expose calendaring as functions * opal: use bit-packed struct to parse, rather than manually shifting about * Update f18 api symbols Co-authored-by: あく --- .../main/nfc/scenes/nfc_scene_device_info.c | 1 + .../nfc_scene_mf_desfire_read_success.c | 59 ++--- .../main/nfc/scenes/nfc_scene_nfc_data_info.c | 2 +- .../main/nfc/scenes/nfc_scene_saved_menu.c | 1 + firmware/targets/f18/api_symbols.csv | 5 +- firmware/targets/f7/api_symbols.csv | 7 +- firmware/targets/f7/furi_hal/furi_hal_rtc.c | 24 ++- .../targets/furi_hal_include/furi_hal_rtc.h | 24 +++ lib/nfc/nfc_worker.c | 13 ++ lib/nfc/parsers/nfc_supported_card.c | 15 ++ lib/nfc/parsers/nfc_supported_card.h | 6 + lib/nfc/parsers/opal.c | 204 ++++++++++++++++++ lib/nfc/parsers/opal.h | 5 + lib/nfc/protocols/mifare_desfire.c | 24 +++ lib/nfc/protocols/mifare_desfire.h | 3 + 15 files changed, 356 insertions(+), 37 deletions(-) create mode 100644 lib/nfc/parsers/opal.c create mode 100644 lib/nfc/parsers/opal.h diff --git a/applications/main/nfc/scenes/nfc_scene_device_info.c b/applications/main/nfc/scenes/nfc_scene_device_info.c index 9780ffe41d2..5d51c0816c8 100644 --- a/applications/main/nfc/scenes/nfc_scene_device_info.c +++ b/applications/main/nfc/scenes/nfc_scene_device_info.c @@ -52,6 +52,7 @@ void nfc_scene_device_info_on_enter(void* context) { } } else if( dev_data->protocol == NfcDeviceProtocolMifareClassic || + dev_data->protocol == NfcDeviceProtocolMifareDesfire || dev_data->protocol == NfcDeviceProtocolMifareUl) { furi_string_set(temp_str, nfc->dev->dev_data.parsed_data); } diff --git a/applications/main/nfc/scenes/nfc_scene_mf_desfire_read_success.c b/applications/main/nfc/scenes/nfc_scene_mf_desfire_read_success.c index 39030397fc4..633549eb5d8 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_desfire_read_success.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_desfire_read_success.c @@ -20,35 +20,40 @@ void nfc_scene_mf_desfire_read_success_on_enter(void* context) { Widget* widget = nfc->widget; // Prepare string for data display - FuriString* temp_str = furi_string_alloc_printf("\e#MIFARE DESfire\n"); - furi_string_cat_printf(temp_str, "UID:"); - for(size_t i = 0; i < nfc_data->uid_len; i++) { - furi_string_cat_printf(temp_str, " %02X", nfc_data->uid[i]); - } + FuriString* temp_str = NULL; + if(furi_string_size(nfc->dev->dev_data.parsed_data)) { + temp_str = furi_string_alloc_set(nfc->dev->dev_data.parsed_data); + } else { + temp_str = furi_string_alloc_printf("\e#MIFARE DESFire\n"); + furi_string_cat_printf(temp_str, "UID:"); + for(size_t i = 0; i < nfc_data->uid_len; i++) { + furi_string_cat_printf(temp_str, " %02X", nfc_data->uid[i]); + } - uint32_t bytes_total = 1UL << (data->version.sw_storage >> 1); - uint32_t bytes_free = data->free_memory ? data->free_memory->bytes : 0; - furi_string_cat_printf(temp_str, "\n%lu", bytes_total); - if(data->version.sw_storage & 1) { - furi_string_push_back(temp_str, '+'); - } - furi_string_cat_printf(temp_str, " bytes, %lu bytes free\n", bytes_free); - - uint16_t n_apps = 0; - uint16_t n_files = 0; - for(MifareDesfireApplication* app = data->app_head; app; app = app->next) { - n_apps++; - for(MifareDesfireFile* file = app->file_head; file; file = file->next) { - n_files++; + uint32_t bytes_total = 1UL << (data->version.sw_storage >> 1); + uint32_t bytes_free = data->free_memory ? data->free_memory->bytes : 0; + furi_string_cat_printf(temp_str, "\n%lu", bytes_total); + if(data->version.sw_storage & 1) { + furi_string_push_back(temp_str, '+'); + } + furi_string_cat_printf(temp_str, " bytes, %lu bytes free\n", bytes_free); + + uint16_t n_apps = 0; + uint16_t n_files = 0; + for(MifareDesfireApplication* app = data->app_head; app; app = app->next) { + n_apps++; + for(MifareDesfireFile* file = app->file_head; file; file = file->next) { + n_files++; + } + } + furi_string_cat_printf(temp_str, "%d Application", n_apps); + if(n_apps != 1) { + furi_string_push_back(temp_str, 's'); + } + furi_string_cat_printf(temp_str, ", %d file", n_files); + if(n_files != 1) { + furi_string_push_back(temp_str, 's'); } - } - furi_string_cat_printf(temp_str, "%d Application", n_apps); - if(n_apps != 1) { - furi_string_push_back(temp_str, 's'); - } - furi_string_cat_printf(temp_str, ", %d file", n_files); - if(n_files != 1) { - furi_string_push_back(temp_str, 's'); } notification_message_block(nfc->notifications, &sequence_set_green_255); diff --git a/applications/main/nfc/scenes/nfc_scene_nfc_data_info.c b/applications/main/nfc/scenes/nfc_scene_nfc_data_info.c index 92ad7b56ef4..b44bb5e64cd 100644 --- a/applications/main/nfc/scenes/nfc_scene_nfc_data_info.c +++ b/applications/main/nfc/scenes/nfc_scene_nfc_data_info.c @@ -40,7 +40,7 @@ void nfc_scene_nfc_data_info_on_enter(void* context) { furi_string_cat_printf( temp_str, "\e#%s\n", nfc_mf_classic_type(dev_data->mf_classic_data.type)); } else if(protocol == NfcDeviceProtocolMifareDesfire) { - furi_string_cat_printf(temp_str, "\e#MIFARE DESfire\n"); + furi_string_cat_printf(temp_str, "\e#MIFARE DESFire\n"); } else { furi_string_cat_printf(temp_str, "\e#Unknown ISO tag\n"); } diff --git a/applications/main/nfc/scenes/nfc_scene_saved_menu.c b/applications/main/nfc/scenes/nfc_scene_saved_menu.c index e45dc4eb7dd..4573cdc4528 100644 --- a/applications/main/nfc/scenes/nfc_scene_saved_menu.c +++ b/applications/main/nfc/scenes/nfc_scene_saved_menu.c @@ -148,6 +148,7 @@ bool nfc_scene_saved_menu_on_event(void* context, SceneManagerEvent event) { application_info_present = true; } else if( dev_data->protocol == NfcDeviceProtocolMifareClassic || + dev_data->protocol == NfcDeviceProtocolMifareDesfire || dev_data->protocol == NfcDeviceProtocolMifareUl) { application_info_present = nfc_supported_card_verify_and_parse(dev_data); } diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index 68248a6d2ba..08aa6913366 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,27.0,, +Version,+,27.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -1048,6 +1048,8 @@ Function,+,furi_hal_rtc_datetime_to_timestamp,uint32_t,FuriHalRtcDateTime* Function,-,furi_hal_rtc_deinit_early,void, Function,+,furi_hal_rtc_get_boot_mode,FuriHalRtcBootMode, Function,+,furi_hal_rtc_get_datetime,void,FuriHalRtcDateTime* +Function,+,furi_hal_rtc_get_days_per_month,uint8_t,"_Bool, uint8_t" +Function,+,furi_hal_rtc_get_days_per_year,uint16_t,uint16_t Function,+,furi_hal_rtc_get_fault_data,uint32_t, Function,+,furi_hal_rtc_get_heap_track_mode,FuriHalRtcHeapTrackMode, Function,+,furi_hal_rtc_get_locale_dateformat,FuriHalRtcLocaleDateFormat, @@ -1060,6 +1062,7 @@ Function,+,furi_hal_rtc_get_timestamp,uint32_t, Function,-,furi_hal_rtc_init,void, Function,-,furi_hal_rtc_init_early,void, Function,+,furi_hal_rtc_is_flag_set,_Bool,FuriHalRtcFlag +Function,+,furi_hal_rtc_is_leap_year,_Bool,uint16_t Function,+,furi_hal_rtc_reset_flag,void,FuriHalRtcFlag Function,+,furi_hal_rtc_set_boot_mode,void,FuriHalRtcBootMode Function,+,furi_hal_rtc_set_datetime,void,FuriHalRtcDateTime* diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index ccbaa531765..1055cb2bfd4 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,27.0,, +Version,+,27.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -1313,6 +1313,8 @@ Function,+,furi_hal_rtc_datetime_to_timestamp,uint32_t,FuriHalRtcDateTime* Function,-,furi_hal_rtc_deinit_early,void, Function,+,furi_hal_rtc_get_boot_mode,FuriHalRtcBootMode, Function,+,furi_hal_rtc_get_datetime,void,FuriHalRtcDateTime* +Function,+,furi_hal_rtc_get_days_per_month,uint8_t,"_Bool, uint8_t" +Function,+,furi_hal_rtc_get_days_per_year,uint16_t,uint16_t Function,+,furi_hal_rtc_get_fault_data,uint32_t, Function,+,furi_hal_rtc_get_heap_track_mode,FuriHalRtcHeapTrackMode, Function,+,furi_hal_rtc_get_locale_dateformat,FuriHalRtcLocaleDateFormat, @@ -1325,6 +1327,7 @@ Function,+,furi_hal_rtc_get_timestamp,uint32_t, Function,-,furi_hal_rtc_init,void, Function,-,furi_hal_rtc_init_early,void, Function,+,furi_hal_rtc_is_flag_set,_Bool,FuriHalRtcFlag +Function,+,furi_hal_rtc_is_leap_year,_Bool,uint16_t Function,+,furi_hal_rtc_reset_flag,void,FuriHalRtcFlag Function,+,furi_hal_rtc_set_boot_mode,void,FuriHalRtcBootMode Function,+,furi_hal_rtc_set_datetime,void,FuriHalRtcDateTime* @@ -1991,6 +1994,8 @@ Function,-,mf_df_cat_key_settings,void,"MifareDesfireKeySettings*, FuriString*" Function,-,mf_df_cat_version,void,"MifareDesfireVersion*, FuriString*" Function,-,mf_df_check_card_type,_Bool,"uint8_t, uint8_t, uint8_t" Function,-,mf_df_clear,void,MifareDesfireData* +Function,-,mf_df_get_application,MifareDesfireApplication*,"MifareDesfireData*, const uint8_t[3]*" +Function,-,mf_df_get_file,MifareDesfireFile*,"MifareDesfireApplication*, uint8_t" Function,-,mf_df_parse_get_application_ids_response,_Bool,"uint8_t*, uint16_t, MifareDesfireApplication**" Function,-,mf_df_parse_get_file_ids_response,_Bool,"uint8_t*, uint16_t, MifareDesfireFile**" Function,-,mf_df_parse_get_file_settings_response,_Bool,"uint8_t*, uint16_t, MifareDesfireFile*" diff --git a/firmware/targets/f7/furi_hal/furi_hal_rtc.c b/firmware/targets/f7/furi_hal/furi_hal_rtc.c index 7bd45c35d71..8dfe1a13ec7 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_rtc.c +++ b/firmware/targets/f7/furi_hal/furi_hal_rtc.c @@ -44,10 +44,8 @@ _Static_assert(sizeof(SystemReg) == 4, "SystemReg size mismatch"); #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] = { +static const uint8_t furi_hal_rtc_days_per_month[2][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}}; @@ -395,7 +393,7 @@ uint32_t furi_hal_rtc_datetime_to_timestamp(FuriHalRtcDateTime* datetime) { 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)) { + if(furi_hal_rtc_is_leap_year(y)) { leap_years++; } else { years++; @@ -406,10 +404,10 @@ uint32_t furi_hal_rtc_datetime_to_timestamp(FuriHalRtcDateTime* datetime) { ((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; + bool leap_year = furi_hal_rtc_is_leap_year(datetime->year); - 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; + for(uint8_t m = 1; m < datetime->month; m++) { + timestamp += furi_hal_rtc_get_days_per_month(leap_year, m) * FURI_HAL_RTC_SECONDS_PER_DAY; } timestamp += (datetime->day - 1) * FURI_HAL_RTC_SECONDS_PER_DAY; @@ -419,3 +417,15 @@ uint32_t furi_hal_rtc_datetime_to_timestamp(FuriHalRtcDateTime* datetime) { return timestamp; } + +uint16_t furi_hal_rtc_get_days_per_year(uint16_t year) { + return furi_hal_rtc_days_per_year[furi_hal_rtc_is_leap_year(year) ? 1 : 0]; +} + +bool furi_hal_rtc_is_leap_year(uint16_t year) { + return (((year) % 4 == 0) && ((year) % 100 != 0)) || ((year) % 400 == 0); +} + +uint8_t furi_hal_rtc_get_days_per_month(bool leap_year, uint8_t month) { + return furi_hal_rtc_days_per_month[leap_year ? 1 : 0][month - 1]; +} diff --git a/firmware/targets/furi_hal_include/furi_hal_rtc.h b/firmware/targets/furi_hal_include/furi_hal_rtc.h index e706c5c76a0..186d22f0798 100644 --- a/firmware/targets/furi_hal_include/furi_hal_rtc.h +++ b/firmware/targets/furi_hal_include/furi_hal_rtc.h @@ -255,6 +255,30 @@ uint32_t furi_hal_rtc_get_timestamp(); */ uint32_t furi_hal_rtc_datetime_to_timestamp(FuriHalRtcDateTime* datetime); +/** Gets the number of days in the year according to the Gregorian calendar. + * + * @param year Input year. + * + * @return number of days in `year`. + */ +uint16_t furi_hal_rtc_get_days_per_year(uint16_t year); + +/** Check if a year a leap year in the Gregorian calendar. + * + * @param year Input year. + * + * @return true if `year` is a leap year. + */ +bool furi_hal_rtc_is_leap_year(uint16_t year); + +/** Get the number of days in the month. + * + * @param leap_year true to calculate based on leap years + * @param month month to check, where 1 = January + * @return the number of days in the month + */ +uint8_t furi_hal_rtc_get_days_per_month(bool leap_year, uint8_t month); + #ifdef __cplusplus } #endif diff --git a/lib/nfc/nfc_worker.c b/lib/nfc/nfc_worker.c index 28a1f682797..daa8fee59a3 100644 --- a/lib/nfc/nfc_worker.c +++ b/lib/nfc/nfc_worker.c @@ -219,6 +219,19 @@ static bool nfc_worker_read_mf_desfire(NfcWorker* nfc_worker, FuriHalNfcTxRxCont do { if(!furi_hal_nfc_detect(&nfc_worker->dev_data->nfc_data, 300)) break; if(!mf_df_read_card(tx_rx, data)) break; + FURI_LOG_I(TAG, "Trying to parse a supported card ..."); + + // The model for parsing DESFire is a little different to other cards; + // we don't have parsers to provide encryption keys, so we can read the + // data normally, and then pass the read data to a parser. + // + // There are fully-protected DESFire cards, but providing keys for them + // is difficult (and unnessesary for many transit cards). + for(size_t i = 0; i < NfcSupportedCardTypeEnd; i++) { + if(nfc_supported_card[i].protocol == NfcDeviceProtocolMifareDesfire) { + if(nfc_supported_card[i].parse(nfc_worker->dev_data)) break; + } + } read_success = true; } while(false); diff --git a/lib/nfc/parsers/nfc_supported_card.c b/lib/nfc/parsers/nfc_supported_card.c index fc2dc34e0ee..153d4d3c511 100644 --- a/lib/nfc/parsers/nfc_supported_card.c +++ b/lib/nfc/parsers/nfc_supported_card.c @@ -6,6 +6,7 @@ #include "troika_4k_parser.h" #include "two_cities.h" #include "all_in_one.h" +#include "opal.h" NfcSupportedCard nfc_supported_card[NfcSupportedCardTypeEnd] = { [NfcSupportedCardTypePlantain] = @@ -50,6 +51,14 @@ NfcSupportedCard nfc_supported_card[NfcSupportedCardTypeEnd] = { .read = all_in_one_parser_read, .parse = all_in_one_parser_parse, }, + [NfcSupportedCardTypeOpal] = + { + .protocol = NfcDeviceProtocolMifareDesfire, + .verify = stub_parser_verify_read, + .read = stub_parser_verify_read, + .parse = opal_parser_parse, + }, + }; bool nfc_supported_card_verify_and_parse(NfcDeviceData* dev_data) { @@ -65,3 +74,9 @@ bool nfc_supported_card_verify_and_parse(NfcDeviceData* dev_data) { return card_parsed; } + +bool stub_parser_verify_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { + UNUSED(nfc_worker); + UNUSED(tx_rx); + return false; +} diff --git a/lib/nfc/parsers/nfc_supported_card.h b/lib/nfc/parsers/nfc_supported_card.h index 4af59aded63..877bda73745 100644 --- a/lib/nfc/parsers/nfc_supported_card.h +++ b/lib/nfc/parsers/nfc_supported_card.h @@ -11,6 +11,7 @@ typedef enum { NfcSupportedCardTypeTroika4K, NfcSupportedCardTypeTwoCities, NfcSupportedCardTypeAllInOne, + NfcSupportedCardTypeOpal, NfcSupportedCardTypeEnd, } NfcSupportedCardType; @@ -31,3 +32,8 @@ typedef struct { extern NfcSupportedCard nfc_supported_card[NfcSupportedCardTypeEnd]; bool nfc_supported_card_verify_and_parse(NfcDeviceData* dev_data); + +// stub_parser_verify_read does nothing, and always reports that it does not +// support the card. This is needed for DESFire card parsers which can't +// provide keys, and only use NfcSupportedCard->parse. +bool stub_parser_verify_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx); diff --git a/lib/nfc/parsers/opal.c b/lib/nfc/parsers/opal.c new file mode 100644 index 00000000000..b5ca37eb61d --- /dev/null +++ b/lib/nfc/parsers/opal.c @@ -0,0 +1,204 @@ +/* + * opal.c - Parser for Opal card (Sydney, Australia). + * + * Copyright 2023 Michael Farrell + * + * This will only read "standard" MIFARE DESFire-based Opal cards. Free travel + * cards (including School Opal cards, veteran, vision-impaired persons and + * TfNSW employees' cards) and single-trip tickets are MIFARE Ultralight C + * cards and not supported. + * + * Reference: https://github.com/metrodroid/metrodroid/wiki/Opal + * + * Note: The card values are all little-endian (like Flipper), but the above + * reference was originally written based on Java APIs, which are big-endian. + * This implementation presumes a little-endian system. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "nfc_supported_card.h" +#include "opal.h" + +#include +#include +#include + +#include + +static const uint8_t opal_aid[3] = {0x31, 0x45, 0x53}; +static const char* opal_modes[5] = + {"Rail / Metro", "Ferry / Light Rail", "Bus", "Unknown mode", "Manly Ferry"}; +static const char* opal_usages[14] = { + "New / Unused", + "Tap on: new journey", + "Tap on: transfer from same mode", + "Tap on: transfer from other mode", + "", // Manly Ferry: new journey + "", // Manly Ferry: transfer from ferry + "", // Manly Ferry: transfer from other + "Tap off: distance fare", + "Tap off: flat fare", + "Automated tap off: failed to tap off", + "Tap off: end of trip without start", + "Tap off: reversal", + "Tap on: rejected", + "Unknown usage", +}; + +// Opal file 0x7 structure. Assumes a little-endian CPU. +typedef struct __attribute__((__packed__)) { + uint32_t serial : 32; + uint8_t check_digit : 4; + bool blocked : 1; + uint16_t txn_number : 16; + int32_t balance : 21; + uint16_t days : 15; + uint16_t minutes : 11; + uint8_t mode : 3; + uint16_t usage : 4; + bool auto_topup : 1; + uint8_t weekly_journeys : 4; + uint16_t checksum : 16; +} OpalFile; + +static_assert(sizeof(OpalFile) == 16); + +// Converts an Opal timestamp to FuriHalRtcDateTime. +// +// Opal measures days since 1980-01-01 and minutes since midnight, and presumes +// all days are 1440 minutes. +void opal_date_time_to_furi(uint16_t days, uint16_t minutes, FuriHalRtcDateTime* out) { + if(!out) return; + uint16_t diy; + out->year = 1980; + out->month = 1; + // 1980-01-01 is a Tuesday + out->weekday = ((days + 1) % 7) + 1; + out->hour = minutes / 60; + out->minute = minutes % 60; + out->second = 0; + + // What year is it? + for(;;) { + diy = furi_hal_rtc_get_days_per_year(out->year); + if(days < diy) break; + days -= diy; + out->year++; + } + + // 1-index the day of the year + days++; + // What month is it? + bool is_leap = furi_hal_rtc_is_leap_year(out->year); + + for(;;) { + uint8_t dim = furi_hal_rtc_get_days_per_month(is_leap, out->month); + if(days <= dim) break; + days -= dim; + out->month++; + } + + out->day = days; +} + +bool opal_parser_parse(NfcDeviceData* dev_data) { + if(dev_data->protocol != NfcDeviceProtocolMifareDesfire) { + return false; + } + + MifareDesfireApplication* app = mf_df_get_application(&dev_data->mf_df_data, &opal_aid); + if(app == NULL) { + return false; + } + MifareDesfireFile* f = mf_df_get_file(app, 0x07); + if(f == NULL || f->type != MifareDesfireFileTypeStandard || f->settings.data.size != 16 || + !f->contents) { + return false; + } + + OpalFile* o = (OpalFile*)f->contents; + + uint8_t serial2 = o->serial / 10000000; + uint16_t serial3 = (o->serial / 1000) % 10000; + uint16_t serial4 = (o->serial % 1000); + + if(o->check_digit > 9) { + return false; + } + + char* sign = ""; + if(o->balance < 0) { + // Negative balance. Make this a positive value again and record the + // sign separately, because then we can handle balances of -99..-1 + // cents, as the "dollars" division below would result in a positive + // zero value. + o->balance = abs(o->balance); + sign = "-"; + } + uint8_t cents = o->balance % 100; + int32_t dollars = o->balance / 100; + + FuriHalRtcDateTime timestamp; + opal_date_time_to_furi(o->days, o->minutes, ×tamp); + + if(o->mode >= 3) { + // 3..7 are "reserved", but we use 4 to indicate the Manly Ferry. + o->mode = 3; + } + + if(o->usage >= 4 && o->usage <= 6) { + // Usages 4..6 associated with the Manly Ferry, which correspond to + // usages 1..3 for other modes. + o->usage -= 3; + o->mode = 4; + } + + const char* mode_str = (o->mode <= 4 ? opal_modes[o->mode] : opal_modes[3]); + const char* usage_str = (o->usage <= 12 ? opal_usages[o->usage] : opal_usages[13]); + + furi_string_printf( + dev_data->parsed_data, + "\e#Opal: $%s%ld.%02hu\n3085 22%02hhu %04hu %03hu%01hhu\n%s, %s\n", + sign, + dollars, + cents, + serial2, + serial3, + serial4, + o->check_digit, + mode_str, + usage_str); + FuriString* timestamp_str = furi_string_alloc(); + locale_format_date(timestamp_str, ×tamp, locale_get_date_format(), "-"); + furi_string_cat(dev_data->parsed_data, timestamp_str); + furi_string_cat_str(dev_data->parsed_data, " at "); + + locale_format_time(timestamp_str, ×tamp, locale_get_time_format(), false); + furi_string_cat(dev_data->parsed_data, timestamp_str); + + furi_string_free(timestamp_str); + furi_string_cat_printf( + dev_data->parsed_data, + "\nWeekly journeys: %hhu, Txn #%hu\n", + o->weekly_journeys, + o->txn_number); + + if(o->auto_topup) { + furi_string_cat_str(dev_data->parsed_data, "Auto-topup enabled\n"); + } + if(o->blocked) { + furi_string_cat_str(dev_data->parsed_data, "Card blocked\n"); + } + return true; +} diff --git a/lib/nfc/parsers/opal.h b/lib/nfc/parsers/opal.h new file mode 100644 index 00000000000..42caf9a1790 --- /dev/null +++ b/lib/nfc/parsers/opal.h @@ -0,0 +1,5 @@ +#pragma once + +#include "nfc_supported_card.h" + +bool opal_parser_parse(NfcDeviceData* dev_data); diff --git a/lib/nfc/protocols/mifare_desfire.c b/lib/nfc/protocols/mifare_desfire.c index 23308ae95e3..e0ead737f87 100644 --- a/lib/nfc/protocols/mifare_desfire.c +++ b/lib/nfc/protocols/mifare_desfire.c @@ -42,6 +42,30 @@ void mf_df_clear(MifareDesfireData* data) { data->app_head = NULL; } +MifareDesfireApplication* mf_df_get_application(MifareDesfireData* data, const uint8_t (*aid)[3]) { + if(!data) { + return NULL; + } + for(MifareDesfireApplication* app = data->app_head; app; app = app->next) { + if(memcmp(aid, app->id, 3) == 0) { + return app; + } + } + return NULL; +} + +MifareDesfireFile* mf_df_get_file(MifareDesfireApplication* app, uint8_t id) { + if(!app) { + return NULL; + } + for(MifareDesfireFile* file = app->file_head; file; file = file->next) { + if(file->id == id) { + return file; + } + } + return NULL; +} + void mf_df_cat_data(MifareDesfireData* data, FuriString* out) { mf_df_cat_card_info(data, out); for(MifareDesfireApplication* app = data->app_head; app; app = app->next) { diff --git a/lib/nfc/protocols/mifare_desfire.h b/lib/nfc/protocols/mifare_desfire.h index 963a18f585b..3dc7c4c2a7c 100644 --- a/lib/nfc/protocols/mifare_desfire.h +++ b/lib/nfc/protocols/mifare_desfire.h @@ -130,6 +130,9 @@ void mf_df_cat_file(MifareDesfireFile* file, FuriString* out); bool mf_df_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK); +MifareDesfireApplication* mf_df_get_application(MifareDesfireData* data, const uint8_t (*aid)[3]); +MifareDesfireFile* mf_df_get_file(MifareDesfireApplication* app, uint8_t id); + uint16_t mf_df_prepare_get_version(uint8_t* dest); bool mf_df_parse_get_version_response(uint8_t* buf, uint16_t len, MifareDesfireVersion* out); From 3de856f8d53ed45b56dd21674927898552bbdd2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Tue, 30 May 2023 01:05:57 +0900 Subject: [PATCH 580/824] [FL-3295] FuriHal: add bus abstraction (#2614) * FuriHal: add bus abstraction and port some subsystem to it * Make PVS happy, cleanup code * Update API symbols for f18 * F18: backport bus changes from f7 * Revert to STOP2 sleep mode * Fix downgrading the firmware via updater * Port iButton TIM1 to furi_hal_bus * Port Infrared TIM1 and TIM2 to furi_hal_bus * Just enable the timer bus * Port furi_hal_pwm to bus API * Fix include statement * Port furi_hal_rfid to bus API * Port furi_hal_subghz and others to bus API * Remove unneeded include * Improve furi_hal_infrared defines * Reset LPTIM1 via furi_hal_bus API * Crash when trying to enable an already enabled peripheral * Better defines * Improved checks * Lots of macro wrappers * Copy spi changes for f18 * Fix crashes in LFRFID system * Fix crashes in NFC system * Improve comments * Create FuriHalBus.md * Update FuriHalBus.md * Fix crash when launching updater * Documentation: couple small fixes in FuriHalBus * FuriHal: fix copypaste in furi_hal_rfid_tim_reset * FuriHal: reset radio core related peripherals on restart * FuriHalBus: is enabled routine and bug fix for uart * RFID HAL: accomodate furi hal bus Co-authored-by: Georgii Surkov Co-authored-by: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> Co-authored-by: SG --- .../scenes/lfrfid_debug_app_scene_tune.c | 5 +- documentation/FuriHalBus.md | 113 +++++++ firmware/targets/f18/api_symbols.csv | 13 +- firmware/targets/f18/furi_hal/furi_hal.c | 5 + .../targets/f18/furi_hal/furi_hal_resources.c | 14 + .../f18/furi_hal/furi_hal_spi_config.c | 31 +- firmware/targets/f7/api_symbols.csv | 27 +- firmware/targets/f7/furi_hal/furi_hal.c | 5 + firmware/targets/f7/furi_hal/furi_hal_bt.c | 11 + firmware/targets/f7/furi_hal/furi_hal_bus.c | 302 +++++++++++++++++ firmware/targets/f7/furi_hal/furi_hal_bus.h | 112 +++++++ firmware/targets/f7/furi_hal/furi_hal_clock.c | 84 ----- .../targets/f7/furi_hal/furi_hal_crypto.c | 10 +- firmware/targets/f7/furi_hal/furi_hal_dma.c | 14 + firmware/targets/f7/furi_hal/furi_hal_dma.h | 15 + firmware/targets/f7/furi_hal/furi_hal_flash.c | 22 +- .../targets/f7/furi_hal/furi_hal_i2c_config.c | 32 +- .../targets/f7/furi_hal/furi_hal_ibutton.c | 12 +- .../targets/f7/furi_hal/furi_hal_idle_timer.h | 8 +- .../targets/f7/furi_hal/furi_hal_infrared.c | 304 ++++++++++-------- firmware/targets/f7/furi_hal/furi_hal_pwm.c | 19 +- .../targets/f7/furi_hal/furi_hal_random.c | 9 +- .../targets/f7/furi_hal/furi_hal_resources.c | 14 + firmware/targets/f7/furi_hal/furi_hal_rfid.c | 107 ++---- firmware/targets/f7/furi_hal/furi_hal_rfid.h | 60 +--- firmware/targets/f7/furi_hal/furi_hal_rtc.c | 2 +- .../targets/f7/furi_hal/furi_hal_speaker.c | 9 +- .../targets/f7/furi_hal/furi_hal_spi_config.c | 31 +- .../targets/f7/furi_hal/furi_hal_spi_types.h | 2 - .../targets/f7/furi_hal/furi_hal_subghz.c | 9 +- firmware/targets/f7/furi_hal/furi_hal_uart.c | 20 +- firmware/targets/f7/furi_hal/furi_hal_usb.c | 9 + firmware/targets/f7/src/update.c | 6 + firmware/targets/furi_hal_include/furi_hal.h | 2 + .../furi_hal_include/furi_hal_random.h | 3 + lib/digital_signal/digital_signal.c | 4 +- lib/lfrfid/lfrfid_raw_worker.c | 4 +- lib/lfrfid/lfrfid_worker_modes.c | 9 +- lib/lfrfid/tools/t5577.c | 9 +- lib/nfc/parsers/opal.c | 4 +- lib/pulse_reader/pulse_reader.c | 3 + 41 files changed, 945 insertions(+), 529 deletions(-) create mode 100644 documentation/FuriHalBus.md create mode 100644 firmware/targets/f7/furi_hal/furi_hal_bus.c create mode 100644 firmware/targets/f7/furi_hal/furi_hal_bus.h create mode 100644 firmware/targets/f7/furi_hal/furi_hal_dma.c create mode 100644 firmware/targets/f7/furi_hal/furi_hal_dma.h diff --git a/applications/debug/lfrfid_debug/scenes/lfrfid_debug_app_scene_tune.c b/applications/debug/lfrfid_debug/scenes/lfrfid_debug_app_scene_tune.c index c7f3bf24fdc..74c53ae6d48 100644 --- a/applications/debug/lfrfid_debug/scenes/lfrfid_debug_app_scene_tune.c +++ b/applications/debug/lfrfid_debug/scenes/lfrfid_debug_app_scene_tune.c @@ -14,9 +14,7 @@ void lfrfid_debug_scene_tune_on_enter(void* context) { furi_hal_rfid_comp_set_callback(comparator_trigger_callback, app); furi_hal_rfid_comp_start(); - furi_hal_rfid_pins_read(); - furi_hal_rfid_tim_read(125000, 0.5); - furi_hal_rfid_tim_read_start(); + furi_hal_rfid_tim_read_start(125000, 0.5); view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidDebugViewTune); } @@ -43,6 +41,5 @@ void lfrfid_debug_scene_tune_on_exit(void* context) { furi_hal_gpio_init_simple(&gpio_ext_pa7, GpioModeAnalog); furi_hal_rfid_tim_read_stop(); - furi_hal_rfid_tim_reset(); furi_hal_rfid_pins_reset(); } diff --git a/documentation/FuriHalBus.md b/documentation/FuriHalBus.md new file mode 100644 index 00000000000..5c754018b93 --- /dev/null +++ b/documentation/FuriHalBus.md @@ -0,0 +1,113 @@ +# Using FuriHalBus API + +## Basic info + +On system startup, most of the peripheral devices are under reset and not clocked by default. This is done to reduce power consumption and to guarantee that the device will always be in the same state before use. +Some crucial peripherals are enabled right away by the system, others must be explicitly enabled by the user code. + +**NOTE:** Here and afterwards the word *"system"* refers to any code belonging to the operating system, hardware drivers or built-in applications. + +To **ENABLE** a peripheral, call `furi_hal_bus_enable()`. At the time of the call, the peripheral in question MUST be disabled, otherwise a crash will occur to indicate improper use. This means that any given peripheral cannot be enabled twice or more without disabling it first. + +To **DISABLE** a peripheral, call `furi_hal_bus_disable()`. Likewise, the peripheral in question MUST be enabled, otherwise a crash will occur. + +To **RESET** a peripheral, call `furi_hal_bus_reset()`. The peripheral in question MUST be enabled, otherwise a crash will occur. This method is used whenever it is necessary to reset all the peripheral's registers to their initial states without disabling it. + +## Peripherals + +Built-in peripherals are divided into three categories: +- Enabled by the system on startup, never disabled; +- Enabled and disabled by the system on demand; +- Enabled and disabled by the user code. + +### Always-on peripherals + +Below is the list of peripherals that are enabled by the system. The user code must NEVER attempt to disable them. If a corresponding API is provided, the user code must employ it in order to access the peripheral. + +*Table 1* - Peripherals enabled by the system + +| Peripheral | Enabled at | +| :-----------: | :-----------------------: | +| DMA1 | `furi_hal_dma.c` | +| DMA2 | -- | +| DMAMUX | -- | +| GPIOA | `furi_hal_resources.c` | +| GPIOB | -- | +| GPIOC | -- | +| GPIOD | -- | +| GPIOE | -- | +| GPIOH | -- | +| PKA | `furi_hal_bt.c` | +| AES2 | -- | +| HSEM | -- | +| IPCC | -- | +| FLASH | enabled by hardware | + +### On-demand system peripherals + +Below is the list of peripherals that are enabled and disabled by the system. The user code must avoid using them directly, preferring the respective APIs instead. + +When not using the API, these peripherals MUST be enabled by the user code and then disabled when not needed anymore. + +*Table 2* - Peripherals enabled and disabled by the system + +| Peripheral | API header file | +| :-----------: | :-------------------: | +| RNG | `furi_hal_random.h` | +| SPI1 | `furi_hal_spi.h` | +| SPI2 | -- | +| I2C1 | `furi_hal_i2c.h` | +| I2C3 | -- | +| USART1 | `furi_hal_uart.h` | +| LPUART1 | -- | +| USB | `furi_hal_usb.h` | + +### On-demand shared peripherals + +Below is the list of peripherals that are not enabled by default and MUST be enabled by the user code each time it accesses them. + +Note that some of these peripherals may also be used by the system to implement its certain features. +The system will take over any given peripheral only when the respective feature is in use. + +*Table 3* - Peripherals enabled and disabled by user + +| Peripheral | System | Purpose | +| :-----------: | :-------: | ------------------------------------- | +| CRC | | | +| TSC | | | +| ADC | | | +| QUADSPI | | | +| TIM1 | yes | subghz, lfrfid, nfc, infrared, etc... | +| TIM2 | yes | -- | +| TIM16 | yes | speaker | +| TIM17 | | | +| LPTIM1 | yes | tickless idle timer | +| LPTIM2 | yes | pwm | +| SAI1 | | | +| LCD | | | + + +## DMA + +The DMA1,2 peripherals are a special case in that they have multiple independent channels. Some of the channels may be in use by the system. + +Below is the list of DMA channels and their usage by the system. + +*Table 4* - DMA channels + +| DMA | Channel | System | Purpose | +| :---: | :-------: | :-------: | ------------------------- | +| DMA1 | 1 | yes | digital signal | +| -- | 2 | yes | -- | +| -- | 3 | | | +| -- | 4 | yes | pulse reader | +| -- | 5 | | | +| -- | 6 | | | +| -- | 7 | | | +| DMA2 | 1 | yes | infrared, lfrfid, subghz | +| -- | 2 | yes | -- | +| -- | 3 | yes | SPI | +| -- | 4 | yes | SPI | +| -- | 5 | | | +| -- | 6 | | | +| -- | 7 | | | diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index 08aa6913366..92c8697aafc 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,27.1,, +Version,+,28.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -40,8 +40,10 @@ Header,-,firmware/targets/f18/furi_hal/furi_hal_power_calibration.h,, Header,+,firmware/targets/f18/furi_hal/furi_hal_resources.h,, Header,+,firmware/targets/f18/furi_hal/furi_hal_spi_config.h,, Header,+,firmware/targets/f18/furi_hal/furi_hal_target_hw.h,, +Header,+,firmware/targets/f7/furi_hal/furi_hal_bus.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_clock.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_console.h,, +Header,+,firmware/targets/f7/furi_hal/furi_hal_dma.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_flash.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_gpio.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_i2c_config.h,, @@ -873,6 +875,12 @@ Function,+,furi_hal_bt_stop_tone_tx,void, Function,+,furi_hal_bt_unlock_core2,void, Function,+,furi_hal_bt_update_battery_level,void,uint8_t Function,+,furi_hal_bt_update_power_state,void, +Function,+,furi_hal_bus_deinit_early,void, +Function,+,furi_hal_bus_disable,void,FuriHalBus +Function,+,furi_hal_bus_enable,void,FuriHalBus +Function,+,furi_hal_bus_init_early,void, +Function,+,furi_hal_bus_is_enabled,_Bool,FuriHalBus +Function,+,furi_hal_bus_reset,void,FuriHalBus Function,+,furi_hal_cdc_get_ctrl_line_state,uint8_t,uint8_t Function,+,furi_hal_cdc_get_port_settings,usb_cdc_line_coding*,uint8_t Function,+,furi_hal_cdc_receive,int32_t,"uint8_t, uint8_t*, uint16_t" @@ -915,6 +923,8 @@ Function,+,furi_hal_debug_disable,void, Function,+,furi_hal_debug_enable,void, Function,+,furi_hal_debug_is_gdb_session_active,_Bool, Function,-,furi_hal_deinit_early,void, +Function,+,furi_hal_dma_deinit_early,void, +Function,+,furi_hal_dma_init_early,void, Function,-,furi_hal_flash_erase,void,uint8_t Function,-,furi_hal_flash_get_base,size_t, Function,-,furi_hal_flash_get_cycles_count,size_t, @@ -1033,6 +1043,7 @@ Function,+,furi_hal_pwm_start,void,"FuriHalPwmOutputId, uint32_t, uint8_t" Function,+,furi_hal_pwm_stop,void,FuriHalPwmOutputId Function,+,furi_hal_random_fill_buf,void,"uint8_t*, uint32_t" Function,+,furi_hal_random_get,uint32_t, +Function,+,furi_hal_random_init,void, Function,+,furi_hal_region_get,const FuriHalRegion*, Function,+,furi_hal_region_get_band,const FuriHalRegionBand*,uint32_t Function,+,furi_hal_region_get_name,const char*, diff --git a/firmware/targets/f18/furi_hal/furi_hal.c b/firmware/targets/f18/furi_hal/furi_hal.c index 4064dd64726..5f4e6165dca 100644 --- a/firmware/targets/f18/furi_hal/furi_hal.c +++ b/firmware/targets/f18/furi_hal/furi_hal.c @@ -9,6 +9,8 @@ void furi_hal_init_early() { furi_hal_cortex_init_early(); furi_hal_clock_init_early(); + furi_hal_bus_init_early(); + furi_hal_dma_init_early(); furi_hal_resources_init_early(); furi_hal_os_init(); furi_hal_spi_config_init_early(); @@ -22,12 +24,15 @@ void furi_hal_deinit_early() { furi_hal_i2c_deinit_early(); furi_hal_spi_config_deinit_early(); furi_hal_resources_deinit_early(); + furi_hal_dma_deinit_early(); + furi_hal_bus_deinit_early(); furi_hal_clock_deinit_early(); } void furi_hal_init() { furi_hal_mpu_init(); furi_hal_clock_init(); + furi_hal_random_init(); furi_hal_console_init(); furi_hal_rtc_init(); furi_hal_interrupt_init(); diff --git a/firmware/targets/f18/furi_hal/furi_hal_resources.c b/firmware/targets/f18/furi_hal/furi_hal_resources.c index 6db483dbcbb..32c9b619c4c 100644 --- a/firmware/targets/f18/furi_hal/furi_hal_resources.c +++ b/firmware/targets/f18/furi_hal/furi_hal_resources.c @@ -1,4 +1,5 @@ #include +#include #include #include @@ -118,6 +119,13 @@ static void furi_hal_resources_init_input_pins(GpioMode mode) { } void furi_hal_resources_init_early() { + furi_hal_bus_enable(FuriHalBusGPIOA); + furi_hal_bus_enable(FuriHalBusGPIOB); + furi_hal_bus_enable(FuriHalBusGPIOC); + furi_hal_bus_enable(FuriHalBusGPIOD); + furi_hal_bus_enable(FuriHalBusGPIOE); + furi_hal_bus_enable(FuriHalBusGPIOH); + furi_hal_resources_init_input_pins(GpioModeInput); // SD Card stepdown control @@ -162,6 +170,12 @@ void furi_hal_resources_init_early() { void furi_hal_resources_deinit_early() { furi_hal_resources_init_input_pins(GpioModeAnalog); + furi_hal_bus_disable(FuriHalBusGPIOA); + furi_hal_bus_disable(FuriHalBusGPIOB); + furi_hal_bus_disable(FuriHalBusGPIOC); + furi_hal_bus_disable(FuriHalBusGPIOD); + furi_hal_bus_disable(FuriHalBusGPIOE); + furi_hal_bus_disable(FuriHalBusGPIOH); } void furi_hal_resources_init() { diff --git a/firmware/targets/f18/furi_hal/furi_hal_spi_config.c b/firmware/targets/f18/furi_hal/furi_hal_spi_config.c index 0fbe55e2ac9..5ac84906f82 100644 --- a/firmware/targets/f18/furi_hal/furi_hal_spi_config.c +++ b/firmware/targets/f18/furi_hal/furi_hal_spi_config.c @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -96,28 +97,17 @@ void furi_hal_spi_config_init() { static void furi_hal_spi_bus_r_event_callback(FuriHalSpiBus* bus, FuriHalSpiBusEvent event) { if(event == FuriHalSpiBusEventInit) { furi_hal_spi_bus_r_mutex = furi_mutex_alloc(FuriMutexTypeNormal); - FURI_CRITICAL_ENTER(); - LL_APB2_GRP1_ForceReset(LL_APB2_GRP1_PERIPH_SPI1); - FURI_CRITICAL_EXIT(); bus->current_handle = NULL; } else if(event == FuriHalSpiBusEventDeinit) { furi_mutex_free(furi_hal_spi_bus_r_mutex); - FURI_CRITICAL_ENTER(); - LL_APB2_GRP1_ForceReset(LL_APB2_GRP1_PERIPH_SPI1); - LL_APB2_GRP1_ReleaseReset(LL_APB2_GRP1_PERIPH_SPI1); - FURI_CRITICAL_EXIT(); } else if(event == FuriHalSpiBusEventLock) { furi_check(furi_mutex_acquire(furi_hal_spi_bus_r_mutex, FuriWaitForever) == FuriStatusOk); } else if(event == FuriHalSpiBusEventUnlock) { furi_check(furi_mutex_release(furi_hal_spi_bus_r_mutex) == FuriStatusOk); } else if(event == FuriHalSpiBusEventActivate) { - FURI_CRITICAL_ENTER(); - LL_APB2_GRP1_ReleaseReset(LL_APB2_GRP1_PERIPH_SPI1); - FURI_CRITICAL_EXIT(); + furi_hal_bus_enable(FuriHalBusSPI1); } else if(event == FuriHalSpiBusEventDeactivate) { - FURI_CRITICAL_ENTER(); - LL_APB2_GRP1_ForceReset(LL_APB2_GRP1_PERIPH_SPI1); - FURI_CRITICAL_EXIT(); + furi_hal_bus_disable(FuriHalBusSPI1); } } @@ -131,28 +121,17 @@ FuriMutex* furi_hal_spi_bus_d_mutex = NULL; static void furi_hal_spi_bus_d_event_callback(FuriHalSpiBus* bus, FuriHalSpiBusEvent event) { if(event == FuriHalSpiBusEventInit) { furi_hal_spi_bus_d_mutex = furi_mutex_alloc(FuriMutexTypeNormal); - FURI_CRITICAL_ENTER(); - LL_APB1_GRP1_ForceReset(LL_APB1_GRP1_PERIPH_SPI2); - FURI_CRITICAL_EXIT(); bus->current_handle = NULL; } else if(event == FuriHalSpiBusEventDeinit) { furi_mutex_free(furi_hal_spi_bus_d_mutex); - FURI_CRITICAL_ENTER(); - LL_APB1_GRP1_ForceReset(LL_APB1_GRP1_PERIPH_SPI2); - LL_APB1_GRP1_ReleaseReset(LL_APB1_GRP1_PERIPH_SPI2); - FURI_CRITICAL_EXIT(); } else if(event == FuriHalSpiBusEventLock) { furi_check(furi_mutex_acquire(furi_hal_spi_bus_d_mutex, FuriWaitForever) == FuriStatusOk); } else if(event == FuriHalSpiBusEventUnlock) { furi_check(furi_mutex_release(furi_hal_spi_bus_d_mutex) == FuriStatusOk); } else if(event == FuriHalSpiBusEventActivate) { - FURI_CRITICAL_ENTER(); - LL_APB1_GRP1_ReleaseReset(LL_APB1_GRP1_PERIPH_SPI2); - FURI_CRITICAL_EXIT(); + furi_hal_bus_enable(FuriHalBusSPI2); } else if(event == FuriHalSpiBusEventDeactivate) { - FURI_CRITICAL_ENTER(); - LL_APB1_GRP1_ForceReset(LL_APB1_GRP1_PERIPH_SPI2); - FURI_CRITICAL_EXIT(); + furi_hal_bus_disable(FuriHalBusSPI2); } } diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 1055cb2bfd4..4b48949d99f 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,27.1,, +Version,+,28.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -36,8 +36,10 @@ Header,+,applications/services/notification/notification_messages.h,, Header,+,applications/services/power/power_service/power.h,, Header,+,applications/services/rpc/rpc_app.h,, Header,+,applications/services/storage/storage.h,, +Header,+,firmware/targets/f7/furi_hal/furi_hal_bus.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_clock.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_console.h,, +Header,+,firmware/targets/f7/furi_hal/furi_hal_dma.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_flash.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_gpio.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_i2c_config.h,, @@ -1065,6 +1067,12 @@ Function,+,furi_hal_bt_stop_tone_tx,void, Function,+,furi_hal_bt_unlock_core2,void, Function,+,furi_hal_bt_update_battery_level,void,uint8_t Function,+,furi_hal_bt_update_power_state,void, +Function,+,furi_hal_bus_deinit_early,void, +Function,+,furi_hal_bus_disable,void,FuriHalBus +Function,+,furi_hal_bus_enable,void,FuriHalBus +Function,+,furi_hal_bus_init_early,void, +Function,+,furi_hal_bus_is_enabled,_Bool,FuriHalBus +Function,+,furi_hal_bus_reset,void,FuriHalBus Function,+,furi_hal_cdc_get_ctrl_line_state,uint8_t,uint8_t Function,+,furi_hal_cdc_get_port_settings,usb_cdc_line_coding*,uint8_t Function,+,furi_hal_cdc_receive,int32_t,"uint8_t, uint8_t*, uint16_t" @@ -1107,6 +1115,8 @@ Function,+,furi_hal_debug_disable,void, Function,+,furi_hal_debug_enable,void, Function,+,furi_hal_debug_is_gdb_session_active,_Bool, Function,-,furi_hal_deinit_early,void, +Function,+,furi_hal_dma_deinit_early,void, +Function,+,furi_hal_dma_init_early,void, Function,-,furi_hal_flash_erase,void,uint8_t Function,-,furi_hal_flash_get_base,size_t, Function,-,furi_hal_flash_get_cycles_count,size_t, @@ -1273,6 +1283,7 @@ Function,+,furi_hal_pwm_start,void,"FuriHalPwmOutputId, uint32_t, uint8_t" Function,+,furi_hal_pwm_stop,void,FuriHalPwmOutputId Function,+,furi_hal_random_fill_buf,void,"uint8_t*, uint32_t" Function,+,furi_hal_random_get,uint32_t, +Function,+,furi_hal_random_init,void, Function,+,furi_hal_region_get,const FuriHalRegion*, Function,+,furi_hal_region_get_band,const FuriHalRegionBand*,uint32_t Function,+,furi_hal_region_get_name,const char*, @@ -1284,31 +1295,23 @@ Function,-,furi_hal_resources_deinit_early,void, Function,+,furi_hal_resources_get_ext_pin_number,int32_t,const GpioPin* Function,-,furi_hal_resources_init,void, Function,-,furi_hal_resources_init_early,void, -Function,+,furi_hal_rfid_change_read_config,void,"float, float" Function,+,furi_hal_rfid_comp_set_callback,void,"FuriHalRfidCompCallback, void*" Function,+,furi_hal_rfid_comp_start,void, Function,+,furi_hal_rfid_comp_stop,void, Function,-,furi_hal_rfid_init,void, Function,+,furi_hal_rfid_pin_pull_pulldown,void, Function,+,furi_hal_rfid_pin_pull_release,void, -Function,+,furi_hal_rfid_pins_emulate,void, -Function,+,furi_hal_rfid_pins_read,void, Function,+,furi_hal_rfid_pins_reset,void, -Function,+,furi_hal_rfid_set_emulate_period,void,uint32_t -Function,+,furi_hal_rfid_set_emulate_pulse,void,uint32_t Function,+,furi_hal_rfid_set_read_period,void,uint32_t Function,+,furi_hal_rfid_set_read_pulse,void,uint32_t -Function,+,furi_hal_rfid_tim_emulate,void,float Function,+,furi_hal_rfid_tim_emulate_dma_start,void,"uint32_t*, uint32_t*, size_t, FuriHalRfidDMACallback, void*" Function,+,furi_hal_rfid_tim_emulate_dma_stop,void, -Function,+,furi_hal_rfid_tim_emulate_start,void,"FuriHalRfidEmulateCallback, void*" -Function,+,furi_hal_rfid_tim_emulate_stop,void, -Function,+,furi_hal_rfid_tim_read,void,"float, float" Function,+,furi_hal_rfid_tim_read_capture_start,void,"FuriHalRfidReadCaptureCallback, void*" Function,+,furi_hal_rfid_tim_read_capture_stop,void, -Function,+,furi_hal_rfid_tim_read_start,void, +Function,+,furi_hal_rfid_tim_read_continue,void, +Function,+,furi_hal_rfid_tim_read_pause,void, +Function,+,furi_hal_rfid_tim_read_start,void,"float, float" Function,+,furi_hal_rfid_tim_read_stop,void, -Function,+,furi_hal_rfid_tim_reset,void, Function,+,furi_hal_rtc_datetime_to_timestamp,uint32_t,FuriHalRtcDateTime* Function,-,furi_hal_rtc_deinit_early,void, Function,+,furi_hal_rtc_get_boot_mode,FuriHalRtcBootMode, diff --git a/firmware/targets/f7/furi_hal/furi_hal.c b/firmware/targets/f7/furi_hal/furi_hal.c index 1b710bb9637..2062645cdf3 100644 --- a/firmware/targets/f7/furi_hal/furi_hal.c +++ b/firmware/targets/f7/furi_hal/furi_hal.c @@ -9,6 +9,8 @@ void furi_hal_init_early() { furi_hal_cortex_init_early(); furi_hal_clock_init_early(); + furi_hal_bus_init_early(); + furi_hal_dma_init_early(); furi_hal_resources_init_early(); furi_hal_os_init(); furi_hal_spi_config_init_early(); @@ -22,12 +24,15 @@ void furi_hal_deinit_early() { furi_hal_i2c_deinit_early(); furi_hal_spi_config_deinit_early(); furi_hal_resources_deinit_early(); + furi_hal_dma_deinit_early(); + furi_hal_bus_deinit_early(); furi_hal_clock_deinit_early(); } void furi_hal_init() { furi_hal_mpu_init(); furi_hal_clock_init(); + furi_hal_random_init(); furi_hal_console_init(); furi_hal_rtc_init(); furi_hal_interrupt_init(); diff --git a/firmware/targets/f7/furi_hal/furi_hal_bt.c b/firmware/targets/f7/furi_hal/furi_hal_bt.c index 048a8b3090b..cec6b8204f8 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_bt.c +++ b/firmware/targets/f7/furi_hal/furi_hal_bt.c @@ -7,6 +7,7 @@ #include #include #include +#include #include "battery_service.h" #include @@ -80,6 +81,11 @@ FuriHalBtProfileConfig profile_config[FuriHalBtProfileNumber] = { FuriHalBtProfileConfig* current_profile = NULL; void furi_hal_bt_init() { + furi_hal_bus_enable(FuriHalBusHSEM); + furi_hal_bus_enable(FuriHalBusIPCC); + furi_hal_bus_enable(FuriHalBusAES2); + furi_hal_bus_enable(FuriHalBusPKA); + if(!furi_hal_bt_core2_mtx) { furi_hal_bt_core2_mtx = furi_mutex_alloc(FuriMutexTypeNormal); furi_assert(furi_hal_bt_core2_mtx); @@ -256,6 +262,11 @@ void furi_hal_bt_reinit() { furi_delay_ms(100); ble_glue_thread_stop(); + furi_hal_bus_disable(FuriHalBusHSEM); + furi_hal_bus_disable(FuriHalBusIPCC); + furi_hal_bus_disable(FuriHalBusAES2); + furi_hal_bus_disable(FuriHalBusPKA); + FURI_LOG_I(TAG, "Start BT initialization"); furi_hal_bt_init(); diff --git a/firmware/targets/f7/furi_hal/furi_hal_bus.c b/firmware/targets/f7/furi_hal/furi_hal_bus.c new file mode 100644 index 00000000000..0a07e9f9e6b --- /dev/null +++ b/firmware/targets/f7/furi_hal/furi_hal_bus.c @@ -0,0 +1,302 @@ +#include +#include + +#include + +/* Bus bitmask definitions */ +#define FURI_HAL_BUS_IGNORE (0x0U) + +#define FURI_HAL_BUS_AHB1_GRP1 \ + (LL_AHB1_GRP1_PERIPH_DMA1 | LL_AHB1_GRP1_PERIPH_DMA2 | LL_AHB1_GRP1_PERIPH_DMAMUX1 | \ + LL_AHB1_GRP1_PERIPH_CRC | LL_AHB1_GRP1_PERIPH_TSC) + +#if defined(ADC_SUPPORT_5_MSPS) +#define FURI_HAL_BUS_AHB2_GRP1 \ + (LL_AHB2_GRP1_PERIPH_GPIOA | LL_AHB2_GRP1_PERIPH_GPIOB | LL_AHB2_GRP1_PERIPH_GPIOC | \ + LL_AHB2_GRP1_PERIPH_GPIOD | LL_AHB2_GRP1_PERIPH_GPIOE | LL_AHB2_GRP1_PERIPH_GPIOH | \ + LL_AHB2_GRP1_PERIPH_ADC | LL_AHB2_GRP1_PERIPH_AES1) + +#define FURI_HAL_BUS_APB2_GRP1 \ + (LL_APB2_GRP1_PERIPH_TIM1 | LL_APB2_GRP1_PERIPH_SPI1 | LL_APB2_GRP1_PERIPH_USART1 | \ + LL_APB2_GRP1_PERIPH_TIM16 | LL_APB2_GRP1_PERIPH_TIM17 | LL_APB2_GRP1_PERIPH_SAI1) +#else +#define FURI_HAL_BUS_AHB2_GRP1 \ + (LL_AHB2_GRP1_PERIPH_GPIOA | LL_AHB2_GRP1_PERIPH_GPIOB | LL_AHB2_GRP1_PERIPH_GPIOC | \ + LL_AHB2_GRP1_PERIPH_GPIOD | LL_AHB2_GRP1_PERIPH_GPIOE | LL_AHB2_GRP1_PERIPH_GPIOH | \ + LL_AHB2_GRP1_PERIPH_AES1) + +#define FURI_HAL_BUS_APB2_GRP1 \ + (LL_APB2_GRP1_PERIPH_ADC | LL_APB2_GRP1_PERIPH_TIM1 | LL_APB2_GRP1_PERIPH_SPI1 | \ + LL_APB2_GRP1_PERIPH_USART1 | LL_APB2_GRP1_PERIPH_TIM16 | LL_APB2_GRP1_PERIPH_TIM17 | \ + LL_APB2_GRP1_PERIPH_SAI1) +#endif + +#define FURI_HAL_BUS_AHB3_GRP1 \ + (LL_AHB3_GRP1_PERIPH_QUADSPI | LL_AHB3_GRP1_PERIPH_PKA | LL_AHB3_GRP1_PERIPH_AES2 | \ + LL_AHB3_GRP1_PERIPH_RNG | LL_AHB3_GRP1_PERIPH_HSEM | LL_AHB3_GRP1_PERIPH_IPCC) +// LL_AHB3_GRP1_PERIPH_FLASH enabled by default + +#define FURI_HAL_BUS_APB1_GRP1 \ + (LL_APB1_GRP1_PERIPH_TIM2 | LL_APB1_GRP1_PERIPH_LCD | LL_APB1_GRP1_PERIPH_RTCAPB | \ + LL_APB1_GRP1_PERIPH_SPI2 | LL_APB1_GRP1_PERIPH_I2C1 | LL_APB1_GRP1_PERIPH_I2C3 | \ + LL_APB1_GRP1_PERIPH_CRS | LL_APB1_GRP1_PERIPH_USB | LL_APB1_GRP1_PERIPH_LPTIM1) + +#define FURI_HAL_BUS_APB1_GRP2 (LL_APB1_GRP2_PERIPH_LPUART1 | LL_APB1_GRP2_PERIPH_LPTIM2) +#define FURI_HAL_BUS_APB3_GRP1 (LL_APB3_GRP1_PERIPH_RF) + +/* Test macro definitions */ +#define FURI_HAL_BUS_IS_ALL_CLEAR(reg, value) (READ_BIT((reg), (value)) == 0UL) +#define FURI_HAL_BUS_IS_ALL_SET(reg, value) (READ_BIT((reg), (value)) == (value)) + +#define FURI_HAL_BUS_IS_CLOCK_ENABLED(bus, value, ...) \ + (FURI_HAL_BUS_IS_ALL_SET(RCC->bus##ENR##__VA_ARGS__, (value))) +#define FURI_HAL_BUS_IS_CLOCK_DISABLED(bus, value, ...) \ + (FURI_HAL_BUS_IS_ALL_CLEAR(RCC->bus##ENR##__VA_ARGS__, (value))) + +#define FURI_HAL_BUS_IS_RESET_ASSERTED(bus, value, ...) \ + (FURI_HAL_BUS_IS_ALL_SET(RCC->bus##RSTR##__VA_ARGS__, (value))) +#define FURI_HAL_BUS_IS_RESET_DEASSERTED(bus, value, ...) \ + (FURI_HAL_BUS_IS_ALL_CLEAR(RCC->bus##RSTR##__VA_ARGS__, (value))) + +#define FURI_HAL_BUS_IS_PERIPH_ENABLED(bus, value, ...) \ + (FURI_HAL_BUS_IS_RESET_DEASSERTED(bus, (value), __VA_ARGS__) && \ + FURI_HAL_BUS_IS_CLOCK_ENABLED(bus, (value), __VA_ARGS__)) + +#define FURI_HAL_BUS_IS_PERIPH_DISABLED(bus, value, ...) \ + (FURI_HAL_BUS_IS_CLOCK_DISABLED(bus, (value), __VA_ARGS__) && \ + FURI_HAL_BUS_IS_RESET_ASSERTED(bus, (value), __VA_ARGS__)) + +/* Control macro definitions */ +#define FURI_HAL_BUS_RESET_ASSERT(bus, value, grp) LL_##bus##_GRP##grp##_ForceReset(value) +#define FURI_HAL_BUS_RESET_DEASSERT(bus, value, grp) LL_##bus##_GRP##grp##_ReleaseReset(value) + +#define FURI_HAL_BUS_CLOCK_ENABLE(bus, value, grp) LL_##bus##_GRP##grp##_EnableClock(value) +#define FURI_HAL_BUS_CLOCK_DISABLE(bus, value, grp) LL_##bus##_GRP##grp##_DisableClock(value) + +#define FURI_HAL_BUS_PERIPH_ENABLE(bus, value, grp) \ + FURI_HAL_BUS_CLOCK_ENABLE(bus, value, grp); \ + FURI_HAL_BUS_RESET_DEASSERT(bus, value, grp) + +#define FURI_HAL_BUS_PERIPH_DISABLE(bus, value, grp) \ + FURI_HAL_BUS_RESET_ASSERT(bus, value, grp); \ + FURI_HAL_BUS_CLOCK_DISABLE(bus, value, grp) + +#define FURI_HAL_BUS_PERIPH_RESET(bus, value, grp) \ + FURI_HAL_BUS_RESET_ASSERT(bus, value, grp); \ + FURI_HAL_BUS_RESET_DEASSERT(bus, value, grp) + +static const uint32_t furi_hal_bus[] = { + [FuriHalBusAHB1_GRP1] = FURI_HAL_BUS_AHB1_GRP1, + [FuriHalBusDMA1] = LL_AHB1_GRP1_PERIPH_DMA1, + [FuriHalBusDMA2] = LL_AHB1_GRP1_PERIPH_DMA2, + [FuriHalBusDMAMUX1] = LL_AHB1_GRP1_PERIPH_DMAMUX1, + [FuriHalBusCRC] = LL_AHB1_GRP1_PERIPH_CRC, + [FuriHalBusTSC] = LL_AHB1_GRP1_PERIPH_TSC, + + [FuriHalBusAHB2_GRP1] = FURI_HAL_BUS_AHB2_GRP1, + [FuriHalBusGPIOA] = LL_AHB2_GRP1_PERIPH_GPIOA, + [FuriHalBusGPIOB] = LL_AHB2_GRP1_PERIPH_GPIOB, + [FuriHalBusGPIOC] = LL_AHB2_GRP1_PERIPH_GPIOC, + [FuriHalBusGPIOD] = LL_AHB2_GRP1_PERIPH_GPIOD, + [FuriHalBusGPIOE] = LL_AHB2_GRP1_PERIPH_GPIOE, + [FuriHalBusGPIOH] = LL_AHB2_GRP1_PERIPH_GPIOH, +#if defined(ADC_SUPPORT_5_MSPS) + [FuriHalBusADC] = LL_AHB2_GRP1_PERIPH_ADC, +#endif + [FuriHalBusAES1] = LL_AHB2_GRP1_PERIPH_AES1, + + [FuriHalBusAHB3_GRP1] = FURI_HAL_BUS_AHB3_GRP1, + [FuriHalBusQUADSPI] = LL_AHB3_GRP1_PERIPH_QUADSPI, + [FuriHalBusPKA] = LL_AHB3_GRP1_PERIPH_PKA, + [FuriHalBusAES2] = LL_AHB3_GRP1_PERIPH_AES2, + [FuriHalBusRNG] = LL_AHB3_GRP1_PERIPH_RNG, + [FuriHalBusHSEM] = LL_AHB3_GRP1_PERIPH_HSEM, + [FuriHalBusIPCC] = LL_AHB3_GRP1_PERIPH_IPCC, + [FuriHalBusFLASH] = LL_AHB3_GRP1_PERIPH_FLASH, + + [FuriHalBusAPB1_GRP1] = FURI_HAL_BUS_APB1_GRP1, + [FuriHalBusTIM2] = LL_APB1_GRP1_PERIPH_TIM2, + [FuriHalBusLCD] = LL_APB1_GRP1_PERIPH_LCD, + [FuriHalBusSPI2] = LL_APB1_GRP1_PERIPH_SPI2, + [FuriHalBusI2C1] = LL_APB1_GRP1_PERIPH_I2C1, + [FuriHalBusI2C3] = LL_APB1_GRP1_PERIPH_I2C3, + [FuriHalBusCRS] = LL_APB1_GRP1_PERIPH_CRS, + [FuriHalBusUSB] = LL_APB1_GRP1_PERIPH_USB, + [FuriHalBusLPTIM1] = LL_APB1_GRP1_PERIPH_LPTIM1, + + [FuriHalBusAPB1_GRP2] = FURI_HAL_BUS_APB1_GRP2, + [FuriHalBusLPUART1] = LL_APB1_GRP2_PERIPH_LPUART1, + [FuriHalBusLPTIM2] = LL_APB1_GRP2_PERIPH_LPTIM2, + + [FuriHalBusAPB2_GRP1] = FURI_HAL_BUS_APB2_GRP1, +#if defined(ADC_SUPPORT_2_5_MSPS) + [FuriHalBusADC] = LL_APB2_GRP1_PERIPH_ADC, +#endif + [FuriHalBusTIM1] = LL_APB2_GRP1_PERIPH_TIM1, + [FuriHalBusSPI1] = LL_APB2_GRP1_PERIPH_SPI1, + [FuriHalBusUSART1] = LL_APB2_GRP1_PERIPH_USART1, + [FuriHalBusTIM16] = LL_APB2_GRP1_PERIPH_TIM16, + [FuriHalBusTIM17] = LL_APB2_GRP1_PERIPH_TIM17, + [FuriHalBusSAI1] = LL_APB2_GRP1_PERIPH_SAI1, + + [FuriHalBusAPB3_GRP1] = FURI_HAL_BUS_IGNORE, // APB3_GRP1 clocking cannot be changed + [FuriHalBusRF] = LL_APB3_GRP1_PERIPH_RF, +}; + +void furi_hal_bus_init_early() { + FURI_CRITICAL_ENTER(); + + FURI_HAL_BUS_PERIPH_DISABLE(AHB1, FURI_HAL_BUS_AHB1_GRP1, 1); + FURI_HAL_BUS_PERIPH_DISABLE(AHB2, FURI_HAL_BUS_AHB2_GRP1, 1); + FURI_HAL_BUS_PERIPH_DISABLE(AHB3, FURI_HAL_BUS_AHB3_GRP1, 1); + FURI_HAL_BUS_PERIPH_DISABLE(APB1, FURI_HAL_BUS_APB1_GRP1, 1); + FURI_HAL_BUS_PERIPH_DISABLE(APB1, FURI_HAL_BUS_APB1_GRP2, 2); + FURI_HAL_BUS_PERIPH_DISABLE(APB2, FURI_HAL_BUS_APB2_GRP1, 1); + + FURI_HAL_BUS_RESET_ASSERT(APB3, FURI_HAL_BUS_APB3_GRP1, 1); + + FURI_CRITICAL_EXIT(); +} + +void furi_hal_bus_deinit_early() { + FURI_CRITICAL_ENTER(); + + FURI_HAL_BUS_PERIPH_ENABLE(AHB1, FURI_HAL_BUS_AHB1_GRP1, 1); + FURI_HAL_BUS_PERIPH_ENABLE(AHB2, FURI_HAL_BUS_AHB2_GRP1, 1); + FURI_HAL_BUS_PERIPH_ENABLE(AHB3, FURI_HAL_BUS_AHB3_GRP1, 1); + FURI_HAL_BUS_PERIPH_ENABLE(APB1, FURI_HAL_BUS_APB1_GRP1, 1); + FURI_HAL_BUS_PERIPH_ENABLE(APB1, FURI_HAL_BUS_APB1_GRP2, 2); + FURI_HAL_BUS_PERIPH_ENABLE(APB2, FURI_HAL_BUS_APB2_GRP1, 1); + + FURI_HAL_BUS_RESET_DEASSERT(APB3, FURI_HAL_BUS_APB3_GRP1, 1); + + FURI_CRITICAL_EXIT(); +} + +void furi_hal_bus_enable(FuriHalBus bus) { + furi_check(bus < FuriHalBusMAX); + const uint32_t value = furi_hal_bus[bus]; + if(!value) { + return; + } + + FURI_CRITICAL_ENTER(); + if(bus < FuriHalBusAHB2_GRP1) { + furi_check(FURI_HAL_BUS_IS_PERIPH_DISABLED(AHB1, value)); + FURI_HAL_BUS_PERIPH_ENABLE(AHB1, value, 1); + } else if(bus < FuriHalBusAHB3_GRP1) { + furi_check(FURI_HAL_BUS_IS_PERIPH_DISABLED(AHB2, value)); + FURI_HAL_BUS_PERIPH_ENABLE(AHB2, value, 1); + } else if(bus < FuriHalBusAPB1_GRP1) { + furi_check(FURI_HAL_BUS_IS_PERIPH_DISABLED(AHB3, value)); + FURI_HAL_BUS_PERIPH_ENABLE(AHB3, value, 1); + } else if(bus < FuriHalBusAPB1_GRP2) { + furi_check(FURI_HAL_BUS_IS_PERIPH_DISABLED(APB1, value, 1)); + FURI_HAL_BUS_PERIPH_ENABLE(APB1, value, 1); + } else if(bus < FuriHalBusAPB2_GRP1) { + furi_check(FURI_HAL_BUS_IS_PERIPH_DISABLED(APB1, value, 2)); + FURI_HAL_BUS_PERIPH_ENABLE(APB1, value, 2); + } else if(bus < FuriHalBusAPB3_GRP1) { + furi_check(FURI_HAL_BUS_IS_PERIPH_DISABLED(APB2, value)); + FURI_HAL_BUS_PERIPH_ENABLE(APB2, value, 1); + } else { + furi_check(FURI_HAL_BUS_IS_RESET_ASSERTED(APB3, value)); + FURI_HAL_BUS_RESET_DEASSERT(APB3, FURI_HAL_BUS_APB3_GRP1, 1); + } + FURI_CRITICAL_EXIT(); +} + +void furi_hal_bus_reset(FuriHalBus bus) { + furi_check(bus < FuriHalBusMAX); + const uint32_t value = furi_hal_bus[bus]; + if(!value) { + return; + } + + FURI_CRITICAL_ENTER(); + if(bus < FuriHalBusAHB2_GRP1) { + furi_check(FURI_HAL_BUS_IS_PERIPH_ENABLED(AHB1, value)); + FURI_HAL_BUS_PERIPH_RESET(AHB1, value, 1); + } else if(bus < FuriHalBusAHB3_GRP1) { + furi_check(FURI_HAL_BUS_IS_PERIPH_ENABLED(AHB2, value)); + FURI_HAL_BUS_PERIPH_RESET(AHB2, value, 1); + } else if(bus < FuriHalBusAPB1_GRP1) { + furi_check(FURI_HAL_BUS_IS_PERIPH_ENABLED(AHB3, value)); + FURI_HAL_BUS_PERIPH_RESET(AHB3, value, 1); + } else if(bus < FuriHalBusAPB1_GRP2) { + furi_check(FURI_HAL_BUS_IS_PERIPH_ENABLED(APB1, value, 1)); + FURI_HAL_BUS_PERIPH_RESET(APB1, value, 1); + } else if(bus < FuriHalBusAPB2_GRP1) { + furi_check(FURI_HAL_BUS_IS_PERIPH_ENABLED(APB1, value, 2)); + FURI_HAL_BUS_PERIPH_RESET(APB1, value, 2); + } else if(bus < FuriHalBusAPB3_GRP1) { + furi_check(FURI_HAL_BUS_IS_PERIPH_ENABLED(APB2, value)); + FURI_HAL_BUS_PERIPH_RESET(APB2, value, 1); + } else { + furi_check(FURI_HAL_BUS_IS_RESET_DEASSERTED(APB3, value)); + FURI_HAL_BUS_PERIPH_RESET(APB3, value, 1); + } + FURI_CRITICAL_EXIT(); +} + +void furi_hal_bus_disable(FuriHalBus bus) { + furi_check(bus < FuriHalBusMAX); + const uint32_t value = furi_hal_bus[bus]; + if(!value) { + return; + } + + FURI_CRITICAL_ENTER(); + if(bus < FuriHalBusAHB2_GRP1) { + furi_check(FURI_HAL_BUS_IS_PERIPH_ENABLED(AHB1, value)); + FURI_HAL_BUS_PERIPH_DISABLE(AHB1, value, 1); + } else if(bus < FuriHalBusAHB3_GRP1) { + furi_check(FURI_HAL_BUS_IS_PERIPH_ENABLED(AHB2, value)); + FURI_HAL_BUS_PERIPH_DISABLE(AHB2, value, 1); + } else if(bus < FuriHalBusAPB1_GRP1) { + furi_check(FURI_HAL_BUS_IS_PERIPH_ENABLED(AHB3, value)); + FURI_HAL_BUS_PERIPH_DISABLE(AHB3, value, 1); + } else if(bus < FuriHalBusAPB1_GRP2) { + furi_check(FURI_HAL_BUS_IS_PERIPH_ENABLED(APB1, value, 1)); + FURI_HAL_BUS_PERIPH_DISABLE(APB1, value, 1); + } else if(bus < FuriHalBusAPB2_GRP1) { + furi_check(FURI_HAL_BUS_IS_PERIPH_ENABLED(APB1, value, 2)); + FURI_HAL_BUS_PERIPH_DISABLE(APB1, value, 2); + } else if(bus < FuriHalBusAPB3_GRP1) { + furi_check(FURI_HAL_BUS_IS_PERIPH_ENABLED(APB2, value)); + FURI_HAL_BUS_PERIPH_DISABLE(APB2, value, 1); + } else { + furi_check(FURI_HAL_BUS_IS_RESET_DEASSERTED(APB3, value)); + FURI_HAL_BUS_RESET_ASSERT(APB3, FURI_HAL_BUS_APB3_GRP1, 1); + } + FURI_CRITICAL_EXIT(); +} + +bool furi_hal_bus_is_enabled(FuriHalBus bus) { + furi_check(bus < FuriHalBusMAX); + const uint32_t value = furi_hal_bus[bus]; + if(value == FURI_HAL_BUS_IGNORE) { + return true; + } + + bool ret = false; + FURI_CRITICAL_ENTER(); + if(bus < FuriHalBusAHB2_GRP1) { + ret = FURI_HAL_BUS_IS_PERIPH_ENABLED(AHB1, value); + } else if(bus < FuriHalBusAHB3_GRP1) { + ret = FURI_HAL_BUS_IS_PERIPH_ENABLED(AHB2, value); + } else if(bus < FuriHalBusAPB1_GRP1) { + ret = FURI_HAL_BUS_IS_PERIPH_ENABLED(AHB3, value); + } else if(bus < FuriHalBusAPB1_GRP2) { + ret = FURI_HAL_BUS_IS_PERIPH_ENABLED(APB1, value, 1); + } else if(bus < FuriHalBusAPB2_GRP1) { + ret = FURI_HAL_BUS_IS_PERIPH_ENABLED(APB1, value, 2); + } else if(bus < FuriHalBusAPB3_GRP1) { + ret = FURI_HAL_BUS_IS_PERIPH_ENABLED(APB2, value); + } else { + ret = FURI_HAL_BUS_IS_RESET_DEASSERTED(APB3, value); + } + FURI_CRITICAL_EXIT(); + + return ret; +} diff --git a/firmware/targets/f7/furi_hal/furi_hal_bus.h b/firmware/targets/f7/furi_hal/furi_hal_bus.h new file mode 100644 index 00000000000..ad4bbec3249 --- /dev/null +++ b/firmware/targets/f7/furi_hal/furi_hal_bus.h @@ -0,0 +1,112 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "stm32wbxx.h" +#include "stdbool.h" + +typedef enum { + FuriHalBusAHB1_GRP1, + FuriHalBusDMA1, + FuriHalBusDMA2, + FuriHalBusDMAMUX1, + FuriHalBusCRC, + FuriHalBusTSC, + + FuriHalBusAHB2_GRP1, + FuriHalBusGPIOA, + FuriHalBusGPIOB, + FuriHalBusGPIOC, + FuriHalBusGPIOD, + FuriHalBusGPIOE, + FuriHalBusGPIOH, +#if defined(ADC_SUPPORT_5_MSPS) + FuriHalBusADC, +#endif + FuriHalBusAES1, + + FuriHalBusAHB3_GRP1, + FuriHalBusQUADSPI, + FuriHalBusPKA, + FuriHalBusAES2, + FuriHalBusRNG, + FuriHalBusHSEM, + FuriHalBusIPCC, + FuriHalBusFLASH, + + FuriHalBusAPB1_GRP1, + FuriHalBusTIM2, + FuriHalBusLCD, + FuriHalBusSPI2, + FuriHalBusI2C1, + FuriHalBusI2C3, + FuriHalBusCRS, + FuriHalBusUSB, + FuriHalBusLPTIM1, + + FuriHalBusAPB1_GRP2, + FuriHalBusLPUART1, + FuriHalBusLPTIM2, + + FuriHalBusAPB2_GRP1, +#if defined(ADC_SUPPORT_2_5_MSPS) + FuriHalBusADC, +#endif + FuriHalBusTIM1, + FuriHalBusSPI1, + FuriHalBusUSART1, + FuriHalBusTIM16, + FuriHalBusTIM17, + FuriHalBusSAI1, + + FuriHalBusAPB3_GRP1, + FuriHalBusRF, + + FuriHalBusMAX, +} FuriHalBus; + +/** Early initialization */ +void furi_hal_bus_init_early(); + +/** Early de-initialization */ +void furi_hal_bus_deinit_early(); + +/** + * Enable a peripheral by turning the clocking on and deasserting the reset. + * @param [in] bus Peripheral to be enabled. + * @warning Peripheral must be in disabled state in order to be enabled. + */ +void furi_hal_bus_enable(FuriHalBus bus); + +/** + * Reset a peripheral by sequentially asserting and deasserting the reset. + * @param [in] bus Peripheral to be reset. + * @warning Peripheral must be in enabled state in order to be reset. + */ +void furi_hal_bus_reset(FuriHalBus bus); + +/** + * Disable a peripheral by turning the clocking off and asserting the reset. + * @param [in] bus Peripheral to be disabled. + * @warning Peripheral must be in enabled state in order to be disabled. + */ +void furi_hal_bus_disable(FuriHalBus bus); + +/** Check if peripheral is enabled + * + * @warning FuriHalBusAPB3_GRP1 is a special group of shared peripherals, for + * core1 its clock is always on and the only status we can report is + * peripheral reset status. Check code and Reference Manual for + * details. + * + * @param[in] bus The peripheral to check + * + * @return true if enabled or always enabled, false otherwise + */ +bool furi_hal_bus_is_enabled(FuriHalBus bus); + +#ifdef __cplusplus +} +#endif diff --git a/firmware/targets/f7/furi_hal/furi_hal_clock.c b/firmware/targets/f7/furi_hal/furi_hal_clock.c index a76fbfbca76..9d228f0f55e 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_clock.c +++ b/firmware/targets/f7/furi_hal/furi_hal_clock.c @@ -6,7 +6,6 @@ #include #include #include -#include #define TAG "FuriHalClock" @@ -19,36 +18,9 @@ void furi_hal_clock_init_early() { LL_SetSystemCoreClock(CPU_CLOCK_HZ_EARLY); LL_Init1msTick(SystemCoreClock); - - LL_AHB2_GRP1_EnableClock(LL_AHB2_GRP1_PERIPH_GPIOA); - LL_AHB2_GRP1_EnableClock(LL_AHB2_GRP1_PERIPH_GPIOB); - LL_AHB2_GRP1_EnableClock(LL_AHB2_GRP1_PERIPH_GPIOC); - LL_AHB2_GRP1_EnableClock(LL_AHB2_GRP1_PERIPH_GPIOD); - LL_AHB2_GRP1_EnableClock(LL_AHB2_GRP1_PERIPH_GPIOE); - LL_AHB2_GRP1_EnableClock(LL_AHB2_GRP1_PERIPH_GPIOH); - - LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_SPI1); - LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_SPI2); - - LL_APB1_GRP2_EnableClock(LL_APB1_GRP2_PERIPH_LPTIM2); - - LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_I2C1); - LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_I2C3); } void furi_hal_clock_deinit_early() { - LL_APB1_GRP1_DisableClock(LL_APB1_GRP1_PERIPH_I2C1); - LL_APB1_GRP1_DisableClock(LL_APB1_GRP1_PERIPH_I2C3); - - LL_APB2_GRP1_DisableClock(LL_APB2_GRP1_PERIPH_SPI1); - LL_APB1_GRP1_DisableClock(LL_APB1_GRP1_PERIPH_SPI2); - - LL_AHB2_GRP1_DisableClock(LL_AHB2_GRP1_PERIPH_GPIOA); - LL_AHB2_GRP1_DisableClock(LL_AHB2_GRP1_PERIPH_GPIOB); - LL_AHB2_GRP1_DisableClock(LL_AHB2_GRP1_PERIPH_GPIOC); - LL_AHB2_GRP1_DisableClock(LL_AHB2_GRP1_PERIPH_GPIOD); - LL_AHB2_GRP1_DisableClock(LL_AHB2_GRP1_PERIPH_GPIOE); - LL_AHB2_GRP1_DisableClock(LL_AHB2_GRP1_PERIPH_GPIOH); } void furi_hal_clock_init() { @@ -137,68 +109,12 @@ void furi_hal_clock_init() { SysTick_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), TICK_INT_PRIORITY, 0)); NVIC_EnableIRQ(SysTick_IRQn); - LL_RCC_SetUSARTClockSource(LL_RCC_USART1_CLKSOURCE_PCLK2); - LL_RCC_SetLPUARTClockSource(LL_RCC_LPUART1_CLKSOURCE_PCLK1); - LL_RCC_SetADCClockSource(LL_RCC_ADC_CLKSOURCE_PLLSAI1); - LL_RCC_SetI2CClockSource(LL_RCC_I2C1_CLKSOURCE_PCLK1); - LL_RCC_SetRNGClockSource(LL_RCC_RNG_CLKSOURCE_CLK48); - LL_RCC_SetUSBClockSource(LL_RCC_USB_CLKSOURCE_PLLSAI1); LL_RCC_SetCLK48ClockSource(LL_RCC_CLK48_CLKSOURCE_PLLSAI1); LL_RCC_HSI_EnableInStopMode(); // Ensure that MR is capable of work in STOP0 LL_RCC_SetSMPSClockSource(LL_RCC_SMPS_CLKSOURCE_HSE); LL_RCC_SetSMPSPrescaler(LL_RCC_SMPS_DIV_1); LL_RCC_SetRFWKPClockSource(LL_RCC_RFWKP_CLKSOURCE_LSE); - // AHB1 GRP1 - LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_DMA1); - LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_DMA2); - LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_DMAMUX1); - LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_CRC); - // LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_TSC); - - // AHB2 GRP1 - LL_AHB2_GRP1_EnableClock(LL_AHB2_GRP1_PERIPH_GPIOA); - LL_AHB2_GRP1_EnableClock(LL_AHB2_GRP1_PERIPH_GPIOB); - LL_AHB2_GRP1_EnableClock(LL_AHB2_GRP1_PERIPH_GPIOC); - LL_AHB2_GRP1_EnableClock(LL_AHB2_GRP1_PERIPH_GPIOD); - LL_AHB2_GRP1_EnableClock(LL_AHB2_GRP1_PERIPH_GPIOE); - LL_AHB2_GRP1_EnableClock(LL_AHB2_GRP1_PERIPH_GPIOH); - LL_AHB2_GRP1_EnableClock(LL_AHB2_GRP1_PERIPH_ADC); - LL_AHB2_GRP1_EnableClock(LL_AHB2_GRP1_PERIPH_AES1); - - // AHB3 GRP1 - // LL_AHB3_GRP1_EnableClock(LL_AHB3_GRP1_PERIPH_QUADSPI); - LL_AHB3_GRP1_EnableClock(LL_AHB3_GRP1_PERIPH_PKA); - LL_AHB3_GRP1_EnableClock(LL_AHB3_GRP1_PERIPH_AES2); - LL_AHB3_GRP1_EnableClock(LL_AHB3_GRP1_PERIPH_RNG); - LL_AHB3_GRP1_EnableClock(LL_AHB3_GRP1_PERIPH_HSEM); - LL_AHB3_GRP1_EnableClock(LL_AHB3_GRP1_PERIPH_IPCC); - LL_AHB3_GRP1_EnableClock(LL_AHB3_GRP1_PERIPH_FLASH); - - // APB1 GRP1 - LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_TIM2); - // LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_LCD); - LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_RTCAPB); - // LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_WWDG); - LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_SPI2); - LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_I2C1); - LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_I2C3); - LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_CRS); - LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_USB); - LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_LPTIM1); - - // APB1 GRP2 - LL_APB1_GRP2_EnableClock(LL_APB1_GRP2_PERIPH_LPUART1); - - // APB2 - // LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_ADC); - LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_TIM1); - LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_SPI1); - LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_USART1); - LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_TIM16); - LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_TIM17); - // LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_SAI1); - FURI_LOG_I(TAG, "Init OK"); } diff --git a/firmware/targets/f7/furi_hal/furi_hal_crypto.c b/firmware/targets/f7/furi_hal/furi_hal_crypto.c index e0ed3ab9be2..eb5c3b782c8 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_crypto.c +++ b/firmware/targets/f7/furi_hal/furi_hal_crypto.c @@ -1,8 +1,9 @@ #include #include #include +#include + #include -#include #include #include @@ -241,6 +242,8 @@ bool furi_hal_crypto_store_load_key(uint8_t slot, const uint8_t* iv) { furi_assert(furi_hal_crypto_mutex); furi_check(furi_mutex_acquire(furi_hal_crypto_mutex, FuriWaitForever) == FuriStatusOk); + furi_hal_bus_enable(FuriHalBusAES1); + if(!furi_hal_bt_is_alive()) { return false; } @@ -267,10 +270,7 @@ bool furi_hal_crypto_store_unload_key(uint8_t slot) { SHCI_CmdStatus_t shci_state = SHCI_C2_FUS_UnloadUsrKey(slot); furi_assert(shci_state == SHCI_Success); - FURI_CRITICAL_ENTER(); - LL_AHB2_GRP1_ForceReset(LL_AHB2_GRP1_PERIPH_AES1); - LL_AHB2_GRP1_ReleaseReset(LL_AHB2_GRP1_PERIPH_AES1); - FURI_CRITICAL_EXIT(); + furi_hal_bus_disable(FuriHalBusAES1); furi_check(furi_mutex_release(furi_hal_crypto_mutex) == FuriStatusOk); return (shci_state == SHCI_Success); diff --git a/firmware/targets/f7/furi_hal/furi_hal_dma.c b/firmware/targets/f7/furi_hal/furi_hal_dma.c new file mode 100644 index 00000000000..a6a30d906da --- /dev/null +++ b/firmware/targets/f7/furi_hal/furi_hal_dma.c @@ -0,0 +1,14 @@ +#include +#include + +void furi_hal_dma_init_early() { + furi_hal_bus_enable(FuriHalBusDMA1); + furi_hal_bus_enable(FuriHalBusDMA2); + furi_hal_bus_enable(FuriHalBusDMAMUX1); +} + +void furi_hal_dma_deinit_early() { + furi_hal_bus_disable(FuriHalBusDMA1); + furi_hal_bus_disable(FuriHalBusDMA2); + furi_hal_bus_disable(FuriHalBusDMAMUX1); +} diff --git a/firmware/targets/f7/furi_hal/furi_hal_dma.h b/firmware/targets/f7/furi_hal/furi_hal_dma.h new file mode 100644 index 00000000000..cadcc7733e0 --- /dev/null +++ b/firmware/targets/f7/furi_hal/furi_hal_dma.h @@ -0,0 +1,15 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +/** Early initialization */ +void furi_hal_dma_init_early(); + +/** Early de-initialization */ +void furi_hal_dma_deinit_early(); + +#ifdef __cplusplus +} +#endif diff --git a/firmware/targets/f7/furi_hal/furi_hal_flash.c b/firmware/targets/f7/furi_hal/furi_hal_flash.c index 94d269345ba..796d20b19fa 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_flash.c +++ b/firmware/targets/f7/furi_hal/furi_hal_flash.c @@ -11,20 +11,20 @@ #define TAG "FuriHalFlash" #define FURI_HAL_CRITICAL_MSG "Critical flash operation fail" -#define FURI_HAL_FLASH_READ_BLOCK 8 -#define FURI_HAL_FLASH_WRITE_BLOCK 8 -#define FURI_HAL_FLASH_PAGE_SIZE 4096 -#define FURI_HAL_FLASH_CYCLES_COUNT 10000 -#define FURI_HAL_FLASH_TIMEOUT 1000 -#define FURI_HAL_FLASH_KEY1 0x45670123U -#define FURI_HAL_FLASH_KEY2 0xCDEF89ABU -#define FURI_HAL_FLASH_TOTAL_PAGES 256 +#define FURI_HAL_FLASH_READ_BLOCK (8U) +#define FURI_HAL_FLASH_WRITE_BLOCK (8U) +#define FURI_HAL_FLASH_PAGE_SIZE (4096U) +#define FURI_HAL_FLASH_CYCLES_COUNT (10000U) +#define FURI_HAL_FLASH_TIMEOUT (1000U) +#define FURI_HAL_FLASH_KEY1 (0x45670123U) +#define FURI_HAL_FLASH_KEY2 (0xCDEF89ABU) +#define FURI_HAL_FLASH_TOTAL_PAGES (256U) #define FURI_HAL_FLASH_SR_ERRORS \ (FLASH_SR_OPERR | FLASH_SR_PROGERR | FLASH_SR_WRPERR | FLASH_SR_PGAERR | FLASH_SR_SIZERR | \ FLASH_SR_PGSERR | FLASH_SR_MISERR | FLASH_SR_FASTERR | FLASH_SR_RDERR | FLASH_SR_OPTVERR) -#define FURI_HAL_FLASH_OPT_KEY1 0x08192A3B -#define FURI_HAL_FLASH_OPT_KEY2 0x4C5D6E7F +#define FURI_HAL_FLASH_OPT_KEY1 (0x08192A3BU) +#define FURI_HAL_FLASH_OPT_KEY2 (0x4C5D6E7FU) #define FURI_HAL_FLASH_OB_TOTAL_WORDS (0x80 / (sizeof(uint32_t) * 2)) /* STM32CubeWB/Projects/P-NUCLEO-WB55.Nucleo/Applications/BLE/BLE_RfWithFlash/Core/Src/flash_driver.c @@ -35,7 +35,7 @@ > If for any reason this test is never passed, this means there is a failure in the system and there is no other > way to recover than applying a device reset. */ -#define FURI_HAL_FLASH_C2_LOCK_TIMEOUT_MS 3000u /* 3 seconds */ +#define FURI_HAL_FLASH_C2_LOCK_TIMEOUT_MS (3000U) /* 3 seconds */ #define IS_ADDR_ALIGNED_64BITS(__VALUE__) (((__VALUE__)&0x7U) == (0x00UL)) #define IS_FLASH_PROGRAM_ADDRESS(__VALUE__) \ diff --git a/firmware/targets/f7/furi_hal/furi_hal_i2c_config.c b/firmware/targets/f7/furi_hal/furi_hal_i2c_config.c index afc4fdf5247..f9d88abb376 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_i2c_config.c +++ b/firmware/targets/f7/furi_hal/furi_hal_i2c_config.c @@ -1,7 +1,8 @@ #include #include #include -#include +#include + #include /** Timing register value is computed with the STM32CubeMX Tool, @@ -21,17 +22,9 @@ FuriMutex* furi_hal_i2c_bus_power_mutex = NULL; static void furi_hal_i2c_bus_power_event(FuriHalI2cBus* bus, FuriHalI2cBusEvent event) { if(event == FuriHalI2cBusEventInit) { furi_hal_i2c_bus_power_mutex = furi_mutex_alloc(FuriMutexTypeNormal); - FURI_CRITICAL_ENTER(); - LL_RCC_SetI2CClockSource(LL_RCC_I2C1_CLKSOURCE_PCLK1); - LL_APB1_GRP1_ForceReset(LL_APB1_GRP1_PERIPH_I2C1); - FURI_CRITICAL_EXIT(); bus->current_handle = NULL; } else if(event == FuriHalI2cBusEventDeinit) { furi_mutex_free(furi_hal_i2c_bus_power_mutex); - FURI_CRITICAL_ENTER(); - LL_APB1_GRP1_ForceReset(LL_APB1_GRP1_PERIPH_I2C1); - LL_APB1_GRP1_ReleaseReset(LL_APB1_GRP1_PERIPH_I2C1); - FURI_CRITICAL_EXIT(); } else if(event == FuriHalI2cBusEventLock) { furi_check( furi_mutex_acquire(furi_hal_i2c_bus_power_mutex, FuriWaitForever) == FuriStatusOk); @@ -39,12 +32,11 @@ static void furi_hal_i2c_bus_power_event(FuriHalI2cBus* bus, FuriHalI2cBusEvent furi_check(furi_mutex_release(furi_hal_i2c_bus_power_mutex) == FuriStatusOk); } else if(event == FuriHalI2cBusEventActivate) { FURI_CRITICAL_ENTER(); - LL_APB1_GRP1_ReleaseReset(LL_APB1_GRP1_PERIPH_I2C1); + furi_hal_bus_enable(FuriHalBusI2C1); + LL_RCC_SetI2CClockSource(LL_RCC_I2C1_CLKSOURCE_PCLK1); FURI_CRITICAL_EXIT(); } else if(event == FuriHalI2cBusEventDeactivate) { - FURI_CRITICAL_ENTER(); - LL_APB1_GRP1_ForceReset(LL_APB1_GRP1_PERIPH_I2C1); - FURI_CRITICAL_EXIT(); + furi_hal_bus_disable(FuriHalBusI2C1); } } @@ -58,17 +50,9 @@ FuriMutex* furi_hal_i2c_bus_external_mutex = NULL; static void furi_hal_i2c_bus_external_event(FuriHalI2cBus* bus, FuriHalI2cBusEvent event) { if(event == FuriHalI2cBusEventInit) { furi_hal_i2c_bus_external_mutex = furi_mutex_alloc(FuriMutexTypeNormal); - FURI_CRITICAL_ENTER(); - LL_RCC_SetI2CClockSource(LL_RCC_I2C3_CLKSOURCE_PCLK1); - LL_APB1_GRP1_ForceReset(LL_APB1_GRP1_PERIPH_I2C3); - FURI_CRITICAL_EXIT(); bus->current_handle = NULL; } else if(event == FuriHalI2cBusEventDeinit) { furi_mutex_free(furi_hal_i2c_bus_external_mutex); - FURI_CRITICAL_ENTER(); - LL_APB1_GRP1_ForceReset(LL_APB1_GRP1_PERIPH_I2C3); - LL_APB1_GRP1_ReleaseReset(LL_APB1_GRP1_PERIPH_I2C3); - FURI_CRITICAL_EXIT(); } else if(event == FuriHalI2cBusEventLock) { furi_check( furi_mutex_acquire(furi_hal_i2c_bus_external_mutex, FuriWaitForever) == FuriStatusOk); @@ -76,13 +60,11 @@ static void furi_hal_i2c_bus_external_event(FuriHalI2cBus* bus, FuriHalI2cBusEve furi_check(furi_mutex_release(furi_hal_i2c_bus_external_mutex) == FuriStatusOk); } else if(event == FuriHalI2cBusEventActivate) { FURI_CRITICAL_ENTER(); + furi_hal_bus_enable(FuriHalBusI2C3); LL_RCC_SetI2CClockSource(LL_RCC_I2C3_CLKSOURCE_PCLK1); - LL_APB1_GRP1_ReleaseReset(LL_APB1_GRP1_PERIPH_I2C3); FURI_CRITICAL_EXIT(); } else if(event == FuriHalI2cBusEventDeactivate) { - FURI_CRITICAL_ENTER(); - LL_APB1_GRP1_ForceReset(LL_APB1_GRP1_PERIPH_I2C3); - FURI_CRITICAL_EXIT(); + furi_hal_bus_disable(FuriHalBusI2C3); } } diff --git a/firmware/targets/f7/furi_hal/furi_hal_ibutton.c b/firmware/targets/f7/furi_hal/furi_hal_ibutton.c index c8041c9f2cd..f8f7e4966dc 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_ibutton.c +++ b/firmware/targets/f7/furi_hal/furi_hal_ibutton.c @@ -1,14 +1,15 @@ #include #include #include +#include #include -#include #include #define TAG "FuriHalIbutton" #define FURI_HAL_IBUTTON_TIMER TIM1 +#define FURI_HAL_IBUTTON_TIMER_BUS FuriHalBusTIM1 #define FURI_HAL_IBUTTON_TIMER_IRQ FuriHalInterruptIdTim1UpTim16 typedef enum { @@ -49,9 +50,7 @@ void furi_hal_ibutton_emulate_start( furi_hal_ibutton->callback = callback; furi_hal_ibutton->context = context; - FURI_CRITICAL_ENTER(); - LL_TIM_DeInit(FURI_HAL_IBUTTON_TIMER); - FURI_CRITICAL_EXIT(); + furi_hal_bus_enable(FURI_HAL_IBUTTON_TIMER_BUS); furi_hal_interrupt_set_isr(FURI_HAL_IBUTTON_TIMER_IRQ, furi_hal_ibutton_emulate_isr, NULL); @@ -81,10 +80,7 @@ void furi_hal_ibutton_emulate_stop() { furi_hal_ibutton->state = FuriHalIbuttonStateIdle; LL_TIM_DisableCounter(FURI_HAL_IBUTTON_TIMER); - FURI_CRITICAL_ENTER(); - LL_TIM_DeInit(FURI_HAL_IBUTTON_TIMER); - FURI_CRITICAL_EXIT(); - + furi_hal_bus_disable(FURI_HAL_IBUTTON_TIMER_BUS); furi_hal_interrupt_set_isr(FURI_HAL_IBUTTON_TIMER_IRQ, NULL, NULL); furi_hal_ibutton->callback = NULL; diff --git a/firmware/targets/f7/furi_hal/furi_hal_idle_timer.h b/firmware/targets/f7/furi_hal/furi_hal_idle_timer.h index 36b45755a92..e1ffb1b25f4 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_idle_timer.h +++ b/firmware/targets/f7/furi_hal/furi_hal_idle_timer.h @@ -1,9 +1,10 @@ #pragma once #include -#include #include -#include +#include + +#include // Timer used for tickless idle #define FURI_HAL_IDLE_TIMER_MAX 0xFFFF @@ -11,6 +12,7 @@ #define FURI_HAL_IDLE_TIMER_IRQ LPTIM1_IRQn static inline void furi_hal_idle_timer_init() { + furi_hal_bus_enable(FuriHalBusLPTIM1); // Configure clock source LL_RCC_SetLPTIMClockSource(LL_RCC_LPTIM1_CLKSOURCE_LSE); // There is a theoretical possibility that we need it @@ -41,7 +43,7 @@ static inline void furi_hal_idle_timer_start(uint32_t count) { static inline void furi_hal_idle_timer_reset() { // Hard reset timer // THE ONLY RELIABLE WAY to stop it according to errata - LL_LPTIM_DeInit(FURI_HAL_IDLE_TIMER); + furi_hal_bus_reset(FuriHalBusLPTIM1); // Prevent IRQ handler call NVIC_ClearPendingIRQ(FURI_HAL_IDLE_TIMER_IRQ); } diff --git a/firmware/targets/f7/furi_hal/furi_hal_infrared.c b/firmware/targets/f7/furi_hal/furi_hal_infrared.c index 7b4f1708474..c60db5f20ef 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_infrared.c +++ b/firmware/targets/f7/furi_hal/furi_hal_infrared.c @@ -1,14 +1,11 @@ #include -#include -#include "stm32wbxx_ll_dma.h" #include #include +#include -#include #include -#include +#include -#include #include #include @@ -27,13 +24,23 @@ (TIM_CCMR2_OC3PE | LL_TIM_OCMODE_FORCED_INACTIVE) /* Space time - force low */ /* DMA Channels definition */ -#define IR_DMA DMA2 -#define IR_DMA_CH1_CHANNEL LL_DMA_CHANNEL_1 -#define IR_DMA_CH2_CHANNEL LL_DMA_CHANNEL_2 -#define IR_DMA_CH1_IRQ FuriHalInterruptIdDma2Ch1 -#define IR_DMA_CH2_IRQ FuriHalInterruptIdDma2Ch2 -#define IR_DMA_CH1_DEF IR_DMA, IR_DMA_CH1_CHANNEL -#define IR_DMA_CH2_DEF IR_DMA, IR_DMA_CH2_CHANNEL +#define INFRARED_DMA DMA2 +#define INFRARED_DMA_CH1_CHANNEL LL_DMA_CHANNEL_1 +#define INFRARED_DMA_CH2_CHANNEL LL_DMA_CHANNEL_2 +#define INFRARED_DMA_CH1_IRQ FuriHalInterruptIdDma2Ch1 +#define INFRARED_DMA_CH2_IRQ FuriHalInterruptIdDma2Ch2 +#define INFRARED_DMA_CH1_DEF INFRARED_DMA, INFRARED_DMA_CH1_CHANNEL +#define INFRARED_DMA_CH2_DEF INFRARED_DMA, INFRARED_DMA_CH2_CHANNEL + +/* Timers definition */ +#define INFRARED_RX_TIMER TIM2 +#define INFRARED_DMA_TIMER TIM1 +#define INFRARED_RX_TIMER_BUS FuriHalBusTIM2 +#define INFRARED_DMA_TIMER_BUS FuriHalBusTIM1 + +/* Misc */ +#define INFRARED_RX_GPIO_ALT GpioAltFn1TIM2 +#define INFRARED_RX_IRQ FuriHalInterruptIdTIM2 typedef struct { FuriHalInfraredRxCaptureCallback capture_callback; @@ -91,8 +98,8 @@ static void furi_hal_infrared_tim_rx_isr() { static uint32_t previous_captured_ch2 = 0; /* Timeout */ - if(LL_TIM_IsActiveFlag_CC3(TIM2)) { - LL_TIM_ClearFlag_CC3(TIM2); + if(LL_TIM_IsActiveFlag_CC3(INFRARED_RX_TIMER)) { + LL_TIM_ClearFlag_CC3(INFRARED_RX_TIMER); furi_assert(furi_hal_infrared_state == InfraredStateAsyncRx); /* Timers CNT register starts to counting from 0 to ARR, but it is @@ -108,13 +115,13 @@ static void furi_hal_infrared_tim_rx_isr() { } /* Rising Edge */ - if(LL_TIM_IsActiveFlag_CC1(TIM2)) { - LL_TIM_ClearFlag_CC1(TIM2); + if(LL_TIM_IsActiveFlag_CC1(INFRARED_RX_TIMER)) { + LL_TIM_ClearFlag_CC1(INFRARED_RX_TIMER); furi_assert(furi_hal_infrared_state == InfraredStateAsyncRx); - if(READ_BIT(TIM2->CCMR1, TIM_CCMR1_CC1S)) { + if(READ_BIT(INFRARED_RX_TIMER->CCMR1, TIM_CCMR1_CC1S)) { /* Low pin level is a Mark state of INFRARED signal. Invert level for further processing. */ - uint32_t duration = LL_TIM_IC_GetCaptureCH1(TIM2) - previous_captured_ch2; + uint32_t duration = LL_TIM_IC_GetCaptureCH1(INFRARED_RX_TIMER) - previous_captured_ch2; if(infrared_tim_rx.capture_callback) infrared_tim_rx.capture_callback(infrared_tim_rx.capture_context, 1, duration); } else { @@ -123,13 +130,13 @@ static void furi_hal_infrared_tim_rx_isr() { } /* Falling Edge */ - if(LL_TIM_IsActiveFlag_CC2(TIM2)) { - LL_TIM_ClearFlag_CC2(TIM2); + if(LL_TIM_IsActiveFlag_CC2(INFRARED_RX_TIMER)) { + LL_TIM_ClearFlag_CC2(INFRARED_RX_TIMER); furi_assert(furi_hal_infrared_state == InfraredStateAsyncRx); - if(READ_BIT(TIM2->CCMR1, TIM_CCMR1_CC2S)) { + if(READ_BIT(INFRARED_RX_TIMER->CCMR1, TIM_CCMR1_CC2S)) { /* High pin level is a Space state of INFRARED signal. Invert level for further processing. */ - uint32_t duration = LL_TIM_IC_GetCaptureCH2(TIM2); + uint32_t duration = LL_TIM_IC_GetCaptureCH2(INFRARED_RX_TIMER); previous_captured_ch2 = duration; if(infrared_tim_rx.capture_callback) infrared_tim_rx.capture_callback(infrared_tim_rx.capture_context, 0, duration); @@ -143,62 +150,66 @@ void furi_hal_infrared_async_rx_start(void) { furi_assert(furi_hal_infrared_state == InfraredStateIdle); furi_hal_gpio_init_ex( - &gpio_infrared_rx, GpioModeAltFunctionPushPull, GpioPullNo, GpioSpeedLow, GpioAltFn1TIM2); + &gpio_infrared_rx, + GpioModeAltFunctionPushPull, + GpioPullNo, + GpioSpeedLow, + INFRARED_RX_GPIO_ALT); + + furi_hal_bus_enable(INFRARED_RX_TIMER_BUS); LL_TIM_InitTypeDef TIM_InitStruct = {0}; TIM_InitStruct.Prescaler = 64 - 1; TIM_InitStruct.CounterMode = LL_TIM_COUNTERMODE_UP; TIM_InitStruct.Autoreload = 0x7FFFFFFE; TIM_InitStruct.ClockDivision = LL_TIM_CLOCKDIVISION_DIV1; - LL_TIM_Init(TIM2, &TIM_InitStruct); - - LL_TIM_SetClockSource(TIM2, LL_TIM_CLOCKSOURCE_INTERNAL); - LL_TIM_DisableARRPreload(TIM2); - LL_TIM_SetTriggerInput(TIM2, LL_TIM_TS_TI1FP1); - LL_TIM_SetSlaveMode(TIM2, LL_TIM_SLAVEMODE_RESET); - LL_TIM_CC_DisableChannel(TIM2, LL_TIM_CHANNEL_CH2); - LL_TIM_IC_SetFilter(TIM2, LL_TIM_CHANNEL_CH2, LL_TIM_IC_FILTER_FDIV1); - LL_TIM_IC_SetPolarity(TIM2, LL_TIM_CHANNEL_CH2, LL_TIM_IC_POLARITY_FALLING); - LL_TIM_DisableIT_TRIG(TIM2); - LL_TIM_DisableDMAReq_TRIG(TIM2); - LL_TIM_SetTriggerOutput(TIM2, LL_TIM_TRGO_RESET); - LL_TIM_EnableMasterSlaveMode(TIM2); - LL_TIM_IC_SetActiveInput(TIM2, LL_TIM_CHANNEL_CH1, LL_TIM_ACTIVEINPUT_DIRECTTI); - LL_TIM_IC_SetPrescaler(TIM2, LL_TIM_CHANNEL_CH1, LL_TIM_ICPSC_DIV1); - LL_TIM_IC_SetFilter(TIM2, LL_TIM_CHANNEL_CH1, LL_TIM_IC_FILTER_FDIV1); - LL_TIM_IC_SetPolarity(TIM2, LL_TIM_CHANNEL_CH1, LL_TIM_IC_POLARITY_RISING); - LL_TIM_IC_SetActiveInput(TIM2, LL_TIM_CHANNEL_CH2, LL_TIM_ACTIVEINPUT_INDIRECTTI); - LL_TIM_IC_SetPrescaler(TIM2, LL_TIM_CHANNEL_CH2, LL_TIM_ICPSC_DIV1); - - furi_hal_interrupt_set_isr(FuriHalInterruptIdTIM2, furi_hal_infrared_tim_rx_isr, NULL); + LL_TIM_Init(INFRARED_RX_TIMER, &TIM_InitStruct); + + LL_TIM_SetClockSource(INFRARED_RX_TIMER, LL_TIM_CLOCKSOURCE_INTERNAL); + LL_TIM_DisableARRPreload(INFRARED_RX_TIMER); + LL_TIM_SetTriggerInput(INFRARED_RX_TIMER, LL_TIM_TS_TI1FP1); + LL_TIM_SetSlaveMode(INFRARED_RX_TIMER, LL_TIM_SLAVEMODE_RESET); + LL_TIM_CC_DisableChannel(INFRARED_RX_TIMER, LL_TIM_CHANNEL_CH2); + LL_TIM_IC_SetFilter(INFRARED_RX_TIMER, LL_TIM_CHANNEL_CH2, LL_TIM_IC_FILTER_FDIV1); + LL_TIM_IC_SetPolarity(INFRARED_RX_TIMER, LL_TIM_CHANNEL_CH2, LL_TIM_IC_POLARITY_FALLING); + LL_TIM_DisableIT_TRIG(INFRARED_RX_TIMER); + LL_TIM_DisableDMAReq_TRIG(INFRARED_RX_TIMER); + LL_TIM_SetTriggerOutput(INFRARED_RX_TIMER, LL_TIM_TRGO_RESET); + LL_TIM_EnableMasterSlaveMode(INFRARED_RX_TIMER); + LL_TIM_IC_SetActiveInput(INFRARED_RX_TIMER, LL_TIM_CHANNEL_CH1, LL_TIM_ACTIVEINPUT_DIRECTTI); + LL_TIM_IC_SetPrescaler(INFRARED_RX_TIMER, LL_TIM_CHANNEL_CH1, LL_TIM_ICPSC_DIV1); + LL_TIM_IC_SetFilter(INFRARED_RX_TIMER, LL_TIM_CHANNEL_CH1, LL_TIM_IC_FILTER_FDIV1); + LL_TIM_IC_SetPolarity(INFRARED_RX_TIMER, LL_TIM_CHANNEL_CH1, LL_TIM_IC_POLARITY_RISING); + LL_TIM_IC_SetActiveInput(INFRARED_RX_TIMER, LL_TIM_CHANNEL_CH2, LL_TIM_ACTIVEINPUT_INDIRECTTI); + LL_TIM_IC_SetPrescaler(INFRARED_RX_TIMER, LL_TIM_CHANNEL_CH2, LL_TIM_ICPSC_DIV1); + + furi_hal_interrupt_set_isr(INFRARED_RX_IRQ, furi_hal_infrared_tim_rx_isr, NULL); furi_hal_infrared_state = InfraredStateAsyncRx; - LL_TIM_EnableIT_CC1(TIM2); - LL_TIM_EnableIT_CC2(TIM2); - LL_TIM_CC_EnableChannel(TIM2, LL_TIM_CHANNEL_CH1); - LL_TIM_CC_EnableChannel(TIM2, LL_TIM_CHANNEL_CH2); + LL_TIM_EnableIT_CC1(INFRARED_RX_TIMER); + LL_TIM_EnableIT_CC2(INFRARED_RX_TIMER); + LL_TIM_CC_EnableChannel(INFRARED_RX_TIMER, LL_TIM_CHANNEL_CH1); + LL_TIM_CC_EnableChannel(INFRARED_RX_TIMER, LL_TIM_CHANNEL_CH2); - LL_TIM_SetCounter(TIM2, 0); - LL_TIM_EnableCounter(TIM2); + LL_TIM_SetCounter(INFRARED_RX_TIMER, 0); + LL_TIM_EnableCounter(INFRARED_RX_TIMER); } void furi_hal_infrared_async_rx_stop(void) { furi_assert(furi_hal_infrared_state == InfraredStateAsyncRx); FURI_CRITICAL_ENTER(); - - LL_TIM_DeInit(TIM2); - furi_hal_interrupt_set_isr(FuriHalInterruptIdTIM2, NULL, NULL); + furi_hal_bus_disable(INFRARED_RX_TIMER_BUS); + furi_hal_interrupt_set_isr(INFRARED_RX_IRQ, NULL, NULL); furi_hal_infrared_state = InfraredStateIdle; - FURI_CRITICAL_EXIT(); } void furi_hal_infrared_async_rx_set_timeout(uint32_t timeout_us) { - LL_TIM_OC_SetCompareCH3(TIM2, timeout_us); - LL_TIM_OC_SetMode(TIM2, LL_TIM_CHANNEL_CH3, LL_TIM_OCMODE_ACTIVE); - LL_TIM_CC_EnableChannel(TIM2, LL_TIM_CHANNEL_CH3); - LL_TIM_EnableIT_CC3(TIM2); + LL_TIM_OC_SetCompareCH3(INFRARED_RX_TIMER, timeout_us); + LL_TIM_OC_SetMode(INFRARED_RX_TIMER, LL_TIM_CHANNEL_CH3, LL_TIM_OCMODE_ACTIVE); + LL_TIM_CC_EnableChannel(INFRARED_RX_TIMER, LL_TIM_CHANNEL_CH3); + LL_TIM_EnableIT_CC3(INFRARED_RX_TIMER); } bool furi_hal_infrared_is_busy(void) { @@ -220,16 +231,16 @@ void furi_hal_infrared_async_rx_set_timeout_isr_callback( } static void furi_hal_infrared_tx_dma_terminate(void) { - LL_DMA_DisableIT_TC(IR_DMA_CH1_DEF); - LL_DMA_DisableIT_HT(IR_DMA_CH2_DEF); - LL_DMA_DisableIT_TC(IR_DMA_CH2_DEF); + LL_DMA_DisableIT_TC(INFRARED_DMA_CH1_DEF); + LL_DMA_DisableIT_HT(INFRARED_DMA_CH2_DEF); + LL_DMA_DisableIT_TC(INFRARED_DMA_CH2_DEF); furi_assert(furi_hal_infrared_state == InfraredStateAsyncTxStopInProgress); - LL_DMA_DisableIT_TC(IR_DMA_CH1_DEF); - LL_DMA_DisableChannel(IR_DMA_CH2_DEF); - LL_DMA_DisableChannel(IR_DMA_CH1_DEF); - LL_TIM_DisableCounter(TIM1); + LL_DMA_DisableIT_TC(INFRARED_DMA_CH1_DEF); + LL_DMA_DisableChannel(INFRARED_DMA_CH2_DEF); + LL_DMA_DisableChannel(INFRARED_DMA_CH1_DEF); + LL_TIM_DisableCounter(INFRARED_DMA_TIMER); FuriStatus status = furi_semaphore_release(infrared_tim_tx.stop_semaphore); furi_check(status == FuriStatusOk); furi_hal_infrared_state = InfraredStateAsyncTxStopped; @@ -237,7 +248,7 @@ static void furi_hal_infrared_tx_dma_terminate(void) { static uint8_t furi_hal_infrared_get_current_dma_tx_buffer(void) { uint8_t buf_num = 0; - uint32_t buffer_adr = LL_DMA_GetMemoryAddress(IR_DMA_CH2_DEF); + uint32_t buffer_adr = LL_DMA_GetMemoryAddress(INFRARED_DMA_CH2_DEF); if(buffer_adr == (uint32_t)infrared_tim_tx.buffer[0].data) { buf_num = 0; } else if(buffer_adr == (uint32_t)infrared_tim_tx.buffer[1].data) { @@ -249,13 +260,13 @@ static uint8_t furi_hal_infrared_get_current_dma_tx_buffer(void) { } static void furi_hal_infrared_tx_dma_polarity_isr() { -#if IR_DMA_CH1_CHANNEL == LL_DMA_CHANNEL_1 - if(LL_DMA_IsActiveFlag_TE1(IR_DMA)) { - LL_DMA_ClearFlag_TE1(IR_DMA); +#if INFRARED_DMA_CH1_CHANNEL == LL_DMA_CHANNEL_1 + if(LL_DMA_IsActiveFlag_TE1(INFRARED_DMA)) { + LL_DMA_ClearFlag_TE1(INFRARED_DMA); furi_crash(NULL); } - if(LL_DMA_IsActiveFlag_TC1(IR_DMA) && LL_DMA_IsEnabledIT_TC(IR_DMA_CH1_DEF)) { - LL_DMA_ClearFlag_TC1(IR_DMA); + if(LL_DMA_IsActiveFlag_TC1(INFRARED_DMA) && LL_DMA_IsEnabledIT_TC(INFRARED_DMA_CH1_DEF)) { + LL_DMA_ClearFlag_TC1(INFRARED_DMA); furi_check( (furi_hal_infrared_state == InfraredStateAsyncTx) || @@ -271,23 +282,23 @@ static void furi_hal_infrared_tx_dma_polarity_isr() { } static void furi_hal_infrared_tx_dma_isr() { -#if IR_DMA_CH2_CHANNEL == LL_DMA_CHANNEL_2 - if(LL_DMA_IsActiveFlag_TE2(IR_DMA)) { - LL_DMA_ClearFlag_TE2(IR_DMA); +#if INFRARED_DMA_CH2_CHANNEL == LL_DMA_CHANNEL_2 + if(LL_DMA_IsActiveFlag_TE2(INFRARED_DMA)) { + LL_DMA_ClearFlag_TE2(INFRARED_DMA); furi_crash(NULL); } - if(LL_DMA_IsActiveFlag_HT2(IR_DMA) && LL_DMA_IsEnabledIT_HT(IR_DMA_CH2_DEF)) { - LL_DMA_ClearFlag_HT2(IR_DMA); + if(LL_DMA_IsActiveFlag_HT2(INFRARED_DMA) && LL_DMA_IsEnabledIT_HT(INFRARED_DMA_CH2_DEF)) { + LL_DMA_ClearFlag_HT2(INFRARED_DMA); uint8_t buf_num = furi_hal_infrared_get_current_dma_tx_buffer(); uint8_t next_buf_num = !buf_num; if(infrared_tim_tx.buffer[buf_num].last_packet_end) { - LL_DMA_DisableIT_HT(IR_DMA_CH2_DEF); + LL_DMA_DisableIT_HT(INFRARED_DMA_CH2_DEF); } else if( !infrared_tim_tx.buffer[buf_num].packet_end || (furi_hal_infrared_state == InfraredStateAsyncTx)) { furi_hal_infrared_tx_fill_buffer(next_buf_num, 0); if(infrared_tim_tx.buffer[next_buf_num].last_packet_end) { - LL_DMA_DisableIT_HT(IR_DMA_CH2_DEF); + LL_DMA_DisableIT_HT(INFRARED_DMA_CH2_DEF); } } else if(furi_hal_infrared_state == InfraredStateAsyncTxStopReq) { /* fallthrough */ @@ -295,8 +306,8 @@ static void furi_hal_infrared_tx_dma_isr() { furi_crash(NULL); } } - if(LL_DMA_IsActiveFlag_TC2(IR_DMA) && LL_DMA_IsEnabledIT_TC(IR_DMA_CH2_DEF)) { - LL_DMA_ClearFlag_TC2(IR_DMA); + if(LL_DMA_IsActiveFlag_TC2(INFRARED_DMA) && LL_DMA_IsEnabledIT_TC(INFRARED_DMA_CH2_DEF)) { + LL_DMA_ClearFlag_TC2(INFRARED_DMA); furi_check( (furi_hal_infrared_state == InfraredStateAsyncTxStopInProgress) || (furi_hal_infrared_state == InfraredStateAsyncTxStopReq) || @@ -328,37 +339,40 @@ static void furi_hal_infrared_tx_dma_isr() { } static void furi_hal_infrared_configure_tim_pwm_tx(uint32_t freq, float duty_cycle) { - LL_TIM_DisableCounter(TIM1); - LL_TIM_SetRepetitionCounter(TIM1, 0); - LL_TIM_SetCounter(TIM1, 0); - LL_TIM_SetPrescaler(TIM1, 0); - LL_TIM_SetCounterMode(TIM1, LL_TIM_COUNTERMODE_UP); - LL_TIM_EnableARRPreload(TIM1); + LL_TIM_DisableCounter(INFRARED_DMA_TIMER); + LL_TIM_SetRepetitionCounter(INFRARED_DMA_TIMER, 0); + LL_TIM_SetCounter(INFRARED_DMA_TIMER, 0); + LL_TIM_SetPrescaler(INFRARED_DMA_TIMER, 0); + LL_TIM_SetCounterMode(INFRARED_DMA_TIMER, LL_TIM_COUNTERMODE_UP); + LL_TIM_EnableARRPreload(INFRARED_DMA_TIMER); LL_TIM_SetAutoReload( - TIM1, __LL_TIM_CALC_ARR(SystemCoreClock, LL_TIM_GetPrescaler(TIM1), freq)); + INFRARED_DMA_TIMER, + __LL_TIM_CALC_ARR(SystemCoreClock, LL_TIM_GetPrescaler(INFRARED_DMA_TIMER), freq)); #if defined INFRARED_TX_DEBUG - LL_TIM_OC_SetCompareCH1(TIM1, ((LL_TIM_GetAutoReload(TIM1) + 1) * (1 - duty_cycle))); - LL_TIM_OC_EnablePreload(TIM1, LL_TIM_CHANNEL_CH1); + LL_TIM_OC_SetCompareCH1( + INFRARED_DMA_TIMER, ((LL_TIM_GetAutoReload(INFRARED_DMA_TIMER) + 1) * (1 - duty_cycle))); + LL_TIM_OC_EnablePreload(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH1); /* LL_TIM_OCMODE_PWM2 set by DMA */ - LL_TIM_OC_SetMode(TIM1, LL_TIM_CHANNEL_CH1, LL_TIM_OCMODE_FORCED_INACTIVE); - LL_TIM_OC_SetPolarity(TIM1, LL_TIM_CHANNEL_CH1N, LL_TIM_OCPOLARITY_HIGH); - LL_TIM_OC_DisableFast(TIM1, LL_TIM_CHANNEL_CH1); - LL_TIM_CC_EnableChannel(TIM1, LL_TIM_CHANNEL_CH1N); - LL_TIM_DisableIT_CC1(TIM1); + LL_TIM_OC_SetMode(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH1, LL_TIM_OCMODE_FORCED_INACTIVE); + LL_TIM_OC_SetPolarity(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH1N, LL_TIM_OCPOLARITY_HIGH); + LL_TIM_OC_DisableFast(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH1); + LL_TIM_CC_EnableChannel(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH1N); + LL_TIM_DisableIT_CC1(INFRARED_DMA_TIMER); #else - LL_TIM_OC_SetCompareCH3(TIM1, ((LL_TIM_GetAutoReload(TIM1) + 1) * (1 - duty_cycle))); - LL_TIM_OC_EnablePreload(TIM1, LL_TIM_CHANNEL_CH3); + LL_TIM_OC_SetCompareCH3( + INFRARED_DMA_TIMER, ((LL_TIM_GetAutoReload(INFRARED_DMA_TIMER) + 1) * (1 - duty_cycle))); + LL_TIM_OC_EnablePreload(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH3); /* LL_TIM_OCMODE_PWM2 set by DMA */ - LL_TIM_OC_SetMode(TIM1, LL_TIM_CHANNEL_CH3, LL_TIM_OCMODE_FORCED_INACTIVE); - LL_TIM_OC_SetPolarity(TIM1, LL_TIM_CHANNEL_CH3N, LL_TIM_OCPOLARITY_HIGH); - LL_TIM_OC_DisableFast(TIM1, LL_TIM_CHANNEL_CH3); - LL_TIM_CC_EnableChannel(TIM1, LL_TIM_CHANNEL_CH3N); - LL_TIM_DisableIT_CC3(TIM1); + LL_TIM_OC_SetMode(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH3, LL_TIM_OCMODE_FORCED_INACTIVE); + LL_TIM_OC_SetPolarity(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH3N, LL_TIM_OCPOLARITY_HIGH); + LL_TIM_OC_DisableFast(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH3); + LL_TIM_CC_EnableChannel(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH3N); + LL_TIM_DisableIT_CC3(INFRARED_DMA_TIMER); #endif - LL_TIM_DisableMasterSlaveMode(TIM1); - LL_TIM_EnableAllOutputs(TIM1); - LL_TIM_DisableIT_UPDATE(TIM1); - LL_TIM_EnableDMAReq_UPDATE(TIM1); + LL_TIM_DisableMasterSlaveMode(INFRARED_DMA_TIMER); + LL_TIM_EnableAllOutputs(INFRARED_DMA_TIMER); + LL_TIM_DisableIT_UPDATE(INFRARED_DMA_TIMER); + LL_TIM_EnableDMAReq_UPDATE(INFRARED_DMA_TIMER); NVIC_SetPriority(TIM1_UP_TIM16_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 5, 0)); NVIC_EnableIRQ(TIM1_UP_TIM16_IRQn); @@ -367,9 +381,9 @@ static void furi_hal_infrared_configure_tim_pwm_tx(uint32_t freq, float duty_cyc static void furi_hal_infrared_configure_tim_cmgr2_dma_tx(void) { LL_DMA_InitTypeDef dma_config = {0}; #if defined INFRARED_TX_DEBUG - dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (TIM1->CCMR1); + dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (INFRARED_DMA_TIMER->CCMR1); #else - dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (TIM1->CCMR2); + dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (INFRARED_DMA_TIMER->CCMR2); #endif dma_config.MemoryOrM2MDstAddress = (uint32_t)NULL; dma_config.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH; @@ -382,24 +396,25 @@ static void furi_hal_infrared_configure_tim_cmgr2_dma_tx(void) { dma_config.NbData = 0; dma_config.PeriphRequest = LL_DMAMUX_REQ_TIM1_UP; dma_config.Priority = LL_DMA_PRIORITY_VERYHIGH; - LL_DMA_Init(IR_DMA_CH1_DEF, &dma_config); + LL_DMA_Init(INFRARED_DMA_CH1_DEF, &dma_config); -#if IR_DMA_CH1_CHANNEL == LL_DMA_CHANNEL_1 - LL_DMA_ClearFlag_TE1(IR_DMA); - LL_DMA_ClearFlag_TC1(IR_DMA); +#if INFRARED_DMA_CH1_CHANNEL == LL_DMA_CHANNEL_1 + LL_DMA_ClearFlag_TE1(INFRARED_DMA); + LL_DMA_ClearFlag_TC1(INFRARED_DMA); #else #error Update this code. Would you kindly? #endif - LL_DMA_EnableIT_TE(IR_DMA_CH1_DEF); - LL_DMA_EnableIT_TC(IR_DMA_CH1_DEF); + LL_DMA_EnableIT_TE(INFRARED_DMA_CH1_DEF); + LL_DMA_EnableIT_TC(INFRARED_DMA_CH1_DEF); - furi_hal_interrupt_set_isr_ex(IR_DMA_CH1_IRQ, 4, furi_hal_infrared_tx_dma_polarity_isr, NULL); + furi_hal_interrupt_set_isr_ex( + INFRARED_DMA_CH1_IRQ, 4, furi_hal_infrared_tx_dma_polarity_isr, NULL); } static void furi_hal_infrared_configure_tim_rcr_dma_tx(void) { LL_DMA_InitTypeDef dma_config = {0}; - dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (TIM1->RCR); + dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (INFRARED_DMA_TIMER->RCR); dma_config.MemoryOrM2MDstAddress = (uint32_t)NULL; dma_config.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH; dma_config.Mode = LL_DMA_MODE_NORMAL; @@ -410,21 +425,21 @@ static void furi_hal_infrared_configure_tim_rcr_dma_tx(void) { dma_config.NbData = 0; dma_config.PeriphRequest = LL_DMAMUX_REQ_TIM1_UP; dma_config.Priority = LL_DMA_PRIORITY_MEDIUM; - LL_DMA_Init(IR_DMA_CH2_DEF, &dma_config); + LL_DMA_Init(INFRARED_DMA_CH2_DEF, &dma_config); -#if IR_DMA_CH2_CHANNEL == LL_DMA_CHANNEL_2 - LL_DMA_ClearFlag_TC2(IR_DMA); - LL_DMA_ClearFlag_HT2(IR_DMA); - LL_DMA_ClearFlag_TE2(IR_DMA); +#if INFRARED_DMA_CH2_CHANNEL == LL_DMA_CHANNEL_2 + LL_DMA_ClearFlag_TC2(INFRARED_DMA); + LL_DMA_ClearFlag_HT2(INFRARED_DMA); + LL_DMA_ClearFlag_TE2(INFRARED_DMA); #else #error Update this code. Would you kindly? #endif - LL_DMA_EnableIT_TC(IR_DMA_CH2_DEF); - LL_DMA_EnableIT_HT(IR_DMA_CH2_DEF); - LL_DMA_EnableIT_TE(IR_DMA_CH2_DEF); + LL_DMA_EnableIT_TC(INFRARED_DMA_CH2_DEF); + LL_DMA_EnableIT_HT(INFRARED_DMA_CH2_DEF); + LL_DMA_EnableIT_TE(INFRARED_DMA_CH2_DEF); - furi_hal_interrupt_set_isr_ex(IR_DMA_CH2_IRQ, 5, furi_hal_infrared_tx_dma_isr, NULL); + furi_hal_interrupt_set_isr_ex(INFRARED_DMA_CH2_IRQ, 5, furi_hal_infrared_tx_dma_isr, NULL); } static void furi_hal_infrared_tx_fill_buffer_last(uint8_t buf_num) { @@ -526,14 +541,14 @@ static void furi_hal_infrared_tx_dma_set_polarity(uint8_t buf_num, uint8_t polar furi_assert(buffer->polarity != NULL); FURI_CRITICAL_ENTER(); - bool channel_enabled = LL_DMA_IsEnabledChannel(IR_DMA_CH1_DEF); + bool channel_enabled = LL_DMA_IsEnabledChannel(INFRARED_DMA_CH1_DEF); if(channel_enabled) { - LL_DMA_DisableChannel(IR_DMA_CH1_DEF); + LL_DMA_DisableChannel(INFRARED_DMA_CH1_DEF); } - LL_DMA_SetMemoryAddress(IR_DMA_CH1_DEF, (uint32_t)buffer->polarity); - LL_DMA_SetDataLength(IR_DMA_CH1_DEF, buffer->size + polarity_shift); + LL_DMA_SetMemoryAddress(INFRARED_DMA_CH1_DEF, (uint32_t)buffer->polarity); + LL_DMA_SetDataLength(INFRARED_DMA_CH1_DEF, buffer->size + polarity_shift); if(channel_enabled) { - LL_DMA_EnableChannel(IR_DMA_CH1_DEF); + LL_DMA_EnableChannel(INFRARED_DMA_CH1_DEF); } FURI_CRITICAL_EXIT(); } @@ -546,14 +561,14 @@ static void furi_hal_infrared_tx_dma_set_buffer(uint8_t buf_num) { /* non-circular mode requires disabled channel before setup */ FURI_CRITICAL_ENTER(); - bool channel_enabled = LL_DMA_IsEnabledChannel(IR_DMA_CH2_DEF); + bool channel_enabled = LL_DMA_IsEnabledChannel(INFRARED_DMA_CH2_DEF); if(channel_enabled) { - LL_DMA_DisableChannel(IR_DMA_CH2_DEF); + LL_DMA_DisableChannel(INFRARED_DMA_CH2_DEF); } - LL_DMA_SetMemoryAddress(IR_DMA_CH2_DEF, (uint32_t)buffer->data); - LL_DMA_SetDataLength(IR_DMA_CH2_DEF, buffer->size); + LL_DMA_SetMemoryAddress(INFRARED_DMA_CH2_DEF, (uint32_t)buffer->data); + LL_DMA_SetDataLength(INFRARED_DMA_CH2_DEF, buffer->size); if(channel_enabled) { - LL_DMA_EnableChannel(IR_DMA_CH2_DEF); + LL_DMA_EnableChannel(INFRARED_DMA_CH2_DEF); } FURI_CRITICAL_EXIT(); } @@ -564,9 +579,10 @@ static void furi_hal_infrared_async_tx_free_resources(void) { (furi_hal_infrared_state == InfraredStateAsyncTxStopped)); furi_hal_gpio_init(&gpio_infrared_tx, GpioModeAnalog, GpioPullDown, GpioSpeedLow); - furi_hal_interrupt_set_isr(IR_DMA_CH1_IRQ, NULL, NULL); - furi_hal_interrupt_set_isr(IR_DMA_CH2_IRQ, NULL, NULL); - LL_TIM_DeInit(TIM1); + furi_hal_interrupt_set_isr(INFRARED_DMA_CH1_IRQ, NULL, NULL); + furi_hal_interrupt_set_isr(INFRARED_DMA_CH2_IRQ, NULL, NULL); + + furi_hal_bus_disable(INFRARED_DMA_TIMER_BUS); furi_semaphore_free(infrared_tim_tx.stop_semaphore); free(infrared_tim_tx.buffer[0].data); @@ -607,6 +623,8 @@ void furi_hal_infrared_async_tx_start(uint32_t freq, float duty_cycle) { furi_hal_infrared_tx_fill_buffer(0, INFRARED_POLARITY_SHIFT); + furi_hal_bus_enable(INFRARED_DMA_TIMER_BUS); + furi_hal_infrared_configure_tim_pwm_tx(freq, duty_cycle); furi_hal_infrared_configure_tim_cmgr2_dma_tx(); furi_hal_infrared_configure_tim_rcr_dma_tx(); @@ -615,11 +633,11 @@ void furi_hal_infrared_async_tx_start(uint32_t freq, float duty_cycle) { furi_hal_infrared_state = InfraredStateAsyncTx; - LL_TIM_ClearFlag_UPDATE(TIM1); - LL_DMA_EnableChannel(IR_DMA_CH1_DEF); - LL_DMA_EnableChannel(IR_DMA_CH2_DEF); + LL_TIM_ClearFlag_UPDATE(INFRARED_DMA_TIMER); + LL_DMA_EnableChannel(INFRARED_DMA_CH1_DEF); + LL_DMA_EnableChannel(INFRARED_DMA_CH2_DEF); furi_delay_us(5); - LL_TIM_GenerateEvent_UPDATE(TIM1); /* DMA -> TIMx_RCR */ + LL_TIM_GenerateEvent_UPDATE(INFRARED_DMA_TIMER); /* DMA -> TIMx_RCR */ furi_delay_us(5); LL_GPIO_ResetOutputPin( gpio_infrared_tx.port, gpio_infrared_tx.pin); /* when disable it prevents false pulse */ @@ -627,8 +645,8 @@ void furi_hal_infrared_async_tx_start(uint32_t freq, float duty_cycle) { &gpio_infrared_tx, GpioModeAltFunctionPushPull, GpioPullUp, GpioSpeedHigh, GpioAltFn1TIM1); FURI_CRITICAL_ENTER(); - LL_TIM_GenerateEvent_UPDATE(TIM1); /* TIMx_RCR -> Repetition counter */ - LL_TIM_EnableCounter(TIM1); + LL_TIM_GenerateEvent_UPDATE(INFRARED_DMA_TIMER); /* TIMx_RCR -> Repetition counter */ + LL_TIM_EnableCounter(INFRARED_DMA_TIMER); FURI_CRITICAL_EXIT(); } diff --git a/firmware/targets/f7/furi_hal/furi_hal_pwm.c b/firmware/targets/f7/furi_hal/furi_hal_pwm.c index 8f84b5fd8d5..7e985cbb11b 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_pwm.c +++ b/firmware/targets/f7/furi_hal/furi_hal_pwm.c @@ -1,8 +1,7 @@ #include -#include #include +#include -#include #include #include #include @@ -29,9 +28,7 @@ void furi_hal_pwm_start(FuriHalPwmOutputId channel, uint32_t freq, uint8_t duty) GpioSpeedVeryHigh, GpioAltFn1TIM1); - FURI_CRITICAL_ENTER(); - LL_TIM_DeInit(TIM1); - FURI_CRITICAL_EXIT(); + furi_hal_bus_enable(FuriHalBusTIM1); LL_TIM_SetCounterMode(TIM1, LL_TIM_COUNTERMODE_UP); LL_TIM_SetRepetitionCounter(TIM1, 0); @@ -58,9 +55,7 @@ void furi_hal_pwm_start(FuriHalPwmOutputId channel, uint32_t freq, uint8_t duty) GpioSpeedVeryHigh, GpioAltFn14LPTIM2); - FURI_CRITICAL_ENTER(); - LL_LPTIM_DeInit(LPTIM2); - FURI_CRITICAL_EXIT(); + furi_hal_bus_enable(FuriHalBusLPTIM2); LL_LPTIM_SetUpdateMode(LPTIM2, LL_LPTIM_UPDATE_MODE_ENDOFPERIOD); LL_RCC_SetLPTIMClockSource(LL_RCC_LPTIM2_CLKSOURCE_PCLK1); @@ -80,14 +75,10 @@ void furi_hal_pwm_start(FuriHalPwmOutputId channel, uint32_t freq, uint8_t duty) void furi_hal_pwm_stop(FuriHalPwmOutputId channel) { if(channel == FuriHalPwmOutputIdTim1PA7) { furi_hal_gpio_init_simple(&gpio_ext_pa7, GpioModeAnalog); - FURI_CRITICAL_ENTER(); - LL_TIM_DeInit(TIM1); - FURI_CRITICAL_EXIT(); + furi_hal_bus_disable(FuriHalBusTIM1); } else if(channel == FuriHalPwmOutputIdLptim2PA4) { furi_hal_gpio_init_simple(&gpio_ext_pa4, GpioModeAnalog); - FURI_CRITICAL_ENTER(); - LL_LPTIM_DeInit(LPTIM2); - FURI_CRITICAL_EXIT(); + furi_hal_bus_disable(FuriHalBusLPTIM2); } } diff --git a/firmware/targets/f7/furi_hal/furi_hal_random.c b/firmware/targets/f7/furi_hal/furi_hal_random.c index d3461c4d12e..cf4b552f6d8 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_random.c +++ b/firmware/targets/f7/furi_hal/furi_hal_random.c @@ -1,8 +1,9 @@ #include +#include #include -#include #include +#include #include #include @@ -32,6 +33,11 @@ static uint32_t furi_hal_random_read_rng() { return LL_RNG_ReadRandData32(RNG); } +void furi_hal_random_init() { + furi_hal_bus_enable(FuriHalBusRNG); + LL_RCC_SetRNGClockSource(LL_RCC_RNG_CLKSOURCE_CLK48); +} + uint32_t furi_hal_random_get() { while(LL_HSEM_1StepLock(HSEM, CFG_HW_RNG_SEMID)) ; @@ -40,6 +46,7 @@ uint32_t furi_hal_random_get() { const uint32_t random_val = furi_hal_random_read_rng(); LL_RNG_Disable(RNG); + ; LL_HSEM_ReleaseLock(HSEM, CFG_HW_RNG_SEMID, 0); return random_val; diff --git a/firmware/targets/f7/furi_hal/furi_hal_resources.c b/firmware/targets/f7/furi_hal/furi_hal_resources.c index abfd977e52c..561cef08a79 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_resources.c +++ b/firmware/targets/f7/furi_hal/furi_hal_resources.c @@ -1,4 +1,5 @@ #include +#include #include #include @@ -106,6 +107,13 @@ static void furi_hal_resources_init_input_pins(GpioMode mode) { } void furi_hal_resources_init_early() { + furi_hal_bus_enable(FuriHalBusGPIOA); + furi_hal_bus_enable(FuriHalBusGPIOB); + furi_hal_bus_enable(FuriHalBusGPIOC); + furi_hal_bus_enable(FuriHalBusGPIOD); + furi_hal_bus_enable(FuriHalBusGPIOE); + furi_hal_bus_enable(FuriHalBusGPIOH); + furi_hal_resources_init_input_pins(GpioModeInput); // SD Card stepdown control @@ -150,6 +158,12 @@ void furi_hal_resources_init_early() { void furi_hal_resources_deinit_early() { furi_hal_resources_init_input_pins(GpioModeAnalog); + furi_hal_bus_disable(FuriHalBusGPIOA); + furi_hal_bus_disable(FuriHalBusGPIOB); + furi_hal_bus_disable(FuriHalBusGPIOC); + furi_hal_bus_disable(FuriHalBusGPIOD); + furi_hal_bus_disable(FuriHalBusGPIOE); + furi_hal_bus_disable(FuriHalBusGPIOH); } void furi_hal_resources_init() { diff --git a/firmware/targets/f7/furi_hal/furi_hal_rfid.c b/firmware/targets/f7/furi_hal/furi_hal_rfid.c index 145e49df2c1..fa0c19b098b 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_rfid.c +++ b/firmware/targets/f7/furi_hal/furi_hal_rfid.c @@ -2,6 +2,7 @@ #include #include #include +#include #include #include @@ -9,15 +10,18 @@ #include #define FURI_HAL_RFID_READ_TIMER TIM1 +#define FURI_HAL_RFID_READ_TIMER_BUS FuriHalBusTIM1 #define FURI_HAL_RFID_READ_TIMER_CHANNEL LL_TIM_CHANNEL_CH1N // We can't use N channel for LL_TIM_OC_Init, so... #define FURI_HAL_RFID_READ_TIMER_CHANNEL_CONFIG LL_TIM_CHANNEL_CH1 #define FURI_HAL_RFID_EMULATE_TIMER TIM2 +#define FURI_HAL_RFID_EMULATE_TIMER_BUS FuriHalBusTIM2 #define FURI_HAL_RFID_EMULATE_TIMER_IRQ FuriHalInterruptIdTIM2 #define FURI_HAL_RFID_EMULATE_TIMER_CHANNEL LL_TIM_CHANNEL_CH3 #define RFID_CAPTURE_TIM TIM2 +#define RFID_CAPTURE_TIM_BUS FuriHalBusTIM2 #define RFID_CAPTURE_IND_CH LL_TIM_CHANNEL_CH3 #define RFID_CAPTURE_DIR_CH LL_TIM_CHANNEL_CH4 @@ -30,7 +34,6 @@ #define RFID_DMA_CH2_DEF RFID_DMA, RFID_DMA_CH2_CHANNEL typedef struct { - FuriHalRfidEmulateCallback callback; FuriHalRfidDMACallback dma_callback; FuriHalRfidReadCaptureCallback read_capture_callback; void* context; @@ -56,11 +59,7 @@ void furi_hal_rfid_init() { COMP_InitStruct.InputPlus = LL_COMP_INPUT_PLUS_IO1; COMP_InitStruct.InputMinus = LL_COMP_INPUT_MINUS_1_2VREFINT; COMP_InitStruct.InputHysteresis = LL_COMP_HYSTERESIS_HIGH; -#ifdef INVERT_RFID_IN - COMP_InitStruct.OutputPolarity = LL_COMP_OUTPUTPOL_INVERTED; -#else COMP_InitStruct.OutputPolarity = LL_COMP_OUTPUTPOL_NONINVERTED; -#endif COMP_InitStruct.OutputBlankingSource = LL_COMP_BLANKINGSRC_NONE; LL_COMP_Init(COMP1, &COMP_InitStruct); LL_COMP_SetCommonWindowMode(__LL_COMP_COMMON_INSTANCE(COMP1), LL_COMP_WINDOWMODE_DISABLE); @@ -92,7 +91,7 @@ void furi_hal_rfid_pins_reset() { furi_hal_gpio_init(&gpio_rfid_data_in, GpioModeAnalog, GpioPullNo, GpioSpeedLow); } -void furi_hal_rfid_pins_emulate() { +static void furi_hal_rfid_pins_emulate() { // ibutton low furi_hal_ibutton_pin_configure(); furi_hal_ibutton_pin_write(false); @@ -113,7 +112,7 @@ void furi_hal_rfid_pins_emulate() { &gpio_rfid_carrier, GpioModeAltFunctionPushPull, GpioPullNo, GpioSpeedLow, GpioAltFn2TIM2); } -void furi_hal_rfid_pins_read() { +static void furi_hal_rfid_pins_read() { // ibutton low furi_hal_ibutton_pin_configure(); furi_hal_ibutton_pin_write(false); @@ -142,10 +141,10 @@ void furi_hal_rfid_pin_pull_pulldown() { furi_hal_gpio_write(&gpio_nfc_irq_rfid_pull, false); } -void furi_hal_rfid_tim_read(float freq, float duty_cycle) { - FURI_CRITICAL_ENTER(); - LL_TIM_DeInit(FURI_HAL_RFID_READ_TIMER); - FURI_CRITICAL_EXIT(); +void furi_hal_rfid_tim_read_start(float freq, float duty_cycle) { + furi_hal_bus_enable(FURI_HAL_RFID_READ_TIMER_BUS); + + furi_hal_rfid_pins_read(); LL_TIM_InitTypeDef TIM_InitStruct = {0}; TIM_InitStruct.Autoreload = (SystemCoreClock / freq) - 1; @@ -160,23 +159,23 @@ void furi_hal_rfid_tim_read(float freq, float duty_cycle) { FURI_HAL_RFID_READ_TIMER, FURI_HAL_RFID_READ_TIMER_CHANNEL_CONFIG, &TIM_OC_InitStruct); LL_TIM_EnableCounter(FURI_HAL_RFID_READ_TIMER); + + furi_hal_rfid_tim_read_continue(); } -void furi_hal_rfid_tim_read_start() { +void furi_hal_rfid_tim_read_continue() { LL_TIM_EnableAllOutputs(FURI_HAL_RFID_READ_TIMER); } -void furi_hal_rfid_tim_read_stop() { +void furi_hal_rfid_tim_read_pause() { LL_TIM_DisableAllOutputs(FURI_HAL_RFID_READ_TIMER); } -void furi_hal_rfid_tim_emulate(float freq) { - UNUSED(freq); // FIXME - // basic PWM setup with needed freq and internal clock - FURI_CRITICAL_ENTER(); - LL_TIM_DeInit(FURI_HAL_RFID_EMULATE_TIMER); - FURI_CRITICAL_EXIT(); +void furi_hal_rfid_tim_read_stop() { + furi_hal_bus_disable(FURI_HAL_RFID_READ_TIMER_BUS); +} +static void furi_hal_rfid_tim_emulate() { LL_TIM_SetPrescaler(FURI_HAL_RFID_EMULATE_TIMER, 0); LL_TIM_SetCounterMode(FURI_HAL_RFID_EMULATE_TIMER, LL_TIM_COUNTERMODE_UP); LL_TIM_SetAutoReload(FURI_HAL_RFID_EMULATE_TIMER, 1); @@ -201,32 +200,6 @@ void furi_hal_rfid_tim_emulate(float freq) { LL_TIM_GenerateEvent_UPDATE(FURI_HAL_RFID_EMULATE_TIMER); } -static void furi_hal_rfid_emulate_isr() { - if(LL_TIM_IsActiveFlag_UPDATE(FURI_HAL_RFID_EMULATE_TIMER)) { - LL_TIM_ClearFlag_UPDATE(FURI_HAL_RFID_EMULATE_TIMER); - furi_hal_rfid->callback(furi_hal_rfid->context); - } -} - -void furi_hal_rfid_tim_emulate_start(FuriHalRfidEmulateCallback callback, void* context) { - furi_assert(furi_hal_rfid); - - furi_hal_rfid->callback = callback; - furi_hal_rfid->context = context; - - furi_hal_interrupt_set_isr(FURI_HAL_RFID_EMULATE_TIMER_IRQ, furi_hal_rfid_emulate_isr, NULL); - - LL_TIM_EnableIT_UPDATE(FURI_HAL_RFID_EMULATE_TIMER); - LL_TIM_EnableAllOutputs(FURI_HAL_RFID_EMULATE_TIMER); - LL_TIM_EnableCounter(FURI_HAL_RFID_EMULATE_TIMER); -} - -void furi_hal_rfid_tim_emulate_stop() { - LL_TIM_DisableCounter(FURI_HAL_RFID_EMULATE_TIMER); - LL_TIM_DisableAllOutputs(FURI_HAL_RFID_EMULATE_TIMER); - furi_hal_interrupt_set_isr(FURI_HAL_RFID_EMULATE_TIMER_IRQ, NULL, NULL); -} - static void furi_hal_capture_dma_isr(void* context) { UNUSED(context); @@ -247,15 +220,13 @@ static void furi_hal_capture_dma_isr(void* context) { } void furi_hal_rfid_tim_read_capture_start(FuriHalRfidReadCaptureCallback callback, void* context) { - FURI_CRITICAL_ENTER(); - LL_TIM_DeInit(RFID_CAPTURE_TIM); - FURI_CRITICAL_EXIT(); - furi_assert(furi_hal_rfid); furi_hal_rfid->read_capture_callback = callback; furi_hal_rfid->context = context; + furi_hal_bus_enable(RFID_CAPTURE_TIM_BUS); + // Timer: base LL_TIM_InitTypeDef TIM_InitStruct = {0}; TIM_InitStruct.Prescaler = 64 - 1; @@ -303,10 +274,7 @@ void furi_hal_rfid_tim_read_capture_stop() { furi_hal_rfid_comp_stop(); furi_hal_interrupt_set_isr(FURI_HAL_RFID_EMULATE_TIMER_IRQ, NULL, NULL); - - FURI_CRITICAL_ENTER(); - LL_TIM_DeInit(RFID_CAPTURE_TIM); - FURI_CRITICAL_EXIT(); + furi_hal_bus_disable(RFID_CAPTURE_TIM_BUS); } static void furi_hal_rfid_dma_isr() { @@ -341,7 +309,8 @@ void furi_hal_rfid_tim_emulate_dma_start( furi_hal_rfid_pins_emulate(); // configure timer - furi_hal_rfid_tim_emulate(125000); + furi_hal_bus_enable(FURI_HAL_RFID_EMULATE_TIMER_BUS); + furi_hal_rfid_tim_emulate(); LL_TIM_OC_SetPolarity( FURI_HAL_RFID_EMULATE_TIMER, FURI_HAL_RFID_EMULATE_TIMER_CHANNEL, LL_TIM_OCPOLARITY_HIGH); LL_TIM_EnableDMAReq_UPDATE(FURI_HAL_RFID_EMULATE_TIMER); @@ -405,32 +374,12 @@ void furi_hal_rfid_tim_emulate_dma_stop() { LL_DMA_DeInit(RFID_DMA_CH1_DEF); LL_DMA_DeInit(RFID_DMA_CH2_DEF); - LL_TIM_DeInit(FURI_HAL_RFID_EMULATE_TIMER); - - FURI_CRITICAL_EXIT(); -} -void furi_hal_rfid_tim_reset() { - FURI_CRITICAL_ENTER(); - - LL_TIM_DeInit(FURI_HAL_RFID_READ_TIMER); - LL_TIM_DeInit(FURI_HAL_RFID_EMULATE_TIMER); + furi_hal_bus_disable(FURI_HAL_RFID_EMULATE_TIMER_BUS); FURI_CRITICAL_EXIT(); } -void furi_hal_rfid_set_emulate_period(uint32_t period) { - LL_TIM_SetAutoReload(FURI_HAL_RFID_EMULATE_TIMER, period); -} - -void furi_hal_rfid_set_emulate_pulse(uint32_t pulse) { -#if FURI_HAL_RFID_EMULATE_TIMER_CHANNEL == LL_TIM_CHANNEL_CH3 - LL_TIM_OC_SetCompareCH3(FURI_HAL_RFID_EMULATE_TIMER, pulse); -#else -#error Update this code. Would you kindly? -#endif -} - void furi_hal_rfid_set_read_period(uint32_t period) { LL_TIM_SetAutoReload(FURI_HAL_RFID_READ_TIMER, period); } @@ -443,12 +392,6 @@ void furi_hal_rfid_set_read_pulse(uint32_t pulse) { #endif } -void furi_hal_rfid_change_read_config(float freq, float duty_cycle) { - uint32_t period = (uint32_t)((SystemCoreClock) / freq) - 1; - furi_hal_rfid_set_read_period(period); - furi_hal_rfid_set_read_pulse(period * duty_cycle); -} - void furi_hal_rfid_comp_start() { LL_COMP_Enable(COMP1); // Magic @@ -483,4 +426,4 @@ void COMP_IRQHandler() { (LL_COMP_ReadOutputLevel(COMP1) == LL_COMP_OUTPUT_LEVEL_LOW), furi_hal_rfid_comp_callback_context); } -} \ No newline at end of file +} diff --git a/firmware/targets/f7/furi_hal/furi_hal_rfid.h b/firmware/targets/f7/furi_hal/furi_hal_rfid.h index 36563c1d16a..78d9b665870 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_rfid.h +++ b/firmware/targets/f7/furi_hal/furi_hal_rfid.h @@ -21,14 +21,6 @@ void furi_hal_rfid_init(); */ void furi_hal_rfid_pins_reset(); -/** Config rfid pins to emulate state - */ -void furi_hal_rfid_pins_emulate(); - -/** Config rfid pins to read state - */ -void furi_hal_rfid_pins_read(); - /** Release rfid pull pin */ void furi_hal_rfid_pin_pull_release(); @@ -37,32 +29,23 @@ void furi_hal_rfid_pin_pull_release(); */ void furi_hal_rfid_pin_pull_pulldown(); -/** Config rfid timer to read state - * +/** Start read timer * @param freq timer frequency * @param duty_cycle timer duty cycle, 0.0-1.0 */ -void furi_hal_rfid_tim_read(float freq, float duty_cycle); +void furi_hal_rfid_tim_read_start(float freq, float duty_cycle); -/** Start read timer +/** Pause read timer, to be able to continue later */ -void furi_hal_rfid_tim_read_start(); +void furi_hal_rfid_tim_read_pause(); -/** Stop read timer +/** Continue read timer */ -void furi_hal_rfid_tim_read_stop(); - -/** Config rfid timer to emulate state - * - * @param freq timer frequency - */ -void furi_hal_rfid_tim_emulate(float freq); +void furi_hal_rfid_tim_read_continue(); -typedef void (*FuriHalRfidEmulateCallback)(void* context); - -/** Start emulation timer +/** Stop read timer */ -void furi_hal_rfid_tim_emulate_start(FuriHalRfidEmulateCallback callback, void* context); +void furi_hal_rfid_tim_read_stop(); typedef void (*FuriHalRfidReadCaptureCallback)(bool level, uint32_t duration, void* context); @@ -81,26 +64,6 @@ void furi_hal_rfid_tim_emulate_dma_start( void furi_hal_rfid_tim_emulate_dma_stop(); -/** Stop emulation timer - */ -void furi_hal_rfid_tim_emulate_stop(); - -/** Config rfid timers to reset state - */ -void furi_hal_rfid_tim_reset(); - -/** Set emulation timer period - * - * @param period overall duration - */ -void furi_hal_rfid_set_emulate_period(uint32_t period); - -/** Set emulation timer pulse - * - * @param pulse duration of high level - */ -void furi_hal_rfid_set_emulate_pulse(uint32_t pulse); - /** Set read timer period * * @param period overall duration @@ -113,13 +76,6 @@ void furi_hal_rfid_set_read_period(uint32_t period); */ void furi_hal_rfid_set_read_pulse(uint32_t pulse); -/** Сhanges the configuration of the RFID timer "on a fly" - * - * @param freq new frequency - * @param duty_cycle new duty cycle - */ -void furi_hal_rfid_change_read_config(float freq, float duty_cycle); - /** Start/Enable comparator */ void furi_hal_rfid_comp_start(); diff --git a/firmware/targets/f7/furi_hal/furi_hal_rtc.c b/firmware/targets/f7/furi_hal/furi_hal_rtc.c index 8dfe1a13ec7..a8e25faad66 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_rtc.c +++ b/firmware/targets/f7/furi_hal/furi_hal_rtc.c @@ -2,8 +2,8 @@ #include #include -#include #include +#include #include #include #include diff --git a/firmware/targets/f7/furi_hal/furi_hal_speaker.c b/firmware/targets/f7/furi_hal/furi_hal_speaker.c index 5421509cca5..ad7ed994af2 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_speaker.c +++ b/firmware/targets/f7/furi_hal/furi_hal_speaker.c @@ -2,6 +2,7 @@ #include #include #include +#include #include #include @@ -20,16 +21,11 @@ static FuriMutex* furi_hal_speaker_mutex = NULL; void furi_hal_speaker_init() { furi_assert(furi_hal_speaker_mutex == NULL); furi_hal_speaker_mutex = furi_mutex_alloc(FuriMutexTypeNormal); - FURI_CRITICAL_ENTER(); - LL_TIM_DeInit(FURI_HAL_SPEAKER_TIMER); - FURI_CRITICAL_EXIT(); FURI_LOG_I(TAG, "Init OK"); } void furi_hal_speaker_deinit() { furi_check(furi_hal_speaker_mutex != NULL); - LL_TIM_DeInit(FURI_HAL_SPEAKER_TIMER); - furi_hal_gpio_init(&gpio_speaker, GpioModeAnalog, GpioPullNo, GpioSpeedLow); furi_mutex_free(furi_hal_speaker_mutex); furi_hal_speaker_mutex = NULL; } @@ -39,6 +35,7 @@ bool furi_hal_speaker_acquire(uint32_t timeout) { if(furi_mutex_acquire(furi_hal_speaker_mutex, timeout) == FuriStatusOk) { furi_hal_power_insomnia_enter(); + furi_hal_bus_enable(FuriHalBusTIM16); furi_hal_gpio_init_ex( &gpio_speaker, GpioModeAltFunctionPushPull, GpioPullNo, GpioSpeedLow, GpioAltFn14TIM16); return true; @@ -53,6 +50,8 @@ void furi_hal_speaker_release() { furi_hal_speaker_stop(); furi_hal_gpio_init(&gpio_speaker, GpioModeAnalog, GpioPullDown, GpioSpeedLow); + + furi_hal_bus_disable(FuriHalBusTIM16); furi_hal_power_insomnia_exit(); furi_check(furi_mutex_release(furi_hal_speaker_mutex) == FuriStatusOk); diff --git a/firmware/targets/f7/furi_hal/furi_hal_spi_config.c b/firmware/targets/f7/furi_hal/furi_hal_spi_config.c index 9cf332dacd6..09ac79d2a3c 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_spi_config.c +++ b/firmware/targets/f7/furi_hal/furi_hal_spi_config.c @@ -1,6 +1,7 @@ #include #include #include +#include #include #define TAG "FuriHalSpiConfig" @@ -100,28 +101,17 @@ void furi_hal_spi_config_init() { static void furi_hal_spi_bus_r_event_callback(FuriHalSpiBus* bus, FuriHalSpiBusEvent event) { if(event == FuriHalSpiBusEventInit) { furi_hal_spi_bus_r_mutex = furi_mutex_alloc(FuriMutexTypeNormal); - FURI_CRITICAL_ENTER(); - LL_APB2_GRP1_ForceReset(LL_APB2_GRP1_PERIPH_SPI1); - FURI_CRITICAL_EXIT(); bus->current_handle = NULL; } else if(event == FuriHalSpiBusEventDeinit) { furi_mutex_free(furi_hal_spi_bus_r_mutex); - FURI_CRITICAL_ENTER(); - LL_APB2_GRP1_ForceReset(LL_APB2_GRP1_PERIPH_SPI1); - LL_APB2_GRP1_ReleaseReset(LL_APB2_GRP1_PERIPH_SPI1); - FURI_CRITICAL_EXIT(); } else if(event == FuriHalSpiBusEventLock) { furi_check(furi_mutex_acquire(furi_hal_spi_bus_r_mutex, FuriWaitForever) == FuriStatusOk); } else if(event == FuriHalSpiBusEventUnlock) { furi_check(furi_mutex_release(furi_hal_spi_bus_r_mutex) == FuriStatusOk); } else if(event == FuriHalSpiBusEventActivate) { - FURI_CRITICAL_ENTER(); - LL_APB2_GRP1_ReleaseReset(LL_APB2_GRP1_PERIPH_SPI1); - FURI_CRITICAL_EXIT(); + furi_hal_bus_enable(FuriHalBusSPI1); } else if(event == FuriHalSpiBusEventDeactivate) { - FURI_CRITICAL_ENTER(); - LL_APB2_GRP1_ForceReset(LL_APB2_GRP1_PERIPH_SPI1); - FURI_CRITICAL_EXIT(); + furi_hal_bus_disable(FuriHalBusSPI1); } } @@ -135,28 +125,17 @@ FuriMutex* furi_hal_spi_bus_d_mutex = NULL; static void furi_hal_spi_bus_d_event_callback(FuriHalSpiBus* bus, FuriHalSpiBusEvent event) { if(event == FuriHalSpiBusEventInit) { furi_hal_spi_bus_d_mutex = furi_mutex_alloc(FuriMutexTypeNormal); - FURI_CRITICAL_ENTER(); - LL_APB1_GRP1_ForceReset(LL_APB1_GRP1_PERIPH_SPI2); - FURI_CRITICAL_EXIT(); bus->current_handle = NULL; } else if(event == FuriHalSpiBusEventDeinit) { furi_mutex_free(furi_hal_spi_bus_d_mutex); - FURI_CRITICAL_ENTER(); - LL_APB1_GRP1_ForceReset(LL_APB1_GRP1_PERIPH_SPI2); - LL_APB1_GRP1_ReleaseReset(LL_APB1_GRP1_PERIPH_SPI2); - FURI_CRITICAL_EXIT(); } else if(event == FuriHalSpiBusEventLock) { furi_check(furi_mutex_acquire(furi_hal_spi_bus_d_mutex, FuriWaitForever) == FuriStatusOk); } else if(event == FuriHalSpiBusEventUnlock) { furi_check(furi_mutex_release(furi_hal_spi_bus_d_mutex) == FuriStatusOk); } else if(event == FuriHalSpiBusEventActivate) { - FURI_CRITICAL_ENTER(); - LL_APB1_GRP1_ReleaseReset(LL_APB1_GRP1_PERIPH_SPI2); - FURI_CRITICAL_EXIT(); + furi_hal_bus_enable(FuriHalBusSPI2); } else if(event == FuriHalSpiBusEventDeactivate) { - FURI_CRITICAL_ENTER(); - LL_APB1_GRP1_ForceReset(LL_APB1_GRP1_PERIPH_SPI2); - FURI_CRITICAL_EXIT(); + furi_hal_bus_disable(FuriHalBusSPI2); } } diff --git a/firmware/targets/f7/furi_hal/furi_hal_spi_types.h b/firmware/targets/f7/furi_hal/furi_hal_spi_types.h index d2273f38ba5..ecc18d50db2 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_spi_types.h +++ b/firmware/targets/f7/furi_hal/furi_hal_spi_types.h @@ -6,8 +6,6 @@ #include #include -#include -#include #ifdef __cplusplus extern "C" { diff --git a/firmware/targets/f7/furi_hal/furi_hal_subghz.c b/firmware/targets/f7/furi_hal/furi_hal_subghz.c index 59646461352..6d671a9e15b 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_subghz.c +++ b/firmware/targets/f7/furi_hal/furi_hal_subghz.c @@ -7,6 +7,7 @@ #include #include #include +#include #include @@ -433,6 +434,8 @@ void furi_hal_subghz_start_async_rx(FuriHalSubGhzCaptureCallback callback, void* furi_hal_gpio_init_ex( &gpio_cc1101_g0, GpioModeAltFunctionPushPull, GpioPullNo, GpioSpeedLow, GpioAltFn1TIM2); + furi_hal_bus_enable(FuriHalBusTIM2); + // Timer: base LL_TIM_InitTypeDef TIM_InitStruct = {0}; TIM_InitStruct.Prescaler = 64 - 1; @@ -496,7 +499,7 @@ void furi_hal_subghz_stop_async_rx() { furi_hal_subghz_idle(); FURI_CRITICAL_ENTER(); - LL_TIM_DeInit(TIM2); + furi_hal_bus_disable(FuriHalBusTIM2); // Stop debug furi_hal_subghz_stop_debug(); @@ -658,6 +661,8 @@ bool furi_hal_subghz_start_async_tx(FuriHalSubGhzAsyncTxCallback callback, void* LL_DMA_EnableIT_HT(SUBGHZ_DMA_CH1_DEF); LL_DMA_EnableChannel(SUBGHZ_DMA_CH1_DEF); + furi_hal_bus_enable(FuriHalBusTIM2); + // Configure TIM2 LL_TIM_InitTypeDef TIM_InitStruct = {0}; TIM_InitStruct.Prescaler = 64 - 1; @@ -740,7 +745,7 @@ void furi_hal_subghz_stop_async_tx() { // Deinitialize Timer FURI_CRITICAL_ENTER(); - LL_TIM_DeInit(TIM2); + furi_hal_bus_disable(FuriHalBusTIM2); furi_hal_interrupt_set_isr(FuriHalInterruptIdTIM2, NULL, NULL); // Deinitialize DMA diff --git a/firmware/targets/f7/furi_hal/furi_hal_uart.c b/firmware/targets/f7/furi_hal/furi_hal_uart.c index 71b5c7ba044..209c6be6a2e 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_uart.c +++ b/firmware/targets/f7/furi_hal/furi_hal_uart.c @@ -4,6 +4,7 @@ #include #include #include +#include #include @@ -13,6 +14,9 @@ static void (*irq_cb[2])(uint8_t ev, uint8_t data, void* context); static void* irq_ctx[2]; static void furi_hal_usart_init(uint32_t baud) { + furi_hal_bus_enable(FuriHalBusUSART1); + LL_RCC_SetUSARTClockSource(LL_RCC_USART1_CLKSOURCE_PCLK2); + furi_hal_gpio_init_ex( &gpio_usart_tx, GpioModeAltFunctionPushPull, @@ -50,6 +54,9 @@ static void furi_hal_usart_init(uint32_t baud) { } static void furi_hal_lpuart_init(uint32_t baud) { + furi_hal_bus_enable(FuriHalBusLPUART1); + LL_RCC_SetLPUARTClockSource(LL_RCC_LPUART1_CLKSOURCE_PCLK1); + furi_hal_gpio_init_ex( &gpio_ext_pc0, GpioModeAltFunctionPushPull, @@ -86,10 +93,11 @@ static void furi_hal_lpuart_init(uint32_t baud) { } void furi_hal_uart_init(FuriHalUartId ch, uint32_t baud) { - if(ch == FuriHalUartIdLPUART1) + if(ch == FuriHalUartIdLPUART1) { furi_hal_lpuart_init(baud); - else if(ch == FuriHalUartIdUSART1) + } else if(ch == FuriHalUartIdUSART1) { furi_hal_usart_init(baud); + } } void furi_hal_uart_set_br(FuriHalUartId ch, uint32_t baud) { @@ -126,11 +134,15 @@ void furi_hal_uart_set_br(FuriHalUartId ch, uint32_t baud) { void furi_hal_uart_deinit(FuriHalUartId ch) { furi_hal_uart_set_irq_cb(ch, NULL, NULL); if(ch == FuriHalUartIdUSART1) { - LL_USART_Disable(USART1); + if(furi_hal_bus_is_enabled(FuriHalBusUSART1)) { + furi_hal_bus_disable(FuriHalBusUSART1); + } furi_hal_gpio_init(&gpio_usart_tx, GpioModeAnalog, GpioPullNo, GpioSpeedLow); furi_hal_gpio_init(&gpio_usart_rx, GpioModeAnalog, GpioPullNo, GpioSpeedLow); } else if(ch == FuriHalUartIdLPUART1) { - LL_LPUART_Disable(LPUART1); + if(furi_hal_bus_is_enabled(FuriHalBusLPUART1)) { + furi_hal_bus_disable(FuriHalBusLPUART1); + } furi_hal_gpio_init(&gpio_ext_pc0, GpioModeAnalog, GpioPullNo, GpioSpeedLow); furi_hal_gpio_init(&gpio_ext_pc1, GpioModeAnalog, GpioPullNo, GpioSpeedLow); } diff --git a/firmware/targets/f7/furi_hal/furi_hal_usb.c b/firmware/targets/f7/furi_hal/furi_hal_usb.c index 011add95323..b88168d5d0c 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_usb.c +++ b/firmware/targets/f7/furi_hal/furi_hal_usb.c @@ -2,7 +2,9 @@ #include #include #include + #include +#include #include #include @@ -86,6 +88,8 @@ static void wkup_evt(usbd_device* dev, uint8_t event, uint8_t ep); /* Low-level init */ void furi_hal_usb_init(void) { + LL_RCC_SetUSBClockSource(LL_RCC_USB_CLKSOURCE_PLLSAI1); + LL_GPIO_InitTypeDef GPIO_InitStruct = {0}; LL_PWR_EnableVddUSB(); @@ -98,7 +102,10 @@ void furi_hal_usb_init(void) { LL_GPIO_Init(GPIOA, &GPIO_InitStruct); usbd_init(&udev, &usbd_hw, USB_EP0_SIZE, ubuf, sizeof(ubuf)); + + FURI_CRITICAL_ENTER(); usbd_enable(&udev, true); + FURI_CRITICAL_EXIT(); usbd_reg_descr(&udev, usb_descriptor_get); usbd_reg_event(&udev, usbd_evt_susp, susp_evt); @@ -359,8 +366,10 @@ static void usb_process_mode_reinit() { usbd_connect(&udev, false); usb.enabled = false; + FURI_CRITICAL_ENTER(); usbd_enable(&udev, false); usbd_enable(&udev, true); + FURI_CRITICAL_EXIT(); furi_delay_ms(USB_RECONNECT_DELAY); usb_process_mode_start(usb.interface, usb.interface_context); diff --git a/firmware/targets/f7/src/update.c b/firmware/targets/f7/src/update.c index c1e1084c218..c6235a150f6 100644 --- a/firmware/targets/f7/src/update.c +++ b/firmware/targets/f7/src/update.c @@ -38,6 +38,12 @@ static bool flipper_update_mount_sd() { } static bool flipper_update_init() { + // TODO: Configure missing peripherals properly + furi_hal_bus_enable(FuriHalBusHSEM); + furi_hal_bus_enable(FuriHalBusIPCC); + furi_hal_bus_enable(FuriHalBusRNG); + furi_hal_bus_enable(FuriHalBusUSART1); + furi_hal_clock_init(); furi_hal_rtc_init(); furi_hal_interrupt_init(); diff --git a/firmware/targets/furi_hal_include/furi_hal.h b/firmware/targets/furi_hal_include/furi_hal.h index 2eb4688d428..9341dccecbe 100644 --- a/firmware/targets/furi_hal_include/furi_hal.h +++ b/firmware/targets/furi_hal_include/furi_hal.h @@ -12,9 +12,11 @@ struct STOP_EXTERNING_ME {}; #include #include +#include #include #include #include +#include #include #include #include diff --git a/firmware/targets/furi_hal_include/furi_hal_random.h b/firmware/targets/furi_hal_include/furi_hal_random.h index ee69326f416..5ca9cb723c3 100644 --- a/firmware/targets/furi_hal_include/furi_hal_random.h +++ b/firmware/targets/furi_hal_include/furi_hal_random.h @@ -6,6 +6,9 @@ extern "C" { #endif +/** Initialize random subsystem */ +void furi_hal_random_init(); + /** Get random value * * @return random value diff --git a/lib/digital_signal/digital_signal.c b/lib/digital_signal/digital_signal.c index 51a87d22a5b..ab25458b518 100644 --- a/lib/digital_signal/digital_signal.c +++ b/lib/digital_signal/digital_signal.c @@ -243,10 +243,12 @@ static void digital_signal_stop_timer() { LL_TIM_DisableCounter(TIM2); LL_TIM_DisableUpdateEvent(TIM2); LL_TIM_DisableDMAReq_UPDATE(TIM2); + + furi_hal_bus_disable(FuriHalBusTIM2); } static void digital_signal_setup_timer() { - digital_signal_stop_timer(); + furi_hal_bus_enable(FuriHalBusTIM2); LL_TIM_SetCounterMode(TIM2, LL_TIM_COUNTERMODE_UP); LL_TIM_SetClockDivision(TIM2, LL_TIM_CLOCKDIVISION_DIV1); diff --git a/lib/lfrfid/lfrfid_raw_worker.c b/lib/lfrfid/lfrfid_raw_worker.c index 22c0bbd02c1..aa962a47d69 100644 --- a/lib/lfrfid/lfrfid_raw_worker.c +++ b/lib/lfrfid/lfrfid_raw_worker.c @@ -151,9 +151,7 @@ static int32_t lfrfid_raw_read_worker_thread(void* thread_context) { if(file_valid) { // setup carrier - furi_hal_rfid_pins_read(); - furi_hal_rfid_tim_read(worker->frequency, worker->duty_cycle); - furi_hal_rfid_tim_read_start(); + furi_hal_rfid_tim_read_start(worker->frequency, worker->duty_cycle); // stabilize detector furi_delay_ms(1500); diff --git a/lib/lfrfid/lfrfid_worker_modes.c b/lib/lfrfid/lfrfid_worker_modes.c index 9b6f16eb14c..8a26677746d 100644 --- a/lib/lfrfid/lfrfid_worker_modes.c +++ b/lib/lfrfid/lfrfid_worker_modes.c @@ -100,24 +100,21 @@ static LFRFIDWorkerReadState lfrfid_worker_read_internal( uint32_t timeout, ProtocolId* result_protocol) { LFRFIDWorkerReadState state = LFRFIDWorkerReadTimeout; - furi_hal_rfid_pins_read(); if(feature & LFRFIDFeatureASK) { - furi_hal_rfid_tim_read(125000, 0.5); + furi_hal_rfid_tim_read_start(125000, 0.5); FURI_LOG_D(TAG, "Start ASK"); if(worker->read_cb) { worker->read_cb(LFRFIDWorkerReadStartASK, PROTOCOL_NO, worker->cb_ctx); } } else { - furi_hal_rfid_tim_read(62500, 0.25); + furi_hal_rfid_tim_read_start(62500, 0.25); FURI_LOG_D(TAG, "Start PSK"); if(worker->read_cb) { worker->read_cb(LFRFIDWorkerReadStartPSK, PROTOCOL_NO, worker->cb_ctx); } } - furi_hal_rfid_tim_read_start(); - // stabilize detector lfrfid_worker_delay(worker, LFRFID_WORKER_READ_STABILIZE_TIME_MS); @@ -205,7 +202,7 @@ static LFRFIDWorkerReadState lfrfid_worker_read_internal( average_index = 0; if(worker->read_cb) { - if(average > 0.2 && average < 0.8) { + if(average > 0.2f && average < 0.8f) { if(!card_detected) { card_detected = true; worker->read_cb( diff --git a/lib/lfrfid/tools/t5577.c b/lib/lfrfid/tools/t5577.c index 3444afea3e4..00c9ddfb627 100644 --- a/lib/lfrfid/tools/t5577.c +++ b/lib/lfrfid/tools/t5577.c @@ -14,9 +14,7 @@ #define T5577_OPCODE_RESET 0b00 static void t5577_start() { - furi_hal_rfid_tim_read(125000, 0.5); - furi_hal_rfid_pins_read(); - furi_hal_rfid_tim_read_start(); + furi_hal_rfid_tim_read_start(125000, 0.5); // do not ground the antenna furi_hal_rfid_pin_pull_release(); @@ -24,14 +22,13 @@ static void t5577_start() { static void t5577_stop() { furi_hal_rfid_tim_read_stop(); - furi_hal_rfid_tim_reset(); furi_hal_rfid_pins_reset(); } static void t5577_write_gap(uint32_t gap_time) { - furi_hal_rfid_tim_read_stop(); + furi_hal_rfid_tim_read_pause(); furi_delay_us(gap_time * 8); - furi_hal_rfid_tim_read_start(); + furi_hal_rfid_tim_read_continue(); } static void t5577_write_bit(bool value) { diff --git a/lib/nfc/parsers/opal.c b/lib/nfc/parsers/opal.c index b5ca37eb61d..2a6b5d1221a 100644 --- a/lib/nfc/parsers/opal.c +++ b/lib/nfc/parsers/opal.c @@ -143,7 +143,7 @@ bool opal_parser_parse(NfcDeviceData* dev_data) { // sign separately, because then we can handle balances of -99..-1 // cents, as the "dollars" division below would result in a positive // zero value. - o->balance = abs(o->balance); + o->balance = abs(o->balance); //-V1081 sign = "-"; } uint8_t cents = o->balance % 100; @@ -164,7 +164,7 @@ bool opal_parser_parse(NfcDeviceData* dev_data) { o->mode = 4; } - const char* mode_str = (o->mode <= 4 ? opal_modes[o->mode] : opal_modes[3]); + const char* mode_str = (o->mode <= 4 ? opal_modes[o->mode] : opal_modes[3]); //-V547 const char* usage_str = (o->usage <= 12 ? opal_usages[o->usage] : opal_usages[13]); furi_string_printf( diff --git a/lib/pulse_reader/pulse_reader.c b/lib/pulse_reader/pulse_reader.c index 74d99fe801c..1c3cb4a586f 100644 --- a/lib/pulse_reader/pulse_reader.c +++ b/lib/pulse_reader/pulse_reader.c @@ -135,6 +135,7 @@ void pulse_reader_stop(PulseReader* signal) { LL_DMA_DisableChannel(DMA1, signal->dma_channel + 1); LL_DMAMUX_DisableRequestGen(NULL, LL_DMAMUX_REQ_GEN_0); LL_TIM_DisableCounter(TIM2); + furi_hal_bus_disable(FuriHalBusTIM2); furi_hal_gpio_init_simple(signal->gpio, GpioModeAnalog); } @@ -146,6 +147,8 @@ void pulse_reader_start(PulseReader* signal) { signal->dma_config_gpio.MemoryOrM2MDstAddress = (uint32_t)signal->gpio_buffer; signal->dma_config_gpio.NbData = signal->size; + furi_hal_bus_enable(FuriHalBusTIM2); + /* start counter */ LL_TIM_SetCounterMode(TIM2, LL_TIM_COUNTERMODE_UP); LL_TIM_SetClockDivision(TIM2, LL_TIM_CLOCKDIVISION_DIV1); From 8d2ea14f0694af81f7e0b04232e910e504d8cfc4 Mon Sep 17 00:00:00 2001 From: hedger Date: Mon, 29 May 2023 20:40:56 +0400 Subject: [PATCH 581/824] [FL-3330] fbt: added hooks for build & dist environments; added FW_ORIGIN_* macro for apps & SDK (#2705) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fbt: added hooks for build & dist environments * Moved env hooks to an optional file * Fixed var name * Added fw origin to device info * Bumped device info version * fbt: added FIRMWARE_ORIGIN option. Different implementation for FW_ORIGIN_* C macro. * api: bumped versions * fbt: added fbt_options_local.py * gitignore: cleanup Co-authored-by: あく --- .gitignore | 22 +++---- documentation/fbt.md | 2 + fbt_options.py | 7 ++ firmware.scons | 11 ++++ firmware/targets/f18/api_symbols.csv | 4 +- firmware/targets/f7/api_symbols.csv | 4 +- firmware/targets/f7/furi_hal/furi_hal_info.c | 20 +++++- furi/core/core_defines.h | 5 ++ lib/toolbox/version.c | 15 ++++- lib/toolbox/version.h | 11 ++++ scripts/debug/flipperversion.py | 16 ++++- scripts/fbt_tools/fbt_envhooks.py | 67 ++++++++++++++++++++ scripts/fbt_tools/fbt_version.py | 4 +- scripts/ufbt/SConstruct | 5 ++ scripts/version.py | 23 +++++++ site_scons/commandline.scons | 7 ++ 16 files changed, 206 insertions(+), 17 deletions(-) create mode 100644 scripts/fbt_tools/fbt_envhooks.py diff --git a/.gitignore b/.gitignore index bf17a94e28d..d60dcec3f5d 100644 --- a/.gitignore +++ b/.gitignore @@ -30,27 +30,25 @@ bindings/ .mxproject Brewfile.lock.json -# Visual Studio Code -/.vscode/ - # Kate .kateproject .kateconfig -# legendary cmake's -build -CMakeLists.txt - -# bundle output -dist - # kde .directory # SCons .sconsign.dblite + + +# Visual Studio Code +/.vscode + +# bundle output +/dist + # SCons build dir -build/ +/build # Toolchain /toolchain @@ -64,3 +62,5 @@ PVS-Studio.log *.PVS-Studio.* .gdbinit + +/fbt_options_local.py \ No newline at end of file diff --git a/documentation/fbt.md b/documentation/fbt.md index d9eb8f4aabb..c19780ef593 100644 --- a/documentation/fbt.md +++ b/documentation/fbt.md @@ -114,6 +114,8 @@ To run cleanup (think of `make clean`) for specified targets, add the `-c` optio Default configuration variables are set in the configuration file: `fbt_options.py`. Values set in the command line have higher precedence over the configuration file. +You can also create a file called `fbt_options_local.py` that will be evaluated when loading default options file, enabling persisent overriding of default options without modifying default configuration. + You can find out available options with `./fbt -h`. ### Firmware application set diff --git a/fbt_options.py b/fbt_options.py index d05b882a0ca..b6fcc70f233 100644 --- a/fbt_options.py +++ b/fbt_options.py @@ -1,7 +1,9 @@ +from pathlib import Path import posixpath # For more details on these options, run 'fbt -h' +FIRMWARE_ORIGIN = "Official" # Default hardware target TARGET_HW = 7 @@ -75,3 +77,8 @@ } FIRMWARE_APP_SET = "default" + +custom_options_fn = "fbt_options_local.py" + +if Path(custom_options_fn).exists(): + exec(compile(Path(custom_options_fn).read_text(), custom_options_fn, "exec")) diff --git a/firmware.scons b/firmware.scons index c4699689913..a91b7cdfc61 100644 --- a/firmware.scons +++ b/firmware.scons @@ -18,6 +18,7 @@ env = ENV.Clone( "fbt_apps", "pvsstudio", "fbt_hwtarget", + "fbt_envhooks", ], COMPILATIONDB_USE_ABSPATH=False, BUILD_DIR=fw_build_meta["build_dir"], @@ -72,6 +73,8 @@ env = ENV.Clone( _APP_ICONS=None, ) +env.PreConfigureFwEnvionment() + if env["IS_BASE_FIRMWARE"]: env.Append( FIRMWARE_BUILD_CFG="firmware", @@ -100,6 +103,13 @@ lib_targets = env.BuildModules( ], ) +# Configure firmware origin definitions +env.Append( + CPPDEFINES=[ + env.subst("FW_ORIGIN_${FIRMWARE_ORIGIN}"), + ] +) + # Now, env is fully set up with everything to build apps fwenv = env.Clone(FW_ARTIFACTS=[]) @@ -271,5 +281,6 @@ if should_gen_cdb_and_link_dir(fwenv, BUILD_TARGETS): Alias(fwenv["FIRMWARE_BUILD_CFG"] + "_all", fw_artifacts) +env.PostConfigureFwEnvionment() Return("fwenv") diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index 92c8697aafc..7f0dcebd52b 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,28.0,, +Version,+,28.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -2003,6 +2003,8 @@ Function,-,vdprintf,int,"int, const char*, __gnuc_va_list" Function,+,version_get,const Version*, Function,+,version_get_builddate,const char*,const Version* Function,+,version_get_dirty_flag,_Bool,const Version* +Function,+,version_get_firmware_origin,const char*,const Version* +Function,+,version_get_git_origin,const char*,const Version* Function,+,version_get_gitbranch,const char*,const Version* Function,+,version_get_gitbranchnum,const char*,const Version* Function,+,version_get_githash,const char*,const Version* diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 4b48949d99f..1259f0fce51 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,28.0,, +Version,+,28.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -2945,6 +2945,8 @@ Function,-,vdprintf,int,"int, const char*, __gnuc_va_list" Function,+,version_get,const Version*, Function,+,version_get_builddate,const char*,const Version* Function,+,version_get_dirty_flag,_Bool,const Version* +Function,+,version_get_firmware_origin,const char*,const Version* +Function,+,version_get_git_origin,const char*,const Version* Function,+,version_get_gitbranch,const char*,const Version* Function,+,version_get_gitbranchnum,const char*,const Version* Function,+,version_get_githash,const char*,const Version* diff --git a/firmware/targets/f7/furi_hal/furi_hal_info.c b/firmware/targets/f7/furi_hal/furi_hal_info.c index 4c034ff3521..47672c97a33 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_info.c +++ b/firmware/targets/f7/furi_hal/furi_hal_info.c @@ -26,7 +26,7 @@ void furi_hal_info_get(PropertyValueCallback out, char sep, void* context) { property_value_out(&property_context, NULL, 2, "format", "minor", "1"); } else { property_value_out(&property_context, NULL, 3, "device", "info", "major", "2"); - property_value_out(&property_context, NULL, 3, "device", "info", "minor", "1"); + property_value_out(&property_context, NULL, 3, "device", "info", "minor", "2"); } // Model name @@ -173,6 +173,24 @@ void furi_hal_info_get(PropertyValueCallback out, char sep, void* context) { &property_context, "%d", 3, "firmware", "api", "major", api_version_major); property_value_out( &property_context, "%d", 3, "firmware", "api", "minor", api_version_minor); + + property_value_out( + &property_context, + NULL, + 3, + "firmware", + "origin", + "fork", + version_get_firmware_origin(firmware_version)); + + property_value_out( + &property_context, + NULL, + 3, + "firmware", + "origin", + "git", + version_get_git_origin(firmware_version)); } if(furi_hal_bt_is_alive()) { diff --git a/furi/core/core_defines.h b/furi/core/core_defines.h index 830bb191c2e..4309c20c589 100644 --- a/furi/core/core_defines.h +++ b/furi/core/core_defines.h @@ -78,6 +78,11 @@ extern "C" { #define TOSTRING(x) STRINGIFY(x) #endif +#ifndef CONCATENATE +#define CONCATENATE(a, b) CONCATENATE_(a, b) +#define CONCATENATE_(a, b) a##b +#endif + #ifndef REVERSE_BYTES_U32 #define REVERSE_BYTES_U32(x) \ ((((x)&0x000000FF) << 24) | (((x)&0x0000FF00) << 8) | (((x)&0x00FF0000) >> 8) | \ diff --git a/lib/toolbox/version.c b/lib/toolbox/version.c index 6ba68e364d9..876695f0785 100644 --- a/lib/toolbox/version.c +++ b/lib/toolbox/version.c @@ -5,7 +5,7 @@ #define VERSION_MAGIC (0xBE40u) #define VERSION_MAJOR (0x1u) -#define VERSION_MINOR (0x0u) +#define VERSION_MINOR (0x1u) struct Version { // Header @@ -20,6 +20,9 @@ struct Version { // Payload bits and pieces const uint8_t target; const bool build_is_dirty; + // v 1.1 + const char* firmware_origin; + const char* git_origin; }; /* version of current running firmware (bootloader/flipper) */ @@ -37,6 +40,8 @@ static const Version version = { , .target = TARGET, .build_is_dirty = BUILD_DIRTY, + .firmware_origin = FIRMWARE_ORIGIN, + .git_origin = GIT_ORIGIN, }; const Version* version_get(void) { @@ -71,3 +76,11 @@ uint8_t version_get_target(const Version* v) { bool version_get_dirty_flag(const Version* v) { return v ? v->build_is_dirty : version.build_is_dirty; } + +const char* version_get_firmware_origin(const Version* v) { + return v ? v->firmware_origin : version.firmware_origin; +} + +const char* version_get_git_origin(const Version* v) { + return v ? v->git_origin : version.git_origin; +} diff --git a/lib/toolbox/version.h b/lib/toolbox/version.h index 652ff3feace..0c04e5c7596 100644 --- a/lib/toolbox/version.h +++ b/lib/toolbox/version.h @@ -82,6 +82,17 @@ uint8_t version_get_target(const Version* v); */ bool version_get_dirty_flag(const Version* v); +/** + * Get firmware origin. "Official" for mainline firmware, fork name for forks. + * Set by FIRMWARE_ORIGIN fbt argument. +*/ +const char* version_get_firmware_origin(const Version* v); + +/** + * Get git repo origin +*/ +const char* version_get_git_origin(const Version* v); + #ifdef __cplusplus } #endif diff --git a/scripts/debug/flipperversion.py b/scripts/debug/flipperversion.py index 4ac3bd200d1..56915c0f28e 100644 --- a/scripts/debug/flipperversion.py +++ b/scripts/debug/flipperversion.py @@ -23,6 +23,10 @@ class VersionData: version: str target: int build_is_dirty: bool + # Since version 1.1 + firmware_origin: str = "" + git_origin: str = "" + # More fields may be added in the future extra: Optional[Dict[str, str]] = field(default_factory=dict) @@ -52,7 +56,7 @@ def load_versioned(self, major, minor): # Struct version 1.0 extra_data = int(self.version_ptr[5].cast(self._uint_type)) - return VersionData( + version_data = VersionData( git_hash=self.version_ptr[1].cast(self._cstr_type).string(), git_branch=self.version_ptr[2].cast(self._cstr_type).string(), build_date=self.version_ptr[3].cast(self._cstr_type).string(), @@ -60,6 +64,12 @@ def load_versioned(self, major, minor): target=extra_data & 0xF, build_is_dirty=bool((extra_data >> 8) & 0xF), ) + if minor >= 1: + version_data.firmware_origin = ( + self.version_ptr[6].cast(self._cstr_type).string() + ) + version_data.git_origin = self.version_ptr[7].cast(self._cstr_type).string() + return version_data def load_unversioned(self): """Parse an early version of the version struct.""" @@ -104,6 +114,10 @@ def invoke(self, arg, from_tty): print(f"\tGit commit: {v.version.git_hash}") print(f"\tDirty: {v.version.build_is_dirty}") print(f"\tHW Target: {v.version.target}") + if v.version.firmware_origin: + print(f"\tOrigin: {v.version.firmware_origin}") + if v.version.git_origin: + print(f"\tGit origin: {v.version.git_origin}") FlipperFwVersion() diff --git a/scripts/fbt_tools/fbt_envhooks.py b/scripts/fbt_tools/fbt_envhooks.py new file mode 100644 index 00000000000..0538e173c65 --- /dev/null +++ b/scripts/fbt_tools/fbt_envhooks.py @@ -0,0 +1,67 @@ +""" + +To introduce changes to firmware build environment that are specific to your fork: + + create a file "scripts/fbt/fbt_hooks.py" + +With it, you can define functions that will be called at specific points of +firmware build configuration, with environment as an argument. + +For example, you can define a function `PreConfigureFwEnvionment(env)` that +defines that will be a part of SDK build, so applications can +use them for conditional compilation. + +Here is a list of all available hooks: + + PreConfigureFwEnvionment(env): + This function is called on firmware environment (incl. updater) + before any major configuration is done. + + PostConfigureFwEnvionment(env): + This function is called on firmware environment (incl. updater) + after all configuration is done. + + PreConfigureUfbtEnvionment(env): + This function is called on ufbt environment at the beginning of + its configuration, before dist environment is created. + + PostConfigureUfbtEnvionment(env): + This function is called on ufbt dist_env environment after all + configuration and target creation is done. +""" + + +class DefaultFbtHooks: + pass + + +try: + from fbt import fbt_hooks +except ImportError: + fbt_hooks = DefaultFbtHooks() + + +def generate(env): + stub_hook = lambda env: None + control_hooks = [ + "PreConfigureFwEnvionment", + "PostConfigureFwEnvionment", + "PreConfigureUfbtEnvionment", + "PostConfigureUfbtEnvionment", + ] + + if ( + isinstance(fbt_hooks, DefaultFbtHooks) + and env.subst("${FIRMWARE_ORIGIN}") != "Official" + ): + # If fbt_hooks.py is not present, but we are not building official firmware, + # create "scripts/fbt/fbt_hooks.py" to implement changes to firmware build environment. + pass + + for hook_name in control_hooks: + hook_fn = getattr(fbt_hooks, hook_name, stub_hook) + env.AddMethod(hook_fn, hook_name) + + +def exists(): + return True diff --git a/scripts/fbt_tools/fbt_version.py b/scripts/fbt_tools/fbt_version.py index 8469e181a32..aead13b29fa 100644 --- a/scripts/fbt_tools/fbt_version.py +++ b/scripts/fbt_tools/fbt_version.py @@ -19,7 +19,9 @@ def generate(env): BUILDERS={ "VersionBuilder": Builder( action=Action( - '${PYTHON3} "${VERSION_SCRIPT}" generate -t ${TARGET_HW} -o ${TARGET.dir.posix} --dir "${ROOT_DIR}"', + '${PYTHON3} "${VERSION_SCRIPT}" generate ' + "-t ${TARGET_HW} -fw-origin ${FIRMWARE_ORIGIN} " + '-o ${TARGET.dir.posix} --dir "${ROOT_DIR}"', "${VERSIONCOMSTR}", ), emitter=version_emitter, diff --git a/scripts/ufbt/SConstruct b/scripts/ufbt/SConstruct index 4dd1fb5b904..d72de380c38 100644 --- a/scripts/ufbt/SConstruct +++ b/scripts/ufbt/SConstruct @@ -98,6 +98,7 @@ env = core_env.Clone( "fbt_apps", "fbt_extapps", "fbt_assets", + "fbt_envhooks", ("compilation_db", {"COMPILATIONDB_COMSTR": "\tCDB\t${TARGET}"}), ], FBT_FAP_DEBUG_ELF_ROOT=ufbt_build_dir, @@ -117,6 +118,8 @@ env = core_env.Clone( wrap_tempfile(env, "LINKCOM") wrap_tempfile(env, "ARCOM") +env.PreConfigureUfbtEnvionment() + # print(env.Dump()) # Dist env @@ -474,3 +477,5 @@ dist_env.PhonyTarget( "env", "@echo $( ${FBT_SCRIPT_DIR}/toolchain/fbtenv.sh $)", ) + +dist_env.PostConfigureUfbtEnvionment() diff --git a/scripts/version.py b/scripts/version.py index 3d68b2e98d4..f00c1f531c1 100644 --- a/scripts/version.py +++ b/scripts/version.py @@ -45,8 +45,23 @@ def get_version_info(self): "GIT_BRANCH": branch, "VERSION": version, "BUILD_DIRTY": dirty and 1 or 0, + "GIT_ORIGIN": ",".join(self._get_git_origins()), } + def _get_git_origins(self): + try: + remotes = self._exec_git("remote -v") + except subprocess.CalledProcessError: + return set() + origins = set() + for line in remotes.split("\n"): + if not line: + continue + _, destination = line.split("\t") + url, _ = destination.split(" ") + origins.add(url) + return origins + def _exec_git(self, args): cmd = ["git"] cmd.extend(args.split(" ")) @@ -74,6 +89,13 @@ def init(self): help="hardware target", required=True, ) + self.parser_generate.add_argument( + "-fw-origin", + dest="firmware_origin", + type=str, + help="firmware origin", + required=True, + ) self.parser_generate.add_argument("--dir", dest="sourcedir", required=True) self.parser_generate.set_defaults(func=self.generate) @@ -89,6 +111,7 @@ def generate(self): { "BUILD_DATE": build_date.strftime("%d-%m-%Y"), "TARGET": self.args.target, + "FIRMWARE_ORIGIN": self.args.firmware_origin, } ) diff --git a/site_scons/commandline.scons b/site_scons/commandline.scons index 2e948662738..e31927c5942 100644 --- a/site_scons/commandline.scons +++ b/site_scons/commandline.scons @@ -236,6 +236,13 @@ vars.AddVariables( help="Don't open browser after generating error repots", default=False, ), + ( + "FIRMWARE_ORIGIN", + "Firmware origin. 'Official' if follows upstream's API structure, otherwise fork name. " + " This will also create a C define FW_ORIGIN_ so that " + " app can check what version it is being built for.", + "Official", + ), ) Return("vars") From a76c189ca52871aa4fa9c94882202e19f008d202 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Tue, 30 May 2023 20:47:28 +0900 Subject: [PATCH 582/824] [FL-3335] Dolphin: new animation (#2713) --- .../external/L1_Kaiju_128x64/frame_0.png | Bin 0 -> 1312 bytes .../external/L1_Kaiju_128x64/frame_1.png | Bin 0 -> 1302 bytes .../external/L1_Kaiju_128x64/frame_10.png | Bin 0 -> 1332 bytes .../external/L1_Kaiju_128x64/frame_11.png | Bin 0 -> 1228 bytes .../external/L1_Kaiju_128x64/frame_12.png | Bin 0 -> 1152 bytes .../external/L1_Kaiju_128x64/frame_13.png | Bin 0 -> 1152 bytes .../external/L1_Kaiju_128x64/frame_14.png | Bin 0 -> 1162 bytes .../external/L1_Kaiju_128x64/frame_15.png | Bin 0 -> 1209 bytes .../external/L1_Kaiju_128x64/frame_16.png | Bin 0 -> 1158 bytes .../external/L1_Kaiju_128x64/frame_17.png | Bin 0 -> 1161 bytes .../external/L1_Kaiju_128x64/frame_18.png | Bin 0 -> 828 bytes .../external/L1_Kaiju_128x64/frame_19.png | Bin 0 -> 817 bytes .../external/L1_Kaiju_128x64/frame_2.png | Bin 0 -> 1288 bytes .../external/L1_Kaiju_128x64/frame_20.png | Bin 0 -> 1222 bytes .../external/L1_Kaiju_128x64/frame_21.png | Bin 0 -> 1494 bytes .../external/L1_Kaiju_128x64/frame_22.png | Bin 0 -> 1685 bytes .../external/L1_Kaiju_128x64/frame_23.png | Bin 0 -> 1680 bytes .../external/L1_Kaiju_128x64/frame_24.png | Bin 0 -> 1690 bytes .../external/L1_Kaiju_128x64/frame_25.png | Bin 0 -> 1658 bytes .../external/L1_Kaiju_128x64/frame_26.png | Bin 0 -> 1716 bytes .../external/L1_Kaiju_128x64/frame_27.png | Bin 0 -> 1741 bytes .../external/L1_Kaiju_128x64/frame_28.png | Bin 0 -> 1686 bytes .../external/L1_Kaiju_128x64/frame_29.png | Bin 0 -> 1626 bytes .../external/L1_Kaiju_128x64/frame_3.png | Bin 0 -> 1305 bytes .../external/L1_Kaiju_128x64/frame_30.png | Bin 0 -> 1677 bytes .../external/L1_Kaiju_128x64/frame_31.png | Bin 0 -> 1639 bytes .../external/L1_Kaiju_128x64/frame_32.png | Bin 0 -> 1618 bytes .../external/L1_Kaiju_128x64/frame_33.png | Bin 0 -> 1595 bytes .../external/L1_Kaiju_128x64/frame_34.png | Bin 0 -> 1591 bytes .../external/L1_Kaiju_128x64/frame_35.png | Bin 0 -> 1560 bytes .../external/L1_Kaiju_128x64/frame_36.png | Bin 0 -> 1592 bytes .../external/L1_Kaiju_128x64/frame_37.png | Bin 0 -> 1494 bytes .../external/L1_Kaiju_128x64/frame_38.png | Bin 0 -> 1489 bytes .../external/L1_Kaiju_128x64/frame_39.png | Bin 0 -> 1438 bytes .../external/L1_Kaiju_128x64/frame_4.png | Bin 0 -> 1284 bytes .../external/L1_Kaiju_128x64/frame_40.png | Bin 0 -> 1438 bytes .../external/L1_Kaiju_128x64/frame_41.png | Bin 0 -> 1412 bytes .../external/L1_Kaiju_128x64/frame_42.png | Bin 0 -> 1425 bytes .../external/L1_Kaiju_128x64/frame_43.png | Bin 0 -> 1397 bytes .../external/L1_Kaiju_128x64/frame_44.png | Bin 0 -> 1217 bytes .../external/L1_Kaiju_128x64/frame_45.png | Bin 0 -> 1177 bytes .../external/L1_Kaiju_128x64/frame_46.png | Bin 0 -> 1300 bytes .../external/L1_Kaiju_128x64/frame_47.png | Bin 0 -> 1268 bytes .../external/L1_Kaiju_128x64/frame_5.png | Bin 0 -> 1318 bytes .../external/L1_Kaiju_128x64/frame_6.png | Bin 0 -> 1312 bytes .../external/L1_Kaiju_128x64/frame_7.png | Bin 0 -> 1301 bytes .../external/L1_Kaiju_128x64/frame_8.png | Bin 0 -> 1308 bytes .../external/L1_Kaiju_128x64/frame_9.png | Bin 0 -> 1336 bytes .../dolphin/external/L1_Kaiju_128x64/meta.txt | 50 ++++++++++++++++++ assets/dolphin/external/manifest.txt | 7 +++ 50 files changed, 57 insertions(+) create mode 100644 assets/dolphin/external/L1_Kaiju_128x64/frame_0.png create mode 100644 assets/dolphin/external/L1_Kaiju_128x64/frame_1.png create mode 100644 assets/dolphin/external/L1_Kaiju_128x64/frame_10.png create mode 100644 assets/dolphin/external/L1_Kaiju_128x64/frame_11.png create mode 100644 assets/dolphin/external/L1_Kaiju_128x64/frame_12.png create mode 100644 assets/dolphin/external/L1_Kaiju_128x64/frame_13.png create mode 100644 assets/dolphin/external/L1_Kaiju_128x64/frame_14.png create mode 100644 assets/dolphin/external/L1_Kaiju_128x64/frame_15.png create mode 100644 assets/dolphin/external/L1_Kaiju_128x64/frame_16.png create mode 100644 assets/dolphin/external/L1_Kaiju_128x64/frame_17.png create mode 100644 assets/dolphin/external/L1_Kaiju_128x64/frame_18.png create mode 100644 assets/dolphin/external/L1_Kaiju_128x64/frame_19.png create mode 100644 assets/dolphin/external/L1_Kaiju_128x64/frame_2.png create mode 100644 assets/dolphin/external/L1_Kaiju_128x64/frame_20.png create mode 100644 assets/dolphin/external/L1_Kaiju_128x64/frame_21.png create mode 100644 assets/dolphin/external/L1_Kaiju_128x64/frame_22.png create mode 100644 assets/dolphin/external/L1_Kaiju_128x64/frame_23.png create mode 100644 assets/dolphin/external/L1_Kaiju_128x64/frame_24.png create mode 100644 assets/dolphin/external/L1_Kaiju_128x64/frame_25.png create mode 100644 assets/dolphin/external/L1_Kaiju_128x64/frame_26.png create mode 100644 assets/dolphin/external/L1_Kaiju_128x64/frame_27.png create mode 100644 assets/dolphin/external/L1_Kaiju_128x64/frame_28.png create mode 100644 assets/dolphin/external/L1_Kaiju_128x64/frame_29.png create mode 100644 assets/dolphin/external/L1_Kaiju_128x64/frame_3.png create mode 100644 assets/dolphin/external/L1_Kaiju_128x64/frame_30.png create mode 100644 assets/dolphin/external/L1_Kaiju_128x64/frame_31.png create mode 100644 assets/dolphin/external/L1_Kaiju_128x64/frame_32.png create mode 100644 assets/dolphin/external/L1_Kaiju_128x64/frame_33.png create mode 100644 assets/dolphin/external/L1_Kaiju_128x64/frame_34.png create mode 100644 assets/dolphin/external/L1_Kaiju_128x64/frame_35.png create mode 100644 assets/dolphin/external/L1_Kaiju_128x64/frame_36.png create mode 100644 assets/dolphin/external/L1_Kaiju_128x64/frame_37.png create mode 100644 assets/dolphin/external/L1_Kaiju_128x64/frame_38.png create mode 100644 assets/dolphin/external/L1_Kaiju_128x64/frame_39.png create mode 100644 assets/dolphin/external/L1_Kaiju_128x64/frame_4.png create mode 100644 assets/dolphin/external/L1_Kaiju_128x64/frame_40.png create mode 100644 assets/dolphin/external/L1_Kaiju_128x64/frame_41.png create mode 100644 assets/dolphin/external/L1_Kaiju_128x64/frame_42.png create mode 100644 assets/dolphin/external/L1_Kaiju_128x64/frame_43.png create mode 100644 assets/dolphin/external/L1_Kaiju_128x64/frame_44.png create mode 100644 assets/dolphin/external/L1_Kaiju_128x64/frame_45.png create mode 100644 assets/dolphin/external/L1_Kaiju_128x64/frame_46.png create mode 100644 assets/dolphin/external/L1_Kaiju_128x64/frame_47.png create mode 100644 assets/dolphin/external/L1_Kaiju_128x64/frame_5.png create mode 100644 assets/dolphin/external/L1_Kaiju_128x64/frame_6.png create mode 100644 assets/dolphin/external/L1_Kaiju_128x64/frame_7.png create mode 100644 assets/dolphin/external/L1_Kaiju_128x64/frame_8.png create mode 100644 assets/dolphin/external/L1_Kaiju_128x64/frame_9.png create mode 100644 assets/dolphin/external/L1_Kaiju_128x64/meta.txt diff --git a/assets/dolphin/external/L1_Kaiju_128x64/frame_0.png b/assets/dolphin/external/L1_Kaiju_128x64/frame_0.png new file mode 100644 index 0000000000000000000000000000000000000000..8b8dc80bce09adfd26988d9021687d77b536961a GIT binary patch literal 1312 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Kaiju_128x64/frame_16.png b/assets/dolphin/external/L1_Kaiju_128x64/frame_16.png new file mode 100644 index 0000000000000000000000000000000000000000..fc7b76696c1140ed9f17af4fcccc840e8a07b32b GIT binary patch literal 1158 zcmV;11bO?3P)%JcTp? zv{oW`0;vKh&L>>}6$Q?wBEZL4B7lkl=TZ^i<17#h&}*D43Q5 ztaiR+Bf<%sjqyIQ2q4*b)e?|})&bP-S%W;Qqe4ch;l~r zASch7RY1pQh>!KmS2wa$jm*8CyVB4ljV zTCR;<3)+hu*ZR{iC1a9FJsa$RT05e!oa~=7bDXpYkby`2vk`!s{n-CK`vII?s~s!# zN|+J2rdy&M7ddwA3A|bWSxb;r1l;c)=SMEDk7)(ZK5+D`f==+f_po#3ca9f8L>8X` z*Z+sd!Q-*>DO=i?OuS=rqyR9dpIlLB*7otUlt^+msNcLoI7R@c&U9}>%X*Au3BZmW z_e3n?w^aaIr;lN@AB(NO8e1o#T}~`Ev}3SL0Nf^t7AgD9+T0D*ez=Wwf3ADtp(Qp z+QiNh_NnhiTiZH;vvurO;ns0&-QPcBc9(4i&2C^oo*Moou<%S}@*S*G$aOsX@m3Lp zz7$G5r1re1T|Mv4nCChpTpplt4CibJ)$XRE3q@!S;E4#)4wCi{qKULW8{WCnlp-Yw zv~GxSzhkz&_MU{d#vetXiaL=?z<-8~2@U+sixh^Vu!QxfjU#L0ND~Rf2w+)*v}i+_ zl~uD};{>i%JcCsP(ml8%fRt!p7qqs0IU5)&mIrU58$rgk-8lePl3-*cO3}8x*Iz}( zwWqg($7G`e*B_6AqMhnpR^Con6@Zl>{rlov!utRbjyH9(UR3(+ApDJ>S!b+h+7$oz Y1sh6G!Jt?Yg#Z8m07*qoM6N<$g7uaj0ssI2 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Kaiju_128x64/frame_17.png b/assets/dolphin/external/L1_Kaiju_128x64/frame_17.png new file mode 100644 index 0000000000000000000000000000000000000000..013e2008a2f51e46a24ea78341d0916c5c7483fb GIT binary patch literal 1161 zcmV;41a|w0P)3~9W2H_#2~le4-xjm4|1^Uy6C#DKO!8|2b2Ie4hi-_3IT`?33fpm z0f-I>c0eit4(cPF0LKc{#<2h|2c!-#!fge1uMf1AEV>uuSP?8Cz_*|kRD{r#lwp|* z__kPJc0ZMbPjq5{5WFO)CVZkpfH15is3?4*Lx9)ggJ=i;4c5^FK73V8h(g;}R5iDFi@o(rRI(>#7}kC4_@Oez;EwKYNVXIjXzI z^dJxkZJyCVXPMtN$khsA70o|U@fg4pTxnIf-I4jc#WVqU2C$mlQr+PREz`6=az6aC z8CJwi0IlRJy{~Dn1kXk*nATg^JS#v5zI_i3Hvyy#c{Xi1lxn8iVr5zqv_E@D&`TZL zInh=C9s}^8Xf_Y7R9g1#197X^D%YC&Xc#I*tZ+M7PqTa1poA|4w&=4skA@A<3WEoA zmM$oF3)&p~^$Mgv8ipLJ0-<^&u($+^)p(*-aBc~kfH%F8N2JgoWmlG1q=%9O z=n4I)0C2XRF}lX$0<6M}#1dr)u!E)}Ve$W?8%(74Jb07K3SZOy$Xe0~5KUFuP4bPdbQPn6_6ou(d}+LPt_TUz34qX& zb}TLZ(a452vsrF|&q1VT6Vbhv2?9B!f(ZZ4f8PUANLI}o5oDH-()_c8ELjRhXlqtr z#hW`?gGgGJMMzqq2-%5*J{^pJrxa=(Cu@tnwf5YSX39O#K3g^jw8EoBmCzGP&njqU z+S5?t^R(}k3<5_$G&G)uR>0vkj27ITNYH}ZZ3{sb!S6(h*0?J6O-jWS?04oJ^-}DV#zBd5c3*afpEIitOQok7Bb%3ZaG6j0v b!2kFKs(*8sivuK+00000NkvXXu0mjf#}Wg} literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Kaiju_128x64/frame_18.png b/assets/dolphin/external/L1_Kaiju_128x64/frame_18.png new file mode 100644 index 0000000000000000000000000000000000000000..795120e772fed8932017fd12c20a30b2fc3ab5be GIT binary patch literal 828 zcmV-C1H=4@P)m_ptak6Zugwsr>A z03Kq^ZIuyzfiytx-Bp}2!Y^=LfVcRRF-99>sDsJffil3C;!{pG0gM5n#GjmM0vH1z zaH0ty3=kdtClhV}H1SW%0^tTg8UQW&Poy${fF%p`-qUA_u$jQ@(Vu<)CzSyn zu#5Pul>t<^GO&~Qtpyoiw;wSxl>u(BqjAdshf@YHvSakO7GQu~f5@y<1~4!?f$Wn$ z9Ay9_yIwt11~9O5$tnXJ&Zh>*Iskl|_K{B-L0%S)2EB~@j)B+9&JhEk3}E1H^h*CX zn1e_p6O;iAP{FSLOtK10&4VQTWq>~*tN5*z0aWg`eA&cr?V}Qq^~bTY)_;oDEm+p`rp5p-u#EEGEc=2g z0S};~e0r*Q?E)ku@gq8fGQdrySW@J#ts(bWX<38`sstF(W&ox7p8@v_u)_e5i+Z|z zKIgE^97YbGQb0@VoAbskso!Yu(#ISzcG6WpjacHn1DYlI&$5tLbcO)Rc{UmfQf=}%1LLA8MwSW~oR;s-Gx$^Z(IU~AN0 z)p+_jZdv4|V$}sGXo6Vo9l*WMjr=2VGXP?`dy9014|fI7m9QvR3qh0s?!eK^L({aD z{;k{Mwi`jX0i=V^m_fMNzWHboI>HRF_X$L(k8k15A4u!b_VCN&h8_e)Wq^q66J>%h z17u6_)-=wsjvubpTc3O4Vt{PoQlyhN^YQI*SxrE{)6)np=K`*ND0G8(?H^*d6W#?N z;x!tN7Mlb9CQ(tHzz$uA1?VoI@aNABphY-(?O!<|3;YKcOHj7tfv;Tv0000FYewfNbI zDWw#ETYlcI0C*P&z&Q|rb07fcKmg8x0GtB>I0pi74g}yF2*5cIfO8-K=Rg3?fdHHX z0XXOP?Fx7!#@Ky+_wft}U@>)va~+(^?-Bw8u!yG}nah{LSt$kxKpCwFF0Wtd3J`!I zF~%jGRJwxp{V@wrVpZptq?Fh}?n3qSw_DWz^BTZsM;AOMyXQ?HYN00h(`I3@Zu#0dd72LcdcH5EVr&Vc|1 zIn4!Toe2UEviqRMF0dUepd9s|<^Z$60S2+=0B!}4mwF`;UFO*n9bjf0;4%f&3ptYf zo*UWPbkMy6gut)+=kn!GE4XujUi6nyF1f}6ZXF;~N55p<-%=WIFMv$$)LZlX(>l9I z0n&!4k6-t8{j5v}O$(qkx^Ic0{uIuQ4)7GSDS(PzD#y3R0v?25Rsb1kC${o``Pixu z%nP73sB5_ZSN;Y!cv45R0^kO5Ww{z^^T(nam=!>Y#098p^6%w!R%QSpgb?$846&r@ zn%-ybCG&o%o1DOCKYmRE>rt>3sOcb=+spDdiD0CTUk=clFW>OSd$isUC3dQ#TPftk zb{VM`xe(N96zaLtY31{}^CdfhnHy#VQa%FU?4NRn)5Xv`&c%K62DL#%{q08njR8X7 z?SI$z*Jwfsp_lqB?*e9;DWJ}8T%&Mh-h$fH=`95H)&<_o`6FYpk-CaDgCr4h+5mF2 zvrZ9h3&|msrws1~mJv_ce{TTUdS08_y{N{UZNwd}z-?;HzV_61u+;u{3WDqW+gM$H zmGc0j_P=V1Nlo$UGNaMc+97P+b2l0;pnf3aNwjOtDtgy&=lq`B`(%faieveSpix0c vr=qj(J9^jXwlmOQYjA7-TT@P>=R##Ln0S>~Ba{48}A=q`7@^@B%6tCz) zdLD2uiq6Co0VKt~@0+f0Bq{nb!FC$RFab2>X9Ja~wL6^{yS?L34se?Ct7S+s`&ZQ% zt1z7dXvk)^CCgJl)?E5HR92TgGRAJ}f1UvX?67`oyw;k!iCOcK6wgtW>{f@iR1dJK z^2aSu`h5mG#q+ghlfaUNkP<-}0s2H%mw&5VsJ8hM;i^tOp2x{R8Ubi5{5rfa$DdKl zN4x-)#g)A}FGvjmKT-MFA%@E5KS#%)eMUn<*(o3kE3HBDV(9pY7toYX`=cHpi%1~? z@N@6ZBTR*5_V3l}vOFakLbT`6Py!J^Q@G@U_(XVH3+TpkSs6;pP&#@FkoJrAv*c(Y zO7ke*Tp$7I`Pe^(lm4`e-TVky>p6nsZCuomG`Eq;JOx0(hCY_FPhw zcY5&JNbS^cludwajFMmn0r+^5QZx%is*YOBr_VJKX#0449pDQs%PWGc!f54Bd*+1?578RT))0Wk zZeC-^sx?$1T8NSq7doQVl>FUyAjSzFhfe?jSw4+NO8_$D;_a zJ9dwPpYyfNM{^-d`4+UQqlIG%K~AtYi3$I zJxNr7BYk^+P3e(@@Q6CV0US&N8V)RsC|@E$Z);%-0npp5uY#7ZNBVi_ZM;jd<8lB? z@uSuvjWLoyRlthHo@2fg*d6Sw~C@i1m-=BQt$HxE=t#z>xZ!W|>v+ zO0Oq+sScL_(U?BF*hv2qHa4RC)$>KX#&8H|?fLa6q+SQlusqs#S8|CSdn-^ecFF$<+#Q!Iv2$$|V;}Lskt?kK yLdN#b4ZO4o3>$ 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Kaiju_128x64/frame_22.png b/assets/dolphin/external/L1_Kaiju_128x64/frame_22.png new file mode 100644 index 0000000000000000000000000000000000000000..d23ee492b07b731fde5b3b7f4847ea499e4e23c1 GIT binary patch literal 1685 zcmV;G25R|&qnJ%ymHr^`B z9Ai8qf#>->ea@L#2E$GM7H{bpTGkzVf59mIV4P6?F8nuuCcd-s#~9Bdz_`9^fWlPbTZuX`k-CDV;gzpAY?A z^Z?wNwUIK6%twzBe+?+UoZkA`17Dk&>&ZjULXuOXDW22wJWu6NBq7k_7h!`+unZ%5 z$oxPlUF{sab(TC5{fzNja#;M|*&9gV9bQ%GmD@w-X=^06Y2A2@egu{wTYJ#LfyQ4` zJf&yhU2(&lfyF4RbW3^s8Re-RfcCp|$aGFbah6d;l~3XAQQ~bqkMM_i04fw0u%h(n z;YB@(BwyyhEo9wphMwomx_EVBG8KfnFZ%Hk7Je(K3tZ1E_h%U^A2s@#!Q22hN-8e} zKRf;`x|fy*Sj?oKC4r5VcM;$huZmU2UseH{KEMw5t zMfP$Mk#Ho<$%e6Z72pXt#@Bkixg?9_xkh0PE1{;Y&Av zjVKQwLAvGiJ1Pe^7Bmp_t*|R4vRQ1c0;ElJJV0inUd)%|+vOE99@I)qDKxK|1;hhL zo1j8c!A>Y&lE*q6Eqrv*HK@$`>KQ?D0LqxpoHuC-8vSV?|y_AHYhsxHs^Gr*63G0d5%1 z7`>!SN=PI7ubxx;&jfntB#<{f0F_}k_&&3H_Cl_O_ab1y6NECA=2i~!w(`4(%3i&> zsPlPegtVS%nSWJ4zPXd}SdwE0w6lqx8RBxhGHxacY7mI{jZ^@-*0-ST9VtVX)@u>5 z#xZ(Ljj9LWG9Yo<2&9S!i7MUqZswhAxa;1e7+LX1-7uh4MOKrnB44Qtqyknme=k3% zHgR_0c2bG(0#<`<8AD{B5D(xXd*J9!+Koy=Ym8~ha_9C--$l>xPZ`+hz)8$#BB%*- zLMfkCPTs0!G(bFn6#_cXJ!H$JlSZKNLzl}OSvrs$KoTOdp0|&+ma>&gM0s%+7`VCq?~|`o~Nwv@BqmH5Vwi*U2-j0wLzMyy?mnEREwZPJN)MXP*_UcQU08S zX84w&?lyvomxTOV!Sq%m6+!7C+oY15XahoAqU_qrS-j}Ns@tNsn!5Q8xTipd04ito zRz|84Y5X*_J%FU}j*G7*b%+~`pmDpgrJbMleY+2!HEuZb3Qko4Ram^OMo^XqQZ5eY z{}+%+in8=}RRL%qqe2I1@EUe{xOb;%;k9TZO`hA0 zpk3uz)e|6<&BM49*56$PR1>yrZsBF!{CBGi)7~G@gf?4MaDH9GMCCSKE@!?A@LIbI z89{g5xCl*&x7SavqOyLMtGA4x6#~Z4ZzsCW_6`Tdzk2h~)yS#JCL97Z@c5#A$LB|( z&5wi-I`Z$e`TV<6i7XEtoJxhyTCbua>!M-i03C4UN|wFzkWnGv z1m9|2RPh!(KbsF&3(~F5Djoe!;UrEPP6b>(SPoz*{zeja3y2qAJ9+UnZ3FNWf9p-% fcS9$RcX<5*G3~Up?wLwo00000NkvXXu0mjfNd6$@ literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Kaiju_128x64/frame_23.png b/assets/dolphin/external/L1_Kaiju_128x64/frame_23.png new file mode 100644 index 0000000000000000000000000000000000000000..9d368900c94f1a6dd069d22c2a5f79cc5ce0d497 GIT binary patch literal 1680 zcmV;B251O}g^0MJHLB< z3T^4WCj4FWt?PVoT;n$fXw$y$dyC~q*A)f*J%``>(H`JQbouva{v!LPR(@UA$4m17 zuX!_am6*uP?_N*Rw}hZs*Gl@@eM#UN|6_c602C9ug>X{&qR3c!R{7~w61=6n6K}Ky zdiT*DpmV5ErIYAO{789#Hdfn5RtYul6A}IxV_f%r-<3n5ge5m5eeSqhzOHqs-o`WZ zC828P;LWr6@y2O;*XBeO0OkQWV{}sy3)40ewH}GKHrBE@iT?;660N8ZxMD?UMU}udAv*Q6eN?=+2uEZ}wLkeA)wX)k+WnBB|1lIlGlKIG{OzS8iFGqt=HKem8-v z2*a9N6>enz)v5sYGl3G(itwyzVJCKY9Ewh*(m9{^T);vmagms*RuNs{pDOnxFBnC1 zEXp|Ekp(J5v>H5$&LN_J^gdqrlkz4H2;N>9H=}_jd0J+l#cAPp(Mg&!@RDl}uepeI z9m9U$Gw*wOLIzI}{>oec8KSB@zE=~kZt_(8MnVfA-JDCKL)VR--6LCUhMw)3&?`pTo0lZ5|!V->*Q{cp-g&RmfHW zokS_SS9(5@!XiA3#h0g0$2x(P5)etZI=7P4cl5}b$3e%e(4cY_vq_8svOE2;nlyb40%do_<0BZ2e*GT@~U3SC~SRe;s# z>0(E}0@4S_)YO%A=X(j2O;_C|r)%w%^Zk^vkK;84=H~(E3!w1-ZU<~;w*G}u~^zxDD8UT_XsZqt(e`osW9A1ky(&Rba2tur- zb>p4@w-eYEKs8~DDi?ikJqzHUt=vJficE*nc06>mdn z-S_?VKZ8=#9Y|K475Y!>`%~;xoC^Nw1fmy~1b-rlJB6K(e-Y=}Rsc`%Pi>Ao4HaP` ayv-jxa^1*R%sztv00001vA}yFDaTPfG_w8 zsoK{V;~EKE*Vpvd>y=rS#^fXMdcD5!x904W?Ue2(`G07Xn(z!B!5Y7=>vlu^Cfw=( z+USwNuIu_SZW((1{Lc|EyyqsNPmS#QX&7mo`I0pCD}pso($wQ!ojbaBo*DkpOX4*S z_5j*I-dIFG;*ZepWCN@JIGJ4#p57kF3@Yaw$pBWwdt>ui;ja~z(8_o9?$X<_?|P5r z{>t~?^!Qr)|Ago%86xu|Zr6s{8i2!HRX)1#CLbykbF*3F`eKC}Hq zWcWG0a;#6s2%ho14Pm6gh(8ixk`9tqGw`yLH`j7151#H_&@zA)YPaz<|Be$#IT@Jr z!`j>AYh@r!AEEv3)+ogIQsBO`7e*9p4i<4z}NX8;K+%Dg#uX8?;<7uo2y z)}IEw2Us})GXqHe^652xQcjN+!x1Ww@?{*s(kJ7Hty2KPP_JcX052%JKoz=Fjqn#*zm79F%9 z&6YP0lpbK#Eh&($$$CDsfm>}*gsgL)MAVb4GA#R=>a^iTQzUivupfnEoSxuQ;tdZ$!KQ)1hP+v=E%8!N(MljYKP;Y zpxpyRV>;3!8nd7H?Z&UjY|YO&hTLn^sXkFU)=y8NYzaV=xWfrLWx!gC(9yc-hB;a% z=U3(atQcvbDyT*SyDmQsk#4Bo-CpB^Fj~z)OF#zN_^1PQo#wYXT{#1wi?aDLGv%|?MG~aV!^c;ipyKBx!6lO-#oHGew7p1GN5x#MBz#v zy#|#F$ucWYdVp2xL)KfVT5nLawhBvDFB4@^y#TZ1+LcKt_W%r7&6>;#NEz8B%WCsI zhnezwFOLo*6Qc4{dVnW>F#ZkDLOkV3YtWm7?>rPL0-nZ%qcC zY+3%49)JbSGkXd^qcnSLMXq+O9%jXV=l-Il@#xsc-IDO;hZD=KHGUdtAe#||91sPdU3Fy+T8U!g{nPBRoQV!p9Vf0)< zra_jXx|}de05fB)dLZwZwhNIHSYuo60e0NZtx&yoWypx$99DT{b<1Reb`KyMA^BM+ ztxEnoNfvJ1j?(??0J) zbd4x7SL=~J;p9aS3sy<2g4s~JH-TLP>}tNO+pVYs==WY`%AS`i`Z3b~DW?v5fJkub z3KYSvFF-Qo>I9yene;xjO28O?bXISs^m@r>B?pr~!gGbH6YO#v^Bw>#0TEG6-1UJbyC>a5Z4zE$ktwBuy4<=a3>*fRVbP262LdFRjA*R}(! k4F9CdkSF8hZK34#AGJsPjXYbvdH?_b07*qoM6N<$f=y#C>i_@% literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Kaiju_128x64/frame_25.png b/assets/dolphin/external/L1_Kaiju_128x64/frame_25.png new file mode 100644 index 0000000000000000000000000000000000000000..b40333654f83c6ccda04cbb4020621e79e7dfbdb GIT binary patch literal 1658 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 KMNUMnLSTZld>|zN literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Kaiju_128x64/frame_27.png b/assets/dolphin/external/L1_Kaiju_128x64/frame_27.png new file mode 100644 index 0000000000000000000000000000000000000000..0291dfb5830a140e9604c82b66e0fead0a9d8577 GIT binary patch literal 1741 zcmV;;1~U1HP)$@V{Y{3g8ONZp?cAc^=h3_`0oJ ze!%-ZVw~TKw-`Xwl05_A9%?;H*GT-SvNc2ajd<-X!rt{;a=@6p$Wr2wut zjRt*QTC+b(a%sq(UH2WqpTDkks0QGNOKD~3W%z3b5S78I0lMj{g6`fo==viO!1FA6 zph*5w2$y(kQH0;8*w?LSf_%^Ho6Anmfz~_E^Q?Pch`dWec3~6A>V@c}k}Xlt*Sz-7m!}qXbI`O%SU1{kduc90=uq0xGo! z3a^d_Qbg0iR0OC1b?Dz{bQ|pmu$Da|kR1ik+&9IDja_^7`5KRG=cWxTA zpvfwqM}gB30l}|JBWoz_-vsV^(re$#2t^G6(FL528Wn!1u4k{yocU`nv|e3wHj_OJ zy7}oGDz&(i{3y6?sz!517JAQQx_~I8a9&w^x+u)@LwJ3K@5UWni0lHQ{Hh*>NF662 z$=414S^P@@*85t${Hpiqfg{L?fTa5q3xyKh&NNKoQ<+U@_oQf*;Z;#S%3vT7z)Atp z`;y|PLL@ctPKztil+ZQ646=p+sR1M*BI|j9Hm!Jxe|5yopF^~}NJOJJvQEbsZX?*P0`A}WW(N4xLQ zl#0Ks(^Q>6Tl`%1$^FM`aJMz0i7(Fs83)AsD4;$&wkM@bn|`$QE&0y3ypG?y9nTXmH<94 z=%Ea!Uj@3Zp{aWB+5E$hhytpIpBc1d=I&k{6E#+CsN_K!{8S9=RPA8S0iqRcMp$I8 zt;v(+Kb1Zvr+-;!#@tE)Yw6zE9jtJg-a$?ecCzRwe|lt05l#UXQBK?%tYj5pjW#V< zxj1sIIvnDdGQh5J@Ac&ru!(r+wt>}+b`L%qaZd5&&sH3YI>%f8$!~Z@&h4@pc|Fhb_kRsbad#kjbyn#o=l*D%G@J_l>jKb(t*=Hi_y%Q5I63o2oGaufVVp3% j_9=sqPshn=A?5W4au)}*O&X|=00000NkvXXu0mjf^msif literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Kaiju_128x64/frame_28.png b/assets/dolphin/external/L1_Kaiju_128x64/frame_28.png new file mode 100644 index 0000000000000000000000000000000000000000..54a889d8151209f012e5b42e1da564793606c3a7 GIT binary patch literal 1686 zcmV;H25I?;P)wUdPS!FH+Q14MrQ7gCtMTXN!pCPM8V$s7-M{BD=lCN>$uj7JR8QwhAmKV@?)%GEc;faQT`gqR=-n+G+cDGI( z3M@-MGFM*6pX5y&y8@1t`@^-gE4%l|e&zGY-}g0|asVxUGEPMFbY)z#CUDFFc4IsF zT8-D%m9jtL0gzFRf$ZHwU*p3%mAFn6Rw&t>dM#|M#(VaS`<>6^&xnsaox8in1dc;N z$jy4`ROoAbNI|WH8G*)=RvoP9c|4<3qD4!SvzMn&MG3p{$W%r75&GWEugX3TBDAde zQ64SY_{_^wn+HI~*vjA0kDkT~GRpTt3mKBG9FyVc9Z`Zs#AC$_BkVW6fN&^d}j{e z!5xD3BD0rD1yt$-NT8dS%xFm(!voCqGI+ArKsk1z)Cru-V#beP!JBjKK&b*|rjOTrCpW^(0e%at{q1Bff6jV?auwjg zDGxAdVa{

Fc87HT~&L9^=BZz|H0U{wlGOFDf9!RT5 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Kaiju_128x64/frame_3.png b/assets/dolphin/external/L1_Kaiju_128x64/frame_3.png new file mode 100644 index 0000000000000000000000000000000000000000..bedf366c6ccf988ca20807c9062e7e7f7b70d9b9 GIT binary patch literal 1305 zcmV+!1?KvRP)td%uS!`N3?sl70Tq=Y_f8vQs0;XV zED)XBPReiX#sDdJNwAypTe}2E!%Bi3mEYPWz|Z5!90#}7NcLlqyRyF$(j0f5e<};0 zCiXnf))PJj@VgcxQ^qg>IOU%TJa%s#gKO{I4ERen`2OlI;e-hACFOGq+UGdanpyhd zh!MrGLQwU>SqPTdX_8FqT&Rq|IwonSQ)8_nr<(vPcE43V*Un>>xuDj1kF+*XGW7m2 z5h_SfY5_)6{wn0bMdEf^hqv5|WpkfN@}%+Map534OM)^2-~o+*G)b*bD!9^Q!o?jG z4_K5>jj4(RWdwjhQ>Dxf&9qw0b0&(G7Mh#)i249W;)uM`O5n^)per5j2#Xbv$inqQ znRfg+C)4VBBFd}C2tIKf?8()m@+)>*2NgG1+?y5~M8@T^0I-u&^ZqFCqGD1=*@Nci zB^QBX5NUkFVgRXF(ywdzZ*?BO0@&PBB;b@yt)UiANG?MfqX#AeSo~X5281<_UjaBa zOagcf=j&Rfk?Myl2ub%vQ4#}S3*gPAx%+kjr4+7MBl&4g9(6sFt$0xSXc&CO*Q4@w z0ab~M#6q585J#rU&2b+MQvj7gnRH8V2Gi7NC2$mmJQ}u(tPy0S0zV50;42>ED&@EK zLthE2CMHD!jtD#M{Ekl)f>wYdffZW12>X#_Xl*oXO@Q6w2`UA&KvMqfj3MO$Mk(lo zN+S;Jv|UVtw!h?qVOR2=~qUvR(D`zla#?~1=3FjjX;$@uZ{~Oa}A+I^}UTn`J627{ZTOhg(`EX z`O_@4V2@LRXV+`W*W8i?TM=NCPpmX@aM3L!kTQm<{-N9P&UKqo@TyX%7ciLX?>S_M zEY%7;j(!ogX#iY9~!cz8q4sMj3sPQ2dGy5OmJhgzk@Z?a zfZ6HVXhc&v>`pjHY3}kukOyB=JVewIaLT0oz2*WUWEf6>(@eStmeMb7-&$@bOJ^Vv z3?V>N=_^a3Goc;)Sn~U-R50{-xCZY|_g2R&@+=j*S&+^LIw<8@&rmWXa1)?=1ibFXJXvWloWgxRI<@+<&e+sR~C zVdb1g^j)3>RPD0r-G4!&{Fg&#D&LE$O5>Ms9}oX=Z1u~b-raxAe;)q;>SwfYfJwge P00000NkvXXu0mjfKQdP` literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Kaiju_128x64/frame_30.png b/assets/dolphin/external/L1_Kaiju_128x64/frame_30.png new file mode 100644 index 0000000000000000000000000000000000000000..0731760dffbaa07d4e1f50c1fd65bd0eb1f6ef19 GIT binary patch literal 1677 zcmV;826Fj{P)Pk0p{ai`))_7(%Z%c13qA~ z?!K<;z5d{Rf4{x&Tl`!542k!Bulv6L%Aug2Yxj7;$MZEuWOG|s-&N3I`;Rtxx1%Lk z&hL^%|Guu=df)f`dfoT^Wyclk$>O;#+4(CS|F1!^rxjqc0O|C_N89<@m1A~)^m+nN z2Dcc2zJ^TcHO6Rj)9*9aM^c;`V7%9i!oROvPYVK+J|2d(Y7a|4UT@Ge>aqEUE5;T2 z9~oc(O}&&6Bo?s7NT1JY4Uf$~Qbo(7NUI67`?A+4R@5mSGEg-Z4JZj7n?JnrjyMVF z{M`&-9X)pz#Iw}PS%t!5^N%!fg#n@pR2ZNFZ_v#sx)V~KAK<&6zo++EHvdWi(H-j9 z!9RroqUdIZNPtQ~Ky`C5KxT(>=TW2Z0_7wI(CVHD=njnPjt+Rqemoe=8h{SS)Bx6^ zD2<^RATtKxd`Sns<7VDqyvj;F9{pHGAV zvhXrMrWhOJ#%n~lWB^pbH`C=5$7_HyKqLip1X(lz#|7? z7*Bm5t%2)mjX`52dyMsbs?PTy!T<g*NrmcQKtaJ zt{*Zad0rj8vbt-EEUZ%q{|>?c(vBKJrLpuoex*uBjLXx9lydf&f);WthOC<_u;w zwsd^>Ko31j0xZQURBo|H#9%XqLBYddU9vNHu4_(3JMx@F-&vaN*!dJc!ReZgkH;XM zDmpt>1H$=SzfLFcTpL+;CnHfv^}rKR(wbNsQz?)T&L8Ne{#}T!iKo`D-vwybF))iP z18^O7!K*{%7`p@StNb!3ijEY3+^s8j|4Lbi^!oMVQ0?lO&{8n76aEFN_5{POwDgq% zRtSrplV^>ii0o)VhLXwOBZ2Bdx@&-$GW<0JXy2cjAGyO2rw?tO6Knvf0jw0@{l3yG z=tf32t8;l$p$Mq~IK1a>$`CwF@MEB6EKAnzqu;E3UDXY-Hs~oJYjA|4I+{IQyAxGs zjJYanhSUJjB2Sw`l0VZ0y}P*_#JUjkSo2o`3VI6gwv}f^AT`>|fY#1Gt@C$c5PZf2 z)^&!+X<;}OT>v5H@}A$L^NsiPa}=`ngfM_-A<|PhdL8rdqtogXz;)?3vaZnC3rG#n zg=z{w$+u1zk-4(=jLM0~f_DPF5r`-pxm-GTCjV=Cqc_6JX9Y8q`v6*g)9%yG`Vu@H zIdm<-^{G71kkHMlQWuu~%~56msfnY3=T%Rms5=Ez9Fj=`QAFmKd@)N2%WD8DuU3%N z)iTa$$4CvH4W+HK<=+FKjJ6{MbR(j#=e|ggN@=*|-vb~jbE#ICJqxG=LbN`idhTF~ ztc|(8Wd@Lnz^db`6?|lT=P|Mq-Xf2KZzkxo09xQ7nkcIpL5PP?(RI%5_kQm^_wzbo zjDz+8q~fm120I!--IU(dw30R)b-X0ETmxwNc~?&C9y6OSTQ~7}yz=OLJT1)RT-To; zhGdi+f~?}qv&bEdAWnrDtSbB@r_#@@F?5`Ezxl%uj{~wh&l_Cyy>$wRfX|h2rdF(0 z0|U)UvtrRa1w^CFgRY&EnNu25rSV1Pwuv5187CM(s~0L zESuji*b7p0MxEZ-_wUBaz{!B?2de=r$L}cOcA?T)Tp=se2=ZX&_|uD^Q;=~cf^B{Q X1UzU}jMrLx00000NkvXXu0mjfn1~uQ literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Kaiju_128x64/frame_31.png b/assets/dolphin/external/L1_Kaiju_128x64/frame_31.png new file mode 100644 index 0000000000000000000000000000000000000000..898efdc4b8fe693c99eb9613e82bb5762b1d1645 GIT binary patch literal 1639 zcmV-t2AKJYP)OFJ=4bXJY&xJ?|e>!-lyPvB>sHvdZy8o=pu-&y$T)tzh#s*&p5yYF#~-|i5fwP5th5uK;OYkVpP zYOG~w{+VFsE0k%WHMxQeU9e>V=H`=#DuT_pjmc_=bFwHwmnW+=3Q1Z}6s|PJl)NiH2l+em#-{@b4L9=pb`XIDy4uJGysIFFVH za0`g>rKCx72L3E62MS3(ME9Bu+RqZ*R0PnNMiFr+kMS+ul*Sv9SdZ>KM**e#$r^t# zmpDWVcj~?N4Q?*wC5k*!i{>?cIipmYhZP{K0R7H_U zc}V5!j9?WGCeesgFS{%MKE^mx1)w1pP4rNnRwjKLZ;-1b0xaY4&w6HoCOZ<{><;cs zqN<=df=70)bHoN;D{Syk?h&3FPfJLu>s*hF8G3Kn!C8FAz+Warp*fGFK(53fPi*b`BpGl~78!dq2=i|2qP#i5V}{oPY~2EocYCK+?i3IT6`1xanA&Z^_>V zRyTrBctbYoFtjS!RUvPS5TP6(I#FWGQBlHG;tgT_Hh+%K2_{mFzr(I6R3Qse_3+a5 zCm_@ZSOYR0K&`B@&flBoptZJ+Xz{k)KSogwusZ@W&pD6Eo#-*J#QTlLj2z{k=)@gp z3)k9ZQngVbM}@OgI^7!`W{jd7fcO5rXa23^3^i~?*Y5-(Ilw16k-}?mjRtq3s8e_p zZaG1fQ+6F;S(H`*Xj|FJ@~f0yrEp0ePY1Q4nTFt6AHY*BNIN}4vjT7E4@3~@T=HkF zRRCwq$94AKy}RvX+1Kf8-s7_*2k^qm89WMlRK;j`BLy_@PiyKJ{=`{*;o!`t` zx!s~HuYYSuj6izHC0)nxC59)G!tEc1AUmw9R*o(n>1A}~0vbIoi&QB{^Rsdy^S7Nq ztF^n}ae_{-z7b8L=R1E-fXs=&a)Q(mFou2%RC%ulXsE4_FAMsD2z_VJ0r&{ez!M$q z9v_b)`n~{7rZT0g*MYR-$%+P@y@Bu%fC`q`VVPZ;-PzG>9SP@BRiO0?s#?XGE|2}c zff=0Y;Gy%QQ4_5a=)KA#N^De(;rnmnvI0xSzDt63Cc%o_6Ip}=&-v`Jrp+8IB z-hm8l{%+$0U(Y3U{({Q;LY;FKIu5$6B^002ovPDHLkV1lXu4UYf- literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Kaiju_128x64/frame_32.png b/assets/dolphin/external/L1_Kaiju_128x64/frame_32.png new file mode 100644 index 0000000000000000000000000000000000000000..39f5db8a0131b28046b4450ce84eda9d9f519ba9 GIT binary patch literal 1618 zcmV-Y2CeytP)d$G==M{J<5M|M zV+}*|tM~rxqHQ%gJMSy__qI3(&}N~8Sv&e(o7u=S6x0bg?-M}yQ&|x?mq&n|@I(QW zo<&oOB=j2XD((`0s~tl3O}%AymjsygLy?)Ot09daNi?;)N9PgU4I7&ckZ4H8=l3l+0RNmph7QtqhZD?rjQ1HQ(B4Z9u{;8B zhP;hOan<1bd+(yMs(?5c)hjn%CIZZ4S`q<45-mH zx*2|DIRF&~Wf*OP&H`&zc@-9k9@X&h=edoP>+rTEIe^z_)&R7yHp_%H{ua%dEL^Ug zkP^a540ETN%MB9897S*}YhI{<*cbXBU8mTr|-`dCoHF)3`ydk_a%2$FK2j2Ab?hbh9&f zWfD~d%@I7ZbDbkLc+bKH59J==x$(4wqh8dj2yEXhUAqvfTBn47u$?(%? zXtHoeogo_mRydER4lwXW1VQ%OXkSgyS;HgI$>QNP$Y{GG3am_G->C4jU4Yg{5`H|B z_WRzV&8%kNsVVSU!Ka0IB#Ec`=)M120%jdEUZ^<%7hW2W1;jwoz-MwIvSo16u{ht7 z-v*+}t%f&bqYguNCDtzFwg?f*0iqox#u~i4hgG;Cte@u3@mfio?&GD4<^g=&UzPMA zOPREF{RBkn0Iz6A&X^|`T5*ShI>PwA170Vn!fms^wW0_>4UHIH>OXUWRfQb1Eys<< zj4b7^Xvbfg2sBwW0{%IdtJ?1t)%ps+CklOFL6QWvD2oAvjhCh}O|7X=ZRkeISBJ<&wWEsRT-4 z{j@T@vu%YFX!KS(n^eZPkj?>UL5CK3w04rP+8_|zxUNdzI;vcrJV`i5ItTFV`>C^3 z%>u5>Y)(;S9DV}Fb#zOGu_OoZV8_yMdimi8NLF#HVj^VdEIAFV7*GYg>mlWzHO!|Z zm1T7R?T}|DZpa6s&Xz@#PC5sW4DLM(km#Y1q7U+Hg)H8_G#5JM1f_oupfUf1P-ww( zD$3V>bfm#RqNn& zfLrj&CV*O7bp5NsO-hw?=~+&zq5y&y&)|7A&vJ;U5s{gI(A5e+7NW(WNPBumlfB3U z)MP+tm~{Ya;8Oh`!KdtxJ>bT*qNyh_+@Y(tWjutr=Gob-HnR_x?v=7>(CBelq@9A) zzeV7*^RE% z{Il0_JZA zxyBgJaNv3VcE7G`JkRsfLR{DNtFQK)0yi&QpOTB4s{-8GU0AXHof%9W(irq%aD6#& z#>K6}Ddl+{))4!2!zv@7*4Jp?vTh3lWXD>JugVCl>%l}1 zek3DU#Q;!tuHD7}E4>pcey+7UBuLJ(iUH==a}B5yZQY}D0E^*K8<%L=vR-zt90kLF zuV4VnzFw09aP&s?(Y(NG3|Q%v;RViL!2lNdX1^^A5HTDsv7zSX8BDDX*FtFiT|*p9+&)i^ruIKvRyEKX8YFJ=Q~mQk>Psc4MXPt?Rbr z0F>}lc)(sC*(B`W#b91wMyk?16%Jub^<9<&a2w_XqUek|Ob@smz+zMydsSgq2%ht` zpwtH{9VdNN6mJ1{<-u!GimsOH)SQFD9q|$l5&Ft1 zi!(Ug&3$@qrJe0zyP#t7AW|6;OcebSgS)6;NN#^VIxqVAR92oA!*w zIl`N0+KNyP5FJP@j8T=`RN-3>K7&ukB3wae$3d(%$ejje3FR0+*T75#kgLQ|iANNb zhjtPxP3&T)k{m$G{-fWA{i+X5Q}}5HV!^f?0Km2cTZ)L^AYl zz~U8rNwq76XY~-t0nBv(2^C7CHlOuk>$!1{E|8_w=?Lz=Jdc zc<#>l-rW&zqbva1jx|@#BcVM9n8vc=@K4AGpmD#!2udq~-SK~}4M8a^szmQN%!Y3W zAD=O5ejWgRb{GQ2uF^8~EGx^(P?=@`>cBG;pP}9r^G0hNZ$at102~T-GC^f1SRu

A_%cY^nB~nLryOmg z7U49U=22AfNPGVgMYdEvpNqd3VkwD66E>@HMdrqGRj_~tkC#`hpViMCE4~fXHi0L# z3a~0XNUxTuz7?`aZ#gBItR?g;=G@#`sSq$HmNM?n>mcMM`hxI|h z90DXkpg`O+O%QE<>+=aIDWdv$=eL6Mba;5xM&MzVa0q}4W#-VvMm@KJ+O6N$JPosf z6gHwpK&@v%%ij&mVpRn%IzGdiTumSos#3}17`{Inmw}b4D(=oF*WW34*t9ZvH2?nq zUSJ_9%kRj3qlg+%Ch7I|tzgFMDLw@*;o$_3=D;|mx+tOBaJ<0smE{1^!S6`ob|ETv tSDy1v9l$*Uf_Ctym1|E$rmB}&>kp8-&f}bY-Pr&D002ovPDHLkV1goI?@a&z literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Kaiju_128x64/frame_34.png b/assets/dolphin/external/L1_Kaiju_128x64/frame_34.png new file mode 100644 index 0000000000000000000000000000000000000000..969b91193d409a832d6d33a097670ca00399d371 GIT binary patch literal 1591 zcmV-72FUq|P)N{wWzs0r^zh-ZRrN*TD7s&hj5Nyx;{jwADBgsG+(0nsZ^C+HrpYADRK4=SlBA&5=9|AUP4f*WGgRceWk! z=kxi_vKwg(_Be<1yk{IK2go?7hh2U`4j|=n5IaV@{lGJl z{GF{;zDK{;*aT0^kBmKr|AevHNzii7(Vf=adgSBzt;RJufQM2}!W*8nXDCC^x$iW- zM?Rh3YTOUg$~NOh$C3<>@88A%5j~d-&_&y)zf+uaNRuPyo?V}MnnqD`8MaTa)XNCeWZ+{pkIz3_WZ zzjg@C&#+l*4j_4DCQ({V<^)#yNz{z5S2#b_No%=hO%7mD zLfiHDbyXI8E3_mGUyI}l9eSD>KvF_7JiSK8sW85U9cVo6awtzS2G4t(z%%A*9kH&d z5Wp=a-Sr^Is^m{%0Bv!t*v$ZRy!S7KMi$z#>#6dQtT=<+PUK+#cz$ONK=mt00xeWM zr(VqiI>gW4&H!V4*EPxjFqYI-=Kz40Wq~!}D=+0HsL7~~*If%IA;JLkS`?8m`mz|G z#&qr;H%=seRfYlX6w=KqnH*pYdx^IS?+_p*G!|;NX5$`Y4CT$pvxw;0p`#@Spb$fV zXF`?`;YD;o$5+F_B0cB`0Up_&@pl|CEn{Aj16UrkB!gPN z-AAz%x^=pl0VuZoMo=ZANY8r{#!e?V#Q>o?K(r$_84Mgsq=k-9cLrU>CO{+)_!SJW zDqdLbON*vCk#(VW*99!HuFe509W_`d$5rX@81z`Wau6!-gyE@d%X2@T0jQC@6XN=L z$KvSs#KKzj{#7|Z#U!|bMTH}E^s(Z$(+`KzQ-H_w$A}+0HSDPNRp>yAZB=r$mP1(% z;7P_{?9M{WX(s@jU;%6Se2fAHupWK5XQ-hn4R?Dk(B&B3@LiGvc+ipbtYn=qL{x@$ z(y{dQu5bF!!>Q1Y*p>yyPM~KeJ8%paX*-e10X)Ypj!|~TX)gR|y8RWTa)9Vr9lzg+ z6Nct;ByZb^_xA+uQUEG`r*@mQeB2@P#t=~Q6)_M`cgO#zF;6(btuW;PD^EOGgRd+< zcOnxCI+6a07?==^?b%9RcOS0bE>^fM1Xw(AmEmJYI296ZcY+&3kIDcMTBUtyl%5AU$shA>3<^T+O(&v|Hf>+}PbGfp7>I!=G??kzLpfEDl7k zS26+JOkIv}^#27|5#7NVMCmtk1kJ?tDCQVr+u>I_->#Ck$j-{{nB6r251Uqg*0TJH zm?$I)+vVk5^@T3Eo_?9I@NKg_#a7_FGjmSGI8j;j;`S;e)vqiE&DR5;S@p!S{sCONsyh002ovPDHLkV1kQQ?b84N literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Kaiju_128x64/frame_35.png b/assets/dolphin/external/L1_Kaiju_128x64/frame_35.png new file mode 100644 index 0000000000000000000000000000000000000000..a72cf1823ee1ad46ad72a8b4e1e9156c0dedcc88 GIT binary patch literal 1560 zcmV+z2Iu*SP)#en?wbq{JX}$ONb>MlP_v`U|ytY95QH*}ta|<5%J8)9=F8MDyFcma}&_&l2lAsxy zPJ%m|hKRHL0mEy&;D$zxqk$V*dTu!v#iZ92oYWItz5@r)xSWQMF(6|6+c0*2@BNE@VhDlk+2Ex8^@#%y!baO6~Yg+kN%Or}LYQ3ps!%q@08|Jy~sdgy3_p zG`>eZpWkfU4`^jK<3`8Q3{Rim76Br9P9wmsuzgC7Bv*sw1h+*1>%KK^83&M{O{!A% zEDdN*a8m@}bE4PTxL2eAir%|8;cOYhuokQoP#pm*`Q!c2na}Nt2%t@b-;*YE5}q7< zIs#}&i2&KV67k^i+!Gb7MWfF>um~2vR!4yG8qxn)1eg&OYtmAVL*UsV5oo)zG6Eoa z(f5*m+Z`Zkd}I;mm;;ux_%*}$ zQiRm5ydM++BB50B%3V8v%eeuw+?+B0m1wq=1882kLX>8cIf0peT4<*CGn}96WL>4U zkOLq}Si7FS&SJs0!XjbxUW6;`&=ZaTni87f`8_^PhVhZ;!1M7ghw_lI(ca?(o-t?Z zNOfIG0n%dfT|We&N`9vZU@b0+>IlHcd;g=*$U-B#o+}?=#Ve?GB2NSu?JGF|*Dr+x zBB8o>yP7w`A#Hzq1ZeGlU850TjHO+(Il#aRSzt@}%uA&SS~9Zp^{$1}5QzZ%UKEiu z`a+D)bGr0Q8Yhy!GSh%`3fUc1RyaUwc!@U)?-ZaB8YHx%Y}|ttLwOnbDk8e>&=KJP z9BK;iObAI4UPKqPwfXV0;yCDOf0E;TNda0YnoY_ITTud|r5!&0Ob0&Z?gd}r0INVa z09~`*KR*kQiY3zGM)OeA36MUwoDiaC_UZ^=9s_BJ^x&kSybD-y#6-rth65lC5i)2& zbC6TJcjvjfHD9DW&H;7~jvj3&F{AfAa=e6brxTot0HHcSw4*f{baL7mU2B_BD4}w* z(?Em={D=tPsU2wjH7BqLD7w(qbpb@y)f@onm<{cmP8UjZg4Xm;CTW{W{Hh4xNg3$b z-5Z`Yr>TZ_9exA{h*S(`EMU2Q*4lhG^*sS)qf(aU$29SmFSY-E%50YXv6y>;}$Ijim@cLoEHf6{iba z{Id@9x)bl;30zTNqySpQyVViU^6?ItH>QAP4j>sEMG&09U`pw-hMPr5JZg^tQ8jWrDJ6d5{Be;^_X zuA~%FcbAA)5OtsxQs`})5gFhAZCn;4?_6Q26xF*jf@;U{g6o&Y0W8CxAaSP<)w_!4 z@IK29Ff;sZweMRIF=mF-gdHFw)c;`(8BXJN03_7^?fQGco#r3AcOtGYWHw&_0000< KMNUMnLSTX%)YRDk literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Kaiju_128x64/frame_36.png b/assets/dolphin/external/L1_Kaiju_128x64/frame_36.png new file mode 100644 index 0000000000000000000000000000000000000000..9a13e7c67db281d96949e0be75aa7ee4197dfa96 GIT binary patch literal 1592 zcmV-82FLk{P)PbXFRCt{2UE6ZqAPmGE|Nk$$Po#>DgjN?WZn85O5*vg-Vp%v% zdOgqcv;nT`YQ2i~z4WtWM*?r7)#LT8dNZ)I?^*rdNT*hC5oqn{V4ml3B@s`+U-)4r zKx_Y!U_~z`ls_^*)2%`?%=8L8uO89;1H zljGedExV^MK*Z0o1mF&Ir&lC`h0!FTex1$$qvs_Ipsf_H%saG%klu~#&e~40wohV! z4!-CRyQxzE4}RZQC8y}S>$=22NyyOj3_BgG8DP|Z|GAHo#~{rBS|;*m2ut*K+-cr^ z<(3gij1w7PG`_F~KswRhOAH~EQew@(XwS5u1%a2VHGt-u=BJARX6(PC!L3~NS!fBd zk^vCEIIBVoAO&QUW!5`s#hns-50&zE98?FbNP^y{<`3fEhX|yV`p?z`QQ^Tw2zMS4 zIBy3X*Ql%rzO+cSlDGTD0NSdr2oN0vIykNU8({6t)fyn;-J zVgbunFPnLjz++P@C0}pa5HaKhdI~`7oCsvaTkTge0JnE%9T*87>D_2wLYDD0Hgo)umh2FL=@ z035fqGNLO-OM)GN&WwTDK>PAw*PL{GTm(^MokN4ZGdZfUlK~J7Koj)3uJ88%ykdoO z)b5Tz@9584f2DsP$3Usl45yGJIE4i=iTXMXz$?xOf~53U1bat9oz4IpW&bSP8VD*1 zwP$a^=^B7T)c}>?!lT1V0zOHeM93yh^f>q<89*Df=W>Hjbj8F}CAIes1i^6{pfVHD z!SNFZKX+@#7o`6RT@z{hBN>1qG9YQ9`?K8>z|4V=DhMaTR;p%;0dgnE`7r_Lej8r|Ob~ zp5-N*(u7-q_N;28jwTGN@TTKxM~VR`l_GwCw-b;-z1)x06LrFyruYDUIJ!U?(L(Hut+6>gsXQTvh@)x_LNhUDOVo4*)hA$o~o z4Lo`ah$?9`5j;s|F-AqwXTcZsa0@^$Ya++38Hgm6mI%>cNl9=*;3YiIgKq&EMj9X+ zY%jZTdh^~VP#YP)`Fxxfc#8BEARSvqM;UZ7tN5PEgg2+$wn3v{FPb{)C z>Gyr#?Js)oZS3~m8wKfnHg?MY#%I5D2tTLaAH!!)kZ^CJRS6`~@=5KKh` z?GJq5Z=Qh}@-6z2HGmgT0Z0P4uN{L7S7;2#^z1FA=c)27_6a2bR&Xc*_a#W}Cq8C1 zCLGI@D>stQBHwDCGy*^+pu*bgF(_hqoX?GA#fcHXqbExMt-~TQoBfm_R*br;+i4MC zcHA8Ssz@NEi4}&gIDLkbA^@}#-b&+sbO998GBK2yIfyd?B#y_35qei`XY#QvusxS+ zfGiSNW~P(^esT>U!CV92L9?U43Opo;r~%+Onp~Sz18}-=dQKw%9uymHri5g?tX$lh zo&=HL2vHdUGs4KeR+6cr5Y)_g>Hf+Ih{p?w0Jx6W ziCG$`lCOca7O=>k)c{z(kO*K!WfZ6`1icZUHQVm`r^eKb`(*5l0Gb*A`lNU8HjrB=kU$k&)R;d7r$>N~Hjul> z@*q_29Su+3&S+sZGBr4+2AB=@&7nFAr$+#c={JEE5@`EQ&jau396UI^2EY(M3iXhH zQjFT)mHRS@^wiYqm>`oaz@}u#p*yv4WKoqmN3@wP9i~T z^1souZZq-r7eZhGmL>eYKmUS-7pJ`>!UgOTQot+95%8x9E9(Rq#Cc-9^3esr-z~KO z#Og9K!${ zq)T?I2W3Hc<#FpFv+Ln`s&3Gvq)QT_0X2qXe2sF{djpLwxI3^cYH0}+0 zyI3~ErF8)3WDm3v#X9?`alIsv^7-zTFDk-`Uj=K*{1`zHZLNsq17DL_(yTMzJtPRU{3zTMJEJPFAvKYk(DCC8>pqsvg>d>X~f0&$?DIvpM-6N3MWkr`72v=2tCtrvM39zpxr$=J*{& w+%9TB?jEFXW0F?irG_|vx-T(jq07*qoM6N<$f*O#b7XSbN literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Kaiju_128x64/frame_38.png b/assets/dolphin/external/L1_Kaiju_128x64/frame_38.png new file mode 100644 index 0000000000000000000000000000000000000000..93da7f4f94b06909289dcbdc89181a3c10a2f39d GIT binary patch literal 1489 zcmV;?1upuDP)1ZUZe~C;v8JmN$l)F2__2&N)} z^#yMgWdi9ag^HljB`=k;8D>#&ZpEYRhCqCyj zCY;NaD>stQBj0PEG6Fy)pu*Pi7!)xg&gbUx;=~9L(UT>B)nS>CRzGEk8KchXc3K3G z&W9twDiUaEVus-}PM2^}1b}wJM{VAZE`VZMC5AGRgE%8V;zW#?q4%nzBp=%X+jF`G zs3L)9WojwlH`f3f+%*86v^omRAVPwS8UW5?$+cBA0H+(Lrx*e7q}Xth5}NUR z5@doS#L5U@(F=a2^t<3d&iR}Hxv(rLP)!C-PPRNLFRvoOkjd#|(eDMnfbXpe;DV#~ z{^Mm;E(=iR3bJuJQ0W05Gn`M=Hrx?k*L!a-tMj#x!+pQX@RY$UMy%4Mwa>)>7lk(h zV9b#EM-5_j@Csmjs?UwKM{Cy;fJEE6KE?5VWj#?fR7q5YLwq0dO6$ z6H*#jCEo&XE#Q$Y)c{z(lnCI($|$h95R68E)_l(+WFaL2oK_z|z3&hK@VO|wtz>KM ze;YW%LT`1wnlN{W09qYY76CK?+2LnbSCP5Wta2n&?3Z}<0R}D$^SBl6$*&bZSOS&4 zEC%6F72S3h&KKoeYgMl~pPJJ$@13zT0$6GQ=#$>T-9T=mKm%29QFHzloE`x_+CXlQ z=Z8?ecQzt?8bF#Xi=CA5pPB|vj{q3rGl3owNP7objbJGtcXSRBoL&Q9 zs1JokNC1`5##_hEVpSV>+X#@Uh<6@|WV3ovb62*3kEsFh#1;e0tgO{rwpF-o1khk5 zfptU3JN7PkTn&&3?0jpTP>}|za5vKadn9ZetBgcd^48pcmkyvcUj)ehO=UGeCT6yl zXXUPl0w)mFbL&2x02#RrJ1To0;H0~O+3?(;Yfbn&fnH(bdJfdw*1L$A?P+wrRnIz5 z*L(fp!7D@IaU)2>aKLzR*T8XiYzwtq>^)ZEH3UTpu84v|mUOL^R5Le-DsV@#PW_p< z0l2C&jB7Py)S1;?1?;Laj+OX90GP|Yw}auUqQ#+b+D#%{3*90G%nG3#ZmkKV`K)8T z*F@xa=DZs|x&Zjza~H5R`N7g^L@Io|U7CeErGS#Z6zbXU=Md=tX4<@rf-`duWL0_- z|8=m(pjgqY)S^Wf?Yivo+FTIcI*K}0@IQB2%9Uw0swFj&&@QXU)U7JMipMS_zkt z?=3oEdGK>2HDg>F%PayY=VY%yF3m_fdG)~ck0fh%%a>(eQwq=&feLZw@OaC-+Wi6v z)QYhJ%RObI34^k^7vU7pnvWn6RJL^*tSKN{t`{(6MPx63AS8n5e*X-t^$$igT4cA) zR`k}|GRks!l{$|@u=|HihV9iW*%M8ru8Iq^2zI3aE3kfPHGt&!9Yx$O%-s7}Aq!tKO#sdDr?yb1A>zyo(u4_+ r5$eB~Lk4Nw1h9nq@2Wo+m^6O?V_bGuj^r&O00000NkvXXu0mjfe?7R4 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Kaiju_128x64/frame_39.png b/assets/dolphin/external/L1_Kaiju_128x64/frame_39.png new file mode 100644 index 0000000000000000000000000000000000000000..7510931b44bb100b314d9bc617843bd51a2cf164 GIT binary patch literal 1438 zcmV;P1!4M$P)$~B7-|wG(!YA*5C(dt?@6`<69HDoHRyL1<=sgX#!|F#% z?fe-fru-e}x9aC31oR!D7xe?e-#J7p0nR__L|VYLH(6#nfo6WnPE>hei-ksqb?`NA z46KaW30~tz#PA&%z#}K>0C0d(aHVlOic<1@->FK-VuDfUi*^FexnaGT0WeTT;M#MF z^fCgcge>}}*0&1-K;8D`FavlX?f$-vg%PmvRyo0L3;;J4)bTI_Xq5tmz=ZDGw7QuL zU|})}J2C);DGZre2=El}GlL0hx z^?zgXrQ`q}NK8QWp~`G0z{awMvr#8#b5inX{n|o7Zpb^iq;4bg;5%`FiA9|*c_b$c;x%a0ie*cSpg!#XsZ~Y&0Pw; zks=rXLSh}P?bU>W)P!}&bP9|bM~z1@K;}BQB&@IV!o$VbKupgDk6S(!?WG}`VA0PDQa<^Vk|j3zR%f@Mx%04a$~V*oh# zP9Y%jE+BHJaTsBMsJS)w01elUaX%VR*%`+`YrDSvO8s9?H~ws_WB^SL&^u_Y_46h$ z+Z#ROXJ)_|*qs5sj)9s^)S`au@r~nh0ICC)7@o8fS7CPsfMAorjQHfiNnv338~_5% z0kWPv!Esl4{>-O=n)b_20hrKHXYe+b=k+XP9s`ff0kAu14=k5T)~n|ea2x})Um}lz z-lHDZFCsL}$Rm!-0U9tSusFqtf_HryX~ikYYD4(j!X*Rl`rIK3mdz6FEFsSVMp=LZ zH2xEIJj4zRz{vn#yD&p!7zaCIj?Sa$3t!LJdE9&yvShGc>ysg%A0+Z_K$GZYv%*eK z12z3EPGw!&`(xsw44l2gdhB{bD8*uHnThuYMPkbA>o{SW(HmP}ZL#s6mIIu)mXA*g zm7D+r_Kq70ZLHp5MzJFcz^m`kG7~S&gwZ*G)?rQq_0FRsLO_GZ*!`|vu^V?`ffJpY zeLnzpG{k@%wXph3SWXbx&T?D`z@Xh#eO&MByvCogoBJ@N{n6mW5J0{kz%qGjt7V8R ztiD~`B@|h}eqVrfLD+7YZH(DQz*yp>5J2Vt4cc9W9Uq?p%n(+#lOEM#r8F1T2CL(2 zadBP-lR^rVf~3nMNjNVeLV>jVI74G?y>XoiuhzVT0{f34QfDtkX&Gh2fYfekWB2V~ zU0;v&k>-WK#@2+48DQTJz!?BrfCe*@Gz}BXII?oi)H@*Ov{_Jv01lMEGicZnzIM2) zfU>QYqm;oAPzs(WaJhqap^^hxlTuA0Wf?yTFa*s$#kfO2DK-c3V1z|R4d`W`kqfXk zu5_;wiETbAmV-wTqYj|We;~X@lR{P&YALp_jvawE4nqZa6f(MdX2)a;n_BV)Nq1?V z4*-S4jyN+jZ!1Q3a%Mkkh~RXNerwnWFz>V)eRX0sg4vE^1(YxJ3nNO2JK~&RYz$tx sXXQDt&#(hXhCl7;*eZB(oCv4+1L}9b5t%S*1^@s607*qoM6N<$f(+J@S^xk5 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Kaiju_128x64/frame_4.png b/assets/dolphin/external/L1_Kaiju_128x64/frame_4.png new file mode 100644 index 0000000000000000000000000000000000000000..f99454b164c598c8b5e6ae11d8d9864b7e3077a6 GIT binary patch literal 1284 zcmV+f1^fDmP)#PQ8w{{HVu407ihiAtVW;!`7}vtu(r>w;lzx{D>EzvbeHm z=LIkV&}3)^cp@f!-vX`xq--vq|D0We&S?z^&=9by974z37NtkLfTnypKk5NcL>g8F zoCaFc&nhP+L_>&nJsV0=jUakRCpSMNRLkvAel!GX$z>#u(n6G0k$4Ds0L`sPF^FzJ z8f#%>?41qEk}V}>EMH0>>F0nr47~bZOWs;6KU;!#5dh(uDk2%I zXH7fh1X=GTDNZW?r19Q9x;ufR(Ayx{N#PV)EuWdIbrY>MwCmQ`ZURKyZ%g~rYIRDD z4sFhCWV3W>ZbW(3xQthZcmSzv(XRKdf$li=!U}^K59cX=RE`4=0qA@HA!7wOq}H#O zGewk7KU>xx^#a?y8+c;r$LoCU9-^VpQoaQ`FIs+X)d!Gz+9mSh}WX z`P+8_NA(m+S<@HrJb{$MWG!D(I{h381zUuG8OhEpcs=_FJmpIkFXai5JuD?a??LT~ zmiS%gMJ=Dpr^KUs@USEV>`<@IfE9~9;%9sRk-mw9sRa0n;-kr|l?z0}0GE>up~DG~ zZHD|Byo_8UfplFfH6y(P4MPaf(&MjVQB9!MIE-9dyH3G-2pL&i=9E@&l literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Kaiju_128x64/frame_40.png b/assets/dolphin/external/L1_Kaiju_128x64/frame_40.png new file mode 100644 index 0000000000000000000000000000000000000000..a17a14044a94054ff24eff8c1b51bac31c273251 GIT binary patch literal 1438 zcmV;P1!4M$P)kKQKvOJQ80A_|Ne}^p9Gsmcu&^)qeHk^&j-j0Un{- ztN*$#I*#MFweV$t57ZyB@A~`K6=~aR?eiJADE$>J*S)%}(*M)bh1E(P$omofqbRRj zq<*V)?+l^sfqX8n-5&kX`8lPB^)7;CM8#xe7NH-P#mdExY`&?c|?LK@#*S2{uUqHEZR0r;Hvt;qmfs07V1`aaTluP3fz z02Fp(0KVZ`A8VdY-dURDCRFMp?fTJVS;nklfRUXM?8pEks0<)^jx^Ys3~OxlBt)EG zh6zUcdAphcMz)RYd}9F5;v(sD2L?c3f^Z}8}S zZGt04qG<3=C$M5OItQ4s55fQ(T9}ZOSI6$V7@+AUdUw+P{VW4;Sga%Ug*t%ru>1j# zIe^C=Nsf0yXP_lv{d+UHLWP|W2k<0ovz}kU05dYJ1&AbLy=}^4 z>&LEif>{P2ir>Z}01p8~@Am^*Hp<6HPJpmyX9nQ!&0;2u!cPG_+m3oKIuVrYszfFS zAd26}X5n&xY$7r<7PV-780g=tLWTiQy0sjD10DiKED!~9$4cR4RtBnH-+xtjl2DQZ z^q6Ii-hrNkI~l-}1MvK;+yRK@o#1JUPqaS+gpYwU2CgW;?27+B2at?<;!d8$?hL@O zECfXGWQW;32jEa009l70^|=F;4)!q&u%Zf=-36I4@r*8{?2pa?ybmW;45j*k#v~OJ znZZXe0OA%d0>o*-;|MEqi;v0yRzk`dc+o-|w+nW%#e+8o$eiCjW2El}a6~SZ_`Ukw zh%f-!UT6+bQN-c$D(d}rV+T7l8~A4@|F9|{kz|jY3>g9>**o0^q7h~Amwz1UU7)Oi zO*V&ifq@JGJKY9)#z&kg`fTd&1kQX9pn8XCdUt^pi=%BNP9oWJ`u8j+@EmC*HnBYR zX{UglZ1Lih7KP}2F*OSIdMcMOQz(-3Yxdm%p2K9@O8ka^M%~wSl~PZWkJhon;`7b( z=nxPApZtoW-8Tf(;6xZbI>u!kp(+GaFVd4jz)6%l?)4Lopsq|6GNL02k9_WosJ0R> zAwZjWPq9U#=^1=OKs{dS0NG4mO7603V6h4++#vC!5b*MA;q7%h5%ebm3w{44XXT=J zD`=HAp-%x^uB18HFxH( zh0lT;DS3P4B(DeoNWGD9R_UEXoAcuWuY%;A(M3n!DXhe5$BBa52g?Dpg})<-y9h($07*qoM6N<$f~i@aj{pDw literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Kaiju_128x64/frame_41.png b/assets/dolphin/external/L1_Kaiju_128x64/frame_41.png new file mode 100644 index 0000000000000000000000000000000000000000..f763540c917b48c20a54984132b516ab47935de5 GIT binary patch literal 1412 zcmV-~1$+95P)hLzz;{h#>kuQEvwZiy!V{y1I$Kh&bN6xMN7?W4b5{{c@8a0}g5 z{pWeYaU9>RhKB(@P`}H*^XsS6(~j@b{fOKu{T(gUy}Iqv|H5>kwOjD4%Nc(VOvd-gHAhx$N z2sl|o$EqM0I%oD+>@x*`Gzrqga>iFVf5@NN)y)7BxO9_E7m}WF-#U+O2EYc-u1gc_ zF|vvVuM7e$H&@pH5&KFQfI>^H090OC0BF@F?l)^IozJbX74R^?=)bZCp!S&qAQOS(AtV{YS-5}3d3fx~ zAc!&mfd4eM0_7)voizGYf(hfX!% zWV0{@K(-KxjIDASP6PN_7cvY`rCX{2D9Tg7j0L=?T(MkwiOQ(zhx@NePaIlm0E{Se z-~hT4u4Dj94M6L&_5c8g2f^K(53oN2xL*S!2CmqE*%g1j2H=bu4@+0aCkDpu3_#Hu z0M=Iae1UN$~54ZF?M(0Uc1UuPc!LI=5dVlBS&GdK< zmFT(p)$lMtwY{Y^K-VUYM12{ZJ2)R;4?83q=)E_+S#kOXkjrhADS+E!*y%K|8eRtf z?;nR+DVkF{`p>}0n#t}?0Xv-rTE=^V%KL2M-wCw*9sqaVKEseTJ{@lXJ3!0D-nJ8O z06uc?waNm^^H6tt3)smPD_$He&(gsRcvV+U0A?2NdBT;THB7ec#CHn7j*o2R;!k&> z#bJk)M}zU!DZmO$dliObc~1)H$GgI4?Nl!lTtjZ=meV(^~{$LXSNR+k$qXt@c7o&Ry z1G8PmF3@@;+e*(yuS zb+9OzRp?qxQH9wgSF=Zj{GF(Oz?=eR;I@mUEoAG&X#r$OAsKWyA({d@5l#UBz5t0J z!y;MciKc*^ict%;`K3?^k=2lOgth*mh|~sGrho&$_n$^(Fw3jlby{S0ZVYi)y^=j_ z>Us6m=1%=v!BJ2nx#;ek|`=|f$13@63_$6Fk5UBk>stGx zC~Lmx=#g)Y?v){o6v*fD>g|yqwa=3p_B_v!6D%bvj4|T`dfzNoIzO`bqIy0h!;Ba~ zo1ET9&m)b6bV3xf!B-vp+Hq~($8j7lj_)VwF^h)i=YAjkeb!k~8bmo=!%hr9?J*in z29Q>AKc*=`gHWj*Xl^k4fjLvX(LkBY2!Y(lde`8DIpJ z0jxw4Kx2W3 zG3Yr?zuA>5={g##gFu>#r_1Uf=rTw8c=U5KfTo0%0(canR<5Q`H3Mjn(#>qT(B$;< zUOTD`P=Sb&C>p#n2&~+Ut^qRoL2H0W3P3V|N3Sawpv!LUJxJ;CSq9*+SVy`R5&)4D zfUE&LdP(zm4Vr;ggwbP}TA@PE zcvif1|4Ihn^N9=)HB=-8WUAr}q!qRT5eDejudD&MeoIq;_8{F^^!gd+(R)`0L6!kp zvlmANUIHL%ByS4P=H@vR>9pM$fFH{ulSY^C0xX~l!LHx#3}6+JOBf&uuTe7;hH@HB z1N2xGW*7h^TdM&$mN$YTbE1qRrI+j+sO#wUtJ0H%-vG!cVy)7dEli)Ya+Z3R|Ql_d9)aHt&ikU83WPUXD zk8CepBOOy;26?GnTWzD_%+uuYH~^Jer0cc7)okNAhgiv@^on}tpr!yT3bPLu-C9~T z#tcR~7=CVs;dZ3{4mAZ>t=*1@!V%iOqPr(O7fO$vs{amG3eaE`Xfwu()*W2_EGn62 z7r0Zv2z1LO^#iih>FGacm~|SRAGIPsDh0F_3y=(ESY(y~vg~7}O^f;?Iv}w8yMi;w z7y{LXj-Jcx4tTM>vi3Pzi~YB8Gw{AM;6}(81(^a?Ai}26EwtJPsCrPYq!jIyMy^f) zXt|O5qQ$9Z^Wy}2L5j|()6wq~R$_JFM8Wlg)c~5~uPEZK0^-G2r7V8WbO2b6|KvS1 f5}6tY5j6h*`5Py|3n0fx00000NkvXXu0mjfU$&E9 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Kaiju_128x64/frame_43.png b/assets/dolphin/external/L1_Kaiju_128x64/frame_43.png new file mode 100644 index 0000000000000000000000000000000000000000..7eadf75185ece2ac3ebd0906c97d054942ac276d GIT binary patch literal 1397 zcmV-*1&aEKP)RCt{2UE6Z&APj_z|NobL9y**7Rp`bF7P77K z&+{y`*8io3hXFoNzDvLJ=iBKi+h?ghGA>GfMoDF_F0FL3a3@}0(1N6#Bf;A;pGeCq1BnCt|cf|Qex{dUNGk_O6FaQE0 zhgit~7QOoKoPH1n(CR4tMpy1=5%Yp44}cIB#H*Fbb=;Fa!^$Gi|Pku08a=&GJuAw7@)~6<*$?W&(Vud zV;!#Tl3hUG7nuWCCWISjhRrl^E0t>#$rUp6JiIX}g1+w#3=kPpd-u_b*K|Y9yC(-| zy54#hpkYN0U|I26|CJ0t$2(fZj1ry@AjK)9@wpYY0v-lv?N{aiRKK?*Fv|m5e=U!Y zoFOW6wG%`cpcH*^WZ<9=B2Hip0g?e|3`H7kcLt!(qKJgiqbZ?zB5D^9eTf;|fdRB6 zav1|~f_fT_Akr6Qv_1^<&$2MX04Ui~4nV;sft<1y`X1FqUJ|)tx$qJl1J$l?zbZU& z_`?7rtO@~i%*@rAfh!rnk^}VfS!;cF0Io426OH!0oAFE8p8@`C0}+E}gn+x^wmCpQ z2$win1XG|-vF?D~8Gr)902;h6cxQ*%JqMs*9l*N<(~gL}N66X8bg-{v0PXN1TCZW| zm^dN}Irg?WKtI^b9$pjN*N?U-)=lxe>2R*cd zxgI74h+YR%!L;4TxsV}%8+)hQKs3D9=t~ZQdPvESbb)~k0Xy9WTKapO%KL8SzY{3M zYpTQ*G`IuxHfXWfTV?{j4IF`^CC~2+?yw8kNf#@o9l%37bb=xWMVx$Y>?=WYnAtKD z%l}ktyJbYv+YZL3O!##npdn*x_k@5Pq+AA}jybbJK(>+Y6auCkfEortU0kwgJ!kO9 z@WL`acPD}=VCE){_T?NQY9y{7o!n9Gu`-F`aJvwI za02dmHb%L}N*y@)XzCw10@sFnCSC?ff@UVrddPz`lH?FxmeN+ARF7QAqwtElb2uzU ziq=d0v=>0;CT%?WOq0d+=jt@tIzIE zW>BkqtsOb@uw?%h8AvSuK}ZCVi9jZhp1R2HfbPUrhNoJJ@88DFzu+iX`qTMD<>k=g>ZopM=qY>6!Qg9g!Rd;WYmMkIM6+eyIw200000NkvXXu0mjf DE2n)B literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Kaiju_128x64/frame_44.png b/assets/dolphin/external/L1_Kaiju_128x64/frame_44.png new file mode 100644 index 0000000000000000000000000000000000000000..5241195d32bc160a3f8f6e1f04eda860211538bd GIT binary patch literal 1217 zcmV;y1U~zTP)z1 zai;(_V&x3?A_cG!Q8PSJNGX5=5t%Cfg)zMlGcyrAyx~1eg1#>WU;qzzZ-7T9n9!hl zXFDpc6aWD9I2r{#UiMVfTC)Zip;_t7Ol)Tq??mncI1qW56A{5mk>oyr5i3sejzoaw z?HS~GfSWO!4lE+!SE>CJfP!5=m(axE6$y1N`uhkf23nw|02o$SKL-s6vP`syfv-OJ zk7Rh;DS!t-3$$fP$%UmkKzp15aN@eIUw<<^4N`qDdYwZHwsw7m0MI;Z-JYd?RuOP( zmS%46@u+X;^%Vk`-HxTuL^4y|k8E0cticle*Em2FMh@W-f%I!;BzP#_i1&2@h}dn5 z$o-{Yg_?HlaccLc1C+nE&x@=(V6|<;0Z`r}1rWJsepk>bqJm$FZ52?;q2~dtLF)}1 z6%(4{dt4qtMFpSs0p&ST01|owBt5S!tB@T!sV#5t>3e5`&-;h)e2f6JAX}c-c%*xH z8uA9eaYY2b6wLN1jurr7+5Fy_nW}L|AR_o4o+FFE2Fu_)q1fv|tKPjlsECdhfEN*K z_-IJgyO)RZjd+d}01sX-n9}xXt^fD$pCX2+6lE3Bku?DSU`QPx-NS1Hlye2FOT8$b zBORc@;sg!aR%&wv?hdA=c3!v?w-vwBBwhAC>R%wBk zxak_3-h0D;wbdLkj`}li!LHIO5tO|Z9n^pm|K`Mk9F3HlZeV~xG zcAp@KmBBA1NC3znaVz#>wAansMEC4R!n{KWTF|Wh=i zM;3szZAF*6IM?zXre(1B*(M00000NkvXXu0mjf=Jgy8 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Kaiju_128x64/frame_45.png b/assets/dolphin/external/L1_Kaiju_128x64/frame_45.png new file mode 100644 index 0000000000000000000000000000000000000000..2a3ea8e23af713aa1210d6e19693e576aea86d6e GIT binary patch literal 1177 zcmV;K1ZMk*P)Y8+afiT?l%M3TOcp1*+Q}B7m0ov{wHCARx)QRU}^y zDG{JI8m0bO#(Cla_nTl`xsni8G5oidK!B{!uO(MnGP*M|6Y6OK+>4?X*OP!E1R|julPl|e8IX2= z#ChYr8BxSd0M4NC_tK&*MtF8^h0uCCd#nl&j_-emhMNGKK^CJu4`hY$9$2DGPlEnu z4+*H!v7Zwi1=4Z=%J|uZKdmNu>v_bhVylF-)Q^T$2+b%^JDa)&<$Nx&MZa3N%}2wY zdZ(~=hLqe&B2-TvX^!#x6-fVR*xNvnz;Yy5qQ(uzJMTv*ZI=!kE|vAoj_#U7S&srf%c%ij<7o4(pZa-07?L@ z9EqOuqoGu%jGG@FW>@q(xWrD|`5s##1h6D4BP8Rb>&LI--=%vIu zb_C3jq2&7jJB5Fbpwk}%GnLbQU8MUrqErOf#qm*SjUP*Wk17Qp?FEc@S{Kg(XP2gu zYMRJ6{tOAi{~y2uZBcJ_zC}vDVRbw=j|U+Fc$vz9DxV^dTC7MQnlThOk;giZ=UPY- zc!!=D^q~s@s$ulT$U=eAA2dL70Pn7|$fn#JGvj>&+|qQG6l~SRxxdXq8Z)CQz{@~{ r<2}3olzuV5yMa+>WIFU51OMX}ZL*z$)XYxz00000NkvXXu0mjfwJRcX literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Kaiju_128x64/frame_46.png b/assets/dolphin/external/L1_Kaiju_128x64/frame_46.png new file mode 100644 index 0000000000000000000000000000000000000000..f4b263b199c8402088ad99c4dc27ab3c38f0bf2e GIT binary patch literal 1300 zcmV+v1?&2WP)W2D-85p<^O-V?-LPnye~A&P*RkfIgD9c7`y0J zKF{+!ki_Tn0RVskQx}Wzr^3i^{3XJ9_^;B~^E~i8&tD>3?2l3c3|tbNgBAh+ToRmu zHUa=#5}bfm0$l8mb^_ceus3c5_;Hlz02)RVI9(r@BUyGXm9{ciLV$0|FFAK((;=kcky4*qM5*a_JDt~((MX(7Ka6%66%u8}l@6alov2Vl>~!z|;Tp^-p3qa)`x?rZcvuW3~z zXo&#xq^s0Fiyg~Ca665%VOiX{4Yoz~r6tN-oB8*1n{0$FCjghdR@jWTqZZc^q2|Ty zI?620r^Zx8f;J&&weu}D@ZxzFOd_@S^YOcg(`G_uqwP_*fzo1WlaJ;k)b8STpCp87 zM^A(dn9y4gpeyui$yFplvx3URtFD7B#FW}#JKk}H2{3nv2ZtA|;~JRTOChE9BU;dIhRXBHMj{N)ui+1(x9s{`bgUh@Nqy9olEMdb(5+Bs;za59=Hi$VW3sb zlF$(~GQi0eM|51Tdqo^g`mL@++HwFbd9v7(9qADQ3rV)UWcHc?>t$KzfA>_m)jyzVvXL5;c2(UuW?}j9T zQEdS92TJhI)+54-R|#Ns`W!w-Tk2xQVk-j4p;lc$x+{3Iyj?~BZc#k2*4wLqL;4tl zG|q~|5r3A60__CICUE$Yg-U)_z*6r6z&~xM`vUxLtzW4hJT@cHu&4^IV_R zVb($d@As&-ls#?dd*)jDZeS%UO23Zsk#YuytdlI|VpdJuQU|EmtzC)Ld>oCRJH{Y6 z-y=6`*e(Kiq)@A!t|@vPTyZQ(k!RZ^&jc!_#2`F8$R-)oEhC=qz$J4gu{|xeA4qX+HE?~YLk;|sG7eg%2SUPIt`A^2)FThKJ zHYwQR+>TPva(!9>Ygn#2CcJj6mqcm~5J^ZpgX3&RNU&=6TbzKU+p4j7NuZ4(904>X zkLu7X`${$_L?ey|RhKZk^c}7OBvp$-l><0|WR)Y{%rQFpPA7r0_%MLapNNAxN~>R~ z20SQ}g7Mk(4Yz#1fYQ4FT!KX5+5NZl%b{Ec$U39bq4ya07ykon_@I`j=(wH$0000< KMNUMnLSTZO1z`dJ literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Kaiju_128x64/frame_47.png b/assets/dolphin/external/L1_Kaiju_128x64/frame_47.png new file mode 100644 index 0000000000000000000000000000000000000000..1563ff39b27a83826ad84e5eb30d17b8d216a971 GIT binary patch literal 1268 zcmV6ZXSuAvd3*nNy%ItPdd9U0;0d7i&NS!1k1c?I~Od5L9!C@=fQ z>HGTn9vNe|_qR4dfNaU_`=@=oQfdj?Hude28xpzn(sd-eLk1n|Vu zlSx459vx!({^{@DG5R*6Aps_UC5PO#hy`0oq&}nfj*qB-WcCvvA zgnmz-O*(Cz&=4YB_l6RP01~U{|5NGYn5kiB*oaaqWZ}8E0kouMv~ZINssP*; zjY4_`YIw<$tsG1W0i_Ax$!04Uo}xFtR!c$Uf94sb^_wCio3U>8hE$EoZ-B?25BDJ zz0(9hk}!?^5@wWTcTyNV1b$2iq(u}V`syb>Y zQwYlP{FRFG8Nj>u|6pbQ^xl3yla` zwF3#^?*{f0()t)Fmqu)@vf(8*O&UFY(LR@yf@k9^68lCCT?vR0`l8L(=KHK=^&E*t z=P>>E0X&n@uG2oa$?s0;ER2M;9&}B$wT&Wt$Z>FM`ol#@uvI*%0&Px6NSfI_coF(y z2n9W~KoR|rQCmz6i1&}?BQtF+mKpA0RwedaAfL2(SB!T}MxW$!rED_d@I2|W&; zVY#>MuH+Kk_GTbs?9fv1uG;%b?0mM2u}ge$1=;$EP)YOFbE}8|NocMCsz~!X0uIheWdDkG7eriteMd~ z@B6+`faiIjNYuqf{HhZ4zArq_Qzh~%sUJ1+ox}HHQ2jDjKJWu&%Emqd?aKC^s0VL&D!zxo6 zw}Tk7YNxC90|aw`ZpvRR;{=8$#s6Qimt|#OHS0B5vxBk5XgZ%~57Kz=IgE9Td+7c2 z{Vd!BIAQ(TeDC|JuGcz`q_~~#+cK9#j3`Pyz^?v3XA$)J3|33>kbv92bxoS3b8D?4 zXBh#;LUum{iX6Z!VdLho-ZS%7k@b!FDG@42P(}dkGCM=Ev;l9s7muN`xVboaR0cN& zzmLacNl@Ae{F3tVLE?WH)cvl?bv$vMBU@%|?OVZB2^jWJSn=MqNC)k`j@RVHSUW8Z zBP4JUfYzm7LqzeM(mCssWX{Stx;76gDgsjupp{DF`YikH(9Vs@=Q8oC$HA6rL>oip zc-LhjfQ8*r85k?QN|s!dFxf)v!ugU8)VP6Bnt5Jk219OqFMHKNLEF)Xl>dKTFPL;mF$zs>CM#C`Ys`J_!0yM=}g#fLt zUI8shlh$RGA<2`@v1Kb*v&L%oueD%TB3jDPa3VqL`L}BX0oYvdY~kLH;^|x>>Io!e za`Ao^0=7I0$keV_hSny{%q_`WK@LmnmyQR>Su!O=Hq5!JfKhTpG23WV_SY&2?EWne zo5po4WBkqJQQ;6EC0a9AXi=18QD15kV`$F@1~}zP`<40%k$ynQ<6rJSM9Qv!Hn zde#~|_jh;yC*UPPI0Vp47GIBIr9!L2fYtq1gb8mL@Kgp{lE9w^i!y2Jwu;yuGFjHp z=G(#Edru{ol@F~t%zeKd>_o?Qa|SJbIC6*eU+_5oyMb4kXYsk8jk|=Y#nO+2ab+UL cRpxp80Znqgg|Y|Bf&c&j07*qoM6N<$g7rsmoB#j- literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Kaiju_128x64/frame_6.png b/assets/dolphin/external/L1_Kaiju_128x64/frame_6.png new file mode 100644 index 0000000000000000000000000000000000000000..3f84ad47e3ded623ecc0323b5d64af350fd1e61a GIT binary patch literal 1312 zcmV+*1>gFKP)%Q;16>(ix1Bf|z0Dlld?)z@nbsKnc*=A;H@KC&0nG zFogie3%m!%1N=Cq5#V@%ci?z{1inPb;jt5BMDJ zQUE;}>`FSPg?pkz0BK{d>w4veb0WWO{#m36pl$x!(C4hJWfl>r{3pqmBES=y?~$KT zoHnMvKJsRn5J#`qbh(NI z$Yqb>J{`-6Fp~r@0#J9h0^F5KuaCep@zGeTV(=!79%w|EK>}z9c&ZGHDur((C2z%o zyG$+CkH$)}_?*!s__E7ZrO;j(MC=-u1Dl+&n*&+yhOIK;~}TCjw)OP@N~MG(!^0f}^or zlAVeDsGK1KND|1c!m_lH96HVdf4`E&YjcrYuqSLp?vovd0`#k4^pU@wm4}4YFX-<} z#FEHOU*rGt`)K5pEM3yy%<5`NCvcbKFrzrCaLFUl#*CD-NptXS{u7w;)vy(^qMjx) zFD?0#Hr|@Y?wjA*#M8hPn58)0)znHcj|}T|@8-|+tgj}(Y)G4J#bk{%9XnZm$JPXR zstK-6lr-Z&Z(u{}whG@d{5F(Vz!t|ZC?@VMXT0SX->b>g(n z*%O_#D6IJJ^)Sm1gGLx7Nr=hvP9v)0Xx&eX(exVaI?eheIT}_}3h)qs&Ib@OP)gJ+ z*2;w>S6#_tEK+5#-3LN;Dg9dWe>~e0;yjzrWm**i>WpBkK0s7S5xLdz)ZI%!Sf!^t zv^-4)+Yn%8yhDX0iv-#R@mwG?N6GT7{3C)bLO`S}dJ0|Ho|?SUztGCh$Vp=aTl4{T z+Gq<}`9?9y2S!~^{#=Xss4)2m0p7Op6eADdxh~QHM&X+0)8uGp z5%hgJzCwa90V3|pD-RhGtTGG@uWXHsmM7r%kj5ZN!s||Irqa%&GNL_lZ@uh!y#h~5 z*7AfJ8MG48yY8@e$`XLyoT9S%n#>4BH986&rF%EVV@$J1P;LZu_Y=Wrg4vB)SzUPY zl}N^~{dtW+N*+Cce=)?usFNni;|kdksN}VJj|>Tx^#M>WcvXy#VD>piM1C|e91>vE z+VqY`hlwcA)qqFVQ)`HjU`T*b;-V!UU+VgCcoRVSSkXha-1D@ip;Z8nK^2*ZYNB$j zC4*um!6k28UQd+({{?|8KGS7;vhnnGEUf|#_QO`c9D1yIJ! WF~&y6@>K)?0000&-uL|>!o_*$B0#|vZP;8qkLkA=s$H*W}iFLX*Ifg?b$?O@I~F zAC-@^7VYrs_-~DEMbzUU0DmC_4FtKeGOXm5=-<1L1-#=K>3(uq{CmFktlR(=0?LL* zwtuZdi5z<5Q0sA$#rJ^AufNZ(A(1Y-M;HNkHjI>6V1*saVlw-Yo*{amJpOCBfTnyt zzlEqhM1m%PGb;hKm2#In+WlJR6iFnl&$D^SN3?>A)%7S2C8M=k;R>_lXdz0gNIawn zpd~IB{{x}K=sX^Fq%Ng}PzccCik11cl!~jnVhGeOJZjnAvs=oq*Ou1Q zI7WB9Mx!=}ED5-rXvI?#A)QRVpwrb7vDTz2lGvffAmC-<+UF7(yz0RlBaPGa0R7W2 zX=AO29&w4*&nv(!Pbw!RrSQ+|Bvfh=)${q9T9?uSv@7#5dfL>dnJt3;>_j)qamG1ln|Qoqq&3hd3Psp6tYV5;^}I|6X}PY z=>SHQXN}7kWj6sL*4NVFmF7gssoAi+V20qH@<(|%hzE!?BoMN-xahS>S3qj+cp)>Q zeEzd#{ZTKlWh2NU5xV7!06%cc)kiA2$q2nj5BnSVW&ujyI7 zO3HT!fB6^IrACXaUKy?HW)#>mx6Id+o=FI|c_nOAPhmE^o_z!@%9lJ|WDi>q;8T=e z&=SAPyvUp~M(TxR6X7lT09qAsIw`DJ?2$g(`}}8%d!h(pl8d^ylFovX*W=Tnkz|X~=c8&n?t@wiJ!>rfCA84;EblN*6}6 z%S>y%$bNdNhb)>yfJTI@%ga)PyWLzi0xO?s z#M%Wiz|IeW_m7fKw)A&ia|2g!_vDJSL&07C;|NZy_mhDl>rw^mi00000 LNkvXXu0mjfjhAve literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Kaiju_128x64/frame_8.png b/assets/dolphin/external/L1_Kaiju_128x64/frame_8.png new file mode 100644 index 0000000000000000000000000000000000000000..2c8cf3f5d7c34f1eedec4def10bb28095f2e1c4a GIT binary patch literal 1308 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 Date: Wed, 31 May 2023 16:59:12 +0400 Subject: [PATCH 583/824] [FL-3340] SubGhz: fix flipper crashes after exiting broadcast blocking message and crash cli (#2714) --- .../main/subghz/helpers/subghz_error_type.h | 1 + .../main/subghz/scenes/subghz_scene_rpc.c | 33 ++++++++++++------- applications/main/subghz/subghz_cli.c | 17 ++++++---- 3 files changed, 33 insertions(+), 18 deletions(-) diff --git a/applications/main/subghz/helpers/subghz_error_type.h b/applications/main/subghz/helpers/subghz_error_type.h index e481aa4be84..0f86d6ea7d1 100644 --- a/applications/main/subghz/helpers/subghz_error_type.h +++ b/applications/main/subghz/helpers/subghz_error_type.h @@ -10,4 +10,5 @@ typedef enum { 1, /** File parsing error, or wrong file structure, or missing required parameters. more accurate data can be obtained through the debug port */ SubGhzErrorTypeOnlyRX = 2, /** Transmission on this frequency is blocked by regional settings */ + SubGhzErrorTypeParserOthers = 3, /** Error in protocol parameters description */ } SubGhzErrorType; diff --git a/applications/main/subghz/scenes/subghz_scene_rpc.c b/applications/main/subghz/scenes/subghz_scene_rpc.c index aa6f132d761..d4bf3e808eb 100644 --- a/applications/main/subghz/scenes/subghz_scene_rpc.c +++ b/applications/main/subghz/scenes/subghz_scene_rpc.c @@ -40,15 +40,26 @@ bool subghz_scene_rpc_on_event(void* context, SceneManagerEvent event) { } else if(event.event == SubGhzCustomEventSceneRpcButtonPress) { bool result = false; if((state == SubGhzRpcStateLoaded)) { - result = subghz_tx_start(subghz, subghz_txrx_get_fff_data(subghz->txrx)); - state = SubGhzRpcStateTx; - if(result) subghz_blink_start(subghz); - } - if(!result) { - rpc_system_app_set_error_code(subghz->rpc_ctx, SubGhzErrorTypeOnlyRX); - rpc_system_app_set_error_text( - subghz->rpc_ctx, - "Transmission on this frequency is restricted in your region"); + switch( + subghz_txrx_tx_start(subghz->txrx, subghz_txrx_get_fff_data(subghz->txrx))) { + case SubGhzTxRxStartTxStateErrorOnlyRx: + rpc_system_app_set_error_code(subghz->rpc_ctx, SubGhzErrorTypeOnlyRX); + rpc_system_app_set_error_text( + subghz->rpc_ctx, + "Transmission on this frequency is restricted in your region"); + break; + case SubGhzTxRxStartTxStateErrorParserOthers: + rpc_system_app_set_error_code(subghz->rpc_ctx, SubGhzErrorTypeParserOthers); + rpc_system_app_set_error_text( + subghz->rpc_ctx, "Error in protocol parameters description"); + break; + + default: //if(SubGhzTxRxStartTxStateOk) + result = true; + subghz_blink_start(subghz); + state = SubGhzRpcStateTx; + break; + } } rpc_system_app_confirm(subghz->rpc_ctx, RpcAppEventButtonPress, result); } else if(event.event == SubGhzCustomEventSceneRpcButtonRelease) { @@ -56,9 +67,9 @@ bool subghz_scene_rpc_on_event(void* context, SceneManagerEvent event) { if(state == SubGhzRpcStateTx) { subghz_txrx_stop(subghz->txrx); subghz_blink_stop(subghz); - state = SubGhzRpcStateIdle; result = true; } + state = SubGhzRpcStateIdle; rpc_system_app_confirm(subghz->rpc_ctx, RpcAppEventButtonRelease, result); } else if(event.event == SubGhzCustomEventSceneRpcLoad) { bool result = false; @@ -95,7 +106,7 @@ bool subghz_scene_rpc_on_event(void* context, SceneManagerEvent event) { void subghz_scene_rpc_on_exit(void* context) { SubGhz* subghz = context; SubGhzRpcState state = scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneRpc); - if(state != SubGhzRpcStateIdle) { + if(state == SubGhzRpcStateTx) { subghz_txrx_stop(subghz->txrx); subghz_blink_stop(subghz); } diff --git a/applications/main/subghz/subghz_cli.c b/applications/main/subghz/subghz_cli.c index ac19d65b499..60845ac9986 100644 --- a/applications/main/subghz/subghz_cli.c +++ b/applications/main/subghz/subghz_cli.c @@ -176,16 +176,19 @@ void subghz_cli_command_tx(Cli* cli, FuriString* args, void* context) { furi_hal_power_suppress_charge_enter(); - furi_hal_subghz_start_async_tx(subghz_transmitter_yield, transmitter); + if(furi_hal_subghz_start_async_tx(subghz_transmitter_yield, transmitter)) { + while(!(furi_hal_subghz_is_async_tx_complete() || cli_cmd_interrupt_received(cli))) { + printf("."); + fflush(stdout); + furi_delay_ms(333); + } + furi_hal_subghz_stop_async_tx(); - while(!(furi_hal_subghz_is_async_tx_complete() || cli_cmd_interrupt_received(cli))) { - printf("."); - fflush(stdout); - furi_delay_ms(333); + } else { + printf("Transmission on this frequency is restricted in your region\r\n"); } - furi_hal_subghz_stop_async_tx(); - furi_hal_subghz_sleep(); + furi_hal_subghz_sleep(); furi_hal_power_suppress_charge_exit(); flipper_format_free(flipper_format); From 86a64487cb1f7b43fcf46b73735ff032c4f23e6f Mon Sep 17 00:00:00 2001 From: AloneLiberty <111039319+AloneLiberty@users.noreply.github.com> Date: Wed, 31 May 2023 13:56:04 +0000 Subject: [PATCH 584/824] NFC: Fix gen1 writing with invalid BCC (lost fix from PR #2511) (#2710) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- .../nfc_magic/lib/magic/classic_gen1.c | 30 --- .../nfc_magic/lib/magic/classic_gen1.h | 2 - .../external/nfc_magic/lib/magic/magic.c | 175 ------------------ .../external/nfc_magic/nfc_magic_worker.c | 66 ++++--- 4 files changed, 45 insertions(+), 228 deletions(-) delete mode 100644 applications/external/nfc_magic/lib/magic/magic.c diff --git a/applications/external/nfc_magic/lib/magic/classic_gen1.c b/applications/external/nfc_magic/lib/magic/classic_gen1.c index ebd2b08057a..8d87d63161d 100644 --- a/applications/external/nfc_magic/lib/magic/classic_gen1.c +++ b/applications/external/nfc_magic/lib/magic/classic_gen1.c @@ -5,7 +5,6 @@ #define TAG "Magic" #define MAGIC_CMD_WUPA (0x40) -#define MAGIC_CMD_WIPE (0x41) #define MAGIC_CMD_ACCESS (0x43) #define MAGIC_MIFARE_READ_CMD (0x30) @@ -144,32 +143,3 @@ bool magic_gen1_write_blk(uint8_t block_num, MfClassicBlock* data) { return write_success; } - -bool magic_gen1_wipe() { - bool wipe_success = false; - uint8_t tx_data[MAGIC_BUFFER_SIZE] = {}; - uint8_t rx_data[MAGIC_BUFFER_SIZE] = {}; - uint16_t rx_len = 0; - FuriHalNfcReturn ret = 0; - - do { - tx_data[0] = MAGIC_CMD_WIPE; - ret = furi_hal_nfc_ll_txrx_bits( - tx_data, - 8, - rx_data, - sizeof(rx_data), - &rx_len, - FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_TX_MANUAL | FURI_HAL_NFC_LL_TXRX_FLAGS_AGC_ON | - FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_RX_KEEP, - furi_hal_nfc_ll_ms2fc(2000)); - - if(ret != FuriHalNfcReturnIncompleteByte) break; - if(rx_len != 4) break; - if(rx_data[0] != MAGIC_ACK) break; - - wipe_success = true; - } while(false); - - return wipe_success; -} \ No newline at end of file diff --git a/applications/external/nfc_magic/lib/magic/classic_gen1.h b/applications/external/nfc_magic/lib/magic/classic_gen1.h index 6d4ff6dcd9a..98de1230239 100644 --- a/applications/external/nfc_magic/lib/magic/classic_gen1.h +++ b/applications/external/nfc_magic/lib/magic/classic_gen1.h @@ -9,5 +9,3 @@ bool magic_gen1_read_block(uint8_t block_num, MfClassicBlock* data); bool magic_gen1_data_access_cmd(); bool magic_gen1_write_blk(uint8_t block_num, MfClassicBlock* data); - -bool magic_gen1_wipe(); \ No newline at end of file diff --git a/applications/external/nfc_magic/lib/magic/magic.c b/applications/external/nfc_magic/lib/magic/magic.c deleted file mode 100644 index ebd2b08057a..00000000000 --- a/applications/external/nfc_magic/lib/magic/magic.c +++ /dev/null @@ -1,175 +0,0 @@ -#include "classic_gen1.h" - -#include - -#define TAG "Magic" - -#define MAGIC_CMD_WUPA (0x40) -#define MAGIC_CMD_WIPE (0x41) -#define MAGIC_CMD_ACCESS (0x43) - -#define MAGIC_MIFARE_READ_CMD (0x30) -#define MAGIC_MIFARE_WRITE_CMD (0xA0) - -#define MAGIC_ACK (0x0A) - -#define MAGIC_BUFFER_SIZE (32) - -bool magic_gen1_wupa() { - bool magic_activated = false; - uint8_t tx_data[MAGIC_BUFFER_SIZE] = {}; - uint8_t rx_data[MAGIC_BUFFER_SIZE] = {}; - uint16_t rx_len = 0; - FuriHalNfcReturn ret = 0; - - do { - // Start communication - tx_data[0] = MAGIC_CMD_WUPA; - ret = furi_hal_nfc_ll_txrx_bits( - tx_data, - 7, - rx_data, - sizeof(rx_data), - &rx_len, - FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_TX_MANUAL | FURI_HAL_NFC_LL_TXRX_FLAGS_AGC_ON | - FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_RX_KEEP, - furi_hal_nfc_ll_ms2fc(20)); - if(ret != FuriHalNfcReturnIncompleteByte) break; - if(rx_len != 4) break; - if(rx_data[0] != MAGIC_ACK) break; - magic_activated = true; - } while(false); - - return magic_activated; -} - -bool magic_gen1_data_access_cmd() { - bool write_cmd_success = false; - uint8_t tx_data[MAGIC_BUFFER_SIZE] = {}; - uint8_t rx_data[MAGIC_BUFFER_SIZE] = {}; - uint16_t rx_len = 0; - FuriHalNfcReturn ret = 0; - - do { - tx_data[0] = MAGIC_CMD_ACCESS; - ret = furi_hal_nfc_ll_txrx_bits( - tx_data, - 8, - rx_data, - sizeof(rx_data), - &rx_len, - FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_TX_MANUAL | FURI_HAL_NFC_LL_TXRX_FLAGS_AGC_ON | - FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_RX_KEEP, - furi_hal_nfc_ll_ms2fc(20)); - if(ret != FuriHalNfcReturnIncompleteByte) break; - if(rx_len != 4) break; - if(rx_data[0] != MAGIC_ACK) break; - - write_cmd_success = true; - } while(false); - - return write_cmd_success; -} - -bool magic_gen1_read_block(uint8_t block_num, MfClassicBlock* data) { - furi_assert(data); - - bool read_success = false; - - uint8_t tx_data[MAGIC_BUFFER_SIZE] = {}; - uint8_t rx_data[MAGIC_BUFFER_SIZE] = {}; - uint16_t rx_len = 0; - FuriHalNfcReturn ret = 0; - - do { - tx_data[0] = MAGIC_MIFARE_READ_CMD; - tx_data[1] = block_num; - ret = furi_hal_nfc_ll_txrx_bits( - tx_data, - 2 * 8, - rx_data, - sizeof(rx_data), - &rx_len, - FURI_HAL_NFC_LL_TXRX_FLAGS_AGC_ON, - furi_hal_nfc_ll_ms2fc(20)); - - if(ret != FuriHalNfcReturnOk) break; - if(rx_len != 16 * 8) break; - memcpy(data->value, rx_data, sizeof(data->value)); - read_success = true; - } while(false); - - return read_success; -} - -bool magic_gen1_write_blk(uint8_t block_num, MfClassicBlock* data) { - furi_assert(data); - - bool write_success = false; - uint8_t tx_data[MAGIC_BUFFER_SIZE] = {}; - uint8_t rx_data[MAGIC_BUFFER_SIZE] = {}; - uint16_t rx_len = 0; - FuriHalNfcReturn ret = 0; - - do { - tx_data[0] = MAGIC_MIFARE_WRITE_CMD; - tx_data[1] = block_num; - ret = furi_hal_nfc_ll_txrx_bits( - tx_data, - 2 * 8, - rx_data, - sizeof(rx_data), - &rx_len, - FURI_HAL_NFC_LL_TXRX_FLAGS_AGC_ON | FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_RX_KEEP, - furi_hal_nfc_ll_ms2fc(20)); - if(ret != FuriHalNfcReturnIncompleteByte) break; - if(rx_len != 4) break; - if(rx_data[0] != MAGIC_ACK) break; - - memcpy(tx_data, data->value, sizeof(data->value)); - ret = furi_hal_nfc_ll_txrx_bits( - tx_data, - 16 * 8, - rx_data, - sizeof(rx_data), - &rx_len, - FURI_HAL_NFC_LL_TXRX_FLAGS_AGC_ON | FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_RX_KEEP, - furi_hal_nfc_ll_ms2fc(20)); - if(ret != FuriHalNfcReturnIncompleteByte) break; - if(rx_len != 4) break; - if(rx_data[0] != MAGIC_ACK) break; - - write_success = true; - } while(false); - - return write_success; -} - -bool magic_gen1_wipe() { - bool wipe_success = false; - uint8_t tx_data[MAGIC_BUFFER_SIZE] = {}; - uint8_t rx_data[MAGIC_BUFFER_SIZE] = {}; - uint16_t rx_len = 0; - FuriHalNfcReturn ret = 0; - - do { - tx_data[0] = MAGIC_CMD_WIPE; - ret = furi_hal_nfc_ll_txrx_bits( - tx_data, - 8, - rx_data, - sizeof(rx_data), - &rx_len, - FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_TX_MANUAL | FURI_HAL_NFC_LL_TXRX_FLAGS_AGC_ON | - FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_RX_KEEP, - furi_hal_nfc_ll_ms2fc(2000)); - - if(ret != FuriHalNfcReturnIncompleteByte) break; - if(rx_len != 4) break; - if(rx_data[0] != MAGIC_ACK) break; - - wipe_success = true; - } while(false); - - return wipe_success; -} \ No newline at end of file diff --git a/applications/external/nfc_magic/nfc_magic_worker.c b/applications/external/nfc_magic/nfc_magic_worker.c index dc22b5d3ec6..9ee7fd3eecd 100644 --- a/applications/external/nfc_magic/nfc_magic_worker.c +++ b/applications/external/nfc_magic/nfc_magic_worker.c @@ -92,51 +92,49 @@ void nfc_magic_worker_write(NfcMagicWorker* nfc_magic_worker) { while(nfc_magic_worker->state == NfcMagicWorkerStateWrite) { do { - if(furi_hal_nfc_detect(&nfc_data, 200)) { - if(nfc_data.cuid != magic_dev->cuid) break; - if(!card_found_notified) { - nfc_magic_worker->callback( - NfcMagicWorkerEventCardDetected, nfc_magic_worker->context); - card_found_notified = true; - } - furi_hal_nfc_sleep(); - - magic_activate(); - if(magic_dev->type == MagicTypeClassicGen1) { - if(dev_protocol != NfcDeviceProtocolMifareClassic) break; - MfClassicData* mfc_data = &dev_data->mf_classic_data; - - if(mfc_data->type != MfClassicType1k) break; + if(magic_dev->type == MagicTypeClassicGen1) { + if(furi_hal_nfc_detect(&nfc_data, 200)) { + magic_deactivate(); + magic_activate(); if(!magic_gen1_wupa()) { - FURI_LOG_E(TAG, "Not Magic card"); + FURI_LOG_E(TAG, "No card response to WUPA (not a magic card)"); nfc_magic_worker->callback( NfcMagicWorkerEventWrongCard, nfc_magic_worker->context); done = true; break; } + magic_deactivate(); + } + magic_activate(); + if(magic_gen1_wupa()) { if(!magic_gen1_data_access_cmd()) { - FURI_LOG_E(TAG, "Not Magic card"); + FURI_LOG_E( + TAG, "No card response to data access command (not a magic card)"); nfc_magic_worker->callback( NfcMagicWorkerEventWrongCard, nfc_magic_worker->context); done = true; break; } + + MfClassicData* mfc_data = &dev_data->mf_classic_data; for(size_t i = 0; i < 64; i++) { FURI_LOG_D(TAG, "Writing block %d", i); if(!magic_gen1_write_blk(i, &mfc_data->block[i])) { FURI_LOG_E(TAG, "Failed to write %d block", i); + done = true; nfc_magic_worker->callback( NfcMagicWorkerEventFail, nfc_magic_worker->context); - done = true; break; } } + done = true; nfc_magic_worker->callback( NfcMagicWorkerEventSuccess, nfc_magic_worker->context); - done = true; break; - } else if(magic_dev->type == MagicTypeGen4) { + } + } else if(magic_dev->type == MagicTypeGen4) { + if(furi_hal_nfc_detect(&nfc_data, 200)) { uint8_t gen4_config[28]; uint32_t password = magic_dev->password; @@ -196,6 +194,7 @@ void nfc_magic_worker_write(NfcMagicWorker* nfc_magic_worker) { gen4_config[25] = dev_data->nfc_data.atqa[1]; gen4_config[26] = dev_data->nfc_data.sak; + furi_hal_nfc_sleep(); furi_hal_nfc_activate_nfca(200, &cuid); if(!magic_gen4_set_cfg(password, gen4_config, sizeof(gen4_config), false)) { nfc_magic_worker->callback( @@ -394,6 +393,11 @@ void nfc_magic_worker_wipe(NfcMagicWorker* nfc_magic_worker) { MfClassicBlock block; memset(&block, 0, sizeof(MfClassicBlock)); + MfClassicBlock empty_block; + memset(&empty_block, 0, sizeof(MfClassicBlock)); + MfClassicBlock trailer_block; + memset(&trailer_block, 0xff, sizeof(MfClassicBlock)); + block.value[0] = 0x01; block.value[1] = 0x02; block.value[2] = 0x03; @@ -402,6 +406,10 @@ void nfc_magic_worker_wipe(NfcMagicWorker* nfc_magic_worker) { block.value[5] = 0x08; block.value[6] = 0x04; + trailer_block.value[7] = 0x07; + trailer_block.value[8] = 0x80; + trailer_block.value[9] = 0x69; + while(nfc_magic_worker->state == NfcMagicWorkerStateWipe) { do { magic_deactivate(); @@ -415,10 +423,26 @@ void nfc_magic_worker_wipe(NfcMagicWorker* nfc_magic_worker) { card_found_notified = true; } - if(!magic_gen1_wipe()) break; if(!magic_gen1_data_access_cmd()) break; if(!magic_gen1_write_blk(0, &block)) break; + for(size_t i = 1; i < 64; i++) { + FURI_LOG_D(TAG, "Wiping block %d", i); + bool success = false; + if((i | 0x03) == i) { + success = magic_gen1_write_blk(i, &trailer_block); + } else { + success = magic_gen1_write_blk(i, &empty_block); + } + + if(!success) { + FURI_LOG_E(TAG, "Failed to write %d block", i); + nfc_magic_worker->callback( + NfcMagicWorkerEventFail, nfc_magic_worker->context); + break; + } + } + card_wiped = true; nfc_magic_worker->callback(NfcMagicWorkerEventSuccess, nfc_magic_worker->context); } else if(magic_dev->type == MagicTypeGen4) { From 3a7203e32e2058e5234ae82b2fa33d2f06fc055c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zo=C3=AB=20Prosvetova?= <109866245+ZoeMeetAgain@users.noreply.github.com> Date: Thu, 1 Jun 2023 11:59:41 +0200 Subject: [PATCH 585/824] Update dolphin.py (#2717) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- scripts/flipper/assets/dolphin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/flipper/assets/dolphin.py b/scripts/flipper/assets/dolphin.py index ebe9fd88986..b4a53a62df5 100644 --- a/scripts/flipper/assets/dolphin.py +++ b/scripts/flipper/assets/dolphin.py @@ -49,11 +49,11 @@ def __init__( def load(self, animation_directory: str): if not os.path.isdir(animation_directory): - raise Exception(f"Animation folder doesn't exists: { animation_directory }") + raise Exception(f"Animation folder doesn't exist: { animation_directory }") meta_filename = os.path.join(animation_directory, "meta.txt") if not os.path.isfile(meta_filename): - raise Exception(f"Animation meta file doesn't exists: { meta_filename }") + raise Exception(f"Animation meta file doesn't exist: { meta_filename }") self.logger.info(f"Loading meta from {meta_filename}") file = FlipperFormatFile() From 1d7966f74ef1b7ba04b536619b3b7d48804d3bd8 Mon Sep 17 00:00:00 2001 From: gornekich Date: Thu, 1 Jun 2023 16:37:47 +0400 Subject: [PATCH 586/824] NFC: fix MFC timings (#2719) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * digital signal: add optimization * nfc test: more restrict tests * digital signal: build as separate library * digital signal: remove unused flags, format sources * digital signal: fix cflag name * target: fix build for f18 target Co-authored-by: あく --- applications/debug/unit_tests/nfc/nfc_test.c | 61 +++++++++++++++++--- firmware/targets/f18/api_symbols.csv | 21 ++++++- firmware/targets/f7/api_symbols.csv | 3 +- firmware/targets/f7/target.json | 1 + lib/ReadMe.md | 1 + lib/SConscript | 2 +- lib/digital_signal/SConscript | 20 +++++++ lib/digital_signal/digital_signal.c | 3 +- lib/misc.scons | 2 - 9 files changed, 98 insertions(+), 16 deletions(-) create mode 100644 lib/digital_signal/SConscript diff --git a/applications/debug/unit_tests/nfc/nfc_test.c b/applications/debug/unit_tests/nfc/nfc_test.c index 54bdd59097e..bc2f7887b1e 100644 --- a/applications/debug/unit_tests/nfc/nfc_test.c +++ b/applications/debug/unit_tests/nfc/nfc_test.c @@ -27,6 +27,12 @@ static const uint32_t nfc_test_file_version = 1; #define NFC_TEST_DATA_MAX_LEN 18 #define NFC_TETS_TIMINGS_MAX_LEN 1350 +// Maximum allowed time for buffer preparation to fit 500us nt message timeout +#define NFC_TEST_4_BYTE_BUILD_BUFFER_TIM_MAX (150) +#define NFC_TEST_16_BYTE_BUILD_BUFFER_TIM_MAX (640) +#define NFC_TEST_4_BYTE_BUILD_SIGNAL_TIM_MAX (110) +#define NFC_TEST_16_BYTE_BUILD_SIGNAL_TIM_MAX (440) + typedef struct { Storage* storage; NfcaSignal* signal; @@ -89,13 +95,13 @@ static bool nfc_test_read_signal_from_file(const char* file_name) { static bool nfc_test_digital_signal_test_encode( const char* file_name, - uint32_t encode_max_time, + uint32_t build_signal_max_time_us, + uint32_t build_buffer_max_time_us, uint32_t timing_tolerance, uint32_t timings_sum_tolerance) { furi_assert(nfc_test); bool success = false; - uint32_t time = 0; uint32_t dut_timings_sum = 0; uint32_t ref_timings_sum = 0; uint8_t parity[10] = {}; @@ -109,17 +115,37 @@ static bool nfc_test_digital_signal_test_encode( // Encode signal FURI_CRITICAL_ENTER(); - time = DWT->CYCCNT; + uint32_t time_start = DWT->CYCCNT; + nfca_signal_encode( nfc_test->signal, nfc_test->test_data, nfc_test->test_data_len * 8, parity); + + uint32_t time_signal = + (DWT->CYCCNT - time_start) / furi_hal_cortex_instructions_per_microsecond(); + + time_start = DWT->CYCCNT; + digital_signal_prepare_arr(nfc_test->signal->tx_signal); - time = (DWT->CYCCNT - time) / furi_hal_cortex_instructions_per_microsecond(); + + uint32_t time_buffer = + (DWT->CYCCNT - time_start) / furi_hal_cortex_instructions_per_microsecond(); FURI_CRITICAL_EXIT(); // Check timings - if(time > encode_max_time) { + if(time_signal > build_signal_max_time_us) { FURI_LOG_E( - TAG, "Encoding time: %ld us while accepted value: %ld us", time, encode_max_time); + TAG, + "Build signal time: %ld us while accepted value: %ld us", + time_signal, + build_signal_max_time_us); + break; + } + if(time_buffer > build_buffer_max_time_us) { + FURI_LOG_E( + TAG, + "Build buffer time: %ld us while accepted value: %ld us", + time_buffer, + build_buffer_max_time_us); break; } @@ -156,7 +182,16 @@ static bool nfc_test_digital_signal_test_encode( break; } - FURI_LOG_I(TAG, "Encoding time: %ld us. Acceptable time: %ld us", time, encode_max_time); + FURI_LOG_I( + TAG, + "Build signal time: %ld us. Acceptable time: %ld us", + time_signal, + build_signal_max_time_us); + FURI_LOG_I( + TAG, + "Build buffer time: %ld us. Acceptable time: %ld us", + time_buffer, + build_buffer_max_time_us); FURI_LOG_I( TAG, "Timings sum difference: %ld [1/64MHZ]. Acceptable difference: %ld [1/64MHz]", @@ -171,11 +206,19 @@ static bool nfc_test_digital_signal_test_encode( MU_TEST(nfc_digital_signal_test) { mu_assert( nfc_test_digital_signal_test_encode( - NFC_TEST_RESOURCES_DIR NFC_TEST_SIGNAL_SHORT_FILE, 500, 1, 37), + NFC_TEST_RESOURCES_DIR NFC_TEST_SIGNAL_SHORT_FILE, + NFC_TEST_4_BYTE_BUILD_SIGNAL_TIM_MAX, + NFC_TEST_4_BYTE_BUILD_BUFFER_TIM_MAX, + 1, + 37), "NFC short digital signal test failed\r\n"); mu_assert( nfc_test_digital_signal_test_encode( - NFC_TEST_RESOURCES_DIR NFC_TEST_SIGNAL_LONG_FILE, 2000, 1, 37), + NFC_TEST_RESOURCES_DIR NFC_TEST_SIGNAL_LONG_FILE, + NFC_TEST_16_BYTE_BUILD_SIGNAL_TIM_MAX, + NFC_TEST_16_BYTE_BUILD_BUFFER_TIM_MAX, + 1, + 37), "NFC long digital signal test failed\r\n"); } diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index 7f0dcebd52b..3c075e0d157 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,28.1,, +Version,+,28.2,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -80,6 +80,7 @@ Header,+,firmware/targets/furi_hal_include/furi_hal_usb_hid.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_usb_hid_u2f.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_version.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_vibro.h,, +Header,+,lib/digital_signal/digital_signal.h,, Header,+,lib/flipper_application/api_hashtable/api_hashtable.h,, Header,+,lib/flipper_application/api_hashtable/compilesort.hpp,, Header,+,lib/flipper_application/flipper_application.h,, @@ -617,6 +618,24 @@ Function,+,dialog_message_set_text,void,"DialogMessage*, const char*, uint8_t, u Function,+,dialog_message_show,DialogMessageButton,"DialogsApp*, const DialogMessage*" Function,+,dialog_message_show_storage_error,void,"DialogsApp*, const char*" Function,-,difftime,double,"time_t, time_t" +Function,-,digital_sequence_add,void,"DigitalSequence*, uint8_t" +Function,-,digital_sequence_alloc,DigitalSequence*,"uint32_t, const GpioPin*" +Function,-,digital_sequence_clear,void,DigitalSequence* +Function,-,digital_sequence_free,void,DigitalSequence* +Function,-,digital_sequence_send,_Bool,DigitalSequence* +Function,-,digital_sequence_set_sendtime,void,"DigitalSequence*, uint32_t" +Function,-,digital_sequence_set_signal,void,"DigitalSequence*, uint8_t, DigitalSignal*" +Function,-,digital_sequence_timebase_correction,void,"DigitalSequence*, float" +Function,-,digital_signal_add,void,"DigitalSignal*, uint32_t" +Function,-,digital_signal_add_pulse,void,"DigitalSignal*, uint32_t, _Bool" +Function,-,digital_signal_alloc,DigitalSignal*,uint32_t +Function,-,digital_signal_append,_Bool,"DigitalSignal*, DigitalSignal*" +Function,-,digital_signal_free,void,DigitalSignal* +Function,-,digital_signal_get_edge,uint32_t,"DigitalSignal*, uint32_t" +Function,-,digital_signal_get_edges_cnt,uint32_t,DigitalSignal* +Function,-,digital_signal_get_start_level,_Bool,DigitalSignal* +Function,-,digital_signal_prepare_arr,void,DigitalSignal* +Function,-,digital_signal_send,void,"DigitalSignal*, const GpioPin*" Function,-,diprintf,int,"int, const char*, ..." Function,+,dir_walk_alloc,DirWalk*,Storage* Function,+,dir_walk_close,void,DirWalk* diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 1259f0fce51..a9ce0c26ad3 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,28.1,, +Version,+,28.2,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -86,6 +86,7 @@ Header,+,firmware/targets/furi_hal_include/furi_hal_usb_hid.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_usb_hid_u2f.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_version.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_vibro.h,, +Header,+,lib/digital_signal/digital_signal.h,, Header,+,lib/flipper_application/api_hashtable/api_hashtable.h,, Header,+,lib/flipper_application/api_hashtable/compilesort.hpp,, Header,+,lib/flipper_application/flipper_application.h,, diff --git a/firmware/targets/f7/target.json b/firmware/targets/f7/target.json index c503644536e..e3dc78325e5 100644 --- a/firmware/targets/f7/target.json +++ b/firmware/targets/f7/target.json @@ -28,6 +28,7 @@ "flipperformat", "toolbox", "nfc", + "digital_signal", "pulse_reader", "microtar", "usb_stm32", diff --git a/lib/ReadMe.md b/lib/ReadMe.md index 93236b2677f..138bef2b343 100644 --- a/lib/ReadMe.md +++ b/lib/ReadMe.md @@ -27,6 +27,7 @@ - `nfc` - NFC library, used by NFC application - `one_wire` - OneWire library, used by iButton application - `print` - Tiny printf implementation +- `digital_signal` - Digital Signal library used by NFC for software implemented protocols - `pulse_reader` - Pulse Reader library used by NFC for software implemented protocols - `qrcode` - QR-Code library - `stm32wb_cmsis` - STM32WB series CMSIS headers, extends CMSIS Core diff --git a/lib/SConscript b/lib/SConscript index 8727746d818..495ba4bfe24 100644 --- a/lib/SConscript +++ b/lib/SConscript @@ -15,7 +15,6 @@ env.Append( Dir("u8g2"), Dir("update_util"), Dir("print"), - Dir("pulse_reader"), ], ) @@ -95,6 +94,7 @@ libs = env.BuildModules( "mbedtls", "subghz", "nfc", + "digital_signal", "pulse_reader", "appframe", "misc", diff --git a/lib/digital_signal/SConscript b/lib/digital_signal/SConscript new file mode 100644 index 00000000000..2ddf7a58b09 --- /dev/null +++ b/lib/digital_signal/SConscript @@ -0,0 +1,20 @@ +Import("env") + +env.Append( + CPPPATH=[ + "#/lib/digital_signal", + ], + SDK_HEADERS=[ + File("digital_signal.h"), + ], +) + +libenv = env.Clone(FW_LIB_NAME="digital_signal") +libenv.ApplyLibFlags() +libenv.Append(CCFLAGS=["-O3", "-funroll-loops", "-Ofast"]) + +sources = libenv.GlobRecursive("*.c*") + +lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources) +libenv.Install("${LIB_DIST_DIR}", lib) +Return("lib") diff --git a/lib/digital_signal/digital_signal.c b/lib/digital_signal/digital_signal.c index ab25458b518..94dbeeab92d 100644 --- a/lib/digital_signal/digital_signal.c +++ b/lib/digital_signal/digital_signal.c @@ -212,8 +212,7 @@ void digital_signal_prepare_arr(DigitalSignal* signal) { internals->reload_reg_entries = 0; for(size_t pos = 0; pos < signal->edge_cnt; pos++) { - uint32_t edge_scaled = (internals->factor * signal->edge_timings[pos]) / (1024 * 1024); - uint32_t pulse_duration = edge_scaled + internals->reload_reg_remainder; + uint32_t pulse_duration = signal->edge_timings[pos] + internals->reload_reg_remainder; if(pulse_duration < 10 || pulse_duration > 10000000) { FURI_LOG_D( TAG, diff --git a/lib/misc.scons b/lib/misc.scons index 1ff6e2fb0ce..10aa3f9d5a9 100644 --- a/lib/misc.scons +++ b/lib/misc.scons @@ -4,7 +4,6 @@ Import("env") env.Append( CPPPATH=[ - "#/lib/digital_signal", "#/lib/fnv1a_hash", "#/lib/heatshrink", "#/lib/micro-ecc", @@ -26,7 +25,6 @@ libenv.ApplyLibFlags() sources = [] libs_recurse = [ - "digital_signal", "micro-ecc", "u8g2", "update_util", From d9a9fa0c10ab9c2dd480c6abf2d90fbf53b9e289 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Sat, 3 Jun 2023 05:59:19 +0900 Subject: [PATCH 587/824] FuriHal: disable bus re-initialization on early init and extra asserts for AHB1,AHB2,AHB3 which must be left intact on entering to FUS (#2725) --- firmware/targets/f7/furi_hal/furi_hal_bt.c | 2 + firmware/targets/f7/furi_hal/furi_hal_bus.c | 44 ++++++++++----------- 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/firmware/targets/f7/furi_hal/furi_hal_bt.c b/firmware/targets/f7/furi_hal/furi_hal_bt.c index cec6b8204f8..3639094f79e 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_bt.c +++ b/firmware/targets/f7/furi_hal/furi_hal_bt.c @@ -85,6 +85,7 @@ void furi_hal_bt_init() { furi_hal_bus_enable(FuriHalBusIPCC); furi_hal_bus_enable(FuriHalBusAES2); furi_hal_bus_enable(FuriHalBusPKA); + furi_hal_bus_enable(FuriHalBusCRC); if(!furi_hal_bt_core2_mtx) { furi_hal_bt_core2_mtx = furi_mutex_alloc(FuriMutexTypeNormal); @@ -266,6 +267,7 @@ void furi_hal_bt_reinit() { furi_hal_bus_disable(FuriHalBusIPCC); furi_hal_bus_disable(FuriHalBusAES2); furi_hal_bus_disable(FuriHalBusPKA); + furi_hal_bus_disable(FuriHalBusCRC); FURI_LOG_I(TAG, "Start BT initialization"); furi_hal_bt_init(); diff --git a/firmware/targets/f7/furi_hal/furi_hal_bus.c b/firmware/targets/f7/furi_hal/furi_hal_bus.c index 0a07e9f9e6b..2c6f1f1ebc9 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_bus.c +++ b/firmware/targets/f7/furi_hal/furi_hal_bus.c @@ -36,10 +36,10 @@ LL_AHB3_GRP1_PERIPH_RNG | LL_AHB3_GRP1_PERIPH_HSEM | LL_AHB3_GRP1_PERIPH_IPCC) // LL_AHB3_GRP1_PERIPH_FLASH enabled by default -#define FURI_HAL_BUS_APB1_GRP1 \ - (LL_APB1_GRP1_PERIPH_TIM2 | LL_APB1_GRP1_PERIPH_LCD | LL_APB1_GRP1_PERIPH_RTCAPB | \ - LL_APB1_GRP1_PERIPH_SPI2 | LL_APB1_GRP1_PERIPH_I2C1 | LL_APB1_GRP1_PERIPH_I2C3 | \ - LL_APB1_GRP1_PERIPH_CRS | LL_APB1_GRP1_PERIPH_USB | LL_APB1_GRP1_PERIPH_LPTIM1) +#define FURI_HAL_BUS_APB1_GRP1 \ + (LL_APB1_GRP1_PERIPH_TIM2 | LL_APB1_GRP1_PERIPH_LCD | LL_APB1_GRP1_PERIPH_SPI2 | \ + LL_APB1_GRP1_PERIPH_I2C1 | LL_APB1_GRP1_PERIPH_I2C3 | LL_APB1_GRP1_PERIPH_CRS | \ + LL_APB1_GRP1_PERIPH_USB | LL_APB1_GRP1_PERIPH_LPTIM1) #define FURI_HAL_BUS_APB1_GRP2 (LL_APB1_GRP2_PERIPH_LPUART1 | LL_APB1_GRP2_PERIPH_LPTIM2) #define FURI_HAL_BUS_APB3_GRP1 (LL_APB3_GRP1_PERIPH_RF) @@ -86,14 +86,14 @@ FURI_HAL_BUS_RESET_DEASSERT(bus, value, grp) static const uint32_t furi_hal_bus[] = { - [FuriHalBusAHB1_GRP1] = FURI_HAL_BUS_AHB1_GRP1, + [FuriHalBusAHB1_GRP1] = FURI_HAL_BUS_IGNORE, [FuriHalBusDMA1] = LL_AHB1_GRP1_PERIPH_DMA1, [FuriHalBusDMA2] = LL_AHB1_GRP1_PERIPH_DMA2, [FuriHalBusDMAMUX1] = LL_AHB1_GRP1_PERIPH_DMAMUX1, [FuriHalBusCRC] = LL_AHB1_GRP1_PERIPH_CRC, [FuriHalBusTSC] = LL_AHB1_GRP1_PERIPH_TSC, - [FuriHalBusAHB2_GRP1] = FURI_HAL_BUS_AHB2_GRP1, + [FuriHalBusAHB2_GRP1] = FURI_HAL_BUS_IGNORE, [FuriHalBusGPIOA] = LL_AHB2_GRP1_PERIPH_GPIOA, [FuriHalBusGPIOB] = LL_AHB2_GRP1_PERIPH_GPIOB, [FuriHalBusGPIOC] = LL_AHB2_GRP1_PERIPH_GPIOC, @@ -105,7 +105,7 @@ static const uint32_t furi_hal_bus[] = { #endif [FuriHalBusAES1] = LL_AHB2_GRP1_PERIPH_AES1, - [FuriHalBusAHB3_GRP1] = FURI_HAL_BUS_AHB3_GRP1, + [FuriHalBusAHB3_GRP1] = FURI_HAL_BUS_IGNORE, [FuriHalBusQUADSPI] = LL_AHB3_GRP1_PERIPH_QUADSPI, [FuriHalBusPKA] = LL_AHB3_GRP1_PERIPH_PKA, [FuriHalBusAES2] = LL_AHB3_GRP1_PERIPH_AES2, @@ -146,9 +146,9 @@ static const uint32_t furi_hal_bus[] = { void furi_hal_bus_init_early() { FURI_CRITICAL_ENTER(); - FURI_HAL_BUS_PERIPH_DISABLE(AHB1, FURI_HAL_BUS_AHB1_GRP1, 1); - FURI_HAL_BUS_PERIPH_DISABLE(AHB2, FURI_HAL_BUS_AHB2_GRP1, 1); - FURI_HAL_BUS_PERIPH_DISABLE(AHB3, FURI_HAL_BUS_AHB3_GRP1, 1); + // FURI_HAL_BUS_PERIPH_DISABLE(AHB1, FURI_HAL_BUS_AHB1_GRP1, 1); + // FURI_HAL_BUS_PERIPH_DISABLE(AHB2, FURI_HAL_BUS_AHB2_GRP1, 1); + // FURI_HAL_BUS_PERIPH_DISABLE(AHB3, FURI_HAL_BUS_AHB3_GRP1, 1); FURI_HAL_BUS_PERIPH_DISABLE(APB1, FURI_HAL_BUS_APB1_GRP1, 1); FURI_HAL_BUS_PERIPH_DISABLE(APB1, FURI_HAL_BUS_APB1_GRP2, 2); FURI_HAL_BUS_PERIPH_DISABLE(APB2, FURI_HAL_BUS_APB2_GRP1, 1); @@ -161,9 +161,9 @@ void furi_hal_bus_init_early() { void furi_hal_bus_deinit_early() { FURI_CRITICAL_ENTER(); - FURI_HAL_BUS_PERIPH_ENABLE(AHB1, FURI_HAL_BUS_AHB1_GRP1, 1); - FURI_HAL_BUS_PERIPH_ENABLE(AHB2, FURI_HAL_BUS_AHB2_GRP1, 1); - FURI_HAL_BUS_PERIPH_ENABLE(AHB3, FURI_HAL_BUS_AHB3_GRP1, 1); + // FURI_HAL_BUS_PERIPH_ENABLE(AHB1, FURI_HAL_BUS_AHB1_GRP1, 1); + // FURI_HAL_BUS_PERIPH_ENABLE(AHB2, FURI_HAL_BUS_AHB2_GRP1, 1); + // FURI_HAL_BUS_PERIPH_ENABLE(AHB3, FURI_HAL_BUS_AHB3_GRP1, 1); FURI_HAL_BUS_PERIPH_ENABLE(APB1, FURI_HAL_BUS_APB1_GRP1, 1); FURI_HAL_BUS_PERIPH_ENABLE(APB1, FURI_HAL_BUS_APB1_GRP2, 2); FURI_HAL_BUS_PERIPH_ENABLE(APB2, FURI_HAL_BUS_APB2_GRP1, 1); @@ -182,13 +182,13 @@ void furi_hal_bus_enable(FuriHalBus bus) { FURI_CRITICAL_ENTER(); if(bus < FuriHalBusAHB2_GRP1) { - furi_check(FURI_HAL_BUS_IS_PERIPH_DISABLED(AHB1, value)); + // furi_check(FURI_HAL_BUS_IS_PERIPH_DISABLED(AHB1, value)); FURI_HAL_BUS_PERIPH_ENABLE(AHB1, value, 1); } else if(bus < FuriHalBusAHB3_GRP1) { - furi_check(FURI_HAL_BUS_IS_PERIPH_DISABLED(AHB2, value)); + // furi_check(FURI_HAL_BUS_IS_PERIPH_DISABLED(AHB2, value)); FURI_HAL_BUS_PERIPH_ENABLE(AHB2, value, 1); } else if(bus < FuriHalBusAPB1_GRP1) { - furi_check(FURI_HAL_BUS_IS_PERIPH_DISABLED(AHB3, value)); + // furi_check(FURI_HAL_BUS_IS_PERIPH_DISABLED(AHB3, value)); FURI_HAL_BUS_PERIPH_ENABLE(AHB3, value, 1); } else if(bus < FuriHalBusAPB1_GRP2) { furi_check(FURI_HAL_BUS_IS_PERIPH_DISABLED(APB1, value, 1)); @@ -215,13 +215,13 @@ void furi_hal_bus_reset(FuriHalBus bus) { FURI_CRITICAL_ENTER(); if(bus < FuriHalBusAHB2_GRP1) { - furi_check(FURI_HAL_BUS_IS_PERIPH_ENABLED(AHB1, value)); + // furi_check(FURI_HAL_BUS_IS_PERIPH_ENABLED(AHB1, value)); FURI_HAL_BUS_PERIPH_RESET(AHB1, value, 1); } else if(bus < FuriHalBusAHB3_GRP1) { - furi_check(FURI_HAL_BUS_IS_PERIPH_ENABLED(AHB2, value)); + // furi_check(FURI_HAL_BUS_IS_PERIPH_ENABLED(AHB2, value)); FURI_HAL_BUS_PERIPH_RESET(AHB2, value, 1); } else if(bus < FuriHalBusAPB1_GRP1) { - furi_check(FURI_HAL_BUS_IS_PERIPH_ENABLED(AHB3, value)); + // furi_check(FURI_HAL_BUS_IS_PERIPH_ENABLED(AHB3, value)); FURI_HAL_BUS_PERIPH_RESET(AHB3, value, 1); } else if(bus < FuriHalBusAPB1_GRP2) { furi_check(FURI_HAL_BUS_IS_PERIPH_ENABLED(APB1, value, 1)); @@ -248,13 +248,13 @@ void furi_hal_bus_disable(FuriHalBus bus) { FURI_CRITICAL_ENTER(); if(bus < FuriHalBusAHB2_GRP1) { - furi_check(FURI_HAL_BUS_IS_PERIPH_ENABLED(AHB1, value)); + // furi_check(FURI_HAL_BUS_IS_PERIPH_ENABLED(AHB1, value)); FURI_HAL_BUS_PERIPH_DISABLE(AHB1, value, 1); } else if(bus < FuriHalBusAHB3_GRP1) { - furi_check(FURI_HAL_BUS_IS_PERIPH_ENABLED(AHB2, value)); + // furi_check(FURI_HAL_BUS_IS_PERIPH_ENABLED(AHB2, value)); FURI_HAL_BUS_PERIPH_DISABLE(AHB2, value, 1); } else if(bus < FuriHalBusAPB1_GRP1) { - furi_check(FURI_HAL_BUS_IS_PERIPH_ENABLED(AHB3, value)); + // furi_check(FURI_HAL_BUS_IS_PERIPH_ENABLED(AHB3, value)); FURI_HAL_BUS_PERIPH_DISABLE(AHB3, value, 1); } else if(bus < FuriHalBusAPB1_GRP2) { furi_check(FURI_HAL_BUS_IS_PERIPH_ENABLED(APB1, value, 1)); From 72ad22bb91e0277394ddf13e8ead026776224455 Mon Sep 17 00:00:00 2001 From: Max Andreev Date: Mon, 5 Jun 2023 14:25:43 +0400 Subject: [PATCH 588/824] [DEVOPS-18]: Add map file parser, mariadb inserter (#2732) --- .github/workflows/build.yml | 42 +++--- scripts/map_mariadb_insert.py | 139 +++++++++++++++++++ scripts/map_parser.py | 251 ++++++++++++++++++++++++++++++++++ 3 files changed, 414 insertions(+), 18 deletions(-) create mode 100755 scripts/map_mariadb_insert.py create mode 100755 scripts/map_parser.py diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ca603a64a7c..e8b76a02cc6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -100,25 +100,31 @@ jobs: cp build/f7-firmware-*/firmware.elf map_analyser_files/firmware.elf cp ${{ github.event_path }} map_analyser_files/event.json - - name: 'Upload map analyser files to storage' + - name: 'Analyse map file' if: ${{ !github.event.pull_request.head.repo.fork }} - uses: prewk/s3-cp-action@v2 - with: - aws_s3_endpoint: "${{ secrets.MAP_REPORT_AWS_ENDPOINT }}" - aws_access_key_id: "${{ secrets.MAP_REPORT_AWS_ACCESS_KEY }}" - aws_secret_access_key: "${{ secrets.MAP_REPORT_AWS_SECRET_KEY }}" - source: "./map_analyser_files/" - dest: "s3://${{ secrets.MAP_REPORT_AWS_BUCKET }}/${{steps.names.outputs.random_hash}}" - flags: "--recursive --acl public-read" - - - name: 'Trigger map file reporter' - if: ${{ !github.event.pull_request.head.repo.fork }} - uses: peter-evans/repository-dispatch@v2 - with: - repository: flipperdevices/flipper-map-reporter - token: ${{ secrets.REPOSITORY_DISPATCH_TOKEN }} - event-type: map-file-analyse - client-payload: '{"random_hash": "${{steps.names.outputs.random_hash}}", "event_type": "${{steps.names.outputs.event_type}}"}' + run: | + source scripts/toolchain/fbtenv.sh + get_size() + { + SECTION="$1"; + arm-none-eabi-size \ + -A map_analyser_files/firmware.elf \ + | grep "^$SECTION" | awk '{print $2}' + } + export BSS_SIZE="$(get_size ".bss")" + export TEXT_SIZE="$(get_size ".text")" + export RODATA_SIZE="$(get_size ".rodata")" + export DATA_SIZE="$(get_size ".data")" + export FREE_FLASH_SIZE="$(get_size ".free_flash")" + python3 -m pip install mariadb==1.1.6 cxxfilt==0.3.0 + python3 scripts/map_parser.py map_analyser_files/firmware.elf.map map_analyser_files/firmware.elf.map.all + python3 scripts/map_mariadb_insert.py \ + ${{ secrets.AMAP_MARIADB_USER }} \ + ${{ secrets.AMAP_MARIADB_PASSWORD }} \ + ${{ secrets.AMAP_MARIADB_HOST }} \ + ${{ secrets.AMAP_MARIADB_PORT }} \ + ${{ secrets.AMAP_MARIADB_DATABASE }} \ + map_analyser_files/firmware.elf.map.all - name: 'Upload artifacts to update server' if: ${{ !github.event.pull_request.head.repo.fork }} diff --git a/scripts/map_mariadb_insert.py b/scripts/map_mariadb_insert.py new file mode 100755 index 00000000000..a4c9ed5c788 --- /dev/null +++ b/scripts/map_mariadb_insert.py @@ -0,0 +1,139 @@ +#!/usr/bin/env python3 + +# Requiremets: +# mariadb==1.1.6 + +from datetime import datetime +import argparse +import mariadb +import sys +import os + + +def parseArgs(): + parser = argparse.ArgumentParser() + parser.add_argument("db_user", help="MariaDB user") + parser.add_argument("db_pass", help="MariaDB password") + parser.add_argument("db_host", help="MariaDB hostname") + parser.add_argument("db_port", type=int, help="MariaDB port") + parser.add_argument("db_name", help="MariaDB database") + parser.add_argument("report_file", help="Report file(.map.all)") + args = parser.parse_args() + return args + + +def mariadbConnect(args): + try: + conn = mariadb.connect( + user=args.db_user, + password=args.db_pass, + host=args.db_host, + port=args.db_port, + database=args.db_name, + ) + except mariadb.Error as e: + print(f"Error connecting to MariaDB: {e}") + sys.exit(1) + return conn + + +def parseEnv(): + outArr = [] + outArr.append(datetime.now().strftime("%Y-%m-%d %H:%M:%S")) + outArr.append(os.getenv("COMMIT_HASH", default=None)) + outArr.append(os.getenv("COMMIT_MSG", default=None)) + outArr.append(os.getenv("BRANCH_NAME", default=None)) + outArr.append(os.getenv("BSS_SIZE", default=None)) + outArr.append(os.getenv("TEXT_SIZE", default=None)) + outArr.append(os.getenv("RODATA_SIZE", default=None)) + outArr.append(os.getenv("DATA_SIZE", default=None)) + outArr.append(os.getenv("FREE_FLASH_SIZE", default=None)) + outArr.append(os.getenv("PULL_ID", default=None)) + outArr.append(os.getenv("PULL_NAME", default=None)) + return outArr + + +def createTables(cur, conn): + headerTable = "CREATE TABLE IF NOT EXISTS `header` ( \ + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, \ + `datetime` datetime NOT NULL, \ + `commit` varchar(40) NOT NULL, \ + `commit_msg` text NOT NULL, \ + `branch_name` text NOT NULL, \ + `bss_size` int(10) unsigned NOT NULL, \ + `text_size` int(10) unsigned NOT NULL, \ + `rodata_size` int(10) unsigned NOT NULL, \ + `data_size` int(10) unsigned NOT NULL, \ + `free_flash_size` int(10) unsigned NOT NULL, \ + `pullrequest_id` int(10) unsigned DEFAULT NULL, \ + `pullrequest_name` text DEFAULT NULL, \ + PRIMARY KEY (`id`), \ + KEY `header_id_index` (`id`) )" + dataTable = "CREATE TABLE IF NOT EXISTS `data` ( \ + `header_id` int(10) unsigned NOT NULL, \ + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, \ + `section` text NOT NULL, \ + `address` text NOT NULL, \ + `size` int(10) unsigned NOT NULL, \ + `name` text NOT NULL, \ + `lib` text NOT NULL, \ + `obj_name` text NOT NULL, \ + PRIMARY KEY (`id`), \ + KEY `data_id_index` (`id`), \ + KEY `data_header_id_index` (`header_id`), \ + CONSTRAINT `data_header_id_foreign` FOREIGN KEY (`header_id`) REFERENCES `header` (`id`) )" + cur.execute(headerTable) + cur.execute(dataTable) + conn.commit() + + +def insertHeader(data, cur, conn): + query = "INSERT INTO `header` ( \ + datetime, commit, commit_msg, branch_name, bss_size, text_size, \ + rodata_size, data_size, free_flash_size, pullrequest_id, pullrequest_name) \ + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" + cur.execute(query, data) + conn.commit() + return cur.lastrowid + + +def parseFile(fileObj, headerID): + arr = [] + fileLines = fileObj.readlines() + for line in fileLines: + lineArr = [] + tempLineArr = line.split("\t") + lineArr.append(headerID) + lineArr.append(tempLineArr[0]) # section + lineArr.append(int(tempLineArr[2], 16)) # address hex + lineArr.append(int(tempLineArr[3])) # size + lineArr.append(tempLineArr[4]) # name + lineArr.append(tempLineArr[5]) # lib + lineArr.append(tempLineArr[6]) # obj_name + arr.append(tuple(lineArr)) + return arr + + +def insertData(data, cur, conn): + query = "INSERT INTO `data` ( \ + header_id, section, address, size, \ + name, lib, obj_name) \ + VALUES (?, ?, ?, ?, ? ,?, ?)" + cur.executemany(query, data) + conn.commit() + + +def main(): + args = parseArgs() + dbConn = mariadbConnect(args) + reportFile = open(args.report_file) + dbCurs = dbConn.cursor() + createTables(dbCurs, dbConn) + headerID = insertHeader(parseEnv(), dbCurs, dbConn) + insertData(parseFile(reportFile, headerID), dbCurs, dbConn) + reportFile.close() + dbCurs.close() + + +if __name__ == "__main__": + main() diff --git a/scripts/map_parser.py b/scripts/map_parser.py new file mode 100755 index 00000000000..c0c34e3d1d3 --- /dev/null +++ b/scripts/map_parser.py @@ -0,0 +1,251 @@ +#!/usr/bin/env python3 + +# Requiremets: +# cxxfilt==0.3.0 + +import sys +import re +import os +from typing import TextIO +from cxxfilt import demangle + + +class Objectfile: + def __init__(self, section: str, offset: int, size: int, comment: str): + self.section = section.strip() + self.offset = offset + self.size = size + self.path = (None, None) + self.basepath = None + + if comment: + self.path = re.match(r"^(.+?)(?:\(([^\)]+)\))?$", comment).groups() + self.basepath = os.path.basename(self.path[0]) + + self.children = [] + + def __repr__(self) -> str: + return f"" + + +def update_children_size(children: list[list], subsection_size: int) -> list: + # set subsection size to an only child + if len(children) == 1: + children[0][1] = subsection_size + return children + + rest_size = subsection_size + + for index in range(1, len(children)): + if rest_size > 0: + # current size = current address - previous child address + child_size = children[index][0] - children[index - 1][0] + rest_size -= child_size + children[index - 1][1] = child_size + + # if there is rest size, set it to the last child element + if rest_size > 0: + children[-1][1] = rest_size + + return children + + +def parse_sections(file_name: str) -> list: + """ + Quick&Dirty parsing for GNU ld’s linker map output, needs LANG=C, because + some messages are localized. + """ + + sections = [] + with open(file_name, "r") as file: + # skip until memory map is found + found = False + + while True: + line = file.readline() + if not line: + break + if line.strip() == "Memory Configuration": + found = True + break + + if not found: + raise Exception(f"Memory configuration is not found in the{input_file}") + + # long section names result in a linebreak afterwards + sectionre = re.compile( + "(?P

.+?|.{14,}\n)[ ]+0x(?P[0-9a-f]+)[ ]+0x(?P[0-9a-f]+)(?:[ ]+(?P.+))?\n+", + re.I, + ) + subsectionre = re.compile( + "[ ]{16}0x(?P[0-9a-f]+)[ ]+(?P.+)\n+", re.I + ) + s = file.read() + pos = 0 + + while True: + m = sectionre.match(s, pos) + if not m: + # skip that line + try: + nextpos = s.index("\n", pos) + 1 + pos = nextpos + continue + except ValueError: + break + + pos = m.end() + section = m.group("section") + v = m.group("offset") + offset = int(v, 16) if v is not None else None + v = m.group("size") + size = int(v, 16) if v is not None else None + comment = m.group("comment") + + if section != "*default*" and size > 0: + of = Objectfile(section, offset, size, comment) + + if section.startswith(" "): + children = [] + sections[-1].children.append(of) + + while True: + m = subsectionre.match(s, pos) + if not m: + break + pos = m.end() + offset, function = m.groups() + offset = int(offset, 16) + if sections and sections[-1].children: + children.append([offset, 0, function]) + + if children: + children = update_children_size( + children=children, subsection_size=of.size + ) + + sections[-1].children[-1].children.extend(children) + + else: + sections.append(of) + + return sections + + +def get_subsection_name(section_name: str, subsection: Objectfile) -> str: + subsection_split_names = subsection.section.split(".") + if subsection.section.startswith("."): + subsection_split_names = subsection_split_names[1:] + + return ( + f".{subsection_split_names[1]}" + if len(subsection_split_names) > 2 + else section_name + ) + + +def write_subsection( + section_name: str, + subsection_name: str, + address: str, + size: int, + demangled_name: str, + module_name: str, + file_name: str, + mangled_name: str, + write_file_object: TextIO, +) -> None: + write_file_object.write( + f"{section_name}\t" + f"{subsection_name}\t" + f"{address}\t" + f"{size}\t" + f"{demangled_name}\t" + f"{module_name}\t" + f"{file_name}\t" + f"{mangled_name}\n" + ) + + +def save_subsection( + section_name: str, subsection: Objectfile, write_file_object: TextIO +) -> None: + subsection_name = get_subsection_name(section_name, subsection) + module_name = subsection.path[0] + file_name = subsection.path[1] + + if not file_name: + file_name, module_name = module_name, "" + + if not subsection.children: + address = f"{subsection.offset:x}" + size = subsection.size + mangled_name = ( + "" + if subsection.section == section_name + else subsection.section.split(".")[-1] + ) + demangled_name = demangle(mangled_name) if mangled_name else mangled_name + + write_subsection( + section_name=section_name, + subsection_name=subsection_name, + address=address, + size=size, + demangled_name=demangled_name, + module_name=module_name, + file_name=file_name, + mangled_name=mangled_name, + write_file_object=write_file_object, + ) + return + + for subsection_child in subsection.children: + address = f"{subsection_child[0]:x}" + size = subsection_child[1] + mangled_name = subsection_child[2] + demangled_name = demangle(mangled_name) + + write_subsection( + section_name=section_name, + subsection_name=subsection_name, + address=address, + size=size, + demangled_name=demangled_name, + module_name=module_name, + file_name=file_name, + mangled_name=mangled_name, + write_file_object=write_file_object, + ) + + +def save_section(section: Objectfile, write_file_object: TextIO) -> None: + section_name = section.section + for subsection in section.children: + save_subsection( + section_name=section_name, + subsection=subsection, + write_file_object=write_file_object, + ) + + +def save_parsed_data(parsed_data: list[Objectfile], output_file_name: str) -> None: + with open(output_file_name, "w") as write_file_object: + for section in parsed_data: + if section.children: + save_section(section=section, write_file_object=write_file_object) + + +if __name__ == "__main__": + if len(sys.argv) < 3: + raise Exception(f"Usage: {sys.argv[0]} ") + + input_file = sys.argv[1] + output_file = sys.argv[2] + + parsed_sections = parse_sections(input_file) + + if parsed_sections is None: + raise Exception(f"Memory configuration is not {input_file}") + + save_parsed_data(parsed_sections, output_file) From 1e512b6add48bb8eef84962e8f71173041691ece Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Wed, 7 Jun 2023 02:15:50 +0900 Subject: [PATCH 589/824] [FL-3293] FuriHal: add system setting to device info, bump device info version (#2736) --- firmware/targets/f7/furi_hal/furi_hal_info.c | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/firmware/targets/f7/furi_hal/furi_hal_info.c b/firmware/targets/f7/furi_hal/furi_hal_info.c index 47672c97a33..a2c9232c058 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_info.c +++ b/firmware/targets/f7/furi_hal/furi_hal_info.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -23,10 +24,10 @@ void furi_hal_info_get(PropertyValueCallback out, char sep, void* context) { // Device Info version if(sep == '.') { property_value_out(&property_context, NULL, 2, "format", "major", "3"); - property_value_out(&property_context, NULL, 2, "format", "minor", "1"); + property_value_out(&property_context, NULL, 2, "format", "minor", "2"); } else { property_value_out(&property_context, NULL, 3, "device", "info", "major", "2"); - property_value_out(&property_context, NULL, 3, "device", "info", "minor", "2"); + property_value_out(&property_context, NULL, 3, "device", "info", "minor", "3"); } // Model name @@ -297,6 +298,18 @@ void furi_hal_info_get(PropertyValueCallback out, char sep, void* context) { property_value_out(&property_context, NULL, 2, "radio", "alive", "false"); } + property_value_out( + &property_context, + "%u", + 2, + "system", + "debug", + furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)); + property_value_out( + &property_context, "%u", 3, "system", "heap", "track", furi_hal_rtc_get_heap_track_mode()); + property_value_out( + &property_context, "%u", 3, "system", "log", "level", furi_hal_rtc_get_log_level()); + property_value_out( &property_context, "%u", 3, "protobuf", "version", "major", PROTOBUF_MAJOR_VERSION); property_context.last = true; From 76c70bdf2c33437aa3d62205fb37d0a5fd66b7f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Wed, 7 Jun 2023 02:46:01 +0900 Subject: [PATCH 590/824] [FL-3316] Settings: add contrast adjustment (#2737) Co-authored-by: hedger --- .../services/notification/notification.h | 2 + .../services/notification/notification_app.c | 55 ++++++++++++------- .../services/notification/notification_app.h | 3 +- .../notification/notification_messages.c | 9 +++ .../notification/notification_messages.h | 6 ++ .../notification_settings_app.c | 44 +++++++++++++++ firmware/targets/f18/api_symbols.csv | 5 +- firmware/targets/f7/api_symbols.csv | 5 +- lib/toolbox/value_index.c | 13 +++++ lib/toolbox/value_index.h | 13 +++++ lib/u8g2/u8g2_glue.c | 20 ++++++- lib/u8g2/u8g2_glue.h | 2 + 12 files changed, 152 insertions(+), 25 deletions(-) diff --git a/applications/services/notification/notification.h b/applications/services/notification/notification.h index b38620f0f5f..0e1c07e5df6 100644 --- a/applications/services/notification/notification.h +++ b/applications/services/notification/notification.h @@ -75,6 +75,8 @@ typedef enum { NotificationMessageTypeForceDisplayBrightnessSetting, NotificationMessageTypeLedBrightnessSettingApply, + + NotificationMessageTypeLcdContrastUpdate, } NotificationMessageType; typedef struct { diff --git a/applications/services/notification/notification_app.c b/applications/services/notification/notification_app.c index f91a73f321d..2f947fe8a00 100644 --- a/applications/services/notification/notification_app.c +++ b/applications/services/notification/notification_app.c @@ -3,6 +3,9 @@ #include #include #include +#include +#include + #include "notification.h" #include "notification_messages.h" #include "notification_app.h" @@ -20,14 +23,14 @@ static const uint8_t reset_sound_mask = 1 << 4; static const uint8_t reset_display_mask = 1 << 5; static const uint8_t reset_blink_mask = 1 << 6; -void notification_vibro_on(bool force); -void notification_vibro_off(); -void notification_sound_on(float freq, float volume, bool force); -void notification_sound_off(); +static void notification_vibro_on(bool force); +static void notification_vibro_off(); +static void notification_sound_on(float freq, float volume, bool force); +static void notification_sound_off(); -uint8_t notification_settings_get_display_brightness(NotificationApp* app, uint8_t value); -uint8_t notification_settings_get_rgb_led_brightness(NotificationApp* app, uint8_t value); -uint32_t notification_settings_display_off_delay_ticks(NotificationApp* app); +static uint8_t notification_settings_get_display_brightness(NotificationApp* app, uint8_t value); +static uint8_t notification_settings_get_rgb_led_brightness(NotificationApp* app, uint8_t value); +static uint32_t notification_settings_display_off_delay_ticks(NotificationApp* app); void notification_message_save_settings(NotificationApp* app) { NotificationAppMessage m = { @@ -39,7 +42,8 @@ void notification_message_save_settings(NotificationApp* app) { }; // internal layer -void notification_apply_internal_led_layer(NotificationLedLayer* layer, uint8_t layer_value) { +static void + notification_apply_internal_led_layer(NotificationLedLayer* layer, uint8_t layer_value) { furi_assert(layer); furi_assert(layer->index < LayerMAX); @@ -52,7 +56,13 @@ void notification_apply_internal_led_layer(NotificationLedLayer* layer, uint8_t } } -bool notification_is_any_led_layer_internal_and_not_empty(NotificationApp* app) { +static void notification_apply_lcd_contrast(NotificationApp* app) { + Gui* gui = furi_record_open(RECORD_GUI); + u8x8_d_st756x_set_contrast(&gui->canvas->fb.u8x8, app->settings.contrast); + furi_record_close(RECORD_GUI); +} + +static bool notification_is_any_led_layer_internal_and_not_empty(NotificationApp* app) { bool result = false; if((app->led[0].index == LayerInternal) || (app->led[1].index == LayerInternal) || (app->led[2].index == LayerInternal)) { @@ -67,7 +77,7 @@ bool notification_is_any_led_layer_internal_and_not_empty(NotificationApp* app) } // notification layer -void notification_apply_notification_led_layer( +static void notification_apply_notification_led_layer( NotificationLedLayer* layer, const uint8_t layer_value) { furi_assert(layer); @@ -81,7 +91,7 @@ void notification_apply_notification_led_layer( furi_hal_light_set(layer->light, layer->value[LayerNotification]); } -void notification_reset_notification_led_layer(NotificationLedLayer* layer) { +static void notification_reset_notification_led_layer(NotificationLedLayer* layer) { furi_assert(layer); furi_assert(layer->index < LayerMAX); @@ -94,7 +104,7 @@ void notification_reset_notification_led_layer(NotificationLedLayer* layer) { furi_hal_light_set(layer->light, layer->value[LayerInternal]); } -void notification_reset_notification_layer(NotificationApp* app, uint8_t reset_mask) { +static void notification_reset_notification_layer(NotificationApp* app, uint8_t reset_mask) { if(reset_mask & reset_blink_mask) { furi_hal_light_blink_stop(); } @@ -130,28 +140,28 @@ uint8_t notification_settings_get_display_brightness(NotificationApp* app, uint8 return (value * app->settings.display_brightness); } -uint8_t notification_settings_get_rgb_led_brightness(NotificationApp* app, uint8_t value) { +static uint8_t notification_settings_get_rgb_led_brightness(NotificationApp* app, uint8_t value) { return (value * app->settings.led_brightness); } -uint32_t notification_settings_display_off_delay_ticks(NotificationApp* app) { +static uint32_t notification_settings_display_off_delay_ticks(NotificationApp* app) { return ( (float)(app->settings.display_off_delay_ms) / (1000.0f / furi_kernel_get_tick_frequency())); } // generics -void notification_vibro_on(bool force) { +static void notification_vibro_on(bool force) { if(!furi_hal_rtc_is_flag_set(FuriHalRtcFlagStealthMode) || force) { furi_hal_vibro_on(true); } } -void notification_vibro_off() { +static void notification_vibro_off() { furi_hal_vibro_on(false); } -void notification_sound_on(float freq, float volume, bool force) { +static void notification_sound_on(float freq, float volume, bool force) { if(!furi_hal_rtc_is_flag_set(FuriHalRtcFlagStealthMode) || force) { if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(30)) { furi_hal_speaker_start(freq, volume); @@ -159,7 +169,7 @@ void notification_sound_on(float freq, float volume, bool force) { } } -void notification_sound_off() { +static void notification_sound_off() { if(furi_hal_speaker_is_mine()) { furi_hal_speaker_stop(); furi_hal_speaker_release(); @@ -174,7 +184,7 @@ static void notification_display_timer(void* ctx) { } // message processing -void notification_process_notification_message( +static void notification_process_notification_message( NotificationApp* app, NotificationAppMessage* message) { uint32_t notification_message_index = 0; @@ -333,6 +343,9 @@ void notification_process_notification_message( reset_mask |= reset_green_mask; reset_mask |= reset_blue_mask; break; + case NotificationMessageTypeLcdContrastUpdate: + notification_apply_lcd_contrast(app); + break; } notification_message_index++; notification_message = (*message->sequence)[notification_message_index]; @@ -361,7 +374,8 @@ void notification_process_notification_message( } } -void notification_process_internal_message(NotificationApp* app, NotificationAppMessage* message) { +static void + notification_process_internal_message(NotificationApp* app, NotificationAppMessage* message) { uint32_t notification_message_index = 0; const NotificationMessage* notification_message; notification_message = (*message->sequence)[notification_message_index]; @@ -548,6 +562,7 @@ int32_t notification_srv(void* p) { notification_apply_internal_led_layer(&app->led[0], 0x00); notification_apply_internal_led_layer(&app->led[1], 0x00); notification_apply_internal_led_layer(&app->led[2], 0x00); + notification_apply_lcd_contrast(app); furi_record_create(RECORD_NOTIFICATION, app); diff --git a/applications/services/notification/notification_app.h b/applications/services/notification/notification_app.h index 88194bfbd53..cacc17ffb0c 100644 --- a/applications/services/notification/notification_app.h +++ b/applications/services/notification/notification_app.h @@ -32,7 +32,7 @@ typedef struct { Light light; } NotificationLedLayer; -#define NOTIFICATION_SETTINGS_VERSION 0x01 +#define NOTIFICATION_SETTINGS_VERSION 0x02 #define NOTIFICATION_SETTINGS_PATH INT_PATH(NOTIFICATION_SETTINGS_FILE_NAME) typedef struct { @@ -41,6 +41,7 @@ typedef struct { float led_brightness; float speaker_volume; uint32_t display_off_delay_ms; + int8_t contrast; bool vibro_on; } NotificationSettings; diff --git a/applications/services/notification/notification_messages.c b/applications/services/notification/notification_messages.c index 51f3533c32d..28ec327c6e6 100644 --- a/applications/services/notification/notification_messages.c +++ b/applications/services/notification/notification_messages.c @@ -197,6 +197,10 @@ const NotificationMessage message_force_display_brightness_setting_1f = { .data.forced_settings.display_brightness = 1.0f, }; +const NotificationMessage message_lcd_contrast_update = { + .type = NotificationMessageTypeLcdContrastUpdate, +}; + /****************************** Message sequences ******************************/ // Reset @@ -566,3 +570,8 @@ const NotificationSequence sequence_audiovisual_alert = { &message_vibro_off, NULL, }; + +const NotificationSequence sequence_lcd_contrast_update = { + &message_lcd_contrast_update, + NULL, +}; diff --git a/applications/services/notification/notification_messages.h b/applications/services/notification/notification_messages.h index 1007969176c..d87cf74f4ee 100644 --- a/applications/services/notification/notification_messages.h +++ b/applications/services/notification/notification_messages.h @@ -63,6 +63,9 @@ extern const NotificationMessage message_force_vibro_setting_on; extern const NotificationMessage message_force_vibro_setting_off; extern const NotificationMessage message_force_display_brightness_setting_1f; +// LCD Messages +extern const NotificationMessage message_lcd_contrast_update; + /****************************** Message sequences ******************************/ // Reset @@ -138,6 +141,9 @@ extern const NotificationSequence sequence_success; extern const NotificationSequence sequence_error; extern const NotificationSequence sequence_audiovisual_alert; +// LCD +extern const NotificationSequence sequence_lcd_contrast_update; + #ifdef __cplusplus } #endif diff --git a/applications/settings/notification_settings/notification_settings_app.c b/applications/settings/notification_settings/notification_settings_app.c index 8efbc5e0844..450aaee144f 100644 --- a/applications/settings/notification_settings/notification_settings_app.c +++ b/applications/settings/notification_settings/notification_settings_app.c @@ -20,6 +20,34 @@ static const NotificationSequence sequence_note_c = { NULL, }; +#define CONTRAST_COUNT 11 +const char* const contrast_text[CONTRAST_COUNT] = { + "-5", + "-4", + "-3", + "-2", + "-1", + "0", + "+1", + "+2", + "+3", + "+4", + "+5", +}; +const int32_t contrast_value[CONTRAST_COUNT] = { + -5, + -4, + -3, + -2, + -1, + 0, + 1, + 2, + 3, + 4, + 5, +}; + #define BACKLIGHT_COUNT 5 const char* const backlight_text[BACKLIGHT_COUNT] = { "0%", @@ -64,6 +92,15 @@ const char* const vibro_text[VIBRO_COUNT] = { }; const bool vibro_value[VIBRO_COUNT] = {false, true}; +static void contrast_changed(VariableItem* item) { + NotificationAppSettings* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + variable_item_set_current_value_text(item, contrast_text[index]); + app->notification->settings.contrast = contrast_value[index]; + notification_message(app->notification, &sequence_lcd_contrast_update); +} + static void backlight_changed(VariableItem* item) { NotificationAppSettings* app = variable_item_get_context(item); uint8_t index = variable_item_get_current_value_index(item); @@ -136,6 +173,13 @@ static NotificationAppSettings* alloc_settings() { VariableItem* item; uint8_t value_index; + item = variable_item_list_add( + app->variable_item_list, "LCD Contrast", CONTRAST_COUNT, contrast_changed, app); + value_index = + value_index_int32(app->notification->settings.contrast, contrast_value, CONTRAST_COUNT); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, contrast_text[value_index]); + item = variable_item_list_add( app->variable_item_list, "LCD Backlight", BACKLIGHT_COUNT, backlight_changed, app); value_index = value_index_float( diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index 3c075e0d157..f3c4e31d75b 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,28.2,, +Version,+,28.3,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -1999,6 +1999,7 @@ Function,+,validator_is_file_callback,_Bool,"const char*, FuriString*, void*" Function,+,validator_is_file_free,void,ValidatorIsFile* Function,+,value_index_bool,uint8_t,"const _Bool, const _Bool[], uint8_t" Function,+,value_index_float,uint8_t,"const float, const float[], uint8_t" +Function,+,value_index_int32,uint8_t,"const int32_t, const int32_t[], uint8_t" Function,+,value_index_uint32,uint8_t,"const uint32_t, const uint32_t[], uint8_t" Function,+,variable_item_get_context,void*,VariableItem* Function,+,variable_item_get_current_value_index,uint8_t,VariableItem* @@ -2259,6 +2260,7 @@ Variable,+,message_force_vibro_setting_off,const NotificationMessage, Variable,+,message_force_vibro_setting_on,const NotificationMessage, Variable,+,message_green_0,const NotificationMessage, Variable,+,message_green_255,const NotificationMessage, +Variable,+,message_lcd_contrast_update,const NotificationMessage, Variable,+,message_note_a0,const NotificationMessage, Variable,+,message_note_a1,const NotificationMessage, Variable,+,message_note_a2,const NotificationMessage, @@ -2402,6 +2404,7 @@ Variable,+,sequence_display_backlight_off_delay_1000,const NotificationSequence, Variable,+,sequence_display_backlight_on,const NotificationSequence, Variable,+,sequence_double_vibro,const NotificationSequence, Variable,+,sequence_error,const NotificationSequence, +Variable,+,sequence_lcd_contrast_update,const NotificationSequence, Variable,+,sequence_not_charging,const NotificationSequence, Variable,+,sequence_reset_blue,const NotificationSequence, Variable,+,sequence_reset_display,const NotificationSequence, diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index a9ce0c26ad3..a8708ce8c55 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,28.2,, +Version,+,28.3,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -2923,6 +2923,7 @@ Function,+,validator_is_file_callback,_Bool,"const char*, FuriString*, void*" Function,+,validator_is_file_free,void,ValidatorIsFile* Function,+,value_index_bool,uint8_t,"const _Bool, const _Bool[], uint8_t" Function,+,value_index_float,uint8_t,"const float, const float[], uint8_t" +Function,+,value_index_int32,uint8_t,"const int32_t, const int32_t[], uint8_t" Function,+,value_index_uint32,uint8_t,"const uint32_t, const uint32_t[], uint8_t" Function,+,variable_item_get_context,void*,VariableItem* Function,+,variable_item_get_current_value_index,uint8_t,VariableItem* @@ -3192,6 +3193,7 @@ Variable,+,message_force_vibro_setting_off,const NotificationMessage, Variable,+,message_force_vibro_setting_on,const NotificationMessage, Variable,+,message_green_0,const NotificationMessage, Variable,+,message_green_255,const NotificationMessage, +Variable,+,message_lcd_contrast_update,const NotificationMessage, Variable,+,message_note_a0,const NotificationMessage, Variable,+,message_note_a1,const NotificationMessage, Variable,+,message_note_a2,const NotificationMessage, @@ -3335,6 +3337,7 @@ Variable,+,sequence_display_backlight_off_delay_1000,const NotificationSequence, Variable,+,sequence_display_backlight_on,const NotificationSequence, Variable,+,sequence_double_vibro,const NotificationSequence, Variable,+,sequence_error,const NotificationSequence, +Variable,+,sequence_lcd_contrast_update,const NotificationSequence, Variable,+,sequence_not_charging,const NotificationSequence, Variable,+,sequence_reset_blue,const NotificationSequence, Variable,+,sequence_reset_display,const NotificationSequence, diff --git a/lib/toolbox/value_index.c b/lib/toolbox/value_index.c index e0745e43410..5ec0fb96287 100644 --- a/lib/toolbox/value_index.c +++ b/lib/toolbox/value_index.c @@ -1,5 +1,18 @@ #include "value_index.h" +uint8_t value_index_int32(const int32_t value, const int32_t values[], uint8_t values_count) { + int64_t last_value = INT64_MIN; + uint8_t index = 0; + for(uint8_t i = 0; i < values_count; i++) { + if((value >= last_value) && (value <= values[i])) { + index = i; + break; + } + last_value = values[i]; + } + return index; +} + uint8_t value_index_uint32(const uint32_t value, const uint32_t values[], uint8_t values_count) { int64_t last_value = INT64_MIN; uint8_t index = 0; diff --git a/lib/toolbox/value_index.h b/lib/toolbox/value_index.h index 9459292a75b..5aa768e3d1d 100644 --- a/lib/toolbox/value_index.h +++ b/lib/toolbox/value_index.h @@ -7,6 +7,19 @@ extern "C" { #endif +/** Get the index of a int32_t array element which is closest to the given value. + * + * Returned index corresponds to the first element found. + * If no suitable elements were found, the function returns 0. + * + * @param value value to be searched. + * @param values pointer to the array to perform the search in. + * @param values_count array size. + * + * @return value's index. + */ +uint8_t value_index_int32(const int32_t value, const int32_t values[], uint8_t values_count); + /** Get the index of a uint32_t array element which is closest to the given value. * * Returned index corresponds to the first element found. diff --git a/lib/u8g2/u8g2_glue.c b/lib/u8g2/u8g2_glue.c index 17a702b50fc..0d4879bce5d 100644 --- a/lib/u8g2/u8g2_glue.c +++ b/lib/u8g2/u8g2_glue.c @@ -2,6 +2,9 @@ #include +#define CONTRAST_ERC 32 +#define CONTRAST_MGG 31 + uint8_t u8g2_gpio_and_delay_stm32(u8x8_t* u8x8, uint8_t msg, uint8_t arg_int, void* arg_ptr) { UNUSED(u8x8); UNUSED(arg_ptr); @@ -207,6 +210,19 @@ void u8x8_d_st756x_init(u8x8_t* u8x8, uint8_t contrast, uint8_t regulation_ratio u8x8_cad_EndTransfer(u8x8); } +void u8x8_d_st756x_set_contrast(u8x8_t* u8x8, int8_t contrast_offset) { + uint8_t contrast = (furi_hal_version_get_hw_display() == FuriHalVersionDisplayMgg) ? + CONTRAST_MGG : + CONTRAST_ERC; + contrast += contrast_offset; + contrast = contrast & 0b00111111; + + u8x8_cad_StartTransfer(u8x8); + u8x8_cad_SendCmd(u8x8, ST756X_CMD_SET_EV); + u8x8_cad_SendArg(u8x8, contrast); + u8x8_cad_EndTransfer(u8x8); +} + uint8_t u8x8_d_st756x_flipper(u8x8_t* u8x8, uint8_t msg, uint8_t arg_int, void* arg_ptr) { /* call common procedure first and handle messages there */ if(u8x8_d_st756x_common(u8x8, msg, arg_int, arg_ptr) == 0) { @@ -225,7 +241,7 @@ uint8_t u8x8_d_st756x_flipper(u8x8_t* u8x8, uint8_t msg, uint8_t arg_int, void* * RR = 10 / ((1 - (63 - 32) / 162) * 2.1) ~= 5.88 is 6 (0b110) * Bias = 1/9 (false) */ - u8x8_d_st756x_init(u8x8, 31, 0b110, false); + u8x8_d_st756x_init(u8x8, CONTRAST_MGG, 0b110, false); } else { /* ERC v1(ST7565) and v2(ST7567) * EV = 33 @@ -233,7 +249,7 @@ uint8_t u8x8_d_st756x_flipper(u8x8_t* u8x8, uint8_t msg, uint8_t arg_int, void* * RR = 9.3 / ((1 - (63 - 32) / 162) * 2.1) ~= 5.47 is 5.5 (0b101) * Bias = 1/9 (false) */ - u8x8_d_st756x_init(u8x8, 32, 0b101, false); + u8x8_d_st756x_init(u8x8, CONTRAST_ERC, 0b101, false); } break; case U8X8_MSG_DISPLAY_SET_FLIP_MODE: diff --git a/lib/u8g2/u8g2_glue.h b/lib/u8g2/u8g2_glue.h index 91ba2980a5c..af236279ece 100644 --- a/lib/u8g2/u8g2_glue.h +++ b/lib/u8g2/u8g2_glue.h @@ -14,3 +14,5 @@ void u8g2_Setup_st756x_flipper( u8x8_msg_cb gpio_and_delay_cb); void u8x8_d_st756x_init(u8x8_t* u8x8, uint8_t contrast, uint8_t regulation_ratio, bool bias); + +void u8x8_d_st756x_set_contrast(u8x8_t* u8x8, int8_t contrast_offset); From b0555d96e989c6b86e67d557fafde060ebe9f134 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Wed, 7 Jun 2023 02:52:49 +0900 Subject: [PATCH 591/824] [FL-3213] f7: add PB9 to debug pins (#2738) Co-authored-by: hedger --- firmware/targets/f7/furi_hal/furi_hal_resources.c | 1 + 1 file changed, 1 insertion(+) diff --git a/firmware/targets/f7/furi_hal/furi_hal_resources.c b/firmware/targets/f7/furi_hal/furi_hal_resources.c index 561cef08a79..34b26b831cf 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_resources.c +++ b/firmware/targets/f7/furi_hal/furi_hal_resources.c @@ -81,6 +81,7 @@ const GpioPinRecord gpio_pins[] = { /* Dangerous pins, may damage hardware */ {.pin = &gpio_usart_rx, .name = "PB7", .debug = true}, {.pin = &gpio_speaker, .name = "PB8", .debug = true}, + {.pin = &gpio_infrared_tx, .name = "PB9", .debug = true}, }; const size_t gpio_pins_count = sizeof(gpio_pins) / sizeof(GpioPinRecord); From 61394b8be4dc36c41ea860e08c4887e8a5c0d3e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Wed, 7 Jun 2023 03:04:27 +0900 Subject: [PATCH 592/824] [FL-3352] Dolphin: new animation (#2735) Co-authored-by: hedger --- assets/dolphin/external/L2_Dj_128x64/frame_0.png | Bin 0 -> 1640 bytes assets/dolphin/external/L2_Dj_128x64/frame_1.png | Bin 0 -> 1687 bytes .../dolphin/external/L2_Dj_128x64/frame_10.png | Bin 0 -> 1630 bytes .../dolphin/external/L2_Dj_128x64/frame_11.png | Bin 0 -> 1660 bytes .../dolphin/external/L2_Dj_128x64/frame_12.png | Bin 0 -> 1637 bytes .../dolphin/external/L2_Dj_128x64/frame_13.png | Bin 0 -> 1654 bytes .../dolphin/external/L2_Dj_128x64/frame_14.png | Bin 0 -> 1667 bytes .../dolphin/external/L2_Dj_128x64/frame_15.png | Bin 0 -> 1344 bytes .../dolphin/external/L2_Dj_128x64/frame_16.png | Bin 0 -> 1251 bytes .../dolphin/external/L2_Dj_128x64/frame_17.png | Bin 0 -> 1292 bytes .../dolphin/external/L2_Dj_128x64/frame_18.png | Bin 0 -> 1498 bytes .../dolphin/external/L2_Dj_128x64/frame_19.png | Bin 0 -> 1530 bytes assets/dolphin/external/L2_Dj_128x64/frame_2.png | Bin 0 -> 1726 bytes .../dolphin/external/L2_Dj_128x64/frame_20.png | Bin 0 -> 1698 bytes .../dolphin/external/L2_Dj_128x64/frame_21.png | Bin 0 -> 1665 bytes .../dolphin/external/L2_Dj_128x64/frame_22.png | Bin 0 -> 1809 bytes .../dolphin/external/L2_Dj_128x64/frame_23.png | Bin 0 -> 1775 bytes .../dolphin/external/L2_Dj_128x64/frame_24.png | Bin 0 -> 1758 bytes .../dolphin/external/L2_Dj_128x64/frame_25.png | Bin 0 -> 1725 bytes .../dolphin/external/L2_Dj_128x64/frame_26.png | Bin 0 -> 1835 bytes .../dolphin/external/L2_Dj_128x64/frame_27.png | Bin 0 -> 1759 bytes .../dolphin/external/L2_Dj_128x64/frame_28.png | Bin 0 -> 1462 bytes .../dolphin/external/L2_Dj_128x64/frame_29.png | Bin 0 -> 1407 bytes assets/dolphin/external/L2_Dj_128x64/frame_3.png | Bin 0 -> 1777 bytes .../dolphin/external/L2_Dj_128x64/frame_30.png | Bin 0 -> 1408 bytes .../dolphin/external/L2_Dj_128x64/frame_31.png | Bin 0 -> 1404 bytes .../dolphin/external/L2_Dj_128x64/frame_32.png | Bin 0 -> 1327 bytes .../dolphin/external/L2_Dj_128x64/frame_33.png | Bin 0 -> 1306 bytes .../dolphin/external/L2_Dj_128x64/frame_34.png | Bin 0 -> 1341 bytes .../dolphin/external/L2_Dj_128x64/frame_35.png | Bin 0 -> 1255 bytes .../dolphin/external/L2_Dj_128x64/frame_36.png | Bin 0 -> 1059 bytes assets/dolphin/external/L2_Dj_128x64/frame_4.png | Bin 0 -> 1727 bytes assets/dolphin/external/L2_Dj_128x64/frame_5.png | Bin 0 -> 1641 bytes assets/dolphin/external/L2_Dj_128x64/frame_6.png | Bin 0 -> 1635 bytes assets/dolphin/external/L2_Dj_128x64/frame_7.png | Bin 0 -> 1588 bytes assets/dolphin/external/L2_Dj_128x64/frame_8.png | Bin 0 -> 1608 bytes assets/dolphin/external/L2_Dj_128x64/frame_9.png | Bin 0 -> 1610 bytes assets/dolphin/external/L2_Dj_128x64/meta.txt | 14 ++++++++++++++ assets/dolphin/external/manifest.txt | 7 +++++++ 39 files changed, 21 insertions(+) create mode 100644 assets/dolphin/external/L2_Dj_128x64/frame_0.png create mode 100644 assets/dolphin/external/L2_Dj_128x64/frame_1.png create mode 100644 assets/dolphin/external/L2_Dj_128x64/frame_10.png create mode 100644 assets/dolphin/external/L2_Dj_128x64/frame_11.png create mode 100644 assets/dolphin/external/L2_Dj_128x64/frame_12.png create mode 100644 assets/dolphin/external/L2_Dj_128x64/frame_13.png create mode 100644 assets/dolphin/external/L2_Dj_128x64/frame_14.png create mode 100644 assets/dolphin/external/L2_Dj_128x64/frame_15.png create mode 100644 assets/dolphin/external/L2_Dj_128x64/frame_16.png create mode 100644 assets/dolphin/external/L2_Dj_128x64/frame_17.png create mode 100644 assets/dolphin/external/L2_Dj_128x64/frame_18.png create mode 100644 assets/dolphin/external/L2_Dj_128x64/frame_19.png create mode 100644 assets/dolphin/external/L2_Dj_128x64/frame_2.png create mode 100644 assets/dolphin/external/L2_Dj_128x64/frame_20.png create mode 100644 assets/dolphin/external/L2_Dj_128x64/frame_21.png create mode 100644 assets/dolphin/external/L2_Dj_128x64/frame_22.png create mode 100644 assets/dolphin/external/L2_Dj_128x64/frame_23.png create mode 100644 assets/dolphin/external/L2_Dj_128x64/frame_24.png create mode 100644 assets/dolphin/external/L2_Dj_128x64/frame_25.png create mode 100644 assets/dolphin/external/L2_Dj_128x64/frame_26.png create mode 100644 assets/dolphin/external/L2_Dj_128x64/frame_27.png create mode 100644 assets/dolphin/external/L2_Dj_128x64/frame_28.png create mode 100644 assets/dolphin/external/L2_Dj_128x64/frame_29.png create mode 100644 assets/dolphin/external/L2_Dj_128x64/frame_3.png create mode 100644 assets/dolphin/external/L2_Dj_128x64/frame_30.png create mode 100644 assets/dolphin/external/L2_Dj_128x64/frame_31.png create mode 100644 assets/dolphin/external/L2_Dj_128x64/frame_32.png create mode 100644 assets/dolphin/external/L2_Dj_128x64/frame_33.png create mode 100644 assets/dolphin/external/L2_Dj_128x64/frame_34.png create mode 100644 assets/dolphin/external/L2_Dj_128x64/frame_35.png create mode 100644 assets/dolphin/external/L2_Dj_128x64/frame_36.png create mode 100644 assets/dolphin/external/L2_Dj_128x64/frame_4.png create mode 100644 assets/dolphin/external/L2_Dj_128x64/frame_5.png create mode 100644 assets/dolphin/external/L2_Dj_128x64/frame_6.png create mode 100644 assets/dolphin/external/L2_Dj_128x64/frame_7.png create mode 100644 assets/dolphin/external/L2_Dj_128x64/frame_8.png create mode 100644 assets/dolphin/external/L2_Dj_128x64/frame_9.png create mode 100644 assets/dolphin/external/L2_Dj_128x64/meta.txt diff --git a/assets/dolphin/external/L2_Dj_128x64/frame_0.png b/assets/dolphin/external/L2_Dj_128x64/frame_0.png new file mode 100644 index 0000000000000000000000000000000000000000..95f72f901a5e03e63373957cbb188c9ae4673162 GIT binary patch literal 1640 zcmaJ=eNfY87!O~VNeuA$d_XjMR_@J904APtJopZ9>n2_@i7=U z7bDo5qK$XJUPju&uN5^?pmsbByVGQPN$hY<6bqD1?xCG>Ooqzsc5iND-YzkV`(MUu zy+r^u)WlR3N~EUfj2LD#=ya%2uU9Bh zW1=F3L=`@4y2B;X4uf5Kfw#kBWmel>Bf*-eP~sNUg5V^jLI@Q}A{L4z5lWGWsgxvv zlZ;h5foESS-*~G63rNH1Q==wv5?9fj0#Tuu0wI+Ij*tw6A_QqAX(dfqX^TQC2NLDp z&UvMid?wJI-tEiT03R=>i*taH62R;jKKImvxto-^G}{Q)(b3Vbd+KgLkUuD{F5A;P zl-J?7g@(qAj(+>#&CXml|0Zr_S82ZHZeDvy*Ju;jJ-hsezA0>E#l3uYha5U^Z>Hrj;ykkbVy( zL46TIbF*1n{J{pT?v6xo9<)U43Tx~)`z5sFVb6jaPj47mAKvhutpZ*b7P-a!N8;9@ z3ro9Gg5bIvmscG*Qq}j>>KVNk`=Zg*T_+_c-ONz+{$JhD?ojH!F6!FjbH_(KGvo4T zNzE+3jDH7L?fGc0wxi?flfyyRHyoX}sd35a<>mwOs(`D<6810nBs+(le86u<`ih#^ zy}u>s^$RmR4?>Tb!eblzdgovIYz}*P@xhY*>qgA)p<$=CdWoYxzNapsLOtW%ntDsQ z4(XcR_@lDhoOz~JcYDjDsQpb_D|=EW^|a>Zb?vTdi!SxwV{T1quXIbck(b-2s<>lA z=A4kxkjeloaz{{gXXn+Y0edb!OMh$%zf~Jw|8dQ2`@z*mMF^XA;pRVs=Vx#4^z56% zZH|gqDAqKcY&;qg@kMJ$LtJNE;i=`Vk5=CdHl3_0S3S)2d> literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Dj_128x64/frame_1.png b/assets/dolphin/external/L2_Dj_128x64/frame_1.png new file mode 100644 index 0000000000000000000000000000000000000000..32e13541d83eb0571414851957a4b8a5647a8c03 GIT binary patch literal 1687 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 F(|@~CN2UM( literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Dj_128x64/frame_11.png b/assets/dolphin/external/L2_Dj_128x64/frame_11.png new file mode 100644 index 0000000000000000000000000000000000000000..eca4a1296ee3dedf9ebd3b010c7be8763eaaa44d GIT binary patch literal 1660 zcmaJ?eM}Q)7{5{wsDfm1laGv?g=~Cy?e%)Jca>^OTLfY&D~+Nvlk448D)z3l2egQz z&P`+lb&HF*5tVG6ZU%;lafqm>bCdXm&S7L5V{Aj6_<=^iIbm0z&OeNoyL;dFx##yh zzvnAgn{QeiC5jgT01%a%qc`)bkAE}~LjL_%#5NS?f3o1<(va*E4De$9i*@h7#onB$x(skEY^LQ)}@u&y;y8II!t8wdjN8j#z|aD)e9+(|(T zdfH98>`uZsFu3yd|`-ndX_A6&C63R}g zNCl2rQA`ODq!ouyxeSL08ID0Ti6W>>VO3Bt6}0mryh4W~a-|N<&eCSfWCp!f3mbGg zxdJw1$g?n59>nG{ZjN9`I_TTZ`<}+iUy4=fTr|P4E(^=93wA<&G0U;;V%7;Fs00O5 zj09ZYE@pshv5veQDF#%C<$5)DPfr$!W1Y1VI&SiC}zV51%cWKt6VJQ zDM|yK^IRtdMSOb(w$EmRKRlZ*n&C&v#m`RjuGTz$?q=jKu^9MuVq(I7eb27|5aP|% zYb~DMKO62ao8Vc74Sf^-H=EP3ttUd7empeZ(3|R&+hf0Q;(q`5)48`byTd&P(!KSar-7QgDb=rJ z&T`&d`M9wt0jL?BCvFSNU#0tZ>{H)9Anj>VXyYf=wq5Vog}8LpwTUB};}U-})+H7T zyCc2c9;bG~psDNE`jSGAgkRkqo!QyGaG<8BCY%ur<(!Py7+XdCH-GNfn*7VCuWO4S zW?$NYWptGfwe?u|!gQD4GPjN0FE>PQ@LxoifZAKo*1Jp5wymV%+~(7C3= zug)%Nm)_oJr5ZLj?B5mH6a|b_e7;%|e<5CDB7|^Q^al=e`ddFmJQ3vi1m(_9>yz(d z_m21ku{QINt;uwuD_q^`&-l;zS;6s_ZjuPu#^3Jhie;4 zyLPa-gK5c88&ma)<23R0+<~1tYBdc)&5FT<^wL4?>*-}9<3k^IS%z2iSJmAJsg5Z) z=-GC=_CRD(3>tlY{eqT(^ME0G`&$14Z_}mH{MDggW7cQ?+;Du^+dJH8x4jJ)Ua2{> z>~g(N{eyW!S9Mu|rl%ztC@&cbi+K=%9jh01KLkF$DySH{zaTx*GV;{|W z(mqGi4mm_>8_+bWlaD8 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Dj_128x64/frame_12.png b/assets/dolphin/external/L2_Dj_128x64/frame_12.png new file mode 100644 index 0000000000000000000000000000000000000000..5f92e47fdd7cdffb93e2cb52b64dd87858071c6b GIT binary patch literal 1637 zcmaJ>dr;GM98U}4iz153Ly=JC&`Fv!P1~fSyxO9@S=M1UVS3s$Noj#LAq}>8J|gGy zrW?~u-E?z$zHcWJ(Xs6We9hwyH@Tfo*ii8?<=n3w_Ddp2oF7Az&gJk~7)NuB}nY1YMnRv%f$ z6f-1UQ`N>~13=U|ucJgLvCTD5tWQOTbyOiAC!ql#J2%9UR0Sh|E~d=uH!AzSX;Fe+ z+NhkbwIMdn#CW`Omh#NJr9}>EX$3{l%G?|D~;}^&f<3u(8^i0oS1JryX=bTvALAFSW}{O2qN2UDfzlDU z3&ZsgNx29J)2IoER1-MFP#B7-buJx^&=EV&!|O~0s?nS9!h-xlwc29N&qpjKlSYSF zw3-4O(L}ITe?TDp6ch37m3$9l^)JL4OguvhEbn01%19^JJ*>b6JS+#Im)P=Yp6esi$rpIx}MbJ1zXtZj*4#!ZO zaw~`N^fT!jYE?-hg~3JG7;qFv^(3Q#^oUvm;W`Y3aEd@640q$Cj>O!gOQVD(ig37d zp6R5BNNUgU_UUX$ho{rU_@$BJrP)#7ubq|VF2+9BVUgB>fq{LUySf2D?y{Qm9igsY zx3_IRgN!ow4h-}jIOpQsRJJK?UC=?6Mvx9J;F@;a+2+2oO1 zzFu|8wr5WPm{rl>cBR_o?VslQ;wJ2+GX8Bz5(}ap-f{vhE4e9VYWf%j&fiQpdyJY` zOU$$%*xg^6bf)SndlFoK{Crc!jXgK~$GZ9)*^TqA3itBjoK&zWy7utr?zXq&2mdHJ zxxBe<)8);DE#u-x^glR%B{nZXFAt4KzbMW(T^ftZpO_NMM+YaZPEL9#-!QKd(N3|?l$+N920Vr zFP%*C{FK(bA_hDPFNyz4eOL>;GC6hA)+a3)7y5K@qDQ2c_;&$X6VUwqHlTK-%;Ou| zfQ)WaC?*2STDRRjQ@UF=ry+4_IhW8oL3Dfqsm|GNR+p#JT(`^_Q)>Mgd}BGEQg`r! zhDCXn)?hKFtqTFdw>`ZNj>NZL+dsWDkQU`!d}o1jBL$*qqDNK!5nhZ?zPej}$m- F{{s^?M>YTe literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Dj_128x64/frame_13.png b/assets/dolphin/external/L2_Dj_128x64/frame_13.png new file mode 100644 index 0000000000000000000000000000000000000000..1b1017ce259d7b511bfb2d6c565cbe8350ca929f GIT binary patch literal 1654 zcmaJ?dr;GM98X(TD+i*^sccSTIXtJPP1CeZdK6j-NN*H-(8=R)Gfh$`&?cmT7Iu#6 zx%0&-6BVc898TSx;Hg99oDZhsd~ABCi0_-5!tmxD+t_?;Se62H{^6NR^85Y1`F_6N z&-Zm%ZO>W|8~t`P006Pp3?f@xgW_XW$i#P#V!cgV;)S$4A%}Gdev)SZ3(Yzi(CQ(J zm~4imtIE$aNdO>Q>2~A^dA3X=#d=g^SVtA`a3UH2l2QU3NtH4J=wyoAUX!x_+fz!= zO`DXtIvZ@`Qki0RMm5jmRA)J;>QV})l_|+!Qotx0@Gt@i20Ue6pD|!k4%;<~`>CoRG!Tarm{Q(;wl)TU|0hha1{k3FzQ5T#x)N_U_`5i5%GpJh*5(Y5fmJ~ zl%hwTb{VsY^ihA}$)qe61kR{d`~7~EU#nvIBDDs`@vsd9fkX|+SLGGR0Oa*0L@W@D zkK)~&;AXvG*dpmmSS)Z_YN}Qb zr|GowF<2YHTD?Aj^ioX3w_Ef*iq*dsYfR-CQeb%p%a%nt!CuS?tgo2mKm=8x;A|U7 zxxHb@d*LdL1Who!yON>PdDa6Cr`hO!1qGK*t4Ex$6GAZD0HFp9gPb^xLAX|@G3YTA z!6=t<6i>gDzL8cHC1My{gpCnHFvLJITF3xvv=F985eTDj7(y`@M(Rn_MLM-gwMbDN z?wpr8DIyZvGrWB<8{*-`bTM9Wq ztw&pOz2Czz1pefd5QH{lj9a@_|D7_VI224w&N(ydvp>vR>5|1K zPA)D6LQyN|o{oI!{$+p9>*{yNexKO+bO$#6cz(h~cv^Ew5882L_OS=V(n-Lf-uP9a zRbc!50(0nn-M!Ymi<&N-Q0&rVOg;Hi_d%D=UK^8d?5nsUZV&00oqu@dl6=44_kExD zKJR_sOUjLz^FGOa40A zApvPyvSe9;0XDePCc(98qBlk|m?0vJ6Hrjqy+*E`=d049h_kF15l4j9L&IWuFToFrtvb2zNtrL@h_v z2nvo}63(N?W>=et^f7i0Y}cQ9lj!B~r&l#A69$r5fy zYNKsxs}jd7D5io)s|ANpg&c=SIgUY;6-7|F(xSA%wy>QS;W4=i!L>wcda5>EF4qxS zEv!pRQz&6wf+7`z6=AI2)#^x1Qws?O#M!Xq?%Z%IiyVW>>^H#9CtWE2;BZHH%gY{ax9> zC)HKLoj@ynAyGVdW}pYi-+d>lv*q>s_e8*!d-?!-{^s#Mkx@3S4ghlgou8e}8w!e( zo|e7I+wsUVyr^6^#C3}9o~_>}zP;?9_57YcelI$Alo{-4NMmufIf zueUd?-I3BUNI+E6Kz>!b)aW;L$IX)5-hF!t0+qd*p?LS1!jfsT z?gkSAZId@$l73gx`nIv0f9xsCyL*^Th_Kfh9_(zX<2iyA9m3NwRBY6_*jWKz*%zXtfZu9Xi~;@8j}*ap0E4 r14aC?w7aOMg&+C!?=^uYKr=29SRN~Sr=Tku5B(POx=f-uHE-R2HCSU* literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Dj_128x64/frame_15.png b/assets/dolphin/external/L2_Dj_128x64/frame_15.png new file mode 100644 index 0000000000000000000000000000000000000000..9b796498c0c48c2c9125e98dbfea9c0c812345ef GIT binary patch literal 1344 zcmaJ>YfKzf6rKgPB1nVPmPk@M9co&mv-4zjX2KR0cA?vJaiwdam25kYyD))yI5V&d z{%}KUjP(aL#-t@ptfo~HZSg^DLSwYs z@0s(R^PO|gnb#6+trca}We9>)#P&qmq0GUjv9uVz7fMIsP(0yAJKgf-Jl&v>fno4Ks|yKzp5oW-Ol(vQ8D#^M^_8p1m2;$+-v20hxIAsg%;YD+3Zy^5e>4ZG3$tOyDo8(r(j?6gBn@wzqD6`oX%=0* zFc{HR(_(w1Wi=k$g|HsiwM2r*WHNya6EN*=f)WJ5^Fh-%w7{J~!@?YQmWe;VsrJIO%{ z5beM*`)viDNxHTGhV^@JAkPr85udd+cra35#8moqV7ReJ2!me&s-}txC-5@M2XRS} z1)OCl0hcI&$AQAqEXB#3N~#4v>*0ASNDJXeOG{I@g`%R7aF~oXH!~a=tz(*ak||(g zhT}?x0t&G;7<&~PeIQnBwt?iDcG5Ka3Z0PXFntSWt`@PAkGGP9+w4`#|5U23UWM4^GX_9#jE#~Z>3eC z2Bu*Z*oZvM(?JO^c#xzRoaa~?=M{m(Sw786oW!OjnZXE1k?=a_zD_ErV0(JYwb_81 zwdn!|94Q;l&SPbN`~>H2Lt9@`cUJ=oZ-7!G~sv4Z*RGdpLt(;QFk`(M1HN3K3S3x z?h*foSF4IX?k2lNh@xByx$Xb`^^t|eft<`8dnruc+pu@~*vV4j;ATZxtVWa@+h?~b zAKm-(&eCFaY&_K3@Yn9CrTJH8o$=WevIB~D-@hQ>q4&yD>PQtJ6~on$xmA#|9Td*{sg zzVn@P&v~ypxqrvDC$|9rc9cu`8d*E!$qXjQ``X|_g)C1*g{kO(XGJX(V30LE9aCio zHE<0hbMEK`JO)7Gq+OqirYaMf;W;ddeb~0+6Epy0>9&uIX&g~HZrE;${`1RoG-aD9 z`nAytU-5IeX_w{$d|-aEZp=>`(4^C^P-AV45I8tORNI+xL#>^n*LXFukIfuSt+_>$k#Zo)oQUViS>d8CqM{e4pC%?2NTY@5o$ATIMQRt9rDs^R}-Z0a4>=X}i7aOlkj200UJ zqz5MTdvKuGk*LvL4Qz5SXeRF&vkrEnay~_qKdfn+nxQ~dm(?VL3>`AEBtQlUP-U2{M}m zxqGNOQ7@2nb#?X3Pam(4-PLkFTW|mT$CYn}&QseH)w`?5=P&=b=aZ!$sL6>UOx}E? ze`{Z3VDQ4?q0HHi+qZJ!SMYAf&9*X!GiMjm)XTRaej`)8)PMSg+Ye_-nOl!u1k1Ru zZ-4*Z>EFQa{^vT4Q^$6_4{FQC<*#3o|8x3-qRjV+yf zhlH=c+dVen?|J(B1@P>hcfzX&hlV?U{WG}u`Ri{dmbV-{HVi&{Jo7>NE_-Fyjpr|2 zTL4D}sArCE_upE&xcB$dO&9!q{oi4KUt%P4Zs1<$BuIdp=XWj~{U~dZTtT@onO_<| Ga^gRXW|=zx literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Dj_128x64/frame_17.png b/assets/dolphin/external/L2_Dj_128x64/frame_17.png new file mode 100644 index 0000000000000000000000000000000000000000..80863f0b6935ebcdc353deb69a6b5cf4d0c4d4ad GIT binary patch literal 1292 zcmaJ>Z)n_P7|*p`cWu3O-I%%|8ZwZ%HJ9W~E|~=*oE27&jfM8X;EgN@(wi zDFRHO&Nw4+p4zXX5s%dHq>@&r(Imqp;?XFbh{af*PK4M&fo7Z7lEZ|nc-Xltt%@l=YD+WXAJXoq~g(Bb#dwD4_l7W{^N3W<Y-MP88j#+;%e)*wV`r+Q!^6&d*!PZ}nesb;Ey{|`4O@H^E zwHM}3|8eWz{nxu@dbl(Br3>c^cWWQC9SPidt0z+1Om{ACD`0t~x literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Dj_128x64/frame_18.png b/assets/dolphin/external/L2_Dj_128x64/frame_18.png new file mode 100644 index 0000000000000000000000000000000000000000..b4527bc83306f43a5ee138258cee037f073fc276 GIT binary patch literal 1498 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 BgtY(w literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Dj_128x64/frame_20.png b/assets/dolphin/external/L2_Dj_128x64/frame_20.png new file mode 100644 index 0000000000000000000000000000000000000000..f63904f29b2f44112bcec74fe738a623b2d9f1c8 GIT binary patch literal 1698 zcmaJ?dr;GM91joWAtK1;K-?K3r>LY&)6k}cN=r+z&LYE3!P|s1O&^0cr46(KL#|9- zn};{2r`s5cn~F}y&BIgY1|lfp&f()w<_XGl^9eqVbHYiN0(Ji3Etll?`+f8Me7~RX z>r#`Eo;EXhZZH4P$O!qoHCl07u%@S#pxQdIFA(RpyXv>l2gnYFjEWxI?eeOyVxm#ChUsYy+_Q2 zzzG#5PXbMn%2H>5a@t0LA`SwR2*Lw}A`Xe52&P4Kls+Cr5tPqGQ1*s-sF;U|Q4E~A zAl9Qzrx#}`lvDoLlLRs{j8)9#Ivfs;gU_LD1};w|5_xQ(D9mcW_6-(>aKaY*0|#Jld{u=Hj=H0et{Rv=I{RiTNol=L6e1M%EG7=5{Ui6(V28& zQXs;$7%qefQY(TnK2HP_JP{64B!*%o<-AG$O6ysoE zoSGm_7LR1Hr%IDSD=3?(h|(!-v>BX8v)J?o3i<@T0M#N|7{x_G7!%?+tQF~SSj11@ z2?aQY;-ns$!s}j3-(;(@5;hF0myH-laa2f9d{~I^_%JTOP#7mg2#n!+oDdM0p3w3k zE=!T?>73U($tz;p)3bdw8|>lLbWs*|q-^Z$Sax10Vdrjo#!5{pySm-(@B12V0Dy0U zN+HuY|LDjLvUDJU^W5&shfb(Mc97N&&EKt1yG}dtkoa=q$2;L=4PA!F!QPzKvZ8Hc zrBR_SpW&_dKk+HQveD;pf6U9hM8~c31N5$es%9uKE;mrxxX~JhxoUm-$0K8BC?WaH z)pfaJbr(-ss~u~ul_U3BOmT+&y~w&!V_^I6`_WRVaZ{jlJg8}-czauB{Gzz#x^P27 z-8va<4hU=7bTs*MS{WV`8VVDQp}l98`YS@nfG-OL&Byx+D}LQPXWHXE@ag7Rzl&am zjXvFe?^MsK`m>8Gm9x_;G*tV-871A4S+Z}}3dkFW3huX8G+p?L7tyOpdlXa~o}Z}s z=9&A#7fU{#QPocYj=_0PvxevOJdGbZV%_&}=gI3ML62R&)y0{;Ys@30f9EwGuwS}+ zMRos@uG*sU)`~L>qQuXs9DPxGg_4ZT_jGno4_4gL?1T*uNv>&o=k{Rr+GRuF`2GEoX$eX;jmbqa)9i>YV9q z(A{mL>RW$~Uca<&%sUtm1R#hl@4Frf3s6%T<53TGiW?9wj})A>}Aon4&N`}L>w58Jsjn05N)j;9{8cm aKN6?|A`4=UbUyd|HB_nTio+?nu73e(FMSmN literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Dj_128x64/frame_21.png b/assets/dolphin/external/L2_Dj_128x64/frame_21.png new file mode 100644 index 0000000000000000000000000000000000000000..076448fa940b42f0740c62feeb1943e00616371f GIT binary patch literal 1665 zcmaJ?c~BE)6kotbz=O_+tyqsGqP55-dnMVd1PBBKAs8W5v>j!$*+2-%#?4{^s6(mX zF)gOmv6Sji4=T0}W(4VBhoYk3RmXx>@MzFt$I&S`)3H>ebOXfthtr+i{l4$*`@Q#j z*X%Z3_L3>#Gs6J@n39pM$r08H;YkV&5#9r#JG8saj+#EE!4y`F8~(u_y3OkiUJ#Q8D|y>LltXSy2Q%Z_zaCoEPRm|%|?=z zD+vQmC?JY9C?Om}ln{j|35cO_6i4I+xe+!7?7RxE(WD>*f@so`)oBQls!^-q)RYuV z4yVenWCF$l*bJM4r))G6@NE`+k7HB*7fYsa48^mYo@I*zouDgZdDc};o2?Golrq|4H$3hT(oNJDS^EdU7Kl%Y}U zU40#^dbtj8a`W?%LyH%`6}ly&*VFx1gE{omDr;oyu{FcK{O{^Fb-JF$&zg8K7dr4+ z?~w=CS(C?*)wptKuE=0Xn&zI_b}E1Bg&0r5*LNmPmX~}PUNL1y=Sb7>y<1o8Y4jdw zZwI{t+h-?EjY?Yk!SuPF)76JR{zTM0i402I&`O?c{Az0W(ETd!jF?BcS1P;S-@NUT zw|O-ud+50|P~8crXXo}~y|vc>Ji+6%sFxQQYDJe*KRO238~!=9sJwO0a&JHNr24_H zf9hdKeX(DBrn+rw{q7)DO`oOcc{H+PZDZq=?CH@WXlH---V0|tkIZM~(+^a(INuw( zT4OqFQk|cX*MerlNwcD^H)tO04-2liX_40fK_NG-Md{Os%+j6hzpSY8w60E!o94c) zQ;GI%=^Nl6$i7s(j zdVXBY@C|y|hNy>;k>!u(HFk)Epp~_O|TUWV$e-xag2+53mE$H?f-@US&5NnTHw%hKG{ubHTrC$+!GWa?jx>IuR=61uGuuCUn-P(;VNI@mp(>qsi0_UI(@nS&LHE==?~46>Y3P-=bBIav+ZV(4UU$iMgUUA8-f& literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Dj_128x64/frame_22.png b/assets/dolphin/external/L2_Dj_128x64/frame_22.png new file mode 100644 index 0000000000000000000000000000000000000000..8651f12f8bd91d684d5943af135697e9e7d700d0 GIT binary patch literal 1809 zcmaJ?dr;GM9FMfpg1~U&alT`4t}1DhG_+|2ZJ`AVR-x9T&KGH#N)OxAHcDYbD|!NR zDpSun>vN~>oKBs}eBcH?PQ|I9PCZbZcy@=6gQ@rcL6#~w|M1Ku`Tc(1d_Ujs=li;p zWll;94;>f^06@5Ig2uqDUhYu|dEDDCT&3rh0j$=Jr~4z zbXlxQgC@1dA9qTEa#_}?lt}XP^Tqj6G2@yeK@|!`zy^lFoCfSJaImBYcDUnuEof*r z<+3?h8{+^27D+QRpG|@|wr|6*JN5c^h8^ynWN~Secu1!N6(bV6JrLU)Z8vM6|EKXm zZMU(&NlOg0o0;#TxSCkwdcj=&KJF->$gxpQcG-5HL;Q7>>%!G7DnqweumoMw5aPD2Apct5Z=_t5K^FZAyw% zhG-L{$pj+p#p)bxmUK{buWuXYy9bMW6st^e(Im^bj0`ikw-YjR8J2P9GENY~#W*O^ zla$R7kc21&&+GSfnTT)$&9ek5!rR@V0tO-&XW=6~~MkwSkE++`stgsNULYjce zWdx29lojg1Ti#3G+g9ZyTo}$?HcA2`FgZy}VL5_IVM2yuFhMC07$>X*DI;+!X_i6~ zj-n*cIq!8+uZU~U!1mp2aEEu(MLW2Wa&fcMYI2R_<}NsMqEXAO-QC?g>bEp+YX!u9OJ&N0#opWU_y|KR&zITY9Ip_V0eMJ9@lr5#9y+=*fn)byNzk7lC0BorAn|VQRv$u&DmcOnOTTQ7J>G zhekI#=Dnzt+{!u@RNbtPSw2{-EV#in0n@Srpi{@2x@pMC8^Ydx{vsjItU&pdSwt-PK6 zS#91uzkh93W838TKX{SGFAjy2XD+JPiNxPi?ZGqDUD3RhoxbRnlB(asvjpeDc{lUj zv5%xHy?x|)qk^-W_*<2p^{;2$-gaQ!6ID$GPfH)q{0bT(hl$$bfOqq}+0Tj_59@Bl zjU9S4v>`9m{LA*Tk~ZfPzb;+?y6=^15A7P(rRI$glsD#HWVeqmsb4sALi%jlynp4V za<~K7N`<9QE9(ZzJC96RKl(Ud6*}uwwpg@c)ssK#D^c&&(jToVK{cst%vzNZeD3QV zg(0gJjm|w`l%o{tsyvoy!0K literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Dj_128x64/frame_23.png b/assets/dolphin/external/L2_Dj_128x64/frame_23.png new file mode 100644 index 0000000000000000000000000000000000000000..d2d8e7e5144697f5932a8e920d673c29de730372 GIT binary patch literal 1775 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Dj_128x64/frame_27.png b/assets/dolphin/external/L2_Dj_128x64/frame_27.png new file mode 100644 index 0000000000000000000000000000000000000000..39ddf46ab3bd195b3e75567a3f91021720c9e1c8 GIT binary patch literal 1759 zcmaJ?Yfuwc6ut?F6yJ_u#Ti)_P*gVAM@Tl&Ktd8El^|j&R7KfrHY7^2Fz> zAERxp7ObESZGB4_6_i?VtbzkoYIQ`bwQ3cgsN;yWDs+Qj{o%N?yZ7F+=R4;+ui4_X z)X4)v!b1Q61N6z-bbj^mk19BTf8PvVXW*9+oGz1_&RRGRMsYbcQl7 zTyTn+1VF$Fn=zBiG^8kK)-Iy_IwG&#$)f?7l;Cwz^n8YcOiYf=p@bjS*1?d?tb{Y- z45-1WVXU^~0v9vAAk|10%%|mMI3XUIVcP3)zdVoZdJ?RI}`-P&$0o%x@}JGI@$ zg-%AC&bZk;7tPni64?vp^Y?y7enp;*Vyer=7lz8!vUHxEad3LA66U{%%r>)vmdZ(! zgp?r^ZIUAr0+S;YCMOYwmf#XhYLc2!bFZCu;ggaQF||%jBqgeoFifXat5KasLr76w z9Fa((L@!qFaC4M{W_o?wc;7u({JmI(#>G$^>oT%zZf_@~Sy_&CTUjTBOGFYV#z4_F zhhH+zU!~VUYZ;diCt*D{4EarWMB?Pd|_X z(!kwm2liYT0v~BwvQ`t(mU2dLdw02U^|SUFq6%q5!o3^feTzR8Zhd$(y1lLi6K}B= z=D(EwF!$o3zN+w!E!%ep+M@-x@zzN4`dTxHJNEqWk-OW7$;P0Sv_^L4W0Jc{QPVBsFpFVzBq>H-D=J4n9;Jnl0}% z1NV!Q+g56vM$O{kF9Uq{hs67C6vnG|bQXZ!{rscSDsu8qQ?FWxczOczZUGoKjse-7sMnTQ0(YR0*4wk;U(q~Fg&DqM*;>`mg;ouFG z-oeo2*|{Kp9rZ%6aI8bCI?=JFI=XS@cG<5{;KzVr^=02?#f3i@ZEelKP+Rhi{zb<@ z`||NZ)%k>s5==OFO(GpqUk84PuU~X)VCAOcu6R`ivF=32g{l>w5Jm20rLW#_3;J?R z@|^Dn2`Yq8SVL#|y^pNzAT*%)j-JjAo0j#W2+XUyH*_L+YOMOKO9-m;!)r_TG{A7* zm_kGNa`lwuG3iel1M7F1moTxj!Y*x&R*?~Dd7HDPzjr)dy>oWMsf>oaTH@w~J-Ke5 zDSpSu&I~;@qkLxIFl^eM}o=7(WVhfH1qs)S1oQZV~t4wRhK}y*sGwL0eYk7f-q=zR5L@a+T0)#p!o_h07zoQc568m|kr)$qATxFuuuqCD z2t1>r*E^6|QdL|fT&ij^Oq(%O#4sGT(`FGPFj62SIrs!jV1(6z5#UB~g273KAmOA>3;ct8uMz$ z0?kp`;+JD;qb7oy1XrcNAb5;Xq2?#G|J;Ng~}pHkho2-Ntl$fGaugTEx}n2YxkD0UL5zhSr+q@mRfC?r_fqL zVb&DZ7m4Y7M3hs$72tagYr7|wDb-|NSG9nuHl#YCGNkHiETl$Zf;5xxYK|9`NK&#U zS*6*a-Lj@^l_jsHhT)ksGs-*^f`wKaAz%VZP_!K-?G%Lyv_zq_wGg-4D3YMWATo!S z?n>Wms{#oKBbs8vPy|KTdD)8EG2Dt$Hj+Rok;YJx3R1j{Cxg6TMJ#}#CD}Q5by7+M z+B3PmGaKM=XS(DF7%2_R&aayucYwJwR+a}mV4a?x?z`B18G_P|`rK?FKJw?AR|kgR zEU)vI=|1(NE$?}b-CA<_jbqMF3hK4JM_kK)8+z!`_OdIB;u&r0q4OVCyV$Y(E2j@; zO>Vos=EN>%r}LS#fwG=~!-F1Z`BY!DwZSwv(QJe#6l2ZA3&x%apYh5>=gFtq*R{QJ z(th9Ou>)z(_MI|Z|KjJI#0UPJx9iu`og;3HU7cL^K-U=ee%FHWe>d(u@p^Snq51o> z{foQWh_y#23MI%4)!Du^tlZv}(8q-wcdlu~aClr;Qd4Tmzi@wUhQZi-BE(ABxt9jt z^^Kliy?tlmZ2E!l=>ALtH}a{M>o=J8z`MPN7P958Gv^jE@Y5Lud)m8Jx^j-^I?5on zz2NV~8;pZzMsH;H8`uu$xyi4KuC-je+Li&|;@E>3a9}Mo-2GGCmYx-v`)aqc{U46s z-VF_}`2AX_pd(>2y?T+!%W|0>e7Lv&o9tz+t@Uqn)kiM;VE!!ERXz3Nru_$&P8v+{ zplijYkp-W3Bwlmor`^mhFUp(x=U8Ifq8IWi#v+u^#^ nn=?Fl^SeV|8BXJuyPeN5bB81L~h5FJCem|@tcg&F+lUB9?%E63=4vCBBTurrX&W%Szjpu)AcUV-Cd zcw=#lkid*v!c@jIalvdcx`o6z=SDPP)2PwR#kruExUh(hjWH8h1m6P=_Xpavec$)# z^ZcIQ^VJ@IW%=5yC$bO(S?k^As)B1AK4qDy@O>k*-v^gm!(C(S&_YIwr~^chwID#f zVX+QW0a0$?yC&~qq7P_%`h;Ej_MPcl5kpvxBq zJ?e6ZuX1^o{lSwRt2c}YZ?Uwrw3u6Hv!>TsNRHzY7!-v=4LsVa8e$ArqXkI>7l=x_ z5-}7_MH7f(P-`;m7-YK?MmXa0tpKahfq!YNsvQlq zMu4RXM71Vef;9;hB#mMI{@+nT5whW5&=pu1vC*YTO<|xKUY8w%U(B*1^OBWggA8lK zMJdSP3{7&lNOCL=B!*&0YtSkaauR1Xdy*yuht^4XnH`?8k3#iuh$GMTCYV=iZL^2kx!Hq zH6eLAQKhAzT|ifwf$Y(>FuIs#URi}gsF=1=K_ZA#EN8BA{^_LDD#DWhk7LI09$b5Gz_mCL{)F%mOJ|5}orv zCnZI&JrmoN*?@jSoDILzvsaM8iICpFO6#+L~7Zw)Yy*4zCAf^tlO9;e%{QcN? z_Gf73tLvs0zT0lymEOB^!zTlU+dq!&EghYa_Df@Zo1SlOyi|03bL-b3%;F`FqxhB75eX$TK?`u%+XpYvmPM`+G{c$OUKK=yUq zOFz-~>Cp*wE@OS kWlgiQ#)aE=FPFznh;lgdO7=+pfy6J->#lUY@2Kti2RHiax&QzG literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Dj_128x64/frame_3.png b/assets/dolphin/external/L2_Dj_128x64/frame_3.png new file mode 100644 index 0000000000000000000000000000000000000000..40d1314c95fb07190f995925e69f77df558f8cbf GIT binary patch literal 1777 zcmaJ?dsNeA6ptX!!4ySyz*i{vnx;+D7Scc`l(rV7h)@;h)TU`F9ki)QfP#v}Q*{jR zdFIq1zQHL+Jvtd4buwS`wZTzgQykzFA7ew9=wLFtRH*Y0k2y)c@4LCbdw=(JQk0TB zEzm#A9{_;BM71K7TkYHv=j+M6+kDq)xFwWTrn703k6 zxlG0afae;MHl0n^Bw++)=Ho6MzRhgq&;SrS)n>(sOp*ol z%6R>x(lsey0!5Rcln+A$3=6?%DW8B5Sgc13q;U#}z=%iyBis!M5lkq?5HUD#@i>pP z!HA_QR0ICFlZ=V0wEt3n9Z)(UTHIID)~Q+w`w!m zJS!{%gg+iqwJ|0#kB#5G5 zr9>o0VUZh~Xkl2~LXd9XCeHT&7I`NYOQ1=drD!chWw|>cC4*uqCWEqqh?p-1Cu(rQ zWN}F*xvJD3w1T8fIix{FQ)cj0nlaN`C>SN8C`1qIAq161L*i%@h4fMb3Q0v0VRRHK zMo_}Y8^9ahNMC=eauO~KtD6mmA}A7#lOiY@7K$J=N{m1#A%!6^YDDoUTx`VkBA$Sw zC~$So8=d49aqa2azMc*4@OrvP3pY|UH#>C0@?dW6yi=02N^W&Jo%?R>_!R&=IuaG} zTHEbE<_%hVAND=xbap>JwCSSv#`WJY*yHNSr8x~ruy?@aZU4qjty|%2&41}Ps)ZTS z&~SME%!2JJ+GanU&~aKNubvfj*2q3x(4}Lv!`8HnpS+}FOw~qo3sp?4yxSN6+UJI6 z72g=TXIoO>V`^=^v)M|&)ZeWeyY$Ph_Jp9F+a8cbS9pEB^t2=Ifm>A4AL0V{EP1mIw>30iOq)uLK>okRiy>lBR_MbYy7I(x7p2uiCOf!87(} zk5z|#q>DSg_b(_iyk=9Yj@Nvp&^OL;0t-6rr`t2MQ@y$I`QhH~oe^hz07hD{rf}=> z9+CXV+XsOP-|`fDw)Y&gb_S5;p(o|;L)Y>I~$)hhJ)JV4SL__$2QpOs=CTkLiU2}Zy^Ck6b=tZFpw6s>+jai0+s<# z^N0?}R1^f9de$R(`qlBW62E&tLqLxVj2K8B>;-$hl`ZNR@@)IWI8|WPqcW(p?quk`u<+*0)$>P* ziZ*rVhj^E4+BFC zJF#+Jeazt5`P9_F!lmPGhd8q@gs07T@cjKlF_)j!%eOkN?RIpXE*gHe_u|cez*Vto z|AtCQL*c}l9bcn2gFoJKY{Vd6AO4iJeTzH-fQAKrUcxWfcGpiRQJJhbB-gF{58GOv ANdN!< literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Dj_128x64/frame_30.png b/assets/dolphin/external/L2_Dj_128x64/frame_30.png new file mode 100644 index 0000000000000000000000000000000000000000..27c297e8d59c983d0a06ce9afafdc39064216039 GIT binary patch literal 1408 zcmaJ>YfKzf6rP0z)~)4H)TFJ3af+4#v-4zkX37ft;Mp#$+cgr4hIuVaV0YHpp}TC` zWUFb~{;1#&joKz9n$}lR5lz%I^00|sYcrp@Gh{}AEc8zbMHO( zeCK@U+;irLx1naed6OA}p!IdNZXcKv;H@;JgYPeQznpE;XP>c}<2Kk}Ak> zU65~;eKId~z4(n>4ngUM6n~T6*iKh9X;17Tj_~ z)ReHUs3ACMkr&iX-HrgZ%V7k=9?z=bP-G=pAZ=KT4`YM{$AZCRY)je^-6#J~V@>Ud zzbhd3k*Wl5o2sjkHZ=^Bw1tL?JiMrcl9Fx7 zTUrjI}Ii=#9XV0bG}2Y7)(FhCJYR?a<@loEmR zOfFY@18i2iOAdjS(m?M#)c2JN`YzME(_aPV#l^*wmrq=QAjAGTx5FR%@%PJrWWS7? zGR7|49sFy*wFU^`;UMVO|*cWakyoStc6*)cm!&`@H_nlwp zF*0}a)EyHKOdH1AzQu0*-9MH0ld-SA$%!u=fd-^IjZyKO4L24ZYPkN0@rcQlQp|;`e;wT z!GC)6s?uWge(8EM@qK>%lf7TuY&N5b(0pO#_Qxp0+f&D1r{rP@#gJW*+n|L}axNu4OzVvwe9JU;vjA4Jq zh#}s1JneAzVjwa1dMUhMYAG5>&wUl@9Nq!_S~Bo(?&b%_kdQPJ~{u%69q#* e7k@BOlQ2NG;zy>fj4vzsPpqqIaG!Rzy!0Pj5bA>f literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Dj_128x64/frame_31.png b/assets/dolphin/external/L2_Dj_128x64/frame_31.png new file mode 100644 index 0000000000000000000000000000000000000000..f2aefbfae2f1ed51e8e87c69f47d42b6d57b6a7b GIT binary patch literal 1404 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 UNHl%vIsb2!jCaRAjU1c&4=x3@iU0rr literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Dj_128x64/frame_34.png b/assets/dolphin/external/L2_Dj_128x64/frame_34.png new file mode 100644 index 0000000000000000000000000000000000000000..81f133ac5592569c6963b813e7eee54087a5195b GIT binary patch literal 1341 zcmaJ>YfKzf6dr^uO>wc>#DuoN>0qKnXXm-QGZVJmC+%vNlI_M4k(7DdWk;AtX9jn` zqy_t<`k(ow@g( zd%knNbM85FsIg(2ckPC?2!eQ%+v90C_rN#2rWAfJujxy{sme(-IlGLklNT+3L=-~; zXi^i~KpKcj*WPbI9fFh|R5MLZQ)-7G8=6ln==faCglGh*+v=L4+zuR60&S`u!e+<5 zz))2QVY~e)GG)d_+=< zkCu`Z(((Fbe{dJVa*ksP1d-3@eR;-bSZxHw^L)VuP2*4lx4U#lbaCCTDO!jFTeeiw zQ4Jj}SQI6r(+Oda?NS(;nM$n~*6rnF!L$*sXcCld| zWhfpODW1cD%+f3skOB&+6z!~rrM|($R-x}@Y~+DhA!Y&5F|3SXbQCM0F=sf2oij|7W_>LBbV`&}y&$PA+|p9eabT&h z0HxkCG;}e|g1QQYte*+c5-H&{#|LpX$Z@#DD;&-)_nFO2{78XwZa_kaP&;`s z^86 zSUq@pTkhoJga3XP9cY=_GmpMM7uzXC{=`}2Ir8(V-PEWZzWVV$rB99_?|#t3jSb&D zG1W6sO5ahlsJpK9YyZ!`hdtM;-btp$kBDg4~+HYXme{t=gri$nls2pT3LVj*o_N?A6hce5Fd%Q GzV;v5Zqt+i literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Dj_128x64/frame_35.png b/assets/dolphin/external/L2_Dj_128x64/frame_35.png new file mode 100644 index 0000000000000000000000000000000000000000..c828207d33ed6ec94a71c904597dd24b4805b3b3 GIT binary patch literal 1255 zcmaJ=U1%It6dt>whW_M5Y@;ZQ(+X8PGk1P=cV?2!-_E94%%;m`!T1t-XXftiG?|~y zOuCblYMP4L2dhGbRV)#;STGjEKd=xj6j5KaNeZtO$oFf;eg zne%<;JLjHrB0scm`<92d5CpM3JD?WuI*p&6));Tnc@?~m%q&H& zx&&h>YE9HgE>ET%50NqhXdM8KOv;Q7cpz%Lfl7Nx9`FJSczn|wuW+Kmi)7=Xa7522 zDFwB^5f6W*s7esH3d>fjRi-L1j#p+mS(YOXo~N+~?N8bP3~Afn)nrhSuY0B&n2t?G zj8JnXf)s_-uBBnQx!gLj?Kg^r%f^P#WjO}0mKEi;>g@*w^uLS^z5U{(i`WA4oe59J zXHweL1mpVMJ5c0^)hPQt6CVs5R~>!ALUxc?JlW9@lWEyg^ zL?d0~MJ}Nw3}7^QHo^-6P(`37`uq9-$7R&sUXV$rg#^gNg+2)gO>EZo18D208Qa9M z8`$_gu}a!QFmSx07njEsKIvB=dj zE9M3gN^v2t}wNUBbI7S+%zqE-u$Dv>gZA(l_taKlmb6d2eFRBM&489_+hz z;oRS%y1e+=hxU!5->~+!w`-g+9qTS^J^3%!Rd4_0{H@X1+h;c|-u(Kt>8>Yhm#FZN zPIPSkw4VL>^Sw*Z`U@RseQS$A5P8p$J+jU16A`K z2Y(-ZyuRG}QEjL#{A=d>(F2!fj?A69zV+hL^Eb*X?~Zl9v-{2p^RoC@aI~eJIOg8x U{4;a%BF>k{W`@*reFtX#193vGHUIzs literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Dj_128x64/frame_36.png b/assets/dolphin/external/L2_Dj_128x64/frame_36.png new file mode 100644 index 0000000000000000000000000000000000000000..fc923b40236dee851d9c3b1f60a64d0050b67350 GIT binary patch literal 1059 zcmaJ=&2G~`5O!4wRh2k$fYZvA_>ow9y>^nd)zBt!N+Z=JN+S_*Vr{P*)A|Q{OWdAN zC4@Nf25{mE55NI9@&pJZj@*#?2w|PXrH863+dDI!Z~o^^fA`__)my8QBwg=q+XHdG z6z}HBviSa4dFzPlChv~;UJ~#L&6w2k6PJNrMEBW%QUCb)ceXA`%df-Xh>x5d%S$4K zmO4rir2;KU>l;N%y#vO9%l5;#DW84*B7@Lx%8zRfa?%bPhubF^+dJ7EdM5{->B}1r zzng(Hff}|n!@>qw99ekG{Jv{)cg?c0N2#nL1#3a+G^0qFPSQg-NPJVmwRod^ZGh zzFjO9E0u}ed3UFHBKGcDe`nYg_jEe_@bmp|L4ls#8Wz9)ep^}++$orTinljQvOH(E Yze-?JTKaM0*{hQ8b$9KL?Wd>z0Hr%cDgXcg literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Dj_128x64/frame_4.png b/assets/dolphin/external/L2_Dj_128x64/frame_4.png new file mode 100644 index 0000000000000000000000000000000000000000..d372ff643b40c4cf27bc810fb25a3ed239535b5d GIT binary patch literal 1727 zcmaJ?c~BE)6kiBRhRdT$MX|0y8B{jeY(lcTLx4aMi4uws6;RO4W&<_xrxL@AuyCU9;nM`l^5xU6V3nUzLjT25#P7;MKG0|tsFK)WH`XvOUk&Y)eK**nBM4mhYn zr%548t5Y5Cmd0AZwNjFRMp;RV z-c0LFM$loA(3mo435Q`j6o$d9QoS*3v<@eWNgK~jn0b6I%rh7qu?=cl=~(K28t>G$ zsx!?LFP5^JGAtxh6P@1(n91M!9XS*kHu%RDJyRGWU1lOP43v>p$|M}-3s3WwtkP(u2_s33_|`MNhq0pfV(|zIMbIXT+GI)}>4cb66K%4ln#>>~m4iCG2GgPtCJ5q- zMW_%#NgZbxuYD_hL#@h4m@v#EY;Y7o5ivmtATi7rK&VKFKq!g9kPy|OgoqI82#tWl zV<_?*o%2>Fjfj}`bZpYwXXb8nOq5#AtOElB`~N6w0|4h! zN|{t`Z#k9V9^C+Y6g^v|cS*1A&?cSPF?M`c%kkols=C1J<@3kDFJ4_`ClV*XJO8+! zox!f^_vyi+_Sly-^|^R=-`s(FaNBrO*MH{L9GK+s-Q$JN>Smw6rTXpA*nNQ;8eA2X z9w*z$n~!S!{wE~ffz64j7pBc;6$59x;t87FbTPD|9L4h&xE59jLtLzJo4L-T0dTU< z-<`VZ2eW{r2_bi@XY}TG^pqRQI@~Ebj>uyfq2m5tBmo(HY z__3&Mpdz+)c+p zEi2yNr+jBFH==i9Nqyr1<@Ur!KF8FpE8{#%XU_1cJ&<0os!v`}=~>dus{scZn=&U{ z-G6S>QO@NJe`?}a#Y#JW)$b02JK8(8*vgiEOAD?=`ezsHE4nxPNQ}QUVaB8l;+-=KlMOI!zDSddDuouQvmIZjsBsC!Fjr4Os1%)+;r1jmjZSE;h9VFzIpQee$VfD z-sjnnVVoB_ar#6607CWY+AMjkke}q>0Qub?ywxBt(ULA#$`&nB3G3tmjafADpx(yj z^I1G=E-gCCCjmgfIw2=l$~7!tIMJqHy*>)J%^{-!AZf1K!E(!a2{iHff?W;WKY9WJ z1+yAjlxRQ@s5xSGJs^&CuLAV3M1W32eN?f^J)(-Nm@o&l8F$N_Vi~z?j zNRH?<)3RUk^6&70eZw@b?6|CjMn zZ&yyKgI8wpF0t6j$!B7T@qy*~y*iNBQC7pe?-b;NVGFe)S8U_$l3uHZf~8TbmoX+p|2A%tfC~ktfB+N2n7MYV_-SK z?zO~uPiZu1E$mEO*I zp_6UZqZfQKqt4>>;xjM z^8PHOwjt_zU~pS+L{S8m^zfUe#+G2q@4#fs@xV*_1Uz{|?YxsM->)fCLNVHs=M&a@PPedfEmY;l0M93#=g}bGU*!Tk%Yz zPkH-Z==ck+4_@og4?2$*o4Xe&4sOhnxVNG!>Tb1EDx=nS3GrlSf`5CEc~4!{hr^kX zU^x9pdxWr&udCkP&l&a|>i7FAB~toiO5v_W?wT5O>-PFzvFL)vnEJACzn->bHMzSj zJMlhE*tOFsD_d=Rxv8Y=*ur$n-U-=@&)0Jgn;G`AM@#g1GnzEFTS_m*9y{e9IDJQE z@wu*B$H>_quhq7MZB4Rd85gd9FC=i+uDUq#>mTz&_I2%Eqe?%tEpE!Y2T!&>uoI&hrEhO+t@MJ8-10H(1?aH75N0`nbEpTRxhfH&ZWW7d2gq>8$F!Z2WfVx#EJR z1=R<-kakt;o3lyd{!Kf|Gp6pn_T}wl=5XSpye9pUwJh&n9kBgWwI{X%Xd4dJ?Hpg* QB=;Vm*BQ0VDS4~^1HhwTkpKVy literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Dj_128x64/frame_6.png b/assets/dolphin/external/L2_Dj_128x64/frame_6.png new file mode 100644 index 0000000000000000000000000000000000000000..8a1e84a11eeb499fcd47e7709e166c714229afe5 GIT binary patch literal 1635 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 a;?v;Bx|Au6L*e|`zuj&tqdJRfTmA#Jq%n&C literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Dj_128x64/frame_9.png b/assets/dolphin/external/L2_Dj_128x64/frame_9.png new file mode 100644 index 0000000000000000000000000000000000000000..05de5d5c698a86baccd74cfe33d1109ee566f793 GIT binary patch literal 1610 zcmaJ?eNfY87*FY-RYd$4;Mce;$Tm0ACTVDsj7keF*o^{*I5_7`lQe}4+JrQW!qls% zb4>51c<1~=xedSADd@J-`89URhTeJNoJ=VSH#ZzR*%XIt?mU(Pb^hU*OY*+&ljrw5 zzvt^xo0pRnGjY~L003ef3vKzz8c-gKR;|2$)qd<$mYK4>NM0nk9l8c<#6f1aibVx_%_lOD_08-QZBF&a^GRSbnyw?oD#SE^5Usx@1i>h-9*y>W2bU|qgz*Ik_81Qg14f;K0ULWN*L!)*nWgimj zA#hYhE;U1Aq>7w*V1^)ZAgM!O7C}(ZMCw=sLvRLjaqjsbhF}Igf+;tQVibx~7!HnK zkm6Btxv6|x=D0uQWQIy)S)}y(%F0Szr9mf1#d?$^$&d{UgB1q%nM#HWRYfs3fT-PY{$azh)(BY!(QKbvXr#x{j{h@b%@^M3B@+5?UVDl|7ko` z+gDH}a{7GECsatRQWJMl7_8*)*^WYr3L7d{;+4YCWj2AW@Niz)VKYO@7oCfDQLK?9 z7@RP{G|P}MZa_(xMo9wZSRBJqBV%+SuCSe_;W3k)w51!xRbht|!tr)~bp`2}2CCp$Lq?Of+YJO$cg$2_ufd1WO_?PPhr$NaJprF+h5SqCV6) zPjpgPq_k&f`*=2#!{h1Vyvj&P%ItLP`YBGCyQsYE0=u$~jEwBPzUKx2L}WQ^)&hUm zo$YT+XA!Oa>wkOX;<~QBn7V_(e%mts)!T`xh`H12)j!U;=6Bz#Oan$@fu38(fL6=y zNHIRq(5=b)VDjF(NkH}=L-&SmJLV1qs({niGM?YH=EceC#=$8`Q-hj7{gn3o&jo?E z(zd2eh*B{t|4ynq`_6-?nxLh{r!^MdY zC$;gLk^YF851H$lZ!L#-^{JECRQ0OaSb|!JnRBA?=;}?wvpR!l3lKP7f3iO!5`2)@ z_=UCtys5c+DeGLeba8gAs{MdZb@N$M@Qcm}vksJ!osZl6;O; zqZVUKS5pJ1I`6?&2&DzA+N}en>UA zv$^-BzOD#!)VsT4T$(-3&eWm3`4IgLP`WPP Date: Tue, 6 Jun 2023 21:11:23 +0300 Subject: [PATCH 593/824] [FL-2872] Remove unused resources (#2740) Co-authored-by: hedger --- .../scenes/nfc_scene_restore_original_confirm.c | 2 +- assets/icons/NFC/Reader_detect_43x40.png | Bin 3799 -> 0 bytes assets/icons/NFC/Restoring_38x32.png | Bin 3794 -> 0 bytes assets/icons/NFC/Tap_reader_36x38.png | Bin 3748 -> 0 bytes 4 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 assets/icons/NFC/Reader_detect_43x40.png delete mode 100644 assets/icons/NFC/Restoring_38x32.png delete mode 100644 assets/icons/NFC/Tap_reader_36x38.png diff --git a/applications/main/nfc/scenes/nfc_scene_restore_original_confirm.c b/applications/main/nfc/scenes/nfc_scene_restore_original_confirm.c index 730dd41e858..16b0953f809 100644 --- a/applications/main/nfc/scenes/nfc_scene_restore_original_confirm.c +++ b/applications/main/nfc/scenes/nfc_scene_restore_original_confirm.c @@ -11,7 +11,7 @@ void nfc_scene_restore_original_confirm_on_enter(void* context) { DialogEx* dialog_ex = nfc->dialog_ex; dialog_ex_set_header(dialog_ex, "Restore Card Data?", 64, 0, AlignCenter, AlignTop); - dialog_ex_set_icon(dialog_ex, 5, 15, &I_Restoring_38x32); + dialog_ex_set_icon(dialog_ex, 5, 11, &I_ArrowC_1_36x36); dialog_ex_set_text( dialog_ex, "It will be returned\nto its original state.", 47, 21, AlignLeft, AlignTop); dialog_ex_set_left_button_text(dialog_ex, "Cancel"); diff --git a/assets/icons/NFC/Reader_detect_43x40.png b/assets/icons/NFC/Reader_detect_43x40.png deleted file mode 100644 index d833a5277fcf067cce9ae363ffb2bccf31c6acb7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3799 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 Date: Tue, 6 Jun 2023 20:18:24 +0200 Subject: [PATCH 594/824] Serial_CLI: Fixing serial cli logger error so it sounds more concise (#2721) Co-authored-by: hedger --- scripts/serial_cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/serial_cli.py b/scripts/serial_cli.py index 2fa37d7512a..6dae68be6fa 100644 --- a/scripts/serial_cli.py +++ b/scripts/serial_cli.py @@ -9,7 +9,7 @@ def main(): logger = logging.getLogger() if not (port := resolve_port(logger, "auto")): - logger.error("Is Flipper connected over USB and is it not in DFU mode?") + logger.error("Is Flipper connected via USB and not in DFU mode?") return 1 subprocess.call( [ From 3e1f209d64981fb806b8819be1806e61a1a6f1ac Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Tue, 6 Jun 2023 12:00:43 -0700 Subject: [PATCH 595/824] Furi: smaller critical enter and critical exit macro (#2716) * Furi: smaller critical enter and critical exit macro * api: bumped version --------- Co-authored-by: hedger Co-authored-by: hedger --- firmware/targets/f18/api_symbols.csv | 4 +++- firmware/targets/f7/api_symbols.csv | 4 +++- furi/core/common_defines.h | 31 +++++++++++----------------- furi/core/critical.c | 29 ++++++++++++++++++++++++++ 4 files changed, 47 insertions(+), 21 deletions(-) create mode 100644 furi/core/critical.c diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index f3c4e31d75b..1dfe4c7a252 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,28.3,, +Version,+,28.4,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -288,6 +288,8 @@ Function,+,__clear_cache,void,"void*, void*" Function,-,__eprintf,void,"const char*, const char*, unsigned int, const char*" Function,+,__errno,int*, Function,+,__furi_crash,void, +Function,+,__furi_critical_enter,__FuriCriticalInfo, +Function,+,__furi_critical_exit,void,__FuriCriticalInfo Function,+,__furi_halt,void, Function,-,__getdelim,ssize_t,"char**, size_t*, int, FILE*" Function,-,__getline,ssize_t,"char**, size_t*, FILE*" diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index a8708ce8c55..2f6d8ffd819 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,28.3,, +Version,+,28.4,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -323,6 +323,8 @@ Function,+,__errno,int*, Function,-,__fpclassifyd,int,double Function,-,__fpclassifyf,int,float Function,+,__furi_crash,void, +Function,+,__furi_critical_enter,__FuriCriticalInfo, +Function,+,__furi_critical_exit,void,__FuriCriticalInfo Function,+,__furi_halt,void, Function,-,__getdelim,ssize_t,"char**, size_t*, int, FILE*" Function,-,__getline,ssize_t,"char**, size_t*, FILE*" diff --git a/furi/core/common_defines.h b/furi/core/common_defines.h index d7bfaf2076d..5bd218d3576 100644 --- a/furi/core/common_defines.h +++ b/furi/core/common_defines.h @@ -31,29 +31,22 @@ extern "C" { #define FURI_IS_ISR() (FURI_IS_IRQ_MODE() || FURI_IS_IRQ_MASKED()) #endif +typedef struct { + uint32_t isrm; + bool from_isr; + bool kernel_running; +} __FuriCriticalInfo; + +__FuriCriticalInfo __furi_critical_enter(void); + +void __furi_critical_exit(__FuriCriticalInfo info); + #ifndef FURI_CRITICAL_ENTER -#define FURI_CRITICAL_ENTER() \ - uint32_t __isrm = 0; \ - bool __from_isr = FURI_IS_ISR(); \ - bool __kernel_running = (xTaskGetSchedulerState() == taskSCHEDULER_RUNNING); \ - if(__from_isr) { \ - __isrm = taskENTER_CRITICAL_FROM_ISR(); \ - } else if(__kernel_running) { \ - taskENTER_CRITICAL(); \ - } else { \ - __disable_irq(); \ - } +#define FURI_CRITICAL_ENTER() __FuriCriticalInfo __furi_critical_info = __furi_critical_enter(); #endif #ifndef FURI_CRITICAL_EXIT -#define FURI_CRITICAL_EXIT() \ - if(__from_isr) { \ - taskEXIT_CRITICAL_FROM_ISR(__isrm); \ - } else if(__kernel_running) { \ - taskEXIT_CRITICAL(); \ - } else { \ - __enable_irq(); \ - } +#define FURI_CRITICAL_EXIT() __furi_critical_exit(__furi_critical_info); #endif #ifdef __cplusplus diff --git a/furi/core/critical.c b/furi/core/critical.c new file mode 100644 index 00000000000..57fe2403be7 --- /dev/null +++ b/furi/core/critical.c @@ -0,0 +1,29 @@ +#include "common_defines.h" + +__FuriCriticalInfo __furi_critical_enter(void) { + __FuriCriticalInfo info; + + info.isrm = 0; + info.from_isr = FURI_IS_ISR(); + info.kernel_running = (xTaskGetSchedulerState() == taskSCHEDULER_RUNNING); + + if(info.from_isr) { + info.isrm = taskENTER_CRITICAL_FROM_ISR(); + } else if(info.kernel_running) { + taskENTER_CRITICAL(); + } else { + __disable_irq(); + } + + return info; +} + +void __furi_critical_exit(__FuriCriticalInfo info) { + if(info.from_isr) { + taskEXIT_CRITICAL_FROM_ISR(info.isrm); + } else if(info.kernel_running) { + taskEXIT_CRITICAL(); + } else { + __enable_irq(); + } +} \ No newline at end of file From dbd48a04d4eced78903f79ffd06499ced58de843 Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Tue, 6 Jun 2023 23:13:41 +0400 Subject: [PATCH 596/824] [FL-3331] SubGhz: add subghz_protocol_registry external API (#2712) * [FL-3331] SubGhz: add subghz_protocol_registry external API * F18: fix API version --------- Co-authored-by: hedger --- firmware/targets/f18/api_symbols.csv | 2 +- firmware/targets/f7/api_symbols.csv | 8 +++++--- lib/subghz/SConscript | 1 + lib/subghz/environment.c | 7 ++++--- lib/subghz/environment.h | 7 +++++-- lib/subghz/protocols/protocol_items.h | 3 +-- lib/subghz/registry.h | 1 + lib/subghz/subghz_protocol_registry.h | 13 +++++++++++++ lib/subghz/types.h | 7 +++++-- 9 files changed, 36 insertions(+), 13 deletions(-) create mode 100644 lib/subghz/subghz_protocol_registry.h diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index 1dfe4c7a252..f551a09c159 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,28.4,, +Version,+,29.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 2f6d8ffd819..b6eb8c76588 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,28.4,, +Version,+,29.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -189,6 +189,7 @@ Header,+,lib/subghz/environment.h,, Header,+,lib/subghz/protocols/raw.h,, Header,+,lib/subghz/receiver.h,, Header,+,lib/subghz/registry.h,, +Header,+,lib/subghz/subghz_protocol_registry.h,, Header,+,lib/subghz/subghz_setting.h,, Header,+,lib/subghz/subghz_tx_rx_worker.h,, Header,+,lib/subghz/subghz_worker.h,, @@ -2662,12 +2663,12 @@ Function,+,subghz_environment_get_came_atomo_rainbow_table_file_name,const char* Function,+,subghz_environment_get_keystore,SubGhzKeystore*,SubGhzEnvironment* Function,+,subghz_environment_get_nice_flor_s_rainbow_table_file_name,const char*,SubGhzEnvironment* Function,+,subghz_environment_get_protocol_name_registry,const char*,"SubGhzEnvironment*, size_t" -Function,+,subghz_environment_get_protocol_registry,void*,SubGhzEnvironment* +Function,+,subghz_environment_get_protocol_registry,const SubGhzProtocolRegistry*,SubGhzEnvironment* Function,+,subghz_environment_load_keystore,_Bool,"SubGhzEnvironment*, const char*" Function,+,subghz_environment_set_alutech_at_4n_rainbow_table_file_name,void,"SubGhzEnvironment*, const char*" Function,+,subghz_environment_set_came_atomo_rainbow_table_file_name,void,"SubGhzEnvironment*, const char*" Function,+,subghz_environment_set_nice_flor_s_rainbow_table_file_name,void,"SubGhzEnvironment*, const char*" -Function,+,subghz_environment_set_protocol_registry,void,"SubGhzEnvironment*, void*" +Function,+,subghz_environment_set_protocol_registry,void,"SubGhzEnvironment*, const SubGhzProtocolRegistry*" Function,-,subghz_keystore_alloc,SubGhzKeystore*, Function,-,subghz_keystore_free,void,SubGhzKeystore* Function,-,subghz_keystore_get_data,SubGhzKeyArray_t*,SubGhzKeystore* @@ -3361,6 +3362,7 @@ Variable,+,sequence_success,const NotificationSequence, Variable,+,subghz_protocol_raw,const SubGhzProtocol, Variable,+,subghz_protocol_raw_decoder,const SubGhzProtocolDecoder, Variable,+,subghz_protocol_raw_encoder,const SubGhzProtocolEncoder, +Variable,+,subghz_protocol_registry,const SubGhzProtocolRegistry, Variable,-,suboptarg,char*, Variable,+,usb_cdc_dual,FuriHalUsbInterface, Variable,+,usb_cdc_single,FuriHalUsbInterface, diff --git a/lib/subghz/SConscript b/lib/subghz/SConscript index 6d9c0cd0638..3a0325b71db 100644 --- a/lib/subghz/SConscript +++ b/lib/subghz/SConscript @@ -18,6 +18,7 @@ env.Append( File("blocks/generic.h"), File("blocks/math.h"), File("subghz_setting.h"), + File("subghz_protocol_registry.h"), ], ) diff --git a/lib/subghz/environment.c b/lib/subghz/environment.c index 5ded243c416..3794dbad841 100644 --- a/lib/subghz/environment.c +++ b/lib/subghz/environment.c @@ -92,16 +92,17 @@ const char* void subghz_environment_set_protocol_registry( SubGhzEnvironment* instance, - void* protocol_registry_items) { + const SubGhzProtocolRegistry* protocol_registry_items) { furi_assert(instance); const SubGhzProtocolRegistry* protocol_registry = protocol_registry_items; instance->protocol_registry = protocol_registry; } -void* subghz_environment_get_protocol_registry(SubGhzEnvironment* instance) { +const SubGhzProtocolRegistry* + subghz_environment_get_protocol_registry(SubGhzEnvironment* instance) { furi_assert(instance); furi_assert(instance->protocol_registry); - return (void*)instance->protocol_registry; + return instance->protocol_registry; } const char* diff --git a/lib/subghz/environment.h b/lib/subghz/environment.h index 7bd38ba2fe5..c15b8b211b0 100644 --- a/lib/subghz/environment.h +++ b/lib/subghz/environment.h @@ -1,6 +1,7 @@ #pragma once #include +#include "registry.h" #include "subghz_keystore.h" @@ -9,6 +10,7 @@ extern "C" { #endif typedef struct SubGhzEnvironment SubGhzEnvironment; +typedef struct SubGhzProtocolRegistry SubGhzProtocolRegistry; /** * Allocate SubGhzEnvironment. @@ -93,14 +95,15 @@ const char* */ void subghz_environment_set_protocol_registry( SubGhzEnvironment* instance, - void* protocol_registry_items); + const SubGhzProtocolRegistry* protocol_registry_items); /** * Get list of protocols to work. * @param instance Pointer to a SubGhzEnvironment instance * @return Pointer to a SubGhzProtocolRegistry */ -void* subghz_environment_get_protocol_registry(SubGhzEnvironment* instance); +const SubGhzProtocolRegistry* + subghz_environment_get_protocol_registry(SubGhzEnvironment* instance); /** * Get list of protocols names. diff --git a/lib/subghz/protocols/protocol_items.h b/lib/subghz/protocols/protocol_items.h index 4ca1c4679d1..f1a28ac9b51 100644 --- a/lib/subghz/protocols/protocol_items.h +++ b/lib/subghz/protocols/protocol_items.h @@ -1,5 +1,6 @@ #pragma once #include "../registry.h" +#include "../subghz_protocol_registry.h" #include "princeton.h" #include "keeloq.h" @@ -43,5 +44,3 @@ #include "alutech_at_4n.h" #include "kinggates_stylo_4k.h" #include "bin_raw.h" - -extern const SubGhzProtocolRegistry subghz_protocol_registry; diff --git a/lib/subghz/registry.h b/lib/subghz/registry.h index 91027807e89..8529c109705 100644 --- a/lib/subghz/registry.h +++ b/lib/subghz/registry.h @@ -9,6 +9,7 @@ extern "C" { typedef struct SubGhzEnvironment SubGhzEnvironment; typedef struct SubGhzProtocolRegistry SubGhzProtocolRegistry; +typedef struct SubGhzProtocol SubGhzProtocol; struct SubGhzProtocolRegistry { const SubGhzProtocol** items; diff --git a/lib/subghz/subghz_protocol_registry.h b/lib/subghz/subghz_protocol_registry.h new file mode 100644 index 00000000000..6a27da99259 --- /dev/null +++ b/lib/subghz/subghz_protocol_registry.h @@ -0,0 +1,13 @@ +#pragma once + +#include "registry.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern const SubGhzProtocolRegistry subghz_protocol_registry; + +#ifdef __cplusplus +} +#endif diff --git a/lib/subghz/types.h b/lib/subghz/types.h index 09eb07eeaad..719beff45f8 100644 --- a/lib/subghz/types.h +++ b/lib/subghz/types.h @@ -21,6 +21,9 @@ #define SUBGHZ_RAW_FILE_VERSION 1 #define SUBGHZ_RAW_FILE_TYPE "Flipper SubGhz RAW File" +typedef struct SubGhzProtocolRegistry SubGhzProtocolRegistry; +typedef struct SubGhzEnvironment SubGhzEnvironment; + // Radio Preset typedef struct { FuriString* name; @@ -115,11 +118,11 @@ typedef enum { SubGhzProtocolFlag_BinRAW = (1 << 10), } SubGhzProtocolFlag; -typedef struct { +struct SubGhzProtocol { const char* name; SubGhzProtocolType type; SubGhzProtocolFlag flag; const SubGhzProtocolEncoder* encoder; const SubGhzProtocolDecoder* decoder; -} SubGhzProtocol; +}; From 6f6ead1726bae2de28b7aac8df3f97338de3be13 Mon Sep 17 00:00:00 2001 From: Max Andreev Date: Tue, 6 Jun 2023 23:33:04 +0400 Subject: [PATCH 597/824] [FL-3045] Fix core2 permisions (#2742) * Fix core2 permisions * Fix Python code style * scripts: copro: changed int literals * scripts: copro: shorter string line in code --------- Co-authored-by: hedger Co-authored-by: hedger --- scripts/flipper/assets/copro.py | 11 ++++++++++- scripts/flipper/assets/coprobin.py | 5 ++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/scripts/flipper/assets/copro.py b/scripts/flipper/assets/copro.py index f176e3b2e82..25c072899ea 100644 --- a/scripts/flipper/assets/copro.py +++ b/scripts/flipper/assets/copro.py @@ -58,14 +58,23 @@ def loadCubeInfo(self, cube_dir, reference_cube_version): def _getFileName(self, name): return posixpath.join(self.COPRO_TAR_DIR, name) + def _addFileReadPermission(self, tarinfo): + tarinfo.mode = 0o644 + return tarinfo + def addFile(self, array, filename, **kwargs): source_file = os.path.join(self.mcu_copro, filename) - self.output_tar.add(source_file, arcname=self._getFileName(filename)) + self.output_tar.add( + source_file, + arcname=self._getFileName(filename), + filter=self._addFileReadPermission, + ) array.append({"name": filename, "sha256": file_sha256(source_file), **kwargs}) def bundle(self, output_file, stack_file_name, stack_type, stack_addr=None): self.output_tar = tarfile.open(output_file, "w:gz", format=tarfile.USTAR_FORMAT) fw_directory = tarfile.TarInfo(self.COPRO_TAR_DIR) + fw_directory.mode = 0o755 fw_directory.type = tarfile.DIRTYPE self.output_tar.addfile(fw_directory) diff --git a/scripts/flipper/assets/coprobin.py b/scripts/flipper/assets/coprobin.py index 75bf76d766d..84f52fbb3eb 100644 --- a/scripts/flipper/assets/coprobin.py +++ b/scripts/flipper/assets/coprobin.py @@ -46,7 +46,10 @@ class CoproFooterBase: _SIG_BIN_COMMON_SIZE = 2 * 4 def get_version(self): - return f"Version {self.version_major}.{self.version_minor}.{self.version_sub}, branch {self.version_branch}, build {self.version_build} (magic {self.magic:X})" + return ( + f"Version {self.version_major}.{self.version_minor}.{self.version_sub}, " + f"branch {self.version_branch}, build {self.version_build} (magic {self.magic:X})" + ) def get_details(self): raise CoproException("Not implemented") From 754e640c8d2cdcb45e6420d549452251c6e171da Mon Sep 17 00:00:00 2001 From: hedger Date: Wed, 7 Jun 2023 06:14:33 +0400 Subject: [PATCH 598/824] [FL-3246] fbt, ufbt: added checks for appid in app manifests(#2720) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- scripts/fbt/appmanifest.py | 9 ++++++++- scripts/ufbt/SConstruct | 8 +++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/scripts/fbt/appmanifest.py b/scripts/fbt/appmanifest.py index eb265cee864..820f5a8c55f 100644 --- a/scripts/fbt/appmanifest.py +++ b/scripts/fbt/appmanifest.py @@ -1,7 +1,8 @@ import os +import re from dataclasses import dataclass, field from enum import Enum -from typing import Callable, List, Optional, Tuple, Union +from typing import Callable, ClassVar, List, Optional, Tuple, Union class FlipperManifestException(Exception): @@ -23,6 +24,8 @@ class FlipperAppType(Enum): @dataclass class FlipperApplication: + APP_ID_REGEX: ClassVar[re.Pattern] = re.compile(r"^[a-z0-9_]+$") + @dataclass class ExternallyBuiltFile: path: str @@ -84,6 +87,10 @@ def is_default_deployable(self): def __post_init__(self): if self.apptype == FlipperAppType.PLUGIN: self.stack_size = 0 + if not self.APP_ID_REGEX.match(self.appid): + raise FlipperManifestException( + f"Invalid appid '{self.appid}'. Must match regex '{self.APP_ID_REGEX}'" + ) if isinstance(self.fap_version, str): try: self.fap_version = tuple(int(v) for v in self.fap_version.split(".")) diff --git a/scripts/ufbt/SConstruct b/scripts/ufbt/SConstruct index d72de380c38..a1acd270a7a 100644 --- a/scripts/ufbt/SConstruct +++ b/scripts/ufbt/SConstruct @@ -75,7 +75,7 @@ from fbt.util import ( wrap_tempfile, path_as_posix, ) -from fbt.appmanifest import FlipperAppType +from fbt.appmanifest import FlipperAppType, FlipperApplication from fbt.sdk.cache import SdkCache # Base environment with all tools loaded from SDK @@ -410,6 +410,12 @@ dist_env.Alias("vscode_dist", vscode_dist) # Creating app from base template dist_env.SetDefault(FBT_APPID=appenv.subst("$APPID") or "template") +if fbt_appid := dist_env.subst("$FBT_APPID"): + if not FlipperApplication.APP_ID_REGEX.match(fbt_appid): + raise UserError( + f"Invalid app id '{fbt_appid}'. App id must match {FlipperApplication.APP_ID_REGEX.pattern}" + ) + app_template_dir = project_template_dir.Dir("app_template") app_template_dist = [] for template_file in app_template_dir.glob("*"): From 09fae620d9bd4fd6ce6bfb263df074739fd122f8 Mon Sep 17 00:00:00 2001 From: glebmashanov <65850300+glebmashanov@users.noreply.github.com> Date: Wed, 7 Jun 2023 16:30:26 +0300 Subject: [PATCH 599/824] Map parser licence description (#2739) * Add map parser licence description * Add map parser copyright text & licence note --------- Co-authored-by: hedger --- scripts/map_parser.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/scripts/map_parser.py b/scripts/map_parser.py index c0c34e3d1d3..1efc4fe82f3 100755 --- a/scripts/map_parser.py +++ b/scripts/map_parser.py @@ -3,6 +3,29 @@ # Requiremets: # cxxfilt==0.3.0 +# Most part of this code written by Lars-Dominik Braun https://github.com/PromyLOPh/linkermapviz +# and distributes under MIT licence + +# Copyright (c) 2017 Lars-Dominik Braun +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + import sys import re import os From 436194e6c79ce143dee9ee25f3581812908cd346 Mon Sep 17 00:00:00 2001 From: hedger Date: Thu, 8 Jun 2023 08:47:27 +0400 Subject: [PATCH 600/824] [FL-3346] fbt: added Flipper selection when multiple are connected over USB (#2723) * fbt: added Flipper selection when multiple are connected over USB * scripts: serial_cli: added --port (-p) option --- SConstruct | 6 ++++-- scripts/fbt_tools/fbt_dist.py | 2 +- scripts/fbt_tools/fbt_extapps.py | 3 +-- scripts/flipper/utils/cdc.py | 2 +- scripts/serial_cli.py | 6 +++++- scripts/ufbt/SConstruct | 4 ++-- scripts/ufbt/commandline.scons | 5 +++++ site_scons/commandline.scons | 5 +++++ 8 files changed, 24 insertions(+), 9 deletions(-) diff --git a/SConstruct b/SConstruct index e2568287dff..b51154f7032 100644 --- a/SConstruct +++ b/SConstruct @@ -171,7 +171,7 @@ distenv.Depends(firmware_env["FW_RESOURCES"], external_apps_artifacts.resources_ fap_deploy = distenv.PhonyTarget( "fap_deploy", - "${PYTHON3} ${ROOT_DIR}/scripts/storage.py send ${SOURCE} /ext/apps", + "${PYTHON3} ${FBT_SCRIPT_DIR}/storage.py -p ${FLIP_PORT} send ${SOURCE} /ext/apps", source=Dir("#/assets/resources/apps"), ) @@ -323,7 +323,9 @@ distenv.PhonyTarget( ) # Start Flipper CLI via PySerial's miniterm -distenv.PhonyTarget("cli", "${PYTHON3} ${FBT_SCRIPT_DIR}/serial_cli.py") +distenv.PhonyTarget( + "cli", "${PYTHON3} ${FBT_SCRIPT_DIR}/serial_cli.py -p ${FLIP_PORT}" +) # Find blackmagic probe diff --git a/scripts/fbt_tools/fbt_dist.py b/scripts/fbt_tools/fbt_dist.py index a43d62e9dc9..e47898bd9ed 100644 --- a/scripts/fbt_tools/fbt_dist.py +++ b/scripts/fbt_tools/fbt_dist.py @@ -132,7 +132,7 @@ def generate(env): "UsbInstall": Builder( action=[ Action( - '${PYTHON3} "${SELFUPDATE_SCRIPT}" ${UPDATE_BUNDLE_DIR}/update.fuf' + '${PYTHON3} "${SELFUPDATE_SCRIPT}" -p ${FLIP_PORT} ${UPDATE_BUNDLE_DIR}/update.fuf' ), Touch("${TARGET}"), ] diff --git a/scripts/fbt_tools/fbt_extapps.py b/scripts/fbt_tools/fbt_extapps.py index 1a1bad29e71..16d5dcbabd3 100644 --- a/scripts/fbt_tools/fbt_extapps.py +++ b/scripts/fbt_tools/fbt_extapps.py @@ -431,7 +431,7 @@ def _add_host_app_to_targets(host_app): # print(deploy_sources, flipp_dist_paths) env.PhonyTarget( launch_target_name, - '${PYTHON3} "${APP_RUN_SCRIPT}" ${EXTRA_ARGS} -s ${SOURCES} -t ${FLIPPER_FILE_TARGETS}', + '${PYTHON3} "${APP_RUN_SCRIPT}" -p ${FLIP_PORT} ${EXTRA_ARGS} -s ${SOURCES} -t ${FLIPPER_FILE_TARGETS}', source=deploy_sources, FLIPPER_FILE_TARGETS=flipp_dist_paths, EXTRA_ARGS=run_script_extra_ars, @@ -443,7 +443,6 @@ def generate(env, **kw): env.SetDefault( EXT_APPS_WORK_DIR="${FBT_FAP_DEBUG_ELF_ROOT}", APP_RUN_SCRIPT="${FBT_SCRIPT_DIR}/runfap.py", - STORAGE_SCRIPT="${FBT_SCRIPT_DIR}/storage.py", ) if not env["VERBOSE"]: env.SetDefault( diff --git a/scripts/flipper/utils/cdc.py b/scripts/flipper/utils/cdc.py index 7c735167029..9564088598e 100644 --- a/scripts/flipper/utils/cdc.py +++ b/scripts/flipper/utils/cdc.py @@ -6,7 +6,7 @@ def resolve_port(logger, portname: str = "auto"): if portname != "auto": return portname # Try guessing - flippers = list(list_ports.grep("flip")) + flippers = list(list_ports.grep("flip_")) if len(flippers) == 1: flipper = flippers[0] logger.info(f"Using {flipper.serial_number} on {flipper.device}") diff --git a/scripts/serial_cli.py b/scripts/serial_cli.py index 6dae68be6fa..8e35d57facd 100644 --- a/scripts/serial_cli.py +++ b/scripts/serial_cli.py @@ -1,3 +1,4 @@ +import argparse import logging import os import subprocess @@ -8,7 +9,10 @@ def main(): logger = logging.getLogger() - if not (port := resolve_port(logger, "auto")): + parser = argparse.ArgumentParser() + parser.add_argument("-p", "--port", help="CDC Port", default="auto") + args = parser.parse_args() + if not (port := resolve_port(logger, args.port)): logger.error("Is Flipper connected via USB and not in DFU mode?") return 1 subprocess.call( diff --git a/scripts/ufbt/SConstruct b/scripts/ufbt/SConstruct index a1acd270a7a..8812a4e5592 100644 --- a/scripts/ufbt/SConstruct +++ b/scripts/ufbt/SConstruct @@ -342,7 +342,7 @@ else: appenv.PhonyTarget( "cli", - '${PYTHON3} "${FBT_SCRIPT_DIR}/serial_cli.py"', + '${PYTHON3} "${FBT_SCRIPT_DIR}/serial_cli.py" -p ${FLIP_PORT}', ) # Linter @@ -469,7 +469,7 @@ if dolphin_src_dir.exists(): ) dist_env.PhonyTarget( "dolphin_ext", - '${PYTHON3} ${FBT_SCRIPT_DIR}/storage.py send "${SOURCE}" /ext/dolphin', + '${PYTHON3} ${FBT_SCRIPT_DIR}/storage.py -p ${FLIP_PORT} send "${SOURCE}" /ext/dolphin', source=ufbt_build_dir.Dir("dolphin"), ) else: diff --git a/scripts/ufbt/commandline.scons b/scripts/ufbt/commandline.scons index a9b91bbca97..349b4ef2524 100644 --- a/scripts/ufbt/commandline.scons +++ b/scripts/ufbt/commandline.scons @@ -71,6 +71,11 @@ vars.AddVariables( validator=PathVariable.PathIsDir, default="", ), + ( + "FLIP_PORT", + "CDC Port of Flipper to use, if multiple are connected", + "auto", + ), ) Return("vars") diff --git a/site_scons/commandline.scons b/site_scons/commandline.scons index e31927c5942..b70b5cff56f 100644 --- a/site_scons/commandline.scons +++ b/site_scons/commandline.scons @@ -243,6 +243,11 @@ vars.AddVariables( " app can check what version it is being built for.", "Official", ), + ( + "FLIP_PORT", + "Full port name of Flipper to use, if multiple Flippers are connected", + "auto", + ), ) Return("vars") From c186d2b0ccb44ab9b2adb1d0aad1efd9219492ac Mon Sep 17 00:00:00 2001 From: "g3gg0.de" Date: Thu, 8 Jun 2023 07:30:53 +0200 Subject: [PATCH 601/824] added ISO15693 (NfcV) reading, saving, emulating and revealing from privacy mode (unlock) (#2316) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * added support for ISO15693 (NfcV) emulation, added support for reading SLIX tags * SLIX: fixed crash situation when an invalid password was requested * ISO15693: show emulate menu when opening file * rename NfcV emulate scene to match other NfcV names * optimize allocation size for signals * ISO15693: further optimizations of allocation and free code * ISO15693: reduce latency on state machine reset * respond with block security status when option flag is set * increased maximum memory size to match standard added security status handling/load/save added SELECT/QUIET handling more fine grained allocation routines and checks fix memset sizes * added "Listen NfcV Reader" to sniff traffic from reader to card * added correct description to delete menu * also added DSFID/AFI handling and locking * increase sniff log size * scale NfcV frequency a bit, add echo mode, fix signal level at the end * use symbolic modulated/unmodulated GPIO levels * honor AFI field, decrease verbosity and removed debug code * refactor defines for less namespace pollution by using NFCV_ prefixes * correct an oversight that original cards return an generic error when addressing outside block range * use inverse modulation, increasing readable range significantly * rework and better document nfc chip initialization * nfcv code review fixes * Disable accidentally left on signal debug gpio output * Improve NFCV Read/Info GUIs. Authored by @xMasterX, committed by @nvx * Fix crash that occurs when you exit from NFCV emulation and start it again. Authored by @xMasterX, committed by @nvx * Remove delay from emulation loop. This improves compatibility when the reader is Android. * Lib: digital signal debug output pin info Co-authored-by: Tiernan Messmer Co-authored-by: MX <10697207+xMasterX@users.noreply.github.com> Co-authored-by: gornekich Co-authored-by: あく --- .../main/nfc/helpers/nfc_custom_event.h | 2 + applications/main/nfc/nfc.c | 3 + .../main/nfc/scenes/nfc_scene_config.h | 7 + .../main/nfc/scenes/nfc_scene_delete.c | 7 +- .../main/nfc/scenes/nfc_scene_extra_actions.c | 20 + .../main/nfc/scenes/nfc_scene_nfc_data_info.c | 162 +- .../main/nfc/scenes/nfc_scene_nfcv_emulate.c | 169 ++ .../nfc/scenes/nfc_scene_nfcv_key_input.c | 48 + .../main/nfc/scenes/nfc_scene_nfcv_menu.c | 68 + .../nfc/scenes/nfc_scene_nfcv_read_success.c | 94 ++ .../main/nfc/scenes/nfc_scene_nfcv_sniff.c | 155 ++ .../main/nfc/scenes/nfc_scene_nfcv_unlock.c | 154 ++ .../nfc/scenes/nfc_scene_nfcv_unlock_menu.c | 60 + applications/main/nfc/scenes/nfc_scene_read.c | 5 + applications/main/nfc/scenes/nfc_scene_rpc.c | 7 + .../main/nfc/scenes/nfc_scene_saved_menu.c | 3 + firmware/targets/f7/api_symbols.csv | 8 + lib/digital_signal/digital_signal.c | 13 +- lib/nfc/nfc_device.c | 376 ++++- lib/nfc/nfc_device.h | 4 + lib/nfc/nfc_worker.c | 245 ++- lib/nfc/nfc_worker.h | 11 + lib/nfc/nfc_worker_i.h | 2 + lib/nfc/protocols/nfcv.c | 1398 +++++++++++++++++ lib/nfc/protocols/nfcv.h | 291 ++++ lib/nfc/protocols/slix.c | 412 +++++ lib/nfc/protocols/slix.h | 46 + 27 files changed, 3737 insertions(+), 33 deletions(-) create mode 100644 applications/main/nfc/scenes/nfc_scene_nfcv_emulate.c create mode 100644 applications/main/nfc/scenes/nfc_scene_nfcv_key_input.c create mode 100644 applications/main/nfc/scenes/nfc_scene_nfcv_menu.c create mode 100644 applications/main/nfc/scenes/nfc_scene_nfcv_read_success.c create mode 100644 applications/main/nfc/scenes/nfc_scene_nfcv_sniff.c create mode 100644 applications/main/nfc/scenes/nfc_scene_nfcv_unlock.c create mode 100644 applications/main/nfc/scenes/nfc_scene_nfcv_unlock_menu.c create mode 100644 lib/nfc/protocols/nfcv.c create mode 100644 lib/nfc/protocols/nfcv.h create mode 100644 lib/nfc/protocols/slix.c create mode 100644 lib/nfc/protocols/slix.h diff --git a/applications/main/nfc/helpers/nfc_custom_event.h b/applications/main/nfc/helpers/nfc_custom_event.h index 4227a5b14e3..aa932a3d857 100644 --- a/applications/main/nfc/helpers/nfc_custom_event.h +++ b/applications/main/nfc/helpers/nfc_custom_event.h @@ -12,4 +12,6 @@ enum NfcCustomEvent { NfcCustomEventDictAttackSkip, NfcCustomEventRpcLoad, NfcCustomEventRpcSessionClose, + NfcCustomEventUpdateLog, + NfcCustomEventSaveShadow, }; diff --git a/applications/main/nfc/nfc.c b/applications/main/nfc/nfc.c index 4540f5d9f06..f68b7f2f24f 100644 --- a/applications/main/nfc/nfc.c +++ b/applications/main/nfc/nfc.c @@ -290,6 +290,9 @@ int32_t nfc_app(void* p) { } else if(nfc->dev->format == NfcDeviceSaveFormatMifareClassic) { scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicEmulate); DOLPHIN_DEED(DolphinDeedNfcEmulate); + } else if(nfc->dev->format == NfcDeviceSaveFormatNfcV) { + scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcVEmulate); + DOLPHIN_DEED(DolphinDeedNfcEmulate); } else if(nfc->dev->format == NfcDeviceSaveFormatBankCard) { scene_manager_next_scene(nfc->scene_manager, NfcSceneDeviceInfo); } else { diff --git a/applications/main/nfc/scenes/nfc_scene_config.h b/applications/main/nfc/scenes/nfc_scene_config.h index a9da07dfda0..f11d1479838 100644 --- a/applications/main/nfc/scenes/nfc_scene_config.h +++ b/applications/main/nfc/scenes/nfc_scene_config.h @@ -14,6 +14,13 @@ ADD_SCENE(nfc, file_select, FileSelect) ADD_SCENE(nfc, emulate_uid, EmulateUid) ADD_SCENE(nfc, nfca_read_success, NfcaReadSuccess) ADD_SCENE(nfc, nfca_menu, NfcaMenu) +ADD_SCENE(nfc, nfcv_menu, NfcVMenu) +ADD_SCENE(nfc, nfcv_unlock_menu, NfcVUnlockMenu) +ADD_SCENE(nfc, nfcv_key_input, NfcVKeyInput) +ADD_SCENE(nfc, nfcv_unlock, NfcVUnlock) +ADD_SCENE(nfc, nfcv_emulate, NfcVEmulate) +ADD_SCENE(nfc, nfcv_sniff, NfcVSniff) +ADD_SCENE(nfc, nfcv_read_success, NfcVReadSuccess) ADD_SCENE(nfc, mf_ultralight_read_success, MfUltralightReadSuccess) ADD_SCENE(nfc, mf_ultralight_data, MfUltralightData) ADD_SCENE(nfc, mf_ultralight_menu, MfUltralightMenu) diff --git a/applications/main/nfc/scenes/nfc_scene_delete.c b/applications/main/nfc/scenes/nfc_scene_delete.c index cbb52bfd0f2..0808db45a32 100644 --- a/applications/main/nfc/scenes/nfc_scene_delete.c +++ b/applications/main/nfc/scenes/nfc_scene_delete.c @@ -31,6 +31,8 @@ void nfc_scene_delete_on_enter(void* context) { nfc->widget, 64, 24, AlignCenter, AlignTop, FontSecondary, furi_string_get_cstr(temp_str)); NfcProtocol protocol = nfc->dev->dev_data.protocol; + const char* nfc_type = "NFC-A"; + if(protocol == NfcDeviceProtocolEMV) { furi_string_set(temp_str, "EMV bank card"); } else if(protocol == NfcDeviceProtocolMifareUl) { @@ -39,12 +41,15 @@ void nfc_scene_delete_on_enter(void* context) { furi_string_set(temp_str, nfc_mf_classic_type(nfc->dev->dev_data.mf_classic_data.type)); } else if(protocol == NfcDeviceProtocolMifareDesfire) { furi_string_set(temp_str, "MIFARE DESFire"); + } else if(protocol == NfcDeviceProtocolNfcV) { + furi_string_set(temp_str, "ISO15693 tag"); + nfc_type = "NFC-V"; } else { furi_string_set(temp_str, "Unknown ISO tag"); } widget_add_string_element( nfc->widget, 64, 34, AlignCenter, AlignTop, FontSecondary, furi_string_get_cstr(temp_str)); - widget_add_string_element(nfc->widget, 64, 44, AlignCenter, AlignTop, FontSecondary, "NFC-A"); + widget_add_string_element(nfc->widget, 64, 44, AlignCenter, AlignTop, FontSecondary, nfc_type); furi_string_free(temp_str); view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); diff --git a/applications/main/nfc/scenes/nfc_scene_extra_actions.c b/applications/main/nfc/scenes/nfc_scene_extra_actions.c index 66aaf5a26dc..7f5bc7e7585 100644 --- a/applications/main/nfc/scenes/nfc_scene_extra_actions.c +++ b/applications/main/nfc/scenes/nfc_scene_extra_actions.c @@ -4,6 +4,8 @@ enum SubmenuIndex { SubmenuIndexReadCardType, SubmenuIndexMfClassicKeys, SubmenuIndexMfUltralightUnlock, + SubmenuIndexNfcVUnlock, + SubmenuIndexNfcVSniff, }; void nfc_scene_extra_actions_submenu_callback(void* context, uint32_t index) { @@ -34,6 +36,18 @@ void nfc_scene_extra_actions_on_enter(void* context) { SubmenuIndexMfUltralightUnlock, nfc_scene_extra_actions_submenu_callback, nfc); + submenu_add_item( + submenu, + "Unlock SLIX-L", + SubmenuIndexNfcVUnlock, + nfc_scene_extra_actions_submenu_callback, + nfc); + submenu_add_item( + submenu, + "Listen NfcV Reader", + SubmenuIndexNfcVSniff, + nfc_scene_extra_actions_submenu_callback, + nfc); submenu_set_selected_item( submenu, scene_manager_get_scene_state(nfc->scene_manager, NfcSceneExtraActions)); view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); @@ -58,6 +72,12 @@ bool nfc_scene_extra_actions_on_event(void* context, SceneManagerEvent event) { scene_manager_set_scene_state(nfc->scene_manager, NfcSceneReadCardType, 0); scene_manager_next_scene(nfc->scene_manager, NfcSceneReadCardType); consumed = true; + } else if(event.event == SubmenuIndexNfcVUnlock) { + scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcVUnlockMenu); + consumed = true; + } else if(event.event == SubmenuIndexNfcVSniff) { + scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcVSniff); + consumed = true; } scene_manager_set_scene_state(nfc->scene_manager, NfcSceneExtraActions, event.event); } diff --git a/applications/main/nfc/scenes/nfc_scene_nfc_data_info.c b/applications/main/nfc/scenes/nfc_scene_nfc_data_info.c index b44bb5e64cd..eb2f939c60f 100644 --- a/applications/main/nfc/scenes/nfc_scene_nfc_data_info.c +++ b/applications/main/nfc/scenes/nfc_scene_nfc_data_info.c @@ -41,19 +41,165 @@ void nfc_scene_nfc_data_info_on_enter(void* context) { temp_str, "\e#%s\n", nfc_mf_classic_type(dev_data->mf_classic_data.type)); } else if(protocol == NfcDeviceProtocolMifareDesfire) { furi_string_cat_printf(temp_str, "\e#MIFARE DESFire\n"); + } else if(protocol == NfcDeviceProtocolNfcV) { + switch(dev_data->nfcv_data.sub_type) { + case NfcVTypePlain: + furi_string_cat_printf(temp_str, "\e#ISO15693\n"); + break; + case NfcVTypeSlix: + furi_string_cat_printf(temp_str, "\e#ISO15693 SLIX\n"); + break; + case NfcVTypeSlixS: + furi_string_cat_printf(temp_str, "\e#ISO15693 SLIX-S\n"); + break; + case NfcVTypeSlixL: + furi_string_cat_printf(temp_str, "\e#ISO15693 SLIX-L\n"); + break; + case NfcVTypeSlix2: + furi_string_cat_printf(temp_str, "\e#ISO15693 SLIX2\n"); + break; + default: + furi_string_cat_printf(temp_str, "\e#ISO15693 (unknown)\n"); + break; + } } else { furi_string_cat_printf(temp_str, "\e#Unknown ISO tag\n"); } // Set tag iso data - char iso_type = FURI_BIT(nfc_data->sak, 5) ? '4' : '3'; - furi_string_cat_printf(temp_str, "ISO 14443-%c (NFC-A)\n", iso_type); - furi_string_cat_printf(temp_str, "UID:"); - for(size_t i = 0; i < nfc_data->uid_len; i++) { - furi_string_cat_printf(temp_str, " %02X", nfc_data->uid[i]); + if(protocol == NfcDeviceProtocolNfcV) { + NfcVData* nfcv_data = &nfc->dev->dev_data.nfcv_data; + + furi_string_cat_printf(temp_str, "UID:\n"); + for(size_t i = 0; i < nfc_data->uid_len; i++) { + furi_string_cat_printf(temp_str, " %02X", nfc_data->uid[i]); + } + furi_string_cat_printf(temp_str, "\n"); + + furi_string_cat_printf( + temp_str, + "DSFID: %02X %s\n", + nfcv_data->dsfid, + (nfcv_data->security_status[0] & NfcVLockBitDsfid) ? "(locked)" : ""); + furi_string_cat_printf( + temp_str, + "AFI: %02X %s\n", + nfcv_data->afi, + (nfcv_data->security_status[0] & NfcVLockBitAfi) ? "(locked)" : ""); + furi_string_cat_printf(temp_str, "IC Ref: %02X\n", nfcv_data->ic_ref); + furi_string_cat_printf(temp_str, "Blocks: %02X\n", nfcv_data->block_num); + furi_string_cat_printf(temp_str, "Blocksize: %02X\n", nfcv_data->block_size); + + switch(dev_data->nfcv_data.sub_type) { + case NfcVTypePlain: + furi_string_cat_printf(temp_str, "Type: Plain\n"); + break; + case NfcVTypeSlix: + furi_string_cat_printf(temp_str, "Type: SLIX\n"); + furi_string_cat_printf(temp_str, "Keys:\n"); + furi_string_cat_printf( + temp_str, + " EAS %08llX\n", + nfc_util_bytes2num(nfcv_data->sub_data.slix.key_eas, 4)); + break; + case NfcVTypeSlixS: + furi_string_cat_printf(temp_str, "Type: SLIX-S\n"); + furi_string_cat_printf(temp_str, "Keys:\n"); + furi_string_cat_printf( + temp_str, + " Read %08llX\n", + nfc_util_bytes2num(nfcv_data->sub_data.slix.key_read, 4)); + furi_string_cat_printf( + temp_str, + " Write %08llX\n", + nfc_util_bytes2num(nfcv_data->sub_data.slix.key_write, 4)); + furi_string_cat_printf( + temp_str, + " Privacy %08llX\n", + nfc_util_bytes2num(nfcv_data->sub_data.slix.key_privacy, 4)); + furi_string_cat_printf( + temp_str, + " Destroy %08llX\n", + nfc_util_bytes2num(nfcv_data->sub_data.slix.key_destroy, 4)); + furi_string_cat_printf( + temp_str, + " EAS %08llX\n", + nfc_util_bytes2num(nfcv_data->sub_data.slix.key_eas, 4)); + break; + case NfcVTypeSlixL: + furi_string_cat_printf(temp_str, "Type: SLIX-L\n"); + furi_string_cat_printf(temp_str, "Keys:\n"); + furi_string_cat_printf( + temp_str, + " Privacy %08llX\n", + nfc_util_bytes2num(nfcv_data->sub_data.slix.key_privacy, 4)); + furi_string_cat_printf( + temp_str, + " Destroy %08llX\n", + nfc_util_bytes2num(nfcv_data->sub_data.slix.key_destroy, 4)); + furi_string_cat_printf( + temp_str, + " EAS %08llX\n", + nfc_util_bytes2num(nfcv_data->sub_data.slix.key_eas, 4)); + break; + case NfcVTypeSlix2: + furi_string_cat_printf(temp_str, "Type: SLIX2\n"); + furi_string_cat_printf(temp_str, "Keys:\n"); + furi_string_cat_printf( + temp_str, + " Read %08llX\n", + nfc_util_bytes2num(nfcv_data->sub_data.slix.key_read, 4)); + furi_string_cat_printf( + temp_str, + " Write %08llX\n", + nfc_util_bytes2num(nfcv_data->sub_data.slix.key_write, 4)); + furi_string_cat_printf( + temp_str, + " Privacy %08llX\n", + nfc_util_bytes2num(nfcv_data->sub_data.slix.key_privacy, 4)); + furi_string_cat_printf( + temp_str, + " Destroy %08llX\n", + nfc_util_bytes2num(nfcv_data->sub_data.slix.key_destroy, 4)); + furi_string_cat_printf( + temp_str, + " EAS %08llX\n", + nfc_util_bytes2num(nfcv_data->sub_data.slix.key_eas, 4)); + break; + default: + furi_string_cat_printf(temp_str, "\e#ISO15693 (unknown)\n"); + break; + } + + furi_string_cat_printf( + temp_str, "Data (%d byte)\n", nfcv_data->block_num * nfcv_data->block_size); + + int maxBlocks = nfcv_data->block_num; + if(maxBlocks > 32) { + maxBlocks = 32; + furi_string_cat_printf(temp_str, "(truncated to %d blocks)\n", maxBlocks); + } + + for(int block = 0; block < maxBlocks; block++) { + const char* status = (nfcv_data->security_status[block] & 0x01) ? "(lck)" : ""; + for(int pos = 0; pos < nfcv_data->block_size; pos++) { + furi_string_cat_printf( + temp_str, " %02X", nfcv_data->data[block * nfcv_data->block_size + pos]); + } + furi_string_cat_printf(temp_str, " %s\n", status); + } + + } else { + char iso_type = FURI_BIT(nfc_data->sak, 5) ? '4' : '3'; + furi_string_cat_printf(temp_str, "ISO 14443-%c (NFC-A)\n", iso_type); + furi_string_cat_printf(temp_str, "UID:"); + for(size_t i = 0; i < nfc_data->uid_len; i++) { + furi_string_cat_printf(temp_str, " %02X", nfc_data->uid[i]); + } + furi_string_cat_printf( + temp_str, "\nATQA: %02X %02X ", nfc_data->atqa[1], nfc_data->atqa[0]); + furi_string_cat_printf(temp_str, " SAK: %02X", nfc_data->sak); } - furi_string_cat_printf(temp_str, "\nATQA: %02X %02X ", nfc_data->atqa[1], nfc_data->atqa[0]); - furi_string_cat_printf(temp_str, " SAK: %02X", nfc_data->sak); // Set application specific data if(protocol == NfcDeviceProtocolMifareDesfire) { @@ -139,6 +285,8 @@ bool nfc_scene_nfc_data_info_on_event(void* context, SceneManagerEvent event) { consumed = true; } else if(protocol == NfcDeviceProtocolMifareClassic) { scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicData); + } else if(protocol == NfcDeviceProtocolNfcV) { + scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcVMenu); consumed = true; } } diff --git a/applications/main/nfc/scenes/nfc_scene_nfcv_emulate.c b/applications/main/nfc/scenes/nfc_scene_nfcv_emulate.c new file mode 100644 index 00000000000..3dd7c460b50 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_nfcv_emulate.c @@ -0,0 +1,169 @@ +#include "../nfc_i.h" + +#define NFC_SCENE_EMULATE_NFCV_LOG_SIZE_MAX (200) + +enum { + NfcSceneNfcVEmulateStateWidget, + NfcSceneNfcVEmulateStateTextBox, +}; + +bool nfc_scene_nfcv_emulate_worker_callback(NfcWorkerEvent event, void* context) { + furi_assert(context); + Nfc* nfc = context; + + switch(event) { + case NfcWorkerEventNfcVCommandExecuted: + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventUpdateLog); + } + break; + case NfcWorkerEventNfcVContentChanged: + view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventSaveShadow); + break; + default: + break; + } + return true; +} + +void nfc_scene_nfcv_emulate_widget_callback(GuiButtonType result, InputType type, void* context) { + furi_assert(context); + Nfc* nfc = context; + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(nfc->view_dispatcher, result); + } +} + +void nfc_scene_nfcv_emulate_textbox_callback(void* context) { + furi_assert(context); + Nfc* nfc = context; + view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit); +} + +static void nfc_scene_nfcv_emulate_widget_config(Nfc* nfc, bool data_received) { + FuriHalNfcDevData* data = &nfc->dev->dev_data.nfc_data; + Widget* widget = nfc->widget; + widget_reset(widget); + FuriString* info_str; + info_str = furi_string_alloc(); + + widget_add_icon_element(widget, 0, 3, &I_NFC_dolphin_emulation_47x61); + widget_add_string_multiline_element( + widget, 87, 13, AlignCenter, AlignTop, FontPrimary, "Emulating\nNFC V"); + if(strcmp(nfc->dev->dev_name, "")) { + furi_string_printf(info_str, "%s", nfc->dev->dev_name); + } else { + for(uint8_t i = 0; i < data->uid_len; i++) { + furi_string_cat_printf(info_str, "%02X ", data->uid[i]); + } + } + furi_string_trim(info_str); + widget_add_text_box_element( + widget, 52, 40, 70, 21, AlignCenter, AlignTop, furi_string_get_cstr(info_str), true); + furi_string_free(info_str); + if(data_received) { + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + widget_add_button_element( + widget, GuiButtonTypeCenter, "Log", nfc_scene_nfcv_emulate_widget_callback, nfc); + } + } +} + +void nfc_scene_nfcv_emulate_on_enter(void* context) { + Nfc* nfc = context; + + // Setup Widget + nfc_scene_nfcv_emulate_widget_config(nfc, false); + // Setup TextBox + TextBox* text_box = nfc->text_box; + text_box_set_font(text_box, TextBoxFontHex); + text_box_set_focus(text_box, TextBoxFocusEnd); + text_box_set_text(text_box, ""); + furi_string_reset(nfc->text_box_store); + + // Set Widget state and view + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneNfcVEmulate, NfcSceneNfcVEmulateStateWidget); + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); + // Start worker + memset(&nfc->dev->dev_data.reader_data, 0, sizeof(NfcReaderRequestData)); + nfc_worker_start( + nfc->worker, + NfcWorkerStateNfcVEmulate, + &nfc->dev->dev_data, + nfc_scene_nfcv_emulate_worker_callback, + nfc); + + nfc_blink_emulate_start(nfc); +} + +bool nfc_scene_nfcv_emulate_on_event(void* context, SceneManagerEvent event) { + Nfc* nfc = context; + NfcVData* nfcv_data = &nfc->dev->dev_data.nfcv_data; + uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneNfcVEmulate); + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcCustomEventUpdateLog) { + // Add data button to widget if data is received for the first time + if(strlen(nfcv_data->last_command) > 0) { + if(!furi_string_size(nfc->text_box_store)) { + nfc_scene_nfcv_emulate_widget_config(nfc, true); + } + /* use the last n bytes from the log so there's enough space for the new log entry */ + size_t maxSize = + NFC_SCENE_EMULATE_NFCV_LOG_SIZE_MAX - (strlen(nfcv_data->last_command) + 1); + if(furi_string_size(nfc->text_box_store) >= maxSize) { + furi_string_right(nfc->text_box_store, (strlen(nfcv_data->last_command) + 1)); + } + furi_string_cat_printf(nfc->text_box_store, "%s", nfcv_data->last_command); + furi_string_push_back(nfc->text_box_store, '\n'); + text_box_set_text(nfc->text_box, furi_string_get_cstr(nfc->text_box_store)); + + /* clear previously logged command */ + strcpy(nfcv_data->last_command, ""); + } + consumed = true; + } else if(event.event == NfcCustomEventSaveShadow) { + if(furi_string_size(nfc->dev->load_path)) { + nfc_device_save_shadow(nfc->dev, furi_string_get_cstr(nfc->dev->load_path)); + } + consumed = true; + } else if(event.event == GuiButtonTypeCenter && state == NfcSceneNfcVEmulateStateWidget) { + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewTextBox); + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneNfcVEmulate, NfcSceneNfcVEmulateStateTextBox); + } + consumed = true; + } else if(event.event == NfcCustomEventViewExit && state == NfcSceneNfcVEmulateStateTextBox) { + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneNfcVEmulate, NfcSceneNfcVEmulateStateWidget); + consumed = true; + } + } else if(event.type == SceneManagerEventTypeBack) { + if(state == NfcSceneNfcVEmulateStateTextBox) { + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneNfcVEmulate, NfcSceneNfcVEmulateStateWidget); + consumed = true; + } + } + + return consumed; +} + +void nfc_scene_nfcv_emulate_on_exit(void* context) { + Nfc* nfc = context; + + // Stop worker + nfc_worker_stop(nfc->worker); + + // Clear view + widget_reset(nfc->widget); + text_box_reset(nfc->text_box); + furi_string_reset(nfc->text_box_store); + + nfc_blink_stop(nfc); +} diff --git a/applications/main/nfc/scenes/nfc_scene_nfcv_key_input.c b/applications/main/nfc/scenes/nfc_scene_nfcv_key_input.c new file mode 100644 index 00000000000..cc53c4dcb43 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_nfcv_key_input.c @@ -0,0 +1,48 @@ +#include "../nfc_i.h" +#include + +void nfc_scene_nfcv_key_input_byte_input_callback(void* context) { + Nfc* nfc = context; + NfcVSlixData* data = &nfc->dev->dev_data.nfcv_data.sub_data.slix; + + memcpy(data->key_privacy, nfc->byte_input_store, 4); + view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventByteInputDone); +} + +void nfc_scene_nfcv_key_input_on_enter(void* context) { + Nfc* nfc = context; + + // Setup view + ByteInput* byte_input = nfc->byte_input; + byte_input_set_header_text(byte_input, "Enter The Password In Hex"); + byte_input_set_result_callback( + byte_input, + nfc_scene_nfcv_key_input_byte_input_callback, + NULL, + nfc, + nfc->byte_input_store, + 4); + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewByteInput); +} + +bool nfc_scene_nfcv_key_input_on_event(void* context, SceneManagerEvent event) { + Nfc* nfc = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcCustomEventByteInputDone) { + scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcVUnlock); + DOLPHIN_DEED(DolphinDeedNfcRead); + consumed = true; + } + } + return consumed; +} + +void nfc_scene_nfcv_key_input_on_exit(void* context) { + Nfc* nfc = context; + + // Clear view + byte_input_set_result_callback(nfc->byte_input, NULL, NULL, NULL, NULL, 0); + byte_input_set_header_text(nfc->byte_input, ""); +} diff --git a/applications/main/nfc/scenes/nfc_scene_nfcv_menu.c b/applications/main/nfc/scenes/nfc_scene_nfcv_menu.c new file mode 100644 index 00000000000..7c6780b7c76 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_nfcv_menu.c @@ -0,0 +1,68 @@ +#include "../nfc_i.h" +#include + +enum SubmenuIndex { + SubmenuIndexSave, + SubmenuIndexEmulate, + SubmenuIndexInfo, +}; + +void nfc_scene_nfcv_menu_submenu_callback(void* context, uint32_t index) { + Nfc* nfc = context; + + view_dispatcher_send_custom_event(nfc->view_dispatcher, index); +} + +void nfc_scene_nfcv_menu_on_enter(void* context) { + Nfc* nfc = context; + Submenu* submenu = nfc->submenu; + + submenu_add_item( + submenu, "Emulate", SubmenuIndexEmulate, nfc_scene_nfcv_menu_submenu_callback, nfc); + submenu_add_item(submenu, "Save", SubmenuIndexSave, nfc_scene_nfcv_menu_submenu_callback, nfc); + submenu_add_item(submenu, "Info", SubmenuIndexInfo, nfc_scene_nfcv_menu_submenu_callback, nfc); + + submenu_set_selected_item( + nfc->submenu, scene_manager_get_scene_state(nfc->scene_manager, NfcSceneNfcVMenu)); + + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); +} + +bool nfc_scene_nfcv_menu_on_event(void* context, SceneManagerEvent event) { + Nfc* nfc = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubmenuIndexSave) { + nfc->dev->format = NfcDeviceSaveFormatNfcV; + // 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_next_scene(nfc->scene_manager, NfcSceneNfcVEmulate); + if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSetType)) { + DOLPHIN_DEED(DolphinDeedNfcAddEmulate); + } else { + DOLPHIN_DEED(DolphinDeedNfcEmulate); + } + consumed = true; + } else if(event.event == SubmenuIndexInfo) { + scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcDataInfo); + consumed = true; + } + scene_manager_set_scene_state(nfc->scene_manager, NfcSceneNfcVMenu, event.event); + + } else if(event.type == SceneManagerEventTypeBack) { + consumed = scene_manager_previous_scene(nfc->scene_manager); + } + + return consumed; +} + +void nfc_scene_nfcv_menu_on_exit(void* context) { + Nfc* nfc = context; + + // Clear view + submenu_reset(nfc->submenu); +} diff --git a/applications/main/nfc/scenes/nfc_scene_nfcv_read_success.c b/applications/main/nfc/scenes/nfc_scene_nfcv_read_success.c new file mode 100644 index 00000000000..bdf7692ccb2 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_nfcv_read_success.c @@ -0,0 +1,94 @@ +#include "../nfc_i.h" + +void nfc_scene_nfcv_read_success_widget_callback( + GuiButtonType result, + InputType type, + void* context) { + furi_assert(context); + Nfc* nfc = context; + + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(nfc->view_dispatcher, result); + } +} + +void nfc_scene_nfcv_read_success_on_enter(void* context) { + Nfc* nfc = context; + NfcDeviceData* dev_data = &nfc->dev->dev_data; + FuriHalNfcDevData* nfc_data = &nfc->dev->dev_data.nfc_data; + NfcVData* nfcv_data = &nfc->dev->dev_data.nfcv_data; + // Setup view + Widget* widget = nfc->widget; + widget_add_button_element( + widget, GuiButtonTypeLeft, "Retry", nfc_scene_nfcv_read_success_widget_callback, nfc); + widget_add_button_element( + widget, GuiButtonTypeRight, "More", nfc_scene_nfcv_read_success_widget_callback, nfc); + + FuriString* temp_str = furi_string_alloc(); + + switch(dev_data->nfcv_data.sub_type) { + case NfcVTypePlain: + furi_string_cat_printf(temp_str, "\e#ISO15693\n"); + break; + case NfcVTypeSlix: + furi_string_cat_printf(temp_str, "\e#ISO15693 SLIX\n"); + break; + case NfcVTypeSlixS: + furi_string_cat_printf(temp_str, "\e#ISO15693 SLIX-S\n"); + break; + case NfcVTypeSlixL: + furi_string_cat_printf(temp_str, "\e#ISO15693 SLIX-L\n"); + break; + case NfcVTypeSlix2: + furi_string_cat_printf(temp_str, "\e#ISO15693 SLIX2\n"); + break; + default: + furi_string_cat_printf(temp_str, "\e#ISO15693 (unknown)\n"); + break; + } + furi_string_cat_printf(temp_str, "UID:"); + for(size_t i = 0; i < nfc_data->uid_len; i++) { + furi_string_cat_printf(temp_str, " %02X", nfc_data->uid[i]); + } + furi_string_cat_printf(temp_str, "\n"); + furi_string_cat_printf(temp_str, "Blocks: %02X\n", nfcv_data->block_num); + furi_string_cat_printf(temp_str, "Blocksize: %02X\n", nfcv_data->block_size); + + widget_add_text_scroll_element(widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); + furi_string_free(temp_str); + + notification_message_block(nfc->notifications, &sequence_set_green_255); + + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); +} + +bool nfc_scene_nfcv_read_success_on_event(void* context, SceneManagerEvent event) { + Nfc* nfc = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == GuiButtonTypeLeft) { + scene_manager_next_scene(nfc->scene_manager, NfcSceneRetryConfirm); + consumed = true; + } else if(event.event == GuiButtonTypeRight) { + // Clear device name + nfc_device_set_name(nfc->dev, ""); + scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcVMenu); + consumed = true; + } + } else if(event.type == SceneManagerEventTypeBack) { + scene_manager_next_scene(nfc->scene_manager, NfcSceneExitConfirm); + consumed = true; + } + + return consumed; +} + +void nfc_scene_nfcv_read_success_on_exit(void* context) { + Nfc* nfc = context; + + notification_message_block(nfc->notifications, &sequence_reset_green); + + // Clear view + widget_reset(nfc->widget); +} diff --git a/applications/main/nfc/scenes/nfc_scene_nfcv_sniff.c b/applications/main/nfc/scenes/nfc_scene_nfcv_sniff.c new file mode 100644 index 00000000000..2c0f17981b4 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_nfcv_sniff.c @@ -0,0 +1,155 @@ +#include "../nfc_i.h" + +#define NFC_SCENE_EMULATE_NFCV_LOG_SIZE_MAX (800) + +enum { + NfcSceneNfcVSniffStateWidget, + NfcSceneNfcVSniffStateTextBox, +}; + +bool nfc_scene_nfcv_sniff_worker_callback(NfcWorkerEvent event, void* context) { + UNUSED(event); + furi_assert(context); + Nfc* nfc = context; + + switch(event) { + case NfcWorkerEventNfcVCommandExecuted: + view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventUpdateLog); + break; + case NfcWorkerEventNfcVContentChanged: + view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventSaveShadow); + break; + default: + break; + } + return true; +} + +void nfc_scene_nfcv_sniff_widget_callback(GuiButtonType result, InputType type, void* context) { + furi_assert(context); + Nfc* nfc = context; + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(nfc->view_dispatcher, result); + } +} + +void nfc_scene_nfcv_sniff_textbox_callback(void* context) { + furi_assert(context); + Nfc* nfc = context; + view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit); +} + +static void nfc_scene_nfcv_sniff_widget_config(Nfc* nfc, bool data_received) { + Widget* widget = nfc->widget; + widget_reset(widget); + FuriString* info_str; + info_str = furi_string_alloc(); + + widget_add_icon_element(widget, 0, 3, &I_RFIDDolphinSend_97x61); + widget_add_string_element(widget, 89, 32, AlignCenter, AlignTop, FontPrimary, "Listen NfcV"); + furi_string_trim(info_str); + widget_add_text_box_element( + widget, 56, 43, 70, 21, AlignCenter, AlignTop, furi_string_get_cstr(info_str), true); + furi_string_free(info_str); + if(data_received) { + widget_add_button_element( + widget, GuiButtonTypeCenter, "Log", nfc_scene_nfcv_sniff_widget_callback, nfc); + } +} + +void nfc_scene_nfcv_sniff_on_enter(void* context) { + Nfc* nfc = context; + + // Setup Widget + nfc_scene_nfcv_sniff_widget_config(nfc, false); + // Setup TextBox + TextBox* text_box = nfc->text_box; + text_box_set_font(text_box, TextBoxFontHex); + text_box_set_focus(text_box, TextBoxFocusEnd); + text_box_set_text(text_box, ""); + furi_string_reset(nfc->text_box_store); + + // Set Widget state and view + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneNfcVSniff, NfcSceneNfcVSniffStateWidget); + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); + // Start worker + memset(&nfc->dev->dev_data.reader_data, 0, sizeof(NfcReaderRequestData)); + nfc_worker_start( + nfc->worker, + NfcWorkerStateNfcVSniff, + &nfc->dev->dev_data, + nfc_scene_nfcv_sniff_worker_callback, + nfc); + + nfc_blink_emulate_start(nfc); +} + +bool nfc_scene_nfcv_sniff_on_event(void* context, SceneManagerEvent event) { + Nfc* nfc = context; + NfcVData* nfcv_data = &nfc->dev->dev_data.nfcv_data; + uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneNfcVSniff); + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcCustomEventUpdateLog) { + // Add data button to widget if data is received for the first time + if(strlen(nfcv_data->last_command) > 0) { + if(!furi_string_size(nfc->text_box_store)) { + nfc_scene_nfcv_sniff_widget_config(nfc, true); + } + /* use the last n bytes from the log so there's enough space for the new log entry */ + size_t maxSize = + NFC_SCENE_EMULATE_NFCV_LOG_SIZE_MAX - (strlen(nfcv_data->last_command) + 1); + if(furi_string_size(nfc->text_box_store) >= maxSize) { + furi_string_right(nfc->text_box_store, (strlen(nfcv_data->last_command) + 1)); + } + furi_string_cat_printf(nfc->text_box_store, "%s", nfcv_data->last_command); + furi_string_push_back(nfc->text_box_store, '\n'); + text_box_set_text(nfc->text_box, furi_string_get_cstr(nfc->text_box_store)); + + /* clear previously logged command */ + strcpy(nfcv_data->last_command, ""); + } + consumed = true; + } else if(event.event == NfcCustomEventSaveShadow) { + if(furi_string_size(nfc->dev->load_path)) { + nfc_device_save_shadow(nfc->dev, furi_string_get_cstr(nfc->dev->load_path)); + } + consumed = true; + } else if(event.event == GuiButtonTypeCenter && state == NfcSceneNfcVSniffStateWidget) { + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewTextBox); + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneNfcVSniff, NfcSceneNfcVSniffStateTextBox); + consumed = true; + } else if(event.event == NfcCustomEventViewExit && state == NfcSceneNfcVSniffStateTextBox) { + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneNfcVSniff, NfcSceneNfcVSniffStateWidget); + consumed = true; + } + } else if(event.type == SceneManagerEventTypeBack) { + if(state == NfcSceneNfcVSniffStateTextBox) { + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneNfcVSniff, NfcSceneNfcVSniffStateWidget); + consumed = true; + } + } + + return consumed; +} + +void nfc_scene_nfcv_sniff_on_exit(void* context) { + Nfc* nfc = context; + + // Stop worker + nfc_worker_stop(nfc->worker); + + // Clear view + widget_reset(nfc->widget); + text_box_reset(nfc->text_box); + furi_string_reset(nfc->text_box_store); + + nfc_blink_stop(nfc); +} diff --git a/applications/main/nfc/scenes/nfc_scene_nfcv_unlock.c b/applications/main/nfc/scenes/nfc_scene_nfcv_unlock.c new file mode 100644 index 00000000000..26de304de54 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_nfcv_unlock.c @@ -0,0 +1,154 @@ +#include "../nfc_i.h" +#include + +typedef enum { + NfcSceneNfcVUnlockStateIdle, + NfcSceneNfcVUnlockStateDetecting, + NfcSceneNfcVUnlockStateUnlocked, + NfcSceneNfcVUnlockStateAlreadyUnlocked, + NfcSceneNfcVUnlockStateNotSupportedCard, +} NfcSceneNfcVUnlockState; + +static bool nfc_scene_nfcv_unlock_worker_callback(NfcWorkerEvent event, void* context) { + Nfc* nfc = context; + NfcVSlixData* data = &nfc->dev->dev_data.nfcv_data.sub_data.slix; + + if(event == NfcWorkerEventNfcVPassKey) { + memcpy(data->key_privacy, nfc->byte_input_store, 4); + } else { + view_dispatcher_send_custom_event(nfc->view_dispatcher, event); + } + return true; +} + +void nfc_scene_nfcv_unlock_popup_callback(void* context) { + Nfc* nfc = context; + view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit); +} + +void nfc_scene_nfcv_unlock_set_state(Nfc* nfc, NfcSceneNfcVUnlockState state) { + FuriHalNfcDevData* nfc_data = &(nfc->dev->dev_data.nfc_data); + NfcVData* nfcv_data = &(nfc->dev->dev_data.nfcv_data); + + uint32_t curr_state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneNfcVUnlock); + if(curr_state != state) { + Popup* popup = nfc->popup; + if(state == NfcSceneNfcVUnlockStateDetecting) { + popup_reset(popup); + popup_set_text( + popup, "Put figurine on\nFlipper's back", 97, 24, AlignCenter, AlignTop); + popup_set_icon(popup, 0, 8, &I_NFC_manual_60x50); + } else if(state == NfcSceneNfcVUnlockStateUnlocked) { + popup_reset(popup); + + if(nfc_worker_get_state(nfc->worker) == NfcWorkerStateNfcVUnlockAndSave) { + snprintf( + nfc->dev->dev_name, + sizeof(nfc->dev->dev_name), + "SLIX_%02X%02X%02X%02X%02X%02X%02X%02X", + nfc_data->uid[0], + nfc_data->uid[1], + nfc_data->uid[2], + nfc_data->uid[3], + nfc_data->uid[4], + nfc_data->uid[5], + nfc_data->uid[6], + nfc_data->uid[7]); + + nfc->dev->format = NfcDeviceSaveFormatNfcV; + + if(nfc_save_file(nfc)) { + popup_set_header(popup, "Successfully\nsaved", 94, 3, AlignCenter, AlignTop); + } else { + popup_set_header( + popup, "Unlocked but\nsave failed!", 94, 3, AlignCenter, AlignTop); + } + } else { + popup_set_header(popup, "Successfully\nunlocked", 94, 3, AlignCenter, AlignTop); + } + + notification_message(nfc->notifications, &sequence_single_vibro); + //notification_message(nfc->notifications, &sequence_success); + + popup_set_icon(popup, 0, 6, &I_RFIDDolphinSuccess_108x57); + popup_set_context(popup, nfc); + popup_set_callback(popup, nfc_scene_nfcv_unlock_popup_callback); + popup_set_timeout(popup, 1500); + + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); + DOLPHIN_DEED(DolphinDeedNfcReadSuccess); + + } else if(state == NfcSceneNfcVUnlockStateAlreadyUnlocked) { + popup_reset(popup); + + popup_set_header(popup, "Already\nUnlocked!", 94, 3, AlignCenter, AlignTop); + popup_set_icon(popup, 0, 6, &I_RFIDDolphinSuccess_108x57); + popup_set_context(popup, nfc); + popup_set_callback(popup, nfc_scene_nfcv_unlock_popup_callback); + popup_set_timeout(popup, 1500); + + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); + } else if(state == NfcSceneNfcVUnlockStateNotSupportedCard) { + popup_reset(popup); + popup_set_header(popup, "Wrong Type Of Card!", 64, 3, AlignCenter, AlignTop); + popup_set_text(popup, nfcv_data->error, 4, 22, AlignLeft, AlignTop); + popup_set_icon(popup, 73, 20, &I_DolphinCommon_56x48); + } + scene_manager_set_scene_state(nfc->scene_manager, NfcSceneNfcVUnlock, state); + } +} + +void nfc_scene_nfcv_unlock_on_enter(void* context) { + Nfc* nfc = context; + + nfc_device_clear(nfc->dev); + // Setup view + nfc_scene_nfcv_unlock_set_state(nfc, NfcSceneNfcVUnlockStateDetecting); + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); + + // Start worker + nfc_worker_start( + nfc->worker, + NfcWorkerStateNfcVUnlockAndSave, + &nfc->dev->dev_data, + nfc_scene_nfcv_unlock_worker_callback, + nfc); + + nfc_blink_read_start(nfc); +} + +bool nfc_scene_nfcv_unlock_on_event(void* context, SceneManagerEvent event) { + Nfc* nfc = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcWorkerEventCardDetected) { + nfc_scene_nfcv_unlock_set_state(nfc, NfcSceneNfcVUnlockStateUnlocked); + consumed = true; + } else if(event.event == NfcWorkerEventAborted) { + nfc_scene_nfcv_unlock_set_state(nfc, NfcSceneNfcVUnlockStateAlreadyUnlocked); + consumed = true; + } else if(event.event == NfcWorkerEventNoCardDetected) { + nfc_scene_nfcv_unlock_set_state(nfc, NfcSceneNfcVUnlockStateDetecting); + consumed = true; + } else if(event.event == NfcWorkerEventWrongCardDetected) { + nfc_scene_nfcv_unlock_set_state(nfc, NfcSceneNfcVUnlockStateNotSupportedCard); + } + } else if(event.type == SceneManagerEventTypeBack) { + consumed = scene_manager_search_and_switch_to_previous_scene( + nfc->scene_manager, NfcSceneNfcVUnlockMenu); + } + return consumed; +} + +void nfc_scene_nfcv_unlock_on_exit(void* context) { + Nfc* nfc = context; + + // Stop worker + nfc_worker_stop(nfc->worker); + // Clear view + popup_reset(nfc->popup); + nfc_blink_stop(nfc); + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneNfcVUnlock, NfcSceneNfcVUnlockStateIdle); +} diff --git a/applications/main/nfc/scenes/nfc_scene_nfcv_unlock_menu.c b/applications/main/nfc/scenes/nfc_scene_nfcv_unlock_menu.c new file mode 100644 index 00000000000..9c4c81fbda6 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_nfcv_unlock_menu.c @@ -0,0 +1,60 @@ +#include "../nfc_i.h" +#include + +enum SubmenuIndex { + SubmenuIndexNfcVUnlockMenuManual, + SubmenuIndexNfcVUnlockMenuTonieBox, +}; + +void nfc_scene_nfcv_unlock_menu_submenu_callback(void* context, uint32_t index) { + Nfc* nfc = context; + + view_dispatcher_send_custom_event(nfc->view_dispatcher, index); +} + +void nfc_scene_nfcv_unlock_menu_on_enter(void* context) { + Nfc* nfc = context; + Submenu* submenu = nfc->submenu; + + uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneNfcVUnlockMenu); + submenu_add_item( + submenu, + "Enter PWD Manually", + SubmenuIndexNfcVUnlockMenuManual, + nfc_scene_nfcv_unlock_menu_submenu_callback, + nfc); + submenu_add_item( + submenu, + "Auth As TonieBox", + SubmenuIndexNfcVUnlockMenuTonieBox, + nfc_scene_nfcv_unlock_menu_submenu_callback, + nfc); + submenu_set_selected_item(submenu, state); + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); +} + +bool nfc_scene_nfcv_unlock_menu_on_event(void* context, SceneManagerEvent event) { + Nfc* nfc = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubmenuIndexNfcVUnlockMenuManual) { + nfc->dev->dev_data.nfcv_data.auth_method = NfcVAuthMethodManual; + scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcVKeyInput); + consumed = true; + } else if(event.event == SubmenuIndexNfcVUnlockMenuTonieBox) { + nfc->dev->dev_data.nfcv_data.auth_method = NfcVAuthMethodTonieBox; + scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcVUnlock); + DOLPHIN_DEED(DolphinDeedNfcRead); + consumed = true; + } + scene_manager_set_scene_state(nfc->scene_manager, NfcSceneNfcVUnlockMenu, event.event); + } + return consumed; +} + +void nfc_scene_nfcv_unlock_menu_on_exit(void* context) { + Nfc* nfc = context; + + submenu_reset(nfc->submenu); +} diff --git a/applications/main/nfc/scenes/nfc_scene_read.c b/applications/main/nfc/scenes/nfc_scene_read.c index 938f2da6754..5a1814b6c6d 100644 --- a/applications/main/nfc/scenes/nfc_scene_read.c +++ b/applications/main/nfc/scenes/nfc_scene_read.c @@ -68,6 +68,11 @@ bool nfc_scene_read_on_event(void* context, SceneManagerEvent event) { scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcaReadSuccess); DOLPHIN_DEED(DolphinDeedNfcReadSuccess); consumed = true; + } else if(event.event == NfcWorkerEventReadNfcV) { + notification_message(nfc->notifications, &sequence_success); + scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcVReadSuccess); + DOLPHIN_DEED(DolphinDeedNfcReadSuccess); + consumed = true; } else if(event.event == NfcWorkerEventReadMfUltralight) { notification_message(nfc->notifications, &sequence_success); // Set unlock password input to 0xFFFFFFFF only on fresh read diff --git a/applications/main/nfc/scenes/nfc_scene_rpc.c b/applications/main/nfc/scenes/nfc_scene_rpc.c index 60d01a30da6..d06ee756465 100644 --- a/applications/main/nfc/scenes/nfc_scene_rpc.c +++ b/applications/main/nfc/scenes/nfc_scene_rpc.c @@ -55,6 +55,13 @@ bool nfc_scene_rpc_on_event(void* context, SceneManagerEvent event) { &nfc->dev->dev_data, nfc_scene_rpc_emulate_callback, nfc); + } else if(nfc->dev->format == NfcDeviceSaveFormatNfcV) { + nfc_worker_start( + nfc->worker, + NfcWorkerStateNfcVEmulate, + &nfc->dev->dev_data, + nfc_scene_rpc_emulate_callback, + nfc); } else { nfc_worker_start( nfc->worker, NfcWorkerStateUidEmulate, &nfc->dev->dev_data, NULL, nfc); diff --git a/applications/main/nfc/scenes/nfc_scene_saved_menu.c b/applications/main/nfc/scenes/nfc_scene_saved_menu.c index 4573cdc4528..8412c17bcb7 100644 --- a/applications/main/nfc/scenes/nfc_scene_saved_menu.c +++ b/applications/main/nfc/scenes/nfc_scene_saved_menu.c @@ -44,6 +44,7 @@ void nfc_scene_saved_menu_on_enter(void* context) { } else if( (nfc->dev->format == NfcDeviceSaveFormatMifareUl && mf_ul_emulation_supported(&nfc->dev->dev_data.mf_ul_data)) || + nfc->dev->format == NfcDeviceSaveFormatNfcV || nfc->dev->format == NfcDeviceSaveFormatMifareClassic) { submenu_add_item( submenu, "Emulate", SubmenuIndexEmulate, nfc_scene_saved_menu_submenu_callback, nfc); @@ -118,6 +119,8 @@ bool nfc_scene_saved_menu_on_event(void* context, SceneManagerEvent event) { scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightEmulate); } else if(nfc->dev->format == NfcDeviceSaveFormatMifareClassic) { scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicEmulate); + } else if(nfc->dev->format == NfcDeviceSaveFormatNfcV) { + scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcVEmulate); } else { scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateUid); } diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index b6eb8c76588..e2de368363d 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -2090,6 +2090,14 @@ Function,-,nfca_get_crc16,uint16_t,"uint8_t*, uint16_t" Function,-,nfca_signal_alloc,NfcaSignal*, Function,-,nfca_signal_encode,void,"NfcaSignal*, uint8_t*, uint16_t, uint8_t*" Function,-,nfca_signal_free,void,NfcaSignal* +Function,+,nfcv_emu_deinit,void,NfcVData* +Function,+,nfcv_emu_init,void,"FuriHalNfcDevData*, NfcVData*" +Function,+,nfcv_emu_loop,_Bool,"FuriHalNfcTxRxContext*, FuriHalNfcDevData*, NfcVData*, uint32_t" +Function,+,nfcv_emu_send,void,"FuriHalNfcTxRxContext*, NfcVData*, uint8_t*, uint8_t, NfcVSendFlags, uint32_t" +Function,-,nfcv_inventory,ReturnCode,uint8_t* +Function,-,nfcv_read_blocks,ReturnCode,"NfcVReader*, NfcVData*" +Function,-,nfcv_read_card,_Bool,"NfcVReader*, FuriHalNfcDevData*, NfcVData*" +Function,-,nfcv_read_sysinfo,ReturnCode,"FuriHalNfcDevData*, NfcVData*" Function,+,notification_internal_message,void,"NotificationApp*, const NotificationSequence*" Function,+,notification_internal_message_block,void,"NotificationApp*, const NotificationSequence*" Function,+,notification_message,void,"NotificationApp*, const NotificationSequence*" diff --git a/lib/digital_signal/digital_signal.c b/lib/digital_signal/digital_signal.c index 94dbeeab92d..39aa9cbc6e1 100644 --- a/lib/digital_signal/digital_signal.c +++ b/lib/digital_signal/digital_signal.c @@ -9,7 +9,7 @@ #include /* must be on bank B */ -#define DEBUG_OUTPUT gpio_ext_pb3 +// For debugging purposes use `--extra-define=DIGITAL_SIGNAL_DEBUG_OUTPUT_PIN=gpio_ext_pb3` fbt option struct ReloadBuffer { uint32_t* buffer; /* DMA ringbuffer */ @@ -194,9 +194,9 @@ void digital_signal_prepare_arr(DigitalSignal* signal) { uint32_t bit_set = internals->gpio->pin; uint32_t bit_reset = internals->gpio->pin << 16; -#ifdef DEBUG_OUTPUT - bit_set |= DEBUG_OUTPUT.pin; - bit_reset |= DEBUG_OUTPUT.pin << 16; +#ifdef DIGITAL_SIGNAL_DEBUG_OUTPUT_PIN + bit_set |= DIGITAL_SIGNAL_DEBUG_OUTPUT_PIN.pin; + bit_reset |= DIGITAL_SIGNAL_DEBUG_OUTPUT_PIN.pin << 16; #endif if(signal->start_level) { @@ -540,8 +540,9 @@ bool digital_sequence_send(DigitalSequence* sequence) { struct ReloadBuffer* dma_buffer = sequence->dma_buffer; furi_hal_gpio_init(sequence->gpio, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); -#ifdef DEBUG_OUTPUT - furi_hal_gpio_init(&DEBUG_OUTPUT, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); +#ifdef DIGITAL_SIGNAL_DEBUG_OUTPUT_PIN + furi_hal_gpio_init( + &DIGITAL_SIGNAL_DEBUG_OUTPUT_PIN, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); #endif if(sequence->bake) { diff --git a/lib/nfc/nfc_device.c b/lib/nfc/nfc_device.c index 5179130702d..9646c262e50 100644 --- a/lib/nfc/nfc_device.c +++ b/lib/nfc/nfc_device.c @@ -58,6 +58,8 @@ static void nfc_device_prepare_format_string(NfcDevice* dev, FuriString* format_ furi_string_set(format_string, "Mifare Classic"); } else if(dev->format == NfcDeviceSaveFormatMifareDesfire) { furi_string_set(format_string, "Mifare DESFire"); + } else if(dev->format == NfcDeviceSaveFormatNfcV) { + furi_string_set(format_string, "ISO15693"); } else { furi_string_set(format_string, "Unknown"); } @@ -93,6 +95,11 @@ static bool nfc_device_parse_format_string(NfcDevice* dev, FuriString* format_st dev->dev_data.protocol = NfcDeviceProtocolMifareDesfire; return true; } + if(furi_string_start_with_str(format_string, "ISO15693")) { + dev->format = NfcDeviceSaveFormatNfcV; + dev->dev_data.protocol = NfcDeviceProtocolNfcV; + return true; + } return false; } @@ -650,7 +657,327 @@ bool nfc_device_load_mifare_df_data(FlipperFormat* file, NfcDevice* dev) { return parsed; } -// Leave for backward compatibility +static bool nfc_device_save_slix_data(FlipperFormat* file, NfcDevice* dev) { + bool saved = false; + NfcVSlixData* data = &dev->dev_data.nfcv_data.sub_data.slix; + + do { + if(!flipper_format_write_comment_cstr(file, "SLIX specific data")) break; + if(!flipper_format_write_hex(file, "Password EAS", data->key_eas, sizeof(data->key_eas))) + break; + saved = true; + } while(false); + + return saved; +} + +bool nfc_device_load_slix_data(FlipperFormat* file, NfcDevice* dev) { + bool parsed = false; + NfcVSlixData* data = &dev->dev_data.nfcv_data.sub_data.slix; + memset(data, 0, sizeof(NfcVSlixData)); + + do { + if(!flipper_format_read_hex(file, "Password EAS", data->key_eas, sizeof(data->key_eas))) + break; + + parsed = true; + } while(false); + + return parsed; +} + +static bool nfc_device_save_slix_s_data(FlipperFormat* file, NfcDevice* dev) { + bool saved = false; + NfcVSlixData* data = &dev->dev_data.nfcv_data.sub_data.slix; + + do { + if(!flipper_format_write_comment_cstr(file, "SLIX-S specific data")) break; + if(!flipper_format_write_hex(file, "Password Read", data->key_read, sizeof(data->key_read))) + break; + if(!flipper_format_write_hex( + file, "Password Write", data->key_write, sizeof(data->key_write))) + break; + if(!flipper_format_write_hex( + file, "Password Privacy", data->key_privacy, sizeof(data->key_privacy))) + break; + if(!flipper_format_write_hex( + file, "Password Destroy", data->key_destroy, sizeof(data->key_destroy))) + break; + if(!flipper_format_write_hex(file, "Password EAS", data->key_eas, sizeof(data->key_eas))) + break; + if(!flipper_format_write_bool(file, "Privacy Mode", &data->privacy, 1)) break; + saved = true; + } while(false); + + return saved; +} + +bool nfc_device_load_slix_s_data(FlipperFormat* file, NfcDevice* dev) { + bool parsed = false; + NfcVSlixData* data = &dev->dev_data.nfcv_data.sub_data.slix; + memset(data, 0, sizeof(NfcVSlixData)); + + do { + if(!flipper_format_read_hex(file, "Password Read", data->key_read, sizeof(data->key_read))) + break; + if(!flipper_format_read_hex( + file, "Password Write", data->key_write, sizeof(data->key_write))) + break; + if(!flipper_format_read_hex( + file, "Password Privacy", data->key_privacy, sizeof(data->key_privacy))) + break; + if(!flipper_format_read_hex( + file, "Password Destroy", data->key_destroy, sizeof(data->key_destroy))) + break; + if(!flipper_format_read_hex(file, "Password EAS", data->key_eas, sizeof(data->key_eas))) + break; + if(!flipper_format_read_bool(file, "Privacy Mode", &data->privacy, 1)) break; + + parsed = true; + } while(false); + + return parsed; +} + +static bool nfc_device_save_slix_l_data(FlipperFormat* file, NfcDevice* dev) { + bool saved = false; + NfcVSlixData* data = &dev->dev_data.nfcv_data.sub_data.slix; + + do { + if(!flipper_format_write_comment_cstr(file, "SLIX-L specific data")) break; + if(!flipper_format_write_hex( + file, "Password Privacy", data->key_privacy, sizeof(data->key_privacy))) + break; + if(!flipper_format_write_hex( + file, "Password Destroy", data->key_destroy, sizeof(data->key_destroy))) + break; + if(!flipper_format_write_hex(file, "Password EAS", data->key_eas, sizeof(data->key_eas))) + break; + if(!flipper_format_write_bool(file, "Privacy Mode", &data->privacy, 1)) break; + saved = true; + } while(false); + + return saved; +} + +bool nfc_device_load_slix_l_data(FlipperFormat* file, NfcDevice* dev) { + bool parsed = false; + NfcVSlixData* data = &dev->dev_data.nfcv_data.sub_data.slix; + memset(data, 0, sizeof(NfcVSlixData)); + + do { + if(!flipper_format_read_hex( + file, "Password Privacy", data->key_privacy, sizeof(data->key_privacy))) + break; + if(!flipper_format_read_hex( + file, "Password Destroy", data->key_destroy, sizeof(data->key_destroy))) + break; + if(!flipper_format_read_hex(file, "Password EAS", data->key_eas, sizeof(data->key_eas))) + break; + if(!flipper_format_read_bool(file, "Privacy Mode", &data->privacy, 1)) break; + + parsed = true; + } while(false); + + return parsed; +} + +static bool nfc_device_save_slix2_data(FlipperFormat* file, NfcDevice* dev) { + bool saved = false; + NfcVSlixData* data = &dev->dev_data.nfcv_data.sub_data.slix; + + do { + if(!flipper_format_write_comment_cstr(file, "SLIX2 specific data")) break; + if(!flipper_format_write_hex(file, "Password Read", data->key_read, sizeof(data->key_read))) + break; + if(!flipper_format_write_hex( + file, "Password Write", data->key_write, sizeof(data->key_write))) + break; + if(!flipper_format_write_hex( + file, "Password Privacy", data->key_privacy, sizeof(data->key_privacy))) + break; + if(!flipper_format_write_hex( + file, "Password Destroy", data->key_destroy, sizeof(data->key_destroy))) + break; + if(!flipper_format_write_hex(file, "Password EAS", data->key_eas, sizeof(data->key_eas))) + break; + if(!flipper_format_write_bool(file, "Privacy Mode", &data->privacy, 1)) break; + saved = true; + } while(false); + + return saved; +} + +bool nfc_device_load_slix2_data(FlipperFormat* file, NfcDevice* dev) { + bool parsed = false; + NfcVSlixData* data = &dev->dev_data.nfcv_data.sub_data.slix; + memset(data, 0, sizeof(NfcVSlixData)); + + do { + if(!flipper_format_read_hex(file, "Password Read", data->key_read, sizeof(data->key_read))) + break; + if(!flipper_format_read_hex( + file, "Password Write", data->key_write, sizeof(data->key_write))) + break; + if(!flipper_format_read_hex( + file, "Password Privacy", data->key_privacy, sizeof(data->key_privacy))) + break; + if(!flipper_format_read_hex( + file, "Password Destroy", data->key_destroy, sizeof(data->key_destroy))) + break; + if(!flipper_format_read_hex(file, "Password EAS", data->key_eas, sizeof(data->key_eas))) + break; + if(!flipper_format_read_bool(file, "Privacy Mode", &data->privacy, 1)) break; + + parsed = true; + } while(false); + + return parsed; +} + +static bool nfc_device_save_nfcv_data(FlipperFormat* file, NfcDevice* dev) { + bool saved = false; + NfcVData* data = &dev->dev_data.nfcv_data; + + do { + uint32_t temp_uint32 = 0; + uint8_t temp_uint8 = 0; + + if(!flipper_format_write_comment_cstr(file, "Data Storage Format Identifier")) break; + if(!flipper_format_write_hex(file, "DSFID", &(data->dsfid), 1)) break; + if(!flipper_format_write_comment_cstr(file, "Application Family Identifier")) break; + if(!flipper_format_write_hex(file, "AFI", &(data->afi), 1)) break; + if(!flipper_format_write_hex(file, "IC Reference", &(data->ic_ref), 1)) break; + temp_uint32 = data->block_num; + if(!flipper_format_write_comment_cstr(file, "Number of memory blocks, usually 0 to 256")) + break; + if(!flipper_format_write_uint32(file, "Block Count", &temp_uint32, 1)) break; + if(!flipper_format_write_comment_cstr(file, "Size of a single memory block, usually 4")) + break; + if(!flipper_format_write_hex(file, "Block Size", &(data->block_size), 1)) break; + if(!flipper_format_write_hex( + file, "Data Content", data->data, data->block_num * data->block_size)) + break; + if(!flipper_format_write_comment_cstr( + file, "First byte: DSFID (0x01) / AFI (0x02) lock info, others: block lock info")) + break; + if(!flipper_format_write_hex( + file, "Security Status", data->security_status, 1 + data->block_num)) + break; + if(!flipper_format_write_comment_cstr( + file, + "Subtype of this card (0 = ISO15693, 1 = SLIX, 2 = SLIX-S, 3 = SLIX-L, 4 = SLIX2)")) + break; + temp_uint8 = (uint8_t)data->sub_type; + if(!flipper_format_write_hex(file, "Subtype", &temp_uint8, 1)) break; + + switch(data->sub_type) { + case NfcVTypePlain: + if(!flipper_format_write_comment_cstr(file, "End of ISO15693 parameters")) break; + saved = true; + break; + case NfcVTypeSlix: + saved = nfc_device_save_slix_data(file, dev); + break; + case NfcVTypeSlixS: + saved = nfc_device_save_slix_s_data(file, dev); + break; + case NfcVTypeSlixL: + saved = nfc_device_save_slix_l_data(file, dev); + break; + case NfcVTypeSlix2: + saved = nfc_device_save_slix2_data(file, dev); + break; + default: + break; + } + } while(false); + + return saved; +} + +bool nfc_device_load_nfcv_data(FlipperFormat* file, NfcDevice* dev) { + bool parsed = false; + NfcVData* data = &dev->dev_data.nfcv_data; + + memset(data, 0x00, sizeof(NfcVData)); + + do { + uint32_t temp_uint32 = 0; + uint8_t temp_value = 0; + + if(!flipper_format_read_hex(file, "DSFID", &(data->dsfid), 1)) break; + if(!flipper_format_read_hex(file, "AFI", &(data->afi), 1)) break; + if(!flipper_format_read_hex(file, "IC Reference", &(data->ic_ref), 1)) break; + if(!flipper_format_read_uint32(file, "Block Count", &temp_uint32, 1)) break; + data->block_num = temp_uint32; + if(!flipper_format_read_hex(file, "Block Size", &(data->block_size), 1)) break; + if(!flipper_format_read_hex( + file, "Data Content", data->data, data->block_num * data->block_size)) + break; + + /* optional, as added later */ + if(flipper_format_key_exist(file, "Security Status")) { + if(!flipper_format_read_hex( + file, "Security Status", data->security_status, 1 + data->block_num)) + break; + } + if(!flipper_format_read_hex(file, "Subtype", &temp_value, 1)) break; + data->sub_type = temp_value; + + switch(data->sub_type) { + case NfcVTypePlain: + parsed = true; + break; + case NfcVTypeSlix: + parsed = nfc_device_load_slix_data(file, dev); + break; + case NfcVTypeSlixS: + parsed = nfc_device_load_slix_s_data(file, dev); + break; + case NfcVTypeSlixL: + parsed = nfc_device_load_slix_l_data(file, dev); + break; + case NfcVTypeSlix2: + parsed = nfc_device_load_slix2_data(file, dev); + break; + default: + break; + } + } while(false); + + return parsed; +} + +static bool nfc_device_save_bank_card_data(FlipperFormat* file, NfcDevice* dev) { + bool saved = false; + EmvData* data = &dev->dev_data.emv_data; + uint32_t data_temp = 0; + + do { + // Write Bank card specific data + if(!flipper_format_write_comment_cstr(file, "Bank card specific data")) break; + if(!flipper_format_write_hex(file, "AID", data->aid, data->aid_len)) break; + if(!flipper_format_write_string_cstr(file, "Name", data->name)) break; + if(!flipper_format_write_hex(file, "Number", data->number, data->number_len)) break; + if(data->exp_mon) { + uint8_t exp_data[2] = {data->exp_mon, data->exp_year}; + if(!flipper_format_write_hex(file, "Exp data", exp_data, sizeof(exp_data))) break; + } + if(data->country_code) { + data_temp = data->country_code; + if(!flipper_format_write_uint32(file, "Country code", &data_temp, 1)) break; + } + if(data->currency_code) { + data_temp = data->currency_code; + if(!flipper_format_write_uint32(file, "Currency code", &data_temp, 1)) break; + } + saved = true; + } while(false); + + return saved; +} + bool nfc_device_load_bank_card_data(FlipperFormat* file, NfcDevice* dev) { bool parsed = false; EmvData* data = &dev->dev_data.emv_data; @@ -1069,23 +1396,32 @@ bool nfc_device_save(NfcDevice* dev, const char* dev_name) { if(!flipper_format_write_header_cstr(file, nfc_file_header, nfc_file_version)) break; // Write nfc device type if(!flipper_format_write_comment_cstr( - file, "Nfc device type can be UID, Mifare Ultralight, Mifare Classic")) + file, "Nfc device type can be UID, Mifare Ultralight, Mifare Classic or ISO15693")) break; nfc_device_prepare_format_string(dev, temp_str); if(!flipper_format_write_string(file, "Device type", temp_str)) break; - // Write UID, ATQA, SAK - if(!flipper_format_write_comment_cstr(file, "UID, ATQA and SAK are common for all formats")) - break; + // Write UID + if(!flipper_format_write_comment_cstr(file, "UID is common for all formats")) break; if(!flipper_format_write_hex(file, "UID", data->uid, data->uid_len)) break; - // Save ATQA in MSB order for correct companion apps display - uint8_t atqa[2] = {data->atqa[1], data->atqa[0]}; - if(!flipper_format_write_hex(file, "ATQA", atqa, 2)) break; - if(!flipper_format_write_hex(file, "SAK", &data->sak, 1)) break; + + if(dev->format != NfcDeviceSaveFormatNfcV) { + // Write ATQA, SAK + if(!flipper_format_write_comment_cstr(file, "ISO14443 specific fields")) break; + // Save ATQA in MSB order for correct companion apps display + uint8_t atqa[2] = {data->atqa[1], data->atqa[0]}; + if(!flipper_format_write_hex(file, "ATQA", atqa, 2)) break; + if(!flipper_format_write_hex(file, "SAK", &data->sak, 1)) break; + } + // Save more data if necessary if(dev->format == NfcDeviceSaveFormatMifareUl) { if(!nfc_device_save_mifare_ul_data(file, dev)) break; } else if(dev->format == NfcDeviceSaveFormatMifareDesfire) { if(!nfc_device_save_mifare_df_data(file, dev)) break; + } else if(dev->format == NfcDeviceSaveFormatNfcV) { + if(!nfc_device_save_nfcv_data(file, dev)) break; + } else if(dev->format == NfcDeviceSaveFormatBankCard) { + if(!nfc_device_save_bank_card_data(file, dev)) break; } else if(dev->format == NfcDeviceSaveFormatMifareClassic) { // Save data if(!nfc_device_save_mifare_classic_data(file, dev)) break; @@ -1160,18 +1496,20 @@ static bool nfc_device_load_data(NfcDevice* dev, FuriString* path, bool show_dia if(!nfc_device_parse_format_string(dev, temp_str)) break; // Read and parse UID, ATQA and SAK if(!flipper_format_get_value_count(file, "UID", &data_cnt)) break; - if(!(data_cnt == 4 || data_cnt == 7)) break; + if(!(data_cnt == 4 || data_cnt == 7 || data_cnt == 8)) break; data->uid_len = data_cnt; if(!flipper_format_read_hex(file, "UID", data->uid, data->uid_len)) break; - if(version == version_with_lsb_atqa) { - if(!flipper_format_read_hex(file, "ATQA", data->atqa, 2)) break; - } else { - uint8_t atqa[2] = {}; - if(!flipper_format_read_hex(file, "ATQA", atqa, 2)) break; - data->atqa[0] = atqa[1]; - data->atqa[1] = atqa[0]; + if(dev->format != NfcDeviceSaveFormatNfcV) { + if(version == version_with_lsb_atqa) { + if(!flipper_format_read_hex(file, "ATQA", data->atqa, 2)) break; + } else { + uint8_t atqa[2] = {}; + if(!flipper_format_read_hex(file, "ATQA", atqa, 2)) break; + data->atqa[0] = atqa[1]; + data->atqa[1] = atqa[0]; + } + if(!flipper_format_read_hex(file, "SAK", &data->sak, 1)) break; } - if(!flipper_format_read_hex(file, "SAK", &data->sak, 1)) break; // Load CUID uint8_t* cuid_start = data->uid; if(data->uid_len == 7) { @@ -1186,6 +1524,8 @@ static bool nfc_device_load_data(NfcDevice* dev, FuriString* path, bool show_dia if(!nfc_device_load_mifare_classic_data(file, dev)) break; } else if(dev->format == NfcDeviceSaveFormatMifareDesfire) { if(!nfc_device_load_mifare_df_data(file, dev)) break; + } else if(dev->format == NfcDeviceSaveFormatNfcV) { + if(!nfc_device_load_nfcv_data(file, dev)) break; } else if(dev->format == NfcDeviceSaveFormatBankCard) { if(!nfc_device_load_bank_card_data(file, dev)) break; } diff --git a/lib/nfc/nfc_device.h b/lib/nfc/nfc_device.h index df37ec3df53..20df4f89181 100644 --- a/lib/nfc/nfc_device.h +++ b/lib/nfc/nfc_device.h @@ -11,6 +11,7 @@ #include #include #include +#include #ifdef __cplusplus extern "C" { @@ -31,6 +32,7 @@ typedef enum { NfcDeviceProtocolMifareUl, NfcDeviceProtocolMifareClassic, NfcDeviceProtocolMifareDesfire, + NfcDeviceProtocolNfcV } NfcProtocol; typedef enum { @@ -39,6 +41,7 @@ typedef enum { NfcDeviceSaveFormatMifareUl, NfcDeviceSaveFormatMifareClassic, NfcDeviceSaveFormatMifareDesfire, + NfcDeviceSaveFormatNfcV, } NfcDeviceSaveFormat; typedef struct { @@ -73,6 +76,7 @@ typedef struct { MfUltralightData mf_ul_data; MfClassicData mf_classic_data; MifareDesfireData mf_df_data; + NfcVData nfcv_data; }; FuriString* parsed_data; } NfcDeviceData; diff --git a/lib/nfc/nfc_worker.c b/lib/nfc/nfc_worker.c index daa8fee59a3..293f1ce705c 100644 --- a/lib/nfc/nfc_worker.c +++ b/lib/nfc/nfc_worker.c @@ -111,6 +111,14 @@ int32_t nfc_worker_task(void* context) { nfc_worker_mf_classic_dict_attack(nfc_worker); } else if(nfc_worker->state == NfcWorkerStateAnalyzeReader) { nfc_worker_analyze_reader(nfc_worker); + } else if(nfc_worker->state == NfcWorkerStateNfcVEmulate) { + nfc_worker_nfcv_emulate(nfc_worker); + } else if(nfc_worker->state == NfcWorkerStateNfcVSniff) { + nfc_worker_nfcv_sniff(nfc_worker); + } else if(nfc_worker->state == NfcWorkerStateNfcVUnlock) { + nfc_worker_nfcv_unlock(nfc_worker); + } else if(nfc_worker->state == NfcWorkerStateNfcVUnlockAndSave) { + nfc_worker_nfcv_unlock(nfc_worker); } furi_hal_nfc_sleep(); nfc_worker_change_state(nfc_worker, NfcWorkerStateReady); @@ -118,6 +126,236 @@ int32_t nfc_worker_task(void* context) { return 0; } +static bool nfc_worker_read_nfcv(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { + bool read_success = false; + NfcVReader reader = {}; + + FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data; + NfcVData* nfcv_data = &nfc_worker->dev_data->nfcv_data; + + furi_hal_nfc_sleep(); + + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + reader_analyzer_prepare_tx_rx(nfc_worker->reader_analyzer, tx_rx, false); + reader_analyzer_start(nfc_worker->reader_analyzer, ReaderAnalyzerModeDebugLog); + } + + do { + if(!furi_hal_nfc_detect(&nfc_worker->dev_data->nfc_data, 200)) break; + if(!nfcv_read_card(&reader, nfc_data, nfcv_data)) break; + + read_success = true; + } while(false); + + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + reader_analyzer_stop(nfc_worker->reader_analyzer); + } + + return read_success; +} + +void nfc_worker_nfcv_emulate(NfcWorker* nfc_worker) { + FuriHalNfcTxRxContext tx_rx = {}; + FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data; + NfcVData* nfcv_data = &nfc_worker->dev_data->nfcv_data; + + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + reader_analyzer_prepare_tx_rx(nfc_worker->reader_analyzer, &tx_rx, true); + reader_analyzer_start(nfc_worker->reader_analyzer, ReaderAnalyzerModeDebugLog); + } + + nfcv_emu_init(nfc_data, nfcv_data); + while(nfc_worker->state == NfcWorkerStateNfcVEmulate) { + if(nfcv_emu_loop(&tx_rx, nfc_data, nfcv_data, 100)) { + if(nfc_worker->callback) { + nfc_worker->callback(NfcWorkerEventNfcVCommandExecuted, nfc_worker->context); + if(nfcv_data->modified) { + nfc_worker->callback(NfcWorkerEventNfcVContentChanged, nfc_worker->context); + nfcv_data->modified = false; + } + } + } + } + nfcv_emu_deinit(nfcv_data); + + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + reader_analyzer_stop(nfc_worker->reader_analyzer); + } +} + +void nfc_worker_nfcv_sniff(NfcWorker* nfc_worker) { + FuriHalNfcTxRxContext tx_rx = {}; + FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data; + NfcVData* nfcv_data = &nfc_worker->dev_data->nfcv_data; + + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + reader_analyzer_prepare_tx_rx(nfc_worker->reader_analyzer, &tx_rx, true); + reader_analyzer_start(nfc_worker->reader_analyzer, ReaderAnalyzerModeDebugLog); + } + + nfcv_data->sub_type = NfcVTypeSniff; + nfcv_emu_init(nfc_data, nfcv_data); + + while(nfc_worker->state == NfcWorkerStateNfcVSniff) { + if(nfcv_emu_loop(&tx_rx, nfc_data, nfcv_data, 100)) { + if(nfc_worker->callback) { + nfc_worker->callback(NfcWorkerEventNfcVCommandExecuted, nfc_worker->context); + } + } + } + nfcv_emu_deinit(nfcv_data); + + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + reader_analyzer_stop(nfc_worker->reader_analyzer); + } +} + +void nfc_worker_nfcv_unlock(NfcWorker* nfc_worker) { + furi_assert(nfc_worker); + furi_assert(nfc_worker->callback); + + NfcVData* nfcv_data = &nfc_worker->dev_data->nfcv_data; + FuriHalNfcTxRxContext tx_rx = {}; + uint8_t* key_data = nfcv_data->sub_data.slix.key_privacy; + uint32_t key = 0; + + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + reader_analyzer_prepare_tx_rx(nfc_worker->reader_analyzer, &tx_rx, true); + reader_analyzer_start(nfc_worker->reader_analyzer, ReaderAnalyzerModeDebugLog); + } + + furi_hal_nfc_sleep(); + + while((nfc_worker->state == NfcWorkerStateNfcVUnlock) || + (nfc_worker->state == NfcWorkerStateNfcVUnlockAndSave)) { + furi_hal_nfc_exit_sleep(); + furi_hal_nfc_ll_txrx_on(); + furi_hal_nfc_ll_poll(); + if(furi_hal_nfc_ll_set_mode( + FuriHalNfcModePollNfcv, FuriHalNfcBitrate26p48, FuriHalNfcBitrate26p48) != + FuriHalNfcReturnOk) { + break; + } + + furi_hal_nfc_ll_set_fdt_listen(FURI_HAL_NFC_LL_FDT_LISTEN_NFCV_POLLER); + furi_hal_nfc_ll_set_fdt_poll(FURI_HAL_NFC_LL_FDT_POLL_NFCV_POLLER); + furi_hal_nfc_ll_set_error_handling(FuriHalNfcErrorHandlingNfc); + furi_hal_nfc_ll_set_guard_time(FURI_HAL_NFC_LL_GT_NFCV); + + FURI_LOG_D(TAG, "Detect presence"); + ReturnCode ret = slix_get_random(nfcv_data); + + if(ret == ERR_NONE) { + /* there is some chip, responding with a RAND */ + nfc_worker->dev_data->protocol = NfcDeviceProtocolNfcV; + FURI_LOG_D(TAG, " Chip detected. In privacy?"); + ret = nfcv_inventory(NULL); + + if(ret == ERR_NONE) { + /* chip is also visible, so no action required, just save */ + if(nfc_worker->state == NfcWorkerStateNfcVUnlockAndSave) { + NfcVReader reader = {}; + + if(!nfcv_read_card(&reader, &nfc_worker->dev_data->nfc_data, nfcv_data)) { + FURI_LOG_D(TAG, " => failed, wait for chip to disappear."); + snprintf(nfcv_data->error, sizeof(nfcv_data->error), "Read card\nfailed"); + nfc_worker->callback(NfcWorkerEventWrongCardDetected, nfc_worker->context); + } else { + FURI_LOG_D(TAG, " => success, wait for chip to disappear."); + nfc_worker->callback(NfcWorkerEventCardDetected, nfc_worker->context); + } + } else { + FURI_LOG_D(TAG, " => success, wait for chip to disappear."); + nfc_worker->callback(NfcWorkerEventCardDetected, nfc_worker->context); + } + + while(slix_get_random(NULL) == ERR_NONE) { + furi_delay_ms(100); + } + + FURI_LOG_D(TAG, " => chip is already visible, wait for chip to disappear.\r\n"); + nfc_worker->callback(NfcWorkerEventAborted, nfc_worker->context); + while(slix_get_random(NULL) == ERR_NONE) { + furi_delay_ms(100); + } + + key_data[0] = 0; + key_data[1] = 0; + key_data[2] = 0; + key_data[3] = 0; + + } else { + /* chip is invisible, try to unlock */ + FURI_LOG_D(TAG, " chip is invisible, unlocking"); + + if(nfcv_data->auth_method == NfcVAuthMethodManual) { + key |= key_data[0] << 24; + key |= key_data[1] << 16; + key |= key_data[2] << 8; + key |= key_data[3] << 0; + + ret = slix_unlock(nfcv_data, 4); + } else { + key = 0x7FFD6E5B; + key_data[0] = key >> 24; + key_data[1] = key >> 16; + key_data[2] = key >> 8; + key_data[3] = key >> 0; + ret = slix_unlock(nfcv_data, 4); + + if(ret != ERR_NONE) { + /* main key failed, trying second one */ + FURI_LOG_D(TAG, " trying second key after resetting"); + + /* reset chip */ + furi_hal_nfc_ll_txrx_off(); + furi_delay_ms(20); + furi_hal_nfc_ll_txrx_on(); + + if(slix_get_random(nfcv_data) != ERR_NONE) { + FURI_LOG_D(TAG, " reset failed"); + } + + key = 0x0F0F0F0F; + key_data[0] = key >> 24; + key_data[1] = key >> 16; + key_data[2] = key >> 8; + key_data[3] = key >> 0; + ret = slix_unlock(nfcv_data, 4); + } + } + if(ret != ERR_NONE) { + /* unlock failed */ + FURI_LOG_D(TAG, " => failed, wait for chip to disappear."); + snprintf( + nfcv_data->error, sizeof(nfcv_data->error), "Passwords not\naccepted"); + nfc_worker->callback(NfcWorkerEventWrongCardDetected, nfc_worker->context); + + /* reset chip */ + furi_hal_nfc_ll_txrx_off(); + furi_delay_ms(20); + furi_hal_nfc_ll_txrx_on(); + + /* wait for disappearing */ + while(slix_get_random(NULL) == ERR_NONE) { + furi_delay_ms(100); + } + } + } + } else { + nfc_worker->callback(NfcWorkerEventNoCardDetected, nfc_worker->context); + } + + furi_hal_nfc_ll_txrx_off(); + furi_hal_nfc_sleep(); + furi_delay_ms(100); + } + + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + reader_analyzer_stop(nfc_worker->reader_analyzer); + } +} + static bool nfc_worker_read_mf_ultralight(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { bool read_success = false; MfUltralightReader reader = {}; @@ -317,7 +555,12 @@ void nfc_worker_read(NfcWorker* nfc_worker) { event = NfcWorkerEventReadUidNfcF; break; } else if(nfc_data->type == FuriHalNfcTypeV) { - event = NfcWorkerEventReadUidNfcV; + FURI_LOG_I(TAG, "NfcV detected"); + nfc_worker->dev_data->protocol = NfcDeviceProtocolNfcV; + if(nfc_worker_read_nfcv(nfc_worker, &tx_rx)) { + FURI_LOG_I(TAG, "nfc_worker_read_nfcv success"); + } + event = NfcWorkerEventReadNfcV; break; } } else { diff --git a/lib/nfc/nfc_worker.h b/lib/nfc/nfc_worker.h index 8e993fc6aa3..722f148574f 100644 --- a/lib/nfc/nfc_worker.h +++ b/lib/nfc/nfc_worker.h @@ -18,6 +18,10 @@ typedef enum { NfcWorkerStateReadMfUltralightReadAuth, NfcWorkerStateMfClassicDictAttack, NfcWorkerStateAnalyzeReader, + NfcWorkerStateNfcVEmulate, + NfcWorkerStateNfcVUnlock, + NfcWorkerStateNfcVUnlockAndSave, + NfcWorkerStateNfcVSniff, // Debug NfcWorkerStateEmulateApdu, NfcWorkerStateField, @@ -39,6 +43,7 @@ typedef enum { NfcWorkerEventReadMfClassicDone, NfcWorkerEventReadMfClassicLoadKeyCache, NfcWorkerEventReadMfClassicDictAttackRequired, + NfcWorkerEventReadNfcV, // Nfc worker common events NfcWorkerEventSuccess, @@ -69,6 +74,9 @@ typedef enum { // Mifare Ultralight events NfcWorkerEventMfUltralightPassKey, // NFC worker requesting manual key NfcWorkerEventMfUltralightPwdAuth, // Reader sent auth command + NfcWorkerEventNfcVPassKey, // NFC worker requesting manual key + NfcWorkerEventNfcVCommandExecuted, + NfcWorkerEventNfcVContentChanged, } NfcWorkerEvent; typedef bool (*NfcWorkerCallback)(NfcWorkerEvent event, void* context); @@ -87,3 +95,6 @@ void nfc_worker_start( void* context); void nfc_worker_stop(NfcWorker* nfc_worker); +void nfc_worker_nfcv_unlock(NfcWorker* nfc_worker); +void nfc_worker_nfcv_emulate(NfcWorker* nfc_worker); +void nfc_worker_nfcv_sniff(NfcWorker* nfc_worker); \ No newline at end of file diff --git a/lib/nfc/nfc_worker_i.h b/lib/nfc/nfc_worker_i.h index 701ecb90c63..b678573ec0d 100644 --- a/lib/nfc/nfc_worker_i.h +++ b/lib/nfc/nfc_worker_i.h @@ -11,6 +11,8 @@ #include #include #include +#include +#include #include struct NfcWorker { diff --git a/lib/nfc/protocols/nfcv.c b/lib/nfc/protocols/nfcv.c new file mode 100644 index 00000000000..ac818b7a4be --- /dev/null +++ b/lib/nfc/protocols/nfcv.c @@ -0,0 +1,1398 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "nfcv.h" +#include "nfc_util.h" +#include "slix.h" + +#define TAG "NfcV" + +/* macros to map "modulate field" flag to GPIO level */ +#define GPIO_LEVEL_MODULATED NFCV_LOAD_MODULATION_POLARITY +#define GPIO_LEVEL_UNMODULATED (!GPIO_LEVEL_MODULATED) + +/* timing macros */ +#define DIGITAL_SIGNAL_UNIT_S (100000000000.0f) +#define DIGITAL_SIGNAL_UNIT_US (100000.0f) + +ReturnCode nfcv_inventory(uint8_t* uid) { + uint16_t received = 0; + rfalNfcvInventoryRes res; + ReturnCode ret = ERR_NONE; + + for(int tries = 0; tries < NFCV_COMMAND_RETRIES; tries++) { + /* TODO: needs proper abstraction via fury_hal(_ll)_* */ + ret = rfalNfcvPollerInventory(RFAL_NFCV_NUM_SLOTS_1, 0, NULL, &res, &received); + + if(ret == ERR_NONE) { + break; + } + } + + if(ret == ERR_NONE) { + if(uid != NULL) { + memcpy(uid, res.UID, NFCV_UID_LENGTH); + } + } + + return ret; +} + +ReturnCode nfcv_read_blocks(NfcVReader* reader, NfcVData* nfcv_data) { + UNUSED(reader); + + uint16_t received = 0; + for(size_t block = 0; block < nfcv_data->block_num; block++) { + uint8_t rxBuf[32]; + FURI_LOG_D(TAG, "Reading block %d/%d", block, (nfcv_data->block_num - 1)); + + ReturnCode ret = ERR_NONE; + for(int tries = 0; tries < NFCV_COMMAND_RETRIES; tries++) { + ret = rfalNfcvPollerReadSingleBlock( + RFAL_NFCV_REQ_FLAG_DEFAULT, NULL, block, rxBuf, sizeof(rxBuf), &received); + + if(ret == ERR_NONE) { + break; + } + } + if(ret != ERR_NONE) { + FURI_LOG_D(TAG, "failed to read: %d", ret); + return ret; + } + memcpy( + &(nfcv_data->data[block * nfcv_data->block_size]), &rxBuf[1], nfcv_data->block_size); + FURI_LOG_D( + TAG, + " %02X %02X %02X %02X", + nfcv_data->data[block * nfcv_data->block_size + 0], + nfcv_data->data[block * nfcv_data->block_size + 1], + nfcv_data->data[block * nfcv_data->block_size + 2], + nfcv_data->data[block * nfcv_data->block_size + 3]); + } + + return ERR_NONE; +} + +ReturnCode nfcv_read_sysinfo(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data) { + uint8_t rxBuf[32]; + uint16_t received = 0; + ReturnCode ret = ERR_NONE; + + FURI_LOG_D(TAG, "Read SYSTEM INFORMATION..."); + + for(int tries = 0; tries < NFCV_COMMAND_RETRIES; tries++) { + /* TODO: needs proper abstraction via fury_hal(_ll)_* */ + ret = rfalNfcvPollerGetSystemInformation( + RFAL_NFCV_REQ_FLAG_DEFAULT, NULL, rxBuf, sizeof(rxBuf), &received); + + if(ret == ERR_NONE) { + break; + } + } + + if(ret == ERR_NONE) { + nfc_data->type = FuriHalNfcTypeV; + nfc_data->uid_len = NFCV_UID_LENGTH; + /* UID is stored reversed in this response */ + for(int pos = 0; pos < nfc_data->uid_len; pos++) { + nfc_data->uid[pos] = rxBuf[2 + (NFCV_UID_LENGTH - 1 - pos)]; + } + nfcv_data->dsfid = rxBuf[NFCV_UID_LENGTH + 2]; + nfcv_data->afi = rxBuf[NFCV_UID_LENGTH + 3]; + nfcv_data->block_num = rxBuf[NFCV_UID_LENGTH + 4] + 1; + nfcv_data->block_size = rxBuf[NFCV_UID_LENGTH + 5] + 1; + nfcv_data->ic_ref = rxBuf[NFCV_UID_LENGTH + 6]; + FURI_LOG_D( + TAG, + " UID: %02X %02X %02X %02X %02X %02X %02X %02X", + nfc_data->uid[0], + nfc_data->uid[1], + nfc_data->uid[2], + nfc_data->uid[3], + nfc_data->uid[4], + nfc_data->uid[5], + nfc_data->uid[6], + nfc_data->uid[7]); + FURI_LOG_D( + TAG, + " DSFID %d, AFI %d, Blocks %d, Size %d, IC Ref %d", + nfcv_data->dsfid, + nfcv_data->afi, + nfcv_data->block_num, + nfcv_data->block_size, + nfcv_data->ic_ref); + return ret; + } + FURI_LOG_D(TAG, "Failed: %d", ret); + + return ret; +} + +bool nfcv_read_card(NfcVReader* reader, FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data) { + furi_assert(reader); + furi_assert(nfc_data); + furi_assert(nfcv_data); + + if(nfcv_read_sysinfo(nfc_data, nfcv_data) != ERR_NONE) { + return false; + } + + if(nfcv_read_blocks(reader, nfcv_data) != ERR_NONE) { + return false; + } + + if(slix_check_card_type(nfc_data)) { + FURI_LOG_I(TAG, "NXP SLIX detected"); + nfcv_data->sub_type = NfcVTypeSlix; + } else if(slix2_check_card_type(nfc_data)) { + FURI_LOG_I(TAG, "NXP SLIX2 detected"); + nfcv_data->sub_type = NfcVTypeSlix2; + } else if(slix_s_check_card_type(nfc_data)) { + FURI_LOG_I(TAG, "NXP SLIX-S detected"); + nfcv_data->sub_type = NfcVTypeSlixS; + } else if(slix_l_check_card_type(nfc_data)) { + FURI_LOG_I(TAG, "NXP SLIX-L detected"); + nfcv_data->sub_type = NfcVTypeSlixL; + } else { + nfcv_data->sub_type = NfcVTypePlain; + } + + return true; +} + +void nfcv_crc(uint8_t* data, uint32_t length) { + uint32_t reg = 0xFFFF; + + for(size_t i = 0; i < length; i++) { + reg = reg ^ ((uint32_t)data[i]); + for(size_t j = 0; j < 8; j++) { + if(reg & 0x0001) { + reg = (reg >> 1) ^ 0x8408; + } else { + reg = (reg >> 1); + } + } + } + + uint16_t crc = ~(uint16_t)(reg & 0xffff); + + data[length + 0] = crc & 0xFF; + data[length + 1] = crc >> 8; +} + +void nfcv_emu_free_signals(NfcVEmuAirSignals* signals) { + furi_assert(signals); + + if(signals->nfcv_resp_one) { + digital_signal_free(signals->nfcv_resp_one); + } + if(signals->nfcv_resp_zero) { + digital_signal_free(signals->nfcv_resp_zero); + } + if(signals->nfcv_resp_sof) { + digital_signal_free(signals->nfcv_resp_sof); + } + if(signals->nfcv_resp_eof) { + digital_signal_free(signals->nfcv_resp_eof); + } + signals->nfcv_resp_one = NULL; + signals->nfcv_resp_zero = NULL; + signals->nfcv_resp_sof = NULL; + signals->nfcv_resp_eof = NULL; +} + +bool nfcv_emu_alloc_signals(NfcVEmuAir* air, NfcVEmuAirSignals* signals, uint32_t slowdown) { + furi_assert(air); + furi_assert(signals); + + bool success = true; + + if(!signals->nfcv_resp_one) { + /* logical one: unmodulated then 8 pulses */ + signals->nfcv_resp_one = digital_signal_alloc( + slowdown * (air->nfcv_resp_unmod->edge_cnt + 8 * air->nfcv_resp_pulse->edge_cnt)); + if(!signals->nfcv_resp_one) { + return false; + } + for(size_t i = 0; i < slowdown; i++) { + success &= digital_signal_append(signals->nfcv_resp_one, air->nfcv_resp_unmod); + } + for(size_t i = 0; i < slowdown * 8; i++) { + success &= digital_signal_append(signals->nfcv_resp_one, air->nfcv_resp_pulse); + } + if(!success) { + return false; + } + } + if(!signals->nfcv_resp_zero) { + /* logical zero: 8 pulses then unmodulated */ + signals->nfcv_resp_zero = digital_signal_alloc( + slowdown * (8 * air->nfcv_resp_pulse->edge_cnt + air->nfcv_resp_unmod->edge_cnt)); + if(!signals->nfcv_resp_zero) { + return false; + } + for(size_t i = 0; i < slowdown * 8; i++) { + success &= digital_signal_append(signals->nfcv_resp_zero, air->nfcv_resp_pulse); + } + for(size_t i = 0; i < slowdown; i++) { + success &= digital_signal_append(signals->nfcv_resp_zero, air->nfcv_resp_unmod); + } + if(!success) { + return false; + } + } + if(!signals->nfcv_resp_sof) { + /* SOF: unmodulated, 24 pulses, logic 1 */ + signals->nfcv_resp_sof = digital_signal_alloc( + slowdown * (3 * air->nfcv_resp_unmod->edge_cnt + 24 * air->nfcv_resp_pulse->edge_cnt) + + signals->nfcv_resp_one->edge_cnt); + if(!signals->nfcv_resp_sof) { + return false; + } + for(size_t i = 0; i < slowdown * 3; i++) { + success &= digital_signal_append(signals->nfcv_resp_sof, air->nfcv_resp_unmod); + } + for(size_t i = 0; i < slowdown * 24; i++) { + success &= digital_signal_append(signals->nfcv_resp_sof, air->nfcv_resp_pulse); + } + success &= digital_signal_append(signals->nfcv_resp_sof, signals->nfcv_resp_one); + if(!success) { + return false; + } + } + if(!signals->nfcv_resp_eof) { + /* EOF: logic 0, 24 pulses, unmodulated */ + signals->nfcv_resp_eof = digital_signal_alloc( + signals->nfcv_resp_zero->edge_cnt + + slowdown * (24 * air->nfcv_resp_pulse->edge_cnt + 3 * air->nfcv_resp_unmod->edge_cnt) + + air->nfcv_resp_unmod->edge_cnt); + if(!signals->nfcv_resp_eof) { + return false; + } + success &= digital_signal_append(signals->nfcv_resp_eof, signals->nfcv_resp_zero); + for(size_t i = 0; i < slowdown * 23; i++) { + success &= digital_signal_append(signals->nfcv_resp_eof, air->nfcv_resp_pulse); + } + /* we don't want to add the last level as we just want a transition to "unmodulated" again */ + for(size_t i = 0; i < slowdown; i++) { + success &= digital_signal_append(signals->nfcv_resp_eof, air->nfcv_resp_half_pulse); + } + } + return success; +} + +bool nfcv_emu_alloc(NfcVData* nfcv_data) { + furi_assert(nfcv_data); + + if(!nfcv_data->frame) { + nfcv_data->frame = malloc(NFCV_FRAMESIZE_MAX); + if(!nfcv_data->frame) { + return false; + } + } + + if(!nfcv_data->emu_air.nfcv_signal) { + /* assuming max frame length is 255 bytes */ + nfcv_data->emu_air.nfcv_signal = digital_sequence_alloc(8 * 255 + 2, &gpio_spi_r_mosi); + if(!nfcv_data->emu_air.nfcv_signal) { + return false; + } + } + if(!nfcv_data->emu_air.nfcv_resp_unmod) { + /* unmodulated 256/fc or 1024/fc signal as building block */ + nfcv_data->emu_air.nfcv_resp_unmod = digital_signal_alloc(4); + if(!nfcv_data->emu_air.nfcv_resp_unmod) { + return false; + } + nfcv_data->emu_air.nfcv_resp_unmod->start_level = GPIO_LEVEL_UNMODULATED; + nfcv_data->emu_air.nfcv_resp_unmod->edge_timings[0] = + (uint32_t)(NFCV_RESP_SUBC1_UNMOD_256 * DIGITAL_SIGNAL_UNIT_S); + nfcv_data->emu_air.nfcv_resp_unmod->edge_cnt = 1; + } + if(!nfcv_data->emu_air.nfcv_resp_pulse) { + /* modulated fc/32 or fc/8 pulse as building block */ + nfcv_data->emu_air.nfcv_resp_pulse = digital_signal_alloc(4); + if(!nfcv_data->emu_air.nfcv_resp_pulse) { + return false; + } + nfcv_data->emu_air.nfcv_resp_pulse->start_level = GPIO_LEVEL_MODULATED; + nfcv_data->emu_air.nfcv_resp_pulse->edge_timings[0] = + (uint32_t)(NFCV_RESP_SUBC1_PULSE_32 * DIGITAL_SIGNAL_UNIT_S); + nfcv_data->emu_air.nfcv_resp_pulse->edge_timings[1] = + (uint32_t)(NFCV_RESP_SUBC1_PULSE_32 * DIGITAL_SIGNAL_UNIT_S); + nfcv_data->emu_air.nfcv_resp_pulse->edge_cnt = 2; + } + + if(!nfcv_data->emu_air.nfcv_resp_half_pulse) { + /* modulated fc/32 or fc/8 pulse as building block */ + nfcv_data->emu_air.nfcv_resp_half_pulse = digital_signal_alloc(4); + if(!nfcv_data->emu_air.nfcv_resp_half_pulse) { + return false; + } + nfcv_data->emu_air.nfcv_resp_half_pulse->start_level = GPIO_LEVEL_MODULATED; + nfcv_data->emu_air.nfcv_resp_half_pulse->edge_timings[0] = + (uint32_t)(NFCV_RESP_SUBC1_PULSE_32 * DIGITAL_SIGNAL_UNIT_S); + nfcv_data->emu_air.nfcv_resp_half_pulse->edge_cnt = 1; + } + + bool success = true; + success &= nfcv_emu_alloc_signals(&nfcv_data->emu_air, &nfcv_data->emu_air.signals_high, 1); + success &= nfcv_emu_alloc_signals(&nfcv_data->emu_air, &nfcv_data->emu_air.signals_low, 4); + + if(!success) { + FURI_LOG_E(TAG, "Failed to allocate signals"); + return false; + } + + digital_sequence_set_signal( + nfcv_data->emu_air.nfcv_signal, + NFCV_SIG_SOF, + nfcv_data->emu_air.signals_high.nfcv_resp_sof); + digital_sequence_set_signal( + nfcv_data->emu_air.nfcv_signal, + NFCV_SIG_BIT0, + nfcv_data->emu_air.signals_high.nfcv_resp_zero); + digital_sequence_set_signal( + nfcv_data->emu_air.nfcv_signal, + NFCV_SIG_BIT1, + nfcv_data->emu_air.signals_high.nfcv_resp_one); + digital_sequence_set_signal( + nfcv_data->emu_air.nfcv_signal, + NFCV_SIG_EOF, + nfcv_data->emu_air.signals_high.nfcv_resp_eof); + digital_sequence_set_signal( + nfcv_data->emu_air.nfcv_signal, + NFCV_SIG_LOW_SOF, + nfcv_data->emu_air.signals_low.nfcv_resp_sof); + digital_sequence_set_signal( + nfcv_data->emu_air.nfcv_signal, + NFCV_SIG_LOW_BIT0, + nfcv_data->emu_air.signals_low.nfcv_resp_zero); + digital_sequence_set_signal( + nfcv_data->emu_air.nfcv_signal, + NFCV_SIG_LOW_BIT1, + nfcv_data->emu_air.signals_low.nfcv_resp_one); + digital_sequence_set_signal( + nfcv_data->emu_air.nfcv_signal, + NFCV_SIG_LOW_EOF, + nfcv_data->emu_air.signals_low.nfcv_resp_eof); + + return true; +} + +void nfcv_emu_free(NfcVData* nfcv_data) { + furi_assert(nfcv_data); + + if(nfcv_data->frame) { + free(nfcv_data->frame); + } + if(nfcv_data->emu_protocol_ctx) { + free(nfcv_data->emu_protocol_ctx); + } + if(nfcv_data->emu_air.nfcv_resp_unmod) { + digital_signal_free(nfcv_data->emu_air.nfcv_resp_unmod); + } + if(nfcv_data->emu_air.nfcv_resp_pulse) { + digital_signal_free(nfcv_data->emu_air.nfcv_resp_pulse); + } + if(nfcv_data->emu_air.nfcv_resp_half_pulse) { + digital_signal_free(nfcv_data->emu_air.nfcv_resp_half_pulse); + } + if(nfcv_data->emu_air.nfcv_signal) { + digital_sequence_free(nfcv_data->emu_air.nfcv_signal); + } + if(nfcv_data->emu_air.reader_signal) { + // Stop pulse reader and disable bus before free + pulse_reader_stop(nfcv_data->emu_air.reader_signal); + // Free pulse reader + pulse_reader_free(nfcv_data->emu_air.reader_signal); + } + + nfcv_data->frame = NULL; + nfcv_data->emu_air.nfcv_resp_unmod = NULL; + nfcv_data->emu_air.nfcv_resp_pulse = NULL; + nfcv_data->emu_air.nfcv_resp_half_pulse = NULL; + nfcv_data->emu_air.nfcv_signal = NULL; + nfcv_data->emu_air.reader_signal = NULL; + + nfcv_emu_free_signals(&nfcv_data->emu_air.signals_high); + nfcv_emu_free_signals(&nfcv_data->emu_air.signals_low); +} + +void nfcv_emu_send( + FuriHalNfcTxRxContext* tx_rx, + NfcVData* nfcv, + uint8_t* data, + uint8_t length, + NfcVSendFlags flags, + uint32_t send_time) { + furi_assert(tx_rx); + furi_assert(nfcv); + + /* picked default value (0) to match the most common format */ + if(!flags) { + flags = NfcVSendFlagsSof | NfcVSendFlagsCrc | NfcVSendFlagsEof | + NfcVSendFlagsOneSubcarrier | NfcVSendFlagsHighRate; + } + + if(flags & NfcVSendFlagsCrc) { + nfcv_crc(data, length); + length += 2; + } + + /* depending on the request flags, send with high or low rate */ + uint32_t bit0 = (flags & NfcVSendFlagsHighRate) ? NFCV_SIG_BIT0 : NFCV_SIG_LOW_BIT0; + uint32_t bit1 = (flags & NfcVSendFlagsHighRate) ? NFCV_SIG_BIT1 : NFCV_SIG_LOW_BIT1; + uint32_t sof = (flags & NfcVSendFlagsHighRate) ? NFCV_SIG_SOF : NFCV_SIG_LOW_SOF; + uint32_t eof = (flags & NfcVSendFlagsHighRate) ? NFCV_SIG_EOF : NFCV_SIG_LOW_EOF; + + digital_sequence_clear(nfcv->emu_air.nfcv_signal); + + if(flags & NfcVSendFlagsSof) { + digital_sequence_add(nfcv->emu_air.nfcv_signal, sof); + } + + for(int bit_total = 0; bit_total < length * 8; bit_total++) { + uint32_t byte_pos = bit_total / 8; + uint32_t bit_pos = bit_total % 8; + uint8_t bit_val = 0x01 << bit_pos; + + digital_sequence_add(nfcv->emu_air.nfcv_signal, (data[byte_pos] & bit_val) ? bit1 : bit0); + } + + if(flags & NfcVSendFlagsEof) { + digital_sequence_add(nfcv->emu_air.nfcv_signal, eof); + } + + furi_hal_gpio_write(&gpio_spi_r_mosi, GPIO_LEVEL_UNMODULATED); + digital_sequence_set_sendtime(nfcv->emu_air.nfcv_signal, send_time); + digital_sequence_send(nfcv->emu_air.nfcv_signal); + furi_hal_gpio_write(&gpio_spi_r_mosi, GPIO_LEVEL_UNMODULATED); + + if(tx_rx->sniff_tx) { + tx_rx->sniff_tx(data, length * 8, false, tx_rx->sniff_context); + } +} + +static void nfcv_revuidcpy(uint8_t* dst, uint8_t* src) { + for(int pos = 0; pos < NFCV_UID_LENGTH; pos++) { + dst[pos] = src[NFCV_UID_LENGTH - 1 - pos]; + } +} + +static int nfcv_revuidcmp(uint8_t* dst, uint8_t* src) { + for(int pos = 0; pos < NFCV_UID_LENGTH; pos++) { + if(dst[pos] != src[NFCV_UID_LENGTH - 1 - pos]) { + return 1; + } + } + return 0; +} + +void nfcv_emu_handle_packet( + FuriHalNfcTxRxContext* tx_rx, + FuriHalNfcDevData* nfc_data, + void* nfcv_data_in) { + furi_assert(tx_rx); + furi_assert(nfc_data); + furi_assert(nfcv_data_in); + + NfcVData* nfcv_data = (NfcVData*)nfcv_data_in; + NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx; + + if(nfcv_data->frame_length < 2) { + return; + } + + if(nfcv_data->echo_mode) { + nfcv_emu_send( + tx_rx, + nfcv_data, + nfcv_data->frame, + nfcv_data->frame_length, + NfcVSendFlagsSof | NfcVSendFlagsHighRate | NfcVSendFlagsEof, + ctx->send_time); + snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "ECHO data"); + return; + } + + /* parse the frame data for the upcoming part 3 handling */ + ctx->flags = nfcv_data->frame[0]; + ctx->command = nfcv_data->frame[1]; + ctx->selected = !(ctx->flags & NFCV_REQ_FLAG_INVENTORY) && (ctx->flags & NFCV_REQ_FLAG_SELECT); + ctx->addressed = !(ctx->flags & NFCV_REQ_FLAG_INVENTORY) && + (ctx->flags & NFCV_REQ_FLAG_ADDRESS); + ctx->advanced = (ctx->command >= NFCV_CMD_ADVANCED); + ctx->address_offset = 2 + (ctx->advanced ? 1 : 0); + ctx->payload_offset = ctx->address_offset + (ctx->addressed ? NFCV_UID_LENGTH : 0); + ctx->response_flags = NfcVSendFlagsSof | NfcVSendFlagsCrc | NfcVSendFlagsEof; + ctx->send_time = nfcv_data->eof_timestamp + NFCV_FDT_FC(4380); + + if(ctx->flags & NFCV_REQ_FLAG_DATA_RATE) { + ctx->response_flags |= NfcVSendFlagsHighRate; + } + if(ctx->flags & NFCV_REQ_FLAG_SUB_CARRIER) { + ctx->response_flags |= NfcVSendFlagsTwoSubcarrier; + } + + if(ctx->payload_offset + 2 > nfcv_data->frame_length) { +#ifdef NFCV_VERBOSE + FURI_LOG_D(TAG, "command 0x%02X, but packet is too short", ctx->command); +#endif + return; + } + + /* standard behavior is implemented */ + if(ctx->addressed) { + uint8_t* address = &nfcv_data->frame[ctx->address_offset]; + if(nfcv_revuidcmp(address, nfc_data->uid)) { +#ifdef NFCV_VERBOSE + FURI_LOG_D(TAG, "addressed command 0x%02X, but not for us:", ctx->command); + FURI_LOG_D( + TAG, + " dest: %02X%02X%02X%02X%02X%02X%02X%02X", + address[7], + address[6], + address[5], + address[4], + address[3], + address[2], + address[1], + address[0]); + FURI_LOG_D( + TAG, + " our UID: %02X%02X%02X%02X%02X%02X%02X%02X", + nfc_data->uid[0], + nfc_data->uid[1], + nfc_data->uid[2], + nfc_data->uid[3], + nfc_data->uid[4], + nfc_data->uid[5], + nfc_data->uid[6], + nfc_data->uid[7]); +#endif + return; + } + } + + if(ctx->selected && !nfcv_data->selected) { +#ifdef NFCV_VERBOSE + FURI_LOG_D( + TAG, + "selected card shall execute command 0x%02X, but we were not selected", + ctx->command); +#endif + return; + } + + /* then give control to the card subtype specific protocol filter */ + if(ctx->emu_protocol_filter != NULL) { + if(ctx->emu_protocol_filter(tx_rx, nfc_data, nfcv_data)) { + if(strlen(nfcv_data->last_command) > 0) { +#ifdef NFCV_VERBOSE + FURI_LOG_D( + TAG, "Received command %s (handled by filter)", nfcv_data->last_command); +#endif + } + return; + } + } + + switch(ctx->command) { + case NFCV_CMD_INVENTORY: { + bool respond = false; + + if(ctx->flags & NFCV_REQ_FLAG_AFI) { + uint8_t afi = nfcv_data->frame[ctx->payload_offset]; + if(afi == nfcv_data->afi) { + respond = true; + } + } else { + respond = true; + } + + if(!nfcv_data->quiet && respond) { + int buffer_pos = 0; + ctx->response_buffer[buffer_pos++] = NFCV_NOERROR; + ctx->response_buffer[buffer_pos++] = nfcv_data->dsfid; + nfcv_revuidcpy(&ctx->response_buffer[buffer_pos], nfc_data->uid); + buffer_pos += NFCV_UID_LENGTH; + + nfcv_emu_send( + tx_rx, + nfcv_data, + ctx->response_buffer, + buffer_pos, + ctx->response_flags, + ctx->send_time); + snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "INVENTORY"); + } else { + snprintf( + nfcv_data->last_command, sizeof(nfcv_data->last_command), "INVENTORY (quiet)"); + } + break; + } + + case NFCV_CMD_STAY_QUIET: { + snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "STAYQUIET"); + nfcv_data->quiet = true; + break; + } + + case NFCV_CMD_LOCK_BLOCK: { + uint8_t block = nfcv_data->frame[ctx->payload_offset]; + nfcv_data->security_status[block] |= 0x01; + nfcv_data->modified = true; + + ctx->response_buffer[0] = NFCV_NOERROR; + nfcv_emu_send( + tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); + + snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "LOCK BLOCK %d", block); + break; + } + + case NFCV_CMD_WRITE_DSFID: { + uint8_t id = nfcv_data->frame[ctx->payload_offset]; + + if(!(nfcv_data->security_status[0] & NfcVLockBitDsfid)) { + nfcv_data->dsfid = id; + nfcv_data->modified = true; + ctx->response_buffer[0] = NFCV_NOERROR; + nfcv_emu_send( + tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); + } + + snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "WRITE DSFID %02X", id); + break; + } + + case NFCV_CMD_WRITE_AFI: { + uint8_t id = nfcv_data->frame[ctx->payload_offset]; + + if(!(nfcv_data->security_status[0] & NfcVLockBitAfi)) { + nfcv_data->afi = id; + nfcv_data->modified = true; + ctx->response_buffer[0] = NFCV_NOERROR; + nfcv_emu_send( + tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); + } + + snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "WRITE AFI %02X", id); + break; + } + + case NFCV_CMD_LOCK_DSFID: { + if(!(nfcv_data->security_status[0] & NfcVLockBitDsfid)) { + nfcv_data->security_status[0] |= NfcVLockBitDsfid; + nfcv_data->modified = true; + + ctx->response_buffer[0] = NFCV_NOERROR; + nfcv_emu_send( + tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); + } + + snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "LOCK DSFID"); + break; + } + + case NFCV_CMD_LOCK_AFI: { + if(!(nfcv_data->security_status[0] & NfcVLockBitAfi)) { + nfcv_data->security_status[0] |= NfcVLockBitAfi; + nfcv_data->modified = true; + + ctx->response_buffer[0] = NFCV_NOERROR; + nfcv_emu_send( + tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); + } + + snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "LOCK AFI"); + break; + } + + case NFCV_CMD_SELECT: { + ctx->response_buffer[0] = NFCV_NOERROR; + nfcv_data->selected = true; + nfcv_data->quiet = false; + nfcv_emu_send( + tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); + snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "SELECT"); + break; + } + + case NFCV_CMD_RESET_TO_READY: { + ctx->response_buffer[0] = NFCV_NOERROR; + nfcv_data->quiet = false; + nfcv_emu_send( + tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); + snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "RESET_TO_READY"); + break; + } + + case NFCV_CMD_READ_MULTI_BLOCK: + case NFCV_CMD_READ_BLOCK: { + uint8_t block = nfcv_data->frame[ctx->payload_offset]; + uint8_t blocks = 1; + + if(ctx->command == NFCV_CMD_READ_MULTI_BLOCK) { + blocks = nfcv_data->frame[ctx->payload_offset + 1] + 1; + } + + if(block + blocks <= nfcv_data->block_num) { + uint8_t buffer_pos = 0; + + ctx->response_buffer[buffer_pos++] = NFCV_NOERROR; + + for(int block_index = 0; block_index < blocks; block_index++) { + int block_current = block + block_index; + /* prepend security status */ + if(ctx->flags & NFCV_REQ_FLAG_OPTION) { + ctx->response_buffer[buffer_pos++] = + nfcv_data->security_status[1 + block_current]; + } + /* then the data block */ + memcpy( + &ctx->response_buffer[buffer_pos], + &nfcv_data->data[nfcv_data->block_size * block_current], + nfcv_data->block_size); + buffer_pos += nfcv_data->block_size; + } + nfcv_emu_send( + tx_rx, + nfcv_data, + ctx->response_buffer, + buffer_pos, + ctx->response_flags, + ctx->send_time); + } else { + ctx->response_buffer[0] = NFCV_RES_FLAG_ERROR; + ctx->response_buffer[1] = NFCV_ERROR_GENERIC; + nfcv_emu_send( + tx_rx, nfcv_data, ctx->response_buffer, 2, ctx->response_flags, ctx->send_time); + } + snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "READ BLOCK %d", block); + + break; + } + + case NFCV_CMD_WRITE_MULTI_BLOCK: + case NFCV_CMD_WRITE_BLOCK: { + uint8_t blocks = 1; + uint8_t block = nfcv_data->frame[ctx->payload_offset]; + uint8_t data_pos = ctx->payload_offset + 1; + + if(ctx->command == NFCV_CMD_WRITE_MULTI_BLOCK) { + blocks = nfcv_data->frame[data_pos] + 1; + data_pos++; + } + + uint8_t* data = &nfcv_data->frame[data_pos]; + uint32_t data_len = nfcv_data->block_size * blocks; + + if((block + blocks) <= nfcv_data->block_num && + (data_pos + data_len + 2) == nfcv_data->frame_length) { + ctx->response_buffer[0] = NFCV_NOERROR; + memcpy( + &nfcv_data->data[nfcv_data->block_size * block], + &nfcv_data->frame[data_pos], + data_len); + nfcv_data->modified = true; + + nfcv_emu_send( + tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); + } else { + ctx->response_buffer[0] = NFCV_RES_FLAG_ERROR; + ctx->response_buffer[1] = NFCV_ERROR_GENERIC; + nfcv_emu_send( + tx_rx, nfcv_data, ctx->response_buffer, 2, ctx->response_flags, ctx->send_time); + } + + if(ctx->command == NFCV_CMD_WRITE_MULTI_BLOCK) { + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "WRITE MULTI BLOCK %d, %d blocks", + block, + blocks); + } else { + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "WRITE BLOCK %d <- %02X %02X %02X %02X", + block, + data[0], + data[1], + data[2], + data[3]); + } + break; + } + + case NFCV_CMD_GET_SYSTEM_INFO: { + int buffer_pos = 0; + ctx->response_buffer[buffer_pos++] = NFCV_NOERROR; + ctx->response_buffer[buffer_pos++] = NFCV_SYSINFO_FLAG_DSFID | NFCV_SYSINFO_FLAG_AFI | + NFCV_SYSINFO_FLAG_MEMSIZE | NFCV_SYSINFO_FLAG_ICREF; + nfcv_revuidcpy(&ctx->response_buffer[buffer_pos], nfc_data->uid); + buffer_pos += NFCV_UID_LENGTH; + ctx->response_buffer[buffer_pos++] = nfcv_data->dsfid; /* DSFID */ + ctx->response_buffer[buffer_pos++] = nfcv_data->afi; /* AFI */ + ctx->response_buffer[buffer_pos++] = nfcv_data->block_num - 1; /* number of blocks */ + ctx->response_buffer[buffer_pos++] = nfcv_data->block_size - 1; /* block size */ + ctx->response_buffer[buffer_pos++] = nfcv_data->ic_ref; /* IC reference */ + + nfcv_emu_send( + tx_rx, + nfcv_data, + ctx->response_buffer, + buffer_pos, + ctx->response_flags, + ctx->send_time); + snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "SYSTEMINFO"); + break; + } + + case NFCV_CMD_CUST_ECHO_MODE: { + ctx->response_buffer[0] = NFCV_NOERROR; + nfcv_data->echo_mode = true; + nfcv_emu_send( + tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); + snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "ECHO mode"); + break; + } + + case NFCV_CMD_CUST_ECHO_DATA: { + nfcv_emu_send( + tx_rx, + nfcv_data, + &nfcv_data->frame[ctx->payload_offset], + nfcv_data->frame_length - ctx->payload_offset - 2, + NfcVSendFlagsSof | NfcVSendFlagsHighRate | NfcVSendFlagsEof, + ctx->send_time); + snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "ECHO data"); + break; + } + + default: + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "unsupported: %02X", + ctx->command); + break; + } + + if(strlen(nfcv_data->last_command) > 0) { +#ifdef NFCV_VERBOSE + FURI_LOG_D(TAG, "Received command %s", nfcv_data->last_command); +#endif + } +} + +void nfcv_emu_sniff_packet( + FuriHalNfcTxRxContext* tx_rx, + FuriHalNfcDevData* nfc_data, + void* nfcv_data_in) { + furi_assert(tx_rx); + furi_assert(nfc_data); + furi_assert(nfcv_data_in); + + NfcVData* nfcv_data = (NfcVData*)nfcv_data_in; + NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx; + + if(nfcv_data->frame_length < 2) { + return; + } + + /* parse the frame data for the upcoming part 3 handling */ + ctx->flags = nfcv_data->frame[0]; + ctx->command = nfcv_data->frame[1]; + ctx->selected = (ctx->flags & NFCV_REQ_FLAG_SELECT); + ctx->addressed = !(ctx->flags & NFCV_REQ_FLAG_INVENTORY) && + (ctx->flags & NFCV_REQ_FLAG_ADDRESS); + ctx->advanced = (ctx->command >= NFCV_CMD_ADVANCED); + ctx->address_offset = 2 + (ctx->advanced ? 1 : 0); + ctx->payload_offset = ctx->address_offset + (ctx->addressed ? NFCV_UID_LENGTH : 0); + + char flags_string[5]; + + snprintf( + flags_string, + 5, + "%c%c%c%d", + (ctx->flags & NFCV_REQ_FLAG_INVENTORY) ? + 'I' : + (ctx->addressed ? 'A' : (ctx->selected ? 'S' : '*')), + ctx->advanced ? 'X' : ' ', + (ctx->flags & NFCV_REQ_FLAG_DATA_RATE) ? 'h' : 'l', + (ctx->flags & NFCV_REQ_FLAG_SUB_CARRIER) ? 2 : 1); + + switch(ctx->command) { + case NFCV_CMD_INVENTORY: { + snprintf( + nfcv_data->last_command, sizeof(nfcv_data->last_command), "%s INVENTORY", flags_string); + break; + } + + case NFCV_CMD_STAY_QUIET: { + snprintf( + nfcv_data->last_command, sizeof(nfcv_data->last_command), "%s STAYQUIET", flags_string); + nfcv_data->quiet = true; + break; + } + + case NFCV_CMD_LOCK_BLOCK: { + uint8_t block = nfcv_data->frame[ctx->payload_offset]; + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "%s LOCK %d", + flags_string, + block); + break; + } + + case NFCV_CMD_WRITE_DSFID: { + uint8_t id = nfcv_data->frame[ctx->payload_offset]; + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "%s WR DSFID %d", + flags_string, + id); + break; + } + + case NFCV_CMD_WRITE_AFI: { + uint8_t id = nfcv_data->frame[ctx->payload_offset]; + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "%s WR AFI %d", + flags_string, + id); + break; + } + + case NFCV_CMD_LOCK_DSFID: { + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "%s LOCK DSFID", + flags_string); + break; + } + + case NFCV_CMD_LOCK_AFI: { + snprintf( + nfcv_data->last_command, sizeof(nfcv_data->last_command), "%s LOCK AFI", flags_string); + break; + } + + case NFCV_CMD_SELECT: { + snprintf( + nfcv_data->last_command, sizeof(nfcv_data->last_command), "%s SELECT", flags_string); + break; + } + + case NFCV_CMD_RESET_TO_READY: { + snprintf( + nfcv_data->last_command, sizeof(nfcv_data->last_command), "%s RESET", flags_string); + break; + } + + case NFCV_CMD_READ_MULTI_BLOCK: + case NFCV_CMD_READ_BLOCK: { + uint8_t block = nfcv_data->frame[ctx->payload_offset]; + uint8_t blocks = 1; + + if(ctx->command == NFCV_CMD_READ_MULTI_BLOCK) { + blocks = nfcv_data->frame[ctx->payload_offset + 1] + 1; + } + + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "%s READ %d cnt: %d", + flags_string, + block, + blocks); + + break; + } + + case NFCV_CMD_WRITE_MULTI_BLOCK: + case NFCV_CMD_WRITE_BLOCK: { + uint8_t block = nfcv_data->frame[ctx->payload_offset]; + uint8_t blocks = 1; + uint8_t data_pos = 1; + + if(ctx->command == NFCV_CMD_WRITE_MULTI_BLOCK) { + blocks = nfcv_data->frame[ctx->payload_offset + 1] + 1; + data_pos++; + } + + uint8_t* data = &nfcv_data->frame[ctx->payload_offset + data_pos]; + + if(ctx->command == NFCV_CMD_WRITE_MULTI_BLOCK) { + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "%s WRITE %d, cnd %d", + flags_string, + block, + blocks); + } else { + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "%s WRITE %d %02X %02X %02X %02X", + flags_string, + block, + data[0], + data[1], + data[2], + data[3]); + } + break; + } + + case NFCV_CMD_GET_SYSTEM_INFO: { + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "%s SYSTEMINFO", + flags_string); + break; + } + + default: + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "%s unsupported: %02X", + flags_string, + ctx->command); + break; + } + + if(strlen(nfcv_data->last_command) > 0) { + FURI_LOG_D(TAG, "Received command %s", nfcv_data->last_command); + } +} + +void nfcv_emu_init(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data) { + furi_assert(nfc_data); + furi_assert(nfcv_data); + + if(!nfcv_emu_alloc(nfcv_data)) { + FURI_LOG_E(TAG, "Failed to allocate structures"); + nfcv_data->ready = false; + return; + } + + strcpy(nfcv_data->last_command, ""); + nfcv_data->quiet = false; + nfcv_data->selected = false; + nfcv_data->modified = false; + + /* everything is initialized */ + nfcv_data->ready = true; + + /* ensure the GPIO is already in unmodulated state */ + furi_hal_gpio_init(&gpio_spi_r_mosi, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); + furi_hal_gpio_write(&gpio_spi_r_mosi, GPIO_LEVEL_UNMODULATED); + + rfal_platform_spi_acquire(); + /* stop operation to configure for transparent and passive mode */ + st25r3916ExecuteCommand(ST25R3916_CMD_STOP); + /* set enable, rx_enable and field detector enable */ + st25r3916WriteRegister( + ST25R3916_REG_OP_CONTROL, + ST25R3916_REG_OP_CONTROL_en | ST25R3916_REG_OP_CONTROL_rx_en | + ST25R3916_REG_OP_CONTROL_en_fd_auto_efd); + /* explicitely set the modulation resistor in case system config changes for some reason */ + st25r3916WriteRegister( + ST25R3916_REG_PT_MOD, + (0 << ST25R3916_REG_PT_MOD_ptm_res_shift) | (15 << ST25R3916_REG_PT_MOD_pt_res_shift)); + /* target mode: target, other fields do not have any effect as we use transparent mode */ + st25r3916WriteRegister(ST25R3916_REG_MODE, ST25R3916_REG_MODE_targ); + /* let us modulate the field using MOSI, read ASK modulation using IRQ */ + st25r3916ExecuteCommand(ST25R3916_CMD_TRANSPARENT_MODE); + + furi_hal_spi_bus_handle_deinit(&furi_hal_spi_bus_handle_nfc); + + /* if not set already, initialize the default protocol handler */ + if(!nfcv_data->emu_protocol_ctx) { + nfcv_data->emu_protocol_ctx = malloc(sizeof(NfcVEmuProtocolCtx)); + if(nfcv_data->sub_type == NfcVTypeSniff) { + nfcv_data->emu_protocol_handler = &nfcv_emu_sniff_packet; + } else { + nfcv_data->emu_protocol_handler = &nfcv_emu_handle_packet; + } + } + + FURI_LOG_D(TAG, "Starting NfcV emulation"); + FURI_LOG_D( + TAG, + " UID: %02X %02X %02X %02X %02X %02X %02X %02X", + nfc_data->uid[0], + nfc_data->uid[1], + nfc_data->uid[2], + nfc_data->uid[3], + nfc_data->uid[4], + nfc_data->uid[5], + nfc_data->uid[6], + nfc_data->uid[7]); + + switch(nfcv_data->sub_type) { + case NfcVTypeSlixL: + FURI_LOG_D(TAG, " Card type: SLIX-L"); + slix_l_prepare(nfcv_data); + break; + case NfcVTypeSlixS: + FURI_LOG_D(TAG, " Card type: SLIX-S"); + slix_s_prepare(nfcv_data); + break; + case NfcVTypeSlix2: + FURI_LOG_D(TAG, " Card type: SLIX2"); + slix2_prepare(nfcv_data); + break; + case NfcVTypeSlix: + FURI_LOG_D(TAG, " Card type: SLIX"); + slix_prepare(nfcv_data); + break; + case NfcVTypePlain: + FURI_LOG_D(TAG, " Card type: Plain"); + break; + case NfcVTypeSniff: + FURI_LOG_D(TAG, " Card type: Sniffing"); + break; + } + + /* allocate a 512 edge buffer, more than enough */ + nfcv_data->emu_air.reader_signal = + pulse_reader_alloc(&gpio_nfc_irq_rfid_pull, NFCV_PULSE_BUFFER); + /* timebase shall be 1 ns */ + pulse_reader_set_timebase(nfcv_data->emu_air.reader_signal, PulseReaderUnitNanosecond); + /* and configure to already calculate the number of bits */ + pulse_reader_set_bittime(nfcv_data->emu_air.reader_signal, NFCV_PULSE_DURATION_NS); + /* this IO is fed into the µC via a diode, so we need a pulldown */ + pulse_reader_set_pull(nfcv_data->emu_air.reader_signal, GpioPullDown); + + /* start sampling */ + pulse_reader_start(nfcv_data->emu_air.reader_signal); +} + +void nfcv_emu_deinit(NfcVData* nfcv_data) { + furi_assert(nfcv_data); + + furi_hal_spi_bus_handle_init(&furi_hal_spi_bus_handle_nfc); + nfcv_emu_free(nfcv_data); + + if(nfcv_data->emu_protocol_ctx) { + free(nfcv_data->emu_protocol_ctx); + nfcv_data->emu_protocol_ctx = NULL; + } + + /* set registers back to how we found them */ + st25r3916WriteRegister(ST25R3916_REG_OP_CONTROL, 0x00); + st25r3916WriteRegister(ST25R3916_REG_MODE, 0x08); + rfal_platform_spi_release(); +} + +bool nfcv_emu_loop( + FuriHalNfcTxRxContext* tx_rx, + FuriHalNfcDevData* nfc_data, + NfcVData* nfcv_data, + uint32_t timeout_ms) { + furi_assert(tx_rx); + furi_assert(nfc_data); + furi_assert(nfcv_data); + + bool ret = false; + uint32_t frame_state = NFCV_FRAME_STATE_SOF1; + uint32_t periods_previous = 0; + uint32_t frame_pos = 0; + uint32_t byte_value = 0; + uint32_t bits_received = 0; + uint32_t timeout = timeout_ms * 1000; + bool wait_for_pulse = false; + + if(!nfcv_data->ready) { + return false; + } + +#ifdef NFCV_DIAGNOSTIC_DUMPS + uint8_t period_buffer[NFCV_DIAGNOSTIC_DUMP_SIZE]; + uint32_t period_buffer_pos = 0; +#endif + + while(true) { + uint32_t periods = pulse_reader_receive(nfcv_data->emu_air.reader_signal, timeout); + uint32_t timestamp = DWT->CYCCNT; + + /* when timed out, reset to SOF state */ + if(periods == PULSE_READER_NO_EDGE || periods == PULSE_READER_LOST_EDGE) { + break; + } + +#ifdef NFCV_DIAGNOSTIC_DUMPS + if(period_buffer_pos < sizeof(period_buffer)) { + period_buffer[period_buffer_pos++] = periods; + } +#endif + + /* short helper for detecting a pulse position */ + if(wait_for_pulse) { + wait_for_pulse = false; + if(periods != 1) { + frame_state = NFCV_FRAME_STATE_RESET; + } + continue; + } + + switch(frame_state) { + case NFCV_FRAME_STATE_SOF1: + if(periods == 1) { + frame_state = NFCV_FRAME_STATE_SOF2; + } else { + frame_state = NFCV_FRAME_STATE_SOF1; + break; + } + break; + + case NFCV_FRAME_STATE_SOF2: + /* waiting for the second low period, telling us about coding */ + if(periods == 6) { + frame_state = NFCV_FRAME_STATE_CODING_256; + periods_previous = 0; + wait_for_pulse = true; + } else if(periods == 4) { + frame_state = NFCV_FRAME_STATE_CODING_4; + periods_previous = 2; + wait_for_pulse = true; + } else { + frame_state = NFCV_FRAME_STATE_RESET; + } + break; + + case NFCV_FRAME_STATE_CODING_256: + if(periods_previous > periods) { + frame_state = NFCV_FRAME_STATE_RESET; + break; + } + + /* previous symbol left us with some pulse periods */ + periods -= periods_previous; + + if(periods > 512) { + frame_state = NFCV_FRAME_STATE_RESET; + break; + } else if(periods == 2) { + frame_state = NFCV_FRAME_STATE_EOF; + break; + } + + periods_previous = 512 - (periods + 1); + byte_value = (periods - 1) / 2; + if(frame_pos < NFCV_FRAMESIZE_MAX) { + nfcv_data->frame[frame_pos++] = (uint8_t)byte_value; + } + + wait_for_pulse = true; + + break; + + case NFCV_FRAME_STATE_CODING_4: + if(periods_previous > periods) { + frame_state = NFCV_FRAME_STATE_RESET; + break; + } + + /* previous symbol left us with some pulse periods */ + periods -= periods_previous; + periods_previous = 0; + + byte_value >>= 2; + bits_received += 2; + + if(periods == 1) { + byte_value |= 0x00 << 6; + periods_previous = 6; + } else if(periods == 3) { + byte_value |= 0x01 << 6; + periods_previous = 4; + } else if(periods == 5) { + byte_value |= 0x02 << 6; + periods_previous = 2; + } else if(periods == 7) { + byte_value |= 0x03 << 6; + periods_previous = 0; + } else if(periods == 2) { + frame_state = NFCV_FRAME_STATE_EOF; + break; + } else { + frame_state = NFCV_FRAME_STATE_RESET; + break; + } + + if(bits_received >= 8) { + if(frame_pos < NFCV_FRAMESIZE_MAX) { + nfcv_data->frame[frame_pos++] = (uint8_t)byte_value; + } + bits_received = 0; + } + wait_for_pulse = true; + break; + } + + /* post-state-machine cleanup and reset */ + if(frame_state == NFCV_FRAME_STATE_RESET) { + frame_state = NFCV_FRAME_STATE_SOF1; + } else if(frame_state == NFCV_FRAME_STATE_EOF) { + nfcv_data->frame_length = frame_pos; + nfcv_data->eof_timestamp = timestamp; + break; + } + } + + if(frame_state == NFCV_FRAME_STATE_EOF) { + /* we know that this code uses TIM2, so stop pulse reader */ + pulse_reader_stop(nfcv_data->emu_air.reader_signal); + if(tx_rx->sniff_rx) { + tx_rx->sniff_rx(nfcv_data->frame, frame_pos * 8, false, tx_rx->sniff_context); + } + nfcv_data->emu_protocol_handler(tx_rx, nfc_data, nfcv_data); + + pulse_reader_start(nfcv_data->emu_air.reader_signal); + ret = true; + + } +#ifdef NFCV_VERBOSE + else { + if(frame_state != NFCV_FRAME_STATE_SOF1) { + FURI_LOG_T(TAG, "leaving while in state: %lu", frame_state); + } + } +#endif + +#ifdef NFCV_DIAGNOSTIC_DUMPS + if(period_buffer_pos) { + FURI_LOG_T(TAG, "pulses:"); + for(uint32_t pos = 0; pos < period_buffer_pos; pos++) { + FURI_LOG_T(TAG, " #%lu: %u", pos, period_buffer[pos]); + } + } +#endif + + return ret; +} diff --git a/lib/nfc/protocols/nfcv.h b/lib/nfc/protocols/nfcv.h new file mode 100644 index 00000000000..87a69673753 --- /dev/null +++ b/lib/nfc/protocols/nfcv.h @@ -0,0 +1,291 @@ +#pragma once + +#include +#include + +#include +#include +#include "nfc_util.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* true: modulating releases load, false: modulating adds load resistor to field coil */ +#define NFCV_LOAD_MODULATION_POLARITY (false) + +#define NFCV_FC (13560000.0f) /* MHz */ +#define NFCV_RESP_SUBC1_PULSE_32 (1.0f / (NFCV_FC / 32) / 2.0f) /* 1.1799 µs */ +#define NFCV_RESP_SUBC1_UNMOD_256 (256.0f / NFCV_FC) /* 18.8791 µs */ +#define NFCV_PULSE_DURATION_NS (128.0f * 1000000000.0f / NFCV_FC) + +/* ISO/IEC 15693-3:2019(E) 10.4.12: maximum number of blocks is defined as 256 */ +#define NFCV_BLOCKS_MAX 256 +/* ISO/IEC 15693-3:2019(E) 10.4.12: maximum size of blocks is defined as 32 */ +#define NFCV_BLOCKSIZE_MAX 32 +/* the resulting memory size a card can have */ +#define NFCV_MEMSIZE_MAX (NFCV_BLOCKS_MAX * NFCV_BLOCKSIZE_MAX) +/* ISO/IEC 15693-3:2019(E) 7.1b: standard allows up to 8192, the maxium frame length that we are expected to receive/send is less */ +#define NFCV_FRAMESIZE_MAX (1 + NFCV_MEMSIZE_MAX + NFCV_BLOCKS_MAX) + +/* maximum string length for log messages */ +#define NFCV_LOG_STR_LEN 128 +/* maximum of pulses to be buffered by pulse reader */ +#define NFCV_PULSE_BUFFER 512 + +//#define NFCV_DIAGNOSTIC_DUMPS +//#define NFCV_DIAGNOSTIC_DUMP_SIZE 256 +//#define NFCV_VERBOSE + +/* helpers to calculate the send time based on DWT->CYCCNT */ +#define NFCV_FDT_USEC(usec) ((usec)*64) +#define NFCV_FDT_FC(ticks) ((ticks)*6400 / 1356) + +/* state machine when receiving frame bits */ +#define NFCV_FRAME_STATE_SOF1 0 +#define NFCV_FRAME_STATE_SOF2 1 +#define NFCV_FRAME_STATE_CODING_4 2 +#define NFCV_FRAME_STATE_CODING_256 3 +#define NFCV_FRAME_STATE_EOF 4 +#define NFCV_FRAME_STATE_RESET 5 + +/* sequences for every section of a frame */ +#define NFCV_SIG_SOF 0 +#define NFCV_SIG_BIT0 1 +#define NFCV_SIG_BIT1 2 +#define NFCV_SIG_EOF 3 +#define NFCV_SIG_LOW_SOF 4 +#define NFCV_SIG_LOW_BIT0 5 +#define NFCV_SIG_LOW_BIT1 6 +#define NFCV_SIG_LOW_EOF 7 + +/* various constants */ +#define NFCV_COMMAND_RETRIES 5 +#define NFCV_UID_LENGTH 8 + +/* ISO15693 protocol flags */ +typedef enum { + /* ISO15693 protocol flags when INVENTORY is NOT set */ + NFCV_REQ_FLAG_SUB_CARRIER = (1 << 0), + NFCV_REQ_FLAG_DATA_RATE = (1 << 1), + NFCV_REQ_FLAG_INVENTORY = (1 << 2), + NFCV_REQ_FLAG_PROTOCOL_EXT = (1 << 3), + NFCV_REQ_FLAG_SELECT = (1 << 4), + NFCV_REQ_FLAG_ADDRESS = (1 << 5), + NFCV_REQ_FLAG_OPTION = (1 << 6), + /* ISO15693 protocol flags when INVENTORY flag is set */ + NFCV_REQ_FLAG_AFI = (1 << 4), + NFCV_REQ_FLAG_NB_SLOTS = (1 << 5) +} NfcVRequestFlags; + +/* ISO15693 protocol flags */ +typedef enum { + NFCV_RES_FLAG_ERROR = (1 << 0), + NFCV_RES_FLAG_VALIDITY = (1 << 1), + NFCV_RES_FLAG_FINAL = (1 << 2), + NFCV_RES_FLAG_PROTOCOL_EXT = (1 << 3), + NFCV_RES_FLAG_SEC_LEN1 = (1 << 4), + NFCV_RES_FLAG_SEC_LEN2 = (1 << 5), + NFCV_RES_FLAG_WAIT_EXT = (1 << 6), +} NfcVRsponseFlags; + +/* flags for SYSINFO response */ +typedef enum { + NFCV_SYSINFO_FLAG_DSFID = (1 << 0), + NFCV_SYSINFO_FLAG_AFI = (1 << 1), + NFCV_SYSINFO_FLAG_MEMSIZE = (1 << 2), + NFCV_SYSINFO_FLAG_ICREF = (1 << 3) +} NfcVSysinfoFlags; + +/* ISO15693 command codes */ +typedef enum { + /* mandatory command codes */ + NFCV_CMD_INVENTORY = 0x01, + NFCV_CMD_STAY_QUIET = 0x02, + /* optional command codes */ + NFCV_CMD_READ_BLOCK = 0x20, + NFCV_CMD_WRITE_BLOCK = 0x21, + NFCV_CMD_LOCK_BLOCK = 0x22, + NFCV_CMD_READ_MULTI_BLOCK = 0x23, + NFCV_CMD_WRITE_MULTI_BLOCK = 0x24, + NFCV_CMD_SELECT = 0x25, + NFCV_CMD_RESET_TO_READY = 0x26, + NFCV_CMD_WRITE_AFI = 0x27, + NFCV_CMD_LOCK_AFI = 0x28, + NFCV_CMD_WRITE_DSFID = 0x29, + NFCV_CMD_LOCK_DSFID = 0x2A, + NFCV_CMD_GET_SYSTEM_INFO = 0x2B, + NFCV_CMD_READ_MULTI_SECSTATUS = 0x2C, + /* advanced command codes */ + NFCV_CMD_ADVANCED = 0xA0, + /* flipper zero custom command codes */ + NFCV_CMD_CUST_ECHO_MODE = 0xDE, + NFCV_CMD_CUST_ECHO_DATA = 0xDF +} NfcVCommands; + +/* ISO15693 Response error codes */ +typedef enum { + NFCV_NOERROR = 0x00, + NFCV_ERROR_CMD_NOT_SUP = 0x01, // Command not supported + NFCV_ERROR_CMD_NOT_REC = 0x02, // Command not recognized (eg. parameter error) + NFCV_ERROR_CMD_OPTION = 0x03, // Command option not supported + NFCV_ERROR_GENERIC = 0x0F, // No additional Info about this error + NFCV_ERROR_BLOCK_UNAVAILABLE = 0x10, + NFCV_ERROR_BLOCK_LOCKED_ALREADY = 0x11, // cannot lock again + NFCV_ERROR_BLOCK_LOCKED = 0x12, // cannot be changed + NFCV_ERROR_BLOCK_WRITE = 0x13, // Writing was unsuccessful + NFCV_ERROR_BLOCL_WRITELOCK = 0x14 // Locking was unsuccessful +} NfcVErrorcodes; + +typedef enum { + NfcVLockBitDsfid = 1, + NfcVLockBitAfi = 2, +} NfcVLockBits; + +typedef enum { + NfcVAuthMethodManual, + NfcVAuthMethodTonieBox, +} NfcVAuthMethod; + +typedef enum { + NfcVTypePlain = 0, + NfcVTypeSlix = 1, + NfcVTypeSlixS = 2, + NfcVTypeSlixL = 3, + NfcVTypeSlix2 = 4, + NfcVTypeSniff = 255, +} NfcVSubtype; + +typedef enum { + NfcVSendFlagsNormal = 0, + NfcVSendFlagsSof = 1 << 0, + NfcVSendFlagsCrc = 1 << 1, + NfcVSendFlagsEof = 1 << 2, + NfcVSendFlagsOneSubcarrier = 0, + NfcVSendFlagsTwoSubcarrier = 1 << 3, + NfcVSendFlagsLowRate = 0, + NfcVSendFlagsHighRate = 1 << 4 +} NfcVSendFlags; + +typedef struct { + uint8_t key_read[4]; + uint8_t key_write[4]; + uint8_t key_privacy[4]; + uint8_t key_destroy[4]; + uint8_t key_eas[4]; + uint8_t rand[2]; + bool privacy; +} NfcVSlixData; + +typedef union { + NfcVSlixData slix; +} NfcVSubtypeData; + +typedef struct { + DigitalSignal* nfcv_resp_sof; + DigitalSignal* nfcv_resp_one; + DigitalSignal* nfcv_resp_zero; + DigitalSignal* nfcv_resp_eof; +} NfcVEmuAirSignals; + +typedef struct { + PulseReader* reader_signal; + DigitalSignal* nfcv_resp_pulse; /* pulse length, fc/32 */ + DigitalSignal* nfcv_resp_half_pulse; /* half pulse length, fc/32 */ + DigitalSignal* nfcv_resp_unmod; /* unmodulated length 256/fc */ + NfcVEmuAirSignals signals_high; + NfcVEmuAirSignals signals_low; + DigitalSequence* nfcv_signal; +} NfcVEmuAir; + +typedef void (*NfcVEmuProtocolHandler)( + FuriHalNfcTxRxContext* tx_rx, + FuriHalNfcDevData* nfc_data, + void* nfcv_data); +typedef bool (*NfcVEmuProtocolFilter)( + FuriHalNfcTxRxContext* tx_rx, + FuriHalNfcDevData* nfc_data, + void* nfcv_data); + +/* the default ISO15693 handler context */ +typedef struct { + uint8_t flags; /* ISO15693-3 flags of the header as specified */ + uint8_t command; /* ISO15693-3 command at offset 1 as specified */ + bool selected; /* ISO15693-3 flags: selected frame */ + bool addressed; /* ISO15693-3 flags: addressed frame */ + bool advanced; /* ISO15693-3 command: advanced command */ + uint8_t address_offset; /* ISO15693-3 offset of the address in frame, if addressed is set */ + uint8_t payload_offset; /* ISO15693-3 offset of the payload in frame */ + + uint8_t response_buffer[NFCV_FRAMESIZE_MAX]; /* pre-allocated response buffer */ + NfcVSendFlags response_flags; /* flags to use when sending response */ + uint32_t send_time; /* timestamp when to send the response */ + + NfcVEmuProtocolFilter emu_protocol_filter; +} NfcVEmuProtocolCtx; + +typedef struct { + /* common ISO15693 fields, being specified in ISO15693-3 */ + uint8_t dsfid; + uint8_t afi; + uint8_t ic_ref; + uint16_t block_num; + uint8_t block_size; + uint8_t data[NFCV_MEMSIZE_MAX]; + uint8_t security_status[1 + NFCV_BLOCKS_MAX]; + bool selected; + bool quiet; + + bool modified; + bool ready; + bool echo_mode; + + /* specfic variant infos */ + NfcVSubtype sub_type; + NfcVSubtypeData sub_data; + NfcVAuthMethod auth_method; + + /* precalced air level data */ + NfcVEmuAir emu_air; + + uint8_t* frame; /* [NFCV_FRAMESIZE_MAX] ISO15693-2 incoming raw data from air layer */ + uint8_t frame_length; /* ISO15693-2 length of incoming data */ + uint32_t eof_timestamp; /* ISO15693-2 EOF timestamp, read from DWT->CYCCNT */ + + /* handler for the protocol layer as specified in ISO15693-3 */ + NfcVEmuProtocolHandler emu_protocol_handler; + void* emu_protocol_ctx; + /* runtime data */ + char last_command[NFCV_LOG_STR_LEN]; + char error[NFCV_LOG_STR_LEN]; +} NfcVData; + +typedef struct { + uint16_t blocks_to_read; + int16_t blocks_read; +} NfcVReader; + +ReturnCode nfcv_read_blocks(NfcVReader* reader, NfcVData* data); +ReturnCode nfcv_read_sysinfo(FuriHalNfcDevData* nfc_data, NfcVData* data); +ReturnCode nfcv_inventory(uint8_t* uid); +bool nfcv_read_card(NfcVReader* reader, FuriHalNfcDevData* nfc_data, NfcVData* data); + +void nfcv_emu_init(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data); +void nfcv_emu_deinit(NfcVData* nfcv_data); +bool nfcv_emu_loop( + FuriHalNfcTxRxContext* tx_rx, + FuriHalNfcDevData* nfc_data, + NfcVData* nfcv_data, + uint32_t timeout_ms); +void nfcv_emu_send( + FuriHalNfcTxRxContext* tx_rx, + NfcVData* nfcv, + uint8_t* data, + uint8_t length, + NfcVSendFlags flags, + uint32_t send_time); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/slix.c b/lib/nfc/protocols/slix.c new file mode 100644 index 00000000000..ec3afc248b9 --- /dev/null +++ b/lib/nfc/protocols/slix.c @@ -0,0 +1,412 @@ + +#include +#include "nfcv.h" +#include "slix.h" +#include "nfc_util.h" +#include +#include "furi_hal_nfc.h" +#include + +#define TAG "SLIX" + +static uint32_t slix_read_be(uint8_t* data, uint32_t length) { + uint32_t value = 0; + + for(uint32_t pos = 0; pos < length; pos++) { + value <<= 8; + value |= data[pos]; + } + + return value; +} + +uint8_t slix_get_ti(FuriHalNfcDevData* nfc_data) { + return (nfc_data->uid[3] >> 3) & 3; +} + +bool slix_check_card_type(FuriHalNfcDevData* nfc_data) { + if((nfc_data->uid[0] == 0xE0) && (nfc_data->uid[1] == 0x04) && (nfc_data->uid[2] == 0x01) && + slix_get_ti(nfc_data) == 2) { + return true; + } + return false; +} + +bool slix2_check_card_type(FuriHalNfcDevData* nfc_data) { + if((nfc_data->uid[0] == 0xE0) && (nfc_data->uid[1] == 0x04) && (nfc_data->uid[2] == 0x01) && + slix_get_ti(nfc_data) == 1) { + return true; + } + return false; +} + +bool slix_s_check_card_type(FuriHalNfcDevData* nfc_data) { + if((nfc_data->uid[0] == 0xE0) && (nfc_data->uid[1] == 0x04) && (nfc_data->uid[2] == 0x02)) { + return true; + } + return false; +} + +bool slix_l_check_card_type(FuriHalNfcDevData* nfc_data) { + if((nfc_data->uid[0] == 0xE0) && (nfc_data->uid[1] == 0x04) && (nfc_data->uid[2] == 0x03)) { + return true; + } + return false; +} + +ReturnCode slix_get_random(NfcVData* data) { + uint16_t received = 0; + uint8_t rxBuf[32]; + + ReturnCode ret = rfalNfcvPollerTransceiveReq( + NFCV_CMD_NXP_GET_RANDOM_NUMBER, + RFAL_NFCV_REQ_FLAG_DEFAULT, + NFCV_MANUFACTURER_NXP, + NULL, + NULL, + 0, + rxBuf, + sizeof(rxBuf), + &received); + + if(ret == ERR_NONE) { + if(received != 3) { + return ERR_PROTO; + } + if(data != NULL) { + data->sub_data.slix.rand[0] = rxBuf[2]; + data->sub_data.slix.rand[1] = rxBuf[1]; + } + } + + return ret; +} + +ReturnCode slix_unlock(NfcVData* data, uint32_t password_id) { + furi_assert(rand); + + uint16_t received = 0; + uint8_t rxBuf[32]; + uint8_t cmd_set_pass[] = { + password_id, + data->sub_data.slix.rand[1], + data->sub_data.slix.rand[0], + data->sub_data.slix.rand[1], + data->sub_data.slix.rand[0]}; + uint8_t* password = NULL; + + switch(password_id) { + case SLIX_PASS_READ: + password = data->sub_data.slix.key_read; + break; + case SLIX_PASS_WRITE: + password = data->sub_data.slix.key_write; + break; + case SLIX_PASS_PRIVACY: + password = data->sub_data.slix.key_privacy; + break; + case SLIX_PASS_DESTROY: + password = data->sub_data.slix.key_destroy; + break; + case SLIX_PASS_EASAFI: + password = data->sub_data.slix.key_eas; + break; + default: + break; + } + + if(!password) { + return ERR_NOTSUPP; + } + + for(int pos = 0; pos < 4; pos++) { + cmd_set_pass[1 + pos] ^= password[3 - pos]; + } + + ReturnCode ret = rfalNfcvPollerTransceiveReq( + NFCV_CMD_NXP_SET_PASSWORD, + RFAL_NFCV_REQ_FLAG_DATA_RATE, + NFCV_MANUFACTURER_NXP, + NULL, + cmd_set_pass, + sizeof(cmd_set_pass), + rxBuf, + sizeof(rxBuf), + &received); + + return ret; +} + +bool slix_generic_protocol_filter( + FuriHalNfcTxRxContext* tx_rx, + FuriHalNfcDevData* nfc_data, + void* nfcv_data_in, + uint32_t password_supported) { + furi_assert(tx_rx); + furi_assert(nfc_data); + furi_assert(nfcv_data_in); + + NfcVData* nfcv_data = (NfcVData*)nfcv_data_in; + NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx; + NfcVSlixData* slix = &nfcv_data->sub_data.slix; + + if(slix->privacy && ctx->command != NFCV_CMD_NXP_GET_RANDOM_NUMBER && + ctx->command != NFCV_CMD_NXP_SET_PASSWORD) { + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "command 0x%02X ignored, privacy mode", + ctx->command); + FURI_LOG_D(TAG, "%s", nfcv_data->last_command); + return true; + } + + bool handled = false; + + switch(ctx->command) { + case NFCV_CMD_NXP_GET_RANDOM_NUMBER: { + slix->rand[0] = furi_hal_random_get(); + slix->rand[1] = furi_hal_random_get(); + + ctx->response_buffer[0] = NFCV_NOERROR; + ctx->response_buffer[1] = slix->rand[1]; + ctx->response_buffer[2] = slix->rand[0]; + + nfcv_emu_send( + tx_rx, nfcv_data, ctx->response_buffer, 3, ctx->response_flags, ctx->send_time); + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "GET_RANDOM_NUMBER -> 0x%02X%02X", + slix->rand[0], + slix->rand[1]); + + handled = true; + break; + } + + case NFCV_CMD_NXP_SET_PASSWORD: { + uint8_t password_id = nfcv_data->frame[ctx->payload_offset]; + + if(!(password_id & password_supported)) { + break; + } + + uint8_t* password_xored = &nfcv_data->frame[ctx->payload_offset + 1]; + uint8_t* rand = slix->rand; + uint8_t* password = NULL; + uint8_t password_rcv[4]; + + switch(password_id) { + case SLIX_PASS_READ: + password = slix->key_read; + break; + case SLIX_PASS_WRITE: + password = slix->key_write; + break; + case SLIX_PASS_PRIVACY: + password = slix->key_privacy; + break; + case SLIX_PASS_DESTROY: + password = slix->key_destroy; + break; + case SLIX_PASS_EASAFI: + password = slix->key_eas; + break; + default: + break; + } + + if(!password) { + break; + } + + for(int pos = 0; pos < 4; pos++) { + password_rcv[pos] = password_xored[3 - pos] ^ rand[pos % 2]; + } + uint32_t pass_expect = slix_read_be(password, 4); + uint32_t pass_received = slix_read_be(password_rcv, 4); + + /* if the password is all-zeroes, just accept any password*/ + if(!pass_expect || pass_expect == pass_received) { + switch(password_id) { + case SLIX_PASS_READ: + break; + case SLIX_PASS_WRITE: + break; + case SLIX_PASS_PRIVACY: + slix->privacy = false; + nfcv_data->modified = true; + break; + case SLIX_PASS_DESTROY: + FURI_LOG_D(TAG, "Pooof! Got destroyed"); + break; + case SLIX_PASS_EASAFI: + break; + default: + break; + } + ctx->response_buffer[0] = NFCV_NOERROR; + nfcv_emu_send( + tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "SET_PASSWORD #%02X 0x%08lX OK", + password_id, + pass_received); + } else { + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "SET_PASSWORD #%02X 0x%08lX/%08lX FAIL", + password_id, + pass_received, + pass_expect); + } + handled = true; + break; + } + + case NFCV_CMD_NXP_ENABLE_PRIVACY: { + ctx->response_buffer[0] = NFCV_NOERROR; + + nfcv_emu_send( + tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "NFCV_CMD_NXP_ENABLE_PRIVACY"); + + slix->privacy = true; + handled = true; + break; + } + } + + return handled; +} + +bool slix_l_protocol_filter( + FuriHalNfcTxRxContext* tx_rx, + FuriHalNfcDevData* nfc_data, + void* nfcv_data_in) { + furi_assert(tx_rx); + furi_assert(nfc_data); + furi_assert(nfcv_data_in); + + bool handled = false; + + /* many SLIX share some of the functions, place that in a generic handler */ + if(slix_generic_protocol_filter( + tx_rx, + nfc_data, + nfcv_data_in, + SLIX_PASS_PRIVACY | SLIX_PASS_DESTROY | SLIX_PASS_EASAFI)) { + return true; + } + + return handled; +} + +void slix_l_prepare(NfcVData* nfcv_data) { + FURI_LOG_D( + TAG, " Privacy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_privacy, 4)); + FURI_LOG_D( + TAG, " Destroy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_destroy, 4)); + FURI_LOG_D(TAG, " EAS pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_eas, 4)); + FURI_LOG_D(TAG, " Privacy mode: %s", nfcv_data->sub_data.slix.privacy ? "ON" : "OFF"); + + NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx; + ctx->emu_protocol_filter = &slix_l_protocol_filter; +} + +bool slix_s_protocol_filter( + FuriHalNfcTxRxContext* tx_rx, + FuriHalNfcDevData* nfc_data, + void* nfcv_data_in) { + furi_assert(tx_rx); + furi_assert(nfc_data); + furi_assert(nfcv_data_in); + + bool handled = false; + + /* many SLIX share some of the functions, place that in a generic handler */ + if(slix_generic_protocol_filter(tx_rx, nfc_data, nfcv_data_in, SLIX_PASS_ALL)) { + return true; + } + + return handled; +} + +void slix_s_prepare(NfcVData* nfcv_data) { + FURI_LOG_D( + TAG, " Privacy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_privacy, 4)); + FURI_LOG_D( + TAG, " Destroy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_destroy, 4)); + FURI_LOG_D(TAG, " EAS pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_eas, 4)); + FURI_LOG_D(TAG, " Privacy mode: %s", nfcv_data->sub_data.slix.privacy ? "ON" : "OFF"); + + NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx; + ctx->emu_protocol_filter = &slix_s_protocol_filter; +} + +bool slix_protocol_filter( + FuriHalNfcTxRxContext* tx_rx, + FuriHalNfcDevData* nfc_data, + void* nfcv_data_in) { + furi_assert(tx_rx); + furi_assert(nfc_data); + furi_assert(nfcv_data_in); + + bool handled = false; + + /* many SLIX share some of the functions, place that in a generic handler */ + if(slix_generic_protocol_filter(tx_rx, nfc_data, nfcv_data_in, SLIX_PASS_EASAFI)) { + return true; + } + + return handled; +} + +void slix_prepare(NfcVData* nfcv_data) { + FURI_LOG_D( + TAG, " Privacy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_privacy, 4)); + FURI_LOG_D( + TAG, " Destroy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_destroy, 4)); + FURI_LOG_D(TAG, " EAS pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_eas, 4)); + FURI_LOG_D(TAG, " Privacy mode: %s", nfcv_data->sub_data.slix.privacy ? "ON" : "OFF"); + + NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx; + ctx->emu_protocol_filter = &slix_protocol_filter; +} + +bool slix2_protocol_filter( + FuriHalNfcTxRxContext* tx_rx, + FuriHalNfcDevData* nfc_data, + void* nfcv_data_in) { + furi_assert(tx_rx); + furi_assert(nfc_data); + furi_assert(nfcv_data_in); + + bool handled = false; + + /* many SLIX share some of the functions, place that in a generic handler */ + if(slix_generic_protocol_filter(tx_rx, nfc_data, nfcv_data_in, SLIX_PASS_ALL)) { + return true; + } + + return handled; +} + +void slix2_prepare(NfcVData* nfcv_data) { + FURI_LOG_D( + TAG, " Privacy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_privacy, 4)); + FURI_LOG_D( + TAG, " Destroy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_destroy, 4)); + FURI_LOG_D(TAG, " EAS pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_eas, 4)); + FURI_LOG_D(TAG, " Privacy mode: %s", nfcv_data->sub_data.slix.privacy ? "ON" : "OFF"); + + NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx; + ctx->emu_protocol_filter = &slix2_protocol_filter; +} diff --git a/lib/nfc/protocols/slix.h b/lib/nfc/protocols/slix.h new file mode 100644 index 00000000000..701fa2f8209 --- /dev/null +++ b/lib/nfc/protocols/slix.h @@ -0,0 +1,46 @@ +#pragma once + +#include +#include +#include "nfc_util.h" +#include + +#define NFCV_MANUFACTURER_NXP 0x04 + +/* ISO15693-3 CUSTOM NXP COMMANDS */ +#define NFCV_CMD_NXP_SET_EAS 0xA2 +#define NFCV_CMD_NXP_RESET_EAS 0xA3 +#define NFCV_CMD_NXP_LOCK_EAS 0xA4 +#define NFCV_CMD_NXP_EAS_ALARM 0xA5 +#define NFCV_CMD_NXP_PASSWORD_PROTECT_EAS_AFI 0xA6 +#define NFCV_CMD_NXP_WRITE_EAS_ID 0xA7 +#define NFCV_CMD_NXP_INVENTORY_PAGE_READ 0xB0 +#define NFCV_CMD_NXP_INVENTORY_PAGE_READ_FAST 0xB1 +#define NFCV_CMD_NXP_GET_RANDOM_NUMBER 0xB2 +#define NFCV_CMD_NXP_SET_PASSWORD 0xB3 +#define NFCV_CMD_NXP_WRITE_PASSWORD 0xB4 +#define NFCV_CMD_NXP_DESTROY 0xB9 +#define NFCV_CMD_NXP_ENABLE_PRIVACY 0xBA + +/* available passwords */ +#define SLIX_PASS_READ 0x01 +#define SLIX_PASS_WRITE 0x02 +#define SLIX_PASS_PRIVACY 0x04 +#define SLIX_PASS_DESTROY 0x08 +#define SLIX_PASS_EASAFI 0x10 + +#define SLIX_PASS_ALL \ + (SLIX_PASS_READ | SLIX_PASS_WRITE | SLIX_PASS_PRIVACY | SLIX_PASS_DESTROY | SLIX_PASS_EASAFI) + +bool slix_check_card_type(FuriHalNfcDevData* nfc_data); +bool slix2_check_card_type(FuriHalNfcDevData* nfc_data); +bool slix_s_check_card_type(FuriHalNfcDevData* nfc_data); +bool slix_l_check_card_type(FuriHalNfcDevData* nfc_data); + +ReturnCode slix_get_random(NfcVData* data); +ReturnCode slix_unlock(NfcVData* data, uint32_t password_id); + +void slix_prepare(NfcVData* nfcv_data); +void slix_s_prepare(NfcVData* nfcv_data); +void slix_l_prepare(NfcVData* nfcv_data); +void slix2_prepare(NfcVData* nfcv_data); From 3226254876d993b9dc7d54a8c069b8fe5d4cf670 Mon Sep 17 00:00:00 2001 From: hedger Date: Thu, 8 Jun 2023 10:16:01 +0400 Subject: [PATCH 602/824] [FL-3351] github: re-enabled f18 build (#2743) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * github: re-enabled f18 build * scripts: storage: better transfer logging * Fix PVS warnings Co-authored-by: あく --- .github/workflows/build.yml | 6 +++--- .../main/nfc/scenes/nfc_scene_nfcv_emulate.c | 2 +- lib/nfc/nfc_device.c | 2 +- lib/nfc/nfc_worker.c | 16 +++++++-------- lib/nfc/protocols/nfcv.c | 4 ++-- lib/nfc/protocols/slix.c | 2 +- scripts/flipper/storage.py | 20 +++++++++++-------- 7 files changed, 28 insertions(+), 24 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e8b76a02cc6..fb0f13e207f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,7 +9,7 @@ on: pull_request: env: - TARGETS: f7 + TARGETS: f7 f18 DEFAULT_TARGET: f7 FBT_TOOLCHAIN_PATH: /runner/_work @@ -96,8 +96,8 @@ jobs: - name: 'Copy map analyser files' if: ${{ !github.event.pull_request.head.repo.fork }} run: | - cp build/f7-firmware-*/firmware.elf.map map_analyser_files/firmware.elf.map - cp build/f7-firmware-*/firmware.elf map_analyser_files/firmware.elf + cp build/${DEFAULT_TARGET}-firmware-*/firmware.elf.map map_analyser_files/firmware.elf.map + cp build/${DEFAULT_TARGET}-firmware-*/firmware.elf map_analyser_files/firmware.elf cp ${{ github.event_path }} map_analyser_files/event.json - name: 'Analyse map file' diff --git a/applications/main/nfc/scenes/nfc_scene_nfcv_emulate.c b/applications/main/nfc/scenes/nfc_scene_nfcv_emulate.c index 3dd7c460b50..d812988bdfd 100644 --- a/applications/main/nfc/scenes/nfc_scene_nfcv_emulate.c +++ b/applications/main/nfc/scenes/nfc_scene_nfcv_emulate.c @@ -50,7 +50,7 @@ static void nfc_scene_nfcv_emulate_widget_config(Nfc* nfc, bool data_received) { widget_add_icon_element(widget, 0, 3, &I_NFC_dolphin_emulation_47x61); widget_add_string_multiline_element( widget, 87, 13, AlignCenter, AlignTop, FontPrimary, "Emulating\nNFC V"); - if(strcmp(nfc->dev->dev_name, "")) { + if(strcmp(nfc->dev->dev_name, "") != 0) { furi_string_printf(info_str, "%s", nfc->dev->dev_name); } else { for(uint8_t i = 0; i < data->uid_len; i++) { diff --git a/lib/nfc/nfc_device.c b/lib/nfc/nfc_device.c index 9646c262e50..952fca254bd 100644 --- a/lib/nfc/nfc_device.c +++ b/lib/nfc/nfc_device.c @@ -808,7 +808,7 @@ static bool nfc_device_save_slix2_data(FlipperFormat* file, NfcDevice* dev) { return saved; } -bool nfc_device_load_slix2_data(FlipperFormat* file, NfcDevice* dev) { +bool nfc_device_load_slix2_data(FlipperFormat* file, NfcDevice* dev) { // -V524 bool parsed = false; NfcVSlixData* data = &dev->dev_data.nfcv_data.sub_data.slix; memset(data, 0, sizeof(NfcVSlixData)); diff --git a/lib/nfc/nfc_worker.c b/lib/nfc/nfc_worker.c index 293f1ce705c..974bb0a4d19 100644 --- a/lib/nfc/nfc_worker.c +++ b/lib/nfc/nfc_worker.c @@ -297,10 +297,10 @@ void nfc_worker_nfcv_unlock(NfcWorker* nfc_worker) { ret = slix_unlock(nfcv_data, 4); } else { key = 0x7FFD6E5B; - key_data[0] = key >> 24; - key_data[1] = key >> 16; - key_data[2] = key >> 8; - key_data[3] = key >> 0; + key_data[0] = (key >> 24) & 0xFF; + key_data[1] = (key >> 16) & 0xFF; + key_data[2] = (key >> 8) & 0xFF; + key_data[3] = (key >> 0) & 0xFF; ret = slix_unlock(nfcv_data, 4); if(ret != ERR_NONE) { @@ -317,10 +317,10 @@ void nfc_worker_nfcv_unlock(NfcWorker* nfc_worker) { } key = 0x0F0F0F0F; - key_data[0] = key >> 24; - key_data[1] = key >> 16; - key_data[2] = key >> 8; - key_data[3] = key >> 0; + key_data[0] = (key >> 24) & 0xFF; + key_data[1] = (key >> 16) & 0xFF; + key_data[2] = (key >> 8) & 0xFF; + key_data[3] = (key >> 0) & 0xFF; ret = slix_unlock(nfcv_data, 4); } } diff --git a/lib/nfc/protocols/nfcv.c b/lib/nfc/protocols/nfcv.c index ac818b7a4be..3c37153d843 100644 --- a/lib/nfc/protocols/nfcv.c +++ b/lib/nfc/protocols/nfcv.c @@ -438,7 +438,7 @@ void nfcv_emu_send( furi_assert(nfcv); /* picked default value (0) to match the most common format */ - if(!flags) { + if(flags == NfcVSendFlagsNormal) { flags = NfcVSendFlagsSof | NfcVSendFlagsCrc | NfcVSendFlagsEof | NfcVSendFlagsOneSubcarrier | NfcVSendFlagsHighRate; } @@ -1326,7 +1326,7 @@ bool nfcv_emu_loop( bits_received += 2; if(periods == 1) { - byte_value |= 0x00 << 6; + byte_value |= 0x00 << 6; // -V684 periods_previous = 6; } else if(periods == 3) { byte_value |= 0x01 << 6; diff --git a/lib/nfc/protocols/slix.c b/lib/nfc/protocols/slix.c index ec3afc248b9..1c14c0bf94b 100644 --- a/lib/nfc/protocols/slix.c +++ b/lib/nfc/protocols/slix.c @@ -381,7 +381,7 @@ void slix_prepare(NfcVData* nfcv_data) { ctx->emu_protocol_filter = &slix_protocol_filter; } -bool slix2_protocol_filter( +bool slix2_protocol_filter( // -V524 FuriHalNfcTxRxContext* tx_rx, FuriHalNfcDevData* nfc_data, void* nfcv_data_in) { diff --git a/scripts/flipper/storage.py b/scripts/flipper/storage.py index f4d622bfe42..2c9c043d5a8 100644 --- a/scripts/flipper/storage.py +++ b/scripts/flipper/storage.py @@ -257,12 +257,12 @@ def send_file(self, filename_from: str, filename_to: str): self.read.until(self.CLI_PROMPT) ftell = file.tell() - percent = str(math.ceil(ftell / filesize * 100)) - total_chunks = str(math.ceil(filesize / buffer_size)) - current_chunk = str(math.ceil(ftell / buffer_size)) + percent = math.ceil(ftell / filesize * 100) + total_chunks = math.ceil(filesize / buffer_size) + current_chunk = math.ceil(ftell / buffer_size) approx_speed = ftell / (time.time() - start_time + 0.0001) sys.stdout.write( - f"\r{percent}%, chunk {current_chunk} of {total_chunks} @ {approx_speed/1024:.2f} kb/s" + f"\r<{percent:3d}%, chunk {current_chunk:2d} of {total_chunks:2d} @ {approx_speed/1024:.2f} kb/s" ) sys.stdout.flush() print() @@ -270,6 +270,7 @@ def send_file(self, filename_from: str, filename_to: str): def read_file(self, filename: str): """Receive file from Flipper, and get filedata (bytes)""" buffer_size = self.chunk_size + start_time = time.time() self.send_and_wait_eol( 'storage read_chunks "' + filename + '" ' + str(buffer_size) + "\r" ) @@ -290,10 +291,13 @@ def read_file(self, filename: str): filedata.extend(self.port.read(chunk_size)) read_size = read_size + chunk_size - percent = str(math.ceil(read_size / size * 100)) - total_chunks = str(math.ceil(size / buffer_size)) - current_chunk = str(math.ceil(read_size / buffer_size)) - sys.stdout.write(f"\r{percent}%, chunk {current_chunk} of {total_chunks}") + percent = math.ceil(read_size / size * 100) + total_chunks = math.ceil(size / buffer_size) + current_chunk = math.ceil(read_size / buffer_size) + approx_speed = read_size / (time.time() - start_time + 0.0001) + sys.stdout.write( + f"\r>{percent:3d}%, chunk {current_chunk:2d} of {total_chunks:2d} @ {approx_speed/1024:.2f} kb/s" + ) sys.stdout.flush() print() self.read.until(self.CLI_PROMPT) From e5343fdc9a44397a80221c8d26722a17082975f5 Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Thu, 8 Jun 2023 01:28:08 -0700 Subject: [PATCH 603/824] Scripts: WiFi board updater (#2625) * Scripts: wifi updater * WiFi board updater: lint, process download error * WiFi board updater: auto cleanup temp dir * Scripts: fix server address --- scripts/wifi_board.py | 240 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 240 insertions(+) create mode 100755 scripts/wifi_board.py diff --git a/scripts/wifi_board.py b/scripts/wifi_board.py new file mode 100755 index 00000000000..3f89ebdc656 --- /dev/null +++ b/scripts/wifi_board.py @@ -0,0 +1,240 @@ +#!/usr/bin/env python3 + +from flipper.app import App +from serial.tools.list_ports_common import ListPortInfo + +import logging +import os +import tempfile +import subprocess +import serial.tools.list_ports as list_ports +import json +import requests +import tarfile + + +class UpdateDownloader: + UPDATE_SERVER = "https://update.flipperzero.one" + UPDATE_PROJECT = "/blackmagic-firmware" + UPDATE_INDEX = UPDATE_SERVER + UPDATE_PROJECT + "/directory.json" + UPDATE_TYPE = "full_tgz" + + CHANNEL_ID_ALIAS = { + "dev": "development", + "rc": "release-candidate", + "r": "release", + "rel": "release", + } + + def __init__(self): + self.logger = logging.getLogger() + + def download(self, channel_id: str, dir: str) -> bool: + # Aliases + if channel_id in self.CHANNEL_ID_ALIAS: + channel_id = self.CHANNEL_ID_ALIAS[channel_id] + + # Make directory + if not os.path.exists(dir): + self.logger.info(f"Creating directory {dir}") + os.makedirs(dir) + + # Download json index + self.logger.info(f"Downloading {self.UPDATE_INDEX}") + response = requests.get(self.UPDATE_INDEX) + if response.status_code != 200: + self.logger.error(f"Failed to download {self.UPDATE_INDEX}") + return False + + # Parse json index + try: + index = json.loads(response.content) + except Exception as e: + self.logger.error(f"Failed to parse json index: {e}") + return False + + # Find channel + channel = None + for channel_candidate in index["channels"]: + if channel_candidate["id"] == channel_id: + channel = channel_candidate + break + + # Check if channel found + if channel is None: + self.logger.error( + f"Channel '{channel_id}' not found. Valid channels: {', '.join([c['id'] for c in index['channels']])}" + ) + return False + + self.logger.info(f"Using channel '{channel_id}'") + + # Get latest version + try: + version = channel["versions"][0] + except Exception as e: + self.logger.error(f"Failed to get version: {e}") + return False + + self.logger.info(f"Using version '{version['version']}'") + + # Get changelog + changelog = None + try: + changelog = version["changelog"] + except Exception as e: + self.logger.error(f"Failed to get changelog: {e}") + + # print changelog + if changelog is not None: + self.logger.info(f"Changelog:") + for line in changelog.split("\n"): + if line.strip() == "": + continue + self.logger.info(f" {line}") + + # Find file + file_url = None + for file_candidate in version["files"]: + if file_candidate["type"] == self.UPDATE_TYPE: + file_url = file_candidate["url"] + break + + if file_url is None: + self.logger.error(f"File not found") + return False + + # Make file path + file_name = file_url.split("/")[-1] + file_path = os.path.join(dir, file_name) + + # Download file + self.logger.info(f"Downloading {file_url} to {file_path}") + with open(file_path, "wb") as f: + response = requests.get(file_url) + f.write(response.content) + + # Unzip tgz + self.logger.info(f"Unzipping {file_path}") + with tarfile.open(file_path, "r") as tar: + tar.extractall(dir) + + return True + + +class Main(App): + def init(self): + self.parser.add_argument("-p", "--port", help="CDC Port", default="auto") + self.parser.add_argument( + "-c", "--channel", help="Channel name", default="release" + ) + self.parser.set_defaults(func=self.update) + + # logging + self.logger = logging.getLogger() + + def find_wifi_board(self) -> bool: + # idk why, but python thinks that list_ports.grep returns tuple[str, str, str] + blackmagics: list[ListPortInfo] = list(list_ports.grep("blackmagic")) # type: ignore + daps: list[ListPortInfo] = list(list_ports.grep("CMSIS-DAP")) # type: ignore + + return len(blackmagics) > 0 or len(daps) > 0 + + def find_wifi_board_bootloader(self): + # idk why, but python thinks that list_ports.grep returns tuple[str, str, str] + ports: list[ListPortInfo] = list(list_ports.grep("ESP32-S2")) # type: ignore + + if len(ports) == 0: + # Blackmagic probe serial port not found, will be handled later + pass + elif len(ports) > 1: + raise Exception("More than one WiFi board found") + else: + port = ports[0] + if os.name == "nt": + port.device = f"\\\\.\\{port.device}" + return port.device + + def update(self): + try: + port = self.find_wifi_board_bootloader() + except Exception as e: + self.logger.error(f"{e}") + return 1 + + if self.args.port != "auto": + port = self.args.port + + available_ports = [p[0] for p in list(list_ports.comports())] + if port not in available_ports: + self.logger.error(f"Port {port} not found") + return 1 + + if port is None: + if self.find_wifi_board(): + self.logger.error("WiFi board found, but not in bootloader mode.") + self.logger.info("Please hold down BOOT button and press RESET button") + else: + self.logger.error("WiFi board not found") + self.logger.info( + "Please connect WiFi board to your computer, hold down BOOT button and press RESET button" + ) + return 1 + + # get temporary dir + with tempfile.TemporaryDirectory() as temp_dir: + downloader = UpdateDownloader() + + # download latest channel update + try: + if not downloader.download(self.args.channel, temp_dir): + self.logger.error(f"Cannot download update") + return 1 + except Exception as e: + self.logger.error(f"Cannot download update: {e}") + return 1 + + with open(os.path.join(temp_dir, "flash.command"), "r") as f: + flash_command = f.read() + + flash_command = flash_command.replace("\n", "").replace("\r", "") + flash_command = flash_command.replace("(PORT)", port) + + # We can't reset the board after flashing via usb + flash_command = flash_command.replace( + "--after hard_reset", "--after no_reset_stub" + ) + + args = flash_command.split(" ")[0:] + args = list(filter(None, args)) + + esptool_params = [] + esptool_params.extend(args) + + self.logger.info(f'Running command: "{" ".join(args)}" in "{temp_dir}"') + + process = subprocess.Popen( + esptool_params, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + cwd=temp_dir, + bufsize=1, + universal_newlines=True, + ) + + while process.poll() is None: + if process.stdout is not None: + for line in process.stdout: + self.logger.debug(f"{line.strip()}") + + if process.returncode != 0: + self.logger.error(f"Failed to flash WiFi board") + else: + self.logger.info("WiFi board flashed successfully") + self.logger.info("Press RESET button on WiFi board to start it") + + return process.returncode + + +if __name__ == "__main__": + Main()() From e3e64e5e839926f2c3b8a190fcc9c1c3ee1a2a4b Mon Sep 17 00:00:00 2001 From: hedger Date: Thu, 8 Jun 2023 13:42:02 +0400 Subject: [PATCH 604/824] [FL-3267] ble: refactored bt gatt characteristics setup (#2587) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ble: refactored bt gatt characteristics setup * ble: naming fixes, small optimizations * ble: expanded bitfields; fixed pvs warnings * ble: fixed pvs warnings for real * ble: using FlipperGattCharacteristicDataPropsFixed for char[] props * ble: removed flipper_gatt_characteristic_props_const_char * ble: gatt: naming changes * ble: gatt: fixed device_info service constant attrs sizes * ble: gatt: copy descriptors to char instances; reworked hid chars to be callback-based; moved max size getter to callback with NULL data; added comments * ble: gatt: removed hid_svc_report_data_callback * ble: hid svc: better double loop idx naming * ble: hid svc: simplified hid_svc_update_info * ble: gatt: removed magic values; fixed type for HidSvcGattCharacteristicInfo * ble: gatt: moved long uuids to separate files Co-authored-by: gornekich Co-authored-by: あく --- applications/services/bt/bt_service/bt.c | 2 +- firmware/targets/f7/ble_glue/app_debug.c | 4 +- firmware/targets/f7/ble_glue/ble_app.c | 91 ++--- .../targets/f7/ble_glue/dev_info_service.c | 220 ------------ firmware/targets/f7/ble_glue/hid_service.c | 332 ------------------ .../ble_glue/{ => services}/battery_service.c | 118 +++---- .../ble_glue/{ => services}/battery_service.h | 0 .../f7/ble_glue/services/dev_info_service.c | 176 ++++++++++ .../{ => services}/dev_info_service.h | 0 .../services/dev_info_service_uuid.inc | 3 + .../targets/f7/ble_glue/services/gatt_char.c | 123 +++++++ .../targets/f7/ble_glue/services/gatt_char.h | 96 +++++ .../f7/ble_glue/services/hid_service.c | 293 ++++++++++++++++ .../f7/ble_glue/{ => services}/hid_service.h | 3 +- .../ble_glue/{ => services}/serial_service.c | 195 +++++----- .../ble_glue/{ => services}/serial_service.h | 0 .../ble_glue/services/serial_service_uuid.inc | 12 + firmware/targets/f7/furi_hal/furi_hal_bt.c | 3 +- .../targets/f7/furi_hal/furi_hal_bt_hid.c | 10 +- .../targets/f7/furi_hal/furi_hal_bt_serial.c | 6 +- .../targets/furi_hal_include/furi_hal_bt.h | 2 +- .../furi_hal_include/furi_hal_bt_serial.h | 2 +- 22 files changed, 898 insertions(+), 793 deletions(-) delete mode 100644 firmware/targets/f7/ble_glue/dev_info_service.c delete mode 100644 firmware/targets/f7/ble_glue/hid_service.c rename firmware/targets/f7/ble_glue/{ => services}/battery_service.c (53%) rename firmware/targets/f7/ble_glue/{ => services}/battery_service.h (100%) create mode 100644 firmware/targets/f7/ble_glue/services/dev_info_service.c rename firmware/targets/f7/ble_glue/{ => services}/dev_info_service.h (100%) create mode 100644 firmware/targets/f7/ble_glue/services/dev_info_service_uuid.inc create mode 100644 firmware/targets/f7/ble_glue/services/gatt_char.c create mode 100644 firmware/targets/f7/ble_glue/services/gatt_char.h create mode 100644 firmware/targets/f7/ble_glue/services/hid_service.c rename firmware/targets/f7/ble_glue/{ => services}/hid_service.h (87%) rename firmware/targets/f7/ble_glue/{ => services}/serial_service.c (57%) rename firmware/targets/f7/ble_glue/{ => services}/serial_service.h (100%) create mode 100644 firmware/targets/f7/ble_glue/services/serial_service_uuid.inc diff --git a/applications/services/bt/bt_service/bt.c b/applications/services/bt/bt_service/bt.c index 2dcea34856c..1b12ee303c6 100644 --- a/applications/services/bt/bt_service/bt.c +++ b/applications/services/bt/bt_service/bt.c @@ -1,7 +1,7 @@ #include "bt_i.h" -#include "battery_service.h" #include "bt_keys_storage.h" +#include #include #include #include diff --git a/firmware/targets/f7/ble_glue/app_debug.c b/firmware/targets/f7/ble_glue/app_debug.c index b443bee21f0..d2885282230 100644 --- a/firmware/targets/f7/ble_glue/app_debug.c +++ b/firmware/targets/f7/ble_glue/app_debug.c @@ -196,14 +196,14 @@ static void APPD_SetCPU2GpioConfig(void) { gpio_config.Pin = gpiob_pin_list; LL_C2_AHB2_GRP1_EnableClock(LL_C2_AHB2_GRP1_PERIPH_GPIOB); LL_GPIO_Init(GPIOB, &gpio_config); - LL_GPIO_ResetOutputPin(GPIOB, gpioa_pin_list); + LL_GPIO_ResetOutputPin(GPIOB, gpiob_pin_list); } if(gpioc_pin_list != 0) { gpio_config.Pin = gpioc_pin_list; LL_C2_AHB2_GRP1_EnableClock(LL_C2_AHB2_GRP1_PERIPH_GPIOC); LL_GPIO_Init(GPIOC, &gpio_config); - LL_GPIO_ResetOutputPin(GPIOC, gpioa_pin_list); + LL_GPIO_ResetOutputPin(GPIOC, gpioc_pin_list); } } diff --git a/firmware/targets/f7/ble_glue/ble_app.c b/firmware/targets/f7/ble_glue/ble_app.c index 37d8f7cd04b..c0418d9fe80 100644 --- a/firmware/targets/f7/ble_glue/ble_app.c +++ b/firmware/targets/f7/ble_glue/ble_app.c @@ -33,6 +33,51 @@ static int32_t ble_app_hci_thread(void* context); static void ble_app_hci_event_handler(void* pPayload); static void ble_app_hci_status_not_handler(HCI_TL_CmdStatus_t status); +static const HCI_TL_HciInitConf_t hci_tl_config = { + .p_cmdbuffer = (uint8_t*)&ble_app_cmd_buffer, + .StatusNotCallBack = ble_app_hci_status_not_handler, +}; + +static const SHCI_C2_CONFIG_Cmd_Param_t config_param = { + .PayloadCmdSize = SHCI_C2_CONFIG_PAYLOAD_CMD_SIZE, + .Config1 = SHCI_C2_CONFIG_CONFIG1_BIT0_BLE_NVM_DATA_TO_SRAM, + .BleNvmRamAddress = (uint32_t)ble_app_nvm, + .EvtMask1 = SHCI_C2_CONFIG_EVTMASK1_BIT1_BLE_NVM_RAM_UPDATE_ENABLE, +}; + +static const SHCI_C2_Ble_Init_Cmd_Packet_t ble_init_cmd_packet = { + .Header = {{0, 0, 0}}, // Header unused + .Param = { + .pBleBufferAddress = 0, // pBleBufferAddress not used + .BleBufferSize = 0, // BleBufferSize not used + .NumAttrRecord = CFG_BLE_NUM_GATT_ATTRIBUTES, + .NumAttrServ = CFG_BLE_NUM_GATT_SERVICES, + .AttrValueArrSize = CFG_BLE_ATT_VALUE_ARRAY_SIZE, + .NumOfLinks = CFG_BLE_NUM_LINK, + .ExtendedPacketLengthEnable = CFG_BLE_DATA_LENGTH_EXTENSION, + .PrWriteListSize = CFG_BLE_PREPARE_WRITE_LIST_SIZE, + .MblockCount = CFG_BLE_MBLOCK_COUNT, + .AttMtu = CFG_BLE_MAX_ATT_MTU, + .SlaveSca = CFG_BLE_SLAVE_SCA, + .MasterSca = CFG_BLE_MASTER_SCA, + .LsSource = CFG_BLE_LSE_SOURCE, + .MaxConnEventLength = CFG_BLE_MAX_CONN_EVENT_LENGTH, + .HsStartupTime = CFG_BLE_HSE_STARTUP_TIME, + .ViterbiEnable = CFG_BLE_VITERBI_MODE, + .Options = CFG_BLE_OPTIONS, + .HwVersion = 0, + .max_coc_initiator_nbr = 32, + .min_tx_power = 0, + .max_tx_power = 0, + .rx_model_config = 1, + /* New stack (13.3->15.0) */ + .max_adv_set_nbr = 1, // Only used if SHCI_C2_BLE_INIT_OPTIONS_EXT_ADV is set + .max_adv_data_len = 31, // Only used if SHCI_C2_BLE_INIT_OPTIONS_EXT_ADV is set + .tx_path_compens = 0, // RF TX Path Compensation, * 0.1 dB + .rx_path_compens = 0, // RF RX Path Compensation, * 0.1 dB + .ble_core_version = 11, // BLE Core Version: 11(5.2), 12(5.3) + }}; + bool ble_app_init() { SHCI_CmdStatus_t status; ble_app = malloc(sizeof(BleApp)); @@ -44,58 +89,16 @@ bool ble_app_init() { furi_thread_start(ble_app->thread); // Initialize Ble Transport Layer - HCI_TL_HciInitConf_t hci_tl_config = { - .p_cmdbuffer = (uint8_t*)&ble_app_cmd_buffer, - .StatusNotCallBack = ble_app_hci_status_not_handler, - }; hci_init(ble_app_hci_event_handler, (void*)&hci_tl_config); // Configure NVM store for pairing data - SHCI_C2_CONFIG_Cmd_Param_t config_param = { - .PayloadCmdSize = SHCI_C2_CONFIG_PAYLOAD_CMD_SIZE, - .Config1 = SHCI_C2_CONFIG_CONFIG1_BIT0_BLE_NVM_DATA_TO_SRAM, - .BleNvmRamAddress = (uint32_t)ble_app_nvm, - .EvtMask1 = SHCI_C2_CONFIG_EVTMASK1_BIT1_BLE_NVM_RAM_UPDATE_ENABLE, - }; - status = SHCI_C2_Config(&config_param); + status = SHCI_C2_Config((SHCI_C2_CONFIG_Cmd_Param_t*)&config_param); if(status) { FURI_LOG_E(TAG, "Failed to configure 2nd core: %d", status); } // Start ble stack on 2nd core - SHCI_C2_Ble_Init_Cmd_Packet_t ble_init_cmd_packet = { - .Header = {{0, 0, 0}}, // Header unused - .Param = { - .pBleBufferAddress = 0, // pBleBufferAddress not used - .BleBufferSize = 0, // BleBufferSize not used - .NumAttrRecord = CFG_BLE_NUM_GATT_ATTRIBUTES, - .NumAttrServ = CFG_BLE_NUM_GATT_SERVICES, - .AttrValueArrSize = CFG_BLE_ATT_VALUE_ARRAY_SIZE, - .NumOfLinks = CFG_BLE_NUM_LINK, - .ExtendedPacketLengthEnable = CFG_BLE_DATA_LENGTH_EXTENSION, - .PrWriteListSize = CFG_BLE_PREPARE_WRITE_LIST_SIZE, - .MblockCount = CFG_BLE_MBLOCK_COUNT, - .AttMtu = CFG_BLE_MAX_ATT_MTU, - .SlaveSca = CFG_BLE_SLAVE_SCA, - .MasterSca = CFG_BLE_MASTER_SCA, - .LsSource = CFG_BLE_LSE_SOURCE, - .MaxConnEventLength = CFG_BLE_MAX_CONN_EVENT_LENGTH, - .HsStartupTime = CFG_BLE_HSE_STARTUP_TIME, - .ViterbiEnable = CFG_BLE_VITERBI_MODE, - .Options = CFG_BLE_OPTIONS, - .HwVersion = 0, - .max_coc_initiator_nbr = 32, - .min_tx_power = 0, - .max_tx_power = 0, - .rx_model_config = 1, - /* New stack (13.3->15.0) */ - .max_adv_set_nbr = 1, // Only used if SHCI_C2_BLE_INIT_OPTIONS_EXT_ADV is set - .max_adv_data_len = 31, // Only used if SHCI_C2_BLE_INIT_OPTIONS_EXT_ADV is set - .tx_path_compens = 0, // RF TX Path Compensation, * 0.1 dB - .rx_path_compens = 0, // RF RX Path Compensation, * 0.1 dB - .ble_core_version = 11, // BLE Core Version: 11(5.2), 12(5.3) - }}; - status = SHCI_C2_BLE_Init(&ble_init_cmd_packet); + status = SHCI_C2_BLE_Init((SHCI_C2_Ble_Init_Cmd_Packet_t*)&ble_init_cmd_packet); if(status) { FURI_LOG_E(TAG, "Failed to start ble stack: %d", status); } diff --git a/firmware/targets/f7/ble_glue/dev_info_service.c b/firmware/targets/f7/ble_glue/dev_info_service.c deleted file mode 100644 index 8bdb2eea84c..00000000000 --- a/firmware/targets/f7/ble_glue/dev_info_service.c +++ /dev/null @@ -1,220 +0,0 @@ -#include "dev_info_service.h" -#include "app_common.h" -#include - -#include -#include -#include - -#define TAG "BtDevInfoSvc" - -typedef struct { - uint16_t service_handle; - uint16_t man_name_char_handle; - uint16_t serial_num_char_handle; - uint16_t firmware_rev_char_handle; - uint16_t software_rev_char_handle; - uint16_t rpc_version_char_handle; - FuriString* version_string; - char hardware_revision[4]; -} DevInfoSvc; - -static DevInfoSvc* dev_info_svc = NULL; - -static const char dev_info_man_name[] = "Flipper Devices Inc."; -static const char dev_info_serial_num[] = "1.0"; -static const char dev_info_rpc_version[] = TOSTRING(PROTOBUF_MAJOR_VERSION.PROTOBUF_MINOR_VERSION); - -static const uint8_t dev_info_rpc_version_uuid[] = - {0x33, 0xa9, 0xb5, 0x3e, 0x87, 0x5d, 0x1a, 0x8e, 0xc8, 0x47, 0x5e, 0xae, 0x6d, 0x66, 0xf6, 0x03}; - -void dev_info_svc_start() { - dev_info_svc = malloc(sizeof(DevInfoSvc)); - dev_info_svc->version_string = furi_string_alloc_printf( - "%s %s %s %s", - version_get_githash(NULL), - version_get_gitbranch(NULL), - version_get_gitbranchnum(NULL), - version_get_builddate(NULL)); - snprintf( - dev_info_svc->hardware_revision, - sizeof(dev_info_svc->hardware_revision), - "%d", - version_get_target(NULL)); - tBleStatus status; - - // Add Device Information Service - uint16_t uuid = DEVICE_INFORMATION_SERVICE_UUID; - status = aci_gatt_add_service( - UUID_TYPE_16, (Service_UUID_t*)&uuid, PRIMARY_SERVICE, 11, &dev_info_svc->service_handle); - if(status) { - FURI_LOG_E(TAG, "Failed to add Device Information Service: %d", status); - } - - // Add characteristics - uuid = MANUFACTURER_NAME_UUID; - status = aci_gatt_add_char( - dev_info_svc->service_handle, - UUID_TYPE_16, - (Char_UUID_t*)&uuid, - strlen(dev_info_man_name), - CHAR_PROP_READ, - ATTR_PERMISSION_AUTHEN_READ, - GATT_DONT_NOTIFY_EVENTS, - 10, - CHAR_VALUE_LEN_CONSTANT, - &dev_info_svc->man_name_char_handle); - if(status) { - FURI_LOG_E(TAG, "Failed to add manufacturer name char: %d", status); - } - uuid = SERIAL_NUMBER_UUID; - status = aci_gatt_add_char( - dev_info_svc->service_handle, - UUID_TYPE_16, - (Char_UUID_t*)&uuid, - strlen(dev_info_serial_num), - CHAR_PROP_READ, - ATTR_PERMISSION_AUTHEN_READ, - GATT_DONT_NOTIFY_EVENTS, - 10, - CHAR_VALUE_LEN_CONSTANT, - &dev_info_svc->serial_num_char_handle); - if(status) { - FURI_LOG_E(TAG, "Failed to add serial number char: %d", status); - } - uuid = FIRMWARE_REVISION_UUID; - status = aci_gatt_add_char( - dev_info_svc->service_handle, - UUID_TYPE_16, - (Char_UUID_t*)&uuid, - strlen(dev_info_svc->hardware_revision), - CHAR_PROP_READ, - ATTR_PERMISSION_AUTHEN_READ, - GATT_DONT_NOTIFY_EVENTS, - 10, - CHAR_VALUE_LEN_CONSTANT, - &dev_info_svc->firmware_rev_char_handle); - if(status) { - FURI_LOG_E(TAG, "Failed to add firmware revision char: %d", status); - } - uuid = SOFTWARE_REVISION_UUID; - status = aci_gatt_add_char( - dev_info_svc->service_handle, - UUID_TYPE_16, - (Char_UUID_t*)&uuid, - furi_string_size(dev_info_svc->version_string), - CHAR_PROP_READ, - ATTR_PERMISSION_AUTHEN_READ, - GATT_DONT_NOTIFY_EVENTS, - 10, - CHAR_VALUE_LEN_CONSTANT, - &dev_info_svc->software_rev_char_handle); - if(status) { - FURI_LOG_E(TAG, "Failed to add software revision char: %d", status); - } - status = aci_gatt_add_char( - dev_info_svc->service_handle, - UUID_TYPE_128, - (const Char_UUID_t*)dev_info_rpc_version_uuid, - strlen(dev_info_rpc_version), - CHAR_PROP_READ, - ATTR_PERMISSION_AUTHEN_READ, - GATT_DONT_NOTIFY_EVENTS, - 10, - CHAR_VALUE_LEN_CONSTANT, - &dev_info_svc->rpc_version_char_handle); - if(status) { - FURI_LOG_E(TAG, "Failed to add rpc version characteristic: %d", status); - } - - // Update characteristics - status = aci_gatt_update_char_value( - dev_info_svc->service_handle, - dev_info_svc->man_name_char_handle, - 0, - strlen(dev_info_man_name), - (uint8_t*)dev_info_man_name); - if(status) { - FURI_LOG_E(TAG, "Failed to update manufacturer name char: %d", status); - } - status = aci_gatt_update_char_value( - dev_info_svc->service_handle, - dev_info_svc->serial_num_char_handle, - 0, - strlen(dev_info_serial_num), - (uint8_t*)dev_info_serial_num); - if(status) { - FURI_LOG_E(TAG, "Failed to update serial number char: %d", status); - } - status = aci_gatt_update_char_value( - dev_info_svc->service_handle, - dev_info_svc->firmware_rev_char_handle, - 0, - strlen(dev_info_svc->hardware_revision), - (uint8_t*)dev_info_svc->hardware_revision); - if(status) { - FURI_LOG_E(TAG, "Failed to update firmware revision char: %d", status); - } - status = aci_gatt_update_char_value( - dev_info_svc->service_handle, - dev_info_svc->software_rev_char_handle, - 0, - furi_string_size(dev_info_svc->version_string), - (uint8_t*)furi_string_get_cstr(dev_info_svc->version_string)); - if(status) { - FURI_LOG_E(TAG, "Failed to update software revision char: %d", status); - } - status = aci_gatt_update_char_value( - dev_info_svc->service_handle, - dev_info_svc->rpc_version_char_handle, - 0, - strlen(dev_info_rpc_version), - (uint8_t*)dev_info_rpc_version); - if(status) { - FURI_LOG_E(TAG, "Failed to update rpc version char: %d", status); - } -} - -void dev_info_svc_stop() { - tBleStatus status; - if(dev_info_svc) { - furi_string_free(dev_info_svc->version_string); - // Delete service characteristics - status = - aci_gatt_del_char(dev_info_svc->service_handle, dev_info_svc->man_name_char_handle); - if(status) { - FURI_LOG_E(TAG, "Failed to delete manufacturer name char: %d", status); - } - status = - aci_gatt_del_char(dev_info_svc->service_handle, dev_info_svc->serial_num_char_handle); - if(status) { - FURI_LOG_E(TAG, "Failed to delete serial number char: %d", status); - } - status = aci_gatt_del_char( - dev_info_svc->service_handle, dev_info_svc->firmware_rev_char_handle); - if(status) { - FURI_LOG_E(TAG, "Failed to delete firmware revision char: %d", status); - } - status = aci_gatt_del_char( - dev_info_svc->service_handle, dev_info_svc->software_rev_char_handle); - if(status) { - FURI_LOG_E(TAG, "Failed to delete software revision char: %d", status); - } - status = - aci_gatt_del_char(dev_info_svc->service_handle, dev_info_svc->rpc_version_char_handle); - if(status) { - FURI_LOG_E(TAG, "Failed to delete rpc version char: %d", status); - } - // Delete service - status = aci_gatt_del_service(dev_info_svc->service_handle); - if(status) { - FURI_LOG_E(TAG, "Failed to delete device info service: %d", status); - } - free(dev_info_svc); - dev_info_svc = NULL; - } -} - -bool dev_info_svc_is_started() { - return dev_info_svc != NULL; -} diff --git a/firmware/targets/f7/ble_glue/hid_service.c b/firmware/targets/f7/ble_glue/hid_service.c deleted file mode 100644 index 47d242d4dff..00000000000 --- a/firmware/targets/f7/ble_glue/hid_service.c +++ /dev/null @@ -1,332 +0,0 @@ -#include "hid_service.h" -#include "app_common.h" -#include - -#include - -#define TAG "BtHid" - -typedef struct { - uint16_t svc_handle; - uint16_t protocol_mode_char_handle; - uint16_t report_char_handle[HID_SVC_REPORT_COUNT]; - uint16_t report_ref_desc_handle[HID_SVC_REPORT_COUNT]; - uint16_t report_map_char_handle; - uint16_t info_char_handle; - uint16_t ctrl_point_char_handle; -} HIDSvc; - -static HIDSvc* hid_svc = NULL; - -static SVCCTL_EvtAckStatus_t hid_svc_event_handler(void* event) { - SVCCTL_EvtAckStatus_t ret = SVCCTL_EvtNotAck; - hci_event_pckt* event_pckt = (hci_event_pckt*)(((hci_uart_pckt*)event)->data); - evt_blecore_aci* blecore_evt = (evt_blecore_aci*)event_pckt->data; - // aci_gatt_attribute_modified_event_rp0* attribute_modified; - if(event_pckt->evt == HCI_VENDOR_SPECIFIC_DEBUG_EVT_CODE) { - if(blecore_evt->ecode == ACI_GATT_ATTRIBUTE_MODIFIED_VSEVT_CODE) { - // Process modification events - ret = SVCCTL_EvtAckFlowEnable; - } else if(blecore_evt->ecode == ACI_GATT_SERVER_CONFIRMATION_VSEVT_CODE) { - // Process notification confirmation - ret = SVCCTL_EvtAckFlowEnable; - } - } - return ret; -} - -void hid_svc_start() { - tBleStatus status; - hid_svc = malloc(sizeof(HIDSvc)); - Service_UUID_t svc_uuid = {}; - Char_Desc_Uuid_t desc_uuid = {}; - Char_UUID_t char_uuid = {}; - - // Register event handler - SVCCTL_RegisterSvcHandler(hid_svc_event_handler); - // Add service - svc_uuid.Service_UUID_16 = HUMAN_INTERFACE_DEVICE_SERVICE_UUID; - /** - * Add Human Interface Device Service - */ - status = aci_gatt_add_service( - UUID_TYPE_16, - &svc_uuid, - PRIMARY_SERVICE, - 2 + /* protocol mode */ - (4 * HID_SVC_INPUT_REPORT_COUNT) + (3 * HID_SVC_OUTPUT_REPORT_COUNT) + - (3 * HID_SVC_FEATURE_REPORT_COUNT) + 1 + 2 + 2 + - 2, /* Service + Report Map + HID Information + HID Control Point */ - &hid_svc->svc_handle); - if(status) { - FURI_LOG_E(TAG, "Failed to add HID service: %d", status); - } - // Add Protocol mode characteristics - char_uuid.Char_UUID_16 = PROTOCOL_MODE_CHAR_UUID; - status = aci_gatt_add_char( - hid_svc->svc_handle, - UUID_TYPE_16, - &char_uuid, - 1, - CHAR_PROP_READ | CHAR_PROP_WRITE_WITHOUT_RESP, - ATTR_PERMISSION_NONE, - GATT_NOTIFY_ATTRIBUTE_WRITE, - 10, - CHAR_VALUE_LEN_CONSTANT, - &hid_svc->protocol_mode_char_handle); - if(status) { - FURI_LOG_E(TAG, "Failed to add protocol mode characteristic: %d", status); - } - // Update Protocol mode characteristic - uint8_t protocol_mode = 1; - status = aci_gatt_update_char_value( - hid_svc->svc_handle, hid_svc->protocol_mode_char_handle, 0, 1, &protocol_mode); - if(status) { - FURI_LOG_E(TAG, "Failed to update protocol mode characteristic: %d", status); - } - -#if(HID_SVC_REPORT_COUNT != 0) - for(uint8_t i = 0; i < HID_SVC_REPORT_COUNT; i++) { - if(i < HID_SVC_INPUT_REPORT_COUNT) { //-V547 - uint8_t buf[2] = {i + 1, 1}; // 1 input - char_uuid.Char_UUID_16 = REPORT_CHAR_UUID; - status = aci_gatt_add_char( - hid_svc->svc_handle, - UUID_TYPE_16, - &char_uuid, - HID_SVC_REPORT_MAX_LEN, - CHAR_PROP_READ | CHAR_PROP_NOTIFY, - ATTR_PERMISSION_NONE, - GATT_DONT_NOTIFY_EVENTS, - 10, - CHAR_VALUE_LEN_VARIABLE, - &(hid_svc->report_char_handle[i])); - if(status) { - FURI_LOG_E(TAG, "Failed to add report characteristic: %d", status); - } - - desc_uuid.Char_UUID_16 = REPORT_REFERENCE_DESCRIPTOR_UUID; - status = aci_gatt_add_char_desc( - hid_svc->svc_handle, - hid_svc->report_char_handle[i], - UUID_TYPE_16, - &desc_uuid, - HID_SVC_REPORT_REF_LEN, - HID_SVC_REPORT_REF_LEN, - buf, - ATTR_PERMISSION_NONE, - ATTR_ACCESS_READ_WRITE, - GATT_DONT_NOTIFY_EVENTS, - MIN_ENCRY_KEY_SIZE, - CHAR_VALUE_LEN_CONSTANT, - &(hid_svc->report_ref_desc_handle[i])); - if(status) { - FURI_LOG_E(TAG, "Failed to add report reference descriptor: %d", status); - } - } else if((i - HID_SVC_INPUT_REPORT_COUNT) < HID_SVC_OUTPUT_REPORT_COUNT) { - uint8_t buf[2] = {i + 1, 2}; // 2 output - char_uuid.Char_UUID_16 = REPORT_CHAR_UUID; - status = aci_gatt_add_char( - hid_svc->svc_handle, - UUID_TYPE_16, - &char_uuid, - HID_SVC_REPORT_MAX_LEN, - CHAR_PROP_READ | CHAR_PROP_NOTIFY, - ATTR_PERMISSION_NONE, - GATT_DONT_NOTIFY_EVENTS, - 10, - CHAR_VALUE_LEN_VARIABLE, - &(hid_svc->report_char_handle[i])); - if(status) { - FURI_LOG_E(TAG, "Failed to add report characteristic: %d", status); - } - - desc_uuid.Char_UUID_16 = REPORT_REFERENCE_DESCRIPTOR_UUID; - status = aci_gatt_add_char_desc( - hid_svc->svc_handle, - hid_svc->report_char_handle[i], - UUID_TYPE_16, - &desc_uuid, - HID_SVC_REPORT_REF_LEN, - HID_SVC_REPORT_REF_LEN, - buf, - ATTR_PERMISSION_NONE, - ATTR_ACCESS_READ_WRITE, - GATT_DONT_NOTIFY_EVENTS, - MIN_ENCRY_KEY_SIZE, - CHAR_VALUE_LEN_CONSTANT, - &(hid_svc->report_ref_desc_handle[i])); - if(status) { - FURI_LOG_E(TAG, "Failed to add report reference descriptor: %d", status); - } - } else { - uint8_t buf[2] = {i + 1, 3}; // 3 feature - char_uuid.Char_UUID_16 = REPORT_CHAR_UUID; - status = aci_gatt_add_char( - hid_svc->svc_handle, - UUID_TYPE_16, - &char_uuid, - HID_SVC_REPORT_MAX_LEN, - CHAR_PROP_READ | CHAR_PROP_NOTIFY, - ATTR_PERMISSION_NONE, - GATT_DONT_NOTIFY_EVENTS, - 10, - CHAR_VALUE_LEN_VARIABLE, - &(hid_svc->report_char_handle[i])); - if(status) { - FURI_LOG_E(TAG, "Failed to add report characteristic: %d", status); - } - - desc_uuid.Char_UUID_16 = REPORT_REFERENCE_DESCRIPTOR_UUID; - status = aci_gatt_add_char_desc( - hid_svc->svc_handle, - hid_svc->report_char_handle[i], - UUID_TYPE_16, - &desc_uuid, - HID_SVC_REPORT_REF_LEN, - HID_SVC_REPORT_REF_LEN, - buf, - ATTR_PERMISSION_NONE, - ATTR_ACCESS_READ_WRITE, - GATT_DONT_NOTIFY_EVENTS, - MIN_ENCRY_KEY_SIZE, - CHAR_VALUE_LEN_CONSTANT, - &(hid_svc->report_ref_desc_handle[i])); - if(status) { - FURI_LOG_E(TAG, "Failed to add report reference descriptor: %d", status); - } - } - } -#endif - // Add Report Map characteristic - char_uuid.Char_UUID_16 = REPORT_MAP_CHAR_UUID; - status = aci_gatt_add_char( - hid_svc->svc_handle, - UUID_TYPE_16, - &char_uuid, - HID_SVC_REPORT_MAP_MAX_LEN, - CHAR_PROP_READ, - ATTR_PERMISSION_NONE, - GATT_DONT_NOTIFY_EVENTS, - 10, - CHAR_VALUE_LEN_VARIABLE, - &hid_svc->report_map_char_handle); - if(status) { - FURI_LOG_E(TAG, "Failed to add report map characteristic: %d", status); - } - - // Add Information characteristic - char_uuid.Char_UUID_16 = HID_INFORMATION_CHAR_UUID; - status = aci_gatt_add_char( - hid_svc->svc_handle, - UUID_TYPE_16, - &char_uuid, - HID_SVC_INFO_LEN, - CHAR_PROP_READ, - ATTR_PERMISSION_NONE, - GATT_DONT_NOTIFY_EVENTS, - 10, - CHAR_VALUE_LEN_CONSTANT, - &hid_svc->info_char_handle); - if(status) { - FURI_LOG_E(TAG, "Failed to add information characteristic: %d", status); - } - // Add Control Point characteristic - char_uuid.Char_UUID_16 = HID_CONTROL_POINT_CHAR_UUID; - status = aci_gatt_add_char( - hid_svc->svc_handle, - UUID_TYPE_16, - &char_uuid, - HID_SVC_CONTROL_POINT_LEN, - CHAR_PROP_WRITE_WITHOUT_RESP, - ATTR_PERMISSION_NONE, - GATT_NOTIFY_ATTRIBUTE_WRITE, - 10, - CHAR_VALUE_LEN_CONSTANT, - &hid_svc->ctrl_point_char_handle); - if(status) { - FURI_LOG_E(TAG, "Failed to add control point characteristic: %d", status); - } -} - -bool hid_svc_update_report_map(const uint8_t* data, uint16_t len) { - furi_assert(data); - furi_assert(hid_svc); - - tBleStatus status = aci_gatt_update_char_value( - hid_svc->svc_handle, hid_svc->report_map_char_handle, 0, len, data); - if(status) { - FURI_LOG_E(TAG, "Failed updating report map characteristic: %d", status); - return false; - } - return true; -} - -bool hid_svc_update_input_report(uint8_t input_report_num, uint8_t* data, uint16_t len) { - furi_assert(data); - furi_assert(hid_svc); - - tBleStatus status = aci_gatt_update_char_value( - hid_svc->svc_handle, hid_svc->report_char_handle[input_report_num], 0, len, data); - if(status) { - FURI_LOG_E(TAG, "Failed updating report characteristic: %d", status); - return false; - } - return true; -} - -bool hid_svc_update_info(uint8_t* data, uint16_t len) { - furi_assert(data); - furi_assert(hid_svc); - - tBleStatus status = - aci_gatt_update_char_value(hid_svc->svc_handle, hid_svc->info_char_handle, 0, len, data); - if(status) { - FURI_LOG_E(TAG, "Failed updating info characteristic: %d", status); - return false; - } - return true; -} - -bool hid_svc_is_started() { - return hid_svc != NULL; -} - -void hid_svc_stop() { - tBleStatus status; - if(hid_svc) { - // Delete characteristics - status = aci_gatt_del_char(hid_svc->svc_handle, hid_svc->report_map_char_handle); - if(status) { - FURI_LOG_E(TAG, "Failed to delete Report Map characteristic: %d", status); - } -#if(HID_SVC_INPUT_REPORT_COUNT != 0) - for(uint8_t i = 0; i < HID_SVC_REPORT_COUNT; i++) { - status = aci_gatt_del_char(hid_svc->svc_handle, hid_svc->report_char_handle[i]); - if(status) { - FURI_LOG_E(TAG, "Failed to delete Report characteristic: %d", status); - } - } -#endif - status = aci_gatt_del_char(hid_svc->svc_handle, hid_svc->protocol_mode_char_handle); - if(status) { - FURI_LOG_E(TAG, "Failed to delete Protocol Mode characteristic: %d", status); - } - status = aci_gatt_del_char(hid_svc->svc_handle, hid_svc->info_char_handle); - if(status) { - FURI_LOG_E(TAG, "Failed to delete Information characteristic: %d", status); - } - status = aci_gatt_del_char(hid_svc->svc_handle, hid_svc->ctrl_point_char_handle); - if(status) { - FURI_LOG_E(TAG, "Failed to delete Control Point characteristic: %d", status); - } - // Delete service - status = aci_gatt_del_service(hid_svc->svc_handle); - if(status) { - FURI_LOG_E(TAG, "Failed to delete HID service: %d", status); - } - // Delete buffer size mutex - free(hid_svc); - hid_svc = NULL; - } -} diff --git a/firmware/targets/f7/ble_glue/battery_service.c b/firmware/targets/f7/ble_glue/services/battery_service.c similarity index 53% rename from firmware/targets/f7/ble_glue/battery_service.c rename to firmware/targets/f7/ble_glue/services/battery_service.c index 8c371efadba..63f736b3b7a 100644 --- a/firmware/targets/f7/ble_glue/battery_service.c +++ b/firmware/targets/f7/ble_glue/services/battery_service.c @@ -1,5 +1,7 @@ #include "battery_service.h" #include "app_common.h" +#include "gatt_char.h" + #include #include @@ -7,12 +9,6 @@ #define TAG "BtBatterySvc" -typedef struct { - uint16_t svc_handle; - uint16_t battery_level_char_handle; - uint16_t power_state_char_handle; -} BatterySvc; - enum { // Common states BatterySvcPowerStateUnknown = 0b00, @@ -40,13 +36,44 @@ typedef struct { _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 battery_level_char_uuid = BATTERY_LEVEL_CHAR_UUID; -static const uint16_t power_state_char_uuid = BATTERY_POWER_STATE; + +typedef enum { + BatterySvcGattCharacteristicBatteryLevel = 0, + BatterySvcGattCharacteristicPowerState, + BatterySvcGattCharacteristicCount, +} BatterySvcGattCharacteristicId; + +static const FlipperGattCharacteristicParams battery_svc_chars[BatterySvcGattCharacteristicCount] = + {[BatterySvcGattCharacteristicBatteryLevel] = + {.name = "Battery Level", + .data_prop_type = FlipperGattCharacteristicDataFixed, + .data.fixed.length = 1, + .uuid.Char_UUID_16 = BATTERY_LEVEL_CHAR_UUID, + .uuid_type = UUID_TYPE_16, + .char_properties = CHAR_PROP_READ | CHAR_PROP_NOTIFY, + .security_permissions = ATTR_PERMISSION_AUTHEN_READ, + .gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS, + .is_variable = CHAR_VALUE_LEN_CONSTANT}, + [BatterySvcGattCharacteristicPowerState] = { + .name = "Power State", + .data_prop_type = FlipperGattCharacteristicDataFixed, + .data.fixed.length = 1, + .uuid.Char_UUID_16 = BATTERY_POWER_STATE, + .uuid_type = UUID_TYPE_16, + .char_properties = CHAR_PROP_READ | CHAR_PROP_NOTIFY, + .security_permissions = ATTR_PERMISSION_AUTHEN_READ, + .gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS, + .is_variable = CHAR_VALUE_LEN_CONSTANT}}; + +typedef struct { + uint16_t svc_handle; + FlipperGattCharacteristicInstance chars[BatterySvcGattCharacteristicCount]; +} BatterySvc; + +static BatterySvc* battery_svc = NULL; void battery_svc_start() { battery_svc = malloc(sizeof(BatterySvc)); @@ -58,53 +85,19 @@ void battery_svc_start() { if(status) { FURI_LOG_E(TAG, "Failed to add Battery service: %d", status); } - // Add Battery level characteristic - status = aci_gatt_add_char( - battery_svc->svc_handle, - UUID_TYPE_16, - (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->battery_level_char_handle); - if(status) { - FURI_LOG_E(TAG, "Failed to add Battery level characteristic: %d", status); + for(size_t i = 0; i < BatterySvcGattCharacteristicCount; i++) { + flipper_gatt_characteristic_init( + battery_svc->svc_handle, &battery_svc_chars[i], &battery_svc->chars[i]); } - // 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->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); + for(size_t i = 0; i < BatterySvcGattCharacteristicCount; i++) { + flipper_gatt_characteristic_delete(battery_svc->svc_handle, &battery_svc->chars[i]); } // Delete Battery service status = aci_gatt_del_service(battery_svc->svc_handle); @@ -126,13 +119,10 @@ bool battery_svc_update_level(uint8_t battery_charge) { return false; } // 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->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; + return flipper_gatt_characteristic_update( + battery_svc->svc_handle, + &battery_svc->chars[BatterySvcGattCharacteristicBatteryLevel], + &battery_charge); } bool battery_svc_update_power_state() { @@ -152,15 +142,9 @@ bool battery_svc_update_power_state() { power_state.charging = BatterySvcPowerStateNotCharging; power_state.discharging = BatterySvcPowerStateDischarging; } - FURI_LOG_D(TAG, "Updating power state characteristic"); - tBleStatus result = aci_gatt_update_char_value( + + return flipper_gatt_characteristic_update( 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; + &battery_svc->chars[BatterySvcGattCharacteristicPowerState], + &power_state); } diff --git a/firmware/targets/f7/ble_glue/battery_service.h b/firmware/targets/f7/ble_glue/services/battery_service.h similarity index 100% rename from firmware/targets/f7/ble_glue/battery_service.h rename to firmware/targets/f7/ble_glue/services/battery_service.h diff --git a/firmware/targets/f7/ble_glue/services/dev_info_service.c b/firmware/targets/f7/ble_glue/services/dev_info_service.c new file mode 100644 index 00000000000..cc95bb2fc1c --- /dev/null +++ b/firmware/targets/f7/ble_glue/services/dev_info_service.c @@ -0,0 +1,176 @@ +#include "dev_info_service.h" +#include "app_common.h" +#include "gatt_char.h" +#include + +#include +#include +#include + +#include "dev_info_service_uuid.inc" + +#define TAG "BtDevInfoSvc" + +typedef enum { + DevInfoSvcGattCharacteristicMfgName = 0, + DevInfoSvcGattCharacteristicSerial, + DevInfoSvcGattCharacteristicFirmwareRev, + DevInfoSvcGattCharacteristicSoftwareRev, + DevInfoSvcGattCharacteristicRpcVersion, + DevInfoSvcGattCharacteristicCount, +} DevInfoSvcGattCharacteristicId; + +#define DEVICE_INFO_HARDWARE_REV_SIZE 4 +typedef struct { + uint16_t service_handle; + FlipperGattCharacteristicInstance characteristics[DevInfoSvcGattCharacteristicCount]; + FuriString* version_string; + char hardware_revision[DEVICE_INFO_HARDWARE_REV_SIZE]; +} DevInfoSvc; + +static DevInfoSvc* dev_info_svc = NULL; + +static const char dev_info_man_name[] = "Flipper Devices Inc."; +static const char dev_info_serial_num[] = "1.0"; +static const char dev_info_rpc_version[] = TOSTRING(PROTOBUF_MAJOR_VERSION.PROTOBUF_MINOR_VERSION); + +static bool dev_info_char_firmware_rev_callback( + const void* context, + const uint8_t** data, + uint16_t* data_len) { + const DevInfoSvc* dev_info_svc = *(DevInfoSvc**)context; + *data_len = sizeof(dev_info_svc->hardware_revision); + if(data) { + *data = (const uint8_t*)&dev_info_svc->hardware_revision; + } + return false; +} + +static bool dev_info_char_software_rev_callback( + const void* context, + const uint8_t** data, + uint16_t* data_len) { + const DevInfoSvc* dev_info_svc = *(DevInfoSvc**)context; + *data_len = furi_string_size(dev_info_svc->version_string); + if(data) { + *data = (const uint8_t*)furi_string_get_cstr(dev_info_svc->version_string); + } + return false; +} + +static const FlipperGattCharacteristicParams dev_info_svc_chars[DevInfoSvcGattCharacteristicCount] = + {[DevInfoSvcGattCharacteristicMfgName] = + {.name = "Manufacturer Name", + .data_prop_type = FlipperGattCharacteristicDataFixed, + .data.fixed.length = sizeof(dev_info_man_name) - 1, + .data.fixed.ptr = (const uint8_t*)&dev_info_man_name, + .uuid.Char_UUID_16 = MANUFACTURER_NAME_UUID, + .uuid_type = UUID_TYPE_16, + .char_properties = CHAR_PROP_READ, + .security_permissions = ATTR_PERMISSION_AUTHEN_READ, + .gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS, + .is_variable = CHAR_VALUE_LEN_CONSTANT}, + [DevInfoSvcGattCharacteristicSerial] = + {.name = "Serial Number", + .data_prop_type = FlipperGattCharacteristicDataFixed, + .data.fixed.length = sizeof(dev_info_serial_num) - 1, + .data.fixed.ptr = (const uint8_t*)&dev_info_serial_num, + .uuid.Char_UUID_16 = SERIAL_NUMBER_UUID, + .uuid_type = UUID_TYPE_16, + .char_properties = CHAR_PROP_READ, + .security_permissions = ATTR_PERMISSION_AUTHEN_READ, + .gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS, + .is_variable = CHAR_VALUE_LEN_CONSTANT}, + [DevInfoSvcGattCharacteristicFirmwareRev] = + {.name = "Firmware Revision", + .data_prop_type = FlipperGattCharacteristicDataCallback, + .data.callback.context = &dev_info_svc, + .data.callback.fn = dev_info_char_firmware_rev_callback, + .uuid.Char_UUID_16 = FIRMWARE_REVISION_UUID, + .uuid_type = UUID_TYPE_16, + .char_properties = CHAR_PROP_READ, + .security_permissions = ATTR_PERMISSION_AUTHEN_READ, + .gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS, + .is_variable = CHAR_VALUE_LEN_CONSTANT}, + [DevInfoSvcGattCharacteristicSoftwareRev] = + {.name = "Software Revision", + .data_prop_type = FlipperGattCharacteristicDataCallback, + .data.callback.context = &dev_info_svc, + .data.callback.fn = dev_info_char_software_rev_callback, + .uuid.Char_UUID_16 = SOFTWARE_REVISION_UUID, + .uuid_type = UUID_TYPE_16, + .char_properties = CHAR_PROP_READ, + .security_permissions = ATTR_PERMISSION_AUTHEN_READ, + .gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS, + .is_variable = CHAR_VALUE_LEN_CONSTANT}, + [DevInfoSvcGattCharacteristicRpcVersion] = { + .name = "RPC Version", + .data_prop_type = FlipperGattCharacteristicDataFixed, + .data.fixed.length = sizeof(dev_info_rpc_version) - 1, + .data.fixed.ptr = (const uint8_t*)&dev_info_rpc_version, + .uuid.Char_UUID_128 = DEV_INVO_RPC_VERSION_UID, + .uuid_type = UUID_TYPE_128, + .char_properties = CHAR_PROP_READ, + .security_permissions = ATTR_PERMISSION_AUTHEN_READ, + .gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS, + .is_variable = CHAR_VALUE_LEN_CONSTANT}}; + +void dev_info_svc_start() { + dev_info_svc = malloc(sizeof(DevInfoSvc)); + dev_info_svc->version_string = furi_string_alloc_printf( + "%s %s %s %s", + version_get_githash(NULL), + version_get_gitbranch(NULL), + version_get_gitbranchnum(NULL), + version_get_builddate(NULL)); + snprintf( + dev_info_svc->hardware_revision, + sizeof(dev_info_svc->hardware_revision), + "%d", + version_get_target(NULL)); + tBleStatus status; + + // Add Device Information Service + uint16_t uuid = DEVICE_INFORMATION_SERVICE_UUID; + status = aci_gatt_add_service( + UUID_TYPE_16, + (Service_UUID_t*)&uuid, + PRIMARY_SERVICE, + 1 + 2 * DevInfoSvcGattCharacteristicCount, + &dev_info_svc->service_handle); + if(status) { + FURI_LOG_E(TAG, "Failed to add Device Information Service: %d", status); + } + + for(size_t i = 0; i < DevInfoSvcGattCharacteristicCount; i++) { + flipper_gatt_characteristic_init( + dev_info_svc->service_handle, + &dev_info_svc_chars[i], + &dev_info_svc->characteristics[i]); + flipper_gatt_characteristic_update( + dev_info_svc->service_handle, &dev_info_svc->characteristics[i], NULL); + } +} + +void dev_info_svc_stop() { + tBleStatus status; + if(dev_info_svc) { + furi_string_free(dev_info_svc->version_string); + // Delete service characteristics + for(size_t i = 0; i < DevInfoSvcGattCharacteristicCount; i++) { + flipper_gatt_characteristic_delete( + dev_info_svc->service_handle, &dev_info_svc->characteristics[i]); + } + // Delete service + status = aci_gatt_del_service(dev_info_svc->service_handle); + if(status) { + FURI_LOG_E(TAG, "Failed to delete device info service: %d", status); + } + free(dev_info_svc); + dev_info_svc = NULL; + } +} + +bool dev_info_svc_is_started() { + return dev_info_svc != NULL; +} diff --git a/firmware/targets/f7/ble_glue/dev_info_service.h b/firmware/targets/f7/ble_glue/services/dev_info_service.h similarity index 100% rename from firmware/targets/f7/ble_glue/dev_info_service.h rename to firmware/targets/f7/ble_glue/services/dev_info_service.h diff --git a/firmware/targets/f7/ble_glue/services/dev_info_service_uuid.inc b/firmware/targets/f7/ble_glue/services/dev_info_service_uuid.inc new file mode 100644 index 00000000000..ad520f62e5e --- /dev/null +++ b/firmware/targets/f7/ble_glue/services/dev_info_service_uuid.inc @@ -0,0 +1,3 @@ +#define DEV_INVO_RPC_VERSION_UID \ + { 0x33, 0xa9, 0xb5, 0x3e, 0x87, 0x5d, 0x1a, 0x8e, 0xc8, 0x47, 0x5e, 0xae, 0x6d, 0x66, 0xf6, 0x03 } + diff --git a/firmware/targets/f7/ble_glue/services/gatt_char.c b/firmware/targets/f7/ble_glue/services/gatt_char.c new file mode 100644 index 00000000000..9b6a44f61b4 --- /dev/null +++ b/firmware/targets/f7/ble_glue/services/gatt_char.c @@ -0,0 +1,123 @@ +#include "gatt_char.h" + +#include + +#define TAG "GattChar" + +#define GATT_MIN_READ_KEY_SIZE (10) + +void flipper_gatt_characteristic_init( + uint16_t svc_handle, + const FlipperGattCharacteristicParams* char_descriptor, + FlipperGattCharacteristicInstance* char_instance) { + furi_assert(char_descriptor); + furi_assert(char_instance); + + // Copy the descriptor to the instance, since it may point to stack memory + // TODO: only copy if really comes from stack + char_instance->characteristic = malloc(sizeof(FlipperGattCharacteristicParams)); + memcpy( + (void*)char_instance->characteristic, + char_descriptor, + sizeof(FlipperGattCharacteristicParams)); + + uint16_t char_data_size = 0; + if(char_descriptor->data_prop_type == FlipperGattCharacteristicDataFixed) { + char_data_size = char_descriptor->data.fixed.length; + } else if(char_descriptor->data_prop_type == FlipperGattCharacteristicDataCallback) { + char_descriptor->data.callback.fn( + char_descriptor->data.callback.context, NULL, &char_data_size); + } + + tBleStatus status = aci_gatt_add_char( + svc_handle, + char_descriptor->uuid_type, + &char_descriptor->uuid, + char_data_size, + char_descriptor->char_properties, + char_descriptor->security_permissions, + char_descriptor->gatt_evt_mask, + GATT_MIN_READ_KEY_SIZE, + char_descriptor->is_variable, + &char_instance->handle); + if(status) { + FURI_LOG_E(TAG, "Failed to add %s char: %d", char_descriptor->name, status); + } + + char_instance->descriptor_handle = 0; + if((status == 0) && char_descriptor->descriptor_params) { + uint8_t const* char_data = NULL; + const FlipperGattCharacteristicDescriptorParams* char_data_descriptor = + char_descriptor->descriptor_params; + bool release_data = char_data_descriptor->data_callback.fn( + char_data_descriptor->data_callback.context, &char_data, &char_data_size); + + status = aci_gatt_add_char_desc( + svc_handle, + char_instance->handle, + char_data_descriptor->uuid_type, + &char_data_descriptor->uuid, + char_data_descriptor->max_length, + char_data_size, + char_data, + char_data_descriptor->security_permissions, + char_data_descriptor->access_permissions, + char_data_descriptor->gatt_evt_mask, + GATT_MIN_READ_KEY_SIZE, + char_data_descriptor->is_variable, + &char_instance->descriptor_handle); + if(status) { + FURI_LOG_E(TAG, "Failed to add %s char descriptor: %d", char_descriptor->name, status); + } + if(release_data) { + free((void*)char_data); + } + } +} + +void flipper_gatt_characteristic_delete( + uint16_t svc_handle, + FlipperGattCharacteristicInstance* char_instance) { + tBleStatus status = aci_gatt_del_char(svc_handle, char_instance->handle); + if(status) { + FURI_LOG_E( + TAG, "Failed to delete %s char: %d", char_instance->characteristic->name, status); + } + free((void*)char_instance->characteristic); +} + +bool flipper_gatt_characteristic_update( + uint16_t svc_handle, + FlipperGattCharacteristicInstance* char_instance, + const void* source) { + furi_assert(char_instance); + const FlipperGattCharacteristicParams* char_descriptor = char_instance->characteristic; + FURI_LOG_D(TAG, "Updating %s char", char_descriptor->name); + + const uint8_t* char_data = NULL; + uint16_t char_data_size = 0; + bool release_data = false; + if(char_descriptor->data_prop_type == FlipperGattCharacteristicDataFixed) { + char_data = char_descriptor->data.fixed.ptr; + if(source) { + char_data = (uint8_t*)source; + } + char_data_size = char_descriptor->data.fixed.length; + } else if(char_descriptor->data_prop_type == FlipperGattCharacteristicDataCallback) { + const void* context = char_descriptor->data.callback.context; + if(source) { + context = source; + } + release_data = char_descriptor->data.callback.fn(context, &char_data, &char_data_size); + } + + tBleStatus result = aci_gatt_update_char_value( + svc_handle, char_instance->handle, 0, char_data_size, char_data); + if(result) { + FURI_LOG_E(TAG, "Failed updating %s characteristic: %d", char_descriptor->name, result); + } + if(release_data) { + free((void*)char_data); + } + return result != BLE_STATUS_SUCCESS; +} \ No newline at end of file diff --git a/firmware/targets/f7/ble_glue/services/gatt_char.h b/firmware/targets/f7/ble_glue/services/gatt_char.h new file mode 100644 index 00000000000..959ab67a49f --- /dev/null +++ b/firmware/targets/f7/ble_glue/services/gatt_char.h @@ -0,0 +1,96 @@ +#pragma once + +#include +#include +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// Callback signature for getting characteristic data +// Is called when characteristic is created to get max data length. Data ptr is NULL in this case +// The result is passed to aci_gatt_add_char as "Char_Value_Length" +// For updates, called with a context - see flipper_gatt_characteristic_update +// Returns true if *data ownership is transferred to the caller and will be freed +typedef bool (*cbFlipperGattCharacteristicData)( + const void* context, + const uint8_t** data, + uint16_t* data_len); + +typedef enum { + FlipperGattCharacteristicDataFixed, + FlipperGattCharacteristicDataCallback, +} FlipperGattCharacteristicDataType; + +typedef struct { + Char_Desc_Uuid_t uuid; + struct { + cbFlipperGattCharacteristicData fn; + const void* context; + } data_callback; + uint8_t uuid_type; + uint8_t max_length; + uint8_t security_permissions; + uint8_t access_permissions; + uint8_t gatt_evt_mask; + uint8_t is_variable; +} FlipperGattCharacteristicDescriptorParams; + +typedef struct { + const char* name; + FlipperGattCharacteristicDescriptorParams* descriptor_params; + union { + struct { + const uint8_t* ptr; + uint16_t length; + } fixed; + struct { + cbFlipperGattCharacteristicData fn; + const void* context; + } callback; + } data; + Char_UUID_t uuid; + // Some packed bitfields to save space + FlipperGattCharacteristicDataType data_prop_type : 2; + uint8_t is_variable : 2; + uint8_t uuid_type : 2; + uint8_t char_properties; + uint8_t security_permissions; + uint8_t gatt_evt_mask; +} FlipperGattCharacteristicParams; + +_Static_assert( + sizeof(FlipperGattCharacteristicParams) == 36, + "FlipperGattCharacteristicParams size must be 36 bytes"); + +typedef struct { + const FlipperGattCharacteristicParams* characteristic; + uint16_t handle; + uint16_t descriptor_handle; +} FlipperGattCharacteristicInstance; + +// Initialize a characteristic instance; copies the characteristic descriptor into the instance +void flipper_gatt_characteristic_init( + uint16_t svc_handle, + const FlipperGattCharacteristicParams* char_descriptor, + FlipperGattCharacteristicInstance* char_instance); + +// Delete a characteristic instance; frees the copied characteristic descriptor from the instance +void flipper_gatt_characteristic_delete( + uint16_t svc_handle, + FlipperGattCharacteristicInstance* char_instance); + +// Update a characteristic instance; if source==NULL, uses the data from the characteristic +// - For fixed data, fixed.ptr is used as the source if source==NULL +// - For callback-based data, collback.context is passed as the context if source==NULL +bool flipper_gatt_characteristic_update( + uint16_t svc_handle, + FlipperGattCharacteristicInstance* char_instance, + const void* source); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/firmware/targets/f7/ble_glue/services/hid_service.c b/firmware/targets/f7/ble_glue/services/hid_service.c new file mode 100644 index 00000000000..cde26b267ec --- /dev/null +++ b/firmware/targets/f7/ble_glue/services/hid_service.c @@ -0,0 +1,293 @@ +#include "hid_service.h" +#include "app_common.h" +#include +#include "gatt_char.h" + +#include + +#define TAG "BtHid" + +typedef enum { + HidSvcGattCharacteristicProtocolMode = 0, + HidSvcGattCharacteristicReportMap, + HidSvcGattCharacteristicInfo, + HidSvcGattCharacteristicCtrlPoint, + HidSvcGattCharacteristicCount, +} HidSvcGattCharacteristicId; + +typedef struct { + uint8_t report_idx; + uint8_t report_type; +} HidSvcReportId; + +static_assert(sizeof(HidSvcReportId) == sizeof(uint16_t), "HidSvcReportId must be 2 bytes"); + +static bool + hid_svc_char_desc_data_callback(const void* context, const uint8_t** data, uint16_t* data_len) { + const HidSvcReportId* report_id = context; + *data_len = sizeof(HidSvcReportId); + if(data) { + *data = (const uint8_t*)report_id; + } + return false; +} + +typedef struct { + const void* data_ptr; + uint16_t data_len; +} HidSvcDataWrapper; + +static bool + hid_svc_report_data_callback(const void* context, const uint8_t** data, uint16_t* data_len) { + const HidSvcDataWrapper* report_data = context; + if(data) { + *data = report_data->data_ptr; + *data_len = report_data->data_len; + } else { + *data_len = HID_SVC_REPORT_MAP_MAX_LEN; + } + return false; +} + +static const FlipperGattCharacteristicParams hid_svc_chars[HidSvcGattCharacteristicCount] = { + [HidSvcGattCharacteristicProtocolMode] = + {.name = "Protocol Mode", + .data_prop_type = FlipperGattCharacteristicDataFixed, + .data.fixed.length = 1, + .uuid.Char_UUID_16 = PROTOCOL_MODE_CHAR_UUID, + .uuid_type = UUID_TYPE_16, + .char_properties = CHAR_PROP_READ | CHAR_PROP_WRITE_WITHOUT_RESP, + .security_permissions = ATTR_PERMISSION_NONE, + .gatt_evt_mask = GATT_NOTIFY_ATTRIBUTE_WRITE, + .is_variable = CHAR_VALUE_LEN_CONSTANT}, + [HidSvcGattCharacteristicReportMap] = + {.name = "Report Map", + .data_prop_type = FlipperGattCharacteristicDataCallback, + .data.callback.fn = hid_svc_report_data_callback, + .data.callback.context = NULL, + .uuid.Char_UUID_16 = REPORT_MAP_CHAR_UUID, + .uuid_type = UUID_TYPE_16, + .char_properties = CHAR_PROP_READ, + .security_permissions = ATTR_PERMISSION_NONE, + .gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS, + .is_variable = CHAR_VALUE_LEN_VARIABLE}, + [HidSvcGattCharacteristicInfo] = + {.name = "HID Information", + .data_prop_type = FlipperGattCharacteristicDataFixed, + .data.fixed.length = HID_SVC_INFO_LEN, + .data.fixed.ptr = NULL, + .uuid.Char_UUID_16 = HID_INFORMATION_CHAR_UUID, + .uuid_type = UUID_TYPE_16, + .char_properties = CHAR_PROP_READ, + .security_permissions = ATTR_PERMISSION_NONE, + .gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS, + .is_variable = CHAR_VALUE_LEN_CONSTANT}, + [HidSvcGattCharacteristicCtrlPoint] = + {.name = "HID Control Point", + .data_prop_type = FlipperGattCharacteristicDataFixed, + .data.fixed.length = HID_SVC_CONTROL_POINT_LEN, + .uuid.Char_UUID_16 = HID_CONTROL_POINT_CHAR_UUID, + .uuid_type = UUID_TYPE_16, + .char_properties = CHAR_PROP_WRITE_WITHOUT_RESP, + .security_permissions = ATTR_PERMISSION_NONE, + .gatt_evt_mask = GATT_NOTIFY_ATTRIBUTE_WRITE, + .is_variable = CHAR_VALUE_LEN_CONSTANT}, +}; + +static const FlipperGattCharacteristicDescriptorParams hid_svc_char_descr_template = { + .uuid_type = UUID_TYPE_16, + .uuid.Char_UUID_16 = REPORT_REFERENCE_DESCRIPTOR_UUID, + .max_length = HID_SVC_REPORT_REF_LEN, + .data_callback.fn = hid_svc_char_desc_data_callback, + .security_permissions = ATTR_PERMISSION_NONE, + .access_permissions = ATTR_ACCESS_READ_WRITE, + .gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS, + .is_variable = CHAR_VALUE_LEN_CONSTANT, +}; + +static const FlipperGattCharacteristicParams hid_svc_report_template = { + .name = "Report", + .data_prop_type = FlipperGattCharacteristicDataCallback, + .data.callback.fn = hid_svc_report_data_callback, + .data.callback.context = NULL, + .uuid.Char_UUID_16 = REPORT_CHAR_UUID, + .uuid_type = UUID_TYPE_16, + .char_properties = CHAR_PROP_READ | CHAR_PROP_NOTIFY, + .security_permissions = ATTR_PERMISSION_NONE, + .gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS, + .is_variable = CHAR_VALUE_LEN_VARIABLE, +}; + +typedef struct { + uint16_t svc_handle; + FlipperGattCharacteristicInstance chars[HidSvcGattCharacteristicCount]; + FlipperGattCharacteristicInstance input_report_chars[HID_SVC_INPUT_REPORT_COUNT]; + FlipperGattCharacteristicInstance output_report_chars[HID_SVC_OUTPUT_REPORT_COUNT]; + FlipperGattCharacteristicInstance feature_report_chars[HID_SVC_FEATURE_REPORT_COUNT]; +} HIDSvc; + +static HIDSvc* hid_svc = NULL; + +static SVCCTL_EvtAckStatus_t hid_svc_event_handler(void* event) { + SVCCTL_EvtAckStatus_t ret = SVCCTL_EvtNotAck; + hci_event_pckt* event_pckt = (hci_event_pckt*)(((hci_uart_pckt*)event)->data); + evt_blecore_aci* blecore_evt = (evt_blecore_aci*)event_pckt->data; + // aci_gatt_attribute_modified_event_rp0* attribute_modified; + if(event_pckt->evt == HCI_VENDOR_SPECIFIC_DEBUG_EVT_CODE) { + if(blecore_evt->ecode == ACI_GATT_ATTRIBUTE_MODIFIED_VSEVT_CODE) { + // Process modification events + ret = SVCCTL_EvtAckFlowEnable; + } else if(blecore_evt->ecode == ACI_GATT_SERVER_CONFIRMATION_VSEVT_CODE) { + // Process notification confirmation + ret = SVCCTL_EvtAckFlowEnable; + } + } + return ret; +} + +void hid_svc_start() { + tBleStatus status; + hid_svc = malloc(sizeof(HIDSvc)); + Service_UUID_t svc_uuid = {}; + + // Register event handler + SVCCTL_RegisterSvcHandler(hid_svc_event_handler); + // Add service + svc_uuid.Service_UUID_16 = HUMAN_INTERFACE_DEVICE_SERVICE_UUID; + /** + * Add Human Interface Device Service + */ + status = aci_gatt_add_service( + UUID_TYPE_16, + &svc_uuid, + PRIMARY_SERVICE, + 2 + /* protocol mode */ + (4 * HID_SVC_INPUT_REPORT_COUNT) + (3 * HID_SVC_OUTPUT_REPORT_COUNT) + + (3 * HID_SVC_FEATURE_REPORT_COUNT) + 1 + 2 + 2 + + 2, /* Service + Report Map + HID Information + HID Control Point */ + &hid_svc->svc_handle); + if(status) { + FURI_LOG_E(TAG, "Failed to add HID service: %d", status); + } + + for(size_t i = 0; i < HidSvcGattCharacteristicCount; i++) { + flipper_gatt_characteristic_init( + hid_svc->svc_handle, &hid_svc_chars[i], &hid_svc->chars[i]); + } + uint8_t protocol_mode = 1; + flipper_gatt_characteristic_update( + hid_svc->svc_handle, + &hid_svc->chars[HidSvcGattCharacteristicProtocolMode], + &protocol_mode); + + // reports + FlipperGattCharacteristicDescriptorParams hid_svc_char_descr; + FlipperGattCharacteristicParams report_char; + HidSvcReportId report_id; + + memcpy(&hid_svc_char_descr, &hid_svc_char_descr_template, sizeof(hid_svc_char_descr)); + memcpy(&report_char, &hid_svc_report_template, sizeof(report_char)); + + hid_svc_char_descr.data_callback.context = &report_id; + report_char.descriptor_params = &hid_svc_char_descr; + + typedef struct { + uint8_t report_type; + uint8_t report_count; + FlipperGattCharacteristicInstance* chars; + } HidSvcReportCharProps; + + HidSvcReportCharProps hid_report_chars[] = { + {0x01, HID_SVC_INPUT_REPORT_COUNT, hid_svc->input_report_chars}, + {0x02, HID_SVC_OUTPUT_REPORT_COUNT, hid_svc->output_report_chars}, + {0x03, HID_SVC_FEATURE_REPORT_COUNT, hid_svc->feature_report_chars}, + }; + + for(size_t report_type_idx = 0; report_type_idx < COUNT_OF(hid_report_chars); + report_type_idx++) { + report_id.report_type = hid_report_chars[report_type_idx].report_type; + for(size_t report_idx = 0; report_idx < hid_report_chars[report_type_idx].report_count; + report_idx++) { + report_id.report_idx = report_idx + 1; + flipper_gatt_characteristic_init( + hid_svc->svc_handle, + &report_char, + &hid_report_chars[report_type_idx].chars[report_idx]); + } + } +} + +bool hid_svc_update_report_map(const uint8_t* data, uint16_t len) { + furi_assert(data); + furi_assert(hid_svc); + + HidSvcDataWrapper report_data = { + .data_ptr = data, + .data_len = len, + }; + return flipper_gatt_characteristic_update( + hid_svc->svc_handle, &hid_svc->chars[HidSvcGattCharacteristicReportMap], &report_data); +} + +bool hid_svc_update_input_report(uint8_t input_report_num, uint8_t* data, uint16_t len) { + furi_assert(data); + furi_assert(hid_svc); + furi_assert(input_report_num < HID_SVC_INPUT_REPORT_COUNT); + + HidSvcDataWrapper report_data = { + .data_ptr = data, + .data_len = len, + }; + return flipper_gatt_characteristic_update( + hid_svc->svc_handle, &hid_svc->input_report_chars[input_report_num], &report_data); +} + +bool hid_svc_update_info(uint8_t* data) { + furi_assert(data); + furi_assert(hid_svc); + + return flipper_gatt_characteristic_update( + hid_svc->svc_handle, &hid_svc->chars[HidSvcGattCharacteristicInfo], &data); +} + +bool hid_svc_is_started() { + return hid_svc != NULL; +} + +void hid_svc_stop() { + tBleStatus status; + if(hid_svc) { + // Delete characteristics + for(size_t i = 0; i < HidSvcGattCharacteristicCount; i++) { + flipper_gatt_characteristic_delete(hid_svc->svc_handle, &hid_svc->chars[i]); + } + + typedef struct { + uint8_t report_count; + FlipperGattCharacteristicInstance* chars; + } HidSvcReportCharProps; + + HidSvcReportCharProps hid_report_chars[] = { + {HID_SVC_INPUT_REPORT_COUNT, hid_svc->input_report_chars}, + {HID_SVC_OUTPUT_REPORT_COUNT, hid_svc->output_report_chars}, + {HID_SVC_FEATURE_REPORT_COUNT, hid_svc->feature_report_chars}, + }; + + for(size_t report_type_idx = 0; report_type_idx < COUNT_OF(hid_report_chars); + report_type_idx++) { + for(size_t report_idx = 0; report_idx < hid_report_chars[report_type_idx].report_count; + report_idx++) { + flipper_gatt_characteristic_delete( + hid_svc->svc_handle, &hid_report_chars[report_type_idx].chars[report_idx]); + } + } + + // Delete service + status = aci_gatt_del_service(hid_svc->svc_handle); + if(status) { + FURI_LOG_E(TAG, "Failed to delete HID service: %d", status); + } + free(hid_svc); + hid_svc = NULL; + } +} diff --git a/firmware/targets/f7/ble_glue/hid_service.h b/firmware/targets/f7/ble_glue/services/hid_service.h similarity index 87% rename from firmware/targets/f7/ble_glue/hid_service.h rename to firmware/targets/f7/ble_glue/services/hid_service.h index 723460d496a..211adcd6c44 100644 --- a/firmware/targets/f7/ble_glue/hid_service.h +++ b/firmware/targets/f7/ble_glue/services/hid_service.h @@ -25,4 +25,5 @@ bool hid_svc_update_report_map(const uint8_t* data, uint16_t len); bool hid_svc_update_input_report(uint8_t input_report_num, uint8_t* data, uint16_t len); -bool hid_svc_update_info(uint8_t* data, uint16_t len); +// Expects data to be of length HID_SVC_INFO_LEN (4 bytes) +bool hid_svc_update_info(uint8_t* data); diff --git a/firmware/targets/f7/ble_glue/serial_service.c b/firmware/targets/f7/ble_glue/services/serial_service.c similarity index 57% rename from firmware/targets/f7/ble_glue/serial_service.c rename to firmware/targets/f7/ble_glue/services/serial_service.c index c6421dc28fe..ab009bbfcb0 100644 --- a/firmware/targets/f7/ble_glue/serial_service.c +++ b/firmware/targets/f7/ble_glue/services/serial_service.c @@ -1,17 +1,67 @@ #include "serial_service.h" #include "app_common.h" #include +#include "gatt_char.h" #include +#include "serial_service_uuid.inc" + #define TAG "BtSerialSvc" +typedef enum { + SerialSvcGattCharacteristicTx = 0, + SerialSvcGattCharacteristicRx, + SerialSvcGattCharacteristicFlowCtrl, + SerialSvcGattCharacteristicStatus, + SerialSvcGattCharacteristicCount, +} SerialSvcGattCharacteristicId; + +static const FlipperGattCharacteristicParams serial_svc_chars[SerialSvcGattCharacteristicCount] = { + [SerialSvcGattCharacteristicTx] = + {.name = "TX", + .data_prop_type = FlipperGattCharacteristicDataFixed, + .data.fixed.length = SERIAL_SVC_DATA_LEN_MAX, + .uuid.Char_UUID_128 = SERIAL_SVC_TX_CHAR_UUID, + .uuid_type = UUID_TYPE_128, + .char_properties = CHAR_PROP_READ | CHAR_PROP_INDICATE, + .security_permissions = ATTR_PERMISSION_AUTHEN_READ, + .gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS, + .is_variable = CHAR_VALUE_LEN_VARIABLE}, + [SerialSvcGattCharacteristicRx] = + {.name = "RX", + .data_prop_type = FlipperGattCharacteristicDataFixed, + .data.fixed.length = SERIAL_SVC_DATA_LEN_MAX, + .uuid.Char_UUID_128 = SERIAL_SVC_RX_CHAR_UUID, + .uuid_type = UUID_TYPE_128, + .char_properties = CHAR_PROP_WRITE_WITHOUT_RESP | CHAR_PROP_WRITE | CHAR_PROP_READ, + .security_permissions = ATTR_PERMISSION_AUTHEN_READ | ATTR_PERMISSION_AUTHEN_WRITE, + .gatt_evt_mask = GATT_NOTIFY_ATTRIBUTE_WRITE, + .is_variable = CHAR_VALUE_LEN_VARIABLE}, + [SerialSvcGattCharacteristicFlowCtrl] = + {.name = "Flow control", + .data_prop_type = FlipperGattCharacteristicDataFixed, + .data.fixed.length = sizeof(uint32_t), + .uuid.Char_UUID_128 = SERIAL_SVC_FLOW_CONTROL_UUID, + .uuid_type = UUID_TYPE_128, + .char_properties = CHAR_PROP_READ | CHAR_PROP_NOTIFY, + .security_permissions = ATTR_PERMISSION_AUTHEN_READ, + .gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS, + .is_variable = CHAR_VALUE_LEN_CONSTANT}, + [SerialSvcGattCharacteristicStatus] = { + .name = "RPC status", + .data_prop_type = FlipperGattCharacteristicDataFixed, + .data.fixed.length = sizeof(SerialServiceRpcStatus), + .uuid.Char_UUID_128 = SERIAL_SVC_RPC_STATUS_UUID, + .uuid_type = UUID_TYPE_128, + .char_properties = CHAR_PROP_READ | CHAR_PROP_WRITE | CHAR_PROP_NOTIFY, + .security_permissions = ATTR_PERMISSION_AUTHEN_READ | ATTR_PERMISSION_AUTHEN_WRITE, + .gatt_evt_mask = GATT_NOTIFY_ATTRIBUTE_WRITE, + .is_variable = CHAR_VALUE_LEN_CONSTANT}}; + typedef struct { uint16_t svc_handle; - uint16_t rx_char_handle; - uint16_t tx_char_handle; - uint16_t flow_ctrl_char_handle; - uint16_t rpc_status_char_handle; + FlipperGattCharacteristicInstance chars[SerialSvcGattCharacteristicCount]; FuriMutex* buff_size_mtx; uint32_t buff_size; uint16_t bytes_ready_to_receive; @@ -21,17 +71,6 @@ typedef struct { static SerialSvc* serial_svc = NULL; -static const uint8_t service_uuid[] = - {0x00, 0x00, 0xfe, 0x60, 0xcc, 0x7a, 0x48, 0x2a, 0x98, 0x4a, 0x7f, 0x2e, 0xd5, 0xb3, 0xe5, 0x8f}; -static const uint8_t char_tx_uuid[] = - {0x00, 0x00, 0xfe, 0x61, 0x8e, 0x22, 0x45, 0x41, 0x9d, 0x4c, 0x21, 0xed, 0xae, 0x82, 0xed, 0x19}; -static const uint8_t char_rx_uuid[] = - {0x00, 0x00, 0xfe, 0x62, 0x8e, 0x22, 0x45, 0x41, 0x9d, 0x4c, 0x21, 0xed, 0xae, 0x82, 0xed, 0x19}; -static const uint8_t flow_ctrl_uuid[] = - {0x00, 0x00, 0xfe, 0x63, 0x8e, 0x22, 0x45, 0x41, 0x9d, 0x4c, 0x21, 0xed, 0xae, 0x82, 0xed, 0x19}; -static const uint8_t rpc_status_uuid[] = - {0x00, 0x00, 0xfe, 0x64, 0x8e, 0x22, 0x45, 0x41, 0x9d, 0x4c, 0x21, 0xed, 0xae, 0x82, 0xed, 0x19}; - static SVCCTL_EvtAckStatus_t serial_svc_event_handler(void* event) { SVCCTL_EvtAckStatus_t ret = SVCCTL_EvtNotAck; hci_event_pckt* event_pckt = (hci_event_pckt*)(((hci_uart_pckt*)event)->data); @@ -40,11 +79,14 @@ static SVCCTL_EvtAckStatus_t serial_svc_event_handler(void* event) { if(event_pckt->evt == HCI_VENDOR_SPECIFIC_DEBUG_EVT_CODE) { if(blecore_evt->ecode == ACI_GATT_ATTRIBUTE_MODIFIED_VSEVT_CODE) { attribute_modified = (aci_gatt_attribute_modified_event_rp0*)blecore_evt->data; - if(attribute_modified->Attr_Handle == serial_svc->rx_char_handle + 2) { + if(attribute_modified->Attr_Handle == + serial_svc->chars[SerialSvcGattCharacteristicRx].handle + 2) { // Descriptor handle ret = SVCCTL_EvtAckFlowEnable; FURI_LOG_D(TAG, "RX descriptor event"); - } else if(attribute_modified->Attr_Handle == serial_svc->rx_char_handle + 1) { + } else if( + attribute_modified->Attr_Handle == + serial_svc->chars[SerialSvcGattCharacteristicRx].handle + 1) { FURI_LOG_D(TAG, "Received %d bytes", attribute_modified->Attr_Data_Length); if(serial_svc->callback) { furi_check( @@ -70,7 +112,9 @@ static SVCCTL_EvtAckStatus_t serial_svc_event_handler(void* event) { furi_check(furi_mutex_release(serial_svc->buff_size_mtx) == FuriStatusOk); } ret = SVCCTL_EvtAckFlowEnable; - } else if(attribute_modified->Attr_Handle == serial_svc->rpc_status_char_handle + 1) { + } else if( + attribute_modified->Attr_Handle == + serial_svc->chars[SerialSvcGattCharacteristicStatus].handle + 1) { SerialServiceRpcStatus* rpc_status = (SerialServiceRpcStatus*)attribute_modified->Attr_Data; if(*rpc_status == SerialServiceRpcStatusNotActive) { @@ -97,18 +141,12 @@ static SVCCTL_EvtAckStatus_t serial_svc_event_handler(void* event) { } static void serial_svc_update_rpc_char(SerialServiceRpcStatus status) { - tBleStatus ble_status = aci_gatt_update_char_value( - serial_svc->svc_handle, - serial_svc->rpc_status_char_handle, - 0, - sizeof(SerialServiceRpcStatus), - (uint8_t*)&status); - if(ble_status) { - FURI_LOG_E(TAG, "Failed to update RPC status char: %d", ble_status); - } + flipper_gatt_characteristic_update( + serial_svc->svc_handle, &serial_svc->chars[SerialSvcGattCharacteristicStatus], &status); } void serial_svc_start() { + UNUSED(serial_svc_chars); tBleStatus status; serial_svc = malloc(sizeof(SerialSvc)); // Register event handler @@ -116,72 +154,17 @@ void serial_svc_start() { // Add service status = aci_gatt_add_service( - UUID_TYPE_128, (Service_UUID_t*)service_uuid, PRIMARY_SERVICE, 12, &serial_svc->svc_handle); + UUID_TYPE_128, &service_uuid, PRIMARY_SERVICE, 12, &serial_svc->svc_handle); if(status) { FURI_LOG_E(TAG, "Failed to add Serial service: %d", status); } - // Add RX characteristics - status = aci_gatt_add_char( - serial_svc->svc_handle, - UUID_TYPE_128, - (const Char_UUID_t*)char_rx_uuid, - SERIAL_SVC_DATA_LEN_MAX, - CHAR_PROP_WRITE_WITHOUT_RESP | CHAR_PROP_WRITE | CHAR_PROP_READ, - ATTR_PERMISSION_AUTHEN_READ | ATTR_PERMISSION_AUTHEN_WRITE, - GATT_NOTIFY_ATTRIBUTE_WRITE, - 10, - CHAR_VALUE_LEN_VARIABLE, - &serial_svc->rx_char_handle); - if(status) { - FURI_LOG_E(TAG, "Failed to add RX characteristic: %d", status); + // Add characteristics + for(uint8_t i = 0; i < SerialSvcGattCharacteristicCount; i++) { + flipper_gatt_characteristic_init( + serial_svc->svc_handle, &serial_svc_chars[i], &serial_svc->chars[i]); } - // Add TX characteristic - status = aci_gatt_add_char( - serial_svc->svc_handle, - UUID_TYPE_128, - (const Char_UUID_t*)char_tx_uuid, - SERIAL_SVC_DATA_LEN_MAX, - CHAR_PROP_READ | CHAR_PROP_INDICATE, - ATTR_PERMISSION_AUTHEN_READ, - GATT_DONT_NOTIFY_EVENTS, - 10, - CHAR_VALUE_LEN_VARIABLE, - &serial_svc->tx_char_handle); - if(status) { - FURI_LOG_E(TAG, "Failed to add TX characteristic: %d", status); - } - // Add Flow Control characteristic - status = aci_gatt_add_char( - serial_svc->svc_handle, - UUID_TYPE_128, - (const Char_UUID_t*)flow_ctrl_uuid, - sizeof(uint32_t), - CHAR_PROP_READ | CHAR_PROP_NOTIFY, - ATTR_PERMISSION_AUTHEN_READ, - GATT_DONT_NOTIFY_EVENTS, - 10, - CHAR_VALUE_LEN_CONSTANT, - &serial_svc->flow_ctrl_char_handle); - if(status) { - FURI_LOG_E(TAG, "Failed to add Flow Control characteristic: %d", status); - } - // Add RPC status characteristic - status = aci_gatt_add_char( - serial_svc->svc_handle, - UUID_TYPE_128, - (const Char_UUID_t*)rpc_status_uuid, - sizeof(SerialServiceRpcStatus), - CHAR_PROP_READ | CHAR_PROP_WRITE | CHAR_PROP_NOTIFY, - ATTR_PERMISSION_AUTHEN_READ | ATTR_PERMISSION_AUTHEN_WRITE, - GATT_NOTIFY_ATTRIBUTE_WRITE, - 10, - CHAR_VALUE_LEN_CONSTANT, - &serial_svc->rpc_status_char_handle); - if(status) { - FURI_LOG_E(TAG, "Failed to add RPC status characteristic: %d", status); - } serial_svc_update_rpc_char(SerialServiceRpcStatusNotActive); // Allocate buffer size mutex serial_svc->buff_size_mtx = furi_mutex_alloc(FuriMutexTypeNormal); @@ -196,13 +179,12 @@ void serial_svc_set_callbacks( serial_svc->context = context; serial_svc->buff_size = buff_size; serial_svc->bytes_ready_to_receive = buff_size; + uint32_t buff_size_reversed = REVERSE_BYTES_U32(serial_svc->buff_size); - aci_gatt_update_char_value( + flipper_gatt_characteristic_update( serial_svc->svc_handle, - serial_svc->flow_ctrl_char_handle, - 0, - sizeof(uint32_t), - (uint8_t*)&buff_size_reversed); + &serial_svc->chars[SerialSvcGattCharacteristicFlowCtrl], + &buff_size_reversed); } void serial_svc_notify_buffer_is_empty() { @@ -213,13 +195,12 @@ void serial_svc_notify_buffer_is_empty() { if(serial_svc->bytes_ready_to_receive == 0) { FURI_LOG_D(TAG, "Buffer is empty. Notifying client"); serial_svc->bytes_ready_to_receive = serial_svc->buff_size; + uint32_t buff_size_reversed = REVERSE_BYTES_U32(serial_svc->buff_size); - aci_gatt_update_char_value( + flipper_gatt_characteristic_update( serial_svc->svc_handle, - serial_svc->flow_ctrl_char_handle, - 0, - sizeof(uint32_t), - (uint8_t*)&buff_size_reversed); + &serial_svc->chars[SerialSvcGattCharacteristicFlowCtrl], + &buff_size_reversed); } furi_check(furi_mutex_release(serial_svc->buff_size_mtx) == FuriStatusOk); } @@ -227,22 +208,8 @@ void serial_svc_notify_buffer_is_empty() { void serial_svc_stop() { tBleStatus status; if(serial_svc) { - // Delete characteristics - status = aci_gatt_del_char(serial_svc->svc_handle, serial_svc->tx_char_handle); - if(status) { - FURI_LOG_E(TAG, "Failed to delete TX characteristic: %d", status); - } - status = aci_gatt_del_char(serial_svc->svc_handle, serial_svc->rx_char_handle); - if(status) { - FURI_LOG_E(TAG, "Failed to delete RX characteristic: %d", status); - } - status = aci_gatt_del_char(serial_svc->svc_handle, serial_svc->flow_ctrl_char_handle); - if(status) { - FURI_LOG_E(TAG, "Failed to delete Flow Control characteristic: %d", status); - } - status = aci_gatt_del_char(serial_svc->svc_handle, serial_svc->rpc_status_char_handle); - if(status) { - FURI_LOG_E(TAG, "Failed to delete RPC Status characteristic: %d", status); + for(uint8_t i = 0; i < SerialSvcGattCharacteristicCount; i++) { + flipper_gatt_characteristic_delete(serial_svc->svc_handle, &serial_svc->chars[i]); } // Delete service status = aci_gatt_del_service(serial_svc->svc_handle); @@ -273,7 +240,7 @@ bool serial_svc_update_tx(uint8_t* data, uint16_t data_len) { tBleStatus result = aci_gatt_update_char_value_ext( 0, serial_svc->svc_handle, - serial_svc->tx_char_handle, + serial_svc->chars[SerialSvcGattCharacteristicTx].handle, remained ? 0x00 : 0x02, data_len, value_offset, diff --git a/firmware/targets/f7/ble_glue/serial_service.h b/firmware/targets/f7/ble_glue/services/serial_service.h similarity index 100% rename from firmware/targets/f7/ble_glue/serial_service.h rename to firmware/targets/f7/ble_glue/services/serial_service.h diff --git a/firmware/targets/f7/ble_glue/services/serial_service_uuid.inc b/firmware/targets/f7/ble_glue/services/serial_service_uuid.inc new file mode 100644 index 00000000000..a297d9ad604 --- /dev/null +++ b/firmware/targets/f7/ble_glue/services/serial_service_uuid.inc @@ -0,0 +1,12 @@ + +static const Service_UUID_t service_uuid = { .Service_UUID_128 = \ + { 0x00, 0x00, 0xfe, 0x60, 0xcc, 0x7a, 0x48, 0x2a, 0x98, 0x4a, 0x7f, 0x2e, 0xd5, 0xb3, 0xe5, 0x8f }}; + +#define SERIAL_SVC_TX_CHAR_UUID \ + { 0x00, 0x00, 0xfe, 0x61, 0x8e, 0x22, 0x45, 0x41, 0x9d, 0x4c, 0x21, 0xed, 0xae, 0x82, 0xed, 0x19 } +#define SERIAL_SVC_RX_CHAR_UUID \ + { 0x00, 0x00, 0xfe, 0x62, 0x8e, 0x22, 0x45, 0x41, 0x9d, 0x4c, 0x21, 0xed, 0xae, 0x82, 0xed, 0x19 } +#define SERIAL_SVC_FLOW_CONTROL_UUID \ + { 0x00, 0x00, 0xfe, 0x63, 0x8e, 0x22, 0x45, 0x41, 0x9d, 0x4c, 0x21, 0xed, 0xae, 0x82, 0xed, 0x19 } +#define SERIAL_SVC_RPC_STATUS_UUID \ + { 0x00, 0x00, 0xfe, 0x64, 0x8e, 0x22, 0x45, 0x41, 0x9d, 0x4c, 0x21, 0xed, 0xae, 0x82, 0xed, 0x19 } diff --git a/firmware/targets/f7/furi_hal/furi_hal_bt.c b/firmware/targets/f7/furi_hal/furi_hal_bt.c index 3639094f79e..6ff9f0e3b1f 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_bt.c +++ b/firmware/targets/f7/furi_hal/furi_hal_bt.c @@ -8,8 +8,7 @@ #include #include #include -#include "battery_service.h" - +#include #include #define TAG "FuriHalBt" diff --git a/firmware/targets/f7/furi_hal/furi_hal_bt_hid.c b/firmware/targets/f7/furi_hal/furi_hal_bt_hid.c index 8259be2f6cd..7ec712af4db 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_bt_hid.c +++ b/firmware/targets/f7/furi_hal/furi_hal_bt_hid.c @@ -1,11 +1,11 @@ #include #include -#include "usb_hid.h" -#include "dev_info_service.h" -#include "battery_service.h" -#include "hid_service.h" +#include +#include +#include #include +#include #define FURI_HAL_BT_INFO_BASE_USB_SPECIFICATION (0x0101) #define FURI_HAL_BT_INFO_COUNTRY_CODE (0x00) @@ -153,7 +153,7 @@ void furi_hal_bt_hid_start() { FURI_HAL_BT_HID_INFO_FLAG_REMOTE_WAKE_MSK | FURI_HAL_BT_HID_INFO_FLAG_NORMALLY_CONNECTABLE_MSK, }; - hid_svc_update_info(hid_info_val, sizeof(hid_info_val)); + hid_svc_update_info(hid_info_val); } void furi_hal_bt_hid_stop() { diff --git a/firmware/targets/f7/furi_hal/furi_hal_bt_serial.c b/firmware/targets/f7/furi_hal/furi_hal_bt_serial.c index 2539e6bd0e9..2927d946f98 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_bt_serial.c +++ b/firmware/targets/f7/furi_hal/furi_hal_bt_serial.c @@ -1,7 +1,7 @@ #include -#include "dev_info_service.h" -#include "battery_service.h" -#include "serial_service.h" +#include +#include +#include #include diff --git a/firmware/targets/furi_hal_include/furi_hal_bt.h b/firmware/targets/furi_hal_include/furi_hal_bt.h index 6ba38cb5e63..4d538265dbb 100644 --- a/firmware/targets/furi_hal_include/furi_hal_bt.h +++ b/firmware/targets/furi_hal_include/furi_hal_bt.h @@ -8,7 +8,7 @@ #include #include #include -#include +#include #include #include diff --git a/firmware/targets/furi_hal_include/furi_hal_bt_serial.h b/firmware/targets/furi_hal_include/furi_hal_bt_serial.h index 1b6e79ab078..0472d31d181 100644 --- a/firmware/targets/furi_hal_include/furi_hal_bt_serial.h +++ b/firmware/targets/furi_hal_include/furi_hal_bt_serial.h @@ -1,6 +1,6 @@ #pragma once -#include "serial_service.h" +#include #ifdef __cplusplus extern "C" { From 1c306a04fcc5b432d4d50931a39ff0acd58be303 Mon Sep 17 00:00:00 2001 From: hedger Date: Thu, 8 Jun 2023 16:43:05 +0400 Subject: [PATCH 605/824] [FL-3359] github: added debugapps artifact; packaging resources per-target (#2750) * github: added debugapps artifact; packaging resources per-target * github: target name fixes * github: fixed path for debug apps * scripts: dist: removed lib stub artifact * scripts: fixed broken SDK * github: removed unused step --- .github/workflows/build.yml | 29 ++++++++++------------------- scripts/sconsdist.py | 11 ----------- 2 files changed, 10 insertions(+), 30 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fb0f13e207f..9fbcb16ebeb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -52,10 +52,8 @@ jobs: - name: 'Make artifacts directory' run: | - rm -rf artifacts - rm -rf map_analyser_files - mkdir artifacts - mkdir map_analyser_files + rm -rf artifacts map_analyser_files + mkdir artifacts map_analyser_files - name: 'Bundle scripts' if: ${{ !github.event.pull_request.head.repo.fork }} @@ -66,28 +64,21 @@ jobs: run: | set -e for TARGET in ${TARGETS}; do - TARGET="$(echo "${TARGET}" | sed 's/f//')"; \ - ./fbt TARGET_HW=$TARGET copro_dist updater_package \ - ${{ startsWith(github.ref, 'refs/tags') && 'DEBUG=0 COMPACT=1' || '' }} - done - - - name: 'Move upload files' - if: ${{ !github.event.pull_request.head.repo.fork }} - run: | - set -e - for TARGET in ${TARGETS}; do + TARGET_HW="$(echo "${TARGET}" | sed 's/f//')"; \ + ./fbt TARGET_HW=$TARGET_HW copro_dist updater_package \ + ${{ startsWith(github.ref, 'refs/tags') && 'DEBUG=0 COMPACT=1' || '' }} mv dist/${TARGET}-*/* artifacts/ + tar czpf "artifacts/flipper-z-${TARGET}-resources-${SUFFIX}.tgz" \ + -C assets resources + ./fbt TARGET_HW=$TARGET_HW fap_dist + tar czpf "artifacts/flipper-z-${TARGET}-debugapps-${SUFFIX}.tgz" \ + -C dist/${TARGET}-*/apps/Debug . done - name: "Check for uncommitted changes" run: | git diff --exit-code - - name: 'Bundle resources' - if: ${{ !github.event.pull_request.head.repo.fork }} - run: | - tar czpf "artifacts/flipper-z-any-resources-${SUFFIX}.tgz" -C assets resources - - name: 'Bundle core2 firmware' if: ${{ !github.event.pull_request.head.repo.fork }} run: | diff --git a/scripts/sconsdist.py b/scripts/sconsdist.py index 23f9526a0b5..2cf43dce02a 100644 --- a/scripts/sconsdist.py +++ b/scripts/sconsdist.py @@ -84,17 +84,6 @@ def copy_single_project(self, project: ProjectDir) -> None: if exists(sdk_folder := join(obj_directory, foldertype)): self.note_dist_component(foldertype, "dir", sdk_folder) - # TODO: remove this after everyone migrates to new uFBT - self.create_zip_stub("lib") - - def create_zip_stub(self, foldertype): - with zipfile.ZipFile( - self.get_dist_path(self.get_dist_file_name(foldertype, "zip")), - "w", - zipfile.ZIP_DEFLATED, - ) as _: - pass - def copy(self) -> int: self._dist_components: dict[str, str] = dict() self.projects: dict[str, ProjectDir] = dict( From bff592126675de07e27d0f2749736119f2917b8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Thu, 8 Jun 2023 22:05:35 +0900 Subject: [PATCH 606/824] FuriHal: always clock SMPS from HSI (#2643) * FuriHal: always clock SMPS from HSI * FuriHal: add clock startup time check, ensure that we conform to core2 config value * FuriHal: set sleep mode to legacy if clock startup time is too high --------- Co-authored-by: hedger --- firmware/targets/f7/furi_hal/furi_hal_clock.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/firmware/targets/f7/furi_hal/furi_hal_clock.c b/firmware/targets/f7/furi_hal/furi_hal_clock.c index 9d228f0f55e..770b74296c7 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_clock.c +++ b/firmware/targets/f7/furi_hal/furi_hal_clock.c @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -48,6 +49,10 @@ void furi_hal_clock_init() { LL_RCC_LSI1_Enable(); while(!LS_CLOCK_IS_READY()) ; + + /* RF wakeup */ + LL_RCC_SetRFWKPClockSource(LL_RCC_RFWKP_CLKSOURCE_LSE); + LL_EXTI_EnableIT_0_31( LL_EXTI_LINE_18); /* Why? Because that's why. See RM0434, Table 61. CPU1 vector table. */ LL_EXTI_EnableRisingTrig_0_31(LL_EXTI_LINE_18); @@ -111,7 +116,7 @@ void furi_hal_clock_init() { LL_RCC_SetCLK48ClockSource(LL_RCC_CLK48_CLKSOURCE_PLLSAI1); LL_RCC_HSI_EnableInStopMode(); // Ensure that MR is capable of work in STOP0 - LL_RCC_SetSMPSClockSource(LL_RCC_SMPS_CLKSOURCE_HSE); + LL_RCC_SetSMPSClockSource(LL_RCC_SMPS_CLKSOURCE_HSI); LL_RCC_SetSMPSPrescaler(LL_RCC_SMPS_DIV_1); LL_RCC_SetRFWKPClockSource(LL_RCC_RFWKP_CLKSOURCE_LSE); @@ -124,8 +129,8 @@ void furi_hal_clock_switch_to_hsi() { while(!LL_RCC_HSI_IsReady()) ; - LL_RCC_SetSMPSClockSource(LL_RCC_SMPS_CLKSOURCE_HSI); LL_RCC_SetSysClkSource(LL_RCC_SYS_CLKSOURCE_HSI); + furi_assert(LL_RCC_GetSMPSClockSource() == LL_RCC_SMPS_CLKSOURCE_HSI); while(LL_RCC_GetSysClkSource() != LL_RCC_SYS_CLKSOURCE_STATUS_HSI) ; @@ -138,6 +143,7 @@ void furi_hal_clock_switch_to_hsi() { } void furi_hal_clock_switch_to_pll() { + uint32_t clock_start_time = DWT->CYCCNT; LL_RCC_HSE_Enable(); LL_RCC_PLL_Enable(); LL_RCC_PLLSAI1_Enable(); @@ -156,10 +162,15 @@ void furi_hal_clock_switch_to_pll() { ; LL_RCC_SetSysClkSource(LL_RCC_SYS_CLKSOURCE_PLL); - LL_RCC_SetSMPSClockSource(LL_RCC_SMPS_CLKSOURCE_HSE); while(LL_RCC_GetSysClkSource() != LL_RCC_SYS_CLKSOURCE_STATUS_PLL) ; + + uint32_t total = DWT->CYCCNT - clock_start_time; + if(total > (20 * 0x148)) { + furi_hal_rtc_set_flag(FuriHalRtcFlagLegacySleep); + furi_crash("Slow HSE/PLL startup"); + } } void furi_hal_clock_suspend_tick() { From 62939dd28b5903a0509c154a3158f6d4fad374a0 Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Fri, 9 Jun 2023 03:49:26 -0700 Subject: [PATCH 607/824] Core2, SRAM2: provide safety gap (#2754) * Core2, SRAM2: use ob, provide safety gap * thread: comment about critical section and scheduler state --- .../targets/f7/furi_hal/furi_hal_memory.c | 82 ++++++++++--------- furi/core/thread.c | 2 + 2 files changed, 44 insertions(+), 40 deletions(-) diff --git a/firmware/targets/f7/furi_hal/furi_hal_memory.c b/firmware/targets/f7/furi_hal/furi_hal_memory.c index 9716f1e5292..7f69b90ca2b 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_memory.c +++ b/firmware/targets/f7/furi_hal/furi_hal_memory.c @@ -4,6 +4,9 @@ #define TAG "FuriHalMemory" +// STM(TM) Copro(TM) bug(TM) workaround size +#define RAM2B_COPRO_GAP_SIZE_KB 2 + typedef enum { SRAM_A, SRAM_B, @@ -30,53 +33,47 @@ void furi_hal_memory_init() { return; } - if(!ble_glue_wait_for_c2_start(FURI_HAL_BT_C2_START_TIMEOUT)) { - FURI_LOG_E(TAG, "C2 start timeout"); - return; - } - FuriHalMemory* memory = malloc(sizeof(FuriHalMemory)); - const BleGlueC2Info* c2_ver = ble_glue_get_c2_info(); + uint32_t sbrsa = (FLASH->SRRVR & FLASH_SRRVR_SBRSA_Msk) >> FLASH_SRRVR_SBRSA_Pos; + uint32_t snbrsa = (FLASH->SRRVR & FLASH_SRRVR_SNBRSA_Msk) >> FLASH_SRRVR_SNBRSA_Pos; + + uint32_t sram2a_busy_size = (uint32_t)&__sram2a_free__ - (uint32_t)&__sram2a_start__; + uint32_t sram2a_unprotected_size = (sbrsa)*1024; + uint32_t sram2b_unprotected_size = (snbrsa)*1024; - if(c2_ver->mode == BleGlueC2ModeStack) { - uint32_t sram2a_busy_size = (uint32_t)&__sram2a_free__ - (uint32_t)&__sram2a_start__; - uint32_t sram2a_unprotected_size = (32 - c2_ver->MemorySizeSram2A) * 1024; - uint32_t sram2b_unprotected_size = (32 - c2_ver->MemorySizeSram2B) * 1024; + // STM(TM) Copro(TM) bug(TM) workaround + sram2b_unprotected_size -= 1024 * RAM2B_COPRO_GAP_SIZE_KB; - memory->region[SRAM_A].start = (uint8_t*)&__sram2a_free__; - memory->region[SRAM_B].start = (uint8_t*)&__sram2b_start__; + memory->region[SRAM_A].start = (uint8_t*)&__sram2a_free__; + memory->region[SRAM_B].start = (uint8_t*)&__sram2b_start__; - if(sram2a_unprotected_size > sram2a_busy_size) { - memory->region[SRAM_A].size = sram2a_unprotected_size - sram2a_busy_size; - } else { - memory->region[SRAM_A].size = 0; + if(sram2a_unprotected_size > sram2a_busy_size) { + memory->region[SRAM_A].size = sram2a_unprotected_size - sram2a_busy_size; + } else { + memory->region[SRAM_A].size = 0; + } + memory->region[SRAM_B].size = sram2b_unprotected_size; + + FURI_LOG_I( + TAG, "SRAM2A: 0x%p, %lu", memory->region[SRAM_A].start, memory->region[SRAM_A].size); + FURI_LOG_I( + TAG, "SRAM2B: 0x%p, %lu", memory->region[SRAM_B].start, memory->region[SRAM_B].size); + + if((memory->region[SRAM_A].size > 0) || (memory->region[SRAM_B].size > 0)) { + if((memory->region[SRAM_A].size > 0)) { + FURI_LOG_I(TAG, "SRAM2A clear"); + memset(memory->region[SRAM_A].start, 0, memory->region[SRAM_A].size); } - memory->region[SRAM_B].size = sram2b_unprotected_size; - - FURI_LOG_I( - TAG, "SRAM2A: 0x%p, %lu", memory->region[SRAM_A].start, memory->region[SRAM_A].size); - FURI_LOG_I( - TAG, "SRAM2B: 0x%p, %lu", memory->region[SRAM_B].start, memory->region[SRAM_B].size); - - if((memory->region[SRAM_A].size > 0) || (memory->region[SRAM_B].size > 0)) { - if((memory->region[SRAM_A].size > 0)) { - FURI_LOG_I(TAG, "SRAM2A clear"); - memset(memory->region[SRAM_A].start, 0, memory->region[SRAM_A].size); - } - if((memory->region[SRAM_B].size > 0)) { - FURI_LOG_I(TAG, "SRAM2B clear"); - memset(memory->region[SRAM_B].start, 0, memory->region[SRAM_B].size); - } - furi_hal_memory = memory; - FURI_LOG_I(TAG, "Enabled"); - } else { - free(memory); - FURI_LOG_E(TAG, "No SRAM2 available"); + if((memory->region[SRAM_B].size > 0)) { + FURI_LOG_I(TAG, "SRAM2B clear"); + memset(memory->region[SRAM_B].start, 0, memory->region[SRAM_B].size); } + furi_hal_memory = memory; + FURI_LOG_I(TAG, "Enabled"); } else { free(memory); - FURI_LOG_E(TAG, "No Core2 available"); + FURI_LOG_E(TAG, "No SRAM2 available"); } } @@ -89,15 +86,20 @@ void* furi_hal_memory_alloc(size_t size) { return NULL; } + void* allocated_memory = NULL; + FURI_CRITICAL_ENTER(); for(int i = 0; i < SRAM_MAX; i++) { if(furi_hal_memory->region[i].size >= size) { void* ptr = furi_hal_memory->region[i].start; furi_hal_memory->region[i].start += size; furi_hal_memory->region[i].size -= size; - return ptr; + allocated_memory = ptr; + break; } } - return NULL; + FURI_CRITICAL_EXIT(); + + return allocated_memory; } size_t furi_hal_memory_get_free() { diff --git a/furi/core/thread.c b/furi/core/thread.c index facbcb41179..657b867d1e7 100644 --- a/furi/core/thread.c +++ b/furi/core/thread.c @@ -56,6 +56,8 @@ static int32_t __furi_thread_stdout_flush(FuriThread* thread); /** Catch threads that are trying to exit wrong way */ __attribute__((__noreturn__)) void furi_thread_catch() { //-V1082 + // If you're here it means you're probably doing something wrong + // with critical sections or with scheduler state asm volatile("nop"); // extra magic furi_crash("You are doing it wrong"); //-V779 __builtin_unreachable(); From 0e4344a83c48ec07cbecb52a6aa9168328ad3fdb Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Fri, 9 Jun 2023 04:02:47 -0700 Subject: [PATCH 608/824] Services: simplify api (#2540) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- applications/external/hid_app/hid.c | 4 ++-- applications/external/mfkey32/mfkey32.c | 2 +- .../picopass/scenes/picopass_scene_device_info.c | 2 +- .../picopass/scenes/picopass_scene_read_card.c | 2 +- .../scenes/picopass_scene_read_card_success.c | 2 +- .../scenes/picopass_scene_read_factory_success.c | 2 +- .../picopass/scenes/picopass_scene_save_success.c | 2 +- .../picopass/scenes/picopass_scene_write_card.c | 2 +- .../scenes/picopass_scene_write_card_success.c | 2 +- .../picopass/scenes/picopass_scene_write_key.c | 2 +- applications/external/snake_game/snake_game.c | 2 +- applications/main/bad_usb/helpers/ducky_script.c | 4 ++-- applications/main/gpio/scenes/gpio_scene_start.c | 2 +- applications/main/ibutton/ibutton.c | 4 ++-- .../main/ibutton/scenes/ibutton_scene_read.c | 2 +- .../ibutton/scenes/ibutton_scene_read_key_menu.c | 2 +- .../main/ibutton/scenes/ibutton_scene_save_name.c | 4 ++-- .../ibutton/scenes/ibutton_scene_saved_key_menu.c | 2 +- .../main/ibutton/scenes/ibutton_scene_start.c | 2 +- applications/main/infrared/infrared.c | 2 +- .../scenes/common/infrared_scene_universal_common.c | 2 +- .../main/infrared/scenes/infrared_scene_learn.c | 2 +- .../scenes/infrared_scene_learn_enter_name.c | 2 +- applications/main/lfrfid/lfrfid.c | 4 ++-- .../main/lfrfid/scenes/lfrfid_scene_extra_actions.c | 4 ++-- applications/main/lfrfid/scenes/lfrfid_scene_read.c | 2 +- .../main/lfrfid/scenes/lfrfid_scene_read_key_menu.c | 2 +- .../main/lfrfid/scenes/lfrfid_scene_save_name.c | 4 ++-- .../main/lfrfid/scenes/lfrfid_scene_saved_key_menu.c | 2 +- applications/main/lfrfid/scenes/lfrfid_scene_start.c | 2 +- applications/main/nfc/nfc.c | 8 ++++---- .../nfc/scenes/nfc_scene_mf_classic_dict_attack.c | 4 ++-- .../main/nfc/scenes/nfc_scene_mf_classic_keys_add.c | 2 +- .../main/nfc/scenes/nfc_scene_mf_classic_menu.c | 6 +++--- .../main/nfc/scenes/nfc_scene_mf_classic_update.c | 2 +- .../nfc/scenes/nfc_scene_mf_classic_update_success.c | 2 +- .../main/nfc/scenes/nfc_scene_mf_classic_write.c | 2 +- .../nfc/scenes/nfc_scene_mf_classic_write_success.c | 2 +- .../main/nfc/scenes/nfc_scene_mf_desfire_menu.c | 4 ++-- .../main/nfc/scenes/nfc_scene_mf_ultralight_menu.c | 4 ++-- .../nfc/scenes/nfc_scene_mf_ultralight_unlock_warn.c | 4 ++-- applications/main/nfc/scenes/nfc_scene_nfca_menu.c | 4 ++-- .../main/nfc/scenes/nfc_scene_nfcv_key_input.c | 2 +- applications/main/nfc/scenes/nfc_scene_nfcv_menu.c | 4 ++-- applications/main/nfc/scenes/nfc_scene_nfcv_unlock.c | 2 +- .../main/nfc/scenes/nfc_scene_nfcv_unlock_menu.c | 2 +- applications/main/nfc/scenes/nfc_scene_read.c | 12 ++++++------ applications/main/nfc/scenes/nfc_scene_save_name.c | 4 ++-- applications/main/nfc/scenes/nfc_scene_saved_menu.c | 4 ++-- applications/main/nfc/scenes/nfc_scene_start.c | 4 ++-- .../main/subghz/scenes/subghz_scene_read_raw.c | 4 ++-- .../main/subghz/scenes/subghz_scene_receiver.c | 2 +- .../main/subghz/scenes/subghz_scene_save_name.c | 4 ++-- applications/main/subghz/scenes/subghz_scene_start.c | 2 +- .../main/subghz/scenes/subghz_scene_transmitter.c | 2 +- applications/main/u2f/scenes/u2f_scene_main.c | 2 +- .../services/desktop/scenes/desktop_scene_debug.c | 4 ++-- applications/services/dolphin/dolphin.c | 5 +++-- applications/services/dolphin/dolphin.h | 9 +-------- firmware/targets/f18/api_symbols.csv | 4 ++-- firmware/targets/f7/api_symbols.csv | 4 ++-- 61 files changed, 94 insertions(+), 100 deletions(-) diff --git a/applications/external/hid_app/hid.c b/applications/external/hid_app/hid.c index a4f64589d76..ea408c39287 100644 --- a/applications/external/hid_app/hid.c +++ b/applications/external/hid_app/hid.c @@ -377,7 +377,7 @@ int32_t hid_usb_app(void* p) { bt_hid_connection_status_changed_callback(BtStatusConnected, app); - DOLPHIN_DEED(DolphinDeedPluginStart); + dolphin_deed(DolphinDeedPluginStart); view_dispatcher_run(app->view_dispatcher); @@ -417,7 +417,7 @@ int32_t hid_ble_app(void* p) { furi_hal_bt_start_advertising(); bt_set_status_changed_callback(app->bt, bt_hid_connection_status_changed_callback, app); - DOLPHIN_DEED(DolphinDeedPluginStart); + dolphin_deed(DolphinDeedPluginStart); view_dispatcher_run(app->view_dispatcher); diff --git a/applications/external/mfkey32/mfkey32.c b/applications/external/mfkey32/mfkey32.c index d4b2d3e4ab0..5e790b01f6e 100644 --- a/applications/external/mfkey32/mfkey32.c +++ b/applications/external/mfkey32/mfkey32.c @@ -1112,7 +1112,7 @@ void mfkey32(ProgramState* program_state) { } if(keyarray_size > 0) { // TODO: Should we use DolphinDeedNfcMfcAdd? - DOLPHIN_DEED(DolphinDeedNfcMfcAdd); + dolphin_deed(DolphinDeedNfcMfcAdd); } napi_mf_classic_nonce_array_free(nonce_arr); napi_mf_classic_dict_free(user_dict); diff --git a/applications/external/picopass/scenes/picopass_scene_device_info.c b/applications/external/picopass/scenes/picopass_scene_device_info.c index 41caeabf5ad..bb149aa6b37 100644 --- a/applications/external/picopass/scenes/picopass_scene_device_info.c +++ b/applications/external/picopass/scenes/picopass_scene_device_info.c @@ -19,7 +19,7 @@ void picopass_scene_device_info_on_enter(void* context) { FuriString* wiegand_str = furi_string_alloc(); FuriString* sio_str = furi_string_alloc(); - DOLPHIN_DEED(DolphinDeedNfcReadSuccess); + dolphin_deed(DolphinDeedNfcReadSuccess); // Setup view PicopassBlock* AA1 = picopass->dev->dev_data.AA1; diff --git a/applications/external/picopass/scenes/picopass_scene_read_card.c b/applications/external/picopass/scenes/picopass_scene_read_card.c index 96ec7c668b9..c1cc7249c42 100644 --- a/applications/external/picopass/scenes/picopass_scene_read_card.c +++ b/applications/external/picopass/scenes/picopass_scene_read_card.c @@ -10,7 +10,7 @@ void picopass_read_card_worker_callback(PicopassWorkerEvent event, void* context void picopass_scene_read_card_on_enter(void* context) { Picopass* picopass = context; - DOLPHIN_DEED(DolphinDeedNfcRead); + dolphin_deed(DolphinDeedNfcRead); // Setup view Popup* popup = picopass->popup; diff --git a/applications/external/picopass/scenes/picopass_scene_read_card_success.c b/applications/external/picopass/scenes/picopass_scene_read_card_success.c index cc18ac066a0..ffe7195b792 100644 --- a/applications/external/picopass/scenes/picopass_scene_read_card_success.c +++ b/applications/external/picopass/scenes/picopass_scene_read_card_success.c @@ -21,7 +21,7 @@ void picopass_scene_read_card_success_on_enter(void* context) { FuriString* wiegand_str = furi_string_alloc(); FuriString* sio_str = furi_string_alloc(); - DOLPHIN_DEED(DolphinDeedNfcReadSuccess); + dolphin_deed(DolphinDeedNfcReadSuccess); // Send notification notification_message(picopass->notifications, &sequence_success); diff --git a/applications/external/picopass/scenes/picopass_scene_read_factory_success.c b/applications/external/picopass/scenes/picopass_scene_read_factory_success.c index bc07bb95302..f5fcd10fda1 100644 --- a/applications/external/picopass/scenes/picopass_scene_read_factory_success.c +++ b/applications/external/picopass/scenes/picopass_scene_read_factory_success.c @@ -19,7 +19,7 @@ void picopass_scene_read_factory_success_on_enter(void* context) { FuriString* title = furi_string_alloc_set("Factory Default"); FuriString* subtitle = furi_string_alloc_set(""); - DOLPHIN_DEED(DolphinDeedNfcReadSuccess); + dolphin_deed(DolphinDeedNfcReadSuccess); // Send notification notification_message(picopass->notifications, &sequence_success); diff --git a/applications/external/picopass/scenes/picopass_scene_save_success.c b/applications/external/picopass/scenes/picopass_scene_save_success.c index e92d91fb440..3b0a1cadd25 100644 --- a/applications/external/picopass/scenes/picopass_scene_save_success.c +++ b/applications/external/picopass/scenes/picopass_scene_save_success.c @@ -8,7 +8,7 @@ void picopass_scene_save_success_popup_callback(void* context) { void picopass_scene_save_success_on_enter(void* context) { Picopass* picopass = context; - DOLPHIN_DEED(DolphinDeedNfcSave); + dolphin_deed(DolphinDeedNfcSave); // Setup view Popup* popup = picopass->popup; diff --git a/applications/external/picopass/scenes/picopass_scene_write_card.c b/applications/external/picopass/scenes/picopass_scene_write_card.c index a905dca9556..ce396fc10e1 100644 --- a/applications/external/picopass/scenes/picopass_scene_write_card.c +++ b/applications/external/picopass/scenes/picopass_scene_write_card.c @@ -9,7 +9,7 @@ void picopass_write_card_worker_callback(PicopassWorkerEvent event, void* contex void picopass_scene_write_card_on_enter(void* context) { Picopass* picopass = context; - DOLPHIN_DEED(DolphinDeedNfcSave); + dolphin_deed(DolphinDeedNfcSave); // Setup view Popup* popup = picopass->popup; diff --git a/applications/external/picopass/scenes/picopass_scene_write_card_success.c b/applications/external/picopass/scenes/picopass_scene_write_card_success.c index 4bbca816aa2..cd760272fe9 100644 --- a/applications/external/picopass/scenes/picopass_scene_write_card_success.c +++ b/applications/external/picopass/scenes/picopass_scene_write_card_success.c @@ -18,7 +18,7 @@ void picopass_scene_write_card_success_on_enter(void* context) { Widget* widget = picopass->widget; FuriString* str = furi_string_alloc_set("Write Success!"); - DOLPHIN_DEED(DolphinDeedNfcReadSuccess); + dolphin_deed(DolphinDeedNfcReadSuccess); // Send notification notification_message(picopass->notifications, &sequence_success); diff --git a/applications/external/picopass/scenes/picopass_scene_write_key.c b/applications/external/picopass/scenes/picopass_scene_write_key.c index 0f417e1c3fe..806a2b5a856 100644 --- a/applications/external/picopass/scenes/picopass_scene_write_key.c +++ b/applications/external/picopass/scenes/picopass_scene_write_key.c @@ -9,7 +9,7 @@ void picopass_write_key_worker_callback(PicopassWorkerEvent event, void* context void picopass_scene_write_key_on_enter(void* context) { Picopass* picopass = context; - DOLPHIN_DEED(DolphinDeedNfcSave); + dolphin_deed(DolphinDeedNfcSave); // Setup view Popup* popup = picopass->popup; diff --git a/applications/external/snake_game/snake_game.c b/applications/external/snake_game/snake_game.c index 3cf9b6d53d0..6852cb215b2 100644 --- a/applications/external/snake_game/snake_game.c +++ b/applications/external/snake_game/snake_game.c @@ -346,7 +346,7 @@ int32_t snake_game_app(void* p) { notification_message_block(notification, &sequence_display_backlight_enforce_on); - DOLPHIN_DEED(DolphinDeedPluginGameStart); + dolphin_deed(DolphinDeedPluginGameStart); SnakeEvent event; for(bool processing = true; processing;) { diff --git a/applications/main/bad_usb/helpers/ducky_script.c b/applications/main/bad_usb/helpers/ducky_script.c index 5a834ad0afe..f194178a067 100644 --- a/applications/main/bad_usb/helpers/ducky_script.c +++ b/applications/main/bad_usb/helpers/ducky_script.c @@ -426,7 +426,7 @@ static int32_t bad_usb_worker(void* context) { if(flags & WorkerEvtEnd) { break; } else if(flags & WorkerEvtStartStop) { // Start executing script - DOLPHIN_DEED(DolphinDeedBadUsbPlayScript); + dolphin_deed(DolphinDeedBadUsbPlayScript); delay_val = 0; bad_usb->buf_len = 0; bad_usb->st.line_cur = 0; @@ -449,7 +449,7 @@ static int32_t bad_usb_worker(void* context) { if(flags & WorkerEvtEnd) { break; } else if(flags & WorkerEvtConnect) { // Start executing script - DOLPHIN_DEED(DolphinDeedBadUsbPlayScript); + dolphin_deed(DolphinDeedBadUsbPlayScript); delay_val = 0; bad_usb->buf_len = 0; bad_usb->st.line_cur = 0; diff --git a/applications/main/gpio/scenes/gpio_scene_start.c b/applications/main/gpio/scenes/gpio_scene_start.c index 0272677934c..42193648800 100644 --- a/applications/main/gpio/scenes/gpio_scene_start.c +++ b/applications/main/gpio/scenes/gpio_scene_start.c @@ -89,7 +89,7 @@ bool gpio_scene_start_on_event(void* context, SceneManagerEvent event) { } else if(event.event == GpioStartEventUsbUart) { scene_manager_set_scene_state(app->scene_manager, GpioSceneStart, GpioItemUsbUart); if(!furi_hal_usb_is_locked()) { - DOLPHIN_DEED(DolphinDeedGpioUartBridge); + dolphin_deed(DolphinDeedGpioUartBridge); scene_manager_next_scene(app->scene_manager, GpioSceneUsbUart); } else { scene_manager_next_scene(app->scene_manager, GpioSceneUsbUartCloseRpc); diff --git a/applications/main/ibutton/ibutton.c b/applications/main/ibutton/ibutton.c index 79999adb28c..ad5b233b56e 100644 --- a/applications/main/ibutton/ibutton.c +++ b/applications/main/ibutton/ibutton.c @@ -282,14 +282,14 @@ int32_t ibutton_app(void* arg) { view_dispatcher_attach_to_gui( ibutton->view_dispatcher, ibutton->gui, ViewDispatcherTypeDesktop); scene_manager_next_scene(ibutton->scene_manager, iButtonSceneRpc); - DOLPHIN_DEED(DolphinDeedIbuttonEmulate); + dolphin_deed(DolphinDeedIbuttonEmulate); } else { view_dispatcher_attach_to_gui( ibutton->view_dispatcher, ibutton->gui, ViewDispatcherTypeFullscreen); if(key_loaded) { //-V547 scene_manager_next_scene(ibutton->scene_manager, iButtonSceneEmulate); - DOLPHIN_DEED(DolphinDeedIbuttonEmulate); + dolphin_deed(DolphinDeedIbuttonEmulate); } else { scene_manager_next_scene(ibutton->scene_manager, iButtonSceneStart); } diff --git a/applications/main/ibutton/scenes/ibutton_scene_read.c b/applications/main/ibutton/scenes/ibutton_scene_read.c index a840fb7b7ec..f360c3ac43a 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_read.c +++ b/applications/main/ibutton/scenes/ibutton_scene_read.c @@ -38,7 +38,7 @@ bool ibutton_scene_read_on_event(void* context, SceneManagerEvent event) { ibutton_notification_message(ibutton, iButtonNotificationMessageSuccess); scene_manager_next_scene(scene_manager, iButtonSceneReadSuccess); - DOLPHIN_DEED(DolphinDeedIbuttonReadSuccess); + dolphin_deed(DolphinDeedIbuttonReadSuccess); } else { scene_manager_next_scene(scene_manager, iButtonSceneReadError); diff --git a/applications/main/ibutton/scenes/ibutton_scene_read_key_menu.c b/applications/main/ibutton/scenes/ibutton_scene_read_key_menu.c index 716f72c7d3d..1555f2cc20c 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_read_key_menu.c +++ b/applications/main/ibutton/scenes/ibutton_scene_read_key_menu.c @@ -75,7 +75,7 @@ bool ibutton_scene_read_key_menu_on_event(void* context, SceneManagerEvent event scene_manager_next_scene(scene_manager, iButtonSceneSaveName); } else if(event.event == SubmenuIndexEmulate) { scene_manager_next_scene(scene_manager, iButtonSceneEmulate); - DOLPHIN_DEED(DolphinDeedIbuttonEmulate); + dolphin_deed(DolphinDeedIbuttonEmulate); } else if(event.event == SubmenuIndexViewData) { scene_manager_next_scene(scene_manager, iButtonSceneViewData); } else if(event.event == SubmenuIndexWriteBlank) { diff --git a/applications/main/ibutton/scenes/ibutton_scene_save_name.c b/applications/main/ibutton/scenes/ibutton_scene_save_name.c index 4ad0315e54e..7bd49df8337 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_save_name.c +++ b/applications/main/ibutton/scenes/ibutton_scene_save_name.c @@ -58,9 +58,9 @@ bool ibutton_scene_save_name_on_event(void* context, SceneManagerEvent event) { // Nothing, do not count editing as saving } else if(scene_manager_has_previous_scene( ibutton->scene_manager, iButtonSceneAddType)) { - DOLPHIN_DEED(DolphinDeedIbuttonAdd); + dolphin_deed(DolphinDeedIbuttonAdd); } else { - DOLPHIN_DEED(DolphinDeedIbuttonSave); + dolphin_deed(DolphinDeedIbuttonSave); } } else { diff --git a/applications/main/ibutton/scenes/ibutton_scene_saved_key_menu.c b/applications/main/ibutton/scenes/ibutton_scene_saved_key_menu.c index 80fca28b5ee..fc0cf42e2ff 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_saved_key_menu.c +++ b/applications/main/ibutton/scenes/ibutton_scene_saved_key_menu.c @@ -48,7 +48,7 @@ bool ibutton_scene_saved_key_menu_on_event(void* context, SceneManagerEvent even consumed = true; if(event.event == SubmenuIndexEmulate) { scene_manager_next_scene(scene_manager, iButtonSceneEmulate); - DOLPHIN_DEED(DolphinDeedIbuttonEmulate); + dolphin_deed(DolphinDeedIbuttonEmulate); } else if(event.event == SubmenuIndexWriteBlank) { ibutton->write_mode = iButtonWriteModeBlank; scene_manager_next_scene(scene_manager, iButtonSceneWrite); diff --git a/applications/main/ibutton/scenes/ibutton_scene_start.c b/applications/main/ibutton/scenes/ibutton_scene_start.c index 37bf96f39f0..63a4cf869d3 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_start.c +++ b/applications/main/ibutton/scenes/ibutton_scene_start.c @@ -33,7 +33,7 @@ bool ibutton_scene_start_on_event(void* context, SceneManagerEvent event) { consumed = true; if(event.event == SubmenuIndexRead) { scene_manager_next_scene(ibutton->scene_manager, iButtonSceneRead); - DOLPHIN_DEED(DolphinDeedIbuttonRead); + dolphin_deed(DolphinDeedIbuttonRead); } else if(event.event == SubmenuIndexSaved) { scene_manager_next_scene(ibutton->scene_manager, iButtonSceneSelectKey); } else if(event.event == SubmenuIndexAdd) { diff --git a/applications/main/infrared/infrared.c b/applications/main/infrared/infrared.c index 685dd57ec05..5957cdb1336 100644 --- a/applications/main/infrared/infrared.c +++ b/applications/main/infrared/infrared.c @@ -319,7 +319,7 @@ void infrared_tx_start_signal(Infrared* infrared, InfraredSignal* signal) { infrared_worker_set_decoded_signal(infrared->worker, message); } - DOLPHIN_DEED(DolphinDeedIrSend); + dolphin_deed(DolphinDeedIrSend); infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkStartSend); infrared_worker_tx_set_get_signal_callback( diff --git a/applications/main/infrared/scenes/common/infrared_scene_universal_common.c b/applications/main/infrared/scenes/common/infrared_scene_universal_common.c index d55d8d0a6db..96f28cc4891 100644 --- a/applications/main/infrared/scenes/common/infrared_scene_universal_common.c +++ b/applications/main/infrared/scenes/common/infrared_scene_universal_common.c @@ -70,7 +70,7 @@ bool infrared_scene_universal_common_on_event(void* context, SceneManagerEvent e uint32_t record_count; if(infrared_brute_force_start( brute_force, infrared_custom_event_get_value(event.event), &record_count)) { - DOLPHIN_DEED(DolphinDeedIrSend); + dolphin_deed(DolphinDeedIrSend); infrared_scene_universal_common_show_popup(infrared, record_count); } else { scene_manager_next_scene(scene_manager, InfraredSceneErrorDatabases); diff --git a/applications/main/infrared/scenes/infrared_scene_learn.c b/applications/main/infrared/scenes/infrared_scene_learn.c index 48699a71fc9..46646c6d69a 100644 --- a/applications/main/infrared/scenes/infrared_scene_learn.c +++ b/applications/main/infrared/scenes/infrared_scene_learn.c @@ -28,7 +28,7 @@ bool infrared_scene_learn_on_event(void* context, SceneManagerEvent event) { if(event.event == InfraredCustomEventTypeSignalReceived) { infrared_play_notification_message(infrared, InfraredNotificationMessageSuccess); scene_manager_next_scene(infrared->scene_manager, InfraredSceneLearnSuccess); - DOLPHIN_DEED(DolphinDeedIrLearnSuccess); + dolphin_deed(DolphinDeedIrLearnSuccess); consumed = true; } } diff --git a/applications/main/infrared/scenes/infrared_scene_learn_enter_name.c b/applications/main/infrared/scenes/infrared_scene_learn_enter_name.c index a8772a985cc..104a4cb7b65 100644 --- a/applications/main/infrared/scenes/infrared_scene_learn_enter_name.c +++ b/applications/main/infrared/scenes/infrared_scene_learn_enter_name.c @@ -50,7 +50,7 @@ bool infrared_scene_learn_enter_name_on_event(void* context, SceneManagerEvent e if(success) { scene_manager_next_scene(scene_manager, InfraredSceneLearnDone); - DOLPHIN_DEED(DolphinDeedIrSave); + dolphin_deed(DolphinDeedIrSave); } else { dialog_message_show_storage_error(infrared->dialogs, "Failed to save file"); const uint32_t possible_scenes[] = {InfraredSceneRemoteList, InfraredSceneStart}; diff --git a/applications/main/lfrfid/lfrfid.c b/applications/main/lfrfid/lfrfid.c index 85a00eea00d..edde23804c2 100644 --- a/applications/main/lfrfid/lfrfid.c +++ b/applications/main/lfrfid/lfrfid.c @@ -183,14 +183,14 @@ int32_t lfrfid_app(void* p) { view_dispatcher_attach_to_gui( app->view_dispatcher, app->gui, ViewDispatcherTypeDesktop); scene_manager_next_scene(app->scene_manager, LfRfidSceneRpc); - DOLPHIN_DEED(DolphinDeedRfidEmulate); + dolphin_deed(DolphinDeedRfidEmulate); } else { furi_string_set(app->file_path, args); lfrfid_load_key_data(app, app->file_path, true); view_dispatcher_attach_to_gui( app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); scene_manager_next_scene(app->scene_manager, LfRfidSceneEmulate); - DOLPHIN_DEED(DolphinDeedRfidEmulate); + dolphin_deed(DolphinDeedRfidEmulate); } } else { diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_extra_actions.c b/applications/main/lfrfid/scenes/lfrfid_scene_extra_actions.c index fac2ebcec54..1aed9a03c37 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_extra_actions.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_extra_actions.c @@ -58,12 +58,12 @@ bool lfrfid_scene_extra_actions_on_event(void* context, SceneManagerEvent event) if(event.event == SubmenuIndexASK) { app->read_type = LFRFIDWorkerReadTypeASKOnly; scene_manager_next_scene(app->scene_manager, LfRfidSceneRead); - DOLPHIN_DEED(DolphinDeedRfidRead); + dolphin_deed(DolphinDeedRfidRead); consumed = true; } else if(event.event == SubmenuIndexPSK) { app->read_type = LFRFIDWorkerReadTypePSKOnly; scene_manager_next_scene(app->scene_manager, LfRfidSceneRead); - DOLPHIN_DEED(DolphinDeedRfidRead); + dolphin_deed(DolphinDeedRfidRead); consumed = true; } else if(event.event == SubmenuIndexRAW) { scene_manager_next_scene(app->scene_manager, LfRfidSceneRawName); diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_read.c b/applications/main/lfrfid/scenes/lfrfid_scene_read.c index 5f19597282a..d04ce41d4a6 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_read.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_read.c @@ -81,7 +81,7 @@ bool lfrfid_scene_read_on_event(void* context, SceneManagerEvent event) { notification_message(app->notifications, &sequence_success); furi_string_reset(app->file_name); scene_manager_next_scene(app->scene_manager, LfRfidSceneReadSuccess); - DOLPHIN_DEED(DolphinDeedRfidReadSuccess); + dolphin_deed(DolphinDeedRfidReadSuccess); consumed = true; } else if(event.event == LfRfidEventReadStartPSK) { if(app->read_type == LFRFIDWorkerReadTypeAuto) { diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_read_key_menu.c b/applications/main/lfrfid/scenes/lfrfid_scene_read_key_menu.c index 081c479123f..36f0d6d93a3 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_read_key_menu.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_read_key_menu.c @@ -44,7 +44,7 @@ bool lfrfid_scene_read_key_menu_on_event(void* context, SceneManagerEvent event) consumed = true; } else if(event.event == SubmenuIndexEmulate) { scene_manager_next_scene(app->scene_manager, LfRfidSceneEmulate); - DOLPHIN_DEED(DolphinDeedRfidEmulate); + dolphin_deed(DolphinDeedRfidEmulate); consumed = true; } scene_manager_set_scene_state(app->scene_manager, LfRfidSceneReadKeyMenu, event.event); diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_save_name.c b/applications/main/lfrfid/scenes/lfrfid_scene_save_name.c index 87e110f185b..771f2f60387 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_save_name.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_save_name.c @@ -59,9 +59,9 @@ bool lfrfid_scene_save_name_on_event(void* context, SceneManagerEvent event) { if(scene_manager_has_previous_scene(scene_manager, LfRfidSceneSavedKeyMenu)) { // Nothing, do not count editing as saving } else if(scene_manager_has_previous_scene(scene_manager, LfRfidSceneSaveType)) { - DOLPHIN_DEED(DolphinDeedRfidAdd); + dolphin_deed(DolphinDeedRfidAdd); } else { - DOLPHIN_DEED(DolphinDeedRfidSave); + dolphin_deed(DolphinDeedRfidSave); } } else { scene_manager_search_and_switch_to_previous_scene( diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_saved_key_menu.c b/applications/main/lfrfid/scenes/lfrfid_scene_saved_key_menu.c index d3c3d389a8d..206074e9b08 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_saved_key_menu.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_saved_key_menu.c @@ -43,7 +43,7 @@ bool lfrfid_scene_saved_key_menu_on_event(void* context, SceneManagerEvent event if(event.type == SceneManagerEventTypeCustom) { if(event.event == SubmenuIndexEmulate) { scene_manager_next_scene(app->scene_manager, LfRfidSceneEmulate); - DOLPHIN_DEED(DolphinDeedRfidEmulate); + dolphin_deed(DolphinDeedRfidEmulate); consumed = true; } else if(event.event == SubmenuIndexWrite) { scene_manager_next_scene(app->scene_manager, LfRfidSceneWrite); diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_start.c b/applications/main/lfrfid/scenes/lfrfid_scene_start.c index 2d83ba53b3b..8a01fc707b1 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_start.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_start.c @@ -49,7 +49,7 @@ bool lfrfid_scene_start_on_event(void* context, SceneManagerEvent event) { if(event.event == SubmenuIndexRead) { scene_manager_set_scene_state(app->scene_manager, LfRfidSceneStart, SubmenuIndexRead); scene_manager_next_scene(app->scene_manager, LfRfidSceneRead); - DOLPHIN_DEED(DolphinDeedRfidRead); + dolphin_deed(DolphinDeedRfidRead); consumed = true; } else if(event.event == SubmenuIndexSaved) { // Like in the other apps, explicitly save the scene state diff --git a/applications/main/nfc/nfc.c b/applications/main/nfc/nfc.c index f68b7f2f24f..56d98a8c61a 100644 --- a/applications/main/nfc/nfc.c +++ b/applications/main/nfc/nfc.c @@ -286,18 +286,18 @@ int32_t nfc_app(void* p) { if(nfc_device_load(nfc->dev, p, true)) { if(nfc->dev->format == NfcDeviceSaveFormatMifareUl) { scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightEmulate); - DOLPHIN_DEED(DolphinDeedNfcEmulate); + dolphin_deed(DolphinDeedNfcEmulate); } else if(nfc->dev->format == NfcDeviceSaveFormatMifareClassic) { scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicEmulate); - DOLPHIN_DEED(DolphinDeedNfcEmulate); + dolphin_deed(DolphinDeedNfcEmulate); } else if(nfc->dev->format == NfcDeviceSaveFormatNfcV) { scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcVEmulate); - DOLPHIN_DEED(DolphinDeedNfcEmulate); + dolphin_deed(DolphinDeedNfcEmulate); } else if(nfc->dev->format == NfcDeviceSaveFormatBankCard) { scene_manager_next_scene(nfc->scene_manager, NfcSceneDeviceInfo); } else { scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateUid); - DOLPHIN_DEED(DolphinDeedNfcEmulate); + dolphin_deed(DolphinDeedNfcEmulate); } } else { // Exit app diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c index cb2f3a82d9e..5bd24d7eac1 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c @@ -111,7 +111,7 @@ bool nfc_scene_mf_classic_dict_attack_on_event(void* context, SceneManagerEvent } else { notification_message(nfc->notifications, &sequence_success); scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicReadSuccess); - DOLPHIN_DEED(DolphinDeedNfcReadSuccess); + dolphin_deed(DolphinDeedNfcReadSuccess); consumed = true; } } else if(event.event == NfcWorkerEventAborted) { @@ -123,7 +123,7 @@ bool nfc_scene_mf_classic_dict_attack_on_event(void* context, SceneManagerEvent notification_message(nfc->notifications, &sequence_success); scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicReadSuccess); // Counting failed attempts too - DOLPHIN_DEED(DolphinDeedNfcReadSuccess); + dolphin_deed(DolphinDeedNfcReadSuccess); consumed = true; } } else if(event.event == NfcWorkerEventCardDetected) { diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_add.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_add.c index b122aa225b9..3a999f03114 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_add.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_add.c @@ -37,7 +37,7 @@ bool nfc_scene_mf_classic_keys_add_on_event(void* context, SceneManagerEvent eve nfc->scene_manager, NfcSceneMfClassicKeysWarnDuplicate); } else if(mf_classic_dict_add_key(dict, nfc->byte_input_store)) { scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveSuccess); - DOLPHIN_DEED(DolphinDeedNfcMfcAdd); + dolphin_deed(DolphinDeedNfcMfcAdd); } else { scene_manager_next_scene(nfc->scene_manager, NfcSceneDictNotFound); } diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_menu.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_menu.c index 67b2a85309e..9c416367646 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_menu.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_menu.c @@ -54,14 +54,14 @@ bool nfc_scene_mf_classic_menu_on_event(void* context, SceneManagerEvent event) } else if(event.event == SubmenuIndexEmulate) { scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicEmulate); if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSetType)) { - DOLPHIN_DEED(DolphinDeedNfcAddEmulate); + dolphin_deed(DolphinDeedNfcAddEmulate); } else { - DOLPHIN_DEED(DolphinDeedNfcEmulate); + dolphin_deed(DolphinDeedNfcEmulate); } consumed = true; } else if(event.event == SubmenuIndexDetectReader) { scene_manager_next_scene(nfc->scene_manager, NfcSceneDetectReader); - DOLPHIN_DEED(DolphinDeedNfcDetectReader); + dolphin_deed(DolphinDeedNfcDetectReader); consumed = true; } else if(event.event == SubmenuIndexInfo) { scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcDataInfo); diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_update.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_update.c index aacf77f773f..ffef1b7b9f0 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_update.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_update.c @@ -34,7 +34,7 @@ static void nfc_scene_mf_classic_update_setup_view(Nfc* nfc) { void nfc_scene_mf_classic_update_on_enter(void* context) { Nfc* nfc = context; - DOLPHIN_DEED(DolphinDeedNfcEmulate); + dolphin_deed(DolphinDeedNfcEmulate); scene_manager_set_scene_state( nfc->scene_manager, NfcSceneMfClassicUpdate, NfcSceneMfClassicUpdateStateCardSearch); diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_update_success.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_update_success.c index fef8fd5e932..fb1868459d4 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_update_success.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_update_success.c @@ -8,7 +8,7 @@ void nfc_scene_mf_classic_update_success_popup_callback(void* context) { void nfc_scene_mf_classic_update_success_on_enter(void* context) { Nfc* nfc = context; - DOLPHIN_DEED(DolphinDeedNfcSave); + dolphin_deed(DolphinDeedNfcSave); notification_message(nfc->notifications, &sequence_success); diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_write.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_write.c index 3543cbc5889..20ebfcc70a2 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_write.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_write.c @@ -34,7 +34,7 @@ static void nfc_scene_mf_classic_write_setup_view(Nfc* nfc) { void nfc_scene_mf_classic_write_on_enter(void* context) { Nfc* nfc = context; - DOLPHIN_DEED(DolphinDeedNfcEmulate); + dolphin_deed(DolphinDeedNfcEmulate); scene_manager_set_scene_state( nfc->scene_manager, NfcSceneMfClassicWrite, NfcSceneMfClassicWriteStateCardSearch); diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_write_success.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_write_success.c index 2f2a3beb119..00030d4fe8c 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_write_success.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_write_success.c @@ -8,7 +8,7 @@ void nfc_scene_mf_classic_write_success_popup_callback(void* context) { void nfc_scene_mf_classic_write_success_on_enter(void* context) { Nfc* nfc = context; - DOLPHIN_DEED(DolphinDeedNfcSave); + dolphin_deed(DolphinDeedNfcSave); notification_message(nfc->notifications, &sequence_success); diff --git a/applications/main/nfc/scenes/nfc_scene_mf_desfire_menu.c b/applications/main/nfc/scenes/nfc_scene_mf_desfire_menu.c index bee63d775bd..9cebefedfa1 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_desfire_menu.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_desfire_menu.c @@ -50,9 +50,9 @@ bool nfc_scene_mf_desfire_menu_on_event(void* context, SceneManagerEvent event) } else if(event.event == SubmenuIndexEmulateUid) { scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateUid); if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSetType)) { - DOLPHIN_DEED(DolphinDeedNfcAddEmulate); + dolphin_deed(DolphinDeedNfcAddEmulate); } else { - DOLPHIN_DEED(DolphinDeedNfcEmulate); + dolphin_deed(DolphinDeedNfcEmulate); } consumed = true; } else if(event.event == SubmenuIndexInfo) { diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_menu.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_menu.c index e7a494d273a..b3bd780f4b4 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_menu.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_menu.c @@ -60,9 +60,9 @@ bool nfc_scene_mf_ultralight_menu_on_event(void* context, SceneManagerEvent even } else if(event.event == SubmenuIndexEmulate) { scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightEmulate); if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSetType)) { - DOLPHIN_DEED(DolphinDeedNfcAddEmulate); + dolphin_deed(DolphinDeedNfcAddEmulate); } else { - DOLPHIN_DEED(DolphinDeedNfcEmulate); + dolphin_deed(DolphinDeedNfcEmulate); } consumed = true; } else if(event.event == SubmenuIndexUnlock) { diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_warn.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_warn.c index 16efae9deae..af2eca0ce5d 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_warn.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_warn.c @@ -61,7 +61,7 @@ bool nfc_scene_mf_ultralight_unlock_warn_on_event(void* context, SceneManagerEve if(event.type == SceneManagerEventTypeCustom) { if(event.event == DialogExResultRight) { scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightReadAuth); - DOLPHIN_DEED(DolphinDeedNfcRead); + dolphin_deed(DolphinDeedNfcRead); consumed = true; } else if(event.event == DialogExResultLeft) { if(auth_method == MfUltralightAuthMethodAuto) { @@ -79,7 +79,7 @@ bool nfc_scene_mf_ultralight_unlock_warn_on_event(void* context, SceneManagerEve if(event.type == SceneManagerEventTypeCustom) { if(event.event == DialogExResultCenter) { scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightReadAuth); - DOLPHIN_DEED(DolphinDeedNfcRead); + dolphin_deed(DolphinDeedNfcRead); consumed = true; } } diff --git a/applications/main/nfc/scenes/nfc_scene_nfca_menu.c b/applications/main/nfc/scenes/nfc_scene_nfca_menu.c index 30f63945c62..9779470a387 100644 --- a/applications/main/nfc/scenes/nfc_scene_nfca_menu.c +++ b/applications/main/nfc/scenes/nfc_scene_nfca_menu.c @@ -43,9 +43,9 @@ bool nfc_scene_nfca_menu_on_event(void* context, SceneManagerEvent event) { } else if(event.event == SubmenuIndexEmulateUid) { scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateUid); if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSetType)) { - DOLPHIN_DEED(DolphinDeedNfcAddEmulate); + dolphin_deed(DolphinDeedNfcAddEmulate); } else { - DOLPHIN_DEED(DolphinDeedNfcEmulate); + dolphin_deed(DolphinDeedNfcEmulate); } consumed = true; } else if(event.event == SubmenuIndexInfo) { diff --git a/applications/main/nfc/scenes/nfc_scene_nfcv_key_input.c b/applications/main/nfc/scenes/nfc_scene_nfcv_key_input.c index cc53c4dcb43..13d903c4b77 100644 --- a/applications/main/nfc/scenes/nfc_scene_nfcv_key_input.c +++ b/applications/main/nfc/scenes/nfc_scene_nfcv_key_input.c @@ -32,7 +32,7 @@ bool nfc_scene_nfcv_key_input_on_event(void* context, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { if(event.event == NfcCustomEventByteInputDone) { scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcVUnlock); - DOLPHIN_DEED(DolphinDeedNfcRead); + dolphin_deed(DolphinDeedNfcRead); consumed = true; } } diff --git a/applications/main/nfc/scenes/nfc_scene_nfcv_menu.c b/applications/main/nfc/scenes/nfc_scene_nfcv_menu.c index 7c6780b7c76..60eb354e858 100644 --- a/applications/main/nfc/scenes/nfc_scene_nfcv_menu.c +++ b/applications/main/nfc/scenes/nfc_scene_nfcv_menu.c @@ -42,9 +42,9 @@ bool nfc_scene_nfcv_menu_on_event(void* context, SceneManagerEvent event) { } else if(event.event == SubmenuIndexEmulate) { scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcVEmulate); if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSetType)) { - DOLPHIN_DEED(DolphinDeedNfcAddEmulate); + dolphin_deed(DolphinDeedNfcAddEmulate); } else { - DOLPHIN_DEED(DolphinDeedNfcEmulate); + dolphin_deed(DolphinDeedNfcEmulate); } consumed = true; } else if(event.event == SubmenuIndexInfo) { diff --git a/applications/main/nfc/scenes/nfc_scene_nfcv_unlock.c b/applications/main/nfc/scenes/nfc_scene_nfcv_unlock.c index 26de304de54..38d7ad563d8 100644 --- a/applications/main/nfc/scenes/nfc_scene_nfcv_unlock.c +++ b/applications/main/nfc/scenes/nfc_scene_nfcv_unlock.c @@ -76,7 +76,7 @@ void nfc_scene_nfcv_unlock_set_state(Nfc* nfc, NfcSceneNfcVUnlockState state) { popup_set_timeout(popup, 1500); view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); - DOLPHIN_DEED(DolphinDeedNfcReadSuccess); + dolphin_deed(DolphinDeedNfcReadSuccess); } else if(state == NfcSceneNfcVUnlockStateAlreadyUnlocked) { popup_reset(popup); diff --git a/applications/main/nfc/scenes/nfc_scene_nfcv_unlock_menu.c b/applications/main/nfc/scenes/nfc_scene_nfcv_unlock_menu.c index 9c4c81fbda6..2f736725674 100644 --- a/applications/main/nfc/scenes/nfc_scene_nfcv_unlock_menu.c +++ b/applications/main/nfc/scenes/nfc_scene_nfcv_unlock_menu.c @@ -45,7 +45,7 @@ bool nfc_scene_nfcv_unlock_menu_on_event(void* context, SceneManagerEvent event) } else if(event.event == SubmenuIndexNfcVUnlockMenuTonieBox) { nfc->dev->dev_data.nfcv_data.auth_method = NfcVAuthMethodTonieBox; scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcVUnlock); - DOLPHIN_DEED(DolphinDeedNfcRead); + dolphin_deed(DolphinDeedNfcRead); consumed = true; } scene_manager_set_scene_state(nfc->scene_manager, NfcSceneNfcVUnlockMenu, event.event); diff --git a/applications/main/nfc/scenes/nfc_scene_read.c b/applications/main/nfc/scenes/nfc_scene_read.c index 5a1814b6c6d..1690a955757 100644 --- a/applications/main/nfc/scenes/nfc_scene_read.c +++ b/applications/main/nfc/scenes/nfc_scene_read.c @@ -61,34 +61,34 @@ bool nfc_scene_read_on_event(void* context, SceneManagerEvent event) { (event.event == NfcWorkerEventReadUidNfcV)) { notification_message(nfc->notifications, &sequence_success); scene_manager_next_scene(nfc->scene_manager, NfcSceneReadCardSuccess); - DOLPHIN_DEED(DolphinDeedNfcReadSuccess); + dolphin_deed(DolphinDeedNfcReadSuccess); consumed = true; } else if(event.event == NfcWorkerEventReadUidNfcA) { notification_message(nfc->notifications, &sequence_success); scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcaReadSuccess); - DOLPHIN_DEED(DolphinDeedNfcReadSuccess); + dolphin_deed(DolphinDeedNfcReadSuccess); consumed = true; } else if(event.event == NfcWorkerEventReadNfcV) { notification_message(nfc->notifications, &sequence_success); scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcVReadSuccess); - DOLPHIN_DEED(DolphinDeedNfcReadSuccess); + dolphin_deed(DolphinDeedNfcReadSuccess); consumed = true; } else if(event.event == NfcWorkerEventReadMfUltralight) { notification_message(nfc->notifications, &sequence_success); // Set unlock password input to 0xFFFFFFFF only on fresh read memset(nfc->byte_input_store, 0xFF, sizeof(nfc->byte_input_store)); scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightReadSuccess); - DOLPHIN_DEED(DolphinDeedNfcReadSuccess); + dolphin_deed(DolphinDeedNfcReadSuccess); consumed = true; } else if(event.event == NfcWorkerEventReadMfClassicDone) { notification_message(nfc->notifications, &sequence_success); scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicReadSuccess); - DOLPHIN_DEED(DolphinDeedNfcReadSuccess); + dolphin_deed(DolphinDeedNfcReadSuccess); consumed = true; } else if(event.event == NfcWorkerEventReadMfDesfire) { notification_message(nfc->notifications, &sequence_success); scene_manager_next_scene(nfc->scene_manager, NfcSceneMfDesfireReadSuccess); - DOLPHIN_DEED(DolphinDeedNfcReadSuccess); + dolphin_deed(DolphinDeedNfcReadSuccess); consumed = true; } else if(event.event == NfcWorkerEventReadMfClassicDictAttackRequired) { if(mf_classic_dict_check_presence(MfClassicDictTypeSystem)) { diff --git a/applications/main/nfc/scenes/nfc_scene_save_name.c b/applications/main/nfc/scenes/nfc_scene_save_name.c index 00727422676..a7b97aac06b 100644 --- a/applications/main/nfc/scenes/nfc_scene_save_name.c +++ b/applications/main/nfc/scenes/nfc_scene_save_name.c @@ -67,9 +67,9 @@ bool nfc_scene_save_name_on_event(void* context, SceneManagerEvent event) { if(!scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSavedMenu)) { // Nothing, do not count editing as saving } else if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSetType)) { - DOLPHIN_DEED(DolphinDeedNfcAddSave); + dolphin_deed(DolphinDeedNfcAddSave); } else { - DOLPHIN_DEED(DolphinDeedNfcSave); + dolphin_deed(DolphinDeedNfcSave); } consumed = true; } else { diff --git a/applications/main/nfc/scenes/nfc_scene_saved_menu.c b/applications/main/nfc/scenes/nfc_scene_saved_menu.c index 8412c17bcb7..b3205554a43 100644 --- a/applications/main/nfc/scenes/nfc_scene_saved_menu.c +++ b/applications/main/nfc/scenes/nfc_scene_saved_menu.c @@ -124,11 +124,11 @@ bool nfc_scene_saved_menu_on_event(void* context, SceneManagerEvent event) { } else { scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateUid); } - DOLPHIN_DEED(DolphinDeedNfcEmulate); + dolphin_deed(DolphinDeedNfcEmulate); consumed = true; } else if(event.event == SubmenuIndexDetectReader) { scene_manager_next_scene(nfc->scene_manager, NfcSceneDetectReader); - DOLPHIN_DEED(DolphinDeedNfcDetectReader); + dolphin_deed(DolphinDeedNfcDetectReader); consumed = true; } else if(event.event == SubmenuIndexWrite) { scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicWrite); diff --git a/applications/main/nfc/scenes/nfc_scene_start.c b/applications/main/nfc/scenes/nfc_scene_start.c index a01f871ab6a..c9e8bf78cf5 100644 --- a/applications/main/nfc/scenes/nfc_scene_start.c +++ b/applications/main/nfc/scenes/nfc_scene_start.c @@ -51,7 +51,7 @@ bool nfc_scene_start_on_event(void* context, SceneManagerEvent event) { scene_manager_set_scene_state(nfc->scene_manager, NfcSceneStart, SubmenuIndexRead); nfc->dev->dev_data.read_mode = NfcReadModeAuto; scene_manager_next_scene(nfc->scene_manager, NfcSceneRead); - DOLPHIN_DEED(DolphinDeedNfcRead); + dolphin_deed(DolphinDeedNfcRead); consumed = true; } else if(event.event == SubmenuIndexDetectReader) { scene_manager_set_scene_state( @@ -60,7 +60,7 @@ bool nfc_scene_start_on_event(void* context, SceneManagerEvent event) { if(sd_exist) { nfc_device_data_clear(&nfc->dev->dev_data); scene_manager_next_scene(nfc->scene_manager, NfcSceneDetectReader); - DOLPHIN_DEED(DolphinDeedNfcDetectReader); + dolphin_deed(DolphinDeedNfcDetectReader); } else { scene_manager_next_scene(nfc->scene_manager, NfcSceneDictNotFound); } diff --git a/applications/main/subghz/scenes/subghz_scene_read_raw.c b/applications/main/subghz/scenes/subghz_scene_read_raw.c index 6e576a86183..a29f86a078c 100644 --- a/applications/main/subghz/scenes/subghz_scene_read_raw.c +++ b/applications/main/subghz/scenes/subghz_scene_read_raw.c @@ -204,7 +204,7 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { } else { if(scene_manager_has_previous_scene(subghz->scene_manager, SubGhzSceneSaved) || !scene_manager_has_previous_scene(subghz->scene_manager, SubGhzSceneStart)) { - DOLPHIN_DEED(DolphinDeedSubGhzSend); + dolphin_deed(DolphinDeedSubGhzSend); } // set callback end tx subghz_txrx_set_raw_file_encoder_worker_callback_end( @@ -259,7 +259,7 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { } else { SubGhzRadioPreset preset = subghz_txrx_get_preset(subghz->txrx); if(subghz_protocol_raw_save_to_file_init(decoder_raw, RAW_FILE_NAME, &preset)) { - DOLPHIN_DEED(DolphinDeedSubGhzRawRec); + dolphin_deed(DolphinDeedSubGhzRawRec); subghz_txrx_rx_start(subghz->txrx); subghz->state_notifications = SubGhzNotificationStateRx; subghz_rx_key_state_set(subghz, SubGhzRxKeyStateAddKey); diff --git a/applications/main/subghz/scenes/subghz_scene_receiver.c b/applications/main/subghz/scenes/subghz_scene_receiver.c index dcc22b91cc7..6771f8213b5 100644 --- a/applications/main/subghz/scenes/subghz_scene_receiver.c +++ b/applications/main/subghz/scenes/subghz_scene_receiver.c @@ -163,7 +163,7 @@ bool subghz_scene_receiver_on_event(void* context, SceneManagerEvent event) { case SubGhzCustomEventViewReceiverOK: subghz->idx_menu_chosen = subghz_view_receiver_get_idx_menu(subghz->subghz_receiver); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReceiverInfo); - DOLPHIN_DEED(DolphinDeedSubGhzReceiverInfo); + dolphin_deed(DolphinDeedSubGhzReceiverInfo); consumed = true; break; case SubGhzCustomEventViewReceiverConfig: diff --git a/applications/main/subghz/scenes/subghz_scene_save_name.c b/applications/main/subghz/scenes/subghz_scene_save_name.c index 2a292a1ef39..7d0a4f4f8b7 100644 --- a/applications/main/subghz/scenes/subghz_scene_save_name.c +++ b/applications/main/subghz/scenes/subghz_scene_save_name.c @@ -137,9 +137,9 @@ bool subghz_scene_save_name_on_event(void* context, SceneManagerEvent event) { // Ditto, for RAW signals } else if(scene_manager_has_previous_scene( subghz->scene_manager, SubGhzSceneSetType)) { - DOLPHIN_DEED(DolphinDeedSubGhzAddManually); + dolphin_deed(DolphinDeedSubGhzAddManually); } else { - DOLPHIN_DEED(DolphinDeedSubGhzSave); + dolphin_deed(DolphinDeedSubGhzSave); } return true; } else { diff --git a/applications/main/subghz/scenes/subghz_scene_start.c b/applications/main/subghz/scenes/subghz_scene_start.c index a41e4b06f4a..0ab5f123ed1 100644 --- a/applications/main/subghz/scenes/subghz_scene_start.c +++ b/applications/main/subghz/scenes/subghz_scene_start.c @@ -92,7 +92,7 @@ bool subghz_scene_start_on_event(void* context, SceneManagerEvent event) { scene_manager_set_scene_state( subghz->scene_manager, SubGhzSceneStart, SubmenuIndexFrequencyAnalyzer); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneFrequencyAnalyzer); - DOLPHIN_DEED(DolphinDeedSubGhzFrequencyAnalyzer); + dolphin_deed(DolphinDeedSubGhzFrequencyAnalyzer); return true; } else if(event.event == SubmenuIndexTest) { scene_manager_set_scene_state( diff --git a/applications/main/subghz/scenes/subghz_scene_transmitter.c b/applications/main/subghz/scenes/subghz_scene_transmitter.c index 1c193c1794d..274dd61ad37 100644 --- a/applications/main/subghz/scenes/subghz_scene_transmitter.c +++ b/applications/main/subghz/scenes/subghz_scene_transmitter.c @@ -61,7 +61,7 @@ bool subghz_scene_transmitter_on_event(void* context, SceneManagerEvent event) { if(subghz_tx_start(subghz, subghz_txrx_get_fff_data(subghz->txrx))) { subghz->state_notifications = SubGhzNotificationStateTx; subghz_scene_transmitter_update_data_show(subghz); - DOLPHIN_DEED(DolphinDeedSubGhzSend); + dolphin_deed(DolphinDeedSubGhzSend); } return true; } else if(event.event == SubGhzCustomEventViewTransmitterSendStop) { diff --git a/applications/main/u2f/scenes/u2f_scene_main.c b/applications/main/u2f/scenes/u2f_scene_main.c index 251bc4d991a..992236e7a8b 100644 --- a/applications/main/u2f/scenes/u2f_scene_main.c +++ b/applications/main/u2f/scenes/u2f_scene_main.c @@ -68,7 +68,7 @@ bool u2f_scene_main_on_event(void* context, SceneManagerEvent event) { notification_message(app->notifications, &sequence_blink_magenta_10); } else if(event.event == U2fCustomEventAuthSuccess) { notification_message_block(app->notifications, &sequence_set_green_255); - DOLPHIN_DEED(DolphinDeedU2fAuthorized); + dolphin_deed(DolphinDeedU2fAuthorized); furi_timer_start(app->timer, U2F_SUCCESS_TIMEOUT); app->event_cur = U2fCustomEventNone; u2f_view_set_state(app->u2f_view, U2fMsgSuccess); diff --git a/applications/services/desktop/scenes/desktop_scene_debug.c b/applications/services/desktop/scenes/desktop_scene_debug.c index e79c56e111a..a5bd3a6b1e6 100644 --- a/applications/services/desktop/scenes/desktop_scene_debug.c +++ b/applications/services/desktop/scenes/desktop_scene_debug.c @@ -34,13 +34,13 @@ bool desktop_scene_debug_on_event(void* context, SceneManagerEvent event) { break; case DesktopDebugEventDeed: - dolphin_deed(dolphin, DolphinDeedTestRight); + dolphin_deed(DolphinDeedTestRight); desktop_debug_get_dolphin_data(desktop->debug_view); consumed = true; break; case DesktopDebugEventWrongDeed: - dolphin_deed(dolphin, DolphinDeedTestLeft); + dolphin_deed(DolphinDeedTestLeft); desktop_debug_get_dolphin_data(desktop->debug_view); consumed = true; break; diff --git a/applications/services/dolphin/dolphin.c b/applications/services/dolphin/dolphin.c index 93a9b3095b8..579b400ad01 100644 --- a/applications/services/dolphin/dolphin.c +++ b/applications/services/dolphin/dolphin.c @@ -13,12 +13,13 @@ static void dolphin_update_clear_limits_timer_period(Dolphin* dolphin); -void dolphin_deed(Dolphin* dolphin, DolphinDeed deed) { - furi_assert(dolphin); +void dolphin_deed(DolphinDeed deed) { + Dolphin* dolphin = (Dolphin*)furi_record_open(RECORD_DOLPHIN); DolphinEvent event; event.type = DolphinEventTypeDeed; event.deed = deed; dolphin_event_send_async(dolphin, &event); + furi_record_close(RECORD_DOLPHIN); } DolphinStats dolphin_stats(Dolphin* dolphin) { diff --git a/applications/services/dolphin/dolphin.h b/applications/services/dolphin/dolphin.h index 8757e2a3773..1035247e718 100644 --- a/applications/services/dolphin/dolphin.h +++ b/applications/services/dolphin/dolphin.h @@ -26,18 +26,11 @@ typedef enum { DolphinPubsubEventUpdate, } DolphinPubsubEvent; -#define DOLPHIN_DEED(deed) \ - do { \ - Dolphin* dolphin = (Dolphin*)furi_record_open("dolphin"); \ - dolphin_deed(dolphin, deed); \ - furi_record_close("dolphin"); \ - } while(0) - /** Deed complete notification. Call it on deed completion. * See dolphin_deed.h for available deeds. In futures it will become part of assets. * Thread safe, async */ -void dolphin_deed(Dolphin* dolphin, DolphinDeed deed); +void dolphin_deed(DolphinDeed deed); /** Retrieve dolphin stats * Thread safe, blocking diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index f551a09c159..85f09f1c3ca 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,29.0,, +Version,+,30.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -648,7 +648,7 @@ Function,+,dir_walk_read,DirWalkResult,"DirWalk*, FuriString*, FileInfo*" Function,+,dir_walk_set_filter_cb,void,"DirWalk*, DirWalkFilterCb, void*" Function,+,dir_walk_set_recursive,void,"DirWalk*, _Bool" Function,-,div,div_t,"int, int" -Function,+,dolphin_deed,void,"Dolphin*, DolphinDeed" +Function,+,dolphin_deed,void,DolphinDeed Function,+,dolphin_deed_get_app,DolphinApp,DolphinDeed Function,+,dolphin_deed_get_app_limit,uint8_t,DolphinApp Function,+,dolphin_deed_get_weight,uint8_t,DolphinDeed diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index e2de368363d..afa76163272 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,29.0,, +Version,+,30.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -775,7 +775,7 @@ Function,+,dir_walk_read,DirWalkResult,"DirWalk*, FuriString*, FileInfo*" Function,+,dir_walk_set_filter_cb,void,"DirWalk*, DirWalkFilterCb, void*" Function,+,dir_walk_set_recursive,void,"DirWalk*, _Bool" Function,-,div,div_t,"int, int" -Function,+,dolphin_deed,void,"Dolphin*, DolphinDeed" +Function,+,dolphin_deed,void,DolphinDeed Function,+,dolphin_deed_get_app,DolphinApp,DolphinDeed Function,+,dolphin_deed_get_app_limit,uint8_t,DolphinApp Function,+,dolphin_deed_get_weight,uint8_t,DolphinDeed From 49d842e213f2a85be759bc4e5234281cbadf163f Mon Sep 17 00:00:00 2001 From: clashlab Date: Fri, 9 Jun 2023 14:18:32 +0200 Subject: [PATCH 609/824] weather_station: add oregon3 with THGR221 (#2748) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: hedger Co-authored-by: あく --- .../weather_station/protocols/oregon3.c | 365 ++++++++++++++++++ .../weather_station/protocols/oregon3.h | 6 + .../protocols/protocol_items.c | 3 +- .../protocols/protocol_items.h | 1 + 4 files changed, 374 insertions(+), 1 deletion(-) create mode 100644 applications/external/weather_station/protocols/oregon3.c create mode 100644 applications/external/weather_station/protocols/oregon3.h diff --git a/applications/external/weather_station/protocols/oregon3.c b/applications/external/weather_station/protocols/oregon3.c new file mode 100644 index 00000000000..a211c5ad326 --- /dev/null +++ b/applications/external/weather_station/protocols/oregon3.c @@ -0,0 +1,365 @@ +#include "oregon3.h" + +#include +#include +#include +#include +#include "ws_generic.h" + +#include +#include + +#define TAG "WSProtocolOregon3" + +static const SubGhzBlockConst ws_oregon3_const = { + .te_long = 1100, + .te_short = 500, + .te_delta = 300, + .min_count_bit_for_found = 32, +}; + +#define OREGON3_PREAMBLE_BITS 28 +#define OREGON3_PREAMBLE_MASK 0b1111111111111111111111111111 +// 24 ones + 0101 (inverted A) +#define OREGON3_PREAMBLE 0b1111111111111111111111110101 + +// Fixed part contains: +// - Sensor type: 16 bits +// - Channel: 4 bits +// - ID (changes when batteries are changed): 8 bits +// - Battery status: 4 bits +#define OREGON3_FIXED_PART_BITS (16 + 4 + 8 + 4) +#define OREGON3_SENSOR_ID(d) (((d) >> 16) & 0xFFFF) +#define OREGON3_CHECKSUM_BITS 8 + +// bit indicating the low battery +#define OREGON3_FLAG_BAT_LOW 0x4 + +/// Documentation for Oregon Scientific protocols can be found here: +/// https://www.osengr.org/Articles/OS-RF-Protocols-IV.pdf +// Sensors ID +#define ID_THGR221 0xf824 + +struct WSProtocolDecoderOregon3 { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + WSBlockGeneric generic; + ManchesterState manchester_state; + bool prev_bit; + + uint8_t var_bits; + uint64_t var_data; +}; + +typedef struct WSProtocolDecoderOregon3 WSProtocolDecoderOregon3; + +typedef enum { + Oregon3DecoderStepReset = 0, + Oregon3DecoderStepFoundPreamble, + Oregon3DecoderStepVarData, +} Oregon3DecoderStep; + +void* ws_protocol_decoder_oregon3_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + WSProtocolDecoderOregon3* instance = malloc(sizeof(WSProtocolDecoderOregon3)); + instance->base.protocol = &ws_protocol_oregon3; + instance->generic.protocol_name = instance->base.protocol->name; + instance->generic.humidity = WS_NO_HUMIDITY; + instance->generic.temp = WS_NO_TEMPERATURE; + instance->generic.btn = WS_NO_BTN; + instance->generic.channel = WS_NO_CHANNEL; + instance->generic.battery_low = WS_NO_BATT; + instance->generic.id = WS_NO_ID; + instance->prev_bit = false; + return instance; +} + +void ws_protocol_decoder_oregon3_free(void* context) { + furi_assert(context); + WSProtocolDecoderOregon3* instance = context; + free(instance); +} + +void ws_protocol_decoder_oregon3_reset(void* context) { + furi_assert(context); + WSProtocolDecoderOregon3* instance = context; + instance->decoder.parser_step = Oregon3DecoderStepReset; + instance->decoder.decode_data = 0UL; + instance->decoder.decode_count_bit = 0; + manchester_advance( + instance->manchester_state, ManchesterEventReset, &instance->manchester_state, NULL); + instance->prev_bit = false; + instance->var_data = 0; + instance->var_bits = 0; +} + +static ManchesterEvent level_and_duration_to_event(bool level, uint32_t duration) { + bool is_long = false; + + if(DURATION_DIFF(duration, ws_oregon3_const.te_long) < ws_oregon3_const.te_delta) { + is_long = true; + } else if(DURATION_DIFF(duration, ws_oregon3_const.te_short) < ws_oregon3_const.te_delta) { + is_long = false; + } else { + return ManchesterEventReset; + } + + if(level) + return is_long ? ManchesterEventLongHigh : ManchesterEventShortHigh; + else + return is_long ? ManchesterEventLongLow : ManchesterEventShortLow; +} + +// From sensor id code return amount of bits in variable section +// https://temofeev.ru/info/articles/o-dekodirovanii-protokola-pogodnykh-datchikov-oregon-scientific +static uint8_t oregon3_sensor_id_var_bits(uint16_t sensor_id) { + switch(sensor_id) { + case ID_THGR221: + default: + // nibbles: temp + hum + '0' + return (4 + 2 + 1) * 4; + } +} + +static void ws_oregon3_decode_const_data(WSBlockGeneric* ws_block) { + ws_block->id = OREGON3_SENSOR_ID(ws_block->data); + ws_block->channel = (ws_block->data >> 12) & 0xF; + ws_block->battery_low = (ws_block->data & OREGON3_FLAG_BAT_LOW) ? 1 : 0; +} + +static uint16_t ws_oregon3_bcd_decode_short(uint32_t data) { + return (data & 0xF) * 10 + ((data >> 4) & 0xF); +} + +static float ws_oregon3_decode_temp(uint32_t data) { + int32_t temp_val; + temp_val = ws_oregon3_bcd_decode_short(data >> 4); + temp_val *= 10; + temp_val += (data >> 12) & 0xF; + if(data & 0xF) temp_val = -temp_val; + return (float)temp_val / 10.0; +} + +static void ws_oregon3_decode_var_data(WSBlockGeneric* ws_b, uint16_t sensor_id, uint32_t data) { + switch(sensor_id) { + case ID_THGR221: + default: + ws_b->humidity = ws_oregon3_bcd_decode_short(data >> 4); + ws_b->temp = ws_oregon3_decode_temp(data >> 12); + break; + } +} + +void ws_protocol_decoder_oregon3_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + WSProtocolDecoderOregon3* instance = context; + // Oregon v3.0 protocol is inverted + ManchesterEvent event = level_and_duration_to_event(!level, duration); + + // low-level bit sequence decoding + if(event == ManchesterEventReset) { + instance->decoder.parser_step = Oregon3DecoderStepReset; + instance->prev_bit = false; + instance->decoder.decode_data = 0UL; + instance->decoder.decode_count_bit = 0; + } + if(manchester_advance( + instance->manchester_state, event, &instance->manchester_state, &instance->prev_bit)) { + subghz_protocol_blocks_add_bit(&instance->decoder, instance->prev_bit); + } + + switch(instance->decoder.parser_step) { + case Oregon3DecoderStepReset: + // waiting for fixed oregon3 preamble + if(instance->decoder.decode_count_bit >= OREGON3_PREAMBLE_BITS && + ((instance->decoder.decode_data & OREGON3_PREAMBLE_MASK) == OREGON3_PREAMBLE)) { + instance->decoder.parser_step = Oregon3DecoderStepFoundPreamble; + instance->decoder.decode_count_bit = 0; + instance->decoder.decode_data = 0UL; + } + break; + case Oregon3DecoderStepFoundPreamble: + // waiting for fixed oregon3 data + if(instance->decoder.decode_count_bit == OREGON3_FIXED_PART_BITS) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + instance->decoder.decode_data = 0UL; + instance->decoder.decode_count_bit = 0; + + // reverse nibbles in decoded data as oregon v3.0 is LSB first + instance->generic.data = (instance->generic.data & 0x55555555) << 1 | + (instance->generic.data & 0xAAAAAAAA) >> 1; + instance->generic.data = (instance->generic.data & 0x33333333) << 2 | + (instance->generic.data & 0xCCCCCCCC) >> 2; + + ws_oregon3_decode_const_data(&instance->generic); + instance->var_bits = + oregon3_sensor_id_var_bits(OREGON3_SENSOR_ID(instance->generic.data)); + + if(!instance->var_bits) { + // sensor is not supported, stop decoding, but showing the decoded fixed part + instance->decoder.parser_step = Oregon3DecoderStepReset; + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } else { + instance->decoder.parser_step = Oregon3DecoderStepVarData; + } + } + break; + case Oregon3DecoderStepVarData: + // waiting for variable (sensor-specific data) + if(instance->decoder.decode_count_bit == instance->var_bits + OREGON3_CHECKSUM_BITS) { + instance->var_data = instance->decoder.decode_data & 0xFFFFFFFFFFFFFFFF; + + // reverse nibbles in var data + instance->var_data = (instance->var_data & 0x5555555555555555) << 1 | + (instance->var_data & 0xAAAAAAAAAAAAAAAA) >> 1; + instance->var_data = (instance->var_data & 0x3333333333333333) << 2 | + (instance->var_data & 0xCCCCCCCCCCCCCCCC) >> 2; + + ws_oregon3_decode_var_data( + &instance->generic, + OREGON3_SENSOR_ID(instance->generic.data), + instance->var_data >> OREGON3_CHECKSUM_BITS); + + instance->decoder.parser_step = Oregon3DecoderStepReset; + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + break; + } +} + +uint8_t ws_protocol_decoder_oregon3_get_hash_data(void* context) { + furi_assert(context); + WSProtocolDecoderOregon3* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +SubGhzProtocolStatus ws_protocol_decoder_oregon3_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + WSProtocolDecoderOregon3* instance = context; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; + ret = ws_block_generic_serialize(&instance->generic, flipper_format, preset); + if(ret != SubGhzProtocolStatusOk) return ret; + uint32_t temp = instance->var_bits; + if(!flipper_format_write_uint32(flipper_format, "VarBits", &temp, 1)) { + FURI_LOG_E(TAG, "Error adding VarBits"); + return SubGhzProtocolStatusErrorParserOthers; + } + if(!flipper_format_write_hex( + flipper_format, + "VarData", + (const uint8_t*)&instance->var_data, + sizeof(instance->var_data))) { + FURI_LOG_E(TAG, "Error adding VarData"); + return SubGhzProtocolStatusErrorParserOthers; + } + return ret; +} + +SubGhzProtocolStatus + ws_protocol_decoder_oregon3_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + WSProtocolDecoderOregon3* instance = context; + uint32_t temp_data; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; + do { + ret = ws_block_generic_deserialize(&instance->generic, flipper_format); + if(ret != SubGhzProtocolStatusOk) { + break; + } + if(!flipper_format_read_uint32(flipper_format, "VarBits", &temp_data, 1)) { + FURI_LOG_E(TAG, "Missing VarLen"); + ret = SubGhzProtocolStatusErrorParserOthers; + break; + } + instance->var_bits = (uint8_t)temp_data; + if(!flipper_format_read_hex( + flipper_format, + "VarData", + (uint8_t*)&instance->var_data, + sizeof(instance->var_data))) { //-V1051 + FURI_LOG_E(TAG, "Missing VarData"); + ret = SubGhzProtocolStatusErrorParserOthers; + break; + } + if(instance->generic.data_count_bit != ws_oregon3_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key: %d", instance->generic.data_count_bit); + ret = SubGhzProtocolStatusErrorValueBitCount; + break; + } + } while(false); + return ret; +} + +static void oregon3_append_check_sum(uint32_t fix_data, uint64_t var_data, FuriString* output) { + uint8_t sum = fix_data & 0xF; + uint8_t ref_sum = var_data & 0xFF; + var_data >>= 4; + + for(uint8_t i = 1; i < 8; i++) { + fix_data >>= 4; + var_data >>= 4; + sum += (fix_data & 0xF) + (var_data & 0xF); + } + + // swap calculated sum nibbles + sum = (((sum >> 4) & 0xF) | (sum << 4)) & 0xFF; + if(sum == ref_sum) + furi_string_cat_printf(output, "Sum ok: 0x%hhX", ref_sum); + else + furi_string_cat_printf(output, "Sum err: 0x%hhX vs 0x%hhX", ref_sum, sum); +} + +void ws_protocol_decoder_oregon3_get_string(void* context, FuriString* output) { + furi_assert(context); + WSProtocolDecoderOregon3* instance = context; + furi_string_cat_printf( + output, + "%s\r\n" + "ID: 0x%04lX, ch: %d, bat: %d, rc: 0x%02lX\r\n", + instance->generic.protocol_name, + instance->generic.id, + instance->generic.channel, + instance->generic.battery_low, + (uint32_t)(instance->generic.data >> 4) & 0xFF); + + if(instance->var_bits > 0) { + furi_string_cat_printf( + output, + "Temp:%d.%d C Hum:%d%%", + (int16_t)instance->generic.temp, + abs( + ((int16_t)(instance->generic.temp * 10) - + (((int16_t)instance->generic.temp) * 10))), + instance->generic.humidity); + oregon3_append_check_sum((uint32_t)instance->generic.data, instance->var_data, output); + } +} + +const SubGhzProtocolDecoder ws_protocol_oregon3_decoder = { + .alloc = ws_protocol_decoder_oregon3_alloc, + .free = ws_protocol_decoder_oregon3_free, + + .feed = ws_protocol_decoder_oregon3_feed, + .reset = ws_protocol_decoder_oregon3_reset, + + .get_hash_data = ws_protocol_decoder_oregon3_get_hash_data, + .serialize = ws_protocol_decoder_oregon3_serialize, + .deserialize = ws_protocol_decoder_oregon3_deserialize, + .get_string = ws_protocol_decoder_oregon3_get_string, +}; + +const SubGhzProtocol ws_protocol_oregon3 = { + .name = WS_PROTOCOL_OREGON3_NAME, + .type = SubGhzProtocolWeatherStation, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable, + + .decoder = &ws_protocol_oregon3_decoder, +}; diff --git a/applications/external/weather_station/protocols/oregon3.h b/applications/external/weather_station/protocols/oregon3.h new file mode 100644 index 00000000000..ec51ddb0001 --- /dev/null +++ b/applications/external/weather_station/protocols/oregon3.h @@ -0,0 +1,6 @@ +#pragma once + +#include + +#define WS_PROTOCOL_OREGON3_NAME "Oregon3" +extern const SubGhzProtocol ws_protocol_oregon3; diff --git a/applications/external/weather_station/protocols/protocol_items.c b/applications/external/weather_station/protocols/protocol_items.c index cd4bae76dc2..93dc25488dd 100644 --- a/applications/external/weather_station/protocols/protocol_items.c +++ b/applications/external/weather_station/protocols/protocol_items.c @@ -11,6 +11,7 @@ const SubGhzProtocol* weather_station_protocol_registry_items[] = { &ws_protocol_lacrosse_tx, &ws_protocol_lacrosse_tx141thbv2, &ws_protocol_oregon2, + &ws_protocol_oregon3, &ws_protocol_acurite_592txr, &ws_protocol_ambient_weather, &ws_protocol_auriol_th, @@ -21,4 +22,4 @@ const SubGhzProtocol* weather_station_protocol_registry_items[] = { const SubGhzProtocolRegistry weather_station_protocol_registry = { .items = weather_station_protocol_registry_items, - .size = COUNT_OF(weather_station_protocol_registry_items)}; \ No newline at end of file + .size = COUNT_OF(weather_station_protocol_registry_items)}; diff --git a/applications/external/weather_station/protocols/protocol_items.h b/applications/external/weather_station/protocols/protocol_items.h index 0398c11f256..712eb07f221 100644 --- a/applications/external/weather_station/protocols/protocol_items.h +++ b/applications/external/weather_station/protocols/protocol_items.h @@ -11,6 +11,7 @@ #include "lacrosse_tx.h" #include "lacrosse_tx141thbv2.h" #include "oregon2.h" +#include "oregon3.h" #include "acurite_592txr.h" #include "ambient_weather.h" #include "auriol_hg0601a.h" From 2312fe5bfc8cc388291acda33ab2aa72ff025743 Mon Sep 17 00:00:00 2001 From: hedger Date: Fri, 9 Jun 2023 16:27:47 +0400 Subject: [PATCH 610/824] [FL-3361] fbt: stable build dates (#2751) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * scripts: using commit date for clean build timestamp; current day otherwise * scripts: version: Removing GIT_COMMIT_DATE from final data Co-authored-by: あく --- scripts/version.py | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/scripts/version.py b/scripts/version.py index f00c1f531c1..e68f7b41d7c 100644 --- a/scripts/version.py +++ b/scripts/version.py @@ -40,12 +40,23 @@ def get_version_info(self): except subprocess.CalledProcessError: version = "unknown" + if "SOURCE_DATE_EPOCH" in os.environ: + commit_date = datetime.utcfromtimestamp( + int(os.environ["SOURCE_DATE_EPOCH"]) + ) + else: + commit_date = datetime.strptime( + self._exec_git("log -1 --format=%cd").strip(), + "%a %b %d %H:%M:%S %Y %z", + ) + return { "GIT_COMMIT": commit, "GIT_BRANCH": branch, "VERSION": version, "BUILD_DIRTY": dirty and 1 or 0, "GIT_ORIGIN": ",".join(self._get_git_origins()), + "GIT_COMMIT_DATE": commit_date, } def _get_git_origins(self): @@ -102,10 +113,11 @@ def init(self): def generate(self): current_info = GitVersion(self.args.sourcedir).get_version_info() - if "SOURCE_DATE_EPOCH" in os.environ: - build_date = datetime.utcfromtimestamp(int(os.environ["SOURCE_DATE_EPOCH"])) - else: - build_date = date.today() + build_date = ( + date.today() + if current_info["BUILD_DIRTY"] + else current_info["GIT_COMMIT_DATE"] + ) current_info.update( { @@ -115,6 +127,8 @@ def generate(self): } ) + del current_info["GIT_COMMIT_DATE"] + version_values = [] for key in current_info: val = current_info[key] From 4900e8b7a2f416c106fe09567cbbfcf1b0d18e70 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Fri, 9 Jun 2023 15:41:40 +0300 Subject: [PATCH 611/824] [FL-3284] Fix reading Mifare Classic cards with unusual access conditions and fix emulation of unknown keys (#2620) * I was outplayed by the C programming language * Fix emulating empty keys as 0s * Add exceptions for Detect Reader * Sync api_symbols.csv for F18 * Outplayed by the C language [X2] Co-authored-by: Aleksandr Kutuzov --- firmware/targets/f18/api_symbols.csv | 2 +- firmware/targets/f7/api_symbols.csv | 4 ++-- lib/nfc/nfc_worker.c | 26 +++++++++++++++++++------ lib/nfc/protocols/mifare_classic.c | 29 +++++++++++++++++++++++----- lib/nfc/protocols/mifare_classic.h | 5 ++++- 5 files changed, 51 insertions(+), 15 deletions(-) diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index 85f09f1c3ca..d429de655a2 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,30.0,, +Version,+,30.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index afa76163272..80b4eedbdc4 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,30.0,, +Version,+,30.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -1957,7 +1957,7 @@ Function,-,mf_classic_dict_get_total_keys,uint32_t,MfClassicDict* Function,-,mf_classic_dict_is_key_present,_Bool,"MfClassicDict*, uint8_t*" Function,-,mf_classic_dict_is_key_present_str,_Bool,"MfClassicDict*, FuriString*" Function,-,mf_classic_dict_rewind,_Bool,MfClassicDict* -Function,-,mf_classic_emulator,_Bool,"MfClassicEmulator*, FuriHalNfcTxRxContext*" +Function,-,mf_classic_emulator,_Bool,"MfClassicEmulator*, FuriHalNfcTxRxContext*, _Bool" Function,-,mf_classic_get_classic_type,MfClassicType,"uint8_t, uint8_t, uint8_t" Function,-,mf_classic_get_read_sectors_and_keys,void,"MfClassicData*, uint8_t*, uint8_t*" Function,-,mf_classic_get_sector_by_block,uint8_t,uint8_t diff --git a/lib/nfc/nfc_worker.c b/lib/nfc/nfc_worker.c index 974bb0a4d19..a6bb93f59ef 100644 --- a/lib/nfc/nfc_worker.c +++ b/lib/nfc/nfc_worker.c @@ -917,7 +917,8 @@ void nfc_worker_mf_classic_dict_attack(NfcWorker* nfc_worker) { if(mf_classic_authenticate_skip_activate( &tx_rx, block_num, key, MfClassicKeyA, !deactivated, cuid)) { mf_classic_set_key_found(data, i, MfClassicKeyA, key); - FURI_LOG_D(TAG, "Key A found"); + FURI_LOG_D( + TAG, "Key A found: %04lx%08lx", (uint32_t)(key >> 32), (uint32_t)key); nfc_worker->callback(NfcWorkerEventFoundKeyA, nfc_worker->context); uint64_t found_key; @@ -939,8 +940,14 @@ void nfc_worker_mf_classic_dict_attack(NfcWorker* nfc_worker) { deactivated = true; } else { // If the key A is marked as found and matches the searching key, invalidate it + uint8_t found_key[6]; + memcpy(found_key, data->block[i].value, 6); + + uint8_t current_key[6]; + memcpy(current_key, &key, 6); + if(mf_classic_is_key_found(data, i, MfClassicKeyA) && - data->block[i].value[0] == key) { + memcmp(found_key, current_key, 6) == 0) { mf_classic_set_key_not_found(data, i, MfClassicKeyA); is_key_a_found = false; FURI_LOG_D(TAG, "Key %dA not found in attack", i); @@ -950,7 +957,8 @@ void nfc_worker_mf_classic_dict_attack(NfcWorker* nfc_worker) { is_key_b_found = mf_classic_is_key_found(data, i, MfClassicKeyB); if(mf_classic_authenticate_skip_activate( &tx_rx, block_num, key, MfClassicKeyB, !deactivated, cuid)) { - FURI_LOG_D(TAG, "Key B found"); + FURI_LOG_D( + TAG, "Key B found: %04lx%08lx", (uint32_t)(key >> 32), (uint32_t)key); mf_classic_set_key_found(data, i, MfClassicKeyB, key); nfc_worker->callback(NfcWorkerEventFoundKeyB, nfc_worker->context); nfc_worker_mf_classic_key_attack(nfc_worker, key, &tx_rx, i + 1); @@ -958,8 +966,14 @@ void nfc_worker_mf_classic_dict_attack(NfcWorker* nfc_worker) { deactivated = true; } else { // If the key B is marked as found and matches the searching key, invalidate it + uint8_t found_key[6]; + memcpy(found_key, data->block[i].value + 10, 6); + + uint8_t current_key[6]; + memcpy(current_key, &key, 6); + if(mf_classic_is_key_found(data, i, MfClassicKeyB) && - data->block[i].value[10] == key) { + memcmp(found_key, current_key, 6) == 0) { mf_classic_set_key_not_found(data, i, MfClassicKeyB); is_key_b_found = false; FURI_LOG_D(TAG, "Key %dB not found in attack", i); @@ -1004,7 +1018,7 @@ void nfc_worker_emulate_mf_classic(NfcWorker* nfc_worker) { furi_hal_nfc_listen_start(nfc_data); while(nfc_worker->state == NfcWorkerStateMfClassicEmulate) { //-V1044 if(furi_hal_nfc_listen_rx(&tx_rx, 300)) { - mf_classic_emulator(&emulator, &tx_rx); + mf_classic_emulator(&emulator, &tx_rx, false); } } if(emulator.data_changed) { @@ -1291,7 +1305,7 @@ void nfc_worker_analyze_reader(NfcWorker* nfc_worker) { NfcProtocol protocol = reader_analyzer_guess_protocol(reader_analyzer, tx_rx.rx_data, tx_rx.rx_bits / 8); if(protocol == NfcDeviceProtocolMifareClassic) { - mf_classic_emulator(&emulator, &tx_rx); + mf_classic_emulator(&emulator, &tx_rx, true); } } else { reader_no_data_received_cnt++; diff --git a/lib/nfc/protocols/mifare_classic.c b/lib/nfc/protocols/mifare_classic.c index d2d7467dce0..ebe49a4a0e0 100644 --- a/lib/nfc/protocols/mifare_classic.c +++ b/lib/nfc/protocols/mifare_classic.c @@ -845,7 +845,10 @@ uint8_t mf_classic_update_card(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data return sectors_read; } -bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_rx) { +bool mf_classic_emulator( + MfClassicEmulator* emulator, + FuriHalNfcTxRxContext* tx_rx, + bool is_reader_analyzer) { furi_assert(emulator); furi_assert(tx_rx); bool command_processed = false; @@ -892,11 +895,27 @@ bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_ MfClassicSectorTrailer* sector_trailer = (MfClassicSectorTrailer*)emulator->data.block[sector_trailer_block].value; if(cmd == MF_CLASSIC_AUTH_KEY_A_CMD) { - key = nfc_util_bytes2num(sector_trailer->key_a, 6); - access_key = MfClassicKeyA; + if(mf_classic_is_key_found( + &emulator->data, mf_classic_get_sector_by_block(block), MfClassicKeyA) || + is_reader_analyzer) { + key = nfc_util_bytes2num(sector_trailer->key_a, 6); + access_key = MfClassicKeyA; + } else { + FURI_LOG_D(TAG, "Key not known"); + command_processed = true; + break; + } } else { - key = nfc_util_bytes2num(sector_trailer->key_b, 6); - access_key = MfClassicKeyB; + if(mf_classic_is_key_found( + &emulator->data, mf_classic_get_sector_by_block(block), MfClassicKeyB) || + is_reader_analyzer) { + key = nfc_util_bytes2num(sector_trailer->key_b, 6); + access_key = MfClassicKeyB; + } else { + FURI_LOG_D(TAG, "Key not known"); + command_processed = true; + break; + } } uint32_t nonce = prng_successor(DWT->CYCCNT, 32) ^ 0xAA; diff --git a/lib/nfc/protocols/mifare_classic.h b/lib/nfc/protocols/mifare_classic.h index c03350f2e93..b3e3cbdf80d 100644 --- a/lib/nfc/protocols/mifare_classic.h +++ b/lib/nfc/protocols/mifare_classic.h @@ -199,7 +199,10 @@ uint8_t mf_classic_read_card( uint8_t mf_classic_update_card(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data); -bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_rx); +bool mf_classic_emulator( + MfClassicEmulator* emulator, + FuriHalNfcTxRxContext* tx_rx, + bool is_reader_analyzer); void mf_classic_halt(FuriHalNfcTxRxContext* tx_rx, Crypto1* crypto); From 392bd3cde09e17ab40fb55616d556ea3f77e9dd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Tue, 13 Jun 2023 21:02:12 +0900 Subject: [PATCH 612/824] FuriHal: remove clock startup time tracking from clean builds (#2764) --- firmware/targets/f7/furi_hal/furi_hal_clock.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/firmware/targets/f7/furi_hal/furi_hal_clock.c b/firmware/targets/f7/furi_hal/furi_hal_clock.c index 770b74296c7..736ad9f7c05 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_clock.c +++ b/firmware/targets/f7/furi_hal/furi_hal_clock.c @@ -1,6 +1,5 @@ #include #include -#include #include #include @@ -143,7 +142,10 @@ void furi_hal_clock_switch_to_hsi() { } void furi_hal_clock_switch_to_pll() { +#ifdef FURI_HAL_CLOCK_TRACK_STARTUP uint32_t clock_start_time = DWT->CYCCNT; +#endif + LL_RCC_HSE_Enable(); LL_RCC_PLL_Enable(); LL_RCC_PLLSAI1_Enable(); @@ -166,11 +168,12 @@ void furi_hal_clock_switch_to_pll() { while(LL_RCC_GetSysClkSource() != LL_RCC_SYS_CLKSOURCE_STATUS_PLL) ; +#ifdef FURI_HAL_CLOCK_TRACK_STARTUP uint32_t total = DWT->CYCCNT - clock_start_time; if(total > (20 * 0x148)) { - furi_hal_rtc_set_flag(FuriHalRtcFlagLegacySleep); furi_crash("Slow HSE/PLL startup"); } +#endif } void furi_hal_clock_suspend_tick() { From b6dbf25f85b55d4058e471b50433bdc87fd5f1f4 Mon Sep 17 00:00:00 2001 From: Leopold Date: Wed, 14 Jun 2023 18:49:26 +0800 Subject: [PATCH 613/824] furi_hal_nfc: fix rfalTransceiveBitsBlockingTx's 4th argument to bits count rather than bytes count (#2773) --- firmware/targets/f7/furi_hal/furi_hal_nfc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc.c b/firmware/targets/f7/furi_hal/furi_hal_nfc.c index 8910d887bcb..c4e7ad9f984 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_nfc.c +++ b/firmware/targets/f7/furi_hal/furi_hal_nfc.c @@ -467,7 +467,7 @@ bool furi_hal_nfc_emulate_nfca( buff_tx, buff_tx_len, buff_rx, - sizeof(buff_rx), + rfalConvBytesToBits(buff_rx_size), &buff_rx_len, data_type, RFAL_FWT_NONE); @@ -491,7 +491,7 @@ bool furi_hal_nfc_emulate_nfca( buff_tx, buff_tx_len, buff_rx, - sizeof(buff_rx), + rfalConvBytesToBits(buff_rx_size), &buff_rx_len, data_type, RFAL_FWT_NONE); From 5334a0ab92393d4c93baa25aaf6f7cfb1c571897 Mon Sep 17 00:00:00 2001 From: hedger Date: Thu, 15 Jun 2023 16:24:47 +0400 Subject: [PATCH 614/824] [FL-3376] Fixed GATT attribute order (#2776) * hal: gatt: swapped rx/tx serial chars order * hal: gatt: reordered HID attrs to maintain previous order Co-authored-by: Aleksandr Kutuzov --- firmware/targets/f7/ble_glue/gap.c | 1 + .../f7/ble_glue/services/hid_service.c | 25 +++++++++++++------ .../f7/ble_glue/services/serial_service.c | 24 +++++++++--------- 3 files changed, 30 insertions(+), 20 deletions(-) diff --git a/firmware/targets/f7/ble_glue/gap.c b/firmware/targets/f7/ble_glue/gap.c index f0a9ced3cb7..360c1f6b684 100644 --- a/firmware/targets/f7/ble_glue/gap.c +++ b/firmware/targets/f7/ble_glue/gap.c @@ -330,6 +330,7 @@ static void gap_init_svc(Gap* gap) { if(status) { FURI_LOG_E(TAG, "Failed updating name characteristic: %d", status); } + uint8_t gap_appearence_char_uuid[2] = { gap->config->appearance_char & 0xff, gap->config->appearance_char >> 8}; status = aci_gatt_update_char_value( diff --git a/firmware/targets/f7/ble_glue/services/hid_service.c b/firmware/targets/f7/ble_glue/services/hid_service.c index cde26b267ec..cf2aca24e3f 100644 --- a/firmware/targets/f7/ble_glue/services/hid_service.c +++ b/firmware/targets/f7/ble_glue/services/hid_service.c @@ -22,6 +22,10 @@ typedef struct { static_assert(sizeof(HidSvcReportId) == sizeof(uint16_t), "HidSvcReportId must be 2 bytes"); +static const Service_UUID_t hid_svc_uuid = { + .Service_UUID_16 = HUMAN_INTERFACE_DEVICE_SERVICE_UUID, +}; + static bool hid_svc_char_desc_data_callback(const void* context, const uint8_t** data, uint16_t* data_len) { const HidSvcReportId* report_id = context; @@ -148,18 +152,15 @@ static SVCCTL_EvtAckStatus_t hid_svc_event_handler(void* event) { void hid_svc_start() { tBleStatus status; hid_svc = malloc(sizeof(HIDSvc)); - Service_UUID_t svc_uuid = {}; // Register event handler SVCCTL_RegisterSvcHandler(hid_svc_event_handler); - // Add service - svc_uuid.Service_UUID_16 = HUMAN_INTERFACE_DEVICE_SERVICE_UUID; /** * Add Human Interface Device Service */ status = aci_gatt_add_service( UUID_TYPE_16, - &svc_uuid, + &hid_svc_uuid, PRIMARY_SERVICE, 2 + /* protocol mode */ (4 * HID_SVC_INPUT_REPORT_COUNT) + (3 * HID_SVC_OUTPUT_REPORT_COUNT) + @@ -170,10 +171,12 @@ void hid_svc_start() { FURI_LOG_E(TAG, "Failed to add HID service: %d", status); } - for(size_t i = 0; i < HidSvcGattCharacteristicCount; i++) { - flipper_gatt_characteristic_init( - hid_svc->svc_handle, &hid_svc_chars[i], &hid_svc->chars[i]); - } + // Maintain previously defined characteristic order + flipper_gatt_characteristic_init( + hid_svc->svc_handle, + &hid_svc_chars[HidSvcGattCharacteristicProtocolMode], + &hid_svc->chars[HidSvcGattCharacteristicProtocolMode]); + uint8_t protocol_mode = 1; flipper_gatt_characteristic_update( hid_svc->svc_handle, @@ -215,6 +218,12 @@ void hid_svc_start() { &hid_report_chars[report_type_idx].chars[report_idx]); } } + + // Setup remaining characteristics + for(size_t i = HidSvcGattCharacteristicReportMap; i < HidSvcGattCharacteristicCount; i++) { + flipper_gatt_characteristic_init( + hid_svc->svc_handle, &hid_svc_chars[i], &hid_svc->chars[i]); + } } bool hid_svc_update_report_map(const uint8_t* data, uint16_t len) { diff --git a/firmware/targets/f7/ble_glue/services/serial_service.c b/firmware/targets/f7/ble_glue/services/serial_service.c index ab009bbfcb0..0db25b3d3af 100644 --- a/firmware/targets/f7/ble_glue/services/serial_service.c +++ b/firmware/targets/f7/ble_glue/services/serial_service.c @@ -10,24 +10,14 @@ #define TAG "BtSerialSvc" typedef enum { - SerialSvcGattCharacteristicTx = 0, - SerialSvcGattCharacteristicRx, + SerialSvcGattCharacteristicRx = 0, + SerialSvcGattCharacteristicTx, SerialSvcGattCharacteristicFlowCtrl, SerialSvcGattCharacteristicStatus, SerialSvcGattCharacteristicCount, } SerialSvcGattCharacteristicId; static const FlipperGattCharacteristicParams serial_svc_chars[SerialSvcGattCharacteristicCount] = { - [SerialSvcGattCharacteristicTx] = - {.name = "TX", - .data_prop_type = FlipperGattCharacteristicDataFixed, - .data.fixed.length = SERIAL_SVC_DATA_LEN_MAX, - .uuid.Char_UUID_128 = SERIAL_SVC_TX_CHAR_UUID, - .uuid_type = UUID_TYPE_128, - .char_properties = CHAR_PROP_READ | CHAR_PROP_INDICATE, - .security_permissions = ATTR_PERMISSION_AUTHEN_READ, - .gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS, - .is_variable = CHAR_VALUE_LEN_VARIABLE}, [SerialSvcGattCharacteristicRx] = {.name = "RX", .data_prop_type = FlipperGattCharacteristicDataFixed, @@ -38,6 +28,16 @@ static const FlipperGattCharacteristicParams serial_svc_chars[SerialSvcGattChara .security_permissions = ATTR_PERMISSION_AUTHEN_READ | ATTR_PERMISSION_AUTHEN_WRITE, .gatt_evt_mask = GATT_NOTIFY_ATTRIBUTE_WRITE, .is_variable = CHAR_VALUE_LEN_VARIABLE}, + [SerialSvcGattCharacteristicTx] = + {.name = "TX", + .data_prop_type = FlipperGattCharacteristicDataFixed, + .data.fixed.length = SERIAL_SVC_DATA_LEN_MAX, + .uuid.Char_UUID_128 = SERIAL_SVC_TX_CHAR_UUID, + .uuid_type = UUID_TYPE_128, + .char_properties = CHAR_PROP_READ | CHAR_PROP_INDICATE, + .security_permissions = ATTR_PERMISSION_AUTHEN_READ, + .gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS, + .is_variable = CHAR_VALUE_LEN_VARIABLE}, [SerialSvcGattCharacteristicFlowCtrl] = {.name = "Flow control", .data_prop_type = FlipperGattCharacteristicDataFixed, From 4ddfe05a5965200ea5c418c52376b42e99e575c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Fri, 16 Jun 2023 15:48:57 +0900 Subject: [PATCH 615/824] Debug: sync apps on attach, makes it possible to debug already started app that has crashed (#2778) --- scripts/debug/flipperapps.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/debug/flipperapps.py b/scripts/debug/flipperapps.py index 608c30412ff..6dba89a5640 100644 --- a/scripts/debug/flipperapps.py +++ b/scripts/debug/flipperapps.py @@ -188,6 +188,7 @@ def attach_to_fw(self) -> None: ) self.app_type_ptr = gdb.lookup_type("FlipperApplication").pointer() self.app_list_entry_type = gdb.lookup_type("struct FlipperApplicationList_s") + self._sync_apps() def handle_stop(self, event) -> None: self._sync_apps() From 761a14e6e2ffd417a8afe2eb8dd6f24dd812f9b5 Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Fri, 23 Jun 2023 15:01:40 +0300 Subject: [PATCH 616/824] [FL-2837][FL-3270] Loader refaptoring: second encounter (#2779) * Core: rename internal FlipperApplication to FlipperInternalApplication * FAP Loader: move load_name_and_icon to flipper_application library * Loader menu: rework api * View holder: move to gui service * Loader: simple "loading" worker * Loader: applications dialog * Loader: fapping * Update f18 api * Apps: remove fap_loader * Libs, flipper application: store args, rename thread allocation * Loader: error handling * Apps: use loader error handling * Loader: documentation * FBT: accomodate loader * Loader: do not raise gui error if loader is locked * Archive: accomodate loader * Loader: fix loading message * Flipper: drop some old dolphin legacy * Loader: generalize error construction Co-authored-by: Aleksandr Kutuzov --- applications/ReadMe.md | 1 - applications/main/application.fam | 1 - .../main/archive/helpers/archive_browser.c | 4 +- .../archive/scenes/archive_scene_browser.c | 57 ++-- applications/main/fap_loader/application.fam | 15 - applications/main/fap_loader/fap_loader_app.c | 216 ------------ applications/main/fap_loader/fap_loader_app.h | 27 -- applications/services/applications.h | 31 +- applications/services/desktop/desktop.c | 1 + .../desktop/scenes/desktop_scene_main.c | 46 +-- applications/services/dialogs/dialogs_i.h | 2 +- .../services/{dialogs => gui}/view_holder.c | 0 .../services/{dialogs => gui}/view_holder.h | 0 applications/services/loader/loader.c | 311 ++++++++++++++---- applications/services/loader/loader.h | 52 ++- .../services/loader/loader_applications.c | 146 ++++++++ .../services/loader/loader_applications.h | 16 + applications/services/loader/loader_cli.c | 18 +- applications/services/loader/loader_i.h | 7 +- applications/services/loader/loader_menu.c | 189 +++++------ applications/services/loader/loader_menu.h | 16 +- applications/services/rpc/rpc_app.c | 2 +- .../scenes/desktop_settings_scene_favorite.c | 3 - .../storage_move_to_sd/storage_move_to_sd.c | 2 +- applications/system/updater/cli/updater_cli.c | 2 +- firmware/targets/f18/api_symbols.csv | 8 +- firmware/targets/f7/api_symbols.csv | 8 +- lib/flipper_application/flipper_application.c | 48 ++- lib/flipper_application/flipper_application.h | 21 +- scripts/distfap.py | 4 +- scripts/fbt/appmanifest.py | 22 +- scripts/runfap.py | 2 +- 32 files changed, 710 insertions(+), 568 deletions(-) delete mode 100644 applications/main/fap_loader/application.fam delete mode 100644 applications/main/fap_loader/fap_loader_app.c delete mode 100644 applications/main/fap_loader/fap_loader_app.h rename applications/services/{dialogs => gui}/view_holder.c (100%) rename applications/services/{dialogs => gui}/view_holder.h (100%) create mode 100644 applications/services/loader/loader_applications.c create mode 100644 applications/services/loader/loader_applications.h diff --git a/applications/ReadMe.md b/applications/ReadMe.md index e50d8e46a5f..10e54ce2257 100644 --- a/applications/ReadMe.md +++ b/applications/ReadMe.md @@ -26,7 +26,6 @@ Applications for main Flipper menu. - `archive` - Archive and file manager - `bad_usb` - Bad USB application -- `fap_loader` - External applications loader - `gpio` - GPIO application: includes USART bridge and GPIO control - `ibutton` - iButton application, onewire keys and more - `infrared` - Infrared application, controls your IR devices diff --git a/applications/main/application.fam b/applications/main/application.fam index 5c2c21d3753..75d55af936f 100644 --- a/applications/main/application.fam +++ b/applications/main/application.fam @@ -12,7 +12,6 @@ App( "subghz", "bad_usb", "u2f", - "fap_loader", "archive", ], ) diff --git a/applications/main/archive/helpers/archive_browser.c b/applications/main/archive/helpers/archive_browser.c index 9a7973cb388..70137d6944d 100644 --- a/applications/main/archive/helpers/archive_browser.c +++ b/applications/main/archive/helpers/archive_browser.c @@ -6,7 +6,7 @@ #include #include #include -#include +#include #include static void @@ -367,7 +367,7 @@ void archive_add_app_item(ArchiveBrowserView* browser, const char* name) { static bool archive_get_fap_meta(FuriString* file_path, FuriString* fap_name, uint8_t** icon_ptr) { Storage* storage = furi_record_open(RECORD_STORAGE); bool success = false; - if(fap_loader_load_name_and_icon(file_path, storage, icon_ptr, fap_name)) { + if(flipper_application_load_name_and_icon(file_path, storage, icon_ptr, fap_name)) { success = true; } furi_record_close(RECORD_STORAGE); diff --git a/applications/main/archive/scenes/archive_scene_browser.c b/applications/main/archive/scenes/archive_scene_browser.c index c28f91f5244..e02f7622a6d 100644 --- a/applications/main/archive/scenes/archive_scene_browser.c +++ b/applications/main/archive/scenes/archive_scene_browser.c @@ -11,17 +11,28 @@ #define SCENE_STATE_DEFAULT (0) #define SCENE_STATE_NEED_REFRESH (1) -static const char* flipper_app_name[] = { - [ArchiveFileTypeIButton] = "iButton", - [ArchiveFileTypeNFC] = "NFC", - [ArchiveFileTypeSubGhz] = "Sub-GHz", - [ArchiveFileTypeLFRFID] = "125 kHz RFID", - [ArchiveFileTypeInfrared] = "Infrared", - [ArchiveFileTypeBadUsb] = "Bad USB", - [ArchiveFileTypeU2f] = "U2F", - [ArchiveFileTypeUpdateManifest] = "UpdaterApp", - [ArchiveFileTypeApplication] = "Applications", -}; +const char* archive_get_flipper_app_name(ArchiveFileTypeEnum file_type) { + switch(file_type) { + case ArchiveFileTypeIButton: + return "iButton"; + case ArchiveFileTypeNFC: + return "NFC"; + case ArchiveFileTypeSubGhz: + return "Sub-GHz"; + case ArchiveFileTypeLFRFID: + return "125 kHz RFID"; + case ArchiveFileTypeInfrared: + return "Infrared"; + case ArchiveFileTypeBadUsb: + return "Bad USB"; + case ArchiveFileTypeU2f: + return "U2F"; + case ArchiveFileTypeUpdateManifest: + return "UpdaterApp"; + default: + return NULL; + } +} static void archive_loader_callback(const void* message, void* context) { furi_assert(message); @@ -39,20 +50,20 @@ static void archive_run_in_app(ArchiveBrowserView* browser, ArchiveFile_t* selec UNUSED(browser); Loader* loader = furi_record_open(RECORD_LOADER); - LoaderStatus status; - if(selected->is_app) { - char* param = strrchr(furi_string_get_cstr(selected->path), '/'); - if(param != NULL) { - param++; + const char* app_name = archive_get_flipper_app_name(selected->type); + + if(app_name) { + if(selected->is_app) { + char* param = strrchr(furi_string_get_cstr(selected->path), '/'); + if(param != NULL) { + param++; + } + loader_start_with_gui_error(loader, app_name, param); + } else { + loader_start_with_gui_error(loader, app_name, furi_string_get_cstr(selected->path)); } - status = loader_start(loader, flipper_app_name[selected->type], param); } else { - status = loader_start( - loader, flipper_app_name[selected->type], furi_string_get_cstr(selected->path)); - } - - if(status != LoaderStatusOk) { - FURI_LOG_E(TAG, "loader_start failed: %d", status); + loader_start_with_gui_error(loader, furi_string_get_cstr(selected->path), NULL); } furi_record_close(RECORD_LOADER); diff --git a/applications/main/fap_loader/application.fam b/applications/main/fap_loader/application.fam deleted file mode 100644 index b0e67cd42e3..00000000000 --- a/applications/main/fap_loader/application.fam +++ /dev/null @@ -1,15 +0,0 @@ -App( - appid="fap_loader", - name="Applications", - apptype=FlipperAppType.APP, - entry_point="fap_loader_app", - cdefines=["APP_FAP_LOADER"], - requires=[ - "gui", - "storage", - "loader", - ], - stack_size=int(1.5 * 1024), - icon="A_Plugins_14", - order=90, -) diff --git a/applications/main/fap_loader/fap_loader_app.c b/applications/main/fap_loader/fap_loader_app.c deleted file mode 100644 index 7af5244ae49..00000000000 --- a/applications/main/fap_loader/fap_loader_app.c +++ /dev/null @@ -1,216 +0,0 @@ -#include "fap_loader_app.h" - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#define TAG "FapLoader" - -struct FapLoader { - FlipperApplication* app; - Storage* storage; - DialogsApp* dialogs; - Gui* gui; - FuriString* fap_path; - ViewDispatcher* view_dispatcher; - Loading* loading; -}; - -bool fap_loader_load_name_and_icon( - FuriString* path, - Storage* storage, - uint8_t** icon_ptr, - FuriString* item_name) { - FlipperApplication* app = flipper_application_alloc(storage, firmware_api_interface); - - FlipperApplicationPreloadStatus preload_res = - flipper_application_preload_manifest(app, furi_string_get_cstr(path)); - - bool load_success = false; - - if(preload_res == FlipperApplicationPreloadStatusSuccess) { - const FlipperApplicationManifest* manifest = flipper_application_get_manifest(app); - if(manifest->has_icon) { - memcpy(*icon_ptr, manifest->icon, FAP_MANIFEST_MAX_ICON_SIZE); - } - furi_string_set(item_name, manifest->name); - load_success = true; - } else { - FURI_LOG_E(TAG, "FAP Loader failed to preload %s", furi_string_get_cstr(path)); - load_success = false; - } - - flipper_application_free(app); - return load_success; -} - -static bool fap_loader_item_callback( - FuriString* path, - void* context, - uint8_t** icon_ptr, - FuriString* item_name) { - FapLoader* fap_loader = context; - furi_assert(fap_loader); - return fap_loader_load_name_and_icon(path, fap_loader->storage, icon_ptr, item_name); -} - -static bool fap_loader_run_selected_app(FapLoader* loader) { - furi_assert(loader); - - FuriString* error_message; - - error_message = furi_string_alloc_set("unknown error"); - - bool file_selected = false; - bool show_error = true; - do { - file_selected = true; - loader->app = flipper_application_alloc(loader->storage, firmware_api_interface); - size_t start = furi_get_tick(); - - FURI_LOG_I(TAG, "FAP Loader is loading %s", furi_string_get_cstr(loader->fap_path)); - - FlipperApplicationPreloadStatus preload_res = - flipper_application_preload(loader->app, furi_string_get_cstr(loader->fap_path)); - if(preload_res != FlipperApplicationPreloadStatusSuccess) { - const char* err_msg = flipper_application_preload_status_to_string(preload_res); - furi_string_printf(error_message, "Preload failed: %s", err_msg); - FURI_LOG_E( - TAG, - "FAP Loader failed to preload %s: %s", - furi_string_get_cstr(loader->fap_path), - err_msg); - break; - } - - FURI_LOG_I(TAG, "FAP Loader is mapping"); - FlipperApplicationLoadStatus load_status = flipper_application_map_to_memory(loader->app); - if(load_status != FlipperApplicationLoadStatusSuccess) { - const char* err_msg = flipper_application_load_status_to_string(load_status); - furi_string_printf(error_message, "Load failed: %s", err_msg); - FURI_LOG_E( - TAG, - "FAP Loader failed to map to memory %s: %s", - furi_string_get_cstr(loader->fap_path), - err_msg); - break; - } - - FURI_LOG_I(TAG, "Loaded in %ums", (size_t)(furi_get_tick() - start)); - FURI_LOG_I(TAG, "FAP Loader is starting app"); - - FuriThread* thread = flipper_application_spawn(loader->app, NULL); - - /* This flag is set by the debugger - to break on app start */ - if(furi_hal_debug_is_gdb_session_active()) { - FURI_LOG_W(TAG, "Triggering BP for debugger"); - /* After hitting this, you can set breakpoints in your .fap's code - * Note that you have to toggle breakpoints that were set before */ - __asm volatile("bkpt 0"); - } - - FuriString* app_name = furi_string_alloc(); - path_extract_filename_no_ext(furi_string_get_cstr(loader->fap_path), app_name); - furi_thread_set_appid(thread, furi_string_get_cstr(app_name)); - furi_string_free(app_name); - - furi_thread_start(thread); - furi_thread_join(thread); - - show_error = false; - int ret = furi_thread_get_return_code(thread); - - FURI_LOG_I(TAG, "FAP app returned: %i", ret); - } while(0); - - if(show_error) { - DialogMessage* message = dialog_message_alloc(); - dialog_message_set_header(message, "Error", 64, 0, AlignCenter, AlignTop); - dialog_message_set_buttons(message, NULL, NULL, NULL); - - FuriString* buffer; - buffer = furi_string_alloc(); - furi_string_printf(buffer, "%s", furi_string_get_cstr(error_message)); - furi_string_replace(buffer, ":", "\n"); - dialog_message_set_text( - message, furi_string_get_cstr(buffer), 64, 32, AlignCenter, AlignCenter); - - dialog_message_show(loader->dialogs, message); - dialog_message_free(message); - furi_string_free(buffer); - } - - furi_string_free(error_message); - - if(file_selected) { - flipper_application_free(loader->app); - } - - return file_selected; -} - -static bool fap_loader_select_app(FapLoader* loader) { - const DialogsFileBrowserOptions browser_options = { - .extension = ".fap", - .skip_assets = true, - .icon = &I_unknown_10px, - .hide_ext = true, - .item_loader_callback = fap_loader_item_callback, - .item_loader_context = loader, - .base_path = EXT_PATH("apps"), - }; - - return dialog_file_browser_show( - loader->dialogs, loader->fap_path, loader->fap_path, &browser_options); -} - -static FapLoader* fap_loader_alloc(const char* path) { - FapLoader* loader = malloc(sizeof(FapLoader)); //-V799 - loader->fap_path = furi_string_alloc_set(path); - loader->storage = furi_record_open(RECORD_STORAGE); - loader->dialogs = furi_record_open(RECORD_DIALOGS); - loader->gui = furi_record_open(RECORD_GUI); - loader->view_dispatcher = view_dispatcher_alloc(); - loader->loading = loading_alloc(); - view_dispatcher_attach_to_gui( - loader->view_dispatcher, loader->gui, ViewDispatcherTypeFullscreen); - view_dispatcher_add_view(loader->view_dispatcher, 0, loading_get_view(loader->loading)); - return loader; -} //-V773 - -static void fap_loader_free(FapLoader* loader) { - view_dispatcher_remove_view(loader->view_dispatcher, 0); - loading_free(loader->loading); - view_dispatcher_free(loader->view_dispatcher); - furi_string_free(loader->fap_path); - furi_record_close(RECORD_GUI); - furi_record_close(RECORD_DIALOGS); - furi_record_close(RECORD_STORAGE); - free(loader); -} - -int32_t fap_loader_app(void* p) { - FapLoader* loader; - if(p) { - loader = fap_loader_alloc((const char*)p); - view_dispatcher_switch_to_view(loader->view_dispatcher, 0); - fap_loader_run_selected_app(loader); - } else { - loader = fap_loader_alloc(EXT_PATH("apps")); - while(fap_loader_select_app(loader)) { - view_dispatcher_switch_to_view(loader->view_dispatcher, 0); - fap_loader_run_selected_app(loader); - }; - } - - fap_loader_free(loader); - return 0; -} diff --git a/applications/main/fap_loader/fap_loader_app.h b/applications/main/fap_loader/fap_loader_app.h deleted file mode 100644 index 9ed725efe52..00000000000 --- a/applications/main/fap_loader/fap_loader_app.h +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once -#include - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct FapLoader FapLoader; - -/** - * @brief Load name and icon from FAP file. - * - * @param path Path to FAP file. - * @param storage Storage instance. - * @param icon_ptr Icon pointer. - * @param item_name Application name. - * @return true if icon and name were loaded successfully. - */ -bool fap_loader_load_name_and_icon( - FuriString* path, - Storage* storage, - uint8_t** icon_ptr, - FuriString* item_name); - -#ifdef __cplusplus -} -#endif \ No newline at end of file diff --git a/applications/services/applications.h b/applications/services/applications.h index 85f73674232..45b050a06ec 100644 --- a/applications/services/applications.h +++ b/applications/services/applications.h @@ -4,9 +4,9 @@ #include typedef enum { - FlipperApplicationFlagDefault = 0, - FlipperApplicationFlagInsomniaSafe = (1 << 0), -} FlipperApplicationFlag; + FlipperInternalApplicationFlagDefault = 0, + FlipperInternalApplicationFlagInsomniaSafe = (1 << 0), +} FlipperInternalApplicationFlag; typedef struct { const FuriThreadCallback app; @@ -14,48 +14,41 @@ typedef struct { const char* appid; const size_t stack_size; const Icon* icon; - const FlipperApplicationFlag flags; -} FlipperApplication; + const FlipperInternalApplicationFlag flags; +} FlipperInternalApplication; -typedef void (*FlipperOnStartHook)(void); +typedef void (*FlipperInternalOnStartHook)(void); extern const char* FLIPPER_AUTORUN_APP_NAME; /* Services list * Spawned on startup */ -extern const FlipperApplication FLIPPER_SERVICES[]; +extern const FlipperInternalApplication FLIPPER_SERVICES[]; extern const size_t FLIPPER_SERVICES_COUNT; /* Apps list * Spawned by loader */ -extern const FlipperApplication FLIPPER_APPS[]; +extern const FlipperInternalApplication FLIPPER_APPS[]; extern const size_t FLIPPER_APPS_COUNT; /* On system start hooks * Called by loader, after OS initialization complete */ -extern const FlipperOnStartHook FLIPPER_ON_SYSTEM_START[]; +extern const FlipperInternalOnStartHook FLIPPER_ON_SYSTEM_START[]; extern const size_t FLIPPER_ON_SYSTEM_START_COUNT; /* System apps * Can only be spawned by loader by name */ -extern const FlipperApplication FLIPPER_SYSTEM_APPS[]; +extern const FlipperInternalApplication FLIPPER_SYSTEM_APPS[]; extern const size_t FLIPPER_SYSTEM_APPS_COUNT; -/* Separate scene app holder - * Spawned by loader - */ -extern const FlipperApplication FLIPPER_SCENE; -extern const FlipperApplication FLIPPER_SCENE_APPS[]; -extern const size_t FLIPPER_SCENE_APPS_COUNT; - -extern const FlipperApplication FLIPPER_ARCHIVE; +extern const FlipperInternalApplication FLIPPER_ARCHIVE; /* Settings list * Spawned by loader */ -extern const FlipperApplication FLIPPER_SETTINGS_APPS[]; +extern const FlipperInternalApplication FLIPPER_SETTINGS_APPS[]; extern const size_t FLIPPER_SETTINGS_APPS_COUNT; diff --git a/applications/services/desktop/desktop.c b/applications/services/desktop/desktop.c index e1da649408b..1233af8938f 100644 --- a/applications/services/desktop/desktop.c +++ b/applications/services/desktop/desktop.c @@ -36,6 +36,7 @@ static void desktop_loader_callback(const void* message, void* context) { view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopGlobalAfterAppFinished); } } + static void desktop_lock_icon_draw_callback(Canvas* canvas, void* context) { UNUSED(context); furi_assert(canvas); diff --git a/applications/services/desktop/scenes/desktop_scene_main.c b/applications/services/desktop/scenes/desktop_scene_main.c index d19b5560f94..ae39ec22376 100644 --- a/applications/services/desktop/scenes/desktop_scene_main.c +++ b/applications/services/desktop/scenes/desktop_scene_main.c @@ -16,8 +16,6 @@ #define SNAKE_GAME_APP EXT_PATH("/apps/Games/snake_game.fap") #define CLOCK_APP EXT_PATH("/apps/Tools/clock.fap") -#define FAP_LOADER_APP_NAME "Applications" - static void desktop_scene_main_new_idle_animation_callback(void* context) { furi_assert(context); Desktop* desktop = context; @@ -40,7 +38,8 @@ static void desktop_scene_main_interact_animation_callback(void* context) { } #ifdef APP_ARCHIVE -static void desktop_switch_to_app(Desktop* desktop, const FlipperApplication* flipper_app) { +static void + desktop_switch_to_app(Desktop* desktop, const FlipperInternalApplication* flipper_app) { furi_assert(desktop); furi_assert(flipper_app); furi_assert(flipper_app->app); @@ -67,30 +66,16 @@ static void desktop_switch_to_app(Desktop* desktop, const FlipperApplication* fl #endif static void desktop_scene_main_open_app_or_profile(Desktop* desktop, const char* path) { - do { - LoaderStatus status = loader_start(desktop->loader, FAP_LOADER_APP_NAME, path); - if(status == LoaderStatusOk) break; - FURI_LOG_E(TAG, "loader_start failed: %d", status); - - status = loader_start(desktop->loader, "Passport", NULL); - if(status != LoaderStatusOk) { - FURI_LOG_E(TAG, "loader_start failed: %d", status); - } - } while(false); + if(loader_start_with_gui_error(desktop->loader, path, NULL) != LoaderStatusOk) { + loader_start(desktop->loader, "Passport", NULL, NULL); + } } static void desktop_scene_main_start_favorite(Desktop* desktop, FavoriteApp* application) { - LoaderStatus status = LoaderStatusErrorInternal; - if(application->is_external) { - status = loader_start(desktop->loader, FAP_LOADER_APP_NAME, application->name_or_path); - } else if(strlen(application->name_or_path) > 0) { - status = loader_start(desktop->loader, application->name_or_path, NULL); + if(strlen(application->name_or_path) > 0) { + loader_start_with_gui_error(desktop->loader, application->name_or_path, NULL); } else { - status = loader_start(desktop->loader, FAP_LOADER_APP_NAME, NULL); - } - - if(status != LoaderStatusOk) { - FURI_LOG_E(TAG, "loader_start failed: %d", status); + loader_start(desktop->loader, LOADER_APPLICATIONS_NAME, NULL, NULL); } } @@ -148,10 +133,7 @@ bool desktop_scene_main_on_event(void* context, SceneManagerEvent event) { break; case DesktopMainEventOpenPowerOff: { - LoaderStatus status = loader_start(desktop->loader, "Power", "off"); - if(status != LoaderStatusOk) { - FURI_LOG_E(TAG, "loader_start failed: %d", status); - } + loader_start(desktop->loader, "Power", "off", NULL); consumed = true; break; } @@ -176,18 +158,12 @@ bool desktop_scene_main_on_event(void* context, SceneManagerEvent event) { break; case DesktopAnimationEventInteractAnimation: if(!animation_manager_interact_process(desktop->animation_manager)) { - LoaderStatus status = loader_start(desktop->loader, "Passport", NULL); - if(status != LoaderStatusOk) { - FURI_LOG_E(TAG, "loader_start failed: %d", status); - } + loader_start(desktop->loader, "Passport", NULL, NULL); } consumed = true; break; case DesktopMainEventOpenPassport: { - LoaderStatus status = loader_start(desktop->loader, "Passport", NULL); - if(status != LoaderStatusOk) { - FURI_LOG_E(TAG, "loader_start failed: %d", status); - } + loader_start(desktop->loader, "Passport", NULL, NULL); break; } case DesktopMainEventOpenGame: { diff --git a/applications/services/dialogs/dialogs_i.h b/applications/services/dialogs/dialogs_i.h index 76495d31b0a..29417b41b52 100644 --- a/applications/services/dialogs/dialogs_i.h +++ b/applications/services/dialogs/dialogs_i.h @@ -1,7 +1,7 @@ #pragma once #include "dialogs.h" #include "dialogs_message.h" -#include "view_holder.h" +#include #include #ifdef __cplusplus diff --git a/applications/services/dialogs/view_holder.c b/applications/services/gui/view_holder.c similarity index 100% rename from applications/services/dialogs/view_holder.c rename to applications/services/gui/view_holder.c diff --git a/applications/services/dialogs/view_holder.h b/applications/services/gui/view_holder.h similarity index 100% rename from applications/services/dialogs/view_holder.h rename to applications/services/gui/view_holder.h diff --git a/applications/services/loader/loader.c b/applications/services/loader/loader.c index f385efdf95a..ab7876a0366 100644 --- a/applications/services/loader/loader.c +++ b/applications/services/loader/loader.c @@ -1,20 +1,27 @@ #include "loader.h" #include "loader_i.h" -#include "loader_menu.h" #include +#include #include +#include +#include +#include +#include + #define TAG "Loader" #define LOADER_MAGIC_THREAD_VALUE 0xDEADBEEF // api -LoaderStatus loader_start(Loader* loader, const char* name, const char* args) { +LoaderStatus + loader_start(Loader* loader, const char* name, const char* args, FuriString* error_message) { LoaderMessage message; LoaderMessageLoaderStatusResult result; message.type = LoaderMessageTypeStartByName; message.start.name = name; message.start.args = args; + message.start.error_message = error_message; message.api_lock = api_lock_alloc_locked(); message.status_value = &result; furi_message_queue_put(loader->queue, &message, FuriWaitForever); @@ -22,6 +29,31 @@ LoaderStatus loader_start(Loader* loader, const char* name, const char* args) { return result.value; } +LoaderStatus loader_start_with_gui_error(Loader* loader, const char* name, const char* args) { + FuriString* error_message = furi_string_alloc(); + LoaderStatus status = loader_start(loader, name, args, error_message); + + // TODO: we have many places where we can emit a double start, ex: desktop, menu + // so i prefer to not show LoaderStatusErrorAppStarted error message for now + if(status == LoaderStatusErrorUnknownApp || status == LoaderStatusErrorInternal) { + DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); + DialogMessage* message = dialog_message_alloc(); + dialog_message_set_header(message, "Error", 64, 0, AlignCenter, AlignTop); + dialog_message_set_buttons(message, NULL, NULL, NULL); + + furi_string_replace(error_message, ":", "\n"); + dialog_message_set_text( + message, furi_string_get_cstr(error_message), 64, 32, AlignCenter, AlignCenter); + + dialog_message_show(dialogs, message); + dialog_message_free(message); + furi_record_close(RECORD_DIALOGS); + } + + furi_string_free(error_message); + return status; +} + bool loader_lock(Loader* loader) { LoaderMessage message; LoaderMessageBoolResult result; @@ -73,27 +105,26 @@ static void loader_menu_closed_callback(void* context) { furi_message_queue_put(loader->queue, &message, FuriWaitForever); } -static void loader_menu_click_callback(const char* name, void* context) { +static void loader_applications_closed_callback(void* context) { Loader* loader = context; - loader_start(loader, name, NULL); + LoaderMessage message; + message.type = LoaderMessageTypeApplicationsClosed; + furi_message_queue_put(loader->queue, &message, FuriWaitForever); } static void loader_thread_state_callback(FuriThreadState thread_state, void* context) { furi_assert(context); Loader* loader = context; - LoaderEvent event; if(thread_state == FuriThreadStateRunning) { + LoaderEvent event; event.type = LoaderEventTypeApplicationStarted; furi_pubsub_publish(loader->pubsub, &event); } else if(thread_state == FuriThreadStateStopped) { LoaderMessage message; message.type = LoaderMessageTypeAppClosed; furi_message_queue_put(loader->queue, &message, FuriWaitForever); - - event.type = LoaderEventTypeApplicationStopped; - furi_pubsub_publish(loader->pubsub, &event); } } @@ -104,16 +135,17 @@ static Loader* loader_alloc() { loader->pubsub = furi_pubsub_alloc(); loader->queue = furi_message_queue_alloc(1, sizeof(LoaderMessage)); loader->loader_menu = NULL; + loader->loader_applications = NULL; loader->app.args = NULL; - loader->app.name = NULL; loader->app.thread = NULL; loader->app.insomniac = false; + loader->app.fap = NULL; return loader; } -static FlipperApplication const* loader_find_application_by_name_in_list( +static FlipperInternalApplication const* loader_find_application_by_name_in_list( const char* name, - const FlipperApplication* list, + const FlipperInternalApplication* list, const uint32_t n_apps) { for(size_t i = 0; i < n_apps; i++) { if(strcmp(name, list[i].name) == 0) { @@ -123,8 +155,8 @@ static FlipperApplication const* loader_find_application_by_name_in_list( return NULL; } -static const FlipperApplication* loader_find_application_by_name(const char* name) { - const FlipperApplication* application = NULL; +static const FlipperInternalApplication* loader_find_application_by_name(const char* name) { + const FlipperInternalApplication* application = NULL; application = loader_find_application_by_name_in_list(name, FLIPPER_APPS, FLIPPER_APPS_COUNT); if(!application) { application = loader_find_application_by_name_in_list( @@ -138,25 +170,7 @@ static const FlipperApplication* loader_find_application_by_name(const char* nam return application; } -static void - loader_start_internal_app(Loader* loader, const FlipperApplication* app, const char* args) { - FURI_LOG_I(TAG, "Starting %s", app->name); - - // store args - furi_assert(loader->app.args == NULL); - if(args && strlen(args) > 0) { - loader->app.args = strdup(args); - } - - // store name - furi_assert(loader->app.name == NULL); - loader->app.name = strdup(app->name); - - // setup app thread - loader->app.thread = - furi_thread_alloc_ex(app->name, app->stack_size, app->app, loader->app.args); - furi_thread_set_appid(loader->app.thread, app->appid); - +static void loader_start_app_thread(Loader* loader, FlipperInternalApplicationFlag flags) { // setup heap trace FuriHalRtcHeapTrackMode mode = furi_hal_rtc_get_heap_track_mode(); if(mode > FuriHalRtcHeapTrackModeNone) { @@ -166,14 +180,14 @@ static void } // setup insomnia - if(!(app->flags & FlipperApplicationFlagInsomniaSafe)) { + if(!(flags & FlipperInternalApplicationFlagInsomniaSafe)) { furi_hal_power_insomnia_enter(); loader->app.insomniac = true; } else { loader->app.insomniac = false; } - // setup app thread callbacks + // setup thread state callbacks furi_thread_set_state_context(loader->app.thread, loader); furi_thread_set_state_callback(loader->app.thread, loader_thread_state_callback); @@ -181,42 +195,206 @@ static void furi_thread_start(loader->app.thread); } +static void loader_start_internal_app( + Loader* loader, + const FlipperInternalApplication* app, + const char* args) { + FURI_LOG_I(TAG, "Starting %s", app->name); + + // store args + furi_assert(loader->app.args == NULL); + if(args && strlen(args) > 0) { + loader->app.args = strdup(args); + } + + loader->app.thread = + furi_thread_alloc_ex(app->name, app->stack_size, app->app, loader->app.args); + furi_thread_set_appid(loader->app.thread, app->appid); + + loader_start_app_thread(loader, app->flags); +} + +static void loader_log_status_error( + LoaderStatus status, + FuriString* error_message, + const char* format, + va_list args) { + if(error_message) { + furi_string_vprintf(error_message, format, args); + FURI_LOG_E(TAG, "Status [%d]: %s", status, furi_string_get_cstr(error_message)); + } else { + FuriString* tmp = furi_string_alloc(); + FURI_LOG_E(TAG, "Status [%d]: %s", status, furi_string_get_cstr(tmp)); + furi_string_free(tmp); + } +} + +static LoaderStatus loader_make_status_error( + LoaderStatus status, + FuriString* error_message, + const char* format, + ...) { + va_list args; + va_start(args, format); + loader_log_status_error(status, error_message, format, args); + va_end(args); + return status; +} + +static LoaderStatus loader_make_success_status(FuriString* error_message) { + if(error_message) { + furi_string_set(error_message, "App started"); + } + + return LoaderStatusOk; +} + +static LoaderStatus loader_start_external_app( + Loader* loader, + Storage* storage, + const char* path, + const char* args, + FuriString* error_message) { + LoaderStatus status = loader_make_success_status(error_message); + + do { + loader->app.fap = flipper_application_alloc(storage, firmware_api_interface); + size_t start = furi_get_tick(); + + FURI_LOG_I(TAG, "Loading %s", path); + + FlipperApplicationPreloadStatus preload_res = + flipper_application_preload(loader->app.fap, path); + if(preload_res != FlipperApplicationPreloadStatusSuccess) { + const char* err_msg = flipper_application_preload_status_to_string(preload_res); + status = loader_make_status_error( + LoaderStatusErrorInternal, error_message, "Preload failed %s: %s", path, err_msg); + break; + } + + FURI_LOG_I(TAG, "Mapping"); + FlipperApplicationLoadStatus load_status = + flipper_application_map_to_memory(loader->app.fap); + if(load_status != FlipperApplicationLoadStatusSuccess) { + const char* err_msg = flipper_application_load_status_to_string(load_status); + status = loader_make_status_error( + LoaderStatusErrorInternal, error_message, "Load failed %s: %s", path, err_msg); + break; + } + + FURI_LOG_I(TAG, "Loaded in %zums", (size_t)(furi_get_tick() - start)); + FURI_LOG_I(TAG, "Starting app"); + + loader->app.thread = flipper_application_alloc_thread(loader->app.fap, args); + FuriString* app_name = furi_string_alloc(); + path_extract_filename_no_ext(path, app_name); + furi_thread_set_appid(loader->app.thread, furi_string_get_cstr(app_name)); + furi_string_free(app_name); + + /* This flag is set by the debugger - to break on app start */ + if(furi_hal_debug_is_gdb_session_active()) { + FURI_LOG_W(TAG, "Triggering BP for debugger"); + /* After hitting this, you can set breakpoints in your .fap's code + * Note that you have to toggle breakpoints that were set before */ + __asm volatile("bkpt 0"); + } + + loader_start_app_thread(loader, FlipperInternalApplicationFlagDefault); + } while(0); + + if(status != LoaderStatusOk) { + flipper_application_free(loader->app.fap); + loader->app.fap = NULL; + } + + return status; +} + // process messages static void loader_do_menu_show(Loader* loader) { if(!loader->loader_menu) { - loader->loader_menu = loader_menu_alloc(); - loader_menu_set_closed_callback(loader->loader_menu, loader_menu_closed_callback, loader); - loader_menu_set_click_callback(loader->loader_menu, loader_menu_click_callback, loader); - loader_menu_start(loader->loader_menu); + loader->loader_menu = loader_menu_alloc(loader_menu_closed_callback, loader); } } static void loader_do_menu_closed(Loader* loader) { if(loader->loader_menu) { - loader_menu_stop(loader->loader_menu); loader_menu_free(loader->loader_menu); loader->loader_menu = NULL; } } +static void loader_do_applications_show(Loader* loader) { + if(!loader->loader_applications) { + loader->loader_applications = + loader_applications_alloc(loader_applications_closed_callback, loader); + } +} + +static void loader_do_applications_closed(Loader* loader) { + if(loader->loader_applications) { + loader_applications_free(loader->loader_applications); + loader->loader_applications = NULL; + } +} + static bool loader_do_is_locked(Loader* loader) { return loader->app.thread != NULL; } -static LoaderStatus loader_do_start_by_name(Loader* loader, const char* name, const char* args) { - if(loader_do_is_locked(loader)) { - return LoaderStatusErrorAppStarted; - } +static LoaderStatus loader_do_start_by_name( + Loader* loader, + const char* name, + const char* args, + FuriString* error_message) { + LoaderStatus status; + do { + // check lock + if(loader_do_is_locked(loader)) { + const char* current_thread_name = + furi_thread_get_name(furi_thread_get_id(loader->app.thread)); + status = loader_make_status_error( + LoaderStatusErrorAppStarted, + error_message, + "Loader is locked, please close the \"%s\" first", + current_thread_name); + break; + } + + // check internal apps + { + const FlipperInternalApplication* app = loader_find_application_by_name(name); + if(app) { + loader_start_internal_app(loader, app, args); + status = loader_make_success_status(error_message); + break; + } + } - const FlipperApplication* app = loader_find_application_by_name(name); + // check Applications + if(strcmp(name, LOADER_APPLICATIONS_NAME) == 0) { + loader_do_applications_show(loader); + status = loader_make_success_status(error_message); + break; + } - if(!app) { - return LoaderStatusErrorUnknownApp; - } + // check external apps + { + Storage* storage = furi_record_open(RECORD_STORAGE); + if(storage_file_exists(storage, name)) { + status = loader_start_external_app(loader, storage, name, args, error_message); + furi_record_close(RECORD_STORAGE); + break; + } + furi_record_close(RECORD_STORAGE); + } - loader_start_internal_app(loader, app, args); - return LoaderStatusOk; + status = loader_make_status_error( + LoaderStatusErrorUnknownApp, error_message, "Application \"%s\" not found", name); + } while(false); + + return status; } static bool loader_do_lock(Loader* loader) { @@ -229,13 +407,16 @@ static bool loader_do_lock(Loader* loader) { } static void loader_do_unlock(Loader* loader) { - furi_assert(loader->app.thread == (FuriThread*)LOADER_MAGIC_THREAD_VALUE); + furi_check(loader->app.thread == (FuriThread*)LOADER_MAGIC_THREAD_VALUE); loader->app.thread = NULL; } static void loader_do_app_closed(Loader* loader) { furi_assert(loader->app.thread); - FURI_LOG_I(TAG, "Application stopped. Free heap: %zu", memmgr_get_free_heap()); + + furi_thread_join(loader->app.thread); + FURI_LOG_I(TAG, "App returned: %li", furi_thread_get_return_code(loader->app.thread)); + if(loader->app.args) { free(loader->app.args); loader->app.args = NULL; @@ -245,12 +426,20 @@ static void loader_do_app_closed(Loader* loader) { furi_hal_power_insomnia_exit(); } - free(loader->app.name); - loader->app.name = NULL; + if(loader->app.fap) { + flipper_application_free(loader->app.fap); + loader->app.fap = NULL; + loader->app.thread = NULL; + } else { + furi_thread_free(loader->app.thread); + loader->app.thread = NULL; + } - furi_thread_join(loader->app.thread); - furi_thread_free(loader->app.thread); - loader->app.thread = NULL; + FURI_LOG_I(TAG, "Application stopped. Free heap: %zu", memmgr_get_free_heap()); + + LoaderEvent event; + event.type = LoaderEventTypeApplicationStopped; + furi_pubsub_publish(loader->pubsub, &event); } // app @@ -266,7 +455,7 @@ int32_t loader_srv(void* p) { } if(FLIPPER_AUTORUN_APP_NAME && strlen(FLIPPER_AUTORUN_APP_NAME)) { - loader_do_start_by_name(loader, FLIPPER_AUTORUN_APP_NAME, NULL); + loader_do_start_by_name(loader, FLIPPER_AUTORUN_APP_NAME, NULL, NULL); } LoaderMessage message; @@ -274,8 +463,8 @@ int32_t loader_srv(void* p) { if(furi_message_queue_get(loader->queue, &message, FuriWaitForever) == FuriStatusOk) { switch(message.type) { case LoaderMessageTypeStartByName: - message.status_value->value = - loader_do_start_by_name(loader, message.start.name, message.start.args); + message.status_value->value = loader_do_start_by_name( + loader, message.start.name, message.start.args, message.start.error_message); api_lock_unlock(message.api_lock); break; case LoaderMessageTypeShowMenu: @@ -297,6 +486,10 @@ int32_t loader_srv(void* p) { break; case LoaderMessageTypeUnlock: loader_do_unlock(loader); + break; + case LoaderMessageTypeApplicationsClosed: + loader_do_applications_closed(loader); + break; } } } diff --git a/applications/services/loader/loader.h b/applications/services/loader/loader.h index e3a691b768c..9fc4059f2d9 100644 --- a/applications/services/loader/loader.h +++ b/applications/services/loader/loader.h @@ -6,6 +6,7 @@ extern "C" { #endif #define RECORD_LOADER "loader" +#define LOADER_APPLICATIONS_NAME "Applications" typedef struct Loader Loader; @@ -25,28 +26,57 @@ typedef struct { LoaderEventType type; } LoaderEvent; -/** Start application - * @param name - application name - * @param args - application arguments - * @retval true on success +/** + * @brief Start application + * @param[in] instance loader instance + * @param[in] name application name + * @param[in] args application arguments + * @param[out] error_message detailed error message, can be NULL + * @return LoaderStatus */ -LoaderStatus loader_start(Loader* instance, const char* name, const char* args); +LoaderStatus + loader_start(Loader* instance, const char* name, const char* args, FuriString* error_message); -/** Lock application start - * @retval true on success +/** + * @brief Start application with GUI error message + * @param[in] instance loader instance + * @param[in] name application name + * @param[in] args application arguments + * @return LoaderStatus + */ +LoaderStatus loader_start_with_gui_error(Loader* loader, const char* name, const char* args); + +/** + * @brief Lock application start + * @param[in] instance loader instance + * @return true on success */ bool loader_lock(Loader* instance); -/** Unlock application start */ +/** + * @brief Unlock application start + * @param[in] instance loader instance + */ void loader_unlock(Loader* instance); -/** Get loader lock status */ +/** + * @brief Check if loader is locked + * @param[in] instance loader instance + * @return true if locked + */ bool loader_is_locked(Loader* instance); -/** Show primary loader */ +/** + * @brief Show loader menu + * @param[in] instance loader instance + */ void loader_show_menu(Loader* instance); -/** Show primary loader */ +/** + * @brief Get loader pubsub + * @param[in] instance loader instance + * @return FuriPubSub* + */ FuriPubSub* loader_get_pubsub(Loader* instance); #ifdef __cplusplus diff --git a/applications/services/loader/loader_applications.c b/applications/services/loader/loader_applications.c new file mode 100644 index 00000000000..1801edef9f9 --- /dev/null +++ b/applications/services/loader/loader_applications.c @@ -0,0 +1,146 @@ +#include "loader.h" +#include "loader_applications.h" +#include +#include +#include +#include +#include +#include + +#define TAG "LoaderApplications" + +struct LoaderApplications { + FuriThread* thread; + void (*closed_cb)(void*); + void* context; +}; + +static int32_t loader_applications_thread(void* p); + +LoaderApplications* loader_applications_alloc(void (*closed_cb)(void*), void* context) { + LoaderApplications* loader_applications = malloc(sizeof(LoaderApplications)); + loader_applications->thread = + furi_thread_alloc_ex(TAG, 512, loader_applications_thread, (void*)loader_applications); + loader_applications->closed_cb = closed_cb; + loader_applications->context = context; + furi_thread_start(loader_applications->thread); + return loader_applications; +} + +void loader_applications_free(LoaderApplications* loader_applications) { + furi_assert(loader_applications); + furi_thread_join(loader_applications->thread); + furi_thread_free(loader_applications->thread); + free(loader_applications); +} + +typedef struct { + FuriString* fap_path; + DialogsApp* dialogs; + Storage* storage; +} LoaderApplicationsApp; + +static LoaderApplicationsApp* loader_applications_app_alloc() { + LoaderApplicationsApp* app = malloc(sizeof(LoaderApplicationsApp)); //-V799 + app->fap_path = furi_string_alloc_set(EXT_PATH("apps")); + app->dialogs = furi_record_open(RECORD_DIALOGS); + app->storage = furi_record_open(RECORD_STORAGE); + return app; +} //-V773 + +static void loader_applications_app_free(LoaderApplicationsApp* loader_applications_app) { + furi_assert(loader_applications_app); + furi_record_close(RECORD_DIALOGS); + furi_record_close(RECORD_STORAGE); + furi_string_free(loader_applications_app->fap_path); + free(loader_applications_app); +} + +static bool loader_applications_item_callback( + FuriString* path, + void* context, + uint8_t** icon_ptr, + FuriString* item_name) { + LoaderApplicationsApp* loader_applications_app = context; + furi_assert(loader_applications_app); + return flipper_application_load_name_and_icon( + path, loader_applications_app->storage, icon_ptr, item_name); +} + +static bool loader_applications_select_app(LoaderApplicationsApp* loader_applications_app) { + const DialogsFileBrowserOptions browser_options = { + .extension = ".fap", + .skip_assets = true, + .icon = &I_unknown_10px, + .hide_ext = true, + .item_loader_callback = loader_applications_item_callback, + .item_loader_context = loader_applications_app, + .base_path = EXT_PATH("apps"), + }; + + return dialog_file_browser_show( + loader_applications_app->dialogs, + loader_applications_app->fap_path, + loader_applications_app->fap_path, + &browser_options); +} + +#define APPLICATION_STOP_EVENT 1 + +static void loader_pubsub_callback(const void* message, void* context) { + const LoaderEvent* event = message; + const FuriThreadId thread_id = (FuriThreadId)context; + + if(event->type == LoaderEventTypeApplicationStopped) { + furi_thread_flags_set(thread_id, APPLICATION_STOP_EVENT); + } +} + +static void loader_applications_start_app(const char* name) { + // start loading animation + Gui* gui = furi_record_open(RECORD_GUI); + ViewHolder* view_holder = view_holder_alloc(); + Loading* loading = loading_alloc(); + + view_holder_attach_to_gui(view_holder, gui); + view_holder_set_view(view_holder, loading_get_view(loading)); + view_holder_start(view_holder); + + // load app + FuriThreadId thread_id = furi_thread_get_current_id(); + Loader* loader = furi_record_open(RECORD_LOADER); + FuriPubSubSubscription* subscription = + furi_pubsub_subscribe(loader_get_pubsub(loader), loader_pubsub_callback, thread_id); + + LoaderStatus status = loader_start_with_gui_error(loader, name, NULL); + + if(status == LoaderStatusOk) { + furi_thread_flags_wait(APPLICATION_STOP_EVENT, FuriFlagWaitAny, FuriWaitForever); + } + + furi_pubsub_unsubscribe(loader_get_pubsub(loader), subscription); + furi_record_close(RECORD_LOADER); + + // stop loading animation + view_holder_stop(view_holder); + view_holder_free(view_holder); + loading_free(loading); + furi_record_close(RECORD_GUI); +} + +static int32_t loader_applications_thread(void* p) { + LoaderApplications* loader_applications = p; + LoaderApplicationsApp* loader_applications_app = loader_applications_app_alloc(); + + while(loader_applications_select_app(loader_applications_app)) { + loader_applications_start_app(furi_string_get_cstr(loader_applications_app->fap_path)); + } + + loader_applications_app_free(loader_applications_app); + + if(loader_applications->closed_cb) { + loader_applications->closed_cb(loader_applications->context); + } + + return 0; +} \ No newline at end of file diff --git a/applications/services/loader/loader_applications.h b/applications/services/loader/loader_applications.h new file mode 100644 index 00000000000..6b132af0559 --- /dev/null +++ b/applications/services/loader/loader_applications.h @@ -0,0 +1,16 @@ +#pragma once +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct LoaderApplications LoaderApplications; + +LoaderApplications* loader_applications_alloc(void (*closed_cb)(void*), void* context); + +void loader_applications_free(LoaderApplications* loader_applications); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/applications/services/loader/loader_cli.c b/applications/services/loader/loader_cli.c index 2d460221578..af3ebf9e002 100644 --- a/applications/services/loader/loader_cli.c +++ b/applications/services/loader/loader_cli.c @@ -50,21 +50,11 @@ static void loader_cli_open(FuriString* args, Loader* loader) { const char* app_name_str = furi_string_get_cstr(app_name); - LoaderStatus status = loader_start(loader, app_name_str, args_str); - - switch(status) { - case LoaderStatusOk: - break; - case LoaderStatusErrorAppStarted: - printf("Can't start, application is running"); - break; - case LoaderStatusErrorUnknownApp: - printf("%s doesn't exists\r\n", app_name_str); - break; - case LoaderStatusErrorInternal: - printf("Internal error\r\n"); - break; + FuriString* error_message = furi_string_alloc(); + if(loader_start(loader, app_name_str, args_str, error_message) != LoaderStatusOk) { + printf("%s\r\n", furi_string_get_cstr(error_message)); } + furi_string_free(error_message); } while(false); furi_string_free(app_name); diff --git a/applications/services/loader/loader_i.h b/applications/services/loader/loader_i.h index 2e3f10dad02..688b8fb665c 100644 --- a/applications/services/loader/loader_i.h +++ b/applications/services/loader/loader_i.h @@ -1,20 +1,23 @@ #pragma once #include #include +#include #include "loader.h" #include "loader_menu.h" +#include "loader_applications.h" typedef struct { char* args; - char* name; FuriThread* thread; bool insomniac; + FlipperApplication* fap; } LoaderAppData; struct Loader { FuriPubSub* pubsub; FuriMessageQueue* queue; LoaderMenu* loader_menu; + LoaderApplications* loader_applications; LoaderAppData app; }; @@ -23,6 +26,7 @@ typedef enum { LoaderMessageTypeAppClosed, LoaderMessageTypeShowMenu, LoaderMessageTypeMenuClosed, + LoaderMessageTypeApplicationsClosed, LoaderMessageTypeLock, LoaderMessageTypeUnlock, LoaderMessageTypeIsLocked, @@ -31,6 +35,7 @@ typedef enum { typedef struct { const char* name; const char* args; + FuriString* error_message; } LoaderMessageStartByName; typedef struct { diff --git a/applications/services/loader/loader_menu.c b/applications/services/loader/loader_menu.c index ec853661fbb..28283f85cd2 100644 --- a/applications/services/loader/loader_menu.c +++ b/applications/services/loader/loader_menu.c @@ -5,106 +5,76 @@ #include #include +#include "loader.h" #include "loader_menu.h" #define TAG "LoaderMenu" struct LoaderMenu { - Gui* gui; - ViewDispatcher* view_dispatcher; - Menu* primary_menu; - Submenu* settings_menu; - - void (*closed_callback)(void*); - void* closed_callback_context; - - void (*click_callback)(const char*, void*); - void* click_callback_context; - FuriThread* thread; + void (*closed_cb)(void*); + void* context; }; -typedef enum { - LoaderMenuViewPrimary, - LoaderMenuViewSettings, -} LoaderMenuView; - static int32_t loader_menu_thread(void* p); -LoaderMenu* loader_menu_alloc() { +LoaderMenu* loader_menu_alloc(void (*closed_cb)(void*), void* context) { LoaderMenu* loader_menu = malloc(sizeof(LoaderMenu)); - loader_menu->gui = furi_record_open(RECORD_GUI); - loader_menu->view_dispatcher = view_dispatcher_alloc(); - loader_menu->primary_menu = menu_alloc(); - loader_menu->settings_menu = submenu_alloc(); - loader_menu->thread = NULL; - return loader_menu; -} - -void loader_menu_free(LoaderMenu* loader_menu) { - furi_assert(loader_menu); - // check if thread is running - furi_assert(!loader_menu->thread); - - submenu_free(loader_menu->settings_menu); - menu_free(loader_menu->primary_menu); - view_dispatcher_free(loader_menu->view_dispatcher); - furi_record_close(RECORD_GUI); - free(loader_menu); -} - -void loader_menu_start(LoaderMenu* loader_menu) { - furi_assert(loader_menu); - furi_assert(!loader_menu->thread); + loader_menu->closed_cb = closed_cb; + loader_menu->context = context; loader_menu->thread = furi_thread_alloc_ex(TAG, 1024, loader_menu_thread, loader_menu); furi_thread_start(loader_menu->thread); + return loader_menu; } -void loader_menu_stop(LoaderMenu* loader_menu) { +void loader_menu_free(LoaderMenu* loader_menu) { furi_assert(loader_menu); - furi_assert(loader_menu->thread); - view_dispatcher_stop(loader_menu->view_dispatcher); furi_thread_join(loader_menu->thread); furi_thread_free(loader_menu->thread); - loader_menu->thread = NULL; + free(loader_menu); } -void loader_menu_set_closed_callback( - LoaderMenu* loader_menu, - void (*callback)(void*), - void* context) { - loader_menu->closed_callback = callback; - loader_menu->closed_callback_context = context; -} +typedef enum { + LoaderMenuViewPrimary, + LoaderMenuViewSettings, +} LoaderMenuView; + +typedef struct { + Gui* gui; + ViewDispatcher* view_dispatcher; + Menu* primary_menu; + Submenu* settings_menu; +} LoaderMenuApp; -void loader_menu_set_click_callback( - LoaderMenu* loader_menu, - void (*callback)(const char*, void*), - void* context) { - loader_menu->click_callback = callback; - loader_menu->click_callback_context = context; +static void loader_menu_start(const char* name) { + Loader* loader = furi_record_open(RECORD_LOADER); + loader_start_with_gui_error(loader, name, NULL); + furi_record_close(RECORD_LOADER); } static void loader_menu_callback(void* context, uint32_t index) { - LoaderMenu* loader_menu = context; + UNUSED(context); const char* name = FLIPPER_APPS[index].name; - if(loader_menu->click_callback) { - loader_menu->click_callback(name, loader_menu->click_callback_context); - } + loader_menu_start(name); +} + +static void loader_menu_applications_callback(void* context, uint32_t index) { + UNUSED(index); + UNUSED(context); + const char* name = LOADER_APPLICATIONS_NAME; + loader_menu_start(name); } static void loader_menu_settings_menu_callback(void* context, uint32_t index) { - LoaderMenu* loader_menu = context; + UNUSED(context); const char* name = FLIPPER_SETTINGS_APPS[index].name; - if(loader_menu->click_callback) { - loader_menu->click_callback(name, loader_menu->click_callback_context); - } + loader_menu_start(name); } static void loader_menu_switch_to_settings(void* context, uint32_t index) { UNUSED(index); - LoaderMenu* loader_menu = context; - view_dispatcher_switch_to_view(loader_menu->view_dispatcher, LoaderMenuViewSettings); + LoaderMenuApp* app = context; + view_dispatcher_switch_to_view(app->view_dispatcher, LoaderMenuViewSettings); } static uint32_t loader_menu_switch_to_primary(void* context) { @@ -117,30 +87,32 @@ static uint32_t loader_menu_exit(void* context) { return VIEW_NONE; } -static void loader_menu_build_menu(LoaderMenu* loader_menu) { +static void loader_menu_build_menu(LoaderMenuApp* app, LoaderMenu* menu) { size_t i; for(i = 0; i < FLIPPER_APPS_COUNT; i++) { menu_add_item( - loader_menu->primary_menu, + app->primary_menu, FLIPPER_APPS[i].name, FLIPPER_APPS[i].icon, i, loader_menu_callback, - (void*)loader_menu); + (void*)menu); } menu_add_item( - loader_menu->primary_menu, - "Settings", - &A_Settings_14, + app->primary_menu, "Settings", &A_Settings_14, i++, loader_menu_switch_to_settings, app); + menu_add_item( + app->primary_menu, + LOADER_APPLICATIONS_NAME, + &A_Plugins_14, i++, - loader_menu_switch_to_settings, - loader_menu); + loader_menu_applications_callback, + (void*)menu); }; -static void loader_menu_build_submenu(LoaderMenu* loader_menu) { +static void loader_menu_build_submenu(LoaderMenuApp* app, LoaderMenu* loader_menu) { for(size_t i = 0; i < FLIPPER_SETTINGS_APPS_COUNT; i++) { submenu_add_item( - loader_menu->settings_menu, + app->settings_menu, FLIPPER_SETTINGS_APPS[i].name, i, loader_menu_settings_menu_callback, @@ -148,40 +120,59 @@ static void loader_menu_build_submenu(LoaderMenu* loader_menu) { } } -static int32_t loader_menu_thread(void* p) { - LoaderMenu* loader_menu = p; - furi_assert(loader_menu); - - loader_menu_build_menu(loader_menu); - loader_menu_build_submenu(loader_menu); +static LoaderMenuApp* loader_menu_app_alloc(LoaderMenu* loader_menu) { + LoaderMenuApp* app = malloc(sizeof(LoaderMenuApp)); + app->gui = furi_record_open(RECORD_GUI); + app->view_dispatcher = view_dispatcher_alloc(); + app->primary_menu = menu_alloc(); + app->settings_menu = submenu_alloc(); - view_dispatcher_attach_to_gui( - loader_menu->view_dispatcher, loader_menu->gui, ViewDispatcherTypeFullscreen); + loader_menu_build_menu(app, loader_menu); + loader_menu_build_submenu(app, loader_menu); // Primary menu - View* primary_view = menu_get_view(loader_menu->primary_menu); - view_set_context(primary_view, loader_menu->primary_menu); + View* primary_view = menu_get_view(app->primary_menu); + view_set_context(primary_view, app->primary_menu); view_set_previous_callback(primary_view, loader_menu_exit); - view_dispatcher_add_view(loader_menu->view_dispatcher, LoaderMenuViewPrimary, primary_view); + view_dispatcher_add_view(app->view_dispatcher, LoaderMenuViewPrimary, primary_view); // Settings menu - View* settings_view = submenu_get_view(loader_menu->settings_menu); - view_set_context(settings_view, loader_menu->settings_menu); + View* settings_view = submenu_get_view(app->settings_menu); + view_set_context(settings_view, app->settings_menu); view_set_previous_callback(settings_view, loader_menu_switch_to_primary); - view_dispatcher_add_view(loader_menu->view_dispatcher, LoaderMenuViewSettings, settings_view); + view_dispatcher_add_view(app->view_dispatcher, LoaderMenuViewSettings, settings_view); - view_dispatcher_enable_queue(loader_menu->view_dispatcher); - view_dispatcher_switch_to_view(loader_menu->view_dispatcher, LoaderMenuViewPrimary); + view_dispatcher_enable_queue(app->view_dispatcher); + view_dispatcher_switch_to_view(app->view_dispatcher, LoaderMenuViewPrimary); - // run view dispatcher - view_dispatcher_run(loader_menu->view_dispatcher); + return app; +} + +static void loader_menu_app_free(LoaderMenuApp* app) { + view_dispatcher_remove_view(app->view_dispatcher, LoaderMenuViewPrimary); + view_dispatcher_remove_view(app->view_dispatcher, LoaderMenuViewSettings); + view_dispatcher_free(app->view_dispatcher); - view_dispatcher_remove_view(loader_menu->view_dispatcher, LoaderMenuViewPrimary); - view_dispatcher_remove_view(loader_menu->view_dispatcher, LoaderMenuViewSettings); + menu_free(app->primary_menu); + submenu_free(app->settings_menu); + furi_record_close(RECORD_GUI); + free(app); +} - if(loader_menu->closed_callback) { - loader_menu->closed_callback(loader_menu->closed_callback_context); +static int32_t loader_menu_thread(void* p) { + LoaderMenu* loader_menu = p; + furi_assert(loader_menu); + + LoaderMenuApp* app = loader_menu_app_alloc(loader_menu); + + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + view_dispatcher_run(app->view_dispatcher); + + if(loader_menu->closed_cb) { + loader_menu->closed_cb(loader_menu->context); } + loader_menu_app_free(app); + return 0; } \ No newline at end of file diff --git a/applications/services/loader/loader_menu.h b/applications/services/loader/loader_menu.h index 7405b87be77..528fe7d291c 100644 --- a/applications/services/loader/loader_menu.h +++ b/applications/services/loader/loader_menu.h @@ -7,24 +7,10 @@ extern "C" { typedef struct LoaderMenu LoaderMenu; -LoaderMenu* loader_menu_alloc(); +LoaderMenu* loader_menu_alloc(void (*closed_cb)(void*), void* context); void loader_menu_free(LoaderMenu* loader_menu); -void loader_menu_start(LoaderMenu* loader_menu); - -void loader_menu_stop(LoaderMenu* loader_menu); - -void loader_menu_set_closed_callback( - LoaderMenu* loader_menu, - void (*callback)(void*), - void* context); - -void loader_menu_set_click_callback( - LoaderMenu* loader_menu, - void (*callback)(const char*, void*), - void* context); - #ifdef __cplusplus } #endif \ No newline at end of file diff --git a/applications/services/rpc/rpc_app.c b/applications/services/rpc/rpc_app.c index cc18b6cec37..bf44ed2deed 100644 --- a/applications/services/rpc/rpc_app.c +++ b/applications/services/rpc/rpc_app.c @@ -52,7 +52,7 @@ static void rpc_system_app_start_process(const PB_Main* request, void* context) snprintf(args_temp, RPC_SYSTEM_APP_TEMP_ARGS_SIZE, "RPC %08lX", (uint32_t)rpc_app); app_args = args_temp; } - LoaderStatus status = loader_start(loader, app_name, app_args); + LoaderStatus status = loader_start(loader, app_name, app_args, NULL); if(status == LoaderStatusErrorAppStarted) { result = PB_CommandStatus_ERROR_APP_SYSTEM_LOCKED; } else if(status == LoaderStatusErrorInternal) { diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c index 4b5c4792123..698cfae1b81 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c @@ -3,7 +3,6 @@ #include "desktop_settings_scene.h" #include #include -#include #define EXTERNAL_APPLICATION_NAME ("[External Application]") #define EXTERNAL_APPLICATION_INDEX (FLIPPER_APPS_COUNT + 1) @@ -65,7 +64,6 @@ void desktop_settings_scene_favorite_on_enter(void* context) { } } -#ifdef APP_FAP_LOADER submenu_add_item( submenu, EXTERNAL_APPLICATION_NAME, @@ -75,7 +73,6 @@ void desktop_settings_scene_favorite_on_enter(void* context) { if(curr_favorite_app->is_external) { pre_select_item = EXTERNAL_APPLICATION_INDEX; } -#endif submenu_set_header( submenu, primary_favorite ? "Primary favorite app:" : "Secondary favorite app:"); diff --git a/applications/system/storage_move_to_sd/storage_move_to_sd.c b/applications/system/storage_move_to_sd/storage_move_to_sd.c index 9c91b926680..25893f01106 100644 --- a/applications/system/storage_move_to_sd/storage_move_to_sd.c +++ b/applications/system/storage_move_to_sd/storage_move_to_sd.c @@ -172,7 +172,7 @@ static void storage_move_to_sd_mount_callback(const void* message, void* context if(storage_event->type == StorageEventTypeCardMount) { Loader* loader = furi_record_open(RECORD_LOADER); - loader_start(loader, "StorageMoveToSd", NULL); + loader_start(loader, "StorageMoveToSd", NULL, NULL); furi_record_close(RECORD_LOADER); } } diff --git a/applications/system/updater/cli/updater_cli.c b/applications/system/updater/cli/updater_cli.c index 659c431f701..cebdc4d7cab 100644 --- a/applications/system/updater/cli/updater_cli.c +++ b/applications/system/updater/cli/updater_cli.c @@ -99,7 +99,7 @@ static void updater_start_app(void* context, uint32_t arg) { * So, accessing its record would cause a deadlock */ Loader* loader = furi_record_open(RECORD_LOADER); - loader_start(loader, "UpdaterApp", NULL); + loader_start(loader, "UpdaterApp", NULL, NULL); furi_record_close(RECORD_LOADER); } diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index d429de655a2..101ea92a6a0 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,30.1,, +Version,+,31.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -735,9 +735,11 @@ Function,+,filesystem_api_error_get_desc,const char*,FS_Error Function,-,fiprintf,int,"FILE*, const char*, ..." Function,-,fiscanf,int,"FILE*, const char*, ..." Function,+,flipper_application_alloc,FlipperApplication*,"Storage*, const ElfApiInterface*" +Function,+,flipper_application_alloc_thread,FuriThread*,"FlipperApplication*, const char*" Function,+,flipper_application_free,void,FlipperApplication* Function,+,flipper_application_get_manifest,const FlipperApplicationManifest*,FlipperApplication* Function,+,flipper_application_is_plugin,_Bool,FlipperApplication* +Function,+,flipper_application_load_name_and_icon,_Bool,"FuriString*, Storage*, uint8_t**, FuriString*" Function,+,flipper_application_load_status_to_string,const char*,FlipperApplicationLoadStatus Function,+,flipper_application_manifest_is_compatible,_Bool,"const FlipperApplicationManifest*, const ElfApiInterface*" Function,+,flipper_application_manifest_is_target_compatible,_Bool,const FlipperApplicationManifest* @@ -747,7 +749,6 @@ Function,+,flipper_application_plugin_get_descriptor,const FlipperAppPluginDescr Function,+,flipper_application_preload,FlipperApplicationPreloadStatus,"FlipperApplication*, const char*" Function,+,flipper_application_preload_manifest,FlipperApplicationPreloadStatus,"FlipperApplication*, const char*" Function,+,flipper_application_preload_status_to_string,const char*,FlipperApplicationPreloadStatus -Function,+,flipper_application_spawn,FuriThread*,"FlipperApplication*, void*" Function,+,flipper_format_buffered_file_alloc,FlipperFormat*,Storage* Function,+,flipper_format_buffered_file_close,_Bool,FlipperFormat* Function,+,flipper_format_buffered_file_open_always,_Bool,"FlipperFormat*, const char*" @@ -1419,7 +1420,8 @@ Function,+,loader_get_pubsub,FuriPubSub*,Loader* Function,+,loader_is_locked,_Bool,Loader* Function,+,loader_lock,_Bool,Loader* Function,+,loader_show_menu,void,Loader* -Function,+,loader_start,LoaderStatus,"Loader*, const char*, const char*" +Function,+,loader_start,LoaderStatus,"Loader*, const char*, const char*, FuriString*" +Function,+,loader_start_with_gui_error,LoaderStatus,"Loader*, const char*, const char*" Function,+,loader_unlock,void,Loader* Function,+,loading_alloc,Loading*, Function,+,loading_free,void,Loading* diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 80b4eedbdc4..5ed26f29611 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,30.1,, +Version,+,31.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -892,9 +892,11 @@ Function,-,finitel,int,long double Function,-,fiprintf,int,"FILE*, const char*, ..." Function,-,fiscanf,int,"FILE*, const char*, ..." Function,+,flipper_application_alloc,FlipperApplication*,"Storage*, const ElfApiInterface*" +Function,+,flipper_application_alloc_thread,FuriThread*,"FlipperApplication*, const char*" Function,+,flipper_application_free,void,FlipperApplication* Function,+,flipper_application_get_manifest,const FlipperApplicationManifest*,FlipperApplication* Function,+,flipper_application_is_plugin,_Bool,FlipperApplication* +Function,+,flipper_application_load_name_and_icon,_Bool,"FuriString*, Storage*, uint8_t**, FuriString*" Function,+,flipper_application_load_status_to_string,const char*,FlipperApplicationLoadStatus Function,+,flipper_application_manifest_is_compatible,_Bool,"const FlipperApplicationManifest*, const ElfApiInterface*" Function,+,flipper_application_manifest_is_target_compatible,_Bool,const FlipperApplicationManifest* @@ -904,7 +906,6 @@ Function,+,flipper_application_plugin_get_descriptor,const FlipperAppPluginDescr Function,+,flipper_application_preload,FlipperApplicationPreloadStatus,"FlipperApplication*, const char*" Function,+,flipper_application_preload_manifest,FlipperApplicationPreloadStatus,"FlipperApplication*, const char*" Function,+,flipper_application_preload_status_to_string,const char*,FlipperApplicationPreloadStatus -Function,+,flipper_application_spawn,FuriThread*,"FlipperApplication*, void*" Function,+,flipper_format_buffered_file_alloc,FlipperFormat*,Storage* Function,+,flipper_format_buffered_file_close,_Bool,FlipperFormat* Function,+,flipper_format_buffered_file_open_always,_Bool,"FlipperFormat*, const char*" @@ -1825,7 +1826,8 @@ Function,+,loader_get_pubsub,FuriPubSub*,Loader* Function,+,loader_is_locked,_Bool,Loader* Function,+,loader_lock,_Bool,Loader* Function,+,loader_show_menu,void,Loader* -Function,+,loader_start,LoaderStatus,"Loader*, const char*, const char*" +Function,+,loader_start,LoaderStatus,"Loader*, const char*, const char*, FuriString*" +Function,+,loader_start_with_gui_error,LoaderStatus,"Loader*, const char*, const char*" Function,+,loader_unlock,void,Loader* Function,+,loading_alloc,Loading*, Function,+,loading_free,void,Loading* diff --git a/lib/flipper_application/flipper_application.c b/lib/flipper_application/flipper_application.c index 1b4f5681486..fbcf2973dbe 100644 --- a/lib/flipper_application/flipper_application.c +++ b/lib/flipper_application/flipper_application.c @@ -2,6 +2,7 @@ #include "elf/elf_file.h" #include #include "application_assets.h" +#include #include @@ -81,6 +82,12 @@ void flipper_application_free(FlipperApplication* app) { } elf_file_free(app->elf); + + if(app->ep_thread_args) { + free(app->ep_thread_args); + app->ep_thread_args = NULL; + } + free(app); } @@ -224,10 +231,19 @@ static int32_t flipper_application_thread(void* context) { return ret_code; } -FuriThread* flipper_application_spawn(FlipperApplication* app, void* args) { +FuriThread* flipper_application_alloc_thread(FlipperApplication* app, const char* args) { furi_check(app->thread == NULL); furi_check(!flipper_application_is_plugin(app)); - app->ep_thread_args = args; + + if(app->ep_thread_args) { + free(app->ep_thread_args); + } + + if(args) { + app->ep_thread_args = strdup(args); + } else { + app->ep_thread_args = NULL; + } const FlipperApplicationManifest* manifest = flipper_application_get_manifest(app); app->thread = furi_thread_alloc_ex( @@ -289,4 +305,32 @@ const FlipperAppPluginDescriptor* lib_descriptor->ep_api_version); return lib_descriptor; +} + +bool flipper_application_load_name_and_icon( + FuriString* path, + Storage* storage, + uint8_t** icon_ptr, + FuriString* item_name) { + FlipperApplication* app = flipper_application_alloc(storage, firmware_api_interface); + + FlipperApplicationPreloadStatus preload_res = + flipper_application_preload_manifest(app, furi_string_get_cstr(path)); + + bool load_success = false; + + if(preload_res == FlipperApplicationPreloadStatusSuccess) { + const FlipperApplicationManifest* manifest = flipper_application_get_manifest(app); + if(manifest->has_icon) { + memcpy(*icon_ptr, manifest->icon, FAP_MANIFEST_MAX_ICON_SIZE); + } + furi_string_set(item_name, manifest->name); + load_success = true; + } else { + FURI_LOG_E(TAG, "Failed to preload %s", furi_string_get_cstr(path)); + load_success = false; + } + + flipper_application_free(app); + return load_success; } \ No newline at end of file diff --git a/lib/flipper_application/flipper_application.h b/lib/flipper_application/flipper_application.h index 519cc397108..20baae8264f 100644 --- a/lib/flipper_application/flipper_application.h +++ b/lib/flipper_application/flipper_application.h @@ -106,14 +106,14 @@ const FlipperApplicationManifest* flipper_application_get_manifest(FlipperApplic FlipperApplicationLoadStatus flipper_application_map_to_memory(FlipperApplication* app); /** - * @brief Create application thread at entry point address, using app name and + * @brief Allocate application thread at entry point address, using app name and * stack size from metadata. Returned thread isn't started yet. * Can be only called once for application instance. * @param app Applicaiton pointer - * @param args Object to pass to app's entry point + * @param args Args to pass to app's entry point * @return Created thread */ -FuriThread* flipper_application_spawn(FlipperApplication* app, void* args); +FuriThread* flipper_application_alloc_thread(FlipperApplication* app, const char* args); /** * @brief Check if application is a plugin (not a runnable standalone app) @@ -149,6 +149,21 @@ typedef const FlipperAppPluginDescriptor* (*FlipperApplicationPluginEntryPoint)( const FlipperAppPluginDescriptor* flipper_application_plugin_get_descriptor(FlipperApplication* app); +/** + * @brief Load name and icon from FAP file. + * + * @param path Path to FAP file. + * @param storage Storage instance. + * @param icon_ptr Icon pointer. + * @param item_name Application name. + * @return true if icon and name were loaded successfully. + */ +bool flipper_application_load_name_and_icon( + FuriString* path, + Storage* storage, + uint8_t** icon_ptr, + FuriString* item_name); + #ifdef __cplusplus } #endif diff --git a/scripts/distfap.py b/scripts/distfap.py index d330988b51e..b1c5587906b 100644 --- a/scripts/distfap.py +++ b/scripts/distfap.py @@ -52,9 +52,7 @@ def install(self): if not self.args.launch_app: return 0 - storage.send_and_wait_eol( - f'loader open "Applications" {fap_dst_path}\r' - ) + storage.send_and_wait_eol(f"loader open {fap_dst_path}\r") if len(result := storage.read.until(storage.CLI_EOL)): self.logger.error(f"Unexpected response: {result.decode('ascii')}") diff --git a/scripts/fbt/appmanifest.py b/scripts/fbt/appmanifest.py index 820f5a8c55f..73e5c777079 100644 --- a/scripts/fbt/appmanifest.py +++ b/scripts/fbt/appmanifest.py @@ -353,12 +353,18 @@ def get_builtin_app_folders(self): class ApplicationsCGenerator: APP_TYPE_MAP = { - FlipperAppType.SERVICE: ("FlipperApplication", "FLIPPER_SERVICES"), - FlipperAppType.SYSTEM: ("FlipperApplication", "FLIPPER_SYSTEM_APPS"), - FlipperAppType.APP: ("FlipperApplication", "FLIPPER_APPS"), - FlipperAppType.DEBUG: ("FlipperApplication", "FLIPPER_DEBUG_APPS"), - FlipperAppType.SETTINGS: ("FlipperApplication", "FLIPPER_SETTINGS_APPS"), - FlipperAppType.STARTUP: ("FlipperOnStartHook", "FLIPPER_ON_SYSTEM_START"), + FlipperAppType.SERVICE: ("FlipperInternalApplication", "FLIPPER_SERVICES"), + FlipperAppType.SYSTEM: ("FlipperInternalApplication", "FLIPPER_SYSTEM_APPS"), + FlipperAppType.APP: ("FlipperInternalApplication", "FLIPPER_APPS"), + FlipperAppType.DEBUG: ("FlipperInternalApplication", "FLIPPER_DEBUG_APPS"), + FlipperAppType.SETTINGS: ( + "FlipperInternalApplication", + "FLIPPER_SETTINGS_APPS", + ), + FlipperAppType.STARTUP: ( + "FlipperInternalOnStartHook", + "FLIPPER_ON_SYSTEM_START", + ), } def __init__(self, buildset: AppBuildset, autorun_app: str = ""): @@ -379,7 +385,7 @@ def get_app_descr(self, app: FlipperApplication): .appid = "{app.appid}", .stack_size = {app.stack_size}, .icon = {f"&{app.icon}" if app.icon else "NULL"}, - .flags = {'|'.join(f"FlipperApplicationFlag{flag}" for flag in app.flags)} }}""" + .flags = {'|'.join(f"FlipperInternalApplicationFlag{flag}" for flag in app.flags)} }}""" def generate(self): contents = [ @@ -408,7 +414,7 @@ def generate(self): contents.extend( [ self.get_app_ep_forward(archive_app[0]), - f"const FlipperApplication FLIPPER_ARCHIVE = {self.get_app_descr(archive_app[0])};", + f"const FlipperInternalApplication FLIPPER_ARCHIVE = {self.get_app_descr(archive_app[0])};", ] ) diff --git a/scripts/runfap.py b/scripts/runfap.py index a240acf1212..42141acff65 100644 --- a/scripts/runfap.py +++ b/scripts/runfap.py @@ -63,7 +63,7 @@ def install(self): storage_ops.recursive_send(fap_dst_path, fap_local_path, False) fap_host_app = self.args.targets[0] - startup_command = f'"Applications" {fap_host_app}' + startup_command = f"{fap_host_app}" if self.args.host_app: startup_command = self.args.host_app From 168fa72d53bd83a77ead8cc4db04ba403f0299c2 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Tue, 27 Jun 2023 12:22:35 +0300 Subject: [PATCH 617/824] [FL-3373] Scroll acceleration (#2784) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Support for scroll acceleration * Revert keyboard acceleration * Add scroll acceleration to the text box * Remove redundant code from the file manager input handler * Archive: slightly better scrolling * Gui,Archive: final version of accelerated scrolling Co-authored-by: あく --- .../main/archive/views/archive_browser_view.c | 32 ++++++++- .../main/archive/views/archive_browser_view.h | 2 + .../services/gui/modules/file_browser.c | 34 ++++++++- applications/services/gui/modules/text_box.c | 70 ++++++++++++++----- 4 files changed, 115 insertions(+), 23 deletions(-) diff --git a/applications/main/archive/views/archive_browser_view.c b/applications/main/archive/views/archive_browser_view.c index 2aca3c02ba4..7e2f84fc228 100644 --- a/applications/main/archive/views/archive_browser_view.c +++ b/applications/main/archive/views/archive_browser_view.c @@ -334,9 +334,22 @@ static bool archive_view_input(InputEvent* event, void* context) { browser->view, ArchiveBrowserViewModel * model, { + int32_t scroll_speed = 1; + if(model->button_held_for_ticks > 5) { + if(model->button_held_for_ticks % 2) { + scroll_speed = 0; + } else { + scroll_speed = model->button_held_for_ticks > 9 ? 4 : 2; + } + } + if(event->key == InputKeyUp) { + if(model->item_idx < scroll_speed) { + scroll_speed = model->item_idx; + } + model->item_idx = - ((model->item_idx - 1) + model->item_cnt) % model->item_cnt; + ((model->item_idx - scroll_speed) + model->item_cnt) % model->item_cnt; if(is_file_list_load_required(model)) { model->list_loading = true; browser->callback(ArchiveBrowserEventLoadPrevItems, browser->context); @@ -345,8 +358,14 @@ static bool archive_view_input(InputEvent* event, void* context) { browser->callback(ArchiveBrowserEventFavMoveUp, browser->context); } model->scroll_counter = 0; + model->button_held_for_ticks += 1; } else if(event->key == InputKeyDown) { - model->item_idx = (model->item_idx + 1) % model->item_cnt; + int32_t count = model->item_cnt; + if(model->item_idx >= (count - scroll_speed)) { + scroll_speed = model->item_cnt - model->item_idx - 1; + } + + model->item_idx = (model->item_idx + scroll_speed) % model->item_cnt; if(is_file_list_load_required(model)) { model->list_loading = true; browser->callback(ArchiveBrowserEventLoadNextItems, browser->context); @@ -355,6 +374,7 @@ static bool archive_view_input(InputEvent* event, void* context) { browser->callback(ArchiveBrowserEventFavMoveDown, browser->context); } model->scroll_counter = 0; + model->button_held_for_ticks += 1; } }, true); @@ -391,6 +411,14 @@ static bool archive_view_input(InputEvent* event, void* context) { } } + if(event->type == InputTypeRelease) { + with_view_model( + browser->view, + ArchiveBrowserViewModel * model, + { model->button_held_for_ticks = 0; }, + true); + } + return true; } diff --git a/applications/main/archive/views/archive_browser_view.h b/applications/main/archive/views/archive_browser_view.h index 0a000e5ac34..25490aedd3e 100644 --- a/applications/main/archive/views/archive_browser_view.h +++ b/applications/main/archive/views/archive_browser_view.h @@ -100,6 +100,8 @@ typedef struct { int32_t array_offset; int32_t list_offset; size_t scroll_counter; + + uint32_t button_held_for_ticks; } ArchiveBrowserViewModel; void archive_browser_set_callback( diff --git a/applications/services/gui/modules/file_browser.c b/applications/services/gui/modules/file_browser.c index d12a00ba598..b2a2c3c2389 100644 --- a/applications/services/gui/modules/file_browser.c +++ b/applications/services/gui/modules/file_browser.c @@ -118,6 +118,8 @@ typedef struct { const Icon* file_icon; bool hide_ext; size_t scroll_counter; + + uint32_t button_held_for_ticks; } FileBrowserModel; static const Icon* BrowserItemIcons[] = { @@ -589,9 +591,22 @@ static bool file_browser_view_input_callback(InputEvent* event, void* context) { browser->view, FileBrowserModel * model, { + int32_t scroll_speed = 1; + if(model->button_held_for_ticks > 5) { + if(model->button_held_for_ticks % 2) { + scroll_speed = 0; + } else { + scroll_speed = model->button_held_for_ticks > 9 ? 5 : 3; + } + } + if(event->key == InputKeyUp) { + if(model->item_idx < scroll_speed) { + scroll_speed = model->item_idx; + } + model->item_idx = - ((model->item_idx - 1) + model->item_cnt) % model->item_cnt; + ((model->item_idx - scroll_speed) + model->item_cnt) % model->item_cnt; if(browser_is_list_load_required(model)) { model->list_loading = true; int32_t load_offset = CLAMP( @@ -602,8 +617,15 @@ static bool file_browser_view_input_callback(InputEvent* event, void* context) { browser->worker, load_offset, ITEM_LIST_LEN_MAX); } model->scroll_counter = 0; + + model->button_held_for_ticks += 1; } else if(event->key == InputKeyDown) { - model->item_idx = (model->item_idx + 1) % model->item_cnt; + int32_t count = model->item_cnt; + if(model->item_idx + scroll_speed >= count) { + scroll_speed = count - model->item_idx - 1; + } + + model->item_idx = (model->item_idx + scroll_speed) % model->item_cnt; if(browser_is_list_load_required(model)) { model->list_loading = true; int32_t load_offset = CLAMP( @@ -614,11 +636,19 @@ static bool file_browser_view_input_callback(InputEvent* event, void* context) { browser->worker, load_offset, ITEM_LIST_LEN_MAX); } model->scroll_counter = 0; + + model->button_held_for_ticks += 1; } }, true); browser_update_offset(browser); consumed = true; + } else if(event->type == InputTypeRelease) { + with_view_model( + browser->view, + FileBrowserModel * model, + { model->button_held_for_ticks = 0; }, + true); } } else if(event->key == InputKeyOk) { if(event->type == InputTypeShort) { diff --git a/applications/services/gui/modules/text_box.c b/applications/services/gui/modules/text_box.c index 01ccdbf52b5..0e4aae9446b 100644 --- a/applications/services/gui/modules/text_box.c +++ b/applications/services/gui/modules/text_box.c @@ -6,6 +6,8 @@ struct TextBox { View* view; + + uint16_t button_held_for_ticks; }; typedef struct { @@ -19,36 +21,52 @@ typedef struct { bool formatted; } TextBoxModel; -static void text_box_process_down(TextBox* text_box) { +static void text_box_process_down(TextBox* text_box, uint8_t lines) { with_view_model( text_box->view, TextBoxModel * model, { - if(model->scroll_pos < model->scroll_num - 1) { - model->scroll_pos++; - // Search next line start - while(*model->text_pos++ != '\n') - ; + if(model->scroll_pos < model->scroll_num - lines) { + model->scroll_pos += lines; + for(uint8_t i = 0; i < lines; i++) { + // Search next line start + while(*model->text_pos++ != '\n') + ; + } + } else if(lines > 1) { + lines = model->scroll_num - model->scroll_pos - 1; + model->scroll_pos = model->scroll_num - 1; + for(uint8_t i = 0; i < lines; i++) { + // Search next line start + while(*model->text_pos++ != '\n') + ; + } } }, true); } -static void text_box_process_up(TextBox* text_box) { +static void text_box_process_up(TextBox* text_box, uint8_t lines) { with_view_model( text_box->view, TextBoxModel * model, { - if(model->scroll_pos > 0) { - model->scroll_pos--; - // Reach last symbol of previous line - model->text_pos--; - // Search previous line start - while((model->text_pos != model->text) && (*(--model->text_pos) != '\n')) - ; - if(*model->text_pos == '\n') { - model->text_pos++; + if(model->scroll_pos > lines - 1) { + model->scroll_pos -= lines; + for(uint8_t i = 0; i < lines; i++) { + // Reach last symbol of previous line + model->text_pos--; + // Search previous line start + while((model->text_pos != model->text) && (*(--model->text_pos) != '\n')) + ; + if(*model->text_pos == '\n') { + model->text_pos++; + } } + } else if(lines > 1) { + lines = model->scroll_pos; + model->scroll_pos = 0; + model->text_pos = (char*)model->text; } }, true); @@ -120,14 +138,28 @@ static bool text_box_view_input_callback(InputEvent* event, void* context) { TextBox* text_box = context; bool consumed = false; - if(event->type == InputTypeShort) { + if(event->type == InputTypeShort || event->type == InputTypeRepeat) { + int32_t scroll_speed = 1; + if(text_box->button_held_for_ticks > 5) { + if(text_box->button_held_for_ticks % 2) { + scroll_speed = 0; + } else { + scroll_speed = text_box->button_held_for_ticks > 9 ? 5 : 3; + } + } + if(event->key == InputKeyDown) { - text_box_process_down(text_box); + text_box_process_down(text_box, scroll_speed); consumed = true; } else if(event->key == InputKeyUp) { - text_box_process_up(text_box); + text_box_process_up(text_box, scroll_speed); consumed = true; } + + text_box->button_held_for_ticks++; + } else if(event->type == InputTypeRelease) { + text_box->button_held_for_ticks = 0; + consumed = true; } return consumed; } From 75354ec5bac8be5f631213c8e236123c2bcea075 Mon Sep 17 00:00:00 2001 From: Petr Portnov | PROgrm_JARvis Date: Tue, 27 Jun 2023 12:46:04 +0300 Subject: [PATCH 618/824] fix: make `dialog_file_browser_set_basic_options` initialize all fields (#2756) * fix: make `dialog_file_browser_set_basic_options` initialize all fields * fix(GH-2756): use alternative test for `test_dialog_file_browser_set_basic_options_should_init_all_fields` Co-authored-by: Aleksandr Kutuzov --- CODING_STYLE.md | 2 +- .../dialogs/dialogs_file_browser_options.c | 32 +++++++++++++++++++ applications/debug/unit_tests/test_index.c | 3 ++ applications/services/dialogs/dialogs.c | 3 +- applications/services/dialogs/dialogs.h | 9 ++++-- 5 files changed, 44 insertions(+), 5 deletions(-) create mode 100644 applications/debug/unit_tests/dialogs/dialogs_file_browser_options.c diff --git a/CODING_STYLE.md b/CODING_STYLE.md index c62009eff5f..002c67f246a 100644 --- a/CODING_STYLE.md +++ b/CODING_STYLE.md @@ -48,7 +48,7 @@ Almost everything in flipper firmware is built around this concept. # C coding style - Tab is 4 spaces -- Use `fbt format` to reformat source code and check style guide before commit +- Use `./fbt format` to reformat source code and check style guide before commit ## Naming diff --git a/applications/debug/unit_tests/dialogs/dialogs_file_browser_options.c b/applications/debug/unit_tests/dialogs/dialogs_file_browser_options.c new file mode 100644 index 00000000000..2d5bad4c8a5 --- /dev/null +++ b/applications/debug/unit_tests/dialogs/dialogs_file_browser_options.c @@ -0,0 +1,32 @@ +#include + +#include "../minunit.h" + +MU_TEST(test_dialog_file_browser_set_basic_options_should_init_all_fields) { + mu_assert( + sizeof(DialogsFileBrowserOptions) == 28, + "Changes to `DialogsFileBrowserOptions` should also be reflected in `dialog_file_browser_set_basic_options`"); + + DialogsFileBrowserOptions options; + dialog_file_browser_set_basic_options(&options, ".fap", NULL); + // note: this assertions can safely be changed, their primary purpose is to remind the maintainer + // to update `dialog_file_browser_set_basic_options` by including all structure fields in it + mu_assert_string_eq(".fap", options.extension); + mu_assert_null(options.base_path); + mu_assert(options.skip_assets, "`skip_assets` should default to `true"); + mu_assert(options.hide_dot_files, "`hide_dot_files` should default to `true"); + mu_assert_null(options.icon); + mu_assert(options.hide_ext, "`hide_ext` should default to `true"); + mu_assert_null(options.item_loader_callback); + mu_assert_null(options.item_loader_context); +} + +MU_TEST_SUITE(dialogs_file_browser_options) { + MU_RUN_TEST(test_dialog_file_browser_set_basic_options_should_init_all_fields); +} + +int run_minunit_test_dialogs_file_browser_options() { + MU_RUN_SUITE(dialogs_file_browser_options); + + return MU_EXIT_CODE; +} diff --git a/applications/debug/unit_tests/test_index.c b/applications/debug/unit_tests/test_index.c index ac71ca397ee..9d7631bfee3 100644 --- a/applications/debug/unit_tests/test_index.c +++ b/applications/debug/unit_tests/test_index.c @@ -27,6 +27,7 @@ int run_minunit_test_nfc(); int run_minunit_test_bit_lib(); int run_minunit_test_float_tools(); int run_minunit_test_bt(); +int run_minunit_test_dialogs_file_browser_options(); typedef int (*UnitTestEntry)(); @@ -55,6 +56,8 @@ const UnitTest unit_tests[] = { {.name = "bit_lib", .entry = run_minunit_test_bit_lib}, {.name = "float_tools", .entry = run_minunit_test_float_tools}, {.name = "bt", .entry = run_minunit_test_bt}, + {.name = "dialogs_file_browser_options", + .entry = run_minunit_test_dialogs_file_browser_options}, }; void minunit_print_progress() { diff --git a/applications/services/dialogs/dialogs.c b/applications/services/dialogs/dialogs.c index 3908ca31b58..10c08a991b3 100644 --- a/applications/services/dialogs/dialogs.c +++ b/applications/services/dialogs/dialogs.c @@ -9,12 +9,13 @@ void dialog_file_browser_set_basic_options( const char* extension, const Icon* icon) { options->extension = extension; + options->base_path = NULL; options->skip_assets = true; + options->hide_dot_files = true; options->icon = icon; options->hide_ext = true; options->item_loader_callback = NULL; options->item_loader_context = NULL; - options->base_path = NULL; } static DialogsApp* dialogs_app_alloc() { diff --git a/applications/services/dialogs/dialogs.h b/applications/services/dialogs/dialogs.h index 4c1b675a644..39b15c67c2e 100644 --- a/applications/services/dialogs/dialogs.h +++ b/applications/services/dialogs/dialogs.h @@ -16,7 +16,8 @@ typedef struct DialogsApp DialogsApp; /****************** FILE BROWSER ******************/ /** - * File browser dialog extra options + * File browser dialog extra options. + * This can be default-initialized using {@link dialog_file_browser_set_basic_options}. * @param extension file extension to be offered for selection * @param base_path root folder path for navigation with back key * @param skip_assets true - do not show assets folders @@ -38,8 +39,10 @@ typedef struct { } DialogsFileBrowserOptions; /** - * Initialize file browser dialog options - * and set default values + * Initialize file browser dialog options and set default values. + * This is guaranteed to initialize all fields + * so it is safe to pass pointer to uninitialized {@code options} + * and assume that the data behind it becomes fully initialized after the call. * @param options pointer to options structure * @param extension file extension to filter * @param icon file icon pointer, NULL for default icon From 0a5508a8a1ab202131262702901eaf0e388b212a Mon Sep 17 00:00:00 2001 From: PpHd Date: Tue, 27 Jun 2023 12:50:09 +0200 Subject: [PATCH 619/824] Fix M*LIB usage (#2762) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix M*LIB usage * Fix oplist definition of SubGhzFrequencyAnalyzerLogItem * Fix oplist definition of M_CSTR_DUP_OPLIST * Remove dependency of furi_string_utf8_decode to the internal definition of string_unicode_t * Replace obsolete macro M_IF_DEFAULT1 to M_DEFAULT_ARGS Co-authored-by: hedger Co-authored-by: あく --- ...subghz_frequency_analyzer_log_item_array.h | 7 ++- furi/core/string.c | 4 +- furi/core/string.h | 44 ++++++++----------- lib/toolbox/m_cstr_dup.h | 19 ++++---- 4 files changed, 37 insertions(+), 37 deletions(-) diff --git a/applications/main/subghz/helpers/subghz_frequency_analyzer_log_item_array.h b/applications/main/subghz/helpers/subghz_frequency_analyzer_log_item_array.h index b94ebe3809d..12c6bef6a92 100644 --- a/applications/main/subghz/helpers/subghz_frequency_analyzer_log_item_array.h +++ b/applications/main/subghz/helpers/subghz_frequency_analyzer_log_item_array.h @@ -27,7 +27,12 @@ TUPLE_DEF2( (rssi_max, uint8_t)) /* Register globally the oplist */ #define M_OPL_SubGhzFrequencyAnalyzerLogItem_t() \ - TUPLE_OPLIST(SubGhzFrequencyAnalyzerLogItem, M_POD_OPLIST, M_DEFAULT_OPLIST, M_DEFAULT_OPLIST) + TUPLE_OPLIST( \ + SubGhzFrequencyAnalyzerLogItem, \ + M_DEFAULT_OPLIST, \ + M_DEFAULT_OPLIST, \ + M_DEFAULT_OPLIST, \ + M_DEFAULT_OPLIST) /* Define the array, register the oplist and define further algorithms on it */ ARRAY_DEF(SubGhzFrequencyAnalyzerLogItemArray, SubGhzFrequencyAnalyzerLogItem_t) diff --git a/furi/core/string.c b/furi/core/string.c index 4384fe06a22..682c8d40977 100644 --- a/furi/core/string.c +++ b/furi/core/string.c @@ -296,7 +296,9 @@ static FuriStringUTF8State state_to_furi_state(m_str1ng_utf8_state_e state) { } void furi_string_utf8_decode(char c, FuriStringUTF8State* state, FuriStringUnicodeValue* unicode) { + string_unicode_t m_u = *unicode; m_str1ng_utf8_state_e m_state = furi_state_to_state(*state); - m_str1ng_utf8_decode(c, &m_state, unicode); + m_str1ng_utf8_decode(c, &m_state, &m_u); *state = state_to_furi_state(m_state); + *unicode = m_u; } diff --git a/furi/core/string.h b/furi/core/string.h index 0523d3ba04e..7529deacd7c 100644 --- a/furi/core/string.h +++ b/furi/core/string.h @@ -633,20 +633,17 @@ void furi_string_utf8_decode(char c, FuriStringUTF8State* state, FuriStringUnico * @brief Search for a string (or C string) in a string * (string, [c]string[, start=0]) */ -#define furi_string_search(v, ...) \ - M_APPLY( \ - FURI_STRING_SELECT3, \ - furi_string_search, \ - furi_string_search_str, \ - v, \ - M_IF_DEFAULT1(0, __VA_ARGS__)) - +#define furi_string_search(...) \ + M_APPLY( \ + FURI_STRING_SELECT3, \ + furi_string_search, \ + furi_string_search_str, \ + M_DEFAULT_ARGS(3, (0), __VA_ARGS__)) /** * @brief Search for a C string in a string * (string, cstring[, start=0]) */ -#define furi_string_search_str(v, ...) \ - M_APPLY(furi_string_search_str, v, M_IF_DEFAULT1(0, __VA_ARGS__)) +#define furi_string_search_str(...) furi_string_search_str(M_DEFAULT_ARGS(3, (0), __VA_ARGS__)) /** * @brief Test if the string starts with the given string (or C string). @@ -672,41 +669,36 @@ void furi_string_utf8_decode(char c, FuriStringUTF8State* state, FuriStringUnico * @brief Trim a string from the given set of characters (default is " \n\r\t"). * (string[, set=" \n\r\t"]) */ -#define furi_string_trim(...) M_APPLY(furi_string_trim, M_IF_DEFAULT1(" \n\r\t", __VA_ARGS__)) +#define furi_string_trim(...) furi_string_trim(M_DEFAULT_ARGS(2, (" \n\r\t"), __VA_ARGS__)) /** * @brief Search for a character in a string. * (string, character[, start=0]) */ -#define furi_string_search_char(v, ...) \ - M_APPLY(furi_string_search_char, v, M_IF_DEFAULT1(0, __VA_ARGS__)) +#define furi_string_search_char(...) furi_string_search_char(M_DEFAULT_ARGS(3, (0), __VA_ARGS__)) /** * @brief Reverse Search for a character in a string. * (string, character[, start=0]) */ -#define furi_string_search_rchar(v, ...) \ - M_APPLY(furi_string_search_rchar, v, M_IF_DEFAULT1(0, __VA_ARGS__)) +#define furi_string_search_rchar(...) furi_string_search_rchar(M_DEFAULT_ARGS(3, (0), __VA_ARGS__)) /** * @brief Replace a string to another string (or C string to another C string) in a string. * (string, [c]string, [c]string[, start=0]) */ -#define furi_string_replace(a, b, ...) \ - M_APPLY( \ - FURI_STRING_SELECT4, \ - furi_string_replace, \ - furi_string_replace_str, \ - a, \ - b, \ - M_IF_DEFAULT1(0, __VA_ARGS__)) +#define furi_string_replace(...) \ + M_APPLY( \ + FURI_STRING_SELECT4, \ + furi_string_replace, \ + furi_string_replace_str, \ + M_DEFAULT_ARGS(4, (0), __VA_ARGS__)) /** * @brief Replace a C string to another C string in a string. * (string, cstring, cstring[, start=0]) */ -#define furi_string_replace_str(a, b, ...) \ - M_APPLY(furi_string_replace_str, a, b, M_IF_DEFAULT1(0, __VA_ARGS__)) +#define furi_string_replace_str(...) furi_string_replace_str(M_DEFAULT_ARGS(4, (0), __VA_ARGS__)) /** * @brief INIT OPLIST for FuriString. @@ -743,4 +735,4 @@ void furi_string_utf8_decode(char c, FuriStringUTF8State* state, FuriStringUnico #ifdef __cplusplus } -#endif \ No newline at end of file +#endif diff --git a/lib/toolbox/m_cstr_dup.h b/lib/toolbox/m_cstr_dup.h index 0555f72c651..11b7fe35ada 100644 --- a/lib/toolbox/m_cstr_dup.h +++ b/lib/toolbox/m_cstr_dup.h @@ -2,15 +2,16 @@ #include #define M_INIT_DUP(a) ((a) = strdup("")) -#define M_SET_DUP(a, b) (M_CHECK_DEFAULT_TYPE(a), free((void*)a), (a) = strdup(b)) +#define M_INIT_SET_DUP(a, b) ((a) = strdup(b)) +#define M_SET_DUP(a, b) (free((void*)a), (a) = strdup(b)) #define M_CLEAR_DUP(a) (free((void*)a)) -#define M_CSTR_DUP_OPLIST \ - (INIT(M_INIT_DUP), \ - INIT_SET(M_SET_DUP), \ - SET(M_SET_DUP), \ - CLEAR(M_CLEAR_DUP), \ - HASH(m_core_cstr_hash), \ - EQUAL(M_CSTR_EQUAL), \ - CMP(strcmp), \ +#define M_CSTR_DUP_OPLIST \ + (INIT(M_INIT_DUP), \ + INIT_SET(M_INIT_SET_DUP), \ + SET(M_SET_DUP), \ + CLEAR(M_CLEAR_DUP), \ + HASH(m_core_cstr_hash), \ + EQUAL(M_CSTR_EQUAL), \ + CMP(strcmp), \ TYPE(const char*)) From e680cf59b60fd80065172bd6902f467f02e08e57 Mon Sep 17 00:00:00 2001 From: Konstantin Volkov <72250702+doomwastaken@users.noreply.github.com> Date: Tue, 27 Jun 2023 23:47:13 +0300 Subject: [PATCH 620/824] Actions: unit_test and updater timeouts (#2807) * added some extra timeouts, fixed duration of units run command and minor logging changes. No list_ports yet needed * increased timeouts * make pvs happy --------- Co-authored-by: doomwastaken Co-authored-by: SG --- .github/workflows/unit_tests.yml | 5 ++++- .github/workflows/updater_test.yml | 2 ++ .../helpers/subghz_frequency_analyzer_log_item_array.h | 2 +- scripts/testing/await_flipper.py | 5 ++++- scripts/testing/units.py | 4 ++-- 5 files changed, 13 insertions(+), 5 deletions(-) diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 81f0e0d050e..4cb112c7709 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -29,12 +29,14 @@ jobs: - name: 'Flash unit tests firmware' id: flashing if: success() + timeout-minutes: 5 run: | ./fbt flash OPENOCD_ADAPTER_SERIAL=2A0906016415303030303032 FIRMWARE_APP_SET=unit_tests FORCE=1 - name: 'Wait for flipper and format ext' id: format_ext if: steps.flashing.outcome == 'success' + timeout-minutes: 5 run: | source scripts/toolchain/fbtenv.sh python3 scripts/testing/await_flipper.py ${{steps.device.outputs.flipper}} @@ -43,6 +45,7 @@ jobs: - name: 'Copy assets and unit data, reboot and wait for flipper' id: copy if: steps.format_ext.outcome == 'success' + timeout-minutes: 3 run: | source scripts/toolchain/fbtenv.sh python3 scripts/storage.py -p ${{steps.device.outputs.flipper}} -f send assets/resources /ext @@ -53,7 +56,7 @@ jobs: - name: 'Run units and validate results' id: run_units if: steps.copy.outcome == 'success' - timeout-minutes: 2.5 + timeout-minutes: 5 run: | source scripts/toolchain/fbtenv.sh python3 scripts/testing/units.py ${{steps.device.outputs.flipper}} diff --git a/.github/workflows/updater_test.yml b/.github/workflows/updater_test.yml index bd837297974..1d383d9eb30 100644 --- a/.github/workflows/updater_test.yml +++ b/.github/workflows/updater_test.yml @@ -30,6 +30,7 @@ jobs: - name: 'Flashing target firmware' id: first_full_flash + timeout-minutes: 5 run: | source scripts/toolchain/fbtenv.sh ./fbt flash_usb_full PORT=${{steps.device.outputs.flipper}} FORCE=1 @@ -37,6 +38,7 @@ jobs: - name: 'Validating updater' id: second_full_flash + timeout-minutes: 5 if: success() run: | source scripts/toolchain/fbtenv.sh diff --git a/applications/main/subghz/helpers/subghz_frequency_analyzer_log_item_array.h b/applications/main/subghz/helpers/subghz_frequency_analyzer_log_item_array.h index 12c6bef6a92..df53143d23f 100644 --- a/applications/main/subghz/helpers/subghz_frequency_analyzer_log_item_array.h +++ b/applications/main/subghz/helpers/subghz_frequency_analyzer_log_item_array.h @@ -35,7 +35,7 @@ TUPLE_DEF2( M_DEFAULT_OPLIST) /* Define the array, register the oplist and define further algorithms on it */ -ARRAY_DEF(SubGhzFrequencyAnalyzerLogItemArray, SubGhzFrequencyAnalyzerLogItem_t) +ARRAY_DEF(SubGhzFrequencyAnalyzerLogItemArray, SubGhzFrequencyAnalyzerLogItem_t) //-V779 #define M_OPL_SubGhzFrequencyAnalyzerLogItemArray_t() \ ARRAY_OPLIST(SubGhzFrequencyAnalyzerLogItemArray, M_OPL_SubGhzFrequencyAnalyzerLogItem_t()) ALGO_DEF(SubGhzFrequencyAnalyzerLogItemArray, SubGhzFrequencyAnalyzerLogItemArray_t) diff --git a/scripts/testing/await_flipper.py b/scripts/testing/await_flipper.py index 2b4c8b4c39b..ea07d6be7ff 100755 --- a/scripts/testing/await_flipper.py +++ b/scripts/testing/await_flipper.py @@ -8,6 +8,7 @@ def flp_serial_by_name(flp_name): if sys.platform == "darwin": # MacOS flp_serial = "/dev/cu.usbmodemflip_" + flp_name + "1" + logging.info(f"Darwin, looking for {flp_serial}") elif sys.platform == "linux": # Linux flp_serial = ( "/dev/serial/by-id/usb-Flipper_Devices_Inc._Flipper_" @@ -16,10 +17,12 @@ def flp_serial_by_name(flp_name): + flp_name + "-if00" ) + logging.info(f"linux, looking for {flp_serial}") if os.path.exists(flp_serial): return flp_serial else: + logging.info(f"Couldn't find {logging.info} on this attempt.") if os.path.exists(flp_name): return flp_name else: @@ -38,7 +41,7 @@ def main(): level=logging.INFO, datefmt="%Y-%m-%d %H:%M:%S", ) - logging.info("Waiting for Flipper to be ready...") + logging.info(f"Waiting for Flipper {flipper_name} to be ready...") while flipper == "" and elapsed < UPDATE_TIMEOUT: elapsed += 1 diff --git a/scripts/testing/units.py b/scripts/testing/units.py index 5083bcd4350..fd8e29a7333 100755 --- a/scripts/testing/units.py +++ b/scripts/testing/units.py @@ -20,13 +20,13 @@ def main(): logging.error("Flipper not found!") sys.exit(1) - with serial.Serial(flp_serial, timeout=1) as flipper: + with serial.Serial(flp_serial, timeout=10) as flipper: logging.info(f"Found Flipper at {flp_serial}") flipper.baudrate = 230400 flipper.flushOutput() flipper.flushInput() - flipper.timeout = 180 + flipper.timeout = 300 flipper.read_until(b">: ").decode("utf-8") flipper.write(b"unit_tests\r") From 92c1bb83bf37c7f1d622c28e20870402830cf4a4 Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Wed, 28 Jun 2023 10:30:59 +0300 Subject: [PATCH 621/824] LF-RFID debug: make it work (#2793) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- .../scenes/lfrfid_debug_app_scene_tune.c | 7 +++ .../views/lfrfid_debug_view_tune.c | 44 +++++++++++++------ .../views/lfrfid_debug_view_tune.h | 5 +++ 3 files changed, 43 insertions(+), 13 deletions(-) diff --git a/applications/debug/lfrfid_debug/scenes/lfrfid_debug_app_scene_tune.c b/applications/debug/lfrfid_debug/scenes/lfrfid_debug_app_scene_tune.c index 74c53ae6d48..ac2e2b80611 100644 --- a/applications/debug/lfrfid_debug/scenes/lfrfid_debug_app_scene_tune.c +++ b/applications/debug/lfrfid_debug/scenes/lfrfid_debug_app_scene_tune.c @@ -6,6 +6,11 @@ static void comparator_trigger_callback(bool level, void* comp_ctx) { furi_hal_gpio_write(&gpio_ext_pa7, !level); } +void lfrfid_debug_view_tune_callback(void* context) { + LfRfidDebug* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, 0xBA); +} + void lfrfid_debug_scene_tune_on_enter(void* context) { LfRfidDebug* app = context; @@ -16,6 +21,8 @@ void lfrfid_debug_scene_tune_on_enter(void* context) { furi_hal_rfid_tim_read_start(125000, 0.5); + lfrfid_debug_view_tune_set_callback(app->tune_view, lfrfid_debug_view_tune_callback, app); + view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidDebugViewTune); } diff --git a/applications/debug/lfrfid_debug/views/lfrfid_debug_view_tune.c b/applications/debug/lfrfid_debug/views/lfrfid_debug_view_tune.c index fd221c4e9d3..9e48a7e27fe 100644 --- a/applications/debug/lfrfid_debug/views/lfrfid_debug_view_tune.c +++ b/applications/debug/lfrfid_debug/views/lfrfid_debug_view_tune.c @@ -13,6 +13,8 @@ typedef struct { uint32_t ARR; uint32_t CCR; int pos; + void (*update_callback)(void* context); + void* update_context; } LfRfidTuneViewModel; static void lfrfid_debug_view_tune_draw_callback(Canvas* canvas, void* _model) { @@ -151,6 +153,18 @@ static bool lfrfid_debug_view_tune_input_callback(InputEvent* event, void* conte consumed = false; break; } + + if(event->key == InputKeyLeft || event->key == InputKeyRight) { + with_view_model( + tune_view->view, + LfRfidTuneViewModel * model, + { + if(model->update_callback) { + model->update_callback(model->update_context); + } + }, + false); + } } return consumed; @@ -161,19 +175,7 @@ LfRfidTuneView* lfrfid_debug_view_tune_alloc() { tune_view->view = view_alloc(); view_set_context(tune_view->view, tune_view); view_allocate_model(tune_view->view, ViewModelTypeLocking, sizeof(LfRfidTuneViewModel)); - - with_view_model( - tune_view->view, - LfRfidTuneViewModel * model, - { - model->dirty = true; - model->fine = false; - model->ARR = 511; - model->CCR = 255; - model->pos = 0; - }, - true); - + lfrfid_debug_view_tune_clean(tune_view); view_set_draw_callback(tune_view->view, lfrfid_debug_view_tune_draw_callback); view_set_input_callback(tune_view->view, lfrfid_debug_view_tune_input_callback); @@ -199,6 +201,8 @@ void lfrfid_debug_view_tune_clean(LfRfidTuneView* tune_view) { model->ARR = 511; model->CCR = 255; model->pos = 0; + model->update_callback = NULL; + model->update_context = NULL; }, true); } @@ -232,3 +236,17 @@ uint32_t lfrfid_debug_view_tune_get_ccr(LfRfidTuneView* tune_view) { return result; } + +void lfrfid_debug_view_tune_set_callback( + LfRfidTuneView* tune_view, + void (*callback)(void* context), + void* context) { + with_view_model( + tune_view->view, + LfRfidTuneViewModel * model, + { + model->update_callback = callback; + model->update_context = context; + }, + false); +} diff --git a/applications/debug/lfrfid_debug/views/lfrfid_debug_view_tune.h b/applications/debug/lfrfid_debug/views/lfrfid_debug_view_tune.h index fd6d0b1fe9a..be54b63f9a8 100644 --- a/applications/debug/lfrfid_debug/views/lfrfid_debug_view_tune.h +++ b/applications/debug/lfrfid_debug/views/lfrfid_debug_view_tune.h @@ -16,3 +16,8 @@ bool lfrfid_debug_view_tune_is_dirty(LfRfidTuneView* tune_view); uint32_t lfrfid_debug_view_tune_get_arr(LfRfidTuneView* tune_view); uint32_t lfrfid_debug_view_tune_get_ccr(LfRfidTuneView* tune_view); + +void lfrfid_debug_view_tune_set_callback( + LfRfidTuneView* tune_view, + void (*callback)(void* context), + void* context); From 645a7c598923bbe1877c1f5ce13bccd20bb5ccac Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Wed, 28 Jun 2023 11:19:10 +0300 Subject: [PATCH 622/824] [FL-3386] Fast FAP Loader (#2790) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * FBT: build and add FastFAP(tm) sections * Elf file: fast loading fap files. Really fast, like x15 times faster. * fastfap.py: cleanup unused imports * Toolchain: 23 version * Elf File: remove log messages * Scripts: fix file permissions * FBT: explicit interpreter for fastfap invocation Co-authored-by: あく --- firmware/targets/f18/api_symbols.csv | 3 +- firmware/targets/f7/api_symbols.csv | 3 +- .../api_hashtable/api_hashtable.cpp | 19 +- .../api_hashtable/api_hashtable.h | 12 +- .../elf/elf_api_interface.h | 2 +- lib/flipper_application/elf/elf_file.c | 126 ++++++++++++- lib/flipper_application/elf/elf_file_i.h | 10 +- .../plugins/composite_resolver.c | 4 +- scripts/distfap.py | 0 scripts/fastfap.py | 169 ++++++++++++++++++ scripts/fbt/sdk/collector.py | 8 +- scripts/fbt/sdk/hashes.py | 5 + scripts/fbt_tools/fbt_extapps.py | 15 +- scripts/fwsize.py | 0 scripts/get_env.py | 0 scripts/runfap.py | 0 scripts/sconsdist.py | 0 scripts/selfupdate.py | 0 scripts/slideshow.py | 0 scripts/toolchain/fbtenv.cmd | 2 +- scripts/toolchain/fbtenv.sh | 2 +- scripts/version.py | 0 22 files changed, 338 insertions(+), 42 deletions(-) mode change 100644 => 100755 scripts/distfap.py create mode 100755 scripts/fastfap.py create mode 100644 scripts/fbt/sdk/hashes.py mode change 100644 => 100755 scripts/fwsize.py mode change 100644 => 100755 scripts/get_env.py mode change 100644 => 100755 scripts/runfap.py mode change 100644 => 100755 scripts/sconsdist.py mode change 100644 => 100755 scripts/selfupdate.py mode change 100644 => 100755 scripts/slideshow.py mode change 100644 => 100755 scripts/version.py diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index 101ea92a6a0..56a7f678da8 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -679,7 +679,8 @@ Function,+,elements_slightly_rounded_box,void,"Canvas*, uint8_t, uint8_t, uint8_ Function,+,elements_slightly_rounded_frame,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t" Function,+,elements_string_fit_width,void,"Canvas*, FuriString*, uint8_t" Function,+,elements_text_box,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, Align, Align, const char*, _Bool" -Function,+,elf_resolve_from_hashtable,_Bool,"const ElfApiInterface*, const char*, Elf32_Addr*" +Function,+,elf_resolve_from_hashtable,_Bool,"const ElfApiInterface*, uint32_t, Elf32_Addr*" +Function,+,elf_symbolname_hash,uint32_t,const char* Function,+,empty_screen_alloc,EmptyScreen*, Function,+,empty_screen_free,void,EmptyScreen* Function,+,empty_screen_get_view,View*,EmptyScreen* diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 5ed26f29611..0f782e966c6 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -808,7 +808,8 @@ Function,+,elements_slightly_rounded_box,void,"Canvas*, uint8_t, uint8_t, uint8_ Function,+,elements_slightly_rounded_frame,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t" Function,+,elements_string_fit_width,void,"Canvas*, FuriString*, uint8_t" Function,+,elements_text_box,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, Align, Align, const char*, _Bool" -Function,+,elf_resolve_from_hashtable,_Bool,"const ElfApiInterface*, const char*, Elf32_Addr*" +Function,+,elf_resolve_from_hashtable,_Bool,"const ElfApiInterface*, uint32_t, Elf32_Addr*" +Function,+,elf_symbolname_hash,uint32_t,const char* Function,+,empty_screen_alloc,EmptyScreen*, Function,+,empty_screen_free,void,EmptyScreen* Function,+,empty_screen_get_view,View*,EmptyScreen* diff --git a/lib/flipper_application/api_hashtable/api_hashtable.cpp b/lib/flipper_application/api_hashtable/api_hashtable.cpp index 022792dce63..6db5fb5fde6 100644 --- a/lib/flipper_application/api_hashtable/api_hashtable.cpp +++ b/lib/flipper_application/api_hashtable/api_hashtable.cpp @@ -7,27 +7,22 @@ bool elf_resolve_from_hashtable( const ElfApiInterface* interface, - const char* name, + uint32_t hash, Elf32_Addr* address) { + bool result = false; const HashtableApiInterface* hashtable_interface = static_cast(interface); - bool result = false; - uint32_t gnu_sym_hash = elf_gnu_hash(name); sym_entry key = { - .hash = gnu_sym_hash, + .hash = hash, .address = 0, }; auto find_res = std::lower_bound(hashtable_interface->table_cbegin, hashtable_interface->table_cend, key); - if((find_res == hashtable_interface->table_cend || (find_res->hash != gnu_sym_hash))) { + if((find_res == hashtable_interface->table_cend || (find_res->hash != hash))) { FURI_LOG_W( - TAG, - "Can't find symbol '%s' (hash %lx) @ %p!", - name, - gnu_sym_hash, - hashtable_interface->table_cbegin); + TAG, "Can't find symbol with hash %lx @ %p!", hash, hashtable_interface->table_cbegin); result = false; } else { result = true; @@ -36,3 +31,7 @@ bool elf_resolve_from_hashtable( return result; } + +uint32_t elf_symbolname_hash(const char* s) { + return elf_gnu_hash(s); +} \ No newline at end of file diff --git a/lib/flipper_application/api_hashtable/api_hashtable.h b/lib/flipper_application/api_hashtable/api_hashtable.h index 7e4b4aba1dd..7ba6aab9727 100644 --- a/lib/flipper_application/api_hashtable/api_hashtable.h +++ b/lib/flipper_application/api_hashtable/api_hashtable.h @@ -19,15 +19,17 @@ struct sym_entry { /** * @brief Resolver for API entries using a pre-sorted table with hashes * @param interface pointer to HashtableApiInterface - * @param name function name + * @param hash gnu hash of function name * @param address output for function address * @return true if the table contains a function */ bool elf_resolve_from_hashtable( const ElfApiInterface* interface, - const char* name, + uint32_t hash, Elf32_Addr* address); +uint32_t elf_symbolname_hash(const char* s); + #ifdef __cplusplus } @@ -48,8 +50,10 @@ struct HashtableApiInterface : public ElfApiInterface { .hash = elf_gnu_hash(#x), .address = (uint32_t)(static_cast(x)) \ } -#define API_VARIABLE(x, var_type) \ - sym_entry { .hash = elf_gnu_hash(#x), .address = (uint32_t)(&(x)), } +#define API_VARIABLE(x, var_type) \ + sym_entry { \ + .hash = elf_gnu_hash(#x), .address = (uint32_t)(&(x)), \ + } constexpr bool operator<(const sym_entry& k1, const sym_entry& k2) { return k1.hash < k2.hash; diff --git a/lib/flipper_application/elf/elf_api_interface.h b/lib/flipper_application/elf/elf_api_interface.h index f07df4edb76..facdc44473d 100644 --- a/lib/flipper_application/elf/elf_api_interface.h +++ b/lib/flipper_application/elf/elf_api_interface.h @@ -11,6 +11,6 @@ typedef struct ElfApiInterface { uint16_t api_version_minor; bool (*resolver_callback)( const struct ElfApiInterface* interface, - const char* name, + uint32_t hash, Elf32_Addr* address); } ElfApiInterface; diff --git a/lib/flipper_application/elf/elf_file.c b/lib/flipper_application/elf/elf_file.c index 0338144a9a7..fc9dd06ba81 100644 --- a/lib/flipper_application/elf/elf_file.c +++ b/lib/flipper_application/elf/elf_file.c @@ -2,6 +2,7 @@ #include "elf_file.h" #include "elf_file_i.h" #include "elf_api_interface.h" +#include "../api_hashtable/api_hashtable.h" #define TAG "elf" @@ -9,6 +10,7 @@ #define SECTION_OFFSET(e, n) ((e)->section_table + (n) * sizeof(Elf32_Shdr)) #define IS_FLAGS_SET(v, m) (((v) & (m)) == (m)) #define RESOLVER_THREAD_YIELD_STEP 30 +#define FAST_RELOCATION_VERSION 1 // #define ELF_DEBUG_LOG 1 @@ -71,6 +73,7 @@ static ELFSection* elf_file_get_or_put_section(ELFFile* elf, const char* name) { .size = 0, .rel_count = 0, .rel_offset = 0, + .fast_rel = NULL, }); section_p = elf_file_get_section(elf, name); } @@ -168,7 +171,8 @@ static ELFSection* elf_section_of(ELFFile* elf, int index) { static Elf32_Addr elf_address_of(ELFFile* elf, Elf32_Sym* sym, const char* sName) { if(sym->st_shndx == SHN_UNDEF) { Elf32_Addr addr = 0; - if(elf->api_interface->resolver_callback(elf->api_interface, sName, &addr)) { + uint32_t hash = elf_symbolname_hash(sName); + if(elf->api_interface->resolver_callback(elf->api_interface, hash, &addr)) { return addr; } } else { @@ -424,6 +428,7 @@ typedef enum { SectionTypeSymTab = 1 << 3, SectionTypeStrTab = 1 << 4, SectionTypeDebugLink = 1 << 5, + SectionTypeFastRelData = 1 << 6, SectionTypeValid = SectionTypeSymTab | SectionTypeStrTab, } SectionType; @@ -505,7 +510,8 @@ static SectionType elf_preload_section( // TODO: how to do it not by name? // .ARM: type 0x70000001, flags SHF_ALLOC | SHF_LINK_ORDER // .rel.ARM: type 0x9, flags SHT_REL - if(str_prefix(name, ".ARM.") || str_prefix(name, ".rel.ARM.")) { + if(str_prefix(name, ".ARM.") || str_prefix(name, ".rel.ARM.") || + str_prefix(name, ".fast.rel.ARM.")) { FURI_LOG_D(TAG, "Ignoring ARM section"); return SectionTypeUnused; } @@ -536,11 +542,31 @@ static SectionType elf_preload_section( // Load link info section if(section_header->sh_flags & SHF_INFO_LINK) { - name = name + strlen(".rel"); + if(str_prefix(name, ".rel")) { + name = name + strlen(".rel"); + ELFSection* section_p = elf_file_get_or_put_section(elf, name); + section_p->rel_count = section_header->sh_size / sizeof(Elf32_Rel); + section_p->rel_offset = section_header->sh_offset; + return SectionTypeRelData; + } else { + FURI_LOG_E(TAG, "Unknown link info section '%s'", name); + return SectionTypeERROR; + } + } + + // Load fast rel section + if(str_prefix(name, ".fast.rel")) { + name = name + strlen(".fast.rel"); ELFSection* section_p = elf_file_get_or_put_section(elf, name); - section_p->rel_count = section_header->sh_size / sizeof(Elf32_Rel); - section_p->rel_offset = section_header->sh_offset; - return SectionTypeRelData; + section_p->fast_rel = malloc(sizeof(ELFSection)); + + if(!elf_load_section_data(elf, section_p->fast_rel, section_header)) { + FURI_LOG_E(TAG, "Error loading section '%s'", name); + return SectionTypeERROR; + } + + FURI_LOG_D(TAG, "Loaded fast rel section for '%s'", name); + return SectionTypeFastRelData; } // Load symbol table @@ -571,8 +597,90 @@ static SectionType elf_preload_section( return SectionTypeUnused; } +static Elf32_Addr elf_address_of_by_hash(ELFFile* elf, uint32_t hash) { + Elf32_Addr addr = 0; + if(elf->api_interface->resolver_callback(elf->api_interface, hash, &addr)) { + return addr; + } + return ELF_INVALID_ADDRESS; +} + +static bool elf_relocate_fast(ELFFile* elf, ELFSection* s) { + UNUSED(elf); + const uint8_t* start = s->fast_rel->data; + const uint8_t version = *start; + + if(version != FAST_RELOCATION_VERSION) { + FURI_LOG_E(TAG, "Unsupported fast relocation version %d", version); + return false; + } + start += 1; + + const uint32_t records_count = *((uint32_t*)start); + start += 4; + FURI_LOG_D(TAG, "Fast relocation records count: %ld", records_count); + + for(uint32_t i = 0; i < records_count; i++) { + bool is_section = (*start & (0x1 << 7)) ? true : false; + uint8_t type = *start & 0x7F; + start += 1; + uint32_t hash_or_section_index = *((uint32_t*)start); + start += 4; + + uint32_t section_value = ELF_INVALID_ADDRESS; + if(is_section) { + section_value = *((uint32_t*)start); + start += 4; + } + + const uint32_t offsets_count = *((uint32_t*)start); + start += 4; + + FURI_LOG_D( + TAG, + "Fast relocation record %ld: is_section=%d, type=%d, hash_or_section_index=%lX, offsets_count=%ld", + i, + is_section, + type, + hash_or_section_index, + offsets_count); + + Elf32_Addr address = 0; + if(is_section) { + ELFSection* symSec = elf_section_of(elf, hash_or_section_index); + if(symSec) { + address = ((Elf32_Addr)symSec->data) + section_value; + } + } else { + address = elf_address_of_by_hash(elf, hash_or_section_index); + } + + if(address == ELF_INVALID_ADDRESS) { + FURI_LOG_E(TAG, "Failed to resolve address for hash %lX", hash_or_section_index); + return false; + } + + for(uint32_t j = 0; j < offsets_count; j++) { + uint32_t offset = *((uint32_t*)start) & 0x00FFFFFF; + start += 3; + // FURI_LOG_I(TAG, " Fast relocation offset %ld: %ld", j, offset); + Elf32_Addr relAddr = ((Elf32_Addr)s->data) + offset; + elf_relocate_symbol(elf, relAddr, type, address); + } + } + + aligned_free(s->fast_rel->data); + free(s->fast_rel); + s->fast_rel = NULL; + + return true; +} + static bool elf_relocate_section(ELFFile* elf, ELFSection* section) { - if(section->rel_count) { + if(section->fast_rel) { + FURI_LOG_D(TAG, "Fast relocating section"); + return elf_relocate_fast(elf, section); + } else if(section->rel_count) { FURI_LOG_D(TAG, "Relocating section"); return elf_relocate(elf, section); } else { @@ -630,6 +738,10 @@ void elf_file_free(ELFFile* elf) { if(itref->value.data) { aligned_free(itref->value.data); } + if(itref->value.fast_rel) { + aligned_free(itref->value.fast_rel->data); + free(itref->value.fast_rel); + } free((void*)itref->key); } diff --git a/lib/flipper_application/elf/elf_file_i.h b/lib/flipper_application/elf/elf_file_i.h index af9a1d9b4fb..39cadfdc6f6 100644 --- a/lib/flipper_application/elf/elf_file_i.h +++ b/lib/flipper_application/elf/elf_file_i.h @@ -13,14 +13,18 @@ DICT_DEF2(AddressCache, int, M_DEFAULT_OPLIST, Elf32_Addr, M_DEFAULT_OPLIST) */ typedef int32_t(entry_t)(void*); -typedef struct { +typedef struct ELFSection ELFSection; + +struct ELFSection { void* data; - uint16_t sec_idx; Elf32_Word size; size_t rel_count; Elf32_Off rel_offset; -} ELFSection; + ELFSection* fast_rel; + + uint16_t sec_idx; +}; DICT_DEF2(ELFSectionDict, const char*, M_CSTR_OPLIST, ELFSection, M_POD_OPLIST) diff --git a/lib/flipper_application/plugins/composite_resolver.c b/lib/flipper_application/plugins/composite_resolver.c index 1402c3ad08f..7cc2b340a96 100644 --- a/lib/flipper_application/plugins/composite_resolver.c +++ b/lib/flipper_application/plugins/composite_resolver.c @@ -13,12 +13,12 @@ struct CompositeApiResolver { static bool composite_api_resolver_callback( const ElfApiInterface* interface, - const char* name, + uint32_t hash, Elf32_Addr* address) { CompositeApiResolver* resolver = (CompositeApiResolver*)interface; for M_EACH(interface, resolver->interfaces, ElfApiInterfaceList_t) { - if((*interface)->resolver_callback(*interface, name, address)) { + if((*interface)->resolver_callback(*interface, hash, address)) { return true; } } diff --git a/scripts/distfap.py b/scripts/distfap.py old mode 100644 new mode 100755 diff --git a/scripts/fastfap.py b/scripts/fastfap.py new file mode 100755 index 00000000000..95e32c37be9 --- /dev/null +++ b/scripts/fastfap.py @@ -0,0 +1,169 @@ +#!/usr/bin/env python3 +import hashlib +import os +import struct +import subprocess +import tempfile +from collections import defaultdict +from dataclasses import dataclass + +from elftools.elf.elffile import ELFFile +from elftools.elf.relocation import RelocationSection +from elftools.elf.sections import SymbolTableSection +from fbt.sdk.hashes import gnu_sym_hash +from flipper.app import App + +VERSION = 1 + + +@dataclass +class RelData: + section: int + section_value: int + type: int + offset: int + name: str + + +@dataclass(frozen=True) +class UniqueRelData: + section: int + section_value: int + type: int + name: str + + +@dataclass +class RelSection: + name: str + oringinal_name: str + data: dict[UniqueRelData, list[int]] + + +def serialize_relsection_data(data: dict[UniqueRelData, list[int]]) -> bytes: + result = struct.pack(" 0: + result += struct.pack("> 8) & 0xFF, (offset >> 16) & 0xFF + ) + + return result + + +class Main(App): + def init(self): + self.parser.add_argument("fap_src_path", help="App file to upload") + self.parser.add_argument("objcopy_path", help="Objcopy path") + self.parser.set_defaults(func=self.process) + + def process(self): + fap_path = self.args.fap_src_path + objcopy_path = self.args.objcopy_path + + sections: list[RelSection] = [] + + with open(fap_path, "rb") as f: + elf_file = ELFFile(f) + + relocation_sections: list[RelocationSection] = [] + symtab_section: SymbolTableSection | None = None + + for section in elf_file.iter_sections(): + if isinstance(section, RelocationSection): + relocation_sections.append(section) + + if isinstance(section, SymbolTableSection): + symtab_section = section + + if not symtab_section: + self.logger.error("No symbol table found") + return 1 + + if not relocation_sections: + self.logger.info("No relocation sections found") + return 0 + + for section in relocation_sections: + section_relocations: list[RelData] = [] + + for relocation in section.iter_relocations(): + symbol_id: int = relocation.entry["r_info_sym"] + offset: int = relocation.entry["r_offset"] + type: int = relocation.entry["r_info_type"] + symbol = symtab_section.get_symbol(symbol_id) + section_index: int = symbol["st_shndx"] + section_value: int = symbol["st_value"] + if section_index == "SHN_UNDEF": + section_index = 0 + + section_relocations.append( + RelData(section_index, section_value, type, offset, symbol.name) + ) + + unique_relocations: dict[UniqueRelData, list[int]] = defaultdict(list) + for relocation in section_relocations: + unique = UniqueRelData( + relocation.section, + relocation.section_value, + relocation.type, + relocation.name, + ) + + unique_relocations[unique].append(relocation.offset) + + section_name = section.name + if section_name.startswith(".rel"): + section_name = ".fast.rel" + section_name[4:] + else: + self.logger.error( + "Unknown relocation section name: %s", section_name + ) + return 1 + + sections.append( + RelSection(section_name, section.name, unique_relocations) + ) + + with tempfile.TemporaryDirectory() as temp_dir: + for section in sections: + data = serialize_relsection_data(section.data) + hash_name = hashlib.md5(section.name.encode()).hexdigest() + filename = f"{temp_dir}/{hash_name}.bin" + + if os.path.isfile(filename): + self.logger.error(f"File {filename} already exists") + return 1 + + with open(filename, "wb") as f: + f.write(data) + + exit_code = subprocess.run( + [ + objcopy_path, + "--add-section", + f"{section.name}={filename}", + fap_path, + ], + check=True, + ) + + if exit_code.returncode != 0: + self.logger.error("objcopy failed") + return 1 + + return 0 + + +if __name__ == "__main__": + Main()() diff --git a/scripts/fbt/sdk/collector.py b/scripts/fbt/sdk/collector.py index 578a8c7a62b..1dd3bc4ebb9 100644 --- a/scripts/fbt/sdk/collector.py +++ b/scripts/fbt/sdk/collector.py @@ -1,4 +1,5 @@ from typing import List +from .hashes import gnu_sym_hash from cxxheaderparser.parser import CxxParser from . import ( @@ -72,13 +73,6 @@ def add_header(self, header: str): self.api.headers.add(ApiHeader(header)) -def gnu_sym_hash(name: str): - h = 0x1505 - for c in name: - h = (h << 5) + h + ord(c) - return str(hex(h))[-8:] - - class SdkCollector: def __init__(self): self.symbol_manager = SymbolManager() diff --git a/scripts/fbt/sdk/hashes.py b/scripts/fbt/sdk/hashes.py new file mode 100644 index 00000000000..fef88ddb5ee --- /dev/null +++ b/scripts/fbt/sdk/hashes.py @@ -0,0 +1,5 @@ +def gnu_sym_hash(name: str) -> int: + h = 0x1505 + for c in name: + h = ((h << 5) + h + ord(c)) & 0xFFFFFFFF + return h diff --git a/scripts/fbt_tools/fbt_extapps.py b/scripts/fbt_tools/fbt_extapps.py index 16d5dcbabd3..69d70021413 100644 --- a/scripts/fbt_tools/fbt_extapps.py +++ b/scripts/fbt_tools/fbt_extapps.py @@ -384,10 +384,16 @@ def generate_embed_app_metadata_actions(source, target, env, for_signature): "${SOURCES} ${TARGET}" ) - actions.append( - Action( - objcopy_str, - "$APPMETAEMBED_COMSTR", + actions.extend( + ( + Action( + objcopy_str, + "$APPMETAEMBED_COMSTR", + ), + Action( + "${PYTHON3} ${FBT_SCRIPT_DIR}/fastfap.py ${TARGET} ${OBJCOPY}", + "$FASTFAP_COMSTR", + ), ) ) @@ -450,6 +456,7 @@ def generate(env, **kw): APPMETA_COMSTR="\tAPPMETA\t${TARGET}", APPFILE_COMSTR="\tAPPFILE\t${TARGET}", APPMETAEMBED_COMSTR="\tFAP\t${TARGET}", + FASTFAP_COMSTR="\tFASTFAP\t${TARGET}", APPCHECK_COMSTR="\tAPPCHK\t${SOURCE}", ) diff --git a/scripts/fwsize.py b/scripts/fwsize.py old mode 100644 new mode 100755 diff --git a/scripts/get_env.py b/scripts/get_env.py old mode 100644 new mode 100755 diff --git a/scripts/runfap.py b/scripts/runfap.py old mode 100644 new mode 100755 diff --git a/scripts/sconsdist.py b/scripts/sconsdist.py old mode 100644 new mode 100755 diff --git a/scripts/selfupdate.py b/scripts/selfupdate.py old mode 100644 new mode 100755 diff --git a/scripts/slideshow.py b/scripts/slideshow.py old mode 100644 new mode 100755 diff --git a/scripts/toolchain/fbtenv.cmd b/scripts/toolchain/fbtenv.cmd index 9d45b7e9d7e..4ae04e2a2cb 100644 --- a/scripts/toolchain/fbtenv.cmd +++ b/scripts/toolchain/fbtenv.cmd @@ -13,7 +13,7 @@ if not ["%FBT_NOENV%"] == [""] ( exit /b 0 ) -set "FLIPPER_TOOLCHAIN_VERSION=21" +set "FLIPPER_TOOLCHAIN_VERSION=22" if ["%FBT_TOOLCHAIN_PATH%"] == [""] ( set "FBT_TOOLCHAIN_PATH=%FBT_ROOT%" diff --git a/scripts/toolchain/fbtenv.sh b/scripts/toolchain/fbtenv.sh index 143dce74b91..e5548f488b1 100755 --- a/scripts/toolchain/fbtenv.sh +++ b/scripts/toolchain/fbtenv.sh @@ -4,7 +4,7 @@ # public variables DEFAULT_SCRIPT_PATH="$(pwd -P)"; -FBT_TOOLCHAIN_VERSION="${FBT_TOOLCHAIN_VERSION:-"21"}"; +FBT_TOOLCHAIN_VERSION="${FBT_TOOLCHAIN_VERSION:-"22"}"; if [ -z ${FBT_TOOLCHAIN_PATH+x} ] ; then FBT_TOOLCHAIN_PATH_WAS_SET=0; diff --git a/scripts/version.py b/scripts/version.py old mode 100644 new mode 100755 From e52fdcf109cd01aaed0175195a3f15c290ae92d6 Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Wed, 28 Jun 2023 13:05:48 +0400 Subject: [PATCH 623/824] [FL-3388] NFC/RFID detector (#2795) * Field_Validation: add driver fild_validation_rfid * Field_Validation: add fild_validation_nfc * Field_Presence: added field validation functions to furi_hal_nfc * Field_Presence: added field validation functions to furi_hal_rfid * Field_Presence: add "NFC/RFID detector" app * Field_Presence: fix GUI "NFC/RFID detector" * NFC/RFID detector: add auto turn on backlight when field is detected * NFC/RFID detector: fix syntax errors * ApiSymbols: fix incorrect name * FuriHal: filed detect naming * FieldDetector: fix grammar Co-authored-by: Aleksandr Kutuzov --- .../nfc_rfid_detector/application.fam | 13 ++ .../helpers/nfc_rfid_detector_event.h | 7 + .../helpers/nfc_rfid_detector_types.h | 15 ++ .../images/Modern_reader_18x34.png | Bin 0 -> 3670 bytes .../images/Move_flipper_26x39.png | Bin 0 -> 3698 bytes .../images/NFC_detect_45x30.png | Bin 0 -> 168 bytes .../images/Rfid_detect_45x30.png | Bin 0 -> 158 bytes .../nfc_rfid_detector_10px.png | Bin 0 -> 124 bytes .../nfc_rfid_detector/nfc_rfid_detector_app.c | 108 ++++++++++++ .../nfc_rfid_detector_app_i.c | 40 +++++ .../nfc_rfid_detector_app_i.h | 30 ++++ .../scenes/nfc_rfid_detector_scene.c | 31 ++++ .../scenes/nfc_rfid_detector_scene.h | 29 ++++ .../scenes/nfc_rfid_detector_scene_about.c | 69 ++++++++ .../scenes/nfc_rfid_detector_scene_config.h | 3 + .../nfc_rfid_detector_scene_field_presence.c | 60 +++++++ .../scenes/nfc_rfid_detector_scene_start.c | 58 +++++++ .../nfc_rfid_detector_view_field_presence.c | 164 ++++++++++++++++++ .../nfc_rfid_detector_view_field_presence.h | 19 ++ firmware/targets/f18/api_symbols.csv | 2 +- firmware/targets/f7/api_symbols.csv | 7 +- firmware/targets/f7/furi_hal/furi_hal_nfc.c | 14 ++ firmware/targets/f7/furi_hal/furi_hal_nfc.h | 4 + firmware/targets/f7/furi_hal/furi_hal_rfid.c | 159 +++++++++++++++++ firmware/targets/f7/furi_hal/furi_hal_rfid.h | 14 ++ 25 files changed, 844 insertions(+), 2 deletions(-) create mode 100644 applications/external/nfc_rfid_detector/application.fam create mode 100644 applications/external/nfc_rfid_detector/helpers/nfc_rfid_detector_event.h create mode 100644 applications/external/nfc_rfid_detector/helpers/nfc_rfid_detector_types.h create mode 100644 applications/external/nfc_rfid_detector/images/Modern_reader_18x34.png create mode 100644 applications/external/nfc_rfid_detector/images/Move_flipper_26x39.png create mode 100644 applications/external/nfc_rfid_detector/images/NFC_detect_45x30.png create mode 100644 applications/external/nfc_rfid_detector/images/Rfid_detect_45x30.png create mode 100644 applications/external/nfc_rfid_detector/nfc_rfid_detector_10px.png create mode 100644 applications/external/nfc_rfid_detector/nfc_rfid_detector_app.c create mode 100644 applications/external/nfc_rfid_detector/nfc_rfid_detector_app_i.c create mode 100644 applications/external/nfc_rfid_detector/nfc_rfid_detector_app_i.h create mode 100644 applications/external/nfc_rfid_detector/scenes/nfc_rfid_detector_scene.c create mode 100644 applications/external/nfc_rfid_detector/scenes/nfc_rfid_detector_scene.h create mode 100644 applications/external/nfc_rfid_detector/scenes/nfc_rfid_detector_scene_about.c create mode 100644 applications/external/nfc_rfid_detector/scenes/nfc_rfid_detector_scene_config.h create mode 100644 applications/external/nfc_rfid_detector/scenes/nfc_rfid_detector_scene_field_presence.c create mode 100644 applications/external/nfc_rfid_detector/scenes/nfc_rfid_detector_scene_start.c create mode 100644 applications/external/nfc_rfid_detector/views/nfc_rfid_detector_view_field_presence.c create mode 100644 applications/external/nfc_rfid_detector/views/nfc_rfid_detector_view_field_presence.h diff --git a/applications/external/nfc_rfid_detector/application.fam b/applications/external/nfc_rfid_detector/application.fam new file mode 100644 index 00000000000..70c91bc8437 --- /dev/null +++ b/applications/external/nfc_rfid_detector/application.fam @@ -0,0 +1,13 @@ +App( + appid="nfc_rfid_detector", + name="NFC/RFID detector", + apptype=FlipperAppType.EXTERNAL, + targets=["f7"], + entry_point="nfc_rfid_detector_app", + requires=["gui"], + stack_size=4 * 1024, + order=50, + fap_icon="nfc_rfid_detector_10px.png", + fap_category="Tools", + fap_icon_assets="images", +) diff --git a/applications/external/nfc_rfid_detector/helpers/nfc_rfid_detector_event.h b/applications/external/nfc_rfid_detector/helpers/nfc_rfid_detector_event.h new file mode 100644 index 00000000000..bbffe2938e4 --- /dev/null +++ b/applications/external/nfc_rfid_detector/helpers/nfc_rfid_detector_event.h @@ -0,0 +1,7 @@ +#pragma once + +typedef enum { + //NfcRfidDetectorCustomEvent + NfcRfidDetectorCustomEventStartId = 100, + +} NfcRfidDetectorCustomEvent; diff --git a/applications/external/nfc_rfid_detector/helpers/nfc_rfid_detector_types.h b/applications/external/nfc_rfid_detector/helpers/nfc_rfid_detector_types.h new file mode 100644 index 00000000000..5d44b09b7f2 --- /dev/null +++ b/applications/external/nfc_rfid_detector/helpers/nfc_rfid_detector_types.h @@ -0,0 +1,15 @@ +#pragma once + +#include +#include + +#define NFC_RFID_DETECTOR_VERSION_APP "0.1" +#define NFC_RFID_DETECTOR_DEVELOPED "SkorP" +#define NFC_RFID_DETECTOR_GITHUB "https://github.com/flipperdevices/flipperzero-firmware" + +typedef enum { + NfcRfidDetectorViewVariableItemList, + NfcRfidDetectorViewSubmenu, + NfcRfidDetectorViewFieldPresence, + NfcRfidDetectorViewWidget, +} NfcRfidDetectorView; diff --git a/applications/external/nfc_rfid_detector/images/Modern_reader_18x34.png b/applications/external/nfc_rfid_detector/images/Modern_reader_18x34.png new file mode 100644 index 0000000000000000000000000000000000000000..b19c0f30c9f3928d3129acc9da92d5a9e962d084 GIT binary patch literal 3670 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 literal 0 HcmV?d00001 diff --git a/applications/external/nfc_rfid_detector/images/Move_flipper_26x39.png b/applications/external/nfc_rfid_detector/images/Move_flipper_26x39.png new file mode 100644 index 0000000000000000000000000000000000000000..ff4af9ff05989e5ff04d3888971b7f7801c59ed4 GIT binary patch literal 3698 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 QfL1Vgy85}Sb4q9e0PSTwjsO4v literal 0 HcmV?d00001 diff --git a/applications/external/nfc_rfid_detector/images/Rfid_detect_45x30.png b/applications/external/nfc_rfid_detector/images/Rfid_detect_45x30.png new file mode 100644 index 0000000000000000000000000000000000000000..35c205049bc5a6c7f0bb13a0cb3057963a023c6e GIT binary patch literal 158 zcmeAS@N?(olHy`uVBq!ia0vp^xG+l41V{G`^6RB9^&ce?Q{EhH1o(i zcG0sF=jLrIU`gfJ9e<(w?c#$+`{GybuV0XK^{NaL55t;h1_!r?84rQRGkCiCxvXf4IeWS|hDc0ZJK-P~g93;1_80%_ zf0mxq_>?eVL9oQDG>54hmUMGg2<9ZE<@p-^(r|y*a$w%vQ*}A2s_`AWqh21A3+I&= XdhU3CV!O&Kpm7YIu6{1-oD!M<1Jf%z literal 0 HcmV?d00001 diff --git a/applications/external/nfc_rfid_detector/nfc_rfid_detector_app.c b/applications/external/nfc_rfid_detector/nfc_rfid_detector_app.c new file mode 100644 index 00000000000..cba8b60856a --- /dev/null +++ b/applications/external/nfc_rfid_detector/nfc_rfid_detector_app.c @@ -0,0 +1,108 @@ +#include "nfc_rfid_detector_app_i.h" + +#include +#include + +static bool nfc_rfid_detector_app_custom_event_callback(void* context, uint32_t event) { + furi_assert(context); + NfcRfidDetectorApp* app = context; + return scene_manager_handle_custom_event(app->scene_manager, event); +} + +static bool nfc_rfid_detector_app_back_event_callback(void* context) { + furi_assert(context); + NfcRfidDetectorApp* app = context; + return scene_manager_handle_back_event(app->scene_manager); +} + +static void nfc_rfid_detector_app_tick_event_callback(void* context) { + furi_assert(context); + NfcRfidDetectorApp* app = context; + scene_manager_handle_tick_event(app->scene_manager); +} + +NfcRfidDetectorApp* nfc_rfid_detector_app_alloc() { + NfcRfidDetectorApp* app = malloc(sizeof(NfcRfidDetectorApp)); + + // GUI + app->gui = furi_record_open(RECORD_GUI); + + // View Dispatcher + app->view_dispatcher = view_dispatcher_alloc(); + app->scene_manager = scene_manager_alloc(&nfc_rfid_detector_scene_handlers, app); + view_dispatcher_enable_queue(app->view_dispatcher); + + view_dispatcher_set_event_callback_context(app->view_dispatcher, app); + view_dispatcher_set_custom_event_callback( + app->view_dispatcher, nfc_rfid_detector_app_custom_event_callback); + view_dispatcher_set_navigation_event_callback( + app->view_dispatcher, nfc_rfid_detector_app_back_event_callback); + view_dispatcher_set_tick_event_callback( + app->view_dispatcher, nfc_rfid_detector_app_tick_event_callback, 100); + + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + + // Open Notification record + app->notifications = furi_record_open(RECORD_NOTIFICATION); + + // SubMenu + app->submenu = submenu_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, NfcRfidDetectorViewSubmenu, submenu_get_view(app->submenu)); + + // Widget + app->widget = widget_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, NfcRfidDetectorViewWidget, widget_get_view(app->widget)); + + // Field Presence + app->nfc_rfid_detector_field_presence = nfc_rfid_detector_view_field_presence_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + NfcRfidDetectorViewFieldPresence, + nfc_rfid_detector_view_field_presence_get_view(app->nfc_rfid_detector_field_presence)); + + scene_manager_next_scene(app->scene_manager, NfcRfidDetectorSceneStart); + + return app; +} + +void nfc_rfid_detector_app_free(NfcRfidDetectorApp* app) { + furi_assert(app); + + // Submenu + view_dispatcher_remove_view(app->view_dispatcher, NfcRfidDetectorViewSubmenu); + submenu_free(app->submenu); + + // Widget + view_dispatcher_remove_view(app->view_dispatcher, NfcRfidDetectorViewWidget); + widget_free(app->widget); + + // Field Presence + view_dispatcher_remove_view(app->view_dispatcher, NfcRfidDetectorViewFieldPresence); + nfc_rfid_detector_view_field_presence_free(app->nfc_rfid_detector_field_presence); + + // View dispatcher + view_dispatcher_free(app->view_dispatcher); + scene_manager_free(app->scene_manager); + + // Notifications + furi_record_close(RECORD_NOTIFICATION); + app->notifications = NULL; + + // Close records + furi_record_close(RECORD_GUI); + + free(app); +} + +int32_t nfc_rfid_detector_app(void* p) { + UNUSED(p); + NfcRfidDetectorApp* nfc_rfid_detector_app = nfc_rfid_detector_app_alloc(); + + view_dispatcher_run(nfc_rfid_detector_app->view_dispatcher); + + nfc_rfid_detector_app_free(nfc_rfid_detector_app); + + return 0; +} diff --git a/applications/external/nfc_rfid_detector/nfc_rfid_detector_app_i.c b/applications/external/nfc_rfid_detector/nfc_rfid_detector_app_i.c new file mode 100644 index 00000000000..c59d40d50c5 --- /dev/null +++ b/applications/external/nfc_rfid_detector/nfc_rfid_detector_app_i.c @@ -0,0 +1,40 @@ +#include "nfc_rfid_detector_app_i.h" + +#include + +#define TAG "NfcRfidDetector" + +void nfc_rfid_detector_app_field_presence_start(NfcRfidDetectorApp* app) { + furi_assert(app); + + // start the field presence rfid detection + furi_hal_rfid_field_detect_start(); + + // start the field presence nfc detection + furi_hal_nfc_exit_sleep(); + furi_hal_nfc_field_detect_start(); +} + +void nfc_rfid_detector_app_field_presence_stop(NfcRfidDetectorApp* app) { + furi_assert(app); + + // stop the field presence rfid detection + furi_hal_rfid_field_detect_stop(); + + // stop the field presence nfc detection + furi_hal_nfc_start_sleep(); +} + +bool nfc_rfid_detector_app_field_presence_is_nfc(NfcRfidDetectorApp* app) { + furi_assert(app); + + // check if the field presence is nfc + return furi_hal_nfc_field_is_present(); +} + +bool nfc_rfid_detector_app_field_presence_is_rfid(NfcRfidDetectorApp* app, uint32_t* frequency) { + furi_assert(app); + + // check if the field presence is rfid + return furi_hal_rfid_field_is_present(frequency); +} \ No newline at end of file diff --git a/applications/external/nfc_rfid_detector/nfc_rfid_detector_app_i.h b/applications/external/nfc_rfid_detector/nfc_rfid_detector_app_i.h new file mode 100644 index 00000000000..72cb126d4a3 --- /dev/null +++ b/applications/external/nfc_rfid_detector/nfc_rfid_detector_app_i.h @@ -0,0 +1,30 @@ +#pragma once + +#include "helpers/nfc_rfid_detector_types.h" +#include "helpers/nfc_rfid_detector_event.h" + +#include "scenes/nfc_rfid_detector_scene.h" +#include +#include +#include +#include +#include +#include +#include "views/nfc_rfid_detector_view_field_presence.h" + +typedef struct NfcRfidDetectorApp NfcRfidDetectorApp; + +struct NfcRfidDetectorApp { + Gui* gui; + ViewDispatcher* view_dispatcher; + SceneManager* scene_manager; + NotificationApp* notifications; + Submenu* submenu; + Widget* widget; + NfcRfidDetectorFieldPresence* nfc_rfid_detector_field_presence; +}; + +void nfc_rfid_detector_app_field_presence_start(NfcRfidDetectorApp* app); +void nfc_rfid_detector_app_field_presence_stop(NfcRfidDetectorApp* app); +bool nfc_rfid_detector_app_field_presence_is_nfc(NfcRfidDetectorApp* app); +bool nfc_rfid_detector_app_field_presence_is_rfid(NfcRfidDetectorApp* app, uint32_t* frequency); \ No newline at end of file diff --git a/applications/external/nfc_rfid_detector/scenes/nfc_rfid_detector_scene.c b/applications/external/nfc_rfid_detector/scenes/nfc_rfid_detector_scene.c new file mode 100644 index 00000000000..d75eb2884fe --- /dev/null +++ b/applications/external/nfc_rfid_detector/scenes/nfc_rfid_detector_scene.c @@ -0,0 +1,31 @@ +#include "../nfc_rfid_detector_app_i.h" + +// Generate scene on_enter handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, +void (*const nfc_rfid_detector_scene_on_enter_handlers[])(void*) = { +#include "nfc_rfid_detector_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 nfc_rfid_detector_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = + { +#include "nfc_rfid_detector_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 nfc_rfid_detector_scene_on_exit_handlers[])(void* context) = { +#include "nfc_rfid_detector_scene_config.h" +}; +#undef ADD_SCENE + +// Initialize scene handlers configuration structure +const SceneManagerHandlers nfc_rfid_detector_scene_handlers = { + .on_enter_handlers = nfc_rfid_detector_scene_on_enter_handlers, + .on_event_handlers = nfc_rfid_detector_scene_on_event_handlers, + .on_exit_handlers = nfc_rfid_detector_scene_on_exit_handlers, + .scene_num = NfcRfidDetectorSceneNum, +}; diff --git a/applications/external/nfc_rfid_detector/scenes/nfc_rfid_detector_scene.h b/applications/external/nfc_rfid_detector/scenes/nfc_rfid_detector_scene.h new file mode 100644 index 00000000000..74d324b4d3b --- /dev/null +++ b/applications/external/nfc_rfid_detector/scenes/nfc_rfid_detector_scene.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +// Generate scene id and total number +#define ADD_SCENE(prefix, name, id) NfcRfidDetectorScene##id, +typedef enum { +#include "nfc_rfid_detector_scene_config.h" + NfcRfidDetectorSceneNum, +} NfcRfidDetectorScene; +#undef ADD_SCENE + +extern const SceneManagerHandlers nfc_rfid_detector_scene_handlers; + +// Generate scene on_enter handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); +#include "nfc_rfid_detector_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 "nfc_rfid_detector_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 "nfc_rfid_detector_scene_config.h" +#undef ADD_SCENE diff --git a/applications/external/nfc_rfid_detector/scenes/nfc_rfid_detector_scene_about.c b/applications/external/nfc_rfid_detector/scenes/nfc_rfid_detector_scene_about.c new file mode 100644 index 00000000000..ddcb8aac0fe --- /dev/null +++ b/applications/external/nfc_rfid_detector/scenes/nfc_rfid_detector_scene_about.c @@ -0,0 +1,69 @@ +#include "../nfc_rfid_detector_app_i.h" + +void nfc_rfid_detector_scene_about_widget_callback( + GuiButtonType result, + InputType type, + void* context) { + NfcRfidDetectorApp* app = context; + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(app->view_dispatcher, result); + } +} + +void nfc_rfid_detector_scene_about_on_enter(void* context) { + NfcRfidDetectorApp* app = context; + + FuriString* temp_str; + temp_str = furi_string_alloc(); + furi_string_printf(temp_str, "\e#%s\n", "Information"); + + furi_string_cat_printf(temp_str, "Version: %s\n", NFC_RFID_DETECTOR_VERSION_APP); + furi_string_cat_printf(temp_str, "Developed by: %s\n", NFC_RFID_DETECTOR_DEVELOPED); + furi_string_cat_printf(temp_str, "Github: %s\n\n", NFC_RFID_DETECTOR_GITHUB); + + furi_string_cat_printf(temp_str, "\e#%s\n", "Description"); + furi_string_cat_printf( + temp_str, + "This application allows\nyou to determine what\ntype of electromagnetic\nfield the reader is using.\nFor LF RFID you can also\nsee the carrier frequency\n\n"); + + widget_add_text_box_element( + app->widget, + 0, + 0, + 128, + 14, + AlignCenter, + AlignBottom, + "\e#\e! \e!\n", + false); + widget_add_text_box_element( + app->widget, + 0, + 2, + 128, + 14, + AlignCenter, + AlignBottom, + "\e#\e! NFC/RFID detector \e!\n", + false); + widget_add_text_scroll_element(app->widget, 0, 16, 128, 50, furi_string_get_cstr(temp_str)); + furi_string_free(temp_str); + + view_dispatcher_switch_to_view(app->view_dispatcher, NfcRfidDetectorViewWidget); +} + +bool nfc_rfid_detector_scene_about_on_event(void* context, SceneManagerEvent event) { + NfcRfidDetectorApp* app = context; + bool consumed = false; + UNUSED(app); + UNUSED(event); + + return consumed; +} + +void nfc_rfid_detector_scene_about_on_exit(void* context) { + NfcRfidDetectorApp* app = context; + + // Clear views + widget_reset(app->widget); +} diff --git a/applications/external/nfc_rfid_detector/scenes/nfc_rfid_detector_scene_config.h b/applications/external/nfc_rfid_detector/scenes/nfc_rfid_detector_scene_config.h new file mode 100644 index 00000000000..ab49ad5c2cf --- /dev/null +++ b/applications/external/nfc_rfid_detector/scenes/nfc_rfid_detector_scene_config.h @@ -0,0 +1,3 @@ +ADD_SCENE(nfc_rfid_detector, start, Start) +ADD_SCENE(nfc_rfid_detector, about, About) +ADD_SCENE(nfc_rfid_detector, field_presence, FieldPresence) diff --git a/applications/external/nfc_rfid_detector/scenes/nfc_rfid_detector_scene_field_presence.c b/applications/external/nfc_rfid_detector/scenes/nfc_rfid_detector_scene_field_presence.c new file mode 100644 index 00000000000..ec53b5a0a60 --- /dev/null +++ b/applications/external/nfc_rfid_detector/scenes/nfc_rfid_detector_scene_field_presence.c @@ -0,0 +1,60 @@ +#include "../nfc_rfid_detector_app_i.h" +#include "../views/nfc_rfid_detector_view_field_presence.h" + +void nfc_rfid_detector_scene_field_presence_callback( + NfcRfidDetectorCustomEvent event, + void* context) { + furi_assert(context); + NfcRfidDetectorApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, event); +} + +static const NotificationSequence notification_app_display_on = { + + &message_display_backlight_on, + NULL, +}; + +static void nfc_rfid_detector_scene_field_presence_update(void* context) { + furi_assert(context); + NfcRfidDetectorApp* app = context; + + uint32_t frequency = 0; + bool nfc_field = nfc_rfid_detector_app_field_presence_is_nfc(app); + bool rfid_field = nfc_rfid_detector_app_field_presence_is_rfid(app, &frequency); + + if(nfc_field || rfid_field) + notification_message(app->notifications, ¬ification_app_display_on); + + nfc_rfid_detector_view_field_presence_update( + app->nfc_rfid_detector_field_presence, nfc_field, rfid_field, frequency); +} + +void nfc_rfid_detector_scene_field_presence_on_enter(void* context) { + furi_assert(context); + NfcRfidDetectorApp* app = context; + + // Start detection of field presence + nfc_rfid_detector_app_field_presence_start(app); + + view_dispatcher_switch_to_view(app->view_dispatcher, NfcRfidDetectorViewFieldPresence); +} + +bool nfc_rfid_detector_scene_field_presence_on_event(void* context, SceneManagerEvent event) { + furi_assert(context); + NfcRfidDetectorApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeTick) { + nfc_rfid_detector_scene_field_presence_update(app); + } + + return consumed; +} + +void nfc_rfid_detector_scene_field_presence_on_exit(void* context) { + furi_assert(context); + NfcRfidDetectorApp* app = context; + // Stop detection of field presence + nfc_rfid_detector_app_field_presence_stop(app); +} diff --git a/applications/external/nfc_rfid_detector/scenes/nfc_rfid_detector_scene_start.c b/applications/external/nfc_rfid_detector/scenes/nfc_rfid_detector_scene_start.c new file mode 100644 index 00000000000..7b71bd97351 --- /dev/null +++ b/applications/external/nfc_rfid_detector/scenes/nfc_rfid_detector_scene_start.c @@ -0,0 +1,58 @@ +#include "../nfc_rfid_detector_app_i.h" + +typedef enum { + SubmenuIndexNfcRfidDetectorFieldPresence, + SubmenuIndexNfcRfidDetectorAbout, +} SubmenuIndex; + +void nfc_rfid_detector_scene_start_submenu_callback(void* context, uint32_t index) { + NfcRfidDetectorApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, index); +} + +void nfc_rfid_detector_scene_start_on_enter(void* context) { + UNUSED(context); + NfcRfidDetectorApp* app = context; + Submenu* submenu = app->submenu; + + submenu_add_item( + submenu, + "Detect field type", + SubmenuIndexNfcRfidDetectorFieldPresence, + nfc_rfid_detector_scene_start_submenu_callback, + app); + submenu_add_item( + submenu, + "About", + SubmenuIndexNfcRfidDetectorAbout, + nfc_rfid_detector_scene_start_submenu_callback, + app); + + submenu_set_selected_item( + submenu, scene_manager_get_scene_state(app->scene_manager, NfcRfidDetectorSceneStart)); + + view_dispatcher_switch_to_view(app->view_dispatcher, NfcRfidDetectorViewSubmenu); +} + +bool nfc_rfid_detector_scene_start_on_event(void* context, SceneManagerEvent event) { + NfcRfidDetectorApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubmenuIndexNfcRfidDetectorAbout) { + scene_manager_next_scene(app->scene_manager, NfcRfidDetectorSceneAbout); + consumed = true; + } else if(event.event == SubmenuIndexNfcRfidDetectorFieldPresence) { + scene_manager_next_scene(app->scene_manager, NfcRfidDetectorSceneFieldPresence); + consumed = true; + } + scene_manager_set_scene_state(app->scene_manager, NfcRfidDetectorSceneStart, event.event); + } + + return consumed; +} + +void nfc_rfid_detector_scene_start_on_exit(void* context) { + NfcRfidDetectorApp* app = context; + submenu_reset(app->submenu); +} diff --git a/applications/external/nfc_rfid_detector/views/nfc_rfid_detector_view_field_presence.c b/applications/external/nfc_rfid_detector/views/nfc_rfid_detector_view_field_presence.c new file mode 100644 index 00000000000..e65eb8362b4 --- /dev/null +++ b/applications/external/nfc_rfid_detector/views/nfc_rfid_detector_view_field_presence.c @@ -0,0 +1,164 @@ +#include "nfc_rfid_detector_view_field_presence.h" +#include "../nfc_rfid_detector_app_i.h" +#include + +#include +#include + +#define FIELD_FOUND_WEIGHT 5 + +typedef enum { + NfcRfidDetectorTypeFieldPresenceNfc, + NfcRfidDetectorTypeFieldPresenceRfid, +} NfcRfidDetectorTypeFieldPresence; + +static const Icon* NfcRfidDetectorFieldPresenceIcons[] = { + [NfcRfidDetectorTypeFieldPresenceNfc] = &I_NFC_detect_45x30, + [NfcRfidDetectorTypeFieldPresenceRfid] = &I_Rfid_detect_45x30, +}; + +struct NfcRfidDetectorFieldPresence { + View* view; +}; + +typedef struct { + uint8_t nfc_field; + uint8_t rfid_field; + uint32_t rfid_frequency; +} NfcRfidDetectorFieldPresenceModel; + +void nfc_rfid_detector_view_field_presence_update( + NfcRfidDetectorFieldPresence* instance, + bool nfc_field, + bool rfid_field, + uint32_t rfid_frequency) { + furi_assert(instance); + with_view_model( + instance->view, + NfcRfidDetectorFieldPresenceModel * model, + { + if(nfc_field) { + model->nfc_field = FIELD_FOUND_WEIGHT; + } else if(model->nfc_field) { + model->nfc_field--; + } + if(rfid_field) { + model->rfid_field = FIELD_FOUND_WEIGHT; + model->rfid_frequency = rfid_frequency; + } else if(model->rfid_field) { + model->rfid_field--; + } + }, + true); +} + +void nfc_rfid_detector_view_field_presence_draw( + Canvas* canvas, + NfcRfidDetectorFieldPresenceModel* model) { + canvas_clear(canvas); + canvas_set_color(canvas, ColorBlack); + + if(!model->nfc_field && !model->rfid_field) { + canvas_draw_icon(canvas, 0, 16, &I_Modern_reader_18x34); + canvas_draw_icon(canvas, 22, 12, &I_Move_flipper_26x39); + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 56, 36, "Touch the reader"); + } else { + if(model->nfc_field) { + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 21, 10, "NFC"); + canvas_draw_icon( + canvas, + 9, + 17, + NfcRfidDetectorFieldPresenceIcons[NfcRfidDetectorTypeFieldPresenceNfc]); + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 9, 62, "13,56 MHz"); + } + + if(model->rfid_field) { + char str[16]; + snprintf(str, sizeof(str), "%.02f KHz", (double)model->rfid_frequency / 1000); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 76, 10, "LF RFID"); + canvas_draw_icon( + canvas, + 71, + 17, + NfcRfidDetectorFieldPresenceIcons[NfcRfidDetectorTypeFieldPresenceRfid]); + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 69, 62, str); + } + } +} + +bool nfc_rfid_detector_view_field_presence_input(InputEvent* event, void* context) { + furi_assert(context); + NfcRfidDetectorFieldPresence* instance = context; + UNUSED(instance); + + if(event->key == InputKeyBack) { + return false; + } + + return true; +} + +void nfc_rfid_detector_view_field_presence_enter(void* context) { + furi_assert(context); + NfcRfidDetectorFieldPresence* instance = context; + with_view_model( + instance->view, + NfcRfidDetectorFieldPresenceModel * model, + { + model->nfc_field = 0; + model->rfid_field = 0; + model->rfid_frequency = 0; + }, + true); +} + +void nfc_rfid_detector_view_field_presence_exit(void* context) { + furi_assert(context); + NfcRfidDetectorFieldPresence* instance = context; + UNUSED(instance); +} + +NfcRfidDetectorFieldPresence* nfc_rfid_detector_view_field_presence_alloc() { + NfcRfidDetectorFieldPresence* instance = malloc(sizeof(NfcRfidDetectorFieldPresence)); + + // View allocation and configuration + instance->view = view_alloc(); + + view_allocate_model( + instance->view, ViewModelTypeLocking, sizeof(NfcRfidDetectorFieldPresenceModel)); + view_set_context(instance->view, instance); + view_set_draw_callback( + instance->view, (ViewDrawCallback)nfc_rfid_detector_view_field_presence_draw); + view_set_input_callback(instance->view, nfc_rfid_detector_view_field_presence_input); + view_set_enter_callback(instance->view, nfc_rfid_detector_view_field_presence_enter); + view_set_exit_callback(instance->view, nfc_rfid_detector_view_field_presence_exit); + + with_view_model( + instance->view, + NfcRfidDetectorFieldPresenceModel * model, + { + model->nfc_field = 0; + model->rfid_field = 0; + model->rfid_frequency = 0; + }, + true); + return instance; +} + +void nfc_rfid_detector_view_field_presence_free(NfcRfidDetectorFieldPresence* instance) { + furi_assert(instance); + + view_free(instance->view); + free(instance); +} + +View* nfc_rfid_detector_view_field_presence_get_view(NfcRfidDetectorFieldPresence* instance) { + furi_assert(instance); + return instance->view; +} diff --git a/applications/external/nfc_rfid_detector/views/nfc_rfid_detector_view_field_presence.h b/applications/external/nfc_rfid_detector/views/nfc_rfid_detector_view_field_presence.h new file mode 100644 index 00000000000..0ddb4e2cd58 --- /dev/null +++ b/applications/external/nfc_rfid_detector/views/nfc_rfid_detector_view_field_presence.h @@ -0,0 +1,19 @@ +#pragma once + +#include +#include "../helpers/nfc_rfid_detector_types.h" +#include "../helpers/nfc_rfid_detector_event.h" + +typedef struct NfcRfidDetectorFieldPresence NfcRfidDetectorFieldPresence; + +void nfc_rfid_detector_view_field_presence_update( + NfcRfidDetectorFieldPresence* instance, + bool nfc_field, + bool rfid_field, + uint32_t rfid_frequency); + +NfcRfidDetectorFieldPresence* nfc_rfid_detector_view_field_presence_alloc(); + +void nfc_rfid_detector_view_field_presence_free(NfcRfidDetectorFieldPresence* instance); + +View* nfc_rfid_detector_view_field_presence_get_view(NfcRfidDetectorFieldPresence* instance); diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index 56a7f678da8..0b4e2d6d61d 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,31.0,, +Version,+,31.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 0f782e966c6..5ba66407ed0 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,31.0,, +Version,+,31.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -1220,6 +1220,8 @@ Function,+,furi_hal_mpu_protect_disable,void,FuriHalMpuRegion Function,+,furi_hal_mpu_protect_no_access,void,"FuriHalMpuRegion, uint32_t, FuriHalMPURegionSize" Function,+,furi_hal_mpu_protect_read_only,void,"FuriHalMpuRegion, uint32_t, FuriHalMPURegionSize" Function,+,furi_hal_nfc_activate_nfca,_Bool,"uint32_t, uint32_t*" +Function,+,furi_hal_nfc_field_is_present,_Bool, +Function,+,furi_hal_nfc_field_detect_start,void, Function,-,furi_hal_nfc_deinit,void, Function,+,furi_hal_nfc_detect,_Bool,"FuriHalNfcDevData*, uint32_t" Function,+,furi_hal_nfc_emulate_nfca,_Bool,"uint8_t*, uint8_t, uint8_t*, uint8_t, FuriHalNfcEmulateCallback, void*, uint32_t" @@ -1304,6 +1306,9 @@ Function,-,furi_hal_resources_init_early,void, Function,+,furi_hal_rfid_comp_set_callback,void,"FuriHalRfidCompCallback, void*" Function,+,furi_hal_rfid_comp_start,void, Function,+,furi_hal_rfid_comp_stop,void, +Function,+,furi_hal_rfid_field_is_present,_Bool,uint32_t* +Function,+,furi_hal_rfid_field_detect_start,void, +Function,+,furi_hal_rfid_field_detect_stop,void, Function,-,furi_hal_rfid_init,void, Function,+,furi_hal_rfid_pin_pull_pulldown,void, Function,+,furi_hal_rfid_pin_pull_release,void, diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc.c b/firmware/targets/f7/furi_hal/furi_hal_nfc.c index c4e7ad9f984..b249c865899 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_nfc.c +++ b/firmware/targets/f7/furi_hal/furi_hal_nfc.c @@ -819,3 +819,17 @@ FuriHalNfcReturn furi_hal_nfc_ll_txrx_bits( void furi_hal_nfc_ll_poll() { rfalWorker(); } + +void furi_hal_nfc_field_detect_start() { + st25r3916WriteRegister( + ST25R3916_REG_OP_CONTROL, + ST25R3916_REG_OP_CONTROL_en | ST25R3916_REG_OP_CONTROL_en_fd_mask); + st25r3916WriteRegister(ST25R3916_REG_MODE, ST25R3916_REG_MODE_targ | ST25R3916_REG_MODE_om0); +} + +bool furi_hal_nfc_field_is_present() { + return st25r3916CheckReg( + ST25R3916_REG_AUX_DISPLAY, + ST25R3916_REG_AUX_DISPLAY_efd_o, + ST25R3916_REG_AUX_DISPLAY_efd_o); +} \ No newline at end of file diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc.h b/firmware/targets/f7/furi_hal/furi_hal_nfc.h index dc3f873f346..c87f04a9a0f 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_nfc.h +++ b/firmware/targets/f7/furi_hal/furi_hal_nfc.h @@ -423,6 +423,10 @@ FuriHalNfcReturn furi_hal_nfc_ll_txrx_bits( void furi_hal_nfc_ll_poll(); +void furi_hal_nfc_field_detect_start(); + +bool furi_hal_nfc_field_is_present(); + #ifdef __cplusplus } #endif diff --git a/firmware/targets/f7/furi_hal/furi_hal_rfid.c b/firmware/targets/f7/furi_hal/furi_hal_rfid.c index fa0c19b098b..67f11d6ff7b 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_rfid.c +++ b/firmware/targets/f7/furi_hal/furi_hal_rfid.c @@ -25,6 +25,19 @@ #define RFID_CAPTURE_IND_CH LL_TIM_CHANNEL_CH3 #define RFID_CAPTURE_DIR_CH LL_TIM_CHANNEL_CH4 +// Field presence detection +#define FURI_HAL_RFID_FIELD_FREQUENCY_MIN 80000 +#define FURI_HAL_RFID_FIELD_FREQUENCY_MAX 200000 + +#define FURI_HAL_RFID_FIELD_COUNTER_TIMER TIM2 +#define FURI_HAL_RFID_FIELD_COUNTER_TIMER_BUS FuriHalBusTIM2 +#define FURI_HAL_RFID_FIELD_COUNTER_TIMER_CHANNEL LL_TIM_CHANNEL_CH3 + +#define FURI_HAL_RFID_FIELD_TIMEOUT_TIMER TIM1 +#define FURI_HAL_RFID_FIELD_TIMEOUT_TIMER_BUS FuriHalBusTIM1 + +#define FURI_HAL_RFID_FIELD_DMAMUX_DMA LL_DMAMUX_REQ_TIM1_UP + /* DMA Channels definition */ #define RFID_DMA DMA2 #define RFID_DMA_CH1_CHANNEL LL_DMA_CHANNEL_1 @@ -33,10 +46,16 @@ #define RFID_DMA_CH1_DEF RFID_DMA, RFID_DMA_CH1_CHANNEL #define RFID_DMA_CH2_DEF RFID_DMA, RFID_DMA_CH2_CHANNEL +typedef struct { + uint32_t counter; + uint32_t set_tim_counter_cnt; +} FuriHalRfidField; + typedef struct { FuriHalRfidDMACallback dma_callback; FuriHalRfidReadCaptureCallback read_capture_callback; void* context; + FuriHalRfidField field; } FuriHalRfid; FuriHalRfid* furi_hal_rfid = NULL; @@ -51,6 +70,8 @@ FuriHalRfid* furi_hal_rfid = NULL; void furi_hal_rfid_init() { furi_assert(furi_hal_rfid == NULL); furi_hal_rfid = malloc(sizeof(FuriHalRfid)); + furi_hal_rfid->field.counter = 0; + furi_hal_rfid->field.set_tim_counter_cnt = 0; furi_hal_rfid_pins_reset(); @@ -133,6 +154,23 @@ static void furi_hal_rfid_pins_read() { furi_hal_gpio_init(&gpio_rfid_data_in, GpioModeAnalog, GpioPullNo, GpioSpeedLow); } +static void furi_hal_rfid_pins_field() { + // ibutton low + furi_hal_ibutton_pin_configure(); + furi_hal_ibutton_pin_write(false); + + // pull pin to timer out + furi_hal_gpio_init(&gpio_nfc_irq_rfid_pull, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_write(&gpio_nfc_irq_rfid_pull, false); + + // pull rfid antenna from carrier side + furi_hal_gpio_init(&gpio_rfid_carrier_out, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_write(&gpio_rfid_carrier_out, false); + + furi_hal_gpio_init_ex( + &gpio_rfid_carrier, GpioModeAltFunctionPushPull, GpioPullNo, GpioSpeedLow, GpioAltFn2TIM2); +} + void furi_hal_rfid_pin_pull_release() { furi_hal_gpio_write(&gpio_nfc_irq_rfid_pull, true); } @@ -427,3 +465,124 @@ void COMP_IRQHandler() { furi_hal_rfid_comp_callback_context); } } + +static void furi_hal_rfid_field_tim_setup() { + // setup timer counter + furi_hal_bus_enable(FURI_HAL_RFID_FIELD_COUNTER_TIMER_BUS); + + LL_TIM_SetPrescaler(FURI_HAL_RFID_FIELD_COUNTER_TIMER, 0); + LL_TIM_SetCounterMode(FURI_HAL_RFID_FIELD_COUNTER_TIMER, LL_TIM_COUNTERMODE_UP); + LL_TIM_SetAutoReload(FURI_HAL_RFID_FIELD_COUNTER_TIMER, 0xFFFFFFFF); + LL_TIM_DisableARRPreload(FURI_HAL_RFID_FIELD_COUNTER_TIMER); + LL_TIM_SetRepetitionCounter(FURI_HAL_RFID_FIELD_COUNTER_TIMER, 0); + + LL_TIM_SetClockDivision(FURI_HAL_RFID_FIELD_COUNTER_TIMER, LL_TIM_CLOCKDIVISION_DIV1); + LL_TIM_SetClockSource(FURI_HAL_RFID_FIELD_COUNTER_TIMER, LL_TIM_CLOCKSOURCE_EXT_MODE2); + LL_TIM_ConfigETR( + FURI_HAL_RFID_FIELD_COUNTER_TIMER, + LL_TIM_ETR_POLARITY_INVERTED, + LL_TIM_ETR_PRESCALER_DIV1, + LL_TIM_ETR_FILTER_FDIV1); + + LL_TIM_OC_InitTypeDef TIM_OC_InitStruct = {0}; + TIM_OC_InitStruct.OCMode = LL_TIM_OCMODE_PWM1; + TIM_OC_InitStruct.OCState = LL_TIM_OCSTATE_ENABLE; + TIM_OC_InitStruct.CompareValue = 1; + LL_TIM_OC_Init( + FURI_HAL_RFID_FIELD_COUNTER_TIMER, + FURI_HAL_RFID_FIELD_COUNTER_TIMER_CHANNEL, + &TIM_OC_InitStruct); + + LL_TIM_GenerateEvent_UPDATE(FURI_HAL_RFID_FIELD_COUNTER_TIMER); + LL_TIM_OC_SetPolarity( + FURI_HAL_RFID_FIELD_COUNTER_TIMER, + FURI_HAL_RFID_FIELD_COUNTER_TIMER_CHANNEL, + LL_TIM_OCPOLARITY_HIGH); + LL_TIM_EnableDMAReq_UPDATE(FURI_HAL_RFID_FIELD_COUNTER_TIMER); + + // setup timer timeouts dma + furi_hal_bus_enable(FURI_HAL_RFID_FIELD_TIMEOUT_TIMER_BUS); + + LL_TIM_SetPrescaler(FURI_HAL_RFID_FIELD_TIMEOUT_TIMER, 64000 - 1); + LL_TIM_SetCounterMode(FURI_HAL_RFID_FIELD_TIMEOUT_TIMER, LL_TIM_COUNTERMODE_UP); + LL_TIM_SetAutoReload(FURI_HAL_RFID_FIELD_TIMEOUT_TIMER, 100 - 1); // 100 ms + LL_TIM_SetClockDivision(FURI_HAL_RFID_FIELD_TIMEOUT_TIMER, LL_TIM_CLOCKDIVISION_DIV1); + LL_TIM_SetClockSource(FURI_HAL_RFID_FIELD_TIMEOUT_TIMER, LL_TIM_CLOCKSOURCE_INTERNAL); + + LL_TIM_DisableARRPreload(FURI_HAL_RFID_FIELD_TIMEOUT_TIMER); + + LL_TIM_EnableDMAReq_UPDATE(FURI_HAL_RFID_FIELD_TIMEOUT_TIMER); + LL_TIM_GenerateEvent_UPDATE(FURI_HAL_RFID_FIELD_TIMEOUT_TIMER); +} + +void furi_hal_rfid_field_detect_start(void) { + // setup pins + furi_hal_rfid_pins_field(); + + // configure timer + furi_hal_rfid_field_tim_setup(); + + // configure DMA "TIM_COUNTER_CNT -> counter" + LL_DMA_SetMemoryAddress(RFID_DMA_CH1_DEF, (uint32_t) & (furi_hal_rfid->field.counter)); + LL_DMA_SetPeriphAddress( + RFID_DMA_CH1_DEF, (uint32_t) & (FURI_HAL_RFID_FIELD_COUNTER_TIMER->CNT)); + LL_DMA_ConfigTransfer( + RFID_DMA_CH1_DEF, + LL_DMA_DIRECTION_PERIPH_TO_MEMORY | LL_DMA_MODE_CIRCULAR | LL_DMA_PERIPH_NOINCREMENT | + LL_DMA_MEMORY_NOINCREMENT | LL_DMA_PDATAALIGN_WORD | LL_DMA_MDATAALIGN_WORD | + LL_DMA_PRIORITY_MEDIUM); + LL_DMA_SetDataLength(RFID_DMA_CH1_DEF, 1); + LL_DMA_SetPeriphRequest(RFID_DMA_CH1_DEF, FURI_HAL_RFID_FIELD_DMAMUX_DMA); + LL_DMA_EnableChannel(RFID_DMA_CH1_DEF); + + // configure DMA "mem -> TIM_COUNTER_CNT" + LL_DMA_SetMemoryAddress( + RFID_DMA_CH2_DEF, (uint32_t) & (furi_hal_rfid->field.set_tim_counter_cnt)); + LL_DMA_SetPeriphAddress( + RFID_DMA_CH2_DEF, (uint32_t) & (FURI_HAL_RFID_FIELD_COUNTER_TIMER->CNT)); + LL_DMA_ConfigTransfer( + RFID_DMA_CH2_DEF, + LL_DMA_DIRECTION_MEMORY_TO_PERIPH | LL_DMA_MODE_CIRCULAR | LL_DMA_PERIPH_NOINCREMENT | + LL_DMA_MEMORY_NOINCREMENT | LL_DMA_PDATAALIGN_WORD | LL_DMA_MDATAALIGN_WORD | + LL_DMA_PRIORITY_LOW); + LL_DMA_SetDataLength(RFID_DMA_CH2_DEF, 1); + LL_DMA_SetPeriphRequest(RFID_DMA_CH2_DEF, FURI_HAL_RFID_FIELD_DMAMUX_DMA); + LL_DMA_EnableChannel(RFID_DMA_CH2_DEF); + + // start tim counter + LL_TIM_EnableAllOutputs(FURI_HAL_RFID_FIELD_COUNTER_TIMER); + + LL_TIM_SetCounter(FURI_HAL_RFID_FIELD_COUNTER_TIMER, 0); + LL_TIM_EnableCounter(FURI_HAL_RFID_FIELD_COUNTER_TIMER); + + // start tim timeout + LL_TIM_SetCounter(FURI_HAL_RFID_FIELD_TIMEOUT_TIMER, 0); + LL_TIM_EnableCounter(FURI_HAL_RFID_FIELD_TIMEOUT_TIMER); + LL_TIM_EnableIT_UPDATE(FURI_HAL_RFID_FIELD_TIMEOUT_TIMER); +} + +void furi_hal_rfid_field_detect_stop(void) { + LL_TIM_DisableCounter(FURI_HAL_RFID_FIELD_COUNTER_TIMER); + LL_TIM_DisableAllOutputs(FURI_HAL_RFID_FIELD_COUNTER_TIMER); + + LL_TIM_DisableCounter(FURI_HAL_RFID_FIELD_TIMEOUT_TIMER); + + FURI_CRITICAL_ENTER(); + + LL_DMA_DeInit(RFID_DMA_CH1_DEF); + LL_DMA_DeInit(RFID_DMA_CH2_DEF); + + furi_hal_bus_disable(FURI_HAL_RFID_FIELD_COUNTER_TIMER_BUS); + furi_hal_bus_disable(FURI_HAL_RFID_FIELD_TIMEOUT_TIMER_BUS); + + furi_hal_rfid_pins_reset(); + + FURI_CRITICAL_EXIT(); +} + +bool furi_hal_rfid_field_is_present(uint32_t* frequency) { + *frequency = furi_hal_rfid->field.counter * 10; + return ( + (*frequency >= FURI_HAL_RFID_FIELD_FREQUENCY_MIN) && + (*frequency <= FURI_HAL_RFID_FIELD_FREQUENCY_MAX)); +} \ No newline at end of file diff --git a/firmware/targets/f7/furi_hal/furi_hal_rfid.h b/firmware/targets/f7/furi_hal/furi_hal_rfid.h index 78d9b665870..7087ba991fa 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_rfid.h +++ b/firmware/targets/f7/furi_hal/furi_hal_rfid.h @@ -87,6 +87,20 @@ typedef void (*FuriHalRfidCompCallback)(bool level, void* context); /** Set comparator callback */ void furi_hal_rfid_comp_set_callback(FuriHalRfidCompCallback callback, void* context); +/** Start/Enable Field Presence detect */ +void furi_hal_rfid_field_detect_start(); + +/** Stop/Disable Field Presence detect */ +void furi_hal_rfid_field_detect_stop(); + +/** Check Field Presence + * + * @param[out] frequency pointer to frequency value to be set if filed detected + * + * @return true if field is present, false if not + */ +bool furi_hal_rfid_field_is_present(uint32_t* frequency); + #ifdef __cplusplus } #endif From ee96e347673814a828dfe0901a7d0af360fdc20a Mon Sep 17 00:00:00 2001 From: MMX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 28 Jun 2023 12:25:07 +0300 Subject: [PATCH 624/824] Fix furi_hal_bus related crashes in plugins (#2799) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix furi_hal_bus issues in plugins * Rework pwm is running check * ApiSymbols: update and sync targets Co-authored-by: あく --- .../helpers/avr_isp_worker_rw.c | 19 ++++++++++--- .../scenes/signal_gen_scene_pwm.c | 27 ++++++++++++++++--- firmware/targets/f18/api_symbols.csv | 3 ++- firmware/targets/f7/api_symbols.csv | 3 ++- firmware/targets/f7/furi_hal/furi_hal_pwm.c | 9 +++++++ firmware/targets/f7/furi_hal/furi_hal_pwm.h | 8 ++++++ 6 files changed, 59 insertions(+), 10 deletions(-) diff --git a/applications/external/avr_isp_programmer/helpers/avr_isp_worker_rw.c b/applications/external/avr_isp_programmer/helpers/avr_isp_worker_rw.c index 0ee5cefa1de..b4c12cbc38a 100644 --- a/applications/external/avr_isp_programmer/helpers/avr_isp_worker_rw.c +++ b/applications/external/avr_isp_programmer/helpers/avr_isp_worker_rw.c @@ -60,7 +60,9 @@ static int32_t avr_isp_worker_rw_thread(void* context) { AvrIspWorkerRW* instance = context; /* start PWM on &gpio_ext_pa4 */ - furi_hal_pwm_start(FuriHalPwmOutputIdLptim2PA4, 4000000, 50); + if(!furi_hal_pwm_is_running(FuriHalPwmOutputIdLptim2PA4)) { + furi_hal_pwm_start(FuriHalPwmOutputIdLptim2PA4, 4000000, 50); + } FURI_LOG_D(TAG, "Start"); @@ -122,7 +124,9 @@ static int32_t avr_isp_worker_rw_thread(void* context) { } FURI_LOG_D(TAG, "Stop"); - furi_hal_pwm_stop(FuriHalPwmOutputIdLptim2PA4); + if(furi_hal_pwm_is_running(FuriHalPwmOutputIdLptim2PA4)) { + furi_hal_pwm_stop(FuriHalPwmOutputIdLptim2PA4); + } return 0; } @@ -136,7 +140,12 @@ bool avr_isp_worker_rw_detect_chip(AvrIspWorkerRW* instance) { instance->chip_arr_ind = avr_isp_chip_arr_size + 1; /* start PWM on &gpio_ext_pa4 */ - furi_hal_pwm_start(FuriHalPwmOutputIdLptim2PA4, 4000000, 50); + bool was_pwm_enabled = false; + if(!furi_hal_pwm_is_running(FuriHalPwmOutputIdLptim2PA4)) { + furi_hal_pwm_start(FuriHalPwmOutputIdLptim2PA4, 4000000, 50); + } else { + was_pwm_enabled = true; + } do { if(!avr_isp_auto_set_spi_speed_start_pmode(instance->avr_isp)) { @@ -200,7 +209,9 @@ bool avr_isp_worker_rw_detect_chip(AvrIspWorkerRW* instance) { } while(0); - furi_hal_pwm_stop(FuriHalPwmOutputIdLptim2PA4); + if(furi_hal_pwm_is_running(FuriHalPwmOutputIdLptim2PA4) && !was_pwm_enabled) { + furi_hal_pwm_stop(FuriHalPwmOutputIdLptim2PA4); + } if(instance->callback) { if(instance->chip_arr_ind > avr_isp_chip_arr_size) { diff --git a/applications/external/signal_generator/scenes/signal_gen_scene_pwm.c b/applications/external/signal_generator/scenes/signal_gen_scene_pwm.c index 7ac3fadda4c..1cadb3a1a47 100644 --- a/applications/external/signal_generator/scenes/signal_gen_scene_pwm.c +++ b/applications/external/signal_generator/scenes/signal_gen_scene_pwm.c @@ -33,7 +33,13 @@ void signal_gen_scene_pwm_on_enter(void* context) { signal_gen_pwm_set_callback(app->pwm_view, signal_gen_pwm_callback, app); signal_gen_pwm_set_params(app->pwm_view, 0, DEFAULT_FREQ, DEFAULT_DUTY); - furi_hal_pwm_start(pwm_ch_id[0], DEFAULT_FREQ, DEFAULT_DUTY); + + if(!furi_hal_pwm_is_running(pwm_ch_id[0])) { + furi_hal_pwm_start(pwm_ch_id[0], DEFAULT_FREQ, DEFAULT_DUTY); + } else { + furi_hal_pwm_stop(pwm_ch_id[0]); + furi_hal_pwm_start(pwm_ch_id[0], DEFAULT_FREQ, DEFAULT_DUTY); + } } bool signal_gen_scene_pwm_on_event(void* context, SceneManagerEvent event) { @@ -46,8 +52,18 @@ bool signal_gen_scene_pwm_on_event(void* context, SceneManagerEvent event) { furi_hal_pwm_set_params(app->pwm_ch, app->pwm_freq, app->pwm_duty); } else if(event.event == SignalGenPwmEventChannelChange) { consumed = true; - furi_hal_pwm_stop(app->pwm_ch_prev); - furi_hal_pwm_start(app->pwm_ch, app->pwm_freq, app->pwm_duty); + // Stop previous channel PWM + if(furi_hal_pwm_is_running(app->pwm_ch_prev)) { + furi_hal_pwm_stop(app->pwm_ch_prev); + } + + // Start PWM and restart if it was starter already + if(furi_hal_pwm_is_running(app->pwm_ch)) { + furi_hal_pwm_stop(app->pwm_ch); + furi_hal_pwm_start(app->pwm_ch, app->pwm_freq, app->pwm_duty); + } else { + furi_hal_pwm_start(app->pwm_ch, app->pwm_freq, app->pwm_duty); + } } } return consumed; @@ -56,5 +72,8 @@ bool signal_gen_scene_pwm_on_event(void* context, SceneManagerEvent event) { void signal_gen_scene_pwm_on_exit(void* context) { SignalGenApp* app = context; variable_item_list_reset(app->var_item_list); - furi_hal_pwm_stop(app->pwm_ch); + + if(furi_hal_pwm_is_running(app->pwm_ch)) { + furi_hal_pwm_stop(app->pwm_ch); + } } diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index 0b4e2d6d61d..2e176a5b5cd 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,31.1,, +Version,+,31.2,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -1061,6 +1061,7 @@ Function,+,furi_hal_power_sleep,void, Function,+,furi_hal_power_sleep_available,_Bool, Function,+,furi_hal_power_suppress_charge_enter,void, Function,+,furi_hal_power_suppress_charge_exit,void, +Function,+,furi_hal_pwm_is_running,_Bool,FuriHalPwmOutputId Function,+,furi_hal_pwm_set_params,void,"FuriHalPwmOutputId, uint32_t, uint8_t" Function,+,furi_hal_pwm_start,void,"FuriHalPwmOutputId, uint32_t, uint8_t" Function,+,furi_hal_pwm_stop,void,FuriHalPwmOutputId diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 5ba66407ed0..ac2b11f3801 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,31.1,, +Version,+,31.2,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -1286,6 +1286,7 @@ Function,+,furi_hal_power_sleep,void, Function,+,furi_hal_power_sleep_available,_Bool, Function,+,furi_hal_power_suppress_charge_enter,void, Function,+,furi_hal_power_suppress_charge_exit,void, +Function,+,furi_hal_pwm_is_running,_Bool,FuriHalPwmOutputId Function,+,furi_hal_pwm_set_params,void,"FuriHalPwmOutputId, uint32_t, uint8_t" Function,+,furi_hal_pwm_start,void,"FuriHalPwmOutputId, uint32_t, uint8_t" Function,+,furi_hal_pwm_stop,void,FuriHalPwmOutputId diff --git a/firmware/targets/f7/furi_hal/furi_hal_pwm.c b/firmware/targets/f7/furi_hal/furi_hal_pwm.c index 7e985cbb11b..879460e6bd5 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_pwm.c +++ b/firmware/targets/f7/furi_hal/furi_hal_pwm.c @@ -82,6 +82,15 @@ void furi_hal_pwm_stop(FuriHalPwmOutputId channel) { } } +bool furi_hal_pwm_is_running(FuriHalPwmOutputId channel) { + if(channel == FuriHalPwmOutputIdTim1PA7) { + return furi_hal_bus_is_enabled(FuriHalBusTIM1); + } else if(channel == FuriHalPwmOutputIdLptim2PA4) { + return furi_hal_bus_is_enabled(FuriHalBusLPTIM2); + } + return false; +} + void furi_hal_pwm_set_params(FuriHalPwmOutputId channel, uint32_t freq, uint8_t duty) { furi_assert(freq > 0); uint32_t freq_div = 64000000LU / freq; diff --git a/firmware/targets/f7/furi_hal/furi_hal_pwm.h b/firmware/targets/f7/furi_hal/furi_hal_pwm.h index a8682c5fbb1..16acca05efe 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_pwm.h +++ b/firmware/targets/f7/furi_hal/furi_hal_pwm.h @@ -9,6 +9,7 @@ extern "C" { #endif #include +#include typedef enum { FuriHalPwmOutputIdTim1PA7, @@ -37,6 +38,13 @@ void furi_hal_pwm_stop(FuriHalPwmOutputId channel); */ void furi_hal_pwm_set_params(FuriHalPwmOutputId channel, uint32_t freq, uint8_t duty); +/** Is PWM channel running? + * + * @param[in] channel PWM channel (FuriHalPwmOutputId) + * @return bool - true if running +*/ +bool furi_hal_pwm_is_running(FuriHalPwmOutputId channel); + #ifdef __cplusplus } #endif From 6f1c46e11de06fb172b2212f606cb0863a54f64f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Legrelle?= Date: Wed, 28 Jun 2023 11:36:40 +0200 Subject: [PATCH 625/824] Fix fr-FR-mac keylayout (#2809) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- .../badusb/assets/layouts/fr-FR-mac.kl | Bin 256 -> 256 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/assets/resources/badusb/assets/layouts/fr-FR-mac.kl b/assets/resources/badusb/assets/layouts/fr-FR-mac.kl index 0906936547cd3c8accd9632bac82cb4193a24c25..2887aae0fda061fe85930922db12ab03126a5002 100644 GIT binary patch delta 43 ycmZo*YG9h+#+WkE-H_9i!Ggh*Nq{M3Vxt)=n>m{<+r&NAoGkoox-5b$3=9C%s0beb delta 43 ycmZo*YG9h+#%Max-H Date: Wed, 28 Jun 2023 14:46:42 +0300 Subject: [PATCH 626/824] Fix roll-over in file browser and archive (#2811) --- .../main/archive/views/archive_browser_view.c | 19 +++++++++++-------- .../services/gui/modules/file_browser.c | 17 ++++++++++------- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/applications/main/archive/views/archive_browser_view.c b/applications/main/archive/views/archive_browser_view.c index 7e2f84fc228..3c2f1321540 100644 --- a/applications/main/archive/views/archive_browser_view.c +++ b/applications/main/archive/views/archive_browser_view.c @@ -345,11 +345,13 @@ static bool archive_view_input(InputEvent* event, void* context) { if(event->key == InputKeyUp) { if(model->item_idx < scroll_speed) { - scroll_speed = model->item_idx; + model->button_held_for_ticks = 0; + model->item_idx = model->item_cnt - 1; + } else { + model->item_idx = + ((model->item_idx - scroll_speed) + model->item_cnt) % + model->item_cnt; } - - model->item_idx = - ((model->item_idx - scroll_speed) + model->item_cnt) % model->item_cnt; if(is_file_list_load_required(model)) { model->list_loading = true; browser->callback(ArchiveBrowserEventLoadPrevItems, browser->context); @@ -361,11 +363,12 @@ static bool archive_view_input(InputEvent* event, void* context) { model->button_held_for_ticks += 1; } else if(event->key == InputKeyDown) { int32_t count = model->item_cnt; - if(model->item_idx >= (count - scroll_speed)) { - scroll_speed = model->item_cnt - model->item_idx - 1; + if(model->item_idx + scroll_speed >= count) { + model->button_held_for_ticks = 0; + model->item_idx = 0; + } else { + model->item_idx = (model->item_idx + scroll_speed) % model->item_cnt; } - - model->item_idx = (model->item_idx + scroll_speed) % model->item_cnt; if(is_file_list_load_required(model)) { model->list_loading = true; browser->callback(ArchiveBrowserEventLoadNextItems, browser->context); diff --git a/applications/services/gui/modules/file_browser.c b/applications/services/gui/modules/file_browser.c index b2a2c3c2389..c764a1cf7e3 100644 --- a/applications/services/gui/modules/file_browser.c +++ b/applications/services/gui/modules/file_browser.c @@ -602,11 +602,13 @@ static bool file_browser_view_input_callback(InputEvent* event, void* context) { if(event->key == InputKeyUp) { if(model->item_idx < scroll_speed) { - scroll_speed = model->item_idx; + model->button_held_for_ticks = 0; + model->item_idx = model->item_cnt - 1; + } else { + model->item_idx = + ((model->item_idx - scroll_speed) + model->item_cnt) % + model->item_cnt; } - - model->item_idx = - ((model->item_idx - scroll_speed) + model->item_cnt) % model->item_cnt; if(browser_is_list_load_required(model)) { model->list_loading = true; int32_t load_offset = CLAMP( @@ -622,10 +624,11 @@ static bool file_browser_view_input_callback(InputEvent* event, void* context) { } else if(event->key == InputKeyDown) { int32_t count = model->item_cnt; if(model->item_idx + scroll_speed >= count) { - scroll_speed = count - model->item_idx - 1; + model->button_held_for_ticks = 0; + model->item_idx = 0; + } else { + model->item_idx = (model->item_idx + scroll_speed) % model->item_cnt; } - - model->item_idx = (model->item_idx + scroll_speed) % model->item_cnt; if(browser_is_list_load_required(model)) { model->list_loading = true; int32_t load_offset = CLAMP( From a595231d2554c8dbdc37ea0c096e20f4cbae9361 Mon Sep 17 00:00:00 2001 From: minchogaydarov <134236905+minchogaydarov@users.noreply.github.com> Date: Wed, 28 Jun 2023 17:54:42 +0200 Subject: [PATCH 627/824] Add Mitsubishi MSZ-AP25VGK universal ac remote (#2800) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- assets/resources/infrared/assets/ac.ir | 37 ++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/assets/resources/infrared/assets/ac.ir b/assets/resources/infrared/assets/ac.ir index 142c49243c3..c7b1aaf7e87 100644 --- a/assets/resources/infrared/assets/ac.ir +++ b/assets/resources/infrared/assets/ac.ir @@ -470,3 +470,40 @@ type: raw frequency: 38000 duty_cycle: 0.330000 data: 4467 4390 571 1583 572 505 595 1560 572 1583 572 505 572 505 596 1559 596 482 596 481 597 1559 626 451 655 422 625 1529 596 1559 596 481 572 1582 573 1583 571 505 572 1583 595 1560 594 1561 592 1562 594 1561 593 1562 593 484 593 1563 592 485 592 485 592 485 592 485 593 484 593 485 592 485 592 1562 593 485 592 1563 592 1562 593 1562 594 483 593 485 593 1562 592 485 593 1561 593 484 593 484 593 484 593 1562 593 1562 592 5163 4462 4370 592 1563 593 484 592 1563 592 1563 592 485 592 485 593 1562 593 484 593 485 592 1562 593 484 593 485 592 1562 593 1562 593 485 592 1563 592 1563 592 485 592 1563 592 1562 593 1562 593 1563 592 1563 592 1562 592 485 593 1562 592 485 592 485 592 485 592 485 592 485 592 485 592 485 592 1563 592 485 592 1563 591 1563 592 1563 593 485 592 485 592 1563 592 485 592 1563 591 485 593 485 592 485 592 1563 592 1563 591 +# +# Model: Mitsubishi MSZ-AP25VGK +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3531 1667 500 1225 499 1225 499 376 499 377 498 377 498 1224 500 377 498 377 498 1224 500 1225 499 377 527 1195 557 318 556 318 555 1167 530 1194 529 374 499 1224 499 1225 497 377 497 378 497 1228 496 379 496 380 495 1229 495 380 495 380 495 380 495 380 495 380 495 380 495 380 495 380 495 380 495 380 495 380 496 380 495 380 495 380 495 380 495 9028 3526 1672 495 1229 495 1229 495 380 495 380 495 380 495 1230 494 380 495 380 495 1229 495 1229 495 380 495 1229 495 380 495 380 495 1229 495 1229 495 380 495 1229 495 1229 495 380 495 380 495 1229 495 380 495 380 495 1229 495 380 495 381 494 381 494 380 495 380 495 380 495 380 495 381 494 380 495 381 494 381 494 381 494 381 494 381 494 380 495 381 494 381 494 381 494 381 494 381 494 381 494 381 494 381 494 381 494 381 494 381 494 1230 494 1230 494 381 494 381 494 381 494 381 494 381 494 381 494 1230 494 381 494 381 494 381 494 381 494 381 494 1230 494 1230 494 381 494 1230 494 1230 494 381 494 381 494 381 494 381 494 381 494 381 494 381 494 381 494 1230 494 381 494 1230 494 381 494 381 494 1230 494 381 494 1230 494 1230 494 381 494 381 494 381 494 381 494 381 494 381 494 381 494 381 494 381 494 381 494 381 494 381 494 382 493 382 493 382 493 382 493 382 493 382 493 382 493 382 493 382 493 382 493 382 493 382 493 382 493 1231 493 382 493 382 493 382 493 382 493 382 493 382 493 382 493 382 493 382 493 382 493 382 493 382 493 382 493 382 493 382 493 382 493 382 493 382 493 382 493 382 493 382 493 382 493 382 493 1231 493 382 493 1231 493 382 493 1231 493 382 493 383 492 382 493 +# +name: Dh +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3561 1666 500 1195 529 1193 531 374 501 375 500 375 500 1196 529 374 501 375 500 1223 501 1223 501 375 529 1194 558 318 557 318 555 1168 530 1193 530 345 529 1194 529 1196 527 348 526 350 525 1200 524 352 522 353 522 1228 496 379 496 379 496 379 496 379 496 379 496 379 497 379 496 379 496 379 496 379 496 379 496 379 496 379 496 379 496 379 496 9028 3529 1670 496 1228 496 1228 496 379 496 379 496 379 496 1228 496 379 496 379 496 1228 496 1228 496 379 496 1228 496 379 496 379 496 1228 496 1228 496 379 496 1228 496 1228 496 379 496 379 496 1228 496 379 496 379 496 1228 496 379 496 379 496 379 496 379 496 379 496 379 496 379 496 379 496 379 496 379 496 379 496 380 495 379 496 379 496 379 496 379 496 380 495 379 496 380 495 379 496 1229 495 380 495 380 495 379 496 380 495 380 495 380 495 1229 495 380 495 380 495 380 495 380 495 380 495 380 495 1229 495 380 495 380 495 380 495 380 495 380 495 1229 495 380 495 380 495 1229 495 1229 495 380 495 380 495 380 495 380 495 380 496 380 495 380 495 380 495 1229 495 380 495 1229 495 380 495 380 495 1229 495 380 495 1229 495 1229 495 380 495 380 495 380 495 380 495 380 495 380 495 380 495 380 495 380 495 380 495 380 495 380 495 380 495 380 495 380 495 380 495 380 495 380 495 380 495 380 495 381 494 380 495 380 495 380 495 380 495 1230 494 380 495 381 494 381 494 380 495 380 495 380 495 380 495 381 494 381 495 380 495 381 494 381 495 380 495 381 494 381 495 380 495 381 494 381 494 381 494 381 494 381 494 381 494 381 494 1230 494 381 494 381 494 1230 494 381 494 1230 494 381 494 381 494 +# +name: Cool_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3534 1637 530 1192 533 1195 529 375 500 375 500 375 500 1195 530 375 501 375 500 1224 500 1224 501 376 528 1195 557 319 556 318 555 1168 530 1193 530 345 530 1194 529 1196 527 348 526 350 525 1201 523 353 522 378 497 1228 496 379 497 379 496 379 497 379 496 379 497 379 497 379 497 379 496 379 496 379 496 379 496 379 497 379 497 379 496 379 496 9030 3530 1671 496 1229 496 1229 495 379 496 379 496 379 497 1229 496 379 496 379 497 1229 496 1229 495 380 496 1229 495 379 497 379 496 1229 496 1229 495 379 497 1229 495 1228 497 379 496 380 496 1229 496 379 497 379 496 1229 496 379 496 380 496 380 495 380 496 379 496 380 496 380 495 380 496 380 496 380 496 379 496 380 496 380 495 380 496 380 495 380 496 380 495 380 496 380 495 380 495 1229 496 380 495 380 495 380 495 380 496 380 495 1229 496 1229 496 380 495 380 496 380 495 380 496 380 496 380 495 380 495 380 496 380 496 380 495 380 496 380 495 1229 495 1230 495 380 495 1229 496 1229 496 380 495 381 495 380 495 380 495 380 496 380 496 380 495 380 496 1229 496 380 495 1230 495 380 495 380 495 1230 495 380 495 1230 495 1230 495 380 495 380 496 380 495 380 495 380 495 380 496 380 496 380 495 380 496 380 495 380 495 380 496 380 495 381 495 380 495 380 495 380 495 381 495 380 495 381 495 380 495 381 494 381 495 381 495 380 495 1230 495 381 495 380 495 381 494 381 495 381 494 381 495 381 494 381 495 381 495 381 494 381 495 381 494 381 495 381 495 381 494 381 495 381 494 381 494 381 495 381 494 381 494 381 494 381 495 1230 495 381 495 1230 495 1230 495 381 494 1230 494 381 495 381 494 +# +name: Cool_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3534 1667 500 1224 501 1224 501 376 500 375 500 376 500 1224 501 376 500 376 499 1224 501 1225 500 376 500 1225 556 320 556 318 555 1167 530 1194 530 345 530 1195 529 1196 528 348 527 377 498 1227 498 378 497 379 496 1229 496 379 496 379 497 379 497 379 497 379 497 380 496 379 497 379 496 379 497 380 496 380 496 380 496 380 496 379 497 379 497 9033 3530 1672 496 1229 496 1229 496 380 496 380 496 380 496 1229 496 380 496 380 496 1229 496 1229 496 380 496 1229 496 380 496 380 496 1229 496 1229 496 380 496 1230 495 1229 496 380 495 380 496 1229 496 380 496 380 496 1229 496 380 496 380 496 380 496 380 496 380 496 380 496 380 496 380 496 380 496 380 496 380 496 380 496 380 495 380 496 380 496 380 496 380 496 380 496 380 496 380 496 1230 495 380 496 380 495 380 496 381 495 380 495 1230 495 1230 495 380 496 380 496 380 496 1230 495 1230 495 1230 495 380 496 380 496 380 496 380 496 380 496 381 495 1230 495 1230 495 381 495 1230 495 1230 495 380 495 381 495 381 495 381 495 381 495 381 495 380 496 381 495 1230 495 381 495 1230 495 381 495 380 496 1230 495 381 495 1230 495 1230 495 381 495 381 495 381 495 381 495 381 495 381 495 381 495 380 496 381 495 381 495 381 495 381 495 381 495 381 495 381 495 381 495 381 495 381 495 381 495 381 495 381 495 381 495 381 495 381 495 381 495 1231 494 381 495 381 495 381 495 381 495 381 495 381 494 381 495 381 495 381 495 381 495 381 495 381 495 381 495 381 495 381 495 381 495 381 495 381 495 381 495 381 495 381 495 381 495 381 495 382 494 382 494 1231 494 381 495 1231 494 1231 494 381 495 382 494 +# +name: Heat_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3539 1670 501 1226 502 1226 501 376 501 377 500 376 501 1226 501 376 499 378 500 1226 501 1226 501 377 528 1199 557 320 557 318 529 1197 531 1195 531 346 530 1196 530 1198 528 349 527 352 524 1229 498 379 498 379 498 1230 497 380 497 380 497 380 497 380 497 380 497 380 497 380 497 380 497 380 497 380 497 380 497 380 497 380 497 380 497 380 497 9045 3536 1674 496 1231 497 1231 497 380 497 380 497 380 497 1231 496 380 497 380 497 1231 497 1231 496 380 497 1231 496 380 497 380 497 1231 496 1231 497 380 497 1231 496 1231 496 380 497 380 497 1231 496 381 496 380 497 1231 497 381 496 380 497 380 497 380 497 380 497 381 496 381 496 381 496 380 497 380 497 381 496 381 496 381 496 381 496 380 497 381 496 381 496 381 496 381 496 380 497 1231 496 381 496 381 496 380 497 381 496 381 496 1232 495 381 496 381 496 381 496 381 496 1232 495 1232 495 1232 496 1232 495 381 496 381 496 381 496 381 496 381 496 381 496 381 496 381 496 1232 496 1232 496 381 496 381 496 381 496 381 496 381 496 381 496 381 496 381 496 381 496 1232 496 381 496 1232 495 381 496 1232 495 381 496 1232 495 1232 495 381 496 381 496 382 495 381 496 381 496 381 496 381 496 382 495 381 496 381 496 381 496 381 496 381 496 382 495 381 496 381 496 381 496 381 496 382 495 382 495 382 495 382 495 382 495 382 495 382 495 1232 495 382 495 382 495 382 495 382 495 382 495 382 495 382 495 382 495 382 495 382 495 382 495 382 495 382 495 382 495 382 495 382 495 382 495 382 495 382 495 382 495 382 495 382 495 382 495 1233 495 1233 494 1233 495 382 495 382 495 1233 495 1233 494 382 495 +# +name: Heat_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3539 1637 533 1225 502 1193 534 375 501 376 501 376 500 1226 501 376 501 376 500 1226 500 1227 501 376 529 1197 558 320 557 319 555 1169 531 1195 531 346 529 1196 530 1198 528 349 526 352 524 1229 497 379 497 379 497 1230 497 380 497 380 497 379 498 380 497 380 497 380 497 380 497 380 497 380 497 380 497 380 497 380 497 380 497 380 497 380 497 9042 3535 1674 496 1230 497 1230 497 380 497 380 497 380 497 1230 497 380 497 380 496 1230 497 1230 497 380 497 1230 497 380 497 380 497 1231 496 1230 497 380 497 1231 496 1231 496 380 496 380 497 1231 496 380 497 380 496 1231 496 380 497 380 497 380 497 380 497 380 497 380 497 380 497 380 497 380 497 380 496 380 497 380 497 380 497 380 497 380 496 381 496 380 497 380 497 380 497 381 496 1231 496 381 496 380 497 380 497 381 496 381 496 1231 496 381 496 381 496 380 497 381 495 1231 496 1231 496 1231 496 380 497 381 496 381 496 380 496 381 496 381 496 381 496 381 496 381 496 1231 496 1231 496 381 496 381 496 381 496 381 496 381 496 381 496 381 496 381 496 1231 496 381 496 381 496 1232 495 381 495 1232 495 381 496 1231 496 1231 496 381 496 381 496 381 496 381 496 381 496 381 496 381 496 381 496 381 496 381 496 381 496 381 496 381 496 381 496 381 496 381 496 381 496 381 496 381 496 381 496 381 495 381 496 381 496 381 496 381 496 1232 495 381 496 381 496 381 496 381 496 381 495 381 496 381 495 382 495 381 496 382 495 381 495 382 495 381 495 382 495 382 495 382 495 382 495 382 495 382 495 382 495 382 495 382 495 381 495 1232 495 1232 495 1232 495 1232 495 1232 495 382 495 382 495 382 495 From dcf105994bf18ef18fe2f67d3a0f9b1ba033a3be Mon Sep 17 00:00:00 2001 From: Patrick Kilter Date: Wed, 28 Jun 2023 18:17:56 +0200 Subject: [PATCH 628/824] Added Power Button for an unknown Sharp Model (#2787) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tested by me, i just asked if I can get the remote. Tested with the universal remote before and thought you would like to add the button data Co-authored-by: あく --- assets/resources/infrared/assets/tv.ir | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/assets/resources/infrared/assets/tv.ir b/assets/resources/infrared/assets/tv.ir index b45171cb142..ba8807123c9 100644 --- a/assets/resources/infrared/assets/tv.ir +++ b/assets/resources/infrared/assets/tv.ir @@ -1675,3 +1675,9 @@ type: parsed protocol: NEC address: 04 00 00 00 command: 03 00 00 00 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3462 1592 490 332 513 1200 489 331 514 1201 489 355 490 1201 489 356 512 1178 489 356 512 1178 512 334 487 1202 488 1202 488 357 512 1178 512 334 486 1203 487 1202 488 1203 487 1204 486 383 461 1228 488 357 488 357 487 357 487 1203 486 1204 486 359 485 1205 485 360 485 361 484 360 485 360 485 361 484 361 484 361 484 1206 484 360 484 361 484 361 484 1206 484 361 484 361 484 361 484 1206 484 361 484 1206 484 361 484 71543 3434 1620 486 359 485 1205 485 360 485 1206 484 360 485 1206 484 360 485 1206 484 360 485 1206 484 360 485 1205 485 1206 484 360 485 1206 484 360 485 1206 484 1206 484 1206 484 1206 484 361 484 1206 484 360 485 360 485 361 484 1206 484 1206 484 360 484 1206 484 360 485 361 484 361 484 360 485 361 484 361 484 361 484 1206 484 361 484 361 484 361 484 1206 484 361 484 361 484 361 484 1207 483 361 484 1206 484 361 484 71543 3435 1619 486 358 486 1204 486 359 486 1205 485 360 485 1205 485 360 485 1205 485 360 485 1205 485 360 484 1205 485 1205 485 360 485 1205 485 360 485 1205 485 1205 485 1205 485 1206 484 360 485 1205 485 360 485 360 485 360 485 1205 485 1206 484 360 485 1206 484 360 485 360 485 360 485 360 485 360 485 360 485 360 485 1206 484 360 485 360 485 360 485 1206 484 360 485 360 485 360 485 1205 485 360 485 1206 484 360 485 71542 3436 1619 486 358 487 1204 486 359 485 1205 485 360 485 1205 485 360 485 1205 485 360 485 1205 485 360 485 1205 485 1205 485 360 485 1205 485 360 485 1206 484 1206 484 1206 484 1206 484 360 485 1206 484 360 485 360 485 361 484 1206 484 1206 484 361 484 1206 484 361 484 361 484 361 484 361 484 361 484 361 484 360 485 1206 484 361 484 361 484 361 484 1206 484 361 484 361 484 360 485 1206 484 361 484 1206 484 361 484 71542 3437 1618 487 358 486 1204 486 359 486 1205 485 360 485 1205 485 360 485 1205 485 360 485 1205 485 360 485 1205 485 1206 484 360 485 1205 485 360 485 1206 484 1205 485 1206 484 1205 485 360 485 1205 485 360 485 360 485 360 485 1205 485 1205 485 360 485 1205 485 360 485 360 485 360 485 360 485 360 485 360 485 360 485 1205 485 360 485 360 485 360 485 1205 485 360 485 360 485 360 485 1205 485 360 485 1205 485 360 485 From feebf2cd775ace59db7015412a936aecc0e6bd6d Mon Sep 17 00:00:00 2001 From: AloneLiberty <111039319+AloneLiberty@users.noreply.github.com> Date: Wed, 28 Jun 2023 19:35:25 +0300 Subject: [PATCH 629/824] NFC: Improvements to NFC Magic app (#2760) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ability to write gen1b tags (ignore 0x43) Ability to write gen1 7 byte UID tags Fix detection of non magic cards Co-authored-by: あく --- applications/external/nfc_magic/nfc_magic_i.h | 1 + .../external/nfc_magic/nfc_magic_worker.c | 66 ++++++++++--------- .../scenes/nfc_magic_scene_file_select.c | 2 +- .../scenes/nfc_magic_scene_not_magic.c | 3 +- 4 files changed, 39 insertions(+), 33 deletions(-) diff --git a/applications/external/nfc_magic/nfc_magic_i.h b/applications/external/nfc_magic/nfc_magic_i.h index 4d6b89103d4..88bc5706fa8 100644 --- a/applications/external/nfc_magic/nfc_magic_i.h +++ b/applications/external/nfc_magic/nfc_magic_i.h @@ -46,6 +46,7 @@ enum NfcMagicCustomEvent { struct NfcMagicDevice { MagicType type; uint32_t cuid; + uint8_t uid_len; uint32_t password; }; diff --git a/applications/external/nfc_magic/nfc_magic_worker.c b/applications/external/nfc_magic/nfc_magic_worker.c index 9ee7fd3eecd..eb715fe0dda 100644 --- a/applications/external/nfc_magic/nfc_magic_worker.c +++ b/applications/external/nfc_magic/nfc_magic_worker.c @@ -107,14 +107,7 @@ void nfc_magic_worker_write(NfcMagicWorker* nfc_magic_worker) { } magic_activate(); if(magic_gen1_wupa()) { - if(!magic_gen1_data_access_cmd()) { - FURI_LOG_E( - TAG, "No card response to data access command (not a magic card)"); - nfc_magic_worker->callback( - NfcMagicWorkerEventWrongCard, nfc_magic_worker->context); - done = true; - break; - } + magic_gen1_data_access_cmd(); MfClassicData* mfc_data = &dev_data->mf_classic_data; for(size_t i = 0; i < 64; i++) { @@ -296,6 +289,7 @@ void nfc_magic_worker_write(NfcMagicWorker* nfc_magic_worker) { } void nfc_magic_worker_check(NfcMagicWorker* nfc_magic_worker) { + FuriHalNfcDevData nfc_data = {}; NfcMagicDevice* magic_dev = nfc_magic_worker->magic_dev; bool card_found_notified = false; uint8_t gen4_config[MAGIC_GEN4_CONFIG_LEN]; @@ -310,32 +304,44 @@ void nfc_magic_worker_check(NfcMagicWorker* nfc_magic_worker) { card_found_notified = true; } - furi_hal_nfc_activate_nfca(200, &magic_dev->cuid); - nfc_magic_worker->callback(NfcMagicWorkerEventSuccess, nfc_magic_worker->context); - break; - } - - magic_deactivate(); - furi_delay_ms(300); - magic_activate(); - - furi_hal_nfc_activate_nfca(200, &magic_dev->cuid); - if(magic_gen4_get_cfg(magic_dev->password, gen4_config)) { - magic_dev->type = MagicTypeGen4; - if(!card_found_notified) { - nfc_magic_worker->callback( - NfcMagicWorkerEventCardDetected, nfc_magic_worker->context); - card_found_notified = true; + if(furi_hal_nfc_detect(&nfc_data, 200)) { + magic_dev->cuid = nfc_data.cuid; + magic_dev->uid_len = nfc_data.uid_len; + } else { + // wrong BCC + magic_dev->uid_len = 4; } - nfc_magic_worker->callback(NfcMagicWorkerEventSuccess, nfc_magic_worker->context); break; - } + } else { + magic_deactivate(); + magic_activate(); + if(furi_hal_nfc_detect(&nfc_data, 200)) { + magic_dev->cuid = nfc_data.cuid; + magic_dev->uid_len = nfc_data.uid_len; + if(magic_gen4_get_cfg(magic_dev->password, gen4_config)) { + magic_dev->type = MagicTypeGen4; + if(!card_found_notified) { + nfc_magic_worker->callback( + NfcMagicWorkerEventCardDetected, nfc_magic_worker->context); + card_found_notified = true; + } - if(card_found_notified) { - nfc_magic_worker->callback( - NfcMagicWorkerEventNoCardDetected, nfc_magic_worker->context); - card_found_notified = false; + nfc_magic_worker->callback( + NfcMagicWorkerEventSuccess, nfc_magic_worker->context); + } else { + nfc_magic_worker->callback( + NfcMagicWorkerEventWrongCard, nfc_magic_worker->context); + card_found_notified = true; + } + break; + } else { + if(card_found_notified) { + nfc_magic_worker->callback( + NfcMagicWorkerEventNoCardDetected, nfc_magic_worker->context); + card_found_notified = false; + } + } } magic_deactivate(); diff --git a/applications/external/nfc_magic/scenes/nfc_magic_scene_file_select.c b/applications/external/nfc_magic/scenes/nfc_magic_scene_file_select.c index baa6bcccc2d..04b7024ffbd 100644 --- a/applications/external/nfc_magic/scenes/nfc_magic_scene_file_select.c +++ b/applications/external/nfc_magic/scenes/nfc_magic_scene_file_select.c @@ -8,7 +8,7 @@ static bool nfc_magic_scene_file_select_is_file_suitable(NfcMagic* nfc_magic) { case MagicTypeClassicDirectWrite: case MagicTypeClassicAPDU: if((nfc_dev->dev_data.mf_classic_data.type != MfClassicType1k) || - (nfc_dev->dev_data.nfc_data.uid_len != 4)) { + (nfc_dev->dev_data.nfc_data.uid_len != nfc_magic->dev->uid_len)) { return false; } return true; diff --git a/applications/external/nfc_magic/scenes/nfc_magic_scene_not_magic.c b/applications/external/nfc_magic/scenes/nfc_magic_scene_not_magic.c index b87f7f383e8..b4f579f444d 100644 --- a/applications/external/nfc_magic/scenes/nfc_magic_scene_not_magic.c +++ b/applications/external/nfc_magic/scenes/nfc_magic_scene_not_magic.c @@ -13,11 +13,10 @@ void nfc_magic_scene_not_magic_on_enter(void* context) { notification_message(nfc_magic->notifications, &sequence_error); - // widget_add_icon_element(widget, 73, 17, &I_DolphinCommon_56x48); widget_add_string_element( widget, 3, 4, AlignLeft, AlignTop, FontPrimary, "This is wrong card"); widget_add_string_multiline_element( - widget, 4, 17, AlignLeft, AlignTop, FontSecondary, "Not a magic\ncard"); + widget, 4, 17, AlignLeft, AlignTop, FontSecondary, "Not magic or unsupported\ncard"); widget_add_button_element( widget, GuiButtonTypeLeft, "Retry", nfc_magic_scene_not_magic_widget_callback, nfc_magic); From d1c27b645702081c73756a1cf9817df9e646ed18 Mon Sep 17 00:00:00 2001 From: Dmitry Zinin Date: Wed, 28 Jun 2023 19:49:28 +0300 Subject: [PATCH 630/824] Keynote with vertical layout (#2794) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cherry pick from: * https://github.com/DarkFlippers/unleashed-firmware/pull/428/files * https://github.com/DarkFlippers/unleashed-firmware/pull/524/files Co-authored-by: * MX <10697207+xMasterX@users.noreply.github.com> * gid9798 <30450294+gid9798@users.noreply.github.com> Co-authored-by: MX <10697207+xMasterX@users.noreply.github.com> Co-authored-by: あく --- .../external/hid_app/assets/Space_60x18.png | Bin 0 -> 2871 bytes applications/external/hid_app/hid.c | 12 +++ .../external/hid_app/views/hid_keynote.c | 98 ++++++++++++++++++ .../external/hid_app/views/hid_keynote.h | 2 + 4 files changed, 112 insertions(+) create mode 100644 applications/external/hid_app/assets/Space_60x18.png diff --git a/applications/external/hid_app/assets/Space_60x18.png b/applications/external/hid_app/assets/Space_60x18.png new file mode 100644 index 0000000000000000000000000000000000000000..e29f50ae9220d2f9a9753850dedcc6be0a211e76 GIT binary patch literal 2871 zcmV-73&`||P)004&%004{+008|`004nN004b?008NW002DY000@xb3BE2000Uv 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 VN_%sSbR7Ty002ovPDHLkV1hy!X)pi) literal 0 HcmV?d00001 diff --git a/applications/external/hid_app/hid.c b/applications/external/hid_app/hid.c index ea408c39287..a969a933a12 100644 --- a/applications/external/hid_app/hid.c +++ b/applications/external/hid_app/hid.c @@ -7,6 +7,7 @@ enum HidDebugSubmenuIndex { HidSubmenuIndexKeynote, + HidSubmenuIndexKeynoteVertical, HidSubmenuIndexKeyboard, HidSubmenuIndexMedia, HidSubmenuIndexTikTok, @@ -20,6 +21,11 @@ static void hid_submenu_callback(void* context, uint32_t index) { Hid* app = context; if(index == HidSubmenuIndexKeynote) { app->view_id = HidViewKeynote; + hid_keynote_set_orientation(app->hid_keynote, false); + view_dispatcher_switch_to_view(app->view_dispatcher, HidViewKeynote); + } else if(index == HidSubmenuIndexKeynoteVertical) { + app->view_id = HidViewKeynote; + hid_keynote_set_orientation(app->hid_keynote, true); view_dispatcher_switch_to_view(app->view_dispatcher, HidViewKeynote); } else if(index == HidSubmenuIndexKeyboard) { app->view_id = HidViewKeyboard; @@ -105,6 +111,12 @@ Hid* hid_alloc(HidTransport transport) { app->device_type_submenu = submenu_alloc(); submenu_add_item( app->device_type_submenu, "Keynote", HidSubmenuIndexKeynote, hid_submenu_callback, app); + submenu_add_item( + app->device_type_submenu, + "Keynote Vertical", + HidSubmenuIndexKeynoteVertical, + hid_submenu_callback, + app); submenu_add_item( app->device_type_submenu, "Keyboard", HidSubmenuIndexKeyboard, hid_submenu_callback, app); submenu_add_item( diff --git a/applications/external/hid_app/views/hid_keynote.c b/applications/external/hid_app/views/hid_keynote.c index 5e5eeb79094..543363bf67b 100644 --- a/applications/external/hid_app/views/hid_keynote.c +++ b/applications/external/hid_app/views/hid_keynote.c @@ -111,6 +111,91 @@ static void hid_keynote_draw_callback(Canvas* canvas, void* context) { elements_multiline_text_aligned(canvas, 91, 57, AlignLeft, AlignBottom, "Back"); } +static void hid_keynote_draw_vertical_callback(Canvas* canvas, void* context) { + furi_assert(context); + HidKeynoteModel* model = context; + + // Header + if(model->transport == HidTransportBle) { + if(model->connected) { + canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); + } else { + canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); + } + canvas_set_font(canvas, FontPrimary); + elements_multiline_text_aligned(canvas, 20, 3, AlignLeft, AlignTop, "Keynote"); + } else { + canvas_set_font(canvas, FontPrimary); + elements_multiline_text_aligned(canvas, 12, 3, AlignLeft, AlignTop, "Keynote"); + } + + canvas_draw_icon(canvas, 2, 18, &I_Pin_back_arrow_10x8); + canvas_set_font(canvas, FontSecondary); + elements_multiline_text_aligned(canvas, 15, 19, AlignLeft, AlignTop, "Hold to exit"); + + const uint8_t x_2 = 23; + const uint8_t x_1 = 2; + const uint8_t x_3 = 44; + + const uint8_t y_1 = 44; + const uint8_t y_2 = 65; + + // Up + canvas_draw_icon(canvas, x_2, y_1, &I_Button_18x18); + if(model->up_pressed) { + elements_slightly_rounded_box(canvas, x_2 + 3, y_1 + 2, 13, 13); + canvas_set_color(canvas, ColorWhite); + } + hid_keynote_draw_arrow(canvas, x_2 + 9, y_1 + 6, CanvasDirectionBottomToTop); + canvas_set_color(canvas, ColorBlack); + + // Down + canvas_draw_icon(canvas, x_2, y_2, &I_Button_18x18); + if(model->down_pressed) { + elements_slightly_rounded_box(canvas, x_2 + 3, y_2 + 2, 13, 13); + canvas_set_color(canvas, ColorWhite); + } + hid_keynote_draw_arrow(canvas, x_2 + 9, y_2 + 10, CanvasDirectionTopToBottom); + canvas_set_color(canvas, ColorBlack); + + // Left + canvas_draw_icon(canvas, x_1, y_2, &I_Button_18x18); + if(model->left_pressed) { + elements_slightly_rounded_box(canvas, x_1 + 3, y_2 + 2, 13, 13); + canvas_set_color(canvas, ColorWhite); + } + hid_keynote_draw_arrow(canvas, x_1 + 7, y_2 + 8, CanvasDirectionRightToLeft); + canvas_set_color(canvas, ColorBlack); + + // Right + canvas_draw_icon(canvas, x_3, y_2, &I_Button_18x18); + if(model->right_pressed) { + elements_slightly_rounded_box(canvas, x_3 + 3, y_2 + 2, 13, 13); + canvas_set_color(canvas, ColorWhite); + } + hid_keynote_draw_arrow(canvas, x_3 + 11, y_2 + 8, CanvasDirectionLeftToRight); + canvas_set_color(canvas, ColorBlack); + + // Ok + canvas_draw_icon(canvas, 2, 86, &I_Space_60x18); + if(model->ok_pressed) { + elements_slightly_rounded_box(canvas, 5, 88, 55, 13); + canvas_set_color(canvas, ColorWhite); + } + canvas_draw_icon(canvas, 11, 90, &I_Ok_btn_9x9); + elements_multiline_text_aligned(canvas, 26, 98, AlignLeft, AlignBottom, "Space"); + canvas_set_color(canvas, ColorBlack); + + // Back + canvas_draw_icon(canvas, 2, 107, &I_Space_60x18); + if(model->back_pressed) { + elements_slightly_rounded_box(canvas, 5, 109, 55, 13); + canvas_set_color(canvas, ColorWhite); + } + canvas_draw_icon(canvas, 11, 111, &I_Pin_back_arrow_10x8); + elements_multiline_text_aligned(canvas, 26, 119, AlignLeft, AlignBottom, "Back"); +} + static void hid_keynote_process(HidKeynote* hid_keynote, InputEvent* event) { with_view_model( hid_keynote->view, @@ -212,3 +297,16 @@ void hid_keynote_set_connected_status(HidKeynote* hid_keynote, bool connected) { with_view_model( hid_keynote->view, HidKeynoteModel * model, { model->connected = connected; }, true); } + +void hid_keynote_set_orientation(HidKeynote* hid_keynote, bool vertical) { + furi_assert(hid_keynote); + + if(vertical) { + view_set_draw_callback(hid_keynote->view, hid_keynote_draw_vertical_callback); + view_set_orientation(hid_keynote->view, ViewOrientationVerticalFlip); + + } else { + view_set_draw_callback(hid_keynote->view, hid_keynote_draw_callback); + view_set_orientation(hid_keynote->view, ViewOrientationHorizontal); + } +} diff --git a/applications/external/hid_app/views/hid_keynote.h b/applications/external/hid_app/views/hid_keynote.h index 4d4a0a9b1a0..84bfed4ce41 100644 --- a/applications/external/hid_app/views/hid_keynote.h +++ b/applications/external/hid_app/views/hid_keynote.h @@ -12,3 +12,5 @@ void hid_keynote_free(HidKeynote* hid_keynote); View* hid_keynote_get_view(HidKeynote* hid_keynote); void hid_keynote_set_connected_status(HidKeynote* hid_keynote, bool connected); + +void hid_keynote_set_orientation(HidKeynote* hid_keynote, bool vertical); From c10c45616dc69a4eb704d21b318562c129a37782 Mon Sep 17 00:00:00 2001 From: "g3gg0.de" Date: Wed, 28 Jun 2023 19:44:34 +0200 Subject: [PATCH 631/824] SLIX2 emulation support / practical use for Dymo printers (#2783) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * improve digital_signal for longer packets, also clean up code * added SLIX2 specific features like signature and unknown keys (for issue #2781), added WRITE_PASSWORD handling * fix NfcV AFI selection * when NFCV_CMD_READ_MULTI_BLOCK reads beyond memory end, return the maximum possible block's content * added SLIX2 reading * fix NXP SYSTEMINFO response check size * capture the first received password if none was set before * clear stored data before reading SLIX details renamed slix2_dump functions to slix2_read * display card block size values as decimal Co-authored-by: あく --- .../main/nfc/scenes/nfc_scene_nfc_data_info.c | 161 ++++--- .../nfc/scenes/nfc_scene_nfcv_read_success.c | 6 +- lib/digital_signal/digital_signal.c | 181 +++---- lib/nfc/nfc_device.c | 354 +++++++------- lib/nfc/protocols/nfcv.c | 56 ++- lib/nfc/protocols/nfcv.h | 49 +- lib/nfc/protocols/slix.c | 452 ++++++++++++++++-- lib/nfc/protocols/slix.h | 46 +- 8 files changed, 902 insertions(+), 403 deletions(-) diff --git a/applications/main/nfc/scenes/nfc_scene_nfc_data_info.c b/applications/main/nfc/scenes/nfc_scene_nfc_data_info.c index eb2f939c60f..66a9174df47 100644 --- a/applications/main/nfc/scenes/nfc_scene_nfc_data_info.c +++ b/applications/main/nfc/scenes/nfc_scene_nfc_data_info.c @@ -7,6 +7,83 @@ void nfc_scene_nfc_data_info_widget_callback(GuiButtonType result, InputType typ } } +void nfc_scene_slix_build_string( + FuriString* temp_str, + NfcVData* nfcv_data, + SlixTypeFeatures features, + const char* type) { + furi_string_cat_printf(temp_str, "Type: %s\n", type); + furi_string_cat_printf(temp_str, "Keys:\n"); + if(features & SlixFeatureRead) { + furi_string_cat_printf( + temp_str, + " Read %08llX%s\n", + nfc_util_bytes2num(nfcv_data->sub_data.slix.key_read, 4), + (nfcv_data->sub_data.slix.flags & NfcVSlixDataFlagsHasKeyRead) ? "" : " (unset)"); + } + if(features & SlixFeatureWrite) { + furi_string_cat_printf( + temp_str, + " Write %08llX%s\n", + nfc_util_bytes2num(nfcv_data->sub_data.slix.key_write, 4), + (nfcv_data->sub_data.slix.flags & NfcVSlixDataFlagsHasKeyWrite) ? "" : " (unset)"); + } + if(features & SlixFeaturePrivacy) { + furi_string_cat_printf( + temp_str, + " Privacy %08llX%s\n", + nfc_util_bytes2num(nfcv_data->sub_data.slix.key_privacy, 4), + (nfcv_data->sub_data.slix.flags & NfcVSlixDataFlagsHasKeyPrivacy) ? "" : " (unset)"); + furi_string_cat_printf( + temp_str, + " Privacy mode %s\n", + (nfcv_data->sub_data.slix.flags & NfcVSlixDataFlagsPrivacy) ? "ENABLED" : "DISABLED"); + } + if(features & SlixFeatureDestroy) { + furi_string_cat_printf( + temp_str, + " Destroy %08llX%s\n", + nfc_util_bytes2num(nfcv_data->sub_data.slix.key_destroy, 4), + (nfcv_data->sub_data.slix.flags & NfcVSlixDataFlagsHasKeyDestroy) ? "" : " (unset)"); + } + if(features & SlixFeatureEas) { + furi_string_cat_printf( + temp_str, + " EAS %08llX%s\n", + nfc_util_bytes2num(nfcv_data->sub_data.slix.key_eas, 4), + (nfcv_data->sub_data.slix.flags & NfcVSlixDataFlagsHasKeyEas) ? "" : " (unset)"); + } + if(features & SlixFeatureSignature) { + furi_string_cat_printf( + temp_str, + "Signature %08llX...\n", + nfc_util_bytes2num(nfcv_data->sub_data.slix.signature, 4)); + } + furi_string_cat_printf( + temp_str, + "DSFID: %02X %s\n", + nfcv_data->dsfid, + (nfcv_data->security_status[0] & NfcVLockBitDsfid) ? "(locked)" : ""); + furi_string_cat_printf( + temp_str, + "AFI: %02X %s\n", + nfcv_data->afi, + (nfcv_data->security_status[0] & NfcVLockBitAfi) ? "(locked)" : ""); + furi_string_cat_printf( + temp_str, + "EAS: %s\n", + (nfcv_data->security_status[0] & NfcVLockBitEas) ? "locked" : "not locked"); + + if(features & SlixFeatureProtection) { + furi_string_cat_printf( + temp_str, + "PPL: %s\n", + (nfcv_data->security_status[0] & NfcVLockBitPpl) ? "locked" : "not locked"); + furi_string_cat_printf(temp_str, "Prot.ptr %02X\n", nfcv_data->sub_data.slix.pp_pointer); + furi_string_cat_printf(temp_str, "Prot.con %02X\n", nfcv_data->sub_data.slix.pp_condition); + } +} + void nfc_scene_nfc_data_info_on_enter(void* context) { Nfc* nfc = context; Widget* widget = nfc->widget; @@ -76,95 +153,25 @@ void nfc_scene_nfc_data_info_on_enter(void* context) { } furi_string_cat_printf(temp_str, "\n"); - furi_string_cat_printf( - temp_str, - "DSFID: %02X %s\n", - nfcv_data->dsfid, - (nfcv_data->security_status[0] & NfcVLockBitDsfid) ? "(locked)" : ""); - furi_string_cat_printf( - temp_str, - "AFI: %02X %s\n", - nfcv_data->afi, - (nfcv_data->security_status[0] & NfcVLockBitAfi) ? "(locked)" : ""); - furi_string_cat_printf(temp_str, "IC Ref: %02X\n", nfcv_data->ic_ref); - furi_string_cat_printf(temp_str, "Blocks: %02X\n", nfcv_data->block_num); - furi_string_cat_printf(temp_str, "Blocksize: %02X\n", nfcv_data->block_size); + furi_string_cat_printf(temp_str, "IC Ref: %d\n", nfcv_data->ic_ref); + furi_string_cat_printf(temp_str, "Blocks: %d\n", nfcv_data->block_num); + furi_string_cat_printf(temp_str, "Blocksize: %d\n", nfcv_data->block_size); switch(dev_data->nfcv_data.sub_type) { case NfcVTypePlain: furi_string_cat_printf(temp_str, "Type: Plain\n"); break; case NfcVTypeSlix: - furi_string_cat_printf(temp_str, "Type: SLIX\n"); - furi_string_cat_printf(temp_str, "Keys:\n"); - furi_string_cat_printf( - temp_str, - " EAS %08llX\n", - nfc_util_bytes2num(nfcv_data->sub_data.slix.key_eas, 4)); + nfc_scene_slix_build_string(temp_str, nfcv_data, SlixFeatureSlix, "SLIX"); break; case NfcVTypeSlixS: - furi_string_cat_printf(temp_str, "Type: SLIX-S\n"); - furi_string_cat_printf(temp_str, "Keys:\n"); - furi_string_cat_printf( - temp_str, - " Read %08llX\n", - nfc_util_bytes2num(nfcv_data->sub_data.slix.key_read, 4)); - furi_string_cat_printf( - temp_str, - " Write %08llX\n", - nfc_util_bytes2num(nfcv_data->sub_data.slix.key_write, 4)); - furi_string_cat_printf( - temp_str, - " Privacy %08llX\n", - nfc_util_bytes2num(nfcv_data->sub_data.slix.key_privacy, 4)); - furi_string_cat_printf( - temp_str, - " Destroy %08llX\n", - nfc_util_bytes2num(nfcv_data->sub_data.slix.key_destroy, 4)); - furi_string_cat_printf( - temp_str, - " EAS %08llX\n", - nfc_util_bytes2num(nfcv_data->sub_data.slix.key_eas, 4)); + nfc_scene_slix_build_string(temp_str, nfcv_data, SlixFeatureSlixS, "SLIX-S"); break; case NfcVTypeSlixL: - furi_string_cat_printf(temp_str, "Type: SLIX-L\n"); - furi_string_cat_printf(temp_str, "Keys:\n"); - furi_string_cat_printf( - temp_str, - " Privacy %08llX\n", - nfc_util_bytes2num(nfcv_data->sub_data.slix.key_privacy, 4)); - furi_string_cat_printf( - temp_str, - " Destroy %08llX\n", - nfc_util_bytes2num(nfcv_data->sub_data.slix.key_destroy, 4)); - furi_string_cat_printf( - temp_str, - " EAS %08llX\n", - nfc_util_bytes2num(nfcv_data->sub_data.slix.key_eas, 4)); + nfc_scene_slix_build_string(temp_str, nfcv_data, SlixFeatureSlixL, "SLIX-L"); break; case NfcVTypeSlix2: - furi_string_cat_printf(temp_str, "Type: SLIX2\n"); - furi_string_cat_printf(temp_str, "Keys:\n"); - furi_string_cat_printf( - temp_str, - " Read %08llX\n", - nfc_util_bytes2num(nfcv_data->sub_data.slix.key_read, 4)); - furi_string_cat_printf( - temp_str, - " Write %08llX\n", - nfc_util_bytes2num(nfcv_data->sub_data.slix.key_write, 4)); - furi_string_cat_printf( - temp_str, - " Privacy %08llX\n", - nfc_util_bytes2num(nfcv_data->sub_data.slix.key_privacy, 4)); - furi_string_cat_printf( - temp_str, - " Destroy %08llX\n", - nfc_util_bytes2num(nfcv_data->sub_data.slix.key_destroy, 4)); - furi_string_cat_printf( - temp_str, - " EAS %08llX\n", - nfc_util_bytes2num(nfcv_data->sub_data.slix.key_eas, 4)); + nfc_scene_slix_build_string(temp_str, nfcv_data, SlixFeatureSlix2, "SLIX2"); break; default: furi_string_cat_printf(temp_str, "\e#ISO15693 (unknown)\n"); diff --git a/applications/main/nfc/scenes/nfc_scene_nfcv_read_success.c b/applications/main/nfc/scenes/nfc_scene_nfcv_read_success.c index bdf7692ccb2..04e60611d00 100644 --- a/applications/main/nfc/scenes/nfc_scene_nfcv_read_success.c +++ b/applications/main/nfc/scenes/nfc_scene_nfcv_read_success.c @@ -16,7 +16,6 @@ void nfc_scene_nfcv_read_success_on_enter(void* context) { Nfc* nfc = context; NfcDeviceData* dev_data = &nfc->dev->dev_data; FuriHalNfcDevData* nfc_data = &nfc->dev->dev_data.nfc_data; - NfcVData* nfcv_data = &nfc->dev->dev_data.nfcv_data; // Setup view Widget* widget = nfc->widget; widget_add_button_element( @@ -46,13 +45,12 @@ void nfc_scene_nfcv_read_success_on_enter(void* context) { furi_string_cat_printf(temp_str, "\e#ISO15693 (unknown)\n"); break; } - furi_string_cat_printf(temp_str, "UID:"); + furi_string_cat_printf(temp_str, "UID:\n"); for(size_t i = 0; i < nfc_data->uid_len; i++) { furi_string_cat_printf(temp_str, " %02X", nfc_data->uid[i]); } furi_string_cat_printf(temp_str, "\n"); - furi_string_cat_printf(temp_str, "Blocks: %02X\n", nfcv_data->block_num); - furi_string_cat_printf(temp_str, "Blocksize: %02X\n", nfcv_data->block_size); + furi_string_cat_printf(temp_str, "(see More->Info for details)\n"); widget_add_text_scroll_element(widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); furi_string_free(temp_str); diff --git a/lib/digital_signal/digital_signal.c b/lib/digital_signal/digital_signal.c index 39aa9cbc6e1..25adb878b78 100644 --- a/lib/digital_signal/digital_signal.c +++ b/lib/digital_signal/digital_signal.c @@ -51,8 +51,16 @@ struct DigitalSignalInternals { #define T_TIM 1562 /* 15.625 ns *100 */ #define T_TIM_DIV2 781 /* 15.625 ns / 2 *100 */ +/* end marker in DMA ringbuffer, will get written into timer register at the end */ +#define SEQ_TIMER_MAX 0xFFFFFFFF + +/* time to wait in loops before returning */ +#define SEQ_LOCK_WAIT_MS 10UL +#define SEQ_LOCK_WAIT_TICKS (SEQ_LOCK_WAIT_MS * 1000 * 64) + /* maximum entry count of the sequence dma ring buffer */ -#define SEQUENCE_DMA_RINGBUFFER_SIZE 32 +#define RINGBUFFER_SIZE 128 + /* maximum number of DigitalSignals in a sequence */ #define SEQUENCE_SIGNALS_SIZE 32 /* @@ -252,7 +260,7 @@ static void digital_signal_setup_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, 0xFFFFFFFF); + LL_TIM_SetAutoReload(TIM2, SEQ_TIMER_MAX); LL_TIM_SetCounter(TIM2, 0); } @@ -335,7 +343,7 @@ DigitalSequence* digital_sequence_alloc(uint32_t size, const GpioPin* gpio) { sequence->bake = false; sequence->dma_buffer = malloc(sizeof(struct ReloadBuffer)); - sequence->dma_buffer->size = SEQUENCE_DMA_RINGBUFFER_SIZE; + sequence->dma_buffer->size = RINGBUFFER_SIZE; sequence->dma_buffer->buffer = malloc(sequence->dma_buffer->size * sizeof(uint32_t)); sequence->dma_config_gpio.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH; @@ -454,39 +462,23 @@ static DigitalSignal* digital_sequence_bake(DigitalSequence* sequence) { return ret; } -static void digital_sequence_update_pos(DigitalSequence* sequence) { - struct ReloadBuffer* dma_buffer = sequence->dma_buffer; - - dma_buffer->read_pos = dma_buffer->size - LL_DMA_GetDataLength(DMA1, LL_DMA_CHANNEL_2); -} - -static const uint32_t wait_ms = 10; -static const uint32_t wait_ticks = wait_ms * 1000 * 64; - static void digital_sequence_finish(DigitalSequence* sequence) { struct ReloadBuffer* dma_buffer = sequence->dma_buffer; if(dma_buffer->dma_active) { uint32_t prev_timer = DWT->CYCCNT; - uint32_t end_pos = (dma_buffer->write_pos + 1) % dma_buffer->size; do { - uint32_t last_pos = dma_buffer->read_pos; - - digital_sequence_update_pos(sequence); - - /* we are finished, when the DMA transferred the 0xFFFFFFFF-timer which is the current write_pos */ - if(dma_buffer->read_pos == end_pos) { + /* we are finished, when the DMA transferred the SEQ_TIMER_MAX marker */ + if(TIM2->ARR == SEQ_TIMER_MAX) { break; } - - if(last_pos != dma_buffer->read_pos) { //-V547 - prev_timer = DWT->CYCCNT; - } - if(DWT->CYCCNT - prev_timer > wait_ticks) { + if(DWT->CYCCNT - prev_timer > SEQ_LOCK_WAIT_TICKS) { + dma_buffer->read_pos = + RINGBUFFER_SIZE - LL_DMA_GetDataLength(DMA1, LL_DMA_CHANNEL_2); FURI_LOG_D( TAG, "[SEQ] hung %lu ms in finish (ARR 0x%08lx, read %lu, write %lu)", - wait_ms, + SEQ_LOCK_WAIT_MS, TIM2->ARR, dma_buffer->read_pos, dma_buffer->write_pos); @@ -504,23 +496,30 @@ static void digital_sequence_queue_pulse(DigitalSequence* sequence, uint32_t len if(dma_buffer->dma_active) { uint32_t prev_timer = DWT->CYCCNT; - uint32_t end_pos = (dma_buffer->write_pos + 1) % dma_buffer->size; do { - uint32_t last_pos = dma_buffer->read_pos; - digital_sequence_update_pos(sequence); + dma_buffer->read_pos = RINGBUFFER_SIZE - LL_DMA_GetDataLength(DMA1, LL_DMA_CHANNEL_2); + + uint32_t free = + (RINGBUFFER_SIZE + dma_buffer->read_pos - dma_buffer->write_pos) % RINGBUFFER_SIZE; - if(dma_buffer->read_pos != end_pos) { + if(free > 2) { break; } - if(last_pos != dma_buffer->read_pos) { //-V547 - prev_timer = DWT->CYCCNT; - } - if(DWT->CYCCNT - prev_timer > wait_ticks) { + if(DWT->CYCCNT - prev_timer > SEQ_LOCK_WAIT_TICKS) { FURI_LOG_D( TAG, "[SEQ] hung %lu ms in queue (ARR 0x%08lx, read %lu, write %lu)", - wait_ms, + SEQ_LOCK_WAIT_MS, + TIM2->ARR, + dma_buffer->read_pos, + dma_buffer->write_pos); + break; + } + if(TIM2->ARR == SEQ_TIMER_MAX) { + FURI_LOG_D( + TAG, + "[SEQ] buffer underrun in queue (ARR 0x%08lx, read %lu, write %lu)", TIM2->ARR, dma_buffer->read_pos, dma_buffer->write_pos); @@ -530,8 +529,9 @@ static void digital_sequence_queue_pulse(DigitalSequence* sequence, uint32_t len } dma_buffer->buffer[dma_buffer->write_pos] = length; - dma_buffer->write_pos = (dma_buffer->write_pos + 1) % dma_buffer->size; - dma_buffer->buffer[dma_buffer->write_pos] = 0xFFFFFFFF; + dma_buffer->write_pos++; + dma_buffer->write_pos %= RINGBUFFER_SIZE; + dma_buffer->buffer[dma_buffer->write_pos] = SEQ_TIMER_MAX; } bool digital_sequence_send(DigitalSequence* sequence) { @@ -553,90 +553,97 @@ bool digital_sequence_send(DigitalSequence* sequence) { return true; } - int32_t remainder = 0; - bool traded_first = false; + if(!sequence->sequence_used) { + return false; + } - FURI_CRITICAL_ENTER(); + int32_t remainder = 0; + uint32_t trade_for_next = 0; + uint32_t seq_pos_next = 1; dma_buffer->dma_active = false; - dma_buffer->buffer[0] = 0xFFFFFFFF; + dma_buffer->buffer[0] = SEQ_TIMER_MAX; dma_buffer->read_pos = 0; dma_buffer->write_pos = 0; - for(uint32_t seq_pos = 0; seq_pos < sequence->sequence_used; seq_pos++) { - uint8_t signal_index = sequence->sequence[seq_pos]; - DigitalSignal* sig = sequence->signals[signal_index]; - bool last_signal = ((seq_pos + 1) == sequence->sequence_used); + /* already prepare the current signal pointer */ + DigitalSignal* sig = sequence->signals[sequence->sequence[0]]; + DigitalSignal* sig_next = NULL; + /* re-use the GPIO buffer from the first signal */ + sequence->gpio_buff = sig->internals->gpio_buff; + + FURI_CRITICAL_ENTER(); + + while(sig) { + bool last_signal = (seq_pos_next >= sequence->sequence_used); - /* all signals are prepared and we can re-use the GPIO buffer from the fist signal */ - if(seq_pos == 0) { - sequence->gpio_buff = sig->internals->gpio_buff; + if(!last_signal) { + sig_next = sequence->signals[sequence->sequence[seq_pos_next++]]; } for(uint32_t pulse_pos = 0; pulse_pos < sig->internals->reload_reg_entries; pulse_pos++) { - if(traded_first) { - traded_first = false; - continue; - } - uint32_t pulse_length = 0; - bool last_pulse = ((pulse_pos + 1) == sig->internals->reload_reg_entries); + bool last_pulse = ((pulse_pos + 1) >= sig->internals->reload_reg_entries); + uint32_t pulse_length = sig->reload_reg_buff[pulse_pos] + trade_for_next; - pulse_length = sig->reload_reg_buff[pulse_pos]; + trade_for_next = 0; /* when we are too late more than half a tick, make the first edge temporarily longer */ if(remainder >= T_TIM_DIV2) { remainder -= T_TIM; pulse_length += 1; } - remainder += sig->internals->reload_reg_remainder; - - /* last pulse in that signal and have a next signal? */ - if(last_pulse) { - if((seq_pos + 1) < sequence->sequence_used) { - DigitalSignal* sig_next = sequence->signals[sequence->sequence[seq_pos + 1]]; - /* when a signal ends with the same level as the next signal begins, let the fist signal generate the whole pulse */ - /* beware, we do not want the level after the last edge, but the last level before that edge */ - bool end_level = sig->start_level ^ ((sig->edge_cnt % 2) == 0); + /* last pulse in current signal and have a next signal? */ + if(last_pulse && sig_next) { + /* when a signal ends with the same level as the next signal begins, let the next signal generate the whole pulse. + beware, we do not want the level after the last edge, but the last level before that edge */ + bool end_level = sig->start_level ^ ((sig->edge_cnt % 2) == 0); - /* take from the next, add it to the current if they have the same level */ - if(end_level == sig_next->start_level) { - pulse_length += sig_next->reload_reg_buff[0]; - traded_first = true; - } + /* if they have the same level, pass the duration to the next pulse(s) */ + if(end_level == sig_next->start_level) { + trade_for_next = pulse_length; } } - digital_sequence_queue_pulse(sequence, pulse_length); + /* if it was decided, that the next signal's first pulse shall also handle our "length", then do not queue here */ + if(!trade_for_next) { + digital_sequence_queue_pulse(sequence, pulse_length); - /* start transmission when buffer was filled enough */ - bool start_send = sequence->dma_buffer->write_pos >= (sequence->dma_buffer->size - 4); - - /* or it was the last pulse */ - if(last_pulse && last_signal) { - start_send = true; - } + if(!dma_buffer->dma_active) { + /* start transmission when buffer was filled enough */ + bool start_send = sequence->dma_buffer->write_pos >= (RINGBUFFER_SIZE - 2); - /* start transmission */ - if(start_send && !dma_buffer->dma_active) { - digital_sequence_setup_dma(sequence); - digital_signal_setup_timer(); + /* or it was the last pulse */ + if(last_pulse && last_signal) { + start_send = true; + } - /* if the send time is specified, wait till the core timer passed beyond that time */ - if(sequence->send_time_active) { - sequence->send_time_active = false; - while(sequence->send_time - DWT->CYCCNT < 0x80000000) { + /* start transmission */ + if(start_send) { + digital_sequence_setup_dma(sequence); + digital_signal_setup_timer(); + + /* if the send time is specified, wait till the core timer passed beyond that time */ + if(sequence->send_time_active) { + sequence->send_time_active = false; + while(sequence->send_time - DWT->CYCCNT < 0x80000000) { + } + } + digital_signal_start_timer(); + dma_buffer->dma_active = true; } } - digital_signal_start_timer(); - dma_buffer->dma_active = true; } } + + remainder += sig->internals->reload_reg_remainder; + sig = sig_next; + sig_next = NULL; } /* wait until last dma transaction was finished */ - digital_sequence_finish(sequence); FURI_CRITICAL_EXIT(); + digital_sequence_finish(sequence); return true; } diff --git a/lib/nfc/nfc_device.c b/lib/nfc/nfc_device.c index 952fca254bd..8abf637d7ec 100644 --- a/lib/nfc/nfc_device.c +++ b/lib/nfc/nfc_device.c @@ -657,178 +657,167 @@ bool nfc_device_load_mifare_df_data(FlipperFormat* file, NfcDevice* dev) { return parsed; } -static bool nfc_device_save_slix_data(FlipperFormat* file, NfcDevice* dev) { - bool saved = false; - NfcVSlixData* data = &dev->dev_data.nfcv_data.sub_data.slix; - - do { - if(!flipper_format_write_comment_cstr(file, "SLIX specific data")) break; - if(!flipper_format_write_hex(file, "Password EAS", data->key_eas, sizeof(data->key_eas))) - break; - saved = true; - } while(false); - - return saved; -} - -bool nfc_device_load_slix_data(FlipperFormat* file, NfcDevice* dev) { - bool parsed = false; - NfcVSlixData* data = &dev->dev_data.nfcv_data.sub_data.slix; - memset(data, 0, sizeof(NfcVSlixData)); - - do { - if(!flipper_format_read_hex(file, "Password EAS", data->key_eas, sizeof(data->key_eas))) - break; - - parsed = true; - } while(false); - - return parsed; -} - -static bool nfc_device_save_slix_s_data(FlipperFormat* file, NfcDevice* dev) { - bool saved = false; - NfcVSlixData* data = &dev->dev_data.nfcv_data.sub_data.slix; - - do { - if(!flipper_format_write_comment_cstr(file, "SLIX-S specific data")) break; - if(!flipper_format_write_hex(file, "Password Read", data->key_read, sizeof(data->key_read))) - break; - if(!flipper_format_write_hex( - file, "Password Write", data->key_write, sizeof(data->key_write))) - break; - if(!flipper_format_write_hex( - file, "Password Privacy", data->key_privacy, sizeof(data->key_privacy))) - break; - if(!flipper_format_write_hex( - file, "Password Destroy", data->key_destroy, sizeof(data->key_destroy))) - break; - if(!flipper_format_write_hex(file, "Password EAS", data->key_eas, sizeof(data->key_eas))) - break; - if(!flipper_format_write_bool(file, "Privacy Mode", &data->privacy, 1)) break; - saved = true; - } while(false); - - return saved; -} - -bool nfc_device_load_slix_s_data(FlipperFormat* file, NfcDevice* dev) { - bool parsed = false; - NfcVSlixData* data = &dev->dev_data.nfcv_data.sub_data.slix; - memset(data, 0, sizeof(NfcVSlixData)); - - do { - if(!flipper_format_read_hex(file, "Password Read", data->key_read, sizeof(data->key_read))) - break; - if(!flipper_format_read_hex( - file, "Password Write", data->key_write, sizeof(data->key_write))) - break; - if(!flipper_format_read_hex( - file, "Password Privacy", data->key_privacy, sizeof(data->key_privacy))) - break; - if(!flipper_format_read_hex( - file, "Password Destroy", data->key_destroy, sizeof(data->key_destroy))) - break; - if(!flipper_format_read_hex(file, "Password EAS", data->key_eas, sizeof(data->key_eas))) - break; - if(!flipper_format_read_bool(file, "Privacy Mode", &data->privacy, 1)) break; - - parsed = true; - } while(false); - - return parsed; -} - -static bool nfc_device_save_slix_l_data(FlipperFormat* file, NfcDevice* dev) { +static bool nfc_device_save_slix_data( + FlipperFormat* file, + NfcDevice* dev, + SlixTypeFeatures features, + const char* type) { bool saved = false; NfcVSlixData* data = &dev->dev_data.nfcv_data.sub_data.slix; do { - if(!flipper_format_write_comment_cstr(file, "SLIX-L specific data")) break; - if(!flipper_format_write_hex( - file, "Password Privacy", data->key_privacy, sizeof(data->key_privacy))) - break; - if(!flipper_format_write_hex( - file, "Password Destroy", data->key_destroy, sizeof(data->key_destroy))) - break; - if(!flipper_format_write_hex(file, "Password EAS", data->key_eas, sizeof(data->key_eas))) - break; - if(!flipper_format_write_bool(file, "Privacy Mode", &data->privacy, 1)) break; - saved = true; - } while(false); - - return saved; -} - -bool nfc_device_load_slix_l_data(FlipperFormat* file, NfcDevice* dev) { - bool parsed = false; - NfcVSlixData* data = &dev->dev_data.nfcv_data.sub_data.slix; - memset(data, 0, sizeof(NfcVSlixData)); - - do { - if(!flipper_format_read_hex( - file, "Password Privacy", data->key_privacy, sizeof(data->key_privacy))) - break; - if(!flipper_format_read_hex( - file, "Password Destroy", data->key_destroy, sizeof(data->key_destroy))) - break; - if(!flipper_format_read_hex(file, "Password EAS", data->key_eas, sizeof(data->key_eas))) + char msg[64]; + snprintf(msg, sizeof(msg), "%s specific data", type); + if(!flipper_format_write_comment_cstr(file, msg)) break; + if(!flipper_format_write_comment_cstr( + file, "Passwords are optional. If password is omitted, any password is accepted")) break; - if(!flipper_format_read_bool(file, "Privacy Mode", &data->privacy, 1)) break; - parsed = true; - } while(false); - - return parsed; -} - -static bool nfc_device_save_slix2_data(FlipperFormat* file, NfcDevice* dev) { - bool saved = false; - NfcVSlixData* data = &dev->dev_data.nfcv_data.sub_data.slix; - - do { - if(!flipper_format_write_comment_cstr(file, "SLIX2 specific data")) break; - if(!flipper_format_write_hex(file, "Password Read", data->key_read, sizeof(data->key_read))) - break; - if(!flipper_format_write_hex( - file, "Password Write", data->key_write, sizeof(data->key_write))) - break; - if(!flipper_format_write_hex( - file, "Password Privacy", data->key_privacy, sizeof(data->key_privacy))) - break; - if(!flipper_format_write_hex( - file, "Password Destroy", data->key_destroy, sizeof(data->key_destroy))) - break; - if(!flipper_format_write_hex(file, "Password EAS", data->key_eas, sizeof(data->key_eas))) - break; - if(!flipper_format_write_bool(file, "Privacy Mode", &data->privacy, 1)) break; + if(features & SlixFeatureRead) { + if(data->flags & NfcVSlixDataFlagsHasKeyRead) { + if(!flipper_format_write_hex( + file, "Password Read", data->key_read, sizeof(data->key_read))) + break; + } + } + if(features & SlixFeatureWrite) { + if(data->flags & NfcVSlixDataFlagsHasKeyWrite) { + if(!flipper_format_write_hex( + file, "Password Write", data->key_write, sizeof(data->key_write))) + break; + } + } + if(features & SlixFeaturePrivacy) { + if(data->flags & NfcVSlixDataFlagsHasKeyPrivacy) { + if(!flipper_format_write_hex( + file, "Password Privacy", data->key_privacy, sizeof(data->key_privacy))) + break; + } + } + if(features & SlixFeatureDestroy) { + if(data->flags & NfcVSlixDataFlagsHasKeyDestroy) { + if(!flipper_format_write_hex( + file, "Password Destroy", data->key_destroy, sizeof(data->key_destroy))) + break; + } + } + if(features & SlixFeatureEas) { + if(data->flags & NfcVSlixDataFlagsHasKeyEas) { + if(!flipper_format_write_hex( + file, "Password EAS", data->key_eas, sizeof(data->key_eas))) + break; + } + } + if(features & SlixFeatureSignature) { + if(!flipper_format_write_comment_cstr( + file, + "This is the card's secp128r1 elliptic curve signature. It can not be calculated without knowing NXP's private key.")) + break; + if(!flipper_format_write_hex( + file, "Signature", data->signature, sizeof(data->signature))) + break; + } + if(features & SlixFeaturePrivacy) { + bool privacy = (data->flags & NfcVSlixDataFlagsPrivacy) ? true : false; + if(!flipper_format_write_bool(file, "Privacy Mode", &privacy, 1)) break; + } + if(features & SlixFeatureProtection) { + if(!flipper_format_write_comment_cstr(file, "Protection pointer configuration")) break; + if(!flipper_format_write_hex(file, "Protection pointer", &data->pp_pointer, 1)) break; + if(!flipper_format_write_hex(file, "Protection condition", &data->pp_condition, 1)) + break; + } saved = true; } while(false); return saved; } -bool nfc_device_load_slix2_data(FlipperFormat* file, NfcDevice* dev) { // -V524 +bool nfc_device_load_slix_data(FlipperFormat* file, NfcDevice* dev, SlixTypeFeatures features) { bool parsed = false; NfcVSlixData* data = &dev->dev_data.nfcv_data.sub_data.slix; memset(data, 0, sizeof(NfcVSlixData)); do { - if(!flipper_format_read_hex(file, "Password Read", data->key_read, sizeof(data->key_read))) - break; - if(!flipper_format_read_hex( - file, "Password Write", data->key_write, sizeof(data->key_write))) - break; - if(!flipper_format_read_hex( - file, "Password Privacy", data->key_privacy, sizeof(data->key_privacy))) - break; - if(!flipper_format_read_hex( - file, "Password Destroy", data->key_destroy, sizeof(data->key_destroy))) - break; - if(!flipper_format_read_hex(file, "Password EAS", data->key_eas, sizeof(data->key_eas))) - break; - if(!flipper_format_read_bool(file, "Privacy Mode", &data->privacy, 1)) break; + data->flags = 0; + if(features & SlixFeatureRead) { + if(flipper_format_key_exist(file, "Password Read")) { + if(!flipper_format_read_hex( + file, "Password Read", data->key_read, sizeof(data->key_read))) { + FURI_LOG_D(TAG, "Failed reading Password Read"); + break; + } + data->flags |= NfcVSlixDataFlagsHasKeyRead; + } + } + if(features & SlixFeatureWrite) { + if(flipper_format_key_exist(file, "Password Write")) { + if(!flipper_format_read_hex( + file, "Password Write", data->key_write, sizeof(data->key_write))) { + FURI_LOG_D(TAG, "Failed reading Password Write"); + break; + } + data->flags |= NfcVSlixDataFlagsHasKeyWrite; + } + } + if(features & SlixFeaturePrivacy) { + if(flipper_format_key_exist(file, "Password Privacy")) { + if(!flipper_format_read_hex( + file, "Password Privacy", data->key_privacy, sizeof(data->key_privacy))) { + FURI_LOG_D(TAG, "Failed reading Password Privacy"); + break; + } + data->flags |= NfcVSlixDataFlagsHasKeyPrivacy; + } + } + if(features & SlixFeatureDestroy) { + if(flipper_format_key_exist(file, "Password Destroy")) { + if(!flipper_format_read_hex( + file, "Password Destroy", data->key_destroy, sizeof(data->key_destroy))) { + FURI_LOG_D(TAG, "Failed reading Password Destroy"); + break; + } + data->flags |= NfcVSlixDataFlagsHasKeyDestroy; + } + } + if(features & SlixFeatureEas) { + if(flipper_format_key_exist(file, "Password EAS")) { + if(!flipper_format_read_hex( + file, "Password EAS", data->key_eas, sizeof(data->key_eas))) { + FURI_LOG_D(TAG, "Failed reading Password EAS"); + break; + } + data->flags |= NfcVSlixDataFlagsHasKeyEas; + } + } + if(features & SlixFeatureSignature) { + if(!flipper_format_read_hex( + file, "Signature", data->signature, sizeof(data->signature))) { + FURI_LOG_D(TAG, "Failed reading Signature"); + break; + } + } + if(features & SlixFeaturePrivacy) { + bool privacy; + if(!flipper_format_read_bool(file, "Privacy Mode", &privacy, 1)) { + FURI_LOG_D(TAG, "Failed reading Privacy Mode"); + break; + } + if(privacy) { + data->flags |= NfcVSlixDataFlagsPrivacy; + } + } + if(features & SlixFeatureProtection) { + if(!flipper_format_read_hex(file, "Protection pointer", &(data->pp_pointer), 1)) { + FURI_LOG_D(TAG, "Failed reading Protection pointer"); + break; + } + if(!flipper_format_read_hex(file, "Protection condition", &(data->pp_condition), 1)) { + FURI_LOG_D(TAG, "Failed reading Protection condition"); + break; + } + } parsed = true; } while(false); @@ -859,7 +848,8 @@ static bool nfc_device_save_nfcv_data(FlipperFormat* file, NfcDevice* dev) { file, "Data Content", data->data, data->block_num * data->block_size)) break; if(!flipper_format_write_comment_cstr( - file, "First byte: DSFID (0x01) / AFI (0x02) lock info, others: block lock info")) + file, + "First byte: DSFID (0x01) / AFI (0x02) / EAS (0x04) / PPL (0x08) lock info, others: block lock info")) break; if(!flipper_format_write_hex( file, "Security Status", data->security_status, 1 + data->block_num)) @@ -877,16 +867,16 @@ static bool nfc_device_save_nfcv_data(FlipperFormat* file, NfcDevice* dev) { saved = true; break; case NfcVTypeSlix: - saved = nfc_device_save_slix_data(file, dev); + saved = nfc_device_save_slix_data(file, dev, SlixFeatureSlix, "SLIX"); break; case NfcVTypeSlixS: - saved = nfc_device_save_slix_s_data(file, dev); + saved = nfc_device_save_slix_data(file, dev, SlixFeatureSlixS, "SLIX-S"); break; case NfcVTypeSlixL: - saved = nfc_device_save_slix_l_data(file, dev); + saved = nfc_device_save_slix_data(file, dev, SlixFeatureSlixL, "SLIX-L"); break; case NfcVTypeSlix2: - saved = nfc_device_save_slix2_data(file, dev); + saved = nfc_device_save_slix_data(file, dev, SlixFeatureSlix2, "SLIX2"); break; default: break; @@ -906,23 +896,45 @@ bool nfc_device_load_nfcv_data(FlipperFormat* file, NfcDevice* dev) { uint32_t temp_uint32 = 0; uint8_t temp_value = 0; - if(!flipper_format_read_hex(file, "DSFID", &(data->dsfid), 1)) break; - if(!flipper_format_read_hex(file, "AFI", &(data->afi), 1)) break; - if(!flipper_format_read_hex(file, "IC Reference", &(data->ic_ref), 1)) break; - if(!flipper_format_read_uint32(file, "Block Count", &temp_uint32, 1)) break; + if(!flipper_format_read_hex(file, "DSFID", &(data->dsfid), 1)) { + FURI_LOG_D(TAG, "Failed reading DSFID"); + break; + } + if(!flipper_format_read_hex(file, "AFI", &(data->afi), 1)) { + FURI_LOG_D(TAG, "Failed reading AFI"); + break; + } + if(!flipper_format_read_hex(file, "IC Reference", &(data->ic_ref), 1)) { + FURI_LOG_D(TAG, "Failed reading IC Reference"); + break; + } + if(!flipper_format_read_uint32(file, "Block Count", &temp_uint32, 1)) { + FURI_LOG_D(TAG, "Failed reading Block Count"); + break; + } data->block_num = temp_uint32; - if(!flipper_format_read_hex(file, "Block Size", &(data->block_size), 1)) break; + if(!flipper_format_read_hex(file, "Block Size", &(data->block_size), 1)) { + FURI_LOG_D(TAG, "Failed reading Block Size"); + break; + } if(!flipper_format_read_hex( - file, "Data Content", data->data, data->block_num * data->block_size)) + file, "Data Content", data->data, data->block_num * data->block_size)) { + FURI_LOG_D(TAG, "Failed reading Data Content"); break; + } /* optional, as added later */ if(flipper_format_key_exist(file, "Security Status")) { if(!flipper_format_read_hex( - file, "Security Status", data->security_status, 1 + data->block_num)) + file, "Security Status", data->security_status, 1 + data->block_num)) { + FURI_LOG_D(TAG, "Failed reading Security Status"); break; + } + } + if(!flipper_format_read_hex(file, "Subtype", &temp_value, 1)) { + FURI_LOG_D(TAG, "Failed reading Subtype"); + break; } - if(!flipper_format_read_hex(file, "Subtype", &temp_value, 1)) break; data->sub_type = temp_value; switch(data->sub_type) { @@ -930,16 +942,16 @@ bool nfc_device_load_nfcv_data(FlipperFormat* file, NfcDevice* dev) { parsed = true; break; case NfcVTypeSlix: - parsed = nfc_device_load_slix_data(file, dev); + parsed = nfc_device_load_slix_data(file, dev, SlixFeatureSlix); break; case NfcVTypeSlixS: - parsed = nfc_device_load_slix_s_data(file, dev); + parsed = nfc_device_load_slix_data(file, dev, SlixFeatureSlixS); break; case NfcVTypeSlixL: - parsed = nfc_device_load_slix_l_data(file, dev); + parsed = nfc_device_load_slix_data(file, dev, SlixFeatureSlixL); break; case NfcVTypeSlix2: - parsed = nfc_device_load_slix2_data(file, dev); + parsed = nfc_device_load_slix_data(file, dev, SlixFeatureSlix2); break; default: break; diff --git a/lib/nfc/protocols/nfcv.c b/lib/nfc/protocols/nfcv.c index 3c37153d843..017b06cae02 100644 --- a/lib/nfc/protocols/nfcv.c +++ b/lib/nfc/protocols/nfcv.c @@ -149,12 +149,18 @@ bool nfcv_read_card(NfcVReader* reader, FuriHalNfcDevData* nfc_data, NfcVData* n return false; } + /* clear all know sub type data before reading them */ + memset(&nfcv_data->sub_data, 0x00, sizeof(nfcv_data->sub_data)); + if(slix_check_card_type(nfc_data)) { FURI_LOG_I(TAG, "NXP SLIX detected"); nfcv_data->sub_type = NfcVTypeSlix; } else if(slix2_check_card_type(nfc_data)) { FURI_LOG_I(TAG, "NXP SLIX2 detected"); nfcv_data->sub_type = NfcVTypeSlix2; + if(slix2_read_custom(nfc_data, nfcv_data) != ERR_NONE) { + return false; + } } else if(slix_s_check_card_type(nfc_data)) { FURI_LOG_I(TAG, "NXP SLIX-S detected"); nfcv_data->sub_type = NfcVTypeSlixS; @@ -612,9 +618,34 @@ void nfcv_emu_handle_packet( if(ctx->flags & NFCV_REQ_FLAG_AFI) { uint8_t afi = nfcv_data->frame[ctx->payload_offset]; - if(afi == nfcv_data->afi) { - respond = true; + + uint8_t family = (afi & 0xF0); + uint8_t subfamily = (afi & 0x0F); + + if(family) { + if(subfamily) { + /* selected family and subfamily only */ + if(afi == nfcv_data->afi) { + respond = true; + } + } else { + /* selected family, any subfamily */ + if(family == (nfcv_data->afi & 0xf0)) { + respond = true; + } + } + } else { + if(subfamily) { + /* proprietary subfamily only */ + if(afi == nfcv_data->afi) { + respond = true; + } + } else { + /* all families and subfamilies */ + respond = true; + } } + } else { respond = true; } @@ -740,13 +771,19 @@ void nfcv_emu_handle_packet( case NFCV_CMD_READ_MULTI_BLOCK: case NFCV_CMD_READ_BLOCK: { uint8_t block = nfcv_data->frame[ctx->payload_offset]; - uint8_t blocks = 1; + int blocks = 1; if(ctx->command == NFCV_CMD_READ_MULTI_BLOCK) { blocks = nfcv_data->frame[ctx->payload_offset + 1] + 1; } - if(block + blocks <= nfcv_data->block_num) { + /* limit the maximum block count, underflow accepted */ + if(block + blocks > nfcv_data->block_num) { + blocks = nfcv_data->block_num - block; + } + + /* only respond with the valid blocks, if there are any */ + if(blocks > 0) { uint8_t buffer_pos = 0; ctx->response_buffer[buffer_pos++] = NFCV_NOERROR; @@ -773,10 +810,13 @@ void nfcv_emu_handle_packet( ctx->response_flags, ctx->send_time); } else { - ctx->response_buffer[0] = NFCV_RES_FLAG_ERROR; - ctx->response_buffer[1] = NFCV_ERROR_GENERIC; - nfcv_emu_send( - tx_rx, nfcv_data, ctx->response_buffer, 2, ctx->response_flags, ctx->send_time); + /* reply with an error only in addressed or selected mode */ + if(ctx->addressed || ctx->selected) { + ctx->response_buffer[0] = NFCV_RES_FLAG_ERROR; + ctx->response_buffer[1] = NFCV_ERROR_GENERIC; + nfcv_emu_send( + tx_rx, nfcv_data, ctx->response_buffer, 2, ctx->response_flags, ctx->send_time); + } } snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "READ BLOCK %d", block); diff --git a/lib/nfc/protocols/nfcv.h b/lib/nfc/protocols/nfcv.h index 87a69673753..e4139de9987 100644 --- a/lib/nfc/protocols/nfcv.h +++ b/lib/nfc/protocols/nfcv.h @@ -139,8 +139,10 @@ typedef enum { } NfcVErrorcodes; typedef enum { - NfcVLockBitDsfid = 1, - NfcVLockBitAfi = 2, + NfcVLockBitDsfid = 1 << 0, + NfcVLockBitAfi = 1 << 1, + NfcVLockBitEas = 1 << 2, + NfcVLockBitPpl = 1 << 3, } NfcVLockBits; typedef enum { @@ -168,14 +170,55 @@ typedef enum { NfcVSendFlagsHighRate = 1 << 4 } NfcVSendFlags; +/* SLIX specific config flags */ +typedef enum { + NfcVSlixDataFlagsNone = 0, + NfcVSlixDataFlagsHasKeyRead = 1 << 0, + NfcVSlixDataFlagsHasKeyWrite = 1 << 1, + NfcVSlixDataFlagsHasKeyPrivacy = 1 << 2, + NfcVSlixDataFlagsHasKeyDestroy = 1 << 3, + NfcVSlixDataFlagsHasKeyEas = 1 << 4, + NfcVSlixDataFlagsValidKeyRead = 1 << 8, + NfcVSlixDataFlagsValidKeyWrite = 1 << 9, + NfcVSlixDataFlagsValidKeyPrivacy = 1 << 10, + NfcVSlixDataFlagsValidKeyDestroy = 1 << 11, + NfcVSlixDataFlagsValidKeyEas = 1 << 12, + NfcVSlixDataFlagsPrivacy = 1 << 16, + NfcVSlixDataFlagsDestroyed = 1 << 17 +} NfcVSlixDataFlags; + +/* abstract the file read/write operations for all SLIX types to reduce duplicated code */ +typedef enum { + SlixFeatureRead = 1 << 0, + SlixFeatureWrite = 1 << 1, + SlixFeaturePrivacy = 1 << 2, + SlixFeatureDestroy = 1 << 3, + SlixFeatureEas = 1 << 4, + SlixFeatureSignature = 1 << 5, + SlixFeatureProtection = 1 << 6, + + SlixFeatureSlix = SlixFeatureEas, + SlixFeatureSlixS = + (SlixFeatureRead | SlixFeatureWrite | SlixFeaturePrivacy | SlixFeatureDestroy | + SlixFeatureEas), + SlixFeatureSlixL = (SlixFeaturePrivacy | SlixFeatureDestroy | SlixFeatureEas), + SlixFeatureSlix2 = + (SlixFeatureRead | SlixFeatureWrite | SlixFeaturePrivacy | SlixFeatureDestroy | + SlixFeatureEas | SlixFeatureSignature | SlixFeatureProtection), +} SlixTypeFeatures; + typedef struct { + uint32_t flags; uint8_t key_read[4]; uint8_t key_write[4]; uint8_t key_privacy[4]; uint8_t key_destroy[4]; uint8_t key_eas[4]; uint8_t rand[2]; - bool privacy; + uint8_t signature[32]; + /* SLIX2 options */ + uint8_t pp_pointer; + uint8_t pp_condition; } NfcVSlixData; typedef union { diff --git a/lib/nfc/protocols/slix.c b/lib/nfc/protocols/slix.c index 1c14c0bf94b..68937d161a6 100644 --- a/lib/nfc/protocols/slix.c +++ b/lib/nfc/protocols/slix.c @@ -9,6 +9,120 @@ #define TAG "SLIX" +ReturnCode slix2_read_nxp_sysinfo(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data) { + furi_assert(nfc_data); + furi_assert(nfcv_data); + + uint8_t rxBuf[32]; + uint16_t received = 0; + ReturnCode ret = ERR_NONE; + + FURI_LOG_D(TAG, "Read NXP SYSTEM INFORMATION..."); + + for(int tries = 0; tries < NFCV_COMMAND_RETRIES; tries++) { + uint8_t cmd[] = {}; + uint8_t uid[NFCV_UID_LENGTH]; + + /* UID is stored reversed in requests */ + for(int pos = 0; pos < nfc_data->uid_len; pos++) { + uid[pos] = nfc_data->uid[nfc_data->uid_len - 1 - pos]; + } + + ReturnCode ret = rfalNfcvPollerTransceiveReq( + NFCV_CMD_NXP_GET_NXP_SYSTEM_INFORMATION, + RFAL_NFCV_REQ_FLAG_DEFAULT, + NFCV_MANUFACTURER_NXP, + uid, + cmd, + sizeof(cmd), + rxBuf, + sizeof(rxBuf), + &received); + + if(ret == ERR_NONE) { + break; + } + } + + if(ret != ERR_NONE || received != 8) { //-V560 + FURI_LOG_D(TAG, "Failed: %d, %d", ret, received); + return ret; + } + FURI_LOG_D(TAG, "Success..."); + + NfcVSlixData* slix = &nfcv_data->sub_data.slix; + slix->pp_pointer = rxBuf[1]; + slix->pp_condition = rxBuf[2]; + + /* convert NXP's to our internal lock bits format */ + nfcv_data->security_status[0] = 0; + nfcv_data->security_status[0] |= (rxBuf[3] & SlixLockBitDsfid) ? NfcVLockBitDsfid : 0; + nfcv_data->security_status[0] |= (rxBuf[3] & SlixLockBitAfi) ? NfcVLockBitAfi : 0; + nfcv_data->security_status[0] |= (rxBuf[3] & SlixLockBitEas) ? NfcVLockBitEas : 0; + nfcv_data->security_status[0] |= (rxBuf[3] & SlixLockBitPpl) ? NfcVLockBitPpl : 0; + + return ERR_NONE; +} + +ReturnCode slix2_read_signature(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data) { + furi_assert(nfc_data); + furi_assert(nfcv_data); + + uint8_t rxBuf[64]; + uint16_t received = 0; + ReturnCode ret = ERR_NONE; + + FURI_LOG_D(TAG, "Read SIGNATURE..."); + + for(int tries = 0; tries < NFCV_COMMAND_RETRIES; tries++) { + uint8_t cmd[] = {}; + uint8_t uid[NFCV_UID_LENGTH]; + + /* UID is stored reversed in requests */ + for(int pos = 0; pos < nfc_data->uid_len; pos++) { + uid[pos] = nfc_data->uid[nfc_data->uid_len - 1 - pos]; + } + + ReturnCode ret = rfalNfcvPollerTransceiveReq( + NFCV_CMD_NXP_READ_SIGNATURE, + RFAL_NFCV_REQ_FLAG_DEFAULT, + NFCV_MANUFACTURER_NXP, + uid, + cmd, + sizeof(cmd), + rxBuf, + sizeof(rxBuf), + &received); + + if(ret == ERR_NONE) { + break; + } + } + + if(ret != ERR_NONE || received != 33) { //-V560 + FURI_LOG_D(TAG, "Failed: %d, %d", ret, received); + return ret; + } + FURI_LOG_D(TAG, "Success..."); + + NfcVSlixData* slix = &nfcv_data->sub_data.slix; + memcpy(slix->signature, &rxBuf[1], 32); + + return ERR_NONE; +} + +ReturnCode slix2_read_custom(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data) { + ReturnCode ret = ERR_NONE; + + ret = slix2_read_nxp_sysinfo(nfc_data, nfcv_data); + if(ret != ERR_NONE) { + return ret; + } + ret = slix2_read_signature(nfc_data, nfcv_data); + + return ret; +} + static uint32_t slix_read_be(uint8_t* data, uint32_t length) { uint32_t value = 0; @@ -137,6 +251,43 @@ ReturnCode slix_unlock(NfcVData* data, uint32_t password_id) { return ret; } +static void slix_generic_pass_infos( + uint8_t password_id, + NfcVSlixData* slix, + uint8_t** password, + uint32_t* flag_valid, + uint32_t* flag_set) { + switch(password_id) { + case SLIX_PASS_READ: + *password = slix->key_read; + *flag_valid = NfcVSlixDataFlagsValidKeyRead; + *flag_set = NfcVSlixDataFlagsHasKeyRead; + break; + case SLIX_PASS_WRITE: + *password = slix->key_write; + *flag_valid = NfcVSlixDataFlagsValidKeyWrite; + *flag_set = NfcVSlixDataFlagsHasKeyWrite; + break; + case SLIX_PASS_PRIVACY: + *password = slix->key_privacy; + *flag_valid = NfcVSlixDataFlagsValidKeyPrivacy; + *flag_set = NfcVSlixDataFlagsHasKeyPrivacy; + break; + case SLIX_PASS_DESTROY: + *password = slix->key_destroy; + *flag_valid = NfcVSlixDataFlagsValidKeyDestroy; + *flag_set = NfcVSlixDataFlagsHasKeyDestroy; + break; + case SLIX_PASS_EASAFI: + *password = slix->key_eas; + *flag_valid = NfcVSlixDataFlagsValidKeyEas; + *flag_set = NfcVSlixDataFlagsHasKeyEas; + break; + default: + break; + } +} + bool slix_generic_protocol_filter( FuriHalNfcTxRxContext* tx_rx, FuriHalNfcDevData* nfc_data, @@ -150,7 +301,8 @@ bool slix_generic_protocol_filter( NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx; NfcVSlixData* slix = &nfcv_data->sub_data.slix; - if(slix->privacy && ctx->command != NFCV_CMD_NXP_GET_RANDOM_NUMBER && + if((slix->flags & NfcVSlixDataFlagsPrivacy) && + ctx->command != NFCV_CMD_NXP_GET_RANDOM_NUMBER && ctx->command != NFCV_CMD_NXP_SET_PASSWORD) { snprintf( nfcv_data->last_command, @@ -186,66 +338,73 @@ bool slix_generic_protocol_filter( } case NFCV_CMD_NXP_SET_PASSWORD: { + /* the password to be set is the first parameter */ uint8_t password_id = nfcv_data->frame[ctx->payload_offset]; + /* right after that is the XORed password */ + uint8_t* password_xored = &nfcv_data->frame[ctx->payload_offset + 1]; + /* only handle if the password type is supported */ if(!(password_id & password_supported)) { break; } - uint8_t* password_xored = &nfcv_data->frame[ctx->payload_offset + 1]; + /* fetch the last RAND value */ uint8_t* rand = slix->rand; - uint8_t* password = NULL; - uint8_t password_rcv[4]; - switch(password_id) { - case SLIX_PASS_READ: - password = slix->key_read; - break; - case SLIX_PASS_WRITE: - password = slix->key_write; - break; - case SLIX_PASS_PRIVACY: - password = slix->key_privacy; - break; - case SLIX_PASS_DESTROY: - password = slix->key_destroy; - break; - case SLIX_PASS_EASAFI: - password = slix->key_eas; - break; - default: - break; + /* first calc the password that has been sent */ + uint8_t password_rcv[4]; + for(int pos = 0; pos < 4; pos++) { + password_rcv[pos] = password_xored[3 - pos] ^ rand[pos % 2]; } + uint32_t pass_received = slix_read_be(password_rcv, 4); + /* then determine the password type (or even update if not set yet) */ + uint8_t* password = NULL; + uint32_t flag_valid = 0; + uint32_t flag_set = 0; + + slix_generic_pass_infos(password_id, slix, &password, &flag_valid, &flag_set); + + /* when the password is not supported, return silently */ if(!password) { break; } - for(int pos = 0; pos < 4; pos++) { - password_rcv[pos] = password_xored[3 - pos] ^ rand[pos % 2]; + /* check if the password is known */ + bool pass_valid = false; + uint32_t pass_expect = 0; + + if(slix->flags & flag_set) { + /* if so, fetch the stored password and compare */ + pass_expect = slix_read_be(password, 4); + pass_valid = (pass_expect == pass_received); + } else { + /* if not known, just accept it and store that password */ + memcpy(password, password_rcv, 4); + nfcv_data->modified = true; + slix->flags |= flag_set; + + pass_valid = true; } - uint32_t pass_expect = slix_read_be(password, 4); - uint32_t pass_received = slix_read_be(password_rcv, 4); - /* if the password is all-zeroes, just accept any password*/ - if(!pass_expect || pass_expect == pass_received) { + /* if the pass was valid or accepted for other reasons, continue */ + if(pass_valid) { + slix->flags |= flag_valid; + + /* handle actions when a correct password was given, aside of setting the flag */ switch(password_id) { - case SLIX_PASS_READ: - break; - case SLIX_PASS_WRITE: - break; case SLIX_PASS_PRIVACY: - slix->privacy = false; + slix->flags &= ~NfcVSlixDataFlagsPrivacy; nfcv_data->modified = true; break; case SLIX_PASS_DESTROY: + slix->flags |= NfcVSlixDataFlagsDestroyed; FURI_LOG_D(TAG, "Pooof! Got destroyed"); break; - case SLIX_PASS_EASAFI: - break; default: break; } + ctx->response_buffer[0] = NFCV_NOERROR; nfcv_emu_send( tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); @@ -268,6 +427,49 @@ bool slix_generic_protocol_filter( break; } + case NFCV_CMD_NXP_WRITE_PASSWORD: { + uint8_t password_id = nfcv_data->frame[ctx->payload_offset]; + + if(!(password_id & password_supported)) { + break; + } + + uint8_t* new_password = &nfcv_data->frame[ctx->payload_offset + 1]; + uint8_t* password = NULL; + uint32_t flag_valid = 0; + uint32_t flag_set = 0; + + slix_generic_pass_infos(password_id, slix, &password, &flag_valid, &flag_set); + + /* when the password is not supported, return silently */ + if(!password) { + break; + } + + bool pass_valid = (slix->flags & flag_valid); + if(!(slix->flags & flag_set)) { + pass_valid = true; + } + + if(pass_valid) { + slix->flags |= flag_valid; + slix->flags |= flag_set; + + memcpy(password, new_password, 4); + + ctx->response_buffer[0] = NFCV_NOERROR; + nfcv_emu_send( + tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); + snprintf( + nfcv_data->last_command, sizeof(nfcv_data->last_command), "WRITE_PASSWORD OK"); + } else { + snprintf( + nfcv_data->last_command, sizeof(nfcv_data->last_command), "WRITE_PASSWORD FAIL"); + } + handled = true; + break; + } + case NFCV_CMD_NXP_ENABLE_PRIVACY: { ctx->response_buffer[0] = NFCV_NOERROR; @@ -278,7 +480,7 @@ bool slix_generic_protocol_filter( sizeof(nfcv_data->last_command), "NFCV_CMD_NXP_ENABLE_PRIVACY"); - slix->privacy = true; + slix->flags |= NfcVSlixDataFlagsPrivacy; handled = true; break; } @@ -315,7 +517,10 @@ void slix_l_prepare(NfcVData* nfcv_data) { FURI_LOG_D( TAG, " Destroy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_destroy, 4)); FURI_LOG_D(TAG, " EAS pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_eas, 4)); - FURI_LOG_D(TAG, " Privacy mode: %s", nfcv_data->sub_data.slix.privacy ? "ON" : "OFF"); + FURI_LOG_D( + TAG, + " Privacy mode: %s", + (nfcv_data->sub_data.slix.flags & NfcVSlixDataFlagsPrivacy) ? "ON" : "OFF"); NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx; ctx->emu_protocol_filter = &slix_l_protocol_filter; @@ -345,7 +550,10 @@ void slix_s_prepare(NfcVData* nfcv_data) { FURI_LOG_D( TAG, " Destroy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_destroy, 4)); FURI_LOG_D(TAG, " EAS pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_eas, 4)); - FURI_LOG_D(TAG, " Privacy mode: %s", nfcv_data->sub_data.slix.privacy ? "ON" : "OFF"); + FURI_LOG_D( + TAG, + " Privacy mode: %s", + (nfcv_data->sub_data.slix.flags & NfcVSlixDataFlagsPrivacy) ? "ON" : "OFF"); NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx; ctx->emu_protocol_filter = &slix_s_protocol_filter; @@ -375,7 +583,10 @@ void slix_prepare(NfcVData* nfcv_data) { FURI_LOG_D( TAG, " Destroy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_destroy, 4)); FURI_LOG_D(TAG, " EAS pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_eas, 4)); - FURI_LOG_D(TAG, " Privacy mode: %s", nfcv_data->sub_data.slix.privacy ? "ON" : "OFF"); + FURI_LOG_D( + TAG, + " Privacy mode: %s", + (nfcv_data->sub_data.slix.flags & NfcVSlixDataFlagsPrivacy) ? "ON" : "OFF"); NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx; ctx->emu_protocol_filter = &slix_protocol_filter; @@ -389,6 +600,10 @@ bool slix2_protocol_filter( // -V524 furi_assert(nfc_data); furi_assert(nfcv_data_in); + NfcVData* nfcv_data = (NfcVData*)nfcv_data_in; + NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx; + NfcVSlixData* slix = &nfcv_data->sub_data.slix; + bool handled = false; /* many SLIX share some of the functions, place that in a generic handler */ @@ -396,6 +611,160 @@ bool slix2_protocol_filter( // -V524 return true; } + switch(ctx->command) { + /* override WRITE BLOCK for block 79 (16 bit counter) */ + case NFCV_CMD_WRITE_BLOCK: + case NFCV_CMD_WRITE_MULTI_BLOCK: { + uint8_t resp_len = 1; + uint8_t blocks = 1; + uint8_t block = nfcv_data->frame[ctx->payload_offset]; + uint8_t data_pos = ctx->payload_offset + 1; + + if(ctx->command == NFCV_CMD_WRITE_MULTI_BLOCK) { + blocks = nfcv_data->frame[data_pos] + 1; + data_pos++; + } + + uint8_t* data = &nfcv_data->frame[data_pos]; + uint32_t data_len = nfcv_data->block_size * blocks; + + if((block + blocks) <= nfcv_data->block_num && + (data_pos + data_len + 2) == nfcv_data->frame_length) { + ctx->response_buffer[0] = NFCV_NOERROR; + + for(int block_num = block; block_num < block + blocks; block_num++) { + /* special case, 16-bit counter */ + if(block_num == 79) { + uint32_t dest; + uint32_t ctr_old; + + memcpy(&dest, &nfcv_data->frame[data_pos], 4); + memcpy(&ctr_old, &nfcv_data->data[nfcv_data->block_size * block_num], 4); + + uint32_t ctr_new = ctr_old; + bool allowed = true; + + /* increment counter */ + if(dest == 1) { + ctr_new = (ctr_old & 0xFFFF0000) | ((ctr_old + 1) & 0xFFFF); + + /* protection flag set? */ + if(ctr_old & 0x01000000) { //-V1051 + allowed = nfcv_data->sub_data.slix.flags & + NfcVSlixDataFlagsValidKeyRead; + } + } else { + ctr_new = dest; + allowed = nfcv_data->sub_data.slix.flags & NfcVSlixDataFlagsValidKeyWrite; + } + + if(allowed) { + memcpy( //-V1086 + &nfcv_data->data[nfcv_data->block_size * block_num], + &ctr_new, + 4); + } else { + /* incorrect read or write password */ + ctx->response_buffer[0] = NFCV_RES_FLAG_ERROR; + ctx->response_buffer[1] = NFCV_ERROR_GENERIC; + resp_len = 2; + } + } else { + memcpy( + &nfcv_data->data[nfcv_data->block_size * block_num], + &nfcv_data->frame[data_pos], + nfcv_data->block_size); + } + data_pos += nfcv_data->block_size; + } + nfcv_data->modified = true; + + } else { + ctx->response_buffer[0] = NFCV_RES_FLAG_ERROR; + ctx->response_buffer[1] = NFCV_ERROR_GENERIC; + resp_len = 2; + } + + bool respond = (ctx->response_buffer[0] == NFCV_NOERROR) || + (ctx->addressed || ctx->selected); + + if(respond) { + nfcv_emu_send( + tx_rx, + nfcv_data, + ctx->response_buffer, + resp_len, + ctx->response_flags, + ctx->send_time); + } + + if(ctx->command == NFCV_CMD_WRITE_MULTI_BLOCK) { + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "WRITE MULTI BLOCK %d, %d blocks", + block, + blocks); + } else { + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "WRITE BLOCK %d <- %02X %02X %02X %02X", + block, + data[0], + data[1], + data[2], + data[3]); + } + handled = true; + break; + } + + case NFCV_CMD_NXP_READ_SIGNATURE: { + uint32_t len = 0; + ctx->response_buffer[len++] = NFCV_NOERROR; + memcpy(&ctx->response_buffer[len], slix->signature, sizeof(slix->signature)); + len += sizeof(slix->signature); + + nfcv_emu_send( + tx_rx, nfcv_data, ctx->response_buffer, len, ctx->response_flags, ctx->send_time); + snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "READ_SIGNATURE"); + + handled = true; + break; + } + + case NFCV_CMD_NXP_GET_NXP_SYSTEM_INFORMATION: { + uint32_t len = 0; + uint8_t lock_bits = 0; + + /* convert our internal lock bits format into NXP's */ + lock_bits |= (nfcv_data->security_status[0] & NfcVLockBitDsfid) ? SlixLockBitDsfid : 0; + lock_bits |= (nfcv_data->security_status[0] & NfcVLockBitAfi) ? SlixLockBitAfi : 0; + lock_bits |= (nfcv_data->security_status[0] & NfcVLockBitEas) ? SlixLockBitEas : 0; + lock_bits |= (nfcv_data->security_status[0] & NfcVLockBitPpl) ? SlixLockBitPpl : 0; + + ctx->response_buffer[len++] = NFCV_NOERROR; + ctx->response_buffer[len++] = nfcv_data->sub_data.slix.pp_pointer; + ctx->response_buffer[len++] = nfcv_data->sub_data.slix.pp_condition; + ctx->response_buffer[len++] = lock_bits; + ctx->response_buffer[len++] = 0x7F; /* features LSB */ + ctx->response_buffer[len++] = 0x35; /* features */ + ctx->response_buffer[len++] = 0; /* features */ + ctx->response_buffer[len++] = 0; /* features MSB */ + + nfcv_emu_send( + tx_rx, nfcv_data, ctx->response_buffer, len, ctx->response_flags, ctx->send_time); + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "GET_NXP_SYSTEM_INFORMATION"); + + handled = true; + break; + } + } + return handled; } @@ -405,7 +774,10 @@ void slix2_prepare(NfcVData* nfcv_data) { FURI_LOG_D( TAG, " Destroy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_destroy, 4)); FURI_LOG_D(TAG, " EAS pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_eas, 4)); - FURI_LOG_D(TAG, " Privacy mode: %s", nfcv_data->sub_data.slix.privacy ? "ON" : "OFF"); + FURI_LOG_D( + TAG, + " Privacy mode: %s", + (nfcv_data->sub_data.slix.flags & NfcVSlixDataFlagsPrivacy) ? "ON" : "OFF"); NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx; ctx->emu_protocol_filter = &slix2_protocol_filter; diff --git a/lib/nfc/protocols/slix.h b/lib/nfc/protocols/slix.h index 701fa2f8209..67f09e46d6f 100644 --- a/lib/nfc/protocols/slix.h +++ b/lib/nfc/protocols/slix.h @@ -8,19 +8,35 @@ #define NFCV_MANUFACTURER_NXP 0x04 /* ISO15693-3 CUSTOM NXP COMMANDS */ -#define NFCV_CMD_NXP_SET_EAS 0xA2 -#define NFCV_CMD_NXP_RESET_EAS 0xA3 -#define NFCV_CMD_NXP_LOCK_EAS 0xA4 -#define NFCV_CMD_NXP_EAS_ALARM 0xA5 -#define NFCV_CMD_NXP_PASSWORD_PROTECT_EAS_AFI 0xA6 -#define NFCV_CMD_NXP_WRITE_EAS_ID 0xA7 -#define NFCV_CMD_NXP_INVENTORY_PAGE_READ 0xB0 -#define NFCV_CMD_NXP_INVENTORY_PAGE_READ_FAST 0xB1 -#define NFCV_CMD_NXP_GET_RANDOM_NUMBER 0xB2 -#define NFCV_CMD_NXP_SET_PASSWORD 0xB3 -#define NFCV_CMD_NXP_WRITE_PASSWORD 0xB4 -#define NFCV_CMD_NXP_DESTROY 0xB9 -#define NFCV_CMD_NXP_ENABLE_PRIVACY 0xBA +typedef enum { + NFCV_CMD_NXP_SET_EAS = 0xA2, + NFCV_CMD_NXP_RESET_EAS = 0xA3, + NFCV_CMD_NXP_LOCK_EAS = 0xA4, + NFCV_CMD_NXP_EAS_ALARM = 0xA5, + NFCV_CMD_NXP_PASSWORD_PROTECT_EAS_AFI = 0xA6, + NFCV_CMD_NXP_WRITE_EAS_ID = 0xA7, + NFCV_CMD_NXP_GET_NXP_SYSTEM_INFORMATION = 0xAB, + NFCV_CMD_NXP_INVENTORY_PAGE_READ = 0xB0, + NFCV_CMD_NXP_INVENTORY_PAGE_READ_FAST = 0xB1, + NFCV_CMD_NXP_GET_RANDOM_NUMBER = 0xB2, + NFCV_CMD_NXP_SET_PASSWORD = 0xB3, + NFCV_CMD_NXP_WRITE_PASSWORD = 0xB4, + NFCV_CMD_NXP_64_BIT_PASSWORD_PROTECTION = 0xB5, + NFCV_CMD_NXP_PROTECT_PAGE = 0xB6, + NFCV_CMD_NXP_LOCK_PAGE_PROTECTION_CONDITION = 0xB7, + NFCV_CMD_NXP_DESTROY = 0xB9, + NFCV_CMD_NXP_ENABLE_PRIVACY = 0xBA, + NFCV_CMD_NXP_STAY_QUIET_PERSISTENT = 0xBC, + NFCV_CMD_NXP_READ_SIGNATURE = 0xBD +} SlixCommands; + +/* lock bit bits used in SLIX's NXP SYSTEM INFORMATION response */ +typedef enum { + SlixLockBitAfi = 1 << 0, + SlixLockBitEas = 1 << 1, + SlixLockBitDsfid = 1 << 2, + SlixLockBitPpl = 1 << 3, +} SlixLockBits; /* available passwords */ #define SLIX_PASS_READ 0x01 @@ -37,6 +53,10 @@ bool slix2_check_card_type(FuriHalNfcDevData* nfc_data); bool slix_s_check_card_type(FuriHalNfcDevData* nfc_data); bool slix_l_check_card_type(FuriHalNfcDevData* nfc_data); +ReturnCode slix2_read_custom(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data); +ReturnCode slix2_read_signature(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data); +ReturnCode slix2_read_nxp_sysinfo(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data); + ReturnCode slix_get_random(NfcVData* data); ReturnCode slix_unlock(NfcVData* data, uint32_t password_id); From e5ae3e22b39f1ae3c2c305dc1f98ff3522ae095c Mon Sep 17 00:00:00 2001 From: AloneLiberty <111039319+AloneLiberty@users.noreply.github.com> Date: Thu, 29 Jun 2023 11:24:13 +0300 Subject: [PATCH 632/824] NFC: Fix key invalidation logic (#2782) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * NFC: Fix key invalidation logic * NFC: Fix crash in CLI with empty response * Fix incorrect key conversions * Proper call to nfc_util Co-authored-by: あく Co-authored-by: Astra --- applications/main/nfc/nfc_cli.c | 4 ++++ lib/nfc/nfc_worker.c | 18 +++++++++--------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/applications/main/nfc/nfc_cli.c b/applications/main/nfc/nfc_cli.c index 6e6e04ca928..0b7e7547548 100644 --- a/applications/main/nfc/nfc_cli.c +++ b/applications/main/nfc/nfc_cli.c @@ -144,6 +144,10 @@ static void nfc_cli_apdu(Cli* cli, FuriString* args) { break; } resp_size = (tx_rx.rx_bits / 8) * 2; + if(!resp_size) { + printf("No response\r\n"); + break; + } resp_buffer = malloc(resp_size); uint8_to_hex_chars(tx_rx.rx_data, resp_buffer, resp_size); resp_buffer[resp_size] = 0; diff --git a/lib/nfc/nfc_worker.c b/lib/nfc/nfc_worker.c index a6bb93f59ef..a39531c8c29 100644 --- a/lib/nfc/nfc_worker.c +++ b/lib/nfc/nfc_worker.c @@ -940,14 +940,14 @@ void nfc_worker_mf_classic_dict_attack(NfcWorker* nfc_worker) { deactivated = true; } else { // If the key A is marked as found and matches the searching key, invalidate it - uint8_t found_key[6]; - memcpy(found_key, data->block[i].value, 6); + MfClassicSectorTrailer* sec_trailer = + mf_classic_get_sector_trailer_by_sector(data, i); uint8_t current_key[6]; - memcpy(current_key, &key, 6); + nfc_util_num2bytes(key, 6, current_key); if(mf_classic_is_key_found(data, i, MfClassicKeyA) && - memcmp(found_key, current_key, 6) == 0) { + memcmp(sec_trailer->key_a, current_key, 6) == 0) { mf_classic_set_key_not_found(data, i, MfClassicKeyA); is_key_a_found = false; FURI_LOG_D(TAG, "Key %dA not found in attack", i); @@ -966,14 +966,14 @@ void nfc_worker_mf_classic_dict_attack(NfcWorker* nfc_worker) { deactivated = true; } else { // If the key B is marked as found and matches the searching key, invalidate it - uint8_t found_key[6]; - memcpy(found_key, data->block[i].value + 10, 6); + MfClassicSectorTrailer* sec_trailer = + mf_classic_get_sector_trailer_by_sector(data, i); uint8_t current_key[6]; - memcpy(current_key, &key, 6); + nfc_util_num2bytes(key, 6, current_key); if(mf_classic_is_key_found(data, i, MfClassicKeyB) && - memcmp(found_key, current_key, 6) == 0) { + memcmp(sec_trailer->key_b, current_key, 6) == 0) { mf_classic_set_key_not_found(data, i, MfClassicKeyB); is_key_b_found = false; FURI_LOG_D(TAG, "Key %dB not found in attack", i); @@ -989,7 +989,7 @@ void nfc_worker_mf_classic_dict_attack(NfcWorker* nfc_worker) { } if(nfc_worker->state != NfcWorkerStateMfClassicDictAttack) break; } - memcpy(&prev_key, &key, sizeof(key)); + prev_key = key; } if(nfc_worker->state != NfcWorkerStateMfClassicDictAttack) break; mf_classic_read_sector(&tx_rx, data, i); From 95c1585df6bfc9825e849644522be205da50f57f Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Thu, 29 Jun 2023 14:23:04 +0300 Subject: [PATCH 633/824] [FL-3211][FL-3212] Debug apps: speaker, uart_echo with baudrate (#2812) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Music player: move music_worker to library * Music player: drop cli * Debug: speaker debug app * Debug: baudrate arg in uart_echo app * Libs: add music_worker to api * Libs: add music_worker to targets linker_dependencies Co-authored-by: あく --- applications/debug/application.fam | 1 + .../debug/speaker_debug/application.fam | 11 ++ .../debug/speaker_debug/speaker_debug.c | 120 ++++++++++++++++++ applications/debug/uart_echo/uart_echo.c | 20 ++- .../external/music_player/application.fam | 10 +- .../external/music_player/music_player.c | 27 ++-- .../external/music_player/music_player_cli.c | 48 ------- .../music_player/music_player_worker.h | 38 ------ firmware/targets/f18/api_symbols.csv | 15 ++- firmware/targets/f18/target.json | 1 + firmware/targets/f7/api_symbols.csv | 21 ++- firmware/targets/f7/target.json | 1 + lib/SConscript | 2 + lib/music_worker/SConscript | 27 ++++ .../music_worker/music_worker.c | 69 +++++----- lib/music_worker/music_worker.h | 37 ++++++ 16 files changed, 295 insertions(+), 153 deletions(-) create mode 100644 applications/debug/speaker_debug/application.fam create mode 100644 applications/debug/speaker_debug/speaker_debug.c delete mode 100644 applications/external/music_player/music_player_cli.c delete mode 100644 applications/external/music_player/music_player_worker.h create mode 100644 lib/music_worker/SConscript rename applications/external/music_player/music_player_worker.c => lib/music_worker/music_worker.c (85%) create mode 100644 lib/music_worker/music_worker.h diff --git a/applications/debug/application.fam b/applications/debug/application.fam index a33b3693dfe..cdbf8fe18b6 100644 --- a/applications/debug/application.fam +++ b/applications/debug/application.fam @@ -12,5 +12,6 @@ App( "display_test", "text_box_test", "file_browser_test", + "speaker_debug", ], ) diff --git a/applications/debug/speaker_debug/application.fam b/applications/debug/speaker_debug/application.fam new file mode 100644 index 00000000000..68d8b188bdd --- /dev/null +++ b/applications/debug/speaker_debug/application.fam @@ -0,0 +1,11 @@ +App( + appid="speaker_debug", + name="Speaker Debug", + apptype=FlipperAppType.DEBUG, + entry_point="speaker_debug_app", + requires=["gui", "notification"], + stack_size=2 * 1024, + order=10, + fap_category="Debug", + fap_libs=["music_worker"], +) diff --git a/applications/debug/speaker_debug/speaker_debug.c b/applications/debug/speaker_debug/speaker_debug.c new file mode 100644 index 00000000000..e01d5b8ec44 --- /dev/null +++ b/applications/debug/speaker_debug/speaker_debug.c @@ -0,0 +1,120 @@ +#include +#include +#include +#include +#include + +#define TAG "SpeakerDebug" +#define CLI_COMMAND "speaker_debug" + +typedef enum { + SpeakerDebugAppMessageTypeStop, +} SpeakerDebugAppMessageType; + +typedef struct { + SpeakerDebugAppMessageType type; +} SpeakerDebugAppMessage; + +typedef struct { + MusicWorker* music_worker; + FuriMessageQueue* message_queue; + Cli* cli; +} SpeakerDebugApp; + +static SpeakerDebugApp* speaker_app_alloc() { + SpeakerDebugApp* app = (SpeakerDebugApp*)malloc(sizeof(SpeakerDebugApp)); + app->music_worker = music_worker_alloc(); + app->message_queue = furi_message_queue_alloc(8, sizeof(SpeakerDebugAppMessage)); + app->cli = furi_record_open(RECORD_CLI); + return app; +} + +static void speaker_app_free(SpeakerDebugApp* app) { + music_worker_free(app->music_worker); + furi_message_queue_free(app->message_queue); + furi_record_close(RECORD_CLI); + free(app); +} + +static void speaker_app_cli(Cli* cli, FuriString* args, void* context) { + UNUSED(cli); + + SpeakerDebugApp* app = (SpeakerDebugApp*)context; + SpeakerDebugAppMessage message; + FuriString* cmd = furi_string_alloc(); + + if(!args_read_string_and_trim(args, cmd)) { + furi_string_free(cmd); + printf("Usage:\r\n"); + printf("\t" CLI_COMMAND " stop\r\n"); + return; + } + + if(furi_string_cmp(cmd, "stop") == 0) { + message.type = SpeakerDebugAppMessageTypeStop; + FuriStatus status = furi_message_queue_put(app->message_queue, &message, 100); + if(status != FuriStatusOk) { + printf("Failed to send message\r\n"); + } else { + printf("Stopping\r\n"); + } + } else { + printf("Usage:\r\n"); + printf("\t" CLI_COMMAND " stop\r\n"); + } + + furi_string_free(cmd); +} + +static bool speaker_app_music_play(SpeakerDebugApp* app, const char* rtttl) { + if(music_worker_is_playing(app->music_worker)) { + music_worker_stop(app->music_worker); + } + + if(!music_worker_load_rtttl_from_string(app->music_worker, rtttl)) { + FURI_LOG_E(TAG, "Failed to load RTTTL"); + return false; + } + + music_worker_set_volume(app->music_worker, 1.0f); + music_worker_start(app->music_worker); + + return true; +} + +static void speaker_app_music_stop(SpeakerDebugApp* app) { + if(music_worker_is_playing(app->music_worker)) { + music_worker_stop(app->music_worker); + } +} + +static void speaker_app_run(SpeakerDebugApp* app, const char* arg) { + if(!arg || !speaker_app_music_play(app, arg)) { + FURI_LOG_E(TAG, "Provided RTTTL is invalid"); + return; + } + + cli_add_command(app->cli, CLI_COMMAND, CliCommandFlagParallelSafe, speaker_app_cli, app); + + SpeakerDebugAppMessage message; + FuriStatus status; + while(true) { + status = furi_message_queue_get(app->message_queue, &message, FuriWaitForever); + + if(status == FuriStatusOk) { + if(message.type == SpeakerDebugAppMessageTypeStop) { + speaker_app_music_stop(app); + break; + } + } + } + + cli_delete_command(app->cli, CLI_COMMAND); +} + +int32_t speaker_debug_app(void* arg) { + SpeakerDebugApp* app = speaker_app_alloc(); + speaker_app_run(app, arg); + speaker_app_free(app); + return 0; +} diff --git a/applications/debug/uart_echo/uart_echo.c b/applications/debug/uart_echo/uart_echo.c index dc132752921..4bede9ab45f 100644 --- a/applications/debug/uart_echo/uart_echo.c +++ b/applications/debug/uart_echo/uart_echo.c @@ -10,6 +10,8 @@ #define LINES_ON_SCREEN 6 #define COLUMNS_ON_SCREEN 21 +#define TAG "UartEcho" +#define DEFAULT_BAUD_RATE 230400 typedef struct UartDumpModel UartDumpModel; @@ -179,7 +181,7 @@ static int32_t uart_echo_worker(void* context) { return 0; } -static UartEchoApp* uart_echo_app_alloc() { +static UartEchoApp* uart_echo_app_alloc(uint32_t baudrate) { UartEchoApp* app = malloc(sizeof(UartEchoApp)); app->rx_stream = furi_stream_buffer_alloc(2048, 1); @@ -220,7 +222,7 @@ static UartEchoApp* uart_echo_app_alloc() { // Enable uart listener furi_hal_console_disable(); - furi_hal_uart_set_br(FuriHalUartIdUSART1, 115200); + furi_hal_uart_set_br(FuriHalUartIdUSART1, baudrate); furi_hal_uart_set_irq_cb(FuriHalUartIdUSART1, uart_echo_on_irq_cb, app); return app; @@ -263,8 +265,18 @@ static void uart_echo_app_free(UartEchoApp* app) { } int32_t uart_echo_app(void* p) { - UNUSED(p); - UartEchoApp* app = uart_echo_app_alloc(); + uint32_t baudrate = DEFAULT_BAUD_RATE; + if(p) { + const char* baudrate_str = p; + if(sscanf(baudrate_str, "%lu", &baudrate) != 1) { + FURI_LOG_E(TAG, "Invalid baudrate: %s", baudrate_str); + baudrate = DEFAULT_BAUD_RATE; + } + } + + FURI_LOG_I(TAG, "Using baudrate: %lu", baudrate); + + UartEchoApp* app = uart_echo_app_alloc(baudrate); view_dispatcher_run(app->view_dispatcher); uart_echo_app_free(app); return 0; diff --git a/applications/external/music_player/application.fam b/applications/external/music_player/application.fam index 3414c0a482d..c9cd5e44de7 100644 --- a/applications/external/music_player/application.fam +++ b/applications/external/music_player/application.fam @@ -7,18 +7,10 @@ App( "gui", "dialogs", ], - provides=["music_player_start"], stack_size=2 * 1024, order=20, fap_icon="icons/music_10px.png", fap_category="Media", fap_icon_assets="icons", -) - -App( - appid="music_player_start", - apptype=FlipperAppType.STARTUP, - entry_point="music_player_on_system_start", - requires=["music_player"], - order=30, + fap_libs=["music_worker"], ) diff --git a/applications/external/music_player/music_player.c b/applications/external/music_player/music_player.c index 2380d7d17be..8b0b758c1c1 100644 --- a/applications/external/music_player/music_player.c +++ b/applications/external/music_player/music_player.c @@ -1,4 +1,4 @@ -#include "music_player_worker.h" +#include #include #include @@ -34,7 +34,7 @@ typedef struct { ViewPort* view_port; Gui* gui; - MusicPlayerWorker* worker; + MusicWorker* worker; } MusicPlayer; static const float MUSIC_PLAYER_VOLUMES[] = {0, .25, .5, .75, 1}; @@ -218,7 +218,7 @@ static void input_callback(InputEvent* input_event, void* ctx) { } } -static void music_player_worker_callback( +static void music_worker_callback( uint8_t semitone, uint8_t dots, uint8_t duration, @@ -250,7 +250,7 @@ static void music_player_worker_callback( void music_player_clear(MusicPlayer* instance) { memset(instance->model->duration_history, 0xff, MUSIC_PLAYER_SEMITONE_HISTORY_SIZE); memset(instance->model->semitone_history, 0xff, MUSIC_PLAYER_SEMITONE_HISTORY_SIZE); - music_player_worker_clear(instance->worker); + music_worker_clear(instance->worker); } MusicPlayer* music_player_alloc() { @@ -263,10 +263,9 @@ MusicPlayer* music_player_alloc() { instance->input_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); - instance->worker = music_player_worker_alloc(); - music_player_worker_set_volume( - instance->worker, MUSIC_PLAYER_VOLUMES[instance->model->volume]); - music_player_worker_set_callback(instance->worker, music_player_worker_callback, instance); + instance->worker = music_worker_alloc(); + music_worker_set_volume(instance->worker, MUSIC_PLAYER_VOLUMES[instance->model->volume]); + music_worker_set_callback(instance->worker, music_worker_callback, instance); music_player_clear(instance); @@ -286,7 +285,7 @@ void music_player_free(MusicPlayer* instance) { furi_record_close(RECORD_GUI); view_port_free(instance->view_port); - music_player_worker_free(instance->worker); + music_worker_free(instance->worker); furi_message_queue_free(instance->input_queue); @@ -330,12 +329,12 @@ int32_t music_player_app(void* p) { } } - if(!music_player_worker_load(music_player->worker, furi_string_get_cstr(file_path))) { + if(!music_worker_load(music_player->worker, furi_string_get_cstr(file_path))) { FURI_LOG_E(TAG, "Unable to load file"); break; } - music_player_worker_start(music_player->worker); + music_worker_start(music_player->worker); InputEvent input; while(furi_message_queue_get(music_player->input_queue, &input, FuriWaitForever) == @@ -349,11 +348,11 @@ int32_t music_player_app(void* p) { } else if(input.key == InputKeyUp) { if(music_player->model->volume < COUNT_OF(MUSIC_PLAYER_VOLUMES) - 1) music_player->model->volume++; - music_player_worker_set_volume( + music_worker_set_volume( music_player->worker, MUSIC_PLAYER_VOLUMES[music_player->model->volume]); } else if(input.key == InputKeyDown) { if(music_player->model->volume > 0) music_player->model->volume--; - music_player_worker_set_volume( + music_worker_set_volume( music_player->worker, MUSIC_PLAYER_VOLUMES[music_player->model->volume]); } @@ -361,7 +360,7 @@ int32_t music_player_app(void* p) { view_port_update(music_player->view_port); } - music_player_worker_stop(music_player->worker); + music_worker_stop(music_player->worker); if(p && strlen(p)) break; // Exit instead of going to browser if launched with arg music_player_clear(music_player); } while(1); diff --git a/applications/external/music_player/music_player_cli.c b/applications/external/music_player/music_player_cli.c deleted file mode 100644 index 90060d7ee00..00000000000 --- a/applications/external/music_player/music_player_cli.c +++ /dev/null @@ -1,48 +0,0 @@ -#include -#include -#include -#include "music_player_worker.h" - -static void music_player_cli(Cli* cli, FuriString* args, void* context) { - UNUSED(context); - MusicPlayerWorker* music_player_worker = music_player_worker_alloc(); - Storage* storage = furi_record_open(RECORD_STORAGE); - - do { - if(storage_common_stat(storage, furi_string_get_cstr(args), NULL) == FSE_OK) { - if(!music_player_worker_load(music_player_worker, furi_string_get_cstr(args))) { - printf("Failed to open file %s\r\n", furi_string_get_cstr(args)); - break; - } - } else { - if(!music_player_worker_load_rtttl_from_string( - music_player_worker, furi_string_get_cstr(args))) { - printf("Argument is not a file or RTTTL\r\n"); - break; - } - } - - printf("Press CTRL+C to stop\r\n"); - music_player_worker_set_volume(music_player_worker, 1.0f); - music_player_worker_start(music_player_worker); - while(!cli_cmd_interrupt_received(cli)) { - furi_delay_ms(50); - } - music_player_worker_stop(music_player_worker); - } while(0); - - furi_record_close(RECORD_STORAGE); - music_player_worker_free(music_player_worker); -} - -void music_player_on_system_start() { -#ifdef SRV_CLI - Cli* cli = furi_record_open(RECORD_CLI); - - cli_add_command(cli, "music_player", CliCommandFlagDefault, music_player_cli, NULL); - - furi_record_close(RECORD_CLI); -#else - UNUSED(music_player_cli); -#endif -} diff --git a/applications/external/music_player/music_player_worker.h b/applications/external/music_player/music_player_worker.h deleted file mode 100644 index 00320b11fe5..00000000000 --- a/applications/external/music_player/music_player_worker.h +++ /dev/null @@ -1,38 +0,0 @@ -#pragma once - -#include -#include - -typedef void (*MusicPlayerWorkerCallback)( - uint8_t semitone, - uint8_t dots, - uint8_t duration, - float position, - void* context); - -typedef struct MusicPlayerWorker MusicPlayerWorker; - -MusicPlayerWorker* music_player_worker_alloc(); - -void music_player_worker_clear(MusicPlayerWorker* instance); - -void music_player_worker_free(MusicPlayerWorker* instance); - -bool music_player_worker_load(MusicPlayerWorker* instance, const char* file_path); - -bool music_player_worker_load_fmf_from_file(MusicPlayerWorker* instance, const char* file_path); - -bool music_player_worker_load_rtttl_from_file(MusicPlayerWorker* instance, const char* file_path); - -bool music_player_worker_load_rtttl_from_string(MusicPlayerWorker* instance, const char* string); - -void music_player_worker_set_callback( - MusicPlayerWorker* instance, - MusicPlayerWorkerCallback callback, - void* context); - -void music_player_worker_set_volume(MusicPlayerWorker* instance, float volume); - -void music_player_worker_start(MusicPlayerWorker* instance); - -void music_player_worker_stop(MusicPlayerWorker* instance); diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index 2e176a5b5cd..a689d5a21b8 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,31.2,, +Version,+,31.3,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -128,6 +128,7 @@ Header,+,lib/mlib/m-list.h,, Header,+,lib/mlib/m-rbtree.h,, Header,+,lib/mlib/m-tuple.h,, Header,+,lib/mlib/m-variant.h,, +Header,+,lib/music_worker/music_worker.h,, Header,+,lib/one_wire/maxim_crc.h,, Header,+,lib/one_wire/one_wire_host.h,, Header,+,lib/one_wire/one_wire_slave.h,, @@ -1519,6 +1520,18 @@ Function,-,mkstemps,int,"char*, int" Function,-,mktemp,char*,char* Function,-,mktime,time_t,tm* Function,-,mrand48,long, +Function,-,music_worker_alloc,MusicWorker*, +Function,-,music_worker_clear,void,MusicWorker* +Function,-,music_worker_free,void,MusicWorker* +Function,-,music_worker_is_playing,_Bool,MusicWorker* +Function,-,music_worker_load,_Bool,"MusicWorker*, const char*" +Function,-,music_worker_load_fmf_from_file,_Bool,"MusicWorker*, const char*" +Function,-,music_worker_load_rtttl_from_file,_Bool,"MusicWorker*, const char*" +Function,-,music_worker_load_rtttl_from_string,_Bool,"MusicWorker*, const char*" +Function,-,music_worker_set_callback,void,"MusicWorker*, MusicWorkerCallback, void*" +Function,-,music_worker_set_volume,void,"MusicWorker*, float" +Function,-,music_worker_start,void,MusicWorker* +Function,-,music_worker_stop,void,MusicWorker* Function,+,notification_internal_message,void,"NotificationApp*, const NotificationSequence*" Function,+,notification_internal_message_block,void,"NotificationApp*, const NotificationSequence*" Function,+,notification_message,void,"NotificationApp*, const NotificationSequence*" diff --git a/firmware/targets/f18/target.json b/firmware/targets/f18/target.json index 14d395d2227..2d14813f6da 100644 --- a/firmware/targets/f18/target.json +++ b/firmware/targets/f18/target.json @@ -25,6 +25,7 @@ "appframe", "assets", "one_wire", + "music_worker", "misc", "flipper_application", "flipperformat", diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index ac2b11f3801..0183540f657 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,31.2,, +Version,+,31.3,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -146,6 +146,7 @@ Header,+,lib/mlib/m-list.h,, Header,+,lib/mlib/m-rbtree.h,, Header,+,lib/mlib/m-tuple.h,, Header,+,lib/mlib/m-variant.h,, +Header,+,lib/music_worker/music_worker.h,, Header,+,lib/nfc/nfc_device.h,, Header,+,lib/nfc/protocols/nfc_util.h,, Header,+,lib/one_wire/maxim_crc.h,, @@ -1220,12 +1221,12 @@ Function,+,furi_hal_mpu_protect_disable,void,FuriHalMpuRegion Function,+,furi_hal_mpu_protect_no_access,void,"FuriHalMpuRegion, uint32_t, FuriHalMPURegionSize" Function,+,furi_hal_mpu_protect_read_only,void,"FuriHalMpuRegion, uint32_t, FuriHalMPURegionSize" Function,+,furi_hal_nfc_activate_nfca,_Bool,"uint32_t, uint32_t*" -Function,+,furi_hal_nfc_field_is_present,_Bool, -Function,+,furi_hal_nfc_field_detect_start,void, Function,-,furi_hal_nfc_deinit,void, Function,+,furi_hal_nfc_detect,_Bool,"FuriHalNfcDevData*, uint32_t" Function,+,furi_hal_nfc_emulate_nfca,_Bool,"uint8_t*, uint8_t, uint8_t*, uint8_t, FuriHalNfcEmulateCallback, void*, uint32_t" Function,+,furi_hal_nfc_exit_sleep,void, +Function,+,furi_hal_nfc_field_detect_start,void, +Function,+,furi_hal_nfc_field_is_present,_Bool, Function,+,furi_hal_nfc_field_off,void, Function,+,furi_hal_nfc_field_on,void, Function,-,furi_hal_nfc_init,void, @@ -1307,9 +1308,9 @@ Function,-,furi_hal_resources_init_early,void, Function,+,furi_hal_rfid_comp_set_callback,void,"FuriHalRfidCompCallback, void*" Function,+,furi_hal_rfid_comp_start,void, Function,+,furi_hal_rfid_comp_stop,void, -Function,+,furi_hal_rfid_field_is_present,_Bool,uint32_t* Function,+,furi_hal_rfid_field_detect_start,void, Function,+,furi_hal_rfid_field_detect_stop,void, +Function,+,furi_hal_rfid_field_is_present,_Bool,uint32_t* Function,-,furi_hal_rfid_init,void, Function,+,furi_hal_rfid_pin_pull_pulldown,void, Function,+,furi_hal_rfid_pin_pull_release,void, @@ -2063,6 +2064,18 @@ Function,-,modf,double,"double, double*" Function,-,modff,float,"float, float*" Function,-,modfl,long double,"long double, long double*" Function,-,mrand48,long, +Function,-,music_worker_alloc,MusicWorker*, +Function,-,music_worker_clear,void,MusicWorker* +Function,-,music_worker_free,void,MusicWorker* +Function,-,music_worker_is_playing,_Bool,MusicWorker* +Function,-,music_worker_load,_Bool,"MusicWorker*, const char*" +Function,-,music_worker_load_fmf_from_file,_Bool,"MusicWorker*, const char*" +Function,-,music_worker_load_rtttl_from_file,_Bool,"MusicWorker*, const char*" +Function,-,music_worker_load_rtttl_from_string,_Bool,"MusicWorker*, const char*" +Function,-,music_worker_set_callback,void,"MusicWorker*, MusicWorkerCallback, void*" +Function,-,music_worker_set_volume,void,"MusicWorker*, float" +Function,-,music_worker_start,void,MusicWorker* +Function,-,music_worker_stop,void,MusicWorker* Function,-,nan,double,const char* Function,-,nanf,float,const char* Function,-,nanl,long double,const char* diff --git a/firmware/targets/f7/target.json b/firmware/targets/f7/target.json index e3dc78325e5..9bb87000c70 100644 --- a/firmware/targets/f7/target.json +++ b/firmware/targets/f7/target.json @@ -38,6 +38,7 @@ "assets", "one_wire", "ibutton", + "music_worker", "misc", "mbedtls", "lfrfid", diff --git a/lib/SConscript b/lib/SConscript index 495ba4bfe24..ab78c6ea4d4 100644 --- a/lib/SConscript +++ b/lib/SConscript @@ -15,6 +15,7 @@ env.Append( Dir("u8g2"), Dir("update_util"), Dir("print"), + Dir("music_worker"), ], ) @@ -100,6 +101,7 @@ libs = env.BuildModules( "misc", "lfrfid", "flipper_application", + "music_worker", ], ) diff --git a/lib/music_worker/SConscript b/lib/music_worker/SConscript new file mode 100644 index 00000000000..36d01d8596a --- /dev/null +++ b/lib/music_worker/SConscript @@ -0,0 +1,27 @@ +Import("env") + +env.Append( + CPPPATH=[ + "#/lib/music_worker", + ], + SDK_HEADERS=[ + File("music_worker.h"), + ], +) + +libenv = env.Clone(FW_LIB_NAME="music_worker") +libenv.ApplyLibFlags() + +libenv.AppendUnique( + CCFLAGS=[ + # Required for lib to be linkable with .faps + "-mword-relocations", + "-mlong-calls", + ], +) + +sources = libenv.GlobRecursive("*.c*") + +lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources) +libenv.Install("${LIB_DIST_DIR}", lib) +Return("lib") diff --git a/applications/external/music_player/music_player_worker.c b/lib/music_worker/music_worker.c similarity index 85% rename from applications/external/music_player/music_player_worker.c rename to lib/music_worker/music_worker.c index ee350ee8045..61fc838f2e3 100644 --- a/applications/external/music_player/music_player_worker.c +++ b/lib/music_worker/music_worker.c @@ -1,4 +1,4 @@ -#include "music_player_worker.h" +#include "music_worker.h" #include #include @@ -9,7 +9,7 @@ #include #include -#define TAG "MusicPlayerWorker" +#define TAG "MusicWorker" #define MUSIC_PLAYER_FILETYPE "Flipper Music Format" #define MUSIC_PLAYER_VERSION 0 @@ -28,11 +28,11 @@ typedef struct { ARRAY_DEF(NoteBlockArray, NoteBlock, M_POD_OPLIST); -struct MusicPlayerWorker { +struct MusicWorker { FuriThread* thread; bool should_work; - MusicPlayerWorkerCallback callback; + MusicWorkerCallback callback; void* callback_context; float volume; @@ -42,9 +42,9 @@ struct MusicPlayerWorker { NoteBlockArray_t notes; }; -static int32_t music_player_worker_thread_callback(void* context) { +static int32_t music_worker_thread_callback(void* context) { furi_assert(context); - MusicPlayerWorker* instance = context; + MusicWorker* instance = context; NoteBlockArray_it_t it; NoteBlockArray_it(it, instance->notes); @@ -97,24 +97,24 @@ static int32_t music_player_worker_thread_callback(void* context) { return 0; } -MusicPlayerWorker* music_player_worker_alloc() { - MusicPlayerWorker* instance = malloc(sizeof(MusicPlayerWorker)); +MusicWorker* music_worker_alloc() { + MusicWorker* instance = malloc(sizeof(MusicWorker)); NoteBlockArray_init(instance->notes); - instance->thread = furi_thread_alloc_ex( - "MusicPlayerWorker", 1024, music_player_worker_thread_callback, instance); + instance->thread = + furi_thread_alloc_ex("MusicWorker", 1024, music_worker_thread_callback, instance); instance->volume = 1.0f; return instance; } -void music_player_worker_clear(MusicPlayerWorker* instance) { +void music_worker_clear(MusicWorker* instance) { NoteBlockArray_reset(instance->notes); } -void music_player_worker_free(MusicPlayerWorker* instance) { +void music_worker_free(MusicWorker* instance) { furi_assert(instance); furi_thread_free(instance->thread); NoteBlockArray_clear(instance->notes); @@ -186,11 +186,8 @@ static size_t skip_till(const char* string, const char symbol) { return ret; } -static bool music_player_worker_add_note( - MusicPlayerWorker* instance, - uint8_t semitone, - uint8_t duration, - uint8_t dots) { +static bool + music_worker_add_note(MusicWorker* instance, uint8_t semitone, uint8_t duration, uint8_t dots) { NoteBlock note_block; note_block.semitone = semitone; @@ -228,7 +225,7 @@ static int8_t note_to_semitone(const char note) { } } -static bool music_player_worker_parse_notes(MusicPlayerWorker* instance, const char* string) { +static bool music_worker_parse_notes(MusicWorker* instance, const char* string) { const char* cursor = string; bool result = true; @@ -286,7 +283,7 @@ static bool music_player_worker_parse_notes(MusicPlayerWorker* instance, const c semitone += sharp_char == '#' ? 1 : 0; } - if(music_player_worker_add_note(instance, semitone, duration, dots)) { + if(music_worker_add_note(instance, semitone, duration, dots)) { FURI_LOG_D( TAG, "Added note: %c%c%lu.%lu = %u %lu", @@ -316,20 +313,20 @@ static bool music_player_worker_parse_notes(MusicPlayerWorker* instance, const c return result; } -bool music_player_worker_load(MusicPlayerWorker* instance, const char* file_path) { +bool music_worker_load(MusicWorker* instance, const char* file_path) { furi_assert(instance); furi_assert(file_path); bool ret = false; if(strcasestr(file_path, ".fmf")) { - ret = music_player_worker_load_fmf_from_file(instance, file_path); + ret = music_worker_load_fmf_from_file(instance, file_path); } else { - ret = music_player_worker_load_rtttl_from_file(instance, file_path); + ret = music_worker_load_rtttl_from_file(instance, file_path); } return ret; } -bool music_player_worker_load_fmf_from_file(MusicPlayerWorker* instance, const char* file_path) { +bool music_worker_load_fmf_from_file(MusicWorker* instance, const char* file_path) { furi_assert(instance); furi_assert(file_path); @@ -369,7 +366,7 @@ bool music_player_worker_load_fmf_from_file(MusicPlayerWorker* instance, const c break; } - if(!music_player_worker_parse_notes(instance, furi_string_get_cstr(temp_str))) { + if(!music_worker_parse_notes(instance, furi_string_get_cstr(temp_str))) { break; } @@ -383,7 +380,7 @@ bool music_player_worker_load_fmf_from_file(MusicPlayerWorker* instance, const c return result; } -bool music_player_worker_load_rtttl_from_file(MusicPlayerWorker* instance, const char* file_path) { +bool music_worker_load_rtttl_from_file(MusicWorker* instance, const char* file_path) { furi_assert(instance); furi_assert(file_path); @@ -414,7 +411,7 @@ bool music_player_worker_load_rtttl_from_file(MusicPlayerWorker* instance, const break; } - if(!music_player_worker_load_rtttl_from_string(instance, furi_string_get_cstr(content))) { + if(!music_worker_load_rtttl_from_string(instance, furi_string_get_cstr(content))) { FURI_LOG_E(TAG, "Invalid file content"); break; } @@ -429,7 +426,7 @@ bool music_player_worker_load_rtttl_from_file(MusicPlayerWorker* instance, const return result; } -bool music_player_worker_load_rtttl_from_string(MusicPlayerWorker* instance, const char* string) { +bool music_worker_load_rtttl_from_string(MusicWorker* instance, const char* string) { furi_assert(instance); const char* cursor = string; @@ -470,28 +467,25 @@ bool music_player_worker_load_rtttl_from_string(MusicPlayerWorker* instance, con return false; } cursor++; - if(!music_player_worker_parse_notes(instance, cursor)) { + if(!music_worker_parse_notes(instance, cursor)) { return false; } return true; } -void music_player_worker_set_callback( - MusicPlayerWorker* instance, - MusicPlayerWorkerCallback callback, - void* context) { +void music_worker_set_callback(MusicWorker* instance, MusicWorkerCallback callback, void* context) { furi_assert(instance); instance->callback = callback; instance->callback_context = context; } -void music_player_worker_set_volume(MusicPlayerWorker* instance, float volume) { +void music_worker_set_volume(MusicWorker* instance, float volume) { furi_assert(instance); instance->volume = volume; } -void music_player_worker_start(MusicPlayerWorker* instance) { +void music_worker_start(MusicWorker* instance) { furi_assert(instance); furi_assert(instance->should_work == false); @@ -499,10 +493,15 @@ void music_player_worker_start(MusicPlayerWorker* instance) { furi_thread_start(instance->thread); } -void music_player_worker_stop(MusicPlayerWorker* instance) { +void music_worker_stop(MusicWorker* instance) { furi_assert(instance); furi_assert(instance->should_work == true); instance->should_work = false; furi_thread_join(instance->thread); } + +bool music_worker_is_playing(MusicWorker* instance) { + furi_assert(instance); + return instance->should_work; +} diff --git a/lib/music_worker/music_worker.h b/lib/music_worker/music_worker.h new file mode 100644 index 00000000000..5a7cb4936a9 --- /dev/null +++ b/lib/music_worker/music_worker.h @@ -0,0 +1,37 @@ +#pragma once + +#include +#include + +typedef void (*MusicWorkerCallback)( + uint8_t semitone, + uint8_t dots, + uint8_t duration, + float position, + void* context); + +typedef struct MusicWorker MusicWorker; + +MusicWorker* music_worker_alloc(); + +void music_worker_clear(MusicWorker* instance); + +void music_worker_free(MusicWorker* instance); + +bool music_worker_load(MusicWorker* instance, const char* file_path); + +bool music_worker_load_fmf_from_file(MusicWorker* instance, const char* file_path); + +bool music_worker_load_rtttl_from_file(MusicWorker* instance, const char* file_path); + +bool music_worker_load_rtttl_from_string(MusicWorker* instance, const char* string); + +void music_worker_set_callback(MusicWorker* instance, MusicWorkerCallback callback, void* context); + +void music_worker_set_volume(MusicWorker* instance, float volume); + +void music_worker_start(MusicWorker* instance); + +void music_worker_stop(MusicWorker* instance); + +bool music_worker_is_playing(MusicWorker* instance); From 5d40193308471914ab3bb9f63bd7969ef8ade3dd Mon Sep 17 00:00:00 2001 From: Konstantin Volkov <72250702+doomwastaken@users.noreply.github.com> Date: Thu, 29 Jun 2023 22:41:35 +0300 Subject: [PATCH 634/824] increased timeouts (#2816) Co-authored-by: doomwastaken --- .github/workflows/unit_tests.yml | 6 +++--- .github/workflows/updater_test.yml | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 4cb112c7709..9c6c6b2db6f 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -29,7 +29,7 @@ jobs: - name: 'Flash unit tests firmware' id: flashing if: success() - timeout-minutes: 5 + timeout-minutes: 10 run: | ./fbt flash OPENOCD_ADAPTER_SERIAL=2A0906016415303030303032 FIRMWARE_APP_SET=unit_tests FORCE=1 @@ -45,7 +45,7 @@ jobs: - name: 'Copy assets and unit data, reboot and wait for flipper' id: copy if: steps.format_ext.outcome == 'success' - timeout-minutes: 3 + timeout-minutes: 5 run: | source scripts/toolchain/fbtenv.sh python3 scripts/storage.py -p ${{steps.device.outputs.flipper}} -f send assets/resources /ext @@ -56,7 +56,7 @@ jobs: - name: 'Run units and validate results' id: run_units if: steps.copy.outcome == 'success' - timeout-minutes: 5 + timeout-minutes: 7 run: | source scripts/toolchain/fbtenv.sh python3 scripts/testing/units.py ${{steps.device.outputs.flipper}} diff --git a/.github/workflows/updater_test.yml b/.github/workflows/updater_test.yml index 1d383d9eb30..27a181c4612 100644 --- a/.github/workflows/updater_test.yml +++ b/.github/workflows/updater_test.yml @@ -30,7 +30,7 @@ jobs: - name: 'Flashing target firmware' id: first_full_flash - timeout-minutes: 5 + timeout-minutes: 10 run: | source scripts/toolchain/fbtenv.sh ./fbt flash_usb_full PORT=${{steps.device.outputs.flipper}} FORCE=1 @@ -38,7 +38,7 @@ jobs: - name: 'Validating updater' id: second_full_flash - timeout-minutes: 5 + timeout-minutes: 10 if: success() run: | source scripts/toolchain/fbtenv.sh From 6d9de2549432a27367bb89d3842809b17a24d6bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Fri, 30 Jun 2023 18:52:43 +0900 Subject: [PATCH 635/824] Furi,FuriHal: various improvements (#2819) * Lib: adjust default contrast for ERC displays * Furi: various improvements in check module * Format Sources * FurHal: ble early hardfault detection --------- Co-authored-by: hedger --- applications/debug/crash_test/application.fam | 10 ++ applications/debug/crash_test/crash_test.c | 128 ++++++++++++++++++ firmware/targets/f7/furi_hal/furi_hal_bt.c | 58 +++++--- furi/core/check.c | 6 +- furi/core/check.h | 47 +++++-- lib/u8g2/u8g2_glue.c | 2 +- 6 files changed, 218 insertions(+), 33 deletions(-) create mode 100644 applications/debug/crash_test/application.fam create mode 100644 applications/debug/crash_test/crash_test.c diff --git a/applications/debug/crash_test/application.fam b/applications/debug/crash_test/application.fam new file mode 100644 index 00000000000..55f62f86d89 --- /dev/null +++ b/applications/debug/crash_test/application.fam @@ -0,0 +1,10 @@ +App( + appid="crash_test", + name="Crash Test", + apptype=FlipperAppType.DEBUG, + entry_point="crash_test_app", + cdefines=["APP_CRASH_TEST"], + requires=["gui"], + stack_size=1 * 1024, + fap_category="Debug", +) diff --git a/applications/debug/crash_test/crash_test.c b/applications/debug/crash_test/crash_test.c new file mode 100644 index 00000000000..92f1668be9f --- /dev/null +++ b/applications/debug/crash_test/crash_test.c @@ -0,0 +1,128 @@ +#include +#include + +#include +#include +#include + +#define TAG "CrashTest" + +typedef struct { + Gui* gui; + ViewDispatcher* view_dispatcher; + Submenu* submenu; +} CrashTest; + +typedef enum { + CrashTestViewSubmenu, +} CrashTestView; + +typedef enum { + CrashTestSubmenuCheck, + CrashTestSubmenuCheckMessage, + CrashTestSubmenuAssert, + CrashTestSubmenuAssertMessage, + CrashTestSubmenuCrash, + CrashTestSubmenuHalt, +} CrashTestSubmenu; + +static void crash_test_submenu_callback(void* context, uint32_t index) { + CrashTest* instance = (CrashTest*)context; + UNUSED(instance); + + switch(index) { + case CrashTestSubmenuCheck: + furi_check(false); + break; + case CrashTestSubmenuCheckMessage: + furi_check(false, "Crash test: furi_check with message"); + break; + case CrashTestSubmenuAssert: + furi_assert(false); + break; + case CrashTestSubmenuAssertMessage: + furi_assert(false, "Crash test: furi_assert with message"); + break; + case CrashTestSubmenuCrash: + furi_crash("Crash test: furi_crash"); + break; + case CrashTestSubmenuHalt: + furi_halt("Crash test: furi_halt"); + break; + default: + furi_crash("Programming error"); + } +} + +static uint32_t crash_test_exit_callback(void* context) { + UNUSED(context); + return VIEW_NONE; +} + +CrashTest* crash_test_alloc() { + CrashTest* instance = malloc(sizeof(CrashTest)); + + View* view = NULL; + + instance->gui = furi_record_open(RECORD_GUI); + instance->view_dispatcher = view_dispatcher_alloc(); + view_dispatcher_enable_queue(instance->view_dispatcher); + view_dispatcher_attach_to_gui( + instance->view_dispatcher, instance->gui, ViewDispatcherTypeFullscreen); + + // Menu + instance->submenu = submenu_alloc(); + view = submenu_get_view(instance->submenu); + view_set_previous_callback(view, crash_test_exit_callback); + view_dispatcher_add_view(instance->view_dispatcher, CrashTestViewSubmenu, view); + submenu_add_item( + instance->submenu, "Check", CrashTestSubmenuCheck, crash_test_submenu_callback, instance); + submenu_add_item( + instance->submenu, + "Check with message", + CrashTestSubmenuCheckMessage, + crash_test_submenu_callback, + instance); + submenu_add_item( + instance->submenu, "Assert", CrashTestSubmenuAssert, crash_test_submenu_callback, instance); + submenu_add_item( + instance->submenu, + "Assert with message", + CrashTestSubmenuAssertMessage, + crash_test_submenu_callback, + instance); + submenu_add_item( + instance->submenu, "Crash", CrashTestSubmenuCrash, crash_test_submenu_callback, instance); + submenu_add_item( + instance->submenu, "Halt", CrashTestSubmenuHalt, crash_test_submenu_callback, instance); + + return instance; +} + +void crash_test_free(CrashTest* instance) { + view_dispatcher_remove_view(instance->view_dispatcher, CrashTestViewSubmenu); + submenu_free(instance->submenu); + + view_dispatcher_free(instance->view_dispatcher); + furi_record_close(RECORD_GUI); + + free(instance); +} + +int32_t crash_test_run(CrashTest* instance) { + view_dispatcher_switch_to_view(instance->view_dispatcher, CrashTestViewSubmenu); + view_dispatcher_run(instance->view_dispatcher); + return 0; +} + +int32_t crash_test_app(void* p) { + UNUSED(p); + + CrashTest* instance = crash_test_alloc(); + + int32_t ret = crash_test_run(instance); + + crash_test_free(instance); + + return ret; +} diff --git a/firmware/targets/f7/furi_hal/furi_hal_bt.c b/firmware/targets/f7/furi_hal/furi_hal_bt.c index 6ff9f0e3b1f..57aee0bf2a2 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_bt.c +++ b/firmware/targets/f7/furi_hal/furi_hal_bt.c @@ -21,8 +21,17 @@ #define FURI_HAL_BT_HARDFAULT_INFO_MAGIC 0x1170FD0F -FuriMutex* furi_hal_bt_core2_mtx = NULL; -static FuriHalBtStack furi_hal_bt_stack = FuriHalBtStackUnknown; +typedef struct { + FuriMutex* core2_mtx; + FuriTimer* hardfault_check_timer; + FuriHalBtStack stack; +} FuriHalBt; + +static FuriHalBt furi_hal_bt = { + .core2_mtx = NULL, + .hardfault_check_timer = NULL, + .stack = FuriHalBtStackUnknown, +}; typedef void (*FuriHalBtProfileStart)(void); typedef void (*FuriHalBtProfileStop)(void); @@ -79,6 +88,13 @@ FuriHalBtProfileConfig profile_config[FuriHalBtProfileNumber] = { }; FuriHalBtProfileConfig* current_profile = NULL; +static void furi_hal_bt_hardfault_check(void* context) { + UNUSED(context); + if(furi_hal_bt_get_hardfault_info()) { + furi_crash("ST(R) Copro(R) HardFault"); + } +} + void furi_hal_bt_init() { furi_hal_bus_enable(FuriHalBusHSEM); furi_hal_bus_enable(FuriHalBusIPCC); @@ -86,9 +102,15 @@ void furi_hal_bt_init() { furi_hal_bus_enable(FuriHalBusPKA); furi_hal_bus_enable(FuriHalBusCRC); - if(!furi_hal_bt_core2_mtx) { - furi_hal_bt_core2_mtx = furi_mutex_alloc(FuriMutexTypeNormal); - furi_assert(furi_hal_bt_core2_mtx); + if(!furi_hal_bt.core2_mtx) { + furi_hal_bt.core2_mtx = furi_mutex_alloc(FuriMutexTypeNormal); + furi_assert(furi_hal_bt.core2_mtx); + } + + if(!furi_hal_bt.hardfault_check_timer) { + furi_hal_bt.hardfault_check_timer = + furi_timer_alloc(furi_hal_bt_hardfault_check, FuriTimerTypePeriodic, NULL); + furi_timer_start(furi_hal_bt.hardfault_check_timer, 5000); } // Explicitly tell that we are in charge of CLK48 domain @@ -99,13 +121,13 @@ void furi_hal_bt_init() { } void furi_hal_bt_lock_core2() { - furi_assert(furi_hal_bt_core2_mtx); - furi_check(furi_mutex_acquire(furi_hal_bt_core2_mtx, FuriWaitForever) == FuriStatusOk); + furi_assert(furi_hal_bt.core2_mtx); + furi_check(furi_mutex_acquire(furi_hal_bt.core2_mtx, FuriWaitForever) == FuriStatusOk); } void furi_hal_bt_unlock_core2() { - furi_assert(furi_hal_bt_core2_mtx); - furi_check(furi_mutex_release(furi_hal_bt_core2_mtx) == FuriStatusOk); + furi_assert(furi_hal_bt.core2_mtx); + furi_check(furi_mutex_release(furi_hal_bt.core2_mtx) == FuriStatusOk); } static bool furi_hal_bt_radio_stack_is_supported(const BleGlueC2Info* info) { @@ -113,26 +135,26 @@ static bool furi_hal_bt_radio_stack_is_supported(const BleGlueC2Info* info) { if(info->StackType == INFO_STACK_TYPE_BLE_LIGHT) { if(info->VersionMajor >= FURI_HAL_BT_STACK_VERSION_MAJOR && info->VersionMinor >= FURI_HAL_BT_STACK_VERSION_MINOR) { - furi_hal_bt_stack = FuriHalBtStackLight; + furi_hal_bt.stack = FuriHalBtStackLight; supported = true; } } else if(info->StackType == INFO_STACK_TYPE_BLE_FULL) { if(info->VersionMajor >= FURI_HAL_BT_STACK_VERSION_MAJOR && info->VersionMinor >= FURI_HAL_BT_STACK_VERSION_MINOR) { - furi_hal_bt_stack = FuriHalBtStackFull; + furi_hal_bt.stack = FuriHalBtStackFull; supported = true; } } else { - furi_hal_bt_stack = FuriHalBtStackUnknown; + furi_hal_bt.stack = FuriHalBtStackUnknown; } return supported; } bool furi_hal_bt_start_radio_stack() { bool res = false; - furi_assert(furi_hal_bt_core2_mtx); + furi_assert(furi_hal_bt.core2_mtx); - furi_mutex_acquire(furi_hal_bt_core2_mtx, FuriWaitForever); + furi_mutex_acquire(furi_hal_bt.core2_mtx, FuriWaitForever); // Explicitly tell that we are in charge of CLK48 domain furi_check(LL_HSEM_1StepLock(HSEM, CFG_HW_CLK48_CONFIG_SEMID) == 0); @@ -166,17 +188,17 @@ bool furi_hal_bt_start_radio_stack() { } res = true; } while(false); - furi_mutex_release(furi_hal_bt_core2_mtx); + furi_mutex_release(furi_hal_bt.core2_mtx); return res; } FuriHalBtStack furi_hal_bt_get_radio_stack() { - return furi_hal_bt_stack; + return furi_hal_bt.stack; } bool furi_hal_bt_is_ble_gatt_gap_supported() { - if(furi_hal_bt_stack == FuriHalBtStackLight || furi_hal_bt_stack == FuriHalBtStackFull) { + if(furi_hal_bt.stack == FuriHalBtStackLight || furi_hal_bt.stack == FuriHalBtStackFull) { return true; } else { return false; @@ -184,7 +206,7 @@ bool furi_hal_bt_is_ble_gatt_gap_supported() { } bool furi_hal_bt_is_testing_supported() { - if(furi_hal_bt_stack == FuriHalBtStackFull) { + if(furi_hal_bt.stack == FuriHalBtStackFull) { return true; } else { return false; diff --git a/furi/core/check.c b/furi/core/check.c index 478f3aacccf..c5c4ef1a4af 100644 --- a/furi/core/check.c +++ b/furi/core/check.c @@ -166,7 +166,11 @@ FURI_NORETURN void __furi_crash() { RESTORE_REGISTERS_AND_HALT_MCU(true); #ifndef FURI_DEBUG } else { - furi_hal_rtc_set_fault_data((uint32_t)__furi_check_message); + uint32_t ptr = (uint32_t)__furi_check_message; + if(ptr < FLASH_BASE || ptr > (FLASH_BASE + FLASH_SIZE)) { + ptr = (uint32_t) "Check serial logs"; + } + furi_hal_rtc_set_fault_data(ptr); furi_hal_console_puts("\r\nRebooting system.\r\n"); furi_hal_console_puts("\033[0m\r\n"); furi_hal_power_reset(); diff --git a/furi/core/check.h b/furi/core/check.h index ea83f2219c2..004422e807e 100644 --- a/furi/core/check.h +++ b/furi/core/check.h @@ -13,6 +13,8 @@ */ #pragma once +#include + #ifdef __cplusplus extern "C" { #define FURI_NORETURN [[noreturn]] @@ -48,28 +50,47 @@ FURI_NORETURN void __furi_halt(); } while(0) /** Check condition and crash if check failed */ -#define furi_check(__e) \ - do { \ - if(!(__e)) { \ - furi_crash(__FURI_CHECK_MESSAGE_FLAG); \ - } \ +#define __furi_check(__e, __m) \ + do { \ + if(!(__e)) { \ + furi_crash(__m); \ + } \ } while(0) +/** Check condition and crash if failed + * + * @param condition to check + * @param optional message + */ +#define furi_check(...) \ + M_APPLY(__furi_check, M_DEFAULT_ARGS(2, (__FURI_CHECK_MESSAGE_FLAG), __VA_ARGS__)) + /** Only in debug build: Assert condition and crash if assert failed */ #ifdef FURI_DEBUG -#define furi_assert(__e) \ - do { \ - if(!(__e)) { \ - furi_crash(__FURI_ASSERT_MESSAGE_FLAG); \ - } \ +#define __furi_assert(__e, __m) \ + do { \ + if(!(__e)) { \ + furi_crash(__m); \ + } \ } while(0) #else -#define furi_assert(__e) \ - do { \ - ((void)(__e)); \ +#define __furi_assert(__e, __m) \ + do { \ + ((void)(__e)); \ + ((void)(__m)); \ } while(0) #endif +/** Assert condition and crash if failed + * + * @warning only will do check if firmware compiled in debug mode + * + * @param condition to check + * @param optional message + */ +#define furi_assert(...) \ + M_APPLY(__furi_assert, M_DEFAULT_ARGS(2, (__FURI_ASSERT_MESSAGE_FLAG), __VA_ARGS__)) + #ifdef __cplusplus } #endif diff --git a/lib/u8g2/u8g2_glue.c b/lib/u8g2/u8g2_glue.c index 0d4879bce5d..0142e3e2fdc 100644 --- a/lib/u8g2/u8g2_glue.c +++ b/lib/u8g2/u8g2_glue.c @@ -2,7 +2,7 @@ #include -#define CONTRAST_ERC 32 +#define CONTRAST_ERC 31 #define CONTRAST_MGG 31 uint8_t u8g2_gpio_and_delay_stm32(u8x8_t* u8x8, uint8_t msg, uint8_t arg_int, void* arg_ptr) { From 8c93695d01d3af4acbd3cb0d5bc756c30c0564dc Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Fri, 30 Jun 2023 18:03:36 +0400 Subject: [PATCH 636/824] [FL-3375] SubGhz: add CC1101 module external (#2747) * SubGhz: add CC1101 Ext driver * SubGhz: move TIM2 -> TIM17 use cc1101_ext * FuriHal: SPI move channel DMA 3,4 -> 6.7 * Documentation: fix font * SubGhz: add work with SubGhz devices by link to device * SubGhz: add support switching external/internal cc1101 "subghz chat" * SubGhz: add support switching external/internal cc1101 "subghz tx" and "subghz rx" * SubGhz: add "Radio Settings" scene * SubGhz: add icon * SubGhz: add supported CC1101 external module in SubGhz app * SubGhz: fix check frequency supported radio device * SubGhz: fix clang-formatted * Sughz: move dirver CC1101_Ext to lib , compile cmd ./fbt launch_app APPSRC=radio_device_cc1101_ext * SubGhz: fix CLI * SubGhz: fix PVS * SubGhz: delete comments * SubGhz: fix unit_test * Format sources * Update api symbols and drivers targets * Drivers: find proper place for target option * SubGhz: external device connected method naming * Format sources * SubGhz: fix module selection menu, when external is not connected * SubGhz: fix furi_assert(device); * SubGhz: fix split h and c * SubGhz: furi_hal_subghz remove preset load function by name * SubGhz: deleted comments * Format Sources * SubGhz: add some consts and fix unit tests * Sync API Symbols Co-authored-by: Aleksandr Kutuzov --- .../debug/unit_tests/subghz/subghz_test.c | 11 +- applications/drivers/application.fam | 6 + applications/drivers/subghz/application.fam | 8 + .../drivers/subghz/cc1101_ext/cc1101_ext.c | 765 ++++++++++++++++++ .../drivers/subghz/cc1101_ext/cc1101_ext.h | 206 +++++ .../cc1101_ext/cc1101_ext_interconnect.c | 110 +++ .../cc1101_ext/cc1101_ext_interconnect.h | 8 + .../main/subghz/helpers/subghz_chat.c | 7 +- .../main/subghz/helpers/subghz_chat.h | 6 +- .../subghz/helpers/subghz_threshold_rssi.c | 3 +- .../subghz/helpers/subghz_threshold_rssi.h | 3 +- .../main/subghz/helpers/subghz_txrx.c | 164 +++- .../main/subghz/helpers/subghz_txrx.h | 46 ++ .../main/subghz/helpers/subghz_txrx_i.h | 2 + .../main/subghz/helpers/subghz_types.h | 7 + .../main/subghz/scenes/subghz_scene_config.h | 1 + .../scenes/subghz_scene_radio_setting.c | 70 ++ .../subghz/scenes/subghz_scene_read_raw.c | 11 +- .../subghz/scenes/subghz_scene_receiver.c | 6 +- .../subghz/scenes/subghz_scene_save_name.c | 3 +- .../main/subghz/scenes/subghz_scene_start.c | 14 +- .../subghz/scenes/subghz_scene_transmitter.c | 2 + applications/main/subghz/subghz_cli.c | 215 +++-- applications/main/subghz/subghz_i.c | 5 +- applications/main/subghz/views/receiver.c | 44 +- applications/main/subghz/views/receiver.h | 4 + .../subghz/views/subghz_frequency_analyzer.c | 3 +- .../main/subghz/views/subghz_read_raw.c | 20 +- .../main/subghz/views/subghz_read_raw.h | 5 + .../main/subghz/views/subghz_test_carrier.c | 3 +- .../main/subghz/views/subghz_test_packet.c | 3 +- .../main/subghz/views/subghz_test_static.c | 3 +- applications/main/subghz/views/transmitter.c | 23 +- applications/main/subghz/views/transmitter.h | 5 + .../icons/SubGhz/External_antenna_20x12.png | Bin 0 -> 990 bytes .../icons/SubGhz/Internal_antenna_20x12.png | Bin 0 -> 994 bytes assets/icons/SubGhz/Scanning_123x52.png | Bin 1690 -> 0 bytes assets/icons/SubGhz/Scanning_short_96x52.png | Bin 0 -> 1388 bytes documentation/FuriHalBus.md | 16 +- firmware/targets/f18/api_symbols.csv | 3 +- firmware/targets/f7/api_symbols.csv | 73 +- firmware/targets/f7/furi_hal/furi_hal_spi.c | 28 +- .../targets/f7/furi_hal/furi_hal_subghz.c | 65 +- .../targets/f7/furi_hal/furi_hal_subghz.h | 51 +- .../f7/furi_hal/furi_hal_subghz_configs.h | 314 ------- .../f7/platform_specific/intrinsic_export.h | 2 + lib/subghz/SConscript | 1 + lib/subghz/devices/cc1101_configs.c | 431 ++++++++++ lib/subghz/devices/cc1101_configs.h | 18 + .../cc1101_int/cc1101_int_interconnect.c | 96 +++ .../cc1101_int/cc1101_int_interconnect.h | 8 + lib/subghz/devices/device_registry.h | 13 + lib/subghz/devices/devices.c | 236 ++++++ lib/subghz/devices/devices.h | 52 ++ lib/subghz/devices/preset.h | 13 + lib/subghz/devices/registry.c | 76 ++ lib/subghz/devices/registry.h | 40 + lib/subghz/devices/types.h | 91 +++ lib/subghz/protocols/raw.c | 25 +- lib/subghz/protocols/raw.h | 6 +- lib/subghz/subghz_file_encoder_worker.c | 13 +- lib/subghz/subghz_file_encoder_worker.h | 7 +- lib/subghz/subghz_setting.c | 32 +- lib/subghz/subghz_tx_rx_worker.c | 65 +- lib/subghz/subghz_tx_rx_worker.h | 7 +- lib/subghz/types.h | 6 + site_scons/commandline.scons | 1 + 67 files changed, 2931 insertions(+), 650 deletions(-) create mode 100644 applications/drivers/application.fam create mode 100644 applications/drivers/subghz/application.fam create mode 100644 applications/drivers/subghz/cc1101_ext/cc1101_ext.c create mode 100644 applications/drivers/subghz/cc1101_ext/cc1101_ext.h create mode 100644 applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.c create mode 100644 applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h create mode 100644 applications/main/subghz/scenes/subghz_scene_radio_setting.c create mode 100644 assets/icons/SubGhz/External_antenna_20x12.png create mode 100644 assets/icons/SubGhz/Internal_antenna_20x12.png delete mode 100644 assets/icons/SubGhz/Scanning_123x52.png create mode 100644 assets/icons/SubGhz/Scanning_short_96x52.png delete mode 100644 firmware/targets/f7/furi_hal/furi_hal_subghz_configs.h create mode 100644 lib/subghz/devices/cc1101_configs.c create mode 100644 lib/subghz/devices/cc1101_configs.h create mode 100644 lib/subghz/devices/cc1101_int/cc1101_int_interconnect.c create mode 100644 lib/subghz/devices/cc1101_int/cc1101_int_interconnect.h create mode 100644 lib/subghz/devices/device_registry.h create mode 100644 lib/subghz/devices/devices.c create mode 100644 lib/subghz/devices/devices.h create mode 100644 lib/subghz/devices/preset.h create mode 100644 lib/subghz/devices/registry.c create mode 100644 lib/subghz/devices/registry.h create mode 100644 lib/subghz/devices/types.h diff --git a/applications/debug/unit_tests/subghz/subghz_test.c b/applications/debug/unit_tests/subghz/subghz_test.c index f1ab926538f..6bdaa641e8d 100644 --- a/applications/debug/unit_tests/subghz/subghz_test.c +++ b/applications/debug/unit_tests/subghz/subghz_test.c @@ -7,6 +7,8 @@ #include #include #include +#include +#include #define TAG "SubGhz TEST" #define KEYSTORE_DIR_NAME EXT_PATH("subghz/assets/keeloq_mfcodes") @@ -49,12 +51,15 @@ static void subghz_test_init(void) { subghz_environment_set_protocol_registry( environment_handler, (void*)&subghz_protocol_registry); + subghz_devices_init(); + receiver_handler = subghz_receiver_alloc_init(environment_handler); subghz_receiver_set_filter(receiver_handler, SubGhzProtocolFlag_Decodable); subghz_receiver_set_rx_callback(receiver_handler, subghz_test_rx_callback, NULL); } static void subghz_test_deinit(void) { + subghz_devices_deinit(); subghz_receiver_free(receiver_handler); subghz_environment_free(environment_handler); } @@ -68,7 +73,7 @@ static bool subghz_decoder_test(const char* path, const char* name_decoder) { if(decoder) { file_worker_encoder_handler = subghz_file_encoder_worker_alloc(); - if(subghz_file_encoder_worker_start(file_worker_encoder_handler, path)) { + if(subghz_file_encoder_worker_start(file_worker_encoder_handler, path, NULL)) { // the worker needs a file in order to open and read part of the file furi_delay_ms(100); @@ -108,7 +113,7 @@ static bool subghz_decode_random_test(const char* path) { uint32_t test_start = furi_get_tick(); file_worker_encoder_handler = subghz_file_encoder_worker_alloc(); - if(subghz_file_encoder_worker_start(file_worker_encoder_handler, path)) { + if(subghz_file_encoder_worker_start(file_worker_encoder_handler, path, NULL)) { // the worker needs a file in order to open and read part of the file furi_delay_ms(100); @@ -318,7 +323,7 @@ bool subghz_hal_async_tx_test_run(SubGhzHalAsyncTxTestType type) { SubGhzHalAsyncTxTest test = {0}; test.type = type; furi_hal_subghz_reset(); - furi_hal_subghz_load_preset(FuriHalSubGhzPresetOok650Async); + furi_hal_subghz_load_custom_preset(subghz_device_cc1101_preset_ook_650khz_async_regs); furi_hal_subghz_set_frequency_and_path(433920000); if(!furi_hal_subghz_start_async_tx(subghz_hal_async_tx_test_yield, &test)) { diff --git a/applications/drivers/application.fam b/applications/drivers/application.fam new file mode 100644 index 00000000000..dc70e630c9f --- /dev/null +++ b/applications/drivers/application.fam @@ -0,0 +1,6 @@ +# Placeholder +App( + appid="drivers", + name="Drivers device", + apptype=FlipperAppType.METAPACKAGE, +) diff --git a/applications/drivers/subghz/application.fam b/applications/drivers/subghz/application.fam new file mode 100644 index 00000000000..aaf0e1bd94d --- /dev/null +++ b/applications/drivers/subghz/application.fam @@ -0,0 +1,8 @@ +App( + appid="radio_device_cc1101_ext", + apptype=FlipperAppType.PLUGIN, + targets=["f7"], + entry_point="subghz_device_cc1101_ext_ep", + requires=["subghz"], + fap_libs=["hwdrivers"], +) diff --git a/applications/drivers/subghz/cc1101_ext/cc1101_ext.c b/applications/drivers/subghz/cc1101_ext/cc1101_ext.c new file mode 100644 index 00000000000..896b9bd2f60 --- /dev/null +++ b/applications/drivers/subghz/cc1101_ext/cc1101_ext.c @@ -0,0 +1,765 @@ +#include "cc1101_ext.h" +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#define TAG "SubGhz_Device_CC1101_Ext" + +#define SUBGHZ_DEVICE_CC1101_EXT_TX_GPIO &gpio_ext_pb2 + +/* DMA Channels definition */ +#define SUBGHZ_DEVICE_CC1101_EXT_DMA DMA2 +#define SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_CHANNEL LL_DMA_CHANNEL_3 +#define SUBGHZ_DEVICE_CC1101_EXT_DMA_CH4_CHANNEL LL_DMA_CHANNEL_4 +#define SUBGHZ_DEVICE_CC1101_EXT_DMA_CH5_CHANNEL LL_DMA_CHANNEL_5 +#define SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_IRQ FuriHalInterruptIdDma2Ch3 +#define SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_DEF \ + SUBGHZ_DEVICE_CC1101_EXT_DMA, SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_CHANNEL +#define SUBGHZ_DEVICE_CC1101_EXT_DMA_CH4_DEF \ + SUBGHZ_DEVICE_CC1101_EXT_DMA, SUBGHZ_DEVICE_CC1101_EXT_DMA_CH4_CHANNEL +#define SUBGHZ_DEVICE_CC1101_EXT_DMA_CH5_DEF \ + SUBGHZ_DEVICE_CC1101_EXT_DMA, SUBGHZ_DEVICE_CC1101_EXT_DMA_CH5_CHANNEL + +/** Low level buffer dimensions and guard times */ +#define SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_BUFFER_FULL (256) +#define SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_BUFFER_HALF \ + (SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_BUFFER_FULL / 2) +#define SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_GUARD_TIME 999 + +/** SubGhz state */ +typedef enum { + SubGhzDeviceCC1101ExtStateInit, /**< Init pending */ + SubGhzDeviceCC1101ExtStateIdle, /**< Idle, energy save mode */ + SubGhzDeviceCC1101ExtStateAsyncRx, /**< Async RX started */ + SubGhzDeviceCC1101ExtStateAsyncTx, /**< Async TX started, DMA and timer is on */ + SubGhzDeviceCC1101ExtStateAsyncTxEnd, /**< Async TX complete, cleanup needed */ +} SubGhzDeviceCC1101ExtState; + +/** SubGhz regulation, receive transmission on the current frequency for the + * region */ +typedef enum { + SubGhzDeviceCC1101ExtRegulationOnlyRx, /**only Rx*/ + SubGhzDeviceCC1101ExtRegulationTxRx, /**TxRx*/ +} SubGhzDeviceCC1101ExtRegulation; + +typedef struct { + uint32_t* buffer; + LevelDuration carry_ld; + SubGhzDeviceCC1101ExtCallback callback; + void* callback_context; + uint32_t gpio_tx_buff[2]; + uint32_t debug_gpio_buff[2]; +} SubGhzDeviceCC1101ExtAsyncTx; + +typedef struct { + uint32_t capture_delta_duration; + SubGhzDeviceCC1101ExtCaptureCallback capture_callback; + void* capture_callback_context; +} SubGhzDeviceCC1101ExtAsyncRx; + +typedef struct { + volatile SubGhzDeviceCC1101ExtState state; + volatile SubGhzDeviceCC1101ExtRegulation regulation; + const GpioPin* async_mirror_pin; + FuriHalSpiBusHandle* spi_bus_handle; + const GpioPin* g0_pin; + SubGhzDeviceCC1101ExtAsyncTx async_tx; + SubGhzDeviceCC1101ExtAsyncRx async_rx; +} SubGhzDeviceCC1101Ext; + +static SubGhzDeviceCC1101Ext* subghz_device_cc1101_ext = NULL; + +static bool subghz_device_cc1101_ext_check_init() { + furi_assert(subghz_device_cc1101_ext->state == SubGhzDeviceCC1101ExtStateInit); + subghz_device_cc1101_ext->state = SubGhzDeviceCC1101ExtStateIdle; + + bool ret = false; + + furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle); + FuriHalCortexTimer timer = furi_hal_cortex_timer_get(100 * 1000); + do { + // Reset + furi_hal_gpio_init( + subghz_device_cc1101_ext->g0_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + cc1101_reset(subghz_device_cc1101_ext->spi_bus_handle); + cc1101_write_reg( + subghz_device_cc1101_ext->spi_bus_handle, CC1101_IOCFG0, CC1101IocfgHighImpedance); + + // Prepare GD0 for power on self test + furi_hal_gpio_init( + subghz_device_cc1101_ext->g0_pin, GpioModeInput, GpioPullNo, GpioSpeedLow); + + // GD0 low + cc1101_write_reg(subghz_device_cc1101_ext->spi_bus_handle, CC1101_IOCFG0, CC1101IocfgHW); + while(furi_hal_gpio_read(subghz_device_cc1101_ext->g0_pin) != false) { + if(furi_hal_cortex_timer_is_expired(timer)) { + //timeout + break; + } + } + if(furi_hal_cortex_timer_is_expired(timer)) { + //timeout + break; + } + + // GD0 high + cc1101_write_reg( + subghz_device_cc1101_ext->spi_bus_handle, + CC1101_IOCFG0, + CC1101IocfgHW | CC1101_IOCFG_INV); + while(furi_hal_gpio_read(subghz_device_cc1101_ext->g0_pin) != true) { + if(furi_hal_cortex_timer_is_expired(timer)) { + //timeout + break; + } + } + if(furi_hal_cortex_timer_is_expired(timer)) { + //timeout + break; + } + + // Reset GD0 to floating state + cc1101_write_reg( + subghz_device_cc1101_ext->spi_bus_handle, CC1101_IOCFG0, CC1101IocfgHighImpedance); + furi_hal_gpio_init( + subghz_device_cc1101_ext->g0_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + + // RF switches + furi_hal_gpio_init(&gpio_rf_sw_0, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); + cc1101_write_reg(subghz_device_cc1101_ext->spi_bus_handle, CC1101_IOCFG2, CC1101IocfgHW); + + // Go to sleep + cc1101_shutdown(subghz_device_cc1101_ext->spi_bus_handle); + ret = true; + } while(false); + + furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); + + if(ret) { + FURI_LOG_I(TAG, "Init OK"); + } else { + FURI_LOG_E(TAG, "Init failed"); + } + return ret; +} + +bool subghz_device_cc1101_ext_alloc() { + furi_assert(subghz_device_cc1101_ext == NULL); + subghz_device_cc1101_ext = malloc(sizeof(SubGhzDeviceCC1101Ext)); + subghz_device_cc1101_ext->state = SubGhzDeviceCC1101ExtStateInit; + subghz_device_cc1101_ext->regulation = SubGhzDeviceCC1101ExtRegulationTxRx; + subghz_device_cc1101_ext->async_mirror_pin = NULL; + subghz_device_cc1101_ext->spi_bus_handle = &furi_hal_spi_bus_handle_external; + subghz_device_cc1101_ext->g0_pin = SUBGHZ_DEVICE_CC1101_EXT_TX_GPIO; + + subghz_device_cc1101_ext->async_rx.capture_delta_duration = 0; + + furi_hal_spi_bus_handle_init(subghz_device_cc1101_ext->spi_bus_handle); + return subghz_device_cc1101_ext_check_init(); +} + +void subghz_device_cc1101_ext_free() { + furi_assert(subghz_device_cc1101_ext != NULL); + furi_hal_spi_bus_handle_deinit(subghz_device_cc1101_ext->spi_bus_handle); + free(subghz_device_cc1101_ext); + subghz_device_cc1101_ext = NULL; +} + +void subghz_device_cc1101_ext_set_async_mirror_pin(const GpioPin* pin) { + subghz_device_cc1101_ext->async_mirror_pin = pin; +} + +const GpioPin* subghz_device_cc1101_ext_get_data_gpio() { + return subghz_device_cc1101_ext->g0_pin; +} + +bool subghz_device_cc1101_ext_is_connect() { + bool ret = false; + + if(subghz_device_cc1101_ext == NULL) { // not initialized + ret = subghz_device_cc1101_ext_alloc(); + subghz_device_cc1101_ext_free(); + } else { // initialized + furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle); + uint8_t partnumber = cc1101_get_partnumber(subghz_device_cc1101_ext->spi_bus_handle); + furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); + ret = (partnumber != 0) && (partnumber != 0xFF); + } + + return ret; +} + +void subghz_device_cc1101_ext_sleep() { + furi_assert(subghz_device_cc1101_ext->state == SubGhzDeviceCC1101ExtStateIdle); + furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle); + + cc1101_switch_to_idle(subghz_device_cc1101_ext->spi_bus_handle); + + cc1101_write_reg( + subghz_device_cc1101_ext->spi_bus_handle, CC1101_IOCFG0, CC1101IocfgHighImpedance); + furi_hal_gpio_init(subghz_device_cc1101_ext->g0_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + + cc1101_shutdown(subghz_device_cc1101_ext->spi_bus_handle); + + furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); +} + +void subghz_device_cc1101_ext_dump_state() { + furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle); + printf( + "[subghz_device_cc1101_ext] cc1101 chip %d, version %d\r\n", + cc1101_get_partnumber(subghz_device_cc1101_ext->spi_bus_handle), + cc1101_get_version(subghz_device_cc1101_ext->spi_bus_handle)); + furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); +} + +void subghz_device_cc1101_ext_load_custom_preset(const uint8_t* preset_data) { + //load config + furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle); + cc1101_reset(subghz_device_cc1101_ext->spi_bus_handle); + uint32_t i = 0; + uint8_t pa[8] = {0}; + while(preset_data[i]) { + cc1101_write_reg( + subghz_device_cc1101_ext->spi_bus_handle, preset_data[i], preset_data[i + 1]); + i += 2; + } + furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); + + //load pa table + memcpy(&pa[0], &preset_data[i + 2], 8); + subghz_device_cc1101_ext_load_patable(pa); + + //show debug + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + i = 0; + FURI_LOG_D(TAG, "Loading custom preset"); + while(preset_data[i]) { + FURI_LOG_D(TAG, "Reg[%lu]: %02X=%02X", i, preset_data[i], preset_data[i + 1]); + i += 2; + } + for(uint8_t y = i; y < i + 10; y++) { + FURI_LOG_D(TAG, "PA[%u]: %02X", y, preset_data[y]); + } + } +} + +void subghz_device_cc1101_ext_load_registers(const uint8_t* data) { + furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle); + cc1101_reset(subghz_device_cc1101_ext->spi_bus_handle); + uint32_t i = 0; + while(data[i]) { + cc1101_write_reg(subghz_device_cc1101_ext->spi_bus_handle, data[i], data[i + 1]); + i += 2; + } + furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); +} + +void subghz_device_cc1101_ext_load_patable(const uint8_t data[8]) { + furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle); + cc1101_set_pa_table(subghz_device_cc1101_ext->spi_bus_handle, data); + furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); +} + +void subghz_device_cc1101_ext_write_packet(const uint8_t* data, uint8_t size) { + furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle); + cc1101_flush_tx(subghz_device_cc1101_ext->spi_bus_handle); + cc1101_write_reg(subghz_device_cc1101_ext->spi_bus_handle, CC1101_FIFO, size); + cc1101_write_fifo(subghz_device_cc1101_ext->spi_bus_handle, data, size); + furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); +} + +void subghz_device_cc1101_ext_flush_rx() { + furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle); + cc1101_flush_rx(subghz_device_cc1101_ext->spi_bus_handle); + furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); +} + +void subghz_device_cc1101_ext_flush_tx() { + furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle); + cc1101_flush_tx(subghz_device_cc1101_ext->spi_bus_handle); + furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); +} + +bool subghz_device_cc1101_ext_rx_pipe_not_empty() { + CC1101RxBytes status[1]; + furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle); + cc1101_read_reg( + subghz_device_cc1101_ext->spi_bus_handle, + (CC1101_STATUS_RXBYTES) | CC1101_BURST, + (uint8_t*)status); + furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); + // TODO: you can add a buffer overflow flag if needed + if(status->NUM_RXBYTES > 0) { + return true; + } else { + return false; + } +} + +bool subghz_device_cc1101_ext_is_rx_data_crc_valid() { + furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle); + uint8_t data[1]; + cc1101_read_reg( + subghz_device_cc1101_ext->spi_bus_handle, CC1101_STATUS_LQI | CC1101_BURST, data); + furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); + if(((data[0] >> 7) & 0x01)) { + return true; + } else { + return false; + } +} + +void subghz_device_cc1101_ext_read_packet(uint8_t* data, uint8_t* size) { + furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle); + cc1101_read_fifo(subghz_device_cc1101_ext->spi_bus_handle, data, size); + furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); +} + +void subghz_device_cc1101_ext_shutdown() { + furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle); + // Reset and shutdown + cc1101_shutdown(subghz_device_cc1101_ext->spi_bus_handle); + furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); +} + +void subghz_device_cc1101_ext_reset() { + furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle); + furi_hal_gpio_init(subghz_device_cc1101_ext->g0_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + cc1101_switch_to_idle(subghz_device_cc1101_ext->spi_bus_handle); + cc1101_reset(subghz_device_cc1101_ext->spi_bus_handle); + cc1101_write_reg( + subghz_device_cc1101_ext->spi_bus_handle, CC1101_IOCFG0, CC1101IocfgHighImpedance); + furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); +} + +void subghz_device_cc1101_ext_idle() { + furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle); + cc1101_switch_to_idle(subghz_device_cc1101_ext->spi_bus_handle); + furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); +} + +void subghz_device_cc1101_ext_rx() { + furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle); + cc1101_switch_to_rx(subghz_device_cc1101_ext->spi_bus_handle); + furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); +} + +bool subghz_device_cc1101_ext_tx() { + if(subghz_device_cc1101_ext->regulation != SubGhzDeviceCC1101ExtRegulationTxRx) return false; + furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle); + cc1101_switch_to_tx(subghz_device_cc1101_ext->spi_bus_handle); + furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); + return true; +} + +float subghz_device_cc1101_ext_get_rssi() { + furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle); + int32_t rssi_dec = cc1101_get_rssi(subghz_device_cc1101_ext->spi_bus_handle); + furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); + + float rssi = rssi_dec; + if(rssi_dec >= 128) { + rssi = ((rssi - 256.0f) / 2.0f) - 74.0f; + } else { + rssi = (rssi / 2.0f) - 74.0f; + } + + return rssi; +} + +uint8_t subghz_device_cc1101_ext_get_lqi() { + furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle); + uint8_t data[1]; + cc1101_read_reg( + subghz_device_cc1101_ext->spi_bus_handle, CC1101_STATUS_LQI | CC1101_BURST, data); + furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); + return data[0] & 0x7F; +} + +bool subghz_device_cc1101_ext_is_frequency_valid(uint32_t value) { + if(!(value >= 299999755 && value <= 348000335) && + !(value >= 386999938 && value <= 464000000) && + !(value >= 778999847 && value <= 928000000)) { + return false; + } + + return true; +} + +uint32_t subghz_device_cc1101_ext_set_frequency(uint32_t value) { + if(furi_hal_region_is_frequency_allowed(value)) { + subghz_device_cc1101_ext->regulation = SubGhzDeviceCC1101ExtRegulationTxRx; + } else { + subghz_device_cc1101_ext->regulation = SubGhzDeviceCC1101ExtRegulationOnlyRx; + } + + furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle); + uint32_t real_frequency = + cc1101_set_frequency(subghz_device_cc1101_ext->spi_bus_handle, value); + cc1101_calibrate(subghz_device_cc1101_ext->spi_bus_handle); + + while(true) { + CC1101Status status = cc1101_get_status(subghz_device_cc1101_ext->spi_bus_handle); + if(status.STATE == CC1101StateIDLE) break; + } + + furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); + return real_frequency; +} + +static bool subghz_device_cc1101_ext_start_debug() { + bool ret = false; + if(subghz_device_cc1101_ext->async_mirror_pin != NULL) { + furi_hal_gpio_init( + subghz_device_cc1101_ext->async_mirror_pin, + GpioModeOutputPushPull, + GpioPullNo, + GpioSpeedVeryHigh); + ret = true; + } + return ret; +} + +static bool subghz_device_cc1101_ext_stop_debug() { + bool ret = false; + if(subghz_device_cc1101_ext->async_mirror_pin != NULL) { + furi_hal_gpio_init( + subghz_device_cc1101_ext->async_mirror_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + ret = true; + } + return ret; +} + +static void subghz_device_cc1101_ext_capture_ISR() { + if(!furi_hal_gpio_read(subghz_device_cc1101_ext->g0_pin)) { + if(subghz_device_cc1101_ext->async_rx.capture_callback) { + if(subghz_device_cc1101_ext->async_mirror_pin != NULL) + furi_hal_gpio_write(subghz_device_cc1101_ext->async_mirror_pin, false); + + subghz_device_cc1101_ext->async_rx.capture_callback( + true, + LL_TIM_GetCounter(TIM17), + (void*)subghz_device_cc1101_ext->async_rx.capture_callback_context); + } + } else { + if(subghz_device_cc1101_ext->async_rx.capture_callback) { + if(subghz_device_cc1101_ext->async_mirror_pin != NULL) + furi_hal_gpio_write(subghz_device_cc1101_ext->async_mirror_pin, true); + + subghz_device_cc1101_ext->async_rx.capture_callback( + false, + LL_TIM_GetCounter(TIM17), + (void*)subghz_device_cc1101_ext->async_rx.capture_callback_context); + } + } + LL_TIM_SetCounter(TIM17, 6); +} + +void subghz_device_cc1101_ext_start_async_rx( + SubGhzDeviceCC1101ExtCaptureCallback callback, + void* context) { + furi_assert(subghz_device_cc1101_ext->state == SubGhzDeviceCC1101ExtStateIdle); + subghz_device_cc1101_ext->state = SubGhzDeviceCC1101ExtStateAsyncRx; + + subghz_device_cc1101_ext->async_rx.capture_callback = callback; + subghz_device_cc1101_ext->async_rx.capture_callback_context = context; + + furi_hal_bus_enable(FuriHalBusTIM17); + + // Configure TIM + LL_TIM_SetPrescaler(TIM17, 64 - 1); + LL_TIM_SetCounterMode(TIM17, LL_TIM_COUNTERMODE_UP); + LL_TIM_SetAutoReload(TIM17, 0xFFFF); + LL_TIM_SetClockDivision(TIM17, LL_TIM_CLOCKDIVISION_DIV1); + + // Timer: advanced + LL_TIM_SetClockSource(TIM17, LL_TIM_CLOCKSOURCE_INTERNAL); + LL_TIM_DisableARRPreload(TIM17); + LL_TIM_DisableDMAReq_TRIG(TIM17); + LL_TIM_DisableIT_TRIG(TIM17); + + furi_hal_gpio_init( + subghz_device_cc1101_ext->g0_pin, GpioModeInterruptRiseFall, GpioPullUp, GpioSpeedVeryHigh); + furi_hal_gpio_remove_int_callback(subghz_device_cc1101_ext->g0_pin); + furi_hal_gpio_add_int_callback( + subghz_device_cc1101_ext->g0_pin, + subghz_device_cc1101_ext_capture_ISR, + subghz_device_cc1101_ext->async_rx.capture_callback); + + // Start timer + LL_TIM_SetCounter(TIM17, 0); + LL_TIM_EnableCounter(TIM17); + + // Start debug + subghz_device_cc1101_ext_start_debug(); + + // Switch to RX + subghz_device_cc1101_ext_rx(); + + //Clear the variable after the end of the session + subghz_device_cc1101_ext->async_rx.capture_delta_duration = 0; +} + +void subghz_device_cc1101_ext_stop_async_rx() { + furi_assert(subghz_device_cc1101_ext->state == SubGhzDeviceCC1101ExtStateAsyncRx); + subghz_device_cc1101_ext->state = SubGhzDeviceCC1101ExtStateIdle; + + // Shutdown radio + subghz_device_cc1101_ext_idle(); + + FURI_CRITICAL_ENTER(); + furi_hal_bus_disable(FuriHalBusTIM17); + + // Stop debug + subghz_device_cc1101_ext_stop_debug(); + + FURI_CRITICAL_EXIT(); + furi_hal_gpio_remove_int_callback(subghz_device_cc1101_ext->g0_pin); + furi_hal_gpio_init(subghz_device_cc1101_ext->g0_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); +} + +static void subghz_device_cc1101_ext_async_tx_refill(uint32_t* buffer, size_t samples) { + furi_assert(subghz_device_cc1101_ext->state == SubGhzDeviceCC1101ExtStateAsyncTx); + while(samples > 0) { + bool is_odd = samples % 2; + LevelDuration ld; + if(level_duration_is_reset(subghz_device_cc1101_ext->async_tx.carry_ld)) { + ld = subghz_device_cc1101_ext->async_tx.callback( + subghz_device_cc1101_ext->async_tx.callback_context); + } else { + ld = subghz_device_cc1101_ext->async_tx.carry_ld; + subghz_device_cc1101_ext->async_tx.carry_ld = level_duration_reset(); + } + + if(level_duration_is_wait(ld)) { + *buffer = SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_GUARD_TIME; + buffer++; + samples--; + } else if(level_duration_is_reset(ld)) { + *buffer = 0; + buffer++; + samples--; + LL_DMA_DisableIT_HT(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_DEF); + LL_DMA_DisableIT_TC(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_DEF); + LL_TIM_EnableIT_UPDATE(TIM17); + break; + } else { + bool level = level_duration_get_level(ld); + + // Inject guard time if level is incorrect + if(is_odd != level) { + *buffer = SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_GUARD_TIME; + buffer++; + samples--; + + // Special case: prevent buffer overflow if sample is last + if(samples == 0) { + subghz_device_cc1101_ext->async_tx.carry_ld = ld; + break; + } + } + + uint32_t duration = level_duration_get_duration(ld); + furi_assert(duration > 0); + *buffer = duration - 1; + buffer++; + samples--; + } + } +} + +static void subghz_device_cc1101_ext_async_tx_dma_isr() { + furi_assert(subghz_device_cc1101_ext->state == SubGhzDeviceCC1101ExtStateAsyncTx); + +#if SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_CHANNEL == LL_DMA_CHANNEL_3 + if(LL_DMA_IsActiveFlag_HT3(SUBGHZ_DEVICE_CC1101_EXT_DMA)) { + LL_DMA_ClearFlag_HT3(SUBGHZ_DEVICE_CC1101_EXT_DMA); + subghz_device_cc1101_ext_async_tx_refill( + subghz_device_cc1101_ext->async_tx.buffer, + SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_BUFFER_HALF); + } + if(LL_DMA_IsActiveFlag_TC3(SUBGHZ_DEVICE_CC1101_EXT_DMA)) { + LL_DMA_ClearFlag_TC3(SUBGHZ_DEVICE_CC1101_EXT_DMA); + subghz_device_cc1101_ext_async_tx_refill( + subghz_device_cc1101_ext->async_tx.buffer + + SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_BUFFER_HALF, + SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_BUFFER_HALF); + } +#else +#error Update this code. Would you kindly? +#endif +} + +static void subghz_device_cc1101_ext_async_tx_timer_isr() { + if(LL_TIM_IsActiveFlag_UPDATE(TIM17)) { + if(LL_TIM_GetAutoReload(TIM17) == 0) { + LL_DMA_DisableChannel(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_DEF); + furi_hal_gpio_write(subghz_device_cc1101_ext->g0_pin, false); + if(subghz_device_cc1101_ext->async_mirror_pin != NULL) + furi_hal_gpio_write(subghz_device_cc1101_ext->async_mirror_pin, false); + LL_TIM_DisableCounter(TIM17); + subghz_device_cc1101_ext->state = SubGhzDeviceCC1101ExtStateAsyncTxEnd; + } + LL_TIM_ClearFlag_UPDATE(TIM17); + } +} + +bool subghz_device_cc1101_ext_start_async_tx(SubGhzDeviceCC1101ExtCallback callback, void* context) { + furi_assert(subghz_device_cc1101_ext->state == SubGhzDeviceCC1101ExtStateIdle); + furi_assert(callback); + + //If transmission is prohibited by regional settings + if(subghz_device_cc1101_ext->regulation != SubGhzDeviceCC1101ExtRegulationTxRx) return false; + + subghz_device_cc1101_ext->async_tx.callback = callback; + subghz_device_cc1101_ext->async_tx.callback_context = context; + + subghz_device_cc1101_ext->state = SubGhzDeviceCC1101ExtStateAsyncTx; + + subghz_device_cc1101_ext->async_tx.buffer = + malloc(SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_BUFFER_FULL * sizeof(uint32_t)); + + //Signal generation with mem-to-mem DMA + furi_hal_gpio_write(subghz_device_cc1101_ext->g0_pin, false); + furi_hal_gpio_init( + subghz_device_cc1101_ext->g0_pin, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); + + // Configure DMA update timer + LL_DMA_SetMemoryAddress( + SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_DEF, (uint32_t)subghz_device_cc1101_ext->async_tx.buffer); + LL_DMA_SetPeriphAddress(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_DEF, (uint32_t) & (TIM17->ARR)); + LL_DMA_ConfigTransfer( + SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_DEF, + LL_DMA_DIRECTION_MEMORY_TO_PERIPH | LL_DMA_MODE_CIRCULAR | LL_DMA_PERIPH_NOINCREMENT | + LL_DMA_MEMORY_INCREMENT | LL_DMA_PDATAALIGN_WORD | LL_DMA_MDATAALIGN_WORD | + LL_DMA_MODE_NORMAL); + LL_DMA_SetDataLength( + SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_DEF, SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_BUFFER_FULL); + LL_DMA_SetPeriphRequest(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_DEF, LL_DMAMUX_REQ_TIM17_UP); + + LL_DMA_EnableIT_TC(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_DEF); + LL_DMA_EnableIT_HT(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_DEF); + LL_DMA_EnableChannel(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_DEF); + + furi_hal_interrupt_set_isr( + SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_IRQ, subghz_device_cc1101_ext_async_tx_dma_isr, NULL); + + furi_hal_bus_enable(FuriHalBusTIM17); + + // Configure TIM + LL_TIM_SetPrescaler(TIM17, 64 - 1); + LL_TIM_SetCounterMode(TIM17, LL_TIM_COUNTERMODE_UP); + LL_TIM_SetAutoReload(TIM17, 0xFFFF); + LL_TIM_SetClockDivision(TIM17, LL_TIM_CLOCKDIVISION_DIV1); + LL_TIM_SetClockSource(TIM17, LL_TIM_CLOCKSOURCE_INTERNAL); + LL_TIM_DisableARRPreload(TIM17); + + furi_hal_interrupt_set_isr( + FuriHalInterruptIdTim1TrgComTim17, subghz_device_cc1101_ext_async_tx_timer_isr, NULL); + + subghz_device_cc1101_ext_async_tx_refill( + subghz_device_cc1101_ext->async_tx.buffer, SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_BUFFER_FULL); + + // Configure tx gpio dma + const GpioPin* gpio = subghz_device_cc1101_ext->g0_pin; + + subghz_device_cc1101_ext->async_tx.gpio_tx_buff[0] = (uint32_t)gpio->pin << GPIO_NUMBER; + subghz_device_cc1101_ext->async_tx.gpio_tx_buff[1] = gpio->pin; + + LL_DMA_SetMemoryAddress( + SUBGHZ_DEVICE_CC1101_EXT_DMA_CH4_DEF, + (uint32_t)subghz_device_cc1101_ext->async_tx.gpio_tx_buff); + LL_DMA_SetPeriphAddress(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH4_DEF, (uint32_t) & (gpio->port->BSRR)); + LL_DMA_ConfigTransfer( + SUBGHZ_DEVICE_CC1101_EXT_DMA_CH4_DEF, + LL_DMA_DIRECTION_MEMORY_TO_PERIPH | LL_DMA_MODE_CIRCULAR | LL_DMA_PERIPH_NOINCREMENT | + LL_DMA_MEMORY_INCREMENT | LL_DMA_PDATAALIGN_WORD | LL_DMA_MDATAALIGN_WORD | + LL_DMA_PRIORITY_HIGH); + LL_DMA_SetDataLength(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH4_DEF, 2); + LL_DMA_SetPeriphRequest(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH4_DEF, LL_DMAMUX_REQ_TIM17_UP); + LL_DMA_EnableChannel(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH4_DEF); + + // Start debug + if(subghz_device_cc1101_ext_start_debug()) { + gpio = subghz_device_cc1101_ext->async_mirror_pin; + subghz_device_cc1101_ext->async_tx.debug_gpio_buff[0] = (uint32_t)gpio->pin << GPIO_NUMBER; + subghz_device_cc1101_ext->async_tx.debug_gpio_buff[1] = gpio->pin; + + LL_DMA_SetMemoryAddress( + SUBGHZ_DEVICE_CC1101_EXT_DMA_CH5_DEF, + (uint32_t)subghz_device_cc1101_ext->async_tx.debug_gpio_buff); + LL_DMA_SetPeriphAddress( + SUBGHZ_DEVICE_CC1101_EXT_DMA_CH5_DEF, (uint32_t) & (gpio->port->BSRR)); + LL_DMA_ConfigTransfer( + SUBGHZ_DEVICE_CC1101_EXT_DMA_CH5_DEF, + LL_DMA_DIRECTION_MEMORY_TO_PERIPH | LL_DMA_MODE_CIRCULAR | LL_DMA_PERIPH_NOINCREMENT | + LL_DMA_MEMORY_INCREMENT | LL_DMA_PDATAALIGN_WORD | LL_DMA_MDATAALIGN_WORD | + LL_DMA_PRIORITY_LOW); + LL_DMA_SetDataLength(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH5_DEF, 2); + LL_DMA_SetPeriphRequest(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH5_DEF, LL_DMAMUX_REQ_TIM17_UP); + LL_DMA_EnableChannel(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH5_DEF); + } + + // Start counter + LL_TIM_EnableDMAReq_UPDATE(TIM17); + LL_TIM_GenerateEvent_UPDATE(TIM17); + + subghz_device_cc1101_ext_tx(); + + LL_TIM_SetCounter(TIM17, 0); + LL_TIM_EnableCounter(TIM17); + + return true; +} + +bool subghz_device_cc1101_ext_is_async_tx_complete() { + return subghz_device_cc1101_ext->state == SubGhzDeviceCC1101ExtStateAsyncTxEnd; +} + +void subghz_device_cc1101_ext_stop_async_tx() { + furi_assert( + subghz_device_cc1101_ext->state == SubGhzDeviceCC1101ExtStateAsyncTx || + subghz_device_cc1101_ext->state == SubGhzDeviceCC1101ExtStateAsyncTxEnd); + + // Shutdown radio + subghz_device_cc1101_ext_idle(); + + // Deinitialize Timer + FURI_CRITICAL_ENTER(); + furi_hal_bus_disable(FuriHalBusTIM17); + furi_hal_interrupt_set_isr(FuriHalInterruptIdTim1TrgComTim17, NULL, NULL); + + // Deinitialize DMA + LL_DMA_DeInit(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_DEF); + LL_DMA_DisableChannel(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH4_DEF); + furi_hal_interrupt_set_isr(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_IRQ, NULL, NULL); + + // Deinitialize GPIO + furi_hal_gpio_write(subghz_device_cc1101_ext->g0_pin, false); + furi_hal_gpio_init(subghz_device_cc1101_ext->g0_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + + // Stop debug + if(subghz_device_cc1101_ext_stop_debug()) { + LL_DMA_DisableChannel(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH5_DEF); + } + + FURI_CRITICAL_EXIT(); + + free(subghz_device_cc1101_ext->async_tx.buffer); + + subghz_device_cc1101_ext->state = SubGhzDeviceCC1101ExtStateIdle; +} diff --git a/applications/drivers/subghz/cc1101_ext/cc1101_ext.h b/applications/drivers/subghz/cc1101_ext/cc1101_ext.h new file mode 100644 index 00000000000..d972fcb6618 --- /dev/null +++ b/applications/drivers/subghz/cc1101_ext/cc1101_ext.h @@ -0,0 +1,206 @@ +/** + * @file furi_hal_subghz.h + * SubGhz HAL API + */ + +#pragma once +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Mirror RX/TX async modulation signal to specified pin + * + * @warning Configures pin to output mode. Make sure it is not connected + * directly to power or ground. + * + * @param[in] pin pointer to the gpio pin structure or NULL to disable + */ +void subghz_device_cc1101_ext_set_async_mirror_pin(const GpioPin* pin); + +/** Get data GPIO + * + * @return pointer to the gpio pin structure + */ +const GpioPin* subghz_device_cc1101_ext_get_data_gpio(); + +/** Initialize device + * + * @return true if success + */ +bool subghz_device_cc1101_ext_alloc(); + +/** Deinitialize device + */ +void subghz_device_cc1101_ext_free(); + +/** Check and switch to power save mode Used by internal API-HAL + * initialization routine Can be used to reinitialize device to safe state and + * send it to sleep + */ +bool subghz_device_cc1101_ext_is_connect(); + +/** Send device to sleep mode + */ +void subghz_device_cc1101_ext_sleep(); + +/** Dump info to stdout + */ +void subghz_device_cc1101_ext_dump_state(); + +/** Load custom registers from preset + * + * @param preset_data registers to load + */ +void subghz_device_cc1101_ext_load_custom_preset(const uint8_t* preset_data); + +/** Load registers + * + * @param data Registers data + */ +void subghz_device_cc1101_ext_load_registers(const uint8_t* data); + +/** Load PATABLE + * + * @param data 8 uint8_t values + */ +void subghz_device_cc1101_ext_load_patable(const uint8_t data[8]); + +/** Write packet to FIFO + * + * @param data bytes array + * @param size size + */ +void subghz_device_cc1101_ext_write_packet(const uint8_t* data, uint8_t size); + +/** Check if receive pipe is not empty + * + * @return true if not empty + */ +bool subghz_device_cc1101_ext_rx_pipe_not_empty(); + +/** Check if received data crc is valid + * + * @return true if valid + */ +bool subghz_device_cc1101_ext_is_rx_data_crc_valid(); + +/** Read packet from FIFO + * + * @param data pointer + * @param size size + */ +void subghz_device_cc1101_ext_read_packet(uint8_t* data, uint8_t* size); + +/** Flush rx FIFO buffer + */ +void subghz_device_cc1101_ext_flush_rx(); + +/** Flush tx FIFO buffer + */ +void subghz_device_cc1101_ext_flush_tx(); + +/** Shutdown Issue SPWD command + * @warning registers content will be lost + */ +void subghz_device_cc1101_ext_shutdown(); + +/** Reset Issue reset command + * @warning registers content will be lost + */ +void subghz_device_cc1101_ext_reset(); + +/** Switch to Idle + */ +void subghz_device_cc1101_ext_idle(); + +/** Switch to Receive + */ +void subghz_device_cc1101_ext_rx(); + +/** Switch to Transmit + * + * @return true if the transfer is allowed by belonging to the region + */ +bool subghz_device_cc1101_ext_tx(); + +/** Get RSSI value in dBm + * + * @return RSSI value + */ +float subghz_device_cc1101_ext_get_rssi(); + +/** Get LQI + * + * @return LQI value + */ +uint8_t subghz_device_cc1101_ext_get_lqi(); + +/** Check if frequency is in valid range + * + * @param value frequency in Hz + * + * @return true if frequency is valid, otherwise false + */ +bool subghz_device_cc1101_ext_is_frequency_valid(uint32_t value); + +/** Set frequency + * + * @param value frequency in Hz + * + * @return real frequency in Hz + */ +uint32_t subghz_device_cc1101_ext_set_frequency(uint32_t value); + +/* High Level API */ + +/** Signal Timings Capture callback */ +typedef void (*SubGhzDeviceCC1101ExtCaptureCallback)(bool level, uint32_t duration, void* context); + +/** Enable signal timings capture Initializes GPIO and TIM2 for timings capture + * + * @param callback SubGhzDeviceCC1101ExtCaptureCallback + * @param context callback context + */ +void subghz_device_cc1101_ext_start_async_rx( + SubGhzDeviceCC1101ExtCaptureCallback callback, + void* context); + +/** Disable signal timings capture Resets GPIO and TIM2 + */ +void subghz_device_cc1101_ext_stop_async_rx(); + +/** Async TX callback type + * @param context callback context + * @return LevelDuration + */ +typedef LevelDuration (*SubGhzDeviceCC1101ExtCallback)(void* context); + +/** Start async TX Initializes GPIO, TIM2 and DMA1 for signal output + * + * @param callback SubGhzDeviceCC1101ExtCallback + * @param context callback context + * + * @return true if the transfer is allowed by belonging to the region + */ +bool subghz_device_cc1101_ext_start_async_tx(SubGhzDeviceCC1101ExtCallback callback, void* context); + +/** Wait for async transmission to complete + * + * @return true if TX complete + */ +bool subghz_device_cc1101_ext_is_async_tx_complete(); + +/** Stop async transmission and cleanup resources Resets GPIO, TIM2, and DMA1 + */ +void subghz_device_cc1101_ext_stop_async_tx(); + +#ifdef __cplusplus +} +#endif diff --git a/applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.c b/applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.c new file mode 100644 index 00000000000..51f5a0d1ddb --- /dev/null +++ b/applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.c @@ -0,0 +1,110 @@ +#include "cc1101_ext_interconnect.h" +#include "cc1101_ext.h" +#include + +#define TAG "SubGhzDeviceCC1101Ext" + +static bool subghz_device_cc1101_ext_interconnect_is_frequency_valid(uint32_t frequency) { + bool ret = subghz_device_cc1101_ext_is_frequency_valid(frequency); + if(!ret) { + furi_crash("SubGhz: Incorrect frequency."); + } + return ret; +} + +static uint32_t subghz_device_cc1101_ext_interconnect_set_frequency(uint32_t frequency) { + subghz_device_cc1101_ext_interconnect_is_frequency_valid(frequency); + return subghz_device_cc1101_ext_set_frequency(frequency); +} + +static bool subghz_device_cc1101_ext_interconnect_start_async_tx(void* callback, void* context) { + return subghz_device_cc1101_ext_start_async_tx( + (SubGhzDeviceCC1101ExtCallback)callback, context); +} + +static void subghz_device_cc1101_ext_interconnect_start_async_rx(void* callback, void* context) { + subghz_device_cc1101_ext_start_async_rx( + (SubGhzDeviceCC1101ExtCaptureCallback)callback, context); +} + +static void subghz_device_cc1101_ext_interconnect_load_preset( + FuriHalSubGhzPreset preset, + uint8_t* preset_data) { + switch(preset) { + case FuriHalSubGhzPresetOok650Async: + subghz_device_cc1101_ext_load_custom_preset( + subghz_device_cc1101_preset_ook_650khz_async_regs); + break; + case FuriHalSubGhzPresetOok270Async: + subghz_device_cc1101_ext_load_custom_preset( + subghz_device_cc1101_preset_ook_270khz_async_regs); + break; + case FuriHalSubGhzPreset2FSKDev238Async: + subghz_device_cc1101_ext_load_custom_preset( + subghz_device_cc1101_preset_2fsk_dev2_38khz_async_regs); + break; + case FuriHalSubGhzPreset2FSKDev476Async: + subghz_device_cc1101_ext_load_custom_preset( + subghz_device_cc1101_preset_2fsk_dev47_6khz_async_regs); + break; + case FuriHalSubGhzPresetMSK99_97KbAsync: + subghz_device_cc1101_ext_load_custom_preset( + subghz_device_cc1101_preset_msk_99_97kb_async_regs); + break; + case FuriHalSubGhzPresetGFSK9_99KbAsync: + subghz_device_cc1101_ext_load_custom_preset( + subghz_device_cc1101_preset_gfsk_9_99kb_async_regs); + break; + + default: + subghz_device_cc1101_ext_load_custom_preset(preset_data); + } +} + +const SubGhzDeviceInterconnect subghz_device_cc1101_ext_interconnect = { + .begin = subghz_device_cc1101_ext_alloc, + .end = subghz_device_cc1101_ext_free, + .is_connect = subghz_device_cc1101_ext_is_connect, + .reset = subghz_device_cc1101_ext_reset, + .sleep = subghz_device_cc1101_ext_sleep, + .idle = subghz_device_cc1101_ext_idle, + .load_preset = subghz_device_cc1101_ext_interconnect_load_preset, + .set_frequency = subghz_device_cc1101_ext_interconnect_set_frequency, + .is_frequency_valid = subghz_device_cc1101_ext_is_frequency_valid, + .set_async_mirror_pin = subghz_device_cc1101_ext_set_async_mirror_pin, + .get_data_gpio = subghz_device_cc1101_ext_get_data_gpio, + + .set_tx = subghz_device_cc1101_ext_tx, + .flush_tx = subghz_device_cc1101_ext_flush_tx, + .start_async_tx = subghz_device_cc1101_ext_interconnect_start_async_tx, + .is_async_complete_tx = subghz_device_cc1101_ext_is_async_tx_complete, + .stop_async_tx = subghz_device_cc1101_ext_stop_async_tx, + + .set_rx = subghz_device_cc1101_ext_rx, + .flush_rx = subghz_device_cc1101_ext_flush_rx, + .start_async_rx = subghz_device_cc1101_ext_interconnect_start_async_rx, + .stop_async_rx = subghz_device_cc1101_ext_stop_async_rx, + + .get_rssi = subghz_device_cc1101_ext_get_rssi, + .get_lqi = subghz_device_cc1101_ext_get_lqi, + + .rx_pipe_not_empty = subghz_device_cc1101_ext_rx_pipe_not_empty, + .is_rx_data_crc_valid = subghz_device_cc1101_ext_is_rx_data_crc_valid, + .read_packet = subghz_device_cc1101_ext_read_packet, + .write_packet = subghz_device_cc1101_ext_write_packet, +}; + +const SubGhzDevice subghz_device_cc1101_ext = { + .name = SUBGHZ_DEVICE_CC1101_EXT_NAME, + .interconnect = &subghz_device_cc1101_ext_interconnect, +}; + +static const FlipperAppPluginDescriptor subghz_device_cc1101_ext_descriptor = { + .appid = SUBGHZ_RADIO_DEVICE_PLUGIN_APP_ID, + .ep_api_version = SUBGHZ_RADIO_DEVICE_PLUGIN_API_VERSION, + .entry_point = &subghz_device_cc1101_ext, +}; + +const FlipperAppPluginDescriptor* subghz_device_cc1101_ext_ep() { + return &subghz_device_cc1101_ext_descriptor; +} \ No newline at end of file diff --git a/applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h b/applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h new file mode 100644 index 00000000000..cf1ff3ee07f --- /dev/null +++ b/applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h @@ -0,0 +1,8 @@ +#pragma once +#include + +#define SUBGHZ_DEVICE_CC1101_EXT_NAME "cc1101_ext" + +typedef struct SubGhzDeviceCC1101Ext SubGhzDeviceCC1101Ext; + +const FlipperAppPluginDescriptor* subghz_device_cc1101_ext_ep(); diff --git a/applications/main/subghz/helpers/subghz_chat.c b/applications/main/subghz/helpers/subghz_chat.c index dbf34c97051..bbe219fd2f7 100644 --- a/applications/main/subghz/helpers/subghz_chat.c +++ b/applications/main/subghz/helpers/subghz_chat.c @@ -76,12 +76,15 @@ void subghz_chat_worker_free(SubGhzChatWorker* instance) { free(instance); } -bool subghz_chat_worker_start(SubGhzChatWorker* instance, uint32_t frequency) { +bool subghz_chat_worker_start( + SubGhzChatWorker* instance, + const SubGhzDevice* device, + uint32_t frequency) { furi_assert(instance); furi_assert(!instance->worker_running); bool res = false; - if(subghz_tx_rx_worker_start(instance->subghz_txrx, frequency)) { + if(subghz_tx_rx_worker_start(instance->subghz_txrx, device, frequency)) { furi_message_queue_reset(instance->event_queue); subghz_tx_rx_worker_set_callback_have_read( instance->subghz_txrx, subghz_chat_worker_update_rx_event_chat, instance); diff --git a/applications/main/subghz/helpers/subghz_chat.h b/applications/main/subghz/helpers/subghz_chat.h index b418bbdbffc..2c454b75d98 100644 --- a/applications/main/subghz/helpers/subghz_chat.h +++ b/applications/main/subghz/helpers/subghz_chat.h @@ -1,5 +1,6 @@ #pragma once #include "../subghz_i.h" +#include #include typedef struct SubGhzChatWorker SubGhzChatWorker; @@ -20,7 +21,10 @@ typedef struct { SubGhzChatWorker* subghz_chat_worker_alloc(Cli* cli); void subghz_chat_worker_free(SubGhzChatWorker* instance); -bool subghz_chat_worker_start(SubGhzChatWorker* instance, uint32_t frequency); +bool subghz_chat_worker_start( + SubGhzChatWorker* instance, + const SubGhzDevice* device, + uint32_t frequency); void subghz_chat_worker_stop(SubGhzChatWorker* instance); bool subghz_chat_worker_is_running(SubGhzChatWorker* instance); SubGhzChatEvent subghz_chat_worker_get_event_chat(SubGhzChatWorker* instance); diff --git a/applications/main/subghz/helpers/subghz_threshold_rssi.c b/applications/main/subghz/helpers/subghz_threshold_rssi.c index 04a06bc1736..07d7bccf93d 100644 --- a/applications/main/subghz/helpers/subghz_threshold_rssi.c +++ b/applications/main/subghz/helpers/subghz_threshold_rssi.c @@ -32,9 +32,8 @@ float subghz_threshold_rssi_get(SubGhzThresholdRssi* instance) { return instance->threshold_rssi; } -SubGhzThresholdRssiData subghz_threshold_get_rssi_data(SubGhzThresholdRssi* instance) { +SubGhzThresholdRssiData subghz_threshold_get_rssi_data(SubGhzThresholdRssi* instance, float rssi) { furi_assert(instance); - float rssi = furi_hal_subghz_get_rssi(); SubGhzThresholdRssiData ret = {.rssi = rssi, .is_above = false}; if(float_is_equal(instance->threshold_rssi, SUBGHZ_RAW_THRESHOLD_MIN)) { diff --git a/applications/main/subghz/helpers/subghz_threshold_rssi.h b/applications/main/subghz/helpers/subghz_threshold_rssi.h index e28092acbc0..1d588e271b3 100644 --- a/applications/main/subghz/helpers/subghz_threshold_rssi.h +++ b/applications/main/subghz/helpers/subghz_threshold_rssi.h @@ -38,6 +38,7 @@ float subghz_threshold_rssi_get(SubGhzThresholdRssi* instance); /** Check threshold * * @param instance Pointer to a SubGhzThresholdRssi + * @param rssi Current RSSI * @return SubGhzThresholdRssiData */ -SubGhzThresholdRssiData subghz_threshold_get_rssi_data(SubGhzThresholdRssi* instance); +SubGhzThresholdRssiData subghz_threshold_get_rssi_data(SubGhzThresholdRssi* instance, float rssi); diff --git a/applications/main/subghz/helpers/subghz_txrx.c b/applications/main/subghz/helpers/subghz_txrx.c index 1517cb99892..f117d397412 100644 --- a/applications/main/subghz/helpers/subghz_txrx.c +++ b/applications/main/subghz/helpers/subghz_txrx.c @@ -1,9 +1,26 @@ #include "subghz_txrx_i.h" #include +#include +#include #define TAG "SubGhz" +static void subghz_txrx_radio_device_power_on(SubGhzTxRx* instance) { + UNUSED(instance); + uint8_t attempts = 0; + while(!furi_hal_power_is_otg_enabled() && attempts++ < 5) { + furi_hal_power_enable_otg(); + //CC1101 power-up time + furi_delay_ms(10); + } +} + +static void subghz_txrx_radio_device_power_off(SubGhzTxRx* instance) { + UNUSED(instance); + if(furi_hal_power_is_otg_enabled()) furi_hal_power_disable_otg(); +} + SubGhzTxRx* subghz_txrx_alloc() { SubGhzTxRx* instance = malloc(sizeof(SubGhzTxRx)); instance->setting = subghz_setting_alloc(); @@ -23,16 +40,15 @@ SubGhzTxRx* subghz_txrx_alloc() { instance->fff_data = flipper_format_string_alloc(); instance->environment = subghz_environment_alloc(); - instance->is_database_loaded = subghz_environment_load_keystore( - instance->environment, EXT_PATH("subghz/assets/keeloq_mfcodes")); - subghz_environment_load_keystore( - instance->environment, EXT_PATH("subghz/assets/keeloq_mfcodes_user")); + instance->is_database_loaded = + subghz_environment_load_keystore(instance->environment, SUBGHZ_KEYSTORE_DIR_NAME); + subghz_environment_load_keystore(instance->environment, SUBGHZ_KEYSTORE_DIR_USER_NAME); subghz_environment_set_came_atomo_rainbow_table_file_name( - instance->environment, EXT_PATH("subghz/assets/came_atomo")); + instance->environment, SUBGHZ_CAME_ATOMO_DIR_NAME); subghz_environment_set_alutech_at_4n_rainbow_table_file_name( - instance->environment, EXT_PATH("subghz/assets/alutech_at_4n")); + instance->environment, SUBGHZ_ALUTECH_AT_4N_DIR_NAME); subghz_environment_set_nice_flor_s_rainbow_table_file_name( - instance->environment, EXT_PATH("subghz/assets/nice_flor_s")); + instance->environment, SUBGHZ_NICE_FLOR_S_DIR_NAME); subghz_environment_set_protocol_registry( instance->environment, (void*)&subghz_protocol_registry); instance->receiver = subghz_receiver_alloc_init(instance->environment); @@ -43,18 +59,32 @@ SubGhzTxRx* subghz_txrx_alloc() { instance->worker, (SubGhzWorkerPairCallback)subghz_receiver_decode); subghz_worker_set_context(instance->worker, instance->receiver); + //set default device External + subghz_devices_init(); + instance->radio_device_type = SubGhzRadioDeviceTypeInternal; + instance->radio_device_type = + subghz_txrx_radio_device_set(instance, SubGhzRadioDeviceTypeExternalCC1101); + return instance; } void subghz_txrx_free(SubGhzTxRx* instance) { furi_assert(instance); + if(instance->radio_device_type != SubGhzRadioDeviceTypeInternal) { + subghz_txrx_radio_device_power_off(instance); + subghz_devices_end(instance->radio_device); + } + + subghz_devices_deinit(); + subghz_worker_free(instance->worker); subghz_receiver_free(instance->receiver); subghz_environment_free(instance->environment); flipper_format_free(instance->fff_data); furi_string_free(instance->preset->name); subghz_setting_free(instance->setting); + free(instance->preset); free(instance); } @@ -122,29 +152,26 @@ void subghz_txrx_get_frequency_and_modulation( static void subghz_txrx_begin(SubGhzTxRx* instance, uint8_t* preset_data) { furi_assert(instance); - furi_hal_subghz_reset(); - furi_hal_subghz_idle(); - furi_hal_subghz_load_custom_preset(preset_data); - furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow); + subghz_devices_reset(instance->radio_device); + subghz_devices_idle(instance->radio_device); + subghz_devices_load_preset(instance->radio_device, FuriHalSubGhzPresetCustom, preset_data); instance->txrx_state = SubGhzTxRxStateIDLE; } static uint32_t subghz_txrx_rx(SubGhzTxRx* instance, uint32_t frequency) { furi_assert(instance); - if(!furi_hal_subghz_is_frequency_valid(frequency)) { - furi_crash("SubGhz: Incorrect RX frequency."); - } + furi_assert( instance->txrx_state != SubGhzTxRxStateRx && instance->txrx_state != SubGhzTxRxStateSleep); - furi_hal_subghz_idle(); - uint32_t value = furi_hal_subghz_set_frequency_and_path(frequency); - furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow); - furi_hal_subghz_flush_rx(); + subghz_devices_idle(instance->radio_device); + + uint32_t value = subghz_devices_set_frequency(instance->radio_device, frequency); + subghz_devices_flush_rx(instance->radio_device); subghz_txrx_speaker_on(instance); - furi_hal_subghz_rx(); - furi_hal_subghz_start_async_rx(subghz_worker_rx_callback, instance->worker); + subghz_devices_start_async_rx( + instance->radio_device, subghz_worker_rx_callback, instance->worker); subghz_worker_start(instance->worker); instance->txrx_state = SubGhzTxRxStateRx; return value; @@ -153,7 +180,7 @@ static uint32_t subghz_txrx_rx(SubGhzTxRx* instance, uint32_t frequency) { static void subghz_txrx_idle(SubGhzTxRx* instance) { furi_assert(instance); furi_assert(instance->txrx_state != SubGhzTxRxStateSleep); - furi_hal_subghz_idle(); + subghz_devices_idle(instance->radio_device); subghz_txrx_speaker_off(instance); instance->txrx_state = SubGhzTxRxStateIDLE; } @@ -164,30 +191,26 @@ static void subghz_txrx_rx_end(SubGhzTxRx* instance) { if(subghz_worker_is_running(instance->worker)) { subghz_worker_stop(instance->worker); - furi_hal_subghz_stop_async_rx(); + subghz_devices_stop_async_rx(instance->radio_device); } - furi_hal_subghz_idle(); + subghz_devices_idle(instance->radio_device); subghz_txrx_speaker_off(instance); instance->txrx_state = SubGhzTxRxStateIDLE; } void subghz_txrx_sleep(SubGhzTxRx* instance) { furi_assert(instance); - furi_hal_subghz_sleep(); + subghz_devices_sleep(instance->radio_device); instance->txrx_state = SubGhzTxRxStateSleep; } static bool subghz_txrx_tx(SubGhzTxRx* instance, uint32_t frequency) { furi_assert(instance); - if(!furi_hal_subghz_is_frequency_valid(frequency)) { - furi_crash("SubGhz: Incorrect TX frequency."); - } furi_assert(instance->txrx_state != SubGhzTxRxStateSleep); - furi_hal_subghz_idle(); - furi_hal_subghz_set_frequency_and_path(frequency); - furi_hal_gpio_write(&gpio_cc1101_g0, false); - furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); - bool ret = furi_hal_subghz_tx(); + subghz_devices_idle(instance->radio_device); + subghz_devices_set_frequency(instance->radio_device, frequency); + + bool ret = subghz_devices_set_tx(instance->radio_device); if(ret) { subghz_txrx_speaker_on(instance); instance->txrx_state = SubGhzTxRxStateTx; @@ -249,8 +272,8 @@ SubGhzTxRxStartTxState subghz_txrx_tx_start(SubGhzTxRx* instance, FlipperFormat* if(ret == SubGhzTxRxStartTxStateOk) { //Start TX - furi_hal_subghz_start_async_tx( - subghz_transmitter_yield, instance->transmitter); + subghz_devices_start_async_tx( + instance->radio_device, subghz_transmitter_yield, instance->transmitter); } } else { ret = SubGhzTxRxStartTxStateErrorParserOthers; @@ -293,7 +316,7 @@ static void subghz_txrx_tx_stop(SubGhzTxRx* instance) { furi_assert(instance); furi_assert(instance->txrx_state == SubGhzTxRxStateTx); //Stop TX - furi_hal_subghz_stop_async_tx(); + subghz_devices_stop_async_tx(instance->radio_device); subghz_transmitter_stop(instance->transmitter); subghz_transmitter_free(instance->transmitter); @@ -306,7 +329,6 @@ static void subghz_txrx_tx_stop(SubGhzTxRx* instance) { subghz_txrx_idle(instance); subghz_txrx_speaker_off(instance); //Todo: Show message - // notification_message(notifications, &sequence_reset_red); } FlipperFormat* subghz_txrx_get_fff_data(SubGhzTxRx* instance) { @@ -356,7 +378,7 @@ void subghz_txrx_hopper_update(SubGhzTxRx* instance) { float rssi = -127.0f; if(instance->hopper_state != SubGhzHopperStateRSSITimeOut) { // See RSSI Calculation timings in CC1101 17.3 RSSI - rssi = furi_hal_subghz_get_rssi(); + rssi = subghz_devices_get_rssi(instance->radio_device); // Stay if RSSI is high enough if(rssi > -90.0f) { @@ -414,7 +436,7 @@ void subghz_txrx_speaker_on(SubGhzTxRx* instance) { furi_assert(instance); if(instance->speaker_state == SubGhzSpeakerStateEnable) { if(furi_hal_speaker_acquire(30)) { - furi_hal_subghz_set_async_mirror_pin(&gpio_speaker); + subghz_devices_set_async_mirror_pin(instance->radio_device, &gpio_speaker); } else { instance->speaker_state = SubGhzSpeakerStateDisable; } @@ -425,7 +447,7 @@ void subghz_txrx_speaker_off(SubGhzTxRx* instance) { furi_assert(instance); if(instance->speaker_state != SubGhzSpeakerStateDisable) { if(furi_hal_speaker_is_mine()) { - furi_hal_subghz_set_async_mirror_pin(NULL); + subghz_devices_set_async_mirror_pin(instance->radio_device, NULL); furi_hal_speaker_release(); if(instance->speaker_state == SubGhzSpeakerStateShutdown) instance->speaker_state = SubGhzSpeakerStateDisable; @@ -437,7 +459,7 @@ void subghz_txrx_speaker_mute(SubGhzTxRx* instance) { furi_assert(instance); if(instance->speaker_state == SubGhzSpeakerStateEnable) { if(furi_hal_speaker_is_mine()) { - furi_hal_subghz_set_async_mirror_pin(NULL); + subghz_devices_set_async_mirror_pin(instance->radio_device, NULL); } } } @@ -446,7 +468,7 @@ void subghz_txrx_speaker_unmute(SubGhzTxRx* instance) { furi_assert(instance); if(instance->speaker_state == SubGhzSpeakerStateEnable) { if(furi_hal_speaker_is_mine()) { - furi_hal_subghz_set_async_mirror_pin(&gpio_speaker); + subghz_devices_set_async_mirror_pin(instance->radio_device, &gpio_speaker); } } } @@ -519,3 +541,63 @@ void subghz_txrx_set_raw_file_encoder_worker_callback_end( callback, context); } + +bool subghz_txrx_radio_device_is_external_connected(SubGhzTxRx* instance, const char* name) { + furi_assert(instance); + + bool is_connect = false; + bool is_otg_enabled = furi_hal_power_is_otg_enabled(); + + if(!is_otg_enabled) { + subghz_txrx_radio_device_power_on(instance); + } + + is_connect = subghz_devices_is_connect(subghz_devices_get_by_name(name)); + + if(!is_otg_enabled) { + subghz_txrx_radio_device_power_off(instance); + } + return is_connect; +} + +SubGhzRadioDeviceType + subghz_txrx_radio_device_set(SubGhzTxRx* instance, SubGhzRadioDeviceType radio_device_type) { + furi_assert(instance); + + if(radio_device_type == SubGhzRadioDeviceTypeExternalCC1101 && + subghz_txrx_radio_device_is_external_connected(instance, SUBGHZ_DEVICE_CC1101_EXT_NAME)) { + subghz_txrx_radio_device_power_on(instance); + instance->radio_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_EXT_NAME); + subghz_devices_begin(instance->radio_device); + instance->radio_device_type = SubGhzRadioDeviceTypeExternalCC1101; + } else { + subghz_txrx_radio_device_power_off(instance); + if(instance->radio_device_type != SubGhzRadioDeviceTypeInternal) { + subghz_devices_end(instance->radio_device); + } + instance->radio_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME); + instance->radio_device_type = SubGhzRadioDeviceTypeInternal; + } + + return instance->radio_device_type; +} + +SubGhzRadioDeviceType subghz_txrx_radio_device_get(SubGhzTxRx* instance) { + furi_assert(instance); + return instance->radio_device_type; +} + +float subghz_txrx_radio_device_get_rssi(SubGhzTxRx* instance) { + furi_assert(instance); + return subghz_devices_get_rssi(instance->radio_device); +} + +const char* subghz_txrx_radio_device_get_name(SubGhzTxRx* instance) { + furi_assert(instance); + return subghz_devices_get_name(instance->radio_device); +} + +bool subghz_txrx_radio_device_is_frequecy_valid(SubGhzTxRx* instance, uint32_t frequency) { + furi_assert(instance); + return subghz_devices_is_frequency_valid(instance->radio_device, frequency); +} \ No newline at end of file diff --git a/applications/main/subghz/helpers/subghz_txrx.h b/applications/main/subghz/helpers/subghz_txrx.h index 0f2daf05d49..e49789206f0 100644 --- a/applications/main/subghz/helpers/subghz_txrx.h +++ b/applications/main/subghz/helpers/subghz_txrx.h @@ -7,6 +7,7 @@ #include #include #include +#include typedef struct SubGhzTxRx SubGhzTxRx; @@ -288,3 +289,48 @@ void subghz_txrx_set_raw_file_encoder_worker_callback_end( SubGhzTxRx* instance, SubGhzProtocolEncoderRAWCallbackEnd callback, void* context); + +/* Checking if an external radio device is connected +* +* @param instance Pointer to a SubGhzTxRx +* @param name Name of external radio device +* @return bool True if is connected to the external radio device +*/ +bool subghz_txrx_radio_device_is_external_connected(SubGhzTxRx* instance, const char* name); + +/* Set the selected radio device to use +* +* @param instance Pointer to a SubGhzTxRx +* @param radio_device_type Radio device type +* @return SubGhzRadioDeviceType Type of installed radio device +*/ +SubGhzRadioDeviceType + subghz_txrx_radio_device_set(SubGhzTxRx* instance, SubGhzRadioDeviceType radio_device_type); + +/* Get the selected radio device to use +* +* @param instance Pointer to a SubGhzTxRx +* @return SubGhzRadioDeviceType Type of installed radio device +*/ +SubGhzRadioDeviceType subghz_txrx_radio_device_get(SubGhzTxRx* instance); + +/* Get RSSI the selected radio device to use +* +* @param instance Pointer to a SubGhzTxRx +* @return float RSSI +*/ +float subghz_txrx_radio_device_get_rssi(SubGhzTxRx* instance); + +/* Get name the selected radio device to use +* +* @param instance Pointer to a SubGhzTxRx +* @return const char* Name of installed radio device +*/ +const char* subghz_txrx_radio_device_get_name(SubGhzTxRx* instance); + +/* Get get intelligence whether frequency the selected radio device to use +* +* @param instance Pointer to a SubGhzTxRx +* @return bool True if the frequency is valid +*/ +bool subghz_txrx_radio_device_is_frequecy_valid(SubGhzTxRx* instance, uint32_t frequency); \ No newline at end of file diff --git a/applications/main/subghz/helpers/subghz_txrx_i.h b/applications/main/subghz/helpers/subghz_txrx_i.h index bd0ad8b7be3..b7d74fd492f 100644 --- a/applications/main/subghz/helpers/subghz_txrx_i.h +++ b/applications/main/subghz/helpers/subghz_txrx_i.h @@ -21,6 +21,8 @@ struct SubGhzTxRx { SubGhzTxRxState txrx_state; SubGhzSpeakerState speaker_state; + const SubGhzDevice* radio_device; + SubGhzRadioDeviceType radio_device_type; SubGhzTxRxNeedSaveCallback need_save_callback; void* need_save_context; diff --git a/applications/main/subghz/helpers/subghz_types.h b/applications/main/subghz/helpers/subghz_types.h index 46bf940f469..8beb7b9ed09 100644 --- a/applications/main/subghz/helpers/subghz_types.h +++ b/applications/main/subghz/helpers/subghz_types.h @@ -35,6 +35,13 @@ typedef enum { SubGhzSpeakerStateEnable, } SubGhzSpeakerState; +/** SubGhzRadioDeviceType */ +typedef enum { + SubGhzRadioDeviceTypeAuto, + SubGhzRadioDeviceTypeInternal, + SubGhzRadioDeviceTypeExternalCC1101, +} SubGhzRadioDeviceType; + /** SubGhzRxKeyState state */ typedef enum { SubGhzRxKeyStateIDLE, diff --git a/applications/main/subghz/scenes/subghz_scene_config.h b/applications/main/subghz/scenes/subghz_scene_config.h index 86a30731792..97aa946e8ad 100644 --- a/applications/main/subghz/scenes/subghz_scene_config.h +++ b/applications/main/subghz/scenes/subghz_scene_config.h @@ -24,3 +24,4 @@ ADD_SCENE(subghz, delete_raw, DeleteRAW) ADD_SCENE(subghz, need_saving, NeedSaving) ADD_SCENE(subghz, rpc, Rpc) ADD_SCENE(subghz, region_info, RegionInfo) +ADD_SCENE(subghz, radio_settings, RadioSettings) diff --git a/applications/main/subghz/scenes/subghz_scene_radio_setting.c b/applications/main/subghz/scenes/subghz_scene_radio_setting.c new file mode 100644 index 00000000000..0a47d5bfdbd --- /dev/null +++ b/applications/main/subghz/scenes/subghz_scene_radio_setting.c @@ -0,0 +1,70 @@ +#include "../subghz_i.h" +#include +#include + +enum SubGhzRadioSettingIndex { + SubGhzRadioSettingIndexDevice, +}; + +#define RADIO_DEVICE_COUNT 2 +const char* const radio_device_text[RADIO_DEVICE_COUNT] = { + "Internal", + "External", +}; + +const uint32_t radio_device_value[RADIO_DEVICE_COUNT] = { + SubGhzRadioDeviceTypeInternal, + SubGhzRadioDeviceTypeExternalCC1101, +}; + +static void subghz_scene_radio_settings_set_device(VariableItem* item) { + SubGhz* subghz = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + if(!subghz_txrx_radio_device_is_external_connected( + subghz->txrx, SUBGHZ_DEVICE_CC1101_EXT_NAME) && + radio_device_value[index] == SubGhzRadioDeviceTypeExternalCC1101) { + //ToDo correct if there is more than 1 module + index = 0; + } + variable_item_set_current_value_text(item, radio_device_text[index]); + subghz_txrx_radio_device_set(subghz->txrx, radio_device_value[index]); +} + +void subghz_scene_radio_settings_on_enter(void* context) { + SubGhz* subghz = context; + VariableItem* item; + uint8_t value_index; + + uint8_t value_count_device = RADIO_DEVICE_COUNT; + if(subghz_txrx_radio_device_get(subghz->txrx) == SubGhzRadioDeviceTypeInternal && + !subghz_txrx_radio_device_is_external_connected(subghz->txrx, SUBGHZ_DEVICE_CC1101_EXT_NAME)) + value_count_device = 1; + item = variable_item_list_add( + subghz->variable_item_list, + "Module", + value_count_device, + subghz_scene_radio_settings_set_device, + subghz); + value_index = value_index_uint32( + subghz_txrx_radio_device_get(subghz->txrx), radio_device_value, value_count_device); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, radio_device_text[value_index]); + + view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdVariableItemList); +} + +bool subghz_scene_radio_settings_on_event(void* context, SceneManagerEvent event) { + SubGhz* subghz = context; + bool consumed = false; + UNUSED(subghz); + UNUSED(event); + + return consumed; +} + +void subghz_scene_radio_settings_on_exit(void* context) { + SubGhz* subghz = context; + variable_item_list_set_selected_item(subghz->variable_item_list, 0); + variable_item_list_reset(subghz->variable_item_list); +} diff --git a/applications/main/subghz/scenes/subghz_scene_read_raw.c b/applications/main/subghz/scenes/subghz_scene_read_raw.c index a29f86a078c..58e4b042954 100644 --- a/applications/main/subghz/scenes/subghz_scene_read_raw.c +++ b/applications/main/subghz/scenes/subghz_scene_read_raw.c @@ -48,6 +48,9 @@ static void subghz_scene_read_raw_update_statusbar(void* context) { furi_string_free(frequency_str); furi_string_free(modulation_str); + + subghz_read_raw_set_radio_device_type( + subghz->subghz_read_raw, subghz_txrx_radio_device_get(subghz->txrx)); } void subghz_scene_read_raw_callback(SubGhzCustomEvent event, void* context) { @@ -238,7 +241,9 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { furi_string_printf( temp_str, "%s/%s%s", SUBGHZ_RAW_FOLDER, RAW_FILE_NAME, SUBGHZ_APP_EXTENSION); subghz_protocol_raw_gen_fff_data( - subghz_txrx_get_fff_data(subghz->txrx), furi_string_get_cstr(temp_str)); + subghz_txrx_get_fff_data(subghz->txrx), + furi_string_get_cstr(temp_str), + subghz_txrx_radio_device_get_name(subghz->txrx)); furi_string_free(temp_str); if(spl_count > 0) { @@ -298,8 +303,8 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { subghz_read_raw_update_sample_write( subghz->subghz_read_raw, subghz_protocol_raw_get_sample_write(decoder_raw)); - SubGhzThresholdRssiData ret_rssi = - subghz_threshold_get_rssi_data(subghz->threshold_rssi); + SubGhzThresholdRssiData ret_rssi = subghz_threshold_get_rssi_data( + subghz->threshold_rssi, subghz_txrx_radio_device_get_rssi(subghz->txrx)); subghz_read_raw_add_data_rssi( subghz->subghz_read_raw, ret_rssi.rssi, ret_rssi.is_above); subghz_protocol_raw_save_to_file_pause(decoder_raw, !ret_rssi.is_above); diff --git a/applications/main/subghz/scenes/subghz_scene_receiver.c b/applications/main/subghz/scenes/subghz_scene_receiver.c index 6771f8213b5..6ab443579bb 100644 --- a/applications/main/subghz/scenes/subghz_scene_receiver.c +++ b/applications/main/subghz/scenes/subghz_scene_receiver.c @@ -56,6 +56,9 @@ static void subghz_scene_receiver_update_statusbar(void* context) { subghz->state_notifications = SubGhzNotificationStateIDLE; } furi_string_free(history_stat_str); + + subghz_view_receiver_set_radio_device_type( + subghz->subghz_receiver, subghz_txrx_radio_device_get(subghz->txrx)); } void subghz_scene_receiver_callback(SubGhzCustomEvent event, void* context) { @@ -189,7 +192,8 @@ bool subghz_scene_receiver_on_event(void* context, SceneManagerEvent event) { subghz_scene_receiver_update_statusbar(subghz); } - SubGhzThresholdRssiData ret_rssi = subghz_threshold_get_rssi_data(subghz->threshold_rssi); + SubGhzThresholdRssiData ret_rssi = subghz_threshold_get_rssi_data( + subghz->threshold_rssi, subghz_txrx_radio_device_get_rssi(subghz->txrx)); subghz_receiver_rssi(subghz->subghz_receiver, ret_rssi.rssi); subghz_protocol_decoder_bin_raw_data_input_rssi( diff --git a/applications/main/subghz/scenes/subghz_scene_save_name.c b/applications/main/subghz/scenes/subghz_scene_save_name.c index 7d0a4f4f8b7..86eddfe8e97 100644 --- a/applications/main/subghz/scenes/subghz_scene_save_name.c +++ b/applications/main/subghz/scenes/subghz_scene_save_name.c @@ -122,7 +122,8 @@ bool subghz_scene_save_name_on_event(void* context, SceneManagerEvent event) { SubGhzCustomEventManagerNoSet) { subghz_protocol_raw_gen_fff_data( subghz_txrx_get_fff_data(subghz->txrx), - furi_string_get_cstr(subghz->file_path)); + furi_string_get_cstr(subghz->file_path), + subghz_txrx_radio_device_get_name(subghz->txrx)); scene_manager_set_scene_state( subghz->scene_manager, SubGhzSceneReadRAW, SubGhzCustomEventManagerNoSet); } else { diff --git a/applications/main/subghz/scenes/subghz_scene_start.c b/applications/main/subghz/scenes/subghz_scene_start.c index 0ab5f123ed1..ce631b39812 100644 --- a/applications/main/subghz/scenes/subghz_scene_start.c +++ b/applications/main/subghz/scenes/subghz_scene_start.c @@ -8,7 +8,8 @@ enum SubmenuIndex { SubmenuIndexAddManually, SubmenuIndexFrequencyAnalyzer, SubmenuIndexReadRAW, - SubmenuIndexShowRegionInfo + SubmenuIndexShowRegionInfo, + SubmenuIndexRadioSetting, }; void subghz_scene_start_submenu_callback(void* context, uint32_t index) { @@ -49,6 +50,12 @@ void subghz_scene_start_on_enter(void* context) { SubmenuIndexShowRegionInfo, subghz_scene_start_submenu_callback, subghz); + submenu_add_item( + subghz->submenu, + "Radio Settings", + SubmenuIndexRadioSetting, + subghz_scene_start_submenu_callback, + subghz); if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { submenu_add_item( subghz->submenu, "Test", SubmenuIndexTest, subghz_scene_start_submenu_callback, subghz); @@ -104,6 +111,11 @@ bool subghz_scene_start_on_event(void* context, SceneManagerEvent event) { subghz->scene_manager, SubGhzSceneStart, SubmenuIndexShowRegionInfo); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneRegionInfo); return true; + } else if(event.event == SubmenuIndexRadioSetting) { + scene_manager_set_scene_state( + subghz->scene_manager, SubGhzSceneStart, SubmenuIndexRadioSetting); + scene_manager_next_scene(subghz->scene_manager, SubGhzSceneRadioSettings); + return true; } } return false; diff --git a/applications/main/subghz/scenes/subghz_scene_transmitter.c b/applications/main/subghz/scenes/subghz_scene_transmitter.c index 274dd61ad37..f83e44a0a1d 100644 --- a/applications/main/subghz/scenes/subghz_scene_transmitter.c +++ b/applications/main/subghz/scenes/subghz_scene_transmitter.c @@ -35,6 +35,8 @@ bool subghz_scene_transmitter_update_data_show(void* context) { furi_string_free(modulation_str); furi_string_free(key_str); } + subghz_view_transmitter_set_radio_device_type( + subghz->subghz_transmitter, subghz_txrx_radio_device_get(subghz->txrx)); return ret; } diff --git a/applications/main/subghz/subghz_cli.c b/applications/main/subghz/subghz_cli.c index 60845ac9986..bc7be507e73 100644 --- a/applications/main/subghz/subghz_cli.c +++ b/applications/main/subghz/subghz_cli.c @@ -10,6 +10,10 @@ #include #include #include +#include +#include +#include +#include #include "helpers/subghz_chat.h" @@ -24,6 +28,19 @@ #define SUBGHZ_REGION_FILENAME "/int/.region_data" +static void subghz_cli_radio_device_power_on() { + uint8_t attempts = 0; + while(!furi_hal_power_is_otg_enabled() && attempts++ < 5) { + furi_hal_power_enable_otg(); + //CC1101 power-up time + furi_delay_ms(10); + } +} + +static void subghz_cli_radio_device_power_off() { + if(furi_hal_power_is_otg_enabled()) furi_hal_power_disable_otg(); +} + void subghz_cli_command_tx_carrier(Cli* cli, FuriString* args, void* context) { UNUSED(context); uint32_t frequency = 433920000; @@ -44,7 +61,7 @@ void subghz_cli_command_tx_carrier(Cli* cli, FuriString* args, void* context) { } furi_hal_subghz_reset(); - furi_hal_subghz_load_preset(FuriHalSubGhzPresetOok650Async); + furi_hal_subghz_load_custom_preset(subghz_device_cc1101_preset_ook_650khz_async_regs); frequency = furi_hal_subghz_set_frequency_and_path(frequency); furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); @@ -88,7 +105,7 @@ void subghz_cli_command_rx_carrier(Cli* cli, FuriString* args, void* context) { } furi_hal_subghz_reset(); - furi_hal_subghz_load_preset(FuriHalSubGhzPresetOok650Async); + furi_hal_subghz_load_custom_preset(subghz_device_cc1101_preset_ook_650khz_async_regs); frequency = furi_hal_subghz_set_frequency_and_path(frequency); printf("Receiving at frequency %lu Hz\r\n", frequency); printf("Press CTRL+C to stop\r\n"); @@ -109,44 +126,70 @@ void subghz_cli_command_rx_carrier(Cli* cli, FuriString* args, void* context) { furi_hal_subghz_sleep(); } +static const SubGhzDevice* subghz_cli_command_get_device(uint32_t device_ind) { + const SubGhzDevice* device = NULL; + switch(device_ind) { + case 1: + subghz_cli_radio_device_power_on(); + device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_EXT_NAME); + break; + + default: + device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME); + break; + } + return device; +} + void subghz_cli_command_tx(Cli* cli, FuriString* args, void* context) { UNUSED(context); uint32_t frequency = 433920000; uint32_t key = 0x0074BADE; uint32_t repeat = 10; uint32_t te = 403; + uint32_t device_ind = 0; // 0 - CC1101_INT, 1 - CC1101_EXT if(furi_string_size(args)) { - int ret = - sscanf(furi_string_get_cstr(args), "%lx %lu %lu %lu", &key, &frequency, &te, &repeat); - if(ret != 4) { + int ret = sscanf( + furi_string_get_cstr(args), + "%lx %lu %lu %lu %lu", + &key, + &frequency, + &te, + &repeat, + &device_ind); + if(ret != 5) { printf( - "sscanf returned %d, key: %lx, frequency: %lu, te:%lu, repeat: %lu\r\n", + "sscanf returned %d, key: %lx, frequency: %lu, te: %lu, repeat: %lu, device: %lu\r\n ", ret, key, frequency, te, - repeat); + repeat, + device_ind); cli_print_usage( "subghz tx", - "<3 Byte Key: in hex> ", + "<3 Byte Key: in hex> ", furi_string_get_cstr(args)); return; } - if(!furi_hal_subghz_is_frequency_valid(frequency)) { - printf( - "Frequency must be in " SUBGHZ_FREQUENCY_RANGE_STR " range, not %lu\r\n", - frequency); - return; - } } - + subghz_devices_init(); + const SubGhzDevice* device = subghz_cli_command_get_device(device_ind); + if(!subghz_devices_is_frequency_valid(device, frequency)) { + printf( + "Frequency must be in " SUBGHZ_FREQUENCY_RANGE_STR " range, not %lu\r\n", frequency); + subghz_devices_deinit(); + subghz_cli_radio_device_power_off(); + return; + } printf( - "Transmitting at %lu, key %lx, te %lu, repeat %lu. Press CTRL+C to stop\r\n", + "Transmitting at %lu, key %lx, te %lu, repeat %lu device %lu. Press CTRL+C to stop\r\n", frequency, key, te, - repeat); + repeat, + device_ind); FuriString* flipper_format_string = furi_string_alloc_printf( "Protocol: Princeton\n" @@ -170,25 +213,29 @@ void subghz_cli_command_tx(Cli* cli, FuriString* args, void* context) { SubGhzTransmitter* transmitter = subghz_transmitter_alloc_init(environment, "Princeton"); subghz_transmitter_deserialize(transmitter, flipper_format); - furi_hal_subghz_reset(); - furi_hal_subghz_load_preset(FuriHalSubGhzPresetOok650Async); - frequency = furi_hal_subghz_set_frequency_and_path(frequency); + subghz_devices_begin(device); + subghz_devices_reset(device); + subghz_devices_load_preset(device, FuriHalSubGhzPresetOok650Async, NULL); + frequency = subghz_devices_set_frequency(device, frequency); furi_hal_power_suppress_charge_enter(); - - if(furi_hal_subghz_start_async_tx(subghz_transmitter_yield, transmitter)) { - while(!(furi_hal_subghz_is_async_tx_complete() || cli_cmd_interrupt_received(cli))) { + if(subghz_devices_start_async_tx(device, subghz_transmitter_yield, transmitter)) { + while(!(subghz_devices_is_async_complete_tx(device) || cli_cmd_interrupt_received(cli))) { printf("."); fflush(stdout); furi_delay_ms(333); } - furi_hal_subghz_stop_async_tx(); + subghz_devices_stop_async_tx(device); } else { printf("Transmission on this frequency is restricted in your region\r\n"); } - furi_hal_subghz_sleep(); + subghz_devices_sleep(device); + subghz_devices_end(device); + subghz_devices_deinit(); + subghz_cli_radio_device_power_off(); + furi_hal_power_suppress_charge_exit(); flipper_format_free(flipper_format); @@ -233,21 +280,29 @@ static void subghz_cli_command_rx_callback( void subghz_cli_command_rx(Cli* cli, FuriString* args, void* context) { UNUSED(context); uint32_t frequency = 433920000; + uint32_t device_ind = 0; // 0 - CC1101_INT, 1 - CC1101_EXT if(furi_string_size(args)) { - int ret = sscanf(furi_string_get_cstr(args), "%lu", &frequency); - if(ret != 1) { - printf("sscanf returned %d, frequency: %lu\r\n", ret, frequency); - cli_print_usage("subghz rx", "", furi_string_get_cstr(args)); - return; - } - if(!furi_hal_subghz_is_frequency_valid(frequency)) { + int ret = sscanf(furi_string_get_cstr(args), "%lu %lu", &frequency, &device_ind); + if(ret != 2) { printf( - "Frequency must be in " SUBGHZ_FREQUENCY_RANGE_STR " range, not %lu\r\n", - frequency); + "sscanf returned %d, frequency: %lu device: %lu\r\n", ret, frequency, device_ind); + cli_print_usage( + "subghz rx", + " ", + furi_string_get_cstr(args)); return; } } + subghz_devices_init(); + const SubGhzDevice* device = subghz_cli_command_get_device(device_ind); + if(!subghz_devices_is_frequency_valid(device, frequency)) { + printf( + "Frequency must be in " SUBGHZ_FREQUENCY_RANGE_STR " range, not %lu\r\n", frequency); + subghz_devices_deinit(); + subghz_cli_radio_device_power_off(); + return; + } // Allocate context and buffers SubGhzCliCommandRx* instance = malloc(sizeof(SubGhzCliCommandRx)); @@ -256,14 +311,14 @@ void subghz_cli_command_rx(Cli* cli, FuriString* args, void* context) { furi_check(instance->stream); SubGhzEnvironment* environment = subghz_environment_alloc(); - subghz_environment_load_keystore(environment, EXT_PATH("subghz/assets/keeloq_mfcodes")); - subghz_environment_load_keystore(environment, EXT_PATH("subghz/assets/keeloq_mfcodes_user")); + subghz_environment_load_keystore(environment, SUBGHZ_KEYSTORE_DIR_NAME); + subghz_environment_load_keystore(environment, SUBGHZ_KEYSTORE_DIR_USER_NAME); subghz_environment_set_came_atomo_rainbow_table_file_name( - environment, EXT_PATH("subghz/assets/came_atomo")); + environment, SUBGHZ_CAME_ATOMO_DIR_NAME); subghz_environment_set_alutech_at_4n_rainbow_table_file_name( - environment, EXT_PATH("subghz/assets/alutech_at_4n")); + environment, SUBGHZ_ALUTECH_AT_4N_DIR_NAME); subghz_environment_set_nice_flor_s_rainbow_table_file_name( - environment, EXT_PATH("subghz/assets/nice_flor_s")); + environment, SUBGHZ_NICE_FLOR_S_DIR_NAME); subghz_environment_set_protocol_registry(environment, (void*)&subghz_protocol_registry); SubGhzReceiver* receiver = subghz_receiver_alloc_init(environment); @@ -271,18 +326,21 @@ void subghz_cli_command_rx(Cli* cli, FuriString* args, void* context) { subghz_receiver_set_rx_callback(receiver, subghz_cli_command_rx_callback, instance); // Configure radio - furi_hal_subghz_reset(); - furi_hal_subghz_load_preset(FuriHalSubGhzPresetOok650Async); - frequency = furi_hal_subghz_set_frequency_and_path(frequency); - furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow); + subghz_devices_begin(device); + subghz_devices_reset(device); + subghz_devices_load_preset(device, FuriHalSubGhzPresetOok650Async, NULL); + frequency = subghz_devices_set_frequency(device, frequency); furi_hal_power_suppress_charge_enter(); // Prepare and start RX - furi_hal_subghz_start_async_rx(subghz_cli_command_rx_capture_callback, instance); + subghz_devices_start_async_rx(device, subghz_cli_command_rx_capture_callback, instance); // Wait for packets to arrive - printf("Listening at %lu. Press CTRL+C to stop\r\n", frequency); + printf( + "Listening at frequency: %lu device: %lu. Press CTRL+C to stop\r\n", + frequency, + device_ind); LevelDuration level_duration; while(!cli_cmd_interrupt_received(cli)) { int ret = furi_stream_buffer_receive( @@ -300,8 +358,11 @@ void subghz_cli_command_rx(Cli* cli, FuriString* args, void* context) { } // Shutdown radio - furi_hal_subghz_stop_async_rx(); - furi_hal_subghz_sleep(); + subghz_devices_stop_async_rx(device); + subghz_devices_sleep(device); + subghz_devices_end(device); + subghz_devices_deinit(); + subghz_cli_radio_device_power_off(); furi_hal_power_suppress_charge_exit(); @@ -341,7 +402,7 @@ void subghz_cli_command_rx_raw(Cli* cli, FuriString* args, void* context) { // Configure radio furi_hal_subghz_reset(); - furi_hal_subghz_load_preset(FuriHalSubGhzPresetOok270Async); + furi_hal_subghz_load_custom_preset(subghz_device_cc1101_preset_ook_270khz_async_regs); frequency = furi_hal_subghz_set_frequency_and_path(frequency); furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow); @@ -389,6 +450,7 @@ void subghz_cli_command_rx_raw(Cli* cli, FuriString* args, void* context) { furi_stream_buffer_free(instance->stream); free(instance); } + void subghz_cli_command_decode_raw(Cli* cli, FuriString* args, void* context) { UNUSED(context); FuriString* file_name; @@ -442,25 +504,23 @@ void subghz_cli_command_decode_raw(Cli* cli, FuriString* args, void* context) { SubGhzCliCommandRx* instance = malloc(sizeof(SubGhzCliCommandRx)); SubGhzEnvironment* environment = subghz_environment_alloc(); - if(subghz_environment_load_keystore( - environment, EXT_PATH("subghz/assets/keeloq_mfcodes"))) { + if(subghz_environment_load_keystore(environment, SUBGHZ_KEYSTORE_DIR_NAME)) { printf("SubGhz decode_raw: Load_keystore keeloq_mfcodes \033[0;32mOK\033[0m\r\n"); } else { printf("SubGhz decode_raw: Load_keystore keeloq_mfcodes \033[0;31mERROR\033[0m\r\n"); } - if(subghz_environment_load_keystore( - environment, EXT_PATH("subghz/assets/keeloq_mfcodes_user"))) { + if(subghz_environment_load_keystore(environment, SUBGHZ_KEYSTORE_DIR_USER_NAME)) { printf("SubGhz decode_raw: Load_keystore keeloq_mfcodes_user \033[0;32mOK\033[0m\r\n"); } else { printf( "SubGhz decode_raw: Load_keystore keeloq_mfcodes_user \033[0;31mERROR\033[0m\r\n"); } subghz_environment_set_came_atomo_rainbow_table_file_name( - environment, EXT_PATH("subghz/assets/came_atomo")); + environment, SUBGHZ_CAME_ATOMO_DIR_NAME); subghz_environment_set_alutech_at_4n_rainbow_table_file_name( - environment, EXT_PATH("subghz/assets/alutech_at_4n")); + environment, SUBGHZ_ALUTECH_AT_4N_DIR_NAME); subghz_environment_set_nice_flor_s_rainbow_table_file_name( - environment, EXT_PATH("subghz/assets/nice_flor_s")); + environment, SUBGHZ_NICE_FLOR_S_DIR_NAME); subghz_environment_set_protocol_registry(environment, (void*)&subghz_protocol_registry); SubGhzReceiver* receiver = subghz_receiver_alloc_init(environment); @@ -468,7 +528,8 @@ void subghz_cli_command_decode_raw(Cli* cli, FuriString* args, void* context) { subghz_receiver_set_rx_callback(receiver, subghz_cli_command_rx_callback, instance); SubGhzFileEncoderWorker* file_worker_encoder = subghz_file_encoder_worker_alloc(); - if(subghz_file_encoder_worker_start(file_worker_encoder, furi_string_get_cstr(file_name))) { + if(subghz_file_encoder_worker_start( + file_worker_encoder, furi_string_get_cstr(file_name), NULL)) { //the worker needs a file in order to open and read part of the file furi_delay_ms(100); } @@ -510,10 +571,11 @@ static void subghz_cli_command_print_usage() { printf("subghz \r\n"); printf("Cmd list:\r\n"); - printf("\tchat \t - Chat with other Flippers\r\n"); printf( - "\ttx <3 byte Key: in hex> \t - Transmitting key\r\n"); - printf("\trx \t - Receive\r\n"); + "\tchat \t - Chat with other Flippers\r\n"); + printf( + "\ttx <3 byte Key: in hex> \t - Transmitting key\r\n"); + printf("\trx \t - Receive\r\n"); printf("\trx_raw \t - Receive RAW\r\n"); printf("\tdecode_raw \t - Testing\r\n"); @@ -611,21 +673,29 @@ static void subghz_cli_command_encrypt_raw(Cli* cli, FuriString* args) { static void subghz_cli_command_chat(Cli* cli, FuriString* args) { uint32_t frequency = 433920000; + uint32_t device_ind = 0; // 0 - CC1101_INT, 1 - CC1101_EXT if(furi_string_size(args)) { - int ret = sscanf(furi_string_get_cstr(args), "%lu", &frequency); - if(ret != 1) { - printf("sscanf returned %d, frequency: %lu\r\n", ret, frequency); - cli_print_usage("subghz chat", "", furi_string_get_cstr(args)); - return; - } - if(!furi_hal_subghz_is_frequency_valid(frequency)) { - printf( - "Frequency must be in " SUBGHZ_FREQUENCY_RANGE_STR " range, not %lu\r\n", - frequency); + int ret = sscanf(furi_string_get_cstr(args), "%lu %lu", &frequency, &device_ind); + if(ret != 2) { + printf("sscanf returned %d, Frequency: %lu\r\n", ret, frequency); + printf("sscanf returned %d, Device: %lu\r\n", ret, device_ind); + cli_print_usage( + "subghz chat", + " ", + furi_string_get_cstr(args)); return; } } + subghz_devices_init(); + const SubGhzDevice* device = subghz_cli_command_get_device(device_ind); + if(!subghz_devices_is_frequency_valid(device, frequency)) { + printf( + "Frequency must be in " SUBGHZ_FREQUENCY_RANGE_STR " range, not %lu\r\n", frequency); + subghz_devices_deinit(); + subghz_cli_radio_device_power_off(); + return; + } if(!furi_hal_region_is_frequency_allowed(frequency)) { printf( "In your region, only reception on this frequency (%lu) is allowed,\r\n" @@ -635,7 +705,8 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) { } SubGhzChatWorker* subghz_chat = subghz_chat_worker_alloc(cli); - if(!subghz_chat_worker_start(subghz_chat, frequency)) { + + if(!subghz_chat_worker_start(subghz_chat, device, frequency)) { printf("Startup error SubGhzChatWorker\r\n"); if(subghz_chat_worker_is_running(subghz_chat)) { @@ -781,6 +852,10 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) { furi_string_free(name); furi_string_free(output); furi_string_free(sysmsg); + + subghz_devices_deinit(); + subghz_cli_radio_device_power_off(); + furi_hal_power_suppress_charge_exit(); furi_record_close(RECORD_NOTIFICATION); diff --git a/applications/main/subghz/subghz_i.c b/applications/main/subghz/subghz_i.c index 8036ed5f7ac..55036846cae 100644 --- a/applications/main/subghz/subghz_i.c +++ b/applications/main/subghz/subghz_i.c @@ -115,7 +115,7 @@ bool subghz_key_load(SubGhz* subghz, const char* file_path, bool show_dialog) { break; } - if(!furi_hal_subghz_is_frequency_valid(temp_data32)) { + if(!subghz_txrx_radio_device_is_frequecy_valid(subghz->txrx, temp_data32)) { FURI_LOG_E(TAG, "Frequency not supported"); break; } @@ -163,7 +163,8 @@ bool subghz_key_load(SubGhz* subghz, const char* file_path, bool show_dialog) { if(!strcmp(furi_string_get_cstr(temp_str), "RAW")) { //if RAW subghz->load_type_file = SubGhzLoadTypeFileRaw; - subghz_protocol_raw_gen_fff_data(fff_data, file_path); + subghz_protocol_raw_gen_fff_data( + fff_data, file_path, subghz_txrx_radio_device_get_name(subghz->txrx)); } else { subghz->load_type_file = SubGhzLoadTypeFileKey; stream_copy_full( diff --git a/applications/main/subghz/views/receiver.c b/applications/main/subghz/views/receiver.c index f84ddfed086..e1014b81109 100644 --- a/applications/main/subghz/views/receiver.c +++ b/applications/main/subghz/views/receiver.c @@ -62,6 +62,7 @@ typedef struct { uint16_t history_item; SubGhzViewReceiverBarShow bar_show; uint8_t u_rssi; + SubGhzRadioDeviceType device_type; } SubGhzViewReceiverModel; void subghz_receiver_rssi(SubGhzViewReceiver* instance, float rssi) { @@ -173,6 +174,17 @@ void subghz_view_receiver_add_data_statusbar( true); } +void subghz_view_receiver_set_radio_device_type( + SubGhzViewReceiver* subghz_receiver, + SubGhzRadioDeviceType device_type) { + furi_assert(subghz_receiver); + with_view_model( + subghz_receiver->view, + SubGhzViewReceiverModel * model, + { model->device_type = device_type; }, + true); +} + static void subghz_view_receiver_draw_frame(Canvas* canvas, uint16_t idx, bool scrollbar) { canvas_set_color(canvas, ColorBlack); canvas_draw_box(canvas, 0, 0 + idx * FRAME_HEIGHT, scrollbar ? 122 : 127, FRAME_HEIGHT); @@ -190,9 +202,9 @@ static void subghz_view_receiver_draw_frame(Canvas* canvas, uint16_t idx, bool s static void subghz_view_rssi_draw(Canvas* canvas, SubGhzViewReceiverModel* model) { for(uint8_t i = 1; i < model->u_rssi; i++) { if(i % 5) { - canvas_draw_dot(canvas, 46 + i, 50); - canvas_draw_dot(canvas, 47 + i, 51); canvas_draw_dot(canvas, 46 + i, 52); + canvas_draw_dot(canvas, 47 + i, 53); + canvas_draw_dot(canvas, 46 + i, 54); } } } @@ -232,22 +244,28 @@ void subghz_view_receiver_draw(Canvas* canvas, SubGhzViewReceiverModel* model) { canvas_set_color(canvas, ColorBlack); if(model->history_item == 0) { - canvas_draw_icon(canvas, 0, 0, &I_Scanning_123x52); + canvas_draw_icon(canvas, 0, 0, &I_Scanning_short_96x52); canvas_set_font(canvas, FontPrimary); canvas_draw_str(canvas, 63, 44, "Scanning..."); canvas_set_font(canvas, FontSecondary); } + if(model->device_type == SubGhzRadioDeviceTypeInternal) { + canvas_draw_icon(canvas, 108, 0, &I_Internal_antenna_20x12); + } else { + canvas_draw_icon(canvas, 108, 0, &I_External_antenna_20x12); + } + subghz_view_rssi_draw(canvas, model); switch(model->bar_show) { case SubGhzViewReceiverBarShowLock: - canvas_draw_icon(canvas, 64, 55, &I_Lock_7x8); - canvas_draw_str(canvas, 74, 62, "Locked"); + canvas_draw_icon(canvas, 64, 56, &I_Lock_7x8); + canvas_draw_str(canvas, 74, 64, "Locked"); break; case SubGhzViewReceiverBarShowToUnlockPress: - canvas_draw_str(canvas, 44, 62, furi_string_get_cstr(model->frequency_str)); - canvas_draw_str(canvas, 79, 62, furi_string_get_cstr(model->preset_str)); - canvas_draw_str(canvas, 96, 62, furi_string_get_cstr(model->history_stat_str)); + canvas_draw_str(canvas, 44, 64, furi_string_get_cstr(model->frequency_str)); + canvas_draw_str(canvas, 79, 64, furi_string_get_cstr(model->preset_str)); + canvas_draw_str(canvas, 97, 64, furi_string_get_cstr(model->history_stat_str)); canvas_set_font(canvas, FontSecondary); elements_bold_rounded_frame(canvas, 14, 8, 99, 48); elements_multiline_text(canvas, 65, 26, "To unlock\npress:"); @@ -258,13 +276,13 @@ void subghz_view_receiver_draw(Canvas* canvas, SubGhzViewReceiverModel* model) { canvas_draw_dot(canvas, 17, 61); break; case SubGhzViewReceiverBarShowUnlock: - canvas_draw_icon(canvas, 64, 55, &I_Unlock_7x8); - canvas_draw_str(canvas, 74, 62, "Unlocked"); + canvas_draw_icon(canvas, 64, 56, &I_Unlock_7x8); + canvas_draw_str(canvas, 74, 64, "Unlocked"); break; default: - canvas_draw_str(canvas, 44, 62, furi_string_get_cstr(model->frequency_str)); - canvas_draw_str(canvas, 79, 62, furi_string_get_cstr(model->preset_str)); - canvas_draw_str(canvas, 96, 62, furi_string_get_cstr(model->history_stat_str)); + canvas_draw_str(canvas, 44, 64, furi_string_get_cstr(model->frequency_str)); + canvas_draw_str(canvas, 79, 64, furi_string_get_cstr(model->preset_str)); + canvas_draw_str(canvas, 97, 64, furi_string_get_cstr(model->history_stat_str)); break; } } diff --git a/applications/main/subghz/views/receiver.h b/applications/main/subghz/views/receiver.h index 5119105e93b..c91c069386c 100644 --- a/applications/main/subghz/views/receiver.h +++ b/applications/main/subghz/views/receiver.h @@ -29,6 +29,10 @@ void subghz_view_receiver_add_data_statusbar( const char* preset_str, const char* history_stat_str); +void subghz_view_receiver_set_radio_device_type( + SubGhzViewReceiver* subghz_receiver, + SubGhzRadioDeviceType device_type); + void subghz_view_receiver_add_item_to_menu( SubGhzViewReceiver* subghz_receiver, const char* name, diff --git a/applications/main/subghz/views/subghz_frequency_analyzer.c b/applications/main/subghz/views/subghz_frequency_analyzer.c index 325664f4a9c..d90401678a8 100644 --- a/applications/main/subghz/views/subghz_frequency_analyzer.c +++ b/applications/main/subghz/views/subghz_frequency_analyzer.c @@ -177,7 +177,8 @@ void subghz_frequency_analyzer_draw(Canvas* canvas, SubGhzFrequencyAnalyzerModel } subghz_frequency_analyzer_log_frequency_draw(canvas, model); } else { - canvas_draw_str(canvas, 20, 8, "Frequency Analyzer"); + canvas_draw_str(canvas, 0, 8, "Frequency Analyzer"); + canvas_draw_icon(canvas, 108, 0, &I_Internal_antenna_20x12); canvas_draw_str(canvas, 0, 64, "RSSI"); subghz_frequency_analyzer_draw_rssi(canvas, model->rssi, 20, 64); diff --git a/applications/main/subghz/views/subghz_read_raw.c b/applications/main/subghz/views/subghz_read_raw.c index 2ff598b6058..88ac129ca9e 100644 --- a/applications/main/subghz/views/subghz_read_raw.c +++ b/applications/main/subghz/views/subghz_read_raw.c @@ -29,6 +29,7 @@ typedef struct { uint8_t ind_sin; SubGhzReadRAWStatus status; float raw_threshold_rssi; + SubGhzRadioDeviceType device_type; } SubGhzReadRAWModel; void subghz_read_raw_set_callback( @@ -56,6 +57,14 @@ void subghz_read_raw_add_data_statusbar( true); } +void subghz_read_raw_set_radio_device_type( + SubGhzReadRAW* instance, + SubGhzRadioDeviceType device_type) { + furi_assert(instance); + with_view_model( + instance->view, SubGhzReadRAWModel * model, { model->device_type = device_type; }, true); +} + void subghz_read_raw_add_data_rssi(SubGhzReadRAW* instance, float rssi, bool trace) { furi_assert(instance); uint8_t u_rssi = 0; @@ -279,11 +288,16 @@ void subghz_read_raw_draw(Canvas* canvas, SubGhzReadRAWModel* model) { uint8_t graphics_mode = 1; canvas_set_color(canvas, ColorBlack); canvas_set_font(canvas, FontSecondary); - canvas_draw_str(canvas, 5, 7, furi_string_get_cstr(model->frequency_str)); - canvas_draw_str(canvas, 40, 7, furi_string_get_cstr(model->preset_str)); + canvas_draw_str(canvas, 0, 9, furi_string_get_cstr(model->frequency_str)); + canvas_draw_str(canvas, 35, 9, furi_string_get_cstr(model->preset_str)); canvas_draw_str_aligned( - canvas, 126, 0, AlignRight, AlignTop, furi_string_get_cstr(model->sample_write)); + canvas, 106, 2, AlignRight, AlignTop, furi_string_get_cstr(model->sample_write)); + if(model->device_type == SubGhzRadioDeviceTypeInternal) { + canvas_draw_icon(canvas, 108, 0, &I_Internal_antenna_20x12); + } else { + canvas_draw_icon(canvas, 108, 0, &I_External_antenna_20x12); + } canvas_draw_line(canvas, 0, 14, 115, 14); canvas_draw_line(canvas, 0, 48, 115, 48); canvas_draw_line(canvas, 115, 14, 115, 48); diff --git a/applications/main/subghz/views/subghz_read_raw.h b/applications/main/subghz/views/subghz_read_raw.h index 31aa9db6fdd..83403e9750c 100644 --- a/applications/main/subghz/views/subghz_read_raw.h +++ b/applications/main/subghz/views/subghz_read_raw.h @@ -1,6 +1,7 @@ #pragma once #include +#include "../helpers/subghz_types.h" #include "../helpers/subghz_custom_event.h" #define SUBGHZ_RAW_THRESHOLD_MIN -90.0f @@ -36,6 +37,10 @@ void subghz_read_raw_add_data_statusbar( const char* frequency_str, const char* preset_str); +void subghz_read_raw_set_radio_device_type( + SubGhzReadRAW* instance, + SubGhzRadioDeviceType device_type); + void subghz_read_raw_update_sample_write(SubGhzReadRAW* instance, size_t sample); void subghz_read_raw_stop_send(SubGhzReadRAW* instance); diff --git a/applications/main/subghz/views/subghz_test_carrier.c b/applications/main/subghz/views/subghz_test_carrier.c index e533a6aac4e..254a4127bc4 100644 --- a/applications/main/subghz/views/subghz_test_carrier.c +++ b/applications/main/subghz/views/subghz_test_carrier.c @@ -1,6 +1,7 @@ #include "subghz_test_carrier.h" #include "../subghz_i.h" #include "../helpers/subghz_testing.h" +#include #include #include @@ -138,7 +139,7 @@ void subghz_test_carrier_enter(void* context) { SubGhzTestCarrier* subghz_test_carrier = context; furi_hal_subghz_reset(); - furi_hal_subghz_load_preset(FuriHalSubGhzPresetOok650Async); + furi_hal_subghz_load_custom_preset(subghz_device_cc1101_preset_ook_650khz_async_regs); furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow); diff --git a/applications/main/subghz/views/subghz_test_packet.c b/applications/main/subghz/views/subghz_test_packet.c index 43502180cea..bc2c474b513 100644 --- a/applications/main/subghz/views/subghz_test_packet.c +++ b/applications/main/subghz/views/subghz_test_packet.c @@ -1,6 +1,7 @@ #include "subghz_test_packet.h" #include "../subghz_i.h" #include "../helpers/subghz_testing.h" +#include #include #include @@ -194,7 +195,7 @@ void subghz_test_packet_enter(void* context) { SubGhzTestPacket* instance = context; furi_hal_subghz_reset(); - furi_hal_subghz_load_preset(FuriHalSubGhzPresetOok650Async); + furi_hal_subghz_load_custom_preset(subghz_device_cc1101_preset_ook_650khz_async_regs); with_view_model( instance->view, diff --git a/applications/main/subghz/views/subghz_test_static.c b/applications/main/subghz/views/subghz_test_static.c index 6abefda763e..197af21fb5f 100644 --- a/applications/main/subghz/views/subghz_test_static.c +++ b/applications/main/subghz/views/subghz_test_static.c @@ -1,6 +1,7 @@ #include "subghz_test_static.h" #include "../subghz_i.h" #include "../helpers/subghz_testing.h" +#include #include #include @@ -141,7 +142,7 @@ void subghz_test_static_enter(void* context) { SubGhzTestStatic* instance = context; furi_hal_subghz_reset(); - furi_hal_subghz_load_preset(FuriHalSubGhzPresetOok650Async); + furi_hal_subghz_load_custom_preset(subghz_device_cc1101_preset_ook_650khz_async_regs); furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); furi_hal_gpio_write(&gpio_cc1101_g0, false); diff --git a/applications/main/subghz/views/transmitter.c b/applications/main/subghz/views/transmitter.c index 86dc17a38f7..2a876f8c268 100644 --- a/applications/main/subghz/views/transmitter.c +++ b/applications/main/subghz/views/transmitter.c @@ -15,6 +15,7 @@ typedef struct { FuriString* preset_str; FuriString* key_str; bool show_button; + SubGhzRadioDeviceType device_type; } SubGhzViewTransmitterModel; void subghz_view_transmitter_set_callback( @@ -46,6 +47,17 @@ void subghz_view_transmitter_add_data_to_show( true); } +void subghz_view_transmitter_set_radio_device_type( + SubGhzViewTransmitter* subghz_transmitter, + SubGhzRadioDeviceType device_type) { + furi_assert(subghz_transmitter); + with_view_model( + subghz_transmitter->view, + SubGhzViewTransmitterModel * model, + { model->device_type = device_type; }, + true); +} + static void subghz_view_transmitter_button_right(Canvas* canvas, const char* str) { const uint8_t button_height = 12; const uint8_t vertical_offset = 3; @@ -56,7 +68,7 @@ static void subghz_view_transmitter_button_right(Canvas* canvas, const char* str const uint8_t icon_width_with_offset = icon_get_width(icon) + icon_offset; const uint8_t button_width = string_width + horizontal_offset * 2 + icon_width_with_offset; - const uint8_t x = (canvas_width(canvas) - button_width) / 2 + 40; + const uint8_t x = (canvas_width(canvas) - button_width) / 2 + 44; const uint8_t y = canvas_height(canvas); canvas_draw_box(canvas, x, y - button_height, button_width, button_height); @@ -88,7 +100,14 @@ void subghz_view_transmitter_draw(Canvas* canvas, SubGhzViewTransmitterModel* mo canvas, 0, 0, AlignLeft, AlignTop, furi_string_get_cstr(model->key_str)); canvas_draw_str(canvas, 78, 7, furi_string_get_cstr(model->frequency_str)); canvas_draw_str(canvas, 113, 7, furi_string_get_cstr(model->preset_str)); - if(model->show_button) subghz_view_transmitter_button_right(canvas, "Send"); + if(model->show_button) { + if(model->device_type == SubGhzRadioDeviceTypeInternal) { + canvas_draw_icon(canvas, 108, 39, &I_Internal_antenna_20x12); + } else { + canvas_draw_icon(canvas, 108, 39, &I_External_antenna_20x12); + } + subghz_view_transmitter_button_right(canvas, "Send"); + } } bool subghz_view_transmitter_input(InputEvent* event, void* context) { diff --git a/applications/main/subghz/views/transmitter.h b/applications/main/subghz/views/transmitter.h index 06aae7c6bf7..19da3145c95 100644 --- a/applications/main/subghz/views/transmitter.h +++ b/applications/main/subghz/views/transmitter.h @@ -1,6 +1,7 @@ #pragma once #include +#include "../helpers/subghz_types.h" #include "../helpers/subghz_custom_event.h" typedef struct SubGhzViewTransmitter SubGhzViewTransmitter; @@ -12,6 +13,10 @@ void subghz_view_transmitter_set_callback( SubGhzViewTransmitterCallback callback, void* context); +void subghz_view_transmitter_set_radio_device_type( + SubGhzViewTransmitter* subghz_transmitter, + SubGhzRadioDeviceType device_type); + SubGhzViewTransmitter* subghz_view_transmitter_alloc(); void subghz_view_transmitter_free(SubGhzViewTransmitter* subghz_transmitter); diff --git a/assets/icons/SubGhz/External_antenna_20x12.png b/assets/icons/SubGhz/External_antenna_20x12.png new file mode 100644 index 0000000000000000000000000000000000000000..940087071a1c4225eb48383e3a93d5c841b638b2 GIT binary patch literal 990 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 N!3~Mzlm%~0{|8*@s)qmo diff --git a/assets/icons/SubGhz/Scanning_short_96x52.png b/assets/icons/SubGhz/Scanning_short_96x52.png new file mode 100644 index 0000000000000000000000000000000000000000..718d0e695a2560bbb7ccd538c062375d2a318ffb GIT binary patch literal 1388 zcmeAS@N?(olHy`uVBq!ia0vp^2|#SZ!3-pwBG0}CQj#UE5hcO-X(i=}MX3yqDfvmM z3ZA)%>8U}fi7AzZCsS=07??FPLn2Bde0{8v^KOF1ees}+T7#dW-K+~^CEYLU9GXQxDrqI_HztY@Xxa#7Ppj3o=u^L<) zQdy9yACy|0Us{w5jJz~ukW~d%&PAz-CHX}m`T04pkPOJkFUc>?$S+VZGSM?t(C|%6 z&ddXeXo9u)`dWGB6_+IDC8v72*eU@H(aX$Cv2rvwbTM{yvotVrF*h`Hb+t5ib#ZZZ zGBYr7HZyQ^c7^G6$xklLP0cHT=}kfCb;PL`lp=BqfHu3N7G;*DrnnX5=PH1GZIy}J zE#^4QgX&Ge?G_81di8;h(FesXQe4A?fC&i1gr{C02cG^@^MHxI2$;HE)=X;#<_xv~ zpAgso|NjG-p@%NlF)%P5_jGX#skrrK#_ef`6*ydP{{Me@b6A>-Lx{=S+e`Fw7pH3% zdAs~uznOdQKDGRP;?HGHaQkgH+0OUlYP}u%=DQ!xddxlW<9jtjncLh35yqCy>x5P@ z99nmph1Eg!gFqI;m$L2IZAmtkId|qVUWuQv{-flTsXLiZSp3i5+YxWBeR>{4#?3=K zQI)4O879AZQ!=f=OphVwlFMhA6*GHUS2Jun{nDgi4&yF~hwUt~FTby;Ok>#fvsCFC zgLT90@}S8k5r-}+1~*7Jtl;ZBy)z@GLQshF(2R*k^si5`SIfI4o-plV-QgL*9Sk=a zbl8*MN4oOyJ#{*I*J09={JmL|?tkbM6X($R#JRC=Vu_J%Y75Vf{xaL$l#n(7>J9Z>Rsez4>?m+v^4&(=dnl-7VtQ3hUR|<$h?CUGgiufh#fR z%(}0KHCtpPcD3>gZd$&^{MVzu8kg;Bj?5L<)Uo%9dbPy-BY$?*-Bo?}_2;U(sTYb~ z&aKl-jNh-QDG;}};pXvkx4&CUAG$r=-JXeuLGKE4-|ks^mx9VaPgg&ebxsLQ09S_o Aa{vGU literal 0 HcmV?d00001 diff --git a/documentation/FuriHalBus.md b/documentation/FuriHalBus.md index 5c754018b93..230a98050fd 100644 --- a/documentation/FuriHalBus.md +++ b/documentation/FuriHalBus.md @@ -78,9 +78,9 @@ The system will take over any given peripheral only when the respective feature | ADC | | | | QUADSPI | | | | TIM1 | yes | subghz, lfrfid, nfc, infrared, etc... | -| TIM2 | yes | -- | +| TIM2 | yes | subghz, infrared, etc... | | TIM16 | yes | speaker | -| TIM17 | | | +| TIM17 | yes | cc1101_ext | | LPTIM1 | yes | tickless idle timer | | LPTIM2 | yes | pwm | | SAI1 | | | @@ -104,10 +104,10 @@ Below is the list of DMA channels and their usage by the system. | -- | 5 | | | | -- | 6 | | | | -- | 7 | | | -| DMA2 | 1 | yes | infrared, lfrfid, subghz | +| DMA2 | 1 | yes | infrared, lfrfid, subghz, | | -- | 2 | yes | -- | -| -- | 3 | yes | SPI | -| -- | 4 | yes | SPI | -| -- | 5 | | | -| -- | 6 | | | -| -- | 7 | | | +| -- | 3 | yes | cc1101_ext | +| -- | 4 | yes | cc1101_ext | +| -- | 5 | yes | cc1101_ext | +| -- | 6 | yes | SPI | +| -- | 7 | yes | SPI | diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index a689d5a21b8..1884fe43373 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,31.3,, +Version,+,32.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -283,6 +283,7 @@ Function,-,LL_mDelay,void,uint32_t Function,-,SystemCoreClockUpdate,void, Function,-,SystemInit,void, Function,-,_Exit,void,int +Function,+,__aeabi_uldivmod,void*,"uint64_t, uint64_t" Function,-,__assert,void,"const char*, int, const char*" Function,+,__assert_func,void,"const char*, int, const char*, const char*" Function,+,__clear_cache,void,"void*, void*" diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 0183540f657..f4dec15a77f 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,31.3,, +Version,+,32.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -56,7 +56,6 @@ Header,+,firmware/targets/f7/furi_hal/furi_hal_rfid.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_spi_config.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_spi_types.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_subghz.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_subghz_configs.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_target_hw.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_uart.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_usb_cdc.h,, @@ -186,6 +185,7 @@ Header,+,lib/subghz/blocks/decoder.h,, Header,+,lib/subghz/blocks/encoder.h,, Header,+,lib/subghz/blocks/generic.h,, Header,+,lib/subghz/blocks/math.h,, +Header,+,lib/subghz/devices/cc1101_configs.h,, Header,+,lib/subghz/environment.h,, Header,+,lib/subghz/protocols/raw.h,, Header,+,lib/subghz/receiver.h,, @@ -317,6 +317,7 @@ Function,-,LL_mDelay,void,uint32_t Function,-,SystemCoreClockUpdate,void, Function,-,SystemInit,void, Function,-,_Exit,void,int +Function,+,__aeabi_uldivmod,void*,"uint64_t, uint64_t" Function,-,__assert,void,"const char*, int, const char*" Function,+,__assert_func,void,"const char*, int, const char*, const char*" Function,+,__clear_cache,void,"void*, void*" @@ -657,26 +658,6 @@ Function,+,canvas_width,uint8_t,const Canvas* Function,-,cbrt,double,double Function,-,cbrtf,float,float Function,-,cbrtl,long double,long double -Function,+,cc1101_calibrate,void,FuriHalSpiBusHandle* -Function,+,cc1101_flush_rx,void,FuriHalSpiBusHandle* -Function,+,cc1101_flush_tx,void,FuriHalSpiBusHandle* -Function,-,cc1101_get_partnumber,uint8_t,FuriHalSpiBusHandle* -Function,+,cc1101_get_rssi,uint8_t,FuriHalSpiBusHandle* -Function,+,cc1101_get_status,CC1101Status,FuriHalSpiBusHandle* -Function,-,cc1101_get_version,uint8_t,FuriHalSpiBusHandle* -Function,+,cc1101_read_fifo,uint8_t,"FuriHalSpiBusHandle*, uint8_t*, uint8_t*" -Function,+,cc1101_read_reg,CC1101Status,"FuriHalSpiBusHandle*, uint8_t, uint8_t*" -Function,+,cc1101_reset,void,FuriHalSpiBusHandle* -Function,+,cc1101_set_frequency,uint32_t,"FuriHalSpiBusHandle*, uint32_t" -Function,-,cc1101_set_intermediate_frequency,uint32_t,"FuriHalSpiBusHandle*, uint32_t" -Function,+,cc1101_set_pa_table,void,"FuriHalSpiBusHandle*, const uint8_t[8]" -Function,+,cc1101_shutdown,void,FuriHalSpiBusHandle* -Function,+,cc1101_strobe,CC1101Status,"FuriHalSpiBusHandle*, uint8_t" -Function,+,cc1101_switch_to_idle,void,FuriHalSpiBusHandle* -Function,+,cc1101_switch_to_rx,void,FuriHalSpiBusHandle* -Function,+,cc1101_switch_to_tx,void,FuriHalSpiBusHandle* -Function,+,cc1101_write_fifo,uint8_t,"FuriHalSpiBusHandle*, const uint8_t*, uint8_t" -Function,+,cc1101_write_reg,CC1101Status,"FuriHalSpiBusHandle*, uint8_t, uint8_t" Function,-,ceil,double,double Function,-,ceilf,float,float Function,-,ceill,long double,long double @@ -1383,6 +1364,7 @@ Function,+,furi_hal_spi_release,void,FuriHalSpiBusHandle* Function,-,furi_hal_subghz_dump_state,void, Function,+,furi_hal_subghz_flush_rx,void, Function,+,furi_hal_subghz_flush_tx,void, +Function,+,furi_hal_subghz_get_data_gpio,const GpioPin*, Function,+,furi_hal_subghz_get_lqi,uint8_t, Function,+,furi_hal_subghz_get_rssi,float, Function,+,furi_hal_subghz_idle,void, @@ -1390,10 +1372,9 @@ Function,-,furi_hal_subghz_init,void, Function,+,furi_hal_subghz_is_async_tx_complete,_Bool, Function,+,furi_hal_subghz_is_frequency_valid,_Bool,uint32_t Function,+,furi_hal_subghz_is_rx_data_crc_valid,_Bool, -Function,+,furi_hal_subghz_load_custom_preset,void,uint8_t* +Function,+,furi_hal_subghz_load_custom_preset,void,const uint8_t* Function,+,furi_hal_subghz_load_patable,void,const uint8_t[8] -Function,+,furi_hal_subghz_load_preset,void,FuriHalSubGhzPreset -Function,+,furi_hal_subghz_load_registers,void,uint8_t* +Function,+,furi_hal_subghz_load_registers,void,const uint8_t* Function,+,furi_hal_subghz_read_packet,void,"uint8_t*, uint8_t*" Function,+,furi_hal_subghz_reset,void, Function,+,furi_hal_subghz_rx,void, @@ -1401,7 +1382,7 @@ Function,+,furi_hal_subghz_rx_pipe_not_empty,_Bool, Function,+,furi_hal_subghz_set_async_mirror_pin,void,const GpioPin* Function,+,furi_hal_subghz_set_frequency,uint32_t,uint32_t Function,+,furi_hal_subghz_set_frequency_and_path,uint32_t,uint32_t -Function,+,furi_hal_subghz_set_path,void,FuriHalSubGhzPath +Function,-,furi_hal_subghz_set_path,void,FuriHalSubGhzPath Function,-,furi_hal_subghz_shutdown,void, Function,+,furi_hal_subghz_sleep,void, Function,+,furi_hal_subghz_start_async_rx,void,"FuriHalSubGhzCaptureCallback, void*" @@ -2686,6 +2667,36 @@ Function,+,subghz_block_generic_deserialize,SubGhzProtocolStatus,"SubGhzBlockGen Function,+,subghz_block_generic_deserialize_check_count_bit,SubGhzProtocolStatus,"SubGhzBlockGeneric*, FlipperFormat*, uint16_t" Function,+,subghz_block_generic_get_preset_name,void,"const char*, FuriString*" Function,+,subghz_block_generic_serialize,SubGhzProtocolStatus,"SubGhzBlockGeneric*, FlipperFormat*, SubGhzRadioPreset*" +Function,+,subghz_devices_begin,_Bool,const SubGhzDevice* +Function,+,subghz_devices_deinit,void, +Function,+,subghz_devices_end,void,const SubGhzDevice* +Function,+,subghz_devices_flush_rx,void,const SubGhzDevice* +Function,+,subghz_devices_flush_tx,void,const SubGhzDevice* +Function,+,subghz_devices_get_by_name,const SubGhzDevice*,const char* +Function,+,subghz_devices_get_data_gpio,const GpioPin*,const SubGhzDevice* +Function,+,subghz_devices_get_lqi,uint8_t,const SubGhzDevice* +Function,+,subghz_devices_get_name,const char*,const SubGhzDevice* +Function,+,subghz_devices_get_rssi,float,const SubGhzDevice* +Function,+,subghz_devices_idle,void,const SubGhzDevice* +Function,+,subghz_devices_init,void, +Function,+,subghz_devices_is_async_complete_tx,_Bool,const SubGhzDevice* +Function,+,subghz_devices_is_connect,_Bool,const SubGhzDevice* +Function,+,subghz_devices_is_frequency_valid,_Bool,"const SubGhzDevice*, uint32_t" +Function,+,subghz_devices_is_rx_data_crc_valid,_Bool,const SubGhzDevice* +Function,+,subghz_devices_load_preset,void,"const SubGhzDevice*, FuriHalSubGhzPreset, uint8_t*" +Function,+,subghz_devices_read_packet,void,"const SubGhzDevice*, uint8_t*, uint8_t*" +Function,+,subghz_devices_reset,void,const SubGhzDevice* +Function,+,subghz_devices_rx_pipe_not_empty,_Bool,const SubGhzDevice* +Function,+,subghz_devices_set_async_mirror_pin,void,"const SubGhzDevice*, const GpioPin*" +Function,+,subghz_devices_set_frequency,uint32_t,"const SubGhzDevice*, uint32_t" +Function,+,subghz_devices_set_rx,void,const SubGhzDevice* +Function,+,subghz_devices_set_tx,_Bool,const SubGhzDevice* +Function,+,subghz_devices_sleep,void,const SubGhzDevice* +Function,+,subghz_devices_start_async_rx,void,"const SubGhzDevice*, void*, void*" +Function,+,subghz_devices_start_async_tx,_Bool,"const SubGhzDevice*, void*, void*" +Function,+,subghz_devices_stop_async_rx,void,const SubGhzDevice* +Function,+,subghz_devices_stop_async_tx,void,const SubGhzDevice* +Function,+,subghz_devices_write_packet,void,"const SubGhzDevice*, const uint8_t*, uint8_t" Function,+,subghz_environment_alloc,SubGhzEnvironment*, Function,+,subghz_environment_free,void,SubGhzEnvironment* Function,+,subghz_environment_get_alutech_at_4n_rainbow_table_file_name,const char*,SubGhzEnvironment* @@ -2744,7 +2755,7 @@ Function,+,subghz_protocol_encoder_raw_free,void,void* Function,+,subghz_protocol_encoder_raw_stop,void,void* Function,+,subghz_protocol_encoder_raw_yield,LevelDuration,void* Function,+,subghz_protocol_raw_file_encoder_worker_set_callback_end,void,"SubGhzProtocolEncoderRAW*, SubGhzProtocolEncoderRAWCallbackEnd, void*" -Function,+,subghz_protocol_raw_gen_fff_data,void,"FlipperFormat*, const char*" +Function,+,subghz_protocol_raw_gen_fff_data,void,"FlipperFormat*, const char*, const char*" Function,+,subghz_protocol_raw_get_sample_write,size_t,SubGhzProtocolDecoderRAW* Function,+,subghz_protocol_raw_save_to_file_init,_Bool,"SubGhzProtocolDecoderRAW*, const char*, SubGhzRadioPreset*" Function,+,subghz_protocol_raw_save_to_file_pause,void,"SubGhzProtocolDecoderRAW*, _Bool" @@ -2788,7 +2799,7 @@ Function,+,subghz_tx_rx_worker_free,void,SubGhzTxRxWorker* Function,+,subghz_tx_rx_worker_is_running,_Bool,SubGhzTxRxWorker* Function,+,subghz_tx_rx_worker_read,size_t,"SubGhzTxRxWorker*, uint8_t*, size_t" Function,+,subghz_tx_rx_worker_set_callback_have_read,void,"SubGhzTxRxWorker*, SubGhzTxRxWorkerCallbackHaveRead, void*" -Function,+,subghz_tx_rx_worker_start,_Bool,"SubGhzTxRxWorker*, uint32_t" +Function,+,subghz_tx_rx_worker_start,_Bool,"SubGhzTxRxWorker*, const SubGhzDevice*, uint32_t" Function,+,subghz_tx_rx_worker_stop,void,SubGhzTxRxWorker* Function,+,subghz_tx_rx_worker_write,_Bool,"SubGhzTxRxWorker*, uint8_t*, size_t" Function,+,subghz_worker_alloc,SubGhzWorker*, @@ -3389,6 +3400,12 @@ Variable,+,sequence_set_vibro_on,const NotificationSequence, Variable,+,sequence_single_vibro,const NotificationSequence, Variable,+,sequence_solid_yellow,const NotificationSequence, Variable,+,sequence_success,const NotificationSequence, +Variable,+,subghz_device_cc1101_preset_2fsk_dev2_38khz_async_regs,const uint8_t[], +Variable,+,subghz_device_cc1101_preset_2fsk_dev47_6khz_async_regs,const uint8_t[], +Variable,+,subghz_device_cc1101_preset_gfsk_9_99kb_async_regs,const uint8_t[], +Variable,+,subghz_device_cc1101_preset_msk_99_97kb_async_regs,const uint8_t[], +Variable,+,subghz_device_cc1101_preset_ook_270khz_async_regs,const uint8_t[], +Variable,+,subghz_device_cc1101_preset_ook_650khz_async_regs,const uint8_t[], Variable,+,subghz_protocol_raw,const SubGhzProtocol, Variable,+,subghz_protocol_raw_decoder,const SubGhzProtocolDecoder, Variable,+,subghz_protocol_raw_encoder,const SubGhzProtocolEncoder, diff --git a/firmware/targets/f7/furi_hal/furi_hal_spi.c b/firmware/targets/f7/furi_hal/furi_hal_spi.c index 42b85479955..17769832b6a 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_spi.c +++ b/firmware/targets/f7/furi_hal/furi_hal_spi.c @@ -12,10 +12,10 @@ #define TAG "FuriHalSpi" #define SPI_DMA DMA2 -#define SPI_DMA_RX_CHANNEL LL_DMA_CHANNEL_3 -#define SPI_DMA_TX_CHANNEL LL_DMA_CHANNEL_4 -#define SPI_DMA_RX_IRQ FuriHalInterruptIdDma2Ch3 -#define SPI_DMA_TX_IRQ FuriHalInterruptIdDma2Ch4 +#define SPI_DMA_RX_CHANNEL LL_DMA_CHANNEL_6 +#define SPI_DMA_TX_CHANNEL LL_DMA_CHANNEL_7 +#define SPI_DMA_RX_IRQ FuriHalInterruptIdDma2Ch6 +#define SPI_DMA_TX_IRQ FuriHalInterruptIdDma2Ch7 #define SPI_DMA_RX_DEF SPI_DMA, SPI_DMA_RX_CHANNEL #define SPI_DMA_TX_DEF SPI_DMA, SPI_DMA_TX_CHANNEL @@ -170,18 +170,18 @@ bool furi_hal_spi_bus_trx( } static void spi_dma_isr() { -#if SPI_DMA_RX_CHANNEL == LL_DMA_CHANNEL_3 - if(LL_DMA_IsActiveFlag_TC3(SPI_DMA) && LL_DMA_IsEnabledIT_TC(SPI_DMA_RX_DEF)) { - LL_DMA_ClearFlag_TC3(SPI_DMA); +#if SPI_DMA_RX_CHANNEL == LL_DMA_CHANNEL_6 + if(LL_DMA_IsActiveFlag_TC6(SPI_DMA) && LL_DMA_IsEnabledIT_TC(SPI_DMA_RX_DEF)) { + LL_DMA_ClearFlag_TC6(SPI_DMA); furi_check(furi_semaphore_release(spi_dma_completed) == FuriStatusOk); } #else #error Update this code. Would you kindly? #endif -#if SPI_DMA_TX_CHANNEL == LL_DMA_CHANNEL_4 - if(LL_DMA_IsActiveFlag_TC4(SPI_DMA) && LL_DMA_IsEnabledIT_TC(SPI_DMA_TX_DEF)) { - LL_DMA_ClearFlag_TC4(SPI_DMA); +#if SPI_DMA_TX_CHANNEL == LL_DMA_CHANNEL_7 + if(LL_DMA_IsActiveFlag_TC7(SPI_DMA) && LL_DMA_IsEnabledIT_TC(SPI_DMA_TX_DEF)) { + LL_DMA_ClearFlag_TC7(SPI_DMA); furi_check(furi_semaphore_release(spi_dma_completed) == FuriStatusOk); } #else @@ -241,8 +241,8 @@ bool furi_hal_spi_bus_trx_dma( dma_config.Priority = LL_DMA_PRIORITY_MEDIUM; LL_DMA_Init(SPI_DMA_TX_DEF, &dma_config); -#if SPI_DMA_TX_CHANNEL == LL_DMA_CHANNEL_4 - LL_DMA_ClearFlag_TC4(SPI_DMA); +#if SPI_DMA_TX_CHANNEL == LL_DMA_CHANNEL_7 + LL_DMA_ClearFlag_TC7(SPI_DMA); #else #error Update this code. Would you kindly? #endif @@ -315,8 +315,8 @@ bool furi_hal_spi_bus_trx_dma( dma_config.Priority = LL_DMA_PRIORITY_MEDIUM; LL_DMA_Init(SPI_DMA_RX_DEF, &dma_config); -#if SPI_DMA_RX_CHANNEL == LL_DMA_CHANNEL_3 - LL_DMA_ClearFlag_TC3(SPI_DMA); +#if SPI_DMA_RX_CHANNEL == LL_DMA_CHANNEL_6 + LL_DMA_ClearFlag_TC6(SPI_DMA); #else #error Update this code. Would you kindly? #endif diff --git a/firmware/targets/f7/furi_hal/furi_hal_subghz.c b/firmware/targets/f7/furi_hal/furi_hal_subghz.c index 6d671a9e15b..ac5adefb8df 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_subghz.c +++ b/firmware/targets/f7/furi_hal/furi_hal_subghz.c @@ -1,6 +1,5 @@ #include -#include - +#include #include #include #include @@ -27,17 +26,36 @@ static uint32_t furi_hal_subghz_debug_gpio_buff[2]; #define SUBGHZ_DMA_CH1_DEF SUBGHZ_DMA, SUBGHZ_DMA_CH1_CHANNEL #define SUBGHZ_DMA_CH2_DEF SUBGHZ_DMA, SUBGHZ_DMA_CH2_CHANNEL +/** SubGhz state */ +typedef enum { + SubGhzStateInit, /**< Init pending */ + + SubGhzStateIdle, /**< Idle, energy save mode */ + + SubGhzStateAsyncRx, /**< Async RX started */ + + SubGhzStateAsyncTx, /**< Async TX started, DMA and timer is on */ + SubGhzStateAsyncTxLast, /**< Async TX continue, DMA completed and timer got last value to go */ + SubGhzStateAsyncTxEnd, /**< Async TX complete, cleanup needed */ + +} SubGhzState; + +/** SubGhz regulation, receive transmission on the current frequency for the + * region */ +typedef enum { + SubGhzRegulationOnlyRx, /**only Rx*/ + SubGhzRegulationTxRx, /**TxRx*/ +} SubGhzRegulation; + typedef struct { volatile SubGhzState state; volatile SubGhzRegulation regulation; - volatile FuriHalSubGhzPreset preset; const GpioPin* async_mirror_pin; } FuriHalSubGhz; volatile FuriHalSubGhz furi_hal_subghz = { .state = SubGhzStateInit, .regulation = SubGhzRegulationTxRx, - .preset = FuriHalSubGhzPresetIDLE, .async_mirror_pin = NULL, }; @@ -45,10 +63,13 @@ void furi_hal_subghz_set_async_mirror_pin(const GpioPin* pin) { furi_hal_subghz.async_mirror_pin = pin; } +const GpioPin* furi_hal_subghz_get_data_gpio() { + return &gpio_cc1101_g0; +} + void furi_hal_subghz_init() { furi_assert(furi_hal_subghz.state == SubGhzStateInit); furi_hal_subghz.state = SubGhzStateIdle; - furi_hal_subghz.preset = FuriHalSubGhzPresetIDLE; furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); @@ -102,8 +123,6 @@ void furi_hal_subghz_sleep() { cc1101_shutdown(&furi_hal_spi_bus_handle_subghz); furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); - - furi_hal_subghz.preset = FuriHalSubGhzPresetIDLE; } void furi_hal_subghz_dump_state() { @@ -115,34 +134,7 @@ void furi_hal_subghz_dump_state() { furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); } -void furi_hal_subghz_load_preset(FuriHalSubGhzPreset preset) { - if(preset == FuriHalSubGhzPresetOok650Async) { - furi_hal_subghz_load_registers((uint8_t*)furi_hal_subghz_preset_ook_650khz_async_regs); - furi_hal_subghz_load_patable(furi_hal_subghz_preset_ook_async_patable); - } else if(preset == FuriHalSubGhzPresetOok270Async) { - furi_hal_subghz_load_registers((uint8_t*)furi_hal_subghz_preset_ook_270khz_async_regs); - furi_hal_subghz_load_patable(furi_hal_subghz_preset_ook_async_patable); - } else if(preset == FuriHalSubGhzPreset2FSKDev238Async) { - furi_hal_subghz_load_registers( - (uint8_t*)furi_hal_subghz_preset_2fsk_dev2_38khz_async_regs); - furi_hal_subghz_load_patable(furi_hal_subghz_preset_2fsk_async_patable); - } else if(preset == FuriHalSubGhzPreset2FSKDev476Async) { - furi_hal_subghz_load_registers( - (uint8_t*)furi_hal_subghz_preset_2fsk_dev47_6khz_async_regs); - furi_hal_subghz_load_patable(furi_hal_subghz_preset_2fsk_async_patable); - } else if(preset == FuriHalSubGhzPresetMSK99_97KbAsync) { - furi_hal_subghz_load_registers((uint8_t*)furi_hal_subghz_preset_msk_99_97kb_async_regs); - furi_hal_subghz_load_patable(furi_hal_subghz_preset_msk_async_patable); - } else if(preset == FuriHalSubGhzPresetGFSK9_99KbAsync) { - furi_hal_subghz_load_registers((uint8_t*)furi_hal_subghz_preset_gfsk_9_99kb_async_regs); - furi_hal_subghz_load_patable(furi_hal_subghz_preset_gfsk_async_patable); - } else { - furi_crash("SubGhz: Missing config."); - } - furi_hal_subghz.preset = preset; -} - -void furi_hal_subghz_load_custom_preset(uint8_t* preset_data) { +void furi_hal_subghz_load_custom_preset(const uint8_t* preset_data) { //load config furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); cc1101_reset(&furi_hal_spi_bus_handle_subghz); @@ -157,7 +149,6 @@ void furi_hal_subghz_load_custom_preset(uint8_t* preset_data) { //load pa table memcpy(&pa[0], &preset_data[i + 2], 8); furi_hal_subghz_load_patable(pa); - furi_hal_subghz.preset = FuriHalSubGhzPresetCustom; //show debug if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { @@ -173,7 +164,7 @@ void furi_hal_subghz_load_custom_preset(uint8_t* preset_data) { } } -void furi_hal_subghz_load_registers(uint8_t* data) { +void furi_hal_subghz_load_registers(const uint8_t* data) { furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); cc1101_reset(&furi_hal_spi_bus_handle_subghz); uint32_t i = 0; diff --git a/firmware/targets/f7/furi_hal/furi_hal_subghz.h b/firmware/targets/f7/furi_hal/furi_hal_subghz.h index 102981dbeb5..855ce31619c 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_subghz.h +++ b/firmware/targets/f7/furi_hal/furi_hal_subghz.h @@ -5,6 +5,8 @@ #pragma once +#include + #include #include #include @@ -20,18 +22,6 @@ extern "C" { #define API_HAL_SUBGHZ_ASYNC_TX_BUFFER_HALF (API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL / 2) #define API_HAL_SUBGHZ_ASYNC_TX_GUARD_TIME 999 -/** Radio Presets */ -typedef enum { - FuriHalSubGhzPresetIDLE, /**< default configuration */ - FuriHalSubGhzPresetOok270Async, /**< OOK, bandwidth 270kHz, asynchronous */ - FuriHalSubGhzPresetOok650Async, /**< OOK, bandwidth 650kHz, asynchronous */ - FuriHalSubGhzPreset2FSKDev238Async, /**< FM, deviation 2.380371 kHz, asynchronous */ - FuriHalSubGhzPreset2FSKDev476Async, /**< FM, deviation 47.60742 kHz, asynchronous */ - FuriHalSubGhzPresetMSK99_97KbAsync, /**< MSK, deviation 47.60742 kHz, 99.97Kb/s, asynchronous */ - FuriHalSubGhzPresetGFSK9_99KbAsync, /**< GFSK, deviation 19.042969 kHz, 9.996Kb/s, asynchronous */ - FuriHalSubGhzPresetCustom, /**Custom Preset*/ -} FuriHalSubGhzPreset; - /** Switchable Radio Paths */ typedef enum { FuriHalSubGhzPathIsolate, /**< Isolate Radio from antenna */ @@ -40,27 +30,6 @@ typedef enum { FuriHalSubGhzPath868, /**< Center Frequency: 868MHz. Path 3: SW1RF3-SW2RF3, LCLC */ } FuriHalSubGhzPath; -/** SubGhz state */ -typedef enum { - SubGhzStateInit, /**< Init pending */ - - SubGhzStateIdle, /**< Idle, energy save mode */ - - SubGhzStateAsyncRx, /**< Async RX started */ - - SubGhzStateAsyncTx, /**< Async TX started, DMA and timer is on */ - SubGhzStateAsyncTxLast, /**< Async TX continue, DMA completed and timer got last value to go */ - SubGhzStateAsyncTxEnd, /**< Async TX complete, cleanup needed */ - -} SubGhzState; - -/** SubGhz regulation, receive transmission on the current frequency for the - * region */ -typedef enum { - SubGhzRegulationOnlyRx, /**only Rx*/ - SubGhzRegulationTxRx, /**TxRx*/ -} SubGhzRegulation; - /* Mirror RX/TX async modulation signal to specified pin * * @warning Configures pin to output mode. Make sure it is not connected @@ -70,6 +39,12 @@ typedef enum { */ void furi_hal_subghz_set_async_mirror_pin(const GpioPin* pin); +/** Get data GPIO + * + * @return pointer to the gpio pin structure + */ +const GpioPin* furi_hal_subghz_get_data_gpio(); + /** Initialize and switch to power save mode Used by internal API-HAL * initialization routine Can be used to reinitialize device to safe state and * send it to sleep @@ -84,23 +59,17 @@ void furi_hal_subghz_sleep(); */ void furi_hal_subghz_dump_state(); -/** Load registers from preset by preset name - * - * @param preset to load - */ -void furi_hal_subghz_load_preset(FuriHalSubGhzPreset preset); - /** Load custom registers from preset * * @param preset_data registers to load */ -void furi_hal_subghz_load_custom_preset(uint8_t* preset_data); +void furi_hal_subghz_load_custom_preset(const uint8_t* preset_data); /** Load registers * * @param data Registers data */ -void furi_hal_subghz_load_registers(uint8_t* data); +void furi_hal_subghz_load_registers(const uint8_t* data); /** Load PATABLE * diff --git a/firmware/targets/f7/furi_hal/furi_hal_subghz_configs.h b/firmware/targets/f7/furi_hal/furi_hal_subghz_configs.h deleted file mode 100644 index 5ea17b6ddd0..00000000000 --- a/firmware/targets/f7/furi_hal/furi_hal_subghz_configs.h +++ /dev/null @@ -1,314 +0,0 @@ -#pragma once - -#include - -static const uint8_t furi_hal_subghz_preset_ook_270khz_async_regs[][2] = { - // https://e2e.ti.com/support/wireless-connectivity/sub-1-ghz-group/sub-1-ghz/f/sub-1-ghz-forum/382066/cc1101---don-t-know-the-correct-registers-configuration - - /* GPIO GD0 */ - {CC1101_IOCFG0, 0x0D}, // GD0 as async serial data output/input - - /* FIFO and internals */ - {CC1101_FIFOTHR, 0x47}, // The only important bit is ADC_RETENTION, FIFO Tx=33 Rx=32 - - /* Packet engine */ - {CC1101_PKTCTRL0, 0x32}, // Async, continious, no whitening - - /* Frequency Synthesizer Control */ - {CC1101_FSCTRL1, 0x06}, // IF = (26*10^6) / (2^10) * 0x06 = 152343.75Hz - - // Modem Configuration - {CC1101_MDMCFG0, 0x00}, // Channel spacing is 25kHz - {CC1101_MDMCFG1, 0x00}, // Channel spacing is 25kHz - {CC1101_MDMCFG2, 0x30}, // Format ASK/OOK, No preamble/sync - {CC1101_MDMCFG3, 0x32}, // Data rate is 3.79372 kBaud - {CC1101_MDMCFG4, 0x67}, // Rx BW filter is 270.833333kHz - - /* Main Radio Control State Machine */ - {CC1101_MCSM0, 0x18}, // Autocalibrate on idle-to-rx/tx, PO_TIMEOUT is 64 cycles(149-155us) - - /* Frequency Offset Compensation Configuration */ - {CC1101_FOCCFG, - 0x18}, // no frequency offset compensation, POST_K same as PRE_K, PRE_K is 4K, GATE is off - - /* Automatic Gain Control */ - {CC1101_AGCCTRL0, - 0x40}, // 01 - Low hysteresis, small asymmetric dead zone, medium gain; 00 - 8 samples agc; 00 - Normal AGC, 00 - 4dB boundary - {CC1101_AGCCTRL1, - 0x00}, // 0; 0 - LNA 2 gain is decreased to minimum before decreasing LNA gain; 00 - Relative carrier sense threshold disabled; 0000 - RSSI to MAIN_TARGET - {CC1101_AGCCTRL2, 0x03}, // 00 - DVGA all; 000 - MAX LNA+LNA2; 011 - MAIN_TARGET 24 dB - - /* Wake on radio and timeouts control */ - {CC1101_WORCTRL, 0xFB}, // WOR_RES is 2^15 periods (0.91 - 0.94 s) 16.5 - 17.2 hours - - /* Frontend configuration */ - {CC1101_FREND0, 0x11}, // Adjusts current TX LO buffer + high is PATABLE[1] - {CC1101_FREND1, 0xB6}, // - - /* End */ - {0, 0}, -}; - -static const uint8_t furi_hal_subghz_preset_ook_650khz_async_regs[][2] = { - // https://e2e.ti.com/support/wireless-connectivity/sub-1-ghz-group/sub-1-ghz/f/sub-1-ghz-forum/382066/cc1101---don-t-know-the-correct-registers-configuration - - /* GPIO GD0 */ - {CC1101_IOCFG0, 0x0D}, // GD0 as async serial data output/input - - /* FIFO and internals */ - {CC1101_FIFOTHR, 0x07}, // The only important bit is ADC_RETENTION - - /* Packet engine */ - {CC1101_PKTCTRL0, 0x32}, // Async, continious, no whitening - - /* Frequency Synthesizer Control */ - {CC1101_FSCTRL1, 0x06}, // IF = (26*10^6) / (2^10) * 0x06 = 152343.75Hz - - // Modem Configuration - {CC1101_MDMCFG0, 0x00}, // Channel spacing is 25kHz - {CC1101_MDMCFG1, 0x00}, // Channel spacing is 25kHz - {CC1101_MDMCFG2, 0x30}, // Format ASK/OOK, No preamble/sync - {CC1101_MDMCFG3, 0x32}, // Data rate is 3.79372 kBaud - {CC1101_MDMCFG4, 0x17}, // Rx BW filter is 650.000kHz - - /* Main Radio Control State Machine */ - {CC1101_MCSM0, 0x18}, // Autocalibrate on idle-to-rx/tx, PO_TIMEOUT is 64 cycles(149-155us) - - /* Frequency Offset Compensation Configuration */ - {CC1101_FOCCFG, - 0x18}, // no frequency offset compensation, POST_K same as PRE_K, PRE_K is 4K, GATE is off - - /* Automatic Gain Control */ - // {CC1101_AGCTRL0,0x40}, // 01 - Low hysteresis, small asymmetric dead zone, medium gain; 00 - 8 samples agc; 00 - Normal AGC, 00 - 4dB boundary - // {CC1101_AGCTRL1,0x00}, // 0; 0 - LNA 2 gain is decreased to minimum before decreasing LNA gain; 00 - Relative carrier sense threshold disabled; 0000 - RSSI to MAIN_TARGET - // {CC1101_AGCCTRL2, 0x03}, // 00 - DVGA all; 000 - MAX LNA+LNA2; 011 - MAIN_TARGET 24 dB - //MAGN_TARGET for RX filter BW =< 100 kHz is 0x3. For higher RX filter BW's MAGN_TARGET is 0x7. - {CC1101_AGCCTRL0, - 0x91}, // 10 - Medium hysteresis, medium asymmetric dead zone, medium gain ; 01 - 16 samples agc; 00 - Normal AGC, 01 - 8dB boundary - {CC1101_AGCCTRL1, - 0x0}, // 0; 0 - LNA 2 gain is decreased to minimum before decreasing LNA gain; 00 - Relative carrier sense threshold disabled; 0000 - RSSI to MAIN_TARGET - {CC1101_AGCCTRL2, 0x07}, // 00 - DVGA all; 000 - MAX LNA+LNA2; 111 - MAIN_TARGET 42 dB - - /* Wake on radio and timeouts control */ - {CC1101_WORCTRL, 0xFB}, // WOR_RES is 2^15 periods (0.91 - 0.94 s) 16.5 - 17.2 hours - - /* Frontend configuration */ - {CC1101_FREND0, 0x11}, // Adjusts current TX LO buffer + high is PATABLE[1] - {CC1101_FREND1, 0xB6}, // - - /* End */ - {0, 0}, -}; - -static const uint8_t furi_hal_subghz_preset_2fsk_dev2_38khz_async_regs[][2] = { - - /* GPIO GD0 */ - {CC1101_IOCFG0, 0x0D}, // GD0 as async serial data output/input - - /* Frequency Synthesizer Control */ - {CC1101_FSCTRL1, 0x06}, // IF = (26*10^6) / (2^10) * 0x06 = 152343.75Hz - - /* Packet engine */ - {CC1101_PKTCTRL0, 0x32}, // Async, continious, no whitening - {CC1101_PKTCTRL1, 0x04}, - - // // Modem Configuration - {CC1101_MDMCFG0, 0x00}, - {CC1101_MDMCFG1, 0x02}, - {CC1101_MDMCFG2, 0x04}, // Format 2-FSK/FM, No preamble/sync, Disable (current optimized) - {CC1101_MDMCFG3, 0x83}, // Data rate is 4.79794 kBaud - {CC1101_MDMCFG4, 0x67}, //Rx BW filter is 270.833333 kHz - {CC1101_DEVIATN, 0x04}, //Deviation 2.380371 kHz - - /* Main Radio Control State Machine */ - {CC1101_MCSM0, 0x18}, // Autocalibrate on idle-to-rx/tx, PO_TIMEOUT is 64 cycles(149-155us) - - /* Frequency Offset Compensation Configuration */ - {CC1101_FOCCFG, - 0x16}, // no frequency offset compensation, POST_K same as PRE_K, PRE_K is 4K, GATE is off - - /* Automatic Gain Control */ - {CC1101_AGCCTRL0, - 0x91}, //10 - Medium hysteresis, medium asymmetric dead zone, medium gain ; 01 - 16 samples agc; 00 - Normal AGC, 01 - 8dB boundary - {CC1101_AGCCTRL1, - 0x00}, // 0; 0 - LNA 2 gain is decreased to minimum before decreasing LNA gain; 00 - Relative carrier sense threshold disabled; 0000 - RSSI to MAIN_TARGET - {CC1101_AGCCTRL2, 0x07}, // 00 - DVGA all; 000 - MAX LNA+LNA2; 111 - MAIN_TARGET 42 dB - - /* Wake on radio and timeouts control */ - {CC1101_WORCTRL, 0xFB}, // WOR_RES is 2^15 periods (0.91 - 0.94 s) 16.5 - 17.2 hours - - /* Frontend configuration */ - {CC1101_FREND0, 0x10}, // Adjusts current TX LO buffer - {CC1101_FREND1, 0x56}, - - /* End */ - {0, 0}, -}; - -static const uint8_t furi_hal_subghz_preset_2fsk_dev47_6khz_async_regs[][2] = { - - /* GPIO GD0 */ - {CC1101_IOCFG0, 0x0D}, // GD0 as async serial data output/input - - /* Frequency Synthesizer Control */ - {CC1101_FSCTRL1, 0x06}, // IF = (26*10^6) / (2^10) * 0x06 = 152343.75Hz - - /* Packet engine */ - {CC1101_PKTCTRL0, 0x32}, // Async, continious, no whitening - {CC1101_PKTCTRL1, 0x04}, - - // // Modem Configuration - {CC1101_MDMCFG0, 0x00}, - {CC1101_MDMCFG1, 0x02}, - {CC1101_MDMCFG2, 0x04}, // Format 2-FSK/FM, No preamble/sync, Disable (current optimized) - {CC1101_MDMCFG3, 0x83}, // Data rate is 4.79794 kBaud - {CC1101_MDMCFG4, 0x67}, //Rx BW filter is 270.833333 kHz - {CC1101_DEVIATN, 0x47}, //Deviation 47.60742 kHz - - /* Main Radio Control State Machine */ - {CC1101_MCSM0, 0x18}, // Autocalibrate on idle-to-rx/tx, PO_TIMEOUT is 64 cycles(149-155us) - - /* Frequency Offset Compensation Configuration */ - {CC1101_FOCCFG, - 0x16}, // no frequency offset compensation, POST_K same as PRE_K, PRE_K is 4K, GATE is off - - /* Automatic Gain Control */ - {CC1101_AGCCTRL0, - 0x91}, //10 - Medium hysteresis, medium asymmetric dead zone, medium gain ; 01 - 16 samples agc; 00 - Normal AGC, 01 - 8dB boundary - {CC1101_AGCCTRL1, - 0x00}, // 0; 0 - LNA 2 gain is decreased to minimum before decreasing LNA gain; 00 - Relative carrier sense threshold disabled; 0000 - RSSI to MAIN_TARGET - {CC1101_AGCCTRL2, 0x07}, // 00 - DVGA all; 000 - MAX LNA+LNA2; 111 - MAIN_TARGET 42 dB - - /* Wake on radio and timeouts control */ - {CC1101_WORCTRL, 0xFB}, // WOR_RES is 2^15 periods (0.91 - 0.94 s) 16.5 - 17.2 hours - - /* Frontend configuration */ - {CC1101_FREND0, 0x10}, // Adjusts current TX LO buffer - {CC1101_FREND1, 0x56}, - - /* End */ - {0, 0}, -}; - -static const uint8_t furi_hal_subghz_preset_msk_99_97kb_async_regs[][2] = { - /* GPIO GD0 */ - {CC1101_IOCFG0, 0x06}, - - {CC1101_FIFOTHR, 0x07}, // The only important bit is ADC_RETENTION - {CC1101_SYNC1, 0x46}, - {CC1101_SYNC0, 0x4C}, - {CC1101_ADDR, 0x00}, - {CC1101_PKTLEN, 0x00}, - {CC1101_CHANNR, 0x00}, - - {CC1101_PKTCTRL0, 0x05}, - - {CC1101_FSCTRL0, 0x23}, - {CC1101_FSCTRL1, 0x06}, - - {CC1101_MDMCFG0, 0xF8}, - {CC1101_MDMCFG1, 0x22}, - {CC1101_MDMCFG2, 0x72}, - {CC1101_MDMCFG3, 0xF8}, - {CC1101_MDMCFG4, 0x5B}, - {CC1101_DEVIATN, 0x47}, - - {CC1101_MCSM0, 0x18}, - {CC1101_FOCCFG, 0x16}, - - {CC1101_AGCCTRL0, 0xB2}, - {CC1101_AGCCTRL1, 0x00}, - {CC1101_AGCCTRL2, 0xC7}, - - {CC1101_FREND0, 0x10}, - {CC1101_FREND1, 0x56}, - - {CC1101_BSCFG, 0x1C}, - {CC1101_FSTEST, 0x59}, - - /* End */ - {0, 0}, -}; - -static const uint8_t furi_hal_subghz_preset_gfsk_9_99kb_async_regs[][2] = { - - {CC1101_IOCFG0, 0x06}, //GDO0 Output Pin Configuration - {CC1101_FIFOTHR, 0x47}, //RX FIFO and TX FIFO Thresholds - - //1 : CRC calculation in TX and CRC check in RX enabled, - //1 : Variable packet length mode. Packet length configured by the first byte after sync word - {CC1101_PKTCTRL0, 0x05}, - - {CC1101_FSCTRL1, 0x06}, //Frequency Synthesizer Control - - {CC1101_SYNC1, 0x46}, - {CC1101_SYNC0, 0x4C}, - {CC1101_ADDR, 0x00}, - {CC1101_PKTLEN, 0x00}, - - {CC1101_MDMCFG4, 0xC8}, //Modem Configuration 9.99 - {CC1101_MDMCFG3, 0x93}, //Modem Configuration - {CC1101_MDMCFG2, 0x12}, // 2: 16/16 sync word bits detected - - {CC1101_DEVIATN, 0x34}, //Deviation = 19.042969 - {CC1101_MCSM0, 0x18}, //Main Radio Control State Machine Configuration - {CC1101_FOCCFG, 0x16}, //Frequency Offset Compensation Configuration - - {CC1101_AGCCTRL2, 0x43}, //AGC Control - {CC1101_AGCCTRL1, 0x40}, - {CC1101_AGCCTRL0, 0x91}, - - {CC1101_WORCTRL, 0xFB}, //Wake On Radio Control - /* End */ - {0, 0}, -}; - -static const uint8_t furi_hal_subghz_preset_ook_async_patable[8] = { - 0x00, - 0xC0, // 12dBm 0xC0, 10dBm 0xC5, 7dBm 0xCD, 5dBm 0x86, 0dBm 0x50, -6dBm 0x37, -10dBm 0x26, -15dBm 0x1D, -20dBm 0x17, -30dBm 0x03 - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00}; - -static const uint8_t furi_hal_subghz_preset_ook_async_patable_au[8] = { - 0x00, - 0x37, // 12dBm 0xC0, 10dBm 0xC5, 7dBm 0xCD, 5dBm 0x86, 0dBm 0x50, -6dBm 0x37, -10dBm 0x26, -15dBm 0x1D, -20dBm 0x17, -30dBm 0x03 - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00}; - -static const uint8_t furi_hal_subghz_preset_2fsk_async_patable[8] = { - 0xC0, // 10dBm 0xC0, 7dBm 0xC8, 5dBm 0x84, 0dBm 0x60, -10dBm 0x34, -15dBm 0x1D, -20dBm 0x0E, -30dBm 0x12 - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00}; - -static const uint8_t furi_hal_subghz_preset_msk_async_patable[8] = { - 0xC0, // 10dBm 0xC0, 7dBm 0xC8, 5dBm 0x84, 0dBm 0x60, -10dBm 0x34, -15dBm 0x1D, -20dBm 0x0E, -30dBm 0x12 - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00}; - -static const uint8_t furi_hal_subghz_preset_gfsk_async_patable[8] = { - 0xC0, // 10dBm 0xC0, 7dBm 0xC8, 5dBm 0x84, 0dBm 0x60, -10dBm 0x34, -15dBm 0x1D, -20dBm 0x0E, -30dBm 0x12 - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00}; diff --git a/firmware/targets/f7/platform_specific/intrinsic_export.h b/firmware/targets/f7/platform_specific/intrinsic_export.h index 8dbc4bd0320..ca343a12862 100644 --- a/firmware/targets/f7/platform_specific/intrinsic_export.h +++ b/firmware/targets/f7/platform_specific/intrinsic_export.h @@ -1,10 +1,12 @@ #include +#include #ifdef __cplusplus extern "C" { #endif void __clear_cache(void*, void*); +void* __aeabi_uldivmod(uint64_t, uint64_t); #ifdef __cplusplus } diff --git a/lib/subghz/SConscript b/lib/subghz/SConscript index 3a0325b71db..2c42a5157f2 100644 --- a/lib/subghz/SConscript +++ b/lib/subghz/SConscript @@ -19,6 +19,7 @@ env.Append( File("blocks/math.h"), File("subghz_setting.h"), File("subghz_protocol_registry.h"), + File("devices/cc1101_configs.h"), ], ) diff --git a/lib/subghz/devices/cc1101_configs.c b/lib/subghz/devices/cc1101_configs.c new file mode 100644 index 00000000000..35ddccd54f6 --- /dev/null +++ b/lib/subghz/devices/cc1101_configs.c @@ -0,0 +1,431 @@ +#include "cc1101_configs.h" +#include + +const uint8_t subghz_device_cc1101_preset_ook_270khz_async_regs[] = { + // https://e2e.ti.com/support/wireless-connectivity/sub-1-ghz-group/sub-1-ghz/f/sub-1-ghz-forum/382066/cc1101---don-t-know-the-correct-registers-configuration + + /* GPIO GD0 */ + CC1101_IOCFG0, + 0x0D, // GD0 as async serial data output/input + + /* FIFO and internals */ + CC1101_FIFOTHR, + 0x47, // The only important bit is ADC_RETENTION, FIFO Tx=33 Rx=32 + + /* Packet engine */ + CC1101_PKTCTRL0, + 0x32, // Async, continious, no whitening + + /* Frequency Synthesizer Control */ + CC1101_FSCTRL1, + 0x06, // IF = (26*10^6) / (2^10) * 0x06 = 152343.75Hz + + // Modem Configuration + CC1101_MDMCFG0, + 0x00, // Channel spacing is 25kHz + CC1101_MDMCFG1, + 0x00, // Channel spacing is 25kHz + CC1101_MDMCFG2, + 0x30, // Format ASK/OOK, No preamble/sync + CC1101_MDMCFG3, + 0x32, // Data rate is 3.79372 kBaud + CC1101_MDMCFG4, + 0x67, // Rx BW filter is 270.833333kHz + + /* Main Radio Control State Machine */ + CC1101_MCSM0, + 0x18, // Autocalibrate on idle-to-rx/tx, PO_TIMEOUT is 64 cycles(149-155us) + + /* Frequency Offset Compensation Configuration */ + CC1101_FOCCFG, + 0x18, // no frequency offset compensation, POST_K same as PRE_K, PRE_K is 4K, GATE is off + + /* Automatic Gain Control */ + CC1101_AGCCTRL0, + 0x40, // 01 - Low hysteresis, small asymmetric dead zone, medium gain; 00 - 8 samples agc; 00 - Normal AGC, 00 - 4dB boundary + CC1101_AGCCTRL1, + 0x00, // 0; 0 - LNA 2 gain is decreased to minimum before decreasing LNA gain; 00 - Relative carrier sense threshold disabled; 0000 - RSSI to MAIN_TARGET + CC1101_AGCCTRL2, + 0x03, // 00 - DVGA all; 000 - MAX LNA+LNA2; 011 - MAIN_TARGET 24 dB + + /* Wake on radio and timeouts control */ + CC1101_WORCTRL, + 0xFB, // WOR_RES is 2^15 periods (0.91 - 0.94 s) 16.5 - 17.2 hours + + /* Frontend configuration */ + CC1101_FREND0, + 0x11, // Adjusts current TX LO buffer + high is PATABLE[1] + CC1101_FREND1, + 0xB6, // + + /* End load reg */ + 0, + 0, + + //ook_async_patable[8] + 0x00, + 0xC0, // 12dBm 0xC0, 10dBm 0xC5, 7dBm 0xCD, 5dBm 0x86, 0dBm 0x50, -6dBm 0x37, -10dBm 0x26, -15dBm 0x1D, -20dBm 0x17, -30dBm 0x03 + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, +}; + +const uint8_t subghz_device_cc1101_preset_ook_650khz_async_regs[] = { + // https://e2e.ti.com/support/wireless-connectivity/sub-1-ghz-group/sub-1-ghz/f/sub-1-ghz-forum/382066/cc1101---don-t-know-the-correct-registers-configuration + + /* GPIO GD0 */ + CC1101_IOCFG0, + 0x0D, // GD0 as async serial data output/input + + /* FIFO and internals */ + CC1101_FIFOTHR, + 0x07, // The only important bit is ADC_RETENTION + + /* Packet engine */ + CC1101_PKTCTRL0, + 0x32, // Async, continious, no whitening + + /* Frequency Synthesizer Control */ + CC1101_FSCTRL1, + 0x06, // IF = (26*10^6) / (2^10) * 0x06 = 152343.75Hz + + // Modem Configuration + CC1101_MDMCFG0, + 0x00, // Channel spacing is 25kHz + CC1101_MDMCFG1, + 0x00, // Channel spacing is 25kHz + CC1101_MDMCFG2, + 0x30, // Format ASK/OOK, No preamble/sync + CC1101_MDMCFG3, + 0x32, // Data rate is 3.79372 kBaud + CC1101_MDMCFG4, + 0x17, // Rx BW filter is 650.000kHz + + /* Main Radio Control State Machine */ + CC1101_MCSM0, + 0x18, // Autocalibrate on idle-to-rx/tx, PO_TIMEOUT is 64 cycles(149-155us) + + /* Frequency Offset Compensation Configuration */ + CC1101_FOCCFG, + 0x18, // no frequency offset compensation, POST_K same as PRE_K, PRE_K is 4K, GATE is off + + /* Automatic Gain Control */ + // CC1101_AGCTRL0,0x40, // 01 - Low hysteresis, small asymmetric dead zone, medium gain; 00 - 8 samples agc; 00 - Normal AGC, 00 - 4dB boundary + // CC1101_AGCTRL1,0x00, // 0; 0 - LNA 2 gain is decreased to minimum before decreasing LNA gain; 00 - Relative carrier sense threshold disabled; 0000 - RSSI to MAIN_TARGET + // CC1101_AGCCTRL2, 0x03, // 00 - DVGA all; 000 - MAX LNA+LNA2; 011 - MAIN_TARGET 24 dB + //MAGN_TARGET for RX filter BW =< 100 kHz is 0x3. For higher RX filter BW's MAGN_TARGET is 0x7. + CC1101_AGCCTRL0, + 0x91, // 10 - Medium hysteresis, medium asymmetric dead zone, medium gain ; 01 - 16 samples agc; 00 - Normal AGC, 01 - 8dB boundary + CC1101_AGCCTRL1, + 0x0, // 0; 0 - LNA 2 gain is decreased to minimum before decreasing LNA gain; 00 - Relative carrier sense threshold disabled; 0000 - RSSI to MAIN_TARGET + CC1101_AGCCTRL2, + 0x07, // 00 - DVGA all; 000 - MAX LNA+LNA2; 111 - MAIN_TARGET 42 dB + + /* Wake on radio and timeouts control */ + CC1101_WORCTRL, + 0xFB, // WOR_RES is 2^15 periods (0.91 - 0.94 s) 16.5 - 17.2 hours + + /* Frontend configuration */ + CC1101_FREND0, + 0x11, // Adjusts current TX LO buffer + high is PATABLE[1] + CC1101_FREND1, + 0xB6, // + + /* End load reg */ + 0, + 0, + + //ook_async_patable[8] + 0x00, + 0xC0, // 12dBm 0xC0, 10dBm 0xC5, 7dBm 0xCD, 5dBm 0x86, 0dBm 0x50, -6dBm 0x37, -10dBm 0x26, -15dBm 0x1D, -20dBm 0x17, -30dBm 0x03 + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, +}; + +const uint8_t subghz_device_cc1101_preset_2fsk_dev2_38khz_async_regs[] = { + + /* GPIO GD0 */ + CC1101_IOCFG0, + 0x0D, // GD0 as async serial data output/input + + /* Frequency Synthesizer Control */ + CC1101_FSCTRL1, + 0x06, // IF = (26*10^6) / (2^10) * 0x06 = 152343.75Hz + + /* Packet engine */ + CC1101_PKTCTRL0, + 0x32, // Async, continious, no whitening + CC1101_PKTCTRL1, + 0x04, + + // // Modem Configuration + CC1101_MDMCFG0, + 0x00, + CC1101_MDMCFG1, + 0x02, + CC1101_MDMCFG2, + 0x04, // Format 2-FSK/FM, No preamble/sync, Disable (current optimized) + CC1101_MDMCFG3, + 0x83, // Data rate is 4.79794 kBaud + CC1101_MDMCFG4, + 0x67, //Rx BW filter is 270.833333 kHz + CC1101_DEVIATN, + 0x04, //Deviation 2.380371 kHz + + /* Main Radio Control State Machine */ + CC1101_MCSM0, + 0x18, // Autocalibrate on idle-to-rx/tx, PO_TIMEOUT is 64 cycles(149-155us) + + /* Frequency Offset Compensation Configuration */ + CC1101_FOCCFG, + 0x16, // no frequency offset compensation, POST_K same as PRE_K, PRE_K is 4K, GATE is off + + /* Automatic Gain Control */ + CC1101_AGCCTRL0, + 0x91, //10 - Medium hysteresis, medium asymmetric dead zone, medium gain ; 01 - 16 samples agc; 00 - Normal AGC, 01 - 8dB boundary + CC1101_AGCCTRL1, + 0x00, // 0; 0 - LNA 2 gain is decreased to minimum before decreasing LNA gain; 00 - Relative carrier sense threshold disabled; 0000 - RSSI to MAIN_TARGET + CC1101_AGCCTRL2, + 0x07, // 00 - DVGA all; 000 - MAX LNA+LNA2; 111 - MAIN_TARGET 42 dB + + /* Wake on radio and timeouts control */ + CC1101_WORCTRL, + 0xFB, // WOR_RES is 2^15 periods (0.91 - 0.94 s) 16.5 - 17.2 hours + + /* Frontend configuration */ + CC1101_FREND0, + 0x10, // Adjusts current TX LO buffer + CC1101_FREND1, + 0x56, + + /* End load reg */ + 0, + 0, + + // 2fsk_async_patable[8] + 0xC0, // 10dBm 0xC0, 7dBm 0xC8, 5dBm 0x84, 0dBm 0x60, -10dBm 0x34, -15dBm 0x1D, -20dBm 0x0E, -30dBm 0x12 + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, +}; + +const uint8_t subghz_device_cc1101_preset_2fsk_dev47_6khz_async_regs[] = { + + /* GPIO GD0 */ + CC1101_IOCFG0, + 0x0D, // GD0 as async serial data output/input + + /* Frequency Synthesizer Control */ + CC1101_FSCTRL1, + 0x06, // IF = (26*10^6) / (2^10) * 0x06 = 152343.75Hz + + /* Packet engine */ + CC1101_PKTCTRL0, + 0x32, // Async, continious, no whitening + CC1101_PKTCTRL1, + 0x04, + + // // Modem Configuration + CC1101_MDMCFG0, + 0x00, + CC1101_MDMCFG1, + 0x02, + CC1101_MDMCFG2, + 0x04, // Format 2-FSK/FM, No preamble/sync, Disable (current optimized) + CC1101_MDMCFG3, + 0x83, // Data rate is 4.79794 kBaud + CC1101_MDMCFG4, + 0x67, //Rx BW filter is 270.833333 kHz + CC1101_DEVIATN, + 0x47, //Deviation 47.60742 kHz + + /* Main Radio Control State Machine */ + CC1101_MCSM0, + 0x18, // Autocalibrate on idle-to-rx/tx, PO_TIMEOUT is 64 cycles(149-155us) + + /* Frequency Offset Compensation Configuration */ + CC1101_FOCCFG, + 0x16, // no frequency offset compensation, POST_K same as PRE_K, PRE_K is 4K, GATE is off + + /* Automatic Gain Control */ + CC1101_AGCCTRL0, + 0x91, //10 - Medium hysteresis, medium asymmetric dead zone, medium gain ; 01 - 16 samples agc; 00 - Normal AGC, 01 - 8dB boundary + CC1101_AGCCTRL1, + 0x00, // 0; 0 - LNA 2 gain is decreased to minimum before decreasing LNA gain; 00 - Relative carrier sense threshold disabled; 0000 - RSSI to MAIN_TARGET + CC1101_AGCCTRL2, + 0x07, // 00 - DVGA all; 000 - MAX LNA+LNA2; 111 - MAIN_TARGET 42 dB + + /* Wake on radio and timeouts control */ + CC1101_WORCTRL, + 0xFB, // WOR_RES is 2^15 periods (0.91 - 0.94 s) 16.5 - 17.2 hours + + /* Frontend configuration */ + CC1101_FREND0, + 0x10, // Adjusts current TX LO buffer + CC1101_FREND1, + 0x56, + + /* End load reg */ + 0, + 0, + + // 2fsk_async_patable[8] + 0xC0, // 10dBm 0xC0, 7dBm 0xC8, 5dBm 0x84, 0dBm 0x60, -10dBm 0x34, -15dBm 0x1D, -20dBm 0x0E, -30dBm 0x12 + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, +}; + +const uint8_t subghz_device_cc1101_preset_msk_99_97kb_async_regs[] = { + /* GPIO GD0 */ + CC1101_IOCFG0, + 0x06, + + CC1101_FIFOTHR, + 0x07, // The only important bit is ADC_RETENTION + CC1101_SYNC1, + 0x46, + CC1101_SYNC0, + 0x4C, + CC1101_ADDR, + 0x00, + CC1101_PKTLEN, + 0x00, + CC1101_CHANNR, + 0x00, + + CC1101_PKTCTRL0, + 0x05, + + CC1101_FSCTRL0, + 0x23, + CC1101_FSCTRL1, + 0x06, + + CC1101_MDMCFG0, + 0xF8, + CC1101_MDMCFG1, + 0x22, + CC1101_MDMCFG2, + 0x72, + CC1101_MDMCFG3, + 0xF8, + CC1101_MDMCFG4, + 0x5B, + CC1101_DEVIATN, + 0x47, + + CC1101_MCSM0, + 0x18, + CC1101_FOCCFG, + 0x16, + + CC1101_AGCCTRL0, + 0xB2, + CC1101_AGCCTRL1, + 0x00, + CC1101_AGCCTRL2, + 0xC7, + + CC1101_FREND0, + 0x10, + CC1101_FREND1, + 0x56, + + CC1101_BSCFG, + 0x1C, + CC1101_FSTEST, + 0x59, + + /* End load reg */ + 0, + 0, + + // msk_async_patable[8] + 0xC0, // 10dBm 0xC0, 7dBm 0xC8, 5dBm 0x84, 0dBm 0x60, -10dBm 0x34, -15dBm 0x1D, -20dBm 0x0E, -30dBm 0x12 + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, +}; + +const uint8_t subghz_device_cc1101_preset_gfsk_9_99kb_async_regs[] = { + + CC1101_IOCFG0, + 0x06, //GDO0 Output Pin Configuration + CC1101_FIFOTHR, + 0x47, //RX FIFO and TX FIFO Thresholds + + //1 : CRC calculation in TX and CRC check in RX enabled, + //1 : Variable packet length mode. Packet length configured by the first byte after sync word + CC1101_PKTCTRL0, + 0x05, + + CC1101_FSCTRL1, + 0x06, //Frequency Synthesizer Control + + CC1101_SYNC1, + 0x46, + CC1101_SYNC0, + 0x4C, + CC1101_ADDR, + 0x00, + CC1101_PKTLEN, + 0x00, + + CC1101_MDMCFG4, + 0xC8, //Modem Configuration 9.99 + CC1101_MDMCFG3, + 0x93, //Modem Configuration + CC1101_MDMCFG2, + 0x12, // 2: 16/16 sync word bits detected + + CC1101_DEVIATN, + 0x34, //Deviation = 19.042969 + CC1101_MCSM0, + 0x18, //Main Radio Control State Machine Configuration + CC1101_FOCCFG, + 0x16, //Frequency Offset Compensation Configuration + + CC1101_AGCCTRL2, + 0x43, //AGC Control + CC1101_AGCCTRL1, + 0x40, + CC1101_AGCCTRL0, + 0x91, + + CC1101_WORCTRL, + 0xFB, //Wake On Radio Control + + /* End load reg */ + 0, + 0, + + // gfsk_async_patable[8] + 0xC0, // 10dBm 0xC0, 7dBm 0xC8, 5dBm 0x84, 0dBm 0x60, -10dBm 0x34, -15dBm 0x1D, -20dBm 0x0E, -30dBm 0x12 + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, +}; diff --git a/lib/subghz/devices/cc1101_configs.h b/lib/subghz/devices/cc1101_configs.h new file mode 100644 index 00000000000..eecab01d999 --- /dev/null +++ b/lib/subghz/devices/cc1101_configs.h @@ -0,0 +1,18 @@ +#pragma once +#pragma once +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern const uint8_t subghz_device_cc1101_preset_ook_270khz_async_regs[]; +extern const uint8_t subghz_device_cc1101_preset_ook_650khz_async_regs[]; +extern const uint8_t subghz_device_cc1101_preset_2fsk_dev2_38khz_async_regs[]; +extern const uint8_t subghz_device_cc1101_preset_2fsk_dev47_6khz_async_regs[]; +extern const uint8_t subghz_device_cc1101_preset_msk_99_97kb_async_regs[]; +extern const uint8_t subghz_device_cc1101_preset_gfsk_9_99kb_async_regs[]; + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/subghz/devices/cc1101_int/cc1101_int_interconnect.c b/lib/subghz/devices/cc1101_int/cc1101_int_interconnect.c new file mode 100644 index 00000000000..41a0609df00 --- /dev/null +++ b/lib/subghz/devices/cc1101_int/cc1101_int_interconnect.c @@ -0,0 +1,96 @@ +#include "cc1101_int_interconnect.h" +#include +#include "../cc1101_configs.h" + +#define TAG "SubGhzDeviceCC1101Int" + +static bool subghz_device_cc1101_int_interconnect_is_frequency_valid(uint32_t frequency) { + bool ret = furi_hal_subghz_is_frequency_valid(frequency); + if(!ret) { + furi_crash("SubGhz: Incorrect frequency."); + } + return ret; +} + +static uint32_t subghz_device_cc1101_int_interconnect_set_frequency(uint32_t frequency) { + subghz_device_cc1101_int_interconnect_is_frequency_valid(frequency); + return furi_hal_subghz_set_frequency_and_path(frequency); +} + +static bool subghz_device_cc1101_int_interconnect_start_async_tx(void* callback, void* context) { + return furi_hal_subghz_start_async_tx((FuriHalSubGhzAsyncTxCallback)callback, context); +} + +static void subghz_device_cc1101_int_interconnect_start_async_rx(void* callback, void* context) { + furi_hal_subghz_start_async_rx((FuriHalSubGhzCaptureCallback)callback, context); +} + +static void subghz_device_cc1101_int_interconnect_load_preset( + FuriHalSubGhzPreset preset, + uint8_t* preset_data) { + switch(preset) { + case FuriHalSubGhzPresetOok650Async: + furi_hal_subghz_load_custom_preset(subghz_device_cc1101_preset_ook_650khz_async_regs); + break; + case FuriHalSubGhzPresetOok270Async: + furi_hal_subghz_load_custom_preset(subghz_device_cc1101_preset_ook_270khz_async_regs); + break; + case FuriHalSubGhzPreset2FSKDev238Async: + furi_hal_subghz_load_custom_preset(subghz_device_cc1101_preset_2fsk_dev2_38khz_async_regs); + break; + case FuriHalSubGhzPreset2FSKDev476Async: + furi_hal_subghz_load_custom_preset(subghz_device_cc1101_preset_2fsk_dev47_6khz_async_regs); + break; + case FuriHalSubGhzPresetMSK99_97KbAsync: + furi_hal_subghz_load_custom_preset(subghz_device_cc1101_preset_msk_99_97kb_async_regs); + break; + case FuriHalSubGhzPresetGFSK9_99KbAsync: + furi_hal_subghz_load_custom_preset(subghz_device_cc1101_preset_gfsk_9_99kb_async_regs); + break; + + default: + furi_hal_subghz_load_custom_preset(preset_data); + } +} + +static bool subghz_device_cc1101_int_interconnect_is_connect(void) { + return true; +} + +const SubGhzDeviceInterconnect subghz_device_cc1101_int_interconnect = { + .begin = NULL, + .end = furi_hal_subghz_shutdown, + .is_connect = subghz_device_cc1101_int_interconnect_is_connect, + .reset = furi_hal_subghz_reset, + .sleep = furi_hal_subghz_sleep, + .idle = furi_hal_subghz_idle, + .load_preset = subghz_device_cc1101_int_interconnect_load_preset, + .set_frequency = subghz_device_cc1101_int_interconnect_set_frequency, + .is_frequency_valid = furi_hal_subghz_is_frequency_valid, + .set_async_mirror_pin = furi_hal_subghz_set_async_mirror_pin, + .get_data_gpio = furi_hal_subghz_get_data_gpio, + + .set_tx = furi_hal_subghz_tx, + .flush_tx = furi_hal_subghz_flush_tx, + .start_async_tx = subghz_device_cc1101_int_interconnect_start_async_tx, + .is_async_complete_tx = furi_hal_subghz_is_async_tx_complete, + .stop_async_tx = furi_hal_subghz_stop_async_tx, + + .set_rx = furi_hal_subghz_rx, + .flush_rx = furi_hal_subghz_flush_rx, + .start_async_rx = subghz_device_cc1101_int_interconnect_start_async_rx, + .stop_async_rx = furi_hal_subghz_stop_async_rx, + + .get_rssi = furi_hal_subghz_get_rssi, + .get_lqi = furi_hal_subghz_get_lqi, + + .rx_pipe_not_empty = furi_hal_subghz_rx_pipe_not_empty, + .is_rx_data_crc_valid = furi_hal_subghz_is_rx_data_crc_valid, + .read_packet = furi_hal_subghz_read_packet, + .write_packet = furi_hal_subghz_write_packet, +}; + +const SubGhzDevice subghz_device_cc1101_int = { + .name = SUBGHZ_DEVICE_CC1101_INT_NAME, + .interconnect = &subghz_device_cc1101_int_interconnect, +}; diff --git a/lib/subghz/devices/cc1101_int/cc1101_int_interconnect.h b/lib/subghz/devices/cc1101_int/cc1101_int_interconnect.h new file mode 100644 index 00000000000..629d1264c09 --- /dev/null +++ b/lib/subghz/devices/cc1101_int/cc1101_int_interconnect.h @@ -0,0 +1,8 @@ +#pragma once +#include "../types.h" + +#define SUBGHZ_DEVICE_CC1101_INT_NAME "cc1101_int" + +typedef struct SubGhzDeviceCC1101Int SubGhzDeviceCC1101Int; + +extern const SubGhzDevice subghz_device_cc1101_int; diff --git a/lib/subghz/devices/device_registry.h b/lib/subghz/devices/device_registry.h new file mode 100644 index 00000000000..70a0db4b265 --- /dev/null +++ b/lib/subghz/devices/device_registry.h @@ -0,0 +1,13 @@ +#pragma once + +#include "registry.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern const SubGhzDeviceRegistry subghz_device_registry; + +#ifdef __cplusplus +} +#endif diff --git a/lib/subghz/devices/devices.c b/lib/subghz/devices/devices.c new file mode 100644 index 00000000000..55db61e1188 --- /dev/null +++ b/lib/subghz/devices/devices.c @@ -0,0 +1,236 @@ +#include "devices.h" + +#include "registry.h" + +void subghz_devices_init() { + furi_check(!subghz_device_registry_is_valid()); + subghz_device_registry_init(); +} + +void subghz_devices_deinit(void) { + furi_check(subghz_device_registry_is_valid()); + subghz_device_registry_deinit(); +} + +const SubGhzDevice* subghz_devices_get_by_name(const char* device_name) { + furi_check(subghz_device_registry_is_valid()); + const SubGhzDevice* device = subghz_device_registry_get_by_name(device_name); + return device; +} + +const char* subghz_devices_get_name(const SubGhzDevice* device) { + const char* ret = NULL; + if(device) { + ret = device->name; + } + return ret; +} + +bool subghz_devices_begin(const SubGhzDevice* device) { + bool ret = false; + furi_assert(device); + if(device->interconnect->begin) { + ret = device->interconnect->begin(); + } + return ret; +} + +void subghz_devices_end(const SubGhzDevice* device) { + furi_assert(device); + if(device->interconnect->end) { + device->interconnect->end(); + } +} + +bool subghz_devices_is_connect(const SubGhzDevice* device) { + bool ret = false; + furi_assert(device); + if(device->interconnect->is_connect) { + ret = device->interconnect->is_connect(); + } + return ret; +} + +void subghz_devices_reset(const SubGhzDevice* device) { + furi_assert(device); + if(device->interconnect->reset) { + device->interconnect->reset(); + } +} + +void subghz_devices_sleep(const SubGhzDevice* device) { + furi_assert(device); + if(device->interconnect->sleep) { + device->interconnect->sleep(); + } +} + +void subghz_devices_idle(const SubGhzDevice* device) { + furi_assert(device); + if(device->interconnect->idle) { + device->interconnect->idle(); + } +} + +void subghz_devices_load_preset( + const SubGhzDevice* device, + FuriHalSubGhzPreset preset, + uint8_t* preset_data) { + furi_assert(device); + if(device->interconnect->load_preset) { + device->interconnect->load_preset(preset, preset_data); + } +} + +uint32_t subghz_devices_set_frequency(const SubGhzDevice* device, uint32_t frequency) { + uint32_t ret = 0; + furi_assert(device); + if(device->interconnect->set_frequency) { + ret = device->interconnect->set_frequency(frequency); + } + return ret; +} + +bool subghz_devices_is_frequency_valid(const SubGhzDevice* device, uint32_t frequency) { + bool ret = false; + furi_assert(device); + if(device->interconnect->is_frequency_valid) { + ret = device->interconnect->is_frequency_valid(frequency); + } + return ret; +} + +void subghz_devices_set_async_mirror_pin(const SubGhzDevice* device, const GpioPin* gpio) { + furi_assert(device); + if(device->interconnect->set_async_mirror_pin) { + device->interconnect->set_async_mirror_pin(gpio); + } +} + +const GpioPin* subghz_devices_get_data_gpio(const SubGhzDevice* device) { + const GpioPin* ret = NULL; + furi_assert(device); + if(device->interconnect->get_data_gpio) { + ret = device->interconnect->get_data_gpio(); + } + return ret; +} + +bool subghz_devices_set_tx(const SubGhzDevice* device) { + bool ret = 0; + furi_assert(device); + if(device->interconnect->set_tx) { + ret = device->interconnect->set_tx(); + } + return ret; +} + +void subghz_devices_flush_tx(const SubGhzDevice* device) { + furi_assert(device); + if(device->interconnect->flush_tx) { + device->interconnect->flush_tx(); + } +} + +bool subghz_devices_start_async_tx(const SubGhzDevice* device, void* callback, void* context) { + bool ret = false; + furi_assert(device); + if(device->interconnect->start_async_tx) { + ret = device->interconnect->start_async_tx(callback, context); + } + return ret; +} + +bool subghz_devices_is_async_complete_tx(const SubGhzDevice* device) { + bool ret = false; + furi_assert(device); + if(device->interconnect->is_async_complete_tx) { + ret = device->interconnect->is_async_complete_tx(); + } + return ret; +} + +void subghz_devices_stop_async_tx(const SubGhzDevice* device) { + furi_assert(device); + if(device->interconnect->stop_async_tx) { + device->interconnect->stop_async_tx(); + } +} + +void subghz_devices_set_rx(const SubGhzDevice* device) { + furi_assert(device); + if(device->interconnect->set_rx) { + device->interconnect->set_rx(); + } +} + +void subghz_devices_flush_rx(const SubGhzDevice* device) { + furi_assert(device); + if(device->interconnect->flush_rx) { + device->interconnect->flush_rx(); + } +} + +void subghz_devices_start_async_rx(const SubGhzDevice* device, void* callback, void* context) { + furi_assert(device); + if(device->interconnect->start_async_rx) { + device->interconnect->start_async_rx(callback, context); + } +} + +void subghz_devices_stop_async_rx(const SubGhzDevice* device) { + furi_assert(device); + if(device->interconnect->stop_async_rx) { + device->interconnect->stop_async_rx(); + } +} + +float subghz_devices_get_rssi(const SubGhzDevice* device) { + float ret = 0; + furi_assert(device); + if(device->interconnect->get_rssi) { + ret = device->interconnect->get_rssi(); + } + return ret; +} + +uint8_t subghz_devices_get_lqi(const SubGhzDevice* device) { + uint8_t ret = 0; + furi_assert(device); + if(device->interconnect->get_lqi) { + ret = device->interconnect->get_lqi(); + } + return ret; +} + +bool subghz_devices_rx_pipe_not_empty(const SubGhzDevice* device) { + bool ret = false; + furi_assert(device); + if(device->interconnect->rx_pipe_not_empty) { + ret = device->interconnect->rx_pipe_not_empty(); + } + return ret; +} + +bool subghz_devices_is_rx_data_crc_valid(const SubGhzDevice* device) { + bool ret = false; + furi_assert(device); + if(device->interconnect->is_rx_data_crc_valid) { + ret = device->interconnect->is_rx_data_crc_valid(); + } + return ret; +} + +void subghz_devices_read_packet(const SubGhzDevice* device, uint8_t* data, uint8_t* size) { + furi_assert(device); + if(device->interconnect->read_packet) { + device->interconnect->read_packet(data, size); + } +} + +void subghz_devices_write_packet(const SubGhzDevice* device, const uint8_t* data, uint8_t size) { + furi_assert(device); + if(device->interconnect->write_packet) { + device->interconnect->write_packet(data, size); + } +} diff --git a/lib/subghz/devices/devices.h b/lib/subghz/devices/devices.h new file mode 100644 index 00000000000..dad3c9aeb53 --- /dev/null +++ b/lib/subghz/devices/devices.h @@ -0,0 +1,52 @@ +#pragma once + +#include "types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct SubGhzDevice SubGhzDevice; + +void subghz_devices_init(); +void subghz_devices_deinit(void); + +const SubGhzDevice* subghz_devices_get_by_name(const char* device_name); +const char* subghz_devices_get_name(const SubGhzDevice* device); +bool subghz_devices_begin(const SubGhzDevice* device); +void subghz_devices_end(const SubGhzDevice* device); +bool subghz_devices_is_connect(const SubGhzDevice* device); +void subghz_devices_reset(const SubGhzDevice* device); +void subghz_devices_sleep(const SubGhzDevice* device); +void subghz_devices_idle(const SubGhzDevice* device); +void subghz_devices_load_preset( + const SubGhzDevice* device, + FuriHalSubGhzPreset preset, + uint8_t* preset_data); +uint32_t subghz_devices_set_frequency(const SubGhzDevice* device, uint32_t frequency); +bool subghz_devices_is_frequency_valid(const SubGhzDevice* device, uint32_t frequency); +void subghz_devices_set_async_mirror_pin(const SubGhzDevice* device, const GpioPin* gpio); +const GpioPin* subghz_devices_get_data_gpio(const SubGhzDevice* device); + +bool subghz_devices_set_tx(const SubGhzDevice* device); +void subghz_devices_flush_tx(const SubGhzDevice* device); +bool subghz_devices_start_async_tx(const SubGhzDevice* device, void* callback, void* context); +bool subghz_devices_is_async_complete_tx(const SubGhzDevice* device); +void subghz_devices_stop_async_tx(const SubGhzDevice* device); + +void subghz_devices_set_rx(const SubGhzDevice* device); +void subghz_devices_flush_rx(const SubGhzDevice* device); +void subghz_devices_start_async_rx(const SubGhzDevice* device, void* callback, void* context); +void subghz_devices_stop_async_rx(const SubGhzDevice* device); + +float subghz_devices_get_rssi(const SubGhzDevice* device); +uint8_t subghz_devices_get_lqi(const SubGhzDevice* device); + +bool subghz_devices_rx_pipe_not_empty(const SubGhzDevice* device); +bool subghz_devices_is_rx_data_crc_valid(const SubGhzDevice* device); +void subghz_devices_read_packet(const SubGhzDevice* device, uint8_t* data, uint8_t* size); +void subghz_devices_write_packet(const SubGhzDevice* device, const uint8_t* data, uint8_t size); + +#ifdef __cplusplus +} +#endif diff --git a/lib/subghz/devices/preset.h b/lib/subghz/devices/preset.h new file mode 100644 index 00000000000..8716f2e233c --- /dev/null +++ b/lib/subghz/devices/preset.h @@ -0,0 +1,13 @@ +#pragma once + +/** Radio Presets */ +typedef enum { + FuriHalSubGhzPresetIDLE, /**< default configuration */ + FuriHalSubGhzPresetOok270Async, /**< OOK, bandwidth 270kHz, asynchronous */ + FuriHalSubGhzPresetOok650Async, /**< OOK, bandwidth 650kHz, asynchronous */ + FuriHalSubGhzPreset2FSKDev238Async, /**< FM, deviation 2.380371 kHz, asynchronous */ + FuriHalSubGhzPreset2FSKDev476Async, /**< FM, deviation 47.60742 kHz, asynchronous */ + FuriHalSubGhzPresetMSK99_97KbAsync, /**< MSK, deviation 47.60742 kHz, 99.97Kb/s, asynchronous */ + FuriHalSubGhzPresetGFSK9_99KbAsync, /**< GFSK, deviation 19.042969 kHz, 9.996Kb/s, asynchronous */ + FuriHalSubGhzPresetCustom, /**Custom Preset*/ +} FuriHalSubGhzPreset; diff --git a/lib/subghz/devices/registry.c b/lib/subghz/devices/registry.c new file mode 100644 index 00000000000..c0d5bb292dc --- /dev/null +++ b/lib/subghz/devices/registry.c @@ -0,0 +1,76 @@ +#include "registry.h" + +#include "cc1101_int/cc1101_int_interconnect.h" +#include +#include + +#define TAG "SubGhzDeviceRegistry" + +struct SubGhzDeviceRegistry { + const SubGhzDevice** items; + size_t size; + PluginManager* manager; +}; + +static SubGhzDeviceRegistry* subghz_device_registry = NULL; + +void subghz_device_registry_init(void) { + SubGhzDeviceRegistry* subghz_device = + (SubGhzDeviceRegistry*)malloc(sizeof(SubGhzDeviceRegistry)); + subghz_device->manager = plugin_manager_alloc( + SUBGHZ_RADIO_DEVICE_PLUGIN_APP_ID, + SUBGHZ_RADIO_DEVICE_PLUGIN_API_VERSION, + firmware_api_interface); + + //ToDo: fix path to plugins + if(plugin_manager_load_all(subghz_device->manager, "/any/apps_data/subghz/plugins") != + //if(plugin_manager_load_all(subghz_device->manager, APP_DATA_PATH("plugins")) != + PluginManagerErrorNone) { + FURI_LOG_E(TAG, "Failed to load all libs"); + } + + subghz_device->size = plugin_manager_get_count(subghz_device->manager) + 1; + subghz_device->items = + (const SubGhzDevice**)malloc(sizeof(SubGhzDevice*) * subghz_device->size); + subghz_device->items[0] = &subghz_device_cc1101_int; + for(uint32_t i = 1; i < subghz_device->size; i++) { + const SubGhzDevice* plugin = plugin_manager_get_ep(subghz_device->manager, i - 1); + subghz_device->items[i] = plugin; + } + + FURI_LOG_I(TAG, "Loaded %zu radio device", subghz_device->size); + subghz_device_registry = subghz_device; +} + +void subghz_device_registry_deinit(void) { + plugin_manager_free(subghz_device_registry->manager); + free(subghz_device_registry->items); + free(subghz_device_registry); + subghz_device_registry = NULL; +} + +bool subghz_device_registry_is_valid(void) { + return subghz_device_registry != NULL; +} + +const SubGhzDevice* subghz_device_registry_get_by_name(const char* name) { + furi_assert(subghz_device_registry); + + if(name != NULL) { + for(size_t i = 0; i < subghz_device_registry->size; i++) { + if(strcmp(name, subghz_device_registry->items[i]->name) == 0) { + return subghz_device_registry->items[i]; + } + } + } + return NULL; +} + +const SubGhzDevice* subghz_device_registry_get_by_index(size_t index) { + furi_assert(subghz_device_registry); + if(index < subghz_device_registry->size) { + return subghz_device_registry->items[index]; + } else { + return NULL; + } +} diff --git a/lib/subghz/devices/registry.h b/lib/subghz/devices/registry.h new file mode 100644 index 00000000000..5200589205b --- /dev/null +++ b/lib/subghz/devices/registry.h @@ -0,0 +1,40 @@ +#pragma once + +#include "types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct SubGhzDevice SubGhzDevice; + +void subghz_device_registry_init(void); + +void subghz_device_registry_deinit(void); + +bool subghz_device_registry_is_valid(void); + +/** + * Registration by name SubGhzDevice. + * @param name SubGhzDevice name + * @return SubGhzDevice* pointer to a SubGhzDevice instance + */ +const SubGhzDevice* subghz_device_registry_get_by_name(const char* name); + +/** + * Registration subghzdevice by index in array SubGhzDevice. + * @param index SubGhzDevice by index in array + * @return SubGhzDevice* pointer to a SubGhzDevice instance + */ +const SubGhzDevice* subghz_device_registry_get_by_index(size_t index); + +/** + * Getting the number of registered subghzdevices. + * @param subghz_device SubGhzDeviceRegistry + * @return Number of subghzdevices + */ +size_t subghz_device_registry_count(void); + +#ifdef __cplusplus +} +#endif diff --git a/lib/subghz/devices/types.h b/lib/subghz/devices/types.h new file mode 100644 index 00000000000..8a41984263f --- /dev/null +++ b/lib/subghz/devices/types.h @@ -0,0 +1,91 @@ +#pragma once + +#include +#include +#include + +#include +#include + +#include "preset.h" + +#include + +#define SUBGHZ_RADIO_DEVICE_PLUGIN_APP_ID "subghz_radio_device" +#define SUBGHZ_RADIO_DEVICE_PLUGIN_API_VERSION 1 + +typedef struct SubGhzDeviceRegistry SubGhzDeviceRegistry; +typedef struct SubGhzDevice SubGhzDevice; + +typedef bool (*SubGhzBegin)(void); +typedef void (*SubGhzEnd)(void); +typedef bool (*SubGhzIsConnect)(void); +typedef void (*SubGhzReset)(void); +typedef void (*SubGhzSleep)(void); +typedef void (*SubGhzIdle)(void); +typedef void (*SubGhzLoadPreset)(FuriHalSubGhzPreset preset, uint8_t* preset_data); +typedef uint32_t (*SubGhzSetFrequency)(uint32_t frequency); +typedef bool (*SubGhzIsFrequencyValid)(uint32_t frequency); + +typedef void (*SubGhzSetAsyncMirrorPin)(const GpioPin* gpio); +typedef const GpioPin* (*SubGhzGetDataGpio)(void); + +typedef bool (*SubGhzSetTx)(void); +typedef void (*SubGhzFlushTx)(void); +typedef bool (*SubGhzStartAsyncTx)(void* callback, void* context); +typedef bool (*SubGhzIsAsyncCompleteTx)(void); +typedef void (*SubGhzStopAsyncTx)(void); + +typedef void (*SubGhzSetRx)(void); +typedef void (*SubGhzFlushRx)(void); +typedef void (*SubGhzStartAsyncRx)(void* callback, void* context); +typedef void (*SubGhzStopAsyncRx)(void); + +typedef float (*SubGhzGetRSSI)(void); +typedef uint8_t (*SubGhzGetLQI)(void); + +typedef bool (*SubGhzRxPipeNotEmpty)(void); +typedef bool (*SubGhzRxIsDataCrcValid)(void); +typedef void (*SubGhzReadPacket)(uint8_t* data, uint8_t* size); +typedef void (*SubGhzWritePacket)(const uint8_t* data, uint8_t size); + +typedef struct { + SubGhzBegin begin; + SubGhzEnd end; + + SubGhzIsConnect is_connect; + SubGhzReset reset; + SubGhzSleep sleep; + SubGhzIdle idle; + + SubGhzLoadPreset load_preset; + SubGhzSetFrequency set_frequency; + SubGhzIsFrequencyValid is_frequency_valid; + SubGhzSetAsyncMirrorPin set_async_mirror_pin; + SubGhzGetDataGpio get_data_gpio; + + SubGhzSetTx set_tx; + SubGhzFlushTx flush_tx; + SubGhzStartAsyncTx start_async_tx; + SubGhzIsAsyncCompleteTx is_async_complete_tx; + SubGhzStopAsyncTx stop_async_tx; + + SubGhzSetRx set_rx; + SubGhzFlushRx flush_rx; + SubGhzStartAsyncRx start_async_rx; + SubGhzStopAsyncRx stop_async_rx; + + SubGhzGetRSSI get_rssi; + SubGhzGetLQI get_lqi; + + SubGhzRxPipeNotEmpty rx_pipe_not_empty; + SubGhzRxIsDataCrcValid is_rx_data_crc_valid; + SubGhzReadPacket read_packet; + SubGhzWritePacket write_packet; + +} SubGhzDeviceInterconnect; + +struct SubGhzDevice { + const char* name; + const SubGhzDeviceInterconnect* interconnect; +}; diff --git a/lib/subghz/protocols/raw.c b/lib/subghz/protocols/raw.c index 66358698ac9..1288c957371 100644 --- a/lib/subghz/protocols/raw.c +++ b/lib/subghz/protocols/raw.c @@ -40,6 +40,7 @@ struct SubGhzProtocolEncoderRAW { bool is_running; FuriString* file_name; + FuriString* radio_device_name; SubGhzFileEncoderWorker* file_worker_encoder; }; @@ -282,6 +283,7 @@ void* subghz_protocol_encoder_raw_alloc(SubGhzEnvironment* environment) { instance->base.protocol = &subghz_protocol_raw; instance->file_name = furi_string_alloc(); + instance->radio_device_name = furi_string_alloc(); instance->is_running = false; return instance; } @@ -300,6 +302,7 @@ void subghz_protocol_encoder_raw_free(void* context) { SubGhzProtocolEncoderRAW* instance = context; subghz_protocol_encoder_raw_stop(instance); furi_string_free(instance->file_name); + furi_string_free(instance->radio_device_name); free(instance); } @@ -318,7 +321,9 @@ static bool subghz_protocol_encoder_raw_worker_init(SubGhzProtocolEncoderRAW* in instance->file_worker_encoder = subghz_file_encoder_worker_alloc(); if(subghz_file_encoder_worker_start( - instance->file_worker_encoder, furi_string_get_cstr(instance->file_name))) { + instance->file_worker_encoder, + furi_string_get_cstr(instance->file_name), + furi_string_get_cstr(instance->radio_device_name))) { //the worker needs a file in order to open and read part of the file furi_delay_ms(100); instance->is_running = true; @@ -328,7 +333,10 @@ static bool subghz_protocol_encoder_raw_worker_init(SubGhzProtocolEncoderRAW* in return instance->is_running; } -void subghz_protocol_raw_gen_fff_data(FlipperFormat* flipper_format, const char* file_path) { +void subghz_protocol_raw_gen_fff_data( + FlipperFormat* flipper_format, + const char* file_path, + const char* radio_device_name) { do { stream_clean(flipper_format_get_raw_stream(flipper_format)); if(!flipper_format_write_string_cstr(flipper_format, "Protocol", "RAW")) { @@ -340,6 +348,12 @@ void subghz_protocol_raw_gen_fff_data(FlipperFormat* flipper_format, const char* FURI_LOG_E(TAG, "Unable to add File_name"); break; } + + if(!flipper_format_write_string_cstr( + flipper_format, "Radio_device_name", radio_device_name)) { + FURI_LOG_E(TAG, "Unable to add Radio_device_name"); + break; + } } while(false); } @@ -364,6 +378,13 @@ SubGhzProtocolStatus } furi_string_set(instance->file_name, temp_str); + if(!flipper_format_read_string(flipper_format, "Radio_device_name", temp_str)) { + FURI_LOG_E(TAG, "Missing Radio_device_name"); + res = SubGhzProtocolStatusErrorParserOthers; + break; + } + furi_string_set(instance->radio_device_name, temp_str); + if(!subghz_protocol_encoder_raw_worker_init(instance)) { res = SubGhzProtocolStatusErrorEncoderGetUpload; break; diff --git a/lib/subghz/protocols/raw.h b/lib/subghz/protocols/raw.h index 4f67a4e2f24..6d791bb3663 100644 --- a/lib/subghz/protocols/raw.h +++ b/lib/subghz/protocols/raw.h @@ -126,8 +126,12 @@ void subghz_protocol_raw_file_encoder_worker_set_callback_end( * File generation for RAW work. * @param flipper_format Pointer to a FlipperFormat instance * @param file_path File path + * @param radio_dev_name Radio device name */ -void subghz_protocol_raw_gen_fff_data(FlipperFormat* flipper_format, const char* file_path); +void subghz_protocol_raw_gen_fff_data( + FlipperFormat* flipper_format, + const char* file_path, + const char* radio_dev_name); /** * Deserialize and generating an upload to send. diff --git a/lib/subghz/subghz_file_encoder_worker.c b/lib/subghz/subghz_file_encoder_worker.c index 5c4d36f78e8..519ff3fdcc3 100644 --- a/lib/subghz/subghz_file_encoder_worker.c +++ b/lib/subghz/subghz_file_encoder_worker.c @@ -3,6 +3,7 @@ #include #include #include +#include #define TAG "SubGhzFileEncoderWorker" @@ -21,6 +22,7 @@ struct SubGhzFileEncoderWorker { bool is_storage_slow; FuriString* str_data; FuriString* file_path; + const SubGhzDevice* device; SubGhzFileEncoderWorkerCallbackEnd callback_end; void* context_end; @@ -156,10 +158,13 @@ static int32_t subghz_file_encoder_worker_thread(void* context) { if(instance->is_storage_slow) { FURI_LOG_E(TAG, "Storage is slow"); } + FURI_LOG_I(TAG, "End read file"); - while(!furi_hal_subghz_is_async_tx_complete() && instance->worker_running) { + while(instance->device && !subghz_devices_is_async_complete_tx(instance->device) && + instance->worker_running) { furi_delay_ms(5); } + FURI_LOG_I(TAG, "End transmission"); while(instance->worker_running) { if(instance->worker_stoping) { @@ -206,12 +211,16 @@ void subghz_file_encoder_worker_free(SubGhzFileEncoderWorker* instance) { free(instance); } -bool subghz_file_encoder_worker_start(SubGhzFileEncoderWorker* instance, const char* file_path) { +bool subghz_file_encoder_worker_start( + SubGhzFileEncoderWorker* instance, + const char* file_path, + const char* radio_device_name) { furi_assert(instance); furi_assert(!instance->worker_running); furi_stream_buffer_reset(instance->stream); furi_string_set(instance->file_path, file_path); + instance->device = subghz_devices_get_by_name(radio_device_name); instance->worker_running = true; furi_thread_start(instance->thread); diff --git a/lib/subghz/subghz_file_encoder_worker.h b/lib/subghz/subghz_file_encoder_worker.h index a87be5cd6f3..e66c66d76ea 100644 --- a/lib/subghz/subghz_file_encoder_worker.h +++ b/lib/subghz/subghz_file_encoder_worker.h @@ -38,9 +38,14 @@ LevelDuration subghz_file_encoder_worker_get_level_duration(void* context); /** * Start SubGhzFileEncoderWorker. * @param instance Pointer to a SubGhzFileEncoderWorker instance + * @param file_path File path + * @param radio_device_name Radio device name * @return bool - true if ok */ -bool subghz_file_encoder_worker_start(SubGhzFileEncoderWorker* instance, const char* file_path); +bool subghz_file_encoder_worker_start( + SubGhzFileEncoderWorker* instance, + const char* file_path, + const char* radio_device_name); /** * Stop SubGhzFileEncoderWorker diff --git a/lib/subghz/subghz_setting.c b/lib/subghz/subghz_setting.c index 656580043f0..9804f827733 100644 --- a/lib/subghz/subghz_setting.c +++ b/lib/subghz/subghz_setting.c @@ -4,7 +4,7 @@ #include #include -#include +#include #define TAG "SubGhzSetting" @@ -218,8 +218,7 @@ void subghz_setting_free(SubGhzSetting* instance) { static void subghz_setting_load_default_preset( SubGhzSetting* instance, const char* preset_name, - const uint8_t* preset_data, - const uint8_t preset_pa_table[8]) { + const uint8_t* preset_data) { furi_assert(instance); furi_assert(preset_data); uint32_t preset_data_count = 0; @@ -235,10 +234,8 @@ static void subghz_setting_load_default_preset( preset_data_count += 2; item->custom_preset_data_size = sizeof(uint8_t) * preset_data_count + sizeof(uint8_t) * 8; item->custom_preset_data = malloc(item->custom_preset_data_size); - //load preset register - memcpy(&item->custom_preset_data[0], &preset_data[0], preset_data_count); - //load pa table - memcpy(&item->custom_preset_data[preset_data_count], &preset_pa_table[0], 8); + //load preset register + pa table + memcpy(&item->custom_preset_data[0], &preset_data[0], item->custom_preset_data_size); } static void subghz_setting_load_default_region( @@ -262,25 +259,13 @@ static void subghz_setting_load_default_region( } subghz_setting_load_default_preset( - instance, - "AM270", - (uint8_t*)furi_hal_subghz_preset_ook_270khz_async_regs, - furi_hal_subghz_preset_ook_async_patable); + instance, "AM270", subghz_device_cc1101_preset_ook_270khz_async_regs); subghz_setting_load_default_preset( - instance, - "AM650", - (uint8_t*)furi_hal_subghz_preset_ook_650khz_async_regs, - furi_hal_subghz_preset_ook_async_patable); + instance, "AM650", subghz_device_cc1101_preset_ook_650khz_async_regs); subghz_setting_load_default_preset( - instance, - "FM238", - (uint8_t*)furi_hal_subghz_preset_2fsk_dev2_38khz_async_regs, - furi_hal_subghz_preset_2fsk_async_patable); + instance, "FM238", subghz_device_cc1101_preset_2fsk_dev2_38khz_async_regs); subghz_setting_load_default_preset( - instance, - "FM476", - (uint8_t*)furi_hal_subghz_preset_2fsk_dev47_6khz_async_regs, - furi_hal_subghz_preset_2fsk_async_patable); + instance, "FM476", subghz_device_cc1101_preset_2fsk_dev47_6khz_async_regs); } void subghz_setting_load_default(SubGhzSetting* instance) { @@ -359,6 +344,7 @@ void subghz_setting_load(SubGhzSetting* instance, const char* file_path) { } while(flipper_format_read_uint32( fff_data_file, "Frequency", (uint32_t*)&temp_data32, 1)) { + //Todo: add a frequency support check depending on the selected radio device if(furi_hal_subghz_is_frequency_valid(temp_data32)) { FURI_LOG_I(TAG, "Frequency loaded %lu", temp_data32); FrequencyList_push_back(instance->frequencies, temp_data32); diff --git a/lib/subghz/subghz_tx_rx_worker.c b/lib/subghz/subghz_tx_rx_worker.c index 42124bebc1c..250e6666f40 100644 --- a/lib/subghz/subghz_tx_rx_worker.c +++ b/lib/subghz/subghz_tx_rx_worker.c @@ -21,6 +21,8 @@ struct SubGhzTxRxWorker { SubGhzTxRxWorkerStatus status; uint32_t frequency; + const SubGhzDevice* device; + const GpioPin* device_data_gpio; SubGhzTxRxWorkerCallbackHaveRead callback_have_read; void* context_have_read; @@ -65,33 +67,33 @@ bool subghz_tx_rx_worker_rx(SubGhzTxRxWorker* instance, uint8_t* data, uint8_t* uint8_t timeout = 100; bool ret = false; if(instance->status != SubGhzTxRxWorkerStatusRx) { - furi_hal_subghz_rx(); + subghz_devices_set_rx(instance->device); instance->status = SubGhzTxRxWorkerStatusRx; furi_delay_tick(1); } //waiting for reception to complete - while(furi_hal_gpio_read(&gpio_cc1101_g0)) { + while(furi_hal_gpio_read(instance->device_data_gpio)) { furi_delay_tick(1); if(!--timeout) { FURI_LOG_W(TAG, "RX cc1101_g0 timeout"); - furi_hal_subghz_flush_rx(); - furi_hal_subghz_rx(); + subghz_devices_flush_rx(instance->device); + subghz_devices_set_rx(instance->device); break; } } - if(furi_hal_subghz_rx_pipe_not_empty()) { + if(subghz_devices_rx_pipe_not_empty(instance->device)) { FURI_LOG_I( TAG, "RSSI: %03.1fdbm LQI: %d", - (double)furi_hal_subghz_get_rssi(), - furi_hal_subghz_get_lqi()); - if(furi_hal_subghz_is_rx_data_crc_valid()) { - furi_hal_subghz_read_packet(data, size); + (double)subghz_devices_get_rssi(instance->device), + subghz_devices_get_lqi(instance->device)); + if(subghz_devices_is_rx_data_crc_valid(instance->device)) { + subghz_devices_read_packet(instance->device, data, size); ret = true; } - furi_hal_subghz_flush_rx(); - furi_hal_subghz_rx(); + subghz_devices_flush_rx(instance->device); + subghz_devices_set_rx(instance->device); } return ret; } @@ -99,26 +101,28 @@ bool subghz_tx_rx_worker_rx(SubGhzTxRxWorker* instance, uint8_t* data, uint8_t* void subghz_tx_rx_worker_tx(SubGhzTxRxWorker* instance, uint8_t* data, size_t size) { uint8_t timeout = 200; if(instance->status != SubGhzTxRxWorkerStatusIDLE) { - furi_hal_subghz_idle(); + subghz_devices_idle(instance->device); } - furi_hal_subghz_write_packet(data, size); - furi_hal_subghz_tx(); //start send + subghz_devices_write_packet(instance->device, data, size); + subghz_devices_set_tx(instance->device); //start send instance->status = SubGhzTxRxWorkerStatusTx; - while(!furi_hal_gpio_read(&gpio_cc1101_g0)) { // Wait for GDO0 to be set -> sync transmitted + while(!furi_hal_gpio_read( + instance->device_data_gpio)) { // Wait for GDO0 to be set -> sync transmitted furi_delay_tick(1); if(!--timeout) { FURI_LOG_W(TAG, "TX !cc1101_g0 timeout"); break; } } - while(furi_hal_gpio_read(&gpio_cc1101_g0)) { // Wait for GDO0 to be cleared -> end of packet + while(furi_hal_gpio_read( + instance->device_data_gpio)) { // Wait for GDO0 to be cleared -> end of packet furi_delay_tick(1); if(!--timeout) { FURI_LOG_W(TAG, "TX cc1101_g0 timeout"); break; } } - furi_hal_subghz_idle(); + subghz_devices_idle(instance->device); instance->status = SubGhzTxRxWorkerStatusIDLE; } /** Worker thread @@ -128,16 +132,19 @@ void subghz_tx_rx_worker_tx(SubGhzTxRxWorker* instance, uint8_t* data, size_t si */ static int32_t subghz_tx_rx_worker_thread(void* context) { SubGhzTxRxWorker* instance = context; + furi_assert(instance->device); FURI_LOG_I(TAG, "Worker start"); - furi_hal_subghz_reset(); - furi_hal_subghz_idle(); - furi_hal_subghz_load_preset(FuriHalSubGhzPresetGFSK9_99KbAsync); - //furi_hal_subghz_load_preset(FuriHalSubGhzPresetMSK99_97KbAsync); - furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow); + subghz_devices_begin(instance->device); + instance->device_data_gpio = subghz_devices_get_data_gpio(instance->device); + subghz_devices_reset(instance->device); + subghz_devices_idle(instance->device); + subghz_devices_load_preset(instance->device, FuriHalSubGhzPresetGFSK9_99KbAsync, NULL); - furi_hal_subghz_set_frequency_and_path(instance->frequency); - furi_hal_subghz_flush_rx(); + furi_hal_gpio_init(instance->device_data_gpio, GpioModeInput, GpioPullNo, GpioSpeedLow); + + subghz_devices_set_frequency(instance->device, instance->frequency); + subghz_devices_flush_rx(instance->device); uint8_t data[SUBGHZ_TXRX_WORKER_MAX_TXRX_SIZE + 1] = {0}; size_t size_tx = 0; @@ -191,8 +198,8 @@ static int32_t subghz_tx_rx_worker_thread(void* context) { furi_delay_tick(1); } - furi_hal_subghz_set_path(FuriHalSubGhzPathIsolate); - furi_hal_subghz_sleep(); + subghz_devices_sleep(instance->device); + subghz_devices_end(instance->device); FURI_LOG_I(TAG, "Worker stop"); return 0; @@ -224,7 +231,10 @@ void subghz_tx_rx_worker_free(SubGhzTxRxWorker* instance) { free(instance); } -bool subghz_tx_rx_worker_start(SubGhzTxRxWorker* instance, uint32_t frequency) { +bool subghz_tx_rx_worker_start( + SubGhzTxRxWorker* instance, + const SubGhzDevice* device, + uint32_t frequency) { furi_assert(instance); furi_assert(!instance->worker_running); bool res = false; @@ -235,6 +245,7 @@ bool subghz_tx_rx_worker_start(SubGhzTxRxWorker* instance, uint32_t frequency) { if(furi_hal_region_is_frequency_allowed(frequency)) { instance->frequency = frequency; + instance->device = device; res = true; } diff --git a/lib/subghz/subghz_tx_rx_worker.h b/lib/subghz/subghz_tx_rx_worker.h index ddc02e749d8..56bdb0a1feb 100644 --- a/lib/subghz/subghz_tx_rx_worker.h +++ b/lib/subghz/subghz_tx_rx_worker.h @@ -1,6 +1,7 @@ #pragma once #include +#include #ifdef __cplusplus extern "C" { @@ -67,9 +68,13 @@ void subghz_tx_rx_worker_free(SubGhzTxRxWorker* instance); /** * Start SubGhzTxRxWorker * @param instance Pointer to a SubGhzTxRxWorker instance + * @param device Pointer to a SubGhzDevice instance * @return bool - true if ok */ -bool subghz_tx_rx_worker_start(SubGhzTxRxWorker* instance, uint32_t frequency); +bool subghz_tx_rx_worker_start( + SubGhzTxRxWorker* instance, + const SubGhzDevice* device, + uint32_t frequency); /** * Stop SubGhzTxRxWorker diff --git a/lib/subghz/types.h b/lib/subghz/types.h index 719beff45f8..d87a0dc760c 100644 --- a/lib/subghz/types.h +++ b/lib/subghz/types.h @@ -21,6 +21,12 @@ #define SUBGHZ_RAW_FILE_VERSION 1 #define SUBGHZ_RAW_FILE_TYPE "Flipper SubGhz RAW File" +#define SUBGHZ_KEYSTORE_DIR_NAME EXT_PATH("subghz/assets/keeloq_mfcodes") +#define SUBGHZ_KEYSTORE_DIR_USER_NAME EXT_PATH("subghz/assets/keeloq_mfcodes_user") +#define SUBGHZ_CAME_ATOMO_DIR_NAME EXT_PATH("subghz/assets/came_atomo") +#define SUBGHZ_NICE_FLOR_S_DIR_NAME EXT_PATH("subghz/assets/nice_flor_s") +#define SUBGHZ_ALUTECH_AT_4N_DIR_NAME EXT_PATH("subghz/assets/alutech_at_4n") + typedef struct SubGhzProtocolRegistry SubGhzProtocolRegistry; typedef struct SubGhzEnvironment SubGhzEnvironment; diff --git a/site_scons/commandline.scons b/site_scons/commandline.scons index b70b5cff56f..776977cda74 100644 --- a/site_scons/commandline.scons +++ b/site_scons/commandline.scons @@ -228,6 +228,7 @@ vars.AddVariables( ("applications/debug", False), ("applications/external", False), ("applications/examples", False), + ("applications/drivers", False), ("applications_user", False), ], ), From 88f8f68e292629d2c11c12334804c1f14742d032 Mon Sep 17 00:00:00 2001 From: Slavik Nychkalo Date: Fri, 30 Jun 2023 18:50:46 +0300 Subject: [PATCH 637/824] fix width of submenu items on Vertical orientation (#2306) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix width of submenu items on vertical view * Gui: slightly better canvas width handling in submenu * Gui: remove unused include Co-authored-by: あく --- applications/services/gui/modules/submenu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/services/gui/modules/submenu.c b/applications/services/gui/modules/submenu.c index 9d81c30b688..3ba35edde07 100644 --- a/applications/services/gui/modules/submenu.c +++ b/applications/services/gui/modules/submenu.c @@ -63,7 +63,7 @@ static void submenu_view_draw_callback(Canvas* canvas, void* _model) { SubmenuModel* model = _model; const uint8_t item_height = 16; - const uint8_t item_width = 123; + uint8_t item_width = canvas_width(canvas) - 5; canvas_clear(canvas); From e7bd547d05c9cb70c5da26caa39ad1efcfd04e74 Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Fri, 30 Jun 2023 20:56:41 +0300 Subject: [PATCH 638/824] SubGHz: properly working with missing external driver (#2821) --- applications/main/subghz/helpers/subghz_txrx.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/applications/main/subghz/helpers/subghz_txrx.c b/applications/main/subghz/helpers/subghz_txrx.c index f117d397412..b911f443459 100644 --- a/applications/main/subghz/helpers/subghz_txrx.c +++ b/applications/main/subghz/helpers/subghz_txrx.c @@ -552,7 +552,10 @@ bool subghz_txrx_radio_device_is_external_connected(SubGhzTxRx* instance, const subghz_txrx_radio_device_power_on(instance); } - is_connect = subghz_devices_is_connect(subghz_devices_get_by_name(name)); + const SubGhzDevice* device = subghz_devices_get_by_name(name); + if(device) { + is_connect = subghz_devices_is_connect(device); + } if(!is_otg_enabled) { subghz_txrx_radio_device_power_off(instance); From bb169978097b13f30d93592d7d13903b747822bb Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Tue, 4 Jul 2023 12:38:47 +0300 Subject: [PATCH 639/824] [FL-3398] Desktop settings: show icon and name for external applications (#2837) --- .../scenes/desktop_settings_scene_favorite.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c index 698cfae1b81..c43f8e350bd 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c @@ -1,6 +1,7 @@ #include "../desktop_settings_app.h" #include "applications.h" #include "desktop_settings_scene.h" +#include #include #include @@ -13,16 +14,9 @@ static bool favorite_fap_selector_item_callback( uint8_t** icon_ptr, FuriString* item_name) { UNUSED(context); -#ifdef APP_FAP_LOADER Storage* storage = furi_record_open(RECORD_STORAGE); - bool success = fap_loader_load_name_and_icon(file_path, storage, icon_ptr, item_name); + bool success = flipper_application_load_name_and_icon(file_path, storage, icon_ptr, item_name); furi_record_close(RECORD_STORAGE); -#else - UNUSED(file_path); - UNUSED(icon_ptr); - UNUSED(item_name); - bool success = false; -#endif return success; } From 08bafc478e98f6d179e059759cc13d9bf199a151 Mon Sep 17 00:00:00 2001 From: Eric Betts Date: Wed, 5 Jul 2023 02:26:50 -0700 Subject: [PATCH 640/824] Picopass fix ice (#2836) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix copypaste error * Add iCE key to dictionary * Write iCE key as elite, others with standard kdf Co-authored-by: あく --- applications/external/picopass/picopass_device.c | 3 +++ applications/external/picopass/picopass_device.h | 1 + applications/external/picopass/picopass_worker.c | 3 ++- .../external/picopass/scenes/picopass_scene_key_menu.c | 8 ++++++-- .../apps_data/picopass/assets/iclass_elite_dict.txt | 4 ++++ 5 files changed, 16 insertions(+), 3 deletions(-) diff --git a/applications/external/picopass/picopass_device.c b/applications/external/picopass/picopass_device.c index 53778cfb322..de43b0bb7c5 100644 --- a/applications/external/picopass/picopass_device.c +++ b/applications/external/picopass/picopass_device.c @@ -16,6 +16,7 @@ PicopassDevice* picopass_device_alloc() { PicopassDevice* picopass_dev = malloc(sizeof(PicopassDevice)); picopass_dev->dev_data.pacs.legacy = false; picopass_dev->dev_data.pacs.se_enabled = false; + picopass_dev->dev_data.pacs.elite_kdf = false; picopass_dev->dev_data.pacs.pin_length = 0; picopass_dev->storage = furi_record_open(RECORD_STORAGE); picopass_dev->dialogs = furi_record_open(RECORD_DIALOGS); @@ -77,6 +78,7 @@ static bool picopass_device_save_file( break; } } + // TODO: Add elite if(!flipper_format_write_comment_cstr(file, "Picopass blocks")) break; bool block_saved = true; @@ -256,6 +258,7 @@ void picopass_device_data_clear(PicopassDeviceData* dev_data) { } dev_data->pacs.legacy = false; dev_data->pacs.se_enabled = false; + dev_data->pacs.elite_kdf = false; dev_data->pacs.pin_length = 0; } diff --git a/applications/external/picopass/picopass_device.h b/applications/external/picopass/picopass_device.h index 7fc35ebda15..b45df346cf2 100644 --- a/applications/external/picopass/picopass_device.h +++ b/applications/external/picopass/picopass_device.h @@ -62,6 +62,7 @@ typedef struct { bool sio; bool biometrics; uint8_t key[8]; + bool elite_kdf; uint8_t pin_length; PicopassEncryption encryption; uint8_t credential[8]; diff --git a/applications/external/picopass/picopass_worker.c b/applications/external/picopass/picopass_worker.c index e671552c5cb..6301704ca8b 100644 --- a/applications/external/picopass/picopass_worker.c +++ b/applications/external/picopass/picopass_worker.c @@ -550,6 +550,7 @@ void picopass_worker_elite_dict_attack(PicopassWorker* picopass_worker) { if(err == ERR_NONE) { FURI_LOG_I(TAG, "Found key"); memcpy(pacs->key, key, PICOPASS_BLOCK_LEN); + pacs->elite_kdf = elite; err = picopass_read_card(AA1); if(err != ERR_NONE) { FURI_LOG_E(TAG, "picopass_read_card error %d", err); @@ -720,7 +721,7 @@ void picopass_worker_write_key(PicopassWorker* picopass_worker) { uint8_t* oldKey = AA1[PICOPASS_KD_BLOCK_INDEX].data; uint8_t newKey[PICOPASS_BLOCK_LEN] = {0}; - loclass_iclass_calc_div_key(csn, pacs->key, newKey, false); + loclass_iclass_calc_div_key(csn, pacs->key, newKey, pacs->elite_kdf); if((fuses & 0x80) == 0x80) { FURI_LOG_D(TAG, "Plain write for personalized mode key change"); diff --git a/applications/external/picopass/scenes/picopass_scene_key_menu.c b/applications/external/picopass/scenes/picopass_scene_key_menu.c index 8aac6cb2491..15a32ff4418 100644 --- a/applications/external/picopass/scenes/picopass_scene_key_menu.c +++ b/applications/external/picopass/scenes/picopass_scene_key_menu.c @@ -60,24 +60,28 @@ bool picopass_scene_key_menu_on_event(void* context, SceneManagerEvent event) { scene_manager_set_scene_state( picopass->scene_manager, PicopassSceneKeyMenu, SubmenuIndexWriteStandard); memcpy(picopass->dev->dev_data.pacs.key, picopass_iclass_key, PICOPASS_BLOCK_LEN); + picopass->dev->dev_data.pacs.elite_kdf = false; scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteKey); consumed = true; } else if(event.event == SubmenuIndexWriteiCE) { scene_manager_set_scene_state( picopass->scene_manager, PicopassSceneKeyMenu, SubmenuIndexWriteiCE); memcpy(picopass->dev->dev_data.pacs.key, picopass_xice_key, PICOPASS_BLOCK_LEN); + picopass->dev->dev_data.pacs.elite_kdf = true; scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteKey); consumed = true; } else if(event.event == SubmenuIndexWriteiCL) { scene_manager_set_scene_state( - picopass->scene_manager, PicopassSceneKeyMenu, SubmenuIndexWriteiCE); + picopass->scene_manager, PicopassSceneKeyMenu, SubmenuIndexWriteiCL); memcpy(picopass->dev->dev_data.pacs.key, picopass_xicl_key, PICOPASS_BLOCK_LEN); + picopass->dev->dev_data.pacs.elite_kdf = false; scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteKey); consumed = true; } else if(event.event == SubmenuIndexWriteiCS) { scene_manager_set_scene_state( - picopass->scene_manager, PicopassSceneKeyMenu, SubmenuIndexWriteiCE); + picopass->scene_manager, PicopassSceneKeyMenu, SubmenuIndexWriteiCS); memcpy(picopass->dev->dev_data.pacs.key, picopass_xics_key, PICOPASS_BLOCK_LEN); + picopass->dev->dev_data.pacs.elite_kdf = false; scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteKey); consumed = true; } diff --git a/assets/resources/apps_data/picopass/assets/iclass_elite_dict.txt b/assets/resources/apps_data/picopass/assets/iclass_elite_dict.txt index d1189237245..908889aecf6 100644 --- a/assets/resources/apps_data/picopass/assets/iclass_elite_dict.txt +++ b/assets/resources/apps_data/picopass/assets/iclass_elite_dict.txt @@ -34,4 +34,8 @@ C1B74D7478053AE2 # default iCLASS RFIDeas 6B65797374726B72 +# CTF key 5C100DF7042EAE64 + +# iCopy-X DRM key (iCE product) +2020666666668888 From 97fbd84e08d77a3ef2edf7a059c8d3635cd7facc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Wed, 5 Jul 2023 22:56:52 +0900 Subject: [PATCH 641/824] FuriHal, Infrared, Protobuf: various fixes and improvements (#2845) * Infrared: fix crash caused by null-ptr dereference in interrupt call. FuriHal: callback checks in interrupt handlers. Protobuf: bump to latest. * DesktopSettings: allow application browser as favourite * Format sources * FuriHal: fix pvs warnings --- .../scenes/desktop_settings_scene_favorite.c | 27 ++++++++++++++++--- assets/protobuf | 2 +- .../targets/f7/furi_hal/furi_hal_infrared.c | 3 --- .../targets/f7/furi_hal/furi_hal_interrupt.c | 11 ++++---- 4 files changed, 30 insertions(+), 13 deletions(-) diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c index c43f8e350bd..c35a10568af 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c @@ -5,8 +5,11 @@ #include #include +#define EXTERNAL_BROWSER_NAME ("Applications") +#define EXTERNAL_BROWSER_INDEX (FLIPPER_APPS_COUNT + 1) + #define EXTERNAL_APPLICATION_NAME ("[External Application]") -#define EXTERNAL_APPLICATION_INDEX (FLIPPER_APPS_COUNT + 1) +#define EXTERNAL_APPLICATION_INDEX (FLIPPER_APPS_COUNT + 2) static bool favorite_fap_selector_item_callback( FuriString* file_path, @@ -58,14 +61,28 @@ void desktop_settings_scene_favorite_on_enter(void* context) { } } + // Special case: Application browser + submenu_add_item( + submenu, + EXTERNAL_BROWSER_NAME, + EXTERNAL_BROWSER_INDEX, + desktop_settings_scene_favorite_submenu_callback, + app); + + // Special case: Specific application submenu_add_item( submenu, EXTERNAL_APPLICATION_NAME, EXTERNAL_APPLICATION_INDEX, desktop_settings_scene_favorite_submenu_callback, app); + if(curr_favorite_app->is_external) { - pre_select_item = EXTERNAL_APPLICATION_INDEX; + if(curr_favorite_app->name_or_path[0] == '\0') { + pre_select_item = EXTERNAL_BROWSER_INDEX; + } else { + pre_select_item = EXTERNAL_APPLICATION_INDEX; + } } submenu_set_header( @@ -86,7 +103,11 @@ bool desktop_settings_scene_favorite_on_event(void* context, SceneManagerEvent e &app->settings.favorite_secondary; if(event.type == SceneManagerEventTypeCustom) { - if(event.event == EXTERNAL_APPLICATION_INDEX) { + if(event.event == EXTERNAL_BROWSER_INDEX) { + curr_favorite_app->is_external = true; + curr_favorite_app->name_or_path[0] = '\0'; + consumed = true; + } else if(event.event == EXTERNAL_APPLICATION_INDEX) { const DialogsFileBrowserOptions browser_options = { .extension = ".fap", .icon = &I_unknown_10px, diff --git a/assets/protobuf b/assets/protobuf index f71c4b7f750..08a907d9573 160000 --- a/assets/protobuf +++ b/assets/protobuf @@ -1 +1 @@ -Subproject commit f71c4b7f750f2539a1fed08925d8da3abdc80ff9 +Subproject commit 08a907d95733600becc41c0602ef5ee4c4baf782 diff --git a/firmware/targets/f7/furi_hal/furi_hal_infrared.c b/firmware/targets/f7/furi_hal/furi_hal_infrared.c index c60db5f20ef..d3e36c2b5d4 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_infrared.c +++ b/firmware/targets/f7/furi_hal/furi_hal_infrared.c @@ -373,9 +373,6 @@ static void furi_hal_infrared_configure_tim_pwm_tx(uint32_t freq, float duty_cyc LL_TIM_EnableAllOutputs(INFRARED_DMA_TIMER); LL_TIM_DisableIT_UPDATE(INFRARED_DMA_TIMER); LL_TIM_EnableDMAReq_UPDATE(INFRARED_DMA_TIMER); - - NVIC_SetPriority(TIM1_UP_TIM16_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 5, 0)); - NVIC_EnableIRQ(TIM1_UP_TIM16_IRQn); } static void furi_hal_infrared_configure_tim_cmgr2_dma_tx(void) { diff --git a/firmware/targets/f7/furi_hal/furi_hal_interrupt.c b/firmware/targets/f7/furi_hal/furi_hal_interrupt.c index b5639d2300c..9f4d43316da 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_interrupt.c +++ b/firmware/targets/f7/furi_hal/furi_hal_interrupt.c @@ -62,7 +62,7 @@ const IRQn_Type furi_hal_interrupt_irqn[FuriHalInterruptIdMax] = { __attribute__((always_inline)) static inline void furi_hal_interrupt_call(FuriHalInterruptId index) { - furi_assert(furi_hal_interrupt_isr[index].isr); + furi_check(furi_hal_interrupt_isr[index].isr); furi_hal_interrupt_isr[index].isr(furi_hal_interrupt_isr[index].context); } @@ -127,16 +127,15 @@ void furi_hal_interrupt_set_isr_ex( uint16_t priority, FuriHalInterruptISR isr, void* context) { - furi_assert(index < FuriHalInterruptIdMax); - furi_assert(priority < 15); - furi_assert(furi_hal_interrupt_irqn[index]); + furi_check(index < FuriHalInterruptIdMax); + furi_check(priority < 15); if(isr) { // Pre ISR set - furi_assert(furi_hal_interrupt_isr[index].isr == NULL); + furi_check(furi_hal_interrupt_isr[index].isr == NULL); } else { // Pre ISR clear - furi_assert(furi_hal_interrupt_isr[index].isr != NULL); + furi_check(furi_hal_interrupt_isr[index].isr != NULL); furi_hal_interrupt_disable(index); furi_hal_interrupt_clear_pending(index); } From f3ae09cc16550025d42766f87d1db521e03e92d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Thu, 6 Jul 2023 01:39:17 +0900 Subject: [PATCH 642/824] FuriHal: allow nulling null isr (#2846) * FuriHal: allow nulling null isr * FuriHal: include interrupt priority 15 as allowed * Furi: prevent compiler from optimizing arg in r0 of RESTORE_REGISTERS_AND_HALT_MCU --- firmware/targets/f7/furi_hal/furi_hal_interrupt.c | 3 +-- furi/core/check.c | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/firmware/targets/f7/furi_hal/furi_hal_interrupt.c b/firmware/targets/f7/furi_hal/furi_hal_interrupt.c index 9f4d43316da..c508dac72bf 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_interrupt.c +++ b/firmware/targets/f7/furi_hal/furi_hal_interrupt.c @@ -128,14 +128,13 @@ void furi_hal_interrupt_set_isr_ex( FuriHalInterruptISR isr, void* context) { furi_check(index < FuriHalInterruptIdMax); - furi_check(priority < 15); + furi_check(priority <= 15); if(isr) { // Pre ISR set furi_check(furi_hal_interrupt_isr[index].isr == NULL); } else { // Pre ISR clear - furi_check(furi_hal_interrupt_isr[index].isr != NULL); furi_hal_interrupt_disable(index); furi_hal_interrupt_clear_pending(index); } diff --git a/furi/core/check.c b/furi/core/check.c index c5c4ef1a4af..f7dcfc59590 100644 --- a/furi/core/check.c +++ b/furi/core/check.c @@ -36,7 +36,7 @@ PLACE_IN_SECTION("MB_MEM2") uint32_t __furi_check_registers[13] = {0}; * */ #define RESTORE_REGISTERS_AND_HALT_MCU(debug) \ - register const bool r0 asm("r0") = debug; \ + register bool r0 asm("r0") = debug; \ asm volatile("cbnz r0, with_debugger%= \n" \ "ldr r12, =__furi_check_registers\n" \ "ldm r12, {r0-r11} \n" \ From 906cca8f242c7864565464a50b0a2ab6cbfa0475 Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Wed, 5 Jul 2023 20:48:02 +0400 Subject: [PATCH 643/824] Furi_Power: fix furi_hal_power_enable_otg (#2842) * Furi_Power: fix furi_hal_power_enable_otg * SubGhz: fix error output connected USB * Furi_Hal: fix target F18 * Fix api_symbols.csv version for F7 Co-authored-by: Aleksandr Kutuzov --- applications/main/subghz/helpers/subghz_txrx.c | 16 +++++++++++----- firmware/targets/f18/api_symbols.csv | 5 +++-- firmware/targets/f7/api_symbols.csv | 5 +++-- firmware/targets/f7/furi_hal/furi_hal_power.c | 14 +++++++++++++- .../targets/furi_hal_include/furi_hal_power.h | 6 +++++- lib/drivers/bq25896.c | 8 +++++++- lib/drivers/bq25896.h | 3 +++ lib/drivers/bq25896_reg.h | 18 ++++++++++-------- 8 files changed, 55 insertions(+), 20 deletions(-) diff --git a/applications/main/subghz/helpers/subghz_txrx.c b/applications/main/subghz/helpers/subghz_txrx.c index b911f443459..cbd47f5e5ee 100644 --- a/applications/main/subghz/helpers/subghz_txrx.c +++ b/applications/main/subghz/helpers/subghz_txrx.c @@ -8,11 +8,17 @@ static void subghz_txrx_radio_device_power_on(SubGhzTxRx* instance) { UNUSED(instance); - uint8_t attempts = 0; - while(!furi_hal_power_is_otg_enabled() && attempts++ < 5) { - furi_hal_power_enable_otg(); - //CC1101 power-up time - furi_delay_ms(10); + uint8_t attempts = 5; + while(--attempts > 0) { + if(furi_hal_power_enable_otg()) break; + } + if(attempts == 0) { + if(furi_hal_power_get_usb_voltage() < 4.5f) { + FURI_LOG_E( + TAG, + "Error power otg enable. BQ2589 check otg fault = %d", + furi_hal_power_check_otg_fault() ? 1 : 0); + } } } diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index 1884fe43373..46099799bd2 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,32.0,, +Version,+,33.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -1030,12 +1030,13 @@ Function,+,furi_hal_mpu_protect_no_access,void,"FuriHalMpuRegion, uint32_t, Furi Function,+,furi_hal_mpu_protect_read_only,void,"FuriHalMpuRegion, uint32_t, FuriHalMPURegionSize" Function,-,furi_hal_os_init,void, Function,+,furi_hal_os_tick,void, +Function,+,furi_hal_power_check_otg_fault,_Bool, Function,+,furi_hal_power_check_otg_status,void, Function,+,furi_hal_power_debug_get,void,"PropertyValueCallback, void*" Function,+,furi_hal_power_disable_external_3_3v,void, Function,+,furi_hal_power_disable_otg,void, Function,+,furi_hal_power_enable_external_3_3v,void, -Function,+,furi_hal_power_enable_otg,void, +Function,+,furi_hal_power_enable_otg,_Bool, Function,+,furi_hal_power_gauge_is_ok,_Bool, Function,+,furi_hal_power_get_bat_health_pct,uint8_t, Function,+,furi_hal_power_get_battery_charge_voltage_limit,float, diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index f4dec15a77f..dbaeb8e4cd4 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,32.0,, +Version,+,33.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -1235,12 +1235,13 @@ Function,+,furi_hal_nfc_tx_rx,_Bool,"FuriHalNfcTxRxContext*, uint16_t" Function,+,furi_hal_nfc_tx_rx_full,_Bool,FuriHalNfcTxRxContext* Function,-,furi_hal_os_init,void, Function,+,furi_hal_os_tick,void, +Function,+,furi_hal_power_check_otg_fault,_Bool, Function,+,furi_hal_power_check_otg_status,void, Function,+,furi_hal_power_debug_get,void,"PropertyValueCallback, void*" Function,+,furi_hal_power_disable_external_3_3v,void, Function,+,furi_hal_power_disable_otg,void, Function,+,furi_hal_power_enable_external_3_3v,void, -Function,+,furi_hal_power_enable_otg,void, +Function,+,furi_hal_power_enable_otg,_Bool, Function,+,furi_hal_power_gauge_is_ok,_Bool, Function,+,furi_hal_power_get_bat_health_pct,uint8_t, Function,+,furi_hal_power_get_battery_charge_voltage_limit,float, diff --git a/firmware/targets/f7/furi_hal/furi_hal_power.c b/firmware/targets/f7/furi_hal/furi_hal_power.c index ec405f1080d..5edde86e9a4 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_power.c +++ b/firmware/targets/f7/furi_hal/furi_hal_power.c @@ -284,10 +284,15 @@ void furi_hal_power_reset() { NVIC_SystemReset(); } -void furi_hal_power_enable_otg() { +bool furi_hal_power_enable_otg() { furi_hal_i2c_acquire(&furi_hal_i2c_handle_power); + bq25896_set_boost_lim(&furi_hal_i2c_handle_power, BoostLim_2150); bq25896_enable_otg(&furi_hal_i2c_handle_power); + furi_delay_ms(30); + bool ret = bq25896_is_otg_enabled(&furi_hal_i2c_handle_power); + bq25896_set_boost_lim(&furi_hal_i2c_handle_power, BoostLim_1400); furi_hal_i2c_release(&furi_hal_i2c_handle_power); + return ret; } void furi_hal_power_disable_otg() { @@ -317,6 +322,13 @@ void furi_hal_power_set_battery_charge_voltage_limit(float voltage) { furi_hal_i2c_release(&furi_hal_i2c_handle_power); } +bool furi_hal_power_check_otg_fault() { + furi_hal_i2c_acquire(&furi_hal_i2c_handle_power); + bool ret = bq25896_check_otg_fault(&furi_hal_i2c_handle_power); + furi_hal_i2c_release(&furi_hal_i2c_handle_power); + return ret; +} + void furi_hal_power_check_otg_status() { furi_hal_i2c_acquire(&furi_hal_i2c_handle_power); if(bq25896_check_otg_fault(&furi_hal_i2c_handle_power)) diff --git a/firmware/targets/furi_hal_include/furi_hal_power.h b/firmware/targets/furi_hal_include/furi_hal_power.h index 00182fa2861..6b4b6cd89b5 100644 --- a/firmware/targets/furi_hal_include/furi_hal_power.h +++ b/firmware/targets/furi_hal_include/furi_hal_power.h @@ -99,12 +99,16 @@ void furi_hal_power_reset(); /** OTG enable */ -void furi_hal_power_enable_otg(); +bool furi_hal_power_enable_otg(); /** OTG disable */ void furi_hal_power_disable_otg(); +/** Check OTG status fault + */ +bool furi_hal_power_check_otg_fault(); + /** Check OTG status and disable it if falt happened */ void furi_hal_power_check_otg_status(); diff --git a/lib/drivers/bq25896.c b/lib/drivers/bq25896.c index 4c1d687cb02..f675233bdbb 100644 --- a/lib/drivers/bq25896.c +++ b/lib/drivers/bq25896.c @@ -61,7 +61,7 @@ void bq25896_init(FuriHalI2cBusHandle* handle) { // OTG power configuration bq25896_regs.r0A.BOOSTV = 0x8; // BOOST Voltage: 5.062V - bq25896_regs.r0A.BOOST_LIM = BOOST_LIM_1400; // BOOST Current limit: 1.4A + bq25896_regs.r0A.BOOST_LIM = BoostLim_1400; // BOOST Current limit: 1.4A furi_hal_i2c_write_reg_8( handle, BQ25896_ADDRESS, 0x0A, *(uint8_t*)&bq25896_regs.r0A, BQ25896_I2C_TIMEOUT); @@ -74,6 +74,12 @@ void bq25896_init(FuriHalI2cBusHandle* handle) { BQ25896_I2C_TIMEOUT); } +void bq25896_set_boost_lim(FuriHalI2cBusHandle* handle, BoostLim boost_lim) { + bq25896_regs.r0A.BOOST_LIM = boost_lim; + furi_hal_i2c_write_reg_8( + handle, BQ25896_ADDRESS, 0x0A, *(uint8_t*)&bq25896_regs.r0A, BQ25896_I2C_TIMEOUT); +} + void bq25896_poweroff(FuriHalI2cBusHandle* handle) { bq25896_regs.r09.BATFET_DIS = 1; furi_hal_i2c_write_reg_8( diff --git a/lib/drivers/bq25896.h b/lib/drivers/bq25896.h index f3d1d0e0583..ce7293960a1 100644 --- a/lib/drivers/bq25896.h +++ b/lib/drivers/bq25896.h @@ -9,6 +9,9 @@ /** Initialize Driver */ void bq25896_init(FuriHalI2cBusHandle* handle); +/** Set boost lim*/ +void bq25896_set_boost_lim(FuriHalI2cBusHandle* handle, BoostLim boost_lim); + /** Send device into shipping mode */ void bq25896_poweroff(FuriHalI2cBusHandle* handle); diff --git a/lib/drivers/bq25896_reg.h b/lib/drivers/bq25896_reg.h index 3cb3d71402d..a6ca3e1c77e 100644 --- a/lib/drivers/bq25896_reg.h +++ b/lib/drivers/bq25896_reg.h @@ -159,14 +159,16 @@ typedef struct { #define BOOSTV_128 (1 << 1) #define BOOSTV_64 (1 << 0) -#define BOOST_LIM_500 (0b000) -#define BOOST_LIM_750 (0b001) -#define BOOST_LIM_1200 (0b010) -#define BOOST_LIM_1400 (0b011) -#define BOOST_LIM_1650 (0b100) -#define BOOST_LIM_1875 (0b101) -#define BOOST_LIM_2150 (0b110) -#define BOOST_LIM_RSVD (0b111) +typedef enum { + BoostLim_500 = 0b000, + BoostLim_750 = 0b001, + BoostLim_1200 = 0b010, + BoostLim_1400 = 0b011, + BoostLim_1650 = 0b100, + BoostLim_1875 = 0b101, + BoostLim_2150 = 0b110, + BoostLim_Rsvd = 0b111, +} BoostLim; typedef struct { uint8_t BOOST_LIM : 3; // Boost Mode Current Limit From e6b0494d4d225658cef09d3e6e86a9778ad876d5 Mon Sep 17 00:00:00 2001 From: gornekich Date: Wed, 5 Jul 2023 20:55:04 +0400 Subject: [PATCH 644/824] [FL-3407] NFC: Mf Ultralight emulation optimization (#2847) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- lib/nfc/nfc_worker.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/nfc/nfc_worker.c b/lib/nfc/nfc_worker.c index a39531c8c29..d2834fa46d0 100644 --- a/lib/nfc/nfc_worker.c +++ b/lib/nfc/nfc_worker.c @@ -724,6 +724,8 @@ void nfc_worker_emulate_mf_ultralight(NfcWorker* nfc_worker) { emulator.auth_received_callback = nfc_worker_mf_ultralight_auth_received_callback; emulator.context = nfc_worker; + rfal_platform_spi_acquire(); + while(nfc_worker->state == NfcWorkerStateMfUltralightEmulate) { mf_ul_reset_emulation(&emulator, true); furi_hal_nfc_emulate_nfca( @@ -743,6 +745,8 @@ void nfc_worker_emulate_mf_ultralight(NfcWorker* nfc_worker) { emulator.data_changed = false; } } + + rfal_platform_spi_release(); } static bool nfc_worker_mf_get_b_key_from_sector_trailer( From d8500510bed982f20a3ebfafdf48ceb62c5b87c0 Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Thu, 6 Jul 2023 14:49:53 +0300 Subject: [PATCH 645/824] API: explicitly add math.h (#2852) * API: explicitly add math.h * sync target api versions --- firmware/targets/f18/api_symbols.csv | 218 +++++++++++++++++- firmware/targets/f7/api_symbols.csv | 3 +- .../f7/platform_specific/intrinsic_export.h | 1 + .../f7/platform_specific/math_wrapper.h | 2 + 4 files changed, 222 insertions(+), 2 deletions(-) create mode 100644 firmware/targets/f7/platform_specific/math_wrapper.h diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index 46099799bd2..62b237a51d9 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,33.0,, +Version,+,33.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -56,6 +56,7 @@ Header,+,firmware/targets/f7/furi_hal/furi_hal_spi_types.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_uart.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_usb_cdc.h,, Header,+,firmware/targets/f7/platform_specific/intrinsic_export.h,, +Header,+,firmware/targets/f7/platform_specific/math_wrapper.h,, Header,+,firmware/targets/furi_hal_include/furi_hal.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_bt.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_bt_hid.h,, @@ -289,12 +290,18 @@ Function,+,__assert_func,void,"const char*, int, const char*, const char*" Function,+,__clear_cache,void,"void*, void*" Function,-,__eprintf,void,"const char*, const char*, unsigned int, const char*" Function,+,__errno,int*, +Function,-,__fpclassifyd,int,double +Function,-,__fpclassifyf,int,float Function,+,__furi_crash,void, Function,+,__furi_critical_enter,__FuriCriticalInfo, Function,+,__furi_critical_exit,void,__FuriCriticalInfo Function,+,__furi_halt,void, Function,-,__getdelim,ssize_t,"char**, size_t*, int, FILE*" Function,-,__getline,ssize_t,"char**, size_t*, FILE*" +Function,-,__isinfd,int,double +Function,-,__isinff,int,float +Function,-,__isnand,int,double +Function,-,__isnanf,int,float Function,-,__itoa,char*,"int, char*, int" Function,-,__locale_mb_cur_max,int, Function,+,__retarget_lock_acquire,void,_LOCK_T @@ -307,6 +314,9 @@ Function,+,__retarget_lock_release,void,_LOCK_T Function,+,__retarget_lock_release_recursive,void,_LOCK_T Function,-,__retarget_lock_try_acquire,int,_LOCK_T Function,-,__retarget_lock_try_acquire_recursive,int,_LOCK_T +Function,-,__signbitd,int,double +Function,-,__signbitf,int,float +Function,-,__signgam,int*, Function,-,__srget_r,int,"_reent*, FILE*" Function,-,__swbuf_r,int,"_reent*, int, FILE*" Function,-,__utoa,char*,"unsigned, char*, int" @@ -459,6 +469,12 @@ Function,-,_wctomb_r,int,"_reent*, char*, wchar_t, _mbstate_t*" Function,-,a64l,long,const char* Function,+,abort,void, Function,-,abs,int,int +Function,-,acos,double,double +Function,-,acosf,float,float +Function,-,acosh,double,double +Function,-,acoshf,float,float +Function,-,acoshl,long double,long double +Function,-,acosl,long double,long double Function,-,aligned_alloc,void*,"size_t, size_t" Function,+,aligned_free,void,void* Function,+,aligned_malloc,void*,"size_t, size_t" @@ -474,11 +490,26 @@ Function,+,args_read_probably_quoted_string_and_trim,_Bool,"FuriString*, FuriStr Function,+,args_read_string_and_trim,_Bool,"FuriString*, FuriString*" Function,-,asctime,char*,const tm* Function,-,asctime_r,char*,"const tm*, char*" +Function,-,asin,double,double +Function,-,asinf,float,float +Function,-,asinh,double,double +Function,-,asinhf,float,float +Function,-,asinhl,long double,long double +Function,-,asinl,long double,long double Function,-,asiprintf,int,"char**, const char*, ..." Function,-,asniprintf,char*,"char*, size_t*, const char*, ..." Function,-,asnprintf,char*,"char*, size_t*, const char*, ..." Function,-,asprintf,int,"char**, const char*, ..." Function,-,at_quick_exit,int,void (*)() +Function,-,atan,double,double +Function,-,atan2,double,"double, double" +Function,-,atan2f,float,"float, float" +Function,-,atan2l,long double,"long double, long double" +Function,-,atanf,float,float +Function,-,atanh,double,double +Function,-,atanhf,float,float +Function,-,atanhl,long double,long double +Function,-,atanl,long double,long double Function,-,atexit,int,void (*)() Function,-,atof,double,const char* Function,-,atoff,float,const char* @@ -571,6 +602,12 @@ Function,+,canvas_set_font,void,"Canvas*, Font" Function,+,canvas_set_font_direction,void,"Canvas*, CanvasDirection" Function,+,canvas_string_width,uint16_t,"Canvas*, const char*" Function,+,canvas_width,uint8_t,const Canvas* +Function,-,cbrt,double,double +Function,-,cbrtf,float,float +Function,-,cbrtl,long double,long double +Function,-,ceil,double,double +Function,-,ceilf,float,float +Function,-,ceill,long double,long double Function,-,cfree,void,void* Function,-,clearerr,void,FILE* Function,-,clearerr_unlocked,void,FILE* @@ -591,6 +628,15 @@ Function,+,composite_api_resolver_add,void,"CompositeApiResolver*, const ElfApiI Function,+,composite_api_resolver_alloc,CompositeApiResolver*, Function,+,composite_api_resolver_free,void,CompositeApiResolver* Function,+,composite_api_resolver_get,const ElfApiInterface*,CompositeApiResolver* +Function,-,copysign,double,"double, double" +Function,-,copysignf,float,"float, float" +Function,-,copysignl,long double,"long double, long double" +Function,-,cos,double,double +Function,-,cosf,float,float +Function,-,cosh,double,double +Function,-,coshf,float,float +Function,-,coshl,long double,long double +Function,-,cosl,long double,long double Function,+,crc32_calc_buffer,uint32_t,"uint32_t, const void*, size_t" Function,+,crc32_calc_file,uint32_t,"File*, const FileCrcProgressCb, void*" Function,-,ctermid,char*,char* @@ -660,6 +706,8 @@ Function,+,dolphin_stats,DolphinStats,Dolphin* Function,+,dolphin_upgrade_level,void,Dolphin* Function,-,dprintf,int,"int, const char*, ..." Function,-,drand48,double, +Function,-,drem,double,"double, double" +Function,-,dremf,float,"float, float" Function,-,eTaskConfirmSleepModeStatus,eSleepModeStatus, Function,-,eTaskGetState,eTaskState,TaskHandle_t Function,+,elements_bold_rounded_frame,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t" @@ -687,10 +735,33 @@ Function,+,empty_screen_alloc,EmptyScreen*, Function,+,empty_screen_free,void,EmptyScreen* Function,+,empty_screen_get_view,View*,EmptyScreen* Function,-,erand48,double,unsigned short[3] +Function,-,erf,double,double +Function,-,erfc,double,double +Function,-,erfcf,float,float +Function,-,erfcl,long double,long double +Function,-,erff,float,float +Function,-,erfl,long double,long double Function,-,exit,void,int +Function,-,exp,double,double +Function,-,exp10,double,double +Function,-,exp10f,float,float +Function,-,exp2,double,double +Function,-,exp2f,float,float +Function,-,exp2l,long double,long double +Function,-,expf,float,float +Function,-,expl,long double,long double Function,-,explicit_bzero,void,"void*, size_t" +Function,-,expm1,double,double +Function,-,expm1f,float,float +Function,-,expm1l,long double,long double +Function,-,fabs,double,double +Function,-,fabsf,float,float +Function,-,fabsl,long double,long double Function,-,fclose,int,FILE* Function,-,fcloseall,int, +Function,-,fdim,double,"double, double" +Function,-,fdimf,float,"float, float" +Function,-,fdiml,long double,"long double, long double" Function,-,fdopen,FILE*,"int, const char*" Function,-,feof,int,FILE* Function,-,feof_unlocked,int,FILE* @@ -735,6 +806,9 @@ Function,+,file_stream_open,_Bool,"Stream*, const char*, FS_AccessMode, FS_OpenM Function,-,fileno,int,FILE* Function,-,fileno_unlocked,int,FILE* Function,+,filesystem_api_error_get_desc,const char*,FS_Error +Function,-,finite,int,double +Function,-,finitef,int,float +Function,-,finitel,int,long double Function,-,fiprintf,int,"FILE*, const char*, ..." Function,-,fiscanf,int,"FILE*, const char*, ..." Function,+,flipper_application_alloc,FlipperApplication*,"Storage*, const ElfApiInterface*" @@ -812,10 +886,25 @@ Function,+,flipper_format_write_string_cstr,_Bool,"FlipperFormat*, const char*, Function,+,flipper_format_write_uint32,_Bool,"FlipperFormat*, const char*, const uint32_t*, const uint16_t" Function,+,float_is_equal,_Bool,"float, float" Function,-,flockfile,void,FILE* +Function,-,floor,double,double +Function,-,floorf,float,float +Function,-,floorl,long double,long double Function,-,fls,int,int Function,-,flsl,int,long Function,-,flsll,int,long long +Function,-,fma,double,"double, double, double" +Function,-,fmaf,float,"float, float, float" +Function,-,fmal,long double,"long double, long double, long double" +Function,-,fmax,double,"double, double" +Function,-,fmaxf,float,"float, float" +Function,-,fmaxl,long double,"long double, long double" Function,-,fmemopen,FILE*,"void*, size_t, const char*" +Function,-,fmin,double,"double, double" +Function,-,fminf,float,"float, float" +Function,-,fminl,long double,"long double, long double" +Function,-,fmod,double,"double, double" +Function,-,fmodf,float,"float, float" +Function,-,fmodl,long double,"long double, long double" Function,-,fopen,FILE*,"const char*, const char*" Function,-,fopencookie,FILE*,"void*, const char*, cookie_io_functions_t" Function,-,fprintf,int,"FILE*, const char*, ..." @@ -828,6 +917,9 @@ Function,-,fread,size_t,"void*, size_t, size_t, FILE*" Function,-,fread_unlocked,size_t,"void*, size_t, size_t, FILE*" Function,+,free,void,void* Function,-,freopen,FILE*,"const char*, const char*, FILE*" +Function,-,frexp,double,"double, int*" +Function,-,frexpf,float,"float, int*" +Function,-,frexpl,long double,"long double, int*" Function,-,fscanf,int,"FILE*, const char*, ..." Function,-,fseek,int,"FILE*, long, int" Function,-,fseeko,int,"FILE*, off_t, int" @@ -1338,6 +1430,10 @@ Function,+,furi_timer_start,FuriStatus,"FuriTimer*, uint32_t" Function,+,furi_timer_stop,FuriStatus,FuriTimer* Function,-,fwrite,size_t,"const void*, size_t, size_t, FILE*" Function,-,fwrite_unlocked,size_t,"const void*, size_t, size_t, FILE*" +Function,-,gamma,double,double +Function,-,gamma_r,double,"double, int*" +Function,-,gammaf,float,float +Function,-,gammaf_r,float,"float, int*" Function,-,gap_get_state,GapState, Function,-,gap_init,_Bool,"GapConfig*, GapEventCallback, void*" Function,-,gap_start_advertising,void, @@ -1370,6 +1466,9 @@ Function,+,hex_char_to_hex_nibble,_Bool,"char, uint8_t*" Function,+,hex_char_to_uint8,_Bool,"char, char, uint8_t*" Function,+,hex_chars_to_uint64,_Bool,"const char*, uint64_t*" Function,+,hex_chars_to_uint8,_Bool,"const char*, uint8_t*" +Function,-,hypot,double,"double, double" +Function,-,hypotf,float,"float, float" +Function,-,hypotl,long double,"long double, long double" Function,+,icon_animation_alloc,IconAnimation*,const Icon* Function,+,icon_animation_free,void,IconAnimation* Function,+,icon_animation_get_height,uint8_t,const IconAnimation* @@ -1381,7 +1480,12 @@ Function,+,icon_animation_stop,void,IconAnimation* Function,+,icon_get_data,const uint8_t*,const Icon* Function,+,icon_get_height,uint8_t,const Icon* Function,+,icon_get_width,uint8_t,const Icon* +Function,-,ilogb,int,double +Function,-,ilogbf,int,float +Function,-,ilogbl,int,long double Function,-,index,char*,"const char*, int" +Function,-,infinity,double, +Function,-,infinityf,float, Function,-,initstate,char*,"unsigned, char*, size_t" Function,+,input_get_key_name,const char*,InputKey Function,+,input_get_type_name,const char*,InputType @@ -1401,8 +1505,12 @@ Function,-,isdigit,int,int Function,-,isdigit_l,int,"int, locale_t" Function,-,isgraph,int,int Function,-,isgraph_l,int,"int, locale_t" +Function,-,isinf,int,double +Function,-,isinff,int,float Function,-,islower,int,int Function,-,islower_l,int,"int, locale_t" +Function,-,isnan,int,double +Function,-,isnanf,int,float Function,-,isprint,int,int Function,-,isprint_l,int,"int, locale_t" Function,-,ispunct,int,int @@ -1414,13 +1522,33 @@ Function,-,isupper_l,int,"int, locale_t" Function,-,isxdigit,int,int Function,-,isxdigit_l,int,"int, locale_t" Function,-,itoa,char*,"int, char*, int" +Function,-,j0,double,double +Function,-,j0f,float,float +Function,-,j1,double,double +Function,-,j1f,float,float +Function,-,jn,double,"int, double" +Function,-,jnf,float,"int, float" Function,-,jrand48,long,unsigned short[3] Function,-,l64a,char*,long Function,-,labs,long,long Function,-,lcong48,void,unsigned short[7] +Function,-,ldexp,double,"double, int" +Function,-,ldexpf,float,"float, int" +Function,-,ldexpl,long double,"long double, int" Function,-,ldiv,ldiv_t,"long, long" +Function,-,lgamma,double,double +Function,-,lgamma_r,double,"double, int*" +Function,-,lgammaf,float,float +Function,-,lgammaf_r,float,"float, int*" +Function,-,lgammal,long double,long double Function,-,llabs,long long,long long Function,-,lldiv,lldiv_t,"long long, long long" +Function,-,llrint,long long int,double +Function,-,llrintf,long long int,float +Function,-,llrintl,long long int,long double +Function,-,llround,long long int,double +Function,-,llroundf,long long int,float +Function,-,llroundl,long long int,long double Function,+,loader_get_pubsub,FuriPubSub*,Loader* Function,+,loader_is_locked,_Bool,Loader* Function,+,loader_lock,_Bool,Loader* @@ -1443,7 +1571,28 @@ Function,+,locale_set_measurement_unit,void,LocaleMeasurementUnits Function,+,locale_set_time_format,void,LocaleTimeFormat Function,-,localtime,tm*,const time_t* Function,-,localtime_r,tm*,"const time_t*, tm*" +Function,-,log,double,double +Function,-,log10,double,double +Function,-,log10f,float,float +Function,-,log10l,long double,long double +Function,-,log1p,double,double +Function,-,log1pf,float,float +Function,-,log1pl,long double,long double +Function,-,log2,double,double +Function,-,log2f,float,float +Function,-,log2l,long double,long double +Function,-,logb,double,double +Function,-,logbf,float,float +Function,-,logbl,long double,long double +Function,-,logf,float,float +Function,-,logl,long double,long double Function,-,lrand48,long, +Function,-,lrint,long int,double +Function,-,lrintf,long int,float +Function,-,lrintl,long int,long double +Function,-,lround,long int,double +Function,-,lroundf,long int,float +Function,-,lroundl,long,long double Function,+,malloc,void*,size_t Function,+,manchester_advance,_Bool,"ManchesterState, ManchesterEvent, ManchesterState*, _Bool*" Function,+,manchester_encoder_advance,_Bool,"ManchesterEncoderState*, const _Bool, ManchesterEncoderResult*" @@ -1521,6 +1670,9 @@ Function,-,mkstemp,int,char* Function,-,mkstemps,int,"char*, int" Function,-,mktemp,char*,char* Function,-,mktime,time_t,tm* +Function,-,modf,double,"double, double*" +Function,-,modff,float,"float, float*" +Function,-,modfl,long double,"long double, long double*" Function,-,mrand48,long, Function,-,music_worker_alloc,MusicWorker*, Function,-,music_worker_clear,void,MusicWorker* @@ -1534,6 +1686,18 @@ Function,-,music_worker_set_callback,void,"MusicWorker*, MusicWorkerCallback, vo Function,-,music_worker_set_volume,void,"MusicWorker*, float" Function,-,music_worker_start,void,MusicWorker* Function,-,music_worker_stop,void,MusicWorker* +Function,-,nan,double,const char* +Function,-,nanf,float,const char* +Function,-,nanl,long double,const char* +Function,-,nearbyint,double,double +Function,-,nearbyintf,float,float +Function,-,nearbyintl,long double,long double +Function,-,nextafter,double,"double, double" +Function,-,nextafterf,float,"float, float" +Function,-,nextafterl,long double,"long double, long double" +Function,-,nexttoward,double,"double, long double" +Function,-,nexttowardf,float,"float, long double" +Function,-,nexttowardl,long double,"long double, long double" Function,+,notification_internal_message,void,"NotificationApp*, const NotificationSequence*" Function,+,notification_internal_message_block,void,"NotificationApp*, const NotificationSequence*" Function,+,notification_message,void,"NotificationApp*, const NotificationSequence*" @@ -1601,12 +1765,17 @@ Function,+,popup_set_icon,void,"Popup*, uint8_t, uint8_t, const Icon*" Function,+,popup_set_text,void,"Popup*, const char*, uint8_t, uint8_t, Align, Align" Function,+,popup_set_timeout,void,"Popup*, uint32_t" Function,-,posix_memalign,int,"void**, size_t, size_t" +Function,-,pow,double,"double, double" +Function,-,pow10,double,double +Function,-,pow10f,float,float Function,+,power_enable_low_battery_level_notification,void,"Power*, _Bool" Function,+,power_get_info,void,"Power*, PowerInfo*" Function,+,power_get_pubsub,FuriPubSub*,Power* Function,+,power_is_battery_healthy,_Bool,Power* Function,+,power_off,void,Power* Function,+,power_reboot,void,PowerBootMode +Function,+,powf,float,"float, float" +Function,-,powl,long double,"long double, long double" Function,+,pretty_format_bytes_hex_canonical,void,"FuriString*, size_t, const char*, const uint8_t*, size_t" Function,-,printf,int,"const char*, ..." Function,+,property_value_out,void,"PropertyValueContext*, const char*, unsigned int, ..." @@ -1664,11 +1833,23 @@ Function,+,realloc,void*,"void*, size_t" Function,-,reallocarray,void*,"void*, size_t, size_t" Function,-,reallocf,void*,"void*, size_t" Function,-,realpath,char*,"const char*, char*" +Function,-,remainder,double,"double, double" +Function,-,remainderf,float,"float, float" +Function,-,remainderl,long double,"long double, long double" Function,-,remove,int,const char* +Function,-,remquo,double,"double, double, int*" +Function,-,remquof,float,"float, float, int*" +Function,-,remquol,long double,"long double, long double, int*" Function,-,rename,int,"const char*, const char*" Function,-,renameat,int,"int, const char*, int, const char*" Function,-,rewind,void,FILE* Function,-,rindex,char*,"const char*, int" +Function,-,rint,double,double +Function,-,rintf,float,float +Function,-,rintl,long double,long double +Function,-,round,double,double +Function,+,roundf,float,float +Function,-,roundl,long double,long double Function,+,rpc_session_close,void,RpcSession* Function,+,rpc_session_feed,size_t,"RpcSession*, uint8_t*, size_t, TickType_t" Function,+,rpc_session_get_available_size,size_t,RpcSession* @@ -1693,6 +1874,12 @@ Function,-,rpmatch,int,const char* Function,+,saved_struct_get_payload_size,_Bool,"const char*, uint8_t, uint8_t, size_t*" Function,+,saved_struct_load,_Bool,"const char*, void*, size_t, uint8_t, uint8_t" Function,+,saved_struct_save,_Bool,"const char*, void*, size_t, uint8_t, uint8_t" +Function,-,scalbln,double,"double, long int" +Function,-,scalblnf,float,"float, long int" +Function,-,scalblnl,long double,"long double, long" +Function,-,scalbn,double,"double, int" +Function,+,scalbnf,float,"float, int" +Function,-,scalbnl,long double,"long double, int" Function,-,scanf,int,"const char*, ..." Function,+,scene_manager_alloc,SceneManager*,"const SceneManagerHandlers*, void*" Function,+,scene_manager_free,void,SceneManager* @@ -1732,11 +1919,22 @@ Function,+,sha256_finish,void,"sha256_context*, unsigned char[32]" Function,+,sha256_process,void,sha256_context* Function,+,sha256_start,void,sha256_context* Function,+,sha256_update,void,"sha256_context*, const unsigned char*, unsigned int" +Function,-,sin,double,double +Function,-,sincos,void,"double, double*, double*" +Function,-,sincosf,void,"float, float*, float*" +Function,-,sinf,float,float +Function,-,sinh,double,double +Function,-,sinhf,float,float +Function,-,sinhl,long double,long double +Function,-,sinl,long double,long double Function,-,siprintf,int,"char*, const char*, ..." Function,-,siscanf,int,"const char*, const char*, ..." Function,-,sniprintf,int,"char*, size_t, const char*, ..." Function,+,snprintf,int,"char*, size_t, const char*, ..." Function,-,sprintf,int,"char*, const char*, ..." +Function,-,sqrt,double,double +Function,-,sqrtf,float,float +Function,-,sqrtl,long double,long double Function,+,srand,void,unsigned Function,-,srand48,void,long Function,-,srandom,void,unsigned @@ -1891,6 +2089,12 @@ Function,+,submenu_reset,void,Submenu* Function,+,submenu_set_header,void,"Submenu*, const char*" Function,+,submenu_set_selected_item,void,"Submenu*, uint32_t" Function,-,system,int,const char* +Function,-,tan,double,double +Function,-,tanf,float,float +Function,-,tanh,double,double +Function,-,tanhf,float,float +Function,-,tanhl,long double,long double +Function,-,tanl,long double,long double Function,+,tar_archive_add_dir,_Bool,"TarArchive*, const char*, const char*" Function,+,tar_archive_add_file,_Bool,"TarArchive*, const char*, const char*, const int32_t" Function,+,tar_archive_alloc,TarArchive*,Storage* @@ -1923,6 +2127,9 @@ Function,+,text_input_reset,void,TextInput* Function,+,text_input_set_header_text,void,"TextInput*, const char*" Function,+,text_input_set_result_callback,void,"TextInput*, TextInputCallback, void*, char*, size_t, _Bool" Function,+,text_input_set_validator,void,"TextInput*, TextInputValidatorCallback, void*" +Function,-,tgamma,double,double +Function,-,tgammaf,float,float +Function,-,tgammal,long double,long double Function,-,time,time_t,time_t* Function,-,timingsafe_bcmp,int,"const void*, const void*, size_t" Function,-,timingsafe_memcmp,int,"const void*, const void*, size_t" @@ -1934,6 +2141,9 @@ Function,-,tolower,int,int Function,-,tolower_l,int,"int, locale_t" Function,-,toupper,int,int Function,-,toupper_l,int,"int, locale_t" +Function,-,trunc,double,double +Function,-,truncf,float,float +Function,-,truncl,long double,long double Function,-,tzset,void, Function,-,uECC_compress,void,"const uint8_t*, uint8_t*, uECC_Curve" Function,+,uECC_compute_public_key,int,"const uint8_t*, uint8_t*, uECC_Curve" @@ -2166,6 +2376,12 @@ Function,-,xTimerGetTimerDaemonTaskHandle,TaskHandle_t, Function,-,xTimerIsTimerActive,BaseType_t,TimerHandle_t Function,-,xTimerPendFunctionCall,BaseType_t,"PendedFunction_t, void*, uint32_t, TickType_t" Function,-,xTimerPendFunctionCallFromISR,BaseType_t,"PendedFunction_t, void*, uint32_t, BaseType_t*" +Function,-,y0,double,double +Function,-,y0f,float,float +Function,-,y1,double,double +Function,-,y1f,float,float +Function,-,yn,double,"int, double" +Function,-,ynf,float,"int, float" Variable,-,AHBPrescTable,const uint32_t[16], Variable,-,APBPrescTable,const uint32_t[8], Variable,-,ITM_RxBuffer,volatile int32_t, diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index dbaeb8e4cd4..a44e663ba65 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,33.0,, +Version,+,33.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -60,6 +60,7 @@ Header,+,firmware/targets/f7/furi_hal/furi_hal_target_hw.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_uart.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_usb_cdc.h,, Header,+,firmware/targets/f7/platform_specific/intrinsic_export.h,, +Header,+,firmware/targets/f7/platform_specific/math_wrapper.h,, Header,+,firmware/targets/furi_hal_include/furi_hal.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_bt.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_bt_hid.h,, diff --git a/firmware/targets/f7/platform_specific/intrinsic_export.h b/firmware/targets/f7/platform_specific/intrinsic_export.h index ca343a12862..d3c7be5e042 100644 --- a/firmware/targets/f7/platform_specific/intrinsic_export.h +++ b/firmware/targets/f7/platform_specific/intrinsic_export.h @@ -1,3 +1,4 @@ +#pragma once #include #include diff --git a/firmware/targets/f7/platform_specific/math_wrapper.h b/firmware/targets/f7/platform_specific/math_wrapper.h new file mode 100644 index 00000000000..83f5a8b75d3 --- /dev/null +++ b/firmware/targets/f7/platform_specific/math_wrapper.h @@ -0,0 +1,2 @@ +#pragma once +#include \ No newline at end of file From cef59887ed367d4d1d22274de42996e385eedaea Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Thu, 6 Jul 2023 19:15:03 +0400 Subject: [PATCH 646/824] [FL-3401, FL-3402] SubGhz: add "SubGhz test" external application and the ability to work "SubGhz" as an external application (#2851) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [FL-3401] SubGhz: add "SubGhz test" external application * SubGhz: delete test test functionality from SubGhz app * [FL-3402] SubGhz: move func protocol creation API Co-authored-by: あく --- .../debug/subghz_test/application.fam | 14 + .../subghz_test/helpers/subghz_test_event.h | 7 + .../helpers/subghz_test_frequency.c} | 2 +- .../helpers/subghz_test_frequency.h} | 2 +- .../subghz_test/helpers/subghz_test_types.h | 18 ++ .../images/DolphinCommon_56x48.png | Bin 0 -> 1416 bytes .../debug/subghz_test/protocol/math.c | 244 ++++++++++++++++++ .../debug/subghz_test/protocol/math.h | 222 ++++++++++++++++ .../protocol}/princeton_for_testing.c | 2 +- .../protocol}/princeton_for_testing.h | 4 +- .../subghz_test/scenes/subghz_test_scene.c | 30 +++ .../subghz_test/scenes/subghz_test_scene.h | 29 +++ .../scenes/subghz_test_scene_about.c | 66 +++++ .../scenes/subghz_test_scene_carrier.c | 29 +++ .../scenes/subghz_test_scene_config.h | 6 + .../scenes/subghz_test_scene_packet.c | 29 +++ .../scenes/subghz_test_scene_show_only_rx.c | 49 ++++ .../scenes/subghz_test_scene_start.c | 77 ++++++ .../scenes/subghz_test_scene_static.c | 29 +++ .../debug/subghz_test/subghz_test_10px.png | Bin 0 -> 181 bytes .../debug/subghz_test/subghz_test_app.c | 139 ++++++++++ .../debug/subghz_test/subghz_test_app_i.c | 5 + .../debug/subghz_test/subghz_test_app_i.h | 32 +++ .../subghz_test}/views/subghz_test_carrier.c | 4 +- .../subghz_test}/views/subghz_test_carrier.h | 0 .../subghz_test}/views/subghz_test_packet.c | 6 +- .../subghz_test}/views/subghz_test_packet.h | 0 .../subghz_test}/views/subghz_test_static.c | 6 +- .../subghz_test}/views/subghz_test_static.h | 0 .../main/subghz/helpers/subghz_types.h | 3 - .../main/subghz/scenes/subghz_scene_config.h | 4 - .../main/subghz/scenes/subghz_scene_start.c | 10 - .../main/subghz/scenes/subghz_scene_test.c | 61 ----- .../subghz/scenes/subghz_scene_test_carrier.c | 30 --- .../subghz/scenes/subghz_scene_test_packet.c | 30 --- .../subghz/scenes/subghz_scene_test_static.c | 30 --- applications/main/subghz/subghz.c | 33 --- applications/main/subghz/subghz_i.h | 7 - firmware/targets/f7/api_symbols.csv | 8 +- lib/subghz/subghz_protocol_registry.h | 25 ++ 40 files changed, 1070 insertions(+), 222 deletions(-) create mode 100644 applications/debug/subghz_test/application.fam create mode 100644 applications/debug/subghz_test/helpers/subghz_test_event.h rename applications/{main/subghz/helpers/subghz_testing.c => debug/subghz_test/helpers/subghz_test_frequency.c} (95%) rename applications/{main/subghz/helpers/subghz_testing.h => debug/subghz_test/helpers/subghz_test_frequency.h} (84%) create mode 100644 applications/debug/subghz_test/helpers/subghz_test_types.h create mode 100644 applications/debug/subghz_test/images/DolphinCommon_56x48.png create mode 100644 applications/debug/subghz_test/protocol/math.c create mode 100644 applications/debug/subghz_test/protocol/math.h rename {lib/subghz/protocols => applications/debug/subghz_test/protocol}/princeton_for_testing.c (99%) rename {lib/subghz/protocols => applications/debug/subghz_test/protocol}/princeton_for_testing.h (97%) create mode 100644 applications/debug/subghz_test/scenes/subghz_test_scene.c create mode 100644 applications/debug/subghz_test/scenes/subghz_test_scene.h create mode 100644 applications/debug/subghz_test/scenes/subghz_test_scene_about.c create mode 100644 applications/debug/subghz_test/scenes/subghz_test_scene_carrier.c create mode 100644 applications/debug/subghz_test/scenes/subghz_test_scene_config.h create mode 100644 applications/debug/subghz_test/scenes/subghz_test_scene_packet.c create mode 100644 applications/debug/subghz_test/scenes/subghz_test_scene_show_only_rx.c create mode 100644 applications/debug/subghz_test/scenes/subghz_test_scene_start.c create mode 100644 applications/debug/subghz_test/scenes/subghz_test_scene_static.c create mode 100644 applications/debug/subghz_test/subghz_test_10px.png create mode 100644 applications/debug/subghz_test/subghz_test_app.c create mode 100644 applications/debug/subghz_test/subghz_test_app_i.c create mode 100644 applications/debug/subghz_test/subghz_test_app_i.h rename applications/{main/subghz => debug/subghz_test}/views/subghz_test_carrier.c (98%) rename applications/{main/subghz => debug/subghz_test}/views/subghz_test_carrier.h (100%) rename applications/{main/subghz => debug/subghz_test}/views/subghz_test_packet.c (98%) rename applications/{main/subghz => debug/subghz_test}/views/subghz_test_packet.h (100%) rename applications/{main/subghz => debug/subghz_test}/views/subghz_test_static.c (98%) rename applications/{main/subghz => debug/subghz_test}/views/subghz_test_static.h (100%) delete mode 100644 applications/main/subghz/scenes/subghz_scene_test.c delete mode 100644 applications/main/subghz/scenes/subghz_scene_test_carrier.c delete mode 100644 applications/main/subghz/scenes/subghz_scene_test_packet.c delete mode 100644 applications/main/subghz/scenes/subghz_scene_test_static.c diff --git a/applications/debug/subghz_test/application.fam b/applications/debug/subghz_test/application.fam new file mode 100644 index 00000000000..1b3e19d73f9 --- /dev/null +++ b/applications/debug/subghz_test/application.fam @@ -0,0 +1,14 @@ +App( + appid="subghz_test", + name="Sub-Ghz test", + apptype=FlipperAppType.DEBUG, + targets=["f7"], + entry_point="subghz_test_app", + requires=["gui"], + stack_size=4 * 1024, + order=50, + fap_icon="subghz_test_10px.png", + fap_category="Debug", + fap_icon_assets="images", + fap_version="0.1", +) diff --git a/applications/debug/subghz_test/helpers/subghz_test_event.h b/applications/debug/subghz_test/helpers/subghz_test_event.h new file mode 100644 index 00000000000..a0a851976ad --- /dev/null +++ b/applications/debug/subghz_test/helpers/subghz_test_event.h @@ -0,0 +1,7 @@ +#pragma once + +typedef enum { + //SubGhzTestCustomEvent + SubGhzTestCustomEventStartId = 100, + SubGhzTestCustomEventSceneShowOnlyRX, +} SubGhzTestCustomEvent; diff --git a/applications/main/subghz/helpers/subghz_testing.c b/applications/debug/subghz_test/helpers/subghz_test_frequency.c similarity index 95% rename from applications/main/subghz/helpers/subghz_testing.c rename to applications/debug/subghz_test/helpers/subghz_test_frequency.c index 8afa868e012..ed1ba704e4d 100644 --- a/applications/main/subghz/helpers/subghz_testing.c +++ b/applications/debug/subghz_test/helpers/subghz_test_frequency.c @@ -1,4 +1,4 @@ -#include "subghz_testing.h" +#include "subghz_test_frequency.h" const uint32_t subghz_frequencies_testing[] = { /* 300 - 348 */ diff --git a/applications/main/subghz/helpers/subghz_testing.h b/applications/debug/subghz_test/helpers/subghz_test_frequency.h similarity index 84% rename from applications/main/subghz/helpers/subghz_testing.h rename to applications/debug/subghz_test/helpers/subghz_test_frequency.h index 29ce578a010..7dd1423f968 100644 --- a/applications/main/subghz/helpers/subghz_testing.h +++ b/applications/debug/subghz_test/helpers/subghz_test_frequency.h @@ -1,5 +1,5 @@ #pragma once -#include "../subghz_i.h" +#include "../subghz_test_app_i.h" extern const uint32_t subghz_frequencies_testing[]; extern const uint32_t subghz_frequencies_count_testing; diff --git a/applications/debug/subghz_test/helpers/subghz_test_types.h b/applications/debug/subghz_test/helpers/subghz_test_types.h new file mode 100644 index 00000000000..03be6459ec1 --- /dev/null +++ b/applications/debug/subghz_test/helpers/subghz_test_types.h @@ -0,0 +1,18 @@ +#pragma once + +#include +#include + +#define SUBGHZ_TEST_VERSION_APP "0.1" +#define SUBGHZ_TEST_DEVELOPED "SkorP" +#define SUBGHZ_TEST_GITHUB "https://github.com/flipperdevices/flipperzero-firmware" + +typedef enum { + SubGhzTestViewVariableItemList, + SubGhzTestViewSubmenu, + SubGhzTestViewStatic, + SubGhzTestViewCarrier, + SubGhzTestViewPacket, + SubGhzTestViewWidget, + SubGhzTestViewPopup, +} SubGhzTestView; diff --git a/applications/debug/subghz_test/images/DolphinCommon_56x48.png b/applications/debug/subghz_test/images/DolphinCommon_56x48.png new file mode 100644 index 0000000000000000000000000000000000000000..089aaed83507431993a76ca25d32fdd9664c1c84 GIT binary patch literal 1416 zcmaJ>eNYr-7(dh;KXS5&nWVIBjS_NizYg|x=Pr^vz*7zxJO|P-dw2IeZq?gec9-rD zoPZchQ_6}yP{Slc4I!!28K==nodOJ_nsCY-(wOq2uZbLx!rlYU{KIi)_Wj!D_j`WN z^FGgREXdEDF)ewT&1Re7Tj(uBvlG44lnH3;I%IzsO|z`*Vr!`uv?9QOwgs{#Ld+Ki zC9n_zxxBOkx@@+IwMwAaD)#3Ik`}gun2kLe))Crfb7e+#AgzHGCc+X$b>qJuIf`S7 z?8b}I{ghw#z>uiaLknQh@LJUrqHcVYS3v97F^OZN zCe|7^J|?QzUx0Zu17e(=CM1fYFpjtLk|a4~$g}e?hGH0!VoBOT&<=s(1ct%J9~?O} z$)jW_dkX9yTX~%W*i_IM%0{ z7EmP^_pKn`<5>E(SixgJU};7`)7Hidp&+DLnizsebUk}_-GfgbN^il9b`v)f+ z{o5Zry)d<7`fHQ^uw_;+x>mcPw0&8iW69x{k92O{Q}`yFdH=5d$pbf49w1&NS)G+vhr6y}5TMsofQirRDUmKilk5=(KGouJ{H9hW=$X zgi;)vI!jl!_4H3jD(?Jz=8By|i47I&tKA1y9{nfp;_|FxKBDNWp{hN9hJ1nU?z%J6 z?>UxyzWvO}Pgc~rCZ#5%Eq+_hNS~bBdiGlT&f%%e`hHjSySR2=JuK2^+%;$R3#Wz~ z=e_mfqW23bPa0fhe)HdE5+GelU&!jS3ckUZOQ)CC5?mo zo=tzG_4|RuvPUO|mhCwA>y)1c%SWC%a4?a-x|J*?ch~+n=R7o@>p6J2dE=$stKZmK z-xoTRwET2^Wu)&1U7!Ebw!!D?x`xwQX3pMnrRwCT?`4GHt4&?|cIiI{_^XYp-np>6 xE^lPSXzOYCC4X`6tl@OB1M5_S7jml-Y~(TPp{aTIejNKZ`m*!Atyxdk{0EAy49frj literal 0 HcmV?d00001 diff --git a/applications/debug/subghz_test/protocol/math.c b/applications/debug/subghz_test/protocol/math.c new file mode 100644 index 00000000000..24202ad1c62 --- /dev/null +++ b/applications/debug/subghz_test/protocol/math.c @@ -0,0 +1,244 @@ +#include "math.h" + +uint64_t subghz_protocol_blocks_reverse_key(uint64_t key, uint8_t bit_count) { + uint64_t reverse_key = 0; + for(uint8_t i = 0; i < bit_count; i++) { + reverse_key = reverse_key << 1 | bit_read(key, i); + } + return reverse_key; +} + +uint8_t subghz_protocol_blocks_get_parity(uint64_t key, uint8_t bit_count) { + uint8_t parity = 0; + for(uint8_t i = 0; i < bit_count; i++) { + parity += bit_read(key, i); + } + return parity & 0x01; +} + +uint8_t subghz_protocol_blocks_crc4( + uint8_t const message[], + size_t size, + uint8_t polynomial, + uint8_t init) { + uint8_t remainder = init << 4; // LSBs are unused + uint8_t poly = polynomial << 4; + uint8_t bit; + + while(size--) { + remainder ^= *message++; + for(bit = 0; bit < 8; bit++) { + if(remainder & 0x80) { + remainder = (remainder << 1) ^ poly; + } else { + remainder = (remainder << 1); + } + } + } + return remainder >> 4 & 0x0f; // discard the LSBs +} + +uint8_t subghz_protocol_blocks_crc7( + uint8_t const message[], + size_t size, + uint8_t polynomial, + uint8_t init) { + uint8_t remainder = init << 1; // LSB is unused + uint8_t poly = polynomial << 1; + + for(size_t byte = 0; byte < size; ++byte) { + remainder ^= message[byte]; + for(uint8_t bit = 0; bit < 8; ++bit) { + if(remainder & 0x80) { + remainder = (remainder << 1) ^ poly; + } else { + remainder = (remainder << 1); + } + } + } + return remainder >> 1 & 0x7f; // discard the LSB +} + +uint8_t subghz_protocol_blocks_crc8( + uint8_t const message[], + size_t size, + uint8_t polynomial, + uint8_t init) { + uint8_t remainder = init; + + for(size_t byte = 0; byte < size; ++byte) { + remainder ^= message[byte]; + for(uint8_t bit = 0; bit < 8; ++bit) { + if(remainder & 0x80) { + remainder = (remainder << 1) ^ polynomial; + } else { + remainder = (remainder << 1); + } + } + } + return remainder; +} + +uint8_t subghz_protocol_blocks_crc8le( + uint8_t const message[], + size_t size, + uint8_t polynomial, + uint8_t init) { + uint8_t remainder = subghz_protocol_blocks_reverse_key(init, 8); + polynomial = subghz_protocol_blocks_reverse_key(polynomial, 8); + + for(size_t byte = 0; byte < size; ++byte) { + remainder ^= message[byte]; + for(uint8_t bit = 0; bit < 8; ++bit) { + if(remainder & 1) { + remainder = (remainder >> 1) ^ polynomial; + } else { + remainder = (remainder >> 1); + } + } + } + return remainder; +} + +uint16_t subghz_protocol_blocks_crc16lsb( + uint8_t const message[], + size_t size, + uint16_t polynomial, + uint16_t init) { + uint16_t remainder = init; + + for(size_t byte = 0; byte < size; ++byte) { + remainder ^= message[byte]; + for(uint8_t bit = 0; bit < 8; ++bit) { + if(remainder & 1) { + remainder = (remainder >> 1) ^ polynomial; + } else { + remainder = (remainder >> 1); + } + } + } + return remainder; +} + +uint16_t subghz_protocol_blocks_crc16( + uint8_t const message[], + size_t size, + uint16_t polynomial, + uint16_t init) { + uint16_t remainder = init; + + for(size_t byte = 0; byte < size; ++byte) { + remainder ^= message[byte] << 8; + for(uint8_t bit = 0; bit < 8; ++bit) { + if(remainder & 0x8000) { + remainder = (remainder << 1) ^ polynomial; + } else { + remainder = (remainder << 1); + } + } + } + return remainder; +} + +uint8_t subghz_protocol_blocks_lfsr_digest8( + uint8_t const message[], + size_t size, + uint8_t gen, + uint8_t key) { + uint8_t sum = 0; + for(size_t byte = 0; byte < size; ++byte) { + uint8_t data = message[byte]; + for(int i = 7; i >= 0; --i) { + // XOR key into sum if data bit is set + if((data >> i) & 1) sum ^= key; + + // roll the key right (actually the LSB is dropped here) + // and apply the gen (needs to include the dropped LSB as MSB) + if(key & 1) + key = (key >> 1) ^ gen; + else + key = (key >> 1); + } + } + return sum; +} + +uint8_t subghz_protocol_blocks_lfsr_digest8_reflect( + uint8_t const message[], + size_t size, + uint8_t gen, + uint8_t key) { + uint8_t sum = 0; + // Process message from last byte to first byte (reflected) + for(int byte = size - 1; byte >= 0; --byte) { + uint8_t data = message[byte]; + // Process individual bits of each byte (reflected) + for(uint8_t i = 0; i < 8; ++i) { + // XOR key into sum if data bit is set + if((data >> i) & 1) { + sum ^= key; + } + + // roll the key left (actually the LSB is dropped here) + // and apply the gen (needs to include the dropped lsb as MSB) + if(key & 0x80) + key = (key << 1) ^ gen; + else + key = (key << 1); + } + } + return sum; +} + +uint16_t subghz_protocol_blocks_lfsr_digest16( + uint8_t const message[], + size_t size, + uint16_t gen, + uint16_t key) { + uint16_t sum = 0; + for(size_t byte = 0; byte < size; ++byte) { + uint8_t data = message[byte]; + for(int8_t i = 7; i >= 0; --i) { + // if data bit is set then xor with key + if((data >> i) & 1) sum ^= key; + + // roll the key right (actually the LSB is dropped here) + // and apply the gen (needs to include the dropped LSB as MSB) + if(key & 1) + key = (key >> 1) ^ gen; + else + key = (key >> 1); + } + } + return sum; +} + +uint8_t subghz_protocol_blocks_add_bytes(uint8_t const message[], size_t size) { + uint32_t result = 0; + for(size_t i = 0; i < size; ++i) { + result += message[i]; + } + return (uint8_t)result; +} + +uint8_t subghz_protocol_blocks_parity8(uint8_t byte) { + byte ^= byte >> 4; + byte &= 0xf; + return (0x6996 >> byte) & 1; +} + +uint8_t subghz_protocol_blocks_parity_bytes(uint8_t const message[], size_t size) { + uint8_t result = 0; + for(size_t i = 0; i < size; ++i) { + result ^= subghz_protocol_blocks_parity8(message[i]); + } + return result; +} + +uint8_t subghz_protocol_blocks_xor_bytes(uint8_t const message[], size_t size) { + uint8_t result = 0; + for(size_t i = 0; i < size; ++i) { + result ^= message[i]; + } + return result; +} \ No newline at end of file diff --git a/applications/debug/subghz_test/protocol/math.h b/applications/debug/subghz_test/protocol/math.h new file mode 100644 index 00000000000..dcea3da5faf --- /dev/null +++ b/applications/debug/subghz_test/protocol/math.h @@ -0,0 +1,222 @@ +#pragma once + +#include +#include +#include + +#define bit_read(value, bit) (((value) >> (bit)) & 0x01) +#define bit_set(value, bit) \ + ({ \ + __typeof__(value) _one = (1); \ + (value) |= (_one << (bit)); \ + }) +#define bit_clear(value, bit) \ + ({ \ + __typeof__(value) _one = (1); \ + (value) &= ~(_one << (bit)); \ + }) +#define bit_write(value, bit, bitvalue) (bitvalue ? bit_set(value, bit) : bit_clear(value, bit)) +#define DURATION_DIFF(x, y) (((x) < (y)) ? ((y) - (x)) : ((x) - (y))) + +#ifdef __cplusplus +extern "C" { +#endif + +/** Flip the data bitwise + * + * @param key In data + * @param bit_count number of data bits + * + * @return Reverse data + */ +uint64_t subghz_protocol_blocks_reverse_key(uint64_t key, uint8_t bit_count); + +/** Get parity the data bitwise + * + * @param key In data + * @param bit_count number of data bits + * + * @return parity + */ +uint8_t subghz_protocol_blocks_get_parity(uint64_t key, uint8_t bit_count); + +/** CRC-4 + * + * @param message array of bytes to check + * @param size number of bytes in message + * @param polynomial CRC polynomial + * @param init starting crc value + * + * @return CRC value + */ +uint8_t subghz_protocol_blocks_crc4( + uint8_t const message[], + size_t size, + uint8_t polynomial, + uint8_t init); + +/** CRC-7 + * + * @param message array of bytes to check + * @param size number of bytes in message + * @param polynomial CRC polynomial + * @param init starting crc value + * + * @return CRC value + */ +uint8_t subghz_protocol_blocks_crc7( + uint8_t const message[], + size_t size, + uint8_t polynomial, + uint8_t init); + +/** Generic Cyclic Redundancy Check CRC-8. Example polynomial: 0x31 = x8 + x5 + + * x4 + 1 (x8 is implicit) Example polynomial: 0x80 = x8 + x7 (a normal + * bit-by-bit parity XOR) + * + * @param message array of bytes to check + * @param size number of bytes in message + * @param polynomial byte is from x^7 to x^0 (x^8 is implicitly one) + * @param init starting crc value + * + * @return CRC value + */ +uint8_t subghz_protocol_blocks_crc8( + uint8_t const message[], + size_t size, + uint8_t polynomial, + uint8_t init); + +/** "Little-endian" Cyclic Redundancy Check CRC-8 LE Input and output are + * reflected, i.e. least significant bit is shifted in first + * + * @param message array of bytes to check + * @param size number of bytes in message + * @param polynomial CRC polynomial + * @param init starting crc value + * + * @return CRC value + */ +uint8_t subghz_protocol_blocks_crc8le( + uint8_t const message[], + size_t size, + uint8_t polynomial, + uint8_t init); + +/** CRC-16 LSB. Input and output are reflected, i.e. least significant bit is + * shifted in first. Note that poly and init already need to be reflected + * + * @param message array of bytes to check + * @param size number of bytes in message + * @param polynomial CRC polynomial + * @param init starting crc value + * + * @return CRC value + */ +uint16_t subghz_protocol_blocks_crc16lsb( + uint8_t const message[], + size_t size, + uint16_t polynomial, + uint16_t init); + +/** CRC-16 + * + * @param message array of bytes to check + * @param size number of bytes in message + * @param polynomial CRC polynomial + * @param init starting crc value + * + * @return CRC value + */ +uint16_t subghz_protocol_blocks_crc16( + uint8_t const message[], + size_t size, + uint16_t polynomial, + uint16_t init); + +/** Digest-8 by "LFSR-based Toeplitz hash" + * + * @param message bytes of message data + * @param size number of bytes to digest + * @param gen key stream generator, needs to includes the MSB if the + * LFSR is rolling + * @param key initial key + * + * @return digest value + */ +uint8_t subghz_protocol_blocks_lfsr_digest8( + uint8_t const message[], + size_t size, + uint8_t gen, + uint8_t key); + +/** Digest-8 by "LFSR-based Toeplitz hash", byte reflect, bit reflect + * + * @param message bytes of message data + * @param size number of bytes to digest + * @param gen key stream generator, needs to includes the MSB if the + * LFSR is rolling + * @param key initial key + * + * @return digest value + */ +uint8_t subghz_protocol_blocks_lfsr_digest8_reflect( + uint8_t const message[], + size_t size, + uint8_t gen, + uint8_t key); + +/** Digest-16 by "LFSR-based Toeplitz hash" + * + * @param message bytes of message data + * @param size number of bytes to digest + * @param gen key stream generator, needs to includes the MSB if the + * LFSR is rolling + * @param key initial key + * + * @return digest value + */ +uint16_t subghz_protocol_blocks_lfsr_digest16( + uint8_t const message[], + size_t size, + uint16_t gen, + uint16_t key); + +/** Compute Addition of a number of bytes + * + * @param message bytes of message data + * @param size number of bytes to sum + * + * @return summation value + */ +uint8_t subghz_protocol_blocks_add_bytes(uint8_t const message[], size_t size); + +/** Compute bit parity of a single byte (8 bits) + * + * @param byte single byte to check + * + * @return 1 odd parity, 0 even parity + */ +uint8_t subghz_protocol_blocks_parity8(uint8_t byte); + +/** Compute bit parity of a number of bytes + * + * @param message bytes of message data + * @param size number of bytes to sum + * + * @return 1 odd parity, 0 even parity + */ +uint8_t subghz_protocol_blocks_parity_bytes(uint8_t const message[], size_t size); + +/** Compute XOR (byte-wide parity) of a number of bytes + * + * @param message bytes of message data + * @param size number of bytes to sum + * + * @return summation value, per bit-position 1 odd parity, 0 even parity + */ +uint8_t subghz_protocol_blocks_xor_bytes(uint8_t const message[], size_t size); + +#ifdef __cplusplus +} +#endif diff --git a/lib/subghz/protocols/princeton_for_testing.c b/applications/debug/subghz_test/protocol/princeton_for_testing.c similarity index 99% rename from lib/subghz/protocols/princeton_for_testing.c rename to applications/debug/subghz_test/protocol/princeton_for_testing.c index 478d14cdfca..334a8241bbe 100644 --- a/lib/subghz/protocols/princeton_for_testing.c +++ b/applications/debug/subghz_test/protocol/princeton_for_testing.c @@ -1,7 +1,7 @@ #include "princeton_for_testing.h" #include -#include "../blocks/math.h" +#include "math.h" /* * Help diff --git a/lib/subghz/protocols/princeton_for_testing.h b/applications/debug/subghz_test/protocol/princeton_for_testing.h similarity index 97% rename from lib/subghz/protocols/princeton_for_testing.h rename to applications/debug/subghz_test/protocol/princeton_for_testing.h index 07a37ec5f7f..7b4201d38a9 100644 --- a/lib/subghz/protocols/princeton_for_testing.h +++ b/applications/debug/subghz_test/protocol/princeton_for_testing.h @@ -1,6 +1,8 @@ #pragma once -#include "base.h" +//#include "base.h" +#include +#include /** SubGhzDecoderPrinceton anonymous type */ typedef struct SubGhzDecoderPrinceton SubGhzDecoderPrinceton; diff --git a/applications/debug/subghz_test/scenes/subghz_test_scene.c b/applications/debug/subghz_test/scenes/subghz_test_scene.c new file mode 100644 index 00000000000..ff439ef0f8d --- /dev/null +++ b/applications/debug/subghz_test/scenes/subghz_test_scene.c @@ -0,0 +1,30 @@ +#include "../subghz_test_app_i.h" + +// Generate scene on_enter handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, +void (*const subghz_test_scene_on_enter_handlers[])(void*) = { +#include "subghz_test_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 subghz_test_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = { +#include "subghz_test_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 subghz_test_scene_on_exit_handlers[])(void* context) = { +#include "subghz_test_scene_config.h" +}; +#undef ADD_SCENE + +// Initialize scene handlers configuration structure +const SceneManagerHandlers subghz_test_scene_handlers = { + .on_enter_handlers = subghz_test_scene_on_enter_handlers, + .on_event_handlers = subghz_test_scene_on_event_handlers, + .on_exit_handlers = subghz_test_scene_on_exit_handlers, + .scene_num = SubGhzTestSceneNum, +}; diff --git a/applications/debug/subghz_test/scenes/subghz_test_scene.h b/applications/debug/subghz_test/scenes/subghz_test_scene.h new file mode 100644 index 00000000000..0e6e06481b5 --- /dev/null +++ b/applications/debug/subghz_test/scenes/subghz_test_scene.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +// Generate scene id and total number +#define ADD_SCENE(prefix, name, id) SubGhzTestScene##id, +typedef enum { +#include "subghz_test_scene_config.h" + SubGhzTestSceneNum, +} SubGhzTestScene; +#undef ADD_SCENE + +extern const SceneManagerHandlers subghz_test_scene_handlers; + +// Generate scene on_enter handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); +#include "subghz_test_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 "subghz_test_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 "subghz_test_scene_config.h" +#undef ADD_SCENE diff --git a/applications/debug/subghz_test/scenes/subghz_test_scene_about.c b/applications/debug/subghz_test/scenes/subghz_test_scene_about.c new file mode 100644 index 00000000000..64263d73887 --- /dev/null +++ b/applications/debug/subghz_test/scenes/subghz_test_scene_about.c @@ -0,0 +1,66 @@ +#include "../subghz_test_app_i.h" + +void subghz_test_scene_about_widget_callback(GuiButtonType result, InputType type, void* context) { + SubGhzTestApp* app = context; + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(app->view_dispatcher, result); + } +} + +void subghz_test_scene_about_on_enter(void* context) { + SubGhzTestApp* app = context; + + FuriString* temp_str; + temp_str = furi_string_alloc(); + furi_string_printf(temp_str, "\e#%s\n", "Information"); + + furi_string_cat_printf(temp_str, "Version: %s\n", SUBGHZ_TEST_VERSION_APP); + furi_string_cat_printf(temp_str, "Developed by: %s\n", SUBGHZ_TEST_DEVELOPED); + furi_string_cat_printf(temp_str, "Github: %s\n\n", SUBGHZ_TEST_GITHUB); + + furi_string_cat_printf(temp_str, "\e#%s\n", "Description"); + furi_string_cat_printf( + temp_str, + "This application is designed\nto test the functionality of the\nbuilt-in CC1101 module.\n\n"); + + widget_add_text_box_element( + app->widget, + 0, + 0, + 128, + 14, + AlignCenter, + AlignBottom, + "\e#\e! \e!\n", + false); + widget_add_text_box_element( + app->widget, + 0, + 2, + 128, + 14, + AlignCenter, + AlignBottom, + "\e#\e! Sub-Ghz Test \e!\n", + false); + widget_add_text_scroll_element(app->widget, 0, 16, 128, 50, furi_string_get_cstr(temp_str)); + furi_string_free(temp_str); + + view_dispatcher_switch_to_view(app->view_dispatcher, SubGhzTestViewWidget); +} + +bool subghz_test_scene_about_on_event(void* context, SceneManagerEvent event) { + SubGhzTestApp* app = context; + bool consumed = false; + UNUSED(app); + UNUSED(event); + + return consumed; +} + +void subghz_test_scene_about_on_exit(void* context) { + SubGhzTestApp* app = context; + + // Clear views + widget_reset(app->widget); +} diff --git a/applications/debug/subghz_test/scenes/subghz_test_scene_carrier.c b/applications/debug/subghz_test/scenes/subghz_test_scene_carrier.c new file mode 100644 index 00000000000..41ff5c8c6e1 --- /dev/null +++ b/applications/debug/subghz_test/scenes/subghz_test_scene_carrier.c @@ -0,0 +1,29 @@ +#include "../subghz_test_app_i.h" + +void subghz_test_scene_carrier_callback(SubGhzTestCarrierEvent event, void* context) { + furi_assert(context); + SubGhzTestApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, event); +} + +void subghz_test_scene_carrier_on_enter(void* context) { + SubGhzTestApp* app = context; + subghz_test_carrier_set_callback( + app->subghz_test_carrier, subghz_test_scene_carrier_callback, app); + view_dispatcher_switch_to_view(app->view_dispatcher, SubGhzTestViewCarrier); +} + +bool subghz_test_scene_carrier_on_event(void* context, SceneManagerEvent event) { + SubGhzTestApp* app = context; + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubGhzTestCarrierEventOnlyRx) { + scene_manager_next_scene(app->scene_manager, SubGhzTestSceneShowOnlyRx); + return true; + } + } + return false; +} + +void subghz_test_scene_carrier_on_exit(void* context) { + UNUSED(context); +} diff --git a/applications/debug/subghz_test/scenes/subghz_test_scene_config.h b/applications/debug/subghz_test/scenes/subghz_test_scene_config.h new file mode 100644 index 00000000000..80a42c3761a --- /dev/null +++ b/applications/debug/subghz_test/scenes/subghz_test_scene_config.h @@ -0,0 +1,6 @@ +ADD_SCENE(subghz_test, start, Start) +ADD_SCENE(subghz_test, about, About) +ADD_SCENE(subghz_test, carrier, Carrier) +ADD_SCENE(subghz_test, packet, Packet) +ADD_SCENE(subghz_test, static, Static) +ADD_SCENE(subghz_test, show_only_rx, ShowOnlyRx) diff --git a/applications/debug/subghz_test/scenes/subghz_test_scene_packet.c b/applications/debug/subghz_test/scenes/subghz_test_scene_packet.c new file mode 100644 index 00000000000..b43a4d0cb39 --- /dev/null +++ b/applications/debug/subghz_test/scenes/subghz_test_scene_packet.c @@ -0,0 +1,29 @@ +#include "../subghz_test_app_i.h" + +void subghz_test_scene_packet_callback(SubGhzTestPacketEvent event, void* context) { + furi_assert(context); + SubGhzTestApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, event); +} + +void subghz_test_scene_packet_on_enter(void* context) { + SubGhzTestApp* app = context; + subghz_test_packet_set_callback( + app->subghz_test_packet, subghz_test_scene_packet_callback, app); + view_dispatcher_switch_to_view(app->view_dispatcher, SubGhzTestViewPacket); +} + +bool subghz_test_scene_packet_on_event(void* context, SceneManagerEvent event) { + SubGhzTestApp* app = context; + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubGhzTestPacketEventOnlyRx) { + scene_manager_next_scene(app->scene_manager, SubGhzTestSceneShowOnlyRx); + return true; + } + } + return false; +} + +void subghz_test_scene_packet_on_exit(void* context) { + UNUSED(context); +} diff --git a/applications/debug/subghz_test/scenes/subghz_test_scene_show_only_rx.c b/applications/debug/subghz_test/scenes/subghz_test_scene_show_only_rx.c new file mode 100644 index 00000000000..3d5a54355c3 --- /dev/null +++ b/applications/debug/subghz_test/scenes/subghz_test_scene_show_only_rx.c @@ -0,0 +1,49 @@ +#include "../subghz_test_app_i.h" +#include + +void subghz_test_scene_show_only_rx_popup_callback(void* context) { + SubGhzTestApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, SubGhzTestCustomEventSceneShowOnlyRX); +} + +void subghz_test_scene_show_only_rx_on_enter(void* context) { + SubGhzTestApp* app = context; + + // Setup view + Popup* popup = app->popup; + + const char* header_text = "Transmission is blocked"; + const char* message_text = "Transmission on\nthis frequency is\nrestricted in\nyour region"; + if(!furi_hal_region_is_provisioned()) { + header_text = "Firmware update needed"; + message_text = "Please update\nfirmware before\nusing this feature\nflipp.dev/upd"; + } + + popup_set_header(popup, header_text, 63, 3, AlignCenter, AlignTop); + popup_set_text(popup, message_text, 0, 17, AlignLeft, AlignTop); + popup_set_icon(popup, 72, 17, &I_DolphinCommon_56x48); + + popup_set_timeout(popup, 1500); + popup_set_context(popup, app); + popup_set_callback(popup, subghz_test_scene_show_only_rx_popup_callback); + popup_enable_timeout(popup); + view_dispatcher_switch_to_view(app->view_dispatcher, SubGhzTestViewPopup); +} + +bool subghz_test_scene_show_only_rx_on_event(void* context, SceneManagerEvent event) { + SubGhzTestApp* app = context; + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubGhzTestCustomEventSceneShowOnlyRX) { + scene_manager_previous_scene(app->scene_manager); + return true; + } + } + return false; +} + +void subghz_test_scene_show_only_rx_on_exit(void* context) { + SubGhzTestApp* app = context; + Popup* popup = app->popup; + + popup_reset(popup); +} diff --git a/applications/debug/subghz_test/scenes/subghz_test_scene_start.c b/applications/debug/subghz_test/scenes/subghz_test_scene_start.c new file mode 100644 index 00000000000..cf3b08163d2 --- /dev/null +++ b/applications/debug/subghz_test/scenes/subghz_test_scene_start.c @@ -0,0 +1,77 @@ +#include "../subghz_test_app_i.h" + +typedef enum { + SubmenuIndexSubGhzTestCarrier, + SubmenuIndexSubGhzTestPacket, + SubmenuIndexSubGhzTestStatic, + SubmenuIndexSubGhzTestAbout, +} SubmenuIndex; + +void subghz_test_scene_start_submenu_callback(void* context, uint32_t index) { + SubGhzTestApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, index); +} + +void subghz_test_scene_start_on_enter(void* context) { + SubGhzTestApp* app = context; + Submenu* submenu = app->submenu; + + submenu_add_item( + submenu, + "Carrier", + SubmenuIndexSubGhzTestCarrier, + subghz_test_scene_start_submenu_callback, + app); + submenu_add_item( + submenu, + "Packet", + SubmenuIndexSubGhzTestPacket, + subghz_test_scene_start_submenu_callback, + app); + submenu_add_item( + submenu, + "Static", + SubmenuIndexSubGhzTestStatic, + subghz_test_scene_start_submenu_callback, + app); + submenu_add_item( + submenu, + "About", + SubmenuIndexSubGhzTestAbout, + subghz_test_scene_start_submenu_callback, + app); + + submenu_set_selected_item( + submenu, scene_manager_get_scene_state(app->scene_manager, SubGhzTestSceneStart)); + + view_dispatcher_switch_to_view(app->view_dispatcher, SubGhzTestViewSubmenu); +} + +bool subghz_test_scene_start_on_event(void* context, SceneManagerEvent event) { + SubGhzTestApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubmenuIndexSubGhzTestAbout) { + scene_manager_next_scene(app->scene_manager, SubGhzTestSceneAbout); + consumed = true; + } else if(event.event == SubmenuIndexSubGhzTestCarrier) { + scene_manager_next_scene(app->scene_manager, SubGhzTestSceneCarrier); + consumed = true; + } else if(event.event == SubmenuIndexSubGhzTestPacket) { + scene_manager_next_scene(app->scene_manager, SubGhzTestScenePacket); + consumed = true; + } else if(event.event == SubmenuIndexSubGhzTestStatic) { + scene_manager_next_scene(app->scene_manager, SubGhzTestSceneStatic); + consumed = true; + } + scene_manager_set_scene_state(app->scene_manager, SubGhzTestSceneStart, event.event); + } + + return consumed; +} + +void subghz_test_scene_start_on_exit(void* context) { + SubGhzTestApp* app = context; + submenu_reset(app->submenu); +} diff --git a/applications/debug/subghz_test/scenes/subghz_test_scene_static.c b/applications/debug/subghz_test/scenes/subghz_test_scene_static.c new file mode 100644 index 00000000000..a008d2438ff --- /dev/null +++ b/applications/debug/subghz_test/scenes/subghz_test_scene_static.c @@ -0,0 +1,29 @@ +#include "../subghz_test_app_i.h" + +void subghz_test_scene_static_callback(SubGhzTestStaticEvent event, void* context) { + furi_assert(context); + SubGhzTestApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, event); +} + +void subghz_test_scene_static_on_enter(void* context) { + SubGhzTestApp* app = context; + subghz_test_static_set_callback( + app->subghz_test_static, subghz_test_scene_static_callback, app); + view_dispatcher_switch_to_view(app->view_dispatcher, SubGhzTestViewStatic); +} + +bool subghz_test_scene_static_on_event(void* context, SceneManagerEvent event) { + SubGhzTestApp* app = context; + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubGhzTestStaticEventOnlyRx) { + scene_manager_next_scene(app->scene_manager, SubGhzTestSceneShowOnlyRx); + return true; + } + } + return false; +} + +void subghz_test_scene_static_on_exit(void* context) { + UNUSED(context); +} diff --git a/applications/debug/subghz_test/subghz_test_10px.png b/applications/debug/subghz_test/subghz_test_10px.png new file mode 100644 index 0000000000000000000000000000000000000000..10dac0ecaac608aba6d445bc5fef45d8cc1efff4 GIT binary patch literal 181 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V8<6ZZI=>f4F%}28J29*~C-V}>VM%xNb!1@J z*w6hZkrl}2EbxddW? +#include + +static bool subghz_test_app_custom_event_callback(void* context, uint32_t event) { + furi_assert(context); + SubGhzTestApp* app = context; + return scene_manager_handle_custom_event(app->scene_manager, event); +} + +static bool subghz_test_app_back_event_callback(void* context) { + furi_assert(context); + SubGhzTestApp* app = context; + return scene_manager_handle_back_event(app->scene_manager); +} + +static void subghz_test_app_tick_event_callback(void* context) { + furi_assert(context); + SubGhzTestApp* app = context; + scene_manager_handle_tick_event(app->scene_manager); +} + +SubGhzTestApp* subghz_test_app_alloc() { + SubGhzTestApp* app = malloc(sizeof(SubGhzTestApp)); + + // GUI + app->gui = furi_record_open(RECORD_GUI); + + // View Dispatcher + app->view_dispatcher = view_dispatcher_alloc(); + app->scene_manager = scene_manager_alloc(&subghz_test_scene_handlers, app); + view_dispatcher_enable_queue(app->view_dispatcher); + + view_dispatcher_set_event_callback_context(app->view_dispatcher, app); + view_dispatcher_set_custom_event_callback( + app->view_dispatcher, subghz_test_app_custom_event_callback); + view_dispatcher_set_navigation_event_callback( + app->view_dispatcher, subghz_test_app_back_event_callback); + view_dispatcher_set_tick_event_callback( + app->view_dispatcher, subghz_test_app_tick_event_callback, 100); + + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + + // Open Notification record + app->notifications = furi_record_open(RECORD_NOTIFICATION); + + // SubMenu + app->submenu = submenu_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, SubGhzTestViewSubmenu, submenu_get_view(app->submenu)); + + // Widget + app->widget = widget_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, SubGhzTestViewWidget, widget_get_view(app->widget)); + + // Popup + app->popup = popup_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, SubGhzTestViewPopup, popup_get_view(app->popup)); + + // Carrier Test Module + app->subghz_test_carrier = subghz_test_carrier_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + SubGhzTestViewCarrier, + subghz_test_carrier_get_view(app->subghz_test_carrier)); + + // Packet Test + app->subghz_test_packet = subghz_test_packet_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + SubGhzTestViewPacket, + subghz_test_packet_get_view(app->subghz_test_packet)); + + // Static send + app->subghz_test_static = subghz_test_static_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + SubGhzTestViewStatic, + subghz_test_static_get_view(app->subghz_test_static)); + + scene_manager_next_scene(app->scene_manager, SubGhzTestSceneStart); + + return app; +} + +void subghz_test_app_free(SubGhzTestApp* app) { + furi_assert(app); + + // Submenu + view_dispatcher_remove_view(app->view_dispatcher, SubGhzTestViewSubmenu); + submenu_free(app->submenu); + + // Widget + view_dispatcher_remove_view(app->view_dispatcher, SubGhzTestViewWidget); + widget_free(app->widget); + + // Popup + view_dispatcher_remove_view(app->view_dispatcher, SubGhzTestViewPopup); + popup_free(app->popup); + + // Carrier Test + view_dispatcher_remove_view(app->view_dispatcher, SubGhzTestViewCarrier); + subghz_test_carrier_free(app->subghz_test_carrier); + + // Packet Test + view_dispatcher_remove_view(app->view_dispatcher, SubGhzTestViewPacket); + subghz_test_packet_free(app->subghz_test_packet); + + // Static + view_dispatcher_remove_view(app->view_dispatcher, SubGhzTestViewStatic); + subghz_test_static_free(app->subghz_test_static); + + // View dispatcher + view_dispatcher_free(app->view_dispatcher); + scene_manager_free(app->scene_manager); + + // Notifications + furi_record_close(RECORD_NOTIFICATION); + app->notifications = NULL; + + // Close records + furi_record_close(RECORD_GUI); + + free(app); +} + +int32_t subghz_test_app(void* p) { + UNUSED(p); + SubGhzTestApp* subghz_test_app = subghz_test_app_alloc(); + + view_dispatcher_run(subghz_test_app->view_dispatcher); + + subghz_test_app_free(subghz_test_app); + + return 0; +} diff --git a/applications/debug/subghz_test/subghz_test_app_i.c b/applications/debug/subghz_test/subghz_test_app_i.c new file mode 100644 index 00000000000..0ec6635a0eb --- /dev/null +++ b/applications/debug/subghz_test/subghz_test_app_i.c @@ -0,0 +1,5 @@ +#include "subghz_test_app_i.h" + +#include + +#define TAG "SubGhzTest" diff --git a/applications/debug/subghz_test/subghz_test_app_i.h b/applications/debug/subghz_test/subghz_test_app_i.h new file mode 100644 index 00000000000..c96f9c4ee4c --- /dev/null +++ b/applications/debug/subghz_test/subghz_test_app_i.h @@ -0,0 +1,32 @@ +#pragma once + +#include "helpers/subghz_test_types.h" +#include "helpers/subghz_test_event.h" + +#include "scenes/subghz_test_scene.h" +#include +#include +#include +#include +#include +#include +#include + +#include "views/subghz_test_static.h" +#include "views/subghz_test_carrier.h" +#include "views/subghz_test_packet.h" + +typedef struct SubGhzTestApp SubGhzTestApp; + +struct SubGhzTestApp { + Gui* gui; + ViewDispatcher* view_dispatcher; + SceneManager* scene_manager; + NotificationApp* notifications; + Submenu* submenu; + Widget* widget; + Popup* popup; + SubGhzTestStatic* subghz_test_static; + SubGhzTestCarrier* subghz_test_carrier; + SubGhzTestPacket* subghz_test_packet; +}; diff --git a/applications/main/subghz/views/subghz_test_carrier.c b/applications/debug/subghz_test/views/subghz_test_carrier.c similarity index 98% rename from applications/main/subghz/views/subghz_test_carrier.c rename to applications/debug/subghz_test/views/subghz_test_carrier.c index 254a4127bc4..53e309b7c98 100644 --- a/applications/main/subghz/views/subghz_test_carrier.c +++ b/applications/debug/subghz_test/views/subghz_test_carrier.c @@ -1,6 +1,6 @@ #include "subghz_test_carrier.h" -#include "../subghz_i.h" -#include "../helpers/subghz_testing.h" +#include "../subghz_test_app_i.h" +#include "../helpers/subghz_test_frequency.h" #include #include diff --git a/applications/main/subghz/views/subghz_test_carrier.h b/applications/debug/subghz_test/views/subghz_test_carrier.h similarity index 100% rename from applications/main/subghz/views/subghz_test_carrier.h rename to applications/debug/subghz_test/views/subghz_test_carrier.h diff --git a/applications/main/subghz/views/subghz_test_packet.c b/applications/debug/subghz_test/views/subghz_test_packet.c similarity index 98% rename from applications/main/subghz/views/subghz_test_packet.c rename to applications/debug/subghz_test/views/subghz_test_packet.c index bc2c474b513..bab83ab5b57 100644 --- a/applications/main/subghz/views/subghz_test_packet.c +++ b/applications/debug/subghz_test/views/subghz_test_packet.c @@ -1,6 +1,6 @@ #include "subghz_test_packet.h" -#include "../subghz_i.h" -#include "../helpers/subghz_testing.h" +#include "../subghz_test_app_i.h" +#include "../helpers/subghz_test_frequency.h" #include #include @@ -8,7 +8,7 @@ #include #include #include -#include +#include "../protocol/princeton_for_testing.h" #define SUBGHZ_TEST_PACKET_COUNT 500 diff --git a/applications/main/subghz/views/subghz_test_packet.h b/applications/debug/subghz_test/views/subghz_test_packet.h similarity index 100% rename from applications/main/subghz/views/subghz_test_packet.h rename to applications/debug/subghz_test/views/subghz_test_packet.h diff --git a/applications/main/subghz/views/subghz_test_static.c b/applications/debug/subghz_test/views/subghz_test_static.c similarity index 98% rename from applications/main/subghz/views/subghz_test_static.c rename to applications/debug/subghz_test/views/subghz_test_static.c index 197af21fb5f..6764fd5ca9f 100644 --- a/applications/main/subghz/views/subghz_test_static.c +++ b/applications/debug/subghz_test/views/subghz_test_static.c @@ -1,6 +1,6 @@ #include "subghz_test_static.h" -#include "../subghz_i.h" -#include "../helpers/subghz_testing.h" +#include "../subghz_test_app_i.h" +#include "../helpers/subghz_test_frequency.h" #include #include @@ -8,7 +8,7 @@ #include #include #include -#include +#include "../protocol/princeton_for_testing.h" #define TAG "SubGhzTestStatic" diff --git a/applications/main/subghz/views/subghz_test_static.h b/applications/debug/subghz_test/views/subghz_test_static.h similarity index 100% rename from applications/main/subghz/views/subghz_test_static.h rename to applications/debug/subghz_test/views/subghz_test_static.h diff --git a/applications/main/subghz/helpers/subghz_types.h b/applications/main/subghz/helpers/subghz_types.h index 8beb7b9ed09..e71c22dd564 100644 --- a/applications/main/subghz/helpers/subghz_types.h +++ b/applications/main/subghz/helpers/subghz_types.h @@ -80,9 +80,6 @@ typedef enum { SubGhzViewIdFrequencyAnalyzer, SubGhzViewIdReadRAW, - SubGhzViewIdStatic, - SubGhzViewIdTestCarrier, - SubGhzViewIdTestPacket, } SubGhzViewId; /** SubGhz load type file */ diff --git a/applications/main/subghz/scenes/subghz_scene_config.h b/applications/main/subghz/scenes/subghz_scene_config.h index 97aa946e8ad..47655958b4b 100644 --- a/applications/main/subghz/scenes/subghz_scene_config.h +++ b/applications/main/subghz/scenes/subghz_scene_config.h @@ -12,10 +12,6 @@ ADD_SCENE(subghz, show_only_rx, ShowOnlyRx) ADD_SCENE(subghz, saved_menu, SavedMenu) ADD_SCENE(subghz, delete, Delete) ADD_SCENE(subghz, delete_success, DeleteSuccess) -ADD_SCENE(subghz, test, Test) -ADD_SCENE(subghz, test_static, TestStatic) -ADD_SCENE(subghz, test_carrier, TestCarrier) -ADD_SCENE(subghz, test_packet, TestPacket) ADD_SCENE(subghz, set_type, SetType) ADD_SCENE(subghz, frequency_analyzer, FrequencyAnalyzer) ADD_SCENE(subghz, read_raw, ReadRAW) diff --git a/applications/main/subghz/scenes/subghz_scene_start.c b/applications/main/subghz/scenes/subghz_scene_start.c index ce631b39812..951c756d843 100644 --- a/applications/main/subghz/scenes/subghz_scene_start.c +++ b/applications/main/subghz/scenes/subghz_scene_start.c @@ -4,7 +4,6 @@ enum SubmenuIndex { SubmenuIndexRead = 10, SubmenuIndexSaved, - SubmenuIndexTest, SubmenuIndexAddManually, SubmenuIndexFrequencyAnalyzer, SubmenuIndexReadRAW, @@ -56,10 +55,6 @@ void subghz_scene_start_on_enter(void* context) { SubmenuIndexRadioSetting, subghz_scene_start_submenu_callback, subghz); - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - submenu_add_item( - subghz->submenu, "Test", SubmenuIndexTest, subghz_scene_start_submenu_callback, subghz); - } submenu_set_selected_item( subghz->submenu, scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneStart)); @@ -101,11 +96,6 @@ bool subghz_scene_start_on_event(void* context, SceneManagerEvent event) { scene_manager_next_scene(subghz->scene_manager, SubGhzSceneFrequencyAnalyzer); dolphin_deed(DolphinDeedSubGhzFrequencyAnalyzer); return true; - } else if(event.event == SubmenuIndexTest) { - scene_manager_set_scene_state( - subghz->scene_manager, SubGhzSceneStart, SubmenuIndexTest); - scene_manager_next_scene(subghz->scene_manager, SubGhzSceneTest); - return true; } else if(event.event == SubmenuIndexShowRegionInfo) { scene_manager_set_scene_state( subghz->scene_manager, SubGhzSceneStart, SubmenuIndexShowRegionInfo); diff --git a/applications/main/subghz/scenes/subghz_scene_test.c b/applications/main/subghz/scenes/subghz_scene_test.c deleted file mode 100644 index 65f9bbdefad..00000000000 --- a/applications/main/subghz/scenes/subghz_scene_test.c +++ /dev/null @@ -1,61 +0,0 @@ -#include "../subghz_i.h" - -enum SubmenuIndex { - SubmenuIndexCarrier, - SubmenuIndexPacket, - SubmenuIndexStatic, -}; - -void subghz_scene_test_submenu_callback(void* context, uint32_t index) { - SubGhz* subghz = context; - view_dispatcher_send_custom_event(subghz->view_dispatcher, index); -} - -void subghz_scene_test_on_enter(void* context) { - SubGhz* subghz = context; - - submenu_add_item( - subghz->submenu, - "Carrier", - SubmenuIndexCarrier, - subghz_scene_test_submenu_callback, - subghz); - submenu_add_item( - subghz->submenu, "Packet", SubmenuIndexPacket, subghz_scene_test_submenu_callback, subghz); - submenu_add_item( - subghz->submenu, "Static", SubmenuIndexStatic, subghz_scene_test_submenu_callback, subghz); - - submenu_set_selected_item( - subghz->submenu, scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneTest)); - - view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdMenu); -} - -bool subghz_scene_test_on_event(void* context, SceneManagerEvent event) { - SubGhz* subghz = context; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SubmenuIndexCarrier) { - scene_manager_set_scene_state( - subghz->scene_manager, SubGhzSceneTest, SubmenuIndexCarrier); - scene_manager_next_scene(subghz->scene_manager, SubGhzSceneTestCarrier); - return true; - } else if(event.event == SubmenuIndexPacket) { - scene_manager_set_scene_state( - subghz->scene_manager, SubGhzSceneTest, SubmenuIndexPacket); - scene_manager_next_scene(subghz->scene_manager, SubGhzSceneTestPacket); - return true; - } else if(event.event == SubmenuIndexStatic) { - scene_manager_set_scene_state( - subghz->scene_manager, SubGhzSceneTest, SubmenuIndexStatic); - scene_manager_next_scene(subghz->scene_manager, SubGhzSceneTestStatic); - return true; - } - } - return false; -} - -void subghz_scene_test_on_exit(void* context) { - SubGhz* subghz = context; - submenu_reset(subghz->submenu); -} diff --git a/applications/main/subghz/scenes/subghz_scene_test_carrier.c b/applications/main/subghz/scenes/subghz_scene_test_carrier.c deleted file mode 100644 index 9677792ba31..00000000000 --- a/applications/main/subghz/scenes/subghz_scene_test_carrier.c +++ /dev/null @@ -1,30 +0,0 @@ -#include "../subghz_i.h" -#include "../views/subghz_test_carrier.h" - -void subghz_scene_test_carrier_callback(SubGhzTestCarrierEvent event, void* context) { - furi_assert(context); - SubGhz* subghz = context; - view_dispatcher_send_custom_event(subghz->view_dispatcher, event); -} - -void subghz_scene_test_carrier_on_enter(void* context) { - SubGhz* subghz = context; - subghz_test_carrier_set_callback( - subghz->subghz_test_carrier, subghz_scene_test_carrier_callback, subghz); - view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdTestCarrier); -} - -bool subghz_scene_test_carrier_on_event(void* context, SceneManagerEvent event) { - SubGhz* subghz = context; - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SubGhzTestCarrierEventOnlyRx) { - scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowOnlyRx); - return true; - } - } - return false; -} - -void subghz_scene_test_carrier_on_exit(void* context) { - UNUSED(context); -} diff --git a/applications/main/subghz/scenes/subghz_scene_test_packet.c b/applications/main/subghz/scenes/subghz_scene_test_packet.c deleted file mode 100644 index 99f0ab1791f..00000000000 --- a/applications/main/subghz/scenes/subghz_scene_test_packet.c +++ /dev/null @@ -1,30 +0,0 @@ -#include "../subghz_i.h" -#include "../views/subghz_test_packet.h" - -void subghz_scene_test_packet_callback(SubGhzTestPacketEvent event, void* context) { - furi_assert(context); - SubGhz* subghz = context; - view_dispatcher_send_custom_event(subghz->view_dispatcher, event); -} - -void subghz_scene_test_packet_on_enter(void* context) { - SubGhz* subghz = context; - subghz_test_packet_set_callback( - subghz->subghz_test_packet, subghz_scene_test_packet_callback, subghz); - view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdTestPacket); -} - -bool subghz_scene_test_packet_on_event(void* context, SceneManagerEvent event) { - SubGhz* subghz = context; - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SubGhzTestPacketEventOnlyRx) { - scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowOnlyRx); - return true; - } - } - return false; -} - -void subghz_scene_test_packet_on_exit(void* context) { - UNUSED(context); -} diff --git a/applications/main/subghz/scenes/subghz_scene_test_static.c b/applications/main/subghz/scenes/subghz_scene_test_static.c deleted file mode 100644 index 10e6d02a1da..00000000000 --- a/applications/main/subghz/scenes/subghz_scene_test_static.c +++ /dev/null @@ -1,30 +0,0 @@ -#include "../subghz_i.h" -#include "../views/subghz_test_static.h" - -void subghz_scene_test_static_callback(SubGhzTestStaticEvent event, void* context) { - furi_assert(context); - SubGhz* subghz = context; - view_dispatcher_send_custom_event(subghz->view_dispatcher, event); -} - -void subghz_scene_test_static_on_enter(void* context) { - SubGhz* subghz = context; - subghz_test_static_set_callback( - subghz->subghz_test_static, subghz_scene_test_static_callback, subghz); - view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdStatic); -} - -bool subghz_scene_test_static_on_event(void* context, SceneManagerEvent event) { - SubGhz* subghz = context; - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SubGhzTestStaticEventOnlyRx) { - scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowOnlyRx); - return true; - } - } - return false; -} - -void subghz_scene_test_static_on_exit(void* context) { - UNUSED(context); -} diff --git a/applications/main/subghz/subghz.c b/applications/main/subghz/subghz.c index 09963584afc..e8148798ef4 100644 --- a/applications/main/subghz/subghz.c +++ b/applications/main/subghz/subghz.c @@ -129,27 +129,6 @@ SubGhz* subghz_alloc() { SubGhzViewIdReadRAW, subghz_read_raw_get_view(subghz->subghz_read_raw)); - // Carrier Test Module - subghz->subghz_test_carrier = subghz_test_carrier_alloc(); - view_dispatcher_add_view( - subghz->view_dispatcher, - SubGhzViewIdTestCarrier, - subghz_test_carrier_get_view(subghz->subghz_test_carrier)); - - // Packet Test - subghz->subghz_test_packet = subghz_test_packet_alloc(); - view_dispatcher_add_view( - subghz->view_dispatcher, - SubGhzViewIdTestPacket, - subghz_test_packet_get_view(subghz->subghz_test_packet)); - - // Static send - subghz->subghz_test_static = subghz_test_static_alloc(); - view_dispatcher_add_view( - subghz->view_dispatcher, - SubGhzViewIdStatic, - subghz_test_static_get_view(subghz->subghz_test_static)); - //init threshold rssi subghz->threshold_rssi = subghz_threshold_rssi_alloc(); @@ -183,18 +162,6 @@ void subghz_free(SubGhz* subghz) { subghz_txrx_stop(subghz->txrx); subghz_txrx_sleep(subghz->txrx); - // Packet Test - view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdTestPacket); - subghz_test_packet_free(subghz->subghz_test_packet); - - // Carrier Test - view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdTestCarrier); - subghz_test_carrier_free(subghz->subghz_test_carrier); - - // Static - view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdStatic); - subghz_test_static_free(subghz->subghz_test_static); - // Receiver view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdReceiver); subghz_view_receiver_free(subghz->subghz_receiver); diff --git a/applications/main/subghz/subghz_i.h b/applications/main/subghz/subghz_i.h index fc3404c07e7..9e58f3947dc 100644 --- a/applications/main/subghz/subghz_i.h +++ b/applications/main/subghz/subghz_i.h @@ -9,10 +9,6 @@ #include "views/subghz_frequency_analyzer.h" #include "views/subghz_read_raw.h" -#include "views/subghz_test_static.h" -#include "views/subghz_test_carrier.h" -#include "views/subghz_test_packet.h" - #include #include #include @@ -64,9 +60,6 @@ struct SubGhz { SubGhzFrequencyAnalyzer* subghz_frequency_analyzer; SubGhzReadRAW* subghz_read_raw; - SubGhzTestStatic* subghz_test_static; - SubGhzTestCarrier* subghz_test_carrier; - SubGhzTestPacket* subghz_test_packet; SubGhzProtocolFlag filter; FuriString* error_str; diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index a44e663ba65..ffbc0104b6b 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1384,8 +1384,8 @@ Function,+,furi_hal_subghz_rx_pipe_not_empty,_Bool, Function,+,furi_hal_subghz_set_async_mirror_pin,void,const GpioPin* Function,+,furi_hal_subghz_set_frequency,uint32_t,uint32_t Function,+,furi_hal_subghz_set_frequency_and_path,uint32_t,uint32_t -Function,-,furi_hal_subghz_set_path,void,FuriHalSubGhzPath -Function,-,furi_hal_subghz_shutdown,void, +Function,+,furi_hal_subghz_set_path,void,FuriHalSubGhzPath +Function,+,furi_hal_subghz_shutdown,void, Function,+,furi_hal_subghz_sleep,void, Function,+,furi_hal_subghz_start_async_rx,void,"FuriHalSubGhzCaptureCallback, void*" Function,+,furi_hal_subghz_start_async_tx,_Bool,"FuriHalSubGhzAsyncTxCallback, void*" @@ -2745,6 +2745,7 @@ Function,+,subghz_protocol_decoder_base_get_hash_data,uint8_t,SubGhzProtocolDeco Function,+,subghz_protocol_decoder_base_get_string,_Bool,"SubGhzProtocolDecoderBase*, FuriString*" Function,+,subghz_protocol_decoder_base_serialize,SubGhzProtocolStatus,"SubGhzProtocolDecoderBase*, FlipperFormat*, SubGhzRadioPreset*" Function,-,subghz_protocol_decoder_base_set_decoder_callback,void,"SubGhzProtocolDecoderBase*, SubGhzProtocolDecoderBaseRxCallback, void*" +Function,+,subghz_protocol_decoder_bin_raw_data_input_rssi,void,"SubGhzProtocolDecoderBinRAW*, float" Function,+,subghz_protocol_decoder_raw_alloc,void*,SubGhzEnvironment* Function,+,subghz_protocol_decoder_raw_deserialize,SubGhzProtocolStatus,"void*, FlipperFormat*" Function,+,subghz_protocol_decoder_raw_feed,void,"void*, _Bool, uint32_t" @@ -2756,6 +2757,7 @@ Function,+,subghz_protocol_encoder_raw_deserialize,SubGhzProtocolStatus,"void*, Function,+,subghz_protocol_encoder_raw_free,void,void* Function,+,subghz_protocol_encoder_raw_stop,void,void* Function,+,subghz_protocol_encoder_raw_yield,LevelDuration,void* +Function,+,subghz_protocol_keeloq_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint8_t, uint16_t, const char*, SubGhzRadioPreset*" Function,+,subghz_protocol_raw_file_encoder_worker_set_callback_end,void,"SubGhzProtocolEncoderRAW*, SubGhzProtocolEncoderRAWCallbackEnd, void*" Function,+,subghz_protocol_raw_gen_fff_data,void,"FlipperFormat*, const char*, const char*" Function,+,subghz_protocol_raw_get_sample_write,size_t,SubGhzProtocolDecoderRAW* @@ -2765,6 +2767,8 @@ Function,+,subghz_protocol_raw_save_to_file_stop,void,SubGhzProtocolDecoderRAW* Function,+,subghz_protocol_registry_count,size_t,const SubGhzProtocolRegistry* Function,+,subghz_protocol_registry_get_by_index,const SubGhzProtocol*,"const SubGhzProtocolRegistry*, size_t" Function,+,subghz_protocol_registry_get_by_name,const SubGhzProtocol*,"const SubGhzProtocolRegistry*, const char*" +Function,+,subghz_protocol_secplus_v1_check_fixed,_Bool,uint32_t +Function,+,subghz_protocol_secplus_v2_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint8_t, uint32_t, SubGhzRadioPreset*" Function,+,subghz_receiver_alloc_init,SubGhzReceiver*,SubGhzEnvironment* Function,+,subghz_receiver_decode,void,"SubGhzReceiver*, _Bool, uint32_t" Function,+,subghz_receiver_free,void,SubGhzReceiver* diff --git a/lib/subghz/subghz_protocol_registry.h b/lib/subghz/subghz_protocol_registry.h index 6a27da99259..8e80071b518 100644 --- a/lib/subghz/subghz_protocol_registry.h +++ b/lib/subghz/subghz_protocol_registry.h @@ -8,6 +8,31 @@ extern "C" { extern const SubGhzProtocolRegistry subghz_protocol_registry; +typedef struct SubGhzProtocolDecoderBinRAW SubGhzProtocolDecoderBinRAW; + +bool subghz_protocol_secplus_v2_create_data( + void* context, + FlipperFormat* flipper_format, + uint32_t serial, + uint8_t btn, + uint32_t cnt, + SubGhzRadioPreset* preset); + +bool subghz_protocol_keeloq_create_data( + void* context, + FlipperFormat* flipper_format, + uint32_t serial, + uint8_t btn, + uint16_t cnt, + const char* manufacture_name, + SubGhzRadioPreset* preset); + +void subghz_protocol_decoder_bin_raw_data_input_rssi( + SubGhzProtocolDecoderBinRAW* instance, + float rssi); + +bool subghz_protocol_secplus_v1_check_fixed(uint32_t fixed); + #ifdef __cplusplus } #endif From b148e396d74ff636c3f90efff43475ba36bf0371 Mon Sep 17 00:00:00 2001 From: Dusan Hlavaty Date: Mon, 10 Jul 2023 08:22:30 +0200 Subject: [PATCH 647/824] feat(infrared): add TCL TV remote (#2853) Adds a TCL TV remote to the universal remotes list. Tested with TCL 55C815 television --- assets/resources/infrared/assets/tv.ir | 36 ++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/assets/resources/infrared/assets/tv.ir b/assets/resources/infrared/assets/tv.ir index ba8807123c9..c4b6f0c42b2 100644 --- a/assets/resources/infrared/assets/tv.ir +++ b/assets/resources/infrared/assets/tv.ir @@ -1681,3 +1681,39 @@ type: raw frequency: 38000 duty_cycle: 0.330000 data: 3462 1592 490 332 513 1200 489 331 514 1201 489 355 490 1201 489 356 512 1178 489 356 512 1178 512 334 487 1202 488 1202 488 357 512 1178 512 334 486 1203 487 1202 488 1203 487 1204 486 383 461 1228 488 357 488 357 487 357 487 1203 486 1204 486 359 485 1205 485 360 485 361 484 360 485 360 485 361 484 361 484 361 484 1206 484 360 484 361 484 361 484 1206 484 361 484 361 484 361 484 1206 484 361 484 1206 484 361 484 71543 3434 1620 486 359 485 1205 485 360 485 1206 484 360 485 1206 484 360 485 1206 484 360 485 1206 484 360 485 1205 485 1206 484 360 485 1206 484 360 485 1206 484 1206 484 1206 484 1206 484 361 484 1206 484 360 485 360 485 361 484 1206 484 1206 484 360 484 1206 484 360 485 361 484 361 484 360 485 361 484 361 484 361 484 1206 484 361 484 361 484 361 484 1206 484 361 484 361 484 361 484 1207 483 361 484 1206 484 361 484 71543 3435 1619 486 358 486 1204 486 359 486 1205 485 360 485 1205 485 360 485 1205 485 360 485 1205 485 360 484 1205 485 1205 485 360 485 1205 485 360 485 1205 485 1205 485 1205 485 1206 484 360 485 1205 485 360 485 360 485 360 485 1205 485 1206 484 360 485 1206 484 360 485 360 485 360 485 360 485 360 485 360 485 360 485 1206 484 360 485 360 485 360 485 1206 484 360 485 360 485 360 485 1205 485 360 485 1206 484 360 485 71542 3436 1619 486 358 487 1204 486 359 485 1205 485 360 485 1205 485 360 485 1205 485 360 485 1205 485 360 485 1205 485 1205 485 360 485 1205 485 360 485 1206 484 1206 484 1206 484 1206 484 360 485 1206 484 360 485 360 485 361 484 1206 484 1206 484 361 484 1206 484 361 484 361 484 361 484 361 484 361 484 361 484 360 485 1206 484 361 484 361 484 361 484 1206 484 361 484 361 484 360 485 1206 484 361 484 1206 484 361 484 71542 3437 1618 487 358 486 1204 486 359 486 1205 485 360 485 1205 485 360 485 1205 485 360 485 1205 485 360 485 1205 485 1206 484 360 485 1205 485 360 485 1206 484 1205 485 1206 484 1205 485 360 485 1205 485 360 485 360 485 360 485 1205 485 1205 485 360 485 1205 485 360 485 360 485 360 485 360 485 360 485 360 485 360 485 1205 485 360 485 360 485 360 485 1205 485 360 485 360 485 360 485 1205 485 360 485 1205 485 360 485 +# Model: TCL +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3977 3993 494 1994 495 1995 496 1997 494 1996 495 1004 496 1004 496 1995 496 1004 496 1997 494 1005 495 1995 495 1007 493 1006 494 1006 494 1005 495 1007 493 1997 494 1995 496 1004 496 1995 496 1005 495 1995 496 1003 497 1995 496 8467 3980 3993 494 1994 495 1996 495 1997 494 1995 496 1004 496 1006 494 1995 496 1004 496 1996 495 1004 496 1996 495 1005 495 1005 495 1004 496 1005 495 1005 495 1996 495 1995 496 1005 495 1996 495 1004 496 1995 496 1006 569 1920 571 8393 3980 3993 571 1918 572 1922 569 1920 571 1920 571 929 572 929 571 1920 571 929 571 1920 571 929 571 1921 570 930 570 929 571 929 571 929 571 928 572 1920 571 1921 570 930 571 1920 572 930 571 1921 571 929 571 1923 569 8396 3980 3994 570 1921 569 1923 569 1921 571 1920 572 929 572 930 571 1921 570 930 571 1922 570 930 571 1921 570 930 570 930 571 929 571 929 571 929 572 1922 570 1921 570 931 570 1922 570 930 571 1922 569 931 570 1921 570 +# +name: Vol_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3979 3995 493 1994 496 1997 495 1999 492 1999 492 1006 495 1008 493 1998 493 1006 494 1997 495 1999 493 1997 494 1998 493 1006 495 1007 493 1005 495 1006 495 2025 467 1998 494 1006 494 1996 495 1006 494 1005 495 1006 494 1007 493 8468 3979 3995 492 1995 495 1999 492 1997 494 1997 494 1007 493 1006 494 1997 494 1006 494 1997 494 1996 571 1923 569 1921 570 930 570 929 571 931 569 931 575 1916 570 1920 571 930 570 1922 570 930 571 930 570 930 576 924 576 +# +name: Vol_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3951 3994 494 1997 493 1998 494 1998 493 1998 493 1005 496 1005 496 1996 495 1005 495 1997 495 1996 495 1996 495 1006 494 1005 495 1006 494 1005 495 1006 494 1996 495 1998 493 1005 495 1997 494 1008 492 1006 494 1006 494 1997 494 8471 3977 3996 493 1996 493 1997 494 1998 493 1997 494 1006 494 1007 493 1997 494 1009 492 1996 495 1996 495 1997 494 1006 494 1006 494 1006 494 1006 494 1006 494 1997 493 1997 494 1006 494 1996 494 1005 495 1004 495 1006 494 1996 494 +# +name: Ch_next +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3978 3994 494 1995 495 1996 495 1996 495 1996 495 1006 494 1004 497 1997 494 1005 495 1997 520 1970 577 923 578 1914 577 924 576 924 576 925 575 924 576 1914 577 1915 576 924 576 1914 577 924 576 923 577 1915 576 926 574 8388 3978 3993 576 1913 576 1915 576 1915 576 1917 574 923 577 923 577 1943 548 925 575 1916 576 1915 575 924 576 1915 576 925 575 927 573 925 575 926 574 1916 574 1918 573 927 573 1918 573 928 572 927 573 1918 573 926 574 8389 4006 3966 572 1918 571 1919 572 1918 573 1920 570 929 571 929 571 1922 569 928 571 1920 571 1921 570 928 572 1919 572 929 571 929 571 929 571 929 571 1921 570 1921 521 980 569 1921 521 981 519 979 521 1971 520 979 521 +# +name: Ch_prev +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3979 3994 494 1995 495 1997 494 1996 495 1998 493 1005 495 1006 494 1997 494 1005 495 1996 495 1995 496 1005 495 1005 495 1006 494 1005 495 1004 496 1005 495 1997 494 1997 494 1004 496 1996 495 1005 495 1005 495 1997 494 1996 495 8467 3976 3991 496 1995 495 1996 494 1994 496 1996 494 1005 495 1005 495 1996 495 1005 495 1995 495 1995 496 1006 494 1005 495 1006 494 1005 495 1004 496 1006 494 1994 496 1996 494 1005 495 1995 495 1004 496 1004 496 1995 495 1996 494 +# +name: Mute +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3981 3992 495 1994 495 1995 496 1996 494 1996 495 1005 495 1006 494 1995 495 1997 494 1996 495 1996 494 1997 494 1996 495 1006 494 1005 495 1004 496 1005 495 1995 496 1994 496 1005 495 1004 496 1005 495 1006 494 1004 496 1006 494 8466 3978 3991 495 1994 495 1997 493 1994 496 1995 495 1004 496 1004 496 1996 494 1997 493 1996 494 1995 495 1995 495 1997 493 1004 495 1004 495 1006 494 1005 494 1998 491 1996 494 1006 494 1004 496 1006 494 1006 493 1005 495 1005 571 From 0195f8bf0044ec913e07b8ad34e449fd3e0f73cb Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Mon, 10 Jul 2023 09:48:00 +0300 Subject: [PATCH 648/824] [FL-3350] Device Info update (#2840) * Update F18 version info * Certification info for F18 Co-authored-by: Aleksandr Kutuzov --- applications/settings/about/about.c | 4 +++- assets/icons/About/Certification2_46x33.png | Bin 0 -> 229 bytes assets/icons/About/Certification2_98x33.png | Bin 2495 -> 0 bytes firmware/targets/f18/api_symbols.csv | 3 ++- .../f18/furi_hal/furi_hal_version_device.c | 12 ++++++++---- firmware/targets/f7/api_symbols.csv | 3 ++- .../f7/furi_hal/furi_hal_version_device.c | 4 ++++ .../targets/furi_hal_include/furi_hal_version.h | 6 ++++++ 8 files changed, 25 insertions(+), 7 deletions(-) create mode 100644 assets/icons/About/Certification2_46x33.png delete mode 100644 assets/icons/About/Certification2_98x33.png diff --git a/applications/settings/about/about.c b/applications/settings/about/about.c index 68810330688..55bd43e5c58 100644 --- a/applications/settings/about/about.c +++ b/applications/settings/about/about.c @@ -82,7 +82,9 @@ static DialogMessageButton icon1_screen(DialogsApp* dialogs, DialogMessage* mess static DialogMessageButton icon2_screen(DialogsApp* dialogs, DialogMessage* message) { DialogMessageButton result; - dialog_message_set_icon(message, &I_Certification2_98x33, 15, 10); + dialog_message_set_icon(message, &I_Certification2_46x33, 15, 10); + dialog_message_set_text( + message, furi_hal_version_get_mic_id(), 63, 27, AlignLeft, AlignCenter); result = dialog_message_show(dialogs, message); dialog_message_set_icon(message, NULL, 0, 0); diff --git a/assets/icons/About/Certification2_46x33.png b/assets/icons/About/Certification2_46x33.png new file mode 100644 index 0000000000000000000000000000000000000000..d421b829149518d4d379b03d76881f88706c3e87 GIT binary patch literal 229 zcmeAS@N?(olHy`uVBq!ia0vp^dO)nm!3-oP8fIPqQfvV}A+G=b{|7Qd4_&SUQnNf= z978H@t(g+YcR+!|`Qg9maYoZ4y+u8qcz^6{V@k`gJEQe1Lp5T}_AiC!OOBS>-1;=T zdEV~dE9_2v2<2Pid?=n_o3MCO-A5)qmHYo@Jp3jwB`^7eym+fxoxz8QtS6Qam1Y|! zr_NbyeOvU-CA$l&z7_nH6v>?!`zVuf^AS(s0M|6%%_#=?UdNv8|9>xd*oX c{KsLyP+HH}pmOhIG0>F^p00i_>zopr08YJL$p8QV literal 0 HcmV?d00001 diff --git a/assets/icons/About/Certification2_98x33.png b/assets/icons/About/Certification2_98x33.png deleted file mode 100644 index 49c5581c7523f2956fda218cfa9c048cf1998ef5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2495 zcma)7eN+=y77u<<3c9DF1uNPeM(e@FB$G)pm^lQ^7bZ%SFD0_A?JzSTQ=2gmAE?2J5Xf$#Jm7}N>SV;K-w?O!$Zhp2HA`rt! z^CZW31cr4(VoV~3%@edR3~*>DxWL$h(cSz|`k)YU5s@oohAdtmS;=X1IVKB& zqBJTjL8>BEv@}7fQb}`&1X4@KnY}J8eb=iatwkMFfl-d12T>@5bno-1ON*3OAxXc45=JzXPf}z(--R@i^+f~G#HRartn{9 zC=nD?EHTY7`81Wxv96&@EbdZc6dq01V>(oA9H|_tQ5Z~mjmfCeAc)cEPZtaU(VrQk z@Qh6jz1=tLu zJZl%c1V`&~K{tW+w%ZwSX$k@z4k=^`M5cb+!|R|yv?n)pfGm_K7suc*wM2O!{ZZrt z2BYRC$RV%?<}n!T@{!3779-yV+6_Jk9GLu$M_VRIBq4#Ys@tw&4`C)Q2QeoB`% z;R2c+T{rKuis}uK1x3ovo!8E3&XtEOMfUFLuh*G#YqJ|~=g)sLYSxvk8MnTAcT-_a zRrQuXHEce3zkkuD=YD?Cez?2yQQb{PO4jauX+C{g-JxHO)v(&?9iPYl;K zQ+uu=1A`}ytX)t2)%N4fq6g7U|GpJ+`*$6DQQzy%>HnF!|MjBk6wMp`VP}#|=eMdm zW3QfQ`>Fnr>3$m*gYUXj6D2d({q%uv=0DHxeqn;^#P`QCdsZlt|Kz*M(FaSqs1W=`??1& zmu);-rqAdrXl!qP@Amc04|ly*cW>N{o)ezkOw!CMw0~c7JgrCTE_TGn_kGyfyFIkh zN#D5^8MP#9>7H`slqp|$)3@RG|5#JS__|645+%zr&Nt17(2jq9#}(&|`Q+8^`n?~0 z>@Me84b^>LJiulR#vGSU&zMFYh&<@t&o@UsZ*7VWU0r$M>~p*=35~jvbf>iXyS?9? zI`B}p9$wS&A^Ob53ER%Rx_JE!y0K+qVT9V(d#|-6tWBNFpAzLv$@yYa_ZoN#+Nq#Kx~p6 zI1|4u?Z2nG3f~<#*7fmn Date: Mon, 10 Jul 2023 10:04:53 +0300 Subject: [PATCH 649/824] Add Hitachi RAK-50PEB universal ac remote (#2826) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- assets/resources/infrared/assets/ac.ir | 37 ++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/assets/resources/infrared/assets/ac.ir b/assets/resources/infrared/assets/ac.ir index c7b1aaf7e87..cb3c2539c48 100644 --- a/assets/resources/infrared/assets/ac.ir +++ b/assets/resources/infrared/assets/ac.ir @@ -507,3 +507,40 @@ type: raw frequency: 38000 duty_cycle: 0.330000 data: 3539 1637 533 1225 502 1193 534 375 501 376 501 376 500 1226 501 376 501 376 500 1226 500 1227 501 376 529 1197 558 320 557 319 555 1169 531 1195 531 346 529 1196 530 1198 528 349 526 352 524 1229 497 379 497 379 497 1230 497 380 497 380 497 379 498 380 497 380 497 380 497 380 497 380 497 380 497 380 497 380 497 380 497 380 497 380 497 380 497 9042 3535 1674 496 1230 497 1230 497 380 497 380 497 380 497 1230 497 380 497 380 496 1230 497 1230 497 380 497 1230 497 380 497 380 497 1231 496 1230 497 380 497 1231 496 1231 496 380 496 380 497 1231 496 380 497 380 496 1231 496 380 497 380 497 380 497 380 497 380 497 380 497 380 497 380 497 380 497 380 496 380 497 380 497 380 497 380 497 380 496 381 496 380 497 380 497 380 497 381 496 1231 496 381 496 380 497 380 497 381 496 381 496 1231 496 381 496 381 496 380 497 381 495 1231 496 1231 496 1231 496 380 497 381 496 381 496 380 496 381 496 381 496 381 496 381 496 381 496 1231 496 1231 496 381 496 381 496 381 496 381 496 381 496 381 496 381 496 381 496 1231 496 381 496 381 496 1232 495 381 495 1232 495 381 496 1231 496 1231 496 381 496 381 496 381 496 381 496 381 496 381 496 381 496 381 496 381 496 381 496 381 496 381 496 381 496 381 496 381 496 381 496 381 496 381 496 381 496 381 496 381 495 381 496 381 496 381 496 381 496 1232 495 381 496 381 496 381 496 381 496 381 495 381 496 381 495 382 495 381 496 382 495 381 495 382 495 381 495 382 495 382 495 382 495 382 495 382 495 382 495 382 495 382 495 382 495 381 495 1232 495 1232 495 1232 495 1232 495 1232 495 382 495 382 495 382 495 +# +# Model: Hitachi RAK-50PEB +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 30683 50966 3411 1600 493 1186 493 347 492 348 491 348 491 349 490 349 490 350 489 351 488 351 488 352 487 352 488 351 488 1192 487 352 487 351 488 352 487 352 487 352 488 352 488 351 488 1192 487 1191 488 352 487 352 487 352 487 352 487 352 487 352 487 352 487 352 487 1192 487 352 487 1192 487 1192 487 1192 487 1192 488 1192 487 1192 488 352 487 1192 487 1192 487 352 487 352 487 352 487 352 488 352 487 352 488 352 487 352 487 1192 487 1192 487 1192 487 1192 487 1192 487 1192 487 1192 487 1192 487 352 487 352 488 352 487 1192 487 352 487 352 487 352 487 352 487 1192 487 352 487 353 486 1192 487 353 486 353 486 353 486 353 486 1193 486 353 487 353 486 353 486 353 486 353 486 353 486 1193 486 1193 486 353 487 353 486 353 486 353 486 353 487 353 486 353 486 353 486 1193 486 353 486 353 486 1193 486 353 487 353 486 353 487 353 486 353 486 353 486 353 486 353 487 353 486 353 486 1193 486 1193 486 353 487 353 486 353 486 353 486 354 485 353 486 354 485 353 486 354 486 353 486 353 486 354 486 353 486 353 487 1193 486 1194 485 353 487 353 486 354 485 354 485 354 486 354 485 354 485 354 485 354 485 354 485 354 485 354 485 354 485 354 485 354 486 354 485 354 485 354 485 354 485 354 485 354 485 354 485 354 485 354 485 354 486 354 485 354 485 354 485 354 485 354 485 354 485 354 485 354 485 354 485 354 485 354 485 354 485 355 484 355 484 355 484 354 485 354 485 355 484 355 484 355 484 355 484 354 486 355 484 378 461 356 484 378 461 355 485 355 484 355 484 355 485 355 484 378 461 378 461 355 484 356 483 355 484 378 461 378 462 355 485 378 461 378 462 378 461 379 461 356 483 378 461 1195 484 378 461 379 461 356 484 378 461 379 460 379 461 378 461 378 462 378 461 379 461 378 461 378 461 379 460 379 460 379 461 378 461 378 461 378 461 378 461 378 461 379 460 379 460 379 460 379 461 379 460 1219 460 1219 460 379 461 1219 460 379 461 1219 460 +# +name: Dh +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 30684 50965 3412 1599 494 1185 494 346 493 346 493 347 492 348 491 349 490 349 490 350 489 351 489 350 489 350 489 351 489 1191 488 351 488 351 488 351 489 351 488 351 488 351 488 351 488 1191 488 1191 488 351 488 351 488 351 488 351 488 351 488 351 489 351 488 351 488 1191 488 351 489 1191 488 1191 488 1191 488 1191 488 1191 488 1191 488 351 488 1191 488 1192 487 351 488 352 487 351 488 351 488 352 487 352 487 352 487 352 488 1192 487 1192 487 1216 463 1192 487 1192 488 1192 487 1193 486 1192 488 352 487 352 487 352 487 1193 486 376 463 376 463 376 464 352 487 1216 463 376 463 376 463 1216 464 376 463 376 463 376 463 1216 463 376 463 376 463 353 486 353 487 376 463 376 463 376 463 1216 463 376 463 1216 463 376 464 376 463 376 463 376 464 376 463 376 463 376 463 376 463 1216 463 376 463 1216 463 376 463 376 463 376 463 376 463 376 463 376 463 376 463 376 463 376 464 376 463 1216 463 1217 463 376 463 377 462 377 462 377 463 376 463 376 463 377 462 376 463 377 462 377 462 377 463 376 463 377 462 377 462 1217 462 1216 463 377 463 377 462 377 462 377 462 377 463 376 463 377 462 377 463 377 462 377 462 377 462 377 462 377 462 377 462 377 462 377 462 377 463 377 462 377 463 377 462 377 462 1217 462 377 462 377 462 377 463 377 462 377 463 377 462 377 462 377 462 377 462 377 463 377 462 377 462 377 462 377 462 377 462 377 463 377 462 377 462 377 462 377 462 377 462 377 462 377 463 377 462 377 462 377 462 377 462 377 462 377 462 377 462 377 462 377 462 377 462 377 462 377 463 377 462 377 463 377 462 377 462 377 462 377 462 377 462 377 462 377 462 377 462 377 462 377 462 377 462 1217 462 377 462 377 462 377 462 377 462 378 461 377 462 377 462 378 462 377 462 377 462 377 463 377 462 378 461 378 462 377 462 378 461 377 462 378 461 378 461 378 461 378 462 377 462 378 462 1217 462 1218 461 1218 461 378 461 378 461 1218 462 377 462 378 462 +# +name: Cool_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 30747 50897 3484 1554 543 1137 542 310 529 309 530 309 530 309 530 310 529 309 530 310 529 309 530 309 530 309 530 309 530 1138 541 309 531 309 530 309 530 309 530 309 530 309 530 309 530 1138 541 1138 541 310 529 309 530 309 531 309 530 309 530 309 530 309 530 310 529 1139 541 309 530 1138 541 1138 541 1138 541 1138 542 1138 541 1138 541 310 529 1138 541 1138 541 309 530 309 530 309 531 310 529 309 530 309 530 309 530 309 530 1139 541 1139 540 1139 541 1138 541 1139 540 1139 540 1138 541 1139 540 310 529 310 529 309 530 1139 541 310 529 309 530 309 530 309 530 1139 540 309 530 309 530 1139 541 309 530 309 530 309 530 1139 540 309 531 309 530 1139 541 309 530 309 530 309 530 309 530 309 530 309 530 1139 540 309 531 309 530 309 530 309 530 309 530 309 530 309 531 309 530 309 531 309 530 1139 540 310 529 309 530 309 530 309 530 309 530 309 531 310 529 310 529 309 531 310 529 1140 540 309 530 309 531 309 530 309 530 309 530 309 531 309 530 309 530 309 530 309 530 309 531 309 530 309 531 309 530 310 529 1140 539 1140 539 309 530 309 530 309 530 309 530 310 529 309 530 309 530 309 530 309 530 309 531 309 530 309 530 309 530 309 530 309 530 309 531 309 530 309 530 309 530 309 530 309 530 1140 540 309 530 309 530 309 530 309 530 309 531 309 530 309 531 309 530 309 530 310 530 309 530 309 531 309 530 309 530 309 530 309 530 309 530 309 531 309 530 309 530 309 531 309 530 309 530 309 531 309 531 309 530 309 530 309 530 309 530 309 531 309 531 309 530 309 530 309 530 309 530 309 530 309 531 309 530 309 531 309 530 309 531 309 530 309 530 309 531 309 530 309 530 309 530 309 530 1141 538 309 531 309 530 309 530 309 530 309 531 309 530 309 530 309 530 309 531 309 530 309 530 309 530 309 530 309 530 309 530 309 530 310 530 309 531 309 530 309 530 309 530 309 530 309 530 309 531 1142 537 309 530 1142 538 309 530 1141 538 309 530 309 530 +# +name: Cool_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 30694 50951 3483 1555 542 1137 542 308 531 309 530 308 531 308 531 308 531 308 531 308 531 308 531 308 532 308 531 308 532 1139 541 308 531 308 531 308 531 308 531 308 531 309 531 308 531 1139 541 1139 540 308 531 308 531 308 531 308 531 309 530 308 531 308 531 308 532 1139 541 308 532 1139 540 1139 541 1139 540 1139 540 1139 540 1139 541 309 530 1139 540 1139 540 308 531 308 531 308 531 308 532 308 531 308 531 308 531 308 531 1140 540 1139 541 1139 540 1139 540 1139 540 1139 541 1139 540 1139 540 308 531 308 531 308 531 1140 540 309 530 308 531 308 532 308 531 1140 540 308 531 308 532 1140 539 308 531 308 531 308 531 308 532 308 531 308 531 1140 540 308 531 308 531 308 531 308 531 308 532 308 531 1140 540 308 531 308 531 308 531 308 531 308 531 308 531 1140 540 1140 540 1140 539 308 531 1140 539 308 531 308 531 308 532 308 531 306 533 308 531 306 533 308 531 307 533 308 531 1141 539 308 532 308 531 308 531 306 534 306 533 306 534 306 533 306 533 306 533 306 533 306 534 307 532 307 533 306 533 308 532 1141 539 1141 539 308 531 308 532 308 531 307 533 307 481 352 538 307 533 307 532 307 533 307 481 352 539 307 532 307 533 306 482 352 487 352 537 306 534 307 482 352 538 307 482 352 487 1192 539 309 530 307 531 306 483 352 487 352 538 307 482 352 538 306 483 352 487 352 487 352 487 353 486 352 488 353 486 352 487 352 487 353 487 353 486 353 486 353 487 352 487 353 486 353 486 353 486 353 486 353 487 353 487 353 486 353 486 353 487 353 486 353 486 353 486 353 487 353 486 353 487 352 487 353 486 353 486 353 487 353 486 353 486 353 487 353 486 353 487 353 486 353 487 353 486 1193 537 306 483 353 486 353 487 353 486 353 487 353 486 353 486 353 487 353 486 353 486 353 487 353 486 353 486 353 486 353 486 353 487 353 486 353 486 353 486 353 486 353 486 353 486 353 486 1194 485 353 487 1193 538 1142 486 1193 486 353 486 353 486 353 568 +# +name: Heat_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 30703 50953 3432 1606 490 1189 490 349 490 349 490 349 490 350 489 350 489 350 489 350 490 350 489 350 489 350 490 350 489 1190 490 350 489 350 489 350 489 350 489 350 490 350 489 350 490 1190 490 1190 489 350 489 350 489 350 489 350 490 350 490 350 489 350 490 350 490 1190 489 350 490 1190 489 1190 490 1190 489 1190 490 1190 489 1190 489 350 490 1190 489 1190 489 350 490 350 489 350 490 350 489 350 489 350 490 350 489 350 490 1190 489 1190 489 1190 490 1190 489 1190 490 1190 489 1190 489 1191 489 350 489 350 489 350 490 1190 490 350 489 350 490 350 489 350 490 1190 489 350 490 350 489 1190 490 350 489 350 490 350 490 350 489 350 489 350 489 1191 489 350 489 350 490 350 489 350 489 1191 489 1190 489 350 489 350 490 350 489 350 490 350 489 351 489 350 489 350 489 350 489 350 490 350 489 350 489 1191 489 350 489 351 489 351 488 351 489 351 488 351 489 350 489 351 489 351 489 1191 488 351 488 351 488 351 489 350 489 351 488 350 490 350 489 351 488 351 488 351 489 350 489 350 489 351 489 351 489 350 489 1191 488 1191 489 350 489 351 488 351 489 351 488 351 489 351 488 351 488 351 489 351 489 351 489 350 489 351 489 351 488 351 488 351 488 351 489 351 488 351 488 351 488 351 489 351 489 1191 488 351 489 351 488 351 489 351 488 351 489 351 489 351 488 351 488 351 489 351 489 351 488 351 488 351 488 351 489 351 488 351 488 351 489 351 488 351 488 351 489 351 489 351 488 351 488 351 489 351 489 351 488 351 489 351 488 351 489 351 488 351 489 351 488 351 489 351 488 351 489 351 488 351 488 351 489 351 488 351 489 351 489 351 489 351 488 351 489 351 488 351 488 351 489 351 488 1192 488 351 488 351 488 351 489 351 488 351 488 351 489 351 489 351 489 351 488 351 488 351 489 351 488 351 489 351 488 351 489 351 488 351 488 351 489 351 488 351 489 351 488 351 489 351 488 351 489 351 489 1191 488 1191 488 352 488 351 488 352 488 351 489 +# +name: Heat_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 30675 50953 3432 1606 490 1190 489 350 489 350 489 350 489 350 489 351 488 350 489 351 489 351 488 351 489 351 488 351 489 1191 488 351 488 351 489 351 488 351 488 351 488 351 489 351 488 1191 489 1191 489 351 488 351 488 351 488 351 488 351 489 351 489 351 488 351 489 1191 488 351 488 1191 489 1191 488 1191 488 1191 488 1191 488 1191 488 351 489 1191 488 1191 489 351 488 351 489 351 488 351 489 351 488 351 488 351 488 351 488 1191 488 1191 488 1191 488 1191 489 1191 488 1191 488 1191 489 1191 488 351 488 351 489 351 488 1191 488 351 489 351 488 351 488 351 489 1191 488 351 489 351 488 1191 488 351 488 351 488 351 489 351 489 351 488 351 489 1191 488 351 488 351 489 351 488 351 488 1191 488 1191 488 351 488 351 489 351 488 351 489 351 488 351 489 351 489 1191 488 1192 488 1191 488 351 489 1191 489 351 488 351 488 351 489 351 489 351 488 351 488 351 489 351 488 351 488 351 488 1192 488 351 489 351 488 351 488 351 489 351 489 351 488 351 488 351 488 352 487 352 488 351 488 352 488 351 488 351 488 351 488 1192 488 1192 487 352 488 352 487 352 487 352 488 352 487 352 488 351 488 352 488 352 488 352 487 352 488 351 488 351 488 352 488 352 487 352 488 352 487 352 488 352 488 352 488 352 487 1192 487 352 487 352 488 352 488 352 488 352 487 352 487 352 488 352 487 352 487 352 487 352 488 352 488 352 487 352 487 352 488 352 487 352 488 352 487 352 487 352 487 352 487 352 487 352 488 352 487 352 487 352 487 352 488 352 487 352 488 352 487 352 488 352 487 352 487 352 488 352 487 352 488 352 487 352 488 352 488 352 487 352 487 352 487 352 487 352 487 352 488 352 487 352 487 352 487 1193 486 352 487 352 488 352 487 352 487 352 488 352 487 352 488 352 488 352 487 352 488 352 487 353 486 353 487 352 487 352 488 352 488 352 487 352 487 352 487 352 487 353 487 352 488 352 488 352 487 1193 486 1193 486 1193 486 1193 487 353 487 352 487 353 486 From 9b2d80d6b7459e7e074b59948341046f3f7ee7f2 Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Mon, 10 Jul 2023 11:03:41 +0300 Subject: [PATCH 650/824] [FL-3400] External menu apps (#2849) * FBT, applications: add MENUEXTERNAL app type * FBT, uFBT: build MENUEXTERNAL as EXTERNAL app * Loader menu: show external menu apps * LFRFID: move to sd card * FBT: always build External Applications list * Archive: look for external apps path * Infrared: move to sd card * Apps: add "start" apps * iButton: move to sd card * BadUSB: move to sd card * External apps: update icons * GPIO: move to sd card * Loader: look for external apps path * U2F: move to sd * SubGHz: move to sd * Apps: "on_start" metapackage * NFC: move to sd * Sync f7 and f18 Co-authored-by: Aleksandr Kutuzov --- applications/main/application.fam | 16 +++- .../archive/scenes/archive_scene_browser.c | 3 +- applications/main/bad_usb/application.fam | 9 +-- applications/main/bad_usb/icon.png | Bin 0 -> 576 bytes applications/main/gpio/application.fam | 6 +- applications/main/gpio/icon.png | Bin 0 -> 1760 bytes applications/main/ibutton/application.fam | 12 +-- applications/main/ibutton/icon.png | Bin 0 -> 304 bytes applications/main/infrared/application.fam | 12 +-- applications/main/infrared/icon.png | Bin 0 -> 305 bytes applications/main/lfrfid/application.fam | 14 +--- applications/main/lfrfid/icon.png | Bin 0 -> 308 bytes applications/main/nfc/application.fam | 13 ++- applications/main/nfc/icon.png | Bin 0 -> 304 bytes applications/main/onewire/application.fam | 8 -- applications/main/subghz/application.fam | 14 ++-- applications/main/subghz/icon.png | Bin 0 -> 299 bytes applications/main/u2f/application.fam | 9 +-- applications/main/u2f/icon.png | Bin 0 -> 583 bytes applications/services/applications.h | 12 +++ applications/services/loader/loader.c | 18 +++++ applications/services/loader/loader_menu.c | 21 ++++- firmware/targets/f18/api_symbols.csv | 2 +- firmware/targets/f7/api_symbols.csv | 75 ++++++++++++------ lib/nfc/SConscript | 5 ++ lib/nfc/helpers/mf_classic_dict.h | 8 ++ lib/nfc/helpers/mfkey32.h | 8 ++ lib/nfc/nfc_types.h | 8 ++ lib/nfc/nfc_worker.h | 10 ++- lib/nfc/parsers/nfc_supported_card.h | 8 ++ lib/nfc/protocols/crypto1.h | 8 ++ lib/nfc/protocols/mifare_classic.h | 8 ++ lib/nfc/protocols/mifare_desfire.h | 8 ++ lib/nfc/protocols/mifare_ultralight.h | 8 ++ scripts/fbt/appmanifest.py | 26 +++++- scripts/fbt_tools/fbt_extapps.py | 5 +- scripts/ufbt/SConstruct | 1 + site_scons/extapps.scons | 1 + 38 files changed, 258 insertions(+), 98 deletions(-) create mode 100644 applications/main/bad_usb/icon.png create mode 100644 applications/main/gpio/icon.png create mode 100644 applications/main/ibutton/icon.png create mode 100644 applications/main/infrared/icon.png create mode 100644 applications/main/lfrfid/icon.png create mode 100644 applications/main/nfc/icon.png create mode 100644 applications/main/subghz/icon.png create mode 100644 applications/main/u2f/icon.png diff --git a/applications/main/application.fam b/applications/main/application.fam index 75d55af936f..0a90ee2243f 100644 --- a/applications/main/application.fam +++ b/applications/main/application.fam @@ -4,7 +4,6 @@ App( apptype=FlipperAppType.METAPACKAGE, provides=[ "gpio", - "onewire", "ibutton", "infrared", "lfrfid", @@ -13,5 +12,20 @@ App( "bad_usb", "u2f", "archive", + "main_apps_on_start", + ], +) + +App( + appid="main_apps_on_start", + name="On start hooks", + apptype=FlipperAppType.METAPACKAGE, + provides=[ + "ibutton_start", + "onewire_start", + "subghz_start", + "infrared_start", + "lfrfid_start", + "nfc_start", ], ) diff --git a/applications/main/archive/scenes/archive_scene_browser.c b/applications/main/archive/scenes/archive_scene_browser.c index e02f7622a6d..370830a0018 100644 --- a/applications/main/archive/scenes/archive_scene_browser.c +++ b/applications/main/archive/scenes/archive_scene_browser.c @@ -5,13 +5,14 @@ #include "../helpers/archive_browser.h" #include "../views/archive_browser_view.h" #include "archive/scenes/archive_scene.h" +#include #define TAG "ArchiveSceneBrowser" #define SCENE_STATE_DEFAULT (0) #define SCENE_STATE_NEED_REFRESH (1) -const char* archive_get_flipper_app_name(ArchiveFileTypeEnum file_type) { +static const char* archive_get_flipper_app_name(ArchiveFileTypeEnum file_type) { switch(file_type) { case ArchiveFileTypeIButton: return "iButton"; diff --git a/applications/main/bad_usb/application.fam b/applications/main/bad_usb/application.fam index 2442dd3aa0f..5c42c9fa3f1 100644 --- a/applications/main/bad_usb/application.fam +++ b/applications/main/bad_usb/application.fam @@ -1,15 +1,12 @@ App( appid="bad_usb", name="Bad USB", - apptype=FlipperAppType.APP, + apptype=FlipperAppType.MENUEXTERNAL, entry_point="bad_usb_app", - cdefines=["APP_BAD_USB"], - requires=[ - "gui", - "dialogs", - ], stack_size=2 * 1024, icon="A_BadUsb_14", order=70, fap_libs=["assets"], + fap_icon="icon.png", + fap_category="USB", ) diff --git a/applications/main/bad_usb/icon.png b/applications/main/bad_usb/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..037474aa3bc9c2e1aca79a68483e69980432bcf5 GIT binary patch literal 576 zcmV-G0>AxEX>4Tx04R}tkv&MmKpe$i(`rSk4t5Z6$WWau6cusQDionYs1;guFuC*#nlvOW zE{=k0!NHHks)LKOt`4q(Aou~|=;Wm6A|?JWDYS_3;J6>}?mh0_0Yan9G%FATG`(u3 z5^*t;T@{0`5D-8=V(6BcWz0!Z5}xDh9zMR_MR}I@xj#prnzI<-6NzV;VOEJZh^IHJ z2Iqa^Fe}O`@j3ChNf#u3C`-Nm{=@yu+qV-Xlle$#1U1~DPPFA zta9Gstd(o5bx;1nP)=W2<~q$0B(R7jND!f*h7!uCB1)@HiiH&I$36VRj$a~|Laq`R zITlcX2HEk0|H1EWt^DMKn-q!zT`#u%F$x5Cfo9#dzmILZc>?&Kfh)c3uQY&}Ptxmc zEph}5Yy%h9ZB5w&E_Z;TCqp)6NAlAY@_FF>jJ_!g4Bi60Yi@6?eVjf3Y3eF@0~{Oz zV+G1y_jq?tXK(+WY4!I5C=YUpXXIhH00006VoOIv0RI600RN!9r;`8x010qNS#tmY z3ljhU3ljkVnw%H_000McNliru<^lu`HWXkp{t5s906j@WK~xyijZi@f05Awj>HlAL zr$MwDdI>{Qf+U53tOUR#xOeyy)jcQo#JNRv)7r6DVVK|+*(cmT+R+EbO(O#X#REG4 O00005OriujUt-p2+p8kXl9tpG9lUcLq-^x45JRS1+%E^do6+pY)OCI_kBL^ z^L^j<-uGRTsW5+e-$PrRKtwBnona~L!R4zM%2+9EyT(WuJ-NWa6x8ydq_(isQtPy6tyorO zq|Q%50XGn7)bDn&0_mr)pe_lYB{PnpL5k?4FtgEw=5jnhH42S_z%nCI9dEUf#rij< zo#BeY9HQtUaop$gDSYV)j<@4VtyYT@DqN+KLxx-kup;f3v%*?QBBY@Qf`w;1BEzw$ zq)AtDUXj8uh@;cuB4e9XXNBqG!$jZ`f-4mS{yZJ{nMLRlGLP$o z&vS(7TiC@9&vd3g)Ss{yRIHkb)1 zFQmau+rd`A+C>M2DTx<=?TqzByCmfDN|o5gGH`3vtc!UTqp%DWuAGI+7KEf!lP1Ow zTxLDv2CM*8XQG$|%N7B1ITy#5z_tbywn?K&*97;QsRbFtjhq$2=`TQr+*}jS*%%kZ z@(r+YE4_?MlrtVIH2ddM&^j z3>C_SP=T|FKAH#Fc35Q!%eL7VSfl`IlG+zlp(+KTP|tPoIRKPf{BZbmXt;Fmp2eoa z=S8mz5}v!L&@W_z0{~7Ed}fru#mq1QESx|*95vss`9+B!VY?YvnE3@kkPZ9m_EQDD zo0G0r^jGDbj;@KRzF|7DF+QPsAT_=%=TyR5UgFYU%UaaQa>d>TXHU<*>!%xcU+9SL zXh0u@jf{;RAH&u?#ZxZsoEYwU?ZJKO{!m!Xmp9dEG2!a&suQu*%1_G^8qdYVY|g42 zJJbwr8j53&{&sgw=9Qtmz@f=YS^39WE+h`eHQAf#H=8ncp3FG2Tcy_2=e|;`v)W?T)HzCD&GN>rbh;(bdimjkF(hwtI`7 zerqbMDEpoKVP*39o$Cr>+P?TWw(k_SdfV;JtNYxS1F}cQ>eIUKom1~?75P%ENV#B?PR&Lb*-5QGoBgi((fy> zb5lo|zbC^ttl*oL9*K&DZ;zKf1!V$)EQ^!AVMt4BA~b3Z`s~uggI|53j7Es1vYx4_ z=I9e!lEzKzS2UPnkpqF_t8X7v6MqhT(E8rj<6Re5{q*aBxZXxZxlTB_{+~gr?QQAB NWXLPjcjUa=@GmQdSu_9u literal 0 HcmV?d00001 diff --git a/applications/main/ibutton/application.fam b/applications/main/ibutton/application.fam index 06968bba48f..a8faa629cea 100644 --- a/applications/main/ibutton/application.fam +++ b/applications/main/ibutton/application.fam @@ -1,25 +1,21 @@ App( appid="ibutton", name="iButton", - apptype=FlipperAppType.APP, + apptype=FlipperAppType.MENUEXTERNAL, targets=["f7"], entry_point="ibutton_app", - cdefines=["APP_IBUTTON"], - requires=[ - "gui", - "dialogs", - ], - provides=["ibutton_start"], icon="A_iButton_14", stack_size=2 * 1024, order=60, fap_libs=["assets"], + fap_icon="icon.png", + fap_category="iButton", ) App( appid="ibutton_start", apptype=FlipperAppType.STARTUP, + targets=["f7"], entry_point="ibutton_on_system_start", - requires=["ibutton"], order=60, ) diff --git a/applications/main/ibutton/icon.png b/applications/main/ibutton/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..2fdaf123a657c00c9c84632ca3c151674e451ae1 GIT binary patch literal 304 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2xkYHHq`AGmsv7|ftIx;Y9?C1WI$O_~uBzpw; zGB8xBF)%c=FfjZA3N^f7U???UV0e|lz+g3lfkC`r&aOZkpafHrx4R1i<>&pI=m5)bW_|craZ$KesPZ!4!j_b)kjvx52z42i= z^Wk##wtdVzTiGS{99;2>!TC2M!yZeXz}?LkD}l;YOI#yLQW8s2t&)pUffR$0fsvuE zfvK*cNr<75m9c@9v4ysQft7*5`ikN&C>nC}Q!>*kp&E>VdO{3LtqcsU49p-Jly36? Qy~)7f>FVdQ&MBb@0C$~I0{{R3 literal 0 HcmV?d00001 diff --git a/applications/main/infrared/application.fam b/applications/main/infrared/application.fam index e5483e9ffa9..b78b088a727 100644 --- a/applications/main/infrared/application.fam +++ b/applications/main/infrared/application.fam @@ -1,25 +1,21 @@ App( appid="infrared", name="Infrared", - apptype=FlipperAppType.APP, + apptype=FlipperAppType.MENUEXTERNAL, entry_point="infrared_app", targets=["f7"], - cdefines=["APP_INFRARED"], - requires=[ - "gui", - "dialogs", - ], - provides=["infrared_start"], icon="A_Infrared_14", stack_size=3 * 1024, order=40, fap_libs=["assets"], + fap_icon="icon.png", + fap_category="Infrared", ) App( appid="infrared_start", apptype=FlipperAppType.STARTUP, + targets=["f7"], entry_point="infrared_on_system_start", - requires=["infrared"], order=20, ) diff --git a/applications/main/infrared/icon.png b/applications/main/infrared/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..22c986180a2bed76dbe4ff439df1cf9177533c32 GIT binary patch literal 305 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2xkYHHq`AGmsv7|ftIx;Y9?C1WI$O_~uBzpw; zGB8xBF)%c=FfjZA3N^f7U???UV0e|lz+g3lfkC`r&aOZkpafHrx4R1i<>&pI=m5)bWC*+s&X`qmbr;B3<$Ms~3f`-KX%+5*7 zhxan`%;z`w!#>%c-@wM^z<~n{3>VXQv+hhNs0FH5Epd$~Nl7e8wMs5Z1yT$~21bUu z2Bx}(CLxAKR>lTa#unNJ237_J>nn=CplHa=PsvQHglaGb>IpG01*)?$HiBr_e$xf$ PHwFezS3j3^P6<>&pI=m5)bWZjP>yH&963)5S4_<9hOs!iI<>&pI=m5)b(dHL6nbwD9yPZ!4!j_b)k${QZ;XFmLn zjqNsD+YPq1J8W%_*aXBie!OR3*tC!PwU_7Q9H4U564!{5l*E!$tK_0oAjM#0U}UIk zV5)0q5@Kj%Wo%$&Y@uynU}a#izM}XGiiX_$l+3hBs0L%8o)7~QD^p9LQiz6svOM}g O4Gf;HelF{r5}E+GUQp8j literal 0 HcmV?d00001 diff --git a/applications/main/onewire/application.fam b/applications/main/onewire/application.fam index 68d4f671693..3d35abce948 100644 --- a/applications/main/onewire/application.fam +++ b/applications/main/onewire/application.fam @@ -1,14 +1,6 @@ -App( - appid="onewire", - name="1-Wire", - apptype=FlipperAppType.METAPACKAGE, - provides=["onewire_start"], -) - App( appid="onewire_start", apptype=FlipperAppType.STARTUP, entry_point="onewire_on_system_start", - requires=["onewire"], order=60, ) diff --git a/applications/main/subghz/application.fam b/applications/main/subghz/application.fam index f0dc66e89cc..4f21cb6c4e9 100644 --- a/applications/main/subghz/application.fam +++ b/applications/main/subghz/application.fam @@ -1,25 +1,21 @@ App( appid="subghz", name="Sub-GHz", - apptype=FlipperAppType.APP, + apptype=FlipperAppType.MENUEXTERNAL, targets=["f7"], entry_point="subghz_app", - cdefines=["APP_SUBGHZ"], - requires=[ - "gui", - "cli", - "dialogs", - ], - provides=["subghz_start"], icon="A_Sub1ghz_14", stack_size=3 * 1024, order=10, + fap_libs=["assets", "hwdrivers"], + fap_icon="icon.png", + fap_category="Sub-GHz", ) App( appid="subghz_start", + targets=["f7"], apptype=FlipperAppType.STARTUP, entry_point="subghz_on_system_start", - requires=["subghz"], order=40, ) diff --git a/applications/main/subghz/icon.png b/applications/main/subghz/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..5a25fdf4ef1c6cf53634aa74675001a3e8c85b7b GIT binary patch literal 299 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2xkYHHq`AGmsv7|ftIx;Y9?C1WI$O_~uBzpw; zGB8xBF)%c=FfjZA3N^f7U???UV0e|lz+g3lfkC`r&aOZkpafHrx4R1i<>&pI=m5)cB{fFDGZlI8yr;B3<$MxhJ?+;A4eL&#) z0Ra}bue?07WhLz78x$BO|L3mq-MMxdP^D^#YeY#(Vo9o1a#1RfVlXl=GSoFN)ipE; zF*LF=Hn1|b&^9ozGB8+QQTzo(LvDUbW?CgwgE3G~h=HkEX>4Tx04R}tkv&MmKpe$i(`rSk4t5Z6$WWau6cusQDionYs1;guFuC*#nlvOW zE{=k0!NHHks)LKOt`4q(Aou~|=;Wm6A|?JWDYS_3;J6>}?mh0_0Yan9G%FATG`(u3 z5^*t;T@{0`5D-8=V(6BcWz0!Z5}xDh9zMR_MR}I@xj#prnzI<-6NzV;VOEJZh^IHJ z2Iqa^Fe}O`@j3ChNf#u3C`-Nm{=@yu+qV-Xlle$#1U1~DPPFA zta9Gstd(o5bx;1nP)=W2<~q$0B(R7jND!f*h7!uCB1)@HiiH&I$36VRj$a~|Laq`R zITlcX2HEk0|H1EWt^DMKn-q!zT`#u%F$x5Cfo9#dzmILZc>?&Kfh)c3uQY&}Ptxmc zEph}5Yy%h9ZB5w&E_Z;TCqp)6NAlAY@_FF>jJ_!g4Bi60Yi@6?eVjf3Y3eF@0~{Oz zV+G1y_jq?tXK(+WY4!I5C=YUpXXIhH00006VoOIv00000008+zyMF)x010qNS#tmY z3ljhU3ljkVnw%H_000McNliru<^lu{1uVUoNs|Bo07OYdK~xyijgYww05Avx?TGzX zz7!EC4G1?heldU+2uZR%k^uSL+0?e2taR-}6`h2x#_2kxune}*>oEbW-V;;Yj|primary_menu, + FLIPPER_EXTERNAL_APPS[i].name, + FLIPPER_EXTERNAL_APPS[i].icon, + i, + loader_menu_external_apps_callback, + (void*)menu); + } + for(i = 0; i < FLIPPER_APPS_COUNT; i++) { menu_add_item( app->primary_menu, FLIPPER_APPS[i].name, FLIPPER_APPS[i].icon, i, - loader_menu_callback, + loader_menu_apps_callback, (void*)menu); } menu_add_item( diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index 5704870c92e..0149701136e 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,33.2,, +Version,+,34.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 29739abb5b1..2e02608a37d 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,33.2,, +Version,+,34.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -147,7 +147,12 @@ Header,+,lib/mlib/m-rbtree.h,, Header,+,lib/mlib/m-tuple.h,, Header,+,lib/mlib/m-variant.h,, Header,+,lib/music_worker/music_worker.h,, +Header,+,lib/nfc/helpers/mfkey32.h,, +Header,+,lib/nfc/helpers/nfc_generators.h,, Header,+,lib/nfc/nfc_device.h,, +Header,+,lib/nfc/nfc_types.h,, +Header,+,lib/nfc/nfc_worker.h,, +Header,+,lib/nfc/parsers/nfc_supported_card.h,, Header,+,lib/nfc/protocols/nfc_util.h,, Header,+,lib/one_wire/maxim_crc.h,, Header,+,lib/one_wire/one_wire_host.h,, @@ -1935,40 +1940,40 @@ Function,-,mf_classic_authenticate,_Bool,"FuriHalNfcTxRxContext*, uint8_t, uint6 Function,-,mf_classic_authenticate_skip_activate,_Bool,"FuriHalNfcTxRxContext*, uint8_t, uint64_t, MfClassicKey, _Bool, uint32_t" Function,-,mf_classic_block_to_value,_Bool,"const uint8_t*, int32_t*, uint8_t*" Function,-,mf_classic_check_card_type,_Bool,"uint8_t, uint8_t, uint8_t" -Function,-,mf_classic_dict_add_key,_Bool,"MfClassicDict*, uint8_t*" +Function,+,mf_classic_dict_add_key,_Bool,"MfClassicDict*, uint8_t*" Function,-,mf_classic_dict_add_key_str,_Bool,"MfClassicDict*, FuriString*" -Function,-,mf_classic_dict_alloc,MfClassicDict*,MfClassicDictType -Function,-,mf_classic_dict_check_presence,_Bool,MfClassicDictType -Function,-,mf_classic_dict_delete_index,_Bool,"MfClassicDict*, uint32_t" +Function,+,mf_classic_dict_alloc,MfClassicDict*,MfClassicDictType +Function,+,mf_classic_dict_check_presence,_Bool,MfClassicDictType +Function,+,mf_classic_dict_delete_index,_Bool,"MfClassicDict*, uint32_t" Function,-,mf_classic_dict_find_index,_Bool,"MfClassicDict*, uint8_t*, uint32_t*" Function,-,mf_classic_dict_find_index_str,_Bool,"MfClassicDict*, FuriString*, uint32_t*" -Function,-,mf_classic_dict_free,void,MfClassicDict* +Function,+,mf_classic_dict_free,void,MfClassicDict* Function,-,mf_classic_dict_get_key_at_index,_Bool,"MfClassicDict*, uint64_t*, uint32_t" -Function,-,mf_classic_dict_get_key_at_index_str,_Bool,"MfClassicDict*, FuriString*, uint32_t" +Function,+,mf_classic_dict_get_key_at_index_str,_Bool,"MfClassicDict*, FuriString*, uint32_t" Function,-,mf_classic_dict_get_next_key,_Bool,"MfClassicDict*, uint64_t*" -Function,-,mf_classic_dict_get_next_key_str,_Bool,"MfClassicDict*, FuriString*" -Function,-,mf_classic_dict_get_total_keys,uint32_t,MfClassicDict* -Function,-,mf_classic_dict_is_key_present,_Bool,"MfClassicDict*, uint8_t*" +Function,+,mf_classic_dict_get_next_key_str,_Bool,"MfClassicDict*, FuriString*" +Function,+,mf_classic_dict_get_total_keys,uint32_t,MfClassicDict* +Function,+,mf_classic_dict_is_key_present,_Bool,"MfClassicDict*, uint8_t*" Function,-,mf_classic_dict_is_key_present_str,_Bool,"MfClassicDict*, FuriString*" Function,-,mf_classic_dict_rewind,_Bool,MfClassicDict* Function,-,mf_classic_emulator,_Bool,"MfClassicEmulator*, FuriHalNfcTxRxContext*, _Bool" Function,-,mf_classic_get_classic_type,MfClassicType,"uint8_t, uint8_t, uint8_t" -Function,-,mf_classic_get_read_sectors_and_keys,void,"MfClassicData*, uint8_t*, uint8_t*" -Function,-,mf_classic_get_sector_by_block,uint8_t,uint8_t +Function,+,mf_classic_get_read_sectors_and_keys,void,"MfClassicData*, uint8_t*, uint8_t*" +Function,+,mf_classic_get_sector_by_block,uint8_t,uint8_t Function,-,mf_classic_get_sector_trailer_block_num_by_sector,uint8_t,uint8_t -Function,-,mf_classic_get_sector_trailer_by_sector,MfClassicSectorTrailer*,"MfClassicData*, uint8_t" +Function,+,mf_classic_get_sector_trailer_by_sector,MfClassicSectorTrailer*,"MfClassicData*, uint8_t" Function,-,mf_classic_get_total_block_num,uint16_t,MfClassicType -Function,-,mf_classic_get_total_sectors_num,uint8_t,MfClassicType +Function,+,mf_classic_get_total_sectors_num,uint8_t,MfClassicType Function,-,mf_classic_get_type_str,const char*,MfClassicType Function,-,mf_classic_halt,void,"FuriHalNfcTxRxContext*, Crypto1*" Function,-,mf_classic_is_allowed_access_data_block,_Bool,"MfClassicData*, uint8_t, MfClassicKey, MfClassicAction" Function,-,mf_classic_is_allowed_access_sector_trailer,_Bool,"MfClassicData*, uint8_t, MfClassicKey, MfClassicAction" -Function,-,mf_classic_is_block_read,_Bool,"MfClassicData*, uint8_t" -Function,-,mf_classic_is_card_read,_Bool,MfClassicData* -Function,-,mf_classic_is_key_found,_Bool,"MfClassicData*, uint8_t, MfClassicKey" +Function,+,mf_classic_is_block_read,_Bool,"MfClassicData*, uint8_t" +Function,+,mf_classic_is_card_read,_Bool,MfClassicData* +Function,+,mf_classic_is_key_found,_Bool,"MfClassicData*, uint8_t, MfClassicKey" Function,-,mf_classic_is_sector_data_read,_Bool,"MfClassicData*, uint8_t" Function,-,mf_classic_is_sector_read,_Bool,"MfClassicData*, uint8_t" -Function,-,mf_classic_is_sector_trailer,_Bool,uint8_t +Function,+,mf_classic_is_sector_trailer,_Bool,uint8_t Function,-,mf_classic_is_value_block,_Bool,"MfClassicData*, uint8_t" Function,-,mf_classic_read_block,_Bool,"FuriHalNfcTxRxContext*, Crypto1*, uint8_t, MfClassicBlock*" Function,-,mf_classic_read_card,uint8_t,"FuriHalNfcTxRxContext*, MfClassicReader*, MfClassicData*" @@ -1986,10 +1991,10 @@ Function,-,mf_classic_value_to_block,void,"int32_t, uint8_t, uint8_t*" Function,-,mf_classic_write_block,_Bool,"FuriHalNfcTxRxContext*, Crypto1*, uint8_t, MfClassicBlock*" Function,-,mf_classic_write_sector,_Bool,"FuriHalNfcTxRxContext*, MfClassicData*, MfClassicData*, uint8_t" Function,-,mf_df_cat_application,void,"MifareDesfireApplication*, FuriString*" -Function,-,mf_df_cat_application_info,void,"MifareDesfireApplication*, FuriString*" -Function,-,mf_df_cat_card_info,void,"MifareDesfireData*, FuriString*" +Function,+,mf_df_cat_application_info,void,"MifareDesfireApplication*, FuriString*" +Function,+,mf_df_cat_card_info,void,"MifareDesfireData*, FuriString*" Function,-,mf_df_cat_data,void,"MifareDesfireData*, FuriString*" -Function,-,mf_df_cat_file,void,"MifareDesfireFile*, FuriString*" +Function,+,mf_df_cat_file,void,"MifareDesfireFile*, FuriString*" Function,-,mf_df_cat_free_mem,void,"MifareDesfireFreeMemory*, FuriString*" Function,-,mf_df_cat_key_settings,void,"MifareDesfireKeySettings*, FuriString*" Function,-,mf_df_cat_version,void,"MifareDesfireVersion*, FuriString*" @@ -2019,8 +2024,8 @@ Function,-,mf_df_prepare_read_records,uint16_t,"uint8_t*, uint8_t, uint32_t, uin Function,-,mf_df_prepare_select_application,uint16_t,"uint8_t*, uint8_t[3]" Function,-,mf_df_read_card,_Bool,"FuriHalNfcTxRxContext*, MifareDesfireData*" Function,-,mf_ul_check_card_type,_Bool,"uint8_t, uint8_t, uint8_t" -Function,-,mf_ul_emulation_supported,_Bool,MfUltralightData* -Function,-,mf_ul_is_full_capture,_Bool,MfUltralightData* +Function,+,mf_ul_emulation_supported,_Bool,MfUltralightData* +Function,+,mf_ul_is_full_capture,_Bool,MfUltralightData* Function,-,mf_ul_prepare_emulation,void,"MfUltralightEmulator*, MfUltralightData*" Function,-,mf_ul_prepare_emulation_response,_Bool,"uint8_t*, uint16_t, uint8_t*, uint16_t*, uint32_t*, void*" Function,-,mf_ul_pwdgen_amiibo,uint32_t,FuriHalNfcDevData* @@ -2030,13 +2035,18 @@ Function,-,mf_ul_reset,void,MfUltralightData* Function,-,mf_ul_reset_emulation,void,"MfUltralightEmulator*, _Bool" Function,-,mf_ultralight_authenticate,_Bool,"FuriHalNfcTxRxContext*, uint32_t, uint16_t*" Function,-,mf_ultralight_fast_read_pages,_Bool,"FuriHalNfcTxRxContext*, MfUltralightReader*, MfUltralightData*" -Function,-,mf_ultralight_get_config_pages,MfUltralightConfigPages*,MfUltralightData* +Function,+,mf_ultralight_get_config_pages,MfUltralightConfigPages*,MfUltralightData* Function,-,mf_ultralight_read_counters,_Bool,"FuriHalNfcTxRxContext*, MfUltralightData*" Function,-,mf_ultralight_read_pages,_Bool,"FuriHalNfcTxRxContext*, MfUltralightReader*, MfUltralightData*" Function,-,mf_ultralight_read_pages_direct,_Bool,"FuriHalNfcTxRxContext*, uint8_t, uint8_t*" Function,-,mf_ultralight_read_signature,_Bool,"FuriHalNfcTxRxContext*, MfUltralightData*" Function,-,mf_ultralight_read_tearing_flags,_Bool,"FuriHalNfcTxRxContext*, MfUltralightData*" Function,-,mf_ultralight_read_version,_Bool,"FuriHalNfcTxRxContext*, MfUltralightReader*, MfUltralightData*" +Function,-,mfkey32_alloc,Mfkey32*,uint32_t +Function,-,mfkey32_free,void,Mfkey32* +Function,+,mfkey32_get_auth_sectors,uint16_t,FuriString* +Function,-,mfkey32_process_data,void,"Mfkey32*, uint8_t*, uint16_t, _Bool, _Bool" +Function,-,mfkey32_set_callback,void,"Mfkey32*, Mfkey32ParseDataCallback, void*" Function,-,mkdtemp,char*,char* Function,-,mkostemp,int,"char*, int" Function,-,mkostemps,int,"char*, int, int" @@ -2085,11 +2095,25 @@ Function,+,nfc_device_save_shadow,_Bool,"NfcDevice*, const char*" Function,+,nfc_device_set_loading_callback,void,"NfcDevice*, NfcLoadingCallback, void*" Function,+,nfc_device_set_name,void,"NfcDevice*, const char*" Function,+,nfc_file_select,_Bool,NfcDevice* +Function,-,nfc_generate_mf_classic,void,"NfcDeviceData*, uint8_t, MfClassicType" +Function,+,nfc_get_dev_type,const char*,FuriHalNfcType +Function,-,nfc_guess_protocol,const char*,NfcProtocol +Function,+,nfc_mf_classic_type,const char*,MfClassicType +Function,+,nfc_mf_ul_type,const char*,"MfUltralightType, _Bool" +Function,+,nfc_supported_card_verify_and_parse,_Bool,NfcDeviceData* Function,+,nfc_util_bytes2num,uint64_t,"const uint8_t*, uint8_t" Function,+,nfc_util_even_parity32,uint8_t,uint32_t Function,+,nfc_util_num2bytes,void,"uint64_t, uint8_t, uint8_t*" Function,+,nfc_util_odd_parity,void,"const uint8_t*, uint8_t*, uint8_t" Function,+,nfc_util_odd_parity8,uint8_t,uint8_t +Function,+,nfc_worker_alloc,NfcWorker*, +Function,+,nfc_worker_free,void,NfcWorker* +Function,+,nfc_worker_get_state,NfcWorkerState,NfcWorker* +Function,-,nfc_worker_nfcv_emulate,void,NfcWorker* +Function,-,nfc_worker_nfcv_sniff,void,NfcWorker* +Function,-,nfc_worker_nfcv_unlock,void,NfcWorker* +Function,+,nfc_worker_start,void,"NfcWorker*, NfcWorkerState, NfcDeviceData*, NfcWorkerCallback, void*" +Function,+,nfc_worker_stop,void,NfcWorker* Function,-,nfca_append_crc16,void,"uint8_t*, uint16_t" Function,-,nfca_emulation_handler,_Bool,"uint8_t*, uint16_t, uint8_t*, uint16_t*" Function,-,nfca_get_crc16,uint16_t,"uint8_t*, uint16_t" @@ -2666,6 +2690,7 @@ Function,-,strupr,char*,char* Function,-,strverscmp,int,"const char*, const char*" Function,-,strxfrm,size_t,"char*, const char*, size_t" Function,-,strxfrm_l,size_t,"char*, const char*, size_t, locale_t" +Function,-,stub_parser_verify_read,_Bool,"NfcWorker*, FuriHalNfcTxRxContext*" Function,+,subghz_block_generic_deserialize,SubGhzProtocolStatus,"SubGhzBlockGeneric*, FlipperFormat*" Function,+,subghz_block_generic_deserialize_check_count_bit,SubGhzProtocolStatus,"SubGhzBlockGeneric*, FlipperFormat*, uint16_t" Function,+,subghz_block_generic_get_preset_name,void,"const char*, FuriString*" @@ -3358,6 +3383,8 @@ Variable,+,message_red_255,const NotificationMessage, Variable,+,message_sound_off,const NotificationMessage, Variable,+,message_vibro_off,const NotificationMessage, Variable,+,message_vibro_on,const NotificationMessage, +Variable,+,nfc_generators,const NfcGenerator*[], +Variable,-,nfc_supported_card,NfcSupportedCard[NfcSupportedCardTypeEnd], Variable,+,sequence_audiovisual_alert,const NotificationSequence, Variable,+,sequence_blink_blue_10,const NotificationSequence, Variable,+,sequence_blink_blue_100,const NotificationSequence, diff --git a/lib/nfc/SConscript b/lib/nfc/SConscript index b8551db8427..7a0859ee462 100644 --- a/lib/nfc/SConscript +++ b/lib/nfc/SConscript @@ -6,6 +6,11 @@ env.Append( ], SDK_HEADERS=[ File("nfc_device.h"), + File("nfc_worker.h"), + File("nfc_types.h"), + File("helpers/mfkey32.h"), + File("parsers/nfc_supported_card.h"), + File("helpers/nfc_generators.h"), File("protocols/nfc_util.h"), ], ) diff --git a/lib/nfc/helpers/mf_classic_dict.h b/lib/nfc/helpers/mf_classic_dict.h index 3b2d560ad1d..b798b1c92e9 100644 --- a/lib/nfc/helpers/mf_classic_dict.h +++ b/lib/nfc/helpers/mf_classic_dict.h @@ -6,6 +6,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + typedef enum { MfClassicDictTypeUser, MfClassicDictTypeSystem, @@ -97,3 +101,7 @@ bool mf_classic_dict_find_index_str(MfClassicDict* dict, FuriString* key, uint32 * @return true on success */ bool mf_classic_dict_delete_index(MfClassicDict* dict, uint32_t target); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/nfc/helpers/mfkey32.h b/lib/nfc/helpers/mfkey32.h index e1f472e50c8..e2904322407 100644 --- a/lib/nfc/helpers/mfkey32.h +++ b/lib/nfc/helpers/mfkey32.h @@ -2,6 +2,10 @@ #include +#ifdef __cplusplus +extern "C" { +#endif + typedef struct Mfkey32 Mfkey32; typedef enum { @@ -24,3 +28,7 @@ void mfkey32_process_data( void mfkey32_set_callback(Mfkey32* instance, Mfkey32ParseDataCallback callback, void* context); uint16_t mfkey32_get_auth_sectors(FuriString* string); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/nfc/nfc_types.h b/lib/nfc/nfc_types.h index fb53ce7c25b..5ebb2d27bc4 100644 --- a/lib/nfc/nfc_types.h +++ b/lib/nfc/nfc_types.h @@ -2,6 +2,10 @@ #include "nfc_device.h" +#ifdef __cplusplus +extern "C" { +#endif + const char* nfc_get_dev_type(FuriHalNfcType type); const char* nfc_guess_protocol(NfcProtocol protocol); @@ -9,3 +13,7 @@ const char* nfc_guess_protocol(NfcProtocol protocol); const char* nfc_mf_ul_type(MfUltralightType type, bool full_name); const char* nfc_mf_classic_type(MfClassicType type); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/nfc/nfc_worker.h b/lib/nfc/nfc_worker.h index 722f148574f..f9f5900bbde 100644 --- a/lib/nfc/nfc_worker.h +++ b/lib/nfc/nfc_worker.h @@ -2,6 +2,10 @@ #include "nfc_device.h" +#ifdef __cplusplus +extern "C" { +#endif + typedef struct NfcWorker NfcWorker; typedef enum { @@ -97,4 +101,8 @@ void nfc_worker_start( void nfc_worker_stop(NfcWorker* nfc_worker); void nfc_worker_nfcv_unlock(NfcWorker* nfc_worker); void nfc_worker_nfcv_emulate(NfcWorker* nfc_worker); -void nfc_worker_nfcv_sniff(NfcWorker* nfc_worker); \ No newline at end of file +void nfc_worker_nfcv_sniff(NfcWorker* nfc_worker); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/nfc/parsers/nfc_supported_card.h b/lib/nfc/parsers/nfc_supported_card.h index 877bda73745..2e8c48a87a7 100644 --- a/lib/nfc/parsers/nfc_supported_card.h +++ b/lib/nfc/parsers/nfc_supported_card.h @@ -4,6 +4,10 @@ #include "../nfc_worker.h" #include "../nfc_device.h" +#ifdef __cplusplus +extern "C" { +#endif + typedef enum { NfcSupportedCardTypePlantain, NfcSupportedCardTypeTroika, @@ -37,3 +41,7 @@ bool nfc_supported_card_verify_and_parse(NfcDeviceData* dev_data); // support the card. This is needed for DESFire card parsers which can't // provide keys, and only use NfcSupportedCard->parse. bool stub_parser_verify_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/nfc/protocols/crypto1.h b/lib/nfc/protocols/crypto1.h index 450d1534e32..bbf6dc239ce 100644 --- a/lib/nfc/protocols/crypto1.h +++ b/lib/nfc/protocols/crypto1.h @@ -3,6 +3,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + typedef struct { uint32_t odd; uint32_t even; @@ -35,3 +39,7 @@ void crypto1_encrypt( uint16_t plain_data_bits, uint8_t* encrypted_data, uint8_t* encrypted_parity); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/nfc/protocols/mifare_classic.h b/lib/nfc/protocols/mifare_classic.h index b3e3cbdf80d..7efe81b8c59 100644 --- a/lib/nfc/protocols/mifare_classic.h +++ b/lib/nfc/protocols/mifare_classic.h @@ -4,6 +4,10 @@ #include "crypto1.h" +#ifdef __cplusplus +extern "C" { +#endif + #define MF_CLASSIC_BLOCK_SIZE (16) #define MF_CLASSIC_TOTAL_BLOCKS_MAX (256) #define MF_MINI_TOTAL_SECTORS_NUM (5) @@ -241,3 +245,7 @@ bool mf_classic_write_sector( MfClassicData* dest_data, MfClassicData* src_data, uint8_t sec_num); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/nfc/protocols/mifare_desfire.h b/lib/nfc/protocols/mifare_desfire.h index 3dc7c4c2a7c..8faa98ec246 100644 --- a/lib/nfc/protocols/mifare_desfire.h +++ b/lib/nfc/protocols/mifare_desfire.h @@ -5,6 +5,10 @@ #include +#ifdef __cplusplus +extern "C" { +#endif + #define MF_DF_GET_VERSION (0x60) #define MF_DF_GET_FREE_MEMORY (0x6E) #define MF_DF_GET_KEY_SETTINGS (0x45) @@ -169,3 +173,7 @@ uint16_t mf_df_prepare_read_records(uint8_t* dest, uint8_t file_id, uint32_t off bool mf_df_parse_read_data_response(uint8_t* buf, uint16_t len, MifareDesfireFile* out); bool mf_df_read_card(FuriHalNfcTxRxContext* tx_rx, MifareDesfireData* data); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/nfc/protocols/mifare_ultralight.h b/lib/nfc/protocols/mifare_ultralight.h index d444fa7983e..9cb7ca5356c 100644 --- a/lib/nfc/protocols/mifare_ultralight.h +++ b/lib/nfc/protocols/mifare_ultralight.h @@ -2,6 +2,10 @@ #include +#ifdef __cplusplus +extern "C" { +#endif + // Largest tag is NTAG I2C Plus 2K, both data sectors plus SRAM #define MF_UL_MAX_DUMP_SIZE ((238 + 256 + 16) * 4) @@ -259,3 +263,7 @@ uint32_t mf_ul_pwdgen_amiibo(FuriHalNfcDevData* data); uint32_t mf_ul_pwdgen_xiaomi(FuriHalNfcDevData* data); bool mf_ul_is_full_capture(MfUltralightData* data); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/scripts/fbt/appmanifest.py b/scripts/fbt/appmanifest.py index 73e5c777079..5b830dda94d 100644 --- a/scripts/fbt/appmanifest.py +++ b/scripts/fbt/appmanifest.py @@ -18,6 +18,7 @@ class FlipperAppType(Enum): SETTINGS = "Settings" STARTUP = "StartupHook" EXTERNAL = "External" + MENUEXTERNAL = "MenuExternal" METAPACKAGE = "Package" PLUGIN = "Plugin" @@ -213,7 +214,7 @@ def __init__( appmgr: AppManager, appnames: List[str], hw_target: str, - message_writer: Callable = None, + message_writer: Callable | None = None, ): self.appmgr = appmgr self.appnames = set(appnames) @@ -367,6 +368,11 @@ class ApplicationsCGenerator: ), } + APP_EXTERNAL_TYPE = ( + "FlipperExternalApplication", + "FLIPPER_EXTERNAL_APPS", + ) + def __init__(self, buildset: AppBuildset, autorun_app: str = ""): self.buildset = buildset self.autorun = autorun_app @@ -387,6 +393,17 @@ def get_app_descr(self, app: FlipperApplication): .icon = {f"&{app.icon}" if app.icon else "NULL"}, .flags = {'|'.join(f"FlipperInternalApplicationFlag{flag}" for flag in app.flags)} }}""" + def get_external_app_descr(self, app: FlipperApplication): + app_path = "/ext/apps" + if app.fap_category: + app_path += f"/{app.fap_category}" + app_path += f"/{app.appid}.fap" + return f""" + {{ + .name = "{app.name}", + .icon = {f"&{app.icon}" if app.icon else "NULL"}, + .path = "{app_path}" }}""" + def generate(self): contents = [ '#include "applications.h"', @@ -418,4 +435,11 @@ def generate(self): ] ) + entry_type, entry_block = self.APP_EXTERNAL_TYPE + external_apps = self.buildset.get_apps_of_type(FlipperAppType.MENUEXTERNAL) + contents.append(f"const {entry_type} {entry_block}[] = {{") + contents.append(",\n".join(map(self.get_external_app_descr, external_apps))) + contents.append("};") + contents.append(f"const size_t {entry_block}_COUNT = COUNT_OF({entry_block});") + return "\n".join(contents) diff --git a/scripts/fbt_tools/fbt_extapps.py b/scripts/fbt_tools/fbt_extapps.py index 69d70021413..a6cd831d40d 100644 --- a/scripts/fbt_tools/fbt_extapps.py +++ b/scripts/fbt_tools/fbt_extapps.py @@ -423,7 +423,10 @@ def _add_host_app_to_targets(host_app): host_app = env["APPMGR"].get(artifacts_app_to_run.app.requires[0]) if host_app: - if host_app.apptype == FlipperAppType.EXTERNAL: + if host_app.apptype in [ + FlipperAppType.EXTERNAL, + FlipperAppType.MENUEXTERNAL, + ]: _add_host_app_to_targets(host_app) else: # host app is a built-in app diff --git a/scripts/ufbt/SConstruct b/scripts/ufbt/SConstruct index 8812a4e5592..703a9187aaa 100644 --- a/scripts/ufbt/SConstruct +++ b/scripts/ufbt/SConstruct @@ -262,6 +262,7 @@ apps_artifacts = appenv["EXT_APPS"] apps_to_build_as_faps = [ FlipperAppType.PLUGIN, FlipperAppType.EXTERNAL, + FlipperAppType.MENUEXTERNAL, ] known_extapps = [ diff --git a/site_scons/extapps.scons b/site_scons/extapps.scons index 6db0e538dfd..0893c455617 100644 --- a/site_scons/extapps.scons +++ b/site_scons/extapps.scons @@ -67,6 +67,7 @@ class FlipperExtAppBuildArtifacts: apps_to_build_as_faps = [ FlipperAppType.PLUGIN, FlipperAppType.EXTERNAL, + FlipperAppType.MENUEXTERNAL, FlipperAppType.DEBUG, ] From 136114890f24f6418c3b1672d8e378902ed4db02 Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Tue, 11 Jul 2023 10:29:45 +0300 Subject: [PATCH 651/824] [FL-3420] Storage: directory sort (#2850) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Storage: sort_data holder in file structure * Storage: sort directory if it possible * make pvs happy * Storage: fail sorting if there is no more space for realloc * Storage: case insensitive sorting. Co-authored-by: あく --- .../storage/filesystem_api_internal.h | 3 +- .../services/storage/storage_processing.c | 180 +++++++++++++++++- .../services/storage/storage_sorting.h | 22 +++ 3 files changed, 194 insertions(+), 11 deletions(-) create mode 100644 applications/services/storage/storage_sorting.h diff --git a/applications/services/storage/filesystem_api_internal.h b/applications/services/storage/filesystem_api_internal.h index 967d3bb41c1..52eb6ef13c5 100644 --- a/applications/services/storage/filesystem_api_internal.h +++ b/applications/services/storage/filesystem_api_internal.h @@ -19,7 +19,8 @@ struct File { FileType type; FS_Error error_id; /**< Standard API error from FS_Error enum */ int32_t internal_error_id; /**< Internal API error value */ - void* storage; + void* storage; /**< Storage API pointer */ + void* sort_data; /**< Sorted file list for directory */ }; /** File api structure diff --git a/applications/services/storage/storage_processing.c b/applications/services/storage/storage_processing.c index e6b42696106..eb745cac4ec 100644 --- a/applications/services/storage/storage_processing.c +++ b/applications/services/storage/storage_processing.c @@ -1,4 +1,5 @@ #include "storage_processing.h" +#include "storage_sorting.h" #include #include @@ -100,7 +101,7 @@ static FS_Error storage_get_data(Storage* app, FuriString* path, StorageData** s /******************* File Functions *******************/ -bool storage_process_file_open( +static bool storage_process_file_open( Storage* app, File* file, FuriString* path, @@ -127,7 +128,7 @@ bool storage_process_file_open( return ret; } -bool storage_process_file_close(Storage* app, File* file) { +static bool storage_process_file_close(Storage* app, File* file) { bool ret = false; StorageData* storage = get_storage_by_file(file, app->storage); @@ -260,9 +261,149 @@ static bool storage_process_file_eof(Storage* app, File* file) { return ret; } +/*************** Sorting Dir Functions ***************/ + +static bool storage_process_dir_rewind_internal(StorageData* storage, File* file); +static bool storage_process_dir_read_internal( + StorageData* storage, + File* file, + FileInfo* fileinfo, + char* name, + const uint16_t name_length); + +static int storage_sorted_file_record_compare(const void* sorted_a, const void* sorted_b) { + SortedFileRecord* a = (SortedFileRecord*)sorted_a; + SortedFileRecord* b = (SortedFileRecord*)sorted_b; + + if(a->info.flags & FSF_DIRECTORY && !(b->info.flags & FSF_DIRECTORY)) + return -1; + else if(!(a->info.flags & FSF_DIRECTORY) && b->info.flags & FSF_DIRECTORY) + return 1; + else + return furi_string_cmpi(a->name, b->name); +} + +static bool storage_sorted_dir_read_next( + SortedDir* dir, + FileInfo* fileinfo, + char* name, + const uint16_t name_length) { + bool ret = false; + + if(dir->index < dir->count) { + SortedFileRecord* sorted = &dir->sorted[dir->index]; + if(fileinfo) { + *fileinfo = sorted->info; + } + if(name) { + strncpy(name, furi_string_get_cstr(sorted->name), name_length); + } + dir->index++; + ret = true; + } + + return ret; +} + +static void storage_sorted_dir_rewind(SortedDir* dir) { + dir->index = 0; +} + +static bool storage_sorted_dir_prepare(SortedDir* dir, StorageData* storage, File* file) { + bool ret = true; + dir->count = 0; + dir->index = 0; + FileInfo info; + char name[SORTING_MAX_NAME_LENGTH + 1] = {0}; + + furi_check(!dir->sorted); + + while(storage_process_dir_read_internal(storage, file, &info, name, SORTING_MAX_NAME_LENGTH)) { + if(memmgr_get_free_heap() < SORTING_MIN_FREE_MEMORY) { + ret = false; + break; + } + + if(dir->count == 0) { //-V547 + dir->sorted = malloc(sizeof(SortedFileRecord)); + } else { + // Our realloc actually mallocs a new block and copies the data over, + // so we need to check if we have enough memory for the new block + size_t size = sizeof(SortedFileRecord) * (dir->count + 1); + if(memmgr_heap_get_max_free_block() >= size) { + dir->sorted = + realloc(dir->sorted, sizeof(SortedFileRecord) * (dir->count + 1)); //-V701 + } else { + ret = false; + break; + } + } + + dir->sorted[dir->count].name = furi_string_alloc_set(name); + dir->sorted[dir->count].info = info; + dir->count++; + } + + return ret; +} + +static void storage_sorted_dir_sort(SortedDir* dir) { + qsort(dir->sorted, dir->count, sizeof(SortedFileRecord), storage_sorted_file_record_compare); +} + +static void storage_sorted_dir_clear_data(SortedDir* dir) { + if(dir->sorted != NULL) { + for(size_t i = 0; i < dir->count; i++) { + furi_string_free(dir->sorted[i].name); + } + + free(dir->sorted); + dir->sorted = NULL; + } +} + +static void storage_file_remove_sort_data(File* file) { + if(file->sort_data != NULL) { + storage_sorted_dir_clear_data(file->sort_data); + free(file->sort_data); + file->sort_data = NULL; + } +} + +static void storage_file_add_sort_data(File* file, StorageData* storage) { + file->sort_data = malloc(sizeof(SortedDir)); + if(storage_sorted_dir_prepare(file->sort_data, storage, file)) { + storage_sorted_dir_sort(file->sort_data); + } else { + storage_file_remove_sort_data(file); + storage_process_dir_rewind_internal(storage, file); + } +} + +static bool storage_file_has_sort_data(File* file) { + return file->sort_data != NULL; +} + /******************* Dir Functions *******************/ -bool storage_process_dir_open(Storage* app, File* file, FuriString* path) { +static bool storage_process_dir_read_internal( + StorageData* storage, + File* file, + FileInfo* fileinfo, + char* name, + const uint16_t name_length) { + bool ret = false; + FS_CALL(storage, dir.read(storage, file, fileinfo, name, name_length)); + return ret; +} + +static bool storage_process_dir_rewind_internal(StorageData* storage, File* file) { + bool ret = false; + FS_CALL(storage, dir.rewind(storage, file)); + return ret; +} + +static bool storage_process_dir_open(Storage* app, File* file, FuriString* path) { bool ret = false; StorageData* storage; file->error_id = storage_get_data(app, path, &storage); @@ -273,13 +414,17 @@ bool storage_process_dir_open(Storage* app, File* file, FuriString* path) { } else { storage_push_storage_file(file, path, storage); FS_CALL(storage, dir.open(storage, file, cstr_path_without_vfs_prefix(path))); + + if(file->error_id == FSE_OK) { + storage_file_add_sort_data(file, storage); + } } } return ret; } -bool storage_process_dir_close(Storage* app, File* file) { +static bool storage_process_dir_close(Storage* app, File* file) { bool ret = false; StorageData* storage = get_storage_by_file(file, app->storage); @@ -287,6 +432,7 @@ bool storage_process_dir_close(Storage* app, File* file) { file->error_id = FSE_INVALID_PARAMETER; } else { FS_CALL(storage, dir.close(storage, file)); + storage_file_remove_sort_data(file); storage_pop_storage_file(file, storage); StorageEvent event = {.type = StorageEventTypeDirClose}; @@ -296,7 +442,7 @@ bool storage_process_dir_close(Storage* app, File* file) { return ret; } -bool storage_process_dir_read( +static bool storage_process_dir_read( Storage* app, File* file, FileInfo* fileinfo, @@ -308,20 +454,34 @@ bool storage_process_dir_read( if(storage == NULL) { file->error_id = FSE_INVALID_PARAMETER; } else { - FS_CALL(storage, dir.read(storage, file, fileinfo, name, name_length)); + if(storage_file_has_sort_data(file)) { + ret = storage_sorted_dir_read_next(file->sort_data, fileinfo, name, name_length); + if(ret) { + file->error_id = FSE_OK; + } else { + file->error_id = FSE_NOT_EXIST; + } + } else { + ret = storage_process_dir_read_internal(storage, file, fileinfo, name, name_length); + } } return ret; } -bool storage_process_dir_rewind(Storage* app, File* file) { +static bool storage_process_dir_rewind(Storage* app, File* file) { bool ret = false; StorageData* storage = get_storage_by_file(file, app->storage); if(storage == NULL) { file->error_id = FSE_INVALID_PARAMETER; } else { - FS_CALL(storage, dir.rewind(storage, file)); + if(storage_file_has_sort_data(file)) { + storage_sorted_dir_rewind(file->sort_data); + ret = true; + } else { + ret = storage_process_dir_rewind_internal(storage, file); + } } return ret; @@ -461,7 +621,7 @@ static FS_Error storage_process_sd_status(Storage* app) { /******************** Aliases processing *******************/ -void storage_process_alias( +static void storage_process_alias( Storage* app, FuriString* path, FuriThreadId thread_id, @@ -505,7 +665,7 @@ void storage_process_alias( /****************** API calls processing ******************/ -void storage_process_message_internal(Storage* app, StorageMessage* message) { +static void storage_process_message_internal(Storage* app, StorageMessage* message) { FuriString* path = NULL; switch(message->command) { diff --git a/applications/services/storage/storage_sorting.h b/applications/services/storage/storage_sorting.h new file mode 100644 index 00000000000..9db9d58bf9f --- /dev/null +++ b/applications/services/storage/storage_sorting.h @@ -0,0 +1,22 @@ +#pragma once +#include + +#define SORTING_MAX_NAME_LENGTH 255 +#define SORTING_MIN_FREE_MEMORY (1024 * 40) + +/** + * @brief Sorted file record, holds file name and info + */ +typedef struct { + FuriString* name; + FileInfo info; +} SortedFileRecord; + +/** + * @brief Sorted directory, holds sorted file records, count and current index + */ +typedef struct { + SortedFileRecord* sorted; + size_t count; + size_t index; +} SortedDir; \ No newline at end of file From 14fc960246a4dccb0a17cdc5fe1254ba5180de29 Mon Sep 17 00:00:00 2001 From: MMX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 11 Jul 2023 11:39:07 +0300 Subject: [PATCH 652/824] Infrared: RCA protocol support (#2823) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * RCA protocol support * Add unit test Co-authored-by: あく --- .../debug/unit_tests/infrared/infrared_test.c | 12 ++ assets/unit_tests/infrared/test_rca.irtest | 105 ++++++++++++++++++ .../file_formats/InfraredFileFormats.md | 17 +++ lib/infrared/encoder_decoder/infrared.c | 15 +++ lib/infrared/encoder_decoder/infrared.h | 1 + .../rca/infrared_decoder_rca.c | 45 ++++++++ .../rca/infrared_encoder_rca.c | 37 ++++++ .../rca/infrared_protocol_rca.c | 40 +++++++ .../rca/infrared_protocol_rca.h | 30 +++++ .../rca/infrared_protocol_rca_i.h | 30 +++++ 10 files changed, 332 insertions(+) create mode 100644 assets/unit_tests/infrared/test_rca.irtest create mode 100644 lib/infrared/encoder_decoder/rca/infrared_decoder_rca.c create mode 100644 lib/infrared/encoder_decoder/rca/infrared_encoder_rca.c create mode 100644 lib/infrared/encoder_decoder/rca/infrared_protocol_rca.c create mode 100644 lib/infrared/encoder_decoder/rca/infrared_protocol_rca.h create mode 100644 lib/infrared/encoder_decoder/rca/infrared_protocol_rca_i.h diff --git a/applications/debug/unit_tests/infrared/infrared_test.c b/applications/debug/unit_tests/infrared/infrared_test.c index 2bcb95da8e5..b2acad470e6 100644 --- a/applications/debug/unit_tests/infrared/infrared_test.c +++ b/applications/debug/unit_tests/infrared/infrared_test.c @@ -425,6 +425,7 @@ MU_TEST(infrared_test_decoder_mixed) { infrared_test_run_decoder(InfraredProtocolSamsung32, 1); infrared_test_run_decoder(InfraredProtocolSIRC, 3); infrared_test_run_decoder(InfraredProtocolKaseikyo, 1); + infrared_test_run_decoder(InfraredProtocolRCA, 1); } MU_TEST(infrared_test_decoder_nec) { @@ -499,6 +500,15 @@ MU_TEST(infrared_test_decoder_kaseikyo) { infrared_test_run_decoder(InfraredProtocolKaseikyo, 6); } +MU_TEST(infrared_test_decoder_rca) { + infrared_test_run_decoder(InfraredProtocolRCA, 1); + infrared_test_run_decoder(InfraredProtocolRCA, 2); + infrared_test_run_decoder(InfraredProtocolRCA, 3); + infrared_test_run_decoder(InfraredProtocolRCA, 4); + infrared_test_run_decoder(InfraredProtocolRCA, 5); + infrared_test_run_decoder(InfraredProtocolRCA, 6); +} + MU_TEST(infrared_test_encoder_decoder_all) { infrared_test_run_encoder_decoder(InfraredProtocolNEC, 1); infrared_test_run_encoder_decoder(InfraredProtocolNECext, 1); @@ -509,6 +519,7 @@ MU_TEST(infrared_test_encoder_decoder_all) { infrared_test_run_encoder_decoder(InfraredProtocolRC5, 1); infrared_test_run_encoder_decoder(InfraredProtocolSIRC, 1); infrared_test_run_encoder_decoder(InfraredProtocolKaseikyo, 1); + infrared_test_run_encoder_decoder(InfraredProtocolRCA, 1); } MU_TEST_SUITE(infrared_test) { @@ -527,6 +538,7 @@ MU_TEST_SUITE(infrared_test) { MU_RUN_TEST(infrared_test_decoder_samsung32); MU_RUN_TEST(infrared_test_decoder_necext1); MU_RUN_TEST(infrared_test_decoder_kaseikyo); + MU_RUN_TEST(infrared_test_decoder_rca); MU_RUN_TEST(infrared_test_decoder_mixed); MU_RUN_TEST(infrared_test_encoder_decoder_all); } diff --git a/assets/unit_tests/infrared/test_rca.irtest b/assets/unit_tests/infrared/test_rca.irtest new file mode 100644 index 00000000000..bfe34e8e41e --- /dev/null +++ b/assets/unit_tests/infrared/test_rca.irtest @@ -0,0 +1,105 @@ +Filetype: IR tests file +Version: 1 +# +name: decoder_input1 +type: raw +data: 1000000 3994 3969 552 1945 551 1945 552 1945 551 1945 552 946 551 947 550 1947 548 951 546 1953 542 979 518 1979 517 981 492 1006 491 1006 492 1006 492 1006 492 2005 492 2005 492 1006 492 2005 492 1006 492 2005 492 1006 492 2006 491 +# +name: decoder_expected1 +type: parsed_array +count: 1 +# +protocol: RCA +address: 0F 00 00 00 +command: 54 00 00 00 +repeat: false +# +name: decoder_input2 +type: raw +data: 1000000 4055 3941 605 1891 551 1947 550 1946 551 1946 551 947 551 947 550 1947 549 949 548 1951 545 1977 519 1978 519 1979 518 980 518 980 518 980 518 980 518 1979 518 1979 518 981 517 1979 518 980 518 980 518 980 518 980 518 +# +name: decoder_expected2 +type: parsed_array +count: 1 +# +protocol: RCA +address: 0F 00 00 00 +command: F4 00 00 00 +repeat: false +# +name: decoder_input3 +type: raw +data: 1000000 4027 3970 551 1946 550 1946 551 1946 551 1946 551 946 551 947 550 1947 549 949 547 1951 545 1978 518 1979 492 1006 492 1007 491 1006 492 1006 492 1006 492 2006 491 2006 491 1006 492 2006 491 1007 491 1007 491 1006 492 2006 491 +# +name: decoder_expected3 +type: parsed_array +count: 1 +# +protocol: RCA +address: 0F 00 00 00 +command: 74 00 00 00 +repeat: false +# +name: decoder_input4 +type: raw +data: 1000000 4021 3941 551 1946 550 1946 551 1946 551 1945 552 946 551 947 550 1947 549 950 547 1952 544 1977 519 979 519 1979 518 980 518 980 518 980 518 980 518 1979 518 1979 518 980 518 1979 518 980 518 980 518 1979 518 980 518 +# +name: decoder_expected4 +type: parsed_array +count: 1 +# +protocol: RCA +address: 0F 00 00 00 +command: B4 00 00 00 +repeat: false +# +name: decoder_input5 +type: raw +data: 1000000 4022 3941 551 1946 551 1946 577 1919 578 1919 578 920 552 946 551 1946 550 947 550 1949 547 1952 544 978 520 979 519 980 518 980 518 980 518 980 518 1979 518 1979 518 980 518 1979 518 980 518 980 518 1979 518 1980 517 +# +name: decoder_expected5 +type: parsed_array +count: 1 +# +protocol: RCA +address: 0F 00 00 00 +command: 34 00 00 00 +repeat: false +# +name: decoder_input6 +type: raw +data: 1000000 3995 3968 552 1944 552 1946 550 1946 550 1946 551 947 550 948 549 1947 549 1949 547 1952 544 1978 518 1979 492 2005 492 1006 492 1006 492 1006 492 1006 492 2005 492 2005 492 1006 492 1006 492 1006 492 1006 492 1006 492 1006 492 +# +name: decoder_expected6 +type: parsed_array +count: 1 +# +protocol: RCA +address: 0F 00 00 00 +command: FC 00 00 00 +repeat: false +# +name: encoder_decoder_input1 +type: parsed_array +count: 4 +# +protocol: RCA +address: 0F 00 00 00 +command: 74 00 00 00 +repeat: false +# +protocol: RCA +address: 0F 00 00 00 +command: B4 00 00 00 +repeat: false +# +protocol: RCA +address: 0F 00 00 00 +command: 34 00 00 00 +repeat: false +# +protocol: RCA +address: 0F 00 00 00 +command: FC 00 00 00 +repeat: false +# diff --git a/documentation/file_formats/InfraredFileFormats.md b/documentation/file_formats/InfraredFileFormats.md index 3c0acdcb738..c9b6a953655 100644 --- a/documentation/file_formats/InfraredFileFormats.md +++ b/documentation/file_formats/InfraredFileFormats.md @@ -1,5 +1,22 @@ # Infrared Flipper File Formats + +## Supported protocols list for `type: parsed` +``` + NEC + NECext + NEC42 + NEC42ext + Samsung32 + RC6 + RC5 + RC5X + SIRC + SIRC15 + SIRC20 + Kaseikyo + RCA +``` ## Infrared Remote File Format ### Example diff --git a/lib/infrared/encoder_decoder/infrared.c b/lib/infrared/encoder_decoder/infrared.c index fcfc5da2b22..56f2c3f9ee6 100644 --- a/lib/infrared/encoder_decoder/infrared.c +++ b/lib/infrared/encoder_decoder/infrared.c @@ -11,6 +11,7 @@ #include "rc6/infrared_protocol_rc6.h" #include "sirc/infrared_protocol_sirc.h" #include "kaseikyo/infrared_protocol_kaseikyo.h" +#include "rca/infrared_protocol_rca.h" typedef struct { InfraredAlloc alloc; @@ -127,6 +128,20 @@ static const InfraredEncoderDecoder infrared_encoder_decoder[] = { .free = infrared_encoder_kaseikyo_free}, .get_protocol_variant = infrared_protocol_kaseikyo_get_variant, }, + { + .decoder = + {.alloc = infrared_decoder_rca_alloc, + .decode = infrared_decoder_rca_decode, + .reset = infrared_decoder_rca_reset, + .check_ready = infrared_decoder_rca_check_ready, + .free = infrared_decoder_rca_free}, + .encoder = + {.alloc = infrared_encoder_rca_alloc, + .encode = infrared_encoder_rca_encode, + .reset = infrared_encoder_rca_reset, + .free = infrared_encoder_rca_free}, + .get_protocol_variant = infrared_protocol_rca_get_variant, + }, }; static int infrared_find_index_by_protocol(InfraredProtocol protocol); diff --git a/lib/infrared/encoder_decoder/infrared.h b/lib/infrared/encoder_decoder/infrared.h index 3ab46cbbf56..ada449b9831 100644 --- a/lib/infrared/encoder_decoder/infrared.h +++ b/lib/infrared/encoder_decoder/infrared.h @@ -33,6 +33,7 @@ typedef enum { InfraredProtocolSIRC15, InfraredProtocolSIRC20, InfraredProtocolKaseikyo, + InfraredProtocolRCA, InfraredProtocolMAX, } InfraredProtocol; diff --git a/lib/infrared/encoder_decoder/rca/infrared_decoder_rca.c b/lib/infrared/encoder_decoder/rca/infrared_decoder_rca.c new file mode 100644 index 00000000000..b6d02a38c77 --- /dev/null +++ b/lib/infrared/encoder_decoder/rca/infrared_decoder_rca.c @@ -0,0 +1,45 @@ +#include "infrared_protocol_rca_i.h" +#include + +InfraredMessage* infrared_decoder_rca_check_ready(void* ctx) { + return infrared_common_decoder_check_ready(ctx); +} + +bool infrared_decoder_rca_interpret(InfraredCommonDecoder* decoder) { + furi_assert(decoder); + + uint32_t* data = (void*)&decoder->data; + + uint8_t address = (*data & 0xF); + uint8_t command = (*data >> 4) & 0xFF; + uint8_t address_inverse = (*data >> 12) & 0xF; + uint8_t command_inverse = (*data >> 16) & 0xFF; + uint8_t inverse_address_inverse = (uint8_t)~address_inverse & 0xF; + uint8_t inverse_command_inverse = (uint8_t)~command_inverse; + + if((command == inverse_command_inverse) && (address == inverse_address_inverse)) { + decoder->message.protocol = InfraredProtocolRCA; + decoder->message.address = address; + decoder->message.command = command; + decoder->message.repeat = false; + return true; + } + + return false; +} + +void* infrared_decoder_rca_alloc(void) { + return infrared_common_decoder_alloc(&infrared_protocol_rca); +} + +InfraredMessage* infrared_decoder_rca_decode(void* decoder, bool level, uint32_t duration) { + return infrared_common_decode(decoder, level, duration); +} + +void infrared_decoder_rca_free(void* decoder) { + infrared_common_decoder_free(decoder); +} + +void infrared_decoder_rca_reset(void* decoder) { + infrared_common_decoder_reset(decoder); +} diff --git a/lib/infrared/encoder_decoder/rca/infrared_encoder_rca.c b/lib/infrared/encoder_decoder/rca/infrared_encoder_rca.c new file mode 100644 index 00000000000..f0be4a6a9e2 --- /dev/null +++ b/lib/infrared/encoder_decoder/rca/infrared_encoder_rca.c @@ -0,0 +1,37 @@ +#include "infrared_protocol_rca_i.h" + +#include + +void infrared_encoder_rca_reset(void* encoder_ptr, const InfraredMessage* message) { + furi_assert(encoder_ptr); + furi_assert(message); + + InfraredCommonEncoder* encoder = encoder_ptr; + infrared_common_encoder_reset(encoder); + + uint32_t* data = (void*)encoder->data; + + uint8_t address = message->address; + uint8_t address_inverse = ~address; + uint8_t command = message->command; + uint8_t command_inverse = ~command; + + *data = address & 0xF; + *data |= command << 4; + *data |= (address_inverse & 0xF) << 12; + *data |= command_inverse << 16; + + encoder->bits_to_encode = encoder->protocol->databit_len[0]; +} + +void* infrared_encoder_rca_alloc(void) { + return infrared_common_encoder_alloc(&infrared_protocol_rca); +} + +void infrared_encoder_rca_free(void* encoder_ptr) { + infrared_common_encoder_free(encoder_ptr); +} + +InfraredStatus infrared_encoder_rca_encode(void* encoder_ptr, uint32_t* duration, bool* level) { + return infrared_common_encode(encoder_ptr, duration, level); +} diff --git a/lib/infrared/encoder_decoder/rca/infrared_protocol_rca.c b/lib/infrared/encoder_decoder/rca/infrared_protocol_rca.c new file mode 100644 index 00000000000..8e1e76dbd37 --- /dev/null +++ b/lib/infrared/encoder_decoder/rca/infrared_protocol_rca.c @@ -0,0 +1,40 @@ +#include "infrared_protocol_rca_i.h" + +const InfraredCommonProtocolSpec infrared_protocol_rca = { + .timings = + { + .preamble_mark = INFRARED_RCA_PREAMBLE_MARK, + .preamble_space = INFRARED_RCA_PREAMBLE_SPACE, + .bit1_mark = INFRARED_RCA_BIT1_MARK, + .bit1_space = INFRARED_RCA_BIT1_SPACE, + .bit0_mark = INFRARED_RCA_BIT0_MARK, + .bit0_space = INFRARED_RCA_BIT0_SPACE, + .preamble_tolerance = INFRARED_RCA_PREAMBLE_TOLERANCE, + .bit_tolerance = INFRARED_RCA_BIT_TOLERANCE, + .silence_time = INFRARED_RCA_SILENCE, + .min_split_time = INFRARED_RCA_MIN_SPLIT_TIME, + }, + .databit_len[0] = 24, + .no_stop_bit = false, + .decode = infrared_common_decode_pdwm, + .encode = infrared_common_encode_pdwm, + .interpret = infrared_decoder_rca_interpret, + .decode_repeat = NULL, + .encode_repeat = NULL, +}; + +static const InfraredProtocolVariant infrared_protocol_variant_rca = { + .name = "RCA", + .address_length = 4, + .command_length = 8, + .frequency = INFRARED_COMMON_CARRIER_FREQUENCY, + .duty_cycle = INFRARED_COMMON_DUTY_CYCLE, + .repeat_count = INFRARED_RCA_REPEAT_COUNT_MIN, +}; + +const InfraredProtocolVariant* infrared_protocol_rca_get_variant(InfraredProtocol protocol) { + if(protocol == InfraredProtocolRCA) + return &infrared_protocol_variant_rca; + else + return NULL; +} diff --git a/lib/infrared/encoder_decoder/rca/infrared_protocol_rca.h b/lib/infrared/encoder_decoder/rca/infrared_protocol_rca.h new file mode 100644 index 00000000000..d9cae48e48d --- /dev/null +++ b/lib/infrared/encoder_decoder/rca/infrared_protocol_rca.h @@ -0,0 +1,30 @@ +#pragma once + +#include "../infrared_i.h" + +/*************************************************************************************************** +* RCA protocol description +* https://www.sbprojects.net/knowledge/ir/rca.php +**************************************************************************************************** +* Preamble Preamble Pulse Distance/Width Pause Preamble Preamble +* mark space Modulation up to period repeat repeat +* mark space +* +* 4000 4000 24 bit ...8000 4000 4000 +* __________ _ _ _ _ _ _ _ _ _ _ _ _ _ ___________ +* ____ __________ _ _ _ __ __ __ _ _ __ __ _ _ ________________ ___________ +* +***************************************************************************************************/ + +void* infrared_decoder_rca_alloc(void); +void infrared_decoder_rca_reset(void* decoder); +void infrared_decoder_rca_free(void* decoder); +InfraredMessage* infrared_decoder_rca_check_ready(void* decoder); +InfraredMessage* infrared_decoder_rca_decode(void* decoder, bool level, uint32_t duration); + +void* infrared_encoder_rca_alloc(void); +InfraredStatus infrared_encoder_rca_encode(void* encoder_ptr, uint32_t* duration, bool* level); +void infrared_encoder_rca_reset(void* encoder_ptr, const InfraredMessage* message); +void infrared_encoder_rca_free(void* encoder_ptr); + +const InfraredProtocolVariant* infrared_protocol_rca_get_variant(InfraredProtocol protocol); diff --git a/lib/infrared/encoder_decoder/rca/infrared_protocol_rca_i.h b/lib/infrared/encoder_decoder/rca/infrared_protocol_rca_i.h new file mode 100644 index 00000000000..9ec4fe3b179 --- /dev/null +++ b/lib/infrared/encoder_decoder/rca/infrared_protocol_rca_i.h @@ -0,0 +1,30 @@ +#pragma once + +#include "../common/infrared_common_i.h" + +#define INFRARED_RCA_PREAMBLE_MARK 4000 +#define INFRARED_RCA_PREAMBLE_SPACE 4000 +#define INFRARED_RCA_BIT1_MARK 500 +#define INFRARED_RCA_BIT1_SPACE 2000 +#define INFRARED_RCA_BIT0_MARK 500 +#define INFRARED_RCA_BIT0_SPACE 1000 +#define INFRARED_RCA_REPEAT_PERIOD 8000 +#define INFRARED_RCA_SILENCE INFRARED_RCA_REPEAT_PERIOD + +#define INFRARED_RCA_MIN_SPLIT_TIME INFRARED_RCA_REPEAT_PAUSE_MIN +#define INFRARED_RCA_REPEAT_PAUSE_MIN 4000 +#define INFRARED_RCA_REPEAT_PAUSE_MAX 150000 +#define INFRARED_RCA_REPEAT_COUNT_MIN 1 +#define INFRARED_RCA_REPEAT_MARK INFRARED_RCA_PREAMBLE_MARK +#define INFRARED_RCA_REPEAT_SPACE INFRARED_RCA_PREAMBLE_SPACE +#define INFRARED_RCA_PREAMBLE_TOLERANCE 200 // us +#define INFRARED_RCA_BIT_TOLERANCE 120 // us + +extern const InfraredCommonProtocolSpec infrared_protocol_rca; + +bool infrared_decoder_rca_interpret(InfraredCommonDecoder* decoder); +InfraredStatus infrared_decoder_rca_decode_repeat(InfraredCommonDecoder* decoder); +InfraredStatus infrared_encoder_rca_encode_repeat( + InfraredCommonEncoder* encoder, + uint32_t* duration, + bool* level); From 8bccfd6fd8010699a80208121392cc4ccf5ad0d2 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Tue, 11 Jul 2023 14:41:16 +0300 Subject: [PATCH 653/824] [FL-3363] More descriptive error messages for the log command (#2835) * More descriptive error messages for the log command * Log level description improvements * Log help changes Co-authored-by: Aleksandr Kutuzov --- applications/services/cli/cli_commands.c | 41 ++++++++++++++---------- firmware/targets/f18/api_symbols.csv | 4 ++- firmware/targets/f7/api_symbols.csv | 4 ++- furi/core/log.c | 35 ++++++++++++++++++++ furi/core/log.h | 18 +++++++++++ 5 files changed, 83 insertions(+), 19 deletions(-) diff --git a/applications/services/cli/cli_commands.c b/applications/services/cli/cli_commands.c index 3f94deebcfb..7009e7531c0 100644 --- a/applications/services/cli/cli_commands.c +++ b/applications/services/cli/cli_commands.c @@ -165,24 +165,23 @@ void cli_command_log_tx_callback(const uint8_t* buffer, size_t size, void* conte furi_stream_buffer_send(context, buffer, size, 0); } -void cli_command_log_level_set_from_string(FuriString* level) { - if(furi_string_cmpi_str(level, "default") == 0) { - furi_log_set_level(FuriLogLevelDefault); - } else if(furi_string_cmpi_str(level, "none") == 0) { - furi_log_set_level(FuriLogLevelNone); - } else if(furi_string_cmpi_str(level, "error") == 0) { - furi_log_set_level(FuriLogLevelError); - } else if(furi_string_cmpi_str(level, "warn") == 0) { - furi_log_set_level(FuriLogLevelWarn); - } else if(furi_string_cmpi_str(level, "info") == 0) { - furi_log_set_level(FuriLogLevelInfo); - } else if(furi_string_cmpi_str(level, "debug") == 0) { - furi_log_set_level(FuriLogLevelDebug); - } else if(furi_string_cmpi_str(level, "trace") == 0) { - furi_log_set_level(FuriLogLevelTrace); +bool cli_command_log_level_set_from_string(FuriString* level) { + FuriLogLevel log_level; + if(furi_log_level_from_string(furi_string_get_cstr(level), &log_level)) { + furi_log_set_level(log_level); + return true; } else { - printf("Unknown log level\r\n"); + printf(" — start logging using the current level from the system settings\r\n"); + printf(" — only critical errors and other important messages\r\n"); + printf(" — non-critical errors and warnings including \r\n"); + printf(" — non-critical information including \r\n"); + printf(" — the default system log level (equivalent to )\r\n"); + printf( + " — debug information including (may impact system performance)\r\n"); + printf( + " — system traces including (may impact system performance)\r\n"); } + return false; } void cli_command_log(Cli* cli, FuriString* args, void* context) { @@ -193,12 +192,20 @@ void cli_command_log(Cli* cli, FuriString* args, void* context) { bool restore_log_level = false; if(furi_string_size(args) > 0) { - cli_command_log_level_set_from_string(args); + if(!cli_command_log_level_set_from_string(args)) { + furi_stream_buffer_free(ring); + return; + } restore_log_level = true; } + const char* current_level; + furi_log_level_to_string(furi_log_get_level(), ¤t_level); + printf("Current log level: %s\r\n", current_level); + furi_hal_console_set_tx_callback(cli_command_log_tx_callback, ring); + printf("Use to list available log levels\r\n"); printf("Press CTRL+C to stop...\r\n"); while(!cli_cmd_interrupt_received(cli)) { size_t ret = furi_stream_buffer_receive(ring, buffer, CLI_COMMAND_LOG_BUFFER_SIZE, 50); diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index 0149701136e..bbeaa3b1a1f 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,34.0,, +Version,+,34.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -1281,6 +1281,8 @@ Function,+,furi_kernel_restore_lock,int32_t,int32_t Function,+,furi_kernel_unlock,int32_t, Function,+,furi_log_get_level,FuriLogLevel, Function,-,furi_log_init,void, +Function,+,furi_log_level_from_string,_Bool,"const char*, FuriLogLevel*" +Function,+,furi_log_level_to_string,_Bool,"FuriLogLevel, const char**" Function,+,furi_log_print_format,void,"FuriLogLevel, const char*, const char*, ..." Function,+,furi_log_print_raw_format,void,"FuriLogLevel, const char*, ..." Function,+,furi_log_set_level,void,FuriLogLevel diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 2e02608a37d..0d33f70f637 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,34.0,, +Version,+,34.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -1450,6 +1450,8 @@ Function,+,furi_kernel_restore_lock,int32_t,int32_t Function,+,furi_kernel_unlock,int32_t, Function,+,furi_log_get_level,FuriLogLevel, Function,-,furi_log_init,void, +Function,+,furi_log_level_from_string,_Bool,"const char*, FuriLogLevel*" +Function,+,furi_log_level_to_string,_Bool,"FuriLogLevel, const char**" Function,+,furi_log_print_format,void,"FuriLogLevel, const char*, const char*, ..." Function,+,furi_log_print_raw_format,void,"FuriLogLevel, const char*, ..." Function,+,furi_log_set_level,void,FuriLogLevel diff --git a/furi/core/log.c b/furi/core/log.c index d910ecf2113..53467ecdb2d 100644 --- a/furi/core/log.c +++ b/furi/core/log.c @@ -14,6 +14,21 @@ typedef struct { static FuriLogParams furi_log; +typedef struct { + const char* str; + FuriLogLevel level; +} FuriLogLevelDescription; + +static const FuriLogLevelDescription FURI_LOG_LEVEL_DESCRIPTIONS[] = { + {"default", FuriLogLevelDefault}, + {"none", FuriLogLevelNone}, + {"error", FuriLogLevelError}, + {"warn", FuriLogLevelWarn}, + {"info", FuriLogLevelInfo}, + {"debug", FuriLogLevelDebug}, + {"trace", FuriLogLevelTrace}, +}; + void furi_log_init() { // Set default logging parameters furi_log.log_level = FURI_LOG_LEVEL_DEFAULT; @@ -117,3 +132,23 @@ void furi_log_set_timestamp(FuriLogTimestamp timestamp) { furi_assert(timestamp); furi_log.timestamp = timestamp; } + +bool furi_log_level_to_string(FuriLogLevel level, const char** str) { + for(size_t i = 0; i < COUNT_OF(FURI_LOG_LEVEL_DESCRIPTIONS); i++) { + if(level == FURI_LOG_LEVEL_DESCRIPTIONS[i].level) { + *str = FURI_LOG_LEVEL_DESCRIPTIONS[i].str; + return true; + } + } + return false; +} + +bool furi_log_level_from_string(const char* str, FuriLogLevel* level) { + for(size_t i = 0; i < COUNT_OF(FURI_LOG_LEVEL_DESCRIPTIONS); i++) { + if(strcmp(str, FURI_LOG_LEVEL_DESCRIPTIONS[i].str) == 0) { + *level = FURI_LOG_LEVEL_DESCRIPTIONS[i].level; + return true; + } + } + return false; +} \ No newline at end of file diff --git a/furi/core/log.h b/furi/core/log.h index 46ae7f00713..5d11add9b91 100644 --- a/furi/core/log.h +++ b/furi/core/log.h @@ -7,6 +7,7 @@ #include #include #include +#include #ifdef __cplusplus extern "C" { @@ -87,6 +88,23 @@ void furi_log_set_puts(FuriLogPuts puts); */ void furi_log_set_timestamp(FuriLogTimestamp timestamp); +/** Log level to string + * + * @param[in] level The level + * + * @return The string + */ +bool furi_log_level_to_string(FuriLogLevel level, const char** str); + +/** Log level from string + * + * @param[in] str The string + * @param level The level + * + * @return True if success, False otherwise + */ +bool furi_log_level_from_string(const char* str, FuriLogLevel* level); + /** Log methods * * @param tag The application tag From b7d2fe769c59b086436217c0a1e2b39a23b87f34 Mon Sep 17 00:00:00 2001 From: Nikita Vostokov Date: Tue, 11 Jul 2023 17:26:18 +0300 Subject: [PATCH 654/824] Decode only supported Oregon 3 sensor (#2829) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: wosk Co-authored-by: あく --- applications/external/weather_station/protocols/oregon3.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/applications/external/weather_station/protocols/oregon3.c b/applications/external/weather_station/protocols/oregon3.c index a211c5ad326..bd35c2fd576 100644 --- a/applications/external/weather_station/protocols/oregon3.c +++ b/applications/external/weather_station/protocols/oregon3.c @@ -116,9 +116,11 @@ static ManchesterEvent level_and_duration_to_event(bool level, uint32_t duration static uint8_t oregon3_sensor_id_var_bits(uint16_t sensor_id) { switch(sensor_id) { case ID_THGR221: - default: // nibbles: temp + hum + '0' return (4 + 2 + 1) * 4; + default: + FURI_LOG_D(TAG, "Unsupported sensor id 0x%x", sensor_id); + return 0; } } @@ -198,10 +200,8 @@ void ws_protocol_decoder_oregon3_feed(void* context, bool level, uint32_t durati oregon3_sensor_id_var_bits(OREGON3_SENSOR_ID(instance->generic.data)); if(!instance->var_bits) { - // sensor is not supported, stop decoding, but showing the decoded fixed part + // sensor is not supported, stop decoding instance->decoder.parser_step = Oregon3DecoderStepReset; - if(instance->base.callback) - instance->base.callback(&instance->base, instance->base.context); } else { instance->decoder.parser_step = Oregon3DecoderStepVarData; } From a319a6fdf230560101ab3e4814e3a2c7f5a5d1ad Mon Sep 17 00:00:00 2001 From: Max Andreev Date: Tue, 11 Jul 2023 19:36:15 +0400 Subject: [PATCH 655/824] Update toolchain to v23 (#2824) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- scripts/toolchain/fbtenv.cmd | 2 +- scripts/toolchain/fbtenv.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/toolchain/fbtenv.cmd b/scripts/toolchain/fbtenv.cmd index 4ae04e2a2cb..51708b8c488 100644 --- a/scripts/toolchain/fbtenv.cmd +++ b/scripts/toolchain/fbtenv.cmd @@ -13,7 +13,7 @@ if not ["%FBT_NOENV%"] == [""] ( exit /b 0 ) -set "FLIPPER_TOOLCHAIN_VERSION=22" +set "FLIPPER_TOOLCHAIN_VERSION=23" if ["%FBT_TOOLCHAIN_PATH%"] == [""] ( set "FBT_TOOLCHAIN_PATH=%FBT_ROOT%" diff --git a/scripts/toolchain/fbtenv.sh b/scripts/toolchain/fbtenv.sh index e5548f488b1..85d139040b1 100755 --- a/scripts/toolchain/fbtenv.sh +++ b/scripts/toolchain/fbtenv.sh @@ -4,7 +4,7 @@ # public variables DEFAULT_SCRIPT_PATH="$(pwd -P)"; -FBT_TOOLCHAIN_VERSION="${FBT_TOOLCHAIN_VERSION:-"22"}"; +FBT_TOOLCHAIN_VERSION="${FBT_TOOLCHAIN_VERSION:-"23"}"; if [ -z ${FBT_TOOLCHAIN_PATH+x} ] ; then FBT_TOOLCHAIN_PATH_WAS_SET=0; From b1e13d44b8b4c9d0423cfe52de2566a6177e6527 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Wed, 12 Jul 2023 12:28:51 +0400 Subject: [PATCH 656/824] Dolphin: add new animation (#2865) --- .../external/L1_My_dude_128x64/frame_0.png | Bin 0 -> 1615 bytes .../external/L1_My_dude_128x64/frame_1.png | Bin 0 -> 1637 bytes .../external/L1_My_dude_128x64/frame_10.png | Bin 0 -> 1044 bytes .../external/L1_My_dude_128x64/frame_11.png | Bin 0 -> 990 bytes .../external/L1_My_dude_128x64/frame_12.png | Bin 0 -> 1100 bytes .../external/L1_My_dude_128x64/frame_13.png | Bin 0 -> 1494 bytes .../external/L1_My_dude_128x64/frame_14.png | Bin 0 -> 1460 bytes .../external/L1_My_dude_128x64/frame_15.png | Bin 0 -> 1440 bytes .../external/L1_My_dude_128x64/frame_16.png | Bin 0 -> 1210 bytes .../external/L1_My_dude_128x64/frame_17.png | Bin 0 -> 1399 bytes .../external/L1_My_dude_128x64/frame_18.png | Bin 0 -> 1454 bytes .../external/L1_My_dude_128x64/frame_19.png | Bin 0 -> 1648 bytes .../external/L1_My_dude_128x64/frame_2.png | Bin 0 -> 1629 bytes .../external/L1_My_dude_128x64/frame_20.png | Bin 0 -> 1433 bytes .../external/L1_My_dude_128x64/frame_21.png | Bin 0 -> 1032 bytes .../external/L1_My_dude_128x64/frame_22.png | Bin 0 -> 1054 bytes .../external/L1_My_dude_128x64/frame_23.png | Bin 0 -> 1050 bytes .../external/L1_My_dude_128x64/frame_24.png | Bin 0 -> 939 bytes .../external/L1_My_dude_128x64/frame_25.png | Bin 0 -> 1447 bytes .../external/L1_My_dude_128x64/frame_26.png | Bin 0 -> 1509 bytes .../external/L1_My_dude_128x64/frame_27.png | Bin 0 -> 1504 bytes .../external/L1_My_dude_128x64/frame_28.png | Bin 0 -> 1529 bytes .../external/L1_My_dude_128x64/frame_29.png | Bin 0 -> 1625 bytes .../external/L1_My_dude_128x64/frame_3.png | Bin 0 -> 1599 bytes .../external/L1_My_dude_128x64/frame_30.png | Bin 0 -> 1575 bytes .../external/L1_My_dude_128x64/frame_31.png | Bin 0 -> 1609 bytes .../external/L1_My_dude_128x64/frame_32.png | Bin 0 -> 1635 bytes .../external/L1_My_dude_128x64/frame_33.png | Bin 0 -> 1668 bytes .../external/L1_My_dude_128x64/frame_34.png | Bin 0 -> 1588 bytes .../external/L1_My_dude_128x64/frame_35.png | Bin 0 -> 1551 bytes .../external/L1_My_dude_128x64/frame_36.png | Bin 0 -> 1656 bytes .../external/L1_My_dude_128x64/frame_37.png | Bin 0 -> 1545 bytes .../external/L1_My_dude_128x64/frame_38.png | Bin 0 -> 1650 bytes .../external/L1_My_dude_128x64/frame_39.png | Bin 0 -> 1028 bytes .../external/L1_My_dude_128x64/frame_4.png | Bin 0 -> 1623 bytes .../external/L1_My_dude_128x64/frame_40.png | Bin 0 -> 1225 bytes .../external/L1_My_dude_128x64/frame_41.png | Bin 0 -> 1256 bytes .../external/L1_My_dude_128x64/frame_42.png | Bin 0 -> 1055 bytes .../external/L1_My_dude_128x64/frame_43.png | Bin 0 -> 831 bytes .../external/L1_My_dude_128x64/frame_44.png | Bin 0 -> 623 bytes .../external/L1_My_dude_128x64/frame_45.png | Bin 0 -> 556 bytes .../external/L1_My_dude_128x64/frame_46.png | Bin 0 -> 928 bytes .../external/L1_My_dude_128x64/frame_47.png | Bin 0 -> 1206 bytes .../external/L1_My_dude_128x64/frame_48.png | Bin 0 -> 1019 bytes .../external/L1_My_dude_128x64/frame_5.png | Bin 0 -> 1648 bytes .../external/L1_My_dude_128x64/frame_6.png | Bin 0 -> 1570 bytes .../external/L1_My_dude_128x64/frame_7.png | Bin 0 -> 1063 bytes .../external/L1_My_dude_128x64/frame_8.png | Bin 0 -> 1024 bytes .../external/L1_My_dude_128x64/frame_9.png | Bin 0 -> 1078 bytes .../external/L1_My_dude_128x64/meta.txt | 32 ++++++++++++++++++ assets/dolphin/external/manifest.txt | 7 ++++ 51 files changed, 39 insertions(+) create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_0.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_1.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_10.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_11.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_12.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_13.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_14.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_15.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_16.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_17.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_18.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_19.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_2.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_20.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_21.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_22.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_23.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_24.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_25.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_26.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_27.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_28.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_29.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_3.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_30.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_31.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_32.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_33.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_34.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_35.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_36.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_37.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_38.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_39.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_4.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_40.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_41.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_42.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_43.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_44.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_45.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_46.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_47.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_48.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_5.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_6.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_7.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_8.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_9.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/meta.txt diff --git a/assets/dolphin/external/L1_My_dude_128x64/frame_0.png b/assets/dolphin/external/L1_My_dude_128x64/frame_0.png new file mode 100644 index 0000000000000000000000000000000000000000..bf07d03d6e633a1cd9725d181b4970b96d216058 GIT binary patch literal 1615 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 M07*qoM6N<$f?iSKcK`qY literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_My_dude_128x64/frame_12.png b/assets/dolphin/external/L1_My_dude_128x64/frame_12.png new file mode 100644 index 0000000000000000000000000000000000000000..19fc985ac866535e616f20dacc7956deb9bf1f9c GIT binary patch literal 1100 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_My_dude_128x64/frame_14.png b/assets/dolphin/external/L1_My_dude_128x64/frame_14.png new file mode 100644 index 0000000000000000000000000000000000000000..9a3a84fff837373f3e503913b6095071d044c33f GIT binary patch literal 1460 zcmV;l1xxygP)AxL1WuIkKPX1RU|H3IM(9i+z9&O$8Zz9^wF+NT%vb zZB|9*Q&fP8a_ODi4sdt!p8_fBsd}Z9a(O%DIywy>qtpAm}OE zuNClo%NW&lF99AE=|2^2skHPywQ% zIMWgCw9Kp^zm(u5AX3WzvLaCA_fj(#7H0|10ixG)>P1Fo z&rOOJcaZ-P`P9#_PJd?Y=VfU)gN%vSeD$(ow}WtNK6O|I%b795HYo=|+@P}^f$Kv{ z?k*>=Bz)0_XG>24vmF8UG1JeElgZ3O9t}Pmz&ikKmRd^A(!)7}G@jmWr4v{qKeDg) z{TBSDc+xe5teR{No@K)`9xeH!IdzZY$6ky<%ddM;I4w)hbONjNM~|i3yR=@%Z9OPR z1Lb~Y>9l=**6%Guulplzq+knM0=(-wHPBNOAu{@A$h~&zo_wm2#^B6gDCzIZ#0f{i5lg|$9Kk~$4M*JlVPYP!1Y~R-cVxsPM7-lD`lVe! zS~6NDb^&le-smr7yj9dXf>b{Ov+o-9X_wv0PpV66?Y{zy2AS=h8hN!kg4AvXo{65d z;q6mYp4lZp^2%A85@sDiYA+#z{24o$=L5CYa^bH_T#0|qI5OqV2k@Y|oI$&t(k7Fz z?h5j$Jv_HOO3SB)*Y^?-y(UE=;%vZ^y=NwUGd8&A9{%1*AT}l?9IN-RQD*`xb`d+I zU&|LRFH9z`)QwJ^ad%v)MiZ|-{Pyr;_ zxC^N5p5-01&hC|dynXBKB$BPXQKjl6kTP~B^a;`IZf0ypC4HWJf#XNIzOWI^_1(}j z|IzrPoO(o}MNzb+Rsp!{kB>nkkGW&?Q;3RC={j;u*1omZ*OMck!|Xdnl0WKy6zS2| zA0LCF*T|D0$+agU;e8BE&R})0+3~(}0C zGfANryU1^?{r-)h)8(H-1ZnVHcPL9FsnE7mpZpql0^t?~!iiVEwkD6e6i_15>I9|e zmzCE^q(fj5ESWW>T$oTPQsDh8>|Fcb0aCy&fSa_P&M_iR+m>pBor{vs*?wonmnrkA z9DsXCO%!A&PG0+*l5kG2A{a8(ezyYPmy^hsCM9yxMr-nnD)e^R#5XcA9wnUV5zi^W zZ1h`SIKmDGt9@#cx#*{y#M&peeN+xGnxF{GPTI)M2wLT$NQLb+lc>dRvdOY6a)53R zPDX^CSy;&&O(Gmus$3_O?B58=Kx^{fyS3Vp>TzWx`L%u?OP@flru+w0?-z}=lz5x~ O0000zkvSgZur`#u4^_xmY;$ErOv@jajJ z4KR;yqW~7`j0oHz>z zC3-U|h2L6|0%QmfVi|aYFY1NgTA~NYvf@fbcv6_*gb%HSFV?+on0OSKuKbfdf+T!; z0+8;tR09D&KsXE8NgcQ0r&#d}G~sja@fheEp}Z7ak=aX3vEm8tUyAu$y#%+BeU+aA zD%^KPcv!6R+u zct$Z}`7@C2?SK37HteyYYDUj~`Q$1k@XSm38eQMlF~N!E$4tC+3LEscZg z)j0u$wkAzB(mlucO!N0x9EUqGytP_SFynP9){|NY0*n?ZWv_QD`cyrwTorQge1NSr z`LAwApK<-3jesI>^WQ}gvXvkb_bq)E+5MV7D+FX~ady%ZLDt?lI=I;5NmR42&1g55 z%taQ*tpR%n%UQn?SA>E{ogyN`O?tQ>)tXzYvBuFR=!1|ug$WBX;=Z*$pvjJg>gf<* zbzL5MfQU6z7qVvPM}g#6{tu&fo|1t)+&5!c5mgs$MV@6faU@;##9wVG(c>>O5w7h3E8?QO%76Q z$K$zvXg5o&Ut#%`u5+6oMpoL9gGS1X(e?FT^+t(U zuAhS5`63g?+$)4{;UA6tqt@yHr0dgH!ZeL}s`bN$qaMtf%&nMX_Ii7xod)hHLpYE6~EyI}QtG&i4N@ff3AAgld(itt-IyX*>PTTrnEZLVyg z|7!RB{50?h?mv5NngUQ`xQZXMLiOA)dVsqF)>x^ecQJrU5AgFOp>sI0*`z&O>E5lV z3xLgT>9K<^VuYTF45_S|hg~Esk@{U2P0_50xI^R0p7o zn8}PWqD+h=V44EaKJ65AwIXoB$9-j|0B#|p&5shLj6^D`T#_*9+N9IZRo=j&lw}@3 zT2R);ql%!#_J~)I+&`sT?GaW8f8Ue;ZJY+0qVO<6LCAC1reeC|!S)C|$2Ga(c*iidap z5-oZLq#EFBn$I=a!cI6X3N!$pkBdH5MZl2k zJTHWKF-OVa&DUGE`tw7nwH6KHsI`8a>#?%F1;YGNuHO71A4whN`QOs1JG@{}oF&Hz zo{*EauHALOdU|qMO{e(bTC$jLQ6D@HYt&$Et+mm3_Z~+pubt8TkSDxs;KBIbO z<9~)6Kgsj|J_B$MabkFxt`URc3&mayO>^Sdsq1JHK=-|Nyi}wWv%SN5a#=QkXae*G zIKAUnMA$K<=ZK7%4!h&u0-sKV>ZDzAa2;6i%yf-$R33OuGM-NbRWH6~q4N$tr^Y=e z!m8=mY_8exbCHcu3SA%O6g~lVN#LS(Oy|s&m_Axl+YA&B4kdx>x1PKjT>hItZpU+` zk3`ny;l#Dx?b9Jy?xK1EaAde5wB~y>!zirt zt-PBxy1P#Rt1+e(D|>kBXFdTW$*E#@$ZJK+8WuaY$LR?G^E^0-&d1#^9lLNX?GD7(CHXb_Y1UIsn%2(z7I2~oxSfxm zm)t9vB%{e=@j8nzALo5m$CYGE@4ODEG0O$KZ}v>T-rD?&p%s%lYIdSzT?^B(Uk^@h zk<5}bi;BbD#tyMP%f>3!05Rz-t|W45cQCZKi*CH^SC zg&C(e5w*!5>ZEoNPsAo!Gyz-}H^!(bFk04m(tNC&*}n&fLTONwWHmC>&DA5-t=9VE z{$FUNs-|Z#?QHVQKyRb67@C#Kjczfs~)$5}HsN!d0jP8f$EsORJOVdy2p!EaB YADMSfJkRd}+5i9m07*qoM6N<$f@&T_hX4Qo literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_My_dude_128x64/frame_17.png b/assets/dolphin/external/L1_My_dude_128x64/frame_17.png new file mode 100644 index 0000000000000000000000000000000000000000..fd910ce5a61c659c7caf0486f2eef29eed874edf GIT binary patch literal 1399 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 IM6N<$f>4IGLjV8( literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_My_dude_128x64/frame_19.png b/assets/dolphin/external/L1_My_dude_128x64/frame_19.png new file mode 100644 index 0000000000000000000000000000000000000000..d602a01d5b14b4134e49fdd08ca5441ee263c2db GIT binary patch literal 1648 zcmV-$29NoPP)@apD>%ah@N-_?RbO+!zEF&^^l!3Q^igL|9 zr8vM7(*M9pC()2zuS_eXsE4xg^-uL?!5exLp_<`oPT!g@%O0(@4ux1T+lZ(EtsA`} z15j)_Tdo#b<8z;X#)erp4J%u<;bxEiSFaUD+MS&ukw0+2Mv%7I0vCS+vUILAz9sr4 zR?o@i##~6Cls?~R_yMUAqQz4pUqkjRxMTowKwzN^qExlnmiwx9e$77NsMqQ<%Mju* zkZLMekg;JN5@;DW+e~eJt>i>r<7>b=07a%W$c~aos@BMnh?Lj(7FbSE)^$|;fGPo* zPoj39l2A$OWS2?K_!+5x;Fba2Zv574Px0`^T24Z6o(CS|YvpQnx-^1T8=qP)%aNi4 zR5}T9pIf0RS5{{;XEEFd@W5NAtB;!%PLKtQ@wI(^?lTEwAHV{S@ufK#-K{L__yP)C zdt9S_hhy+u3HYAEB+yElD#vp&BB*eHS?QyANtJGAMDXqit;VYR(-OWhuJ@kX2cYUl zgcMSkwV3hxP>0oyHhZdO89$pJVfz_4CGwEzX%uet$k}|#g64k57_9gBv91SYMD&s% z$^KwPI0dkvs6vqzb2hy9V!a1$|D^dDJqPhwfTjKUB%^7;m9nZ{eKsn}Ae46_fxk@b zsPw%Gp6IvA#j-S~hjT|XLpH0qp1J03WRpHj+_}#b*A|NvW8N$|p2~GGR6T ziXH^n2cQ;zgCQUr_ptIRL>!rWOwEm*UBj}zeSj4Pwh$VRPEwzt`dd8~M4#@>Nc!v= z#7Pa(c#;>x2_!GhbVyiYW@}e~>veeKWZC6H9I0mk(!}iJ2*#=iU6BzY#`iu0z&#ec z{w>)5k>`7oRE3q~uWBtz?WUk@#Yeft3B3gmpnlH=Mch`_>Fj47t*tZukSm?oxYx19 z_xk`6>#y!~{W?~(zF|qj{S^2Ljo*9!+X$My{xUMNz$}B!I+0ObW&|5M!O?r~nzZ|C zO&N@g`{PpojI=>ilq8A>UNQh&^uHAO1G|jhTKyyUp$Ftw@b$#a89ZPJ_#ZY{oeK6T zDuEwLeJHQsUf%+%uF=k9c6nUs2p0~(H4dKCMjengXk}l+oBF9ejUFF8_%?#L$d{B` zg{qEW^q#!N_vljP2%o2>e_X37S^wbkWjqN|FE8)Q=k<85;#< zXOUwI@spm_jQ1WrE`Jw5Wq>z;YNvA061@yk7~Mb5e5<2hz7oc<|5c8VQEsfda^p3G zE;IV!8rtR@mgPA$O#1-4%7E7yDtZ`HDy}d-ly8+mUDxd4IWzt);M#4SE4`P%-Lqv= z@fi4CihR6=^oqF})N}A34a5upg+2w6fuyMPW;UGuBV|$3E3l?}YsBAl>izyK;Hk!+ z!DHl^)3nBaYntN(oN;DlX{NXIx%(LSq_m#}s*l0F&eAStB+5XTfJuA-?=@!!D(z!O zirDPN0e%78rb(NuIsItQ?b>~Wvb01TI#twOz4va}Yf`_~ zxwLvo<1)alE&iLhETgS-0x43pk;NI(YDBNz@LCiz-9!H-&ZERCSj&MVgOb-8&Gxx+ z%H2B^99aZU*G4ip6pD=CQ(s@1x?R6 zT5F9A9LGUKL|T>OINtZYdJp!-VHp5lVDDb!`Gfs%5K$xg^xm0;>}(+We6T0Z)*7|e z=s1q|M^d|0c4&((`;=`S>;X6gRM5NC$CE?chm%P6=pWow+B~E@?Yg&7%ANblC2F5X42xK~*cSs>o{rYz ziqY;{v8yW9%I|zU@;bsy5$}`jW{nbMDi6HA_BDgs0>oP^C42NK`wzudr>-^ks17r-WxZ3Yc*Wu)7LR_s3Cj75?purR z^{th%AE{f-qou47t6K8E;=I9Ztu;D5dba4>5|m*8OR?xPZCn+lW?WY2^XBFm7@}iY zYVErmURvY+a&}@r3PtIo^8QCXI31S3Pw zIEJ>!mMn`MUi{%mpeJC3wIi_T&coGXw2;x6X6Tv@P+6yEIbLF6bsqKHt5F&6?=W}^ zRzAV6wXvS^29o(-4J5C)iuR>UOZG@tUU;nvp_;MFzE7_~zwaS4;HcMBc$&8OQP$cG zQZE@jombOXR^c<_M8ez*b_xX+tTCQmrP0Kq$(PnH0=_#z`jd5c@${9f6InANB?nlE zii!6)))&`XeA%uV+L=iNRr;7=pHp_Sdlui(4Z+)z^18d=3cW@0xgi5o#Wy4?uEJf5 z?@$fl1Rb82jNajPrzzb14 z@61xJ$0VygQCs{S53F{?%X(m0RRqz+&lrrg_;)OZJki@Z8SQ|!f!@)u_sJk~tg9Kz z=zYejQjSfV*VRB|zJCycb|xuSdn1LM-46#A=)Y(&a4qwW75XqVP@xHwO<_qs}N!U zWWtsqc>QL8dRIlbv9RJ`Yik2|HyY5ctnUsWxosP@izu-=L7Q2?s|~>1272(=K+6db zL=3?TC3l_!aQVN2lmQUgy8`sNg=pBYGV(j{Yl}}5Oah5~%FR6cHLPCbnO#Yy-3J4B zos6>g$liB%c#5pZIjS+E@2{6Zh_GtfzY;5UCRHAL#}Rfq2_(Yfzx;kDcFFi>4xu=* zNq5Ka4@3Gh@7x_RgdN#n7RYh+E{1O*^qWA>NVuW=&xpM28X5kqW6XL#5fB0-ReACK zfAM%E3EaWZE5`8Z9eH%6Js%mu-HG7rY22#5D-b@zW*zGezfdiSU_$Xvzu?G<9dmrA0V1- zd-UFWn}p*yn$*2k7isV9@4+w5Sshms(tB^mafIt1@R*_iBO)Ga-3LF_9Rm4R5DZ@6 zQ7#?WK&Nd1+Tc-w9ygx{HqhRCtq0hFoW~3Bpq?4>9 z5%Ew~rT{y*f55jZ37)$YltY^(TL4y$h@yNekzR0CKU%vI1)!wTe3n2?X-1bq-v*1- zvu!h1!PP`pfm4oMWSap>zURt3a{?6H88Yj>HW@Q}~Jr2uzcMCu-K*&Pwt@3}T6 z1;`LzaXP4esRX#ILQ^86%Dp*CAp(k zvI8PkZ)q!ffV+~9Wf&b#Y5QF-kTT>C`{q2royn(&Y#%!*L6-bc`?uG@J6-8s0hs>| z>oA-A*8UV=w@7^#T&V;c`I+Zw-RpDJ77$f{6}NU-B3CQ-i2TZ6mJI^E-@M7(4}8%W_odUGzvC{o}j&%`5!zYPZCyEF%Mq|A>#qGJz zIRE0Edl{4!$l3=M#9Egb3vjpd{^-qKMzLO7z$m4kvjA7mS#0$-fVUPr<|zKXivsiz$&zA?F7ce;W2y{1Kk1zvS_iXK3n}cZif9Xjz z2pQHpFu@!TxS~JgcqiuSWKWQgY{!TcK_sb>x1HhL<6=QlW-O}eMwR!+4f3()R zMPqjH0G37cT90RJu6@+?XU3??pH_{zf_%+-F|Z{^T&ES4rLiST%Xk=XJmIZ}{148I0*YM`l~J2tmVB>V9QiBKR}64>C5T|9g+^tG zmRnQPJ;0{{;t6R1rMxG;;PN!Z_13-LT3+G-yc6FMoDyf}Q)&HHOP0#B>CaA34z6$jTvr} z${rz~+g`7>d`PGM+c<6!W&_6YSdx`|fTbTSPw)SO+K3#M`e-Wtfru*5$n6fpW>tSg3Ftp@qPcNGxz^j***ABZyWI%tv)L=|uq zw8;m;0j|Lg@_}%GE3k`v;M)PLOPa8IOT7_Z`7-D&c7^=*x@)5f@QK|gzuX%dz#&eN z@1hkP_=%I`1Dz_sTKwz~QyMt2UX}JPQB=bCx77oC`YRG*W&n3OR zKO(>(tOza2Tq8#qm6EILibfD4*K2`ul55>iuV?f*%SHs4L)!sHbGB)M5*cQ5ju3r- z33iYVJV$}{vzb14iXt3M0T3O)Ll|DMUeQYhE9>Ttj}$P#uG2qz2G;>rQbD8uCDaX` z)1}=?6mjbS)@g&&k$dV0dPzUipA*bq3A+tcBDi6+*A%M9ah#}^1~|?nNd>Rf06l_c z_m?rF&7(Mng%d~|;B{{Uxx^Px+q0BSy(wZ2HGqdaOtIy<&c!Qe-`v&DI87?YnR;Sy zR2KhofK~)~xAj`J$!Q&dlTJLH{&wGhe?0$60rmT9uC;K2dJVfk&FAfUyg0&W|E?5) zr-<)8TPo_f_xBur1g;RgP6S$?s!>jED+ww_c2&y#7A$6DEHjfC8c9B!hYmpAxQnCF^ z3Q80ime!t9z=_i-ZPq-Er_3=*J*$qW(L8eF04GE9iW6w$=2i5ZNwR+~G=ODZriw#J zBZfO(GXJW$b>RRf(IbM=-><<9=_`$$Q50D{I8zaLqBr0q3;pQp_d1EAUVr;8VDzed zHvZ|oLi;|Xb|$gS+5X?hpMs{^raq6l51chDCHl7>+0000@+>tR_foptVg8;1$^}g>N znIq@CSFYbNJ^(yd1o|PH@qw!W++dmaCAb7|Kqk>w;}YOtXhexi0Apm#`xKwD*d1-g z9plf+$9i4K{%#dugzTPQa8tm4j!OUooZ|h!C4f3kGCpt!0WyfW)iJy1tH&z>e-YK= zpRLn-tu|f)=)fZMTZMg!BT_(ioN9ak_+-eN7NZwVHa>ux0wg%y_`oH=6x@i?JI>X0 zyv_0Bdbfj6492a|si9kc>!B#~)_hBdL|}BDx6%#8psNO;wS1Y^GU#kRojd9esP+N= z-ar03a@uuv52N)sUf)~0HGf0`YH0em)?AZwec$(uHtLS?wQ@6EPb=iE?zd%p`d(|k z_PHJ<0wwuzG*{F3qcvOJJc7qQk7)$eIg*Q3ZuLgl_ijqF*n4^c6d|R_{90gH1i_K9 zdg8NpohAMeAXS>2Y^v0 za23(Um+XTxXQTz_@7`MALu*sbaRph7&qdvp(^xACX*9qL7CCqqkwRejP}YsA007`eyZqBC4`*og#I@F+MVJg;uLZYf0iNE^@l4M3yT}{?Tr_}XuIzQ{ zMu*7_;SywR02P>P0FQ&FqNbP2YY|81L{t+s38G-00z3#Hxrb#M7=`{-Sb9HToC30x zNh{Fy!V`=S7(NY*%(0xgt=E!$_twa0e85Npcw<+a%+}-Xq1G2W>-FYSt)h9(AB%e5 z+xsp05i^q1*YymyYOK-x-sdRSh(-$$m_7V!dXPn^@4aUMbpm{|@mEU5-kvy-z!NFH z66OUB&)+pr)@n#y1bTYEr#My3yNO2C6yQk!fd7R^30ke&uNbum;AQ-sds&LU({Y>z z&>~vzA=2zRPojH3g#eikr8eqlpDUk%(@Qu3OCm4}B%#06XxT;76GRIvbpnBS<b7`u=1GuEKZmvU*g0wg16vatNj zimn=F+K5?cjRSBytJHn_|2NE307=IuP$!X-9NdG60Pu9dJ=scuItko;K~ojDctbS+ zPRS+IMMk43R3{rEY4+10+#i) zziaRqAk_+Bq3`FPkz(z&^>Kbo%5I{yf-cX&s@%!}W-_`YEu3~>JRk>!G}hM7CPs z{Gb&CtNL>t7G-$bX#Z;WL@d`x%m z;?9h69vrv|_Ai831+{EH4X>0@9MWp(zi8u*fe!~;09eR=th`&W&mvq^fYJV)mNw-o zcQp_=#z5@_(4XYk1MJMM*?GTQ<<1=E#65tCUj}hw^e*9y?o*}-QVYP!h(`8PE_{sN zKN;U`gNN(~AO=8h1UwC`@}Gj~gv?J^B7s^_t>uJN@p|x*{Q!=pfL(nTBYzZ7ov+UR zlZ3&qg!N8L3Rqc|J_Z$@GCyH@473mcTz0fZM5^3$Ij?0b&qhY z?Q)a@XaxY5^K0PX5GI^CA%_A$)A`uzGw{-*rQv1|z=`Yp4~D)Hrg`D21cspnSRi7m zlG*~a0suSiY5?3yaOmye9+Knp>Efv%1;9z!pXYszrvTd~o_3p;iY^DxB!8cJwRATf)f_Z2=z2zqEa)R{|FmpapKn)GEyB zWA=})PU3&en*#7C;Eb`r>N4Ze_G z_kG`xh;dyP%nUK*`q?%|d{|mZns^rMDYPK0a%D+`Y@RQn^>E^T|T9=jPr4or}nV; zlHVhD0UJcE`!!ht7CAwDfJ}fTP7xm<6JVOij=x2S1h6)HCz(DR(**uPIoscv{jK&| zZ9Y8*d&~r&aZ=r{2~h&9IaPcBfRA-u^;WMn@FG3{WC?hJd-i)0A0QLpAJi43HEh`Q z&E9Xp*ZKiGP>V$srYCUk@2wrhY@ZTrnLz70&$18x6{Y-8!Pk%OFXGpI*L_-k|1A^f z;eYRTOkBI5Sy&k7~+TGyzL!+qbkwFp*`8{94yz6E}cI*~NS;dLfZO8j2?t4W|G z%WDpT>Uu8;Uh-G^Yn2TtUXcJ?^`6SCfLda=IQAqEn<{Nu#&Gl;I0^o0wDkC#>;bIA zZ}pFpp*8C*yuh&~z*>BO_9`%1?t80%Bu6H|jQI63tHJ6vRH_6VcN9E3xVtOImKe?UVsZlHhoh3EZvwyZiTEoA_V|Cjv&(3?AbFxCe=2CGcQG zpEx1`xCXj+E`KsdBmgH|Ng$Ct5`Hbb2asB3DYbcYZ_ntt^Z*`?Ye%t%38(hvvaLCd(^TJaWM^B0yGj}r;kzMjP~~0D?msS=zV+jBz_OGmqFGw ze#*5fi7(L)e#JZUER?uHxDwz!0cJcyjm7K-2v!18e15{b4fI_1d-dJB_y9pXfF?5n z)ShHk{N6qF4nbpFKGyqw%6Wjd+5aT`NTAU@v*T3m-HP5#pr-iLnZ>o#_de4kj0e!7 zUUllde63Uj}>lBpqIK|YIf^A#mJnE-fMUm-xA%f09Lc# z+We|Vj|reX`@Pj=O+uXnO0s%RA02lIU|IW1t$$MzkW8Q!7UH-1d{YvTP67e!lS7-` zB=F$yNzsc5v|9R~`t`CMmBO6|@W$!oz(oRho&ANAlfXp+MBvmU_yPHmyDc)**GB*V N002ovPDHLkV1kz(xQ_q; literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_My_dude_128x64/frame_25.png b/assets/dolphin/external/L1_My_dude_128x64/frame_25.png new file mode 100644 index 0000000000000000000000000000000000000000..832c2bde9f9cda0876517a95f8d7b6f9a7350b11 GIT binary patch literal 1447 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_My_dude_128x64/frame_28.png b/assets/dolphin/external/L1_My_dude_128x64/frame_28.png new file mode 100644 index 0000000000000000000000000000000000000000..94e05f94d9d191742662fcbf308a2eafd80f54df GIT binary patch literal 1529 zcmVoO27YwfGe>VrP3^|=~UF70==pIvy(=oBY5qI`fKMWfR3p0};W&ar2p z+yGWs^=>1m@cvUUg#je=F3@geQ=B44B}xror5Efnj3;?Nz(NMNJMOGx3uOjiEzs!V zY2Lnz1)k#l0KW_n!rhf@;i~B5$qU4RjB`p&VieOEpvpLQg#k%^sW^34{Dy!8&={WL z{Qw09@MQXb^4t(5IRFQz#Y&poMY%NgKV=61P?!W#tX`DXTDdZ`*pFl3{oOHS0H-Vj z^+P6o3UmCB;Y)yoJ^_kMtY0q{leuzHaqvhlBqG-aNuWY4grXQJ~k4S-j^ zEbLymsGQP%UVCo$oIE+WVMpJic=3*S2X|0@1!f*+_ad?zk-oi*^ow|5N74iTi}E>6 z@%|B5&P(SuR0#4GDI!26GADyPexBz2bbNq$YbF{%#S!bOMYZCfhC9Z7?I>J&FM}@w zpx^ZO8bQUjan?AQWa$)R0EjDFo1Zq9F9V33$c2X-4^)T>OOBibO1R6`4QEc1DlnQSTRx30iqHVGt!*_o9;L|Dq1`u&sy2t@koTB9!ChF+eq{_qblI+md5-@^?y@p-z9% z8|zA6?imoF^;Nwvk=jF5?VuMk4Z=Z)hjEvnRv@+D*G^}_7)QAUi&CDsAxK+^rJc)Kgc_IQ3( zNSIw~0kFU6_^1SOfwZE~>^yq_;t3SN{WdQoJ!~jzJEL72Y4>C1| z=sIYLLZzcXxqqKq~|?`k)YNAGB|0+5M8jS5=V?6s(kDJk-VWt z@d)*cpe$&1x!VXRGW0*H+}>O&%e)r-XV$;9_MHQafLifrGLwNdmX!T_iCT}6y?#7+ ztl(X>Fs_Y_&tB3?Bajwk6(ZhJMdzcrfhIrK!{~UL5n%tN48S|b3Cx6N;r#ch%BQGO fJ~bx(hyDKnt&=3EFH=)P00000NkvXXu0mjf(FWoE literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_My_dude_128x64/frame_29.png b/assets/dolphin/external/L1_My_dude_128x64/frame_29.png new file mode 100644 index 0000000000000000000000000000000000000000..1ce384b165cb3b83bcffcc872254ad52ea846971 GIT binary patch literal 1625 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_My_dude_128x64/frame_30.png b/assets/dolphin/external/L1_My_dude_128x64/frame_30.png new file mode 100644 index 0000000000000000000000000000000000000000..8d42b8b482bade89f498e5ed1cae3a82bf89ee26 GIT binary patch literal 1575 zcmV+?2H5$DP)ZS7q4@pXh&b}8Xmfz6By2Q`mvWB6 z$=rd{R(}+ChtR2ri18ub_x*F;-?J z*?^iuOBV2L;G7Tsz8Y15aESw)5~Quboa;$7i42xh0xOiXYa6JRjkk{pYai8Y^FIi5 z02(io|9dk4?%fJ!NISqc)4#+xku5;d)h->jFaY+4 z?A4OIXkQf-K+Hawxu+c9lt>sK9aoR1qUNvFNj%`TrO5a~4seR$F{&J)qvqeiCY%h< z{9O*<5xior)&i;uNA0NjcfvbovjbF8SQWI`BjN}ZMqAC0nSV{09Dw_0jt8BE8tOaM zx7RS)BkWE`;Qngo#WP>D9OPI!SWS7Bv0-IN$FWrhTD#8*B*3{mWAH}9b#Do~148RK zt|*6pzsaG$5;`)S8WS( zobUU7J|5Yr?-6yL(lK?zViPPp7D$^#-o)ePfKB7zO`Q42v_x)CV{?_ibP z=K&Tu-T#`BaL)l)gnQ|edREOSkWL{ix1~9%onR1S$iZ49N*BN?TC|w!oW`YJsn`_q!QD$_`Ej zr5s9`*8Q|<-kr>4i7!M3z)V0nM@8APG9u0@RH|EW?J7~pJW~Eo+4;JzpF$9K)dX9B zi#6Kw4sgLAE}?exwObC9g|Clfe90+1;NGE}L91R_Xmhs9|mXhC3SO<+YVh37=s^@a_pK!LjNTzN2knIA6V)yBcaE>I@yBLa^hy=^P$ zAIgOH54Y0seKiAUbZ(ivrX;HZGnDNylEiY=u-f&;9>LvL(-xPGdD60qj3i>fiaxbi zC)<}4@RBjWykbwm&#Rq)lc)6)UA0!_b~so^&}Z$FWq{cTH2F7L1!}sm>Plk)&sKmxT-KIifNwDR1UD~k_2Mhno_mjbKF1&hb{g>NryQ`H0e%ZO z14}_4Gz3bH`IIIp7BcPN<(dK;axng-*Ox2|?&)!cm zz!c(L{gBdjLfcy@XU8iBKDS;q`IbfI1?AA*akDW%bUmQu+qQPh|cNX=F_f93$n7)VLx#NHGBC z2;M>Masmv>DIiN#(Yoh~$HVEIa$4$lh;{}z)d{54PxYTQV&;m+gP8?BgYEGLJ92bh Ze*l(B5h$^b7i0hc002ovPDHLkV1m%s`g;HX literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_My_dude_128x64/frame_31.png b/assets/dolphin/external/L1_My_dude_128x64/frame_31.png new file mode 100644 index 0000000000000000000000000000000000000000..ac926d7be3120cd6322739b62a89f67b326563c0 GIT binary patch literal 1609 zcmV-P2DbT$P)dSg`=RNpK1QE70NrC`5^|+t?gFVfT+Q#-mc$$_1FjSiRs?WTfQmAWta)ehRX? zpSxEgR~I?T2p}y$cBgW?eYF?p9!rvq$x)@puLlTXb?nbpR2)i@z;{f3yf|%+RmpLC z6;QQs+=O>j33I&bx{f1;7r!CE04e|Au1m7u5z6yCUxhRQ-hBsnG3N_g!Cs?yD**1< z3No~K0IXOgYj?Q&DuPS0c5R{(0(6xqp7B?BBFg3O_JG;(Qv^8W?Hn`){GEjq`=eu3 zbO5C{kzRxdaEjqEx`;4kQe?x`M?8v5ljM)H+dnzd@w_Tin6h59BUJ+WA}5r)v6b~-#ZpJrxyai zn<_m4^@gxBrea9fSI7QL-OfSV_1yV~wdHAjcnHl+&q4W8C zj@w$6%&>48q(n2TZv`1FGO9L6Bnf7}3G}*>li<-Y+UNJm=B`(fEQ&iT;8W3A213JE zS3@`?O81WOj{ugK9VH2+L|2CGMR%K&z(K77qL2(?t;H?RMEeb^8b6s9Zxq&-V&AF&s!sO^#DekPgvXOm&r7daPfbu^ zC|C(8l)qE~(qeiRw!3^S<$$Kkn#J&@y-yUJ{(@9VQ~g z^So@M?D`b~sDyz^AWmDvidHqm>R4?>WRBS5KzqWj>5W^c)Xx7TNbY|Zya_3l8{ucT zbI{%<4^RzQ4_AQm0;dwV+G{~rDlfMOkg5ZZVz>!{n0uLJTN0T zrr@JjG3`88+EJ6xKlM3#*)t{DboX zoPBS}_>|k2gLHuk4_w0I(Su9=q&Zd8+T8UOIf}Nquj~5%RWP^X@$V|dHFv&i2YGeq zLbdy!M{L)x*TK?`?{NQ=i}ECemIcz}K<^S=@MuyzMlZo|f(y^V-`8I~__|oi4XPB4 zz*9F%wZF$06?3-|H#3fB4Q}hs?}}TIiFPDTE5_{(S$GVKUB7)0Ld6K`d^NZvkGr=D z(wr*Dp7Czc&;sXUsp`Ic5=JqFI^P9L_G|@L+Hpqr^ccE&2a;SVH^SF65ANy%tTO%t zc3=3sS5C+Mp8UfKZGE|Q;qJBCRqX?;G(MgL{96S)SI^vc*8i5oo$LdgX#6T@**|5l zNIxPA&P7X*u9tcMZLpsL%`;d%hqkFap#cA3a$AnKb`u`>UBK=Bs=kBwH@*f97u5%b z$B6OGIe>Lyr^8wSP*sq0Klhw;o-!UhFA;;g&Xsy49w3w9o&qZVQ=S1E#<~f#lB;|D zq;X9iK$_e;!Rr309W&2+j?_3(PF0auOY;fm08_{(>~;(;oKcy#cM;+9nw)u_(kWYY zdG%&<0L$Xx>nA{qoAVwiVZ!%B7FBY?oSyhxgbZm95RFwZt33fx_ir_#w6nGASid<2 z)IGY6X%CPA+*PN+>i#oE*5u6V@W|`n1I&RUApqYC7kL%9aVeJ%|6hH? zI!n4r;PQxV9snz5nM4OT0^{F1jE_A4Ha6af(jS~hW?p{)K=@bv8-Shm00000NkvXX Hu0mjfz{LRy literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_My_dude_128x64/frame_32.png b/assets/dolphin/external/L1_My_dude_128x64/frame_32.png new file mode 100644 index 0000000000000000000000000000000000000000..35070eb6b4a23c5e6493389e66a563f9171ae144 GIT binary patch literal 1635 zcmV-p2AuhcP)<$Zr!=JTD$MedqQ_3dNi%@o2Cw&94$|7YAy8wfGLjSn_|)*{!OR@3Z%0F- zV;TH4l;;3hf=fx?j%2X&yRIvBVS1l~qgOT0|4ASKDgPLse=4R8;5UT-5q-Wp+R~kG zroojYfmFaufp>)aod(V&gU1=bs*F|Rzha@rz295^BN0Q&ak#Op`A!+YDW`KZi_Vfq z>ph^nq!UdJUb!>o8!~`X7M`NY5IRjQ5kxU7lhrfuMh2Gwcoe&0ua@MbwAvjV?y?XKCDb5-mxt?7T$dg+OftjZF`WxYWX21-55%u_}M_Nj!kzhypC_!ul{dRbaK{5g)8_&#zMxN-Q)uVr82^AI8lRwNLLUU)Q zFSDJ?L9345p4|Zn5UZYf?vaB-#z9X+My=wZ2R7A)2loN0tZ??&6Fo(X_8V35;l5=ttI5X>$b z+L^0@w2o#3;6APrur=NgtR~8g0%`t}Ri;^VyvC10iUO_(V0r$EIvEL>9)pyUzj02K z)@<0XdLIR{p08g8OCC?}vm$^M2EBWD1$OM!Rs1-<_kI664(39kbe}0y$5>Y6WmUv3f=`$;WyZQjD7Qcfl1IKYMor?E8`3FkR>P1L;(dZ%9$4QG% zCo%r50@n*bfmS7m$9sV!?%efN1$Y+!5LF zBg6B@1*U)A#lJ4{djQP}Kg|dv?~f{<&fj}Kj%DbQZjX`0hjRdHVxI!E0Zs`Okc^-?0hNqc4#-2Tr+2I^I)6IdlzW(3gqb9|u)P$}F>py8kMm_F{ga(uL$ zDkJ;X<`d2VI+1Vib_y%hM#3lJ=~@Kf@~qheF(n5;lt-VR0Bhgu?`R1VeJ7$+EeuO? z(y@p!Oh*9C2sHHXPBDaN_(vB}^K9!mm9O&{WsmA{Is(jK^s3WMNC5lU0M1M{J7#R4`-`6_5;=-tSm^8Ug5H hQ%BVP#(B)l>klc^gV)~j;w%6F002ovPDHLkV1mQPe z&+|NO0ebJPwbpR>-uuTjZ4COXH0BAQEL7IW8mncB&imdeNL0Y*d49cfdV>*JtT51406?MumB=4_~ z{Uu9|1p2NU7V^%nVH9%YgFYM zS-fTk_*Qroqj{ag64mj4jEQr4*j;S|S=T(^gjm5MnK5_t-)TjK#z3!CJE8YGM1K-H zU~Sy&{fHbKA|;hX|4|ut7*ho9KENu$GbM@kz9%P%cu{rZr=m?<1fOObjdYZPNJfNS_v(be^de5W>#dzRm#bBDbb)Mx}Wvp9Y!#dGvK;kV<78@ zf6q=ni|9u0WCu@=paPK`h|}v&2*3{s+)*0%B&xlwGKORJzZ=MuRG+j<`KBk_@!K$) z?METySllzwF(pXtcuv1>ivg-ZH=<$=$yjeQV9n^Hq&5nxbv=&M2wwQnLdyZTbFX07 z{gRp^Rq&RY-sEp3jaPL9HRi_H&oTfqffgR3f?3x+Uf}HwXzz1O;lWL8l7Xa0R}L_v z{D^?9@rEE7M+J2v`&B6VWcaZ{A|78JPsj7sNfU+#i6SF%NXehJrixnoBrNCdz5n+w zVVcL|c@+a#bRLc2E3j(p-Bp&R*PqY9n#Zr8e~zLeNug&0t#K&3WEB$Glb!tlOmOKn zdfr=Wk5!_q>R67-}$5Yk)`j;@yQn>4@U@146^^c<#;_!LTT$Q~8Zoj2DrTci# zV(?HOV3+a-kiPJGbet8>NAeHvcvde$+81p+&(Z7y>{LE3g8p^^*BV*7w<_^?d=#F< zojd+i0TJcjoD5+Sq-FoNtRqrsX;llizG()KCi~Z5F@uLWDmw+u)`D86{iejQjK`$%bQj*OXy^U!B0QK+j|n(3*Ea znwO(|`uFZ5%B;z%LCPbJF#rnYS;PuR28`z?l#dL6%0@@jesmo(>-q!Mx7!J;WBAzs O0000 zim)k8LW|__~{crvasO5}iAg2c`g)_E#-qT05tqUGGNK`mH2+ zH6|MWJwO26%4X5#%7Nkoq5w~feO2s^{avMSdli6UR7?7HC4;9aIcnbjLqGy^;dwLY zi6|?;X_nCeD*@H+TeRqr(u=&0LW>9BOd^@Sy9qOeO|3u5Q?vrm-aRG+bY-ZK@w+?` zlCDMTE-zYAfKy^1&Bp0D8j{PuI|Nq8Pbt7Dg6BXhf))F-V<0+!)=p#;Aq9v`dPl5Y zxl^Q~YRXV~pWcfAm5`1RPyplvP*~+26-8JrWHf6>(6S&UhaS2v1>lR35qNi8=D($} zD{dRv^Y27-j!OmLlV9P^jTsH}o@WE8D!AP_5}#k_UF(xDUh`TwAt6N6}p~M!CLuH7NvS+LGEpnfEHF@Gcnw zvf;A|3z;#heFZTDAhsGizYyZICcHGgeT;5JMQ)R8io1fE15}g%Q8spslTpeJWvG1M z!K)0e1fEJ~;>QOe=+TEo`_9@BpiBn)VzWNYZXJ z_;##TwN%|bN*T26W0az;tX{kR<(*(9>+A$N(d@8(PP0NEnLAzAHQv#(y$j77CqZAQe2i9OgNZ_3Rjbl0Z*M z63G=-F%Pl0-YnNbcdf6vI=}{Y9q*V&4MKmZ)Wa!2i0%rREBPWOO+e%3utgx z0bZ=?{%W_5($OYf;d6B_ibzui1#)S@#;PL!+32Kdl_KK;!aGNaJxs{8Ry2 z14z1;1=0$lO28Fbi;|d zgOos2x@)H?kA&T^+KbHEagQTy6n#7ZeNeLeDkwAb4@El~3ak?84nz~uxt@_OF1P%7 z{91VcB&2s@IzJCjJX`=3WH*Khw9 z;Pn6~STxZ|-s*mK!7B}%P6dxRyya;d&cYP9awa&FDwRPsJ+x_k@}qy#UQcTd?j!J-}+Pe+4Vg;Po6U zql$zA{^zOt`BB^pkh1=#DS>AFmCU?y&hJsdW%;2Ab4K{?9KbuV)8VZEq$uN5Wt_Mi@XckxRT4Kzjq%w&lay1v^?TA55O7oOrist0`v1b mgwH(y7dAIU>yOT(GOu6O;4lu`N0BK2000009HC`cVKj^{g#Zb zqY1MKJu5zyRp%&FlcIXLm8^bPn*% z^lz>GI}<>PSr+&T23S!O@Djm@9FC|Q>&zsIuLyt+ct*`Q&DthMpyfecKN8XbfGAx( zE3KK%v+>G)D{B6g!Q{z^9GHazz&qmI1+K#k)ppnXI}trCeU3(6150q{?DJq9C2P-W z_ci&^*}wY8Iy$aDWAfe#&J3pWZJsLG)`^_;0!yY~(-8=g{0!r$OTbJNO9vN~SUIz@ zech#0ECxVF@^%Mw9cUplG`kb{)d3PfbDmD>t}JQOAV8P40Wkn}zid$rByZ?E&hC^c z`6^PhdT10f1dq>ML0>8zA8$M>f`8@(5AG9b7lw@SIF93q7ASUY^Jd@`jnK8ZYP{NP z9rqAKP7RU6CpZVFDzdCXmbF%_{T-7jyKKXSAgMD7%i!I?p#`L;lQ-_Jir|dQYJHxP zP-);^VqqD3bq;|VDY6t~v2w?u1+^7`;`;#Fy6JU3KJ+)zwgOko(h-KK>c@g5|5+XN zS_DQFL;4vqs3E0nc;)sELC0AE-R&Uq$MX-ZqQr{}QsXP2YI*4x6W>bHKwY)X)cpFf z3$WJrt2nz7E16++O@T6>m$$tOLEe>r{{aJ1l&OZ)pXAr~5X`{9KJ&-*=`lbSvxOn# z0Ehq2m#gR)b|$x#`f8Jq-h&w+Qt_io@efzTgVvr#ej22L z64G|eGALRbK*0^}ktBdd7O4JlvH_PmwZb3`_32hI7`9-clk11sP8!vMSJ-=lNH0JDb5w%6Kt@;Z_} zuMf|!ZXKX&=4<${QqGq0A0d z0y~XRNx?HR5k{H$jr4Cp%8fL(j4bQoS%5^H-9doLkJ7i5{YPYZISP`)tk^}-eo7c1 zlblryB6B|rDNv*J01z#HW}xCe;>;()jo!h!~N!_v8#{ z&ZEhm2J3!AZ#4%%494$Y3Fz`AIZOM$A}BQGs2+Ek0jwafKq>7mu-0MEiJHBT`}8^k zi3Kvj`jG?7D0Ccq*#PO=qW>8udvd6L64OxGGvl9PfZ0HxL9jCrJo=~qRnU6m%Jri< z@i9bgxRn8@65614W9XA{e$|!g!Sx!tW$l*H=O3)jD!XUt?P34`002ovPDHLkV1hI$ B=oSC~ literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_My_dude_128x64/frame_36.png b/assets/dolphin/external/L1_My_dude_128x64/frame_36.png new file mode 100644 index 0000000000000000000000000000000000000000..d2cd0c970c61dea42452df834901ad2a0501a55c GIT binary patch literal 1656 zcmV-;28a2HP)uPiWtMMVeVe+b?pOUj<{4EYp z83`w{KsNfNyhrP^8q1QviEpIIiEiB`+1NR%^eA_Ll?L9)0;|`4EWfJHS6YGtQKey} zunVU#SJj*>b$|}+aE{sE7~?p{@S=1H`1Z-*ASIz(yCe%J4`Eh8TKlCl z4oxOTR7QmpvJMa#Aa$_wTfjL(=6%@aK~+L}U5o={Jc>KmS%J#kGHnjLS$l;n2~=`u zk=s@UU`JR1YPXDQ^PqaqzZ2fTBORbJ;j)x1S>tdrqCY6fj(s)zWgLA-KM7kkkPJ&i zbkoMgMSNt#C#qvae)&(rC>rk=%xapHCTu11oq+qgdmvo9Wxr;1y;53q_YvPoB0^mC3k+pM}p`r^jaejF@q&PeyT_pR^3?NO;+i098DVscmIwK(7KLV6p;*7an|lZNAzQv8TnPZO`Yzpb(xz;@1csv0lYhy8cS4V z{;LvB&-mvv%l|JRVkB!n)^d;QqdNLEc~zx!M(-C?WEHf?vmkpsJ7sv}S7H91;CY@> zO24l6-z!)uw0pjUABdJIFtPdube5*vJgg{(r4kqZ&7KBY?s1^Xj}C{Zl-U{a-3F(a z5bq0ccy$=YQu=lD6?IZin-@FS(9++0=EZI4N*g%c+oT?7gFRt6g6r~1NP8~Ren0;i zBqda*7`qDHhdvZ$!dJ29k#QzG?w=(vSmxpuTfT;15%-DC8w4H zz~^4rBREg7Dyw;56(Z#wM~$4bkeg>UM)y^ccn*Qe% z+x6!-Sc>=x`kx)bq;N9Bt^^AvtWg~z??su^+XoIj%C5I3@++x}tK&VHxOD=h+8_2C z$tNPqVWf`t0#`(wU0+h9cNw2ja)M$6jwR;(%E_vDj>C$*32Wqk#DDuq809IMfmk5z8&Dspmjr6y@P1S;YyX-IuUsit!fhIZ zzcGNNRPaoE1Rjr{&vr+zrbBhV+T40%4xn8tjZevn^us+HF#=~ik3L$ztCmo! z@m&m1sW=z$Rxh_cr)B@yrJxENMMw4WFu=Xq_z+MoPF_|bQkr?cPiV`X#rZj=#Lj&z z1HgfB1NRz4YtQBd99vnxs}qo?fzmozJ@FBF7Iv_9DafcK*y{{w2H+-HbBGSsn2z9C9^ktZR%iAYQHpG>9qTvyfVxNLGtB^= z38OmRxqNuQ1 zd7kG0<2a5XiPf%WWmV*$aU91OWBk>F>a!z{=>~9T;zz{bMX-fVbdHvwsX=09euAof+C}-^BuNnf+t@XMhx*>SPO$ z0C=NeJiKA{kFkIOP{!H43_RJ%7QVX`cp|QV)}2Pq*(<0Nlm#9Oyu>*8aQNAd6H0WCp!rt=9CI!3bI~ROHZORu;Q;VT zy*uC;u@eX$y>?66Qh(XD2)l;@fE%OI@$7Y(|Cai$G6HJNx7zMnO56b8gZHp{AQ9c@ zy-4zOm&`wcuC8VP@I9}E-3E*5nJwO+Gk!z`(E1!L3pD_qDRg;K(9S1S=*0 z?aTW{L45^%21gpDnar&07+HE9D+OAG?q|L1K?Z_X+=}Y`jXszu2CW1-(VU&Qt;!3$ zg4cELliiy>VcUnCv9s21{#QkJ2WKXM{{mLO+gI1(MBkkDBjs%kA1ZJLB&B8;87*^0 zFd&LS2wzbdAE|SxlT3j{O2p_NI?KNF%Nl28q2|(WM-j|QjMYL=GODv*8k4l#RmLi{i7YYE zzP#roStq@YA(^eeZ7WBl~%M@tm=`OE}ROY*d5XY{|TZ!|PN zX@33jL5Rcv()F3jckXB5(IrB@2g*WdXiMA=2B3;c3?N;f3DlNP+RjSn{P{?K+1i0; zJHBN@!(#R?Jz1Fmdg#vfZ2QdfUXoP>l4-qM?pF?QC+F{kR11ir8yHA?Yc&sb7w|^s zTlB9vMJk}I0Lso*!K=goV>~1Koehr}I?RDW*7+d@xbU5T$=*$o0`|?*)O>uAsa literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_My_dude_128x64/frame_38.png b/assets/dolphin/external/L1_My_dude_128x64/frame_38.png new file mode 100644 index 0000000000000000000000000000000000000000..70e56b168bf780745f329c7eabd85a4f48c5ae39 GIT binary patch literal 1650 zcmV-&295cNP)c;^d8YrnSe7xyUx8+fKgNH`1x3R% zt=owcj6aKV2Us!T-A-_d16H7g0XSqGW4Ey-c%mQLb;}Q9&~ZHDK9z#3q&t}OYI;4< z8OQkPkhqS^n{b;_2l!Xujs6|r7-)=7#{iTq0h$74l_SyU>gC~Emodx&RQ~@NAc+&B zpMxb^2?Lz0fGTvcSSLI(x9=eT|qT9Uxbfkd|NZge|1cU+$R7QT_lpl?d*{%uXoe z01?HmSgR#DDjdOzrc;bRt7o?ZSVz4(VC5cFjBw(qAKisyS-|B0(l2u&=u#S%MUpG( z2&WjCL-q1zIso;43#%39k&U||&HOBFr(;z7#EygN01;~09Wh=xxvd<1zk4niTEtUX zGdqEY>j>9J$6;Wrt-^EVAMvQ*??5~fZ9J;_w&yDTD^@HZxc zbUCP4#@O4yiiv{|+^ft}0??;+M{(Qg6E z9AUx_2cTJH6}oQ-Em<{3IVt)jXm-(-0t=7#$kTh{w@P8(Au2?od8Ir=|9x&Y$x z1pG|#cnujj3_1aodCU%QP9VJpXctzJBZ#{I?V{y$Cx{sBl#@OVSl3)47(L?VY$qNB zRifKzI{H7w8R*-i>oM0(oRZ}oUE$4Bz2Dv!hUW5k$5>L1>7 z8Sm){Y6RLP-R$>J|2_zkwh!Dp4W3AyfwV2MAZ5_>dG}Jlp;9I~@0;HINhSS%cJNt5 zo!~U1a?qSK(-+neSH51aanqztD(U|x;I}1$TDFhdWE>xt*g0;?Qz{U-sq4n7iz?jz zYH-^=8u>qxK#c(^ARQxDj3I41cSkeGkp0fu`*TS(cMrhX7uP?ukyOr3iV92nsy0^U zfTZ&5f--9c)*c?(aPPf#eI#RBA6G#0UJkPiA~A&r8RKW=k?JHnN&R~4c}vFCKJEaf zHaZ5>K^xk3hCQm-4fo+?6))pxc97P1+#c7{@|HY9`q&bD7Sy=d@LXFjq~uSVQ`8}{ z&d3>0d+#Z|?ccu&mLi@$S22J^=UED^LuGZWd=Uefy?KM3L02(G>wQj#aNt%5eEh_lb?#)^2& zE}r*VfA1UsH`3OcPFPBeCS_1+R7l%<#-fo`<&>O}aJ?rN@V-%YJEi_-r5b&(mV%PX?!|){I?2puCsFo{c-(BiC>hFrJRk9!Enn#_^!tCxX~93TrERj0vA|1-2}^#ku|8U4P}_}4{pfLTYtPO#b$D(HU&?P7VV wjk%QpsIo1ibb*@(m1C#Uu{^tte_hx318OBM5yM-Wwg3PC07*qoM6N<$g62{mX#fBK literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_My_dude_128x64/frame_39.png b/assets/dolphin/external/L1_My_dude_128x64/frame_39.png new file mode 100644 index 0000000000000000000000000000000000000000..450b4d4f63dbc666d19228fd3223d0ca0c621835 GIT binary patch literal 1028 zcmV+f1pE7mP){-C}4AW%O z6H`hl#u!pcVK<^e+CRn^LI{w4An=3ObTzr9um}!K%1->CHlOJNl=Rlg)~@G)oYy+Q zEGNioXoSS?kpze)

9$$O&zcY9YR~w&q?BeL2bjN@6GxES+45_>u$E0!_+J{6U}m z8bSQud&y5JndW#PWhefildzJx>i*jNwQmYJ0yK;SOLC11GlQr@!RMp< zuoI!*2F?@#b&+23PZWWh=Kvfv1m&NzIC@Avnf#Peh$-RzDtJ@`R^qvP)C5q9mGvR} z41W$Fp%hl)^SXIx)~2|sK6szKI)IjF$t@(8sLi9-J@jGL;dz_`(4?g(-NR43CGvXc z`e*e|BERVXt=Lfn-KUicB~g>NW<8AvrSKpI2}K8>jVNJBX&ig)E1yFCT52@KT4=?c z@6pydv;Lhp{2U-RsfIJ`ixVP|HgZ~}H9gq*iuK(-2WW{-I>l53^giwB=j))!=Q+7@ z08~?2PLM~8@~@6opzB>pevFPk1&oS-i=kHp=sI)WtJZ&{16W0nh0qfo>RgGBb$}ib zSnfsFwF|d70B!yX5ok}dMg(Y5Ug-dofED7hL~UM-&wm1F<|X~HlHP!t2qApUKq65&zJ^>{Wc*xHqR=*9*!C&*>^F~k`J#PqS z=J_TJXa!ybql+Xi!Z3J9;^K9HW3e?h?2ju>JJ2MdaMhsubpKWG>agbH0b8;2vA1@; z?(YKfCaVxlW>1|OQby~W-M>fwJ;1Kd$u%4aFXI2-Gu_|02jLgb=wvR!#eh%ZA`o39 zF;~$p^6-vitZ7;faB;JS@ yDz9ztS}*C(#=>ZSglUmpyZ?%1?}@Knm&PybUz7epZzaC~0000{{JuMJW)BWFJmx-?CD5Vwh3T^Zv(0J zIM4GW1IKX?5s}v9IF8SKuiX!ODaAzL<(WB#xwUgvqz zd7g9}$LAwcyLD~Qj4sENeSX*ja0#eL@75SU4sjn|M0!NG2)4fWT=^L7ej8gp`(X{f zGC-E-^$~k1ADuu3*Q>X-M;t->E|svjhh==CE06GYFxL3mIDZ{-efXz417w7I2G!bm z_p)tK{WND-l_5rWN_6fLtxI0M18Yws{l1HMX2cEN7#1q*9XJlo$fwy~Ve}82FE7H++ zd=b3+qxIFRl_&>@u2Px9?T*qNwlT5J2J@oODlL_z6m9p#0kp}^zFNwm7+uK7 zIb~`_N*|v?M@G$}D(zc(ppHEf5YgI6IhMZe7{q=Qig;0^ zSk~xhsZ?KLCxPH+~}zpD(*lAEy^k-`C1VAKgvZX(-OFS>SSlEDty=OqWp zZoe|9nlN_+^e}0cn!kcGW%yi}0e0Hza5P8o&Lnu-RXSMY>TRJ zhw-0^a2tpK-Sf^y4=Ei*hB&X@^IL5>-pE$SI!BI>IUpI`l4(bwE7LBQzjMl$cv}xN zgLn(;m68<)Mbh{?oH9!6SdL5w&@h|z_c)AY{5v9028+xJcj{x|Ya1xxEh^uNX`f!D z(PLfBS)vzOv#wG%si(F!JCusce+coEvQn(OZ>#Rw0!E zkO^Ck;Eme@#DP#J8!^_5F}HPqPp2U5%J!}To!hpkUuF@j8TyC;S35vz8`$7wgI)rZ zaRjfF+<6Yb} VUEO9qCnW#?002ovPDHLkV1gCk1=0Wj literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_My_dude_128x64/frame_40.png b/assets/dolphin/external/L1_My_dude_128x64/frame_40.png new file mode 100644 index 0000000000000000000000000000000000000000..369200345db3985d5b105b40ed3ba37e3a0ea311 GIT binary patch literal 1225 zcmV;)1UCDLP)WJa;M*fY6M zDWwomx%fMgGLX1<5L1x2cnm26iHlbOE*?V4K;q(6fQyHaGLX1<72x6_qzoi3UIn;# z2q^=Ji&p_I9zx1M;^I|+i-(Xhkhpji;Nl^q3?vQ&{dSz@a}-mM*jQx#o}W9-_h6pR zlURw>wDT+xDfn*36eJcGzn+u3pI`f}_>~HP6uEc(*I)_~8x)|P*REyYdjg-oZ|Hd* z%8b>J@$?fENwcnb{Lk{sgs%)F(B#(QdB)4RJ=6RlO?Ns^BlyZd0!=QGF3#f~?K(F$$n1A2Dkzq5_uBUbD95 zA@jdN0eBL$9gHGR-b~@5%%fEGy+kD5)lCrgO`vA2B1QV=>uBQ;AEWbE7Drk-1*oKW zGAh=Clb;D<##X8Yq5v!M8`T6;&j6AXM^1hu=;$U`X_7ZlyfW~0EdQJbgr)Sq3A7gF zNr()#wO-G9<7Tdp+(uU39<2O()>jpvI&C?t?^}yU1$9aRp6CDD7iznUZ`G&r8Bzc% z2$%EVAd%vEcPT$Fi|F-@@>{6{)zD`A_bI>{%$@Ek>g+su4oT36@7`Fe;db5@G<@+% zyNfSxCoLYW>!?*JFQxo5-VgLExJs`DjR8*-W*g_(B|K! z0FjGYL83*^ypt_%E#>R^=qW;U(YfDO>*TCcD=4JsS~3trcc7f(HH4dN2CKPuHUak= z@@rr(2yf9_Nn2rcJx`-OPydS|elePnHdHwCa3kM3x1aHD43e$er~IVg`TP4MPF zS^iqVG5AB_z_L8!y#C;ZFfpuo`*G4>~WSS^Ty0NPKAC zXX^J5QSp+DQ~<9cw62}BUS70uClo;XC88e%kSBL4KyQb0elqbD>w#Kpr^!REp{;G@ zg^!OY{;UE>zeE)K3@|Eth!R)@UhG`Vg|UOjQQ{H`v( z^w<19Kx7j{72jHK))2*M<%btf&PR*jDOQA{O25#jfZp|Ns1;_`Rx8{t|2!TlKdY5* zK*ac*nIl8I5lF3sXz#TIo(@9ewd*(?FIqo^uP>NvL3@+)t;HYB!`m}}v{JBVfY$h* n_4fbE6}k6CS(bQ^dT90sAA)%(Wh!_*00000NkvXXu0mjfk>5o{ literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_My_dude_128x64/frame_41.png b/assets/dolphin/external/L1_My_dude_128x64/frame_41.png new file mode 100644 index 0000000000000000000000000000000000000000..e0f882268275af59257326019b77b30c145070ac GIT binary patch literal 1256 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 JPDHLkV1iSbhV}pe literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_My_dude_128x64/frame_44.png b/assets/dolphin/external/L1_My_dude_128x64/frame_44.png new file mode 100644 index 0000000000000000000000000000000000000000..f425bcc179405dedfcbf095ce131b75707a636a7 GIT binary patch literal 623 zcmV-#0+9WQP)Hos!R)?YJ15L?jey!PUnW3d2QCjBTVZ%cj}7jHkcMWFgO>x-qa_c<7c zrrmkzL6SWeZc)<$ADB(l^IXO@gnbR96lhmg{`}KNnz9z4klwz4@^2SG{M{bRYfbZY9C-C?eZ? z08<2WutT!*$IDl8>F+Qm(~%xmRsIh!J7u>rGVe1Vb~B$ULNL8q%3tpNVpU*LEk6r) zW}XL4(eJ{&pJm*fOIiTAVkjkjZ~jxolH2d9A||KyVl3rjtzYe{s%B<4KE?n5002ov JPDHLkV1h`J6Q2M8 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_My_dude_128x64/frame_45.png b/assets/dolphin/external/L1_My_dude_128x64/frame_45.png new file mode 100644 index 0000000000000000000000000000000000000000..b0ea1a7e78c0b590c493451bc16d27dfbd421928 GIT binary patch literal 556 zcmeAS@N?(olHy`uVBq!ia0vp^4M6O`!3HERU8}DLQk(@Ik;M!Q+`=Ht$S`Y;1Oo%( zJx>?Mkcv5PXGZrm8wjwp-}!&;-uwtJfn#Pj-kl3l?)6d-s*)=yo9v;vw8P`WOdfW@ z01s}#fMW#$jD-vuNfsRq77QYWGA<4>3|$g@N(y`ou0S;c>oUFo?wu76 zg|E)ay31R7w|a7vg$Sp<6~mVVK5zDh?6&#L>=g>d4;X+}Sun_)3^l*K-1VbDYct0W z2KI(|U^l!-(!8>(ZsKyj3BkHTTrvsve}ei%E=zq{_E#C`xQrJJA0)x*Ds-}UKKQrm zQB<(4ifH=N=e66W1l`)E8{~EI@Xd>>k2h|WD4c)sZ}nZxGPSFR_XMS%4tRIXd{@1G zyPCDJQr8|~z0?2qr9U$M?)Ycc<|v*i`pt1HBA1+w-00nQ;o-UdIlmA0?bvZR#614) z^!1;8#baga_}U*O7HU4*Q_0t^^zfu}U4>AY`Q|CwU!T0Fy!*{=!;ed;V(CXuSw$`0 zt-rLl>~2KG`IUUupj%ioWi#iK@kh`xU5^^EHL^RJYD@<);T3K0RYV)<>vqZ literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_My_dude_128x64/frame_46.png b/assets/dolphin/external/L1_My_dude_128x64/frame_46.png new file mode 100644 index 0000000000000000000000000000000000000000..3113ff2e623dbc8f684d790420b396e3e1fd811c GIT binary patch literal 928 zcmV;R17G}!P)}cXvuQxv{iDYrc2Fmz3-sv83^Kk}L(s zc{R{tAn-^9&@$DGkSl=}04;`_3SfadNuCjJ#{_6GTvPz7WbBMP z8oMSyi{YXISS4d;+?kL}&>P2t7zn(+=I(<`%RIA2XqiW}VKoJh!)g!{17^ur#_q8nk~s zPGCO&A3;>SS@CJVmJveqDLwEc8su1!Ie|m;n^b^N?1_)&_Y_QMaYOv503kjQq3;4p zaQigqZvf~=EiX74-El0OCB;hrSouyWz-;XPf>p*S`{)&S_mzIE0!VGOzm@X?zw@mx zI(kxu$r@^Co>S<8L?8^42NDxx@4M2xq6-oe;-h&y4U-2F10-GWcqFLmLP%iW9IGLTJ9^)ozbq8^7tsZY zKp3WK#|5cCbJ0KhXflCtGvG-Cez3%``+Ask;7{>O7_ar67yzPur^Oeri<$h7|7w4UhF^2sml2^GJDY4u zt+kd?>cPK>B|{Mh|6{u!wr_@}4;}|NcpTv14lEgpICvc3;3g~?ia2;2;NT`K8H&h; z_!hZ8tRZCIXmX#1JBA>*zx_pILwp(|5!c|3JDSXlPze0oH)8Ra;N49FtWKP;La1>BDmjs(>qAm(L6zhN&gx66l01a z5)1(p2R;{B=V)J#I?7YUwazEa>$Puc2guem4l4(M`KXiVu`kX);{Zl|=MZ&O{1#gA zcZ#qTQxuWF0bs73{YCG8{X8hwbrnw>#cDo_`pTPxPreqB{li|Y`jX(wTXZZIhrRWj zXx8Gxu;ydbKQ{;?9#-^#&zm~{H`ShbxR~7!4n))moZa7wUpEK9ldZ`k6%(J784;B8=iPR1E%&q@wUcR}x zPm_f3+)zY|4{O=M&sA%!8Q{=l~I- zdp3{r?;ZkNSL2~Q7tKFaSFP_?T@Z)oe)zuC&4Ukb-lcf^6flZ*odj`2zRg&j{*}IG z9U+Pr3cLRcpfHo&_lB<4O4hMk{88TUp}-Dc)m*grjksP7)d@Kkgb|l~S=`9xC1Oa{^>U5YP_=nGpf)e8{Sgi@>j;!EvmqH4 zZ}Gz&AiMY+`O=du8HyF*+q$QT|40YO5U>_c(Q$b2_-YTdV$80iP(#j+h!}E>J{<3j z$69}W4*;8)-E4)7c0IBQtxuwbUCrjf>mYiBl`V7mv^RSq`UHXihxElf^zgU9a-~-CH;5 z>ZX#D_f6ur$bKkbowrW`J)2s#83tQkP5x}+xBjrb;}Z_Ai^f*Z#{syLjvre(!#CQ0 z;{wL6lP!Jx>n#Ajm|65uXvLT8!R?nFZ?zXR0GNmsC*bnz913mphH1B$vwFbz2SXzB Uk+^RXRR91007*qoM6N<$g2xy?&Hw-a literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_My_dude_128x64/frame_48.png b/assets/dolphin/external/L1_My_dude_128x64/frame_48.png new file mode 100644 index 0000000000000000000000000000000000000000..2734e2fcd57ef6b102da9c2a977264694246b63e GIT binary patch literal 1019 zcmVWDt>!^@==SY6v+uI=P?5h$0A%&%c7vK{+G^7KhfbovmUKNs2OGzjjAg382i^-IqbvR!$)z5w{8p+}aJUitM9m7ED2;&qY5^${xQ96Rp!zUljtpe~oNABIa4=K9YB%wMZ zf@Q23^h6Y4L5s@3wE}od@r53ikZaxX81u`S081vcR)#pH*_yNzLD)LrR~!g4wU*%c z@g|_SR>)5>pU5}?B9b$Lk2e9^1b7iWL`?v`J`*4~c!}L+>!r^|#s8=Ypb->gwZnUb z@feOcL?gJutH#&vM;Ad&Pc*;&UzFGDRlZ*P0$;DPhIPLcW@tU~UJpC8&Jt7Q#p;*38*u{_Ldao!k5luxh?n(OCB3fP03H zV5QTwex&(BOhlBq^R1PCRSZ3>n(xv1qp;??`W@iY%$Lt|Qh4z_UZckA{Z`|!ynlpU zC%{Vb(dO%pX_+AR9sxR@C|>@PzzpJBMbI*I1ik(4kSEsk9nW1Rz(^6Sz!C>KpQHIA z3BJAqTi43D8X5A?<5Ux1r;b^y=+(#+e=8qvzL1PGUy9>ocoo$Q&+OxA{!Sm{zO4UU z3no($>?j6H@$`_XiF!pV@4Nkrpok!55OEG0p26f9e;7pc^IO2L7lX~+T?|&#CXz;m zysR|mTivInx0He3d#~=J7`4!G zd*zLYa{uq(OvQkXZ)4utoqiR3*5usLWX6migV%FO$et|gLhF~Tu_He(u7Vnf#=aP~ pp1*pZH+vyUZ;cr<@y_}o@ef@Xp~TjZ6$$_V002ovPDHLkV1g9->bC#@ literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_My_dude_128x64/frame_5.png b/assets/dolphin/external/L1_My_dude_128x64/frame_5.png new file mode 100644 index 0000000000000000000000000000000000000000..df225594910480d7e00543e706d7286736ca814a GIT binary patch literal 1648 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_My_dude_128x64/frame_7.png b/assets/dolphin/external/L1_My_dude_128x64/frame_7.png new file mode 100644 index 0000000000000000000000000000000000000000..9703809405f7a9cb8e800272d21c3cdb499ede15 GIT binary patch literal 1063 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_My_dude_128x64/meta.txt b/assets/dolphin/external/L1_My_dude_128x64/meta.txt new file mode 100644 index 00000000000..8c326cf42de --- /dev/null +++ b/assets/dolphin/external/L1_My_dude_128x64/meta.txt @@ -0,0 +1,32 @@ +Filetype: Flipper Animation +Version: 1 + +Width: 128 +Height: 64 +Passive frames: 19 +Active frames: 51 +Frames order: 0 1 2 3 4 5 6 0 1 2 7 8 9 10 11 12 7 8 9 13 14 15 14 13 14 15 7 8 9 16 17 18 13 14 19 20 21 22 23 24 21 25 26 27 28 29 30 31 32 33 32 34 35 36 35 34 37 38 39 40 41 42 43 44 45 46 17 47 48 7 +Active cycles: 1 +Frame rate: 2 +Duration: 3600 +Active cooldown: 7 + +Bubble slots: 1 + +Slot: 0 +X: 41 +Y: 43 +Text: My dude +AlignH: Right +AlignV: Top +StartFrame: 50 +EndFrame: 50 + +Slot: 0 +X: 59 +Y: 43 +Text: My dude +AlignH: Left +AlignV: Top +StartFrame: 54 +EndFrame: 54 \ No newline at end of file diff --git a/assets/dolphin/external/manifest.txt b/assets/dolphin/external/manifest.txt index 55abe0ce8d2..4e3dbbf1105 100644 --- a/assets/dolphin/external/manifest.txt +++ b/assets/dolphin/external/manifest.txt @@ -97,6 +97,13 @@ Min butthurt: 0 Max butthurt: 10 Min level: 1 Max level: 3 +Weight: 3 + +Name: L1_My_dude_128x64 +Min butthurt: 0 +Max butthurt: 8 +Min level: 1 +Max level: 3 Weight: 4 Name: L2_Wake_up_128x64 From bf15d3ce7450490ad294145c318772caa0c06990 Mon Sep 17 00:00:00 2001 From: AloneLiberty <111039319+AloneLiberty@users.noreply.github.com> Date: Wed, 12 Jul 2023 09:14:11 +0000 Subject: [PATCH 657/824] NFC: Improved MFC emulation on some readers (#2825) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * NFC: Improved MFC emulation on some readers * NFC: Improved emulation on some readers (part 2): Some Android devices don't like this * NFC: Improved emulation on some readers (part 3): I knew that during the emulation timings are critical, but one log breaks all... * NFC: Improved emulation on some readers (part 4): Add fixes to Detect reader and refactor code * NFC: Improved emulation on some readers (part 5) * NFC: Improved emulation on some readers (part 6): GUI doesn't update without delay * NFC: Improved emulation on some readers (part 7): Reworked emulation flow, some bug fixes and improvements Co-authored-by: あく Co-authored-by: gornekich --- lib/nfc/nfc_worker.c | 11 +- lib/nfc/protocols/mifare_classic.c | 165 ++++++++++++++++++++--------- lib/nfc/protocols/nfca.c | 12 +-- lib/nfc/protocols/nfca.h | 3 + 4 files changed, 127 insertions(+), 64 deletions(-) diff --git a/lib/nfc/nfc_worker.c b/lib/nfc/nfc_worker.c index d2834fa46d0..145007bd3b3 100644 --- a/lib/nfc/nfc_worker.c +++ b/lib/nfc/nfc_worker.c @@ -1022,7 +1022,9 @@ void nfc_worker_emulate_mf_classic(NfcWorker* nfc_worker) { furi_hal_nfc_listen_start(nfc_data); while(nfc_worker->state == NfcWorkerStateMfClassicEmulate) { //-V1044 if(furi_hal_nfc_listen_rx(&tx_rx, 300)) { - mf_classic_emulator(&emulator, &tx_rx, false); + if(!mf_classic_emulator(&emulator, &tx_rx, false)) { + furi_hal_nfc_listen_start(nfc_data); + } } } if(emulator.data_changed) { @@ -1297,8 +1299,6 @@ void nfc_worker_analyze_reader(NfcWorker* nfc_worker) { bool reader_no_data_notified = true; while(nfc_worker->state == NfcWorkerStateAnalyzeReader) { - furi_hal_nfc_stop_cmd(); - furi_delay_ms(5); furi_hal_nfc_listen_start(nfc_data); if(furi_hal_nfc_listen_rx(&tx_rx, 300)) { if(reader_no_data_notified) { @@ -1309,7 +1309,9 @@ void nfc_worker_analyze_reader(NfcWorker* nfc_worker) { NfcProtocol protocol = reader_analyzer_guess_protocol(reader_analyzer, tx_rx.rx_data, tx_rx.rx_bits / 8); if(protocol == NfcDeviceProtocolMifareClassic) { - mf_classic_emulator(&emulator, &tx_rx, true); + if(!mf_classic_emulator(&emulator, &tx_rx, true)) { + furi_hal_nfc_listen_start(nfc_data); + } } } else { reader_no_data_received_cnt++; @@ -1321,6 +1323,7 @@ void nfc_worker_analyze_reader(NfcWorker* nfc_worker) { FURI_LOG_D(TAG, "No data from reader"); continue; } + furi_delay_ms(1); } rfal_platform_spi_release(); diff --git a/lib/nfc/protocols/mifare_classic.c b/lib/nfc/protocols/mifare_classic.c index ebe49a4a0e0..011747d59fd 100644 --- a/lib/nfc/protocols/mifare_classic.c +++ b/lib/nfc/protocols/mifare_classic.c @@ -851,16 +851,20 @@ bool mf_classic_emulator( bool is_reader_analyzer) { 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; + bool need_reset = false; + bool need_nack = false; + bool is_encrypted = false; + uint8_t sector = 0; + // Used for decrement and increment - copy to block on transfer - uint8_t transfer_buf[MF_CLASSIC_BLOCK_SIZE] = {}; + uint8_t transfer_buf[MF_CLASSIC_BLOCK_SIZE]; bool transfer_buf_valid = false; - // Read command - while(!command_processed) { //-V654 + // Process commands + while(!need_reset && !need_nack) { //-V654 + memset(plain_data, 0, MF_CLASSIC_MAX_DATA_SIZE); if(!is_encrypted) { crypto1_reset(&emulator->crypto); memcpy(plain_data, tx_rx->rx_data, tx_rx->rx_bits / 8); @@ -868,9 +872,10 @@ bool mf_classic_emulator( if(!furi_hal_nfc_tx_rx(tx_rx, 300)) { FURI_LOG_D( TAG, - "Error in tx rx. Tx :%d bits, Rx: %d bits", + "Error in tx rx. Tx: %d bits, Rx: %d bits", tx_rx->tx_bits, tx_rx->rx_bits); + need_reset = true; break; } crypto1_decrypt(&emulator->crypto, tx_rx->rx_data, tx_rx->rx_bits, plain_data); @@ -879,19 +884,28 @@ bool mf_classic_emulator( // After increment, decrement or restore the only allowed command is transfer uint8_t cmd = plain_data[0]; if(transfer_buf_valid && cmd != MF_CLASSIC_TRANSFER_CMD) { + need_nack = true; break; } - if(cmd == 0x50 && plain_data[1] == 0x00) { + if(cmd == NFCA_CMD_HALT && plain_data[1] == 0x00) { FURI_LOG_T(TAG, "Halt received"); - furi_hal_nfc_listen_sleep(); - command_processed = true; + need_reset = true; break; } + + if(cmd == NFCA_CMD_RATS) { + // Mifare Classic doesn't support ATS, NACK it and start listening again + FURI_LOG_T(TAG, "RATS received"); + need_nack = true; + break; + } + if(cmd == MF_CLASSIC_AUTH_KEY_A_CMD || cmd == MF_CLASSIC_AUTH_KEY_B_CMD) { uint8_t block = plain_data[1]; uint64_t key = 0; uint8_t sector_trailer_block = mf_classic_get_sector_trailer_num_by_block(block); + sector = mf_classic_get_sector_by_block(block); MfClassicSectorTrailer* sector_trailer = (MfClassicSectorTrailer*)emulator->data.block[sector_trailer_block].value; if(cmd == MF_CLASSIC_AUTH_KEY_A_CMD) { @@ -902,7 +916,7 @@ bool mf_classic_emulator( access_key = MfClassicKeyA; } else { FURI_LOG_D(TAG, "Key not known"); - command_processed = true; + need_nack = true; break; } } else { @@ -913,7 +927,7 @@ bool mf_classic_emulator( access_key = MfClassicKeyB; } else { FURI_LOG_D(TAG, "Key not known"); - command_processed = true; + need_nack = true; break; } } @@ -942,15 +956,15 @@ bool mf_classic_emulator( 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; + need_reset = true; break; } if(tx_rx->rx_bits != 64) { - FURI_LOG_W(TAG, "Incorrect nr + ar length: %d", tx_rx->rx_bits); - command_processed = true; + need_reset = true; break; } @@ -960,9 +974,14 @@ bool mf_classic_emulator( 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! %08lX != %08lX", cardRr, prng_successor(nonce, 64)); + FURI_LOG_T( + TAG, + "Wrong AUTH on block %u! %08lX != %08lX", + block, + cardRr, + prng_successor(nonce, 64)); // Don't send NACK, as the tag doesn't send it - command_processed = true; + need_reset = true; break; } @@ -985,11 +1004,25 @@ bool mf_classic_emulator( if(!is_encrypted) { FURI_LOG_T(TAG, "Invalid command before auth session established: %02X", cmd); + need_nack = true; break; } - if(cmd == MF_CLASSIC_READ_BLOCK_CMD) { - uint8_t block = plain_data[1]; + // Mifare Classic commands always have block number after command + uint8_t block = plain_data[1]; + if(mf_classic_get_sector_by_block(block) != sector) { + // Don't allow access to sectors other than authorized + FURI_LOG_T( + TAG, + "Trying to access block %u from not authorized sector (command: %02X)", + block, + cmd); + need_nack = true; + break; + } + + switch(cmd) { + case MF_CLASSIC_READ_BLOCK_CMD: { uint8_t block_data[MF_CLASSIC_BLOCK_SIZE + 2] = {}; memcpy(block_data, emulator->data.block[block].value, MF_CLASSIC_BLOCK_SIZE); if(mf_classic_is_sector_trailer(block)) { @@ -1005,17 +1038,14 @@ bool mf_classic_emulator( emulator, block, access_key, MfClassicActionACRead)) { memset(&block_data[6], 0, 4); } - } else if(!mf_classic_is_allowed_access( - emulator, block, access_key, MfClassicActionDataRead)) { - // Send NACK - uint8_t nack = 0x04; - crypto1_encrypt( - &emulator->crypto, NULL, &nack, 4, tx_rx->tx_data, tx_rx->tx_parity); - tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; - tx_rx->tx_bits = 4; - furi_hal_nfc_tx_rx(tx_rx, 300); + } else if( + !mf_classic_is_allowed_access( + emulator, block, access_key, MfClassicActionDataRead) || + !mf_classic_is_block_read(&emulator->data, block)) { + need_nack = true; break; } + nfca_append_crc16(block_data, 16); crypto1_encrypt( @@ -1027,23 +1057,36 @@ bool mf_classic_emulator( tx_rx->tx_parity); tx_rx->tx_bits = (MF_CLASSIC_BLOCK_SIZE + 2) * 8; tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; - } else if(cmd == MF_CLASSIC_WRITE_BLOCK_CMD) { - uint8_t block = plain_data[1]; - if(block > mf_classic_get_total_block_num(emulator->data.type)) { - break; - } + break; + } + + case MF_CLASSIC_WRITE_BLOCK_CMD: { // Send ACK uint8_t ack = MF_CLASSIC_ACK_CMD; 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 != (MF_CLASSIC_BLOCK_SIZE + 2) * 8) break; + if(!furi_hal_nfc_tx_rx(tx_rx, 300)) { + need_reset = true; + break; + } + + if(tx_rx->rx_bits != (MF_CLASSIC_BLOCK_SIZE + 2) * 8) { + need_reset = true; + break; + } crypto1_decrypt(&emulator->crypto, tx_rx->rx_data, tx_rx->rx_bits, plain_data); uint8_t block_data[MF_CLASSIC_BLOCK_SIZE] = {}; memcpy(block_data, emulator->data.block[block].value, MF_CLASSIC_BLOCK_SIZE); + + if(!mf_classic_is_block_read(&emulator->data, block)) { + // Don't allow writing to the block for which we haven't read data yet + need_nack = true; + break; + } + if(mf_classic_is_sector_trailer(block)) { if(mf_classic_is_allowed_access( emulator, block, access_key, MfClassicActionKeyAWrite)) { @@ -1062,38 +1105,39 @@ bool mf_classic_emulator( emulator, block, access_key, MfClassicActionDataWrite)) { memcpy(block_data, plain_data, MF_CLASSIC_BLOCK_SIZE); } else { + need_nack = true; break; } } + if(memcmp(block_data, emulator->data.block[block].value, MF_CLASSIC_BLOCK_SIZE) != 0) { memcpy(emulator->data.block[block].value, block_data, MF_CLASSIC_BLOCK_SIZE); emulator->data_changed = true; } + // Send ACK ack = MF_CLASSIC_ACK_CMD; 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 if( - cmd == MF_CLASSIC_DECREMENT_CMD || cmd == MF_CLASSIC_INCREMENT_CMD || - cmd == MF_CLASSIC_RESTORE_CMD) { - uint8_t block = plain_data[1]; + break; + } - if(block > mf_classic_get_total_block_num(emulator->data.type)) { - break; - } + case MF_CLASSIC_DECREMENT_CMD: + case MF_CLASSIC_INCREMENT_CMD: + case MF_CLASSIC_RESTORE_CMD: { + MfClassicAction action = (cmd == MF_CLASSIC_INCREMENT_CMD) ? MfClassicActionDataInc : + MfClassicActionDataDec; - MfClassicAction action = MfClassicActionDataDec; - if(cmd == MF_CLASSIC_INCREMENT_CMD) { - action = MfClassicActionDataInc; - } if(!mf_classic_is_allowed_access(emulator, block, access_key, action)) { + need_nack = true; break; } int32_t prev_value; uint8_t addr; if(!mf_classic_block_to_value(emulator->data.block[block].value, &prev_value, &addr)) { + need_nack = true; break; } @@ -1103,8 +1147,15 @@ bool mf_classic_emulator( 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 != (sizeof(int32_t) + 2) * 8) break; + if(!furi_hal_nfc_tx_rx(tx_rx, 300)) { + need_reset = true; + break; + } + + if(tx_rx->rx_bits != (sizeof(int32_t) + 2) * 8) { + need_reset = true; + break; + } crypto1_decrypt(&emulator->crypto, tx_rx->rx_data, tx_rx->rx_bits, plain_data); int32_t value = *(int32_t*)&plain_data[0]; @@ -1121,9 +1172,12 @@ bool mf_classic_emulator( transfer_buf_valid = true; // Commands do not ACK tx_rx->tx_bits = 0; - } else if(cmd == MF_CLASSIC_TRANSFER_CMD) { - uint8_t block = plain_data[1]; + break; + } + + case MF_CLASSIC_TRANSFER_CMD: { if(!mf_classic_is_allowed_access(emulator, block, access_key, MfClassicActionDataDec)) { + need_nack = true; break; } if(memcmp(transfer_buf, emulator->data.block[block].value, MF_CLASSIC_BLOCK_SIZE) != @@ -1137,13 +1191,17 @@ bool mf_classic_emulator( 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 { + break; + } + + default: FURI_LOG_T(TAG, "Unknown command: %02X", cmd); + need_nack = true; break; } } - if(!command_processed) { + if(need_nack && !need_reset) { // Send NACK uint8_t nack = transfer_buf_valid ? MF_CLASSIC_NACK_BUF_VALID_CMD : MF_CLASSIC_NACK_BUF_INVALID_CMD; @@ -1155,15 +1213,16 @@ bool mf_classic_emulator( tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; tx_rx->tx_bits = 4; furi_hal_nfc_tx_rx(tx_rx, 300); + need_reset = true; } - return true; + return !need_reset; } void mf_classic_halt(FuriHalNfcTxRxContext* tx_rx, Crypto1* crypto) { furi_assert(tx_rx); - uint8_t plain_data[4] = {0x50, 0x00, 0x00, 0x00}; + uint8_t plain_data[4] = {NFCA_CMD_HALT, 0x00, 0x00, 0x00}; nfca_append_crc16(plain_data, 2); if(crypto) { diff --git a/lib/nfc/protocols/nfca.c b/lib/nfc/protocols/nfca.c index c401f8cc587..ab4f3f23c60 100644 --- a/lib/nfc/protocols/nfca.c +++ b/lib/nfc/protocols/nfca.c @@ -3,8 +3,6 @@ #include #include -#define NFCA_CMD_RATS (0xE0U) - #define NFCA_CRC_INIT (0x6363) #define NFCA_F_SIG (13560000.0) @@ -22,7 +20,7 @@ typedef struct { static uint8_t nfca_default_ats[] = {0x05, 0x78, 0x80, 0x80, 0x00}; -static uint8_t nfca_sleep_req[] = {0x50, 0x00}; +static uint8_t nfca_halt_req[] = {NFCA_CMD_HALT, 0x00}; uint16_t nfca_get_crc16(uint8_t* buff, uint16_t len) { uint16_t crc = NFCA_CRC_INIT; @@ -50,17 +48,17 @@ bool nfca_emulation_handler( uint16_t buff_rx_len, uint8_t* buff_tx, uint16_t* buff_tx_len) { - bool sleep = false; + bool halt = false; uint8_t rx_bytes = buff_rx_len / 8; - if(rx_bytes == sizeof(nfca_sleep_req) && !memcmp(buff_rx, nfca_sleep_req, rx_bytes)) { - sleep = true; + if(rx_bytes == sizeof(nfca_halt_req) && !memcmp(buff_rx, nfca_halt_req, rx_bytes)) { + halt = true; } else if(rx_bytes == sizeof(nfca_cmd_rats) && buff_rx[0] == NFCA_CMD_RATS) { memcpy(buff_tx, nfca_default_ats, sizeof(nfca_default_ats)); *buff_tx_len = sizeof(nfca_default_ats) * 8; } - return sleep; + return halt; } static void nfca_add_bit(DigitalSignal* signal, bool bit) { diff --git a/lib/nfc/protocols/nfca.h b/lib/nfc/protocols/nfca.h index 498ef28431c..e4978a3e0b5 100644 --- a/lib/nfc/protocols/nfca.h +++ b/lib/nfc/protocols/nfca.h @@ -5,6 +5,9 @@ #include +#define NFCA_CMD_RATS (0xE0U) +#define NFCA_CMD_HALT (0x50U) + typedef struct { DigitalSignal* one; DigitalSignal* zero; From 25ec09c7eb3682473c19047b992c3ad05cd0b7c0 Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Wed, 12 Jul 2023 13:41:46 +0400 Subject: [PATCH 658/824] SubGhz: fix check connect cc1101_ext (#2857) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * SubGhz: fix check connect cc1101_ext * SubGhz: fix syntax * SubGhz: enable interface pin pullups * SubGhz: fix syntax * SubGhz: fix CLI check connect CC1101_ext * SubGhz: fix CLI display of the selected device Co-authored-by: あく --- .../drivers/subghz/cc1101_ext/cc1101_ext.c | 55 +++++++++--- applications/main/subghz/subghz_cli.c | 34 +++++--- .../targets/f7/furi_hal/furi_hal_spi_config.c | 49 ++++++++++- lib/drivers/cc1101.c | 83 ++++++++++--------- lib/drivers/cc1101.h | 28 +++++-- lib/drivers/cc1101_regs.h | 2 +- 6 files changed, 178 insertions(+), 73 deletions(-) diff --git a/applications/drivers/subghz/cc1101_ext/cc1101_ext.c b/applications/drivers/subghz/cc1101_ext/cc1101_ext.c index 896b9bd2f60..594a74a0150 100644 --- a/applications/drivers/subghz/cc1101_ext/cc1101_ext.c +++ b/applications/drivers/subghz/cc1101_ext/cc1101_ext.c @@ -87,6 +87,7 @@ static bool subghz_device_cc1101_ext_check_init() { subghz_device_cc1101_ext->state = SubGhzDeviceCC1101ExtStateIdle; bool ret = false; + CC1101Status cc1101_status = {0}; furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle); FuriHalCortexTimer timer = furi_hal_cortex_timer_get(100 * 1000); @@ -94,16 +95,34 @@ static bool subghz_device_cc1101_ext_check_init() { // Reset furi_hal_gpio_init( subghz_device_cc1101_ext->g0_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); - cc1101_reset(subghz_device_cc1101_ext->spi_bus_handle); - cc1101_write_reg( + furi_hal_gpio_init( + subghz_device_cc1101_ext->spi_bus_handle->miso, + GpioModeInput, + GpioPullUp, + GpioSpeedLow); + + cc1101_status = cc1101_reset(subghz_device_cc1101_ext->spi_bus_handle); + if(cc1101_status.CHIP_RDYn != 0) { + //timeout or error + break; + } + cc1101_status = cc1101_write_reg( subghz_device_cc1101_ext->spi_bus_handle, CC1101_IOCFG0, CC1101IocfgHighImpedance); - + if(cc1101_status.CHIP_RDYn != 0) { + //timeout or error + break; + } // Prepare GD0 for power on self test furi_hal_gpio_init( - subghz_device_cc1101_ext->g0_pin, GpioModeInput, GpioPullNo, GpioSpeedLow); + subghz_device_cc1101_ext->g0_pin, GpioModeInput, GpioPullUp, GpioSpeedLow); // GD0 low - cc1101_write_reg(subghz_device_cc1101_ext->spi_bus_handle, CC1101_IOCFG0, CC1101IocfgHW); + cc1101_status = cc1101_write_reg( + subghz_device_cc1101_ext->spi_bus_handle, CC1101_IOCFG0, CC1101IocfgHW); + if(cc1101_status.CHIP_RDYn != 0) { + //timeout or error + break; + } while(furi_hal_gpio_read(subghz_device_cc1101_ext->g0_pin) != false) { if(furi_hal_cortex_timer_is_expired(timer)) { //timeout @@ -116,10 +135,16 @@ static bool subghz_device_cc1101_ext_check_init() { } // GD0 high - cc1101_write_reg( + furi_hal_gpio_init( + subghz_device_cc1101_ext->g0_pin, GpioModeInput, GpioPullDown, GpioSpeedLow); + cc1101_status = cc1101_write_reg( subghz_device_cc1101_ext->spi_bus_handle, CC1101_IOCFG0, CC1101IocfgHW | CC1101_IOCFG_INV); + if(cc1101_status.CHIP_RDYn != 0) { + //timeout or error + break; + } while(furi_hal_gpio_read(subghz_device_cc1101_ext->g0_pin) != true) { if(furi_hal_cortex_timer_is_expired(timer)) { //timeout @@ -132,17 +157,21 @@ static bool subghz_device_cc1101_ext_check_init() { } // Reset GD0 to floating state - cc1101_write_reg( + cc1101_status = cc1101_write_reg( subghz_device_cc1101_ext->spi_bus_handle, CC1101_IOCFG0, CC1101IocfgHighImpedance); + if(cc1101_status.CHIP_RDYn != 0) { + //timeout or error + break; + } furi_hal_gpio_init( subghz_device_cc1101_ext->g0_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); - // RF switches - furi_hal_gpio_init(&gpio_rf_sw_0, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); - cc1101_write_reg(subghz_device_cc1101_ext->spi_bus_handle, CC1101_IOCFG2, CC1101IocfgHW); - // Go to sleep - cc1101_shutdown(subghz_device_cc1101_ext->spi_bus_handle); + cc1101_status = cc1101_shutdown(subghz_device_cc1101_ext->spi_bus_handle); + if(cc1101_status.CHIP_RDYn != 0) { + //timeout or error + break; + } ret = true; } while(false); @@ -152,6 +181,8 @@ static bool subghz_device_cc1101_ext_check_init() { FURI_LOG_I(TAG, "Init OK"); } else { FURI_LOG_E(TAG, "Init failed"); + furi_hal_gpio_init( + subghz_device_cc1101_ext->g0_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); } return ret; } diff --git a/applications/main/subghz/subghz_cli.c b/applications/main/subghz/subghz_cli.c index bc7be507e73..fe97c8a0671 100644 --- a/applications/main/subghz/subghz_cli.c +++ b/applications/main/subghz/subghz_cli.c @@ -28,12 +28,20 @@ #define SUBGHZ_REGION_FILENAME "/int/.region_data" +#define TAG "SubGhz CLI" + static void subghz_cli_radio_device_power_on() { - uint8_t attempts = 0; - while(!furi_hal_power_is_otg_enabled() && attempts++ < 5) { - furi_hal_power_enable_otg(); - //CC1101 power-up time - furi_delay_ms(10); + uint8_t attempts = 5; + while(--attempts > 0) { + if(furi_hal_power_enable_otg()) break; + } + if(attempts == 0) { + if(furi_hal_power_get_usb_voltage() < 4.5f) { + FURI_LOG_E( + "TAG", + "Error power otg enable. BQ2589 check otg fault = %d", + furi_hal_power_check_otg_fault() ? 1 : 0); + } } } @@ -126,9 +134,9 @@ void subghz_cli_command_rx_carrier(Cli* cli, FuriString* args, void* context) { furi_hal_subghz_sleep(); } -static const SubGhzDevice* subghz_cli_command_get_device(uint32_t device_ind) { +static const SubGhzDevice* subghz_cli_command_get_device(uint32_t* device_ind) { const SubGhzDevice* device = NULL; - switch(device_ind) { + switch(*device_ind) { case 1: subghz_cli_radio_device_power_on(); device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_EXT_NAME); @@ -138,6 +146,12 @@ static const SubGhzDevice* subghz_cli_command_get_device(uint32_t device_ind) { device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME); break; } + //check if the device is connected + if(!subghz_devices_is_connect(device)) { + subghz_cli_radio_device_power_off(); + device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME); + *device_ind = 0; + } return device; } @@ -175,7 +189,7 @@ void subghz_cli_command_tx(Cli* cli, FuriString* args, void* context) { } } subghz_devices_init(); - const SubGhzDevice* device = subghz_cli_command_get_device(device_ind); + const SubGhzDevice* device = subghz_cli_command_get_device(&device_ind); if(!subghz_devices_is_frequency_valid(device, frequency)) { printf( "Frequency must be in " SUBGHZ_FREQUENCY_RANGE_STR " range, not %lu\r\n", frequency); @@ -295,7 +309,7 @@ void subghz_cli_command_rx(Cli* cli, FuriString* args, void* context) { } } subghz_devices_init(); - const SubGhzDevice* device = subghz_cli_command_get_device(device_ind); + const SubGhzDevice* device = subghz_cli_command_get_device(&device_ind); if(!subghz_devices_is_frequency_valid(device, frequency)) { printf( "Frequency must be in " SUBGHZ_FREQUENCY_RANGE_STR " range, not %lu\r\n", frequency); @@ -688,7 +702,7 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) { } } subghz_devices_init(); - const SubGhzDevice* device = subghz_cli_command_get_device(device_ind); + const SubGhzDevice* device = subghz_cli_command_get_device(&device_ind); if(!subghz_devices_is_frequency_valid(device, frequency)) { printf( "Frequency must be in " SUBGHZ_FREQUENCY_RANGE_STR " range, not %lu\r\n", frequency); diff --git a/firmware/targets/f7/furi_hal/furi_hal_spi_config.c b/firmware/targets/f7/furi_hal/furi_hal_spi_config.c index 09ac79d2a3c..757ac23661d 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_spi_config.c +++ b/firmware/targets/f7/furi_hal/furi_hal_spi_config.c @@ -192,6 +192,52 @@ inline static void furi_hal_spi_bus_r_handle_event_callback( } } +inline static void furi_hal_spi_bus_external_handle_event_callback( + FuriHalSpiBusHandle* handle, + FuriHalSpiBusHandleEvent event, + const LL_SPI_InitTypeDef* preset) { + if(event == FuriHalSpiBusHandleEventInit) { + furi_hal_gpio_write(handle->cs, true); + furi_hal_gpio_init(handle->cs, GpioModeOutputPushPull, GpioPullUp, GpioSpeedVeryHigh); + } else if(event == FuriHalSpiBusHandleEventDeinit) { + furi_hal_gpio_write(handle->cs, true); + furi_hal_gpio_init(handle->cs, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + } else if(event == FuriHalSpiBusHandleEventActivate) { + LL_SPI_Init(handle->bus->spi, (LL_SPI_InitTypeDef*)preset); + LL_SPI_SetRxFIFOThreshold(handle->bus->spi, LL_SPI_RX_FIFO_TH_QUARTER); + LL_SPI_Enable(handle->bus->spi); + + furi_hal_gpio_init_ex( + handle->miso, + GpioModeAltFunctionPushPull, + GpioPullDown, + GpioSpeedVeryHigh, + GpioAltFn5SPI1); + furi_hal_gpio_init_ex( + handle->mosi, + GpioModeAltFunctionPushPull, + GpioPullDown, + GpioSpeedVeryHigh, + GpioAltFn5SPI1); + furi_hal_gpio_init_ex( + handle->sck, + GpioModeAltFunctionPushPull, + GpioPullDown, + GpioSpeedVeryHigh, + GpioAltFn5SPI1); + + furi_hal_gpio_write(handle->cs, false); + } else if(event == FuriHalSpiBusHandleEventDeactivate) { + furi_hal_gpio_write(handle->cs, true); + + furi_hal_gpio_init(handle->miso, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_init(handle->mosi, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_init(handle->sck, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + + LL_SPI_Disable(handle->bus->spi); + } +} + inline static void furi_hal_spi_bus_nfc_handle_event_callback( FuriHalSpiBusHandle* handle, FuriHalSpiBusHandleEvent event, @@ -291,7 +337,8 @@ FuriHalSpiBusHandle furi_hal_spi_bus_handle_nfc = { static void furi_hal_spi_bus_handle_external_event_callback( FuriHalSpiBusHandle* handle, FuriHalSpiBusHandleEvent event) { - furi_hal_spi_bus_r_handle_event_callback(handle, event, &furi_hal_spi_preset_1edge_low_2m); + furi_hal_spi_bus_external_handle_event_callback( + handle, event, &furi_hal_spi_preset_1edge_low_2m); } FuriHalSpiBusHandle furi_hal_spi_bus_handle_external = { diff --git a/lib/drivers/cc1101.c b/lib/drivers/cc1101.c index d0feb021878..85d915acdcc 100644 --- a/lib/drivers/cc1101.c +++ b/lib/drivers/cc1101.c @@ -1,14 +1,27 @@ #include "cc1101.h" #include #include +#include + +static bool cc1101_spi_trx(FuriHalSpiBusHandle* handle, uint8_t* tx, uint8_t* rx, uint8_t size) { + FuriHalCortexTimer timer = furi_hal_cortex_timer_get(CC1101_TIMEOUT * 1000); + + while(furi_hal_gpio_read(handle->miso)) { + if(furi_hal_cortex_timer_is_expired(timer)) { + //timeout + return false; + } + } + if(!furi_hal_spi_bus_trx(handle, tx, rx, size, CC1101_TIMEOUT)) return false; + return true; +} CC1101Status cc1101_strobe(FuriHalSpiBusHandle* handle, uint8_t strobe) { uint8_t tx[1] = {strobe}; CC1101Status rx[1] = {0}; + rx[0].CHIP_RDYn = 1; - while(furi_hal_gpio_read(handle->miso)) - ; - furi_hal_spi_bus_trx(handle, tx, (uint8_t*)rx, 1, CC1101_TIMEOUT); + cc1101_spi_trx(handle, tx, (uint8_t*)rx, 1); assert(rx[0].CHIP_RDYn == 0); return rx[0]; @@ -17,10 +30,10 @@ CC1101Status cc1101_strobe(FuriHalSpiBusHandle* handle, uint8_t strobe) { CC1101Status cc1101_write_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t data) { uint8_t tx[2] = {reg, data}; CC1101Status rx[2] = {0}; + rx[0].CHIP_RDYn = 1; + rx[1].CHIP_RDYn = 1; - while(furi_hal_gpio_read(handle->miso)) - ; - furi_hal_spi_bus_trx(handle, tx, (uint8_t*)rx, 2, CC1101_TIMEOUT); + cc1101_spi_trx(handle, tx, (uint8_t*)rx, 2); assert((rx[0].CHIP_RDYn | rx[1].CHIP_RDYn) == 0); return rx[1]; @@ -30,10 +43,9 @@ CC1101Status cc1101_read_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* assert(sizeof(CC1101Status) == 1); uint8_t tx[2] = {reg | CC1101_READ, 0}; CC1101Status rx[2] = {0}; + rx[0].CHIP_RDYn = 1; - while(furi_hal_gpio_read(handle->miso)) - ; - furi_hal_spi_bus_trx(handle, tx, (uint8_t*)rx, 2, CC1101_TIMEOUT); + cc1101_spi_trx(handle, tx, (uint8_t*)rx, 2); assert((rx[0].CHIP_RDYn) == 0); *data = *(uint8_t*)&rx[1]; @@ -58,40 +70,40 @@ uint8_t cc1101_get_rssi(FuriHalSpiBusHandle* handle) { return rssi; } -void cc1101_reset(FuriHalSpiBusHandle* handle) { - cc1101_strobe(handle, CC1101_STROBE_SRES); +CC1101Status cc1101_reset(FuriHalSpiBusHandle* handle) { + return cc1101_strobe(handle, CC1101_STROBE_SRES); } CC1101Status cc1101_get_status(FuriHalSpiBusHandle* handle) { return cc1101_strobe(handle, CC1101_STROBE_SNOP); } -void cc1101_shutdown(FuriHalSpiBusHandle* handle) { - cc1101_strobe(handle, CC1101_STROBE_SPWD); +CC1101Status cc1101_shutdown(FuriHalSpiBusHandle* handle) { + return cc1101_strobe(handle, CC1101_STROBE_SPWD); } -void cc1101_calibrate(FuriHalSpiBusHandle* handle) { - cc1101_strobe(handle, CC1101_STROBE_SCAL); +CC1101Status cc1101_calibrate(FuriHalSpiBusHandle* handle) { + return cc1101_strobe(handle, CC1101_STROBE_SCAL); } -void cc1101_switch_to_idle(FuriHalSpiBusHandle* handle) { - cc1101_strobe(handle, CC1101_STROBE_SIDLE); +CC1101Status cc1101_switch_to_idle(FuriHalSpiBusHandle* handle) { + return cc1101_strobe(handle, CC1101_STROBE_SIDLE); } -void cc1101_switch_to_rx(FuriHalSpiBusHandle* handle) { - cc1101_strobe(handle, CC1101_STROBE_SRX); +CC1101Status cc1101_switch_to_rx(FuriHalSpiBusHandle* handle) { + return cc1101_strobe(handle, CC1101_STROBE_SRX); } -void cc1101_switch_to_tx(FuriHalSpiBusHandle* handle) { - cc1101_strobe(handle, CC1101_STROBE_STX); +CC1101Status cc1101_switch_to_tx(FuriHalSpiBusHandle* handle) { + return cc1101_strobe(handle, CC1101_STROBE_STX); } -void cc1101_flush_rx(FuriHalSpiBusHandle* handle) { - cc1101_strobe(handle, CC1101_STROBE_SFRX); +CC1101Status cc1101_flush_rx(FuriHalSpiBusHandle* handle) { + return cc1101_strobe(handle, CC1101_STROBE_SFRX); } -void cc1101_flush_tx(FuriHalSpiBusHandle* handle) { - cc1101_strobe(handle, CC1101_STROBE_SFTX); +CC1101Status cc1101_flush_tx(FuriHalSpiBusHandle* handle) { + return cc1101_strobe(handle, CC1101_STROBE_SFTX); } uint32_t cc1101_set_frequency(FuriHalSpiBusHandle* handle, uint32_t value) { @@ -123,12 +135,12 @@ uint32_t cc1101_set_intermediate_frequency(FuriHalSpiBusHandle* handle, uint32_t void cc1101_set_pa_table(FuriHalSpiBusHandle* handle, const uint8_t value[8]) { uint8_t tx[9] = {CC1101_PATABLE | CC1101_BURST}; //-V1009 CC1101Status rx[9] = {0}; + rx[0].CHIP_RDYn = 1; + rx[8].CHIP_RDYn = 1; memcpy(&tx[1], &value[0], 8); - while(furi_hal_gpio_read(handle->miso)) - ; - furi_hal_spi_bus_trx(handle, tx, (uint8_t*)rx, sizeof(rx), CC1101_TIMEOUT); + cc1101_spi_trx(handle, tx, (uint8_t*)rx, sizeof(rx)); assert((rx[0].CHIP_RDYn | rx[8].CHIP_RDYn) == 0); } @@ -139,12 +151,7 @@ uint8_t cc1101_write_fifo(FuriHalSpiBusHandle* handle, const uint8_t* data, uint buff_tx[0] = CC1101_FIFO | CC1101_BURST; memcpy(&buff_tx[1], data, size); - // Start transaction - // Wait IC to become ready - while(furi_hal_gpio_read(handle->miso)) - ; - // Tell IC what we want - furi_hal_spi_bus_trx(handle, buff_tx, (uint8_t*)buff_rx, size + 1, CC1101_TIMEOUT); + cc1101_spi_trx(handle, buff_tx, (uint8_t*)buff_rx, size + 1); return size; } @@ -153,13 +160,7 @@ uint8_t cc1101_read_fifo(FuriHalSpiBusHandle* handle, uint8_t* data, uint8_t* si uint8_t buff_trx[2]; buff_trx[0] = CC1101_FIFO | CC1101_READ | CC1101_BURST; - // Start transaction - // Wait IC to become ready - while(furi_hal_gpio_read(handle->miso)) - ; - - // First byte - packet length - furi_hal_spi_bus_trx(handle, buff_trx, buff_trx, 2, CC1101_TIMEOUT); + cc1101_spi_trx(handle, buff_trx, buff_trx, 2); // Check that the packet is placed in the receive buffer if(buff_trx[1] > 64) { diff --git a/lib/drivers/cc1101.h b/lib/drivers/cc1101.h index af1f15569d1..d8ee05d5289 100644 --- a/lib/drivers/cc1101.h +++ b/lib/drivers/cc1101.h @@ -46,8 +46,10 @@ CC1101Status cc1101_read_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* /** Reset * * @param handle - pointer to FuriHalSpiHandle + * + * @return CC1101Status structure */ -void cc1101_reset(FuriHalSpiBusHandle* handle); +CC1101Status cc1101_reset(FuriHalSpiBusHandle* handle); /** Get status * @@ -60,8 +62,10 @@ CC1101Status cc1101_get_status(FuriHalSpiBusHandle* handle); /** Enable shutdown mode * * @param handle - pointer to FuriHalSpiHandle + * + * @return CC1101Status structure */ -void cc1101_shutdown(FuriHalSpiBusHandle* handle); +CC1101Status cc1101_shutdown(FuriHalSpiBusHandle* handle); /** Get Partnumber * @@ -90,38 +94,46 @@ uint8_t cc1101_get_rssi(FuriHalSpiBusHandle* handle); /** Calibrate oscillator * * @param handle - pointer to FuriHalSpiHandle + * + * @return CC1101Status structure */ -void cc1101_calibrate(FuriHalSpiBusHandle* handle); +CC1101Status cc1101_calibrate(FuriHalSpiBusHandle* handle); /** Switch to idle * * @param handle - pointer to FuriHalSpiHandle */ -void cc1101_switch_to_idle(FuriHalSpiBusHandle* handle); +CC1101Status cc1101_switch_to_idle(FuriHalSpiBusHandle* handle); /** Switch to RX * * @param handle - pointer to FuriHalSpiHandle + * + * @return CC1101Status structure */ -void cc1101_switch_to_rx(FuriHalSpiBusHandle* handle); +CC1101Status cc1101_switch_to_rx(FuriHalSpiBusHandle* handle); /** Switch to TX * * @param handle - pointer to FuriHalSpiHandle + * + * @return CC1101Status structure */ -void cc1101_switch_to_tx(FuriHalSpiBusHandle* handle); +CC1101Status cc1101_switch_to_tx(FuriHalSpiBusHandle* handle); /** Flush RX FIFO * * @param handle - pointer to FuriHalSpiHandle + * + * @return CC1101Status structure */ -void cc1101_flush_rx(FuriHalSpiBusHandle* handle); +CC1101Status cc1101_flush_rx(FuriHalSpiBusHandle* handle); /** Flush TX FIFO * * @param handle - pointer to FuriHalSpiHandle */ -void cc1101_flush_tx(FuriHalSpiBusHandle* handle); +CC1101Status cc1101_flush_tx(FuriHalSpiBusHandle* handle); /** Set Frequency * diff --git a/lib/drivers/cc1101_regs.h b/lib/drivers/cc1101_regs.h index a326dc92ce8..e0aed6bd93c 100644 --- a/lib/drivers/cc1101_regs.h +++ b/lib/drivers/cc1101_regs.h @@ -14,7 +14,7 @@ extern "C" { #define CC1101_IFDIV 0x400 /* IO Bus constants */ -#define CC1101_TIMEOUT 500 +#define CC1101_TIMEOUT 250 /* Bits and pieces */ #define CC1101_READ (1 << 7) /** Read Bit */ From dcb49c540f50b0f7ab501e1662272a21d52b10b3 Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Wed, 12 Jul 2023 12:49:17 +0300 Subject: [PATCH 659/824] [FL-3422] Loader: exit animation fix (#2860) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- .../services/loader/loader_applications.c | 67 +++++++++++-------- 1 file changed, 39 insertions(+), 28 deletions(-) diff --git a/applications/services/loader/loader_applications.c b/applications/services/loader/loader_applications.c index 1801edef9f9..7bf189e55ab 100644 --- a/applications/services/loader/loader_applications.c +++ b/applications/services/loader/loader_applications.c @@ -38,6 +38,11 @@ typedef struct { FuriString* fap_path; DialogsApp* dialogs; Storage* storage; + Loader* loader; + + Gui* gui; + ViewHolder* view_holder; + Loading* loading; } LoaderApplicationsApp; static LoaderApplicationsApp* loader_applications_app_alloc() { @@ -45,15 +50,30 @@ static LoaderApplicationsApp* loader_applications_app_alloc() { app->fap_path = furi_string_alloc_set(EXT_PATH("apps")); app->dialogs = furi_record_open(RECORD_DIALOGS); app->storage = furi_record_open(RECORD_STORAGE); + app->loader = furi_record_open(RECORD_LOADER); + + app->gui = furi_record_open(RECORD_GUI); + app->view_holder = view_holder_alloc(); + app->loading = loading_alloc(); + + view_holder_attach_to_gui(app->view_holder, app->gui); + view_holder_set_view(app->view_holder, loading_get_view(app->loading)); + return app; } //-V773 -static void loader_applications_app_free(LoaderApplicationsApp* loader_applications_app) { - furi_assert(loader_applications_app); +static void loader_applications_app_free(LoaderApplicationsApp* app) { + furi_assert(app); + + view_holder_free(app->view_holder); + loading_free(app->loading); + furi_record_close(RECORD_GUI); + + furi_record_close(RECORD_LOADER); furi_record_close(RECORD_DIALOGS); furi_record_close(RECORD_STORAGE); - furi_string_free(loader_applications_app->fap_path); - free(loader_applications_app); + furi_string_free(app->fap_path); + free(app); } static bool loader_applications_item_callback( @@ -96,47 +116,38 @@ static void loader_pubsub_callback(const void* message, void* context) { } } -static void loader_applications_start_app(const char* name) { - // start loading animation - Gui* gui = furi_record_open(RECORD_GUI); - ViewHolder* view_holder = view_holder_alloc(); - Loading* loading = loading_alloc(); - - view_holder_attach_to_gui(view_holder, gui); - view_holder_set_view(view_holder, loading_get_view(loading)); - view_holder_start(view_holder); +static void loader_applications_start_app(LoaderApplicationsApp* app) { + const char* name = furi_string_get_cstr(app->fap_path); // load app FuriThreadId thread_id = furi_thread_get_current_id(); - Loader* loader = furi_record_open(RECORD_LOADER); FuriPubSubSubscription* subscription = - furi_pubsub_subscribe(loader_get_pubsub(loader), loader_pubsub_callback, thread_id); + furi_pubsub_subscribe(loader_get_pubsub(app->loader), loader_pubsub_callback, thread_id); - LoaderStatus status = loader_start_with_gui_error(loader, name, NULL); + LoaderStatus status = loader_start_with_gui_error(app->loader, name, NULL); if(status == LoaderStatusOk) { furi_thread_flags_wait(APPLICATION_STOP_EVENT, FuriFlagWaitAny, FuriWaitForever); } - furi_pubsub_unsubscribe(loader_get_pubsub(loader), subscription); - furi_record_close(RECORD_LOADER); - - // stop loading animation - view_holder_stop(view_holder); - view_holder_free(view_holder); - loading_free(loading); - furi_record_close(RECORD_GUI); + furi_pubsub_unsubscribe(loader_get_pubsub(app->loader), subscription); } static int32_t loader_applications_thread(void* p) { LoaderApplications* loader_applications = p; - LoaderApplicationsApp* loader_applications_app = loader_applications_app_alloc(); + LoaderApplicationsApp* app = loader_applications_app_alloc(); - while(loader_applications_select_app(loader_applications_app)) { - loader_applications_start_app(furi_string_get_cstr(loader_applications_app->fap_path)); + // start loading animation + view_holder_start(app->view_holder); + + while(loader_applications_select_app(app)) { + loader_applications_start_app(app); } - loader_applications_app_free(loader_applications_app); + // stop loading animation + view_holder_stop(app->view_holder); + + loader_applications_app_free(app); if(loader_applications->closed_cb) { loader_applications->closed_cb(loader_applications->context); From a4b48028976613bbefd523e1493cd1a6edc342b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Wed, 12 Jul 2023 15:02:52 +0400 Subject: [PATCH 660/824] Revert "[FL-3420] Storage: directory sort (#2850)" (#2868) This reverts commit 136114890f24f6418c3b1672d8e378902ed4db02. --- .../storage/filesystem_api_internal.h | 3 +- .../services/storage/storage_processing.c | 180 +----------------- .../services/storage/storage_sorting.h | 22 --- 3 files changed, 11 insertions(+), 194 deletions(-) delete mode 100644 applications/services/storage/storage_sorting.h diff --git a/applications/services/storage/filesystem_api_internal.h b/applications/services/storage/filesystem_api_internal.h index 52eb6ef13c5..967d3bb41c1 100644 --- a/applications/services/storage/filesystem_api_internal.h +++ b/applications/services/storage/filesystem_api_internal.h @@ -19,8 +19,7 @@ struct File { FileType type; FS_Error error_id; /**< Standard API error from FS_Error enum */ int32_t internal_error_id; /**< Internal API error value */ - void* storage; /**< Storage API pointer */ - void* sort_data; /**< Sorted file list for directory */ + void* storage; }; /** File api structure diff --git a/applications/services/storage/storage_processing.c b/applications/services/storage/storage_processing.c index eb745cac4ec..e6b42696106 100644 --- a/applications/services/storage/storage_processing.c +++ b/applications/services/storage/storage_processing.c @@ -1,5 +1,4 @@ #include "storage_processing.h" -#include "storage_sorting.h" #include #include @@ -101,7 +100,7 @@ static FS_Error storage_get_data(Storage* app, FuriString* path, StorageData** s /******************* File Functions *******************/ -static bool storage_process_file_open( +bool storage_process_file_open( Storage* app, File* file, FuriString* path, @@ -128,7 +127,7 @@ static bool storage_process_file_open( return ret; } -static bool storage_process_file_close(Storage* app, File* file) { +bool storage_process_file_close(Storage* app, File* file) { bool ret = false; StorageData* storage = get_storage_by_file(file, app->storage); @@ -261,149 +260,9 @@ static bool storage_process_file_eof(Storage* app, File* file) { return ret; } -/*************** Sorting Dir Functions ***************/ - -static bool storage_process_dir_rewind_internal(StorageData* storage, File* file); -static bool storage_process_dir_read_internal( - StorageData* storage, - File* file, - FileInfo* fileinfo, - char* name, - const uint16_t name_length); - -static int storage_sorted_file_record_compare(const void* sorted_a, const void* sorted_b) { - SortedFileRecord* a = (SortedFileRecord*)sorted_a; - SortedFileRecord* b = (SortedFileRecord*)sorted_b; - - if(a->info.flags & FSF_DIRECTORY && !(b->info.flags & FSF_DIRECTORY)) - return -1; - else if(!(a->info.flags & FSF_DIRECTORY) && b->info.flags & FSF_DIRECTORY) - return 1; - else - return furi_string_cmpi(a->name, b->name); -} - -static bool storage_sorted_dir_read_next( - SortedDir* dir, - FileInfo* fileinfo, - char* name, - const uint16_t name_length) { - bool ret = false; - - if(dir->index < dir->count) { - SortedFileRecord* sorted = &dir->sorted[dir->index]; - if(fileinfo) { - *fileinfo = sorted->info; - } - if(name) { - strncpy(name, furi_string_get_cstr(sorted->name), name_length); - } - dir->index++; - ret = true; - } - - return ret; -} - -static void storage_sorted_dir_rewind(SortedDir* dir) { - dir->index = 0; -} - -static bool storage_sorted_dir_prepare(SortedDir* dir, StorageData* storage, File* file) { - bool ret = true; - dir->count = 0; - dir->index = 0; - FileInfo info; - char name[SORTING_MAX_NAME_LENGTH + 1] = {0}; - - furi_check(!dir->sorted); - - while(storage_process_dir_read_internal(storage, file, &info, name, SORTING_MAX_NAME_LENGTH)) { - if(memmgr_get_free_heap() < SORTING_MIN_FREE_MEMORY) { - ret = false; - break; - } - - if(dir->count == 0) { //-V547 - dir->sorted = malloc(sizeof(SortedFileRecord)); - } else { - // Our realloc actually mallocs a new block and copies the data over, - // so we need to check if we have enough memory for the new block - size_t size = sizeof(SortedFileRecord) * (dir->count + 1); - if(memmgr_heap_get_max_free_block() >= size) { - dir->sorted = - realloc(dir->sorted, sizeof(SortedFileRecord) * (dir->count + 1)); //-V701 - } else { - ret = false; - break; - } - } - - dir->sorted[dir->count].name = furi_string_alloc_set(name); - dir->sorted[dir->count].info = info; - dir->count++; - } - - return ret; -} - -static void storage_sorted_dir_sort(SortedDir* dir) { - qsort(dir->sorted, dir->count, sizeof(SortedFileRecord), storage_sorted_file_record_compare); -} - -static void storage_sorted_dir_clear_data(SortedDir* dir) { - if(dir->sorted != NULL) { - for(size_t i = 0; i < dir->count; i++) { - furi_string_free(dir->sorted[i].name); - } - - free(dir->sorted); - dir->sorted = NULL; - } -} - -static void storage_file_remove_sort_data(File* file) { - if(file->sort_data != NULL) { - storage_sorted_dir_clear_data(file->sort_data); - free(file->sort_data); - file->sort_data = NULL; - } -} - -static void storage_file_add_sort_data(File* file, StorageData* storage) { - file->sort_data = malloc(sizeof(SortedDir)); - if(storage_sorted_dir_prepare(file->sort_data, storage, file)) { - storage_sorted_dir_sort(file->sort_data); - } else { - storage_file_remove_sort_data(file); - storage_process_dir_rewind_internal(storage, file); - } -} - -static bool storage_file_has_sort_data(File* file) { - return file->sort_data != NULL; -} - /******************* Dir Functions *******************/ -static bool storage_process_dir_read_internal( - StorageData* storage, - File* file, - FileInfo* fileinfo, - char* name, - const uint16_t name_length) { - bool ret = false; - FS_CALL(storage, dir.read(storage, file, fileinfo, name, name_length)); - return ret; -} - -static bool storage_process_dir_rewind_internal(StorageData* storage, File* file) { - bool ret = false; - FS_CALL(storage, dir.rewind(storage, file)); - return ret; -} - -static bool storage_process_dir_open(Storage* app, File* file, FuriString* path) { +bool storage_process_dir_open(Storage* app, File* file, FuriString* path) { bool ret = false; StorageData* storage; file->error_id = storage_get_data(app, path, &storage); @@ -414,17 +273,13 @@ static bool storage_process_dir_open(Storage* app, File* file, FuriString* path) } else { storage_push_storage_file(file, path, storage); FS_CALL(storage, dir.open(storage, file, cstr_path_without_vfs_prefix(path))); - - if(file->error_id == FSE_OK) { - storage_file_add_sort_data(file, storage); - } } } return ret; } -static bool storage_process_dir_close(Storage* app, File* file) { +bool storage_process_dir_close(Storage* app, File* file) { bool ret = false; StorageData* storage = get_storage_by_file(file, app->storage); @@ -432,7 +287,6 @@ static bool storage_process_dir_close(Storage* app, File* file) { file->error_id = FSE_INVALID_PARAMETER; } else { FS_CALL(storage, dir.close(storage, file)); - storage_file_remove_sort_data(file); storage_pop_storage_file(file, storage); StorageEvent event = {.type = StorageEventTypeDirClose}; @@ -442,7 +296,7 @@ static bool storage_process_dir_close(Storage* app, File* file) { return ret; } -static bool storage_process_dir_read( +bool storage_process_dir_read( Storage* app, File* file, FileInfo* fileinfo, @@ -454,34 +308,20 @@ static bool storage_process_dir_read( if(storage == NULL) { file->error_id = FSE_INVALID_PARAMETER; } else { - if(storage_file_has_sort_data(file)) { - ret = storage_sorted_dir_read_next(file->sort_data, fileinfo, name, name_length); - if(ret) { - file->error_id = FSE_OK; - } else { - file->error_id = FSE_NOT_EXIST; - } - } else { - ret = storage_process_dir_read_internal(storage, file, fileinfo, name, name_length); - } + FS_CALL(storage, dir.read(storage, file, fileinfo, name, name_length)); } return ret; } -static bool storage_process_dir_rewind(Storage* app, File* file) { +bool storage_process_dir_rewind(Storage* app, File* file) { bool ret = false; StorageData* storage = get_storage_by_file(file, app->storage); if(storage == NULL) { file->error_id = FSE_INVALID_PARAMETER; } else { - if(storage_file_has_sort_data(file)) { - storage_sorted_dir_rewind(file->sort_data); - ret = true; - } else { - ret = storage_process_dir_rewind_internal(storage, file); - } + FS_CALL(storage, dir.rewind(storage, file)); } return ret; @@ -621,7 +461,7 @@ static FS_Error storage_process_sd_status(Storage* app) { /******************** Aliases processing *******************/ -static void storage_process_alias( +void storage_process_alias( Storage* app, FuriString* path, FuriThreadId thread_id, @@ -665,7 +505,7 @@ static void storage_process_alias( /****************** API calls processing ******************/ -static void storage_process_message_internal(Storage* app, StorageMessage* message) { +void storage_process_message_internal(Storage* app, StorageMessage* message) { FuriString* path = NULL; switch(message->command) { diff --git a/applications/services/storage/storage_sorting.h b/applications/services/storage/storage_sorting.h deleted file mode 100644 index 9db9d58bf9f..00000000000 --- a/applications/services/storage/storage_sorting.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once -#include - -#define SORTING_MAX_NAME_LENGTH 255 -#define SORTING_MIN_FREE_MEMORY (1024 * 40) - -/** - * @brief Sorted file record, holds file name and info - */ -typedef struct { - FuriString* name; - FileInfo info; -} SortedFileRecord; - -/** - * @brief Sorted directory, holds sorted file records, count and current index - */ -typedef struct { - SortedFileRecord* sorted; - size_t count; - size_t index; -} SortedDir; \ No newline at end of file From 92c0baa46192b29f0dc331f1fc4704a246b43024 Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Wed, 12 Jul 2023 19:35:11 +0300 Subject: [PATCH 661/824] [FL-3383, FL-3413] Archive and file browser fixes (#2862) * File browser: flickering and reload fixes * The same for archive browser --- .../main/archive/helpers/archive_browser.c | 34 +++++++++++++++- .../main/archive/helpers/archive_browser.h | 1 + .../main/archive/views/archive_browser_view.c | 26 ++++--------- .../services/gui/modules/file_browser.c | 39 ++++++++++++++----- 4 files changed, 71 insertions(+), 29 deletions(-) diff --git a/applications/main/archive/helpers/archive_browser.c b/applications/main/archive/helpers/archive_browser.c index 70137d6944d..51457fe81fe 100644 --- a/applications/main/archive/helpers/archive_browser.c +++ b/applications/main/archive/helpers/archive_browser.c @@ -64,8 +64,20 @@ static void if(!is_last) { archive_add_file_item(browser, is_folder, furi_string_get_cstr(item_path)); } else { + bool load_again = false; with_view_model( - browser->view, ArchiveBrowserViewModel * model, { model->list_loading = false; }, true); + browser->view, + ArchiveBrowserViewModel * model, + { + model->list_loading = false; + if(archive_is_file_list_load_required(model)) { + load_again = true; + } + }, + true); + if(load_again) { + archive_file_array_load(browser, 0); + } } } @@ -111,6 +123,26 @@ bool archive_is_item_in_array(ArchiveBrowserViewModel* model, uint32_t idx) { return true; } +bool archive_is_file_list_load_required(ArchiveBrowserViewModel* model) { + size_t array_size = files_array_size(model->files); + + if((model->list_loading) || (array_size >= model->item_cnt)) { + return false; + } + + if((model->array_offset > 0) && + (model->item_idx < (model->array_offset + FILE_LIST_BUF_LEN / 4))) { + return true; + } + + if(((model->array_offset + array_size) < model->item_cnt) && + (model->item_idx > (int32_t)(model->array_offset + array_size - FILE_LIST_BUF_LEN / 4))) { + return true; + } + + return false; +} + void archive_update_offset(ArchiveBrowserView* browser) { furi_assert(browser); diff --git a/applications/main/archive/helpers/archive_browser.h b/applications/main/archive/helpers/archive_browser.h index 09ffea1f9c8..5e66a3dbbcb 100644 --- a/applications/main/archive/helpers/archive_browser.h +++ b/applications/main/archive/helpers/archive_browser.h @@ -64,6 +64,7 @@ inline bool archive_is_known_app(ArchiveFileTypeEnum type) { } bool archive_is_item_in_array(ArchiveBrowserViewModel* model, uint32_t idx); +bool archive_is_file_list_load_required(ArchiveBrowserViewModel* model); void archive_update_offset(ArchiveBrowserView* browser); void archive_update_focus(ArchiveBrowserView* browser, const char* target); diff --git a/applications/main/archive/views/archive_browser_view.c b/applications/main/archive/views/archive_browser_view.c index 3c2f1321540..ba147f74c8c 100644 --- a/applications/main/archive/views/archive_browser_view.c +++ b/applications/main/archive/views/archive_browser_view.c @@ -248,24 +248,10 @@ View* archive_browser_get_view(ArchiveBrowserView* browser) { return browser->view; } -static bool is_file_list_load_required(ArchiveBrowserViewModel* model) { - size_t array_size = files_array_size(model->files); - - if((model->list_loading) || (array_size >= model->item_cnt)) { - return false; - } - - if((model->array_offset > 0) && - (model->item_idx < (model->array_offset + FILE_LIST_BUF_LEN / 4))) { - return true; +static void file_list_rollover(ArchiveBrowserViewModel* model) { + if(!model->list_loading && files_array_size(model->files) < model->item_cnt) { + files_array_reset(model->files); } - - if(((model->array_offset + array_size) < model->item_cnt) && - (model->item_idx > (int32_t)(model->array_offset + array_size - FILE_LIST_BUF_LEN / 4))) { - return true; - } - - return false; } static bool archive_view_input(InputEvent* event, void* context) { @@ -347,12 +333,13 @@ static bool archive_view_input(InputEvent* event, void* context) { if(model->item_idx < scroll_speed) { model->button_held_for_ticks = 0; model->item_idx = model->item_cnt - 1; + file_list_rollover(model); } else { model->item_idx = ((model->item_idx - scroll_speed) + model->item_cnt) % model->item_cnt; } - if(is_file_list_load_required(model)) { + if(archive_is_file_list_load_required(model)) { model->list_loading = true; browser->callback(ArchiveBrowserEventLoadPrevItems, browser->context); } @@ -366,10 +353,11 @@ static bool archive_view_input(InputEvent* event, void* context) { if(model->item_idx + scroll_speed >= count) { model->button_held_for_ticks = 0; model->item_idx = 0; + file_list_rollover(model); } else { model->item_idx = (model->item_idx + scroll_speed) % model->item_cnt; } - if(is_file_list_load_required(model)) { + if(archive_is_file_list_load_required(model)) { model->list_loading = true; browser->callback(ArchiveBrowserEventLoadNextItems, browser->context); } diff --git a/applications/services/gui/modules/file_browser.c b/applications/services/gui/modules/file_browser.c index c764a1cf7e3..91b03ec8aae 100644 --- a/applications/services/gui/modules/file_browser.c +++ b/applications/services/gui/modules/file_browser.c @@ -303,6 +303,12 @@ static bool browser_is_list_load_required(FileBrowserModel* model) { return false; } +static void browser_list_rollover(FileBrowserModel* model) { + if(!model->list_loading && items_array_size(model->items) < model->item_cnt) { + items_array_reset(model->items); + } +} + static void browser_update_offset(FileBrowser* browser) { furi_assert(browser); @@ -385,7 +391,7 @@ static void browser_list_load_cb(void* context, uint32_t list_load_offset) { } } }, - true); + false); BrowserItem_t_clear(&back_item); } @@ -425,14 +431,15 @@ static void (browser->hide_ext) && (item.type == BrowserItemTypeFile)); } + // We shouldn't update screen on each item if custom callback is not set + // Otherwise it will cause screen flickering + bool instant_update = (browser->item_callback != NULL); with_view_model( browser->view, FileBrowserModel * model, - { - items_array_push_back(model->items, item); - // TODO: calculate if element is visible - }, - true); + { items_array_push_back(model->items, item); }, + instant_update); + furi_string_free(item.display_name); furi_string_free(item.path); if(item.custom_icon_data) { @@ -440,7 +447,18 @@ static void } } else { with_view_model( - browser->view, FileBrowserModel * model, { model->list_loading = false; }, true); + browser->view, + FileBrowserModel * model, + { + model->list_loading = false; + if(browser_is_list_load_required(model)) { + model->list_loading = true; + int32_t load_offset = CLAMP( + model->item_idx - ITEM_LIST_LEN_MAX / 2, (int32_t)model->item_cnt, 0); + file_browser_worker_load(browser->worker, load_offset, ITEM_LIST_LEN_MAX); + } + }, + true); } } @@ -604,11 +622,13 @@ static bool file_browser_view_input_callback(InputEvent* event, void* context) { if(model->item_idx < scroll_speed) { model->button_held_for_ticks = 0; model->item_idx = model->item_cnt - 1; + browser_list_rollover(model); } else { model->item_idx = ((model->item_idx - scroll_speed) + model->item_cnt) % model->item_cnt; } + if(browser_is_list_load_required(model)) { model->list_loading = true; int32_t load_offset = CLAMP( @@ -622,13 +642,14 @@ static bool file_browser_view_input_callback(InputEvent* event, void* context) { model->button_held_for_ticks += 1; } else if(event->key == InputKeyDown) { - int32_t count = model->item_cnt; - if(model->item_idx + scroll_speed >= count) { + if(model->item_idx + scroll_speed >= (int32_t)model->item_cnt) { model->button_held_for_ticks = 0; model->item_idx = 0; + browser_list_rollover(model); } else { model->item_idx = (model->item_idx + scroll_speed) % model->item_cnt; } + if(browser_is_list_load_required(model)) { model->list_loading = true; int32_t load_offset = CLAMP( From b55d97f82796e2be84a0baaee70f5ce8b82b57b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Thu, 13 Jul 2023 15:02:08 +0400 Subject: [PATCH 662/824] Desktop,Cli: add uptime info (#2874) --- applications/services/cli/cli_commands.c | 9 + .../desktop/scenes/desktop_scene_debug.c | 23 +- .../desktop/views/desktop_view_debug.c | 213 ++++++------------ .../desktop/views/desktop_view_debug.h | 19 +- 4 files changed, 87 insertions(+), 177 deletions(-) diff --git a/applications/services/cli/cli_commands.c b/applications/services/cli/cli_commands.c index 7009e7531c0..467e7c53024 100644 --- a/applications/services/cli/cli_commands.c +++ b/applications/services/cli/cli_commands.c @@ -89,6 +89,14 @@ void cli_command_help(Cli* cli, FuriString* args, void* context) { } } +void cli_command_uptime(Cli* cli, FuriString* args, void* context) { + UNUSED(cli); + UNUSED(args); + UNUSED(context); + uint32_t uptime = furi_get_tick() / furi_kernel_get_tick_frequency(); + printf("Uptime: %luh%lum%lus", uptime / 60 / 60, uptime / 60 % 60, uptime % 60); +} + void cli_command_date(Cli* cli, FuriString* args, void* context) { UNUSED(cli); UNUSED(context); @@ -451,6 +459,7 @@ void cli_commands_init(Cli* cli) { cli_add_command(cli, "?", CliCommandFlagParallelSafe, cli_command_help, NULL); cli_add_command(cli, "help", CliCommandFlagParallelSafe, cli_command_help, NULL); + cli_add_command(cli, "uptime", CliCommandFlagDefault, cli_command_uptime, NULL); cli_add_command(cli, "date", CliCommandFlagParallelSafe, cli_command_date, NULL); cli_add_command(cli, "log", CliCommandFlagParallelSafe, cli_command_log, NULL); cli_add_command(cli, "sysctl", CliCommandFlagDefault, cli_command_sysctl, NULL); diff --git a/applications/services/desktop/scenes/desktop_scene_debug.c b/applications/services/desktop/scenes/desktop_scene_debug.c index a5bd3a6b1e6..866c736abad 100644 --- a/applications/services/desktop/scenes/desktop_scene_debug.c +++ b/applications/services/desktop/scenes/desktop_scene_debug.c @@ -14,8 +14,6 @@ void desktop_scene_debug_callback(DesktopEvent event, void* context) { void desktop_scene_debug_on_enter(void* context) { Desktop* desktop = (Desktop*)context; - desktop_debug_get_dolphin_data(desktop->debug_view); - desktop_debug_set_callback(desktop->debug_view, desktop_scene_debug_callback, desktop); view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewIdDebug); } @@ -32,24 +30,6 @@ bool desktop_scene_debug_on_event(void* context, SceneManagerEvent event) { dolphin_flush(dolphin); consumed = true; break; - - case DesktopDebugEventDeed: - dolphin_deed(DolphinDeedTestRight); - desktop_debug_get_dolphin_data(desktop->debug_view); - consumed = true; - break; - - case DesktopDebugEventWrongDeed: - dolphin_deed(DolphinDeedTestLeft); - desktop_debug_get_dolphin_data(desktop->debug_view); - consumed = true; - break; - - case DesktopDebugEventSaveState: - dolphin_flush(dolphin); - consumed = true; - break; - default: break; } @@ -60,6 +40,5 @@ bool desktop_scene_debug_on_event(void* context, SceneManagerEvent event) { } void desktop_scene_debug_on_exit(void* context) { - Desktop* desktop = (Desktop*)context; - desktop_debug_reset_screen_idx(desktop->debug_view); + UNUSED(context); } diff --git a/applications/services/desktop/views/desktop_view_debug.c b/applications/services/desktop/views/desktop_view_debug.c index 7a16c08479a..35c7dc03886 100644 --- a/applications/services/desktop/views/desktop_view_debug.c +++ b/applications/services/desktop/views/desktop_view_debug.c @@ -18,96 +18,71 @@ void desktop_debug_set_callback( } void desktop_debug_render(Canvas* canvas, void* model) { + UNUSED(model); canvas_clear(canvas); - DesktopDebugViewModel* m = model; const Version* ver; char buffer[64]; - static const char* headers[] = {"Device Info:", "Dolphin Info:"}; - canvas_set_color(canvas, ColorBlack); canvas_set_font(canvas, FontPrimary); - canvas_draw_str_aligned( - canvas, 64, 1 + STATUS_BAR_Y_SHIFT, AlignCenter, AlignTop, headers[m->screen]); + + uint32_t uptime = furi_get_tick() / furi_kernel_get_tick_frequency(); + snprintf( + buffer, + sizeof(buffer), + "Uptime: %luh%lum%lus", + uptime / 60 / 60, + uptime / 60 % 60, + uptime % 60); + canvas_draw_str_aligned(canvas, 64, 1 + STATUS_BAR_Y_SHIFT, AlignCenter, AlignTop, buffer); + canvas_set_font(canvas, FontSecondary); - if(m->screen != DesktopViewStatsMeta) { - // Hardware version - const char* my_name = furi_hal_version_get_name_ptr(); - snprintf( - buffer, - sizeof(buffer), - "%d.F%dB%dC%d %s:%s %s", - furi_hal_version_get_hw_version(), - furi_hal_version_get_hw_target(), - furi_hal_version_get_hw_body(), - furi_hal_version_get_hw_connect(), - furi_hal_version_get_hw_region_name(), - furi_hal_region_get_name(), - my_name ? my_name : "Unknown"); - canvas_draw_str(canvas, 0, 19 + STATUS_BAR_Y_SHIFT, buffer); - - ver = furi_hal_version_get_firmware_version(); - const BleGlueC2Info* c2_ver = NULL; + // Hardware version + const char* my_name = furi_hal_version_get_name_ptr(); + snprintf( + buffer, + sizeof(buffer), + "%d.F%dB%dC%d %s:%s %s", + furi_hal_version_get_hw_version(), + furi_hal_version_get_hw_target(), + furi_hal_version_get_hw_body(), + furi_hal_version_get_hw_connect(), + furi_hal_version_get_hw_region_name(), + furi_hal_region_get_name(), + my_name ? my_name : "Unknown"); + canvas_draw_str(canvas, 0, 19 + STATUS_BAR_Y_SHIFT, buffer); + + ver = furi_hal_version_get_firmware_version(); + const BleGlueC2Info* c2_ver = NULL; #ifdef SRV_BT - c2_ver = ble_glue_get_c2_info(); + c2_ver = ble_glue_get_c2_info(); #endif - if(!ver) { //-V1051 - canvas_draw_str(canvas, 0, 30 + STATUS_BAR_Y_SHIFT, "No info"); - return; - } - - snprintf( - buffer, - sizeof(buffer), - "%s [%s]", - version_get_version(ver), - version_get_builddate(ver)); - canvas_draw_str(canvas, 0, 30 + STATUS_BAR_Y_SHIFT, buffer); - - uint16_t api_major, api_minor; - furi_hal_info_get_api_version(&api_major, &api_minor); - snprintf( - buffer, - sizeof(buffer), - "%s%s [%d.%d] %s", - version_get_dirty_flag(ver) ? "[!] " : "", - version_get_githash(ver), - api_major, - api_minor, - c2_ver ? c2_ver->StackTypeString : ""); - canvas_draw_str(canvas, 0, 40 + STATUS_BAR_Y_SHIFT, buffer); - - snprintf( - buffer, sizeof(buffer), "[%d] %s", version_get_target(ver), version_get_gitbranch(ver)); - canvas_draw_str(canvas, 0, 50 + STATUS_BAR_Y_SHIFT, buffer); - - } else { - Dolphin* dolphin = furi_record_open(RECORD_DOLPHIN); - DolphinStats stats = dolphin_stats(dolphin); - furi_record_close(RECORD_DOLPHIN); - - uint32_t current_lvl = stats.level; - uint32_t remaining = dolphin_state_xp_to_levelup(m->icounter); - - canvas_set_font(canvas, FontSecondary); - snprintf(buffer, sizeof(buffer), "Icounter: %lu Butthurt %lu", m->icounter, m->butthurt); - canvas_draw_str(canvas, 5, 19 + STATUS_BAR_Y_SHIFT, buffer); - - snprintf( - buffer, - sizeof(buffer), - "Level: %lu To level up: %lu", - current_lvl, - (remaining == (uint32_t)(-1) ? remaining : 0)); - canvas_draw_str(canvas, 5, 29 + STATUS_BAR_Y_SHIFT, buffer); - - // even if timestamp is uint64_t, it's safe to cast it to uint32_t, because furi_hal_rtc_datetime_to_timestamp only returns uint32_t - snprintf(buffer, sizeof(buffer), "%lu", (uint32_t)m->timestamp); - - canvas_draw_str(canvas, 5, 39 + STATUS_BAR_Y_SHIFT, buffer); - canvas_draw_str(canvas, 0, 49 + STATUS_BAR_Y_SHIFT, "[< >] icounter value [ok] save"); + if(!ver) { //-V1051 + canvas_draw_str(canvas, 0, 30 + STATUS_BAR_Y_SHIFT, "No info"); + return; } + + snprintf( + buffer, sizeof(buffer), "%s [%s]", version_get_version(ver), version_get_builddate(ver)); + canvas_draw_str(canvas, 0, 30 + STATUS_BAR_Y_SHIFT, buffer); + + uint16_t api_major, api_minor; + furi_hal_info_get_api_version(&api_major, &api_minor); + snprintf( + buffer, + sizeof(buffer), + "%s%s [%d.%d] %s", + version_get_dirty_flag(ver) ? "[!] " : "", + version_get_githash(ver), + api_major, + api_minor, + c2_ver ? c2_ver->StackTypeString : ""); + canvas_draw_str(canvas, 0, 40 + STATUS_BAR_Y_SHIFT, buffer); + + snprintf( + buffer, sizeof(buffer), "[%d] %s", version_get_target(ver), version_get_gitbranch(ver)); + canvas_draw_str(canvas, 0, 50 + STATUS_BAR_Y_SHIFT, buffer); } View* desktop_debug_get_view(DesktopDebugView* debug_view) { @@ -115,61 +90,43 @@ View* desktop_debug_get_view(DesktopDebugView* debug_view) { return debug_view->view; } -bool desktop_debug_input(InputEvent* event, void* context) { +static bool desktop_debug_input(InputEvent* event, void* context) { furi_assert(event); furi_assert(context); DesktopDebugView* debug_view = context; - if(event->type != InputTypeShort && event->type != InputTypeRepeat) { - return false; - } - - DesktopViewStatsScreens current = 0; - with_view_model( - debug_view->view, - DesktopDebugViewModel * model, - { -#ifdef SRV_DOLPHIN_STATE_DEBUG - if((event->key == InputKeyDown) || (event->key == InputKeyUp)) { - model->screen = !model->screen; - } -#endif - current = model->screen; - }, - true); - - size_t count = (event->type == InputTypeRepeat) ? 10 : 1; - if(current == DesktopViewStatsMeta) { - if(event->key == InputKeyLeft) { - while(count-- > 0) { - debug_view->callback(DesktopDebugEventWrongDeed, debug_view->context); - } - } else if(event->key == InputKeyRight) { - while(count-- > 0) { - debug_view->callback(DesktopDebugEventDeed, debug_view->context); - } - } else if(event->key == InputKeyOk) { - debug_view->callback(DesktopDebugEventSaveState, debug_view->context); - } else { - return false; - } - } - - if(event->key == InputKeyBack) { + if(event->key == InputKeyBack && event->type == InputTypeShort) { debug_view->callback(DesktopDebugEventExit, debug_view->context); } return true; } +static void desktop_debug_enter(void* context) { + DesktopDebugView* debug_view = context; + furi_timer_start(debug_view->timer, furi_ms_to_ticks(1000)); +} + +static void desktop_debug_exit(void* context) { + DesktopDebugView* debug_view = context; + furi_timer_stop(debug_view->timer); +} +void desktop_debug_timer(void* context) { + DesktopDebugView* debug_view = context; + view_get_model(debug_view->view); + view_commit_model(debug_view->view, true); +} + DesktopDebugView* desktop_debug_alloc() { DesktopDebugView* debug_view = malloc(sizeof(DesktopDebugView)); debug_view->view = view_alloc(); - view_allocate_model(debug_view->view, ViewModelTypeLocking, sizeof(DesktopDebugViewModel)); + debug_view->timer = furi_timer_alloc(desktop_debug_timer, FuriTimerTypePeriodic, debug_view); view_set_context(debug_view->view, debug_view); view_set_draw_callback(debug_view->view, (ViewDrawCallback)desktop_debug_render); view_set_input_callback(debug_view->view, desktop_debug_input); + view_set_enter_callback(debug_view->view, desktop_debug_enter); + view_set_exit_callback(debug_view->view, desktop_debug_exit); return debug_view; } @@ -177,27 +134,7 @@ DesktopDebugView* desktop_debug_alloc() { void desktop_debug_free(DesktopDebugView* debug_view) { furi_assert(debug_view); + furi_timer_free(debug_view->timer); view_free(debug_view->view); free(debug_view); } - -void desktop_debug_get_dolphin_data(DesktopDebugView* debug_view) { - Dolphin* dolphin = furi_record_open(RECORD_DOLPHIN); - DolphinStats stats = dolphin_stats(dolphin); - with_view_model( - debug_view->view, - DesktopDebugViewModel * model, - { - model->icounter = stats.icounter; - model->butthurt = stats.butthurt; - model->timestamp = stats.timestamp; - }, - true); - - furi_record_close(RECORD_DOLPHIN); -} - -void desktop_debug_reset_screen_idx(DesktopDebugView* debug_view) { - with_view_model( - debug_view->view, DesktopDebugViewModel * model, { model->screen = 0; }, true); -} diff --git a/applications/services/desktop/views/desktop_view_debug.h b/applications/services/desktop/views/desktop_view_debug.h index f6af16b2e96..fea0c22a080 100644 --- a/applications/services/desktop/views/desktop_view_debug.h +++ b/applications/services/desktop/views/desktop_view_debug.h @@ -8,26 +8,13 @@ typedef struct DesktopDebugView DesktopDebugView; typedef void (*DesktopDebugViewCallback)(DesktopEvent event, void* context); -// Debug info -typedef enum { - DesktopViewStatsFw, - DesktopViewStatsMeta, - DesktopViewStatsTotalCount, -} DesktopViewStatsScreens; - struct DesktopDebugView { View* view; + FuriTimer* timer; DesktopDebugViewCallback callback; void* context; }; -typedef struct { - uint32_t icounter; - uint32_t butthurt; - uint64_t timestamp; - DesktopViewStatsScreens screen; -} DesktopDebugViewModel; - void desktop_debug_set_callback( DesktopDebugView* debug_view, DesktopDebugViewCallback callback, @@ -36,7 +23,5 @@ void desktop_debug_set_callback( View* desktop_debug_get_view(DesktopDebugView* debug_view); DesktopDebugView* desktop_debug_alloc(); -void desktop_debug_free(DesktopDebugView* debug_view); -void desktop_debug_get_dolphin_data(DesktopDebugView* debug_view); -void desktop_debug_reset_screen_idx(DesktopDebugView* debug_view); +void desktop_debug_free(DesktopDebugView* debug_view); From 8dc1edac18139aed83ee2f389c33e338403c085f Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Thu, 13 Jul 2023 15:02:59 +0300 Subject: [PATCH 663/824] Loader: good looking error messages (#2873) * Loader: special error for unknown external app * Loader: update special error * Loader: beautify GUI errors, remove redundant logs * Loader: fix gui error vertical position * Desktop settings: add external menu apps * Desktop: smaller settings struct and fix incorrect behavior with ext apps Co-authored-by: Aleksandr Kutuzov --- .../services/desktop/desktop_settings.h | 3 +- applications/services/loader/loader.c | 60 ++++++++++++------- .../services/loader/loader_applications.c | 2 +- applications/services/loader/loader_menu.c | 2 +- .../scenes/desktop_settings_scene_favorite.c | 44 ++++++++------ 5 files changed, 68 insertions(+), 43 deletions(-) diff --git a/applications/services/desktop/desktop_settings.h b/applications/services/desktop/desktop_settings.h index 7ab39094d16..9b88868a8dc 100644 --- a/applications/services/desktop/desktop_settings.h +++ b/applications/services/desktop/desktop_settings.h @@ -8,7 +8,7 @@ #include #include -#define DESKTOP_SETTINGS_VER (7) +#define DESKTOP_SETTINGS_VER (8) #define DESKTOP_SETTINGS_PATH INT_PATH(DESKTOP_SETTINGS_FILE_NAME) #define DESKTOP_SETTINGS_MAGIC (0x17) @@ -42,7 +42,6 @@ typedef struct { } PinCode; typedef struct { - bool is_external; char name_or_path[MAX_APP_LENGTH]; } FavoriteApp; diff --git a/applications/services/loader/loader.c b/applications/services/loader/loader.c index e7fa38596e9..41c0f95d420 100644 --- a/applications/services/loader/loader.c +++ b/applications/services/loader/loader.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -11,7 +12,20 @@ #define TAG "Loader" #define LOADER_MAGIC_THREAD_VALUE 0xDEADBEEF -// api + +// helpers + +static const char* loader_find_external_application_by_name(const char* app_name) { + for(size_t i = 0; i < FLIPPER_EXTERNAL_APPS_COUNT; i++) { + if(strcmp(FLIPPER_EXTERNAL_APPS[i].name, app_name) == 0) { + return FLIPPER_EXTERNAL_APPS[i].path; + } + } + + return NULL; +} + +// API LoaderStatus loader_start(Loader* loader, const char* name, const char* args, FuriString* error_message) { @@ -33,17 +47,33 @@ LoaderStatus loader_start_with_gui_error(Loader* loader, const char* name, const FuriString* error_message = furi_string_alloc(); LoaderStatus status = loader_start(loader, name, args, error_message); - // TODO: we have many places where we can emit a double start, ex: desktop, menu - // so i prefer to not show LoaderStatusErrorAppStarted error message for now - if(status == LoaderStatusErrorUnknownApp || status == LoaderStatusErrorInternal) { + if(status == LoaderStatusErrorUnknownApp && + loader_find_external_application_by_name(name) != NULL) { + // Special case for external apps + DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); + DialogMessage* message = dialog_message_alloc(); + dialog_message_set_header(message, "Update needed", 64, 3, AlignCenter, AlignTop); + dialog_message_set_buttons(message, NULL, NULL, NULL); + dialog_message_set_icon(message, &I_DolphinCommon_56x48, 72, 17); + dialog_message_set_text( + message, "Update firmware\nto run this app", 3, 26, AlignLeft, AlignTop); + dialog_message_show(dialogs, message); + dialog_message_free(message); + furi_record_close(RECORD_DIALOGS); + } else if(status == LoaderStatusErrorUnknownApp || status == LoaderStatusErrorInternal) { + // TODO: we have many places where we can emit a double start, ex: desktop, menu + // so i prefer to not show LoaderStatusErrorAppStarted error message for now DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); DialogMessage* message = dialog_message_alloc(); dialog_message_set_header(message, "Error", 64, 0, AlignCenter, AlignTop); dialog_message_set_buttons(message, NULL, NULL, NULL); - furi_string_replace(error_message, ":", "\n"); + furi_string_replace(error_message, "/ext/apps/", ""); + furi_string_replace(error_message, ", ", "\n"); + furi_string_replace(error_message, ": ", "\n"); + dialog_message_set_text( - message, furi_string_get_cstr(error_message), 64, 32, AlignCenter, AlignCenter); + message, furi_string_get_cstr(error_message), 64, 35, AlignCenter, AlignCenter); dialog_message_show(dialogs, message); dialog_message_free(message); @@ -170,16 +200,6 @@ static const FlipperInternalApplication* loader_find_application_by_name(const c return application; } -static const char* loader_find_external_application_by_name(const char* app_name) { - for(size_t i = 0; i < FLIPPER_EXTERNAL_APPS_COUNT; i++) { - if(strcmp(FLIPPER_EXTERNAL_APPS[i].name, app_name) == 0) { - return FLIPPER_EXTERNAL_APPS[i].path; - } - } - - return NULL; -} - static void loader_start_app_thread(Loader* loader, FlipperInternalApplicationFlag flags) { // setup heap trace FuriHalRtcHeapTrackMode mode = furi_hal_rtc_get_heap_track_mode(); @@ -278,22 +298,20 @@ static LoaderStatus loader_start_external_app( if(preload_res != FlipperApplicationPreloadStatusSuccess) { const char* err_msg = flipper_application_preload_status_to_string(preload_res); status = loader_make_status_error( - LoaderStatusErrorInternal, error_message, "Preload failed %s: %s", path, err_msg); + LoaderStatusErrorInternal, error_message, "Preload failed, %s: %s", path, err_msg); break; } - FURI_LOG_I(TAG, "Mapping"); FlipperApplicationLoadStatus load_status = flipper_application_map_to_memory(loader->app.fap); if(load_status != FlipperApplicationLoadStatusSuccess) { const char* err_msg = flipper_application_load_status_to_string(load_status); status = loader_make_status_error( - LoaderStatusErrorInternal, error_message, "Load failed %s: %s", path, err_msg); + LoaderStatusErrorInternal, error_message, "Load failed, %s: %s", path, err_msg); break; } FURI_LOG_I(TAG, "Loaded in %zums", (size_t)(furi_get_tick() - start)); - FURI_LOG_I(TAG, "Starting app"); loader->app.thread = flipper_application_alloc_thread(loader->app.fap, args); FuriString* app_name = furi_string_alloc(); @@ -397,7 +415,7 @@ static LoaderStatus loader_do_start_by_name( } } - // check external apps + // check Faps { Storage* storage = furi_record_open(RECORD_STORAGE); if(storage_file_exists(storage, name)) { diff --git a/applications/services/loader/loader_applications.c b/applications/services/loader/loader_applications.c index 7bf189e55ab..8ae91d76408 100644 --- a/applications/services/loader/loader_applications.c +++ b/applications/services/loader/loader_applications.c @@ -20,7 +20,7 @@ static int32_t loader_applications_thread(void* p); LoaderApplications* loader_applications_alloc(void (*closed_cb)(void*), void* context) { LoaderApplications* loader_applications = malloc(sizeof(LoaderApplications)); loader_applications->thread = - furi_thread_alloc_ex(TAG, 512, loader_applications_thread, (void*)loader_applications); + furi_thread_alloc_ex(TAG, 768, loader_applications_thread, (void*)loader_applications); loader_applications->closed_cb = closed_cb; loader_applications->context = context; furi_thread_start(loader_applications->thread); diff --git a/applications/services/loader/loader_menu.c b/applications/services/loader/loader_menu.c index 2c48b0923b1..149fea72c19 100644 --- a/applications/services/loader/loader_menu.c +++ b/applications/services/loader/loader_menu.c @@ -60,7 +60,7 @@ static void loader_menu_apps_callback(void* context, uint32_t index) { static void loader_menu_external_apps_callback(void* context, uint32_t index) { UNUSED(context); - const char* path = FLIPPER_EXTERNAL_APPS[index].path; + const char* path = FLIPPER_EXTERNAL_APPS[index].name; loader_menu_start(path); } diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c index c35a10568af..26e7bc5875b 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c @@ -5,11 +5,26 @@ #include #include +#define APPS_COUNT (FLIPPER_APPS_COUNT + FLIPPER_EXTERNAL_APPS_COUNT) + #define EXTERNAL_BROWSER_NAME ("Applications") -#define EXTERNAL_BROWSER_INDEX (FLIPPER_APPS_COUNT + 1) +#define EXTERNAL_BROWSER_INDEX (APPS_COUNT + 1) #define EXTERNAL_APPLICATION_NAME ("[External Application]") -#define EXTERNAL_APPLICATION_INDEX (FLIPPER_APPS_COUNT + 2) +#define EXTERNAL_APPLICATION_INDEX (APPS_COUNT + 2) + +#define PRESELECTED_SPECIAL 0xffffffff + +static const char* favorite_fap_get_app_name(size_t i) { + const char* name; + if(i < FLIPPER_APPS_COUNT) { + name = FLIPPER_APPS[i].name; + } else { + name = FLIPPER_EXTERNAL_APPS[i - FLIPPER_APPS_COUNT].name; + } + + return name; +} static bool favorite_fap_selector_item_callback( FuriString* file_path, @@ -42,21 +57,17 @@ void desktop_settings_scene_favorite_on_enter(void* context) { uint32_t primary_favorite = scene_manager_get_scene_state(app->scene_manager, DesktopSettingsAppSceneFavorite); - uint32_t pre_select_item = 0; + uint32_t pre_select_item = PRESELECTED_SPECIAL; FavoriteApp* curr_favorite_app = primary_favorite ? &app->settings.favorite_primary : &app->settings.favorite_secondary; - for(size_t i = 0; i < FLIPPER_APPS_COUNT; i++) { - submenu_add_item( - submenu, - FLIPPER_APPS[i].name, - i, - desktop_settings_scene_favorite_submenu_callback, - app); + for(size_t i = 0; i < APPS_COUNT; i++) { + const char* name = favorite_fap_get_app_name(i); + + submenu_add_item(submenu, name, i, desktop_settings_scene_favorite_submenu_callback, app); // Select favorite item in submenu - if(!curr_favorite_app->is_external && - !strcmp(FLIPPER_APPS[i].name, curr_favorite_app->name_or_path)) { + if(!strcmp(name, curr_favorite_app->name_or_path)) { pre_select_item = i; } } @@ -77,7 +88,7 @@ void desktop_settings_scene_favorite_on_enter(void* context) { desktop_settings_scene_favorite_submenu_callback, app); - if(curr_favorite_app->is_external) { + if(pre_select_item == PRESELECTED_SPECIAL) { if(curr_favorite_app->name_or_path[0] == '\0') { pre_select_item = EXTERNAL_BROWSER_INDEX; } else { @@ -104,7 +115,6 @@ bool desktop_settings_scene_favorite_on_event(void* context, SceneManagerEvent e if(event.type == SceneManagerEventTypeCustom) { if(event.event == EXTERNAL_BROWSER_INDEX) { - curr_favorite_app->is_external = true; curr_favorite_app->name_or_path[0] = '\0'; consumed = true; } else if(event.event == EXTERNAL_APPLICATION_INDEX) { @@ -125,7 +135,6 @@ bool desktop_settings_scene_favorite_on_event(void* context, SceneManagerEvent e if(dialog_file_browser_show(app->dialogs, temp_path, temp_path, &browser_options)) { submenu_reset(app->submenu); // Prevent menu from being shown when we exiting scene - curr_favorite_app->is_external = true; strncpy( curr_favorite_app->name_or_path, furi_string_get_cstr(temp_path), @@ -133,9 +142,8 @@ bool desktop_settings_scene_favorite_on_event(void* context, SceneManagerEvent e consumed = true; } } else { - curr_favorite_app->is_external = false; - strncpy( - curr_favorite_app->name_or_path, FLIPPER_APPS[event.event].name, MAX_APP_LENGTH); + const char* name = favorite_fap_get_app_name(event.event); + if(name) strncpy(curr_favorite_app->name_or_path, name, MAX_APP_LENGTH); consumed = true; } if(consumed) { From 6605740ce9e1e9440d577e7cce0273d269676d74 Mon Sep 17 00:00:00 2001 From: Andrey Zakharov Date: Thu, 13 Jul 2023 15:09:04 +0300 Subject: [PATCH 664/824] Add LG A/C IR signals to universal remote (#2871) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Andrey Zakharov Co-authored-by: あく --- assets/resources/infrared/assets/ac.ir | 37 ++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/assets/resources/infrared/assets/ac.ir b/assets/resources/infrared/assets/ac.ir index cb3c2539c48..6ec6fc82959 100644 --- a/assets/resources/infrared/assets/ac.ir +++ b/assets/resources/infrared/assets/ac.ir @@ -544,3 +544,40 @@ type: raw frequency: 38000 duty_cycle: 0.330000 data: 30675 50953 3432 1606 490 1190 489 350 489 350 489 350 489 350 489 351 488 350 489 351 489 351 488 351 489 351 488 351 489 1191 488 351 488 351 489 351 488 351 488 351 488 351 489 351 488 1191 489 1191 489 351 488 351 488 351 488 351 488 351 489 351 489 351 488 351 489 1191 488 351 488 1191 489 1191 488 1191 488 1191 488 1191 488 1191 488 351 489 1191 488 1191 489 351 488 351 489 351 488 351 489 351 488 351 488 351 488 351 488 1191 488 1191 488 1191 488 1191 489 1191 488 1191 488 1191 489 1191 488 351 488 351 489 351 488 1191 488 351 489 351 488 351 488 351 489 1191 488 351 489 351 488 1191 488 351 488 351 488 351 489 351 489 351 488 351 489 1191 488 351 488 351 489 351 488 351 488 1191 488 1191 488 351 488 351 489 351 488 351 489 351 488 351 489 351 489 1191 488 1192 488 1191 488 351 489 1191 489 351 488 351 488 351 489 351 489 351 488 351 488 351 489 351 488 351 488 351 488 1192 488 351 489 351 488 351 488 351 489 351 489 351 488 351 488 351 488 352 487 352 488 351 488 352 488 351 488 351 488 351 488 1192 488 1192 487 352 488 352 487 352 487 352 488 352 487 352 488 351 488 352 488 352 488 352 487 352 488 351 488 351 488 352 488 352 487 352 488 352 487 352 488 352 488 352 488 352 487 1192 487 352 487 352 488 352 488 352 488 352 487 352 487 352 488 352 487 352 487 352 487 352 488 352 488 352 487 352 487 352 488 352 487 352 488 352 487 352 487 352 487 352 487 352 487 352 488 352 487 352 487 352 487 352 488 352 487 352 488 352 487 352 488 352 487 352 487 352 488 352 487 352 488 352 487 352 488 352 488 352 487 352 487 352 487 352 487 352 487 352 488 352 487 352 487 352 487 1193 486 352 487 352 488 352 487 352 487 352 488 352 487 352 488 352 488 352 487 352 488 352 487 353 486 353 487 352 487 352 488 352 488 352 487 352 487 352 487 352 487 353 487 352 488 352 488 352 487 1193 486 1193 486 1193 486 1193 487 353 487 352 487 353 486 +# +# Model: LG PC07SQR +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3169 9836 535 1553 511 551 491 543 491 544 490 1586 490 532 510 530 511 543 491 1579 489 1577 490 543 490 543 491 543 491 544 490 544 489 544 490 543 491 544 490 550 492 544 490 543 491 1586 489 542 492 1583 492 552 490 532 510 537 512 1585 491 +# +name: Dh +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3385 9888 512 1544 512 544 489 544 489 544 490 1586 489 545 489 544 489 545 489 531 511 541 508 533 508 542 507 544 489 546 487 544 490 1587 489 1573 510 543 490 543 491 1578 490 545 489 1574 509 545 488 1587 489 1573 510 1586 490 1579 489 1568 507 +# +name: Cool_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3288 9816 598 1504 574 472 569 463 571 463 571 1515 568 472 569 461 573 471 571 459 590 462 571 463 571 447 594 462 572 462 571 464 570 459 590 463 570 464 570 1496 571 1497 570 463 571 1514 569 464 570 1504 572 1514 569 471 571 473 568 462 572 +# +name: Cool_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3205 9865 616 1473 536 518 516 517 517 517 517 1575 517 527 568 464 515 517 571 463 570 462 572 462 572 469 573 461 573 463 570 462 572 469 573 1504 572 451 590 451 591 472 569 463 571 1494 573 461 573 1497 570 1505 570 1503 572 471 571 1491 592 +# +name: Heat_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3363 9846 596 1495 514 518 515 517 517 515 518 1552 516 502 539 517 517 516 518 516 518 517 517 517 571 462 517 518 516 1554 568 461 589 462 572 1505 571 1507 568 1514 514 1568 570 461 573 1504 572 472 570 1507 592 1490 593 460 574 462 572 447 594 +# +name: Heat_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3204 9889 537 1587 491 529 512 544 489 545 489 1573 510 530 511 543 491 552 490 538 511 543 491 543 491 532 509 543 491 1587 489 537 512 543 491 1577 490 543 491 543 491 544 489 543 491 1586 489 544 490 1587 489 539 510 543 491 543 491 1586 490 From e073c603a4bad53cbaca3c67d46c143a80779206 Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Fri, 14 Jul 2023 12:16:22 +0300 Subject: [PATCH 665/824] [FL-3334] Storage: explosive rename fix (#2876) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- .../services/storage/storage_external_api.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/applications/services/storage/storage_external_api.c b/applications/services/storage/storage_external_api.c index 5fcaa59216b..585ded41444 100644 --- a/applications/services/storage/storage_external_api.c +++ b/applications/services/storage/storage_external_api.c @@ -430,6 +430,20 @@ FS_Error storage_common_rename(Storage* storage, const char* old_path, const cha break; } + if(storage_dir_exists(storage, old_path)) { + FuriString* dir_path = furi_string_alloc_set_str(old_path); + if(!furi_string_end_with_str(dir_path, "/")) { + furi_string_cat_str(dir_path, "/"); + } + const char* dir_path_s = furi_string_get_cstr(dir_path); + if(strncmp(new_path, dir_path_s, strlen(dir_path_s)) == 0) { + error = FSE_INVALID_NAME; + furi_string_free(dir_path); + break; + } + furi_string_free(dir_path); + } + if(storage_file_exists(storage, new_path)) { storage_common_remove(storage, new_path); } From af64ae0e4015ed58d102b15f4a1f9140c67e286d Mon Sep 17 00:00:00 2001 From: DEXV <89728480+DXVVAY@users.noreply.github.com> Date: Fri, 14 Jul 2023 14:43:13 +0200 Subject: [PATCH 666/824] Badusb: Ducky script to auto install qFlipper (#2346) * Badusb:Script to auto install/update qFlipper * Update Install_qFlipper_windows.txt --- .../badusb/Install_qFlipper_windows.txt | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 assets/resources/badusb/Install_qFlipper_windows.txt diff --git a/assets/resources/badusb/Install_qFlipper_windows.txt b/assets/resources/badusb/Install_qFlipper_windows.txt new file mode 100644 index 00000000000..94282321512 --- /dev/null +++ b/assets/resources/badusb/Install_qFlipper_windows.txt @@ -0,0 +1,42 @@ +REM Written by @dexv +DELAY 2000 +GUI r +DELAY 500 +STRING powershell +ENTER +DELAY 1000 +STRING $url = "https://update.flipperzero.one/qFlipper/release/windows-amd64/portable" +ENTER +STRING $output = "$env:USERPROFILE\Documents\qFlipper.zip" +ENTER +STRING $destination = "$env:USERPROFILE\Documents\qFlipper" +ENTER +STRING $shortcutPath = "$env:USERPROFILE\Desktop\qFlipper.lnk" +ENTER +STRING $scriptPath = "$env:USERPROFILE\Documents\qFlipperInstall.ps1" +ENTER +STRING $driverPath = "$destination\STM32 Driver" +ENTER +STRING $installBat = "$driverPath\install.bat" +ENTER +STRING (New-Object System.Net.WebClient).DownloadFile($url, $output) +ENTER +STRING Expand-Archive -Path $output -DestinationPath $destination -Force +ENTER +STRING Set-Location -Path $destination +ENTER +STRING Start-Process -FilePath ".\qFlipper.exe" +ENTER +STRING Start-Process -Wait -FilePath "cmd.exe" -ArgumentList "/c $installBat" +ENTER +STRING $shell = New-Object -ComObject WScript.Shell +ENTER +STRING $shortcut = $shell.CreateShortcut($shortcutPath) +ENTER +STRING $shortcut.TargetPath = "$destination\qFlipper.exe" +ENTER +STRING $shortcut.Save() +ENTER +DELAY 500 +STRING "powershell -ExecutionPolicy Bypass -File $scriptPath" +ENTER From f2324e4d1ca57654e66e580c4ea14da145cc7e4c Mon Sep 17 00:00:00 2001 From: hedger Date: Fri, 14 Jul 2023 16:45:16 +0300 Subject: [PATCH 667/824] [FL-3377] Update error code descriptions (#2875) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * updater: added update error code descriptions * updater: separate ram/flash messages * updater: extra pre-update checks * updater: fixed string comparison * updater: Additional logging Co-authored-by: あく --- .../system/updater/util/update_task.c | 192 +++++++++++++++++- .../system/updater/util/update_task_i.h | 5 + .../updater/util/update_task_worker_backup.c | 5 - .../updater/util/update_task_worker_flasher.c | 26 ++- .../system/updater/views/updater_main.c | 15 +- documentation/OTA.md | 7 +- lib/update_util/update_operation.c | 21 +- lib/update_util/update_operation.h | 3 +- 8 files changed, 238 insertions(+), 36 deletions(-) diff --git a/applications/system/updater/util/update_task.c b/applications/system/updater/util/update_task.c index 708d10ce040..74d752b434b 100644 --- a/applications/system/updater/util/update_task.c +++ b/applications/system/updater/util/update_task.c @@ -19,7 +19,7 @@ static const char* update_task_stage_descr[] = { [UpdateTaskStageRadioErase] = "Uninstalling radio FW", [UpdateTaskStageRadioWrite] = "Writing radio FW", [UpdateTaskStageRadioInstall] = "Installing radio FW", - [UpdateTaskStageRadioBusy] = "Radio is updating", + [UpdateTaskStageRadioBusy] = "Core 2 busy", [UpdateTaskStageOBValidation] = "Validating opt. bytes", [UpdateTaskStageLfsBackup] = "Backing up LFS", [UpdateTaskStageLfsRestore] = "Restoring LFS", @@ -30,6 +30,191 @@ static const char* update_task_stage_descr[] = { [UpdateTaskStageOBError] = "OB, report", }; +static const struct { + UpdateTaskStage stage; + uint8_t percent_min, percent_max; + const char* descr; +} update_task_error_detail[] = { + { + .stage = UpdateTaskStageReadManifest, + .percent_min = 0, + .percent_max = 13, + .descr = "Wrong Updater HW", + }, + { + .stage = UpdateTaskStageReadManifest, + .percent_min = 14, + .percent_max = 20, + .descr = "Manifest pointer error", + }, + { + .stage = UpdateTaskStageReadManifest, + .percent_min = 21, + .percent_max = 30, + .descr = "Manifest load error", + }, + { + .stage = UpdateTaskStageReadManifest, + .percent_min = 31, + .percent_max = 40, + .descr = "Wrong package version", + }, + { + .stage = UpdateTaskStageReadManifest, + .percent_min = 41, + .percent_max = 50, + .descr = "HW Target mismatch", + }, + { + .stage = UpdateTaskStageReadManifest, + .percent_min = 51, + .percent_max = 60, + .descr = "No DFU file", + }, + { + .stage = UpdateTaskStageReadManifest, + .percent_min = 61, + .percent_max = 80, + .descr = "No Radio file", + }, +#ifndef FURI_RAM_EXEC + { + .stage = UpdateTaskStageLfsBackup, + .percent_min = 0, + .percent_max = 100, + .descr = "FS R/W error", + }, +#else + { + .stage = UpdateTaskStageRadioImageValidate, + .percent_min = 0, + .percent_max = 98, + .descr = "FS Read error", + }, + { + .stage = UpdateTaskStageRadioImageValidate, + .percent_min = 99, + .percent_max = 100, + .descr = "CRC mismatch", + }, + { + .stage = UpdateTaskStageRadioErase, + .percent_min = 0, + .percent_max = 30, + .descr = "Stack remove: cmd error", + }, + { + .stage = UpdateTaskStageRadioErase, + .percent_min = 31, + .percent_max = 100, + .descr = "Stack remove: wait failed", + }, + { + .stage = UpdateTaskStageRadioWrite, + .percent_min = 0, + .percent_max = 100, + .descr = "Stack write: error", + }, + { + .stage = UpdateTaskStageRadioInstall, + .percent_min = 0, + .percent_max = 10, + .descr = "Stack install: cmd error", + }, + { + .stage = UpdateTaskStageRadioInstall, + .percent_min = 11, + .percent_max = 100, + .descr = "Stack install: wait failed", + }, + { + .stage = UpdateTaskStageRadioBusy, + .percent_min = 0, + .percent_max = 10, + .descr = "Failed to start C2", + }, + { + .stage = UpdateTaskStageRadioBusy, + .percent_min = 11, + .percent_max = 20, + .descr = "C2 FUS swich failed", + }, + { + .stage = UpdateTaskStageRadioBusy, + .percent_min = 21, + .percent_max = 30, + .descr = "FUS operation failed", + }, + { + .stage = UpdateTaskStageRadioBusy, + .percent_min = 31, + .percent_max = 100, + .descr = "C2 Stach switch failed", + }, + { + .stage = UpdateTaskStageOBValidation, + .percent_min = 0, + .percent_max = 100, + .descr = "Uncorr. value mismatch", + }, + { + .stage = UpdateTaskStageValidateDFUImage, + .percent_min = 0, + .percent_max = 1, + .descr = "Failed to open DFU file", + }, + { + .stage = UpdateTaskStageValidateDFUImage, + .percent_min = 1, + .percent_max = 97, + .descr = "DFU file read error", + }, + { + .stage = UpdateTaskStageValidateDFUImage, + .percent_min = 98, + .percent_max = 100, + .descr = "DFU file CRC mismatch", + }, + { + .stage = UpdateTaskStageFlashWrite, + .percent_min = 0, + .percent_max = 100, + .descr = "Flash write error", + }, + { + .stage = UpdateTaskStageFlashValidate, + .percent_min = 0, + .percent_max = 100, + .descr = "Flash compare error", + }, +#endif +#ifndef FURI_RAM_EXEC + { + .stage = UpdateTaskStageLfsRestore, + .percent_min = 0, + .percent_max = 100, + .descr = "LFS I/O error", + }, + { + .stage = UpdateTaskStageResourcesUpdate, + .percent_min = 0, + .percent_max = 100, + .descr = "SD card I/O error", + }, +#endif +}; + +static const char* update_task_get_error_message(UpdateTaskStage stage, uint8_t percent) { + for(size_t i = 0; i < COUNT_OF(update_task_error_detail); i++) { + if(update_task_error_detail[i].stage == stage && + percent >= update_task_error_detail[i].percent_min && + percent <= update_task_error_detail[i].percent_max) { + return update_task_error_detail[i].descr; + } + } + return "Unknown error"; +} + typedef struct { UpdateTaskStageGroup group; uint8_t weight; @@ -111,8 +296,9 @@ void update_task_set_progress(UpdateTask* update_task, UpdateTaskStage stage, ui if(stage >= UpdateTaskStageError) { furi_string_printf( update_task->state.status, - "%s #[%d-%d]", - update_task_stage_descr[stage], + "%s\n#[%d-%d]", + update_task_get_error_message( + update_task->state.stage, update_task->state.stage_progress), update_task->state.stage, update_task->state.stage_progress); } else { diff --git a/applications/system/updater/util/update_task_i.h b/applications/system/updater/util/update_task_i.h index 0dbeca5f491..1b664e57e4c 100644 --- a/applications/system/updater/util/update_task_i.h +++ b/applications/system/updater/util/update_task_i.h @@ -24,3 +24,8 @@ bool update_task_open_file(UpdateTask* update_task, FuriString* filename); int32_t update_task_worker_flash_writer(void* context); int32_t update_task_worker_backup_restore(void* context); + +#define CHECK_RESULT(x) \ + if(!(x)) { \ + break; \ + } diff --git a/applications/system/updater/util/update_task_worker_backup.c b/applications/system/updater/util/update_task_worker_backup.c index f2c33c2edb4..ef4276fac5e 100644 --- a/applications/system/updater/util/update_task_worker_backup.c +++ b/applications/system/updater/util/update_task_worker_backup.c @@ -15,11 +15,6 @@ #define TAG "UpdWorkerBackup" -#define CHECK_RESULT(x) \ - if(!(x)) { \ - break; \ - } - static bool update_task_pre_update(UpdateTask* update_task) { bool success = false; FuriString* backup_file_path; diff --git a/applications/system/updater/util/update_task_worker_flasher.c b/applications/system/updater/util/update_task_worker_flasher.c index 5d247746464..d6dc13e378d 100644 --- a/applications/system/updater/util/update_task_worker_flasher.c +++ b/applications/system/updater/util/update_task_worker_flasher.c @@ -13,11 +13,6 @@ #define TAG "UpdWorkerRAM" -#define CHECK_RESULT(x) \ - if(!(x)) { \ - break; \ - } - #define STM_DFU_VENDOR_ID 0x0483 #define STM_DFU_PRODUCT_ID 0xDF11 /* Written into DFU file by build pipeline */ @@ -137,7 +132,7 @@ static bool update_task_write_stack_data(UpdateTask* update_task) { } static void update_task_wait_for_restart(UpdateTask* update_task) { - update_task_set_progress(update_task, UpdateTaskStageRadioBusy, 10); + update_task_set_progress(update_task, UpdateTaskStageRadioBusy, 70); furi_delay_ms(C2_MODE_SWITCH_TIMEOUT); furi_crash("C2 timeout"); } @@ -153,12 +148,12 @@ static bool update_task_write_stack(UpdateTask* update_task) { manifest->radio_crc); CHECK_RESULT(update_task_write_stack_data(update_task)); - update_task_set_progress(update_task, UpdateTaskStageRadioInstall, 0); + update_task_set_progress(update_task, UpdateTaskStageRadioInstall, 10); CHECK_RESULT( ble_glue_fus_stack_install(manifest->radio_address, 0) != BleGlueCommandResultError); - update_task_set_progress(update_task, UpdateTaskStageRadioInstall, 80); + update_task_set_progress(update_task, UpdateTaskStageProgress, 80); CHECK_RESULT(ble_glue_fus_wait_operation() == BleGlueCommandResultOK); - update_task_set_progress(update_task, UpdateTaskStageRadioInstall, 100); + update_task_set_progress(update_task, UpdateTaskStageProgress, 100); /* ...system will restart here. */ update_task_wait_for_restart(update_task); } while(false); @@ -170,9 +165,9 @@ static bool update_task_remove_stack(UpdateTask* update_task) { FURI_LOG_W(TAG, "Removing stack"); update_task_set_progress(update_task, UpdateTaskStageRadioErase, 30); CHECK_RESULT(ble_glue_fus_stack_delete() != BleGlueCommandResultError); - update_task_set_progress(update_task, UpdateTaskStageRadioErase, 80); + update_task_set_progress(update_task, UpdateTaskStageProgress, 80); CHECK_RESULT(ble_glue_fus_wait_operation() == BleGlueCommandResultOK); - update_task_set_progress(update_task, UpdateTaskStageRadioErase, 100); + update_task_set_progress(update_task, UpdateTaskStageProgress, 100); /* ...system will restart here. */ update_task_wait_for_restart(update_task); } while(false); @@ -180,6 +175,7 @@ static bool update_task_remove_stack(UpdateTask* update_task) { } static bool update_task_manage_radiostack(UpdateTask* update_task) { + update_task_set_progress(update_task, UpdateTaskStageRadioBusy, 10); bool success = false; do { CHECK_RESULT(ble_glue_wait_for_c2_start(FURI_HAL_BT_C2_START_TIMEOUT)); @@ -208,15 +204,17 @@ static bool update_task_manage_radiostack(UpdateTask* update_task) { /* Version or type mismatch. Let's boot to FUS and start updating. */ FURI_LOG_W(TAG, "Restarting to FUS"); furi_hal_rtc_set_flag(FuriHalRtcFlagC2Update); + update_task_set_progress(update_task, UpdateTaskStageProgress, 20); + CHECK_RESULT(furi_hal_bt_ensure_c2_mode(BleGlueC2ModeFUS)); /* ...system will restart here. */ update_task_wait_for_restart(update_task); } } else if(c2_state->mode == BleGlueC2ModeFUS) { /* OK, we're in FUS mode. */ - update_task_set_progress(update_task, UpdateTaskStageRadioBusy, 10); FURI_LOG_W(TAG, "Waiting for FUS to settle"); - ble_glue_fus_wait_operation(); + update_task_set_progress(update_task, UpdateTaskStageProgress, 30); + CHECK_RESULT(ble_glue_fus_wait_operation() == BleGlueCommandResultOK); if(stack_version_match) { /* We can't check StackType with FUS, but partial version matches */ if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagC2Update)) { @@ -230,7 +228,7 @@ static bool update_task_manage_radiostack(UpdateTask* update_task) { /* We might just had the stack installed. * Let's start it up to check its version */ FURI_LOG_W(TAG, "Starting stack to check full version"); - update_task_set_progress(update_task, UpdateTaskStageRadioBusy, 40); + update_task_set_progress(update_task, UpdateTaskStageProgress, 50); CHECK_RESULT(furi_hal_bt_ensure_c2_mode(BleGlueC2ModeStack)); /* ...system will restart here. */ update_task_wait_for_restart(update_task); diff --git a/applications/system/updater/views/updater_main.c b/applications/system/updater/views/updater_main.c index 1199cc882aa..d32d51b7c0b 100644 --- a/applications/system/updater/views/updater_main.c +++ b/applications/system/updater/views/updater_main.c @@ -81,16 +81,17 @@ static void updater_main_draw_callback(Canvas* canvas, void* _model) { canvas_set_font(canvas, FontPrimary); if(model->failed) { - canvas_draw_str_aligned(canvas, 42, 16, AlignLeft, AlignTop, "Update Failed!"); + canvas_draw_icon(canvas, 2, 22, &I_Warning_30x23); + canvas_draw_str_aligned(canvas, 40, 9, AlignLeft, AlignTop, "Update Failed!"); canvas_set_font(canvas, FontSecondary); - canvas_draw_str_aligned( - canvas, 42, 32, AlignLeft, AlignTop, furi_string_get_cstr(model->status)); - canvas_draw_icon(canvas, 7, 16, &I_Warning_30x23); + elements_multiline_text_aligned( + canvas, 75, 26, AlignCenter, AlignTop, furi_string_get_cstr(model->status)); + canvas_draw_str_aligned( - canvas, 18, 51, AlignLeft, AlignTop, "to retry, hold to abort"); - canvas_draw_icon(canvas, 7, 50, &I_Ok_btn_9x9); - canvas_draw_icon(canvas, 75, 51, &I_Pin_back_arrow_10x8); + canvas, 18, 55, AlignLeft, AlignTop, "to retry, hold to abort"); + canvas_draw_icon(canvas, 7, 54, &I_Ok_btn_9x9); + canvas_draw_icon(canvas, 75, 55, &I_Pin_back_arrow_10x8); } else { canvas_draw_str_aligned(canvas, 55, 14, AlignLeft, AlignTop, "UPDATING"); canvas_set_font(canvas, FontSecondary); diff --git a/documentation/OTA.md b/documentation/OTA.md index 799548f4d52..ed75560cfe6 100644 --- a/documentation/OTA.md +++ b/documentation/OTA.md @@ -87,9 +87,12 @@ Even if something goes wrong, updater allows you to retry failed operations and | Uninstalling radio FW | **4** | **0** | SHCI Delete command error | | | | **80** | Error awaiting command status | | Writing radio FW | **5** | **0-100** | Block read/write error | -| Installing radio FW | **6** | **0** | SHCI Install command error | +| Installing radio FW | **6** | **10** | SHCI Install command error | | | | **80** | Error awaiting command status | -| Radio is updating | **7** | **10** | Error waiting for operation completion | +| Core2 is busy | **7** | **10** | Couldn't start C2 | +| | | **20** | Failed to switch C2 to FUS mode | +| | | **30** | Error in FUS operation | +| | | **50** | Failed to switch C2 to stack mode | | Validating opt. bytes | **8** | **yy** | Option byte code | | Checking DFU file | **9** | **0** | Error opening DFU file | | | | **1-98** | Error reading DFU file | diff --git a/lib/update_util/update_operation.c b/lib/update_util/update_operation.c index 6e05b023317..0cecfc016a3 100644 --- a/lib/update_util/update_operation.c +++ b/lib/update_util/update_operation.c @@ -20,7 +20,8 @@ static const char* update_prepare_result_descr[] = { [UpdatePrepareResultManifestInvalid] = "Invalid manifest data", [UpdatePrepareResultStageMissing] = "Missing Stage2 loader", [UpdatePrepareResultStageIntegrityError] = "Corrupted Stage2 loader", - [UpdatePrepareResultManifestPointerError] = "Failed to create update pointer file", + [UpdatePrepareResultManifestPointerCreateError] = "Failed to create update pointer file", + [UpdatePrepareResultManifestPointerCheckError] = "Update pointer file error (corrupted FS?)", [UpdatePrepareResultTargetMismatch] = "Hardware target mismatch", [UpdatePrepareResultOutdatedManifestVersion] = "Update package is too old", [UpdatePrepareResultIntFull] = "Need more free space in internal storage", @@ -142,8 +143,8 @@ UpdatePrepareResult update_operation_prepare(const char* manifest_file_path) { File* file = storage_file_alloc(storage); uint64_t free_int_space; - FuriString* stage_path; - stage_path = furi_string_alloc(); + FuriString* stage_path = furi_string_alloc(); + FuriString* manifest_path_check = furi_string_alloc(); do { if((storage_common_fs_info(storage, STORAGE_INT_PATH_PREFIX, NULL, &free_int_space) != FSE_OK) || @@ -188,7 +189,18 @@ UpdatePrepareResult update_operation_prepare(const char* manifest_file_path) { } if(!update_operation_persist_manifest_path(storage, manifest_file_path)) { - result = UpdatePrepareResultManifestPointerError; + result = UpdatePrepareResultManifestPointerCreateError; + break; + } + + if(!update_operation_get_current_package_manifest_path(storage, manifest_path_check) || + (furi_string_cmpi_str(manifest_path_check, manifest_file_path) != 0)) { + FURI_LOG_E( + "update", + "Manifest pointer check failed: '%s' != '%s'", + furi_string_get_cstr(manifest_path_check), + manifest_file_path); + result = UpdatePrepareResultManifestPointerCheckError; break; } @@ -197,6 +209,7 @@ UpdatePrepareResult update_operation_prepare(const char* manifest_file_path) { } while(false); furi_string_free(stage_path); + furi_string_free(manifest_path_check); storage_file_free(file); update_manifest_free(manifest); diff --git a/lib/update_util/update_operation.h b/lib/update_util/update_operation.h index 65abf8e1504..8e36b5a1370 100644 --- a/lib/update_util/update_operation.h +++ b/lib/update_util/update_operation.h @@ -28,7 +28,8 @@ typedef enum { UpdatePrepareResultManifestInvalid, UpdatePrepareResultStageMissing, UpdatePrepareResultStageIntegrityError, - UpdatePrepareResultManifestPointerError, + UpdatePrepareResultManifestPointerCreateError, + UpdatePrepareResultManifestPointerCheckError, UpdatePrepareResultTargetMismatch, UpdatePrepareResultOutdatedManifestVersion, UpdatePrepareResultIntFull, From 20f6394ad8177e7e7589ad185e16193f7bab5f11 Mon Sep 17 00:00:00 2001 From: hedger Date: Mon, 17 Jul 2023 11:51:15 +0400 Subject: [PATCH 668/824] [FL-3431] Radio headers in SDK (#2881) --- applications/drivers/subghz/application.fam | 1 + firmware/targets/f18/api_symbols.csv | 3 ++- firmware/targets/f7/api_symbols.csv | 7 ++++++- lib/drivers/SConscript | 3 +++ lib/subghz/SConscript | 1 + scripts/fbt/appmanifest.py | 11 ++++++++++- 6 files changed, 23 insertions(+), 3 deletions(-) diff --git a/applications/drivers/subghz/application.fam b/applications/drivers/subghz/application.fam index aaf0e1bd94d..2ee114833bb 100644 --- a/applications/drivers/subghz/application.fam +++ b/applications/drivers/subghz/application.fam @@ -4,5 +4,6 @@ App( targets=["f7"], entry_point="subghz_device_cc1101_ext_ep", requires=["subghz"], + sdk_headers=["cc1101_ext/cc1101_ext_interconnect.h"], fap_libs=["hwdrivers"], ) diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index bbeaa3b1a1f..20c82975581 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,34.1,, +Version,+,34.2,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -82,6 +82,7 @@ Header,+,firmware/targets/furi_hal_include/furi_hal_usb_hid_u2f.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_version.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_vibro.h,, Header,+,lib/digital_signal/digital_signal.h,, +Header,+,lib/drivers/cc1101_regs.h,, Header,+,lib/flipper_application/api_hashtable/api_hashtable.h,, Header,+,lib/flipper_application/api_hashtable/compilesort.hpp,, Header,+,lib/flipper_application/flipper_application.h,, diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 0d33f70f637..cc1d016dd19 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,6 @@ entry,status,name,type,params -Version,+,34.1,, +Version,+,34.2,, +Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -87,6 +88,7 @@ Header,+,firmware/targets/furi_hal_include/furi_hal_usb_hid_u2f.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_version.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_vibro.h,, Header,+,lib/digital_signal/digital_signal.h,, +Header,+,lib/drivers/cc1101_regs.h,, Header,+,lib/flipper_application/api_hashtable/api_hashtable.h,, Header,+,lib/flipper_application/api_hashtable/compilesort.hpp,, Header,+,lib/flipper_application/flipper_application.h,, @@ -192,6 +194,7 @@ Header,+,lib/subghz/blocks/encoder.h,, Header,+,lib/subghz/blocks/generic.h,, Header,+,lib/subghz/blocks/math.h,, Header,+,lib/subghz/devices/cc1101_configs.h,, +Header,+,lib/subghz/devices/cc1101_int/cc1101_int_interconnect.h,, Header,+,lib/subghz/environment.h,, Header,+,lib/subghz/protocols/raw.h,, Header,+,lib/subghz/receiver.h,, @@ -2697,6 +2700,7 @@ Function,+,subghz_block_generic_deserialize,SubGhzProtocolStatus,"SubGhzBlockGen Function,+,subghz_block_generic_deserialize_check_count_bit,SubGhzProtocolStatus,"SubGhzBlockGeneric*, FlipperFormat*, uint16_t" Function,+,subghz_block_generic_get_preset_name,void,"const char*, FuriString*" Function,+,subghz_block_generic_serialize,SubGhzProtocolStatus,"SubGhzBlockGeneric*, FlipperFormat*, SubGhzRadioPreset*" +Function,-,subghz_device_cc1101_ext_ep,const FlipperAppPluginDescriptor*, Function,+,subghz_devices_begin,_Bool,const SubGhzDevice* Function,+,subghz_devices_deinit,void, Function,+,subghz_devices_end,void,const SubGhzDevice* @@ -3436,6 +3440,7 @@ Variable,+,sequence_set_vibro_on,const NotificationSequence, Variable,+,sequence_single_vibro,const NotificationSequence, Variable,+,sequence_solid_yellow,const NotificationSequence, Variable,+,sequence_success,const NotificationSequence, +Variable,-,subghz_device_cc1101_int,const SubGhzDevice, Variable,+,subghz_device_cc1101_preset_2fsk_dev2_38khz_async_regs,const uint8_t[], Variable,+,subghz_device_cc1101_preset_2fsk_dev47_6khz_async_regs,const uint8_t[], Variable,+,subghz_device_cc1101_preset_gfsk_9_99kb_async_regs,const uint8_t[], diff --git a/lib/drivers/SConscript b/lib/drivers/SConscript index 3b7ee2401e6..103472ccb35 100644 --- a/lib/drivers/SConscript +++ b/lib/drivers/SConscript @@ -4,6 +4,9 @@ env.Append( CPPPATH=[ "#/lib/drivers", ], + SDK_HEADERS=[ + File("cc1101_regs.h"), + ], ) diff --git a/lib/subghz/SConscript b/lib/subghz/SConscript index 2c42a5157f2..82c925c1a31 100644 --- a/lib/subghz/SConscript +++ b/lib/subghz/SConscript @@ -20,6 +20,7 @@ env.Append( File("subghz_setting.h"), File("subghz_protocol_registry.h"), File("devices/cc1101_configs.h"), + File("devices/cc1101_int/cc1101_int_interconnect.h"), ], ) diff --git a/scripts/fbt/appmanifest.py b/scripts/fbt/appmanifest.py index 5b830dda94d..0064b359107 100644 --- a/scripts/fbt/appmanifest.py +++ b/scripts/fbt/appmanifest.py @@ -325,7 +325,16 @@ def get_apps_cdefs(self): def get_sdk_headers(self): sdk_headers = [] for app in self.apps: - sdk_headers.extend([app._appdir.File(header) for header in app.sdk_headers]) + sdk_headers.extend( + [ + src._appdir.File(header) + for src in [ + app, + *(plugin for plugin in app._plugins), + ] + for header in src.sdk_headers + ] + ) return sdk_headers def get_apps_of_type(self, apptype: FlipperAppType, all_known: bool = False): From 9bb04832a84901b0fcf1b0921b472825fbca6bef Mon Sep 17 00:00:00 2001 From: Dzhos Oleksii <35292229+Programistich@users.noreply.github.com> Date: Mon, 17 Jul 2023 11:03:27 +0300 Subject: [PATCH 669/824] IButton: on delete scene key name not fully display if so long (#2882) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- .../main/ibutton/scenes/ibutton_scene_delete_confirm.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/applications/main/ibutton/scenes/ibutton_scene_delete_confirm.c b/applications/main/ibutton/scenes/ibutton_scene_delete_confirm.c index 587cb748cd4..b293af952ce 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_delete_confirm.c +++ b/applications/main/ibutton/scenes/ibutton_scene_delete_confirm.c @@ -12,15 +12,15 @@ void ibutton_scene_delete_confirm_on_enter(void* context) { widget_add_button_element( widget, GuiButtonTypeRight, "Delete", ibutton_widget_callback, context); - furi_string_printf(tmp, "Delete %s?", ibutton->key_name); - widget_add_string_element( - widget, 128 / 2, 0, AlignCenter, AlignTop, FontPrimary, furi_string_get_cstr(tmp)); + furi_string_printf(tmp, "\e#Delete %s?\e#", ibutton->key_name); + widget_add_text_box_element( + widget, 0, 0, 128, 23, AlignCenter, AlignCenter, furi_string_get_cstr(tmp), false); furi_string_reset(tmp); ibutton_protocols_render_brief_data(ibutton->protocols, key, tmp); widget_add_string_multiline_element( - widget, 128 / 2, 16, AlignCenter, AlignTop, FontSecondary, furi_string_get_cstr(tmp)); + widget, 128 / 2, 24, AlignCenter, AlignTop, FontSecondary, furi_string_get_cstr(tmp)); view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget); furi_string_free(tmp); From 309f65e40167799fb11a259b12ffa4e62338508b Mon Sep 17 00:00:00 2001 From: hedger Date: Tue, 18 Jul 2023 13:39:30 +0400 Subject: [PATCH 670/824] [FL-3437] fbt: `build` target for faps (#2888) * fbt: added build target for faps. Usage: ./fbt build APPSRC= * Updated docs & vscode config * Code cleanup --- .vscode/example/tasks.json | 20 +++++-- .../examples/example_thermo/README.md | 2 +- documentation/AppsOnSDCard.md | 3 +- scripts/fbt/appmanifest.py | 5 +- scripts/fbt_tools/fbt_extapps.py | 55 ++++++++++++------- scripts/fbt_tools/fbt_help.py | 5 +- scripts/ufbt/SConstruct | 2 + site_scons/extapps.scons | 14 ++++- 8 files changed, 73 insertions(+), 33 deletions(-) diff --git a/.vscode/example/tasks.json b/.vscode/example/tasks.json index 28e67d456dc..3c01506a8c1 100644 --- a/.vscode/example/tasks.json +++ b/.vscode/example/tasks.json @@ -4,13 +4,13 @@ "version": "2.0.0", "tasks": [ { - "label": "[Release] Build", + "label": "[Release] Build Firmware", "group": "build", "type": "shell", "command": "./fbt COMPACT=1 DEBUG=0" }, { - "label": "[Debug] Build", + "label": "[Debug] Build Firmware", "group": "build", "type": "shell", "command": "./fbt" @@ -123,17 +123,29 @@ "type": "shell", "command": "./fbt COMPACT=1 DEBUG=0 fap_dist" }, + { + "label": "[Debug] Build App", + "group": "build", + "type": "shell", + "command": "./fbt build APPSRC=${relativeFileDirname}" + }, + { + "label": "[Release] Build App", + "group": "build", + "type": "shell", + "command": "./fbt COMPACT=1 DEBUG=0 build APPSRC=${relativeFileDirname}" + }, { "label": "[Debug] Launch App on Flipper", "group": "build", "type": "shell", - "command": "./fbt launch_app APPSRC=${relativeFileDirname}" + "command": "./fbt launch APPSRC=${relativeFileDirname}" }, { "label": "[Release] Launch App on Flipper", "group": "build", "type": "shell", - "command": "./fbt COMPACT=1 DEBUG=0 launch_app APPSRC=${relativeFileDirname}" + "command": "./fbt COMPACT=1 DEBUG=0 launch APPSRC=${relativeFileDirname}" }, { "label": "[Debug] Launch App on Flipper with Serial Console", diff --git a/applications/examples/example_thermo/README.md b/applications/examples/example_thermo/README.md index 08240a1f8fa..d298de64309 100644 --- a/applications/examples/example_thermo/README.md +++ b/applications/examples/example_thermo/README.md @@ -18,7 +18,7 @@ Before launching the application, connect the sensor to Flipper's external GPIO In order to launch this demo, follow the steps below: 1. Make sure your Flipper has an SD card installed. 2. Connect your Flipper to the computer via a USB cable. -3. Run `./fbt launch_app APPSRC=example_thermo` in your terminal emulator of choice. +3. Run `./fbt launch APPSRC=example_thermo` in your terminal emulator of choice. ## Changing the data pin It is possible to use other GPIO pin as a 1-Wire data pin. In order to change it, set the `THERMO_GPIO_PIN` macro to any of the options listed below: diff --git a/documentation/AppsOnSDCard.md b/documentation/AppsOnSDCard.md index 212df5b1bf0..051fbb84f5b 100644 --- a/documentation/AppsOnSDCard.md +++ b/documentation/AppsOnSDCard.md @@ -13,7 +13,8 @@ FAPs are created and developed the same way as internal applications that are pa To build your application as a FAP, create a folder with your app's source code in `applications_user`, then write its code the way you'd do when creating a regular built-in application. Then configure its `application.fam` manifest, and set its _apptype_ to FlipperAppType.EXTERNAL. See [Application Manifests](./AppManifests.md#application-definition) for more details. - To build your application, run `./fbt fap_{APPID}`, where APPID is your application's ID in its manifest. -- To build your app and upload it over USB to run on Flipper, use `./fbt launch_app APPSRC=applications_user/path/to/app`. This command is configured in the default [VS Code profile](../.vscode/ReadMe.md) as a "Launch App on Flipper" build action (Ctrl+Shift+B menu). +- To build your app and upload it over USB to run on Flipper, use `./fbt launch APPSRC=applications_user/path/to/app`. This command is configured in the default [VS Code profile](../.vscode/ReadMe.md) as a "Launch App on Flipper" build action (Ctrl+Shift+B menu). +- To build an app without uploading it to Flipper, use `./fbt build APPSRC=applications_user/path/to/app`. This command is also availabe in VSCode configuration as "Build App". - To build all FAPs, run `./fbt faps` or `./fbt fap_dist`. ## FAP assets diff --git a/scripts/fbt/appmanifest.py b/scripts/fbt/appmanifest.py index 0064b359107..067b4a26f09 100644 --- a/scripts/fbt/appmanifest.py +++ b/scripts/fbt/appmanifest.py @@ -328,10 +328,7 @@ def get_sdk_headers(self): sdk_headers.extend( [ src._appdir.File(header) - for src in [ - app, - *(plugin for plugin in app._plugins), - ] + for src in [app, *app._plugins] for header in src.sdk_headers ] ) diff --git a/scripts/fbt_tools/fbt_extapps.py b/scripts/fbt_tools/fbt_extapps.py index a6cd831d40d..1766d4c4447 100644 --- a/scripts/fbt_tools/fbt_extapps.py +++ b/scripts/fbt_tools/fbt_extapps.py @@ -3,7 +3,7 @@ import pathlib import shutil from dataclasses import dataclass, field -from typing import Optional +from typing import Optional, Dict, List import SCons.Warnings from ansi.color import fg @@ -400,22 +400,26 @@ def generate_embed_app_metadata_actions(source, target, env, for_signature): return Action(actions) -def AddAppLaunchTarget(env, appname, launch_target_name): - deploy_sources, flipp_dist_paths, validators = [], [], [] - run_script_extra_ars = "" +@dataclass +class AppDeploymentComponents: + deploy_sources: Dict[str, object] = field(default_factory=dict) + validators: List[object] = field(default_factory=list) + extra_launch_args: str = "" - def _add_dist_targets(app_artifacts): - validators.append(app_artifacts.validator) + def add_app(self, app_artifacts): for _, ext_path in app_artifacts.dist_entries: - deploy_sources.append(app_artifacts.compact) - flipp_dist_paths.append(f"/ext/{ext_path}") - return app_artifacts + self.deploy_sources[f"/ext/{ext_path}"] = app_artifacts.compact + self.validators.append(app_artifacts.validator) + + +def _gather_app_components(env, appname) -> AppDeploymentComponents: + components = AppDeploymentComponents() def _add_host_app_to_targets(host_app): artifacts_app_to_run = env["EXT_APPS"].get(host_app.appid, None) - _add_dist_targets(artifacts_app_to_run) + components.add_app(artifacts_app_to_run) for plugin in host_app._plugins: - _add_dist_targets(env["EXT_APPS"].get(plugin.appid, None)) + components.add_app(env["EXT_APPS"].get(plugin.appid, None)) artifacts_app_to_run = env.GetExtAppByIdOrPath(appname) if artifacts_app_to_run.app.apptype == FlipperAppType.PLUGIN: @@ -427,25 +431,35 @@ def _add_host_app_to_targets(host_app): FlipperAppType.EXTERNAL, FlipperAppType.MENUEXTERNAL, ]: - _add_host_app_to_targets(host_app) + components.add_app(host_app) else: # host app is a built-in app - run_script_extra_ars = f"-a {host_app.name}" - _add_dist_targets(artifacts_app_to_run) + components.add_app(artifacts_app_to_run) + components.extra_launch_args = f"-a {host_app.name}" else: raise UserError("Host app is unknown") else: _add_host_app_to_targets(artifacts_app_to_run.app) + return components - # print(deploy_sources, flipp_dist_paths) - env.PhonyTarget( + +def AddAppLaunchTarget(env, appname, launch_target_name): + components = _gather_app_components(env, appname) + target = env.PhonyTarget( launch_target_name, '${PYTHON3} "${APP_RUN_SCRIPT}" -p ${FLIP_PORT} ${EXTRA_ARGS} -s ${SOURCES} -t ${FLIPPER_FILE_TARGETS}', - source=deploy_sources, - FLIPPER_FILE_TARGETS=flipp_dist_paths, - EXTRA_ARGS=run_script_extra_ars, + source=components.deploy_sources.values(), + FLIPPER_FILE_TARGETS=components.deploy_sources.keys(), + EXTRA_ARGS=components.extra_launch_args, ) - env.Alias(launch_target_name, validators) + env.Alias(launch_target_name, components.validators) + return target + + +def AddAppBuildTarget(env, appname, build_target_name): + components = _gather_app_components(env, appname) + env.Alias(build_target_name, components.validators) + env.Alias(build_target_name, components.deploy_sources.values()) def generate(env, **kw): @@ -474,6 +488,7 @@ def generate(env, **kw): env.AddMethod(BuildAppElf) env.AddMethod(GetExtAppByIdOrPath) env.AddMethod(AddAppLaunchTarget) + env.AddMethod(AddAppBuildTarget) env.Append( BUILDERS={ diff --git a/scripts/fbt_tools/fbt_help.py b/scripts/fbt_tools/fbt_help.py index c7452af9884..68fc2aaf9ff 100644 --- a/scripts/fbt_tools/fbt_help.py +++ b/scripts/fbt_tools/fbt_help.py @@ -4,15 +4,16 @@ tail_help = """ TASKS: -Building: +Firmware & apps: firmware_all, fw_dist: Build firmware; create distribution package faps, fap_dist: Build all FAP apps - fap_{APPID}, launch_app APPSRC={APPID}: + fap_{APPID}, build APPSRC={APPID}; launch APPSRC={APPID}: Build FAP app with appid={APPID}; upload & start it over USB fap_deploy: Build and upload all FAP apps over USB + Flashing & debugging: flash, flash_blackmagic, jflash: diff --git a/scripts/ufbt/SConstruct b/scripts/ufbt/SConstruct index 703a9187aaa..9a9e0938c1b 100644 --- a/scripts/ufbt/SConstruct +++ b/scripts/ufbt/SConstruct @@ -336,8 +336,10 @@ def ambiguous_app_call(**kw): if app_to_launch: appenv.AddAppLaunchTarget(app_to_launch, "launch") + appenv.AddAppBuildTarget(app_to_launch, "build") else: dist_env.PhonyTarget("launch", Action(ambiguous_app_call, None)) + dist_env.PhonyTarget("build", Action(ambiguous_app_call, None)) # cli handler diff --git a/site_scons/extapps.scons b/site_scons/extapps.scons index 0893c455617..5c6f18d6884 100644 --- a/site_scons/extapps.scons +++ b/site_scons/extapps.scons @@ -114,8 +114,20 @@ extapps.resources_dist = appenv.FapDist(appenv["RESOURCES_ROOT"], []) if appsrc := appenv.subst("$APPSRC"): - appenv.AddAppLaunchTarget(appsrc, "launch_app") + launch_target = appenv.AddAppLaunchTarget(appsrc, "launch") + Alias("launch_app", launch_target) + appenv.PhonyTarget( + "launch_app", + Action( + lambda **kw: warn( + WarningOnByDefault, + "The 'launch_app' target is deprecated. Use 'launch' instead.", + ), + None, + ), + ) + appenv.AddAppBuildTarget(appsrc, "build") # SDK management From 76e97b8d35807ea86308b2a45445311226ff64ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Tue, 18 Jul 2023 14:46:38 +0400 Subject: [PATCH 671/824] [FL-3051] Gauge initialization routine refactoring, new DataMemory layout, configuration update (#2887) * FuriHal: refactor power gauge config * Format sources and move gauge DM load to separate method * FuriHal: bq27220 refactoring part 1 * Power: use SYSDWN battery status flag for system shutdown * Libs: bq27220 read DM before write, fix incorrect shift * FuriHal: cleanup gauge config, add flags, add ptr DM type, update symbols * FuriHal: 2 stage gauge DM verification and update, better detection routine * FuriHal: update gauge configuration, lower sleep current and deadband * FuriHal: gauge and charger health reporting * Lib: cleanup bq27220 sources * FuriHal: correct documentation for furi_hal_power_is_shutdown_requested * FuriHal: proper gauge config for f7 --- .../services/power/power_service/power.c | 3 +- .../services/power/power_service/power.h | 1 + .../power_settings_app/views/battery_info.c | 3 +- firmware/targets/f18/api_symbols.csv | 4 +- .../f18/furi_hal/furi_hal_power_calibration.h | 37 --- .../f18/furi_hal/furi_hal_power_config.c | 149 +++++++++++ firmware/targets/f7/api_symbols.csv | 4 +- firmware/targets/f7/furi_hal/furi_hal_power.c | 46 +++- .../f7/furi_hal/furi_hal_power_calibration.h | 37 --- .../f7/furi_hal/furi_hal_power_config.c | 149 +++++++++++ .../targets/furi_hal_include/furi_hal_power.h | 6 + lib/drivers/bq25896.c | 18 +- lib/drivers/bq25896.h | 2 +- lib/drivers/bq27220.c | 248 +++++++++++++----- lib/drivers/bq27220.h | 60 +---- lib/drivers/bq27220_data_memory.h | 84 ++++++ lib/drivers/bq27220_reg.h | 25 -- 17 files changed, 626 insertions(+), 250 deletions(-) delete mode 100644 firmware/targets/f18/furi_hal/furi_hal_power_calibration.h create mode 100644 firmware/targets/f18/furi_hal/furi_hal_power_config.c delete mode 100644 firmware/targets/f7/furi_hal/furi_hal_power_calibration.h create mode 100644 firmware/targets/f7/furi_hal/furi_hal_power_config.c create mode 100644 lib/drivers/bq27220_data_memory.h diff --git a/applications/services/power/power_service/power.c b/applications/services/power/power_service/power.c index 72dc8f3f1e7..aadb5f46e40 100644 --- a/applications/services/power/power_service/power.c +++ b/applications/services/power/power_service/power.c @@ -117,6 +117,7 @@ static bool power_update_info(Power* power) { info.is_charging = furi_hal_power_is_charging(); info.gauge_is_ok = furi_hal_power_gauge_is_ok(); + info.is_shutdown_requested = furi_hal_power_is_shutdown_requested(); info.charge = furi_hal_power_get_pct(); info.health = furi_hal_power_get_bat_health_pct(); info.capacity_remaining = furi_hal_power_get_battery_remaining_capacity(); @@ -145,7 +146,7 @@ static void power_check_low_battery(Power* power) { } // Check battery charge and vbus voltage - if((power->info.charge == 0) && (power->info.voltage_vbus < 4.0f) && + if((power->info.is_shutdown_requested) && (power->info.voltage_vbus < 4.0f) && power->show_low_bat_level_message) { if(!power->battery_low) { view_dispatcher_send_to_front(power->view_dispatcher); diff --git a/applications/services/power/power_service/power.h b/applications/services/power/power_service/power.h index c7f5d7e3501..fdc5b527a60 100644 --- a/applications/services/power/power_service/power.h +++ b/applications/services/power/power_service/power.h @@ -37,6 +37,7 @@ typedef struct { typedef struct { bool gauge_is_ok; bool is_charging; + bool is_shutdown_requested; float current_charger; float current_gauge; diff --git a/applications/settings/power_settings_app/views/battery_info.c b/applications/settings/power_settings_app/views/battery_info.c index d56dfc62859..8add60db5ab 100644 --- a/applications/settings/power_settings_app/views/battery_info.c +++ b/applications/settings/power_settings_app/views/battery_info.c @@ -54,8 +54,7 @@ static void draw_battery(Canvas* canvas, BatteryInfoModel* data, int x, int y) { (uint32_t)(data->vbus_voltage * 10) % 10, current); } else if(current < -5) { - // Often gauge reports anything in the range 1~5ma as 5ma - // That brings confusion, so we'll treat it as Napping + // 0-5ma deadband snprintf( emote, sizeof(emote), diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index 20c82975581..8b04f441a8d 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,34.2,, +Version,+,34.3,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -36,7 +36,6 @@ Header,+,applications/services/notification/notification_messages.h,, Header,+,applications/services/power/power_service/power.h,, Header,+,applications/services/rpc/rpc_app.h,, Header,+,applications/services/storage/storage.h,, -Header,-,firmware/targets/f18/furi_hal/furi_hal_power_calibration.h,, Header,+,firmware/targets/f18/furi_hal/furi_hal_resources.h,, Header,+,firmware/targets/f18/furi_hal/furi_hal_spi_config.h,, Header,+,firmware/targets/f18/furi_hal/furi_hal_target_hw.h,, @@ -1149,6 +1148,7 @@ Function,-,furi_hal_power_insomnia_level,uint16_t, Function,+,furi_hal_power_is_charging,_Bool, Function,+,furi_hal_power_is_charging_done,_Bool, Function,+,furi_hal_power_is_otg_enabled,_Bool, +Function,+,furi_hal_power_is_shutdown_requested,_Bool, Function,+,furi_hal_power_off,void, Function,+,furi_hal_power_reset,void, Function,+,furi_hal_power_set_battery_charge_voltage_limit,void,float diff --git a/firmware/targets/f18/furi_hal/furi_hal_power_calibration.h b/firmware/targets/f18/furi_hal/furi_hal_power_calibration.h deleted file mode 100644 index e97e1657dba..00000000000 --- a/firmware/targets/f18/furi_hal/furi_hal_power_calibration.h +++ /dev/null @@ -1,37 +0,0 @@ -const ParamCEDV cedv = { - .cedv_conf.gauge_conf = - { - .CCT = 1, - .CSYNC = 0, - .EDV_CMP = 0, - .SC = 1, - .FIXED_EDV0 = 1, - .FCC_LIM = 1, - .FC_FOR_VDQ = 1, - .IGNORE_SD = 1, - .SME0 = 0, - }, - .full_charge_cap = 1300, - .design_cap = 1300, - .EDV0 = 3300, - .EDV1 = 3321, - .EDV2 = 3355, - .EMF = 3679, - .C0 = 430, - .C1 = 0, - .R1 = 408, - .R0 = 334, - .T0 = 4626, - .TC = 11, - .DOD0 = 4044, - .DOD10 = 3905, - .DOD20 = 3807, - .DOD30 = 3718, - .DOD40 = 3642, - .DOD50 = 3585, - .DOD60 = 3546, - .DOD70 = 3514, - .DOD80 = 3477, - .DOD90 = 3411, - .DOD100 = 3299, -}; diff --git a/firmware/targets/f18/furi_hal/furi_hal_power_config.c b/firmware/targets/f18/furi_hal/furi_hal_power_config.c new file mode 100644 index 00000000000..50efceb7de3 --- /dev/null +++ b/firmware/targets/f18/furi_hal/furi_hal_power_config.c @@ -0,0 +1,149 @@ +#include + +const BQ27220DMGaugingConfig furi_hal_power_gauge_data_memory_gauging_config = { + .CCT = 1, + .CSYNC = 0, + .EDV_CMP = 0, + .SC = 1, + .FIXED_EDV0 = 1, + .FCC_LIM = 1, + .FC_FOR_VDQ = 1, + .IGNORE_SD = 1, + .SME0 = 0, +}; + +const BQ27220DMData furi_hal_power_gauge_data_memory[] = { + { + .address = BQ27220DMAddressGasGaugingCEDVProfile1GaugingConfig, + .type = BQ27220DMTypePtr16, + .value.u32 = (uint32_t)&furi_hal_power_gauge_data_memory_gauging_config, + }, + { + .address = BQ27220DMAddressGasGaugingCEDVProfile1FullChargeCapacity, + .type = BQ27220DMTypeU16, + .value.u16 = 1300, + }, + { + .address = BQ27220DMAddressGasGaugingCEDVProfile1DesignCapacity, + .type = BQ27220DMTypeU16, + .value.u16 = 1300, + }, + { + .address = BQ27220DMAddressGasGaugingCEDVProfile1EMF, + .type = BQ27220DMTypeU16, + .value.u16 = 3679, + }, + { + .address = BQ27220DMAddressGasGaugingCEDVProfile1C0, + .type = BQ27220DMTypeU16, + .value.u16 = 430, + }, + { + .address = BQ27220DMAddressGasGaugingCEDVProfile1R0, + .type = BQ27220DMTypeU16, + .value.u16 = 334, + }, + { + .address = BQ27220DMAddressGasGaugingCEDVProfile1T0, + .type = BQ27220DMTypeU16, + .value.u16 = 4626, + }, + { + .address = BQ27220DMAddressGasGaugingCEDVProfile1R1, + .type = BQ27220DMTypeU16, + .value.u16 = 408, + }, + { + .address = BQ27220DMAddressGasGaugingCEDVProfile1TC, + .type = BQ27220DMTypeU8, + .value.u8 = 11, + }, + { + .address = BQ27220DMAddressGasGaugingCEDVProfile1C1, + .type = BQ27220DMTypeU8, + .value.u8 = 0, + }, + { + .address = BQ27220DMAddressGasGaugingCEDVProfile1StartDOD0, + .type = BQ27220DMTypeU16, + .value.u16 = 4044, + }, + { + .address = BQ27220DMAddressGasGaugingCEDVProfile1StartDOD10, + .type = BQ27220DMTypeU16, + .value.u16 = 3905, + }, + { + .address = BQ27220DMAddressGasGaugingCEDVProfile1StartDOD20, + .type = BQ27220DMTypeU16, + .value.u16 = 3807, + }, + { + .address = BQ27220DMAddressGasGaugingCEDVProfile1StartDOD30, + .type = BQ27220DMTypeU16, + .value.u16 = 3718, + }, + { + .address = BQ27220DMAddressGasGaugingCEDVProfile1StartDOD40, + .type = BQ27220DMTypeU16, + .value.u16 = 3642, + }, + { + .address = BQ27220DMAddressGasGaugingCEDVProfile1StartDOD50, + .type = BQ27220DMTypeU16, + .value.u16 = 3585, + }, + { + .address = BQ27220DMAddressGasGaugingCEDVProfile1StartDOD60, + .type = BQ27220DMTypeU16, + .value.u16 = 3546, + }, + { + .address = BQ27220DMAddressGasGaugingCEDVProfile1StartDOD70, + .type = BQ27220DMTypeU16, + .value.u16 = 3514, + }, + { + .address = BQ27220DMAddressGasGaugingCEDVProfile1StartDOD80, + .type = BQ27220DMTypeU16, + .value.u16 = 3477, + }, + { + .address = BQ27220DMAddressGasGaugingCEDVProfile1StartDOD90, + .type = BQ27220DMTypeU16, + .value.u16 = 3411, + }, + { + .address = BQ27220DMAddressGasGaugingCEDVProfile1StartDOD100, + .type = BQ27220DMTypeU16, + .value.u16 = 3299, + }, + { + .address = BQ27220DMAddressGasGaugingCEDVProfile1EDV0, + .type = BQ27220DMTypeU16, + .value.u16 = 3300, + }, + { + .address = BQ27220DMAddressGasGaugingCEDVProfile1EDV1, + .type = BQ27220DMTypeU16, + .value.u16 = 3321, + }, + { + .address = BQ27220DMAddressGasGaugingCEDVProfile1EDV2, + .type = BQ27220DMTypeU16, + .value.u16 = 3355, + }, + { + .address = BQ27220DMAddressCalibrationCurrentDeadband, + .type = BQ27220DMTypeU8, + .value.u8 = 1, + }, + { + .address = BQ27220DMAddressConfigurationPowerSleepCurrent, + .type = BQ27220DMTypeI16, + .value.i16 = 1, + }, + { + .type = BQ27220DMTypeEnd, + }, +}; diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index cc1d016dd19..dc0720ee217 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,34.2,, +Version,+,34.3,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -50,7 +50,6 @@ Header,+,firmware/targets/f7/furi_hal/furi_hal_idle_timer.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_interrupt.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_nfc.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_os.h,, -Header,-,firmware/targets/f7/furi_hal/furi_hal_power_calibration.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_pwm.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_resources.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_rfid.h,, @@ -1270,6 +1269,7 @@ Function,-,furi_hal_power_insomnia_level,uint16_t, Function,+,furi_hal_power_is_charging,_Bool, Function,+,furi_hal_power_is_charging_done,_Bool, Function,+,furi_hal_power_is_otg_enabled,_Bool, +Function,+,furi_hal_power_is_shutdown_requested,_Bool, Function,+,furi_hal_power_off,void, Function,+,furi_hal_power_reset,void, Function,+,furi_hal_power_set_battery_charge_voltage_limit,void,float diff --git a/firmware/targets/f7/furi_hal/furi_hal_power.c b/firmware/targets/f7/furi_hal/furi_hal_power.c index 5edde86e9a4..035919d784d 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_power.c +++ b/firmware/targets/f7/furi_hal/furi_hal_power.c @@ -15,6 +15,7 @@ #include #include +#include #include #include @@ -37,16 +38,18 @@ typedef struct { volatile uint8_t insomnia; volatile uint8_t suppress_charge; - uint8_t gauge_initialized; - uint8_t charger_initialized; + bool gauge_ok; + bool charger_ok; } FuriHalPower; static volatile FuriHalPower furi_hal_power = { .insomnia = 0, .suppress_charge = 0, + .gauge_ok = false, + .charger_ok = false, }; -#include +extern const BQ27220DMData furi_hal_power_gauge_data_memory[]; void furi_hal_power_init() { #ifdef FURI_HAL_POWER_DEBUG @@ -63,8 +66,13 @@ void furi_hal_power_init() { LL_C2_PWR_SetPowerMode(FURI_HAL_POWER_STOP_MODE); furi_hal_i2c_acquire(&furi_hal_i2c_handle_power); - bq27220_init(&furi_hal_i2c_handle_power, &cedv); - bq25896_init(&furi_hal_i2c_handle_power); + // Find and init gauge + if(bq27220_init(&furi_hal_i2c_handle_power)) { + furi_hal_power.gauge_ok = bq27220_apply_data_memory( + &furi_hal_i2c_handle_power, furi_hal_power_gauge_data_memory); + } + // Find and init charger + furi_hal_power.charger_ok = bq25896_init(&furi_hal_i2c_handle_power); furi_hal_i2c_release(&furi_hal_i2c_handle_power); FURI_LOG_I(TAG, "Init OK"); @@ -78,14 +86,29 @@ bool furi_hal_power_gauge_is_ok() { furi_hal_i2c_acquire(&furi_hal_i2c_handle_power); - if(bq27220_get_battery_status(&furi_hal_i2c_handle_power, &battery_status) == BQ27220_ERROR || - bq27220_get_operation_status(&furi_hal_i2c_handle_power, &operation_status) == - BQ27220_ERROR) { + if(!bq27220_get_battery_status(&furi_hal_i2c_handle_power, &battery_status) || + !bq27220_get_operation_status(&furi_hal_i2c_handle_power, &operation_status)) { ret = false; } else { ret &= battery_status.BATTPRES; ret &= operation_status.INITCOMP; - ret &= (cedv.design_cap == bq27220_get_design_capacity(&furi_hal_i2c_handle_power)); + ret &= furi_hal_power.gauge_ok; + } + + furi_hal_i2c_release(&furi_hal_i2c_handle_power); + + return ret; +} + +bool furi_hal_power_is_shutdown_requested() { + bool ret = false; + + BatteryStatus battery_status; + + furi_hal_i2c_acquire(&furi_hal_i2c_handle_power); + + if(bq27220_get_battery_status(&furi_hal_i2c_handle_power, &battery_status) != BQ27220_ERROR) { + ret = battery_status.SYSDWN; } furi_hal_i2c_release(&furi_hal_i2c_handle_power); @@ -576,9 +599,8 @@ void furi_hal_power_debug_get(PropertyValueCallback out, void* context) { const uint32_t ntc_mpct = bq25896_get_ntc_mpct(&furi_hal_i2c_handle_power); - if(bq27220_get_battery_status(&furi_hal_i2c_handle_power, &battery_status) != BQ27220_ERROR && - bq27220_get_operation_status(&furi_hal_i2c_handle_power, &operation_status) != - BQ27220_ERROR) { + if(bq27220_get_battery_status(&furi_hal_i2c_handle_power, &battery_status) && + bq27220_get_operation_status(&furi_hal_i2c_handle_power, &operation_status)) { property_value_out(&property_context, "%lu", 2, "charger", "ntc", ntc_mpct); property_value_out(&property_context, "%d", 2, "gauge", "calmd", operation_status.CALMD); property_value_out(&property_context, "%d", 2, "gauge", "sec", operation_status.SEC); diff --git a/firmware/targets/f7/furi_hal/furi_hal_power_calibration.h b/firmware/targets/f7/furi_hal/furi_hal_power_calibration.h deleted file mode 100644 index 5eb0f938b2e..00000000000 --- a/firmware/targets/f7/furi_hal/furi_hal_power_calibration.h +++ /dev/null @@ -1,37 +0,0 @@ -const ParamCEDV cedv = { - .cedv_conf.gauge_conf = - { - .CCT = 1, - .CSYNC = 0, - .EDV_CMP = 0, - .SC = 1, - .FIXED_EDV0 = 1, - .FCC_LIM = 1, - .FC_FOR_VDQ = 1, - .IGNORE_SD = 1, - .SME0 = 0, - }, - .full_charge_cap = 2101, - .design_cap = 2101, - .EDV0 = 3300, - .EDV1 = 3321, - .EDV2 = 3355, - .EMF = 3679, - .C0 = 430, - .C1 = 0, - .R1 = 408, - .R0 = 334, - .T0 = 4626, - .TC = 11, - .DOD0 = 4044, - .DOD10 = 3905, - .DOD20 = 3807, - .DOD30 = 3718, - .DOD40 = 3642, - .DOD50 = 3585, - .DOD60 = 3546, - .DOD70 = 3514, - .DOD80 = 3477, - .DOD90 = 3411, - .DOD100 = 3299, -}; diff --git a/firmware/targets/f7/furi_hal/furi_hal_power_config.c b/firmware/targets/f7/furi_hal/furi_hal_power_config.c new file mode 100644 index 00000000000..488edce91ee --- /dev/null +++ b/firmware/targets/f7/furi_hal/furi_hal_power_config.c @@ -0,0 +1,149 @@ +#include + +const BQ27220DMGaugingConfig furi_hal_power_gauge_data_memory_gauging_config = { + .CCT = 1, + .CSYNC = 0, + .EDV_CMP = 0, + .SC = 1, + .FIXED_EDV0 = 1, + .FCC_LIM = 1, + .FC_FOR_VDQ = 1, + .IGNORE_SD = 1, + .SME0 = 0, +}; + +const BQ27220DMData furi_hal_power_gauge_data_memory[] = { + { + .address = BQ27220DMAddressGasGaugingCEDVProfile1GaugingConfig, + .type = BQ27220DMTypePtr16, + .value.u32 = (uint32_t)&furi_hal_power_gauge_data_memory_gauging_config, + }, + { + .address = BQ27220DMAddressGasGaugingCEDVProfile1FullChargeCapacity, + .type = BQ27220DMTypeU16, + .value.u16 = 2100, + }, + { + .address = BQ27220DMAddressGasGaugingCEDVProfile1DesignCapacity, + .type = BQ27220DMTypeU16, + .value.u16 = 2100, + }, + { + .address = BQ27220DMAddressGasGaugingCEDVProfile1EMF, + .type = BQ27220DMTypeU16, + .value.u16 = 3679, + }, + { + .address = BQ27220DMAddressGasGaugingCEDVProfile1C0, + .type = BQ27220DMTypeU16, + .value.u16 = 430, + }, + { + .address = BQ27220DMAddressGasGaugingCEDVProfile1R0, + .type = BQ27220DMTypeU16, + .value.u16 = 334, + }, + { + .address = BQ27220DMAddressGasGaugingCEDVProfile1T0, + .type = BQ27220DMTypeU16, + .value.u16 = 4626, + }, + { + .address = BQ27220DMAddressGasGaugingCEDVProfile1R1, + .type = BQ27220DMTypeU16, + .value.u16 = 408, + }, + { + .address = BQ27220DMAddressGasGaugingCEDVProfile1TC, + .type = BQ27220DMTypeU8, + .value.u8 = 11, + }, + { + .address = BQ27220DMAddressGasGaugingCEDVProfile1C1, + .type = BQ27220DMTypeU8, + .value.u8 = 0, + }, + { + .address = BQ27220DMAddressGasGaugingCEDVProfile1StartDOD0, + .type = BQ27220DMTypeU16, + .value.u16 = 4044, + }, + { + .address = BQ27220DMAddressGasGaugingCEDVProfile1StartDOD10, + .type = BQ27220DMTypeU16, + .value.u16 = 3905, + }, + { + .address = BQ27220DMAddressGasGaugingCEDVProfile1StartDOD20, + .type = BQ27220DMTypeU16, + .value.u16 = 3807, + }, + { + .address = BQ27220DMAddressGasGaugingCEDVProfile1StartDOD30, + .type = BQ27220DMTypeU16, + .value.u16 = 3718, + }, + { + .address = BQ27220DMAddressGasGaugingCEDVProfile1StartDOD40, + .type = BQ27220DMTypeU16, + .value.u16 = 3642, + }, + { + .address = BQ27220DMAddressGasGaugingCEDVProfile1StartDOD50, + .type = BQ27220DMTypeU16, + .value.u16 = 3585, + }, + { + .address = BQ27220DMAddressGasGaugingCEDVProfile1StartDOD60, + .type = BQ27220DMTypeU16, + .value.u16 = 3546, + }, + { + .address = BQ27220DMAddressGasGaugingCEDVProfile1StartDOD70, + .type = BQ27220DMTypeU16, + .value.u16 = 3514, + }, + { + .address = BQ27220DMAddressGasGaugingCEDVProfile1StartDOD80, + .type = BQ27220DMTypeU16, + .value.u16 = 3477, + }, + { + .address = BQ27220DMAddressGasGaugingCEDVProfile1StartDOD90, + .type = BQ27220DMTypeU16, + .value.u16 = 3411, + }, + { + .address = BQ27220DMAddressGasGaugingCEDVProfile1StartDOD100, + .type = BQ27220DMTypeU16, + .value.u16 = 3299, + }, + { + .address = BQ27220DMAddressGasGaugingCEDVProfile1EDV0, + .type = BQ27220DMTypeU16, + .value.u16 = 3300, + }, + { + .address = BQ27220DMAddressGasGaugingCEDVProfile1EDV1, + .type = BQ27220DMTypeU16, + .value.u16 = 3321, + }, + { + .address = BQ27220DMAddressGasGaugingCEDVProfile1EDV2, + .type = BQ27220DMTypeU16, + .value.u16 = 3355, + }, + { + .address = BQ27220DMAddressCalibrationCurrentDeadband, + .type = BQ27220DMTypeU8, + .value.u8 = 1, + }, + { + .address = BQ27220DMAddressConfigurationPowerSleepCurrent, + .type = BQ27220DMTypeI16, + .value.i16 = 1, + }, + { + .type = BQ27220DMTypeEnd, + }, +}; diff --git a/firmware/targets/furi_hal_include/furi_hal_power.h b/firmware/targets/furi_hal_include/furi_hal_power.h index 6b4b6cd89b5..5edda6ba197 100644 --- a/firmware/targets/furi_hal_include/furi_hal_power.h +++ b/firmware/targets/furi_hal_include/furi_hal_power.h @@ -34,6 +34,12 @@ void furi_hal_power_init(); */ bool furi_hal_power_gauge_is_ok(); +/** Check if gauge requests system shutdown + * + * @return true if system shutdown requested + */ +bool furi_hal_power_is_shutdown_requested(); + /** Get current insomnia level * * @return insomnia level: 0 - no insomnia, >0 - insomnia, bearer count. diff --git a/lib/drivers/bq25896.c b/lib/drivers/bq25896.c index f675233bdbb..76aae5e8236 100644 --- a/lib/drivers/bq25896.c +++ b/lib/drivers/bq25896.c @@ -35,13 +35,15 @@ typedef struct { static bq25896_regs_t bq25896_regs; -void bq25896_init(FuriHalI2cBusHandle* handle) { +bool bq25896_init(FuriHalI2cBusHandle* handle) { + bool result = true; + bq25896_regs.r14.REG_RST = 1; - furi_hal_i2c_write_reg_8( + result &= furi_hal_i2c_write_reg_8( handle, BQ25896_ADDRESS, 0x14, *(uint8_t*)&bq25896_regs.r14, BQ25896_I2C_TIMEOUT); // Readout all registers - furi_hal_i2c_read_mem( + result &= furi_hal_i2c_read_mem( handle, BQ25896_ADDRESS, 0x00, @@ -52,26 +54,28 @@ void bq25896_init(FuriHalI2cBusHandle* handle) { // Poll ADC forever bq25896_regs.r02.CONV_START = 1; bq25896_regs.r02.CONV_RATE = 1; - furi_hal_i2c_write_reg_8( + result &= furi_hal_i2c_write_reg_8( handle, BQ25896_ADDRESS, 0x02, *(uint8_t*)&bq25896_regs.r02, BQ25896_I2C_TIMEOUT); bq25896_regs.r07.WATCHDOG = WatchdogDisable; - furi_hal_i2c_write_reg_8( + result &= furi_hal_i2c_write_reg_8( handle, BQ25896_ADDRESS, 0x07, *(uint8_t*)&bq25896_regs.r07, BQ25896_I2C_TIMEOUT); // OTG power configuration bq25896_regs.r0A.BOOSTV = 0x8; // BOOST Voltage: 5.062V bq25896_regs.r0A.BOOST_LIM = BoostLim_1400; // BOOST Current limit: 1.4A - furi_hal_i2c_write_reg_8( + result &= furi_hal_i2c_write_reg_8( handle, BQ25896_ADDRESS, 0x0A, *(uint8_t*)&bq25896_regs.r0A, BQ25896_I2C_TIMEOUT); - furi_hal_i2c_read_mem( + result &= furi_hal_i2c_read_mem( handle, BQ25896_ADDRESS, 0x00, (uint8_t*)&bq25896_regs, sizeof(bq25896_regs), BQ25896_I2C_TIMEOUT); + + return result; } void bq25896_set_boost_lim(FuriHalI2cBusHandle* handle, BoostLim boost_lim) { diff --git a/lib/drivers/bq25896.h b/lib/drivers/bq25896.h index ce7293960a1..d35625ab3f5 100644 --- a/lib/drivers/bq25896.h +++ b/lib/drivers/bq25896.h @@ -7,7 +7,7 @@ #include /** Initialize Driver */ -void bq25896_init(FuriHalI2cBusHandle* handle); +bool bq25896_init(FuriHalI2cBusHandle* handle); /** Set boost lim*/ void bq25896_set_boost_lim(FuriHalI2cBusHandle* handle, BoostLim boost_lim); diff --git a/lib/drivers/bq27220.c b/lib/drivers/bq27220.c index f64120fa83e..92dbfcd6a29 100644 --- a/lib/drivers/bq27220.c +++ b/lib/drivers/bq27220.c @@ -1,12 +1,16 @@ + #include "bq27220.h" #include "bq27220_reg.h" +#include "bq27220_data_memory.h" + +_Static_assert(sizeof(BQ27220DMGaugingConfig) == 2, "Incorrect structure size"); #include #include #define TAG "Gauge" -uint16_t bq27220_read_word(FuriHalI2cBusHandle* handle, uint8_t address) { +static uint16_t bq27220_read_word(FuriHalI2cBusHandle* handle, uint8_t address) { uint16_t buf = 0; furi_hal_i2c_read_mem( @@ -15,14 +19,14 @@ uint16_t bq27220_read_word(FuriHalI2cBusHandle* handle, uint8_t address) { return buf; } -bool bq27220_control(FuriHalI2cBusHandle* handle, uint16_t control) { +static bool bq27220_control(FuriHalI2cBusHandle* handle, uint16_t control) { bool ret = furi_hal_i2c_write_mem( handle, BQ27220_ADDRESS, CommandControl, (uint8_t*)&control, 2, BQ27220_I2C_TIMEOUT); return ret; } -uint8_t bq27220_get_checksum(uint8_t* data, uint16_t len) { +static uint8_t bq27220_get_checksum(uint8_t* data, uint16_t len) { uint8_t ret = 0; for(uint16_t i = 0; i < len; i++) { ret += data[i]; @@ -30,80 +34,181 @@ uint8_t bq27220_get_checksum(uint8_t* data, uint16_t len) { return 0xFF - ret; } -bool bq27220_set_parameter_u16(FuriHalI2cBusHandle* handle, uint16_t address, uint16_t value) { - bool ret; - uint8_t buffer[4]; +static bool bq27220_parameter_check( + FuriHalI2cBusHandle* handle, + uint16_t address, + uint32_t value, + size_t size, + bool update) { + furi_assert(size == 1 || size == 2 || size == 4); + bool ret = false; + uint8_t buffer[6] = {0}; + uint8_t old_data[4] = {0}; + + do { + buffer[0] = address & 0xFF; + buffer[1] = (address >> 8) & 0xFF; + + for(size_t i = 0; i < size; i++) { + buffer[1 + size - i] = (value >> (i * 8)) & 0xFF; + } - buffer[0] = address & 0xFF; - buffer[1] = (address >> 8) & 0xFF; - buffer[2] = (value >> 8) & 0xFF; - buffer[3] = value & 0xFF; - ret = furi_hal_i2c_write_mem( - handle, BQ27220_ADDRESS, CommandSelectSubclass, buffer, 4, BQ27220_I2C_TIMEOUT); + if(update) { + if(!furi_hal_i2c_write_mem( + handle, + BQ27220_ADDRESS, + CommandSelectSubclass, + buffer, + size + 2, + BQ27220_I2C_TIMEOUT)) { + FURI_LOG_I(TAG, "DM write failed"); + break; + } - furi_delay_us(10000); + furi_delay_us(10000); - uint8_t checksum = bq27220_get_checksum(buffer, 4); - buffer[0] = checksum; - buffer[1] = 6; - ret &= furi_hal_i2c_write_mem( - handle, BQ27220_ADDRESS, CommandMACDataSum, buffer, 2, BQ27220_I2C_TIMEOUT); + uint8_t checksum = bq27220_get_checksum(buffer, size + 2); + buffer[0] = checksum; + buffer[1] = 4 + size; // TODO: why 4? + if(!furi_hal_i2c_write_mem( + handle, BQ27220_ADDRESS, CommandMACDataSum, buffer, 2, BQ27220_I2C_TIMEOUT)) { + FURI_LOG_I(TAG, "CRC write failed"); + break; + } + + furi_delay_us(10000); + ret = true; + } else { + if(!furi_hal_i2c_write_mem( + handle, BQ27220_ADDRESS, CommandSelectSubclass, buffer, 2, BQ27220_I2C_TIMEOUT)) { + FURI_LOG_I(TAG, "DM SelectSubclass for read failed"); + break; + } + + if(!furi_hal_i2c_rx(handle, BQ27220_ADDRESS, old_data, size, BQ27220_I2C_TIMEOUT)) { + FURI_LOG_I(TAG, "DM read failed"); + break; + } + + if(*(uint32_t*)&(old_data[0]) != *(uint32_t*)&(buffer[2])) { + FURI_LOG_W( //-V641 + TAG, + "Data at 0x%04x(%zu): 0x%08lx!=0x%08lx", + address, + size, + *(uint32_t*)&(old_data[0]), + *(uint32_t*)&(buffer[2])); + } else { + ret = true; + } + } + } while(0); - furi_delay_us(10000); return ret; } -bool bq27220_init(FuriHalI2cBusHandle* handle, const ParamCEDV* cedv) { - uint32_t timeout = 100; - uint16_t design_cap = bq27220_get_design_capacity(handle); - if(cedv->design_cap == design_cap) { - FURI_LOG_I(TAG, "Skip battery profile update"); - return true; +static bool bq27220_data_memory_check( + FuriHalI2cBusHandle* handle, + const BQ27220DMData* data_memory, + bool update) { + if(update) { + if(!bq27220_control(handle, Control_ENTER_CFG_UPDATE)) { + FURI_LOG_E(TAG, "ENTER_CFG_UPDATE command failed"); + return false; + }; + + // Wait for enter CFG update mode + uint32_t timeout = 100; + OperationStatus status = {0}; + while((status.CFGUPDATE != true) && (timeout-- > 0)) { + bq27220_get_operation_status(handle, &status); + } + + if(timeout == 0) { + FURI_LOG_E(TAG, "CFGUPDATE mode failed"); + return false; + } } - FURI_LOG_I(TAG, "Start updating battery profile"); - OperationStatus status = {0}; - if(!bq27220_control(handle, Control_ENTER_CFG_UPDATE)) { - FURI_LOG_E(TAG, "Can't configure update"); - return false; - }; - while((status.CFGUPDATE != true) && (timeout-- > 0)) { - bq27220_get_operation_status(handle, &status); + // Process data memory records + bool result = true; + while(data_memory->type != BQ27220DMTypeEnd) { + if(data_memory->type == BQ27220DMTypeWait) { + furi_delay_us(data_memory->value.u32); + } else if(data_memory->type == BQ27220DMTypeU8) { + result &= bq27220_parameter_check( + handle, data_memory->address, data_memory->value.u8, 1, update); + } else if(data_memory->type == BQ27220DMTypeU16) { + result &= bq27220_parameter_check( + handle, data_memory->address, data_memory->value.u16, 2, update); + } else if(data_memory->type == BQ27220DMTypeU32) { + result &= bq27220_parameter_check( + handle, data_memory->address, data_memory->value.u32, 4, update); + } else if(data_memory->type == BQ27220DMTypeI8) { + result &= bq27220_parameter_check( + handle, data_memory->address, data_memory->value.i8, 1, update); + } else if(data_memory->type == BQ27220DMTypeI16) { + result &= bq27220_parameter_check( + handle, data_memory->address, data_memory->value.i16, 2, update); + } else if(data_memory->type == BQ27220DMTypeI32) { + result &= bq27220_parameter_check( + handle, data_memory->address, data_memory->value.i32, 4, update); + } else if(data_memory->type == BQ27220DMTypeF32) { + result &= bq27220_parameter_check( + handle, data_memory->address, data_memory->value.u32, 4, update); + } else if(data_memory->type == BQ27220DMTypePtr8) { + result &= bq27220_parameter_check( + handle, data_memory->address, *(uint8_t*)data_memory->value.u32, 1, update); + } else if(data_memory->type == BQ27220DMTypePtr16) { + result &= bq27220_parameter_check( + handle, data_memory->address, *(uint16_t*)data_memory->value.u32, 2, update); + } else if(data_memory->type == BQ27220DMTypePtr32) { + result &= bq27220_parameter_check( + handle, data_memory->address, *(uint32_t*)data_memory->value.u32, 4, update); + } else { + furi_crash("Invalid DM Type"); + } + data_memory++; } - bq27220_set_parameter_u16(handle, AddressGaugingConfig, cedv->cedv_conf.gauge_conf_raw); - bq27220_set_parameter_u16(handle, AddressFullChargeCapacity, cedv->full_charge_cap); - bq27220_set_parameter_u16(handle, AddressDesignCapacity, cedv->design_cap); - bq27220_set_parameter_u16(handle, AddressEMF, cedv->EMF); - bq27220_set_parameter_u16(handle, AddressC0, cedv->C0); - bq27220_set_parameter_u16(handle, AddressR0, cedv->R0); - bq27220_set_parameter_u16(handle, AddressT0, cedv->T0); - bq27220_set_parameter_u16(handle, AddressR1, cedv->R1); - bq27220_set_parameter_u16(handle, AddressTC, (cedv->TC) << 8 | cedv->C1); - bq27220_set_parameter_u16(handle, AddressStartDOD0, cedv->DOD0); - bq27220_set_parameter_u16(handle, AddressStartDOD10, cedv->DOD10); - bq27220_set_parameter_u16(handle, AddressStartDOD20, cedv->DOD20); - bq27220_set_parameter_u16(handle, AddressStartDOD30, cedv->DOD30); - bq27220_set_parameter_u16(handle, AddressStartDOD40, cedv->DOD40); - bq27220_set_parameter_u16(handle, AddressStartDOD50, cedv->DOD40); - bq27220_set_parameter_u16(handle, AddressStartDOD60, cedv->DOD60); - bq27220_set_parameter_u16(handle, AddressStartDOD70, cedv->DOD70); - bq27220_set_parameter_u16(handle, AddressStartDOD80, cedv->DOD80); - bq27220_set_parameter_u16(handle, AddressStartDOD90, cedv->DOD90); - bq27220_set_parameter_u16(handle, AddressStartDOD100, cedv->DOD100); - bq27220_set_parameter_u16(handle, AddressEDV0, cedv->EDV0); - bq27220_set_parameter_u16(handle, AddressEDV1, cedv->EDV1); - bq27220_set_parameter_u16(handle, AddressEDV2, cedv->EDV2); - - bq27220_control(handle, Control_EXIT_CFG_UPDATE_REINIT); - furi_delay_us(10000); - design_cap = bq27220_get_design_capacity(handle); - if(cedv->design_cap == design_cap) { - FURI_LOG_I(TAG, "Battery profile update success"); - return true; - } else { - FURI_LOG_E(TAG, "Battery profile update failed"); + + // Finalize configuration update + if(update) { + bq27220_control(handle, Control_EXIT_CFG_UPDATE_REINIT); + furi_delay_us(10000); + } + + return result; +} + +bool bq27220_init(FuriHalI2cBusHandle* handle) { + // Request device number(chip PN) + if(!bq27220_control(handle, Control_DEVICE_NUMBER)) { + FURI_LOG_E(TAG, "Device is not present"); + return false; + }; + // Check control response + uint16_t data = 0; + data = bq27220_read_word(handle, CommandControl); + if(data != 0xFF00) { + FURI_LOG_E(TAG, "Invalid control response: %x", data); return false; + }; + + data = bq27220_read_word(handle, CommandMACData); + FURI_LOG_I(TAG, "Device Number %04x", data); + + return data == 0x0220; +} + +bool bq27220_apply_data_memory(FuriHalI2cBusHandle* handle, const BQ27220DMData* data_memory) { + FURI_LOG_I(TAG, "Verifying data memory"); + if(!bq27220_data_memory_check(handle, data_memory, false)) { + FURI_LOG_I(TAG, "Updating data memory"); + bq27220_data_memory_check(handle, data_memory, true); } + FURI_LOG_I(TAG, "Data memory verification complete"); + + return true; } uint16_t bq27220_get_voltage(FuriHalI2cBusHandle* handle) { @@ -114,24 +219,23 @@ int16_t bq27220_get_current(FuriHalI2cBusHandle* handle) { return bq27220_read_word(handle, CommandCurrent); } -uint8_t bq27220_get_battery_status(FuriHalI2cBusHandle* handle, BatteryStatus* battery_status) { +bool bq27220_get_battery_status(FuriHalI2cBusHandle* handle, BatteryStatus* battery_status) { uint16_t data = bq27220_read_word(handle, CommandBatteryStatus); if(data == BQ27220_ERROR) { - return BQ27220_ERROR; + return false; } else { *(uint16_t*)battery_status = data; - return BQ27220_SUCCESS; + return true; } } -uint8_t - bq27220_get_operation_status(FuriHalI2cBusHandle* handle, OperationStatus* operation_status) { +bool bq27220_get_operation_status(FuriHalI2cBusHandle* handle, OperationStatus* operation_status) { uint16_t data = bq27220_read_word(handle, CommandOperationStatus); if(data == BQ27220_ERROR) { - return BQ27220_ERROR; + return false; } else { *(uint16_t*)operation_status = data; - return BQ27220_SUCCESS; + return true; } } diff --git a/lib/drivers/bq27220.h b/lib/drivers/bq27220.h index c822301a479..ca9e0312e87 100644 --- a/lib/drivers/bq27220.h +++ b/lib/drivers/bq27220.h @@ -47,60 +47,17 @@ typedef struct { _Static_assert(sizeof(OperationStatus) == 2, "Incorrect structure size"); -typedef struct { - // Low byte, Low bit first - bool CCT : 1; - bool CSYNC : 1; - bool RSVD0 : 1; - bool EDV_CMP : 1; - bool SC : 1; - bool FIXED_EDV0 : 1; - uint8_t RSVD1 : 2; - // High byte, Low bit first - bool FCC_LIM : 1; - bool RSVD2 : 1; - bool FC_FOR_VDQ : 1; - bool IGNORE_SD : 1; - bool SME0 : 1; - uint8_t RSVD3 : 3; -} GaugingConfig; - -_Static_assert(sizeof(GaugingConfig) == 2, "Incorrect structure size"); +typedef struct BQ27220DMData BQ27220DMData; -typedef struct { - union { - GaugingConfig gauge_conf; - uint16_t gauge_conf_raw; - } cedv_conf; - uint16_t full_charge_cap; - uint16_t design_cap; - uint16_t EDV0; - uint16_t EDV1; - uint16_t EDV2; - uint16_t EMF; - uint16_t C0; - uint16_t R0; - uint16_t T0; - uint16_t R1; - uint8_t TC; - uint8_t C1; - uint16_t DOD0; - uint16_t DOD10; - uint16_t DOD20; - uint16_t DOD30; - uint16_t DOD40; - uint16_t DOD50; - uint16_t DOD60; - uint16_t DOD70; - uint16_t DOD80; - uint16_t DOD90; - uint16_t DOD100; -} ParamCEDV; +/** Initialize Driver + * @return true on success, false otherwise + */ +bool bq27220_init(FuriHalI2cBusHandle* handle); /** Initialize Driver * @return true on success, false otherwise */ -bool bq27220_init(FuriHalI2cBusHandle* handle, const ParamCEDV* cedv); +bool bq27220_apply_data_memory(FuriHalI2cBusHandle* handle, const BQ27220DMData* data_memory); /** Get battery voltage in mV or error */ uint16_t bq27220_get_voltage(FuriHalI2cBusHandle* handle); @@ -109,11 +66,10 @@ uint16_t bq27220_get_voltage(FuriHalI2cBusHandle* handle); int16_t bq27220_get_current(FuriHalI2cBusHandle* handle); /** Get battery status */ -uint8_t bq27220_get_battery_status(FuriHalI2cBusHandle* handle, BatteryStatus* battery_status); +bool bq27220_get_battery_status(FuriHalI2cBusHandle* handle, BatteryStatus* battery_status); /** Get operation status */ -uint8_t - bq27220_get_operation_status(FuriHalI2cBusHandle* handle, OperationStatus* operation_status); +bool bq27220_get_operation_status(FuriHalI2cBusHandle* handle, OperationStatus* operation_status); /** Get temperature in units of 0.1°K */ uint16_t bq27220_get_temperature(FuriHalI2cBusHandle* handle); diff --git a/lib/drivers/bq27220_data_memory.h b/lib/drivers/bq27220_data_memory.h new file mode 100644 index 00000000000..ae00be88360 --- /dev/null +++ b/lib/drivers/bq27220_data_memory.h @@ -0,0 +1,84 @@ +#pragma once + +#include +#include + +typedef enum { + BQ27220DMTypeEnd, + BQ27220DMTypeWait, + BQ27220DMTypeU8, + BQ27220DMTypeU16, + BQ27220DMTypeU32, + BQ27220DMTypeI8, + BQ27220DMTypeI16, + BQ27220DMTypeI32, + BQ27220DMTypeF32, + BQ27220DMTypePtr8, + BQ27220DMTypePtr16, + BQ27220DMTypePtr32, +} BQ27220DMType; + +typedef enum { + BQ27220DMAddressGasGaugingCEDVProfile1GaugingConfig = 0x929B, + BQ27220DMAddressGasGaugingCEDVProfile1FullChargeCapacity = 0x929D, + BQ27220DMAddressGasGaugingCEDVProfile1DesignCapacity = 0x929F, + BQ27220DMAddressGasGaugingCEDVProfile1EMF = 0x92A3, + BQ27220DMAddressGasGaugingCEDVProfile1C0 = 0x92A9, + BQ27220DMAddressGasGaugingCEDVProfile1R0 = 0x92AB, + BQ27220DMAddressGasGaugingCEDVProfile1T0 = 0x92AD, + BQ27220DMAddressGasGaugingCEDVProfile1R1 = 0x92AF, + BQ27220DMAddressGasGaugingCEDVProfile1TC = 0x92B1, + BQ27220DMAddressGasGaugingCEDVProfile1C1 = 0x92B2, + BQ27220DMAddressGasGaugingCEDVProfile1EDV0 = 0x92B4, + BQ27220DMAddressGasGaugingCEDVProfile1EDV1 = 0x92B7, + BQ27220DMAddressGasGaugingCEDVProfile1EDV2 = 0x92BA, + BQ27220DMAddressGasGaugingCEDVProfile1StartDOD0 = 0x92BD, + BQ27220DMAddressGasGaugingCEDVProfile1StartDOD10 = 0x92BF, + BQ27220DMAddressGasGaugingCEDVProfile1StartDOD20 = 0x92C1, + BQ27220DMAddressGasGaugingCEDVProfile1StartDOD30 = 0x92C3, + BQ27220DMAddressGasGaugingCEDVProfile1StartDOD40 = 0x92C5, + BQ27220DMAddressGasGaugingCEDVProfile1StartDOD50 = 0x92C7, + BQ27220DMAddressGasGaugingCEDVProfile1StartDOD60 = 0x92C9, + BQ27220DMAddressGasGaugingCEDVProfile1StartDOD70 = 0x92CB, + BQ27220DMAddressGasGaugingCEDVProfile1StartDOD80 = 0x92CD, + BQ27220DMAddressGasGaugingCEDVProfile1StartDOD90 = 0x92CF, + BQ27220DMAddressGasGaugingCEDVProfile1StartDOD100 = 0x92D1, + BQ27220DMAddressCalibrationCurrentDeadband = 0x91DE, + BQ27220DMAddressConfigurationPowerSleepCurrent = 0x9217, + BQ27220DMAddressConfigurationCurrentThresholdsDischargeDetectionThreshold = 0x9228, + BQ27220DMAddressConfigurationDataInitialStandby = 0x923C, +} BQ27220DMAddress; + +typedef struct BQ27220DMData BQ27220DMData; + +struct BQ27220DMData { + uint16_t type; + uint16_t address; + union { + uint8_t u8; + uint16_t u16; + uint32_t u32; + int8_t i8; + int16_t i16; + int32_t i32; + float f32; + } value; +}; + +typedef struct { + // Low byte, Low bit first + const bool CCT : 1; + const bool CSYNC : 1; + const bool RSVD0 : 1; + const bool EDV_CMP : 1; + const bool SC : 1; + const bool FIXED_EDV0 : 1; + const uint8_t RSVD1 : 2; + // High byte, Low bit first + const bool FCC_LIM : 1; + const bool RSVD2 : 1; + const bool FC_FOR_VDQ : 1; + const bool IGNORE_SD : 1; + const bool SME0 : 1; + const uint8_t RSVD3 : 3; +} BQ27220DMGaugingConfig; diff --git a/lib/drivers/bq27220_reg.h b/lib/drivers/bq27220_reg.h index fa81b66eb4a..2e6e54aabef 100644 --- a/lib/drivers/bq27220_reg.h +++ b/lib/drivers/bq27220_reg.h @@ -66,28 +66,3 @@ #define Control_EXIT_CFG_UPDATE_REINIT 0x0091 #define Control_EXIT_CFG_UPDATE 0x0092 #define Control_RETURN_TO_ROM 0x0F00 - -#define AddressGaugingConfig 0x929B -#define AddressFullChargeCapacity 0x929D -#define AddressDesignCapacity 0x929F -#define AddressEMF 0x92A3 -#define AddressC0 0x92A9 -#define AddressR0 0x92AB -#define AddressT0 0x92AD -#define AddressR1 0x92AF -#define AddressTC 0x92B1 -#define AddressC1 0x92B2 -#define AddressEDV0 0x92B4 -#define AddressEDV1 0x92B7 -#define AddressEDV2 0x92BA -#define AddressStartDOD0 0x92BD -#define AddressStartDOD10 0x92BF -#define AddressStartDOD20 0x92C1 -#define AddressStartDOD30 0x92C3 -#define AddressStartDOD40 0x92C5 -#define AddressStartDOD50 0x92C7 -#define AddressStartDOD60 0x92C9 -#define AddressStartDOD70 0x92CB -#define AddressStartDOD80 0x92CD -#define AddressStartDOD90 0x92CF -#define AddressStartDOD100 0x92D1 From ff2e1acfdb480712b06f6c913424294d9a38e70d Mon Sep 17 00:00:00 2001 From: Max Andreev Date: Fri, 28 Jul 2023 22:45:27 +0300 Subject: [PATCH 672/824] Fix fbtenv restore (#2924) --- scripts/toolchain/fbtenv.sh | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/scripts/toolchain/fbtenv.sh b/scripts/toolchain/fbtenv.sh index 85d139040b1..a86b0ecedb2 100755 --- a/scripts/toolchain/fbtenv.sh +++ b/scripts/toolchain/fbtenv.sh @@ -38,11 +38,11 @@ fbtenv_wget() fbtenv_restore_env() { TOOLCHAIN_ARCH_DIR_SED="$(echo "$TOOLCHAIN_ARCH_DIR" | sed 's/\//\\\//g')" - PATH="$(echo "$PATH" | /usr/bin/sed "s/$TOOLCHAIN_ARCH_DIR_SED\/python\/bin://g")"; - PATH="$(echo "$PATH" | /usr/bin/sed "s/$TOOLCHAIN_ARCH_DIR_SED\/bin://g")"; - PATH="$(echo "$PATH" | /usr/bin/sed "s/$TOOLCHAIN_ARCH_DIR_SED\/protobuf\/bin://g")"; - PATH="$(echo "$PATH" | /usr/bin/sed "s/$TOOLCHAIN_ARCH_DIR_SED\/openocd\/bin://g")"; - PATH="$(echo "$PATH" | /usr/bin/sed "s/$TOOLCHAIN_ARCH_DIR_SED\/openssl\/bin://g")"; + PATH="$(echo "$PATH" | sed "s/$TOOLCHAIN_ARCH_DIR_SED\/python\/bin://g")"; + PATH="$(echo "$PATH" | sed "s/$TOOLCHAIN_ARCH_DIR_SED\/bin://g")"; + PATH="$(echo "$PATH" | sed "s/$TOOLCHAIN_ARCH_DIR_SED\/protobuf\/bin://g")"; + PATH="$(echo "$PATH" | sed "s/$TOOLCHAIN_ARCH_DIR_SED\/openocd\/bin://g")"; + PATH="$(echo "$PATH" | sed "s/$TOOLCHAIN_ARCH_DIR_SED\/openssl\/bin://g")"; if [ -n "${PS1:-""}" ]; then PS1="$(echo "$PS1" | sed 's/\[fbt\]//g')"; elif [ -n "${PROMPT:-""}" ]; then @@ -104,8 +104,6 @@ fbtenv_check_if_sourced_multiple_times() return 0; fi fi - echo "Warning! FBT environment script was sourced more than once!"; - echo "You might be doing things wrong, please open a new shell!"; return 1; } @@ -160,7 +158,7 @@ fbtenv_get_kernel_type() fbtenv_check_rosetta() { if [ "$ARCH_TYPE" = "arm64" ]; then - if ! /usr/bin/pgrep -q oahd; then + if ! pgrep -q oahd; then echo "Flipper Zero Toolchain needs Rosetta2 to run under Apple Silicon"; echo "Please instal it by typing 'softwareupdate --install-rosetta --agree-to-license'"; return 1; @@ -312,7 +310,9 @@ fbtenv_main() fbtenv_restore_env; return 0; fi - fbtenv_check_if_sourced_multiple_times; + if ! fbtenv_check_if_sourced_multiple_times; then + return 0; + fi; fbtenv_check_env_vars || return 1; fbtenv_check_download_toolchain || return 1; fbtenv_set_shell_prompt; From a2a4fa8cdac2a3c7753100ccaa2f7c9f8c28b9c8 Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Tue, 1 Aug 2023 10:56:11 +0300 Subject: [PATCH 673/824] [FL-3408, FL-3429, FL-3430] Backlight notification fix (#2878) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Notifications: display brightness override fix * FuriHalVersionColor update * float comparison fix Co-authored-by: あく --- .../services/notification/notification_app.c | 15 +++++++++++---- .../targets/furi_hal_include/furi_hal_version.h | 1 + scripts/otp.py | 1 + 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/applications/services/notification/notification_app.c b/applications/services/notification/notification_app.c index 2f947fe8a00..6fa48e7f4ec 100644 --- a/applications/services/notification/notification_app.c +++ b/applications/services/notification/notification_app.c @@ -5,7 +5,7 @@ #include #include #include - +#include #include "notification.h" #include "notification_messages.h" #include "notification_app.h" @@ -104,7 +104,10 @@ static void notification_reset_notification_led_layer(NotificationLedLayer* laye furi_hal_light_set(layer->light, layer->value[LayerInternal]); } -static void notification_reset_notification_layer(NotificationApp* app, uint8_t reset_mask) { +static void notification_reset_notification_layer( + NotificationApp* app, + uint8_t reset_mask, + float display_brightness_set) { if(reset_mask & reset_blink_mask) { furi_hal_light_blink_stop(); } @@ -124,6 +127,9 @@ static void notification_reset_notification_layer(NotificationApp* app, uint8_t notification_sound_off(); } if(reset_mask & reset_display_mask) { + if(!float_is_equal(display_brightness_set, app->settings.display_brightness)) { + furi_hal_light_set(LightBacklight, app->settings.display_brightness * 0xFF); + } furi_timer_start(app->display_timer, notification_settings_display_off_delay_ticks(app)); } } @@ -212,13 +218,14 @@ static void notification_process_notification_message( notification_apply_notification_led_layer( &app->display, notification_message->data.led.value * display_brightness_setting); + reset_mask |= reset_display_mask; } else { + reset_mask &= ~reset_display_mask; notification_reset_notification_led_layer(&app->display); if(furi_timer_is_running(app->display_timer)) { furi_timer_stop(app->display_timer); } } - reset_mask |= reset_display_mask; break; case NotificationMessageTypeLedDisplayBacklightEnforceOn: furi_assert(app->display_led_lock < UINT8_MAX); @@ -370,7 +377,7 @@ static void notification_process_notification_message( } if(reset_notifications) { - notification_reset_notification_layer(app, reset_mask); + notification_reset_notification_layer(app, reset_mask, display_brightness_setting); } } diff --git a/firmware/targets/furi_hal_include/furi_hal_version.h b/firmware/targets/furi_hal_include/furi_hal_version.h index a9339a6c0ee..98d011cb389 100644 --- a/firmware/targets/furi_hal_include/furi_hal_version.h +++ b/firmware/targets/furi_hal_include/furi_hal_version.h @@ -33,6 +33,7 @@ typedef enum { FuriHalVersionColorUnknown = 0x00, FuriHalVersionColorBlack = 0x01, FuriHalVersionColorWhite = 0x02, + FuriHalVersionColorTransparent = 0x03, } FuriHalVersionColor; /** Device Regions */ diff --git a/scripts/otp.py b/scripts/otp.py index 19b8c4df42e..e3f070999a7 100755 --- a/scripts/otp.py +++ b/scripts/otp.py @@ -17,6 +17,7 @@ "unknown": 0x00, "black": 0x01, "white": 0x02, + "transparent": 0x03, } OTP_REGIONS = { From 68eb1ecebb463b148b21358dd2ba89b1bfc29e41 Mon Sep 17 00:00:00 2001 From: Dzhos Oleksii <35292229+Programistich@users.noreply.github.com> Date: Tue, 1 Aug 2023 11:20:30 +0300 Subject: [PATCH 674/824] [FL-3385] New RTC flags in device info (#2884) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add new rtc flags to device info * FuriHal: bump info version * Fix local * Rework device info by hierarchy filtering Co-authored-by: あく --- firmware/targets/f7/furi_hal/furi_hal_info.c | 49 +++++++++++++++++++- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/firmware/targets/f7/furi_hal/furi_hal_info.c b/firmware/targets/f7/furi_hal/furi_hal_info.c index a2c9232c058..cefb6a11bbf 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_info.c +++ b/firmware/targets/f7/furi_hal/furi_hal_info.c @@ -24,10 +24,10 @@ void furi_hal_info_get(PropertyValueCallback out, char sep, void* context) { // Device Info version if(sep == '.') { property_value_out(&property_context, NULL, 2, "format", "major", "3"); - property_value_out(&property_context, NULL, 2, "format", "minor", "2"); + property_value_out(&property_context, NULL, 2, "format", "minor", "3"); } else { property_value_out(&property_context, NULL, 3, "device", "info", "major", "2"); - property_value_out(&property_context, NULL, 3, "device", "info", "minor", "3"); + property_value_out(&property_context, NULL, 3, "device", "info", "minor", "4"); } // Model name @@ -298,6 +298,7 @@ void furi_hal_info_get(PropertyValueCallback out, char sep, void* context) { property_value_out(&property_context, NULL, 2, "radio", "alive", "false"); } + // RTC flags property_value_out( &property_context, "%u", @@ -305,8 +306,52 @@ void furi_hal_info_get(PropertyValueCallback out, char sep, void* context) { "system", "debug", furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)); + property_value_out( + &property_context, "%u", 2, "system", "lock", furi_hal_rtc_is_flag_set(FuriHalRtcFlagLock)); + property_value_out( + &property_context, + "%u", + 2, + "system", + "orient", + furi_hal_rtc_is_flag_set(FuriHalRtcFlagHandOrient)); + property_value_out( + &property_context, + "%u", + 3, + "system", + "sleep", + "legacy", + furi_hal_rtc_is_flag_set(FuriHalRtcFlagLegacySleep)); + property_value_out( + &property_context, + "%u", + 2, + "system", + "stealth", + furi_hal_rtc_is_flag_set(FuriHalRtcFlagStealthMode)); + property_value_out( &property_context, "%u", 3, "system", "heap", "track", furi_hal_rtc_get_heap_track_mode()); + property_value_out(&property_context, "%u", 2, "system", "boot", furi_hal_rtc_get_boot_mode()); + property_value_out( + &property_context, + "%u", + 3, + "system", + "locale", + "time", + furi_hal_rtc_get_locale_timeformat()); + property_value_out( + &property_context, + "%u", + 3, + "system", + "locale", + "date", + furi_hal_rtc_get_locale_dateformat()); + property_value_out( + &property_context, "%u", 3, "system", "locale", "unit", furi_hal_rtc_get_locale_units()); property_value_out( &property_context, "%u", 3, "system", "log", "level", furi_hal_rtc_get_log_level()); From aadb72af53b2b9d95c6b52ce200df1c7112d0d94 Mon Sep 17 00:00:00 2001 From: MMX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 1 Aug 2023 12:09:30 +0300 Subject: [PATCH 675/824] UI: New way to input bytes in byte_input (#2890) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * New byte input UI option * Gui: reformat and cleanup byte_input docs, make PVS happy Co-authored-by: hedger Co-authored-by: あく --- .../services/gui/modules/byte_input.c | 442 ++++++++++-------- 1 file changed, 251 insertions(+), 191 deletions(-) diff --git a/applications/services/gui/modules/byte_input.c b/applications/services/gui/modules/byte_input.c index b2d21f7ae39..3bca8fc744d 100644 --- a/applications/services/gui/modules/byte_input.c +++ b/applications/services/gui/modules/byte_input.c @@ -4,6 +4,7 @@ #include #include +/** ByteInput type */ struct ByteInput { View* view; }; @@ -25,7 +26,7 @@ typedef struct { bool selected_high_nibble; uint8_t selected_byte; - int8_t selected_row; // row -1 - input, row 0 & 1 - keyboard + int8_t selected_row; // row -2 - mini_editor, -1 - input, row 0 & 1 - keyboard uint8_t selected_column; uint8_t first_visible_byte; } ByteInputModel; @@ -61,11 +62,11 @@ static const ByteInputKey keyboard_keys_row_2[] = { {enter_symbol, 95, 17}, }; -/** - * @brief Get row size - * - * @param row_index Index of row - * @return uint8_t Row size +/** Get row size + * + * @param row_index Index of row + * + * @return uint8_t Row size */ static uint8_t byte_input_get_row_size(uint8_t row_index) { uint8_t row_size = 0; @@ -84,11 +85,11 @@ static uint8_t byte_input_get_row_size(uint8_t row_index) { return row_size; } -/** - * @brief Get row pointer - * - * @param row_index Index of row - * @return const ByteInputKey* Row pointer +/** Get row pointer + * + * @param row_index Index of row + * + * @return const ByteInputKey* Row pointer */ static const ByteInputKey* byte_input_get_row(uint8_t row_index) { const ByteInputKey* row = NULL; @@ -107,12 +108,12 @@ static const ByteInputKey* byte_input_get_row(uint8_t row_index) { return row; } -/** - * @brief Get text from nibble - * - * @param byte byte value - * @param high_nibble Get from high nibble, otherwise low nibble - * @return char nibble text +/** Get text from nibble + * + * @param byte byte value + * @param high_nibble Get from high nibble, otherwise low nibble + * + * @return char nibble text */ static char byte_input_get_nibble_text(uint8_t byte, bool high_nibble) { if(high_nibble) { @@ -149,15 +150,20 @@ static char byte_input_get_nibble_text(uint8_t byte, bool high_nibble) { return byte; } -/** - * @brief Draw input box (common view) - * - * @param canvas - * @param model +const char num_to_char[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}; + +/** Draw input box (common view) + * + * @param canvas The canvas + * @param model The model */ static void byte_input_draw_input(Canvas* canvas, ByteInputModel* model) { const uint8_t text_x = 8; const uint8_t text_y = 25; + const uint8_t text_y2 = 40; + const bool draw_index_line = + (model->selected_row == -2) && + (model->first_visible_byte + MIN(model->bytes_count, max_drawable_bytes + 1) <= 100); elements_slightly_rounded_frame(canvas, 6, 14, 116, 15); @@ -225,6 +231,27 @@ static void byte_input_draw_input(Canvas* canvas, ByteInputModel* model) { text_y, byte_input_get_nibble_text(model->bytes[i], false)); } + + if(draw_index_line) { + canvas_draw_glyph( + canvas, text_x + 2 + byte_position * 14, text_y2, num_to_char[(i + 1) / 10]); + + canvas_draw_glyph( + canvas, text_x + 8 + byte_position * 14, text_y2, num_to_char[(i + 1) % 10]); + } + } + + if((model->selected_row == -2) && + (model->first_visible_byte + MIN(model->bytes_count, max_drawable_bytes + 1) > 100)) { + char str[20]; + + canvas_set_font(canvas, FontSecondary); + snprintf(str, 20, "Selected index"); + canvas_draw_str(canvas, text_x, text_y2, str); + + canvas_set_font(canvas, FontPrimary); + snprintf(str, 20, "%u", (model->selected_byte + 1)); + canvas_draw_str(canvas, text_x + 75, text_y2, str); } if(model->bytes_count - model->first_visible_byte > max_drawable_bytes) { @@ -236,11 +263,10 @@ static void byte_input_draw_input(Canvas* canvas, ByteInputModel* model) { } } -/** - * @brief Draw input box (selected view) - * - * @param canvas - * @param model +/** Draw input box (selected view) + * + * @param canvas The canvas + * @param model The model */ static void byte_input_draw_input_selected(Canvas* canvas, ByteInputModel* model) { const uint8_t text_x = 7; @@ -297,13 +323,12 @@ static void byte_input_draw_input_selected(Canvas* canvas, ByteInputModel* model canvas_invert_color(canvas); } -/** - * @brief Set nibble at position - * - * @param data where to set nibble - * @param position byte position - * @param value char value - * @param high_nibble set high nibble +/** Set nibble at position + * + * @param data where to set nibble + * @param position byte position + * @param value char value + * @param high_nibble set high nibble */ static void byte_input_set_nibble(uint8_t* data, uint8_t position, char value, bool high_nibble) { switch(value) { @@ -341,29 +366,28 @@ static void byte_input_set_nibble(uint8_t* data, uint8_t position, char value, b } } -/** - * @brief What currently selected - * - * @return true - keyboard selected, false - input selected +/** What currently selected + * + * @param model The model + * + * @return true - keyboard selected, false - input selected */ static bool byte_input_keyboard_selected(ByteInputModel* model) { return model->selected_row >= 0; } -/** - * @brief Do transition from keyboard - * - * @param model +/** Do transition from keyboard + * + * @param model The model */ static void byte_input_transition_from_keyboard(ByteInputModel* model) { model->selected_row += 1; model->selected_high_nibble = true; } -/** - * @brief Increase selected byte position - * - * @param model +/** Increase selected byte position + * + * @param model The model */ static void byte_input_inc_selected_byte(ByteInputModel* model) { if(model->selected_byte < model->bytes_count - 1) { @@ -379,10 +403,20 @@ static void byte_input_inc_selected_byte(ByteInputModel* model) { } } -/** - * @brief Decrease selected byte position - * - * @param model +static void byte_input_inc_selected_byte_mini(ByteInputModel* model) { + if((model->selected_byte < model->bytes_count - 1) || model->selected_high_nibble) { + if(!model->selected_high_nibble) { + model->selected_high_nibble = !model->selected_high_nibble; //-V547 + byte_input_inc_selected_byte(model); + } else { + model->selected_high_nibble = !model->selected_high_nibble; //-V547 + } + } +} + +/** Decrease selected byte position + * + * @param model The model */ static void byte_input_dec_selected_byte(ByteInputModel* model) { if(model->selected_byte > 0) { @@ -397,10 +431,20 @@ static void byte_input_dec_selected_byte(ByteInputModel* model) { } } -/** - * @brief Call input callback - * - * @param model +static void byte_input_dec_selected_byte_mini(ByteInputModel* model) { + if(model->selected_byte > 0 || !model->selected_high_nibble) { + if(model->selected_high_nibble) { + model->selected_high_nibble = !model->selected_high_nibble; //-V547 + byte_input_dec_selected_byte(model); + } else { + model->selected_high_nibble = !model->selected_high_nibble; //-V547 + } + } +} + +/** Call input callback + * + * @param model The model */ static void byte_input_call_input_callback(ByteInputModel* model) { if(model->input_callback != NULL) { @@ -408,10 +452,9 @@ static void byte_input_call_input_callback(ByteInputModel* model) { } } -/** - * @brief Call changed callback - * - * @param model +/** Call changed callback + * + * @param model The model */ static void byte_input_call_changed_callback(ByteInputModel* model) { if(model->changed_callback != NULL) { @@ -419,8 +462,9 @@ static void byte_input_call_changed_callback(ByteInputModel* model) { } } -/** - * @brief Clear selected byte +/** Clear selected byte + * + * @param model The model */ static void byte_input_clear_selected_byte(ByteInputModel* model) { @@ -430,36 +474,55 @@ static void byte_input_clear_selected_byte(ByteInputModel* model) { byte_input_call_changed_callback(model); } -/** - * @brief Handle up button - * - * @param model +/** Handle up button + * + * @param model The model */ static void byte_input_handle_up(ByteInputModel* model) { - if(model->selected_row > -1) { + if(model->selected_row > -2) { model->selected_row -= 1; + } else if(model->selected_row == -2) { + if(!model->selected_high_nibble) { + model->bytes[model->selected_byte] = (model->bytes[model->selected_byte] & 0xF0) | + ((model->bytes[model->selected_byte] + 1) & 0x0F); + } else { + model->bytes[model->selected_byte] = + ((model->bytes[model->selected_byte] + 0x10) & 0xF0) | + (model->bytes[model->selected_byte] & 0x0F); + } + byte_input_call_changed_callback(model); } } -/** - * @brief Handle down button - * - * @param model +/** Handle down button + * + * @param model The model */ static void byte_input_handle_down(ByteInputModel* model) { - if(byte_input_keyboard_selected(model)) { - if(model->selected_row < keyboard_row_count - 1) { - model->selected_row += 1; + if(model->selected_row != -2) { + if(byte_input_keyboard_selected(model)) { + if(model->selected_row < keyboard_row_count - 1) { + model->selected_row += 1; + } + } else { + byte_input_transition_from_keyboard(model); } } else { - byte_input_transition_from_keyboard(model); + if(!model->selected_high_nibble) { + model->bytes[model->selected_byte] = (model->bytes[model->selected_byte] & 0xF0) | + ((model->bytes[model->selected_byte] - 1) & 0x0F); + } else { + model->bytes[model->selected_byte] = + ((model->bytes[model->selected_byte] - 0x10) & 0xF0) | + (model->bytes[model->selected_byte] & 0x0F); + } + byte_input_call_changed_callback(model); } } -/** - * @brief Handle left button - * - * @param model +/** Handle left button + * + * @param model The model */ static void byte_input_handle_left(ByteInputModel* model) { if(byte_input_keyboard_selected(model)) { @@ -469,14 +532,17 @@ static void byte_input_handle_left(ByteInputModel* model) { model->selected_column = byte_input_get_row_size(model->selected_row) - 1; } } else { - byte_input_dec_selected_byte(model); + if(model->selected_row != -2) { + byte_input_dec_selected_byte(model); + } else { + byte_input_dec_selected_byte_mini(model); + } } } -/** - * @brief Handle right button - * - * @param model +/** Handle right button + * + * @param model The model */ static void byte_input_handle_right(ByteInputModel* model) { if(byte_input_keyboard_selected(model)) { @@ -486,14 +552,17 @@ static void byte_input_handle_right(ByteInputModel* model) { model->selected_column = 0; } } else { - byte_input_inc_selected_byte(model); + if(model->selected_row != -2) { + byte_input_inc_selected_byte(model); + } else { + byte_input_inc_selected_byte_mini(model); + } } } -/** - * @brief Handle OK button - * - * @param model +/** Handle OK button + * + * @param model The model */ static void byte_input_handle_ok(ByteInputModel* model) { if(byte_input_keyboard_selected(model)) { @@ -514,16 +583,17 @@ static void byte_input_handle_ok(ByteInputModel* model) { } byte_input_call_changed_callback(model); } + } else if(model->selected_row == -2) { + byte_input_call_input_callback(model); } else { byte_input_transition_from_keyboard(model); } } -/** - * @brief Draw callback - * - * @param canvas - * @param _model +/** Draw callback + * + * @param canvas The canvas + * @param _model The model */ static void byte_input_view_draw_callback(Canvas* canvas, void* _model) { ByteInputModel* model = _model; @@ -541,80 +611,89 @@ static void byte_input_view_draw_callback(Canvas* canvas, void* _model) { byte_input_draw_input(canvas, model); } - for(uint8_t row = 0; row < keyboard_row_count; row++) { - const uint8_t column_count = byte_input_get_row_size(row); - const ByteInputKey* keys = byte_input_get_row(row); + if(model->selected_row == -2) { + canvas_set_font(canvas, FontSecondary); + canvas_draw_icon(canvas, 3, 52, &I_Pin_back_arrow_10x8); + canvas_draw_str_aligned(canvas, 16, 60, AlignLeft, AlignBottom, "back to keyboard"); + } else { + // Draw keyboard + for(uint8_t row = 0; row < keyboard_row_count; row++) { + const uint8_t column_count = byte_input_get_row_size(row); + const ByteInputKey* keys = byte_input_get_row(row); - for(size_t column = 0; column < column_count; column++) { - if(keys[column].value == enter_symbol) { - canvas_set_color(canvas, ColorBlack); - if(model->selected_row == row && model->selected_column == column) { - canvas_draw_icon( - canvas, - keyboard_origin_x + keys[column].x, - keyboard_origin_y + keys[column].y, - &I_KeySaveSelected_24x11); - } else { - canvas_draw_icon( - canvas, - keyboard_origin_x + keys[column].x, - keyboard_origin_y + keys[column].y, - &I_KeySave_24x11); - } - } else if(keys[column].value == backspace_symbol) { - canvas_set_color(canvas, ColorBlack); - if(model->selected_row == row && model->selected_column == column) { - canvas_draw_icon( - canvas, - keyboard_origin_x + keys[column].x, - keyboard_origin_y + keys[column].y, - &I_KeyBackspaceSelected_16x9); + for(size_t column = 0; column < column_count; column++) { + if(keys[column].value == enter_symbol) { + canvas_set_color(canvas, ColorBlack); + if(model->selected_row == row && model->selected_column == column) { + canvas_draw_icon( + canvas, + keyboard_origin_x + keys[column].x, + keyboard_origin_y + keys[column].y, + &I_KeySaveSelected_24x11); + } else { + canvas_draw_icon( + canvas, + keyboard_origin_x + keys[column].x, + keyboard_origin_y + keys[column].y, + &I_KeySave_24x11); + } + } else if(keys[column].value == backspace_symbol) { + canvas_set_color(canvas, ColorBlack); + if(model->selected_row == row && model->selected_column == column) { + canvas_draw_icon( + canvas, + keyboard_origin_x + keys[column].x, + keyboard_origin_y + keys[column].y, + &I_KeyBackspaceSelected_16x9); + } else { + canvas_draw_icon( + canvas, + keyboard_origin_x + keys[column].x, + keyboard_origin_y + keys[column].y, + &I_KeyBackspace_16x9); + } } else { - canvas_draw_icon( + if(model->selected_row == row && model->selected_column == column) { + canvas_set_color(canvas, ColorBlack); + canvas_draw_box( + canvas, + keyboard_origin_x + keys[column].x - 3, + keyboard_origin_y + keys[column].y - 10, + 11, + 13); + canvas_set_color(canvas, ColorWhite); + } else if( + model->selected_row == -1 && row == 0 && + model->selected_column == column) { + canvas_set_color(canvas, ColorBlack); + canvas_draw_frame( + canvas, + keyboard_origin_x + keys[column].x - 3, + keyboard_origin_y + keys[column].y - 10, + 11, + 13); + } else { + canvas_set_color(canvas, ColorBlack); + } + + canvas_draw_glyph( canvas, keyboard_origin_x + keys[column].x, keyboard_origin_y + keys[column].y, - &I_KeyBackspace_16x9); - } - } else { - if(model->selected_row == row && model->selected_column == column) { - canvas_set_color(canvas, ColorBlack); - canvas_draw_box( - canvas, - keyboard_origin_x + keys[column].x - 3, - keyboard_origin_y + keys[column].y - 10, - 11, - 13); - canvas_set_color(canvas, ColorWhite); - } else if(model->selected_row == -1 && row == 0 && model->selected_column == column) { - canvas_set_color(canvas, ColorBlack); - canvas_draw_frame( - canvas, - keyboard_origin_x + keys[column].x - 3, - keyboard_origin_y + keys[column].y - 10, - 11, - 13); - } else { - canvas_set_color(canvas, ColorBlack); + keys[column].value); } - - canvas_draw_glyph( - canvas, - keyboard_origin_x + keys[column].x, - keyboard_origin_y + keys[column].y, - keys[column].value); } } } } -/** - * @brief Input callback - * - * @param event - * @param context - * @return true - * @return false +/** Input callback + * + * @param event The event + * @param context The context + * + * @return true + * @return false */ static bool byte_input_view_input_callback(InputEvent* event, void* context) { ByteInput* byte_input = context; @@ -656,6 +735,20 @@ static bool byte_input_view_input_callback(InputEvent* event, void* context) { } } + if(event->type == InputTypeShort && event->key == InputKeyBack) { + // Back to keyboard + with_view_model( + byte_input->view, + ByteInputModel * model, + { + if(model->selected_row == -2) { + model->selected_row += 1; + consumed = true; + }; + }, + true); + } + if((event->type == InputTypeLong || event->type == InputTypeRepeat) && event->key == InputKeyBack) { with_view_model( @@ -669,10 +762,9 @@ static bool byte_input_view_input_callback(InputEvent* event, void* context) { return consumed; } -/** - * @brief Reset all input-related data in model - * - * @param model ByteInputModel +/** Reset all input-related data in model + * + * @param model The model */ static void byte_input_reset_model_input_data(ByteInputModel* model) { model->bytes = NULL; @@ -684,11 +776,6 @@ static void byte_input_reset_model_input_data(ByteInputModel* model) { model->first_visible_byte = 0; } -/** - * @brief Allocate and initialize byte input. This byte input is used to enter bytes. - * - * @return ByteInput instance pointer - */ ByteInput* byte_input_alloc() { ByteInput* byte_input = malloc(sizeof(ByteInput)); byte_input->view = view_alloc(); @@ -712,38 +799,17 @@ ByteInput* byte_input_alloc() { return byte_input; } -/** - * @brief Deinitialize and free byte input - * - * @param byte_input Byte input instance - */ void byte_input_free(ByteInput* byte_input) { furi_assert(byte_input); view_free(byte_input->view); free(byte_input); } -/** - * @brief Get byte input view - * - * @param byte_input byte input instance - * @return View instance that can be used for embedding - */ View* byte_input_get_view(ByteInput* byte_input) { furi_assert(byte_input); return byte_input->view; } -/** - * @brief Deinitialize and free byte input - * - * @param byte_input byte input instance - * @param input_callback input callback fn - * @param changed_callback changed callback fn - * @param callback_context callback context - * @param bytes buffer to use - * @param bytes_count buffer length - */ void byte_input_set_result_callback( ByteInput* byte_input, ByteInputCallback input_callback, @@ -765,12 +831,6 @@ void byte_input_set_result_callback( true); } -/** - * @brief Set byte input header text - * - * @param byte_input byte input instance - * @param text text to be shown - */ void byte_input_set_header_text(ByteInput* byte_input, const char* text) { with_view_model( byte_input->view, ByteInputModel * model, { model->header = text; }, true); From fe7a1c2fccc96a1dc0589d5f01db8e22ffd9c122 Mon Sep 17 00:00:00 2001 From: MMX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 1 Aug 2023 12:32:24 +0300 Subject: [PATCH 676/824] SubGHz - Keeloq: Read Centurion Nova remotes (#2892) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: hedger Co-authored-by: あく --- assets/resources/subghz/assets/keeloq_mfcodes | 107 +++++++++--------- lib/subghz/protocols/keeloq.c | 26 ++++- 2 files changed, 77 insertions(+), 56 deletions(-) diff --git a/assets/resources/subghz/assets/keeloq_mfcodes b/assets/resources/subghz/assets/keeloq_mfcodes index 3eedc564f27..f2e45b491b3 100644 --- a/assets/resources/subghz/assets/keeloq_mfcodes +++ b/assets/resources/subghz/assets/keeloq_mfcodes @@ -1,56 +1,57 @@ Filetype: Flipper SubGhz Keystore File Version: 0 Encryption: 1 -IV: 41 84 34 43 84 1D 43 04 45 44 34 38 41 24 3E 74 -8C5AEF725F0620DB3952B40BB8E76A815BCEE1D1B92F7E8E8E63D1894F1C7FD0 -1DFF1D6A322D6B3D8AD7C594A02462AADE723D417B9233585526982F08187DAA -0A9184F15D4A5589DDDA6422063BACD58580661CFE60EE600D87F73F0CB5013E -6E56802DAA049C3DFDEDC90432A0E694A172C369EBECD136F4C911B979AA098D -A659716B51053604059F7FC3651D6A153F5EAB1852F95B20C44C41A7889A0DE91A078B63E3C311280C4315F0A3C8BA1F -A315170EDC51627157725D9A96490DB75EBF8232957FBA313C03B2BA2884EA85 -DEAB3C2C2E2DC76FE45AEBAC7EBFB478CECCD970A63B8DE2024FBFDCCBD1B26E -7BBFC36CBA77468B4624C6B685610877D53985C985DAD8EFE47527EB7C7260CD -879EE18B314ED4F3F548B41176099176FB97F4F1A062481C935B2DDFBCE2FE4D -493372D7D47A96A66305DFDC8A915EB651620881AE1D603B7E9605E004C04CA9 -F80AAA4C447F8E8C0B039DDAECF9126119C32FF164118780BE268E326A8CBF8010DE2EBF94033CEAC39815D6A8958CF4 -41C1393A039E665F6A53A5E5D1C105008BD14D9755751545A667860C39B2E39AA47306E76E2BA7DDDAA2A286FDB58D23 -34853A4CDE42CB80045E641AB4800C86C1CF6907EAAFA399156CCC727A008584 -D0783A34BD6A36D31BFF5F70FA1116CAE48EF02716D80481AE406DABB3C3400E -0BB3582605434CF2A5D74A27773B88DA331B6033C444837E9F7A155897258B03 -E4E71F3EB290B9436FFF0FDADA468BE37D964E89BE8D9971A14776F821822769 -744AA59D129C892120B5DAB81C8A3D573B0AD80EF0708C1B9ECF13DA60CECA07DC0591A08611DB4D3A8B7C70994D5DEF -716F9F8D5D2C697BC4183EFCC97E52D08ECA07B613A0F389C801F65803DFF4A4 -560262DA8489D2C18C8D97E314DC663403AFE4DE9DCB6D453087D2BFBD36532D -9E31F7152C50B6940EE3E3894C98F75702C7897F702B48E5C9B54B6E25083641AD2E521267505066C7E5BAB7F6CF1433 -6630EDA18A6E58BD395792CCC656DD10CD9C5DD2B1949FE677122FA39A53C724E79C0D0752A3A39A03407BBA2282185E -00D15A06F5DD82A4B926B78809CC4D129AAFA9A92B0A81568F255D15697FE0FD -29FF9A4F5346ABEE8FEDE034988F87FCD29EA747735898F1E7207EF74FAB71A8 -C0E8EB6AE6F77EE38DF2AB1B7742E34ED5236F3D8E964845E66762A4675AA21F -00FC4C459DC4CE92B62D0AC2546F9FBBE0893F84D2AF0A20ED462A5EAE63DE3B -E92EF482A40CEEFC8339BBB713BBC452A266A09B2645EDEB12716544B2DB9B09 -D7D9C5C757831BCE2FF1DB25A080D77769FB36A1F3F48F4361418A0A45609280 -C19246F52AE1EE5CE968CED52F642D9CD78B020029632FE83C49C657D23ED075 -FEE3C05432FB3860D5D28562323F5D1B053B8F3ADCD416BD0C4645F6F4D43DCF -D780A4AADD0205E0BACDCC9AF46ED259E0946C5DA888C341BFE96E09A87CCCFA -CE3C13CFA08E532B637FDB707E29548D57EE92EAEF6516C3D67E9D36FCD59CF9 -5E88CE71258CB0D91631FEB41C9A2F47AE0FF4810A9A1EDF3F308BBDE6944D5E -1531F4107FC64810BA5DB5E46C7B9AD61531AF5430E137B7688109FBC06B6221 -68050A39C0B302E0B713FAAC5F829C79AB30E18B1D982A94005DBAC7CCFB95379A619C0B9F7409C44D19FF2C5E8E4546 -3F73E8BA22C602280496EF8E88E2CAA9EC442E3B3083B684942DBF9CB5121241 -FA1FCD7C9182FAE8FFF4E88433AE68F66076B3BDFF8AD0BF5CEA43870082E9BE -DFF7DD2678C03401656B093BF7AC7E033F15FD0F30188E48A62045740B423699 -371BCFF653E7811D99C048A1A39921AAA563E06AC86CB3D2F392C2C955A1ABD0 -F4F1766DEAEDE934478208B9EB3050326D9FFCC001C73EEE93407D8B12CD49E4 -A241C9FC62DDF67D645936245FAFFE2A42C86151F484B7BCE5410E8F36FC87901D3AC4E40334E08FFFC2AD676E490D94 -3566A94A9C0479E0C4387D9137375ADF2C921504364F3903F198D6757CDFD21B -7274E1B5A6445FDC29C355D550E981C17F349BC4A14251B3B51BC96FC334FBCA -04EEA5EDD9B3BC3E0638E53A5561DC8BF761D615A64D435BD31A94AF2650159E -B84818CC1695FE8B731CD653D0679D1AAA0578C0B06AD1E3510785B2DE20841C -4121343D6B79E38C06DD038D770D76D10336AFF47ED0D0DCDDD6B0FEA4DAE67C -75E49C839CCD7019D9CE90AC364F488468B2AB01E387A8BEF8815915925166A6 -CFAA9F4717568C1EC7B96E0D71D260B828A70484E1D9CA7C99A50D10704F8BBBAE62EE98C9FBDFF06F357F1C1E2F2677 -41E4D250B92BC57442B91DE2015C41226531CF9A8D77B83AFC8E4F3183DB11DE -45EA8BD854D7F044FB249C16F08A0C24FF117D54BC20A4CC667B3DAD09EAC4F9 -F455CA0BB8B496C301406DE4FB52C9B0F64645776803BC2935A2F38675318BE2 -22FF72A5D2E1A2EBFB6C55FFD0A3CEA0474CCBD13462D63229C9708276E87D3F -8470F9A300170F226C0216C07AA829591CBD4CE34AA918EAE49363BDE86CC77EEEBEEA84A097488D35B92F773F5DBB4C +IV: 23 17 32 54 92 3A 28 13 12 DE 8B A7 24 71 25 74 +B8ACA082EA4B34E8B217A6FE63F3545D160AB7F4833C87E8743BAA7002ACC123 +E779135FE66629CAC4661B1C3D5C9857C9417CAF216241933FF6CE60A74D53C0 +15340C049A444C79ED489619A31E2AFDCFF6E4864CC4D9B1DA8E98D7EAB9C8D5 +8E696E85837B3BA8D6D380E1F36E445D630CF0B7B18A45930A08F832EE634A02 +C85341BE669E509E902FEFCA7D025A20DC80D4F14FE1C3542B1DC3A5C7393A36F901A63C3BC074B058897B12F0F5F0D6 +2834E5D7726670A03ED6C8B27B5863EEE2FD3668795251AB65B9E4FF76ADEC3E +8E71AF7660BCFC545E92674D74F98A35FCF1B54AB5BACCEE182B7F8EDB3FA356 +C20442D506332D1A410AF3187C29BE01729C282A69B85CF8D92D70FAE3407BA8 +00BC449D006566A6549A86F52B98410871B0C14D60C181BFC017446B192C934A +8A6A28DAF1F61867B1209AB9105986A7383ECE72A40F8D93F1D0066251800A3A +4A6AFDFA6130A50180358B111145D7914C720E04D69EE5459C049FCD64249153315F86A5A9F75AEE5CB726847BA64F2A +91F838BC515C8CDD32C5803679B81A24FD5CF0C3A4AEA2C07342ACA1020E05F3B393A410B33847A8C48141D923202CF3 +7C4DEBC03295291EC5B2EBB002670D14E7C972F32DB2B3CD4E61B2BE846345D9 +8FFEB948DA3D1A7FCF620F043B1D35354F83861F22B6D693F2A7119A2E287D01 +309C66AF38E6447CB33FB0967C9365CA36314DBF74695F63CF9E558A4FF5193B +411D32EC6ADED3360DE9E9A0962F97DF2B80A09F2A2AC8D4DB6DB09FAD6EA217 +A5E5550BD59425003F9166237CD4BB6A323885D7916F71F3B2795B8A4C7125ACC1237BBF0840F0E999BD2EC14327654F +4370A828AFED684B96E8EDAB670C46D2D525FCB7E469B63CD1228C902C3ACF4E +88E3853F58129C4D834B18E4D01785591094301148D802A3A21B63521DDDA325 +D5365BF55DFE7107909684F4ECCE052E617F91FAF08CE5254721A30BEF87F368F329C46439BD1EA2819DC416B2CEF247 +BDAB9FFD3A82ECE05E8CA00F11954CDB1C124B558A5D369DFE16D32C3AEF08B2013945CC69212F170F17F62A55D35928 +8E448AA1D39C894F306FE65B0D0DD733717EF81758FFA908AF7583BFD00108E5 +019A1B0AA3492427DF3DB817B438D0302B0B8C77807D19EE45EAC7F697B2EB85 +C678A3F9DA726FE1688F86BB063EA017EDB5389E0F185484D9F5DFD4EBEE0579 +760F77AB9BD99593AC855E4AE00FBAFBB34FA2BFF504BD5CC9557658E850C6D0 +4F3E77342E9A655D4EC167E2B0428833FEE31CF603FA1CE3D1BCD619D5C9D511 +2233EFBEDD4A3A95BA8160EFCED533D9F25B65AAB10F627681D2753871814366 +A1261BE878933B4905956BE7708F7C40E999714503B52B8021D44E131D87FF6C +8F31E76314A46EA9B0CCD3EDA4B2DF603829D8E3AF8CE41AEA6BFFBA2E2990AC +850C08E45CDA4E42BD4F09B956299C0ED58615BE3664791C88D1E2608D46D205 +6283656518C6ADFC3B7D309D2763BD91111720CD51029FAA691211DEFF84CB47 +9FCAF9F77C9D1B9B6934E53DB10DFF88D392D8E7BB4DC28D65F65DC396E2E00A +41D2406BE1887FCC094861E4DF0683B1BE534C5CBF059259E946F9D04222C2AB +1D6F37E645591F0C491312C1E0CB54F1A6B70F94B54D63100C4584AA016DB589338A704B6D8B6B89C2381F660D987A2F +DCEA08D6E5C0D6229EBDE07DBB6CBC0EA8D6E24A3B631CC3775CE23A496CF178 +907051215A741BE55603F27C3FD86561CC6231CEED83AA76665D6A0B6B3FEC88 +36CD5B5BCC63D45B89592B2938D6542B9D82AB47BEB73C0D30D2EC19BD8B35C2 +1FD9651ABAD773D6D22DE677CF7B19615D5B805594DCA117F04AC3255AE8184A +4900723A8F7736C9BEAE938EE021F3E44AD806244010F825D04C59E6C67B5EA2 +0D56A8CE77106DA2CB44F65B8CF5F4A024B9CC774D1C4CB8E862C95BD30B42E7B2E946CAC361F2830361C010DC938833 +11543FD104EA7D68B6B9C3BFE3F25A84F651FCB7FB5423AADDC472A48E12AFB2 +98CD236E8B92D706DA0F92F43AC1D2CAF1CFBCDC7B330A7C9D65CDC4F2E2A677 +753557A9A5075C033206111E3C832044227A32EBF3AB2B08F6FBFCC2109B2F2A +4AA124232EF6C25CB7321F5E5F7BE11114C763F8DDAFB6B05E8412ADF37B5C7B +01514EA408E26BAFD30290429DE1A0211F4FBDB09DE2E66CE92A6F09B998EF29 +4715716D5AFF99A2D8205A46AEEF0BC20D72F3F54991F7DF4142FF187485C178 +281A32B90F8B85F31F54BF0D83B6D03E4E65A72ADB3D5A297C91881E36B45E8F +9C05D3CB9F473D5693697A8567DBA7CF650C0A0D6C1BBE3ACE78ACDE0BD027B6C836B6CB6365D5B5D00F101FA83876B9 +DC88B0237634CB007244A4169A5D9F5F65FC782E3C4388358A7E2019F7B4CC05 +BC7ACAA91D23616A0070DCA328901DDDE3BB5B59260FB45083916B6ED0E6450C +A280434D059D652D77ACB9F44409CDC7393A876239BA894DD4BE843FAB0AC561 +334F1316A35EB8A838F6B209FA4F8148062972E27A296379731B1F728A0BB32E +DDC9883E90EBE73915CFDF8EA7193F2449B66010D45F5EC6F20949D5C49719A9298B95269531A3440577B00EADE1B379 diff --git a/lib/subghz/protocols/keeloq.c b/lib/subghz/protocols/keeloq.c index 7748da1ee05..80fecbbf934 100644 --- a/lib/subghz/protocols/keeloq.c +++ b/lib/subghz/protocols/keeloq.c @@ -467,6 +467,19 @@ static inline bool subghz_protocol_keeloq_check_decrypt( } return false; } +// Centurion specific check +static inline bool subghz_protocol_keeloq_check_decrypt_centurion( + SubGhzBlockGeneric* instance, + uint32_t decrypt, + uint8_t btn) { + furi_assert(instance); + + if((decrypt >> 28 == btn) && (((((uint16_t)(decrypt >> 16)) & 0x3FF) == 0x1CE))) { + instance->cnt = decrypt & 0x0000FFFF; + return true; + } + return false; +} /** * Checking the accepted code against the database manafacture key @@ -509,9 +522,16 @@ static uint8_t subghz_protocol_keeloq_check_remote_controller_selector( // https://phreakerclub.com/forum/showpost.php?p=43557&postcount=37 man = subghz_protocol_keeloq_common_normal_learning(fix, manufacture_code->key); decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); - if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { - *manufacture_name = furi_string_get_cstr(manufacture_code->name); - return 1; + if((strcmp(furi_string_get_cstr(manufacture_code->name), "Centurion") == 0)) { + if(subghz_protocol_keeloq_check_decrypt_centurion(instance, decrypt, btn)) { + *manufacture_name = furi_string_get_cstr(manufacture_code->name); + return 1; + } + } else { + if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { + *manufacture_name = furi_string_get_cstr(manufacture_code->name); + return 1; + } } break; case KEELOQ_LEARNING_SECURE: From 67ca96ea96cdcdf0942b7186128db9a8db3e88a6 Mon Sep 17 00:00:00 2001 From: Francis Date: Tue, 1 Aug 2023 11:45:39 +0200 Subject: [PATCH 677/824] Added French Canadian layout (#2896) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit French Canadian layout for Windows Co-authored-by: あく --- assets/resources/badusb/assets/layouts/fr-CA.kl | Bin 0 -> 256 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 assets/resources/badusb/assets/layouts/fr-CA.kl diff --git a/assets/resources/badusb/assets/layouts/fr-CA.kl b/assets/resources/badusb/assets/layouts/fr-CA.kl new file mode 100644 index 0000000000000000000000000000000000000000..9de8a4732d3184c4de543bec65d445bcc6e82200 GIT binary patch literal 256 zcmaKnHx9x;0KhDZ-g|F>6hxO$lJ)-oDp*d}Zgp{{$itwNeC-+vYTC;A$rY+lc?Ao*Mz@a0@PMkV(?!u)j*KXXJ vhTJSXx%&(is??~{ph=519lG@BGhoPwF%zcDn6qHXiZvUy#32t!czw@4f$0q} literal 0 HcmV?d00001 From 1536f7a643ce9636745f5270bbd79f53249bfc2a Mon Sep 17 00:00:00 2001 From: Astro <130178009+astro-cyberpaws@users.noreply.github.com> Date: Tue, 1 Aug 2023 06:33:17 -0700 Subject: [PATCH 678/824] Update audio.ir (#2897) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update audio.ir: added Panasonic SA-PM193 codes to universal remote * Update audio.ir: added play/pause code as Pause button Co-authored-by: あく --- assets/resources/infrared/assets/audio.ir | 49 +++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/assets/resources/infrared/assets/audio.ir b/assets/resources/infrared/assets/audio.ir index 825d1bc3ef1..3b9e626ba57 100644 --- a/assets/resources/infrared/assets/audio.ir +++ b/assets/resources/infrared/assets/audio.ir @@ -328,3 +328,52 @@ type: parsed protocol: NECext address: 30 FC 00 00 command: 0C F3 00 00 +# +# Model: Panasonic SA-PM193 +name: Power +type: parsed +protocol: Kaseikyo +address: AC 02 20 00 +command: D1 03 00 00 +# CD play/pause, tape play also exists but probably less commonly used +name: Play +type: parsed +protocol: Kaseikyo +address: AA 02 20 00 +command: A0 00 00 00 +# same as above +name: Pause +type: parsed +protocol: Kaseikyo +address: AA 02 20 00 +command: A0 00 00 00 +# +name: Vol_up +type: parsed +protocol: Kaseikyo +address: A0 02 20 00 +command: 00 02 00 00 +# +name: Vol_dn +type: parsed +protocol: Kaseikyo +address: A0 02 20 00 +command: 10 02 00 00 +# +name: Next +type: parsed +protocol: Kaseikyo +address: AC 02 20 01 +command: A1 00 00 00 +# +name: Prev +type: parsed +protocol: Kaseikyo +address: AC 02 20 01 +command: 91 00 00 00 +# +name: Mute +type: parsed +protocol: Kaseikyo +address: A0 02 20 00 +command: 20 03 00 00 From 17bcfee2242f5fe00923fdc61cec5a4f3c856719 Mon Sep 17 00:00:00 2001 From: hedger Date: Tue, 1 Aug 2023 18:43:14 +0300 Subject: [PATCH 679/824] [FL-3441] faploader: always create app dir, even if it doesn't have subdirs (#2901) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- lib/flipper_application/application_assets.c | 25 ++++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/lib/flipper_application/application_assets.c b/lib/flipper_application/application_assets.c index 1262870d5e9..083c3ca197e 100644 --- a/lib/flipper_application/application_assets.c +++ b/lib/flipper_application/application_assets.c @@ -157,14 +157,6 @@ static bool flipper_application_assets_process_dirs( FuriString* full_path = flipper_application_assets_alloc_app_full_path(app_name); do { - if(!storage_simply_mkdir(storage, APPS_ASSETS_PATH)) { - break; - } - - if(!storage_simply_mkdir(storage, furi_string_get_cstr(full_path))) { - break; - } - FuriString* dir_path = furi_string_alloc(); char* path = NULL; @@ -279,6 +271,8 @@ bool flipper_application_assets_load(File* file, const char* elf_path, size_t of FURI_LOG_D(TAG, "Loading assets for %s", furi_string_get_cstr(app_name)); + FuriString* full_path = flipper_application_assets_alloc_app_full_path(app_name); + do { if(!storage_file_seek(file, offset, true)) { break; @@ -319,13 +313,23 @@ bool flipper_application_assets_load(File* file, const char* elf_path, size_t of FURI_LOG_D(TAG, "Assets removed"); } + if(!storage_simply_mkdir(storage, APPS_ASSETS_PATH)) { + break; + } + + if(!storage_simply_mkdir(storage, furi_string_get_cstr(full_path))) { + break; + } + // process directories - if(!flipper_application_assets_process_dirs(storage, file, app_name, header.dirs_count)) { + if(header.dirs_count && + !flipper_application_assets_process_dirs(storage, file, app_name, header.dirs_count)) { break; } // process files - if(!flipper_application_assets_process_files(storage, file, app_name, header.files_count)) { + if(header.files_count && !flipper_application_assets_process_files( + storage, file, app_name, header.files_count)) { break; } @@ -353,6 +357,7 @@ bool flipper_application_assets_load(File* file, const char* elf_path, size_t of } furi_record_close(RECORD_STORAGE); + furi_string_free(full_path); furi_string_free(app_name); FURI_LOG_D(TAG, "Assets loading %s", result ? "success" : "failed"); From a677b2bcc23a6f44f584b4bfdd4bbdfb8443b815 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Wed, 2 Aug 2023 00:50:17 +0900 Subject: [PATCH 680/824] [FL-3469] Move U2F path to ext (#2935) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- applications/main/u2f/u2f_data.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/main/u2f/u2f_data.c b/applications/main/u2f/u2f_data.c index 66604d166ac..e5433544a15 100644 --- a/applications/main/u2f/u2f_data.c +++ b/applications/main/u2f/u2f_data.c @@ -7,7 +7,7 @@ #define TAG "U2F" -#define U2F_DATA_FOLDER ANY_PATH("u2f/") +#define U2F_DATA_FOLDER EXT_PATH("u2f/") #define U2F_CERT_FILE U2F_DATA_FOLDER "assets/cert.der" #define U2F_CERT_KEY_FILE U2F_DATA_FOLDER "assets/cert_key.u2f" #define U2F_KEY_FILE U2F_DATA_FOLDER "key.u2f" From be86b0f38ad7245749ac8bcafa12bf55f142f76b Mon Sep 17 00:00:00 2001 From: plgcoder <112718803+plgcoder@users.noreply.github.com> Date: Wed, 2 Aug 2023 05:43:36 +0200 Subject: [PATCH 681/824] Overly missed feature: Infrared: move button (change button order in a remote) (#2894) * Feature: Infrared: move button (change button order in a remote) * little fix in furi_assert (case Move button to the end) --- applications/main/infrared/infrared_i.h | 3 + applications/main/infrared/infrared_remote.c | 15 +++ applications/main/infrared/infrared_remote.h | 1 + .../infrared/scenes/infrared_scene_config.h | 2 + .../infrared/scenes/infrared_scene_edit.c | 12 ++ .../infrared_scene_edit_button_select.c | 35 +++++- .../scenes/infrared_scene_edit_move.c | 103 ++++++++++++++++++ .../scenes/infrared_scene_edit_move_done.c | 48 ++++++++ 8 files changed, 215 insertions(+), 4 deletions(-) create mode 100644 applications/main/infrared/scenes/infrared_scene_edit_move.c create mode 100644 applications/main/infrared/scenes/infrared_scene_edit_move_done.c diff --git a/applications/main/infrared/infrared_i.h b/applications/main/infrared/infrared_i.h index 9e65c2b1c9d..96932d9bc5c 100644 --- a/applications/main/infrared/infrared_i.h +++ b/applications/main/infrared/infrared_i.h @@ -60,6 +60,8 @@ typedef enum { InfraredEditModeNone, InfraredEditModeRename, InfraredEditModeDelete, + InfraredEditModeMove, + InfraredEditModeMoveSelectDest } InfraredEditMode; typedef struct { @@ -69,6 +71,7 @@ typedef struct { InfraredEditTarget edit_target : 8; InfraredEditMode edit_mode : 8; int32_t current_button_index; + int32_t current_button_index_move_orig; uint32_t last_transmit_time; } InfraredAppState; diff --git a/applications/main/infrared/infrared_remote.c b/applications/main/infrared/infrared_remote.c index d3dfc2cce91..a04a338baa0 100644 --- a/applications/main/infrared/infrared_remote.c +++ b/applications/main/infrared/infrared_remote.c @@ -108,6 +108,21 @@ bool infrared_remote_delete_button(InfraredRemote* remote, size_t index) { return infrared_remote_store(remote); } +bool infrared_remote_move_button(InfraredRemote* remote, size_t index_orig, size_t index_dest) { + furi_assert(index_orig < InfraredButtonArray_size(remote->buttons)); + furi_assert(index_dest <= InfraredButtonArray_size(remote->buttons)); + if(index_orig == index_dest) { + return true; + } + InfraredRemoteButton* button; + InfraredButtonArray_pop_at(&button, remote->buttons, index_orig); + if(index_orig > index_dest) + InfraredButtonArray_push_at(remote->buttons, index_dest, button); + else + InfraredButtonArray_push_at(remote->buttons, index_dest - 1, button); + return infrared_remote_store(remote); +} + bool infrared_remote_store(InfraredRemote* remote) { Storage* storage = furi_record_open(RECORD_STORAGE); FlipperFormat* ff = flipper_format_file_alloc(storage); diff --git a/applications/main/infrared/infrared_remote.h b/applications/main/infrared/infrared_remote.h index 6eac193d3a0..2640149a482 100644 --- a/applications/main/infrared/infrared_remote.h +++ b/applications/main/infrared/infrared_remote.h @@ -23,6 +23,7 @@ bool infrared_remote_find_button_by_name(InfraredRemote* remote, const char* nam bool infrared_remote_add_button(InfraredRemote* remote, const char* name, InfraredSignal* signal); bool infrared_remote_rename_button(InfraredRemote* remote, const char* new_name, size_t index); bool infrared_remote_delete_button(InfraredRemote* remote, size_t index); +bool infrared_remote_move_button(InfraredRemote* remote, size_t index_orig, size_t index_dest); bool infrared_remote_store(InfraredRemote* remote); bool infrared_remote_load(InfraredRemote* remote, FuriString* path); diff --git a/applications/main/infrared/scenes/infrared_scene_config.h b/applications/main/infrared/scenes/infrared_scene_config.h index 27eabe225f5..36e6ae252f4 100644 --- a/applications/main/infrared/scenes/infrared_scene_config.h +++ b/applications/main/infrared/scenes/infrared_scene_config.h @@ -7,6 +7,8 @@ ADD_SCENE(infrared, edit_delete_done, EditDeleteDone) ADD_SCENE(infrared, edit_button_select, EditButtonSelect) ADD_SCENE(infrared, edit_rename, EditRename) ADD_SCENE(infrared, edit_rename_done, EditRenameDone) +ADD_SCENE(infrared, edit_move, EditMove) +ADD_SCENE(infrared, edit_move_done, EditMoveDone) ADD_SCENE(infrared, learn, Learn) ADD_SCENE(infrared, learn_done, LearnDone) ADD_SCENE(infrared, learn_enter_name, LearnEnterName) diff --git a/applications/main/infrared/scenes/infrared_scene_edit.c b/applications/main/infrared/scenes/infrared_scene_edit.c index 360ed49b351..79de04bda60 100644 --- a/applications/main/infrared/scenes/infrared_scene_edit.c +++ b/applications/main/infrared/scenes/infrared_scene_edit.c @@ -3,6 +3,7 @@ typedef enum { SubmenuIndexAddButton, SubmenuIndexRenameButton, + SubmenuIndexMoveButton, SubmenuIndexDeleteButton, SubmenuIndexRenameRemote, SubmenuIndexDeleteRemote, @@ -30,6 +31,12 @@ void infrared_scene_edit_on_enter(void* context) { SubmenuIndexRenameButton, infrared_scene_edit_submenu_callback, context); + submenu_add_item( + submenu, + "Move Button", + SubmenuIndexMoveButton, + infrared_scene_edit_submenu_callback, + context); submenu_add_item( submenu, "Delete Button", @@ -74,6 +81,11 @@ bool infrared_scene_edit_on_event(void* context, SceneManagerEvent event) { infrared->app_state.edit_mode = InfraredEditModeRename; scene_manager_next_scene(scene_manager, InfraredSceneEditButtonSelect); consumed = true; + } else if(submenu_index == SubmenuIndexMoveButton) { + infrared->app_state.edit_target = InfraredEditTargetButton; + infrared->app_state.edit_mode = InfraredEditModeMove; + scene_manager_next_scene(scene_manager, InfraredSceneEditButtonSelect); + consumed = true; } else if(submenu_index == SubmenuIndexDeleteButton) { infrared->app_state.edit_target = InfraredEditTargetButton; infrared->app_state.edit_mode = InfraredEditModeDelete; diff --git a/applications/main/infrared/scenes/infrared_scene_edit_button_select.c b/applications/main/infrared/scenes/infrared_scene_edit_button_select.c index a7f8a2bf7aa..7056a205396 100644 --- a/applications/main/infrared/scenes/infrared_scene_edit_button_select.c +++ b/applications/main/infrared/scenes/infrared_scene_edit_button_select.c @@ -11,9 +11,23 @@ void infrared_scene_edit_button_select_on_enter(void* context) { InfraredRemote* remote = infrared->remote; InfraredAppState* app_state = &infrared->app_state; - const char* header = infrared->app_state.edit_mode == InfraredEditModeRename ? - "Rename Button:" : - "Delete Button:"; + const char* header = NULL; + switch(infrared->app_state.edit_mode) { + case InfraredEditModeRename: + header = "Rename Button:"; + break; + case InfraredEditModeDelete: + header = "Delete Button:"; + break; + case InfraredEditModeMove: + header = "Select Button to Move:"; + break; + case InfraredEditModeMoveSelectDest: + case InfraredEditModeNone: + default: + header = "Move Button Before:"; + break; + } submenu_set_header(submenu, header); const size_t button_count = infrared_remote_get_button_count(remote); @@ -26,7 +40,14 @@ void infrared_scene_edit_button_select_on_enter(void* context) { infrared_scene_edit_button_select_submenu_callback, context); } - + if(infrared->app_state.edit_mode == InfraredEditModeMoveSelectDest) { + submenu_add_item( + submenu, + "-- Move to the end --", + button_count, + infrared_scene_edit_button_select_submenu_callback, + context); + } if(button_count && app_state->current_button_index != InfraredButtonIndexNone) { submenu_set_selected_item(submenu, app_state->current_button_index); app_state->current_button_index = InfraredButtonIndexNone; @@ -48,6 +69,12 @@ bool infrared_scene_edit_button_select_on_event(void* context, SceneManagerEvent scene_manager_next_scene(scene_manager, InfraredSceneEditRename); } else if(edit_mode == InfraredEditModeDelete) { scene_manager_next_scene(scene_manager, InfraredSceneEditDelete); + } else if(edit_mode == InfraredEditModeMove) { + app_state->current_button_index_move_orig = event.event; + app_state->edit_mode = InfraredEditModeMoveSelectDest; + scene_manager_next_scene(scene_manager, InfraredSceneEditButtonSelect); + } else if(edit_mode == InfraredEditModeMoveSelectDest) { + scene_manager_next_scene(scene_manager, InfraredSceneEditMove); } else { furi_assert(0); } diff --git a/applications/main/infrared/scenes/infrared_scene_edit_move.c b/applications/main/infrared/scenes/infrared_scene_edit_move.c new file mode 100644 index 00000000000..69c7ec41dea --- /dev/null +++ b/applications/main/infrared/scenes/infrared_scene_edit_move.c @@ -0,0 +1,103 @@ +#include "../infrared_i.h" + +static void infrared_scene_edit_move_dialog_result_callback(DialogExResult result, void* context) { + Infrared* infrared = context; + view_dispatcher_send_custom_event(infrared->view_dispatcher, result); +} + +void infrared_scene_edit_move_on_enter(void* context) { + Infrared* infrared = context; + DialogEx* dialog_ex = infrared->dialog_ex; + InfraredRemote* remote = infrared->remote; + + const InfraredEditTarget edit_target = infrared->app_state.edit_target; + if(edit_target == InfraredEditTargetButton) { + int32_t current_button_index = infrared->app_state.current_button_index_move_orig; + furi_assert(current_button_index != InfraredButtonIndexNone); + + dialog_ex_set_header(dialog_ex, "Move Button?", 64, 0, AlignCenter, AlignTop); + InfraredRemoteButton* current_button = + infrared_remote_get_button(remote, current_button_index); + InfraredSignal* signal = infrared_remote_button_get_signal(current_button); + + if(infrared_signal_is_raw(signal)) { + const InfraredRawSignal* raw = infrared_signal_get_raw_signal(signal); + infrared_text_store_set( + infrared, + 0, + "%s\nRAW\n%ld samples", + infrared_remote_button_get_name(current_button), + raw->timings_size); + + } else { + const InfraredMessage* message = infrared_signal_get_message(signal); + infrared_text_store_set( + infrared, + 0, + "%s\n%s\nA=0x%0*lX C=0x%0*lX", + infrared_remote_button_get_name(current_button), + infrared_get_protocol_name(message->protocol), + ROUND_UP_TO(infrared_get_protocol_address_length(message->protocol), 4), + message->address, + ROUND_UP_TO(infrared_get_protocol_command_length(message->protocol), 4), + message->command); + } + } else { + furi_assert(0); + } + + dialog_ex_set_text(dialog_ex, infrared->text_store[0], 64, 31, AlignCenter, AlignCenter); + dialog_ex_set_icon(dialog_ex, 0, 0, NULL); + dialog_ex_set_left_button_text(dialog_ex, "Cancel"); + dialog_ex_set_right_button_text(dialog_ex, "Move"); + dialog_ex_set_result_callback(dialog_ex, infrared_scene_edit_move_dialog_result_callback); + dialog_ex_set_context(dialog_ex, context); + + view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewDialogEx); +} + +bool infrared_scene_edit_move_on_event(void* context, SceneManagerEvent event) { + Infrared* infrared = context; + SceneManager* scene_manager = infrared->scene_manager; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == DialogExResultLeft) { + scene_manager_previous_scene(scene_manager); + consumed = true; + } else if(event.event == DialogExResultRight) { + bool success = false; + InfraredRemote* remote = infrared->remote; + InfraredAppState* app_state = &infrared->app_state; + const InfraredEditTarget edit_target = app_state->edit_target; + + if(edit_target == InfraredEditTargetButton) { + furi_assert(app_state->current_button_index != InfraredButtonIndexNone); + success = infrared_remote_move_button( + remote, + app_state->current_button_index_move_orig, + app_state->current_button_index); + app_state->current_button_index_move_orig = InfraredButtonIndexNone; + app_state->current_button_index = InfraredButtonIndexNone; + } else { + furi_assert(0); + } + + if(success) { + scene_manager_next_scene(scene_manager, InfraredSceneEditMoveDone); + } else { + const uint32_t possible_scenes[] = {InfraredSceneRemoteList, InfraredSceneStart}; + scene_manager_search_and_switch_to_previous_scene_one_of( + scene_manager, possible_scenes, COUNT_OF(possible_scenes)); + } + consumed = true; + } + } + + return consumed; +} + +void infrared_scene_edit_move_on_exit(void* context) { + Infrared* infrared = context; + UNUSED(infrared); +} diff --git a/applications/main/infrared/scenes/infrared_scene_edit_move_done.c b/applications/main/infrared/scenes/infrared_scene_edit_move_done.c new file mode 100644 index 00000000000..9f9b4b80d3e --- /dev/null +++ b/applications/main/infrared/scenes/infrared_scene_edit_move_done.c @@ -0,0 +1,48 @@ +#include "../infrared_i.h" + +void infrared_scene_edit_move_done_on_enter(void* context) { + Infrared* infrared = context; + Popup* popup = infrared->popup; + + popup_set_icon(popup, 0, 2, &I_DolphinMafia_115x62); + popup_set_header(popup, "Moved", 83, 19, AlignLeft, AlignBottom); + + popup_set_callback(popup, infrared_popup_closed_callback); + popup_set_context(popup, context); + popup_set_timeout(popup, 1500); + popup_enable_timeout(popup); + + view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewPopup); +} + +bool infrared_scene_edit_move_done_on_event(void* context, SceneManagerEvent event) { + Infrared* infrared = context; + SceneManager* scene_manager = infrared->scene_manager; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == InfraredCustomEventTypePopupClosed) { + const InfraredEditTarget edit_target = infrared->app_state.edit_target; + if(edit_target == InfraredEditTargetButton) { + scene_manager_search_and_switch_to_previous_scene( + scene_manager, InfraredSceneRemote); + } else if(edit_target == InfraredEditTargetRemote) { + const uint32_t possible_scenes[] = {InfraredSceneStart, InfraredSceneRemoteList}; + if(!scene_manager_search_and_switch_to_previous_scene_one_of( + scene_manager, possible_scenes, COUNT_OF(possible_scenes))) { + view_dispatcher_stop(infrared->view_dispatcher); + } + } else { + furi_assert(0); + } + consumed = true; + } + } + + return consumed; +} + +void infrared_scene_edit_move_done_on_exit(void* context) { + Infrared* infrared = context; + UNUSED(infrared); +} From 035e447009b6e075ef2f94fd5e761ffb2ed87f99 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Wed, 2 Aug 2023 12:58:39 +0900 Subject: [PATCH 682/824] [FL-3462] External apps icounter (#2928) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- applications/external/hid_app/hid.c | 5 ----- applications/services/dolphin/helpers/dolphin_deed.c | 2 +- applications/services/loader/loader_applications.c | 3 +++ 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/applications/external/hid_app/hid.c b/applications/external/hid_app/hid.c index a969a933a12..2be9afd919c 100644 --- a/applications/external/hid_app/hid.c +++ b/applications/external/hid_app/hid.c @@ -1,7 +1,6 @@ #include "hid.h" #include "views.h" #include -#include #define TAG "HidApp" @@ -389,8 +388,6 @@ int32_t hid_usb_app(void* p) { bt_hid_connection_status_changed_callback(BtStatusConnected, app); - dolphin_deed(DolphinDeedPluginStart); - view_dispatcher_run(app->view_dispatcher); furi_hal_usb_set_config(usb_mode_prev, NULL); @@ -429,8 +426,6 @@ int32_t hid_ble_app(void* p) { furi_hal_bt_start_advertising(); bt_set_status_changed_callback(app->bt, bt_hid_connection_status_changed_callback, app); - dolphin_deed(DolphinDeedPluginStart); - view_dispatcher_run(app->view_dispatcher); bt_set_status_changed_callback(app->bt, NULL, NULL); diff --git a/applications/services/dolphin/helpers/dolphin_deed.c b/applications/services/dolphin/helpers/dolphin_deed.c index 51db56fdf68..f1f42b770fd 100644 --- a/applications/services/dolphin/helpers/dolphin_deed.c +++ b/applications/services/dolphin/helpers/dolphin_deed.c @@ -39,7 +39,7 @@ static const DolphinDeedWeight dolphin_deed_weights[] = { {1, DolphinAppPlugin}, // DolphinDeedGpioUartBridge - {1, DolphinAppPlugin}, // DolphinDeedPluginStart + {2, DolphinAppPlugin}, // DolphinDeedPluginStart {1, DolphinAppPlugin}, // DolphinDeedPluginGameStart {10, DolphinAppPlugin}, // DolphinDeedPluginGameWin }; diff --git a/applications/services/loader/loader_applications.c b/applications/services/loader/loader_applications.c index 8ae91d76408..2e1de6134aa 100644 --- a/applications/services/loader/loader_applications.c +++ b/applications/services/loader/loader_applications.c @@ -6,6 +6,7 @@ #include #include #include +#include #define TAG "LoaderApplications" @@ -119,6 +120,8 @@ static void loader_pubsub_callback(const void* message, void* context) { static void loader_applications_start_app(LoaderApplicationsApp* app) { const char* name = furi_string_get_cstr(app->fap_path); + dolphin_deed(DolphinDeedPluginStart); + // load app FuriThreadId thread_id = furi_thread_get_current_id(); FuriPubSubSubscription* subscription = From c72531edc636ddbacda75d86f9bb74e0f1bb92fb Mon Sep 17 00:00:00 2001 From: Leopold Date: Wed, 2 Aug 2023 12:23:04 +0800 Subject: [PATCH 683/824] change FuriThreadPriorityIsr to 31 (configMAX_PRIORITIES-1) (#2920) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * change FuriThreadPriorityIsr to 31 (configMAX_PRIORITIES-1) * Furi: less hardcoded max priority, fix spelling * Format sources Co-authored-by: hedger Co-authored-by: あく --- furi/core/thread.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/furi/core/thread.h b/furi/core/thread.h index 022894ee8f6..692f2a10083 100644 --- a/furi/core/thread.h +++ b/furi/core/thread.h @@ -28,7 +28,7 @@ typedef enum { FuriThreadPriorityNormal = 16, /**< Normal */ FuriThreadPriorityHigh = 17, /**< High */ FuriThreadPriorityHighest = 18, /**< Highest */ - FuriThreadPriorityIsr = 32, /**< Deffered Isr (highest possible) */ + FuriThreadPriorityIsr = (configMAX_PRIORITIES - 1), /**< Deferred ISR (highest possible) */ } FuriThreadPriority; /** FuriThread anonymous structure */ From 313e9c3d890f874ef62821b0108540071fe59e61 Mon Sep 17 00:00:00 2001 From: minchogaydarov <134236905+minchogaydarov@users.noreply.github.com> Date: Wed, 2 Aug 2023 07:32:12 +0300 Subject: [PATCH 684/824] Add Daikin FTXC35DV1B ac remote (#2913) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: hedger Co-authored-by: あく --- assets/resources/infrared/assets/ac.ir | 37 ++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/assets/resources/infrared/assets/ac.ir b/assets/resources/infrared/assets/ac.ir index 6ec6fc82959..ac7628e1785 100644 --- a/assets/resources/infrared/assets/ac.ir +++ b/assets/resources/infrared/assets/ac.ir @@ -581,3 +581,40 @@ type: raw frequency: 38000 duty_cycle: 0.330000 data: 3204 9889 537 1587 491 529 512 544 489 545 489 1573 510 530 511 543 491 552 490 538 511 543 491 543 491 532 509 543 491 1587 489 537 512 543 491 1577 490 543 491 543 491 544 489 543 491 1586 489 544 490 1587 489 539 510 543 491 543 491 1586 490 +# +# Model: Daikin FTXC35DV1B +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 530 315 532 314 532 314 532 314 532 314 506 340 561 24780 3568 1647 507 1213 535 313 534 340 506 340 506 1212 507 339 507 339 506 339 506 339 506 1214 505 341 504 1215 505 1216 504 343 503 1217 503 1218 502 1218 502 1218 502 1218 502 344 502 344 502 1218 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 1218 502 344 502 1219 501 345 501 344 502 345 501 1219 501 1219 501 345 501 344 502 344 502 345 501 344 502 345 501 344 502 345 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 1219 501 1219 501 1219 501 345 501 1219 501 345 501 1219 501 1219 501 34807 3563 1652 502 1218 502 344 502 344 502 344 502 1218 502 344 502 344 502 344 502 344 502 1219 501 344 502 1218 502 1219 501 344 502 1219 501 1218 502 1219 501 1219 501 1219 501 345 501 344 502 1219 501 345 501 344 502 345 501 344 502 344 502 345 501 345 501 345 501 344 502 345 501 345 501 1219 501 345 501 345 501 345 501 345 501 1219 501 345 501 345 501 1219 501 345 501 345 501 1219 501 1219 501 345 501 1219 501 1219 501 1219 501 345 501 345 501 345 501 345 501 345 501 345 501 1219 501 345 501 345 501 1220 500 346 500 345 501 346 500 346 500 34806 3564 1652 502 1218 502 344 502 344 502 344 502 1218 502 344 502 344 502 344 502 344 502 1218 502 344 502 1218 502 1218 502 344 502 1218 502 1218 502 1218 502 1219 501 1218 502 344 502 345 501 1219 501 345 501 344 502 345 501 344 502 344 502 345 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 1219 501 345 501 1219 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 1219 501 1219 501 345 501 345 501 345 501 346 500 345 501 345 501 345 501 345 501 346 500 346 500 346 500 346 500 346 500 1220 499 346 500 1220 499 346 500 346 500 346 500 346 500 346 500 346 500 346 500 346 500 346 500 346 500 346 500 346 500 346 500 346 500 346 500 347 499 346 500 1221 499 1221 499 1221 499 347 499 347 499 347 499 347 499 347 499 347 499 347 499 347 499 347 499 1245 475 1222 498 1245 475 371 475 348 498 348 498 371 475 371 475 371 475 371 475 371 475 371 475 371 475 371 475 372 474 372 474 372 474 371 475 371 475 1246 474 371 475 1246 474 372 474 372 474 372 474 1246 474 1246 474 372 474 371 475 372 474 372 474 372 474 372 474 372 474 372 474 372 474 372 474 372 474 372 474 372 474 372 474 372 474 372 474 1246 474 372 474 1246 474 1246 473 372 474 372 474 1246 474 372 474 +# +name: Dh +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 536 339 507 311 535 312 534 312 534 312 534 339 507 24807 3710 1507 594 1156 563 305 543 304 542 304 570 1126 592 304 515 304 542 304 542 304 542 1155 564 304 541 1158 505 1215 505 342 504 1217 503 1217 503 1218 502 1218 502 1218 502 344 502 343 503 1218 502 343 503 343 503 344 502 344 502 344 502 344 502 344 502 343 503 343 503 344 502 1218 502 344 502 1218 502 344 502 344 502 344 502 1218 502 1218 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 1218 502 1218 502 1218 501 344 502 1218 502 344 502 1218 502 1218 502 34800 3564 1651 503 1217 503 343 503 344 502 343 503 1217 503 343 503 343 503 344 502 344 502 1218 502 344 502 1217 503 1218 502 344 502 1218 502 1218 502 1218 502 1218 502 1218 502 344 502 344 502 1218 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 1218 502 344 502 344 502 344 502 344 502 1218 502 344 502 1218 502 1218 502 344 502 344 502 1218 502 1218 502 344 502 1218 502 1219 501 1219 501 345 501 344 502 344 502 344 502 344 502 345 501 345 501 1219 501 345 501 1219 501 345 501 345 501 345 501 345 501 34805 3565 1650 503 1216 503 343 503 343 503 344 502 1217 503 343 503 344 502 343 503 343 503 1217 503 344 502 1218 502 1218 502 344 502 1218 502 1218 502 1218 502 1218 502 1218 502 344 502 344 502 1218 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 1218 502 344 502 344 502 1218 502 344 502 1218 501 344 502 344 502 344 502 345 501 344 502 345 501 344 502 345 501 1219 501 1219 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 1219 501 345 501 1219 501 345 501 345 501 345 501 345 501 346 500 345 501 345 501 346 500 370 476 346 500 346 500 346 500 370 476 346 500 346 500 346 500 346 500 1221 499 1244 476 1244 476 370 476 346 500 370 476 370 476 370 476 370 476 370 476 370 476 370 476 1244 476 1244 476 1244 476 370 476 370 476 370 476 370 476 370 476 370 476 370 476 370 476 370 476 370 476 370 476 370 476 370 476 370 476 370 476 370 476 1244 476 370 476 1245 475 370 476 371 475 370 476 1245 475 1245 475 371 475 371 475 370 476 370 476 370 476 370 476 370 476 371 475 370 476 371 475 371 475 371 475 371 475 370 476 371 475 371 475 371 475 1245 475 1245 475 1245 475 371 475 371 475 1245 475 371 475 +# +name: Cool_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 507 340 559 306 487 341 530 315 531 313 561 305 541 24776 3569 1646 508 1212 537 340 506 340 506 340 506 1213 507 339 507 339 506 339 506 340 505 1214 505 341 504 1215 504 1216 503 343 503 1217 502 1218 502 1218 502 1218 502 1218 501 344 502 344 502 1218 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 1218 502 344 502 1218 502 344 502 344 502 344 502 1219 501 1218 502 345 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 1219 501 1219 501 1219 501 345 501 1219 501 345 501 1219 501 1219 501 34800 3563 1652 502 1217 502 344 502 344 502 344 502 1218 502 344 502 344 502 344 502 344 502 1218 502 344 502 1218 502 1218 501 344 502 1218 502 1218 502 1218 502 1218 502 1218 502 344 502 344 502 1218 502 345 501 345 501 344 502 345 501 344 502 345 501 345 501 345 501 344 502 345 501 345 501 1219 501 345 501 345 501 345 501 345 501 1219 501 345 501 1219 501 1219 501 345 501 345 501 1219 501 1219 501 345 501 1219 500 1219 500 1219 500 345 501 345 501 345 501 345 501 345 501 345 501 345 501 1219 501 345 501 1220 500 346 500 346 500 346 500 346 500 34808 3564 1652 503 1218 502 344 502 344 502 344 502 1218 502 344 502 344 502 345 501 344 502 1218 502 345 501 1218 502 1219 501 344 502 1219 501 1219 501 1219 501 1219 501 1219 501 345 501 345 501 1219 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 1219 500 345 501 345 501 1219 500 1219 501 1219 501 345 501 345 501 345 501 345 501 1219 501 345 501 345 501 1219 501 346 500 346 500 345 501 345 501 346 500 346 500 346 500 345 501 346 500 346 500 346 500 346 500 346 500 346 500 346 500 1220 500 346 500 1220 500 346 500 346 500 346 500 346 500 346 500 346 500 346 500 346 500 346 500 346 500 346 500 346 500 346 500 346 500 347 499 347 499 346 500 1221 499 1245 475 1221 499 347 499 347 499 347 499 348 498 347 499 348 498 348 498 371 475 348 498 1222 498 1245 475 1245 475 348 498 371 475 371 475 371 475 371 475 371 475 371 475 371 475 371 475 371 475 371 475 371 475 372 474 372 474 372 474 371 475 1245 474 372 474 1246 474 372 474 372 474 372 474 1246 474 1246 474 372 474 372 474 372 474 372 474 372 474 372 474 372 474 372 474 372 474 372 474 372 474 372 474 372 474 372 474 372 474 372 474 372 474 1246 474 372 474 372 474 372 474 372 474 1247 473 1247 473 +# +name: Cool_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 533 311 535 312 534 312 534 313 533 310 536 312 534 24804 3705 1509 593 1129 590 305 542 305 542 305 569 1127 590 304 514 305 541 304 542 305 541 1155 564 305 540 1157 561 1159 504 341 504 1217 503 1217 503 1217 503 1218 502 1218 502 344 502 344 502 1218 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 1218 502 344 502 1218 502 344 502 344 502 344 502 1218 502 1218 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 345 501 1218 502 1218 502 1218 502 344 502 1219 501 345 501 1219 501 1219 501 34806 3565 1650 503 1217 502 343 503 343 503 343 503 1217 503 344 502 344 502 344 502 344 502 1218 502 344 502 1218 502 1218 502 344 502 1218 502 1218 502 1218 502 1218 502 1218 502 344 502 344 502 1218 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 1218 502 344 502 344 502 344 502 344 502 1218 502 344 502 1219 501 344 502 1218 502 344 502 1219 501 1218 501 345 501 1218 501 1219 501 1218 502 345 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 1219 501 1219 501 345 501 345 501 345 501 345 501 34805 3565 1651 503 1217 502 343 503 344 502 344 502 1218 502 344 502 344 502 344 502 344 502 1218 502 344 502 1218 502 1218 502 344 502 1218 502 1218 502 1218 502 1218 502 1218 502 344 502 344 502 1218 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 1218 502 344 502 344 502 1218 502 1219 501 1218 502 345 501 344 502 345 501 1219 501 1219 501 1219 501 345 501 1219 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 1219 501 345 501 1219 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 346 500 346 500 345 501 345 501 346 500 346 500 346 500 346 500 346 500 346 500 1220 500 1221 499 1221 498 346 500 346 500 370 476 346 500 347 499 347 499 370 476 370 476 370 476 1244 476 1244 476 1244 476 370 476 370 476 370 476 370 476 370 476 370 476 370 476 371 475 370 476 370 476 370 476 371 475 370 476 370 476 370 476 370 476 1244 476 371 475 1245 475 370 476 371 475 371 475 1245 475 1245 475 371 475 371 475 371 475 371 475 371 475 371 475 371 475 371 475 371 475 371 475 371 475 371 475 371 475 371 475 371 475 371 475 371 476 371 475 1245 475 1245 475 371 475 371 475 1246 474 1245 475 +# +name: Heat_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 535 313 533 311 535 311 535 312 534 312 534 313 533 24805 3711 1506 592 1129 592 305 543 305 540 305 569 1126 592 304 514 304 542 304 541 304 485 1213 507 340 506 1214 506 1215 505 342 504 1217 503 1218 502 1218 502 1218 502 1218 502 344 502 344 502 1218 503 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 1218 502 344 502 1218 502 344 502 344 502 344 502 1218 502 1218 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 503 344 502 1219 501 1219 501 1219 501 345 501 1219 501 345 501 1219 502 1219 502 34818 3566 1651 503 1217 503 343 503 344 502 344 502 1218 502 344 502 344 502 344 502 344 503 1218 502 344 502 1218 503 1218 502 344 502 1218 502 1218 503 1218 503 1218 502 1218 502 344 502 344 502 1218 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 1218 502 344 502 345 501 344 502 344 502 1219 501 345 501 345 502 1219 501 345 501 1219 502 1219 502 1219 502 344 502 1219 501 1219 502 1219 501 345 501 345 501 345 501 345 501 345 501 345 501 1219 501 345 501 345 501 345 501 1219 501 345 501 345 501 345 501 34812 3566 1651 503 1217 503 344 502 344 502 344 502 1218 502 344 502 344 502 344 502 344 502 1218 503 344 502 1218 502 1218 502 344 502 1218 502 1218 502 1218 502 1219 501 1218 502 344 502 344 502 1218 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 344 502 345 501 344 502 345 501 345 501 1219 502 345 501 345 501 1219 502 345 501 345 501 1219 501 345 501 345 501 1219 501 1219 501 1219 501 345 501 1219 501 345 501 345 501 345 501 345 501 345 501 345 502 345 501 345 501 345 501 346 500 345 501 345 501 345 501 345 501 345 501 1220 500 346 500 1220 500 346 500 346 500 346 500 346 500 346 500 346 500 346 500 346 500 346 500 346 500 346 500 346 500 347 499 346 500 347 499 347 499 370 476 1244 476 1221 499 1244 476 370 476 370 476 370 476 370 476 370 476 370 476 370 476 370 476 370 476 1245 475 1245 476 1245 475 371 475 370 476 370 476 371 475 371 475 370 476 370 476 371 475 371 476 370 476 370 476 371 475 371 475 371 475 371 475 371 475 1245 475 371 475 1245 475 371 475 371 475 371 475 1245 475 1245 475 371 475 371 475 371 475 371 475 371 475 371 475 371 475 371 476 371 475 371 475 371 475 371 475 371 475 371 475 371 475 371 475 371 475 371 475 1245 475 1245 475 1245 475 371 475 1245 475 1246 474 +# +name: Heat_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 535 314 532 314 533 313 533 312 534 313 533 312 508 24840 3569 1647 508 1213 536 342 505 342 504 341 505 1214 506 340 506 340 506 340 505 340 506 1214 506 341 504 1216 504 1217 503 343 503 1218 502 1219 501 1219 502 1219 502 1219 502 345 502 344 502 1219 502 345 501 345 502 345 502 345 501 345 501 345 501 345 501 345 501 345 502 345 501 1219 502 345 501 1219 501 345 502 345 501 345 501 1219 501 1219 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 1220 500 1219 501 1220 500 345 501 1220 500 345 501 1220 501 1220 500 34815 3564 1653 502 1218 502 344 502 345 501 344 502 1219 501 345 501 345 501 345 501 345 501 1219 502 345 501 1219 502 1220 501 345 502 1219 501 1219 502 1219 501 1219 502 1219 502 345 501 345 501 1220 501 345 502 345 501 345 501 345 501 345 501 345 501 345 502 345 501 345 502 345 501 345 501 1220 501 345 501 345 501 345 501 345 501 1220 500 345 501 346 501 1220 500 1220 501 345 501 345 501 346 500 1220 500 1220 500 1220 500 1220 500 346 500 346 500 346 500 345 501 346 500 345 501 1220 500 346 500 1220 500 1220 500 1220 500 346 500 346 500 346 500 34816 3565 1653 502 1219 501 344 502 345 501 345 501 1219 502 345 501 345 502 345 501 345 501 1219 501 345 501 1219 502 1219 501 345 501 1219 502 1219 501 1219 501 1219 501 1219 501 345 501 345 501 1219 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 345 501 1220 501 346 501 345 501 1220 501 346 500 346 500 1220 500 346 500 345 501 346 500 1220 500 1220 500 1220 501 1220 500 346 500 346 500 346 500 346 500 346 500 346 500 346 500 346 500 346 500 346 500 346 500 346 500 346 500 346 500 346 500 1220 500 346 500 1220 500 347 499 346 500 346 500 346 500 347 499 347 499 346 500 346 500 347 499 347 499 347 499 347 499 347 499 347 499 347 499 347 499 347 499 1222 498 1222 499 1222 498 347 499 348 498 348 498 347 499 371 475 348 498 348 498 348 498 371 475 1222 498 1246 474 1246 474 372 475 371 475 372 474 372 474 348 498 371 475 372 474 372 474 372 474 372 474 372 474 372 474 372 474 372 474 372 474 372 474 1246 474 372 474 1246 474 372 474 372 474 372 474 1246 474 1246 474 372 474 372 474 372 474 372 474 372 474 372 474 372 474 372 474 372 474 372 474 372 474 372 474 372 474 372 474 372 474 372 474 372 474 1246 474 372 474 1247 473 372 474 1246 474 1246 474 1246 474 From 9fd9dd85e3a157bed74f4ce4b6120433b2983be8 Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Wed, 2 Aug 2023 08:45:39 +0400 Subject: [PATCH 685/824] =?UTF-8?q?SubGhz:=20change=20CC1101=5Fext=20TIM17?= =?UTF-8?q?=20resolution=20to=202=C2=B5s=20(#2909)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * SubGhz: change the operation of the TIM17 timer in CC1101_ext to 2µs * SubGhz: remove special characters Co-authored-by: あく --- .../drivers/subghz/cc1101_ext/cc1101_ext.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/applications/drivers/subghz/cc1101_ext/cc1101_ext.c b/applications/drivers/subghz/cc1101_ext/cc1101_ext.c index 594a74a0150..e9560ce5f3e 100644 --- a/applications/drivers/subghz/cc1101_ext/cc1101_ext.c +++ b/applications/drivers/subghz/cc1101_ext/cc1101_ext.c @@ -37,7 +37,7 @@ #define SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_BUFFER_FULL (256) #define SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_BUFFER_HALF \ (SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_BUFFER_FULL / 2) -#define SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_GUARD_TIME 999 +#define SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_GUARD_TIME 999 << 1 /** SubGhz state */ typedef enum { @@ -483,7 +483,7 @@ static void subghz_device_cc1101_ext_capture_ISR() { subghz_device_cc1101_ext->async_rx.capture_callback( true, - LL_TIM_GetCounter(TIM17), + LL_TIM_GetCounter(TIM17) << 1, (void*)subghz_device_cc1101_ext->async_rx.capture_callback_context); } } else { @@ -493,11 +493,11 @@ static void subghz_device_cc1101_ext_capture_ISR() { subghz_device_cc1101_ext->async_rx.capture_callback( false, - LL_TIM_GetCounter(TIM17), + LL_TIM_GetCounter(TIM17) << 1, (void*)subghz_device_cc1101_ext->async_rx.capture_callback_context); } } - LL_TIM_SetCounter(TIM17, 6); + LL_TIM_SetCounter(TIM17, 4); //8>>1 } void subghz_device_cc1101_ext_start_async_rx( @@ -512,7 +512,8 @@ void subghz_device_cc1101_ext_start_async_rx( furi_hal_bus_enable(FuriHalBusTIM17); // Configure TIM - LL_TIM_SetPrescaler(TIM17, 64 - 1); + //Set the timer resolution to 2 us + LL_TIM_SetPrescaler(TIM17, (64 << 1) - 1); LL_TIM_SetCounterMode(TIM17, LL_TIM_COUNTERMODE_UP); LL_TIM_SetAutoReload(TIM17, 0xFFFF); LL_TIM_SetClockDivision(TIM17, LL_TIM_CLOCKDIVISION_DIV1); @@ -606,7 +607,7 @@ static void subghz_device_cc1101_ext_async_tx_refill(uint32_t* buffer, size_t sa uint32_t duration = level_duration_get_duration(ld); furi_assert(duration > 0); - *buffer = duration - 1; + *buffer = duration >> 1; buffer++; samples--; } @@ -692,7 +693,8 @@ bool subghz_device_cc1101_ext_start_async_tx(SubGhzDeviceCC1101ExtCallback callb furi_hal_bus_enable(FuriHalBusTIM17); // Configure TIM - LL_TIM_SetPrescaler(TIM17, 64 - 1); + // Set the timer resolution to 2 us + LL_TIM_SetPrescaler(TIM17, (64 << 1) - 1); LL_TIM_SetCounterMode(TIM17, LL_TIM_COUNTERMODE_UP); LL_TIM_SetAutoReload(TIM17, 0xFFFF); LL_TIM_SetClockDivision(TIM17, LL_TIM_CLOCKDIVISION_DIV1); From 7a45db38812f50649f6501fa43acfc16d16dc2b5 Mon Sep 17 00:00:00 2001 From: Andrey Zakharov Date: Wed, 2 Aug 2023 08:19:00 +0300 Subject: [PATCH 686/824] Fix about screen (#2907) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix about screen * About: use COUNT_OF Co-authored-by: Andrey Zakharov Co-authored-by: あく --- applications/settings/about/about.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/applications/settings/about/about.c b/applications/settings/about/about.c index 55bd43e5c58..dcd7656fc47 100644 --- a/applications/settings/about/about.c +++ b/applications/settings/about/about.c @@ -87,6 +87,7 @@ static DialogMessageButton icon2_screen(DialogsApp* dialogs, DialogMessage* mess message, furi_hal_version_get_mic_id(), 63, 27, AlignLeft, AlignCenter); result = dialog_message_show(dialogs, message); dialog_message_set_icon(message, NULL, 0, 0); + dialog_message_set_text(message, NULL, 0, 0, AlignLeft, AlignTop); return result; } @@ -172,8 +173,6 @@ const AboutDialogScreen about_screens[] = { hw_version_screen, fw_version_screen}; -const size_t about_screens_count = sizeof(about_screens) / sizeof(AboutDialogScreen); - int32_t about_settings_app(void* p) { UNUSED(p); DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); @@ -194,7 +193,7 @@ int32_t about_settings_app(void* p) { view_dispatcher_switch_to_view(view_dispatcher, empty_screen_index); while(1) { - if(screen_index >= about_screens_count - 1) { + if(screen_index >= COUNT_OF(about_screens) - 1) { dialog_message_set_buttons(message, "Back", NULL, NULL); } else { dialog_message_set_buttons(message, "Back", NULL, "Next"); @@ -209,7 +208,7 @@ int32_t about_settings_app(void* p) { screen_index--; } } else if(screen_result == DialogMessageButtonRight) { - if(screen_index < about_screens_count) { + if(screen_index < COUNT_OF(about_screens) - 1) { screen_index++; } } else if(screen_result == DialogMessageButtonBack) { From 3e8e99990909c241e164f309a583d76ca3b7305e Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Wed, 2 Aug 2023 23:58:59 +0900 Subject: [PATCH 687/824] [FL-3470] Rename Applications to Apps (#2939) * Applications are now apps * Desktop: Apps in settings Co-authored-by: Aleksandr Kutuzov --- applications/services/loader/loader.h | 2 +- applications/services/loader/loader_cli.c | 2 +- .../desktop_settings/scenes/desktop_settings_scene_favorite.c | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/applications/services/loader/loader.h b/applications/services/loader/loader.h index 9fc4059f2d9..3da676e651a 100644 --- a/applications/services/loader/loader.h +++ b/applications/services/loader/loader.h @@ -6,7 +6,7 @@ extern "C" { #endif #define RECORD_LOADER "loader" -#define LOADER_APPLICATIONS_NAME "Applications" +#define LOADER_APPLICATIONS_NAME "Apps" typedef struct Loader Loader; diff --git a/applications/services/loader/loader_cli.c b/applications/services/loader/loader_cli.c index af3ebf9e002..cbec4adca8a 100644 --- a/applications/services/loader/loader_cli.c +++ b/applications/services/loader/loader_cli.c @@ -14,7 +14,7 @@ static void loader_cli_print_usage() { } static void loader_cli_list() { - printf("Applications:\r\n"); + printf("Apps:\r\n"); for(size_t i = 0; i < FLIPPER_APPS_COUNT; i++) { printf("\t%s\r\n", FLIPPER_APPS[i].name); } diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c index 26e7bc5875b..e0b4c118e18 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c @@ -7,10 +7,10 @@ #define APPS_COUNT (FLIPPER_APPS_COUNT + FLIPPER_EXTERNAL_APPS_COUNT) -#define EXTERNAL_BROWSER_NAME ("Applications") +#define EXTERNAL_BROWSER_NAME ("Apps") #define EXTERNAL_BROWSER_INDEX (APPS_COUNT + 1) -#define EXTERNAL_APPLICATION_NAME ("[External Application]") +#define EXTERNAL_APPLICATION_NAME ("[Select App]") #define EXTERNAL_APPLICATION_INDEX (APPS_COUNT + 2) #define PRESELECTED_SPECIAL 0xffffffff From a7aef0bfc27541639aa7be0cfc06a2db0ecc8c9a Mon Sep 17 00:00:00 2001 From: AloneLiberty <111039319+AloneLiberty@users.noreply.github.com> Date: Wed, 2 Aug 2023 15:06:38 +0000 Subject: [PATCH 688/824] NFC: Fix MFC key invalidation (#2912) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * NFC: Fix key invalidation (again~) * NFC: shouldn't be there This code get called each time we check for B key Co-authored-by: あく --- lib/nfc/nfc_worker.c | 69 +++++++++++++++++++------------------------- 1 file changed, 30 insertions(+), 39 deletions(-) diff --git a/lib/nfc/nfc_worker.c b/lib/nfc/nfc_worker.c index 145007bd3b3..b2c845f07d1 100644 --- a/lib/nfc/nfc_worker.c +++ b/lib/nfc/nfc_worker.c @@ -810,16 +810,10 @@ static void nfc_worker_mf_classic_key_attack( uint8_t block_num = mf_classic_get_sector_trailer_block_num_by_sector(i); if(mf_classic_is_sector_read(data, i)) continue; if(!mf_classic_is_key_found(data, i, MfClassicKeyA)) { - FURI_LOG_D( - TAG, - "Trying A key for sector %d, key: %04lx%08lx", - i, - (uint32_t)(key >> 32), - (uint32_t)key); + FURI_LOG_D(TAG, "Trying A key for sector %d, key: %012llX", i, key); if(mf_classic_authenticate(tx_rx, block_num, key, MfClassicKeyA)) { mf_classic_set_key_found(data, i, MfClassicKeyA, key); - FURI_LOG_D( - TAG, "Key A found: %04lx%08lx", (uint32_t)(key >> 32), (uint32_t)key); + FURI_LOG_D(TAG, "Key A found: %012llX", key); nfc_worker->callback(NfcWorkerEventFoundKeyA, nfc_worker->context); uint64_t found_key; @@ -832,18 +826,13 @@ static void nfc_worker_mf_classic_key_attack( } } } + furi_hal_nfc_sleep(); } if(!mf_classic_is_key_found(data, i, MfClassicKeyB)) { - FURI_LOG_D( - TAG, - "Trying B key for sector %d, key: %04lx%08lx", - i, - (uint32_t)(key >> 32), - (uint32_t)key); + FURI_LOG_D(TAG, "Trying B key for sector %d, key: %012llX", i, key); if(mf_classic_authenticate(tx_rx, block_num, key, MfClassicKeyB)) { mf_classic_set_key_found(data, i, MfClassicKeyB, key); - FURI_LOG_D( - TAG, "Key B found: %04lx%08lx", (uint32_t)(key >> 32), (uint32_t)key); + FURI_LOG_D(TAG, "Key B found: %012llX", key); nfc_worker->callback(NfcWorkerEventFoundKeyB, nfc_worker->context); } } @@ -891,8 +880,9 @@ void nfc_worker_mf_classic_dict_attack(NfcWorker* nfc_worker) { nfc_worker->callback(NfcWorkerEventNewSector, nfc_worker->context); uint8_t block_num = mf_classic_get_sector_trailer_block_num_by_sector(i); if(mf_classic_is_sector_read(data, i)) continue; - bool is_key_a_found = mf_classic_is_key_found(data, i, MfClassicKeyA); - bool is_key_b_found = mf_classic_is_key_found(data, i, MfClassicKeyB); + if(mf_classic_is_key_found(data, i, MfClassicKeyA) && + mf_classic_is_key_found(data, i, MfClassicKeyB)) + continue; uint16_t key_index = 0; while(mf_classic_dict_get_next_key(dict, &key)) { FURI_LOG_T(TAG, "Key %d", key_index); @@ -910,19 +900,12 @@ void nfc_worker_mf_classic_dict_attack(NfcWorker* nfc_worker) { nfc_worker_mf_classic_key_attack(nfc_worker, prev_key, &tx_rx, i); deactivated = true; } - FURI_LOG_D( - TAG, - "Try to auth to sector %d with key %04lx%08lx", - i, - (uint32_t)(key >> 32), - (uint32_t)key); - if(!is_key_a_found) { - is_key_a_found = mf_classic_is_key_found(data, i, MfClassicKeyA); + FURI_LOG_D(TAG, "Try to auth to sector %d with key %012llX", i, key); + if(!mf_classic_is_key_found(data, i, MfClassicKeyA)) { if(mf_classic_authenticate_skip_activate( &tx_rx, block_num, key, MfClassicKeyA, !deactivated, cuid)) { mf_classic_set_key_found(data, i, MfClassicKeyA, key); - FURI_LOG_D( - TAG, "Key A found: %04lx%08lx", (uint32_t)(key >> 32), (uint32_t)key); + FURI_LOG_D(TAG, "Key A found: %012llX", key); nfc_worker->callback(NfcWorkerEventFoundKeyA, nfc_worker->context); uint64_t found_key; @@ -952,17 +935,19 @@ void nfc_worker_mf_classic_dict_attack(NfcWorker* nfc_worker) { if(mf_classic_is_key_found(data, i, MfClassicKeyA) && memcmp(sec_trailer->key_a, current_key, 6) == 0) { - mf_classic_set_key_not_found(data, i, MfClassicKeyA); - is_key_a_found = false; - FURI_LOG_D(TAG, "Key %dA not found in attack", i); + if(!mf_classic_authenticate_skip_activate( + &tx_rx, block_num, key, MfClassicKeyA, !deactivated, cuid)) { + mf_classic_set_key_not_found(data, i, MfClassicKeyA); + FURI_LOG_D(TAG, "Key %dA not found in attack", i); + } } + furi_hal_nfc_sleep(); + deactivated = true; } - if(!is_key_b_found) { - is_key_b_found = mf_classic_is_key_found(data, i, MfClassicKeyB); + if(!mf_classic_is_key_found(data, i, MfClassicKeyB)) { if(mf_classic_authenticate_skip_activate( &tx_rx, block_num, key, MfClassicKeyB, !deactivated, cuid)) { - FURI_LOG_D( - TAG, "Key B found: %04lx%08lx", (uint32_t)(key >> 32), (uint32_t)key); + FURI_LOG_D(TAG, "Key B found: %012llX", key); mf_classic_set_key_found(data, i, MfClassicKeyB, key); nfc_worker->callback(NfcWorkerEventFoundKeyB, nfc_worker->context); nfc_worker_mf_classic_key_attack(nfc_worker, key, &tx_rx, i + 1); @@ -978,12 +963,18 @@ void nfc_worker_mf_classic_dict_attack(NfcWorker* nfc_worker) { if(mf_classic_is_key_found(data, i, MfClassicKeyB) && memcmp(sec_trailer->key_b, current_key, 6) == 0) { - mf_classic_set_key_not_found(data, i, MfClassicKeyB); - is_key_b_found = false; - FURI_LOG_D(TAG, "Key %dB not found in attack", i); + if(!mf_classic_authenticate_skip_activate( + &tx_rx, block_num, key, MfClassicKeyB, !deactivated, cuid)) { + mf_classic_set_key_not_found(data, i, MfClassicKeyB); + FURI_LOG_D(TAG, "Key %dB not found in attack", i); + } + furi_hal_nfc_sleep(); + deactivated = true; } } - if(is_key_a_found && is_key_b_found) break; + if(mf_classic_is_key_found(data, i, MfClassicKeyA) && + mf_classic_is_key_found(data, i, MfClassicKeyB)) + break; if(nfc_worker->state != NfcWorkerStateMfClassicDictAttack) break; } else { if(!card_removed_notified) { From cf6706c42eebff3631d702aa51119af6925a5b24 Mon Sep 17 00:00:00 2001 From: erikj95 Date: Wed, 2 Aug 2023 17:24:02 +0200 Subject: [PATCH 689/824] NFC CLI: Fix multiple apdu commands from not working when one of them gives an empty response (#2922) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * NFC CLI: Fix multiple apdu commands from not working when one of them gives an empty response * Make PVS happy Co-authored-by: hedger Co-authored-by: あく --- applications/main/nfc/nfc_cli.c | 2 +- lib/nfc/nfc_worker.c | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/applications/main/nfc/nfc_cli.c b/applications/main/nfc/nfc_cli.c index 0b7e7547548..e9617438124 100644 --- a/applications/main/nfc/nfc_cli.c +++ b/applications/main/nfc/nfc_cli.c @@ -146,7 +146,7 @@ static void nfc_cli_apdu(Cli* cli, FuriString* args) { resp_size = (tx_rx.rx_bits / 8) * 2; if(!resp_size) { printf("No response\r\n"); - break; + continue; } resp_buffer = malloc(resp_size); uint8_to_hex_chars(tx_rx.rx_data, resp_buffer, resp_size); diff --git a/lib/nfc/nfc_worker.c b/lib/nfc/nfc_worker.c index b2c845f07d1..36776d80470 100644 --- a/lib/nfc/nfc_worker.c +++ b/lib/nfc/nfc_worker.c @@ -946,13 +946,13 @@ void nfc_worker_mf_classic_dict_attack(NfcWorker* nfc_worker) { } if(!mf_classic_is_key_found(data, i, MfClassicKeyB)) { if(mf_classic_authenticate_skip_activate( - &tx_rx, block_num, key, MfClassicKeyB, !deactivated, cuid)) { + &tx_rx, block_num, key, MfClassicKeyB, !deactivated, cuid)) { //-V547 FURI_LOG_D(TAG, "Key B found: %012llX", key); mf_classic_set_key_found(data, i, MfClassicKeyB, key); nfc_worker->callback(NfcWorkerEventFoundKeyB, nfc_worker->context); nfc_worker_mf_classic_key_attack(nfc_worker, key, &tx_rx, i + 1); } - deactivated = true; + deactivated = true; //-V1048 } else { // If the key B is marked as found and matches the searching key, invalidate it MfClassicSectorTrailer* sec_trailer = @@ -964,12 +964,12 @@ void nfc_worker_mf_classic_dict_attack(NfcWorker* nfc_worker) { if(mf_classic_is_key_found(data, i, MfClassicKeyB) && memcmp(sec_trailer->key_b, current_key, 6) == 0) { if(!mf_classic_authenticate_skip_activate( - &tx_rx, block_num, key, MfClassicKeyB, !deactivated, cuid)) { + &tx_rx, block_num, key, MfClassicKeyB, !deactivated, cuid)) { //-V547 mf_classic_set_key_not_found(data, i, MfClassicKeyB); FURI_LOG_D(TAG, "Key %dB not found in attack", i); } furi_hal_nfc_sleep(); - deactivated = true; + deactivated = true; //-V1048 } } if(mf_classic_is_key_found(data, i, MfClassicKeyA) && From c7648eb932bb8accedfc3a10ca54c953c7476996 Mon Sep 17 00:00:00 2001 From: Lesha Lomalkin Date: Wed, 2 Aug 2023 18:38:51 +0300 Subject: [PATCH 690/824] fbtenv: add additional environ variable to control execution flow (#2938) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fbtenv: add flags FBT_PRESERVE_TAR, FBT_SKIP_CHECK_SOURCED for usage with external tools * fbtenv: beautify, add info to fbtenv_print_config section if FBT_VERBOSE * fbtenv: fixes Co-authored-by: あく --- scripts/toolchain/fbtenv.sh | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/scripts/toolchain/fbtenv.sh b/scripts/toolchain/fbtenv.sh index a86b0ecedb2..c5040ed81e7 100755 --- a/scripts/toolchain/fbtenv.sh +++ b/scripts/toolchain/fbtenv.sh @@ -82,6 +82,9 @@ fbtenv_restore_env() fbtenv_check_sourced() { + if [ -n "${FBT_SKIP_CHECK_SOURCED:-""}" ]; then + return 0; + fi case "${ZSH_EVAL_CONTEXT:-""}" in *:file:*) setopt +o nomatch; # disabling 'no match found' warning in zsh return 0;; @@ -200,7 +203,7 @@ fbtenv_download_toolchain_tar() return 0; } -fbtenv_remove_old_tooclhain() +fbtenv_remove_old_toolchain() { printf "Removing old toolchain.."; rm -rf "${TOOLCHAIN_ARCH_DIR:?}"; @@ -231,12 +234,14 @@ fbtenv_unpack_toolchain() fbtenv_cleanup() { - printf "Cleaning up.."; if [ -n "${FBT_TOOLCHAIN_PATH:-""}" ]; then - rm -rf "${FBT_TOOLCHAIN_PATH:?}/toolchain/"*.tar.gz; + printf "Cleaning up.."; rm -rf "${FBT_TOOLCHAIN_PATH:?}/toolchain/"*.part; + if [ -z "${FBT_PRESERVE_TAR:-""}" ]; then + rm -rf "${FBT_TOOLCHAIN_PATH:?}/toolchain/"*.tar.gz; + fi + echo "done"; fi - echo "done"; trap - 2; return 0; } @@ -289,16 +294,22 @@ fbtenv_download_toolchain() fbtenv_curl_wget_check || return 1; fbtenv_download_toolchain_tar || return 1; fi - fbtenv_remove_old_tooclhain; + fbtenv_remove_old_toolchain; fbtenv_unpack_toolchain || return 1; fbtenv_cleanup; return 0; } -fbtenv_print_version() +fbtenv_print_config() { - if [ -n "$FBT_VERBOSE" ]; then + if [ -n "${FBT_VERBOSE:-""}" ]; then echo "FBT: using toolchain version $(cat "$TOOLCHAIN_ARCH_DIR/VERSION")"; + if [ -n "${FBT_SKIP_CHECK_SOURCED:-""}" ]; then + echo "FBT: fbtenv will not check if it is sourced or not"; + fi + if [ -n "${FBT_PRESERVE_TAR:-""}" ]; then + echo "FBT: toolchain archives will be saved"; + fi fi } @@ -316,7 +327,7 @@ fbtenv_main() fbtenv_check_env_vars || return 1; fbtenv_check_download_toolchain || return 1; fbtenv_set_shell_prompt; - fbtenv_print_version; + fbtenv_print_config; PATH="$TOOLCHAIN_ARCH_DIR/python/bin:$PATH"; PATH="$TOOLCHAIN_ARCH_DIR/bin:$PATH"; PATH="$TOOLCHAIN_ARCH_DIR/protobuf/bin:$PATH"; From 4c771b66dcb0dca0552c9a4b187645cf0a7e0b67 Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Mon, 7 Aug 2023 12:18:46 +0300 Subject: [PATCH 691/824] [FL-3471] Infrared: buttons move feature rework (#2949) --- applications/main/infrared/infrared.c | 7 + applications/main/infrared/infrared_i.h | 5 +- applications/main/infrared/infrared_remote.c | 14 +- applications/main/infrared/infrared_remote.h | 2 +- .../infrared/scenes/infrared_scene_config.h | 1 - .../infrared/scenes/infrared_scene_edit.c | 4 +- .../infrared_scene_edit_button_select.c | 34 +-- .../scenes/infrared_scene_edit_move.c | 101 ++------ .../scenes/infrared_scene_edit_move_done.c | 48 ---- .../main/infrared/views/infrared_move_view.c | 215 ++++++++++++++++++ .../main/infrared/views/infrared_move_view.h | 25 ++ 11 files changed, 280 insertions(+), 176 deletions(-) delete mode 100644 applications/main/infrared/scenes/infrared_scene_edit_move_done.c create mode 100644 applications/main/infrared/views/infrared_move_view.c create mode 100644 applications/main/infrared/views/infrared_move_view.h diff --git a/applications/main/infrared/infrared.c b/applications/main/infrared/infrared.c index 5957cdb1336..fcf45c25c28 100644 --- a/applications/main/infrared/infrared.c +++ b/applications/main/infrared/infrared.c @@ -165,6 +165,10 @@ static Infrared* infrared_alloc() { view_dispatcher_add_view( view_dispatcher, InfraredViewStack, view_stack_get_view(infrared->view_stack)); + infrared->move_view = infrared_move_view_alloc(); + view_dispatcher_add_view( + view_dispatcher, InfraredViewMove, infrared_move_view_get_view(infrared->move_view)); + if(app_state->is_debug_enabled) { infrared->debug_view = infrared_debug_view_alloc(); view_dispatcher_add_view( @@ -209,6 +213,9 @@ static void infrared_free(Infrared* infrared) { view_dispatcher_remove_view(view_dispatcher, InfraredViewStack); view_stack_free(infrared->view_stack); + view_dispatcher_remove_view(view_dispatcher, InfraredViewMove); + infrared_move_view_free(infrared->move_view); + if(app_state->is_debug_enabled) { view_dispatcher_remove_view(view_dispatcher, InfraredViewDebugView); infrared_debug_view_free(infrared->debug_view); diff --git a/applications/main/infrared/infrared_i.h b/applications/main/infrared/infrared_i.h index 96932d9bc5c..6fb6a65c7b9 100644 --- a/applications/main/infrared/infrared_i.h +++ b/applications/main/infrared/infrared_i.h @@ -30,6 +30,7 @@ #include "scenes/infrared_scene.h" #include "views/infrared_progress_view.h" #include "views/infrared_debug_view.h" +#include "views/infrared_move_view.h" #include "rpc/rpc_app.h" @@ -60,8 +61,6 @@ typedef enum { InfraredEditModeNone, InfraredEditModeRename, InfraredEditModeDelete, - InfraredEditModeMove, - InfraredEditModeMoveSelectDest } InfraredEditMode; typedef struct { @@ -96,6 +95,7 @@ struct Infrared { ViewStack* view_stack; InfraredDebugView* debug_view; + InfraredMoveView* move_view; ButtonPanel* button_panel; Loading* loading; @@ -116,6 +116,7 @@ typedef enum { InfraredViewPopup, InfraredViewStack, InfraredViewDebugView, + InfraredViewMove, } InfraredView; typedef enum { diff --git a/applications/main/infrared/infrared_remote.c b/applications/main/infrared/infrared_remote.c index a04a338baa0..70d1b59ef38 100644 --- a/applications/main/infrared/infrared_remote.c +++ b/applications/main/infrared/infrared_remote.c @@ -108,19 +108,13 @@ bool infrared_remote_delete_button(InfraredRemote* remote, size_t index) { return infrared_remote_store(remote); } -bool infrared_remote_move_button(InfraredRemote* remote, size_t index_orig, size_t index_dest) { +void infrared_remote_move_button(InfraredRemote* remote, size_t index_orig, size_t index_dest) { furi_assert(index_orig < InfraredButtonArray_size(remote->buttons)); - furi_assert(index_dest <= InfraredButtonArray_size(remote->buttons)); - if(index_orig == index_dest) { - return true; - } + furi_assert(index_dest < InfraredButtonArray_size(remote->buttons)); + InfraredRemoteButton* button; InfraredButtonArray_pop_at(&button, remote->buttons, index_orig); - if(index_orig > index_dest) - InfraredButtonArray_push_at(remote->buttons, index_dest, button); - else - InfraredButtonArray_push_at(remote->buttons, index_dest - 1, button); - return infrared_remote_store(remote); + InfraredButtonArray_push_at(remote->buttons, index_dest, button); } bool infrared_remote_store(InfraredRemote* remote) { diff --git a/applications/main/infrared/infrared_remote.h b/applications/main/infrared/infrared_remote.h index 2640149a482..47aa77e2ef8 100644 --- a/applications/main/infrared/infrared_remote.h +++ b/applications/main/infrared/infrared_remote.h @@ -23,7 +23,7 @@ bool infrared_remote_find_button_by_name(InfraredRemote* remote, const char* nam bool infrared_remote_add_button(InfraredRemote* remote, const char* name, InfraredSignal* signal); bool infrared_remote_rename_button(InfraredRemote* remote, const char* new_name, size_t index); bool infrared_remote_delete_button(InfraredRemote* remote, size_t index); -bool infrared_remote_move_button(InfraredRemote* remote, size_t index_orig, size_t index_dest); +void infrared_remote_move_button(InfraredRemote* remote, size_t index_orig, size_t index_dest); bool infrared_remote_store(InfraredRemote* remote); bool infrared_remote_load(InfraredRemote* remote, FuriString* path); diff --git a/applications/main/infrared/scenes/infrared_scene_config.h b/applications/main/infrared/scenes/infrared_scene_config.h index 36e6ae252f4..27ef2f3b179 100644 --- a/applications/main/infrared/scenes/infrared_scene_config.h +++ b/applications/main/infrared/scenes/infrared_scene_config.h @@ -8,7 +8,6 @@ ADD_SCENE(infrared, edit_button_select, EditButtonSelect) ADD_SCENE(infrared, edit_rename, EditRename) ADD_SCENE(infrared, edit_rename_done, EditRenameDone) ADD_SCENE(infrared, edit_move, EditMove) -ADD_SCENE(infrared, edit_move_done, EditMoveDone) ADD_SCENE(infrared, learn, Learn) ADD_SCENE(infrared, learn_done, LearnDone) ADD_SCENE(infrared, learn_enter_name, LearnEnterName) diff --git a/applications/main/infrared/scenes/infrared_scene_edit.c b/applications/main/infrared/scenes/infrared_scene_edit.c index 79de04bda60..02bba7a3f46 100644 --- a/applications/main/infrared/scenes/infrared_scene_edit.c +++ b/applications/main/infrared/scenes/infrared_scene_edit.c @@ -82,9 +82,7 @@ bool infrared_scene_edit_on_event(void* context, SceneManagerEvent event) { scene_manager_next_scene(scene_manager, InfraredSceneEditButtonSelect); consumed = true; } else if(submenu_index == SubmenuIndexMoveButton) { - infrared->app_state.edit_target = InfraredEditTargetButton; - infrared->app_state.edit_mode = InfraredEditModeMove; - scene_manager_next_scene(scene_manager, InfraredSceneEditButtonSelect); + scene_manager_next_scene(scene_manager, InfraredSceneEditMove); consumed = true; } else if(submenu_index == SubmenuIndexDeleteButton) { infrared->app_state.edit_target = InfraredEditTargetButton; diff --git a/applications/main/infrared/scenes/infrared_scene_edit_button_select.c b/applications/main/infrared/scenes/infrared_scene_edit_button_select.c index 7056a205396..5f5a1d8face 100644 --- a/applications/main/infrared/scenes/infrared_scene_edit_button_select.c +++ b/applications/main/infrared/scenes/infrared_scene_edit_button_select.c @@ -11,23 +11,9 @@ void infrared_scene_edit_button_select_on_enter(void* context) { InfraredRemote* remote = infrared->remote; InfraredAppState* app_state = &infrared->app_state; - const char* header = NULL; - switch(infrared->app_state.edit_mode) { - case InfraredEditModeRename: - header = "Rename Button:"; - break; - case InfraredEditModeDelete: - header = "Delete Button:"; - break; - case InfraredEditModeMove: - header = "Select Button to Move:"; - break; - case InfraredEditModeMoveSelectDest: - case InfraredEditModeNone: - default: - header = "Move Button Before:"; - break; - } + const char* header = infrared->app_state.edit_mode == InfraredEditModeRename ? + "Rename Button:" : + "Delete Button:"; submenu_set_header(submenu, header); const size_t button_count = infrared_remote_get_button_count(remote); @@ -40,14 +26,6 @@ void infrared_scene_edit_button_select_on_enter(void* context) { infrared_scene_edit_button_select_submenu_callback, context); } - if(infrared->app_state.edit_mode == InfraredEditModeMoveSelectDest) { - submenu_add_item( - submenu, - "-- Move to the end --", - button_count, - infrared_scene_edit_button_select_submenu_callback, - context); - } if(button_count && app_state->current_button_index != InfraredButtonIndexNone) { submenu_set_selected_item(submenu, app_state->current_button_index); app_state->current_button_index = InfraredButtonIndexNone; @@ -69,12 +47,6 @@ bool infrared_scene_edit_button_select_on_event(void* context, SceneManagerEvent scene_manager_next_scene(scene_manager, InfraredSceneEditRename); } else if(edit_mode == InfraredEditModeDelete) { scene_manager_next_scene(scene_manager, InfraredSceneEditDelete); - } else if(edit_mode == InfraredEditModeMove) { - app_state->current_button_index_move_orig = event.event; - app_state->edit_mode = InfraredEditModeMoveSelectDest; - scene_manager_next_scene(scene_manager, InfraredSceneEditButtonSelect); - } else if(edit_mode == InfraredEditModeMoveSelectDest) { - scene_manager_next_scene(scene_manager, InfraredSceneEditMove); } else { furi_assert(0); } diff --git a/applications/main/infrared/scenes/infrared_scene_edit_move.c b/applications/main/infrared/scenes/infrared_scene_edit_move.c index 69c7ec41dea..370c352dbd0 100644 --- a/applications/main/infrared/scenes/infrared_scene_edit_move.c +++ b/applications/main/infrared/scenes/infrared_scene_edit_move.c @@ -1,103 +1,44 @@ #include "../infrared_i.h" -static void infrared_scene_edit_move_dialog_result_callback(DialogExResult result, void* context) { - Infrared* infrared = context; - view_dispatcher_send_custom_event(infrared->view_dispatcher, result); +static void infrared_scene_move_button(uint32_t index_old, uint32_t index_new, void* context) { + InfraredRemote* remote = context; + furi_assert(remote); + infrared_remote_move_button(remote, index_old, index_new); +} + +static const char* infrared_scene_get_btn_name(uint32_t index, void* context) { + InfraredRemote* remote = context; + furi_assert(remote); + InfraredRemoteButton* button = infrared_remote_get_button(remote, index); + return (infrared_remote_button_get_name(button)); } void infrared_scene_edit_move_on_enter(void* context) { Infrared* infrared = context; - DialogEx* dialog_ex = infrared->dialog_ex; InfraredRemote* remote = infrared->remote; - const InfraredEditTarget edit_target = infrared->app_state.edit_target; - if(edit_target == InfraredEditTargetButton) { - int32_t current_button_index = infrared->app_state.current_button_index_move_orig; - furi_assert(current_button_index != InfraredButtonIndexNone); - - dialog_ex_set_header(dialog_ex, "Move Button?", 64, 0, AlignCenter, AlignTop); - InfraredRemoteButton* current_button = - infrared_remote_get_button(remote, current_button_index); - InfraredSignal* signal = infrared_remote_button_get_signal(current_button); - - if(infrared_signal_is_raw(signal)) { - const InfraredRawSignal* raw = infrared_signal_get_raw_signal(signal); - infrared_text_store_set( - infrared, - 0, - "%s\nRAW\n%ld samples", - infrared_remote_button_get_name(current_button), - raw->timings_size); - - } else { - const InfraredMessage* message = infrared_signal_get_message(signal); - infrared_text_store_set( - infrared, - 0, - "%s\n%s\nA=0x%0*lX C=0x%0*lX", - infrared_remote_button_get_name(current_button), - infrared_get_protocol_name(message->protocol), - ROUND_UP_TO(infrared_get_protocol_address_length(message->protocol), 4), - message->address, - ROUND_UP_TO(infrared_get_protocol_command_length(message->protocol), 4), - message->command); - } - } else { - furi_assert(0); - } + infrared_move_view_set_callback(infrared->move_view, infrared_scene_move_button); - dialog_ex_set_text(dialog_ex, infrared->text_store[0], 64, 31, AlignCenter, AlignCenter); - dialog_ex_set_icon(dialog_ex, 0, 0, NULL); - dialog_ex_set_left_button_text(dialog_ex, "Cancel"); - dialog_ex_set_right_button_text(dialog_ex, "Move"); - dialog_ex_set_result_callback(dialog_ex, infrared_scene_edit_move_dialog_result_callback); - dialog_ex_set_context(dialog_ex, context); + uint32_t btn_count = infrared_remote_get_button_count(remote); + infrared_move_view_list_init( + infrared->move_view, btn_count, infrared_scene_get_btn_name, remote); + infrared_move_view_list_update(infrared->move_view); - view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewDialogEx); + view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewMove); } bool infrared_scene_edit_move_on_event(void* context, SceneManagerEvent event) { Infrared* infrared = context; - SceneManager* scene_manager = infrared->scene_manager; bool consumed = false; - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == DialogExResultLeft) { - scene_manager_previous_scene(scene_manager); - consumed = true; - } else if(event.event == DialogExResultRight) { - bool success = false; - InfraredRemote* remote = infrared->remote; - InfraredAppState* app_state = &infrared->app_state; - const InfraredEditTarget edit_target = app_state->edit_target; - - if(edit_target == InfraredEditTargetButton) { - furi_assert(app_state->current_button_index != InfraredButtonIndexNone); - success = infrared_remote_move_button( - remote, - app_state->current_button_index_move_orig, - app_state->current_button_index); - app_state->current_button_index_move_orig = InfraredButtonIndexNone; - app_state->current_button_index = InfraredButtonIndexNone; - } else { - furi_assert(0); - } - - if(success) { - scene_manager_next_scene(scene_manager, InfraredSceneEditMoveDone); - } else { - const uint32_t possible_scenes[] = {InfraredSceneRemoteList, InfraredSceneStart}; - scene_manager_search_and_switch_to_previous_scene_one_of( - scene_manager, possible_scenes, COUNT_OF(possible_scenes)); - } - consumed = true; - } - } + UNUSED(event); + UNUSED(infrared); return consumed; } void infrared_scene_edit_move_on_exit(void* context) { Infrared* infrared = context; - UNUSED(infrared); + InfraredRemote* remote = infrared->remote; + infrared_remote_store(remote); } diff --git a/applications/main/infrared/scenes/infrared_scene_edit_move_done.c b/applications/main/infrared/scenes/infrared_scene_edit_move_done.c deleted file mode 100644 index 9f9b4b80d3e..00000000000 --- a/applications/main/infrared/scenes/infrared_scene_edit_move_done.c +++ /dev/null @@ -1,48 +0,0 @@ -#include "../infrared_i.h" - -void infrared_scene_edit_move_done_on_enter(void* context) { - Infrared* infrared = context; - Popup* popup = infrared->popup; - - popup_set_icon(popup, 0, 2, &I_DolphinMafia_115x62); - popup_set_header(popup, "Moved", 83, 19, AlignLeft, AlignBottom); - - popup_set_callback(popup, infrared_popup_closed_callback); - popup_set_context(popup, context); - popup_set_timeout(popup, 1500); - popup_enable_timeout(popup); - - view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewPopup); -} - -bool infrared_scene_edit_move_done_on_event(void* context, SceneManagerEvent event) { - Infrared* infrared = context; - SceneManager* scene_manager = infrared->scene_manager; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == InfraredCustomEventTypePopupClosed) { - const InfraredEditTarget edit_target = infrared->app_state.edit_target; - if(edit_target == InfraredEditTargetButton) { - scene_manager_search_and_switch_to_previous_scene( - scene_manager, InfraredSceneRemote); - } else if(edit_target == InfraredEditTargetRemote) { - const uint32_t possible_scenes[] = {InfraredSceneStart, InfraredSceneRemoteList}; - if(!scene_manager_search_and_switch_to_previous_scene_one_of( - scene_manager, possible_scenes, COUNT_OF(possible_scenes))) { - view_dispatcher_stop(infrared->view_dispatcher); - } - } else { - furi_assert(0); - } - consumed = true; - } - } - - return consumed; -} - -void infrared_scene_edit_move_done_on_exit(void* context) { - Infrared* infrared = context; - UNUSED(infrared); -} diff --git a/applications/main/infrared/views/infrared_move_view.c b/applications/main/infrared/views/infrared_move_view.c new file mode 100644 index 00000000000..d838a5f828f --- /dev/null +++ b/applications/main/infrared/views/infrared_move_view.c @@ -0,0 +1,215 @@ +#include "infrared_move_view.h" + +#include +#include + +#include +#include + +#define LIST_ITEMS 4U +#define LIST_LINE_H 13U +#define HEADER_H 12U +#define MOVE_X_OFFSET 5U + +struct InfraredMoveView { + View* view; + InfraredMoveCallback move_cb; + void* cb_context; +}; + +typedef struct { + const char** btn_names; + uint32_t btn_number; + int32_t list_offset; + int32_t item_idx; + bool is_moving; + + InfraredMoveGetItemCallback get_item_cb; +} InfraredMoveViewModel; + +static void infrared_move_view_draw_callback(Canvas* canvas, void* _model) { + InfraredMoveViewModel* model = _model; + + UNUSED(model); + + canvas_set_color(canvas, ColorBlack); + canvas_set_font(canvas, FontPrimary); + elements_multiline_text_aligned( + canvas, canvas_width(canvas) / 2, 0, AlignCenter, AlignTop, "Select a button to move"); + + bool show_scrollbar = model->btn_number > LIST_ITEMS; + + canvas_set_font(canvas, FontSecondary); + + for(uint32_t i = 0; i < MIN(model->btn_number, LIST_ITEMS); i++) { + int32_t idx = CLAMP((uint32_t)(i + model->list_offset), model->btn_number, 0u); + uint8_t x_offset = (model->is_moving && model->item_idx == idx) ? MOVE_X_OFFSET : 0; + uint8_t y_offset = HEADER_H + i * LIST_LINE_H; + uint8_t box_end_x = canvas_width(canvas) - (show_scrollbar ? 6 : 1); + + canvas_set_color(canvas, ColorBlack); + if(model->item_idx == idx) { + canvas_draw_box(canvas, x_offset, y_offset, box_end_x - x_offset, LIST_LINE_H); + + canvas_set_color(canvas, ColorWhite); + canvas_draw_dot(canvas, x_offset, y_offset); + canvas_draw_dot(canvas, x_offset + 1, y_offset); + canvas_draw_dot(canvas, x_offset, y_offset + 1); + canvas_draw_dot(canvas, x_offset, y_offset + LIST_LINE_H - 1); + canvas_draw_dot(canvas, box_end_x - 1, y_offset); + canvas_draw_dot(canvas, box_end_x - 1, y_offset + LIST_LINE_H - 1); + } + canvas_draw_str_aligned( + canvas, x_offset + 3, y_offset + 3, AlignLeft, AlignTop, model->btn_names[idx]); + } + + if(show_scrollbar) { + elements_scrollbar_pos( + canvas, + canvas_width(canvas), + HEADER_H, + canvas_height(canvas) - HEADER_H, + model->item_idx, + model->btn_number); + } +} + +static void update_list_offset(InfraredMoveViewModel* model) { + int32_t bounds = model->btn_number > (LIST_ITEMS - 1) ? 2 : model->btn_number; + + if((model->btn_number > (LIST_ITEMS - 1)) && + (model->item_idx >= ((int32_t)model->btn_number - 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->btn_number - bounds, 0); + } else if(model->list_offset > model->item_idx - bounds) { + model->list_offset = CLAMP(model->item_idx - 1, (int32_t)model->btn_number - bounds, 0); + } +} + +static bool infrared_move_view_input_callback(InputEvent* event, void* context) { + InfraredMoveView* move_view = context; + + bool consumed = false; + + if(((event->type == InputTypeShort || event->type == InputTypeRepeat)) && + ((event->key == InputKeyUp) || (event->key == InputKeyDown))) { + bool is_moving = false; + uint32_t index_old = 0; + uint32_t index_new = 0; + with_view_model( + move_view->view, + InfraredMoveViewModel * model, + { + is_moving = model->is_moving; + index_old = model->item_idx; + if(event->key == InputKeyUp) { + if(model->item_idx <= 0) { + model->item_idx = model->btn_number; + } + model->item_idx--; + } else if(event->key == InputKeyDown) { + model->item_idx++; + if(model->item_idx >= (int32_t)(model->btn_number)) { + model->item_idx = 0; + } + } + index_new = model->item_idx; + update_list_offset(model); + }, + !is_moving); + if((is_moving) && (move_view->move_cb)) { + move_view->move_cb(index_old, index_new, move_view->cb_context); + infrared_move_view_list_update(move_view); + } + consumed = true; + } + + if((event->key == InputKeyOk) && (event->type == InputTypeShort)) { + with_view_model( + move_view->view, + InfraredMoveViewModel * model, + { model->is_moving = !(model->is_moving); }, + true); + consumed = true; + } + return consumed; +} + +static void infrared_move_view_on_exit(void* context) { + furi_assert(context); + InfraredMoveView* move_view = context; + + with_view_model( + move_view->view, + InfraredMoveViewModel * model, + { + if(model->btn_names) { + free(model->btn_names); + model->btn_names = NULL; + } + model->btn_number = 0; + model->get_item_cb = NULL; + }, + false); + move_view->cb_context = NULL; +} + +void infrared_move_view_set_callback(InfraredMoveView* move_view, InfraredMoveCallback callback) { + furi_assert(move_view); + move_view->move_cb = callback; +} + +void infrared_move_view_list_init( + InfraredMoveView* move_view, + uint32_t item_count, + InfraredMoveGetItemCallback load_cb, + void* context) { + furi_assert(move_view); + move_view->cb_context = context; + with_view_model( + move_view->view, + InfraredMoveViewModel * model, + { + furi_assert(model->btn_names == NULL); + model->btn_names = malloc(sizeof(char*) * item_count); + model->btn_number = item_count; + model->get_item_cb = load_cb; + }, + false); +} + +void infrared_move_view_list_update(InfraredMoveView* move_view) { + furi_assert(move_view); + with_view_model( + move_view->view, + InfraredMoveViewModel * model, + { + for(uint32_t i = 0; i < model->btn_number; i++) { + if(!model->get_item_cb) break; + model->btn_names[i] = model->get_item_cb(i, move_view->cb_context); + } + }, + true); +} + +InfraredMoveView* infrared_move_view_alloc(void) { + InfraredMoveView* move_view = malloc(sizeof(InfraredMoveView)); + move_view->view = view_alloc(); + view_allocate_model(move_view->view, ViewModelTypeLocking, sizeof(InfraredMoveViewModel)); + view_set_draw_callback(move_view->view, infrared_move_view_draw_callback); + view_set_input_callback(move_view->view, infrared_move_view_input_callback); + view_set_exit_callback(move_view->view, infrared_move_view_on_exit); + view_set_context(move_view->view, move_view); + return move_view; +} + +void infrared_move_view_free(InfraredMoveView* move_view) { + view_free(move_view->view); + free(move_view); +} + +View* infrared_move_view_get_view(InfraredMoveView* move_view) { + return move_view->view; +} diff --git a/applications/main/infrared/views/infrared_move_view.h b/applications/main/infrared/views/infrared_move_view.h new file mode 100644 index 00000000000..b9b0cd864a5 --- /dev/null +++ b/applications/main/infrared/views/infrared_move_view.h @@ -0,0 +1,25 @@ +#pragma once + +#include + +typedef struct InfraredMoveView InfraredMoveView; + +typedef void (*InfraredMoveCallback)(uint32_t index_old, uint32_t index_new, void* context); + +typedef const char* (*InfraredMoveGetItemCallback)(uint32_t index, void* context); + +InfraredMoveView* infrared_move_view_alloc(void); + +void infrared_move_view_free(InfraredMoveView* debug_view); + +View* infrared_move_view_get_view(InfraredMoveView* debug_view); + +void infrared_move_view_set_callback(InfraredMoveView* move_view, InfraredMoveCallback callback); + +void infrared_move_view_list_init( + InfraredMoveView* move_view, + uint32_t item_count, + InfraredMoveGetItemCallback load_cb, + void* context); + +void infrared_move_view_list_update(InfraredMoveView* move_view); \ No newline at end of file From 1e4af1d550ee21f848cb0f34bbe9c9dfcbbc794f Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Mon, 7 Aug 2023 19:28:47 +0900 Subject: [PATCH 692/824] FDX-B temperature in system units (#2941) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * FDX-B temperature now uses system units * LF-RFID: wrap floats in fdx-b temperature conversion Co-authored-by: あく --- lib/lfrfid/protocols/protocol_fdx_b.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/lfrfid/protocols/protocol_fdx_b.c b/lib/lfrfid/protocols/protocol_fdx_b.c index dd54cffb060..04386a67528 100644 --- a/lib/lfrfid/protocols/protocol_fdx_b.c +++ b/lib/lfrfid/protocols/protocol_fdx_b.c @@ -4,6 +4,7 @@ #include #include #include "lfrfid_protocols.h" +#include #define FDX_B_ENCODED_BIT_SIZE (128) #define FDX_B_ENCODED_BYTE_SIZE (((FDX_B_ENCODED_BIT_SIZE) / 8)) @@ -323,8 +324,12 @@ void protocol_fdx_b_render_brief_data(ProtocolFDXB* protocol, FuriString* result float temperature; if(protocol_fdx_b_get_temp(protocol->data, &temperature)) { - float temperature_c = (temperature - 32) / 1.8; - furi_string_cat_printf(result, "T: %.2fC", (double)temperature_c); + if(furi_hal_rtc_get_locale_units() == FuriHalRtcLocaleUnitsMetric) { + float temperature_c = (temperature - 32.0f) / 1.8f; + furi_string_cat_printf(result, "T: %.2fC", (double)temperature_c); + } else { + furi_string_cat_printf(result, "T: %.2fF", (double)temperature); + } } else { furi_string_cat_printf(result, "T: ---"); } From ab006361ccdbdb090a41b137184f5ced15f81992 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Tue, 8 Aug 2023 15:35:51 +0900 Subject: [PATCH 693/824] Update the application catalog link in the readme (#2959) --- ReadMe.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ReadMe.md b/ReadMe.md index b60d66fbdd8..bbaf9603e28 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -26,7 +26,7 @@ If you've found an issue and want to report it, please check our [Issues](https: ## I want to contribute code -Before opening a PR, please confirm that your changes must be contained in the firmware. Many ideas can easily be implemented as external applications and published in the Flipper Application Catalog (coming soon). If you are unsure, reach out to us on the [Discord Server](https://flipp.dev/discord) or the [Issues](https://github.com/flipperdevices/flipperzero-firmware/issues) page, and we'll help you find the right place for your code. +Before opening a PR, please confirm that your changes must be contained in the firmware. Many ideas can easily be implemented as external applications and published in the [Flipper Application Catalog](https://github.com/flipperdevices/flipper-application-catalog). If you are unsure, reach out to us on the [Discord Server](https://flipp.dev/discord) or the [Issues](https://github.com/flipperdevices/flipperzero-firmware/issues) page, and we'll help you find the right place for your code. Also, please read our [Contribution Guide](/CONTRIBUTING.md) and our [Coding Style](/CODING_STYLE.md), and make sure your code is compatible with our [Project License](/LICENSE). From e9f1af44f2f6d1fb703a937470c2efe86bdd9e6a Mon Sep 17 00:00:00 2001 From: Derek Jamison Date: Tue, 8 Aug 2023 03:03:39 -0500 Subject: [PATCH 694/824] Fixes #2957 - subghz decode_raw (#2958) * Fixes #2957 - subghz decode_raw * SubGhz: proper decode_raw fix Co-authored-by: Aleksandr Kutuzov --- lib/subghz/subghz_file_encoder_worker.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/subghz/subghz_file_encoder_worker.c b/lib/subghz/subghz_file_encoder_worker.c index 519ff3fdcc3..3c045c26966 100644 --- a/lib/subghz/subghz_file_encoder_worker.c +++ b/lib/subghz/subghz_file_encoder_worker.c @@ -220,7 +220,9 @@ bool subghz_file_encoder_worker_start( furi_stream_buffer_reset(instance->stream); furi_string_set(instance->file_path, file_path); - instance->device = subghz_devices_get_by_name(radio_device_name); + if(radio_device_name) { + instance->device = subghz_devices_get_by_name(radio_device_name); + } instance->worker_running = true; furi_thread_start(instance->thread); From 00cdc3d1cb2a575a2004745057f3637f56c3811a Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Wed, 9 Aug 2023 00:34:54 +0300 Subject: [PATCH 695/824] [FL-3461] RPC: md5 in storage list (#2929) * Protobuf: update * Toolbox: md5 for file. Unit-Tests: test md5_calc. * Storage RPC, CLI, unit tests: use new md5_calc * Protobuf: update * RPC, StorageList: append md5 info to file * fbt: attempt to fix shallow submodule checkouts * pvs: make happy * Protobuf: update to latest release Co-authored-by: hedger Co-authored-by: hedger Co-authored-by: Aleksandr Kutuzov --- .gitmodules | 1 + applications/debug/unit_tests/rpc/rpc_test.c | 134 +++++++++++------- .../debug/unit_tests/storage/storage_test.c | 48 +++++++ applications/services/rpc/rpc_storage.c | 56 ++++---- applications/services/storage/storage_cli.c | 32 +---- assets/protobuf | 2 +- assets/unit_tests/storage/md5.txt | 1 + fbt | 2 +- furi/core/thread.c | 4 +- lib/flipper_application/elf/elf_file.c | 2 +- lib/toolbox/md5_calc.c | 44 ++++++ lib/toolbox/md5_calc.h | 16 +++ 12 files changed, 234 insertions(+), 108 deletions(-) create mode 100644 assets/unit_tests/storage/md5.txt create mode 100644 lib/toolbox/md5_calc.c create mode 100644 lib/toolbox/md5_calc.h diff --git a/.gitmodules b/.gitmodules index 671e7e2c416..4fba0483eb7 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,6 +10,7 @@ [submodule "assets/protobuf"] path = assets/protobuf url = https://github.com/flipperdevices/flipperzero-protobuf.git + shallow = false [submodule "lib/libusb_stm32"] path = lib/libusb_stm32 url = https://github.com/flipperdevices/libusb_stm32.git diff --git a/applications/debug/unit_tests/rpc/rpc_test.c b/applications/debug/unit_tests/rpc/rpc_test.c index 167266a8433..533a8a9ca6a 100644 --- a/applications/debug/unit_tests/rpc/rpc_test.c +++ b/applications/debug/unit_tests/rpc/rpc_test.c @@ -13,7 +13,7 @@ #include #include #include -#include +#include #include #include #include @@ -287,7 +287,8 @@ static void test_rpc_create_simple_message( PB_Main* message, uint16_t tag, const char* str, - uint32_t command_id) { + uint32_t command_id, + bool flag) { furi_check(message); char* str_copy = NULL; @@ -308,6 +309,7 @@ static void test_rpc_create_simple_message( break; case PB_Main_storage_list_request_tag: message->content.storage_list_request.path = str_copy; + message->content.storage_list_request.include_md5 = flag; break; case PB_Main_storage_mkdir_request_tag: message->content.storage_mkdir_request.path = str_copy; @@ -419,6 +421,7 @@ static void } mu_check(result_msg_file->size == expected_msg_file->size); mu_check(result_msg_file->type == expected_msg_file->type); + mu_assert_string_eq(expected_msg_file->md5sum, result_msg_file->md5sum); if(result_msg_file->data && result_msg_file->type != PB_Storage_File_FileType_DIR) { mu_check(!result_msg_file->data == !expected_msg_file->data); // Zlo: WTF??? @@ -430,10 +433,10 @@ static void } static void test_rpc_compare_messages(PB_Main* result, PB_Main* expected) { - mu_check(result->command_id == expected->command_id); - mu_check(result->command_status == expected->command_status); - mu_check(result->has_next == expected->has_next); - mu_check(result->which_content == expected->which_content); + mu_assert_int_eq(expected->command_id, result->command_id); + mu_assert_int_eq(expected->command_status, result->command_status); + mu_assert_int_eq(expected->has_next, result->has_next); + mu_assert_int_eq(expected->which_content, result->which_content); if(result->command_status != PB_CommandStatus_OK) { mu_check(result->which_content == PB_Main_empty_tag); } @@ -573,10 +576,15 @@ static void static void test_rpc_storage_list_create_expected_list( MsgList_t msg_list, const char* path, - uint32_t command_id) { + uint32_t command_id, + bool append_md5) { Storage* fs_api = furi_record_open(RECORD_STORAGE); File* dir = storage_file_alloc(fs_api); + FuriString* md5 = furi_string_alloc(); + FuriString* md5_path = furi_string_alloc(); + File* file = storage_file_alloc(fs_api); + PB_Main response = { .command_id = command_id, .has_next = false, @@ -614,6 +622,17 @@ static void test_rpc_storage_list_create_expected_list( list->file[i].data = NULL; /* memory free inside rpc_encode_and_send() -> pb_release() */ list->file[i].name = name; + + if(append_md5 && !file_info_is_dir(&fileinfo)) { + furi_string_printf(md5_path, "%s/%s", path, name); + + if(md5_string_calc_file(file, furi_string_get_cstr(md5_path), md5, NULL)) { + char* md5sum = list->file[i].md5sum; + size_t md5sum_size = sizeof(list->file[i].md5sum); + snprintf(md5sum, md5sum_size, "%s", furi_string_get_cstr(md5)); + } + } + ++i; } } else { @@ -626,6 +645,10 @@ static void test_rpc_storage_list_create_expected_list( response.has_next = false; MsgList_push_back(msg_list, response); + furi_string_free(md5); + furi_string_free(md5_path); + storage_file_free(file); + storage_dir_close(dir); storage_file_free(dir); @@ -675,16 +698,17 @@ static void test_rpc_free_msg_list(MsgList_t msg_list) { MsgList_clear(msg_list); } -static void test_rpc_storage_list_run(const char* path, uint32_t command_id) { +static void test_rpc_storage_list_run(const char* path, uint32_t command_id, bool md5) { PB_Main request; MsgList_t expected_msg_list; MsgList_init(expected_msg_list); - test_rpc_create_simple_message(&request, PB_Main_storage_list_request_tag, path, command_id); + test_rpc_create_simple_message( + &request, PB_Main_storage_list_request_tag, path, command_id, md5); if(!strcmp(path, "/")) { test_rpc_storage_list_create_expected_list_root(expected_msg_list, command_id); } else { - test_rpc_storage_list_create_expected_list(expected_msg_list, path, command_id); + test_rpc_storage_list_create_expected_list(expected_msg_list, path, command_id, md5); } test_rpc_encode_and_feed_one(&request, 0); test_rpc_decode_and_compare(expected_msg_list, 0); @@ -694,15 +718,25 @@ static void test_rpc_storage_list_run(const char* path, uint32_t command_id) { } MU_TEST(test_storage_list) { - test_rpc_storage_list_run("/", ++command_id); - test_rpc_storage_list_run(EXT_PATH("nfc"), ++command_id); - - test_rpc_storage_list_run(STORAGE_INT_PATH_PREFIX, ++command_id); - test_rpc_storage_list_run(STORAGE_EXT_PATH_PREFIX, ++command_id); - test_rpc_storage_list_run(EXT_PATH("infrared"), ++command_id); - test_rpc_storage_list_run(EXT_PATH("ibutton"), ++command_id); - test_rpc_storage_list_run(EXT_PATH("lfrfid"), ++command_id); - test_rpc_storage_list_run("error_path", ++command_id); + test_rpc_storage_list_run("/", ++command_id, false); + test_rpc_storage_list_run(EXT_PATH("nfc"), ++command_id, false); + test_rpc_storage_list_run(STORAGE_INT_PATH_PREFIX, ++command_id, false); + test_rpc_storage_list_run(STORAGE_EXT_PATH_PREFIX, ++command_id, false); + test_rpc_storage_list_run(EXT_PATH("infrared"), ++command_id, false); + test_rpc_storage_list_run(EXT_PATH("ibutton"), ++command_id, false); + test_rpc_storage_list_run(EXT_PATH("lfrfid"), ++command_id, false); + test_rpc_storage_list_run("error_path", ++command_id, false); +} + +MU_TEST(test_storage_list_md5) { + test_rpc_storage_list_run("/", ++command_id, true); + test_rpc_storage_list_run(EXT_PATH("nfc"), ++command_id, true); + test_rpc_storage_list_run(STORAGE_INT_PATH_PREFIX, ++command_id, true); + test_rpc_storage_list_run(STORAGE_EXT_PATH_PREFIX, ++command_id, true); + test_rpc_storage_list_run(EXT_PATH("infrared"), ++command_id, true); + test_rpc_storage_list_run(EXT_PATH("ibutton"), ++command_id, true); + test_rpc_storage_list_run(EXT_PATH("lfrfid"), ++command_id, true); + test_rpc_storage_list_run("error_path", ++command_id, true); } static void @@ -770,7 +804,8 @@ static void test_storage_read_run(const char* path, uint32_t command_id) { MsgList_init(expected_msg_list); test_rpc_add_read_to_list_by_reading_real_file(expected_msg_list, path, command_id); - test_rpc_create_simple_message(&request, PB_Main_storage_read_request_tag, path, command_id); + test_rpc_create_simple_message( + &request, PB_Main_storage_read_request_tag, path, command_id, false); test_rpc_encode_and_feed_one(&request, 0); test_rpc_decode_and_compare(expected_msg_list, 0); @@ -824,7 +859,8 @@ static void test_rpc_storage_info_run(const char* path, uint32_t command_id) { MsgList_t expected_msg_list; MsgList_init(expected_msg_list); - test_rpc_create_simple_message(&request, PB_Main_storage_info_request_tag, path, command_id); + test_rpc_create_simple_message( + &request, PB_Main_storage_info_request_tag, path, command_id, false); PB_Main* response = MsgList_push_new(expected_msg_list); response->command_id = command_id; @@ -856,7 +892,8 @@ static void test_rpc_storage_stat_run(const char* path, uint32_t command_id) { MsgList_t expected_msg_list; MsgList_init(expected_msg_list); - test_rpc_create_simple_message(&request, PB_Main_storage_stat_request_tag, path, command_id); + test_rpc_create_simple_message( + &request, PB_Main_storage_stat_request_tag, path, command_id, false); Storage* fs_api = furi_record_open(RECORD_STORAGE); FileInfo fileinfo; @@ -968,7 +1005,11 @@ static void test_storage_write_read_run( test_rpc_add_empty_to_list(expected_msg_list, PB_CommandStatus_OK, *command_id); test_rpc_create_simple_message( - MsgList_push_raw(input_msg_list), PB_Main_storage_read_request_tag, path, ++*command_id); + MsgList_push_raw(input_msg_list), + PB_Main_storage_read_request_tag, + path, + ++*command_id, + false); test_rpc_add_read_or_write_to_list( expected_msg_list, READ_RESPONSE, @@ -1041,7 +1082,8 @@ MU_TEST(test_storage_interrupt_continuous_same_system) { MsgList_push_new(input_msg_list), PB_Main_storage_mkdir_request_tag, TEST_DIR "dir1", - command_id + 1); + command_id + 1, + false); test_rpc_add_read_or_write_to_list( input_msg_list, WRITE_REQUEST, @@ -1121,7 +1163,8 @@ static void test_storage_delete_run( MsgList_t expected_msg_list; MsgList_init(expected_msg_list); - test_rpc_create_simple_message(&request, PB_Main_storage_delete_request_tag, path, command_id); + test_rpc_create_simple_message( + &request, PB_Main_storage_delete_request_tag, path, command_id, false); request.content.storage_delete_request.recursive = recursive; test_rpc_add_empty_to_list(expected_msg_list, status, command_id); @@ -1202,7 +1245,8 @@ static void test_storage_mkdir_run(const char* path, size_t command_id, PB_Comma MsgList_t expected_msg_list; MsgList_init(expected_msg_list); - test_rpc_create_simple_message(&request, PB_Main_storage_mkdir_request_tag, path, command_id); + test_rpc_create_simple_message( + &request, PB_Main_storage_mkdir_request_tag, path, command_id, false); test_rpc_add_empty_to_list(expected_msg_list, status, command_id); test_rpc_encode_and_feed_one(&request, 0); @@ -1229,33 +1273,15 @@ MU_TEST(test_storage_mkdir) { static void test_storage_calculate_md5sum(const char* path, char* md5sum, size_t md5sum_size) { Storage* api = furi_record_open(RECORD_STORAGE); File* file = storage_file_alloc(api); + FuriString* md5 = furi_string_alloc(); - if(storage_file_open(file, path, FSAM_READ, FSOM_OPEN_EXISTING)) { - const uint16_t once_read_size = 512; - const uint8_t hash_size = MD5SUM_SIZE; - uint8_t* data = malloc(once_read_size); - uint8_t* hash = malloc(sizeof(uint8_t) * hash_size); - md5_context* md5_ctx = malloc(sizeof(md5_context)); - - md5_starts(md5_ctx); - while(true) { - uint16_t read_size = storage_file_read(file, data, once_read_size); - if(read_size == 0) break; - md5_update(md5_ctx, data, read_size); - } - md5_finish(md5_ctx, hash); - free(md5_ctx); - - for(uint8_t i = 0; i < hash_size; i++) { - md5sum += snprintf(md5sum, md5sum_size, "%02x", hash[i]); - } - - free(hash); - free(data); + if(md5_string_calc_file(file, path, md5, NULL)) { + snprintf(md5sum, md5sum_size, "%s", furi_string_get_cstr(md5)); } else { furi_check(0); } + furi_string_free(md5); storage_file_close(file); storage_file_free(file); @@ -1271,11 +1297,12 @@ static void test_storage_md5sum_run( MsgList_t expected_msg_list; MsgList_init(expected_msg_list); - test_rpc_create_simple_message(&request, PB_Main_storage_md5sum_request_tag, path, command_id); + test_rpc_create_simple_message( + &request, PB_Main_storage_md5sum_request_tag, path, command_id, false); if(status == PB_CommandStatus_OK) { PB_Main* response = MsgList_push_new(expected_msg_list); test_rpc_create_simple_message( - response, PB_Main_storage_md5sum_response_tag, md5sum, command_id); + response, PB_Main_storage_md5sum_response_tag, md5sum, command_id, false); response->command_status = status; } else { test_rpc_add_empty_to_list(expected_msg_list, status, command_id); @@ -1433,6 +1460,7 @@ MU_TEST_SUITE(test_rpc_storage) { MU_RUN_TEST(test_storage_info); MU_RUN_TEST(test_storage_stat); MU_RUN_TEST(test_storage_list); + MU_RUN_TEST(test_storage_list_md5); MU_RUN_TEST(test_storage_read); MU_RUN_TEST(test_storage_write_read); MU_RUN_TEST(test_storage_write); @@ -1731,7 +1759,8 @@ MU_TEST(test_rpc_multisession_storage) { MsgList_push_raw(input_0), PB_Main_storage_read_request_tag, TEST_DIR "file0.txt", - ++command_id); + ++command_id, + false); test_rpc_add_read_or_write_to_list( expected_0, READ_RESPONSE, TEST_DIR "file0.txt", pattern, sizeof(pattern), 1, command_id); @@ -1739,7 +1768,8 @@ MU_TEST(test_rpc_multisession_storage) { MsgList_push_raw(input_1), PB_Main_storage_read_request_tag, TEST_DIR "file1.txt", - ++command_id); + ++command_id, + false); test_rpc_add_read_or_write_to_list( expected_1, READ_RESPONSE, TEST_DIR "file1.txt", pattern, sizeof(pattern), 1, command_id); diff --git a/applications/debug/unit_tests/storage/storage_test.c b/applications/debug/unit_tests/storage/storage_test.c index f0b45c598c8..13188e5e0fc 100644 --- a/applications/debug/unit_tests/storage/storage_test.c +++ b/applications/debug/unit_tests/storage/storage_test.c @@ -582,6 +582,49 @@ MU_TEST(test_storage_common_migrate) { furi_record_close(RECORD_STORAGE); } +#define MD5_HASH_SIZE (16) +#include + +MU_TEST(test_md5_calc) { + Storage* storage = furi_record_open(RECORD_STORAGE); + File* file = storage_file_alloc(storage); + + const char* path = UNIT_TESTS_PATH("storage/md5.txt"); + const char* md5_cstr = "2a456fa43e75088fdde41c93159d62a2"; + const uint8_t md5[MD5_HASH_SIZE] = { + 0x2a, + 0x45, + 0x6f, + 0xa4, + 0x3e, + 0x75, + 0x08, + 0x8f, + 0xdd, + 0xe4, + 0x1c, + 0x93, + 0x15, + 0x9d, + 0x62, + 0xa2, + }; + + uint8_t md5_output[MD5_HASH_SIZE]; + FuriString* md5_output_str = furi_string_alloc(); + memset(md5_output, 0, MD5_HASH_SIZE); + + mu_check(md5_calc_file(file, path, md5_output, NULL)); + mu_check(md5_string_calc_file(file, path, md5_output_str, NULL)); + + mu_assert_mem_eq(md5, md5_output, MD5_HASH_SIZE); + mu_assert_string_eq(md5_cstr, furi_string_get_cstr(md5_output_str)); + + storage_file_free(file); + furi_string_free(md5_output_str); + furi_record_close(RECORD_STORAGE); +} + MU_TEST_SUITE(test_data_path) { MU_RUN_TEST(test_storage_data_path); MU_RUN_TEST(test_storage_data_path_apps); @@ -591,11 +634,16 @@ MU_TEST_SUITE(test_storage_common) { MU_RUN_TEST(test_storage_common_migrate); } +MU_TEST_SUITE(test_md5_calc_suite) { + MU_RUN_TEST(test_md5_calc); +} + int run_minunit_test_storage() { MU_RUN_SUITE(storage_file); MU_RUN_SUITE(storage_dir); MU_RUN_SUITE(storage_rename); MU_RUN_SUITE(test_data_path); MU_RUN_SUITE(test_storage_common); + MU_RUN_SUITE(test_md5_calc_suite); return MU_EXIT_CODE; } diff --git a/applications/services/rpc/rpc_storage.c b/applications/services/rpc/rpc_storage.c index c3a4a047049..93c7043e8a9 100644 --- a/applications/services/rpc/rpc_storage.c +++ b/applications/services/rpc/rpc_storage.c @@ -9,7 +9,7 @@ #include "storage/filesystem_api_defines.h" #include "storage/storage.h" #include -#include +#include #include #include @@ -271,6 +271,11 @@ static void rpc_system_storage_list_process(const PB_Main* request, void* contex }; PB_Storage_ListResponse* list = &response.content.storage_list_response; + bool include_md5 = request->content.storage_list_request.include_md5; + FuriString* md5 = furi_string_alloc(); + FuriString* md5_path = furi_string_alloc(); + File* file = storage_file_alloc(fs_api); + bool finish = false; int i = 0; @@ -296,6 +301,21 @@ static void rpc_system_storage_list_process(const PB_Main* request, void* contex list->file[i].size = fileinfo.size; list->file[i].data = NULL; list->file[i].name = name; + + if(include_md5 && !file_info_is_dir(&fileinfo)) { + furi_string_printf( //-V576 + md5_path, + "%s/%s", + request->content.storage_list_request.path, + name); + + if(md5_string_calc_file(file, furi_string_get_cstr(md5_path), md5, NULL)) { + char* md5sum = list->file[i].md5sum; + size_t md5sum_size = sizeof(list->file[i].md5sum); + snprintf(md5sum, md5sum_size, "%s", furi_string_get_cstr(md5)); + } + } + ++i; } else { free(name); @@ -310,8 +330,11 @@ static void rpc_system_storage_list_process(const PB_Main* request, void* contex response.has_next = false; rpc_send_and_release(session, &response); + furi_string_free(md5); + furi_string_free(md5_path); storage_dir_close(dir); storage_file_free(dir); + storage_file_free(file); furi_record_close(RECORD_STORAGE); } @@ -569,23 +592,10 @@ static void rpc_system_storage_md5sum_process(const PB_Main* request, void* cont Storage* fs_api = furi_record_open(RECORD_STORAGE); File* file = storage_file_alloc(fs_api); + FuriString* md5 = furi_string_alloc(); + FS_Error file_error; - if(storage_file_open(file, filename, FSAM_READ, FSOM_OPEN_EXISTING)) { - const uint16_t size_to_read = 512; - const uint8_t hash_size = 16; - uint8_t* data = malloc(size_to_read); - uint8_t* hash = malloc(sizeof(uint8_t) * hash_size); - md5_context* md5_ctx = malloc(sizeof(md5_context)); - - md5_starts(md5_ctx); - while(true) { - uint16_t read_size = storage_file_read(file, data, size_to_read); - if(read_size == 0) break; - md5_update(md5_ctx, data, read_size); - } - md5_finish(md5_ctx, hash); - free(md5_ctx); - + if(md5_string_calc_file(file, filename, md5, &file_error)) { PB_Main response = { .command_id = request->command_id, .command_status = PB_CommandStatus_OK, @@ -595,21 +605,15 @@ static void rpc_system_storage_md5sum_process(const PB_Main* request, void* cont char* md5sum = response.content.storage_md5sum_response.md5sum; size_t md5sum_size = sizeof(response.content.storage_md5sum_response.md5sum); - (void)md5sum_size; - furi_assert(hash_size <= ((md5sum_size - 1) / 2)); //-V547 - for(uint8_t i = 0; i < hash_size; i++) { - md5sum += snprintf(md5sum, md5sum_size, "%02x", hash[i]); - } + snprintf(md5sum, md5sum_size, "%s", furi_string_get_cstr(md5)); - free(hash); - free(data); - storage_file_close(file); rpc_send_and_release(session, &response); } else { rpc_send_and_release_empty( - session, request->command_id, rpc_system_storage_get_file_error(file)); + session, request->command_id, rpc_system_storage_get_error(file_error)); } + furi_string_free(md5); storage_file_free(file); furi_record_close(RECORD_STORAGE); diff --git a/applications/services/storage/storage_cli.c b/applications/services/storage/storage_cli.c index 8e2dcdbbbd3..74bcf2d92af 100644 --- a/applications/services/storage/storage_cli.c +++ b/applications/services/storage/storage_cli.c @@ -3,7 +3,7 @@ #include #include -#include +#include #include #include #include @@ -482,34 +482,16 @@ static void storage_cli_md5(Cli* cli, FuriString* path) { UNUSED(cli); Storage* api = furi_record_open(RECORD_STORAGE); File* file = storage_file_alloc(api); + FuriString* md5 = furi_string_alloc(); + FS_Error file_error; - if(storage_file_open(file, furi_string_get_cstr(path), FSAM_READ, FSOM_OPEN_EXISTING)) { - const uint16_t buffer_size = 512; - const uint8_t hash_size = 16; - uint8_t* data = malloc(buffer_size); - uint8_t* hash = malloc(sizeof(uint8_t) * hash_size); - md5_context* md5_ctx = malloc(sizeof(md5_context)); - - md5_starts(md5_ctx); - while(true) { - uint16_t read_size = storage_file_read(file, data, buffer_size); - if(read_size == 0) break; - md5_update(md5_ctx, data, read_size); - } - md5_finish(md5_ctx, hash); - free(md5_ctx); - - for(uint8_t i = 0; i < hash_size; i++) { - printf("%02x", hash[i]); - } - printf("\r\n"); - - free(hash); - free(data); + if(md5_string_calc_file(file, furi_string_get_cstr(path), md5, &file_error)) { + printf("%s\r\n", furi_string_get_cstr(md5)); } else { - storage_cli_print_error(storage_file_get_error(file)); + storage_cli_print_error(file_error); } + furi_string_free(md5); storage_file_close(file); storage_file_free(file); diff --git a/assets/protobuf b/assets/protobuf index 08a907d9573..7e011a95863 160000 --- a/assets/protobuf +++ b/assets/protobuf @@ -1 +1 @@ -Subproject commit 08a907d95733600becc41c0602ef5ee4c4baf782 +Subproject commit 7e011a95863716e72e7c6b5d552bca241d688304 diff --git a/assets/unit_tests/storage/md5.txt b/assets/unit_tests/storage/md5.txt new file mode 100644 index 00000000000..777e390be8c --- /dev/null +++ b/assets/unit_tests/storage/md5.txt @@ -0,0 +1 @@ +Yo dawg, I heard you like md5... \ No newline at end of file diff --git a/fbt b/fbt index ef41cc056b6..471285a76c1 100755 --- a/fbt +++ b/fbt @@ -29,7 +29,7 @@ if [ -z "$FBT_NO_SYNC" ]; then echo "\".git\" directory not found, please clone repo via \"git clone\""; exit 1; fi - git submodule update --init --depth 1 --jobs "$N_GIT_THREADS"; + git submodule update --init --jobs "$N_GIT_THREADS"; fi $SCONS_EP $SCONS_DEFAULT_FLAGS "$@" diff --git a/furi/core/thread.c b/furi/core/thread.c index 657b867d1e7..de50bde7a47 100644 --- a/furi/core/thread.c +++ b/furi/core/thread.c @@ -92,10 +92,10 @@ static void furi_thread_body(void* context) { if(thread->heap_trace_enabled == true) { furi_delay_ms(33); thread->heap_size = memmgr_heap_get_thread_memory((FuriThreadId)task_handle); - furi_log_print_format( //-V576 + furi_log_print_format( thread->heap_size ? FuriLogLevelError : FuriLogLevelInfo, TAG, - "%s allocation balance: %u", + "%s allocation balance: %zu", thread->name ? thread->name : "Thread", thread->heap_size); memmgr_heap_disable_thread_trace((FuriThreadId)task_handle); diff --git a/lib/flipper_application/elf/elf_file.c b/lib/flipper_application/elf/elf_file.c index fc9dd06ba81..539a48c85a7 100644 --- a/lib/flipper_application/elf/elf_file.c +++ b/lib/flipper_application/elf/elf_file.c @@ -892,7 +892,7 @@ ELFFileLoadStatus elf_file_load_sections(ELFFile* elf) { ELFSectionDict_itref_t* itref = ELFSectionDict_ref(it); total_size += itref->value.size; } - FURI_LOG_I(TAG, "Total size of loaded sections: %u", total_size); //-V576 + FURI_LOG_I(TAG, "Total size of loaded sections: %zu", total_size); } return status; diff --git a/lib/toolbox/md5_calc.c b/lib/toolbox/md5_calc.c new file mode 100644 index 00000000000..b050295a147 --- /dev/null +++ b/lib/toolbox/md5_calc.c @@ -0,0 +1,44 @@ +#include "md5.h" +#include "md5_calc.h" + +bool md5_calc_file(File* file, const char* path, unsigned char output[16], FS_Error* file_error) { + bool result = storage_file_open(file, path, FSAM_READ, FSOM_OPEN_EXISTING); + + if(result) { + const uint16_t size_to_read = 512; + uint8_t* data = malloc(size_to_read); + md5_context* md5_ctx = malloc(sizeof(md5_context)); + + md5_starts(md5_ctx); + while(true) { + uint16_t read_size = storage_file_read(file, data, size_to_read); + if(read_size == 0) break; + md5_update(md5_ctx, data, read_size); + } + md5_finish(md5_ctx, output); + free(md5_ctx); + free(data); + } + + if(file_error != NULL) { + *file_error = storage_file_get_error(file); + } + + storage_file_close(file); + return result; +} + +bool md5_string_calc_file(File* file, const char* path, FuriString* output, FS_Error* file_error) { + const size_t hash_size = 16; + unsigned char hash[hash_size]; + bool result = md5_calc_file(file, path, hash, file_error); + + if(result) { + furi_string_set(output, ""); + for(size_t i = 0; i < hash_size; i++) { + furi_string_cat_printf(output, "%02x", hash[i]); + } + } + + return result; +} \ No newline at end of file diff --git a/lib/toolbox/md5_calc.h b/lib/toolbox/md5_calc.h new file mode 100644 index 00000000000..cf82e37181c --- /dev/null +++ b/lib/toolbox/md5_calc.h @@ -0,0 +1,16 @@ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +bool md5_calc_file(File* file, const char* path, unsigned char output[16], FS_Error* file_error); + +bool md5_string_calc_file(File* file, const char* path, FuriString* output, FS_Error* file_error); + +#ifdef __cplusplus +} +#endif From 98d4309b61c063af5480d7bbc9ba2d429d35d6d6 Mon Sep 17 00:00:00 2001 From: Robin Gareus Date: Tue, 8 Aug 2023 23:44:45 +0200 Subject: [PATCH 696/824] IR Universal Audio Remote: add NAD C316BEE (#2954) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remote control "NAD Amp1" https://nad.de/product/nad-c316bee-v2-vollverstaerker/ Co-authored-by: あく --- assets/resources/infrared/assets/audio.ir | 48 +++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/assets/resources/infrared/assets/audio.ir b/assets/resources/infrared/assets/audio.ir index 3b9e626ba57..5cdc9048a89 100644 --- a/assets/resources/infrared/assets/audio.ir +++ b/assets/resources/infrared/assets/audio.ir @@ -377,3 +377,51 @@ type: parsed protocol: Kaseikyo address: A0 02 20 00 command: 20 03 00 00 +# +# Model: NAD C316BEE +name: Power +type: parsed +protocol: NECext +address: 87 7C 00 00 +command: 25 DA 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 87 7C 00 00 +command: 94 6B 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 87 7C 00 00 +command: 88 77 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 87 7C 00 00 +command: 8C 73 00 00 +# +name: Play +type: parsed +protocol: NECext +address: 87 7C 00 00 +command: 01 FE 00 00 +# +name: Pause +type: parsed +protocol: NECext +address: 87 7C 00 00 +command: 4A B5 00 00 +# +name: Prev +type: parsed +protocol: NECext +address: 87 7C 00 00 +command: 05 FA 00 00 +# +name: Next +type: parsed +protocol: NECext +address: 87 7C 00 0 From d9e931b7b7540ae81b9077cd6fefdcd9b9c84dea Mon Sep 17 00:00:00 2001 From: Alexandre L Date: Wed, 9 Aug 2023 02:46:07 +0200 Subject: [PATCH 697/824] fbt: Fix building using path with space (#2948) * fbt: Fix building on Windows using path with space * scripts: Fixed formatting --------- Co-authored-by: hedger Co-authored-by: hedger --- scripts/fbt/util.py | 4 +++- scripts/fbt_tools/fbt_assets.py | 8 ++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/scripts/fbt/util.py b/scripts/fbt/util.py index ae850a8c3c0..57e60aecfa4 100644 --- a/scripts/fbt/util.py +++ b/scripts/fbt/util.py @@ -59,7 +59,9 @@ def extract_abs_dir_path(node): if abs_dir_node is None: raise StopError(f"Can't find absolute path for {node.name}") - return abs_dir_node.abspath + # Don't return abspath attribute (type is str), it will break in + # OverrideEnvironment.subst_list() by splitting path on spaces + return abs_dir_node def path_as_posix(path): diff --git a/scripts/fbt_tools/fbt_assets.py b/scripts/fbt_tools/fbt_assets.py index 68617c2549b..b2b9310ba68 100644 --- a/scripts/fbt_tools/fbt_assets.py +++ b/scripts/fbt_tools/fbt_assets.py @@ -137,14 +137,14 @@ def generate(env): BUILDERS={ "IconBuilder": Builder( action=Action( - '${PYTHON3} "${ASSETS_COMPILER}" icons "${ABSPATHGETTERFUNC(SOURCE)}" "${TARGET.dir}" --filename ${ICON_FILE_NAME}', + '${PYTHON3} ${ASSETS_COMPILER} icons ${ABSPATHGETTERFUNC(SOURCE)} ${TARGET.dir} --filename "${ICON_FILE_NAME}"', "${ICONSCOMSTR}", ), emitter=icons_emitter, ), "ProtoBuilder": Builder( action=Action( - '${PYTHON3} "${NANOPB_COMPILER}" -q -I${SOURCE.dir.posix} -D${TARGET.dir.posix} ${SOURCES.posix}', + "${PYTHON3} ${NANOPB_COMPILER} -q -I${SOURCE.dir.posix} -D${TARGET.dir.posix} ${SOURCES.posix}", "${PROTOCOMSTR}", ), emitter=proto_emitter, @@ -153,14 +153,14 @@ def generate(env): ), "DolphinSymBuilder": Builder( action=Action( - '${PYTHON3} "${ASSETS_COMPILER}" dolphin -s dolphin_${DOLPHIN_RES_TYPE} "${SOURCE}" "${_DOLPHIN_OUT_DIR}"', + "${PYTHON3} ${ASSETS_COMPILER} dolphin -s dolphin_${DOLPHIN_RES_TYPE} ${SOURCE} ${_DOLPHIN_OUT_DIR}", "${DOLPHINCOMSTR}", ), emitter=dolphin_emitter, ), "DolphinExtBuilder": Builder( action=Action( - '${PYTHON3} "${ASSETS_COMPILER}" dolphin "${SOURCE}" "${_DOLPHIN_OUT_DIR}"', + "${PYTHON3} ${ASSETS_COMPILER} dolphin ${SOURCE} ${_DOLPHIN_OUT_DIR}", "${DOLPHINCOMSTR}", ), emitter=dolphin_emitter, From a39ef50fdbf97b8bf4f895c2bbc33beb40c743d3 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Wed, 9 Aug 2023 16:52:41 +0900 Subject: [PATCH 698/824] [FL-3433] Add compressor.h to the SDK (#2962) --- firmware/targets/f18/api_symbols.csv | 10 +++++++++- firmware/targets/f7/api_symbols.csv | 10 +++++++++- lib/toolbox/SConscript | 1 + 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index 8b04f441a8d..529477b50a5 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,34.3,, +Version,+,34.4,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -164,6 +164,7 @@ Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_utils.h,, Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_wwdg.h,, Header,+,lib/toolbox/api_lock.h,, Header,+,lib/toolbox/args.h,, +Header,+,lib/toolbox/compress.h,, Header,+,lib/toolbox/crc32_calc.h,, Header,+,lib/toolbox/dir_walk.h,, Header,+,lib/toolbox/float_tools.h,, @@ -628,6 +629,13 @@ Function,+,composite_api_resolver_add,void,"CompositeApiResolver*, const ElfApiI Function,+,composite_api_resolver_alloc,CompositeApiResolver*, Function,+,composite_api_resolver_free,void,CompositeApiResolver* Function,+,composite_api_resolver_get,const ElfApiInterface*,CompositeApiResolver* +Function,+,compress_alloc,Compress*,uint16_t +Function,+,compress_decode,_Bool,"Compress*, uint8_t*, size_t, uint8_t*, size_t, size_t*" +Function,+,compress_encode,_Bool,"Compress*, uint8_t*, size_t, uint8_t*, size_t, size_t*" +Function,+,compress_free,void,Compress* +Function,+,compress_icon_alloc,CompressIcon*, +Function,+,compress_icon_decode,void,"CompressIcon*, const uint8_t*, uint8_t**" +Function,+,compress_icon_free,void,CompressIcon* Function,-,copysign,double,"double, double" Function,-,copysignf,float,"float, float" Function,-,copysignl,long double,"long double, long double" diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index dc0720ee217..6fc4356c54f 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,34.3,, +Version,+,34.4,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -205,6 +205,7 @@ Header,+,lib/subghz/subghz_worker.h,, Header,+,lib/subghz/transmitter.h,, Header,+,lib/toolbox/api_lock.h,, Header,+,lib/toolbox/args.h,, +Header,+,lib/toolbox/compress.h,, Header,+,lib/toolbox/crc32_calc.h,, Header,+,lib/toolbox/dir_walk.h,, Header,+,lib/toolbox/float_tools.h,, @@ -689,6 +690,13 @@ Function,+,composite_api_resolver_add,void,"CompositeApiResolver*, const ElfApiI Function,+,composite_api_resolver_alloc,CompositeApiResolver*, Function,+,composite_api_resolver_free,void,CompositeApiResolver* Function,+,composite_api_resolver_get,const ElfApiInterface*,CompositeApiResolver* +Function,+,compress_alloc,Compress*,uint16_t +Function,+,compress_decode,_Bool,"Compress*, uint8_t*, size_t, uint8_t*, size_t, size_t*" +Function,+,compress_encode,_Bool,"Compress*, uint8_t*, size_t, uint8_t*, size_t, size_t*" +Function,+,compress_free,void,Compress* +Function,+,compress_icon_alloc,CompressIcon*, +Function,+,compress_icon_decode,void,"CompressIcon*, const uint8_t*, uint8_t**" +Function,+,compress_icon_free,void,CompressIcon* Function,-,copysign,double,"double, double" Function,-,copysignf,float,"float, float" Function,-,copysignl,long double,"long double, long double" diff --git a/lib/toolbox/SConscript b/lib/toolbox/SConscript index 6084969c4c5..761755d2de6 100644 --- a/lib/toolbox/SConscript +++ b/lib/toolbox/SConscript @@ -9,6 +9,7 @@ env.Append( ], SDK_HEADERS=[ File("api_lock.h"), + File("compress.h"), File("manchester_decoder.h"), File("manchester_encoder.h"), File("path.h"), From 107862577590c166aa28e4abd3ea33f2622edb2b Mon Sep 17 00:00:00 2001 From: 47LeCoste <82815207+grugnoymeme@users.noreply.github.com> Date: Wed, 9 Aug 2023 23:10:04 +0200 Subject: [PATCH 699/824] BadUSB: qFlipper install script for MacOS (#2915) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Create Install_qFlipper_macOS.txt * BadUSB: qFlipper mac install routine improvements Co-authored-by: あく --- .../resources/badusb/Install_qFlipper_macOS.txt | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 assets/resources/badusb/Install_qFlipper_macOS.txt diff --git a/assets/resources/badusb/Install_qFlipper_macOS.txt b/assets/resources/badusb/Install_qFlipper_macOS.txt new file mode 100644 index 00000000000..252954ecbdb --- /dev/null +++ b/assets/resources/badusb/Install_qFlipper_macOS.txt @@ -0,0 +1,16 @@ +ID 05ac:021e Apple:Keyboard +REM Keep these 3 lines IF (and only if) it's the first time you are performing a badKB attack against a specific macOS target. +REM In fact, it helps Flipper Zero bypass the macOS keyboard setup assistant. Otherwise the attack will not start. +REM Author: 47LeCoste +REM Version 1.0 (Flipper Ducky) +REM Target: macOS +DELAY 3000 +F4 +DELAY 2500 +STRING Terminal +DELAY 2500 +ENTER +DELAY 1500 +STRING (cd /tmp && curl -L -o qFlipper.dmg https://update.flipperzero.one/qFlipper/release/macos-amd64/dmg && hdiutil attach qFlipper.dmg && app_volume=$(ls /Volumes | grep -i "qFlipper") && (test -e /Applications/qFlipper.app && rm -rf /Applications/qFlipper.app ); cp -R "/Volumes/$app_volume/qFlipper.app" /Applications/ && hdiutil detach "/Volumes/$app_volume" && rm qFlipper.dmg && open /Applications/qFlipper.app) +DELAY 1000 +ENTER From fb63e53d9ac2c1bc53223eb2d7c0f519e828e0cc Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Thu, 10 Aug 2023 00:18:40 +0300 Subject: [PATCH 700/824] [FL-3435] External apps removed (#2934) Co-authored-by: Aleksandr Kutuzov --- .github/CODEOWNERS | 3 - .github/workflows/build.yml | 2 +- .gitmodules | 3 - .pvsoptions | 2 +- applications/ReadMe.md | 14 +- applications/external/application.fam | 6 - .../avr_isp_programmer/application.fam | 17 - .../avr_isp_programmer/avr_app_icon_10x10.png | Bin 3614 -> 0 bytes .../external/avr_isp_programmer/avr_isp_app.c | 179 --- .../avr_isp_programmer/avr_isp_app_i.c | 31 - .../avr_isp_programmer/avr_isp_app_i.h | 44 - .../avr_isp_programmer/helpers/avr_isp.c | 496 ------ .../avr_isp_programmer/helpers/avr_isp.h | 70 - .../helpers/avr_isp_event.h | 23 - .../helpers/avr_isp_types.h | 32 - .../helpers/avr_isp_worker.c | 266 ---- .../helpers/avr_isp_worker.h | 49 - .../helpers/avr_isp_worker_rw.c | 1157 -------------- .../helpers/avr_isp_worker_rw.h | 99 -- .../helpers/flipper_i32hex_file.c | 321 ---- .../helpers/flipper_i32hex_file.h | 55 - .../images/avr_app_icon_10x10.png | Bin 3614 -> 0 bytes .../avr_isp_programmer/images/avr_wiring.png | Bin 4513 -> 0 bytes .../images/chif_not_found_83x37.png | Bin 3742 -> 0 bytes .../images/chip_error_70x22.png | Bin 3688 -> 0 bytes .../images/chip_long_70x22.png | Bin 3656 -> 0 bytes .../images/chip_not_found_83x37.png | Bin 3779 -> 0 bytes .../images/dolphin_nice_96x59.png | Bin 2459 -> 0 bytes .../images/isp_active_128x53.png | Bin 3961 -> 0 bytes .../images/link_waiting_77x56.png | Bin 3883 -> 0 bytes .../lib/driver/avr_isp_chip_arr.c | 386 ----- .../lib/driver/avr_isp_chip_arr.h | 33 - .../lib/driver/avr_isp_prog.c | 639 -------- .../lib/driver/avr_isp_prog.h | 16 - .../lib/driver/avr_isp_prog_cmd.h | 97 -- .../lib/driver/avr_isp_spi_sw.c | 71 - .../lib/driver/avr_isp_spi_sw.h | 24 - .../avr_isp_programmer/lib/driver/clock.png | Bin 3649 -> 0 bytes .../avr_isp_programmer/scenes/avr_isp_scene.c | 30 - .../avr_isp_programmer/scenes/avr_isp_scene.h | 29 - .../scenes/avr_isp_scene_about.c | 99 -- .../scenes/avr_isp_scene_chip_detect.c | 72 - .../scenes/avr_isp_scene_config.h | 10 - .../scenes/avr_isp_scene_input_name.c | 89 -- .../scenes/avr_isp_scene_load.c | 22 - .../scenes/avr_isp_scene_programmer.c | 28 - .../scenes/avr_isp_scene_reader.c | 64 - .../scenes/avr_isp_scene_start.c | 75 - .../scenes/avr_isp_scene_success.c | 44 - .../scenes/avr_isp_scene_wiring.c | 21 - .../scenes/avr_isp_scene_writer.c | 69 - .../views/avr_isp_view_chip_detect.c | 213 --- .../views/avr_isp_view_chip_detect.h | 32 - .../views/avr_isp_view_programmer.c | 134 -- .../views/avr_isp_view_programmer.h | 27 - .../views/avr_isp_view_reader.c | 215 --- .../views/avr_isp_view_reader.h | 35 - .../views/avr_isp_view_writer.c | 268 ---- .../views/avr_isp_view_writer.h | 37 - applications/external/dap_link/README.md | 105 -- .../external/dap_link/application.fam | 24 - applications/external/dap_link/dap_config.h | 234 --- applications/external/dap_link/dap_link.c | 527 ------- applications/external/dap_link/dap_link.h | 55 - applications/external/dap_link/dap_link.png | Bin 143 -> 0 bytes applications/external/dap_link/gui/dap_gui.c | 92 -- applications/external/dap_link/gui/dap_gui.h | 4 - .../dap_link/gui/dap_gui_custom_event.h | 7 - .../external/dap_link/gui/dap_gui_i.h | 34 - .../dap_link/gui/scenes/config/dap_scene.c | 30 - .../dap_link/gui/scenes/config/dap_scene.h | 29 - .../gui/scenes/config/dap_scene_config.h | 4 - .../dap_link/gui/scenes/dap_scene_about.c | 68 - .../dap_link/gui/scenes/dap_scene_config.c | 107 -- .../dap_link/gui/scenes/dap_scene_help.c | 102 -- .../dap_link/gui/scenes/dap_scene_main.c | 154 -- .../dap_link/gui/views/dap_main_view.c | 189 --- .../dap_link/gui/views/dap_main_view.h | 45 - .../dap_link/icons/ActiveConnection_50x64.png | Bin 3842 -> 0 bytes .../dap_link/icons/ArrowUpEmpty_12x18.png | Bin 159 -> 0 bytes .../dap_link/icons/ArrowUpFilled_12x18.png | Bin 173 -> 0 bytes applications/external/dap_link/lib/free-dap | 1 - .../external/dap_link/usb/dap_v2_usb.c | 977 ------------ .../external/dap_link/usb/dap_v2_usb.h | 53 - .../external/dap_link/usb/usb_winusb.h | 143 -- applications/external/hid_app/application.fam | 24 - .../external/hid_app/assets/Arr_dwn_7x9.png | Bin 3602 -> 0 bytes .../external/hid_app/assets/Arr_up_7x9.png | Bin 3605 -> 0 bytes .../hid_app/assets/Ble_connected_15x15.png | Bin 3634 -> 0 bytes .../hid_app/assets/Ble_disconnected_15x15.png | Bin 657 -> 0 bytes .../hid_app/assets/ButtonDown_7x4.png | Bin 102 -> 0 bytes .../external/hid_app/assets/ButtonF10_5x8.png | Bin 172 -> 0 bytes .../external/hid_app/assets/ButtonF11_5x8.png | Bin 173 -> 0 bytes .../external/hid_app/assets/ButtonF12_5x8.png | Bin 180 -> 0 bytes .../external/hid_app/assets/ButtonF1_5x8.png | Bin 177 -> 0 bytes .../external/hid_app/assets/ButtonF2_5x8.png | Bin 179 -> 0 bytes .../external/hid_app/assets/ButtonF3_5x8.png | Bin 178 -> 0 bytes .../external/hid_app/assets/ButtonF4_5x8.png | Bin 177 -> 0 bytes .../external/hid_app/assets/ButtonF5_5x8.png | Bin 178 -> 0 bytes .../external/hid_app/assets/ButtonF6_5x8.png | Bin 177 -> 0 bytes .../external/hid_app/assets/ButtonF7_5x8.png | Bin 176 -> 0 bytes .../external/hid_app/assets/ButtonF8_5x8.png | Bin 176 -> 0 bytes .../external/hid_app/assets/ButtonF9_5x8.png | Bin 179 -> 0 bytes .../hid_app/assets/ButtonLeft_4x7.png | Bin 1415 -> 0 bytes .../hid_app/assets/ButtonRight_4x7.png | Bin 1839 -> 0 bytes .../external/hid_app/assets/ButtonUp_7x4.png | Bin 102 -> 0 bytes .../external/hid_app/assets/Button_18x18.png | Bin 3609 -> 0 bytes .../external/hid_app/assets/Circles_47x47.png | Bin 3712 -> 0 bytes .../hid_app/assets/Left_mouse_icon_9x9.png | Bin 3622 -> 0 bytes .../external/hid_app/assets/Like_def_11x9.png | Bin 3616 -> 0 bytes .../hid_app/assets/Like_pressed_17x17.png | Bin 3643 -> 0 bytes .../external/hid_app/assets/Ok_btn_9x9.png | Bin 3605 -> 0 bytes .../hid_app/assets/Ok_btn_pressed_13x13.png | Bin 3625 -> 0 bytes .../hid_app/assets/Pin_arrow_down_7x9.png | Bin 3607 -> 0 bytes .../hid_app/assets/Pin_arrow_left_9x7.png | Bin 3603 -> 0 bytes .../hid_app/assets/Pin_arrow_right_9x7.png | Bin 3602 -> 0 bytes .../hid_app/assets/Pin_arrow_up_7x9.png | Bin 3603 -> 0 bytes .../hid_app/assets/Pin_back_arrow_10x8.png | Bin 3606 -> 0 bytes .../hid_app/assets/Pressed_Button_13x13.png | Bin 3606 -> 0 bytes .../hid_app/assets/Right_mouse_icon_9x9.png | Bin 3622 -> 0 bytes .../external/hid_app/assets/Space_60x18.png | Bin 2871 -> 0 bytes .../external/hid_app/assets/Space_65x18.png | Bin 3619 -> 0 bytes .../external/hid_app/assets/Voldwn_6x6.png | Bin 3593 -> 0 bytes .../external/hid_app/assets/Volup_8x6.png | Bin 3595 -> 0 bytes applications/external/hid_app/hid.c | 447 ------ applications/external/hid_app/hid.h | 67 - .../external/hid_app/hid_ble_10px.png | Bin 151 -> 0 bytes .../external/hid_app/hid_usb_10px.png | Bin 969 -> 0 bytes applications/external/hid_app/views.h | 11 - .../external/hid_app/views/hid_keyboard.c | 411 ----- .../external/hid_app/views/hid_keyboard.h | 14 - .../external/hid_app/views/hid_keynote.c | 312 ---- .../external/hid_app/views/hid_keynote.h | 16 - .../external/hid_app/views/hid_media.c | 218 --- .../external/hid_app/views/hid_media.h | 13 - .../external/hid_app/views/hid_mouse.c | 226 --- .../external/hid_app/views/hid_mouse.h | 17 - .../hid_app/views/hid_mouse_clicker.c | 214 --- .../hid_app/views/hid_mouse_clicker.h | 14 - .../hid_app/views/hid_mouse_jiggler.c | 159 -- .../hid_app/views/hid_mouse_jiggler.h | 17 - .../external/hid_app/views/hid_tiktok.c | 241 --- .../external/hid_app/views/hid_tiktok.h | 14 - applications/external/mfkey32/application.fam | 17 - .../external/mfkey32/images/mfkey.png | Bin 9060 -> 0 bytes applications/external/mfkey32/mfkey.png | Bin 9060 -> 0 bytes applications/external/mfkey32/mfkey32.c | 1349 ---------------- .../external/nfc_magic/application.fam | 21 - .../nfc_magic/assets/DolphinCommon_56x48.png | Bin 1416 -> 0 bytes .../nfc_magic/assets/DolphinNice_96x59.png | Bin 2459 -> 0 bytes .../external/nfc_magic/assets/Loading_24.png | Bin 3649 -> 0 bytes .../nfc_magic/assets/NFC_manual_60x50.png | Bin 3804 -> 0 bytes .../nfc_magic/lib/magic/classic_gen1.c | 145 -- .../nfc_magic/lib/magic/classic_gen1.h | 11 - .../external/nfc_magic/lib/magic/common.c | 33 - .../external/nfc_magic/lib/magic/common.h | 19 - .../external/nfc_magic/lib/magic/gen4.c | 199 --- .../external/nfc_magic/lib/magic/gen4.h | 48 - .../external/nfc_magic/lib/magic/types.c | 23 - .../external/nfc_magic/lib/magic/types.h | 5 - applications/external/nfc_magic/nfc_magic.c | 184 --- applications/external/nfc_magic/nfc_magic.h | 5 - applications/external/nfc_magic/nfc_magic_i.h | 94 -- .../external/nfc_magic/nfc_magic_worker.c | 480 ------ .../external/nfc_magic/nfc_magic_worker.h | 42 - .../external/nfc_magic/nfc_magic_worker_i.h | 29 - .../nfc_magic/scenes/nfc_magic_scene.c | 30 - .../nfc_magic/scenes/nfc_magic_scene.h | 29 - .../scenes/nfc_magic_scene_actions.c | 50 - .../nfc_magic/scenes/nfc_magic_scene_check.c | 89 -- .../nfc_magic/scenes/nfc_magic_scene_config.h | 18 - .../scenes/nfc_magic_scene_file_select.c | 76 - .../scenes/nfc_magic_scene_gen4_actions.c | 70 - .../scenes/nfc_magic_scene_key_input.c | 45 - .../scenes/nfc_magic_scene_magic_info.c | 59 - .../scenes/nfc_magic_scene_new_key_input.c | 45 - .../scenes/nfc_magic_scene_not_magic.c | 43 - .../nfc_magic/scenes/nfc_magic_scene_rekey.c | 95 -- .../scenes/nfc_magic_scene_rekey_fail.c | 50 - .../nfc_magic/scenes/nfc_magic_scene_start.c | 56 - .../scenes/nfc_magic_scene_success.c | 42 - .../nfc_magic/scenes/nfc_magic_scene_wipe.c | 97 -- .../scenes/nfc_magic_scene_wipe_fail.c | 41 - .../nfc_magic/scenes/nfc_magic_scene_write.c | 97 -- .../scenes/nfc_magic_scene_write_confirm.c | 64 - .../scenes/nfc_magic_scene_write_fail.c | 58 - .../scenes/nfc_magic_scene_wrong_card.c | 53 - .../nfc_rfid_detector/application.fam | 13 - .../helpers/nfc_rfid_detector_event.h | 7 - .../helpers/nfc_rfid_detector_types.h | 15 - .../images/Modern_reader_18x34.png | Bin 3670 -> 0 bytes .../images/Move_flipper_26x39.png | Bin 3698 -> 0 bytes .../images/NFC_detect_45x30.png | Bin 168 -> 0 bytes .../images/Rfid_detect_45x30.png | Bin 158 -> 0 bytes .../nfc_rfid_detector_10px.png | Bin 124 -> 0 bytes .../nfc_rfid_detector/nfc_rfid_detector_app.c | 108 -- .../nfc_rfid_detector_app_i.c | 40 - .../nfc_rfid_detector_app_i.h | 30 - .../scenes/nfc_rfid_detector_scene.c | 31 - .../scenes/nfc_rfid_detector_scene.h | 29 - .../scenes/nfc_rfid_detector_scene_about.c | 69 - .../scenes/nfc_rfid_detector_scene_config.h | 3 - .../nfc_rfid_detector_scene_field_presence.c | 60 - .../scenes/nfc_rfid_detector_scene_start.c | 58 - .../nfc_rfid_detector_view_field_presence.c | 164 -- .../nfc_rfid_detector_view_field_presence.h | 19 - applications/external/picopass/125_10px.png | Bin 308 -> 0 bytes .../external/picopass/application.fam | 22 - .../picopass/helpers/iclass_elite_dict.c | 164 -- .../picopass/helpers/iclass_elite_dict.h | 29 - .../picopass/icons/DolphinMafia_115x62.png | Bin 2504 -> 0 bytes .../picopass/icons/DolphinNice_96x59.png | Bin 2459 -> 0 bytes .../external/picopass/icons/Nfc_10px.png | Bin 304 -> 0 bytes .../icons/RFIDDolphinReceive_97x61.png | Bin 1421 -> 0 bytes .../picopass/icons/RFIDDolphinSend_97x61.png | Bin 1418 -> 0 bytes .../picopass/lib/loclass/optimized_cipher.c | 299 ---- .../picopass/lib/loclass/optimized_cipher.h | 98 -- .../lib/loclass/optimized_cipherutils.c | 136 -- .../lib/loclass/optimized_cipherutils.h | 64 - .../picopass/lib/loclass/optimized_elite.c | 232 --- .../picopass/lib/loclass/optimized_elite.h | 58 - .../picopass/lib/loclass/optimized_ikeys.c | 320 ---- .../picopass/lib/loclass/optimized_ikeys.h | 66 - applications/external/picopass/picopass.c | 212 --- applications/external/picopass/picopass.h | 3 - .../external/picopass/picopass_device.c | 380 ----- .../external/picopass/picopass_device.h | 117 -- applications/external/picopass/picopass_i.h | 99 -- .../external/picopass/picopass_keys.c | 8 - .../external/picopass/picopass_keys.h | 10 - .../external/picopass/picopass_worker.c | 752 --------- .../external/picopass/picopass_worker.h | 52 - .../external/picopass/picopass_worker_i.h | 34 - .../external/picopass/rfal_picopass.c | 214 --- .../external/picopass/rfal_picopass.h | 48 - .../external/picopass/scenes/picopass_scene.c | 30 - .../external/picopass/scenes/picopass_scene.h | 29 - .../scenes/picopass_scene_card_menu.c | 78 - .../picopass/scenes/picopass_scene_config.h | 17 - .../picopass/scenes/picopass_scene_delete.c | 58 - .../scenes/picopass_scene_delete_success.c | 40 - .../scenes/picopass_scene_device_info.c | 114 -- .../scenes/picopass_scene_elite_dict_attack.c | 172 -- .../scenes/picopass_scene_file_select.c | 25 - .../picopass/scenes/picopass_scene_key_menu.c | 100 -- .../scenes/picopass_scene_read_card.c | 61 - .../scenes/picopass_scene_read_card_success.c | 172 -- .../picopass_scene_read_factory_success.c | 80 - .../scenes/picopass_scene_save_name.c | 81 - .../scenes/picopass_scene_save_success.c | 47 - .../scenes/picopass_scene_saved_menu.c | 64 - .../picopass/scenes/picopass_scene_start.c | 64 - .../scenes/picopass_scene_write_card.c | 53 - .../picopass_scene_write_card_success.c | 70 - .../scenes/picopass_scene_write_key.c | 53 - .../external/picopass/views/dict_attack.c | 281 ---- .../external/picopass/views/dict_attack.h | 44 - .../external/signal_generator/application.fam | 12 - .../icons/SmallArrowDown_3x5.png | Bin 3592 -> 0 bytes .../icons/SmallArrowUp_3x5.png | Bin 7976 -> 0 bytes .../scenes/signal_gen_scene.c | 30 - .../scenes/signal_gen_scene.h | 29 - .../scenes/signal_gen_scene_config.h | 3 - .../scenes/signal_gen_scene_mco.c | 145 -- .../scenes/signal_gen_scene_pwm.c | 79 - .../scenes/signal_gen_scene_start.c | 55 - .../signal_generator/signal_gen_10px.png | Bin 6082 -> 0 bytes .../signal_generator/signal_gen_app.c | 93 -- .../signal_generator/signal_gen_app_i.h | 46 - .../signal_generator/views/signal_gen_pwm.c | 310 ---- .../signal_generator/views/signal_gen_pwm.h | 21 - .../external/spi_mem_manager/application.fam | 17 - .../images/ChipLooking_64x64/frame_01.png | Bin 7231 -> 0 bytes .../images/ChipLooking_64x64/frame_02.png | Bin 6909 -> 0 bytes .../images/ChipLooking_64x64/frame_03.png | Bin 7308 -> 0 bytes .../images/ChipLooking_64x64/frame_rate | 1 - .../spi_mem_manager/images/Dip8_10px.png | Bin 195 -> 0 bytes .../spi_mem_manager/images/Dip8_32x36.png | Bin 977 -> 0 bytes .../images/DolphinMafia_115x62.png | Bin 2504 -> 0 bytes .../images/DolphinNice_96x59.png | Bin 2459 -> 0 bytes .../images/SDQuestion_35x43.png | Bin 1950 -> 0 bytes .../images/Wiring_SPI_128x64.png | Bin 4446 -> 0 bytes .../spi_mem_manager/lib/spi/spi_mem_chip.c | 105 -- .../spi_mem_manager/lib/spi/spi_mem_chip.h | 34 - .../lib/spi/spi_mem_chip_arr.c | 1399 ----------------- .../spi_mem_manager/lib/spi/spi_mem_chip_i.h | 85 - .../spi_mem_manager/lib/spi/spi_mem_tools.c | 152 -- .../spi_mem_manager/lib/spi/spi_mem_tools.h | 14 - .../spi_mem_manager/lib/spi/spi_mem_worker.c | 129 -- .../spi_mem_manager/lib/spi/spi_mem_worker.h | 54 - .../lib/spi/spi_mem_worker_i.h | 24 - .../lib/spi/spi_mem_worker_modes.c | 214 --- .../spi_mem_manager/scenes/spi_mem_scene.c | 30 - .../spi_mem_manager/scenes/spi_mem_scene.h | 29 - .../scenes/spi_mem_scene_about.c | 42 - .../scenes/spi_mem_scene_chip_detect.c | 37 - .../scenes/spi_mem_scene_chip_detect_fail.c | 57 - .../scenes/spi_mem_scene_chip_detected.c | 94 -- .../scenes/spi_mem_scene_chip_error.c | 52 - .../scenes/spi_mem_scene_config.h | 21 - .../scenes/spi_mem_scene_delete_confirm.c | 62 - .../scenes/spi_mem_scene_erase.c | 65 - .../scenes/spi_mem_scene_file_info.c | 29 - .../scenes/spi_mem_scene_read.c | 57 - .../scenes/spi_mem_scene_read_filename.c | 46 - .../scenes/spi_mem_scene_saved_file_menu.c | 76 - .../scenes/spi_mem_scene_select_file.c | 22 - .../scenes/spi_mem_scene_select_model.c | 45 - .../scenes/spi_mem_scene_select_vendor.c | 70 - .../scenes/spi_mem_scene_start.c | 84 - .../scenes/spi_mem_scene_storage_error.c | 56 - .../scenes/spi_mem_scene_success.c | 40 - .../scenes/spi_mem_scene_verify.c | 59 - .../scenes/spi_mem_scene_verify_error.c | 43 - .../scenes/spi_mem_scene_wiring.c | 18 - .../scenes/spi_mem_scene_write.c | 58 - .../external/spi_mem_manager/spi_mem_app.c | 112 -- .../external/spi_mem_manager/spi_mem_app.h | 3 - .../external/spi_mem_manager/spi_mem_app_i.h | 78 - .../external/spi_mem_manager/spi_mem_files.c | 68 - .../external/spi_mem_manager/spi_mem_files.h | 13 - .../external/spi_mem_manager/tools/README.md | 7 - .../spi_mem_manager/tools/chiplist/LICENSE | 22 - .../tools/chiplist/chiplist.xml | 984 ------------ .../spi_mem_manager/tools/chiplist_convert.py | 109 -- .../views/spi_mem_view_detect.c | 64 - .../views/spi_mem_view_detect.h | 9 - .../views/spi_mem_view_progress.c | 230 --- .../views/spi_mem_view_progress.h | 26 - .../external/weather_station/application.fam | 13 - .../helpers/weather_station_event.h | 14 - .../helpers/weather_station_types.h | 49 - .../weather_station/images/Humid_10x15.png | Bin 3624 -> 0 bytes .../weather_station/images/Humid_8x13.png | Bin 3618 -> 0 bytes .../weather_station/images/Lock_7x8.png | Bin 3597 -> 0 bytes .../images/Pin_back_arrow_10x8.png | Bin 3606 -> 0 bytes .../weather_station/images/Quest_7x8.png | Bin 3675 -> 0 bytes .../images/Scanning_123x52.png | Bin 1690 -> 0 bytes .../weather_station/images/Therm_7x16.png | Bin 3611 -> 0 bytes .../weather_station/images/Timer_11x11.png | Bin 3616 -> 0 bytes .../weather_station/images/Unlock_7x8.png | Bin 3598 -> 0 bytes .../images/WarningDolphin_45x42.png | Bin 1139 -> 0 bytes .../weather_station/images/station_icon.png | Bin 3607 -> 0 bytes .../protocols/acurite_592txr.c | 298 ---- .../protocols/acurite_592txr.h | 80 - .../weather_station/protocols/acurite_606tx.c | 239 --- .../weather_station/protocols/acurite_606tx.h | 80 - .../protocols/acurite_609txc.c | 239 --- .../protocols/acurite_609txc.h | 80 - .../protocols/ambient_weather.c | 268 ---- .../protocols/ambient_weather.h | 80 - .../protocols/auriol_hg0601a.c | 248 --- .../protocols/auriol_hg0601a.h | 80 - .../weather_station/protocols/gt_wt_02.c | 255 --- .../weather_station/protocols/gt_wt_02.h | 80 - .../weather_station/protocols/gt_wt_03.c | 330 ---- .../weather_station/protocols/gt_wt_03.h | 80 - .../weather_station/protocols/infactory.c | 286 ---- .../weather_station/protocols/infactory.h | 80 - .../weather_station/protocols/lacrosse_tx.c | 319 ---- .../weather_station/protocols/lacrosse_tx.h | 80 - .../protocols/lacrosse_tx141thbv2.c | 294 ---- .../protocols/lacrosse_tx141thbv2.h | 81 - .../weather_station/protocols/nexus_th.c | 254 --- .../weather_station/protocols/nexus_th.h | 80 - .../weather_station/protocols/oregon2.c | 429 ----- .../weather_station/protocols/oregon2.h | 6 - .../weather_station/protocols/oregon3.c | 365 ----- .../weather_station/protocols/oregon3.h | 6 - .../weather_station/protocols/oregon_v1.c | 321 ---- .../weather_station/protocols/oregon_v1.h | 80 - .../protocols/protocol_items.c | 25 - .../protocols/protocol_items.h | 22 - .../weather_station/protocols/thermopro_tx4.c | 251 --- .../weather_station/protocols/thermopro_tx4.h | 80 - .../weather_station/protocols/tx_8300.c | 284 ---- .../weather_station/protocols/tx_8300.h | 80 - .../weather_station/protocols/wendox_w6726.c | 299 ---- .../weather_station/protocols/wendox_w6726.h | 80 - .../weather_station/protocols/ws_generic.c | 256 --- .../weather_station/protocols/ws_generic.h | 81 - .../scenes/weather_station_receiver.c | 211 --- .../scenes/weather_station_scene.c | 30 - .../scenes/weather_station_scene.h | 29 - .../scenes/weather_station_scene_about.c | 78 - .../scenes/weather_station_scene_config.h | 5 - .../weather_station_scene_receiver_config.c | 223 --- .../weather_station_scene_receiver_info.c | 50 - .../scenes/weather_station_scene_start.c | 58 - .../views/weather_station_receiver.c | 464 ------ .../views/weather_station_receiver.h | 38 - .../views/weather_station_receiver_info.c | 245 --- .../views/weather_station_receiver_info.h | 16 - .../weather_station/weather_station_10px.png | Bin 175 -> 0 bytes .../weather_station/weather_station_app.c | 178 --- .../weather_station/weather_station_app_i.c | 156 -- .../weather_station/weather_station_app_i.h | 73 - .../weather_station/weather_station_history.c | 245 --- .../weather_station/weather_station_history.h | 112 -- applications/main/application.fam | 3 + .../{external => main}/clock/application.fam | 0 .../{external => main}/clock/clock.png | Bin .../{external => main}/clock/clock_app.c | 0 .../music_player/application.fam | 0 .../music_player/icons/music_10px.png | Bin .../music_player/music_player.c | 0 .../snake_game/application.fam | 0 .../snake_game/snake_10px.png | Bin .../snake_game/snake_game.c | 0 documentation/Doxyfile | 3 +- site_scons/commandline.scons | 1 - 411 files changed, 7 insertions(+), 35705 deletions(-) delete mode 100644 applications/external/application.fam delete mode 100644 applications/external/avr_isp_programmer/application.fam delete mode 100644 applications/external/avr_isp_programmer/avr_app_icon_10x10.png delete mode 100644 applications/external/avr_isp_programmer/avr_isp_app.c delete mode 100644 applications/external/avr_isp_programmer/avr_isp_app_i.c delete mode 100644 applications/external/avr_isp_programmer/avr_isp_app_i.h delete mode 100644 applications/external/avr_isp_programmer/helpers/avr_isp.c delete mode 100644 applications/external/avr_isp_programmer/helpers/avr_isp.h delete mode 100644 applications/external/avr_isp_programmer/helpers/avr_isp_event.h delete mode 100644 applications/external/avr_isp_programmer/helpers/avr_isp_types.h delete mode 100644 applications/external/avr_isp_programmer/helpers/avr_isp_worker.c delete mode 100644 applications/external/avr_isp_programmer/helpers/avr_isp_worker.h delete mode 100644 applications/external/avr_isp_programmer/helpers/avr_isp_worker_rw.c delete mode 100644 applications/external/avr_isp_programmer/helpers/avr_isp_worker_rw.h delete mode 100644 applications/external/avr_isp_programmer/helpers/flipper_i32hex_file.c delete mode 100644 applications/external/avr_isp_programmer/helpers/flipper_i32hex_file.h delete mode 100644 applications/external/avr_isp_programmer/images/avr_app_icon_10x10.png delete mode 100644 applications/external/avr_isp_programmer/images/avr_wiring.png delete mode 100644 applications/external/avr_isp_programmer/images/chif_not_found_83x37.png delete mode 100644 applications/external/avr_isp_programmer/images/chip_error_70x22.png delete mode 100644 applications/external/avr_isp_programmer/images/chip_long_70x22.png delete mode 100644 applications/external/avr_isp_programmer/images/chip_not_found_83x37.png delete mode 100644 applications/external/avr_isp_programmer/images/dolphin_nice_96x59.png delete mode 100644 applications/external/avr_isp_programmer/images/isp_active_128x53.png delete mode 100644 applications/external/avr_isp_programmer/images/link_waiting_77x56.png delete mode 100644 applications/external/avr_isp_programmer/lib/driver/avr_isp_chip_arr.c delete mode 100644 applications/external/avr_isp_programmer/lib/driver/avr_isp_chip_arr.h delete mode 100644 applications/external/avr_isp_programmer/lib/driver/avr_isp_prog.c delete mode 100644 applications/external/avr_isp_programmer/lib/driver/avr_isp_prog.h delete mode 100644 applications/external/avr_isp_programmer/lib/driver/avr_isp_prog_cmd.h delete mode 100644 applications/external/avr_isp_programmer/lib/driver/avr_isp_spi_sw.c delete mode 100644 applications/external/avr_isp_programmer/lib/driver/avr_isp_spi_sw.h delete mode 100644 applications/external/avr_isp_programmer/lib/driver/clock.png delete mode 100644 applications/external/avr_isp_programmer/scenes/avr_isp_scene.c delete mode 100644 applications/external/avr_isp_programmer/scenes/avr_isp_scene.h delete mode 100644 applications/external/avr_isp_programmer/scenes/avr_isp_scene_about.c delete mode 100644 applications/external/avr_isp_programmer/scenes/avr_isp_scene_chip_detect.c delete mode 100644 applications/external/avr_isp_programmer/scenes/avr_isp_scene_config.h delete mode 100644 applications/external/avr_isp_programmer/scenes/avr_isp_scene_input_name.c delete mode 100644 applications/external/avr_isp_programmer/scenes/avr_isp_scene_load.c delete mode 100644 applications/external/avr_isp_programmer/scenes/avr_isp_scene_programmer.c delete mode 100644 applications/external/avr_isp_programmer/scenes/avr_isp_scene_reader.c delete mode 100644 applications/external/avr_isp_programmer/scenes/avr_isp_scene_start.c delete mode 100644 applications/external/avr_isp_programmer/scenes/avr_isp_scene_success.c delete mode 100644 applications/external/avr_isp_programmer/scenes/avr_isp_scene_wiring.c delete mode 100644 applications/external/avr_isp_programmer/scenes/avr_isp_scene_writer.c delete mode 100644 applications/external/avr_isp_programmer/views/avr_isp_view_chip_detect.c delete mode 100644 applications/external/avr_isp_programmer/views/avr_isp_view_chip_detect.h delete mode 100644 applications/external/avr_isp_programmer/views/avr_isp_view_programmer.c delete mode 100644 applications/external/avr_isp_programmer/views/avr_isp_view_programmer.h delete mode 100644 applications/external/avr_isp_programmer/views/avr_isp_view_reader.c delete mode 100644 applications/external/avr_isp_programmer/views/avr_isp_view_reader.h delete mode 100644 applications/external/avr_isp_programmer/views/avr_isp_view_writer.c delete mode 100644 applications/external/avr_isp_programmer/views/avr_isp_view_writer.h delete mode 100644 applications/external/dap_link/README.md delete mode 100644 applications/external/dap_link/application.fam delete mode 100644 applications/external/dap_link/dap_config.h delete mode 100644 applications/external/dap_link/dap_link.c delete mode 100644 applications/external/dap_link/dap_link.h delete mode 100644 applications/external/dap_link/dap_link.png delete mode 100644 applications/external/dap_link/gui/dap_gui.c delete mode 100644 applications/external/dap_link/gui/dap_gui.h delete mode 100644 applications/external/dap_link/gui/dap_gui_custom_event.h delete mode 100644 applications/external/dap_link/gui/dap_gui_i.h delete mode 100644 applications/external/dap_link/gui/scenes/config/dap_scene.c delete mode 100644 applications/external/dap_link/gui/scenes/config/dap_scene.h delete mode 100644 applications/external/dap_link/gui/scenes/config/dap_scene_config.h delete mode 100644 applications/external/dap_link/gui/scenes/dap_scene_about.c delete mode 100644 applications/external/dap_link/gui/scenes/dap_scene_config.c delete mode 100644 applications/external/dap_link/gui/scenes/dap_scene_help.c delete mode 100644 applications/external/dap_link/gui/scenes/dap_scene_main.c delete mode 100644 applications/external/dap_link/gui/views/dap_main_view.c delete mode 100644 applications/external/dap_link/gui/views/dap_main_view.h delete mode 100644 applications/external/dap_link/icons/ActiveConnection_50x64.png delete mode 100644 applications/external/dap_link/icons/ArrowUpEmpty_12x18.png delete mode 100644 applications/external/dap_link/icons/ArrowUpFilled_12x18.png delete mode 160000 applications/external/dap_link/lib/free-dap delete mode 100644 applications/external/dap_link/usb/dap_v2_usb.c delete mode 100644 applications/external/dap_link/usb/dap_v2_usb.h delete mode 100644 applications/external/dap_link/usb/usb_winusb.h delete mode 100644 applications/external/hid_app/application.fam delete mode 100644 applications/external/hid_app/assets/Arr_dwn_7x9.png delete mode 100644 applications/external/hid_app/assets/Arr_up_7x9.png delete mode 100644 applications/external/hid_app/assets/Ble_connected_15x15.png delete mode 100644 applications/external/hid_app/assets/Ble_disconnected_15x15.png delete mode 100644 applications/external/hid_app/assets/ButtonDown_7x4.png delete mode 100644 applications/external/hid_app/assets/ButtonF10_5x8.png delete mode 100644 applications/external/hid_app/assets/ButtonF11_5x8.png delete mode 100644 applications/external/hid_app/assets/ButtonF12_5x8.png delete mode 100644 applications/external/hid_app/assets/ButtonF1_5x8.png delete mode 100644 applications/external/hid_app/assets/ButtonF2_5x8.png delete mode 100644 applications/external/hid_app/assets/ButtonF3_5x8.png delete mode 100644 applications/external/hid_app/assets/ButtonF4_5x8.png delete mode 100644 applications/external/hid_app/assets/ButtonF5_5x8.png delete mode 100644 applications/external/hid_app/assets/ButtonF6_5x8.png delete mode 100644 applications/external/hid_app/assets/ButtonF7_5x8.png delete mode 100644 applications/external/hid_app/assets/ButtonF8_5x8.png delete mode 100644 applications/external/hid_app/assets/ButtonF9_5x8.png delete mode 100644 applications/external/hid_app/assets/ButtonLeft_4x7.png delete mode 100644 applications/external/hid_app/assets/ButtonRight_4x7.png delete mode 100644 applications/external/hid_app/assets/ButtonUp_7x4.png delete mode 100644 applications/external/hid_app/assets/Button_18x18.png delete mode 100644 applications/external/hid_app/assets/Circles_47x47.png delete mode 100644 applications/external/hid_app/assets/Left_mouse_icon_9x9.png delete mode 100644 applications/external/hid_app/assets/Like_def_11x9.png delete mode 100644 applications/external/hid_app/assets/Like_pressed_17x17.png delete mode 100644 applications/external/hid_app/assets/Ok_btn_9x9.png delete mode 100644 applications/external/hid_app/assets/Ok_btn_pressed_13x13.png delete mode 100644 applications/external/hid_app/assets/Pin_arrow_down_7x9.png delete mode 100644 applications/external/hid_app/assets/Pin_arrow_left_9x7.png delete mode 100644 applications/external/hid_app/assets/Pin_arrow_right_9x7.png delete mode 100644 applications/external/hid_app/assets/Pin_arrow_up_7x9.png delete mode 100644 applications/external/hid_app/assets/Pin_back_arrow_10x8.png delete mode 100644 applications/external/hid_app/assets/Pressed_Button_13x13.png delete mode 100644 applications/external/hid_app/assets/Right_mouse_icon_9x9.png delete mode 100644 applications/external/hid_app/assets/Space_60x18.png delete mode 100644 applications/external/hid_app/assets/Space_65x18.png delete mode 100644 applications/external/hid_app/assets/Voldwn_6x6.png delete mode 100644 applications/external/hid_app/assets/Volup_8x6.png delete mode 100644 applications/external/hid_app/hid.c delete mode 100644 applications/external/hid_app/hid.h delete mode 100644 applications/external/hid_app/hid_ble_10px.png delete mode 100644 applications/external/hid_app/hid_usb_10px.png delete mode 100644 applications/external/hid_app/views.h delete mode 100644 applications/external/hid_app/views/hid_keyboard.c delete mode 100644 applications/external/hid_app/views/hid_keyboard.h delete mode 100644 applications/external/hid_app/views/hid_keynote.c delete mode 100644 applications/external/hid_app/views/hid_keynote.h delete mode 100644 applications/external/hid_app/views/hid_media.c delete mode 100644 applications/external/hid_app/views/hid_media.h delete mode 100644 applications/external/hid_app/views/hid_mouse.c delete mode 100644 applications/external/hid_app/views/hid_mouse.h delete mode 100644 applications/external/hid_app/views/hid_mouse_clicker.c delete mode 100644 applications/external/hid_app/views/hid_mouse_clicker.h delete mode 100644 applications/external/hid_app/views/hid_mouse_jiggler.c delete mode 100644 applications/external/hid_app/views/hid_mouse_jiggler.h delete mode 100644 applications/external/hid_app/views/hid_tiktok.c delete mode 100644 applications/external/hid_app/views/hid_tiktok.h delete mode 100644 applications/external/mfkey32/application.fam delete mode 100644 applications/external/mfkey32/images/mfkey.png delete mode 100644 applications/external/mfkey32/mfkey.png delete mode 100644 applications/external/mfkey32/mfkey32.c delete mode 100644 applications/external/nfc_magic/application.fam delete mode 100644 applications/external/nfc_magic/assets/DolphinCommon_56x48.png delete mode 100644 applications/external/nfc_magic/assets/DolphinNice_96x59.png delete mode 100644 applications/external/nfc_magic/assets/Loading_24.png delete mode 100644 applications/external/nfc_magic/assets/NFC_manual_60x50.png delete mode 100644 applications/external/nfc_magic/lib/magic/classic_gen1.c delete mode 100644 applications/external/nfc_magic/lib/magic/classic_gen1.h delete mode 100644 applications/external/nfc_magic/lib/magic/common.c delete mode 100644 applications/external/nfc_magic/lib/magic/common.h delete mode 100644 applications/external/nfc_magic/lib/magic/gen4.c delete mode 100644 applications/external/nfc_magic/lib/magic/gen4.h delete mode 100644 applications/external/nfc_magic/lib/magic/types.c delete mode 100644 applications/external/nfc_magic/lib/magic/types.h delete mode 100644 applications/external/nfc_magic/nfc_magic.c delete mode 100644 applications/external/nfc_magic/nfc_magic.h delete mode 100644 applications/external/nfc_magic/nfc_magic_i.h delete mode 100644 applications/external/nfc_magic/nfc_magic_worker.c delete mode 100644 applications/external/nfc_magic/nfc_magic_worker.h delete mode 100644 applications/external/nfc_magic/nfc_magic_worker_i.h delete mode 100644 applications/external/nfc_magic/scenes/nfc_magic_scene.c delete mode 100644 applications/external/nfc_magic/scenes/nfc_magic_scene.h delete mode 100644 applications/external/nfc_magic/scenes/nfc_magic_scene_actions.c delete mode 100644 applications/external/nfc_magic/scenes/nfc_magic_scene_check.c delete mode 100644 applications/external/nfc_magic/scenes/nfc_magic_scene_config.h delete mode 100644 applications/external/nfc_magic/scenes/nfc_magic_scene_file_select.c delete mode 100644 applications/external/nfc_magic/scenes/nfc_magic_scene_gen4_actions.c delete mode 100644 applications/external/nfc_magic/scenes/nfc_magic_scene_key_input.c delete mode 100644 applications/external/nfc_magic/scenes/nfc_magic_scene_magic_info.c delete mode 100644 applications/external/nfc_magic/scenes/nfc_magic_scene_new_key_input.c delete mode 100644 applications/external/nfc_magic/scenes/nfc_magic_scene_not_magic.c delete mode 100644 applications/external/nfc_magic/scenes/nfc_magic_scene_rekey.c delete mode 100644 applications/external/nfc_magic/scenes/nfc_magic_scene_rekey_fail.c delete mode 100644 applications/external/nfc_magic/scenes/nfc_magic_scene_start.c delete mode 100644 applications/external/nfc_magic/scenes/nfc_magic_scene_success.c delete mode 100644 applications/external/nfc_magic/scenes/nfc_magic_scene_wipe.c delete mode 100644 applications/external/nfc_magic/scenes/nfc_magic_scene_wipe_fail.c delete mode 100644 applications/external/nfc_magic/scenes/nfc_magic_scene_write.c delete mode 100644 applications/external/nfc_magic/scenes/nfc_magic_scene_write_confirm.c delete mode 100644 applications/external/nfc_magic/scenes/nfc_magic_scene_write_fail.c delete mode 100644 applications/external/nfc_magic/scenes/nfc_magic_scene_wrong_card.c delete mode 100644 applications/external/nfc_rfid_detector/application.fam delete mode 100644 applications/external/nfc_rfid_detector/helpers/nfc_rfid_detector_event.h delete mode 100644 applications/external/nfc_rfid_detector/helpers/nfc_rfid_detector_types.h delete mode 100644 applications/external/nfc_rfid_detector/images/Modern_reader_18x34.png delete mode 100644 applications/external/nfc_rfid_detector/images/Move_flipper_26x39.png delete mode 100644 applications/external/nfc_rfid_detector/images/NFC_detect_45x30.png delete mode 100644 applications/external/nfc_rfid_detector/images/Rfid_detect_45x30.png delete mode 100644 applications/external/nfc_rfid_detector/nfc_rfid_detector_10px.png delete mode 100644 applications/external/nfc_rfid_detector/nfc_rfid_detector_app.c delete mode 100644 applications/external/nfc_rfid_detector/nfc_rfid_detector_app_i.c delete mode 100644 applications/external/nfc_rfid_detector/nfc_rfid_detector_app_i.h delete mode 100644 applications/external/nfc_rfid_detector/scenes/nfc_rfid_detector_scene.c delete mode 100644 applications/external/nfc_rfid_detector/scenes/nfc_rfid_detector_scene.h delete mode 100644 applications/external/nfc_rfid_detector/scenes/nfc_rfid_detector_scene_about.c delete mode 100644 applications/external/nfc_rfid_detector/scenes/nfc_rfid_detector_scene_config.h delete mode 100644 applications/external/nfc_rfid_detector/scenes/nfc_rfid_detector_scene_field_presence.c delete mode 100644 applications/external/nfc_rfid_detector/scenes/nfc_rfid_detector_scene_start.c delete mode 100644 applications/external/nfc_rfid_detector/views/nfc_rfid_detector_view_field_presence.c delete mode 100644 applications/external/nfc_rfid_detector/views/nfc_rfid_detector_view_field_presence.h delete mode 100644 applications/external/picopass/125_10px.png delete mode 100644 applications/external/picopass/application.fam delete mode 100644 applications/external/picopass/helpers/iclass_elite_dict.c delete mode 100644 applications/external/picopass/helpers/iclass_elite_dict.h delete mode 100644 applications/external/picopass/icons/DolphinMafia_115x62.png delete mode 100644 applications/external/picopass/icons/DolphinNice_96x59.png delete mode 100644 applications/external/picopass/icons/Nfc_10px.png delete mode 100644 applications/external/picopass/icons/RFIDDolphinReceive_97x61.png delete mode 100644 applications/external/picopass/icons/RFIDDolphinSend_97x61.png delete mode 100644 applications/external/picopass/lib/loclass/optimized_cipher.c delete mode 100644 applications/external/picopass/lib/loclass/optimized_cipher.h delete mode 100644 applications/external/picopass/lib/loclass/optimized_cipherutils.c delete mode 100644 applications/external/picopass/lib/loclass/optimized_cipherutils.h delete mode 100644 applications/external/picopass/lib/loclass/optimized_elite.c delete mode 100644 applications/external/picopass/lib/loclass/optimized_elite.h delete mode 100644 applications/external/picopass/lib/loclass/optimized_ikeys.c delete mode 100644 applications/external/picopass/lib/loclass/optimized_ikeys.h delete mode 100644 applications/external/picopass/picopass.c delete mode 100644 applications/external/picopass/picopass.h delete mode 100644 applications/external/picopass/picopass_device.c delete mode 100644 applications/external/picopass/picopass_device.h delete mode 100644 applications/external/picopass/picopass_i.h delete mode 100644 applications/external/picopass/picopass_keys.c delete mode 100644 applications/external/picopass/picopass_keys.h delete mode 100644 applications/external/picopass/picopass_worker.c delete mode 100644 applications/external/picopass/picopass_worker.h delete mode 100644 applications/external/picopass/picopass_worker_i.h delete mode 100644 applications/external/picopass/rfal_picopass.c delete mode 100644 applications/external/picopass/rfal_picopass.h delete mode 100644 applications/external/picopass/scenes/picopass_scene.c delete mode 100644 applications/external/picopass/scenes/picopass_scene.h delete mode 100644 applications/external/picopass/scenes/picopass_scene_card_menu.c delete mode 100644 applications/external/picopass/scenes/picopass_scene_config.h delete mode 100644 applications/external/picopass/scenes/picopass_scene_delete.c delete mode 100644 applications/external/picopass/scenes/picopass_scene_delete_success.c delete mode 100644 applications/external/picopass/scenes/picopass_scene_device_info.c delete mode 100644 applications/external/picopass/scenes/picopass_scene_elite_dict_attack.c delete mode 100644 applications/external/picopass/scenes/picopass_scene_file_select.c delete mode 100644 applications/external/picopass/scenes/picopass_scene_key_menu.c delete mode 100644 applications/external/picopass/scenes/picopass_scene_read_card.c delete mode 100644 applications/external/picopass/scenes/picopass_scene_read_card_success.c delete mode 100644 applications/external/picopass/scenes/picopass_scene_read_factory_success.c delete mode 100644 applications/external/picopass/scenes/picopass_scene_save_name.c delete mode 100644 applications/external/picopass/scenes/picopass_scene_save_success.c delete mode 100644 applications/external/picopass/scenes/picopass_scene_saved_menu.c delete mode 100644 applications/external/picopass/scenes/picopass_scene_start.c delete mode 100644 applications/external/picopass/scenes/picopass_scene_write_card.c delete mode 100644 applications/external/picopass/scenes/picopass_scene_write_card_success.c delete mode 100644 applications/external/picopass/scenes/picopass_scene_write_key.c delete mode 100644 applications/external/picopass/views/dict_attack.c delete mode 100644 applications/external/picopass/views/dict_attack.h delete mode 100644 applications/external/signal_generator/application.fam delete mode 100644 applications/external/signal_generator/icons/SmallArrowDown_3x5.png delete mode 100644 applications/external/signal_generator/icons/SmallArrowUp_3x5.png delete mode 100644 applications/external/signal_generator/scenes/signal_gen_scene.c delete mode 100644 applications/external/signal_generator/scenes/signal_gen_scene.h delete mode 100644 applications/external/signal_generator/scenes/signal_gen_scene_config.h delete mode 100644 applications/external/signal_generator/scenes/signal_gen_scene_mco.c delete mode 100644 applications/external/signal_generator/scenes/signal_gen_scene_pwm.c delete mode 100644 applications/external/signal_generator/scenes/signal_gen_scene_start.c delete mode 100644 applications/external/signal_generator/signal_gen_10px.png delete mode 100644 applications/external/signal_generator/signal_gen_app.c delete mode 100644 applications/external/signal_generator/signal_gen_app_i.h delete mode 100644 applications/external/signal_generator/views/signal_gen_pwm.c delete mode 100644 applications/external/signal_generator/views/signal_gen_pwm.h delete mode 100644 applications/external/spi_mem_manager/application.fam delete mode 100644 applications/external/spi_mem_manager/images/ChipLooking_64x64/frame_01.png delete mode 100644 applications/external/spi_mem_manager/images/ChipLooking_64x64/frame_02.png delete mode 100644 applications/external/spi_mem_manager/images/ChipLooking_64x64/frame_03.png delete mode 100644 applications/external/spi_mem_manager/images/ChipLooking_64x64/frame_rate delete mode 100644 applications/external/spi_mem_manager/images/Dip8_10px.png delete mode 100644 applications/external/spi_mem_manager/images/Dip8_32x36.png delete mode 100644 applications/external/spi_mem_manager/images/DolphinMafia_115x62.png delete mode 100644 applications/external/spi_mem_manager/images/DolphinNice_96x59.png delete mode 100644 applications/external/spi_mem_manager/images/SDQuestion_35x43.png delete mode 100644 applications/external/spi_mem_manager/images/Wiring_SPI_128x64.png delete mode 100644 applications/external/spi_mem_manager/lib/spi/spi_mem_chip.c delete mode 100644 applications/external/spi_mem_manager/lib/spi/spi_mem_chip.h delete mode 100644 applications/external/spi_mem_manager/lib/spi/spi_mem_chip_arr.c delete mode 100644 applications/external/spi_mem_manager/lib/spi/spi_mem_chip_i.h delete mode 100644 applications/external/spi_mem_manager/lib/spi/spi_mem_tools.c delete mode 100644 applications/external/spi_mem_manager/lib/spi/spi_mem_tools.h delete mode 100644 applications/external/spi_mem_manager/lib/spi/spi_mem_worker.c delete mode 100644 applications/external/spi_mem_manager/lib/spi/spi_mem_worker.h delete mode 100644 applications/external/spi_mem_manager/lib/spi/spi_mem_worker_i.h delete mode 100644 applications/external/spi_mem_manager/lib/spi/spi_mem_worker_modes.c delete mode 100644 applications/external/spi_mem_manager/scenes/spi_mem_scene.c delete mode 100644 applications/external/spi_mem_manager/scenes/spi_mem_scene.h delete mode 100644 applications/external/spi_mem_manager/scenes/spi_mem_scene_about.c delete mode 100644 applications/external/spi_mem_manager/scenes/spi_mem_scene_chip_detect.c delete mode 100644 applications/external/spi_mem_manager/scenes/spi_mem_scene_chip_detect_fail.c delete mode 100644 applications/external/spi_mem_manager/scenes/spi_mem_scene_chip_detected.c delete mode 100644 applications/external/spi_mem_manager/scenes/spi_mem_scene_chip_error.c delete mode 100644 applications/external/spi_mem_manager/scenes/spi_mem_scene_config.h delete mode 100644 applications/external/spi_mem_manager/scenes/spi_mem_scene_delete_confirm.c delete mode 100644 applications/external/spi_mem_manager/scenes/spi_mem_scene_erase.c delete mode 100644 applications/external/spi_mem_manager/scenes/spi_mem_scene_file_info.c delete mode 100644 applications/external/spi_mem_manager/scenes/spi_mem_scene_read.c delete mode 100644 applications/external/spi_mem_manager/scenes/spi_mem_scene_read_filename.c delete mode 100644 applications/external/spi_mem_manager/scenes/spi_mem_scene_saved_file_menu.c delete mode 100644 applications/external/spi_mem_manager/scenes/spi_mem_scene_select_file.c delete mode 100644 applications/external/spi_mem_manager/scenes/spi_mem_scene_select_model.c delete mode 100644 applications/external/spi_mem_manager/scenes/spi_mem_scene_select_vendor.c delete mode 100644 applications/external/spi_mem_manager/scenes/spi_mem_scene_start.c delete mode 100644 applications/external/spi_mem_manager/scenes/spi_mem_scene_storage_error.c delete mode 100644 applications/external/spi_mem_manager/scenes/spi_mem_scene_success.c delete mode 100644 applications/external/spi_mem_manager/scenes/spi_mem_scene_verify.c delete mode 100644 applications/external/spi_mem_manager/scenes/spi_mem_scene_verify_error.c delete mode 100644 applications/external/spi_mem_manager/scenes/spi_mem_scene_wiring.c delete mode 100644 applications/external/spi_mem_manager/scenes/spi_mem_scene_write.c delete mode 100644 applications/external/spi_mem_manager/spi_mem_app.c delete mode 100644 applications/external/spi_mem_manager/spi_mem_app.h delete mode 100644 applications/external/spi_mem_manager/spi_mem_app_i.h delete mode 100644 applications/external/spi_mem_manager/spi_mem_files.c delete mode 100644 applications/external/spi_mem_manager/spi_mem_files.h delete mode 100644 applications/external/spi_mem_manager/tools/README.md delete mode 100644 applications/external/spi_mem_manager/tools/chiplist/LICENSE delete mode 100644 applications/external/spi_mem_manager/tools/chiplist/chiplist.xml delete mode 100755 applications/external/spi_mem_manager/tools/chiplist_convert.py delete mode 100644 applications/external/spi_mem_manager/views/spi_mem_view_detect.c delete mode 100644 applications/external/spi_mem_manager/views/spi_mem_view_detect.h delete mode 100644 applications/external/spi_mem_manager/views/spi_mem_view_progress.c delete mode 100644 applications/external/spi_mem_manager/views/spi_mem_view_progress.h delete mode 100644 applications/external/weather_station/application.fam delete mode 100644 applications/external/weather_station/helpers/weather_station_event.h delete mode 100644 applications/external/weather_station/helpers/weather_station_types.h delete mode 100644 applications/external/weather_station/images/Humid_10x15.png delete mode 100644 applications/external/weather_station/images/Humid_8x13.png delete mode 100644 applications/external/weather_station/images/Lock_7x8.png delete mode 100644 applications/external/weather_station/images/Pin_back_arrow_10x8.png delete mode 100644 applications/external/weather_station/images/Quest_7x8.png delete mode 100644 applications/external/weather_station/images/Scanning_123x52.png delete mode 100644 applications/external/weather_station/images/Therm_7x16.png delete mode 100644 applications/external/weather_station/images/Timer_11x11.png delete mode 100644 applications/external/weather_station/images/Unlock_7x8.png delete mode 100644 applications/external/weather_station/images/WarningDolphin_45x42.png delete mode 100644 applications/external/weather_station/images/station_icon.png delete mode 100644 applications/external/weather_station/protocols/acurite_592txr.c delete mode 100644 applications/external/weather_station/protocols/acurite_592txr.h delete mode 100644 applications/external/weather_station/protocols/acurite_606tx.c delete mode 100644 applications/external/weather_station/protocols/acurite_606tx.h delete mode 100644 applications/external/weather_station/protocols/acurite_609txc.c delete mode 100644 applications/external/weather_station/protocols/acurite_609txc.h delete mode 100644 applications/external/weather_station/protocols/ambient_weather.c delete mode 100644 applications/external/weather_station/protocols/ambient_weather.h delete mode 100644 applications/external/weather_station/protocols/auriol_hg0601a.c delete mode 100644 applications/external/weather_station/protocols/auriol_hg0601a.h delete mode 100644 applications/external/weather_station/protocols/gt_wt_02.c delete mode 100644 applications/external/weather_station/protocols/gt_wt_02.h delete mode 100644 applications/external/weather_station/protocols/gt_wt_03.c delete mode 100644 applications/external/weather_station/protocols/gt_wt_03.h delete mode 100644 applications/external/weather_station/protocols/infactory.c delete mode 100644 applications/external/weather_station/protocols/infactory.h delete mode 100644 applications/external/weather_station/protocols/lacrosse_tx.c delete mode 100644 applications/external/weather_station/protocols/lacrosse_tx.h delete mode 100644 applications/external/weather_station/protocols/lacrosse_tx141thbv2.c delete mode 100644 applications/external/weather_station/protocols/lacrosse_tx141thbv2.h delete mode 100644 applications/external/weather_station/protocols/nexus_th.c delete mode 100644 applications/external/weather_station/protocols/nexus_th.h delete mode 100644 applications/external/weather_station/protocols/oregon2.c delete mode 100644 applications/external/weather_station/protocols/oregon2.h delete mode 100644 applications/external/weather_station/protocols/oregon3.c delete mode 100644 applications/external/weather_station/protocols/oregon3.h delete mode 100644 applications/external/weather_station/protocols/oregon_v1.c delete mode 100644 applications/external/weather_station/protocols/oregon_v1.h delete mode 100644 applications/external/weather_station/protocols/protocol_items.c delete mode 100644 applications/external/weather_station/protocols/protocol_items.h delete mode 100644 applications/external/weather_station/protocols/thermopro_tx4.c delete mode 100644 applications/external/weather_station/protocols/thermopro_tx4.h delete mode 100644 applications/external/weather_station/protocols/tx_8300.c delete mode 100644 applications/external/weather_station/protocols/tx_8300.h delete mode 100644 applications/external/weather_station/protocols/wendox_w6726.c delete mode 100644 applications/external/weather_station/protocols/wendox_w6726.h delete mode 100644 applications/external/weather_station/protocols/ws_generic.c delete mode 100644 applications/external/weather_station/protocols/ws_generic.h delete mode 100644 applications/external/weather_station/scenes/weather_station_receiver.c delete mode 100644 applications/external/weather_station/scenes/weather_station_scene.c delete mode 100644 applications/external/weather_station/scenes/weather_station_scene.h delete mode 100644 applications/external/weather_station/scenes/weather_station_scene_about.c delete mode 100644 applications/external/weather_station/scenes/weather_station_scene_config.h delete mode 100644 applications/external/weather_station/scenes/weather_station_scene_receiver_config.c delete mode 100644 applications/external/weather_station/scenes/weather_station_scene_receiver_info.c delete mode 100644 applications/external/weather_station/scenes/weather_station_scene_start.c delete mode 100644 applications/external/weather_station/views/weather_station_receiver.c delete mode 100644 applications/external/weather_station/views/weather_station_receiver.h delete mode 100644 applications/external/weather_station/views/weather_station_receiver_info.c delete mode 100644 applications/external/weather_station/views/weather_station_receiver_info.h delete mode 100644 applications/external/weather_station/weather_station_10px.png delete mode 100644 applications/external/weather_station/weather_station_app.c delete mode 100644 applications/external/weather_station/weather_station_app_i.c delete mode 100644 applications/external/weather_station/weather_station_app_i.h delete mode 100644 applications/external/weather_station/weather_station_history.c delete mode 100644 applications/external/weather_station/weather_station_history.h rename applications/{external => main}/clock/application.fam (100%) rename applications/{external => main}/clock/clock.png (100%) rename applications/{external => main}/clock/clock_app.c (100%) rename applications/{external => main}/music_player/application.fam (100%) rename applications/{external => main}/music_player/icons/music_10px.png (100%) rename applications/{external => main}/music_player/music_player.c (100%) rename applications/{external => main}/snake_game/application.fam (100%) rename applications/{external => main}/snake_game/snake_10px.png (100%) rename applications/{external => main}/snake_game/snake_game.c (100%) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index bed54dfa045..6626f9ae2a4 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -22,9 +22,6 @@ /applications/main/subghz/ @skotopes @DrZlo13 @hedger @Skorpionm /applications/main/u2f/ @skotopes @DrZlo13 @hedger @nminaylov -/applications/external/bt_hid_app/ @skotopes @DrZlo13 @hedger @gornekich -/applications/external/picopass/ @skotopes @DrZlo13 @hedger @gornekich - /applications/services/bt/ @skotopes @DrZlo13 @hedger @gornekich /applications/services/cli/ @skotopes @DrZlo13 @hedger @nminaylov /applications/services/crypto/ @skotopes @DrZlo13 @hedger @nminaylov diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9fbcb16ebeb..b9eb4d7097d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -201,7 +201,7 @@ jobs: - name: Build example & external apps with uFBT run: | - for appdir in 'applications/external' 'applications/examples'; do + for appdir in 'applications/examples'; do for app in $(find "$appdir" -maxdepth 1 -mindepth 1 -type d); do pushd $app TARGETS_FAM=$(grep "targets" application.fam || echo "${{ matrix.target }}") diff --git a/.gitmodules b/.gitmodules index 4fba0483eb7..52cf4a207b7 100644 --- a/.gitmodules +++ b/.gitmodules @@ -26,9 +26,6 @@ [submodule "lib/cxxheaderparser"] path = lib/cxxheaderparser url = https://github.com/robotpy/cxxheaderparser.git -[submodule "applications/external/dap_link/lib/free-dap"] - path = applications/external/dap_link/lib/free-dap - url = https://github.com/ataradov/free-dap.git [submodule "lib/heatshrink"] path = lib/heatshrink url = https://github.com/flipperdevices/heatshrink.git diff --git a/.pvsoptions b/.pvsoptions index b25d4633e21..2e0b2fb8d24 100644 --- a/.pvsoptions +++ b/.pvsoptions @@ -1 +1 @@ ---ignore-ccache -C gccarm --rules-config .pvsconfig -e lib/cmsis_core -e lib/fatfs -e lib/fnv1a-hash -e lib/FreeRTOS-Kernel -e lib/heatshrink -e lib/libusb_stm32 -e lib/littlefs -e lib/mbedtls -e lib/micro-ecc -e lib/microtar -e lib/mlib -e lib/qrcode -e lib/ST25RFAL002 -e lib/stm32wb_cmsis -e lib/stm32wb_copro -e lib/stm32wb_hal -e lib/u8g2 -e lib/nanopb -e */arm-none-eabi/* -e applications/external/dap_link/lib/free-dap +--ignore-ccache -C gccarm --rules-config .pvsconfig -e lib/cmsis_core -e lib/fatfs -e lib/fnv1a-hash -e lib/FreeRTOS-Kernel -e lib/heatshrink -e lib/libusb_stm32 -e lib/littlefs -e lib/mbedtls -e lib/micro-ecc -e lib/microtar -e lib/mlib -e lib/qrcode -e lib/ST25RFAL002 -e lib/stm32wb_cmsis -e lib/stm32wb_copro -e lib/stm32wb_hal -e lib/u8g2 -e lib/nanopb -e */arm-none-eabi/* diff --git a/applications/ReadMe.md b/applications/ReadMe.md index 10e54ce2257..91ef02bbb6e 100644 --- a/applications/ReadMe.md +++ b/applications/ReadMe.md @@ -33,22 +33,10 @@ Applications for main Flipper menu. - `nfc` - NFC application, HF rfid, EMV and etc - `subghz` - SubGhz application, 433 fobs and etc - `u2f` - U2F Application - - -## External - -External applications deployed to SD Card - - `clock` - Clock application -- `dap_link` - DAP Link OnChip debugger -- `hid_app` - USB/BT Remote controller - `music_player` - Music player app (demo) -- `nfc_magic` - NFC MFC Magic card application -- `picopass` - Picopass reader / writer -- `signal_generator` - Signal generator app: PWM and clock generator - `snake_game` - Snake game application -- `spi_mem_manager` - SPI Memory reader / flasher -- `weather_station` - SubGHz weather station + ## services diff --git a/applications/external/application.fam b/applications/external/application.fam deleted file mode 100644 index 12dc1cc1ae5..00000000000 --- a/applications/external/application.fam +++ /dev/null @@ -1,6 +0,0 @@ -# Placeholder -App( - appid="external_apps", - name="External apps bundle", - apptype=FlipperAppType.METAPACKAGE, -) diff --git a/applications/external/avr_isp_programmer/application.fam b/applications/external/avr_isp_programmer/application.fam deleted file mode 100644 index 19556d03d3a..00000000000 --- a/applications/external/avr_isp_programmer/application.fam +++ /dev/null @@ -1,17 +0,0 @@ -App( - appid="avr_isp", - name="AVR Flasher", - apptype=FlipperAppType.EXTERNAL, - entry_point="avr_isp_app", - requires=["gui"], - stack_size=4 * 1024, - order=20, - fap_icon="avr_app_icon_10x10.png", - fap_category="GPIO", - fap_icon_assets="images", - fap_private_libs=[ - Lib( - name="driver", - ), - ], -) diff --git a/applications/external/avr_isp_programmer/avr_app_icon_10x10.png b/applications/external/avr_isp_programmer/avr_app_icon_10x10.png deleted file mode 100644 index 533787fe3569f70196d3f71e8373102dfd3967a0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3614 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 Vpqb^9w@m;7?5&;gRaoD1{{|C}E&c!i diff --git a/applications/external/avr_isp_programmer/avr_isp_app.c b/applications/external/avr_isp_programmer/avr_isp_app.c deleted file mode 100644 index 740dc3610a6..00000000000 --- a/applications/external/avr_isp_programmer/avr_isp_app.c +++ /dev/null @@ -1,179 +0,0 @@ -#include "avr_isp_app_i.h" - -static bool avr_isp_app_custom_event_callback(void* context, uint32_t event) { - furi_assert(context); - AvrIspApp* app = context; - return scene_manager_handle_custom_event(app->scene_manager, event); -} - -static bool avr_isp_app_back_event_callback(void* context) { - furi_assert(context); - AvrIspApp* app = context; - return scene_manager_handle_back_event(app->scene_manager); -} - -static void avr_isp_app_tick_event_callback(void* context) { - furi_assert(context); - AvrIspApp* app = context; - scene_manager_handle_tick_event(app->scene_manager); -} - -AvrIspApp* avr_isp_app_alloc() { - AvrIspApp* app = malloc(sizeof(AvrIspApp)); - - app->file_path = furi_string_alloc(); - furi_string_set(app->file_path, STORAGE_APP_DATA_PATH_PREFIX); - app->error = AvrIspErrorNoError; - - // GUI - app->gui = furi_record_open(RECORD_GUI); - - // View Dispatcher - app->view_dispatcher = view_dispatcher_alloc(); - app->scene_manager = scene_manager_alloc(&avr_isp_scene_handlers, app); - view_dispatcher_enable_queue(app->view_dispatcher); - - view_dispatcher_set_event_callback_context(app->view_dispatcher, app); - view_dispatcher_set_custom_event_callback( - app->view_dispatcher, avr_isp_app_custom_event_callback); - view_dispatcher_set_navigation_event_callback( - app->view_dispatcher, avr_isp_app_back_event_callback); - view_dispatcher_set_tick_event_callback( - app->view_dispatcher, avr_isp_app_tick_event_callback, 100); - - view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); - - // Open Notification record - app->notifications = furi_record_open(RECORD_NOTIFICATION); - - // SubMenu - app->submenu = submenu_alloc(); - view_dispatcher_add_view( - app->view_dispatcher, AvrIspViewSubmenu, submenu_get_view(app->submenu)); - - // Widget - app->widget = widget_alloc(); - view_dispatcher_add_view(app->view_dispatcher, AvrIspViewWidget, widget_get_view(app->widget)); - - // Text Input - app->text_input = text_input_alloc(); - view_dispatcher_add_view( - app->view_dispatcher, AvrIspViewTextInput, text_input_get_view(app->text_input)); - - // Popup - app->popup = popup_alloc(); - view_dispatcher_add_view(app->view_dispatcher, AvrIspViewPopup, popup_get_view(app->popup)); - - //Dialog - app->dialogs = furi_record_open(RECORD_DIALOGS); - - // Programmer view - app->avr_isp_programmer_view = avr_isp_programmer_view_alloc(); - view_dispatcher_add_view( - app->view_dispatcher, - AvrIspViewProgrammer, - avr_isp_programmer_view_get_view(app->avr_isp_programmer_view)); - - // Reader view - app->avr_isp_reader_view = avr_isp_reader_view_alloc(); - view_dispatcher_add_view( - app->view_dispatcher, - AvrIspViewReader, - avr_isp_reader_view_get_view(app->avr_isp_reader_view)); - - // Writer view - app->avr_isp_writer_view = avr_isp_writer_view_alloc(); - view_dispatcher_add_view( - app->view_dispatcher, - AvrIspViewWriter, - avr_isp_writer_view_get_view(app->avr_isp_writer_view)); - - // Chip detect view - app->avr_isp_chip_detect_view = avr_isp_chip_detect_view_alloc(); - view_dispatcher_add_view( - app->view_dispatcher, - AvrIspViewChipDetect, - avr_isp_chip_detect_view_get_view(app->avr_isp_chip_detect_view)); - - // Enable 5v power, multiple attempts to avoid issues with power chip protection false triggering - uint8_t attempts = 0; - while(!furi_hal_power_is_otg_enabled() && attempts++ < 5) { - furi_hal_power_enable_otg(); - furi_delay_ms(10); - } - - scene_manager_next_scene(app->scene_manager, AvrIspSceneStart); - - return app; -} //-V773 - -void avr_isp_app_free(AvrIspApp* app) { - furi_assert(app); - - // Disable 5v power - if(furi_hal_power_is_otg_enabled()) { - furi_hal_power_disable_otg(); - } - - // Submenu - view_dispatcher_remove_view(app->view_dispatcher, AvrIspViewSubmenu); - submenu_free(app->submenu); - - // Widget - view_dispatcher_remove_view(app->view_dispatcher, AvrIspViewWidget); - widget_free(app->widget); - - // TextInput - view_dispatcher_remove_view(app->view_dispatcher, AvrIspViewTextInput); - text_input_free(app->text_input); - - // Popup - view_dispatcher_remove_view(app->view_dispatcher, AvrIspViewPopup); - popup_free(app->popup); - - //Dialog - furi_record_close(RECORD_DIALOGS); - - // Programmer view - view_dispatcher_remove_view(app->view_dispatcher, AvrIspViewProgrammer); - avr_isp_programmer_view_free(app->avr_isp_programmer_view); - - // Reader view - view_dispatcher_remove_view(app->view_dispatcher, AvrIspViewReader); - avr_isp_reader_view_free(app->avr_isp_reader_view); - - // Writer view - view_dispatcher_remove_view(app->view_dispatcher, AvrIspViewWriter); - avr_isp_writer_view_free(app->avr_isp_writer_view); - - // Chip detect view - view_dispatcher_remove_view(app->view_dispatcher, AvrIspViewChipDetect); - avr_isp_chip_detect_view_free(app->avr_isp_chip_detect_view); - - // View dispatcher - view_dispatcher_free(app->view_dispatcher); - scene_manager_free(app->scene_manager); - - // Notifications - furi_record_close(RECORD_NOTIFICATION); - app->notifications = NULL; - - // Close records - furi_record_close(RECORD_GUI); - - // Path strings - furi_string_free(app->file_path); - - free(app); -} - -int32_t avr_isp_app(void* p) { - UNUSED(p); - AvrIspApp* avr_isp_app = avr_isp_app_alloc(); - - view_dispatcher_run(avr_isp_app->view_dispatcher); - - avr_isp_app_free(avr_isp_app); - - return 0; -} diff --git a/applications/external/avr_isp_programmer/avr_isp_app_i.c b/applications/external/avr_isp_programmer/avr_isp_app_i.c deleted file mode 100644 index 7a7fa6d7f18..00000000000 --- a/applications/external/avr_isp_programmer/avr_isp_app_i.c +++ /dev/null @@ -1,31 +0,0 @@ -#include "avr_isp_app_i.h" -#include -#include - -#define TAG "AvrIsp" - -bool avr_isp_load_from_file(AvrIspApp* app) { - furi_assert(app); - - FuriString* file_path = furi_string_alloc(); - FuriString* file_name = furi_string_alloc(); - - DialogsFileBrowserOptions browser_options; - dialog_file_browser_set_basic_options( - &browser_options, AVR_ISP_APP_EXTENSION, &I_avr_app_icon_10x10); - browser_options.base_path = STORAGE_APP_DATA_PATH_PREFIX; - - // Input events and views are managed by file_select - bool res = dialog_file_browser_show(app->dialogs, file_path, app->file_path, &browser_options); - - if(res) { - path_extract_dirname(furi_string_get_cstr(file_path), app->file_path); - path_extract_filename(file_path, file_name, true); - strncpy(app->file_name_tmp, furi_string_get_cstr(file_name), AVR_ISP_MAX_LEN_NAME); - } - - furi_string_free(file_name); - furi_string_free(file_path); - - return res; -} diff --git a/applications/external/avr_isp_programmer/avr_isp_app_i.h b/applications/external/avr_isp_programmer/avr_isp_app_i.h deleted file mode 100644 index 17c69f8f2e2..00000000000 --- a/applications/external/avr_isp_programmer/avr_isp_app_i.h +++ /dev/null @@ -1,44 +0,0 @@ -#pragma once - -#include "helpers/avr_isp_types.h" -#include - -#include "scenes/avr_isp_scene.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "views/avr_isp_view_programmer.h" -#include "views/avr_isp_view_reader.h" -#include "views/avr_isp_view_writer.h" -#include "views/avr_isp_view_chip_detect.h" - -#define AVR_ISP_MAX_LEN_NAME 64 - -typedef struct { - Gui* gui; - ViewDispatcher* view_dispatcher; - SceneManager* scene_manager; - NotificationApp* notifications; - DialogsApp* dialogs; - Popup* popup; - Submenu* submenu; - Widget* widget; - TextInput* text_input; - FuriString* file_path; - char file_name_tmp[AVR_ISP_MAX_LEN_NAME]; - AvrIspProgrammerView* avr_isp_programmer_view; - AvrIspReaderView* avr_isp_reader_view; - AvrIspWriterView* avr_isp_writer_view; - AvrIspChipDetectView* avr_isp_chip_detect_view; - AvrIspError error; -} AvrIspApp; - -bool avr_isp_load_from_file(AvrIspApp* app); \ No newline at end of file diff --git a/applications/external/avr_isp_programmer/helpers/avr_isp.c b/applications/external/avr_isp_programmer/helpers/avr_isp.c deleted file mode 100644 index 283c17bfd48..00000000000 --- a/applications/external/avr_isp_programmer/helpers/avr_isp.c +++ /dev/null @@ -1,496 +0,0 @@ -#include "avr_isp.h" -#include "../lib/driver/avr_isp_prog_cmd.h" -#include "../lib/driver/avr_isp_spi_sw.h" - -#include - -#define AVR_ISP_PROG_TX_RX_BUF_SIZE 320 -#define TAG "AvrIsp" - -struct AvrIsp { - AvrIspSpiSw* spi; - bool pmode; - AvrIspCallback callback; - void* context; -}; - -AvrIsp* avr_isp_alloc(void) { - AvrIsp* instance = malloc(sizeof(AvrIsp)); - return instance; -} - -void avr_isp_free(AvrIsp* instance) { - furi_assert(instance); - - if(instance->spi) avr_isp_end_pmode(instance); - free(instance); -} - -void avr_isp_set_tx_callback(AvrIsp* instance, AvrIspCallback callback, void* context) { - furi_assert(instance); - furi_assert(context); - - instance->callback = callback; - instance->context = context; -} - -uint8_t avr_isp_spi_transaction( - AvrIsp* instance, - uint8_t cmd, - uint8_t addr_hi, - uint8_t addr_lo, - uint8_t data) { - furi_assert(instance); - - avr_isp_spi_sw_txrx(instance->spi, cmd); - avr_isp_spi_sw_txrx(instance->spi, addr_hi); - avr_isp_spi_sw_txrx(instance->spi, addr_lo); - return avr_isp_spi_sw_txrx(instance->spi, data); -} - -static bool avr_isp_set_pmode(AvrIsp* instance, uint8_t a, uint8_t b, uint8_t c, uint8_t d) { - furi_assert(instance); - - uint8_t res = 0; - avr_isp_spi_sw_txrx(instance->spi, a); - avr_isp_spi_sw_txrx(instance->spi, b); - res = avr_isp_spi_sw_txrx(instance->spi, c); - avr_isp_spi_sw_txrx(instance->spi, d); - return res == 0x53; -} - -void avr_isp_end_pmode(AvrIsp* instance) { - furi_assert(instance); - - if(instance->pmode) { - avr_isp_spi_sw_res_set(instance->spi, true); - // We're about to take the target out of reset - // so configure SPI pins as input - if(instance->spi) avr_isp_spi_sw_free(instance->spi); - instance->spi = NULL; - } - - instance->pmode = false; -} - -static bool avr_isp_start_pmode(AvrIsp* instance, AvrIspSpiSwSpeed spi_speed) { - furi_assert(instance); - - // Reset target before driving PIN_SCK or PIN_MOSI - - // SPI.begin() will configure SS as output, - // so SPI master mode is selected. - // We have defined RESET as pin 10, - // which for many arduino's is not the SS pin. - // So we have to configure RESET as output here, - // (reset_target() first sets the correct level) - if(instance->spi) avr_isp_spi_sw_free(instance->spi); - instance->spi = avr_isp_spi_sw_init(spi_speed); - - avr_isp_spi_sw_res_set(instance->spi, false); - // See avr datasheets, chapter "SERIAL_PRG Programming Algorithm": - - // Pulse RESET after PIN_SCK is low: - avr_isp_spi_sw_sck_set(instance->spi, false); - - // discharge PIN_SCK, value arbitrally chosen - furi_delay_ms(20); - avr_isp_spi_sw_res_set(instance->spi, true); - - // Pulse must be minimum 2 target CPU speed cycles - // so 100 usec is ok for CPU speeds above 20KHz - furi_delay_ms(1); - - avr_isp_spi_sw_res_set(instance->spi, false); - - // Send the enable programming command: - // datasheet: must be > 20 msec - furi_delay_ms(50); - if(avr_isp_set_pmode(instance, AVR_ISP_SET_PMODE)) { - instance->pmode = true; - return true; - } - return false; -} - -bool avr_isp_auto_set_spi_speed_start_pmode(AvrIsp* instance) { - furi_assert(instance); - - AvrIspSpiSwSpeed spi_speed[] = { - AvrIspSpiSwSpeed1Mhz, - AvrIspSpiSwSpeed400Khz, - AvrIspSpiSwSpeed250Khz, - AvrIspSpiSwSpeed125Khz, - AvrIspSpiSwSpeed60Khz, - AvrIspSpiSwSpeed40Khz, - AvrIspSpiSwSpeed20Khz, - AvrIspSpiSwSpeed10Khz, - AvrIspSpiSwSpeed5Khz, - AvrIspSpiSwSpeed1Khz, - }; - for(uint8_t i = 0; i < COUNT_OF(spi_speed); i++) { - if(avr_isp_start_pmode(instance, spi_speed[i])) { - AvrIspSignature sig = avr_isp_read_signature(instance); - AvrIspSignature sig_examination = avr_isp_read_signature(instance); //-V656 - uint8_t y = 0; - while(y < 8) { - if(memcmp((uint8_t*)&sig, (uint8_t*)&sig_examination, sizeof(AvrIspSignature)) != - 0) - break; - sig_examination = avr_isp_read_signature(instance); - y++; - } - if(y == 8) { - if(spi_speed[i] > AvrIspSpiSwSpeed1Mhz) { - if(i < (COUNT_OF(spi_speed) - 1)) { - avr_isp_end_pmode(instance); - i++; - return avr_isp_start_pmode(instance, spi_speed[i]); - } - } - return true; - } - } - } - - if(instance->spi) { - avr_isp_spi_sw_free(instance->spi); - instance->spi = NULL; - } - - return false; -} - -static void avr_isp_commit(AvrIsp* instance, uint16_t addr, uint8_t data) { - furi_assert(instance); - - avr_isp_spi_transaction(instance, AVR_ISP_COMMIT(addr)); - /* polling flash */ - if(data == 0xFF) { - furi_delay_ms(5); - } else { - /* polling flash */ - uint32_t starttime = furi_get_tick(); - while((furi_get_tick() - starttime) < 30) { - if(avr_isp_spi_transaction(instance, AVR_ISP_READ_FLASH_HI(addr)) != 0xFF) { - break; - }; - } - } -} - -static uint16_t avr_isp_current_page(AvrIsp* instance, uint32_t addr, uint16_t page_size) { - furi_assert(instance); - - uint16_t page = 0; - switch(page_size) { - case 32: - page = addr & 0xFFFFFFF0; - break; - case 64: - page = addr & 0xFFFFFFE0; - break; - case 128: - page = addr & 0xFFFFFFC0; - break; - case 256: - page = addr & 0xFFFFFF80; - break; - - default: - page = addr; - break; - } - - return page; -} - -static bool avr_isp_flash_write_pages( - AvrIsp* instance, - uint16_t addr, - uint16_t page_size, - uint8_t* data, - uint32_t data_size) { - furi_assert(instance); - - size_t x = 0; - uint16_t page = avr_isp_current_page(instance, addr, page_size); - - while(x < data_size) { - if(page != avr_isp_current_page(instance, addr, page_size)) { - avr_isp_commit(instance, page, data[x - 1]); - page = avr_isp_current_page(instance, addr, page_size); - } - avr_isp_spi_transaction(instance, AVR_ISP_WRITE_FLASH_LO(addr, data[x++])); - avr_isp_spi_transaction(instance, AVR_ISP_WRITE_FLASH_HI(addr, data[x++])); - addr++; - } - avr_isp_commit(instance, page, data[x - 1]); - return true; -} - -bool avr_isp_erase_chip(AvrIsp* instance) { - furi_assert(instance); - - bool ret = false; - if(!instance->pmode) avr_isp_auto_set_spi_speed_start_pmode(instance); - if(instance->pmode) { - avr_isp_spi_transaction(instance, AVR_ISP_ERASE_CHIP); - furi_delay_ms(100); - avr_isp_end_pmode(instance); - ret = true; - } - return ret; -} - -static bool - avr_isp_eeprom_write(AvrIsp* instance, uint16_t addr, uint8_t* data, uint32_t data_size) { - furi_assert(instance); - - for(uint16_t i = 0; i < data_size; i++) { - avr_isp_spi_transaction(instance, AVR_ISP_WRITE_EEPROM(addr, data[i])); - furi_delay_ms(10); - addr++; - } - return true; -} - -bool avr_isp_write_page( - AvrIsp* instance, - uint32_t mem_type, - uint32_t mem_size, - uint16_t addr, - uint16_t page_size, - uint8_t* data, - uint32_t data_size) { - furi_assert(instance); - - bool ret = false; - switch(mem_type) { - case STK_SET_FLASH_TYPE: - if((addr + data_size / 2) <= mem_size) { - ret = avr_isp_flash_write_pages(instance, addr, page_size, data, data_size); - } - break; - - case STK_SET_EEPROM_TYPE: - if((addr + data_size) <= mem_size) { - ret = avr_isp_eeprom_write(instance, addr, data, data_size); - } - break; - - default: - furi_crash(TAG " Incorrect mem type."); - break; - } - - return ret; -} - -static bool avr_isp_flash_read_page( - AvrIsp* instance, - uint16_t addr, - uint16_t page_size, - uint8_t* data, - uint32_t data_size) { - furi_assert(instance); - - if(page_size > data_size) return false; - for(uint16_t i = 0; i < page_size; i += 2) { - data[i] = avr_isp_spi_transaction(instance, AVR_ISP_READ_FLASH_LO(addr)); - data[i + 1] = avr_isp_spi_transaction(instance, AVR_ISP_READ_FLASH_HI(addr)); - addr++; - } - return true; -} - -static bool avr_isp_eeprom_read_page( - AvrIsp* instance, - uint16_t addr, - uint16_t page_size, - uint8_t* data, - uint32_t data_size) { - furi_assert(instance); - - if(page_size > data_size) return false; - for(uint16_t i = 0; i < page_size; i++) { - data[i] = avr_isp_spi_transaction(instance, AVR_ISP_READ_EEPROM(addr)); - addr++; - } - return true; -} - -bool avr_isp_read_page( - AvrIsp* instance, - uint32_t mem_type, - uint16_t addr, - uint16_t page_size, - uint8_t* data, - uint32_t data_size) { - furi_assert(instance); - - bool res = false; - if(mem_type == STK_SET_FLASH_TYPE) - res = avr_isp_flash_read_page(instance, addr, page_size, data, data_size); - if(mem_type == STK_SET_EEPROM_TYPE) - res = avr_isp_eeprom_read_page(instance, addr, page_size, data, data_size); - - return res; -} - -AvrIspSignature avr_isp_read_signature(AvrIsp* instance) { - furi_assert(instance); - - AvrIspSignature signature; - signature.vendor = avr_isp_spi_transaction(instance, AVR_ISP_READ_VENDOR); - signature.part_family = avr_isp_spi_transaction(instance, AVR_ISP_READ_PART_FAMILY); - signature.part_number = avr_isp_spi_transaction(instance, AVR_ISP_READ_PART_NUMBER); - return signature; -} - -uint8_t avr_isp_read_lock_byte(AvrIsp* instance) { - furi_assert(instance); - - uint8_t data = 0; - uint32_t starttime = furi_get_tick(); - while((furi_get_tick() - starttime) < 300) { - data = avr_isp_spi_transaction(instance, AVR_ISP_READ_LOCK_BYTE); - if(avr_isp_spi_transaction(instance, AVR_ISP_READ_LOCK_BYTE) == data) { - break; - }; - data = 0x00; - } - return data; -} - -bool avr_isp_write_lock_byte(AvrIsp* instance, uint8_t lock) { - furi_assert(instance); - - bool ret = false; - if(avr_isp_read_lock_byte(instance) == lock) { - ret = true; - } else { - avr_isp_spi_transaction(instance, AVR_ISP_WRITE_LOCK_BYTE(lock)); - /* polling lock byte */ - uint32_t starttime = furi_get_tick(); - while((furi_get_tick() - starttime) < 30) { - if(avr_isp_spi_transaction(instance, AVR_ISP_READ_LOCK_BYTE) == lock) { - ret = true; - break; - }; - } - } - return ret; -} - -uint8_t avr_isp_read_fuse_low(AvrIsp* instance) { - furi_assert(instance); - - uint8_t data = 0; - uint32_t starttime = furi_get_tick(); - while((furi_get_tick() - starttime) < 300) { - data = avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_LOW); - if(avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_LOW) == data) { - break; - }; - data = 0x00; - } - return data; -} - -bool avr_isp_write_fuse_low(AvrIsp* instance, uint8_t lfuse) { - furi_assert(instance); - - bool ret = false; - if(avr_isp_read_fuse_low(instance) == lfuse) { - ret = true; - } else { - avr_isp_spi_transaction(instance, AVR_ISP_WRITE_FUSE_LOW(lfuse)); - /* polling fuse */ - uint32_t starttime = furi_get_tick(); - while((furi_get_tick() - starttime) < 30) { - if(avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_LOW) == lfuse) { - ret = true; - break; - }; - } - } - return ret; -} - -uint8_t avr_isp_read_fuse_high(AvrIsp* instance) { - furi_assert(instance); - - uint8_t data = 0; - uint32_t starttime = furi_get_tick(); - while((furi_get_tick() - starttime) < 300) { - data = avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_HIGH); - if(avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_HIGH) == data) { - break; - }; - data = 0x00; - } - return data; -} - -bool avr_isp_write_fuse_high(AvrIsp* instance, uint8_t hfuse) { - furi_assert(instance); - - bool ret = false; - if(avr_isp_read_fuse_high(instance) == hfuse) { - ret = true; - } else { - avr_isp_spi_transaction(instance, AVR_ISP_WRITE_FUSE_HIGH(hfuse)); - /* polling fuse */ - uint32_t starttime = furi_get_tick(); - while((furi_get_tick() - starttime) < 30) { - if(avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_HIGH) == hfuse) { - ret = true; - break; - }; - } - } - return ret; -} - -uint8_t avr_isp_read_fuse_extended(AvrIsp* instance) { - furi_assert(instance); - - uint8_t data = 0; - uint32_t starttime = furi_get_tick(); - while((furi_get_tick() - starttime) < 300) { - data = avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_EXTENDED); - if(avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_EXTENDED) == data) { - break; - }; - data = 0x00; - } - return data; -} - -bool avr_isp_write_fuse_extended(AvrIsp* instance, uint8_t efuse) { - furi_assert(instance); - - bool ret = false; - if(avr_isp_read_fuse_extended(instance) == efuse) { - ret = true; - } else { - avr_isp_spi_transaction(instance, AVR_ISP_WRITE_FUSE_EXTENDED(efuse)); - /* polling fuse */ - uint32_t starttime = furi_get_tick(); - while((furi_get_tick() - starttime) < 30) { - if(avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_EXTENDED) == efuse) { - ret = true; - break; - }; - } - } - return ret; -} - -void avr_isp_write_extended_addr(AvrIsp* instance, uint8_t extended_addr) { - furi_assert(instance); - - avr_isp_spi_transaction(instance, AVR_ISP_EXTENDED_ADDR(extended_addr)); - furi_delay_ms(10); -} \ No newline at end of file diff --git a/applications/external/avr_isp_programmer/helpers/avr_isp.h b/applications/external/avr_isp_programmer/helpers/avr_isp.h deleted file mode 100644 index 476fc3d6427..00000000000 --- a/applications/external/avr_isp_programmer/helpers/avr_isp.h +++ /dev/null @@ -1,70 +0,0 @@ -#pragma once - -#include - -typedef struct AvrIsp AvrIsp; -typedef void (*AvrIspCallback)(void* context); - -struct AvrIspSignature { - uint8_t vendor; - uint8_t part_family; - uint8_t part_number; -}; - -typedef struct AvrIspSignature AvrIspSignature; - -AvrIsp* avr_isp_alloc(void); - -void avr_isp_free(AvrIsp* instance); - -void avr_isp_set_tx_callback(AvrIsp* instance, AvrIspCallback callback, void* context); - -bool avr_isp_auto_set_spi_speed_start_pmode(AvrIsp* instance); - -AvrIspSignature avr_isp_read_signature(AvrIsp* instance); - -void avr_isp_end_pmode(AvrIsp* instance); - -bool avr_isp_erase_chip(AvrIsp* instance); - -uint8_t avr_isp_spi_transaction( - AvrIsp* instance, - uint8_t cmd, - uint8_t addr_hi, - uint8_t addr_lo, - uint8_t data); - -bool avr_isp_read_page( - AvrIsp* instance, - uint32_t memtype, - uint16_t addr, - uint16_t page_size, - uint8_t* data, - uint32_t data_size); - -bool avr_isp_write_page( - AvrIsp* instance, - uint32_t mem_type, - uint32_t mem_size, - uint16_t addr, - uint16_t page_size, - uint8_t* data, - uint32_t data_size); - -uint8_t avr_isp_read_lock_byte(AvrIsp* instance); - -bool avr_isp_write_lock_byte(AvrIsp* instance, uint8_t lock); - -uint8_t avr_isp_read_fuse_low(AvrIsp* instance); - -bool avr_isp_write_fuse_low(AvrIsp* instance, uint8_t lfuse); - -uint8_t avr_isp_read_fuse_high(AvrIsp* instance); - -bool avr_isp_write_fuse_high(AvrIsp* instance, uint8_t hfuse); - -uint8_t avr_isp_read_fuse_extended(AvrIsp* instance); - -bool avr_isp_write_fuse_extended(AvrIsp* instance, uint8_t efuse); - -void avr_isp_write_extended_addr(AvrIsp* instance, uint8_t extended_addr); \ No newline at end of file diff --git a/applications/external/avr_isp_programmer/helpers/avr_isp_event.h b/applications/external/avr_isp_programmer/helpers/avr_isp_event.h deleted file mode 100644 index c704b5b356a..00000000000 --- a/applications/external/avr_isp_programmer/helpers/avr_isp_event.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -typedef enum { - //SubmenuIndex - SubmenuIndexAvrIspProgrammer = 10, - SubmenuIndexAvrIspReader, - SubmenuIndexAvrIspWriter, - SubmenuIndexAvrIsWiring, - SubmenuIndexAvrIspAbout, - - //AvrIspCustomEvent - AvrIspCustomEventSceneChipDetectOk = 100, - AvrIspCustomEventSceneReadingOk, - AvrIspCustomEventSceneWritingOk, - AvrIspCustomEventSceneErrorVerification, - AvrIspCustomEventSceneErrorReading, - AvrIspCustomEventSceneErrorWriting, - AvrIspCustomEventSceneErrorWritingFuse, - AvrIspCustomEventSceneInputName, - AvrIspCustomEventSceneSuccess, - AvrIspCustomEventSceneExit, - AvrIspCustomEventSceneExitStartMenu, -} AvrIspCustomEvent; diff --git a/applications/external/avr_isp_programmer/helpers/avr_isp_types.h b/applications/external/avr_isp_programmer/helpers/avr_isp_types.h deleted file mode 100644 index 5e174ec3b9c..00000000000 --- a/applications/external/avr_isp_programmer/helpers/avr_isp_types.h +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once - -#include -#include - -#define AVR_ISP_VERSION_APP "0.1" -#define AVR_ISP_DEVELOPED "SkorP" -#define AVR_ISP_GITHUB "https://github.com/flipperdevices/flipperzero-firmware" - -#define AVR_ISP_APP_FILE_VERSION 1 -#define AVR_ISP_APP_FILE_TYPE "Flipper Dump AVR" -#define AVR_ISP_APP_EXTENSION ".avr" - -typedef enum { - //AvrIspViewVariableItemList, - AvrIspViewSubmenu, - AvrIspViewProgrammer, - AvrIspViewReader, - AvrIspViewWriter, - AvrIspViewWidget, - AvrIspViewPopup, - AvrIspViewTextInput, - AvrIspViewChipDetect, -} AvrIspView; - -typedef enum { - AvrIspErrorNoError, - AvrIspErrorReading, - AvrIspErrorWriting, - AvrIspErrorVerification, - AvrIspErrorWritingFuse, -} AvrIspError; \ No newline at end of file diff --git a/applications/external/avr_isp_programmer/helpers/avr_isp_worker.c b/applications/external/avr_isp_programmer/helpers/avr_isp_worker.c deleted file mode 100644 index dfe1f43c2c3..00000000000 --- a/applications/external/avr_isp_programmer/helpers/avr_isp_worker.c +++ /dev/null @@ -1,266 +0,0 @@ -#include "avr_isp_worker.h" -#include -#include "../lib/driver/avr_isp_prog.h" -#include "../lib/driver/avr_isp_prog_cmd.h" -#include "../lib/driver/avr_isp_chip_arr.h" - -#include - -#define TAG "AvrIspWorker" - -typedef enum { - AvrIspWorkerEvtStop = (1 << 0), - - AvrIspWorkerEvtRx = (1 << 1), - AvrIspWorkerEvtTxCoplete = (1 << 2), - AvrIspWorkerEvtTx = (1 << 3), - AvrIspWorkerEvtState = (1 << 4), - - //AvrIspWorkerEvtCfg = (1 << 5), - -} AvrIspWorkerEvt; - -struct AvrIspWorker { - FuriThread* thread; - volatile bool worker_running; - uint8_t connect_usb; - AvrIspWorkerCallback callback; - void* context; -}; - -#define AVR_ISP_WORKER_PROG_ALL_EVENTS (AvrIspWorkerEvtStop) -#define AVR_ISP_WORKER_ALL_EVENTS \ - (AvrIspWorkerEvtTx | AvrIspWorkerEvtTxCoplete | AvrIspWorkerEvtRx | AvrIspWorkerEvtStop | \ - AvrIspWorkerEvtState) - -//########################/* VCP CDC */############################################# -#include "usb_cdc.h" -#include -#include -#include - -#define AVR_ISP_VCP_CDC_CH 1 -#define AVR_ISP_VCP_CDC_PKT_LEN CDC_DATA_SZ -#define AVR_ISP_VCP_UART_RX_BUF_SIZE (AVR_ISP_VCP_CDC_PKT_LEN * 5) - -static void vcp_on_cdc_tx_complete(void* context); -static void vcp_on_cdc_rx(void* context); -static void vcp_state_callback(void* context, uint8_t state); -static void vcp_on_cdc_control_line(void* context, uint8_t state); -static void vcp_on_line_config(void* context, struct usb_cdc_line_coding* config); - -static const CdcCallbacks cdc_cb = { - vcp_on_cdc_tx_complete, - vcp_on_cdc_rx, - vcp_state_callback, - vcp_on_cdc_control_line, - vcp_on_line_config, -}; - -/* VCP callbacks */ - -static void vcp_on_cdc_tx_complete(void* context) { - furi_assert(context); - AvrIspWorker* instance = context; - furi_thread_flags_set(furi_thread_get_id(instance->thread), AvrIspWorkerEvtTxCoplete); -} - -static void vcp_on_cdc_rx(void* context) { - furi_assert(context); - AvrIspWorker* instance = context; - furi_thread_flags_set(furi_thread_get_id(instance->thread), AvrIspWorkerEvtRx); -} - -static void vcp_state_callback(void* context, uint8_t state) { - UNUSED(context); - - AvrIspWorker* instance = context; - instance->connect_usb = state; - furi_thread_flags_set(furi_thread_get_id(instance->thread), AvrIspWorkerEvtState); -} - -static void vcp_on_cdc_control_line(void* context, uint8_t state) { - UNUSED(context); - UNUSED(state); -} - -static void vcp_on_line_config(void* context, struct usb_cdc_line_coding* config) { - UNUSED(context); - UNUSED(config); -} - -static void avr_isp_worker_vcp_cdc_init(void* context) { - furi_hal_usb_unlock(); - Cli* cli = furi_record_open(RECORD_CLI); - //close cli - cli_session_close(cli); - //disable callbacks VCP_CDC=0 - furi_hal_cdc_set_callbacks(0, NULL, NULL); - //set 2 cdc - furi_check(furi_hal_usb_set_config(&usb_cdc_dual, NULL) == true); - //open cli VCP_CDC=0 - cli_session_open(cli, &cli_vcp); - furi_record_close(RECORD_CLI); - - furi_hal_cdc_set_callbacks(AVR_ISP_VCP_CDC_CH, (CdcCallbacks*)&cdc_cb, context); -} - -static void avr_isp_worker_vcp_cdc_deinit(void) { - //disable callbacks AVR_ISP_VCP_CDC_CH - furi_hal_cdc_set_callbacks(AVR_ISP_VCP_CDC_CH, NULL, NULL); - - Cli* cli = furi_record_open(RECORD_CLI); - //close cli - cli_session_close(cli); - furi_hal_usb_unlock(); - //set 1 cdc - furi_check(furi_hal_usb_set_config(&usb_cdc_single, NULL) == true); - //open cli VCP_CDC=0 - cli_session_open(cli, &cli_vcp); - furi_record_close(RECORD_CLI); -} - -//################################################################################# - -static int32_t avr_isp_worker_prog_thread(void* context) { - AvrIspProg* prog = context; - FURI_LOG_D(TAG, "AvrIspProgWorker Start"); - while(1) { - if(furi_thread_flags_get() & AvrIspWorkerEvtStop) break; - avr_isp_prog_avrisp(prog); - } - FURI_LOG_D(TAG, "AvrIspProgWorker Stop"); - return 0; -} - -static void avr_isp_worker_prog_tx_data(void* context) { - furi_assert(context); - AvrIspWorker* instance = context; - furi_thread_flags_set(furi_thread_get_id(instance->thread), AvrIspWorkerEvtTx); -} - -/** Worker thread - * - * @param context - * @return exit code - */ -static int32_t avr_isp_worker_thread(void* context) { - AvrIspWorker* instance = context; - avr_isp_worker_vcp_cdc_init(instance); - - /* start PWM on &gpio_ext_pa4 */ - furi_hal_pwm_start(FuriHalPwmOutputIdLptim2PA4, 4000000, 50); - - AvrIspProg* prog = avr_isp_prog_init(); - avr_isp_prog_set_tx_callback(prog, avr_isp_worker_prog_tx_data, instance); - - uint8_t buf[AVR_ISP_VCP_UART_RX_BUF_SIZE]; - size_t len = 0; - - FuriThread* prog_thread = - furi_thread_alloc_ex("AvrIspProgWorker", 1024, avr_isp_worker_prog_thread, prog); - furi_thread_start(prog_thread); - - FURI_LOG_D(TAG, "Start"); - - while(instance->worker_running) { - uint32_t events = - furi_thread_flags_wait(AVR_ISP_WORKER_ALL_EVENTS, FuriFlagWaitAny, FuriWaitForever); - - if(events & AvrIspWorkerEvtRx) { - if(avr_isp_prog_spaces_rx(prog) >= AVR_ISP_VCP_CDC_PKT_LEN) { - len = furi_hal_cdc_receive(AVR_ISP_VCP_CDC_CH, buf, AVR_ISP_VCP_CDC_PKT_LEN); - // for(uint8_t i = 0; i < len; i++) { - // FURI_LOG_I(TAG, "--> %X", buf[i]); - // } - avr_isp_prog_rx(prog, buf, len); - } else { - furi_thread_flags_set(furi_thread_get_id(instance->thread), AvrIspWorkerEvtRx); - } - } - - if((events & AvrIspWorkerEvtTxCoplete) || (events & AvrIspWorkerEvtTx)) { - len = avr_isp_prog_tx(prog, buf, AVR_ISP_VCP_CDC_PKT_LEN); - - // for(uint8_t i = 0; i < len; i++) { - // FURI_LOG_I(TAG, "<-- %X", buf[i]); - // } - - if(len > 0) furi_hal_cdc_send(AVR_ISP_VCP_CDC_CH, buf, len); - } - - if(events & AvrIspWorkerEvtStop) { - break; - } - - if(events & AvrIspWorkerEvtState) { - if(instance->callback) - instance->callback(instance->context, (bool)instance->connect_usb); - } - } - - FURI_LOG_D(TAG, "Stop"); - - furi_thread_flags_set(furi_thread_get_id(prog_thread), AvrIspWorkerEvtStop); - avr_isp_prog_exit(prog); - furi_delay_ms(10); - furi_thread_join(prog_thread); - furi_thread_free(prog_thread); - - avr_isp_prog_free(prog); - furi_hal_pwm_stop(FuriHalPwmOutputIdLptim2PA4); - avr_isp_worker_vcp_cdc_deinit(); - return 0; -} - -AvrIspWorker* avr_isp_worker_alloc(void* context) { - furi_assert(context); - UNUSED(context); - AvrIspWorker* instance = malloc(sizeof(AvrIspWorker)); - - instance->thread = furi_thread_alloc_ex("AvrIspWorker", 2048, avr_isp_worker_thread, instance); - return instance; -} - -void avr_isp_worker_free(AvrIspWorker* instance) { - furi_assert(instance); - - furi_check(!instance->worker_running); - furi_thread_free(instance->thread); - free(instance); -} - -void avr_isp_worker_set_callback( - AvrIspWorker* instance, - AvrIspWorkerCallback callback, - void* context) { - furi_assert(instance); - - instance->callback = callback; - instance->context = context; -} - -void avr_isp_worker_start(AvrIspWorker* instance) { - furi_assert(instance); - furi_assert(!instance->worker_running); - - instance->worker_running = true; - - furi_thread_start(instance->thread); -} - -void avr_isp_worker_stop(AvrIspWorker* instance) { - furi_assert(instance); - furi_assert(instance->worker_running); - - instance->worker_running = false; - furi_thread_flags_set(furi_thread_get_id(instance->thread), AvrIspWorkerEvtStop); - - furi_thread_join(instance->thread); -} - -bool avr_isp_worker_is_running(AvrIspWorker* instance) { - furi_assert(instance); - - return instance->worker_running; -} diff --git a/applications/external/avr_isp_programmer/helpers/avr_isp_worker.h b/applications/external/avr_isp_programmer/helpers/avr_isp_worker.h deleted file mode 100644 index cd9897dff3f..00000000000 --- a/applications/external/avr_isp_programmer/helpers/avr_isp_worker.h +++ /dev/null @@ -1,49 +0,0 @@ -#pragma once - -#include - -typedef struct AvrIspWorker AvrIspWorker; - -typedef void (*AvrIspWorkerCallback)(void* context, bool connect_usb); - -/** Allocate AvrIspWorker - * - * @param context AvrIsp* context - * @return AvrIspWorker* - */ -AvrIspWorker* avr_isp_worker_alloc(void* context); - -/** Free AvrIspWorker - * - * @param instance AvrIspWorker instance - */ -void avr_isp_worker_free(AvrIspWorker* instance); - -/** Callback AvrIspWorker - * - * @param instance AvrIspWorker instance - * @param callback AvrIspWorkerOverrunCallback callback - * @param context - */ -void avr_isp_worker_set_callback( - AvrIspWorker* instance, - AvrIspWorkerCallback callback, - void* context); - -/** Start AvrIspWorker - * - * @param instance AvrIspWorker instance - */ -void avr_isp_worker_start(AvrIspWorker* instance); - -/** Stop AvrIspWorker - * - * @param instance AvrIspWorker instance - */ -void avr_isp_worker_stop(AvrIspWorker* instance); - -/** Check if worker is running - * @param instance AvrIspWorker instance - * @return bool - true if running - */ -bool avr_isp_worker_is_running(AvrIspWorker* instance); diff --git a/applications/external/avr_isp_programmer/helpers/avr_isp_worker_rw.c b/applications/external/avr_isp_programmer/helpers/avr_isp_worker_rw.c deleted file mode 100644 index b4c12cbc38a..00000000000 --- a/applications/external/avr_isp_programmer/helpers/avr_isp_worker_rw.c +++ /dev/null @@ -1,1157 +0,0 @@ -#include "avr_isp_worker_rw.h" -#include -#include "avr_isp_types.h" -#include "avr_isp.h" -#include "../lib/driver/avr_isp_prog_cmd.h" -#include "../lib/driver/avr_isp_chip_arr.h" - -#include "flipper_i32hex_file.h" -#include - -#include - -#define TAG "AvrIspWorkerRW" - -#define NAME_PATERN_FLASH_FILE "flash.hex" -#define NAME_PATERN_EEPROM_FILE "eeprom.hex" - -struct AvrIspWorkerRW { - AvrIsp* avr_isp; - FuriThread* thread; - volatile bool worker_running; - - uint32_t chip_arr_ind; - bool chip_detect; - uint8_t lfuse; - uint8_t hfuse; - uint8_t efuse; - uint8_t lock; - float progress_flash; - float progress_eeprom; - const char* file_path; - const char* file_name; - AvrIspSignature signature; - AvrIspWorkerRWCallback callback; - void* context; - - AvrIspWorkerRWStatusCallback callback_status; - void* context_status; -}; - -typedef enum { - AvrIspWorkerRWEvtStop = (1 << 0), - - AvrIspWorkerRWEvtReading = (1 << 1), - AvrIspWorkerRWEvtVerification = (1 << 2), - AvrIspWorkerRWEvtWriting = (1 << 3), - AvrIspWorkerRWEvtWritingFuse = (1 << 4), - -} AvrIspWorkerRWEvt; -#define AVR_ISP_WORKER_ALL_EVENTS \ - (AvrIspWorkerRWEvtWritingFuse | AvrIspWorkerRWEvtWriting | AvrIspWorkerRWEvtVerification | \ - AvrIspWorkerRWEvtReading | AvrIspWorkerRWEvtStop) - -/** Worker thread - * - * @param context - * @return exit code - */ -static int32_t avr_isp_worker_rw_thread(void* context) { - AvrIspWorkerRW* instance = context; - - /* start PWM on &gpio_ext_pa4 */ - if(!furi_hal_pwm_is_running(FuriHalPwmOutputIdLptim2PA4)) { - furi_hal_pwm_start(FuriHalPwmOutputIdLptim2PA4, 4000000, 50); - } - - FURI_LOG_D(TAG, "Start"); - - while(1) { - uint32_t events = - furi_thread_flags_wait(AVR_ISP_WORKER_ALL_EVENTS, FuriFlagWaitAny, FuriWaitForever); - - if(events & AvrIspWorkerRWEvtStop) { - break; - } - - if(events & AvrIspWorkerRWEvtWritingFuse) { - if(avr_isp_worker_rw_write_fuse(instance, instance->file_path, instance->file_name)) { - if(instance->callback_status) - instance->callback_status( - instance->context_status, AvrIspWorkerRWStatusEndWritingFuse); - } else { - if(instance->callback_status) - instance->callback_status( - instance->context_status, AvrIspWorkerRWStatusErrorWritingFuse); - } - } - - if(events & AvrIspWorkerRWEvtWriting) { - if(avr_isp_worker_rw_write_dump(instance, instance->file_path, instance->file_name)) { - if(instance->callback_status) - instance->callback_status( - instance->context_status, AvrIspWorkerRWStatusEndWriting); - } else { - if(instance->callback_status) - instance->callback_status( - instance->context_status, AvrIspWorkerRWStatusErrorWriting); - } - } - - if(events & AvrIspWorkerRWEvtVerification) { - if(avr_isp_worker_rw_verification(instance, instance->file_path, instance->file_name)) { - if(instance->callback_status) - instance->callback_status( - instance->context_status, AvrIspWorkerRWStatusEndVerification); - } else { - if(instance->callback_status) - instance->callback_status( - instance->context_status, AvrIspWorkerRWStatusErrorVerification); - } - } - - if(events & AvrIspWorkerRWEvtReading) { - if(avr_isp_worker_rw_read_dump(instance, instance->file_path, instance->file_name)) { - if(instance->callback_status) - instance->callback_status( - instance->context_status, AvrIspWorkerRWStatusEndReading); - } else { - if(instance->callback_status) - instance->callback_status( - instance->context_status, AvrIspWorkerRWStatusErrorReading); - } - } - } - FURI_LOG_D(TAG, "Stop"); - - if(furi_hal_pwm_is_running(FuriHalPwmOutputIdLptim2PA4)) { - furi_hal_pwm_stop(FuriHalPwmOutputIdLptim2PA4); - } - - return 0; -} - -bool avr_isp_worker_rw_detect_chip(AvrIspWorkerRW* instance) { - furi_assert(instance); - - FURI_LOG_D(TAG, "Detecting AVR chip"); - - instance->chip_detect = false; - instance->chip_arr_ind = avr_isp_chip_arr_size + 1; - - /* start PWM on &gpio_ext_pa4 */ - bool was_pwm_enabled = false; - if(!furi_hal_pwm_is_running(FuriHalPwmOutputIdLptim2PA4)) { - furi_hal_pwm_start(FuriHalPwmOutputIdLptim2PA4, 4000000, 50); - } else { - was_pwm_enabled = true; - } - - do { - if(!avr_isp_auto_set_spi_speed_start_pmode(instance->avr_isp)) { - FURI_LOG_E(TAG, "Well, I managed to enter the mod program"); - break; - } - instance->signature = avr_isp_read_signature(instance->avr_isp); - - if(instance->signature.vendor != 0x1E) { - //No detect chip - } else { - for(uint32_t ind = 0; ind < avr_isp_chip_arr_size; ind++) { - if(avr_isp_chip_arr[ind].avrarch != F_AVR8) continue; - if(avr_isp_chip_arr[ind].sigs[1] == instance->signature.part_family) { - if(avr_isp_chip_arr[ind].sigs[2] == instance->signature.part_number) { - FURI_LOG_D(TAG, "Detect AVR chip = \"%s\"", avr_isp_chip_arr[ind].name); - FURI_LOG_D( - TAG, - "Signature = 0x%02X 0x%02X 0x%02X", - instance->signature.vendor, - instance->signature.part_family, - instance->signature.part_number); - - switch(avr_isp_chip_arr[ind].nfuses) { - case 1: - instance->lfuse = avr_isp_read_fuse_low(instance->avr_isp); - FURI_LOG_D(TAG, "Lfuse = %02X", instance->lfuse); - break; - case 2: - instance->lfuse = avr_isp_read_fuse_low(instance->avr_isp); - instance->hfuse = avr_isp_read_fuse_high(instance->avr_isp); - FURI_LOG_D( - TAG, "Lfuse = %02X Hfuse = %02X", instance->lfuse, instance->hfuse); - break; - case 3: - instance->lfuse = avr_isp_read_fuse_low(instance->avr_isp); - instance->hfuse = avr_isp_read_fuse_high(instance->avr_isp); - instance->efuse = avr_isp_read_fuse_extended(instance->avr_isp); - FURI_LOG_D( - TAG, - "Lfuse = %02X Hfuse = %02X Efuse = %02X", - instance->lfuse, - instance->hfuse, - instance->efuse); - break; - default: - break; - } - if(avr_isp_chip_arr[ind].nlocks == 1) { - instance->lock = avr_isp_read_lock_byte(instance->avr_isp); - FURI_LOG_D(TAG, "Lock = %02X", instance->lock); - } - instance->chip_detect = true; - instance->chip_arr_ind = ind; - break; - } - } - } - } - avr_isp_end_pmode(instance->avr_isp); - - } while(0); - - if(furi_hal_pwm_is_running(FuriHalPwmOutputIdLptim2PA4) && !was_pwm_enabled) { - furi_hal_pwm_stop(FuriHalPwmOutputIdLptim2PA4); - } - - if(instance->callback) { - if(instance->chip_arr_ind > avr_isp_chip_arr_size) { - instance->callback(instance->context, "No detect", instance->chip_detect, 0); - } else if(instance->chip_arr_ind < avr_isp_chip_arr_size) { - instance->callback( - instance->context, - avr_isp_chip_arr[instance->chip_arr_ind].name, - instance->chip_detect, - avr_isp_chip_arr[instance->chip_arr_ind].flashsize); - } else { - instance->callback(instance->context, "Unknown", instance->chip_detect, 0); - } - } - - return instance->chip_detect; -} - -AvrIspWorkerRW* avr_isp_worker_rw_alloc(void* context) { - furi_assert(context); - UNUSED(context); - - AvrIspWorkerRW* instance = malloc(sizeof(AvrIspWorkerRW)); - instance->avr_isp = avr_isp_alloc(); - - instance->thread = - furi_thread_alloc_ex("AvrIspWorkerRW", 4096, avr_isp_worker_rw_thread, instance); - - instance->chip_detect = false; - instance->lfuse = 0; - instance->hfuse = 0; - instance->efuse = 0; - instance->lock = 0; - instance->progress_flash = 0.0f; - instance->progress_eeprom = 0.0f; - - return instance; -} - -void avr_isp_worker_rw_free(AvrIspWorkerRW* instance) { - furi_assert(instance); - - avr_isp_free(instance->avr_isp); - - furi_check(!instance->worker_running); - furi_thread_free(instance->thread); - - free(instance); -} - -void avr_isp_worker_rw_start(AvrIspWorkerRW* instance) { - furi_assert(instance); - furi_assert(!instance->worker_running); - - instance->worker_running = true; - - furi_thread_start(instance->thread); -} - -void avr_isp_worker_rw_stop(AvrIspWorkerRW* instance) { - furi_assert(instance); - furi_assert(instance->worker_running); - - instance->worker_running = false; - furi_thread_flags_set(furi_thread_get_id(instance->thread), AvrIspWorkerRWEvtStop); - - furi_thread_join(instance->thread); -} - -bool avr_isp_worker_rw_is_running(AvrIspWorkerRW* instance) { - furi_assert(instance); - - return instance->worker_running; -} - -void avr_isp_worker_rw_set_callback( - AvrIspWorkerRW* instance, - AvrIspWorkerRWCallback callback, - void* context) { - furi_assert(instance); - - instance->callback = callback; - instance->context = context; -} - -void avr_isp_worker_rw_set_callback_status( - AvrIspWorkerRW* instance, - AvrIspWorkerRWStatusCallback callback_status, - void* context_status) { - furi_assert(instance); - - instance->callback_status = callback_status; - instance->context_status = context_status; -} - -float avr_isp_worker_rw_get_progress_flash(AvrIspWorkerRW* instance) { - furi_assert(instance); - - return instance->progress_flash; -} - -float avr_isp_worker_rw_get_progress_eeprom(AvrIspWorkerRW* instance) { - furi_assert(instance); - - return instance->progress_eeprom; -} - -static void avr_isp_worker_rw_get_dump_flash(AvrIspWorkerRW* instance, const char* file_path) { - furi_assert(instance); - furi_check(instance->avr_isp); - - FURI_LOG_D(TAG, "Dump FLASH %s", file_path); - - FlipperI32HexFile* flipper_hex_flash = flipper_i32hex_file_open_write( - file_path, avr_isp_chip_arr[instance->chip_arr_ind].flashoffset); - - uint8_t data[272] = {0}; - bool send_extended_addr = ((avr_isp_chip_arr[instance->chip_arr_ind].flashsize / 2) > 0x10000); - uint8_t extended_addr = 0; - - for(int32_t i = avr_isp_chip_arr[instance->chip_arr_ind].flashoffset; - i < avr_isp_chip_arr[instance->chip_arr_ind].flashsize / 2; - i += avr_isp_chip_arr[instance->chip_arr_ind].pagesize / 2) { - if(send_extended_addr) { - if(extended_addr <= ((i >> 16) & 0xFF)) { - avr_isp_write_extended_addr(instance->avr_isp, extended_addr); - extended_addr = ((i >> 16) & 0xFF) + 1; - } - } - avr_isp_read_page( - instance->avr_isp, - STK_SET_FLASH_TYPE, - (uint16_t)i, - avr_isp_chip_arr[instance->chip_arr_ind].pagesize, - data, - sizeof(data)); - flipper_i32hex_file_bin_to_i32hex_set_data( - flipper_hex_flash, data, avr_isp_chip_arr[instance->chip_arr_ind].pagesize); - FURI_LOG_D(TAG, "%s", flipper_i32hex_file_get_string(flipper_hex_flash)); - instance->progress_flash = - (float)(i) / ((float)avr_isp_chip_arr[instance->chip_arr_ind].flashsize / 2.0f); - } - flipper_i32hex_file_bin_to_i32hex_set_end_line(flipper_hex_flash); - FURI_LOG_D(TAG, "%s", flipper_i32hex_file_get_string(flipper_hex_flash)); - flipper_i32hex_file_close(flipper_hex_flash); - instance->progress_flash = 1.0f; -} - -static void avr_isp_worker_rw_get_dump_eeprom(AvrIspWorkerRW* instance, const char* file_path) { - furi_assert(instance); - furi_check(instance->avr_isp); - - FURI_LOG_D(TAG, "Dump EEPROM %s", file_path); - - FlipperI32HexFile* flipper_hex_eeprom = flipper_i32hex_file_open_write( - file_path, avr_isp_chip_arr[instance->chip_arr_ind].eepromoffset); - - int32_t size_data = 32; - uint8_t data[256] = {0}; - - if(size_data > avr_isp_chip_arr[instance->chip_arr_ind].eepromsize) - size_data = avr_isp_chip_arr[instance->chip_arr_ind].eepromsize; - - for(int32_t i = avr_isp_chip_arr[instance->chip_arr_ind].eepromoffset; - i < avr_isp_chip_arr[instance->chip_arr_ind].eepromsize; - i += size_data) { - avr_isp_read_page( - instance->avr_isp, STK_SET_EEPROM_TYPE, (uint16_t)i, size_data, data, sizeof(data)); - flipper_i32hex_file_bin_to_i32hex_set_data(flipper_hex_eeprom, data, size_data); - FURI_LOG_D(TAG, "%s", flipper_i32hex_file_get_string(flipper_hex_eeprom)); - instance->progress_eeprom = - (float)(i) / ((float)avr_isp_chip_arr[instance->chip_arr_ind].eepromsize); - } - flipper_i32hex_file_bin_to_i32hex_set_end_line(flipper_hex_eeprom); - FURI_LOG_D(TAG, "%s", flipper_i32hex_file_get_string(flipper_hex_eeprom)); - flipper_i32hex_file_close(flipper_hex_eeprom); - instance->progress_eeprom = 1.0f; -} - -bool avr_isp_worker_rw_read_dump( - AvrIspWorkerRW* instance, - const char* file_path, - const char* file_name) { - furi_assert(instance); - furi_assert(file_path); - furi_assert(file_name); - - FURI_LOG_D(TAG, "Read dump chip"); - - instance->progress_flash = 0.0f; - instance->progress_eeprom = 0.0f; - bool ret = false; - Storage* storage = furi_record_open(RECORD_STORAGE); - FlipperFormat* flipper_format = flipper_format_file_alloc(storage); - FuriString* file_path_name = furi_string_alloc(); - - if(!avr_isp_worker_rw_detect_chip(instance)) { - FURI_LOG_E(TAG, "No detect AVR chip"); - } else { - do { - furi_string_printf( - file_path_name, "%s/%s%s", file_path, file_name, AVR_ISP_APP_EXTENSION); - if(!flipper_format_file_open_always( - flipper_format, furi_string_get_cstr(file_path_name))) { - FURI_LOG_E(TAG, "flipper_format_file_open_always"); - break; - } - if(!flipper_format_write_header_cstr( - flipper_format, AVR_ISP_APP_FILE_TYPE, AVR_ISP_APP_FILE_VERSION)) { - FURI_LOG_E(TAG, "flipper_format_write_header_cstr"); - break; - } - if(!flipper_format_write_string_cstr( - flipper_format, "Chip name", avr_isp_chip_arr[instance->chip_arr_ind].name)) { - FURI_LOG_E(TAG, "Chip name"); - break; - } - if(!flipper_format_write_hex( - flipper_format, - "Signature", - (uint8_t*)&instance->signature, - sizeof(AvrIspSignature))) { - FURI_LOG_E(TAG, "Unable to add Signature"); - break; - } - if(avr_isp_chip_arr[instance->chip_arr_ind].nfuses > 0) { - if(!flipper_format_write_hex(flipper_format, "Lfuse", &instance->lfuse, 1)) { - FURI_LOG_E(TAG, "Unable to add Lfuse"); - break; - } - } - if(avr_isp_chip_arr[instance->chip_arr_ind].nfuses > 1) { - if(!flipper_format_write_hex(flipper_format, "Hfuse", &instance->hfuse, 1)) { - FURI_LOG_E(TAG, "Unable to add Hfuse"); - break; - } - } - if(avr_isp_chip_arr[instance->chip_arr_ind].nfuses > 2) { - if(!flipper_format_write_hex(flipper_format, "Efuse", &instance->efuse, 1)) { - FURI_LOG_E(TAG, "Unable to add Efuse"); - break; - } - } - if(avr_isp_chip_arr[instance->chip_arr_ind].nlocks == 1) { - if(!flipper_format_write_hex(flipper_format, "Lock", &instance->lock, 1)) { - FURI_LOG_E(TAG, "Unable to add Lock"); - break; - } - } - furi_string_printf(file_path_name, "%s_%s", file_name, NAME_PATERN_FLASH_FILE); - if(!flipper_format_write_string_cstr( - flipper_format, "Dump_flash", furi_string_get_cstr(file_path_name))) { - FURI_LOG_E(TAG, "Unable to add Dump_flash"); - break; - } - - if(avr_isp_chip_arr[instance->chip_arr_ind].eepromsize > 0) { - furi_string_printf(file_path_name, "%s_%s", file_name, NAME_PATERN_EEPROM_FILE); - if(avr_isp_chip_arr[instance->chip_arr_ind].eepromsize > 0) { - if(!flipper_format_write_string_cstr( - flipper_format, "Dump_eeprom", furi_string_get_cstr(file_path_name))) { - FURI_LOG_E(TAG, "Unable to add Dump_eeprom"); - break; - } - } - } - ret = true; - } while(false); - } - - flipper_format_free(flipper_format); - furi_record_close(RECORD_STORAGE); - - if(ret) { - if(avr_isp_auto_set_spi_speed_start_pmode(instance->avr_isp)) { - //Dump flash - furi_string_printf( - file_path_name, "%s/%s_%s", file_path, file_name, NAME_PATERN_FLASH_FILE); - avr_isp_worker_rw_get_dump_flash(instance, furi_string_get_cstr(file_path_name)); - //Dump eeprom - if(avr_isp_chip_arr[instance->chip_arr_ind].eepromsize > 0) { - furi_string_printf( - file_path_name, "%s/%s_%s", file_path, file_name, NAME_PATERN_EEPROM_FILE); - avr_isp_worker_rw_get_dump_eeprom(instance, furi_string_get_cstr(file_path_name)); - } - - avr_isp_end_pmode(instance->avr_isp); - } - } - - furi_string_free(file_path_name); - - return true; -} - -void avr_isp_worker_rw_read_dump_start( - AvrIspWorkerRW* instance, - const char* file_path, - const char* file_name) { - furi_assert(instance); - - instance->file_path = file_path; - instance->file_name = file_name; - furi_thread_flags_set(furi_thread_get_id(instance->thread), AvrIspWorkerRWEvtReading); -} - -static bool avr_isp_worker_rw_verification_flash(AvrIspWorkerRW* instance, const char* file_path) { - furi_assert(instance); - furi_assert(file_path); - - FURI_LOG_D(TAG, "Verification flash %s", file_path); - - instance->progress_flash = 0.0; - bool ret = true; - - FlipperI32HexFile* flipper_hex_flash = flipper_i32hex_file_open_read(file_path); - - uint8_t data_read_flash[272] = {0}; - uint8_t data_read_hex[272] = {0}; - - uint32_t addr = avr_isp_chip_arr[instance->chip_arr_ind].flashoffset; - bool send_extended_addr = ((avr_isp_chip_arr[instance->chip_arr_ind].flashsize / 2) > 0x10000); - uint8_t extended_addr = 0; - - FlipperI32HexFileRet flipper_hex_ret = flipper_i32hex_file_i32hex_to_bin_get_data( - flipper_hex_flash, data_read_hex, sizeof(data_read_hex)); - - while(((flipper_hex_ret.status == FlipperI32HexFileStatusData) || - (flipper_hex_ret.status == FlipperI32HexFileStatusUdateAddr)) && - ret) { - switch(flipper_hex_ret.status) { - case FlipperI32HexFileStatusData: - - if(send_extended_addr) { - if(extended_addr <= ((addr >> 16) & 0xFF)) { - avr_isp_write_extended_addr(instance->avr_isp, extended_addr); - extended_addr = ((addr >> 16) & 0xFF) + 1; - } - } - - avr_isp_read_page( - instance->avr_isp, - STK_SET_FLASH_TYPE, - (uint16_t)addr, - flipper_hex_ret.data_size, - data_read_flash, - sizeof(data_read_flash)); - - if(memcmp(data_read_hex, data_read_flash, flipper_hex_ret.data_size) != 0) { - ret = false; - - FURI_LOG_E(TAG, "Verification flash error"); - FURI_LOG_E(TAG, "Addr: 0x%04lX", addr); - for(uint32_t i = 0; i < flipper_hex_ret.data_size; i++) { - FURI_LOG_RAW_E("%02X ", data_read_hex[i]); - } - FURI_LOG_RAW_E("\r\n"); - for(uint32_t i = 0; i < flipper_hex_ret.data_size; i++) { - FURI_LOG_RAW_E("%02X ", data_read_flash[i]); - } - FURI_LOG_RAW_E("\r\n"); - } - - addr += flipper_hex_ret.data_size / 2; - instance->progress_flash = - (float)(addr) / ((float)avr_isp_chip_arr[instance->chip_arr_ind].flashsize / 2.0f); - break; - - case FlipperI32HexFileStatusUdateAddr: - addr = (data_read_hex[0] << 24 | data_read_hex[1] << 16) / 2; - break; - - default: - furi_crash(TAG " Incorrect status."); - break; - } - - flipper_hex_ret = flipper_i32hex_file_i32hex_to_bin_get_data( - flipper_hex_flash, data_read_hex, sizeof(data_read_hex)); - } - - flipper_i32hex_file_close(flipper_hex_flash); - instance->progress_flash = 1.0f; - - return ret; -} - -static bool - avr_isp_worker_rw_verification_eeprom(AvrIspWorkerRW* instance, const char* file_path) { - furi_assert(instance); - furi_assert(file_path); - - FURI_LOG_D(TAG, "Verification eeprom %s", file_path); - - instance->progress_eeprom = 0.0; - bool ret = true; - - FlipperI32HexFile* flipper_hex_eeprom = flipper_i32hex_file_open_read(file_path); - - uint8_t data_read_eeprom[272] = {0}; - uint8_t data_read_hex[272] = {0}; - - uint32_t addr = avr_isp_chip_arr[instance->chip_arr_ind].eepromoffset; - - FlipperI32HexFileRet flipper_hex_ret = flipper_i32hex_file_i32hex_to_bin_get_data( - flipper_hex_eeprom, data_read_hex, sizeof(data_read_hex)); - - while(((flipper_hex_ret.status == FlipperI32HexFileStatusData) || - (flipper_hex_ret.status == FlipperI32HexFileStatusUdateAddr)) && - ret) { - switch(flipper_hex_ret.status) { - case FlipperI32HexFileStatusData: - avr_isp_read_page( - instance->avr_isp, - STK_SET_EEPROM_TYPE, - (uint16_t)addr, - flipper_hex_ret.data_size, - data_read_eeprom, - sizeof(data_read_eeprom)); - - if(memcmp(data_read_hex, data_read_eeprom, flipper_hex_ret.data_size) != 0) { - ret = false; - FURI_LOG_E(TAG, "Verification eeprom error"); - FURI_LOG_E(TAG, "Addr: 0x%04lX", addr); - for(uint32_t i = 0; i < flipper_hex_ret.data_size; i++) { - FURI_LOG_RAW_E("%02X ", data_read_hex[i]); - } - FURI_LOG_RAW_E("\r\n"); - for(uint32_t i = 0; i < flipper_hex_ret.data_size; i++) { - FURI_LOG_RAW_E("%02X ", data_read_eeprom[i]); - } - FURI_LOG_RAW_E("\r\n"); - } - - addr += flipper_hex_ret.data_size; - instance->progress_eeprom = - (float)(addr) / ((float)avr_isp_chip_arr[instance->chip_arr_ind].eepromsize); - break; - - case FlipperI32HexFileStatusUdateAddr: - addr = (data_read_hex[0] << 24 | data_read_hex[1] << 16); - break; - - default: - furi_crash(TAG " Incorrect status."); - break; - } - - flipper_hex_ret = flipper_i32hex_file_i32hex_to_bin_get_data( - flipper_hex_eeprom, data_read_hex, sizeof(data_read_hex)); - } - - flipper_i32hex_file_close(flipper_hex_eeprom); - instance->progress_eeprom = 1.0f; - - return ret; -} - -bool avr_isp_worker_rw_verification( - AvrIspWorkerRW* instance, - const char* file_path, - const char* file_name) { - furi_assert(instance); - furi_assert(file_path); - furi_assert(file_name); - - FURI_LOG_D(TAG, "Verification chip"); - - instance->progress_flash = 0.0f; - instance->progress_eeprom = 0.0f; - FuriString* file_path_name = furi_string_alloc(); - - bool ret = false; - - if(avr_isp_auto_set_spi_speed_start_pmode(instance->avr_isp)) { - do { - furi_string_printf( - file_path_name, "%s/%s_%s", file_path, file_name, NAME_PATERN_FLASH_FILE); - if(!avr_isp_worker_rw_verification_flash( - instance, furi_string_get_cstr(file_path_name))) - break; - - if(avr_isp_chip_arr[instance->chip_arr_ind].eepromsize > 0) { - furi_string_printf( - file_path_name, "%s/%s_%s", file_path, file_name, NAME_PATERN_EEPROM_FILE); - - if(!avr_isp_worker_rw_verification_eeprom( - instance, furi_string_get_cstr(file_path_name))) - break; - } - ret = true; - } while(false); - avr_isp_end_pmode(instance->avr_isp); - furi_string_free(file_path_name); - } - return ret; -} - -void avr_isp_worker_rw_verification_start( - AvrIspWorkerRW* instance, - const char* file_path, - const char* file_name) { - furi_assert(instance); - - instance->file_path = file_path; - instance->file_name = file_name; - furi_thread_flags_set(furi_thread_get_id(instance->thread), AvrIspWorkerRWEvtVerification); -} - -static void avr_isp_worker_rw_write_flash(AvrIspWorkerRW* instance, const char* file_path) { - furi_assert(instance); - furi_check(instance->avr_isp); - - instance->progress_flash = 0.0; - - FURI_LOG_D(TAG, "Write Flash %s", file_path); - - uint8_t data[288] = {0}; - - FlipperI32HexFile* flipper_hex_flash = flipper_i32hex_file_open_read(file_path); - - uint32_t addr = avr_isp_chip_arr[instance->chip_arr_ind].flashoffset; - bool send_extended_addr = ((avr_isp_chip_arr[instance->chip_arr_ind].flashsize / 2) > 0x10000); - uint8_t extended_addr = 0; - - FlipperI32HexFileRet flipper_hex_ret = - flipper_i32hex_file_i32hex_to_bin_get_data(flipper_hex_flash, data, sizeof(data)); - - while((flipper_hex_ret.status == FlipperI32HexFileStatusData) || - (flipper_hex_ret.status == FlipperI32HexFileStatusUdateAddr)) { - switch(flipper_hex_ret.status) { - case FlipperI32HexFileStatusData: - - if(send_extended_addr) { - if(extended_addr <= ((addr >> 16) & 0xFF)) { - avr_isp_write_extended_addr(instance->avr_isp, extended_addr); - extended_addr = ((addr >> 16) & 0xFF) + 1; - } - } - - if(!avr_isp_write_page( - instance->avr_isp, - STK_SET_FLASH_TYPE, - avr_isp_chip_arr[instance->chip_arr_ind].flashsize, - (uint16_t)addr, - avr_isp_chip_arr[instance->chip_arr_ind].pagesize, - data, - flipper_hex_ret.data_size)) { - break; - } - addr += flipper_hex_ret.data_size / 2; - instance->progress_flash = - (float)(addr) / ((float)avr_isp_chip_arr[instance->chip_arr_ind].flashsize / 2.0f); - break; - - case FlipperI32HexFileStatusUdateAddr: - addr = (data[0] << 24 | data[1] << 16) / 2; - break; - - default: - furi_crash(TAG " Incorrect status."); - break; - } - - flipper_hex_ret = - flipper_i32hex_file_i32hex_to_bin_get_data(flipper_hex_flash, data, sizeof(data)); - } - - flipper_i32hex_file_close(flipper_hex_flash); - instance->progress_flash = 1.0f; -} - -static void avr_isp_worker_rw_write_eeprom(AvrIspWorkerRW* instance, const char* file_path) { - furi_assert(instance); - furi_check(instance->avr_isp); - - instance->progress_eeprom = 0.0; - uint8_t data[288] = {0}; - - FURI_LOG_D(TAG, "Write EEPROM %s", file_path); - - FlipperI32HexFile* flipper_hex_eeprom_read = flipper_i32hex_file_open_read(file_path); - - uint32_t addr = avr_isp_chip_arr[instance->chip_arr_ind].eepromoffset; - FlipperI32HexFileRet flipper_hex_ret = - flipper_i32hex_file_i32hex_to_bin_get_data(flipper_hex_eeprom_read, data, sizeof(data)); - - while((flipper_hex_ret.status == FlipperI32HexFileStatusData) || - (flipper_hex_ret.status == FlipperI32HexFileStatusUdateAddr)) { - switch(flipper_hex_ret.status) { - case FlipperI32HexFileStatusData: - if(!avr_isp_write_page( - instance->avr_isp, - STK_SET_EEPROM_TYPE, - avr_isp_chip_arr[instance->chip_arr_ind].eepromsize, - (uint16_t)addr, - avr_isp_chip_arr[instance->chip_arr_ind].eeprompagesize, - data, - flipper_hex_ret.data_size)) { - break; - } - addr += flipper_hex_ret.data_size; - instance->progress_eeprom = - (float)(addr) / ((float)avr_isp_chip_arr[instance->chip_arr_ind].eepromsize); - break; - - case FlipperI32HexFileStatusUdateAddr: - addr = data[0] << 24 | data[1] << 16; - break; - - default: - furi_crash(TAG " Incorrect status."); - break; - } - - flipper_hex_ret = flipper_i32hex_file_i32hex_to_bin_get_data( - flipper_hex_eeprom_read, data, sizeof(data)); - } - - flipper_i32hex_file_close(flipper_hex_eeprom_read); - instance->progress_eeprom = 1.0f; -} - -bool avr_isp_worker_rw_write_dump( - AvrIspWorkerRW* instance, - const char* file_path, - const char* file_name) { - furi_assert(instance); - furi_assert(file_path); - furi_assert(file_name); - - FURI_LOG_D(TAG, "Write dump chip"); - - instance->progress_flash = 0.0f; - instance->progress_eeprom = 0.0f; - bool ret = false; - - Storage* storage = furi_record_open(RECORD_STORAGE); - FlipperFormat* flipper_format = flipper_format_file_alloc(storage); - FuriString* file_path_name = furi_string_alloc(); - - FuriString* temp_str_1 = furi_string_alloc(); - FuriString* temp_str_2 = furi_string_alloc(); - uint32_t temp_data32; - - if(!avr_isp_worker_rw_detect_chip(instance)) { - FURI_LOG_E(TAG, "No detect AVR chip"); - } else { - //upload file with description - do { - furi_string_printf( - file_path_name, "%s/%s%s", file_path, file_name, AVR_ISP_APP_EXTENSION); - if(!flipper_format_file_open_existing( - flipper_format, furi_string_get_cstr(file_path_name))) { - FURI_LOG_E(TAG, "Error open file %s", furi_string_get_cstr(file_path_name)); - break; - } - - if(!flipper_format_read_header(flipper_format, temp_str_1, &temp_data32)) { - FURI_LOG_E(TAG, "Missing or incorrect header"); - break; - } - - if((!strcmp(furi_string_get_cstr(temp_str_1), AVR_ISP_APP_FILE_TYPE)) && - temp_data32 == AVR_ISP_APP_FILE_VERSION) { - } else { - FURI_LOG_E(TAG, "Type or version mismatch"); - break; - } - - AvrIspSignature sig_read = {0}; - - if(!flipper_format_read_hex( - flipper_format, "Signature", (uint8_t*)&sig_read, sizeof(AvrIspSignature))) { - FURI_LOG_E(TAG, "Missing Signature"); - break; - } - - if(memcmp( - (uint8_t*)&instance->signature, (uint8_t*)&sig_read, sizeof(AvrIspSignature)) != - 0) { - FURI_LOG_E( - TAG, - "Wrong chip. Connected (%02X %02X %02X), read from file (%02X %02X %02X)", - instance->signature.vendor, - instance->signature.part_family, - instance->signature.part_number, - sig_read.vendor, - sig_read.part_family, - sig_read.part_number); - break; - } - - if(!flipper_format_read_string(flipper_format, "Dump_flash", temp_str_1)) { - FURI_LOG_E(TAG, "Missing Dump_flash"); - break; - } - - //may not be - flipper_format_read_string(flipper_format, "Dump_eeprom", temp_str_2); - ret = true; - } while(false); - } - flipper_format_free(flipper_format); - furi_record_close(RECORD_STORAGE); - - if(ret) { - do { - //checking .hex files for errors - - furi_string_printf( - file_path_name, "%s/%s", file_path, furi_string_get_cstr(temp_str_1)); - - FURI_LOG_D(TAG, "Check flash file"); - FlipperI32HexFile* flipper_hex_flash_read = - flipper_i32hex_file_open_read(furi_string_get_cstr(file_path_name)); - if(flipper_i32hex_file_check(flipper_hex_flash_read)) { - FURI_LOG_D(TAG, "Check flash file: OK"); - } else { - FURI_LOG_E(TAG, "Check flash file: Error"); - ret = false; - } - flipper_i32hex_file_close(flipper_hex_flash_read); - - if(furi_string_size(temp_str_2) > 4) { - furi_string_printf( - file_path_name, "%s/%s", file_path, furi_string_get_cstr(temp_str_2)); - - FURI_LOG_D(TAG, "Check eeprom file"); - FlipperI32HexFile* flipper_hex_eeprom_read = - flipper_i32hex_file_open_read(furi_string_get_cstr(file_path_name)); - if(flipper_i32hex_file_check(flipper_hex_eeprom_read)) { - FURI_LOG_D(TAG, "Check eeprom file: OK"); - } else { - FURI_LOG_E(TAG, "Check eeprom file: Error"); - ret = false; - } - flipper_i32hex_file_close(flipper_hex_eeprom_read); - } - - if(!ret) break; - ret = false; - - //erase chip - FURI_LOG_D(TAG, "Erase chip"); - if(!avr_isp_erase_chip(instance->avr_isp)) { - FURI_LOG_E(TAG, "Erase chip: Error"); - break; - } - - if(!avr_isp_auto_set_spi_speed_start_pmode(instance->avr_isp)) { - FURI_LOG_E(TAG, "Well, I managed to enter the mod program"); - break; - } - - //write flash - furi_string_printf( - file_path_name, "%s/%s", file_path, furi_string_get_cstr(temp_str_1)); - avr_isp_worker_rw_write_flash(instance, furi_string_get_cstr(file_path_name)); - - //write eeprom - if(furi_string_size(temp_str_2) > 4) { - furi_string_printf( - file_path_name, "%s/%s", file_path, furi_string_get_cstr(temp_str_2)); - avr_isp_worker_rw_write_eeprom(instance, furi_string_get_cstr(file_path_name)); - } - ret = true; - avr_isp_end_pmode(instance->avr_isp); - } while(false); - } - - furi_string_free(file_path_name); - furi_string_free(temp_str_1); - furi_string_free(temp_str_2); - - return ret; -} - -void avr_isp_worker_rw_write_dump_start( - AvrIspWorkerRW* instance, - const char* file_path, - const char* file_name) { - furi_assert(instance); - - instance->file_path = file_path; - instance->file_name = file_name; - furi_thread_flags_set(furi_thread_get_id(instance->thread), AvrIspWorkerRWEvtWriting); -} - -bool avr_isp_worker_rw_write_fuse( - AvrIspWorkerRW* instance, - const char* file_path, - const char* file_name) { - furi_assert(instance); - furi_assert(file_path); - furi_assert(file_name); - - FURI_LOG_D(TAG, "Write fuse chip"); - - bool ret = false; - uint8_t lfuse; - uint8_t hfuse; - uint8_t efuse; - uint8_t lock; - - Storage* storage = furi_record_open(RECORD_STORAGE); - FlipperFormat* flipper_format = flipper_format_file_alloc(storage); - FuriString* temp_str = furi_string_alloc(); - - uint32_t temp_data32; - - if(!avr_isp_worker_rw_detect_chip(instance)) { - FURI_LOG_E(TAG, "No detect AVR chip"); - } else { - //upload file with description - do { - furi_string_printf(temp_str, "%s/%s%s", file_path, file_name, AVR_ISP_APP_EXTENSION); - if(!flipper_format_file_open_existing(flipper_format, furi_string_get_cstr(temp_str))) { - FURI_LOG_E(TAG, "Error open file %s", furi_string_get_cstr(temp_str)); - break; - } - - if(!flipper_format_read_header(flipper_format, temp_str, &temp_data32)) { - FURI_LOG_E(TAG, "Missing or incorrect header"); - break; - } - - if((!strcmp(furi_string_get_cstr(temp_str), AVR_ISP_APP_FILE_TYPE)) && - temp_data32 == AVR_ISP_APP_FILE_VERSION) { - } else { - FURI_LOG_E(TAG, "Type or version mismatch"); - break; - } - - AvrIspSignature sig_read = {0}; - - if(!flipper_format_read_hex( - flipper_format, "Signature", (uint8_t*)&sig_read, sizeof(AvrIspSignature))) { - FURI_LOG_E(TAG, "Missing Signature"); - break; - } - - if(memcmp( - (uint8_t*)&instance->signature, (uint8_t*)&sig_read, sizeof(AvrIspSignature)) != - 0) { - FURI_LOG_E( - TAG, - "Wrong chip. Connected (%02X %02X %02X), read from file (%02X %02X %02X)", - instance->signature.vendor, - instance->signature.part_family, - instance->signature.part_number, - sig_read.vendor, - sig_read.part_family, - sig_read.part_number); - break; - } - - if(avr_isp_chip_arr[instance->chip_arr_ind].nfuses > 0) { - if(!flipper_format_read_hex(flipper_format, "Lfuse", &lfuse, 1)) { - FURI_LOG_E(TAG, "Missing Lfuse"); - break; - } - } - if(avr_isp_chip_arr[instance->chip_arr_ind].nfuses > 1) { - if(!flipper_format_read_hex(flipper_format, "Hfuse", &hfuse, 1)) { - FURI_LOG_E(TAG, "Missing Hfuse"); - break; - } - } - if(avr_isp_chip_arr[instance->chip_arr_ind].nfuses > 2) { - if(!flipper_format_read_hex(flipper_format, "Efuse", &efuse, 1)) { - FURI_LOG_E(TAG, "Missing Efuse"); - break; - } - } - if(avr_isp_chip_arr[instance->chip_arr_ind].nlocks == 1) { - if(!flipper_format_read_hex(flipper_format, "Lock", &lock, 1)) { - FURI_LOG_E(TAG, "Missing Lock"); - break; - } - } - - if(!avr_isp_auto_set_spi_speed_start_pmode(instance->avr_isp)) { - FURI_LOG_E(TAG, "Well, I managed to enter the mod program"); - break; - } - - ret = true; - - if(avr_isp_chip_arr[instance->chip_arr_ind].nfuses > 0) { - if(instance->lfuse != lfuse) { - if(!avr_isp_write_fuse_low(instance->avr_isp, lfuse)) { - FURI_LOG_E(TAG, "Write Lfuse: error"); - ret = false; - } - } - } - if(avr_isp_chip_arr[instance->chip_arr_ind].nfuses > 1) { - if(instance->hfuse != hfuse) { - if(!avr_isp_write_fuse_high(instance->avr_isp, hfuse)) { - FURI_LOG_E(TAG, "Write Hfuse: error"); - ret = false; - } - } - } - if(avr_isp_chip_arr[instance->chip_arr_ind].nfuses > 2) { - if(instance->efuse != efuse) { - if(!avr_isp_write_fuse_extended(instance->avr_isp, efuse)) { - FURI_LOG_E(TAG, "Write Efuse: error"); - ret = false; - } - } - } - - if(avr_isp_chip_arr[instance->chip_arr_ind].nlocks == 1) { - FURI_LOG_D(TAG, "Write lock byte"); - if(instance->lock != lock) { - if(!avr_isp_write_lock_byte(instance->avr_isp, lock)) { - FURI_LOG_E(TAG, "Write Lock byte: error"); - ret = false; - } - } - } - avr_isp_end_pmode(instance->avr_isp); - } while(false); - } - - flipper_format_free(flipper_format); - furi_record_close(RECORD_STORAGE); - furi_string_free(temp_str); - return ret; -} - -void avr_isp_worker_rw_write_fuse_start( - AvrIspWorkerRW* instance, - const char* file_path, - const char* file_name) { - furi_assert(instance); - - instance->file_path = file_path; - instance->file_name = file_name; - furi_thread_flags_set(furi_thread_get_id(instance->thread), AvrIspWorkerRWEvtWritingFuse); -} \ No newline at end of file diff --git a/applications/external/avr_isp_programmer/helpers/avr_isp_worker_rw.h b/applications/external/avr_isp_programmer/helpers/avr_isp_worker_rw.h deleted file mode 100644 index 2c52a8700d5..00000000000 --- a/applications/external/avr_isp_programmer/helpers/avr_isp_worker_rw.h +++ /dev/null @@ -1,99 +0,0 @@ -#pragma once - -#include - -typedef struct AvrIspWorkerRW AvrIspWorkerRW; - -typedef void (*AvrIspWorkerRWCallback)( - void* context, - const char* name, - bool detect_chip, - uint32_t flash_size); - -typedef enum { - AvrIspWorkerRWStatusILDE = 0, - AvrIspWorkerRWStatusEndReading = 1, - AvrIspWorkerRWStatusEndVerification = 2, - AvrIspWorkerRWStatusEndWriting = 3, - AvrIspWorkerRWStatusEndWritingFuse = 4, - - AvrIspWorkerRWStatusErrorReading = (-1), - AvrIspWorkerRWStatusErrorVerification = (-2), - AvrIspWorkerRWStatusErrorWriting = (-3), - AvrIspWorkerRWStatusErrorWritingFuse = (-4), - - AvrIspWorkerRWStatusReserved = 0x7FFFFFFF, ///< Prevents enum down-size compiler optimization. -} AvrIspWorkerRWStatus; - -typedef void (*AvrIspWorkerRWStatusCallback)(void* context, AvrIspWorkerRWStatus status); - -AvrIspWorkerRW* avr_isp_worker_rw_alloc(void* context); - -void avr_isp_worker_rw_free(AvrIspWorkerRW* instance); - -void avr_isp_worker_rw_start(AvrIspWorkerRW* instance); - -void avr_isp_worker_rw_stop(AvrIspWorkerRW* instance); - -bool avr_isp_worker_rw_is_running(AvrIspWorkerRW* instance); - -void avr_isp_worker_rw_set_callback( - AvrIspWorkerRW* instance, - AvrIspWorkerRWCallback callback, - void* context); - -void avr_isp_worker_rw_set_callback_status( - AvrIspWorkerRW* instance, - AvrIspWorkerRWStatusCallback callback_status, - void* context_status); - -bool avr_isp_worker_rw_detect_chip(AvrIspWorkerRW* instance); - -float avr_isp_worker_rw_get_progress_flash(AvrIspWorkerRW* instance); - -float avr_isp_worker_rw_get_progress_eeprom(AvrIspWorkerRW* instance); - -bool avr_isp_worker_rw_read_dump( - AvrIspWorkerRW* instance, - const char* file_path, - const char* file_name); - -void avr_isp_worker_rw_read_dump_start( - AvrIspWorkerRW* instance, - const char* file_path, - const char* file_name); - -bool avr_isp_worker_rw_verification( - AvrIspWorkerRW* instance, - const char* file_path, - const char* file_name); - -void avr_isp_worker_rw_verification_start( - AvrIspWorkerRW* instance, - const char* file_path, - const char* file_name); - -bool avr_isp_worker_rw_check_hex( - AvrIspWorkerRW* instance, - const char* file_path, - const char* file_name); - -bool avr_isp_worker_rw_write_dump( - AvrIspWorkerRW* instance, - const char* file_path, - const char* file_name); - -void avr_isp_worker_rw_write_dump_start( - AvrIspWorkerRW* instance, - const char* file_path, - const char* file_name); - -bool avr_isp_worker_rw_write_fuse( - AvrIspWorkerRW* instance, - const char* file_path, - const char* file_name); - -void avr_isp_worker_rw_write_fuse_start( - AvrIspWorkerRW* instance, - const char* file_path, - const char* file_name); \ No newline at end of file diff --git a/applications/external/avr_isp_programmer/helpers/flipper_i32hex_file.c b/applications/external/avr_isp_programmer/helpers/flipper_i32hex_file.c deleted file mode 100644 index a8c20a0bd94..00000000000 --- a/applications/external/avr_isp_programmer/helpers/flipper_i32hex_file.c +++ /dev/null @@ -1,321 +0,0 @@ -#include "flipper_i32hex_file.h" -#include -#include -#include -#include -#include - -//https://en.wikipedia.org/wiki/Intel_HEX - -#define TAG "FlipperI32HexFile" - -#define COUNT_BYTE_PAYLOAD 32 //how much payload will be used - -#define I32HEX_TYPE_DATA 0x00 -#define I32HEX_TYPE_END_OF_FILE 0x01 -#define I32HEX_TYPE_EXT_LINEAR_ADDR 0x04 -#define I32HEX_TYPE_START_LINEAR_ADDR 0x05 - -struct FlipperI32HexFile { - uint32_t addr; - uint32_t addr_last; - Storage* storage; - Stream* stream; - FuriString* str_data; - FlipperI32HexFileStatus file_open; -}; - -FlipperI32HexFile* flipper_i32hex_file_open_write(const char* name, uint32_t start_addr) { - furi_assert(name); - - FlipperI32HexFile* instance = malloc(sizeof(FlipperI32HexFile)); - instance->addr = start_addr; - instance->addr_last = 0; - instance->storage = furi_record_open(RECORD_STORAGE); - instance->stream = file_stream_alloc(instance->storage); - - if(file_stream_open(instance->stream, name, FSAM_WRITE, FSOM_CREATE_ALWAYS)) { - instance->file_open = FlipperI32HexFileStatusOpenFileWrite; - FURI_LOG_D(TAG, "Open write file %s", name); - } else { - FURI_LOG_E(TAG, "Failed to open file %s", name); - instance->file_open = FlipperI32HexFileStatusErrorNoOpenFile; - } - instance->str_data = furi_string_alloc(instance->storage); - - return instance; -} - -FlipperI32HexFile* flipper_i32hex_file_open_read(const char* name) { - furi_assert(name); - - FlipperI32HexFile* instance = malloc(sizeof(FlipperI32HexFile)); - instance->addr = 0; - instance->addr_last = 0; - instance->storage = furi_record_open(RECORD_STORAGE); - instance->stream = file_stream_alloc(instance->storage); - - if(file_stream_open(instance->stream, name, FSAM_READ, FSOM_OPEN_EXISTING)) { - instance->file_open = FlipperI32HexFileStatusOpenFileRead; - FURI_LOG_D(TAG, "Open read file %s", name); - } else { - FURI_LOG_E(TAG, "Failed to open file %s", name); - instance->file_open = FlipperI32HexFileStatusErrorNoOpenFile; - } - instance->str_data = furi_string_alloc(instance->storage); - - return instance; -} - -void flipper_i32hex_file_close(FlipperI32HexFile* instance) { - furi_assert(instance); - - furi_string_free(instance->str_data); - file_stream_close(instance->stream); - stream_free(instance->stream); - furi_record_close(RECORD_STORAGE); -} - -FlipperI32HexFileRet flipper_i32hex_file_bin_to_i32hex_set_data( - FlipperI32HexFile* instance, - uint8_t* data, - uint32_t data_size) { - furi_assert(instance); - furi_assert(data); - - FlipperI32HexFileRet ret = {.status = FlipperI32HexFileStatusOK, .data_size = 0}; - if(instance->file_open != FlipperI32HexFileStatusOpenFileWrite) { - ret.status = FlipperI32HexFileStatusErrorFileWrite; - } - uint8_t count_byte = 0; - uint32_t ind = 0; - uint8_t crc = 0; - - furi_string_reset(instance->str_data); - - if((instance->addr_last & 0xFF0000) < (instance->addr & 0xFF0000)) { - crc = 0x02 + 0x04 + ((instance->addr >> 24) & 0xFF) + ((instance->addr >> 16) & 0xFF); - crc = 0x01 + ~crc; - //I32HEX_TYPE_EXT_LINEAR_ADDR - furi_string_cat_printf( - instance->str_data, ":02000004%04lX%02X\r\n", (instance->addr >> 16), crc); - instance->addr_last = instance->addr; - } - - while(ind < data_size) { - if((ind + COUNT_BYTE_PAYLOAD) > data_size) { - count_byte = data_size - ind; - } else { - count_byte = COUNT_BYTE_PAYLOAD; - } - //I32HEX_TYPE_DATA - furi_string_cat_printf( - instance->str_data, ":%02X%04lX00", count_byte, (instance->addr & 0xFFFF)); - crc = count_byte + ((instance->addr >> 8) & 0xFF) + (instance->addr & 0xFF); - - for(uint32_t i = 0; i < count_byte; i++) { - furi_string_cat_printf(instance->str_data, "%02X", *data); - crc += *data++; - } - crc = 0x01 + ~crc; - furi_string_cat_printf(instance->str_data, "%02X\r\n", crc); - - ind += count_byte; - instance->addr += count_byte; - } - if(instance->file_open) stream_write_string(instance->stream, instance->str_data); - return ret; -} - -FlipperI32HexFileRet flipper_i32hex_file_bin_to_i32hex_set_end_line(FlipperI32HexFile* instance) { - furi_assert(instance); - - FlipperI32HexFileRet ret = {.status = FlipperI32HexFileStatusOK, .data_size = 0}; - if(instance->file_open != FlipperI32HexFileStatusOpenFileWrite) { - ret.status = FlipperI32HexFileStatusErrorFileWrite; - } - furi_string_reset(instance->str_data); - //I32HEX_TYPE_END_OF_FILE - furi_string_cat_printf(instance->str_data, ":00000001FF\r\n"); - if(instance->file_open) stream_write_string(instance->stream, instance->str_data); - return ret; -} - -void flipper_i32hex_file_bin_to_i32hex_set_addr(FlipperI32HexFile* instance, uint32_t addr) { - furi_assert(instance); - - instance->addr = addr; -} - -const char* flipper_i32hex_file_get_string(FlipperI32HexFile* instance) { - furi_assert(instance); - - return furi_string_get_cstr(instance->str_data); -} - -static FlipperI32HexFileRet flipper_i32hex_file_parse_line( - FlipperI32HexFile* instance, - const char* str, - uint8_t* data, - uint32_t data_size) { - furi_assert(instance); - furi_assert(data); - - char* str1; - uint32_t data_wrire_ind = 0; - uint32_t data_len = 0; - FlipperI32HexFileRet ret = {.status = FlipperI32HexFileStatusErrorData, .data_size = 0}; - - //Search for start of data I32HEX - str1 = strstr(str, ":"); - do { - if(str1 == NULL) { - ret.status = FlipperI32HexFileStatusErrorData; - break; - } - str1++; - if(!hex_char_to_uint8(*str1, str1[1], data + data_wrire_ind)) { - ret.status = FlipperI32HexFileStatusErrorData; - break; - } - str1++; - if(++data_wrire_ind > data_size) { - ret.status = FlipperI32HexFileStatusErrorOverflow; - break; - } - data_len = 5 + data[0]; // +5 bytes per header and crc - while(data_len > data_wrire_ind) { - str1++; - if(!hex_char_to_uint8(*str1, str1[1], data + data_wrire_ind)) { - ret.status = FlipperI32HexFileStatusErrorData; - break; - } - str1++; - if(++data_wrire_ind > data_size) { - ret.status = FlipperI32HexFileStatusErrorOverflow; - break; - } - } - ret.status = FlipperI32HexFileStatusOK; - ret.data_size = data_wrire_ind; - - } while(0); - return ret; -} - -static bool flipper_i32hex_file_check_data(uint8_t* data, uint32_t data_size) { - furi_assert(data); - - uint8_t crc = 0; - uint32_t data_read_ind = 0; - if(data[0] > data_size) return false; - while(data_read_ind < data_size - 1) { - crc += data[data_read_ind++]; - } - return data[data_size - 1] == ((1 + ~crc) & 0xFF); -} - -static FlipperI32HexFileRet flipper_i32hex_file_parse( - FlipperI32HexFile* instance, - const char* str, - uint8_t* data, - uint32_t data_size) { - furi_assert(instance); - furi_assert(data); - - FlipperI32HexFileRet ret = flipper_i32hex_file_parse_line(instance, str, data, data_size); - - if((ret.status == FlipperI32HexFileStatusOK) && (ret.data_size > 4)) { - switch(data[3]) { - case I32HEX_TYPE_DATA: - if(flipper_i32hex_file_check_data(data, ret.data_size)) { - ret.data_size -= 5; - memcpy(data, data + 4, ret.data_size); - ret.status = FlipperI32HexFileStatusData; - } else { - ret.status = FlipperI32HexFileStatusErrorCrc; - ret.data_size = 0; - } - break; - case I32HEX_TYPE_END_OF_FILE: - if(flipper_i32hex_file_check_data(data, ret.data_size)) { - ret.status = FlipperI32HexFileStatusEofFile; - ret.data_size = 0; - } else { - ret.status = FlipperI32HexFileStatusErrorCrc; - ret.data_size = 0; - } - break; - case I32HEX_TYPE_EXT_LINEAR_ADDR: - if(flipper_i32hex_file_check_data(data, ret.data_size)) { - data[0] = data[4]; - data[1] = data[5]; - data[3] = 0; - data[4] = 0; - ret.status = FlipperI32HexFileStatusUdateAddr; - ret.data_size = 4; - } else { - ret.status = FlipperI32HexFileStatusErrorCrc; - ret.data_size = 0; - } - break; - case I32HEX_TYPE_START_LINEAR_ADDR: - ret.status = FlipperI32HexFileStatusErrorUnsupportedCommand; - ret.data_size = 0; - break; - default: - ret.status = FlipperI32HexFileStatusErrorUnsupportedCommand; - ret.data_size = 0; - break; - } - } else { - ret.status = FlipperI32HexFileStatusErrorData; - ret.data_size = 0; - } - return ret; -} - -bool flipper_i32hex_file_check(FlipperI32HexFile* instance) { - furi_assert(instance); - - uint32_t data_size = 280; - uint8_t data[280] = {0}; - bool ret = true; - - if(instance->file_open != FlipperI32HexFileStatusOpenFileRead) { - FURI_LOG_E(TAG, "File is not open"); - ret = false; - } else { - stream_rewind(instance->stream); - - while(stream_read_line(instance->stream, instance->str_data)) { - FlipperI32HexFileRet parse_ret = flipper_i32hex_file_parse( - instance, furi_string_get_cstr(instance->str_data), data, data_size); - - if(parse_ret.status < 0) { - ret = false; - } - } - stream_rewind(instance->stream); - } - return ret; -} - -FlipperI32HexFileRet flipper_i32hex_file_i32hex_to_bin_get_data( - FlipperI32HexFile* instance, - uint8_t* data, - uint32_t data_size) { - furi_assert(instance); - furi_assert(data); - - FlipperI32HexFileRet ret = {.status = FlipperI32HexFileStatusOK, .data_size = 0}; - if(instance->file_open != FlipperI32HexFileStatusOpenFileRead) { - ret.status = FlipperI32HexFileStatusErrorFileRead; - } else { - stream_read_line(instance->stream, instance->str_data); - ret = flipper_i32hex_file_parse( - instance, furi_string_get_cstr(instance->str_data), data, data_size); - } - - return ret; -} \ No newline at end of file diff --git a/applications/external/avr_isp_programmer/helpers/flipper_i32hex_file.h b/applications/external/avr_isp_programmer/helpers/flipper_i32hex_file.h deleted file mode 100644 index 765b94beb59..00000000000 --- a/applications/external/avr_isp_programmer/helpers/flipper_i32hex_file.h +++ /dev/null @@ -1,55 +0,0 @@ -#pragma once - -#include - -typedef struct FlipperI32HexFile FlipperI32HexFile; - -typedef enum { - FlipperI32HexFileStatusOK = 0, - FlipperI32HexFileStatusData = 2, - FlipperI32HexFileStatusUdateAddr = 3, - FlipperI32HexFileStatusEofFile = 4, - FlipperI32HexFileStatusOpenFileWrite = 5, - FlipperI32HexFileStatusOpenFileRead = 6, - - // Errors - FlipperI32HexFileStatusErrorCrc = (-1), - FlipperI32HexFileStatusErrorOverflow = (-2), - FlipperI32HexFileStatusErrorData = (-3), - FlipperI32HexFileStatusErrorUnsupportedCommand = (-4), - FlipperI32HexFileStatusErrorNoOpenFile = (-5), - FlipperI32HexFileStatusErrorFileWrite = (-6), - FlipperI32HexFileStatusErrorFileRead = (-7), - - FlipperI32HexFileStatusReserved = - 0x7FFFFFFF, ///< Prevents enum down-size compiler optimization. -} FlipperI32HexFileStatus; - -typedef struct { - FlipperI32HexFileStatus status; - uint32_t data_size; -} FlipperI32HexFileRet; - -FlipperI32HexFile* flipper_i32hex_file_open_write(const char* name, uint32_t start_addr); - -FlipperI32HexFile* flipper_i32hex_file_open_read(const char* name); - -void flipper_i32hex_file_close(FlipperI32HexFile* instance); - -FlipperI32HexFileRet flipper_i32hex_file_bin_to_i32hex_set_data( - FlipperI32HexFile* instance, - uint8_t* data, - uint32_t data_size); - -FlipperI32HexFileRet flipper_i32hex_file_bin_to_i32hex_set_end_line(FlipperI32HexFile* instance); - -const char* flipper_i32hex_file_get_string(FlipperI32HexFile* instance); - -void flipper_i32hex_file_bin_to_i32hex_set_addr(FlipperI32HexFile* instance, uint32_t addr); - -bool flipper_i32hex_file_check(FlipperI32HexFile* instance); - -FlipperI32HexFileRet flipper_i32hex_file_i32hex_to_bin_get_data( - FlipperI32HexFile* instance, - uint8_t* data, - uint32_t data_size); \ No newline at end of file diff --git a/applications/external/avr_isp_programmer/images/avr_app_icon_10x10.png b/applications/external/avr_isp_programmer/images/avr_app_icon_10x10.png deleted file mode 100644 index 533787fe3569f70196d3f71e8373102dfd3967a0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3614 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 Vpqb^9w@m;7?5&;gRaoD1{{|C}E&c!i diff --git a/applications/external/avr_isp_programmer/images/avr_wiring.png b/applications/external/avr_isp_programmer/images/avr_wiring.png deleted file mode 100644 index 957012405127ce4504ef8cd4e805b1fc31963026..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4513 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 diff --git a/applications/external/avr_isp_programmer/images/chif_not_found_83x37.png b/applications/external/avr_isp_programmer/images/chif_not_found_83x37.png deleted file mode 100644 index b03bf3567ade9b2e4ce2d29328032ebd40c89401..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3742 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 diff --git a/applications/external/avr_isp_programmer/images/chip_error_70x22.png b/applications/external/avr_isp_programmer/images/chip_error_70x22.png deleted file mode 100644 index 16f81178c0ec857591db3fe40830a5955db54e73..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3688 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 diff --git a/applications/external/avr_isp_programmer/images/chip_long_70x22.png b/applications/external/avr_isp_programmer/images/chip_long_70x22.png deleted file mode 100644 index 3edfff82de952d6f49a012154a23104e3fd935fa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3656 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 diff --git a/applications/external/avr_isp_programmer/images/dolphin_nice_96x59.png b/applications/external/avr_isp_programmer/images/dolphin_nice_96x59.png deleted file mode 100644 index a299d3630239b4486e249cc501872bed5996df3b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2459 zcmbVO3s4i+8V(M(gEFORwSrA`4O0uPn|M|5y* zB*aMDxC&7(gP9JN;POOi-9khrC>Z9YJs2U!LnVcQEEC0fDtKo&ILlzb30%M}3J^;~ zv7RzcsilOs4Mq@tD*&R;!LMSk2A~{(`HK9|hQBqEX)3sQr9Je6SZU*F-^fD-p+~Hs; zHLkO%v?>ZoxEv+F#whudr%615FkA0DYR0tMEo}3OOY#xecLWe>xV?u5KtSmC^ z7)Fmj6gjfKstiEV-*Cxbbb+&rRWuI_rBJ)ybs_f1Rn&f2>q3pYwI^|J(hdn{j{0EZIm_F zpIyIWLsRUgOItR-dUbVd|6Zo=_BU_Tj4|{{jxO#=JH4o8er(5{!nZD_j4}MH&zh~9 zVLC~y(0-D6GO0ghZD8BYzP?o{>22~lT6^d@X{SwQ8vrNY-PPIMajIwC)`s14Ep72@ zeq7YOzM`?U{+W)ocXBr`eSOcpk?Rxc=ou5&)fWW|pD};-Z0mvk9}=&`Rb&y<77W~a z(>6YM;6Y5aIU~JKZ}mQZynKHiSTQ#Bczn@&jTiN^?vPJ(jhm7cXLx0oum5P$`TceG zU+wR;OO^)8CVlnM)5p$CO&e94KJt>HccCaHGusmW_b`T6m| z-R6V6Db1pErTot?^d22ojm+2>_)FbD`_+WbDGMx9f@hO27maS2`csiV(D&Fs`PS2& zvrq18du_&zXID(!KIxsU$)iuTYuZ?zmYiP&n&i@Be{IdbS-jA2c0QAlu5NXQv_0K< z3Hvs4eeu6B7yD&CNT~gIkMV&UkRU=V!iQ(+_(O&u^ah$+s{_yn(yBYeD40HeU{xGsIT6W Zfq!wOp!QjS=r55&&8K)tZJ>! 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 B&F}yK diff --git a/applications/external/avr_isp_programmer/images/link_waiting_77x56.png b/applications/external/avr_isp_programmer/images/link_waiting_77x56.png deleted file mode 100644 index d7d32aed59a19e24a7e1f2216c806872610a0af5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3883 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 - -//https://github.com/avrdudes/avrdude/blob/master/src/avrintel.c - -const AvrIspChipArr avr_isp_chip_arr[] = { // Value of -1 typically means unknown - //{mcu_name, mcuid, family, {sig, na, ture}, flstart, flsize, pgsiz, nb, bootsz, eestart, eesize, ep, rambeg, ramsiz, nf, nl, ni}, // Source - {"ATtiny4", 0, F_AVR8L, {0x1E, 0x8F, 0x0A}, 0, 0x00200, 0x010, 0, 0, 0, 0, 0, 0x0040, 0x0020, 1, 1, 10}, // atdf, avr-gcc 12.2.0, avrdude, boot size (manual) - {"ATtiny5", 1, F_AVR8L, {0x1E, 0x8F, 0x09}, 0, 0x00200, 0x010, 0, 0, 0, 0, 0, 0x0040, 0x0020, 1, 1, 11}, // atdf, avr-gcc 12.2.0, avrdude, boot size (manual) - {"ATtiny9", 2, F_AVR8L, {0x1E, 0x90, 0x08}, 0, 0x00400, 0x010, 0, 0, 0, 0, 0, 0x0040, 0x0020, 1, 1, 10}, // atdf, avr-gcc 12.2.0, avrdude, boot size (manual) - {"ATtiny10", 3, F_AVR8L, {0x1E, 0x90, 0x03}, 0, 0x00400, 0x010, 0, 0, 0, 0, 0, 0x0040, 0x0020, 1, 1, 11}, // atdf, avr-gcc 12.2.0, avrdude, boot size (manual) - {"ATtiny20", 4, F_AVR8L, {0x1E, 0x91, 0x0F}, 0, 0x00800, 0x020, 0, 0, 0, 0, 0, 0x0040, 0x0080, 1, 1, 17}, // atdf, avr-gcc 12.2.0, avrdude, boot size (manual) - {"ATtiny40", 5, F_AVR8L, {0x1E, 0x92, 0x0E}, 0, 0x01000, 0x040, 0, 0, 0, 0, 0, 0x0040, 0x0100, 1, 1, 18}, // atdf, avr-gcc 12.2.0, avrdude, boot size (manual) - {"ATtiny102", 6, F_AVR8L, {0x1E, 0x90, 0x0C}, 0, 0x00400, 0x010, 0, 0, 0, 0, 0, 0x0040, 0x0020, 1, 1, 16}, // atdf, avrdude, boot size (manual) - {"ATtiny104", 7, F_AVR8L, {0x1E, 0x90, 0x0B}, 0, 0x00400, 0x010, 0, 0, 0, 0, 0, 0x0040, 0x0020, 1, 1, 16}, // atdf, avrdude, boot size (manual) - - {"ATtiny11", 8, F_AVR8, {0x1E, 0x90, 0x04}, 0, 0x00400, 0x001, 0, 0, 0, 0x0040, 1, 0x0060, 0x0020, 1, 1, 5}, // atdf, avr-gcc 12.2.0, avrdude - {"ATtiny12", 9, F_AVR8, {0x1E, 0x90, 0x05}, 0, 0x00400, 0x001, 0, 0, 0, 0x0040, 2, 0x0060, 0x0020, 1, 1, 6}, // atdf, avr-gcc 12.2.0, avrdude - {"ATtiny13", 10, F_AVR8, {0x1E, 0x90, 0x07}, 0, 0x00400, 0x020, 0, 0, 0, 0x0040, 4, 0x0060, 0x0040, 2, 1, 10}, // atdf, avr-gcc 12.2.0, avrdude - {"ATtiny13A", 11, F_AVR8, {0x1E, 0x90, 0x07}, 0, 0x00400, 0x020, 0, 0, 0, 0x0040, 4, 0x0060, 0x0040, 2, 1, 10}, // atdf, avr-gcc 12.2.0, avrdude - {"ATtiny15", 12, F_AVR8, {0x1E, 0x90, 0x06}, 0, 0x00400, 0x001, 0, 0, 0, 0x0040, 2, 0x0060, 0x0020, 1, 1, 9}, // atdf, avr-gcc 12.2.0, avrdude - {"ATtiny22", 13, F_AVR8, {0x1E, 0x91, 0x06}, 0, 0x00800, -1, 0, 0, -1, -1, -1, 0x0060, 0x0080, 1, 1, 3}, // avr-gcc 12.2.0, boot size (manual) - {"ATtiny24", 14, F_AVR8, {0x1E, 0x91, 0x0B}, 0, 0x00800, 0x020, 0, 0, 0, 0x0080, 4, 0x0060, 0x0080, 3, 1, 17}, // atdf, avr-gcc 12.2.0, avrdude - {"ATtiny24A", 15, F_AVR8, {0x1E, 0x91, 0x0B}, 0, 0x00800, 0x020, 0, 0, 0, 0x0080, 4, 0x0060, 0x0080, 3, 1, 17}, // atdf, avr-gcc 12.2.0, avrdude - {"ATtiny25", 16, F_AVR8, {0x1E, 0x91, 0x08}, 0, 0x00800, 0x020, 0, 0, 0, 0x0080, 4, 0x0060, 0x0080, 3, 1, 15}, // atdf, avr-gcc 12.2.0, avrdude - {"ATtiny26", 17, F_AVR8, {0x1E, 0x91, 0x09}, 0, 0x00800, 0x020, 0, 0, 0, 0x0080, 4, 0x0060, 0x0080, 2, 1, 12}, // atdf, avr-gcc 12.2.0, avrdude - {"ATtiny28", 18, F_AVR8, {0x1E, 0x91, 0x07}, 0, 0x00800, 0x002, 0, 0, 0, 0, 0, 0x0060, 0x0020, 1, 1, 6}, // atdf, avr-gcc 12.2.0, avrdude - {"ATtiny43U", 19, F_AVR8, {0x1E, 0x92, 0x0C}, 0, 0x01000, 0x040, 0, 0, 0, 0x0040, 4, 0x0060, 0x0100, 3, 1, 16}, // atdf, avr-gcc 12.2.0, avrdude - {"ATtiny44", 20, F_AVR8, {0x1E, 0x92, 0x07}, 0, 0x01000, 0x040, 0, 0, 0, 0x0100, 4, 0x0060, 0x0100, 3, 1, 17}, // atdf, avr-gcc 12.2.0, avrdude - {"ATtiny44A", 21, F_AVR8, {0x1E, 0x92, 0x07}, 0, 0x01000, 0x040, 0, 0, 0, 0x0100, 4, 0x0060, 0x0100, 3, 1, 17}, // atdf, avr-gcc 12.2.0, avrdude - {"ATtiny45", 22, F_AVR8, {0x1E, 0x92, 0x06}, 0, 0x01000, 0x040, 0, 0, 0, 0x0100, 4, 0x0060, 0x0100, 3, 1, 15}, // atdf, avr-gcc 12.2.0, avrdude - {"ATtiny48", 23, F_AVR8, {0x1E, 0x92, 0x09}, 0, 0x01000, 0x040, 0, 0, 0, 0x0040, 4, 0x0100, 0x0100, 3, 1, 20}, // atdf, avr-gcc 12.2.0, avrdude - {"ATtiny84", 24, F_AVR8, {0x1E, 0x93, 0x0C}, 0, 0x02000, 0x040, 0, 0, 0, 0x0200, 4, 0x0060, 0x0200, 3, 1, 17}, // atdf, avr-gcc 12.2.0, avrdude - {"ATtiny84A", 25, F_AVR8, {0x1E, 0x93, 0x0C}, 0, 0x02000, 0x040, 0, 0, 0, 0x0200, 4, 0x0060, 0x0200, 3, 1, 17}, // atdf, avr-gcc 12.2.0, avrdude - {"ATtiny85", 26, F_AVR8, {0x1E, 0x93, 0x0B}, 0, 0x02000, 0x040, 0, 0, 0, 0x0200, 4, 0x0060, 0x0200, 3, 1, 15}, // atdf, avr-gcc 12.2.0, avrdude - {"ATtiny87", 27, F_AVR8, {0x1E, 0x93, 0x87}, 0, 0x02000, 0x080, 0, 0, 0, 0x0200, 4, 0x0100, 0x0200, 3, 1, 20}, // atdf, avr-gcc 12.2.0, avrdude - {"ATtiny88", 28, F_AVR8, {0x1E, 0x93, 0x11}, 0, 0x02000, 0x040, 0, 0, 0, 0x0040, 4, 0x0100, 0x0200, 3, 1, 20}, // atdf, avr-gcc 12.2.0, avrdude - {"ATtiny167", 29, F_AVR8, {0x1E, 0x94, 0x87}, 0, 0x04000, 0x080, 0, 0, 0, 0x0200, 4, 0x0100, 0x0200, 3, 1, 20}, // atdf, avr-gcc 12.2.0, avrdude - {"ATtiny261", 30, F_AVR8, {0x1E, 0x91, 0x0C}, 0, 0x00800, 0x020, 0, 0, 0, 0x0080, 4, 0x0060, 0x0080, 3, 1, 19}, // atdf, avr-gcc 12.2.0, avrdude - {"ATtiny261A", 31, F_AVR8, {0x1E, 0x91, 0x0C}, 0, 0x00800, 0x020, 0, 0, 0, 0x0080, 4, 0x0060, 0x0080, 3, 1, 19}, // atdf, avr-gcc 12.2.0, avrdude - {"ATtiny441", 32, F_AVR8, {0x1E, 0x92, 0x15}, 0, 0x01000, 0x010, 0, 0, 0, 0x0100, 4, 0x0100, 0x0100, 3, 1, 30}, // atdf, avr-gcc 12.2.0, avrdude - {"ATtiny461", 33, F_AVR8, {0x1E, 0x92, 0x08}, 0, 0x01000, 0x040, 0, 0, 0, 0x0100, 4, 0x0060, 0x0100, 3, 1, 19}, // atdf, avr-gcc 12.2.0, avrdude - {"ATtiny461A", 34, F_AVR8, {0x1E, 0x92, 0x08}, 0, 0x01000, 0x040, 0, 0, 0, 0x0100, 4, 0x0060, 0x0100, 3, 1, 19}, // atdf, avr-gcc 12.2.0, avrdude - {"ATtiny828", 35, F_AVR8, {0x1E, 0x93, 0x14}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0100, 4, 0x0100, 0x0200, 3, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude - {"ATtiny828R", 36, F_AVR8, {0x1E, 0x93, 0x14}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0100, 4, 0x0100, 0x0200, 3, 1, 26}, // avrdude, from ATtiny828 - {"ATtiny841", 37, F_AVR8, {0x1E, 0x93, 0x15}, 0, 0x02000, 0x010, 0, 0, 0, 0x0200, 4, 0x0100, 0x0200, 3, 1, 30}, // atdf, avr-gcc 12.2.0, avrdude - {"ATtiny861", 38, F_AVR8, {0x1E, 0x93, 0x0D}, 0, 0x02000, 0x040, 0, 0, 0, 0x0200, 4, 0x0060, 0x0200, 3, 1, 19}, // atdf, avr-gcc 12.2.0, avrdude - {"ATtiny861A", 39, F_AVR8, {0x1E, 0x93, 0x0D}, 0, 0x02000, 0x040, 0, 0, 0, 0x0200, 4, 0x0060, 0x0200, 3, 1, 19}, // atdf, avr-gcc 12.2.0, avrdude - {"ATtiny1634", 40, F_AVR8, {0x1E, 0x94, 0x12}, 0, 0x04000, 0x020, 0, 0, 0, 0x0100, 4, 0x0100, 0x0400, 3, 1, 28}, // atdf, avr-gcc 12.2.0, avrdude - {"ATtiny1634R", 41, F_AVR8, {0x1E, 0x94, 0x12}, 0, 0x04000, 0x020, 0, 0, 0, 0x0100, 4, 0x0100, 0x0400, 3, 1, 28}, // avrdude, from ATtiny1634 - {"ATtiny2313", 42, F_AVR8, {0x1E, 0x91, 0x0A}, 0, 0x00800, 0x020, 0, 0, 0, 0x0080, 4, 0x0060, 0x0080, 3, 1, 19}, // atdf, avr-gcc 12.2.0, avrdude - {"ATtiny2313A", 43, F_AVR8, {0x1E, 0x91, 0x0A}, 0, 0x00800, 0x020, 0, 0, 0, 0x0080, 4, 0x0060, 0x0080, 3, 1, 21}, // atdf, avr-gcc 12.2.0, avrdude - {"ATtiny4313", 44, F_AVR8, {0x1E, 0x92, 0x0D}, 0, 0x01000, 0x040, 0, 0, 0, 0x0100, 4, 0x0060, 0x0100, 3, 1, 21}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega8", 45, F_AVR8, {0x1E, 0x93, 0x07}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0200, 4, 0x0060, 0x0400, 2, 1, 19}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega8A", 46, F_AVR8, {0x1E, 0x93, 0x07}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0200, 4, 0x0060, 0x0400, 2, 1, 19}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega8HVA", 47, F_AVR8, {0x1E, 0x93, 0x10}, 0, 0x02000, 0x080, 0, 0, 0, 0x0100, 4, 0x0100, 0x0200, 1, 1, 21}, // atdf, avr-gcc 12.2.0 - {"ATmega8U2", 48, F_AVR8, {0x1E, 0x93, 0x89}, 0, 0x02000, 0x080, 4, 0x0200, 0, 0x0200, 4, 0x0100, 0x0200, 3, 1, 29}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega16", 49, F_AVR8, {0x1E, 0x94, 0x03}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0060, 0x0400, 2, 1, 21}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega16A", 50, F_AVR8, {0x1E, 0x94, 0x03}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0060, 0x0400, 2, 1, 21}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega16HVA", 51, F_AVR8, {0x1E, 0x94, 0x0C}, 0, 0x04000, 0x080, 0, 0, 0, 0x0100, 4, 0x0100, 0x0200, 1, 1, 21}, // atdf, avr-gcc 12.2.0 - {"ATmega16HVB", 52, F_AVR8, {0x1E, 0x94, 0x0D}, 0, 0x04000, 0x080, 4, 0x0200, 0, 0x0200, 4, 0x0100, 0x0400, 2, 1, 29}, // atdf, avr-gcc 12.2.0 - {"ATmega16HVBrevB", 53, F_AVR8, {0x1E, 0x94, 0x0D}, 0, 0x04000, 0x080, 4, 0x0200, 0, 0x0200, 4, 0x0100, 0x0400, 2, 1, 29}, // atdf, avr-gcc 12.2.0 - {"ATmega16M1", 54, F_AVR8, {0x1E, 0x94, 0x84}, 0, 0x04000, 0x080, 4, 0x0200, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 31}, // atdf, avr-gcc 12.2.0 - {"ATmega16HVA2", 55, F_AVR8, {0x1E, 0x94, 0x0E}, 0, 0x04000, 0x080, -1, -1, -1, -1, -1, 0x0100, 0x0400, 2, 1, 22}, // avr-gcc 12.2.0 - {"ATmega16U2", 56, F_AVR8, {0x1E, 0x94, 0x89}, 0, 0x04000, 0x080, 4, 0x0200, 0, 0x0200, 4, 0x0100, 0x0200, 3, 1, 29}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega16U4", 57, F_AVR8, {0x1E, 0x94, 0x88}, 0, 0x04000, 0x080, 4, 0x0200, 0, 0x0200, 4, 0x0100, 0x0500, 3, 1, 43}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega32", 58, F_AVR8, {0x1E, 0x95, 0x02}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0060, 0x0800, 2, 1, 21}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega32A", 59, F_AVR8, {0x1E, 0x95, 0x02}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0060, 0x0800, 2, 1, 21}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega32HVB", 60, F_AVR8, {0x1E, 0x95, 0x10}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 2, 1, 29}, // atdf, avr-gcc 12.2.0 - {"ATmega32HVBrevB", 61, F_AVR8, {0x1E, 0x95, 0x10}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 2, 1, 29}, // atdf, avr-gcc 12.2.0 - {"ATmega32C1", 62, F_AVR8, {0x1E, 0x95, 0x86}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 31}, // atdf, avr-gcc 12.2.0 - {"ATmega32M1", 63, F_AVR8, {0x1E, 0x95, 0x84}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega32U2", 64, F_AVR8, {0x1E, 0x95, 0x8A}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0400, 3, 1, 29}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega32U4", 65, F_AVR8, {0x1E, 0x95, 0x87}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0a00, 3, 1, 43}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega32U6", 66, F_AVR8, {0x1E, 0x95, 0x88}, 0, 0x08000, 0x080, 4, 0x0200, -1, -1, -1, 0x0100, 0x0a00, 3, 1, 38}, // avr-gcc 12.2.0, boot size (manual) - {"ATmega48", 67, F_AVR8, {0x1E, 0x92, 0x05}, 0, 0x01000, 0x040, 0, 0, 0, 0x0100, 4, 0x0100, 0x0200, 3, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega48A", 68, F_AVR8, {0x1E, 0x92, 0x05}, 0, 0x01000, 0x040, 0, 0, 0, 0x0100, 4, 0x0100, 0x0200, 3, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega48P", 69, F_AVR8, {0x1E, 0x92, 0x0A}, 0, 0x01000, 0x040, 0, 0, 0, 0x0100, 4, 0x0100, 0x0200, 3, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega48PA", 70, F_AVR8, {0x1E, 0x92, 0x0A}, 0, 0x01000, 0x040, 0, 0, 0, 0x0100, 4, 0x0100, 0x0200, 3, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega48PB", 71, F_AVR8, {0x1E, 0x92, 0x10}, 0, 0x01000, 0x040, 0, 0, 0, 0x0100, 4, 0x0100, 0x0200, 3, 1, 27}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega64", 72, F_AVR8, {0x1E, 0x96, 0x02}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 35}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega64A", 73, F_AVR8, {0x1E, 0x96, 0x02}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 35}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega64HVE", 74, F_AVR8, {0x1E, 0x96, 0x10}, 0, 0x10000, 0x080, 4, 0x0400, -1, -1, -1, 0x0100, 0x1000, 2, 1, 25}, // avr-gcc 12.2.0, boot size (manual) - {"ATmega64C1", 75, F_AVR8, {0x1E, 0x96, 0x86}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 31}, // atdf, avr-gcc 12.2.0 - {"ATmega64M1", 76, F_AVR8, {0x1E, 0x96, 0x84}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega64HVE2", 77, F_AVR8, {0x1E, 0x96, 0x10}, 0, 0x10000, 0x080, 4, 0x0400, 0, 0x0400, 4, 0x0100, 0x1000, 2, 1, 25}, // atdf, avr-gcc 12.2.0 - {"ATmega64RFR2", 78, F_AVR8, {0x1E, 0xA6, 0x02}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0200, 0x2000, 3, 1, 77}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega88", 79, F_AVR8, {0x1E, 0x93, 0x0A}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega88A", 80, F_AVR8, {0x1E, 0x93, 0x0A}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega88P", 81, F_AVR8, {0x1E, 0x93, 0x0F}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega88PA", 82, F_AVR8, {0x1E, 0x93, 0x0F}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega88PB", 83, F_AVR8, {0x1E, 0x93, 0x16}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 27}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega103", 84, F_AVR8, {0x1E, 0x97, 0x01}, 0, 0x20000, 0x100, 0, 0, 0, 0x1000, 1, 0x0060, 0x0fa0, 1, 1, 24}, // avr-gcc 12.2.0, avrdude, boot size (manual) - {"ATmega128", 85, F_AVR8, {0x1E, 0x97, 0x02}, 0, 0x20000, 0x100, 4, 0x0400, 0, 0x1000, 8, 0x0100, 0x1000, 3, 1, 35}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega128A", 86, F_AVR8, {0x1E, 0x97, 0x02}, 0, 0x20000, 0x100, 4, 0x0400, 0, 0x1000, 8, 0x0100, 0x1000, 3, 1, 35}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega128RFA1", 87, F_AVR8, {0x1E, 0xA7, 0x01}, 0, 0x20000, 0x100, 4, 0x0400, 0, 0x1000, 8, 0x0200, 0x4000, 3, 1, 72}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega128RFR2", 88, F_AVR8, {0x1E, 0xA7, 0x02}, 0, 0x20000, 0x100, 4, 0x0400, 0, 0x1000, 8, 0x0200, 0x4000, 3, 1, 77}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega161", 89, F_AVR8, {0x1E, 0x94, 0x01}, 0, 0x04000, 0x080, 1, 0x0400, 0, 0x0200, 1, 0x0060, 0x0400, 1, 1, 21}, // avr-gcc 12.2.0, avrdude, boot size (manual) - {"ATmega162", 90, F_AVR8, {0x1E, 0x94, 0x04}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 28}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega163", 91, F_AVR8, {0x1E, 0x94, 0x02}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 1, 0x0060, 0x0400, 2, 1, 18}, // avr-gcc 12.2.0, avrdude, boot size (manual) - {"ATmega164A", 92, F_AVR8, {0x1E, 0x94, 0x0F}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega164P", 93, F_AVR8, {0x1E, 0x94, 0x0A}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega164PA", 94, F_AVR8, {0x1E, 0x94, 0x0A}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega165", 95, F_AVR8, {0x1E, 0x94, 0x10}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 22}, // avr-gcc 12.2.0, avrdude, boot size (manual) - {"ATmega165A", 96, F_AVR8, {0x1E, 0x94, 0x10}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 22}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega165P", 97, F_AVR8, {0x1E, 0x94, 0x07}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 22}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega165PA", 98, F_AVR8, {0x1E, 0x94, 0x07}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 22}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega168", 99, F_AVR8, {0x1E, 0x94, 0x06}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega168A", 100, F_AVR8, {0x1E, 0x94, 0x06}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega168P", 101, F_AVR8, {0x1E, 0x94, 0x0B}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega168PA", 102, F_AVR8, {0x1E, 0x94, 0x0B}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega168PB", 103, F_AVR8, {0x1E, 0x94, 0x15}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 27}, // atdf, avr-gcc 7.3.0, avrdude - {"ATmega169", 104, F_AVR8, {0x1E, 0x94, 0x05}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 23}, // avr-gcc 12.2.0, avrdude, boot size (manual) - {"ATmega169A", 105, F_AVR8, {0x1E, 0x94, 0x11}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 23}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega169P", 106, F_AVR8, {0x1E, 0x94, 0x05}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 23}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega169PA", 107, F_AVR8, {0x1E, 0x94, 0x05}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 23}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega256RFR2", 108, F_AVR8, {0x1E, 0xA8, 0x02}, 0, 0x40000, 0x100, 4, 0x0400, 0, 0x2000, 8, 0x0200, 0x8000, 3, 1, 77}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega323", 109, F_AVR8, {0x1E, 0x95, 0x01}, 0, 0x08000, 0x080, 4, 0x0200, -1, -1, -1, 0x0060, 0x0800, 2, 1, 21}, // avr-gcc 12.2.0, boot size (manual) - {"ATmega324A", 110, F_AVR8, {0x1E, 0x95, 0x15}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega324P", 111, F_AVR8, {0x1E, 0x95, 0x08}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega324PA", 112, F_AVR8, {0x1E, 0x95, 0x11}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega324PB", 113, F_AVR8, {0x1E, 0x95, 0x17}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 51}, // atdf, avrdude - {"ATmega325", 114, F_AVR8, {0x1E, 0x95, 0x05}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 22}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega325A", 115, F_AVR8, {0x1E, 0x95, 0x05}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 22}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega325P", 116, F_AVR8, {0x1E, 0x95, 0x0D}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 22}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega325PA", 117, F_AVR8, {0x1E, 0x95, 0x0D}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 22}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega328", 118, F_AVR8, {0x1E, 0x95, 0x14}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega328P", 119, F_AVR8, {0x1E, 0x95, 0x0F}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega328PB", 120, F_AVR8, {0x1E, 0x95, 0x16}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 45}, // atdf, avr-gcc 7.3.0, avrdude - {"ATmega329", 121, F_AVR8, {0x1E, 0x95, 0x03}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 23}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega329A", 122, F_AVR8, {0x1E, 0x95, 0x03}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 23}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega329P", 123, F_AVR8, {0x1E, 0x95, 0x0B}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 23}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega329PA", 124, F_AVR8, {0x1E, 0x95, 0x0B}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 23}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega406", 125, F_AVR8, {0x1E, 0x95, 0x07}, 0, 0x0a000, 0x080, 4, 0x0200, 0, 0x0200, 4, 0x0100, 0x0800, 2, 1, 23}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega640", 126, F_AVR8, {0x1E, 0x96, 0x08}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x1000, 8, 0x0200, 0x2000, 3, 1, 57}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega644", 127, F_AVR8, {0x1E, 0x96, 0x09}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 28}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega644A", 128, F_AVR8, {0x1E, 0x96, 0x09}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega644P", 129, F_AVR8, {0x1E, 0x96, 0x0A}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega644PA", 130, F_AVR8, {0x1E, 0x96, 0x0A}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega644RFR2", 131, F_AVR8, {0x1E, 0xA6, 0x03}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0200, 0x2000, 3, 1, 77}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega645", 132, F_AVR8, {0x1E, 0x96, 0x05}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 22}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega645A", 133, F_AVR8, {0x1E, 0x96, 0x05}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 22}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega645P", 134, F_AVR8, {0x1E, 0x96, 0x0D}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 22}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega649", 135, F_AVR8, {0x1E, 0x96, 0x03}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 23}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega649A", 136, F_AVR8, {0x1E, 0x96, 0x03}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 23}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega649P", 137, F_AVR8, {0x1E, 0x96, 0x0B}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 23}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega1280", 138, F_AVR8, {0x1E, 0x97, 0x03}, 0, 0x20000, 0x100, 4, 0x0400, 0, 0x1000, 8, 0x0200, 0x2000, 3, 1, 57}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega1281", 139, F_AVR8, {0x1E, 0x97, 0x04}, 0, 0x20000, 0x100, 4, 0x0400, 0, 0x1000, 8, 0x0200, 0x2000, 3, 1, 57}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega1284", 140, F_AVR8, {0x1E, 0x97, 0x06}, 0, 0x20000, 0x100, 4, 0x0400, 0, 0x1000, 8, 0x0100, 0x4000, 3, 1, 35}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega1284P", 141, F_AVR8, {0x1E, 0x97, 0x05}, 0, 0x20000, 0x100, 4, 0x0400, 0, 0x1000, 8, 0x0100, 0x4000, 3, 1, 35}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega1284RFR2", 142, F_AVR8, {0x1E, 0xA7, 0x03}, 0, 0x20000, 0x100, 4, 0x0400, 0, 0x1000, 8, 0x0200, 0x4000, 3, 1, 77}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega2560", 143, F_AVR8, {0x1E, 0x98, 0x01}, 0, 0x40000, 0x100, 4, 0x0400, 0, 0x1000, 8, 0x0200, 0x2000, 3, 1, 57}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega2561", 144, F_AVR8, {0x1E, 0x98, 0x02}, 0, 0x40000, 0x100, 4, 0x0400, 0, 0x1000, 8, 0x0200, 0x2000, 3, 1, 57}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega2564RFR2", 145, F_AVR8, {0x1E, 0xA8, 0x03}, 0, 0x40000, 0x100, 4, 0x0400, 0, 0x2000, 8, 0x0200, 0x8000, 3, 1, 77}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega3250", 146, F_AVR8, {0x1E, 0x95, 0x06}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 25}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega3250A", 147, F_AVR8, {0x1E, 0x95, 0x06}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 25}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega3250P", 148, F_AVR8, {0x1E, 0x95, 0x0E}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 25}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega3250PA", 149, F_AVR8, {0x1E, 0x95, 0x0E}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 25}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega3290", 150, F_AVR8, {0x1E, 0x95, 0x04}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 25}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega3290A", 151, F_AVR8, {0x1E, 0x95, 0x04}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 25}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega3290P", 152, F_AVR8, {0x1E, 0x95, 0x0C}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 25}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega3290PA", 153, F_AVR8, {0x1E, 0x95, 0x0C}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 25}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega6450", 154, F_AVR8, {0x1E, 0x96, 0x06}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 25}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega6450A", 155, F_AVR8, {0x1E, 0x96, 0x06}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 25}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega6450P", 156, F_AVR8, {0x1E, 0x96, 0x0E}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 25}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega6490", 157, F_AVR8, {0x1E, 0x96, 0x04}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 25}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega6490A", 158, F_AVR8, {0x1E, 0x96, 0x04}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 25}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega6490P", 159, F_AVR8, {0x1E, 0x96, 0x0C}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 25}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega8515", 160, F_AVR8, {0x1E, 0x93, 0x06}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0200, 4, 0x0060, 0x0200, 2, 1, 17}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega8535", 161, F_AVR8, {0x1E, 0x93, 0x08}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0200, 4, 0x0060, 0x0200, 2, 1, 21}, // atdf, avr-gcc 12.2.0, avrdude - {"AT43USB320", 162, F_AVR8, {0xff, -1, -1}, 0, 0x10000, -1, -1, -1, -1, -1, -1, 0x0060, 0x0200, -1, -1, 0}, // avr-gcc 12.2.0 - {"AT43USB355", 163, F_AVR8, {0xff, -1, -1}, 0, 0x06000, -1, -1, -1, -1, -1, -1, 0x0060, 0x0400, -1, -1, 0}, // avr-gcc 12.2.0 - {"AT76C711", 164, F_AVR8, {0xff, -1, -1}, 0, 0x04000, -1, -1, -1, -1, -1, -1, 0x0060, 0x07a0, -1, -1, 0}, // avr-gcc 12.2.0 - {"AT86RF401", 165, F_AVR8, {0x1E, 0x91, 0x81}, 0, 0x00800, -1, -1, -1, -1, -1, -1, 0x0060, 0x0080, 0, 1, 3}, // avr-gcc 12.2.0 - {"AT90PWM1", 166, F_AVR8, {0x1E, 0x93, 0x83}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0200, 3, 1, 32}, // atdf, avr-gcc 12.2.0 - {"AT90PWM2", 167, F_AVR8, {0x1E, 0x93, 0x81}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0200, 3, 1, 32}, // avr-gcc 12.2.0, avrdude, boot size (manual) - {"AT90PWM2B", 168, F_AVR8, {0x1E, 0x93, 0x83}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0200, 3, 1, 32}, // atdf, avr-gcc 12.2.0, avrdude - {"AT90PWM3", 169, F_AVR8, {0x1E, 0x93, 0x81}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0200, 3, 1, 32}, // atdf, avr-gcc 12.2.0, avrdude - {"AT90PWM3B", 170, F_AVR8, {0x1E, 0x93, 0x83}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0200, 3, 1, 32}, // atdf, avr-gcc 12.2.0, avrdude - {"AT90CAN32", 171, F_AVR8, {0x1E, 0x95, 0x81}, 0, 0x08000, 0x100, 4, 0x0400, 0, 0x0400, 8, 0x0100, 0x0800, 3, 1, 37}, // atdf, avr-gcc 12.2.0, avrdude - {"AT90CAN64", 172, F_AVR8, {0x1E, 0x96, 0x81}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 37}, // atdf, avr-gcc 12.2.0, avrdude - {"AT90PWM81", 173, F_AVR8, {0x1E, 0x93, 0x88}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0100, 3, 1, 20}, // atdf, avr-gcc 12.2.0 - {"AT90USB82", 174, F_AVR8, {0x1E, 0x93, 0x82}, 0, 0x02000, 0x080, 4, 0x0200, 0, 0x0200, 4, 0x0100, 0x0200, 3, 1, 29}, // atdf, avr-gcc 12.2.0, avrdude - {"AT90SCR100", 175, F_AVR8, {0x1E, 0x96, 0xC1}, 0, 0x10000, 0x100, 4, 0x0200, -1, -1, -1, 0x0100, 0x1000, 3, 1, 38}, // avr-gcc 12.2.0, boot size (manual) - {"AT90CAN128", 176, F_AVR8, {0x1E, 0x97, 0x81}, 0, 0x20000, 0x100, 4, 0x0400, 0, 0x1000, 8, 0x0100, 0x1000, 3, 1, 37}, // atdf, avr-gcc 12.2.0, avrdude - {"AT90PWM161", 177, F_AVR8, {0x1E, 0x94, 0x8B}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 20}, // atdf, avr-gcc 12.2.0 - {"AT90USB162", 178, F_AVR8, {0x1E, 0x94, 0x82}, 0, 0x04000, 0x080, 4, 0x0200, 0, 0x0200, 4, 0x0100, 0x0200, 3, 1, 29}, // atdf, avr-gcc 12.2.0, avrdude - {"AT90PWM216", 179, F_AVR8, {0x1E, 0x94, 0x83}, 0, 0x04000, 0x080, 4, 0x0200, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 32}, // atdf, avr-gcc 12.2.0, avrdude - {"AT90PWM316", 180, F_AVR8, {0x1E, 0x94, 0x83}, 0, 0x04000, 0x080, 4, 0x0200, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 32}, // atdf, avr-gcc 12.2.0, avrdude - {"AT90USB646", 181, F_AVR8, {0x1E, 0x96, 0x82}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 38}, // atdf, avr-gcc 12.2.0, avrdude - {"AT90USB647", 182, F_AVR8, {0x1E, 0x96, 0x82}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 38}, // atdf, avr-gcc 12.2.0, avrdude - {"AT90S1200", 183, F_AVR8, {0x1E, 0x90, 0x01}, 0, 0x00400, 0x001, 0, 0, 0, 0x0040, 1, 0x0060, 0x0020, 1, 1, 4}, // avr-gcc 12.2.0, avrdude, boot size (manual) - {"AT90USB1286", 184, F_AVR8, {0x1E, 0x97, 0x82}, 0, 0x20000, 0x100, 4, 0x0400, 0, 0x1000, 8, 0x0100, 0x2000, 3, 1, 38}, // atdf, avr-gcc 12.2.0, avrdude - {"AT90USB1287", 185, F_AVR8, {0x1E, 0x97, 0x82}, 0, 0x20000, 0x100, 4, 0x0400, 0, 0x1000, 8, 0x0100, 0x2000, 3, 1, 38}, // atdf, avr-gcc 12.2.0, avrdude - {"AT90S2313", 186, F_AVR8, {0x1E, 0x91, 0x01}, 0, 0x00800, 0x001, 0, 0, 0, 0x0080, 1, 0x0060, 0x0080, 1, 1, 11}, // avr-gcc 12.2.0, avrdude, boot size (manual) - {"AT90S2323", 187, F_AVR8, {0x1E, 0x91, 0x02}, 0, 0x00800, -1, 0, 0, -1, -1, -1, 0x0060, 0x0080, 1, 1, 3}, // avr-gcc 12.2.0, boot size (manual) - {"AT90S2333", 188, F_AVR8, {0x1E, 0x91, 0x05}, 0, 0x00800, 0x001, 0, 0, 0, 0x0080, 1, 0x0060, 0x0080, -1, -1, 14}, // avr-gcc 12.2.0, avrdude, boot size (manual) - {"AT90S2343", 189, F_AVR8, {0x1E, 0x91, 0x03}, 0, 0x00800, 0x001, 0, 0, 0, 0x0080, 1, 0x0060, 0x0080, 1, 1, 3}, // avr-gcc 12.2.0, avrdude, boot size (manual) - {"AT90S4414", 190, F_AVR8, {0x1E, 0x92, 0x01}, 0, 0x01000, 0x001, 0, 0, 0, 0x0100, 1, 0x0060, 0x0100, 1, 1, 13}, // avr-gcc 12.2.0, avrdude, boot size (manual) - {"AT90S4433", 191, F_AVR8, {0x1E, 0x92, 0x03}, 0, 0x01000, 0x001, 0, 0, 0, 0x0100, 1, 0x0060, 0x0080, 1, 1, 14}, // avr-gcc 12.2.0, avrdude, boot size (manual) - {"AT90S4434", 192, F_AVR8, {0x1E, 0x92, 0x02}, 0, 0x01000, 0x001, 0, 0, 0, 0x0100, 1, 0x0060, 0x0100, 1, 1, 17}, // avr-gcc 12.2.0, avrdude, boot size (manual) - {"AT90S8515", 193, F_AVR8, {0x1E, 0x93, 0x01}, 0, 0x02000, 0x001, 0, 0, 0, 0x0200, 1, 0x0060, 0x0200, 1, 1, 13}, // avr-gcc 12.2.0, avrdude, boot size (manual) - {"AT90C8534", 194, F_AVR8, {0xff, -1, -1}, 0, 0x02000, -1, -1, -1, -1, -1, -1, 0x0060, 0x0100, -1, -1, 0}, // avr-gcc 12.2.0 - {"AT90S8535", 195, F_AVR8, {0x1E, 0x93, 0x03}, 0, 0x02000, 0x001, 0, 0, 0, 0x0200, 1, 0x0060, 0x0200, 1, 1, 17}, // avr-gcc 12.2.0, avrdude, boot size (manual) - {"AT94K", 196, F_AVR8, {0xff, -1, -1}, 0, 0x08000, -1, -1, -1, -1, -1, -1, 0x0060, 0x0fa0, -1, -1, 0}, // avr-gcc 12.2.0 - {"ATA5272", 197, F_AVR8, {0x1E, 0x93, 0x87}, 0, 0x02000, 0x080, 0, 0, 0, 0x0200, 4, 0x0100, 0x0200, 3, 1, 37}, // atdf, avr-gcc 12.2.0 - {"ATA5505", 198, F_AVR8, {0x1E, 0x94, 0x87}, 0, 0x04000, 0x080, 0, 0, 0, 0x0200, 4, 0x0100, 0x0200, 3, 1, 20}, // atdf, avr-gcc 12.2.0 - {"ATA5700M322", 199, F_AVR8, {0x1E, 0x95, 0x67}, 0x08000, 0x08000, 0x040, 0, 0, 0, 0x0880, 16, 0x0200, 0x0400, 1, 1, 51}, // atdf - {"ATA5702M322", 200, F_AVR8, {0x1E, 0x95, 0x69}, 0x08000, 0x08000, 0x040, 0, 0, 0, 0x0880, 16, 0x0200, 0x0400, 1, 1, 51}, // atdf, avr-gcc 12.2.0 - {"ATA5781", 201, F_AVR8, {0x1E, 0x95, 0x64}, -1, -1, -1, 0, 0, 0, 0x0400, 16, 0x0200, 0x0400, 1, 1, 42}, // atdf - {"ATA5782", 202, F_AVR8, {0x1E, 0x95, 0x65}, 0x08000, 0x05000, 0x040, 1, 0x5000, 0, 0x0400, 16, 0x0200, 0x0400, 1, 1, 42}, // atdf, avr-gcc 12.2.0 - {"ATA5783", 203, F_AVR8, {0x1E, 0x95, 0x66}, -1, -1, -1, 0, 0, 0, 0x0400, 16, 0x0200, 0x0400, 1, 1, 42}, // atdf - {"ATA5787", 204, F_AVR8, {0x1E, 0x94, 0x6C}, 0x08000, 0x05200, 0x040, 0, 0, 0, 0x0400, 16, 0x0200, 0x0800, 1, 1, 44}, // atdf - {"ATA5790", 205, F_AVR8, {0x1E, 0x94, 0x61}, 0, 0x04000, 0x080, 1, 0x0800, 0, 0x0800, 16, 0x0100, 0x0200, 1, 1, 30}, // atdf, avr-gcc 12.2.0 - {"ATA5790N", 206, F_AVR8, {0x1E, 0x94, 0x62}, 0, 0x04000, 0x080, 1, 0x0800, 0, 0x0800, 16, 0x0100, 0x0200, 1, 1, 31}, // atdf, avr-gcc 12.2.0 - {"ATA5791", 207, F_AVR8, {0x1E, 0x94, 0x62}, 0, 0x04000, 0x080, 1, 0x0800, 0, 0x0800, 16, 0x0100, 0x0200, 1, 1, 31}, // atdf, avr-gcc 7.3.0 - {"ATA5795", 208, F_AVR8, {0x1E, 0x93, 0x61}, 0, 0x02000, 0x040, 1, 0x0800, 0, 0x0800, 16, 0x0100, 0x0200, 1, 1, 23}, // atdf, avr-gcc 12.2.0 - {"ATA5831", 209, F_AVR8, {0x1E, 0x95, 0x61}, 0x08000, 0x05000, 0x040, 1, 0x5000, 0, 0x0400, 16, 0x0200, 0x0400, 1, 1, 42}, // atdf, avr-gcc 12.2.0 - {"ATA5832", 210, F_AVR8, {0x1E, 0x95, 0x62}, -1, -1, -1, 0, 0, 0, 0x0400, 16, 0x0200, 0x0400, 1, 1, 42}, // atdf - {"ATA5833", 211, F_AVR8, {0x1E, 0x95, 0x63}, -1, -1, -1, 0, 0, 0, 0x0400, 16, 0x0200, 0x0400, 1, 1, 42}, // atdf - {"ATA5835", 212, F_AVR8, {0x1E, 0x94, 0x6B}, 0x08000, 0x05200, 0x040, 0, 0, 0, 0x0400, 16, 0x0200, 0x0800, 1, 1, 44}, // atdf - {"ATA6285", 213, F_AVR8, {0x1E, 0x93, 0x82}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0140, 4, 0x0100, 0x0200, 2, 1, 27}, // atdf, avr-gcc 12.2.0 - {"ATA6286", 214, F_AVR8, {0x1E, 0x93, 0x82}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0140, 4, 0x0100, 0x0200, 2, 1, 27}, // atdf, avr-gcc 12.2.0 - {"ATA6289", 215, F_AVR8, {0x1E, 0x93, 0x82}, 0, 0x02000, 0x040, 4, 0x0100, -1, -1, -1, 0x0100, 0x0200, 2, 1, 27}, // avr-gcc 12.2.0, boot size (manual) - {"ATA6612C", 216, F_AVR8, {0x1E, 0x93, 0x0A}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 26}, // atdf, avr-gcc 12.2.0 - {"ATA6613C", 217, F_AVR8, {0x1E, 0x94, 0x06}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 26}, // atdf, avr-gcc 12.2.0 - {"ATA6614Q", 218, F_AVR8, {0x1E, 0x95, 0x0F}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 26}, // atdf, avr-gcc 12.2.0 - {"ATA6616C", 219, F_AVR8, {0x1E, 0x93, 0x87}, 0, 0x02000, 0x080, 0, 0, 0, 0x0200, 4, 0x0100, 0x0200, 3, 1, 20}, // atdf, avr-gcc 12.2.0 - {"ATA6617C", 220, F_AVR8, {0x1E, 0x94, 0x87}, 0, 0x04000, 0x080, 0, 0, 0, 0x0200, 4, 0x0100, 0x0200, 3, 1, 20}, // atdf, avr-gcc 12.2.0 - {"ATA8210", 221, F_AVR8, {0x1E, 0x95, 0x65}, 0x08000, 0x05000, 0x040, 1, 0x5000, 0, 0x0400, 16, 0x0200, 0x0400, 1, 1, 42}, // atdf, avr-gcc 7.3.0 - {"ATA8215", 222, F_AVR8, {0x1E, 0x95, 0x64}, -1, -1, -1, 0, 0, 0, 0x0400, 16, 0x0200, 0x0400, 1, 1, 42}, // atdf - {"ATA8510", 223, F_AVR8, {0x1E, 0x95, 0x61}, 0x08000, 0x05000, 0x040, 1, 0x5000, 0, 0x0400, 16, 0x0200, 0x0400, 1, 1, 42}, // atdf, avr-gcc 7.3.0 - {"ATA8515", 224, F_AVR8, {0x1E, 0x95, 0x63}, -1, -1, -1, 0, 0, 0, 0x0400, 16, 0x0200, 0x0400, 1, 1, 42}, // atdf - {"ATA664251", 225, F_AVR8, {0x1E, 0x94, 0x87}, 0, 0x04000, 0x080, 0, 0, 0, 0x0200, 4, 0x0100, 0x0200, 3, 1, 20}, // atdf, avr-gcc 12.2.0 - {"M3000", 226, F_AVR8, {0xff, -1, -1}, 0, 0x10000, -1, -1, -1, -1, -1, -1, 0x1000, 0x1000, -1, -1, 0}, // avr-gcc 12.2.0 - {"LGT8F88P", 227, F_AVR8, {0x1E, 0x93, 0x0F}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 26}, // avrdude, from ATmega88 - {"LGT8F168P", 228, F_AVR8, {0x1E, 0x94, 0x0B}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 26}, // avrdude, from ATmega168P - {"LGT8F328P", 229, F_AVR8, {0x1E, 0x95, 0x0F}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 26}, // avrdude, from ATmega328P - - {"ATxmega8E5", 230, F_XMEGA, {0x1E, 0x93, 0x41}, 0, 0x02800, 0x080, 1, 0x0800, 0, 0x0200, 32, 0x2000, 0x0400, 7, 1, 43}, // atdf, avr-gcc 12.2.0, avrdude - {"ATxmega16A4", 231, F_XMEGA, {0x1E, 0x94, 0x41}, 0, 0x05000, 0x100, 1, 0x1000, 0, 0x0400, 32, 0x2000, 0x0800, 6, 1, 94}, // atdf, avr-gcc 12.2.0, avrdude - {"ATxmega16A4U", 232, F_XMEGA, {0x1E, 0x94, 0x41}, 0, 0x05000, 0x100, 1, 0x1000, 0, 0x0400, 32, 0x2000, 0x0800, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude - {"ATxmega16C4", 233, F_XMEGA, {0x1E, 0x94, 0x43}, 0, 0x05000, 0x100, 1, 0x1000, 0, 0x0400, 32, 0x2000, 0x0800, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude - {"ATxmega16D4", 234, F_XMEGA, {0x1E, 0x94, 0x42}, 0, 0x05000, 0x100, 1, 0x1000, 0, 0x0400, 32, 0x2000, 0x0800, 6, 1, 91}, // atdf, avr-gcc 12.2.0, avrdude - {"ATxmega16E5", 235, F_XMEGA, {0x1E, 0x94, 0x45}, 0, 0x05000, 0x080, 1, 0x1000, 0, 0x0200, 32, 0x2000, 0x0800, 7, 1, 43}, // atdf, avr-gcc 7.3.0, avrdude - {"ATxmega32C3", 236, F_XMEGA, {0x1E, 0x95, 0x49}, 0, 0x09000, 0x100, 1, 0x1000, 0, 0x0400, 32, 0x2000, 0x1000, 6, 1, 127}, // atdf, avr-gcc 12.2.0 - {"ATxmega32D3", 237, F_XMEGA, {0x1E, 0x95, 0x4A}, 0, 0x09000, 0x100, 1, 0x1000, 0, 0x0400, 32, 0x2000, 0x1000, 6, 1, 114}, // atdf, avr-gcc 12.2.0 - {"ATxmega32A4", 238, F_XMEGA, {0x1E, 0x95, 0x41}, 0, 0x09000, 0x100, 1, 0x1000, 0, 0x0400, 32, 0x2000, 0x1000, 6, 1, 94}, // atdf, avr-gcc 12.2.0, avrdude - {"ATxmega32A4U", 239, F_XMEGA, {0x1E, 0x95, 0x41}, 0, 0x09000, 0x100, 1, 0x1000, 0, 0x0400, 32, 0x2000, 0x1000, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude - {"ATxmega32C4", 240, F_XMEGA, {0x1E, 0x95, 0x44}, 0, 0x09000, 0x100, 1, 0x1000, 0, 0x0400, 32, 0x2000, 0x1000, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude - {"ATxmega32D4", 241, F_XMEGA, {0x1E, 0x95, 0x42}, 0, 0x09000, 0x100, 1, 0x1000, 0, 0x0400, 32, 0x2000, 0x1000, 6, 1, 91}, // atdf, avr-gcc 12.2.0, avrdude - {"ATxmega32E5", 242, F_XMEGA, {0x1E, 0x95, 0x4C}, 0, 0x09000, 0x080, 1, 0x1000, 0, 0x0400, 32, 0x2000, 0x1000, 7, 1, 43}, // atdf, avr-gcc 12.2.0, avrdude - {"ATxmega64A1", 243, F_XMEGA, {0x1E, 0x96, 0x4E}, 0, 0x11000, 0x100, 1, 0x1000, 0, 0x0800, 32, 0x2000, 0x1000, 6, 1, 125}, // atdf, avr-gcc 12.2.0, avrdude - {"ATxmega64A1U", 244, F_XMEGA, {0x1E, 0x96, 0x4E}, 0, 0x11000, 0x100, 1, 0x1000, 0, 0x0800, 32, 0x2000, 0x1000, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude - {"ATxmega64B1", 245, F_XMEGA, {0x1E, 0x96, 0x52}, 0, 0x11000, 0x100, 1, 0x1000, 0, 0x0800, 32, 0x2000, 0x1000, 6, 1, 81}, // atdf, avr-gcc 12.2.0, avrdude - {"ATxmega64A3", 246, F_XMEGA, {0x1E, 0x96, 0x42}, 0, 0x11000, 0x100, 1, 0x1000, 0, 0x0800, 32, 0x2000, 0x1000, 6, 1, 122}, // atdf, avr-gcc 12.2.0, avrdude - {"ATxmega64A3U", 247, F_XMEGA, {0x1E, 0x96, 0x42}, 0, 0x11000, 0x100, 1, 0x1000, 0, 0x0800, 32, 0x2000, 0x1000, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude - {"ATxmega64B3", 248, F_XMEGA, {0x1E, 0x96, 0x51}, 0, 0x11000, 0x100, 1, 0x1000, 0, 0x0800, 32, 0x2000, 0x1000, 6, 1, 54}, // atdf, avr-gcc 12.2.0, avrdude - {"ATxmega64C3", 249, F_XMEGA, {0x1E, 0x96, 0x49}, 0, 0x11000, 0x100, 1, 0x1000, 0, 0x0800, 32, 0x2000, 0x1000, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude - {"ATxmega64D3", 250, F_XMEGA, {0x1E, 0x96, 0x4A}, 0, 0x11000, 0x100, 1, 0x1000, 0, 0x0800, 32, 0x2000, 0x1000, 6, 1, 114}, // atdf, avr-gcc 12.2.0, avrdude - {"ATxmega64A4", 251, F_XMEGA, {0x1E, 0x96, 0x46}, 0, 0x11000, 0x100, -1, -1, 0, 0x0800, 32, -1, -1, -1, -1, 0}, // avrdude - {"ATxmega64A4U", 252, F_XMEGA, {0x1E, 0x96, 0x46}, 0, 0x11000, 0x100, 1, 0x1000, 0, 0x0800, 32, 0x2000, 0x1000, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude - {"ATxmega64D4", 253, F_XMEGA, {0x1E, 0x96, 0x47}, 0, 0x11000, 0x100, 1, 0x1000, 0, 0x0800, 32, 0x2000, 0x1000, 6, 1, 91}, // atdf, avr-gcc 12.2.0, avrdude - {"ATxmega128A1", 254, F_XMEGA, {0x1E, 0x97, 0x4C}, 0, 0x22000, 0x200, 1, 0x2000, 0, 0x0800, 32, 0x2000, 0x2000, 6, 1, 125}, // atdf, avr-gcc 12.2.0, avrdude - {"ATxmega128A1revD", 255, F_XMEGA, {0x1E, 0x97, 0x41}, 0, 0x22000, 0x200, -1, -1, 0, 0x0800, 32, -1, -1, -1, -1, 0}, // avrdude - {"ATxmega128A1U", 256, F_XMEGA, {0x1E, 0x97, 0x4C}, 0, 0x22000, 0x200, 1, 0x2000, 0, 0x0800, 32, 0x2000, 0x2000, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude - {"ATxmega128B1", 257, F_XMEGA, {0x1E, 0x97, 0x4D}, 0, 0x22000, 0x100, 1, 0x2000, 0, 0x0800, 32, 0x2000, 0x2000, 6, 1, 81}, // atdf, avr-gcc 12.2.0, avrdude - {"ATxmega128A3", 258, F_XMEGA, {0x1E, 0x97, 0x42}, 0, 0x22000, 0x200, 1, 0x2000, 0, 0x0800, 32, 0x2000, 0x2000, 6, 1, 122}, // atdf, avr-gcc 12.2.0, avrdude - {"ATxmega128A3U", 259, F_XMEGA, {0x1E, 0x97, 0x42}, 0, 0x22000, 0x200, 1, 0x2000, 0, 0x0800, 32, 0x2000, 0x2000, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude - {"ATxmega128B3", 260, F_XMEGA, {0x1E, 0x97, 0x4B}, 0, 0x22000, 0x100, 1, 0x2000, 0, 0x0800, 32, 0x2000, 0x2000, 6, 1, 54}, // atdf, avr-gcc 12.2.0, avrdude - {"ATxmega128C3", 261, F_XMEGA, {0x1E, 0x97, 0x52}, 0, 0x22000, 0x200, 1, 0x2000, 0, 0x0800, 32, 0x2000, 0x2000, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude - {"ATxmega128D3", 262, F_XMEGA, {0x1E, 0x97, 0x48}, 0, 0x22000, 0x200, 1, 0x2000, 0, 0x0800, 32, 0x2000, 0x2000, 6, 1, 114}, // atdf, avr-gcc 12.2.0, avrdude - {"ATxmega128A4", 263, F_XMEGA, {0x1E, 0x97, 0x46}, 0, 0x22000, 0x200, -1, -1, 0, 0x0800, 32, -1, -1, -1, -1, 0}, // avrdude - {"ATxmega128A4U", 264, F_XMEGA, {0x1E, 0x97, 0x46}, 0, 0x22000, 0x100, 1, 0x2000, 0, 0x0800, 32, 0x2000, 0x2000, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude - {"ATxmega128D4", 265, F_XMEGA, {0x1E, 0x97, 0x47}, 0, 0x22000, 0x100, 1, 0x2000, 0, 0x0800, 32, 0x2000, 0x2000, 6, 1, 91}, // atdf, avr-gcc 12.2.0, avrdude - {"ATxmega192A1", 266, F_XMEGA, {0x1E, 0x97, 0x4E}, 0, 0x32000, 0x200, -1, -1, 0, 0x0800, 32, -1, -1, -1, -1, 0}, // avrdude - {"ATxmega192A3", 267, F_XMEGA, {0x1E, 0x97, 0x44}, 0, 0x32000, 0x200, 1, 0x2000, 0, 0x0800, 32, 0x2000, 0x4000, 6, 1, 122}, // atdf, avr-gcc 12.2.0, avrdude - {"ATxmega192A3U", 268, F_XMEGA, {0x1E, 0x97, 0x44}, 0, 0x32000, 0x200, 1, 0x2000, 0, 0x0800, 32, 0x2000, 0x4000, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude - {"ATxmega192C3", 269, F_XMEGA, {0x1E, 0x97, 0x51}, 0, 0x32000, 0x200, 1, 0x2000, 0, 0x0800, 32, 0x2000, 0x4000, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude - {"ATxmega192D3", 270, F_XMEGA, {0x1E, 0x97, 0x49}, 0, 0x32000, 0x200, 1, 0x2000, 0, 0x0800, 32, 0x2000, 0x4000, 6, 1, 114}, // atdf, avr-gcc 12.2.0, avrdude - {"ATxmega256A1", 271, F_XMEGA, {0x1E, 0x98, 0x46}, 0, 0x42000, 0x200, -1, -1, 0, 0x1000, 32, -1, -1, -1, -1, 0}, // avrdude - {"ATxmega256A3", 272, F_XMEGA, {0x1E, 0x98, 0x42}, 0, 0x42000, 0x200, 1, 0x2000, 0, 0x1000, 32, 0x2000, 0x4000, 6, 1, 122}, // atdf, avr-gcc 12.2.0, avrdude - {"ATxmega256A3B", 273, F_XMEGA, {0x1E, 0x98, 0x43}, 0, 0x42000, 0x200, 1, 0x2000, 0, 0x1000, 32, 0x2000, 0x4000, 6, 1, 122}, // atdf, avr-gcc 12.2.0, avrdude - {"ATxmega256A3BU", 274, F_XMEGA, {0x1E, 0x98, 0x43}, 0, 0x42000, 0x200, 1, 0x2000, 0, 0x1000, 32, 0x2000, 0x4000, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude - {"ATxmega256A3U", 275, F_XMEGA, {0x1E, 0x98, 0x42}, 0, 0x42000, 0x200, 1, 0x2000, 0, 0x1000, 32, 0x2000, 0x4000, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude - {"ATxmega256C3", 276, F_XMEGA, {0x1E, 0x98, 0x46}, 0, 0x42000, 0x200, 1, 0x2000, 0, 0x1000, 32, 0x2000, 0x4000, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude - {"ATxmega256D3", 277, F_XMEGA, {0x1E, 0x98, 0x44}, 0, 0x42000, 0x200, 1, 0x2000, 0, 0x1000, 32, 0x2000, 0x4000, 6, 1, 114}, // atdf, avr-gcc 12.2.0, avrdude - {"ATxmega384C3", 278, F_XMEGA, {0x1E, 0x98, 0x45}, 0, 0x62000, 0x200, 1, 0x2000, 0, 0x1000, 32, 0x2000, 0x8000, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude - {"ATxmega384D3", 279, F_XMEGA, {0x1E, 0x98, 0x47}, 0, 0x62000, 0x200, 1, 0x2000, 0, 0x1000, 32, 0x2000, 0x8000, 6, 1, 114}, // atdf, avr-gcc 12.2.0, avrdude - - {"ATtiny202", 280, F_AVR8X, {0x1E, 0x91, 0x23}, 0, 0x00800, 0x040, 1, 0, 0x01400, 0x0040, 32, 0x3f80, 0x0080, 10, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude - {"ATtiny204", 281, F_AVR8X, {0x1E, 0x91, 0x22}, 0, 0x00800, 0x040, 1, 0, 0x01400, 0x0040, 32, 0x3f80, 0x0080, 10, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude - {"ATtiny212", 282, F_AVR8X, {0x1E, 0x91, 0x21}, 0, 0x00800, 0x040, 1, 0, 0x01400, 0x0040, 32, 0x3f80, 0x0080, 10, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude - {"ATtiny214", 283, F_AVR8X, {0x1E, 0x91, 0x20}, 0, 0x00800, 0x040, 1, 0, 0x01400, 0x0040, 32, 0x3f80, 0x0080, 10, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude - {"ATtiny402", 284, F_AVR8X, {0x1E, 0x92, 0x27}, 0, 0x01000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3f00, 0x0100, 10, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude - {"ATtiny404", 285, F_AVR8X, {0x1E, 0x92, 0x26}, 0, 0x01000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3f00, 0x0100, 10, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude - {"ATtiny406", 286, F_AVR8X, {0x1E, 0x92, 0x25}, 0, 0x01000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3f00, 0x0100, 10, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude - {"ATtiny412", 287, F_AVR8X, {0x1E, 0x92, 0x23}, 0, 0x01000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3f00, 0x0100, 10, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude - {"ATtiny414", 288, F_AVR8X, {0x1E, 0x92, 0x22}, 0, 0x01000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3f00, 0x0100, 10, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude - {"ATtiny416", 289, F_AVR8X, {0x1E, 0x92, 0x21}, 0, 0x01000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3f00, 0x0100, 10, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude - {"ATtiny416auto", 290, F_AVR8X, {0x1E, 0x92, 0x28}, 0, 0x01000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3f00, 0x0100, 10, 1, 26}, // atdf - {"ATtiny417", 291, F_AVR8X, {0x1E, 0x92, 0x20}, 0, 0x01000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3f00, 0x0100, 10, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude - {"ATtiny424", 292, F_AVR8X, {0x1E, 0x92, 0x2C}, 0, 0x01000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3e00, 0x0200, 10, 1, 30}, // atdf, avrdude - {"ATtiny426", 293, F_AVR8X, {0x1E, 0x92, 0x2B}, 0, 0x01000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3e00, 0x0200, 10, 1, 30}, // atdf, avrdude - {"ATtiny427", 294, F_AVR8X, {0x1E, 0x92, 0x2A}, 0, 0x01000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3e00, 0x0200, 10, 1, 30}, // atdf, avrdude - {"ATtiny804", 295, F_AVR8X, {0x1E, 0x93, 0x25}, 0, 0x02000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3e00, 0x0200, 10, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude - {"ATtiny806", 296, F_AVR8X, {0x1E, 0x93, 0x24}, 0, 0x02000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3e00, 0x0200, 10, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude - {"ATtiny807", 297, F_AVR8X, {0x1E, 0x93, 0x23}, 0, 0x02000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3e00, 0x0200, 10, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude - {"ATtiny814", 298, F_AVR8X, {0x1E, 0x93, 0x22}, 0, 0x02000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3e00, 0x0200, 10, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude - {"ATtiny816", 299, F_AVR8X, {0x1E, 0x93, 0x21}, 0, 0x02000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3e00, 0x0200, 10, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude - {"ATtiny817", 300, F_AVR8X, {0x1E, 0x93, 0x20}, 0, 0x02000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3e00, 0x0200, 10, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude - {"ATtiny824", 301, F_AVR8X, {0x1E, 0x93, 0x29}, 0, 0x02000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3c00, 0x0400, 10, 1, 30}, // atdf, avrdude - {"ATtiny826", 302, F_AVR8X, {0x1E, 0x93, 0x28}, 0, 0x02000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3c00, 0x0400, 10, 1, 30}, // atdf, avrdude - {"ATtiny827", 303, F_AVR8X, {0x1E, 0x93, 0x27}, 0, 0x02000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3c00, 0x0400, 10, 1, 30}, // atdf, avrdude - {"ATtiny1604", 304, F_AVR8X, {0x1E, 0x94, 0x25}, 0, 0x04000, 0x040, 1, 0, 0x01400, 0x0100, 32, 0x3c00, 0x0400, 10, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude - {"ATtiny1606", 305, F_AVR8X, {0x1E, 0x94, 0x24}, 0, 0x04000, 0x040, 1, 0, 0x01400, 0x0100, 32, 0x3c00, 0x0400, 10, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude - {"ATtiny1607", 306, F_AVR8X, {0x1E, 0x94, 0x23}, 0, 0x04000, 0x040, 1, 0, 0x01400, 0x0100, 32, 0x3c00, 0x0400, 10, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude - {"ATtiny1614", 307, F_AVR8X, {0x1E, 0x94, 0x22}, 0, 0x04000, 0x040, 1, 0, 0x01400, 0x0100, 32, 0x3800, 0x0800, 10, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude - {"ATtiny1616", 308, F_AVR8X, {0x1E, 0x94, 0x21}, 0, 0x04000, 0x040, 1, 0, 0x01400, 0x0100, 32, 0x3800, 0x0800, 10, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude - {"ATtiny1617", 309, F_AVR8X, {0x1E, 0x94, 0x20}, 0, 0x04000, 0x040, 1, 0, 0x01400, 0x0100, 32, 0x3800, 0x0800, 10, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude - {"ATtiny1624", 310, F_AVR8X, {0x1E, 0x94, 0x2A}, 0, 0x04000, 0x040, 1, 0, 0x01400, 0x0100, 32, 0x3800, 0x0800, 10, 1, 30}, // atdf, avrdude - {"ATtiny1626", 311, F_AVR8X, {0x1E, 0x94, 0x29}, 0, 0x04000, 0x040, 1, 0, 0x01400, 0x0100, 32, 0x3800, 0x0800, 10, 1, 30}, // atdf, avrdude - {"ATtiny1627", 312, F_AVR8X, {0x1E, 0x94, 0x28}, 0, 0x04000, 0x040, 1, 0, 0x01400, 0x0100, 32, 0x3800, 0x0800, 10, 1, 30}, // atdf, avrdude - {"ATtiny3214", 313, F_AVR8X, {0x1E, 0x95, 0x20}, 0, 0x08000, 0x080, 1, 0, 0x01400, 0x0100, 64, 0x3800, 0x0800, 10, 1, 31}, // avr-gcc 12.2.0 - {"ATtiny3216", 314, F_AVR8X, {0x1E, 0x95, 0x21}, 0, 0x08000, 0x080, 1, 0, 0x01400, 0x0100, 64, 0x3800, 0x0800, 10, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude - {"ATtiny3217", 315, F_AVR8X, {0x1E, 0x95, 0x22}, 0, 0x08000, 0x080, 1, 0, 0x01400, 0x0100, 64, 0x3800, 0x0800, 10, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude - {"ATtiny3224", 316, F_AVR8X, {0x1E, 0x95, 0x28}, 0, 0x08000, 0x080, 1, 0, 0x01400, 0x0100, 64, 0x3400, 0x0c00, 10, 1, 30}, // atdf, avrdude - {"ATtiny3226", 317, F_AVR8X, {0x1E, 0x95, 0x27}, 0, 0x08000, 0x080, 1, 0, 0x01400, 0x0100, 64, 0x3400, 0x0c00, 10, 1, 30}, // atdf, avrdude - {"ATtiny3227", 318, F_AVR8X, {0x1E, 0x95, 0x26}, 0, 0x08000, 0x080, 1, 0, 0x01400, 0x0100, 64, 0x3400, 0x0c00, 10, 1, 30}, // atdf, avrdude - {"ATmega808", 319, F_AVR8X, {0x1E, 0x93, 0x26}, 0, 0x02000, 0x040, 1, 0, 0x01400, 0x0100, 32, 0x3c00, 0x0400, 10, 1, 36}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega809", 320, F_AVR8X, {0x1E, 0x93, 0x2A}, 0, 0x02000, 0x040, 1, 0, 0x01400, 0x0100, 32, 0x3c00, 0x0400, 10, 1, 40}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega1608", 321, F_AVR8X, {0x1E, 0x94, 0x27}, 0, 0x04000, 0x040, 1, 0, 0x01400, 0x0100, 32, 0x3800, 0x0800, 10, 1, 36}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega1609", 322, F_AVR8X, {0x1E, 0x94, 0x26}, 0, 0x04000, 0x040, 1, 0, 0x01400, 0x0100, 32, 0x3800, 0x0800, 10, 1, 40}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega3208", 323, F_AVR8X, {0x1E, 0x95, 0x30}, 0, 0x08000, 0x080, 1, 0, 0x01400, 0x0100, 64, 0x3000, 0x1000, 10, 1, 36}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega3209", 324, F_AVR8X, {0x1E, 0x95, 0x31}, 0, 0x08000, 0x080, 1, 0, 0x01400, 0x0100, 64, 0x3000, 0x1000, 10, 1, 40}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega4808", 325, F_AVR8X, {0x1E, 0x96, 0x50}, 0, 0x0c000, 0x080, 1, 0, 0x01400, 0x0100, 64, 0x2800, 0x1800, 10, 1, 36}, // atdf, avr-gcc 12.2.0, avrdude - {"ATmega4809", 326, F_AVR8X, {0x1E, 0x96, 0x51}, 0, 0x0c000, 0x080, 1, 0, 0x01400, 0x0100, 64, 0x2800, 0x1800, 10, 1, 40}, // atdf, avr-gcc 12.2.0, avrdude - {"AVR8EA28", 327, F_AVR8X, {0x1E, 0x93, 0x2C}, 0, 0x02000, 0x040, 1, 0, 0x01400, 0x0200, 8, -1, -1, -1, -1, 0}, // avrdude - {"AVR8EA32", 328, F_AVR8X, {0x1E, 0x93, 0x2B}, 0, 0x02000, 0x040, 1, 0, 0x01400, 0x0200, 8, -1, -1, -1, -1, 0}, // avrdude - {"AVR16DD14", 329, F_AVR8X, {0x1E, 0x94, 0x34}, 0, 0x04000, 0x200, 1, 0, 0x01400, 0x0100, 1, 0x7800, 0x0800, 16, 4, 36}, // atdf, avrdude - {"AVR16DD20", 330, F_AVR8X, {0x1E, 0x94, 0x33}, 0, 0x04000, 0x200, 1, 0, 0x01400, 0x0100, 1, 0x7800, 0x0800, 16, 4, 36}, // atdf, avrdude - {"AVR16DD28", 331, F_AVR8X, {0x1E, 0x94, 0x32}, 0, 0x04000, 0x200, 1, 0, 0x01400, 0x0100, 1, 0x7800, 0x0800, 16, 4, 36}, // atdf, avrdude - {"AVR16EA28", 332, F_AVR8X, {0x1E, 0x94, 0x37}, 0, 0x04000, 0x040, 1, 0, 0x01400, 0x0200, 8, -1, -1, -1, -1, 0}, // avrdude - {"AVR16DD32", 333, F_AVR8X, {0x1E, 0x94, 0x31}, 0, 0x04000, 0x200, 1, 0, 0x01400, 0x0100, 1, 0x7800, 0x0800, 16, 4, 36}, // atdf, avrdude - {"AVR16EA32", 334, F_AVR8X, {0x1E, 0x94, 0x36}, 0, 0x04000, 0x040, 1, 0, 0x01400, 0x0200, 8, -1, -1, -1, -1, 0}, // avrdude - {"AVR16EA48", 335, F_AVR8X, {0x1E, 0x94, 0x35}, 0, 0x04000, 0x040, 1, 0, 0x01400, 0x0200, 8, -1, -1, -1, -1, 0}, // avrdude - {"AVR32DD14", 336, F_AVR8X, {0x1E, 0x95, 0x3B}, 0, 0x08000, 0x200, 1, 0, 0x01400, 0x0100, 1, 0x7000, 0x1000, 16, 4, 36}, // atdf, avrdude - {"AVR32DD20", 337, F_AVR8X, {0x1E, 0x95, 0x3A}, 0, 0x08000, 0x200, 1, 0, 0x01400, 0x0100, 1, 0x7000, 0x1000, 16, 4, 36}, // atdf, avrdude - {"AVR32DA28", 338, F_AVR8X, {0x1E, 0x95, 0x34}, 0, 0x08000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x7000, 0x1000, 16, 4, 41}, // atdf, avrdude - {"AVR32DB28", 339, F_AVR8X, {0x1E, 0x95, 0x37}, 0, 0x08000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x7000, 0x1000, 16, 4, 42}, // atdf, avrdude - {"AVR32DD28", 340, F_AVR8X, {0x1E, 0x95, 0x39}, 0, 0x08000, 0x200, 1, 0, 0x01400, 0x0100, 1, 0x7000, 0x1000, 16, 4, 36}, // atdf, avrdude - {"AVR32EA28", 341, F_AVR8X, {0x1E, 0x95, 0x3E}, 0, 0x08000, 0x040, 1, 0, 0x01400, 0x0200, 8, -1, -1, -1, -1, 0}, // avrdude - {"AVR32DA32", 342, F_AVR8X, {0x1E, 0x95, 0x33}, 0, 0x08000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x7000, 0x1000, 16, 4, 44}, // atdf, avrdude - {"AVR32DB32", 343, F_AVR8X, {0x1E, 0x95, 0x36}, 0, 0x08000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x7000, 0x1000, 16, 4, 44}, // atdf, avrdude - {"AVR32DD32", 344, F_AVR8X, {0x1E, 0x95, 0x38}, 0, 0x08000, 0x200, 1, 0, 0x01400, 0x0100, 1, 0x7000, 0x1000, 16, 4, 36}, // atdf, avrdude - {"AVR32EA32", 345, F_AVR8X, {0x1E, 0x95, 0x3D}, 0, 0x08000, 0x040, 1, 0, 0x01400, 0x0200, 8, -1, -1, -1, -1, 0}, // avrdude - {"AVR32DA48", 346, F_AVR8X, {0x1E, 0x95, 0x32}, 0, 0x08000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x7000, 0x1000, 16, 4, 58}, // atdf, avrdude - {"AVR32DB48", 347, F_AVR8X, {0x1E, 0x95, 0x35}, 0, 0x08000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x7000, 0x1000, 16, 4, 61}, // atdf, avrdude - {"AVR32EA48", 348, F_AVR8X, {0x1E, 0x95, 0x3C}, 0, 0x08000, 0x040, 1, 0, 0x01400, 0x0200, 8, -1, -1, -1, -1, 0}, // avrdude - {"AVR64DD14", 349, F_AVR8X, {0x1E, 0x96, 0x1D}, 0, 0x10000, 0x200, 1, 0, 0x01400, 0x0100, 1, 0x6000, 0x2000, 16, 4, 36}, // atdf, avrdude - {"AVR64DD20", 350, F_AVR8X, {0x1E, 0x96, 0x1C}, 0, 0x10000, 0x200, 1, 0, 0x01400, 0x0100, 1, 0x6000, 0x2000, 16, 4, 36}, // atdf, avrdude - {"AVR64DA28", 351, F_AVR8X, {0x1E, 0x96, 0x15}, 0, 0x10000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x6000, 0x2000, 16, 4, 41}, // atdf, avrdude - {"AVR64DB28", 352, F_AVR8X, {0x1E, 0x96, 0x19}, 0, 0x10000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x6000, 0x2000, 16, 4, 42}, // atdf, avrdude - {"AVR64DD28", 353, F_AVR8X, {0x1E, 0x96, 0x1B}, 0, 0x10000, 0x200, 1, 0, 0x01400, 0x0100, 1, 0x6000, 0x2000, 16, 4, 36}, // atdf, avrdude - {"AVR64EA28", 354, F_AVR8X, {0x1E, 0x96, 0x20}, 0, 0x10000, 0x080, 1, 0, 0x01400, 0x0200, 8, 0x6800, 0x1800, 16, 4, 37}, // atdf, avrdude - {"AVR64DA32", 355, F_AVR8X, {0x1E, 0x96, 0x14}, 0, 0x10000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x6000, 0x2000, 16, 4, 44}, // atdf, avrdude - {"AVR64DB32", 356, F_AVR8X, {0x1E, 0x96, 0x18}, 0, 0x10000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x6000, 0x2000, 16, 4, 44}, // atdf, avrdude - {"AVR64DD32", 357, F_AVR8X, {0x1E, 0x96, 0x1A}, 0, 0x10000, 0x200, 1, 0, 0x01400, 0x0100, 1, 0x6000, 0x2000, 16, 4, 36}, // atdf, avrdude - {"AVR64EA32", 358, F_AVR8X, {0x1E, 0x96, 0x1F}, 0, 0x10000, 0x080, 1, 0, 0x01400, 0x0200, 8, 0x6800, 0x1800, 16, 4, 37}, // atdf, avrdude - {"AVR64DA48", 359, F_AVR8X, {0x1E, 0x96, 0x13}, 0, 0x10000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x6000, 0x2000, 16, 4, 58}, // atdf, avrdude - {"AVR64DB48", 360, F_AVR8X, {0x1E, 0x96, 0x17}, 0, 0x10000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x6000, 0x2000, 16, 4, 61}, // atdf, avrdude - {"AVR64EA48", 361, F_AVR8X, {0x1E, 0x96, 0x1E}, 0, 0x10000, 0x080, 1, 0, 0x01400, 0x0200, 8, 0x6800, 0x1800, 16, 4, 45}, // atdf, avrdude - {"AVR64DA64", 362, F_AVR8X, {0x1E, 0x96, 0x12}, 0, 0x10000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x6000, 0x2000, 16, 4, 64}, // atdf, avrdude - {"AVR64DB64", 363, F_AVR8X, {0x1E, 0x96, 0x16}, 0, 0x10000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x6000, 0x2000, 16, 4, 65}, // atdf, avrdude - {"AVR128DA28", 364, F_AVR8X, {0x1E, 0x97, 0x0A}, 0, 0x20000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x4000, 0x4000, 16, 4, 41}, // atdf, avrdude - {"AVR128DB28", 365, F_AVR8X, {0x1E, 0x97, 0x0E}, 0, 0x20000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x4000, 0x4000, 16, 4, 42}, // atdf, avrdude - {"AVR128DA32", 366, F_AVR8X, {0x1E, 0x97, 0x09}, 0, 0x20000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x4000, 0x4000, 16, 4, 44}, // atdf, avrdude - {"AVR128DB32", 367, F_AVR8X, {0x1E, 0x97, 0x0D}, 0, 0x20000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x4000, 0x4000, 16, 4, 44}, // atdf, avrdude - {"AVR128DA48", 368, F_AVR8X, {0x1E, 0x97, 0x08}, 0, 0x20000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x4000, 0x4000, 16, 4, 58}, // atdf, avrdude - {"AVR128DB48", 369, F_AVR8X, {0x1E, 0x97, 0x0C}, 0, 0x20000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x4000, 0x4000, 16, 4, 61}, // atdf, avrdude - {"AVR128DA64", 370, F_AVR8X, {0x1E, 0x97, 0x07}, 0, 0x20000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x4000, 0x4000, 16, 4, 64}, // atdf, avrdude - {"AVR128DB64", 371, F_AVR8X, {0x1E, 0x97, 0x0B}, 0, 0x20000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x4000, 0x4000, 16, 4, 65}, // atdf, avrdude -}; - -const size_t avr_isp_chip_arr_size = COUNT_OF(avr_isp_chip_arr); \ No newline at end of file diff --git a/applications/external/avr_isp_programmer/lib/driver/avr_isp_chip_arr.h b/applications/external/avr_isp_programmer/lib/driver/avr_isp_chip_arr.h deleted file mode 100644 index 66f16a7b96a..00000000000 --- a/applications/external/avr_isp_programmer/lib/driver/avr_isp_chip_arr.h +++ /dev/null @@ -1,33 +0,0 @@ -#pragma once - -#include - -#define F_AVR8L 1 // TPI programming, ATtiny(4|5|9|10|20|40|102|104) -#define F_AVR8 2 // ISP programming with SPI, "classic" AVRs -#define F_XMEGA 4 // PDI programming, ATxmega family -#define F_AVR8X 8 // UPDI programming, newer 8-bit MCUs - -struct AvrIspChipArr { // Value of -1 typically means unknown - const char* name; // Name of part - uint16_t mcuid; // ID of MCU in 0..2039 - uint8_t avrarch; // F_AVR8L, F_AVR8, F_XMEGA or F_AVR8X - uint8_t sigs[3]; // Signature bytes - int32_t flashoffset; // Flash offset - int32_t flashsize; // Flash size - int16_t pagesize; // Flash page size - int8_t nboots; // Number of supported boot sectors - int16_t bootsize; // Size of (smallest) boot sector - int32_t eepromoffset; // EEPROM offset - int32_t eepromsize; // EEPROM size - int32_t eeprompagesize; // EEPROM page size - int32_t sramstart; // SRAM offset - int32_t sramsize; // SRAM size - int8_t nfuses; // Number of fuse bytes - int8_t nlocks; // Number of lock bytes - uint8_t ninterrupts; // Number of vectors in interrupt vector table -}; - -typedef struct AvrIspChipArr AvrIspChipArr; - -extern const AvrIspChipArr avr_isp_chip_arr[]; -extern const size_t avr_isp_chip_arr_size; \ No newline at end of file diff --git a/applications/external/avr_isp_programmer/lib/driver/avr_isp_prog.c b/applications/external/avr_isp_programmer/lib/driver/avr_isp_prog.c deleted file mode 100644 index bbb6d47393f..00000000000 --- a/applications/external/avr_isp_programmer/lib/driver/avr_isp_prog.c +++ /dev/null @@ -1,639 +0,0 @@ -#include "avr_isp_prog.h" -#include "avr_isp_prog_cmd.h" - -#include - -#define AVR_ISP_PROG_TX_RX_BUF_SIZE 320 -#define TAG "AvrIspProg" - -struct AvrIspProgSignature { - uint8_t vendor; - uint8_t part_family; - uint8_t part_number; -}; - -typedef struct AvrIspProgSignature AvrIspProgSignature; - -struct AvrIspProgCfgDevice { - uint8_t devicecode; - uint8_t revision; - uint8_t progtype; - uint8_t parmode; - uint8_t polling; - uint8_t selftimed; - uint8_t lockbytes; - uint8_t fusebytes; - uint8_t flashpoll; - uint16_t eeprompoll; - uint16_t pagesize; - uint16_t eepromsize; - uint32_t flashsize; -}; - -typedef struct AvrIspProgCfgDevice AvrIspProgCfgDevice; - -struct AvrIspProg { - AvrIspSpiSw* spi; - AvrIspProgCfgDevice* cfg; - FuriStreamBuffer* stream_rx; - FuriStreamBuffer* stream_tx; - - uint16_t error; - uint16_t addr; - bool pmode; - bool exit; - bool rst_active_high; - uint8_t buff[AVR_ISP_PROG_TX_RX_BUF_SIZE]; - - AvrIspProgCallback callback; - void* context; -}; - -static void avr_isp_prog_end_pmode(AvrIspProg* instance); - -AvrIspProg* avr_isp_prog_init(void) { - AvrIspProg* instance = malloc(sizeof(AvrIspProg)); - instance->cfg = malloc(sizeof(AvrIspProgCfgDevice)); - instance->stream_rx = - furi_stream_buffer_alloc(sizeof(int8_t) * AVR_ISP_PROG_TX_RX_BUF_SIZE, sizeof(int8_t)); - instance->stream_tx = - furi_stream_buffer_alloc(sizeof(int8_t) * AVR_ISP_PROG_TX_RX_BUF_SIZE, sizeof(int8_t)); - instance->rst_active_high = false; - instance->exit = false; - return instance; -} - -void avr_isp_prog_free(AvrIspProg* instance) { - furi_assert(instance); - if(instance->spi) avr_isp_prog_end_pmode(instance); - furi_stream_buffer_free(instance->stream_tx); - furi_stream_buffer_free(instance->stream_rx); - free(instance->cfg); - free(instance); -} - -size_t avr_isp_prog_spaces_rx(AvrIspProg* instance) { - return furi_stream_buffer_spaces_available(instance->stream_rx); -} - -bool avr_isp_prog_rx(AvrIspProg* instance, uint8_t* data, size_t len) { - furi_assert(instance); - furi_assert(data); - furi_assert(len != 0); - size_t ret = furi_stream_buffer_send(instance->stream_rx, data, sizeof(uint8_t) * len, 0); - return ret == sizeof(uint8_t) * len; -} - -size_t avr_isp_prog_tx(AvrIspProg* instance, uint8_t* data, size_t max_len) { - furi_assert(instance); - return furi_stream_buffer_receive(instance->stream_tx, data, sizeof(int8_t) * max_len, 0); -} - -void avr_isp_prog_exit(AvrIspProg* instance) { - furi_assert(instance); - instance->exit = true; -} - -void avr_isp_prog_set_tx_callback(AvrIspProg* instance, AvrIspProgCallback callback, void* context) { - furi_assert(instance); - furi_assert(context); - instance->callback = callback; - instance->context = context; -} - -static void avr_isp_prog_tx_ch(AvrIspProg* instance, uint8_t data) { - furi_assert(instance); - furi_stream_buffer_send(instance->stream_tx, &data, sizeof(uint8_t), FuriWaitForever); -} - -static uint8_t avr_isp_prog_getch(AvrIspProg* instance) { - furi_assert(instance); - uint8_t data[1] = {0}; - while(furi_stream_buffer_receive(instance->stream_rx, &data, sizeof(int8_t), 30) == 0) { - if(instance->exit) break; - }; - return data[0]; -} - -static void avr_isp_prog_fill(AvrIspProg* instance, size_t len) { - furi_assert(instance); - for(size_t x = 0; x < len; x++) { - instance->buff[x] = avr_isp_prog_getch(instance); - } -} - -static void avr_isp_prog_reset_target(AvrIspProg* instance, bool reset) { - furi_assert(instance); - avr_isp_spi_sw_res_set(instance->spi, (reset == instance->rst_active_high) ? true : false); -} - -static uint8_t avr_isp_prog_spi_transaction( - AvrIspProg* instance, - uint8_t cmd, - uint8_t addr_hi, - uint8_t addr_lo, - uint8_t data) { - furi_assert(instance); - - avr_isp_spi_sw_txrx(instance->spi, cmd); - avr_isp_spi_sw_txrx(instance->spi, addr_hi); - avr_isp_spi_sw_txrx(instance->spi, addr_lo); - return avr_isp_spi_sw_txrx(instance->spi, data); -} - -static void avr_isp_prog_empty_reply(AvrIspProg* instance) { - furi_assert(instance); - if(avr_isp_prog_getch(instance) == CRC_EOP) { - avr_isp_prog_tx_ch(instance, STK_INSYNC); - avr_isp_prog_tx_ch(instance, STK_OK); - } else { - instance->error++; - avr_isp_prog_tx_ch(instance, STK_NOSYNC); - } -} - -static void avr_isp_prog_breply(AvrIspProg* instance, uint8_t data) { - furi_assert(instance); - if(avr_isp_prog_getch(instance) == CRC_EOP) { - avr_isp_prog_tx_ch(instance, STK_INSYNC); - avr_isp_prog_tx_ch(instance, data); - avr_isp_prog_tx_ch(instance, STK_OK); - } else { - instance->error++; - avr_isp_prog_tx_ch(instance, STK_NOSYNC); - } -} - -static void avr_isp_prog_get_version(AvrIspProg* instance, uint8_t data) { - furi_assert(instance); - switch(data) { - case STK_HW_VER: - avr_isp_prog_breply(instance, AVR_ISP_HWVER); - break; - case STK_SW_MAJOR: - avr_isp_prog_breply(instance, AVR_ISP_SWMAJ); - break; - case STK_SW_MINOR: - avr_isp_prog_breply(instance, AVR_ISP_SWMIN); - break; - case AVP_ISP_CONNECT_TYPE: - avr_isp_prog_breply(instance, AVP_ISP_SERIAL_CONNECT_TYPE); - break; - default: - avr_isp_prog_breply(instance, AVR_ISP_RESP_0); - } -} - -static void avr_isp_prog_set_cfg(AvrIspProg* instance) { - furi_assert(instance); - // call this after reading cfg packet into buff[] - instance->cfg->devicecode = instance->buff[0]; - instance->cfg->revision = instance->buff[1]; - instance->cfg->progtype = instance->buff[2]; - instance->cfg->parmode = instance->buff[3]; - instance->cfg->polling = instance->buff[4]; - instance->cfg->selftimed = instance->buff[5]; - instance->cfg->lockbytes = instance->buff[6]; - instance->cfg->fusebytes = instance->buff[7]; - instance->cfg->flashpoll = instance->buff[8]; - // ignore (instance->buff[9] == instance->buff[8]) //FLASH polling value. Same as flashpoll - instance->cfg->eeprompoll = instance->buff[10] << 8 | instance->buff[11]; - instance->cfg->pagesize = instance->buff[12] << 8 | instance->buff[13]; - instance->cfg->eepromsize = instance->buff[14] << 8 | instance->buff[15]; - instance->cfg->flashsize = instance->buff[16] << 24 | instance->buff[17] << 16 | - instance->buff[18] << 8 | instance->buff[19]; - - // avr devices have active low reset, at89sx are active high - instance->rst_active_high = (instance->cfg->devicecode >= 0xe0); -} -static bool - avr_isp_prog_set_pmode(AvrIspProg* instance, uint8_t a, uint8_t b, uint8_t c, uint8_t d) { - furi_assert(instance); - uint8_t res = 0; - avr_isp_spi_sw_txrx(instance->spi, a); - avr_isp_spi_sw_txrx(instance->spi, b); - res = avr_isp_spi_sw_txrx(instance->spi, c); - avr_isp_spi_sw_txrx(instance->spi, d); - return res == 0x53; -} - -static void avr_isp_prog_end_pmode(AvrIspProg* instance) { - furi_assert(instance); - if(instance->pmode) { - avr_isp_prog_reset_target(instance, false); - // We're about to take the target out of reset - // so configure SPI pins as input - - if(instance->spi) avr_isp_spi_sw_free(instance->spi); - instance->spi = NULL; - } - - instance->pmode = false; -} - -static bool avr_isp_prog_start_pmode(AvrIspProg* instance, AvrIspSpiSwSpeed spi_speed) { - furi_assert(instance); - // Reset target before driving PIN_SCK or PIN_MOSI - - // SPI.begin() will configure SS as output, - // so SPI master mode is selected. - // We have defined RESET as pin 10, - // which for many arduino's is not the SS pin. - // So we have to configure RESET as output here, - // (reset_target() first sets the correct level) - if(instance->spi) avr_isp_spi_sw_free(instance->spi); - instance->spi = avr_isp_spi_sw_init(spi_speed); - - avr_isp_prog_reset_target(instance, true); - // See avr datasheets, chapter "SERIAL_PRG Programming Algorithm": - - // Pulse RESET after PIN_SCK is low: - avr_isp_spi_sw_sck_set(instance->spi, false); - - // discharge PIN_SCK, value arbitrally chosen - furi_delay_ms(20); - avr_isp_prog_reset_target(instance, false); - - // Pulse must be minimum 2 target CPU speed cycles - // so 100 usec is ok for CPU speeds above 20KHz - furi_delay_ms(1); - - avr_isp_prog_reset_target(instance, true); - - // Send the enable programming command: - // datasheet: must be > 20 msec - furi_delay_ms(50); - if(avr_isp_prog_set_pmode(instance, AVR_ISP_SET_PMODE)) { - instance->pmode = true; - return true; - } - return false; -} - -static AvrIspProgSignature avr_isp_prog_check_signature(AvrIspProg* instance) { - furi_assert(instance); - AvrIspProgSignature signature; - signature.vendor = avr_isp_prog_spi_transaction(instance, AVR_ISP_READ_VENDOR); - signature.part_family = avr_isp_prog_spi_transaction(instance, AVR_ISP_READ_PART_FAMILY); - signature.part_number = avr_isp_prog_spi_transaction(instance, AVR_ISP_READ_PART_NUMBER); - return signature; -} - -static bool avr_isp_prog_auto_set_spi_speed_start_pmode(AvrIspProg* instance) { - AvrIspSpiSwSpeed spi_speed[] = { - AvrIspSpiSwSpeed1Mhz, - AvrIspSpiSwSpeed400Khz, - AvrIspSpiSwSpeed250Khz, - AvrIspSpiSwSpeed125Khz, - AvrIspSpiSwSpeed60Khz, - AvrIspSpiSwSpeed40Khz, - AvrIspSpiSwSpeed20Khz, - AvrIspSpiSwSpeed10Khz, - AvrIspSpiSwSpeed5Khz, - AvrIspSpiSwSpeed1Khz, - }; - for(uint8_t i = 0; i < COUNT_OF(spi_speed); i++) { - if(avr_isp_prog_start_pmode(instance, spi_speed[i])) { - AvrIspProgSignature sig = avr_isp_prog_check_signature(instance); - AvrIspProgSignature sig_examination = avr_isp_prog_check_signature(instance); //-V656 - uint8_t y = 0; - while(y < 8) { - if(memcmp( - (uint8_t*)&sig, (uint8_t*)&sig_examination, sizeof(AvrIspProgSignature)) != - 0) - break; - sig_examination = avr_isp_prog_check_signature(instance); - y++; - } - if(y == 8) { - if(spi_speed[i] > AvrIspSpiSwSpeed1Mhz) { - if(i < (COUNT_OF(spi_speed) - 1)) { - avr_isp_prog_end_pmode(instance); - i++; - return avr_isp_prog_start_pmode(instance, spi_speed[i]); - } - } - return true; - } - } - } - - if(instance->spi) { - avr_isp_spi_sw_free(instance->spi); - instance->spi = NULL; - } - - return false; -} - -static void avr_isp_prog_universal(AvrIspProg* instance) { - furi_assert(instance); - uint8_t data; - - avr_isp_prog_fill(instance, 4); - data = avr_isp_prog_spi_transaction( - instance, instance->buff[0], instance->buff[1], instance->buff[2], instance->buff[3]); - avr_isp_prog_breply(instance, data); -} - -static void avr_isp_prog_commit(AvrIspProg* instance, uint16_t addr, uint8_t data) { - furi_assert(instance); - avr_isp_prog_spi_transaction(instance, AVR_ISP_COMMIT(addr)); - /* polling flash */ - if(data == 0xFF) { - furi_delay_ms(5); - } else { - /* polling flash */ - uint32_t starttime = furi_get_tick(); - while((furi_get_tick() - starttime) < 30) { - if(avr_isp_prog_spi_transaction(instance, AVR_ISP_READ_FLASH_HI(addr)) != 0xFF) { - break; - }; - } - } -} - -static uint16_t avr_isp_prog_current_page(AvrIspProg* instance) { - furi_assert(instance); - uint16_t page = 0; - switch(instance->cfg->pagesize) { - case 32: - page = instance->addr & 0xFFFFFFF0; - break; - case 64: - page = instance->addr & 0xFFFFFFE0; - break; - case 128: - page = instance->addr & 0xFFFFFFC0; - break; - case 256: - page = instance->addr & 0xFFFFFF80; - break; - - default: - page = instance->addr; - break; - } - - return page; -} - -static uint8_t avr_isp_prog_write_flash_pages(AvrIspProg* instance, size_t length) { - furi_assert(instance); - size_t x = 0; - uint16_t page = avr_isp_prog_current_page(instance); - while(x < length) { - if(page != avr_isp_prog_current_page(instance)) { - --x; - avr_isp_prog_commit(instance, page, instance->buff[x++]); - page = avr_isp_prog_current_page(instance); - } - avr_isp_prog_spi_transaction( - instance, AVR_ISP_WRITE_FLASH_LO(instance->addr, instance->buff[x++])); - - avr_isp_prog_spi_transaction( - instance, AVR_ISP_WRITE_FLASH_HI(instance->addr, instance->buff[x++])); - instance->addr++; - } - - avr_isp_prog_commit(instance, page, instance->buff[--x]); - return STK_OK; -} - -static void avr_isp_prog_write_flash(AvrIspProg* instance, size_t length) { - furi_assert(instance); - avr_isp_prog_fill(instance, length); - if(avr_isp_prog_getch(instance) == CRC_EOP) { - avr_isp_prog_tx_ch(instance, STK_INSYNC); - avr_isp_prog_tx_ch(instance, avr_isp_prog_write_flash_pages(instance, length)); - } else { - instance->error++; - avr_isp_prog_tx_ch(instance, STK_NOSYNC); - } -} - -// write (length) bytes, (start) is a byte address -static uint8_t - avr_isp_prog_write_eeprom_chunk(AvrIspProg* instance, uint16_t start, uint16_t length) { - furi_assert(instance); - // this writes byte-by-byte, - // page writing may be faster (4 bytes at a time) - avr_isp_prog_fill(instance, length); - for(uint16_t x = 0; x < length; x++) { - uint16_t addr = start + x; - avr_isp_prog_spi_transaction(instance, AVR_ISP_WRITE_EEPROM(addr, instance->buff[x])); - furi_delay_ms(10); - } - return STK_OK; -} - -static uint8_t avr_isp_prog_write_eeprom(AvrIspProg* instance, size_t length) { - furi_assert(instance); - // here is a word address, get the byte address - uint16_t start = instance->addr * 2; - uint16_t remaining = length; - if(length > instance->cfg->eepromsize) { - instance->error++; - return STK_FAILED; - } - while(remaining > AVR_ISP_EECHUNK) { - avr_isp_prog_write_eeprom_chunk(instance, start, AVR_ISP_EECHUNK); - start += AVR_ISP_EECHUNK; - remaining -= AVR_ISP_EECHUNK; - } - avr_isp_prog_write_eeprom_chunk(instance, start, remaining); - return STK_OK; -} - -static void avr_isp_prog_program_page(AvrIspProg* instance) { - furi_assert(instance); - uint8_t result = STK_FAILED; - uint16_t length = avr_isp_prog_getch(instance) << 8 | avr_isp_prog_getch(instance); - uint8_t memtype = avr_isp_prog_getch(instance); - // flash memory @addr, (length) bytes - if(memtype == STK_SET_FLASH_TYPE) { - avr_isp_prog_write_flash(instance, length); - return; - } - if(memtype == STK_SET_EEPROM_TYPE) { - result = avr_isp_prog_write_eeprom(instance, length); - if(avr_isp_prog_getch(instance) == CRC_EOP) { - avr_isp_prog_tx_ch(instance, STK_INSYNC); - avr_isp_prog_tx_ch(instance, result); - - } else { - instance->error++; - avr_isp_prog_tx_ch(instance, STK_NOSYNC); - } - return; - } - avr_isp_prog_tx_ch(instance, STK_FAILED); - return; -} - -static uint8_t avr_isp_prog_flash_read_page(AvrIspProg* instance, uint16_t length) { - furi_assert(instance); - for(uint16_t x = 0; x < length; x += 2) { - avr_isp_prog_tx_ch( - instance, - avr_isp_prog_spi_transaction(instance, AVR_ISP_READ_FLASH_LO(instance->addr))); - avr_isp_prog_tx_ch( - instance, - avr_isp_prog_spi_transaction(instance, AVR_ISP_READ_FLASH_HI(instance->addr))); - instance->addr++; - } - return STK_OK; -} - -static uint8_t avr_isp_prog_eeprom_read_page(AvrIspProg* instance, uint16_t length) { - furi_assert(instance); - // here again we have a word address - uint16_t start = instance->addr * 2; - for(uint16_t x = 0; x < length; x++) { - uint16_t addr = start + x; - avr_isp_prog_tx_ch( - instance, avr_isp_prog_spi_transaction(instance, AVR_ISP_READ_EEPROM(addr))); - } - return STK_OK; -} - -static void avr_isp_prog_read_page(AvrIspProg* instance) { - furi_assert(instance); - uint8_t result = STK_FAILED; - uint16_t length = avr_isp_prog_getch(instance) << 8 | avr_isp_prog_getch(instance); - uint8_t memtype = avr_isp_prog_getch(instance); - if(avr_isp_prog_getch(instance) != CRC_EOP) { - instance->error++; - avr_isp_prog_tx_ch(instance, STK_NOSYNC); - return; - } - avr_isp_prog_tx_ch(instance, STK_INSYNC); - if(memtype == STK_SET_FLASH_TYPE) result = avr_isp_prog_flash_read_page(instance, length); - if(memtype == STK_SET_EEPROM_TYPE) result = avr_isp_prog_eeprom_read_page(instance, length); - avr_isp_prog_tx_ch(instance, result); -} - -static void avr_isp_prog_read_signature(AvrIspProg* instance) { - furi_assert(instance); - if(avr_isp_prog_getch(instance) != CRC_EOP) { - instance->error++; - avr_isp_prog_tx_ch(instance, STK_NOSYNC); - return; - } - avr_isp_prog_tx_ch(instance, STK_INSYNC); - - avr_isp_prog_tx_ch(instance, avr_isp_prog_spi_transaction(instance, AVR_ISP_READ_VENDOR)); - avr_isp_prog_tx_ch(instance, avr_isp_prog_spi_transaction(instance, AVR_ISP_READ_PART_FAMILY)); - avr_isp_prog_tx_ch(instance, avr_isp_prog_spi_transaction(instance, AVR_ISP_READ_PART_NUMBER)); - - avr_isp_prog_tx_ch(instance, STK_OK); -} - -void avr_isp_prog_avrisp(AvrIspProg* instance) { - furi_assert(instance); - uint8_t ch = avr_isp_prog_getch(instance); - - switch(ch) { - case STK_GET_SYNC: - FURI_LOG_D(TAG, "cmd STK_GET_SYNC"); - instance->error = 0; - avr_isp_prog_empty_reply(instance); - break; - case STK_GET_SIGN_ON: - FURI_LOG_D(TAG, "cmd STK_GET_SIGN_ON"); - if(avr_isp_prog_getch(instance) == CRC_EOP) { - avr_isp_prog_tx_ch(instance, STK_INSYNC); - - avr_isp_prog_tx_ch(instance, 'A'); - avr_isp_prog_tx_ch(instance, 'V'); - avr_isp_prog_tx_ch(instance, 'R'); - avr_isp_prog_tx_ch(instance, ' '); - avr_isp_prog_tx_ch(instance, 'I'); - avr_isp_prog_tx_ch(instance, 'S'); - avr_isp_prog_tx_ch(instance, 'P'); - - avr_isp_prog_tx_ch(instance, STK_OK); - } else { - instance->error++; - avr_isp_prog_tx_ch(instance, STK_NOSYNC); - } - break; - case STK_GET_PARAMETER: - FURI_LOG_D(TAG, "cmd STK_GET_PARAMETER"); - avr_isp_prog_get_version(instance, avr_isp_prog_getch(instance)); - break; - case STK_SET_DEVICE: - FURI_LOG_D(TAG, "cmd STK_SET_DEVICE"); - avr_isp_prog_fill(instance, 20); - avr_isp_prog_set_cfg(instance); - avr_isp_prog_empty_reply(instance); - break; - case STK_SET_DEVICE_EXT: // ignore for now - FURI_LOG_D(TAG, "cmd STK_SET_DEVICE_EXT"); - avr_isp_prog_fill(instance, 5); - avr_isp_prog_empty_reply(instance); - break; - case STK_ENTER_PROGMODE: - FURI_LOG_D(TAG, "cmd STK_ENTER_PROGMODE"); - if(!instance->pmode) avr_isp_prog_auto_set_spi_speed_start_pmode(instance); - avr_isp_prog_empty_reply(instance); - break; - case STK_LOAD_ADDRESS: - FURI_LOG_D(TAG, "cmd STK_LOAD_ADDRESS"); - instance->addr = avr_isp_prog_getch(instance) | avr_isp_prog_getch(instance) << 8; - avr_isp_prog_empty_reply(instance); - break; - case STK_PROG_FLASH: // ignore for now - FURI_LOG_D(TAG, "cmd STK_PROG_FLASH"); - avr_isp_prog_getch(instance); - avr_isp_prog_getch(instance); - avr_isp_prog_empty_reply(instance); - break; - case STK_PROG_DATA: // ignore for now - FURI_LOG_D(TAG, "cmd STK_PROG_DATA"); - avr_isp_prog_getch(instance); - avr_isp_prog_empty_reply(instance); - break; - case STK_PROG_PAGE: - FURI_LOG_D(TAG, "cmd STK_PROG_PAGE"); - avr_isp_prog_program_page(instance); - break; - case STK_READ_PAGE: - FURI_LOG_D(TAG, "cmd STK_READ_PAGE"); - avr_isp_prog_read_page(instance); - break; - case STK_UNIVERSAL: - FURI_LOG_D(TAG, "cmd STK_UNIVERSAL"); - avr_isp_prog_universal(instance); - break; - case STK_LEAVE_PROGMODE: - FURI_LOG_D(TAG, "cmd STK_LEAVE_PROGMODE"); - instance->error = 0; - if(instance->pmode) avr_isp_prog_end_pmode(instance); - avr_isp_prog_empty_reply(instance); - break; - case STK_READ_SIGN: - FURI_LOG_D(TAG, "cmd STK_READ_SIGN"); - avr_isp_prog_read_signature(instance); - break; - // expecting a command, not CRC_EOP - // this is how we can get back in sync - case CRC_EOP: - FURI_LOG_D(TAG, "cmd CRC_EOP"); - instance->error++; - avr_isp_prog_tx_ch(instance, STK_NOSYNC); - break; - // anything else we will return STK_UNKNOWN - default: - FURI_LOG_D(TAG, "cmd STK_ERROR_CMD"); - instance->error++; - if(avr_isp_prog_getch(instance) == CRC_EOP) - avr_isp_prog_tx_ch(instance, STK_UNKNOWN); - else - avr_isp_prog_tx_ch(instance, STK_NOSYNC); - } - - if(instance->callback) { - instance->callback(instance->context); - } -} diff --git a/applications/external/avr_isp_programmer/lib/driver/avr_isp_prog.h b/applications/external/avr_isp_programmer/lib/driver/avr_isp_prog.h deleted file mode 100644 index 2c15ab06636..00000000000 --- a/applications/external/avr_isp_programmer/lib/driver/avr_isp_prog.h +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once - -#include "avr_isp_spi_sw.h" -#include - -typedef struct AvrIspProg AvrIspProg; -typedef void (*AvrIspProgCallback)(void* context); - -AvrIspProg* avr_isp_prog_init(void); -void avr_isp_prog_free(AvrIspProg* instance); -size_t avr_isp_prog_spaces_rx(AvrIspProg* instance) ; -bool avr_isp_prog_rx(AvrIspProg* instance, uint8_t* data, size_t len); -size_t avr_isp_prog_tx(AvrIspProg* instance, uint8_t* data, size_t max_len); -void avr_isp_prog_avrisp(AvrIspProg* instance); -void avr_isp_prog_exit(AvrIspProg* instance); -void avr_isp_prog_set_tx_callback(AvrIspProg* instance, AvrIspProgCallback callback, void* context); diff --git a/applications/external/avr_isp_programmer/lib/driver/avr_isp_prog_cmd.h b/applications/external/avr_isp_programmer/lib/driver/avr_isp_prog_cmd.h deleted file mode 100644 index f8b07203e66..00000000000 --- a/applications/external/avr_isp_programmer/lib/driver/avr_isp_prog_cmd.h +++ /dev/null @@ -1,97 +0,0 @@ -#pragma once - -// http://ww1.microchip.com/downloads/en/appnotes/atmel-0943-in-system-programming_applicationnote_avr910.pdf -// AVR ISP Definitions -#define AVR_ISP_HWVER 0X02 -#define AVR_ISP_SWMAJ 0X01 -#define AVR_ISP_SWMIN 0X12 -#define AVP_ISP_SERIAL_CONNECT_TYPE 0X53 -#define AVP_ISP_CONNECT_TYPE 0x93 -#define AVR_ISP_RESP_0 0X00 - -#define AVR_ISP_SET_PMODE 0xAC, 0x53, 0x00, 0x00 -#define AVR_ISP_READ_VENDOR 0x30, 0x00, 0x00, 0x00 -#define AVR_ISP_READ_PART_FAMILY 0x30, 0x00, 0x01, 0x00 -#define AVR_ISP_READ_PART_NUMBER 0x30, 0x00, 0x02, 0x00 -#define AVR_ISP_ERASE_CHIP \ - 0xAC, 0x80, 0x00, 0x00 //Erase Chip, Wait N ms, Release RESET to end the erase. -//The only way to end a Chip Erase cycle is by temporarily releasing the Reset line - -#define AVR_ISP_EXTENDED_ADDR(data) 0x4D, 0x00, data, 0x00 -#define AVR_ISP_WRITE_FLASH_LO(add, data) 0x40, (add >> 8) & 0xFF, add & 0xFF, data -#define AVR_ISP_WRITE_FLASH_HI(add, data) 0x48, (add >> 8) & 0xFF, add & 0xFF, data -#define AVR_ISP_READ_FLASH_LO(add) 0x20, (add >> 8) & 0xFF, add & 0xFF, 0x00 -#define AVR_ISP_READ_FLASH_HI(add) 0x28, (add >> 8) & 0xFF, add & 0xFF, 0x00 - -#define AVR_ISP_WRITE_EEPROM(add, data) \ - 0xC0, (add >> 8) & 0xFF, add & 0xFF, data //Send cmd, Wait N ms -#define AVR_ISP_READ_EEPROM(add) 0xA0, (add >> 8) & 0xFF, add & 0xFF, 0xFF - -#define AVR_ISP_COMMIT(add) \ - 0x4C, (add >> 8) & 0xFF, add & 0xFF, 0x00 //Send cmd, polling read last addr page - -#define AVR_ISP_OSCCAL(add) 0x38, 0x00, add, 0x00 - -#define AVR_ISP_WRITE_LOCK_BYTE(data) 0xAC, 0xE0, 0x00, data //Send cmd, Wait N ms -#define AVR_ISP_READ_LOCK_BYTE 0x58, 0x00, 0x00, 0x00 -#define AVR_ISP_WRITE_FUSE_LOW(data) 0xAC, 0xA0, 0x00, data //Send cmd, Wait N ms -#define AVR_ISP_READ_FUSE_LOW 0x50, 0x00, 0x00, 0x00 -#define AVR_ISP_WRITE_FUSE_HIGH(data) 0xAC, 0xA8, 0x00, data //Send cmd, Wait N ms -#define AVR_ISP_READ_FUSE_HIGH 0x58, 0x08, 0x00, 0x00 -#define AVR_ISP_WRITE_FUSE_EXTENDED(data) 0xAC, 0xA4, 0x00, data //Send cmd, Wait N ms (~write) -#define AVR_ISP_READ_FUSE_EXTENDED 0x50, 0x08, 0x00, 0x00 - -#define AVR_ISP_EECHUNK 0x20 - -// https://www.microchip.com/content/dam/mchp/documents/OTH/ApplicationNotes/ApplicationNotes/doc2525.pdf -// STK Definitions -#define STK_OK 0x10 -#define STK_FAILED 0x11 -#define STK_UNKNOWN 0x12 -#define STK_INSYNC 0x14 -#define STK_NOSYNC 0x15 -#define CRC_EOP 0x20 - -#define STK_GET_SYNC 0x30 -#define STK_GET_SIGN_ON 0x31 -#define STK_SET_PARAMETER 0x40 -#define STK_GET_PARAMETER 0x41 -#define STK_SET_DEVICE 0x42 -#define STK_SET_DEVICE_EXT 0x45 -#define STK_ENTER_PROGMODE 0x50 -#define STK_LEAVE_PROGMODE 0x51 -#define STK_CHIP_ERASE 0x52 -#define STK_CHECK_AUTOINC 0x53 -#define STK_LOAD_ADDRESS 0x55 -#define STK_UNIVERSAL 0x56 -#define STK_UNIVERSAL_MULTI 0x57 -#define STK_PROG_FLASH 0x60 -#define STK_PROG_DATA 0x61 -#define STK_PROG_FUSE 0x62 -#define STK_PROG_FUSE_EXT 0x65 -#define STK_PROG_LOCK 0x63 -#define STK_PROG_PAGE 0x64 -#define STK_READ_FLASH 0x70 -#define STK_READ_DATA 0x71 -#define STK_READ_FUSE 0x72 -#define STK_READ_LOCK 0x73 -#define STK_READ_PAGE 0x74 -#define STK_READ_SIGN 0x75 -#define STK_READ_OSCCAL 0x76 -#define STK_READ_FUSE_EXT 0x77 -#define STK_READ_OSCCAL_EXT 0x78 -#define STK_HW_VER 0x80 -#define STK_SW_MAJOR 0x81 -#define STK_SW_MINOR 0x82 -#define STK_LEDS 0x83 -#define STK_VTARGET 0x84 -#define STK_VADJUST 0x85 -#define STK_OSC_PSCALE 0x86 -#define STK_OSC_CMATCH 0x87 -#define STK_SCK_DURATION 0x89 -#define STK_BUFSIZEL 0x90 -#define STK_BUFSIZEH 0x91 -#define STK_STK500_TOPCARD_DETECT 0x98 - -#define STK_SET_EEPROM_TYPE 0X45 -#define STK_SET_FLASH_TYPE 0X46 diff --git a/applications/external/avr_isp_programmer/lib/driver/avr_isp_spi_sw.c b/applications/external/avr_isp_programmer/lib/driver/avr_isp_spi_sw.c deleted file mode 100644 index c6d9d54c892..00000000000 --- a/applications/external/avr_isp_programmer/lib/driver/avr_isp_spi_sw.c +++ /dev/null @@ -1,71 +0,0 @@ -#include "avr_isp_spi_sw.h" - -#include - -#define AVR_ISP_SPI_SW_MISO &gpio_ext_pa6 -#define AVR_ISP_SPI_SW_MOSI &gpio_ext_pa7 -#define AVR_ISP_SPI_SW_SCK &gpio_ext_pb3 -#define AVR_ISP_RESET &gpio_ext_pb2 - -struct AvrIspSpiSw { - AvrIspSpiSwSpeed speed_wait_time; - const GpioPin* miso; - const GpioPin* mosi; - const GpioPin* sck; - const GpioPin* res; -}; - -AvrIspSpiSw* avr_isp_spi_sw_init(AvrIspSpiSwSpeed speed) { - AvrIspSpiSw* instance = malloc(sizeof(AvrIspSpiSw)); - instance->speed_wait_time = speed; - instance->miso = AVR_ISP_SPI_SW_MISO; - instance->mosi = AVR_ISP_SPI_SW_MOSI; - instance->sck = AVR_ISP_SPI_SW_SCK; - instance->res = AVR_ISP_RESET; - - furi_hal_gpio_init(instance->miso, GpioModeInput, GpioPullNo, GpioSpeedVeryHigh); - furi_hal_gpio_write(instance->mosi, false); - furi_hal_gpio_init(instance->mosi, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); - furi_hal_gpio_write(instance->sck, false); - furi_hal_gpio_init(instance->sck, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); - furi_hal_gpio_init(instance->res, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); - - return instance; -} - -void avr_isp_spi_sw_free(AvrIspSpiSw* instance) { - furi_assert(instance); - furi_hal_gpio_init(instance->res, GpioModeAnalog, GpioPullNo, GpioSpeedLow); - furi_hal_gpio_init(instance->miso, GpioModeAnalog, GpioPullNo, GpioSpeedLow); - furi_hal_gpio_init(instance->mosi, GpioModeAnalog, GpioPullNo, GpioSpeedLow); - furi_hal_gpio_init(instance->sck, GpioModeAnalog, GpioPullNo, GpioSpeedLow); - free(instance); -} - -uint8_t avr_isp_spi_sw_txrx(AvrIspSpiSw* instance, uint8_t data) { - furi_assert(instance); - for(uint8_t i = 0; i < 8; ++i) { - furi_hal_gpio_write(instance->mosi, (data & 0x80) ? true : false); - - furi_hal_gpio_write(instance->sck, true); - if(instance->speed_wait_time != AvrIspSpiSwSpeed1Mhz) - furi_delay_us(instance->speed_wait_time - 1); - - data = (data << 1) | furi_hal_gpio_read(instance->miso); //-V792 - - furi_hal_gpio_write(instance->sck, false); - if(instance->speed_wait_time != AvrIspSpiSwSpeed1Mhz) - furi_delay_us(instance->speed_wait_time - 1); - } - return data; -} - -void avr_isp_spi_sw_res_set(AvrIspSpiSw* instance, bool state) { - furi_assert(instance); - furi_hal_gpio_write(instance->res, state); -} - -void avr_isp_spi_sw_sck_set(AvrIspSpiSw* instance, bool state) { - furi_assert(instance); - furi_hal_gpio_write(instance->sck, state); -} \ No newline at end of file diff --git a/applications/external/avr_isp_programmer/lib/driver/avr_isp_spi_sw.h b/applications/external/avr_isp_programmer/lib/driver/avr_isp_spi_sw.h deleted file mode 100644 index 44de5ff79cb..00000000000 --- a/applications/external/avr_isp_programmer/lib/driver/avr_isp_spi_sw.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -#include - -typedef enum { - AvrIspSpiSwSpeed1Mhz = 0, - AvrIspSpiSwSpeed400Khz = 1, - AvrIspSpiSwSpeed250Khz = 2, - AvrIspSpiSwSpeed125Khz = 4, - AvrIspSpiSwSpeed60Khz = 8, - AvrIspSpiSwSpeed40Khz = 12, - AvrIspSpiSwSpeed20Khz = 24, - AvrIspSpiSwSpeed10Khz = 48, - AvrIspSpiSwSpeed5Khz = 96, - AvrIspSpiSwSpeed1Khz = 480, -} AvrIspSpiSwSpeed; - -typedef struct AvrIspSpiSw AvrIspSpiSw; - -AvrIspSpiSw* avr_isp_spi_sw_init(AvrIspSpiSwSpeed speed); -void avr_isp_spi_sw_free(AvrIspSpiSw* instance); -uint8_t avr_isp_spi_sw_txrx(AvrIspSpiSw* instance, uint8_t data); -void avr_isp_spi_sw_res_set(AvrIspSpiSw* instance, bool state); -void avr_isp_spi_sw_sck_set(AvrIspSpiSw* instance, bool state); \ No newline at end of file diff --git a/applications/external/avr_isp_programmer/lib/driver/clock.png b/applications/external/avr_isp_programmer/lib/driver/clock.png deleted file mode 100644 index 93a59fe681d26795d0db342a0c55ba42fc2c7529..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3649 zcmaJ@c{r3^8-FYniewGR7?BDy24yB=8_Oum7~4q77=ytqjlm2hDWzn~mNi>R4Q+~K zs}!Vu|T0fG&^kldAlcBl>S5JG204rZ&Cc^O@cJQ3w^Qg>RR zx8UiyV9wOk>ZjF;v5c{`7FO%duw7y*@uRsu02~{khv-s>wL#Z5REF_NqWk$lqN9zk zytcdnfEhj(GnDbrV2$Si72pME9UA+@>IQyYEXSxg0ibxGA1pSuohJ?p)N9z+O91t| zfroZaJcNKm0Ptg-H3kFsgn`K)7W!L&uEK;~X`m~2PoV%1%>$&Wn(yN^d;z#QT)?XF z*1Q6;*@j>Z{+eQ*Fz075bKbDZEkIxlE^eox8xWRitkwj8ba?^PUh!r=kR@L>w7t5& z@H8!=49x@7G$u8t9BC`)=T8#Fi5Kd3nP%I}deUiyHjr{FL+BPCr)96iQo*|Gxw zWS84sZs;1sjg1ZujCzjwaelnX-SC~Eg7p<=`!*`B^YR0t)~%fG(<39De6%{AhXK{T zg)Tt1BjDY)?5foxn0-R%eeiM=OLxt1Z&nVbUQd3H(Dv<9%I-Op(4i>(Us?my{;1GJ z?(RlU@Cl;(z*(Pd0m3+JI=uOHEzjv3{|W7ba-Z zTiteNz1m%IS&-kTUO*hLh=|>6`(r_iNryc~mwsx(;Tr=^)V_UwDya9&K?<&Y%dzv6_Jb4d+LR~!ZNE zNW`rZ7Ub+e48-nAp}2NHnsRfx6sj>_J+I?^8p(^a z6H7uQIVOcBjoq_%@OLoiVBOnpf8Sx}{Zo$T?wC0|!3-4&ew4c3Q7G^5qVRBW3pNNF zi)pnzomX{wJ$!{A{P=Q&S@vago;{)TtxU9{)LR&F7H8Z^cjTK;^Sx>1?(%qf(lT(% zs$3u>#L^Dsf6tTc8Sj}ndZw92F=CQPMg9JsJ6i2I2k`pUBXOL9O0YqO;TCg%%y?5yBfXA<7>V1+AQ++m#Iu& z@fy-$O6z;Fse9bn+FyyizIu3f609e`Hvi3V)q&Q(#uliikvlbn3+ce|Nv8cmQb;;eyXB)R9TO}{CZ#wEbvK$v2Kd~)3Pfn;!kUO3H zFmg`mJJJ#9jnD2Dr5Du(rjz?51|?z-v>#ZoqjYOdu1yL}rcG|0f-mA1l^4m2t@2HK z#N<1VGLD|5GXk0d{b&^v`2*Uo3u_Bsk2`tEdFA+L&g)3uIUd(2mJ*mEZAUJ+RzSHG z+?X^XJ6+!X^ut14`iu15qR-@yUz(6_&fQ#;wp2Uv4bv({VOcwX|1@Kj!qz3_z3mrsE|mH+lOoh{K@UTlTz z(3dpcAt>yuKu@67NYBYF6SR80)Y94{-w9+&o{(FCHmO+d?c5b}xmBP~G?aR0*>b$; znLuQ}xnE?N0!b!Sdik8hfrGGn8sBY8>=M!t2kE_V_%b2YRu6 z{IGt6$@H?YvU_D0m{)$9&ZdYl#PWw&h?FJd?jfejZWm@5x)Ocj zqgJ2i#`k5V?cq{qE8`ww${s%HDq}j&_JgZUUq~rM*+~a!Xu4v{J(#4K_H&KijgOPp zF@rd)!<-MRcP<8dvHkXK)S+-E?WDrQhDJ*9j}y-clK3PK2aZolhl}I+gVIT-*);au z;-3%A%0>sBtWS5GU0{*ByT2YQeK$3Mp2(k|u$P>x9~`UnG3t1Kc}BQMZZ>*E?lk$> zS4K{-&q7RdN%OmAJ{`QyluOeycF$bS;k?D*%=4~|j_XDDORGMsbaz&N2@07PxhOAr z^eZQEvf}9>rju`_>A3|;`*ir1SXp{-d09!qeoQ=$>xS13nwh!9Yx6YG?fovDhPT^Z^Wi45*rTV(sx>kCjTC)tK8Pk@fr;6aM$d`ql?mkGJC1x@NX7N3~WLvkK?w zoco0j5Oqp*3KcCZoH9;%UtOg_s_L5I24=o(g-}=U-eyUE?Ci!GWa-lU zY8YI37x%AHhGB|h*ik(hL3lb5F!G?f6G0YaycZEm#Cx#LG!XRwfKQcVk7MAhED;1M zSp&c6qroK8xM%>-Ghov21YaTp+3>pFg2?`3*2-4D^(!C&>a5x+Sg+X92b*_iHKa0Y^Gu0{nO1~LQi2ejR ziN+vNDWFY8ygN03fdq4t{r4%zw0~$R{(o1BTQdj~PlIS`KsQhI+tJGE|GSdO|9JZ| zu*Co5`#*{O?O8M;1WWX%2G9xI-gzo*hN2-*bRwQXrQ1`fe!mNe@uo7U{@zp?2&Sc> z1yZ%b6G)Uz%YnZjR#pfLia!HSArLK0kYFx}28rZ>(AGYzWd?^Do9aN1Xlk0GjEr@( zOwCY7bYYq>xRw_DH`ato2p|(FjNe#~|6oyn#BK_LOyfp2A<{{KL=Q7Ml??jp)Ckg_ zbAkVn?{BQfpK~$#BNoC<2C~`P|LXN`6IVc+(|^RvUHl_|B897YI#=9}_AkY9FUD4k zrM>B|@Xb4NEn;?-J6Kzo7}+zs^RX^M07#%``usTPM&dJQT7TW0pZvvcreZ!fk89eR zxb$l$y&OrR&%MN0k$&Et1-(znrXGup@9h&S%{ikQa$ LTALIbyM_M?u*zuP diff --git a/applications/external/avr_isp_programmer/scenes/avr_isp_scene.c b/applications/external/avr_isp_programmer/scenes/avr_isp_scene.c deleted file mode 100644 index 4af125aeeaa..00000000000 --- a/applications/external/avr_isp_programmer/scenes/avr_isp_scene.c +++ /dev/null @@ -1,30 +0,0 @@ -#include "../avr_isp_app_i.h" - -// Generate scene on_enter handlers array -#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, -void (*const avr_isp_scene_on_enter_handlers[])(void*) = { -#include "avr_isp_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 avr_isp_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = { -#include "avr_isp_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 avr_isp_scene_on_exit_handlers[])(void* context) = { -#include "avr_isp_scene_config.h" -}; -#undef ADD_SCENE - -// Initialize scene handlers configuration structure -const SceneManagerHandlers avr_isp_scene_handlers = { - .on_enter_handlers = avr_isp_scene_on_enter_handlers, - .on_event_handlers = avr_isp_scene_on_event_handlers, - .on_exit_handlers = avr_isp_scene_on_exit_handlers, - .scene_num = AvrIspSceneNum, -}; diff --git a/applications/external/avr_isp_programmer/scenes/avr_isp_scene.h b/applications/external/avr_isp_programmer/scenes/avr_isp_scene.h deleted file mode 100644 index 658ee74326f..00000000000 --- a/applications/external/avr_isp_programmer/scenes/avr_isp_scene.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -#include - -// Generate scene id and total number -#define ADD_SCENE(prefix, name, id) AvrIspScene##id, -typedef enum { -#include "avr_isp_scene_config.h" - AvrIspSceneNum, -} AvrIspScene; -#undef ADD_SCENE - -extern const SceneManagerHandlers avr_isp_scene_handlers; - -// Generate scene on_enter handlers declaration -#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); -#include "avr_isp_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 "avr_isp_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 "avr_isp_scene_config.h" -#undef ADD_SCENE diff --git a/applications/external/avr_isp_programmer/scenes/avr_isp_scene_about.c b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_about.c deleted file mode 100644 index e5f530fec89..00000000000 --- a/applications/external/avr_isp_programmer/scenes/avr_isp_scene_about.c +++ /dev/null @@ -1,99 +0,0 @@ -#include "../avr_isp_app_i.h" -#include "../helpers/avr_isp_types.h" - -void avr_isp_scene_about_widget_callback(GuiButtonType result, InputType type, void* context) { - furi_assert(context); - - AvrIspApp* app = context; - if(type == InputTypeShort) { - view_dispatcher_send_custom_event(app->view_dispatcher, result); - } -} - -void avr_isp_scene_about_on_enter(void* context) { - furi_assert(context); - - AvrIspApp* app = context; - FuriString* temp_str = furi_string_alloc(); - furi_string_printf(temp_str, "\e#%s\n", "Information"); - - furi_string_cat_printf(temp_str, "Version: %s\n", AVR_ISP_VERSION_APP); - furi_string_cat_printf(temp_str, "Developed by: %s\n", AVR_ISP_DEVELOPED); - furi_string_cat_printf(temp_str, "Github: %s\n\n", AVR_ISP_GITHUB); - - furi_string_cat_printf(temp_str, "\e#%s\n", "Description"); - furi_string_cat_printf( - temp_str, - "This application is an AVR in-system programmer based on stk500mk1. It is compatible with AVR-based" - " microcontrollers including Arduino. You can also use it to repair the chip if you accidentally" - " corrupt the bootloader.\n\n"); - - furi_string_cat_printf(temp_str, "\e#%s\n", "What it can do:"); - furi_string_cat_printf(temp_str, "- Create a dump of your chip on an SD card\n"); - furi_string_cat_printf(temp_str, "- Flash your chip firmware from the SD card\n"); - furi_string_cat_printf(temp_str, "- Act as a wired USB ISP using avrdude software\n\n"); - - furi_string_cat_printf(temp_str, "\e#%s\n", "Supported chip series:"); - furi_string_cat_printf( - temp_str, - "Example command for avrdude flashing: avrdude.exe -p m328p -c stk500v1 -P COMxx -U flash:r:" - "X:\\sketch_sample.hex" - ":i\n"); - furi_string_cat_printf( - temp_str, - "Where: " - "-p m328p" - " brand of your chip, " - "-P COMxx" - " com port number in the system when " - "ISP Programmer" - " is enabled\n\n"); - - furi_string_cat_printf(temp_str, "\e#%s\n", "Info"); - furi_string_cat_printf( - temp_str, - "ATtinyXXXX\nATmegaXXXX\nAT43Uxxx\nAT76C711\nAT86RF401\nAT90xxxxx\nAT94K\n" - "ATAxxxxx\nATA664251\nM3000\nLGT8F88P\nLGT8F168P\nLGT8F328P\n"); - - furi_string_cat_printf( - temp_str, "For a more detailed list of supported chips, see AVRDude help\n"); - - widget_add_text_box_element( - app->widget, - 0, - 0, - 128, - 14, - AlignCenter, - AlignBottom, - "\e#\e! \e!\n", - false); - widget_add_text_box_element( - app->widget, - 0, - 2, - 128, - 14, - AlignCenter, - AlignBottom, - "\e#\e! ISP Programmer \e!\n", - false); - widget_add_text_scroll_element(app->widget, 0, 16, 128, 50, furi_string_get_cstr(temp_str)); - furi_string_free(temp_str); - - view_dispatcher_switch_to_view(app->view_dispatcher, AvrIspViewWidget); -} - -bool avr_isp_scene_about_on_event(void* context, SceneManagerEvent event) { - UNUSED(context); - UNUSED(event); - return false; -} - -void avr_isp_scene_about_on_exit(void* context) { - furi_assert(context); - - AvrIspApp* app = context; - // Clear views - widget_reset(app->widget); -} diff --git a/applications/external/avr_isp_programmer/scenes/avr_isp_scene_chip_detect.c b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_chip_detect.c deleted file mode 100644 index 79c23939074..00000000000 --- a/applications/external/avr_isp_programmer/scenes/avr_isp_scene_chip_detect.c +++ /dev/null @@ -1,72 +0,0 @@ -#include "../avr_isp_app_i.h" - -void avr_isp_scene_chip_detect_callback(AvrIspCustomEvent event, void* context) { - furi_assert(context); - - AvrIspApp* app = context; - view_dispatcher_send_custom_event(app->view_dispatcher, event); -} - -void avr_isp_scene_chip_detect_on_enter(void* context) { - furi_assert(context); - - AvrIspApp* app = context; - switch(app->error) { - case AvrIspErrorReading: - case AvrIspErrorWriting: - case AvrIspErrorWritingFuse: - avr_isp_chip_detect_set_state( - app->avr_isp_chip_detect_view, AvrIspChipDetectViewStateErrorOccured); - break; - case AvrIspErrorVerification: - avr_isp_chip_detect_set_state( - app->avr_isp_chip_detect_view, AvrIspChipDetectViewStateErrorVerification); - break; - - default: - avr_isp_chip_detect_set_state( - app->avr_isp_chip_detect_view, AvrIspChipDetectViewStateNoDetect); - break; - } - app->error = AvrIspErrorNoError; - avr_isp_chip_detect_view_set_callback( - app->avr_isp_chip_detect_view, avr_isp_scene_chip_detect_callback, app); - - view_dispatcher_switch_to_view(app->view_dispatcher, AvrIspViewChipDetect); -} - -bool avr_isp_scene_chip_detect_on_event(void* context, SceneManagerEvent event) { - furi_assert(context); - - AvrIspApp* app = context; - bool consumed = false; - if(event.type == SceneManagerEventTypeCustom) { - switch(event.event) { - case AvrIspCustomEventSceneChipDetectOk: - - if(scene_manager_get_scene_state(app->scene_manager, AvrIspSceneChipDetect) == - AvrIspViewProgrammer) { - scene_manager_next_scene(app->scene_manager, AvrIspSceneProgrammer); - } else if( - scene_manager_get_scene_state(app->scene_manager, AvrIspSceneChipDetect) == - AvrIspViewReader) { - scene_manager_next_scene(app->scene_manager, AvrIspSceneInputName); - } else if( - scene_manager_get_scene_state(app->scene_manager, AvrIspSceneChipDetect) == - AvrIspViewWriter) { - scene_manager_next_scene(app->scene_manager, AvrIspSceneLoad); - } - - consumed = true; - break; - default: - break; - } - } else if(event.type == SceneManagerEventTypeTick) { - } - return consumed; -} - -void avr_isp_scene_chip_detect_on_exit(void* context) { - UNUSED(context); -} diff --git a/applications/external/avr_isp_programmer/scenes/avr_isp_scene_config.h b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_config.h deleted file mode 100644 index 6f22511dbe2..00000000000 --- a/applications/external/avr_isp_programmer/scenes/avr_isp_scene_config.h +++ /dev/null @@ -1,10 +0,0 @@ -ADD_SCENE(avr_isp, start, Start) -ADD_SCENE(avr_isp, about, About) -ADD_SCENE(avr_isp, programmer, Programmer) -ADD_SCENE(avr_isp, reader, Reader) -ADD_SCENE(avr_isp, input_name, InputName) -ADD_SCENE(avr_isp, load, Load) -ADD_SCENE(avr_isp, writer, Writer) -ADD_SCENE(avr_isp, wiring, Wiring) -ADD_SCENE(avr_isp, chip_detect, ChipDetect) -ADD_SCENE(avr_isp, success, Success) diff --git a/applications/external/avr_isp_programmer/scenes/avr_isp_scene_input_name.c b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_input_name.c deleted file mode 100644 index 3394f4362a6..00000000000 --- a/applications/external/avr_isp_programmer/scenes/avr_isp_scene_input_name.c +++ /dev/null @@ -1,89 +0,0 @@ -#include "../avr_isp_app_i.h" -#include - -#define MAX_TEXT_INPUT_LEN 22 - -void avr_isp_scene_input_name_text_callback(void* context) { - furi_assert(context); - - AvrIspApp* app = context; - view_dispatcher_send_custom_event(app->view_dispatcher, AvrIspCustomEventSceneInputName); -} - -void avr_isp_scene_input_name_get_timefilename(FuriString* name) { - FuriHalRtcDateTime datetime = {0}; - furi_hal_rtc_get_datetime(&datetime); - furi_string_printf( - name, - "AVR_dump-%.4d%.2d%.2d-%.2d%.2d%.2d", - datetime.year, - datetime.month, - datetime.day, - datetime.hour, - datetime.minute, - datetime.second); -} - -void avr_isp_scene_input_name_on_enter(void* context) { - furi_assert(context); - - AvrIspApp* app = context; - // Setup view - TextInput* text_input = app->text_input; - bool dev_name_empty = false; - - FuriString* file_name = furi_string_alloc(); - - avr_isp_scene_input_name_get_timefilename(file_name); - furi_string_set(app->file_path, STORAGE_APP_DATA_PATH_PREFIX); - //highlighting the entire filename by default - dev_name_empty = true; - - strncpy(app->file_name_tmp, furi_string_get_cstr(file_name), AVR_ISP_MAX_LEN_NAME); - text_input_set_header_text(text_input, "Name dump"); - text_input_set_result_callback( - text_input, - avr_isp_scene_input_name_text_callback, - app, - app->file_name_tmp, - MAX_TEXT_INPUT_LEN, // buffer size - dev_name_empty); - - ValidatorIsFile* validator_is_file = - validator_is_file_alloc_init(STORAGE_APP_DATA_PATH_PREFIX, AVR_ISP_APP_EXTENSION, ""); - text_input_set_validator(text_input, validator_is_file_callback, validator_is_file); - - furi_string_free(file_name); - - view_dispatcher_switch_to_view(app->view_dispatcher, AvrIspViewTextInput); -} - -bool avr_isp_scene_input_name_on_event(void* context, SceneManagerEvent event) { - furi_assert(context); - - AvrIspApp* app = context; - if(event.type == SceneManagerEventTypeBack) { - scene_manager_previous_scene(app->scene_manager); - return true; - } else if(event.type == SceneManagerEventTypeCustom) { - if(event.event == AvrIspCustomEventSceneInputName) { - if(strcmp(app->file_name_tmp, "") != 0) { - scene_manager_next_scene(app->scene_manager, AvrIspSceneReader); - } else { - } - } - } - return false; -} - -void avr_isp_scene_input_name_on_exit(void* context) { - furi_assert(context); - - AvrIspApp* app = context; - // Clear validator - void* validator_context = text_input_get_validator_callback_context(app->text_input); - text_input_set_validator(app->text_input, NULL, NULL); - validator_is_file_free(validator_context); - // Clear view - text_input_reset(app->text_input); -} diff --git a/applications/external/avr_isp_programmer/scenes/avr_isp_scene_load.c b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_load.c deleted file mode 100644 index e8890e37381..00000000000 --- a/applications/external/avr_isp_programmer/scenes/avr_isp_scene_load.c +++ /dev/null @@ -1,22 +0,0 @@ -#include "../avr_isp_app_i.h" - -void avr_isp_scene_load_on_enter(void* context) { - furi_assert(context); - - AvrIspApp* app = context; - if(avr_isp_load_from_file(app)) { - scene_manager_next_scene(app->scene_manager, AvrIspSceneWriter); - } else { - scene_manager_previous_scene(app->scene_manager); - } -} - -bool avr_isp_scene_load_on_event(void* context, SceneManagerEvent event) { - UNUSED(context); - UNUSED(event); - return false; -} - -void avr_isp_scene_load_on_exit(void* context) { - UNUSED(context); -} diff --git a/applications/external/avr_isp_programmer/scenes/avr_isp_scene_programmer.c b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_programmer.c deleted file mode 100644 index 0915e1e8a2a..00000000000 --- a/applications/external/avr_isp_programmer/scenes/avr_isp_scene_programmer.c +++ /dev/null @@ -1,28 +0,0 @@ -#include "../avr_isp_app_i.h" - -void avr_isp_scene_programmer_callback(AvrIspCustomEvent event, void* context) { - furi_assert(context); - - AvrIspApp* app = context; - view_dispatcher_send_custom_event(app->view_dispatcher, event); -} - -void avr_isp_scene_programmer_on_enter(void* context) { - furi_assert(context); - - AvrIspApp* app = context; - avr_isp_programmer_view_set_callback( - app->avr_isp_programmer_view, avr_isp_scene_programmer_callback, app); - - view_dispatcher_switch_to_view(app->view_dispatcher, AvrIspViewProgrammer); -} - -bool avr_isp_scene_programmer_on_event(void* context, SceneManagerEvent event) { - UNUSED(context); - UNUSED(event); - return false; -} - -void avr_isp_scene_programmer_on_exit(void* context) { - UNUSED(context); -} diff --git a/applications/external/avr_isp_programmer/scenes/avr_isp_scene_reader.c b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_reader.c deleted file mode 100644 index 8dcb4759700..00000000000 --- a/applications/external/avr_isp_programmer/scenes/avr_isp_scene_reader.c +++ /dev/null @@ -1,64 +0,0 @@ -#include "../avr_isp_app_i.h" - -void avr_isp_scene_reader_callback(AvrIspCustomEvent event, void* context) { - furi_assert(context); - - AvrIspApp* app = context; - view_dispatcher_send_custom_event(app->view_dispatcher, event); -} - -void avr_isp_scene_reader_on_enter(void* context) { - furi_assert(context); - - AvrIspApp* app = context; - avr_isp_reader_set_file_path( - app->avr_isp_reader_view, furi_string_get_cstr(app->file_path), app->file_name_tmp); - avr_isp_reader_view_set_callback(app->avr_isp_reader_view, avr_isp_scene_reader_callback, app); - - view_dispatcher_switch_to_view(app->view_dispatcher, AvrIspViewReader); -} - -bool avr_isp_scene_reader_on_event(void* context, SceneManagerEvent event) { - furi_assert(context); - - AvrIspApp* app = context; - UNUSED(app); - bool consumed = false; - if(event.type == SceneManagerEventTypeBack) { - //do not handle exit on "Back" - consumed = true; - } else if(event.type == SceneManagerEventTypeCustom) { - switch(event.event) { - case AvrIspCustomEventSceneReadingOk: - scene_manager_next_scene(app->scene_manager, AvrIspSceneSuccess); - consumed = true; - break; - case AvrIspCustomEventSceneExit: - scene_manager_search_and_switch_to_previous_scene( - app->scene_manager, AvrIspSceneChipDetect); - consumed = true; - break; - case AvrIspCustomEventSceneErrorVerification: - app->error = AvrIspErrorVerification; - scene_manager_search_and_switch_to_previous_scene( - app->scene_manager, AvrIspSceneChipDetect); - consumed = true; - break; - case AvrIspCustomEventSceneErrorReading: - app->error = AvrIspErrorReading; - scene_manager_search_and_switch_to_previous_scene( - app->scene_manager, AvrIspSceneChipDetect); - consumed = true; - break; - default: - break; - } - } else if(event.type == SceneManagerEventTypeTick) { - avr_isp_reader_update_progress(app->avr_isp_reader_view); - } - return consumed; -} - -void avr_isp_scene_reader_on_exit(void* context) { - UNUSED(context); -} diff --git a/applications/external/avr_isp_programmer/scenes/avr_isp_scene_start.c b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_start.c deleted file mode 100644 index b00bfefce2f..00000000000 --- a/applications/external/avr_isp_programmer/scenes/avr_isp_scene_start.c +++ /dev/null @@ -1,75 +0,0 @@ -#include "../avr_isp_app_i.h" - -void avr_isp_scene_start_submenu_callback(void* context, uint32_t index) { - furi_assert(context); - AvrIspApp* app = context; - - view_dispatcher_send_custom_event(app->view_dispatcher, index); -} - -void avr_isp_scene_start_on_enter(void* context) { - furi_assert(context); - - AvrIspApp* app = context; - Submenu* submenu = app->submenu; - submenu_add_item( - submenu, "Dump AVR", SubmenuIndexAvrIspReader, avr_isp_scene_start_submenu_callback, app); - submenu_add_item( - submenu, "Flash AVR", SubmenuIndexAvrIspWriter, avr_isp_scene_start_submenu_callback, app); - submenu_add_item( - submenu, - "ISP Programmer", - SubmenuIndexAvrIspProgrammer, - avr_isp_scene_start_submenu_callback, - app); - submenu_add_item( - submenu, "Wiring", SubmenuIndexAvrIsWiring, avr_isp_scene_start_submenu_callback, app); - submenu_add_item( - submenu, "About", SubmenuIndexAvrIspAbout, avr_isp_scene_start_submenu_callback, app); - - submenu_set_selected_item( - submenu, scene_manager_get_scene_state(app->scene_manager, AvrIspSceneStart)); - - view_dispatcher_switch_to_view(app->view_dispatcher, AvrIspViewSubmenu); -} - -bool avr_isp_scene_start_on_event(void* context, SceneManagerEvent event) { - furi_assert(context); - - AvrIspApp* app = context; - bool consumed = false; - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SubmenuIndexAvrIspAbout) { - scene_manager_next_scene(app->scene_manager, AvrIspSceneAbout); - consumed = true; - } else if(event.event == SubmenuIndexAvrIspProgrammer) { - scene_manager_set_scene_state( - app->scene_manager, AvrIspSceneChipDetect, AvrIspViewProgrammer); - scene_manager_next_scene(app->scene_manager, AvrIspSceneChipDetect); - consumed = true; - } else if(event.event == SubmenuIndexAvrIspReader) { - scene_manager_set_scene_state( - app->scene_manager, AvrIspSceneChipDetect, AvrIspViewReader); - scene_manager_next_scene(app->scene_manager, AvrIspSceneChipDetect); - consumed = true; - } else if(event.event == SubmenuIndexAvrIspWriter) { - scene_manager_set_scene_state( - app->scene_manager, AvrIspSceneChipDetect, AvrIspViewWriter); - scene_manager_next_scene(app->scene_manager, AvrIspSceneChipDetect); - consumed = true; - } else if(event.event == SubmenuIndexAvrIsWiring) { - scene_manager_next_scene(app->scene_manager, AvrIspSceneWiring); - consumed = true; - } - scene_manager_set_scene_state(app->scene_manager, AvrIspSceneStart, event.event); - } - - return consumed; -} - -void avr_isp_scene_start_on_exit(void* context) { - furi_assert(context); - - AvrIspApp* app = context; - submenu_reset(app->submenu); -} diff --git a/applications/external/avr_isp_programmer/scenes/avr_isp_scene_success.c b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_success.c deleted file mode 100644 index a88ed28aa83..00000000000 --- a/applications/external/avr_isp_programmer/scenes/avr_isp_scene_success.c +++ /dev/null @@ -1,44 +0,0 @@ -#include "../avr_isp_app_i.h" - -void avr_isp_scene_success_popup_callback(void* context) { - furi_assert(context); - - AvrIspApp* app = context; - view_dispatcher_send_custom_event(app->view_dispatcher, AvrIspCustomEventSceneSuccess); -} - -void avr_isp_scene_success_on_enter(void* context) { - furi_assert(context); - - AvrIspApp* app = context; - Popup* popup = app->popup; - popup_set_icon(popup, 32, 5, &I_dolphin_nice_96x59); - popup_set_header(popup, "Success!", 8, 22, AlignLeft, AlignBottom); - popup_set_timeout(popup, 1500); - popup_set_context(popup, app); - popup_set_callback(popup, avr_isp_scene_success_popup_callback); - popup_enable_timeout(popup); - view_dispatcher_switch_to_view(app->view_dispatcher, AvrIspViewPopup); -} - -bool avr_isp_scene_success_on_event(void* context, SceneManagerEvent event) { - furi_assert(context); - - AvrIspApp* app = context; - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == AvrIspCustomEventSceneSuccess) { - scene_manager_search_and_switch_to_previous_scene( - app->scene_manager, AvrIspSceneStart); - return true; - } - } - return false; -} - -void avr_isp_scene_success_on_exit(void* context) { - furi_assert(context); - - AvrIspApp* app = context; - Popup* popup = app->popup; - popup_reset(popup); -} diff --git a/applications/external/avr_isp_programmer/scenes/avr_isp_scene_wiring.c b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_wiring.c deleted file mode 100644 index 787ed56732e..00000000000 --- a/applications/external/avr_isp_programmer/scenes/avr_isp_scene_wiring.c +++ /dev/null @@ -1,21 +0,0 @@ -#include "../avr_isp_app_i.h" - -void avr_isp_scene_wiring_on_enter(void* context) { - furi_assert(context); - - AvrIspApp* app = context; - widget_add_icon_element(app->widget, 0, 0, &I_avr_wiring); - view_dispatcher_switch_to_view(app->view_dispatcher, AvrIspViewWidget); -} - -bool avr_isp_scene_wiring_on_event(void* context, SceneManagerEvent event) { - UNUSED(context); - UNUSED(event); - return false; -} -void avr_isp_scene_wiring_on_exit(void* context) { - furi_assert(context); - - AvrIspApp* app = context; - widget_reset(app->widget); -} diff --git a/applications/external/avr_isp_programmer/scenes/avr_isp_scene_writer.c b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_writer.c deleted file mode 100644 index 39c944fd5fc..00000000000 --- a/applications/external/avr_isp_programmer/scenes/avr_isp_scene_writer.c +++ /dev/null @@ -1,69 +0,0 @@ -#include "../avr_isp_app_i.h" - -void avr_isp_scene_writer_callback(AvrIspCustomEvent event, void* context) { - furi_assert(context); - - AvrIspApp* app = context; - view_dispatcher_send_custom_event(app->view_dispatcher, event); -} - -void avr_isp_scene_writer_on_enter(void* context) { - furi_assert(context); - - AvrIspApp* app = context; - avr_isp_writer_set_file_path( - app->avr_isp_writer_view, furi_string_get_cstr(app->file_path), app->file_name_tmp); - avr_isp_writer_view_set_callback(app->avr_isp_writer_view, avr_isp_scene_writer_callback, app); - view_dispatcher_switch_to_view(app->view_dispatcher, AvrIspViewWriter); -} - -bool avr_isp_scene_writer_on_event(void* context, SceneManagerEvent event) { - furi_assert(context); - - AvrIspApp* app = context; - bool consumed = false; - if(event.type == SceneManagerEventTypeBack) { - //do not handle exit on "Back" - consumed = true; - } else if(event.type == SceneManagerEventTypeCustom) { - switch(event.event) { - case AvrIspCustomEventSceneExitStartMenu: - scene_manager_search_and_switch_to_previous_scene( - app->scene_manager, AvrIspSceneStart); - consumed = true; - break; - case AvrIspCustomEventSceneExit: - scene_manager_search_and_switch_to_previous_scene( - app->scene_manager, AvrIspSceneChipDetect); - consumed = true; - break; - case AvrIspCustomEventSceneErrorVerification: - app->error = AvrIspErrorVerification; - scene_manager_search_and_switch_to_previous_scene( - app->scene_manager, AvrIspSceneChipDetect); - consumed = true; - break; - case AvrIspCustomEventSceneErrorWriting: - app->error = AvrIspErrorWriting; - scene_manager_search_and_switch_to_previous_scene( - app->scene_manager, AvrIspSceneChipDetect); - consumed = true; - break; - case AvrIspCustomEventSceneErrorWritingFuse: - app->error = AvrIspErrorWritingFuse; - scene_manager_search_and_switch_to_previous_scene( - app->scene_manager, AvrIspSceneChipDetect); - consumed = true; - break; - default: - break; - } - } else if(event.type == SceneManagerEventTypeTick) { - avr_isp_writer_update_progress(app->avr_isp_writer_view); - } - return consumed; -} - -void avr_isp_scene_writer_on_exit(void* context) { - UNUSED(context); -} diff --git a/applications/external/avr_isp_programmer/views/avr_isp_view_chip_detect.c b/applications/external/avr_isp_programmer/views/avr_isp_view_chip_detect.c deleted file mode 100644 index fdcf71c36c1..00000000000 --- a/applications/external/avr_isp_programmer/views/avr_isp_view_chip_detect.c +++ /dev/null @@ -1,213 +0,0 @@ -#include "avr_isp_view_chip_detect.h" -#include -#include - -#include "../helpers/avr_isp_worker_rw.h" - -struct AvrIspChipDetectView { - View* view; - AvrIspWorkerRW* avr_isp_worker_rw; - AvrIspChipDetectViewCallback callback; - void* context; -}; - -typedef struct { - uint16_t idx; - const char* name_chip; - uint32_t flash_size; - AvrIspChipDetectViewState state; -} AvrIspChipDetectViewModel; - -void avr_isp_chip_detect_view_set_callback( - AvrIspChipDetectView* instance, - AvrIspChipDetectViewCallback callback, - void* context) { - furi_assert(instance); - furi_assert(callback); - - instance->callback = callback; - instance->context = context; -} - -void avr_isp_chip_detect_set_state(AvrIspChipDetectView* instance, AvrIspChipDetectViewState state) { - furi_assert(instance); - - with_view_model( - instance->view, AvrIspChipDetectViewModel * model, { model->state = state; }, true); -} - -void avr_isp_chip_detect_view_draw(Canvas* canvas, AvrIspChipDetectViewModel* model) { - canvas_clear(canvas); - - char str_buf[64] = {0}; - canvas_set_font(canvas, FontPrimary); - - switch(model->state) { - case AvrIspChipDetectViewStateDetected: - canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, "AVR chip detected!"); - canvas_draw_icon(canvas, 29, 14, &I_chip_long_70x22); - canvas_set_font(canvas, FontSecondary); - snprintf(str_buf, sizeof(str_buf), "%ld Kb", model->flash_size / 1024); - canvas_draw_str_aligned(canvas, 64, 25, AlignCenter, AlignCenter, str_buf); - canvas_draw_str_aligned(canvas, 64, 45, AlignCenter, AlignCenter, model->name_chip); - elements_button_right(canvas, "Next"); - break; - case AvrIspChipDetectViewStateErrorOccured: - canvas_draw_str_aligned( - canvas, 64, 5, AlignCenter, AlignCenter, "Error occured, try again!"); - canvas_draw_icon(canvas, 29, 14, &I_chip_error_70x22); - canvas_set_font(canvas, FontSecondary); - canvas_draw_str_aligned( - canvas, 64, 45, AlignCenter, AlignCenter, "Check the wiring and retry"); - break; - case AvrIspChipDetectViewStateErrorVerification: - canvas_draw_str_aligned( - canvas, 64, 5, AlignCenter, AlignCenter, "Data verification failed"); - canvas_draw_icon(canvas, 29, 14, &I_chip_error_70x22); - canvas_set_font(canvas, FontSecondary); - canvas_draw_str_aligned( - canvas, 64, 45, AlignCenter, AlignCenter, "Try to restart the process"); - break; - - default: - //AvrIspChipDetectViewStateNoDetect - canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, "AVR chip not found!"); - canvas_draw_icon(canvas, 29, 12, &I_chif_not_found_83x37); - - break; - } - canvas_set_font(canvas, FontSecondary); - elements_button_left(canvas, "Retry"); -} - -bool avr_isp_chip_detect_view_input(InputEvent* event, void* context) { - furi_assert(context); - - AvrIspChipDetectView* instance = context; - - if(event->type == InputTypeShort) { - if(event->key == InputKeyBack) { - return false; - } else if(event->key == InputKeyRight) { - with_view_model( - instance->view, - AvrIspChipDetectViewModel * model, - { - if(model->state == AvrIspChipDetectViewStateDetected) { - if(instance->callback) - instance->callback( - AvrIspCustomEventSceneChipDetectOk, instance->context); - } - }, - false); - - } else if(event->key == InputKeyLeft) { - bool detect_chip = false; - with_view_model( - instance->view, - AvrIspChipDetectViewModel * model, - { - if(model->state != AvrIspChipDetectViewStateDetecting) { - model->state = AvrIspChipDetectViewStateDetecting; - detect_chip = true; - } - }, - false); - if(detect_chip) avr_isp_worker_rw_detect_chip(instance->avr_isp_worker_rw); - } - } else { - return false; - } - - return true; -} - -static void avr_isp_chip_detect_detect_chip_callback( - void* context, - const char* name, - bool detect_chip, - uint32_t flash_size) { - furi_assert(context); - - AvrIspChipDetectView* instance = context; - with_view_model( - instance->view, - AvrIspChipDetectViewModel * model, - { - model->name_chip = name; - model->flash_size = flash_size; - if(detect_chip) { - model->state = AvrIspChipDetectViewStateDetected; - } else { - model->state = AvrIspChipDetectViewStateNoDetect; - } - }, - true); -} -void avr_isp_chip_detect_view_enter(void* context) { - furi_assert(context); - - AvrIspChipDetectView* instance = context; - bool detect_chip = false; - with_view_model( - instance->view, - AvrIspChipDetectViewModel * model, - { - if(model->state == AvrIspChipDetectViewStateNoDetect || - model->state == AvrIspChipDetectViewStateDetected) { - detect_chip = true; - } - }, - false); - - //Start avr_isp_worker_rw - instance->avr_isp_worker_rw = avr_isp_worker_rw_alloc(instance->context); - - avr_isp_worker_rw_set_callback( - instance->avr_isp_worker_rw, avr_isp_chip_detect_detect_chip_callback, instance); - - if(detect_chip) avr_isp_worker_rw_detect_chip(instance->avr_isp_worker_rw); -} - -void avr_isp_chip_detect_view_exit(void* context) { - furi_assert(context); - - AvrIspChipDetectView* instance = context; - - avr_isp_worker_rw_set_callback(instance->avr_isp_worker_rw, NULL, NULL); - avr_isp_worker_rw_free(instance->avr_isp_worker_rw); -} - -AvrIspChipDetectView* avr_isp_chip_detect_view_alloc() { - AvrIspChipDetectView* instance = malloc(sizeof(AvrIspChipDetectView)); - - // View allocation and configuration - instance->view = view_alloc(); - - view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(AvrIspChipDetectViewModel)); - view_set_context(instance->view, instance); - view_set_draw_callback(instance->view, (ViewDrawCallback)avr_isp_chip_detect_view_draw); - view_set_input_callback(instance->view, avr_isp_chip_detect_view_input); - view_set_enter_callback(instance->view, avr_isp_chip_detect_view_enter); - view_set_exit_callback(instance->view, avr_isp_chip_detect_view_exit); - - with_view_model( - instance->view, - AvrIspChipDetectViewModel * model, - { model->state = AvrIspChipDetectViewStateNoDetect; }, - false); - return instance; -} - -void avr_isp_chip_detect_view_free(AvrIspChipDetectView* instance) { - furi_assert(instance); - - view_free(instance->view); - free(instance); -} - -View* avr_isp_chip_detect_view_get_view(AvrIspChipDetectView* instance) { - furi_assert(instance); - - return instance->view; -} diff --git a/applications/external/avr_isp_programmer/views/avr_isp_view_chip_detect.h b/applications/external/avr_isp_programmer/views/avr_isp_view_chip_detect.h deleted file mode 100644 index 37f2ae23369..00000000000 --- a/applications/external/avr_isp_programmer/views/avr_isp_view_chip_detect.h +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once - -#include -#include "../helpers/avr_isp_types.h" -#include "../helpers/avr_isp_event.h" - -typedef struct AvrIspChipDetectView AvrIspChipDetectView; - -typedef void (*AvrIspChipDetectViewCallback)(AvrIspCustomEvent event, void* context); - -typedef enum { - AvrIspChipDetectViewStateNoDetect, - AvrIspChipDetectViewStateDetecting, - AvrIspChipDetectViewStateDetected, - AvrIspChipDetectViewStateErrorOccured, - AvrIspChipDetectViewStateErrorVerification, -} AvrIspChipDetectViewState; - -void avr_isp_chip_detect_view_set_callback( - AvrIspChipDetectView* instance, - AvrIspChipDetectViewCallback callback, - void* context); - -void avr_isp_chip_detect_set_state(AvrIspChipDetectView* instance, AvrIspChipDetectViewState state); - -AvrIspChipDetectView* avr_isp_chip_detect_view_alloc(); - -void avr_isp_chip_detect_view_free(AvrIspChipDetectView* instance); - -View* avr_isp_chip_detect_view_get_view(AvrIspChipDetectView* instance); - -void avr_isp_chip_detect_view_exit(void* context); diff --git a/applications/external/avr_isp_programmer/views/avr_isp_view_programmer.c b/applications/external/avr_isp_programmer/views/avr_isp_view_programmer.c deleted file mode 100644 index 34e18770b2f..00000000000 --- a/applications/external/avr_isp_programmer/views/avr_isp_view_programmer.c +++ /dev/null @@ -1,134 +0,0 @@ -#include "avr_isp_view_programmer.h" -#include - -#include "../helpers/avr_isp_worker.h" -#include - -struct AvrIspProgrammerView { - View* view; - AvrIspWorker* worker; - AvrIspProgrammerViewCallback callback; - void* context; -}; - -typedef struct { - AvrIspProgrammerViewStatus status; -} AvrIspProgrammerViewModel; - -void avr_isp_programmer_view_set_callback( - AvrIspProgrammerView* instance, - AvrIspProgrammerViewCallback callback, - void* context) { - furi_assert(instance); - furi_assert(callback); - - instance->callback = callback; - instance->context = context; -} - -void avr_isp_programmer_view_draw(Canvas* canvas, AvrIspProgrammerViewModel* model) { - canvas_clear(canvas); - - if(model->status == AvrIspProgrammerViewStatusUSBConnect) { - canvas_set_font(canvas, FontPrimary); - canvas_draw_icon(canvas, 0, 0, &I_isp_active_128x53); - elements_multiline_text(canvas, 45, 10, "ISP mode active"); - } else { - canvas_set_font(canvas, FontSecondary); - canvas_draw_icon(canvas, 51, 6, &I_link_waiting_77x56); - elements_multiline_text(canvas, 0, 25, "Waiting for\nsoftware\nconnection"); - } -} - -bool avr_isp_programmer_view_input(InputEvent* event, void* context) { - furi_assert(context); - UNUSED(context); - - if(event->key == InputKeyBack || event->type != InputTypeShort) { - return false; - } - - return true; -} - -static void avr_isp_programmer_usb_connect_callback(void* context, bool status_connect) { - furi_assert(context); - AvrIspProgrammerView* instance = context; - - with_view_model( - instance->view, - AvrIspProgrammerViewModel * model, - { - if(status_connect) { - model->status = AvrIspProgrammerViewStatusUSBConnect; - } else { - model->status = AvrIspProgrammerViewStatusNoUSBConnect; - } - }, - true); -} - -void avr_isp_programmer_view_enter(void* context) { - furi_assert(context); - - AvrIspProgrammerView* instance = context; - with_view_model( - instance->view, - AvrIspProgrammerViewModel * model, - { model->status = AvrIspProgrammerViewStatusNoUSBConnect; }, - true); - - //Start worker - instance->worker = avr_isp_worker_alloc(instance->context); - - avr_isp_worker_set_callback( - instance->worker, avr_isp_programmer_usb_connect_callback, instance); - - avr_isp_worker_start(instance->worker); -} - -void avr_isp_programmer_view_exit(void* context) { - furi_assert(context); - - AvrIspProgrammerView* instance = context; - //Stop worker - if(avr_isp_worker_is_running(instance->worker)) { - avr_isp_worker_stop(instance->worker); - } - avr_isp_worker_set_callback(instance->worker, NULL, NULL); - avr_isp_worker_free(instance->worker); -} - -AvrIspProgrammerView* avr_isp_programmer_view_alloc() { - AvrIspProgrammerView* instance = malloc(sizeof(AvrIspProgrammerView)); - - // View allocation and configuration - instance->view = view_alloc(); - - view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(AvrIspProgrammerViewModel)); - view_set_context(instance->view, instance); - view_set_draw_callback(instance->view, (ViewDrawCallback)avr_isp_programmer_view_draw); - view_set_input_callback(instance->view, avr_isp_programmer_view_input); - view_set_enter_callback(instance->view, avr_isp_programmer_view_enter); - view_set_exit_callback(instance->view, avr_isp_programmer_view_exit); - - with_view_model( - instance->view, - AvrIspProgrammerViewModel * model, - { model->status = AvrIspProgrammerViewStatusNoUSBConnect; }, - false); - return instance; -} - -void avr_isp_programmer_view_free(AvrIspProgrammerView* instance) { - furi_assert(instance); - - view_free(instance->view); - free(instance); -} - -View* avr_isp_programmer_view_get_view(AvrIspProgrammerView* instance) { - furi_assert(instance); - - return instance->view; -} diff --git a/applications/external/avr_isp_programmer/views/avr_isp_view_programmer.h b/applications/external/avr_isp_programmer/views/avr_isp_view_programmer.h deleted file mode 100644 index 9f005b026a2..00000000000 --- a/applications/external/avr_isp_programmer/views/avr_isp_view_programmer.h +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once - -#include -#include "../helpers/avr_isp_types.h" -#include "../helpers/avr_isp_event.h" - -typedef struct AvrIspProgrammerView AvrIspProgrammerView; - -typedef void (*AvrIspProgrammerViewCallback)(AvrIspCustomEvent event, void* context); - -typedef enum { - AvrIspProgrammerViewStatusNoUSBConnect, - AvrIspProgrammerViewStatusUSBConnect, -} AvrIspProgrammerViewStatus; - -void avr_isp_programmer_view_set_callback( - AvrIspProgrammerView* instance, - AvrIspProgrammerViewCallback callback, - void* context); - -AvrIspProgrammerView* avr_isp_programmer_view_alloc(); - -void avr_isp_programmer_view_free(AvrIspProgrammerView* instance); - -View* avr_isp_programmer_view_get_view(AvrIspProgrammerView* instance); - -void avr_isp_programmer_view_exit(void* context); diff --git a/applications/external/avr_isp_programmer/views/avr_isp_view_reader.c b/applications/external/avr_isp_programmer/views/avr_isp_view_reader.c deleted file mode 100644 index 92d15bd7f8b..00000000000 --- a/applications/external/avr_isp_programmer/views/avr_isp_view_reader.c +++ /dev/null @@ -1,215 +0,0 @@ -#include "avr_isp_view_reader.h" -#include - -#include "../helpers/avr_isp_worker_rw.h" - -struct AvrIspReaderView { - View* view; - AvrIspWorkerRW* avr_isp_worker_rw; - const char* file_path; - const char* file_name; - AvrIspReaderViewCallback callback; - void* context; -}; - -typedef struct { - AvrIspReaderViewStatus status; - float progress_flash; - float progress_eeprom; -} AvrIspReaderViewModel; - -void avr_isp_reader_update_progress(AvrIspReaderView* instance) { - with_view_model( - instance->view, - AvrIspReaderViewModel * model, - { - model->progress_flash = - avr_isp_worker_rw_get_progress_flash(instance->avr_isp_worker_rw); - model->progress_eeprom = - avr_isp_worker_rw_get_progress_eeprom(instance->avr_isp_worker_rw); - }, - true); -} - -void avr_isp_reader_view_set_callback( - AvrIspReaderView* instance, - AvrIspReaderViewCallback callback, - void* context) { - furi_assert(instance); - furi_assert(callback); - - instance->callback = callback; - instance->context = context; -} - -void avr_isp_reader_set_file_path( - AvrIspReaderView* instance, - const char* file_path, - const char* file_name) { - furi_assert(instance); - - instance->file_path = file_path; - instance->file_name = file_name; -} - -void avr_isp_reader_view_draw(Canvas* canvas, AvrIspReaderViewModel* model) { - canvas_clear(canvas); - char str_buf[64] = {0}; - - canvas_set_font(canvas, FontPrimary); - switch(model->status) { - case AvrIspReaderViewStatusIDLE: - canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, "Press start to dump"); - canvas_set_font(canvas, FontSecondary); - elements_button_center(canvas, "Start"); - break; - case AvrIspReaderViewStatusReading: - canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, "Reading dump"); - break; - case AvrIspReaderViewStatusVerification: - canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, "Verifyng dump"); - break; - - default: - break; - } - - canvas_set_font(canvas, FontSecondary); - canvas_draw_str(canvas, 0, 27, "Flash"); - snprintf(str_buf, sizeof(str_buf), "%d%%", (uint8_t)(model->progress_flash * 100)); - elements_progress_bar_with_text(canvas, 44, 17, 84, model->progress_flash, str_buf); - canvas_draw_str(canvas, 0, 43, "EEPROM"); - snprintf(str_buf, sizeof(str_buf), "%d%%", (uint8_t)(model->progress_eeprom * 100)); - elements_progress_bar_with_text(canvas, 44, 34, 84, model->progress_eeprom, str_buf); -} - -bool avr_isp_reader_view_input(InputEvent* event, void* context) { - furi_assert(context); - AvrIspReaderView* instance = context; - - bool ret = true; - if(event->key == InputKeyBack && event->type == InputTypeShort) { - with_view_model( - instance->view, - AvrIspReaderViewModel * model, - { - if(model->status == AvrIspReaderViewStatusIDLE) { - if(instance->callback) - instance->callback(AvrIspCustomEventSceneExit, instance->context); - ret = false; - } - }, - false); - } else if(event->key == InputKeyOk && event->type == InputTypeShort) { - with_view_model( - instance->view, - AvrIspReaderViewModel * model, - { - if(model->status == AvrIspReaderViewStatusIDLE) { - model->status = AvrIspReaderViewStatusReading; - avr_isp_worker_rw_read_dump_start( - instance->avr_isp_worker_rw, instance->file_path, instance->file_name); - } - }, - false); - } - return ret; -} - -static void avr_isp_reader_callback_status(void* context, AvrIspWorkerRWStatus status) { - furi_assert(context); - AvrIspReaderView* instance = context; - - with_view_model( - instance->view, - AvrIspReaderViewModel * model, - { - switch(status) { - case AvrIspWorkerRWStatusEndReading: - model->status = AvrIspReaderViewStatusVerification; - avr_isp_worker_rw_verification_start( - instance->avr_isp_worker_rw, instance->file_path, instance->file_name); - model->status = AvrIspReaderViewStatusVerification; - break; - case AvrIspWorkerRWStatusEndVerification: - if(instance->callback) - instance->callback(AvrIspCustomEventSceneReadingOk, instance->context); - break; - case AvrIspWorkerRWStatusErrorVerification: - if(instance->callback) - instance->callback(AvrIspCustomEventSceneErrorVerification, instance->context); - break; - - default: - //AvrIspWorkerRWStatusErrorReading; - if(instance->callback) - instance->callback(AvrIspCustomEventSceneErrorReading, instance->context); - break; - } - }, - true); -} - -void avr_isp_reader_view_enter(void* context) { - furi_assert(context); - AvrIspReaderView* instance = context; - - with_view_model( - instance->view, - AvrIspReaderViewModel * model, - { - model->status = AvrIspReaderViewStatusIDLE; - model->progress_flash = 0.0f; - model->progress_eeprom = 0.0f; - }, - true); - - //Start avr_isp_worker_rw - instance->avr_isp_worker_rw = avr_isp_worker_rw_alloc(instance->context); - - avr_isp_worker_rw_set_callback_status( - instance->avr_isp_worker_rw, avr_isp_reader_callback_status, instance); - - avr_isp_worker_rw_start(instance->avr_isp_worker_rw); -} - -void avr_isp_reader_view_exit(void* context) { - furi_assert(context); - - AvrIspReaderView* instance = context; - //Stop avr_isp_worker_rw - if(avr_isp_worker_rw_is_running(instance->avr_isp_worker_rw)) { - avr_isp_worker_rw_stop(instance->avr_isp_worker_rw); - } - - avr_isp_worker_rw_free(instance->avr_isp_worker_rw); -} - -AvrIspReaderView* avr_isp_reader_view_alloc() { - AvrIspReaderView* instance = malloc(sizeof(AvrIspReaderView)); - - // View allocation and configuration - instance->view = view_alloc(); - - view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(AvrIspReaderViewModel)); - view_set_context(instance->view, instance); - view_set_draw_callback(instance->view, (ViewDrawCallback)avr_isp_reader_view_draw); - view_set_input_callback(instance->view, avr_isp_reader_view_input); - view_set_enter_callback(instance->view, avr_isp_reader_view_enter); - view_set_exit_callback(instance->view, avr_isp_reader_view_exit); - - return instance; -} - -void avr_isp_reader_view_free(AvrIspReaderView* instance) { - furi_assert(instance); - - view_free(instance->view); - free(instance); -} - -View* avr_isp_reader_view_get_view(AvrIspReaderView* instance) { - furi_assert(instance); - - return instance->view; -} diff --git a/applications/external/avr_isp_programmer/views/avr_isp_view_reader.h b/applications/external/avr_isp_programmer/views/avr_isp_view_reader.h deleted file mode 100644 index 44a43994815..00000000000 --- a/applications/external/avr_isp_programmer/views/avr_isp_view_reader.h +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once - -#include -#include "../helpers/avr_isp_types.h" -#include "../helpers/avr_isp_event.h" - -typedef struct AvrIspReaderView AvrIspReaderView; - -typedef void (*AvrIspReaderViewCallback)(AvrIspCustomEvent event, void* context); - -typedef enum { - AvrIspReaderViewStatusIDLE, - AvrIspReaderViewStatusReading, - AvrIspReaderViewStatusVerification, -} AvrIspReaderViewStatus; - -void avr_isp_reader_update_progress(AvrIspReaderView* instance); - -void avr_isp_reader_set_file_path( - AvrIspReaderView* instance, - const char* file_path, - const char* file_name); - -void avr_isp_reader_view_set_callback( - AvrIspReaderView* instance, - AvrIspReaderViewCallback callback, - void* context); - -AvrIspReaderView* avr_isp_reader_view_alloc(); - -void avr_isp_reader_view_free(AvrIspReaderView* instance); - -View* avr_isp_reader_view_get_view(AvrIspReaderView* instance); - -void avr_isp_reader_view_exit(void* context); diff --git a/applications/external/avr_isp_programmer/views/avr_isp_view_writer.c b/applications/external/avr_isp_programmer/views/avr_isp_view_writer.c deleted file mode 100644 index a06b7853555..00000000000 --- a/applications/external/avr_isp_programmer/views/avr_isp_view_writer.c +++ /dev/null @@ -1,268 +0,0 @@ -#include "avr_isp_view_writer.h" -#include - -#include "../helpers/avr_isp_worker_rw.h" -#include - -struct AvrIspWriterView { - View* view; - AvrIspWorkerRW* avr_isp_worker_rw; - const char* file_path; - const char* file_name; - AvrIspWriterViewCallback callback; - void* context; -}; - -typedef struct { - AvrIspWriterViewStatus status; - float progress_flash; - float progress_eeprom; -} AvrIspWriterViewModel; - -void avr_isp_writer_update_progress(AvrIspWriterView* instance) { - with_view_model( - instance->view, - AvrIspWriterViewModel * model, - { - model->progress_flash = - avr_isp_worker_rw_get_progress_flash(instance->avr_isp_worker_rw); - model->progress_eeprom = - avr_isp_worker_rw_get_progress_eeprom(instance->avr_isp_worker_rw); - }, - true); -} - -void avr_isp_writer_view_set_callback( - AvrIspWriterView* instance, - AvrIspWriterViewCallback callback, - void* context) { - furi_assert(instance); - furi_assert(callback); - - instance->callback = callback; - instance->context = context; -} - -void avr_isp_writer_set_file_path( - AvrIspWriterView* instance, - const char* file_path, - const char* file_name) { - furi_assert(instance); - - instance->file_path = file_path; - instance->file_name = file_name; -} - -void avr_isp_writer_view_draw(Canvas* canvas, AvrIspWriterViewModel* model) { - canvas_clear(canvas); - char str_flash[32] = {0}; - char str_eeprom[32] = {0}; - - canvas_set_font(canvas, FontPrimary); - - switch(model->status) { - case AvrIspWriterViewStatusIDLE: - canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, "Press start to write"); - canvas_set_font(canvas, FontSecondary); - elements_button_center(canvas, "Start"); - snprintf(str_flash, sizeof(str_flash), "%d%%", (uint8_t)(model->progress_flash * 100)); - snprintf(str_eeprom, sizeof(str_eeprom), "%d%%", (uint8_t)(model->progress_eeprom * 100)); - break; - case AvrIspWriterViewStatusWriting: - if(float_is_equal(model->progress_flash, 0.f)) { - canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, "Verifying firmware"); - snprintf(str_flash, sizeof(str_flash), "***"); - snprintf(str_eeprom, sizeof(str_eeprom), "***"); - } else { - canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, "Writing dump"); - snprintf(str_flash, sizeof(str_flash), "%d%%", (uint8_t)(model->progress_flash * 100)); - snprintf( - str_eeprom, sizeof(str_eeprom), "%d%%", (uint8_t)(model->progress_eeprom * 100)); - } - break; - case AvrIspWriterViewStatusVerification: - canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, "Verifying dump"); - snprintf(str_flash, sizeof(str_flash), "%d%%", (uint8_t)(model->progress_flash * 100)); - snprintf(str_eeprom, sizeof(str_eeprom), "%d%%", (uint8_t)(model->progress_eeprom * 100)); - break; - case AvrIspWriterViewStatusWritingFuse: - canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, "Writing fuse"); - snprintf(str_flash, sizeof(str_flash), "%d%%", (uint8_t)(model->progress_flash * 100)); - snprintf(str_eeprom, sizeof(str_eeprom), "%d%%", (uint8_t)(model->progress_eeprom * 100)); - break; - case AvrIspWriterViewStatusWritingFuseOk: - canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, "Done!"); - snprintf(str_flash, sizeof(str_flash), "%d%%", (uint8_t)(model->progress_flash * 100)); - snprintf(str_eeprom, sizeof(str_eeprom), "%d%%", (uint8_t)(model->progress_eeprom * 100)); - canvas_set_font(canvas, FontSecondary); - elements_button_center(canvas, "Reflash"); - elements_button_right(canvas, "Exit"); - break; - - default: - break; - } - - canvas_set_font(canvas, FontSecondary); - canvas_draw_str(canvas, 0, 27, "Flash"); - // snprintf(str_buf, sizeof(str_buf), "%d%%", (uint8_t)(model->progress_flash * 100)); - elements_progress_bar_with_text(canvas, 44, 17, 84, model->progress_flash, str_flash); - canvas_draw_str(canvas, 0, 43, "EEPROM"); - // snprintf(str_buf, sizeof(str_buf), "%d%%", (uint8_t)(model->progress_eeprom * 100)); - elements_progress_bar_with_text(canvas, 44, 34, 84, model->progress_eeprom, str_eeprom); -} - -bool avr_isp_writer_view_input(InputEvent* event, void* context) { - furi_assert(context); - AvrIspWriterView* instance = context; - - bool ret = true; - if(event->key == InputKeyBack && event->type == InputTypeShort) { - with_view_model( - instance->view, - AvrIspWriterViewModel * model, - { - if((model->status == AvrIspWriterViewStatusIDLE) || - (model->status == AvrIspWriterViewStatusWritingFuseOk)) { - if(instance->callback) - instance->callback(AvrIspCustomEventSceneExit, instance->context); - ret = false; - } - }, - false); - } else if(event->key == InputKeyOk && event->type == InputTypeShort) { - with_view_model( - instance->view, - AvrIspWriterViewModel * model, - { - if((model->status == AvrIspWriterViewStatusIDLE) || - (model->status == AvrIspWriterViewStatusWritingFuseOk)) { - model->status = AvrIspWriterViewStatusWriting; - - avr_isp_worker_rw_write_dump_start( - instance->avr_isp_worker_rw, instance->file_path, instance->file_name); - } - }, - false); - } else if(event->key == InputKeyRight && event->type == InputTypeShort) { - with_view_model( - instance->view, - AvrIspWriterViewModel * model, - { - if((model->status == AvrIspWriterViewStatusIDLE) || - (model->status == AvrIspWriterViewStatusWritingFuseOk)) { - if(instance->callback) - instance->callback(AvrIspCustomEventSceneExitStartMenu, instance->context); - ret = false; - } - }, - false); - } - return ret; -} - -static void avr_isp_writer_callback_status(void* context, AvrIspWorkerRWStatus status) { - furi_assert(context); - - AvrIspWriterView* instance = context; - with_view_model( - instance->view, - AvrIspWriterViewModel * model, - { - switch(status) { - case AvrIspWorkerRWStatusEndWriting: - model->status = AvrIspWriterViewStatusVerification; - avr_isp_worker_rw_verification_start( - instance->avr_isp_worker_rw, instance->file_path, instance->file_name); - model->status = AvrIspWriterViewStatusVerification; - break; - case AvrIspWorkerRWStatusErrorVerification: - if(instance->callback) - instance->callback(AvrIspCustomEventSceneErrorVerification, instance->context); - break; - case AvrIspWorkerRWStatusEndVerification: - avr_isp_worker_rw_write_fuse_start( - instance->avr_isp_worker_rw, instance->file_path, instance->file_name); - model->status = AvrIspWriterViewStatusWritingFuse; - break; - case AvrIspWorkerRWStatusErrorWritingFuse: - if(instance->callback) - instance->callback(AvrIspCustomEventSceneErrorWritingFuse, instance->context); - break; - case AvrIspWorkerRWStatusEndWritingFuse: - model->status = AvrIspWriterViewStatusWritingFuseOk; - break; - - default: - //AvrIspWorkerRWStatusErrorWriting; - if(instance->callback) - instance->callback(AvrIspCustomEventSceneErrorWriting, instance->context); - break; - } - }, - true); -} - -void avr_isp_writer_view_enter(void* context) { - furi_assert(context); - - AvrIspWriterView* instance = context; - with_view_model( - instance->view, - AvrIspWriterViewModel * model, - { - model->status = AvrIspWriterViewStatusIDLE; - model->progress_flash = 0.0f; - model->progress_eeprom = 0.0f; - }, - true); - - //Start avr_isp_worker_rw - instance->avr_isp_worker_rw = avr_isp_worker_rw_alloc(instance->context); - - avr_isp_worker_rw_set_callback_status( - instance->avr_isp_worker_rw, avr_isp_writer_callback_status, instance); - - avr_isp_worker_rw_start(instance->avr_isp_worker_rw); -} - -void avr_isp_writer_view_exit(void* context) { - furi_assert(context); - AvrIspWriterView* instance = context; - - //Stop avr_isp_worker_rw - if(avr_isp_worker_rw_is_running(instance->avr_isp_worker_rw)) { - avr_isp_worker_rw_stop(instance->avr_isp_worker_rw); - } - - avr_isp_worker_rw_free(instance->avr_isp_worker_rw); -} - -AvrIspWriterView* avr_isp_writer_view_alloc() { - AvrIspWriterView* instance = malloc(sizeof(AvrIspWriterView)); - - // View allocation and configuration - instance->view = view_alloc(); - - view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(AvrIspWriterViewModel)); - view_set_context(instance->view, instance); - view_set_draw_callback(instance->view, (ViewDrawCallback)avr_isp_writer_view_draw); - view_set_input_callback(instance->view, avr_isp_writer_view_input); - view_set_enter_callback(instance->view, avr_isp_writer_view_enter); - view_set_exit_callback(instance->view, avr_isp_writer_view_exit); - - return instance; -} - -void avr_isp_writer_view_free(AvrIspWriterView* instance) { - furi_assert(instance); - - view_free(instance->view); - free(instance); -} - -View* avr_isp_writer_view_get_view(AvrIspWriterView* instance) { - furi_assert(instance); - - return instance->view; -} diff --git a/applications/external/avr_isp_programmer/views/avr_isp_view_writer.h b/applications/external/avr_isp_programmer/views/avr_isp_view_writer.h deleted file mode 100644 index 1ff72838765..00000000000 --- a/applications/external/avr_isp_programmer/views/avr_isp_view_writer.h +++ /dev/null @@ -1,37 +0,0 @@ -#pragma once - -#include -#include "../helpers/avr_isp_types.h" -#include "../helpers/avr_isp_event.h" - -typedef struct AvrIspWriterView AvrIspWriterView; - -typedef void (*AvrIspWriterViewCallback)(AvrIspCustomEvent event, void* context); - -typedef enum { - AvrIspWriterViewStatusIDLE, - AvrIspWriterViewStatusWriting, - AvrIspWriterViewStatusVerification, - AvrIspWriterViewStatusWritingFuse, - AvrIspWriterViewStatusWritingFuseOk, -} AvrIspWriterViewStatus; - -void avr_isp_writer_update_progress(AvrIspWriterView* instance); - -void avr_isp_writer_set_file_path( - AvrIspWriterView* instance, - const char* file_path, - const char* file_name); - -void avr_isp_writer_view_set_callback( - AvrIspWriterView* instance, - AvrIspWriterViewCallback callback, - void* context); - -AvrIspWriterView* avr_isp_writer_view_alloc(); - -void avr_isp_writer_view_free(AvrIspWriterView* instance); - -View* avr_isp_writer_view_get_view(AvrIspWriterView* instance); - -void avr_isp_writer_view_exit(void* context); diff --git a/applications/external/dap_link/README.md b/applications/external/dap_link/README.md deleted file mode 100644 index aead0a60a22..00000000000 --- a/applications/external/dap_link/README.md +++ /dev/null @@ -1,105 +0,0 @@ -# Flipper Zero as CMSIS DAP/DAP Link -Flipper Zero as a [Free-DAP](https://github.com/ataradov/free-dap) based SWD\JTAG debugger. Free-DAP is a free and open source firmware implementation of the [CMSIS-DAP](https://www.keil.com/pack/doc/CMSIS_Dev/DAP/html/index.html) debugger. - -## Protocols -SWD, JTAG , CMSIS-DAP v1 (18 KiB/s), CMSIS-DAP v2 (46 KiB/s), VCP (USB-UART). - -WinUSB for driverless installation for Windows 8 and above. - -## Usage - -### VSCode + Cortex-Debug - Set `"device": "cmsis-dap"` - -

- BluePill configuration example - - ```json -{ - "name": "Attach (DAP)", - "cwd": "${workspaceFolder}", - "executable": "./build/firmware.elf", - "request": "attach", - "type": "cortex-debug", - "servertype": "openocd", - "device": "cmsis-dap", - "configFiles": [ - "interface/cmsis-dap.cfg", - "target/stm32f1x.cfg", - ], -}, - ``` -
- -
- Flipper Zero configuration example - - ```json -{ - "name": "Attach (DAP)", - "cwd": "${workspaceFolder}", - "executable": "./build/latest/firmware.elf", - "request": "attach", - "type": "cortex-debug", - "servertype": "openocd", - "device": "cmsis-dap", - "svdFile": "./debug/STM32WB55_CM4.svd", - "rtos": "FreeRTOS", - "configFiles": [ - "interface/cmsis-dap.cfg", - "./debug/stm32wbx.cfg", - ], - "postAttachCommands": [ - "source debug/flipperapps.py", - ], -}, - ``` -
- -### OpenOCD -Use `interface/cmsis-dap.cfg`. You will need OpenOCD v0.11.0. - -Additional commands: -* `cmsis_dap_backend hid` for CMSIS-DAP v1 protocol. -* `cmsis_dap_backend usb_bulk` for CMSIS-DAP v2 protocol. -* `cmsis_dap_serial DAP_Oyevoxo` use DAP-Link running on Flipper named `Oyevoxo`. -* `cmsis-dap cmd 81` - reboot connected DAP-Link. - -
- Flash BluePill - - ``` -openocd -f interface/cmsis-dap.cfg -f target/stm32f1x.cfg -c init -c "program build/firmware.bin reset exit 0x8000000" - ``` -
- -
- Flash Flipper Zero using DAP v2 protocol - - ``` -openocd -f interface/cmsis-dap.cfg -c "cmsis_dap_backend usb_bulk" -f debug/stm32wbx.cfg -c init -c "program build/latest/firmware.bin reset exit 0x8000000" - ``` -
- -
- Reboot connected DAP-Link on Flipper named Oyevoxo - - ``` -openocd -f interface/cmsis-dap.cfg -c "cmsis_dap_serial DAP_Oyevoxo" -c "transport select swd" -c "adapter speed 4000000" -c init -c "cmsis-dap cmd 81" -c "exit" - ``` -
- -### PlatformIO -Use `debug_tool = cmsis-dap` and `upload_protocol = cmsis-dap`. [Documentation](https://docs.platformio.org/en/latest/plus/debug-tools/cmsis-dap.html#debugging-tool-cmsis-dap). Remember that Windows 8 and above do not require drivers. - -
- BluePill platformio.ini example - - ``` -[env:bluepill_f103c8] -platform = ststm32 -board = bluepill_f103c8 -debug_tool = cmsis-dap -upload_protocol = cmsis-dap - ``` -
diff --git a/applications/external/dap_link/application.fam b/applications/external/dap_link/application.fam deleted file mode 100644 index 0171438030f..00000000000 --- a/applications/external/dap_link/application.fam +++ /dev/null @@ -1,24 +0,0 @@ -App( - appid="dap_link", - name="DAP Link", - apptype=FlipperAppType.EXTERNAL, - entry_point="dap_link_app", - requires=[ - "gui", - "dialogs", - ], - stack_size=4 * 1024, - order=20, - fap_icon="dap_link.png", - fap_category="GPIO", - fap_private_libs=[ - Lib( - name="free-dap", - cincludes=["."], - sources=[ - "dap.c", - ], - ), - ], - fap_icon_assets="icons", -) diff --git a/applications/external/dap_link/dap_config.h b/applications/external/dap_link/dap_config.h deleted file mode 100644 index 88b90bd34a9..00000000000 --- a/applications/external/dap_link/dap_config.h +++ /dev/null @@ -1,234 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -// Copyright (c) 2022, Alex Taradov . All rights reserved. - -#ifndef _DAP_CONFIG_H_ -#define _DAP_CONFIG_H_ - -/*- Includes ----------------------------------------------------------------*/ -#include - -/*- Definitions -------------------------------------------------------------*/ -#define DAP_CONFIG_ENABLE_JTAG - -#define DAP_CONFIG_DEFAULT_PORT DAP_PORT_SWD -#define DAP_CONFIG_DEFAULT_CLOCK 4200000 // Hz - -#define DAP_CONFIG_PACKET_SIZE 64 -#define DAP_CONFIG_PACKET_COUNT 1 - -#define DAP_CONFIG_JTAG_DEV_COUNT 8 - -// DAP_CONFIG_PRODUCT_STR must contain "CMSIS-DAP" to be compatible with the standard -#define DAP_CONFIG_VENDOR_STR "Flipper Zero" -#define DAP_CONFIG_PRODUCT_STR "Generic CMSIS-DAP Adapter" -#define DAP_CONFIG_SER_NUM_STR usb_serial_number -#define DAP_CONFIG_CMSIS_DAP_VER_STR "2.0.0" - -#define DAP_CONFIG_RESET_TARGET_FN dap_app_target_reset -#define DAP_CONFIG_VENDOR_FN dap_app_vendor_cmd - -// Attribute to use for performance-critical functions -#define DAP_CONFIG_PERFORMANCE_ATTR - -// A value at which dap_clock_test() produces 1 kHz output on the SWCLK pin -// #define DAP_CONFIG_DELAY_CONSTANT 19000 -#define DAP_CONFIG_DELAY_CONSTANT 6290 - -// A threshold for switching to fast clock (no added delays) -// This is the frequency produced by dap_clock_test(1) on the SWCLK pin -#define DAP_CONFIG_FAST_CLOCK 2400000 // Hz - -/*- Prototypes --------------------------------------------------------------*/ -extern char usb_serial_number[16]; - -/*- Implementations ---------------------------------------------------------*/ -extern GpioPin flipper_dap_swclk_pin; -extern GpioPin flipper_dap_swdio_pin; -extern GpioPin flipper_dap_reset_pin; -extern GpioPin flipper_dap_tdo_pin; -extern GpioPin flipper_dap_tdi_pin; - -extern void dap_app_vendor_cmd(uint8_t cmd); -extern void dap_app_target_reset(); -extern void dap_app_disconnect(); -extern void dap_app_connect_swd(); -extern void dap_app_connect_jtag(); - -//----------------------------------------------------------------------------- -static inline void DAP_CONFIG_SWCLK_TCK_write(int value) { - furi_hal_gpio_write(&flipper_dap_swclk_pin, value); -} - -//----------------------------------------------------------------------------- -static inline void DAP_CONFIG_SWDIO_TMS_write(int value) { - furi_hal_gpio_write(&flipper_dap_swdio_pin, value); -} - -//----------------------------------------------------------------------------- -static inline void DAP_CONFIG_TDI_write(int value) { -#ifdef DAP_CONFIG_ENABLE_JTAG - furi_hal_gpio_write(&flipper_dap_tdi_pin, value); -#else - (void)value; -#endif -} - -//----------------------------------------------------------------------------- -static inline void DAP_CONFIG_TDO_write(int value) { -#ifdef DAP_CONFIG_ENABLE_JTAG - furi_hal_gpio_write(&flipper_dap_tdo_pin, value); -#else - (void)value; -#endif -} - -//----------------------------------------------------------------------------- -static inline void DAP_CONFIG_nTRST_write(int value) { - (void)value; -} - -//----------------------------------------------------------------------------- -static inline void DAP_CONFIG_nRESET_write(int value) { - furi_hal_gpio_write(&flipper_dap_reset_pin, value); -} - -//----------------------------------------------------------------------------- -static inline int DAP_CONFIG_SWCLK_TCK_read(void) { - return furi_hal_gpio_read(&flipper_dap_swclk_pin); -} - -//----------------------------------------------------------------------------- -static inline int DAP_CONFIG_SWDIO_TMS_read(void) { - return furi_hal_gpio_read(&flipper_dap_swdio_pin); -} - -//----------------------------------------------------------------------------- -static inline int DAP_CONFIG_TDO_read(void) { -#ifdef DAP_CONFIG_ENABLE_JTAG - return furi_hal_gpio_read(&flipper_dap_tdo_pin); -#else - return 0; -#endif -} - -//----------------------------------------------------------------------------- -static inline int DAP_CONFIG_TDI_read(void) { -#ifdef DAP_CONFIG_ENABLE_JTAG - return furi_hal_gpio_read(&flipper_dap_tdi_pin); -#else - return 0; -#endif -} - -//----------------------------------------------------------------------------- -static inline int DAP_CONFIG_nTRST_read(void) { - return 0; -} - -//----------------------------------------------------------------------------- -static inline int DAP_CONFIG_nRESET_read(void) { - return furi_hal_gpio_read(&flipper_dap_reset_pin); -} - -//----------------------------------------------------------------------------- -static inline void DAP_CONFIG_SWCLK_TCK_set(void) { - LL_GPIO_SetOutputPin(flipper_dap_swclk_pin.port, flipper_dap_swclk_pin.pin); -} - -//----------------------------------------------------------------------------- -static inline void DAP_CONFIG_SWCLK_TCK_clr(void) { - LL_GPIO_ResetOutputPin(flipper_dap_swclk_pin.port, flipper_dap_swclk_pin.pin); -} - -//----------------------------------------------------------------------------- -static inline void DAP_CONFIG_SWDIO_TMS_in(void) { - LL_GPIO_SetPinMode(flipper_dap_swdio_pin.port, flipper_dap_swdio_pin.pin, LL_GPIO_MODE_INPUT); -} - -//----------------------------------------------------------------------------- -static inline void DAP_CONFIG_SWDIO_TMS_out(void) { - LL_GPIO_SetPinMode(flipper_dap_swdio_pin.port, flipper_dap_swdio_pin.pin, LL_GPIO_MODE_OUTPUT); -} - -//----------------------------------------------------------------------------- -static inline void DAP_CONFIG_SETUP(void) { - furi_hal_gpio_init(&flipper_dap_swdio_pin, GpioModeInput, GpioPullNo, GpioSpeedVeryHigh); - furi_hal_gpio_init(&flipper_dap_swclk_pin, GpioModeInput, GpioPullNo, GpioSpeedVeryHigh); - furi_hal_gpio_init(&flipper_dap_reset_pin, GpioModeInput, GpioPullNo, GpioSpeedVeryHigh); -#ifdef DAP_CONFIG_ENABLE_JTAG - furi_hal_gpio_init(&flipper_dap_tdo_pin, GpioModeInput, GpioPullNo, GpioSpeedVeryHigh); - furi_hal_gpio_init(&flipper_dap_tdi_pin, GpioModeInput, GpioPullNo, GpioSpeedVeryHigh); -#endif -} - -//----------------------------------------------------------------------------- -static inline void DAP_CONFIG_DISCONNECT(void) { - furi_hal_gpio_init(&flipper_dap_swdio_pin, GpioModeInput, GpioPullNo, GpioSpeedVeryHigh); - furi_hal_gpio_init(&flipper_dap_swclk_pin, GpioModeInput, GpioPullNo, GpioSpeedVeryHigh); - furi_hal_gpio_init(&flipper_dap_reset_pin, GpioModeInput, GpioPullNo, GpioSpeedVeryHigh); -#ifdef DAP_CONFIG_ENABLE_JTAG - furi_hal_gpio_init(&flipper_dap_tdo_pin, GpioModeInput, GpioPullNo, GpioSpeedVeryHigh); - furi_hal_gpio_init(&flipper_dap_tdi_pin, GpioModeInput, GpioPullNo, GpioSpeedVeryHigh); -#endif - dap_app_disconnect(); -} - -//----------------------------------------------------------------------------- -static inline void DAP_CONFIG_CONNECT_SWD(void) { - furi_hal_gpio_init( - &flipper_dap_swdio_pin, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); - furi_hal_gpio_write(&flipper_dap_swdio_pin, true); - - furi_hal_gpio_init( - &flipper_dap_swclk_pin, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); - furi_hal_gpio_write(&flipper_dap_swclk_pin, true); - - furi_hal_gpio_init( - &flipper_dap_reset_pin, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); - furi_hal_gpio_write(&flipper_dap_reset_pin, true); - -#ifdef DAP_CONFIG_ENABLE_JTAG - furi_hal_gpio_init(&flipper_dap_tdo_pin, GpioModeInput, GpioPullNo, GpioSpeedVeryHigh); - furi_hal_gpio_init(&flipper_dap_tdi_pin, GpioModeInput, GpioPullNo, GpioSpeedVeryHigh); -#endif - dap_app_connect_swd(); -} - -//----------------------------------------------------------------------------- -static inline void DAP_CONFIG_CONNECT_JTAG(void) { - furi_hal_gpio_init( - &flipper_dap_swdio_pin, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); - furi_hal_gpio_write(&flipper_dap_swdio_pin, true); - - furi_hal_gpio_init( - &flipper_dap_swclk_pin, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); - furi_hal_gpio_write(&flipper_dap_swclk_pin, true); - - furi_hal_gpio_init( - &flipper_dap_reset_pin, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); - furi_hal_gpio_write(&flipper_dap_reset_pin, true); - -#ifdef DAP_CONFIG_ENABLE_JTAG - furi_hal_gpio_init(&flipper_dap_tdo_pin, GpioModeInput, GpioPullNo, GpioSpeedVeryHigh); - - furi_hal_gpio_init( - &flipper_dap_tdi_pin, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); - furi_hal_gpio_write(&flipper_dap_tdi_pin, true); -#endif - dap_app_connect_jtag(); -} - -//----------------------------------------------------------------------------- -static inline void DAP_CONFIG_LED(int index, int state) { - (void)index; - (void)state; -} - -//----------------------------------------------------------------------------- -__attribute__((always_inline)) static inline void DAP_CONFIG_DELAY(uint32_t cycles) { - asm volatile("1: subs %[cycles], %[cycles], #1 \n" - " bne 1b \n" - : [cycles] "+l"(cycles)); -} - -#endif // _DAP_CONFIG_H_ diff --git a/applications/external/dap_link/dap_link.c b/applications/external/dap_link/dap_link.c deleted file mode 100644 index eafb435e7b3..00000000000 --- a/applications/external/dap_link/dap_link.c +++ /dev/null @@ -1,527 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "dap_link.h" -#include "dap_config.h" -#include "gui/dap_gui.h" -#include "usb/dap_v2_usb.h" -#include -#include "dap_link_icons.h" - -/***************************************************************************/ -/****************************** DAP COMMON *********************************/ -/***************************************************************************/ - -struct DapApp { - FuriThread* dap_thread; - FuriThread* cdc_thread; - FuriThread* gui_thread; - - DapState state; - DapConfig config; -}; - -void dap_app_get_state(DapApp* app, DapState* state) { - *state = app->state; -} - -#define DAP_PROCESS_THREAD_TICK 500 - -typedef enum { - DapThreadEventStop = (1 << 0), -} DapThreadEvent; - -void dap_thread_send_stop(FuriThread* thread) { - furi_thread_flags_set(furi_thread_get_id(thread), DapThreadEventStop); -} - -GpioPin flipper_dap_swclk_pin; -GpioPin flipper_dap_swdio_pin; -GpioPin flipper_dap_reset_pin; -GpioPin flipper_dap_tdo_pin; -GpioPin flipper_dap_tdi_pin; - -/***************************************************************************/ -/****************************** DAP PROCESS ********************************/ -/***************************************************************************/ - -typedef struct { - uint8_t data[DAP_CONFIG_PACKET_SIZE]; - uint8_t size; -} DapPacket; - -typedef enum { - DAPThreadEventStop = DapThreadEventStop, - DAPThreadEventRxV1 = (1 << 1), - DAPThreadEventRxV2 = (1 << 2), - DAPThreadEventUSBConnect = (1 << 3), - DAPThreadEventUSBDisconnect = (1 << 4), - DAPThreadEventApplyConfig = (1 << 5), - DAPThreadEventAll = DAPThreadEventStop | DAPThreadEventRxV1 | DAPThreadEventRxV2 | - DAPThreadEventUSBConnect | DAPThreadEventUSBDisconnect | - DAPThreadEventApplyConfig, -} DAPThreadEvent; - -#define USB_SERIAL_NUMBER_LEN 16 -char usb_serial_number[USB_SERIAL_NUMBER_LEN] = {0}; - -const char* dap_app_get_serial(DapApp* app) { - UNUSED(app); - return usb_serial_number; -} - -static void dap_app_rx1_callback(void* context) { - furi_assert(context); - FuriThreadId thread_id = (FuriThreadId)context; - furi_thread_flags_set(thread_id, DAPThreadEventRxV1); -} - -static void dap_app_rx2_callback(void* context) { - furi_assert(context); - FuriThreadId thread_id = (FuriThreadId)context; - furi_thread_flags_set(thread_id, DAPThreadEventRxV2); -} - -static void dap_app_usb_state_callback(bool state, void* context) { - furi_assert(context); - FuriThreadId thread_id = (FuriThreadId)context; - if(state) { - furi_thread_flags_set(thread_id, DAPThreadEventUSBConnect); - } else { - furi_thread_flags_set(thread_id, DAPThreadEventUSBDisconnect); - } -} - -static void dap_app_process_v1() { - DapPacket tx_packet; - DapPacket rx_packet; - memset(&tx_packet, 0, sizeof(DapPacket)); - rx_packet.size = dap_v1_usb_rx(rx_packet.data, DAP_CONFIG_PACKET_SIZE); - dap_process_request(rx_packet.data, rx_packet.size, tx_packet.data, DAP_CONFIG_PACKET_SIZE); - dap_v1_usb_tx(tx_packet.data, DAP_CONFIG_PACKET_SIZE); -} - -static void dap_app_process_v2() { - DapPacket tx_packet; - DapPacket rx_packet; - memset(&tx_packet, 0, sizeof(DapPacket)); - rx_packet.size = dap_v2_usb_rx(rx_packet.data, DAP_CONFIG_PACKET_SIZE); - size_t len = dap_process_request( - rx_packet.data, rx_packet.size, tx_packet.data, DAP_CONFIG_PACKET_SIZE); - dap_v2_usb_tx(tx_packet.data, len); -} - -void dap_app_vendor_cmd(uint8_t cmd) { - // openocd -c "cmsis-dap cmd 81" - if(cmd == 0x01) { - furi_hal_power_reset(); - } -} - -void dap_app_target_reset() { - FURI_LOG_I("DAP", "Target reset"); -} - -static void dap_init_gpio(DapSwdPins swd_pins) { - switch(swd_pins) { - case DapSwdPinsPA7PA6: - flipper_dap_swclk_pin = gpio_ext_pa7; - flipper_dap_swdio_pin = gpio_ext_pa6; - break; - case DapSwdPinsPA14PA13: - flipper_dap_swclk_pin = (GpioPin){.port = GPIOA, .pin = LL_GPIO_PIN_14}; - flipper_dap_swdio_pin = (GpioPin){.port = GPIOA, .pin = LL_GPIO_PIN_13}; - break; - } - - flipper_dap_reset_pin = gpio_ext_pa4; - flipper_dap_tdo_pin = gpio_ext_pb3; - flipper_dap_tdi_pin = gpio_ext_pb2; -} - -static void dap_deinit_gpio(DapSwdPins swd_pins) { - // setup gpio pins to default state - furi_hal_gpio_init(&flipper_dap_reset_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); - furi_hal_gpio_init(&flipper_dap_tdo_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); - furi_hal_gpio_init(&flipper_dap_tdi_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); - - if(DapSwdPinsPA14PA13 == swd_pins) { - // PA14 and PA13 are used by SWD - furi_hal_gpio_init_ex( - &flipper_dap_swclk_pin, - GpioModeAltFunctionPushPull, - GpioPullDown, - GpioSpeedLow, - GpioAltFn0JTCK_SWCLK); - furi_hal_gpio_init_ex( - &flipper_dap_swdio_pin, - GpioModeAltFunctionPushPull, - GpioPullUp, - GpioSpeedVeryHigh, - GpioAltFn0JTMS_SWDIO); - } else { - furi_hal_gpio_init(&flipper_dap_swclk_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); - furi_hal_gpio_init(&flipper_dap_swdio_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); - } -} - -static int32_t dap_process(void* p) { - DapApp* app = p; - DapState* dap_state = &(app->state); - - // allocate resources - FuriHalUsbInterface* usb_config_prev; - app->config.swd_pins = DapSwdPinsPA7PA6; - DapSwdPins swd_pins_prev = app->config.swd_pins; - - // init pins - dap_init_gpio(swd_pins_prev); - - // init dap - dap_init(); - - // get name - const char* name = furi_hal_version_get_name_ptr(); - if(!name) { - name = "Flipper"; - } - snprintf(usb_serial_number, USB_SERIAL_NUMBER_LEN, "DAP_%s", name); - - // init usb - usb_config_prev = furi_hal_usb_get_config(); - dap_common_usb_alloc_name(usb_serial_number); - dap_common_usb_set_context(furi_thread_get_id(furi_thread_get_current())); - dap_v1_usb_set_rx_callback(dap_app_rx1_callback); - dap_v2_usb_set_rx_callback(dap_app_rx2_callback); - dap_common_usb_set_state_callback(dap_app_usb_state_callback); - furi_hal_usb_set_config(&dap_v2_usb_hid, NULL); - - // work - uint32_t events; - while(1) { - events = furi_thread_flags_wait(DAPThreadEventAll, FuriFlagWaitAny, FuriWaitForever); - - if(!(events & FuriFlagError)) { - if(events & DAPThreadEventRxV1) { - dap_app_process_v1(); - dap_state->dap_counter++; - dap_state->dap_version = DapVersionV1; - } - - if(events & DAPThreadEventRxV2) { - dap_app_process_v2(); - dap_state->dap_counter++; - dap_state->dap_version = DapVersionV2; - } - - if(events & DAPThreadEventUSBConnect) { - dap_state->usb_connected = true; - } - - if(events & DAPThreadEventUSBDisconnect) { - dap_state->usb_connected = false; - dap_state->dap_version = DapVersionUnknown; - } - - if(events & DAPThreadEventApplyConfig) { - if(swd_pins_prev != app->config.swd_pins) { - dap_deinit_gpio(swd_pins_prev); - swd_pins_prev = app->config.swd_pins; - dap_init_gpio(swd_pins_prev); - } - } - - if(events & DAPThreadEventStop) { - break; - } - } - } - - // deinit usb - furi_hal_usb_set_config(usb_config_prev, NULL); - dap_common_usb_free_name(); - dap_deinit_gpio(swd_pins_prev); - return 0; -} - -/***************************************************************************/ -/****************************** CDC PROCESS ********************************/ -/***************************************************************************/ - -typedef enum { - CDCThreadEventStop = DapThreadEventStop, - CDCThreadEventUARTRx = (1 << 1), - CDCThreadEventCDCRx = (1 << 2), - CDCThreadEventCDCConfig = (1 << 3), - CDCThreadEventApplyConfig = (1 << 4), - CDCThreadEventAll = CDCThreadEventStop | CDCThreadEventUARTRx | CDCThreadEventCDCRx | - CDCThreadEventCDCConfig | CDCThreadEventApplyConfig, -} CDCThreadEvent; - -typedef struct { - FuriStreamBuffer* rx_stream; - FuriThreadId thread_id; - FuriHalUartId uart_id; - struct usb_cdc_line_coding line_coding; -} CDCProcess; - -static void cdc_uart_irq_cb(UartIrqEvent ev, uint8_t data, void* ctx) { - CDCProcess* app = ctx; - - if(ev == UartIrqEventRXNE) { - furi_stream_buffer_send(app->rx_stream, &data, 1, 0); - furi_thread_flags_set(app->thread_id, CDCThreadEventUARTRx); - } -} - -static void cdc_usb_rx_callback(void* context) { - CDCProcess* app = context; - furi_thread_flags_set(app->thread_id, CDCThreadEventCDCRx); -} - -static void cdc_usb_control_line_callback(uint8_t state, void* context) { - UNUSED(context); - UNUSED(state); -} - -static void cdc_usb_config_callback(struct usb_cdc_line_coding* config, void* context) { - CDCProcess* app = context; - app->line_coding = *config; - furi_thread_flags_set(app->thread_id, CDCThreadEventCDCConfig); -} - -static FuriHalUartId cdc_init_uart( - DapUartType type, - DapUartTXRX swap, - uint32_t baudrate, - void (*cb)(UartIrqEvent ev, uint8_t data, void* ctx), - void* ctx) { - FuriHalUartId uart_id = FuriHalUartIdUSART1; - if(baudrate == 0) baudrate = 115200; - - switch(type) { - case DapUartTypeUSART1: - uart_id = FuriHalUartIdUSART1; - furi_hal_console_disable(); - furi_hal_uart_deinit(uart_id); - if(swap == DapUartTXRXSwap) { - LL_USART_SetTXRXSwap(USART1, LL_USART_TXRX_SWAPPED); - } else { - LL_USART_SetTXRXSwap(USART1, LL_USART_TXRX_STANDARD); - } - furi_hal_uart_init(uart_id, baudrate); - furi_hal_uart_set_irq_cb(uart_id, cb, ctx); - break; - case DapUartTypeLPUART1: - uart_id = FuriHalUartIdLPUART1; - furi_hal_uart_deinit(uart_id); - if(swap == DapUartTXRXSwap) { - LL_LPUART_SetTXRXSwap(LPUART1, LL_LPUART_TXRX_SWAPPED); - } else { - LL_LPUART_SetTXRXSwap(LPUART1, LL_LPUART_TXRX_STANDARD); - } - furi_hal_uart_init(uart_id, baudrate); - furi_hal_uart_set_irq_cb(uart_id, cb, ctx); - break; - } - - return uart_id; -} - -static void cdc_deinit_uart(DapUartType type) { - switch(type) { - case DapUartTypeUSART1: - furi_hal_uart_deinit(FuriHalUartIdUSART1); - LL_USART_SetTXRXSwap(USART1, LL_USART_TXRX_STANDARD); - furi_hal_console_init(); - break; - case DapUartTypeLPUART1: - furi_hal_uart_deinit(FuriHalUartIdLPUART1); - LL_LPUART_SetTXRXSwap(LPUART1, LL_LPUART_TXRX_STANDARD); - break; - } -} - -static int32_t cdc_process(void* p) { - DapApp* dap_app = p; - DapState* dap_state = &(dap_app->state); - - dap_app->config.uart_pins = DapUartTypeLPUART1; - dap_app->config.uart_swap = DapUartTXRXNormal; - - DapUartType uart_pins_prev = dap_app->config.uart_pins; - DapUartTXRX uart_swap_prev = dap_app->config.uart_swap; - - CDCProcess* app = malloc(sizeof(CDCProcess)); - app->thread_id = furi_thread_get_id(furi_thread_get_current()); - app->rx_stream = furi_stream_buffer_alloc(512, 1); - - const uint8_t rx_buffer_size = 64; - uint8_t* rx_buffer = malloc(rx_buffer_size); - - app->uart_id = cdc_init_uart( - uart_pins_prev, uart_swap_prev, dap_state->cdc_baudrate, cdc_uart_irq_cb, app); - - dap_cdc_usb_set_context(app); - dap_cdc_usb_set_rx_callback(cdc_usb_rx_callback); - dap_cdc_usb_set_control_line_callback(cdc_usb_control_line_callback); - dap_cdc_usb_set_config_callback(cdc_usb_config_callback); - - uint32_t events; - while(1) { - events = furi_thread_flags_wait(CDCThreadEventAll, FuriFlagWaitAny, FuriWaitForever); - - if(!(events & FuriFlagError)) { - if(events & CDCThreadEventCDCConfig) { - if(dap_state->cdc_baudrate != app->line_coding.dwDTERate) { - dap_state->cdc_baudrate = app->line_coding.dwDTERate; - if(dap_state->cdc_baudrate > 0) { - furi_hal_uart_set_br(app->uart_id, dap_state->cdc_baudrate); - } - } - } - - if(events & CDCThreadEventUARTRx) { - size_t len = - furi_stream_buffer_receive(app->rx_stream, rx_buffer, rx_buffer_size, 0); - - if(len > 0) { - dap_cdc_usb_tx(rx_buffer, len); - } - dap_state->cdc_rx_counter += len; - } - - if(events & CDCThreadEventCDCRx) { - size_t len = dap_cdc_usb_rx(rx_buffer, rx_buffer_size); - if(len > 0) { - furi_hal_uart_tx(app->uart_id, rx_buffer, len); - } - dap_state->cdc_tx_counter += len; - } - - if(events & CDCThreadEventApplyConfig) { - if(uart_pins_prev != dap_app->config.uart_pins || - uart_swap_prev != dap_app->config.uart_swap) { - cdc_deinit_uart(uart_pins_prev); - uart_pins_prev = dap_app->config.uart_pins; - uart_swap_prev = dap_app->config.uart_swap; - app->uart_id = cdc_init_uart( - uart_pins_prev, - uart_swap_prev, - dap_state->cdc_baudrate, - cdc_uart_irq_cb, - app); - } - } - - if(events & CDCThreadEventStop) { - break; - } - } - } - - cdc_deinit_uart(uart_pins_prev); - free(rx_buffer); - furi_stream_buffer_free(app->rx_stream); - free(app); - - return 0; -} - -/***************************************************************************/ -/******************************* MAIN APP **********************************/ -/***************************************************************************/ - -static DapApp* dap_app_alloc() { - DapApp* dap_app = malloc(sizeof(DapApp)); - dap_app->dap_thread = furi_thread_alloc_ex("DAP Process", 1024, dap_process, dap_app); - dap_app->cdc_thread = furi_thread_alloc_ex("DAP CDC", 1024, cdc_process, dap_app); - dap_app->gui_thread = furi_thread_alloc_ex("DAP GUI", 1024, dap_gui_thread, dap_app); - return dap_app; -} - -static void dap_app_free(DapApp* dap_app) { - furi_assert(dap_app); - furi_thread_free(dap_app->dap_thread); - furi_thread_free(dap_app->cdc_thread); - furi_thread_free(dap_app->gui_thread); - free(dap_app); -} - -static DapApp* app_handle = NULL; - -void dap_app_disconnect() { - app_handle->state.dap_mode = DapModeDisconnected; -} - -void dap_app_connect_swd() { - app_handle->state.dap_mode = DapModeSWD; -} - -void dap_app_connect_jtag() { - app_handle->state.dap_mode = DapModeJTAG; -} - -void dap_app_set_config(DapApp* app, DapConfig* config) { - app->config = *config; - furi_thread_flags_set(furi_thread_get_id(app->dap_thread), DAPThreadEventApplyConfig); - furi_thread_flags_set(furi_thread_get_id(app->cdc_thread), CDCThreadEventApplyConfig); -} - -DapConfig* dap_app_get_config(DapApp* app) { - return &app->config; -} - -int32_t dap_link_app(void* p) { - UNUSED(p); - - if(furi_hal_usb_is_locked()) { - DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); - DialogMessage* message = dialog_message_alloc(); - dialog_message_set_header(message, "Connection\nis active!", 3, 2, AlignLeft, AlignTop); - dialog_message_set_text( - message, - "Disconnect from\nPC or phone to\nuse this function.", - 3, - 30, - AlignLeft, - AlignTop); - dialog_message_set_icon(message, &I_ActiveConnection_50x64, 78, 0); - dialog_message_show(dialogs, message); - dialog_message_free(message); - furi_record_close(RECORD_DIALOGS); - return -1; - } - - // alloc app - DapApp* app = dap_app_alloc(); - app_handle = app; - - furi_thread_start(app->dap_thread); - furi_thread_start(app->cdc_thread); - furi_thread_start(app->gui_thread); - - // wait until gui thread is finished - furi_thread_join(app->gui_thread); - - // send stop event to threads - dap_thread_send_stop(app->dap_thread); - dap_thread_send_stop(app->cdc_thread); - - // wait for threads to stop - furi_thread_join(app->dap_thread); - furi_thread_join(app->cdc_thread); - - // free app - dap_app_free(app); - - return 0; -} \ No newline at end of file diff --git a/applications/external/dap_link/dap_link.h b/applications/external/dap_link/dap_link.h deleted file mode 100644 index d51726c4587..00000000000 --- a/applications/external/dap_link/dap_link.h +++ /dev/null @@ -1,55 +0,0 @@ -#pragma once -#include - -typedef enum { - DapModeDisconnected, - DapModeSWD, - DapModeJTAG, -} DapMode; - -typedef enum { - DapVersionUnknown, - DapVersionV1, - DapVersionV2, -} DapVersion; - -typedef struct { - bool usb_connected; - DapMode dap_mode; - DapVersion dap_version; - uint32_t dap_counter; - uint32_t cdc_baudrate; - uint32_t cdc_tx_counter; - uint32_t cdc_rx_counter; -} DapState; - -typedef enum { - DapSwdPinsPA7PA6, // Pins 2, 3 - DapSwdPinsPA14PA13, // Pins 10, 12 -} DapSwdPins; - -typedef enum { - DapUartTypeUSART1, // Pins 13, 14 - DapUartTypeLPUART1, // Pins 15, 16 -} DapUartType; - -typedef enum { - DapUartTXRXNormal, - DapUartTXRXSwap, -} DapUartTXRX; - -typedef struct { - DapSwdPins swd_pins; - DapUartType uart_pins; - DapUartTXRX uart_swap; -} DapConfig; - -typedef struct DapApp DapApp; - -void dap_app_get_state(DapApp* app, DapState* state); - -const char* dap_app_get_serial(DapApp* app); - -void dap_app_set_config(DapApp* app, DapConfig* config); - -DapConfig* dap_app_get_config(DapApp* app); \ No newline at end of file diff --git a/applications/external/dap_link/dap_link.png b/applications/external/dap_link/dap_link.png deleted file mode 100644 index 2278ce2b61cf97e51720bafb2d07dae48d986560..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 143 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V8<6ZZI=>f4F%}28J29*~C-V}>@$__Y43U`H zI>D2R!GMF={pJ7tHnyBWw`O@WO=4vXnQOryxnPsm9sxmyCsV>A?tXRt_DY<&Wq)IO q>DB4a%~XFc{Gk)L^WR4G>Gvdb>|Y;BDpm&?%HZkh=d#Wzp$P!!VJ~6; diff --git a/applications/external/dap_link/gui/dap_gui.c b/applications/external/dap_link/gui/dap_gui.c deleted file mode 100644 index 4dd9861530f..00000000000 --- a/applications/external/dap_link/gui/dap_gui.c +++ /dev/null @@ -1,92 +0,0 @@ -#include "dap_gui.h" -#include "dap_gui_i.h" - -#define DAP_GUI_TICK 250 - -static bool dap_gui_custom_event_callback(void* context, uint32_t event) { - furi_assert(context); - DapGuiApp* app = context; - return scene_manager_handle_custom_event(app->scene_manager, event); -} - -static bool dap_gui_back_event_callback(void* context) { - furi_assert(context); - DapGuiApp* app = context; - return scene_manager_handle_back_event(app->scene_manager); -} - -static void dap_gui_tick_event_callback(void* context) { - furi_assert(context); - DapGuiApp* app = context; - scene_manager_handle_tick_event(app->scene_manager); -} - -DapGuiApp* dap_gui_alloc() { - DapGuiApp* app = malloc(sizeof(DapGuiApp)); - app->gui = furi_record_open(RECORD_GUI); - app->view_dispatcher = view_dispatcher_alloc(); - app->scene_manager = scene_manager_alloc(&dap_scene_handlers, app); - view_dispatcher_enable_queue(app->view_dispatcher); - view_dispatcher_set_event_callback_context(app->view_dispatcher, app); - - view_dispatcher_set_custom_event_callback(app->view_dispatcher, dap_gui_custom_event_callback); - view_dispatcher_set_navigation_event_callback( - app->view_dispatcher, dap_gui_back_event_callback); - view_dispatcher_set_tick_event_callback( - app->view_dispatcher, dap_gui_tick_event_callback, DAP_GUI_TICK); - - view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); - - app->notifications = furi_record_open(RECORD_NOTIFICATION); - - app->var_item_list = variable_item_list_alloc(); - view_dispatcher_add_view( - app->view_dispatcher, - DapGuiAppViewVarItemList, - variable_item_list_get_view(app->var_item_list)); - - app->main_view = dap_main_view_alloc(); - view_dispatcher_add_view( - app->view_dispatcher, DapGuiAppViewMainView, dap_main_view_get_view(app->main_view)); - - app->widget = widget_alloc(); - view_dispatcher_add_view( - app->view_dispatcher, DapGuiAppViewWidget, widget_get_view(app->widget)); - - scene_manager_next_scene(app->scene_manager, DapSceneMain); - - return app; -} - -void dap_gui_free(DapGuiApp* app) { - view_dispatcher_remove_view(app->view_dispatcher, DapGuiAppViewVarItemList); - variable_item_list_free(app->var_item_list); - - view_dispatcher_remove_view(app->view_dispatcher, DapGuiAppViewMainView); - dap_main_view_free(app->main_view); - - view_dispatcher_remove_view(app->view_dispatcher, DapGuiAppViewWidget); - widget_free(app->widget); - - // View dispatcher - view_dispatcher_free(app->view_dispatcher); - scene_manager_free(app->scene_manager); - - // Close records - furi_record_close(RECORD_GUI); - furi_record_close(RECORD_NOTIFICATION); - - free(app); -} - -int32_t dap_gui_thread(void* arg) { - DapGuiApp* app = dap_gui_alloc(); - app->dap_app = arg; - - notification_message_block(app->notifications, &sequence_display_backlight_enforce_on); - view_dispatcher_run(app->view_dispatcher); - notification_message_block(app->notifications, &sequence_display_backlight_enforce_auto); - - dap_gui_free(app); - return 0; -} \ No newline at end of file diff --git a/applications/external/dap_link/gui/dap_gui.h b/applications/external/dap_link/gui/dap_gui.h deleted file mode 100644 index 3d8e6bdf965..00000000000 --- a/applications/external/dap_link/gui/dap_gui.h +++ /dev/null @@ -1,4 +0,0 @@ -#pragma once -#include - -int32_t dap_gui_thread(void* arg); \ No newline at end of file diff --git a/applications/external/dap_link/gui/dap_gui_custom_event.h b/applications/external/dap_link/gui/dap_gui_custom_event.h deleted file mode 100644 index 8b127c9d4bc..00000000000 --- a/applications/external/dap_link/gui/dap_gui_custom_event.h +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once - -typedef enum { - DapAppCustomEventConfig, - DapAppCustomEventHelp, - DapAppCustomEventAbout, -} DapAppCustomEvent; diff --git a/applications/external/dap_link/gui/dap_gui_i.h b/applications/external/dap_link/gui/dap_gui_i.h deleted file mode 100644 index 59411e78c1a..00000000000 --- a/applications/external/dap_link/gui/dap_gui_i.h +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include - -#include "dap_gui.h" -#include "../dap_link.h" -#include "scenes/config/dap_scene.h" -#include "dap_gui_custom_event.h" -#include "views/dap_main_view.h" - -typedef struct { - DapApp* dap_app; - - Gui* gui; - NotificationApp* notifications; - ViewDispatcher* view_dispatcher; - SceneManager* scene_manager; - - VariableItemList* var_item_list; - DapMainView* main_view; - Widget* widget; -} DapGuiApp; - -typedef enum { - DapGuiAppViewVarItemList, - DapGuiAppViewMainView, - DapGuiAppViewWidget, -} DapGuiAppView; diff --git a/applications/external/dap_link/gui/scenes/config/dap_scene.c b/applications/external/dap_link/gui/scenes/config/dap_scene.c deleted file mode 100644 index 37e235540db..00000000000 --- a/applications/external/dap_link/gui/scenes/config/dap_scene.c +++ /dev/null @@ -1,30 +0,0 @@ -#include "dap_scene.h" - -// Generate scene on_enter handlers array -#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, -void (*const dap_scene_on_enter_handlers[])(void*) = { -#include "dap_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 dap_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = { -#include "dap_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 dap_scene_on_exit_handlers[])(void* context) = { -#include "dap_scene_config.h" -}; -#undef ADD_SCENE - -// Initialize scene handlers configuration structure -const SceneManagerHandlers dap_scene_handlers = { - .on_enter_handlers = dap_scene_on_enter_handlers, - .on_event_handlers = dap_scene_on_event_handlers, - .on_exit_handlers = dap_scene_on_exit_handlers, - .scene_num = DapSceneNum, -}; diff --git a/applications/external/dap_link/gui/scenes/config/dap_scene.h b/applications/external/dap_link/gui/scenes/config/dap_scene.h deleted file mode 100644 index 6fb38da4abe..00000000000 --- a/applications/external/dap_link/gui/scenes/config/dap_scene.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -#include - -// Generate scene id and total number -#define ADD_SCENE(prefix, name, id) DapScene##id, -typedef enum { -#include "dap_scene_config.h" - DapSceneNum, -} DapScene; -#undef ADD_SCENE - -extern const SceneManagerHandlers dap_scene_handlers; - -// Generate scene on_enter handlers declaration -#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); -#include "dap_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 "dap_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 "dap_scene_config.h" -#undef ADD_SCENE diff --git a/applications/external/dap_link/gui/scenes/config/dap_scene_config.h b/applications/external/dap_link/gui/scenes/config/dap_scene_config.h deleted file mode 100644 index 8957aca068e..00000000000 --- a/applications/external/dap_link/gui/scenes/config/dap_scene_config.h +++ /dev/null @@ -1,4 +0,0 @@ -ADD_SCENE(dap, main, Main) -ADD_SCENE(dap, config, Config) -ADD_SCENE(dap, help, Help) -ADD_SCENE(dap, about, About) \ No newline at end of file diff --git a/applications/external/dap_link/gui/scenes/dap_scene_about.c b/applications/external/dap_link/gui/scenes/dap_scene_about.c deleted file mode 100644 index 0974e60a7fb..00000000000 --- a/applications/external/dap_link/gui/scenes/dap_scene_about.c +++ /dev/null @@ -1,68 +0,0 @@ -#include "../dap_gui_i.h" - -#define DAP_VERSION_APP "0.1.0" -#define DAP_DEVELOPED "Dr_Zlo" -#define DAP_GITHUB "https://github.com/flipperdevices/flipperzero-firmware" - -void dap_scene_about_on_enter(void* context) { - DapGuiApp* app = context; - - FuriString* temp_str; - temp_str = furi_string_alloc(); - furi_string_printf(temp_str, "\e#%s\n", "Information"); - - furi_string_cat_printf(temp_str, "Version: %s\n", DAP_VERSION_APP); - furi_string_cat_printf(temp_str, "Developed by: %s\n", DAP_DEVELOPED); - furi_string_cat_printf(temp_str, "Github: %s\n\n", DAP_GITHUB); - - furi_string_cat_printf(temp_str, "\e#%s\n", "Description"); - furi_string_cat_printf( - temp_str, "CMSIS-DAP debugger\nbased on Free-DAP\nThanks to Alex Taradov\n\n"); - - furi_string_cat_printf( - temp_str, - "Supported protocols:\n" - "SWD, JTAG, UART\n" - "DAP v1 (cmsis_backend hid), DAP v2 (cmsis_backend usb_bulk), VCP\n"); - - widget_add_text_box_element( - app->widget, - 0, - 0, - 128, - 14, - AlignCenter, - AlignBottom, - "\e#\e! \e!\n", - false); - widget_add_text_box_element( - app->widget, - 0, - 2, - 128, - 14, - AlignCenter, - AlignBottom, - "\e#\e! DAP Link \e!\n", - false); - widget_add_text_scroll_element(app->widget, 0, 16, 128, 50, furi_string_get_cstr(temp_str)); - furi_string_free(temp_str); - - view_dispatcher_switch_to_view(app->view_dispatcher, DapGuiAppViewWidget); -} - -bool dap_scene_about_on_event(void* context, SceneManagerEvent event) { - DapGuiApp* app = context; - bool consumed = false; - UNUSED(app); - UNUSED(event); - - return consumed; -} - -void dap_scene_about_on_exit(void* context) { - DapGuiApp* app = context; - - // Clear views - widget_reset(app->widget); -} diff --git a/applications/external/dap_link/gui/scenes/dap_scene_config.c b/applications/external/dap_link/gui/scenes/dap_scene_config.c deleted file mode 100644 index 48d5fedcd08..00000000000 --- a/applications/external/dap_link/gui/scenes/dap_scene_config.c +++ /dev/null @@ -1,107 +0,0 @@ -#include "../dap_gui_i.h" - -static const char* swd_pins[] = {[DapSwdPinsPA7PA6] = "2,3", [DapSwdPinsPA14PA13] = "10,12"}; -static const char* uart_pins[] = {[DapUartTypeUSART1] = "13,14", [DapUartTypeLPUART1] = "15,16"}; -static const char* uart_swap[] = {[DapUartTXRXNormal] = "No", [DapUartTXRXSwap] = "Yes"}; - -static void swd_pins_cb(VariableItem* item) { - DapGuiApp* app = variable_item_get_context(item); - uint8_t index = variable_item_get_current_value_index(item); - - variable_item_set_current_value_text(item, swd_pins[index]); - - DapConfig* config = dap_app_get_config(app->dap_app); - config->swd_pins = index; - dap_app_set_config(app->dap_app, config); -} - -static void uart_pins_cb(VariableItem* item) { - DapGuiApp* app = variable_item_get_context(item); - uint8_t index = variable_item_get_current_value_index(item); - - variable_item_set_current_value_text(item, uart_pins[index]); - - DapConfig* config = dap_app_get_config(app->dap_app); - config->uart_pins = index; - dap_app_set_config(app->dap_app, config); -} - -static void uart_swap_cb(VariableItem* item) { - DapGuiApp* app = variable_item_get_context(item); - uint8_t index = variable_item_get_current_value_index(item); - - variable_item_set_current_value_text(item, uart_swap[index]); - - DapConfig* config = dap_app_get_config(app->dap_app); - config->uart_swap = index; - dap_app_set_config(app->dap_app, config); -} - -static void ok_cb(void* context, uint32_t index) { - DapGuiApp* app = context; - switch(index) { - case 3: - view_dispatcher_send_custom_event(app->view_dispatcher, DapAppCustomEventHelp); - break; - case 4: - view_dispatcher_send_custom_event(app->view_dispatcher, DapAppCustomEventAbout); - break; - default: - break; - } -} - -void dap_scene_config_on_enter(void* context) { - DapGuiApp* app = context; - VariableItemList* var_item_list = app->var_item_list; - VariableItem* item; - DapConfig* config = dap_app_get_config(app->dap_app); - - item = variable_item_list_add( - var_item_list, "SWC SWD Pins", COUNT_OF(swd_pins), swd_pins_cb, app); - variable_item_set_current_value_index(item, config->swd_pins); - variable_item_set_current_value_text(item, swd_pins[config->swd_pins]); - - item = - variable_item_list_add(var_item_list, "UART Pins", COUNT_OF(uart_pins), uart_pins_cb, app); - variable_item_set_current_value_index(item, config->uart_pins); - variable_item_set_current_value_text(item, uart_pins[config->uart_pins]); - - item = variable_item_list_add( - var_item_list, "Swap TX RX", COUNT_OF(uart_swap), uart_swap_cb, app); - variable_item_set_current_value_index(item, config->uart_swap); - variable_item_set_current_value_text(item, uart_swap[config->uart_swap]); - - variable_item_list_add(var_item_list, "Help and Pinout", 0, NULL, NULL); - variable_item_list_add(var_item_list, "About", 0, NULL, NULL); - - variable_item_list_set_selected_item( - var_item_list, scene_manager_get_scene_state(app->scene_manager, DapSceneConfig)); - - variable_item_list_set_enter_callback(var_item_list, ok_cb, app); - - view_dispatcher_switch_to_view(app->view_dispatcher, DapGuiAppViewVarItemList); -} - -bool dap_scene_config_on_event(void* context, SceneManagerEvent event) { - DapGuiApp* app = context; - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == DapAppCustomEventHelp) { - scene_manager_next_scene(app->scene_manager, DapSceneHelp); - return true; - } else if(event.event == DapAppCustomEventAbout) { - scene_manager_next_scene(app->scene_manager, DapSceneAbout); - return true; - } - } - return false; -} - -void dap_scene_config_on_exit(void* context) { - DapGuiApp* app = context; - scene_manager_set_scene_state( - app->scene_manager, - DapSceneConfig, - variable_item_list_get_selected_item_index(app->var_item_list)); - variable_item_list_reset(app->var_item_list); -} \ No newline at end of file diff --git a/applications/external/dap_link/gui/scenes/dap_scene_help.c b/applications/external/dap_link/gui/scenes/dap_scene_help.c deleted file mode 100644 index d8d70e7ff29..00000000000 --- a/applications/external/dap_link/gui/scenes/dap_scene_help.c +++ /dev/null @@ -1,102 +0,0 @@ -#include "../dap_gui_i.h" - -void dap_scene_help_on_enter(void* context) { - DapGuiApp* app = context; - DapConfig* config = dap_app_get_config(app->dap_app); - FuriString* string = furi_string_alloc(); - - furi_string_cat(string, "CMSIS DAP/DAP Link v2\r\n"); - furi_string_cat_printf(string, "Serial: %s\r\n", dap_app_get_serial(app->dap_app)); - furi_string_cat( - string, - "Pinout:\r\n" - "\e#SWD:\r\n"); - - switch(config->swd_pins) { - case DapSwdPinsPA7PA6: - furi_string_cat( - string, - " SWC: 2 [A7]\r\n" - " SWD: 3 [A6]\r\n"); - break; - case DapSwdPinsPA14PA13: - furi_string_cat( - string, - " SWC: 10 [SWC]\r\n" - " SWD: 12 [SIO]\r\n"); - break; - default: - break; - } - - furi_string_cat(string, "\e#JTAG:\r\n"); - switch(config->swd_pins) { - case DapSwdPinsPA7PA6: - furi_string_cat( - string, - " TCK: 2 [A7]\r\n" - " TMS: 3 [A6]\r\n" - " RST: 4 [A4]\r\n" - " TDO: 5 [B3]\r\n" - " TDI: 6 [B2]\r\n"); - break; - case DapSwdPinsPA14PA13: - furi_string_cat( - string, - " RST: 4 [A4]\r\n" - " TDO: 5 [B3]\r\n" - " TDI: 6 [B2]\r\n" - " TCK: 10 [SWC]\r\n" - " TMS: 12 [SIO]\r\n"); - break; - default: - break; - } - - furi_string_cat(string, "\e#UART:\r\n"); - switch(config->uart_pins) { - case DapUartTypeUSART1: - if(config->uart_swap == DapUartTXRXNormal) { - furi_string_cat( - string, - " TX: 13 [TX]\r\n" - " RX: 14 [RX]\r\n"); - } else { - furi_string_cat( - string, - " RX: 13 [TX]\r\n" - " TX: 14 [RX]\r\n"); - } - break; - case DapUartTypeLPUART1: - if(config->uart_swap == DapUartTXRXNormal) { - furi_string_cat( - string, - " TX: 15 [C1]\r\n" - " RX: 16 [C0]\r\n"); - } else { - furi_string_cat( - string, - " RX: 15 [C1]\r\n" - " TX: 16 [C0]\r\n"); - } - break; - default: - break; - } - - widget_add_text_scroll_element(app->widget, 0, 0, 128, 64, furi_string_get_cstr(string)); - furi_string_free(string); - view_dispatcher_switch_to_view(app->view_dispatcher, DapGuiAppViewWidget); -} - -bool dap_scene_help_on_event(void* context, SceneManagerEvent event) { - UNUSED(context); - UNUSED(event); - return false; -} - -void dap_scene_help_on_exit(void* context) { - DapGuiApp* app = context; - widget_reset(app->widget); -} \ No newline at end of file diff --git a/applications/external/dap_link/gui/scenes/dap_scene_main.c b/applications/external/dap_link/gui/scenes/dap_scene_main.c deleted file mode 100644 index 8c19bd6a5d5..00000000000 --- a/applications/external/dap_link/gui/scenes/dap_scene_main.c +++ /dev/null @@ -1,154 +0,0 @@ -#include "../dap_gui_i.h" -#include "../../dap_link.h" - -typedef struct { - DapState dap_state; - bool dap_active; - bool tx_active; - bool rx_active; -} DapSceneMainState; - -static bool process_dap_state(DapGuiApp* app) { - DapSceneMainState* state = - (DapSceneMainState*)scene_manager_get_scene_state(app->scene_manager, DapSceneMain); - if(state == NULL) return true; - - DapState* prev_state = &state->dap_state; - DapState next_state; - dap_app_get_state(app->dap_app, &next_state); - bool need_to_update = false; - - if(prev_state->dap_mode != next_state.dap_mode) { - switch(next_state.dap_mode) { - case DapModeDisconnected: - dap_main_view_set_mode(app->main_view, DapMainViewModeDisconnected); - notification_message(app->notifications, &sequence_blink_stop); - break; - case DapModeSWD: - dap_main_view_set_mode(app->main_view, DapMainViewModeSWD); - notification_message(app->notifications, &sequence_blink_start_blue); - break; - case DapModeJTAG: - dap_main_view_set_mode(app->main_view, DapMainViewModeJTAG); - notification_message(app->notifications, &sequence_blink_start_magenta); - break; - } - need_to_update = true; - } - - if(prev_state->dap_version != next_state.dap_version) { - switch(next_state.dap_version) { - case DapVersionUnknown: - dap_main_view_set_version(app->main_view, DapMainViewVersionUnknown); - break; - case DapVersionV1: - dap_main_view_set_version(app->main_view, DapMainViewVersionV1); - break; - case DapVersionV2: - dap_main_view_set_version(app->main_view, DapMainViewVersionV2); - break; - } - need_to_update = true; - } - - if(prev_state->usb_connected != next_state.usb_connected) { - dap_main_view_set_usb_connected(app->main_view, next_state.usb_connected); - need_to_update = true; - } - - if(prev_state->dap_counter != next_state.dap_counter) { - if(!state->dap_active) { - state->dap_active = true; - dap_main_view_set_dap(app->main_view, state->dap_active); - need_to_update = true; - } - } else { - if(state->dap_active) { - state->dap_active = false; - dap_main_view_set_dap(app->main_view, state->dap_active); - need_to_update = true; - } - } - - if(prev_state->cdc_baudrate != next_state.cdc_baudrate) { - dap_main_view_set_baudrate(app->main_view, next_state.cdc_baudrate); - need_to_update = true; - } - - if(prev_state->cdc_tx_counter != next_state.cdc_tx_counter) { - if(!state->tx_active) { - state->tx_active = true; - dap_main_view_set_tx(app->main_view, state->tx_active); - need_to_update = true; - notification_message(app->notifications, &sequence_blink_start_red); - } - } else { - if(state->tx_active) { - state->tx_active = false; - dap_main_view_set_tx(app->main_view, state->tx_active); - need_to_update = true; - notification_message(app->notifications, &sequence_blink_stop); - } - } - - if(prev_state->cdc_rx_counter != next_state.cdc_rx_counter) { - if(!state->rx_active) { - state->rx_active = true; - dap_main_view_set_rx(app->main_view, state->rx_active); - need_to_update = true; - notification_message(app->notifications, &sequence_blink_start_green); - } - } else { - if(state->rx_active) { - state->rx_active = false; - dap_main_view_set_rx(app->main_view, state->rx_active); - need_to_update = true; - notification_message(app->notifications, &sequence_blink_stop); - } - } - - if(need_to_update) { - dap_main_view_update(app->main_view); - } - - *prev_state = next_state; - return true; -} - -static void dap_scene_main_on_left(void* context) { - DapGuiApp* app = (DapGuiApp*)context; - view_dispatcher_send_custom_event(app->view_dispatcher, DapAppCustomEventConfig); -} - -void dap_scene_main_on_enter(void* context) { - DapGuiApp* app = context; - DapSceneMainState* state = malloc(sizeof(DapSceneMainState)); - dap_main_view_set_left_callback(app->main_view, dap_scene_main_on_left, app); - view_dispatcher_switch_to_view(app->view_dispatcher, DapGuiAppViewMainView); - scene_manager_set_scene_state(app->scene_manager, DapSceneMain, (uint32_t)state); -} - -bool dap_scene_main_on_event(void* context, SceneManagerEvent event) { - DapGuiApp* app = context; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == DapAppCustomEventConfig) { - scene_manager_next_scene(app->scene_manager, DapSceneConfig); - return true; - } - } else if(event.type == SceneManagerEventTypeTick) { - return process_dap_state(app); - } - - return false; -} - -void dap_scene_main_on_exit(void* context) { - DapGuiApp* app = context; - DapSceneMainState* state = - (DapSceneMainState*)scene_manager_get_scene_state(app->scene_manager, DapSceneMain); - scene_manager_set_scene_state(app->scene_manager, DapSceneMain, (uint32_t)NULL); - FURI_SW_MEMBARRIER(); - free(state); - notification_message(app->notifications, &sequence_blink_stop); -} \ No newline at end of file diff --git a/applications/external/dap_link/gui/views/dap_main_view.c b/applications/external/dap_link/gui/views/dap_main_view.c deleted file mode 100644 index f54c5e3d582..00000000000 --- a/applications/external/dap_link/gui/views/dap_main_view.c +++ /dev/null @@ -1,189 +0,0 @@ -#include "dap_main_view.h" -#include "dap_link_icons.h" -#include - -// extern const Icon I_ArrowDownEmpty_12x18; -// extern const Icon I_ArrowDownFilled_12x18; -// extern const Icon I_ArrowUpEmpty_12x18; -// extern const Icon I_ArrowUpFilled_12x18; - -struct DapMainView { - View* view; - DapMainViewButtonCallback cb_left; - void* cb_context; -}; - -typedef struct { - DapMainViewMode mode; - DapMainViewVersion version; - bool usb_connected; - uint32_t baudrate; - bool dap_active; - bool tx_active; - bool rx_active; -} DapMainViewModel; - -static void dap_main_view_draw_callback(Canvas* canvas, void* _model) { - DapMainViewModel* model = _model; - UNUSED(model); - canvas_clear(canvas); - elements_button_left(canvas, "Config"); - - canvas_set_color(canvas, ColorBlack); - canvas_draw_box(canvas, 0, 0, 127, 11); - canvas_set_color(canvas, ColorWhite); - - const char* header_string; - if(model->usb_connected) { - if(model->version == DapMainViewVersionV1) { - header_string = "DAP Link V1 Connected"; - } else if(model->version == DapMainViewVersionV2) { - header_string = "DAP Link V2 Connected"; - } else { - header_string = "DAP Link Connected"; - } - } else { - header_string = "DAP Link"; - } - - canvas_draw_str_aligned(canvas, 64, 9, AlignCenter, AlignBottom, header_string); - - canvas_set_color(canvas, ColorBlack); - if(model->dap_active) { - canvas_draw_icon(canvas, 14, 16, &I_ArrowUpFilled_12x18); - canvas_draw_icon_ex(canvas, 28, 16, &I_ArrowUpFilled_12x18, IconRotation180); - } else { - canvas_draw_icon(canvas, 14, 16, &I_ArrowUpEmpty_12x18); - canvas_draw_icon_ex(canvas, 28, 16, &I_ArrowUpEmpty_12x18, IconRotation180); - } - - switch(model->mode) { - case DapMainViewModeDisconnected: - canvas_draw_str_aligned(canvas, 26, 38, AlignCenter, AlignTop, "----"); - break; - case DapMainViewModeSWD: - canvas_draw_str_aligned(canvas, 26, 38, AlignCenter, AlignTop, "SWD"); - break; - case DapMainViewModeJTAG: - canvas_draw_str_aligned(canvas, 26, 38, AlignCenter, AlignTop, "JTAG"); - break; - } - - if(model->tx_active) { - canvas_draw_icon(canvas, 87, 16, &I_ArrowUpFilled_12x18); - } else { - canvas_draw_icon(canvas, 87, 16, &I_ArrowUpEmpty_12x18); - } - - if(model->rx_active) { - canvas_draw_icon_ex(canvas, 101, 16, &I_ArrowUpFilled_12x18, IconRotation180); - } else { - canvas_draw_icon_ex(canvas, 101, 16, &I_ArrowUpEmpty_12x18, IconRotation180); - } - - canvas_draw_str_aligned(canvas, 100, 38, AlignCenter, AlignTop, "UART"); - - canvas_draw_line(canvas, 44, 52, 123, 52); - if(model->baudrate == 0) { - canvas_draw_str(canvas, 45, 62, "Baud: ????"); - } else { - char baudrate_str[18]; - snprintf(baudrate_str, 18, "Baud: %lu", model->baudrate); - canvas_draw_str(canvas, 45, 62, baudrate_str); - } -} - -static bool dap_main_view_input_callback(InputEvent* event, void* context) { - furi_assert(context); - DapMainView* dap_main_view = context; - bool consumed = false; - - if(event->type == InputTypeShort) { - if(event->key == InputKeyLeft) { - if(dap_main_view->cb_left) { - dap_main_view->cb_left(dap_main_view->cb_context); - } - consumed = true; - } - } - - return consumed; -} - -DapMainView* dap_main_view_alloc() { - DapMainView* dap_main_view = malloc(sizeof(DapMainView)); - - dap_main_view->view = view_alloc(); - view_allocate_model(dap_main_view->view, ViewModelTypeLocking, sizeof(DapMainViewModel)); - view_set_context(dap_main_view->view, dap_main_view); - view_set_draw_callback(dap_main_view->view, dap_main_view_draw_callback); - view_set_input_callback(dap_main_view->view, dap_main_view_input_callback); - return dap_main_view; -} - -void dap_main_view_free(DapMainView* dap_main_view) { - view_free(dap_main_view->view); - free(dap_main_view); -} - -View* dap_main_view_get_view(DapMainView* dap_main_view) { - return dap_main_view->view; -} - -void dap_main_view_set_left_callback( - DapMainView* dap_main_view, - DapMainViewButtonCallback callback, - void* context) { - with_view_model( - dap_main_view->view, - DapMainViewModel * model, - { - UNUSED(model); - dap_main_view->cb_left = callback; - dap_main_view->cb_context = context; - }, - true); -} - -void dap_main_view_set_mode(DapMainView* dap_main_view, DapMainViewMode mode) { - with_view_model( - dap_main_view->view, DapMainViewModel * model, { model->mode = mode; }, false); -} - -void dap_main_view_set_dap(DapMainView* dap_main_view, bool active) { - with_view_model( - dap_main_view->view, DapMainViewModel * model, { model->dap_active = active; }, false); -} - -void dap_main_view_set_tx(DapMainView* dap_main_view, bool active) { - with_view_model( - dap_main_view->view, DapMainViewModel * model, { model->tx_active = active; }, false); -} - -void dap_main_view_set_rx(DapMainView* dap_main_view, bool active) { - with_view_model( - dap_main_view->view, DapMainViewModel * model, { model->rx_active = active; }, false); -} - -void dap_main_view_set_baudrate(DapMainView* dap_main_view, uint32_t baudrate) { - with_view_model( - dap_main_view->view, DapMainViewModel * model, { model->baudrate = baudrate; }, false); -} - -void dap_main_view_update(DapMainView* dap_main_view) { - with_view_model( - dap_main_view->view, DapMainViewModel * model, { UNUSED(model); }, true); -} - -void dap_main_view_set_version(DapMainView* dap_main_view, DapMainViewVersion version) { - with_view_model( - dap_main_view->view, DapMainViewModel * model, { model->version = version; }, false); -} - -void dap_main_view_set_usb_connected(DapMainView* dap_main_view, bool connected) { - with_view_model( - dap_main_view->view, - DapMainViewModel * model, - { model->usb_connected = connected; }, - false); -} \ No newline at end of file diff --git a/applications/external/dap_link/gui/views/dap_main_view.h b/applications/external/dap_link/gui/views/dap_main_view.h deleted file mode 100644 index 1fd90045279..00000000000 --- a/applications/external/dap_link/gui/views/dap_main_view.h +++ /dev/null @@ -1,45 +0,0 @@ -#pragma once -#include - -typedef struct DapMainView DapMainView; - -typedef void (*DapMainViewButtonCallback)(void* context); - -typedef enum { - DapMainViewVersionUnknown, - DapMainViewVersionV1, - DapMainViewVersionV2, -} DapMainViewVersion; - -typedef enum { - DapMainViewModeDisconnected, - DapMainViewModeSWD, - DapMainViewModeJTAG, -} DapMainViewMode; - -DapMainView* dap_main_view_alloc(); - -void dap_main_view_free(DapMainView* dap_main_view); - -View* dap_main_view_get_view(DapMainView* dap_main_view); - -void dap_main_view_set_left_callback( - DapMainView* dap_main_view, - DapMainViewButtonCallback callback, - void* context); - -void dap_main_view_set_mode(DapMainView* dap_main_view, DapMainViewMode mode); - -void dap_main_view_set_version(DapMainView* dap_main_view, DapMainViewVersion version); - -void dap_main_view_set_dap(DapMainView* dap_main_view, bool active); - -void dap_main_view_set_tx(DapMainView* dap_main_view, bool active); - -void dap_main_view_set_rx(DapMainView* dap_main_view, bool active); - -void dap_main_view_set_usb_connected(DapMainView* dap_main_view, bool connected); - -void dap_main_view_set_baudrate(DapMainView* dap_main_view, uint32_t baudrate); - -void dap_main_view_update(DapMainView* dap_main_view); \ No newline at end of file diff --git a/applications/external/dap_link/icons/ActiveConnection_50x64.png b/applications/external/dap_link/icons/ActiveConnection_50x64.png deleted file mode 100644 index 1d7686dddf8a33b724c7528ed36435514b7518b2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3842 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 G5}E*=05`}0 diff --git a/applications/external/dap_link/icons/ArrowUpFilled_12x18.png b/applications/external/dap_link/icons/ArrowUpFilled_12x18.png deleted file mode 100644 index dc481517ebae2d875880d96c3a6863c9ca985690..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 173 zcmeAS@N?(olHy`uVBq!ia0vp^JU}eO!3HGrSK5O(jKx9jP7LeL$-D$|(mh=qLo_Cy zop_M%fC7he^yB~ki^>fomK}Y&?Q6<{^IZXxHgu>k9N2PUPNa~E?*^7bKA+0AH5l)R zS}i4d#BjFRWa(K?7>YJFIonF7y_qz@_V#wWmAe;3-!N4C_Ce~$H&1(?8x60XU0}74 WwRe~lwXYaxC4;A{pUXO@geCw-ib4GV diff --git a/applications/external/dap_link/lib/free-dap b/applications/external/dap_link/lib/free-dap deleted file mode 160000 index e7752beb5e8..00000000000 --- a/applications/external/dap_link/lib/free-dap +++ /dev/null @@ -1 +0,0 @@ -Subproject commit e7752beb5e8a69119af67b70b9179cb3c90f3ac5 diff --git a/applications/external/dap_link/usb/dap_v2_usb.c b/applications/external/dap_link/usb/dap_v2_usb.c deleted file mode 100644 index cba786648f7..00000000000 --- a/applications/external/dap_link/usb/dap_v2_usb.c +++ /dev/null @@ -1,977 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#include "dap_v2_usb.h" - -// #define DAP_USB_LOG - -#define HID_EP_IN 0x80 -#define HID_EP_OUT 0x00 - -#define DAP_HID_EP_SEND 1 -#define DAP_HID_EP_RECV 2 -#define DAP_HID_EP_BULK_RECV 3 -#define DAP_HID_EP_BULK_SEND 4 -#define DAP_CDC_EP_COMM 5 -#define DAP_CDC_EP_SEND 6 -#define DAP_CDC_EP_RECV 7 - -#define DAP_HID_EP_IN (HID_EP_IN | DAP_HID_EP_SEND) -#define DAP_HID_EP_OUT (HID_EP_OUT | DAP_HID_EP_RECV) -#define DAP_HID_EP_BULK_IN (HID_EP_IN | DAP_HID_EP_BULK_SEND) -#define DAP_HID_EP_BULK_OUT (HID_EP_OUT | DAP_HID_EP_BULK_RECV) - -#define DAP_HID_EP_SIZE 64 -#define DAP_CDC_COMM_EP_SIZE 8 -#define DAP_CDC_EP_SIZE 64 - -#define DAP_BULK_INTERVAL 0 -#define DAP_HID_INTERVAL 1 -#define DAP_CDC_INTERVAL 0 -#define DAP_CDC_COMM_INTERVAL 1 - -#define DAP_HID_VID 0x0483 -#define DAP_HID_PID 0x5740 - -#define DAP_USB_EP0_SIZE 8 - -#define EP_CFG_DECONFIGURE 0 -#define EP_CFG_CONFIGURE 1 - -enum { - USB_INTF_HID, - USB_INTF_BULK, - USB_INTF_CDC_COMM, - USB_INTF_CDC_DATA, - USB_INTF_COUNT, -}; - -enum { - USB_STR_ZERO, - USB_STR_MANUFACTURER, - USB_STR_PRODUCT, - USB_STR_SERIAL_NUMBER, - USB_STR_CMSIS_DAP_V1, - USB_STR_CMSIS_DAP_V2, - USB_STR_COM_PORT, - USB_STR_COUNT, -}; - -// static const char* usb_str[] = { -// [USB_STR_MANUFACTURER] = "Flipper Devices Inc.", -// [USB_STR_PRODUCT] = "Combined VCP and CMSIS-DAP Adapter", -// [USB_STR_COM_PORT] = "Virtual COM-Port", -// [USB_STR_CMSIS_DAP_V1] = "CMSIS-DAP v1 Adapter", -// [USB_STR_CMSIS_DAP_V2] = "CMSIS-DAP v2 Adapter", -// [USB_STR_SERIAL_NUMBER] = "01234567890ABCDEF", -// }; - -static const struct usb_string_descriptor dev_manuf_descr = - USB_STRING_DESC("Flipper Devices Inc."); - -static const struct usb_string_descriptor dev_prod_descr = - USB_STRING_DESC("Combined VCP and CMSIS-DAP Adapter"); - -static struct usb_string_descriptor* dev_serial_descr = NULL; - -static const struct usb_string_descriptor dev_dap_v1_descr = - USB_STRING_DESC("CMSIS-DAP v1 Adapter"); - -static const struct usb_string_descriptor dev_dap_v2_descr = - USB_STRING_DESC("CMSIS-DAP v2 Adapter"); - -static const struct usb_string_descriptor dev_com_descr = USB_STRING_DESC("Virtual COM-Port"); - -struct HidConfigDescriptor { - struct usb_config_descriptor configuration; - - // CMSIS-DAP v1 - struct usb_interface_descriptor hid_interface; - struct usb_hid_descriptor hid; - struct usb_endpoint_descriptor hid_ep_in; - struct usb_endpoint_descriptor hid_ep_out; - - // CMSIS-DAP v2 - struct usb_interface_descriptor bulk_interface; - struct usb_endpoint_descriptor bulk_ep_out; - struct usb_endpoint_descriptor bulk_ep_in; - - // CDC - struct usb_iad_descriptor iad; - struct usb_interface_descriptor interface_comm; - struct usb_cdc_header_desc cdc_header; - struct usb_cdc_call_mgmt_desc cdc_acm; - struct usb_cdc_acm_desc cdc_call_mgmt; - struct usb_cdc_union_desc cdc_union; - struct usb_endpoint_descriptor ep_comm; - struct usb_interface_descriptor interface_data; - struct usb_endpoint_descriptor ep_in; - struct usb_endpoint_descriptor ep_out; - -} __attribute__((packed)); - -static const struct usb_device_descriptor hid_device_desc = { - .bLength = sizeof(struct usb_device_descriptor), - .bDescriptorType = USB_DTYPE_DEVICE, - .bcdUSB = VERSION_BCD(2, 1, 0), - .bDeviceClass = USB_CLASS_MISC, - .bDeviceSubClass = USB_SUBCLASS_IAD, - .bDeviceProtocol = USB_PROTO_IAD, - .bMaxPacketSize0 = DAP_USB_EP0_SIZE, - .idVendor = DAP_HID_VID, - .idProduct = DAP_HID_PID, - .bcdDevice = VERSION_BCD(1, 0, 0), - .iManufacturer = USB_STR_MANUFACTURER, - .iProduct = USB_STR_PRODUCT, - .iSerialNumber = USB_STR_SERIAL_NUMBER, - .bNumConfigurations = 1, -}; - -static const uint8_t hid_report_desc[] = { - 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) - 0x09, 0x00, // Usage (Undefined) - 0xa1, 0x01, // Collection (Application) - 0x15, 0x00, // Logical Minimum (0) - 0x26, 0xff, 0x00, // Logical Maximum (255) - 0x75, 0x08, // Report Size (8) - 0x95, 0x40, // Report Count (64) - 0x09, 0x00, // Usage (Undefined) - 0x81, 0x82, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) - 0x75, 0x08, // Report Size (8) - 0x95, 0x40, // Report Count (64) - 0x09, 0x00, // Usage (Undefined) - 0x91, 0x82, // Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Volatile) - 0xc0, // End Collection -}; - -static const struct HidConfigDescriptor hid_cfg_desc = { - .configuration = - { - .bLength = sizeof(struct usb_config_descriptor), - .bDescriptorType = USB_DTYPE_CONFIGURATION, - .wTotalLength = sizeof(struct HidConfigDescriptor), - .bNumInterfaces = USB_INTF_COUNT, - .bConfigurationValue = 1, - .iConfiguration = NO_DESCRIPTOR, - .bmAttributes = USB_CFG_ATTR_RESERVED, - .bMaxPower = USB_CFG_POWER_MA(500), - }, - - // CMSIS-DAP v1 - .hid_interface = - { - .bLength = sizeof(struct usb_interface_descriptor), - .bDescriptorType = USB_DTYPE_INTERFACE, - .bInterfaceNumber = USB_INTF_HID, - .bAlternateSetting = 0, - .bNumEndpoints = 2, - .bInterfaceClass = USB_CLASS_HID, - .bInterfaceSubClass = USB_HID_SUBCLASS_NONBOOT, - .bInterfaceProtocol = USB_HID_PROTO_NONBOOT, - .iInterface = USB_STR_CMSIS_DAP_V1, - }, - - .hid = - { - .bLength = sizeof(struct usb_hid_descriptor), - .bDescriptorType = USB_DTYPE_HID, - .bcdHID = VERSION_BCD(1, 1, 1), - .bCountryCode = USB_HID_COUNTRY_NONE, - .bNumDescriptors = 1, - .bDescriptorType0 = USB_DTYPE_HID_REPORT, - .wDescriptorLength0 = sizeof(hid_report_desc), - }, - - .hid_ep_in = - { - .bLength = sizeof(struct usb_endpoint_descriptor), - .bDescriptorType = USB_DTYPE_ENDPOINT, - .bEndpointAddress = DAP_HID_EP_IN, - .bmAttributes = USB_EPTYPE_INTERRUPT, - .wMaxPacketSize = DAP_HID_EP_SIZE, - .bInterval = DAP_HID_INTERVAL, - }, - - .hid_ep_out = - { - .bLength = sizeof(struct usb_endpoint_descriptor), - .bDescriptorType = USB_DTYPE_ENDPOINT, - .bEndpointAddress = DAP_HID_EP_OUT, - .bmAttributes = USB_EPTYPE_INTERRUPT, - .wMaxPacketSize = DAP_HID_EP_SIZE, - .bInterval = DAP_HID_INTERVAL, - }, - - // CMSIS-DAP v2 - .bulk_interface = - { - .bLength = sizeof(struct usb_interface_descriptor), - .bDescriptorType = USB_DTYPE_INTERFACE, - .bInterfaceNumber = USB_INTF_BULK, - .bAlternateSetting = 0, - .bNumEndpoints = 2, - .bInterfaceClass = USB_CLASS_VENDOR, - .bInterfaceSubClass = 0, - .bInterfaceProtocol = 0, - .iInterface = USB_STR_CMSIS_DAP_V2, - }, - - .bulk_ep_out = - { - .bLength = sizeof(struct usb_endpoint_descriptor), - .bDescriptorType = USB_DTYPE_ENDPOINT, - .bEndpointAddress = DAP_HID_EP_BULK_OUT, - .bmAttributes = USB_EPTYPE_BULK, - .wMaxPacketSize = DAP_HID_EP_SIZE, - .bInterval = DAP_BULK_INTERVAL, - }, - - .bulk_ep_in = - { - .bLength = sizeof(struct usb_endpoint_descriptor), - .bDescriptorType = USB_DTYPE_ENDPOINT, - .bEndpointAddress = DAP_HID_EP_BULK_IN, - .bmAttributes = USB_EPTYPE_BULK, - .wMaxPacketSize = DAP_HID_EP_SIZE, - .bInterval = DAP_BULK_INTERVAL, - }, - - // CDC - .iad = - { - .bLength = sizeof(struct usb_iad_descriptor), - .bDescriptorType = USB_DTYPE_INTERFASEASSOC, - .bFirstInterface = USB_INTF_CDC_COMM, - .bInterfaceCount = 2, - .bFunctionClass = USB_CLASS_CDC, - .bFunctionSubClass = USB_CDC_SUBCLASS_ACM, - .bFunctionProtocol = USB_PROTO_NONE, - .iFunction = USB_STR_COM_PORT, - }, - .interface_comm = - { - .bLength = sizeof(struct usb_interface_descriptor), - .bDescriptorType = USB_DTYPE_INTERFACE, - .bInterfaceNumber = USB_INTF_CDC_COMM, - .bAlternateSetting = 0, - .bNumEndpoints = 1, - .bInterfaceClass = USB_CLASS_CDC, - .bInterfaceSubClass = USB_CDC_SUBCLASS_ACM, - .bInterfaceProtocol = USB_PROTO_NONE, - .iInterface = 0, - }, - - .cdc_header = - { - .bFunctionLength = sizeof(struct usb_cdc_header_desc), - .bDescriptorType = USB_DTYPE_CS_INTERFACE, - .bDescriptorSubType = USB_DTYPE_CDC_HEADER, - .bcdCDC = VERSION_BCD(1, 1, 0), - }, - - .cdc_acm = - { - .bFunctionLength = sizeof(struct usb_cdc_call_mgmt_desc), - .bDescriptorType = USB_DTYPE_CS_INTERFACE, - .bDescriptorSubType = USB_DTYPE_CDC_CALL_MANAGEMENT, - // .bmCapabilities = USB_CDC_CAP_LINE | USB_CDC_CAP_BRK, - .bmCapabilities = 0, - }, - - .cdc_call_mgmt = - { - .bFunctionLength = sizeof(struct usb_cdc_acm_desc), - .bDescriptorType = USB_DTYPE_CS_INTERFACE, - .bDescriptorSubType = USB_DTYPE_CDC_ACM, - .bmCapabilities = USB_CDC_CALL_MGMT_CAP_DATA_INTF, - // .bDataInterface = USB_INTF_CDC_DATA, - }, - - .cdc_union = - { - .bFunctionLength = sizeof(struct usb_cdc_union_desc), - .bDescriptorType = USB_DTYPE_CS_INTERFACE, - .bDescriptorSubType = USB_DTYPE_CDC_UNION, - .bMasterInterface0 = USB_INTF_CDC_COMM, - .bSlaveInterface0 = USB_INTF_CDC_DATA, - }, - - .ep_comm = - { - .bLength = sizeof(struct usb_endpoint_descriptor), - .bDescriptorType = USB_DTYPE_ENDPOINT, - .bEndpointAddress = HID_EP_IN | DAP_CDC_EP_COMM, - .bmAttributes = USB_EPTYPE_INTERRUPT, - .wMaxPacketSize = DAP_CDC_COMM_EP_SIZE, - .bInterval = DAP_CDC_COMM_INTERVAL, - }, - - .interface_data = - { - .bLength = sizeof(struct usb_interface_descriptor), - .bDescriptorType = USB_DTYPE_INTERFACE, - .bInterfaceNumber = USB_INTF_CDC_DATA, - .bAlternateSetting = 0, - .bNumEndpoints = 2, - .bInterfaceClass = USB_CLASS_CDC_DATA, - .bInterfaceSubClass = USB_SUBCLASS_NONE, - .bInterfaceProtocol = USB_PROTO_NONE, - .iInterface = NO_DESCRIPTOR, - }, - - .ep_in = - { - .bLength = sizeof(struct usb_endpoint_descriptor), - .bDescriptorType = USB_DTYPE_ENDPOINT, - .bEndpointAddress = HID_EP_IN | DAP_CDC_EP_SEND, - .bmAttributes = USB_EPTYPE_BULK, - .wMaxPacketSize = DAP_CDC_EP_SIZE, - .bInterval = DAP_CDC_INTERVAL, - }, - - .ep_out = - { - .bLength = sizeof(struct usb_endpoint_descriptor), - .bDescriptorType = USB_DTYPE_ENDPOINT, - .bEndpointAddress = HID_EP_OUT | DAP_CDC_EP_RECV, - .bmAttributes = USB_EPTYPE_BULK, - .wMaxPacketSize = DAP_CDC_EP_SIZE, - .bInterval = DAP_CDC_INTERVAL, - }, -}; - -// WinUSB -#include "usb_winusb.h" - -typedef struct USB_PACK { - usb_binary_object_store_descriptor_t bos; - usb_winusb_capability_descriptor_t winusb; -} usb_bos_hierarchy_t; - -typedef struct USB_PACK { - usb_winusb_subset_header_function_t header; - usb_winusb_feature_compatble_id_t comp_id; - usb_winusb_feature_reg_property_guids_t property; -} usb_msos_descriptor_subset_t; - -typedef struct USB_PACK { - usb_winusb_set_header_descriptor_t header; - usb_msos_descriptor_subset_t subset; -} usb_msos_descriptor_set_t; - -#define USB_DTYPE_BINARY_OBJECT_STORE 15 -#define USB_DTYPE_DEVICE_CAPABILITY_DESCRIPTOR 16 -#define USB_DC_TYPE_PLATFORM 5 - -const usb_bos_hierarchy_t usb_bos_hierarchy = { - .bos = - { - .bLength = sizeof(usb_binary_object_store_descriptor_t), - .bDescriptorType = USB_DTYPE_BINARY_OBJECT_STORE, - .wTotalLength = sizeof(usb_bos_hierarchy_t), - .bNumDeviceCaps = 1, - }, - .winusb = - { - .bLength = sizeof(usb_winusb_capability_descriptor_t), - .bDescriptorType = USB_DTYPE_DEVICE_CAPABILITY_DESCRIPTOR, - .bDevCapabilityType = USB_DC_TYPE_PLATFORM, - .bReserved = 0, - .PlatformCapabilityUUID = USB_WINUSB_PLATFORM_CAPABILITY_ID, - .dwWindowsVersion = USB_WINUSB_WINDOWS_VERSION, - .wMSOSDescriptorSetTotalLength = sizeof(usb_msos_descriptor_set_t), - .bMS_VendorCode = USB_WINUSB_VENDOR_CODE, - .bAltEnumCode = 0, - }, -}; - -const usb_msos_descriptor_set_t usb_msos_descriptor_set = { - .header = - { - .wLength = sizeof(usb_winusb_set_header_descriptor_t), - .wDescriptorType = USB_WINUSB_SET_HEADER_DESCRIPTOR, - .dwWindowsVersion = USB_WINUSB_WINDOWS_VERSION, - .wDescriptorSetTotalLength = sizeof(usb_msos_descriptor_set_t), - }, - - .subset = - { - .header = - { - .wLength = sizeof(usb_winusb_subset_header_function_t), - .wDescriptorType = USB_WINUSB_SUBSET_HEADER_FUNCTION, - .bFirstInterface = USB_INTF_BULK, - .bReserved = 0, - .wSubsetLength = sizeof(usb_msos_descriptor_subset_t), - }, - - .comp_id = - { - .wLength = sizeof(usb_winusb_feature_compatble_id_t), - .wDescriptorType = USB_WINUSB_FEATURE_COMPATBLE_ID, - .CompatibleID = "WINUSB\0\0", - .SubCompatibleID = {0}, - }, - - .property = - { - .wLength = sizeof(usb_winusb_feature_reg_property_guids_t), - .wDescriptorType = USB_WINUSB_FEATURE_REG_PROPERTY, - .wPropertyDataType = USB_WINUSB_PROPERTY_DATA_TYPE_MULTI_SZ, - .wPropertyNameLength = - sizeof(usb_msos_descriptor_set.subset.property.PropertyName), - .PropertyName = {'D', 0, 'e', 0, 'v', 0, 'i', 0, 'c', 0, 'e', 0, 'I', 0, - 'n', 0, 't', 0, 'e', 0, 'r', 0, 'f', 0, 'a', 0, 'c', 0, - 'e', 0, 'G', 0, 'U', 0, 'I', 0, 'D', 0, 's', 0, 0, 0}, - .wPropertyDataLength = - sizeof(usb_msos_descriptor_set.subset.property.PropertyData), - .PropertyData = {'{', 0, 'C', 0, 'D', 0, 'B', 0, '3', 0, 'B', 0, '5', 0, - 'A', 0, 'D', 0, '-', 0, '2', 0, '9', 0, '3', 0, 'B', 0, - '-', 0, '4', 0, '6', 0, '6', 0, '3', 0, '-', 0, 'A', 0, - 'A', 0, '3', 0, '6', 0, '-', 0, '1', 0, 'A', 0, 'A', 0, - 'E', 0, '4', 0, '6', 0, '4', 0, '6', 0, '3', 0, '7', 0, - '7', 0, '6', 0, '}', 0, 0, 0, 0, 0}, - }, - }, -}; - -typedef struct { - FuriSemaphore* semaphore_v1; - FuriSemaphore* semaphore_v2; - FuriSemaphore* semaphore_cdc; - bool connected; - usbd_device* usb_dev; - DapStateCallback state_callback; - DapRxCallback rx_callback_v1; - DapRxCallback rx_callback_v2; - DapRxCallback rx_callback_cdc; - DapCDCControlLineCallback control_line_callback_cdc; - DapCDCConfigCallback config_callback_cdc; - void* context; - void* context_cdc; -} DAPState; - -static DAPState dap_state = { - .semaphore_v1 = NULL, - .semaphore_v2 = NULL, - .semaphore_cdc = NULL, - .connected = false, - .usb_dev = NULL, - .state_callback = NULL, - .rx_callback_v1 = NULL, - .rx_callback_v2 = NULL, - .rx_callback_cdc = NULL, - .control_line_callback_cdc = NULL, - .config_callback_cdc = NULL, - .context = NULL, - .context_cdc = NULL, -}; - -static struct usb_cdc_line_coding cdc_config = {0}; -static uint8_t cdc_ctrl_line_state = 0; - -#ifdef DAP_USB_LOG -void furi_console_log_printf(const char* format, ...) _ATTRIBUTE((__format__(__printf__, 1, 2))); - -void furi_console_log_printf(const char* format, ...) { - char buffer[256]; - va_list args; - va_start(args, format); - vsnprintf(buffer, sizeof(buffer), format, args); - va_end(args); - furi_hal_console_puts(buffer); - furi_hal_console_puts("\r\n"); - UNUSED(format); -} -#else -#define furi_console_log_printf(...) -#endif - -int32_t dap_v1_usb_tx(uint8_t* buffer, uint8_t size) { - if((dap_state.semaphore_v1 == NULL) || (dap_state.connected == false)) return 0; - - furi_check(furi_semaphore_acquire(dap_state.semaphore_v1, FuriWaitForever) == FuriStatusOk); - - if(dap_state.connected) { - int32_t len = usbd_ep_write(dap_state.usb_dev, DAP_HID_EP_IN, buffer, size); - furi_console_log_printf("v1 tx %ld", len); - return len; - } else { - return 0; - } -} - -int32_t dap_v2_usb_tx(uint8_t* buffer, uint8_t size) { - if((dap_state.semaphore_v2 == NULL) || (dap_state.connected == false)) return 0; - - furi_check(furi_semaphore_acquire(dap_state.semaphore_v2, FuriWaitForever) == FuriStatusOk); - - if(dap_state.connected) { - int32_t len = usbd_ep_write(dap_state.usb_dev, DAP_HID_EP_BULK_IN, buffer, size); - furi_console_log_printf("v2 tx %ld", len); - return len; - } else { - return 0; - } -} - -int32_t dap_cdc_usb_tx(uint8_t* buffer, uint8_t size) { - if((dap_state.semaphore_cdc == NULL) || (dap_state.connected == false)) return 0; - - furi_check(furi_semaphore_acquire(dap_state.semaphore_cdc, FuriWaitForever) == FuriStatusOk); - - if(dap_state.connected) { - int32_t len = usbd_ep_write(dap_state.usb_dev, HID_EP_IN | DAP_CDC_EP_SEND, buffer, size); - furi_console_log_printf("cdc tx %ld", len); - return len; - } else { - return 0; - } -} - -void dap_v1_usb_set_rx_callback(DapRxCallback callback) { - dap_state.rx_callback_v1 = callback; -} - -void dap_v2_usb_set_rx_callback(DapRxCallback callback) { - dap_state.rx_callback_v2 = callback; -} - -void dap_cdc_usb_set_rx_callback(DapRxCallback callback) { - dap_state.rx_callback_cdc = callback; -} - -void dap_cdc_usb_set_control_line_callback(DapCDCControlLineCallback callback) { - dap_state.control_line_callback_cdc = callback; -} - -void dap_cdc_usb_set_config_callback(DapCDCConfigCallback callback) { - dap_state.config_callback_cdc = callback; -} - -void dap_cdc_usb_set_context(void* context) { - dap_state.context_cdc = context; -} - -void dap_common_usb_set_context(void* context) { - dap_state.context = context; -} - -void dap_common_usb_set_state_callback(DapStateCallback callback) { - dap_state.state_callback = callback; -} - -static void* dap_usb_alloc_string_descr(const char* str) { - furi_assert(str); - - size_t len = strlen(str); - size_t wlen = (len + 1) * sizeof(uint16_t); - struct usb_string_descriptor* dev_str_desc = malloc(wlen); - dev_str_desc->bLength = wlen; - dev_str_desc->bDescriptorType = USB_DTYPE_STRING; - for(size_t i = 0; i < len; i++) { - dev_str_desc->wString[i] = str[i]; - } - - return dev_str_desc; -} - -void dap_common_usb_alloc_name(const char* name) { - dev_serial_descr = dap_usb_alloc_string_descr(name); -} - -void dap_common_usb_free_name() { - free(dev_serial_descr); -} - -static void hid_init(usbd_device* dev, FuriHalUsbInterface* intf, void* ctx); -static void hid_deinit(usbd_device* dev); -static void hid_on_wakeup(usbd_device* dev); -static void hid_on_suspend(usbd_device* dev); - -static usbd_respond hid_ep_config(usbd_device* dev, uint8_t cfg); -static usbd_respond hid_control(usbd_device* dev, usbd_ctlreq* req, usbd_rqc_callback* callback); - -FuriHalUsbInterface dap_v2_usb_hid = { - .init = hid_init, - .deinit = hid_deinit, - .wakeup = hid_on_wakeup, - .suspend = hid_on_suspend, - .dev_descr = (struct usb_device_descriptor*)&hid_device_desc, - .cfg_descr = (void*)&hid_cfg_desc, -}; - -static void hid_init(usbd_device* dev, FuriHalUsbInterface* intf, void* ctx) { - UNUSED(intf); - UNUSED(ctx); - - dap_v2_usb_hid.str_manuf_descr = (void*)&dev_manuf_descr; - dap_v2_usb_hid.str_prod_descr = (void*)&dev_prod_descr; - dap_v2_usb_hid.str_serial_descr = (void*)dev_serial_descr; - - dap_state.usb_dev = dev; - if(dap_state.semaphore_v1 == NULL) dap_state.semaphore_v1 = furi_semaphore_alloc(1, 1); - if(dap_state.semaphore_v2 == NULL) dap_state.semaphore_v2 = furi_semaphore_alloc(1, 1); - if(dap_state.semaphore_cdc == NULL) dap_state.semaphore_cdc = furi_semaphore_alloc(1, 1); - - usbd_reg_config(dev, hid_ep_config); - usbd_reg_control(dev, hid_control); - - usbd_connect(dev, true); -} - -static void hid_deinit(usbd_device* dev) { - dap_state.usb_dev = NULL; - - furi_semaphore_free(dap_state.semaphore_v1); - furi_semaphore_free(dap_state.semaphore_v2); - furi_semaphore_free(dap_state.semaphore_cdc); - dap_state.semaphore_v1 = NULL; - dap_state.semaphore_v2 = NULL; - dap_state.semaphore_cdc = NULL; - - usbd_reg_config(dev, NULL); - usbd_reg_control(dev, NULL); -} - -static void hid_on_wakeup(usbd_device* dev) { - UNUSED(dev); - if(!dap_state.connected) { - dap_state.connected = true; - if(dap_state.state_callback != NULL) { - dap_state.state_callback(dap_state.connected, dap_state.context); - } - } -} - -static void hid_on_suspend(usbd_device* dev) { - UNUSED(dev); - if(dap_state.connected) { - dap_state.connected = false; - if(dap_state.state_callback != NULL) { - dap_state.state_callback(dap_state.connected, dap_state.context); - } - } -} - -size_t dap_v1_usb_rx(uint8_t* buffer, size_t size) { - size_t len = 0; - - if(dap_state.connected) { - len = usbd_ep_read(dap_state.usb_dev, DAP_HID_EP_OUT, buffer, size); - } - - return len; -} - -size_t dap_v2_usb_rx(uint8_t* buffer, size_t size) { - size_t len = 0; - - if(dap_state.connected) { - len = usbd_ep_read(dap_state.usb_dev, DAP_HID_EP_BULK_OUT, buffer, size); - } - - return len; -} - -size_t dap_cdc_usb_rx(uint8_t* buffer, size_t size) { - size_t len = 0; - - if(dap_state.connected) { - len = usbd_ep_read(dap_state.usb_dev, HID_EP_OUT | DAP_CDC_EP_RECV, buffer, size); - } - - return len; -} - -static void hid_txrx_ep_callback(usbd_device* dev, uint8_t event, uint8_t ep) { - UNUSED(dev); - UNUSED(ep); - - switch(event) { - case usbd_evt_eptx: - furi_semaphore_release(dap_state.semaphore_v1); - furi_console_log_printf("hid tx complete"); - break; - case usbd_evt_eprx: - if(dap_state.rx_callback_v1 != NULL) { - dap_state.rx_callback_v1(dap_state.context); - } - break; - default: - furi_console_log_printf("hid %d, %d", event, ep); - break; - } -} - -static void hid_txrx_ep_bulk_callback(usbd_device* dev, uint8_t event, uint8_t ep) { - UNUSED(dev); - UNUSED(ep); - - switch(event) { - case usbd_evt_eptx: - furi_semaphore_release(dap_state.semaphore_v2); - furi_console_log_printf("bulk tx complete"); - break; - case usbd_evt_eprx: - if(dap_state.rx_callback_v2 != NULL) { - dap_state.rx_callback_v2(dap_state.context); - } - break; - default: - furi_console_log_printf("bulk %d, %d", event, ep); - break; - } -} - -static void cdc_txrx_ep_callback(usbd_device* dev, uint8_t event, uint8_t ep) { - UNUSED(dev); - UNUSED(ep); - - switch(event) { - case usbd_evt_eptx: - furi_semaphore_release(dap_state.semaphore_cdc); - furi_console_log_printf("cdc tx complete"); - break; - case usbd_evt_eprx: - if(dap_state.rx_callback_cdc != NULL) { - dap_state.rx_callback_cdc(dap_state.context_cdc); - } - break; - default: - furi_console_log_printf("cdc %d, %d", event, ep); - break; - } -} - -static usbd_respond hid_ep_config(usbd_device* dev, uint8_t cfg) { - switch(cfg) { - case EP_CFG_DECONFIGURE: - usbd_ep_deconfig(dev, DAP_HID_EP_OUT); - usbd_ep_deconfig(dev, DAP_HID_EP_IN); - usbd_ep_deconfig(dev, DAP_HID_EP_BULK_IN); - usbd_ep_deconfig(dev, DAP_HID_EP_BULK_OUT); - usbd_ep_deconfig(dev, HID_EP_IN | DAP_CDC_EP_COMM); - usbd_ep_deconfig(dev, HID_EP_IN | DAP_CDC_EP_SEND); - usbd_ep_deconfig(dev, HID_EP_OUT | DAP_CDC_EP_RECV); - usbd_reg_endpoint(dev, DAP_HID_EP_OUT, NULL); - usbd_reg_endpoint(dev, DAP_HID_EP_IN, NULL); - usbd_reg_endpoint(dev, DAP_HID_EP_BULK_IN, NULL); - usbd_reg_endpoint(dev, DAP_HID_EP_BULK_OUT, NULL); - usbd_reg_endpoint(dev, HID_EP_IN | DAP_CDC_EP_SEND, 0); - usbd_reg_endpoint(dev, HID_EP_OUT | DAP_CDC_EP_RECV, 0); - return usbd_ack; - case EP_CFG_CONFIGURE: - usbd_ep_config(dev, DAP_HID_EP_IN, USB_EPTYPE_INTERRUPT, DAP_HID_EP_SIZE); - usbd_ep_config(dev, DAP_HID_EP_OUT, USB_EPTYPE_INTERRUPT, DAP_HID_EP_SIZE); - usbd_ep_config(dev, DAP_HID_EP_BULK_OUT, USB_EPTYPE_BULK, DAP_HID_EP_SIZE); - usbd_ep_config(dev, DAP_HID_EP_BULK_IN, USB_EPTYPE_BULK, DAP_HID_EP_SIZE); - usbd_ep_config(dev, HID_EP_OUT | DAP_CDC_EP_RECV, USB_EPTYPE_BULK, DAP_CDC_EP_SIZE); - usbd_ep_config(dev, HID_EP_IN | DAP_CDC_EP_SEND, USB_EPTYPE_BULK, DAP_CDC_EP_SIZE); - usbd_ep_config(dev, HID_EP_IN | DAP_CDC_EP_COMM, USB_EPTYPE_INTERRUPT, DAP_CDC_EP_SIZE); - usbd_reg_endpoint(dev, DAP_HID_EP_IN, hid_txrx_ep_callback); - usbd_reg_endpoint(dev, DAP_HID_EP_OUT, hid_txrx_ep_callback); - usbd_reg_endpoint(dev, DAP_HID_EP_BULK_OUT, hid_txrx_ep_bulk_callback); - usbd_reg_endpoint(dev, DAP_HID_EP_BULK_IN, hid_txrx_ep_bulk_callback); - usbd_reg_endpoint(dev, HID_EP_OUT | DAP_CDC_EP_RECV, cdc_txrx_ep_callback); - usbd_reg_endpoint(dev, HID_EP_IN | DAP_CDC_EP_SEND, cdc_txrx_ep_callback); - // usbd_ep_write(dev, DAP_HID_EP_IN, NULL, 0); - // usbd_ep_write(dev, DAP_HID_EP_BULK_IN, NULL, 0); - // usbd_ep_write(dev, HID_EP_IN | DAP_CDC_EP_SEND, NULL, 0); - return usbd_ack; - default: - return usbd_fail; - } -} - -#ifdef DAP_USB_LOG -static void dump_request_type(uint8_t type) { - switch(type & USB_REQ_DIRECTION) { - case USB_REQ_HOSTTODEV: - furi_hal_console_puts("host to dev, "); - break; - case USB_REQ_DEVTOHOST: - furi_hal_console_puts("dev to host, "); - break; - } - - switch(type & USB_REQ_TYPE) { - case USB_REQ_STANDARD: - furi_hal_console_puts("standard, "); - break; - case USB_REQ_CLASS: - furi_hal_console_puts("class, "); - break; - case USB_REQ_VENDOR: - furi_hal_console_puts("vendor, "); - break; - } - - switch(type & USB_REQ_RECIPIENT) { - case USB_REQ_DEVICE: - furi_hal_console_puts("device"); - break; - case USB_REQ_INTERFACE: - furi_hal_console_puts("interface"); - break; - case USB_REQ_ENDPOINT: - furi_hal_console_puts("endpoint"); - break; - case USB_REQ_OTHER: - furi_hal_console_puts("other"); - break; - } - - furi_hal_console_puts("\r\n"); -} -#else -#define dump_request_type(...) -#endif - -static usbd_respond hid_control(usbd_device* dev, usbd_ctlreq* req, usbd_rqc_callback* callback) { - UNUSED(callback); - - dump_request_type(req->bmRequestType); - furi_console_log_printf( - "control: RT %02x, R %02x, V %04x, I %04x, L %04x", - req->bmRequestType, - req->bRequest, - req->wValue, - req->wIndex, - req->wLength); - - if(((USB_REQ_RECIPIENT | USB_REQ_TYPE | USB_REQ_DIRECTION) & req->bmRequestType) == - (USB_REQ_STANDARD | USB_REQ_VENDOR | USB_REQ_DEVTOHOST)) { - // vendor request, device to host - furi_console_log_printf("vendor request"); - if(USB_WINUSB_VENDOR_CODE == req->bRequest) { - // WINUSB request - if(USB_WINUSB_DESCRIPTOR_INDEX == req->wIndex) { - furi_console_log_printf("WINUSB descriptor"); - uint16_t length = req->wLength; - if(length > sizeof(usb_msos_descriptor_set_t)) { - length = sizeof(usb_msos_descriptor_set_t); - } - - dev->status.data_ptr = (uint8_t*)&usb_msos_descriptor_set; - dev->status.data_count = length; - return usbd_ack; - } - } - } - - if(((USB_REQ_RECIPIENT | USB_REQ_TYPE) & req->bmRequestType) == - (USB_REQ_STANDARD | USB_REQ_DEVICE)) { - // device request - if(req->bRequest == USB_STD_GET_DESCRIPTOR) { - const uint8_t dtype = req->wValue >> 8; - const uint8_t dnumber = req->wValue & 0xFF; - // get string descriptor - if(USB_DTYPE_STRING == dtype) { - if(dnumber == USB_STR_CMSIS_DAP_V1) { - furi_console_log_printf("str CMSIS-DAP v1"); - dev->status.data_ptr = (uint8_t*)&dev_dap_v1_descr; - dev->status.data_count = dev_dap_v1_descr.bLength; - return usbd_ack; - } else if(dnumber == USB_STR_CMSIS_DAP_V2) { - furi_console_log_printf("str CMSIS-DAP v2"); - dev->status.data_ptr = (uint8_t*)&dev_dap_v2_descr; - dev->status.data_count = dev_dap_v2_descr.bLength; - return usbd_ack; - } else if(dnumber == USB_STR_COM_PORT) { - furi_console_log_printf("str COM port"); - dev->status.data_ptr = (uint8_t*)&dev_com_descr; - dev->status.data_count = dev_com_descr.bLength; - return usbd_ack; - } - } else if(USB_DTYPE_BINARY_OBJECT_STORE == dtype) { - furi_console_log_printf("BOS descriptor"); - uint16_t length = req->wLength; - if(length > sizeof(usb_bos_hierarchy_t)) { - length = sizeof(usb_bos_hierarchy_t); - } - dev->status.data_ptr = (uint8_t*)&usb_bos_hierarchy; - dev->status.data_count = length; - return usbd_ack; - } - } - } - - if(((USB_REQ_RECIPIENT | USB_REQ_TYPE) & req->bmRequestType) == - (USB_REQ_INTERFACE | USB_REQ_CLASS) && - req->wIndex == 0) { - // class request - switch(req->bRequest) { - // get hid descriptor - case USB_HID_GETREPORT: - furi_console_log_printf("get report"); - return usbd_fail; - // set hid idle - case USB_HID_SETIDLE: - furi_console_log_printf("set idle"); - return usbd_ack; - default: - break; - } - } - - if(((USB_REQ_RECIPIENT | USB_REQ_TYPE) & req->bmRequestType) == - (USB_REQ_INTERFACE | USB_REQ_CLASS) && - req->wIndex == 2) { - // class request - switch(req->bRequest) { - // control line state - case USB_CDC_SET_CONTROL_LINE_STATE: - furi_console_log_printf("set control line state"); - cdc_ctrl_line_state = req->wValue; - if(dap_state.control_line_callback_cdc != NULL) { - dap_state.control_line_callback_cdc(cdc_ctrl_line_state, dap_state.context_cdc); - } - return usbd_ack; - // set cdc line coding - case USB_CDC_SET_LINE_CODING: - furi_console_log_printf("set line coding"); - memcpy(&cdc_config, req->data, sizeof(cdc_config)); - if(dap_state.config_callback_cdc != NULL) { - dap_state.config_callback_cdc(&cdc_config, dap_state.context_cdc); - } - return usbd_ack; - // get cdc line coding - case USB_CDC_GET_LINE_CODING: - furi_console_log_printf("get line coding"); - dev->status.data_ptr = &cdc_config; - dev->status.data_count = sizeof(cdc_config); - return usbd_ack; - default: - break; - } - } - - if(((USB_REQ_RECIPIENT | USB_REQ_TYPE) & req->bmRequestType) == - (USB_REQ_INTERFACE | USB_REQ_STANDARD) && - req->wIndex == 0 && req->bRequest == USB_STD_GET_DESCRIPTOR) { - // standard request - switch(req->wValue >> 8) { - // get hid descriptor - case USB_DTYPE_HID: - furi_console_log_printf("get hid descriptor"); - dev->status.data_ptr = (uint8_t*)&(hid_cfg_desc.hid); - dev->status.data_count = sizeof(hid_cfg_desc.hid); - return usbd_ack; - // get hid report descriptor - case USB_DTYPE_HID_REPORT: - furi_console_log_printf("get hid report descriptor"); - dev->status.data_ptr = (uint8_t*)hid_report_desc; - dev->status.data_count = sizeof(hid_report_desc); - return usbd_ack; - default: - break; - } - } - - return usbd_fail; -} diff --git a/applications/external/dap_link/usb/dap_v2_usb.h b/applications/external/dap_link/usb/dap_v2_usb.h deleted file mode 100644 index 3f1534ffd56..00000000000 --- a/applications/external/dap_link/usb/dap_v2_usb.h +++ /dev/null @@ -1,53 +0,0 @@ -#pragma once -#include -#include - -extern FuriHalUsbInterface dap_v2_usb_hid; - -// receive callback type -typedef void (*DapRxCallback)(void* context); - -typedef void (*DapStateCallback)(bool state, void* context); - -/************************************ V1 ***************************************/ - -int32_t dap_v1_usb_tx(uint8_t* buffer, uint8_t size); - -size_t dap_v1_usb_rx(uint8_t* buffer, size_t size); - -void dap_v1_usb_set_rx_callback(DapRxCallback callback); - -/************************************ V2 ***************************************/ - -int32_t dap_v2_usb_tx(uint8_t* buffer, uint8_t size); - -size_t dap_v2_usb_rx(uint8_t* buffer, size_t size); - -void dap_v2_usb_set_rx_callback(DapRxCallback callback); - -/************************************ CDC **************************************/ - -typedef void (*DapCDCControlLineCallback)(uint8_t state, void* context); -typedef void (*DapCDCConfigCallback)(struct usb_cdc_line_coding* config, void* context); - -int32_t dap_cdc_usb_tx(uint8_t* buffer, uint8_t size); - -size_t dap_cdc_usb_rx(uint8_t* buffer, size_t size); - -void dap_cdc_usb_set_rx_callback(DapRxCallback callback); - -void dap_cdc_usb_set_control_line_callback(DapCDCControlLineCallback callback); - -void dap_cdc_usb_set_config_callback(DapCDCConfigCallback callback); - -void dap_cdc_usb_set_context(void* context); - -/*********************************** Common ************************************/ - -void dap_common_usb_set_context(void* context); - -void dap_common_usb_set_state_callback(DapStateCallback callback); - -void dap_common_usb_alloc_name(const char* name); - -void dap_common_usb_free_name(); diff --git a/applications/external/dap_link/usb/usb_winusb.h b/applications/external/dap_link/usb/usb_winusb.h deleted file mode 100644 index 9c3a172dc20..00000000000 --- a/applications/external/dap_link/usb/usb_winusb.h +++ /dev/null @@ -1,143 +0,0 @@ -#pragma once -#include - -/*- Definitions -------------------------------------------------------------*/ - -#define USB_PACK __attribute__((packed)) - -#define USB_WINUSB_VENDOR_CODE 0x20 - -#define USB_WINUSB_WINDOWS_VERSION 0x06030000 // Windows 8.1 - -#define USB_WINUSB_PLATFORM_CAPABILITY_ID \ - { \ - 0xdf, 0x60, 0xdd, 0xd8, 0x89, 0x45, 0xc7, 0x4c, 0x9c, 0xd2, 0x65, 0x9d, 0x9e, 0x64, 0x8a, \ - 0x9f \ - } - -enum // WinUSB Microsoft OS 2.0 descriptor request codes -{ - USB_WINUSB_DESCRIPTOR_INDEX = 0x07, - USB_WINUSB_SET_ALT_ENUMERATION = 0x08, -}; - -enum // wDescriptorType -{ - USB_WINUSB_SET_HEADER_DESCRIPTOR = 0x00, - USB_WINUSB_SUBSET_HEADER_CONFIGURATION = 0x01, - USB_WINUSB_SUBSET_HEADER_FUNCTION = 0x02, - USB_WINUSB_FEATURE_COMPATBLE_ID = 0x03, - USB_WINUSB_FEATURE_REG_PROPERTY = 0x04, - USB_WINUSB_FEATURE_MIN_RESUME_TIME = 0x05, - USB_WINUSB_FEATURE_MODEL_ID = 0x06, - USB_WINUSB_FEATURE_CCGP_DEVICE = 0x07, - USB_WINUSB_FEATURE_VENDOR_REVISION = 0x08, -}; - -enum // wPropertyDataType -{ - USB_WINUSB_PROPERTY_DATA_TYPE_SZ = 1, - USB_WINUSB_PROPERTY_DATA_TYPE_EXPAND_SZ = 2, - USB_WINUSB_PROPERTY_DATA_TYPE_BINARY = 3, - USB_WINUSB_PROPERTY_DATA_TYPE_DWORD_LITTLE_ENDIAN = 4, - USB_WINUSB_PROPERTY_DATA_TYPE_DWORD_BIG_ENDIAN = 5, - USB_WINUSB_PROPERTY_DATA_TYPE_LINK = 6, - USB_WINUSB_PROPERTY_DATA_TYPE_MULTI_SZ = 7, -}; - -/*- Types BOS -------------------------------------------------------------------*/ - -typedef struct USB_PACK { - uint8_t bLength; - uint8_t bDescriptorType; - uint16_t wTotalLength; - uint8_t bNumDeviceCaps; -} usb_binary_object_store_descriptor_t; - -/*- Types WinUSB -------------------------------------------------------------------*/ - -typedef struct USB_PACK { - uint8_t bLength; - uint8_t bDescriptorType; - uint8_t bDevCapabilityType; - uint8_t bReserved; - uint8_t PlatformCapabilityUUID[16]; - uint32_t dwWindowsVersion; - uint16_t wMSOSDescriptorSetTotalLength; - uint8_t bMS_VendorCode; - uint8_t bAltEnumCode; -} usb_winusb_capability_descriptor_t; - -typedef struct USB_PACK { - uint16_t wLength; - uint16_t wDescriptorType; - uint32_t dwWindowsVersion; - uint16_t wDescriptorSetTotalLength; -} usb_winusb_set_header_descriptor_t; - -typedef struct USB_PACK { - uint16_t wLength; - uint16_t wDescriptorType; - uint8_t bConfigurationValue; - uint8_t bReserved; - uint16_t wTotalLength; -} usb_winusb_subset_header_configuration_t; - -typedef struct USB_PACK { - uint16_t wLength; - uint16_t wDescriptorType; - uint8_t bFirstInterface; - uint8_t bReserved; - uint16_t wSubsetLength; -} usb_winusb_subset_header_function_t; - -typedef struct USB_PACK { - uint16_t wLength; - uint16_t wDescriptorType; - uint8_t CompatibleID[8]; - uint8_t SubCompatibleID[8]; -} usb_winusb_feature_compatble_id_t; - -typedef struct USB_PACK { - uint16_t wLength; - uint16_t wDescriptorType; - uint16_t wPropertyDataType; - //uint16_t wPropertyNameLength; - //uint8_t PropertyName[...]; - //uint16_t wPropertyDataLength - //uint8_t PropertyData[...]; -} usb_winusb_feature_reg_property_t; - -typedef struct USB_PACK { - uint16_t wLength; - uint16_t wDescriptorType; - uint16_t wPropertyDataType; - uint16_t wPropertyNameLength; - uint8_t PropertyName[42]; - uint16_t wPropertyDataLength; - uint8_t PropertyData[80]; -} usb_winusb_feature_reg_property_guids_t; - -typedef struct USB_PACK { - uint16_t wLength; - uint16_t wDescriptorType; - uint8_t bResumeRecoveryTime; - uint8_t bResumeSignalingTime; -} usb_winusb_feature_min_resume_time_t; - -typedef struct USB_PACK { - uint16_t wLength; - uint16_t wDescriptorType; - uint8_t ModelID[16]; -} usb_winusb_feature_model_id_t; - -typedef struct USB_PACK { - uint16_t wLength; - uint16_t wDescriptorType; -} usb_winusb_feature_ccgp_device_t; - -typedef struct USB_PACK { - uint16_t wLength; - uint16_t wDescriptorType; - uint16_t VendorRevision; -} usb_winusb_feature_vendor_revision_t; \ No newline at end of file diff --git a/applications/external/hid_app/application.fam b/applications/external/hid_app/application.fam deleted file mode 100644 index a9d8305ddda..00000000000 --- a/applications/external/hid_app/application.fam +++ /dev/null @@ -1,24 +0,0 @@ -App( - appid="hid_usb", - name="Remote", - apptype=FlipperAppType.EXTERNAL, - entry_point="hid_usb_app", - stack_size=1 * 1024, - fap_category="USB", - fap_icon="hid_usb_10px.png", - fap_icon_assets="assets", - fap_icon_assets_symbol="hid", -) - - -App( - appid="hid_ble", - name="Remote", - apptype=FlipperAppType.EXTERNAL, - entry_point="hid_ble_app", - stack_size=1 * 1024, - fap_category="Bluetooth", - fap_icon="hid_ble_10px.png", - fap_icon_assets="assets", - fap_icon_assets_symbol="hid", -) diff --git a/applications/external/hid_app/assets/Arr_dwn_7x9.png b/applications/external/hid_app/assets/Arr_dwn_7x9.png deleted file mode 100644 index d4034efc432b102f6f751d001dc6891b0763c55c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3602 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 JTZ;Aw|2NmVF(Lo} diff --git a/applications/external/hid_app/assets/Arr_up_7x9.png b/applications/external/hid_app/assets/Arr_up_7x9.png deleted file mode 100644 index 28b4236a292708b412629ffafe30f6d011491505..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3605 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 AV*mgE diff --git a/applications/external/hid_app/assets/ButtonF10_5x8.png b/applications/external/hid_app/assets/ButtonF10_5x8.png deleted file mode 100644 index d1a7a04f06dcc4b5446aad5a40a9863038bf56b5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 172 zcmeAS@N?(olHy`uVBq!ia0vp^tU%1c!2~4tO5(ej@)Wnk16ovB4k_-iRPv3y>Mm}+% zA~jDJ#}JO|$tewu|Ns9tHZU+a6e)0^L#NYq9;5F(PTxtKzKT}z3_A)0e;QviHwNlp N@O1TaS?83{1OPU#E%g8Z diff --git a/applications/external/hid_app/assets/ButtonF11_5x8.png b/applications/external/hid_app/assets/ButtonF11_5x8.png deleted file mode 100644 index 7e177358e81695342f2a283a220c7cacc7bda939..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 173 zcmeAS@N?(olHy`uVBq!ia0vp^tU%1c!2~4tOpBPSmNg(OQ{BTAg}b8}PkN*J7rQWHy3QxwWGOEMJPJ$(bh8~Mb6 ziqt(_978y+C#N(t{{R2q*ucQxP^7?t4xLU{IYmyznHN-MN(3-k$uq=X7x{LAUCkJ% Og~8L+&t;ucLK6T7Y%hHP diff --git a/applications/external/hid_app/assets/ButtonF12_5x8.png b/applications/external/hid_app/assets/ButtonF12_5x8.png deleted file mode 100644 index 50d2a7dc63b9d366ccfbacbc05e6bb0d9e335b5b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 180 zcmeAS@N?(olHy`uVBq!ia0vp^tU%1c!2~4tO zk)EfEV+hCf+#W|R1_Pc$J^%l2ww|&zRcE|OcGEe{j diff --git a/applications/external/hid_app/assets/ButtonF1_5x8.png b/applications/external/hid_app/assets/ButtonF1_5x8.png deleted file mode 100644 index 7394d27105fd0a27067803bfd633a26bedd0f1d5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 177 zcmeAS@N?(olHy`uVBq!ia0vp^tU%1c!2~4tO5(ej@)Wnk16ovB4k_-iRPv3y>Mm}+% zB5h9>#}JO|$tewu|Ns9tHZU+a6e)0^L+9jy0|#0HPM+X+s?4mGa`wiPi$55Iw(~PB TTpxE5sExtX)z4*}Q$iB}`k6I% diff --git a/applications/external/hid_app/assets/ButtonF2_5x8.png b/applications/external/hid_app/assets/ButtonF2_5x8.png deleted file mode 100644 index 9d922a3858147116d65b6f03e2b36ea846b2f60c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 179 zcmeAS@N?(olHy`uVBq!ia0vp^tU%1c!2~4tO zk*=qUV+hCf)NV&U1_ho&w~qX;m*hWYIaS!#v1}4USoB8kx*gxNJa@^Tf4zarr_o#d UH)s04hd_-Cp00i_>zopr0BX-NbN~PV diff --git a/applications/external/hid_app/assets/ButtonF3_5x8.png b/applications/external/hid_app/assets/ButtonF3_5x8.png deleted file mode 100644 index 95c2dd4f4198e182a1a62927c4d3627400a7b883..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 178 zcmeAS@N?(olHy`uVBq!ia0vp^tU%1c!2~4tO zk&dT}V+hCf)GkXd1_g%0LLdIeuWPOlF*aSY$&;z#+9C5#-hV?Uh3H=_oX_AhhhO~n UO!>#_f%+IcUHx3vIVCg!073*Z7ytkO diff --git a/applications/external/hid_app/assets/ButtonF4_5x8.png b/applications/external/hid_app/assets/ButtonF4_5x8.png deleted file mode 100644 index 602466f4b667b6df4d5335517bd9d43e5f0b6e69..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 177 zcmeAS@N?(olHy`uVBq!ia0vp^tU%1c!2~4tO5(ej@)Wnk16ovB4k_-iRPv3y>Mm}+% zB5h9>#}JO|vE3Va85}s64*o6Qr{DAJBNr3n8pa7vN_+nsOQj%x4ZT`lf}PS TtvtgA)W+cH>gTe~DWM4fb!sy& diff --git a/applications/external/hid_app/assets/ButtonF5_5x8.png b/applications/external/hid_app/assets/ButtonF5_5x8.png deleted file mode 100644 index d73b5405275f6c53f7a2f157df063db1ca351caa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 178 zcmeAS@N?(olHy`uVBq!ia0vp^tU%1c!2~4tOFVdQ&MBb@0Fom!%>V!Z diff --git a/applications/external/hid_app/assets/ButtonF6_5x8.png b/applications/external/hid_app/assets/ButtonF6_5x8.png deleted file mode 100644 index c50748257ab8e06f90007e93b913d5be4999d096..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 177 zcmeAS@N?(olHy`uVBq!ia0vp^tU%1c!2~4tOA;}Wgh!W@g+}zZ>5(ej@)Wnk16ovB4k_-iRPv3y>Mm}+% zB5h9>#}JO|shy5|3<^BWD?a{@Kh_*L{p89i1p$qBwtDvdG5Wpwnq5@=JeG)jb@A^; T#rkJ}+88`t{an^LB{Ts5$FwwN diff --git a/applications/external/hid_app/assets/ButtonF7_5x8.png b/applications/external/hid_app/assets/ButtonF7_5x8.png deleted file mode 100644 index 396c98f5104f94b6310593ce6c7e6ce3d2369ef3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 176 zcmeAS@N?(olHy`uVBq!ia0vp^tU%1c!2~4tO+nf~Hxmr&P}udD~(3jVRo RuLQY`!PC{xWt~$(69B*FH3|R# diff --git a/applications/external/hid_app/assets/ButtonF8_5x8.png b/applications/external/hid_app/assets/ButtonF8_5x8.png deleted file mode 100644 index 6304d7fb888b2cf38c54e7124aaa604d1610629c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 176 zcmeAS@N?(olHy`uVBq!ia0vp^tU%1c!2~4tO5(ej@)Wnk16ovB4k_-iRPv3y>Mm}+% zA}voB#}JO|wVjT93<^BWEB^mCmh0Jd#>H=GOE1@xHLh7tre2KydVRe*qgvi_@vq`% Sf29C*F?hQAxvXEZzkxv|` zNY~TFF@)oKa!Nzv|NsAu4GatpMG73~&^g&~GSNx=@BjbyU3DUS%F5hxSQ8l-I!}xL U>{@7g2&j?4)78&qol`;+0Ic0IyZ`_I diff --git a/applications/external/hid_app/assets/ButtonLeft_4x7.png b/applications/external/hid_app/assets/ButtonLeft_4x7.png deleted file mode 100644 index 0b4655d43247083aa705620e9836ac415b42ca46..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1415 zcmbVM+iKK67*5rq)>aU2M7$VM1Vxif;vTv~W2u`S7ED{V3s&&L*<`XiG|9wd+THd> z5CnY!sdyuJtrvQyAo>KpiLcV|{Tkc)riAbluXfwSZCApL`ztB&p zx6LGKvks4K_4~)qD&oGa-YdJlW)hAKMNJd7<=t?6c^RI1>c$ifyjaM>^|&8!ey zB4!nh9u>5uen6Ve@<H5rru6h<2Ef#GQdQ*CmZOlQi~N!?9H`Rp;C% zU}CB21#?;r`&0|6C0}b-=jODa5|nEJ#ntxQ&{~jpgtwDta4hftr~G=#p@V36e4Zjh zq%J~{y26Jjn=1Nw-l*3%QW5YFE*v4z3gt0$&(*xf2en34c?JpH8+FYldo+Alvg8af-pG4(=!fyUi-Wsg z`g#n9VUcf(DFr{poMSNzw-lz>w+HV+n1ELr&SLA#LHUb0p(xWQ(1*vJ-i+1!`swxZ Z!O7;c$;lT_->m1Ovaz)0yuI`A$q$F8u*d)a diff --git a/applications/external/hid_app/assets/ButtonRight_4x7.png b/applications/external/hid_app/assets/ButtonRight_4x7.png deleted file mode 100644 index 8e1c74c1c0038ea55172f19ac875003fc80c2d06..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1839 zcmcIlO>f*p7#Yw)M6zw!O+@VZ{?d|D~WYi~8rHRY?X-&T}Yen`g$^+EJ;z+|RV zE@PoDvZ9%#+_}3bC_5Cj8jDGq541mi{7F+&KF}W65sr$Xn5H|YrMQ2(J7%Yc%;(zO z57ax000=TsQ+1Ke@+w#iw3au3cGGQWY740k2ijH>P(6tD)S)be>gX6Tj7`<`b>di- zgWp$8Y+?i31~CzF0&E4uRlA=C(Mp~K`{74jEchB|)4DDK!ZVhSwdFyw0YIZ1cDh0S{OvfO-U_~ zvmRF*m9sWDXNH)GOyqS1Skhxbr6}s*7t&@~kFM(NW5}qh?Lu@lJ}HE;FDiLdGO>LO z5pS*%E2grR)l^;|?O5b_?u0me&c1U}%jrk8*%=Wk%i)8yp2P|kuxmKg<=(u_`oQRI_0 zS`-DNysBx=#3&qSkgA@hJP>~D+ZM(s5jI6Owp`?yE=3e`YGUqkVOp#Cp=3wR3O4hX zX6BLsN3UBzV(vI5;|SZHgOb=HD0VFjpTyfFW}GnQuh>2*Q`k>*cAmA#iUT7EXSpo# zkPm5~#I-o^cpgfe#P$=4-Pi*SpT!-@nJgp8L347xe>5EKl`=_ZFc8XGy+_j=_R_7! z@vZZMowS1GJ?Zw)eetks%~G{BTR>T}9|jt0j3Btyb*C3-`C?fwY3EY`q*oYZ39DpM z&uJ;PCZPLs4QO1Jd_|A1PF)azZJ)RZ`^-VMWr6e#XUOA%3eLG_Ch@BDOHzMk*MF0G zCo7xMd?Mg*HMIXw%nNz?%60fZiZPlqb?GqUpXO`F&Yi!okZl(n>P@r1P2i)yk3DgRwbHeNn6e|;J^SK4TM LH~i+q&mR8;k>NTA diff --git a/applications/external/hid_app/assets/ButtonUp_7x4.png b/applications/external/hid_app/assets/ButtonUp_7x4.png deleted file mode 100644 index 1be79328b40a93297a5609756328406565c437c0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 102 zcmeAS@N?(olHy`uVBq!ia0vp^>_E)I!3HFqj;YoHDIHH2#}J8d-yTOk1_O>mFaFD) zeWb+ZHz{mGZZ1QpXe09^4tcYT#4oe=UbmGC^A-KE*|F&zP#=S*tDnm{r-UX30HgpM AM*si- diff --git a/applications/external/hid_app/assets/Button_18x18.png b/applications/external/hid_app/assets/Button_18x18.png deleted file mode 100644 index 30a5b4fab236d8b57242559ef94fb1c5dbb5d10a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3609 zcmaJ@c{r49`+jVNvSba(81Yt?8Cx+K+gL`~8rw)>jKN@*W(G4tN=nI=Eo(wa4Q%8vkx{u?zYHw>PBq%Eg0DzDc z(hS9!#kL=Q9?lyh8i4}IOAwh8Gn{vj+=2WcHX}wv6 z{-S5$q3oHN^-t@S6WJ3RZH#u2$UR~zN#ptcfIceP0M?_BV27-0s*2>6L=N(TM8}(7 z`|{NTz#I>Q9zlC#w88a|1aJf7E{y|X4MV@8D(qEU08kPz2o{^z#g&Kx8Z{gnC4k1g zz$1sJ-hx0100c6^Ou@i?Az=E4l_4L{Q=Hr{4fN#iE9M8{xPXj4 zwXcCZrZHH9x3-ik()GEPC3j>M9}pamP82cr1R^s`)mi|M9yfs4FW$-nvgXNycGe6Q zdyu19NG_nZIkh$YM5nd{EA_o>$im#H=N(jFoW#zri2 zzHaq}&H-mLjWbGW3!*m9Vu-<|sQ8IyUQrUuD^Y zZ5kLaP)TNrO{v3TljpVO71A~Zl0$?5=4HED+vhu;fXTOrK ztd-`*>@YLleW2Dr)O5#aVKIBW;(Net{L&fmykHDc=SE~9Xfj6PB)GnjQpjCw>YwC} zR9aA{Na)9%HeO5YYXoUs+qhO~shM)&$w{7%+(E`K?kUJ#dz(k?py`OXN2cWmbjX(N zhetloFX}k)Erp$Yef^5L=T)?gtyCsf!S5mvbxHH}AK>JBc4f+;Vyks@FWBQm zv;|XTR&l>#uJV~bgvC9Qkq3mEZj9OrDk>*xS?#h4K=vWk3mpm#J4Nx?)+$qpgr={f z{7)j8p!B5jM3F?h8|zJPM$08&^)bWN0{I6}g(+gkb#X>xymxMCnP%kOKiOKG`;q^C z4D8k^D?(ndJ;dQkvA9l9rgCeR6r#CMy`bxTCf*mn;s=?eRS0~E+HaozKD{&G+s?^} z$*3P8yM-F2nbx$W4+H`tb7MFv+BM zVyUoH=hTSQiTjRDR41b@#{FH651d3EoN*4nYvJ_Nexz97qtt`0VtJ>R#YalpP$8%U z`}UI_1=Sv#7uT>tPcBDWD+@7pXTL<&4 z%LPNuSvw%8_kEZ?Nj^E_XIr_1-##9k)Bl`(yiKu9sO_9OkGhfi<8J>FpOT1@qrIWM z)xBOblo_d+sa|#vImb9hEoTWvfUN`xR2-=|SrJ{)7u5dU@B?;=F)6V0Zb^9ZONZqW z;YY!e^mleQyF=k9REPgaqD-Ks9(JxJ5&JFRCZ5$XcWLO}o@T#_q&mNX4y%GcSSqtu zd`EQY(uO`v(mpSy&R1N2fC0t}uhmyrS6DwQhyL`(& zG5PLev}0iuT2M=HAh~j?a7gD(ab5A7Nf%!^-`mujMP2E;ClZ^*(u32b9SB9&iio#D zn^VVRXDd3NeOM~UdYRQ<@|p1QOAEX{{K2}7MwVQY`x`jhf8pan$LN{4B@!7wn-ktw}#xeLT_EEzFQ3*fLAL; zbVp=F?A*v*KepDqneek_h_N6wZ_DS&^@?kZtLlR6g{M3LJPN!Symxl$^2PDJ+yU8b zC~3M|K*&{rl1!?VUXWYGYWMr9Wp+ruL) z{+L0_z!;VSUM53&HC*D*VXgZb-%pk~(9Y6U)Vi6YuIs*4@$(7A*Iyj#^M6hW_GS79 zq5`qgS*%Fbebxo~m7nJG>0&hT0|GNwN9%g(;8#be+!KMB+S#L-j%hS(=~#dM3+eI6 zw&vUr16N(w#4x?+n_}rtjK-osruLA%c4I|E8+q}COIgu&=GFOe`6nNjvyL0w7|(G| zUDo?@EF7`sciGM&=&iPZ9ZHpvBy;11(xQ#CS@&0F`{%Qt)%8=dQ?d(CLin^Y)lbm! zgXMNUs;bFCql|IFJGta5?^Z^YR;i19l7Z3I9R+2mQhQ-3YsfuSy4zkiIty8aJoQm~ zz-R0Gs?x5DQejnzkL+2Gp7yZluJeQ78uOP@O0f>oAsU+Qs0wd7ey%gT*{}IY+NS+5 z8s)U$&*)!>M@4nsxr0!>=%SNaoYK@xEd6on1y&N1>g~k#Pw#SbK7Uv`)q_c9-Yfn2 z$bvOK>|*QD6}H46^!9!|UjA-o3OQ9cMP#nH);v63nqiQIhLn4AaU_*dHP zQ2(X)*0R=jtvtFI-5Ix*=ghu^+eZqPLvzl%H#={ZJSeaJtkT5nouAA$Ik-3Fq#d+qrDcp7N)W0{b7<)I1R& zppL}tN5aTsS&^jPteMP^XXI0dgjgRTXXGub%YQ|%HAk>P4Y~;~xp_GU;q$Ab7n4Vdyo+*kY>nU_ zGx`}T)*BfC?kC-=d=c%rM$)ud>vE5krp2!l3GQ>1E|a6_gjoA_Sa-zzYeJtgQrJupeGtwb~ zv)29Yp$YVd8`Zs=-*>Kwd_P~d^%z%682ss3>)HOsRfH`pa3yyu<=2NRL!Fi_mR(8~ zN^uD}3JP*UvQ-P-ZOKDLPm09b-$gk8VoXsVObl!eub*f~Z}iOVT8(Y5DP-hN@s|B!#~QYw=)K*F;Y8Th24v;Z;(DaM z@*d7#r3}p+O>-dm&_Xa29AM&2^1^|v2pC@+3WxD#oNdAx0055)-Vseh+gQV}B!UKJ z+ed>=Aal?FU|>WiW3T}@8psRhizmXt?3XoQ5Z)UOcG0zg+K>@AKRhy&f^!J9b;O1S zVD-JhMus2*I*da=z|k-uIw6oqh0)>QKY3xC^|l!T2L0(m3xI?F5{0(02O&rl9O$Tq zraBf1g@TUiYj|V4Fjy}yHINomOA`XsfoSTeL!mHjeVC38=&Lss+)~Qs;Q6QyD}WhOSPeD*a|K!%?vmJeh_k z5kcFG7%x%~4G!i={VN9o`5#&$_3v}yoEU_TAwx7ZpxZh9cC@ki|6K`$f4r$Q6z;!z z|CN~P$ROh&C>)g(M8R?@=cBY8iVQ=&_Npv z7Ej!^9QqStV*|4yQfU|>7H4G!2Xja?@OW<+RM*_}sEH{;SI0tMQ_~z_s%xQZenj8Y z#MBIGc2r;QH`a`V4IPoKl5_wQQ%!g~LUmcOwk{}T)0h=FX^_W#uSw~5n0+sl7im$Uh&`Ef)}$5S}1 zy?{b{ajwM@~ diff --git a/applications/external/hid_app/assets/Circles_47x47.png b/applications/external/hid_app/assets/Circles_47x47.png deleted file mode 100644 index 6a16ebf7bbe999fa143c683bef713a6e2f466cbb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3712 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 biU<_Yn+9Io=xs3K`2!qm58ISjpSt)z2v?8| diff --git a/applications/external/hid_app/assets/Left_mouse_icon_9x9.png b/applications/external/hid_app/assets/Left_mouse_icon_9x9.png deleted file mode 100644 index c533d85729f9b778cba33227141c90dea298f5e3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3622 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 diff --git a/applications/external/hid_app/assets/Like_def_11x9.png b/applications/external/hid_app/assets/Like_def_11x9.png deleted file mode 100644 index 555bea3d4b0239ae1be630d0306316d3e9494c4c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3616 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 diff --git a/applications/external/hid_app/assets/Pin_arrow_down_7x9.png b/applications/external/hid_app/assets/Pin_arrow_down_7x9.png deleted file mode 100644 index 9687397afa81c92fb727bd40542830392fabdccb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3607 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 QX&eA7&1_6djNPvM5BL~PlmGw# diff --git a/applications/external/hid_app/assets/Pin_arrow_left_9x7.png b/applications/external/hid_app/assets/Pin_arrow_left_9x7.png deleted file mode 100644 index fb4ded78fde8d1bf4f053ba0b353e7acf14f031a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3603 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 diff --git a/applications/external/hid_app/assets/Pin_arrow_up_7x9.png b/applications/external/hid_app/assets/Pin_arrow_up_7x9.png deleted file mode 100644 index a91a6fd5e99a72112e28865cd8a004c7d1933fff..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3603 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 TFC+3}9cE|kU{!4CefEC<&8td2 diff --git a/applications/external/hid_app/assets/Pressed_Button_13x13.png b/applications/external/hid_app/assets/Pressed_Button_13x13.png deleted file mode 100644 index 823926b842b8f9868fd70d6f1434dff071c04d5d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3606 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 VN_%sSbR7Ty002ovPDHLkV1hy!X)pi) diff --git a/applications/external/hid_app/assets/Space_65x18.png b/applications/external/hid_app/assets/Space_65x18.png deleted file mode 100644 index b60ae50970b8be827ae32ddbd9e1b0d28c8b3a9a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3619 zcmaJ@c{r5q+kR|?vSeS9G2*Q(Gqz$f_GOf18r!JE7=ytq%?xHFO-U))vSm#u)X=6# zwu*&|8!^APw~9?k(a6Vz_{`1J?VwO%hlm80mweJ_2Ll%+w5Jal|B#ZToHj zkX!A1wWV(yKRGcrJmE7L$o^5EyA?1;0vjpK4{lZ7;N}HH?K{|g9^>OZJmdzhM?p0K zMW=v17r<|D)m^7wAm^muyU^8WhW>`hzU({5Mni?Yg1dIjs(9V0f{sQT{n8mG4Mm49 zbG~l%ht2_K(@oNfYx5#D&tizdC8*fR7G5(g;>x7*Rzu{4&DevTBf5`It4m&=M_(&P zg6$d@FHi{BFL>ue9`qCWpjMUz{dO z@9>n#el1gZMS$0|kzX961dH0^726AL=a){4^e8+sFE>V1@t?G01xkRvR~uHtV6d@Jy=*+_LjJ^<;I%HkfZ+ zJ{WS&*3q1L--qRs;FC3Rwv9{p?cw*6_FMFK^@Q9yZ8!?f0Ei>znMIVlCNa;%nYvD_=OIcyvaxrpYxGcGRWZCqbo>reG^tc8h zWbb>x%$fc-l1kK>Pg=_9^WFC8k{QaNGP~oK)fB= zk~}W=y`t;c`=z{$ml*@ap9mj5x5DesKUlZZ%#d$#e*hBLJ2$e|kFK?B#{H}rW-Lg}+w*yHz2X|@s=6q5@hMLLk0Ngx@77A0z{8^GG<=3FCsOHJ6w{_pD*!j4k8!wLb`#+}y`?CB4 zQGwW*jB;lA{ql?St3NI0Q^jcF`vqpNjn(zm!LN-{xhDhDbu!1&ol`GQ%#aWnhw%cUor3tn<%~!N%j(>i+!K$>%8wb|oXB!X zUe^D7^t}0+-xUX|ptm{#4k$H7g6z!~%8Pa`7Cm2B9iPsA(lAKMOv=nd3E@*p)jmSY z4wO0gsHr6ijWH$&&GLy?n^(q^SE-Brl7W%7oq46G5~Q${Eu>J5eoE#Py&O@6IQcl%pM`Lo~JAQ5D{F{9M=h7QdD!DVxX< zG|G9wpE0lyi;C#Fd)Hj;lB;fVQBqS2vE;|e7g$M5vbQtaKehXm%Y{SI$sQ~+tFYwf zBdhX>5m$SU?yw~Wp|9`Dv9jjbX~cB?G?BI9R`c*!mA`5CyDM`-#q#qpezqJMP@wb32zU+0*_sQsBVDnwlp9 z1k~Y}eFzwNJcCK<%a~0Mc}6~YNcgqs_^ZDL?}eQkMSi{0{$}7!+hE#-vL*g$1VgP0 zRujb1$Rp&y?^LnB-pI>RIHO=)UG^)Stu=}bYS4>w&Cba>0H0qSyOcOu;9ZcNWp51s zkT$?rvE4`ua6jQ*ND(zN(xGR}RjlKca_;?=KGcDxu~0=Et)Zw@0K zo+3@-R$69V4NGW0?52-)vfp1=^RMlue*F1S)BQH1iv4y*zKp2)d2hK&#nR8<o8NY>iF~_Iy7d@WOBnj;S?k&H#!ZAREO0e@E9uw!tHWK^t=8Sj zR?0DPS&EACLUL6L-tCFQ1y2gZJDS5?ele!04<-jUN7j#bpf`HwcCAKt)RZua7Afop zMGs*O$_4u!*bGtM^Q3;}>g74L+mq3vv8SQ0@K zvyIWD6UZDk02mt6$rx+^jt26=`QnLiF#BZ<7=-tRgI)FPpmt<)oF5($O2IjX+B;!G z1F#0(U}GbYAsxmMAmC^i5S7-)K9yf9cVFLjVMR9g!I)rDy3YCxed9RrxIF6f^D=D4GH`@m2ZR{uET z?BHNO8jTEtKte)7G(&VWNfcj*mVto*1gZ_u*4E%4G^h+B4MW!;Qk8!zSm3Bw3Z6{E zlZc>gMT{3Ihz199LjBJf2;_fdiPV4c#K{#)rmpIK~Oj6!<%hNIw#dMD-()LE1W+P|yK8 z3>Ht^wjBJMVrK`lAyR1=A{J+30S9wLH1T+En5mAg1yo=Eh@P&MzLu7yxtX4op5xA}2IPRCO?t!+X9zvoGZ%D;b1(Y*s+gO-7(fhnSIU^r{GM99afXqQXz`L$cAW!v1IOaB9=s#hui diff --git a/applications/external/hid_app/assets/Voldwn_6x6.png b/applications/external/hid_app/assets/Voldwn_6x6.png deleted file mode 100644 index d7a82a2df8262667a9a03419f437ff9b350e645f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3593 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 - -#define TAG "HidApp" - -enum HidDebugSubmenuIndex { - HidSubmenuIndexKeynote, - HidSubmenuIndexKeynoteVertical, - HidSubmenuIndexKeyboard, - HidSubmenuIndexMedia, - HidSubmenuIndexTikTok, - HidSubmenuIndexMouse, - HidSubmenuIndexMouseClicker, - HidSubmenuIndexMouseJiggler, -}; - -static void hid_submenu_callback(void* context, uint32_t index) { - furi_assert(context); - Hid* app = context; - if(index == HidSubmenuIndexKeynote) { - app->view_id = HidViewKeynote; - hid_keynote_set_orientation(app->hid_keynote, false); - view_dispatcher_switch_to_view(app->view_dispatcher, HidViewKeynote); - } else if(index == HidSubmenuIndexKeynoteVertical) { - app->view_id = HidViewKeynote; - hid_keynote_set_orientation(app->hid_keynote, true); - view_dispatcher_switch_to_view(app->view_dispatcher, HidViewKeynote); - } else if(index == HidSubmenuIndexKeyboard) { - app->view_id = HidViewKeyboard; - view_dispatcher_switch_to_view(app->view_dispatcher, HidViewKeyboard); - } else if(index == HidSubmenuIndexMedia) { - app->view_id = HidViewMedia; - view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMedia); - } else if(index == HidSubmenuIndexMouse) { - app->view_id = HidViewMouse; - view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMouse); - } else if(index == HidSubmenuIndexTikTok) { - app->view_id = BtHidViewTikTok; - view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewTikTok); - } else if(index == HidSubmenuIndexMouseClicker) { - app->view_id = HidViewMouseClicker; - view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMouseClicker); - } else if(index == HidSubmenuIndexMouseJiggler) { - app->view_id = HidViewMouseJiggler; - view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMouseJiggler); - } -} - -static void bt_hid_connection_status_changed_callback(BtStatus status, void* context) { - furi_assert(context); - Hid* hid = context; - bool connected = (status == BtStatusConnected); - if(hid->transport == HidTransportBle) { - if(connected) { - notification_internal_message(hid->notifications, &sequence_set_blue_255); - } else { - notification_internal_message(hid->notifications, &sequence_reset_blue); - } - } - hid_keynote_set_connected_status(hid->hid_keynote, connected); - hid_keyboard_set_connected_status(hid->hid_keyboard, connected); - hid_media_set_connected_status(hid->hid_media, connected); - hid_mouse_set_connected_status(hid->hid_mouse, connected); - hid_mouse_clicker_set_connected_status(hid->hid_mouse_clicker, connected); - hid_mouse_jiggler_set_connected_status(hid->hid_mouse_jiggler, connected); - hid_tiktok_set_connected_status(hid->hid_tiktok, connected); -} - -static void hid_dialog_callback(DialogExResult result, void* context) { - furi_assert(context); - Hid* app = context; - if(result == DialogExResultLeft) { - view_dispatcher_stop(app->view_dispatcher); - } else if(result == DialogExResultRight) { - view_dispatcher_switch_to_view(app->view_dispatcher, app->view_id); // Show last view - } else if(result == DialogExResultCenter) { - view_dispatcher_switch_to_view(app->view_dispatcher, HidViewSubmenu); - } -} - -static uint32_t hid_exit_confirm_view(void* context) { - UNUSED(context); - return HidViewExitConfirm; -} - -static uint32_t hid_exit(void* context) { - UNUSED(context); - return VIEW_NONE; -} - -Hid* hid_alloc(HidTransport transport) { - Hid* app = malloc(sizeof(Hid)); - app->transport = transport; - - // Gui - app->gui = furi_record_open(RECORD_GUI); - - // Bt - app->bt = furi_record_open(RECORD_BT); - - // Notifications - app->notifications = furi_record_open(RECORD_NOTIFICATION); - - // View dispatcher - app->view_dispatcher = view_dispatcher_alloc(); - view_dispatcher_enable_queue(app->view_dispatcher); - view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); - // Device Type Submenu view - app->device_type_submenu = submenu_alloc(); - submenu_add_item( - app->device_type_submenu, "Keynote", HidSubmenuIndexKeynote, hid_submenu_callback, app); - submenu_add_item( - app->device_type_submenu, - "Keynote Vertical", - HidSubmenuIndexKeynoteVertical, - hid_submenu_callback, - app); - submenu_add_item( - app->device_type_submenu, "Keyboard", HidSubmenuIndexKeyboard, hid_submenu_callback, app); - submenu_add_item( - app->device_type_submenu, "Media", HidSubmenuIndexMedia, hid_submenu_callback, app); - submenu_add_item( - app->device_type_submenu, "Mouse", HidSubmenuIndexMouse, hid_submenu_callback, app); - if(app->transport == HidTransportBle) { - submenu_add_item( - app->device_type_submenu, - "TikTok Controller", - HidSubmenuIndexTikTok, - hid_submenu_callback, - app); - } - submenu_add_item( - app->device_type_submenu, - "Mouse Clicker", - HidSubmenuIndexMouseClicker, - hid_submenu_callback, - app); - submenu_add_item( - app->device_type_submenu, - "Mouse Jiggler", - HidSubmenuIndexMouseJiggler, - hid_submenu_callback, - app); - view_set_previous_callback(submenu_get_view(app->device_type_submenu), hid_exit); - view_dispatcher_add_view( - app->view_dispatcher, HidViewSubmenu, submenu_get_view(app->device_type_submenu)); - app->view_id = HidViewSubmenu; - view_dispatcher_switch_to_view(app->view_dispatcher, app->view_id); - return app; -} - -Hid* hid_app_alloc_view(void* context) { - furi_assert(context); - Hid* app = context; - // Dialog view - app->dialog = dialog_ex_alloc(); - dialog_ex_set_result_callback(app->dialog, hid_dialog_callback); - dialog_ex_set_context(app->dialog, app); - dialog_ex_set_left_button_text(app->dialog, "Exit"); - dialog_ex_set_right_button_text(app->dialog, "Stay"); - dialog_ex_set_center_button_text(app->dialog, "Menu"); - dialog_ex_set_header(app->dialog, "Close Current App?", 16, 12, AlignLeft, AlignTop); - view_dispatcher_add_view( - app->view_dispatcher, HidViewExitConfirm, dialog_ex_get_view(app->dialog)); - - // Keynote view - app->hid_keynote = hid_keynote_alloc(app); - view_set_previous_callback(hid_keynote_get_view(app->hid_keynote), hid_exit_confirm_view); - view_dispatcher_add_view( - app->view_dispatcher, HidViewKeynote, hid_keynote_get_view(app->hid_keynote)); - - // Keyboard view - app->hid_keyboard = hid_keyboard_alloc(app); - view_set_previous_callback(hid_keyboard_get_view(app->hid_keyboard), hid_exit_confirm_view); - view_dispatcher_add_view( - app->view_dispatcher, HidViewKeyboard, hid_keyboard_get_view(app->hid_keyboard)); - - // Media view - app->hid_media = hid_media_alloc(app); - view_set_previous_callback(hid_media_get_view(app->hid_media), hid_exit_confirm_view); - view_dispatcher_add_view( - app->view_dispatcher, HidViewMedia, hid_media_get_view(app->hid_media)); - - // TikTok view - app->hid_tiktok = hid_tiktok_alloc(app); - view_set_previous_callback(hid_tiktok_get_view(app->hid_tiktok), hid_exit_confirm_view); - view_dispatcher_add_view( - app->view_dispatcher, BtHidViewTikTok, hid_tiktok_get_view(app->hid_tiktok)); - - // Mouse view - app->hid_mouse = hid_mouse_alloc(app); - view_set_previous_callback(hid_mouse_get_view(app->hid_mouse), hid_exit_confirm_view); - view_dispatcher_add_view( - app->view_dispatcher, HidViewMouse, hid_mouse_get_view(app->hid_mouse)); - - // Mouse clicker view - app->hid_mouse_clicker = hid_mouse_clicker_alloc(app); - view_set_previous_callback( - hid_mouse_clicker_get_view(app->hid_mouse_clicker), hid_exit_confirm_view); - view_dispatcher_add_view( - app->view_dispatcher, - HidViewMouseClicker, - hid_mouse_clicker_get_view(app->hid_mouse_clicker)); - - // Mouse jiggler view - app->hid_mouse_jiggler = hid_mouse_jiggler_alloc(app); - view_set_previous_callback( - hid_mouse_jiggler_get_view(app->hid_mouse_jiggler), hid_exit_confirm_view); - view_dispatcher_add_view( - app->view_dispatcher, - HidViewMouseJiggler, - hid_mouse_jiggler_get_view(app->hid_mouse_jiggler)); - - return app; -} - -void hid_free(Hid* app) { - furi_assert(app); - - // Reset notification - if(app->transport == HidTransportBle) { - notification_internal_message(app->notifications, &sequence_reset_blue); - } - - // Free views - view_dispatcher_remove_view(app->view_dispatcher, HidViewSubmenu); - submenu_free(app->device_type_submenu); - view_dispatcher_remove_view(app->view_dispatcher, HidViewExitConfirm); - dialog_ex_free(app->dialog); - view_dispatcher_remove_view(app->view_dispatcher, HidViewKeynote); - hid_keynote_free(app->hid_keynote); - view_dispatcher_remove_view(app->view_dispatcher, HidViewKeyboard); - hid_keyboard_free(app->hid_keyboard); - view_dispatcher_remove_view(app->view_dispatcher, HidViewMedia); - hid_media_free(app->hid_media); - view_dispatcher_remove_view(app->view_dispatcher, HidViewMouse); - hid_mouse_free(app->hid_mouse); - view_dispatcher_remove_view(app->view_dispatcher, HidViewMouseClicker); - hid_mouse_clicker_free(app->hid_mouse_clicker); - view_dispatcher_remove_view(app->view_dispatcher, HidViewMouseJiggler); - hid_mouse_jiggler_free(app->hid_mouse_jiggler); - view_dispatcher_remove_view(app->view_dispatcher, BtHidViewTikTok); - hid_tiktok_free(app->hid_tiktok); - view_dispatcher_free(app->view_dispatcher); - - // Close records - furi_record_close(RECORD_GUI); - app->gui = NULL; - furi_record_close(RECORD_NOTIFICATION); - app->notifications = NULL; - furi_record_close(RECORD_BT); - app->bt = NULL; - - // Free rest - free(app); -} - -void hid_hal_keyboard_press(Hid* instance, uint16_t event) { - furi_assert(instance); - if(instance->transport == HidTransportBle) { - furi_hal_bt_hid_kb_press(event); - } else if(instance->transport == HidTransportUsb) { - furi_hal_hid_kb_press(event); - } else { - furi_crash(NULL); - } -} - -void hid_hal_keyboard_release(Hid* instance, uint16_t event) { - furi_assert(instance); - if(instance->transport == HidTransportBle) { - furi_hal_bt_hid_kb_release(event); - } else if(instance->transport == HidTransportUsb) { - furi_hal_hid_kb_release(event); - } else { - furi_crash(NULL); - } -} - -void hid_hal_keyboard_release_all(Hid* instance) { - furi_assert(instance); - if(instance->transport == HidTransportBle) { - furi_hal_bt_hid_kb_release_all(); - } else if(instance->transport == HidTransportUsb) { - furi_hal_hid_kb_release_all(); - } else { - furi_crash(NULL); - } -} - -void hid_hal_consumer_key_press(Hid* instance, uint16_t event) { - furi_assert(instance); - if(instance->transport == HidTransportBle) { - furi_hal_bt_hid_consumer_key_press(event); - } else if(instance->transport == HidTransportUsb) { - furi_hal_hid_consumer_key_press(event); - } else { - furi_crash(NULL); - } -} - -void hid_hal_consumer_key_release(Hid* instance, uint16_t event) { - furi_assert(instance); - if(instance->transport == HidTransportBle) { - furi_hal_bt_hid_consumer_key_release(event); - } else if(instance->transport == HidTransportUsb) { - furi_hal_hid_consumer_key_release(event); - } else { - furi_crash(NULL); - } -} - -void hid_hal_consumer_key_release_all(Hid* instance) { - furi_assert(instance); - if(instance->transport == HidTransportBle) { - furi_hal_bt_hid_consumer_key_release_all(); - } else if(instance->transport == HidTransportUsb) { - furi_hal_hid_kb_release_all(); - } else { - furi_crash(NULL); - } -} - -void hid_hal_mouse_move(Hid* instance, int8_t dx, int8_t dy) { - furi_assert(instance); - if(instance->transport == HidTransportBle) { - furi_hal_bt_hid_mouse_move(dx, dy); - } else if(instance->transport == HidTransportUsb) { - furi_hal_hid_mouse_move(dx, dy); - } else { - furi_crash(NULL); - } -} - -void hid_hal_mouse_scroll(Hid* instance, int8_t delta) { - furi_assert(instance); - if(instance->transport == HidTransportBle) { - furi_hal_bt_hid_mouse_scroll(delta); - } else if(instance->transport == HidTransportUsb) { - furi_hal_hid_mouse_scroll(delta); - } else { - furi_crash(NULL); - } -} - -void hid_hal_mouse_press(Hid* instance, uint16_t event) { - furi_assert(instance); - if(instance->transport == HidTransportBle) { - furi_hal_bt_hid_mouse_press(event); - } else if(instance->transport == HidTransportUsb) { - furi_hal_hid_mouse_press(event); - } else { - furi_crash(NULL); - } -} - -void hid_hal_mouse_release(Hid* instance, uint16_t event) { - furi_assert(instance); - if(instance->transport == HidTransportBle) { - furi_hal_bt_hid_mouse_release(event); - } else if(instance->transport == HidTransportUsb) { - furi_hal_hid_mouse_release(event); - } else { - furi_crash(NULL); - } -} - -void hid_hal_mouse_release_all(Hid* instance) { - furi_assert(instance); - if(instance->transport == HidTransportBle) { - furi_hal_bt_hid_mouse_release_all(); - } else if(instance->transport == HidTransportUsb) { - furi_hal_hid_mouse_release(HID_MOUSE_BTN_LEFT); - furi_hal_hid_mouse_release(HID_MOUSE_BTN_RIGHT); - } else { - furi_crash(NULL); - } -} - -int32_t hid_usb_app(void* p) { - UNUSED(p); - Hid* app = hid_alloc(HidTransportUsb); - app = hid_app_alloc_view(app); - FuriHalUsbInterface* usb_mode_prev = furi_hal_usb_get_config(); - furi_hal_usb_unlock(); - furi_check(furi_hal_usb_set_config(&usb_hid, NULL) == true); - - bt_hid_connection_status_changed_callback(BtStatusConnected, app); - - view_dispatcher_run(app->view_dispatcher); - - furi_hal_usb_set_config(usb_mode_prev, NULL); - - hid_free(app); - - return 0; -} - -int32_t hid_ble_app(void* p) { - UNUSED(p); - Hid* app = hid_alloc(HidTransportBle); - app = hid_app_alloc_view(app); - - bt_disconnect(app->bt); - - // Wait 2nd core to update nvm storage - furi_delay_ms(200); - - // Migrate data from old sd-card folder - Storage* storage = furi_record_open(RECORD_STORAGE); - - storage_common_migrate( - storage, - EXT_PATH("apps/Tools/" HID_BT_KEYS_STORAGE_NAME), - APP_DATA_PATH(HID_BT_KEYS_STORAGE_NAME)); - - bt_keys_storage_set_storage_path(app->bt, APP_DATA_PATH(HID_BT_KEYS_STORAGE_NAME)); - - furi_record_close(RECORD_STORAGE); - - if(!bt_set_profile(app->bt, BtProfileHidKeyboard)) { - FURI_LOG_E(TAG, "Failed to switch to HID profile"); - } - - furi_hal_bt_start_advertising(); - bt_set_status_changed_callback(app->bt, bt_hid_connection_status_changed_callback, app); - - view_dispatcher_run(app->view_dispatcher); - - bt_set_status_changed_callback(app->bt, NULL, NULL); - - bt_disconnect(app->bt); - - // Wait 2nd core to update nvm storage - furi_delay_ms(200); - - bt_keys_storage_set_default_path(app->bt); - - if(!bt_set_profile(app->bt, BtProfileSerial)) { - FURI_LOG_E(TAG, "Failed to switch to Serial profile"); - } - - hid_free(app); - - return 0; -} diff --git a/applications/external/hid_app/hid.h b/applications/external/hid_app/hid.h deleted file mode 100644 index 49d8b4e045d..00000000000 --- a/applications/external/hid_app/hid.h +++ /dev/null @@ -1,67 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include "views/hid_keynote.h" -#include "views/hid_keyboard.h" -#include "views/hid_media.h" -#include "views/hid_mouse.h" -#include "views/hid_mouse_clicker.h" -#include "views/hid_mouse_jiggler.h" -#include "views/hid_tiktok.h" - -#define HID_BT_KEYS_STORAGE_NAME ".bt_hid.keys" - -typedef enum { - HidTransportUsb, - HidTransportBle, -} HidTransport; - -typedef struct Hid Hid; - -struct Hid { - Bt* bt; - Gui* gui; - NotificationApp* notifications; - ViewDispatcher* view_dispatcher; - Submenu* device_type_submenu; - DialogEx* dialog; - HidKeynote* hid_keynote; - HidKeyboard* hid_keyboard; - HidMedia* hid_media; - HidMouse* hid_mouse; - HidMouseClicker* hid_mouse_clicker; - HidMouseJiggler* hid_mouse_jiggler; - HidTikTok* hid_tiktok; - - HidTransport transport; - uint32_t view_id; -}; - -void hid_hal_keyboard_press(Hid* instance, uint16_t event); -void hid_hal_keyboard_release(Hid* instance, uint16_t event); -void hid_hal_keyboard_release_all(Hid* instance); - -void hid_hal_consumer_key_press(Hid* instance, uint16_t event); -void hid_hal_consumer_key_release(Hid* instance, uint16_t event); -void hid_hal_consumer_key_release_all(Hid* instance); - -void hid_hal_mouse_move(Hid* instance, int8_t dx, int8_t dy); -void hid_hal_mouse_scroll(Hid* instance, int8_t delta); -void hid_hal_mouse_press(Hid* instance, uint16_t event); -void hid_hal_mouse_release(Hid* instance, uint16_t event); -void hid_hal_mouse_release_all(Hid* instance); \ No newline at end of file diff --git a/applications/external/hid_app/hid_ble_10px.png b/applications/external/hid_app/hid_ble_10px.png deleted file mode 100644 index d4d30afe0465e3bd0d87355a09bbdfc6c44aa5d9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 151 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2VGmzZ%#=aj&u?6^qxc>kDAIJlDSNs& zhE&W+PH5C8xG diff --git a/applications/external/hid_app/hid_usb_10px.png b/applications/external/hid_app/hid_usb_10px.png deleted file mode 100644 index 415de7d2304fe982c025b2b9a942abbf0a2b6dd0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 969 zcmaJ=J#W)M7(OY0prTdS3e(9IQYsOjefe;0)l|h!Xha;MG(g5)>`P;{_8I$1oJb(V z#>Co{9{@i91|(QuX5%*?v9pwOnxqT_55D(az0dQ0J@>lZy1%+|YXtzX+Ss!@;>_%o zt2y!i@N?&lIBxPQduOaNrjU+e?;YX%)UR2L%LyN@}>atRF6-9xXE~}dAVr@YBcOX_U zM#>gat3`~BQpG5%aP~Eh*gj89{x|#<%&i_M$ zU=f}04!x-NpTtRb98uJv2|I~hvAe-WmMSu=m=ez7E@Q{@LAHmCvt-C3h|9793l4Gp zF!O9qA&z4-!i1C1r48GZ1c~hXo`JDuS-aJ0wOrd$)taqWN;ON>tZH4WV;fs@tj*k$ zfQEdI^)9g5QfwxOAQG8v8vD3;Z_1q-{ zl$i_hipxU&G!&YTg}8q|eDGX6j4SPCw{~`RCd@~lzrPU2?S{SEO@H(cJnv<$o(G-l ph0_~rZ>7^_`EovpzT_W+OY7j;8rXcd{ -#include -#include -#include "../hid.h" -#include "hid_icons.h" - -#define TAG "HidKeyboard" - -struct HidKeyboard { - View* view; - Hid* hid; -}; - -typedef struct { - bool shift; - bool alt; - bool ctrl; - bool gui; - uint8_t x; - uint8_t y; - uint8_t last_key_code; - uint16_t modifier_code; - bool ok_pressed; - bool back_pressed; - bool connected; - char key_string[5]; - HidTransport transport; -} HidKeyboardModel; - -typedef struct { - uint8_t width; - char* key; - const Icon* icon; - char* shift_key; - uint8_t value; -} HidKeyboardKey; - -typedef struct { - int8_t x; - int8_t y; -} HidKeyboardPoint; -// 4 BY 12 -#define MARGIN_TOP 0 -#define MARGIN_LEFT 4 -#define KEY_WIDTH 9 -#define KEY_HEIGHT 12 -#define KEY_PADDING 1 -#define ROW_COUNT 7 -#define COLUMN_COUNT 12 - -// 0 width items are not drawn, but there value is used -const HidKeyboardKey hid_keyboard_keyset[ROW_COUNT][COLUMN_COUNT] = { - { - {.width = 1, .icon = &I_ButtonF1_5x8, .value = HID_KEYBOARD_F1}, - {.width = 1, .icon = &I_ButtonF2_5x8, .value = HID_KEYBOARD_F2}, - {.width = 1, .icon = &I_ButtonF3_5x8, .value = HID_KEYBOARD_F3}, - {.width = 1, .icon = &I_ButtonF4_5x8, .value = HID_KEYBOARD_F4}, - {.width = 1, .icon = &I_ButtonF5_5x8, .value = HID_KEYBOARD_F5}, - {.width = 1, .icon = &I_ButtonF6_5x8, .value = HID_KEYBOARD_F6}, - {.width = 1, .icon = &I_ButtonF7_5x8, .value = HID_KEYBOARD_F7}, - {.width = 1, .icon = &I_ButtonF8_5x8, .value = HID_KEYBOARD_F8}, - {.width = 1, .icon = &I_ButtonF9_5x8, .value = HID_KEYBOARD_F9}, - {.width = 1, .icon = &I_ButtonF10_5x8, .value = HID_KEYBOARD_F10}, - {.width = 1, .icon = &I_ButtonF11_5x8, .value = HID_KEYBOARD_F11}, - {.width = 1, .icon = &I_ButtonF12_5x8, .value = HID_KEYBOARD_F12}, - }, - { - {.width = 1, .icon = NULL, .key = "1", .shift_key = "!", .value = HID_KEYBOARD_1}, - {.width = 1, .icon = NULL, .key = "2", .shift_key = "@", .value = HID_KEYBOARD_2}, - {.width = 1, .icon = NULL, .key = "3", .shift_key = "#", .value = HID_KEYBOARD_3}, - {.width = 1, .icon = NULL, .key = "4", .shift_key = "$", .value = HID_KEYBOARD_4}, - {.width = 1, .icon = NULL, .key = "5", .shift_key = "%", .value = HID_KEYBOARD_5}, - {.width = 1, .icon = NULL, .key = "6", .shift_key = "^", .value = HID_KEYBOARD_6}, - {.width = 1, .icon = NULL, .key = "7", .shift_key = "&", .value = HID_KEYBOARD_7}, - {.width = 1, .icon = NULL, .key = "8", .shift_key = "*", .value = HID_KEYBOARD_8}, - {.width = 1, .icon = NULL, .key = "9", .shift_key = "(", .value = HID_KEYBOARD_9}, - {.width = 1, .icon = NULL, .key = "0", .shift_key = ")", .value = HID_KEYBOARD_0}, - {.width = 2, .icon = &I_Pin_arrow_left_9x7, .value = HID_KEYBOARD_DELETE}, - {.width = 0, .value = HID_KEYBOARD_DELETE}, - }, - { - {.width = 1, .icon = NULL, .key = "q", .shift_key = "Q", .value = HID_KEYBOARD_Q}, - {.width = 1, .icon = NULL, .key = "w", .shift_key = "W", .value = HID_KEYBOARD_W}, - {.width = 1, .icon = NULL, .key = "e", .shift_key = "E", .value = HID_KEYBOARD_E}, - {.width = 1, .icon = NULL, .key = "r", .shift_key = "R", .value = HID_KEYBOARD_R}, - {.width = 1, .icon = NULL, .key = "t", .shift_key = "T", .value = HID_KEYBOARD_T}, - {.width = 1, .icon = NULL, .key = "y", .shift_key = "Y", .value = HID_KEYBOARD_Y}, - {.width = 1, .icon = NULL, .key = "u", .shift_key = "U", .value = HID_KEYBOARD_U}, - {.width = 1, .icon = NULL, .key = "i", .shift_key = "I", .value = HID_KEYBOARD_I}, - {.width = 1, .icon = NULL, .key = "o", .shift_key = "O", .value = HID_KEYBOARD_O}, - {.width = 1, .icon = NULL, .key = "p", .shift_key = "P", .value = HID_KEYBOARD_P}, - {.width = 1, .icon = NULL, .key = "[", .shift_key = "{", .value = HID_KEYBOARD_OPEN_BRACKET}, - {.width = 1, - .icon = NULL, - .key = "]", - .shift_key = "}", - .value = HID_KEYBOARD_CLOSE_BRACKET}, - }, - { - {.width = 1, .icon = NULL, .key = "a", .shift_key = "A", .value = HID_KEYBOARD_A}, - {.width = 1, .icon = NULL, .key = "s", .shift_key = "S", .value = HID_KEYBOARD_S}, - {.width = 1, .icon = NULL, .key = "d", .shift_key = "D", .value = HID_KEYBOARD_D}, - {.width = 1, .icon = NULL, .key = "f", .shift_key = "F", .value = HID_KEYBOARD_F}, - {.width = 1, .icon = NULL, .key = "g", .shift_key = "G", .value = HID_KEYBOARD_G}, - {.width = 1, .icon = NULL, .key = "h", .shift_key = "H", .value = HID_KEYBOARD_H}, - {.width = 1, .icon = NULL, .key = "j", .shift_key = "J", .value = HID_KEYBOARD_J}, - {.width = 1, .icon = NULL, .key = "k", .shift_key = "K", .value = HID_KEYBOARD_K}, - {.width = 1, .icon = NULL, .key = "l", .shift_key = "L", .value = HID_KEYBOARD_L}, - {.width = 1, .icon = NULL, .key = ";", .shift_key = ":", .value = HID_KEYBOARD_SEMICOLON}, - {.width = 2, .icon = &I_Pin_arrow_right_9x7, .value = HID_KEYBOARD_RETURN}, - {.width = 0, .value = HID_KEYBOARD_RETURN}, - }, - { - {.width = 1, .icon = NULL, .key = "z", .shift_key = "Z", .value = HID_KEYBOARD_Z}, - {.width = 1, .icon = NULL, .key = "x", .shift_key = "X", .value = HID_KEYBOARD_X}, - {.width = 1, .icon = NULL, .key = "c", .shift_key = "C", .value = HID_KEYBOARD_C}, - {.width = 1, .icon = NULL, .key = "v", .shift_key = "V", .value = HID_KEYBOARD_V}, - {.width = 1, .icon = NULL, .key = "b", .shift_key = "B", .value = HID_KEYBOARD_B}, - {.width = 1, .icon = NULL, .key = "n", .shift_key = "N", .value = HID_KEYBOARD_N}, - {.width = 1, .icon = NULL, .key = "m", .shift_key = "M", .value = HID_KEYBOARD_M}, - {.width = 1, .icon = NULL, .key = "/", .shift_key = "?", .value = HID_KEYBOARD_SLASH}, - {.width = 1, .icon = NULL, .key = "\\", .shift_key = "|", .value = HID_KEYBOARD_BACKSLASH}, - {.width = 1, .icon = NULL, .key = "`", .shift_key = "~", .value = HID_KEYBOARD_GRAVE_ACCENT}, - {.width = 1, .icon = &I_ButtonUp_7x4, .value = HID_KEYBOARD_UP_ARROW}, - {.width = 1, .icon = NULL, .key = "-", .shift_key = "_", .value = HID_KEYBOARD_MINUS}, - }, - { - {.width = 1, .icon = &I_Pin_arrow_up_7x9, .value = HID_KEYBOARD_L_SHIFT}, - {.width = 1, .icon = NULL, .key = ",", .shift_key = "<", .value = HID_KEYBOARD_COMMA}, - {.width = 1, .icon = NULL, .key = ".", .shift_key = ">", .value = HID_KEYBOARD_DOT}, - {.width = 4, .icon = NULL, .key = " ", .value = HID_KEYBOARD_SPACEBAR}, - {.width = 0, .value = HID_KEYBOARD_SPACEBAR}, - {.width = 0, .value = HID_KEYBOARD_SPACEBAR}, - {.width = 0, .value = HID_KEYBOARD_SPACEBAR}, - {.width = 1, .icon = NULL, .key = "'", .shift_key = "\"", .value = HID_KEYBOARD_APOSTROPHE}, - {.width = 1, .icon = NULL, .key = "=", .shift_key = "+", .value = HID_KEYBOARD_EQUAL_SIGN}, - {.width = 1, .icon = &I_ButtonLeft_4x7, .value = HID_KEYBOARD_LEFT_ARROW}, - {.width = 1, .icon = &I_ButtonDown_7x4, .value = HID_KEYBOARD_DOWN_ARROW}, - {.width = 1, .icon = &I_ButtonRight_4x7, .value = HID_KEYBOARD_RIGHT_ARROW}, - }, - { - {.width = 3, .icon = NULL, .key = "Ctrl", .value = HID_KEYBOARD_L_CTRL}, - {.width = 0, .icon = NULL, .value = HID_KEYBOARD_L_CTRL}, - {.width = 0, .icon = NULL, .value = HID_KEYBOARD_L_CTRL}, - {.width = 3, .icon = NULL, .key = "Alt", .value = HID_KEYBOARD_L_ALT}, - {.width = 0, .icon = NULL, .value = HID_KEYBOARD_L_ALT}, - {.width = 0, .icon = NULL, .value = HID_KEYBOARD_L_ALT}, - {.width = 3, .icon = NULL, .key = "Cmd", .value = HID_KEYBOARD_L_GUI}, - {.width = 0, .icon = NULL, .value = HID_KEYBOARD_L_GUI}, - {.width = 0, .icon = NULL, .value = HID_KEYBOARD_L_GUI}, - {.width = 3, .icon = NULL, .key = "Tab", .value = HID_KEYBOARD_TAB}, - {.width = 0, .icon = NULL, .value = HID_KEYBOARD_TAB}, - {.width = 0, .icon = NULL, .value = HID_KEYBOARD_TAB}, - }, -}; - -static void hid_keyboard_to_upper(char* str) { - while(*str) { - *str = toupper((unsigned char)*str); - str++; - } -} - -static void hid_keyboard_draw_key( - Canvas* canvas, - HidKeyboardModel* model, - uint8_t x, - uint8_t y, - HidKeyboardKey key, - bool selected) { - if(!key.width) return; - - canvas_set_color(canvas, ColorBlack); - uint8_t keyWidth = KEY_WIDTH * key.width + KEY_PADDING * (key.width - 1); - if(selected) { - // Draw a filled box - elements_slightly_rounded_box( - canvas, - MARGIN_LEFT + x * (KEY_WIDTH + KEY_PADDING), - MARGIN_TOP + y * (KEY_HEIGHT + KEY_PADDING), - keyWidth, - KEY_HEIGHT); - canvas_set_color(canvas, ColorWhite); - } else { - // Draw a framed box - elements_slightly_rounded_frame( - canvas, - MARGIN_LEFT + x * (KEY_WIDTH + KEY_PADDING), - MARGIN_TOP + y * (KEY_HEIGHT + KEY_PADDING), - keyWidth, - KEY_HEIGHT); - } - if(key.icon != NULL) { - // Draw the icon centered on the button - canvas_draw_icon( - canvas, - MARGIN_LEFT + x * (KEY_WIDTH + KEY_PADDING) + keyWidth / 2 - key.icon->width / 2, - MARGIN_TOP + y * (KEY_HEIGHT + KEY_PADDING) + KEY_HEIGHT / 2 - key.icon->height / 2, - key.icon); - } else { - // If shift is toggled use the shift key when available - strcpy(model->key_string, (model->shift && key.shift_key != 0) ? key.shift_key : key.key); - // Upper case if ctrl or alt was toggled true - if((model->ctrl && key.value == HID_KEYBOARD_L_CTRL) || - (model->alt && key.value == HID_KEYBOARD_L_ALT) || - (model->gui && key.value == HID_KEYBOARD_L_GUI)) { - hid_keyboard_to_upper(model->key_string); - } - canvas_draw_str_aligned( - canvas, - MARGIN_LEFT + x * (KEY_WIDTH + KEY_PADDING) + keyWidth / 2 + 1, - MARGIN_TOP + y * (KEY_HEIGHT + KEY_PADDING) + KEY_HEIGHT / 2, - AlignCenter, - AlignCenter, - model->key_string); - } -} - -static void hid_keyboard_draw_callback(Canvas* canvas, void* context) { - furi_assert(context); - HidKeyboardModel* model = context; - - // Header - if((!model->connected) && (model->transport == HidTransportBle)) { - canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); - canvas_set_font(canvas, FontPrimary); - elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Keyboard"); - - canvas_draw_icon(canvas, 68, 3, &I_Pin_back_arrow_10x8); - canvas_set_font(canvas, FontSecondary); - elements_multiline_text_aligned(canvas, 127, 4, AlignRight, AlignTop, "Hold to exit"); - - elements_multiline_text_aligned( - canvas, 4, 60, AlignLeft, AlignBottom, "Waiting for Connection..."); - return; // Dont render the keyboard if we are not yet connected - } - - canvas_set_font(canvas, FontKeyboard); - // Start shifting the all keys up if on the next row (Scrolling) - uint8_t initY = model->y == 0 ? 0 : 1; - - if(model->y > 5) { - initY = model->y - 4; - } - - for(uint8_t y = initY; y < ROW_COUNT; y++) { - const HidKeyboardKey* keyboardKeyRow = hid_keyboard_keyset[y]; - uint8_t x = 0; - for(uint8_t i = 0; i < COLUMN_COUNT; i++) { - HidKeyboardKey key = keyboardKeyRow[i]; - // Select when the button is hovered - // Select if the button is hovered within its width - // Select if back is clicked and its the backspace key - // Deselect when the button clicked or not hovered - bool keySelected = (x <= model->x && model->x < (x + key.width)) && y == model->y; - bool backSelected = model->back_pressed && key.value == HID_KEYBOARD_DELETE; - hid_keyboard_draw_key( - canvas, - model, - x, - y - initY, - key, - (!model->ok_pressed && keySelected) || backSelected); - x += key.width; - } - } -} - -static uint8_t hid_keyboard_get_selected_key(HidKeyboardModel* model) { - HidKeyboardKey key = hid_keyboard_keyset[model->y][model->x]; - return key.value; -} - -static void hid_keyboard_get_select_key(HidKeyboardModel* model, HidKeyboardPoint delta) { - // Keep going until a valid spot is found, this allows for nulls and zero width keys in the map - do { - const int delta_sum = model->y + delta.y; - model->y = delta_sum < 0 ? ROW_COUNT - 1 : delta_sum % ROW_COUNT; - } while(delta.y != 0 && hid_keyboard_keyset[model->y][model->x].value == 0); - - do { - const int delta_sum = model->x + delta.x; - model->x = delta_sum < 0 ? COLUMN_COUNT - 1 : delta_sum % COLUMN_COUNT; - } while(delta.x != 0 && hid_keyboard_keyset[model->y][model->x].width == - 0); // Skip zero width keys, pretend they are one key -} - -static void hid_keyboard_process(HidKeyboard* hid_keyboard, InputEvent* event) { - with_view_model( - hid_keyboard->view, - HidKeyboardModel * model, - { - if(event->key == InputKeyOk) { - if(event->type == InputTypePress) { - model->ok_pressed = true; - } else if(event->type == InputTypeLong || event->type == InputTypeShort) { - model->last_key_code = hid_keyboard_get_selected_key(model); - - // Toggle the modifier key when clicked, and click the key - if(model->last_key_code == HID_KEYBOARD_L_SHIFT) { - model->shift = !model->shift; - if(model->shift) - model->modifier_code |= KEY_MOD_LEFT_SHIFT; - else - model->modifier_code &= ~KEY_MOD_LEFT_SHIFT; - } else if(model->last_key_code == HID_KEYBOARD_L_ALT) { - model->alt = !model->alt; - if(model->alt) - model->modifier_code |= KEY_MOD_LEFT_ALT; - else - model->modifier_code &= ~KEY_MOD_LEFT_ALT; - } else if(model->last_key_code == HID_KEYBOARD_L_CTRL) { - model->ctrl = !model->ctrl; - if(model->ctrl) - model->modifier_code |= KEY_MOD_LEFT_CTRL; - else - model->modifier_code &= ~KEY_MOD_LEFT_CTRL; - } else if(model->last_key_code == HID_KEYBOARD_L_GUI) { - model->gui = !model->gui; - if(model->gui) - model->modifier_code |= KEY_MOD_LEFT_GUI; - else - model->modifier_code &= ~KEY_MOD_LEFT_GUI; - } - hid_hal_keyboard_press( - hid_keyboard->hid, model->modifier_code | model->last_key_code); - } else if(event->type == InputTypeRelease) { - // Release happens after short and long presses - hid_hal_keyboard_release( - hid_keyboard->hid, model->modifier_code | model->last_key_code); - model->ok_pressed = false; - } - } else if(event->key == InputKeyBack) { - // If back is pressed for a short time, backspace - if(event->type == InputTypePress) { - model->back_pressed = true; - } else if(event->type == InputTypeShort) { - hid_hal_keyboard_press(hid_keyboard->hid, HID_KEYBOARD_DELETE); - hid_hal_keyboard_release(hid_keyboard->hid, HID_KEYBOARD_DELETE); - } else if(event->type == InputTypeRelease) { - model->back_pressed = false; - } - } else if(event->type == InputTypePress || event->type == InputTypeRepeat) { - // Cycle the selected keys - if(event->key == InputKeyUp) { - hid_keyboard_get_select_key(model, (HidKeyboardPoint){.x = 0, .y = -1}); - } else if(event->key == InputKeyDown) { - hid_keyboard_get_select_key(model, (HidKeyboardPoint){.x = 0, .y = 1}); - } else if(event->key == InputKeyLeft) { - hid_keyboard_get_select_key(model, (HidKeyboardPoint){.x = -1, .y = 0}); - } else if(event->key == InputKeyRight) { - hid_keyboard_get_select_key(model, (HidKeyboardPoint){.x = 1, .y = 0}); - } - } - }, - true); -} - -static bool hid_keyboard_input_callback(InputEvent* event, void* context) { - furi_assert(context); - HidKeyboard* hid_keyboard = context; - bool consumed = false; - - if(event->type == InputTypeLong && event->key == InputKeyBack) { - hid_hal_keyboard_release_all(hid_keyboard->hid); - } else { - hid_keyboard_process(hid_keyboard, event); - consumed = true; - } - - return consumed; -} - -HidKeyboard* hid_keyboard_alloc(Hid* bt_hid) { - HidKeyboard* hid_keyboard = malloc(sizeof(HidKeyboard)); - hid_keyboard->view = view_alloc(); - hid_keyboard->hid = bt_hid; - view_set_context(hid_keyboard->view, hid_keyboard); - view_allocate_model(hid_keyboard->view, ViewModelTypeLocking, sizeof(HidKeyboardModel)); - view_set_draw_callback(hid_keyboard->view, hid_keyboard_draw_callback); - view_set_input_callback(hid_keyboard->view, hid_keyboard_input_callback); - - with_view_model( - hid_keyboard->view, - HidKeyboardModel * model, - { - model->transport = bt_hid->transport; - model->y = 1; - }, - true); - - return hid_keyboard; -} - -void hid_keyboard_free(HidKeyboard* hid_keyboard) { - furi_assert(hid_keyboard); - view_free(hid_keyboard->view); - free(hid_keyboard); -} - -View* hid_keyboard_get_view(HidKeyboard* hid_keyboard) { - furi_assert(hid_keyboard); - return hid_keyboard->view; -} - -void hid_keyboard_set_connected_status(HidKeyboard* hid_keyboard, bool connected) { - furi_assert(hid_keyboard); - with_view_model( - hid_keyboard->view, HidKeyboardModel * model, { model->connected = connected; }, true); -} diff --git a/applications/external/hid_app/views/hid_keyboard.h b/applications/external/hid_app/views/hid_keyboard.h deleted file mode 100644 index 7127713643b..00000000000 --- a/applications/external/hid_app/views/hid_keyboard.h +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - -#include - -typedef struct Hid Hid; -typedef struct HidKeyboard HidKeyboard; - -HidKeyboard* hid_keyboard_alloc(Hid* bt_hid); - -void hid_keyboard_free(HidKeyboard* hid_keyboard); - -View* hid_keyboard_get_view(HidKeyboard* hid_keyboard); - -void hid_keyboard_set_connected_status(HidKeyboard* hid_keyboard, bool connected); diff --git a/applications/external/hid_app/views/hid_keynote.c b/applications/external/hid_app/views/hid_keynote.c deleted file mode 100644 index 543363bf67b..00000000000 --- a/applications/external/hid_app/views/hid_keynote.c +++ /dev/null @@ -1,312 +0,0 @@ -#include "hid_keynote.h" -#include -#include "../hid.h" - -#include "hid_icons.h" - -#define TAG "HidKeynote" - -struct HidKeynote { - View* view; - Hid* hid; -}; - -typedef struct { - bool left_pressed; - bool up_pressed; - bool right_pressed; - bool down_pressed; - bool ok_pressed; - bool back_pressed; - bool connected; - HidTransport transport; -} HidKeynoteModel; - -static void hid_keynote_draw_arrow(Canvas* canvas, uint8_t x, uint8_t y, CanvasDirection dir) { - canvas_draw_triangle(canvas, x, y, 5, 3, dir); - if(dir == CanvasDirectionBottomToTop) { - canvas_draw_line(canvas, x, y + 6, x, y - 1); - } else if(dir == CanvasDirectionTopToBottom) { - canvas_draw_line(canvas, x, y - 6, x, y + 1); - } else if(dir == CanvasDirectionRightToLeft) { - canvas_draw_line(canvas, x + 6, y, x - 1, y); - } else if(dir == CanvasDirectionLeftToRight) { - canvas_draw_line(canvas, x - 6, y, x + 1, y); - } -} - -static void hid_keynote_draw_callback(Canvas* canvas, void* context) { - furi_assert(context); - HidKeynoteModel* model = context; - - // Header - if(model->transport == HidTransportBle) { - if(model->connected) { - canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); - } else { - canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); - } - } - - canvas_set_font(canvas, FontPrimary); - elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Keynote"); - - canvas_draw_icon(canvas, 68, 2, &I_Pin_back_arrow_10x8); - canvas_set_font(canvas, FontSecondary); - elements_multiline_text_aligned(canvas, 127, 3, AlignRight, AlignTop, "Hold to exit"); - - // Up - canvas_draw_icon(canvas, 21, 24, &I_Button_18x18); - if(model->up_pressed) { - elements_slightly_rounded_box(canvas, 24, 26, 13, 13); - canvas_set_color(canvas, ColorWhite); - } - hid_keynote_draw_arrow(canvas, 30, 30, CanvasDirectionBottomToTop); - canvas_set_color(canvas, ColorBlack); - - // Down - canvas_draw_icon(canvas, 21, 45, &I_Button_18x18); - if(model->down_pressed) { - elements_slightly_rounded_box(canvas, 24, 47, 13, 13); - canvas_set_color(canvas, ColorWhite); - } - hid_keynote_draw_arrow(canvas, 30, 55, CanvasDirectionTopToBottom); - canvas_set_color(canvas, ColorBlack); - - // Left - canvas_draw_icon(canvas, 0, 45, &I_Button_18x18); - if(model->left_pressed) { - elements_slightly_rounded_box(canvas, 3, 47, 13, 13); - canvas_set_color(canvas, ColorWhite); - } - hid_keynote_draw_arrow(canvas, 7, 53, CanvasDirectionRightToLeft); - canvas_set_color(canvas, ColorBlack); - - // Right - canvas_draw_icon(canvas, 42, 45, &I_Button_18x18); - if(model->right_pressed) { - elements_slightly_rounded_box(canvas, 45, 47, 13, 13); - canvas_set_color(canvas, ColorWhite); - } - hid_keynote_draw_arrow(canvas, 53, 53, CanvasDirectionLeftToRight); - canvas_set_color(canvas, ColorBlack); - - // Ok - canvas_draw_icon(canvas, 63, 25, &I_Space_65x18); - if(model->ok_pressed) { - elements_slightly_rounded_box(canvas, 66, 27, 60, 13); - canvas_set_color(canvas, ColorWhite); - } - canvas_draw_icon(canvas, 74, 29, &I_Ok_btn_9x9); - elements_multiline_text_aligned(canvas, 91, 36, AlignLeft, AlignBottom, "Space"); - canvas_set_color(canvas, ColorBlack); - - // Back - canvas_draw_icon(canvas, 63, 45, &I_Space_65x18); - if(model->back_pressed) { - elements_slightly_rounded_box(canvas, 66, 47, 60, 13); - canvas_set_color(canvas, ColorWhite); - } - canvas_draw_icon(canvas, 74, 49, &I_Pin_back_arrow_10x8); - elements_multiline_text_aligned(canvas, 91, 57, AlignLeft, AlignBottom, "Back"); -} - -static void hid_keynote_draw_vertical_callback(Canvas* canvas, void* context) { - furi_assert(context); - HidKeynoteModel* model = context; - - // Header - if(model->transport == HidTransportBle) { - if(model->connected) { - canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); - } else { - canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); - } - canvas_set_font(canvas, FontPrimary); - elements_multiline_text_aligned(canvas, 20, 3, AlignLeft, AlignTop, "Keynote"); - } else { - canvas_set_font(canvas, FontPrimary); - elements_multiline_text_aligned(canvas, 12, 3, AlignLeft, AlignTop, "Keynote"); - } - - canvas_draw_icon(canvas, 2, 18, &I_Pin_back_arrow_10x8); - canvas_set_font(canvas, FontSecondary); - elements_multiline_text_aligned(canvas, 15, 19, AlignLeft, AlignTop, "Hold to exit"); - - const uint8_t x_2 = 23; - const uint8_t x_1 = 2; - const uint8_t x_3 = 44; - - const uint8_t y_1 = 44; - const uint8_t y_2 = 65; - - // Up - canvas_draw_icon(canvas, x_2, y_1, &I_Button_18x18); - if(model->up_pressed) { - elements_slightly_rounded_box(canvas, x_2 + 3, y_1 + 2, 13, 13); - canvas_set_color(canvas, ColorWhite); - } - hid_keynote_draw_arrow(canvas, x_2 + 9, y_1 + 6, CanvasDirectionBottomToTop); - canvas_set_color(canvas, ColorBlack); - - // Down - canvas_draw_icon(canvas, x_2, y_2, &I_Button_18x18); - if(model->down_pressed) { - elements_slightly_rounded_box(canvas, x_2 + 3, y_2 + 2, 13, 13); - canvas_set_color(canvas, ColorWhite); - } - hid_keynote_draw_arrow(canvas, x_2 + 9, y_2 + 10, CanvasDirectionTopToBottom); - canvas_set_color(canvas, ColorBlack); - - // Left - canvas_draw_icon(canvas, x_1, y_2, &I_Button_18x18); - if(model->left_pressed) { - elements_slightly_rounded_box(canvas, x_1 + 3, y_2 + 2, 13, 13); - canvas_set_color(canvas, ColorWhite); - } - hid_keynote_draw_arrow(canvas, x_1 + 7, y_2 + 8, CanvasDirectionRightToLeft); - canvas_set_color(canvas, ColorBlack); - - // Right - canvas_draw_icon(canvas, x_3, y_2, &I_Button_18x18); - if(model->right_pressed) { - elements_slightly_rounded_box(canvas, x_3 + 3, y_2 + 2, 13, 13); - canvas_set_color(canvas, ColorWhite); - } - hid_keynote_draw_arrow(canvas, x_3 + 11, y_2 + 8, CanvasDirectionLeftToRight); - canvas_set_color(canvas, ColorBlack); - - // Ok - canvas_draw_icon(canvas, 2, 86, &I_Space_60x18); - if(model->ok_pressed) { - elements_slightly_rounded_box(canvas, 5, 88, 55, 13); - canvas_set_color(canvas, ColorWhite); - } - canvas_draw_icon(canvas, 11, 90, &I_Ok_btn_9x9); - elements_multiline_text_aligned(canvas, 26, 98, AlignLeft, AlignBottom, "Space"); - canvas_set_color(canvas, ColorBlack); - - // Back - canvas_draw_icon(canvas, 2, 107, &I_Space_60x18); - if(model->back_pressed) { - elements_slightly_rounded_box(canvas, 5, 109, 55, 13); - canvas_set_color(canvas, ColorWhite); - } - canvas_draw_icon(canvas, 11, 111, &I_Pin_back_arrow_10x8); - elements_multiline_text_aligned(canvas, 26, 119, AlignLeft, AlignBottom, "Back"); -} - -static void hid_keynote_process(HidKeynote* hid_keynote, InputEvent* event) { - with_view_model( - hid_keynote->view, - HidKeynoteModel * model, - { - if(event->type == InputTypePress) { - if(event->key == InputKeyUp) { - model->up_pressed = true; - hid_hal_keyboard_press(hid_keynote->hid, HID_KEYBOARD_UP_ARROW); - } else if(event->key == InputKeyDown) { - model->down_pressed = true; - hid_hal_keyboard_press(hid_keynote->hid, HID_KEYBOARD_DOWN_ARROW); - } else if(event->key == InputKeyLeft) { - model->left_pressed = true; - hid_hal_keyboard_press(hid_keynote->hid, HID_KEYBOARD_LEFT_ARROW); - } else if(event->key == InputKeyRight) { - model->right_pressed = true; - hid_hal_keyboard_press(hid_keynote->hid, HID_KEYBOARD_RIGHT_ARROW); - } else if(event->key == InputKeyOk) { - model->ok_pressed = true; - hid_hal_keyboard_press(hid_keynote->hid, HID_KEYBOARD_SPACEBAR); - } else if(event->key == InputKeyBack) { - model->back_pressed = true; - } - } else if(event->type == InputTypeRelease) { - if(event->key == InputKeyUp) { - model->up_pressed = false; - hid_hal_keyboard_release(hid_keynote->hid, HID_KEYBOARD_UP_ARROW); - } else if(event->key == InputKeyDown) { - model->down_pressed = false; - hid_hal_keyboard_release(hid_keynote->hid, HID_KEYBOARD_DOWN_ARROW); - } else if(event->key == InputKeyLeft) { - model->left_pressed = false; - hid_hal_keyboard_release(hid_keynote->hid, HID_KEYBOARD_LEFT_ARROW); - } else if(event->key == InputKeyRight) { - model->right_pressed = false; - hid_hal_keyboard_release(hid_keynote->hid, HID_KEYBOARD_RIGHT_ARROW); - } else if(event->key == InputKeyOk) { - model->ok_pressed = false; - hid_hal_keyboard_release(hid_keynote->hid, HID_KEYBOARD_SPACEBAR); - } else if(event->key == InputKeyBack) { - model->back_pressed = false; - } - } else if(event->type == InputTypeShort) { - if(event->key == InputKeyBack) { - hid_hal_keyboard_press(hid_keynote->hid, HID_KEYBOARD_DELETE); - hid_hal_keyboard_release(hid_keynote->hid, HID_KEYBOARD_DELETE); - hid_hal_consumer_key_press(hid_keynote->hid, HID_CONSUMER_AC_BACK); - hid_hal_consumer_key_release(hid_keynote->hid, HID_CONSUMER_AC_BACK); - } - } - }, - true); -} - -static bool hid_keynote_input_callback(InputEvent* event, void* context) { - furi_assert(context); - HidKeynote* hid_keynote = context; - bool consumed = false; - - if(event->type == InputTypeLong && event->key == InputKeyBack) { - hid_hal_keyboard_release_all(hid_keynote->hid); - } else { - hid_keynote_process(hid_keynote, event); - consumed = true; - } - - return consumed; -} - -HidKeynote* hid_keynote_alloc(Hid* hid) { - HidKeynote* hid_keynote = malloc(sizeof(HidKeynote)); - hid_keynote->view = view_alloc(); - hid_keynote->hid = hid; - view_set_context(hid_keynote->view, hid_keynote); - view_allocate_model(hid_keynote->view, ViewModelTypeLocking, sizeof(HidKeynoteModel)); - view_set_draw_callback(hid_keynote->view, hid_keynote_draw_callback); - view_set_input_callback(hid_keynote->view, hid_keynote_input_callback); - - with_view_model( - hid_keynote->view, HidKeynoteModel * model, { model->transport = hid->transport; }, true); - - return hid_keynote; -} - -void hid_keynote_free(HidKeynote* hid_keynote) { - furi_assert(hid_keynote); - view_free(hid_keynote->view); - free(hid_keynote); -} - -View* hid_keynote_get_view(HidKeynote* hid_keynote) { - furi_assert(hid_keynote); - return hid_keynote->view; -} - -void hid_keynote_set_connected_status(HidKeynote* hid_keynote, bool connected) { - furi_assert(hid_keynote); - with_view_model( - hid_keynote->view, HidKeynoteModel * model, { model->connected = connected; }, true); -} - -void hid_keynote_set_orientation(HidKeynote* hid_keynote, bool vertical) { - furi_assert(hid_keynote); - - if(vertical) { - view_set_draw_callback(hid_keynote->view, hid_keynote_draw_vertical_callback); - view_set_orientation(hid_keynote->view, ViewOrientationVerticalFlip); - - } else { - view_set_draw_callback(hid_keynote->view, hid_keynote_draw_callback); - view_set_orientation(hid_keynote->view, ViewOrientationHorizontal); - } -} diff --git a/applications/external/hid_app/views/hid_keynote.h b/applications/external/hid_app/views/hid_keynote.h deleted file mode 100644 index 84bfed4ce41..00000000000 --- a/applications/external/hid_app/views/hid_keynote.h +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once - -#include - -typedef struct Hid Hid; -typedef struct HidKeynote HidKeynote; - -HidKeynote* hid_keynote_alloc(Hid* bt_hid); - -void hid_keynote_free(HidKeynote* hid_keynote); - -View* hid_keynote_get_view(HidKeynote* hid_keynote); - -void hid_keynote_set_connected_status(HidKeynote* hid_keynote, bool connected); - -void hid_keynote_set_orientation(HidKeynote* hid_keynote, bool vertical); diff --git a/applications/external/hid_app/views/hid_media.c b/applications/external/hid_app/views/hid_media.c deleted file mode 100644 index 468529d56a8..00000000000 --- a/applications/external/hid_app/views/hid_media.c +++ /dev/null @@ -1,218 +0,0 @@ -#include "hid_media.h" -#include -#include -#include -#include -#include "../hid.h" - -#include "hid_icons.h" - -#define TAG "HidMedia" - -struct HidMedia { - View* view; - Hid* hid; -}; - -typedef struct { - bool left_pressed; - bool up_pressed; - bool right_pressed; - bool down_pressed; - bool ok_pressed; - bool connected; - HidTransport transport; -} HidMediaModel; - -static void hid_media_draw_arrow(Canvas* canvas, uint8_t x, uint8_t y, CanvasDirection dir) { - canvas_draw_triangle(canvas, x, y, 5, 3, dir); - if(dir == CanvasDirectionBottomToTop) { - canvas_draw_dot(canvas, x, y - 1); - } else if(dir == CanvasDirectionTopToBottom) { - canvas_draw_dot(canvas, x, y + 1); - } else if(dir == CanvasDirectionRightToLeft) { - canvas_draw_dot(canvas, x - 1, y); - } else if(dir == CanvasDirectionLeftToRight) { - canvas_draw_dot(canvas, x + 1, y); - } -} - -static void hid_media_draw_callback(Canvas* canvas, void* context) { - furi_assert(context); - HidMediaModel* model = context; - - // Header - if(model->transport == HidTransportBle) { - if(model->connected) { - canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); - } else { - canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); - } - } - - canvas_set_font(canvas, FontPrimary); - elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Media"); - canvas_set_font(canvas, FontSecondary); - - // Keypad circles - canvas_draw_icon(canvas, 76, 8, &I_Circles_47x47); - - // Up - if(model->up_pressed) { - canvas_set_bitmap_mode(canvas, 1); - canvas_draw_icon(canvas, 93, 9, &I_Pressed_Button_13x13); - canvas_set_bitmap_mode(canvas, 0); - canvas_set_color(canvas, ColorWhite); - } - canvas_draw_icon(canvas, 96, 12, &I_Volup_8x6); - canvas_set_color(canvas, ColorBlack); - - // Down - if(model->down_pressed) { - canvas_set_bitmap_mode(canvas, 1); - canvas_draw_icon(canvas, 93, 41, &I_Pressed_Button_13x13); - canvas_set_bitmap_mode(canvas, 0); - canvas_set_color(canvas, ColorWhite); - } - canvas_draw_icon(canvas, 96, 45, &I_Voldwn_6x6); - canvas_set_color(canvas, ColorBlack); - - // Left - if(model->left_pressed) { - canvas_set_bitmap_mode(canvas, 1); - canvas_draw_icon(canvas, 77, 25, &I_Pressed_Button_13x13); - canvas_set_bitmap_mode(canvas, 0); - canvas_set_color(canvas, ColorWhite); - } - hid_media_draw_arrow(canvas, 82, 31, CanvasDirectionRightToLeft); - hid_media_draw_arrow(canvas, 86, 31, CanvasDirectionRightToLeft); - canvas_set_color(canvas, ColorBlack); - - // Right - if(model->right_pressed) { - canvas_set_bitmap_mode(canvas, 1); - canvas_draw_icon(canvas, 109, 25, &I_Pressed_Button_13x13); - canvas_set_bitmap_mode(canvas, 0); - canvas_set_color(canvas, ColorWhite); - } - hid_media_draw_arrow(canvas, 112, 31, CanvasDirectionLeftToRight); - hid_media_draw_arrow(canvas, 116, 31, CanvasDirectionLeftToRight); - canvas_set_color(canvas, ColorBlack); - - // Ok - if(model->ok_pressed) { - canvas_draw_icon(canvas, 93, 25, &I_Pressed_Button_13x13); - canvas_set_color(canvas, ColorWhite); - } - hid_media_draw_arrow(canvas, 96, 31, CanvasDirectionLeftToRight); - canvas_draw_line(canvas, 100, 29, 100, 33); - canvas_draw_line(canvas, 102, 29, 102, 33); - canvas_set_color(canvas, ColorBlack); - - // Exit - canvas_draw_icon(canvas, 0, 54, &I_Pin_back_arrow_10x8); - canvas_set_font(canvas, FontSecondary); - elements_multiline_text_aligned(canvas, 13, 62, AlignLeft, AlignBottom, "Hold to exit"); -} - -static void hid_media_process_press(HidMedia* hid_media, InputEvent* event) { - with_view_model( - hid_media->view, - HidMediaModel * model, - { - if(event->key == InputKeyUp) { - model->up_pressed = true; - hid_hal_consumer_key_press(hid_media->hid, HID_CONSUMER_VOLUME_INCREMENT); - } else if(event->key == InputKeyDown) { - model->down_pressed = true; - hid_hal_consumer_key_press(hid_media->hid, HID_CONSUMER_VOLUME_DECREMENT); - } else if(event->key == InputKeyLeft) { - model->left_pressed = true; - hid_hal_consumer_key_press(hid_media->hid, HID_CONSUMER_SCAN_PREVIOUS_TRACK); - } else if(event->key == InputKeyRight) { - model->right_pressed = true; - hid_hal_consumer_key_press(hid_media->hid, HID_CONSUMER_SCAN_NEXT_TRACK); - } else if(event->key == InputKeyOk) { - model->ok_pressed = true; - hid_hal_consumer_key_press(hid_media->hid, HID_CONSUMER_PLAY_PAUSE); - } - }, - true); -} - -static void hid_media_process_release(HidMedia* hid_media, InputEvent* event) { - with_view_model( - hid_media->view, - HidMediaModel * model, - { - if(event->key == InputKeyUp) { - model->up_pressed = false; - hid_hal_consumer_key_release(hid_media->hid, HID_CONSUMER_VOLUME_INCREMENT); - } else if(event->key == InputKeyDown) { - model->down_pressed = false; - hid_hal_consumer_key_release(hid_media->hid, HID_CONSUMER_VOLUME_DECREMENT); - } else if(event->key == InputKeyLeft) { - model->left_pressed = false; - hid_hal_consumer_key_release(hid_media->hid, HID_CONSUMER_SCAN_PREVIOUS_TRACK); - } else if(event->key == InputKeyRight) { - model->right_pressed = false; - hid_hal_consumer_key_release(hid_media->hid, HID_CONSUMER_SCAN_NEXT_TRACK); - } else if(event->key == InputKeyOk) { - model->ok_pressed = false; - hid_hal_consumer_key_release(hid_media->hid, HID_CONSUMER_PLAY_PAUSE); - } - }, - true); -} - -static bool hid_media_input_callback(InputEvent* event, void* context) { - furi_assert(context); - HidMedia* hid_media = context; - bool consumed = false; - - if(event->type == InputTypePress) { - hid_media_process_press(hid_media, event); - consumed = true; - } else if(event->type == InputTypeRelease) { - hid_media_process_release(hid_media, event); - consumed = true; - } else if(event->type == InputTypeShort) { - if(event->key == InputKeyBack) { - hid_hal_consumer_key_release_all(hid_media->hid); - } - } - - return consumed; -} - -HidMedia* hid_media_alloc(Hid* hid) { - HidMedia* hid_media = malloc(sizeof(HidMedia)); - hid_media->view = view_alloc(); - hid_media->hid = hid; - view_set_context(hid_media->view, hid_media); - view_allocate_model(hid_media->view, ViewModelTypeLocking, sizeof(HidMediaModel)); - view_set_draw_callback(hid_media->view, hid_media_draw_callback); - view_set_input_callback(hid_media->view, hid_media_input_callback); - - with_view_model( - hid_media->view, HidMediaModel * model, { model->transport = hid->transport; }, true); - - return hid_media; -} - -void hid_media_free(HidMedia* hid_media) { - furi_assert(hid_media); - view_free(hid_media->view); - free(hid_media); -} - -View* hid_media_get_view(HidMedia* hid_media) { - furi_assert(hid_media); - return hid_media->view; -} - -void hid_media_set_connected_status(HidMedia* hid_media, bool connected) { - furi_assert(hid_media); - with_view_model( - hid_media->view, HidMediaModel * model, { model->connected = connected; }, true); -} diff --git a/applications/external/hid_app/views/hid_media.h b/applications/external/hid_app/views/hid_media.h deleted file mode 100644 index 4aa51dc173b..00000000000 --- a/applications/external/hid_app/views/hid_media.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -#include - -typedef struct HidMedia HidMedia; - -HidMedia* hid_media_alloc(); - -void hid_media_free(HidMedia* hid_media); - -View* hid_media_get_view(HidMedia* hid_media); - -void hid_media_set_connected_status(HidMedia* hid_media, bool connected); diff --git a/applications/external/hid_app/views/hid_mouse.c b/applications/external/hid_app/views/hid_mouse.c deleted file mode 100644 index 30a9d9d0635..00000000000 --- a/applications/external/hid_app/views/hid_mouse.c +++ /dev/null @@ -1,226 +0,0 @@ -#include "hid_mouse.h" -#include -#include "../hid.h" - -#include "hid_icons.h" - -#define TAG "HidMouse" - -struct HidMouse { - View* view; - Hid* hid; -}; - -typedef struct { - bool left_pressed; - bool up_pressed; - bool right_pressed; - bool down_pressed; - bool left_mouse_pressed; - bool left_mouse_held; - bool right_mouse_pressed; - bool connected; - HidTransport transport; -} HidMouseModel; - -static void hid_mouse_draw_callback(Canvas* canvas, void* context) { - furi_assert(context); - HidMouseModel* model = context; - - // Header - if(model->transport == HidTransportBle) { - if(model->connected) { - canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); - } else { - canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); - } - } - - canvas_set_font(canvas, FontPrimary); - elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Mouse"); - canvas_set_font(canvas, FontSecondary); - - if(model->left_mouse_held == true) { - elements_multiline_text_aligned(canvas, 0, 62, AlignLeft, AlignBottom, "Selecting..."); - } else { - canvas_draw_icon(canvas, 0, 54, &I_Pin_back_arrow_10x8); - canvas_set_font(canvas, FontSecondary); - elements_multiline_text_aligned(canvas, 13, 62, AlignLeft, AlignBottom, "Hold to exit"); - } - - // Keypad circles - canvas_draw_icon(canvas, 64, 8, &I_Circles_47x47); - - // Up - if(model->up_pressed) { - canvas_set_bitmap_mode(canvas, 1); - canvas_draw_icon(canvas, 81, 9, &I_Pressed_Button_13x13); - canvas_set_bitmap_mode(canvas, 0); - canvas_set_color(canvas, ColorWhite); - } - canvas_draw_icon(canvas, 84, 10, &I_Pin_arrow_up_7x9); - canvas_set_color(canvas, ColorBlack); - - // Down - if(model->down_pressed) { - canvas_set_bitmap_mode(canvas, 1); - canvas_draw_icon(canvas, 81, 41, &I_Pressed_Button_13x13); - canvas_set_bitmap_mode(canvas, 0); - canvas_set_color(canvas, ColorWhite); - } - canvas_draw_icon(canvas, 84, 43, &I_Pin_arrow_down_7x9); - canvas_set_color(canvas, ColorBlack); - - // Left - if(model->left_pressed) { - canvas_set_bitmap_mode(canvas, 1); - canvas_draw_icon(canvas, 65, 25, &I_Pressed_Button_13x13); - canvas_set_bitmap_mode(canvas, 0); - canvas_set_color(canvas, ColorWhite); - } - canvas_draw_icon(canvas, 67, 28, &I_Pin_arrow_left_9x7); - canvas_set_color(canvas, ColorBlack); - - // Right - if(model->right_pressed) { - canvas_set_bitmap_mode(canvas, 1); - canvas_draw_icon(canvas, 97, 25, &I_Pressed_Button_13x13); - canvas_set_bitmap_mode(canvas, 0); - canvas_set_color(canvas, ColorWhite); - } - canvas_draw_icon(canvas, 99, 28, &I_Pin_arrow_right_9x7); - canvas_set_color(canvas, ColorBlack); - - // Ok - if(model->left_mouse_pressed) { - canvas_draw_icon(canvas, 81, 25, &I_Ok_btn_pressed_13x13); - } else { - canvas_draw_icon(canvas, 83, 27, &I_Left_mouse_icon_9x9); - } - - // Back - if(model->right_mouse_pressed) { - canvas_draw_icon(canvas, 108, 48, &I_Ok_btn_pressed_13x13); - } else { - canvas_draw_icon(canvas, 110, 50, &I_Right_mouse_icon_9x9); - } -} - -static void hid_mouse_process(HidMouse* hid_mouse, InputEvent* event) { - with_view_model( - hid_mouse->view, - HidMouseModel * model, - { - if(event->key == InputKeyBack) { - if(event->type == InputTypeShort) { - hid_hal_mouse_press(hid_mouse->hid, HID_MOUSE_BTN_RIGHT); - hid_hal_mouse_release(hid_mouse->hid, HID_MOUSE_BTN_RIGHT); - } else if(event->type == InputTypePress) { - model->right_mouse_pressed = true; - } else if(event->type == InputTypeRelease) { - model->right_mouse_pressed = false; - } - } else if(event->key == InputKeyOk) { - if(event->type == InputTypeShort) { - // Just release if it was being held before - if(!model->left_mouse_held) - hid_hal_mouse_press(hid_mouse->hid, HID_MOUSE_BTN_LEFT); - hid_hal_mouse_release(hid_mouse->hid, HID_MOUSE_BTN_LEFT); - model->left_mouse_held = false; - } else if(event->type == InputTypeLong) { - hid_hal_mouse_press(hid_mouse->hid, HID_MOUSE_BTN_LEFT); - model->left_mouse_held = true; - model->left_mouse_pressed = true; - } else if(event->type == InputTypePress) { - model->left_mouse_pressed = true; - } else if(event->type == InputTypeRelease) { - // Only release if it wasn't a long press - if(!model->left_mouse_held) model->left_mouse_pressed = false; - } - } else if(event->key == InputKeyRight) { - if(event->type == InputTypePress) { - model->right_pressed = true; - hid_hal_mouse_move(hid_mouse->hid, MOUSE_MOVE_SHORT, 0); - } else if(event->type == InputTypeRepeat) { - hid_hal_mouse_move(hid_mouse->hid, MOUSE_MOVE_LONG, 0); - } else if(event->type == InputTypeRelease) { - model->right_pressed = false; - } - } else if(event->key == InputKeyLeft) { - if(event->type == InputTypePress) { - model->left_pressed = true; - hid_hal_mouse_move(hid_mouse->hid, -MOUSE_MOVE_SHORT, 0); - } else if(event->type == InputTypeRepeat) { - hid_hal_mouse_move(hid_mouse->hid, -MOUSE_MOVE_LONG, 0); - } else if(event->type == InputTypeRelease) { - model->left_pressed = false; - } - } else if(event->key == InputKeyDown) { - if(event->type == InputTypePress) { - model->down_pressed = true; - hid_hal_mouse_move(hid_mouse->hid, 0, MOUSE_MOVE_SHORT); - } else if(event->type == InputTypeRepeat) { - hid_hal_mouse_move(hid_mouse->hid, 0, MOUSE_MOVE_LONG); - } else if(event->type == InputTypeRelease) { - model->down_pressed = false; - } - } else if(event->key == InputKeyUp) { - if(event->type == InputTypePress) { - model->up_pressed = true; - hid_hal_mouse_move(hid_mouse->hid, 0, -MOUSE_MOVE_SHORT); - } else if(event->type == InputTypeRepeat) { - hid_hal_mouse_move(hid_mouse->hid, 0, -MOUSE_MOVE_LONG); - } else if(event->type == InputTypeRelease) { - model->up_pressed = false; - } - } - }, - true); -} - -static bool hid_mouse_input_callback(InputEvent* event, void* context) { - furi_assert(context); - HidMouse* hid_mouse = context; - bool consumed = false; - - if(event->type == InputTypeLong && event->key == InputKeyBack) { - hid_hal_mouse_release_all(hid_mouse->hid); - } else { - hid_mouse_process(hid_mouse, event); - consumed = true; - } - - return consumed; -} - -HidMouse* hid_mouse_alloc(Hid* hid) { - HidMouse* hid_mouse = malloc(sizeof(HidMouse)); - hid_mouse->view = view_alloc(); - hid_mouse->hid = hid; - view_set_context(hid_mouse->view, hid_mouse); - view_allocate_model(hid_mouse->view, ViewModelTypeLocking, sizeof(HidMouseModel)); - view_set_draw_callback(hid_mouse->view, hid_mouse_draw_callback); - view_set_input_callback(hid_mouse->view, hid_mouse_input_callback); - - with_view_model( - hid_mouse->view, HidMouseModel * model, { model->transport = hid->transport; }, true); - - return hid_mouse; -} - -void hid_mouse_free(HidMouse* hid_mouse) { - furi_assert(hid_mouse); - view_free(hid_mouse->view); - free(hid_mouse); -} - -View* hid_mouse_get_view(HidMouse* hid_mouse) { - furi_assert(hid_mouse); - return hid_mouse->view; -} - -void hid_mouse_set_connected_status(HidMouse* hid_mouse, bool connected) { - furi_assert(hid_mouse); - with_view_model( - hid_mouse->view, HidMouseModel * model, { model->connected = connected; }, true); -} diff --git a/applications/external/hid_app/views/hid_mouse.h b/applications/external/hid_app/views/hid_mouse.h deleted file mode 100644 index d9fb2fd88a7..00000000000 --- a/applications/external/hid_app/views/hid_mouse.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include - -#define MOUSE_MOVE_SHORT 5 -#define MOUSE_MOVE_LONG 20 - -typedef struct Hid Hid; -typedef struct HidMouse HidMouse; - -HidMouse* hid_mouse_alloc(Hid* bt_hid); - -void hid_mouse_free(HidMouse* hid_mouse); - -View* hid_mouse_get_view(HidMouse* hid_mouse); - -void hid_mouse_set_connected_status(HidMouse* hid_mouse, bool connected); diff --git a/applications/external/hid_app/views/hid_mouse_clicker.c b/applications/external/hid_app/views/hid_mouse_clicker.c deleted file mode 100644 index d85affc4338..00000000000 --- a/applications/external/hid_app/views/hid_mouse_clicker.c +++ /dev/null @@ -1,214 +0,0 @@ -#include "hid_mouse_clicker.h" -#include -#include "../hid.h" - -#include "hid_icons.h" - -#define TAG "HidMouseClicker" -#define DEFAULT_CLICK_RATE 1 -#define MAXIMUM_CLICK_RATE 60 - -struct HidMouseClicker { - View* view; - Hid* hid; - FuriTimer* timer; -}; - -typedef struct { - bool connected; - bool running; - int rate; - HidTransport transport; -} HidMouseClickerModel; - -static void hid_mouse_clicker_start_or_restart_timer(void* context) { - furi_assert(context); - HidMouseClicker* hid_mouse_clicker = context; - - if(furi_timer_is_running(hid_mouse_clicker->timer)) { - furi_timer_stop(hid_mouse_clicker->timer); - } - - with_view_model( - hid_mouse_clicker->view, - HidMouseClickerModel * model, - { - furi_timer_start( - hid_mouse_clicker->timer, furi_kernel_get_tick_frequency() / model->rate); - }, - true); -} - -static void hid_mouse_clicker_draw_callback(Canvas* canvas, void* context) { - furi_assert(context); - HidMouseClickerModel* model = context; - - // Header - if(model->transport == HidTransportBle) { - if(model->connected) { - canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); - } else { - canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); - } - } - - canvas_set_font(canvas, FontPrimary); - elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Mouse Clicker"); - - // Ok - canvas_draw_icon(canvas, 63, 25, &I_Space_65x18); - if(model->running) { - canvas_set_font(canvas, FontPrimary); - - FuriString* rate_label = furi_string_alloc(); - furi_string_printf(rate_label, "%d clicks/s\n\nUp / Down", model->rate); - elements_multiline_text(canvas, AlignLeft, 35, furi_string_get_cstr(rate_label)); - canvas_set_font(canvas, FontSecondary); - furi_string_free(rate_label); - - elements_slightly_rounded_box(canvas, 66, 27, 60, 13); - canvas_set_color(canvas, ColorWhite); - } else { - canvas_set_font(canvas, FontPrimary); - elements_multiline_text(canvas, AlignLeft, 35, "Press Start\nto start\nclicking"); - canvas_set_font(canvas, FontSecondary); - } - canvas_draw_icon(canvas, 74, 29, &I_Ok_btn_9x9); - if(model->running) { - elements_multiline_text_aligned(canvas, 91, 36, AlignLeft, AlignBottom, "Stop"); - } else { - elements_multiline_text_aligned(canvas, 91, 36, AlignLeft, AlignBottom, "Start"); - } - canvas_set_color(canvas, ColorBlack); - - // Back - canvas_draw_icon(canvas, 74, 49, &I_Pin_back_arrow_10x8); - elements_multiline_text_aligned(canvas, 91, 57, AlignLeft, AlignBottom, "Quit"); -} - -static void hid_mouse_clicker_timer_callback(void* context) { - furi_assert(context); - HidMouseClicker* hid_mouse_clicker = context; - with_view_model( - hid_mouse_clicker->view, - HidMouseClickerModel * model, - { - if(model->running) { - hid_hal_mouse_press(hid_mouse_clicker->hid, HID_MOUSE_BTN_LEFT); - hid_hal_mouse_release(hid_mouse_clicker->hid, HID_MOUSE_BTN_LEFT); - } - }, - false); -} - -static void hid_mouse_clicker_enter_callback(void* context) { - hid_mouse_clicker_start_or_restart_timer(context); -} - -static void hid_mouse_clicker_exit_callback(void* context) { - furi_assert(context); - HidMouseClicker* hid_mouse_clicker = context; - furi_timer_stop(hid_mouse_clicker->timer); -} - -static bool hid_mouse_clicker_input_callback(InputEvent* event, void* context) { - furi_assert(context); - HidMouseClicker* hid_mouse_clicker = context; - - bool consumed = false; - bool rate_changed = false; - - if(event->type != InputTypeShort && event->type != InputTypeRepeat) { - return false; - } - - with_view_model( - hid_mouse_clicker->view, - HidMouseClickerModel * model, - { - switch(event->key) { - case InputKeyOk: - model->running = !model->running; - consumed = true; - break; - case InputKeyUp: - if(model->rate < MAXIMUM_CLICK_RATE) { - model->rate++; - } - rate_changed = true; - consumed = true; - break; - case InputKeyDown: - if(model->rate > 1) { - model->rate--; - } - rate_changed = true; - consumed = true; - break; - default: - consumed = true; - break; - } - }, - true); - - if(rate_changed) { - hid_mouse_clicker_start_or_restart_timer(context); - } - - return consumed; -} - -HidMouseClicker* hid_mouse_clicker_alloc(Hid* hid) { - HidMouseClicker* hid_mouse_clicker = malloc(sizeof(HidMouseClicker)); - - hid_mouse_clicker->view = view_alloc(); - view_set_context(hid_mouse_clicker->view, hid_mouse_clicker); - view_allocate_model( - hid_mouse_clicker->view, ViewModelTypeLocking, sizeof(HidMouseClickerModel)); - view_set_draw_callback(hid_mouse_clicker->view, hid_mouse_clicker_draw_callback); - view_set_input_callback(hid_mouse_clicker->view, hid_mouse_clicker_input_callback); - view_set_enter_callback(hid_mouse_clicker->view, hid_mouse_clicker_enter_callback); - view_set_exit_callback(hid_mouse_clicker->view, hid_mouse_clicker_exit_callback); - - hid_mouse_clicker->hid = hid; - - hid_mouse_clicker->timer = furi_timer_alloc( - hid_mouse_clicker_timer_callback, FuriTimerTypePeriodic, hid_mouse_clicker); - - with_view_model( - hid_mouse_clicker->view, - HidMouseClickerModel * model, - { - model->transport = hid->transport; - model->rate = DEFAULT_CLICK_RATE; - }, - true); - - return hid_mouse_clicker; -} - -void hid_mouse_clicker_free(HidMouseClicker* hid_mouse_clicker) { - furi_assert(hid_mouse_clicker); - - furi_timer_stop(hid_mouse_clicker->timer); - furi_timer_free(hid_mouse_clicker->timer); - - view_free(hid_mouse_clicker->view); - - free(hid_mouse_clicker); -} - -View* hid_mouse_clicker_get_view(HidMouseClicker* hid_mouse_clicker) { - furi_assert(hid_mouse_clicker); - return hid_mouse_clicker->view; -} - -void hid_mouse_clicker_set_connected_status(HidMouseClicker* hid_mouse_clicker, bool connected) { - furi_assert(hid_mouse_clicker); - with_view_model( - hid_mouse_clicker->view, - HidMouseClickerModel * model, - { model->connected = connected; }, - true); -} diff --git a/applications/external/hid_app/views/hid_mouse_clicker.h b/applications/external/hid_app/views/hid_mouse_clicker.h deleted file mode 100644 index d72847baa70..00000000000 --- a/applications/external/hid_app/views/hid_mouse_clicker.h +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - -#include - -typedef struct Hid Hid; -typedef struct HidMouseClicker HidMouseClicker; - -HidMouseClicker* hid_mouse_clicker_alloc(Hid* bt_hid); - -void hid_mouse_clicker_free(HidMouseClicker* hid_mouse_clicker); - -View* hid_mouse_clicker_get_view(HidMouseClicker* hid_mouse_clicker); - -void hid_mouse_clicker_set_connected_status(HidMouseClicker* hid_mouse_clicker, bool connected); diff --git a/applications/external/hid_app/views/hid_mouse_jiggler.c b/applications/external/hid_app/views/hid_mouse_jiggler.c deleted file mode 100644 index 15547eb26b5..00000000000 --- a/applications/external/hid_app/views/hid_mouse_jiggler.c +++ /dev/null @@ -1,159 +0,0 @@ -#include "hid_mouse_jiggler.h" -#include -#include "../hid.h" - -#include "hid_icons.h" - -#define TAG "HidMouseJiggler" - -struct HidMouseJiggler { - View* view; - Hid* hid; - FuriTimer* timer; -}; - -typedef struct { - bool connected; - bool running; - uint8_t counter; - HidTransport transport; -} HidMouseJigglerModel; - -static void hid_mouse_jiggler_draw_callback(Canvas* canvas, void* context) { - furi_assert(context); - HidMouseJigglerModel* model = context; - - // Header - if(model->transport == HidTransportBle) { - if(model->connected) { - canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); - } else { - canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); - } - } - - canvas_set_font(canvas, FontPrimary); - elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Mouse Jiggler"); - - canvas_set_font(canvas, FontPrimary); - elements_multiline_text(canvas, AlignLeft, 35, "Press Start\nto jiggle"); - canvas_set_font(canvas, FontSecondary); - - // Ok - canvas_draw_icon(canvas, 63, 25, &I_Space_65x18); - if(model->running) { - elements_slightly_rounded_box(canvas, 66, 27, 60, 13); - canvas_set_color(canvas, ColorWhite); - } - canvas_draw_icon(canvas, 74, 29, &I_Ok_btn_9x9); - if(model->running) { - elements_multiline_text_aligned(canvas, 91, 36, AlignLeft, AlignBottom, "Stop"); - } else { - elements_multiline_text_aligned(canvas, 91, 36, AlignLeft, AlignBottom, "Start"); - } - canvas_set_color(canvas, ColorBlack); - - // Back - canvas_draw_icon(canvas, 74, 49, &I_Pin_back_arrow_10x8); - elements_multiline_text_aligned(canvas, 91, 57, AlignLeft, AlignBottom, "Quit"); -} - -static void hid_mouse_jiggler_timer_callback(void* context) { - furi_assert(context); - HidMouseJiggler* hid_mouse_jiggler = context; - with_view_model( - hid_mouse_jiggler->view, - HidMouseJigglerModel * model, - { - if(model->running) { - model->counter++; - hid_hal_mouse_move( - hid_mouse_jiggler->hid, - (model->counter % 2 == 0) ? MOUSE_MOVE_SHORT : -MOUSE_MOVE_SHORT, - 0); - } - }, - false); -} - -static void hid_mouse_jiggler_enter_callback(void* context) { - furi_assert(context); - HidMouseJiggler* hid_mouse_jiggler = context; - - furi_timer_start(hid_mouse_jiggler->timer, 500); -} - -static void hid_mouse_jiggler_exit_callback(void* context) { - furi_assert(context); - HidMouseJiggler* hid_mouse_jiggler = context; - furi_timer_stop(hid_mouse_jiggler->timer); -} - -static bool hid_mouse_jiggler_input_callback(InputEvent* event, void* context) { - furi_assert(context); - HidMouseJiggler* hid_mouse_jiggler = context; - - bool consumed = false; - - if(event->type == InputTypeShort && event->key == InputKeyOk) { - with_view_model( - hid_mouse_jiggler->view, - HidMouseJigglerModel * model, - { model->running = !model->running; }, - true); - consumed = true; - } - - return consumed; -} - -HidMouseJiggler* hid_mouse_jiggler_alloc(Hid* hid) { - HidMouseJiggler* hid_mouse_jiggler = malloc(sizeof(HidMouseJiggler)); - - hid_mouse_jiggler->view = view_alloc(); - view_set_context(hid_mouse_jiggler->view, hid_mouse_jiggler); - view_allocate_model( - hid_mouse_jiggler->view, ViewModelTypeLocking, sizeof(HidMouseJigglerModel)); - view_set_draw_callback(hid_mouse_jiggler->view, hid_mouse_jiggler_draw_callback); - view_set_input_callback(hid_mouse_jiggler->view, hid_mouse_jiggler_input_callback); - view_set_enter_callback(hid_mouse_jiggler->view, hid_mouse_jiggler_enter_callback); - view_set_exit_callback(hid_mouse_jiggler->view, hid_mouse_jiggler_exit_callback); - - hid_mouse_jiggler->hid = hid; - - hid_mouse_jiggler->timer = furi_timer_alloc( - hid_mouse_jiggler_timer_callback, FuriTimerTypePeriodic, hid_mouse_jiggler); - - with_view_model( - hid_mouse_jiggler->view, - HidMouseJigglerModel * model, - { model->transport = hid->transport; }, - true); - - return hid_mouse_jiggler; -} - -void hid_mouse_jiggler_free(HidMouseJiggler* hid_mouse_jiggler) { - furi_assert(hid_mouse_jiggler); - - furi_timer_stop(hid_mouse_jiggler->timer); - furi_timer_free(hid_mouse_jiggler->timer); - - view_free(hid_mouse_jiggler->view); - - free(hid_mouse_jiggler); -} - -View* hid_mouse_jiggler_get_view(HidMouseJiggler* hid_mouse_jiggler) { - furi_assert(hid_mouse_jiggler); - return hid_mouse_jiggler->view; -} - -void hid_mouse_jiggler_set_connected_status(HidMouseJiggler* hid_mouse_jiggler, bool connected) { - furi_assert(hid_mouse_jiggler); - with_view_model( - hid_mouse_jiggler->view, - HidMouseJigglerModel * model, - { model->connected = connected; }, - true); -} diff --git a/applications/external/hid_app/views/hid_mouse_jiggler.h b/applications/external/hid_app/views/hid_mouse_jiggler.h deleted file mode 100644 index 0813b4351e1..00000000000 --- a/applications/external/hid_app/views/hid_mouse_jiggler.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include - -#define MOUSE_MOVE_SHORT 5 -#define MOUSE_MOVE_LONG 20 - -typedef struct Hid Hid; -typedef struct HidMouseJiggler HidMouseJiggler; - -HidMouseJiggler* hid_mouse_jiggler_alloc(Hid* bt_hid); - -void hid_mouse_jiggler_free(HidMouseJiggler* hid_mouse_jiggler); - -View* hid_mouse_jiggler_get_view(HidMouseJiggler* hid_mouse_jiggler); - -void hid_mouse_jiggler_set_connected_status(HidMouseJiggler* hid_mouse_jiggler, bool connected); diff --git a/applications/external/hid_app/views/hid_tiktok.c b/applications/external/hid_app/views/hid_tiktok.c deleted file mode 100644 index e1f9f4bed4c..00000000000 --- a/applications/external/hid_app/views/hid_tiktok.c +++ /dev/null @@ -1,241 +0,0 @@ -#include "hid_tiktok.h" -#include "../hid.h" -#include - -#include "hid_icons.h" - -#define TAG "HidTikTok" - -struct HidTikTok { - View* view; - Hid* hid; -}; - -typedef struct { - bool left_pressed; - bool up_pressed; - bool right_pressed; - bool down_pressed; - bool ok_pressed; - bool connected; - bool is_cursor_set; - HidTransport transport; -} HidTikTokModel; - -static void hid_tiktok_draw_callback(Canvas* canvas, void* context) { - furi_assert(context); - HidTikTokModel* model = context; - - // Header - if(model->transport == HidTransportBle) { - if(model->connected) { - canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); - } else { - canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); - } - } - - canvas_set_font(canvas, FontPrimary); - elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "TikTok"); - canvas_set_font(canvas, FontSecondary); - - // Keypad circles - canvas_draw_icon(canvas, 76, 8, &I_Circles_47x47); - - // Up - if(model->up_pressed) { - canvas_set_bitmap_mode(canvas, 1); - canvas_draw_icon(canvas, 93, 9, &I_Pressed_Button_13x13); - canvas_set_bitmap_mode(canvas, 0); - canvas_set_color(canvas, ColorWhite); - } - canvas_draw_icon(canvas, 96, 11, &I_Arr_up_7x9); - canvas_set_color(canvas, ColorBlack); - - // Down - if(model->down_pressed) { - canvas_set_bitmap_mode(canvas, 1); - canvas_draw_icon(canvas, 93, 41, &I_Pressed_Button_13x13); - canvas_set_bitmap_mode(canvas, 0); - canvas_set_color(canvas, ColorWhite); - } - canvas_draw_icon(canvas, 96, 44, &I_Arr_dwn_7x9); - canvas_set_color(canvas, ColorBlack); - - // Left - if(model->left_pressed) { - canvas_set_bitmap_mode(canvas, 1); - canvas_draw_icon(canvas, 77, 25, &I_Pressed_Button_13x13); - canvas_set_bitmap_mode(canvas, 0); - canvas_set_color(canvas, ColorWhite); - } - canvas_draw_icon(canvas, 81, 29, &I_Voldwn_6x6); - canvas_set_color(canvas, ColorBlack); - - // Right - if(model->right_pressed) { - canvas_set_bitmap_mode(canvas, 1); - canvas_draw_icon(canvas, 109, 25, &I_Pressed_Button_13x13); - canvas_set_bitmap_mode(canvas, 0); - canvas_set_color(canvas, ColorWhite); - } - canvas_draw_icon(canvas, 111, 29, &I_Volup_8x6); - canvas_set_color(canvas, ColorBlack); - - // Ok - if(model->ok_pressed) { - canvas_draw_icon(canvas, 91, 23, &I_Like_pressed_17x17); - } else { - canvas_draw_icon(canvas, 94, 27, &I_Like_def_11x9); - } - // Exit - canvas_draw_icon(canvas, 0, 54, &I_Pin_back_arrow_10x8); - canvas_set_font(canvas, FontSecondary); - elements_multiline_text_aligned(canvas, 13, 62, AlignLeft, AlignBottom, "Hold to exit"); -} - -static void hid_tiktok_reset_cursor(HidTikTok* hid_tiktok) { - // Set cursor to the phone's left up corner - // Delays to guarantee one packet per connection interval - for(size_t i = 0; i < 8; i++) { - hid_hal_mouse_move(hid_tiktok->hid, -127, -127); - furi_delay_ms(50); - } - // Move cursor from the corner - hid_hal_mouse_move(hid_tiktok->hid, 20, 120); - furi_delay_ms(50); -} - -static void - hid_tiktok_process_press(HidTikTok* hid_tiktok, HidTikTokModel* model, InputEvent* event) { - if(event->key == InputKeyUp) { - model->up_pressed = true; - } else if(event->key == InputKeyDown) { - model->down_pressed = true; - } else if(event->key == InputKeyLeft) { - model->left_pressed = true; - hid_hal_consumer_key_press(hid_tiktok->hid, HID_CONSUMER_VOLUME_DECREMENT); - } else if(event->key == InputKeyRight) { - model->right_pressed = true; - hid_hal_consumer_key_press(hid_tiktok->hid, HID_CONSUMER_VOLUME_INCREMENT); - } else if(event->key == InputKeyOk) { - model->ok_pressed = true; - } -} - -static void - hid_tiktok_process_release(HidTikTok* hid_tiktok, HidTikTokModel* model, InputEvent* event) { - if(event->key == InputKeyUp) { - model->up_pressed = false; - } else if(event->key == InputKeyDown) { - model->down_pressed = false; - } else if(event->key == InputKeyLeft) { - model->left_pressed = false; - hid_hal_consumer_key_release(hid_tiktok->hid, HID_CONSUMER_VOLUME_DECREMENT); - } else if(event->key == InputKeyRight) { - model->right_pressed = false; - hid_hal_consumer_key_release(hid_tiktok->hid, HID_CONSUMER_VOLUME_INCREMENT); - } else if(event->key == InputKeyOk) { - model->ok_pressed = false; - } -} - -static bool hid_tiktok_input_callback(InputEvent* event, void* context) { - furi_assert(context); - HidTikTok* hid_tiktok = context; - bool consumed = false; - - with_view_model( - hid_tiktok->view, - HidTikTokModel * model, - { - if(event->type == InputTypePress) { - hid_tiktok_process_press(hid_tiktok, model, event); - if(model->connected && !model->is_cursor_set) { - hid_tiktok_reset_cursor(hid_tiktok); - model->is_cursor_set = true; - } - consumed = true; - } else if(event->type == InputTypeRelease) { - hid_tiktok_process_release(hid_tiktok, model, event); - consumed = true; - } else if(event->type == InputTypeShort) { - if(event->key == InputKeyOk) { - hid_hal_mouse_press(hid_tiktok->hid, HID_MOUSE_BTN_LEFT); - furi_delay_ms(50); - hid_hal_mouse_release(hid_tiktok->hid, HID_MOUSE_BTN_LEFT); - furi_delay_ms(50); - hid_hal_mouse_press(hid_tiktok->hid, HID_MOUSE_BTN_LEFT); - furi_delay_ms(50); - hid_hal_mouse_release(hid_tiktok->hid, HID_MOUSE_BTN_LEFT); - consumed = true; - } else if(event->key == InputKeyUp) { - // Emulate up swipe - hid_hal_mouse_scroll(hid_tiktok->hid, -6); - hid_hal_mouse_scroll(hid_tiktok->hid, -12); - hid_hal_mouse_scroll(hid_tiktok->hid, -19); - hid_hal_mouse_scroll(hid_tiktok->hid, -12); - hid_hal_mouse_scroll(hid_tiktok->hid, -6); - consumed = true; - } else if(event->key == InputKeyDown) { - // Emulate down swipe - hid_hal_mouse_scroll(hid_tiktok->hid, 6); - hid_hal_mouse_scroll(hid_tiktok->hid, 12); - hid_hal_mouse_scroll(hid_tiktok->hid, 19); - hid_hal_mouse_scroll(hid_tiktok->hid, 12); - hid_hal_mouse_scroll(hid_tiktok->hid, 6); - consumed = true; - } else if(event->key == InputKeyBack) { - hid_hal_consumer_key_release_all(hid_tiktok->hid); - consumed = true; - } - } else if(event->type == InputTypeLong) { - if(event->key == InputKeyBack) { - hid_hal_consumer_key_release_all(hid_tiktok->hid); - model->is_cursor_set = false; - consumed = false; - } - } - }, - true); - - return consumed; -} - -HidTikTok* hid_tiktok_alloc(Hid* bt_hid) { - HidTikTok* hid_tiktok = malloc(sizeof(HidTikTok)); - hid_tiktok->hid = bt_hid; - hid_tiktok->view = view_alloc(); - view_set_context(hid_tiktok->view, hid_tiktok); - view_allocate_model(hid_tiktok->view, ViewModelTypeLocking, sizeof(HidTikTokModel)); - view_set_draw_callback(hid_tiktok->view, hid_tiktok_draw_callback); - view_set_input_callback(hid_tiktok->view, hid_tiktok_input_callback); - - with_view_model( - hid_tiktok->view, HidTikTokModel * model, { model->transport = bt_hid->transport; }, true); - - return hid_tiktok; -} - -void hid_tiktok_free(HidTikTok* hid_tiktok) { - furi_assert(hid_tiktok); - view_free(hid_tiktok->view); - free(hid_tiktok); -} - -View* hid_tiktok_get_view(HidTikTok* hid_tiktok) { - furi_assert(hid_tiktok); - return hid_tiktok->view; -} - -void hid_tiktok_set_connected_status(HidTikTok* hid_tiktok, bool connected) { - furi_assert(hid_tiktok); - with_view_model( - hid_tiktok->view, - HidTikTokModel * model, - { - model->connected = connected; - model->is_cursor_set = false; - }, - true); -} diff --git a/applications/external/hid_app/views/hid_tiktok.h b/applications/external/hid_app/views/hid_tiktok.h deleted file mode 100644 index b2efc3692d3..00000000000 --- a/applications/external/hid_app/views/hid_tiktok.h +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - -#include - -typedef struct Hid Hid; -typedef struct HidTikTok HidTikTok; - -HidTikTok* hid_tiktok_alloc(Hid* bt_hid); - -void hid_tiktok_free(HidTikTok* hid_tiktok); - -View* hid_tiktok_get_view(HidTikTok* hid_tiktok); - -void hid_tiktok_set_connected_status(HidTikTok* hid_tiktok, bool connected); diff --git a/applications/external/mfkey32/application.fam b/applications/external/mfkey32/application.fam deleted file mode 100644 index 9a9cbf581ab..00000000000 --- a/applications/external/mfkey32/application.fam +++ /dev/null @@ -1,17 +0,0 @@ -App( - appid="mfkey32", - name="Mfkey32", - apptype=FlipperAppType.EXTERNAL, - targets=["f7"], - entry_point="mfkey32_main", - requires=[ - "gui", - "storage", - ], - stack_size=1 * 1024, - fap_icon="mfkey.png", - fap_category="Nfc", - fap_author="noproto", - fap_icon_assets="images", - fap_weburl="https://github.com/noproto/FlipperMfkey", -) diff --git a/applications/external/mfkey32/images/mfkey.png b/applications/external/mfkey32/images/mfkey.png deleted file mode 100644 index 52ab29efb92bf5aa7c790b8043d2d07a7e74704d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9060 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 T2_+RDfDS|^SsNGcaF6&O#taN} diff --git a/applications/external/mfkey32/mfkey.png b/applications/external/mfkey32/mfkey.png deleted file mode 100644 index 52ab29efb92bf5aa7c790b8043d2d07a7e74704d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9060 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 T2_+RDfDS|^SsNGcaF6&O#taN} diff --git a/applications/external/mfkey32/mfkey32.c b/applications/external/mfkey32/mfkey32.c deleted file mode 100644 index 5e790b01f6e..00000000000 --- a/applications/external/mfkey32/mfkey32.c +++ /dev/null @@ -1,1349 +0,0 @@ -#pragma GCC optimize("O3") -#pragma GCC optimize("-funroll-all-loops") - -// TODO: Add keys to top of the user dictionary, not the bottom -// TODO: More efficient dictionary bruteforce by scanning through hardcoded very common keys and previously found dictionary keys first? -// (a cache for napi_key_already_found_for_nonce) - -#include -#include -#include "time.h" -#include -#include -#include -#include -#include "mfkey32_icons.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define MF_CLASSIC_DICT_FLIPPER_PATH EXT_PATH("nfc/assets/mf_classic_dict.nfc") -#define MF_CLASSIC_DICT_USER_PATH EXT_PATH("nfc/assets/mf_classic_dict_user.nfc") -#define MF_CLASSIC_NONCE_PATH EXT_PATH("nfc/.mfkey32.log") -#define TAG "Mfkey32" -#define NFC_MF_CLASSIC_KEY_LEN (13) - -#define MIN_RAM 115632 -#define LF_POLY_ODD (0x29CE5C) -#define LF_POLY_EVEN (0x870804) -#define CONST_M1_1 (LF_POLY_EVEN << 1 | 1) -#define CONST_M2_1 (LF_POLY_ODD << 1) -#define CONST_M1_2 (LF_POLY_ODD) -#define CONST_M2_2 (LF_POLY_EVEN << 1 | 1) -#define BIT(x, n) ((x) >> (n)&1) -#define BEBIT(x, n) BIT(x, (n) ^ 24) -#define SWAPENDIAN(x) \ - ((x) = ((x) >> 8 & 0xff00ff) | ((x)&0xff00ff) << 8, (x) = (x) >> 16 | (x) << 16) -//#define SIZEOF(arr) sizeof(arr) / sizeof(*arr) - -static int eta_round_time = 56; -static int eta_total_time = 900; -// MSB_LIMIT: Chunk size (out of 256) -static int MSB_LIMIT = 16; - -struct Crypto1State { - uint32_t odd, even; -}; -struct Crypto1Params { - uint64_t key; - uint32_t nr0_enc, uid_xor_nt0, uid_xor_nt1, nr1_enc, p64b, ar1_enc; -}; -struct Msb { - int tail; - uint32_t states[768]; -}; - -typedef enum { - EventTypeTick, - EventTypeKey, -} EventType; - -typedef struct { - EventType type; - InputEvent input; -} PluginEvent; - -typedef enum { - MissingNonces, - ZeroNonces, -} MfkeyError; - -typedef enum { - Ready, - Initializing, - DictionaryAttack, - MfkeyAttack, - Complete, - Error, - Help, -} MfkeyState; - -// TODO: Can we eliminate any of the members of this struct? -typedef struct { - FuriMutex* mutex; - MfkeyError err; - MfkeyState mfkey_state; - int cracked; - int unique_cracked; - int num_completed; - int total; - int dict_count; - int search; - int eta_timestamp; - int eta_total; - int eta_round; - bool is_thread_running; - bool close_thread_please; - FuriThread* mfkeythread; -} ProgramState; - -// TODO: Merge this with Crypto1Params? -typedef struct { - uint32_t uid; // serial number - uint32_t nt0; // tag challenge first - uint32_t nt1; // tag challenge second - uint32_t nr0_enc; // first encrypted reader challenge - uint32_t ar0_enc; // first encrypted reader response - uint32_t nr1_enc; // second encrypted reader challenge - uint32_t ar1_enc; // second encrypted reader response -} MfClassicNonce; - -typedef struct { - Stream* stream; - uint32_t total_nonces; - MfClassicNonce* remaining_nonce_array; - size_t remaining_nonces; -} MfClassicNonceArray; - -struct MfClassicDict { - Stream* stream; - uint32_t total_keys; -}; - -static const uint8_t table[256] = { - 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, - 4, 4, 5, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, - 4, 5, 4, 5, 5, 6, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, - 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, - 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, - 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, - 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, - 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, - 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8}; -static const uint8_t lookup1[256] = { - 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, - 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, - 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, 8, 8, 24, 24, 8, 24, 8, 8, - 8, 24, 8, 8, 24, 24, 24, 24, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, - 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, - 0, 16, 0, 0, 16, 16, 16, 16, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, - 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, - 0, 16, 0, 0, 16, 16, 16, 16, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, - 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, 0, 0, 16, 16, 0, 16, 0, 0, - 0, 16, 0, 0, 16, 16, 16, 16, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, - 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24}; -static const uint8_t lookup2[256] = { - 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, - 4, 4, 4, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, - 2, 2, 6, 6, 6, 6, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 2, 2, 6, 6, 2, 6, 2, - 2, 2, 6, 2, 2, 6, 6, 6, 6, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 0, 0, 4, 4, - 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 2, - 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, - 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, - 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, - 2, 6, 2, 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6}; - -uint32_t prng_successor(uint32_t x, uint32_t n) { - SWAPENDIAN(x); - while(n--) x = x >> 1 | (x >> 16 ^ x >> 18 ^ x >> 19 ^ x >> 21) << 31; - return SWAPENDIAN(x); -} - -static inline int filter(uint32_t const x) { - uint32_t f; - f = lookup1[x & 0xff] | lookup2[(x >> 8) & 0xff]; - f |= 0x0d938 >> (x >> 16 & 0xf) & 1; - return BIT(0xEC57E80A, f); -} - -static inline uint8_t evenparity32(uint32_t x) { - if((table[x & 0xff] + table[(x >> 8) & 0xff] + table[(x >> 16) & 0xff] + table[x >> 24]) % 2 == - 0) { - return 0; - } else { - return 1; - } - //return ((table[x & 0xff] + table[(x >> 8) & 0xff] + table[(x >> 16) & 0xff] + table[x >> 24]) % 2) & 0xFF; -} - -static inline void update_contribution(unsigned int data[], int item, int mask1, int mask2) { - int p = data[item] >> 25; - p = p << 1 | evenparity32(data[item] & mask1); - p = p << 1 | evenparity32(data[item] & mask2); - data[item] = p << 24 | (data[item] & 0xffffff); -} - -void crypto1_get_lfsr(struct Crypto1State* state, uint64_t* lfsr) { - int i; - for(*lfsr = 0, i = 23; i >= 0; --i) { - *lfsr = *lfsr << 1 | BIT(state->odd, i ^ 3); - *lfsr = *lfsr << 1 | BIT(state->even, i ^ 3); - } -} - -static inline uint32_t crypt_word(struct Crypto1State* s) { - // "in" and "x" are always 0 (last iteration) - uint32_t res_ret = 0; - uint32_t feedin, t; - for(int i = 0; i <= 31; i++) { - res_ret |= (filter(s->odd) << (24 ^ i)); //-V629 - feedin = LF_POLY_EVEN & s->even; - feedin ^= LF_POLY_ODD & s->odd; - s->even = s->even << 1 | (evenparity32(feedin)); - t = s->odd, s->odd = s->even, s->even = t; - } - return res_ret; -} - -static inline void crypt_word_noret(struct Crypto1State* s, uint32_t in, int x) { - uint8_t ret; - uint32_t feedin, t, next_in; - for(int i = 0; i <= 31; i++) { - next_in = BEBIT(in, i); - ret = filter(s->odd); - feedin = ret & (!!x); - feedin ^= LF_POLY_EVEN & s->even; - feedin ^= LF_POLY_ODD & s->odd; - feedin ^= !!next_in; - s->even = s->even << 1 | (evenparity32(feedin)); - t = s->odd, s->odd = s->even, s->even = t; - } - return; -} - -static inline void rollback_word_noret(struct Crypto1State* s, uint32_t in, int x) { - uint8_t ret; - uint32_t feedin, t, next_in; - for(int i = 31; i >= 0; i--) { - next_in = BEBIT(in, i); - s->odd &= 0xffffff; - t = s->odd, s->odd = s->even, s->even = t; - ret = filter(s->odd); - feedin = ret & (!!x); - feedin ^= s->even & 1; - feedin ^= LF_POLY_EVEN & (s->even >>= 1); - feedin ^= LF_POLY_ODD & s->odd; - feedin ^= !!next_in; - s->even |= (evenparity32(feedin)) << 23; - } - return; -} - -int key_already_found_for_nonce( - uint64_t* keyarray, - int keyarray_size, - uint32_t uid_xor_nt1, - uint32_t nr1_enc, - uint32_t p64b, - uint32_t ar1_enc) { - for(int k = 0; k < keyarray_size; k++) { - struct Crypto1State temp = {0, 0}; - - for(int i = 0; i < 24; i++) { - (&temp)->odd |= (BIT(keyarray[k], 2 * i + 1) << (i ^ 3)); - (&temp)->even |= (BIT(keyarray[k], 2 * i) << (i ^ 3)); - } - - crypt_word_noret(&temp, uid_xor_nt1, 0); - crypt_word_noret(&temp, nr1_enc, 1); - - if(ar1_enc == (crypt_word(&temp) ^ p64b)) { - return 1; - } - } - return 0; -} - -int check_state(struct Crypto1State* t, struct Crypto1Params* p) { - if(!(t->odd | t->even)) return 0; - rollback_word_noret(t, 0, 0); - rollback_word_noret(t, p->nr0_enc, 1); - rollback_word_noret(t, p->uid_xor_nt0, 0); - struct Crypto1State temp = {t->odd, t->even}; - crypt_word_noret(t, p->uid_xor_nt1, 0); - crypt_word_noret(t, p->nr1_enc, 1); - if(p->ar1_enc == (crypt_word(t) ^ p->p64b)) { - crypto1_get_lfsr(&temp, &(p->key)); - return 1; - } - return 0; -} - -static inline int state_loop(unsigned int* states_buffer, int xks, int m1, int m2) { - int states_tail = 0; - int round = 0, s = 0, xks_bit = 0; - - for(round = 1; round <= 12; round++) { - xks_bit = BIT(xks, round); - - for(s = 0; s <= states_tail; s++) { - states_buffer[s] <<= 1; - - if((filter(states_buffer[s]) ^ filter(states_buffer[s] | 1)) != 0) { - states_buffer[s] |= filter(states_buffer[s]) ^ xks_bit; - if(round > 4) { - update_contribution(states_buffer, s, m1, m2); - } - } else if(filter(states_buffer[s]) == xks_bit) { - // TODO: Refactor - if(round > 4) { - states_buffer[++states_tail] = states_buffer[s + 1]; - states_buffer[s + 1] = states_buffer[s] | 1; - update_contribution(states_buffer, s, m1, m2); - s++; - update_contribution(states_buffer, s, m1, m2); - } else { - states_buffer[++states_tail] = states_buffer[++s]; - states_buffer[s] = states_buffer[s - 1] | 1; - } - } else { - states_buffer[s--] = states_buffer[states_tail--]; - } - } - } - - return states_tail; -} - -int binsearch(unsigned int data[], int start, int stop) { - int mid, val = data[stop] & 0xff000000; - while(start != stop) { - mid = (stop - start) >> 1; - if((data[start + mid] ^ 0x80000000) > (val ^ 0x80000000)) - stop = start + mid; - else - start += mid + 1; - } - return start; -} -void quicksort(unsigned int array[], int low, int high) { - //if (SIZEOF(array) == 0) - // return; - if(low >= high) return; - int middle = low + (high - low) / 2; - unsigned int pivot = array[middle]; - int i = low, j = high; - while(i <= j) { - while(array[i] < pivot) { - i++; - } - while(array[j] > pivot) { - j--; - } - if(i <= j) { // swap - int temp = array[i]; - array[i] = array[j]; - array[j] = temp; - i++; - j--; - } - } - if(low < j) { - quicksort(array, low, j); - } - if(high > i) { - quicksort(array, i, high); - } -} -int extend_table(unsigned int data[], int tbl, int end, int bit, int m1, int m2) { - for(data[tbl] <<= 1; tbl <= end; data[++tbl] <<= 1) { - if((filter(data[tbl]) ^ filter(data[tbl] | 1)) != 0) { - data[tbl] |= filter(data[tbl]) ^ bit; - update_contribution(data, tbl, m1, m2); - } else if(filter(data[tbl]) == bit) { - data[++end] = data[tbl + 1]; - data[tbl + 1] = data[tbl] | 1; - update_contribution(data, tbl, m1, m2); - tbl++; - update_contribution(data, tbl, m1, m2); - } else { - data[tbl--] = data[end--]; - } - } - return end; -} - -int old_recover( - unsigned int odd[], - int o_head, - int o_tail, - int oks, - unsigned int even[], - int e_head, - int e_tail, - int eks, - int rem, - int s, - struct Crypto1Params* p, - int first_run) { - int o, e, i; - if(rem == -1) { - for(e = e_head; e <= e_tail; ++e) { - even[e] = (even[e] << 1) ^ evenparity32(even[e] & LF_POLY_EVEN); - for(o = o_head; o <= o_tail; ++o, ++s) { - struct Crypto1State temp = {0, 0}; - temp.even = odd[o]; - temp.odd = even[e] ^ evenparity32(odd[o] & LF_POLY_ODD); - if(check_state(&temp, p)) { - return -1; - } - } - } - return s; - } - if(first_run == 0) { - for(i = 0; (i < 4) && (rem-- != 0); i++) { - oks >>= 1; - eks >>= 1; - o_tail = extend_table( - odd, o_head, o_tail, oks & 1, LF_POLY_EVEN << 1 | 1, LF_POLY_ODD << 1); - if(o_head > o_tail) return s; - e_tail = - extend_table(even, e_head, e_tail, eks & 1, LF_POLY_ODD, LF_POLY_EVEN << 1 | 1); - if(e_head > e_tail) return s; - } - } - first_run = 0; - quicksort(odd, o_head, o_tail); - quicksort(even, e_head, e_tail); - while(o_tail >= o_head && e_tail >= e_head) { - if(((odd[o_tail] ^ even[e_tail]) >> 24) == 0) { - o_tail = binsearch(odd, o_head, o = o_tail); - e_tail = binsearch(even, e_head, e = e_tail); - s = old_recover(odd, o_tail--, o, oks, even, e_tail--, e, eks, rem, s, p, first_run); - if(s == -1) { - break; - } - } else if((odd[o_tail] ^ 0x80000000) > (even[e_tail] ^ 0x80000000)) { - o_tail = binsearch(odd, o_head, o_tail) - 1; - } else { - e_tail = binsearch(even, e_head, e_tail) - 1; - } - } - return s; -} - -static inline int sync_state(ProgramState* program_state) { - int ts = furi_hal_rtc_get_timestamp(); - program_state->eta_round = program_state->eta_round - (ts - program_state->eta_timestamp); - program_state->eta_total = program_state->eta_total - (ts - program_state->eta_timestamp); - program_state->eta_timestamp = ts; - if(program_state->close_thread_please) { - return 1; - } - return 0; -} - -int calculate_msb_tables( - int oks, - int eks, - int msb_round, - struct Crypto1Params* p, - unsigned int* states_buffer, - struct Msb* odd_msbs, - struct Msb* even_msbs, - unsigned int* temp_states_odd, - unsigned int* temp_states_even, - ProgramState* program_state) { - //FURI_LOG_I(TAG, "MSB GO %i", msb_iter); // DEBUG - unsigned int msb_head = (MSB_LIMIT * msb_round); // msb_iter ranges from 0 to (256/MSB_LIMIT)-1 - unsigned int msb_tail = (MSB_LIMIT * (msb_round + 1)); - int states_tail = 0, tail = 0; - int i = 0, j = 0, semi_state = 0, found = 0; - unsigned int msb = 0; - // TODO: Why is this necessary? - memset(odd_msbs, 0, MSB_LIMIT * sizeof(struct Msb)); - memset(even_msbs, 0, MSB_LIMIT * sizeof(struct Msb)); - - for(semi_state = 1 << 20; semi_state >= 0; semi_state--) { - if(semi_state % 32768 == 0) { - if(sync_state(program_state) == 1) { - return 0; - } - } - - if(filter(semi_state) == (oks & 1)) { //-V547 - states_buffer[0] = semi_state; - states_tail = state_loop(states_buffer, oks, CONST_M1_1, CONST_M2_1); - - for(i = states_tail; i >= 0; i--) { - msb = states_buffer[i] >> 24; - if((msb >= msb_head) && (msb < msb_tail)) { - found = 0; - for(j = 0; j < odd_msbs[msb - msb_head].tail - 1; j++) { - if(odd_msbs[msb - msb_head].states[j] == states_buffer[i]) { - found = 1; - break; - } - } - - if(!found) { - tail = odd_msbs[msb - msb_head].tail++; - odd_msbs[msb - msb_head].states[tail] = states_buffer[i]; - } - } - } - } - - if(filter(semi_state) == (eks & 1)) { //-V547 - states_buffer[0] = semi_state; - states_tail = state_loop(states_buffer, eks, CONST_M1_2, CONST_M2_2); - - for(i = 0; i <= states_tail; i++) { - msb = states_buffer[i] >> 24; - if((msb >= msb_head) && (msb < msb_tail)) { - found = 0; - - for(j = 0; j < even_msbs[msb - msb_head].tail; j++) { - if(even_msbs[msb - msb_head].states[j] == states_buffer[i]) { - found = 1; - break; - } - } - - if(!found) { - tail = even_msbs[msb - msb_head].tail++; - even_msbs[msb - msb_head].states[tail] = states_buffer[i]; - } - } - } - } - } - - oks >>= 12; - eks >>= 12; - - for(i = 0; i < MSB_LIMIT; i++) { - if(sync_state(program_state) == 1) { - return 0; - } - // TODO: Why is this necessary? - memset(temp_states_even, 0, sizeof(unsigned int) * (1280)); - memset(temp_states_odd, 0, sizeof(unsigned int) * (1280)); - memcpy(temp_states_odd, odd_msbs[i].states, odd_msbs[i].tail * sizeof(unsigned int)); - memcpy(temp_states_even, even_msbs[i].states, even_msbs[i].tail * sizeof(unsigned int)); - int res = old_recover( - temp_states_odd, - 0, - odd_msbs[i].tail, - oks, - temp_states_even, - 0, - even_msbs[i].tail, - eks, - 3, - 0, - p, - 1); - if(res == -1) { - return 1; - } - //odd_msbs[i].tail = 0; - //even_msbs[i].tail = 0; - } - - return 0; -} - -bool recover(struct Crypto1Params* p, int ks2, ProgramState* program_state) { - bool found = false; - unsigned int* states_buffer = malloc(sizeof(unsigned int) * (2 << 9)); - struct Msb* odd_msbs = (struct Msb*)malloc(MSB_LIMIT * sizeof(struct Msb)); - struct Msb* even_msbs = (struct Msb*)malloc(MSB_LIMIT * sizeof(struct Msb)); - unsigned int* temp_states_odd = malloc(sizeof(unsigned int) * (1280)); - unsigned int* temp_states_even = malloc(sizeof(unsigned int) * (1280)); - int oks = 0, eks = 0; - int i = 0, msb = 0; - for(i = 31; i >= 0; i -= 2) { - oks = oks << 1 | BEBIT(ks2, i); - } - for(i = 30; i >= 0; i -= 2) { - eks = eks << 1 | BEBIT(ks2, i); - } - int bench_start = furi_hal_rtc_get_timestamp(); - program_state->eta_total = eta_total_time; - program_state->eta_timestamp = bench_start; - for(msb = 0; msb <= ((256 / MSB_LIMIT) - 1); msb++) { - program_state->search = msb; - program_state->eta_round = eta_round_time; - program_state->eta_total = eta_total_time - (eta_round_time * msb); - if(calculate_msb_tables( - oks, - eks, - msb, - p, - states_buffer, - odd_msbs, - even_msbs, - temp_states_odd, - temp_states_even, - program_state)) { - int bench_stop = furi_hal_rtc_get_timestamp(); - FURI_LOG_I(TAG, "Cracked in %i seconds", bench_stop - bench_start); - found = true; - break; - } - if(program_state->close_thread_please) { - break; - } - } - free(states_buffer); - free(odd_msbs); - free(even_msbs); - free(temp_states_odd); - free(temp_states_even); - return found; -} - -bool napi_mf_classic_dict_check_presence(MfClassicDictType dict_type) { - Storage* storage = furi_record_open(RECORD_STORAGE); - - bool dict_present = false; - if(dict_type == MfClassicDictTypeSystem) { - dict_present = storage_common_stat(storage, MF_CLASSIC_DICT_FLIPPER_PATH, NULL) == FSE_OK; - } else if(dict_type == MfClassicDictTypeUser) { - dict_present = storage_common_stat(storage, MF_CLASSIC_DICT_USER_PATH, NULL) == FSE_OK; - } - - furi_record_close(RECORD_STORAGE); - - return dict_present; -} - -MfClassicDict* napi_mf_classic_dict_alloc(MfClassicDictType dict_type) { - MfClassicDict* dict = malloc(sizeof(MfClassicDict)); - Storage* storage = furi_record_open(RECORD_STORAGE); - dict->stream = buffered_file_stream_alloc(storage); - furi_record_close(RECORD_STORAGE); - - bool dict_loaded = false; - do { - if(dict_type == MfClassicDictTypeSystem) { - if(!buffered_file_stream_open( - dict->stream, - MF_CLASSIC_DICT_FLIPPER_PATH, - FSAM_READ_WRITE, - FSOM_OPEN_EXISTING)) { - buffered_file_stream_close(dict->stream); - break; - } - } else if(dict_type == MfClassicDictTypeUser) { - if(!buffered_file_stream_open( - dict->stream, MF_CLASSIC_DICT_USER_PATH, FSAM_READ_WRITE, FSOM_OPEN_ALWAYS)) { - buffered_file_stream_close(dict->stream); - break; - } - } - - // Check for newline ending - if(!stream_eof(dict->stream)) { - if(!stream_seek(dict->stream, -1, StreamOffsetFromEnd)) break; - uint8_t last_char = 0; - if(stream_read(dict->stream, &last_char, 1) != 1) break; - if(last_char != '\n') { - FURI_LOG_D(TAG, "Adding new line ending"); - if(stream_write_char(dict->stream, '\n') != 1) break; - } - if(!stream_rewind(dict->stream)) break; - } - - // Read total amount of keys - FuriString* next_line; - next_line = furi_string_alloc(); - while(true) { - if(!stream_read_line(dict->stream, next_line)) { - FURI_LOG_T(TAG, "No keys left in dict"); - break; - } - FURI_LOG_T( - TAG, - "Read line: %s, len: %zu", - furi_string_get_cstr(next_line), - furi_string_size(next_line)); - if(furi_string_get_char(next_line, 0) == '#') continue; - if(furi_string_size(next_line) != NFC_MF_CLASSIC_KEY_LEN) continue; - dict->total_keys++; - } - furi_string_free(next_line); - stream_rewind(dict->stream); - - dict_loaded = true; - FURI_LOG_I(TAG, "Loaded dictionary with %lu keys", dict->total_keys); - } while(false); - - if(!dict_loaded) { - buffered_file_stream_close(dict->stream); - free(dict); - dict = NULL; - } - - return dict; -} - -bool napi_mf_classic_dict_add_key_str(MfClassicDict* dict, FuriString* key) { - furi_assert(dict); - furi_assert(dict->stream); - FURI_LOG_I(TAG, "Saving key: %s", furi_string_get_cstr(key)); - - furi_string_cat_printf(key, "\n"); - - bool key_added = false; - do { - if(!stream_seek(dict->stream, 0, StreamOffsetFromEnd)) break; - if(!stream_insert_string(dict->stream, key)) break; - dict->total_keys++; - key_added = true; - } while(false); - - furi_string_left(key, 12); - return key_added; -} - -void napi_mf_classic_dict_free(MfClassicDict* dict) { - furi_assert(dict); - furi_assert(dict->stream); - - buffered_file_stream_close(dict->stream); - stream_free(dict->stream); - free(dict); -} - -static void napi_mf_classic_dict_int_to_str(uint8_t* key_int, FuriString* key_str) { - furi_string_reset(key_str); - for(size_t i = 0; i < 6; i++) { - furi_string_cat_printf(key_str, "%02X", key_int[i]); - } -} - -static void napi_mf_classic_dict_str_to_int(FuriString* key_str, uint64_t* key_int) { - uint8_t key_byte_tmp; - - *key_int = 0ULL; - for(uint8_t i = 0; i < 12; i += 2) { - args_char_to_hex( - furi_string_get_char(key_str, i), furi_string_get_char(key_str, i + 1), &key_byte_tmp); - *key_int |= (uint64_t)key_byte_tmp << (8 * (5 - i / 2)); - } -} - -uint32_t napi_mf_classic_dict_get_total_keys(MfClassicDict* dict) { - furi_assert(dict); - - return dict->total_keys; -} - -bool napi_mf_classic_dict_rewind(MfClassicDict* dict) { - furi_assert(dict); - furi_assert(dict->stream); - - return stream_rewind(dict->stream); -} - -bool napi_mf_classic_dict_get_next_key_str(MfClassicDict* dict, FuriString* key) { - furi_assert(dict); - furi_assert(dict->stream); - - bool key_read = false; - furi_string_reset(key); - while(!key_read) { - if(!stream_read_line(dict->stream, key)) break; - if(furi_string_get_char(key, 0) == '#') continue; - if(furi_string_size(key) != NFC_MF_CLASSIC_KEY_LEN) continue; - furi_string_left(key, 12); - key_read = true; - } - - return key_read; -} - -bool napi_mf_classic_dict_get_next_key(MfClassicDict* dict, uint64_t* key) { - furi_assert(dict); - furi_assert(dict->stream); - - FuriString* temp_key; - temp_key = furi_string_alloc(); - bool key_read = napi_mf_classic_dict_get_next_key_str(dict, temp_key); - if(key_read) { - napi_mf_classic_dict_str_to_int(temp_key, key); - } - furi_string_free(temp_key); - return key_read; -} - -bool napi_mf_classic_dict_is_key_present_str(MfClassicDict* dict, FuriString* key) { - furi_assert(dict); - furi_assert(dict->stream); - - FuriString* next_line; - next_line = furi_string_alloc(); - - bool key_found = false; - stream_rewind(dict->stream); - while(!key_found) { //-V654 - if(!stream_read_line(dict->stream, next_line)) break; - if(furi_string_get_char(next_line, 0) == '#') continue; - if(furi_string_size(next_line) != NFC_MF_CLASSIC_KEY_LEN) continue; - furi_string_left(next_line, 12); - if(!furi_string_equal(key, next_line)) continue; - key_found = true; - } - - furi_string_free(next_line); - return key_found; -} - -bool napi_mf_classic_dict_is_key_present(MfClassicDict* dict, uint8_t* key) { - FuriString* temp_key; - - temp_key = furi_string_alloc(); - napi_mf_classic_dict_int_to_str(key, temp_key); - bool key_found = napi_mf_classic_dict_is_key_present_str(dict, temp_key); - furi_string_free(temp_key); - return key_found; -} - -bool napi_key_already_found_for_nonce( - MfClassicDict* dict, - uint32_t uid_xor_nt1, - uint32_t nr1_enc, - uint32_t p64b, - uint32_t ar1_enc) { - bool found = false; - uint64_t k = 0; - napi_mf_classic_dict_rewind(dict); - while(napi_mf_classic_dict_get_next_key(dict, &k)) { - struct Crypto1State temp = {0, 0}; - int i; - for(i = 0; i < 24; i++) { - (&temp)->odd |= (BIT(k, 2 * i + 1) << (i ^ 3)); - (&temp)->even |= (BIT(k, 2 * i) << (i ^ 3)); - } - crypt_word_noret(&temp, uid_xor_nt1, 0); - crypt_word_noret(&temp, nr1_enc, 1); - if(ar1_enc == (crypt_word(&temp) ^ p64b)) { - found = true; - break; - } - } - return found; -} - -bool napi_mf_classic_nonces_check_presence() { - Storage* storage = furi_record_open(RECORD_STORAGE); - - bool nonces_present = storage_common_stat(storage, MF_CLASSIC_NONCE_PATH, NULL) == FSE_OK; - - furi_record_close(RECORD_STORAGE); - - return nonces_present; -} - -MfClassicNonceArray* napi_mf_classic_nonce_array_alloc( - MfClassicDict* system_dict, - bool system_dict_exists, - MfClassicDict* user_dict, - ProgramState* program_state) { - MfClassicNonceArray* nonce_array = malloc(sizeof(MfClassicNonceArray)); - MfClassicNonce* remaining_nonce_array_init = malloc(sizeof(MfClassicNonce) * 1); - nonce_array->remaining_nonce_array = remaining_nonce_array_init; - Storage* storage = furi_record_open(RECORD_STORAGE); - nonce_array->stream = buffered_file_stream_alloc(storage); - furi_record_close(RECORD_STORAGE); - - bool array_loaded = false; - do { - // https://github.com/flipperdevices/flipperzero-firmware/blob/5134f44c09d39344a8747655c0d59864bb574b96/applications/services/storage/filesystem_api_defines.h#L8-L22 - if(!buffered_file_stream_open( - nonce_array->stream, MF_CLASSIC_NONCE_PATH, FSAM_READ_WRITE, FSOM_OPEN_EXISTING)) { - buffered_file_stream_close(nonce_array->stream); - break; - } - - // Check for newline ending - if(!stream_eof(nonce_array->stream)) { - if(!stream_seek(nonce_array->stream, -1, StreamOffsetFromEnd)) break; - uint8_t last_char = 0; - if(stream_read(nonce_array->stream, &last_char, 1) != 1) break; - if(last_char != '\n') { - FURI_LOG_D(TAG, "Adding new line ending"); - if(stream_write_char(nonce_array->stream, '\n') != 1) break; - } - if(!stream_rewind(nonce_array->stream)) break; - } - - // Read total amount of nonces - FuriString* next_line; - next_line = furi_string_alloc(); - while(!(program_state->close_thread_please)) { - if(!stream_read_line(nonce_array->stream, next_line)) { - FURI_LOG_T(TAG, "No nonces left"); - break; - } - FURI_LOG_T( - TAG, - "Read line: %s, len: %zu", - furi_string_get_cstr(next_line), - furi_string_size(next_line)); - if(!furi_string_start_with_str(next_line, "Sec")) continue; - const char* next_line_cstr = furi_string_get_cstr(next_line); - MfClassicNonce res = {0}; - int i = 0; - char* endptr; - for(i = 0; i <= 17; i++) { - if(i != 0) { - next_line_cstr = strchr(next_line_cstr, ' '); - if(next_line_cstr) { - next_line_cstr++; - } else { - break; - } - } - unsigned long value = strtoul(next_line_cstr, &endptr, 16); - switch(i) { - case 5: - res.uid = value; - break; - case 7: - res.nt0 = value; - break; - case 9: - res.nr0_enc = value; - break; - case 11: - res.ar0_enc = value; - break; - case 13: - res.nt1 = value; - break; - case 15: - res.nr1_enc = value; - break; - case 17: - res.ar1_enc = value; - break; - default: - break; // Do nothing - } - next_line_cstr = endptr; - } - (program_state->total)++; - uint32_t p64b = prng_successor(res.nt1, 64); - if((system_dict_exists && - napi_key_already_found_for_nonce( - system_dict, res.uid ^ res.nt1, res.nr1_enc, p64b, res.ar1_enc)) || - (napi_key_already_found_for_nonce( - user_dict, res.uid ^ res.nt1, res.nr1_enc, p64b, res.ar1_enc))) { - (program_state->cracked)++; - (program_state->num_completed)++; - continue; - } - FURI_LOG_I(TAG, "No key found for %8lx %8lx", res.uid, res.ar1_enc); - // TODO: Refactor - nonce_array->remaining_nonce_array = realloc( //-V701 - nonce_array->remaining_nonce_array, - sizeof(MfClassicNonce) * ((nonce_array->remaining_nonces) + 1)); - nonce_array->remaining_nonces++; - nonce_array->remaining_nonce_array[(nonce_array->remaining_nonces) - 1] = res; - nonce_array->total_nonces++; - } - furi_string_free(next_line); - buffered_file_stream_close(nonce_array->stream); - - array_loaded = true; - FURI_LOG_I(TAG, "Loaded %lu nonces", nonce_array->total_nonces); - } while(false); - - if(!array_loaded) { - free(nonce_array); - nonce_array = NULL; - } - - return nonce_array; -} - -void napi_mf_classic_nonce_array_free(MfClassicNonceArray* nonce_array) { - furi_assert(nonce_array); - furi_assert(nonce_array->stream); - - buffered_file_stream_close(nonce_array->stream); - stream_free(nonce_array->stream); - free(nonce_array); -} - -static void finished_beep() { - // Beep to indicate completion - NotificationApp* notification = furi_record_open("notification"); - notification_message(notification, &sequence_audiovisual_alert); - notification_message(notification, &sequence_display_backlight_on); - furi_record_close("notification"); -} - -void mfkey32(ProgramState* program_state) { - uint64_t found_key; // recovered key - size_t keyarray_size = 0; - uint64_t* keyarray = malloc(sizeof(uint64_t) * 1); - uint32_t i = 0, j = 0; - // Check for nonces - if(!napi_mf_classic_nonces_check_presence()) { - program_state->err = MissingNonces; - program_state->mfkey_state = Error; - free(keyarray); - return; - } - // Read dictionaries (optional) - MfClassicDict* system_dict = {0}; - bool system_dict_exists = napi_mf_classic_dict_check_presence(MfClassicDictTypeSystem); - MfClassicDict* user_dict = {0}; - bool user_dict_exists = napi_mf_classic_dict_check_presence(MfClassicDictTypeUser); - uint32_t total_dict_keys = 0; - if(system_dict_exists) { - system_dict = napi_mf_classic_dict_alloc(MfClassicDictTypeSystem); - total_dict_keys += napi_mf_classic_dict_get_total_keys(system_dict); - } - user_dict = napi_mf_classic_dict_alloc(MfClassicDictTypeUser); - if(user_dict_exists) { - total_dict_keys += napi_mf_classic_dict_get_total_keys(user_dict); - } - user_dict_exists = true; - program_state->dict_count = total_dict_keys; - program_state->mfkey_state = DictionaryAttack; - // Read nonces - MfClassicNonceArray* nonce_arr; - nonce_arr = napi_mf_classic_nonce_array_alloc( - system_dict, system_dict_exists, user_dict, program_state); - if(system_dict_exists) { - napi_mf_classic_dict_free(system_dict); - } - if(nonce_arr->total_nonces == 0) { - // Nothing to crack - program_state->err = ZeroNonces; - program_state->mfkey_state = Error; - napi_mf_classic_nonce_array_free(nonce_arr); - napi_mf_classic_dict_free(user_dict); - free(keyarray); - return; - } - if(memmgr_get_free_heap() < MIN_RAM) { - // System has less than the guaranteed amount of RAM (140 KB) - adjust some parameters to run anyway at half speed - eta_round_time *= 2; - eta_total_time *= 2; - MSB_LIMIT /= 2; - } - program_state->mfkey_state = MfkeyAttack; - // TODO: Work backwards on this array and free memory - for(i = 0; i < nonce_arr->total_nonces; i++) { - MfClassicNonce next_nonce = nonce_arr->remaining_nonce_array[i]; - uint32_t p64 = prng_successor(next_nonce.nt0, 64); - uint32_t p64b = prng_successor(next_nonce.nt1, 64); - if(key_already_found_for_nonce( - keyarray, - keyarray_size, - next_nonce.uid ^ next_nonce.nt1, - next_nonce.nr1_enc, - p64b, - next_nonce.ar1_enc)) { - nonce_arr->remaining_nonces--; - (program_state->cracked)++; - (program_state->num_completed)++; - continue; - } - FURI_LOG_I(TAG, "Cracking %8lx %8lx", next_nonce.uid, next_nonce.ar1_enc); - struct Crypto1Params p = { - 0, - next_nonce.nr0_enc, - next_nonce.uid ^ next_nonce.nt0, - next_nonce.uid ^ next_nonce.nt1, - next_nonce.nr1_enc, - p64b, - next_nonce.ar1_enc}; - if(!recover(&p, next_nonce.ar0_enc ^ p64, program_state)) { - if(program_state->close_thread_please) { - break; - } - // No key found in recover() - (program_state->num_completed)++; - continue; - } - (program_state->cracked)++; - (program_state->num_completed)++; - found_key = p.key; - bool already_found = false; - for(j = 0; j < keyarray_size; j++) { - if(keyarray[j] == found_key) { - already_found = true; - break; - } - } - if(already_found == false) { - // New key - keyarray = realloc(keyarray, sizeof(uint64_t) * (keyarray_size + 1)); //-V701 - keyarray_size += 1; - keyarray[keyarray_size - 1] = found_key; - (program_state->unique_cracked)++; - } - } - // TODO: Update display to show all keys were found - // TODO: Prepend found key(s) to user dictionary file - //FURI_LOG_I(TAG, "Unique keys found:"); - for(i = 0; i < keyarray_size; i++) { - //FURI_LOG_I(TAG, "%012" PRIx64, keyarray[i]); - FuriString* temp_key = furi_string_alloc(); - furi_string_cat_printf(temp_key, "%012" PRIX64, keyarray[i]); - napi_mf_classic_dict_add_key_str(user_dict, temp_key); - furi_string_free(temp_key); - } - if(keyarray_size > 0) { - // TODO: Should we use DolphinDeedNfcMfcAdd? - dolphin_deed(DolphinDeedNfcMfcAdd); - } - napi_mf_classic_nonce_array_free(nonce_arr); - napi_mf_classic_dict_free(user_dict); - free(keyarray); - //FURI_LOG_I(TAG, "mfkey32 function completed normally"); // DEBUG - program_state->mfkey_state = Complete; - // No need to alert the user if they asked it to stop - if(!(program_state->close_thread_please)) { - finished_beep(); - } - return; -} - -// Screen is 128x64 px -static void render_callback(Canvas* const canvas, void* ctx) { - furi_assert(ctx); - ProgramState* program_state = ctx; - furi_mutex_acquire(program_state->mutex, FuriWaitForever); - char draw_str[44] = {}; - canvas_clear(canvas); - canvas_draw_frame(canvas, 0, 0, 128, 64); - canvas_draw_frame(canvas, 0, 15, 128, 64); - canvas_set_font(canvas, FontPrimary); - canvas_draw_str_aligned(canvas, 5, 4, AlignLeft, AlignTop, "Mfkey32"); - canvas_draw_icon(canvas, 114, 4, &I_mfkey); - if(program_state->is_thread_running && program_state->mfkey_state == MfkeyAttack) { - float eta_round = (float)1 - ((float)program_state->eta_round / (float)eta_round_time); - float eta_total = (float)1 - ((float)program_state->eta_total / (float)eta_total_time); - float progress = (float)program_state->num_completed / (float)program_state->total; - if(eta_round < 0) { - // Round ETA miscalculated - eta_round = 1; - program_state->eta_round = 0; - } - if(eta_total < 0) { - // Total ETA miscalculated - eta_total = 1; - program_state->eta_total = 0; - } - canvas_set_font(canvas, FontSecondary); - snprintf( - draw_str, - sizeof(draw_str), - "Cracking: %d/%d - in prog.", - program_state->num_completed, - program_state->total); - elements_progress_bar_with_text(canvas, 5, 18, 118, progress, draw_str); - snprintf( - draw_str, - sizeof(draw_str), - "Round: %d/%d - ETA %02d Sec", - (program_state->search) + 1, // Zero indexed - 256 / MSB_LIMIT, - program_state->eta_round); - elements_progress_bar_with_text(canvas, 5, 31, 118, eta_round, draw_str); - snprintf(draw_str, sizeof(draw_str), "Total ETA %03d Sec", program_state->eta_total); - elements_progress_bar_with_text(canvas, 5, 44, 118, eta_total, draw_str); - } else if(program_state->is_thread_running && program_state->mfkey_state == DictionaryAttack) { - canvas_set_font(canvas, FontSecondary); - snprintf( - draw_str, sizeof(draw_str), "Dict solves: %d (in progress)", program_state->cracked); - canvas_draw_str_aligned(canvas, 10, 18, AlignLeft, AlignTop, draw_str); - snprintf(draw_str, sizeof(draw_str), "Keys in dict: %d", program_state->dict_count); - canvas_draw_str_aligned(canvas, 26, 28, AlignLeft, AlignTop, draw_str); - } else if(program_state->mfkey_state == Complete) { - // TODO: Scrollable list view to see cracked keys if user presses down - elements_progress_bar_with_text(canvas, 5, 18, 118, 1, draw_str); - canvas_set_font(canvas, FontSecondary); - snprintf(draw_str, sizeof(draw_str), "Complete"); - canvas_draw_str_aligned(canvas, 40, 31, AlignLeft, AlignTop, draw_str); - snprintf( - draw_str, - sizeof(draw_str), - "Keys added to user dict: %d", - program_state->unique_cracked); - canvas_draw_str_aligned(canvas, 10, 41, AlignLeft, AlignTop, draw_str); - } else if(program_state->mfkey_state == Ready) { - canvas_set_font(canvas, FontSecondary); - canvas_draw_str_aligned(canvas, 50, 30, AlignLeft, AlignTop, "Ready"); - elements_button_center(canvas, "Start"); - elements_button_right(canvas, "Help"); - } else if(program_state->mfkey_state == Help) { - canvas_set_font(canvas, FontSecondary); - canvas_draw_str_aligned(canvas, 7, 20, AlignLeft, AlignTop, "Collect nonces using"); - canvas_draw_str_aligned(canvas, 7, 30, AlignLeft, AlignTop, "Detect Reader."); - canvas_draw_str_aligned(canvas, 7, 40, AlignLeft, AlignTop, "Developers: noproto, AG"); - canvas_draw_str_aligned(canvas, 7, 50, AlignLeft, AlignTop, "Thanks: bettse"); - } else if(program_state->mfkey_state == Error) { - canvas_draw_str_aligned(canvas, 50, 25, AlignLeft, AlignTop, "Error"); - canvas_set_font(canvas, FontSecondary); - if(program_state->err == MissingNonces) { - canvas_draw_str_aligned(canvas, 25, 36, AlignLeft, AlignTop, "No nonces found"); - } else if(program_state->err == ZeroNonces) { - canvas_draw_str_aligned(canvas, 15, 36, AlignLeft, AlignTop, "Nonces already cracked"); - } else { - // Unhandled error - } - } else { - // Unhandled program state - } - furi_mutex_release(program_state->mutex); -} - -static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) { - furi_assert(event_queue); - - PluginEvent event = {.type = EventTypeKey, .input = *input_event}; - furi_message_queue_put(event_queue, &event, FuriWaitForever); -} - -static void mfkey32_state_init(ProgramState* program_state) { - program_state->is_thread_running = false; - program_state->mfkey_state = Ready; - program_state->cracked = 0; - program_state->unique_cracked = 0; - program_state->num_completed = 0; - program_state->total = 0; - program_state->dict_count = 0; -} - -// Entrypoint for worker thread -static int32_t mfkey32_worker_thread(void* ctx) { - ProgramState* program_state = ctx; - program_state->is_thread_running = true; - program_state->mfkey_state = Initializing; - //FURI_LOG_I(TAG, "Hello from the mfkey32 worker thread"); // DEBUG - mfkey32(program_state); - program_state->is_thread_running = false; - return 0; -} - -void start_mfkey32_thread(ProgramState* program_state) { - if(!program_state->is_thread_running) { - furi_thread_start(program_state->mfkeythread); - } -} - -int32_t mfkey32_main() { - FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent)); - - ProgramState* program_state = malloc(sizeof(ProgramState)); - - mfkey32_state_init(program_state); - - program_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal); - if(!program_state->mutex) { - FURI_LOG_E(TAG, "cannot create mutex\r\n"); - free(program_state); - return 255; - } - - // Set system callbacks - ViewPort* view_port = view_port_alloc(); - view_port_draw_callback_set(view_port, render_callback, program_state); - view_port_input_callback_set(view_port, input_callback, event_queue); - - // Open GUI and register view_port - Gui* gui = furi_record_open(RECORD_GUI); - gui_add_view_port(gui, view_port, GuiLayerFullscreen); - - program_state->mfkeythread = furi_thread_alloc(); - furi_thread_set_name(program_state->mfkeythread, "Mfkey32 Worker"); - furi_thread_set_stack_size(program_state->mfkeythread, 2048); - furi_thread_set_context(program_state->mfkeythread, program_state); - furi_thread_set_callback(program_state->mfkeythread, mfkey32_worker_thread); - - PluginEvent event; - for(bool main_loop = true; main_loop;) { - FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100); - - furi_mutex_acquire(program_state->mutex, FuriWaitForever); - - if(event_status == FuriStatusOk) { - // press events - if(event.type == EventTypeKey) { - if(event.input.type == InputTypePress) { - switch(event.input.key) { - case InputKeyUp: - break; - case InputKeyDown: - break; - case InputKeyRight: - if(!program_state->is_thread_running && - program_state->mfkey_state == Ready) { - program_state->mfkey_state = Help; - view_port_update(view_port); - } - break; - case InputKeyLeft: - break; - case InputKeyOk: - if(!program_state->is_thread_running && - program_state->mfkey_state == Ready) { - start_mfkey32_thread(program_state); - view_port_update(view_port); - } - break; - case InputKeyBack: - if(!program_state->is_thread_running && - program_state->mfkey_state == Help) { - program_state->mfkey_state = Ready; - view_port_update(view_port); - } else { - program_state->close_thread_please = true; - if(program_state->is_thread_running && program_state->mfkeythread) { - // Wait until thread is finished - furi_thread_join(program_state->mfkeythread); - } - program_state->close_thread_please = false; - main_loop = false; - } - break; - default: - break; - } - } - } - } - - view_port_update(view_port); - furi_mutex_release(program_state->mutex); - } - - furi_thread_free(program_state->mfkeythread); - view_port_enabled_set(view_port, false); - gui_remove_view_port(gui, view_port); - furi_record_close("gui"); - view_port_free(view_port); - furi_message_queue_free(event_queue); - furi_mutex_free(program_state->mutex); - free(program_state); - - return 0; -} diff --git a/applications/external/nfc_magic/application.fam b/applications/external/nfc_magic/application.fam deleted file mode 100644 index a89b45d009f..00000000000 --- a/applications/external/nfc_magic/application.fam +++ /dev/null @@ -1,21 +0,0 @@ -App( - appid="nfc_magic", - name="Nfc Magic", - apptype=FlipperAppType.EXTERNAL, - targets=["f7"], - entry_point="nfc_magic_app", - requires=[ - "storage", - "gui", - ], - stack_size=4 * 1024, - order=30, - fap_icon="../../../assets/icons/Archive/125_10px.png", - fap_category="NFC", - fap_private_libs=[ - Lib( - name="magic", - ), - ], - fap_icon_assets="assets", -) diff --git a/applications/external/nfc_magic/assets/DolphinCommon_56x48.png b/applications/external/nfc_magic/assets/DolphinCommon_56x48.png deleted file mode 100644 index 089aaed83507431993a76ca25d32fdd9664c1c84..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1416 zcmaJ>eNYr-7(dh;KXS5&nWVIBjS_NizYg|x=Pr^vz*7zxJO|P-dw2IeZq?gec9-rD zoPZchQ_6}yP{Slc4I!!28K==nodOJ_nsCY-(wOq2uZbLx!rlYU{KIi)_Wj!D_j`WN z^FGgREXdEDF)ewT&1Re7Tj(uBvlG44lnH3;I%IzsO|z`*Vr!`uv?9QOwgs{#Ld+Ki zC9n_zxxBOkx@@+IwMwAaD)#3Ik`}gun2kLe))Crfb7e+#AgzHGCc+X$b>qJuIf`S7 z?8b}I{ghw#z>uiaLknQh@LJUrqHcVYS3v97F^OZN zCe|7^J|?QzUx0Zu17e(=CM1fYFpjtLk|a4~$g}e?hGH0!VoBOT&<=s(1ct%J9~?O} z$)jW_dkX9yTX~%W*i_IM%0{ z7EmP^_pKn`<5>E(SixgJU};7`)7Hidp&+DLnizsebUk}_-GfgbN^il9b`v)f+ z{o5Zry)d<7`fHQ^uw_;+x>mcPw0&8iW69x{k92O{Q}`yFdH=5d$pbf49w1&NS)G+vhr6y}5TMsofQirRDUmKilk5=(KGouJ{H9hW=$X zgi;)vI!jl!_4H3jD(?Jz=8By|i47I&tKA1y9{nfp;_|FxKBDNWp{hN9hJ1nU?z%J6 z?>UxyzWvO}Pgc~rCZ#5%Eq+_hNS~bBdiGlT&f%%e`hHjSySR2=JuK2^+%;$R3#Wz~ z=e_mfqW23bPa0fhe)HdE5+GelU&!jS3ckUZOQ)CC5?mo zo=tzG_4|RuvPUO|mhCwA>y)1c%SWC%a4?a-x|J*?ch~+n=R7o@>p6J2dE=$stKZmK z-xoTRwET2^Wu)&1U7!Ebw!!D?x`xwQX3pMnrRwCT?`4GHt4&?|cIiI{_^XYp-np>6 xE^lPSXzOYCC4X`6tl@OB1M5_S7jml-Y~(TPp{aTIejNKZ`m*!Atyxdk{0EAy49frj diff --git a/applications/external/nfc_magic/assets/DolphinNice_96x59.png b/applications/external/nfc_magic/assets/DolphinNice_96x59.png deleted file mode 100644 index a299d3630239b4486e249cc501872bed5996df3b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2459 zcmbVO3s4i+8V(M(gEFORwSrA`4O0uPn|M|5y* zB*aMDxC&7(gP9JN;POOi-9khrC>Z9YJs2U!LnVcQEEC0fDtKo&ILlzb30%M}3J^;~ zv7RzcsilOs4Mq@tD*&R;!LMSk2A~{(`HK9|hQBqEX)3sQr9Je6SZU*F-^fD-p+~Hs; zHLkO%v?>ZoxEv+F#whudr%615FkA0DYR0tMEo}3OOY#xecLWe>xV?u5KtSmC^ z7)Fmj6gjfKstiEV-*Cxbbb+&rRWuI_rBJ)ybs_f1Rn&f2>q3pYwI^|J(hdn{j{0EZIm_F zpIyIWLsRUgOItR-dUbVd|6Zo=_BU_Tj4|{{jxO#=JH4o8er(5{!nZD_j4}MH&zh~9 zVLC~y(0-D6GO0ghZD8BYzP?o{>22~lT6^d@X{SwQ8vrNY-PPIMajIwC)`s14Ep72@ zeq7YOzM`?U{+W)ocXBr`eSOcpk?Rxc=ou5&)fWW|pD};-Z0mvk9}=&`Rb&y<77W~a z(>6YM;6Y5aIU~JKZ}mQZynKHiSTQ#Bczn@&jTiN^?vPJ(jhm7cXLx0oum5P$`TceG zU+wR;OO^)8CVlnM)5p$CO&e94KJt>HccCaHGusmW_b`T6m| z-R6V6Db1pErTot?^d22ojm+2>_)FbD`_+WbDGMx9f@hO27maS2`csiV(D&Fs`PS2& zvrq18du_&zXID(!KIxsU$)iuTYuZ?zmYiP&n&i@Be{IdbS-jA2c0QAlu5NXQv_0K< z3Hvs4eeu6B7yD&CNT~gIkMV&UkRU=V!iQ(+_(O&u^ah$+s{_yn(yBYeD40HeU{xGsIT6W Zfq!wOp!QR4Q+~K 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 LTALIbyM_M?u*zuP diff --git a/applications/external/nfc_magic/assets/NFC_manual_60x50.png b/applications/external/nfc_magic/assets/NFC_manual_60x50.png deleted file mode 100644 index 787c0bcfe01755f4dcadcdce004a1a0fcfb06f41..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3804 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 IrsqTc1+$%)k^lez diff --git a/applications/external/nfc_magic/lib/magic/classic_gen1.c b/applications/external/nfc_magic/lib/magic/classic_gen1.c deleted file mode 100644 index 8d87d63161d..00000000000 --- a/applications/external/nfc_magic/lib/magic/classic_gen1.c +++ /dev/null @@ -1,145 +0,0 @@ -#include "classic_gen1.h" - -#include - -#define TAG "Magic" - -#define MAGIC_CMD_WUPA (0x40) -#define MAGIC_CMD_ACCESS (0x43) - -#define MAGIC_MIFARE_READ_CMD (0x30) -#define MAGIC_MIFARE_WRITE_CMD (0xA0) - -#define MAGIC_ACK (0x0A) - -#define MAGIC_BUFFER_SIZE (32) - -bool magic_gen1_wupa() { - bool magic_activated = false; - uint8_t tx_data[MAGIC_BUFFER_SIZE] = {}; - uint8_t rx_data[MAGIC_BUFFER_SIZE] = {}; - uint16_t rx_len = 0; - FuriHalNfcReturn ret = 0; - - do { - // Start communication - tx_data[0] = MAGIC_CMD_WUPA; - ret = furi_hal_nfc_ll_txrx_bits( - tx_data, - 7, - rx_data, - sizeof(rx_data), - &rx_len, - FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_TX_MANUAL | FURI_HAL_NFC_LL_TXRX_FLAGS_AGC_ON | - FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_RX_KEEP, - furi_hal_nfc_ll_ms2fc(20)); - if(ret != FuriHalNfcReturnIncompleteByte) break; - if(rx_len != 4) break; - if(rx_data[0] != MAGIC_ACK) break; - magic_activated = true; - } while(false); - - return magic_activated; -} - -bool magic_gen1_data_access_cmd() { - bool write_cmd_success = false; - uint8_t tx_data[MAGIC_BUFFER_SIZE] = {}; - uint8_t rx_data[MAGIC_BUFFER_SIZE] = {}; - uint16_t rx_len = 0; - FuriHalNfcReturn ret = 0; - - do { - tx_data[0] = MAGIC_CMD_ACCESS; - ret = furi_hal_nfc_ll_txrx_bits( - tx_data, - 8, - rx_data, - sizeof(rx_data), - &rx_len, - FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_TX_MANUAL | FURI_HAL_NFC_LL_TXRX_FLAGS_AGC_ON | - FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_RX_KEEP, - furi_hal_nfc_ll_ms2fc(20)); - if(ret != FuriHalNfcReturnIncompleteByte) break; - if(rx_len != 4) break; - if(rx_data[0] != MAGIC_ACK) break; - - write_cmd_success = true; - } while(false); - - return write_cmd_success; -} - -bool magic_gen1_read_block(uint8_t block_num, MfClassicBlock* data) { - furi_assert(data); - - bool read_success = false; - - uint8_t tx_data[MAGIC_BUFFER_SIZE] = {}; - uint8_t rx_data[MAGIC_BUFFER_SIZE] = {}; - uint16_t rx_len = 0; - FuriHalNfcReturn ret = 0; - - do { - tx_data[0] = MAGIC_MIFARE_READ_CMD; - tx_data[1] = block_num; - ret = furi_hal_nfc_ll_txrx_bits( - tx_data, - 2 * 8, - rx_data, - sizeof(rx_data), - &rx_len, - FURI_HAL_NFC_LL_TXRX_FLAGS_AGC_ON, - furi_hal_nfc_ll_ms2fc(20)); - - if(ret != FuriHalNfcReturnOk) break; - if(rx_len != 16 * 8) break; - memcpy(data->value, rx_data, sizeof(data->value)); - read_success = true; - } while(false); - - return read_success; -} - -bool magic_gen1_write_blk(uint8_t block_num, MfClassicBlock* data) { - furi_assert(data); - - bool write_success = false; - uint8_t tx_data[MAGIC_BUFFER_SIZE] = {}; - uint8_t rx_data[MAGIC_BUFFER_SIZE] = {}; - uint16_t rx_len = 0; - FuriHalNfcReturn ret = 0; - - do { - tx_data[0] = MAGIC_MIFARE_WRITE_CMD; - tx_data[1] = block_num; - ret = furi_hal_nfc_ll_txrx_bits( - tx_data, - 2 * 8, - rx_data, - sizeof(rx_data), - &rx_len, - FURI_HAL_NFC_LL_TXRX_FLAGS_AGC_ON | FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_RX_KEEP, - furi_hal_nfc_ll_ms2fc(20)); - if(ret != FuriHalNfcReturnIncompleteByte) break; - if(rx_len != 4) break; - if(rx_data[0] != MAGIC_ACK) break; - - memcpy(tx_data, data->value, sizeof(data->value)); - ret = furi_hal_nfc_ll_txrx_bits( - tx_data, - 16 * 8, - rx_data, - sizeof(rx_data), - &rx_len, - FURI_HAL_NFC_LL_TXRX_FLAGS_AGC_ON | FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_RX_KEEP, - furi_hal_nfc_ll_ms2fc(20)); - if(ret != FuriHalNfcReturnIncompleteByte) break; - if(rx_len != 4) break; - if(rx_data[0] != MAGIC_ACK) break; - - write_success = true; - } while(false); - - return write_success; -} diff --git a/applications/external/nfc_magic/lib/magic/classic_gen1.h b/applications/external/nfc_magic/lib/magic/classic_gen1.h deleted file mode 100644 index 98de1230239..00000000000 --- a/applications/external/nfc_magic/lib/magic/classic_gen1.h +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -#include - -bool magic_gen1_wupa(); - -bool magic_gen1_read_block(uint8_t block_num, MfClassicBlock* data); - -bool magic_gen1_data_access_cmd(); - -bool magic_gen1_write_blk(uint8_t block_num, MfClassicBlock* data); diff --git a/applications/external/nfc_magic/lib/magic/common.c b/applications/external/nfc_magic/lib/magic/common.c deleted file mode 100644 index 0ea3cb218dd..00000000000 --- a/applications/external/nfc_magic/lib/magic/common.c +++ /dev/null @@ -1,33 +0,0 @@ -#include "common.h" - -#include - -#define REQA (0x26) -#define CL1_PREFIX (0x93) -#define SELECT (0x70) - -#define MAGIC_BUFFER_SIZE (32) - -bool magic_activate() { - FuriHalNfcReturn ret = 0; - - // Setup nfc poller - furi_hal_nfc_exit_sleep(); - furi_hal_nfc_ll_txrx_on(); - furi_hal_nfc_ll_poll(); - ret = furi_hal_nfc_ll_set_mode( - FuriHalNfcModePollNfca, FuriHalNfcBitrate106, FuriHalNfcBitrate106); - if(ret != FuriHalNfcReturnOk) return false; - - furi_hal_nfc_ll_set_fdt_listen(FURI_HAL_NFC_LL_FDT_LISTEN_NFCA_POLLER); - furi_hal_nfc_ll_set_fdt_poll(FURI_HAL_NFC_LL_FDT_POLL_NFCA_POLLER); - furi_hal_nfc_ll_set_error_handling(FuriHalNfcErrorHandlingNfc); - furi_hal_nfc_ll_set_guard_time(FURI_HAL_NFC_LL_GT_NFCA); - - return true; -} - -void magic_deactivate() { - furi_hal_nfc_ll_txrx_off(); - furi_hal_nfc_sleep(); -} \ No newline at end of file diff --git a/applications/external/nfc_magic/lib/magic/common.h b/applications/external/nfc_magic/lib/magic/common.h deleted file mode 100644 index bef166c8f22..00000000000 --- a/applications/external/nfc_magic/lib/magic/common.h +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once - -#include -#include - -typedef enum { - MagicTypeClassicGen1, - MagicTypeClassicDirectWrite, - MagicTypeClassicAPDU, - MagicTypeUltralightGen1, - MagicTypeUltralightDirectWrite, - MagicTypeUltralightC_Gen1, - MagicTypeUltralightC_DirectWrite, - MagicTypeGen4, -} MagicType; - -bool magic_activate(); - -void magic_deactivate(); \ No newline at end of file diff --git a/applications/external/nfc_magic/lib/magic/gen4.c b/applications/external/nfc_magic/lib/magic/gen4.c deleted file mode 100644 index 31be649a04e..00000000000 --- a/applications/external/nfc_magic/lib/magic/gen4.c +++ /dev/null @@ -1,199 +0,0 @@ -#include "gen4.h" - -#include -#include - -#define TAG "Magic" - -#define MAGIC_CMD_PREFIX (0xCF) - -#define MAGIC_CMD_GET_CFG (0xC6) -#define MAGIC_CMD_WRITE (0xCD) -#define MAGIC_CMD_READ (0xCE) -#define MAGIC_CMD_SET_CFG (0xF0) -#define MAGIC_CMD_FUSE_CFG (0xF1) -#define MAGIC_CMD_SET_PWD (0xFE) - -#define MAGIC_BUFFER_SIZE (40) - -const uint8_t MAGIC_DEFAULT_CONFIG[] = { - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x09, 0x78, 0x00, 0x91, 0x02, 0xDA, 0xBC, 0x19, 0x10, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x04, 0x00, 0x08, 0x00 -}; - -const uint8_t MAGIC_DEFAULT_BLOCK0[] = { - 0x00, 0x01, 0x02, 0x03, 0x04, 0x04, 0x08, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 -}; - -const uint8_t MAGIC_EMPTY_BLOCK[16] = { 0 }; - -const uint8_t MAGIC_DEFAULT_SECTOR_TRAILER[] = { - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x07, 0x80, 0x69, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF -}; - -static bool magic_gen4_is_block_num_trailer(uint8_t n) { - n++; - if (n < 32 * 4) { - return (n % 4 == 0); - } - - return (n % 16 == 0); -} - -bool magic_gen4_get_cfg(uint32_t pwd, uint8_t* config) { - bool is_valid_config_len = false; - uint8_t tx_data[MAGIC_BUFFER_SIZE] = {}; - uint8_t rx_data[MAGIC_BUFFER_SIZE] = {}; - uint16_t rx_len = 0; - FuriHalNfcReturn ret = 0; - - do { - // Start communication - tx_data[0] = MAGIC_CMD_PREFIX; - tx_data[1] = (uint8_t)(pwd >> 24); - tx_data[2] = (uint8_t)(pwd >> 16); - tx_data[3] = (uint8_t)(pwd >> 8); - tx_data[4] = (uint8_t)pwd; - tx_data[5] = MAGIC_CMD_GET_CFG; - ret = furi_hal_nfc_ll_txrx( - tx_data, - 6, - rx_data, - sizeof(rx_data), - &rx_len, - FURI_HAL_NFC_TXRX_DEFAULT, - furi_hal_nfc_ll_ms2fc(20)); - if(ret != FuriHalNfcReturnOk) break; - if(rx_len != 30 && rx_len != 32) break; - memcpy(config, rx_data, rx_len); - is_valid_config_len = true; - } while(false); - - return is_valid_config_len; -} - -bool magic_gen4_set_cfg(uint32_t pwd, const uint8_t* config, uint8_t config_length, bool fuse) { - bool write_success = false; - uint8_t tx_data[MAGIC_BUFFER_SIZE] = {}; - uint8_t rx_data[MAGIC_BUFFER_SIZE] = {}; - uint16_t rx_len = 0; - FuriHalNfcReturn ret = 0; - - do { - // Start communication - tx_data[0] = MAGIC_CMD_PREFIX; - tx_data[1] = (uint8_t)(pwd >> 24); - tx_data[2] = (uint8_t)(pwd >> 16); - tx_data[3] = (uint8_t)(pwd >> 8); - tx_data[4] = (uint8_t)pwd; - tx_data[5] = fuse ? MAGIC_CMD_FUSE_CFG : MAGIC_CMD_SET_CFG; - memcpy(tx_data + 6, config, config_length); - ret = furi_hal_nfc_ll_txrx( - tx_data, - 6 + config_length, - rx_data, - sizeof(rx_data), - &rx_len, - FURI_HAL_NFC_TXRX_DEFAULT, - furi_hal_nfc_ll_ms2fc(20)); - if(ret != FuriHalNfcReturnOk) break; - if(rx_len != 2) break; - write_success = true; - } while(false); - - return write_success; -} - -bool magic_gen4_set_pwd(uint32_t old_pwd, uint32_t new_pwd) { - bool change_success = false; - uint8_t tx_data[MAGIC_BUFFER_SIZE] = {}; - uint8_t rx_data[MAGIC_BUFFER_SIZE] = {}; - uint16_t rx_len = 0; - FuriHalNfcReturn ret = 0; - - do { - // Start communication - tx_data[0] = MAGIC_CMD_PREFIX; - tx_data[1] = (uint8_t)(old_pwd >> 24); - tx_data[2] = (uint8_t)(old_pwd >> 16); - tx_data[3] = (uint8_t)(old_pwd >> 8); - tx_data[4] = (uint8_t)old_pwd; - tx_data[5] = MAGIC_CMD_SET_PWD; - tx_data[6] = (uint8_t)(new_pwd >> 24); - tx_data[7] = (uint8_t)(new_pwd >> 16); - tx_data[8] = (uint8_t)(new_pwd >> 8); - tx_data[9] = (uint8_t)new_pwd; - ret = furi_hal_nfc_ll_txrx( - tx_data, - 10, - rx_data, - sizeof(rx_data), - &rx_len, - FURI_HAL_NFC_TXRX_DEFAULT, - furi_hal_nfc_ll_ms2fc(20)); - FURI_LOG_I(TAG, "ret %d, len %d", ret, rx_len); - if(ret != FuriHalNfcReturnOk) break; - if(rx_len != 2) break; - change_success = true; - } while(false); - - return change_success; -} - -bool magic_gen4_write_blk(uint32_t pwd, uint8_t block_num, const uint8_t* data) { - bool write_success = false; - uint8_t tx_data[MAGIC_BUFFER_SIZE] = {}; - uint8_t rx_data[MAGIC_BUFFER_SIZE] = {}; - uint16_t rx_len = 0; - FuriHalNfcReturn ret = 0; - - do { - // Start communication - tx_data[0] = MAGIC_CMD_PREFIX; - tx_data[1] = (uint8_t)(pwd >> 24); - tx_data[2] = (uint8_t)(pwd >> 16); - tx_data[3] = (uint8_t)(pwd >> 8); - tx_data[4] = (uint8_t)pwd; - tx_data[5] = MAGIC_CMD_WRITE; - tx_data[6] = block_num; - memcpy(tx_data + 7, data, 16); - ret = furi_hal_nfc_ll_txrx( - tx_data, - 23, - rx_data, - sizeof(rx_data), - &rx_len, - FURI_HAL_NFC_TXRX_DEFAULT, - furi_hal_nfc_ll_ms2fc(200)); - if(ret != FuriHalNfcReturnOk) break; - if(rx_len != 2) break; - write_success = true; - } while(false); - - return write_success; -} - -bool magic_gen4_wipe(uint32_t pwd) { - if(!magic_gen4_set_cfg(pwd, MAGIC_DEFAULT_CONFIG, sizeof(MAGIC_DEFAULT_CONFIG), false)) { - FURI_LOG_E(TAG, "Set config failed"); - return false; - } - if(!magic_gen4_write_blk(pwd, 0, MAGIC_DEFAULT_BLOCK0)) { - FURI_LOG_E(TAG, "Block 0 write failed"); - return false; - } - for(size_t i = 1; i < 64; i++) { - const uint8_t* block = magic_gen4_is_block_num_trailer(i) ? MAGIC_DEFAULT_SECTOR_TRAILER : MAGIC_EMPTY_BLOCK; - if(!magic_gen4_write_blk(pwd, i, block)) { - FURI_LOG_E(TAG, "Block %d write failed", i); - return false; - } - } - for(size_t i = 65; i < 256; i++) { - if(!magic_gen4_write_blk(pwd, i, MAGIC_EMPTY_BLOCK)) { - FURI_LOG_E(TAG, "Block %d write failed", i); - return false; - } - } - - return true; -} \ No newline at end of file diff --git a/applications/external/nfc_magic/lib/magic/gen4.h b/applications/external/nfc_magic/lib/magic/gen4.h deleted file mode 100644 index c515af820b0..00000000000 --- a/applications/external/nfc_magic/lib/magic/gen4.h +++ /dev/null @@ -1,48 +0,0 @@ -#pragma once - -#include - -#define MAGIC_GEN4_DEFAULT_PWD 0x00000000 -#define MAGIC_GEN4_CONFIG_LEN 32 - -#define NFCID1_SINGLE_SIZE 4 -#define NFCID1_DOUBLE_SIZE 7 -#define NFCID1_TRIPLE_SIZE 10 - -typedef enum { - MagicGen4UIDLengthSingle = 0x00, - MagicGen4UIDLengthDouble = 0x01, - MagicGen4UIDLengthTriple = 0x02 -} MagicGen4UIDLength; - -typedef enum { - MagicGen4UltralightModeUL_EV1 = 0x00, - MagicGen4UltralightModeNTAG = 0x01, - MagicGen4UltralightModeUL_C = 0x02, - MagicGen4UltralightModeUL = 0x03 -} MagicGen4UltralightMode; - -typedef enum { - // for writing original (shadow) data - MagicGen4ShadowModePreWrite = 0x00, - // written data can be read once before restored to original - MagicGen4ShadowModeRestore = 0x01, - // written data is discarded - MagicGen4ShadowModeIgnore = 0x02, - // apparently for UL? - MagicGen4ShadowModeHighSpeedIgnore = 0x03 -} MagicGen4ShadowMode; - -bool magic_gen4_get_cfg(uint32_t pwd, uint8_t* config); - -bool magic_gen4_set_cfg(uint32_t pwd, const uint8_t* config, uint8_t config_length, bool fuse); - -bool magic_gen4_set_pwd(uint32_t old_pwd, uint32_t new_pwd); - -bool magic_gen4_read_blk(uint32_t pwd, uint8_t block_num, uint8_t* data); - -bool magic_gen4_write_blk(uint32_t pwd, uint8_t block_num, const uint8_t* data); - -bool magic_gen4_wipe(uint32_t pwd); - -void magic_gen4_deactivate(); diff --git a/applications/external/nfc_magic/lib/magic/types.c b/applications/external/nfc_magic/lib/magic/types.c deleted file mode 100644 index 77c6c0a4eff..00000000000 --- a/applications/external/nfc_magic/lib/magic/types.c +++ /dev/null @@ -1,23 +0,0 @@ -#include "types.h" - -const char* nfc_magic_type(MagicType type) { - if(type == MagicTypeClassicGen1) { - return "Classic Gen 1A/B"; - } else if(type == MagicTypeClassicDirectWrite) { - return "Classic DirectWrite"; - } else if(type == MagicTypeClassicAPDU) { - return "Classic APDU"; - } else if(type == MagicTypeUltralightGen1) { - return "Ultralight Gen 1"; - } else if(type == MagicTypeUltralightDirectWrite) { - return "Ultralight DirectWrite"; - } else if(type == MagicTypeUltralightC_Gen1) { - return "Ultralight-C Gen 1"; - } else if(type == MagicTypeUltralightC_DirectWrite) { - return "Ultralight-C DirectWrite"; - } else if(type == MagicTypeGen4) { - return "Gen 4 GTU"; - } else { - return "Unknown"; - } -} diff --git a/applications/external/nfc_magic/lib/magic/types.h b/applications/external/nfc_magic/lib/magic/types.h deleted file mode 100644 index dbf5540637c..00000000000 --- a/applications/external/nfc_magic/lib/magic/types.h +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once - -#include "common.h" - -const char* nfc_magic_type(MagicType type); \ No newline at end of file diff --git a/applications/external/nfc_magic/nfc_magic.c b/applications/external/nfc_magic/nfc_magic.c deleted file mode 100644 index 68c9a65b5fd..00000000000 --- a/applications/external/nfc_magic/nfc_magic.c +++ /dev/null @@ -1,184 +0,0 @@ -#include "nfc_magic_i.h" - -bool nfc_magic_custom_event_callback(void* context, uint32_t event) { - furi_assert(context); - NfcMagic* nfc_magic = context; - return scene_manager_handle_custom_event(nfc_magic->scene_manager, event); -} - -bool nfc_magic_back_event_callback(void* context) { - furi_assert(context); - NfcMagic* nfc_magic = context; - return scene_manager_handle_back_event(nfc_magic->scene_manager); -} - -void nfc_magic_tick_event_callback(void* context) { - furi_assert(context); - NfcMagic* nfc_magic = context; - scene_manager_handle_tick_event(nfc_magic->scene_manager); -} - -void nfc_magic_show_loading_popup(void* context, bool show) { - NfcMagic* nfc_magic = context; - TaskHandle_t timer_task = xTaskGetHandle(configTIMER_SERVICE_TASK_NAME); - - if(show) { - // Raise timer priority so that animations can play - vTaskPrioritySet(timer_task, configMAX_PRIORITIES - 1); - view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewLoading); - } else { - // Restore default timer priority - vTaskPrioritySet(timer_task, configTIMER_TASK_PRIORITY); - } -} - -NfcMagic* nfc_magic_alloc() { - NfcMagic* nfc_magic = malloc(sizeof(NfcMagic)); - - nfc_magic->worker = nfc_magic_worker_alloc(); - nfc_magic->view_dispatcher = view_dispatcher_alloc(); - nfc_magic->scene_manager = scene_manager_alloc(&nfc_magic_scene_handlers, nfc_magic); - view_dispatcher_enable_queue(nfc_magic->view_dispatcher); - view_dispatcher_set_event_callback_context(nfc_magic->view_dispatcher, nfc_magic); - view_dispatcher_set_custom_event_callback( - nfc_magic->view_dispatcher, nfc_magic_custom_event_callback); - view_dispatcher_set_navigation_event_callback( - nfc_magic->view_dispatcher, nfc_magic_back_event_callback); - view_dispatcher_set_tick_event_callback( - nfc_magic->view_dispatcher, nfc_magic_tick_event_callback, 100); - - // Nfc device - nfc_magic->dev = malloc(sizeof(NfcMagicDevice)); - nfc_magic->source_dev = nfc_device_alloc(); - furi_string_set(nfc_magic->source_dev->folder, NFC_APP_FOLDER); - - // Open GUI record - nfc_magic->gui = furi_record_open(RECORD_GUI); - view_dispatcher_attach_to_gui( - nfc_magic->view_dispatcher, nfc_magic->gui, ViewDispatcherTypeFullscreen); - - // Open Notification record - nfc_magic->notifications = furi_record_open(RECORD_NOTIFICATION); - - // Submenu - nfc_magic->submenu = submenu_alloc(); - view_dispatcher_add_view( - nfc_magic->view_dispatcher, NfcMagicViewMenu, submenu_get_view(nfc_magic->submenu)); - - // Popup - nfc_magic->popup = popup_alloc(); - view_dispatcher_add_view( - nfc_magic->view_dispatcher, NfcMagicViewPopup, popup_get_view(nfc_magic->popup)); - - // Loading - nfc_magic->loading = loading_alloc(); - view_dispatcher_add_view( - nfc_magic->view_dispatcher, NfcMagicViewLoading, loading_get_view(nfc_magic->loading)); - - // Text Input - nfc_magic->text_input = text_input_alloc(); - view_dispatcher_add_view( - nfc_magic->view_dispatcher, - NfcMagicViewTextInput, - text_input_get_view(nfc_magic->text_input)); - - // Byte Input - nfc_magic->byte_input = byte_input_alloc(); - view_dispatcher_add_view( - nfc_magic->view_dispatcher, - NfcMagicViewByteInput, - byte_input_get_view(nfc_magic->byte_input)); - - // Custom Widget - nfc_magic->widget = widget_alloc(); - view_dispatcher_add_view( - nfc_magic->view_dispatcher, NfcMagicViewWidget, widget_get_view(nfc_magic->widget)); - - return nfc_magic; -} - -void nfc_magic_free(NfcMagic* nfc_magic) { - furi_assert(nfc_magic); - - // Nfc device - free(nfc_magic->dev); - nfc_device_free(nfc_magic->source_dev); - - // Submenu - view_dispatcher_remove_view(nfc_magic->view_dispatcher, NfcMagicViewMenu); - submenu_free(nfc_magic->submenu); - - // Popup - view_dispatcher_remove_view(nfc_magic->view_dispatcher, NfcMagicViewPopup); - popup_free(nfc_magic->popup); - - // Loading - view_dispatcher_remove_view(nfc_magic->view_dispatcher, NfcMagicViewLoading); - loading_free(nfc_magic->loading); - - // Text Input - view_dispatcher_remove_view(nfc_magic->view_dispatcher, NfcMagicViewTextInput); - text_input_free(nfc_magic->text_input); - - // Byte Input - view_dispatcher_remove_view(nfc_magic->view_dispatcher, NfcMagicViewByteInput); - byte_input_free(nfc_magic->byte_input); - - // Custom Widget - view_dispatcher_remove_view(nfc_magic->view_dispatcher, NfcMagicViewWidget); - widget_free(nfc_magic->widget); - - // Worker - nfc_magic_worker_stop(nfc_magic->worker); - nfc_magic_worker_free(nfc_magic->worker); - - // View Dispatcher - view_dispatcher_free(nfc_magic->view_dispatcher); - - // Scene Manager - scene_manager_free(nfc_magic->scene_manager); - - // GUI - furi_record_close(RECORD_GUI); - nfc_magic->gui = NULL; - - // Notifications - furi_record_close(RECORD_NOTIFICATION); - nfc_magic->notifications = NULL; - - free(nfc_magic); -} - -static const NotificationSequence nfc_magic_sequence_blink_start_cyan = { - &message_blink_start_10, - &message_blink_set_color_cyan, - &message_do_not_reset, - NULL, -}; - -static const NotificationSequence nfc_magic_sequence_blink_stop = { - &message_blink_stop, - NULL, -}; - -void nfc_magic_blink_start(NfcMagic* nfc_magic) { - notification_message(nfc_magic->notifications, &nfc_magic_sequence_blink_start_cyan); -} - -void nfc_magic_blink_stop(NfcMagic* nfc_magic) { - notification_message(nfc_magic->notifications, &nfc_magic_sequence_blink_stop); -} - -int32_t nfc_magic_app(void* p) { - UNUSED(p); - NfcMagic* nfc_magic = nfc_magic_alloc(); - - scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneStart); - - view_dispatcher_run(nfc_magic->view_dispatcher); - - magic_deactivate(); - nfc_magic_free(nfc_magic); - - return 0; -} diff --git a/applications/external/nfc_magic/nfc_magic.h b/applications/external/nfc_magic/nfc_magic.h deleted file mode 100644 index f9cf395d826..00000000000 --- a/applications/external/nfc_magic/nfc_magic.h +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once - -typedef struct NfcMagicDevice NfcMagicDevice; - -typedef struct NfcMagic NfcMagic; diff --git a/applications/external/nfc_magic/nfc_magic_i.h b/applications/external/nfc_magic/nfc_magic_i.h deleted file mode 100644 index 88bc5706fa8..00000000000 --- a/applications/external/nfc_magic/nfc_magic_i.h +++ /dev/null @@ -1,94 +0,0 @@ -#pragma once - -#include "nfc_magic.h" -#include "nfc_magic_worker.h" - -#include "lib/magic/common.h" -#include "lib/magic/types.h" -#include "lib/magic/classic_gen1.h" -#include "lib/magic/gen4.h" - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include - -#include "scenes/nfc_magic_scene.h" - -#include -#include - -#include -#include "nfc_magic_icons.h" - -#define NFC_APP_FOLDER ANY_PATH("nfc") - -enum NfcMagicCustomEvent { - // Reserve first 100 events for button types and indexes, starting from 0 - NfcMagicCustomEventReserved = 100, - - NfcMagicCustomEventViewExit, - NfcMagicCustomEventWorkerExit, - NfcMagicCustomEventByteInputDone, - NfcMagicCustomEventTextInputDone, -}; - -struct NfcMagicDevice { - MagicType type; - uint32_t cuid; - uint8_t uid_len; - uint32_t password; -}; - -struct NfcMagic { - NfcMagicWorker* worker; - ViewDispatcher* view_dispatcher; - Gui* gui; - NotificationApp* notifications; - SceneManager* scene_manager; - struct NfcMagicDevice* dev; - NfcDevice* source_dev; - - uint32_t new_password; - - FuriString* text_box_store; - - // Common Views - Submenu* submenu; - Popup* popup; - Loading* loading; - TextInput* text_input; - ByteInput* byte_input; - Widget* widget; -}; - -typedef enum { - NfcMagicViewMenu, - NfcMagicViewPopup, - NfcMagicViewLoading, - NfcMagicViewTextInput, - NfcMagicViewByteInput, - NfcMagicViewWidget, -} NfcMagicView; - -NfcMagic* nfc_magic_alloc(); - -void nfc_magic_text_store_set(NfcMagic* nfc_magic, const char* text, ...); - -void nfc_magic_text_store_clear(NfcMagic* nfc_magic); - -void nfc_magic_blink_start(NfcMagic* nfc_magic); - -void nfc_magic_blink_stop(NfcMagic* nfc_magic); - -void nfc_magic_show_loading_popup(void* context, bool show); diff --git a/applications/external/nfc_magic/nfc_magic_worker.c b/applications/external/nfc_magic/nfc_magic_worker.c deleted file mode 100644 index eb715fe0dda..00000000000 --- a/applications/external/nfc_magic/nfc_magic_worker.c +++ /dev/null @@ -1,480 +0,0 @@ -#include "nfc_magic_worker_i.h" - -#include "nfc_magic_i.h" -#include "lib/magic/common.h" -#include "lib/magic/classic_gen1.h" -#include "lib/magic/gen4.h" - -#define TAG "NfcMagicWorker" - -static void - nfc_magic_worker_change_state(NfcMagicWorker* nfc_magic_worker, NfcMagicWorkerState state) { - furi_assert(nfc_magic_worker); - - nfc_magic_worker->state = state; -} - -NfcMagicWorker* nfc_magic_worker_alloc() { - NfcMagicWorker* nfc_magic_worker = malloc(sizeof(NfcMagicWorker)); - - // Worker thread attributes - nfc_magic_worker->thread = - furi_thread_alloc_ex("NfcMagicWorker", 8192, nfc_magic_worker_task, nfc_magic_worker); - - nfc_magic_worker->callback = NULL; - nfc_magic_worker->context = NULL; - - nfc_magic_worker_change_state(nfc_magic_worker, NfcMagicWorkerStateReady); - - return nfc_magic_worker; -} - -void nfc_magic_worker_free(NfcMagicWorker* nfc_magic_worker) { - furi_assert(nfc_magic_worker); - - furi_thread_free(nfc_magic_worker->thread); - free(nfc_magic_worker); -} - -void nfc_magic_worker_stop(NfcMagicWorker* nfc_magic_worker) { - furi_assert(nfc_magic_worker); - - nfc_magic_worker_change_state(nfc_magic_worker, NfcMagicWorkerStateStop); - furi_thread_join(nfc_magic_worker->thread); -} - -void nfc_magic_worker_start( - NfcMagicWorker* nfc_magic_worker, - NfcMagicWorkerState state, - NfcMagicDevice* magic_dev, - NfcDeviceData* dev_data, - uint32_t new_password, - NfcMagicWorkerCallback callback, - void* context) { - furi_assert(nfc_magic_worker); - furi_assert(magic_dev); - furi_assert(dev_data); - - nfc_magic_worker->callback = callback; - nfc_magic_worker->context = context; - nfc_magic_worker->magic_dev = magic_dev; - nfc_magic_worker->dev_data = dev_data; - nfc_magic_worker->new_password = new_password; - nfc_magic_worker_change_state(nfc_magic_worker, state); - furi_thread_start(nfc_magic_worker->thread); -} - -int32_t nfc_magic_worker_task(void* context) { - NfcMagicWorker* nfc_magic_worker = context; - - if(nfc_magic_worker->state == NfcMagicWorkerStateCheck) { - nfc_magic_worker_check(nfc_magic_worker); - } else if(nfc_magic_worker->state == NfcMagicWorkerStateWrite) { - nfc_magic_worker_write(nfc_magic_worker); - } else if(nfc_magic_worker->state == NfcMagicWorkerStateRekey) { - nfc_magic_worker_rekey(nfc_magic_worker); - } else if(nfc_magic_worker->state == NfcMagicWorkerStateWipe) { - nfc_magic_worker_wipe(nfc_magic_worker); - } - - nfc_magic_worker_change_state(nfc_magic_worker, NfcMagicWorkerStateReady); - - return 0; -} - -void nfc_magic_worker_write(NfcMagicWorker* nfc_magic_worker) { - bool card_found_notified = false; - bool done = false; - FuriHalNfcDevData nfc_data = {}; - NfcMagicDevice* magic_dev = nfc_magic_worker->magic_dev; - NfcDeviceData* dev_data = nfc_magic_worker->dev_data; - NfcProtocol dev_protocol = dev_data->protocol; - - while(nfc_magic_worker->state == NfcMagicWorkerStateWrite) { - do { - if(magic_dev->type == MagicTypeClassicGen1) { - if(furi_hal_nfc_detect(&nfc_data, 200)) { - magic_deactivate(); - magic_activate(); - if(!magic_gen1_wupa()) { - FURI_LOG_E(TAG, "No card response to WUPA (not a magic card)"); - nfc_magic_worker->callback( - NfcMagicWorkerEventWrongCard, nfc_magic_worker->context); - done = true; - break; - } - magic_deactivate(); - } - magic_activate(); - if(magic_gen1_wupa()) { - magic_gen1_data_access_cmd(); - - MfClassicData* mfc_data = &dev_data->mf_classic_data; - for(size_t i = 0; i < 64; i++) { - FURI_LOG_D(TAG, "Writing block %d", i); - if(!magic_gen1_write_blk(i, &mfc_data->block[i])) { - FURI_LOG_E(TAG, "Failed to write %d block", i); - done = true; - nfc_magic_worker->callback( - NfcMagicWorkerEventFail, nfc_magic_worker->context); - break; - } - } - - done = true; - nfc_magic_worker->callback( - NfcMagicWorkerEventSuccess, nfc_magic_worker->context); - break; - } - } else if(magic_dev->type == MagicTypeGen4) { - if(furi_hal_nfc_detect(&nfc_data, 200)) { - uint8_t gen4_config[28]; - uint32_t password = magic_dev->password; - - uint32_t cuid; - if(dev_protocol == NfcDeviceProtocolMifareClassic) { - gen4_config[0] = 0x00; - gen4_config[27] = 0x00; - } else if(dev_protocol == NfcDeviceProtocolMifareUl) { - MfUltralightData* mf_ul_data = &dev_data->mf_ul_data; - gen4_config[0] = 0x01; - switch(mf_ul_data->type) { - case MfUltralightTypeUL11: - case MfUltralightTypeUL21: - // UL-C? - // UL? - default: - gen4_config[27] = MagicGen4UltralightModeUL_EV1; - break; - case MfUltralightTypeNTAG203: - case MfUltralightTypeNTAG213: - case MfUltralightTypeNTAG215: - case MfUltralightTypeNTAG216: - case MfUltralightTypeNTAGI2C1K: - case MfUltralightTypeNTAGI2C2K: - case MfUltralightTypeNTAGI2CPlus1K: - case MfUltralightTypeNTAGI2CPlus2K: - gen4_config[27] = MagicGen4UltralightModeNTAG; - break; - } - } - - if(dev_data->nfc_data.uid_len == 4) { - gen4_config[1] = MagicGen4UIDLengthSingle; - } else if(dev_data->nfc_data.uid_len == 7) { - gen4_config[1] = MagicGen4UIDLengthDouble; - } else { - FURI_LOG_E(TAG, "Unexpected UID length %d", dev_data->nfc_data.uid_len); - nfc_magic_worker->callback( - NfcMagicWorkerEventFail, nfc_magic_worker->context); - done = true; - break; - } - - gen4_config[2] = (uint8_t)(password >> 24); - gen4_config[3] = (uint8_t)(password >> 16); - gen4_config[4] = (uint8_t)(password >> 8); - gen4_config[5] = (uint8_t)password; - - if(dev_protocol == NfcDeviceProtocolMifareUl) { - gen4_config[6] = MagicGen4ShadowModeHighSpeedIgnore; - } else { - gen4_config[6] = MagicGen4ShadowModeIgnore; - } - gen4_config[7] = 0x00; - memset(gen4_config + 8, 0, 16); - gen4_config[24] = dev_data->nfc_data.atqa[0]; - gen4_config[25] = dev_data->nfc_data.atqa[1]; - gen4_config[26] = dev_data->nfc_data.sak; - - furi_hal_nfc_sleep(); - furi_hal_nfc_activate_nfca(200, &cuid); - if(!magic_gen4_set_cfg(password, gen4_config, sizeof(gen4_config), false)) { - nfc_magic_worker->callback( - NfcMagicWorkerEventFail, nfc_magic_worker->context); - done = true; - break; - } - if(dev_protocol == NfcDeviceProtocolMifareClassic) { - MfClassicData* mfc_data = &dev_data->mf_classic_data; - size_t block_count = 64; - if(mfc_data->type == MfClassicType4k) block_count = 256; - for(size_t i = 0; i < block_count; i++) { - FURI_LOG_D(TAG, "Writing block %d", i); - if(!magic_gen4_write_blk(password, i, mfc_data->block[i].value)) { - FURI_LOG_E(TAG, "Failed to write %d block", i); - nfc_magic_worker->callback( - NfcMagicWorkerEventFail, nfc_magic_worker->context); - done = true; - break; - } - } - } else if(dev_protocol == NfcDeviceProtocolMifareUl) { - MfUltralightData* mf_ul_data = &dev_data->mf_ul_data; - for(size_t i = 0; (i * 4) < mf_ul_data->data_read; i++) { - size_t data_offset = i * 4; - FURI_LOG_D( - TAG, - "Writing page %zu (%zu/%u)", - i, - data_offset, - mf_ul_data->data_read); - uint8_t* block = mf_ul_data->data + data_offset; - if(!magic_gen4_write_blk(password, i, block)) { - FURI_LOG_E(TAG, "Failed to write %zu page", i); - nfc_magic_worker->callback( - NfcMagicWorkerEventFail, nfc_magic_worker->context); - done = true; - break; - } - } - - uint8_t buffer[16] = {0}; - - for(size_t i = 0; i < 8; i++) { - memcpy(buffer, &mf_ul_data->signature[i * 4], 4); //-V1086 - if(!magic_gen4_write_blk(password, 0xF2 + i, buffer)) { - FURI_LOG_E(TAG, "Failed to write signature block %d", i); - nfc_magic_worker->callback( - NfcMagicWorkerEventFail, nfc_magic_worker->context); - done = true; - break; - } - } - - buffer[0] = mf_ul_data->version.header; - buffer[1] = mf_ul_data->version.vendor_id; - buffer[2] = mf_ul_data->version.prod_type; - buffer[3] = mf_ul_data->version.prod_subtype; - if(!magic_gen4_write_blk(password, 0xFA, buffer)) { - FURI_LOG_E(TAG, "Failed to write version block 0"); - nfc_magic_worker->callback( - NfcMagicWorkerEventFail, nfc_magic_worker->context); - done = true; - break; - } - - buffer[0] = mf_ul_data->version.prod_ver_major; - buffer[1] = mf_ul_data->version.prod_ver_minor; - buffer[2] = mf_ul_data->version.storage_size; - buffer[3] = mf_ul_data->version.protocol_type; - if(!magic_gen4_write_blk(password, 0xFB, buffer)) { - FURI_LOG_E(TAG, "Failed to write version block 1"); - nfc_magic_worker->callback( - NfcMagicWorkerEventFail, nfc_magic_worker->context); - done = true; - break; - } - } - - nfc_magic_worker->callback( - NfcMagicWorkerEventSuccess, nfc_magic_worker->context); - done = true; - break; - } - } - } while(false); - - if(done) break; - - if(card_found_notified) { - nfc_magic_worker->callback( - NfcMagicWorkerEventNoCardDetected, nfc_magic_worker->context); - card_found_notified = false; - } - - furi_delay_ms(300); - } - magic_deactivate(); -} - -void nfc_magic_worker_check(NfcMagicWorker* nfc_magic_worker) { - FuriHalNfcDevData nfc_data = {}; - NfcMagicDevice* magic_dev = nfc_magic_worker->magic_dev; - bool card_found_notified = false; - uint8_t gen4_config[MAGIC_GEN4_CONFIG_LEN]; - - while(nfc_magic_worker->state == NfcMagicWorkerStateCheck) { - magic_activate(); - if(magic_gen1_wupa()) { - magic_dev->type = MagicTypeClassicGen1; - if(!card_found_notified) { - nfc_magic_worker->callback( - NfcMagicWorkerEventCardDetected, nfc_magic_worker->context); - card_found_notified = true; - } - - if(furi_hal_nfc_detect(&nfc_data, 200)) { - magic_dev->cuid = nfc_data.cuid; - magic_dev->uid_len = nfc_data.uid_len; - } else { - // wrong BCC - magic_dev->uid_len = 4; - } - nfc_magic_worker->callback(NfcMagicWorkerEventSuccess, nfc_magic_worker->context); - break; - } else { - magic_deactivate(); - magic_activate(); - if(furi_hal_nfc_detect(&nfc_data, 200)) { - magic_dev->cuid = nfc_data.cuid; - magic_dev->uid_len = nfc_data.uid_len; - if(magic_gen4_get_cfg(magic_dev->password, gen4_config)) { - magic_dev->type = MagicTypeGen4; - if(!card_found_notified) { - nfc_magic_worker->callback( - NfcMagicWorkerEventCardDetected, nfc_magic_worker->context); - card_found_notified = true; - } - - nfc_magic_worker->callback( - NfcMagicWorkerEventSuccess, nfc_magic_worker->context); - } else { - nfc_magic_worker->callback( - NfcMagicWorkerEventWrongCard, nfc_magic_worker->context); - card_found_notified = true; - } - break; - } else { - if(card_found_notified) { - nfc_magic_worker->callback( - NfcMagicWorkerEventNoCardDetected, nfc_magic_worker->context); - card_found_notified = false; - } - } - } - - magic_deactivate(); - furi_delay_ms(300); - } - - magic_deactivate(); -} - -void nfc_magic_worker_rekey(NfcMagicWorker* nfc_magic_worker) { - NfcMagicDevice* magic_dev = nfc_magic_worker->magic_dev; - bool card_found_notified = false; - - if(magic_dev->type != MagicTypeGen4) { - nfc_magic_worker->callback(NfcMagicWorkerEventCardDetected, nfc_magic_worker->context); - return; - } - - while(nfc_magic_worker->state == NfcMagicWorkerStateRekey) { - magic_activate(); - uint32_t cuid; - furi_hal_nfc_activate_nfca(200, &cuid); - if(cuid != magic_dev->cuid) { - if(card_found_notified) { - nfc_magic_worker->callback( - NfcMagicWorkerEventNoCardDetected, nfc_magic_worker->context); - card_found_notified = false; - } - continue; - } - - nfc_magic_worker->callback(NfcMagicWorkerEventCardDetected, nfc_magic_worker->context); - card_found_notified = true; - - if(magic_gen4_set_pwd(magic_dev->password, nfc_magic_worker->new_password)) { - magic_dev->password = nfc_magic_worker->new_password; - nfc_magic_worker->callback(NfcMagicWorkerEventSuccess, nfc_magic_worker->context); - break; - } - - if(card_found_notified) { //-V547 - nfc_magic_worker->callback( - NfcMagicWorkerEventNoCardDetected, nfc_magic_worker->context); - card_found_notified = false; - } - furi_delay_ms(300); - } - magic_deactivate(); -} - -void nfc_magic_worker_wipe(NfcMagicWorker* nfc_magic_worker) { - NfcMagicDevice* magic_dev = nfc_magic_worker->magic_dev; - bool card_found_notified = false; - bool card_wiped = false; - - MfClassicBlock block; - memset(&block, 0, sizeof(MfClassicBlock)); - MfClassicBlock empty_block; - memset(&empty_block, 0, sizeof(MfClassicBlock)); - MfClassicBlock trailer_block; - memset(&trailer_block, 0xff, sizeof(MfClassicBlock)); - - block.value[0] = 0x01; - block.value[1] = 0x02; - block.value[2] = 0x03; - block.value[3] = 0x04; - block.value[4] = 0x04; - block.value[5] = 0x08; - block.value[6] = 0x04; - - trailer_block.value[7] = 0x07; - trailer_block.value[8] = 0x80; - trailer_block.value[9] = 0x69; - - while(nfc_magic_worker->state == NfcMagicWorkerStateWipe) { - do { - magic_deactivate(); - furi_delay_ms(300); - if(!magic_activate()) break; - if(magic_dev->type == MagicTypeClassicGen1) { - if(!magic_gen1_wupa()) break; - if(!card_found_notified) { - nfc_magic_worker->callback( - NfcMagicWorkerEventCardDetected, nfc_magic_worker->context); - card_found_notified = true; - } - - if(!magic_gen1_data_access_cmd()) break; - if(!magic_gen1_write_blk(0, &block)) break; - - for(size_t i = 1; i < 64; i++) { - FURI_LOG_D(TAG, "Wiping block %d", i); - bool success = false; - if((i | 0x03) == i) { - success = magic_gen1_write_blk(i, &trailer_block); - } else { - success = magic_gen1_write_blk(i, &empty_block); - } - - if(!success) { - FURI_LOG_E(TAG, "Failed to write %d block", i); - nfc_magic_worker->callback( - NfcMagicWorkerEventFail, nfc_magic_worker->context); - break; - } - } - - card_wiped = true; - nfc_magic_worker->callback(NfcMagicWorkerEventSuccess, nfc_magic_worker->context); - } else if(magic_dev->type == MagicTypeGen4) { - uint32_t cuid; - if(!furi_hal_nfc_activate_nfca(200, &cuid)) break; - if(cuid != magic_dev->cuid) break; - if(!card_found_notified) { - nfc_magic_worker->callback( - NfcMagicWorkerEventCardDetected, nfc_magic_worker->context); - card_found_notified = true; - } - - if(!magic_gen4_wipe(magic_dev->password)) break; - - card_wiped = true; - nfc_magic_worker->callback(NfcMagicWorkerEventSuccess, nfc_magic_worker->context); - } - } while(false); - - if(card_wiped) break; - - if(card_found_notified) { - nfc_magic_worker->callback( - NfcMagicWorkerEventNoCardDetected, nfc_magic_worker->context); - card_found_notified = false; - } - } - magic_deactivate(); -} diff --git a/applications/external/nfc_magic/nfc_magic_worker.h b/applications/external/nfc_magic/nfc_magic_worker.h deleted file mode 100644 index 51ff4ee438d..00000000000 --- a/applications/external/nfc_magic/nfc_magic_worker.h +++ /dev/null @@ -1,42 +0,0 @@ -#pragma once - -#include -#include "nfc_magic.h" - -typedef struct NfcMagicWorker NfcMagicWorker; - -typedef enum { - NfcMagicWorkerStateReady, - - NfcMagicWorkerStateCheck, - NfcMagicWorkerStateWrite, - NfcMagicWorkerStateRekey, - NfcMagicWorkerStateWipe, - - NfcMagicWorkerStateStop, -} NfcMagicWorkerState; - -typedef enum { - NfcMagicWorkerEventSuccess, - NfcMagicWorkerEventFail, - NfcMagicWorkerEventCardDetected, - NfcMagicWorkerEventNoCardDetected, - NfcMagicWorkerEventWrongCard, -} NfcMagicWorkerEvent; - -typedef bool (*NfcMagicWorkerCallback)(NfcMagicWorkerEvent event, void* context); - -NfcMagicWorker* nfc_magic_worker_alloc(); - -void nfc_magic_worker_free(NfcMagicWorker* nfc_magic_worker); - -void nfc_magic_worker_stop(NfcMagicWorker* nfc_magic_worker); - -void nfc_magic_worker_start( - NfcMagicWorker* nfc_magic_worker, - NfcMagicWorkerState state, - NfcMagicDevice* magic_dev, - NfcDeviceData* dev_data, - uint32_t new_password, - NfcMagicWorkerCallback callback, - void* context); diff --git a/applications/external/nfc_magic/nfc_magic_worker_i.h b/applications/external/nfc_magic/nfc_magic_worker_i.h deleted file mode 100644 index a354f804763..00000000000 --- a/applications/external/nfc_magic/nfc_magic_worker_i.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -#include - -#include "nfc_magic_worker.h" -#include "lib/magic/common.h" - -struct NfcMagicWorker { - FuriThread* thread; - - NfcMagicDevice* magic_dev; - NfcDeviceData* dev_data; - uint32_t new_password; - - NfcMagicWorkerCallback callback; - void* context; - - NfcMagicWorkerState state; -}; - -int32_t nfc_magic_worker_task(void* context); - -void nfc_magic_worker_check(NfcMagicWorker* nfc_magic_worker); - -void nfc_magic_worker_write(NfcMagicWorker* nfc_magic_worker); - -void nfc_magic_worker_rekey(NfcMagicWorker* nfc_magic_worker); - -void nfc_magic_worker_wipe(NfcMagicWorker* nfc_magic_worker); diff --git a/applications/external/nfc_magic/scenes/nfc_magic_scene.c b/applications/external/nfc_magic/scenes/nfc_magic_scene.c deleted file mode 100644 index 520ef2a9dea..00000000000 --- a/applications/external/nfc_magic/scenes/nfc_magic_scene.c +++ /dev/null @@ -1,30 +0,0 @@ -#include "nfc_magic_scene.h" - -// Generate scene on_enter handlers array -#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, -void (*const nfc_magic_on_enter_handlers[])(void*) = { -#include "nfc_magic_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 nfc_magic_on_event_handlers[])(void* context, SceneManagerEvent event) = { -#include "nfc_magic_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 nfc_magic_on_exit_handlers[])(void* context) = { -#include "nfc_magic_scene_config.h" -}; -#undef ADD_SCENE - -// Initialize scene handlers configuration structure -const SceneManagerHandlers nfc_magic_scene_handlers = { - .on_enter_handlers = nfc_magic_on_enter_handlers, - .on_event_handlers = nfc_magic_on_event_handlers, - .on_exit_handlers = nfc_magic_on_exit_handlers, - .scene_num = NfcMagicSceneNum, -}; diff --git a/applications/external/nfc_magic/scenes/nfc_magic_scene.h b/applications/external/nfc_magic/scenes/nfc_magic_scene.h deleted file mode 100644 index f1e9f715dd4..00000000000 --- a/applications/external/nfc_magic/scenes/nfc_magic_scene.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -#include - -// Generate scene id and total number -#define ADD_SCENE(prefix, name, id) NfcMagicScene##id, -typedef enum { -#include "nfc_magic_scene_config.h" - NfcMagicSceneNum, -} NfcMagicScene; -#undef ADD_SCENE - -extern const SceneManagerHandlers nfc_magic_scene_handlers; - -// Generate scene on_enter handlers declaration -#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); -#include "nfc_magic_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 "nfc_magic_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 "nfc_magic_scene_config.h" -#undef ADD_SCENE diff --git a/applications/external/nfc_magic/scenes/nfc_magic_scene_actions.c b/applications/external/nfc_magic/scenes/nfc_magic_scene_actions.c deleted file mode 100644 index 675262a9b2f..00000000000 --- a/applications/external/nfc_magic/scenes/nfc_magic_scene_actions.c +++ /dev/null @@ -1,50 +0,0 @@ -#include "../nfc_magic_i.h" -enum SubmenuIndex { - SubmenuIndexWrite, - SubmenuIndexWipe, -}; - -void nfc_magic_scene_actions_submenu_callback(void* context, uint32_t index) { - NfcMagic* nfc_magic = context; - view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, index); -} - -void nfc_magic_scene_actions_on_enter(void* context) { - NfcMagic* nfc_magic = context; - - Submenu* submenu = nfc_magic->submenu; - submenu_add_item( - submenu, "Write", SubmenuIndexWrite, nfc_magic_scene_actions_submenu_callback, nfc_magic); - submenu_add_item( - submenu, "Wipe", SubmenuIndexWipe, nfc_magic_scene_actions_submenu_callback, nfc_magic); - - submenu_set_selected_item( - submenu, scene_manager_get_scene_state(nfc_magic->scene_manager, NfcMagicSceneActions)); - view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewMenu); -} - -bool nfc_magic_scene_actions_on_event(void* context, SceneManagerEvent event) { - NfcMagic* nfc_magic = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SubmenuIndexWrite) { - scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneFileSelect); - consumed = true; - } else if(event.event == SubmenuIndexWipe) { - scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneWipe); - consumed = true; - } - scene_manager_set_scene_state(nfc_magic->scene_manager, NfcMagicSceneActions, event.event); - } else if(event.type == SceneManagerEventTypeBack) { - consumed = scene_manager_search_and_switch_to_previous_scene( - nfc_magic->scene_manager, NfcMagicSceneStart); - } - - return consumed; -} - -void nfc_magic_scene_actions_on_exit(void* context) { - NfcMagic* nfc_magic = context; - submenu_reset(nfc_magic->submenu); -} diff --git a/applications/external/nfc_magic/scenes/nfc_magic_scene_check.c b/applications/external/nfc_magic/scenes/nfc_magic_scene_check.c deleted file mode 100644 index 90b43d7d3a2..00000000000 --- a/applications/external/nfc_magic/scenes/nfc_magic_scene_check.c +++ /dev/null @@ -1,89 +0,0 @@ -#include "../nfc_magic_i.h" - -enum { - NfcMagicSceneCheckStateCardSearch, - NfcMagicSceneCheckStateCardFound, -}; - -bool nfc_magic_check_worker_callback(NfcMagicWorkerEvent event, void* context) { - furi_assert(context); - - NfcMagic* nfc_magic = context; - view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, event); - - return true; -} - -static void nfc_magic_scene_check_setup_view(NfcMagic* nfc_magic) { - Popup* popup = nfc_magic->popup; - popup_reset(popup); - uint32_t state = scene_manager_get_scene_state(nfc_magic->scene_manager, NfcMagicSceneCheck); - - if(state == NfcMagicSceneCheckStateCardSearch) { - popup_set_icon(nfc_magic->popup, 0, 8, &I_NFC_manual_60x50); - popup_set_text( - nfc_magic->popup, "Apply card to\nthe back", 128, 32, AlignRight, AlignCenter); - } else { - popup_set_icon(popup, 12, 23, &I_Loading_24); - popup_set_header(popup, "Checking\nDon't move...", 52, 32, AlignLeft, AlignCenter); - } - - view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewPopup); -} - -void nfc_magic_scene_check_on_enter(void* context) { - NfcMagic* nfc_magic = context; - - scene_manager_set_scene_state( - nfc_magic->scene_manager, NfcMagicSceneCheck, NfcMagicSceneCheckStateCardSearch); - nfc_magic_scene_check_setup_view(nfc_magic); - - // Setup and start worker - nfc_magic_worker_start( - nfc_magic->worker, - NfcMagicWorkerStateCheck, - nfc_magic->dev, - &nfc_magic->source_dev->dev_data, - nfc_magic->new_password, - nfc_magic_check_worker_callback, - nfc_magic); - nfc_magic_blink_start(nfc_magic); -} - -bool nfc_magic_scene_check_on_event(void* context, SceneManagerEvent event) { - NfcMagic* nfc_magic = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == NfcMagicWorkerEventSuccess) { - scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneMagicInfo); - consumed = true; - } else if(event.event == NfcMagicWorkerEventWrongCard) { - scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneNotMagic); - consumed = true; - } else if(event.event == NfcMagicWorkerEventCardDetected) { - scene_manager_set_scene_state( - nfc_magic->scene_manager, NfcMagicSceneCheck, NfcMagicSceneCheckStateCardFound); - nfc_magic_scene_check_setup_view(nfc_magic); - consumed = true; - } else if(event.event == NfcMagicWorkerEventNoCardDetected) { - scene_manager_set_scene_state( - nfc_magic->scene_manager, NfcMagicSceneCheck, NfcMagicSceneCheckStateCardSearch); - nfc_magic_scene_check_setup_view(nfc_magic); - consumed = true; - } - } - return consumed; -} - -void nfc_magic_scene_check_on_exit(void* context) { - NfcMagic* nfc_magic = context; - - nfc_magic_worker_stop(nfc_magic->worker); - scene_manager_set_scene_state( - nfc_magic->scene_manager, NfcMagicSceneCheck, NfcMagicSceneCheckStateCardSearch); - // Clear view - popup_reset(nfc_magic->popup); - - nfc_magic_blink_stop(nfc_magic); -} diff --git a/applications/external/nfc_magic/scenes/nfc_magic_scene_config.h b/applications/external/nfc_magic/scenes/nfc_magic_scene_config.h deleted file mode 100644 index 2f9860d96f2..00000000000 --- a/applications/external/nfc_magic/scenes/nfc_magic_scene_config.h +++ /dev/null @@ -1,18 +0,0 @@ -ADD_SCENE(nfc_magic, start, Start) -ADD_SCENE(nfc_magic, key_input, KeyInput) -ADD_SCENE(nfc_magic, actions, Actions) -ADD_SCENE(nfc_magic, gen4_actions, Gen4Actions) -ADD_SCENE(nfc_magic, new_key_input, NewKeyInput) -ADD_SCENE(nfc_magic, file_select, FileSelect) -ADD_SCENE(nfc_magic, write_confirm, WriteConfirm) -ADD_SCENE(nfc_magic, wrong_card, WrongCard) -ADD_SCENE(nfc_magic, write, Write) -ADD_SCENE(nfc_magic, write_fail, WriteFail) -ADD_SCENE(nfc_magic, success, Success) -ADD_SCENE(nfc_magic, check, Check) -ADD_SCENE(nfc_magic, not_magic, NotMagic) -ADD_SCENE(nfc_magic, magic_info, MagicInfo) -ADD_SCENE(nfc_magic, rekey, Rekey) -ADD_SCENE(nfc_magic, rekey_fail, RekeyFail) -ADD_SCENE(nfc_magic, wipe, Wipe) -ADD_SCENE(nfc_magic, wipe_fail, WipeFail) diff --git a/applications/external/nfc_magic/scenes/nfc_magic_scene_file_select.c b/applications/external/nfc_magic/scenes/nfc_magic_scene_file_select.c deleted file mode 100644 index 04b7024ffbd..00000000000 --- a/applications/external/nfc_magic/scenes/nfc_magic_scene_file_select.c +++ /dev/null @@ -1,76 +0,0 @@ -#include "../nfc_magic_i.h" - -static bool nfc_magic_scene_file_select_is_file_suitable(NfcMagic* nfc_magic) { - NfcDevice* nfc_dev = nfc_magic->source_dev; - if(nfc_dev->format == NfcDeviceSaveFormatMifareClassic) { - switch(nfc_magic->dev->type) { - case MagicTypeClassicGen1: - case MagicTypeClassicDirectWrite: - case MagicTypeClassicAPDU: - if((nfc_dev->dev_data.mf_classic_data.type != MfClassicType1k) || - (nfc_dev->dev_data.nfc_data.uid_len != nfc_magic->dev->uid_len)) { - return false; - } - return true; - - case MagicTypeGen4: - return true; - default: - return false; - } - } else if( - (nfc_dev->format == NfcDeviceSaveFormatMifareUl) && - (nfc_dev->dev_data.nfc_data.uid_len == 7)) { - switch(nfc_magic->dev->type) { - case MagicTypeUltralightGen1: - case MagicTypeUltralightDirectWrite: - case MagicTypeUltralightC_Gen1: - case MagicTypeUltralightC_DirectWrite: - case MagicTypeGen4: - switch(nfc_dev->dev_data.mf_ul_data.type) { - case MfUltralightTypeNTAGI2C1K: - case MfUltralightTypeNTAGI2C2K: - case MfUltralightTypeNTAGI2CPlus1K: - case MfUltralightTypeNTAGI2CPlus2K: - return false; - default: - return true; - } - default: - return false; - } - } - - return false; -} - -void nfc_magic_scene_file_select_on_enter(void* context) { - NfcMagic* nfc_magic = context; - // Process file_select return - nfc_device_set_loading_callback( - nfc_magic->source_dev, nfc_magic_show_loading_popup, nfc_magic); - - if(!furi_string_size(nfc_magic->source_dev->load_path)) { - furi_string_set_str(nfc_magic->source_dev->load_path, NFC_APP_FOLDER); - } - if(nfc_file_select(nfc_magic->source_dev)) { - if(nfc_magic_scene_file_select_is_file_suitable(nfc_magic)) { - scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneWriteConfirm); - } else { - scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneWrongCard); - } - } else { - scene_manager_previous_scene(nfc_magic->scene_manager); - } -} - -bool nfc_magic_scene_file_select_on_event(void* context, SceneManagerEvent event) { - UNUSED(context); - UNUSED(event); - return false; -} - -void nfc_magic_scene_file_select_on_exit(void* context) { - NfcMagic* nfc_magic = context; - nfc_device_set_loading_callback(nfc_magic->source_dev, NULL, nfc_magic); -} diff --git a/applications/external/nfc_magic/scenes/nfc_magic_scene_gen4_actions.c b/applications/external/nfc_magic/scenes/nfc_magic_scene_gen4_actions.c deleted file mode 100644 index ceaa33e29f0..00000000000 --- a/applications/external/nfc_magic/scenes/nfc_magic_scene_gen4_actions.c +++ /dev/null @@ -1,70 +0,0 @@ -#include "../nfc_magic_i.h" -enum SubmenuIndex { - SubmenuIndexWrite, - SubmenuIndexChangePassword, - SubmenuIndexWipe, -}; - -void nfc_magic_scene_gen4_actions_submenu_callback(void* context, uint32_t index) { - NfcMagic* nfc_magic = context; - view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, index); -} - -void nfc_magic_scene_gen4_actions_on_enter(void* context) { - NfcMagic* nfc_magic = context; - - Submenu* submenu = nfc_magic->submenu; - submenu_add_item( - submenu, - "Write", - SubmenuIndexWrite, - nfc_magic_scene_gen4_actions_submenu_callback, - nfc_magic); - submenu_add_item( - submenu, - "Change password", - SubmenuIndexChangePassword, - nfc_magic_scene_gen4_actions_submenu_callback, - nfc_magic); - submenu_add_item( - submenu, - "Wipe", - SubmenuIndexWipe, - nfc_magic_scene_gen4_actions_submenu_callback, - nfc_magic); - - submenu_set_selected_item( - submenu, - scene_manager_get_scene_state(nfc_magic->scene_manager, NfcMagicSceneGen4Actions)); - view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewMenu); -} - -bool nfc_magic_scene_gen4_actions_on_event(void* context, SceneManagerEvent event) { - NfcMagic* nfc_magic = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SubmenuIndexWrite) { - scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneFileSelect); - consumed = true; - } else if(event.event == SubmenuIndexChangePassword) { - scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneNewKeyInput); - consumed = true; - } else if(event.event == SubmenuIndexWipe) { - scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneWipe); - consumed = true; - } - scene_manager_set_scene_state( - nfc_magic->scene_manager, NfcMagicSceneGen4Actions, event.event); - } else if(event.type == SceneManagerEventTypeBack) { - consumed = scene_manager_search_and_switch_to_previous_scene( - nfc_magic->scene_manager, NfcMagicSceneStart); - } - - return consumed; -} - -void nfc_magic_scene_gen4_actions_on_exit(void* context) { - NfcMagic* nfc_magic = context; - submenu_reset(nfc_magic->submenu); -} diff --git a/applications/external/nfc_magic/scenes/nfc_magic_scene_key_input.c b/applications/external/nfc_magic/scenes/nfc_magic_scene_key_input.c deleted file mode 100644 index 58b487a09bb..00000000000 --- a/applications/external/nfc_magic/scenes/nfc_magic_scene_key_input.c +++ /dev/null @@ -1,45 +0,0 @@ -#include "../nfc_magic_i.h" - -void nfc_magic_scene_key_input_byte_input_callback(void* context) { - NfcMagic* nfc_magic = context; - - view_dispatcher_send_custom_event( - nfc_magic->view_dispatcher, NfcMagicCustomEventByteInputDone); -} - -void nfc_magic_scene_key_input_on_enter(void* context) { - NfcMagic* nfc_magic = context; - - // Setup view - ByteInput* byte_input = nfc_magic->byte_input; - byte_input_set_header_text(byte_input, "Enter the password in hex"); - byte_input_set_result_callback( - byte_input, - nfc_magic_scene_key_input_byte_input_callback, - NULL, - nfc_magic, - (uint8_t*)&nfc_magic->dev->password, - 4); - view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewByteInput); -} - -bool nfc_magic_scene_key_input_on_event(void* context, SceneManagerEvent event) { - NfcMagic* nfc_magic = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == NfcMagicCustomEventByteInputDone) { - scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneCheck); - consumed = true; - } - } - return consumed; -} - -void nfc_magic_scene_key_input_on_exit(void* context) { - NfcMagic* nfc_magic = context; - - // Clear view - byte_input_set_result_callback(nfc_magic->byte_input, NULL, NULL, NULL, NULL, 0); - byte_input_set_header_text(nfc_magic->byte_input, ""); -} diff --git a/applications/external/nfc_magic/scenes/nfc_magic_scene_magic_info.c b/applications/external/nfc_magic/scenes/nfc_magic_scene_magic_info.c deleted file mode 100644 index c147ac4383f..00000000000 --- a/applications/external/nfc_magic/scenes/nfc_magic_scene_magic_info.c +++ /dev/null @@ -1,59 +0,0 @@ -#include "../nfc_magic_i.h" -#include "../lib/magic/types.h" - -void nfc_magic_scene_magic_info_widget_callback( - GuiButtonType result, - InputType type, - void* context) { - NfcMagic* nfc_magic = context; - if(type == InputTypeShort) { - view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, result); - } -} - -void nfc_magic_scene_magic_info_on_enter(void* context) { - NfcMagic* nfc_magic = context; - Widget* widget = nfc_magic->widget; - const char* card_type = nfc_magic_type(nfc_magic->dev->type); - - notification_message(nfc_magic->notifications, &sequence_success); - - widget_add_icon_element(widget, 73, 17, &I_DolphinCommon_56x48); - widget_add_string_element( - widget, 3, 4, AlignLeft, AlignTop, FontPrimary, "Magic card detected"); - widget_add_string_element(widget, 3, 17, AlignLeft, AlignTop, FontSecondary, card_type); - widget_add_button_element( - widget, GuiButtonTypeLeft, "Retry", nfc_magic_scene_magic_info_widget_callback, nfc_magic); - widget_add_button_element( - widget, GuiButtonTypeRight, "More", nfc_magic_scene_magic_info_widget_callback, nfc_magic); - - // Setup and start worker - view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewWidget); -} - -bool nfc_magic_scene_magic_info_on_event(void* context, SceneManagerEvent event) { - NfcMagic* nfc_magic = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == GuiButtonTypeLeft) { - consumed = scene_manager_previous_scene(nfc_magic->scene_manager); - } else if(event.event == GuiButtonTypeRight) { - MagicType type = nfc_magic->dev->type; - if(type == MagicTypeGen4) { - scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneGen4Actions); - consumed = true; - } else { - scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneActions); - consumed = true; - } - } - } - return consumed; -} - -void nfc_magic_scene_magic_info_on_exit(void* context) { - NfcMagic* nfc_magic = context; - - widget_reset(nfc_magic->widget); -} diff --git a/applications/external/nfc_magic/scenes/nfc_magic_scene_new_key_input.c b/applications/external/nfc_magic/scenes/nfc_magic_scene_new_key_input.c deleted file mode 100644 index b5247f6c555..00000000000 --- a/applications/external/nfc_magic/scenes/nfc_magic_scene_new_key_input.c +++ /dev/null @@ -1,45 +0,0 @@ -#include "../nfc_magic_i.h" - -void nfc_magic_scene_new_key_input_byte_input_callback(void* context) { - NfcMagic* nfc_magic = context; - - view_dispatcher_send_custom_event( - nfc_magic->view_dispatcher, NfcMagicCustomEventByteInputDone); -} - -void nfc_magic_scene_new_key_input_on_enter(void* context) { - NfcMagic* nfc_magic = context; - - // Setup view - ByteInput* byte_input = nfc_magic->byte_input; - byte_input_set_header_text(byte_input, "Enter the password in hex"); - byte_input_set_result_callback( - byte_input, - nfc_magic_scene_new_key_input_byte_input_callback, - NULL, - nfc_magic, - (uint8_t*)&nfc_magic->new_password, - 4); - view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewByteInput); -} - -bool nfc_magic_scene_new_key_input_on_event(void* context, SceneManagerEvent event) { - NfcMagic* nfc_magic = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == NfcMagicCustomEventByteInputDone) { - scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneRekey); - consumed = true; - } - } - return consumed; -} - -void nfc_magic_scene_new_key_input_on_exit(void* context) { - NfcMagic* nfc_magic = context; - - // Clear view - byte_input_set_result_callback(nfc_magic->byte_input, NULL, NULL, NULL, NULL, 0); - byte_input_set_header_text(nfc_magic->byte_input, ""); -} diff --git a/applications/external/nfc_magic/scenes/nfc_magic_scene_not_magic.c b/applications/external/nfc_magic/scenes/nfc_magic_scene_not_magic.c deleted file mode 100644 index b4f579f444d..00000000000 --- a/applications/external/nfc_magic/scenes/nfc_magic_scene_not_magic.c +++ /dev/null @@ -1,43 +0,0 @@ -#include "../nfc_magic_i.h" - -void nfc_magic_scene_not_magic_widget_callback(GuiButtonType result, InputType type, void* context) { - NfcMagic* nfc_magic = context; - if(type == InputTypeShort) { - view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, result); - } -} - -void nfc_magic_scene_not_magic_on_enter(void* context) { - NfcMagic* nfc_magic = context; - Widget* widget = nfc_magic->widget; - - notification_message(nfc_magic->notifications, &sequence_error); - - widget_add_string_element( - widget, 3, 4, AlignLeft, AlignTop, FontPrimary, "This is wrong card"); - widget_add_string_multiline_element( - widget, 4, 17, AlignLeft, AlignTop, FontSecondary, "Not magic or unsupported\ncard"); - widget_add_button_element( - widget, GuiButtonTypeLeft, "Retry", nfc_magic_scene_not_magic_widget_callback, nfc_magic); - - // Setup and start worker - view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewWidget); -} - -bool nfc_magic_scene_not_magic_on_event(void* context, SceneManagerEvent event) { - NfcMagic* nfc_magic = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == GuiButtonTypeLeft) { - consumed = scene_manager_previous_scene(nfc_magic->scene_manager); - } - } - return consumed; -} - -void nfc_magic_scene_not_magic_on_exit(void* context) { - NfcMagic* nfc_magic = context; - - widget_reset(nfc_magic->widget); -} diff --git a/applications/external/nfc_magic/scenes/nfc_magic_scene_rekey.c b/applications/external/nfc_magic/scenes/nfc_magic_scene_rekey.c deleted file mode 100644 index 259dc78eaa5..00000000000 --- a/applications/external/nfc_magic/scenes/nfc_magic_scene_rekey.c +++ /dev/null @@ -1,95 +0,0 @@ -#include "../nfc_magic_i.h" - -enum { - NfcMagicSceneRekeyStateCardSearch, - NfcMagicSceneRekeyStateCardFound, -}; - -bool nfc_magic_rekey_worker_callback(NfcMagicWorkerEvent event, void* context) { - furi_assert(context); - - NfcMagic* nfc_magic = context; - view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, event); - - return true; -} - -static void nfc_magic_scene_rekey_setup_view(NfcMagic* nfc_magic) { - Popup* popup = nfc_magic->popup; - popup_reset(popup); - uint32_t state = scene_manager_get_scene_state(nfc_magic->scene_manager, NfcMagicSceneRekey); - - if(state == NfcMagicSceneRekeyStateCardSearch) { - popup_set_text( - nfc_magic->popup, - "Apply the\nsame card\nto the back", - 128, - 32, - AlignRight, - AlignCenter); - popup_set_icon(nfc_magic->popup, 0, 8, &I_NFC_manual_60x50); - } else { - popup_set_icon(popup, 12, 23, &I_Loading_24); - popup_set_header(popup, "Writing\nDon't move...", 52, 32, AlignLeft, AlignCenter); - } - - view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewPopup); -} - -void nfc_magic_scene_rekey_on_enter(void* context) { - NfcMagic* nfc_magic = context; - - scene_manager_set_scene_state( - nfc_magic->scene_manager, NfcMagicSceneRekey, NfcMagicSceneRekeyStateCardSearch); - nfc_magic_scene_rekey_setup_view(nfc_magic); - - // Setup and start worker - nfc_magic_worker_start( - nfc_magic->worker, - NfcMagicWorkerStateRekey, - nfc_magic->dev, - &nfc_magic->source_dev->dev_data, - nfc_magic->new_password, - nfc_magic_rekey_worker_callback, - nfc_magic); - nfc_magic_blink_start(nfc_magic); -} - -bool nfc_magic_scene_rekey_on_event(void* context, SceneManagerEvent event) { - NfcMagic* nfc_magic = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == NfcMagicWorkerEventSuccess) { - nfc_magic->dev->password = nfc_magic->new_password; - scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneSuccess); - consumed = true; - } else if(event.event == NfcMagicWorkerEventFail) { - scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneRekeyFail); - consumed = true; - } else if(event.event == NfcMagicWorkerEventCardDetected) { - scene_manager_set_scene_state( - nfc_magic->scene_manager, NfcMagicSceneRekey, NfcMagicSceneRekeyStateCardFound); - nfc_magic_scene_rekey_setup_view(nfc_magic); - consumed = true; - } else if(event.event == NfcMagicWorkerEventNoCardDetected) { - scene_manager_set_scene_state( - nfc_magic->scene_manager, NfcMagicSceneRekey, NfcMagicSceneRekeyStateCardSearch); - nfc_magic_scene_rekey_setup_view(nfc_magic); - consumed = true; - } - } - return consumed; -} - -void nfc_magic_scene_rekey_on_exit(void* context) { - NfcMagic* nfc_magic = context; - - nfc_magic_worker_stop(nfc_magic->worker); - scene_manager_set_scene_state( - nfc_magic->scene_manager, NfcMagicSceneRekey, NfcMagicSceneRekeyStateCardSearch); - // Clear view - popup_reset(nfc_magic->popup); - - nfc_magic_blink_stop(nfc_magic); -} diff --git a/applications/external/nfc_magic/scenes/nfc_magic_scene_rekey_fail.c b/applications/external/nfc_magic/scenes/nfc_magic_scene_rekey_fail.c deleted file mode 100644 index d30ee57bcfc..00000000000 --- a/applications/external/nfc_magic/scenes/nfc_magic_scene_rekey_fail.c +++ /dev/null @@ -1,50 +0,0 @@ -#include "../nfc_magic_i.h" - -void nfc_magic_scene_rekey_fail_widget_callback( - GuiButtonType result, - InputType type, - void* context) { - NfcMagic* nfc_magic = context; - if(type == InputTypeShort) { - view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, result); - } -} - -void nfc_magic_scene_rekey_fail_on_enter(void* context) { - NfcMagic* nfc_magic = context; - Widget* widget = nfc_magic->widget; - - notification_message(nfc_magic->notifications, &sequence_error); - - widget_add_icon_element(widget, 72, 17, &I_DolphinCommon_56x48); - widget_add_string_element( - widget, 7, 4, AlignLeft, AlignTop, FontPrimary, "Can't change password!"); - - widget_add_button_element( - widget, GuiButtonTypeLeft, "Finish", nfc_magic_scene_rekey_fail_widget_callback, nfc_magic); - - // Setup and start worker - view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewWidget); -} - -bool nfc_magic_scene_rekey_fail_on_event(void* context, SceneManagerEvent event) { - NfcMagic* nfc_magic = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == GuiButtonTypeLeft) { - consumed = scene_manager_search_and_switch_to_previous_scene( - nfc_magic->scene_manager, NfcMagicSceneStart); - } - } else if(event.type == SceneManagerEventTypeBack) { - consumed = scene_manager_search_and_switch_to_previous_scene( - nfc_magic->scene_manager, NfcMagicSceneStart); - } - return consumed; -} - -void nfc_magic_scene_rekey_fail_on_exit(void* context) { - NfcMagic* nfc_magic = context; - - widget_reset(nfc_magic->widget); -} diff --git a/applications/external/nfc_magic/scenes/nfc_magic_scene_start.c b/applications/external/nfc_magic/scenes/nfc_magic_scene_start.c deleted file mode 100644 index b5861629e43..00000000000 --- a/applications/external/nfc_magic/scenes/nfc_magic_scene_start.c +++ /dev/null @@ -1,56 +0,0 @@ -#include "../nfc_magic_i.h" -enum SubmenuIndex { - SubmenuIndexCheck, - SubmenuIndexAuthenticateGen4, -}; - -void nfc_magic_scene_start_submenu_callback(void* context, uint32_t index) { - NfcMagic* nfc_magic = context; - view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, index); -} - -void nfc_magic_scene_start_on_enter(void* context) { - NfcMagic* nfc_magic = context; - - Submenu* submenu = nfc_magic->submenu; - submenu_add_item( - submenu, - "Check Magic Tag", - SubmenuIndexCheck, - nfc_magic_scene_start_submenu_callback, - nfc_magic); - submenu_add_item( - submenu, - "Authenticate Gen4", - SubmenuIndexAuthenticateGen4, - nfc_magic_scene_start_submenu_callback, - nfc_magic); - - submenu_set_selected_item( - submenu, scene_manager_get_scene_state(nfc_magic->scene_manager, NfcMagicSceneStart)); - view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewMenu); -} - -bool nfc_magic_scene_start_on_event(void* context, SceneManagerEvent event) { - NfcMagic* nfc_magic = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SubmenuIndexCheck) { - nfc_magic->dev->password = MAGIC_GEN4_DEFAULT_PWD; - scene_manager_set_scene_state( - nfc_magic->scene_manager, NfcMagicSceneStart, SubmenuIndexCheck); - scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneCheck); - consumed = true; - } else if(event.event == SubmenuIndexAuthenticateGen4) { - scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneKeyInput); - } - } - - return consumed; -} - -void nfc_magic_scene_start_on_exit(void* context) { - NfcMagic* nfc_magic = context; - submenu_reset(nfc_magic->submenu); -} diff --git a/applications/external/nfc_magic/scenes/nfc_magic_scene_success.c b/applications/external/nfc_magic/scenes/nfc_magic_scene_success.c deleted file mode 100644 index 37441e80e8a..00000000000 --- a/applications/external/nfc_magic/scenes/nfc_magic_scene_success.c +++ /dev/null @@ -1,42 +0,0 @@ -#include "../nfc_magic_i.h" - -void nfc_magic_scene_success_popup_callback(void* context) { - NfcMagic* nfc_magic = context; - view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, NfcMagicCustomEventViewExit); -} - -void nfc_magic_scene_success_on_enter(void* context) { - NfcMagic* nfc_magic = context; - - notification_message(nfc_magic->notifications, &sequence_success); - - Popup* popup = nfc_magic->popup; - popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); - popup_set_header(popup, "Success!", 10, 20, AlignLeft, AlignBottom); - popup_set_timeout(popup, 1500); - popup_set_context(popup, nfc_magic); - popup_set_callback(popup, nfc_magic_scene_success_popup_callback); - popup_enable_timeout(popup); - - view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewPopup); -} - -bool nfc_magic_scene_success_on_event(void* context, SceneManagerEvent event) { - NfcMagic* nfc_magic = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == NfcMagicCustomEventViewExit) { - consumed = scene_manager_search_and_switch_to_previous_scene( - nfc_magic->scene_manager, NfcMagicSceneStart); - } - } - return consumed; -} - -void nfc_magic_scene_success_on_exit(void* context) { - NfcMagic* nfc_magic = context; - - // Clear view - popup_reset(nfc_magic->popup); -} diff --git a/applications/external/nfc_magic/scenes/nfc_magic_scene_wipe.c b/applications/external/nfc_magic/scenes/nfc_magic_scene_wipe.c deleted file mode 100644 index 29640f89c6b..00000000000 --- a/applications/external/nfc_magic/scenes/nfc_magic_scene_wipe.c +++ /dev/null @@ -1,97 +0,0 @@ -#include "../nfc_magic_i.h" - -enum { - NfcMagicSceneWipeStateCardSearch, - NfcMagicSceneWipeStateCardFound, -}; - -bool nfc_magic_wipe_worker_callback(NfcMagicWorkerEvent event, void* context) { - furi_assert(context); - - NfcMagic* nfc_magic = context; - view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, event); - - return true; -} - -static void nfc_magic_scene_wipe_setup_view(NfcMagic* nfc_magic) { - Popup* popup = nfc_magic->popup; - popup_reset(popup); - uint32_t state = scene_manager_get_scene_state(nfc_magic->scene_manager, NfcMagicSceneWipe); - - if(state == NfcMagicSceneWipeStateCardSearch) { - popup_set_icon(nfc_magic->popup, 0, 8, &I_NFC_manual_60x50); - popup_set_text( - nfc_magic->popup, - "Apply the\nsame card\nto the back", - 128, - 32, - AlignRight, - AlignCenter); - } else { - popup_set_icon(popup, 12, 23, &I_Loading_24); - popup_set_header(popup, "Wiping\nDon't move...", 52, 32, AlignLeft, AlignCenter); - } - - view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewPopup); -} - -void nfc_magic_scene_wipe_on_enter(void* context) { - NfcMagic* nfc_magic = context; - - scene_manager_set_scene_state( - nfc_magic->scene_manager, NfcMagicSceneWipe, NfcMagicSceneWipeStateCardSearch); - nfc_magic_scene_wipe_setup_view(nfc_magic); - - // Setup and start worker - nfc_magic_worker_start( - nfc_magic->worker, - NfcMagicWorkerStateWipe, - nfc_magic->dev, - &nfc_magic->source_dev->dev_data, - nfc_magic->new_password, - nfc_magic_wipe_worker_callback, - nfc_magic); - nfc_magic_blink_start(nfc_magic); -} - -bool nfc_magic_scene_wipe_on_event(void* context, SceneManagerEvent event) { - NfcMagic* nfc_magic = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == NfcMagicWorkerEventSuccess) { - scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneSuccess); - consumed = true; - } else if(event.event == NfcMagicWorkerEventFail) { - scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneWipeFail); - consumed = true; - } else if(event.event == NfcMagicWorkerEventWrongCard) { - scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneNotMagic); - consumed = true; - } else if(event.event == NfcMagicWorkerEventCardDetected) { - scene_manager_set_scene_state( - nfc_magic->scene_manager, NfcMagicSceneWipe, NfcMagicSceneWipeStateCardFound); - nfc_magic_scene_wipe_setup_view(nfc_magic); - consumed = true; - } else if(event.event == NfcMagicWorkerEventNoCardDetected) { - scene_manager_set_scene_state( - nfc_magic->scene_manager, NfcMagicSceneWipe, NfcMagicSceneWipeStateCardSearch); - nfc_magic_scene_wipe_setup_view(nfc_magic); - consumed = true; - } - } - return consumed; -} - -void nfc_magic_scene_wipe_on_exit(void* context) { - NfcMagic* nfc_magic = context; - - nfc_magic_worker_stop(nfc_magic->worker); - scene_manager_set_scene_state( - nfc_magic->scene_manager, NfcMagicSceneWipe, NfcMagicSceneWipeStateCardSearch); - // Clear view - popup_reset(nfc_magic->popup); - - nfc_magic_blink_stop(nfc_magic); -} diff --git a/applications/external/nfc_magic/scenes/nfc_magic_scene_wipe_fail.c b/applications/external/nfc_magic/scenes/nfc_magic_scene_wipe_fail.c deleted file mode 100644 index 828b65e6c58..00000000000 --- a/applications/external/nfc_magic/scenes/nfc_magic_scene_wipe_fail.c +++ /dev/null @@ -1,41 +0,0 @@ -#include "../nfc_magic_i.h" - -void nfc_magic_scene_wipe_fail_widget_callback(GuiButtonType result, InputType type, void* context) { - NfcMagic* nfc_magic = context; - if(type == InputTypeShort) { - view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, result); - } -} - -void nfc_magic_scene_wipe_fail_on_enter(void* context) { - NfcMagic* nfc_magic = context; - Widget* widget = nfc_magic->widget; - - notification_message(nfc_magic->notifications, &sequence_error); - - widget_add_icon_element(widget, 73, 17, &I_DolphinCommon_56x48); - widget_add_string_element(widget, 3, 4, AlignLeft, AlignTop, FontPrimary, "Wipe failed"); - widget_add_button_element( - widget, GuiButtonTypeLeft, "Retry", nfc_magic_scene_wipe_fail_widget_callback, nfc_magic); - - // Setup and start worker - view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewWidget); -} - -bool nfc_magic_scene_wipe_fail_on_event(void* context, SceneManagerEvent event) { - NfcMagic* nfc_magic = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == GuiButtonTypeLeft) { - consumed = scene_manager_previous_scene(nfc_magic->scene_manager); - } - } - return consumed; -} - -void nfc_magic_scene_wipe_fail_on_exit(void* context) { - NfcMagic* nfc_magic = context; - - widget_reset(nfc_magic->widget); -} diff --git a/applications/external/nfc_magic/scenes/nfc_magic_scene_write.c b/applications/external/nfc_magic/scenes/nfc_magic_scene_write.c deleted file mode 100644 index 45c54557f15..00000000000 --- a/applications/external/nfc_magic/scenes/nfc_magic_scene_write.c +++ /dev/null @@ -1,97 +0,0 @@ -#include "../nfc_magic_i.h" - -enum { - NfcMagicSceneWriteStateCardSearch, - NfcMagicSceneWriteStateCardFound, -}; - -bool nfc_magic_write_worker_callback(NfcMagicWorkerEvent event, void* context) { - furi_assert(context); - - NfcMagic* nfc_magic = context; - view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, event); - - return true; -} - -static void nfc_magic_scene_write_setup_view(NfcMagic* nfc_magic) { - Popup* popup = nfc_magic->popup; - popup_reset(popup); - uint32_t state = scene_manager_get_scene_state(nfc_magic->scene_manager, NfcMagicSceneWrite); - - if(state == NfcMagicSceneWriteStateCardSearch) { - popup_set_text( - nfc_magic->popup, - "Apply the\nsame card\nto the back", - 128, - 32, - AlignRight, - AlignCenter); - popup_set_icon(nfc_magic->popup, 0, 8, &I_NFC_manual_60x50); - } else { - popup_set_icon(popup, 12, 23, &I_Loading_24); - popup_set_header(popup, "Writing\nDon't move...", 52, 32, AlignLeft, AlignCenter); - } - - view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewPopup); -} - -void nfc_magic_scene_write_on_enter(void* context) { - NfcMagic* nfc_magic = context; - - scene_manager_set_scene_state( - nfc_magic->scene_manager, NfcMagicSceneWrite, NfcMagicSceneWriteStateCardSearch); - nfc_magic_scene_write_setup_view(nfc_magic); - - // Setup and start worker - nfc_magic_worker_start( - nfc_magic->worker, - NfcMagicWorkerStateWrite, - nfc_magic->dev, - &nfc_magic->source_dev->dev_data, - nfc_magic->new_password, - nfc_magic_write_worker_callback, - nfc_magic); - nfc_magic_blink_start(nfc_magic); -} - -bool nfc_magic_scene_write_on_event(void* context, SceneManagerEvent event) { - NfcMagic* nfc_magic = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == NfcMagicWorkerEventSuccess) { - scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneSuccess); - consumed = true; - } else if(event.event == NfcMagicWorkerEventFail) { - scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneWriteFail); - consumed = true; - } else if(event.event == NfcMagicWorkerEventWrongCard) { - scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneNotMagic); - consumed = true; - } else if(event.event == NfcMagicWorkerEventCardDetected) { - scene_manager_set_scene_state( - nfc_magic->scene_manager, NfcMagicSceneWrite, NfcMagicSceneWriteStateCardFound); - nfc_magic_scene_write_setup_view(nfc_magic); - consumed = true; - } else if(event.event == NfcMagicWorkerEventNoCardDetected) { - scene_manager_set_scene_state( - nfc_magic->scene_manager, NfcMagicSceneWrite, NfcMagicSceneWriteStateCardSearch); - nfc_magic_scene_write_setup_view(nfc_magic); - consumed = true; - } - } - return consumed; -} - -void nfc_magic_scene_write_on_exit(void* context) { - NfcMagic* nfc_magic = context; - - nfc_magic_worker_stop(nfc_magic->worker); - scene_manager_set_scene_state( - nfc_magic->scene_manager, NfcMagicSceneWrite, NfcMagicSceneWriteStateCardSearch); - // Clear view - popup_reset(nfc_magic->popup); - - nfc_magic_blink_stop(nfc_magic); -} diff --git a/applications/external/nfc_magic/scenes/nfc_magic_scene_write_confirm.c b/applications/external/nfc_magic/scenes/nfc_magic_scene_write_confirm.c deleted file mode 100644 index d31c1c194d0..00000000000 --- a/applications/external/nfc_magic/scenes/nfc_magic_scene_write_confirm.c +++ /dev/null @@ -1,64 +0,0 @@ -#include "../nfc_magic_i.h" - -void nfc_magic_scene_write_confirm_widget_callback( - GuiButtonType result, - InputType type, - void* context) { - NfcMagic* nfc_magic = context; - if(type == InputTypeShort) { - view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, result); - } -} - -void nfc_magic_scene_write_confirm_on_enter(void* context) { - NfcMagic* nfc_magic = context; - Widget* widget = nfc_magic->widget; - - widget_add_string_element(widget, 3, 0, AlignLeft, AlignTop, FontPrimary, "Risky operation"); - widget_add_text_box_element( - widget, - 0, - 13, - 128, - 54, - AlignLeft, - AlignTop, - "Writing to this card will change manufacturer block. On some cards it may not be rewritten", - false); - widget_add_button_element( - widget, - GuiButtonTypeCenter, - "Continue", - nfc_magic_scene_write_confirm_widget_callback, - nfc_magic); - widget_add_button_element( - widget, - GuiButtonTypeLeft, - "Back", - nfc_magic_scene_write_confirm_widget_callback, - nfc_magic); - - // Setup and start worker - view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewWidget); -} - -bool nfc_magic_scene_write_confirm_on_event(void* context, SceneManagerEvent event) { - NfcMagic* nfc_magic = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == GuiButtonTypeLeft) { - consumed = scene_manager_previous_scene(nfc_magic->scene_manager); - } else if(event.event == GuiButtonTypeCenter) { - scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneWrite); - consumed = true; - } - } - return consumed; -} - -void nfc_magic_scene_write_confirm_on_exit(void* context) { - NfcMagic* nfc_magic = context; - - widget_reset(nfc_magic->widget); -} diff --git a/applications/external/nfc_magic/scenes/nfc_magic_scene_write_fail.c b/applications/external/nfc_magic/scenes/nfc_magic_scene_write_fail.c deleted file mode 100644 index 8a465bf61e7..00000000000 --- a/applications/external/nfc_magic/scenes/nfc_magic_scene_write_fail.c +++ /dev/null @@ -1,58 +0,0 @@ -#include "../nfc_magic_i.h" - -void nfc_magic_scene_write_fail_widget_callback( - GuiButtonType result, - InputType type, - void* context) { - NfcMagic* nfc_magic = context; - if(type == InputTypeShort) { - view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, result); - } -} - -void nfc_magic_scene_write_fail_on_enter(void* context) { - NfcMagic* nfc_magic = context; - Widget* widget = nfc_magic->widget; - - notification_message(nfc_magic->notifications, &sequence_error); - - widget_add_icon_element(widget, 72, 17, &I_DolphinCommon_56x48); - widget_add_string_element( - widget, 7, 4, AlignLeft, AlignTop, FontPrimary, "Writing gone wrong!"); - widget_add_string_multiline_element( - widget, - 7, - 17, - AlignLeft, - AlignTop, - FontSecondary, - "Not all sectors\nwere written\ncorrectly."); - - widget_add_button_element( - widget, GuiButtonTypeLeft, "Finish", nfc_magic_scene_write_fail_widget_callback, nfc_magic); - - // Setup and start worker - view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewWidget); -} - -bool nfc_magic_scene_write_fail_on_event(void* context, SceneManagerEvent event) { - NfcMagic* nfc_magic = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == GuiButtonTypeLeft) { - consumed = scene_manager_search_and_switch_to_previous_scene( - nfc_magic->scene_manager, NfcMagicSceneStart); - } - } else if(event.type == SceneManagerEventTypeBack) { - consumed = scene_manager_search_and_switch_to_previous_scene( - nfc_magic->scene_manager, NfcMagicSceneStart); - } - return consumed; -} - -void nfc_magic_scene_write_fail_on_exit(void* context) { - NfcMagic* nfc_magic = context; - - widget_reset(nfc_magic->widget); -} diff --git a/applications/external/nfc_magic/scenes/nfc_magic_scene_wrong_card.c b/applications/external/nfc_magic/scenes/nfc_magic_scene_wrong_card.c deleted file mode 100644 index 857d50c1f7d..00000000000 --- a/applications/external/nfc_magic/scenes/nfc_magic_scene_wrong_card.c +++ /dev/null @@ -1,53 +0,0 @@ -#include "../nfc_magic_i.h" - -void nfc_magic_scene_wrong_card_widget_callback( - GuiButtonType result, - InputType type, - void* context) { - NfcMagic* nfc_magic = context; - if(type == InputTypeShort) { - view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, result); - } -} - -void nfc_magic_scene_wrong_card_on_enter(void* context) { - NfcMagic* nfc_magic = context; - Widget* widget = nfc_magic->widget; - - notification_message(nfc_magic->notifications, &sequence_error); - - widget_add_icon_element(widget, 73, 17, &I_DolphinCommon_56x48); - widget_add_string_element( - widget, 1, 4, AlignLeft, AlignTop, FontPrimary, "This is wrong card"); - widget_add_string_multiline_element( - widget, - 1, - 17, - AlignLeft, - AlignTop, - FontSecondary, - "Writing this file is\nnot supported for\nthis magic card."); - widget_add_button_element( - widget, GuiButtonTypeLeft, "Retry", nfc_magic_scene_wrong_card_widget_callback, nfc_magic); - - // Setup and start worker - view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewWidget); -} - -bool nfc_magic_scene_wrong_card_on_event(void* context, SceneManagerEvent event) { - NfcMagic* nfc_magic = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == GuiButtonTypeLeft) { - consumed = scene_manager_previous_scene(nfc_magic->scene_manager); - } - } - return consumed; -} - -void nfc_magic_scene_wrong_card_on_exit(void* context) { - NfcMagic* nfc_magic = context; - - widget_reset(nfc_magic->widget); -} diff --git a/applications/external/nfc_rfid_detector/application.fam b/applications/external/nfc_rfid_detector/application.fam deleted file mode 100644 index 70c91bc8437..00000000000 --- a/applications/external/nfc_rfid_detector/application.fam +++ /dev/null @@ -1,13 +0,0 @@ -App( - appid="nfc_rfid_detector", - name="NFC/RFID detector", - apptype=FlipperAppType.EXTERNAL, - targets=["f7"], - entry_point="nfc_rfid_detector_app", - requires=["gui"], - stack_size=4 * 1024, - order=50, - fap_icon="nfc_rfid_detector_10px.png", - fap_category="Tools", - fap_icon_assets="images", -) diff --git a/applications/external/nfc_rfid_detector/helpers/nfc_rfid_detector_event.h b/applications/external/nfc_rfid_detector/helpers/nfc_rfid_detector_event.h deleted file mode 100644 index bbffe2938e4..00000000000 --- a/applications/external/nfc_rfid_detector/helpers/nfc_rfid_detector_event.h +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once - -typedef enum { - //NfcRfidDetectorCustomEvent - NfcRfidDetectorCustomEventStartId = 100, - -} NfcRfidDetectorCustomEvent; diff --git a/applications/external/nfc_rfid_detector/helpers/nfc_rfid_detector_types.h b/applications/external/nfc_rfid_detector/helpers/nfc_rfid_detector_types.h deleted file mode 100644 index 5d44b09b7f2..00000000000 --- a/applications/external/nfc_rfid_detector/helpers/nfc_rfid_detector_types.h +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once - -#include -#include - -#define NFC_RFID_DETECTOR_VERSION_APP "0.1" -#define NFC_RFID_DETECTOR_DEVELOPED "SkorP" -#define NFC_RFID_DETECTOR_GITHUB "https://github.com/flipperdevices/flipperzero-firmware" - -typedef enum { - NfcRfidDetectorViewVariableItemList, - NfcRfidDetectorViewSubmenu, - NfcRfidDetectorViewFieldPresence, - NfcRfidDetectorViewWidget, -} NfcRfidDetectorView; diff --git a/applications/external/nfc_rfid_detector/images/Modern_reader_18x34.png b/applications/external/nfc_rfid_detector/images/Modern_reader_18x34.png deleted file mode 100644 index b19c0f30c9f3928d3129acc9da92d5a9e962d084..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3670 zcmaJ@c{r478-GRiEm@Lu#t;&-TAE=jGh>S(jEuAxj4^2zV`?lVl&v}>Wo<-dUn)uo zWeX)lN%pcNI{1zyPGY`s&gp#LA79^lz3=-x&wbs$-~GFn_qyJMgHE9q!yUB8;Xo`l)1P*d0stWcJU1%QZCV+#GO~nqh>yJH zz;sm-2f1P|MJgt1>uE^HABfk;?N@SX*k)}lqSlrZFPxYdd0ELtU;3itd$9?PTZ!jy z$6tK8_A&f+;JezDPaPW%`^=|G7kQOkV)f$Esdh*gqe$r@?CxzJ&bKzVe4Kz-MoDV1 z0D19BKaJpZO(9@4!pv+RxL)ijAQbXON*t&sWYxoV#qs54uo*{$A}O1A7Dgbe50Ok@OvlkEv2fW)fHA8?4 z8GxeAf`{4f`^x2~^aPd4s4%P6LRm+7i5mood3Zo}>vr0!>{B!*Zy{$|LK;IeR1r~z zavv670YFZ&k|5i~^^i{4^3G1<#46e21~bn@`CuQP@r}u@5|$+ZeB?xQZ|FlScSf3u zM$$KK?U@q^I3|^IYUPrDg`DL>AZL2OW0AF48|&OF)&2dG6BF+bG-JKUFFnp~P#cfe zd#s=QBf{+a%JPS&V_H#&qfxdZs~;L)Eji}x>bfd%!Dr}GlI{0LQvC1gZ@|s=KGh^W z#c>yfphSG;@-q zi($!qBa3G@=+;I_h*-6WZzpRE#0&XcBxxp!t7OEiYBbo1C|uG4y@*$I0Xrlc*}+{e z5<%{E>I)e57F663nr15gw%-SrN|&_kymzQnxF%uQ zx9dJvL?Oz$Ucy*}iv^K)TiKBuNlx$W3PHQH47UwPm`Dg;aB0*5rxZFo(0;P*kLDdd z2zVUHPG9q#Leh4qe0V&r*+fer0f*43zOu#s{vBeELXS-k!&P%yzbMPlZl`9-ivhpD z3Nh3*ebBzPmlcJP#gq8d4OxNMU zT;evPq{G;<+$z_*E^&q14NqmFI?gNGJLHw!y8dQofJ(p$?e1sJlWoJ-cRQuM_ULJ! zw*8#;S$K&nEfcGBzBQhztD3b#YzI}9yW?)UW4`K}ORB9zmvhMMG|jQOWccj2fw(fxlxNu z3*(BZg-oKwoe0nM1X0f>$0ldo9haQ@$H!}1KvKS{l_B~Xfifkrr=pCSweNTIpE<2p zlfJHAa|u&il#9Y44-290%eK-a(MoA8(Lw3X9cIss zf|zFN(AL4*TbL7m};H&2IPF{Awe2nbvY-Tx*=(LT|aPEvl`d?Le3z z%w@U~s`K~en>w00wsySgxYhA4!zc>_??X&wO=b0EjXv@|9CBE{s<7%Y#lB+VaK7hU zRV^dtFv>HJQrA-;HY|p!zvYLWz1=UU|P9@pzs7?2NuX<5c^hovII|O` zW;Mlf{?(j)bKHE~%wz;H;(7d)N&Ta?NA1o{%D3JMF*rFDrSyLgmYQ7PfQuBua)hsy9->&~D@I`1iOYdb^z# z?DPm>SAR>cH44>wj?B}atiGUAbfwl&#&I|covoaC8bn86&~@L>rx?WL5MijC)tOOK$tuZz71th`dX)zd(-3Y-6#cv!bjPppDU@$i4vk?<0gT9Uo5 zWA;_$%fTxqH|B5hXB8S1K3=WLi*@iYP$zw=D?Nd#FbfJDlpI&ux-a&SXsOxbi&c8` zUgwfokF@fLI_)q*VAQdOm(dLmg#y1wxl2yQoc%J?H+$5X1oa$!Nd6YfQ!`gexLB?@ zsFJ31?!E3%$fQ~v^X0RQp=%F{N}8+vy8L_mr$3DtWP8b`7N>nmlV!;C4?K_=J@jC9 z`K$FHG_6B-u;zRfuKM;fv&XfRf)||~rWV9I#3kZ4qVZhM@I!LnDx-T&Exh)t;cvZz zUbQRh<}aQOx(m4zdi{GTYxZlED;DJm#nY>)YxJXKPV}JJR^cAubumrZs=n&Cz3M#} zqHEH-eP3*4TYq`F!JFqA$QaAG|9YckOp}EVotR#c7+u*dgC012IlT0v*qdKYt5emX zC$O0dnKoH&nQLA?UQe7~nRmaN843GtJNS#-4MQ`}&;yIa7qo%t=r<|Ug|5rI>%6lO zkUxgJ2X9q{Px*F^o{(eCKauBr?6Kxwnli05?L4yZn6pqZIJw>9u}9`z^l|zOXU1$J z<&AS|&5fGO^6Ddj)pKEW55xUerq!}dI)|6)LVs80zw6CLVTS7#!z(a2{al^7vRdcb<4cyaR{gl)xLymdjiLARL+4J^b8{BEhiq3wW6pPNBrhk);kG7a zB(=xN#D2-%Z;nEZS+LiqzZc-T{JONWRW@#Iw3n+WLnBsuzw~u>r+4S3Eu^J9qo2uJ zpQ-<%dUvp;v1Rwu7a>Uav86+6vklxKuKN7#Q90*{GoW+2{D431FT1@iSW8h&N#TnK zr!Rh=H@X%r_^(vuSd%zzOn(lS%%%WVeoP+<$evE7Qd}uyztEr;6f*!2)};|i91_71 z?aQP?$eTWp5IReM1^_dQ5Ej`tkir4^P^dHp20UN$3=E?AVZa_n1Q>yZqXf|G!q^nI zFejpKSfDS;4{Tu$G7CWq2_OC4Htbb@3!GBjuP%~%ok9RP~ zmGU3G|C2bF7|NnRT`9rLQ*2*B@BB44L$S~}HigV#vWZOQ$sdJ07{KH(g9Df>5CRE- zgLDaGUm9c6viDC2fq=GW1ars?Uy3~*0~U}#Xf!`G9uC9W*z89l_alwqaBKX2BnoZ? zvw|5T_oLv3CfFZXJk$3SoxWBq=v1@TiXR3HYr+1vl>^$(L^fHt@P46oqu&-haqf|+LvhFcAp;MtHb?>>aojMU&+$&Jw2#YyGzNAg?=0N~xO|1%9#hzl z?cEASvP%gSwpPE+s1{*#d-3TN&&Ml8>K`_AH+6iBd^^X2G{h(8k diff --git a/applications/external/nfc_rfid_detector/images/Move_flipper_26x39.png b/applications/external/nfc_rfid_detector/images/Move_flipper_26x39.png deleted file mode 100644 index ff4af9ff05989e5ff04d3888971b7f7801c59ed4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3698 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 QfL1Vgy85}Sb4q9e0PSTwjsO4v diff --git a/applications/external/nfc_rfid_detector/images/Rfid_detect_45x30.png b/applications/external/nfc_rfid_detector/images/Rfid_detect_45x30.png deleted file mode 100644 index 35c205049bc5a6c7f0bb13a0cb3057963a023c6e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 158 zcmeAS@N?(olHy`uVBq!ia0vp^xG+l41V{G`^6RB9^&ce?Q{EhH1o(i zcG0sF=jLrIU`gfJ9e<(w?c#$+`{GybuV0XK^{NaL55t;h1_!r?84rQRGkCiCxvX
f4IeWS|hDc0ZJK-P~g93;1_80%_ zf0mxq_>?eVL9oQDG>54hmUMGg2<9ZE<@p-^(r|y*a$w%vQ*}A2s_`AWqh21A3+I&= XdhU3CV!O&Kpm7YIu6{1-oD!M<1Jf%z diff --git a/applications/external/nfc_rfid_detector/nfc_rfid_detector_app.c b/applications/external/nfc_rfid_detector/nfc_rfid_detector_app.c deleted file mode 100644 index cba8b60856a..00000000000 --- a/applications/external/nfc_rfid_detector/nfc_rfid_detector_app.c +++ /dev/null @@ -1,108 +0,0 @@ -#include "nfc_rfid_detector_app_i.h" - -#include -#include - -static bool nfc_rfid_detector_app_custom_event_callback(void* context, uint32_t event) { - furi_assert(context); - NfcRfidDetectorApp* app = context; - return scene_manager_handle_custom_event(app->scene_manager, event); -} - -static bool nfc_rfid_detector_app_back_event_callback(void* context) { - furi_assert(context); - NfcRfidDetectorApp* app = context; - return scene_manager_handle_back_event(app->scene_manager); -} - -static void nfc_rfid_detector_app_tick_event_callback(void* context) { - furi_assert(context); - NfcRfidDetectorApp* app = context; - scene_manager_handle_tick_event(app->scene_manager); -} - -NfcRfidDetectorApp* nfc_rfid_detector_app_alloc() { - NfcRfidDetectorApp* app = malloc(sizeof(NfcRfidDetectorApp)); - - // GUI - app->gui = furi_record_open(RECORD_GUI); - - // View Dispatcher - app->view_dispatcher = view_dispatcher_alloc(); - app->scene_manager = scene_manager_alloc(&nfc_rfid_detector_scene_handlers, app); - view_dispatcher_enable_queue(app->view_dispatcher); - - view_dispatcher_set_event_callback_context(app->view_dispatcher, app); - view_dispatcher_set_custom_event_callback( - app->view_dispatcher, nfc_rfid_detector_app_custom_event_callback); - view_dispatcher_set_navigation_event_callback( - app->view_dispatcher, nfc_rfid_detector_app_back_event_callback); - view_dispatcher_set_tick_event_callback( - app->view_dispatcher, nfc_rfid_detector_app_tick_event_callback, 100); - - view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); - - // Open Notification record - app->notifications = furi_record_open(RECORD_NOTIFICATION); - - // SubMenu - app->submenu = submenu_alloc(); - view_dispatcher_add_view( - app->view_dispatcher, NfcRfidDetectorViewSubmenu, submenu_get_view(app->submenu)); - - // Widget - app->widget = widget_alloc(); - view_dispatcher_add_view( - app->view_dispatcher, NfcRfidDetectorViewWidget, widget_get_view(app->widget)); - - // Field Presence - app->nfc_rfid_detector_field_presence = nfc_rfid_detector_view_field_presence_alloc(); - view_dispatcher_add_view( - app->view_dispatcher, - NfcRfidDetectorViewFieldPresence, - nfc_rfid_detector_view_field_presence_get_view(app->nfc_rfid_detector_field_presence)); - - scene_manager_next_scene(app->scene_manager, NfcRfidDetectorSceneStart); - - return app; -} - -void nfc_rfid_detector_app_free(NfcRfidDetectorApp* app) { - furi_assert(app); - - // Submenu - view_dispatcher_remove_view(app->view_dispatcher, NfcRfidDetectorViewSubmenu); - submenu_free(app->submenu); - - // Widget - view_dispatcher_remove_view(app->view_dispatcher, NfcRfidDetectorViewWidget); - widget_free(app->widget); - - // Field Presence - view_dispatcher_remove_view(app->view_dispatcher, NfcRfidDetectorViewFieldPresence); - nfc_rfid_detector_view_field_presence_free(app->nfc_rfid_detector_field_presence); - - // View dispatcher - view_dispatcher_free(app->view_dispatcher); - scene_manager_free(app->scene_manager); - - // Notifications - furi_record_close(RECORD_NOTIFICATION); - app->notifications = NULL; - - // Close records - furi_record_close(RECORD_GUI); - - free(app); -} - -int32_t nfc_rfid_detector_app(void* p) { - UNUSED(p); - NfcRfidDetectorApp* nfc_rfid_detector_app = nfc_rfid_detector_app_alloc(); - - view_dispatcher_run(nfc_rfid_detector_app->view_dispatcher); - - nfc_rfid_detector_app_free(nfc_rfid_detector_app); - - return 0; -} diff --git a/applications/external/nfc_rfid_detector/nfc_rfid_detector_app_i.c b/applications/external/nfc_rfid_detector/nfc_rfid_detector_app_i.c deleted file mode 100644 index c59d40d50c5..00000000000 --- a/applications/external/nfc_rfid_detector/nfc_rfid_detector_app_i.c +++ /dev/null @@ -1,40 +0,0 @@ -#include "nfc_rfid_detector_app_i.h" - -#include - -#define TAG "NfcRfidDetector" - -void nfc_rfid_detector_app_field_presence_start(NfcRfidDetectorApp* app) { - furi_assert(app); - - // start the field presence rfid detection - furi_hal_rfid_field_detect_start(); - - // start the field presence nfc detection - furi_hal_nfc_exit_sleep(); - furi_hal_nfc_field_detect_start(); -} - -void nfc_rfid_detector_app_field_presence_stop(NfcRfidDetectorApp* app) { - furi_assert(app); - - // stop the field presence rfid detection - furi_hal_rfid_field_detect_stop(); - - // stop the field presence nfc detection - furi_hal_nfc_start_sleep(); -} - -bool nfc_rfid_detector_app_field_presence_is_nfc(NfcRfidDetectorApp* app) { - furi_assert(app); - - // check if the field presence is nfc - return furi_hal_nfc_field_is_present(); -} - -bool nfc_rfid_detector_app_field_presence_is_rfid(NfcRfidDetectorApp* app, uint32_t* frequency) { - furi_assert(app); - - // check if the field presence is rfid - return furi_hal_rfid_field_is_present(frequency); -} \ No newline at end of file diff --git a/applications/external/nfc_rfid_detector/nfc_rfid_detector_app_i.h b/applications/external/nfc_rfid_detector/nfc_rfid_detector_app_i.h deleted file mode 100644 index 72cb126d4a3..00000000000 --- a/applications/external/nfc_rfid_detector/nfc_rfid_detector_app_i.h +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -#include "helpers/nfc_rfid_detector_types.h" -#include "helpers/nfc_rfid_detector_event.h" - -#include "scenes/nfc_rfid_detector_scene.h" -#include -#include -#include -#include -#include -#include -#include "views/nfc_rfid_detector_view_field_presence.h" - -typedef struct NfcRfidDetectorApp NfcRfidDetectorApp; - -struct NfcRfidDetectorApp { - Gui* gui; - ViewDispatcher* view_dispatcher; - SceneManager* scene_manager; - NotificationApp* notifications; - Submenu* submenu; - Widget* widget; - NfcRfidDetectorFieldPresence* nfc_rfid_detector_field_presence; -}; - -void nfc_rfid_detector_app_field_presence_start(NfcRfidDetectorApp* app); -void nfc_rfid_detector_app_field_presence_stop(NfcRfidDetectorApp* app); -bool nfc_rfid_detector_app_field_presence_is_nfc(NfcRfidDetectorApp* app); -bool nfc_rfid_detector_app_field_presence_is_rfid(NfcRfidDetectorApp* app, uint32_t* frequency); \ No newline at end of file diff --git a/applications/external/nfc_rfid_detector/scenes/nfc_rfid_detector_scene.c b/applications/external/nfc_rfid_detector/scenes/nfc_rfid_detector_scene.c deleted file mode 100644 index d75eb2884fe..00000000000 --- a/applications/external/nfc_rfid_detector/scenes/nfc_rfid_detector_scene.c +++ /dev/null @@ -1,31 +0,0 @@ -#include "../nfc_rfid_detector_app_i.h" - -// Generate scene on_enter handlers array -#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, -void (*const nfc_rfid_detector_scene_on_enter_handlers[])(void*) = { -#include "nfc_rfid_detector_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 nfc_rfid_detector_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = - { -#include "nfc_rfid_detector_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 nfc_rfid_detector_scene_on_exit_handlers[])(void* context) = { -#include "nfc_rfid_detector_scene_config.h" -}; -#undef ADD_SCENE - -// Initialize scene handlers configuration structure -const SceneManagerHandlers nfc_rfid_detector_scene_handlers = { - .on_enter_handlers = nfc_rfid_detector_scene_on_enter_handlers, - .on_event_handlers = nfc_rfid_detector_scene_on_event_handlers, - .on_exit_handlers = nfc_rfid_detector_scene_on_exit_handlers, - .scene_num = NfcRfidDetectorSceneNum, -}; diff --git a/applications/external/nfc_rfid_detector/scenes/nfc_rfid_detector_scene.h b/applications/external/nfc_rfid_detector/scenes/nfc_rfid_detector_scene.h deleted file mode 100644 index 74d324b4d3b..00000000000 --- a/applications/external/nfc_rfid_detector/scenes/nfc_rfid_detector_scene.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -#include - -// Generate scene id and total number -#define ADD_SCENE(prefix, name, id) NfcRfidDetectorScene##id, -typedef enum { -#include "nfc_rfid_detector_scene_config.h" - NfcRfidDetectorSceneNum, -} NfcRfidDetectorScene; -#undef ADD_SCENE - -extern const SceneManagerHandlers nfc_rfid_detector_scene_handlers; - -// Generate scene on_enter handlers declaration -#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); -#include "nfc_rfid_detector_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 "nfc_rfid_detector_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 "nfc_rfid_detector_scene_config.h" -#undef ADD_SCENE diff --git a/applications/external/nfc_rfid_detector/scenes/nfc_rfid_detector_scene_about.c b/applications/external/nfc_rfid_detector/scenes/nfc_rfid_detector_scene_about.c deleted file mode 100644 index ddcb8aac0fe..00000000000 --- a/applications/external/nfc_rfid_detector/scenes/nfc_rfid_detector_scene_about.c +++ /dev/null @@ -1,69 +0,0 @@ -#include "../nfc_rfid_detector_app_i.h" - -void nfc_rfid_detector_scene_about_widget_callback( - GuiButtonType result, - InputType type, - void* context) { - NfcRfidDetectorApp* app = context; - if(type == InputTypeShort) { - view_dispatcher_send_custom_event(app->view_dispatcher, result); - } -} - -void nfc_rfid_detector_scene_about_on_enter(void* context) { - NfcRfidDetectorApp* app = context; - - FuriString* temp_str; - temp_str = furi_string_alloc(); - furi_string_printf(temp_str, "\e#%s\n", "Information"); - - furi_string_cat_printf(temp_str, "Version: %s\n", NFC_RFID_DETECTOR_VERSION_APP); - furi_string_cat_printf(temp_str, "Developed by: %s\n", NFC_RFID_DETECTOR_DEVELOPED); - furi_string_cat_printf(temp_str, "Github: %s\n\n", NFC_RFID_DETECTOR_GITHUB); - - furi_string_cat_printf(temp_str, "\e#%s\n", "Description"); - furi_string_cat_printf( - temp_str, - "This application allows\nyou to determine what\ntype of electromagnetic\nfield the reader is using.\nFor LF RFID you can also\nsee the carrier frequency\n\n"); - - widget_add_text_box_element( - app->widget, - 0, - 0, - 128, - 14, - AlignCenter, - AlignBottom, - "\e#\e! \e!\n", - false); - widget_add_text_box_element( - app->widget, - 0, - 2, - 128, - 14, - AlignCenter, - AlignBottom, - "\e#\e! NFC/RFID detector \e!\n", - false); - widget_add_text_scroll_element(app->widget, 0, 16, 128, 50, furi_string_get_cstr(temp_str)); - furi_string_free(temp_str); - - view_dispatcher_switch_to_view(app->view_dispatcher, NfcRfidDetectorViewWidget); -} - -bool nfc_rfid_detector_scene_about_on_event(void* context, SceneManagerEvent event) { - NfcRfidDetectorApp* app = context; - bool consumed = false; - UNUSED(app); - UNUSED(event); - - return consumed; -} - -void nfc_rfid_detector_scene_about_on_exit(void* context) { - NfcRfidDetectorApp* app = context; - - // Clear views - widget_reset(app->widget); -} diff --git a/applications/external/nfc_rfid_detector/scenes/nfc_rfid_detector_scene_config.h b/applications/external/nfc_rfid_detector/scenes/nfc_rfid_detector_scene_config.h deleted file mode 100644 index ab49ad5c2cf..00000000000 --- a/applications/external/nfc_rfid_detector/scenes/nfc_rfid_detector_scene_config.h +++ /dev/null @@ -1,3 +0,0 @@ -ADD_SCENE(nfc_rfid_detector, start, Start) -ADD_SCENE(nfc_rfid_detector, about, About) -ADD_SCENE(nfc_rfid_detector, field_presence, FieldPresence) diff --git a/applications/external/nfc_rfid_detector/scenes/nfc_rfid_detector_scene_field_presence.c b/applications/external/nfc_rfid_detector/scenes/nfc_rfid_detector_scene_field_presence.c deleted file mode 100644 index ec53b5a0a60..00000000000 --- a/applications/external/nfc_rfid_detector/scenes/nfc_rfid_detector_scene_field_presence.c +++ /dev/null @@ -1,60 +0,0 @@ -#include "../nfc_rfid_detector_app_i.h" -#include "../views/nfc_rfid_detector_view_field_presence.h" - -void nfc_rfid_detector_scene_field_presence_callback( - NfcRfidDetectorCustomEvent event, - void* context) { - furi_assert(context); - NfcRfidDetectorApp* app = context; - view_dispatcher_send_custom_event(app->view_dispatcher, event); -} - -static const NotificationSequence notification_app_display_on = { - - &message_display_backlight_on, - NULL, -}; - -static void nfc_rfid_detector_scene_field_presence_update(void* context) { - furi_assert(context); - NfcRfidDetectorApp* app = context; - - uint32_t frequency = 0; - bool nfc_field = nfc_rfid_detector_app_field_presence_is_nfc(app); - bool rfid_field = nfc_rfid_detector_app_field_presence_is_rfid(app, &frequency); - - if(nfc_field || rfid_field) - notification_message(app->notifications, ¬ification_app_display_on); - - nfc_rfid_detector_view_field_presence_update( - app->nfc_rfid_detector_field_presence, nfc_field, rfid_field, frequency); -} - -void nfc_rfid_detector_scene_field_presence_on_enter(void* context) { - furi_assert(context); - NfcRfidDetectorApp* app = context; - - // Start detection of field presence - nfc_rfid_detector_app_field_presence_start(app); - - view_dispatcher_switch_to_view(app->view_dispatcher, NfcRfidDetectorViewFieldPresence); -} - -bool nfc_rfid_detector_scene_field_presence_on_event(void* context, SceneManagerEvent event) { - furi_assert(context); - NfcRfidDetectorApp* app = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeTick) { - nfc_rfid_detector_scene_field_presence_update(app); - } - - return consumed; -} - -void nfc_rfid_detector_scene_field_presence_on_exit(void* context) { - furi_assert(context); - NfcRfidDetectorApp* app = context; - // Stop detection of field presence - nfc_rfid_detector_app_field_presence_stop(app); -} diff --git a/applications/external/nfc_rfid_detector/scenes/nfc_rfid_detector_scene_start.c b/applications/external/nfc_rfid_detector/scenes/nfc_rfid_detector_scene_start.c deleted file mode 100644 index 7b71bd97351..00000000000 --- a/applications/external/nfc_rfid_detector/scenes/nfc_rfid_detector_scene_start.c +++ /dev/null @@ -1,58 +0,0 @@ -#include "../nfc_rfid_detector_app_i.h" - -typedef enum { - SubmenuIndexNfcRfidDetectorFieldPresence, - SubmenuIndexNfcRfidDetectorAbout, -} SubmenuIndex; - -void nfc_rfid_detector_scene_start_submenu_callback(void* context, uint32_t index) { - NfcRfidDetectorApp* app = context; - view_dispatcher_send_custom_event(app->view_dispatcher, index); -} - -void nfc_rfid_detector_scene_start_on_enter(void* context) { - UNUSED(context); - NfcRfidDetectorApp* app = context; - Submenu* submenu = app->submenu; - - submenu_add_item( - submenu, - "Detect field type", - SubmenuIndexNfcRfidDetectorFieldPresence, - nfc_rfid_detector_scene_start_submenu_callback, - app); - submenu_add_item( - submenu, - "About", - SubmenuIndexNfcRfidDetectorAbout, - nfc_rfid_detector_scene_start_submenu_callback, - app); - - submenu_set_selected_item( - submenu, scene_manager_get_scene_state(app->scene_manager, NfcRfidDetectorSceneStart)); - - view_dispatcher_switch_to_view(app->view_dispatcher, NfcRfidDetectorViewSubmenu); -} - -bool nfc_rfid_detector_scene_start_on_event(void* context, SceneManagerEvent event) { - NfcRfidDetectorApp* app = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SubmenuIndexNfcRfidDetectorAbout) { - scene_manager_next_scene(app->scene_manager, NfcRfidDetectorSceneAbout); - consumed = true; - } else if(event.event == SubmenuIndexNfcRfidDetectorFieldPresence) { - scene_manager_next_scene(app->scene_manager, NfcRfidDetectorSceneFieldPresence); - consumed = true; - } - scene_manager_set_scene_state(app->scene_manager, NfcRfidDetectorSceneStart, event.event); - } - - return consumed; -} - -void nfc_rfid_detector_scene_start_on_exit(void* context) { - NfcRfidDetectorApp* app = context; - submenu_reset(app->submenu); -} diff --git a/applications/external/nfc_rfid_detector/views/nfc_rfid_detector_view_field_presence.c b/applications/external/nfc_rfid_detector/views/nfc_rfid_detector_view_field_presence.c deleted file mode 100644 index e65eb8362b4..00000000000 --- a/applications/external/nfc_rfid_detector/views/nfc_rfid_detector_view_field_presence.c +++ /dev/null @@ -1,164 +0,0 @@ -#include "nfc_rfid_detector_view_field_presence.h" -#include "../nfc_rfid_detector_app_i.h" -#include - -#include -#include - -#define FIELD_FOUND_WEIGHT 5 - -typedef enum { - NfcRfidDetectorTypeFieldPresenceNfc, - NfcRfidDetectorTypeFieldPresenceRfid, -} NfcRfidDetectorTypeFieldPresence; - -static const Icon* NfcRfidDetectorFieldPresenceIcons[] = { - [NfcRfidDetectorTypeFieldPresenceNfc] = &I_NFC_detect_45x30, - [NfcRfidDetectorTypeFieldPresenceRfid] = &I_Rfid_detect_45x30, -}; - -struct NfcRfidDetectorFieldPresence { - View* view; -}; - -typedef struct { - uint8_t nfc_field; - uint8_t rfid_field; - uint32_t rfid_frequency; -} NfcRfidDetectorFieldPresenceModel; - -void nfc_rfid_detector_view_field_presence_update( - NfcRfidDetectorFieldPresence* instance, - bool nfc_field, - bool rfid_field, - uint32_t rfid_frequency) { - furi_assert(instance); - with_view_model( - instance->view, - NfcRfidDetectorFieldPresenceModel * model, - { - if(nfc_field) { - model->nfc_field = FIELD_FOUND_WEIGHT; - } else if(model->nfc_field) { - model->nfc_field--; - } - if(rfid_field) { - model->rfid_field = FIELD_FOUND_WEIGHT; - model->rfid_frequency = rfid_frequency; - } else if(model->rfid_field) { - model->rfid_field--; - } - }, - true); -} - -void nfc_rfid_detector_view_field_presence_draw( - Canvas* canvas, - NfcRfidDetectorFieldPresenceModel* model) { - canvas_clear(canvas); - canvas_set_color(canvas, ColorBlack); - - if(!model->nfc_field && !model->rfid_field) { - canvas_draw_icon(canvas, 0, 16, &I_Modern_reader_18x34); - canvas_draw_icon(canvas, 22, 12, &I_Move_flipper_26x39); - canvas_set_font(canvas, FontSecondary); - canvas_draw_str(canvas, 56, 36, "Touch the reader"); - } else { - if(model->nfc_field) { - canvas_set_font(canvas, FontPrimary); - canvas_draw_str(canvas, 21, 10, "NFC"); - canvas_draw_icon( - canvas, - 9, - 17, - NfcRfidDetectorFieldPresenceIcons[NfcRfidDetectorTypeFieldPresenceNfc]); - canvas_set_font(canvas, FontSecondary); - canvas_draw_str(canvas, 9, 62, "13,56 MHz"); - } - - if(model->rfid_field) { - char str[16]; - snprintf(str, sizeof(str), "%.02f KHz", (double)model->rfid_frequency / 1000); - canvas_set_font(canvas, FontPrimary); - canvas_draw_str(canvas, 76, 10, "LF RFID"); - canvas_draw_icon( - canvas, - 71, - 17, - NfcRfidDetectorFieldPresenceIcons[NfcRfidDetectorTypeFieldPresenceRfid]); - canvas_set_font(canvas, FontSecondary); - canvas_draw_str(canvas, 69, 62, str); - } - } -} - -bool nfc_rfid_detector_view_field_presence_input(InputEvent* event, void* context) { - furi_assert(context); - NfcRfidDetectorFieldPresence* instance = context; - UNUSED(instance); - - if(event->key == InputKeyBack) { - return false; - } - - return true; -} - -void nfc_rfid_detector_view_field_presence_enter(void* context) { - furi_assert(context); - NfcRfidDetectorFieldPresence* instance = context; - with_view_model( - instance->view, - NfcRfidDetectorFieldPresenceModel * model, - { - model->nfc_field = 0; - model->rfid_field = 0; - model->rfid_frequency = 0; - }, - true); -} - -void nfc_rfid_detector_view_field_presence_exit(void* context) { - furi_assert(context); - NfcRfidDetectorFieldPresence* instance = context; - UNUSED(instance); -} - -NfcRfidDetectorFieldPresence* nfc_rfid_detector_view_field_presence_alloc() { - NfcRfidDetectorFieldPresence* instance = malloc(sizeof(NfcRfidDetectorFieldPresence)); - - // View allocation and configuration - instance->view = view_alloc(); - - view_allocate_model( - instance->view, ViewModelTypeLocking, sizeof(NfcRfidDetectorFieldPresenceModel)); - view_set_context(instance->view, instance); - view_set_draw_callback( - instance->view, (ViewDrawCallback)nfc_rfid_detector_view_field_presence_draw); - view_set_input_callback(instance->view, nfc_rfid_detector_view_field_presence_input); - view_set_enter_callback(instance->view, nfc_rfid_detector_view_field_presence_enter); - view_set_exit_callback(instance->view, nfc_rfid_detector_view_field_presence_exit); - - with_view_model( - instance->view, - NfcRfidDetectorFieldPresenceModel * model, - { - model->nfc_field = 0; - model->rfid_field = 0; - model->rfid_frequency = 0; - }, - true); - return instance; -} - -void nfc_rfid_detector_view_field_presence_free(NfcRfidDetectorFieldPresence* instance) { - furi_assert(instance); - - view_free(instance->view); - free(instance); -} - -View* nfc_rfid_detector_view_field_presence_get_view(NfcRfidDetectorFieldPresence* instance) { - furi_assert(instance); - return instance->view; -} diff --git a/applications/external/nfc_rfid_detector/views/nfc_rfid_detector_view_field_presence.h b/applications/external/nfc_rfid_detector/views/nfc_rfid_detector_view_field_presence.h deleted file mode 100644 index 0ddb4e2cd58..00000000000 --- a/applications/external/nfc_rfid_detector/views/nfc_rfid_detector_view_field_presence.h +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once - -#include -#include "../helpers/nfc_rfid_detector_types.h" -#include "../helpers/nfc_rfid_detector_event.h" - -typedef struct NfcRfidDetectorFieldPresence NfcRfidDetectorFieldPresence; - -void nfc_rfid_detector_view_field_presence_update( - NfcRfidDetectorFieldPresence* instance, - bool nfc_field, - bool rfid_field, - uint32_t rfid_frequency); - -NfcRfidDetectorFieldPresence* nfc_rfid_detector_view_field_presence_alloc(); - -void nfc_rfid_detector_view_field_presence_free(NfcRfidDetectorFieldPresence* instance); - -View* nfc_rfid_detector_view_field_presence_get_view(NfcRfidDetectorFieldPresence* instance); diff --git a/applications/external/picopass/125_10px.png b/applications/external/picopass/125_10px.png deleted file mode 100644 index ce01284a2c1f3eb413f581b84f1fb3f3a2a7223b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 308 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2xkYHHq`AGmsv7|ftIx;Y9?C1WI$O_~uBzpw; zGB8xBF)%c=FfjZA3N^f7U???UV0e|lz+g3lfkC`r&aOZkpafHrx4R1i<>&pI=m5)bWZjP>yH&963)5S4_<9hOs!iI -#include - -#define ICLASS_ELITE_DICT_FLIPPER_NAME APP_DATA_PATH("assets/iclass_elite_dict.txt") -#define ICLASS_ELITE_DICT_USER_NAME APP_DATA_PATH("assets/iclass_elite_dict_user.txt") -#define ICLASS_STANDARD_DICT_FLIPPER_NAME APP_DATA_PATH("assets/iclass_standard_dict.txt") - -#define TAG "IclassEliteDict" - -#define ICLASS_ELITE_KEY_LINE_LEN (17) -#define ICLASS_ELITE_KEY_LEN (8) - -struct IclassEliteDict { - Stream* stream; - uint32_t total_keys; -}; - -bool iclass_elite_dict_check_presence(IclassEliteDictType dict_type) { - Storage* storage = furi_record_open(RECORD_STORAGE); - - bool dict_present = false; - if(dict_type == IclassEliteDictTypeFlipper) { - dict_present = - (storage_common_stat(storage, ICLASS_ELITE_DICT_FLIPPER_NAME, NULL) == FSE_OK); - } else if(dict_type == IclassEliteDictTypeUser) { - dict_present = (storage_common_stat(storage, ICLASS_ELITE_DICT_USER_NAME, NULL) == FSE_OK); - } else if(dict_type == IclassStandardDictTypeFlipper) { - dict_present = - (storage_common_stat(storage, ICLASS_STANDARD_DICT_FLIPPER_NAME, NULL) == FSE_OK); - } - - furi_record_close(RECORD_STORAGE); - - return dict_present; -} - -IclassEliteDict* iclass_elite_dict_alloc(IclassEliteDictType dict_type) { - IclassEliteDict* dict = malloc(sizeof(IclassEliteDict)); - Storage* storage = furi_record_open(RECORD_STORAGE); - dict->stream = buffered_file_stream_alloc(storage); - FuriString* next_line = furi_string_alloc(); - - bool dict_loaded = false; - do { - if(dict_type == IclassEliteDictTypeFlipper) { - if(!buffered_file_stream_open( - dict->stream, ICLASS_ELITE_DICT_FLIPPER_NAME, FSAM_READ, FSOM_OPEN_EXISTING)) { - buffered_file_stream_close(dict->stream); - break; - } - } else if(dict_type == IclassEliteDictTypeUser) { - if(!buffered_file_stream_open( - dict->stream, ICLASS_ELITE_DICT_USER_NAME, FSAM_READ_WRITE, FSOM_OPEN_ALWAYS)) { - buffered_file_stream_close(dict->stream); - break; - } - } else if(dict_type == IclassStandardDictTypeFlipper) { - if(!buffered_file_stream_open( - dict->stream, - ICLASS_STANDARD_DICT_FLIPPER_NAME, - FSAM_READ, - FSOM_OPEN_EXISTING)) { - buffered_file_stream_close(dict->stream); - break; - } - } - - // Read total amount of keys - while(true) { //-V547 - if(!stream_read_line(dict->stream, next_line)) break; - if(furi_string_get_char(next_line, 0) == '#') continue; - if(furi_string_size(next_line) != ICLASS_ELITE_KEY_LINE_LEN) continue; - dict->total_keys++; - } - furi_string_reset(next_line); - stream_rewind(dict->stream); - - dict_loaded = true; - FURI_LOG_I(TAG, "Loaded dictionary with %lu keys", dict->total_keys); - } while(false); - - if(!dict_loaded) { //-V547 - buffered_file_stream_close(dict->stream); - free(dict); - dict = NULL; - } - - furi_record_close(RECORD_STORAGE); - furi_string_free(next_line); - - return dict; -} - -void iclass_elite_dict_free(IclassEliteDict* dict) { - furi_assert(dict); - furi_assert(dict->stream); - - buffered_file_stream_close(dict->stream); - stream_free(dict->stream); - free(dict); -} - -uint32_t iclass_elite_dict_get_total_keys(IclassEliteDict* dict) { - furi_assert(dict); - - return dict->total_keys; -} - -bool iclass_elite_dict_get_next_key(IclassEliteDict* dict, uint8_t* key) { - furi_assert(dict); - furi_assert(dict->stream); - - uint8_t key_byte_tmp = 0; - FuriString* next_line = furi_string_alloc(); - - bool key_read = false; - *key = 0ULL; - while(!key_read) { - if(!stream_read_line(dict->stream, next_line)) break; - if(furi_string_get_char(next_line, 0) == '#') continue; - if(furi_string_size(next_line) != ICLASS_ELITE_KEY_LINE_LEN) continue; - for(uint8_t i = 0; i < ICLASS_ELITE_KEY_LEN * 2; i += 2) { - args_char_to_hex( - furi_string_get_char(next_line, i), - furi_string_get_char(next_line, i + 1), - &key_byte_tmp); - key[i / 2] = key_byte_tmp; - } - key_read = true; - } - - furi_string_free(next_line); - return key_read; -} - -bool iclass_elite_dict_rewind(IclassEliteDict* dict) { - furi_assert(dict); - furi_assert(dict->stream); - - return stream_rewind(dict->stream); -} - -bool iclass_elite_dict_add_key(IclassEliteDict* dict, uint8_t* key) { - furi_assert(dict); - furi_assert(dict->stream); - - FuriString* key_str = furi_string_alloc(); - for(size_t i = 0; i < 6; i++) { - furi_string_cat_printf(key_str, "%02X", key[i]); - } - furi_string_cat_printf(key_str, "\n"); - - bool key_added = false; - do { - if(!stream_seek(dict->stream, 0, StreamOffsetFromEnd)) break; - if(!stream_insert_string(dict->stream, key_str)) break; - key_added = true; - } while(false); - - furi_string_free(key_str); - return key_added; -} diff --git a/applications/external/picopass/helpers/iclass_elite_dict.h b/applications/external/picopass/helpers/iclass_elite_dict.h deleted file mode 100644 index 150cd1b76a3..00000000000 --- a/applications/external/picopass/helpers/iclass_elite_dict.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -typedef enum { - IclassEliteDictTypeUser, - IclassEliteDictTypeFlipper, - IclassStandardDictTypeFlipper, -} IclassEliteDictType; - -typedef struct IclassEliteDict IclassEliteDict; - -bool iclass_elite_dict_check_presence(IclassEliteDictType dict_type); - -IclassEliteDict* iclass_elite_dict_alloc(IclassEliteDictType dict_type); - -void iclass_elite_dict_free(IclassEliteDict* dict); - -uint32_t iclass_elite_dict_get_total_keys(IclassEliteDict* dict); - -bool iclass_elite_dict_get_next_key(IclassEliteDict* dict, uint8_t* key); - -bool iclass_elite_dict_rewind(IclassEliteDict* dict); - -bool iclass_elite_dict_add_key(IclassEliteDict* dict, uint8_t* key); diff --git a/applications/external/picopass/icons/DolphinMafia_115x62.png b/applications/external/picopass/icons/DolphinMafia_115x62.png deleted file mode 100644 index 66fdb40ff2651916faed4a2ae1d564cafdbf7bcb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2504 zcmbVO3se(V8ji5Kg5ZmV3I#ewZHbsZB8b0=g#+k z_y7La$vcsnwa$(njyxXES*27&ad(Ehf@a%szx(??vFC0MMrAy=Img9%&ES885R5X2P@K{dB9p<$p?SQ()g~i~r4cNkC3JdH&i}sg6d%yza(--p8dMv@ zh!njtmnNcfH8EIj8V2M1)j>d@3E>C~1d9SDLpsSICOLnC7va{{Z80C1fUs$Deu(uz zAWj_#gi$mBNJXF!13?Io!6J#&-(L!@03Z+o#bAI~0tqEj1oTHFGGOY%=T4*XWF$)Q z`>C_ICpkZbWsQhfoSmI5%Jvgcv`#F6VOR`8Vh9p)2qBY0vZzT&GE1fz6a<6OdLyf+ zNWjX7YNwhuAF&nut zlTM%T7{|m!I$L;81?0JCCML&7h@%LG%A_%3 zO%`|J5~~^`5=Ij!OVKeDl|G%Q$Z2^1BoRS?PpqEAscc5@GXp|_vV@$^WlbUkA?_Ok zK?n#V5acTX5fGe&s<}GAQ5OB*z!a`e&iO^CEx1S+l}^!W3g`Ur;{!N`BvZ5jW@%VH?>OF2Tk@O zPGOv@&rGEfX|lv0Cxk2gKu)ie6Af#Vr9x}>!CI+Aiv@szVry$~6u{(al2-hTBEgTzn_D^}jklllIvu1V{Q`ig6OgP|0jI zN)sVEE|=@hm?j7H6PqgYzU5==|fB0<6@J90B?N8); z?B48M`Q6&q<>QYftD|a*tJ$!0YduA;TS}(23t@i9jJ}9E&d>+O-{j}lDtd6mP7wiU?pLh0* zla-TQ!!6f>9b(>jct-Z*@vzVmEjaUp9adYyRH)W#u&{1)0G7#K8z}OOe9Z4J`?k~5 z;u#n4^?R%GdBZDjly!H8xtVMF9ud_Q|CsUp%X4BI?jMd19&&9{QqgG_a)Rz9J*BH| z$zM9cbZYA6R(n(=QYD(cO(#Aoy6CQh;hG<}_gRz&>ZIovmNuT&Z9VwM8m5pu&$kG$ zvTJ!+pA|E6E-UBtJJrv;*XaRo7|Z#x4L(qON`UQa?6`jZqnkg3XliTEuJKo%PCa~M z@WlnE3u1ZRT?c;b@m&$07PGImr1km-TQZ8*DS|rZudw{x4R!5F9=$VOt{XWj(Y>BT zd-yG`a(KJ-o0Dfs8h&U=J*C(_ z=8hNq6aC?^r7wqGy5!v`zvX@KNEDDEpXqBVXiB`Z=eNZRgGG2tG`F;x~xDn9)G1Y@4Fl28Px*E!|ivy@~-8Lx%@`DyQ}?V z4f!BGF*jl}N~1D%!=YeZY6W)9lyDw_Uq#NDJx^=CJZDD2|CF# zA7Ixt{Z7BT8@4fZgFkI{D9fJxang<$JS``+d(*81cbB@prG*c!rZ)8U4y-<__Pt)Z zZ3lJfK;Y5eZHd?A3O-!mWX3$UChhmy)r@4iKkvyz(mdTtF7?TWn4`7t4=} zZ`OLe!fHzEo3eUH7jwVD-n?Xnx$AC<-H6`;RB2iYH9UO}ROfZkPOl32mRZ%`xW#FL zD@GqK${E&#=gzidc(qkxLZ^tk7u}u0Uu|;00}}A@rq4$9xE75>Hwj!4$Nk!`)YmDg{{4HeKCy?7Z85xPzg%Peucca}QJ6#D*z!+`G0ZOj diff --git a/applications/external/picopass/icons/DolphinNice_96x59.png b/applications/external/picopass/icons/DolphinNice_96x59.png deleted file mode 100644 index a299d3630239b4486e249cc501872bed5996df3b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2459 zcmbVO3s4i+8V(M(gEFORwSrA`4O0uPn|M|5y* zB*aMDxC&7(gP9JN;POOi-9khrC>Z9YJs2U!LnVcQEEC0fDtKo&ILlzb30%M}3J^;~ zv7RzcsilOs4Mq@tD*&R;!LMSk2A~{(`HK9|hQBqEX)3sQr9Je6SZU*F-^fD-p+~Hs; zHLkO%v?>ZoxEv+F#whudr%615FkA0DYR0tMEo}3OOY#xecLWe>xV?u5KtSmC^ z7)Fmj6gjfKstiEV-*Cxbbb+&rRWuI_rBJ)ybs_f1Rn&f2>q3pYwI^|J(hdn{j{0EZIm_F zpIyIWLsRUgOItR-dUbVd|6Zo=_BU_Tj4|{{jxO#=JH4o8er(5{!nZD_j4}MH&zh~9 zVLC~y(0-D6GO0ghZD8BYzP?o{>22~lT6^d@X{SwQ8vrNY-PPIMajIwC)`s14Ep72@ zeq7YOzM`?U{+W)ocXBr`eSOcpk?Rxc=ou5&)fWW|pD};-Z0mvk9}=&`Rb&y<77W~a z(>6YM;6Y5aIU~JKZ}mQZynKHiSTQ#Bczn@&jTiN^?vPJ(jhm7cXLx0oum5P$`TceG zU+wR;OO^)8CVlnM)5p$CO&e94KJt>HccCaHGusmW_b`T6m| z-R6V6Db1pErTot?^d22ojm+2>_)FbD`_+WbDGMx9f@hO27maS2`csiV(D&Fs`PS2& zvrq18du_&zXID(!KIxsU$)iuTYuZ?zmYiP&n&i@Be{IdbS-jA2c0QAlu5NXQv_0K< z3Hvs4eeu6B7yD&CNT~gIkMV&UkRU=V!iQ(+_(O&u^ah$+s{_yn(yBYeD40HeU{xGsIT6W Zfq!wOp!Q<>&pI=m5)b(dHL6nbwD9yPZ!4!j_b)k${QZ;XFmLn zjqNsD+YPq1J8W%_*aXBie!OR3*tC!PwU_7Q9H4U564!{5l*E!$tK_0oAjM#0U}UIk zV5)0q5@Kj%Wo%$&Y@uynU}a#izM}XGiiX_$l+3hBs0L%8o)7~QD^p9LQiz6svOM}g O4Gf;HelF{r5}E+GUQp8j diff --git a/applications/external/picopass/icons/RFIDDolphinReceive_97x61.png b/applications/external/picopass/icons/RFIDDolphinReceive_97x61.png deleted file mode 100644 index e1f5f9f8017b49ab1ea78a9394ed8ea7684e3083..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1421 zcmaJ>eQXnD7{Agv#{x2p_yIvKhl^r%z3;AfTbW%yMuEcUiZn{X?&IxtS=&4BZnPU= zlx>i)WI>$aQvw7<5XB`bIuRj@1WD9@Lr2D(5=YPwGc*zsSTf&kEAj{7lDqeL-}m`F zpTFm}Rj;U;Sva>4L6DijCB86RMfkc4?C{(9_MQ1~dCu}jtr{(6r9=ZD9z~M?8cc|F zAPhvM>5U7Z96{_EH4?R=q2+?CB^+W_$B|Cx5RD+^6=_|R8-RsMpiWJ?vC&g!FjQ6C z*cvWGhIB8eSC=#!pr(06L~d@7c?GLjjFzVbXdnSB5ltuJNmEF>u?f2Zl(WYKhEAwh z4Q^~QsA#Af^=bw{oemP0Nz#dy@(x9mL|KwbP@1GEf@BGb#Ys|Nc!6cnsRx7Z3?(Ln zeSs-waOcMAElU>&B9%%xQj9}0>IjPGd4i+~n#Q39ZZ;(?F^wn9g*gj8V9JK7TdI~s zvlc~3YqZ=L40SSxgdPgrH=H!5Dg|psq(z;e93+uQWD}dvHmxxDKa7WJn~^3R5Mf|y zjfM;x5?h!9!{R;KQC1N~Bdj!3*cCDE)8xhkNLoRk8-q6vMO6faWjLq8D>(0>TsY@s zOL2+gT{ut5lFP+&G;q>6I}gJ}uK`3$Ga{N6&(WZ|Ub8f_Uei&p7kz1snpCuuxhUJA$%K8tP}c(` zU}y<+qQrvwF!u}>V`HR<(=n36VBt1B^`_fx&WPzU_AMY;oo7=&mIg2tu^u1f5 z)@y#lGg2HF{icooYxXeey6HJl+%===Q-Yg*f$J(< z+gbGCvVprluc__jmS6m=F>l7JjJ;Cb^sMdho~B4w{1|(u#k_H5R;4;`zs)u0gC*%S zI_>C5rsHbY>U}-r=8b&^Mh7zat>Eaqs$E;p%^t}^&M*C`d_!V*2g<#^ZLQq9;N6x= zv^)OzpYh#+OwHKfQ+kHHZreNi()*6Nw&PX5?kxF@U2EB*+}LH?toC1`{oRjksXb78 zx8u;V!Qv~6!ySjp4u16f-y8F;3}d=*b!=ao^)Gw)nS({6qa!CbyuwrWMvi?_zz4rL rb-KI#{JuTj%qEZPotyLfwj*}ruaRky;O7Gyvp>k7e}(TvWo_$!Vg&g_ diff --git a/applications/external/picopass/icons/RFIDDolphinSend_97x61.png b/applications/external/picopass/icons/RFIDDolphinSend_97x61.png deleted file mode 100644 index 380a970d9004cba5520560fd9aa24aa42924e2a1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1418 zcmaJ>eQXnD7{6`EHeeAZ8ii;s2g4C}y=!}S>mBQswzrLLl$EYRfoyOe?`_T2-g$Sk z9pb2CPQ(a0XM$+dKhO~O0nx;1L}kUOGdh8Ve-^?LG)CD7lOfXp&bQl&{IPJ!-TS=n z`~05I-*YefH&^B@S+xW~kUZ~3J^)t%zRsL1_&wM?{Wx46Gs{C}t*V$YK?jISRz-k% zBSHfR06}hjW(brZNLC^o44EO{CQec#79pi$iAOYuMv#)SxF$$Vz(hsR5RN*rYhQeg zp<&sHZKHjpPxFAr@WwqlsNJ(UDD7#ISQ#rTMN8rwG!Ox%fW{-uQG<&+v01wulvBq9 zhR&*(O-^hssF2T(dQ=^tjD^G{l4Q_g)*=g{AcBJ%MzrGu-R~^fg7z+Q;6eHV@=uu4-82U zYi3xDqA81lsJ56+42C+FLqzlW?i!97^Ob@%BjSQaSS=(GiKG&n)i%rk_&3wK+`#f1_%uMx&~s9uHc$EgY5An6W<9p}B;4 zpogCYa)qu&(Ag4m;RW0?c3PnnQowBrN#lw@*>TY-L0(ZbMKQG9>QDeSkC*Q$-5f{Z z2~0stN5WYZfssX-#AS)L;{r{IxG2~K90(F6+7!i3SxJn5ArdLp+{2>u5u|2HygL+d zb9byj6wZ} zqrIB@aESUiV~B&zwY0sUci%;mf;cmkA+7cD0^$ih9{f{w;v_DJ`sY;R`f3( z?7BXf_vMbW zuU1_w753GAG_~{axB58aI?KM!#N|b)zyZV)ZU9QaOj9KuN$fX{&>fy=f`f8Io+CbZIMpovDCx1HL z?$&C^=R1DyispWLc%|FSKGs*ccUMOLz=7=zt7r7(!|y7;X08;c-@aJ>V5pwIR`S;) wTk7+73`}?J{<7dJ@~ diff --git a/applications/external/picopass/lib/loclass/optimized_cipher.c b/applications/external/picopass/lib/loclass/optimized_cipher.c deleted file mode 100644 index 94df07bae8d..00000000000 --- a/applications/external/picopass/lib/loclass/optimized_cipher.c +++ /dev/null @@ -1,299 +0,0 @@ -//----------------------------------------------------------------------------- -// Borrowed initially from https://github.com/holiman/loclass -// Copyright (C) 2014 Martin Holst Swende -// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// See LICENSE.txt for the text of the license. -//----------------------------------------------------------------------------- -// WARNING -// -// THIS CODE IS CREATED FOR EXPERIMENTATION AND EDUCATIONAL USE ONLY. -// -// USAGE OF THIS CODE IN OTHER WAYS MAY INFRINGE UPON THE INTELLECTUAL -// PROPERTY OF OTHER PARTIES, SUCH AS INSIDE SECURE AND HID GLOBAL, -// AND MAY EXPOSE YOU TO AN INFRINGEMENT ACTION FROM THOSE PARTIES. -// -// THIS CODE SHOULD NEVER BE USED TO INFRINGE PATENTS OR INTELLECTUAL PROPERTY RIGHTS. -//----------------------------------------------------------------------------- -// It is a reconstruction of the cipher engine used in iClass, and RFID techology. -// -// The implementation is based on the work performed by -// Flavio D. Garcia, Gerhard de Koning Gans, Roel Verdult and -// Milosch Meriac in the paper "Dismantling IClass". -//----------------------------------------------------------------------------- -/* - This file contains an optimized version of the MAC-calculation algorithm. Some measurements on - a std laptop showed it runs in about 1/3 of the time: - - Std: 0.428962 - Opt: 0.151609 - - Additionally, it is self-reliant, not requiring e.g. bitstreams from the cipherutils, thus can - be easily dropped into a code base. - - The optimizations have been performed in the following steps: - * Parameters passed by reference instead of by value. - * Iteration instead of recursion, un-nesting recursive loops into for-loops. - * Handling of bytes instead of individual bits, for less shuffling and masking - * Less creation of "objects", structs, and instead reuse of alloc:ed memory - * Inlining some functions via #define:s - - As a consequence, this implementation is less generic. Also, I haven't bothered documenting this. - For a thorough documentation, check out the MAC-calculation within cipher.c instead. - - -- MHS 2015 -**/ - -/** - - The runtime of opt_doTagMAC_2() with the MHS optimized version was 403 microseconds on Proxmark3. - This was still to slow for some newer readers which didn't want to wait that long. - - Further optimizations to speedup the MAC calculations: - * Optimized opt_Tt logic - * Look up table for opt_select - * Removing many unnecessary bit maskings (& 0x1) - * updating state in place instead of alternating use of a second state structure - * remove the necessity to reverse bits of input and output bytes - - opt_doTagMAC_2() now completes in 270 microseconds. - - -- piwi 2019 -**/ - -/** - add the possibility to do iCLASS on device only - -- iceman 2020 -**/ - -#include "optimized_cipher.h" -#include "optimized_elite.h" -#include "optimized_ikeys.h" -#include "optimized_cipherutils.h" - -static const uint8_t loclass_opt_select_LUT[256] = { - 00, 03, 02, 01, 02, 03, 00, 01, 04, 07, 07, 04, 06, 07, 05, 04, 01, 02, 03, 00, 02, 03, 00, 01, - 05, 06, 06, 05, 06, 07, 05, 04, 06, 05, 04, 07, 04, 05, 06, 07, 06, 05, 05, 06, 04, 05, 07, 06, - 07, 04, 05, 06, 04, 05, 06, 07, 07, 04, 04, 07, 04, 05, 07, 06, 06, 05, 04, 07, 04, 05, 06, 07, - 02, 01, 01, 02, 00, 01, 03, 02, 03, 00, 01, 02, 00, 01, 02, 03, 07, 04, 04, 07, 04, 05, 07, 06, - 00, 03, 02, 01, 02, 03, 00, 01, 00, 03, 03, 00, 02, 03, 01, 00, 05, 06, 07, 04, 06, 07, 04, 05, - 05, 06, 06, 05, 06, 07, 05, 04, 02, 01, 00, 03, 00, 01, 02, 03, 06, 05, 05, 06, 04, 05, 07, 06, - 03, 00, 01, 02, 00, 01, 02, 03, 07, 04, 04, 07, 04, 05, 07, 06, 02, 01, 00, 03, 00, 01, 02, 03, - 02, 01, 01, 02, 00, 01, 03, 02, 03, 00, 01, 02, 00, 01, 02, 03, 03, 00, 00, 03, 00, 01, 03, 02, - 04, 07, 06, 05, 06, 07, 04, 05, 00, 03, 03, 00, 02, 03, 01, 00, 01, 02, 03, 00, 02, 03, 00, 01, - 05, 06, 06, 05, 06, 07, 05, 04, 04, 07, 06, 05, 06, 07, 04, 05, 04, 07, 07, 04, 06, 07, 05, 04, - 01, 02, 03, 00, 02, 03, 00, 01, 01, 02, 02, 01, 02, 03, 01, 00}; - -/********************** the table above has been generated with this code: ******** -#include "util.h" -static void init_opt_select_LUT(void) { - for (int r = 0; r < 256; r++) { - uint8_t r_ls2 = r << 2; - uint8_t r_and_ls2 = r & r_ls2; - uint8_t r_or_ls2 = r | r_ls2; - uint8_t z0 = (r_and_ls2 >> 5) ^ ((r & ~r_ls2) >> 4) ^ ( r_or_ls2 >> 3); - uint8_t z1 = (r_or_ls2 >> 6) ^ ( r_or_ls2 >> 1) ^ (r >> 5) ^ r; - uint8_t z2 = ((r & ~r_ls2) >> 4) ^ (r_and_ls2 >> 3) ^ r; - loclass_opt_select_LUT[r] = (z0 & 4) | (z1 & 2) | (z2 & 1); - } - print_result("", loclass_opt_select_LUT, 256); -} -***********************************************************************************/ - -#define loclass_opt__select(x, y, r) \ - (4 & ((((r) & ((r) << 2)) >> 5) ^ (((r) & ~((r) << 2)) >> 4) ^ (((r) | (r) << 2) >> 3))) | \ - (2 & ((((r) | (r) << 2) >> 6) ^ (((r) | (r) << 2) >> 1) ^ ((r) >> 5) ^ (r) ^ (((x) ^ (y)) << 1))) | \ - (1 & ((((r) & ~((r) << 2)) >> 4) ^ (((r) & ((r) << 2)) >> 3) ^ (r) ^ (x))) - -static void loclass_opt_successor(const uint8_t* k, LoclassState_t* s, uint8_t y) { - uint16_t Tt = s->t & 0xc533; - Tt = Tt ^ (Tt >> 1); - Tt = Tt ^ (Tt >> 4); - Tt = Tt ^ (Tt >> 10); - Tt = Tt ^ (Tt >> 8); - - s->t = (s->t >> 1); - s->t |= (Tt ^ (s->r >> 7) ^ (s->r >> 3)) << 15; - - uint8_t opt_B = s->b; - opt_B ^= s->b >> 6; - opt_B ^= s->b >> 5; - opt_B ^= s->b >> 4; - - s->b = s->b >> 1; - s->b |= (opt_B ^ s->r) << 7; - - uint8_t opt_select = loclass_opt_select_LUT[s->r] & 0x04; - opt_select |= (loclass_opt_select_LUT[s->r] ^ ((Tt ^ y) << 1)) & 0x02; - opt_select |= (loclass_opt_select_LUT[s->r] ^ Tt) & 0x01; - - uint8_t r = s->r; - s->r = (k[opt_select] ^ s->b) + s->l; - s->l = s->r + r; -} - -static void loclass_opt_suc( - const uint8_t* k, - LoclassState_t* s, - const uint8_t* in, - uint8_t length, - bool add32Zeroes) { - for(int i = 0; i < length; i++) { - uint8_t head = in[i]; - for(int j = 0; j < 8; j++) { - loclass_opt_successor(k, s, head); - head >>= 1; - } - } - //For tag MAC, an additional 32 zeroes - if(add32Zeroes) { - for(int i = 0; i < 16; i++) { - loclass_opt_successor(k, s, 0); - loclass_opt_successor(k, s, 0); - } - } -} - -static void loclass_opt_output(const uint8_t* k, LoclassState_t* s, uint8_t* buffer) { - for(uint8_t times = 0; times < 4; times++) { - uint8_t bout = 0; - bout |= (s->r & 0x4) >> 2; - loclass_opt_successor(k, s, 0); - bout |= (s->r & 0x4) >> 1; - loclass_opt_successor(k, s, 0); - bout |= (s->r & 0x4); - loclass_opt_successor(k, s, 0); - bout |= (s->r & 0x4) << 1; - loclass_opt_successor(k, s, 0); - bout |= (s->r & 0x4) << 2; - loclass_opt_successor(k, s, 0); - bout |= (s->r & 0x4) << 3; - loclass_opt_successor(k, s, 0); - bout |= (s->r & 0x4) << 4; - loclass_opt_successor(k, s, 0); - bout |= (s->r & 0x4) << 5; - loclass_opt_successor(k, s, 0); - buffer[times] = bout; - } -} - -static void loclass_opt_MAC(uint8_t* k, uint8_t* input, uint8_t* out) { - LoclassState_t _init = { - ((k[0] ^ 0x4c) + 0xEC) & 0xFF, // l - ((k[0] ^ 0x4c) + 0x21) & 0xFF, // r - 0x4c, // b - 0xE012 // t - }; - - loclass_opt_suc(k, &_init, input, 12, false); - loclass_opt_output(k, &_init, out); -} - -static void loclass_opt_MAC_N(uint8_t* k, uint8_t* input, uint8_t in_size, uint8_t* out) { - LoclassState_t _init = { - ((k[0] ^ 0x4c) + 0xEC) & 0xFF, // l - ((k[0] ^ 0x4c) + 0x21) & 0xFF, // r - 0x4c, // b - 0xE012 // t - }; - - loclass_opt_suc(k, &_init, input, in_size, false); - loclass_opt_output(k, &_init, out); -} - -void loclass_opt_doReaderMAC(uint8_t* cc_nr_p, uint8_t* div_key_p, uint8_t mac[4]) { - uint8_t dest[] = {0, 0, 0, 0, 0, 0, 0, 0}; - loclass_opt_MAC(div_key_p, cc_nr_p, dest); - memcpy(mac, dest, 4); -} - -void loclass_opt_doReaderMAC_2( - LoclassState_t _init, - uint8_t* nr, - uint8_t mac[4], - const uint8_t* div_key_p) { - loclass_opt_suc(div_key_p, &_init, nr, 4, false); - loclass_opt_output(div_key_p, &_init, mac); -} - -void loclass_doMAC_N(uint8_t* in_p, uint8_t in_size, uint8_t* div_key_p, uint8_t mac[4]) { - uint8_t dest[] = {0, 0, 0, 0, 0, 0, 0, 0}; - loclass_opt_MAC_N(div_key_p, in_p, in_size, dest); - memcpy(mac, dest, 4); -} - -void loclass_opt_doTagMAC(uint8_t* cc_p, const uint8_t* div_key_p, uint8_t mac[4]) { - LoclassState_t _init = { - ((div_key_p[0] ^ 0x4c) + 0xEC) & 0xFF, // l - ((div_key_p[0] ^ 0x4c) + 0x21) & 0xFF, // r - 0x4c, // b - 0xE012 // t - }; - loclass_opt_suc(div_key_p, &_init, cc_p, 12, true); - loclass_opt_output(div_key_p, &_init, mac); -} - -/** - * The tag MAC can be divided (both can, but no point in dividing the reader mac) into - * two functions, since the first 8 bytes are known, we can pre-calculate the state - * reached after feeding CC to the cipher. - * @param cc_p - * @param div_key_p - * @return the cipher state - */ -LoclassState_t loclass_opt_doTagMAC_1(uint8_t* cc_p, const uint8_t* div_key_p) { - LoclassState_t _init = { - ((div_key_p[0] ^ 0x4c) + 0xEC) & 0xFF, // l - ((div_key_p[0] ^ 0x4c) + 0x21) & 0xFF, // r - 0x4c, // b - 0xE012 // t - }; - loclass_opt_suc(div_key_p, &_init, cc_p, 8, false); - return _init; -} - -/** - * The second part of the tag MAC calculation, since the CC is already calculated into the state, - * this function is fed only the NR, and internally feeds the remaining 32 0-bits to generate the tag - * MAC response. - * @param _init - precalculated cipher state - * @param nr - the reader challenge - * @param mac - where to store the MAC - * @param div_key_p - the key to use - */ -void loclass_opt_doTagMAC_2( - LoclassState_t _init, - uint8_t* nr, - uint8_t mac[4], - const uint8_t* div_key_p) { - loclass_opt_suc(div_key_p, &_init, nr, 4, true); - loclass_opt_output(div_key_p, &_init, mac); -} - -void loclass_iclass_calc_div_key(uint8_t* csn, uint8_t* key, uint8_t* div_key, bool elite) { - if(elite) { - uint8_t keytable[128] = {0}; - uint8_t key_index[8] = {0}; - uint8_t key_sel[8] = {0}; - uint8_t key_sel_p[8] = {0}; - loclass_hash2(key, keytable); - loclass_hash1(csn, key_index); - for(uint8_t i = 0; i < 8; i++) key_sel[i] = keytable[key_index[i]]; - - //Permute from iclass format to standard format - loclass_permutekey_rev(key_sel, key_sel_p); - loclass_diversifyKey(csn, key_sel_p, div_key); - } else { - loclass_diversifyKey(csn, key, div_key); - } -} diff --git a/applications/external/picopass/lib/loclass/optimized_cipher.h b/applications/external/picopass/lib/loclass/optimized_cipher.h deleted file mode 100644 index 2158f0acf75..00000000000 --- a/applications/external/picopass/lib/loclass/optimized_cipher.h +++ /dev/null @@ -1,98 +0,0 @@ -//----------------------------------------------------------------------------- -// Borrowed initially from https://github.com/holiman/loclass -// More recently from https://github.com/RfidResearchGroup/proxmark3 -// Copyright (C) 2014 Martin Holst Swende -// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// See LICENSE.txt for the text of the license. -//----------------------------------------------------------------------------- -// WARNING -// -// THIS CODE IS CREATED FOR EXPERIMENTATION AND EDUCATIONAL USE ONLY. -// -// USAGE OF THIS CODE IN OTHER WAYS MAY INFRINGE UPON THE INTELLECTUAL -// PROPERTY OF OTHER PARTIES, SUCH AS INSIDE SECURE AND HID GLOBAL, -// AND MAY EXPOSE YOU TO AN INFRINGEMENT ACTION FROM THOSE PARTIES. -// -// THIS CODE SHOULD NEVER BE USED TO INFRINGE PATENTS OR INTELLECTUAL PROPERTY RIGHTS. -//----------------------------------------------------------------------------- -// It is a reconstruction of the cipher engine used in iClass, and RFID techology. -// -// The implementation is based on the work performed by -// Flavio D. Garcia, Gerhard de Koning Gans, Roel Verdult and -// Milosch Meriac in the paper "Dismantling IClass". -//----------------------------------------------------------------------------- -#ifndef OPTIMIZED_CIPHER_H -#define OPTIMIZED_CIPHER_H -#include -#include -#include -#include - -/** -* Definition 1 (Cipher state). A cipher state of iClass s is an element of F 40/2 -* consisting of the following four components: -* 1. the left register l = (l 0 . . . l 7 ) ∈ F 8/2 ; -* 2. the right register r = (r 0 . . . r 7 ) ∈ F 8/2 ; -* 3. the top register t = (t 0 . . . t 15 ) ∈ F 16/2 . -* 4. the bottom register b = (b 0 . . . b 7 ) ∈ F 8/2 . -**/ -typedef struct { - uint8_t l; - uint8_t r; - uint8_t b; - uint16_t t; -} LoclassState_t; - -/** The reader MAC is MAC(key, CC * NR ) - **/ -void loclass_opt_doReaderMAC(uint8_t* cc_nr_p, uint8_t* div_key_p, uint8_t mac[4]); - -void loclass_opt_doReaderMAC_2( - LoclassState_t _init, - uint8_t* nr, - uint8_t mac[4], - const uint8_t* div_key_p); - -/** - * The tag MAC is MAC(key, CC * NR * 32x0)) - */ -void loclass_opt_doTagMAC(uint8_t* cc_p, const uint8_t* div_key_p, uint8_t mac[4]); - -/** - * The tag MAC can be divided (both can, but no point in dividing the reader mac) into - * two functions, since the first 8 bytes are known, we can pre-calculate the state - * reached after feeding CC to the cipher. - * @param cc_p - * @param div_key_p - * @return the cipher state - */ -LoclassState_t loclass_opt_doTagMAC_1(uint8_t* cc_p, const uint8_t* div_key_p); -/** - * The second part of the tag MAC calculation, since the CC is already calculated into the state, - * this function is fed only the NR, and internally feeds the remaining 32 0-bits to generate the tag - * MAC response. - * @param _init - precalculated cipher state - * @param nr - the reader challenge - * @param mac - where to store the MAC - * @param div_key_p - the key to use - */ -void loclass_opt_doTagMAC_2( - LoclassState_t _init, - uint8_t* nr, - uint8_t mac[4], - const uint8_t* div_key_p); - -void loclass_doMAC_N(uint8_t* in_p, uint8_t in_size, uint8_t* div_key_p, uint8_t mac[4]); -void loclass_iclass_calc_div_key(uint8_t* csn, uint8_t* key, uint8_t* div_key, bool elite); -#endif // OPTIMIZED_CIPHER_H diff --git a/applications/external/picopass/lib/loclass/optimized_cipherutils.c b/applications/external/picopass/lib/loclass/optimized_cipherutils.c deleted file mode 100644 index e6a87c4a760..00000000000 --- a/applications/external/picopass/lib/loclass/optimized_cipherutils.c +++ /dev/null @@ -1,136 +0,0 @@ -//----------------------------------------------------------------------------- -// Borrowed initially from https://github.com/holiman/loclass -// Copyright (C) 2014 Martin Holst Swende -// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// See LICENSE.txt for the text of the license. -//----------------------------------------------------------------------------- -// WARNING -// -// THIS CODE IS CREATED FOR EXPERIMENTATION AND EDUCATIONAL USE ONLY. -// -// USAGE OF THIS CODE IN OTHER WAYS MAY INFRINGE UPON THE INTELLECTUAL -// PROPERTY OF OTHER PARTIES, SUCH AS INSIDE SECURE AND HID GLOBAL, -// AND MAY EXPOSE YOU TO AN INFRINGEMENT ACTION FROM THOSE PARTIES. -// -// THIS CODE SHOULD NEVER BE USED TO INFRINGE PATENTS OR INTELLECTUAL PROPERTY RIGHTS. -//----------------------------------------------------------------------------- -// It is a reconstruction of the cipher engine used in iClass, and RFID techology. -// -// The implementation is based on the work performed by -// Flavio D. Garcia, Gerhard de Koning Gans, Roel Verdult and -// Milosch Meriac in the paper "Dismantling IClass". -//----------------------------------------------------------------------------- -#include "optimized_cipherutils.h" -#include - -/** - * - * @brief Return and remove the first bit (x0) in the stream : - * @param stream - * @return - */ -bool loclass_headBit(LoclassBitstreamIn_t* stream) { - int bytepos = stream->position >> 3; // divide by 8 - int bitpos = (stream->position++) & 7; // mask out 00000111 - return (*(stream->buffer + bytepos) >> (7 - bitpos)) & 1; -} -/** - * @brief Return and remove the last bit (xn) in the stream: - * @param stream - * @return - */ -bool loclass_tailBit(LoclassBitstreamIn_t* stream) { - int bitpos = stream->numbits - 1 - (stream->position++); - - int bytepos = bitpos >> 3; - bitpos &= 7; - return (*(stream->buffer + bytepos) >> (7 - bitpos)) & 1; -} -/** - * @brief Pushes bit onto the stream - * @param stream - * @param bit - */ -void loclass_pushBit(LoclassBitstreamOut_t* stream, bool bit) { - int bytepos = stream->position >> 3; // divide by 8 - int bitpos = stream->position & 7; - *(stream->buffer + bytepos) |= (bit) << (7 - bitpos); - stream->position++; - stream->numbits++; -} - -/** - * @brief Pushes the lower six bits onto the stream - * as b0 b1 b2 b3 b4 b5 b6 - * @param stream - * @param bits - */ -void loclass_push6bits(LoclassBitstreamOut_t* stream, uint8_t bits) { - loclass_pushBit(stream, bits & 0x20); - loclass_pushBit(stream, bits & 0x10); - loclass_pushBit(stream, bits & 0x08); - loclass_pushBit(stream, bits & 0x04); - loclass_pushBit(stream, bits & 0x02); - loclass_pushBit(stream, bits & 0x01); -} - -/** - * @brief loclass_bitsLeft - * @param stream - * @return number of bits left in stream - */ -int loclass_bitsLeft(LoclassBitstreamIn_t* stream) { - return stream->numbits - stream->position; -} -/** - * @brief numBits - * @param stream - * @return Number of bits stored in stream - */ -void loclass_x_num_to_bytes(uint64_t n, size_t len, uint8_t* dest) { - while(len--) { - dest[len] = (uint8_t)n; - n >>= 8; - } -} - -uint64_t loclass_x_bytes_to_num(uint8_t* src, size_t len) { - uint64_t num = 0; - while(len--) { - num = (num << 8) | (*src); - src++; - } - return num; -} - -uint8_t loclass_reversebytes(uint8_t b) { - b = (b & 0xF0) >> 4 | (b & 0x0F) << 4; - b = (b & 0xCC) >> 2 | (b & 0x33) << 2; - b = (b & 0xAA) >> 1 | (b & 0x55) << 1; - return b; -} - -void loclass_reverse_arraybytes(uint8_t* arr, size_t len) { - uint8_t i; - for(i = 0; i < len; i++) { - arr[i] = loclass_reversebytes(arr[i]); - } -} - -void loclass_reverse_arraycopy(uint8_t* arr, uint8_t* dest, size_t len) { - uint8_t i; - for(i = 0; i < len; i++) { - dest[i] = loclass_reversebytes(arr[i]); - } -} diff --git a/applications/external/picopass/lib/loclass/optimized_cipherutils.h b/applications/external/picopass/lib/loclass/optimized_cipherutils.h deleted file mode 100644 index 05b6820792d..00000000000 --- a/applications/external/picopass/lib/loclass/optimized_cipherutils.h +++ /dev/null @@ -1,64 +0,0 @@ -//----------------------------------------------------------------------------- -// Borrowed initially from https://github.com/holiman/loclass -// More recently from https://github.com/RfidResearchGroup/proxmark3 -// Copyright (C) 2014 Martin Holst Swende -// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// See LICENSE.txt for the text of the license. -//----------------------------------------------------------------------------- -// WARNING -// -// THIS CODE IS CREATED FOR EXPERIMENTATION AND EDUCATIONAL USE ONLY. -// -// USAGE OF THIS CODE IN OTHER WAYS MAY INFRINGE UPON THE INTELLECTUAL -// PROPERTY OF OTHER PARTIES, SUCH AS INSIDE SECURE AND HID GLOBAL, -// AND MAY EXPOSE YOU TO AN INFRINGEMENT ACTION FROM THOSE PARTIES. -// -// THIS CODE SHOULD NEVER BE USED TO INFRINGE PATENTS OR INTELLECTUAL PROPERTY RIGHTS. -//----------------------------------------------------------------------------- -// It is a reconstruction of the cipher engine used in iClass, and RFID techology. -// -// The implementation is based on the work performed by -// Flavio D. Garcia, Gerhard de Koning Gans, Roel Verdult and -// Milosch Meriac in the paper "Dismantling IClass". -//----------------------------------------------------------------------------- -#ifndef CIPHERUTILS_H -#define CIPHERUTILS_H -#include -#include -#include - -typedef struct { - uint8_t* buffer; - uint8_t numbits; - uint8_t position; -} LoclassBitstreamIn_t; - -typedef struct { - uint8_t* buffer; - uint8_t numbits; - uint8_t position; -} LoclassBitstreamOut_t; - -bool loclass_headBit(LoclassBitstreamIn_t* stream); -bool loclass_tailBit(LoclassBitstreamIn_t* stream); -void loclass_pushBit(LoclassBitstreamOut_t* stream, bool bit); -int loclass_bitsLeft(LoclassBitstreamIn_t* stream); - -void loclass_push6bits(LoclassBitstreamOut_t* stream, uint8_t bits); -void loclass_x_num_to_bytes(uint64_t n, size_t len, uint8_t* dest); -uint64_t loclass_x_bytes_to_num(uint8_t* src, size_t len); -uint8_t loclass_reversebytes(uint8_t b); -void loclass_reverse_arraybytes(uint8_t* arr, size_t len); -void loclass_reverse_arraycopy(uint8_t* arr, uint8_t* dest, size_t len); -#endif // CIPHERUTILS_H diff --git a/applications/external/picopass/lib/loclass/optimized_elite.c b/applications/external/picopass/lib/loclass/optimized_elite.c deleted file mode 100644 index 34e98706026..00000000000 --- a/applications/external/picopass/lib/loclass/optimized_elite.c +++ /dev/null @@ -1,232 +0,0 @@ -//----------------------------------------------------------------------------- -// Borrowed initially from https://github.com/holiman/loclass -// Copyright (C) 2014 Martin Holst Swende -// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// See LICENSE.txt for the text of the license. -//----------------------------------------------------------------------------- -// WARNING -// -// THIS CODE IS CREATED FOR EXPERIMENTATION AND EDUCATIONAL USE ONLY. -// -// USAGE OF THIS CODE IN OTHER WAYS MAY INFRINGE UPON THE INTELLECTUAL -// PROPERTY OF OTHER PARTIES, SUCH AS INSIDE SECURE AND HID GLOBAL, -// AND MAY EXPOSE YOU TO AN INFRINGEMENT ACTION FROM THOSE PARTIES. -// -// THIS CODE SHOULD NEVER BE USED TO INFRINGE PATENTS OR INTELLECTUAL PROPERTY RIGHTS. -//----------------------------------------------------------------------------- -// It is a reconstruction of the cipher engine used in iClass, and RFID techology. -// -// The implementation is based on the work performed by -// Flavio D. Garcia, Gerhard de Koning Gans, Roel Verdult and -// Milosch Meriac in the paper "Dismantling IClass". -//----------------------------------------------------------------------------- -#include "optimized_elite.h" - -#include -#include -#include -#include -#include "optimized_ikeys.h" - -/** - * @brief Permutes a key from standard NIST format to Iclass specific format - * from http://www.proxmark.org/forum/viewtopic.php?pid=11220#p11220 - * - * If you loclass_permute [6c 8d 44 f9 2a 2d 01 bf] you get [8a 0d b9 88 bb a7 90 ea] as shown below. - * - * 1 0 1 1 1 1 1 1 bf - * 0 0 0 0 0 0 0 1 01 - * 0 0 1 0 1 1 0 1 2d - * 0 0 1 0 1 0 1 0 2a - * 1 1 1 1 1 0 0 1 f9 - * 0 1 0 0 0 1 0 0 44 - * 1 0 0 0 1 1 0 1 8d - * 0 1 1 0 1 1 0 0 6c - * - * 8 0 b 8 b a 9 e - * a d 9 8 b 7 0 a - * - * @param key - * @param dest - */ -void loclass_permutekey(const uint8_t key[8], uint8_t dest[8]) { - int i; - for(i = 0; i < 8; i++) { - dest[i] = (((key[7] & (0x80 >> i)) >> (7 - i)) << 7) | - (((key[6] & (0x80 >> i)) >> (7 - i)) << 6) | - (((key[5] & (0x80 >> i)) >> (7 - i)) << 5) | - (((key[4] & (0x80 >> i)) >> (7 - i)) << 4) | - (((key[3] & (0x80 >> i)) >> (7 - i)) << 3) | - (((key[2] & (0x80 >> i)) >> (7 - i)) << 2) | - (((key[1] & (0x80 >> i)) >> (7 - i)) << 1) | - (((key[0] & (0x80 >> i)) >> (7 - i)) << 0); - } -} -/** - * Permutes a key from iclass specific format to NIST format - * @brief loclass_permutekey_rev - * @param key - * @param dest - */ -void loclass_permutekey_rev(const uint8_t key[8], uint8_t dest[8]) { - int i; - for(i = 0; i < 8; i++) { - dest[7 - i] = (((key[0] & (0x80 >> i)) >> (7 - i)) << 7) | - (((key[1] & (0x80 >> i)) >> (7 - i)) << 6) | - (((key[2] & (0x80 >> i)) >> (7 - i)) << 5) | - (((key[3] & (0x80 >> i)) >> (7 - i)) << 4) | - (((key[4] & (0x80 >> i)) >> (7 - i)) << 3) | - (((key[5] & (0x80 >> i)) >> (7 - i)) << 2) | - (((key[6] & (0x80 >> i)) >> (7 - i)) << 1) | - (((key[7] & (0x80 >> i)) >> (7 - i)) << 0); - } -} - -/** - * Helper function for loclass_hash1 - * @brief loclass_rr - * @param val - * @return - */ -static uint8_t loclass_rr(uint8_t val) { - return val >> 1 | ((val & 1) << 7); -} - -/** - * Helper function for loclass_hash1 - * @brief rl - * @param val - * @return - */ -static uint8_t loclass_rl(uint8_t val) { - return val << 1 | ((val & 0x80) >> 7); -} - -/** - * Helper function for loclass_hash1 - * @brief loclass_swap - * @param val - * @return - */ -static uint8_t loclass_swap(uint8_t val) { - return ((val >> 4) & 0xFF) | ((val & 0xFF) << 4); -} - -/** - * Hash1 takes CSN as input, and determines what bytes in the keytable will be used - * when constructing the K_sel. - * @param csn the CSN used - * @param k output - */ -void loclass_hash1(const uint8_t csn[], uint8_t k[]) { - k[0] = csn[0] ^ csn[1] ^ csn[2] ^ csn[3] ^ csn[4] ^ csn[5] ^ csn[6] ^ csn[7]; - k[1] = csn[0] + csn[1] + csn[2] + csn[3] + csn[4] + csn[5] + csn[6] + csn[7]; - k[2] = loclass_rr(loclass_swap(csn[2] + k[1])); - k[3] = loclass_rl(loclass_swap(csn[3] + k[0])); - k[4] = ~loclass_rr(csn[4] + k[2]) + 1; - k[5] = ~loclass_rl(csn[5] + k[3]) + 1; - k[6] = loclass_rr(csn[6] + (k[4] ^ 0x3c)); - k[7] = loclass_rl(csn[7] + (k[5] ^ 0xc3)); - - k[7] &= 0x7F; - k[6] &= 0x7F; - k[5] &= 0x7F; - k[4] &= 0x7F; - k[3] &= 0x7F; - k[2] &= 0x7F; - k[1] &= 0x7F; - k[0] &= 0x7F; -} -/** -Definition 14. Define the rotate key function loclass_rk : (F 82 ) 8 × N → (F 82 ) 8 as -loclass_rk(x [0] . . . x [7] , 0) = x [0] . . . x [7] -loclass_rk(x [0] . . . x [7] , n + 1) = loclass_rk(loclass_rl(x [0] ) . . . loclass_rl(x [7] ), n) -**/ -static void loclass_rk(uint8_t* key, uint8_t n, uint8_t* outp_key) { - memcpy(outp_key, key, 8); - uint8_t j; - while(n-- > 0) { - for(j = 0; j < 8; j++) outp_key[j] = loclass_rl(outp_key[j]); - } - return; -} - -static mbedtls_des_context loclass_ctx_enc; -static mbedtls_des_context loclass_ctx_dec; - -static void loclass_desdecrypt_iclass(uint8_t* iclass_key, uint8_t* input, uint8_t* output) { - uint8_t key_std_format[8] = {0}; - loclass_permutekey_rev(iclass_key, key_std_format); - mbedtls_des_setkey_dec(&loclass_ctx_dec, key_std_format); - mbedtls_des_crypt_ecb(&loclass_ctx_dec, input, output); -} - -static void loclass_desencrypt_iclass(uint8_t* iclass_key, uint8_t* input, uint8_t* output) { - uint8_t key_std_format[8] = {0}; - loclass_permutekey_rev(iclass_key, key_std_format); - mbedtls_des_setkey_enc(&loclass_ctx_enc, key_std_format); - mbedtls_des_crypt_ecb(&loclass_ctx_enc, input, output); -} - -/** - * @brief Insert uint8_t[8] custom master key to calculate hash2 and return key_select. - * @param key unpermuted custom key - * @param loclass_hash1 loclass_hash1 - * @param key_sel output key_sel=h[loclass_hash1[i]] - */ -void loclass_hash2(uint8_t* key64, uint8_t* outp_keytable) { - /** - *Expected: - * High Security Key Table - - 00 F1 35 59 A1 0D 5A 26 7F 18 60 0B 96 8A C0 25 C1 - 10 BF A1 3B B0 FF 85 28 75 F2 1F C6 8F 0E 74 8F 21 - 20 14 7A 55 16 C8 A9 7D B3 13 0C 5D C9 31 8D A9 B2 - 30 A3 56 83 0F 55 7E DE 45 71 21 D2 6D C1 57 1C 9C - 40 78 2F 64 51 42 7B 64 30 FA 26 51 76 D3 E0 FB B6 - 50 31 9F BF 2F 7E 4F 94 B4 BD 4F 75 91 E3 1B EB 42 - 60 3F 88 6F B8 6C 2C 93 0D 69 2C D5 20 3C C1 61 95 - 70 43 08 A0 2F FE B3 26 D7 98 0B 34 7B 47 70 A0 AB - - **** The 64-bit HS Custom Key Value = 5B7C62C491C11B39 ******/ - uint8_t key64_negated[8] = {0}; - uint8_t z[8][8] = {{0}, {0}}; - uint8_t temp_output[8] = {0}; - - //calculate complement of key - int i; - for(i = 0; i < 8; i++) key64_negated[i] = ~key64[i]; - - // Once again, key is on iclass-format - loclass_desencrypt_iclass(key64, key64_negated, z[0]); - - uint8_t y[8][8] = {{0}, {0}}; - - // y[0]=DES_dec(z[0],~key) - // Once again, key is on iclass-format - loclass_desdecrypt_iclass(z[0], key64_negated, y[0]); - - for(i = 1; i < 8; i++) { - loclass_rk(key64, i, temp_output); - loclass_desdecrypt_iclass(temp_output, z[i - 1], z[i]); - loclass_desencrypt_iclass(temp_output, y[i - 1], y[i]); - } - - if(outp_keytable != NULL) { - for(i = 0; i < 8; i++) { - memcpy(outp_keytable + i * 16, y[i], 8); - memcpy(outp_keytable + 8 + i * 16, z[i], 8); - } - } -} diff --git a/applications/external/picopass/lib/loclass/optimized_elite.h b/applications/external/picopass/lib/loclass/optimized_elite.h deleted file mode 100644 index 5343ebb0740..00000000000 --- a/applications/external/picopass/lib/loclass/optimized_elite.h +++ /dev/null @@ -1,58 +0,0 @@ -//----------------------------------------------------------------------------- -// Borrowed initially from https://github.com/holiman/loclass -// More recently from https://github.com/RfidResearchGroup/proxmark3 -// Copyright (C) 2014 Martin Holst Swende -// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// See LICENSE.txt for the text of the license. -//----------------------------------------------------------------------------- -// WARNING -// -// THIS CODE IS CREATED FOR EXPERIMENTATION AND EDUCATIONAL USE ONLY. -// -// USAGE OF THIS CODE IN OTHER WAYS MAY INFRINGE UPON THE INTELLECTUAL -// PROPERTY OF OTHER PARTIES, SUCH AS INSIDE SECURE AND HID GLOBAL, -// AND MAY EXPOSE YOU TO AN INFRINGEMENT ACTION FROM THOSE PARTIES. -// -// THIS CODE SHOULD NEVER BE USED TO INFRINGE PATENTS OR INTELLECTUAL PROPERTY RIGHTS. -//----------------------------------------------------------------------------- -// It is a reconstruction of the cipher engine used in iClass, and RFID techology. -// -// The implementation is based on the work performed by -// Flavio D. Garcia, Gerhard de Koning Gans, Roel Verdult and -// Milosch Meriac in the paper "Dismantling IClass". -//----------------------------------------------------------------------------- -#ifndef ELITE_CRACK_H -#define ELITE_CRACK_H - -#include -#include - -void loclass_permutekey(const uint8_t key[8], uint8_t dest[8]); -/** - * Permutes a key from iclass specific format to NIST format - * @brief loclass_permutekey_rev - * @param key - * @param dest - */ -void loclass_permutekey_rev(const uint8_t key[8], uint8_t dest[8]); -/** - * Hash1 takes CSN as input, and determines what bytes in the keytable will be used - * when constructing the K_sel. - * @param csn the CSN used - * @param k output - */ -void loclass_hash1(const uint8_t* csn, uint8_t* k); -void loclass_hash2(uint8_t* key64, uint8_t* outp_keytable); - -#endif diff --git a/applications/external/picopass/lib/loclass/optimized_ikeys.c b/applications/external/picopass/lib/loclass/optimized_ikeys.c deleted file mode 100644 index 1e6f12c5678..00000000000 --- a/applications/external/picopass/lib/loclass/optimized_ikeys.c +++ /dev/null @@ -1,320 +0,0 @@ -//----------------------------------------------------------------------------- -// Borrowed initially from https://github.com/holiman/loclass -// Copyright (C) 2014 Martin Holst Swende -// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// See LICENSE.txt for the text of the license. -//----------------------------------------------------------------------------- -// WARNING -// -// THIS CODE IS CREATED FOR EXPERIMENTATION AND EDUCATIONAL USE ONLY. -// -// USAGE OF THIS CODE IN OTHER WAYS MAY INFRINGE UPON THE INTELLECTUAL -// PROPERTY OF OTHER PARTIES, SUCH AS INSIDE SECURE AND HID GLOBAL, -// AND MAY EXPOSE YOU TO AN INFRINGEMENT ACTION FROM THOSE PARTIES. -// -// THIS CODE SHOULD NEVER BE USED TO INFRINGE PATENTS OR INTELLECTUAL PROPERTY RIGHTS. -//----------------------------------------------------------------------------- -// It is a reconstruction of the cipher engine used in iClass, and RFID techology. -// -// The implementation is based on the work performed by -// Flavio D. Garcia, Gerhard de Koning Gans, Roel Verdult and -// Milosch Meriac in the paper "Dismantling IClass". -//----------------------------------------------------------------------------- - -/** -From "Dismantling iclass": - This section describes in detail the built-in key diversification algorithm of iClass. - Besides the obvious purpose of deriving a card key from a master key, this - algorithm intends to circumvent weaknesses in the cipher by preventing the - usage of certain ‘weak’ keys. In order to compute a diversified key, the iClass - reader first encrypts the card identity id with the master key K, using single - DES. The resulting ciphertext is then input to a function called loclass_hash0 which - outputs the diversified key k. - - k = loclass_hash0(DES enc (id, K)) - - Here the DES encryption of id with master key K outputs a cryptogram c - of 64 bits. These 64 bits are divided as c = x, y, z [0] , . . . , z [7] ∈ F 82 × F 82 × (F 62 ) 8 - which is used as input to the loclass_hash0 function. This function introduces some - obfuscation by performing a number of permutations, complement and modulo - operations, see Figure 2.5. Besides that, it checks for and removes patterns like - similar key bytes, which could produce a strong bias in the cipher. Finally, the - output of loclass_hash0 is the diversified card key k = k [0] , . . . , k [7] ∈ (F 82 ) 8 . - -**/ -#include "optimized_ikeys.h" - -#include -#include -#include -#include -#include "optimized_cipherutils.h" - -static const uint8_t loclass_pi[35] = {0x0F, 0x17, 0x1B, 0x1D, 0x1E, 0x27, 0x2B, 0x2D, 0x2E, - 0x33, 0x35, 0x39, 0x36, 0x3A, 0x3C, 0x47, 0x4B, 0x4D, - 0x4E, 0x53, 0x55, 0x56, 0x59, 0x5A, 0x5C, 0x63, 0x65, - 0x66, 0x69, 0x6A, 0x6C, 0x71, 0x72, 0x74, 0x78}; - -/** - * @brief The key diversification algorithm uses 6-bit bytes. - * This implementation uses 64 bit uint to pack seven of them into one - * variable. When they are there, they are placed as follows: - * XXXX XXXX N0 .... N7, occupying the last 48 bits. - * - * This function picks out one from such a collection - * @param all - * @param n bitnumber - * @return - */ -static uint8_t loclass_getSixBitByte(uint64_t c, int n) { - return (c >> (42 - 6 * n)) & 0x3F; -} - -/** - * @brief Puts back a six-bit 'byte' into a uint64_t. - * @param c buffer - * @param z the value to place there - * @param n bitnumber. - */ -static void loclass_pushbackSixBitByte(uint64_t* c, uint8_t z, int n) { - //0x XXXX YYYY ZZZZ ZZZZ ZZZZ - // ^z0 ^z7 - //z0: 1111 1100 0000 0000 - - uint64_t masked = z & 0x3F; - uint64_t eraser = 0x3F; - masked <<= 42 - 6 * n; - eraser <<= 42 - 6 * n; - - //masked <<= 6*n; - //eraser <<= 6*n; - - eraser = ~eraser; - (*c) &= eraser; - (*c) |= masked; -} -/** - * @brief Swaps the z-values. - * If the input value has format XYZ0Z1...Z7, the output will have the format - * XYZ7Z6...Z0 instead - * @param c - * @return - */ -static uint64_t loclass_swapZvalues(uint64_t c) { - uint64_t newz = 0; - loclass_pushbackSixBitByte(&newz, loclass_getSixBitByte(c, 0), 7); - loclass_pushbackSixBitByte(&newz, loclass_getSixBitByte(c, 1), 6); - loclass_pushbackSixBitByte(&newz, loclass_getSixBitByte(c, 2), 5); - loclass_pushbackSixBitByte(&newz, loclass_getSixBitByte(c, 3), 4); - loclass_pushbackSixBitByte(&newz, loclass_getSixBitByte(c, 4), 3); - loclass_pushbackSixBitByte(&newz, loclass_getSixBitByte(c, 5), 2); - loclass_pushbackSixBitByte(&newz, loclass_getSixBitByte(c, 6), 1); - loclass_pushbackSixBitByte(&newz, loclass_getSixBitByte(c, 7), 0); - newz |= (c & 0xFFFF000000000000); - return newz; -} - -/** -* @return 4 six-bit bytes chunked into a uint64_t,as 00..00a0a1a2a3 -*/ -static uint64_t loclass_ck(int i, int j, uint64_t z) { - if(i == 1 && j == -1) { - // loclass_ck(1, −1, z [0] . . . z [3] ) = z [0] . . . z [3] - return z; - } else if(j == -1) { - // loclass_ck(i, −1, z [0] . . . z [3] ) = loclass_ck(i − 1, i − 2, z [0] . . . z [3] ) - return loclass_ck(i - 1, i - 2, z); - } - - if(loclass_getSixBitByte(z, i) == loclass_getSixBitByte(z, j)) { - //loclass_ck(i, j − 1, z [0] . . . z [i] ← j . . . z [3] ) - uint64_t newz = 0; - int c; - for(c = 0; c < 4; c++) { - uint8_t val = loclass_getSixBitByte(z, c); - if(c == i) - loclass_pushbackSixBitByte(&newz, j, c); - else - loclass_pushbackSixBitByte(&newz, val, c); - } - return loclass_ck(i, j - 1, newz); - } else { - return loclass_ck(i, j - 1, z); - } -} -/** - - Definition 8. - Let the function check : (F 62 ) 8 → (F 62 ) 8 be defined as - check(z [0] . . . z [7] ) = loclass_ck(3, 2, z [0] . . . z [3] ) · loclass_ck(3, 2, z [4] . . . z [7] ) - - where loclass_ck : N × N × (F 62 ) 4 → (F 62 ) 4 is defined as - - loclass_ck(1, −1, z [0] . . . z [3] ) = z [0] . . . z [3] - loclass_ck(i, −1, z [0] . . . z [3] ) = loclass_ck(i − 1, i − 2, z [0] . . . z [3] ) - loclass_ck(i, j, z [0] . . . z [3] ) = - loclass_ck(i, j − 1, z [0] . . . z [i] ← j . . . z [3] ), if z [i] = z [j] ; - loclass_ck(i, j − 1, z [0] . . . z [3] ), otherwise - - otherwise. -**/ - -static uint64_t loclass_check(uint64_t z) { - //These 64 bits are divided as c = x, y, z [0] , . . . , z [7] - - // loclass_ck(3, 2, z [0] . . . z [3] ) - uint64_t ck1 = loclass_ck(3, 2, z); - - // loclass_ck(3, 2, z [4] . . . z [7] ) - uint64_t ck2 = loclass_ck(3, 2, z << 24); - - //The loclass_ck function will place the values - // in the middle of z. - ck1 &= 0x00000000FFFFFF000000; - ck2 &= 0x00000000FFFFFF000000; - - return ck1 | ck2 >> 24; -} - -static void loclass_permute( - LoclassBitstreamIn_t* p_in, - uint64_t z, - int l, - int r, - LoclassBitstreamOut_t* out) { - if(loclass_bitsLeft(p_in) == 0) return; - - bool pn = loclass_tailBit(p_in); - if(pn) { // pn = 1 - uint8_t zl = loclass_getSixBitByte(z, l); - - loclass_push6bits(out, zl + 1); - loclass_permute(p_in, z, l + 1, r, out); - } else { // otherwise - uint8_t zr = loclass_getSixBitByte(z, r); - - loclass_push6bits(out, zr); - loclass_permute(p_in, z, l, r + 1, out); - } -} - -/** - * @brief - *Definition 11. Let the function loclass_hash0 : F 82 × F 82 × (F 62 ) 8 → (F 82 ) 8 be defined as - * loclass_hash0(x, y, z [0] . . . z [7] ) = k [0] . . . k [7] where - * z'[i] = (z[i] mod (63-i)) + i i = 0...3 - * z'[i+4] = (z[i+4] mod (64-i)) + i i = 0...3 - * ẑ = check(z'); - * @param c - * @param k this is where the diversified key is put (should be 8 bytes) - * @return - */ -void loclass_hash0(uint64_t c, uint8_t k[8]) { - c = loclass_swapZvalues(c); - - //These 64 bits are divided as c = x, y, z [0] , . . . , z [7] - // x = 8 bits - // y = 8 bits - // z0-z7 6 bits each : 48 bits - uint8_t x = (c & 0xFF00000000000000) >> 56; - uint8_t y = (c & 0x00FF000000000000) >> 48; - uint64_t zP = 0; - - for(int n = 0; n < 4; n++) { - uint8_t zn = loclass_getSixBitByte(c, n); - uint8_t zn4 = loclass_getSixBitByte(c, n + 4); - uint8_t _zn = (zn % (63 - n)) + n; - uint8_t _zn4 = (zn4 % (64 - n)) + n; - loclass_pushbackSixBitByte(&zP, _zn, n); - loclass_pushbackSixBitByte(&zP, _zn4, n + 4); - } - - uint64_t zCaret = loclass_check(zP); - uint8_t p = loclass_pi[x % 35]; - - if(x & 1) //Check if x7 is 1 - p = ~p; - - LoclassBitstreamIn_t p_in = {&p, 8, 0}; - uint8_t outbuffer[] = {0, 0, 0, 0, 0, 0, 0, 0}; - LoclassBitstreamOut_t out = {outbuffer, 0, 0}; - loclass_permute(&p_in, zCaret, 0, 4, &out); //returns 48 bits? or 6 8-bytes - - //Out is now a buffer containing six-bit bytes, should be 48 bits - // if all went well - //Shift z-values down onto the lower segment - - uint64_t zTilde = loclass_x_bytes_to_num(outbuffer, sizeof(outbuffer)); - - zTilde >>= 16; - - for(int i = 0; i < 8; i++) { - // the key on index i is first a bit from y - // then six bits from z, - // then a bit from p - - // Init with zeroes - k[i] = 0; - // First, place yi leftmost in k - //k[i] |= (y << i) & 0x80 ; - - // First, place y(7-i) leftmost in k - k[i] |= (y << (7 - i)) & 0x80; - - uint8_t zTilde_i = loclass_getSixBitByte(zTilde, i); - // zTildeI is now on the form 00XXXXXX - // with one leftshift, it'll be - // 0XXXXXX0 - // So after leftshift, we can OR it into k - // However, when doing complement, we need to - // again MASK 0XXXXXX0 (0x7E) - zTilde_i <<= 1; - - //Finally, add bit from p or p-mod - //Shift bit i into rightmost location (mask only after complement) - uint8_t p_i = p >> i & 0x1; - - if(k[i]) { // yi = 1 - k[i] |= ~zTilde_i & 0x7E; - k[i] |= p_i & 1; - k[i] += 1; - - } else { // otherwise - k[i] |= zTilde_i & 0x7E; - k[i] |= (~p_i) & 1; - } - } -} -/** - * @brief Performs Elite-class key diversification - * @param csn - * @param key - * @param div_key - */ -void loclass_diversifyKey(uint8_t* csn, const uint8_t* key, uint8_t* div_key) { - mbedtls_des_context loclass_ctx_enc; - - // Prepare the DES key - mbedtls_des_setkey_enc(&loclass_ctx_enc, key); - - uint8_t crypted_csn[8] = {0}; - - // Calculate DES(CSN, KEY) - mbedtls_des_crypt_ecb(&loclass_ctx_enc, csn, crypted_csn); - - //Calculate HASH0(DES)) - uint64_t c_csn = loclass_x_bytes_to_num(crypted_csn, sizeof(crypted_csn)); - - loclass_hash0(c_csn, div_key); -} diff --git a/applications/external/picopass/lib/loclass/optimized_ikeys.h b/applications/external/picopass/lib/loclass/optimized_ikeys.h deleted file mode 100644 index f2711d31ef5..00000000000 --- a/applications/external/picopass/lib/loclass/optimized_ikeys.h +++ /dev/null @@ -1,66 +0,0 @@ -//----------------------------------------------------------------------------- -// Borrowed initially from https://github.com/holiman/loclass -// More recently from https://github.com/RfidResearchGroup/proxmark3 -// Copyright (C) 2014 Martin Holst Swende -// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// See LICENSE.txt for the text of the license. -//----------------------------------------------------------------------------- -// WARNING -// -// THIS CODE IS CREATED FOR EXPERIMENTATION AND EDUCATIONAL USE ONLY. -// -// USAGE OF THIS CODE IN OTHER WAYS MAY INFRINGE UPON THE INTELLECTUAL -// PROPERTY OF OTHER PARTIES, SUCH AS INSIDE SECURE AND HID GLOBAL, -// AND MAY EXPOSE YOU TO AN INFRINGEMENT ACTION FROM THOSE PARTIES. -// -// THIS CODE SHOULD NEVER BE USED TO INFRINGE PATENTS OR INTELLECTUAL PROPERTY RIGHTS. -//----------------------------------------------------------------------------- -// It is a reconstruction of the cipher engine used in iClass, and RFID techology. -// -// The implementation is based on the work performed by -// Flavio D. Garcia, Gerhard de Koning Gans, Roel Verdult and -// Milosch Meriac in the paper "Dismantling IClass". -//----------------------------------------------------------------------------- -#ifndef IKEYS_H -#define IKEYS_H - -#include - -/** - * @brief - *Definition 11. Let the function loclass_hash0 : F 82 × F 82 × (F 62 ) 8 → (F 82 ) 8 be defined as - * loclass_hash0(x, y, z [0] . . . z [7] ) = k [0] . . . k [7] where - * z'[i] = (z[i] mod (63-i)) + i i = 0...3 - * z'[i+4] = (z[i+4] mod (64-i)) + i i = 0...3 - * ẑ = check(z'); - * @param c - * @param k this is where the diversified key is put (should be 8 bytes) - * @return - */ -void loclass_hash0(uint64_t c, uint8_t k[8]); -/** - * @brief Performs Elite-class key diversification - * @param csn - * @param key - * @param div_key - */ - -void loclass_diversifyKey(uint8_t* csn, const uint8_t* key, uint8_t* div_key); -/** - * @brief Permutes a key from standard NIST format to Iclass specific format - * @param key - * @param dest - */ - -#endif // IKEYS_H diff --git a/applications/external/picopass/picopass.c b/applications/external/picopass/picopass.c deleted file mode 100644 index 6737d8077d1..00000000000 --- a/applications/external/picopass/picopass.c +++ /dev/null @@ -1,212 +0,0 @@ -#include "picopass_i.h" - -#define TAG "PicoPass" - -bool picopass_custom_event_callback(void* context, uint32_t event) { - furi_assert(context); - Picopass* picopass = context; - return scene_manager_handle_custom_event(picopass->scene_manager, event); -} - -bool picopass_back_event_callback(void* context) { - furi_assert(context); - Picopass* picopass = context; - return scene_manager_handle_back_event(picopass->scene_manager); -} - -void picopass_tick_event_callback(void* context) { - furi_assert(context); - Picopass* picopass = context; - scene_manager_handle_tick_event(picopass->scene_manager); -} - -Picopass* picopass_alloc() { - Picopass* picopass = malloc(sizeof(Picopass)); - - picopass->worker = picopass_worker_alloc(); - picopass->view_dispatcher = view_dispatcher_alloc(); - picopass->scene_manager = scene_manager_alloc(&picopass_scene_handlers, picopass); - view_dispatcher_enable_queue(picopass->view_dispatcher); - view_dispatcher_set_event_callback_context(picopass->view_dispatcher, picopass); - view_dispatcher_set_custom_event_callback( - picopass->view_dispatcher, picopass_custom_event_callback); - view_dispatcher_set_navigation_event_callback( - picopass->view_dispatcher, picopass_back_event_callback); - view_dispatcher_set_tick_event_callback( - picopass->view_dispatcher, picopass_tick_event_callback, 100); - - // Picopass device - picopass->dev = picopass_device_alloc(); - - // Open GUI record - picopass->gui = furi_record_open(RECORD_GUI); - view_dispatcher_attach_to_gui( - picopass->view_dispatcher, picopass->gui, ViewDispatcherTypeFullscreen); - - // Open Notification record - picopass->notifications = furi_record_open(RECORD_NOTIFICATION); - - // Submenu - picopass->submenu = submenu_alloc(); - view_dispatcher_add_view( - picopass->view_dispatcher, PicopassViewMenu, submenu_get_view(picopass->submenu)); - - // Popup - picopass->popup = popup_alloc(); - view_dispatcher_add_view( - picopass->view_dispatcher, PicopassViewPopup, popup_get_view(picopass->popup)); - - // Loading - picopass->loading = loading_alloc(); - view_dispatcher_add_view( - picopass->view_dispatcher, PicopassViewLoading, loading_get_view(picopass->loading)); - - // Text Input - picopass->text_input = text_input_alloc(); - view_dispatcher_add_view( - picopass->view_dispatcher, - PicopassViewTextInput, - text_input_get_view(picopass->text_input)); - - // Custom Widget - picopass->widget = widget_alloc(); - view_dispatcher_add_view( - picopass->view_dispatcher, PicopassViewWidget, widget_get_view(picopass->widget)); - - picopass->dict_attack = dict_attack_alloc(); - view_dispatcher_add_view( - picopass->view_dispatcher, - PicopassViewDictAttack, - dict_attack_get_view(picopass->dict_attack)); - - return picopass; -} - -void picopass_free(Picopass* picopass) { - furi_assert(picopass); - - // Picopass device - picopass_device_free(picopass->dev); - picopass->dev = NULL; - - // Submenu - view_dispatcher_remove_view(picopass->view_dispatcher, PicopassViewMenu); - submenu_free(picopass->submenu); - - // Popup - view_dispatcher_remove_view(picopass->view_dispatcher, PicopassViewPopup); - popup_free(picopass->popup); - - // Loading - view_dispatcher_remove_view(picopass->view_dispatcher, PicopassViewLoading); - loading_free(picopass->loading); - - // TextInput - view_dispatcher_remove_view(picopass->view_dispatcher, PicopassViewTextInput); - text_input_free(picopass->text_input); - - // Custom Widget - view_dispatcher_remove_view(picopass->view_dispatcher, PicopassViewWidget); - widget_free(picopass->widget); - - view_dispatcher_remove_view(picopass->view_dispatcher, PicopassViewDictAttack); - dict_attack_free(picopass->dict_attack); - - // Worker - picopass_worker_stop(picopass->worker); - picopass_worker_free(picopass->worker); - - // View Dispatcher - view_dispatcher_free(picopass->view_dispatcher); - - // Scene Manager - scene_manager_free(picopass->scene_manager); - - // GUI - furi_record_close(RECORD_GUI); - picopass->gui = NULL; - - // Notifications - furi_record_close(RECORD_NOTIFICATION); - picopass->notifications = NULL; - - free(picopass); -} - -void picopass_text_store_set(Picopass* picopass, const char* text, ...) { - va_list args; - va_start(args, text); - - vsnprintf(picopass->text_store, sizeof(picopass->text_store), text, args); - - va_end(args); -} - -void picopass_text_store_clear(Picopass* picopass) { - memset(picopass->text_store, 0, sizeof(picopass->text_store)); -} - -static const NotificationSequence picopass_sequence_blink_start_cyan = { - &message_blink_start_10, - &message_blink_set_color_cyan, - &message_do_not_reset, - NULL, -}; - -static const NotificationSequence picopass_sequence_blink_stop = { - &message_blink_stop, - NULL, -}; - -void picopass_blink_start(Picopass* picopass) { - notification_message(picopass->notifications, &picopass_sequence_blink_start_cyan); -} - -void picopass_blink_stop(Picopass* picopass) { - notification_message(picopass->notifications, &picopass_sequence_blink_stop); -} - -void picopass_show_loading_popup(void* context, bool show) { - Picopass* picopass = context; - TaskHandle_t timer_task = xTaskGetHandle(configTIMER_SERVICE_TASK_NAME); - - if(show) { - // Raise timer priority so that animations can play - vTaskPrioritySet(timer_task, configMAX_PRIORITIES - 1); - view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewLoading); - } else { - // Restore default timer priority - vTaskPrioritySet(timer_task, configTIMER_TASK_PRIORITY); - } -} - -static void picopass_migrate_from_old_folder() { - Storage* storage = furi_record_open(RECORD_STORAGE); - storage_common_migrate(storage, "/ext/picopass", STORAGE_APP_DATA_PATH_PREFIX); - furi_record_close(RECORD_STORAGE); -} - -bool picopass_is_memset(const uint8_t* data, const uint8_t pattern, size_t size) { - bool result = size > 0; - while(size > 0) { - result &= (*data == pattern); - data++; - size--; - } - return result; -} - -int32_t picopass_app(void* p) { - UNUSED(p); - picopass_migrate_from_old_folder(); - - Picopass* picopass = picopass_alloc(); - - scene_manager_next_scene(picopass->scene_manager, PicopassSceneStart); - - view_dispatcher_run(picopass->view_dispatcher); - - picopass_free(picopass); - - return 0; -} diff --git a/applications/external/picopass/picopass.h b/applications/external/picopass/picopass.h deleted file mode 100644 index a1a87d7f869..00000000000 --- a/applications/external/picopass/picopass.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -typedef struct Picopass Picopass; diff --git a/applications/external/picopass/picopass_device.c b/applications/external/picopass/picopass_device.c deleted file mode 100644 index de43b0bb7c5..00000000000 --- a/applications/external/picopass/picopass_device.c +++ /dev/null @@ -1,380 +0,0 @@ -#include "picopass_device.h" - -#include -#include -#include - -#define TAG "PicopassDevice" - -static const char* picopass_file_header = "Flipper Picopass device"; -static const uint32_t picopass_file_version = 1; - -const uint8_t picopass_iclass_decryptionkey[] = - {0xb4, 0x21, 0x2c, 0xca, 0xb7, 0xed, 0x21, 0x0f, 0x7b, 0x93, 0xd4, 0x59, 0x39, 0xc7, 0xdd, 0x36}; - -PicopassDevice* picopass_device_alloc() { - PicopassDevice* picopass_dev = malloc(sizeof(PicopassDevice)); - picopass_dev->dev_data.pacs.legacy = false; - picopass_dev->dev_data.pacs.se_enabled = false; - picopass_dev->dev_data.pacs.elite_kdf = false; - picopass_dev->dev_data.pacs.pin_length = 0; - picopass_dev->storage = furi_record_open(RECORD_STORAGE); - picopass_dev->dialogs = furi_record_open(RECORD_DIALOGS); - picopass_dev->load_path = furi_string_alloc(); - return picopass_dev; -} - -void picopass_device_set_name(PicopassDevice* dev, const char* name) { - furi_assert(dev); - - strlcpy(dev->dev_name, name, PICOPASS_DEV_NAME_MAX_LEN); -} - -static bool picopass_device_save_file( - PicopassDevice* dev, - const char* dev_name, - const char* folder, - const char* extension, - bool use_load_path) { - furi_assert(dev); - - bool saved = false; - FlipperFormat* file = flipper_format_file_alloc(dev->storage); - PicopassPacs* pacs = &dev->dev_data.pacs; - PicopassBlock* AA1 = dev->dev_data.AA1; - FuriString* temp_str; - temp_str = furi_string_alloc(); - - do { - if(use_load_path && !furi_string_empty(dev->load_path)) { - // Get directory name - path_extract_dirname(furi_string_get_cstr(dev->load_path), temp_str); - // Make path to file to save - furi_string_cat_printf(temp_str, "/%s%s", dev_name, extension); - } else { - // First remove picopass device file if it was saved - furi_string_printf(temp_str, "%s/%s%s", folder, dev_name, extension); - } - // Open file - if(!flipper_format_file_open_always(file, furi_string_get_cstr(temp_str))) break; - - if(dev->format == PicopassDeviceSaveFormatHF) { - uint32_t fc = pacs->record.FacilityCode; - uint32_t cn = pacs->record.CardNumber; - // Write header - if(!flipper_format_write_header_cstr(file, picopass_file_header, picopass_file_version)) - break; - if(pacs->record.valid) { - if(!flipper_format_write_uint32(file, "Facility Code", &fc, 1)) break; - if(!flipper_format_write_uint32(file, "Card Number", &cn, 1)) break; - if(!flipper_format_write_hex( - file, "Credential", pacs->credential, PICOPASS_BLOCK_LEN)) - break; - if(pacs->pin_length > 0) { - if(!flipper_format_write_hex(file, "PIN\t\t", pacs->pin0, PICOPASS_BLOCK_LEN)) - break; - if(!flipper_format_write_hex( - file, "PIN(cont.)\t", pacs->pin1, PICOPASS_BLOCK_LEN)) - break; - } - } - // TODO: Add elite - if(!flipper_format_write_comment_cstr(file, "Picopass blocks")) break; - bool block_saved = true; - - size_t app_limit = AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[0] < PICOPASS_MAX_APP_LIMIT ? - AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[0] : - PICOPASS_MAX_APP_LIMIT; - for(size_t i = 0; i < app_limit; i++) { - furi_string_printf(temp_str, "Block %d", i); - if(!flipper_format_write_hex( - file, furi_string_get_cstr(temp_str), AA1[i].data, PICOPASS_BLOCK_LEN)) { - block_saved = false; - break; - } - } - if(!block_saved) break; - } else if(dev->format == PicopassDeviceSaveFormatLF) { - const char* lf_header = "Flipper RFID key"; - // Write header - if(!flipper_format_write_header_cstr(file, lf_header, 1)) break; - if(!flipper_format_write_comment_cstr( - file, - "This was generated from the Picopass plugin and may not match current lfrfid")) - break; - // When lfrfid supports more formats, update this - if(!flipper_format_write_string_cstr(file, "Key type", "H10301")) break; - uint8_t H10301[3] = {0}; - H10301[0] = pacs->record.FacilityCode; - H10301[1] = pacs->record.CardNumber >> 8; - H10301[2] = pacs->record.CardNumber & 0x00FF; - if(!flipper_format_write_hex(file, "Data", H10301, 3)) break; - } - saved = true; - } while(0); - - if(!saved) { - dialog_message_show_storage_error(dev->dialogs, "Can not save\nfile"); - } - furi_string_free(temp_str); - flipper_format_free(file); - return saved; -} - -bool picopass_device_save(PicopassDevice* dev, const char* dev_name) { - if(dev->format == PicopassDeviceSaveFormatHF) { - return picopass_device_save_file( - dev, dev_name, STORAGE_APP_DATA_PATH_PREFIX, PICOPASS_APP_EXTENSION, true); - } else if(dev->format == PicopassDeviceSaveFormatLF) { - return picopass_device_save_file(dev, dev_name, ANY_PATH("lfrfid"), ".rfid", true); - } - - return false; -} - -static bool picopass_device_load_data(PicopassDevice* dev, FuriString* path, bool show_dialog) { - bool parsed = false; - FlipperFormat* file = flipper_format_file_alloc(dev->storage); - PicopassBlock* AA1 = dev->dev_data.AA1; - PicopassPacs* pacs = &dev->dev_data.pacs; - FuriString* temp_str; - temp_str = furi_string_alloc(); - bool deprecated_version = false; - - if(dev->loading_cb) { - dev->loading_cb(dev->loading_cb_ctx, true); - } - - do { - if(!flipper_format_file_open_existing(file, furi_string_get_cstr(path))) break; - - // Read and verify file header - uint32_t version = 0; - if(!flipper_format_read_header(file, temp_str, &version)) break; - if(furi_string_cmp_str(temp_str, picopass_file_header) || - (version != picopass_file_version)) { - deprecated_version = true; - break; - } - - // Parse header blocks - bool block_read = true; - for(size_t i = 0; i < 6; i++) { - furi_string_printf(temp_str, "Block %d", i); - if(!flipper_format_read_hex( - file, furi_string_get_cstr(temp_str), AA1[i].data, PICOPASS_BLOCK_LEN)) { - block_read = false; - break; - } - } - - size_t app_limit = AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[0]; - // Fix for unpersonalized cards that have app_limit set to 0xFF - if(app_limit > PICOPASS_MAX_APP_LIMIT) app_limit = PICOPASS_MAX_APP_LIMIT; - for(size_t i = 6; i < app_limit; i++) { - furi_string_printf(temp_str, "Block %d", i); - if(!flipper_format_read_hex( - file, furi_string_get_cstr(temp_str), AA1[i].data, PICOPASS_BLOCK_LEN)) { - block_read = false; - break; - } - } - if(!block_read) break; - - if(picopass_device_parse_credential(AA1, pacs) != ERR_NONE) break; - if(picopass_device_parse_wiegand(pacs->credential, &pacs->record) != ERR_NONE) break; - - parsed = true; - } while(false); - - if(dev->loading_cb) { - dev->loading_cb(dev->loading_cb_ctx, false); - } - - if((!parsed) && (show_dialog)) { - if(deprecated_version) { - dialog_message_show_storage_error(dev->dialogs, "File format deprecated"); - } else { - dialog_message_show_storage_error(dev->dialogs, "Can not parse\nfile"); - } - } - - furi_string_free(temp_str); - flipper_format_free(file); - - return parsed; -} - -void picopass_device_clear(PicopassDevice* dev) { - furi_assert(dev); - - picopass_device_data_clear(&dev->dev_data); - memset(&dev->dev_data, 0, sizeof(dev->dev_data)); - dev->format = PicopassDeviceSaveFormatHF; - furi_string_reset(dev->load_path); -} - -void picopass_device_free(PicopassDevice* picopass_dev) { - furi_assert(picopass_dev); - picopass_device_clear(picopass_dev); - furi_record_close(RECORD_STORAGE); - furi_record_close(RECORD_DIALOGS); - furi_string_free(picopass_dev->load_path); - free(picopass_dev); -} - -bool picopass_file_select(PicopassDevice* dev) { - furi_assert(dev); - - FuriString* picopass_app_folder; - picopass_app_folder = furi_string_alloc_set(STORAGE_APP_DATA_PATH_PREFIX); - - DialogsFileBrowserOptions browser_options; - dialog_file_browser_set_basic_options(&browser_options, PICOPASS_APP_EXTENSION, &I_Nfc_10px); - browser_options.base_path = STORAGE_APP_DATA_PATH_PREFIX; - - bool res = dialog_file_browser_show( - dev->dialogs, dev->load_path, picopass_app_folder, &browser_options); - - furi_string_free(picopass_app_folder); - if(res) { - FuriString* filename; - filename = furi_string_alloc(); - path_extract_filename(dev->load_path, filename, true); - strncpy(dev->dev_name, furi_string_get_cstr(filename), PICOPASS_DEV_NAME_MAX_LEN); - res = picopass_device_load_data(dev, dev->load_path, true); - if(res) { - picopass_device_set_name(dev, dev->dev_name); - } - furi_string_free(filename); - } - - return res; -} - -void picopass_device_data_clear(PicopassDeviceData* dev_data) { - for(size_t i = 0; i < PICOPASS_MAX_APP_LIMIT; i++) { - memset(dev_data->AA1[i].data, 0, sizeof(dev_data->AA1[i].data)); - } - dev_data->pacs.legacy = false; - dev_data->pacs.se_enabled = false; - dev_data->pacs.elite_kdf = false; - dev_data->pacs.pin_length = 0; -} - -bool picopass_device_delete(PicopassDevice* dev, bool use_load_path) { - furi_assert(dev); - - bool deleted = false; - FuriString* file_path; - file_path = furi_string_alloc(); - - do { - // Delete original file - if(use_load_path && !furi_string_empty(dev->load_path)) { - furi_string_set(file_path, dev->load_path); - } else { - furi_string_printf( - file_path, APP_DATA_PATH("%s%s"), dev->dev_name, PICOPASS_APP_EXTENSION); - } - if(!storage_simply_remove(dev->storage, furi_string_get_cstr(file_path))) break; - deleted = true; - } while(0); - - if(!deleted) { - dialog_message_show_storage_error(dev->dialogs, "Can not remove file"); - } - - furi_string_free(file_path); - return deleted; -} - -void picopass_device_set_loading_callback( - PicopassDevice* dev, - PicopassLoadingCallback callback, - void* context) { - furi_assert(dev); - - dev->loading_cb = callback; - dev->loading_cb_ctx = context; -} - -ReturnCode picopass_device_decrypt(uint8_t* enc_data, uint8_t* dec_data) { - uint8_t key[32] = {0}; - memcpy(key, picopass_iclass_decryptionkey, sizeof(picopass_iclass_decryptionkey)); - mbedtls_des3_context ctx; - mbedtls_des3_init(&ctx); - mbedtls_des3_set2key_dec(&ctx, key); - mbedtls_des3_crypt_ecb(&ctx, enc_data, dec_data); - mbedtls_des3_free(&ctx); - return ERR_NONE; -} - -ReturnCode picopass_device_parse_credential(PicopassBlock* AA1, PicopassPacs* pacs) { - ReturnCode err; - - pacs->biometrics = AA1[6].data[4]; - pacs->pin_length = AA1[6].data[6] & 0x0F; - pacs->encryption = AA1[6].data[7]; - - if(pacs->encryption == PicopassDeviceEncryption3DES) { - FURI_LOG_D(TAG, "3DES Encrypted"); - err = picopass_device_decrypt(AA1[7].data, pacs->credential); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "decrypt error %d", err); - return err; - } - - err = picopass_device_decrypt(AA1[8].data, pacs->pin0); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "decrypt error %d", err); - return err; - } - - err = picopass_device_decrypt(AA1[9].data, pacs->pin1); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "decrypt error %d", err); - return err; - } - } else if(pacs->encryption == PicopassDeviceEncryptionNone) { - FURI_LOG_D(TAG, "No Encryption"); - memcpy(pacs->credential, AA1[7].data, PICOPASS_BLOCK_LEN); - memcpy(pacs->pin0, AA1[8].data, PICOPASS_BLOCK_LEN); - memcpy(pacs->pin1, AA1[9].data, PICOPASS_BLOCK_LEN); - } else if(pacs->encryption == PicopassDeviceEncryptionDES) { - FURI_LOG_D(TAG, "DES Encrypted"); - } else { - FURI_LOG_D(TAG, "Unknown encryption"); - } - - pacs->sio = (AA1[10].data[0] == 0x30); // rough check - - return ERR_NONE; -} - -ReturnCode picopass_device_parse_wiegand(uint8_t* data, PicopassWiegandRecord* record) { - uint32_t* halves = (uint32_t*)data; - if(halves[0] == 0) { - uint8_t leading0s = __builtin_clz(REVERSE_BYTES_U32(halves[1])); - record->bitLength = 31 - leading0s; - } else { - uint8_t leading0s = __builtin_clz(REVERSE_BYTES_U32(halves[0])); - record->bitLength = 63 - leading0s; - } - FURI_LOG_D(TAG, "bitLength: %d", record->bitLength); - - if(record->bitLength == 26) { - uint8_t* v4 = data + 4; - uint32_t bot = v4[3] | (v4[2] << 8) | (v4[1] << 16) | (v4[0] << 24); - - record->CardNumber = (bot >> 1) & 0xFFFF; - record->FacilityCode = (bot >> 17) & 0xFF; - FURI_LOG_D(TAG, "FC: %u CN: %u", record->FacilityCode, record->CardNumber); - record->valid = true; - } else { - record->CardNumber = 0; - record->FacilityCode = 0; - record->valid = false; - } - return ERR_NONE; -} diff --git a/applications/external/picopass/picopass_device.h b/applications/external/picopass/picopass_device.h deleted file mode 100644 index b45df346cf2..00000000000 --- a/applications/external/picopass/picopass_device.h +++ /dev/null @@ -1,117 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -#include "rfal_picopass.h" -#include -#include -#include "helpers/iclass_elite_dict.h" - -#define PICOPASS_DEV_NAME_MAX_LEN 22 -#define PICOPASS_READER_DATA_MAX_SIZE 64 -#define PICOPASS_BLOCK_LEN 8 -#define PICOPASS_MAX_APP_LIMIT 32 - -#define PICOPASS_CSN_BLOCK_INDEX 0 -#define PICOPASS_CONFIG_BLOCK_INDEX 1 -#define PICOPASS_EPURSE_BLOCK_INDEX 2 -#define PICOPASS_KD_BLOCK_INDEX 3 -#define PICOPASS_KC_BLOCK_INDEX 4 -#define PICOPASS_AIA_BLOCK_INDEX 5 -#define PICOPASS_PACS_CFG_BLOCK_INDEX 6 - -#define PICOPASS_APP_EXTENSION ".picopass" -#define PICOPASS_APP_SHADOW_EXTENSION ".pas" - -#define PICOPASS_DICT_KEY_BATCH_SIZE 10 - -typedef void (*PicopassLoadingCallback)(void* context, bool state); - -typedef struct { - IclassEliteDict* dict; - IclassEliteDictType type; - uint8_t current_sector; -} IclassEliteDictAttackData; - -typedef enum { - PicopassDeviceEncryptionUnknown = 0, - PicopassDeviceEncryptionNone = 0x14, - PicopassDeviceEncryptionDES = 0x15, - PicopassDeviceEncryption3DES = 0x17, -} PicopassEncryption; - -typedef enum { - PicopassDeviceSaveFormatHF, - PicopassDeviceSaveFormatLF, -} PicopassDeviceSaveFormat; - -typedef struct { - bool valid; - uint8_t bitLength; - uint8_t FacilityCode; - uint16_t CardNumber; -} PicopassWiegandRecord; - -typedef struct { - bool legacy; - bool se_enabled; - bool sio; - bool biometrics; - uint8_t key[8]; - bool elite_kdf; - uint8_t pin_length; - PicopassEncryption encryption; - uint8_t credential[8]; - uint8_t pin0[8]; - uint8_t pin1[8]; - PicopassWiegandRecord record; -} PicopassPacs; - -typedef struct { - uint8_t data[PICOPASS_BLOCK_LEN]; -} PicopassBlock; - -typedef struct { - PicopassBlock AA1[PICOPASS_MAX_APP_LIMIT]; - PicopassPacs pacs; - IclassEliteDictAttackData iclass_elite_dict_attack_data; -} PicopassDeviceData; - -typedef struct { - Storage* storage; - DialogsApp* dialogs; - PicopassDeviceData dev_data; - char dev_name[PICOPASS_DEV_NAME_MAX_LEN + 1]; - FuriString* load_path; - PicopassDeviceSaveFormat format; - PicopassLoadingCallback loading_cb; - void* loading_cb_ctx; -} PicopassDevice; - -PicopassDevice* picopass_device_alloc(); - -void picopass_device_free(PicopassDevice* picopass_dev); - -void picopass_device_set_name(PicopassDevice* dev, const char* name); - -bool picopass_device_save(PicopassDevice* dev, const char* dev_name); - -bool picopass_file_select(PicopassDevice* dev); - -void picopass_device_data_clear(PicopassDeviceData* dev_data); - -void picopass_device_clear(PicopassDevice* dev); - -bool picopass_device_delete(PicopassDevice* dev, bool use_load_path); - -void picopass_device_set_loading_callback( - PicopassDevice* dev, - PicopassLoadingCallback callback, - void* context); - -ReturnCode picopass_device_parse_credential(PicopassBlock* AA1, PicopassPacs* pacs); -ReturnCode picopass_device_parse_wiegand(uint8_t* data, PicopassWiegandRecord* record); diff --git a/applications/external/picopass/picopass_i.h b/applications/external/picopass/picopass_i.h deleted file mode 100644 index 9147cfa0cfe..00000000000 --- a/applications/external/picopass/picopass_i.h +++ /dev/null @@ -1,99 +0,0 @@ -#pragma once - -#include "picopass.h" -#include "picopass_worker.h" -#include "picopass_device.h" - -#include "rfal_picopass.h" - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include - -#include "scenes/picopass_scene.h" -#include "views/dict_attack.h" - -#include -#include -#include - -#define PICOPASS_TEXT_STORE_SIZE 128 - -enum PicopassCustomEvent { - // Reserve first 100 events for button types and indexes, starting from 0 - PicopassCustomEventReserved = 100, - - PicopassCustomEventViewExit, - PicopassCustomEventWorkerExit, - PicopassCustomEventByteInputDone, - PicopassCustomEventTextInputDone, - PicopassCustomEventDictAttackSkip, -}; - -typedef enum { - EventTypeTick, - EventTypeKey, -} EventType; - -struct Picopass { - PicopassWorker* worker; - ViewDispatcher* view_dispatcher; - Gui* gui; - NotificationApp* notifications; - SceneManager* scene_manager; - PicopassDevice* dev; - - char text_store[PICOPASS_TEXT_STORE_SIZE + 1]; - FuriString* text_box_store; - - // Common Views - Submenu* submenu; - Popup* popup; - Loading* loading; - TextInput* text_input; - Widget* widget; - DictAttack* dict_attack; -}; - -typedef enum { - PicopassViewMenu, - PicopassViewPopup, - PicopassViewLoading, - PicopassViewTextInput, - PicopassViewWidget, - PicopassViewDictAttack, -} PicopassView; - -Picopass* picopass_alloc(); - -void picopass_text_store_set(Picopass* picopass, const char* text, ...); - -void picopass_text_store_clear(Picopass* picopass); - -void picopass_blink_start(Picopass* picopass); - -void picopass_blink_stop(Picopass* picopass); - -void picopass_show_loading_popup(void* context, bool show); - -/** Check if memory is set to pattern - * - * @warning zero size will return false - * - * @param[in] data Pointer to the byte array - * @param[in] pattern The pattern - * @param[in] size The byte array size - * - * @return True if memory is set to pattern, false otherwise - */ -bool picopass_is_memset(const uint8_t* data, const uint8_t pattern, size_t size); diff --git a/applications/external/picopass/picopass_keys.c b/applications/external/picopass/picopass_keys.c deleted file mode 100644 index 43dfc631269..00000000000 --- a/applications/external/picopass/picopass_keys.c +++ /dev/null @@ -1,8 +0,0 @@ -#include "picopass_keys.h" - -const uint8_t picopass_iclass_key[] = {0xaf, 0xa7, 0x85, 0xa7, 0xda, 0xb3, 0x33, 0x78}; -const uint8_t picopass_factory_credit_key[] = {0x76, 0x65, 0x54, 0x43, 0x32, 0x21, 0x10, 0x00}; -const uint8_t picopass_factory_debit_key[] = {0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87}; -const uint8_t picopass_xice_key[] = {0x20, 0x20, 0x66, 0x66, 0x66, 0x66, 0x88, 0x88}; -const uint8_t picopass_xicl_key[] = {0x20, 0x20, 0x66, 0x66, 0x66, 0x66, 0x88, 0x88}; -const uint8_t picopass_xics_key[] = {0x66, 0x66, 0x20, 0x20, 0x66, 0x66, 0x88, 0x88}; diff --git a/applications/external/picopass/picopass_keys.h b/applications/external/picopass/picopass_keys.h deleted file mode 100644 index 2b5dba66106..00000000000 --- a/applications/external/picopass/picopass_keys.h +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once - -#include "picopass_device.h" - -extern const uint8_t picopass_iclass_key[PICOPASS_BLOCK_LEN]; -extern const uint8_t picopass_factory_credit_key[PICOPASS_BLOCK_LEN]; -extern const uint8_t picopass_factory_debit_key[PICOPASS_BLOCK_LEN]; -extern const uint8_t picopass_xice_key[PICOPASS_BLOCK_LEN]; -extern const uint8_t picopass_xicl_key[PICOPASS_BLOCK_LEN]; -extern const uint8_t picopass_xics_key[PICOPASS_BLOCK_LEN]; diff --git a/applications/external/picopass/picopass_worker.c b/applications/external/picopass/picopass_worker.c deleted file mode 100644 index 6301704ca8b..00000000000 --- a/applications/external/picopass/picopass_worker.c +++ /dev/null @@ -1,752 +0,0 @@ -#include "picopass_worker_i.h" - -#include - -#define TAG "PicopassWorker" - -static void picopass_worker_enable_field() { - furi_hal_nfc_ll_txrx_on(); - furi_hal_nfc_exit_sleep(); - furi_hal_nfc_ll_poll(); -} - -static ReturnCode picopass_worker_disable_field(ReturnCode rc) { - furi_hal_nfc_ll_txrx_off(); - furi_hal_nfc_start_sleep(); - return rc; -} - -/***************************** Picopass Worker API *******************************/ - -PicopassWorker* picopass_worker_alloc() { - PicopassWorker* picopass_worker = malloc(sizeof(PicopassWorker)); - - // Worker thread attributes - picopass_worker->thread = - furi_thread_alloc_ex("PicopassWorker", 8 * 1024, picopass_worker_task, picopass_worker); - - picopass_worker->callback = NULL; - picopass_worker->context = NULL; - picopass_worker->storage = furi_record_open(RECORD_STORAGE); - - picopass_worker_change_state(picopass_worker, PicopassWorkerStateReady); - - return picopass_worker; -} - -void picopass_worker_free(PicopassWorker* picopass_worker) { - furi_assert(picopass_worker); - - furi_thread_free(picopass_worker->thread); - - furi_record_close(RECORD_STORAGE); - - free(picopass_worker); -} - -PicopassWorkerState picopass_worker_get_state(PicopassWorker* picopass_worker) { - return picopass_worker->state; -} - -void picopass_worker_start( - PicopassWorker* picopass_worker, - PicopassWorkerState state, - PicopassDeviceData* dev_data, - PicopassWorkerCallback callback, - void* context) { - furi_assert(picopass_worker); - furi_assert(dev_data); - - picopass_worker->callback = callback; - picopass_worker->context = context; - picopass_worker->dev_data = dev_data; - picopass_worker_change_state(picopass_worker, state); - furi_thread_start(picopass_worker->thread); -} - -void picopass_worker_stop(PicopassWorker* picopass_worker) { - furi_assert(picopass_worker); - furi_assert(picopass_worker->thread); - - if(furi_thread_get_state(picopass_worker->thread) != FuriThreadStateStopped) { - picopass_worker_change_state(picopass_worker, PicopassWorkerStateStop); - furi_thread_join(picopass_worker->thread); - } -} - -void picopass_worker_change_state(PicopassWorker* picopass_worker, PicopassWorkerState state) { - picopass_worker->state = state; -} - -/***************************** Picopass Worker Thread *******************************/ - -ReturnCode picopass_detect_card(int timeout) { - UNUSED(timeout); - - ReturnCode err; - - err = rfalPicoPassPollerInitialize(); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "rfalPicoPassPollerInitialize error %d", err); - return err; - } - - err = rfalFieldOnAndStartGT(); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "rfalFieldOnAndStartGT error %d", err); - return err; - } - - err = rfalPicoPassPollerCheckPresence(); - if(err != ERR_RF_COLLISION) { - FURI_LOG_E(TAG, "rfalPicoPassPollerCheckPresence error %d", err); - return err; - } - - return ERR_NONE; -} - -ReturnCode picopass_read_preauth(PicopassBlock* AA1) { - rfalPicoPassIdentifyRes idRes; - rfalPicoPassSelectRes selRes; - - ReturnCode err; - - err = rfalPicoPassPollerIdentify(&idRes); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "rfalPicoPassPollerIdentify error %d", err); - return err; - } - - err = rfalPicoPassPollerSelect(idRes.CSN, &selRes); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "rfalPicoPassPollerSelect error %d", err); - return err; - } - - memcpy(AA1[PICOPASS_CSN_BLOCK_INDEX].data, selRes.CSN, sizeof(selRes.CSN)); - FURI_LOG_D( - TAG, - "csn %02x%02x%02x%02x%02x%02x%02x%02x", - AA1[PICOPASS_CSN_BLOCK_INDEX].data[0], - AA1[PICOPASS_CSN_BLOCK_INDEX].data[1], - AA1[PICOPASS_CSN_BLOCK_INDEX].data[2], - AA1[PICOPASS_CSN_BLOCK_INDEX].data[3], - AA1[PICOPASS_CSN_BLOCK_INDEX].data[4], - AA1[PICOPASS_CSN_BLOCK_INDEX].data[5], - AA1[PICOPASS_CSN_BLOCK_INDEX].data[6], - AA1[PICOPASS_CSN_BLOCK_INDEX].data[7]); - - rfalPicoPassReadBlockRes cfg = {0}; - rfalPicoPassPollerReadBlock(PICOPASS_CONFIG_BLOCK_INDEX, &cfg); - memcpy(AA1[PICOPASS_CONFIG_BLOCK_INDEX].data, cfg.data, sizeof(cfg.data)); - FURI_LOG_D( - TAG, - "config %02x%02x%02x%02x%02x%02x%02x%02x", - AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[0], - AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[1], - AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[2], - AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[3], - AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[4], - AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[5], - AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[6], - AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[7]); - - rfalPicoPassReadBlockRes aia; - rfalPicoPassPollerReadBlock(PICOPASS_AIA_BLOCK_INDEX, &aia); - memcpy(AA1[PICOPASS_AIA_BLOCK_INDEX].data, aia.data, sizeof(aia.data)); - FURI_LOG_D( - TAG, - "aia %02x%02x%02x%02x%02x%02x%02x%02x", - AA1[PICOPASS_AIA_BLOCK_INDEX].data[0], - AA1[PICOPASS_AIA_BLOCK_INDEX].data[1], - AA1[PICOPASS_AIA_BLOCK_INDEX].data[2], - AA1[PICOPASS_AIA_BLOCK_INDEX].data[3], - AA1[PICOPASS_AIA_BLOCK_INDEX].data[4], - AA1[PICOPASS_AIA_BLOCK_INDEX].data[5], - AA1[PICOPASS_AIA_BLOCK_INDEX].data[6], - AA1[PICOPASS_AIA_BLOCK_INDEX].data[7]); - - return ERR_NONE; -} - -static ReturnCode - picopass_auth_dict(PicopassWorker* picopass_worker, IclassEliteDictType dict_type) { - rfalPicoPassReadCheckRes rcRes; - rfalPicoPassCheckRes chkRes; - bool elite = (dict_type != IclassStandardDictTypeFlipper); - - PicopassDeviceData* dev_data = picopass_worker->dev_data; - PicopassBlock* AA1 = dev_data->AA1; - PicopassPacs* pacs = &dev_data->pacs; - - uint8_t* csn = AA1[PICOPASS_CSN_BLOCK_INDEX].data; - uint8_t* div_key = AA1[PICOPASS_KD_BLOCK_INDEX].data; - - ReturnCode err = ERR_PARAM; - - uint8_t mac[4] = {0}; - uint8_t ccnr[12] = {0}; - - size_t index = 0; - uint8_t key[PICOPASS_BLOCK_LEN] = {0}; - - if(!iclass_elite_dict_check_presence(dict_type)) { - FURI_LOG_E(TAG, "Dictionary not found"); - return ERR_PARAM; - } - - IclassEliteDict* dict = iclass_elite_dict_alloc(dict_type); - if(!dict) { - FURI_LOG_E(TAG, "Dictionary not allocated"); - return ERR_PARAM; - } - - FURI_LOG_D(TAG, "Loaded %lu keys", iclass_elite_dict_get_total_keys(dict)); - while(iclass_elite_dict_get_next_key(dict, key)) { - FURI_LOG_D( - TAG, - "Try to %s auth with key %zu %02x%02x%02x%02x%02x%02x%02x%02x", - elite ? "elite" : "standard", - index++, - key[0], - key[1], - key[2], - key[3], - key[4], - key[5], - key[6], - key[7]); - - err = rfalPicoPassPollerReadCheck(&rcRes); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "rfalPicoPassPollerReadCheck error %d", err); - break; - } - memcpy(ccnr, rcRes.CCNR, sizeof(rcRes.CCNR)); // last 4 bytes left 0 - - loclass_iclass_calc_div_key(csn, key, div_key, elite); - loclass_opt_doReaderMAC(ccnr, div_key, mac); - - err = rfalPicoPassPollerCheck(mac, &chkRes); - if(err == ERR_NONE) { - memcpy(pacs->key, key, PICOPASS_BLOCK_LEN); - break; - } - - if(picopass_worker->state != PicopassWorkerStateDetect) break; - } - - iclass_elite_dict_free(dict); - - return err; -} - -ReturnCode picopass_auth(PicopassWorker* picopass_worker) { - ReturnCode err; - - FURI_LOG_I(TAG, "Starting system dictionary attack [Standard KDF]"); - err = picopass_auth_dict(picopass_worker, IclassStandardDictTypeFlipper); - if(err == ERR_NONE) { - return ERR_NONE; - } - - FURI_LOG_I(TAG, "Starting user dictionary attack [Elite KDF]"); - err = picopass_auth_dict(picopass_worker, IclassEliteDictTypeUser); - if(err == ERR_NONE) { - return ERR_NONE; - } - - FURI_LOG_I(TAG, "Starting system dictionary attack [Elite KDF]"); - err = picopass_auth_dict(picopass_worker, IclassEliteDictTypeFlipper); - if(err == ERR_NONE) { - return ERR_NONE; - } - - return err; -} - -ReturnCode picopass_read_card(PicopassBlock* AA1) { - ReturnCode err; - - size_t app_limit = AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[0] < PICOPASS_MAX_APP_LIMIT ? - AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[0] : - PICOPASS_MAX_APP_LIMIT; - - for(size_t i = 2; i < app_limit; i++) { - if(i == PICOPASS_KD_BLOCK_INDEX) { - // Skip over Kd block which is populated earlier (READ of Kd returns all FF's) - continue; - } - - rfalPicoPassReadBlockRes block; - err = rfalPicoPassPollerReadBlock(i, &block); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "rfalPicoPassPollerReadBlock error %d", err); - return err; - } - - FURI_LOG_D( - TAG, - "rfalPicoPassPollerReadBlock %d %02x%02x%02x%02x%02x%02x%02x%02x", - i, - block.data[0], - block.data[1], - block.data[2], - block.data[3], - block.data[4], - block.data[5], - block.data[6], - block.data[7]); - - memcpy(AA1[i].data, block.data, sizeof(block.data)); - } - - return ERR_NONE; -} - -ReturnCode picopass_write_card(PicopassBlock* AA1) { - rfalPicoPassIdentifyRes idRes; - rfalPicoPassSelectRes selRes; - rfalPicoPassReadCheckRes rcRes; - rfalPicoPassCheckRes chkRes; - - ReturnCode err; - - uint8_t div_key[8] = {0}; - uint8_t mac[4] = {0}; - uint8_t ccnr[12] = {0}; - - err = rfalPicoPassPollerIdentify(&idRes); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "rfalPicoPassPollerIdentify error %d", err); - return err; - } - - err = rfalPicoPassPollerSelect(idRes.CSN, &selRes); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "rfalPicoPassPollerSelect error %d", err); - return err; - } - - err = rfalPicoPassPollerReadCheck(&rcRes); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "rfalPicoPassPollerReadCheck error %d", err); - return err; - } - memcpy(ccnr, rcRes.CCNR, sizeof(rcRes.CCNR)); // last 4 bytes left 0 - - loclass_iclass_calc_div_key(selRes.CSN, (uint8_t*)picopass_iclass_key, div_key, false); - loclass_opt_doReaderMAC(ccnr, div_key, mac); - - err = rfalPicoPassPollerCheck(mac, &chkRes); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "rfalPicoPassPollerCheck error %d", err); - return err; - } - - for(size_t i = 6; i < 10; i++) { - FURI_LOG_D(TAG, "rfalPicoPassPollerWriteBlock %d", i); - uint8_t data[9] = {0}; - data[0] = i; - memcpy(data + 1, AA1[i].data, RFAL_PICOPASS_MAX_BLOCK_LEN); - loclass_doMAC_N(data, sizeof(data), div_key, mac); - FURI_LOG_D( - TAG, - "loclass_doMAC_N %d %02x%02x%02x%02x%02x%02x%02x%02x %02x%02x%02x%02x", - i, - data[1], - data[2], - data[3], - data[4], - data[5], - data[6], - data[7], - data[8], - mac[0], - mac[1], - mac[2], - mac[3]); - - err = rfalPicoPassPollerWriteBlock(i, AA1[i].data, mac); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "rfalPicoPassPollerWriteBlock error %d", err); - return err; - } - } - - return ERR_NONE; -} - -ReturnCode picopass_write_block(PicopassBlock* AA1, uint8_t blockNo, uint8_t* newBlock) { - rfalPicoPassIdentifyRes idRes; - rfalPicoPassSelectRes selRes; - rfalPicoPassReadCheckRes rcRes; - rfalPicoPassCheckRes chkRes; - - ReturnCode err; - - uint8_t mac[4] = {0}; - uint8_t ccnr[12] = {0}; - - err = rfalPicoPassPollerIdentify(&idRes); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "rfalPicoPassPollerIdentify error %d", err); - return err; - } - - err = rfalPicoPassPollerSelect(idRes.CSN, &selRes); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "rfalPicoPassPollerSelect error %d", err); - return err; - } - - err = rfalPicoPassPollerReadCheck(&rcRes); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "rfalPicoPassPollerReadCheck error %d", err); - return err; - } - memcpy(ccnr, rcRes.CCNR, sizeof(rcRes.CCNR)); // last 4 bytes left 0 - - if(memcmp(selRes.CSN, AA1[PICOPASS_CSN_BLOCK_INDEX].data, PICOPASS_BLOCK_LEN) != 0) { - FURI_LOG_E(TAG, "Wrong CSN for write"); - return ERR_REQUEST; - } - - loclass_opt_doReaderMAC(ccnr, AA1[PICOPASS_KD_BLOCK_INDEX].data, mac); - err = rfalPicoPassPollerCheck(mac, &chkRes); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "rfalPicoPassPollerCheck error %d", err); - return err; - } - - FURI_LOG_D(TAG, "rfalPicoPassPollerWriteBlock %d", blockNo); - uint8_t data[9] = { - blockNo, - newBlock[0], - newBlock[1], - newBlock[2], - newBlock[3], - newBlock[4], - newBlock[5], - newBlock[6], - newBlock[7]}; - loclass_doMAC_N(data, sizeof(data), AA1[PICOPASS_KD_BLOCK_INDEX].data, mac); - FURI_LOG_D( - TAG, - "loclass_doMAC_N %d %02x%02x%02x%02x%02x%02x%02x%02x %02x%02x%02x%02x", - blockNo, - data[1], - data[2], - data[3], - data[4], - data[5], - data[6], - data[7], - data[8], - mac[0], - mac[1], - mac[2], - mac[3]); - - err = rfalPicoPassPollerWriteBlock(data[0], data + 1, mac); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "rfalPicoPassPollerWriteBlock error %d", err); - return err; - } - - return ERR_NONE; -} - -void picopass_worker_elite_dict_attack(PicopassWorker* picopass_worker) { - furi_assert(picopass_worker); - furi_assert(picopass_worker->callback); - - picopass_device_data_clear(picopass_worker->dev_data); - PicopassDeviceData* dev_data = picopass_worker->dev_data; - PicopassBlock* AA1 = dev_data->AA1; - PicopassPacs* pacs = &dev_data->pacs; - - for(size_t i = 0; i < PICOPASS_MAX_APP_LIMIT; i++) { - memset(AA1[i].data, 0, sizeof(AA1[i].data)); - } - memset(pacs, 0, sizeof(PicopassPacs)); - - IclassEliteDictAttackData* dict_attack_data = - &picopass_worker->dev_data->iclass_elite_dict_attack_data; - bool elite = (dict_attack_data->type != IclassStandardDictTypeFlipper); - - rfalPicoPassReadCheckRes rcRes; - rfalPicoPassCheckRes chkRes; - - ReturnCode err; - uint8_t mac[4] = {0}; - uint8_t ccnr[12] = {0}; - - size_t index = 0; - uint8_t key[PICOPASS_BLOCK_LEN] = {0}; - - // Load dictionary - IclassEliteDict* dict = dict_attack_data->dict; - if(!dict) { - FURI_LOG_E(TAG, "Dictionary not found"); - picopass_worker->callback(PicopassWorkerEventNoDictFound, picopass_worker->context); - return; - } - - do { - if(picopass_detect_card(1000) == ERR_NONE) { - picopass_worker->callback(PicopassWorkerEventCardDetected, picopass_worker->context); - - // Process first found device - err = picopass_read_preauth(AA1); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "picopass_read_preauth error %d", err); - picopass_worker->callback(PicopassWorkerEventAborted, picopass_worker->context); - return; - } - - // Thank you proxmark! - pacs->legacy = picopass_is_memset(AA1[5].data, 0xFF, 8); - pacs->se_enabled = (memcmp(AA1[5].data, "\xff\xff\xff\x00\x06\xff\xff\xff", 8) == 0); - if(pacs->se_enabled) { - FURI_LOG_D(TAG, "SE enabled"); - picopass_worker->callback(PicopassWorkerEventAborted, picopass_worker->context); - return; - } - - break; - } else { - picopass_worker->callback(PicopassWorkerEventNoCardDetected, picopass_worker->context); - } - if(picopass_worker->state != PicopassWorkerStateEliteDictAttack) break; - - furi_delay_ms(100); - } while(true); - - FURI_LOG_D( - TAG, "Start Dictionary attack, Key Count %lu", iclass_elite_dict_get_total_keys(dict)); - while(iclass_elite_dict_get_next_key(dict, key)) { - FURI_LOG_T(TAG, "Key %zu", index); - if(++index % PICOPASS_DICT_KEY_BATCH_SIZE == 0) { - picopass_worker->callback( - PicopassWorkerEventNewDictKeyBatch, picopass_worker->context); - } - - err = rfalPicoPassPollerReadCheck(&rcRes); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "rfalPicoPassPollerReadCheck error %d", err); - break; - } - memcpy(ccnr, rcRes.CCNR, sizeof(rcRes.CCNR)); // last 4 bytes left 0 - - uint8_t* csn = AA1[PICOPASS_CSN_BLOCK_INDEX].data; - uint8_t* div_key = AA1[PICOPASS_KD_BLOCK_INDEX].data; - - loclass_iclass_calc_div_key(csn, key, div_key, elite); - loclass_opt_doReaderMAC(ccnr, div_key, mac); - - err = rfalPicoPassPollerCheck(mac, &chkRes); - if(err == ERR_NONE) { - FURI_LOG_I(TAG, "Found key"); - memcpy(pacs->key, key, PICOPASS_BLOCK_LEN); - pacs->elite_kdf = elite; - err = picopass_read_card(AA1); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "picopass_read_card error %d", err); - picopass_worker->callback(PicopassWorkerEventFail, picopass_worker->context); - break; - } - - err = picopass_device_parse_credential(AA1, pacs); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "picopass_device_parse_credential error %d", err); - picopass_worker->callback(PicopassWorkerEventFail, picopass_worker->context); - break; - } - - err = picopass_device_parse_wiegand(pacs->credential, &pacs->record); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "picopass_device_parse_wiegand error %d", err); - picopass_worker->callback(PicopassWorkerEventFail, picopass_worker->context); - break; - } - picopass_worker->callback(PicopassWorkerEventAborted, picopass_worker->context); - break; - } - - if(picopass_worker->state != PicopassWorkerStateEliteDictAttack) break; - } - FURI_LOG_D(TAG, "Dictionary complete"); - if(picopass_worker->state == PicopassWorkerStateEliteDictAttack) { - picopass_worker->callback(PicopassWorkerEventSuccess, picopass_worker->context); - } else { - picopass_worker->callback(PicopassWorkerEventAborted, picopass_worker->context); - } -} - -int32_t picopass_worker_task(void* context) { - PicopassWorker* picopass_worker = context; - - picopass_worker_enable_field(); - if(picopass_worker->state == PicopassWorkerStateDetect) { - picopass_worker_detect(picopass_worker); - } else if(picopass_worker->state == PicopassWorkerStateWrite) { - picopass_worker_write(picopass_worker); - } else if(picopass_worker->state == PicopassWorkerStateWriteKey) { - picopass_worker_write_key(picopass_worker); - } else if(picopass_worker->state == PicopassWorkerStateEliteDictAttack) { - picopass_worker_elite_dict_attack(picopass_worker); - } else if(picopass_worker->state == PicopassWorkerStateStop) { - FURI_LOG_D(TAG, "Worker state stop"); - // no-op - } else { - FURI_LOG_W(TAG, "Unknown state %d", picopass_worker->state); - } - picopass_worker_disable_field(ERR_NONE); - picopass_worker_change_state(picopass_worker, PicopassWorkerStateReady); - - return 0; -} - -void picopass_worker_detect(PicopassWorker* picopass_worker) { - picopass_device_data_clear(picopass_worker->dev_data); - PicopassDeviceData* dev_data = picopass_worker->dev_data; - - PicopassBlock* AA1 = dev_data->AA1; - PicopassPacs* pacs = &dev_data->pacs; - ReturnCode err; - - // reset device data - for(size_t i = 0; i < PICOPASS_MAX_APP_LIMIT; i++) { - memset(AA1[i].data, 0, sizeof(AA1[i].data)); - } - memset(pacs, 0, sizeof(PicopassPacs)); - - PicopassWorkerEvent nextState = PicopassWorkerEventSuccess; - - while(picopass_worker->state == PicopassWorkerStateDetect) { - if(picopass_detect_card(1000) == ERR_NONE) { - // Process first found device - err = picopass_read_preauth(AA1); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "picopass_read_preauth error %d", err); - nextState = PicopassWorkerEventFail; - } - - // Thank you proxmark! - pacs->legacy = picopass_is_memset(AA1[5].data, 0xFF, 8); - pacs->se_enabled = (memcmp(AA1[5].data, "\xff\xff\xff\x00\x06\xff\xff\xff", 8) == 0); - if(pacs->se_enabled) { - FURI_LOG_D(TAG, "SE enabled"); - nextState = PicopassWorkerEventFail; - } - - if(nextState == PicopassWorkerEventSuccess) { - err = picopass_auth(picopass_worker); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "picopass_try_auth error %d", err); - nextState = PicopassWorkerEventFail; - } - } - - if(nextState == PicopassWorkerEventSuccess) { - err = picopass_read_card(AA1); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "picopass_read_card error %d", err); - nextState = PicopassWorkerEventFail; - } - } - - if(nextState == PicopassWorkerEventSuccess) { - err = picopass_device_parse_credential(AA1, pacs); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "picopass_device_parse_credential error %d", err); - nextState = PicopassWorkerEventFail; - } - } - - if(nextState == PicopassWorkerEventSuccess) { - err = picopass_device_parse_wiegand(pacs->credential, &pacs->record); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "picopass_device_parse_wiegand error %d", err); - nextState = PicopassWorkerEventFail; - } - } - - // Notify caller and exit - if(picopass_worker->callback) { - picopass_worker->callback(nextState, picopass_worker->context); - } - break; - } - furi_delay_ms(100); - } -} - -void picopass_worker_write(PicopassWorker* picopass_worker) { - PicopassDeviceData* dev_data = picopass_worker->dev_data; - PicopassBlock* AA1 = dev_data->AA1; - ReturnCode err; - PicopassWorkerEvent nextState = PicopassWorkerEventSuccess; - - while(picopass_worker->state == PicopassWorkerStateWrite) { - if(picopass_detect_card(1000) == ERR_NONE) { - err = picopass_write_card(AA1); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "picopass_write_card error %d", err); - nextState = PicopassWorkerEventFail; - } - - // Notify caller and exit - if(picopass_worker->callback) { - picopass_worker->callback(nextState, picopass_worker->context); - } - break; - } - furi_delay_ms(100); - } -} - -void picopass_worker_write_key(PicopassWorker* picopass_worker) { - PicopassDeviceData* dev_data = picopass_worker->dev_data; - PicopassBlock* AA1 = dev_data->AA1; - PicopassPacs* pacs = &dev_data->pacs; - ReturnCode err; - PicopassWorkerEvent nextState = PicopassWorkerEventSuccess; - - uint8_t* csn = AA1[PICOPASS_CSN_BLOCK_INDEX].data; - uint8_t* configBlock = AA1[PICOPASS_CONFIG_BLOCK_INDEX].data; - uint8_t fuses = configBlock[7]; - uint8_t* oldKey = AA1[PICOPASS_KD_BLOCK_INDEX].data; - - uint8_t newKey[PICOPASS_BLOCK_LEN] = {0}; - loclass_iclass_calc_div_key(csn, pacs->key, newKey, pacs->elite_kdf); - - if((fuses & 0x80) == 0x80) { - FURI_LOG_D(TAG, "Plain write for personalized mode key change"); - } else { - FURI_LOG_D(TAG, "XOR write for application mode key change"); - // XOR when in application mode - for(size_t i = 0; i < PICOPASS_BLOCK_LEN; i++) { - newKey[i] ^= oldKey[i]; - } - } - - while(picopass_worker->state == PicopassWorkerStateWriteKey) { - if(picopass_detect_card(1000) == ERR_NONE) { - err = picopass_write_block(AA1, PICOPASS_KD_BLOCK_INDEX, newKey); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "picopass_write_block error %d", err); - nextState = PicopassWorkerEventFail; - } - - // Notify caller and exit - if(picopass_worker->callback) { - picopass_worker->callback(nextState, picopass_worker->context); - } - break; - } - furi_delay_ms(100); - } -} diff --git a/applications/external/picopass/picopass_worker.h b/applications/external/picopass/picopass_worker.h deleted file mode 100644 index e9d37481b19..00000000000 --- a/applications/external/picopass/picopass_worker.h +++ /dev/null @@ -1,52 +0,0 @@ -#pragma once - -#include "picopass_device.h" -#include "picopass_keys.h" - -typedef struct PicopassWorker PicopassWorker; - -typedef enum { - // Init states - PicopassWorkerStateNone, - PicopassWorkerStateBroken, - PicopassWorkerStateReady, - // Main worker states - PicopassWorkerStateDetect, - PicopassWorkerStateWrite, - PicopassWorkerStateWriteKey, - PicopassWorkerStateEliteDictAttack, - // Transition - PicopassWorkerStateStop, -} PicopassWorkerState; - -typedef enum { - // Reserve first 50 events for application events - PicopassWorkerEventReserved = 50, - - // Picopass worker common events - PicopassWorkerEventSuccess, - PicopassWorkerEventFail, - PicopassWorkerEventNoCardDetected, - PicopassWorkerEventSeEnabled, - PicopassWorkerEventAborted, - PicopassWorkerEventCardDetected, - PicopassWorkerEventNewDictKeyBatch, - PicopassWorkerEventNoDictFound, -} PicopassWorkerEvent; - -typedef void (*PicopassWorkerCallback)(PicopassWorkerEvent event, void* context); - -PicopassWorker* picopass_worker_alloc(); - -PicopassWorkerState picopass_worker_get_state(PicopassWorker* picopass_worker); - -void picopass_worker_free(PicopassWorker* picopass_worker); - -void picopass_worker_start( - PicopassWorker* picopass_worker, - PicopassWorkerState state, - PicopassDeviceData* dev_data, - PicopassWorkerCallback callback, - void* context); - -void picopass_worker_stop(PicopassWorker* picopass_worker); diff --git a/applications/external/picopass/picopass_worker_i.h b/applications/external/picopass/picopass_worker_i.h deleted file mode 100644 index f41cfce45d4..00000000000 --- a/applications/external/picopass/picopass_worker_i.h +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once - -#include "picopass_worker.h" -#include "picopass_i.h" - -#include -#include - -#include - -#include -#include - -#include - -struct PicopassWorker { - FuriThread* thread; - Storage* storage; - Stream* dict_stream; - - PicopassDeviceData* dev_data; - PicopassWorkerCallback callback; - void* context; - - PicopassWorkerState state; -}; - -void picopass_worker_change_state(PicopassWorker* picopass_worker, PicopassWorkerState state); - -int32_t picopass_worker_task(void* context); - -void picopass_worker_detect(PicopassWorker* picopass_worker); -void picopass_worker_write(PicopassWorker* picopass_worker); -void picopass_worker_write_key(PicopassWorker* picopass_worker); diff --git a/applications/external/picopass/rfal_picopass.c b/applications/external/picopass/rfal_picopass.c deleted file mode 100644 index ac66cb92d97..00000000000 --- a/applications/external/picopass/rfal_picopass.c +++ /dev/null @@ -1,214 +0,0 @@ -#include "rfal_picopass.h" - -#define RFAL_PICOPASS_TXRX_FLAGS \ - (FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_TX_MANUAL | FURI_HAL_NFC_LL_TXRX_FLAGS_AGC_ON | \ - FURI_HAL_NFC_LL_TXRX_FLAGS_PAR_RX_REMV | FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_RX_KEEP) - -#define TAG "RFAL_PICOPASS" - -typedef struct { - uint8_t CMD; - uint8_t CSN[RFAL_PICOPASS_UID_LEN]; -} rfalPicoPassSelectReq; - -typedef struct { - uint8_t CMD; - uint8_t null[4]; - uint8_t mac[4]; -} rfalPicoPassCheckReq; - -static uint16_t rfalPicoPassUpdateCcitt(uint16_t crcSeed, uint8_t dataByte) { - uint16_t crc = crcSeed; - uint8_t dat = dataByte; - - dat ^= (uint8_t)(crc & 0xFFU); - dat ^= (dat << 4); - - crc = (crc >> 8) ^ (((uint16_t)dat) << 8) ^ (((uint16_t)dat) << 3) ^ (((uint16_t)dat) >> 4); - - return crc; -} - -static uint16_t - rfalPicoPassCalculateCcitt(uint16_t preloadValue, const uint8_t* buf, uint16_t length) { - uint16_t crc = preloadValue; - uint16_t index; - - for(index = 0; index < length; index++) { - crc = rfalPicoPassUpdateCcitt(crc, buf[index]); - } - - return crc; -} - -FuriHalNfcReturn rfalPicoPassPollerInitialize(void) { - FuriHalNfcReturn ret; - - ret = furi_hal_nfc_ll_set_mode( - FuriHalNfcModePollPicopass, FuriHalNfcBitrate26p48, FuriHalNfcBitrate26p48); - if(ret != FuriHalNfcReturnOk) { - return ret; - }; - - furi_hal_nfc_ll_set_error_handling(FuriHalNfcErrorHandlingNfc); - furi_hal_nfc_ll_set_guard_time(FURI_HAL_NFC_LL_GT_PICOPASS); - furi_hal_nfc_ll_set_fdt_listen(FURI_HAL_NFC_LL_FDT_LISTEN_PICOPASS_POLLER); - furi_hal_nfc_ll_set_fdt_poll(FURI_HAL_NFC_LL_FDT_POLL_PICOPASS_POLLER); - - return FuriHalNfcReturnOk; -} - -FuriHalNfcReturn rfalPicoPassPollerCheckPresence(void) { - FuriHalNfcReturn ret; - uint8_t txBuf[1] = {RFAL_PICOPASS_CMD_ACTALL}; - uint8_t rxBuf[32] = {0}; - uint16_t recvLen = 0; - uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS; - uint32_t fwt = furi_hal_nfc_ll_ms2fc(20); - - ret = furi_hal_nfc_ll_txrx(txBuf, 1, rxBuf, 32, &recvLen, flags, fwt); - return ret; -} - -FuriHalNfcReturn rfalPicoPassPollerIdentify(rfalPicoPassIdentifyRes* idRes) { - FuriHalNfcReturn ret; - - uint8_t txBuf[1] = {RFAL_PICOPASS_CMD_IDENTIFY}; - uint16_t recvLen = 0; - uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS; - uint32_t fwt = furi_hal_nfc_ll_ms2fc(20); - - ret = furi_hal_nfc_ll_txrx( - txBuf, - sizeof(txBuf), - (uint8_t*)idRes, - sizeof(rfalPicoPassIdentifyRes), - &recvLen, - flags, - fwt); - // printf("identify rx: %d %s\n", recvLen, hex2Str(idRes->CSN, RFAL_PICOPASS_UID_LEN)); - - return ret; -} - -FuriHalNfcReturn rfalPicoPassPollerSelect(uint8_t* csn, rfalPicoPassSelectRes* selRes) { - FuriHalNfcReturn ret; - - rfalPicoPassSelectReq selReq; - selReq.CMD = RFAL_PICOPASS_CMD_SELECT; - memcpy(selReq.CSN, csn, RFAL_PICOPASS_UID_LEN); - uint16_t recvLen = 0; - uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS; - uint32_t fwt = furi_hal_nfc_ll_ms2fc(20); - - ret = furi_hal_nfc_ll_txrx( - (uint8_t*)&selReq, - sizeof(rfalPicoPassSelectReq), - (uint8_t*)selRes, - sizeof(rfalPicoPassSelectRes), - &recvLen, - flags, - fwt); - // printf("select rx: %d %s\n", recvLen, hex2Str(selRes->CSN, RFAL_PICOPASS_UID_LEN)); - if(ret == FuriHalNfcReturnTimeout) { - return FuriHalNfcReturnOk; - } - - return ret; -} - -FuriHalNfcReturn rfalPicoPassPollerReadCheck(rfalPicoPassReadCheckRes* rcRes) { - FuriHalNfcReturn ret; - uint8_t txBuf[2] = {RFAL_PICOPASS_CMD_READCHECK, 0x02}; - uint16_t recvLen = 0; - uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS; - uint32_t fwt = furi_hal_nfc_ll_ms2fc(20); - - ret = furi_hal_nfc_ll_txrx( - txBuf, - sizeof(txBuf), - (uint8_t*)rcRes, - sizeof(rfalPicoPassReadCheckRes), - &recvLen, - flags, - fwt); - // printf("readcheck rx: %d %s\n", recvLen, hex2Str(rcRes->CCNR, 8)); - - if(ret == FuriHalNfcReturnCrc) { - return FuriHalNfcReturnOk; - } - - return ret; -} - -FuriHalNfcReturn rfalPicoPassPollerCheck(uint8_t* mac, rfalPicoPassCheckRes* chkRes) { - FuriHalNfcReturn ret; - rfalPicoPassCheckReq chkReq; - chkReq.CMD = RFAL_PICOPASS_CMD_CHECK; - memcpy(chkReq.mac, mac, 4); - memset(chkReq.null, 0, 4); - uint16_t recvLen = 0; - uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS; - uint32_t fwt = furi_hal_nfc_ll_ms2fc(20); - - // printf("check tx: %s\n", hex2Str((uint8_t *)&chkReq, sizeof(rfalPicoPassCheckReq))); - ret = furi_hal_nfc_ll_txrx( - (uint8_t*)&chkReq, - sizeof(rfalPicoPassCheckReq), - (uint8_t*)chkRes, - sizeof(rfalPicoPassCheckRes), - &recvLen, - flags, - fwt); - // printf("check rx: %d %s\n", recvLen, hex2Str(chkRes->mac, 4)); - if(ret == FuriHalNfcReturnCrc) { - return FuriHalNfcReturnOk; - } - - return ret; -} - -FuriHalNfcReturn rfalPicoPassPollerReadBlock(uint8_t blockNum, rfalPicoPassReadBlockRes* readRes) { - FuriHalNfcReturn ret; - - uint8_t txBuf[4] = {RFAL_PICOPASS_CMD_READ, 0, 0, 0}; - txBuf[1] = blockNum; - uint16_t crc = rfalPicoPassCalculateCcitt(0xE012, txBuf + 1, 1); - memcpy(txBuf + 2, &crc, sizeof(uint16_t)); - - uint16_t recvLen = 0; - uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS; - uint32_t fwt = furi_hal_nfc_ll_ms2fc(20); - - ret = furi_hal_nfc_ll_txrx( - txBuf, - sizeof(txBuf), - (uint8_t*)readRes, - sizeof(rfalPicoPassReadBlockRes), - &recvLen, - flags, - fwt); - return ret; -} - -FuriHalNfcReturn rfalPicoPassPollerWriteBlock(uint8_t blockNum, uint8_t data[8], uint8_t mac[4]) { - FuriHalNfcReturn ret; - - uint8_t txBuf[14] = {RFAL_PICOPASS_CMD_WRITE, blockNum, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - memcpy(txBuf + 2, data, RFAL_PICOPASS_MAX_BLOCK_LEN); - memcpy(txBuf + 10, mac, 4); - - uint16_t recvLen = 0; - uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS; - uint32_t fwt = furi_hal_nfc_ll_ms2fc(20); - rfalPicoPassReadBlockRes block; - - ret = furi_hal_nfc_ll_txrx( - txBuf, sizeof(txBuf), (uint8_t*)&block, sizeof(block), &recvLen, flags, fwt); - - if(ret == FuriHalNfcReturnOk) { - // TODO: compare response - } - - return ret; -} diff --git a/applications/external/picopass/rfal_picopass.h b/applications/external/picopass/rfal_picopass.h deleted file mode 100644 index 6926b2a79d6..00000000000 --- a/applications/external/picopass/rfal_picopass.h +++ /dev/null @@ -1,48 +0,0 @@ -#pragma once - -#include - -#define RFAL_PICOPASS_UID_LEN 8 -#define RFAL_PICOPASS_MAX_BLOCK_LEN 8 - -enum { - RFAL_PICOPASS_CMD_ACTALL = 0x0A, - RFAL_PICOPASS_CMD_IDENTIFY = 0x0C, - RFAL_PICOPASS_CMD_SELECT = 0x81, - RFAL_PICOPASS_CMD_READCHECK = 0x88, - RFAL_PICOPASS_CMD_CHECK = 0x05, - RFAL_PICOPASS_CMD_READ = 0x0C, - RFAL_PICOPASS_CMD_WRITE = 0x87, -}; - -typedef struct { - uint8_t CSN[RFAL_PICOPASS_UID_LEN]; // Anti-collision CSN - uint8_t crc[2]; -} rfalPicoPassIdentifyRes; - -typedef struct { - uint8_t CSN[RFAL_PICOPASS_UID_LEN]; // Real CSN - uint8_t crc[2]; -} rfalPicoPassSelectRes; - -typedef struct { - uint8_t CCNR[8]; -} rfalPicoPassReadCheckRes; - -typedef struct { - uint8_t mac[4]; -} rfalPicoPassCheckRes; - -typedef struct { - uint8_t data[RFAL_PICOPASS_MAX_BLOCK_LEN]; - uint8_t crc[2]; -} rfalPicoPassReadBlockRes; - -FuriHalNfcReturn rfalPicoPassPollerInitialize(void); -FuriHalNfcReturn rfalPicoPassPollerCheckPresence(void); -FuriHalNfcReturn rfalPicoPassPollerIdentify(rfalPicoPassIdentifyRes* idRes); -FuriHalNfcReturn rfalPicoPassPollerSelect(uint8_t* csn, rfalPicoPassSelectRes* selRes); -FuriHalNfcReturn rfalPicoPassPollerReadCheck(rfalPicoPassReadCheckRes* rcRes); -FuriHalNfcReturn rfalPicoPassPollerCheck(uint8_t* mac, rfalPicoPassCheckRes* chkRes); -FuriHalNfcReturn rfalPicoPassPollerReadBlock(uint8_t blockNum, rfalPicoPassReadBlockRes* readRes); -FuriHalNfcReturn rfalPicoPassPollerWriteBlock(uint8_t blockNum, uint8_t data[8], uint8_t mac[4]); diff --git a/applications/external/picopass/scenes/picopass_scene.c b/applications/external/picopass/scenes/picopass_scene.c deleted file mode 100644 index 61bd5e8fea4..00000000000 --- a/applications/external/picopass/scenes/picopass_scene.c +++ /dev/null @@ -1,30 +0,0 @@ -#include "picopass_scene.h" - -// Generate scene on_enter handlers array -#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, -void (*const picopass_on_enter_handlers[])(void*) = { -#include "picopass_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 picopass_on_event_handlers[])(void* context, SceneManagerEvent event) = { -#include "picopass_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 picopass_on_exit_handlers[])(void* context) = { -#include "picopass_scene_config.h" -}; -#undef ADD_SCENE - -// Initialize scene handlers configuration structure -const SceneManagerHandlers picopass_scene_handlers = { - .on_enter_handlers = picopass_on_enter_handlers, - .on_event_handlers = picopass_on_event_handlers, - .on_exit_handlers = picopass_on_exit_handlers, - .scene_num = PicopassSceneNum, -}; diff --git a/applications/external/picopass/scenes/picopass_scene.h b/applications/external/picopass/scenes/picopass_scene.h deleted file mode 100644 index 2faa80b122f..00000000000 --- a/applications/external/picopass/scenes/picopass_scene.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -#include - -// Generate scene id and total number -#define ADD_SCENE(prefix, name, id) PicopassScene##id, -typedef enum { -#include "picopass_scene_config.h" - PicopassSceneNum, -} PicopassScene; -#undef ADD_SCENE - -extern const SceneManagerHandlers picopass_scene_handlers; - -// Generate scene on_enter handlers declaration -#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); -#include "picopass_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 "picopass_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 "picopass_scene_config.h" -#undef ADD_SCENE diff --git a/applications/external/picopass/scenes/picopass_scene_card_menu.c b/applications/external/picopass/scenes/picopass_scene_card_menu.c deleted file mode 100644 index fe63f7c86bb..00000000000 --- a/applications/external/picopass/scenes/picopass_scene_card_menu.c +++ /dev/null @@ -1,78 +0,0 @@ -#include "../picopass_i.h" - -enum SubmenuIndex { - SubmenuIndexSave, - SubmenuIndexSaveAsLF, - SubmenuIndexChangeKey, -}; - -void picopass_scene_card_menu_submenu_callback(void* context, uint32_t index) { - Picopass* picopass = context; - - view_dispatcher_send_custom_event(picopass->view_dispatcher, index); -} - -void picopass_scene_card_menu_on_enter(void* context) { - Picopass* picopass = context; - Submenu* submenu = picopass->submenu; - - submenu_add_item( - submenu, "Save", SubmenuIndexSave, picopass_scene_card_menu_submenu_callback, picopass); - if(picopass->dev->dev_data.pacs.record.valid) { - submenu_add_item( - submenu, - "Save as LF", - SubmenuIndexSaveAsLF, - picopass_scene_card_menu_submenu_callback, - picopass); - } - submenu_add_item( - submenu, - "Change Key", - SubmenuIndexChangeKey, - picopass_scene_card_menu_submenu_callback, - picopass); - - submenu_set_selected_item( - picopass->submenu, - scene_manager_get_scene_state(picopass->scene_manager, PicopassSceneCardMenu)); - - view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewMenu); -} - -bool picopass_scene_card_menu_on_event(void* context, SceneManagerEvent event) { - Picopass* picopass = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SubmenuIndexSave) { - scene_manager_set_scene_state( - picopass->scene_manager, PicopassSceneCardMenu, SubmenuIndexSave); - scene_manager_next_scene(picopass->scene_manager, PicopassSceneSaveName); - picopass->dev->format = PicopassDeviceSaveFormatHF; - consumed = true; - } else if(event.event == SubmenuIndexSaveAsLF) { - scene_manager_set_scene_state( - picopass->scene_manager, PicopassSceneCardMenu, SubmenuIndexSaveAsLF); - picopass->dev->format = PicopassDeviceSaveFormatLF; - scene_manager_next_scene(picopass->scene_manager, PicopassSceneSaveName); - consumed = true; - } else if(event.event == SubmenuIndexChangeKey) { - scene_manager_set_scene_state( - picopass->scene_manager, PicopassSceneCardMenu, SubmenuIndexChangeKey); - scene_manager_next_scene(picopass->scene_manager, PicopassSceneKeyMenu); - consumed = true; - } - } else if(event.type == SceneManagerEventTypeBack) { - consumed = scene_manager_search_and_switch_to_previous_scene( - picopass->scene_manager, PicopassSceneStart); - } - - return consumed; -} - -void picopass_scene_card_menu_on_exit(void* context) { - Picopass* picopass = context; - - submenu_reset(picopass->submenu); -} diff --git a/applications/external/picopass/scenes/picopass_scene_config.h b/applications/external/picopass/scenes/picopass_scene_config.h deleted file mode 100644 index 8ea97049856..00000000000 --- a/applications/external/picopass/scenes/picopass_scene_config.h +++ /dev/null @@ -1,17 +0,0 @@ -ADD_SCENE(picopass, start, Start) -ADD_SCENE(picopass, read_card, ReadCard) -ADD_SCENE(picopass, read_card_success, ReadCardSuccess) -ADD_SCENE(picopass, card_menu, CardMenu) -ADD_SCENE(picopass, save_name, SaveName) -ADD_SCENE(picopass, save_success, SaveSuccess) -ADD_SCENE(picopass, saved_menu, SavedMenu) -ADD_SCENE(picopass, file_select, FileSelect) -ADD_SCENE(picopass, device_info, DeviceInfo) -ADD_SCENE(picopass, delete, Delete) -ADD_SCENE(picopass, delete_success, DeleteSuccess) -ADD_SCENE(picopass, write_card, WriteCard) -ADD_SCENE(picopass, write_card_success, WriteCardSuccess) -ADD_SCENE(picopass, read_factory_success, ReadFactorySuccess) -ADD_SCENE(picopass, write_key, WriteKey) -ADD_SCENE(picopass, key_menu, KeyMenu) -ADD_SCENE(picopass, elite_dict_attack, EliteDictAttack) diff --git a/applications/external/picopass/scenes/picopass_scene_delete.c b/applications/external/picopass/scenes/picopass_scene_delete.c deleted file mode 100644 index fb23cb5d4de..00000000000 --- a/applications/external/picopass/scenes/picopass_scene_delete.c +++ /dev/null @@ -1,58 +0,0 @@ -#include "../picopass_i.h" - -void picopass_scene_delete_widget_callback(GuiButtonType result, InputType type, void* context) { - Picopass* picopass = context; - if(type == InputTypeShort) { - view_dispatcher_send_custom_event(picopass->view_dispatcher, result); - } -} - -void picopass_scene_delete_on_enter(void* context) { - Picopass* picopass = context; - - // Setup Custom Widget view - char temp_str[64]; - snprintf(temp_str, sizeof(temp_str), "\e#Delete %s?\e#", picopass->dev->dev_name); - widget_add_text_box_element( - picopass->widget, 0, 0, 128, 23, AlignCenter, AlignCenter, temp_str, false); - widget_add_button_element( - picopass->widget, - GuiButtonTypeLeft, - "Back", - picopass_scene_delete_widget_callback, - picopass); - widget_add_button_element( - picopass->widget, - GuiButtonTypeRight, - "Delete", - picopass_scene_delete_widget_callback, - picopass); - - view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewWidget); -} - -bool picopass_scene_delete_on_event(void* context, SceneManagerEvent event) { - Picopass* picopass = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == GuiButtonTypeLeft) { - return scene_manager_previous_scene(picopass->scene_manager); - } else if(event.event == GuiButtonTypeRight) { - if(picopass_device_delete(picopass->dev, true)) { - scene_manager_next_scene(picopass->scene_manager, PicopassSceneDeleteSuccess); - } else { - scene_manager_search_and_switch_to_previous_scene( - picopass->scene_manager, PicopassSceneStart); - } - consumed = true; - } - } - return consumed; -} - -void picopass_scene_delete_on_exit(void* context) { - Picopass* picopass = context; - - widget_reset(picopass->widget); -} diff --git a/applications/external/picopass/scenes/picopass_scene_delete_success.c b/applications/external/picopass/scenes/picopass_scene_delete_success.c deleted file mode 100644 index f2a36a7fb1e..00000000000 --- a/applications/external/picopass/scenes/picopass_scene_delete_success.c +++ /dev/null @@ -1,40 +0,0 @@ -#include "../picopass_i.h" - -void picopass_scene_delete_success_popup_callback(void* context) { - Picopass* picopass = context; - view_dispatcher_send_custom_event(picopass->view_dispatcher, PicopassCustomEventViewExit); -} - -void picopass_scene_delete_success_on_enter(void* context) { - Picopass* picopass = context; - - // Setup view - Popup* popup = picopass->popup; - popup_set_icon(popup, 0, 2, &I_DolphinMafia_115x62); - popup_set_header(popup, "Deleted", 83, 19, AlignLeft, AlignBottom); - popup_set_timeout(popup, 1500); - popup_set_context(popup, picopass); - popup_set_callback(popup, picopass_scene_delete_success_popup_callback); - popup_enable_timeout(popup); - view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewPopup); -} - -bool picopass_scene_delete_success_on_event(void* context, SceneManagerEvent event) { - Picopass* picopass = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == PicopassCustomEventViewExit) { - consumed = scene_manager_search_and_switch_to_previous_scene( - picopass->scene_manager, PicopassSceneStart); - } - } - return consumed; -} - -void picopass_scene_delete_success_on_exit(void* context) { - Picopass* picopass = context; - - // Clear view - popup_reset(picopass->popup); -} diff --git a/applications/external/picopass/scenes/picopass_scene_device_info.c b/applications/external/picopass/scenes/picopass_scene_device_info.c deleted file mode 100644 index bb149aa6b37..00000000000 --- a/applications/external/picopass/scenes/picopass_scene_device_info.c +++ /dev/null @@ -1,114 +0,0 @@ -#include "../picopass_i.h" -#include - -void picopass_scene_device_info_widget_callback( - GuiButtonType result, - InputType type, - void* context) { - Picopass* picopass = context; - if(type == InputTypeShort) { - view_dispatcher_send_custom_event(picopass->view_dispatcher, result); - } -} - -void picopass_scene_device_info_on_enter(void* context) { - Picopass* picopass = context; - - FuriString* csn_str = furi_string_alloc_set("CSN:"); - FuriString* credential_str = furi_string_alloc(); - FuriString* wiegand_str = furi_string_alloc(); - FuriString* sio_str = furi_string_alloc(); - - dolphin_deed(DolphinDeedNfcReadSuccess); - - // Setup view - PicopassBlock* AA1 = picopass->dev->dev_data.AA1; - PicopassPacs* pacs = &picopass->dev->dev_data.pacs; - Widget* widget = picopass->widget; - - uint8_t csn[PICOPASS_BLOCK_LEN] = {0}; - memcpy(csn, AA1[PICOPASS_CSN_BLOCK_INDEX].data, PICOPASS_BLOCK_LEN); - for(uint8_t i = 0; i < PICOPASS_BLOCK_LEN; i++) { - furi_string_cat_printf(csn_str, "%02X ", csn[i]); - } - - if(pacs->record.bitLength == 0 || pacs->record.bitLength == 255) { - // Neither of these are valid. Indicates the block was all 0x00 or all 0xff - furi_string_cat_printf(wiegand_str, "Invalid PACS"); - } else { - size_t bytesLength = pacs->record.bitLength / 8; - if(pacs->record.bitLength % 8 > 0) { - // Add extra byte if there are bits remaining - bytesLength++; - } - furi_string_set(credential_str, ""); - for(uint8_t i = PICOPASS_BLOCK_LEN - bytesLength; i < PICOPASS_BLOCK_LEN; i++) { - furi_string_cat_printf(credential_str, " %02X", pacs->credential[i]); - } - - if(pacs->record.valid) { - furi_string_cat_printf( - wiegand_str, "FC: %u CN: %u", pacs->record.FacilityCode, pacs->record.CardNumber); - } else { - furi_string_cat_printf(wiegand_str, "%d bits", pacs->record.bitLength); - } - - if(pacs->sio) { - furi_string_cat_printf(sio_str, "+SIO"); - } - } - - widget_add_string_element( - widget, 64, 5, AlignCenter, AlignCenter, FontSecondary, furi_string_get_cstr(csn_str)); - widget_add_string_element( - widget, 64, 20, AlignCenter, AlignCenter, FontPrimary, furi_string_get_cstr(wiegand_str)); - widget_add_string_element( - widget, - 64, - 36, - AlignCenter, - AlignCenter, - FontSecondary, - furi_string_get_cstr(credential_str)); - widget_add_string_element( - widget, 64, 46, AlignCenter, AlignCenter, FontSecondary, furi_string_get_cstr(sio_str)); - - furi_string_free(csn_str); - furi_string_free(credential_str); - furi_string_free(wiegand_str); - furi_string_free(sio_str); - - widget_add_button_element( - picopass->widget, - GuiButtonTypeLeft, - "Back", - picopass_scene_device_info_widget_callback, - picopass); - - view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewWidget); -} - -bool picopass_scene_device_info_on_event(void* context, SceneManagerEvent event) { - Picopass* picopass = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == GuiButtonTypeLeft) { - consumed = scene_manager_previous_scene(picopass->scene_manager); - } else if(event.event == PicopassCustomEventViewExit) { - view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewWidget); - consumed = true; - } - } else if(event.type == SceneManagerEventTypeBack) { - view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewWidget); - consumed = true; - } - return consumed; -} - -void picopass_scene_device_info_on_exit(void* context) { - Picopass* picopass = context; - - // Clear views - widget_reset(picopass->widget); -} diff --git a/applications/external/picopass/scenes/picopass_scene_elite_dict_attack.c b/applications/external/picopass/scenes/picopass_scene_elite_dict_attack.c deleted file mode 100644 index e6191d5baf4..00000000000 --- a/applications/external/picopass/scenes/picopass_scene_elite_dict_attack.c +++ /dev/null @@ -1,172 +0,0 @@ -#include "../picopass_i.h" -#include - -#define TAG "IclassEliteDictAttack" - -typedef enum { - DictAttackStateIdle, - DictAttackStateUserDictInProgress, - DictAttackStateFlipperDictInProgress, - DictAttackStateStandardDictInProgress, -} DictAttackState; - -void picopass_dict_attack_worker_callback(PicopassWorkerEvent event, void* context) { - furi_assert(context); - Picopass* picopass = context; - view_dispatcher_send_custom_event(picopass->view_dispatcher, event); -} - -void picopass_dict_attack_result_callback(void* context) { - furi_assert(context); - Picopass* picopass = context; - view_dispatcher_send_custom_event( - picopass->view_dispatcher, PicopassCustomEventDictAttackSkip); -} - -static void - picopass_scene_elite_dict_attack_prepare_view(Picopass* picopass, DictAttackState state) { - IclassEliteDictAttackData* dict_attack_data = - &picopass->dev->dev_data.iclass_elite_dict_attack_data; - PicopassWorkerState worker_state = PicopassWorkerStateReady; - IclassEliteDict* dict = NULL; - - // Identify scene state - if(state == DictAttackStateIdle) { - if(iclass_elite_dict_check_presence(IclassEliteDictTypeUser)) { - FURI_LOG_D(TAG, "Starting with user dictionary"); - state = DictAttackStateUserDictInProgress; - } else { - FURI_LOG_D(TAG, "Starting with standard dictionary"); - state = DictAttackStateStandardDictInProgress; - } - } else if(state == DictAttackStateUserDictInProgress) { - FURI_LOG_D(TAG, "Moving from user dictionary to standard dictionary"); - state = DictAttackStateStandardDictInProgress; - } else if(state == DictAttackStateStandardDictInProgress) { - FURI_LOG_D(TAG, "Moving from standard dictionary to elite dictionary"); - state = DictAttackStateFlipperDictInProgress; - } - - // Setup view - if(state == DictAttackStateUserDictInProgress) { - worker_state = PicopassWorkerStateEliteDictAttack; - dict_attack_set_header(picopass->dict_attack, "Elite User Dictionary"); - dict_attack_data->type = IclassEliteDictTypeUser; - dict = iclass_elite_dict_alloc(IclassEliteDictTypeUser); - - // If failed to load user dictionary - try the system dictionary - if(!dict) { - FURI_LOG_E(TAG, "User dictionary not found"); - state = DictAttackStateStandardDictInProgress; - } - } - if(state == DictAttackStateStandardDictInProgress) { - worker_state = PicopassWorkerStateEliteDictAttack; - dict_attack_set_header(picopass->dict_attack, "Standard System Dictionary"); - dict_attack_data->type = IclassStandardDictTypeFlipper; - dict = iclass_elite_dict_alloc(IclassStandardDictTypeFlipper); - - if(!dict) { - FURI_LOG_E(TAG, "Flipper standard dictionary not found"); - state = DictAttackStateFlipperDictInProgress; - } - } - if(state == DictAttackStateFlipperDictInProgress) { - worker_state = PicopassWorkerStateEliteDictAttack; - dict_attack_set_header(picopass->dict_attack, "Elite System Dictionary"); - dict_attack_data->type = IclassEliteDictTypeFlipper; - dict = iclass_elite_dict_alloc(IclassEliteDictTypeFlipper); - if(!dict) { - FURI_LOG_E(TAG, "Flipper Elite dictionary not found"); - // Pass through to let the worker handle the failure - } - } - // Free previous dictionary - if(dict_attack_data->dict) { - iclass_elite_dict_free(dict_attack_data->dict); - } - dict_attack_data->dict = dict; - scene_manager_set_scene_state(picopass->scene_manager, PicopassSceneEliteDictAttack, state); - dict_attack_set_callback( - picopass->dict_attack, picopass_dict_attack_result_callback, picopass); - dict_attack_set_current_sector(picopass->dict_attack, 0); - dict_attack_set_card_detected(picopass->dict_attack); - dict_attack_set_total_dict_keys( - picopass->dict_attack, dict ? iclass_elite_dict_get_total_keys(dict) : 0); - picopass_worker_start( - picopass->worker, - worker_state, - &picopass->dev->dev_data, - picopass_dict_attack_worker_callback, - picopass); -} - -void picopass_scene_elite_dict_attack_on_enter(void* context) { - Picopass* picopass = context; - picopass_scene_elite_dict_attack_prepare_view(picopass, DictAttackStateIdle); - view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewDictAttack); - picopass_blink_start(picopass); - notification_message(picopass->notifications, &sequence_display_backlight_enforce_on); -} - -bool picopass_scene_elite_dict_attack_on_event(void* context, SceneManagerEvent event) { - Picopass* picopass = context; - bool consumed = false; - - uint32_t state = - scene_manager_get_scene_state(picopass->scene_manager, PicopassSceneEliteDictAttack); - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == PicopassWorkerEventSuccess) { - if(state == DictAttackStateUserDictInProgress || - state == DictAttackStateStandardDictInProgress) { - picopass_worker_stop(picopass->worker); - picopass_scene_elite_dict_attack_prepare_view(picopass, state); - consumed = true; - } else { - scene_manager_next_scene(picopass->scene_manager, PicopassSceneReadCardSuccess); - consumed = true; - } - } else if(event.event == PicopassWorkerEventAborted) { - scene_manager_next_scene(picopass->scene_manager, PicopassSceneReadCardSuccess); - consumed = true; - } else if(event.event == PicopassWorkerEventCardDetected) { - dict_attack_set_card_detected(picopass->dict_attack); - consumed = true; - } else if(event.event == PicopassWorkerEventNoCardDetected) { - dict_attack_set_card_removed(picopass->dict_attack); - consumed = true; - } else if(event.event == PicopassWorkerEventNewDictKeyBatch) { - dict_attack_inc_current_dict_key(picopass->dict_attack, PICOPASS_DICT_KEY_BATCH_SIZE); - consumed = true; - } else if(event.event == PicopassCustomEventDictAttackSkip) { - if(state == DictAttackStateUserDictInProgress) { - picopass_worker_stop(picopass->worker); - consumed = true; - } else if(state == DictAttackStateFlipperDictInProgress) { - picopass_worker_stop(picopass->worker); - consumed = true; - } else if(state == DictAttackStateStandardDictInProgress) { - picopass_worker_stop(picopass->worker); - consumed = true; - } - } - } else if(event.type == SceneManagerEventTypeBack) { - consumed = scene_manager_previous_scene(picopass->scene_manager); - } - return consumed; -} - -void picopass_scene_elite_dict_attack_on_exit(void* context) { - Picopass* picopass = context; - IclassEliteDictAttackData* dict_attack_data = - &picopass->dev->dev_data.iclass_elite_dict_attack_data; - // Stop worker - picopass_worker_stop(picopass->worker); - if(dict_attack_data->dict) { - iclass_elite_dict_free(dict_attack_data->dict); - dict_attack_data->dict = NULL; - } - dict_attack_reset(picopass->dict_attack); - picopass_blink_stop(picopass); - notification_message(picopass->notifications, &sequence_display_backlight_enforce_auto); -} diff --git a/applications/external/picopass/scenes/picopass_scene_file_select.c b/applications/external/picopass/scenes/picopass_scene_file_select.c deleted file mode 100644 index 2fc64746ebb..00000000000 --- a/applications/external/picopass/scenes/picopass_scene_file_select.c +++ /dev/null @@ -1,25 +0,0 @@ -#include "../picopass_i.h" -#include "../picopass_device.h" - -void picopass_scene_file_select_on_enter(void* context) { - Picopass* picopass = context; - // Process file_select return - picopass_device_set_loading_callback(picopass->dev, picopass_show_loading_popup, picopass); - if(picopass_file_select(picopass->dev)) { - scene_manager_next_scene(picopass->scene_manager, PicopassSceneSavedMenu); - } else { - scene_manager_search_and_switch_to_previous_scene( - picopass->scene_manager, PicopassSceneStart); - } - picopass_device_set_loading_callback(picopass->dev, NULL, picopass); -} - -bool picopass_scene_file_select_on_event(void* context, SceneManagerEvent event) { - UNUSED(context); - UNUSED(event); - return false; -} - -void picopass_scene_file_select_on_exit(void* context) { - UNUSED(context); -} diff --git a/applications/external/picopass/scenes/picopass_scene_key_menu.c b/applications/external/picopass/scenes/picopass_scene_key_menu.c deleted file mode 100644 index 15a32ff4418..00000000000 --- a/applications/external/picopass/scenes/picopass_scene_key_menu.c +++ /dev/null @@ -1,100 +0,0 @@ -#include "../picopass_i.h" -#include "../picopass_keys.h" - -enum SubmenuIndex { - SubmenuIndexWriteStandard, - SubmenuIndexWriteiCE, - SubmenuIndexWriteiCL, - SubmenuIndexWriteiCS, - SubmenuIndexWriteCustom, //TODO: user input of key -}; - -void picopass_scene_key_menu_submenu_callback(void* context, uint32_t index) { - Picopass* picopass = context; - - view_dispatcher_send_custom_event(picopass->view_dispatcher, index); -} - -void picopass_scene_key_menu_on_enter(void* context) { - Picopass* picopass = context; - Submenu* submenu = picopass->submenu; - - submenu_add_item( - submenu, - "Write Standard", - SubmenuIndexWriteStandard, - picopass_scene_key_menu_submenu_callback, - picopass); - submenu_add_item( - submenu, - "Write iCE", - SubmenuIndexWriteiCE, - picopass_scene_key_menu_submenu_callback, - picopass); - submenu_add_item( - submenu, - "Write iCL", - SubmenuIndexWriteiCL, - picopass_scene_key_menu_submenu_callback, - picopass); - submenu_add_item( - submenu, - "Write iCS", - SubmenuIndexWriteiCS, - picopass_scene_key_menu_submenu_callback, - picopass); - - submenu_set_selected_item( - picopass->submenu, - scene_manager_get_scene_state(picopass->scene_manager, PicopassSceneKeyMenu)); - - view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewMenu); -} - -bool picopass_scene_key_menu_on_event(void* context, SceneManagerEvent event) { - Picopass* picopass = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SubmenuIndexWriteStandard) { - scene_manager_set_scene_state( - picopass->scene_manager, PicopassSceneKeyMenu, SubmenuIndexWriteStandard); - memcpy(picopass->dev->dev_data.pacs.key, picopass_iclass_key, PICOPASS_BLOCK_LEN); - picopass->dev->dev_data.pacs.elite_kdf = false; - scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteKey); - consumed = true; - } else if(event.event == SubmenuIndexWriteiCE) { - scene_manager_set_scene_state( - picopass->scene_manager, PicopassSceneKeyMenu, SubmenuIndexWriteiCE); - memcpy(picopass->dev->dev_data.pacs.key, picopass_xice_key, PICOPASS_BLOCK_LEN); - picopass->dev->dev_data.pacs.elite_kdf = true; - scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteKey); - consumed = true; - } else if(event.event == SubmenuIndexWriteiCL) { - scene_manager_set_scene_state( - picopass->scene_manager, PicopassSceneKeyMenu, SubmenuIndexWriteiCL); - memcpy(picopass->dev->dev_data.pacs.key, picopass_xicl_key, PICOPASS_BLOCK_LEN); - picopass->dev->dev_data.pacs.elite_kdf = false; - scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteKey); - consumed = true; - } else if(event.event == SubmenuIndexWriteiCS) { - scene_manager_set_scene_state( - picopass->scene_manager, PicopassSceneKeyMenu, SubmenuIndexWriteiCS); - memcpy(picopass->dev->dev_data.pacs.key, picopass_xics_key, PICOPASS_BLOCK_LEN); - picopass->dev->dev_data.pacs.elite_kdf = false; - scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteKey); - consumed = true; - } - } else if(event.type == SceneManagerEventTypeBack) { - consumed = scene_manager_search_and_switch_to_previous_scene( - picopass->scene_manager, PicopassSceneStart); - } - - return consumed; -} - -void picopass_scene_key_menu_on_exit(void* context) { - Picopass* picopass = context; - - submenu_reset(picopass->submenu); -} diff --git a/applications/external/picopass/scenes/picopass_scene_read_card.c b/applications/external/picopass/scenes/picopass_scene_read_card.c deleted file mode 100644 index c1cc7249c42..00000000000 --- a/applications/external/picopass/scenes/picopass_scene_read_card.c +++ /dev/null @@ -1,61 +0,0 @@ -#include "../picopass_i.h" -#include -#include "../picopass_keys.h" - -void picopass_read_card_worker_callback(PicopassWorkerEvent event, void* context) { - UNUSED(event); - Picopass* picopass = context; - view_dispatcher_send_custom_event(picopass->view_dispatcher, PicopassCustomEventWorkerExit); -} - -void picopass_scene_read_card_on_enter(void* context) { - Picopass* picopass = context; - dolphin_deed(DolphinDeedNfcRead); - - // Setup view - Popup* popup = picopass->popup; - popup_set_header(popup, "Detecting\npicopass\ncard", 68, 30, AlignLeft, AlignTop); - popup_set_icon(popup, 0, 3, &I_RFIDDolphinReceive_97x61); - - // Start worker - view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewPopup); - picopass_worker_start( - picopass->worker, - PicopassWorkerStateDetect, - &picopass->dev->dev_data, - picopass_read_card_worker_callback, - picopass); - - picopass_blink_start(picopass); -} - -bool picopass_scene_read_card_on_event(void* context, SceneManagerEvent event) { - Picopass* picopass = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == PicopassCustomEventWorkerExit) { - if(memcmp( - picopass->dev->dev_data.pacs.key, - picopass_factory_debit_key, - PICOPASS_BLOCK_LEN) == 0) { - scene_manager_next_scene(picopass->scene_manager, PicopassSceneReadFactorySuccess); - } else { - scene_manager_next_scene(picopass->scene_manager, PicopassSceneReadCardSuccess); - } - consumed = true; - } - } - return consumed; -} - -void picopass_scene_read_card_on_exit(void* context) { - Picopass* picopass = context; - - // Stop worker - picopass_worker_stop(picopass->worker); - // Clear view - popup_reset(picopass->popup); - - picopass_blink_stop(picopass); -} diff --git a/applications/external/picopass/scenes/picopass_scene_read_card_success.c b/applications/external/picopass/scenes/picopass_scene_read_card_success.c deleted file mode 100644 index ffe7195b792..00000000000 --- a/applications/external/picopass/scenes/picopass_scene_read_card_success.c +++ /dev/null @@ -1,172 +0,0 @@ -#include "../picopass_i.h" -#include - -void picopass_scene_read_card_success_widget_callback( - GuiButtonType result, - InputType type, - void* context) { - furi_assert(context); - Picopass* picopass = context; - - if(type == InputTypeShort) { - view_dispatcher_send_custom_event(picopass->view_dispatcher, result); - } -} - -void picopass_scene_read_card_success_on_enter(void* context) { - Picopass* picopass = context; - - FuriString* csn_str = furi_string_alloc_set("CSN:"); - FuriString* credential_str = furi_string_alloc(); - FuriString* wiegand_str = furi_string_alloc(); - FuriString* sio_str = furi_string_alloc(); - - dolphin_deed(DolphinDeedNfcReadSuccess); - - // Send notification - notification_message(picopass->notifications, &sequence_success); - - // Setup view - PicopassBlock* AA1 = picopass->dev->dev_data.AA1; - PicopassPacs* pacs = &picopass->dev->dev_data.pacs; - Widget* widget = picopass->widget; - - uint8_t csn[PICOPASS_BLOCK_LEN] = {0}; - memcpy(csn, AA1[PICOPASS_CSN_BLOCK_INDEX].data, PICOPASS_BLOCK_LEN); - for(uint8_t i = 0; i < PICOPASS_BLOCK_LEN; i++) { - furi_string_cat_printf(csn_str, "%02X", csn[i]); - } - - bool no_key = picopass_is_memset(pacs->key, 0x00, PICOPASS_BLOCK_LEN); - bool empty = - picopass_is_memset(AA1[PICOPASS_PACS_CFG_BLOCK_INDEX].data, 0xFF, PICOPASS_BLOCK_LEN); - - if(no_key) { - furi_string_cat_printf(wiegand_str, "Read Failed"); - - if(pacs->se_enabled) { - furi_string_cat_printf(credential_str, "SE enabled"); - } - - widget_add_button_element( - widget, - GuiButtonTypeCenter, - "Menu", - picopass_scene_read_card_success_widget_callback, - picopass); - } else if(empty) { - furi_string_cat_printf(wiegand_str, "Empty"); - widget_add_button_element( - widget, - GuiButtonTypeCenter, - "Menu", - picopass_scene_read_card_success_widget_callback, - picopass); - } else if(pacs->record.bitLength == 0 || pacs->record.bitLength == 255) { - // Neither of these are valid. Indicates the block was all 0x00 or all 0xff - furi_string_cat_printf(wiegand_str, "Invalid PACS"); - - if(pacs->se_enabled) { - furi_string_cat_printf(credential_str, "SE enabled"); - } - widget_add_button_element( - widget, - GuiButtonTypeCenter, - "Menu", - picopass_scene_read_card_success_widget_callback, - picopass); - } else { - size_t bytesLength = 1 + pacs->record.bitLength / 8; - furi_string_set(credential_str, ""); - for(uint8_t i = PICOPASS_BLOCK_LEN - bytesLength; i < PICOPASS_BLOCK_LEN; i++) { - furi_string_cat_printf(credential_str, " %02X", pacs->credential[i]); - } - - if(pacs->record.valid) { - furi_string_cat_printf( - wiegand_str, "FC: %u CN: %u", pacs->record.FacilityCode, pacs->record.CardNumber); - } else { - furi_string_cat_printf(wiegand_str, "%d bits", pacs->record.bitLength); - } - - if(pacs->sio) { - furi_string_cat_printf(sio_str, "+SIO"); - } - - if(pacs->key) { - if(pacs->sio) { - furi_string_cat_printf(sio_str, " "); - } - furi_string_cat_printf(sio_str, "Key: "); - - uint8_t key[PICOPASS_BLOCK_LEN]; - memcpy(key, &pacs->key, PICOPASS_BLOCK_LEN); - for(uint8_t i = 0; i < PICOPASS_BLOCK_LEN; i++) { - furi_string_cat_printf(sio_str, "%02X", key[i]); - } - } - - widget_add_button_element( - widget, - GuiButtonTypeRight, - "More", - picopass_scene_read_card_success_widget_callback, - picopass); - } - - widget_add_button_element( - widget, - GuiButtonTypeLeft, - "Retry", - picopass_scene_read_card_success_widget_callback, - picopass); - - widget_add_string_element( - widget, 64, 5, AlignCenter, AlignCenter, FontSecondary, furi_string_get_cstr(csn_str)); - widget_add_string_element( - widget, 64, 20, AlignCenter, AlignCenter, FontPrimary, furi_string_get_cstr(wiegand_str)); - widget_add_string_element( - widget, - 64, - 36, - AlignCenter, - AlignCenter, - FontSecondary, - furi_string_get_cstr(credential_str)); - widget_add_string_element( - widget, 64, 46, AlignCenter, AlignCenter, FontSecondary, furi_string_get_cstr(sio_str)); - - furi_string_free(csn_str); - furi_string_free(credential_str); - furi_string_free(wiegand_str); - furi_string_free(sio_str); - - view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewWidget); -} - -bool picopass_scene_read_card_success_on_event(void* context, SceneManagerEvent event) { - Picopass* picopass = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == GuiButtonTypeLeft) { - consumed = scene_manager_previous_scene(picopass->scene_manager); - } else if(event.event == GuiButtonTypeRight) { - // Clear device name - picopass_device_set_name(picopass->dev, ""); - scene_manager_next_scene(picopass->scene_manager, PicopassSceneCardMenu); - consumed = true; - } else if(event.event == GuiButtonTypeCenter) { - consumed = scene_manager_search_and_switch_to_another_scene( - picopass->scene_manager, PicopassSceneStart); - } - } - return consumed; -} - -void picopass_scene_read_card_success_on_exit(void* context) { - Picopass* picopass = context; - - // Clear view - widget_reset(picopass->widget); -} diff --git a/applications/external/picopass/scenes/picopass_scene_read_factory_success.c b/applications/external/picopass/scenes/picopass_scene_read_factory_success.c deleted file mode 100644 index f5fcd10fda1..00000000000 --- a/applications/external/picopass/scenes/picopass_scene_read_factory_success.c +++ /dev/null @@ -1,80 +0,0 @@ -#include "../picopass_i.h" -#include -#include "../picopass_keys.h" - -void picopass_scene_read_factory_success_widget_callback( - GuiButtonType result, - InputType type, - void* context) { - furi_assert(context); - Picopass* picopass = context; - - if(type == InputTypeShort) { - view_dispatcher_send_custom_event(picopass->view_dispatcher, result); - } -} - -void picopass_scene_read_factory_success_on_enter(void* context) { - Picopass* picopass = context; - FuriString* title = furi_string_alloc_set("Factory Default"); - FuriString* subtitle = furi_string_alloc_set(""); - - dolphin_deed(DolphinDeedNfcReadSuccess); - - // Send notification - notification_message(picopass->notifications, &sequence_success); - - // Setup view - Widget* widget = picopass->widget; - //PicopassPacs* pacs = &picopass->dev->dev_data.pacs; - PicopassBlock* AA1 = picopass->dev->dev_data.AA1; - - uint8_t* configBlock = AA1[PICOPASS_CONFIG_BLOCK_INDEX].data; - uint8_t fuses = configBlock[7]; - - if((fuses & 0x80) == 0x80) { - furi_string_cat_printf(subtitle, "Personalization mode"); - } else { - furi_string_cat_printf(subtitle, "Application mode"); - } - - widget_add_button_element( - widget, - GuiButtonTypeCenter, - "Write Standard iClass Key", - picopass_scene_read_factory_success_widget_callback, - picopass); - - widget_add_string_element( - widget, 64, 5, AlignCenter, AlignCenter, FontSecondary, furi_string_get_cstr(title)); - widget_add_string_element( - widget, 64, 20, AlignCenter, AlignCenter, FontPrimary, furi_string_get_cstr(subtitle)); - - furi_string_free(title); - furi_string_free(subtitle); - - view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewWidget); -} - -bool picopass_scene_read_factory_success_on_event(void* context, SceneManagerEvent event) { - Picopass* picopass = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == GuiButtonTypeLeft) { - consumed = scene_manager_previous_scene(picopass->scene_manager); - } else if(event.event == GuiButtonTypeCenter) { - memcpy(picopass->dev->dev_data.pacs.key, picopass_iclass_key, PICOPASS_BLOCK_LEN); - scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteKey); - consumed = true; - } - } - return consumed; -} - -void picopass_scene_read_factory_success_on_exit(void* context) { - Picopass* picopass = context; - - // Clear view - widget_reset(picopass->widget); -} diff --git a/applications/external/picopass/scenes/picopass_scene_save_name.c b/applications/external/picopass/scenes/picopass_scene_save_name.c deleted file mode 100644 index baf882b8075..00000000000 --- a/applications/external/picopass/scenes/picopass_scene_save_name.c +++ /dev/null @@ -1,81 +0,0 @@ -#include "../picopass_i.h" -#include -#include -#include - -void picopass_scene_save_name_text_input_callback(void* context) { - Picopass* picopass = context; - - view_dispatcher_send_custom_event(picopass->view_dispatcher, PicopassCustomEventTextInputDone); -} - -void picopass_scene_save_name_on_enter(void* context) { - Picopass* picopass = context; - - // Setup view - TextInput* text_input = picopass->text_input; - bool dev_name_empty = false; - if(!strcmp(picopass->dev->dev_name, "")) { - set_random_name(picopass->text_store, sizeof(picopass->text_store)); - dev_name_empty = true; - } else { - picopass_text_store_set(picopass, picopass->dev->dev_name); - } - text_input_set_header_text(text_input, "Name the card"); - text_input_set_result_callback( - text_input, - picopass_scene_save_name_text_input_callback, - picopass, - picopass->text_store, - PICOPASS_DEV_NAME_MAX_LEN, - dev_name_empty); - - FuriString* folder_path; - folder_path = furi_string_alloc_set(STORAGE_APP_DATA_PATH_PREFIX); - - if(furi_string_end_with(picopass->dev->load_path, PICOPASS_APP_EXTENSION)) { - path_extract_dirname(furi_string_get_cstr(picopass->dev->load_path), folder_path); - } - - ValidatorIsFile* validator_is_file = validator_is_file_alloc_init( - furi_string_get_cstr(folder_path), PICOPASS_APP_EXTENSION, picopass->dev->dev_name); - text_input_set_validator(text_input, validator_is_file_callback, validator_is_file); - - view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewTextInput); - - furi_string_free(folder_path); -} - -bool picopass_scene_save_name_on_event(void* context, SceneManagerEvent event) { - Picopass* picopass = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == PicopassCustomEventTextInputDone) { - if(strcmp(picopass->dev->dev_name, "") != 0) { - // picopass_device_delete(picopass->dev, true); - } - strlcpy( - picopass->dev->dev_name, picopass->text_store, strlen(picopass->text_store) + 1); - if(picopass_device_save(picopass->dev, picopass->text_store)) { - scene_manager_next_scene(picopass->scene_manager, PicopassSceneSaveSuccess); - consumed = true; - } else { - consumed = scene_manager_search_and_switch_to_previous_scene( - picopass->scene_manager, PicopassSceneStart); - } - } - } - return consumed; -} - -void picopass_scene_save_name_on_exit(void* context) { - Picopass* picopass = context; - - // Clear view - void* validator_context = text_input_get_validator_callback_context(picopass->text_input); - text_input_set_validator(picopass->text_input, NULL, NULL); - validator_is_file_free(validator_context); - - text_input_reset(picopass->text_input); -} diff --git a/applications/external/picopass/scenes/picopass_scene_save_success.c b/applications/external/picopass/scenes/picopass_scene_save_success.c deleted file mode 100644 index 3b0a1cadd25..00000000000 --- a/applications/external/picopass/scenes/picopass_scene_save_success.c +++ /dev/null @@ -1,47 +0,0 @@ -#include "../picopass_i.h" -#include - -void picopass_scene_save_success_popup_callback(void* context) { - Picopass* picopass = context; - view_dispatcher_send_custom_event(picopass->view_dispatcher, PicopassCustomEventViewExit); -} - -void picopass_scene_save_success_on_enter(void* context) { - Picopass* picopass = context; - dolphin_deed(DolphinDeedNfcSave); - - // Setup view - Popup* popup = picopass->popup; - popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); - popup_set_header(popup, "Saved!", 13, 22, AlignLeft, AlignBottom); - popup_set_timeout(popup, 1500); - popup_set_context(popup, picopass); - popup_set_callback(popup, picopass_scene_save_success_popup_callback); - popup_enable_timeout(popup); - view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewPopup); -} - -bool picopass_scene_save_success_on_event(void* context, SceneManagerEvent event) { - Picopass* picopass = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == PicopassCustomEventViewExit) { - if(scene_manager_has_previous_scene(picopass->scene_manager, PicopassSceneCardMenu)) { - consumed = scene_manager_search_and_switch_to_previous_scene( - picopass->scene_manager, PicopassSceneCardMenu); - } else { - consumed = scene_manager_search_and_switch_to_previous_scene( - picopass->scene_manager, PicopassSceneStart); - } - } - } - return consumed; -} - -void picopass_scene_save_success_on_exit(void* context) { - Picopass* picopass = context; - - // Clear view - popup_reset(picopass->popup); -} diff --git a/applications/external/picopass/scenes/picopass_scene_saved_menu.c b/applications/external/picopass/scenes/picopass_scene_saved_menu.c deleted file mode 100644 index 90a27ee8167..00000000000 --- a/applications/external/picopass/scenes/picopass_scene_saved_menu.c +++ /dev/null @@ -1,64 +0,0 @@ -#include "../picopass_i.h" - -enum SubmenuIndex { - SubmenuIndexDelete, - SubmenuIndexInfo, - SubmenuIndexWrite, -}; - -void picopass_scene_saved_menu_submenu_callback(void* context, uint32_t index) { - Picopass* picopass = context; - - view_dispatcher_send_custom_event(picopass->view_dispatcher, index); -} - -void picopass_scene_saved_menu_on_enter(void* context) { - Picopass* picopass = context; - Submenu* submenu = picopass->submenu; - - submenu_add_item( - submenu, - "Delete", - SubmenuIndexDelete, - picopass_scene_saved_menu_submenu_callback, - picopass); - submenu_add_item( - submenu, "Info", SubmenuIndexInfo, picopass_scene_saved_menu_submenu_callback, picopass); - submenu_add_item( - submenu, "Write", SubmenuIndexWrite, picopass_scene_saved_menu_submenu_callback, picopass); - - submenu_set_selected_item( - picopass->submenu, - scene_manager_get_scene_state(picopass->scene_manager, PicopassSceneSavedMenu)); - - view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewMenu); -} - -bool picopass_scene_saved_menu_on_event(void* context, SceneManagerEvent event) { - Picopass* picopass = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - scene_manager_set_scene_state( - picopass->scene_manager, PicopassSceneSavedMenu, event.event); - - if(event.event == SubmenuIndexDelete) { - scene_manager_next_scene(picopass->scene_manager, PicopassSceneDelete); - consumed = true; - } else if(event.event == SubmenuIndexInfo) { - scene_manager_next_scene(picopass->scene_manager, PicopassSceneDeviceInfo); - consumed = true; - } else if(event.event == SubmenuIndexWrite) { - scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteCard); - consumed = true; - } - } - - return consumed; -} - -void picopass_scene_saved_menu_on_exit(void* context) { - Picopass* picopass = context; - - submenu_reset(picopass->submenu); -} diff --git a/applications/external/picopass/scenes/picopass_scene_start.c b/applications/external/picopass/scenes/picopass_scene_start.c deleted file mode 100644 index 8f7b627aaf3..00000000000 --- a/applications/external/picopass/scenes/picopass_scene_start.c +++ /dev/null @@ -1,64 +0,0 @@ -#include "../picopass_i.h" -enum SubmenuIndex { - SubmenuIndexRead, - SubmenuIndexEliteDictAttack, - SubmenuIndexSaved, -}; - -void picopass_scene_start_submenu_callback(void* context, uint32_t index) { - Picopass* picopass = context; - view_dispatcher_send_custom_event(picopass->view_dispatcher, index); -} -void picopass_scene_start_on_enter(void* context) { - Picopass* picopass = context; - - Submenu* submenu = picopass->submenu; - submenu_add_item( - submenu, "Read Card", SubmenuIndexRead, picopass_scene_start_submenu_callback, picopass); - submenu_add_item( - submenu, - "Elite Dict. Attack", - SubmenuIndexEliteDictAttack, - picopass_scene_start_submenu_callback, - picopass); - submenu_add_item( - submenu, "Saved", SubmenuIndexSaved, picopass_scene_start_submenu_callback, picopass); - - submenu_set_selected_item( - submenu, scene_manager_get_scene_state(picopass->scene_manager, PicopassSceneStart)); - picopass_device_clear(picopass->dev); - view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewMenu); -} - -bool picopass_scene_start_on_event(void* context, SceneManagerEvent event) { - Picopass* picopass = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SubmenuIndexRead) { - scene_manager_set_scene_state( - picopass->scene_manager, PicopassSceneStart, SubmenuIndexRead); - scene_manager_next_scene(picopass->scene_manager, PicopassSceneReadCard); - consumed = true; - } else if(event.event == SubmenuIndexSaved) { - // Explicitly save state so that the correct item is - // reselected if the user cancels loading a file. - scene_manager_set_scene_state( - picopass->scene_manager, PicopassSceneStart, SubmenuIndexSaved); - scene_manager_next_scene(picopass->scene_manager, PicopassSceneFileSelect); - consumed = true; - } else if(event.event == SubmenuIndexEliteDictAttack) { - scene_manager_set_scene_state( - picopass->scene_manager, PicopassSceneStart, SubmenuIndexEliteDictAttack); - scene_manager_next_scene(picopass->scene_manager, PicopassSceneEliteDictAttack); - consumed = true; - } - } - - return consumed; -} - -void picopass_scene_start_on_exit(void* context) { - Picopass* picopass = context; - submenu_reset(picopass->submenu); -} diff --git a/applications/external/picopass/scenes/picopass_scene_write_card.c b/applications/external/picopass/scenes/picopass_scene_write_card.c deleted file mode 100644 index ce396fc10e1..00000000000 --- a/applications/external/picopass/scenes/picopass_scene_write_card.c +++ /dev/null @@ -1,53 +0,0 @@ -#include "../picopass_i.h" -#include - -void picopass_write_card_worker_callback(PicopassWorkerEvent event, void* context) { - UNUSED(event); - Picopass* picopass = context; - view_dispatcher_send_custom_event(picopass->view_dispatcher, PicopassCustomEventWorkerExit); -} - -void picopass_scene_write_card_on_enter(void* context) { - Picopass* picopass = context; - dolphin_deed(DolphinDeedNfcSave); - - // Setup view - Popup* popup = picopass->popup; - popup_set_header(popup, "Writing\npicopass\ncard", 68, 30, AlignLeft, AlignTop); - popup_set_icon(popup, 0, 3, &I_RFIDDolphinSend_97x61); - - // Start worker - view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewPopup); - picopass_worker_start( - picopass->worker, - PicopassWorkerStateWrite, - &picopass->dev->dev_data, - picopass_write_card_worker_callback, - picopass); - - picopass_blink_start(picopass); -} - -bool picopass_scene_write_card_on_event(void* context, SceneManagerEvent event) { - Picopass* picopass = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == PicopassCustomEventWorkerExit) { - scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteCardSuccess); - consumed = true; - } - } - return consumed; -} - -void picopass_scene_write_card_on_exit(void* context) { - Picopass* picopass = context; - - // Stop worker - picopass_worker_stop(picopass->worker); - // Clear view - popup_reset(picopass->popup); - - picopass_blink_stop(picopass); -} diff --git a/applications/external/picopass/scenes/picopass_scene_write_card_success.c b/applications/external/picopass/scenes/picopass_scene_write_card_success.c deleted file mode 100644 index cd760272fe9..00000000000 --- a/applications/external/picopass/scenes/picopass_scene_write_card_success.c +++ /dev/null @@ -1,70 +0,0 @@ -#include "../picopass_i.h" -#include - -void picopass_scene_write_card_success_widget_callback( - GuiButtonType result, - InputType type, - void* context) { - furi_assert(context); - Picopass* picopass = context; - - if(type == InputTypeShort) { - view_dispatcher_send_custom_event(picopass->view_dispatcher, result); - } -} - -void picopass_scene_write_card_success_on_enter(void* context) { - Picopass* picopass = context; - Widget* widget = picopass->widget; - FuriString* str = furi_string_alloc_set("Write Success!"); - - dolphin_deed(DolphinDeedNfcReadSuccess); - - // Send notification - notification_message(picopass->notifications, &sequence_success); - - widget_add_button_element( - widget, - GuiButtonTypeLeft, - "Retry", - picopass_scene_write_card_success_widget_callback, - picopass); - - widget_add_button_element( - widget, - GuiButtonTypeRight, - "Menu", - picopass_scene_write_card_success_widget_callback, - picopass); - - widget_add_string_element( - widget, 64, 5, AlignCenter, AlignCenter, FontSecondary, furi_string_get_cstr(str)); - - furi_string_free(str); - - view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewWidget); -} - -bool picopass_scene_write_card_success_on_event(void* context, SceneManagerEvent event) { - Picopass* picopass = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == GuiButtonTypeLeft) { - consumed = scene_manager_previous_scene(picopass->scene_manager); - } else if(event.event == GuiButtonTypeRight) { - // Clear device name - picopass_device_set_name(picopass->dev, ""); - scene_manager_next_scene(picopass->scene_manager, PicopassSceneCardMenu); - consumed = true; - } - } - return consumed; -} - -void picopass_scene_write_card_success_on_exit(void* context) { - Picopass* picopass = context; - - // Clear view - widget_reset(picopass->widget); -} diff --git a/applications/external/picopass/scenes/picopass_scene_write_key.c b/applications/external/picopass/scenes/picopass_scene_write_key.c deleted file mode 100644 index 806a2b5a856..00000000000 --- a/applications/external/picopass/scenes/picopass_scene_write_key.c +++ /dev/null @@ -1,53 +0,0 @@ -#include "../picopass_i.h" -#include - -void picopass_write_key_worker_callback(PicopassWorkerEvent event, void* context) { - UNUSED(event); - Picopass* picopass = context; - view_dispatcher_send_custom_event(picopass->view_dispatcher, PicopassCustomEventWorkerExit); -} - -void picopass_scene_write_key_on_enter(void* context) { - Picopass* picopass = context; - dolphin_deed(DolphinDeedNfcSave); - - // Setup view - Popup* popup = picopass->popup; - popup_set_header(popup, "Writing\niClass\nkey", 68, 30, AlignLeft, AlignTop); - popup_set_icon(popup, 0, 3, &I_RFIDDolphinSend_97x61); - - // Start worker - view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewPopup); - picopass_worker_start( - picopass->worker, - PicopassWorkerStateWriteKey, - &picopass->dev->dev_data, - picopass_write_key_worker_callback, - picopass); - - picopass_blink_start(picopass); -} - -bool picopass_scene_write_key_on_event(void* context, SceneManagerEvent event) { - Picopass* picopass = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == PicopassCustomEventWorkerExit) { - scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteCardSuccess); - consumed = true; - } - } - return consumed; -} - -void picopass_scene_write_key_on_exit(void* context) { - Picopass* picopass = context; - - // Stop worker - picopass_worker_stop(picopass->worker); - // Clear view - popup_reset(picopass->popup); - - picopass_blink_stop(picopass); -} diff --git a/applications/external/picopass/views/dict_attack.c b/applications/external/picopass/views/dict_attack.c deleted file mode 100644 index fb7335f6c3d..00000000000 --- a/applications/external/picopass/views/dict_attack.c +++ /dev/null @@ -1,281 +0,0 @@ -#include "dict_attack.h" - -#include - -typedef enum { - DictAttackStateRead, - DictAttackStateCardRemoved, -} DictAttackState; - -struct DictAttack { - View* view; - DictAttackCallback callback; - void* context; -}; - -typedef struct { - DictAttackState state; - MfClassicType type; - FuriString* header; - uint8_t sectors_total; - uint8_t sectors_read; - uint8_t sector_current; - uint8_t keys_total; - uint8_t keys_found; - uint16_t dict_keys_total; - uint16_t dict_keys_current; - bool is_key_attack; - uint8_t key_attack_current_sector; -} DictAttackViewModel; - -static void dict_attack_draw_callback(Canvas* canvas, void* model) { - DictAttackViewModel* m = model; - if(m->state == DictAttackStateCardRemoved) { - canvas_set_font(canvas, FontPrimary); - canvas_draw_str_aligned(canvas, 64, 4, AlignCenter, AlignTop, "Lost the tag!"); - canvas_set_font(canvas, FontSecondary); - elements_multiline_text_aligned( - canvas, 64, 23, AlignCenter, AlignTop, "Make sure the tag is\npositioned correctly."); - } else if(m->state == DictAttackStateRead) { - char draw_str[32] = {}; - canvas_set_font(canvas, FontSecondary); - canvas_draw_str_aligned( - canvas, 64, 0, AlignCenter, AlignTop, furi_string_get_cstr(m->header)); - if(m->is_key_attack) { - snprintf( - draw_str, - sizeof(draw_str), - "Reuse key check for sector: %d", - m->key_attack_current_sector); - } else { - snprintf(draw_str, sizeof(draw_str), "Unlocking sector: %d", m->sector_current); - } - canvas_draw_str_aligned(canvas, 0, 10, AlignLeft, AlignTop, draw_str); - float dict_progress = m->dict_keys_total == 0 ? - 0 : - (float)(m->dict_keys_current) / (float)(m->dict_keys_total); - float progress = m->sectors_total == 0 ? 0 : - ((float)(m->sector_current) + dict_progress) / - (float)(m->sectors_total); - if(progress > 1.0) { - progress = 1.0; - } - if(m->dict_keys_current == 0) { - // Cause when people see 0 they think it's broken - snprintf(draw_str, sizeof(draw_str), "%d/%d", 1, m->dict_keys_total); - } else { - snprintf( - draw_str, sizeof(draw_str), "%d/%d", m->dict_keys_current, m->dict_keys_total); - } - elements_progress_bar_with_text(canvas, 0, 20, 128, dict_progress, draw_str); - canvas_set_font(canvas, FontSecondary); - snprintf(draw_str, sizeof(draw_str), "Keys found: %d/%d", m->keys_found, m->keys_total); - canvas_draw_str_aligned(canvas, 0, 33, AlignLeft, AlignTop, draw_str); - snprintf( - draw_str, sizeof(draw_str), "Sectors Read: %d/%d", m->sectors_read, m->sectors_total); - canvas_draw_str_aligned(canvas, 0, 43, AlignLeft, AlignTop, draw_str); - } - elements_button_center(canvas, "Skip"); -} - -static bool dict_attack_input_callback(InputEvent* event, void* context) { - DictAttack* dict_attack = context; - bool consumed = false; - if(event->type == InputTypeShort && event->key == InputKeyOk) { - if(dict_attack->callback) { - dict_attack->callback(dict_attack->context); - } - consumed = true; - } - return consumed; -} - -DictAttack* dict_attack_alloc() { - DictAttack* dict_attack = malloc(sizeof(DictAttack)); - dict_attack->view = view_alloc(); - view_allocate_model(dict_attack->view, ViewModelTypeLocking, sizeof(DictAttackViewModel)); - view_set_draw_callback(dict_attack->view, dict_attack_draw_callback); - view_set_input_callback(dict_attack->view, dict_attack_input_callback); - view_set_context(dict_attack->view, dict_attack); - with_view_model( - dict_attack->view, - DictAttackViewModel * model, - { model->header = furi_string_alloc(); }, - false); - return dict_attack; -} - -void dict_attack_free(DictAttack* dict_attack) { - furi_assert(dict_attack); - with_view_model( - dict_attack->view, - DictAttackViewModel * model, - { furi_string_free(model->header); }, - false); - view_free(dict_attack->view); - free(dict_attack); -} - -void dict_attack_reset(DictAttack* dict_attack) { - furi_assert(dict_attack); - with_view_model( - dict_attack->view, - DictAttackViewModel * model, - { - model->state = DictAttackStateRead; - model->type = MfClassicType1k; - model->sectors_total = 1; - model->sectors_read = 0; - model->sector_current = 0; - model->keys_total = 0; - model->keys_found = 0; - model->dict_keys_total = 0; - model->dict_keys_current = 0; - model->is_key_attack = false; - furi_string_reset(model->header); - }, - false); -} - -View* dict_attack_get_view(DictAttack* dict_attack) { - furi_assert(dict_attack); - return dict_attack->view; -} - -void dict_attack_set_callback(DictAttack* dict_attack, DictAttackCallback callback, void* context) { - furi_assert(dict_attack); - furi_assert(callback); - dict_attack->callback = callback; - dict_attack->context = context; -} - -void dict_attack_set_header(DictAttack* dict_attack, const char* header) { - furi_assert(dict_attack); - furi_assert(header); - - with_view_model( - dict_attack->view, - DictAttackViewModel * model, - { furi_string_set(model->header, header); }, - true); -} - -void dict_attack_set_card_detected(DictAttack* dict_attack) { - furi_assert(dict_attack); - with_view_model( - dict_attack->view, - DictAttackViewModel * model, - { - model->state = DictAttackStateRead; - model->sectors_total = 1; - model->keys_total = model->sectors_total; - }, - true); -} - -void dict_attack_set_card_removed(DictAttack* dict_attack) { - furi_assert(dict_attack); - with_view_model( - dict_attack->view, - DictAttackViewModel * model, - { model->state = DictAttackStateCardRemoved; }, - true); -} - -void dict_attack_set_sector_read(DictAttack* dict_attack, uint8_t sec_read) { - furi_assert(dict_attack); - with_view_model( - dict_attack->view, DictAttackViewModel * model, { model->sectors_read = sec_read; }, true); -} - -void dict_attack_set_keys_found(DictAttack* dict_attack, uint8_t keys_found) { - furi_assert(dict_attack); - with_view_model( - dict_attack->view, DictAttackViewModel * model, { model->keys_found = keys_found; }, true); -} - -void dict_attack_set_current_sector(DictAttack* dict_attack, uint8_t curr_sec) { - furi_assert(dict_attack); - with_view_model( - dict_attack->view, - DictAttackViewModel * model, - { - model->sector_current = curr_sec; - model->dict_keys_current = 0; - }, - true); -} - -void dict_attack_inc_current_sector(DictAttack* dict_attack) { - furi_assert(dict_attack); - with_view_model( - dict_attack->view, - DictAttackViewModel * model, - { - if(model->sector_current < model->sectors_total) { - model->sector_current++; - model->dict_keys_current = 0; - } - }, - true); -} - -void dict_attack_inc_keys_found(DictAttack* dict_attack) { - furi_assert(dict_attack); - with_view_model( - dict_attack->view, - DictAttackViewModel * model, - { - if(model->keys_found < model->keys_total) { - model->keys_found++; - } - }, - true); -} - -void dict_attack_set_total_dict_keys(DictAttack* dict_attack, uint16_t dict_keys_total) { - furi_assert(dict_attack); - with_view_model( - dict_attack->view, - DictAttackViewModel * model, - { model->dict_keys_total = dict_keys_total; }, - true); -} - -void dict_attack_inc_current_dict_key(DictAttack* dict_attack, uint16_t keys_tried) { - furi_assert(dict_attack); - with_view_model( - dict_attack->view, - DictAttackViewModel * model, - { - if(model->dict_keys_current + keys_tried < model->dict_keys_total) { - model->dict_keys_current += keys_tried; - } - }, - true); -} - -void dict_attack_set_key_attack(DictAttack* dict_attack, bool is_key_attack, uint8_t sector) { - furi_assert(dict_attack); - with_view_model( - dict_attack->view, - DictAttackViewModel * model, - { - model->is_key_attack = is_key_attack; - model->key_attack_current_sector = sector; - }, - true); -} - -void dict_attack_inc_key_attack_current_sector(DictAttack* dict_attack) { - furi_assert(dict_attack); - with_view_model( - dict_attack->view, - DictAttackViewModel * model, - { - if(model->key_attack_current_sector < model->sectors_total) { - model->key_attack_current_sector++; - } - }, - true); -} diff --git a/applications/external/picopass/views/dict_attack.h b/applications/external/picopass/views/dict_attack.h deleted file mode 100644 index bdfa3e95200..00000000000 --- a/applications/external/picopass/views/dict_attack.h +++ /dev/null @@ -1,44 +0,0 @@ -#pragma once -#include -#include -#include - -#include - -typedef struct DictAttack DictAttack; - -typedef void (*DictAttackCallback)(void* context); - -DictAttack* dict_attack_alloc(); - -void dict_attack_free(DictAttack* dict_attack); - -void dict_attack_reset(DictAttack* dict_attack); - -View* dict_attack_get_view(DictAttack* dict_attack); - -void dict_attack_set_callback(DictAttack* dict_attack, DictAttackCallback callback, void* context); - -void dict_attack_set_header(DictAttack* dict_attack, const char* header); - -void dict_attack_set_card_detected(DictAttack* dict_attack); - -void dict_attack_set_card_removed(DictAttack* dict_attack); - -void dict_attack_set_sector_read(DictAttack* dict_attack, uint8_t sec_read); - -void dict_attack_set_keys_found(DictAttack* dict_attack, uint8_t keys_found); - -void dict_attack_set_current_sector(DictAttack* dict_attack, uint8_t curr_sec); - -void dict_attack_inc_current_sector(DictAttack* dict_attack); - -void dict_attack_inc_keys_found(DictAttack* dict_attack); - -void dict_attack_set_total_dict_keys(DictAttack* dict_attack, uint16_t dict_keys_total); - -void dict_attack_inc_current_dict_key(DictAttack* dict_attack, uint16_t keys_tried); - -void dict_attack_set_key_attack(DictAttack* dict_attack, bool is_key_attack, uint8_t sector); - -void dict_attack_inc_key_attack_current_sector(DictAttack* dict_attack); diff --git a/applications/external/signal_generator/application.fam b/applications/external/signal_generator/application.fam deleted file mode 100644 index 094e784cc85..00000000000 --- a/applications/external/signal_generator/application.fam +++ /dev/null @@ -1,12 +0,0 @@ -App( - appid="signal_generator", - name="Signal Generator", - apptype=FlipperAppType.EXTERNAL, - entry_point="signal_gen_app", - requires=["gui"], - stack_size=1 * 1024, - order=50, - fap_icon="signal_gen_10px.png", - fap_category="GPIO", - fap_icon_assets="icons", -) diff --git a/applications/external/signal_generator/icons/SmallArrowDown_3x5.png b/applications/external/signal_generator/icons/SmallArrowDown_3x5.png deleted file mode 100644 index 1912e5d246268d75a20984bdc8b996d503f3d166..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3592 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 BDRuw= diff --git a/applications/external/signal_generator/icons/SmallArrowUp_3x5.png b/applications/external/signal_generator/icons/SmallArrowUp_3x5.png deleted file mode 100644 index 9c6242078d3bcc9a93eb55b6b5f358720ee6662c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7976 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 - -// Generate scene id and total number -#define ADD_SCENE(prefix, name, id) SignalGenScene##id, -typedef enum { -#include "signal_gen_scene_config.h" - SignalGenSceneNum, -} SignalGenScene; -#undef ADD_SCENE - -extern const SceneManagerHandlers signal_gen_scene_handlers; - -// Generate scene on_enter handlers declaration -#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); -#include "signal_gen_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 "signal_gen_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 "signal_gen_scene_config.h" -#undef ADD_SCENE diff --git a/applications/external/signal_generator/scenes/signal_gen_scene_config.h b/applications/external/signal_generator/scenes/signal_gen_scene_config.h deleted file mode 100644 index b6c75025669..00000000000 --- a/applications/external/signal_generator/scenes/signal_gen_scene_config.h +++ /dev/null @@ -1,3 +0,0 @@ -ADD_SCENE(signal_gen, start, Start) -ADD_SCENE(signal_gen, pwm, Pwm) -ADD_SCENE(signal_gen, mco, Mco) diff --git a/applications/external/signal_generator/scenes/signal_gen_scene_mco.c b/applications/external/signal_generator/scenes/signal_gen_scene_mco.c deleted file mode 100644 index 0855cde0a0b..00000000000 --- a/applications/external/signal_generator/scenes/signal_gen_scene_mco.c +++ /dev/null @@ -1,145 +0,0 @@ -#include "../signal_gen_app_i.h" - -typedef enum { - LineIndexPin, - LineIndexSource, - LineIndexDivision, -} LineIndex; - -static const char* const mco_pin_names[] = { - "13(Tx)", -}; - -static const char* const mco_source_names[] = { - "32768Hz", - "64MHz", - "~100K", - "~200K", - "~400K", - "~800K", - "~1MHz", - "~2MHz", - "~4MHz", - "~8MHz", - "~16MHz", - "~24MHz", - "~32MHz", - "~48MHz", -}; - -static const FuriHalClockMcoSourceId mco_sources[] = { - FuriHalClockMcoLse, - FuriHalClockMcoSysclk, - FuriHalClockMcoMsi100k, - FuriHalClockMcoMsi200k, - FuriHalClockMcoMsi400k, - FuriHalClockMcoMsi800k, - FuriHalClockMcoMsi1m, - FuriHalClockMcoMsi2m, - FuriHalClockMcoMsi4m, - FuriHalClockMcoMsi8m, - FuriHalClockMcoMsi16m, - FuriHalClockMcoMsi24m, - FuriHalClockMcoMsi32m, - FuriHalClockMcoMsi48m, -}; - -static const char* const mco_divisor_names[] = { - "1", - "2", - "4", - "8", - "16", -}; - -static const FuriHalClockMcoDivisorId mco_divisors[] = { - FuriHalClockMcoDiv1, - FuriHalClockMcoDiv2, - FuriHalClockMcoDiv4, - FuriHalClockMcoDiv8, - FuriHalClockMcoDiv16, -}; - -static void mco_source_list_change_callback(VariableItem* item) { - SignalGenApp* app = variable_item_get_context(item); - uint8_t index = variable_item_get_current_value_index(item); - variable_item_set_current_value_text(item, mco_source_names[index]); - - app->mco_src = mco_sources[index]; - - view_dispatcher_send_custom_event(app->view_dispatcher, SignalGenMcoEventUpdate); -} - -static void mco_divisor_list_change_callback(VariableItem* item) { - SignalGenApp* app = variable_item_get_context(item); - uint8_t index = variable_item_get_current_value_index(item); - variable_item_set_current_value_text(item, mco_divisor_names[index]); - - app->mco_div = mco_divisors[index]; - - view_dispatcher_send_custom_event(app->view_dispatcher, SignalGenMcoEventUpdate); -} - -void signal_gen_scene_mco_on_enter(void* context) { - SignalGenApp* app = context; - VariableItemList* var_item_list = app->var_item_list; - - VariableItem* item; - - item = variable_item_list_add(var_item_list, "GPIO Pin", COUNT_OF(mco_pin_names), NULL, NULL); - variable_item_set_current_value_index(item, 0); - variable_item_set_current_value_text(item, mco_pin_names[0]); - - item = variable_item_list_add( - var_item_list, - "Frequency", - COUNT_OF(mco_source_names), - mco_source_list_change_callback, - app); - variable_item_set_current_value_index(item, 0); - variable_item_set_current_value_text(item, mco_source_names[0]); - - item = variable_item_list_add( - var_item_list, - "Freq. divider", - COUNT_OF(mco_divisor_names), - mco_divisor_list_change_callback, - app); - variable_item_set_current_value_index(item, 0); - variable_item_set_current_value_text(item, mco_divisor_names[0]); - - variable_item_list_set_selected_item(var_item_list, LineIndexSource); - - view_dispatcher_switch_to_view(app->view_dispatcher, SignalGenViewVarItemList); - - app->mco_src = FuriHalClockMcoLse; - app->mco_div = FuriHalClockMcoDiv1; - furi_hal_clock_mco_enable(app->mco_src, app->mco_div); - furi_hal_gpio_init_ex( - &gpio_usart_tx, GpioModeAltFunctionPushPull, GpioPullUp, GpioSpeedVeryHigh, GpioAltFn0MCO); -} - -bool signal_gen_scene_mco_on_event(void* context, SceneManagerEvent event) { - SignalGenApp* app = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SignalGenMcoEventUpdate) { - consumed = true; - furi_hal_clock_mco_enable(app->mco_src, app->mco_div); - } - } - return consumed; -} - -void signal_gen_scene_mco_on_exit(void* context) { - SignalGenApp* app = context; - variable_item_list_reset(app->var_item_list); - furi_hal_gpio_init_ex( - &gpio_usart_tx, - GpioModeAltFunctionPushPull, - GpioPullUp, - GpioSpeedVeryHigh, - GpioAltFn7USART1); - furi_hal_clock_mco_disable(); -} diff --git a/applications/external/signal_generator/scenes/signal_gen_scene_pwm.c b/applications/external/signal_generator/scenes/signal_gen_scene_pwm.c deleted file mode 100644 index 1cadb3a1a47..00000000000 --- a/applications/external/signal_generator/scenes/signal_gen_scene_pwm.c +++ /dev/null @@ -1,79 +0,0 @@ -#include "../signal_gen_app_i.h" - -static const FuriHalPwmOutputId pwm_ch_id[] = { - FuriHalPwmOutputIdTim1PA7, - FuriHalPwmOutputIdLptim2PA4, -}; - -#define DEFAULT_FREQ 1000 -#define DEFAULT_DUTY 50 - -static void - signal_gen_pwm_callback(uint8_t channel_id, uint32_t freq, uint8_t duty, void* context) { - SignalGenApp* app = context; - - app->pwm_freq = freq; - app->pwm_duty = duty; - - if(app->pwm_ch != pwm_ch_id[channel_id]) { //-V1051 - app->pwm_ch_prev = app->pwm_ch; - app->pwm_ch = pwm_ch_id[channel_id]; - view_dispatcher_send_custom_event(app->view_dispatcher, SignalGenPwmEventChannelChange); - } else { - app->pwm_ch = pwm_ch_id[channel_id]; //-V1048 - view_dispatcher_send_custom_event(app->view_dispatcher, SignalGenPwmEventUpdate); - } -} - -void signal_gen_scene_pwm_on_enter(void* context) { - SignalGenApp* app = context; - - view_dispatcher_switch_to_view(app->view_dispatcher, SignalGenViewPwm); - - signal_gen_pwm_set_callback(app->pwm_view, signal_gen_pwm_callback, app); - - signal_gen_pwm_set_params(app->pwm_view, 0, DEFAULT_FREQ, DEFAULT_DUTY); - - if(!furi_hal_pwm_is_running(pwm_ch_id[0])) { - furi_hal_pwm_start(pwm_ch_id[0], DEFAULT_FREQ, DEFAULT_DUTY); - } else { - furi_hal_pwm_stop(pwm_ch_id[0]); - furi_hal_pwm_start(pwm_ch_id[0], DEFAULT_FREQ, DEFAULT_DUTY); - } -} - -bool signal_gen_scene_pwm_on_event(void* context, SceneManagerEvent event) { - SignalGenApp* app = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SignalGenPwmEventUpdate) { - consumed = true; - furi_hal_pwm_set_params(app->pwm_ch, app->pwm_freq, app->pwm_duty); - } else if(event.event == SignalGenPwmEventChannelChange) { - consumed = true; - // Stop previous channel PWM - if(furi_hal_pwm_is_running(app->pwm_ch_prev)) { - furi_hal_pwm_stop(app->pwm_ch_prev); - } - - // Start PWM and restart if it was starter already - if(furi_hal_pwm_is_running(app->pwm_ch)) { - furi_hal_pwm_stop(app->pwm_ch); - furi_hal_pwm_start(app->pwm_ch, app->pwm_freq, app->pwm_duty); - } else { - furi_hal_pwm_start(app->pwm_ch, app->pwm_freq, app->pwm_duty); - } - } - } - return consumed; -} - -void signal_gen_scene_pwm_on_exit(void* context) { - SignalGenApp* app = context; - variable_item_list_reset(app->var_item_list); - - if(furi_hal_pwm_is_running(app->pwm_ch)) { - furi_hal_pwm_stop(app->pwm_ch); - } -} diff --git a/applications/external/signal_generator/scenes/signal_gen_scene_start.c b/applications/external/signal_generator/scenes/signal_gen_scene_start.c deleted file mode 100644 index 3c7b9cc32a1..00000000000 --- a/applications/external/signal_generator/scenes/signal_gen_scene_start.c +++ /dev/null @@ -1,55 +0,0 @@ -#include "../signal_gen_app_i.h" - -typedef enum { - SubmenuIndexPwm, - SubmenuIndexClockOutput, -} SubmenuIndex; - -void signal_gen_scene_start_submenu_callback(void* context, uint32_t index) { - SignalGenApp* app = context; - - view_dispatcher_send_custom_event(app->view_dispatcher, index); -} - -void signal_gen_scene_start_on_enter(void* context) { - SignalGenApp* app = context; - Submenu* submenu = app->submenu; - - submenu_add_item( - submenu, "PWM Generator", SubmenuIndexPwm, signal_gen_scene_start_submenu_callback, app); - submenu_add_item( - submenu, - "Clock Generator", - SubmenuIndexClockOutput, - signal_gen_scene_start_submenu_callback, - app); - - submenu_set_selected_item( - submenu, scene_manager_get_scene_state(app->scene_manager, SignalGenSceneStart)); - - view_dispatcher_switch_to_view(app->view_dispatcher, SignalGenViewSubmenu); -} - -bool signal_gen_scene_start_on_event(void* context, SceneManagerEvent event) { - SignalGenApp* app = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SubmenuIndexPwm) { - scene_manager_next_scene(app->scene_manager, SignalGenScenePwm); - consumed = true; - } else if(event.event == SubmenuIndexClockOutput) { - scene_manager_next_scene(app->scene_manager, SignalGenSceneMco); - consumed = true; - } - scene_manager_set_scene_state(app->scene_manager, SignalGenSceneStart, event.event); - } - - return consumed; -} - -void signal_gen_scene_start_on_exit(void* context) { - SignalGenApp* app = context; - - submenu_reset(app->submenu); -} diff --git a/applications/external/signal_generator/signal_gen_10px.png b/applications/external/signal_generator/signal_gen_10px.png deleted file mode 100644 index 9f6dcc5d0d9a9c1fbc54b7459b46c50c16c7e863..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6082 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 diff --git a/applications/external/signal_generator/signal_gen_app.c b/applications/external/signal_generator/signal_gen_app.c deleted file mode 100644 index ca065d330d1..00000000000 --- a/applications/external/signal_generator/signal_gen_app.c +++ /dev/null @@ -1,93 +0,0 @@ -#include "signal_gen_app_i.h" - -#include -#include - -static bool signal_gen_app_custom_event_callback(void* context, uint32_t event) { - furi_assert(context); - SignalGenApp* app = context; - return scene_manager_handle_custom_event(app->scene_manager, event); -} - -static bool signal_gen_app_back_event_callback(void* context) { - furi_assert(context); - SignalGenApp* app = context; - return scene_manager_handle_back_event(app->scene_manager); -} - -static void signal_gen_app_tick_event_callback(void* context) { - furi_assert(context); - SignalGenApp* app = context; - scene_manager_handle_tick_event(app->scene_manager); -} - -SignalGenApp* signal_gen_app_alloc() { - SignalGenApp* app = malloc(sizeof(SignalGenApp)); - - app->gui = furi_record_open(RECORD_GUI); - - app->view_dispatcher = view_dispatcher_alloc(); - app->scene_manager = scene_manager_alloc(&signal_gen_scene_handlers, app); - view_dispatcher_enable_queue(app->view_dispatcher); - view_dispatcher_set_event_callback_context(app->view_dispatcher, app); - - view_dispatcher_set_custom_event_callback( - app->view_dispatcher, signal_gen_app_custom_event_callback); - view_dispatcher_set_navigation_event_callback( - app->view_dispatcher, signal_gen_app_back_event_callback); - view_dispatcher_set_tick_event_callback( - app->view_dispatcher, signal_gen_app_tick_event_callback, 100); - - view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); - - app->var_item_list = variable_item_list_alloc(); - view_dispatcher_add_view( - app->view_dispatcher, - SignalGenViewVarItemList, - variable_item_list_get_view(app->var_item_list)); - - app->submenu = submenu_alloc(); - view_dispatcher_add_view( - app->view_dispatcher, SignalGenViewSubmenu, submenu_get_view(app->submenu)); - - app->pwm_view = signal_gen_pwm_alloc(); - view_dispatcher_add_view( - app->view_dispatcher, SignalGenViewPwm, signal_gen_pwm_get_view(app->pwm_view)); - - scene_manager_next_scene(app->scene_manager, SignalGenSceneStart); - - return app; -} - -void signal_gen_app_free(SignalGenApp* app) { - furi_assert(app); - - // Views - view_dispatcher_remove_view(app->view_dispatcher, SignalGenViewVarItemList); - view_dispatcher_remove_view(app->view_dispatcher, SignalGenViewSubmenu); - view_dispatcher_remove_view(app->view_dispatcher, SignalGenViewPwm); - - submenu_free(app->submenu); - variable_item_list_free(app->var_item_list); - signal_gen_pwm_free(app->pwm_view); - - // View dispatcher - view_dispatcher_free(app->view_dispatcher); - scene_manager_free(app->scene_manager); - - // Close records - furi_record_close(RECORD_GUI); - - free(app); -} - -int32_t signal_gen_app(void* p) { - UNUSED(p); - SignalGenApp* signal_gen_app = signal_gen_app_alloc(); - - view_dispatcher_run(signal_gen_app->view_dispatcher); - - signal_gen_app_free(signal_gen_app); - - return 0; -} diff --git a/applications/external/signal_generator/signal_gen_app_i.h b/applications/external/signal_generator/signal_gen_app_i.h deleted file mode 100644 index 60e4d7ed9ab..00000000000 --- a/applications/external/signal_generator/signal_gen_app_i.h +++ /dev/null @@ -1,46 +0,0 @@ -#pragma once - -#include "scenes/signal_gen_scene.h" - -#include -#include - -#include -#include -#include -#include -#include -#include -#include "views/signal_gen_pwm.h" - -typedef struct SignalGenApp SignalGenApp; - -struct SignalGenApp { - Gui* gui; - ViewDispatcher* view_dispatcher; - SceneManager* scene_manager; - - VariableItemList* var_item_list; - Submenu* submenu; - SignalGenPwm* pwm_view; - - FuriHalClockMcoSourceId mco_src; - FuriHalClockMcoDivisorId mco_div; - - FuriHalPwmOutputId pwm_ch_prev; - FuriHalPwmOutputId pwm_ch; - uint32_t pwm_freq; - uint8_t pwm_duty; -}; - -typedef enum { - SignalGenViewVarItemList, - SignalGenViewSubmenu, - SignalGenViewPwm, -} SignalGenAppView; - -typedef enum { - SignalGenMcoEventUpdate, - SignalGenPwmEventUpdate, - SignalGenPwmEventChannelChange, -} SignalGenCustomEvent; diff --git a/applications/external/signal_generator/views/signal_gen_pwm.c b/applications/external/signal_generator/views/signal_gen_pwm.c deleted file mode 100644 index d625ed5a9e2..00000000000 --- a/applications/external/signal_generator/views/signal_gen_pwm.c +++ /dev/null @@ -1,310 +0,0 @@ -#include "../signal_gen_app_i.h" -#include -#include -#include - -typedef enum { - LineIndexChannel, - LineIndexFrequency, - LineIndexDuty, - LineIndexTotalCount -} LineIndex; - -static const char* const pwm_ch_names[] = {"2(A7)", "4(A4)"}; - -struct SignalGenPwm { - View* view; - SignalGenPwmViewCallback callback; - void* context; -}; - -typedef struct { - LineIndex line_sel; - bool edit_mode; - uint8_t edit_digit; - - uint8_t channel_id; - uint32_t freq; - uint8_t duty; - -} SignalGenPwmViewModel; - -#define ITEM_H 64 / 3 -#define ITEM_W 128 - -#define VALUE_X 100 -#define VALUE_W 45 - -#define FREQ_VALUE_X 62 -#define FREQ_MAX 1000000UL -#define FREQ_DIGITS_NB 7 - -static void pwm_set_config(SignalGenPwm* pwm) { - FuriHalPwmOutputId channel; - uint32_t freq; - uint8_t duty; - - with_view_model( - pwm->view, - SignalGenPwmViewModel * model, - { - channel = model->channel_id; - freq = model->freq; - duty = model->duty; - }, - false); - - furi_assert(pwm->callback); - pwm->callback(channel, freq, duty, pwm->context); -} - -static void pwm_channel_change(SignalGenPwmViewModel* model, InputEvent* event) { - if(event->key == InputKeyLeft) { - if(model->channel_id > 0) { - model->channel_id--; - } - } else if(event->key == InputKeyRight) { - if(model->channel_id < (COUNT_OF(pwm_ch_names) - 1)) { - model->channel_id++; - } - } -} - -static void pwm_duty_change(SignalGenPwmViewModel* model, InputEvent* event) { - if(event->key == InputKeyLeft) { - if(model->duty > 0) { - model->duty--; - } - } else if(event->key == InputKeyRight) { - if(model->duty < 100) { - model->duty++; - } - } -} - -static bool pwm_freq_edit(SignalGenPwmViewModel* model, InputEvent* event) { - bool consumed = false; - if((event->type == InputTypeShort) || (event->type == InputTypeRepeat)) { - if(event->key == InputKeyRight) { - if(model->edit_digit > 0) { - model->edit_digit--; - } - consumed = true; - } else if(event->key == InputKeyLeft) { - if(model->edit_digit < (FREQ_DIGITS_NB - 1)) { - model->edit_digit++; - } - consumed = true; - } else if(event->key == InputKeyUp) { - uint32_t step = 1; - for(uint8_t i = 0; i < model->edit_digit; i++) { - step *= 10; - } - if((model->freq + step) < FREQ_MAX) { - model->freq += step; - } else { - model->freq = FREQ_MAX; - } - consumed = true; - } else if(event->key == InputKeyDown) { - uint32_t step = 1; - for(uint8_t i = 0; i < model->edit_digit; i++) { - step *= 10; - } - if(model->freq > (step + 1)) { - model->freq -= step; - } else { - model->freq = 1; - } - consumed = true; - } - } - return consumed; -} - -static void signal_gen_pwm_draw_callback(Canvas* canvas, void* _model) { - SignalGenPwmViewModel* model = _model; - char* line_label = NULL; - char val_text[16]; - - for(size_t line = 0; line < LineIndexTotalCount; line++) { - if(line == LineIndexChannel) { - line_label = "GPIO Pin"; - } else if(line == LineIndexFrequency) { - line_label = "Frequency"; - } else if(line == LineIndexDuty) { //-V547 - line_label = "Pulse width"; - } - - canvas_set_color(canvas, ColorBlack); - if(line == model->line_sel) { - elements_slightly_rounded_box(canvas, 0, ITEM_H * line + 1, ITEM_W, ITEM_H - 1); - canvas_set_color(canvas, ColorWhite); - } - - uint8_t text_y = ITEM_H * line + ITEM_H / 2 + 2; - - canvas_draw_str_aligned(canvas, 6, text_y, AlignLeft, AlignCenter, line_label); - - if(line == LineIndexChannel) { - snprintf(val_text, sizeof(val_text), "%s", pwm_ch_names[model->channel_id]); - canvas_draw_str_aligned(canvas, VALUE_X, text_y, AlignCenter, AlignCenter, val_text); - if(model->channel_id != 0) { - canvas_draw_str_aligned( - canvas, VALUE_X - VALUE_W / 2, text_y, AlignCenter, AlignCenter, "<"); - } - if(model->channel_id != (COUNT_OF(pwm_ch_names) - 1)) { - canvas_draw_str_aligned( - canvas, VALUE_X + VALUE_W / 2, text_y, AlignCenter, AlignCenter, ">"); - } - } else if(line == LineIndexFrequency) { - snprintf(val_text, sizeof(val_text), "%7lu Hz", model->freq); - canvas_set_font(canvas, FontKeyboard); - canvas_draw_str_aligned( - canvas, FREQ_VALUE_X, text_y, AlignLeft, AlignCenter, val_text); - canvas_set_font(canvas, FontSecondary); - - if(model->edit_mode) { - uint8_t icon_x = (FREQ_VALUE_X) + (FREQ_DIGITS_NB - model->edit_digit - 1) * 6; - canvas_draw_icon(canvas, icon_x, text_y - 9, &I_SmallArrowUp_3x5); - canvas_draw_icon(canvas, icon_x, text_y + 5, &I_SmallArrowDown_3x5); - } - } else if(line == LineIndexDuty) { //-V547 - snprintf(val_text, sizeof(val_text), "%d%%", model->duty); - canvas_draw_str_aligned(canvas, VALUE_X, text_y, AlignCenter, AlignCenter, val_text); - if(model->duty != 0) { - canvas_draw_str_aligned( - canvas, VALUE_X - VALUE_W / 2, text_y, AlignCenter, AlignCenter, "<"); - } - if(model->duty != 100) { - canvas_draw_str_aligned( - canvas, VALUE_X + VALUE_W / 2, text_y, AlignCenter, AlignCenter, ">"); - } - } - } -} - -static bool signal_gen_pwm_input_callback(InputEvent* event, void* context) { - furi_assert(context); - SignalGenPwm* pwm = context; - bool consumed = false; - bool need_update = false; - - with_view_model( - pwm->view, - SignalGenPwmViewModel * model, - { - if(model->edit_mode == false) { - if((event->type == InputTypeShort) || (event->type == InputTypeRepeat)) { - if(event->key == InputKeyUp) { - if(model->line_sel == 0) { - model->line_sel = LineIndexTotalCount - 1; - } else { - model->line_sel = - CLAMP(model->line_sel - 1, LineIndexTotalCount - 1, 0); - } - consumed = true; - } else if(event->key == InputKeyDown) { - if(model->line_sel == LineIndexTotalCount - 1) { - model->line_sel = 0; - } else { - model->line_sel = - CLAMP(model->line_sel + 1, LineIndexTotalCount - 1, 0); - } - consumed = true; - } else if((event->key == InputKeyLeft) || (event->key == InputKeyRight)) { - if(model->line_sel == LineIndexChannel) { - pwm_channel_change(model, event); - need_update = true; - } else if(model->line_sel == LineIndexDuty) { - pwm_duty_change(model, event); - need_update = true; - } else if(model->line_sel == LineIndexFrequency) { - model->edit_mode = true; - } - consumed = true; - } else if(event->key == InputKeyOk) { - if(model->line_sel == LineIndexFrequency) { - model->edit_mode = true; - } - consumed = true; - } - } - } else { - if((event->key == InputKeyOk) || (event->key == InputKeyBack)) { - if(event->type == InputTypeShort) { - model->edit_mode = false; - consumed = true; - } - } else { - if(model->line_sel == LineIndexFrequency) { - consumed = pwm_freq_edit(model, event); - need_update = consumed; - } - } - } - }, - true); - - if(need_update) { - pwm_set_config(pwm); - } - - return consumed; -} - -SignalGenPwm* signal_gen_pwm_alloc() { - SignalGenPwm* pwm = malloc(sizeof(SignalGenPwm)); - - pwm->view = view_alloc(); - view_allocate_model(pwm->view, ViewModelTypeLocking, sizeof(SignalGenPwmViewModel)); - view_set_context(pwm->view, pwm); - view_set_draw_callback(pwm->view, signal_gen_pwm_draw_callback); - view_set_input_callback(pwm->view, signal_gen_pwm_input_callback); - - return pwm; -} - -void signal_gen_pwm_free(SignalGenPwm* pwm) { - furi_assert(pwm); - view_free(pwm->view); - free(pwm); -} - -View* signal_gen_pwm_get_view(SignalGenPwm* pwm) { - furi_assert(pwm); - return pwm->view; -} - -void signal_gen_pwm_set_callback( - SignalGenPwm* pwm, - SignalGenPwmViewCallback callback, - void* context) { - furi_assert(pwm); - furi_assert(callback); - - with_view_model( - pwm->view, - SignalGenPwmViewModel * model, - { - UNUSED(model); - pwm->callback = callback; - pwm->context = context; - }, - false); -} - -void signal_gen_pwm_set_params(SignalGenPwm* pwm, uint8_t channel_id, uint32_t freq, uint8_t duty) { - with_view_model( - pwm->view, - SignalGenPwmViewModel * model, - { - model->channel_id = channel_id; - model->freq = freq; - model->duty = duty; - }, - true); - - furi_assert(pwm->callback); - pwm->callback(channel_id, freq, duty, pwm->context); -} diff --git a/applications/external/signal_generator/views/signal_gen_pwm.h b/applications/external/signal_generator/views/signal_gen_pwm.h deleted file mode 100644 index 986794e7a28..00000000000 --- a/applications/external/signal_generator/views/signal_gen_pwm.h +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -#include -#include "../signal_gen_app_i.h" - -typedef struct SignalGenPwm SignalGenPwm; -typedef void ( - *SignalGenPwmViewCallback)(uint8_t channel_id, uint32_t freq, uint8_t duty, void* context); - -SignalGenPwm* signal_gen_pwm_alloc(); - -void signal_gen_pwm_free(SignalGenPwm* pwm); - -View* signal_gen_pwm_get_view(SignalGenPwm* pwm); - -void signal_gen_pwm_set_callback( - SignalGenPwm* pwm, - SignalGenPwmViewCallback callback, - void* context); - -void signal_gen_pwm_set_params(SignalGenPwm* pwm, uint8_t channel_id, uint32_t freq, uint8_t duty); diff --git a/applications/external/spi_mem_manager/application.fam b/applications/external/spi_mem_manager/application.fam deleted file mode 100644 index c1b10bfee24..00000000000 --- a/applications/external/spi_mem_manager/application.fam +++ /dev/null @@ -1,17 +0,0 @@ -App( - appid="spi_mem_manager", - name="SPI Mem Manager", - apptype=FlipperAppType.EXTERNAL, - entry_point="spi_mem_app", - requires=["gui"], - stack_size=1 * 2048, - order=30, - fap_icon="images/Dip8_10px.png", - fap_category="GPIO", - fap_icon_assets="images", - fap_private_libs=[ - Lib( - name="spi", - ), - ], -) diff --git a/applications/external/spi_mem_manager/images/ChipLooking_64x64/frame_01.png b/applications/external/spi_mem_manager/images/ChipLooking_64x64/frame_01.png deleted file mode 100644 index 4ff2e3042e540d8b499118e9edf4ecd180b39511..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7231 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 NY%KPgSDX4C`yZHc9FG71 diff --git a/applications/external/spi_mem_manager/images/ChipLooking_64x64/frame_03.png b/applications/external/spi_mem_manager/images/ChipLooking_64x64/frame_03.png deleted file mode 100644 index 1342dc7bf95265db5d0ab2924e1ef03fe4df9943..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7308 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 diff --git a/applications/external/spi_mem_manager/images/ChipLooking_64x64/frame_rate b/applications/external/spi_mem_manager/images/ChipLooking_64x64/frame_rate deleted file mode 100644 index d8263ee9860..00000000000 --- a/applications/external/spi_mem_manager/images/ChipLooking_64x64/frame_rate +++ /dev/null @@ -1 +0,0 @@ -2 \ No newline at end of file diff --git a/applications/external/spi_mem_manager/images/Dip8_10px.png b/applications/external/spi_mem_manager/images/Dip8_10px.png deleted file mode 100644 index 9de9364d13c5cbd7fe43a6df59789193d9133057..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 195 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V6Od#Ih{XE z)7O>#85cXB6sN>NS&&A_64!_l=ltB<)VvY~=c3falGGH1^30M91$R&1fbd2>aiAhw zPZ!4!iOaqHj$8}|9EV^1`~UonL4lUvGtG!wL5x*034X4|#-7<_>P#}{{_l#8SgPZ+ js&kpz9*+3KCj7Uj_`9EfRa>$PXb^*^tDnm{r-UW|gHJf_ diff --git a/applications/external/spi_mem_manager/images/Dip8_32x36.png b/applications/external/spi_mem_manager/images/Dip8_32x36.png deleted file mode 100644 index 8f01af2760363b31029cbc425d9a9f2e2becf96b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 977 zcmeAS@N?(olHy`uVBq!ia0vp^3P7yF!3-qto1VD9z`)E9;1l8s2$&H|6fVg?3oVGw3ym^DWNC|K_4;uvDl`*xDGP=kVibG*#E`lk^cO$zev z+}xJ~R3@KooBBeTVTU7E1RI;jy{)QdNiPgUSiTrM5ocMrlj*@Cjo*LH^-f?g|F=lv zl)Fs&L8nKli4M~yCl?8HDNptH;hVjxE12WemhD};j?@R{i;C*6a`dzQvVm!LtxQw_ S>l}Mfba=Y@xvXZB8b0=g#+k z_y7La$vcsnwa$(njyxXES*27&ad(Ehf@a%szx(??vFC0MMrAy=Img9%&ES885R5X2P@K{dB9p<$p?SQ()g~i~r4cNkC3JdH&i}sg6d%yza(--p8dMv@ zh!njtmnNcfH8EIj8V2M1)j>d@3E>C~1d9SDLpsSICOLnC7va{{Z80C1fUs$Deu(uz zAWj_#gi$mBNJXF!13?Io!6J#&-(L!@03Z+o#bAI~0tqEj1oTHFGGOY%=T4*XWF$)Q z`>C_ICpkZbWsQhfoSmI5%Jvgcv`#F6VOR`8Vh9p)2qBY0vZzT&GE1fz6a<6OdLyf+ zNWjX7YNwhuAF&nut zlTM%T7{|m!I$L;81?0JCCML&7h@%LG%A_%3 zO%`|J5~~^`5=Ij!OVKeDl|G%Q$Z2^1BoRS?PpqEAscc5@GXp|_vV@$^WlbUkA?_Ok zK?n#V5acTX5fGe&s<}GAQ5OB*z!a`e&iO^CEx1S+l}^!W3g`Ur;{!N`BvZ5jW@%VH?>OF2Tk@O zPGOv@&rGEfX|lv0Cxk2gKu)ie6Af#Vr9x}>!CI+Aiv@szVry$~6u{(al2-hTBEgTzn_D^}jklllIvu1V{Q`ig6OgP|0jI zN)sVEE|=@hm?j7H6PqgYzU5==|fB0<6@J90B?N8); z?B48M`Q6&q<>QYftD|a*tJ$!0YduA;TS}(23t@i9jJ}9E&d>+O-{j}lDtd6mP7wiU?pLh0* zla-TQ!!6f>9b(>jct-Z*@vzVmEjaUp9adYyRH)W#u&{1)0G7#K8z}OOe9Z4J`?k~5 z;u#n4^?R%GdBZDjly!H8xtVMF9ud_Q|CsUp%X4BI?jMd19&&9{QqgG_a)Rz9J*BH| z$zM9cbZYA6R(n(=QYD(cO(#Aoy6CQh;hG<}_gRz&>ZIovmNuT&Z9VwM8m5pu&$kG$ zvTJ!+pA|E6E-UBtJJrv;*XaRo7|Z#x4L(qON`UQa?6`jZqnkg3XliTEuJKo%PCa~M z@WlnE3u1ZRT?c;b@m&$07PGImr1km-TQZ8*DS|rZudw{x4R!5F9=$VOt{XWj(Y>BT zd-yG`a(KJ-o0Dfs8h&U=J*C(_ z=8hNq6aC?^r7wqGy5!v`zvX@KNEDDEpXqBVXiB`Z=eNZRgGG2tG`F;x~xDn9)G1Y@4Fl28Px*E!|ivy@~-8Lx%@`DyQ}?V z4f!BGF*jl}N~1D%!=YeZY6W)9lyDw_Uq#NDJx^=CJZDD2|CF# zA7Ixt{Z7BT8@4fZgFkI{D9fJxang<$JS``+d(*81cbB@prG*c!rZ)8U4y-<__Pt)Z zZ3lJfK;Y5eZHd?A3O-!mWX3$UChhmy)r@4iKkvyz(mdTtF7?TWn4`7t4=} zZ`OLe!fHzEo3eUH7jwVD-n?Xnx$AC<-H6`;RB2iYH9UO}ROfZkPOl32mRZ%`xW#FL zD@GqK${E&#=gzidc(qkxLZ^tk7u}u0Uu|;00}}A@rq4$9xE75>Hwj!4$Nk!`)YmDg{{4HeKCy?7Z85xPzg%Peucca}QJ6#D*z!+`G0ZOj diff --git a/applications/external/spi_mem_manager/images/DolphinNice_96x59.png b/applications/external/spi_mem_manager/images/DolphinNice_96x59.png deleted file mode 100644 index a299d3630239b4486e249cc501872bed5996df3b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2459 zcmbVO3s4i+8V(M(gEFORwSrA`4O0uPn|M|5y* zB*aMDxC&7(gP9JN;POOi-9khrC>Z9YJs2U!LnVcQEEC0fDtKo&ILlzb30%M}3J^;~ zv7RzcsilOs4Mq@tD*&R;!LMSk2A~{(`HK9|hQBqEX)3sQr9Je6SZU*F-^fD-p+~Hs; zHLkO%v?>ZoxEv+F#whudr%615FkA0DYR0tMEo}3OOY#xecLWe>xV?u5KtSmC^ z7)Fmj6gjfKstiEV-*Cxbbb+&rRWuI_rBJ)ybs_f1Rn&f2>q3pYwI^|J(hdn{j{0EZIm_F zpIyIWLsRUgOItR-dUbVd|6Zo=_BU_Tj4|{{jxO#=JH4o8er(5{!nZD_j4}MH&zh~9 zVLC~y(0-D6GO0ghZD8BYzP?o{>22~lT6^d@X{SwQ8vrNY-PPIMajIwC)`s14Ep72@ zeq7YOzM`?U{+W)ocXBr`eSOcpk?Rxc=ou5&)fWW|pD};-Z0mvk9}=&`Rb&y<77W~a z(>6YM;6Y5aIU~JKZ}mQZynKHiSTQ#Bczn@&jTiN^?vPJ(jhm7cXLx0oum5P$`TceG zU+wR;OO^)8CVlnM)5p$CO&e94KJt>HccCaHGusmW_b`T6m| z-R6V6Db1pErTot?^d22ojm+2>_)FbD`_+WbDGMx9f@hO27maS2`csiV(D&Fs`PS2& zvrq18du_&zXID(!KIxsU$)iuTYuZ?zmYiP&n&i@Be{IdbS-jA2c0QAlu5NXQv_0K< z3Hvs4eeu6B7yD&CNT~gIkMV&UkRU=V!iQ(+_(O&u^ah$+s{_yn(yBYeD40HeU{xGsIT6W Zfq!wOp!Q8gyJRa{_+4UIP z$!=1i2t*O^Q!1)9DGybMBGF3a6SW|L6h5LVfJD{LegT4thW>zPQHvPws{yrXept!t zv3=&;bMHMf^XAC#V8_NS8##{a$PeX4*}aQh-5b`i|CfLH7_-|w{?PLw$KCsIeBHqv zeQy)T-TjJN7>~xyod%|r1hT0`619rY&>XjYN6klgf<(MUimsOtE`R=|z`J%v*qt1RpF9hwQq*vxPN&rD$57IyUT+iM0RsE`QpwMy9wjao*i^BQa%zm^2P4v8i*LT?<9 zA2&z%EDZ>+C4h(lkolCJfSRgm;7MKvGLS%0g0cuT1E>Z}@y(yWq6M~NjOGTKvDi~a zC`FNPNK&<0O;nWx4T=)fbzK6oB+DX0h~cysp_=H0T`h(j331^1kxM;3W<(a9j4}dK z+DM_|w`skwSteF6sfK(BCP1803uv0FLo1awI*j_KSd^yTn-YhGX`e`=B&3r8CjC>y zi@I9D{1T05SfaPk*8co2g*I*n^e2OIy*xISNSRa^cgV1?uFp5J0YMQB3Y3;xjT&i1 zyC$M##e?pUVhLRKj&_L)9?Wld>u%HCq;SStTN}Y*kccThRe>U`i)-U2J}i z;>oxY@%)BuZHgI3yPAgMs43vsNJP47iHfQ!qLpHl$)u9T2onYB=@#3rz-223l~=OH zs_a-*O3@VL0MW4s7KyDoOqHyNDzSA4a2n~Dsk#w2OUpDcsm-dZtbCu(W=8_*xMlVs z93AZA^Zi*3>Y66X2`KP3HXIsM5Hp%vK}90@UNN>klflv*azobR>E=QjBQG^aWtXqJ z(?B?06d3`>ZXmYMeC^(>%xg-hL0c^mM!Jei8nBQ$Q56NGx5!#@TNg^V5+9y5Z&x22##B&{B?u64ye+M3KZ=XlsY71%@jTp=Dy zHDISkcY5&@J8>5Bx!%I~{^i3@-@m}$cNaW+{nKk_j+x(U>F+k}c?6!^@X+fADi3lO z_rLKG{`yum;ZU>ayk!Z+q>VzZOn^bqQ>?UO0Drz@_TNg$rjt zNcQEpt?wS&gMaKe^8V2SorGL{ZtT%R?yPd~`n9FG(}zAOOPl5My;t&Z3(*F9I?g}- zvp)ag@#QCe{o%9hrGrn+&dpu9`rFcD;MqUV>^-?JG4|Gpeamy-PL6)jzu5T`{Cd7~ fwr}T&J8Rt1;5!ej|9##1_yo=O59dzx?S1thQe1#H diff --git a/applications/external/spi_mem_manager/images/Wiring_SPI_128x64.png b/applications/external/spi_mem_manager/images/Wiring_SPI_128x64.png deleted file mode 100644 index e6c3ce363f801cff5080d10599ca258d07783fa1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4446 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 != SPIMemChipVendorUnknown && vendor->vendor_enum != vendor_enum) - vendor++; - return vendor->vendor_name; -} - -bool spi_mem_chip_find_all(SPIMemChip* chip_info, found_chips_t found_chips) { - const SPIMemChip* chip_info_arr; - found_chips_reset(found_chips); - for(chip_info_arr = SPIMemChips; chip_info_arr->model_name != NULL; chip_info_arr++) { - if(chip_info->vendor_id != chip_info_arr->vendor_id) continue; - if(chip_info->type_id != chip_info_arr->type_id) continue; - if(chip_info->capacity_id != chip_info_arr->capacity_id) continue; - found_chips_push_back(found_chips, chip_info_arr); - } - if(found_chips_size(found_chips)) return true; - return false; -} - -void spi_mem_chip_copy_chip_info(SPIMemChip* dest, const SPIMemChip* src) { - memcpy(dest, src, sizeof(SPIMemChip)); -} - -size_t spi_mem_chip_get_size(SPIMemChip* chip) { - return (chip->size); -} - -const char* spi_mem_chip_get_vendor_name(const SPIMemChip* chip) { - return (spi_mem_chip_search_vendor_name(chip->vendor_enum)); -} - -const char* spi_mem_chip_get_vendor_name_by_enum(uint32_t vendor_enum) { - return (spi_mem_chip_search_vendor_name(vendor_enum)); -} - -const char* spi_mem_chip_get_model_name(const SPIMemChip* chip) { - return (chip->model_name); -} - -uint8_t spi_mem_chip_get_vendor_id(SPIMemChip* chip) { - return (chip->vendor_id); -} - -uint8_t spi_mem_chip_get_type_id(SPIMemChip* chip) { - return (chip->type_id); -} - -uint8_t spi_mem_chip_get_capacity_id(SPIMemChip* chip) { - return (chip->capacity_id); -} - -SPIMemChipWriteMode spi_mem_chip_get_write_mode(SPIMemChip* chip) { - return (chip->write_mode); -} - -size_t spi_mem_chip_get_page_size(SPIMemChip* chip) { - return (chip->page_size); -} - -uint32_t spi_mem_chip_get_vendor_enum(const SPIMemChip* chip) { - return ((uint32_t)chip->vendor_enum); -} diff --git a/applications/external/spi_mem_manager/lib/spi/spi_mem_chip.h b/applications/external/spi_mem_manager/lib/spi/spi_mem_chip.h deleted file mode 100644 index 683937df4fd..00000000000 --- a/applications/external/spi_mem_manager/lib/spi/spi_mem_chip.h +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once - -#include -#include - -typedef struct SPIMemChip SPIMemChip; - -ARRAY_DEF(found_chips, const SPIMemChip*, M_POD_OPLIST) - -typedef enum { - SPIMemChipStatusBusy, - SPIMemChipStatusIdle, - SPIMemChipStatusError -} SPIMemChipStatus; - -typedef enum { - SPIMemChipWriteModeUnknown = 0, - SPIMemChipWriteModePage = (0x01 << 0), - SPIMemChipWriteModeAAIByte = (0x01 << 1), - SPIMemChipWriteModeAAIWord = (0x01 << 2), -} SPIMemChipWriteMode; - -const char* spi_mem_chip_get_vendor_name(const SPIMemChip* chip); -const char* spi_mem_chip_get_model_name(const SPIMemChip* chip); -size_t spi_mem_chip_get_size(SPIMemChip* chip); -uint8_t spi_mem_chip_get_vendor_id(SPIMemChip* chip); -uint8_t spi_mem_chip_get_type_id(SPIMemChip* chip); -uint8_t spi_mem_chip_get_capacity_id(SPIMemChip* chip); -SPIMemChipWriteMode spi_mem_chip_get_write_mode(SPIMemChip* chip); -size_t spi_mem_chip_get_page_size(SPIMemChip* chip); -bool spi_mem_chip_find_all(SPIMemChip* chip_info, found_chips_t found_chips); -void spi_mem_chip_copy_chip_info(SPIMemChip* dest, const SPIMemChip* src); -uint32_t spi_mem_chip_get_vendor_enum(const SPIMemChip* chip); -const char* spi_mem_chip_get_vendor_name_by_enum(uint32_t vendor_enum); diff --git a/applications/external/spi_mem_manager/lib/spi/spi_mem_chip_arr.c b/applications/external/spi_mem_manager/lib/spi/spi_mem_chip_arr.c deleted file mode 100644 index 25b237d6829..00000000000 --- a/applications/external/spi_mem_manager/lib/spi/spi_mem_chip_arr.c +++ /dev/null @@ -1,1399 +0,0 @@ -#include "spi_mem_chip_i.h" -const SPIMemChip SPIMemChips[] = { - {0x1F, 0x40, 0x00, "AT25DN256", 32768, 256, SPIMemChipVendorADESTO, SPIMemChipWriteModePage}, - {0x37, 0x20, 0x20, "A25L05PT", 65536, 256, SPIMemChipVendorAMIC, SPIMemChipWriteModePage}, - {0x37, 0x20, 0x10, "A25L05PU", 65536, 256, SPIMemChipVendorAMIC, SPIMemChipWriteModePage}, - {0x37, 0x20, 0x21, "A25L10PT", 131072, 256, SPIMemChipVendorAMIC, SPIMemChipWriteModePage}, - {0x37, 0x20, 0x11, "A25L10PU", 131072, 256, SPIMemChipVendorAMIC, SPIMemChipWriteModePage}, - {0x37, 0x20, 0x22, "A25L20PT", 262144, 256, SPIMemChipVendorAMIC, SPIMemChipWriteModePage}, - {0x37, 0x20, 0x12, "A25L20PU", 262144, 256, SPIMemChipVendorAMIC, SPIMemChipWriteModePage}, - {0x37, 0x20, 0x23, "A25L40PT", 524288, 256, SPIMemChipVendorAMIC, SPIMemChipWriteModePage}, - {0x37, 0x20, 0x13, "A25L40PU", 524288, 256, SPIMemChipVendorAMIC, SPIMemChipWriteModePage}, - {0x37, 0x20, 0x24, "A25L80PT", 1048576, 256, SPIMemChipVendorAMIC, SPIMemChipWriteModePage}, - {0x37, 0x20, 0x14, "A25L80PU", 1048576, 256, SPIMemChipVendorAMIC, SPIMemChipWriteModePage}, - {0x37, 0x20, 0x25, "A25L16PT", 2097152, 256, SPIMemChipVendorAMIC, SPIMemChipWriteModePage}, - {0x37, 0x20, 0x15, "A25L16PU", 2097152, 256, SPIMemChipVendorAMIC, SPIMemChipWriteModePage}, - {0x37, 0x30, 0x10, "A25L512", 65536, 256, SPIMemChipVendorAMIC, SPIMemChipWriteModePage}, - {0x37, 0x30, 0x11, "A25L010", 131072, 256, SPIMemChipVendorAMIC, SPIMemChipWriteModePage}, - {0x37, 0x30, 0x12, "A25L020", 262144, 256, SPIMemChipVendorAMIC, SPIMemChipWriteModePage}, - {0x37, 0x30, 0x13, "A25L040", 524288, 256, SPIMemChipVendorAMIC, SPIMemChipWriteModePage}, - {0x37, 0x30, 0x14, "A25L080", 1048576, 256, SPIMemChipVendorAMIC, SPIMemChipWriteModePage}, - {0x37, 0x30, 0x15, "A25L016", 2097152, 256, SPIMemChipVendorAMIC, SPIMemChipWriteModePage}, - {0x37, 0x30, 0x16, "A25L032", 4194304, 256, SPIMemChipVendorAMIC, SPIMemChipWriteModePage}, - {0x37, 0x40, 0x15, "A25LQ16", 2097152, 256, SPIMemChipVendorAMIC, SPIMemChipWriteModePage}, - {0x37, 0x40, 0x16, "A25LQ32A", 4194304, 256, SPIMemChipVendorAMIC, SPIMemChipWriteModePage}, - {0x68, 0x40, 0x14, "BY25D80", 1048576, 256, SPIMemChipVendorBoya, SPIMemChipWriteModePage}, - {0x1C, 0x20, 0x10, "EN25B05", 65536, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, - {0x1C, 0x20, 0x10, "EN25B05T", 65536, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, - {0x1C, 0x20, 0x11, "EN25B10", 131072, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, - {0x1C, 0x20, 0x11, "EN25B10T", 131072, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, - {0x1C, 0x20, 0x12, "EN25B20", 262144, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, - {0x1C, 0x20, 0x12, "EN25B20T", 262144, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, - {0x1C, 0x20, 0x13, "EN25B40", 524288, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, - {0x1C, 0x20, 0x13, "EN25B40T", 524288, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, - {0x1C, 0x20, 0x14, "EN25B80", 1048576, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, - {0x1C, 0x20, 0x14, "EN25B80T", 1048576, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, - {0x1C, 0x20, 0x15, "EN25B16", 2097152, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, - {0x1C, 0x20, 0x15, "EN25B16T", 2097152, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, - {0x1C, 0x20, 0x16, "EN25B32", 4194304, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, - {0x1C, 0x20, 0x16, "EN25B32T", 4194304, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, - {0x1C, 0x20, 0x17, "EN25B64", 8388608, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, - {0x1C, 0x20, 0x17, "EN25B64T", 8388608, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, - {0x1C, 0x31, 0x10, "EN25F05", 65536, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, - {0x1C, 0x31, 0x11, "EN25F10", 131072, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, - {0x1C, 0x31, 0x12, "EN25F20", 262144, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, - {0x1C, 0x31, 0x13, "EN25F40", 524288, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, - {0x1C, 0x31, 0x14, "EN25F80", 1048576, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, - {0x1C, 0x31, 0x15, "EN25F16", 2097152, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, - {0x1C, 0x31, 0x16, "EN25F32", 4194304, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, - {0x1C, 0x31, 0x10, "EN25LF05", 65536, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, - {0x1C, 0x31, 0x11, "EN25LF10", 131072, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, - {0x1C, 0x31, 0x12, "EN25LF20", 262144, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, - {0x1C, 0x31, 0x13, "EN25LF40", 524288, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, - {0x1C, 0x20, 0x10, "EN25P05", 65536, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, - {0x1C, 0x20, 0x11, "EN25P10", 131072, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, - {0x1C, 0x20, 0x12, "EN25P20", 262144, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, - {0x1C, 0x20, 0x13, "EN25P40", 524288, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, - {0x1C, 0x20, 0x14, "EN25P80", 1048576, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, - {0x1C, 0x20, 0x15, "EN25P16", 2097152, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, - {0x1C, 0x20, 0x16, "EN25P32", 4194304, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, - {0x1C, 0x20, 0x17, "EN25P64", 8388608, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, - {0x1C, 0x30, 0x13, "EN25Q40", 524288, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, - {0x1C, 0x30, 0x14, "EN25Q80A", 1048576, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, - {0x1C, 0x30, 0x15, "EN25Q16A", 2097152, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, - {0x1C, 0x30, 0x16, "EN25Q32A", 4194304, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, - {0x1C, 0x70, 0x16, "EN25Q32A", 4194304, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, - {0x1C, 0x30, 0x16, "EN25Q32B", 4194304, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, - {0x1C, 0x30, 0x17, "EN25Q64", 8388608, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, - {0x1C, 0x30, 0x18, "EN25Q128", 16777216, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, - {0x1C, 0x70, 0x15, "EN25QH16", 2097152, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, - {0x1C, 0x70, 0x16, "EN25QH32", 4194304, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, - {0x1C, 0x70, 0x17, "EN25QH64", 8388608, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, - {0x1C, 0x70, 0x18, "EN25QH128", 16777216, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, - {0x1C, 0x70, 0x19, "EN25QH256", 33554432, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, - {0x1C, 0x51, 0x14, "EN25T80", 1048576, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, - {0x1C, 0x51, 0x15, "EN25T16", 2097152, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, - {0x1C, 0x31, 0x17, "EN25F64", 8388608, 256, SPIMemChipVendorEON, SPIMemChipWriteModePage}, - {0x7F, 0x9D, 0x7C, "Pm25LV010", 131072, 256, SPIMemChipVendorPFLASH, SPIMemChipWriteModePage}, - {0x7F, 0x9D, 0x21, "Pm25LD010", 131072, 256, SPIMemChipVendorPFLASH, SPIMemChipWriteModePage}, - {0x7F, 0x9D, 0x22, "Pm25LV020", 262144, 256, SPIMemChipVendorPFLASH, SPIMemChipWriteModePage}, - {0x7F, 0x9D, 0x7D, "Pm25W020", 262144, 256, SPIMemChipVendorPFLASH, SPIMemChipWriteModePage}, - {0x7F, 0x9D, 0x7E, "Pm25LV040", 524288, 256, SPIMemChipVendorPFLASH, SPIMemChipWriteModePage}, - {0x37, 0x30, 0x10, "TS25L512A", 65536, 256, SPIMemChipVendorTERRA, SPIMemChipWriteModePage}, - {0x37, 0x30, 0x11, "TS25L010A", 131072, 256, SPIMemChipVendorTERRA, SPIMemChipWriteModePage}, - {0x37, 0x30, 0x12, "TS25L020A", 262144, 256, SPIMemChipVendorTERRA, SPIMemChipWriteModePage}, - {0x20, 0x20, 0x15, "TS25L16AP", 2097152, 256, SPIMemChipVendorTERRA, SPIMemChipWriteModePage}, - {0x20, 0x20, 0x15, "TS25L16BP", 2097152, 256, SPIMemChipVendorTERRA, SPIMemChipWriteModePage}, - {0x20, 0x20, 0x15, "ZP25L16P", 2097152, 256, SPIMemChipVendorTERRA, SPIMemChipWriteModePage}, - {0x20, 0x80, 0x15, "TS25L16PE", 2097152, 256, SPIMemChipVendorTERRA, SPIMemChipWriteModePage}, - {0x20, 0x80, 0x14, "TS25L80PE", 1048576, 256, SPIMemChipVendorTERRA, SPIMemChipWriteModePage}, - {0x37, 0x30, 0x16, "TS25L032A", 4194304, 256, SPIMemChipVendorTERRA, SPIMemChipWriteModePage}, - {0x20, 0x20, 0x13, "TS25L40P", 524288, 256, SPIMemChipVendorTERRA, SPIMemChipWriteModePage}, - {0xC2, - 0x20, - 0x10, - "GPR25L005E", - 65536, - 256, - SPIMemChipVendorGeneralplus, - SPIMemChipWriteModePage}, - {0xC2, - 0x20, - 0x15, - "GPR25L161B", - 262144, - 256, - SPIMemChipVendorGeneralplus, - SPIMemChipWriteModePage}, - {0xC2, - 0x20, - 0x12, - "GPR25L020B", - 262144, - 256, - SPIMemChipVendorGeneralplus, - SPIMemChipWriteModePage}, - {0xC2, - 0x20, - 0x16, - "GPR25L3203F", - 4194304, - 256, - SPIMemChipVendorGeneralplus, - SPIMemChipWriteModePage}, - {0x9D, 0x7B, 0x00, "AC25LV512", 65536, 256, SPIMemChipVendorDEUTRON, SPIMemChipWriteModePage}, - {0x9D, 0x7C, 0x00, "AC25LV010", 131072, 256, SPIMemChipVendorDEUTRON, SPIMemChipWriteModePage}, - {0x9D, 0x7B, 0x00, "EM25LV512", 65536, 256, SPIMemChipVendorEFST, SPIMemChipWriteModePage}, - {0x9D, 0x7C, 0x00, "EM25LV010", 131072, 256, SPIMemChipVendorEFST, SPIMemChipWriteModePage}, - {0x8C, 0x20, 0x13, "F25L004A", 524288, 256, SPIMemChipVendorEFST, SPIMemChipWriteModePage}, - {0x8C, 0x20, 0x14, "F25L008A", 1048576, 256, SPIMemChipVendorEFST, SPIMemChipWriteModePage}, - {0x8C, 0x20, 0x15, "F25L016A", 2097152, 256, SPIMemChipVendorEFST, SPIMemChipWriteModePage}, - {0x8C, 0x8C, 0x8C, "F25L04UA", 524288, 256, SPIMemChipVendorEFST, SPIMemChipWriteModePage}, - {0x8C, 0x20, 0x13, "F25L04P", 524288, 256, SPIMemChipVendorEFST, SPIMemChipWriteModePage}, - {0x8C, 0x30, 0x13, "F25S04P", 524288, 256, SPIMemChipVendorEFST, SPIMemChipWriteModePage}, - {0x8C, 0x20, 0x14, "F25L08P", 1048576, 256, SPIMemChipVendorEFST, SPIMemChipWriteModePage}, - {0x8C, 0x20, 0x15, "F25L16P", 2097152, 256, SPIMemChipVendorEFST, SPIMemChipWriteModePage}, - {0x8C, 0x20, 0x16, "F25L32P", 4194304, 256, SPIMemChipVendorEFST, SPIMemChipWriteModePage}, - {0x8C, 0x40, 0x16, "F25L32Q", 4194304, 256, SPIMemChipVendorEFST, SPIMemChipWriteModePage}, - {0x4A, 0x20, 0x11, "ES25P10", 131072, 256, SPIMemChipVendorEXCELSEMI, SPIMemChipWriteModePage}, - {0x4A, 0x20, 0x12, "ES25P20", 262144, 256, SPIMemChipVendorEXCELSEMI, SPIMemChipWriteModePage}, - {0x4A, 0x20, 0x13, "ES25P40", 524288, 256, SPIMemChipVendorEXCELSEMI, SPIMemChipWriteModePage}, - {0x4A, 0x20, 0x14, "ES25P80", 1048576, 256, SPIMemChipVendorEXCELSEMI, SPIMemChipWriteModePage}, - {0x4A, 0x20, 0x15, "ES25P16", 2097152, 256, SPIMemChipVendorEXCELSEMI, SPIMemChipWriteModePage}, - {0x4A, 0x20, 0x16, "ES25P32", 4194304, 256, SPIMemChipVendorEXCELSEMI, SPIMemChipWriteModePage}, - {0x4A, 0x32, 0x13, "ES25M40A", 524288, 256, SPIMemChipVendorEXCELSEMI, SPIMemChipWriteModePage}, - {0x4A, 0x32, 0x14, "ES25M80A", 1048576, 256, SPIMemChipVendorEXCELSEMI, SPIMemChipWriteModePage}, - {0x4A, 0x32, 0x15, "ES25M16A", 2097152, 256, SPIMemChipVendorEXCELSEMI, SPIMemChipWriteModePage}, - {0xF8, 0x32, 0x14, "FM25Q08A", 1048576, 256, SPIMemChipVendorFIDELIX, SPIMemChipWriteModePage}, - {0xF8, 0x32, 0x15, "FM25Q16A", 2097152, 256, SPIMemChipVendorFIDELIX, SPIMemChipWriteModePage}, - {0xF8, 0x32, 0x15, "FM25Q16B", 2097152, 256, SPIMemChipVendorFIDELIX, SPIMemChipWriteModePage}, - {0xF8, 0x32, 0x16, "FM25Q32A", 4194304, 256, SPIMemChipVendorFIDELIX, SPIMemChipWriteModePage}, - {0xF8, 0x32, 0x17, "FM25Q64A", 8388608, 256, SPIMemChipVendorFIDELIX, SPIMemChipWriteModePage}, - {0xC8, 0x30, 0x13, "GD25D40", 524288, 256, SPIMemChipVendorGIGADEVICE, SPIMemChipWriteModePage}, - {0xC8, 0x30, 0x14, "GD25D80", 1048576, 256, SPIMemChipVendorGIGADEVICE, SPIMemChipWriteModePage}, - {0xC8, 0x20, 0x13, "GD25F40", 524288, 256, SPIMemChipVendorGIGADEVICE, SPIMemChipWriteModePage}, - {0xC8, 0x20, 0x14, "GD25F80", 1048576, 256, SPIMemChipVendorGIGADEVICE, SPIMemChipWriteModePage}, - {0xC8, 0x40, 0x10, "GD25Q512", 65536, 256, SPIMemChipVendorGIGADEVICE, SPIMemChipWriteModePage}, - {0xC8, 0x40, 0x11, "GD25Q10", 131072, 256, SPIMemChipVendorGIGADEVICE, SPIMemChipWriteModePage}, - {0xC8, 0x40, 0x12, "GD25Q20", 262144, 256, SPIMemChipVendorGIGADEVICE, SPIMemChipWriteModePage}, - {0xC8, - 0x60, - 0x12, - "GD25LQ20C_1.8V", - 262144, - 256, - SPIMemChipVendorGIGADEVICE, - SPIMemChipWriteModePage}, - {0xC8, 0x40, 0x13, "GD25Q40", 524288, 256, SPIMemChipVendorGIGADEVICE, SPIMemChipWriteModePage}, - {0xC8, 0x40, 0x14, "GD25Q80", 1048576, 256, SPIMemChipVendorGIGADEVICE, SPIMemChipWriteModePage}, - {0xC8, - 0x40, - 0x14, - "GD25Q80B", - 1048576, - 256, - SPIMemChipVendorGIGADEVICE, - SPIMemChipWriteModePage}, - {0xC8, - 0x40, - 0x14, - "GD25Q80C", - 1048576, - 256, - SPIMemChipVendorGIGADEVICE, - SPIMemChipWriteModePage}, - {0xC8, 0x40, 0x15, "GD25Q16", 2097152, 256, SPIMemChipVendorGIGADEVICE, SPIMemChipWriteModePage}, - {0xC8, - 0x40, - 0x15, - "GD25Q16B", - 2097152, - 256, - SPIMemChipVendorGIGADEVICE, - SPIMemChipWriteModePage}, - {0xC8, 0x40, 0x16, "GD25Q32", 4194304, 256, SPIMemChipVendorGIGADEVICE, SPIMemChipWriteModePage}, - {0xC8, - 0x40, - 0x16, - "GD25Q32B", - 4194304, - 256, - SPIMemChipVendorGIGADEVICE, - SPIMemChipWriteModePage}, - {0xC8, 0x40, 0x17, "GD25Q64", 8388608, 256, SPIMemChipVendorGIGADEVICE, SPIMemChipWriteModePage}, - {0xC8, - 0x40, - 0x17, - "GD25Q64B", - 8388608, - 256, - SPIMemChipVendorGIGADEVICE, - SPIMemChipWriteModePage}, - {0xC8, - 0x40, - 0x17, - "GD25B64C", - 8388608, - 256, - SPIMemChipVendorGIGADEVICE, - SPIMemChipWriteModePage}, - {0xC8, - 0x40, - 0x18, - "GD25Q128B", - 16777216, - 256, - SPIMemChipVendorGIGADEVICE, - SPIMemChipWriteModePage}, - {0xC8, - 0x40, - 0x18, - "GD25Q128C", - 16777216, - 256, - SPIMemChipVendorGIGADEVICE, - SPIMemChipWriteModePage}, - {0xC8, - 0x60, - 0x17, - "GD25LQ064C_1.8V", - 8388608, - 256, - SPIMemChipVendorGIGADEVICE, - SPIMemChipWriteModePage}, - {0xC8, - 0x60, - 0x18, - "GD25LQ128C_1.8V", - 16777216, - 256, - SPIMemChipVendorGIGADEVICE, - SPIMemChipWriteModePage}, - {0xC8, - 0x60, - 0x19, - "GD25LQ256C_1.8V", - 33554432, - 256, - SPIMemChipVendorGIGADEVICE, - SPIMemChipWriteModePage}, - {0xC8, 0x31, 0x14, "MD25T80", 1048576, 256, SPIMemChipVendorGIGADEVICE, SPIMemChipWriteModePage}, - {0x51, 0x40, 0x12, "MD25D20", 262144, 256, SPIMemChipVendorGIGADEVICE, SPIMemChipWriteModePage}, - {0x51, 0x40, 0x13, "MD25D40", 524288, 256, SPIMemChipVendorGIGADEVICE, SPIMemChipWriteModePage}, - {0x51, 0x40, 0x14, "MD25D80", 1048576, 256, SPIMemChipVendorGIGADEVICE, SPIMemChipWriteModePage}, - {0x51, 0x40, 0x15, "MD25D16", 2097152, 256, SPIMemChipVendorGIGADEVICE, SPIMemChipWriteModePage}, - {0x1C, 0x20, 0x10, "ICE25P05", 65536, 128, SPIMemChipVendorICE, SPIMemChipWriteModePage}, - {0x89, 0x89, 0x11, "QB25F016S33B", 2097152, 256, SPIMemChipVendorINTEL, SPIMemChipWriteModePage}, - {0x89, 0x89, 0x11, "QB25F160S33B", 2097152, 256, SPIMemChipVendorINTEL, SPIMemChipWriteModePage}, - {0x89, 0x89, 0x12, "QB25F320S33B", 4194304, 256, SPIMemChipVendorINTEL, SPIMemChipWriteModePage}, - {0x89, 0x89, 0x13, "QB25F640S33B", 8388608, 256, SPIMemChipVendorINTEL, SPIMemChipWriteModePage}, - {0x89, 0x89, 0x11, "QH25F016S33B", 2097152, 256, SPIMemChipVendorINTEL, SPIMemChipWriteModePage}, - {0x89, 0x89, 0x11, "QH25F160S33B", 2097152, 256, SPIMemChipVendorINTEL, SPIMemChipWriteModePage}, - {0x89, 0x89, 0x12, "QH25F320S33B", 4194304, 256, SPIMemChipVendorINTEL, SPIMemChipWriteModePage}, - {0xC2, 0x20, 0x11, "KH25L1005", 131072, 256, SPIMemChipVendorKHIC, SPIMemChipWriteModePage}, - {0xC2, 0x20, 0x11, "KH25L1005A", 131072, 256, SPIMemChipVendorKHIC, SPIMemChipWriteModePage}, - {0xC2, 0x20, 0x12, "KH25L2005", 262144, 256, SPIMemChipVendorKHIC, SPIMemChipWriteModePage}, - {0xC2, 0x20, 0x13, "KH25L4005", 524288, 256, SPIMemChipVendorKHIC, SPIMemChipWriteModePage}, - {0xC2, 0x20, 0x13, "KH25L4005A", 524288, 256, SPIMemChipVendorKHIC, SPIMemChipWriteModePage}, - {0xC2, 0x20, 0x10, "KH25L512", 65536, 256, SPIMemChipVendorKHIC, SPIMemChipWriteModePage}, - {0xC2, 0x20, 0x10, "KH25L512A", 65536, 256, SPIMemChipVendorKHIC, SPIMemChipWriteModePage}, - {0xC2, 0x20, 0x14, "KH25L8005", 1048576, 256, SPIMemChipVendorKHIC, SPIMemChipWriteModePage}, - {0xC2, 0x26, 0x15, "KH25L8036D", 1048576, 256, SPIMemChipVendorKHIC, SPIMemChipWriteModePage}, - {0xC2, 0x20, 0x11, "MX25L1005", 131072, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, - {0xC2, 0x20, 0x11, "MX25L1005A", 131072, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, - {0xC2, 0x20, 0x11, "MX25L1005C", 131072, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, - {0xC2, 0x20, 0x11, "MX25L1006E", 131072, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, - {0xC2, 0x22, 0x11, "MX25L1021E", 131072, 32, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, - {0xC2, 0x20, 0x11, "MX25L1025C", 131072, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, - {0xC2, 0x20, 0x11, "MX25L1026E", 131072, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, - {0xC2, - 0x20, - 0x18, - "MX25L12805D", - 16777216, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x20, - 0x18, - "MX25L12835E", - 16777216, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x20, - 0x18, - "MX25L12835F", - 16777216, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x20, - 0x18, - "MX25L12836E", - 16777216, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x20, - 0x18, - "MX25L12839F", - 16777216, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x20, - 0x18, - "MX25L12845E", - 16777216, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x20, - 0x18, - "MX25L12845G", - 16777216, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x20, - 0x18, - "MX25L12845F", - 16777216, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x20, - 0x18, - "MX25L12865E", - 16777216, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x20, - 0x18, - "MX25L12865F", - 16777216, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x20, - 0x18, - "MX25L12873F", - 16777216, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x20, - 0x18, - "MX25L12875F", - 16777216, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x20, - 0x19, - "MX25L25635E", - 33554432, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, 0x20, 0x15, "MX25L1605", 2097152, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, - {0xC2, - 0x20, - 0x15, - "MX25L1605A", - 2097152, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x20, - 0x15, - "MX25L1605D", - 2097152, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x20, - 0x15, - "MX25L1606E", - 2097152, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x24, - 0x15, - "MX25L1633E", - 2097152, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x24, - 0x15, - "MX25L1635D", - 2097152, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x25, - 0x15, - "MX25L1635E", - 2097152, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x24, - 0x15, - "MX25L1636D", - 2097152, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x25, - 0x15, - "MX25L1636E", - 2097152, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x24, - 0x15, - "MX25L1673E", - 2097152, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x24, - 0x15, - "MX25L1675E", - 2097152, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, 0x20, 0x12, "MX25L2005", 262144, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, - {0xC2, 0x20, 0x12, "MX25L2005C", 262144, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, - {0xC2, 0x20, 0x12, "MX25L2006E", 262144, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, - {0xC2, 0x20, 0x12, "MX25L2026C", 262144, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, - {0xC2, 0x20, 0x12, "MX25L2026E", 262144, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, - {0xC2, 0x20, 0x16, "MX25L3205", 4194304, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, - {0xC2, - 0x20, - 0x16, - "MX25L3205A", - 4194304, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x20, - 0x16, - "MX25L3205D", - 4194304, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x20, - 0x16, - "MX25L3206E", - 4194304, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x20, - 0x16, - "MX25L3208E", - 4194304, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x5E, - 0x16, - "MX25L3225D", - 4194304, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x20, - 0x16, - "MX25L3233F", - 4194304, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x5E, - 0x16, - "MX25L3235D", - 4194304, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x20, - 0x16, - "MX25L3235E", - 4194304, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x5E, - 0x16, - "MX25L3236D", - 4194304, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x5E, - 0x16, - "MX25L3237D", - 4194304, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x25, - 0x36, - "MX25L3239E", - 4194304, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x20, - 0x16, - "MX25L3273E", - 4194304, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x20, - 0x16, - "MX25L3273F", - 4194304, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x20, - 0x16, - "MX25L3275E", - 4194304, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, 0x20, 0x13, "MX25L4005", 524288, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, - {0xC2, 0x20, 0x13, "MX25L4005A", 524288, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, - {0xC2, 0x20, 0x13, "MX25L4005C", 524288, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, - {0xC2, 0x20, 0x13, "MX25L4006E", 524288, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, - {0xC2, 0x20, 0x13, "MX25L4026E", 524288, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, - {0xC2, 0x20, 0x10, "MX25L512", 65536, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, - {0xC2, 0x20, 0x10, "MX25L512A", 65536, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, - {0xC2, 0x20, 0x10, "MX25L512C", 65536, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, - {0xC2, 0x22, 0x10, "MX25L5121E", 65536, 32, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, - {0xC2, 0x20, 0x17, "MX25L6405", 8388608, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, - {0xC2, - 0x20, - 0x17, - "MX25L6405D", - 8388608, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x20, - 0x17, - "MX25L6406E", - 8388608, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x20, - 0x17, - "MX25L6408E", - 8388608, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x20, - 0x17, - "MX25L6433F", - 8388608, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x20, - 0x17, - "MX25L6435E", - 8388608, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x20, - 0x17, - "MX25L6436E", - 8388608, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x20, - 0x17, - "MX25L6436F", - 8388608, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x25, - 0x37, - "MX25L6439E", - 8388608, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x20, - 0x17, - "MX25L6445E", - 8388608, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x20, - 0x17, - "MX25L6465E", - 8388608, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x20, - 0x17, - "MX25L6473E", - 8388608, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x20, - 0x17, - "MX25L6473F", - 8388608, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x20, - 0x17, - "MX25L6475E", - 8388608, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, 0x20, 0x14, "MX25L8005", 1048576, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, - {0xC2, - 0x20, - 0x14, - "MX25L8006E", - 1048576, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x20, - 0x14, - "MX25L8008E", - 1048576, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x20, - 0x14, - "MX25L8035E", - 1048576, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x20, - 0x14, - "MX25L8036E", - 1048576, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x20, - 0x14, - "MX25L8073E", - 1048576, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x20, - 0x14, - "MX25L8075E", - 1048576, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x20, - 0x19, - "MX25L25673G", - 33554432, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, 0x28, 0x10, "MX25R512F", 65536, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, - {0xC2, 0x28, 0x11, "MX25R1035F", 131072, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, - {0xC2, - 0x28, - 0x15, - "MX25R1635F", - 2097152, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, 0x28, 0x12, "MX25R2035F", 262144, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, - {0xC2, - 0x28, - 0x16, - "MX25R3235F", - 4194304, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, 0x28, 0x13, "MX25R4035F", 524288, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, - {0xC2, - 0x28, - 0x17, - "MX25R6435F", - 8388608, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x28, - 0x14, - "MX25R8035F", - 1048576, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x25, - 0x31, - "MX25U1001E_1.8V", - 131072, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x25, - 0x18, - "MX25U12835F_1.8V", - 16777216, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x25, - 0x39, - "MX25U25673G_1.8V", - 33554432, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x25, - 0x39, - "MX25U25645G_1.8V", - 33554432, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x25, - 0x35, - "MX25U1635E_1.8V", - 2097152, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x25, - 0x35, - "MX25U1635F_1.8V", - 2097152, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x25, - 0x32, - "MX25U2032E_1.8V", - 262144, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x25, - 0x32, - "MX25U2033E_1.8V", - 262144, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x25, - 0x36, - "MX25U3235E_1.8V", - 4194304, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x25, - 0x36, - "MX25U3235F_1.8V", - 4194304, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x25, - 0x33, - "MX25U4032E_1.8V", - 524288, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x25, - 0x33, - "MX25U4033E_1.8V", - 524288, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x25, - 0x33, - "MX25U4035_1.8V", - 524288, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x25, - 0x30, - "MX25U5121E_1.8V", - 65536, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x25, - 0x37, - "MX25U6435F_1.8V", - 8388608, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x25, - 0x37, - "MX25U6473F_1.8V", - 8388608, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x25, - 0x34, - "MX25U8032E_1.8V", - 1048576, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x25, - 0x34, - "MX25U8033E_1.8V", - 1048576, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x25, - 0x34, - "MX25U8035_1.8V", - 1048576, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x25, - 0x34, - "MX25U8035E_1.8V", - 1048576, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x25, - 0x38, - "MX25U12873F_1.8V", - 16777216, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, 0x20, 0x11, "MX25V1006E", 131072, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, - {0xC2, 0x23, 0x11, "MX25V1035F", 131072, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, - {0xC2, 0x20, 0x12, "MX25V2006E", 262144, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, - {0xC2, 0x23, 0x12, "MX25V2035F", 262144, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, - {0xC2, 0x20, 0x10, "MX25V512", 65536, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, - {0xC2, 0x20, 0x10, "MX25V512C", 65536, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, - {0xC2, 0x20, 0x10, "MX25V512E", 65536, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, - {0xC2, 0x23, 0x10, "MX25V512F", 65536, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, - {0xC2, 0x20, 0x13, "MX25V4005", 524288, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, - {0xC2, 0x20, 0x13, "MX25V4006E", 524288, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, - {0xC2, 0x25, 0x53, "MX25V4035", 524288, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, - {0xC2, 0x23, 0x13, "MX25V4035F", 524288, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, - {0xC2, 0x20, 0x14, "MX25V8005", 1048576, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, - {0xC2, - 0x20, - 0x14, - "MX25V8006E", - 1048576, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, 0x25, 0x54, "MX25V8035", 1048576, 256, SPIMemChipVendorMACRONIX, SPIMemChipWriteModePage}, - {0xC2, - 0x23, - 0x14, - "MX25V8035F", - 1048576, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x25, - 0x3A, - "MX66U51235F_1.8V", - 67108864, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0xC2, - 0x25, - 0x3B, - "MX66U1G45G_1.8V", - 134217728, - 256, - SPIMemChipVendorMACRONIX, - SPIMemChipWriteModePage}, - {0x20, 0xBA, 0x16, "N25Q032A", 4194304, 256, SPIMemChipVendorMICRON, SPIMemChipWriteModePage}, - {0x20, 0xBA, 0x17, "N25Q064A", 8388608, 256, SPIMemChipVendorMICRON, SPIMemChipWriteModePage}, - {0x20, 0xBA, 0x19, "N25Q256A13", 33554432, 256, SPIMemChipVendorMICRON, SPIMemChipWriteModePage}, - {0x20, 0xBA, 0x20, "N25Q512A83", 67108864, 256, SPIMemChipVendorMICRON, SPIMemChipWriteModePage}, - {0x2C, 0xCB, 0x19, "N25W256A11", 33554432, 256, SPIMemChipVendorMICRON, SPIMemChipWriteModePage}, - {0x20, - 0xBA, - 0x18, - "MT25QL128AB", - 16777216, - 256, - SPIMemChipVendorMICRON, - SPIMemChipWriteModePage}, - {0x20, 0xBA, 0x19, "MT25QL256A", 33554432, 256, SPIMemChipVendorMICRON, SPIMemChipWriteModePage}, - {0x20, 0xBA, 0x20, "MT25QL512A", 67108864, 256, SPIMemChipVendorMICRON, SPIMemChipWriteModePage}, - {0x20, - 0xBA, - 0x22, - "MT25QL02GC", - 268435456, - 256, - SPIMemChipVendorMICRON, - SPIMemChipWriteModePage}, - {0x20, 0xBB, 0x19, "MT25QU256", 33554432, 256, SPIMemChipVendorMICRON, SPIMemChipWriteModePage}, - {0x20, - 0xBA, - 0x21, - "N25Q00AA13G", - 134217728, - 256, - SPIMemChipVendorMICRON, - SPIMemChipWriteModePage}, - {0x37, 0x30, 0x10, "MS25X512", 65536, 256, SPIMemChipVendorMSHINE, SPIMemChipWriteModePage}, - {0x37, 0x30, 0x11, "MS25X10", 131072, 256, SPIMemChipVendorMSHINE, SPIMemChipWriteModePage}, - {0x37, 0x30, 0x12, "MS25X20", 262144, 256, SPIMemChipVendorMSHINE, SPIMemChipWriteModePage}, - {0x37, 0x30, 0x13, "MS25X40", 524288, 256, SPIMemChipVendorMSHINE, SPIMemChipWriteModePage}, - {0x37, 0x30, 0x14, "MS25X80", 1048576, 256, SPIMemChipVendorMSHINE, SPIMemChipWriteModePage}, - {0x37, 0x30, 0x15, "MS25X16", 2097152, 256, SPIMemChipVendorMSHINE, SPIMemChipWriteModePage}, - {0x37, 0x30, 0x16, "MS25X32", 4194304, 256, SPIMemChipVendorMSHINE, SPIMemChipWriteModePage}, - {0xD5, 0x30, 0x11, "N25S10", 131072, 256, SPIMemChipVendorNANTRONICS, SPIMemChipWriteModePage}, - {0xD5, 0x30, 0x12, "N25S20", 262144, 256, SPIMemChipVendorNANTRONICS, SPIMemChipWriteModePage}, - {0xD5, 0x30, 0x13, "N25S40", 524288, 256, SPIMemChipVendorNANTRONICS, SPIMemChipWriteModePage}, - {0xD5, 0x30, 0x15, "N25S16", 2097152, 256, SPIMemChipVendorNANTRONICS, SPIMemChipWriteModePage}, - {0xD5, 0x30, 0x16, "N25S32", 4194304, 256, SPIMemChipVendorNANTRONICS, SPIMemChipWriteModePage}, - {0xD5, 0x30, 0x14, "N25S80", 1048576, 256, SPIMemChipVendorNANTRONICS, SPIMemChipWriteModePage}, - {0x9D, 0x7F, 0x7C, "NX25P10", 131072, 256, SPIMemChipVendorNEXFLASH, SPIMemChipWriteModePage}, - {0xEF, 0x20, 0x15, "NX25P16", 2097152, 256, SPIMemChipVendorNEXFLASH, SPIMemChipWriteModePage}, - {0x9D, 0x7F, 0x7D, "NX25P20", 262144, 256, SPIMemChipVendorNEXFLASH, SPIMemChipWriteModePage}, - {0xEF, 0x20, 0x16, "NX25P32", 4194304, 256, SPIMemChipVendorNEXFLASH, SPIMemChipWriteModePage}, - {0x9D, 0x7F, 0x7E, "NX25P40", 524288, 256, SPIMemChipVendorNEXFLASH, SPIMemChipWriteModePage}, - {0x9D, 0x7F, 0x13, "NX25P80", 1048576, 256, SPIMemChipVendorNEXFLASH, SPIMemChipWriteModePage}, - {0x20, 0x40, 0x15, "M45PE16", 2097152, 256, SPIMemChipVendorNUMONYX, SPIMemChipWriteModePage}, - {0x20, 0x20, 0x10, "M25P05", 65536, 128, SPIMemChipVendorNUMONYX, SPIMemChipWriteModePage}, - {0x20, 0x20, 0x10, "M25P05A", 65536, 256, SPIMemChipVendorNUMONYX, SPIMemChipWriteModePage}, - {0x20, 0x20, 0x11, "M25P10", 131072, 128, SPIMemChipVendorNUMONYX, SPIMemChipWriteModePage}, - {0x20, 0x20, 0x11, "M25P10A", 131072, 256, SPIMemChipVendorNUMONYX, SPIMemChipWriteModePage}, - {0x20, 0x20, 0x12, "M25P20", 262144, 256, SPIMemChipVendorNUMONYX, SPIMemChipWriteModePage}, - {0x20, 0x20, 0x13, "M25P40", 524288, 256, SPIMemChipVendorNUMONYX, SPIMemChipWriteModePage}, - {0x20, 0x20, 0x14, "M25P80", 1048576, 256, SPIMemChipVendorNUMONYX, SPIMemChipWriteModePage}, - {0x20, 0x20, 0x15, "M25P16", 2097152, 256, SPIMemChipVendorNUMONYX, SPIMemChipWriteModePage}, - {0x20, 0x20, 0x16, "M25P32", 4194304, 256, SPIMemChipVendorNUMONYX, SPIMemChipWriteModePage}, - {0x20, 0x20, 0x17, "M25P64", 8388608, 256, SPIMemChipVendorNUMONYX, SPIMemChipWriteModePage}, - {0x20, - 0x20, - 0x18, - "M25P128_ST25P28V6G", - 16777216, - 256, - SPIMemChipVendorNUMONYX, - SPIMemChipWriteModePage}, - {0x20, 0x80, 0x11, "M25PE10", 131072, 256, SPIMemChipVendorNUMONYX, SPIMemChipWriteModePage}, - {0x20, 0x80, 0x15, "M25PE16", 2097152, 256, SPIMemChipVendorNUMONYX, SPIMemChipWriteModePage}, - {0x20, 0x80, 0x12, "M25PE20", 262144, 256, SPIMemChipVendorNUMONYX, SPIMemChipWriteModePage}, - {0x20, 0x80, 0x13, "M25PE40", 524288, 256, SPIMemChipVendorNUMONYX, SPIMemChipWriteModePage}, - {0x20, 0x80, 0x14, "M25PE80", 1048576, 256, SPIMemChipVendorNUMONYX, SPIMemChipWriteModePage}, - {0xBF, 0x43, 0x00, "PCT25LF020A", 262144, 256, SPIMemChipVendorPCT, SPIMemChipWriteModePage}, - {0xBF, 0x49, 0x00, "PCT25VF010A", 131072, 256, SPIMemChipVendorPCT, SPIMemChipWriteModePage}, - {0xBF, 0x25, 0x41, "PCT25VF016B", 2097152, 256, SPIMemChipVendorPCT, SPIMemChipWriteModePage}, - {0xBF, 0x43, 0x00, "PCT25VF020A", 262144, 256, SPIMemChipVendorPCT, SPIMemChipWriteModePage}, - {0xBF, 0x25, 0x4A, "PCT25VF032B", 4194304, 256, SPIMemChipVendorPCT, SPIMemChipWriteModePage}, - {0xBF, 0x44, 0x00, "PCT25VF040A", 524288, 256, SPIMemChipVendorPCT, SPIMemChipWriteModePage}, - {0xBF, 0x25, 0x8D, "PCT25VF040B", 524288, 256, SPIMemChipVendorPCT, SPIMemChipWriteModePage}, - {0xBF, 0x25, 0x8E, "PCT25VF080B", 1048576, 256, SPIMemChipVendorPCT, SPIMemChipWriteModePage}, - {0x01, 0x02, 0x10, "S25FL001D", 131072, 256, SPIMemChipVendorSPANSION, SPIMemChipWriteModePage}, - {0x01, 0x02, 0x11, "S25FL002D", 262144, 256, SPIMemChipVendorSPANSION, SPIMemChipWriteModePage}, - {0x01, 0x02, 0x12, "S25FL004A", 524288, 256, SPIMemChipVendorSPANSION, SPIMemChipWriteModePage}, - {0x01, 0x02, 0x12, "S25FL004D", 524288, 256, SPIMemChipVendorSPANSION, SPIMemChipWriteModePage}, - {0xEF, 0x40, 0x13, "S25FL004K", 524288, 256, SPIMemChipVendorSPANSION, SPIMemChipWriteModePage}, - {0x01, 0x02, 0x13, "S25FL008A", 1048576, 256, SPIMemChipVendorSPANSION, SPIMemChipWriteModePage}, - {0x01, 0x02, 0x13, "S25FL008D", 1048576, 256, SPIMemChipVendorSPANSION, SPIMemChipWriteModePage}, - {0xEF, 0x40, 0x14, "S25FL008K", 1048576, 256, SPIMemChipVendorSPANSION, SPIMemChipWriteModePage}, - {0x01, 0x02, 0x14, "S25FL016A", 2097152, 256, SPIMemChipVendorSPANSION, SPIMemChipWriteModePage}, - {0xEF, 0x40, 0x15, "S25FL016K", 2097152, 256, SPIMemChipVendorSPANSION, SPIMemChipWriteModePage}, - {0x01, 0x02, 0x15, "S25FL032A", 4194304, 256, SPIMemChipVendorSPANSION, SPIMemChipWriteModePage}, - {0xEF, 0x40, 0x16, "S25FL032K", 4194304, 256, SPIMemChipVendorSPANSION, SPIMemChipWriteModePage}, - {0x01, 0x02, 0x15, "S25FL032P", 4194304, 256, SPIMemChipVendorSPANSION, SPIMemChipWriteModePage}, - {0x01, 0x02, 0x12, "S25FL040A", 524288, 256, SPIMemChipVendorSPANSION, SPIMemChipWriteModePage}, - {0x01, - 0x02, - 0x26, - "S25FL040A_BOT", - 524288, - 256, - SPIMemChipVendorSPANSION, - SPIMemChipWriteModePage}, - {0x01, - 0x02, - 0x25, - "S25FL040A_TOP", - 524288, - 256, - SPIMemChipVendorSPANSION, - SPIMemChipWriteModePage}, - {0x01, 0x02, 0x16, "S25FL064A", 8388608, 256, SPIMemChipVendorSPANSION, SPIMemChipWriteModePage}, - {0xEF, 0x40, 0x17, "S25FL064K", 8388608, 256, SPIMemChipVendorSPANSION, SPIMemChipWriteModePage}, - {0x01, 0x02, 0x16, "S25FL064P", 8388608, 256, SPIMemChipVendorSPANSION, SPIMemChipWriteModePage}, - {0x01, 0x40, 0x15, "S25FL116K", 2097152, 256, SPIMemChipVendorSPANSION, SPIMemChipWriteModePage}, - {0xEF, - 0x40, - 0x18, - "S25FL128K", - 16777216, - 256, - SPIMemChipVendorSPANSION, - SPIMemChipWriteModePage}, - {0x01, - 0x20, - 0x18, - "S25FL128P", - 16777216, - 256, - SPIMemChipVendorSPANSION, - SPIMemChipWriteModePage}, - {0x01, - 0x20, - 0x18, - "S25FL128S", - 16777216, - 256, - SPIMemChipVendorSPANSION, - SPIMemChipWriteModePage}, - {0x01, 0x40, 0x16, "S25FL132K", 4194304, 256, SPIMemChipVendorSPANSION, SPIMemChipWriteModePage}, - {0x01, 0x40, 0x17, "S25FL164K", 8388608, 256, SPIMemChipVendorSPANSION, SPIMemChipWriteModePage}, - {0x01, - 0x02, - 0x19, - "S25FL256S", - 33554432, - 256, - SPIMemChipVendorSPANSION, - SPIMemChipWriteModePage}, - {0xBF, 0x25, 0x41, "SST25VF016B", 2097152, 1, SPIMemChipVendorSST, SPIMemChipWriteModeAAIWord}, - {0xBF, 0x25, 0x8C, "SST25VF020B", 262144, 1, SPIMemChipVendorSST, SPIMemChipWriteModeAAIWord}, - {0xBF, 0x25, 0x4A, "SST25VF032B", 4194304, 1, SPIMemChipVendorSST, SPIMemChipWriteModeAAIWord}, - {0xBF, 0x25, 0x4B, "SST25VF064C", 8388608, 256, SPIMemChipVendorSST, SPIMemChipWriteModePage}, - {0xBF, 0x25, 0x8D, "SST25VF040B", 524288, 1, SPIMemChipVendorSST, SPIMemChipWriteModeAAIWord}, - {0xBF, 0x25, 0x8E, "SST25VF080B", 1048576, 1, SPIMemChipVendorSST, SPIMemChipWriteModeAAIWord}, - {0x20, 0x71, 0x15, "M25PX16", 2097152, 256, SPIMemChipVendorST, SPIMemChipWriteModePage}, - {0x20, 0x71, 0x16, "M25PX32", 4194304, 256, SPIMemChipVendorST, SPIMemChipWriteModePage}, - {0x20, 0x71, 0x17, "M25PX64", 8388608, 256, SPIMemChipVendorST, SPIMemChipWriteModePage}, - {0x20, 0x71, 0x14, "M25PX80", 1048576, 256, SPIMemChipVendorST, SPIMemChipWriteModePage}, - {0x20, 0x20, 0x10, "ST25P05", 65536, 128, SPIMemChipVendorST, SPIMemChipWriteModePage}, - {0x20, 0x20, 0x10, "ST25P05A", 65536, 256, SPIMemChipVendorST, SPIMemChipWriteModePage}, - {0x20, 0x20, 0x11, "ST25P10", 131072, 128, SPIMemChipVendorST, SPIMemChipWriteModePage}, - {0x20, 0x20, 0x11, "ST25P10A", 131072, 256, SPIMemChipVendorST, SPIMemChipWriteModePage}, - {0x20, 0x20, 0x15, "ST25P16", 2097152, 256, SPIMemChipVendorST, SPIMemChipWriteModePage}, - {0x20, 0x20, 0x12, "ST25P20", 262144, 256, SPIMemChipVendorST, SPIMemChipWriteModePage}, - {0x20, 0x20, 0x16, "ST25P32", 4194304, 256, SPIMemChipVendorST, SPIMemChipWriteModePage}, - {0x20, 0x20, 0x13, "ST25P40", 524288, 256, SPIMemChipVendorST, SPIMemChipWriteModePage}, - {0x20, 0x20, 0x17, "ST25P64", 8388608, 256, SPIMemChipVendorST, SPIMemChipWriteModePage}, - {0x20, 0x20, 0x14, "ST25P80", 1048576, 256, SPIMemChipVendorST, SPIMemChipWriteModePage}, - {0xEF, 0x10, 0x00, "W25P10", 131072, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, 0x20, 0x15, "W25P16", 2097152, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, 0x11, 0x00, "W25P20", 262144, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, 0x20, 0x16, "W25P32", 4194304, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, 0x12, 0x00, "W25P40", 524288, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, 0x20, 0x17, "W25P64", 8388608, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, 0x20, 0x14, "W25P80", 1048576, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, - 0x60, - 0x11, - "W25Q10EW_1.8V", - 131072, - 256, - SPIMemChipVendorWINBOND, - SPIMemChipWriteModePage}, - {0xEF, 0x40, 0x18, "W25Q128BV", 16777216, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, 0x40, 0x18, "W25Q128FV", 16777216, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, 0x70, 0x18, "W25Q128JV", 16777216, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, 0x40, 0x19, "W25Q256FV", 33554432, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, 0x40, 0x19, "W25Q256JV", 33554432, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, 0x70, 0x19, "W25Q256JV", 33554432, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, - 0x60, - 0x18, - "W25Q128FW_1.8V", - 16777216, - 256, - SPIMemChipVendorWINBOND, - SPIMemChipWriteModePage}, - {0xEF, 0x40, 0x15, "W25Q16", 2097152, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, 0x40, 0x15, "W25Q16BV", 2097152, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, 0x40, 0x15, "W25Q16CL", 2097152, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, 0x40, 0x15, "W25Q16CV", 2097152, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, 0x40, 0x15, "W25Q16DV", 2097152, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, - 0x60, - 0x15, - "W25Q16FW_1.8V", - 2097152, - 256, - SPIMemChipVendorWINBOND, - SPIMemChipWriteModePage}, - {0xEF, 0x40, 0x15, "W25Q16V", 2097152, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, 0x40, 0x12, "W25Q20CL", 262144, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, - 0x60, - 0x12, - "W25Q20EW_1.8V", - 262144, - 256, - SPIMemChipVendorWINBOND, - SPIMemChipWriteModePage}, - {0xEF, 0x40, 0x16, "W25Q32", 4194304, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, 0x40, 0x16, "W25Q32BV", 4194304, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, 0x40, 0x16, "W25Q32FV", 4194304, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, - 0x60, - 0x16, - "W25Q32FW_1.8V", - 4194304, - 256, - SPIMemChipVendorWINBOND, - SPIMemChipWriteModePage}, - {0xEF, 0x40, 0x16, "W25Q32V", 4194304, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, 0x40, 0x13, "W25Q40BL", 524288, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, 0x40, 0x13, "W25Q40BV", 524288, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, 0x40, 0x13, "W25Q40CL", 524288, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, - 0x60, - 0x13, - "W25Q40EW_1.8V", - 524288, - 256, - SPIMemChipVendorWINBOND, - SPIMemChipWriteModePage}, - {0xEF, 0x40, 0x17, "W25Q64BV", 8388608, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, 0x40, 0x17, "W25Q64CV", 8388608, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, 0x40, 0x17, "W25Q64FV", 8388608, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, 0x40, 0x17, "W25Q64JV", 8388608, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, - 0x60, - 0x17, - "W25Q64FW_1.8V", - 8388608, - 256, - SPIMemChipVendorWINBOND, - SPIMemChipWriteModePage}, - {0xEF, 0x40, 0x14, "W25Q80BL", 1048576, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, 0x40, 0x14, "W25Q80BV", 1048576, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, - 0x50, - 0x14, - "W25Q80BW_1.8V", - 1048576, - 256, - SPIMemChipVendorWINBOND, - SPIMemChipWriteModePage}, - {0xEF, 0x40, 0x14, "W25Q80DV", 1048576, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, - 0x60, - 0x14, - "W25Q80EW_1.8V", - 1048576, - 256, - SPIMemChipVendorWINBOND, - SPIMemChipWriteModePage}, - {0xEF, 0x30, 0x10, "W25X05", 65536, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, 0x30, 0x10, "W25X05CL", 65536, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, 0x30, 0x11, "W25X10AV", 131072, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, 0x30, 0x11, "W25X10BL", 131072, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, 0x30, 0x11, "W25X10BV", 131072, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, 0x30, 0x11, "W25X10CL", 131072, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, 0x30, 0x11, "W25X10L", 131072, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, 0x30, 0x11, "W25X10V", 131072, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, 0x30, 0x15, "W25X16", 2097152, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, 0x30, 0x15, "W25X16AL", 2097152, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, 0x30, 0x15, "W25X16AV", 2097152, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, 0x30, 0x15, "W25X16BV", 2097152, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, 0x30, 0x15, "W25X16V", 2097152, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, 0x30, 0x12, "W25X20AL", 262144, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, 0x30, 0x12, "W25X20AV", 262144, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, 0x30, 0x12, "W25X20BL", 262144, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, 0x30, 0x12, "W25X20BV", 262144, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, 0x30, 0x12, "W25X20CL", 262144, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, 0x30, 0x12, "W25X20L", 262144, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, 0x30, 0x12, "W25X20V", 262144, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, 0x30, 0x16, "W25X32", 4194304, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, 0x30, 0x16, "W25X32AV", 4194304, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, 0x30, 0x16, "W25X32BV", 4194304, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, 0x30, 0x16, "W25X32V", 4194304, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, 0x30, 0x13, "W25X40AL", 524288, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, 0x30, 0x13, "W25X40AV", 524288, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, 0x30, 0x13, "W25X40BL", 524288, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, 0x30, 0x13, "W25X40BV", 524288, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, 0x30, 0x13, "W25X40CL", 524288, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, 0x30, 0x13, "W25X40L", 524288, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, 0x30, 0x13, "W25X40V", 524288, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, 0x30, 0x17, "W25X64", 8388608, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, 0x30, 0x17, "W25X64BV", 8388608, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, 0x30, 0x17, "W25X64V", 8388608, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, 0x30, 0x14, "W25X80AL", 1048576, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, 0x30, 0x14, "W25X80AV", 1048576, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, 0x30, 0x14, "W25X80BV", 1048576, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, 0x30, 0x14, "W25X80L", 1048576, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, 0x30, 0x14, "W25X80V", 1048576, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, 0x71, 0x19, "W25M512JV", 67108864, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0xEF, 0x40, 0x19, "W25R256JV", 33554432, 256, SPIMemChipVendorWINBOND, SPIMemChipWriteModePage}, - {0x37, 0x20, 0x10, "TS25L512A", 65536, 256, SPIMemChipVendorZEMPRO, SPIMemChipWriteModePage}, - {0x37, 0x30, 0x11, "TS25L010A", 131072, 256, SPIMemChipVendorZEMPRO, SPIMemChipWriteModePage}, - {0x37, 0x30, 0x12, "TS25L020A", 262144, 256, SPIMemChipVendorZEMPRO, SPIMemChipWriteModePage}, - {0x20, 0x20, 0x15, "TS25L16AP", 2097152, 256, SPIMemChipVendorZEMPRO, SPIMemChipWriteModePage}, - {0x20, 0x20, 0x15, "TS25L16BP", 2097152, 256, SPIMemChipVendorZEMPRO, SPIMemChipWriteModePage}, - {0x37, 0x20, 0x15, "TS25L16P", 2097152, 256, SPIMemChipVendorZEMPRO, SPIMemChipWriteModePage}, - {0x5E, 0x40, 0x15, "ZB25D16", 2097152, 256, SPIMemChipVendorZbit, SPIMemChipWriteModePage}, - {0xE0, 0x40, 0x13, "BG25Q40A", 524288, 256, SPIMemChipVendorBerg_Micro, SPIMemChipWriteModePage}, - {0xE0, - 0x40, - 0x14, - "BG25Q80A", - 1048576, - 256, - SPIMemChipVendorBerg_Micro, - SPIMemChipWriteModePage}, - {0xE0, - 0x40, - 0x15, - "BG25Q16A", - 2097152, - 256, - SPIMemChipVendorBerg_Micro, - SPIMemChipWriteModePage}, - {0xE0, - 0x40, - 0x16, - "BG25Q32A", - 4194304, - 256, - SPIMemChipVendorBerg_Micro, - SPIMemChipWriteModePage}, - {0x1F, 0x23, 0x00, "AT45DB021D", 270336, 264, SPIMemChipVendorATMEL, SPIMemChipWriteModePage}, - {0x1F, 0x24, 0x00, "AT45DB041D", 540672, 264, SPIMemChipVendorATMEL, SPIMemChipWriteModePage}, - {0x1F, 0x26, 0x00, "AT45DB161D", 2162688, 528, SPIMemChipVendorATMEL, SPIMemChipWriteModePage}, - {0x1F, 0x27, 0x01, "AT45DB321D", 4325376, 528, SPIMemChipVendorATMEL, SPIMemChipWriteModePage}, - {0x1F, 0x43, 0x00, "AT25DF021", 262144, 256, SPIMemChipVendorATMEL, SPIMemChipWriteModePage}, - {0x1F, 0x44, 0x00, "AT25DF041", 524288, 256, SPIMemChipVendorATMEL, SPIMemChipWriteModePage}, - {0x1F, 0x44, 0x00, "AT25DF041A", 524288, 256, SPIMemChipVendorATMEL, SPIMemChipWriteModePage}, - {0x1F, 0x84, 0x00, "AT25SF041", 524288, 256, SPIMemChipVendorATMEL, SPIMemChipWriteModePage}, - {0x1F, 0x45, 0x00, "AT25DF081", 1048576, 256, SPIMemChipVendorATMEL, SPIMemChipWriteModePage}, - {0x1F, 0x45, 0x00, "AT25DF081A", 1048576, 256, SPIMemChipVendorATMEL, SPIMemChipWriteModePage}, - {0x1F, 0x46, 0x00, "AT25DF161", 2097152, 256, SPIMemChipVendorATMEL, SPIMemChipWriteModePage}, - {0x1F, 0x47, 0x00, "AT25DF321", 4194304, 256, SPIMemChipVendorATMEL, SPIMemChipWriteModePage}, - {0x1F, 0x47, 0x00, "AT25DF321A", 4194304, 256, SPIMemChipVendorATMEL, SPIMemChipWriteModePage}, - {0x1F, 0x48, 0x00, "AT25DF641", 8388608, 256, SPIMemChipVendorATMEL, SPIMemChipWriteModePage}, - {0x1F, 0x65, 0x00, "AT25F512B", 65536, 256, SPIMemChipVendorATMEL, SPIMemChipWriteModePage}, - {0x1F, 0x45, 0x00, "AT26DF081", 1048576, 256, SPIMemChipVendorATMEL, SPIMemChipWriteModePage}, - {0x1F, 0x45, 0x00, "AT26DF081A", 1048576, 256, SPIMemChipVendorATMEL, SPIMemChipWriteModePage}, - {0x1F, 0x46, 0x00, "AT26DF161", 2097152, 256, SPIMemChipVendorATMEL, SPIMemChipWriteModePage}, - {0x1F, 0x46, 0x00, "AT26DF161A", 2097152, 256, SPIMemChipVendorATMEL, SPIMemChipWriteModePage}, - {0x1F, 0x47, 0x00, "AT26DF321", 4194304, 256, SPIMemChipVendorATMEL, SPIMemChipWriteModePage}, - {0x1F, 0x47, 0x00, "AT26DF321A", 4194304, 256, SPIMemChipVendorATMEL, SPIMemChipWriteModePage}, - {0x1F, 0x04, 0x00, "AT26F004", 524288, 256, SPIMemChipVendorATMEL, SPIMemChipWriteModePage}, - {0xE0, - 0x60, - 0x18, - "ACE25A128G_1.8V", - 16777216, - 256, - SPIMemChipVendorACE, - SPIMemChipWriteModePage}, - {0x9B, 0x32, 0x16, "ATO25Q32", 4194304, 256, SPIMemChipVendorATO, SPIMemChipWriteModePage}, - {0x54, 0x40, 0x17, "DQ25Q64A", 8388608, 256, SPIMemChipVendorDOUQI, SPIMemChipWriteModePage}, - {0x0E, 0x40, 0x15, "FT25H16", 2097152, 256, SPIMemChipVendorFremont, SPIMemChipWriteModePage}, - {0xA1, 0x40, 0x13, "FM25Q04A", 524288, 256, SPIMemChipVendorFudan, SPIMemChipWriteModePage}, - {0xA1, 0x40, 0x16, "FM25Q32", 4194304, 256, SPIMemChipVendorFudan, SPIMemChipWriteModePage}, - {0xE0, 0x40, 0x14, "GT25Q80A", 1048576, 256, SPIMemChipVendorGenitop, SPIMemChipWriteModePage}, - {0xE0, 0x40, 0x13, "PN25F04A", 524288, 256, SPIMemChipVendorParagon, SPIMemChipWriteModePage}}; diff --git a/applications/external/spi_mem_manager/lib/spi/spi_mem_chip_i.h b/applications/external/spi_mem_manager/lib/spi/spi_mem_chip_i.h deleted file mode 100644 index 30d6070945b..00000000000 --- a/applications/external/spi_mem_manager/lib/spi/spi_mem_chip_i.h +++ /dev/null @@ -1,85 +0,0 @@ -#pragma once - -#include -#include "spi_mem_chip.h" - -typedef enum { - SPIMemChipVendorUnknown, - SPIMemChipVendorADESTO, - SPIMemChipVendorAMIC, - SPIMemChipVendorBoya, - SPIMemChipVendorEON, - SPIMemChipVendorPFLASH, - SPIMemChipVendorTERRA, - SPIMemChipVendorGeneralplus, - SPIMemChipVendorDEUTRON, - SPIMemChipVendorEFST, - SPIMemChipVendorEXCELSEMI, - SPIMemChipVendorFIDELIX, - SPIMemChipVendorGIGADEVICE, - SPIMemChipVendorICE, - SPIMemChipVendorINTEL, - SPIMemChipVendorKHIC, - SPIMemChipVendorMACRONIX, - SPIMemChipVendorMICRON, - SPIMemChipVendorMSHINE, - SPIMemChipVendorNANTRONICS, - SPIMemChipVendorNEXFLASH, - SPIMemChipVendorNUMONYX, - SPIMemChipVendorPCT, - SPIMemChipVendorSPANSION, - SPIMemChipVendorSST, - SPIMemChipVendorST, - SPIMemChipVendorWINBOND, - SPIMemChipVendorZEMPRO, - SPIMemChipVendorZbit, - SPIMemChipVendorBerg_Micro, - SPIMemChipVendorATMEL, - SPIMemChipVendorACE, - SPIMemChipVendorATO, - SPIMemChipVendorDOUQI, - SPIMemChipVendorFremont, - SPIMemChipVendorFudan, - SPIMemChipVendorGenitop, - SPIMemChipVendorParagon -} SPIMemChipVendor; - -typedef enum { - SPIMemChipCMDReadJEDECChipID = 0x9F, - SPIMemChipCMDReadData = 0x03, - SPIMemChipCMDChipErase = 0xC7, - SPIMemChipCMDWriteEnable = 0x06, - SPIMemChipCMDWriteDisable = 0x04, - SPIMemChipCMDReadStatus = 0x05, - SPIMemChipCMDWriteData = 0x02, - SPIMemChipCMDReleasePowerDown = 0xAB -} SPIMemChipCMD; - -enum SPIMemChipStatusBit { - SPIMemChipStatusBitBusy = (0x01 << 0), - SPIMemChipStatusBitWriteEnabled = (0x01 << 1), - SPIMemChipStatusBitBitProtection1 = (0x01 << 2), - SPIMemChipStatusBitBitProtection2 = (0x01 << 3), - SPIMemChipStatusBitBitProtection3 = (0x01 << 4), - SPIMemChipStatusBitTopBottomProtection = (0x01 << 5), - SPIMemChipStatusBitSectorProtect = (0x01 << 6), - SPIMemChipStatusBitRegisterProtect = (0x01 << 7) -}; - -typedef struct { - const char* vendor_name; - SPIMemChipVendor vendor_enum; -} SPIMemChipVendorName; - -struct SPIMemChip { - uint8_t vendor_id; - uint8_t type_id; - uint8_t capacity_id; - const char* model_name; - size_t size; - size_t page_size; - SPIMemChipVendor vendor_enum; - SPIMemChipWriteMode write_mode; -}; - -extern const SPIMemChip SPIMemChips[]; diff --git a/applications/external/spi_mem_manager/lib/spi/spi_mem_tools.c b/applications/external/spi_mem_manager/lib/spi/spi_mem_tools.c deleted file mode 100644 index 3518ca25c01..00000000000 --- a/applications/external/spi_mem_manager/lib/spi/spi_mem_tools.c +++ /dev/null @@ -1,152 +0,0 @@ -#include -#include -#include "spi_mem_chip_i.h" -#include "spi_mem_tools.h" - -static uint8_t spi_mem_tools_addr_to_byte_arr(uint32_t addr, uint8_t* cmd) { - uint8_t len = 3; // TODO(add support of 4 bytes address mode) - for(uint8_t i = 0; i < len; i++) { - cmd[i] = (addr >> ((len - (i + 1)) * 8)) & 0xFF; - } - return len; -} - -static bool spi_mem_tools_trx( - SPIMemChipCMD cmd, - uint8_t* tx_buf, - size_t tx_size, - uint8_t* rx_buf, - size_t rx_size) { - bool success = false; - furi_hal_spi_acquire(&furi_hal_spi_bus_handle_external); - do { - if(!furi_hal_spi_bus_tx( - &furi_hal_spi_bus_handle_external, (uint8_t*)&cmd, 1, SPI_MEM_SPI_TIMEOUT)) - break; - if(tx_buf) { - if(!furi_hal_spi_bus_tx( - &furi_hal_spi_bus_handle_external, tx_buf, tx_size, SPI_MEM_SPI_TIMEOUT)) - break; - } - if(rx_buf) { - if(!furi_hal_spi_bus_rx( - &furi_hal_spi_bus_handle_external, rx_buf, rx_size, SPI_MEM_SPI_TIMEOUT)) - break; - } - success = true; - } while(0); - furi_hal_spi_release(&furi_hal_spi_bus_handle_external); - return success; -} - -static bool spi_mem_tools_write_buffer(uint8_t* data, size_t size, size_t offset) { - furi_hal_spi_acquire(&furi_hal_spi_bus_handle_external); - uint8_t cmd = (uint8_t)SPIMemChipCMDWriteData; - uint8_t address[4]; - uint8_t address_size = spi_mem_tools_addr_to_byte_arr(offset, address); - bool success = false; - do { - if(!furi_hal_spi_bus_tx(&furi_hal_spi_bus_handle_external, &cmd, 1, SPI_MEM_SPI_TIMEOUT)) - break; - if(!furi_hal_spi_bus_tx( - &furi_hal_spi_bus_handle_external, address, address_size, SPI_MEM_SPI_TIMEOUT)) - break; - if(!furi_hal_spi_bus_tx(&furi_hal_spi_bus_handle_external, data, size, SPI_MEM_SPI_TIMEOUT)) - break; - success = true; - } while(0); - furi_hal_spi_release(&furi_hal_spi_bus_handle_external); - return success; -} - -bool spi_mem_tools_read_chip_info(SPIMemChip* chip) { - uint8_t rx_buf[3] = {0, 0, 0}; - do { - if(!spi_mem_tools_trx(SPIMemChipCMDReadJEDECChipID, NULL, 0, rx_buf, 3)) break; - if(rx_buf[0] == 0 || rx_buf[0] == 255) break; - chip->vendor_id = rx_buf[0]; - chip->type_id = rx_buf[1]; - chip->capacity_id = rx_buf[2]; - return true; - } while(0); - return false; -} - -bool spi_mem_tools_check_chip_info(SPIMemChip* chip) { - SPIMemChip new_chip_info; - spi_mem_tools_read_chip_info(&new_chip_info); - do { - if(chip->vendor_id != new_chip_info.vendor_id) break; - if(chip->type_id != new_chip_info.type_id) break; - if(chip->capacity_id != new_chip_info.capacity_id) break; - return true; - } while(0); - return false; -} - -bool spi_mem_tools_read_block(SPIMemChip* chip, size_t offset, uint8_t* data, size_t block_size) { - if(!spi_mem_tools_check_chip_info(chip)) return false; - for(size_t i = 0; i < block_size; i += SPI_MEM_MAX_BLOCK_SIZE) { - uint8_t cmd[4]; - if((offset + SPI_MEM_MAX_BLOCK_SIZE) > chip->size) return false; - if(!spi_mem_tools_trx( - SPIMemChipCMDReadData, - cmd, - spi_mem_tools_addr_to_byte_arr(offset, cmd), - data, - SPI_MEM_MAX_BLOCK_SIZE)) - return false; - offset += SPI_MEM_MAX_BLOCK_SIZE; - data += SPI_MEM_MAX_BLOCK_SIZE; - } - return true; -} - -size_t spi_mem_tools_get_file_max_block_size(SPIMemChip* chip) { - UNUSED(chip); - return (SPI_MEM_FILE_BUFFER_SIZE); -} - -SPIMemChipStatus spi_mem_tools_get_chip_status(SPIMemChip* chip) { - UNUSED(chip); - uint8_t status; - if(!spi_mem_tools_trx(SPIMemChipCMDReadStatus, NULL, 0, &status, 1)) - return SPIMemChipStatusError; - if(status & SPIMemChipStatusBitBusy) return SPIMemChipStatusBusy; - return SPIMemChipStatusIdle; -} - -static bool spi_mem_tools_set_write_enabled(SPIMemChip* chip, bool enable) { - UNUSED(chip); - uint8_t status; - SPIMemChipCMD cmd = SPIMemChipCMDWriteDisable; - if(enable) cmd = SPIMemChipCMDWriteEnable; - do { - if(!spi_mem_tools_trx(cmd, NULL, 0, NULL, 0)) break; - if(!spi_mem_tools_trx(SPIMemChipCMDReadStatus, NULL, 0, &status, 1)) break; - if(!(status & SPIMemChipStatusBitWriteEnabled) && enable) break; - if((status & SPIMemChipStatusBitWriteEnabled) && !enable) break; - return true; - } while(0); - return false; -} - -bool spi_mem_tools_erase_chip(SPIMemChip* chip) { - do { - if(!spi_mem_tools_set_write_enabled(chip, true)) break; - if(!spi_mem_tools_trx(SPIMemChipCMDChipErase, NULL, 0, NULL, 0)) break; - return true; - } while(0); - return true; -} - -bool spi_mem_tools_write_bytes(SPIMemChip* chip, size_t offset, uint8_t* data, size_t block_size) { - do { - if(!spi_mem_tools_check_chip_info(chip)) break; - if(!spi_mem_tools_set_write_enabled(chip, true)) break; - if((offset + block_size) > chip->size) break; - if(!spi_mem_tools_write_buffer(data, block_size, offset)) break; - return true; - } while(0); - return false; -} diff --git a/applications/external/spi_mem_manager/lib/spi/spi_mem_tools.h b/applications/external/spi_mem_manager/lib/spi/spi_mem_tools.h deleted file mode 100644 index ad006b8fff9..00000000000 --- a/applications/external/spi_mem_manager/lib/spi/spi_mem_tools.h +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - -#include "spi_mem_chip.h" - -#define SPI_MEM_SPI_TIMEOUT 1000 -#define SPI_MEM_MAX_BLOCK_SIZE 256 -#define SPI_MEM_FILE_BUFFER_SIZE 4096 - -bool spi_mem_tools_read_chip_info(SPIMemChip* chip); -bool spi_mem_tools_read_block(SPIMemChip* chip, size_t offset, uint8_t* data, size_t block_size); -size_t spi_mem_tools_get_file_max_block_size(SPIMemChip* chip); -SPIMemChipStatus spi_mem_tools_get_chip_status(SPIMemChip* chip); -bool spi_mem_tools_erase_chip(SPIMemChip* chip); -bool spi_mem_tools_write_bytes(SPIMemChip* chip, size_t offset, uint8_t* data, size_t block_size); diff --git a/applications/external/spi_mem_manager/lib/spi/spi_mem_worker.c b/applications/external/spi_mem_manager/lib/spi/spi_mem_worker.c deleted file mode 100644 index 438f338f130..00000000000 --- a/applications/external/spi_mem_manager/lib/spi/spi_mem_worker.c +++ /dev/null @@ -1,129 +0,0 @@ -#include "spi_mem_worker_i.h" - -typedef enum { - SPIMemEventStopThread = (1 << 0), - SPIMemEventChipDetect = (1 << 1), - SPIMemEventRead = (1 << 2), - SPIMemEventVerify = (1 << 3), - SPIMemEventErase = (1 << 4), - SPIMemEventWrite = (1 << 5), - SPIMemEventAll = - (SPIMemEventStopThread | SPIMemEventChipDetect | SPIMemEventRead | SPIMemEventVerify | - SPIMemEventErase | SPIMemEventWrite) -} SPIMemEventEventType; - -static int32_t spi_mem_worker_thread(void* thread_context); - -SPIMemWorker* spi_mem_worker_alloc() { - SPIMemWorker* worker = malloc(sizeof(SPIMemWorker)); - worker->callback = NULL; - worker->thread = furi_thread_alloc(); - worker->mode_index = SPIMemWorkerModeIdle; - furi_thread_set_name(worker->thread, "SPIMemWorker"); - furi_thread_set_callback(worker->thread, spi_mem_worker_thread); - furi_thread_set_context(worker->thread, worker); - furi_thread_set_stack_size(worker->thread, 10240); - return worker; -} - -void spi_mem_worker_free(SPIMemWorker* worker) { - furi_thread_free(worker->thread); - free(worker); -} - -bool spi_mem_worker_check_for_stop(SPIMemWorker* worker) { - UNUSED(worker); - uint32_t flags = furi_thread_flags_get(); - return (flags & SPIMemEventStopThread); -} - -static int32_t spi_mem_worker_thread(void* thread_context) { - SPIMemWorker* worker = thread_context; - while(true) { - uint32_t flags = furi_thread_flags_wait(SPIMemEventAll, FuriFlagWaitAny, FuriWaitForever); - if(flags != (unsigned)FuriFlagErrorTimeout) { - if(flags & SPIMemEventStopThread) break; - if(flags & SPIMemEventChipDetect) worker->mode_index = SPIMemWorkerModeChipDetect; - if(flags & SPIMemEventRead) worker->mode_index = SPIMemWorkerModeRead; - if(flags & SPIMemEventVerify) worker->mode_index = SPIMemWorkerModeVerify; - if(flags & SPIMemEventErase) worker->mode_index = SPIMemWorkerModeErase; - if(flags & SPIMemEventWrite) worker->mode_index = SPIMemWorkerModeWrite; - if(spi_mem_worker_modes[worker->mode_index].process) { - spi_mem_worker_modes[worker->mode_index].process(worker); - } - worker->mode_index = SPIMemWorkerModeIdle; - } - } - return 0; -} - -void spi_mem_worker_start_thread(SPIMemWorker* worker) { - furi_thread_start(worker->thread); -} - -void spi_mem_worker_stop_thread(SPIMemWorker* worker) { - furi_thread_flags_set(furi_thread_get_id(worker->thread), SPIMemEventStopThread); - furi_thread_join(worker->thread); -} - -void spi_mem_worker_chip_detect_start( - SPIMemChip* chip_info, - found_chips_t* found_chips, - SPIMemWorker* worker, - SPIMemWorkerCallback callback, - void* context) { - furi_check(worker->mode_index == SPIMemWorkerModeIdle); - worker->callback = callback; - worker->cb_ctx = context; - worker->chip_info = chip_info; - worker->found_chips = found_chips; - furi_thread_flags_set(furi_thread_get_id(worker->thread), SPIMemEventChipDetect); -} - -void spi_mem_worker_read_start( - SPIMemChip* chip_info, - SPIMemWorker* worker, - SPIMemWorkerCallback callback, - void* context) { - furi_check(worker->mode_index == SPIMemWorkerModeIdle); - worker->callback = callback; - worker->cb_ctx = context; - worker->chip_info = chip_info; - furi_thread_flags_set(furi_thread_get_id(worker->thread), SPIMemEventRead); -} - -void spi_mem_worker_verify_start( - SPIMemChip* chip_info, - SPIMemWorker* worker, - SPIMemWorkerCallback callback, - void* context) { - furi_check(worker->mode_index == SPIMemWorkerModeIdle); - worker->callback = callback; - worker->cb_ctx = context; - worker->chip_info = chip_info; - furi_thread_flags_set(furi_thread_get_id(worker->thread), SPIMemEventVerify); -} - -void spi_mem_worker_erase_start( - SPIMemChip* chip_info, - SPIMemWorker* worker, - SPIMemWorkerCallback callback, - void* context) { - furi_check(worker->mode_index == SPIMemWorkerModeIdle); - worker->callback = callback; - worker->cb_ctx = context; - worker->chip_info = chip_info; - furi_thread_flags_set(furi_thread_get_id(worker->thread), SPIMemEventErase); -} - -void spi_mem_worker_write_start( - SPIMemChip* chip_info, - SPIMemWorker* worker, - SPIMemWorkerCallback callback, - void* context) { - furi_check(worker->mode_index == SPIMemWorkerModeIdle); - worker->callback = callback; - worker->cb_ctx = context; - worker->chip_info = chip_info; - furi_thread_flags_set(furi_thread_get_id(worker->thread), SPIMemEventWrite); -} diff --git a/applications/external/spi_mem_manager/lib/spi/spi_mem_worker.h b/applications/external/spi_mem_manager/lib/spi/spi_mem_worker.h deleted file mode 100644 index c3761cd5a77..00000000000 --- a/applications/external/spi_mem_manager/lib/spi/spi_mem_worker.h +++ /dev/null @@ -1,54 +0,0 @@ -#pragma once - -#include -#include "spi_mem_chip.h" - -typedef struct SPIMemWorker SPIMemWorker; - -typedef struct { - void (*const process)(SPIMemWorker* worker); -} SPIMemWorkerModeType; - -typedef enum { - SPIMemCustomEventWorkerChipIdentified, - SPIMemCustomEventWorkerChipUnknown, - SPIMemCustomEventWorkerBlockReaded, - SPIMemCustomEventWorkerChipFail, - SPIMemCustomEventWorkerFileFail, - SPIMemCustomEventWorkerDone, - SPIMemCustomEventWorkerVerifyFail, -} SPIMemCustomEventWorker; - -typedef void (*SPIMemWorkerCallback)(void* context, SPIMemCustomEventWorker event); - -SPIMemWorker* spi_mem_worker_alloc(); -void spi_mem_worker_free(SPIMemWorker* worker); -void spi_mem_worker_start_thread(SPIMemWorker* worker); -void spi_mem_worker_stop_thread(SPIMemWorker* worker); -bool spi_mem_worker_check_for_stop(SPIMemWorker* worker); -void spi_mem_worker_chip_detect_start( - SPIMemChip* chip_info, - found_chips_t* found_chips, - SPIMemWorker* worker, - SPIMemWorkerCallback callback, - void* context); -void spi_mem_worker_read_start( - SPIMemChip* chip_info, - SPIMemWorker* worker, - SPIMemWorkerCallback callback, - void* context); -void spi_mem_worker_verify_start( - SPIMemChip* chip_info, - SPIMemWorker* worker, - SPIMemWorkerCallback callback, - void* context); -void spi_mem_worker_erase_start( - SPIMemChip* chip_info, - SPIMemWorker* worker, - SPIMemWorkerCallback callback, - void* context); -void spi_mem_worker_write_start( - SPIMemChip* chip_info, - SPIMemWorker* worker, - SPIMemWorkerCallback callback, - void* context); diff --git a/applications/external/spi_mem_manager/lib/spi/spi_mem_worker_i.h b/applications/external/spi_mem_manager/lib/spi/spi_mem_worker_i.h deleted file mode 100644 index 43e2d228703..00000000000 --- a/applications/external/spi_mem_manager/lib/spi/spi_mem_worker_i.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -#include "spi_mem_worker.h" - -typedef enum { - SPIMemWorkerModeIdle, - SPIMemWorkerModeChipDetect, - SPIMemWorkerModeRead, - SPIMemWorkerModeVerify, - SPIMemWorkerModeErase, - SPIMemWorkerModeWrite -} SPIMemWorkerMode; - -struct SPIMemWorker { - SPIMemChip* chip_info; - found_chips_t* found_chips; - SPIMemWorkerMode mode_index; - SPIMemWorkerCallback callback; - void* cb_ctx; - FuriThread* thread; - FuriString* file_name; -}; - -extern const SPIMemWorkerModeType spi_mem_worker_modes[]; diff --git a/applications/external/spi_mem_manager/lib/spi/spi_mem_worker_modes.c b/applications/external/spi_mem_manager/lib/spi/spi_mem_worker_modes.c deleted file mode 100644 index a393e54903e..00000000000 --- a/applications/external/spi_mem_manager/lib/spi/spi_mem_worker_modes.c +++ /dev/null @@ -1,214 +0,0 @@ -#include "spi_mem_worker_i.h" -#include "spi_mem_chip.h" -#include "spi_mem_tools.h" -#include "../../spi_mem_files.h" - -static void spi_mem_worker_chip_detect_process(SPIMemWorker* worker); -static void spi_mem_worker_read_process(SPIMemWorker* worker); -static void spi_mem_worker_verify_process(SPIMemWorker* worker); -static void spi_mem_worker_erase_process(SPIMemWorker* worker); -static void spi_mem_worker_write_process(SPIMemWorker* worker); - -const SPIMemWorkerModeType spi_mem_worker_modes[] = { - [SPIMemWorkerModeIdle] = {.process = NULL}, - [SPIMemWorkerModeChipDetect] = {.process = spi_mem_worker_chip_detect_process}, - [SPIMemWorkerModeRead] = {.process = spi_mem_worker_read_process}, - [SPIMemWorkerModeVerify] = {.process = spi_mem_worker_verify_process}, - [SPIMemWorkerModeErase] = {.process = spi_mem_worker_erase_process}, - [SPIMemWorkerModeWrite] = {.process = spi_mem_worker_write_process}}; - -static void spi_mem_worker_run_callback(SPIMemWorker* worker, SPIMemCustomEventWorker event) { - if(worker->callback) { - worker->callback(worker->cb_ctx, event); - } -} - -static bool spi_mem_worker_await_chip_busy(SPIMemWorker* worker) { - while(true) { - furi_delay_tick(10); // to give some time to OS - if(spi_mem_worker_check_for_stop(worker)) return true; - SPIMemChipStatus chip_status = spi_mem_tools_get_chip_status(worker->chip_info); - if(chip_status == SPIMemChipStatusError) return false; - if(chip_status == SPIMemChipStatusBusy) continue; - return true; - } -} - -static size_t spi_mem_worker_modes_get_total_size(SPIMemWorker* worker) { - size_t chip_size = spi_mem_chip_get_size(worker->chip_info); - size_t file_size = spi_mem_file_get_size(worker->cb_ctx); - size_t total_size = chip_size; - if(chip_size > file_size) total_size = file_size; - return total_size; -} - -// ChipDetect -static void spi_mem_worker_chip_detect_process(SPIMemWorker* worker) { - SPIMemCustomEventWorker event; - while(!spi_mem_tools_read_chip_info(worker->chip_info)) { - furi_delay_tick(10); // to give some time to OS - if(spi_mem_worker_check_for_stop(worker)) return; - } - if(spi_mem_chip_find_all(worker->chip_info, *worker->found_chips)) { - event = SPIMemCustomEventWorkerChipIdentified; - } else { - event = SPIMemCustomEventWorkerChipUnknown; - } - spi_mem_worker_run_callback(worker, event); -} - -// Read -static bool spi_mem_worker_read(SPIMemWorker* worker, SPIMemCustomEventWorker* event) { - uint8_t data_buffer[SPI_MEM_FILE_BUFFER_SIZE]; - size_t chip_size = spi_mem_chip_get_size(worker->chip_info); - size_t offset = 0; - bool success = true; - while(true) { - furi_delay_tick(10); // to give some time to OS - size_t block_size = SPI_MEM_FILE_BUFFER_SIZE; - if(spi_mem_worker_check_for_stop(worker)) break; - if(offset >= chip_size) break; - if((offset + block_size) > chip_size) block_size = chip_size - offset; - if(!spi_mem_tools_read_block(worker->chip_info, offset, data_buffer, block_size)) { - *event = SPIMemCustomEventWorkerChipFail; - success = false; - break; - } - if(!spi_mem_file_write_block(worker->cb_ctx, data_buffer, block_size)) { - success = false; - break; - } - offset += block_size; - spi_mem_worker_run_callback(worker, SPIMemCustomEventWorkerBlockReaded); - } - if(success) *event = SPIMemCustomEventWorkerDone; - return success; -} - -static void spi_mem_worker_read_process(SPIMemWorker* worker) { - SPIMemCustomEventWorker event = SPIMemCustomEventWorkerFileFail; - do { - if(!spi_mem_worker_await_chip_busy(worker)) break; - if(!spi_mem_file_create_open(worker->cb_ctx)) break; - if(!spi_mem_worker_read(worker, &event)) break; - } while(0); - spi_mem_file_close(worker->cb_ctx); - spi_mem_worker_run_callback(worker, event); -} - -// Verify -static bool - spi_mem_worker_verify(SPIMemWorker* worker, size_t total_size, SPIMemCustomEventWorker* event) { - uint8_t data_buffer_chip[SPI_MEM_FILE_BUFFER_SIZE]; - uint8_t data_buffer_file[SPI_MEM_FILE_BUFFER_SIZE]; - size_t offset = 0; - bool success = true; - while(true) { - furi_delay_tick(10); // to give some time to OS - size_t block_size = SPI_MEM_FILE_BUFFER_SIZE; - if(spi_mem_worker_check_for_stop(worker)) break; - if(offset >= total_size) break; - if((offset + block_size) > total_size) block_size = total_size - offset; - if(!spi_mem_tools_read_block(worker->chip_info, offset, data_buffer_chip, block_size)) { - *event = SPIMemCustomEventWorkerChipFail; - success = false; - break; - } - if(!spi_mem_file_read_block(worker->cb_ctx, data_buffer_file, block_size)) { - success = false; - break; - } - if(memcmp(data_buffer_chip, data_buffer_file, block_size) != 0) { - *event = SPIMemCustomEventWorkerVerifyFail; - success = false; - break; - } - offset += block_size; - spi_mem_worker_run_callback(worker, SPIMemCustomEventWorkerBlockReaded); - } - if(success) *event = SPIMemCustomEventWorkerDone; - return success; -} - -static void spi_mem_worker_verify_process(SPIMemWorker* worker) { - SPIMemCustomEventWorker event = SPIMemCustomEventWorkerFileFail; - size_t total_size = spi_mem_worker_modes_get_total_size(worker); - do { - if(!spi_mem_worker_await_chip_busy(worker)) break; - if(!spi_mem_file_open(worker->cb_ctx)) break; - if(!spi_mem_worker_verify(worker, total_size, &event)) break; - } while(0); - spi_mem_file_close(worker->cb_ctx); - spi_mem_worker_run_callback(worker, event); -} - -// Erase -static void spi_mem_worker_erase_process(SPIMemWorker* worker) { - SPIMemCustomEventWorker event = SPIMemCustomEventWorkerChipFail; - do { - if(!spi_mem_worker_await_chip_busy(worker)) break; - if(!spi_mem_tools_erase_chip(worker->chip_info)) break; - if(!spi_mem_worker_await_chip_busy(worker)) break; - event = SPIMemCustomEventWorkerDone; - } while(0); - spi_mem_worker_run_callback(worker, event); -} - -// Write -static bool spi_mem_worker_write_block_by_page( - SPIMemWorker* worker, - size_t offset, - uint8_t* data, - size_t block_size, - size_t page_size) { - for(size_t i = 0; i < block_size; i += page_size) { - if(!spi_mem_worker_await_chip_busy(worker)) return false; - if(!spi_mem_tools_write_bytes(worker->chip_info, offset, data, page_size)) return false; - offset += page_size; - data += page_size; - } - return true; -} - -static bool - spi_mem_worker_write(SPIMemWorker* worker, size_t total_size, SPIMemCustomEventWorker* event) { - bool success = true; - uint8_t data_buffer[SPI_MEM_FILE_BUFFER_SIZE]; - size_t page_size = spi_mem_chip_get_page_size(worker->chip_info); - size_t offset = 0; - while(true) { - furi_delay_tick(10); // to give some time to OS - size_t block_size = SPI_MEM_FILE_BUFFER_SIZE; - if(spi_mem_worker_check_for_stop(worker)) break; - if(offset >= total_size) break; - if((offset + block_size) > total_size) block_size = total_size - offset; - if(!spi_mem_file_read_block(worker->cb_ctx, data_buffer, block_size)) { - *event = SPIMemCustomEventWorkerFileFail; - success = false; - break; - } - if(!spi_mem_worker_write_block_by_page( - worker, offset, data_buffer, block_size, page_size)) { - success = false; - break; - } - offset += block_size; - spi_mem_worker_run_callback(worker, SPIMemCustomEventWorkerBlockReaded); - } - return success; -} - -static void spi_mem_worker_write_process(SPIMemWorker* worker) { - SPIMemCustomEventWorker event = SPIMemCustomEventWorkerChipFail; - size_t total_size = - spi_mem_worker_modes_get_total_size(worker); // need to be executed before opening file - do { - if(!spi_mem_file_open(worker->cb_ctx)) break; - if(!spi_mem_worker_await_chip_busy(worker)) break; - if(!spi_mem_worker_write(worker, total_size, &event)) break; - if(!spi_mem_worker_await_chip_busy(worker)) break; - event = SPIMemCustomEventWorkerDone; - } while(0); - spi_mem_file_close(worker->cb_ctx); - spi_mem_worker_run_callback(worker, event); -} diff --git a/applications/external/spi_mem_manager/scenes/spi_mem_scene.c b/applications/external/spi_mem_manager/scenes/spi_mem_scene.c deleted file mode 100644 index 7780005f41c..00000000000 --- a/applications/external/spi_mem_manager/scenes/spi_mem_scene.c +++ /dev/null @@ -1,30 +0,0 @@ -#include "spi_mem_scene.h" - -// Generate scene on_enter handlers array -#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, -void (*const spi_mem_on_enter_handlers[])(void*) = { -#include "spi_mem_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 spi_mem_on_event_handlers[])(void* context, SceneManagerEvent event) = { -#include "spi_mem_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 spi_mem_on_exit_handlers[])(void* context) = { -#include "spi_mem_scene_config.h" -}; -#undef ADD_SCENE - -// Initialize scene handlers configuration structure -const SceneManagerHandlers spi_mem_scene_handlers = { - .on_enter_handlers = spi_mem_on_enter_handlers, - .on_event_handlers = spi_mem_on_event_handlers, - .on_exit_handlers = spi_mem_on_exit_handlers, - .scene_num = SPIMemSceneNum, -}; diff --git a/applications/external/spi_mem_manager/scenes/spi_mem_scene.h b/applications/external/spi_mem_manager/scenes/spi_mem_scene.h deleted file mode 100644 index 2ac6d21e3cd..00000000000 --- a/applications/external/spi_mem_manager/scenes/spi_mem_scene.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -#include - -// Generate scene id and total number -#define ADD_SCENE(prefix, name, id) SPIMemScene##id, -typedef enum { -#include "spi_mem_scene_config.h" - SPIMemSceneNum, -} SPIMemScene; -#undef ADD_SCENE - -extern const SceneManagerHandlers spi_mem_scene_handlers; - -// Generate scene on_enter handlers declaration -#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); -#include "spi_mem_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 "spi_mem_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 "spi_mem_scene_config.h" -#undef ADD_SCENE diff --git a/applications/external/spi_mem_manager/scenes/spi_mem_scene_about.c b/applications/external/spi_mem_manager/scenes/spi_mem_scene_about.c deleted file mode 100644 index dc0cc4fe4de..00000000000 --- a/applications/external/spi_mem_manager/scenes/spi_mem_scene_about.c +++ /dev/null @@ -1,42 +0,0 @@ -#include "../spi_mem_app_i.h" -#include "../lib/spi/spi_mem_chip.h" - -#define SPI_MEM_VERSION_APP "0.1.0" -#define SPI_MEM_DEVELOPER "DrunkBatya" -#define SPI_MEM_GITHUB "https://github.com/flipperdevices/flipperzero-firmware" -#define SPI_MEM_NAME "\e#\e! SPI Mem Manager \e!\n" -#define SPI_MEM_BLANK_INV "\e#\e! \e!\n" - -void spi_mem_scene_about_on_enter(void* context) { - SPIMemApp* app = context; - FuriString* tmp_string = furi_string_alloc(); - - widget_add_text_box_element( - app->widget, 0, 0, 128, 14, AlignCenter, AlignBottom, SPI_MEM_BLANK_INV, false); - widget_add_text_box_element( - app->widget, 0, 2, 128, 14, AlignCenter, AlignBottom, SPI_MEM_NAME, false); - furi_string_printf(tmp_string, "\e#%s\n", "Information"); - furi_string_cat_printf(tmp_string, "Version: %s\n", SPI_MEM_VERSION_APP); - furi_string_cat_printf(tmp_string, "Developed by: %s\n", SPI_MEM_DEVELOPER); - furi_string_cat_printf(tmp_string, "Github: %s\n\n", SPI_MEM_GITHUB); - furi_string_cat_printf(tmp_string, "\e#%s\n", "Description"); - furi_string_cat_printf( - tmp_string, - "SPI memory dumper\n" - "Originally written by Hedger, ghettorce and x893 at\n" - "Flipper Hackathon 2021\n\n"); - widget_add_text_scroll_element(app->widget, 0, 16, 128, 50, furi_string_get_cstr(tmp_string)); - - furi_string_free(tmp_string); - view_dispatcher_switch_to_view(app->view_dispatcher, SPIMemViewWidget); -} - -bool spi_mem_scene_about_on_event(void* context, SceneManagerEvent event) { - UNUSED(context); - UNUSED(event); - return false; -} -void spi_mem_scene_about_on_exit(void* context) { - SPIMemApp* app = context; - widget_reset(app->widget); -} diff --git a/applications/external/spi_mem_manager/scenes/spi_mem_scene_chip_detect.c b/applications/external/spi_mem_manager/scenes/spi_mem_scene_chip_detect.c deleted file mode 100644 index d9b8f0aa386..00000000000 --- a/applications/external/spi_mem_manager/scenes/spi_mem_scene_chip_detect.c +++ /dev/null @@ -1,37 +0,0 @@ -#include "../spi_mem_app_i.h" - -static void spi_mem_scene_chip_detect_callback(void* context, SPIMemCustomEventWorker event) { - SPIMemApp* app = context; - view_dispatcher_send_custom_event(app->view_dispatcher, event); -} - -void spi_mem_scene_chip_detect_on_enter(void* context) { - SPIMemApp* app = context; - notification_message(app->notifications, &sequence_blink_start_yellow); - view_dispatcher_switch_to_view(app->view_dispatcher, SPIMemViewDetect); - spi_mem_worker_start_thread(app->worker); - spi_mem_worker_chip_detect_start( - app->chip_info, &app->found_chips, app->worker, spi_mem_scene_chip_detect_callback, app); -} - -bool spi_mem_scene_chip_detect_on_event(void* context, SceneManagerEvent event) { - SPIMemApp* app = context; - bool success = false; - if(event.type == SceneManagerEventTypeCustom) { - success = true; - if(event.event == SPIMemCustomEventWorkerChipIdentified) { - scene_manager_set_scene_state(app->scene_manager, SPIMemSceneSelectVendor, 0); - scene_manager_next_scene(app->scene_manager, SPIMemSceneSelectVendor); - } else if(event.event == SPIMemCustomEventWorkerChipUnknown) { - scene_manager_next_scene(app->scene_manager, SPIMemSceneChipDetectFail); - } - } - return success; -} - -void spi_mem_scene_chip_detect_on_exit(void* context) { - SPIMemApp* app = context; - spi_mem_worker_stop_thread(app->worker); - notification_message(app->notifications, &sequence_blink_stop); - popup_reset(app->popup); -} diff --git a/applications/external/spi_mem_manager/scenes/spi_mem_scene_chip_detect_fail.c b/applications/external/spi_mem_manager/scenes/spi_mem_scene_chip_detect_fail.c deleted file mode 100644 index 876a287212a..00000000000 --- a/applications/external/spi_mem_manager/scenes/spi_mem_scene_chip_detect_fail.c +++ /dev/null @@ -1,57 +0,0 @@ -#include "../spi_mem_app_i.h" -#include "../lib/spi/spi_mem_chip.h" - -static void spi_mem_scene_chip_detect_fail_widget_callback( - GuiButtonType result, - InputType type, - void* context) { - SPIMemApp* app = context; - if(type == InputTypeShort) { - view_dispatcher_send_custom_event(app->view_dispatcher, result); - } -} - -void spi_mem_scene_chip_detect_fail_on_enter(void* context) { - SPIMemApp* app = context; - FuriString* str = furi_string_alloc(); - widget_add_button_element( - app->widget, - GuiButtonTypeCenter, - "Retry", - spi_mem_scene_chip_detect_fail_widget_callback, - app); - widget_add_string_element( - app->widget, 64, 9, AlignCenter, AlignBottom, FontPrimary, "Detected"); - widget_add_string_element( - app->widget, 64, 20, AlignCenter, AlignBottom, FontPrimary, "unknown SPI chip"); - furi_string_printf(str, "Vendor\nid: 0x%02X", spi_mem_chip_get_vendor_id(app->chip_info)); - widget_add_string_multiline_element( - app->widget, 16, 44, AlignCenter, AlignBottom, FontSecondary, furi_string_get_cstr(str)); - furi_string_printf(str, "Type\nid: 0x%02X", spi_mem_chip_get_type_id(app->chip_info)); - widget_add_string_multiline_element( - app->widget, 64, 44, AlignCenter, AlignBottom, FontSecondary, furi_string_get_cstr(str)); - furi_string_printf(str, "Capacity\nid: 0x%02X", spi_mem_chip_get_capacity_id(app->chip_info)); - widget_add_string_multiline_element( - app->widget, 110, 44, AlignCenter, AlignBottom, FontSecondary, furi_string_get_cstr(str)); - furi_string_free(str); - view_dispatcher_switch_to_view(app->view_dispatcher, SPIMemViewWidget); -} - -bool spi_mem_scene_chip_detect_fail_on_event(void* context, SceneManagerEvent event) { - SPIMemApp* app = context; - bool success = false; - if(event.type == SceneManagerEventTypeBack) { - success = true; - scene_manager_search_and_switch_to_previous_scene(app->scene_manager, SPIMemSceneStart); - } else if(event.type == SceneManagerEventTypeCustom) { - success = true; - if(event.event == GuiButtonTypeCenter) { - scene_manager_previous_scene(app->scene_manager); - } - } - return success; -} -void spi_mem_scene_chip_detect_fail_on_exit(void* context) { - SPIMemApp* app = context; - widget_reset(app->widget); -} diff --git a/applications/external/spi_mem_manager/scenes/spi_mem_scene_chip_detected.c b/applications/external/spi_mem_manager/scenes/spi_mem_scene_chip_detected.c deleted file mode 100644 index 539578a4545..00000000000 --- a/applications/external/spi_mem_manager/scenes/spi_mem_scene_chip_detected.c +++ /dev/null @@ -1,94 +0,0 @@ -#include "../spi_mem_app_i.h" - -static void spi_mem_scene_chip_detected_widget_callback( - GuiButtonType result, - InputType type, - void* context) { - SPIMemApp* app = context; - if(type == InputTypeShort) { - view_dispatcher_send_custom_event(app->view_dispatcher, result); - } -} - -static void spi_mem_scene_chip_detected_print_chip_info(Widget* widget, SPIMemChip* chip_info) { - FuriString* tmp_string = furi_string_alloc(); - widget_add_string_element( - widget, - 40, - 12, - AlignLeft, - AlignTop, - FontSecondary, - spi_mem_chip_get_vendor_name(chip_info)); - widget_add_string_element( - widget, 40, 20, AlignLeft, AlignTop, FontSecondary, spi_mem_chip_get_model_name(chip_info)); - furi_string_printf(tmp_string, "Size: %zu KB", spi_mem_chip_get_size(chip_info) / 1024); - widget_add_string_element( - widget, 40, 28, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(tmp_string)); - furi_string_free(tmp_string); -} - -static void spi_mem_scene_chip_detect_draw_next_button(SPIMemApp* app) { - FuriString* str = furi_string_alloc(); - if(app->mode == SPIMemModeRead) furi_string_printf(str, "%s", "Read"); - if(app->mode == SPIMemModeWrite) furi_string_printf(str, "%s", "Write"); - if(app->mode == SPIMemModeErase) furi_string_printf(str, "%s", "Erase"); - if(app->mode == SPIMemModeCompare) furi_string_printf(str, "%s", "Check"); - widget_add_button_element( - app->widget, - GuiButtonTypeRight, - furi_string_get_cstr(str), - spi_mem_scene_chip_detected_widget_callback, - app); - furi_string_free(str); -} - -static void spi_mem_scene_chip_detected_set_previous_scene(SPIMemApp* app) { - uint32_t scene = SPIMemSceneStart; - if(app->mode == SPIMemModeCompare || app->mode == SPIMemModeWrite) - scene = SPIMemSceneSavedFileMenu; - scene_manager_search_and_switch_to_previous_scene(app->scene_manager, scene); -} - -static void spi_mem_scene_chip_detected_set_next_scene(SPIMemApp* app) { - uint32_t scene = SPIMemSceneStart; - if(app->mode == SPIMemModeRead) scene = SPIMemSceneReadFilename; - if(app->mode == SPIMemModeWrite) scene = SPIMemSceneErase; - if(app->mode == SPIMemModeErase) scene = SPIMemSceneErase; - if(app->mode == SPIMemModeCompare) scene = SPIMemSceneVerify; - scene_manager_next_scene(app->scene_manager, scene); -} - -void spi_mem_scene_chip_detected_on_enter(void* context) { - SPIMemApp* app = context; - widget_add_button_element( - app->widget, GuiButtonTypeLeft, "Retry", spi_mem_scene_chip_detected_widget_callback, app); - spi_mem_scene_chip_detect_draw_next_button(app); - widget_add_icon_element(app->widget, 0, 12, &I_Dip8_32x36); - widget_add_string_element( - app->widget, 64, 9, AlignCenter, AlignBottom, FontPrimary, "Detected SPI chip"); - spi_mem_scene_chip_detected_print_chip_info(app->widget, app->chip_info); - view_dispatcher_switch_to_view(app->view_dispatcher, SPIMemViewWidget); -} - -bool spi_mem_scene_chip_detected_on_event(void* context, SceneManagerEvent event) { - SPIMemApp* app = context; - bool success = false; - if(event.type == SceneManagerEventTypeBack) { - success = true; - spi_mem_scene_chip_detected_set_previous_scene(app); - } else if(event.type == SceneManagerEventTypeCustom) { - success = true; - if(event.event == GuiButtonTypeLeft) { - scene_manager_search_and_switch_to_previous_scene( - app->scene_manager, SPIMemSceneChipDetect); - } else if(event.event == GuiButtonTypeRight) { - spi_mem_scene_chip_detected_set_next_scene(app); - } - } - return success; -} -void spi_mem_scene_chip_detected_on_exit(void* context) { - SPIMemApp* app = context; - widget_reset(app->widget); -} diff --git a/applications/external/spi_mem_manager/scenes/spi_mem_scene_chip_error.c b/applications/external/spi_mem_manager/scenes/spi_mem_scene_chip_error.c deleted file mode 100644 index ca4b765a209..00000000000 --- a/applications/external/spi_mem_manager/scenes/spi_mem_scene_chip_error.c +++ /dev/null @@ -1,52 +0,0 @@ -#include "../spi_mem_app_i.h" - -static void - spi_mem_scene_chip_error_widget_callback(GuiButtonType result, InputType type, void* context) { - SPIMemApp* app = context; - if(type == InputTypeShort) { - view_dispatcher_send_custom_event(app->view_dispatcher, result); - } -} - -void spi_mem_scene_chip_error_on_enter(void* context) { - SPIMemApp* app = context; - widget_add_button_element( - app->widget, GuiButtonTypeLeft, "Back", spi_mem_scene_chip_error_widget_callback, app); - widget_add_string_element( - app->widget, 85, 15, AlignCenter, AlignBottom, FontPrimary, "SPI chip error"); - widget_add_string_multiline_element( - app->widget, - 85, - 52, - AlignCenter, - AlignBottom, - FontSecondary, - "Error while\ncommunicating\nwith chip"); - widget_add_icon_element(app->widget, 5, 6, &I_Dip8_32x36); - view_dispatcher_switch_to_view(app->view_dispatcher, SPIMemViewWidget); -} - -static void spi_mem_scene_chip_error_set_previous_scene(SPIMemApp* app) { - uint32_t scene = SPIMemSceneChipDetect; - if(app->mode == SPIMemModeRead || app->mode == SPIMemModeErase) scene = SPIMemSceneStart; - scene_manager_search_and_switch_to_previous_scene(app->scene_manager, scene); -} - -bool spi_mem_scene_chip_error_on_event(void* context, SceneManagerEvent event) { - SPIMemApp* app = context; - bool success = false; - if(event.type == SceneManagerEventTypeBack) { - success = true; - spi_mem_scene_chip_error_set_previous_scene(app); - } else if(event.type == SceneManagerEventTypeCustom) { - success = true; - if(event.event == GuiButtonTypeLeft) { - spi_mem_scene_chip_error_set_previous_scene(app); - } - } - return success; -} -void spi_mem_scene_chip_error_on_exit(void* context) { - SPIMemApp* app = context; - widget_reset(app->widget); -} diff --git a/applications/external/spi_mem_manager/scenes/spi_mem_scene_config.h b/applications/external/spi_mem_manager/scenes/spi_mem_scene_config.h deleted file mode 100644 index c0e37730359..00000000000 --- a/applications/external/spi_mem_manager/scenes/spi_mem_scene_config.h +++ /dev/null @@ -1,21 +0,0 @@ -ADD_SCENE(spi_mem, start, Start) -ADD_SCENE(spi_mem, chip_detect, ChipDetect) -ADD_SCENE(spi_mem, chip_detected, ChipDetected) -ADD_SCENE(spi_mem, chip_detect_fail, ChipDetectFail) -ADD_SCENE(spi_mem, select_file, SelectFile) -ADD_SCENE(spi_mem, saved_file_menu, SavedFileMenu) -ADD_SCENE(spi_mem, read, Read) -ADD_SCENE(spi_mem, read_filename, ReadFilename) -ADD_SCENE(spi_mem, delete_confirm, DeleteConfirm) -ADD_SCENE(spi_mem, success, Success) -ADD_SCENE(spi_mem, about, About) -ADD_SCENE(spi_mem, verify, Verify) -ADD_SCENE(spi_mem, file_info, FileInfo) -ADD_SCENE(spi_mem, erase, Erase) -ADD_SCENE(spi_mem, chip_error, ChipError) -ADD_SCENE(spi_mem, verify_error, VerifyError) -ADD_SCENE(spi_mem, write, Write) -ADD_SCENE(spi_mem, storage_error, StorageError) -ADD_SCENE(spi_mem, select_vendor, SelectVendor) -ADD_SCENE(spi_mem, select_model, SelectModel) -ADD_SCENE(spi_mem, wiring, Wiring) diff --git a/applications/external/spi_mem_manager/scenes/spi_mem_scene_delete_confirm.c b/applications/external/spi_mem_manager/scenes/spi_mem_scene_delete_confirm.c deleted file mode 100644 index bb514245260..00000000000 --- a/applications/external/spi_mem_manager/scenes/spi_mem_scene_delete_confirm.c +++ /dev/null @@ -1,62 +0,0 @@ -#include "../spi_mem_app_i.h" -#include "../spi_mem_files.h" - -static void spi_mem_scene_delete_confirm_widget_callback( - GuiButtonType result, - InputType type, - void* context) { - SPIMemApp* app = context; - if(type == InputTypeShort) { - view_dispatcher_send_custom_event(app->view_dispatcher, result); - } -} - -void spi_mem_scene_delete_confirm_on_enter(void* context) { - SPIMemApp* app = context; - FuriString* file_name = furi_string_alloc(); - FuriString* message = furi_string_alloc(); - path_extract_filename(app->file_path, file_name, true); - furi_string_printf(message, "\e#Delete %s?\e#", furi_string_get_cstr(file_name)); - widget_add_text_box_element( - app->widget, 0, 0, 128, 27, AlignCenter, AlignCenter, furi_string_get_cstr(message), true); - widget_add_button_element( - app->widget, - GuiButtonTypeLeft, - "Cancel", - spi_mem_scene_delete_confirm_widget_callback, - app); - widget_add_button_element( - app->widget, - GuiButtonTypeRight, - "Delete", - spi_mem_scene_delete_confirm_widget_callback, - app); - view_dispatcher_switch_to_view(app->view_dispatcher, SPIMemViewWidget); - furi_string_free(file_name); - furi_string_free(message); -} - -bool spi_mem_scene_delete_confirm_on_event(void* context, SceneManagerEvent event) { - SPIMemApp* app = context; - bool success = false; - if(event.type == SceneManagerEventTypeCustom) { - success = true; - if(event.event == GuiButtonTypeRight) { - app->mode = SPIMemModeDelete; - if(spi_mem_file_delete(app)) { - scene_manager_next_scene(app->scene_manager, SPIMemSceneSuccess); - } else { - scene_manager_next_scene(app->scene_manager, SPIMemSceneStorageError); - } - } else if(event.event == GuiButtonTypeLeft) { - scene_manager_search_and_switch_to_previous_scene( - app->scene_manager, SPIMemSceneSavedFileMenu); - } - } - return success; -} - -void spi_mem_scene_delete_confirm_on_exit(void* context) { - SPIMemApp* app = context; - widget_reset(app->widget); -} diff --git a/applications/external/spi_mem_manager/scenes/spi_mem_scene_erase.c b/applications/external/spi_mem_manager/scenes/spi_mem_scene_erase.c deleted file mode 100644 index 0d3ae66bfb2..00000000000 --- a/applications/external/spi_mem_manager/scenes/spi_mem_scene_erase.c +++ /dev/null @@ -1,65 +0,0 @@ -#include "../spi_mem_app_i.h" - -static void - spi_mem_scene_erase_widget_callback(GuiButtonType result, InputType type, void* context) { - SPIMemApp* app = context; - if(type == InputTypeShort) { - view_dispatcher_send_custom_event(app->view_dispatcher, result); - } -} - -static void spi_mem_scene_erase_callback(void* context, SPIMemCustomEventWorker event) { - SPIMemApp* app = context; - view_dispatcher_send_custom_event(app->view_dispatcher, event); -} - -void spi_mem_scene_erase_on_enter(void* context) { - SPIMemApp* app = context; - widget_add_button_element( - app->widget, GuiButtonTypeLeft, "Cancel", spi_mem_scene_erase_widget_callback, app); - widget_add_string_element( - app->widget, 64, 15, AlignCenter, AlignBottom, FontPrimary, "Erasing SPI chip"); - widget_add_string_element( - app->widget, 64, 27, AlignCenter, AlignBottom, FontSecondary, "Please be patient"); - notification_message(app->notifications, &sequence_blink_start_magenta); - view_dispatcher_switch_to_view(app->view_dispatcher, SPIMemViewWidget); - spi_mem_worker_start_thread(app->worker); - spi_mem_worker_erase_start(app->chip_info, app->worker, spi_mem_scene_erase_callback, app); -} - -static void spi_mem_scene_erase_set_previous_scene(SPIMemApp* app) { - uint32_t scene = SPIMemSceneStart; - if(app->mode == SPIMemModeWrite) scene = SPIMemSceneSavedFileMenu; - scene_manager_search_and_switch_to_previous_scene(app->scene_manager, scene); -} - -static void spi_mem_scene_erase_set_next_scene(SPIMemApp* app) { - uint32_t scene = SPIMemSceneSuccess; - if(app->mode == SPIMemModeWrite) scene = SPIMemSceneWrite; - scene_manager_next_scene(app->scene_manager, scene); -} - -bool spi_mem_scene_erase_on_event(void* context, SceneManagerEvent event) { - SPIMemApp* app = context; - bool success = false; - if(event.type == SceneManagerEventTypeBack) { - success = true; - spi_mem_scene_erase_set_previous_scene(app); - } else if(event.type == SceneManagerEventTypeCustom) { - success = true; - if(event.event == GuiButtonTypeLeft) { - scene_manager_previous_scene(app->scene_manager); - } else if(event.event == SPIMemCustomEventWorkerDone) { - spi_mem_scene_erase_set_next_scene(app); - } else if(event.event == SPIMemCustomEventWorkerChipFail) { - scene_manager_next_scene(app->scene_manager, SPIMemSceneChipError); - } - } - return success; -} -void spi_mem_scene_erase_on_exit(void* context) { - SPIMemApp* app = context; - spi_mem_worker_stop_thread(app->worker); - notification_message(app->notifications, &sequence_blink_stop); - widget_reset(app->widget); -} diff --git a/applications/external/spi_mem_manager/scenes/spi_mem_scene_file_info.c b/applications/external/spi_mem_manager/scenes/spi_mem_scene_file_info.c deleted file mode 100644 index 687f17f8194..00000000000 --- a/applications/external/spi_mem_manager/scenes/spi_mem_scene_file_info.c +++ /dev/null @@ -1,29 +0,0 @@ -#include "../spi_mem_app_i.h" -#include "../spi_mem_files.h" - -void spi_mem_scene_file_info_on_enter(void* context) { - SPIMemApp* app = context; - FuriString* str = furi_string_alloc(); - furi_string_printf(str, "Size: %zu KB", spi_mem_file_get_size(app) / 1024); - widget_add_string_element( - app->widget, 64, 9, AlignCenter, AlignBottom, FontPrimary, "File info"); - widget_add_string_element( - app->widget, 64, 20, AlignCenter, AlignBottom, FontSecondary, furi_string_get_cstr(str)); - furi_string_free(str); - view_dispatcher_switch_to_view(app->view_dispatcher, SPIMemViewWidget); -} - -bool spi_mem_scene_file_info_on_event(void* context, SceneManagerEvent event) { - SPIMemApp* app = context; - bool success = false; - if(event.type == SceneManagerEventTypeBack) { - success = true; - scene_manager_search_and_switch_to_previous_scene( - app->scene_manager, SPIMemSceneSavedFileMenu); - } - return success; -} -void spi_mem_scene_file_info_on_exit(void* context) { - SPIMemApp* app = context; - widget_reset(app->widget); -} diff --git a/applications/external/spi_mem_manager/scenes/spi_mem_scene_read.c b/applications/external/spi_mem_manager/scenes/spi_mem_scene_read.c deleted file mode 100644 index bbf38a3036d..00000000000 --- a/applications/external/spi_mem_manager/scenes/spi_mem_scene_read.c +++ /dev/null @@ -1,57 +0,0 @@ -#include "../spi_mem_app_i.h" -#include "../spi_mem_files.h" -#include "../lib/spi/spi_mem_chip.h" -#include "../lib/spi/spi_mem_tools.h" - -void spi_mem_scene_read_progress_view_result_callback(void* context) { - SPIMemApp* app = context; - view_dispatcher_send_custom_event(app->view_dispatcher, SPIMemCustomEventViewReadCancel); -} - -static void spi_mem_scene_read_callback(void* context, SPIMemCustomEventWorker event) { - SPIMemApp* app = context; - view_dispatcher_send_custom_event(app->view_dispatcher, event); -} - -void spi_mem_scene_read_on_enter(void* context) { - SPIMemApp* app = context; - spi_mem_view_progress_set_read_callback( - app->view_progress, spi_mem_scene_read_progress_view_result_callback, app); - notification_message(app->notifications, &sequence_blink_start_blue); - spi_mem_view_progress_set_chip_size(app->view_progress, spi_mem_chip_get_size(app->chip_info)); - spi_mem_view_progress_set_block_size( - app->view_progress, spi_mem_tools_get_file_max_block_size(app->chip_info)); - view_dispatcher_switch_to_view(app->view_dispatcher, SPIMemViewProgress); - spi_mem_worker_start_thread(app->worker); - spi_mem_worker_read_start(app->chip_info, app->worker, spi_mem_scene_read_callback, app); -} - -bool spi_mem_scene_read_on_event(void* context, SceneManagerEvent event) { - SPIMemApp* app = context; - UNUSED(app); - bool success = false; - if(event.type == SceneManagerEventTypeBack) { - success = true; - } else if(event.type == SceneManagerEventTypeCustom) { - success = true; - if(event.event == SPIMemCustomEventViewReadCancel) { - scene_manager_search_and_switch_to_previous_scene( - app->scene_manager, SPIMemSceneChipDetect); - } else if(event.event == SPIMemCustomEventWorkerBlockReaded) { - spi_mem_view_progress_inc_progress(app->view_progress); - } else if(event.event == SPIMemCustomEventWorkerDone) { - scene_manager_next_scene(app->scene_manager, SPIMemSceneVerify); - } else if(event.event == SPIMemCustomEventWorkerChipFail) { - scene_manager_next_scene(app->scene_manager, SPIMemSceneChipError); - } else if(event.event == SPIMemCustomEventWorkerFileFail) { - scene_manager_next_scene(app->scene_manager, SPIMemSceneStorageError); - } - } - return success; -} -void spi_mem_scene_read_on_exit(void* context) { - SPIMemApp* app = context; - spi_mem_worker_stop_thread(app->worker); - spi_mem_view_progress_reset(app->view_progress); - notification_message(app->notifications, &sequence_blink_stop); -} diff --git a/applications/external/spi_mem_manager/scenes/spi_mem_scene_read_filename.c b/applications/external/spi_mem_manager/scenes/spi_mem_scene_read_filename.c deleted file mode 100644 index 4b16baa2efb..00000000000 --- a/applications/external/spi_mem_manager/scenes/spi_mem_scene_read_filename.c +++ /dev/null @@ -1,46 +0,0 @@ -#include "../spi_mem_app_i.h" -#include "../spi_mem_files.h" - -void spi_mem_scene_read_filename_view_result_callback(void* context) { - SPIMemApp* app = context; - view_dispatcher_send_custom_event(app->view_dispatcher, SPIMemCustomEventTextEditResult); -} - -void spi_mem_scene_read_set_random_filename(SPIMemApp* app) { - if(furi_string_end_with(app->file_path, SPI_MEM_FILE_EXTENSION)) { - size_t filename_start = furi_string_search_rchar(app->file_path, '/'); - furi_string_left(app->file_path, filename_start); - } - set_random_name(app->text_buffer, SPI_MEM_TEXT_BUFFER_SIZE); -} - -void spi_mem_scene_read_filename_on_enter(void* context) { - SPIMemApp* app = context; - spi_mem_scene_read_set_random_filename(app); - text_input_set_header_text(app->text_input, "Name the dump"); - text_input_set_result_callback( - app->text_input, - spi_mem_scene_read_filename_view_result_callback, - app, - app->text_buffer, - SPI_MEM_FILE_NAME_SIZE, - true); - view_dispatcher_switch_to_view(app->view_dispatcher, SPIMemViewTextInput); -} - -bool spi_mem_scene_read_filename_on_event(void* context, SceneManagerEvent event) { - SPIMemApp* app = context; - UNUSED(app); - bool success = false; - if(event.type == SceneManagerEventTypeCustom) { - success = true; - if(event.event == SPIMemCustomEventTextEditResult) { - scene_manager_next_scene(app->scene_manager, SPIMemSceneRead); - } - } - return success; -} -void spi_mem_scene_read_filename_on_exit(void* context) { - SPIMemApp* app = context; - text_input_reset(app->text_input); -} diff --git a/applications/external/spi_mem_manager/scenes/spi_mem_scene_saved_file_menu.c b/applications/external/spi_mem_manager/scenes/spi_mem_scene_saved_file_menu.c deleted file mode 100644 index d5767455e84..00000000000 --- a/applications/external/spi_mem_manager/scenes/spi_mem_scene_saved_file_menu.c +++ /dev/null @@ -1,76 +0,0 @@ -#include "../spi_mem_app_i.h" - -typedef enum { - SPIMemSceneSavedFileMenuSubmenuIndexWrite, - SPIMemSceneSavedFileMenuSubmenuIndexCompare, - SPIMemSceneSavedFileMenuSubmenuIndexInfo, - SPIMemSceneSavedFileMenuSubmenuIndexDelete, -} SPIMemSceneSavedFileMenuSubmenuIndex; - -static void spi_mem_scene_saved_file_menu_submenu_callback(void* context, uint32_t index) { - SPIMemApp* app = context; - view_dispatcher_send_custom_event(app->view_dispatcher, index); -} - -void spi_mem_scene_saved_file_menu_on_enter(void* context) { - SPIMemApp* app = context; - submenu_add_item( - app->submenu, - "Write", - SPIMemSceneSavedFileMenuSubmenuIndexWrite, - spi_mem_scene_saved_file_menu_submenu_callback, - app); - submenu_add_item( - app->submenu, - "Compare", - SPIMemSceneSavedFileMenuSubmenuIndexCompare, - spi_mem_scene_saved_file_menu_submenu_callback, - app); - submenu_add_item( - app->submenu, - "Info", - SPIMemSceneSavedFileMenuSubmenuIndexInfo, - spi_mem_scene_saved_file_menu_submenu_callback, - app); - submenu_add_item( - app->submenu, - "Delete", - SPIMemSceneSavedFileMenuSubmenuIndexDelete, - spi_mem_scene_saved_file_menu_submenu_callback, - app); - submenu_set_selected_item( - app->submenu, scene_manager_get_scene_state(app->scene_manager, SPIMemSceneSavedFileMenu)); - view_dispatcher_switch_to_view(app->view_dispatcher, SPIMemViewSubmenu); -} - -bool spi_mem_scene_saved_file_menu_on_event(void* context, SceneManagerEvent event) { - SPIMemApp* app = context; - bool success = false; - if(event.type == SceneManagerEventTypeCustom) { - scene_manager_set_scene_state(app->scene_manager, SPIMemSceneSavedFileMenu, event.event); - if(event.event == SPIMemSceneSavedFileMenuSubmenuIndexWrite) { - app->mode = SPIMemModeWrite; - scene_manager_next_scene(app->scene_manager, SPIMemSceneChipDetect); - success = true; - } - if(event.event == SPIMemSceneSavedFileMenuSubmenuIndexCompare) { - app->mode = SPIMemModeCompare; - scene_manager_next_scene(app->scene_manager, SPIMemSceneChipDetect); - success = true; - } - if(event.event == SPIMemSceneSavedFileMenuSubmenuIndexDelete) { - scene_manager_next_scene(app->scene_manager, SPIMemSceneDeleteConfirm); - success = true; - } - if(event.event == SPIMemSceneSavedFileMenuSubmenuIndexInfo) { - scene_manager_next_scene(app->scene_manager, SPIMemSceneFileInfo); - success = true; - } - } - return success; -} - -void spi_mem_scene_saved_file_menu_on_exit(void* context) { - SPIMemApp* app = context; - submenu_reset(app->submenu); -} diff --git a/applications/external/spi_mem_manager/scenes/spi_mem_scene_select_file.c b/applications/external/spi_mem_manager/scenes/spi_mem_scene_select_file.c deleted file mode 100644 index cb48035b5f3..00000000000 --- a/applications/external/spi_mem_manager/scenes/spi_mem_scene_select_file.c +++ /dev/null @@ -1,22 +0,0 @@ -#include "../spi_mem_app_i.h" -#include "../spi_mem_files.h" - -void spi_mem_scene_select_file_on_enter(void* context) { - SPIMemApp* app = context; - if(spi_mem_file_select(app)) { - scene_manager_set_scene_state(app->scene_manager, SPIMemSceneSavedFileMenu, 0); - scene_manager_next_scene(app->scene_manager, SPIMemSceneSavedFileMenu); - } else { - scene_manager_search_and_switch_to_previous_scene(app->scene_manager, SPIMemSceneStart); - } -} - -bool spi_mem_scene_select_file_on_event(void* context, SceneManagerEvent event) { - UNUSED(context); - UNUSED(event); - return false; -} - -void spi_mem_scene_select_file_on_exit(void* context) { - UNUSED(context); -} diff --git a/applications/external/spi_mem_manager/scenes/spi_mem_scene_select_model.c b/applications/external/spi_mem_manager/scenes/spi_mem_scene_select_model.c deleted file mode 100644 index c39c4a1828b..00000000000 --- a/applications/external/spi_mem_manager/scenes/spi_mem_scene_select_model.c +++ /dev/null @@ -1,45 +0,0 @@ -#include "../spi_mem_app_i.h" - -static void spi_mem_scene_select_model_submenu_callback(void* context, uint32_t index) { - SPIMemApp* app = context; - spi_mem_chip_copy_chip_info(app->chip_info, *found_chips_get(app->found_chips, index)); - view_dispatcher_send_custom_event(app->view_dispatcher, index); -} - -void spi_mem_scene_select_model_on_enter(void* context) { - SPIMemApp* app = context; - size_t models_on_vendor = 0; - for(size_t index = 0; index < found_chips_size(app->found_chips); index++) { - if(spi_mem_chip_get_vendor_enum(*found_chips_get(app->found_chips, index)) != - app->chip_vendor_enum) - continue; - submenu_add_item( - app->submenu, - spi_mem_chip_get_model_name(*found_chips_get(app->found_chips, index)), - index, - spi_mem_scene_select_model_submenu_callback, - app); - models_on_vendor++; - } - if(models_on_vendor == 1) spi_mem_scene_select_model_submenu_callback(context, 0); - submenu_set_header(app->submenu, "Choose chip model"); - submenu_set_selected_item( - app->submenu, scene_manager_get_scene_state(app->scene_manager, SPIMemSceneSelectVendor)); - view_dispatcher_switch_to_view(app->view_dispatcher, SPIMemViewSubmenu); -} - -bool spi_mem_scene_select_model_on_event(void* context, SceneManagerEvent event) { - SPIMemApp* app = context; - bool success = false; - if(event.type == SceneManagerEventTypeCustom) { - scene_manager_set_scene_state(app->scene_manager, SPIMemSceneSelectVendor, event.event); - scene_manager_next_scene(app->scene_manager, SPIMemSceneChipDetected); - success = true; - } - return success; -} - -void spi_mem_scene_select_model_on_exit(void* context) { - SPIMemApp* app = context; - submenu_reset(app->submenu); -} diff --git a/applications/external/spi_mem_manager/scenes/spi_mem_scene_select_vendor.c b/applications/external/spi_mem_manager/scenes/spi_mem_scene_select_vendor.c deleted file mode 100644 index c7f736f8848..00000000000 --- a/applications/external/spi_mem_manager/scenes/spi_mem_scene_select_vendor.c +++ /dev/null @@ -1,70 +0,0 @@ -#include "../spi_mem_app_i.h" -#include -#include - -ARRAY_DEF(vendors, uint32_t) -ALGO_DEF(vendors, ARRAY_OPLIST(vendors)) - -static void spi_mem_scene_select_vendor_submenu_callback(void* context, uint32_t index) { - SPIMemApp* app = context; - app->chip_vendor_enum = index; - view_dispatcher_send_custom_event(app->view_dispatcher, index); -} - -static void spi_mem_scene_select_vendor_sort_vendors(SPIMemApp* app, vendors_t vendors_arr) { - for(size_t index = 0; index < found_chips_size(app->found_chips); index++) { - vendors_push_back( - vendors_arr, spi_mem_chip_get_vendor_enum(*found_chips_get(app->found_chips, index))); - } - vendors_uniq(vendors_arr); -} - -void spi_mem_scene_select_vendor_on_enter(void* context) { - SPIMemApp* app = context; - vendors_t vendors_arr; - vendors_init(vendors_arr); - spi_mem_scene_select_vendor_sort_vendors(app, vendors_arr); - size_t vendors_arr_size = vendors_size(vendors_arr); - if(vendors_arr_size == 1) - spi_mem_scene_select_vendor_submenu_callback(context, *vendors_get(vendors_arr, 0)); - for(size_t index = 0; index < vendors_arr_size; index++) { - uint32_t vendor_enum = *vendors_get(vendors_arr, index); - submenu_add_item( - app->submenu, - spi_mem_chip_get_vendor_name_by_enum(vendor_enum), - vendor_enum, - spi_mem_scene_select_vendor_submenu_callback, - app); - } - vendors_clear(vendors_arr); - submenu_set_header(app->submenu, "Choose chip vendor"); - submenu_set_selected_item( - app->submenu, scene_manager_get_scene_state(app->scene_manager, SPIMemSceneSelectVendor)); - view_dispatcher_switch_to_view(app->view_dispatcher, SPIMemViewSubmenu); -} - -static void spi_mem_scene_select_vendor_set_previous_scene(SPIMemApp* app) { - uint32_t scene = SPIMemSceneStart; - if(app->mode == SPIMemModeCompare || app->mode == SPIMemModeWrite) - scene = SPIMemSceneSavedFileMenu; - scene_manager_search_and_switch_to_previous_scene(app->scene_manager, scene); -} - -bool spi_mem_scene_select_vendor_on_event(void* context, SceneManagerEvent event) { - SPIMemApp* app = context; - bool success = false; - if(event.type == SceneManagerEventTypeBack) { - success = true; - spi_mem_scene_select_vendor_set_previous_scene(app); - } else if(event.type == SceneManagerEventTypeCustom) { - scene_manager_set_scene_state(app->scene_manager, SPIMemSceneSelectVendor, event.event); - scene_manager_next_scene(app->scene_manager, SPIMemSceneSelectModel); - success = true; - } - return success; -} - -void spi_mem_scene_select_vendor_on_exit(void* context) { - SPIMemApp* app = context; - submenu_reset(app->submenu); -} diff --git a/applications/external/spi_mem_manager/scenes/spi_mem_scene_start.c b/applications/external/spi_mem_manager/scenes/spi_mem_scene_start.c deleted file mode 100644 index 38d064a4d2e..00000000000 --- a/applications/external/spi_mem_manager/scenes/spi_mem_scene_start.c +++ /dev/null @@ -1,84 +0,0 @@ -#include "../spi_mem_app_i.h" - -typedef enum { - SPIMemSceneStartSubmenuIndexRead, - SPIMemSceneStartSubmenuIndexSaved, - SPIMemSceneStartSubmenuIndexErase, - SPIMemSceneStartSubmenuIndexWiring, - SPIMemSceneStartSubmenuIndexAbout -} SPIMemSceneStartSubmenuIndex; - -static void spi_mem_scene_start_submenu_callback(void* context, uint32_t index) { - SPIMemApp* app = context; - view_dispatcher_send_custom_event(app->view_dispatcher, index); -} - -void spi_mem_scene_start_on_enter(void* context) { - SPIMemApp* app = context; - submenu_add_item( - app->submenu, - "Read", - SPIMemSceneStartSubmenuIndexRead, - spi_mem_scene_start_submenu_callback, - app); - submenu_add_item( - app->submenu, - "Saved", - SPIMemSceneStartSubmenuIndexSaved, - spi_mem_scene_start_submenu_callback, - app); - submenu_add_item( - app->submenu, - "Erase", - SPIMemSceneStartSubmenuIndexErase, - spi_mem_scene_start_submenu_callback, - app); - submenu_add_item( - app->submenu, - "Wiring", - SPIMemSceneStartSubmenuIndexWiring, - spi_mem_scene_start_submenu_callback, - app); - submenu_add_item( - app->submenu, - "About", - SPIMemSceneStartSubmenuIndexAbout, - spi_mem_scene_start_submenu_callback, - app); - submenu_set_selected_item( - app->submenu, scene_manager_get_scene_state(app->scene_manager, SPIMemSceneStart)); - view_dispatcher_switch_to_view(app->view_dispatcher, SPIMemViewSubmenu); -} - -bool spi_mem_scene_start_on_event(void* context, SceneManagerEvent event) { - SPIMemApp* app = context; - bool success = false; - if(event.type == SceneManagerEventTypeCustom) { - scene_manager_set_scene_state(app->scene_manager, SPIMemSceneStart, event.event); - if(event.event == SPIMemSceneStartSubmenuIndexRead) { - app->mode = SPIMemModeRead; - scene_manager_next_scene(app->scene_manager, SPIMemSceneChipDetect); - success = true; - } else if(event.event == SPIMemSceneStartSubmenuIndexSaved) { - furi_string_set(app->file_path, STORAGE_APP_DATA_PATH_PREFIX); - scene_manager_next_scene(app->scene_manager, SPIMemSceneSelectFile); - success = true; - } else if(event.event == SPIMemSceneStartSubmenuIndexErase) { - app->mode = SPIMemModeErase; - scene_manager_next_scene(app->scene_manager, SPIMemSceneChipDetect); - success = true; - } else if(event.event == SPIMemSceneStartSubmenuIndexWiring) { - scene_manager_next_scene(app->scene_manager, SPIMemSceneWiring); - success = true; - } else if(event.event == SPIMemSceneStartSubmenuIndexAbout) { - scene_manager_next_scene(app->scene_manager, SPIMemSceneAbout); - success = true; - } - } - return success; -} - -void spi_mem_scene_start_on_exit(void* context) { - SPIMemApp* app = context; - submenu_reset(app->submenu); -} diff --git a/applications/external/spi_mem_manager/scenes/spi_mem_scene_storage_error.c b/applications/external/spi_mem_manager/scenes/spi_mem_scene_storage_error.c deleted file mode 100644 index d5e289e2447..00000000000 --- a/applications/external/spi_mem_manager/scenes/spi_mem_scene_storage_error.c +++ /dev/null @@ -1,56 +0,0 @@ -#include "../spi_mem_app_i.h" - -static void spi_mem_scene_storage_error_widget_callback( - GuiButtonType result, - InputType type, - void* context) { - SPIMemApp* app = context; - if(type == InputTypeShort) { - view_dispatcher_send_custom_event(app->view_dispatcher, result); - } -} - -void spi_mem_scene_storage_error_on_enter(void* context) { - SPIMemApp* app = context; - widget_add_button_element( - app->widget, GuiButtonTypeLeft, "Back", spi_mem_scene_storage_error_widget_callback, app); - widget_add_string_element( - app->widget, 85, 15, AlignCenter, AlignBottom, FontPrimary, "Storage error"); - widget_add_string_multiline_element( - app->widget, - 85, - 52, - AlignCenter, - AlignBottom, - FontSecondary, - "Error while\nworking with\nfilesystem"); - widget_add_icon_element(app->widget, 5, 6, &I_SDQuestion_35x43); - view_dispatcher_switch_to_view(app->view_dispatcher, SPIMemViewWidget); -} - -static void spi_mem_scene_storage_error_set_previous_scene(SPIMemApp* app) { - uint32_t scene = SPIMemSceneChipDetect; - if(app->mode == SPIMemModeRead) scene = SPIMemSceneStart; - if(app->mode == SPIMemModeErase) scene = SPIMemSceneStart; - if(app->mode == SPIMemModeDelete) scene = SPIMemSceneStart; - scene_manager_search_and_switch_to_previous_scene(app->scene_manager, scene); -} - -bool spi_mem_scene_storage_error_on_event(void* context, SceneManagerEvent event) { - SPIMemApp* app = context; - bool success = false; - if(event.type == SceneManagerEventTypeBack) { - success = true; - spi_mem_scene_storage_error_set_previous_scene(app); - } else if(event.type == SceneManagerEventTypeCustom) { - success = true; - if(event.event == GuiButtonTypeLeft) { - spi_mem_scene_storage_error_set_previous_scene(app); - } - } - return success; -} -void spi_mem_scene_storage_error_on_exit(void* context) { - SPIMemApp* app = context; - widget_reset(app->widget); -} diff --git a/applications/external/spi_mem_manager/scenes/spi_mem_scene_success.c b/applications/external/spi_mem_manager/scenes/spi_mem_scene_success.c deleted file mode 100644 index 39039466fc1..00000000000 --- a/applications/external/spi_mem_manager/scenes/spi_mem_scene_success.c +++ /dev/null @@ -1,40 +0,0 @@ -#include "../spi_mem_app_i.h" - -static void spi_mem_scene_success_popup_callback(void* context) { - SPIMemApp* app = context; - view_dispatcher_send_custom_event(app->view_dispatcher, SPIMemCustomEventPopupBack); -} - -void spi_mem_scene_success_on_enter(void* context) { - SPIMemApp* app = context; - popup_set_icon(app->popup, 32, 5, &I_DolphinNice_96x59); - popup_set_header(app->popup, "Success!", 5, 7, AlignLeft, AlignTop); - popup_set_callback(app->popup, spi_mem_scene_success_popup_callback); - popup_set_context(app->popup, app); - popup_set_timeout(app->popup, 1500); - popup_enable_timeout(app->popup); - view_dispatcher_switch_to_view(app->view_dispatcher, SPIMemViewPopup); -} - -static void spi_mem_scene_success_set_previous_scene(SPIMemApp* app) { - uint32_t scene = SPIMemSceneSelectFile; - if(app->mode == SPIMemModeErase) scene = SPIMemSceneStart; - scene_manager_search_and_switch_to_another_scene(app->scene_manager, scene); -} - -bool spi_mem_scene_success_on_event(void* context, SceneManagerEvent event) { - SPIMemApp* app = context; - bool success = false; - if(event.type == SceneManagerEventTypeCustom) { - success = true; - if(event.event == SPIMemCustomEventPopupBack) { - spi_mem_scene_success_set_previous_scene(app); - } - } - return success; -} - -void spi_mem_scene_success_on_exit(void* context) { - SPIMemApp* app = context; - popup_reset(app->popup); -} diff --git a/applications/external/spi_mem_manager/scenes/spi_mem_scene_verify.c b/applications/external/spi_mem_manager/scenes/spi_mem_scene_verify.c deleted file mode 100644 index 08a8d1057ed..00000000000 --- a/applications/external/spi_mem_manager/scenes/spi_mem_scene_verify.c +++ /dev/null @@ -1,59 +0,0 @@ -#include "../spi_mem_app_i.h" -#include "../spi_mem_files.h" -#include "../lib/spi/spi_mem_chip.h" -#include "../lib/spi/spi_mem_tools.h" - -void spi_mem_scene_verify_view_result_callback(void* context) { - SPIMemApp* app = context; - view_dispatcher_send_custom_event(app->view_dispatcher, SPIMemCustomEventViewVerifySkip); -} - -static void spi_mem_scene_verify_callback(void* context, SPIMemCustomEventWorker event) { - SPIMemApp* app = context; - view_dispatcher_send_custom_event(app->view_dispatcher, event); -} - -void spi_mem_scene_verify_on_enter(void* context) { - SPIMemApp* app = context; - spi_mem_view_progress_set_verify_callback( - app->view_progress, spi_mem_scene_verify_view_result_callback, app); - notification_message(app->notifications, &sequence_blink_start_cyan); - spi_mem_view_progress_set_chip_size(app->view_progress, spi_mem_chip_get_size(app->chip_info)); - spi_mem_view_progress_set_file_size(app->view_progress, spi_mem_file_get_size(app)); - spi_mem_view_progress_set_block_size( - app->view_progress, spi_mem_tools_get_file_max_block_size(app->chip_info)); - view_dispatcher_switch_to_view(app->view_dispatcher, SPIMemViewProgress); - spi_mem_worker_start_thread(app->worker); - spi_mem_worker_verify_start(app->chip_info, app->worker, spi_mem_scene_verify_callback, app); -} - -bool spi_mem_scene_verify_on_event(void* context, SceneManagerEvent event) { - SPIMemApp* app = context; - UNUSED(app); - bool success = false; - if(event.type == SceneManagerEventTypeBack) { - success = true; - } else if(event.type == SceneManagerEventTypeCustom) { - success = true; - if(event.event == SPIMemCustomEventViewVerifySkip) { - scene_manager_next_scene(app->scene_manager, SPIMemSceneSuccess); - } else if(event.event == SPIMemCustomEventWorkerBlockReaded) { - spi_mem_view_progress_inc_progress(app->view_progress); - } else if(event.event == SPIMemCustomEventWorkerChipFail) { - scene_manager_next_scene(app->scene_manager, SPIMemSceneChipError); - } else if(event.event == SPIMemCustomEventWorkerFileFail) { - scene_manager_next_scene(app->scene_manager, SPIMemSceneStorageError); - } else if(event.event == SPIMemCustomEventWorkerDone) { - scene_manager_next_scene(app->scene_manager, SPIMemSceneSuccess); - } else if(event.event == SPIMemCustomEventWorkerVerifyFail) { - scene_manager_next_scene(app->scene_manager, SPIMemSceneVerifyError); - } - } - return success; -} -void spi_mem_scene_verify_on_exit(void* context) { - SPIMemApp* app = context; - spi_mem_worker_stop_thread(app->worker); - spi_mem_view_progress_reset(app->view_progress); - notification_message(app->notifications, &sequence_blink_stop); -} diff --git a/applications/external/spi_mem_manager/scenes/spi_mem_scene_verify_error.c b/applications/external/spi_mem_manager/scenes/spi_mem_scene_verify_error.c deleted file mode 100644 index fbe954fa6c7..00000000000 --- a/applications/external/spi_mem_manager/scenes/spi_mem_scene_verify_error.c +++ /dev/null @@ -1,43 +0,0 @@ -#include "../spi_mem_app_i.h" - -static void spi_mem_scene_verify_error_widget_callback( - GuiButtonType result, - InputType type, - void* context) { - SPIMemApp* app = context; - if(type == InputTypeShort) { - view_dispatcher_send_custom_event(app->view_dispatcher, result); - } -} - -void spi_mem_scene_verify_error_on_enter(void* context) { - SPIMemApp* app = context; - widget_add_button_element( - app->widget, GuiButtonTypeLeft, "Back", spi_mem_scene_verify_error_widget_callback, app); - widget_add_string_element( - app->widget, 64, 9, AlignCenter, AlignBottom, FontPrimary, "Verification error"); - widget_add_string_element( - app->widget, 64, 21, AlignCenter, AlignBottom, FontSecondary, "Data mismatch"); - view_dispatcher_switch_to_view(app->view_dispatcher, SPIMemViewWidget); -} - -bool spi_mem_scene_verify_error_on_event(void* context, SceneManagerEvent event) { - SPIMemApp* app = context; - bool success = false; - if(event.type == SceneManagerEventTypeBack) { - success = true; - scene_manager_search_and_switch_to_previous_scene( - app->scene_manager, SPIMemSceneChipDetect); - } else if(event.type == SceneManagerEventTypeCustom) { - success = true; - if(event.event == GuiButtonTypeLeft) { - scene_manager_search_and_switch_to_previous_scene( - app->scene_manager, SPIMemSceneChipDetect); - } - } - return success; -} -void spi_mem_scene_verify_error_on_exit(void* context) { - SPIMemApp* app = context; - widget_reset(app->widget); -} diff --git a/applications/external/spi_mem_manager/scenes/spi_mem_scene_wiring.c b/applications/external/spi_mem_manager/scenes/spi_mem_scene_wiring.c deleted file mode 100644 index 22036f4bc32..00000000000 --- a/applications/external/spi_mem_manager/scenes/spi_mem_scene_wiring.c +++ /dev/null @@ -1,18 +0,0 @@ -#include "../spi_mem_app_i.h" -#include "../lib/spi/spi_mem_chip.h" - -void spi_mem_scene_wiring_on_enter(void* context) { - SPIMemApp* app = context; - widget_add_icon_element(app->widget, 0, 0, &I_Wiring_SPI_128x64); - view_dispatcher_switch_to_view(app->view_dispatcher, SPIMemViewWidget); -} - -bool spi_mem_scene_wiring_on_event(void* context, SceneManagerEvent event) { - UNUSED(context); - UNUSED(event); - return false; -} -void spi_mem_scene_wiring_on_exit(void* context) { - SPIMemApp* app = context; - widget_reset(app->widget); -} diff --git a/applications/external/spi_mem_manager/scenes/spi_mem_scene_write.c b/applications/external/spi_mem_manager/scenes/spi_mem_scene_write.c deleted file mode 100644 index dfa384fbbe0..00000000000 --- a/applications/external/spi_mem_manager/scenes/spi_mem_scene_write.c +++ /dev/null @@ -1,58 +0,0 @@ -#include "../spi_mem_app_i.h" -#include "../spi_mem_files.h" -#include "../lib/spi/spi_mem_chip.h" -#include "../lib/spi/spi_mem_tools.h" - -void spi_mem_scene_write_progress_view_result_callback(void* context) { - SPIMemApp* app = context; - view_dispatcher_send_custom_event(app->view_dispatcher, SPIMemCustomEventViewReadCancel); -} - -static void spi_mem_scene_write_callback(void* context, SPIMemCustomEventWorker event) { - SPIMemApp* app = context; - view_dispatcher_send_custom_event(app->view_dispatcher, event); -} - -void spi_mem_scene_write_on_enter(void* context) { - SPIMemApp* app = context; - spi_mem_view_progress_set_write_callback( - app->view_progress, spi_mem_scene_write_progress_view_result_callback, app); - notification_message(app->notifications, &sequence_blink_start_cyan); - spi_mem_view_progress_set_chip_size(app->view_progress, spi_mem_chip_get_size(app->chip_info)); - spi_mem_view_progress_set_file_size(app->view_progress, spi_mem_file_get_size(app)); - spi_mem_view_progress_set_block_size( - app->view_progress, spi_mem_tools_get_file_max_block_size(app->chip_info)); - view_dispatcher_switch_to_view(app->view_dispatcher, SPIMemViewProgress); - spi_mem_worker_start_thread(app->worker); - spi_mem_worker_write_start(app->chip_info, app->worker, spi_mem_scene_write_callback, app); -} - -bool spi_mem_scene_write_on_event(void* context, SceneManagerEvent event) { - SPIMemApp* app = context; - UNUSED(app); - bool success = false; - if(event.type == SceneManagerEventTypeBack) { - success = true; - } else if(event.type == SceneManagerEventTypeCustom) { - success = true; - if(event.event == SPIMemCustomEventViewReadCancel) { - scene_manager_search_and_switch_to_previous_scene( - app->scene_manager, SPIMemSceneChipDetect); - } else if(event.event == SPIMemCustomEventWorkerBlockReaded) { - spi_mem_view_progress_inc_progress(app->view_progress); - } else if(event.event == SPIMemCustomEventWorkerDone) { - scene_manager_next_scene(app->scene_manager, SPIMemSceneVerify); - } else if(event.event == SPIMemCustomEventWorkerChipFail) { - scene_manager_next_scene(app->scene_manager, SPIMemSceneChipError); - } else if(event.event == SPIMemCustomEventWorkerFileFail) { - scene_manager_next_scene(app->scene_manager, SPIMemSceneStorageError); - } - } - return success; -} -void spi_mem_scene_write_on_exit(void* context) { - SPIMemApp* app = context; - spi_mem_worker_stop_thread(app->worker); - spi_mem_view_progress_reset(app->view_progress); - notification_message(app->notifications, &sequence_blink_stop); -} diff --git a/applications/external/spi_mem_manager/spi_mem_app.c b/applications/external/spi_mem_manager/spi_mem_app.c deleted file mode 100644 index 96c3632d056..00000000000 --- a/applications/external/spi_mem_manager/spi_mem_app.c +++ /dev/null @@ -1,112 +0,0 @@ -#include -#include "spi_mem_app_i.h" -#include "spi_mem_files.h" -#include "lib/spi/spi_mem_chip_i.h" - -static bool spi_mem_custom_event_callback(void* context, uint32_t event) { - furi_assert(context); - SPIMemApp* app = context; - return scene_manager_handle_custom_event(app->scene_manager, event); -} - -static bool spi_mem_back_event_callback(void* context) { - furi_assert(context); - SPIMemApp* app = context; - return scene_manager_handle_back_event(app->scene_manager); -} - -SPIMemApp* spi_mem_alloc(void) { - SPIMemApp* instance = malloc(sizeof(SPIMemApp)); //-V799 - - instance->file_path = furi_string_alloc_set(STORAGE_APP_DATA_PATH_PREFIX); - instance->gui = furi_record_open(RECORD_GUI); - instance->notifications = furi_record_open(RECORD_NOTIFICATION); - instance->view_dispatcher = view_dispatcher_alloc(); - instance->scene_manager = scene_manager_alloc(&spi_mem_scene_handlers, instance); - instance->submenu = submenu_alloc(); - instance->dialog_ex = dialog_ex_alloc(); - instance->popup = popup_alloc(); - instance->worker = spi_mem_worker_alloc(); - instance->dialogs = furi_record_open(RECORD_DIALOGS); - instance->storage = furi_record_open(RECORD_STORAGE); - instance->widget = widget_alloc(); - instance->chip_info = malloc(sizeof(SPIMemChip)); - found_chips_init(instance->found_chips); - instance->view_progress = spi_mem_view_progress_alloc(); - instance->view_detect = spi_mem_view_detect_alloc(); - instance->text_input = text_input_alloc(); - instance->mode = SPIMemModeUnknown; - - // Migrate data from old sd-card folder - storage_common_migrate(instance->storage, EXT_PATH("spimem"), STORAGE_APP_DATA_PATH_PREFIX); - - view_dispatcher_enable_queue(instance->view_dispatcher); - view_dispatcher_set_event_callback_context(instance->view_dispatcher, instance); - view_dispatcher_set_custom_event_callback( - instance->view_dispatcher, spi_mem_custom_event_callback); - view_dispatcher_set_navigation_event_callback( - instance->view_dispatcher, spi_mem_back_event_callback); - view_dispatcher_attach_to_gui( - instance->view_dispatcher, instance->gui, ViewDispatcherTypeFullscreen); - view_dispatcher_add_view( - instance->view_dispatcher, SPIMemViewSubmenu, submenu_get_view(instance->submenu)); - view_dispatcher_add_view( - instance->view_dispatcher, SPIMemViewDialogEx, dialog_ex_get_view(instance->dialog_ex)); - view_dispatcher_add_view( - instance->view_dispatcher, SPIMemViewPopup, popup_get_view(instance->popup)); - view_dispatcher_add_view( - instance->view_dispatcher, SPIMemViewWidget, widget_get_view(instance->widget)); - view_dispatcher_add_view( - instance->view_dispatcher, - SPIMemViewProgress, - spi_mem_view_progress_get_view(instance->view_progress)); - view_dispatcher_add_view( - instance->view_dispatcher, - SPIMemViewDetect, - spi_mem_view_detect_get_view(instance->view_detect)); - view_dispatcher_add_view( - instance->view_dispatcher, SPIMemViewTextInput, text_input_get_view(instance->text_input)); - - furi_hal_power_enable_otg(); - furi_hal_spi_bus_handle_init(&furi_hal_spi_bus_handle_external); - scene_manager_next_scene(instance->scene_manager, SPIMemSceneStart); - return instance; -} //-V773 - -void spi_mem_free(SPIMemApp* instance) { - view_dispatcher_remove_view(instance->view_dispatcher, SPIMemViewSubmenu); - view_dispatcher_remove_view(instance->view_dispatcher, SPIMemViewDialogEx); - view_dispatcher_remove_view(instance->view_dispatcher, SPIMemViewPopup); - view_dispatcher_remove_view(instance->view_dispatcher, SPIMemViewWidget); - view_dispatcher_remove_view(instance->view_dispatcher, SPIMemViewProgress); - view_dispatcher_remove_view(instance->view_dispatcher, SPIMemViewDetect); - view_dispatcher_remove_view(instance->view_dispatcher, SPIMemViewTextInput); - spi_mem_view_progress_free(instance->view_progress); - spi_mem_view_detect_free(instance->view_detect); - submenu_free(instance->submenu); - dialog_ex_free(instance->dialog_ex); - popup_free(instance->popup); - widget_free(instance->widget); - text_input_free(instance->text_input); - view_dispatcher_free(instance->view_dispatcher); - scene_manager_free(instance->scene_manager); - spi_mem_worker_free(instance->worker); - free(instance->chip_info); - found_chips_clear(instance->found_chips); - furi_record_close(RECORD_STORAGE); - furi_record_close(RECORD_DIALOGS); - furi_record_close(RECORD_NOTIFICATION); - furi_record_close(RECORD_GUI); - furi_string_free(instance->file_path); - furi_hal_spi_bus_handle_deinit(&furi_hal_spi_bus_handle_external); - furi_hal_power_disable_otg(); - free(instance); -} - -int32_t spi_mem_app(void* p) { - UNUSED(p); - SPIMemApp* instance = spi_mem_alloc(); - view_dispatcher_run(instance->view_dispatcher); - spi_mem_free(instance); - return 0; -} diff --git a/applications/external/spi_mem_manager/spi_mem_app.h b/applications/external/spi_mem_manager/spi_mem_app.h deleted file mode 100644 index 37ac927dbc3..00000000000 --- a/applications/external/spi_mem_manager/spi_mem_app.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -typedef struct SPIMemApp SPIMemApp; diff --git a/applications/external/spi_mem_manager/spi_mem_app_i.h b/applications/external/spi_mem_manager/spi_mem_app_i.h deleted file mode 100644 index 285ca66d2f3..00000000000 --- a/applications/external/spi_mem_manager/spi_mem_app_i.h +++ /dev/null @@ -1,78 +0,0 @@ -#pragma once - -#include -#include -#include -#include "spi_mem_app.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "scenes/spi_mem_scene.h" -#include "lib/spi/spi_mem_worker.h" -#include "spi_mem_manager_icons.h" -#include "views/spi_mem_view_progress.h" -#include "views/spi_mem_view_detect.h" - -#define TAG "SPIMem" -#define SPI_MEM_FILE_EXTENSION ".bin" -#define SPI_MEM_FILE_NAME_SIZE 100 -#define SPI_MEM_TEXT_BUFFER_SIZE 128 - -typedef enum { - SPIMemModeRead, - SPIMemModeWrite, - SPIMemModeCompare, - SPIMemModeErase, - SPIMemModeDelete, - SPIMemModeUnknown -} SPIMemMode; - -struct SPIMemApp { - Gui* gui; - ViewDispatcher* view_dispatcher; - SceneManager* scene_manager; - Submenu* submenu; - DialogEx* dialog_ex; - Popup* popup; - NotificationApp* notifications; - FuriString* file_path; - DialogsApp* dialogs; - Storage* storage; - File* file; - Widget* widget; - SPIMemWorker* worker; - SPIMemChip* chip_info; - found_chips_t found_chips; - uint32_t chip_vendor_enum; - SPIMemProgressView* view_progress; - SPIMemDetectView* view_detect; - TextInput* text_input; - SPIMemMode mode; - char text_buffer[SPI_MEM_TEXT_BUFFER_SIZE + 1]; -}; - -typedef enum { - SPIMemViewSubmenu, - SPIMemViewDialogEx, - SPIMemViewPopup, - SPIMemViewWidget, - SPIMemViewTextInput, - SPIMemViewProgress, - SPIMemViewDetect -} SPIMemView; - -typedef enum { - SPIMemCustomEventViewReadCancel, - SPIMemCustomEventViewVerifySkip, - SPIMemCustomEventTextEditResult, - SPIMemCustomEventPopupBack -} SPIMemCustomEvent; diff --git a/applications/external/spi_mem_manager/spi_mem_files.c b/applications/external/spi_mem_manager/spi_mem_files.c deleted file mode 100644 index 9b787bd7f9c..00000000000 --- a/applications/external/spi_mem_manager/spi_mem_files.c +++ /dev/null @@ -1,68 +0,0 @@ -#include "spi_mem_app_i.h" - -bool spi_mem_file_delete(SPIMemApp* app) { - return (storage_simply_remove(app->storage, furi_string_get_cstr(app->file_path))); -} - -bool spi_mem_file_select(SPIMemApp* app) { - DialogsFileBrowserOptions browser_options; - dialog_file_browser_set_basic_options(&browser_options, SPI_MEM_FILE_EXTENSION, &I_Dip8_10px); - browser_options.base_path = STORAGE_APP_DATA_PATH_PREFIX; - bool success = - dialog_file_browser_show(app->dialogs, app->file_path, app->file_path, &browser_options); - return success; -} - -bool spi_mem_file_create_open(SPIMemApp* app) { - bool success = false; - app->file = storage_file_alloc(app->storage); - do { - if(furi_string_end_with(app->file_path, SPI_MEM_FILE_EXTENSION)) { - if(!spi_mem_file_delete(app)) break; - size_t filename_start = furi_string_search_rchar(app->file_path, '/'); - furi_string_left(app->file_path, filename_start); - } - furi_string_cat_printf(app->file_path, "/%s%s", app->text_buffer, SPI_MEM_FILE_EXTENSION); - if(!storage_file_open( - app->file, furi_string_get_cstr(app->file_path), FSAM_WRITE, FSOM_CREATE_NEW)) - break; - success = true; - } while(0); - if(!success) { //-V547 - dialog_message_show_storage_error(app->dialogs, "Cannot save\nfile"); - } - return success; -} - -bool spi_mem_file_open(SPIMemApp* app) { - app->file = storage_file_alloc(app->storage); - if(!storage_file_open( - app->file, furi_string_get_cstr(app->file_path), FSAM_READ_WRITE, FSOM_OPEN_EXISTING)) { - dialog_message_show_storage_error(app->dialogs, "Cannot save\nfile"); - return false; - } - return true; -} - -bool spi_mem_file_write_block(SPIMemApp* app, uint8_t* data, size_t size) { - if(storage_file_write(app->file, data, size) != size) return false; - return true; -} - -bool spi_mem_file_read_block(SPIMemApp* app, uint8_t* data, size_t size) { - if(storage_file_read(app->file, data, size) != size) return false; - return true; -} - -void spi_mem_file_close(SPIMemApp* app) { - storage_file_close(app->file); - storage_file_free(app->file); -} - -size_t spi_mem_file_get_size(SPIMemApp* app) { - FileInfo file_info; - if(storage_common_stat(app->storage, furi_string_get_cstr(app->file_path), &file_info) != - FSE_OK) - return 0; - return file_info.size; -} diff --git a/applications/external/spi_mem_manager/spi_mem_files.h b/applications/external/spi_mem_manager/spi_mem_files.h deleted file mode 100644 index 6a529d3279f..00000000000 --- a/applications/external/spi_mem_manager/spi_mem_files.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once -#include "spi_mem_app.h" - -bool spi_mem_file_select(SPIMemApp* app); -bool spi_mem_file_create(SPIMemApp* app, const char* file_name); -bool spi_mem_file_delete(SPIMemApp* app); -bool spi_mem_file_create_open(SPIMemApp* app); -bool spi_mem_file_open(SPIMemApp* app); -bool spi_mem_file_write_block(SPIMemApp* app, uint8_t* data, size_t size); -bool spi_mem_file_read_block(SPIMemApp* app, uint8_t* data, size_t size); -void spi_mem_file_close(SPIMemApp* app); -void spi_mem_file_show_storage_error(SPIMemApp* app, const char* error_text); -size_t spi_mem_file_get_size(SPIMemApp* app); diff --git a/applications/external/spi_mem_manager/tools/README.md b/applications/external/spi_mem_manager/tools/README.md deleted file mode 100644 index 91080941ff5..00000000000 --- a/applications/external/spi_mem_manager/tools/README.md +++ /dev/null @@ -1,7 +0,0 @@ -This utility can convert nofeletru's UsbAsp-flash's chiplist.xml to C array - -Usage: -```bash - ./chiplist_convert.py chiplist/chiplist.xml - mv spi_mem_chip_arr.c ../lib/spi/spi_mem_chip_arr.c -``` diff --git a/applications/external/spi_mem_manager/tools/chiplist/LICENSE b/applications/external/spi_mem_manager/tools/chiplist/LICENSE deleted file mode 100644 index 56364f15060..00000000000 --- a/applications/external/spi_mem_manager/tools/chiplist/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2015 nofeletru - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - diff --git a/applications/external/spi_mem_manager/tools/chiplist/chiplist.xml b/applications/external/spi_mem_manager/tools/chiplist/chiplist.xml deleted file mode 100644 index 91a65474322..00000000000 --- a/applications/external/spi_mem_manager/tools/chiplist/chiplist.xml +++ /dev/null @@ -1,984 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <_25AA010A page="16" size="128" spicmd="95"/> - <_25AA020A page="16" size="256" spicmd="95"/> - <_25AA040 page="16" size="512" spicmd="95"/> - <_25AA040A page="16" size="512" spicmd="95"/> - <_25AA080 page="16" size="1024" spicmd="95"/> - <_25AA080A page="16" size="1024" spicmd="95"/> - <_25AA080B page="32" size="1024" spicmd="95"/> - <_25AA080C page="16" size="1024" spicmd="95"/> - <_25AA080D page="32" size="1024" spicmd="95"/> - <_25AA1024 page="256" size="131072" spicmd="95"/> - <_25AA128 page="64" size="16384" spicmd="95"/> - <_25AA160 page="16" size="2048" spicmd="95"/> - <_25AA160A page="16" size="2048" spicmd="95"/> - <_25AA160B page="32" size="2048" spicmd="95"/> - <_25AA256 page="64" size="32768" spicmd="95"/> - <_25AA320 page="32" size="4096" spicmd="95"/> - <_25AA512 page="128" size="65536" spicmd="95"/> - <_25AA640 page="32" size="8192" spicmd="95"/> - <_25C040 page="16" size="512" spicmd="95"/> - <_25C080 page="16" size="1024" spicmd="95"/> - <_25C160 page="16" size="2048" spicmd="95"/> - <_25C320 page="32" size="4096" spicmd="95"/> - <_25C640 page="32" size="8192" spicmd="95"/> - <_25LC010A page="16" size="128" spicmd="95"/> - <_25LC020A page="16" size="256" spicmd="95"/> - <_25LC040 page="16" size="512" spicmd="95"/> - <_25LC040A page="16" size="512" spicmd="95"/> - <_25LC080 page="16" size="1024" spicmd="95"/> - <_25LC080A page="16" size="1024" spicmd="95"/> - <_25LC080B page="32" size="1024" spicmd="95"/> - <_25LC080C page="16" size="1024" spicmd="95"/> - <_25LC080D page="32" size="1024" spicmd="95"/> - <_25LC1024 page="256" size="131072" spicmd="95"/> - <_25LC128 page="64" size="16384" spicmd="95"/> - <_25LC160 page="16" size="2048" spicmd="95"/> - <_25LC160A page="16" size="2048" spicmd="95"/> - <_25LC160B page="32" size="2048" spicmd="95"/> - <_25LC256 page="64" size="32768" spicmd="95"/> - <_25LC320 page="32" size="4096" spicmd="95"/> - <_25LC512 page="128" size="65536" spicmd="95"/> - <_25LC640 page="32" size="8192" spicmd="95"/> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <_24Cxxx> - - <_24C01 page="1" size="128" addrtype="1"/> - <_24C02 page="1" size="256" addrtype="1"/> - <_24C04 page="1" size="512" addrtype="2"/> - <_24C08 page="16" size="1024" addrtype="3"/> - <_24C16 page="16" size="2048" addrtype="4"/> - <_24C32 page="32" size="4096" addrtype="5"/> - <_24C64 page="32" size="8192" addrtype="5"/> - <_24C128 page="64" size="16384" addrtype="5"/> - <_24C256 page="64" size="32768" addrtype="5"/> - <_24C512 page="128" size="65536" addrtype="5"/> - <_24C1024 page="128" size="131072" addrtype="6"/> - - - - - - - - - - - - - diff --git a/applications/external/spi_mem_manager/tools/chiplist_convert.py b/applications/external/spi_mem_manager/tools/chiplist_convert.py deleted file mode 100755 index 8b623eb3e97..00000000000 --- a/applications/external/spi_mem_manager/tools/chiplist_convert.py +++ /dev/null @@ -1,109 +0,0 @@ -#!/usr/bin/env python3 - -import argparse -import xml.etree.ElementTree as XML -import sys - - -def getArgs(): - parser = argparse.ArgumentParser( - description="chiplist.xml to C array converter", - ) - parser.add_argument("file", help="chiplist.xml file") - return parser.parse_args() - - -def getXML(file): - tree = XML.parse(file) - root = tree.getroot() - return root - - -def parseChip(cur, arr, vendor, vendorCodeArr): - chip = {} - chipAttr = cur.attrib - if "page" not in chipAttr: # chip without page size not supported - return - if "id" not in chipAttr: # I2C not supported yet - return - if len(chipAttr["id"]) < 6: # ID wihout capacity id not supported yet - return - chip["modelName"] = cur.tag - chip["vendorEnum"] = "SPIMemChipVendor" + vendor - chip["vendorID"] = "0x" + chipAttr["id"][0] + chipAttr["id"][1] - chip["typeID"] = chipAttr["id"][2] + chipAttr["id"][3] - chip["capacityID"] = chipAttr["id"][4] + chipAttr["id"][5] - chip["size"] = chipAttr["size"] - if chipAttr["page"] == "SSTW": - chip["writeMode"] = "SPIMemChipWriteModeAAIWord" - chip["pageSize"] = "1" - elif chipAttr["page"] == "SSTB": - chip["writeMode"] = "SPIMemChipWriteModeAAIByte" - chip["pageSize"] = "1" - else: - chip["writeMode"] = "SPIMemChipWriteModePage" - chip["pageSize"] = chipAttr["page"] - arr.append(chip) - vendorCodeArr[vendor].add(chip["vendorID"]) - - -def cleanEmptyVendors(vendors): - for cur in list(vendors): - if not vendors[cur]: - vendors.pop(cur) - - -def getVendors(xml, interface): - arr = {} - for cur in xml.find(interface): - arr[cur.tag] = set() - return arr - - -def parseXML(xml, interface, vendorCodeArr): - arr = [] - for vendor in xml.find(interface): - for cur in vendor: - parseChip(cur, arr, vendor.tag, vendorCodeArr) - return arr - - -def getVendorNameEnum(vendorID): - try: - return vendors[vendorID] - except: - print("Unknown vendor: " + vendorID) - sys.exit(1) - - -def generateCArr(arr, filename): - with open(filename, "w") as out: - print('#include "spi_mem_chip_i.h"', file=out) - print("const SPIMemChip SPIMemChips[] = {", file=out) - for cur in arr: - print(" {" + cur["vendorID"] + ",", file=out, end="") - print(" 0x" + cur["typeID"] + ",", file=out, end="") - print(" 0x" + cur["capacityID"] + ",", file=out, end="") - print(' "' + cur["modelName"] + '",', file=out, end="") - print(" " + cur["size"] + ",", file=out, end="") - print(" " + cur["pageSize"] + ",", file=out, end="") - print(" " + cur["vendorEnum"] + ",", file=out, end="") - if cur == arr[-1]: - print(" " + cur["writeMode"] + "}};", file=out) - else: - print(" " + cur["writeMode"] + "},", file=out) - -def main(): - filename = "spi_mem_chip_arr.c" - args = getArgs() - xml = getXML(args.file) - vendors = getVendors(xml, "SPI") - chipArr = parseXML(xml, "SPI", vendors) - cleanEmptyVendors(vendors) - for cur in vendors: - print(' {"' + cur + '", SPIMemChipVendor' + cur + "},") - generateCArr(chipArr, filename) - - -if __name__ == "__main__": - main() diff --git a/applications/external/spi_mem_manager/views/spi_mem_view_detect.c b/applications/external/spi_mem_manager/views/spi_mem_view_detect.c deleted file mode 100644 index eddf36e4957..00000000000 --- a/applications/external/spi_mem_manager/views/spi_mem_view_detect.c +++ /dev/null @@ -1,64 +0,0 @@ -#include "spi_mem_view_detect.h" -#include "spi_mem_manager_icons.h" -#include - -struct SPIMemDetectView { - View* view; - IconAnimation* icon; - SPIMemDetectViewCallback callback; - void* cb_ctx; -}; - -typedef struct { - IconAnimation* icon; -} SPIMemDetectViewModel; - -View* spi_mem_view_detect_get_view(SPIMemDetectView* app) { - return app->view; -} - -static void spi_mem_view_detect_draw_callback(Canvas* canvas, void* context) { - SPIMemDetectViewModel* model = context; - canvas_set_font(canvas, FontPrimary); - canvas_draw_icon_animation(canvas, 0, 0, model->icon); - canvas_draw_str_aligned(canvas, 64, 26, AlignLeft, AlignCenter, "Detecting"); - canvas_draw_str_aligned(canvas, 64, 36, AlignLeft, AlignCenter, "SPI chip..."); -} - -static void spi_mem_view_detect_enter_callback(void* context) { - SPIMemDetectView* app = context; - with_view_model( - app->view, SPIMemDetectViewModel * model, { icon_animation_start(model->icon); }, false); -} - -static void spi_mem_view_detect_exit_callback(void* context) { - SPIMemDetectView* app = context; - with_view_model( - app->view, SPIMemDetectViewModel * model, { icon_animation_stop(model->icon); }, false); -} - -SPIMemDetectView* spi_mem_view_detect_alloc() { - SPIMemDetectView* app = malloc(sizeof(SPIMemDetectView)); - app->view = view_alloc(); - view_set_context(app->view, app); - view_allocate_model(app->view, ViewModelTypeLocking, sizeof(SPIMemDetectViewModel)); - with_view_model( - app->view, - SPIMemDetectViewModel * model, - { - model->icon = icon_animation_alloc(&A_ChipLooking_64x64); - view_tie_icon_animation(app->view, model->icon); - }, - false); - view_set_draw_callback(app->view, spi_mem_view_detect_draw_callback); - view_set_enter_callback(app->view, spi_mem_view_detect_enter_callback); - view_set_exit_callback(app->view, spi_mem_view_detect_exit_callback); - return app; -} - -void spi_mem_view_detect_free(SPIMemDetectView* app) { - with_view_model( - app->view, SPIMemDetectViewModel * model, { icon_animation_free(model->icon); }, false); - view_free(app->view); - free(app); -} diff --git a/applications/external/spi_mem_manager/views/spi_mem_view_detect.h b/applications/external/spi_mem_manager/views/spi_mem_view_detect.h deleted file mode 100644 index f95edb60d29..00000000000 --- a/applications/external/spi_mem_manager/views/spi_mem_view_detect.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once -#include - -typedef struct SPIMemDetectView SPIMemDetectView; -typedef void (*SPIMemDetectViewCallback)(void* context); - -View* spi_mem_view_detect_get_view(SPIMemDetectView* app); -SPIMemDetectView* spi_mem_view_detect_alloc(); -void spi_mem_view_detect_free(SPIMemDetectView* app); diff --git a/applications/external/spi_mem_manager/views/spi_mem_view_progress.c b/applications/external/spi_mem_manager/views/spi_mem_view_progress.c deleted file mode 100644 index 790f979974b..00000000000 --- a/applications/external/spi_mem_manager/views/spi_mem_view_progress.c +++ /dev/null @@ -1,230 +0,0 @@ -#include "spi_mem_view_progress.h" -#include - -struct SPIMemProgressView { - View* view; - SPIMemProgressViewCallback callback; - void* cb_ctx; -}; - -typedef enum { - SPIMemProgressViewTypeRead, - SPIMemProgressViewTypeVerify, - SPIMemProgressViewTypeWrite, - SPIMemProgressViewTypeUnknown -} SPIMemProgressViewType; - -typedef struct { - size_t chip_size; - size_t file_size; - size_t blocks_written; - size_t block_size; - float progress; - SPIMemProgressViewType view_type; -} SPIMemProgressViewModel; - -View* spi_mem_view_progress_get_view(SPIMemProgressView* app) { - return app->view; -} - -static void spi_mem_view_progress_draw_progress(Canvas* canvas, float progress) { - FuriString* progress_str = furi_string_alloc(); - if(progress > 1.0) progress = 1.0; - furi_string_printf(progress_str, "%d %%", (int)(progress * 100)); - elements_progress_bar(canvas, 13, 35, 100, progress); - canvas_draw_str_aligned( - canvas, 64, 25, AlignCenter, AlignTop, furi_string_get_cstr(progress_str)); - furi_string_free(progress_str); -} - -static void - spi_mem_view_progress_read_draw_callback(Canvas* canvas, SPIMemProgressViewModel* model) { - canvas_draw_str_aligned(canvas, 64, 4, AlignCenter, AlignTop, "Reading dump"); - spi_mem_view_progress_draw_progress(canvas, model->progress); - elements_button_left(canvas, "Cancel"); -} - -static void - spi_mem_view_progress_draw_size_warning(Canvas* canvas, SPIMemProgressViewModel* model) { - if(model->file_size > model->chip_size) { - canvas_draw_str_aligned(canvas, 64, 13, AlignCenter, AlignTop, "Size clamped to chip!"); - } - if(model->chip_size > model->file_size) { - canvas_draw_str_aligned(canvas, 64, 13, AlignCenter, AlignTop, "Size clamped to file!"); - } -} - -static void - spi_mem_view_progress_verify_draw_callback(Canvas* canvas, SPIMemProgressViewModel* model) { - canvas_draw_str_aligned(canvas, 64, 2, AlignCenter, AlignTop, "Verifying dump"); - spi_mem_view_progress_draw_size_warning(canvas, model); - spi_mem_view_progress_draw_progress(canvas, model->progress); - elements_button_center(canvas, "Skip"); -} - -static void - spi_mem_view_progress_write_draw_callback(Canvas* canvas, SPIMemProgressViewModel* model) { - canvas_draw_str_aligned(canvas, 64, 4, AlignCenter, AlignTop, "Writing dump"); - spi_mem_view_progress_draw_size_warning(canvas, model); - spi_mem_view_progress_draw_progress(canvas, model->progress); - elements_button_left(canvas, "Cancel"); -} - -static void spi_mem_view_progress_draw_callback(Canvas* canvas, void* context) { - SPIMemProgressViewModel* model = context; - SPIMemProgressViewType view_type = model->view_type; - if(view_type == SPIMemProgressViewTypeRead) { - spi_mem_view_progress_read_draw_callback(canvas, model); - } else if(view_type == SPIMemProgressViewTypeVerify) { - spi_mem_view_progress_verify_draw_callback(canvas, model); - } else if(view_type == SPIMemProgressViewTypeWrite) { - spi_mem_view_progress_write_draw_callback(canvas, model); - } -} - -static bool - spi_mem_view_progress_read_write_input_callback(InputEvent* event, SPIMemProgressView* app) { - bool success = false; - if(event->type == InputTypeShort && event->key == InputKeyLeft) { - if(app->callback) { - app->callback(app->cb_ctx); - } - success = true; - } - return success; -} - -static bool - spi_mem_view_progress_verify_input_callback(InputEvent* event, SPIMemProgressView* app) { - bool success = false; - if(event->type == InputTypeShort && event->key == InputKeyOk) { - if(app->callback) { - app->callback(app->cb_ctx); - } - success = true; - } - return success; -} - -static bool spi_mem_view_progress_input_callback(InputEvent* event, void* context) { - SPIMemProgressView* app = context; - bool success = false; - SPIMemProgressViewType view_type; - with_view_model( - app->view, SPIMemProgressViewModel * model, { view_type = model->view_type; }, true); - if(view_type == SPIMemProgressViewTypeRead) { - success = spi_mem_view_progress_read_write_input_callback(event, app); - } else if(view_type == SPIMemProgressViewTypeVerify) { - success = spi_mem_view_progress_verify_input_callback(event, app); - } else if(view_type == SPIMemProgressViewTypeWrite) { - success = spi_mem_view_progress_read_write_input_callback(event, app); - } - return success; -} - -SPIMemProgressView* spi_mem_view_progress_alloc() { - SPIMemProgressView* app = malloc(sizeof(SPIMemProgressView)); - app->view = view_alloc(); - view_allocate_model(app->view, ViewModelTypeLocking, sizeof(SPIMemProgressViewModel)); - view_set_context(app->view, app); - view_set_draw_callback(app->view, spi_mem_view_progress_draw_callback); - view_set_input_callback(app->view, spi_mem_view_progress_input_callback); - spi_mem_view_progress_reset(app); - return app; -} - -void spi_mem_view_progress_free(SPIMemProgressView* app) { - view_free(app->view); - free(app); -} - -void spi_mem_view_progress_set_read_callback( - SPIMemProgressView* app, - SPIMemProgressViewCallback callback, - void* cb_ctx) { - app->callback = callback; - app->cb_ctx = cb_ctx; - with_view_model( - app->view, - SPIMemProgressViewModel * model, - { model->view_type = SPIMemProgressViewTypeRead; }, - true); -} - -void spi_mem_view_progress_set_verify_callback( - SPIMemProgressView* app, - SPIMemProgressViewCallback callback, - void* cb_ctx) { - app->callback = callback; - app->cb_ctx = cb_ctx; - with_view_model( - app->view, - SPIMemProgressViewModel * model, - { model->view_type = SPIMemProgressViewTypeVerify; }, - true); -} - -void spi_mem_view_progress_set_write_callback( - SPIMemProgressView* app, - SPIMemProgressViewCallback callback, - void* cb_ctx) { - app->callback = callback; - app->cb_ctx = cb_ctx; - with_view_model( - app->view, - SPIMemProgressViewModel * model, - { model->view_type = SPIMemProgressViewTypeWrite; }, - true); -} - -void spi_mem_view_progress_set_chip_size(SPIMemProgressView* app, size_t chip_size) { - with_view_model( - app->view, SPIMemProgressViewModel * model, { model->chip_size = chip_size; }, true); -} - -void spi_mem_view_progress_set_file_size(SPIMemProgressView* app, size_t file_size) { - with_view_model( - app->view, SPIMemProgressViewModel * model, { model->file_size = file_size; }, true); -} - -void spi_mem_view_progress_set_block_size(SPIMemProgressView* app, size_t block_size) { - with_view_model( - app->view, SPIMemProgressViewModel * model, { model->block_size = block_size; }, true); -} - -static size_t spi_mem_view_progress_set_total_size(SPIMemProgressViewModel* model) { - size_t total_size = model->chip_size; - if((model->chip_size > model->file_size) && model->view_type != SPIMemProgressViewTypeRead) { - total_size = model->file_size; - } - return total_size; -} - -void spi_mem_view_progress_inc_progress(SPIMemProgressView* app) { - with_view_model( - app->view, - SPIMemProgressViewModel * model, - { - size_t total_size = spi_mem_view_progress_set_total_size(model); - if(total_size == 0) total_size = 1; - model->blocks_written++; - model->progress = - ((float)model->block_size * (float)model->blocks_written) / ((float)total_size); - }, - true); -} - -void spi_mem_view_progress_reset(SPIMemProgressView* app) { - with_view_model( - app->view, - SPIMemProgressViewModel * model, - { - model->blocks_written = 0; - model->block_size = 0; - model->chip_size = 0; - model->file_size = 0; - model->progress = 0; - model->view_type = SPIMemProgressViewTypeUnknown; - }, - true); -} diff --git a/applications/external/spi_mem_manager/views/spi_mem_view_progress.h b/applications/external/spi_mem_manager/views/spi_mem_view_progress.h deleted file mode 100644 index 6a8645b6ce0..00000000000 --- a/applications/external/spi_mem_manager/views/spi_mem_view_progress.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once -#include - -typedef struct SPIMemProgressView SPIMemProgressView; -typedef void (*SPIMemProgressViewCallback)(void* context); - -View* spi_mem_view_progress_get_view(SPIMemProgressView* app); -SPIMemProgressView* spi_mem_view_progress_alloc(); -void spi_mem_view_progress_free(SPIMemProgressView* app); -void spi_mem_view_progress_set_read_callback( - SPIMemProgressView* app, - SPIMemProgressViewCallback callback, - void* cb_ctx); -void spi_mem_view_progress_set_verify_callback( - SPIMemProgressView* app, - SPIMemProgressViewCallback callback, - void* cb_ctx); -void spi_mem_view_progress_set_write_callback( - SPIMemProgressView* app, - SPIMemProgressViewCallback callback, - void* cb_ctx); -void spi_mem_view_progress_set_chip_size(SPIMemProgressView* app, size_t chip_size); -void spi_mem_view_progress_set_file_size(SPIMemProgressView* app, size_t file_size); -void spi_mem_view_progress_set_block_size(SPIMemProgressView* app, size_t block_size); -void spi_mem_view_progress_inc_progress(SPIMemProgressView* app); -void spi_mem_view_progress_reset(SPIMemProgressView* app); diff --git a/applications/external/weather_station/application.fam b/applications/external/weather_station/application.fam deleted file mode 100644 index 8dcaa1259b8..00000000000 --- a/applications/external/weather_station/application.fam +++ /dev/null @@ -1,13 +0,0 @@ -App( - appid="weather_station", - name="Weather Station", - apptype=FlipperAppType.EXTERNAL, - targets=["f7"], - entry_point="weather_station_app", - requires=["gui"], - stack_size=4 * 1024, - order=50, - fap_icon="weather_station_10px.png", - fap_category="Sub-GHz", - fap_icon_assets="images", -) diff --git a/applications/external/weather_station/helpers/weather_station_event.h b/applications/external/weather_station/helpers/weather_station_event.h deleted file mode 100644 index b0486183d9b..00000000000 --- a/applications/external/weather_station/helpers/weather_station_event.h +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - -typedef enum { - //WSCustomEvent - WSCustomEventStartId = 100, - - WSCustomEventSceneSettingLock, - - WSCustomEventViewReceiverOK, - WSCustomEventViewReceiverConfig, - WSCustomEventViewReceiverBack, - WSCustomEventViewReceiverOffDisplay, - WSCustomEventViewReceiverUnlock, -} WSCustomEvent; diff --git a/applications/external/weather_station/helpers/weather_station_types.h b/applications/external/weather_station/helpers/weather_station_types.h deleted file mode 100644 index 111465978b8..00000000000 --- a/applications/external/weather_station/helpers/weather_station_types.h +++ /dev/null @@ -1,49 +0,0 @@ -#pragma once - -#include -#include - -#define WS_VERSION_APP "0.8" -#define WS_DEVELOPED "SkorP" -#define WS_GITHUB "https://github.com/flipperdevices/flipperzero-firmware" - -#define WS_KEY_FILE_VERSION 1 -#define WS_KEY_FILE_TYPE "Flipper Weather Station Key File" - -/** WSRxKeyState state */ -typedef enum { - WSRxKeyStateIDLE, - WSRxKeyStateBack, - WSRxKeyStateStart, - WSRxKeyStateAddKey, -} WSRxKeyState; - -/** WSHopperState state */ -typedef enum { - WSHopperStateOFF, - WSHopperStateRunnig, - WSHopperStatePause, - WSHopperStateRSSITimeOut, -} WSHopperState; - -/** WSLock */ -typedef enum { - WSLockOff, - WSLockOn, -} WSLock; - -typedef enum { - WeatherStationViewVariableItemList, - WeatherStationViewSubmenu, - WeatherStationViewReceiver, - WeatherStationViewReceiverInfo, - WeatherStationViewWidget, -} WeatherStationView; - -/** WeatherStationTxRx state */ -typedef enum { - WSTxRxStateIDLE, - WSTxRxStateRx, - WSTxRxStateTx, - WSTxRxStateSleep, -} WSTxRxState; diff --git a/applications/external/weather_station/images/Humid_10x15.png b/applications/external/weather_station/images/Humid_10x15.png deleted file mode 100644 index 34b074e5f75150853ec976d7521e1663f0f30f1c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3624 zcmaJ@c{r4N`+r3CJxfSu3?VU#wV7rtGh^Sv$cV}qV@#UGm>Nro%2pkc?2V{oR}m7* zmL(xdWv`HM@E%K)@Q(AI&ini0*ZW-0^?dL9zV6TGUZ3mw#vgXFmJn4I1pq+8)&}Rw zJGW&iVSe6sMy(9R(=Dl3>|t9h7Q|#R{HdqN01z_Bb)(?jrWMeuqstikxX2s!3|Dz! zkSpd&q+F7wj+%(HU7T9(fV@kijHRW3N_$Qme?mg!Re2X(@ynv`g(lQ)CtSP}clpKo z$M8FWZ|hb+cWqX_Go30~;#TwsH3*BR+8DSPMT!?<_R4&?*w)heaROo|UP$Kim0LqJK- zk;|3<0S3tV+qWQq_j&-#*2CWhcu);AbW4ks1H$3}%q1>*KOhhe__V95hX9u{06D8g z57eIr%A}`sc%8~9N7ZN`ETg=H^@4;vJRp0uyKNN@$QcuN5HrmoO`#b|`cZ~bAC_JM zKu(f8uiB-JkZ#Gc?r!6RD#;UiGtUIKz`nlYo0C1oOmhJE$d2gU)P+_kM;;Q4q;1~b zH!l!yTrB7G>J|TTDf3DoXL`_MiMiby%iL=<0|S#26YuR>FkZwL9_KbGO(z;WHcowu zK>b)<`SA3UMwI@sC~JYW4^1zZ9rE_{To<|IJN!A(`bV|c)(_R!;1*lo8iJ18xQlF1 z0xt9Fl71dI9&>&F^L>3=exJs4*ZEDyjDQCxP5Hu;^a_rV_`lj~NfX!&pH=~2v6j*J zMq8LaGT`FJ9?sT+*@kt_J|NQH_IeNi9LH%u@GmON+JpfBmlLJ)z(QrYakp-R;GV{v z!;NA;e2gz)G+LT4(il;{$UQ8d{UsML+A&=ZRCRoyZ_HH<8(acnl9`f_CilmZXr|P6 zqHuPjc3qT+fJM9TE~46C9G~xHf_j3mVn+0uTBD7C>=g}AN1U7s*gna~2JU(p4|2Cr zT|~2XAY#3(o+KS=2lOxeh^e!N--s%ALBA2N#MTs;C||O=E%wTf4bMze$jN%edZdiL zYMeXusyIMuFwqp-25b1TTgag06b#bZjCpuaS0tI#`4C(pUfinu;7AF7ZTt$U=OITx zHp;R=#8`lX0TK6F*bp2DPVa3BKzlR{Wd=n|MEEbcG--j83+x|hK9Tv>vfEc59!s#% zRevj+xC<&B9*1o)(U6VD>TA_p+hP0gF1}B;&#I5^sy?k-m}O|Ate)I4=oeTngt(y# zI?x_H!JTNHFqlx8P+Rm8<@%Zj-CcA0r0x3Rq@B{F^rYdWAUR#%!u?LB>qtQ^UdAZ# zD5f;G%JsfWY{4$W)0v2_iwd^(d8M~gUMmME2CP!=e_=n78A;jel=jM_uXEb^OWGIy zWsbN+jQqv6IEuDX)^4HQ6eZ5?`{@q%lwMy^YQw`!;Irvd8B!SxcY;op&RO}S7osV4 zDVixNI#7IJ(Y>P4A~E+R_fC9b;c>TfWmfJ6ZsUa_Z&Hihi@1kp-BjEtg@+1aizo#Q zyxH9d&y9FN&t`{aXY5^smo#B&CWFU9~`o;+WG>MlG5Ty9Uml(Wy<}P_4a! zE-K7LU=8dHJStq5ZupxCji(2#-DEq7Oljw*Ek#@&m0Q^VX}`)nLx&nT**mZ(H7%7; zY*Xw~Y&~0VTsD`_y;pBp>$x5!Y0+k<<*j8+N$lRqopKv+8_5^VS8zllSIQtofq5#q zwK&c*dj5QR_S55$*$#~S(a`#-?|aTcH}D&@@A)g%;sn78aSg#C@$TKI=SD#clq$4s z=ua2yv1W5@9x;WO_VH3uO)u(Bzt!(nQdg<1-s2kMv{qW{9Zf+^HBEcR8OQldSI3%r z`|llcIONdQ^|I@B*V_!EEHwO`{#4df*1N2+YM-MaM|G~$ds|ytn=g}JSUI{w(F|2Qen^lq3G*>Wm zf8KbWIv+cH>!snX{n?%d!LORzu^(I}d(FgdrN9EmN+O)G&QX-gDRn3bn&eUX?m=}P zr)ZV9plJHllyz&|bR1wD^mF5U>t?1Zjj~KHAW*kAe7oKLs=^e%fkKw-KQgNeM6u2|uzMh?tj%g9( zBx=y)iQyBoR*1jn%YFivV0+4b4+5f7W=uczbnM66QtT)0C$aHx#dK)s5I%_8xkwgwORQClTeSpwJ=FarvDGVvY!wpdMeY(xLS`7teX5 zl||HRhB*dC9dCSbp|O%La8}G+bTazf?C`s}W6lJq=U652dkj~_R6hQ4ncR?Kn*90q z+QT7}DzS_g&oYK@JSr@1sqyRa@AIGjJgS%NC7D{3_Bl&W>X-Cc*w@OSac`0se*`M!}#;=46^@4QNQ-B-gu`iH#gRyRyL zo({S5xjXjz_mkIc*DF@d%HoTr*HYJM$4Z@OL33^Vef%3j>XKFOYTop#_M!2viEj_g zT1&S5_H>iGz|oU1mT>?5X6q+)CN6YhdR1g>b*}_+@XXcll8-{Ke>l?y-|njD;uC?2mnxTUVwI)g9{gUVO}6EFYTOStK z?1QEV#3wV>#`KSTY>!`$X13zy?aj_IMFnWYTL0|3?%wp?+_c5CQ{o;V9Sue}xU?cs{stNit3rR3x-0si!*9}7k| zF7WP^N^DC4+l}GR<`7wAz`~E=O9t7}h!nCbndlc9)IsLmI{CG!cmkW?=zt_KXb|GI z4ooWfsCDk^;$WkT01+rK7sHsvjEcVdMyNWMatyRGTms*)7ZoPYMep zA^gB*rXW-Zl1D%zvx%S(+9`T4G6W6&ixPOnyQ-g#j*kD^l}7u=JDBZC{%^kj zFL5wFlu3rVl7ktiStQ=<{MENZF_BmnnaF0a@C?SOpN%{mz+f|i0~kz@z5xUd(sm@0 zsPt{i{=XoOj!0X2Fq=pxk!^8kFpmU6rTQTeCRT>pAs#PEI$!NU%COWwJ) zwUsw;YlJ5m*y1ekA%mx0dWr%tVgBBZ4QT1}%17T|QZh+GD|>c8)x1$$<;$bFUFXY^ eH%$H@?+^!+6#;XN=3*rt9I&-?!j)lsF8mviygF|H diff --git a/applications/external/weather_station/images/Humid_8x13.png b/applications/external/weather_station/images/Humid_8x13.png deleted file mode 100644 index 6d8c71b000699e148e9480f7ddf9795b33e2fe7f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3618 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 diff --git a/applications/external/weather_station/images/Lock_7x8.png b/applications/external/weather_station/images/Lock_7x8.png deleted file mode 100644 index f7c9ca2c702f1b93d7d06bd12ae708655c79d7c8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3597 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 a9|QyVQ~@!E@+ey_4zMz}H7hmoyzn0`d`!~- diff --git a/applications/external/weather_station/images/Pin_back_arrow_10x8.png b/applications/external/weather_station/images/Pin_back_arrow_10x8.png deleted file mode 100644 index 3bafabd144864b575144c75b592e5eaf53974566..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3606 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 TFC+3}9cE|kU{!4CefEC<&8td2 diff --git a/applications/external/weather_station/images/Quest_7x8.png b/applications/external/weather_station/images/Quest_7x8.png deleted file mode 100644 index 6825247fbeaf98b4d0d9f8aa15d4f2c5f45dcf3c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3675 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 N!3~Mzlm%~0{|8*@s)qmo diff --git a/applications/external/weather_station/images/Therm_7x16.png b/applications/external/weather_station/images/Therm_7x16.png deleted file mode 100644 index 7c55500b7e7ea9112c92ba966643e3684887eb92..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3611 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 diff --git a/applications/external/weather_station/images/Timer_11x11.png b/applications/external/weather_station/images/Timer_11x11.png deleted file mode 100644 index 21ad47f4b2bf69d81929187ca63d7a3d1b111ea4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3616 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 Y4M;=-T9{t958 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 MrJY5ondim-0h+K&wEzGB diff --git a/applications/external/weather_station/images/WarningDolphin_45x42.png b/applications/external/weather_station/images/WarningDolphin_45x42.png deleted file mode 100644 index d766ffbb444db1739f2ccd030e506e8bada11ee8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1139 zcmaJ=TWAz#6rQ4%BpO*okt7zz3Bkm=bK9L=XV}qhW_HceY#KHzP4Z$UGyf(-b}pUy z<8ESW=_MtCk6tj|`k)VrCbo(SMH0n`eW+KIU`wG5O`$IeMg)Vzf7adDhv+af=lqBB zo%5Z`zqhqzdu2s+1%_dji6%LPq#u2o%9fzN?;7Dlq6)^^VVjkKImH23RI|DPo-mXi zkOGP}@Wrnnf?-SQ^>jOIPc{pxWsr*JL*@+|p)oA7EpIDoAAoo_=+RA)c=F3Qf$N$` ze9k55q%DD7y=l+^ZG$aob+Aw6HDcRVJdzhs00Te;&l_3O74jlch$|r7GgAa!aDjay z@rG1;vK5ys2jF3n@vAgV<6)izn!k6Qhd>D(EhD7l zcrhJ1i9|1iwm?z2T#n2INXzM=7@p@Tnx$CQk39VDfC-hn-*jtB5oF-1j&4KUGI1}W z(rxuakw9eMRAJc3%8 zlBq3$QTyJX$a6$&1ldyi4Pe5AEE32Sg@vDzY~7qR?1u@oXh zd9(fBtV<@eK%Tm=yy&p7{=h^#@1W)WFFSe!U5pP~o71uR`FW)7xc*=d5_b}EG@XBZ z@<7MRp-;+|o}SzJvMyfx>37Y4etBizf%0H*zaIF?2ObZt?$D;{o|esJ z*PgAOIO^*8tL%z2)|}k$DP5kN5}8qo*wNa8y?R2=%wO{gCD(`sHt}TzWQuK zMDIJap(nb2=7*ns*oDcRBbC23yy`kvKYV`ovU`a=EmxNv-90;;5r6+l8hQTp^u`Hn XX6*+%8gHC`h)Tl}u@-r>vFqE{F~5F& diff --git a/applications/external/weather_station/images/station_icon.png b/applications/external/weather_station/images/station_icon.png deleted file mode 100644 index b839eeb7aebfcf8aee9b2ba9570a3d9cf1d8af12..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3607 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 PjRq`CY_X+i_pAQ_NTe=6 diff --git a/applications/external/weather_station/protocols/acurite_592txr.c b/applications/external/weather_station/protocols/acurite_592txr.c deleted file mode 100644 index 874f6dd33bf..00000000000 --- a/applications/external/weather_station/protocols/acurite_592txr.c +++ /dev/null @@ -1,298 +0,0 @@ -#include "acurite_592txr.h" - -#define TAG "WSProtocolAcurite_592TXR" - -/* - * Help - * https://github.com/merbanan/rtl_433/blob/master/src/devices/acurite.c - * - * Acurite 592TXR Temperature Humidity sensor decoder - * Message Type 0x04, 7 bytes - * | Byte 0 | Byte 1 | Byte 2 | Byte 3 | Byte 4 | Byte 5 | Byte 6 | - * | --------- | --------- | --------- | --------- | --------- | --------- | --------- | - * | CCII IIII | IIII IIII | pB00 0100 | pHHH HHHH | p??T TTTT | pTTT TTTT | KKKK KKKK | - * - C: Channel 00: C, 10: B, 11: A, (01 is invalid) - * - I: Device ID (14 bits) - * - B: Battery, 1 is battery OK, 0 is battery low - * - M: Message type (6 bits), 0x04 - * - T: Temperature Celsius (11 - 14 bits?), + 1000 * 10 - * - H: Relative Humidity (%) (7 bits) - * - K: Checksum (8 bits) - * - p: Parity bit - * Notes: - * - Temperature - * - Encoded as Celsius + 1000 * 10 - * - only 11 bits needed for specified range -40 C to 70 C (-40 F - 158 F) - * - However 14 bits available for temperature, giving possible range of -100 C to 1538.4 C - * - @todo - check if high 3 bits ever used for anything else - * - */ - -static const SubGhzBlockConst ws_protocol_acurite_592txr_const = { - .te_short = 200, - .te_long = 400, - .te_delta = 90, - .min_count_bit_for_found = 56, -}; - -struct WSProtocolDecoderAcurite_592TXR { - SubGhzProtocolDecoderBase base; - - SubGhzBlockDecoder decoder; - WSBlockGeneric generic; - - uint16_t header_count; -}; - -struct WSProtocolEncoderAcurite_592TXR { - SubGhzProtocolEncoderBase base; - - SubGhzProtocolBlockEncoder encoder; - WSBlockGeneric generic; -}; - -typedef enum { - Acurite_592TXRDecoderStepReset = 0, - Acurite_592TXRDecoderStepCheckPreambule, - Acurite_592TXRDecoderStepSaveDuration, - Acurite_592TXRDecoderStepCheckDuration, -} Acurite_592TXRDecoderStep; - -const SubGhzProtocolDecoder ws_protocol_acurite_592txr_decoder = { - .alloc = ws_protocol_decoder_acurite_592txr_alloc, - .free = ws_protocol_decoder_acurite_592txr_free, - - .feed = ws_protocol_decoder_acurite_592txr_feed, - .reset = ws_protocol_decoder_acurite_592txr_reset, - - .get_hash_data = ws_protocol_decoder_acurite_592txr_get_hash_data, - .serialize = ws_protocol_decoder_acurite_592txr_serialize, - .deserialize = ws_protocol_decoder_acurite_592txr_deserialize, - .get_string = ws_protocol_decoder_acurite_592txr_get_string, -}; - -const SubGhzProtocolEncoder ws_protocol_acurite_592txr_encoder = { - .alloc = NULL, - .free = NULL, - - .deserialize = NULL, - .stop = NULL, - .yield = NULL, -}; - -const SubGhzProtocol ws_protocol_acurite_592txr = { - .name = WS_PROTOCOL_ACURITE_592TXR_NAME, - .type = SubGhzProtocolWeatherStation, - .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | - SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable, - - .decoder = &ws_protocol_acurite_592txr_decoder, - .encoder = &ws_protocol_acurite_592txr_encoder, -}; - -void* ws_protocol_decoder_acurite_592txr_alloc(SubGhzEnvironment* environment) { - UNUSED(environment); - WSProtocolDecoderAcurite_592TXR* instance = malloc(sizeof(WSProtocolDecoderAcurite_592TXR)); - instance->base.protocol = &ws_protocol_acurite_592txr; - instance->generic.protocol_name = instance->base.protocol->name; - return instance; -} - -void ws_protocol_decoder_acurite_592txr_free(void* context) { - furi_assert(context); - WSProtocolDecoderAcurite_592TXR* instance = context; - free(instance); -} - -void ws_protocol_decoder_acurite_592txr_reset(void* context) { - furi_assert(context); - WSProtocolDecoderAcurite_592TXR* instance = context; - instance->decoder.parser_step = Acurite_592TXRDecoderStepReset; -} - -static bool ws_protocol_acurite_592txr_check_crc(WSProtocolDecoderAcurite_592TXR* instance) { - uint8_t msg[] = { - instance->decoder.decode_data >> 48, - instance->decoder.decode_data >> 40, - instance->decoder.decode_data >> 32, - instance->decoder.decode_data >> 24, - instance->decoder.decode_data >> 16, - instance->decoder.decode_data >> 8}; - - if((subghz_protocol_blocks_add_bytes(msg, 6) == - (uint8_t)(instance->decoder.decode_data & 0xFF)) && - (!subghz_protocol_blocks_parity_bytes(&msg[2], 4))) { - return true; - } else { - return false; - } -} - -/** - * Analysis of received data - * @param instance Pointer to a WSBlockGeneric* instance - */ -static void ws_protocol_acurite_592txr_remote_controller(WSBlockGeneric* instance) { - uint8_t channel[] = {3, 0, 2, 1}; - uint8_t channel_raw = ((instance->data >> 54) & 0x03); - instance->channel = channel[channel_raw]; - instance->id = (instance->data >> 40) & 0x3FFF; - instance->battery_low = !((instance->data >> 38) & 1); - instance->humidity = (instance->data >> 24) & 0x7F; - - uint16_t temp_raw = ((instance->data >> 9) & 0xF80) | ((instance->data >> 8) & 0x7F); - instance->temp = ((float)(temp_raw)-1000) / 10.0f; - - instance->btn = WS_NO_BTN; -} - -void ws_protocol_decoder_acurite_592txr_feed(void* context, bool level, uint32_t duration) { - furi_assert(context); - WSProtocolDecoderAcurite_592TXR* instance = context; - - switch(instance->decoder.parser_step) { - case Acurite_592TXRDecoderStepReset: - if((level) && (DURATION_DIFF(duration, ws_protocol_acurite_592txr_const.te_short * 3) < - ws_protocol_acurite_592txr_const.te_delta * 2)) { - instance->decoder.parser_step = Acurite_592TXRDecoderStepCheckPreambule; - instance->decoder.te_last = duration; - instance->header_count = 0; - } - break; - - case Acurite_592TXRDecoderStepCheckPreambule: - if(level) { - instance->decoder.te_last = duration; - } else { - if((DURATION_DIFF( - instance->decoder.te_last, ws_protocol_acurite_592txr_const.te_short * 3) < - ws_protocol_acurite_592txr_const.te_delta * 2) && - (DURATION_DIFF(duration, ws_protocol_acurite_592txr_const.te_short * 3) < - ws_protocol_acurite_592txr_const.te_delta * 2)) { - //Found preambule - instance->header_count++; - } else if((instance->header_count > 2) && (instance->header_count < 5)) { - if((DURATION_DIFF( - instance->decoder.te_last, ws_protocol_acurite_592txr_const.te_short) < - ws_protocol_acurite_592txr_const.te_delta) && - (DURATION_DIFF(duration, ws_protocol_acurite_592txr_const.te_long) < - ws_protocol_acurite_592txr_const.te_delta)) { - instance->decoder.decode_data = 0; - instance->decoder.decode_count_bit = 0; - subghz_protocol_blocks_add_bit(&instance->decoder, 0); - instance->decoder.parser_step = Acurite_592TXRDecoderStepSaveDuration; - } else if( - (DURATION_DIFF( - instance->decoder.te_last, ws_protocol_acurite_592txr_const.te_long) < - ws_protocol_acurite_592txr_const.te_delta) && - (DURATION_DIFF(duration, ws_protocol_acurite_592txr_const.te_short) < - ws_protocol_acurite_592txr_const.te_delta)) { - instance->decoder.decode_data = 0; - instance->decoder.decode_count_bit = 0; - subghz_protocol_blocks_add_bit(&instance->decoder, 1); - instance->decoder.parser_step = Acurite_592TXRDecoderStepSaveDuration; - } else { - instance->decoder.parser_step = Acurite_592TXRDecoderStepReset; - } - } else { - instance->decoder.parser_step = Acurite_592TXRDecoderStepReset; - } - } - break; - - case Acurite_592TXRDecoderStepSaveDuration: - if(level) { - instance->decoder.te_last = duration; - instance->decoder.parser_step = Acurite_592TXRDecoderStepCheckDuration; - } else { - instance->decoder.parser_step = Acurite_592TXRDecoderStepReset; - } - break; - - case Acurite_592TXRDecoderStepCheckDuration: - if(!level) { - if(duration >= ((uint32_t)ws_protocol_acurite_592txr_const.te_short * 5)) { - if((instance->decoder.decode_count_bit == - ws_protocol_acurite_592txr_const.min_count_bit_for_found) && - ws_protocol_acurite_592txr_check_crc(instance)) { - instance->generic.data = instance->decoder.decode_data; - instance->generic.data_count_bit = instance->decoder.decode_count_bit; - ws_protocol_acurite_592txr_remote_controller(&instance->generic); - if(instance->base.callback) - instance->base.callback(&instance->base, instance->base.context); - } - instance->decoder.decode_data = 0; - instance->decoder.decode_count_bit = 0; - instance->decoder.parser_step = Acurite_592TXRDecoderStepReset; - break; - } else if( - (DURATION_DIFF( - instance->decoder.te_last, ws_protocol_acurite_592txr_const.te_short) < - ws_protocol_acurite_592txr_const.te_delta) && - (DURATION_DIFF(duration, ws_protocol_acurite_592txr_const.te_long) < - ws_protocol_acurite_592txr_const.te_delta)) { - subghz_protocol_blocks_add_bit(&instance->decoder, 0); - instance->decoder.parser_step = Acurite_592TXRDecoderStepSaveDuration; - } else if( - (DURATION_DIFF( - instance->decoder.te_last, ws_protocol_acurite_592txr_const.te_long) < - ws_protocol_acurite_592txr_const.te_delta) && - (DURATION_DIFF(duration, ws_protocol_acurite_592txr_const.te_short) < - ws_protocol_acurite_592txr_const.te_delta)) { - subghz_protocol_blocks_add_bit(&instance->decoder, 1); - instance->decoder.parser_step = Acurite_592TXRDecoderStepSaveDuration; - } else { - instance->decoder.parser_step = Acurite_592TXRDecoderStepReset; - } - } else { - instance->decoder.parser_step = Acurite_592TXRDecoderStepReset; - } - break; - } -} - -uint8_t ws_protocol_decoder_acurite_592txr_get_hash_data(void* context) { - furi_assert(context); - WSProtocolDecoderAcurite_592TXR* instance = context; - return subghz_protocol_blocks_get_hash_data( - &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); -} - -SubGhzProtocolStatus ws_protocol_decoder_acurite_592txr_serialize( - void* context, - FlipperFormat* flipper_format, - SubGhzRadioPreset* preset) { - furi_assert(context); - WSProtocolDecoderAcurite_592TXR* instance = context; - return ws_block_generic_serialize(&instance->generic, flipper_format, preset); -} - -SubGhzProtocolStatus - ws_protocol_decoder_acurite_592txr_deserialize(void* context, FlipperFormat* flipper_format) { - furi_assert(context); - WSProtocolDecoderAcurite_592TXR* instance = context; - return ws_block_generic_deserialize_check_count_bit( - &instance->generic, - flipper_format, - ws_protocol_acurite_592txr_const.min_count_bit_for_found); -} - -void ws_protocol_decoder_acurite_592txr_get_string(void* context, FuriString* output) { - furi_assert(context); - WSProtocolDecoderAcurite_592TXR* instance = context; - furi_string_printf( - output, - "%s %dbit\r\n" - "Key:0x%lX%08lX\r\n" - "Sn:0x%lX Ch:%d Bat:%d\r\n" - "Temp:%3.1f C Hum:%d%%", - instance->generic.protocol_name, - instance->generic.data_count_bit, - (uint32_t)(instance->generic.data >> 32), - (uint32_t)(instance->generic.data), - instance->generic.id, - instance->generic.channel, - instance->generic.battery_low, - (double)instance->generic.temp, - instance->generic.humidity); -} diff --git a/applications/external/weather_station/protocols/acurite_592txr.h b/applications/external/weather_station/protocols/acurite_592txr.h deleted file mode 100644 index 1071d1cf77a..00000000000 --- a/applications/external/weather_station/protocols/acurite_592txr.h +++ /dev/null @@ -1,80 +0,0 @@ -#pragma once - -#include - -#include -#include -#include -#include "ws_generic.h" -#include - -#define WS_PROTOCOL_ACURITE_592TXR_NAME "Acurite 592TXR" - -typedef struct WSProtocolDecoderAcurite_592TXR WSProtocolDecoderAcurite_592TXR; -typedef struct WSProtocolEncoderAcurite_592TXR WSProtocolEncoderAcurite_592TXR; - -extern const SubGhzProtocolDecoder ws_protocol_acurite_592txr_decoder; -extern const SubGhzProtocolEncoder ws_protocol_acurite_592txr_encoder; -extern const SubGhzProtocol ws_protocol_acurite_592txr; - -/** - * Allocate WSProtocolDecoderAcurite_592TXR. - * @param environment Pointer to a SubGhzEnvironment instance - * @return WSProtocolDecoderAcurite_592TXR* pointer to a WSProtocolDecoderAcurite_592TXR instance - */ -void* ws_protocol_decoder_acurite_592txr_alloc(SubGhzEnvironment* environment); - -/** - * Free WSProtocolDecoderAcurite_592TXR. - * @param context Pointer to a WSProtocolDecoderAcurite_592TXR instance - */ -void ws_protocol_decoder_acurite_592txr_free(void* context); - -/** - * Reset decoder WSProtocolDecoderAcurite_592TXR. - * @param context Pointer to a WSProtocolDecoderAcurite_592TXR instance - */ -void ws_protocol_decoder_acurite_592txr_reset(void* context); - -/** - * Parse a raw sequence of levels and durations received from the air. - * @param context Pointer to a WSProtocolDecoderAcurite_592TXR instance - * @param level Signal level true-high false-low - * @param duration Duration of this level in, us - */ -void ws_protocol_decoder_acurite_592txr_feed(void* context, bool level, uint32_t duration); - -/** - * Getting the hash sum of the last randomly received parcel. - * @param context Pointer to a WSProtocolDecoderAcurite_592TXR instance - * @return hash Hash sum - */ -uint8_t ws_protocol_decoder_acurite_592txr_get_hash_data(void* context); - -/** - * Serialize data WSProtocolDecoderAcurite_592TXR. - * @param context Pointer to a WSProtocolDecoderAcurite_592TXR instance - * @param flipper_format Pointer to a FlipperFormat instance - * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return status - */ -SubGhzProtocolStatus ws_protocol_decoder_acurite_592txr_serialize( - void* context, - FlipperFormat* flipper_format, - SubGhzRadioPreset* preset); - -/** - * Deserialize data WSProtocolDecoderAcurite_592TXR. - * @param context Pointer to a WSProtocolDecoderAcurite_592TXR instance - * @param flipper_format Pointer to a FlipperFormat instance - * @return status - */ -SubGhzProtocolStatus - ws_protocol_decoder_acurite_592txr_deserialize(void* context, FlipperFormat* flipper_format); - -/** - * Getting a textual representation of the received data. - * @param context Pointer to a WSProtocolDecoderAcurite_592TXR instance - * @param output Resulting text - */ -void ws_protocol_decoder_acurite_592txr_get_string(void* context, FuriString* output); diff --git a/applications/external/weather_station/protocols/acurite_606tx.c b/applications/external/weather_station/protocols/acurite_606tx.c deleted file mode 100644 index e0d405c6623..00000000000 --- a/applications/external/weather_station/protocols/acurite_606tx.c +++ /dev/null @@ -1,239 +0,0 @@ -#include "acurite_606tx.h" - -#define TAG "WSProtocolAcurite_606TX" - -/* - * Help - * https://github.com/merbanan/rtl_433/blob/5bef4e43133ac4c0e2d18d36f87c52b4f9458453/src/devices/acurite.c#L1644 - * - * 0000 1111 | 0011 0000 | 0101 1100 | 1110 0111 - * iiii iiii | buuu tttt | tttt tttt | cccc cccc - * - i: identification; changes on battery switch - * - c: lfsr_digest8; - * - u: unknown; - * - b: battery low; flag to indicate low battery voltage - * - t: Temperature; in °C - * - */ - -static const SubGhzBlockConst ws_protocol_acurite_606tx_const = { - .te_short = 500, - .te_long = 2000, - .te_delta = 150, - .min_count_bit_for_found = 32, -}; - -struct WSProtocolDecoderAcurite_606TX { - SubGhzProtocolDecoderBase base; - - SubGhzBlockDecoder decoder; - WSBlockGeneric generic; -}; - -struct WSProtocolEncoderAcurite_606TX { - SubGhzProtocolEncoderBase base; - - SubGhzProtocolBlockEncoder encoder; - WSBlockGeneric generic; -}; - -typedef enum { - Acurite_606TXDecoderStepReset = 0, - Acurite_606TXDecoderStepSaveDuration, - Acurite_606TXDecoderStepCheckDuration, -} Acurite_606TXDecoderStep; - -const SubGhzProtocolDecoder ws_protocol_acurite_606tx_decoder = { - .alloc = ws_protocol_decoder_acurite_606tx_alloc, - .free = ws_protocol_decoder_acurite_606tx_free, - - .feed = ws_protocol_decoder_acurite_606tx_feed, - .reset = ws_protocol_decoder_acurite_606tx_reset, - - .get_hash_data = ws_protocol_decoder_acurite_606tx_get_hash_data, - .serialize = ws_protocol_decoder_acurite_606tx_serialize, - .deserialize = ws_protocol_decoder_acurite_606tx_deserialize, - .get_string = ws_protocol_decoder_acurite_606tx_get_string, -}; - -const SubGhzProtocolEncoder ws_protocol_acurite_606tx_encoder = { - .alloc = NULL, - .free = NULL, - - .deserialize = NULL, - .stop = NULL, - .yield = NULL, -}; - -const SubGhzProtocol ws_protocol_acurite_606tx = { - .name = WS_PROTOCOL_ACURITE_606TX_NAME, - .type = SubGhzProtocolWeatherStation, - .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | - SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable, - - .decoder = &ws_protocol_acurite_606tx_decoder, - .encoder = &ws_protocol_acurite_606tx_encoder, -}; - -void* ws_protocol_decoder_acurite_606tx_alloc(SubGhzEnvironment* environment) { - UNUSED(environment); - WSProtocolDecoderAcurite_606TX* instance = malloc(sizeof(WSProtocolDecoderAcurite_606TX)); - instance->base.protocol = &ws_protocol_acurite_606tx; - instance->generic.protocol_name = instance->base.protocol->name; - return instance; -} - -void ws_protocol_decoder_acurite_606tx_free(void* context) { - furi_assert(context); - WSProtocolDecoderAcurite_606TX* instance = context; - free(instance); -} - -void ws_protocol_decoder_acurite_606tx_reset(void* context) { - furi_assert(context); - WSProtocolDecoderAcurite_606TX* instance = context; - instance->decoder.parser_step = Acurite_606TXDecoderStepReset; -} - -static bool ws_protocol_acurite_606tx_check(WSProtocolDecoderAcurite_606TX* instance) { - if(!instance->decoder.decode_data) return false; - uint8_t msg[] = { - instance->decoder.decode_data >> 24, - instance->decoder.decode_data >> 16, - instance->decoder.decode_data >> 8}; - - uint8_t crc = subghz_protocol_blocks_lfsr_digest8(msg, 3, 0x98, 0xF1); - return (crc == (instance->decoder.decode_data & 0xFF)); -} - -/** - * Analysis of received data - * @param instance Pointer to a WSBlockGeneric* instance - */ -static void ws_protocol_acurite_606tx_remote_controller(WSBlockGeneric* instance) { - instance->id = (instance->data >> 24) & 0xFF; - instance->battery_low = (instance->data >> 23) & 1; - - instance->channel = WS_NO_CHANNEL; - - if(!((instance->data >> 19) & 1)) { - instance->temp = (float)((instance->data >> 8) & 0x07FF) / 10.0f; - } else { - instance->temp = (float)((~(instance->data >> 8) & 0x07FF) + 1) / -10.0f; - } - instance->btn = WS_NO_BTN; - instance->humidity = WS_NO_HUMIDITY; -} - -void ws_protocol_decoder_acurite_606tx_feed(void* context, bool level, uint32_t duration) { - furi_assert(context); - WSProtocolDecoderAcurite_606TX* instance = context; - - switch(instance->decoder.parser_step) { - case Acurite_606TXDecoderStepReset: - if((!level) && (DURATION_DIFF(duration, ws_protocol_acurite_606tx_const.te_short * 17) < - ws_protocol_acurite_606tx_const.te_delta * 8)) { - //Found syncPrefix - instance->decoder.parser_step = Acurite_606TXDecoderStepSaveDuration; - instance->decoder.decode_data = 0; - instance->decoder.decode_count_bit = 0; - } - break; - - case Acurite_606TXDecoderStepSaveDuration: - if(level) { - instance->decoder.te_last = duration; - instance->decoder.parser_step = Acurite_606TXDecoderStepCheckDuration; - } else { - instance->decoder.parser_step = Acurite_606TXDecoderStepReset; - } - break; - - case Acurite_606TXDecoderStepCheckDuration: - if(!level) { - if(DURATION_DIFF(instance->decoder.te_last, ws_protocol_acurite_606tx_const.te_short) < - ws_protocol_acurite_606tx_const.te_delta) { - if((DURATION_DIFF(duration, ws_protocol_acurite_606tx_const.te_short) < - ws_protocol_acurite_606tx_const.te_delta) || - (duration > ws_protocol_acurite_606tx_const.te_long * 3)) { - //Found syncPostfix - instance->decoder.parser_step = Acurite_606TXDecoderStepReset; - if((instance->decoder.decode_count_bit == - ws_protocol_acurite_606tx_const.min_count_bit_for_found) && - ws_protocol_acurite_606tx_check(instance)) { - instance->generic.data = instance->decoder.decode_data; - instance->generic.data_count_bit = instance->decoder.decode_count_bit; - ws_protocol_acurite_606tx_remote_controller(&instance->generic); - if(instance->base.callback) - instance->base.callback(&instance->base, instance->base.context); - } - instance->decoder.decode_data = 0; - instance->decoder.decode_count_bit = 0; - } else if( - DURATION_DIFF(duration, ws_protocol_acurite_606tx_const.te_long) < - ws_protocol_acurite_606tx_const.te_delta * 2) { - subghz_protocol_blocks_add_bit(&instance->decoder, 0); - instance->decoder.parser_step = Acurite_606TXDecoderStepSaveDuration; - } else if( - DURATION_DIFF(duration, ws_protocol_acurite_606tx_const.te_long * 2) < - ws_protocol_acurite_606tx_const.te_delta * 4) { - subghz_protocol_blocks_add_bit(&instance->decoder, 1); - instance->decoder.parser_step = Acurite_606TXDecoderStepSaveDuration; - } else { - instance->decoder.parser_step = Acurite_606TXDecoderStepReset; - } - } else { - instance->decoder.parser_step = Acurite_606TXDecoderStepReset; - } - } else { - instance->decoder.parser_step = Acurite_606TXDecoderStepReset; - } - break; - } -} - -uint8_t ws_protocol_decoder_acurite_606tx_get_hash_data(void* context) { - furi_assert(context); - WSProtocolDecoderAcurite_606TX* instance = context; - return subghz_protocol_blocks_get_hash_data( - &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); -} - -SubGhzProtocolStatus ws_protocol_decoder_acurite_606tx_serialize( - void* context, - FlipperFormat* flipper_format, - SubGhzRadioPreset* preset) { - furi_assert(context); - WSProtocolDecoderAcurite_606TX* instance = context; - return ws_block_generic_serialize(&instance->generic, flipper_format, preset); -} - -SubGhzProtocolStatus - ws_protocol_decoder_acurite_606tx_deserialize(void* context, FlipperFormat* flipper_format) { - furi_assert(context); - WSProtocolDecoderAcurite_606TX* instance = context; - return ws_block_generic_deserialize_check_count_bit( - &instance->generic, - flipper_format, - ws_protocol_acurite_606tx_const.min_count_bit_for_found); -} - -void ws_protocol_decoder_acurite_606tx_get_string(void* context, FuriString* output) { - furi_assert(context); - WSProtocolDecoderAcurite_606TX* instance = context; - furi_string_printf( - output, - "%s %dbit\r\n" - "Key:0x%lX%08lX\r\n" - "Sn:0x%lX Ch:%d Bat:%d\r\n" - "Temp:%3.1f C Hum:%d%%", - instance->generic.protocol_name, - instance->generic.data_count_bit, - (uint32_t)(instance->generic.data >> 32), - (uint32_t)(instance->generic.data), - instance->generic.id, - instance->generic.channel, - instance->generic.battery_low, - (double)instance->generic.temp, - instance->generic.humidity); -} diff --git a/applications/external/weather_station/protocols/acurite_606tx.h b/applications/external/weather_station/protocols/acurite_606tx.h deleted file mode 100644 index 15b6d1eb50d..00000000000 --- a/applications/external/weather_station/protocols/acurite_606tx.h +++ /dev/null @@ -1,80 +0,0 @@ -#pragma once - -#include - -#include -#include -#include -#include "ws_generic.h" -#include - -#define WS_PROTOCOL_ACURITE_606TX_NAME "Acurite-606TX" - -typedef struct WSProtocolDecoderAcurite_606TX WSProtocolDecoderAcurite_606TX; -typedef struct WSProtocolEncoderAcurite_606TX WSProtocolEncoderAcurite_606TX; - -extern const SubGhzProtocolDecoder ws_protocol_acurite_606tx_decoder; -extern const SubGhzProtocolEncoder ws_protocol_acurite_606tx_encoder; -extern const SubGhzProtocol ws_protocol_acurite_606tx; - -/** - * Allocate WSProtocolDecoderAcurite_606TX. - * @param environment Pointer to a SubGhzEnvironment instance - * @return WSProtocolDecoderAcurite_606TX* pointer to a WSProtocolDecoderAcurite_606TX instance - */ -void* ws_protocol_decoder_acurite_606tx_alloc(SubGhzEnvironment* environment); - -/** - * Free WSProtocolDecoderAcurite_606TX. - * @param context Pointer to a WSProtocolDecoderAcurite_606TX instance - */ -void ws_protocol_decoder_acurite_606tx_free(void* context); - -/** - * Reset decoder WSProtocolDecoderAcurite_606TX. - * @param context Pointer to a WSProtocolDecoderAcurite_606TX instance - */ -void ws_protocol_decoder_acurite_606tx_reset(void* context); - -/** - * Parse a raw sequence of levels and durations received from the air. - * @param context Pointer to a WSProtocolDecoderAcurite_606TX instance - * @param level Signal level true-high false-low - * @param duration Duration of this level in, us - */ -void ws_protocol_decoder_acurite_606tx_feed(void* context, bool level, uint32_t duration); - -/** - * Getting the hash sum of the last randomly received parcel. - * @param context Pointer to a WSProtocolDecoderAcurite_606TX instance - * @return hash Hash sum - */ -uint8_t ws_protocol_decoder_acurite_606tx_get_hash_data(void* context); - -/** - * Serialize data WSProtocolDecoderAcurite_606TX. - * @param context Pointer to a WSProtocolDecoderAcurite_606TX instance - * @param flipper_format Pointer to a FlipperFormat instance - * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return status - */ -SubGhzProtocolStatus ws_protocol_decoder_acurite_606tx_serialize( - void* context, - FlipperFormat* flipper_format, - SubGhzRadioPreset* preset); - -/** - * Deserialize data WSProtocolDecoderAcurite_606TX. - * @param context Pointer to a WSProtocolDecoderAcurite_606TX instance - * @param flipper_format Pointer to a FlipperFormat instance - * @return status - */ -SubGhzProtocolStatus - ws_protocol_decoder_acurite_606tx_deserialize(void* context, FlipperFormat* flipper_format); - -/** - * Getting a textual representation of the received data. - * @param context Pointer to a WSProtocolDecoderAcurite_606TX instance - * @param output Resulting text - */ -void ws_protocol_decoder_acurite_606tx_get_string(void* context, FuriString* output); diff --git a/applications/external/weather_station/protocols/acurite_609txc.c b/applications/external/weather_station/protocols/acurite_609txc.c deleted file mode 100644 index 853b78446cb..00000000000 --- a/applications/external/weather_station/protocols/acurite_609txc.c +++ /dev/null @@ -1,239 +0,0 @@ -#include "acurite_609txc.h" - -#define TAG "WSProtocolAcurite_609TXC" - -/* - * Help - * https://github.com/merbanan/rtl_433/blob/5bef4e43133ac4c0e2d18d36f87c52b4f9458453/src/devices/acurite.c#L216 - * - * 0000 1111 | 0011 0000 | 0101 1100 | 0000 0000 | 1110 0111 - * iiii iiii | buuu tttt | tttt tttt | hhhh hhhh | cccc cccc - * - i: identification; changes on battery switch - * - c: checksum (sum of previous by bytes) - * - u: unknown - * - b: battery low; flag to indicate low battery voltage - * - t: temperature; in °C * 10, 12 bit with complement - * - h: humidity - * - */ - -static const SubGhzBlockConst ws_protocol_acurite_609txc_const = { - .te_short = 500, - .te_long = 1000, - .te_delta = 150, - .min_count_bit_for_found = 40, -}; - -struct WSProtocolDecoderAcurite_609TXC { - SubGhzProtocolDecoderBase base; - - SubGhzBlockDecoder decoder; - WSBlockGeneric generic; -}; - -struct WSProtocolEncoderAcurite_609TXC { - SubGhzProtocolEncoderBase base; - - SubGhzProtocolBlockEncoder encoder; - WSBlockGeneric generic; -}; - -typedef enum { - Acurite_609TXCDecoderStepReset = 0, - Acurite_609TXCDecoderStepSaveDuration, - Acurite_609TXCDecoderStepCheckDuration, -} Acurite_609TXCDecoderStep; - -const SubGhzProtocolDecoder ws_protocol_acurite_609txc_decoder = { - .alloc = ws_protocol_decoder_acurite_609txc_alloc, - .free = ws_protocol_decoder_acurite_609txc_free, - - .feed = ws_protocol_decoder_acurite_609txc_feed, - .reset = ws_protocol_decoder_acurite_609txc_reset, - - .get_hash_data = ws_protocol_decoder_acurite_609txc_get_hash_data, - .serialize = ws_protocol_decoder_acurite_609txc_serialize, - .deserialize = ws_protocol_decoder_acurite_609txc_deserialize, - .get_string = ws_protocol_decoder_acurite_609txc_get_string, -}; - -const SubGhzProtocolEncoder ws_protocol_acurite_609txc_encoder = { - .alloc = NULL, - .free = NULL, - - .deserialize = NULL, - .stop = NULL, - .yield = NULL, -}; - -const SubGhzProtocol ws_protocol_acurite_609txc = { - .name = WS_PROTOCOL_ACURITE_609TXC_NAME, - .type = SubGhzProtocolWeatherStation, - .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | - SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable, - - .decoder = &ws_protocol_acurite_609txc_decoder, - .encoder = &ws_protocol_acurite_609txc_encoder, -}; - -void* ws_protocol_decoder_acurite_609txc_alloc(SubGhzEnvironment* environment) { - UNUSED(environment); - WSProtocolDecoderAcurite_609TXC* instance = malloc(sizeof(WSProtocolDecoderAcurite_609TXC)); - instance->base.protocol = &ws_protocol_acurite_609txc; - instance->generic.protocol_name = instance->base.protocol->name; - return instance; -} - -void ws_protocol_decoder_acurite_609txc_free(void* context) { - furi_assert(context); - WSProtocolDecoderAcurite_609TXC* instance = context; - free(instance); -} - -void ws_protocol_decoder_acurite_609txc_reset(void* context) { - furi_assert(context); - WSProtocolDecoderAcurite_609TXC* instance = context; - instance->decoder.parser_step = Acurite_609TXCDecoderStepReset; -} - -static bool ws_protocol_acurite_609txc_check(WSProtocolDecoderAcurite_609TXC* instance) { - if(!instance->decoder.decode_data) return false; - uint8_t crc = (uint8_t)(instance->decoder.decode_data >> 32) + - (uint8_t)(instance->decoder.decode_data >> 24) + - (uint8_t)(instance->decoder.decode_data >> 16) + - (uint8_t)(instance->decoder.decode_data >> 8); - return (crc == (instance->decoder.decode_data & 0xFF)); -} - -/** - * Analysis of received data - * @param instance Pointer to a WSBlockGeneric* instance - */ -static void ws_protocol_acurite_609txc_remote_controller(WSBlockGeneric* instance) { - instance->id = (instance->data >> 32) & 0xFF; - instance->battery_low = (instance->data >> 31) & 1; - - instance->channel = WS_NO_CHANNEL; - - // Temperature in Celsius is encoded as a 12 bit integer value - // multiplied by 10 using the 4th - 6th nybbles (bytes 1 & 2) - // negative values are recovered by sign extend from int16_t. - int16_t temp_raw = - (int16_t)(((instance->data >> 12) & 0xf000) | ((instance->data >> 16) << 4)); - instance->temp = (temp_raw >> 4) * 0.1f; - instance->humidity = (instance->data >> 8) & 0xff; - instance->btn = WS_NO_BTN; -} - -void ws_protocol_decoder_acurite_609txc_feed(void* context, bool level, uint32_t duration) { - furi_assert(context); - WSProtocolDecoderAcurite_609TXC* instance = context; - - switch(instance->decoder.parser_step) { - case Acurite_609TXCDecoderStepReset: - if((!level) && (DURATION_DIFF(duration, ws_protocol_acurite_609txc_const.te_short * 17) < - ws_protocol_acurite_609txc_const.te_delta * 8)) { - //Found syncPrefix - instance->decoder.parser_step = Acurite_609TXCDecoderStepSaveDuration; - instance->decoder.decode_data = 0; - instance->decoder.decode_count_bit = 0; - } - break; - - case Acurite_609TXCDecoderStepSaveDuration: - if(level) { - instance->decoder.te_last = duration; - instance->decoder.parser_step = Acurite_609TXCDecoderStepCheckDuration; - } else { - instance->decoder.parser_step = Acurite_609TXCDecoderStepReset; - } - break; - - case Acurite_609TXCDecoderStepCheckDuration: - if(!level) { - if(DURATION_DIFF(instance->decoder.te_last, ws_protocol_acurite_609txc_const.te_short) < - ws_protocol_acurite_609txc_const.te_delta) { - if((DURATION_DIFF(duration, ws_protocol_acurite_609txc_const.te_short) < - ws_protocol_acurite_609txc_const.te_delta) || - (duration > ws_protocol_acurite_609txc_const.te_long * 3)) { - //Found syncPostfix - instance->decoder.parser_step = Acurite_609TXCDecoderStepReset; - if((instance->decoder.decode_count_bit == - ws_protocol_acurite_609txc_const.min_count_bit_for_found) && - ws_protocol_acurite_609txc_check(instance)) { - instance->generic.data = instance->decoder.decode_data; - instance->generic.data_count_bit = instance->decoder.decode_count_bit; - ws_protocol_acurite_609txc_remote_controller(&instance->generic); - if(instance->base.callback) - instance->base.callback(&instance->base, instance->base.context); - } - instance->decoder.decode_data = 0; - instance->decoder.decode_count_bit = 0; - } else if( - DURATION_DIFF(duration, ws_protocol_acurite_609txc_const.te_long) < - ws_protocol_acurite_609txc_const.te_delta * 2) { - subghz_protocol_blocks_add_bit(&instance->decoder, 0); - instance->decoder.parser_step = Acurite_609TXCDecoderStepSaveDuration; - } else if( - DURATION_DIFF(duration, ws_protocol_acurite_609txc_const.te_long * 2) < - ws_protocol_acurite_609txc_const.te_delta * 4) { - subghz_protocol_blocks_add_bit(&instance->decoder, 1); - instance->decoder.parser_step = Acurite_609TXCDecoderStepSaveDuration; - } else { - instance->decoder.parser_step = Acurite_609TXCDecoderStepReset; - } - } else { - instance->decoder.parser_step = Acurite_609TXCDecoderStepReset; - } - } else { - instance->decoder.parser_step = Acurite_609TXCDecoderStepReset; - } - break; - } -} - -uint8_t ws_protocol_decoder_acurite_609txc_get_hash_data(void* context) { - furi_assert(context); - WSProtocolDecoderAcurite_609TXC* instance = context; - return subghz_protocol_blocks_get_hash_data( - &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); -} - -SubGhzProtocolStatus ws_protocol_decoder_acurite_609txc_serialize( - void* context, - FlipperFormat* flipper_format, - SubGhzRadioPreset* preset) { - furi_assert(context); - WSProtocolDecoderAcurite_609TXC* instance = context; - return ws_block_generic_serialize(&instance->generic, flipper_format, preset); -} - -SubGhzProtocolStatus - ws_protocol_decoder_acurite_609txc_deserialize(void* context, FlipperFormat* flipper_format) { - furi_assert(context); - WSProtocolDecoderAcurite_609TXC* instance = context; - return ws_block_generic_deserialize_check_count_bit( - &instance->generic, - flipper_format, - ws_protocol_acurite_609txc_const.min_count_bit_for_found); -} - -void ws_protocol_decoder_acurite_609txc_get_string(void* context, FuriString* output) { - furi_assert(context); - WSProtocolDecoderAcurite_609TXC* instance = context; - furi_string_printf( - output, - "%s %dbit\r\n" - "Key:0x%lX%08lX\r\n" - "Sn:0x%lX Ch:%d Bat:%d\r\n" - "Temp:%3.1f C Hum:%d%%", - instance->generic.protocol_name, - instance->generic.data_count_bit, - (uint32_t)(instance->generic.data >> 40), - (uint32_t)(instance->generic.data), - instance->generic.id, - instance->generic.channel, - instance->generic.battery_low, - (double)instance->generic.temp, - instance->generic.humidity); -} diff --git a/applications/external/weather_station/protocols/acurite_609txc.h b/applications/external/weather_station/protocols/acurite_609txc.h deleted file mode 100644 index 3e3b9cee8d2..00000000000 --- a/applications/external/weather_station/protocols/acurite_609txc.h +++ /dev/null @@ -1,80 +0,0 @@ -#pragma once - -#include - -#include -#include -#include -#include "ws_generic.h" -#include - -#define WS_PROTOCOL_ACURITE_609TXC_NAME "Acurite-609TXC" - -typedef struct WSProtocolDecoderAcurite_609TXC WSProtocolDecoderAcurite_609TXC; -typedef struct WSProtocolEncoderAcurite_609TXC WSProtocolEncoderAcurite_609TXC; - -extern const SubGhzProtocolDecoder ws_protocol_acurite_609txc_decoder; -extern const SubGhzProtocolEncoder ws_protocol_acurite_609txc_encoder; -extern const SubGhzProtocol ws_protocol_acurite_609txc; - -/** - * Allocate WSProtocolDecoderAcurite_609TXC. - * @param environment Pointer to a SubGhzEnvironment instance - * @return WSProtocolDecoderAcurite_609TXC* pointer to a WSProtocolDecoderAcurite_609TXC instance - */ -void* ws_protocol_decoder_acurite_609txc_alloc(SubGhzEnvironment* environment); - -/** - * Free WSProtocolDecoderAcurite_609TXC. - * @param context Pointer to a WSProtocolDecoderAcurite_609TXC instance - */ -void ws_protocol_decoder_acurite_609txc_free(void* context); - -/** - * Reset decoder WSProtocolDecoderAcurite_609TXC. - * @param context Pointer to a WSProtocolDecoderAcurite_609TXC instance - */ -void ws_protocol_decoder_acurite_609txc_reset(void* context); - -/** - * Parse a raw sequence of levels and durations received from the air. - * @param context Pointer to a WSProtocolDecoderAcurite_609TXC instance - * @param level Signal level true-high false-low - * @param duration Duration of this level in, us - */ -void ws_protocol_decoder_acurite_609txc_feed(void* context, bool level, uint32_t duration); - -/** - * Getting the hash sum of the last randomly received parcel. - * @param context Pointer to a WSProtocolDecoderAcurite_609TXC instance - * @return hash Hash sum - */ -uint8_t ws_protocol_decoder_acurite_609txc_get_hash_data(void* context); - -/** - * Serialize data WSProtocolDecoderAcurite_609TXC. - * @param context Pointer to a WSProtocolDecoderAcurite_609TXC instance - * @param flipper_format Pointer to a FlipperFormat instance - * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return status - */ -SubGhzProtocolStatus ws_protocol_decoder_acurite_609txc_serialize( - void* context, - FlipperFormat* flipper_format, - SubGhzRadioPreset* preset); - -/** - * Deserialize data WSProtocolDecoderAcurite_609TXC. - * @param context Pointer to a WSProtocolDecoderAcurite_609TXC instance - * @param flipper_format Pointer to a FlipperFormat instance - * @return status - */ -SubGhzProtocolStatus - ws_protocol_decoder_acurite_609txc_deserialize(void* context, FlipperFormat* flipper_format); - -/** - * Getting a textual representation of the received data. - * @param context Pointer to a WSProtocolDecoderAcurite_609TXC instance - * @param output Resulting text - */ -void ws_protocol_decoder_acurite_609txc_get_string(void* context, FuriString* output); diff --git a/applications/external/weather_station/protocols/ambient_weather.c b/applications/external/weather_station/protocols/ambient_weather.c deleted file mode 100644 index 588a7f82ae9..00000000000 --- a/applications/external/weather_station/protocols/ambient_weather.c +++ /dev/null @@ -1,268 +0,0 @@ -#include "ambient_weather.h" -#include - -#define TAG "WSProtocolAmbient_Weather" - -/* - * Help - * https://github.com/merbanan/rtl_433/blob/master/src/devices/ambient_weather.c - * - * Decode Ambient Weather F007TH, F012TH, TF 30.3208.02, SwitchDoc F016TH. - * Devices supported: - * - Ambient Weather F007TH Thermo-Hygrometer. - * - Ambient Weather F012TH Indoor/Display Thermo-Hygrometer. - * - TFA senders 30.3208.02 from the TFA "Klima-Monitor" 30.3054, - * - SwitchDoc Labs F016TH. - * This decoder handles the 433mhz/868mhz thermo-hygrometers. - * The 915mhz (WH*) family of devices use different modulation/encoding. - * Byte 0 Byte 1 Byte 2 Byte 3 Byte 4 Byte 5 - * xxxxMMMM IIIIIIII BCCCTTTT TTTTTTTT HHHHHHHH MMMMMMMM - * - x: Unknown 0x04 on F007TH/F012TH - * - M: Model Number?, 0x05 on F007TH/F012TH/SwitchDocLabs F016TH - * - I: ID byte (8 bits), volatie, changes at power up, - * - B: Battery Low - * - C: Channel (3 bits 1-8) - F007TH set by Dip switch, F012TH soft setting - * - T: Temperature 12 bits - Fahrenheit * 10 + 400 - * - H: Humidity (8 bits) - * - M: Message integrity check LFSR Digest-8, gen 0x98, key 0x3e, init 0x64 - * - * three repeats without gap - * full preamble is 0x00145 (the last bits might not be fixed, e.g. 0x00146) - * and on decoding also 0xffd45 - */ - -#define AMBIENT_WEATHER_PACKET_HEADER_1 0xFFD440000000000 //0xffd45 .. 0xffd46 -#define AMBIENT_WEATHER_PACKET_HEADER_2 0x001440000000000 //0x00145 .. 0x00146 -#define AMBIENT_WEATHER_PACKET_HEADER_MASK 0xFFFFC0000000000 - -static const SubGhzBlockConst ws_protocol_ambient_weather_const = { - .te_short = 500, - .te_long = 1000, - .te_delta = 120, - .min_count_bit_for_found = 48, -}; - -struct WSProtocolDecoderAmbient_Weather { - SubGhzProtocolDecoderBase base; - - SubGhzBlockDecoder decoder; - WSBlockGeneric generic; - ManchesterState manchester_saved_state; - uint16_t header_count; -}; - -struct WSProtocolEncoderAmbient_Weather { - SubGhzProtocolEncoderBase base; - - SubGhzProtocolBlockEncoder encoder; - WSBlockGeneric generic; -}; - -const SubGhzProtocolDecoder ws_protocol_ambient_weather_decoder = { - .alloc = ws_protocol_decoder_ambient_weather_alloc, - .free = ws_protocol_decoder_ambient_weather_free, - - .feed = ws_protocol_decoder_ambient_weather_feed, - .reset = ws_protocol_decoder_ambient_weather_reset, - - .get_hash_data = ws_protocol_decoder_ambient_weather_get_hash_data, - .serialize = ws_protocol_decoder_ambient_weather_serialize, - .deserialize = ws_protocol_decoder_ambient_weather_deserialize, - .get_string = ws_protocol_decoder_ambient_weather_get_string, -}; - -const SubGhzProtocolEncoder ws_protocol_ambient_weather_encoder = { - .alloc = NULL, - .free = NULL, - - .deserialize = NULL, - .stop = NULL, - .yield = NULL, -}; - -const SubGhzProtocol ws_protocol_ambient_weather = { - .name = WS_PROTOCOL_AMBIENT_WEATHER_NAME, - .type = SubGhzProtocolWeatherStation, - .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | - SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable, - - .decoder = &ws_protocol_ambient_weather_decoder, - .encoder = &ws_protocol_ambient_weather_encoder, -}; - -void* ws_protocol_decoder_ambient_weather_alloc(SubGhzEnvironment* environment) { - UNUSED(environment); - WSProtocolDecoderAmbient_Weather* instance = malloc(sizeof(WSProtocolDecoderAmbient_Weather)); - instance->base.protocol = &ws_protocol_ambient_weather; - instance->generic.protocol_name = instance->base.protocol->name; - return instance; -} - -void ws_protocol_decoder_ambient_weather_free(void* context) { - furi_assert(context); - WSProtocolDecoderAmbient_Weather* instance = context; - free(instance); -} - -void ws_protocol_decoder_ambient_weather_reset(void* context) { - furi_assert(context); - WSProtocolDecoderAmbient_Weather* instance = context; - manchester_advance( - instance->manchester_saved_state, - ManchesterEventReset, - &instance->manchester_saved_state, - NULL); -} - -static bool ws_protocol_ambient_weather_check_crc(WSProtocolDecoderAmbient_Weather* instance) { - uint8_t msg[] = { - instance->decoder.decode_data >> 40, - instance->decoder.decode_data >> 32, - instance->decoder.decode_data >> 24, - instance->decoder.decode_data >> 16, - instance->decoder.decode_data >> 8}; - - uint8_t crc = subghz_protocol_blocks_lfsr_digest8(msg, 5, 0x98, 0x3e) ^ 0x64; - return (crc == (uint8_t)(instance->decoder.decode_data & 0xFF)); -} - -/** - * Analysis of received data - * @param instance Pointer to a WSBlockGeneric* instance - */ -static void ws_protocol_ambient_weather_remote_controller(WSBlockGeneric* instance) { - instance->id = (instance->data >> 32) & 0xFF; - instance->battery_low = (instance->data >> 31) & 1; - instance->channel = ((instance->data >> 28) & 0x07) + 1; - instance->temp = - locale_fahrenheit_to_celsius(((float)((instance->data >> 16) & 0x0FFF) - 400.0f) / 10.0f); - instance->humidity = (instance->data >> 8) & 0xFF; - instance->btn = WS_NO_BTN; - - // ToDo maybe it won't be needed - /* - Sanity checks to reduce false positives and other bad data - Packets with Bad data often pass the MIC check. - - humidity > 100 (such as 255) and - - temperatures > 140 F (such as 369.5 F and 348.8 F - Specs in the F007TH and F012TH manuals state the range is: - - Temperature: -40 to 140 F - - Humidity: 10 to 99% - @todo - sanity check b[0] "model number" - - 0x45 - F007TH and F012TH - - 0x?5 - SwitchDocLabs F016TH temperature sensor (based on comment b[0] & 0x0f == 5) - - ? - TFA 30.3208.02 - if (instance->humidity < 0 || instance->humidity > 100) { - ERROR; - } - - if (instance->temp < -40.0 || instance->temp > 140.0) { - ERROR; - } - */ -} - -void ws_protocol_decoder_ambient_weather_feed(void* context, bool level, uint32_t duration) { - furi_assert(context); - WSProtocolDecoderAmbient_Weather* instance = context; - - ManchesterEvent event = ManchesterEventReset; - if(!level) { - if(DURATION_DIFF(duration, ws_protocol_ambient_weather_const.te_short) < - ws_protocol_ambient_weather_const.te_delta) { - event = ManchesterEventShortLow; - } else if( - DURATION_DIFF(duration, ws_protocol_ambient_weather_const.te_long) < - ws_protocol_ambient_weather_const.te_delta * 2) { - event = ManchesterEventLongLow; - } - } else { - if(DURATION_DIFF(duration, ws_protocol_ambient_weather_const.te_short) < - ws_protocol_ambient_weather_const.te_delta) { - event = ManchesterEventShortHigh; - } else if( - DURATION_DIFF(duration, ws_protocol_ambient_weather_const.te_long) < - ws_protocol_ambient_weather_const.te_delta * 2) { - event = ManchesterEventLongHigh; - } - } - if(event != ManchesterEventReset) { - bool data; - bool data_ok = manchester_advance( - instance->manchester_saved_state, event, &instance->manchester_saved_state, &data); - - if(data_ok) { - instance->decoder.decode_data = (instance->decoder.decode_data << 1) | !data; - } - - if(((instance->decoder.decode_data & AMBIENT_WEATHER_PACKET_HEADER_MASK) == - AMBIENT_WEATHER_PACKET_HEADER_1) || - ((instance->decoder.decode_data & AMBIENT_WEATHER_PACKET_HEADER_MASK) == - AMBIENT_WEATHER_PACKET_HEADER_2)) { - if(ws_protocol_ambient_weather_check_crc(instance)) { - instance->generic.data = instance->decoder.decode_data; - instance->generic.data_count_bit = - ws_protocol_ambient_weather_const.min_count_bit_for_found; - ws_protocol_ambient_weather_remote_controller(&instance->generic); - if(instance->base.callback) - instance->base.callback(&instance->base, instance->base.context); - instance->decoder.decode_data = 0; - instance->decoder.decode_count_bit = 0; - } - } - } else { - instance->decoder.decode_data = 0; - instance->decoder.decode_count_bit = 0; - manchester_advance( - instance->manchester_saved_state, - ManchesterEventReset, - &instance->manchester_saved_state, - NULL); - } -} - -uint8_t ws_protocol_decoder_ambient_weather_get_hash_data(void* context) { - furi_assert(context); - WSProtocolDecoderAmbient_Weather* instance = context; - return subghz_protocol_blocks_get_hash_data( - &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); -} - -SubGhzProtocolStatus ws_protocol_decoder_ambient_weather_serialize( - void* context, - FlipperFormat* flipper_format, - SubGhzRadioPreset* preset) { - furi_assert(context); - WSProtocolDecoderAmbient_Weather* instance = context; - return ws_block_generic_serialize(&instance->generic, flipper_format, preset); -} - -SubGhzProtocolStatus - ws_protocol_decoder_ambient_weather_deserialize(void* context, FlipperFormat* flipper_format) { - furi_assert(context); - WSProtocolDecoderAmbient_Weather* instance = context; - return ws_block_generic_deserialize_check_count_bit( - &instance->generic, - flipper_format, - ws_protocol_ambient_weather_const.min_count_bit_for_found); -} - -void ws_protocol_decoder_ambient_weather_get_string(void* context, FuriString* output) { - furi_assert(context); - WSProtocolDecoderAmbient_Weather* instance = context; - furi_string_printf( - output, - "%s %dbit\r\n" - "Key:0x%lX%08lX\r\n" - "Sn:0x%lX Ch:%d Bat:%d\r\n" - "Temp:%3.1f C Hum:%d%%", - instance->generic.protocol_name, - instance->generic.data_count_bit, - (uint32_t)(instance->generic.data >> 32), - (uint32_t)(instance->generic.data), - instance->generic.id, - instance->generic.channel, - instance->generic.battery_low, - (double)instance->generic.temp, - instance->generic.humidity); -} diff --git a/applications/external/weather_station/protocols/ambient_weather.h b/applications/external/weather_station/protocols/ambient_weather.h deleted file mode 100644 index 1694403cd1d..00000000000 --- a/applications/external/weather_station/protocols/ambient_weather.h +++ /dev/null @@ -1,80 +0,0 @@ -#pragma once - -#include - -#include -#include -#include -#include "ws_generic.h" -#include - -#define WS_PROTOCOL_AMBIENT_WEATHER_NAME "Ambient_Weather" - -typedef struct WSProtocolDecoderAmbient_Weather WSProtocolDecoderAmbient_Weather; -typedef struct WSProtocolEncoderAmbient_Weather WSProtocolEncoderAmbient_Weather; - -extern const SubGhzProtocolDecoder ws_protocol_ambient_weather_decoder; -extern const SubGhzProtocolEncoder ws_protocol_ambient_weather_encoder; -extern const SubGhzProtocol ws_protocol_ambient_weather; - -/** - * Allocate WSProtocolDecoderAmbient_Weather. - * @param environment Pointer to a SubGhzEnvironment instance - * @return WSProtocolDecoderAmbient_Weather* pointer to a WSProtocolDecoderAmbient_Weather instance - */ -void* ws_protocol_decoder_ambient_weather_alloc(SubGhzEnvironment* environment); - -/** - * Free WSProtocolDecoderAmbient_Weather. - * @param context Pointer to a WSProtocolDecoderAmbient_Weather instance - */ -void ws_protocol_decoder_ambient_weather_free(void* context); - -/** - * Reset decoder WSProtocolDecoderAmbient_Weather. - * @param context Pointer to a WSProtocolDecoderAmbient_Weather instance - */ -void ws_protocol_decoder_ambient_weather_reset(void* context); - -/** - * Parse a raw sequence of levels and durations received from the air. - * @param context Pointer to a WSProtocolDecoderAmbient_Weather instance - * @param level Signal level true-high false-low - * @param duration Duration of this level in, us - */ -void ws_protocol_decoder_ambient_weather_feed(void* context, bool level, uint32_t duration); - -/** - * Getting the hash sum of the last randomly received parcel. - * @param context Pointer to a WSProtocolDecoderAmbient_Weather instance - * @return hash Hash sum - */ -uint8_t ws_protocol_decoder_ambient_weather_get_hash_data(void* context); - -/** - * Serialize data WSProtocolDecoderAmbient_Weather. - * @param context Pointer to a WSProtocolDecoderAmbient_Weather instance - * @param flipper_format Pointer to a FlipperFormat instance - * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return status - */ -SubGhzProtocolStatus ws_protocol_decoder_ambient_weather_serialize( - void* context, - FlipperFormat* flipper_format, - SubGhzRadioPreset* preset); - -/** - * Deserialize data WSProtocolDecoderAmbient_Weather. - * @param context Pointer to a WSProtocolDecoderAmbient_Weather instance - * @param flipper_format Pointer to a FlipperFormat instance - * @return status - */ -SubGhzProtocolStatus - ws_protocol_decoder_ambient_weather_deserialize(void* context, FlipperFormat* flipper_format); - -/** - * Getting a textual representation of the received data. - * @param context Pointer to a WSProtocolDecoderAmbient_Weather instance - * @param output Resulting text - */ -void ws_protocol_decoder_ambient_weather_get_string(void* context, FuriString* output); diff --git a/applications/external/weather_station/protocols/auriol_hg0601a.c b/applications/external/weather_station/protocols/auriol_hg0601a.c deleted file mode 100644 index 813fe1526c8..00000000000 --- a/applications/external/weather_station/protocols/auriol_hg0601a.c +++ /dev/null @@ -1,248 +0,0 @@ -#include "auriol_hg0601a.h" - -#define TAG "WSProtocolAuriol_TH" - -/* - * -Auriol HG06061A-DCF-TX sensor. - -Data layout: - DDDDDDDD-B0-NN-TT-TTTTTTTTTT-CCCC-HHHHHHHH -Exmpl.: 11110100-10-01-00-0001001100-1111-01011101 - -- D: id, 8 bit -- B: where B is the battery status: 1=OK, 0=LOW, 1 bit -- 0: just zero :) -- N: NN is the channel: 00=CH1, 01=CH2, 11=CH3, 2bit -- T: temperature, 12 bit: 2's complement, scaled by 10 -- C: 4 bit: seems to be 0xf constantly, a separator between temp and humidity -- H: humidity sensor, humidity is 8 bits - - * The sensor sends 37 bits 10 times, - * the packets are ppm modulated (distance coding) with a pulse of ~500 us - * followed by a short gap of ~1000 us for a 0 bit or a long ~2000 us gap for a - * 1 bit, the sync gap is ~4000 us. - * - */ - -#define AURIOL_TH_CONST_DATA 0b1110 - -static const SubGhzBlockConst ws_protocol_auriol_th_const = { - .te_short = 500, - .te_long = 2000, - .te_delta = 150, - .min_count_bit_for_found = 37, -}; - -struct WSProtocolDecoderAuriol_TH { - SubGhzProtocolDecoderBase base; - - SubGhzBlockDecoder decoder; - WSBlockGeneric generic; -}; - -struct WSProtocolEncoderAuriol_TH { - SubGhzProtocolEncoderBase base; - - SubGhzProtocolBlockEncoder encoder; - WSBlockGeneric generic; -}; - -typedef enum { - auriol_THDecoderStepReset = 0, - auriol_THDecoderStepSaveDuration, - auriol_THDecoderStepCheckDuration, -} auriol_THDecoderStep; - -const SubGhzProtocolDecoder ws_protocol_auriol_th_decoder = { - .alloc = ws_protocol_decoder_auriol_th_alloc, - .free = ws_protocol_decoder_auriol_th_free, - - .feed = ws_protocol_decoder_auriol_th_feed, - .reset = ws_protocol_decoder_auriol_th_reset, - - .get_hash_data = ws_protocol_decoder_auriol_th_get_hash_data, - .serialize = ws_protocol_decoder_auriol_th_serialize, - .deserialize = ws_protocol_decoder_auriol_th_deserialize, - .get_string = ws_protocol_decoder_auriol_th_get_string, -}; - -const SubGhzProtocolEncoder ws_protocol_auriol_th_encoder = { - .alloc = NULL, - .free = NULL, - - .deserialize = NULL, - .stop = NULL, - .yield = NULL, -}; - -const SubGhzProtocol ws_protocol_auriol_th = { - .name = WS_PROTOCOL_AURIOL_TH_NAME, - .type = SubGhzProtocolWeatherStation, - .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | - SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable, - - .decoder = &ws_protocol_auriol_th_decoder, - .encoder = &ws_protocol_auriol_th_encoder, -}; - -void* ws_protocol_decoder_auriol_th_alloc(SubGhzEnvironment* environment) { - UNUSED(environment); - WSProtocolDecoderAuriol_TH* instance = malloc(sizeof(WSProtocolDecoderAuriol_TH)); - instance->base.protocol = &ws_protocol_auriol_th; - instance->generic.protocol_name = instance->base.protocol->name; - return instance; -} - -void ws_protocol_decoder_auriol_th_free(void* context) { - furi_assert(context); - WSProtocolDecoderAuriol_TH* instance = context; - free(instance); -} - -void ws_protocol_decoder_auriol_th_reset(void* context) { - furi_assert(context); - WSProtocolDecoderAuriol_TH* instance = context; - instance->decoder.parser_step = auriol_THDecoderStepReset; -} - -static bool ws_protocol_auriol_th_check(WSProtocolDecoderAuriol_TH* instance) { - uint8_t type = (instance->decoder.decode_data >> 8) & 0x0F; - - if((type == AURIOL_TH_CONST_DATA) && ((instance->decoder.decode_data >> 4) != 0xffffffff)) { - return true; - } else { - return false; - } - return true; -} - -/** - * Analysis of received data - * @param instance Pointer to a WSBlockGeneric* instance - */ -static void ws_protocol_auriol_th_remote_controller(WSBlockGeneric* instance) { - instance->id = (instance->data >> 31) & 0xFF; - instance->battery_low = ((instance->data >> 30) & 1); - instance->channel = ((instance->data >> 25) & 0x03) + 1; - instance->btn = WS_NO_BTN; - if(!((instance->data >> 23) & 1)) { - instance->temp = (float)((instance->data >> 13) & 0x07FF) / 10.0f; - } else { - instance->temp = (float)((~(instance->data >> 13) & 0x07FF) + 1) / -10.0f; - } - - instance->humidity = (instance->data >> 1) & 0x7F; -} - -void ws_protocol_decoder_auriol_th_feed(void* context, bool level, uint32_t duration) { - furi_assert(context); - WSProtocolDecoderAuriol_TH* instance = context; - - switch(instance->decoder.parser_step) { - case auriol_THDecoderStepReset: - if((!level) && (DURATION_DIFF(duration, ws_protocol_auriol_th_const.te_short * 8) < - ws_protocol_auriol_th_const.te_delta)) { - //Found sync - instance->decoder.parser_step = auriol_THDecoderStepSaveDuration; - instance->decoder.decode_data = 0; - instance->decoder.decode_count_bit = 0; - } - break; - - case auriol_THDecoderStepSaveDuration: - if(level) { - instance->decoder.te_last = duration; - instance->decoder.parser_step = auriol_THDecoderStepCheckDuration; - } else { - instance->decoder.parser_step = auriol_THDecoderStepReset; - } - break; - - case auriol_THDecoderStepCheckDuration: - if(!level) { - if(DURATION_DIFF(duration, ws_protocol_auriol_th_const.te_short * 8) < - ws_protocol_auriol_th_const.te_delta) { - //Found sync - instance->decoder.parser_step = auriol_THDecoderStepReset; - if((instance->decoder.decode_count_bit == - ws_protocol_auriol_th_const.min_count_bit_for_found) && - ws_protocol_auriol_th_check(instance)) { - instance->generic.data = instance->decoder.decode_data; - instance->generic.data_count_bit = instance->decoder.decode_count_bit; - ws_protocol_auriol_th_remote_controller(&instance->generic); - if(instance->base.callback) - instance->base.callback(&instance->base, instance->base.context); - instance->decoder.parser_step = auriol_THDecoderStepCheckDuration; - } - instance->decoder.decode_data = 0; - instance->decoder.decode_count_bit = 0; - - break; - } else if( - (DURATION_DIFF(instance->decoder.te_last, ws_protocol_auriol_th_const.te_short) < - ws_protocol_auriol_th_const.te_delta) && - (DURATION_DIFF(duration, ws_protocol_auriol_th_const.te_short * 2) < - ws_protocol_auriol_th_const.te_delta)) { - subghz_protocol_blocks_add_bit(&instance->decoder, 0); - instance->decoder.parser_step = auriol_THDecoderStepSaveDuration; - } else if( - (DURATION_DIFF(instance->decoder.te_last, ws_protocol_auriol_th_const.te_short) < - ws_protocol_auriol_th_const.te_delta) && - (DURATION_DIFF(duration, ws_protocol_auriol_th_const.te_short * 4) < - ws_protocol_auriol_th_const.te_delta * 2)) { - subghz_protocol_blocks_add_bit(&instance->decoder, 1); - instance->decoder.parser_step = auriol_THDecoderStepSaveDuration; - } else { - instance->decoder.parser_step = auriol_THDecoderStepReset; - } - } else { - instance->decoder.parser_step = auriol_THDecoderStepReset; - } - break; - } -} - -uint8_t ws_protocol_decoder_auriol_th_get_hash_data(void* context) { - furi_assert(context); - WSProtocolDecoderAuriol_TH* instance = context; - return subghz_protocol_blocks_get_hash_data( - &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); -} - -SubGhzProtocolStatus ws_protocol_decoder_auriol_th_serialize( - void* context, - FlipperFormat* flipper_format, - SubGhzRadioPreset* preset) { - furi_assert(context); - WSProtocolDecoderAuriol_TH* instance = context; - return ws_block_generic_serialize(&instance->generic, flipper_format, preset); -} - -SubGhzProtocolStatus - ws_protocol_decoder_auriol_th_deserialize(void* context, FlipperFormat* flipper_format) { - furi_assert(context); - WSProtocolDecoderAuriol_TH* instance = context; - return ws_block_generic_deserialize_check_count_bit( - &instance->generic, flipper_format, ws_protocol_auriol_th_const.min_count_bit_for_found); -} - -void ws_protocol_decoder_auriol_th_get_string(void* context, FuriString* output) { - furi_assert(context); - WSProtocolDecoderAuriol_TH* instance = context; - furi_string_printf( - output, - "%s %dbit\r\n" - "Key:0x%lX%08lX\r\n" - "Sn:0x%lX Ch:%d Bat:%d\r\n" - "Temp:%3.1f C Hum:%d%%", - instance->generic.protocol_name, - instance->generic.data_count_bit, - (uint32_t)(instance->generic.data >> 32), - (uint32_t)(instance->generic.data), - instance->generic.id, - instance->generic.channel, - instance->generic.battery_low, - (double)instance->generic.temp, - instance->generic.humidity); -} diff --git a/applications/external/weather_station/protocols/auriol_hg0601a.h b/applications/external/weather_station/protocols/auriol_hg0601a.h deleted file mode 100644 index 155bb07fc46..00000000000 --- a/applications/external/weather_station/protocols/auriol_hg0601a.h +++ /dev/null @@ -1,80 +0,0 @@ -#pragma once - -#include - -#include -#include -#include -#include "ws_generic.h" -#include - -#define WS_PROTOCOL_AURIOL_TH_NAME "Auriol HG06061" //HG06061A-DCF-TX - -typedef struct WSProtocolDecoderAuriol_TH WSProtocolDecoderAuriol_TH; -typedef struct WSProtocolEncoderAuriol_TH WSProtocolEncoderAuriol_TH; - -extern const SubGhzProtocolDecoder ws_protocol_auriol_th_decoder; -extern const SubGhzProtocolEncoder ws_protocol_auriol_th_encoder; -extern const SubGhzProtocol ws_protocol_auriol_th; - -/** - * Allocate WSProtocolDecoderAuriol_TH. - * @param environment Pointer to a SubGhzEnvironment instance - * @return WSProtocolDecoderAuriol_TH* pointer to a WSProtocolDecoderAuriol_TH instance - */ -void* ws_protocol_decoder_auriol_th_alloc(SubGhzEnvironment* environment); - -/** - * Free WSProtocolDecoderAuriol_TH. - * @param context Pointer to a WSProtocolDecoderAuriol_TH instance - */ -void ws_protocol_decoder_auriol_th_free(void* context); - -/** - * Reset decoder WSProtocolDecoderAuriol_TH. - * @param context Pointer to a WSProtocolDecoderAuriol_TH instance - */ -void ws_protocol_decoder_auriol_th_reset(void* context); - -/** - * Parse a raw sequence of levels and durations received from the air. - * @param context Pointer to a WSProtocolDecoderAuriol_TH instance - * @param level Signal level true-high false-low - * @param duration Duration of this level in, us - */ -void ws_protocol_decoder_auriol_th_feed(void* context, bool level, uint32_t duration); - -/** - * Getting the hash sum of the last randomly received parcel. - * @param context Pointer to a WSProtocolDecoderAuriol_TH instance - * @return hash Hash sum - */ -uint8_t ws_protocol_decoder_auriol_th_get_hash_data(void* context); - -/** - * Serialize data WSProtocolDecoderAuriol_TH. - * @param context Pointer to a WSProtocolDecoderAuriol_TH instance - * @param flipper_format Pointer to a FlipperFormat instance - * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return status - */ -SubGhzProtocolStatus ws_protocol_decoder_auriol_th_serialize( - void* context, - FlipperFormat* flipper_format, - SubGhzRadioPreset* preset); - -/** - * Deserialize data WSProtocolDecoderAuriol_TH. - * @param context Pointer to a WSProtocolDecoderAuriol_TH instance - * @param flipper_format Pointer to a FlipperFormat instance - * @return status - */ -SubGhzProtocolStatus - ws_protocol_decoder_auriol_th_deserialize(void* context, FlipperFormat* flipper_format); - -/** - * Getting a textual representation of the received data. - * @param context Pointer to a WSProtocolDecoderAuriol_TH instance - * @param output Resulting text - */ -void ws_protocol_decoder_auriol_th_get_string(void* context, FuriString* output); diff --git a/applications/external/weather_station/protocols/gt_wt_02.c b/applications/external/weather_station/protocols/gt_wt_02.c deleted file mode 100644 index d333164b491..00000000000 --- a/applications/external/weather_station/protocols/gt_wt_02.c +++ /dev/null @@ -1,255 +0,0 @@ -#include "gt_wt_02.h" - -#define TAG "WSProtocolGT_WT02" - -/* - * Help - * https://github.com/merbanan/rtl_433/blob/master/src/devices/gt_wt_02.c - * - * GT-WT-02 sensor on 433.92MHz. - * Example and frame description provided by https://github.com/ludwich66 - * [01] {37} 34 00 ed 47 60 : 00110100 00000000 11101101 01000111 01100000 - * code, BatOK,not-man-send, Channel1, +23,7C, 35% - * [01] {37} 34 8f 87 15 90 : 00110100 10001111 10000111 00010101 10010000 - * code, BatOK,not-man-send, Channel1,-12,1C, 10% - * Humidity: - * - the working range is 20-90 % - * - if "LL" in display view it sends 10 % - * - if "HH" in display view it sends 110% - * SENSOR: GT-WT-02 (ALDI Globaltronics..) - * TYP IIIIIIII BMCCTTTT TTTTTTTT HHHHHHHX XXXXX - * TYPE Description: - * - I = Random Device Code, changes with battery reset - * - B = Battery 0=OK 1=LOW - * - M = Manual Send Button Pressed 0=not pressed 1=pressed - * - C = Channel 00=CH1, 01=CH2, 10=CH3 - * - T = Temperature, 12 Bit 2's complement, scaled by 10 - * - H = Humidity = 7 Bit bin2dez 00-99, Display LL=10%, Display HH=110% (Range 20-90%) - * - X = Checksum, sum modulo 64 - * A Lidl AURIO (from 12/2018) with PCB marking YJ-T12 V02 has two extra bits in front. - * -*/ - -static const SubGhzBlockConst ws_protocol_gt_wt_02_const = { - .te_short = 500, - .te_long = 2000, - .te_delta = 150, - .min_count_bit_for_found = 37, -}; - -struct WSProtocolDecoderGT_WT02 { - SubGhzProtocolDecoderBase base; - - SubGhzBlockDecoder decoder; - WSBlockGeneric generic; -}; - -struct WSProtocolEncoderGT_WT02 { - SubGhzProtocolEncoderBase base; - - SubGhzProtocolBlockEncoder encoder; - WSBlockGeneric generic; -}; - -typedef enum { - GT_WT02DecoderStepReset = 0, - GT_WT02DecoderStepSaveDuration, - GT_WT02DecoderStepCheckDuration, -} GT_WT02DecoderStep; - -const SubGhzProtocolDecoder ws_protocol_gt_wt_02_decoder = { - .alloc = ws_protocol_decoder_gt_wt_02_alloc, - .free = ws_protocol_decoder_gt_wt_02_free, - - .feed = ws_protocol_decoder_gt_wt_02_feed, - .reset = ws_protocol_decoder_gt_wt_02_reset, - - .get_hash_data = ws_protocol_decoder_gt_wt_02_get_hash_data, - .serialize = ws_protocol_decoder_gt_wt_02_serialize, - .deserialize = ws_protocol_decoder_gt_wt_02_deserialize, - .get_string = ws_protocol_decoder_gt_wt_02_get_string, -}; - -const SubGhzProtocolEncoder ws_protocol_gt_wt_02_encoder = { - .alloc = NULL, - .free = NULL, - - .deserialize = NULL, - .stop = NULL, - .yield = NULL, -}; - -const SubGhzProtocol ws_protocol_gt_wt_02 = { - .name = WS_PROTOCOL_GT_WT_02_NAME, - .type = SubGhzProtocolWeatherStation, - .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | - SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable, - - .decoder = &ws_protocol_gt_wt_02_decoder, - .encoder = &ws_protocol_gt_wt_02_encoder, -}; - -void* ws_protocol_decoder_gt_wt_02_alloc(SubGhzEnvironment* environment) { - UNUSED(environment); - WSProtocolDecoderGT_WT02* instance = malloc(sizeof(WSProtocolDecoderGT_WT02)); - instance->base.protocol = &ws_protocol_gt_wt_02; - instance->generic.protocol_name = instance->base.protocol->name; - return instance; -} - -void ws_protocol_decoder_gt_wt_02_free(void* context) { - furi_assert(context); - WSProtocolDecoderGT_WT02* instance = context; - free(instance); -} - -void ws_protocol_decoder_gt_wt_02_reset(void* context) { - furi_assert(context); - WSProtocolDecoderGT_WT02* instance = context; - instance->decoder.parser_step = GT_WT02DecoderStepReset; -} - -static bool ws_protocol_gt_wt_02_check(WSProtocolDecoderGT_WT02* instance) { - if(!instance->decoder.decode_data) return false; - uint8_t sum = (instance->decoder.decode_data >> 5) & 0xe; - uint64_t temp_data = instance->decoder.decode_data >> 9; - for(uint8_t i = 0; i < 7; i++) { - sum += (temp_data >> (i * 4)) & 0xF; - } - return ((uint8_t)(instance->decoder.decode_data & 0x3F) == (sum & 0x3F)); -} - -/** - * Analysis of received data - * @param instance Pointer to a WSBlockGeneric* instance - */ -static void ws_protocol_gt_wt_02_remote_controller(WSBlockGeneric* instance) { - instance->id = (instance->data >> 29) & 0xFF; - instance->battery_low = (instance->data >> 28) & 1; - instance->btn = (instance->data >> 27) & 1; - instance->channel = ((instance->data >> 25) & 0x3) + 1; - - if(!((instance->data >> 24) & 1)) { - instance->temp = (float)((instance->data >> 13) & 0x07FF) / 10.0f; - } else { - instance->temp = (float)((~(instance->data >> 13) & 0x07FF) + 1) / -10.0f; - } - - instance->humidity = (instance->data >> 6) & 0x7F; - if(instance->humidity <= 10) // actually the sensors sends 10 below working range of 20% - instance->humidity = 0; - else if(instance->humidity > 90) // actually the sensors sends 110 above working range of 90% - instance->humidity = 100; -} - -void ws_protocol_decoder_gt_wt_02_feed(void* context, bool level, uint32_t duration) { - furi_assert(context); - WSProtocolDecoderGT_WT02* instance = context; - - switch(instance->decoder.parser_step) { - case GT_WT02DecoderStepReset: - if((!level) && (DURATION_DIFF(duration, ws_protocol_gt_wt_02_const.te_short * 18) < - ws_protocol_gt_wt_02_const.te_delta * 8)) { - //Found syncPrefix - instance->decoder.parser_step = GT_WT02DecoderStepSaveDuration; - instance->decoder.decode_data = 0; - instance->decoder.decode_count_bit = 0; - } - break; - - case GT_WT02DecoderStepSaveDuration: - if(level) { - instance->decoder.te_last = duration; - instance->decoder.parser_step = GT_WT02DecoderStepCheckDuration; - } else { - instance->decoder.parser_step = GT_WT02DecoderStepReset; - } - break; - - case GT_WT02DecoderStepCheckDuration: - if(!level) { - if(DURATION_DIFF(instance->decoder.te_last, ws_protocol_gt_wt_02_const.te_short) < - ws_protocol_gt_wt_02_const.te_delta) { - if(DURATION_DIFF(duration, ws_protocol_gt_wt_02_const.te_short * 18) < - ws_protocol_gt_wt_02_const.te_delta * 8) { - //Found syncPostfix - instance->decoder.parser_step = GT_WT02DecoderStepReset; - if((instance->decoder.decode_count_bit == - ws_protocol_gt_wt_02_const.min_count_bit_for_found) && - ws_protocol_gt_wt_02_check(instance)) { - instance->generic.data = instance->decoder.decode_data; - instance->generic.data_count_bit = instance->decoder.decode_count_bit; - ws_protocol_gt_wt_02_remote_controller(&instance->generic); - if(instance->base.callback) - instance->base.callback(&instance->base, instance->base.context); - } else if(instance->decoder.decode_count_bit == 1) { - instance->decoder.parser_step = GT_WT02DecoderStepSaveDuration; - } - instance->decoder.decode_data = 0; - instance->decoder.decode_count_bit = 0; - } else if( - DURATION_DIFF(duration, ws_protocol_gt_wt_02_const.te_long) < - ws_protocol_gt_wt_02_const.te_delta * 2) { - subghz_protocol_blocks_add_bit(&instance->decoder, 0); - instance->decoder.parser_step = GT_WT02DecoderStepSaveDuration; - } else if( - DURATION_DIFF(duration, ws_protocol_gt_wt_02_const.te_long * 2) < - ws_protocol_gt_wt_02_const.te_delta * 4) { - subghz_protocol_blocks_add_bit(&instance->decoder, 1); - instance->decoder.parser_step = GT_WT02DecoderStepSaveDuration; - } else { - instance->decoder.parser_step = GT_WT02DecoderStepReset; - } - } else { - instance->decoder.parser_step = GT_WT02DecoderStepReset; - } - } else { - instance->decoder.parser_step = GT_WT02DecoderStepReset; - } - break; - } -} - -uint8_t ws_protocol_decoder_gt_wt_02_get_hash_data(void* context) { - furi_assert(context); - WSProtocolDecoderGT_WT02* instance = context; - return subghz_protocol_blocks_get_hash_data( - &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); -} - -SubGhzProtocolStatus ws_protocol_decoder_gt_wt_02_serialize( - void* context, - FlipperFormat* flipper_format, - SubGhzRadioPreset* preset) { - furi_assert(context); - WSProtocolDecoderGT_WT02* instance = context; - return ws_block_generic_serialize(&instance->generic, flipper_format, preset); -} - -SubGhzProtocolStatus - ws_protocol_decoder_gt_wt_02_deserialize(void* context, FlipperFormat* flipper_format) { - furi_assert(context); - WSProtocolDecoderGT_WT02* instance = context; - return ws_block_generic_deserialize_check_count_bit( - &instance->generic, flipper_format, ws_protocol_gt_wt_02_const.min_count_bit_for_found); -} - -void ws_protocol_decoder_gt_wt_02_get_string(void* context, FuriString* output) { - furi_assert(context); - WSProtocolDecoderGT_WT02* instance = context; - furi_string_printf( - output, - "%s %dbit\r\n" - "Key:0x%lX%08lX\r\n" - "Sn:0x%lX Ch:%d Bat:%d\r\n" - "Temp:%3.1f C Hum:%d%%", - instance->generic.protocol_name, - instance->generic.data_count_bit, - (uint32_t)(instance->generic.data >> 32), - (uint32_t)(instance->generic.data), - instance->generic.id, - instance->generic.channel, - instance->generic.battery_low, - (double)instance->generic.temp, - instance->generic.humidity); -} diff --git a/applications/external/weather_station/protocols/gt_wt_02.h b/applications/external/weather_station/protocols/gt_wt_02.h deleted file mode 100644 index e13576d218d..00000000000 --- a/applications/external/weather_station/protocols/gt_wt_02.h +++ /dev/null @@ -1,80 +0,0 @@ -#pragma once - -#include - -#include -#include -#include -#include "ws_generic.h" -#include - -#define WS_PROTOCOL_GT_WT_02_NAME "GT-WT02" - -typedef struct WSProtocolDecoderGT_WT02 WSProtocolDecoderGT_WT02; -typedef struct WSProtocolEncoderGT_WT02 WSProtocolEncoderGT_WT02; - -extern const SubGhzProtocolDecoder ws_protocol_gt_wt_02_decoder; -extern const SubGhzProtocolEncoder ws_protocol_gt_wt_02_encoder; -extern const SubGhzProtocol ws_protocol_gt_wt_02; - -/** - * Allocate WSProtocolDecoderGT_WT02. - * @param environment Pointer to a SubGhzEnvironment instance - * @return WSProtocolDecoderGT_WT02* pointer to a WSProtocolDecoderGT_WT02 instance - */ -void* ws_protocol_decoder_gt_wt_02_alloc(SubGhzEnvironment* environment); - -/** - * Free WSProtocolDecoderGT_WT02. - * @param context Pointer to a WSProtocolDecoderGT_WT02 instance - */ -void ws_protocol_decoder_gt_wt_02_free(void* context); - -/** - * Reset decoder WSProtocolDecoderGT_WT02. - * @param context Pointer to a WSProtocolDecoderGT_WT02 instance - */ -void ws_protocol_decoder_gt_wt_02_reset(void* context); - -/** - * Parse a raw sequence of levels and durations received from the air. - * @param context Pointer to a WSProtocolDecoderGT_WT02 instance - * @param level Signal level true-high false-low - * @param duration Duration of this level in, us - */ -void ws_protocol_decoder_gt_wt_02_feed(void* context, bool level, uint32_t duration); - -/** - * Getting the hash sum of the last randomly received parcel. - * @param context Pointer to a WSProtocolDecoderGT_WT02 instance - * @return hash Hash sum - */ -uint8_t ws_protocol_decoder_gt_wt_02_get_hash_data(void* context); - -/** - * Serialize data WSProtocolDecoderGT_WT02. - * @param context Pointer to a WSProtocolDecoderGT_WT02 instance - * @param flipper_format Pointer to a FlipperFormat instance - * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return status - */ -SubGhzProtocolStatus ws_protocol_decoder_gt_wt_02_serialize( - void* context, - FlipperFormat* flipper_format, - SubGhzRadioPreset* preset); - -/** - * Deserialize data WSProtocolDecoderGT_WT02. - * @param context Pointer to a WSProtocolDecoderGT_WT02 instance - * @param flipper_format Pointer to a FlipperFormat instance - * @return status - */ -SubGhzProtocolStatus - ws_protocol_decoder_gt_wt_02_deserialize(void* context, FlipperFormat* flipper_format); - -/** - * Getting a textual representation of the received data. - * @param context Pointer to a WSProtocolDecoderGT_WT02 instance - * @param output Resulting text - */ -void ws_protocol_decoder_gt_wt_02_get_string(void* context, FuriString* output); diff --git a/applications/external/weather_station/protocols/gt_wt_03.c b/applications/external/weather_station/protocols/gt_wt_03.c deleted file mode 100644 index 30430b83904..00000000000 --- a/applications/external/weather_station/protocols/gt_wt_03.c +++ /dev/null @@ -1,330 +0,0 @@ -#include "gt_wt_03.h" - -#define TAG "WSProtocolGT_WT03" - -/* - * Help - * https://github.com/merbanan/rtl_433/blob/master/src/devices/gt_wt_03.c - * - * - * Globaltronics GT-WT-03 sensor on 433.92MHz. - * The 01-set sensor has 60 ms packet gap with 10 repeats. - * The 02-set sensor has no packet gap with 23 repeats. - * Example: - * {41} 17 cf be fa 6a 80 [ S1 C1 26,1 C 78.9 F 48% Bat-Good Manual-Yes ] - * {41} 17 cf be fa 6a 80 [ S1 C1 26,1 C 78.9 F 48% Bat-Good Manual-Yes Batt-Changed ] - * {41} 17 cf fe fa ea 80 [ S1 C1 26,1 C 78.9 F 48% Bat-Good Manual-No Batt-Changed ] - * {41} 01 cf 6f 11 b2 80 [ S2 C2 23,8 C 74.8 F 48% Bat-LOW Manual-No ] - * {41} 01 c8 d0 2b 76 80 [ S2 C3 -4,4 C 24.1 F 55% Bat-Good Manual-No Batt-Changed ] - * Format string: - * ID:8h HUM:8d B:b M:b C:2d TEMP:12d CHK:8h 1x - * Data layout: - * TYP IIIIIIII HHHHHHHH BMCCTTTT TTTTTTTT XXXXXXXX - * - I: Random Device Code: changes with battery reset - * - H: Humidity: 8 Bit 00-99, Display LL=10%, Display HH=110% (Range 20-95%) - * - B: Battery: 0=OK 1=LOW - * - M: Manual Send Button Pressed: 0=not pressed, 1=pressed - * - C: Channel: 00=CH1, 01=CH2, 10=CH3 - * - T: Temperature: 12 Bit 2's complement, scaled by 10, range-50.0 C (-50.1 shown as Lo) to +70.0 C (+70.1 C is shown as Hi) - * - X: Checksum, xor shifting key per byte - * Humidity: - * - the working range is 20-95 % - * - if "LL" in display view it sends 10 % - * - if "HH" in display view it sends 110% - * Checksum: - * Per byte xor the key for each 1-bit, shift per bit. Key list per bit, starting at MSB: - * - 0x00 [07] - * - 0x80 [06] - * - 0x40 [05] - * - 0x20 [04] - * - 0x10 [03] - * - 0x88 [02] - * - 0xc4 [01] - * - 0x62 [00] - * Note: this can also be seen as lower byte of a Galois/Fibonacci LFSR-16, gen 0x00, init 0x3100 (or 0x62 if reversed) resetting at every byte. - * Battery voltages: - * - U=<2,65V +- ~5% Battery indicator - * - U=>2.10C +- 5% plausible readings - * - U=2,00V +- ~5% Temperature offset -5°C Humidity offset unknown - * - U=<1,95V +- ~5% does not initialize anymore - * - U=1,90V +- 5% temperature offset -15°C - * - U=1,80V +- 5% Display is showing refresh pattern - * - U=1.75V +- ~5% TX causes cut out - * - */ - -static const SubGhzBlockConst ws_protocol_gt_wt_03_const = { - .te_short = 285, - .te_long = 570, - .te_delta = 120, - .min_count_bit_for_found = 41, -}; - -struct WSProtocolDecoderGT_WT03 { - SubGhzProtocolDecoderBase base; - - SubGhzBlockDecoder decoder; - WSBlockGeneric generic; - - uint16_t header_count; -}; - -struct WSProtocolEncoderGT_WT03 { - SubGhzProtocolEncoderBase base; - - SubGhzProtocolBlockEncoder encoder; - WSBlockGeneric generic; -}; - -typedef enum { - GT_WT03DecoderStepReset = 0, - GT_WT03DecoderStepCheckPreambule, - GT_WT03DecoderStepSaveDuration, - GT_WT03DecoderStepCheckDuration, -} GT_WT03DecoderStep; - -const SubGhzProtocolDecoder ws_protocol_gt_wt_03_decoder = { - .alloc = ws_protocol_decoder_gt_wt_03_alloc, - .free = ws_protocol_decoder_gt_wt_03_free, - - .feed = ws_protocol_decoder_gt_wt_03_feed, - .reset = ws_protocol_decoder_gt_wt_03_reset, - - .get_hash_data = ws_protocol_decoder_gt_wt_03_get_hash_data, - .serialize = ws_protocol_decoder_gt_wt_03_serialize, - .deserialize = ws_protocol_decoder_gt_wt_03_deserialize, - .get_string = ws_protocol_decoder_gt_wt_03_get_string, -}; - -const SubGhzProtocolEncoder ws_protocol_gt_wt_03_encoder = { - .alloc = NULL, - .free = NULL, - - .deserialize = NULL, - .stop = NULL, - .yield = NULL, -}; - -const SubGhzProtocol ws_protocol_gt_wt_03 = { - .name = WS_PROTOCOL_GT_WT_03_NAME, - .type = SubGhzProtocolWeatherStation, - .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | - SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable, - - .decoder = &ws_protocol_gt_wt_03_decoder, - .encoder = &ws_protocol_gt_wt_03_encoder, -}; - -void* ws_protocol_decoder_gt_wt_03_alloc(SubGhzEnvironment* environment) { - UNUSED(environment); - WSProtocolDecoderGT_WT03* instance = malloc(sizeof(WSProtocolDecoderGT_WT03)); - instance->base.protocol = &ws_protocol_gt_wt_03; - instance->generic.protocol_name = instance->base.protocol->name; - return instance; -} - -void ws_protocol_decoder_gt_wt_03_free(void* context) { - furi_assert(context); - WSProtocolDecoderGT_WT03* instance = context; - free(instance); -} - -void ws_protocol_decoder_gt_wt_03_reset(void* context) { - furi_assert(context); - WSProtocolDecoderGT_WT03* instance = context; - instance->decoder.parser_step = GT_WT03DecoderStepReset; -} - -static bool ws_protocol_gt_wt_03_check_crc(WSProtocolDecoderGT_WT03* instance) { - uint8_t msg[] = { - instance->decoder.decode_data >> 33, - instance->decoder.decode_data >> 25, - instance->decoder.decode_data >> 17, - instance->decoder.decode_data >> 9}; - - uint8_t sum = 0; - for(unsigned k = 0; k < sizeof(msg); ++k) { - uint8_t data = msg[k]; - uint16_t key = 0x3100; - for(int i = 7; i >= 0; --i) { - // XOR key into sum if data bit is set - if((data >> i) & 1) sum ^= key & 0xff; - // roll the key right - key = (key >> 1); - } - } - return ((sum ^ (uint8_t)((instance->decoder.decode_data >> 1) & 0xFF)) == 0x2D); -} - -/** - * Analysis of received data - * @param instance Pointer to a WSBlockGeneric* instance - */ -static void ws_protocol_gt_wt_03_remote_controller(WSBlockGeneric* instance) { - instance->id = instance->data >> 33; - instance->humidity = (instance->data >> 25) & 0xFF; - - if(instance->humidity <= 10) { // actually the sensors sends 10 below working range of 20% - instance->humidity = 0; - } else if(instance->humidity > 95) { // actually the sensors sends 110 above working range of 90% - instance->humidity = 100; - } - - instance->battery_low = (instance->data >> 24) & 1; - instance->btn = (instance->data >> 23) & 1; - instance->channel = ((instance->data >> 21) & 0x03) + 1; - - if(!((instance->data >> 20) & 1)) { - instance->temp = (float)((instance->data >> 9) & 0x07FF) / 10.0f; - } else { - instance->temp = (float)((~(instance->data >> 9) & 0x07FF) + 1) / -10.0f; - } -} - -void ws_protocol_decoder_gt_wt_03_feed(void* context, bool level, uint32_t duration) { - furi_assert(context); - WSProtocolDecoderGT_WT03* instance = context; - - switch(instance->decoder.parser_step) { - case GT_WT03DecoderStepReset: - if((level) && (DURATION_DIFF(duration, ws_protocol_gt_wt_03_const.te_short * 3) < - ws_protocol_gt_wt_03_const.te_delta * 2)) { - instance->decoder.parser_step = GT_WT03DecoderStepCheckPreambule; - instance->decoder.te_last = duration; - instance->header_count = 0; - } - break; - - case GT_WT03DecoderStepCheckPreambule: - if(level) { - instance->decoder.te_last = duration; - } else { - if((DURATION_DIFF(instance->decoder.te_last, ws_protocol_gt_wt_03_const.te_short * 3) < - ws_protocol_gt_wt_03_const.te_delta * 2) && - (DURATION_DIFF(duration, ws_protocol_gt_wt_03_const.te_short * 3) < - ws_protocol_gt_wt_03_const.te_delta * 2)) { - //Found preambule - instance->header_count++; - } else if(instance->header_count == 4) { - if((DURATION_DIFF(instance->decoder.te_last, ws_protocol_gt_wt_03_const.te_short) < - ws_protocol_gt_wt_03_const.te_delta) && - (DURATION_DIFF(duration, ws_protocol_gt_wt_03_const.te_long) < - ws_protocol_gt_wt_03_const.te_delta)) { - instance->decoder.decode_data = 0; - instance->decoder.decode_count_bit = 0; - subghz_protocol_blocks_add_bit(&instance->decoder, 0); - instance->decoder.parser_step = GT_WT03DecoderStepSaveDuration; - } else if( - (DURATION_DIFF(instance->decoder.te_last, ws_protocol_gt_wt_03_const.te_long) < - ws_protocol_gt_wt_03_const.te_delta) && - (DURATION_DIFF(duration, ws_protocol_gt_wt_03_const.te_short) < - ws_protocol_gt_wt_03_const.te_delta)) { - instance->decoder.decode_data = 0; - instance->decoder.decode_count_bit = 0; - subghz_protocol_blocks_add_bit(&instance->decoder, 1); - instance->decoder.parser_step = GT_WT03DecoderStepSaveDuration; - } else { - instance->decoder.parser_step = GT_WT03DecoderStepReset; - } - } else { - instance->decoder.parser_step = GT_WT03DecoderStepReset; - } - } - break; - - case GT_WT03DecoderStepSaveDuration: - if(level) { - instance->decoder.te_last = duration; - instance->decoder.parser_step = GT_WT03DecoderStepCheckDuration; - } else { - instance->decoder.parser_step = GT_WT03DecoderStepReset; - } - break; - - case GT_WT03DecoderStepCheckDuration: - if(!level) { - if(((DURATION_DIFF(instance->decoder.te_last, ws_protocol_gt_wt_03_const.te_short * 3) < - ws_protocol_gt_wt_03_const.te_delta * 2) && - (DURATION_DIFF(duration, ws_protocol_gt_wt_03_const.te_short * 3) < - ws_protocol_gt_wt_03_const.te_delta * 2))) { - if((instance->decoder.decode_count_bit == - ws_protocol_gt_wt_03_const.min_count_bit_for_found) && - ws_protocol_gt_wt_03_check_crc(instance)) { - instance->generic.data = instance->decoder.decode_data; - instance->generic.data_count_bit = instance->decoder.decode_count_bit; - ws_protocol_gt_wt_03_remote_controller(&instance->generic); - if(instance->base.callback) - instance->base.callback(&instance->base, instance->base.context); - } - instance->decoder.decode_data = 0; - instance->decoder.decode_count_bit = 0; - instance->header_count = 1; - instance->decoder.parser_step = GT_WT03DecoderStepCheckPreambule; - break; - } else if( - (DURATION_DIFF(instance->decoder.te_last, ws_protocol_gt_wt_03_const.te_short) < - ws_protocol_gt_wt_03_const.te_delta) && - (DURATION_DIFF(duration, ws_protocol_gt_wt_03_const.te_long) < - ws_protocol_gt_wt_03_const.te_delta)) { - subghz_protocol_blocks_add_bit(&instance->decoder, 0); - instance->decoder.parser_step = GT_WT03DecoderStepSaveDuration; - } else if( - (DURATION_DIFF(instance->decoder.te_last, ws_protocol_gt_wt_03_const.te_long) < - ws_protocol_gt_wt_03_const.te_delta) && - (DURATION_DIFF(duration, ws_protocol_gt_wt_03_const.te_short) < - ws_protocol_gt_wt_03_const.te_delta)) { - subghz_protocol_blocks_add_bit(&instance->decoder, 1); - instance->decoder.parser_step = GT_WT03DecoderStepSaveDuration; - } else { - instance->decoder.parser_step = GT_WT03DecoderStepReset; - } - } else { - instance->decoder.parser_step = GT_WT03DecoderStepReset; - } - break; - } -} - -uint8_t ws_protocol_decoder_gt_wt_03_get_hash_data(void* context) { - furi_assert(context); - WSProtocolDecoderGT_WT03* instance = context; - return subghz_protocol_blocks_get_hash_data( - &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); -} - -SubGhzProtocolStatus ws_protocol_decoder_gt_wt_03_serialize( - void* context, - FlipperFormat* flipper_format, - SubGhzRadioPreset* preset) { - furi_assert(context); - WSProtocolDecoderGT_WT03* instance = context; - return ws_block_generic_serialize(&instance->generic, flipper_format, preset); -} - -SubGhzProtocolStatus - ws_protocol_decoder_gt_wt_03_deserialize(void* context, FlipperFormat* flipper_format) { - furi_assert(context); - WSProtocolDecoderGT_WT03* instance = context; - return ws_block_generic_deserialize_check_count_bit( - &instance->generic, flipper_format, ws_protocol_gt_wt_03_const.min_count_bit_for_found); -} - -void ws_protocol_decoder_gt_wt_03_get_string(void* context, FuriString* output) { - furi_assert(context); - WSProtocolDecoderGT_WT03* instance = context; - furi_string_printf( - output, - "%s %dbit\r\n" - "Key:0x%lX%08lX\r\n" - "Sn:0x%lX Ch:%d Bat:%d\r\n" - "Temp:%3.1f C Hum:%d%%", - instance->generic.protocol_name, - instance->generic.data_count_bit, - (uint32_t)(instance->generic.data >> 32), - (uint32_t)(instance->generic.data), - instance->generic.id, - instance->generic.channel, - instance->generic.battery_low, - (double)instance->generic.temp, - instance->generic.humidity); -} diff --git a/applications/external/weather_station/protocols/gt_wt_03.h b/applications/external/weather_station/protocols/gt_wt_03.h deleted file mode 100644 index d566bb399c7..00000000000 --- a/applications/external/weather_station/protocols/gt_wt_03.h +++ /dev/null @@ -1,80 +0,0 @@ -#pragma once - -#include - -#include -#include -#include -#include "ws_generic.h" -#include - -#define WS_PROTOCOL_GT_WT_03_NAME "GT-WT03" - -typedef struct WSProtocolDecoderGT_WT03 WSProtocolDecoderGT_WT03; -typedef struct WSProtocolEncoderGT_WT03 WSProtocolEncoderGT_WT03; - -extern const SubGhzProtocolDecoder ws_protocol_gt_wt_03_decoder; -extern const SubGhzProtocolEncoder ws_protocol_gt_wt_03_encoder; -extern const SubGhzProtocol ws_protocol_gt_wt_03; - -/** - * Allocate WSProtocolDecoderGT_WT03. - * @param environment Pointer to a SubGhzEnvironment instance - * @return WSProtocolDecoderGT_WT03* pointer to a WSProtocolDecoderGT_WT03 instance - */ -void* ws_protocol_decoder_gt_wt_03_alloc(SubGhzEnvironment* environment); - -/** - * Free WSProtocolDecoderGT_WT03. - * @param context Pointer to a WSProtocolDecoderGT_WT03 instance - */ -void ws_protocol_decoder_gt_wt_03_free(void* context); - -/** - * Reset decoder WSProtocolDecoderGT_WT03. - * @param context Pointer to a WSProtocolDecoderGT_WT03 instance - */ -void ws_protocol_decoder_gt_wt_03_reset(void* context); - -/** - * Parse a raw sequence of levels and durations received from the air. - * @param context Pointer to a WSProtocolDecoderGT_WT03 instance - * @param level Signal level true-high false-low - * @param duration Duration of this level in, us - */ -void ws_protocol_decoder_gt_wt_03_feed(void* context, bool level, uint32_t duration); - -/** - * Getting the hash sum of the last randomly received parcel. - * @param context Pointer to a WSProtocolDecoderGT_WT03 instance - * @return hash Hash sum - */ -uint8_t ws_protocol_decoder_gt_wt_03_get_hash_data(void* context); - -/** - * Serialize data WSProtocolDecoderGT_WT03. - * @param context Pointer to a WSProtocolDecoderGT_WT03 instance - * @param flipper_format Pointer to a FlipperFormat instance - * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return status - */ -SubGhzProtocolStatus ws_protocol_decoder_gt_wt_03_serialize( - void* context, - FlipperFormat* flipper_format, - SubGhzRadioPreset* preset); - -/** - * Deserialize data WSProtocolDecoderGT_WT03. - * @param context Pointer to a WSProtocolDecoderGT_WT03 instance - * @param flipper_format Pointer to a FlipperFormat instance - * @return status - */ -SubGhzProtocolStatus - ws_protocol_decoder_gt_wt_03_deserialize(void* context, FlipperFormat* flipper_format); - -/** - * Getting a textual representation of the received data. - * @param context Pointer to a WSProtocolDecoderGT_WT03 instance - * @param output Resulting text - */ -void ws_protocol_decoder_gt_wt_03_get_string(void* context, FuriString* output); diff --git a/applications/external/weather_station/protocols/infactory.c b/applications/external/weather_station/protocols/infactory.c deleted file mode 100644 index 4b0e6602a53..00000000000 --- a/applications/external/weather_station/protocols/infactory.c +++ /dev/null @@ -1,286 +0,0 @@ -#include "infactory.h" - -#define TAG "WSProtocolInfactory" - -/* - * Help - * https://github.com/merbanan/rtl_433/blob/master/src/devices/infactory.c - * - * Analysis using Genuino (see http://gitlab.com/hp-uno, e.g. uno_log_433): - * Observed On-Off-Key (OOK) data pattern: - * preamble syncPrefix data...(40 bit) syncPostfix - * HHLL HHLL HHLL HHLL HLLLLLLLLLLLLLLLL (HLLLL HLLLLLLLL HLLLL HLLLLLLLL ....) HLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL - * Breakdown: - * - four preamble pairs '1'/'0' each with a length of ca. 1000us - * - syncPre, syncPost, data0, data1 have a '1' start pulse of ca. 500us - * - syncPre pulse before dataPtr has a '0' pulse length of ca. 8000us - * - data0 (0-bits) have then a '0' pulse length of ca. 2000us - * - data1 (1-bits) have then a '0' pulse length of ca. 4000us - * - syncPost after dataPtr has a '0' pulse length of ca. 16000us - * This analysis is the reason for the new r_device definitions below. - * NB: pulse_slicer_ppm does not use .gap_limit if .tolerance is set. - * - * Outdoor sensor, transmits temperature and humidity data - * - inFactory NC-3982-913/NX-5817-902, Pearl (for FWS-686 station) - * - nor-tec 73383 (weather station + sensor), Schou Company AS, Denmark - * - DAY 73365 (weather station + sensor), Schou Company AS, Denmark - * Known brand names: inFactory, nor-tec, GreenBlue, DAY. Manufacturer in China. - * Transmissions includes an id. Every 60 seconds the sensor transmits 6 packets: - * 0000 1111 | 0011 0000 | 0101 1100 | 1110 0111 | 0110 0001 - * iiii iiii | cccc ub?? | tttt tttt | tttt hhhh | hhhh ??nn - * - i: identification; changes on battery switch - * - c: CRC-4; CCITT checksum, see below for computation specifics - * - u: unknown; (sometimes set at power-on, but not always) - * - b: battery low; flag to indicate low battery voltage - * - h: Humidity; BCD-encoded, each nibble is one digit, 'A0' means 100%rH - * - t: Temperature; in °F as binary number with one decimal place + 90 °F offset - * - n: Channel; Channel number 1 - 3 - * - */ - -static const SubGhzBlockConst ws_protocol_infactory_const = { - .te_short = 500, - .te_long = 2000, - .te_delta = 150, - .min_count_bit_for_found = 40, -}; - -struct WSProtocolDecoderInfactory { - SubGhzProtocolDecoderBase base; - - SubGhzBlockDecoder decoder; - WSBlockGeneric generic; - - uint16_t header_count; -}; - -struct WSProtocolEncoderInfactory { - SubGhzProtocolEncoderBase base; - - SubGhzProtocolBlockEncoder encoder; - WSBlockGeneric generic; -}; - -typedef enum { - InfactoryDecoderStepReset = 0, - InfactoryDecoderStepCheckPreambule, - InfactoryDecoderStepSaveDuration, - InfactoryDecoderStepCheckDuration, -} InfactoryDecoderStep; - -const SubGhzProtocolDecoder ws_protocol_infactory_decoder = { - .alloc = ws_protocol_decoder_infactory_alloc, - .free = ws_protocol_decoder_infactory_free, - - .feed = ws_protocol_decoder_infactory_feed, - .reset = ws_protocol_decoder_infactory_reset, - - .get_hash_data = ws_protocol_decoder_infactory_get_hash_data, - .serialize = ws_protocol_decoder_infactory_serialize, - .deserialize = ws_protocol_decoder_infactory_deserialize, - .get_string = ws_protocol_decoder_infactory_get_string, -}; - -const SubGhzProtocolEncoder ws_protocol_infactory_encoder = { - .alloc = NULL, - .free = NULL, - - .deserialize = NULL, - .stop = NULL, - .yield = NULL, -}; - -const SubGhzProtocol ws_protocol_infactory = { - .name = WS_PROTOCOL_INFACTORY_NAME, - .type = SubGhzProtocolWeatherStation, - .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | - SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable, - - .decoder = &ws_protocol_infactory_decoder, - .encoder = &ws_protocol_infactory_encoder, -}; - -void* ws_protocol_decoder_infactory_alloc(SubGhzEnvironment* environment) { - UNUSED(environment); - WSProtocolDecoderInfactory* instance = malloc(sizeof(WSProtocolDecoderInfactory)); - instance->base.protocol = &ws_protocol_infactory; - instance->generic.protocol_name = instance->base.protocol->name; - return instance; -} - -void ws_protocol_decoder_infactory_free(void* context) { - furi_assert(context); - WSProtocolDecoderInfactory* instance = context; - free(instance); -} - -void ws_protocol_decoder_infactory_reset(void* context) { - furi_assert(context); - WSProtocolDecoderInfactory* instance = context; - instance->decoder.parser_step = InfactoryDecoderStepReset; -} - -static bool ws_protocol_infactory_check_crc(WSProtocolDecoderInfactory* instance) { - uint8_t msg[] = { - instance->decoder.decode_data >> 32, - (((instance->decoder.decode_data >> 24) & 0x0F) | (instance->decoder.decode_data & 0x0F) - << 4), - instance->decoder.decode_data >> 16, - instance->decoder.decode_data >> 8, - instance->decoder.decode_data}; - - uint8_t crc = - subghz_protocol_blocks_crc4(msg, 4, 0x13, 0); // Koopmann 0x9, CCITT-4; FP-4; ITU-T G.704 - crc ^= msg[4] >> 4; // last nibble is only XORed - return (crc == ((instance->decoder.decode_data >> 28) & 0x0F)); -} - -/** - * Analysis of received data - * @param instance Pointer to a WSBlockGeneric* instance - */ -static void ws_protocol_infactory_remote_controller(WSBlockGeneric* instance) { - instance->id = instance->data >> 32; - instance->battery_low = (instance->data >> 26) & 1; - instance->btn = WS_NO_BTN; - instance->temp = - locale_fahrenheit_to_celsius(((float)((instance->data >> 12) & 0x0FFF) - 900.0f) / 10.0f); - instance->humidity = - (((instance->data >> 8) & 0x0F) * 10) + ((instance->data >> 4) & 0x0F); // BCD, 'A0'=100%rH - instance->channel = instance->data & 0x03; -} - -void ws_protocol_decoder_infactory_feed(void* context, bool level, uint32_t duration) { - furi_assert(context); - WSProtocolDecoderInfactory* instance = context; - - switch(instance->decoder.parser_step) { - case InfactoryDecoderStepReset: - if((level) && (DURATION_DIFF(duration, ws_protocol_infactory_const.te_short * 2) < - ws_protocol_infactory_const.te_delta * 2)) { - instance->decoder.parser_step = InfactoryDecoderStepCheckPreambule; - instance->decoder.te_last = duration; - instance->header_count = 0; - } - break; - - case InfactoryDecoderStepCheckPreambule: - if(level) { - instance->decoder.te_last = duration; - } else { - if((DURATION_DIFF(instance->decoder.te_last, ws_protocol_infactory_const.te_short * 2) < - ws_protocol_infactory_const.te_delta * 2) && - (DURATION_DIFF(duration, ws_protocol_infactory_const.te_short * 2) < - ws_protocol_infactory_const.te_delta * 2)) { - //Found preambule - instance->header_count++; - } else if( - (DURATION_DIFF(instance->decoder.te_last, ws_protocol_infactory_const.te_short) < - ws_protocol_infactory_const.te_delta) && - (DURATION_DIFF(duration, ws_protocol_infactory_const.te_short * 16) < - ws_protocol_infactory_const.te_delta * 8)) { - //Found syncPrefix - if(instance->header_count > 3) { - instance->decoder.parser_step = InfactoryDecoderStepSaveDuration; - instance->decoder.decode_data = 0; - instance->decoder.decode_count_bit = 0; - } - } else { - instance->decoder.parser_step = InfactoryDecoderStepReset; - } - } - break; - - case InfactoryDecoderStepSaveDuration: - if(level) { - instance->decoder.te_last = duration; - instance->decoder.parser_step = InfactoryDecoderStepCheckDuration; - } else { - instance->decoder.parser_step = InfactoryDecoderStepReset; - } - break; - - case InfactoryDecoderStepCheckDuration: - if(!level) { - if(duration >= ((uint32_t)ws_protocol_infactory_const.te_short * 30)) { - //Found syncPostfix - if((instance->decoder.decode_count_bit == - ws_protocol_infactory_const.min_count_bit_for_found) && - ws_protocol_infactory_check_crc(instance)) { - instance->generic.data = instance->decoder.decode_data; - instance->generic.data_count_bit = instance->decoder.decode_count_bit; - ws_protocol_infactory_remote_controller(&instance->generic); - if(instance->base.callback) - instance->base.callback(&instance->base, instance->base.context); - } - instance->decoder.decode_data = 0; - instance->decoder.decode_count_bit = 0; - instance->decoder.parser_step = InfactoryDecoderStepReset; - break; - } else if( - (DURATION_DIFF(instance->decoder.te_last, ws_protocol_infactory_const.te_short) < - ws_protocol_infactory_const.te_delta) && - (DURATION_DIFF(duration, ws_protocol_infactory_const.te_long) < - ws_protocol_infactory_const.te_delta * 2)) { - subghz_protocol_blocks_add_bit(&instance->decoder, 0); - instance->decoder.parser_step = InfactoryDecoderStepSaveDuration; - } else if( - (DURATION_DIFF(instance->decoder.te_last, ws_protocol_infactory_const.te_short) < - ws_protocol_infactory_const.te_delta) && - (DURATION_DIFF(duration, ws_protocol_infactory_const.te_long * 2) < - ws_protocol_infactory_const.te_delta * 4)) { - subghz_protocol_blocks_add_bit(&instance->decoder, 1); - instance->decoder.parser_step = InfactoryDecoderStepSaveDuration; - } else { - instance->decoder.parser_step = InfactoryDecoderStepReset; - } - } else { - instance->decoder.parser_step = InfactoryDecoderStepReset; - } - break; - } -} - -uint8_t ws_protocol_decoder_infactory_get_hash_data(void* context) { - furi_assert(context); - WSProtocolDecoderInfactory* instance = context; - return subghz_protocol_blocks_get_hash_data( - &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); -} - -SubGhzProtocolStatus ws_protocol_decoder_infactory_serialize( - void* context, - FlipperFormat* flipper_format, - SubGhzRadioPreset* preset) { - furi_assert(context); - WSProtocolDecoderInfactory* instance = context; - return ws_block_generic_serialize(&instance->generic, flipper_format, preset); -} - -SubGhzProtocolStatus - ws_protocol_decoder_infactory_deserialize(void* context, FlipperFormat* flipper_format) { - furi_assert(context); - WSProtocolDecoderInfactory* instance = context; - return ws_block_generic_deserialize_check_count_bit( - &instance->generic, flipper_format, ws_protocol_infactory_const.min_count_bit_for_found); -} - -void ws_protocol_decoder_infactory_get_string(void* context, FuriString* output) { - furi_assert(context); - WSProtocolDecoderInfactory* instance = context; - furi_string_printf( - output, - "%s %dbit\r\n" - "Key:0x%lX%08lX\r\n" - "Sn:0x%lX Ch:%d Bat:%d\r\n" - "Temp:%3.1f C Hum:%d%%", - instance->generic.protocol_name, - instance->generic.data_count_bit, - (uint32_t)(instance->generic.data >> 32), - (uint32_t)(instance->generic.data), - instance->generic.id, - instance->generic.channel, - instance->generic.battery_low, - (double)instance->generic.temp, - instance->generic.humidity); -} diff --git a/applications/external/weather_station/protocols/infactory.h b/applications/external/weather_station/protocols/infactory.h deleted file mode 100644 index 9431a067ea9..00000000000 --- a/applications/external/weather_station/protocols/infactory.h +++ /dev/null @@ -1,80 +0,0 @@ -#pragma once - -#include - -#include -#include -#include -#include "ws_generic.h" -#include - -#define WS_PROTOCOL_INFACTORY_NAME "inFactory-TH" - -typedef struct WSProtocolDecoderInfactory WSProtocolDecoderInfactory; -typedef struct WSProtocolEncoderInfactory WSProtocolEncoderInfactory; - -extern const SubGhzProtocolDecoder ws_protocol_infactory_decoder; -extern const SubGhzProtocolEncoder ws_protocol_infactory_encoder; -extern const SubGhzProtocol ws_protocol_infactory; - -/** - * Allocate WSProtocolDecoderInfactory. - * @param environment Pointer to a SubGhzEnvironment instance - * @return WSProtocolDecoderInfactory* pointer to a WSProtocolDecoderInfactory instance - */ -void* ws_protocol_decoder_infactory_alloc(SubGhzEnvironment* environment); - -/** - * Free WSProtocolDecoderInfactory. - * @param context Pointer to a WSProtocolDecoderInfactory instance - */ -void ws_protocol_decoder_infactory_free(void* context); - -/** - * Reset decoder WSProtocolDecoderInfactory. - * @param context Pointer to a WSProtocolDecoderInfactory instance - */ -void ws_protocol_decoder_infactory_reset(void* context); - -/** - * Parse a raw sequence of levels and durations received from the air. - * @param context Pointer to a WSProtocolDecoderInfactory instance - * @param level Signal level true-high false-low - * @param duration Duration of this level in, us - */ -void ws_protocol_decoder_infactory_feed(void* context, bool level, uint32_t duration); - -/** - * Getting the hash sum of the last randomly received parcel. - * @param context Pointer to a WSProtocolDecoderInfactory instance - * @return hash Hash sum - */ -uint8_t ws_protocol_decoder_infactory_get_hash_data(void* context); - -/** - * Serialize data WSProtocolDecoderInfactory. - * @param context Pointer to a WSProtocolDecoderInfactory instance - * @param flipper_format Pointer to a FlipperFormat instance - * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return status - */ -SubGhzProtocolStatus ws_protocol_decoder_infactory_serialize( - void* context, - FlipperFormat* flipper_format, - SubGhzRadioPreset* preset); - -/** - * Deserialize data WSProtocolDecoderInfactory. - * @param context Pointer to a WSProtocolDecoderInfactory instance - * @param flipper_format Pointer to a FlipperFormat instance - * @return status - */ -SubGhzProtocolStatus - ws_protocol_decoder_infactory_deserialize(void* context, FlipperFormat* flipper_format); - -/** - * Getting a textual representation of the received data. - * @param context Pointer to a WSProtocolDecoderInfactory instance - * @param output Resulting text - */ -void ws_protocol_decoder_infactory_get_string(void* context, FuriString* output); diff --git a/applications/external/weather_station/protocols/lacrosse_tx.c b/applications/external/weather_station/protocols/lacrosse_tx.c deleted file mode 100644 index f4b850d7693..00000000000 --- a/applications/external/weather_station/protocols/lacrosse_tx.c +++ /dev/null @@ -1,319 +0,0 @@ -#include "lacrosse_tx.h" - -#define TAG "WSProtocolLaCrosse_TX" - -/* - * Help - * https://github.com/merbanan/rtl_433/blob/master/src/devices/lacrosse.c - * - * - * LaCrosse TX 433 Mhz Temperature and Humidity Sensors. - * - Tested: TX-7U and TX-6U (Temperature only) - * - Not Tested but should work: TX-3, TX-4 - * - also TFA Dostmann 30.3120.90 sensor (for e.g. 35.1018.06 (WS-9015) station) - * - also TFA Dostmann 30.3121 sensor - * Protocol Documentation: http://www.f6fbb.org/domo/sensors/tx3_th.php - * Message is 44 bits, 11 x 4 bit nybbles: - * [00] [cnt = 10] [type] [addr] [addr + parity] [v1] [v2] [v3] [iv1] [iv2] [check] - * Notes: - * - Zero Pulses are longer (1,400 uS High, 1,000 uS Low) = 2,400 uS - * - One Pulses are shorter ( 550 uS High, 1,000 uS Low) = 1,600 uS - * - Sensor id changes when the battery is changed - * - Primary Value are BCD with one decimal place: vvv = 12.3 - * - Secondary value is integer only intval = 12, seems to be a repeat of primary - * This may actually be an additional data check because the 4 bit checksum - * and parity bit is pretty week at detecting errors. - * - Temperature is in Celsius with 50.0 added (to handle negative values) - * - Humidity values appear to be integer precision, decimal always 0. - * - There is a 4 bit checksum and a parity bit covering the three digit value - * - Parity check for TX-3 and TX-4 might be different. - * - Msg sent with one repeat after 30 mS - * - Temperature and humidity are sent as separate messages - * - Frequency for each sensor may be could be off by as much as 50-75 khz - * - LaCrosse Sensors in other frequency ranges (915 Mhz) use FSK not OOK - * so they can't be decoded by rtl_433 currently. - * - Temperature and Humidity are sent in different messages bursts. -*/ - -#define LACROSSE_TX_GAP 1000 -#define LACROSSE_TX_BIT_SIZE 44 -#define LACROSSE_TX_SUNC_PATTERN 0x0A000000000 -#define LACROSSE_TX_SUNC_MASK 0x0F000000000 -#define LACROSSE_TX_MSG_TYPE_TEMP 0x00 -#define LACROSSE_TX_MSG_TYPE_HUM 0x0E - -static const SubGhzBlockConst ws_protocol_lacrosse_tx_const = { - .te_short = 550, - .te_long = 1300, - .te_delta = 120, - .min_count_bit_for_found = 40, -}; - -struct WSProtocolDecoderLaCrosse_TX { - SubGhzProtocolDecoderBase base; - - SubGhzBlockDecoder decoder; - WSBlockGeneric generic; - - uint16_t header_count; -}; - -struct WSProtocolEncoderLaCrosse_TX { - SubGhzProtocolEncoderBase base; - - SubGhzProtocolBlockEncoder encoder; - WSBlockGeneric generic; -}; - -typedef enum { - LaCrosse_TXDecoderStepReset = 0, - LaCrosse_TXDecoderStepCheckPreambule, - LaCrosse_TXDecoderStepSaveDuration, - LaCrosse_TXDecoderStepCheckDuration, -} LaCrosse_TXDecoderStep; - -const SubGhzProtocolDecoder ws_protocol_lacrosse_tx_decoder = { - .alloc = ws_protocol_decoder_lacrosse_tx_alloc, - .free = ws_protocol_decoder_lacrosse_tx_free, - - .feed = ws_protocol_decoder_lacrosse_tx_feed, - .reset = ws_protocol_decoder_lacrosse_tx_reset, - - .get_hash_data = ws_protocol_decoder_lacrosse_tx_get_hash_data, - .serialize = ws_protocol_decoder_lacrosse_tx_serialize, - .deserialize = ws_protocol_decoder_lacrosse_tx_deserialize, - .get_string = ws_protocol_decoder_lacrosse_tx_get_string, -}; - -const SubGhzProtocolEncoder ws_protocol_lacrosse_tx_encoder = { - .alloc = NULL, - .free = NULL, - - .deserialize = NULL, - .stop = NULL, - .yield = NULL, -}; - -const SubGhzProtocol ws_protocol_lacrosse_tx = { - .name = WS_PROTOCOL_LACROSSE_TX_NAME, - .type = SubGhzProtocolWeatherStation, - .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | - SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable, - - .decoder = &ws_protocol_lacrosse_tx_decoder, - .encoder = &ws_protocol_lacrosse_tx_encoder, -}; - -void* ws_protocol_decoder_lacrosse_tx_alloc(SubGhzEnvironment* environment) { - UNUSED(environment); - WSProtocolDecoderLaCrosse_TX* instance = malloc(sizeof(WSProtocolDecoderLaCrosse_TX)); - instance->base.protocol = &ws_protocol_lacrosse_tx; - instance->generic.protocol_name = instance->base.protocol->name; - return instance; -} - -void ws_protocol_decoder_lacrosse_tx_free(void* context) { - furi_assert(context); - WSProtocolDecoderLaCrosse_TX* instance = context; - free(instance); -} - -void ws_protocol_decoder_lacrosse_tx_reset(void* context) { - furi_assert(context); - WSProtocolDecoderLaCrosse_TX* instance = context; - instance->header_count = 0; - instance->decoder.parser_step = LaCrosse_TXDecoderStepReset; -} - -static bool ws_protocol_lacrosse_tx_check_crc(WSProtocolDecoderLaCrosse_TX* instance) { - if(!instance->decoder.decode_data) return false; - uint8_t msg[] = { - (instance->decoder.decode_data >> 36) & 0x0F, - (instance->decoder.decode_data >> 32) & 0x0F, - (instance->decoder.decode_data >> 28) & 0x0F, - (instance->decoder.decode_data >> 24) & 0x0F, - (instance->decoder.decode_data >> 20) & 0x0F, - (instance->decoder.decode_data >> 16) & 0x0F, - (instance->decoder.decode_data >> 12) & 0x0F, - (instance->decoder.decode_data >> 8) & 0x0F, - (instance->decoder.decode_data >> 4) & 0x0F}; - - uint8_t crc = subghz_protocol_blocks_add_bytes(msg, 9); - return ((crc & 0x0F) == ((instance->decoder.decode_data) & 0x0F)); -} - -/** - * Analysis of received data - * @param instance Pointer to a WSBlockGeneric* instance - */ -static void ws_protocol_lacrosse_tx_remote_controller(WSBlockGeneric* instance) { - uint8_t msg_type = (instance->data >> 32) & 0x0F; - instance->id = (((instance->data >> 28) & 0x0F) << 3) | (((instance->data >> 24) & 0x0F) >> 1); - - float msg_value = (float)((instance->data >> 20) & 0x0F) * 10.0f + - (float)((instance->data >> 16) & 0x0F) + - (float)((instance->data >> 12) & 0x0F) * 0.1f; - - if(msg_type == LACROSSE_TX_MSG_TYPE_TEMP) { //-V1051 - instance->temp = msg_value - 50.0f; - instance->humidity = WS_NO_HUMIDITY; - } else if(msg_type == LACROSSE_TX_MSG_TYPE_HUM) { - //ToDo for verification, records are needed with sensors maintaining temperature and temperature for this standard - instance->humidity = (uint8_t)msg_value; - } else { - furi_crash("WS: WSProtocolLaCrosse_TX incorrect msg_type."); - } - - instance->btn = WS_NO_BTN; - instance->battery_low = WS_NO_BATT; - instance->channel = WS_NO_CHANNEL; -} - -void ws_protocol_decoder_lacrosse_tx_feed(void* context, bool level, uint32_t duration) { - furi_assert(context); - WSProtocolDecoderLaCrosse_TX* instance = context; - - switch(instance->decoder.parser_step) { - case LaCrosse_TXDecoderStepReset: - if((!level) && (DURATION_DIFF(duration, LACROSSE_TX_GAP) < - ws_protocol_lacrosse_tx_const.te_delta * 2)) { - instance->decoder.parser_step = LaCrosse_TXDecoderStepCheckPreambule; - instance->header_count = 0; - } - break; - - case LaCrosse_TXDecoderStepCheckPreambule: - - if(level) { - if((DURATION_DIFF(duration, ws_protocol_lacrosse_tx_const.te_short) < - ws_protocol_lacrosse_tx_const.te_delta) && - (instance->header_count > 1)) { - instance->decoder.parser_step = LaCrosse_TXDecoderStepCheckDuration; - instance->decoder.decode_data = 0; - instance->decoder.decode_count_bit = 0; - instance->decoder.te_last = duration; - } else if(duration > (ws_protocol_lacrosse_tx_const.te_long * 2)) { - instance->decoder.parser_step = LaCrosse_TXDecoderStepReset; - } - } else { - if(DURATION_DIFF(duration, LACROSSE_TX_GAP) < - ws_protocol_lacrosse_tx_const.te_delta * 2) { - instance->decoder.te_last = duration; - instance->header_count++; - } else { - instance->decoder.parser_step = LaCrosse_TXDecoderStepReset; - } - } - - break; - - case LaCrosse_TXDecoderStepSaveDuration: - if(level) { - instance->decoder.te_last = duration; - instance->decoder.parser_step = LaCrosse_TXDecoderStepCheckDuration; - } else { - instance->decoder.parser_step = LaCrosse_TXDecoderStepReset; - } - break; - - case LaCrosse_TXDecoderStepCheckDuration: - - if(!level) { - if(duration > LACROSSE_TX_GAP * 3) { - if(DURATION_DIFF( - instance->decoder.te_last, ws_protocol_lacrosse_tx_const.te_short) < - ws_protocol_lacrosse_tx_const.te_delta) { - subghz_protocol_blocks_add_bit(&instance->decoder, 1); - instance->decoder.parser_step = LaCrosse_TXDecoderStepSaveDuration; - } else if( - DURATION_DIFF( - instance->decoder.te_last, ws_protocol_lacrosse_tx_const.te_long) < - ws_protocol_lacrosse_tx_const.te_delta) { - subghz_protocol_blocks_add_bit(&instance->decoder, 0); - instance->decoder.parser_step = LaCrosse_TXDecoderStepSaveDuration; - } - if((instance->decoder.decode_data & LACROSSE_TX_SUNC_MASK) == - LACROSSE_TX_SUNC_PATTERN) { - if(ws_protocol_lacrosse_tx_check_crc(instance)) { - instance->generic.data = instance->decoder.decode_data; - instance->generic.data_count_bit = LACROSSE_TX_BIT_SIZE; - ws_protocol_lacrosse_tx_remote_controller(&instance->generic); - if(instance->base.callback) - instance->base.callback(&instance->base, instance->base.context); - } - } - - instance->decoder.decode_data = 0; - instance->decoder.decode_count_bit = 0; - instance->header_count = 0; - instance->decoder.parser_step = LaCrosse_TXDecoderStepReset; - break; - } else if( - (DURATION_DIFF(instance->decoder.te_last, ws_protocol_lacrosse_tx_const.te_short) < - ws_protocol_lacrosse_tx_const.te_delta) && - (DURATION_DIFF(duration, LACROSSE_TX_GAP) < - ws_protocol_lacrosse_tx_const.te_delta * 2)) { - subghz_protocol_blocks_add_bit(&instance->decoder, 1); - instance->decoder.parser_step = LaCrosse_TXDecoderStepSaveDuration; - } else if( - (DURATION_DIFF(instance->decoder.te_last, ws_protocol_lacrosse_tx_const.te_long) < - ws_protocol_lacrosse_tx_const.te_delta) && - (DURATION_DIFF(duration, LACROSSE_TX_GAP) < - ws_protocol_lacrosse_tx_const.te_delta * 2)) { - subghz_protocol_blocks_add_bit(&instance->decoder, 0); - instance->decoder.parser_step = LaCrosse_TXDecoderStepSaveDuration; - } else { - instance->decoder.parser_step = LaCrosse_TXDecoderStepReset; - } - - } else { - instance->decoder.parser_step = LaCrosse_TXDecoderStepReset; - } - - break; - } -} - -uint8_t ws_protocol_decoder_lacrosse_tx_get_hash_data(void* context) { - furi_assert(context); - WSProtocolDecoderLaCrosse_TX* instance = context; - return subghz_protocol_blocks_get_hash_data( - &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); -} - -SubGhzProtocolStatus ws_protocol_decoder_lacrosse_tx_serialize( - void* context, - FlipperFormat* flipper_format, - SubGhzRadioPreset* preset) { - furi_assert(context); - WSProtocolDecoderLaCrosse_TX* instance = context; - return ws_block_generic_serialize(&instance->generic, flipper_format, preset); -} - -SubGhzProtocolStatus - ws_protocol_decoder_lacrosse_tx_deserialize(void* context, FlipperFormat* flipper_format) { - furi_assert(context); - WSProtocolDecoderLaCrosse_TX* instance = context; - return ws_block_generic_deserialize_check_count_bit( - &instance->generic, flipper_format, ws_protocol_lacrosse_tx_const.min_count_bit_for_found); -} - -void ws_protocol_decoder_lacrosse_tx_get_string(void* context, FuriString* output) { - furi_assert(context); - WSProtocolDecoderLaCrosse_TX* instance = context; - furi_string_printf( - output, - "%s %dbit\r\n" - "Key:0x%lX%08lX\r\n" - "Sn:0x%lX Ch:%d Bat:%d\r\n" - "Temp:%3.1f C Hum:%d%%", - instance->generic.protocol_name, - instance->generic.data_count_bit, - (uint32_t)(instance->generic.data >> 32), - (uint32_t)(instance->generic.data), - instance->generic.id, - instance->generic.channel, - instance->generic.battery_low, - (double)instance->generic.temp, - instance->generic.humidity); -} diff --git a/applications/external/weather_station/protocols/lacrosse_tx.h b/applications/external/weather_station/protocols/lacrosse_tx.h deleted file mode 100644 index 151282b3a07..00000000000 --- a/applications/external/weather_station/protocols/lacrosse_tx.h +++ /dev/null @@ -1,80 +0,0 @@ -#pragma once - -#include - -#include -#include -#include -#include "ws_generic.h" -#include - -#define WS_PROTOCOL_LACROSSE_TX_NAME "LaCrosse_TX" - -typedef struct WSProtocolDecoderLaCrosse_TX WSProtocolDecoderLaCrosse_TX; -typedef struct WSProtocolEncoderLaCrosse_TX WSProtocolEncoderLaCrosse_TX; - -extern const SubGhzProtocolDecoder ws_protocol_lacrosse_tx_decoder; -extern const SubGhzProtocolEncoder ws_protocol_lacrosse_tx_encoder; -extern const SubGhzProtocol ws_protocol_lacrosse_tx; - -/** - * Allocate WSProtocolDecoderLaCrosse_TX. - * @param environment Pointer to a SubGhzEnvironment instance - * @return WSProtocolDecoderLaCrosse_TX* pointer to a WSProtocolDecoderLaCrosse_TX instance - */ -void* ws_protocol_decoder_lacrosse_tx_alloc(SubGhzEnvironment* environment); - -/** - * Free WSProtocolDecoderLaCrosse_TX. - * @param context Pointer to a WSProtocolDecoderLaCrosse_TX instance - */ -void ws_protocol_decoder_lacrosse_tx_free(void* context); - -/** - * Reset decoder WSProtocolDecoderLaCrosse_TX. - * @param context Pointer to a WSProtocolDecoderLaCrosse_TX instance - */ -void ws_protocol_decoder_lacrosse_tx_reset(void* context); - -/** - * Parse a raw sequence of levels and durations received from the air. - * @param context Pointer to a WSProtocolDecoderLaCrosse_TX instance - * @param level Signal level true-high false-low - * @param duration Duration of this level in, us - */ -void ws_protocol_decoder_lacrosse_tx_feed(void* context, bool level, uint32_t duration); - -/** - * Getting the hash sum of the last randomly received parcel. - * @param context Pointer to a WSProtocolDecoderLaCrosse_TX instance - * @return hash Hash sum - */ -uint8_t ws_protocol_decoder_lacrosse_tx_get_hash_data(void* context); - -/** - * Serialize data WSProtocolDecoderLaCrosse_TX. - * @param context Pointer to a WSProtocolDecoderLaCrosse_TX instance - * @param flipper_format Pointer to a FlipperFormat instance - * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return status - */ -SubGhzProtocolStatus ws_protocol_decoder_lacrosse_tx_serialize( - void* context, - FlipperFormat* flipper_format, - SubGhzRadioPreset* preset); - -/** - * Deserialize data WSProtocolDecoderLaCrosse_TX. - * @param context Pointer to a WSProtocolDecoderLaCrosse_TX instance - * @param flipper_format Pointer to a FlipperFormat instance - * @return status - */ -SubGhzProtocolStatus - ws_protocol_decoder_lacrosse_tx_deserialize(void* context, FlipperFormat* flipper_format); - -/** - * Getting a textual representation of the received data. - * @param context Pointer to a WSProtocolDecoderLaCrosse_TX instance - * @param output Resulting text - */ -void ws_protocol_decoder_lacrosse_tx_get_string(void* context, FuriString* output); diff --git a/applications/external/weather_station/protocols/lacrosse_tx141thbv2.c b/applications/external/weather_station/protocols/lacrosse_tx141thbv2.c deleted file mode 100644 index f2fddd40c7a..00000000000 --- a/applications/external/weather_station/protocols/lacrosse_tx141thbv2.c +++ /dev/null @@ -1,294 +0,0 @@ -#include "lacrosse_tx141thbv2.h" - -#define TAG "WSProtocolLaCrosse_TX141THBv2" - -#define LACROSSE_TX141TH_BV2_BIT_COUNT 41 - -/* - * Help - * https://github.com/merbanan/rtl_433/blob/master/src/devices/lacrosse_tx141x.c - * - * iiii iiii | bkcc tttt | tttt tttt | hhhh hhhh | cccc cccc | u - 41 bit - * or - * iiii iiii | bkcc tttt | tttt tttt | hhhh hhhh | cccc cccc | -40 bit - * - i: identification; changes on battery switch - * - c: lfsr_digest8_reflect; - * - u: unknown; - * - b: battery low; flag to indicate low battery voltage - * - h: Humidity; - * - t: Temperature; in °F as binary number with one decimal place + 50 °F offset - * - n: Channel; Channel number 1 - 3 - */ - -static const SubGhzBlockConst ws_protocol_lacrosse_tx141thbv2_const = { - .te_short = 208, - .te_long = 417, - .te_delta = 120, - .min_count_bit_for_found = 40, -}; - -struct WSProtocolDecoderLaCrosse_TX141THBv2 { - SubGhzProtocolDecoderBase base; - - SubGhzBlockDecoder decoder; - WSBlockGeneric generic; - - uint16_t header_count; -}; - -struct WSProtocolEncoderLaCrosse_TX141THBv2 { - SubGhzProtocolEncoderBase base; - - SubGhzProtocolBlockEncoder encoder; - WSBlockGeneric generic; -}; - -typedef enum { - LaCrosse_TX141THBv2DecoderStepReset = 0, - LaCrosse_TX141THBv2DecoderStepCheckPreambule, - LaCrosse_TX141THBv2DecoderStepSaveDuration, - LaCrosse_TX141THBv2DecoderStepCheckDuration, -} LaCrosse_TX141THBv2DecoderStep; - -const SubGhzProtocolDecoder ws_protocol_lacrosse_tx141thbv2_decoder = { - .alloc = ws_protocol_decoder_lacrosse_tx141thbv2_alloc, - .free = ws_protocol_decoder_lacrosse_tx141thbv2_free, - - .feed = ws_protocol_decoder_lacrosse_tx141thbv2_feed, - .reset = ws_protocol_decoder_lacrosse_tx141thbv2_reset, - - .get_hash_data = ws_protocol_decoder_lacrosse_tx141thbv2_get_hash_data, - .serialize = ws_protocol_decoder_lacrosse_tx141thbv2_serialize, - .deserialize = ws_protocol_decoder_lacrosse_tx141thbv2_deserialize, - .get_string = ws_protocol_decoder_lacrosse_tx141thbv2_get_string, -}; - -const SubGhzProtocolEncoder ws_protocol_lacrosse_tx141thbv2_encoder = { - .alloc = NULL, - .free = NULL, - - .deserialize = NULL, - .stop = NULL, - .yield = NULL, -}; - -const SubGhzProtocol ws_protocol_lacrosse_tx141thbv2 = { - .name = WS_PROTOCOL_LACROSSE_TX141THBV2_NAME, - .type = SubGhzProtocolWeatherStation, - .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | - SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable, - - .decoder = &ws_protocol_lacrosse_tx141thbv2_decoder, - .encoder = &ws_protocol_lacrosse_tx141thbv2_encoder, -}; - -void* ws_protocol_decoder_lacrosse_tx141thbv2_alloc(SubGhzEnvironment* environment) { - UNUSED(environment); - WSProtocolDecoderLaCrosse_TX141THBv2* instance = - malloc(sizeof(WSProtocolDecoderLaCrosse_TX141THBv2)); - instance->base.protocol = &ws_protocol_lacrosse_tx141thbv2; - instance->generic.protocol_name = instance->base.protocol->name; - return instance; -} - -void ws_protocol_decoder_lacrosse_tx141thbv2_free(void* context) { - furi_assert(context); - WSProtocolDecoderLaCrosse_TX141THBv2* instance = context; - free(instance); -} - -void ws_protocol_decoder_lacrosse_tx141thbv2_reset(void* context) { - furi_assert(context); - WSProtocolDecoderLaCrosse_TX141THBv2* instance = context; - instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepReset; -} - -static bool - ws_protocol_lacrosse_tx141thbv2_check_crc(WSProtocolDecoderLaCrosse_TX141THBv2* instance) { - if(!instance->decoder.decode_data) return false; - uint64_t data = instance->decoder.decode_data; - if(instance->decoder.decode_count_bit == LACROSSE_TX141TH_BV2_BIT_COUNT) { - data >>= 1; - } - uint8_t msg[] = {data >> 32, data >> 24, data >> 16, data >> 8}; - - uint8_t crc = subghz_protocol_blocks_lfsr_digest8_reflect(msg, 4, 0x31, 0xF4); - return (crc == (data & 0xFF)); -} - -/** - * Analysis of received data - * @param instance Pointer to a WSBlockGeneric* instance - */ -static void ws_protocol_lacrosse_tx141thbv2_remote_controller(WSBlockGeneric* instance) { - uint64_t data = instance->data; - if(instance->data_count_bit == LACROSSE_TX141TH_BV2_BIT_COUNT) { - data >>= 1; - } - instance->id = data >> 32; - instance->battery_low = (data >> 31) & 1; - instance->btn = (data >> 30) & 1; - instance->channel = ((data >> 28) & 0x03) + 1; - instance->temp = ((float)((data >> 16) & 0x0FFF) - 500.0f) / 10.0f; - instance->humidity = (data >> 8) & 0xFF; -} - -/** - * Analysis of received data - * @param instance Pointer to a WSBlockGeneric* instance - */ -static bool ws_protocol_decoder_lacrosse_tx141thbv2_add_bit( - WSProtocolDecoderLaCrosse_TX141THBv2* instance, - uint32_t te_last, - uint32_t te_current) { - furi_assert(instance); - bool ret = false; - if(DURATION_DIFF( - te_last + te_current, - ws_protocol_lacrosse_tx141thbv2_const.te_short + - ws_protocol_lacrosse_tx141thbv2_const.te_long) < - ws_protocol_lacrosse_tx141thbv2_const.te_delta * 2) { - if(te_last > te_current) { - subghz_protocol_blocks_add_bit(&instance->decoder, 1); - } else { - subghz_protocol_blocks_add_bit(&instance->decoder, 0); - } - ret = true; - } - - return ret; -} -void ws_protocol_decoder_lacrosse_tx141thbv2_feed(void* context, bool level, uint32_t duration) { - furi_assert(context); - WSProtocolDecoderLaCrosse_TX141THBv2* instance = context; - - switch(instance->decoder.parser_step) { - case LaCrosse_TX141THBv2DecoderStepReset: - if((level) && - (DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_short * 4) < - ws_protocol_lacrosse_tx141thbv2_const.te_delta * 2)) { - instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepCheckPreambule; - instance->decoder.te_last = duration; - instance->header_count = 0; - } - break; - - case LaCrosse_TX141THBv2DecoderStepCheckPreambule: - if(level) { - instance->decoder.te_last = duration; - } else { - if((DURATION_DIFF( - instance->decoder.te_last, - ws_protocol_lacrosse_tx141thbv2_const.te_short * 4) < - ws_protocol_lacrosse_tx141thbv2_const.te_delta * 2) && - (DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_short * 4) < - ws_protocol_lacrosse_tx141thbv2_const.te_delta * 2)) { - //Found preambule - instance->header_count++; - } else if(instance->header_count == 4) { - if(ws_protocol_decoder_lacrosse_tx141thbv2_add_bit( - instance, instance->decoder.te_last, duration)) { - instance->decoder.decode_data = instance->decoder.decode_data & 1; - instance->decoder.decode_count_bit = 1; - instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepSaveDuration; - } else { - instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepReset; - } - } else { - instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepReset; - } - } - break; - - case LaCrosse_TX141THBv2DecoderStepSaveDuration: - if(level) { - instance->decoder.te_last = duration; - instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepCheckDuration; - } else { - instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepReset; - } - break; - - case LaCrosse_TX141THBv2DecoderStepCheckDuration: - if(!level) { - if(((DURATION_DIFF( - instance->decoder.te_last, - ws_protocol_lacrosse_tx141thbv2_const.te_short * 3) < - ws_protocol_lacrosse_tx141thbv2_const.te_delta * 2) && - (DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_short * 4) < - ws_protocol_lacrosse_tx141thbv2_const.te_delta * 2))) { - if((instance->decoder.decode_count_bit == - ws_protocol_lacrosse_tx141thbv2_const.min_count_bit_for_found) || - (instance->decoder.decode_count_bit == LACROSSE_TX141TH_BV2_BIT_COUNT)) { - if(ws_protocol_lacrosse_tx141thbv2_check_crc(instance)) { - instance->generic.data = instance->decoder.decode_data; - instance->generic.data_count_bit = instance->decoder.decode_count_bit; - ws_protocol_lacrosse_tx141thbv2_remote_controller(&instance->generic); - if(instance->base.callback) - instance->base.callback(&instance->base, instance->base.context); - } - instance->decoder.decode_data = 0; - instance->decoder.decode_count_bit = 0; - instance->header_count = 1; - instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepCheckPreambule; - break; - } - } else if(ws_protocol_decoder_lacrosse_tx141thbv2_add_bit( - instance, instance->decoder.te_last, duration)) { - instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepSaveDuration; - } else { - instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepReset; - } - } else { - instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepReset; - } - break; - } -} - -uint8_t ws_protocol_decoder_lacrosse_tx141thbv2_get_hash_data(void* context) { - furi_assert(context); - WSProtocolDecoderLaCrosse_TX141THBv2* instance = context; - return subghz_protocol_blocks_get_hash_data( - &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); -} - -SubGhzProtocolStatus ws_protocol_decoder_lacrosse_tx141thbv2_serialize( - void* context, - FlipperFormat* flipper_format, - SubGhzRadioPreset* preset) { - furi_assert(context); - WSProtocolDecoderLaCrosse_TX141THBv2* instance = context; - return ws_block_generic_serialize(&instance->generic, flipper_format, preset); -} - -SubGhzProtocolStatus ws_protocol_decoder_lacrosse_tx141thbv2_deserialize( - void* context, - FlipperFormat* flipper_format) { - furi_assert(context); - WSProtocolDecoderLaCrosse_TX141THBv2* instance = context; - return ws_block_generic_deserialize_check_count_bit( - &instance->generic, - flipper_format, - ws_protocol_lacrosse_tx141thbv2_const.min_count_bit_for_found); -} - -void ws_protocol_decoder_lacrosse_tx141thbv2_get_string(void* context, FuriString* output) { - furi_assert(context); - WSProtocolDecoderLaCrosse_TX141THBv2* instance = context; - furi_string_printf( - output, - "%s %dbit\r\n" - "Key:0x%lX%08lX\r\n" - "Sn:0x%lX Ch:%d Bat:%d\r\n" - "Temp:%3.1f C Hum:%d%%", - instance->generic.protocol_name, - instance->generic.data_count_bit, - (uint32_t)(instance->generic.data >> 32), - (uint32_t)(instance->generic.data), - instance->generic.id, - instance->generic.channel, - instance->generic.battery_low, - (double)instance->generic.temp, - instance->generic.humidity); -} diff --git a/applications/external/weather_station/protocols/lacrosse_tx141thbv2.h b/applications/external/weather_station/protocols/lacrosse_tx141thbv2.h deleted file mode 100644 index 036db05481e..00000000000 --- a/applications/external/weather_station/protocols/lacrosse_tx141thbv2.h +++ /dev/null @@ -1,81 +0,0 @@ -#pragma once - -#include - -#include -#include -#include -#include "ws_generic.h" -#include - -#define WS_PROTOCOL_LACROSSE_TX141THBV2_NAME "TX141THBv2" - -typedef struct WSProtocolDecoderLaCrosse_TX141THBv2 WSProtocolDecoderLaCrosse_TX141THBv2; -typedef struct WSProtocolEncoderLaCrosse_TX141THBv2 WSProtocolEncoderLaCrosse_TX141THBv2; - -extern const SubGhzProtocolDecoder ws_protocol_lacrosse_tx141thbv2_decoder; -extern const SubGhzProtocolEncoder ws_protocol_lacrosse_tx141thbv2_encoder; -extern const SubGhzProtocol ws_protocol_lacrosse_tx141thbv2; - -/** - * Allocate WSProtocolDecoderLaCrosse_TX141THBv2. - * @param environment Pointer to a SubGhzEnvironment instance - * @return WSProtocolDecoderLaCrosse_TX141THBv2* pointer to a WSProtocolDecoderLaCrosse_TX141THBv2 instance - */ -void* ws_protocol_decoder_lacrosse_tx141thbv2_alloc(SubGhzEnvironment* environment); - -/** - * Free WSProtocolDecoderLaCrosse_TX141THBv2. - * @param context Pointer to a WSProtocolDecoderLaCrosse_TX141THBv2 instance - */ -void ws_protocol_decoder_lacrosse_tx141thbv2_free(void* context); - -/** - * Reset decoder WSProtocolDecoderLaCrosse_TX141THBv2. - * @param context Pointer to a WSProtocolDecoderLaCrosse_TX141THBv2 instance - */ -void ws_protocol_decoder_lacrosse_tx141thbv2_reset(void* context); - -/** - * Parse a raw sequence of levels and durations received from the air. - * @param context Pointer to a WSProtocolDecoderLaCrosse_TX141THBv2 instance - * @param level Signal level true-high false-low - * @param duration Duration of this level in, us - */ -void ws_protocol_decoder_lacrosse_tx141thbv2_feed(void* context, bool level, uint32_t duration); - -/** - * Getting the hash sum of the last randomly received parcel. - * @param context Pointer to a WSProtocolDecoderLaCrosse_TX141THBv2 instance - * @return hash Hash sum - */ -uint8_t ws_protocol_decoder_lacrosse_tx141thbv2_get_hash_data(void* context); - -/** - * Serialize data WSProtocolDecoderLaCrosse_TX141THBv2. - * @param context Pointer to a WSProtocolDecoderLaCrosse_TX141THBv2 instance - * @param flipper_format Pointer to a FlipperFormat instance - * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return status - */ -SubGhzProtocolStatus ws_protocol_decoder_lacrosse_tx141thbv2_serialize( - void* context, - FlipperFormat* flipper_format, - SubGhzRadioPreset* preset); - -/** - * Deserialize data WSProtocolDecoderLaCrosse_TX141THBv2. - * @param context Pointer to a WSProtocolDecoderLaCrosse_TX141THBv2 instance - * @param flipper_format Pointer to a FlipperFormat instance - * @return status - */ -SubGhzProtocolStatus ws_protocol_decoder_lacrosse_tx141thbv2_deserialize( - void* context, - FlipperFormat* flipper_format); - -/** - * Getting a textual representation of the received data. - * @param context Pointer to a WSProtocolDecoderLaCrosse_TX141THBv2 instance - * @param output Resulting text - */ -void ws_protocol_decoder_lacrosse_tx141thbv2_get_string(void* context, FuriString* output); diff --git a/applications/external/weather_station/protocols/nexus_th.c b/applications/external/weather_station/protocols/nexus_th.c deleted file mode 100644 index 14ba8b273a6..00000000000 --- a/applications/external/weather_station/protocols/nexus_th.c +++ /dev/null @@ -1,254 +0,0 @@ -#include "nexus_th.h" - -#define TAG "WSProtocolNexus_TH" - -/* - * Help - * https://github.com/merbanan/rtl_433/blob/master/src/devices/nexus.c - * - * Nexus sensor protocol with ID, temperature and optional humidity - * also FreeTec (Pearl) NC-7345 sensors for FreeTec Weatherstation NC-7344, - * also infactory/FreeTec (Pearl) NX-3980 sensors for infactory/FreeTec NX-3974 station, - * also Solight TE82S sensors for Solight TE76/TE82/TE83/TE84 stations, - * also TFA 30.3209.02 temperature/humidity sensor. - * The sensor sends 36 bits 12 times, - * the packets are ppm modulated (distance coding) with a pulse of ~500 us - * followed by a short gap of ~1000 us for a 0 bit or a long ~2000 us gap for a - * 1 bit, the sync gap is ~4000 us. - * The data is grouped in 9 nibbles: - * [id0] [id1] [flags] [temp0] [temp1] [temp2] [const] [humi0] [humi1] - * - The 8-bit id changes when the battery is changed in the sensor. - * - flags are 4 bits B 0 C C, where B is the battery status: 1=OK, 0=LOW - * - and CC is the channel: 0=CH1, 1=CH2, 2=CH3 - * - temp is 12 bit signed scaled by 10 - * - const is always 1111 (0x0F) - * - humidity is 8 bits - * The sensors can be bought at Clas Ohlsen (Nexus) and Pearl (infactory/FreeTec). - * - */ - -#define NEXUS_TH_CONST_DATA 0b1111 - -static const SubGhzBlockConst ws_protocol_nexus_th_const = { - .te_short = 500, - .te_long = 2000, - .te_delta = 150, - .min_count_bit_for_found = 36, -}; - -struct WSProtocolDecoderNexus_TH { - SubGhzProtocolDecoderBase base; - - SubGhzBlockDecoder decoder; - WSBlockGeneric generic; -}; - -struct WSProtocolEncoderNexus_TH { - SubGhzProtocolEncoderBase base; - - SubGhzProtocolBlockEncoder encoder; - WSBlockGeneric generic; -}; - -typedef enum { - Nexus_THDecoderStepReset = 0, - Nexus_THDecoderStepSaveDuration, - Nexus_THDecoderStepCheckDuration, -} Nexus_THDecoderStep; - -const SubGhzProtocolDecoder ws_protocol_nexus_th_decoder = { - .alloc = ws_protocol_decoder_nexus_th_alloc, - .free = ws_protocol_decoder_nexus_th_free, - - .feed = ws_protocol_decoder_nexus_th_feed, - .reset = ws_protocol_decoder_nexus_th_reset, - - .get_hash_data = ws_protocol_decoder_nexus_th_get_hash_data, - .serialize = ws_protocol_decoder_nexus_th_serialize, - .deserialize = ws_protocol_decoder_nexus_th_deserialize, - .get_string = ws_protocol_decoder_nexus_th_get_string, -}; - -const SubGhzProtocolEncoder ws_protocol_nexus_th_encoder = { - .alloc = NULL, - .free = NULL, - - .deserialize = NULL, - .stop = NULL, - .yield = NULL, -}; - -const SubGhzProtocol ws_protocol_nexus_th = { - .name = WS_PROTOCOL_NEXUS_TH_NAME, - .type = SubGhzProtocolWeatherStation, - .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | - SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable, - - .decoder = &ws_protocol_nexus_th_decoder, - .encoder = &ws_protocol_nexus_th_encoder, -}; - -void* ws_protocol_decoder_nexus_th_alloc(SubGhzEnvironment* environment) { - UNUSED(environment); - WSProtocolDecoderNexus_TH* instance = malloc(sizeof(WSProtocolDecoderNexus_TH)); - instance->base.protocol = &ws_protocol_nexus_th; - instance->generic.protocol_name = instance->base.protocol->name; - return instance; -} - -void ws_protocol_decoder_nexus_th_free(void* context) { - furi_assert(context); - WSProtocolDecoderNexus_TH* instance = context; - free(instance); -} - -void ws_protocol_decoder_nexus_th_reset(void* context) { - furi_assert(context); - WSProtocolDecoderNexus_TH* instance = context; - instance->decoder.parser_step = Nexus_THDecoderStepReset; -} - -static bool ws_protocol_nexus_th_check(WSProtocolDecoderNexus_TH* instance) { - uint8_t type = (instance->decoder.decode_data >> 8) & 0x0F; - - if((type == NEXUS_TH_CONST_DATA) && ((instance->decoder.decode_data >> 4) != 0xffffffff)) { - return true; - } else { - return false; - } - return true; -} - -/** - * Analysis of received data - * @param instance Pointer to a WSBlockGeneric* instance - */ -static void ws_protocol_nexus_th_remote_controller(WSBlockGeneric* instance) { - instance->id = (instance->data >> 28) & 0xFF; - instance->battery_low = !((instance->data >> 27) & 1); - instance->channel = ((instance->data >> 24) & 0x03) + 1; - instance->btn = WS_NO_BTN; - if(!((instance->data >> 23) & 1)) { - instance->temp = (float)((instance->data >> 12) & 0x07FF) / 10.0f; - } else { - instance->temp = (float)((~(instance->data >> 12) & 0x07FF) + 1) / -10.0f; - } - - instance->humidity = instance->data & 0xFF; - if(instance->humidity > 95) - instance->humidity = 95; - else if(instance->humidity < 20) - instance->humidity = 20; -} - -void ws_protocol_decoder_nexus_th_feed(void* context, bool level, uint32_t duration) { - furi_assert(context); - WSProtocolDecoderNexus_TH* instance = context; - - switch(instance->decoder.parser_step) { - case Nexus_THDecoderStepReset: - if((!level) && (DURATION_DIFF(duration, ws_protocol_nexus_th_const.te_short * 8) < - ws_protocol_nexus_th_const.te_delta * 4)) { - //Found sync - instance->decoder.parser_step = Nexus_THDecoderStepSaveDuration; - instance->decoder.decode_data = 0; - instance->decoder.decode_count_bit = 0; - } - break; - - case Nexus_THDecoderStepSaveDuration: - if(level) { - instance->decoder.te_last = duration; - instance->decoder.parser_step = Nexus_THDecoderStepCheckDuration; - } else { - instance->decoder.parser_step = Nexus_THDecoderStepReset; - } - break; - - case Nexus_THDecoderStepCheckDuration: - if(!level) { - if(DURATION_DIFF(duration, ws_protocol_nexus_th_const.te_short * 8) < - ws_protocol_nexus_th_const.te_delta * 4) { - //Found sync - instance->decoder.parser_step = Nexus_THDecoderStepReset; - if((instance->decoder.decode_count_bit == - ws_protocol_nexus_th_const.min_count_bit_for_found) && - ws_protocol_nexus_th_check(instance)) { - instance->generic.data = instance->decoder.decode_data; - instance->generic.data_count_bit = instance->decoder.decode_count_bit; - ws_protocol_nexus_th_remote_controller(&instance->generic); - if(instance->base.callback) - instance->base.callback(&instance->base, instance->base.context); - instance->decoder.parser_step = Nexus_THDecoderStepCheckDuration; - } - instance->decoder.decode_data = 0; - instance->decoder.decode_count_bit = 0; - - break; - } else if( - (DURATION_DIFF(instance->decoder.te_last, ws_protocol_nexus_th_const.te_short) < - ws_protocol_nexus_th_const.te_delta) && - (DURATION_DIFF(duration, ws_protocol_nexus_th_const.te_short * 2) < - ws_protocol_nexus_th_const.te_delta * 2)) { - subghz_protocol_blocks_add_bit(&instance->decoder, 0); - instance->decoder.parser_step = Nexus_THDecoderStepSaveDuration; - } else if( - (DURATION_DIFF(instance->decoder.te_last, ws_protocol_nexus_th_const.te_short) < - ws_protocol_nexus_th_const.te_delta) && - (DURATION_DIFF(duration, ws_protocol_nexus_th_const.te_short * 4) < - ws_protocol_nexus_th_const.te_delta * 4)) { - subghz_protocol_blocks_add_bit(&instance->decoder, 1); - instance->decoder.parser_step = Nexus_THDecoderStepSaveDuration; - } else { - instance->decoder.parser_step = Nexus_THDecoderStepReset; - } - } else { - instance->decoder.parser_step = Nexus_THDecoderStepReset; - } - break; - } -} - -uint8_t ws_protocol_decoder_nexus_th_get_hash_data(void* context) { - furi_assert(context); - WSProtocolDecoderNexus_TH* instance = context; - return subghz_protocol_blocks_get_hash_data( - &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); -} - -SubGhzProtocolStatus ws_protocol_decoder_nexus_th_serialize( - void* context, - FlipperFormat* flipper_format, - SubGhzRadioPreset* preset) { - furi_assert(context); - WSProtocolDecoderNexus_TH* instance = context; - return ws_block_generic_serialize(&instance->generic, flipper_format, preset); -} - -SubGhzProtocolStatus - ws_protocol_decoder_nexus_th_deserialize(void* context, FlipperFormat* flipper_format) { - furi_assert(context); - WSProtocolDecoderNexus_TH* instance = context; - return ws_block_generic_deserialize_check_count_bit( - &instance->generic, flipper_format, ws_protocol_nexus_th_const.min_count_bit_for_found); -} - -void ws_protocol_decoder_nexus_th_get_string(void* context, FuriString* output) { - furi_assert(context); - WSProtocolDecoderNexus_TH* instance = context; - furi_string_printf( - output, - "%s %dbit\r\n" - "Key:0x%lX%08lX\r\n" - "Sn:0x%lX Ch:%d Bat:%d\r\n" - "Temp:%3.1f C Hum:%d%%", - instance->generic.protocol_name, - instance->generic.data_count_bit, - (uint32_t)(instance->generic.data >> 32), - (uint32_t)(instance->generic.data), - instance->generic.id, - instance->generic.channel, - instance->generic.battery_low, - (double)instance->generic.temp, - instance->generic.humidity); -} diff --git a/applications/external/weather_station/protocols/nexus_th.h b/applications/external/weather_station/protocols/nexus_th.h deleted file mode 100644 index 6c2715b8511..00000000000 --- a/applications/external/weather_station/protocols/nexus_th.h +++ /dev/null @@ -1,80 +0,0 @@ -#pragma once - -#include - -#include -#include -#include -#include "ws_generic.h" -#include - -#define WS_PROTOCOL_NEXUS_TH_NAME "Nexus-TH" - -typedef struct WSProtocolDecoderNexus_TH WSProtocolDecoderNexus_TH; -typedef struct WSProtocolEncoderNexus_TH WSProtocolEncoderNexus_TH; - -extern const SubGhzProtocolDecoder ws_protocol_nexus_th_decoder; -extern const SubGhzProtocolEncoder ws_protocol_nexus_th_encoder; -extern const SubGhzProtocol ws_protocol_nexus_th; - -/** - * Allocate WSProtocolDecoderNexus_TH. - * @param environment Pointer to a SubGhzEnvironment instance - * @return WSProtocolDecoderNexus_TH* pointer to a WSProtocolDecoderNexus_TH instance - */ -void* ws_protocol_decoder_nexus_th_alloc(SubGhzEnvironment* environment); - -/** - * Free WSProtocolDecoderNexus_TH. - * @param context Pointer to a WSProtocolDecoderNexus_TH instance - */ -void ws_protocol_decoder_nexus_th_free(void* context); - -/** - * Reset decoder WSProtocolDecoderNexus_TH. - * @param context Pointer to a WSProtocolDecoderNexus_TH instance - */ -void ws_protocol_decoder_nexus_th_reset(void* context); - -/** - * Parse a raw sequence of levels and durations received from the air. - * @param context Pointer to a WSProtocolDecoderNexus_TH instance - * @param level Signal level true-high false-low - * @param duration Duration of this level in, us - */ -void ws_protocol_decoder_nexus_th_feed(void* context, bool level, uint32_t duration); - -/** - * Getting the hash sum of the last randomly received parcel. - * @param context Pointer to a WSProtocolDecoderNexus_TH instance - * @return hash Hash sum - */ -uint8_t ws_protocol_decoder_nexus_th_get_hash_data(void* context); - -/** - * Serialize data WSProtocolDecoderNexus_TH. - * @param context Pointer to a WSProtocolDecoderNexus_TH instance - * @param flipper_format Pointer to a FlipperFormat instance - * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return status - */ -SubGhzProtocolStatus ws_protocol_decoder_nexus_th_serialize( - void* context, - FlipperFormat* flipper_format, - SubGhzRadioPreset* preset); - -/** - * Deserialize data WSProtocolDecoderNexus_TH. - * @param context Pointer to a WSProtocolDecoderNexus_TH instance - * @param flipper_format Pointer to a FlipperFormat instance - * @return status - */ -SubGhzProtocolStatus - ws_protocol_decoder_nexus_th_deserialize(void* context, FlipperFormat* flipper_format); - -/** - * Getting a textual representation of the received data. - * @param context Pointer to a WSProtocolDecoderNexus_TH instance - * @param output Resulting text - */ -void ws_protocol_decoder_nexus_th_get_string(void* context, FuriString* output); diff --git a/applications/external/weather_station/protocols/oregon2.c b/applications/external/weather_station/protocols/oregon2.c deleted file mode 100644 index 313748ccfb1..00000000000 --- a/applications/external/weather_station/protocols/oregon2.c +++ /dev/null @@ -1,429 +0,0 @@ -#include "oregon2.h" - -#include -#include -#include -#include -#include "ws_generic.h" - -#include -#include - -#define TAG "WSProtocolOregon2" - -static const SubGhzBlockConst ws_oregon2_const = { - .te_long = 1000, - .te_short = 500, - .te_delta = 200, - .min_count_bit_for_found = 32, -}; - -#define OREGON2_PREAMBLE_BITS 19 -#define OREGON2_PREAMBLE_MASK 0b1111111111111111111 -#define OREGON2_SENSOR_ID(d) (((d) >> 16) & 0xFFFF) -#define OREGON2_CHECKSUM_BITS 8 - -// 15 ones + 0101 (inverted A) -#define OREGON2_PREAMBLE 0b1111111111111110101 - -// bit indicating the low battery -#define OREGON2_FLAG_BAT_LOW 0x4 - -/// Documentation for Oregon Scientific protocols can be found here: -/// http://wmrx00.sourceforge.net/Arduino/OregonScientific-RF-Protocols.pdf -// Sensors ID -#define ID_THGR122N 0x1d20 -#define ID_THGR968 0x1d30 -#define ID_BTHR918 0x5d50 -#define ID_BHTR968 0x5d60 -#define ID_RGR968 0x2d10 -#define ID_THR228N 0xec40 -#define ID_THN132N 0xec40 // same as THR228N but different packet size -#define ID_RTGN318 0x0cc3 // warning: id is from 0x0cc3 and 0xfcc3 -#define ID_RTGN129 0x0cc3 // same as RTGN318 but different packet size -#define ID_THGR810 0xf824 // This might be ID_THGR81, but what's true is lost in (git) history -#define ID_THGR810a 0xf8b4 // unconfirmed version -#define ID_THN802 0xc844 -#define ID_PCR800 0x2914 -#define ID_PCR800a 0x2d14 // Different PCR800 ID - AU version I think -#define ID_WGR800 0x1984 -#define ID_WGR800a 0x1994 // unconfirmed version -#define ID_WGR968 0x3d00 -#define ID_UV800 0xd874 -#define ID_THN129 0xcc43 // THN129 Temp only -#define ID_RTHN129 0x0cd3 // RTHN129 Temp, clock sensors -#define ID_RTHN129_1 0x9cd3 -#define ID_RTHN129_2 0xacd3 -#define ID_RTHN129_3 0xbcd3 -#define ID_RTHN129_4 0xccd3 -#define ID_RTHN129_5 0xdcd3 -#define ID_BTHGN129 0x5d53 // Baro, Temp, Hygro sensor -#define ID_UVR128 0xec70 -#define ID_THGR328N 0xcc23 // Temp & Hygro sensor similar to THR228N with 5 channel instead of 3 -#define ID_RTGR328N_1 0xdcc3 // RTGR328N_[1-5] RFclock(date &time)&Temp&Hygro sensor -#define ID_RTGR328N_2 0xccc3 -#define ID_RTGR328N_3 0xbcc3 -#define ID_RTGR328N_4 0xacc3 -#define ID_RTGR328N_5 0x9cc3 -#define ID_RTGR328N_6 0x8ce3 // RTGR328N_6&7 RFclock(date &time)&Temp&Hygro sensor like THGR328N -#define ID_RTGR328N_7 0x8ae3 - -struct WSProtocolDecoderOregon2 { - SubGhzProtocolDecoderBase base; - - SubGhzBlockDecoder decoder; - WSBlockGeneric generic; - ManchesterState manchester_state; - bool prev_bit; - bool have_bit; - - uint8_t var_bits; - uint32_t var_data; -}; - -typedef struct WSProtocolDecoderOregon2 WSProtocolDecoderOregon2; - -typedef enum { - Oregon2DecoderStepReset = 0, - Oregon2DecoderStepFoundPreamble, - Oregon2DecoderStepVarData, -} Oregon2DecoderStep; - -void* ws_protocol_decoder_oregon2_alloc(SubGhzEnvironment* environment) { - UNUSED(environment); - WSProtocolDecoderOregon2* instance = malloc(sizeof(WSProtocolDecoderOregon2)); - instance->base.protocol = &ws_protocol_oregon2; - instance->generic.protocol_name = instance->base.protocol->name; - instance->generic.humidity = WS_NO_HUMIDITY; - instance->generic.temp = WS_NO_TEMPERATURE; - instance->generic.btn = WS_NO_BTN; - instance->generic.channel = WS_NO_CHANNEL; - instance->generic.battery_low = WS_NO_BATT; - instance->generic.id = WS_NO_ID; - return instance; -} - -void ws_protocol_decoder_oregon2_free(void* context) { - furi_assert(context); - WSProtocolDecoderOregon2* instance = context; - free(instance); -} - -void ws_protocol_decoder_oregon2_reset(void* context) { - furi_assert(context); - WSProtocolDecoderOregon2* instance = context; - instance->decoder.parser_step = Oregon2DecoderStepReset; - instance->decoder.decode_data = 0UL; - instance->decoder.decode_count_bit = 0; - manchester_advance( - instance->manchester_state, ManchesterEventReset, &instance->manchester_state, NULL); - instance->have_bit = false; - instance->var_data = 0; - instance->var_bits = 0; -} - -static ManchesterEvent level_and_duration_to_event(bool level, uint32_t duration) { - bool is_long = false; - - if(DURATION_DIFF(duration, ws_oregon2_const.te_long) < ws_oregon2_const.te_delta) { - is_long = true; - } else if(DURATION_DIFF(duration, ws_oregon2_const.te_short) < ws_oregon2_const.te_delta) { - is_long = false; - } else { - return ManchesterEventReset; - } - - if(level) - return is_long ? ManchesterEventLongHigh : ManchesterEventShortHigh; - else - return is_long ? ManchesterEventLongLow : ManchesterEventShortLow; -} - -// From sensor id code return amount of bits in variable section -// https://temofeev.ru/info/articles/o-dekodirovanii-protokola-pogodnykh-datchikov-oregon-scientific -static uint8_t oregon2_sensor_id_var_bits(uint16_t sensor_id) { - switch(sensor_id) { - case ID_THR228N: - case ID_RTHN129_1: - case ID_RTHN129_2: - case ID_RTHN129_3: - case ID_RTHN129_4: - case ID_RTHN129_5: - return 16; - case ID_THGR122N: - return 24; - default: - return 0; - } -} - -static void ws_oregon2_decode_const_data(WSBlockGeneric* ws_block) { - ws_block->id = OREGON2_SENSOR_ID(ws_block->data); - - uint8_t ch_bits = (ws_block->data >> 12) & 0xF; - ws_block->channel = 1; - while(ch_bits > 1) { - ws_block->channel++; - ch_bits >>= 1; - } - - ws_block->battery_low = (ws_block->data & OREGON2_FLAG_BAT_LOW) ? 1 : 0; -} - -uint16_t bcd_decode_short(uint32_t data) { - return (data & 0xF) * 10 + ((data >> 4) & 0xF); -} - -static float ws_oregon2_decode_temp(uint32_t data) { - int32_t temp_val; - temp_val = bcd_decode_short(data >> 4); - temp_val *= 10; - temp_val += (data >> 12) & 0xF; - if(data & 0xF) temp_val = -temp_val; - return (float)temp_val / 10.0; -} - -static void ws_oregon2_decode_var_data(WSBlockGeneric* ws_b, uint16_t sensor_id, uint32_t data) { - switch(sensor_id) { - case ID_THR228N: - case ID_RTHN129_1: - case ID_RTHN129_2: - case ID_RTHN129_3: - case ID_RTHN129_4: - case ID_RTHN129_5: - ws_b->temp = ws_oregon2_decode_temp(data); - ws_b->humidity = WS_NO_HUMIDITY; - return; - case ID_THGR122N: - ws_b->humidity = bcd_decode_short(data); - ws_b->temp = ws_oregon2_decode_temp(data >> 8); - return; - default: - break; - } -} - -void ws_protocol_decoder_oregon2_feed(void* context, bool level, uint32_t duration) { - furi_assert(context); - WSProtocolDecoderOregon2* instance = context; - // oregon v2.1 signal is inverted - ManchesterEvent event = level_and_duration_to_event(!level, duration); - bool data; - - // low-level bit sequence decoding - if(event == ManchesterEventReset) { - instance->decoder.parser_step = Oregon2DecoderStepReset; - instance->have_bit = false; - instance->decoder.decode_data = 0UL; - instance->decoder.decode_count_bit = 0; - } - if(manchester_advance(instance->manchester_state, event, &instance->manchester_state, &data)) { - if(instance->have_bit) { - if(!instance->prev_bit && data) { - subghz_protocol_blocks_add_bit(&instance->decoder, 1); - } else if(instance->prev_bit && !data) { - subghz_protocol_blocks_add_bit(&instance->decoder, 0); - } else { - ws_protocol_decoder_oregon2_reset(context); - } - instance->have_bit = false; - } else { - instance->prev_bit = data; - instance->have_bit = true; - } - } - - switch(instance->decoder.parser_step) { - case Oregon2DecoderStepReset: - // waiting for fixed oregon2 preamble - if(instance->decoder.decode_count_bit >= OREGON2_PREAMBLE_BITS && - ((instance->decoder.decode_data & OREGON2_PREAMBLE_MASK) == OREGON2_PREAMBLE)) { - instance->decoder.parser_step = Oregon2DecoderStepFoundPreamble; - instance->decoder.decode_count_bit = 0; - instance->decoder.decode_data = 0UL; - } - break; - case Oregon2DecoderStepFoundPreamble: - // waiting for fixed oregon2 data - if(instance->decoder.decode_count_bit == 32) { - instance->generic.data = instance->decoder.decode_data; - instance->generic.data_count_bit = instance->decoder.decode_count_bit; - instance->decoder.decode_data = 0UL; - instance->decoder.decode_count_bit = 0; - - // reverse nibbles in decoded data - instance->generic.data = (instance->generic.data & 0x55555555) << 1 | - (instance->generic.data & 0xAAAAAAAA) >> 1; - instance->generic.data = (instance->generic.data & 0x33333333) << 2 | - (instance->generic.data & 0xCCCCCCCC) >> 2; - - ws_oregon2_decode_const_data(&instance->generic); - instance->var_bits = - oregon2_sensor_id_var_bits(OREGON2_SENSOR_ID(instance->generic.data)); - - if(!instance->var_bits) { - // sensor is not supported, stop decoding, but showing the decoded fixed part - instance->decoder.parser_step = Oregon2DecoderStepReset; - if(instance->base.callback) - instance->base.callback(&instance->base, instance->base.context); - } else { - instance->decoder.parser_step = Oregon2DecoderStepVarData; - } - } - break; - case Oregon2DecoderStepVarData: - // waiting for variable (sensor-specific data) - if(instance->decoder.decode_count_bit == instance->var_bits + OREGON2_CHECKSUM_BITS) { - instance->var_data = instance->decoder.decode_data & 0xFFFFFFFF; - - // reverse nibbles in var data - instance->var_data = (instance->var_data & 0x55555555) << 1 | - (instance->var_data & 0xAAAAAAAA) >> 1; - instance->var_data = (instance->var_data & 0x33333333) << 2 | - (instance->var_data & 0xCCCCCCCC) >> 2; - - ws_oregon2_decode_var_data( - &instance->generic, - OREGON2_SENSOR_ID(instance->generic.data), - instance->var_data >> OREGON2_CHECKSUM_BITS); - - instance->decoder.parser_step = Oregon2DecoderStepReset; - if(instance->base.callback) - instance->base.callback(&instance->base, instance->base.context); - } - break; - } -} - -uint8_t ws_protocol_decoder_oregon2_get_hash_data(void* context) { - furi_assert(context); - WSProtocolDecoderOregon2* instance = context; - return subghz_protocol_blocks_get_hash_data( - &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); -} - -SubGhzProtocolStatus ws_protocol_decoder_oregon2_serialize( - void* context, - FlipperFormat* flipper_format, - SubGhzRadioPreset* preset) { - furi_assert(context); - WSProtocolDecoderOregon2* instance = context; - SubGhzProtocolStatus ret = SubGhzProtocolStatusError; - ret = ws_block_generic_serialize(&instance->generic, flipper_format, preset); - if(ret != SubGhzProtocolStatusOk) return ret; - uint32_t temp = instance->var_bits; - if(!flipper_format_write_uint32(flipper_format, "VarBits", &temp, 1)) { - FURI_LOG_E(TAG, "Error adding VarBits"); - return SubGhzProtocolStatusErrorParserOthers; - } - if(!flipper_format_write_hex( - flipper_format, - "VarData", - (const uint8_t*)&instance->var_data, - sizeof(instance->var_data))) { - FURI_LOG_E(TAG, "Error adding VarData"); - return SubGhzProtocolStatusErrorParserOthers; - } - return ret; -} - -SubGhzProtocolStatus - ws_protocol_decoder_oregon2_deserialize(void* context, FlipperFormat* flipper_format) { - furi_assert(context); - WSProtocolDecoderOregon2* instance = context; - uint32_t temp_data; - SubGhzProtocolStatus ret = SubGhzProtocolStatusError; - do { - ret = ws_block_generic_deserialize(&instance->generic, flipper_format); - if(ret != SubGhzProtocolStatusOk) { - break; - } - if(!flipper_format_read_uint32(flipper_format, "VarBits", &temp_data, 1)) { - FURI_LOG_E(TAG, "Missing VarLen"); - ret = SubGhzProtocolStatusErrorParserOthers; - break; - } - instance->var_bits = (uint8_t)temp_data; - if(!flipper_format_read_hex( - flipper_format, - "VarData", - (uint8_t*)&instance->var_data, - sizeof(instance->var_data))) { //-V1051 - FURI_LOG_E(TAG, "Missing VarData"); - ret = SubGhzProtocolStatusErrorParserOthers; - break; - } - if(instance->generic.data_count_bit != ws_oregon2_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key: %d", instance->generic.data_count_bit); - ret = SubGhzProtocolStatusErrorValueBitCount; - break; - } - } while(false); - return ret; -} - -static void oregon2_append_check_sum(uint32_t fix_data, uint32_t var_data, FuriString* output) { - uint8_t sum = fix_data & 0xF; - uint8_t ref_sum = var_data & 0xFF; - var_data >>= 8; - - for(uint8_t i = 1; i < 8; i++) { - fix_data >>= 4; - var_data >>= 4; - sum += (fix_data & 0xF) + (var_data & 0xF); - } - - // swap calculated sum nibbles - sum = (((sum >> 4) & 0xF) | (sum << 4)) & 0xFF; - if(sum == ref_sum) - furi_string_cat_printf(output, "Sum ok: 0x%hhX", ref_sum); - else - furi_string_cat_printf(output, "Sum err: 0x%hhX vs 0x%hhX", ref_sum, sum); -} - -void ws_protocol_decoder_oregon2_get_string(void* context, FuriString* output) { - furi_assert(context); - WSProtocolDecoderOregon2* instance = context; - furi_string_cat_printf( - output, - "%s\r\n" - "ID: 0x%04lX, ch: %d, bat: %d, rc: 0x%02lX\r\n", - instance->generic.protocol_name, - instance->generic.id, - instance->generic.channel, - instance->generic.battery_low, - (uint32_t)(instance->generic.data >> 4) & 0xFF); - - if(instance->var_bits > 0) { - furi_string_cat_printf( - output, - "Temp:%d.%d C Hum:%d%%", - (int16_t)instance->generic.temp, - abs( - ((int16_t)(instance->generic.temp * 10) - - (((int16_t)instance->generic.temp) * 10))), - instance->generic.humidity); - oregon2_append_check_sum((uint32_t)instance->generic.data, instance->var_data, output); - } -} - -const SubGhzProtocolDecoder ws_protocol_oregon2_decoder = { - .alloc = ws_protocol_decoder_oregon2_alloc, - .free = ws_protocol_decoder_oregon2_free, - - .feed = ws_protocol_decoder_oregon2_feed, - .reset = ws_protocol_decoder_oregon2_reset, - - .get_hash_data = ws_protocol_decoder_oregon2_get_hash_data, - .serialize = ws_protocol_decoder_oregon2_serialize, - .deserialize = ws_protocol_decoder_oregon2_deserialize, - .get_string = ws_protocol_decoder_oregon2_get_string, -}; - -const SubGhzProtocol ws_protocol_oregon2 = { - .name = WS_PROTOCOL_OREGON2_NAME, - .type = SubGhzProtocolWeatherStation, - .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable, - - .decoder = &ws_protocol_oregon2_decoder, -}; diff --git a/applications/external/weather_station/protocols/oregon2.h b/applications/external/weather_station/protocols/oregon2.h deleted file mode 100644 index cfe938e6d83..00000000000 --- a/applications/external/weather_station/protocols/oregon2.h +++ /dev/null @@ -1,6 +0,0 @@ -#pragma once - -#include - -#define WS_PROTOCOL_OREGON2_NAME "Oregon2" -extern const SubGhzProtocol ws_protocol_oregon2; diff --git a/applications/external/weather_station/protocols/oregon3.c b/applications/external/weather_station/protocols/oregon3.c deleted file mode 100644 index bd35c2fd576..00000000000 --- a/applications/external/weather_station/protocols/oregon3.c +++ /dev/null @@ -1,365 +0,0 @@ -#include "oregon3.h" - -#include -#include -#include -#include -#include "ws_generic.h" - -#include -#include - -#define TAG "WSProtocolOregon3" - -static const SubGhzBlockConst ws_oregon3_const = { - .te_long = 1100, - .te_short = 500, - .te_delta = 300, - .min_count_bit_for_found = 32, -}; - -#define OREGON3_PREAMBLE_BITS 28 -#define OREGON3_PREAMBLE_MASK 0b1111111111111111111111111111 -// 24 ones + 0101 (inverted A) -#define OREGON3_PREAMBLE 0b1111111111111111111111110101 - -// Fixed part contains: -// - Sensor type: 16 bits -// - Channel: 4 bits -// - ID (changes when batteries are changed): 8 bits -// - Battery status: 4 bits -#define OREGON3_FIXED_PART_BITS (16 + 4 + 8 + 4) -#define OREGON3_SENSOR_ID(d) (((d) >> 16) & 0xFFFF) -#define OREGON3_CHECKSUM_BITS 8 - -// bit indicating the low battery -#define OREGON3_FLAG_BAT_LOW 0x4 - -/// Documentation for Oregon Scientific protocols can be found here: -/// https://www.osengr.org/Articles/OS-RF-Protocols-IV.pdf -// Sensors ID -#define ID_THGR221 0xf824 - -struct WSProtocolDecoderOregon3 { - SubGhzProtocolDecoderBase base; - - SubGhzBlockDecoder decoder; - WSBlockGeneric generic; - ManchesterState manchester_state; - bool prev_bit; - - uint8_t var_bits; - uint64_t var_data; -}; - -typedef struct WSProtocolDecoderOregon3 WSProtocolDecoderOregon3; - -typedef enum { - Oregon3DecoderStepReset = 0, - Oregon3DecoderStepFoundPreamble, - Oregon3DecoderStepVarData, -} Oregon3DecoderStep; - -void* ws_protocol_decoder_oregon3_alloc(SubGhzEnvironment* environment) { - UNUSED(environment); - WSProtocolDecoderOregon3* instance = malloc(sizeof(WSProtocolDecoderOregon3)); - instance->base.protocol = &ws_protocol_oregon3; - instance->generic.protocol_name = instance->base.protocol->name; - instance->generic.humidity = WS_NO_HUMIDITY; - instance->generic.temp = WS_NO_TEMPERATURE; - instance->generic.btn = WS_NO_BTN; - instance->generic.channel = WS_NO_CHANNEL; - instance->generic.battery_low = WS_NO_BATT; - instance->generic.id = WS_NO_ID; - instance->prev_bit = false; - return instance; -} - -void ws_protocol_decoder_oregon3_free(void* context) { - furi_assert(context); - WSProtocolDecoderOregon3* instance = context; - free(instance); -} - -void ws_protocol_decoder_oregon3_reset(void* context) { - furi_assert(context); - WSProtocolDecoderOregon3* instance = context; - instance->decoder.parser_step = Oregon3DecoderStepReset; - instance->decoder.decode_data = 0UL; - instance->decoder.decode_count_bit = 0; - manchester_advance( - instance->manchester_state, ManchesterEventReset, &instance->manchester_state, NULL); - instance->prev_bit = false; - instance->var_data = 0; - instance->var_bits = 0; -} - -static ManchesterEvent level_and_duration_to_event(bool level, uint32_t duration) { - bool is_long = false; - - if(DURATION_DIFF(duration, ws_oregon3_const.te_long) < ws_oregon3_const.te_delta) { - is_long = true; - } else if(DURATION_DIFF(duration, ws_oregon3_const.te_short) < ws_oregon3_const.te_delta) { - is_long = false; - } else { - return ManchesterEventReset; - } - - if(level) - return is_long ? ManchesterEventLongHigh : ManchesterEventShortHigh; - else - return is_long ? ManchesterEventLongLow : ManchesterEventShortLow; -} - -// From sensor id code return amount of bits in variable section -// https://temofeev.ru/info/articles/o-dekodirovanii-protokola-pogodnykh-datchikov-oregon-scientific -static uint8_t oregon3_sensor_id_var_bits(uint16_t sensor_id) { - switch(sensor_id) { - case ID_THGR221: - // nibbles: temp + hum + '0' - return (4 + 2 + 1) * 4; - default: - FURI_LOG_D(TAG, "Unsupported sensor id 0x%x", sensor_id); - return 0; - } -} - -static void ws_oregon3_decode_const_data(WSBlockGeneric* ws_block) { - ws_block->id = OREGON3_SENSOR_ID(ws_block->data); - ws_block->channel = (ws_block->data >> 12) & 0xF; - ws_block->battery_low = (ws_block->data & OREGON3_FLAG_BAT_LOW) ? 1 : 0; -} - -static uint16_t ws_oregon3_bcd_decode_short(uint32_t data) { - return (data & 0xF) * 10 + ((data >> 4) & 0xF); -} - -static float ws_oregon3_decode_temp(uint32_t data) { - int32_t temp_val; - temp_val = ws_oregon3_bcd_decode_short(data >> 4); - temp_val *= 10; - temp_val += (data >> 12) & 0xF; - if(data & 0xF) temp_val = -temp_val; - return (float)temp_val / 10.0; -} - -static void ws_oregon3_decode_var_data(WSBlockGeneric* ws_b, uint16_t sensor_id, uint32_t data) { - switch(sensor_id) { - case ID_THGR221: - default: - ws_b->humidity = ws_oregon3_bcd_decode_short(data >> 4); - ws_b->temp = ws_oregon3_decode_temp(data >> 12); - break; - } -} - -void ws_protocol_decoder_oregon3_feed(void* context, bool level, uint32_t duration) { - furi_assert(context); - WSProtocolDecoderOregon3* instance = context; - // Oregon v3.0 protocol is inverted - ManchesterEvent event = level_and_duration_to_event(!level, duration); - - // low-level bit sequence decoding - if(event == ManchesterEventReset) { - instance->decoder.parser_step = Oregon3DecoderStepReset; - instance->prev_bit = false; - instance->decoder.decode_data = 0UL; - instance->decoder.decode_count_bit = 0; - } - if(manchester_advance( - instance->manchester_state, event, &instance->manchester_state, &instance->prev_bit)) { - subghz_protocol_blocks_add_bit(&instance->decoder, instance->prev_bit); - } - - switch(instance->decoder.parser_step) { - case Oregon3DecoderStepReset: - // waiting for fixed oregon3 preamble - if(instance->decoder.decode_count_bit >= OREGON3_PREAMBLE_BITS && - ((instance->decoder.decode_data & OREGON3_PREAMBLE_MASK) == OREGON3_PREAMBLE)) { - instance->decoder.parser_step = Oregon3DecoderStepFoundPreamble; - instance->decoder.decode_count_bit = 0; - instance->decoder.decode_data = 0UL; - } - break; - case Oregon3DecoderStepFoundPreamble: - // waiting for fixed oregon3 data - if(instance->decoder.decode_count_bit == OREGON3_FIXED_PART_BITS) { - instance->generic.data = instance->decoder.decode_data; - instance->generic.data_count_bit = instance->decoder.decode_count_bit; - instance->decoder.decode_data = 0UL; - instance->decoder.decode_count_bit = 0; - - // reverse nibbles in decoded data as oregon v3.0 is LSB first - instance->generic.data = (instance->generic.data & 0x55555555) << 1 | - (instance->generic.data & 0xAAAAAAAA) >> 1; - instance->generic.data = (instance->generic.data & 0x33333333) << 2 | - (instance->generic.data & 0xCCCCCCCC) >> 2; - - ws_oregon3_decode_const_data(&instance->generic); - instance->var_bits = - oregon3_sensor_id_var_bits(OREGON3_SENSOR_ID(instance->generic.data)); - - if(!instance->var_bits) { - // sensor is not supported, stop decoding - instance->decoder.parser_step = Oregon3DecoderStepReset; - } else { - instance->decoder.parser_step = Oregon3DecoderStepVarData; - } - } - break; - case Oregon3DecoderStepVarData: - // waiting for variable (sensor-specific data) - if(instance->decoder.decode_count_bit == instance->var_bits + OREGON3_CHECKSUM_BITS) { - instance->var_data = instance->decoder.decode_data & 0xFFFFFFFFFFFFFFFF; - - // reverse nibbles in var data - instance->var_data = (instance->var_data & 0x5555555555555555) << 1 | - (instance->var_data & 0xAAAAAAAAAAAAAAAA) >> 1; - instance->var_data = (instance->var_data & 0x3333333333333333) << 2 | - (instance->var_data & 0xCCCCCCCCCCCCCCCC) >> 2; - - ws_oregon3_decode_var_data( - &instance->generic, - OREGON3_SENSOR_ID(instance->generic.data), - instance->var_data >> OREGON3_CHECKSUM_BITS); - - instance->decoder.parser_step = Oregon3DecoderStepReset; - if(instance->base.callback) - instance->base.callback(&instance->base, instance->base.context); - } - break; - } -} - -uint8_t ws_protocol_decoder_oregon3_get_hash_data(void* context) { - furi_assert(context); - WSProtocolDecoderOregon3* instance = context; - return subghz_protocol_blocks_get_hash_data( - &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); -} - -SubGhzProtocolStatus ws_protocol_decoder_oregon3_serialize( - void* context, - FlipperFormat* flipper_format, - SubGhzRadioPreset* preset) { - furi_assert(context); - WSProtocolDecoderOregon3* instance = context; - SubGhzProtocolStatus ret = SubGhzProtocolStatusError; - ret = ws_block_generic_serialize(&instance->generic, flipper_format, preset); - if(ret != SubGhzProtocolStatusOk) return ret; - uint32_t temp = instance->var_bits; - if(!flipper_format_write_uint32(flipper_format, "VarBits", &temp, 1)) { - FURI_LOG_E(TAG, "Error adding VarBits"); - return SubGhzProtocolStatusErrorParserOthers; - } - if(!flipper_format_write_hex( - flipper_format, - "VarData", - (const uint8_t*)&instance->var_data, - sizeof(instance->var_data))) { - FURI_LOG_E(TAG, "Error adding VarData"); - return SubGhzProtocolStatusErrorParserOthers; - } - return ret; -} - -SubGhzProtocolStatus - ws_protocol_decoder_oregon3_deserialize(void* context, FlipperFormat* flipper_format) { - furi_assert(context); - WSProtocolDecoderOregon3* instance = context; - uint32_t temp_data; - SubGhzProtocolStatus ret = SubGhzProtocolStatusError; - do { - ret = ws_block_generic_deserialize(&instance->generic, flipper_format); - if(ret != SubGhzProtocolStatusOk) { - break; - } - if(!flipper_format_read_uint32(flipper_format, "VarBits", &temp_data, 1)) { - FURI_LOG_E(TAG, "Missing VarLen"); - ret = SubGhzProtocolStatusErrorParserOthers; - break; - } - instance->var_bits = (uint8_t)temp_data; - if(!flipper_format_read_hex( - flipper_format, - "VarData", - (uint8_t*)&instance->var_data, - sizeof(instance->var_data))) { //-V1051 - FURI_LOG_E(TAG, "Missing VarData"); - ret = SubGhzProtocolStatusErrorParserOthers; - break; - } - if(instance->generic.data_count_bit != ws_oregon3_const.min_count_bit_for_found) { - FURI_LOG_E(TAG, "Wrong number of bits in key: %d", instance->generic.data_count_bit); - ret = SubGhzProtocolStatusErrorValueBitCount; - break; - } - } while(false); - return ret; -} - -static void oregon3_append_check_sum(uint32_t fix_data, uint64_t var_data, FuriString* output) { - uint8_t sum = fix_data & 0xF; - uint8_t ref_sum = var_data & 0xFF; - var_data >>= 4; - - for(uint8_t i = 1; i < 8; i++) { - fix_data >>= 4; - var_data >>= 4; - sum += (fix_data & 0xF) + (var_data & 0xF); - } - - // swap calculated sum nibbles - sum = (((sum >> 4) & 0xF) | (sum << 4)) & 0xFF; - if(sum == ref_sum) - furi_string_cat_printf(output, "Sum ok: 0x%hhX", ref_sum); - else - furi_string_cat_printf(output, "Sum err: 0x%hhX vs 0x%hhX", ref_sum, sum); -} - -void ws_protocol_decoder_oregon3_get_string(void* context, FuriString* output) { - furi_assert(context); - WSProtocolDecoderOregon3* instance = context; - furi_string_cat_printf( - output, - "%s\r\n" - "ID: 0x%04lX, ch: %d, bat: %d, rc: 0x%02lX\r\n", - instance->generic.protocol_name, - instance->generic.id, - instance->generic.channel, - instance->generic.battery_low, - (uint32_t)(instance->generic.data >> 4) & 0xFF); - - if(instance->var_bits > 0) { - furi_string_cat_printf( - output, - "Temp:%d.%d C Hum:%d%%", - (int16_t)instance->generic.temp, - abs( - ((int16_t)(instance->generic.temp * 10) - - (((int16_t)instance->generic.temp) * 10))), - instance->generic.humidity); - oregon3_append_check_sum((uint32_t)instance->generic.data, instance->var_data, output); - } -} - -const SubGhzProtocolDecoder ws_protocol_oregon3_decoder = { - .alloc = ws_protocol_decoder_oregon3_alloc, - .free = ws_protocol_decoder_oregon3_free, - - .feed = ws_protocol_decoder_oregon3_feed, - .reset = ws_protocol_decoder_oregon3_reset, - - .get_hash_data = ws_protocol_decoder_oregon3_get_hash_data, - .serialize = ws_protocol_decoder_oregon3_serialize, - .deserialize = ws_protocol_decoder_oregon3_deserialize, - .get_string = ws_protocol_decoder_oregon3_get_string, -}; - -const SubGhzProtocol ws_protocol_oregon3 = { - .name = WS_PROTOCOL_OREGON3_NAME, - .type = SubGhzProtocolWeatherStation, - .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable, - - .decoder = &ws_protocol_oregon3_decoder, -}; diff --git a/applications/external/weather_station/protocols/oregon3.h b/applications/external/weather_station/protocols/oregon3.h deleted file mode 100644 index ec51ddb0001..00000000000 --- a/applications/external/weather_station/protocols/oregon3.h +++ /dev/null @@ -1,6 +0,0 @@ -#pragma once - -#include - -#define WS_PROTOCOL_OREGON3_NAME "Oregon3" -extern const SubGhzProtocol ws_protocol_oregon3; diff --git a/applications/external/weather_station/protocols/oregon_v1.c b/applications/external/weather_station/protocols/oregon_v1.c deleted file mode 100644 index 03215bbf5fa..00000000000 --- a/applications/external/weather_station/protocols/oregon_v1.c +++ /dev/null @@ -1,321 +0,0 @@ -#include "oregon_v1.h" -#include - -#define TAG "WSProtocolOregon_V1" - -/* - * Help - * https://github.dev/merbanan/rtl_433/blob/bb1be7f186ac0fdb7dc5d77693847d96fb95281e/src/devices/oregon_scientific_v1.c - * - * OSv1 protocol. - * - * MC with nominal bit width of 2930 us. - * Pulses are somewhat longer than nominal half-bit width, 1748 us / 3216 us, - * Gaps are somewhat shorter than nominal half-bit width, 1176 us / 2640 us. - * After 12 preamble bits there is 4200 us gap, 5780 us pulse, 5200 us gap. - * And next 32 bit data - * - * Care must be taken with the gap after the sync pulse since it - * is outside of the normal clocking. Because of this a data stream - * beginning with a 0 will have data in this gap. - * - * - * Data is in reverse order of bits - * RevBit(data32bit)=> tib23atad - * - * tib23atad => xxxxxxxx | busuTTTT | ttttzzzz | ccuuiiii - * - * - i: ID - * - x: CRC; - * - u: unknown; - * - b: battery low; flag to indicate low battery voltage - * - s: temperature sign - * - T: BCD, Temperature; in C * 10 - * - t: BCD, Temperature; in C * 1 - * - z: BCD, Temperature; in C * 0.1 - * - c: Channel 00=CH1, 01=CH2, 10=CH3 - * - */ - -#define OREGON_V1_HEADER_OK 0xFF - -static const SubGhzBlockConst ws_protocol_oregon_v1_const = { - .te_short = 1465, - .te_long = 2930, - .te_delta = 350, - .min_count_bit_for_found = 32, -}; - -struct WSProtocolDecoderOregon_V1 { - SubGhzProtocolDecoderBase base; - - SubGhzBlockDecoder decoder; - WSBlockGeneric generic; - ManchesterState manchester_state; - uint16_t header_count; - uint8_t first_bit; -}; - -struct WSProtocolEncoderOregon_V1 { - SubGhzProtocolEncoderBase base; - - SubGhzProtocolBlockEncoder encoder; - WSBlockGeneric generic; -}; - -typedef enum { - Oregon_V1DecoderStepReset = 0, - Oregon_V1DecoderStepFoundPreamble, - Oregon_V1DecoderStepParse, -} Oregon_V1DecoderStep; - -const SubGhzProtocolDecoder ws_protocol_oregon_v1_decoder = { - .alloc = ws_protocol_decoder_oregon_v1_alloc, - .free = ws_protocol_decoder_oregon_v1_free, - - .feed = ws_protocol_decoder_oregon_v1_feed, - .reset = ws_protocol_decoder_oregon_v1_reset, - - .get_hash_data = ws_protocol_decoder_oregon_v1_get_hash_data, - .serialize = ws_protocol_decoder_oregon_v1_serialize, - .deserialize = ws_protocol_decoder_oregon_v1_deserialize, - .get_string = ws_protocol_decoder_oregon_v1_get_string, -}; - -const SubGhzProtocolEncoder ws_protocol_oregon_v1_encoder = { - .alloc = NULL, - .free = NULL, - - .deserialize = NULL, - .stop = NULL, - .yield = NULL, -}; - -const SubGhzProtocol ws_protocol_oregon_v1 = { - .name = WS_PROTOCOL_OREGON_V1_NAME, - .type = SubGhzProtocolWeatherStation, - .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | - SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable, - - .decoder = &ws_protocol_oregon_v1_decoder, - .encoder = &ws_protocol_oregon_v1_encoder, -}; - -void* ws_protocol_decoder_oregon_v1_alloc(SubGhzEnvironment* environment) { - UNUSED(environment); - WSProtocolDecoderOregon_V1* instance = malloc(sizeof(WSProtocolDecoderOregon_V1)); - instance->base.protocol = &ws_protocol_oregon_v1; - instance->generic.protocol_name = instance->base.protocol->name; - return instance; -} - -void ws_protocol_decoder_oregon_v1_free(void* context) { - furi_assert(context); - WSProtocolDecoderOregon_V1* instance = context; - free(instance); -} - -void ws_protocol_decoder_oregon_v1_reset(void* context) { - furi_assert(context); - WSProtocolDecoderOregon_V1* instance = context; - instance->decoder.parser_step = Oregon_V1DecoderStepReset; -} - -static bool ws_protocol_oregon_v1_check(WSProtocolDecoderOregon_V1* instance) { - if(!instance->decoder.decode_data) return false; - uint64_t data = subghz_protocol_blocks_reverse_key(instance->decoder.decode_data, 32); - uint16_t crc = (data & 0xff) + ((data >> 8) & 0xff) + ((data >> 16) & 0xff); - crc = (crc & 0xff) + ((crc >> 8) & 0xff); - return (crc == ((data >> 24) & 0xFF)); -} - -/** - * Analysis of received data - * @param instance Pointer to a WSBlockGeneric* instance - */ -static void ws_protocol_oregon_v1_remote_controller(WSBlockGeneric* instance) { - uint64_t data = subghz_protocol_blocks_reverse_key(instance->data, 32); - - instance->id = data & 0xFF; - instance->channel = ((data >> 6) & 0x03) + 1; - - float temp_raw = - ((data >> 8) & 0x0F) * 0.1f + ((data >> 12) & 0x0F) + ((data >> 16) & 0x0F) * 10.0f; - if(!((data >> 21) & 1)) { - instance->temp = temp_raw; - } else { - instance->temp = -temp_raw; - } - - instance->battery_low = !((instance->data >> 23) & 1ULL); - - instance->btn = WS_NO_BTN; - instance->humidity = WS_NO_HUMIDITY; -} - -void ws_protocol_decoder_oregon_v1_feed(void* context, bool level, uint32_t duration) { - furi_assert(context); - WSProtocolDecoderOregon_V1* instance = context; - ManchesterEvent event = ManchesterEventReset; - switch(instance->decoder.parser_step) { - case Oregon_V1DecoderStepReset: - if((level) && (DURATION_DIFF(duration, ws_protocol_oregon_v1_const.te_short) < - ws_protocol_oregon_v1_const.te_delta)) { - instance->decoder.parser_step = Oregon_V1DecoderStepFoundPreamble; - instance->decoder.te_last = duration; - instance->header_count = 0; - } - break; - case Oregon_V1DecoderStepFoundPreamble: - if(level) { - //keep high levels, if they suit our durations - if((DURATION_DIFF(duration, ws_protocol_oregon_v1_const.te_short) < - ws_protocol_oregon_v1_const.te_delta) || - (DURATION_DIFF(duration, ws_protocol_oregon_v1_const.te_short * 4) < - ws_protocol_oregon_v1_const.te_delta)) { - instance->decoder.te_last = duration; - } else { - instance->decoder.parser_step = Oregon_V1DecoderStepReset; - } - } else if( - //checking low levels - (DURATION_DIFF(duration, ws_protocol_oregon_v1_const.te_short) < - ws_protocol_oregon_v1_const.te_delta) && - (DURATION_DIFF(instance->decoder.te_last, ws_protocol_oregon_v1_const.te_short) < - ws_protocol_oregon_v1_const.te_delta)) { - // Found header - instance->header_count++; - } else if( - (DURATION_DIFF(duration, ws_protocol_oregon_v1_const.te_short * 3) < - ws_protocol_oregon_v1_const.te_delta) && - (DURATION_DIFF(instance->decoder.te_last, ws_protocol_oregon_v1_const.te_short) < - ws_protocol_oregon_v1_const.te_delta)) { - // check header - if(instance->header_count > 7) { - instance->header_count = OREGON_V1_HEADER_OK; - } - } else if( - (instance->header_count == OREGON_V1_HEADER_OK) && - (DURATION_DIFF(instance->decoder.te_last, ws_protocol_oregon_v1_const.te_short * 4) < - ws_protocol_oregon_v1_const.te_delta)) { - //found all the necessary patterns - instance->decoder.decode_data = 0; - instance->decoder.decode_count_bit = 1; - manchester_advance( - instance->manchester_state, - ManchesterEventReset, - &instance->manchester_state, - NULL); - instance->decoder.parser_step = Oregon_V1DecoderStepParse; - if(duration < ws_protocol_oregon_v1_const.te_short * 4) { - instance->first_bit = 1; - } else { - instance->first_bit = 0; - } - } else { - instance->decoder.parser_step = Oregon_V1DecoderStepReset; - } - break; - case Oregon_V1DecoderStepParse: - if(level) { - if(DURATION_DIFF(duration, ws_protocol_oregon_v1_const.te_short) < - ws_protocol_oregon_v1_const.te_delta) { - event = ManchesterEventShortHigh; - } else if( - DURATION_DIFF(duration, ws_protocol_oregon_v1_const.te_long) < - ws_protocol_oregon_v1_const.te_delta) { - event = ManchesterEventLongHigh; - } else { - instance->decoder.parser_step = Oregon_V1DecoderStepReset; - } - } else { - if(DURATION_DIFF(duration, ws_protocol_oregon_v1_const.te_short) < - ws_protocol_oregon_v1_const.te_delta) { - event = ManchesterEventShortLow; - } else if( - DURATION_DIFF(duration, ws_protocol_oregon_v1_const.te_long) < - ws_protocol_oregon_v1_const.te_delta) { - event = ManchesterEventLongLow; - } else if(duration >= ((uint32_t)ws_protocol_oregon_v1_const.te_long * 2)) { - if(instance->decoder.decode_count_bit == - ws_protocol_oregon_v1_const.min_count_bit_for_found) { - if(instance->first_bit) { - instance->decoder.decode_data = ~instance->decoder.decode_data | (1 << 31); - } - if(ws_protocol_oregon_v1_check(instance)) { - instance->generic.data = instance->decoder.decode_data; - instance->generic.data_count_bit = instance->decoder.decode_count_bit; - ws_protocol_oregon_v1_remote_controller(&instance->generic); - if(instance->base.callback) - instance->base.callback(&instance->base, instance->base.context); - } - } - instance->decoder.decode_data = 0; - instance->decoder.decode_count_bit = 0; - manchester_advance( - instance->manchester_state, - ManchesterEventReset, - &instance->manchester_state, - NULL); - } else { - instance->decoder.parser_step = Oregon_V1DecoderStepReset; - } - } - if(event != ManchesterEventReset) { - bool data; - bool data_ok = manchester_advance( - instance->manchester_state, event, &instance->manchester_state, &data); - - if(data_ok) { - instance->decoder.decode_data = (instance->decoder.decode_data << 1) | !data; - instance->decoder.decode_count_bit++; - } - } - - break; - } -} - -uint8_t ws_protocol_decoder_oregon_v1_get_hash_data(void* context) { - furi_assert(context); - WSProtocolDecoderOregon_V1* instance = context; - return subghz_protocol_blocks_get_hash_data( - &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); -} - -SubGhzProtocolStatus ws_protocol_decoder_oregon_v1_serialize( - void* context, - FlipperFormat* flipper_format, - SubGhzRadioPreset* preset) { - furi_assert(context); - WSProtocolDecoderOregon_V1* instance = context; - return ws_block_generic_serialize(&instance->generic, flipper_format, preset); -} - -SubGhzProtocolStatus - ws_protocol_decoder_oregon_v1_deserialize(void* context, FlipperFormat* flipper_format) { - furi_assert(context); - WSProtocolDecoderOregon_V1* instance = context; - return ws_block_generic_deserialize_check_count_bit( - &instance->generic, flipper_format, ws_protocol_oregon_v1_const.min_count_bit_for_found); -} - -void ws_protocol_decoder_oregon_v1_get_string(void* context, FuriString* output) { - furi_assert(context); - WSProtocolDecoderOregon_V1* instance = context; - furi_string_printf( - output, - "%s %dbit\r\n" - "Key:0x%lX%08lX\r\n" - "Sn:0x%lX Ch:%d Bat:%d\r\n" - "Temp:%3.1f C Hum:%d%%", - instance->generic.protocol_name, - instance->generic.data_count_bit, - (uint32_t)(instance->generic.data >> 32), - (uint32_t)(instance->generic.data), - instance->generic.id, - instance->generic.channel, - instance->generic.battery_low, - (double)instance->generic.temp, - instance->generic.humidity); -} diff --git a/applications/external/weather_station/protocols/oregon_v1.h b/applications/external/weather_station/protocols/oregon_v1.h deleted file mode 100644 index 48937601deb..00000000000 --- a/applications/external/weather_station/protocols/oregon_v1.h +++ /dev/null @@ -1,80 +0,0 @@ -#pragma once - -#include - -#include -#include -#include -#include "ws_generic.h" -#include - -#define WS_PROTOCOL_OREGON_V1_NAME "Oregon-v1" - -typedef struct WSProtocolDecoderOregon_V1 WSProtocolDecoderOregon_V1; -typedef struct WSProtocolEncoderOregon_V1 WSProtocolEncoderOregon_V1; - -extern const SubGhzProtocolDecoder ws_protocol_oregon_v1_decoder; -extern const SubGhzProtocolEncoder ws_protocol_oregon_v1_encoder; -extern const SubGhzProtocol ws_protocol_oregon_v1; - -/** - * Allocate WSProtocolDecoderOregon_V1. - * @param environment Pointer to a SubGhzEnvironment instance - * @return WSProtocolDecoderOregon_V1* pointer to a WSProtocolDecoderOregon_V1 instance - */ -void* ws_protocol_decoder_oregon_v1_alloc(SubGhzEnvironment* environment); - -/** - * Free WSProtocolDecoderOregon_V1. - * @param context Pointer to a WSProtocolDecoderOregon_V1 instance - */ -void ws_protocol_decoder_oregon_v1_free(void* context); - -/** - * Reset decoder WSProtocolDecoderOregon_V1. - * @param context Pointer to a WSProtocolDecoderOregon_V1 instance - */ -void ws_protocol_decoder_oregon_v1_reset(void* context); - -/** - * Parse a raw sequence of levels and durations received from the air. - * @param context Pointer to a WSProtocolDecoderOregon_V1 instance - * @param level Signal level true-high false-low - * @param duration Duration of this level in, us - */ -void ws_protocol_decoder_oregon_v1_feed(void* context, bool level, uint32_t duration); - -/** - * Getting the hash sum of the last randomly received parcel. - * @param context Pointer to a WSProtocolDecoderOregon_V1 instance - * @return hash Hash sum - */ -uint8_t ws_protocol_decoder_oregon_v1_get_hash_data(void* context); - -/** - * Serialize data WSProtocolDecoderOregon_V1. - * @param context Pointer to a WSProtocolDecoderOregon_V1 instance - * @param flipper_format Pointer to a FlipperFormat instance - * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return status - */ -SubGhzProtocolStatus ws_protocol_decoder_oregon_v1_serialize( - void* context, - FlipperFormat* flipper_format, - SubGhzRadioPreset* preset); - -/** - * Deserialize data WSProtocolDecoderOregon_V1. - * @param context Pointer to a WSProtocolDecoderOregon_V1 instance - * @param flipper_format Pointer to a FlipperFormat instance - * @return status - */ -SubGhzProtocolStatus - ws_protocol_decoder_oregon_v1_deserialize(void* context, FlipperFormat* flipper_format); - -/** - * Getting a textual representation of the received data. - * @param context Pointer to a WSProtocolDecoderOregon_V1 instance - * @param output Resulting text - */ -void ws_protocol_decoder_oregon_v1_get_string(void* context, FuriString* output); diff --git a/applications/external/weather_station/protocols/protocol_items.c b/applications/external/weather_station/protocols/protocol_items.c deleted file mode 100644 index 93dc25488dd..00000000000 --- a/applications/external/weather_station/protocols/protocol_items.c +++ /dev/null @@ -1,25 +0,0 @@ -#include "protocol_items.h" - -const SubGhzProtocol* weather_station_protocol_registry_items[] = { - &ws_protocol_infactory, - &ws_protocol_thermopro_tx4, - &ws_protocol_nexus_th, - &ws_protocol_gt_wt_02, - &ws_protocol_gt_wt_03, - &ws_protocol_acurite_606tx, - &ws_protocol_acurite_609txc, - &ws_protocol_lacrosse_tx, - &ws_protocol_lacrosse_tx141thbv2, - &ws_protocol_oregon2, - &ws_protocol_oregon3, - &ws_protocol_acurite_592txr, - &ws_protocol_ambient_weather, - &ws_protocol_auriol_th, - &ws_protocol_oregon_v1, - &ws_protocol_tx_8300, - &ws_protocol_wendox_w6726, -}; - -const SubGhzProtocolRegistry weather_station_protocol_registry = { - .items = weather_station_protocol_registry_items, - .size = COUNT_OF(weather_station_protocol_registry_items)}; diff --git a/applications/external/weather_station/protocols/protocol_items.h b/applications/external/weather_station/protocols/protocol_items.h deleted file mode 100644 index 712eb07f221..00000000000 --- a/applications/external/weather_station/protocols/protocol_items.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once -#include "../weather_station_app_i.h" - -#include "infactory.h" -#include "thermopro_tx4.h" -#include "nexus_th.h" -#include "gt_wt_02.h" -#include "gt_wt_03.h" -#include "acurite_606tx.h" -#include "acurite_609txc.h" -#include "lacrosse_tx.h" -#include "lacrosse_tx141thbv2.h" -#include "oregon2.h" -#include "oregon3.h" -#include "acurite_592txr.h" -#include "ambient_weather.h" -#include "auriol_hg0601a.h" -#include "oregon_v1.h" -#include "tx_8300.h" -#include "wendox_w6726.h" - -extern const SubGhzProtocolRegistry weather_station_protocol_registry; diff --git a/applications/external/weather_station/protocols/thermopro_tx4.c b/applications/external/weather_station/protocols/thermopro_tx4.c deleted file mode 100644 index 24e883e607b..00000000000 --- a/applications/external/weather_station/protocols/thermopro_tx4.c +++ /dev/null @@ -1,251 +0,0 @@ -#include "thermopro_tx4.h" - -#define TAG "WSProtocolThermoPRO_TX4" - -/* - * Help - * https://github.com/merbanan/rtl_433/blob/master/src/devices/thermopro_tx2.c - * - * The sensor sends 37 bits 6 times, before the first packet there is a sync pulse. - * The packets are ppm modulated (distance coding) with a pulse of ~500 us - * followed by a short gap of ~2000 us for a 0 bit or a long ~4000 us gap for a - * 1 bit, the sync gap is ~9000 us. - * The data is grouped in 9 nibbles - * [type] [id0] [id1] [flags] [temp0] [temp1] [temp2] [humi0] [humi1] - * - type: 4 bit fixed 1001 (9) or 0110 (5) - * - id: 8 bit a random id that is generated when the sensor starts, could include battery status - * the same batteries often generate the same id - * - flags(3): is 1 when the battery is low, otherwise 0 (ok) - * - flags(2): is 1 when the sensor sends a reading when pressing the button on the sensor - * - flags(1,0): the channel number that can be set by the sensor (1, 2, 3, X) - * - temp: 12 bit signed scaled by 10 - * - humi: 8 bit always 11001100 (0xCC) if no humidity sensor is available - * - */ - -#define THERMO_PRO_TX4_TYPE_1 0b1001 -#define THERMO_PRO_TX4_TYPE_2 0b0110 - -static const SubGhzBlockConst ws_protocol_thermopro_tx4_const = { - .te_short = 500, - .te_long = 2000, - .te_delta = 150, - .min_count_bit_for_found = 37, -}; - -struct WSProtocolDecoderThermoPRO_TX4 { - SubGhzProtocolDecoderBase base; - - SubGhzBlockDecoder decoder; - WSBlockGeneric generic; -}; - -struct WSProtocolEncoderThermoPRO_TX4 { - SubGhzProtocolEncoderBase base; - - SubGhzProtocolBlockEncoder encoder; - WSBlockGeneric generic; -}; - -typedef enum { - ThermoPRO_TX4DecoderStepReset = 0, - ThermoPRO_TX4DecoderStepSaveDuration, - ThermoPRO_TX4DecoderStepCheckDuration, -} ThermoPRO_TX4DecoderStep; - -const SubGhzProtocolDecoder ws_protocol_thermopro_tx4_decoder = { - .alloc = ws_protocol_decoder_thermopro_tx4_alloc, - .free = ws_protocol_decoder_thermopro_tx4_free, - - .feed = ws_protocol_decoder_thermopro_tx4_feed, - .reset = ws_protocol_decoder_thermopro_tx4_reset, - - .get_hash_data = ws_protocol_decoder_thermopro_tx4_get_hash_data, - .serialize = ws_protocol_decoder_thermopro_tx4_serialize, - .deserialize = ws_protocol_decoder_thermopro_tx4_deserialize, - .get_string = ws_protocol_decoder_thermopro_tx4_get_string, -}; - -const SubGhzProtocolEncoder ws_protocol_thermopro_tx4_encoder = { - .alloc = NULL, - .free = NULL, - - .deserialize = NULL, - .stop = NULL, - .yield = NULL, -}; - -const SubGhzProtocol ws_protocol_thermopro_tx4 = { - .name = WS_PROTOCOL_THERMOPRO_TX4_NAME, - .type = SubGhzProtocolWeatherStation, - .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | - SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable, - - .decoder = &ws_protocol_thermopro_tx4_decoder, - .encoder = &ws_protocol_thermopro_tx4_encoder, -}; - -void* ws_protocol_decoder_thermopro_tx4_alloc(SubGhzEnvironment* environment) { - UNUSED(environment); - WSProtocolDecoderThermoPRO_TX4* instance = malloc(sizeof(WSProtocolDecoderThermoPRO_TX4)); - instance->base.protocol = &ws_protocol_thermopro_tx4; - instance->generic.protocol_name = instance->base.protocol->name; - return instance; -} - -void ws_protocol_decoder_thermopro_tx4_free(void* context) { - furi_assert(context); - WSProtocolDecoderThermoPRO_TX4* instance = context; - free(instance); -} - -void ws_protocol_decoder_thermopro_tx4_reset(void* context) { - furi_assert(context); - WSProtocolDecoderThermoPRO_TX4* instance = context; - instance->decoder.parser_step = ThermoPRO_TX4DecoderStepReset; -} - -static bool ws_protocol_thermopro_tx4_check(WSProtocolDecoderThermoPRO_TX4* instance) { - uint8_t type = instance->decoder.decode_data >> 33; - - if((type == THERMO_PRO_TX4_TYPE_1) || (type == THERMO_PRO_TX4_TYPE_2)) { - return true; - } else { - return false; - } -} - -/** - * Analysis of received data - * @param instance Pointer to a WSBlockGeneric* instance - */ -static void ws_protocol_thermopro_tx4_remote_controller(WSBlockGeneric* instance) { - instance->id = (instance->data >> 25) & 0xFF; - instance->battery_low = (instance->data >> 24) & 1; - instance->btn = (instance->data >> 23) & 1; - instance->channel = ((instance->data >> 21) & 0x03) + 1; - - if(!((instance->data >> 20) & 1)) { - instance->temp = (float)((instance->data >> 9) & 0x07FF) / 10.0f; - } else { - instance->temp = (float)((~(instance->data >> 9) & 0x07FF) + 1) / -10.0f; - } - - instance->humidity = (instance->data >> 1) & 0xFF; -} - -void ws_protocol_decoder_thermopro_tx4_feed(void* context, bool level, uint32_t duration) { - furi_assert(context); - WSProtocolDecoderThermoPRO_TX4* instance = context; - - switch(instance->decoder.parser_step) { - case ThermoPRO_TX4DecoderStepReset: - if((!level) && (DURATION_DIFF(duration, ws_protocol_thermopro_tx4_const.te_short * 18) < - ws_protocol_thermopro_tx4_const.te_delta * 10)) { - //Found sync - instance->decoder.parser_step = ThermoPRO_TX4DecoderStepSaveDuration; - instance->decoder.decode_data = 0; - instance->decoder.decode_count_bit = 0; - } - break; - - case ThermoPRO_TX4DecoderStepSaveDuration: - if(level) { - instance->decoder.te_last = duration; - instance->decoder.parser_step = ThermoPRO_TX4DecoderStepCheckDuration; - } else { - instance->decoder.parser_step = ThermoPRO_TX4DecoderStepReset; - } - break; - - case ThermoPRO_TX4DecoderStepCheckDuration: - if(!level) { - if(DURATION_DIFF(duration, ws_protocol_thermopro_tx4_const.te_short * 18) < - ws_protocol_thermopro_tx4_const.te_delta * 10) { - //Found sync - instance->decoder.parser_step = ThermoPRO_TX4DecoderStepReset; - if((instance->decoder.decode_count_bit == - ws_protocol_thermopro_tx4_const.min_count_bit_for_found) && - ws_protocol_thermopro_tx4_check(instance)) { - instance->generic.data = instance->decoder.decode_data; - instance->generic.data_count_bit = instance->decoder.decode_count_bit; - ws_protocol_thermopro_tx4_remote_controller(&instance->generic); - if(instance->base.callback) - instance->base.callback(&instance->base, instance->base.context); - instance->decoder.parser_step = ThermoPRO_TX4DecoderStepCheckDuration; - } - instance->decoder.decode_data = 0; - instance->decoder.decode_count_bit = 0; - - break; - } else if( - (DURATION_DIFF( - instance->decoder.te_last, ws_protocol_thermopro_tx4_const.te_short) < - ws_protocol_thermopro_tx4_const.te_delta) && - (DURATION_DIFF(duration, ws_protocol_thermopro_tx4_const.te_long) < - ws_protocol_thermopro_tx4_const.te_delta * 2)) { - subghz_protocol_blocks_add_bit(&instance->decoder, 0); - instance->decoder.parser_step = ThermoPRO_TX4DecoderStepSaveDuration; - } else if( - (DURATION_DIFF( - instance->decoder.te_last, ws_protocol_thermopro_tx4_const.te_short) < - ws_protocol_thermopro_tx4_const.te_delta) && - (DURATION_DIFF(duration, ws_protocol_thermopro_tx4_const.te_long * 2) < - ws_protocol_thermopro_tx4_const.te_delta * 4)) { - subghz_protocol_blocks_add_bit(&instance->decoder, 1); - instance->decoder.parser_step = ThermoPRO_TX4DecoderStepSaveDuration; - } else { - instance->decoder.parser_step = ThermoPRO_TX4DecoderStepReset; - } - } else { - instance->decoder.parser_step = ThermoPRO_TX4DecoderStepReset; - } - break; - } -} - -uint8_t ws_protocol_decoder_thermopro_tx4_get_hash_data(void* context) { - furi_assert(context); - WSProtocolDecoderThermoPRO_TX4* instance = context; - return subghz_protocol_blocks_get_hash_data( - &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); -} - -SubGhzProtocolStatus ws_protocol_decoder_thermopro_tx4_serialize( - void* context, - FlipperFormat* flipper_format, - SubGhzRadioPreset* preset) { - furi_assert(context); - WSProtocolDecoderThermoPRO_TX4* instance = context; - return ws_block_generic_serialize(&instance->generic, flipper_format, preset); -} - -SubGhzProtocolStatus - ws_protocol_decoder_thermopro_tx4_deserialize(void* context, FlipperFormat* flipper_format) { - furi_assert(context); - WSProtocolDecoderThermoPRO_TX4* instance = context; - return ws_block_generic_deserialize_check_count_bit( - &instance->generic, - flipper_format, - ws_protocol_thermopro_tx4_const.min_count_bit_for_found); -} - -void ws_protocol_decoder_thermopro_tx4_get_string(void* context, FuriString* output) { - furi_assert(context); - WSProtocolDecoderThermoPRO_TX4* instance = context; - furi_string_printf( - output, - "%s %dbit\r\n" - "Key:0x%lX%08lX\r\n" - "Sn:0x%lX Ch:%d Bat:%d\r\n" - "Temp:%3.1f C Hum:%d%%", - instance->generic.protocol_name, - instance->generic.data_count_bit, - (uint32_t)(instance->generic.data >> 32), - (uint32_t)(instance->generic.data), - instance->generic.id, - instance->generic.channel, - instance->generic.battery_low, - (double)instance->generic.temp, - instance->generic.humidity); -} diff --git a/applications/external/weather_station/protocols/thermopro_tx4.h b/applications/external/weather_station/protocols/thermopro_tx4.h deleted file mode 100644 index 526648d1eeb..00000000000 --- a/applications/external/weather_station/protocols/thermopro_tx4.h +++ /dev/null @@ -1,80 +0,0 @@ -#pragma once - -#include - -#include -#include -#include -#include "ws_generic.h" -#include - -#define WS_PROTOCOL_THERMOPRO_TX4_NAME "ThermoPRO-TX4" - -typedef struct WSProtocolDecoderThermoPRO_TX4 WSProtocolDecoderThermoPRO_TX4; -typedef struct WSProtocolEncoderThermoPRO_TX4 WSProtocolEncoderThermoPRO_TX4; - -extern const SubGhzProtocolDecoder ws_protocol_thermopro_tx4_decoder; -extern const SubGhzProtocolEncoder ws_protocol_thermopro_tx4_encoder; -extern const SubGhzProtocol ws_protocol_thermopro_tx4; - -/** - * Allocate WSProtocolDecoderThermoPRO_TX4. - * @param environment Pointer to a SubGhzEnvironment instance - * @return WSProtocolDecoderThermoPRO_TX4* pointer to a WSProtocolDecoderThermoPRO_TX4 instance - */ -void* ws_protocol_decoder_thermopro_tx4_alloc(SubGhzEnvironment* environment); - -/** - * Free WSProtocolDecoderThermoPRO_TX4. - * @param context Pointer to a WSProtocolDecoderThermoPRO_TX4 instance - */ -void ws_protocol_decoder_thermopro_tx4_free(void* context); - -/** - * Reset decoder WSProtocolDecoderThermoPRO_TX4. - * @param context Pointer to a WSProtocolDecoderThermoPRO_TX4 instance - */ -void ws_protocol_decoder_thermopro_tx4_reset(void* context); - -/** - * Parse a raw sequence of levels and durations received from the air. - * @param context Pointer to a WSProtocolDecoderThermoPRO_TX4 instance - * @param level Signal level true-high false-low - * @param duration Duration of this level in, us - */ -void ws_protocol_decoder_thermopro_tx4_feed(void* context, bool level, uint32_t duration); - -/** - * Getting the hash sum of the last randomly received parcel. - * @param context Pointer to a WSProtocolDecoderThermoPRO_TX4 instance - * @return hash Hash sum - */ -uint8_t ws_protocol_decoder_thermopro_tx4_get_hash_data(void* context); - -/** - * Serialize data WSProtocolDecoderThermoPRO_TX4. - * @param context Pointer to a WSProtocolDecoderThermoPRO_TX4 instance - * @param flipper_format Pointer to a FlipperFormat instance - * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return status - */ -SubGhzProtocolStatus ws_protocol_decoder_thermopro_tx4_serialize( - void* context, - FlipperFormat* flipper_format, - SubGhzRadioPreset* preset); - -/** - * Deserialize data WSProtocolDecoderThermoPRO_TX4. - * @param context Pointer to a WSProtocolDecoderThermoPRO_TX4 instance - * @param flipper_format Pointer to a FlipperFormat instance - * @return status - */ -SubGhzProtocolStatus - ws_protocol_decoder_thermopro_tx4_deserialize(void* context, FlipperFormat* flipper_format); - -/** - * Getting a textual representation of the received data. - * @param context Pointer to a WSProtocolDecoderThermoPRO_TX4 instance - * @param output Resulting text - */ -void ws_protocol_decoder_thermopro_tx4_get_string(void* context, FuriString* output); diff --git a/applications/external/weather_station/protocols/tx_8300.c b/applications/external/weather_station/protocols/tx_8300.c deleted file mode 100644 index 3a06ce49dec..00000000000 --- a/applications/external/weather_station/protocols/tx_8300.c +++ /dev/null @@ -1,284 +0,0 @@ -#include "tx_8300.h" - -#define TAG "WSProtocolTX_8300" - -/* - * Help - * https://github.com/merbanan/rtl_433/blob/master/src/devices/ambientweather_tx8300.c - * - * Ambient Weather TX-8300 (also sold as TFA 30.3211.02). - * 1970us pulse with variable gap (third pulse 3920 us). - * Above 79% humidity, gap after third pulse is 5848 us. - * - Bit 1 : 1970us pulse with 3888 us gap - * - Bit 0 : 1970us pulse with 1936 us gap - * 74 bit (2 bit preamble and 72 bit data => 9 bytes => 18 nibbles) - * The preamble seems to be a repeat counter (00, and 01 seen), - * the first 4 bytes are data, - * the second 4 bytes the same data inverted, - * the last byte is a checksum. - * Preamble format (2 bits): - * [1 bit (0)] [1 bit rolling count] - * Payload format (32 bits): - * HHHHhhhh ??CCNIII IIIITTTT ttttuuuu - * - H = First BCD digit humidity (the MSB might be distorted by the demod) - * - h = Second BCD digit humidity, invalid humidity seems to be 0x0e - * - ? = Likely battery flag, 2 bits - * - C = Channel, 2 bits - * - N = Negative temperature sign bit - * - I = ID, 7-bit - * - T = First BCD digit temperature - * - t = Second BCD digit temperature - * - u = Third BCD digit temperature - * The Checksum seems to covers the 4 data bytes and is something like Fletcher-8. - **/ - -#define TX_8300_PACKAGE_SIZE 32 - -static const SubGhzBlockConst ws_protocol_tx_8300_const = { - .te_short = 1940, - .te_long = 3880, - .te_delta = 250, - .min_count_bit_for_found = 72, -}; - -struct WSProtocolDecoderTX_8300 { - SubGhzProtocolDecoderBase base; - - SubGhzBlockDecoder decoder; - WSBlockGeneric generic; - uint32_t package_1; - uint32_t package_2; -}; - -struct WSProtocolEncoderTX_8300 { - SubGhzProtocolEncoderBase base; - - SubGhzProtocolBlockEncoder encoder; - WSBlockGeneric generic; -}; - -typedef enum { - TX_8300DecoderStepReset = 0, - TX_8300DecoderStepCheckPreambule, - TX_8300DecoderStepSaveDuration, - TX_8300DecoderStepCheckDuration, -} TX_8300DecoderStep; - -const SubGhzProtocolDecoder ws_protocol_tx_8300_decoder = { - .alloc = ws_protocol_decoder_tx_8300_alloc, - .free = ws_protocol_decoder_tx_8300_free, - - .feed = ws_protocol_decoder_tx_8300_feed, - .reset = ws_protocol_decoder_tx_8300_reset, - - .get_hash_data = ws_protocol_decoder_tx_8300_get_hash_data, - .serialize = ws_protocol_decoder_tx_8300_serialize, - .deserialize = ws_protocol_decoder_tx_8300_deserialize, - .get_string = ws_protocol_decoder_tx_8300_get_string, -}; - -const SubGhzProtocolEncoder ws_protocol_tx_8300_encoder = { - .alloc = NULL, - .free = NULL, - - .deserialize = NULL, - .stop = NULL, - .yield = NULL, -}; - -const SubGhzProtocol ws_protocol_tx_8300 = { - .name = WS_PROTOCOL_TX_8300_NAME, - .type = SubGhzProtocolWeatherStation, - .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | - SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable, - - .decoder = &ws_protocol_tx_8300_decoder, - .encoder = &ws_protocol_tx_8300_encoder, -}; - -void* ws_protocol_decoder_tx_8300_alloc(SubGhzEnvironment* environment) { - UNUSED(environment); - WSProtocolDecoderTX_8300* instance = malloc(sizeof(WSProtocolDecoderTX_8300)); - instance->base.protocol = &ws_protocol_tx_8300; - instance->generic.protocol_name = instance->base.protocol->name; - return instance; -} - -void ws_protocol_decoder_tx_8300_free(void* context) { - furi_assert(context); - WSProtocolDecoderTX_8300* instance = context; - free(instance); -} - -void ws_protocol_decoder_tx_8300_reset(void* context) { - furi_assert(context); - WSProtocolDecoderTX_8300* instance = context; - instance->decoder.parser_step = TX_8300DecoderStepReset; -} - -static bool ws_protocol_tx_8300_check_crc(WSProtocolDecoderTX_8300* instance) { - if(!instance->package_2) return false; - if(instance->package_1 != ~instance->package_2) return false; - - uint16_t x = 0; - uint16_t y = 0; - for(int i = 0; i < 32; i += 4) { - x += (instance->package_1 >> i) & 0x0F; - y += (instance->package_1 >> i) & 0x05; - } - uint8_t crc = (~x & 0xF) << 4 | (~y & 0xF); - return (crc == ((instance->decoder.decode_data) & 0xFF)); -} - -/** - * Analysis of received data - * @param instance Pointer to a WSBlockGeneric* instance - */ -static void ws_protocol_tx_8300_remote_controller(WSBlockGeneric* instance) { - instance->humidity = (((instance->data >> 28) & 0x0F) * 10) + ((instance->data >> 24) & 0x0F); - instance->btn = WS_NO_BTN; - if(!((instance->data >> 22) & 0x03)) - instance->battery_low = 0; - else - instance->battery_low = 1; - instance->channel = (instance->data >> 20) & 0x03; - instance->id = (instance->data >> 12) & 0x7F; - - float temp_raw = ((instance->data >> 8) & 0x0F) * 10.0f + ((instance->data >> 4) & 0x0F) + - (instance->data & 0x0F) * 0.1f; - if(!((instance->data >> 19) & 1)) { - instance->temp = temp_raw; - } else { - instance->temp = -temp_raw; - } -} - -void ws_protocol_decoder_tx_8300_feed(void* context, bool level, uint32_t duration) { - furi_assert(context); - WSProtocolDecoderTX_8300* instance = context; - - switch(instance->decoder.parser_step) { - case TX_8300DecoderStepReset: - if((level) && (DURATION_DIFF(duration, ws_protocol_tx_8300_const.te_short * 2) < - ws_protocol_tx_8300_const.te_delta)) { - instance->decoder.parser_step = TX_8300DecoderStepCheckPreambule; - } - break; - - case TX_8300DecoderStepCheckPreambule: - if((!level) && ((DURATION_DIFF(duration, ws_protocol_tx_8300_const.te_short * 2) < - ws_protocol_tx_8300_const.te_delta) || - (DURATION_DIFF(duration, ws_protocol_tx_8300_const.te_short * 3) < - ws_protocol_tx_8300_const.te_delta))) { - instance->decoder.parser_step = TX_8300DecoderStepSaveDuration; - instance->decoder.decode_data = 0; - instance->decoder.decode_count_bit = 1; - instance->package_1 = 0; - instance->package_2 = 0; - } else { - instance->decoder.parser_step = TX_8300DecoderStepReset; - } - break; - - case TX_8300DecoderStepSaveDuration: - if(level) { - instance->decoder.te_last = duration; - instance->decoder.parser_step = TX_8300DecoderStepCheckDuration; - } else { - instance->decoder.parser_step = TX_8300DecoderStepReset; - } - break; - - case TX_8300DecoderStepCheckDuration: - if(!level) { - if(duration >= ((uint32_t)ws_protocol_tx_8300_const.te_short * 5)) { - //Found syncPostfix - if((instance->decoder.decode_count_bit == - ws_protocol_tx_8300_const.min_count_bit_for_found) && - ws_protocol_tx_8300_check_crc(instance)) { - instance->generic.data = instance->package_1; - instance->generic.data_count_bit = instance->decoder.decode_count_bit; - ws_protocol_tx_8300_remote_controller(&instance->generic); - if(instance->base.callback) - instance->base.callback(&instance->base, instance->base.context); - } - instance->decoder.decode_data = 0; - instance->decoder.decode_count_bit = 1; - instance->decoder.parser_step = TX_8300DecoderStepReset; - break; - } else if( - (DURATION_DIFF(instance->decoder.te_last, ws_protocol_tx_8300_const.te_short) < - ws_protocol_tx_8300_const.te_delta) && - (DURATION_DIFF(duration, ws_protocol_tx_8300_const.te_long) < - ws_protocol_tx_8300_const.te_delta * 2)) { - subghz_protocol_blocks_add_bit(&instance->decoder, 1); - instance->decoder.parser_step = TX_8300DecoderStepSaveDuration; - } else if( - (DURATION_DIFF(instance->decoder.te_last, ws_protocol_tx_8300_const.te_short) < - ws_protocol_tx_8300_const.te_delta) && - (DURATION_DIFF(duration, ws_protocol_tx_8300_const.te_short) < - ws_protocol_tx_8300_const.te_delta)) { - subghz_protocol_blocks_add_bit(&instance->decoder, 0); - instance->decoder.parser_step = TX_8300DecoderStepSaveDuration; - } else { - instance->decoder.parser_step = TX_8300DecoderStepReset; - } - - if(instance->decoder.decode_count_bit == TX_8300_PACKAGE_SIZE) { - instance->package_1 = instance->decoder.decode_data; - instance->decoder.decode_data = 0; - } else if(instance->decoder.decode_count_bit == TX_8300_PACKAGE_SIZE * 2) { - instance->package_2 = instance->decoder.decode_data; - instance->decoder.decode_data = 0; - } - - } else { - instance->decoder.parser_step = TX_8300DecoderStepReset; - } - break; - } -} - -uint8_t ws_protocol_decoder_tx_8300_get_hash_data(void* context) { - furi_assert(context); - WSProtocolDecoderTX_8300* instance = context; - return subghz_protocol_blocks_get_hash_data( - &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); -} - -SubGhzProtocolStatus ws_protocol_decoder_tx_8300_serialize( - void* context, - FlipperFormat* flipper_format, - SubGhzRadioPreset* preset) { - furi_assert(context); - WSProtocolDecoderTX_8300* instance = context; - return ws_block_generic_serialize(&instance->generic, flipper_format, preset); -} - -SubGhzProtocolStatus - ws_protocol_decoder_tx_8300_deserialize(void* context, FlipperFormat* flipper_format) { - furi_assert(context); - WSProtocolDecoderTX_8300* instance = context; - return ws_block_generic_deserialize_check_count_bit( - &instance->generic, flipper_format, ws_protocol_tx_8300_const.min_count_bit_for_found); -} - -void ws_protocol_decoder_tx_8300_get_string(void* context, FuriString* output) { - furi_assert(context); - WSProtocolDecoderTX_8300* instance = context; - furi_string_printf( - output, - "%s %dbit\r\n" - "Key:0x%lX%08lX\r\n" - "Sn:0x%lX Ch:%d Bat:%d\r\n" - "Temp:%3.1f C Hum:%d%%", - instance->generic.protocol_name, - instance->generic.data_count_bit, - (uint32_t)(instance->generic.data >> 32), - (uint32_t)(instance->generic.data), - instance->generic.id, - instance->generic.channel, - instance->generic.battery_low, - (double)instance->generic.temp, - instance->generic.humidity); -} diff --git a/applications/external/weather_station/protocols/tx_8300.h b/applications/external/weather_station/protocols/tx_8300.h deleted file mode 100644 index 088ccd7c034..00000000000 --- a/applications/external/weather_station/protocols/tx_8300.h +++ /dev/null @@ -1,80 +0,0 @@ -#pragma once - -#include - -#include -#include -#include -#include "ws_generic.h" -#include - -#define WS_PROTOCOL_TX_8300_NAME "TX8300" - -typedef struct WSProtocolDecoderTX_8300 WSProtocolDecoderTX_8300; -typedef struct WSProtocolEncoderTX_8300 WSProtocolEncoderTX_8300; - -extern const SubGhzProtocolDecoder ws_protocol_tx_8300_decoder; -extern const SubGhzProtocolEncoder ws_protocol_tx_8300_encoder; -extern const SubGhzProtocol ws_protocol_tx_8300; - -/** - * Allocate WSProtocolDecoderTX_8300. - * @param environment Pointer to a SubGhzEnvironment instance - * @return WSProtocolDecoderTX_8300* pointer to a WSProtocolDecoderTX_8300 instance - */ -void* ws_protocol_decoder_tx_8300_alloc(SubGhzEnvironment* environment); - -/** - * Free WSProtocolDecoderTX_8300. - * @param context Pointer to a WSProtocolDecoderTX_8300 instance - */ -void ws_protocol_decoder_tx_8300_free(void* context); - -/** - * Reset decoder WSProtocolDecoderTX_8300. - * @param context Pointer to a WSProtocolDecoderTX_8300 instance - */ -void ws_protocol_decoder_tx_8300_reset(void* context); - -/** - * Parse a raw sequence of levels and durations received from the air. - * @param context Pointer to a WSProtocolDecoderTX_8300 instance - * @param level Signal level true-high false-low - * @param duration Duration of this level in, us - */ -void ws_protocol_decoder_tx_8300_feed(void* context, bool level, uint32_t duration); - -/** - * Getting the hash sum of the last randomly received parcel. - * @param context Pointer to a WSProtocolDecoderTX_8300 instance - * @return hash Hash sum - */ -uint8_t ws_protocol_decoder_tx_8300_get_hash_data(void* context); - -/** - * Serialize data WSProtocolDecoderTX_8300. - * @param context Pointer to a WSProtocolDecoderTX_8300 instance - * @param flipper_format Pointer to a FlipperFormat instance - * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return status - */ -SubGhzProtocolStatus ws_protocol_decoder_tx_8300_serialize( - void* context, - FlipperFormat* flipper_format, - SubGhzRadioPreset* preset); - -/** - * Deserialize data WSProtocolDecoderTX_8300. - * @param context Pointer to a WSProtocolDecoderTX_8300 instance - * @param flipper_format Pointer to a FlipperFormat instance - * @return status - */ -SubGhzProtocolStatus - ws_protocol_decoder_tx_8300_deserialize(void* context, FlipperFormat* flipper_format); - -/** - * Getting a textual representation of the received data. - * @param context Pointer to a WSProtocolDecoderTX_8300 instance - * @param output Resulting text - */ -void ws_protocol_decoder_tx_8300_get_string(void* context, FuriString* output); diff --git a/applications/external/weather_station/protocols/wendox_w6726.c b/applications/external/weather_station/protocols/wendox_w6726.c deleted file mode 100644 index 2fbe961f725..00000000000 --- a/applications/external/weather_station/protocols/wendox_w6726.c +++ /dev/null @@ -1,299 +0,0 @@ -#include "wendox_w6726.h" - -#define TAG "WSProtocolWendoxW6726" - -/* - * Wendox W6726 - * - * Temperature -50C to +70C - * _ _ _ __ _ - * _| |___| |___| |___ ... | |_| |__...._______________ - * preamble data guard time - * - * 3 reps every 3 minutes - * in the first message 11 bytes of the preamble in the rest by 7 - * - * bit 0: 1955-hi, 5865-lo - * bit 1: 5865-hi, 1955-lo - * guard time: 12*1955+(lo last bit) - * data: 29 bit - * - * IIIII | ZTTTTTTTTT | uuuuuuuBuu | CCCC - * - * I: identification; - * Z: temperature sign; - * T: temperature sign dependent +12C; - * B: battery low; flag to indicate low battery voltage; - * C: CRC4 (polynomial = 0x9, start_data = 0xD); - * u: unknown; - */ - -static const SubGhzBlockConst ws_protocol_wendox_w6726_const = { - .te_short = 1955, - .te_long = 5865, - .te_delta = 300, - .min_count_bit_for_found = 29, -}; - -struct WSProtocolDecoderWendoxW6726 { - SubGhzProtocolDecoderBase base; - - SubGhzBlockDecoder decoder; - WSBlockGeneric generic; - - uint16_t header_count; -}; - -struct WSProtocolEncoderWendoxW6726 { - SubGhzProtocolEncoderBase base; - - SubGhzProtocolBlockEncoder encoder; - WSBlockGeneric generic; -}; - -typedef enum { - WendoxW6726DecoderStepReset = 0, - WendoxW6726DecoderStepCheckPreambule, - WendoxW6726DecoderStepSaveDuration, - WendoxW6726DecoderStepCheckDuration, -} WendoxW6726DecoderStep; - -const SubGhzProtocolDecoder ws_protocol_wendox_w6726_decoder = { - .alloc = ws_protocol_decoder_wendox_w6726_alloc, - .free = ws_protocol_decoder_wendox_w6726_free, - - .feed = ws_protocol_decoder_wendox_w6726_feed, - .reset = ws_protocol_decoder_wendox_w6726_reset, - - .get_hash_data = ws_protocol_decoder_wendox_w6726_get_hash_data, - .serialize = ws_protocol_decoder_wendox_w6726_serialize, - .deserialize = ws_protocol_decoder_wendox_w6726_deserialize, - .get_string = ws_protocol_decoder_wendox_w6726_get_string, -}; - -const SubGhzProtocolEncoder ws_protocol_wendox_w6726_encoder = { - .alloc = NULL, - .free = NULL, - - .deserialize = NULL, - .stop = NULL, - .yield = NULL, -}; - -const SubGhzProtocol ws_protocol_wendox_w6726 = { - .name = WS_PROTOCOL_WENDOX_W6726_NAME, - .type = SubGhzProtocolWeatherStation, - .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | - SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable, - - .decoder = &ws_protocol_wendox_w6726_decoder, - .encoder = &ws_protocol_wendox_w6726_encoder, -}; - -void* ws_protocol_decoder_wendox_w6726_alloc(SubGhzEnvironment* environment) { - UNUSED(environment); - WSProtocolDecoderWendoxW6726* instance = malloc(sizeof(WSProtocolDecoderWendoxW6726)); - instance->base.protocol = &ws_protocol_wendox_w6726; - instance->generic.protocol_name = instance->base.protocol->name; - return instance; -} - -void ws_protocol_decoder_wendox_w6726_free(void* context) { - furi_assert(context); - WSProtocolDecoderWendoxW6726* instance = context; - free(instance); -} - -void ws_protocol_decoder_wendox_w6726_reset(void* context) { - furi_assert(context); - WSProtocolDecoderWendoxW6726* instance = context; - instance->decoder.parser_step = WendoxW6726DecoderStepReset; -} - -static bool ws_protocol_wendox_w6726_check(WSProtocolDecoderWendoxW6726* instance) { - if(!instance->decoder.decode_data) return false; - uint8_t msg[] = { - instance->decoder.decode_data >> 28, - instance->decoder.decode_data >> 20, - instance->decoder.decode_data >> 12, - instance->decoder.decode_data >> 4}; - - uint8_t crc = subghz_protocol_blocks_crc4(msg, 4, 0x9, 0xD); - return (crc == (instance->decoder.decode_data & 0x0F)); -} - -/** - * Analysis of received data - * @param instance Pointer to a WSBlockGeneric* instance - */ -static void ws_protocol_wendox_w6726_remote_controller(WSBlockGeneric* instance) { - instance->id = (instance->data >> 24) & 0xFF; - instance->battery_low = (instance->data >> 6) & 1; - instance->channel = WS_NO_CHANNEL; - - if(((instance->data >> 23) & 1)) { - instance->temp = (float)(((instance->data >> 14) & 0x1FF) + 12) / 10.0f; - } else { - instance->temp = (float)((~(instance->data >> 14) & 0x1FF) + 1 - 12) / -10.0f; - } - - if(instance->temp < -50.0f) { - instance->temp = -50.0f; - } else if(instance->temp > 70.0f) { - instance->temp = 70.0f; - } - - instance->btn = WS_NO_BTN; - instance->humidity = WS_NO_HUMIDITY; -} - -void ws_protocol_decoder_wendox_w6726_feed(void* context, bool level, uint32_t duration) { - furi_assert(context); - WSProtocolDecoderWendoxW6726* instance = context; - - switch(instance->decoder.parser_step) { - case WendoxW6726DecoderStepReset: - if((level) && (DURATION_DIFF(duration, ws_protocol_wendox_w6726_const.te_short) < - ws_protocol_wendox_w6726_const.te_delta)) { - instance->decoder.parser_step = WendoxW6726DecoderStepCheckPreambule; - instance->decoder.te_last = duration; - instance->header_count = 0; - } - break; - - case WendoxW6726DecoderStepCheckPreambule: - if(level) { - instance->decoder.te_last = duration; - } else { - if((DURATION_DIFF(instance->decoder.te_last, ws_protocol_wendox_w6726_const.te_short) < - ws_protocol_wendox_w6726_const.te_delta * 1) && - (DURATION_DIFF(duration, ws_protocol_wendox_w6726_const.te_long) < - ws_protocol_wendox_w6726_const.te_delta * 2)) { - instance->header_count++; - } else if((instance->header_count > 4) && (instance->header_count < 12)) { - if((DURATION_DIFF( - instance->decoder.te_last, ws_protocol_wendox_w6726_const.te_long) < - ws_protocol_wendox_w6726_const.te_delta * 2) && - (DURATION_DIFF(duration, ws_protocol_wendox_w6726_const.te_short) < - ws_protocol_wendox_w6726_const.te_delta)) { - instance->decoder.decode_data = 0; - instance->decoder.decode_count_bit = 0; - subghz_protocol_blocks_add_bit(&instance->decoder, 1); - instance->decoder.parser_step = WendoxW6726DecoderStepSaveDuration; - } else { - instance->decoder.parser_step = WendoxW6726DecoderStepReset; - } - - } else { - instance->decoder.parser_step = WendoxW6726DecoderStepReset; - } - } - break; - - case WendoxW6726DecoderStepSaveDuration: - if(level) { - instance->decoder.te_last = duration; - instance->decoder.parser_step = WendoxW6726DecoderStepCheckDuration; - } else { - instance->decoder.parser_step = WendoxW6726DecoderStepReset; - } - break; - - case WendoxW6726DecoderStepCheckDuration: - if(!level) { - if(duration > - ws_protocol_wendox_w6726_const.te_short + ws_protocol_wendox_w6726_const.te_long) { - if(DURATION_DIFF( - instance->decoder.te_last, ws_protocol_wendox_w6726_const.te_short) < - ws_protocol_wendox_w6726_const.te_delta) { - subghz_protocol_blocks_add_bit(&instance->decoder, 0); - instance->decoder.parser_step = WendoxW6726DecoderStepSaveDuration; - } else if( - DURATION_DIFF( - instance->decoder.te_last, ws_protocol_wendox_w6726_const.te_long) < - ws_protocol_wendox_w6726_const.te_delta * 2) { - subghz_protocol_blocks_add_bit(&instance->decoder, 1); - instance->decoder.parser_step = WendoxW6726DecoderStepSaveDuration; - } else { - instance->decoder.parser_step = WendoxW6726DecoderStepReset; - } - if((instance->decoder.decode_count_bit == - ws_protocol_wendox_w6726_const.min_count_bit_for_found) && - ws_protocol_wendox_w6726_check(instance)) { - instance->generic.data = instance->decoder.decode_data; - instance->generic.data_count_bit = instance->decoder.decode_count_bit; - ws_protocol_wendox_w6726_remote_controller(&instance->generic); - if(instance->base.callback) - instance->base.callback(&instance->base, instance->base.context); - } - - instance->decoder.parser_step = WendoxW6726DecoderStepReset; - } else if( - (DURATION_DIFF(instance->decoder.te_last, ws_protocol_wendox_w6726_const.te_short) < - ws_protocol_wendox_w6726_const.te_delta) && - (DURATION_DIFF(duration, ws_protocol_wendox_w6726_const.te_long) < - ws_protocol_wendox_w6726_const.te_delta * 3)) { - subghz_protocol_blocks_add_bit(&instance->decoder, 0); - instance->decoder.parser_step = WendoxW6726DecoderStepSaveDuration; - } else if( - (DURATION_DIFF(instance->decoder.te_last, ws_protocol_wendox_w6726_const.te_long) < - ws_protocol_wendox_w6726_const.te_delta * 2) && - (DURATION_DIFF(duration, ws_protocol_wendox_w6726_const.te_short) < - ws_protocol_wendox_w6726_const.te_delta)) { - subghz_protocol_blocks_add_bit(&instance->decoder, 1); - instance->decoder.parser_step = WendoxW6726DecoderStepSaveDuration; - } else { - instance->decoder.parser_step = WendoxW6726DecoderStepReset; - } - } else { - instance->decoder.parser_step = WendoxW6726DecoderStepReset; - } - break; - } -} - -uint8_t ws_protocol_decoder_wendox_w6726_get_hash_data(void* context) { - furi_assert(context); - WSProtocolDecoderWendoxW6726* instance = context; - return subghz_protocol_blocks_get_hash_data( - &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); -} - -SubGhzProtocolStatus ws_protocol_decoder_wendox_w6726_serialize( - void* context, - FlipperFormat* flipper_format, - SubGhzRadioPreset* preset) { - furi_assert(context); - WSProtocolDecoderWendoxW6726* instance = context; - return ws_block_generic_serialize(&instance->generic, flipper_format, preset); -} - -SubGhzProtocolStatus - ws_protocol_decoder_wendox_w6726_deserialize(void* context, FlipperFormat* flipper_format) { - furi_assert(context); - WSProtocolDecoderWendoxW6726* instance = context; - return ws_block_generic_deserialize_check_count_bit( - &instance->generic, - flipper_format, - ws_protocol_wendox_w6726_const.min_count_bit_for_found); -} - -void ws_protocol_decoder_wendox_w6726_get_string(void* context, FuriString* output) { - furi_assert(context); - WSProtocolDecoderWendoxW6726* instance = context; - furi_string_printf( - output, - "%s %dbit\r\n" - "Key:0x%lX%08lX\r\n" - "Sn:0x%lX Ch:%d Bat:%d\r\n" - "Temp:%3.1f C Hum:%d%%", - instance->generic.protocol_name, - instance->generic.data_count_bit, - (uint32_t)(instance->generic.data >> 32), - (uint32_t)(instance->generic.data), - instance->generic.id, - instance->generic.channel, - instance->generic.battery_low, - (double)instance->generic.temp, - instance->generic.humidity); -} diff --git a/applications/external/weather_station/protocols/wendox_w6726.h b/applications/external/weather_station/protocols/wendox_w6726.h deleted file mode 100644 index 236777a1c86..00000000000 --- a/applications/external/weather_station/protocols/wendox_w6726.h +++ /dev/null @@ -1,80 +0,0 @@ -#pragma once - -#include - -#include -#include -#include -#include "ws_generic.h" -#include - -#define WS_PROTOCOL_WENDOX_W6726_NAME "Wendox W6726" - -typedef struct WSProtocolDecoderWendoxW6726 WSProtocolDecoderWendoxW6726; -typedef struct WSProtocolEncoderWendoxW6726 WSProtocolEncoderWendoxW6726; - -extern const SubGhzProtocolDecoder ws_protocol_wendox_w6726_decoder; -extern const SubGhzProtocolEncoder ws_protocol_wendox_w6726_encoder; -extern const SubGhzProtocol ws_protocol_wendox_w6726; - -/** - * Allocate WSProtocolDecoderWendoxW6726. - * @param environment Pointer to a SubGhzEnvironment instance - * @return WSProtocolDecoderWendoxW6726* pointer to a WSProtocolDecoderWendoxW6726 instance - */ -void* ws_protocol_decoder_wendox_w6726_alloc(SubGhzEnvironment* environment); - -/** - * Free WSProtocolDecoderWendoxW6726. - * @param context Pointer to a WSProtocolDecoderWendoxW6726 instance - */ -void ws_protocol_decoder_wendox_w6726_free(void* context); - -/** - * Reset decoder WSProtocolDecoderWendoxW6726. - * @param context Pointer to a WSProtocolDecoderWendoxW6726 instance - */ -void ws_protocol_decoder_wendox_w6726_reset(void* context); - -/** - * Parse a raw sequence of levels and durations received from the air. - * @param context Pointer to a WSProtocolDecoderWendoxW6726 instance - * @param level Signal level true-high false-low - * @param duration Duration of this level in, us - */ -void ws_protocol_decoder_wendox_w6726_feed(void* context, bool level, uint32_t duration); - -/** - * Getting the hash sum of the last randomly received parcel. - * @param context Pointer to a WSProtocolDecoderWendoxW6726 instance - * @return hash Hash sum - */ -uint8_t ws_protocol_decoder_wendox_w6726_get_hash_data(void* context); - -/** - * Serialize data WSProtocolDecoderWendoxW6726. - * @param context Pointer to a WSProtocolDecoderWendoxW6726 instance - * @param flipper_format Pointer to a FlipperFormat instance - * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return status - */ -SubGhzProtocolStatus ws_protocol_decoder_wendox_w6726_serialize( - void* context, - FlipperFormat* flipper_format, - SubGhzRadioPreset* preset); - -/** - * Deserialize data WSProtocolDecoderWendoxW6726. - * @param context Pointer to a WSProtocolDecoderWendoxW6726 instance - * @param flipper_format Pointer to a FlipperFormat instance - * @return status - */ -SubGhzProtocolStatus - ws_protocol_decoder_wendox_w6726_deserialize(void* context, FlipperFormat* flipper_format); - -/** - * Getting a textual representation of the received data. - * @param context Pointer to a WSProtocolDecoderWendoxW6726 instance - * @param output Resulting text - */ -void ws_protocol_decoder_wendox_w6726_get_string(void* context, FuriString* output); diff --git a/applications/external/weather_station/protocols/ws_generic.c b/applications/external/weather_station/protocols/ws_generic.c deleted file mode 100644 index 026856a9eb5..00000000000 --- a/applications/external/weather_station/protocols/ws_generic.c +++ /dev/null @@ -1,256 +0,0 @@ -#include "ws_generic.h" -#include -#include -#include "../helpers/weather_station_types.h" - -#define TAG "WSBlockGeneric" - -void ws_block_generic_get_preset_name(const char* preset_name, FuriString* preset_str) { - const char* preset_name_temp; - if(!strcmp(preset_name, "AM270")) { - preset_name_temp = "FuriHalSubGhzPresetOok270Async"; - } else if(!strcmp(preset_name, "AM650")) { - preset_name_temp = "FuriHalSubGhzPresetOok650Async"; - } else if(!strcmp(preset_name, "FM238")) { - preset_name_temp = "FuriHalSubGhzPreset2FSKDev238Async"; - } else if(!strcmp(preset_name, "FM476")) { - preset_name_temp = "FuriHalSubGhzPreset2FSKDev476Async"; - } else { - preset_name_temp = "FuriHalSubGhzPresetCustom"; - } - furi_string_set(preset_str, preset_name_temp); -} - -SubGhzProtocolStatus ws_block_generic_serialize( - WSBlockGeneric* instance, - FlipperFormat* flipper_format, - SubGhzRadioPreset* preset) { - furi_assert(instance); - SubGhzProtocolStatus res = SubGhzProtocolStatusError; - FuriString* temp_str; - temp_str = furi_string_alloc(); - do { - stream_clean(flipper_format_get_raw_stream(flipper_format)); - if(!flipper_format_write_header_cstr( - flipper_format, WS_KEY_FILE_TYPE, WS_KEY_FILE_VERSION)) { - FURI_LOG_E(TAG, "Unable to add header"); - res = SubGhzProtocolStatusErrorParserHeader; - break; - } - - if(!flipper_format_write_uint32(flipper_format, "Frequency", &preset->frequency, 1)) { - FURI_LOG_E(TAG, "Unable to add Frequency"); - res = SubGhzProtocolStatusErrorParserFrequency; - break; - } - - ws_block_generic_get_preset_name(furi_string_get_cstr(preset->name), temp_str); - if(!flipper_format_write_string_cstr( - flipper_format, "Preset", furi_string_get_cstr(temp_str))) { - FURI_LOG_E(TAG, "Unable to add Preset"); - res = SubGhzProtocolStatusErrorParserPreset; - break; - } - if(!strcmp(furi_string_get_cstr(temp_str), "FuriHalSubGhzPresetCustom")) { - if(!flipper_format_write_string_cstr( - flipper_format, "Custom_preset_module", "CC1101")) { - FURI_LOG_E(TAG, "Unable to add Custom_preset_module"); - res = SubGhzProtocolStatusErrorParserCustomPreset; - break; - } - if(!flipper_format_write_hex( - flipper_format, "Custom_preset_data", preset->data, preset->data_size)) { - FURI_LOG_E(TAG, "Unable to add Custom_preset_data"); - res = SubGhzProtocolStatusErrorParserCustomPreset; - break; - } - } - if(!flipper_format_write_string_cstr(flipper_format, "Protocol", instance->protocol_name)) { - FURI_LOG_E(TAG, "Unable to add Protocol"); - res = SubGhzProtocolStatusErrorParserProtocolName; - break; - } - - uint32_t temp_data = instance->id; - if(!flipper_format_write_uint32(flipper_format, "Id", &temp_data, 1)) { - FURI_LOG_E(TAG, "Unable to add Id"); - res = SubGhzProtocolStatusErrorParserOthers; - break; - } - - temp_data = instance->data_count_bit; - if(!flipper_format_write_uint32(flipper_format, "Bit", &temp_data, 1)) { - FURI_LOG_E(TAG, "Unable to add Bit"); - res = SubGhzProtocolStatusErrorParserBitCount; - 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->data >> (i * 8)) & 0xFF; - } - - if(!flipper_format_write_hex(flipper_format, "Data", key_data, sizeof(uint64_t))) { - FURI_LOG_E(TAG, "Unable to add Data"); - res = SubGhzProtocolStatusErrorParserOthers; - break; - } - - temp_data = instance->battery_low; - if(!flipper_format_write_uint32(flipper_format, "Batt", &temp_data, 1)) { - FURI_LOG_E(TAG, "Unable to add Battery_low"); - res = SubGhzProtocolStatusErrorParserOthers; - break; - } - - temp_data = instance->humidity; - if(!flipper_format_write_uint32(flipper_format, "Hum", &temp_data, 1)) { - FURI_LOG_E(TAG, "Unable to add Humidity"); - res = SubGhzProtocolStatusErrorParserOthers; - break; - } - - //DATE AGE set - FuriHalRtcDateTime curr_dt; - furi_hal_rtc_get_datetime(&curr_dt); - uint32_t curr_ts = furi_hal_rtc_datetime_to_timestamp(&curr_dt); - - temp_data = curr_ts; - if(!flipper_format_write_uint32(flipper_format, "Ts", &temp_data, 1)) { - FURI_LOG_E(TAG, "Unable to add timestamp"); - res = SubGhzProtocolStatusErrorParserOthers; - break; - } - - temp_data = instance->channel; - if(!flipper_format_write_uint32(flipper_format, "Ch", &temp_data, 1)) { - FURI_LOG_E(TAG, "Unable to add Channel"); - res = SubGhzProtocolStatusErrorParserOthers; - break; - } - - temp_data = instance->btn; - if(!flipper_format_write_uint32(flipper_format, "Btn", &temp_data, 1)) { - FURI_LOG_E(TAG, "Unable to add Btn"); - res = SubGhzProtocolStatusErrorParserOthers; - break; - } - - float temp = instance->temp; - if(!flipper_format_write_float(flipper_format, "Temp", &temp, 1)) { - FURI_LOG_E(TAG, "Unable to add Temperature"); - res = SubGhzProtocolStatusErrorParserOthers; - break; - } - - res = SubGhzProtocolStatusOk; - } while(false); - furi_string_free(temp_str); - return res; -} - -SubGhzProtocolStatus - ws_block_generic_deserialize(WSBlockGeneric* instance, FlipperFormat* flipper_format) { - furi_assert(instance); - SubGhzProtocolStatus res = SubGhzProtocolStatusError; - uint32_t temp_data = 0; - - do { - if(!flipper_format_rewind(flipper_format)) { - FURI_LOG_E(TAG, "Rewind error"); - res = SubGhzProtocolStatusErrorParserOthers; - break; - } - - if(!flipper_format_read_uint32(flipper_format, "Id", (uint32_t*)&temp_data, 1)) { - FURI_LOG_E(TAG, "Missing Id"); - res = SubGhzProtocolStatusErrorParserOthers; - break; - } - instance->id = (uint32_t)temp_data; - - if(!flipper_format_read_uint32(flipper_format, "Bit", (uint32_t*)&temp_data, 1)) { - FURI_LOG_E(TAG, "Missing Bit"); - res = SubGhzProtocolStatusErrorParserBitCount; - break; - } - instance->data_count_bit = (uint8_t)temp_data; - - uint8_t key_data[sizeof(uint64_t)] = {0}; - if(!flipper_format_read_hex(flipper_format, "Data", key_data, sizeof(uint64_t))) { - FURI_LOG_E(TAG, "Missing Data"); - res = SubGhzProtocolStatusErrorParserOthers; - break; - } - - for(uint8_t i = 0; i < sizeof(uint64_t); i++) { - instance->data = instance->data << 8 | key_data[i]; - } - - if(!flipper_format_read_uint32(flipper_format, "Batt", (uint32_t*)&temp_data, 1)) { - FURI_LOG_E(TAG, "Missing Battery_low"); - res = SubGhzProtocolStatusErrorParserOthers; - break; - } - instance->battery_low = (uint8_t)temp_data; - - if(!flipper_format_read_uint32(flipper_format, "Hum", (uint32_t*)&temp_data, 1)) { - FURI_LOG_E(TAG, "Missing Humidity"); - res = SubGhzProtocolStatusErrorParserOthers; - break; - } - instance->humidity = (uint8_t)temp_data; - - if(!flipper_format_read_uint32(flipper_format, "Ts", (uint32_t*)&temp_data, 1)) { - FURI_LOG_E(TAG, "Missing timestamp"); - res = SubGhzProtocolStatusErrorParserOthers; - break; - } - instance->timestamp = (uint32_t)temp_data; - - if(!flipper_format_read_uint32(flipper_format, "Ch", (uint32_t*)&temp_data, 1)) { - FURI_LOG_E(TAG, "Missing Channel"); - res = SubGhzProtocolStatusErrorParserOthers; - break; - } - instance->channel = (uint8_t)temp_data; - - if(!flipper_format_read_uint32(flipper_format, "Btn", (uint32_t*)&temp_data, 1)) { - FURI_LOG_E(TAG, "Missing Btn"); - res = SubGhzProtocolStatusErrorParserOthers; - break; - } - instance->btn = (uint8_t)temp_data; - - float temp; - if(!flipper_format_read_float(flipper_format, "Temp", (float*)&temp, 1)) { - FURI_LOG_E(TAG, "Missing Temperature"); - res = SubGhzProtocolStatusErrorParserOthers; - break; - } - instance->temp = temp; - - res = SubGhzProtocolStatusOk; - } while(0); - - return res; -} - -SubGhzProtocolStatus ws_block_generic_deserialize_check_count_bit( - WSBlockGeneric* instance, - FlipperFormat* flipper_format, - uint16_t count_bit) { - SubGhzProtocolStatus ret = SubGhzProtocolStatusError; - do { - ret = ws_block_generic_deserialize(instance, flipper_format); - if(ret != SubGhzProtocolStatusOk) { - break; - } - if(instance->data_count_bit != count_bit) { - FURI_LOG_E(TAG, "Wrong number of bits in key"); - ret = SubGhzProtocolStatusErrorValueBitCount; - break; - } - } while(false); - return ret; -} \ No newline at end of file diff --git a/applications/external/weather_station/protocols/ws_generic.h b/applications/external/weather_station/protocols/ws_generic.h deleted file mode 100644 index ff047fae654..00000000000 --- a/applications/external/weather_station/protocols/ws_generic.h +++ /dev/null @@ -1,81 +0,0 @@ -#pragma once - -#include -#include -#include - -#include -#include "furi.h" -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -#define WS_NO_ID 0xFFFFFFFF -#define WS_NO_BATT 0xFF -#define WS_NO_HUMIDITY 0xFF -#define WS_NO_CHANNEL 0xFF -#define WS_NO_BTN 0xFF -#define WS_NO_TEMPERATURE -273.0f - -typedef struct WSBlockGeneric WSBlockGeneric; - -struct WSBlockGeneric { - const char* protocol_name; - uint64_t data; - uint32_t id; - uint8_t data_count_bit; - uint8_t battery_low; - uint8_t humidity; - uint32_t timestamp; - uint8_t channel; - uint8_t btn; - float temp; -}; - -/** - * Get name preset. - * @param preset_name name preset - * @param preset_str Output name preset - */ -void ws_block_generic_get_preset_name(const char* preset_name, FuriString* preset_str); - -/** - * Serialize data WSBlockGeneric. - * @param instance Pointer to a WSBlockGeneric instance - * @param flipper_format Pointer to a FlipperFormat instance - * @param preset The modulation on which the signal was received, SubGhzRadioPreset - * @return status - */ -SubGhzProtocolStatus ws_block_generic_serialize( - WSBlockGeneric* instance, - FlipperFormat* flipper_format, - SubGhzRadioPreset* preset); - -/** - * Deserialize data WSBlockGeneric. - * @param instance Pointer to a WSBlockGeneric instance - * @param flipper_format Pointer to a FlipperFormat instance - * @return status - */ -SubGhzProtocolStatus - ws_block_generic_deserialize(WSBlockGeneric* instance, FlipperFormat* flipper_format); - -/** - * Deserialize data WSBlockGeneric. - * @param instance Pointer to a WSBlockGeneric instance - * @param flipper_format Pointer to a FlipperFormat instance - * @param count_bit Count bit protocol - * @return status - */ -SubGhzProtocolStatus ws_block_generic_deserialize_check_count_bit( - WSBlockGeneric* instance, - FlipperFormat* flipper_format, - uint16_t count_bit); - -#ifdef __cplusplus -} -#endif \ No newline at end of file diff --git a/applications/external/weather_station/scenes/weather_station_receiver.c b/applications/external/weather_station/scenes/weather_station_receiver.c deleted file mode 100644 index e76810430a7..00000000000 --- a/applications/external/weather_station/scenes/weather_station_receiver.c +++ /dev/null @@ -1,211 +0,0 @@ -#include "../weather_station_app_i.h" -#include "../views/weather_station_receiver.h" - -static const NotificationSequence subghs_sequence_rx = { - &message_green_255, - - &message_vibro_on, - &message_note_c6, - &message_delay_50, - &message_sound_off, - &message_vibro_off, - - &message_delay_50, - NULL, -}; - -static const NotificationSequence subghs_sequence_rx_locked = { - &message_green_255, - - &message_display_backlight_on, - - &message_vibro_on, - &message_note_c6, - &message_delay_50, - &message_sound_off, - &message_vibro_off, - - &message_delay_500, - - &message_display_backlight_off, - NULL, -}; - -static void weather_station_scene_receiver_update_statusbar(void* context) { - WeatherStationApp* app = context; - FuriString* history_stat_str; - history_stat_str = furi_string_alloc(); - if(!ws_history_get_text_space_left(app->txrx->history, history_stat_str)) { - FuriString* frequency_str; - FuriString* modulation_str; - - frequency_str = furi_string_alloc(); - modulation_str = furi_string_alloc(); - - ws_get_frequency_modulation(app, frequency_str, modulation_str); - - ws_view_receiver_add_data_statusbar( - app->ws_receiver, - furi_string_get_cstr(frequency_str), - furi_string_get_cstr(modulation_str), - furi_string_get_cstr(history_stat_str)); - - furi_string_free(frequency_str); - furi_string_free(modulation_str); - } else { - ws_view_receiver_add_data_statusbar( - app->ws_receiver, furi_string_get_cstr(history_stat_str), "", ""); - } - furi_string_free(history_stat_str); -} - -void weather_station_scene_receiver_callback(WSCustomEvent event, void* context) { - furi_assert(context); - WeatherStationApp* app = context; - view_dispatcher_send_custom_event(app->view_dispatcher, event); -} - -static void weather_station_scene_receiver_add_to_history_callback( - SubGhzReceiver* receiver, - SubGhzProtocolDecoderBase* decoder_base, - void* context) { - furi_assert(context); - WeatherStationApp* app = context; - FuriString* str_buff; - str_buff = furi_string_alloc(); - - if(ws_history_add_to_history(app->txrx->history, decoder_base, app->txrx->preset) == - WSHistoryStateAddKeyNewDada) { - furi_string_reset(str_buff); - - ws_history_get_text_item_menu( - app->txrx->history, str_buff, ws_history_get_item(app->txrx->history) - 1); - ws_view_receiver_add_item_to_menu( - app->ws_receiver, - furi_string_get_cstr(str_buff), - ws_history_get_type_protocol( - app->txrx->history, ws_history_get_item(app->txrx->history) - 1)); - - weather_station_scene_receiver_update_statusbar(app); - notification_message(app->notifications, &sequence_blink_green_10); - if(app->lock != WSLockOn) { - notification_message(app->notifications, &subghs_sequence_rx); - } else { - notification_message(app->notifications, &subghs_sequence_rx_locked); - } - } - subghz_receiver_reset(receiver); - furi_string_free(str_buff); - app->txrx->rx_key_state = WSRxKeyStateAddKey; -} - -void weather_station_scene_receiver_on_enter(void* context) { - WeatherStationApp* app = context; - - FuriString* str_buff; - str_buff = furi_string_alloc(); - - if(app->txrx->rx_key_state == WSRxKeyStateIDLE) { - ws_preset_init(app, "AM650", subghz_setting_get_default_frequency(app->setting), NULL, 0); - ws_history_reset(app->txrx->history); - app->txrx->rx_key_state = WSRxKeyStateStart; - } - - ws_view_receiver_set_lock(app->ws_receiver, app->lock); - - //Load history to receiver - ws_view_receiver_exit(app->ws_receiver); - for(uint8_t i = 0; i < ws_history_get_item(app->txrx->history); i++) { - furi_string_reset(str_buff); - ws_history_get_text_item_menu(app->txrx->history, str_buff, i); - ws_view_receiver_add_item_to_menu( - app->ws_receiver, - furi_string_get_cstr(str_buff), - ws_history_get_type_protocol(app->txrx->history, i)); - app->txrx->rx_key_state = WSRxKeyStateAddKey; - } - furi_string_free(str_buff); - weather_station_scene_receiver_update_statusbar(app); - - ws_view_receiver_set_callback(app->ws_receiver, weather_station_scene_receiver_callback, app); - subghz_receiver_set_rx_callback( - app->txrx->receiver, weather_station_scene_receiver_add_to_history_callback, app); - - if(app->txrx->txrx_state == WSTxRxStateRx) { - ws_rx_end(app); - }; - if((app->txrx->txrx_state == WSTxRxStateIDLE) || (app->txrx->txrx_state == WSTxRxStateSleep)) { - ws_begin( - app, - subghz_setting_get_preset_data_by_name( - app->setting, furi_string_get_cstr(app->txrx->preset->name))); - - ws_rx(app, app->txrx->preset->frequency); - } - - ws_view_receiver_set_idx_menu(app->ws_receiver, app->txrx->idx_menu_chosen); - view_dispatcher_switch_to_view(app->view_dispatcher, WeatherStationViewReceiver); -} - -bool weather_station_scene_receiver_on_event(void* context, SceneManagerEvent event) { - WeatherStationApp* app = context; - bool consumed = false; - if(event.type == SceneManagerEventTypeCustom) { - switch(event.event) { - case WSCustomEventViewReceiverBack: - // Stop CC1101 Rx - if(app->txrx->txrx_state == WSTxRxStateRx) { - ws_rx_end(app); - ws_sleep(app); - }; - app->txrx->hopper_state = WSHopperStateOFF; - app->txrx->idx_menu_chosen = 0; - subghz_receiver_set_rx_callback(app->txrx->receiver, NULL, app); - - app->txrx->rx_key_state = WSRxKeyStateIDLE; - ws_preset_init( - app, "AM650", subghz_setting_get_default_frequency(app->setting), NULL, 0); - scene_manager_search_and_switch_to_previous_scene( - app->scene_manager, WeatherStationSceneStart); - consumed = true; - break; - case WSCustomEventViewReceiverOK: - app->txrx->idx_menu_chosen = ws_view_receiver_get_idx_menu(app->ws_receiver); - scene_manager_next_scene(app->scene_manager, WeatherStationSceneReceiverInfo); - consumed = true; - break; - case WSCustomEventViewReceiverConfig: - app->txrx->idx_menu_chosen = ws_view_receiver_get_idx_menu(app->ws_receiver); - scene_manager_next_scene(app->scene_manager, WeatherStationSceneReceiverConfig); - consumed = true; - break; - case WSCustomEventViewReceiverOffDisplay: - notification_message(app->notifications, &sequence_display_backlight_off); - consumed = true; - break; - case WSCustomEventViewReceiverUnlock: - app->lock = WSLockOff; - consumed = true; - break; - default: - break; - } - } else if(event.type == SceneManagerEventTypeTick) { - if(app->txrx->hopper_state != WSHopperStateOFF) { - ws_hopper_update(app); - weather_station_scene_receiver_update_statusbar(app); - } - // Get current RSSI - float rssi = furi_hal_subghz_get_rssi(); - ws_view_receiver_set_rssi(app->ws_receiver, rssi); - - if(app->txrx->txrx_state == WSTxRxStateRx) { - notification_message(app->notifications, &sequence_blink_cyan_10); - } - } - return consumed; -} - -void weather_station_scene_receiver_on_exit(void* context) { - UNUSED(context); -} diff --git a/applications/external/weather_station/scenes/weather_station_scene.c b/applications/external/weather_station/scenes/weather_station_scene.c deleted file mode 100644 index f9306e5f456..00000000000 --- a/applications/external/weather_station/scenes/weather_station_scene.c +++ /dev/null @@ -1,30 +0,0 @@ -#include "../weather_station_app_i.h" - -// Generate scene on_enter handlers array -#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, -void (*const weather_station_scene_on_enter_handlers[])(void*) = { -#include "weather_station_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 weather_station_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = { -#include "weather_station_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 weather_station_scene_on_exit_handlers[])(void* context) = { -#include "weather_station_scene_config.h" -}; -#undef ADD_SCENE - -// Initialize scene handlers configuration structure -const SceneManagerHandlers weather_station_scene_handlers = { - .on_enter_handlers = weather_station_scene_on_enter_handlers, - .on_event_handlers = weather_station_scene_on_event_handlers, - .on_exit_handlers = weather_station_scene_on_exit_handlers, - .scene_num = WeatherStationSceneNum, -}; diff --git a/applications/external/weather_station/scenes/weather_station_scene.h b/applications/external/weather_station/scenes/weather_station_scene.h deleted file mode 100644 index 8cee4ee6052..00000000000 --- a/applications/external/weather_station/scenes/weather_station_scene.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -#include - -// Generate scene id and total number -#define ADD_SCENE(prefix, name, id) WeatherStationScene##id, -typedef enum { -#include "weather_station_scene_config.h" - WeatherStationSceneNum, -} WeatherStationScene; -#undef ADD_SCENE - -extern const SceneManagerHandlers weather_station_scene_handlers; - -// Generate scene on_enter handlers declaration -#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); -#include "weather_station_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 "weather_station_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 "weather_station_scene_config.h" -#undef ADD_SCENE diff --git a/applications/external/weather_station/scenes/weather_station_scene_about.c b/applications/external/weather_station/scenes/weather_station_scene_about.c deleted file mode 100644 index d916dc76fe3..00000000000 --- a/applications/external/weather_station/scenes/weather_station_scene_about.c +++ /dev/null @@ -1,78 +0,0 @@ -#include "../weather_station_app_i.h" -#include "../helpers/weather_station_types.h" - -void weather_station_scene_about_widget_callback( - GuiButtonType result, - InputType type, - void* context) { - WeatherStationApp* app = context; - if(type == InputTypeShort) { - view_dispatcher_send_custom_event(app->view_dispatcher, result); - } -} - -void weather_station_scene_about_on_enter(void* context) { - WeatherStationApp* app = context; - - FuriString* temp_str; - temp_str = furi_string_alloc(); - furi_string_printf(temp_str, "\e#%s\n", "Information"); - - furi_string_cat_printf(temp_str, "Version: %s\n", WS_VERSION_APP); - furi_string_cat_printf(temp_str, "Developed by: %s\n", WS_DEVELOPED); - furi_string_cat_printf(temp_str, "Github: %s\n\n", WS_GITHUB); - - furi_string_cat_printf(temp_str, "\e#%s\n", "Description"); - furi_string_cat_printf( - temp_str, "Reading messages from\nweather stations that work\nwith SubGhz sensors\n\n"); - - furi_string_cat_printf(temp_str, "Supported protocols:\n"); - size_t i = 0; - const char* protocol_name = - subghz_environment_get_protocol_name_registry(app->txrx->environment, i++); - do { - furi_string_cat_printf(temp_str, "%s\n", protocol_name); - protocol_name = subghz_environment_get_protocol_name_registry(app->txrx->environment, i++); - } while(protocol_name != NULL); - - widget_add_text_box_element( - app->widget, - 0, - 0, - 128, - 14, - AlignCenter, - AlignBottom, - "\e#\e! \e!\n", - false); - widget_add_text_box_element( - app->widget, - 0, - 2, - 128, - 14, - AlignCenter, - AlignBottom, - "\e#\e! Weather station \e!\n", - false); - widget_add_text_scroll_element(app->widget, 0, 16, 128, 50, furi_string_get_cstr(temp_str)); - furi_string_free(temp_str); - - view_dispatcher_switch_to_view(app->view_dispatcher, WeatherStationViewWidget); -} - -bool weather_station_scene_about_on_event(void* context, SceneManagerEvent event) { - WeatherStationApp* app = context; - bool consumed = false; - UNUSED(app); - UNUSED(event); - - return consumed; -} - -void weather_station_scene_about_on_exit(void* context) { - WeatherStationApp* app = context; - - // Clear views - widget_reset(app->widget); -} diff --git a/applications/external/weather_station/scenes/weather_station_scene_config.h b/applications/external/weather_station/scenes/weather_station_scene_config.h deleted file mode 100644 index 0ba8ec013dc..00000000000 --- a/applications/external/weather_station/scenes/weather_station_scene_config.h +++ /dev/null @@ -1,5 +0,0 @@ -ADD_SCENE(weather_station, start, Start) -ADD_SCENE(weather_station, about, About) -ADD_SCENE(weather_station, receiver, Receiver) -ADD_SCENE(weather_station, receiver_config, ReceiverConfig) -ADD_SCENE(weather_station, receiver_info, ReceiverInfo) diff --git a/applications/external/weather_station/scenes/weather_station_scene_receiver_config.c b/applications/external/weather_station/scenes/weather_station_scene_receiver_config.c deleted file mode 100644 index fcd8f6d3e6b..00000000000 --- a/applications/external/weather_station/scenes/weather_station_scene_receiver_config.c +++ /dev/null @@ -1,223 +0,0 @@ -#include "../weather_station_app_i.h" - -enum WSSettingIndex { - WSSettingIndexFrequency, - WSSettingIndexHopping, - WSSettingIndexModulation, - WSSettingIndexLock, -}; - -#define HOPPING_COUNT 2 -const char* const hopping_text[HOPPING_COUNT] = { - "OFF", - "ON", -}; -const uint32_t hopping_value[HOPPING_COUNT] = { - WSHopperStateOFF, - WSHopperStateRunnig, -}; - -uint8_t weather_station_scene_receiver_config_next_frequency(const uint32_t value, void* context) { - furi_assert(context); - WeatherStationApp* app = context; - uint8_t index = 0; - for(uint8_t i = 0; i < subghz_setting_get_frequency_count(app->setting); i++) { - if(value == subghz_setting_get_frequency(app->setting, i)) { - index = i; - break; - } else { - index = subghz_setting_get_frequency_default_index(app->setting); - } - } - return index; -} - -uint8_t weather_station_scene_receiver_config_next_preset(const char* preset_name, void* context) { - furi_assert(context); - WeatherStationApp* app = context; - uint8_t index = 0; - for(uint8_t i = 0; i < subghz_setting_get_preset_count(app->setting); i++) { - if(!strcmp(subghz_setting_get_preset_name(app->setting, i), preset_name)) { - index = i; - break; - } else { - // index = subghz_setting_get_frequency_default_index(app ->setting); - } - } - return index; -} - -uint8_t weather_station_scene_receiver_config_hopper_value_index( - const uint32_t value, - const uint32_t values[], - uint8_t values_count, - void* context) { - furi_assert(context); - UNUSED(values_count); - WeatherStationApp* app = context; - - if(value == values[0]) { - return 0; - } else { - variable_item_set_current_value_text( - (VariableItem*)scene_manager_get_scene_state( - app->scene_manager, WeatherStationSceneReceiverConfig), - " -----"); - return 1; - } -} - -static void weather_station_scene_receiver_config_set_frequency(VariableItem* item) { - WeatherStationApp* app = variable_item_get_context(item); - uint8_t index = variable_item_get_current_value_index(item); - - if(app->txrx->hopper_state == WSHopperStateOFF) { - char text_buf[10] = {0}; - snprintf( - text_buf, - sizeof(text_buf), - "%lu.%02lu", - subghz_setting_get_frequency(app->setting, index) / 1000000, - (subghz_setting_get_frequency(app->setting, index) % 1000000) / 10000); - variable_item_set_current_value_text(item, text_buf); - app->txrx->preset->frequency = subghz_setting_get_frequency(app->setting, index); - } else { - variable_item_set_current_value_index( - item, subghz_setting_get_frequency_default_index(app->setting)); - } -} - -static void weather_station_scene_receiver_config_set_preset(VariableItem* item) { - WeatherStationApp* app = variable_item_get_context(item); - uint8_t index = variable_item_get_current_value_index(item); - variable_item_set_current_value_text( - item, subghz_setting_get_preset_name(app->setting, index)); - ws_preset_init( - app, - subghz_setting_get_preset_name(app->setting, index), - app->txrx->preset->frequency, - subghz_setting_get_preset_data(app->setting, index), - subghz_setting_get_preset_data_size(app->setting, index)); -} - -static void weather_station_scene_receiver_config_set_hopping_running(VariableItem* item) { - WeatherStationApp* app = variable_item_get_context(item); - uint8_t index = variable_item_get_current_value_index(item); - - variable_item_set_current_value_text(item, hopping_text[index]); - if(hopping_value[index] == WSHopperStateOFF) { - char text_buf[10] = {0}; - snprintf( - text_buf, - sizeof(text_buf), - "%lu.%02lu", - subghz_setting_get_default_frequency(app->setting) / 1000000, - (subghz_setting_get_default_frequency(app->setting) % 1000000) / 10000); - variable_item_set_current_value_text( - (VariableItem*)scene_manager_get_scene_state( - app->scene_manager, WeatherStationSceneReceiverConfig), - text_buf); - app->txrx->preset->frequency = subghz_setting_get_default_frequency(app->setting); - variable_item_set_current_value_index( - (VariableItem*)scene_manager_get_scene_state( - app->scene_manager, WeatherStationSceneReceiverConfig), - subghz_setting_get_frequency_default_index(app->setting)); - } else { - variable_item_set_current_value_text( - (VariableItem*)scene_manager_get_scene_state( - app->scene_manager, WeatherStationSceneReceiverConfig), - " -----"); - variable_item_set_current_value_index( - (VariableItem*)scene_manager_get_scene_state( - app->scene_manager, WeatherStationSceneReceiverConfig), - subghz_setting_get_frequency_default_index(app->setting)); - } - - app->txrx->hopper_state = hopping_value[index]; -} - -static void - weather_station_scene_receiver_config_var_list_enter_callback(void* context, uint32_t index) { - furi_assert(context); - WeatherStationApp* app = context; - if(index == WSSettingIndexLock) { - view_dispatcher_send_custom_event(app->view_dispatcher, WSCustomEventSceneSettingLock); - } -} - -void weather_station_scene_receiver_config_on_enter(void* context) { - WeatherStationApp* app = context; - VariableItem* item; - uint8_t value_index; - - item = variable_item_list_add( - app->variable_item_list, - "Frequency:", - subghz_setting_get_frequency_count(app->setting), - weather_station_scene_receiver_config_set_frequency, - app); - value_index = - weather_station_scene_receiver_config_next_frequency(app->txrx->preset->frequency, app); - scene_manager_set_scene_state( - app->scene_manager, WeatherStationSceneReceiverConfig, (uint32_t)item); - variable_item_set_current_value_index(item, value_index); - char text_buf[10] = {0}; - snprintf( - text_buf, - sizeof(text_buf), - "%lu.%02lu", - subghz_setting_get_frequency(app->setting, value_index) / 1000000, - (subghz_setting_get_frequency(app->setting, value_index) % 1000000) / 10000); - variable_item_set_current_value_text(item, text_buf); - - item = variable_item_list_add( - app->variable_item_list, - "Hopping:", - HOPPING_COUNT, - weather_station_scene_receiver_config_set_hopping_running, - app); - value_index = weather_station_scene_receiver_config_hopper_value_index( - app->txrx->hopper_state, hopping_value, HOPPING_COUNT, app); - variable_item_set_current_value_index(item, value_index); - variable_item_set_current_value_text(item, hopping_text[value_index]); - - item = variable_item_list_add( - app->variable_item_list, - "Modulation:", - subghz_setting_get_preset_count(app->setting), - weather_station_scene_receiver_config_set_preset, - app); - value_index = weather_station_scene_receiver_config_next_preset( - furi_string_get_cstr(app->txrx->preset->name), app); - variable_item_set_current_value_index(item, value_index); - variable_item_set_current_value_text( - item, subghz_setting_get_preset_name(app->setting, value_index)); - - variable_item_list_add(app->variable_item_list, "Lock Keyboard", 1, NULL, NULL); - variable_item_list_set_enter_callback( - app->variable_item_list, - weather_station_scene_receiver_config_var_list_enter_callback, - app); - - view_dispatcher_switch_to_view(app->view_dispatcher, WeatherStationViewVariableItemList); -} - -bool weather_station_scene_receiver_config_on_event(void* context, SceneManagerEvent event) { - WeatherStationApp* app = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == WSCustomEventSceneSettingLock) { - app->lock = WSLockOn; - scene_manager_previous_scene(app->scene_manager); - consumed = true; - } - } - return consumed; -} - -void weather_station_scene_receiver_config_on_exit(void* context) { - WeatherStationApp* app = context; - variable_item_list_set_selected_item(app->variable_item_list, 0); - variable_item_list_reset(app->variable_item_list); -} diff --git a/applications/external/weather_station/scenes/weather_station_scene_receiver_info.c b/applications/external/weather_station/scenes/weather_station_scene_receiver_info.c deleted file mode 100644 index b26661be385..00000000000 --- a/applications/external/weather_station/scenes/weather_station_scene_receiver_info.c +++ /dev/null @@ -1,50 +0,0 @@ -#include "../weather_station_app_i.h" -#include "../views/weather_station_receiver.h" - -void weather_station_scene_receiver_info_callback(WSCustomEvent event, void* context) { - furi_assert(context); - WeatherStationApp* app = context; - view_dispatcher_send_custom_event(app->view_dispatcher, event); -} - -static void weather_station_scene_receiver_info_add_to_history_callback( - SubGhzReceiver* receiver, - SubGhzProtocolDecoderBase* decoder_base, - void* context) { - furi_assert(context); - WeatherStationApp* app = context; - - if(ws_history_add_to_history(app->txrx->history, decoder_base, app->txrx->preset) == - WSHistoryStateAddKeyUpdateData) { - ws_view_receiver_info_update( - app->ws_receiver_info, - ws_history_get_raw_data(app->txrx->history, app->txrx->idx_menu_chosen)); - subghz_receiver_reset(receiver); - - notification_message(app->notifications, &sequence_blink_green_10); - app->txrx->rx_key_state = WSRxKeyStateAddKey; - } -} - -void weather_station_scene_receiver_info_on_enter(void* context) { - WeatherStationApp* app = context; - - subghz_receiver_set_rx_callback( - app->txrx->receiver, weather_station_scene_receiver_info_add_to_history_callback, app); - ws_view_receiver_info_update( - app->ws_receiver_info, - ws_history_get_raw_data(app->txrx->history, app->txrx->idx_menu_chosen)); - view_dispatcher_switch_to_view(app->view_dispatcher, WeatherStationViewReceiverInfo); -} - -bool weather_station_scene_receiver_info_on_event(void* context, SceneManagerEvent event) { - WeatherStationApp* app = context; - bool consumed = false; - UNUSED(app); - UNUSED(event); - return consumed; -} - -void weather_station_scene_receiver_info_on_exit(void* context) { - UNUSED(context); -} diff --git a/applications/external/weather_station/scenes/weather_station_scene_start.c b/applications/external/weather_station/scenes/weather_station_scene_start.c deleted file mode 100644 index 56dd6fa86dc..00000000000 --- a/applications/external/weather_station/scenes/weather_station_scene_start.c +++ /dev/null @@ -1,58 +0,0 @@ -#include "../weather_station_app_i.h" - -typedef enum { - SubmenuIndexWeatherStationReceiver, - SubmenuIndexWeatherStationAbout, -} SubmenuIndex; - -void weather_station_scene_start_submenu_callback(void* context, uint32_t index) { - WeatherStationApp* app = context; - view_dispatcher_send_custom_event(app->view_dispatcher, index); -} - -void weather_station_scene_start_on_enter(void* context) { - UNUSED(context); - WeatherStationApp* app = context; - Submenu* submenu = app->submenu; - - submenu_add_item( - submenu, - "Read Weather Station", - SubmenuIndexWeatherStationReceiver, - weather_station_scene_start_submenu_callback, - app); - submenu_add_item( - submenu, - "About", - SubmenuIndexWeatherStationAbout, - weather_station_scene_start_submenu_callback, - app); - - submenu_set_selected_item( - submenu, scene_manager_get_scene_state(app->scene_manager, WeatherStationSceneStart)); - - view_dispatcher_switch_to_view(app->view_dispatcher, WeatherStationViewSubmenu); -} - -bool weather_station_scene_start_on_event(void* context, SceneManagerEvent event) { - WeatherStationApp* app = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SubmenuIndexWeatherStationAbout) { - scene_manager_next_scene(app->scene_manager, WeatherStationSceneAbout); - consumed = true; - } else if(event.event == SubmenuIndexWeatherStationReceiver) { - scene_manager_next_scene(app->scene_manager, WeatherStationSceneReceiver); - consumed = true; - } - scene_manager_set_scene_state(app->scene_manager, WeatherStationSceneStart, event.event); - } - - return consumed; -} - -void weather_station_scene_start_on_exit(void* context) { - WeatherStationApp* app = context; - submenu_reset(app->submenu); -} diff --git a/applications/external/weather_station/views/weather_station_receiver.c b/applications/external/weather_station/views/weather_station_receiver.c deleted file mode 100644 index c4ce1627ec2..00000000000 --- a/applications/external/weather_station/views/weather_station_receiver.c +++ /dev/null @@ -1,464 +0,0 @@ -#include "weather_station_receiver.h" -#include "../weather_station_app_i.h" -#include -#include - -#include -#include -#include - -#define FRAME_HEIGHT 12 -#define MAX_LEN_PX 112 -#define MENU_ITEMS 4u -#define UNLOCK_CNT 3 - -#define SUBGHZ_RAW_THRESHOLD_MIN -90.0f -typedef struct { - FuriString* item_str; - uint8_t type; -} WSReceiverMenuItem; - -ARRAY_DEF(WSReceiverMenuItemArray, WSReceiverMenuItem, M_POD_OPLIST) - -#define M_OPL_WSReceiverMenuItemArray_t() ARRAY_OPLIST(WSReceiverMenuItemArray, M_POD_OPLIST) - -struct WSReceiverHistory { - WSReceiverMenuItemArray_t data; -}; - -typedef struct WSReceiverHistory WSReceiverHistory; - -static const Icon* ReceiverItemIcons[] = { - [SubGhzProtocolTypeUnknown] = &I_Quest_7x8, - [SubGhzProtocolTypeStatic] = &I_Unlock_7x8, - [SubGhzProtocolTypeDynamic] = &I_Lock_7x8, - [SubGhzProtocolWeatherStation] = &I_station_icon, -}; - -typedef enum { - WSReceiverBarShowDefault, - WSReceiverBarShowLock, - WSReceiverBarShowToUnlockPress, - WSReceiverBarShowUnlock, -} WSReceiverBarShow; - -struct WSReceiver { - WSLock lock; - uint8_t lock_count; - FuriTimer* timer; - View* view; - WSReceiverCallback callback; - void* context; -}; - -typedef struct { - FuriString* frequency_str; - FuriString* preset_str; - FuriString* history_stat_str; - WSReceiverHistory* history; - uint16_t idx; - uint16_t list_offset; - uint16_t history_item; - WSReceiverBarShow bar_show; - uint8_t u_rssi; -} WSReceiverModel; - -void ws_view_receiver_set_rssi(WSReceiver* instance, float rssi) { - furi_assert(instance); - with_view_model( - instance->view, - WSReceiverModel * model, - { - if(rssi < SUBGHZ_RAW_THRESHOLD_MIN) { - model->u_rssi = 0; - } else { - model->u_rssi = (uint8_t)(rssi - SUBGHZ_RAW_THRESHOLD_MIN); - } - }, - true); -} - -void ws_view_receiver_set_lock(WSReceiver* ws_receiver, WSLock lock) { - furi_assert(ws_receiver); - ws_receiver->lock_count = 0; - if(lock == WSLockOn) { - ws_receiver->lock = lock; - with_view_model( - ws_receiver->view, - WSReceiverModel * model, - { model->bar_show = WSReceiverBarShowLock; }, - true); - furi_timer_start(ws_receiver->timer, pdMS_TO_TICKS(1000)); - } else { - with_view_model( - ws_receiver->view, - WSReceiverModel * model, - { model->bar_show = WSReceiverBarShowDefault; }, - true); - } -} - -void ws_view_receiver_set_callback( - WSReceiver* ws_receiver, - WSReceiverCallback callback, - void* context) { - furi_assert(ws_receiver); - furi_assert(callback); - ws_receiver->callback = callback; - ws_receiver->context = context; -} - -static void ws_view_receiver_update_offset(WSReceiver* ws_receiver) { - furi_assert(ws_receiver); - - with_view_model( - ws_receiver->view, - WSReceiverModel * model, - { - size_t history_item = model->history_item; - uint16_t bounds = history_item > 3 ? 2 : history_item; - - if(history_item > 3 && model->idx >= (int16_t)(history_item - 1)) { - model->list_offset = model->idx - 3; - } else if(model->list_offset < model->idx - bounds) { - model->list_offset = - CLAMP(model->list_offset + 1, (int16_t)(history_item - bounds), 0); - } else if(model->list_offset > model->idx - bounds) { - model->list_offset = CLAMP(model->idx - 1, (int16_t)(history_item - bounds), 0); - } - }, - true); -} - -void ws_view_receiver_add_item_to_menu(WSReceiver* ws_receiver, const char* name, uint8_t type) { - furi_assert(ws_receiver); - with_view_model( - ws_receiver->view, - WSReceiverModel * model, - { - WSReceiverMenuItem* item_menu = WSReceiverMenuItemArray_push_raw(model->history->data); - item_menu->item_str = furi_string_alloc_set(name); - item_menu->type = type; - if((model->idx == model->history_item - 1)) { - model->history_item++; - model->idx++; - } else { - model->history_item++; - } - }, - true); - ws_view_receiver_update_offset(ws_receiver); -} - -void ws_view_receiver_add_data_statusbar( - WSReceiver* ws_receiver, - const char* frequency_str, - const char* preset_str, - const char* history_stat_str) { - furi_assert(ws_receiver); - with_view_model( - ws_receiver->view, - WSReceiverModel * model, - { - furi_string_set_str(model->frequency_str, frequency_str); - furi_string_set_str(model->preset_str, preset_str); - furi_string_set_str(model->history_stat_str, history_stat_str); - }, - true); -} - -static void ws_view_receiver_draw_frame(Canvas* canvas, uint16_t idx, bool scrollbar) { - canvas_set_color(canvas, ColorBlack); - canvas_draw_box(canvas, 0, 0 + idx * FRAME_HEIGHT, scrollbar ? 122 : 127, FRAME_HEIGHT); - - canvas_set_color(canvas, ColorWhite); - canvas_draw_dot(canvas, 0, 0 + idx * FRAME_HEIGHT); - canvas_draw_dot(canvas, 1, 0 + idx * FRAME_HEIGHT); - canvas_draw_dot(canvas, 0, (0 + idx * FRAME_HEIGHT) + 1); - - canvas_draw_dot(canvas, 0, (0 + idx * FRAME_HEIGHT) + 11); - canvas_draw_dot(canvas, scrollbar ? 121 : 126, 0 + idx * FRAME_HEIGHT); - canvas_draw_dot(canvas, scrollbar ? 121 : 126, (0 + idx * FRAME_HEIGHT) + 11); -} - -static void ws_view_rssi_draw(Canvas* canvas, WSReceiverModel* model) { - for(uint8_t i = 1; i < model->u_rssi; i++) { - if(i % 5) { - canvas_draw_dot(canvas, 46 + i, 50); - canvas_draw_dot(canvas, 47 + i, 51); - canvas_draw_dot(canvas, 46 + i, 52); - } - } -} - -void ws_view_receiver_draw(Canvas* canvas, WSReceiverModel* model) { - canvas_clear(canvas); - canvas_set_color(canvas, ColorBlack); - canvas_set_font(canvas, FontSecondary); - - elements_button_left(canvas, "Config"); - - bool scrollbar = model->history_item > 4; - FuriString* str_buff; - str_buff = furi_string_alloc(); - - WSReceiverMenuItem* item_menu; - - for(size_t i = 0; i < MIN(model->history_item, MENU_ITEMS); ++i) { - size_t idx = CLAMP((uint16_t)(i + model->list_offset), model->history_item, 0); - item_menu = WSReceiverMenuItemArray_get(model->history->data, idx); - furi_string_set(str_buff, item_menu->item_str); - elements_string_fit_width(canvas, str_buff, scrollbar ? MAX_LEN_PX - 6 : MAX_LEN_PX); - if(model->idx == idx) { - ws_view_receiver_draw_frame(canvas, i, scrollbar); - } else { - canvas_set_color(canvas, ColorBlack); - } - canvas_draw_icon(canvas, 4, 2 + i * FRAME_HEIGHT, ReceiverItemIcons[item_menu->type]); - canvas_draw_str(canvas, 14, 9 + i * FRAME_HEIGHT, furi_string_get_cstr(str_buff)); - furi_string_reset(str_buff); - } - if(scrollbar) { - elements_scrollbar_pos(canvas, 128, 0, 49, model->idx, model->history_item); - } - furi_string_free(str_buff); - - canvas_set_color(canvas, ColorBlack); - - if(model->history_item == 0) { - canvas_draw_icon(canvas, 0, 0, &I_Scanning_123x52); - canvas_set_font(canvas, FontPrimary); - canvas_draw_str(canvas, 63, 46, "Scanning..."); - canvas_set_font(canvas, FontSecondary); - } - - // Draw RSSI - ws_view_rssi_draw(canvas, model); - - switch(model->bar_show) { - case WSReceiverBarShowLock: - canvas_draw_icon(canvas, 64, 55, &I_Lock_7x8); - canvas_draw_str(canvas, 74, 62, "Locked"); - break; - case WSReceiverBarShowToUnlockPress: - canvas_draw_str(canvas, 44, 62, furi_string_get_cstr(model->frequency_str)); - canvas_draw_str(canvas, 79, 62, furi_string_get_cstr(model->preset_str)); - canvas_draw_str(canvas, 96, 62, furi_string_get_cstr(model->history_stat_str)); - canvas_set_font(canvas, FontSecondary); - elements_bold_rounded_frame(canvas, 14, 8, 99, 48); - elements_multiline_text(canvas, 65, 26, "To unlock\npress:"); - canvas_draw_icon(canvas, 65, 42, &I_Pin_back_arrow_10x8); - canvas_draw_icon(canvas, 80, 42, &I_Pin_back_arrow_10x8); - canvas_draw_icon(canvas, 95, 42, &I_Pin_back_arrow_10x8); - canvas_draw_icon(canvas, 16, 13, &I_WarningDolphin_45x42); - canvas_draw_dot(canvas, 17, 61); - break; - case WSReceiverBarShowUnlock: - canvas_draw_icon(canvas, 64, 55, &I_Unlock_7x8); - canvas_draw_str(canvas, 74, 62, "Unlocked"); - break; - default: - canvas_draw_str(canvas, 44, 62, furi_string_get_cstr(model->frequency_str)); - canvas_draw_str(canvas, 79, 62, furi_string_get_cstr(model->preset_str)); - canvas_draw_str(canvas, 96, 62, furi_string_get_cstr(model->history_stat_str)); - break; - } -} - -static void ws_view_receiver_timer_callback(void* context) { - furi_assert(context); - WSReceiver* ws_receiver = context; - with_view_model( - ws_receiver->view, - WSReceiverModel * model, - { model->bar_show = WSReceiverBarShowDefault; }, - true); - if(ws_receiver->lock_count < UNLOCK_CNT) { - ws_receiver->callback(WSCustomEventViewReceiverOffDisplay, ws_receiver->context); - } else { - ws_receiver->lock = WSLockOff; - ws_receiver->callback(WSCustomEventViewReceiverUnlock, ws_receiver->context); - } - ws_receiver->lock_count = 0; -} - -bool ws_view_receiver_input(InputEvent* event, void* context) { - furi_assert(context); - WSReceiver* ws_receiver = context; - - if(ws_receiver->lock == WSLockOn) { - with_view_model( - ws_receiver->view, - WSReceiverModel * model, - { model->bar_show = WSReceiverBarShowToUnlockPress; }, - true); - if(ws_receiver->lock_count == 0) { - furi_timer_start(ws_receiver->timer, pdMS_TO_TICKS(1000)); - } - if(event->key == InputKeyBack && event->type == InputTypeShort) { - ws_receiver->lock_count++; - } - if(ws_receiver->lock_count >= UNLOCK_CNT) { - ws_receiver->callback(WSCustomEventViewReceiverUnlock, ws_receiver->context); - with_view_model( - ws_receiver->view, - WSReceiverModel * model, - { model->bar_show = WSReceiverBarShowUnlock; }, - true); - ws_receiver->lock = WSLockOff; - furi_timer_start(ws_receiver->timer, pdMS_TO_TICKS(650)); - } - - return true; - } - - if(event->key == InputKeyBack && event->type == InputTypeShort) { - ws_receiver->callback(WSCustomEventViewReceiverBack, ws_receiver->context); - } else if( - event->key == InputKeyUp && - (event->type == InputTypeShort || event->type == InputTypeRepeat)) { - with_view_model( - ws_receiver->view, - WSReceiverModel * model, - { - if(model->idx != 0) model->idx--; - }, - true); - } else if( - event->key == InputKeyDown && - (event->type == InputTypeShort || event->type == InputTypeRepeat)) { - with_view_model( - ws_receiver->view, - WSReceiverModel * model, - { - if(model->history_item && model->idx != model->history_item - 1) model->idx++; - }, - true); - } else if(event->key == InputKeyLeft && event->type == InputTypeShort) { - ws_receiver->callback(WSCustomEventViewReceiverConfig, ws_receiver->context); - } else if(event->key == InputKeyOk && event->type == InputTypeShort) { - with_view_model( - ws_receiver->view, - WSReceiverModel * model, - { - if(model->history_item != 0) { - ws_receiver->callback(WSCustomEventViewReceiverOK, ws_receiver->context); - } - }, - false); - } - - ws_view_receiver_update_offset(ws_receiver); - - return true; -} - -void ws_view_receiver_enter(void* context) { - furi_assert(context); -} - -void ws_view_receiver_exit(void* context) { - furi_assert(context); - WSReceiver* ws_receiver = context; - with_view_model( - ws_receiver->view, - WSReceiverModel * model, - { - furi_string_reset(model->frequency_str); - furi_string_reset(model->preset_str); - furi_string_reset(model->history_stat_str); - for - M_EACH(item_menu, model->history->data, WSReceiverMenuItemArray_t) { - furi_string_free(item_menu->item_str); - item_menu->type = 0; - } - WSReceiverMenuItemArray_reset(model->history->data); - model->idx = 0; - model->list_offset = 0; - model->history_item = 0; - }, - false); - furi_timer_stop(ws_receiver->timer); -} - -WSReceiver* ws_view_receiver_alloc() { - WSReceiver* ws_receiver = malloc(sizeof(WSReceiver)); - - // View allocation and configuration - ws_receiver->view = view_alloc(); - - ws_receiver->lock = WSLockOff; - ws_receiver->lock_count = 0; - view_allocate_model(ws_receiver->view, ViewModelTypeLocking, sizeof(WSReceiverModel)); - view_set_context(ws_receiver->view, ws_receiver); - view_set_draw_callback(ws_receiver->view, (ViewDrawCallback)ws_view_receiver_draw); - view_set_input_callback(ws_receiver->view, ws_view_receiver_input); - view_set_enter_callback(ws_receiver->view, ws_view_receiver_enter); - view_set_exit_callback(ws_receiver->view, ws_view_receiver_exit); - - with_view_model( - ws_receiver->view, - WSReceiverModel * model, - { - model->frequency_str = furi_string_alloc(); - model->preset_str = furi_string_alloc(); - model->history_stat_str = furi_string_alloc(); - model->bar_show = WSReceiverBarShowDefault; - model->history = malloc(sizeof(WSReceiverHistory)); - WSReceiverMenuItemArray_init(model->history->data); - }, - true); - ws_receiver->timer = - furi_timer_alloc(ws_view_receiver_timer_callback, FuriTimerTypeOnce, ws_receiver); - return ws_receiver; -} - -void ws_view_receiver_free(WSReceiver* ws_receiver) { - furi_assert(ws_receiver); - - with_view_model( - ws_receiver->view, - WSReceiverModel * model, - { - furi_string_free(model->frequency_str); - furi_string_free(model->preset_str); - furi_string_free(model->history_stat_str); - for - M_EACH(item_menu, model->history->data, WSReceiverMenuItemArray_t) { - furi_string_free(item_menu->item_str); - item_menu->type = 0; - } - WSReceiverMenuItemArray_clear(model->history->data); - free(model->history); - }, - false); - furi_timer_free(ws_receiver->timer); - view_free(ws_receiver->view); - free(ws_receiver); -} - -View* ws_view_receiver_get_view(WSReceiver* ws_receiver) { - furi_assert(ws_receiver); - return ws_receiver->view; -} - -uint16_t ws_view_receiver_get_idx_menu(WSReceiver* ws_receiver) { - furi_assert(ws_receiver); - uint32_t idx = 0; - with_view_model( - ws_receiver->view, WSReceiverModel * model, { idx = model->idx; }, false); - return idx; -} - -void ws_view_receiver_set_idx_menu(WSReceiver* ws_receiver, uint16_t idx) { - furi_assert(ws_receiver); - with_view_model( - ws_receiver->view, - WSReceiverModel * model, - { - model->idx = idx; - if(model->idx > 2) model->list_offset = idx - 2; - }, - true); - ws_view_receiver_update_offset(ws_receiver); -} diff --git a/applications/external/weather_station/views/weather_station_receiver.h b/applications/external/weather_station/views/weather_station_receiver.h deleted file mode 100644 index f81aa1f5ec6..00000000000 --- a/applications/external/weather_station/views/weather_station_receiver.h +++ /dev/null @@ -1,38 +0,0 @@ -#pragma once - -#include -#include "../helpers/weather_station_types.h" -#include "../helpers/weather_station_event.h" - -typedef struct WSReceiver WSReceiver; - -typedef void (*WSReceiverCallback)(WSCustomEvent event, void* context); - -void ws_view_receiver_set_rssi(WSReceiver* instance, float rssi); - -void ws_view_receiver_set_lock(WSReceiver* ws_receiver, WSLock keyboard); - -void ws_view_receiver_set_callback( - WSReceiver* ws_receiver, - WSReceiverCallback callback, - void* context); - -WSReceiver* ws_view_receiver_alloc(); - -void ws_view_receiver_free(WSReceiver* ws_receiver); - -View* ws_view_receiver_get_view(WSReceiver* ws_receiver); - -void ws_view_receiver_add_data_statusbar( - WSReceiver* ws_receiver, - const char* frequency_str, - const char* preset_str, - const char* history_stat_str); - -void ws_view_receiver_add_item_to_menu(WSReceiver* ws_receiver, const char* name, uint8_t type); - -uint16_t ws_view_receiver_get_idx_menu(WSReceiver* ws_receiver); - -void ws_view_receiver_set_idx_menu(WSReceiver* ws_receiver, uint16_t idx); - -void ws_view_receiver_exit(void* context); diff --git a/applications/external/weather_station/views/weather_station_receiver_info.c b/applications/external/weather_station/views/weather_station_receiver_info.c deleted file mode 100644 index b3b3f219392..00000000000 --- a/applications/external/weather_station/views/weather_station_receiver_info.c +++ /dev/null @@ -1,245 +0,0 @@ -#include "weather_station_receiver.h" -#include "../weather_station_app_i.h" -#include "weather_station_icons.h" -#include "../protocols/ws_generic.h" -#include -#include -#include - -struct WSReceiverInfo { - View* view; - FuriTimer* timer; -}; - -typedef struct { - uint32_t curr_ts; - FuriString* protocol_name; - WSBlockGeneric* generic; -} WSReceiverInfoModel; - -void ws_view_receiver_info_update(WSReceiverInfo* ws_receiver_info, FlipperFormat* fff) { - furi_assert(ws_receiver_info); - furi_assert(fff); - - with_view_model( - ws_receiver_info->view, - WSReceiverInfoModel * model, - { - flipper_format_rewind(fff); - flipper_format_read_string(fff, "Protocol", model->protocol_name); - - ws_block_generic_deserialize(model->generic, fff); - - FuriHalRtcDateTime curr_dt; - furi_hal_rtc_get_datetime(&curr_dt); - model->curr_ts = furi_hal_rtc_datetime_to_timestamp(&curr_dt); - }, - true); -} - -void ws_view_receiver_info_draw(Canvas* canvas, WSReceiverInfoModel* model) { - char buffer[64]; - canvas_clear(canvas); - canvas_set_color(canvas, ColorBlack); - canvas_set_font(canvas, FontSecondary); - - snprintf( - buffer, - sizeof(buffer), - "%s %db", - furi_string_get_cstr(model->protocol_name), - model->generic->data_count_bit); - canvas_draw_str(canvas, 0, 8, buffer); - - if(model->generic->channel != WS_NO_CHANNEL) { - snprintf(buffer, sizeof(buffer), "Ch: %01d", model->generic->channel); - canvas_draw_str(canvas, 106, 8, buffer); - } - - if(model->generic->id != WS_NO_ID) { - snprintf(buffer, sizeof(buffer), "Sn: 0x%02lX", model->generic->id); - canvas_draw_str(canvas, 0, 20, buffer); - } - - if(model->generic->btn != WS_NO_BTN) { - snprintf(buffer, sizeof(buffer), "Btn: %01d", model->generic->btn); - canvas_draw_str(canvas, 57, 20, buffer); - } - - if(model->generic->battery_low != WS_NO_BATT) { - snprintf( - buffer, sizeof(buffer), "Batt: %s", (!model->generic->battery_low ? "ok" : "low")); - canvas_draw_str_aligned(canvas, 126, 17, AlignRight, AlignCenter, buffer); - } - - snprintf(buffer, sizeof(buffer), "Data: 0x%llX", model->generic->data); - canvas_draw_str(canvas, 0, 32, buffer); - - elements_bold_rounded_frame(canvas, 0, 38, 127, 25); - canvas_set_font(canvas, FontPrimary); - - if(!float_is_equal(model->generic->temp, WS_NO_TEMPERATURE)) { - canvas_draw_icon(canvas, 6, 43, &I_Therm_7x16); - - uint8_t temp_x1 = 0; - uint8_t temp_x2 = 0; - if(furi_hal_rtc_get_locale_units() == FuriHalRtcLocaleUnitsMetric) { - snprintf(buffer, sizeof(buffer), "%3.1f C", (double)model->generic->temp); - if(model->generic->temp < -9.0f) { - temp_x1 = 49; - temp_x2 = 40; - } else { - temp_x1 = 47; - temp_x2 = 38; - } - } else { - snprintf( - buffer, - sizeof(buffer), - "%3.1f F", - (double)locale_celsius_to_fahrenheit(model->generic->temp)); - if((model->generic->temp < -27.77f) || (model->generic->temp > 37.77f)) { - temp_x1 = 50; - temp_x2 = 42; - } else { - temp_x1 = 48; - temp_x2 = 40; - } - } - - canvas_draw_str_aligned(canvas, temp_x1, 47, AlignRight, AlignTop, buffer); - canvas_draw_circle(canvas, temp_x2, 46, 1); - } - - if(model->generic->humidity != WS_NO_HUMIDITY) { - canvas_draw_icon(canvas, 53, 44, &I_Humid_8x13); - snprintf(buffer, sizeof(buffer), "%d%%", model->generic->humidity); - canvas_draw_str(canvas, 64, 55, buffer); - } - - if((int)model->generic->timestamp > 0 && model->curr_ts) { - int ts_diff = (int)model->curr_ts - (int)model->generic->timestamp; - - canvas_draw_icon(canvas, 91, 46, &I_Timer_11x11); - - if(ts_diff > 60) { - int tmp_sec = ts_diff; - int cnt_min = 1; - for(int i = 1; tmp_sec > 60; i++) { - tmp_sec = tmp_sec - 60; - cnt_min = i; - } - - if(model->curr_ts % 2 == 0) { - canvas_draw_str_aligned(canvas, 105, 51, AlignLeft, AlignCenter, "Old"); - } else { - if(cnt_min >= 59) { - canvas_draw_str_aligned(canvas, 105, 51, AlignLeft, AlignCenter, "Old"); - } else { - snprintf(buffer, sizeof(buffer), "%dm", cnt_min); - canvas_draw_str_aligned(canvas, 114, 51, AlignCenter, AlignCenter, buffer); - } - } - - } else { - snprintf(buffer, sizeof(buffer), "%d", ts_diff); - canvas_draw_str_aligned(canvas, 112, 51, AlignCenter, AlignCenter, buffer); - } - } -} - -bool ws_view_receiver_info_input(InputEvent* event, void* context) { - furi_assert(context); - //WSReceiverInfo* ws_receiver_info = context; - - if(event->key == InputKeyBack) { - return false; - } - - return true; -} - -static void ws_view_receiver_info_enter(void* context) { - furi_assert(context); - WSReceiverInfo* ws_receiver_info = context; - - furi_timer_start(ws_receiver_info->timer, 1000); -} - -static void ws_view_receiver_info_exit(void* context) { - furi_assert(context); - WSReceiverInfo* ws_receiver_info = context; - - furi_timer_stop(ws_receiver_info->timer); - - with_view_model( - ws_receiver_info->view, - WSReceiverInfoModel * model, - { furi_string_reset(model->protocol_name); }, - false); -} - -static void ws_view_receiver_info_timer(void* context) { - WSReceiverInfo* ws_receiver_info = context; - // Force redraw - with_view_model( - ws_receiver_info->view, - WSReceiverInfoModel * model, - { - FuriHalRtcDateTime curr_dt; - furi_hal_rtc_get_datetime(&curr_dt); - model->curr_ts = furi_hal_rtc_datetime_to_timestamp(&curr_dt); - }, - true); -} - -WSReceiverInfo* ws_view_receiver_info_alloc() { - WSReceiverInfo* ws_receiver_info = malloc(sizeof(WSReceiverInfo)); - - // View allocation and configuration - ws_receiver_info->view = view_alloc(); - - view_allocate_model(ws_receiver_info->view, ViewModelTypeLocking, sizeof(WSReceiverInfoModel)); - view_set_context(ws_receiver_info->view, ws_receiver_info); - view_set_draw_callback(ws_receiver_info->view, (ViewDrawCallback)ws_view_receiver_info_draw); - view_set_input_callback(ws_receiver_info->view, ws_view_receiver_info_input); - view_set_enter_callback(ws_receiver_info->view, ws_view_receiver_info_enter); - view_set_exit_callback(ws_receiver_info->view, ws_view_receiver_info_exit); - - with_view_model( - ws_receiver_info->view, - WSReceiverInfoModel * model, - { - model->generic = malloc(sizeof(WSBlockGeneric)); - model->protocol_name = furi_string_alloc(); - }, - true); - - ws_receiver_info->timer = - furi_timer_alloc(ws_view_receiver_info_timer, FuriTimerTypePeriodic, ws_receiver_info); - - return ws_receiver_info; -} - -void ws_view_receiver_info_free(WSReceiverInfo* ws_receiver_info) { - furi_assert(ws_receiver_info); - - furi_timer_free(ws_receiver_info->timer); - - with_view_model( - ws_receiver_info->view, - WSReceiverInfoModel * model, - { - furi_string_free(model->protocol_name); - free(model->generic); - }, - false); - - view_free(ws_receiver_info->view); - free(ws_receiver_info); -} - -View* ws_view_receiver_info_get_view(WSReceiverInfo* ws_receiver_info) { - furi_assert(ws_receiver_info); - return ws_receiver_info->view; -} diff --git a/applications/external/weather_station/views/weather_station_receiver_info.h b/applications/external/weather_station/views/weather_station_receiver_info.h deleted file mode 100644 index 705434a2383..00000000000 --- a/applications/external/weather_station/views/weather_station_receiver_info.h +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once - -#include -#include "../helpers/weather_station_types.h" -#include "../helpers/weather_station_event.h" -#include - -typedef struct WSReceiverInfo WSReceiverInfo; - -void ws_view_receiver_info_update(WSReceiverInfo* ws_receiver_info, FlipperFormat* fff); - -WSReceiverInfo* ws_view_receiver_info_alloc(); - -void ws_view_receiver_info_free(WSReceiverInfo* ws_receiver_info); - -View* ws_view_receiver_info_get_view(WSReceiverInfo* ws_receiver_info); diff --git a/applications/external/weather_station/weather_station_10px.png b/applications/external/weather_station/weather_station_10px.png deleted file mode 100644 index 7d5cc318c369f583c531ca5e8846a9a498ec44b6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 175 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V6Od#Ihk44ofy`glX(f`u%tWsIx;Y9 z?C1WI$O`0h7I;J!GcfQS24TkI`72U@f-asejv*Ssy?udP3<@01TYulb=@zZ(cJX(> z9E<0IiRH6?9buO;SoeAj)2m&2)(%T&iEpuE4*KbLE`4W{EU(#4ulkv@OZPmG6Pd23 R{2OQ -#include -#include "protocols/protocol_items.h" - -static bool weather_station_app_custom_event_callback(void* context, uint32_t event) { - furi_assert(context); - WeatherStationApp* app = context; - return scene_manager_handle_custom_event(app->scene_manager, event); -} - -static bool weather_station_app_back_event_callback(void* context) { - furi_assert(context); - WeatherStationApp* app = context; - return scene_manager_handle_back_event(app->scene_manager); -} - -static void weather_station_app_tick_event_callback(void* context) { - furi_assert(context); - WeatherStationApp* app = context; - scene_manager_handle_tick_event(app->scene_manager); -} - -WeatherStationApp* weather_station_app_alloc() { - WeatherStationApp* app = malloc(sizeof(WeatherStationApp)); - - // GUI - app->gui = furi_record_open(RECORD_GUI); - - // View Dispatcher - app->view_dispatcher = view_dispatcher_alloc(); - app->scene_manager = scene_manager_alloc(&weather_station_scene_handlers, app); - view_dispatcher_enable_queue(app->view_dispatcher); - - view_dispatcher_set_event_callback_context(app->view_dispatcher, app); - view_dispatcher_set_custom_event_callback( - app->view_dispatcher, weather_station_app_custom_event_callback); - view_dispatcher_set_navigation_event_callback( - app->view_dispatcher, weather_station_app_back_event_callback); - view_dispatcher_set_tick_event_callback( - app->view_dispatcher, weather_station_app_tick_event_callback, 100); - - view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); - - // Open Notification record - app->notifications = furi_record_open(RECORD_NOTIFICATION); - - // Variable Item List - app->variable_item_list = variable_item_list_alloc(); - view_dispatcher_add_view( - app->view_dispatcher, - WeatherStationViewVariableItemList, - variable_item_list_get_view(app->variable_item_list)); - - // SubMenu - app->submenu = submenu_alloc(); - view_dispatcher_add_view( - app->view_dispatcher, WeatherStationViewSubmenu, submenu_get_view(app->submenu)); - - // Widget - app->widget = widget_alloc(); - view_dispatcher_add_view( - app->view_dispatcher, WeatherStationViewWidget, widget_get_view(app->widget)); - - // Receiver - app->ws_receiver = ws_view_receiver_alloc(); - view_dispatcher_add_view( - app->view_dispatcher, - WeatherStationViewReceiver, - ws_view_receiver_get_view(app->ws_receiver)); - - // Receiver Info - app->ws_receiver_info = ws_view_receiver_info_alloc(); - view_dispatcher_add_view( - app->view_dispatcher, - WeatherStationViewReceiverInfo, - ws_view_receiver_info_get_view(app->ws_receiver_info)); - - //init setting - app->setting = subghz_setting_alloc(); - - //ToDo FIX file name setting - subghz_setting_load(app->setting, EXT_PATH("subghz/assets/setting_user")); - - //init Worker & Protocol & History - app->lock = WSLockOff; - app->txrx = malloc(sizeof(WeatherStationTxRx)); - app->txrx->preset = malloc(sizeof(SubGhzRadioPreset)); - app->txrx->preset->name = furi_string_alloc(); - ws_preset_init(app, "AM650", subghz_setting_get_default_frequency(app->setting), NULL, 0); - - app->txrx->hopper_state = WSHopperStateOFF; - app->txrx->history = ws_history_alloc(); - app->txrx->worker = subghz_worker_alloc(); - app->txrx->environment = subghz_environment_alloc(); - subghz_environment_set_protocol_registry( - app->txrx->environment, (void*)&weather_station_protocol_registry); - app->txrx->receiver = subghz_receiver_alloc_init(app->txrx->environment); - - subghz_receiver_set_filter(app->txrx->receiver, SubGhzProtocolFlag_Decodable); - subghz_worker_set_overrun_callback( - app->txrx->worker, (SubGhzWorkerOverrunCallback)subghz_receiver_reset); - subghz_worker_set_pair_callback( - app->txrx->worker, (SubGhzWorkerPairCallback)subghz_receiver_decode); - subghz_worker_set_context(app->txrx->worker, app->txrx->receiver); - - furi_hal_power_suppress_charge_enter(); - - scene_manager_next_scene(app->scene_manager, WeatherStationSceneStart); - - return app; -} - -void weather_station_app_free(WeatherStationApp* app) { - furi_assert(app); - - //CC1101 off - ws_sleep(app); - - // Submenu - view_dispatcher_remove_view(app->view_dispatcher, WeatherStationViewSubmenu); - submenu_free(app->submenu); - - // Variable Item List - view_dispatcher_remove_view(app->view_dispatcher, WeatherStationViewVariableItemList); - variable_item_list_free(app->variable_item_list); - - // Widget - view_dispatcher_remove_view(app->view_dispatcher, WeatherStationViewWidget); - widget_free(app->widget); - - // Receiver - view_dispatcher_remove_view(app->view_dispatcher, WeatherStationViewReceiver); - ws_view_receiver_free(app->ws_receiver); - - // Receiver Info - view_dispatcher_remove_view(app->view_dispatcher, WeatherStationViewReceiverInfo); - ws_view_receiver_info_free(app->ws_receiver_info); - - //setting - subghz_setting_free(app->setting); - - //Worker & Protocol & History - subghz_receiver_free(app->txrx->receiver); - subghz_environment_free(app->txrx->environment); - ws_history_free(app->txrx->history); - subghz_worker_free(app->txrx->worker); - furi_string_free(app->txrx->preset->name); - free(app->txrx->preset); - free(app->txrx); - - // View dispatcher - view_dispatcher_free(app->view_dispatcher); - scene_manager_free(app->scene_manager); - - // Notifications - furi_record_close(RECORD_NOTIFICATION); - app->notifications = NULL; - - // Close records - furi_record_close(RECORD_GUI); - - furi_hal_power_suppress_charge_exit(); - - free(app); -} - -int32_t weather_station_app(void* p) { - UNUSED(p); - WeatherStationApp* weather_station_app = weather_station_app_alloc(); - - view_dispatcher_run(weather_station_app->view_dispatcher); - - weather_station_app_free(weather_station_app); - - return 0; -} diff --git a/applications/external/weather_station/weather_station_app_i.c b/applications/external/weather_station/weather_station_app_i.c deleted file mode 100644 index 712634a2c04..00000000000 --- a/applications/external/weather_station/weather_station_app_i.c +++ /dev/null @@ -1,156 +0,0 @@ -#include "weather_station_app_i.h" - -#define TAG "WeatherStation" -#include - -void ws_preset_init( - void* context, - const char* preset_name, - uint32_t frequency, - uint8_t* preset_data, - size_t preset_data_size) { - furi_assert(context); - WeatherStationApp* app = context; - furi_string_set(app->txrx->preset->name, preset_name); - app->txrx->preset->frequency = frequency; - app->txrx->preset->data = preset_data; - app->txrx->preset->data_size = preset_data_size; -} - -bool ws_set_preset(WeatherStationApp* app, const char* preset) { - if(!strcmp(preset, "FuriHalSubGhzPresetOok270Async")) { - furi_string_set(app->txrx->preset->name, "AM270"); - } else if(!strcmp(preset, "FuriHalSubGhzPresetOok650Async")) { - furi_string_set(app->txrx->preset->name, "AM650"); - } else if(!strcmp(preset, "FuriHalSubGhzPreset2FSKDev238Async")) { - furi_string_set(app->txrx->preset->name, "FM238"); - } else if(!strcmp(preset, "FuriHalSubGhzPreset2FSKDev476Async")) { - furi_string_set(app->txrx->preset->name, "FM476"); - } else if(!strcmp(preset, "FuriHalSubGhzPresetCustom")) { - furi_string_set(app->txrx->preset->name, "CUSTOM"); - } else { - FURI_LOG_E(TAG, "Unknown preset"); - return false; - } - return true; -} - -void ws_get_frequency_modulation( - WeatherStationApp* app, - FuriString* frequency, - FuriString* modulation) { - furi_assert(app); - if(frequency != NULL) { - furi_string_printf( - frequency, - "%03ld.%02ld", - app->txrx->preset->frequency / 1000000 % 1000, - app->txrx->preset->frequency / 10000 % 100); - } - if(modulation != NULL) { - furi_string_printf(modulation, "%.2s", furi_string_get_cstr(app->txrx->preset->name)); - } -} - -void ws_begin(WeatherStationApp* app, uint8_t* preset_data) { - furi_assert(app); - UNUSED(preset_data); - furi_hal_subghz_reset(); - furi_hal_subghz_idle(); - furi_hal_subghz_load_custom_preset(preset_data); - furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow); - app->txrx->txrx_state = WSTxRxStateIDLE; -} - -uint32_t ws_rx(WeatherStationApp* app, uint32_t frequency) { - furi_assert(app); - if(!furi_hal_subghz_is_frequency_valid(frequency)) { - furi_crash("WeatherStation: Incorrect RX frequency."); - } - furi_assert( - app->txrx->txrx_state != WSTxRxStateRx && app->txrx->txrx_state != WSTxRxStateSleep); - - furi_hal_subghz_idle(); - uint32_t value = furi_hal_subghz_set_frequency_and_path(frequency); - furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow); - furi_hal_subghz_flush_rx(); - furi_hal_subghz_rx(); - - furi_hal_subghz_start_async_rx(subghz_worker_rx_callback, app->txrx->worker); - subghz_worker_start(app->txrx->worker); - app->txrx->txrx_state = WSTxRxStateRx; - return value; -} - -void ws_idle(WeatherStationApp* app) { - furi_assert(app); - furi_assert(app->txrx->txrx_state != WSTxRxStateSleep); - furi_hal_subghz_idle(); - app->txrx->txrx_state = WSTxRxStateIDLE; -} - -void ws_rx_end(WeatherStationApp* app) { - furi_assert(app); - furi_assert(app->txrx->txrx_state == WSTxRxStateRx); - if(subghz_worker_is_running(app->txrx->worker)) { - subghz_worker_stop(app->txrx->worker); - furi_hal_subghz_stop_async_rx(); - } - furi_hal_subghz_idle(); - app->txrx->txrx_state = WSTxRxStateIDLE; -} - -void ws_sleep(WeatherStationApp* app) { - furi_assert(app); - furi_hal_subghz_sleep(); - app->txrx->txrx_state = WSTxRxStateSleep; -} - -void ws_hopper_update(WeatherStationApp* app) { - furi_assert(app); - - switch(app->txrx->hopper_state) { - case WSHopperStateOFF: - case WSHopperStatePause: - return; - case WSHopperStateRSSITimeOut: - if(app->txrx->hopper_timeout != 0) { - app->txrx->hopper_timeout--; - return; - } - break; - default: - break; - } - float rssi = -127.0f; - if(app->txrx->hopper_state != WSHopperStateRSSITimeOut) { - // See RSSI Calculation timings in CC1101 17.3 RSSI - rssi = furi_hal_subghz_get_rssi(); - - // Stay if RSSI is high enough - if(rssi > -90.0f) { - app->txrx->hopper_timeout = 10; - app->txrx->hopper_state = WSHopperStateRSSITimeOut; - return; - } - } else { - app->txrx->hopper_state = WSHopperStateRunnig; - } - // Select next frequency - if(app->txrx->hopper_idx_frequency < - subghz_setting_get_hopper_frequency_count(app->setting) - 1) { - app->txrx->hopper_idx_frequency++; - } else { - app->txrx->hopper_idx_frequency = 0; - } - - if(app->txrx->txrx_state == WSTxRxStateRx) { - ws_rx_end(app); - }; - if(app->txrx->txrx_state == WSTxRxStateIDLE) { - subghz_receiver_reset(app->txrx->receiver); - app->txrx->preset->frequency = - subghz_setting_get_hopper_frequency(app->setting, app->txrx->hopper_idx_frequency); - ws_rx(app, app->txrx->preset->frequency); - } -} diff --git a/applications/external/weather_station/weather_station_app_i.h b/applications/external/weather_station/weather_station_app_i.h deleted file mode 100644 index 41e2481120d..00000000000 --- a/applications/external/weather_station/weather_station_app_i.h +++ /dev/null @@ -1,73 +0,0 @@ -#pragma once - -#include "helpers/weather_station_types.h" - -#include "scenes/weather_station_scene.h" -#include -#include -#include -#include -#include -#include -#include -#include "views/weather_station_receiver.h" -#include "views/weather_station_receiver_info.h" -#include "weather_station_history.h" - -#include -#include -#include -#include -#include - -typedef struct WeatherStationApp WeatherStationApp; - -struct WeatherStationTxRx { - SubGhzWorker* worker; - - SubGhzEnvironment* environment; - SubGhzReceiver* receiver; - SubGhzRadioPreset* preset; - WSHistory* history; - uint16_t idx_menu_chosen; - WSTxRxState txrx_state; - WSHopperState hopper_state; - uint8_t hopper_timeout; - uint8_t hopper_idx_frequency; - WSRxKeyState rx_key_state; -}; - -typedef struct WeatherStationTxRx WeatherStationTxRx; - -struct WeatherStationApp { - Gui* gui; - ViewDispatcher* view_dispatcher; - WeatherStationTxRx* txrx; - SceneManager* scene_manager; - NotificationApp* notifications; - VariableItemList* variable_item_list; - Submenu* submenu; - Widget* widget; - WSReceiver* ws_receiver; - WSReceiverInfo* ws_receiver_info; - WSLock lock; - SubGhzSetting* setting; -}; - -void ws_preset_init( - void* context, - const char* preset_name, - uint32_t frequency, - uint8_t* preset_data, - size_t preset_data_size); -bool ws_set_preset(WeatherStationApp* app, const char* preset); -void ws_get_frequency_modulation( - WeatherStationApp* app, - FuriString* frequency, - FuriString* modulation); -void ws_begin(WeatherStationApp* app, uint8_t* preset_data); -uint32_t ws_rx(WeatherStationApp* app, uint32_t frequency); -void ws_idle(WeatherStationApp* app); -void ws_rx_end(WeatherStationApp* app); -void ws_sleep(WeatherStationApp* app); -void ws_hopper_update(WeatherStationApp* app); diff --git a/applications/external/weather_station/weather_station_history.c b/applications/external/weather_station/weather_station_history.c deleted file mode 100644 index 9adff39c697..00000000000 --- a/applications/external/weather_station/weather_station_history.c +++ /dev/null @@ -1,245 +0,0 @@ -#include "weather_station_history.h" -#include -#include -#include -#include "protocols/ws_generic.h" - -#include - -#define WS_HISTORY_MAX 50 -#define TAG "WSHistory" - -typedef struct { - FuriString* item_str; - FlipperFormat* flipper_string; - uint8_t type; - uint32_t id; - SubGhzRadioPreset* preset; -} WSHistoryItem; - -ARRAY_DEF(WSHistoryItemArray, WSHistoryItem, M_POD_OPLIST) - -#define M_OPL_WSHistoryItemArray_t() ARRAY_OPLIST(WSHistoryItemArray, M_POD_OPLIST) - -typedef struct { - WSHistoryItemArray_t data; -} WSHistoryStruct; - -struct WSHistory { - uint32_t last_update_timestamp; - uint16_t last_index_write; - uint8_t code_last_hash_data; - FuriString* tmp_string; - WSHistoryStruct* history; -}; - -WSHistory* ws_history_alloc(void) { - WSHistory* instance = malloc(sizeof(WSHistory)); - instance->tmp_string = furi_string_alloc(); - instance->history = malloc(sizeof(WSHistoryStruct)); - WSHistoryItemArray_init(instance->history->data); - return instance; -} - -void ws_history_free(WSHistory* instance) { - furi_assert(instance); - furi_string_free(instance->tmp_string); - for - M_EACH(item, instance->history->data, WSHistoryItemArray_t) { - furi_string_free(item->item_str); - furi_string_free(item->preset->name); - free(item->preset); - flipper_format_free(item->flipper_string); - item->type = 0; - } - WSHistoryItemArray_clear(instance->history->data); - free(instance->history); - free(instance); -} - -uint32_t ws_history_get_frequency(WSHistory* instance, uint16_t idx) { - furi_assert(instance); - WSHistoryItem* item = WSHistoryItemArray_get(instance->history->data, idx); - return item->preset->frequency; -} - -SubGhzRadioPreset* ws_history_get_radio_preset(WSHistory* instance, uint16_t idx) { - furi_assert(instance); - WSHistoryItem* item = WSHistoryItemArray_get(instance->history->data, idx); - return item->preset; -} - -const char* ws_history_get_preset(WSHistory* instance, uint16_t idx) { - furi_assert(instance); - WSHistoryItem* item = WSHistoryItemArray_get(instance->history->data, idx); - return furi_string_get_cstr(item->preset->name); -} - -void ws_history_reset(WSHistory* instance) { - furi_assert(instance); - furi_string_reset(instance->tmp_string); - for - M_EACH(item, instance->history->data, WSHistoryItemArray_t) { - furi_string_free(item->item_str); - furi_string_free(item->preset->name); - free(item->preset); - flipper_format_free(item->flipper_string); - item->type = 0; - } - WSHistoryItemArray_reset(instance->history->data); - instance->last_index_write = 0; - instance->code_last_hash_data = 0; -} - -uint16_t ws_history_get_item(WSHistory* instance) { - furi_assert(instance); - return instance->last_index_write; -} - -uint8_t ws_history_get_type_protocol(WSHistory* instance, uint16_t idx) { - furi_assert(instance); - WSHistoryItem* item = WSHistoryItemArray_get(instance->history->data, idx); - return item->type; -} - -const char* ws_history_get_protocol_name(WSHistory* instance, uint16_t idx) { - furi_assert(instance); - WSHistoryItem* item = WSHistoryItemArray_get(instance->history->data, idx); - flipper_format_rewind(item->flipper_string); - if(!flipper_format_read_string(item->flipper_string, "Protocol", instance->tmp_string)) { - FURI_LOG_E(TAG, "Missing Protocol"); - furi_string_reset(instance->tmp_string); - } - return furi_string_get_cstr(instance->tmp_string); -} - -FlipperFormat* ws_history_get_raw_data(WSHistory* instance, uint16_t idx) { - furi_assert(instance); - WSHistoryItem* item = WSHistoryItemArray_get(instance->history->data, idx); - if(item->flipper_string) { - return item->flipper_string; - } else { - return NULL; - } -} -bool ws_history_get_text_space_left(WSHistory* instance, FuriString* output) { - furi_assert(instance); - if(instance->last_index_write == WS_HISTORY_MAX) { - if(output != NULL) furi_string_printf(output, "Memory is FULL"); - return true; - } - if(output != NULL) - furi_string_printf(output, "%02u/%02u", instance->last_index_write, WS_HISTORY_MAX); - return false; -} - -void ws_history_get_text_item_menu(WSHistory* instance, FuriString* output, uint16_t idx) { - WSHistoryItem* item = WSHistoryItemArray_get(instance->history->data, idx); - furi_string_set(output, item->item_str); -} - -WSHistoryStateAddKey - ws_history_add_to_history(WSHistory* instance, void* context, SubGhzRadioPreset* preset) { - furi_assert(instance); - furi_assert(context); - - if(instance->last_index_write >= WS_HISTORY_MAX) return WSHistoryStateAddKeyOverflow; - - SubGhzProtocolDecoderBase* decoder_base = context; - if((instance->code_last_hash_data == - subghz_protocol_decoder_base_get_hash_data(decoder_base)) && - ((furi_get_tick() - instance->last_update_timestamp) < 500)) { - instance->last_update_timestamp = furi_get_tick(); - return WSHistoryStateAddKeyTimeOut; - } - - instance->code_last_hash_data = subghz_protocol_decoder_base_get_hash_data(decoder_base); - instance->last_update_timestamp = furi_get_tick(); - - FlipperFormat* fff = flipper_format_string_alloc(); - uint32_t id = 0; - subghz_protocol_decoder_base_serialize(decoder_base, fff, preset); - - do { - if(!flipper_format_rewind(fff)) { - FURI_LOG_E(TAG, "Rewind error"); - break; - } - if(!flipper_format_read_uint32(fff, "Id", (uint32_t*)&id, 1)) { - FURI_LOG_E(TAG, "Missing Id"); - break; - } - } while(false); - flipper_format_free(fff); - - //Update record if found - bool sensor_found = false; - for(size_t i = 0; i < WSHistoryItemArray_size(instance->history->data); i++) { - WSHistoryItem* item = WSHistoryItemArray_get(instance->history->data, i); - if(item->id == id) { - sensor_found = true; - Stream* flipper_string_stream = flipper_format_get_raw_stream(item->flipper_string); - stream_clean(flipper_string_stream); - subghz_protocol_decoder_base_serialize(decoder_base, item->flipper_string, preset); - return WSHistoryStateAddKeyUpdateData; - } - } - - // or add new record - if(!sensor_found) { //-V547 - WSHistoryItem* item = WSHistoryItemArray_push_raw(instance->history->data); - item->preset = malloc(sizeof(SubGhzRadioPreset)); - item->type = decoder_base->protocol->type; - item->preset->frequency = preset->frequency; - item->preset->name = furi_string_alloc(); - furi_string_set(item->preset->name, preset->name); - item->preset->data = preset->data; - item->preset->data_size = preset->data_size; - item->id = id; - - item->item_str = furi_string_alloc(); - item->flipper_string = flipper_format_string_alloc(); - subghz_protocol_decoder_base_serialize(decoder_base, item->flipper_string, preset); - - do { - if(!flipper_format_rewind(item->flipper_string)) { - FURI_LOG_E(TAG, "Rewind error"); - break; - } - if(!flipper_format_read_string( - item->flipper_string, "Protocol", instance->tmp_string)) { - FURI_LOG_E(TAG, "Missing Protocol"); - break; - } - - if(!flipper_format_rewind(item->flipper_string)) { - FURI_LOG_E(TAG, "Rewind error"); - break; - } - uint8_t key_data[sizeof(uint64_t)] = {0}; - if(!flipper_format_read_hex(item->flipper_string, "Data", key_data, sizeof(uint64_t))) { - FURI_LOG_E(TAG, "Missing Data"); - break; - } - uint64_t data = 0; - for(uint8_t i = 0; i < sizeof(uint64_t); i++) { - data = (data << 8) | key_data[i]; - } - uint32_t temp_data = 0; - if(!flipper_format_read_uint32(item->flipper_string, "Ch", (uint32_t*)&temp_data, 1)) { - FURI_LOG_E(TAG, "Missing Channel"); - break; - } - if(temp_data != WS_NO_CHANNEL) { - furi_string_cat_printf(instance->tmp_string, " Ch:%X", (uint8_t)temp_data); - } - - furi_string_printf( - item->item_str, "%s %llX", furi_string_get_cstr(instance->tmp_string), data); - - } while(false); - instance->last_index_write++; - return WSHistoryStateAddKeyNewDada; - } - return WSHistoryStateAddKeyUnknown; -} diff --git a/applications/external/weather_station/weather_station_history.h b/applications/external/weather_station/weather_station_history.h deleted file mode 100644 index 11601fe79ec..00000000000 --- a/applications/external/weather_station/weather_station_history.h +++ /dev/null @@ -1,112 +0,0 @@ - -#pragma once - -#include -#include -#include -#include -#include - -typedef struct WSHistory WSHistory; - -/** History state add key */ -typedef enum { - WSHistoryStateAddKeyUnknown, - WSHistoryStateAddKeyTimeOut, - WSHistoryStateAddKeyNewDada, - WSHistoryStateAddKeyUpdateData, - WSHistoryStateAddKeyOverflow, -} WSHistoryStateAddKey; - -/** Allocate WSHistory - * - * @return WSHistory* - */ -WSHistory* ws_history_alloc(void); - -/** Free WSHistory - * - * @param instance - WSHistory instance - */ -void ws_history_free(WSHistory* instance); - -/** Clear history - * - * @param instance - WSHistory instance - */ -void ws_history_reset(WSHistory* instance); - -/** Get frequency to history[idx] - * - * @param instance - WSHistory instance - * @param idx - record index - * @return frequency - frequency Hz - */ -uint32_t ws_history_get_frequency(WSHistory* instance, uint16_t idx); - -SubGhzRadioPreset* ws_history_get_radio_preset(WSHistory* instance, uint16_t idx); - -/** Get preset to history[idx] - * - * @param instance - WSHistory instance - * @param idx - record index - * @return preset - preset name - */ -const char* ws_history_get_preset(WSHistory* instance, uint16_t idx); - -/** Get history index write - * - * @param instance - WSHistory instance - * @return idx - current record index - */ -uint16_t ws_history_get_item(WSHistory* instance); - -/** Get type protocol to history[idx] - * - * @param instance - WSHistory instance - * @param idx - record index - * @return type - type protocol - */ -uint8_t ws_history_get_type_protocol(WSHistory* instance, uint16_t idx); - -/** Get name protocol to history[idx] - * - * @param instance - WSHistory instance - * @param idx - record index - * @return name - const char* name protocol - */ -const char* ws_history_get_protocol_name(WSHistory* instance, uint16_t idx); - -/** Get string item menu to history[idx] - * - * @param instance - WSHistory instance - * @param output - FuriString* output - * @param idx - record index - */ -void ws_history_get_text_item_menu(WSHistory* instance, FuriString* output, uint16_t idx); - -/** Get string the remaining number of records to history - * - * @param instance - WSHistory instance - * @param output - FuriString* output - * @return bool - is FUUL - */ -bool ws_history_get_text_space_left(WSHistory* instance, FuriString* output); - -/** Add protocol to history - * - * @param instance - WSHistory instance - * @param context - SubGhzProtocolCommon context - * @param preset - SubGhzRadioPreset preset - * @return WSHistoryStateAddKey; - */ -WSHistoryStateAddKey - ws_history_add_to_history(WSHistory* instance, void* context, SubGhzRadioPreset* preset); - -/** Get SubGhzProtocolCommonLoad to load into the protocol decoder bin data - * - * @param instance - WSHistory instance - * @param idx - record index - * @return SubGhzProtocolCommonLoad* - */ -FlipperFormat* ws_history_get_raw_data(WSHistory* instance, uint16_t idx); diff --git a/applications/main/application.fam b/applications/main/application.fam index 0a90ee2243f..e3ceac34d10 100644 --- a/applications/main/application.fam +++ b/applications/main/application.fam @@ -11,6 +11,9 @@ App( "subghz", "bad_usb", "u2f", + "clock", + "music_player", + "snake_game", "archive", "main_apps_on_start", ], diff --git a/applications/external/clock/application.fam b/applications/main/clock/application.fam similarity index 100% rename from applications/external/clock/application.fam rename to applications/main/clock/application.fam diff --git a/applications/external/clock/clock.png b/applications/main/clock/clock.png similarity index 100% rename from applications/external/clock/clock.png rename to applications/main/clock/clock.png diff --git a/applications/external/clock/clock_app.c b/applications/main/clock/clock_app.c similarity index 100% rename from applications/external/clock/clock_app.c rename to applications/main/clock/clock_app.c diff --git a/applications/external/music_player/application.fam b/applications/main/music_player/application.fam similarity index 100% rename from applications/external/music_player/application.fam rename to applications/main/music_player/application.fam diff --git a/applications/external/music_player/icons/music_10px.png b/applications/main/music_player/icons/music_10px.png similarity index 100% rename from applications/external/music_player/icons/music_10px.png rename to applications/main/music_player/icons/music_10px.png diff --git a/applications/external/music_player/music_player.c b/applications/main/music_player/music_player.c similarity index 100% rename from applications/external/music_player/music_player.c rename to applications/main/music_player/music_player.c diff --git a/applications/external/snake_game/application.fam b/applications/main/snake_game/application.fam similarity index 100% rename from applications/external/snake_game/application.fam rename to applications/main/snake_game/application.fam diff --git a/applications/external/snake_game/snake_10px.png b/applications/main/snake_game/snake_10px.png similarity index 100% rename from applications/external/snake_game/snake_10px.png rename to applications/main/snake_game/snake_10px.png diff --git a/applications/external/snake_game/snake_game.c b/applications/main/snake_game/snake_game.c similarity index 100% rename from applications/external/snake_game/snake_game.c rename to applications/main/snake_game/snake_game.c diff --git a/documentation/Doxyfile b/documentation/Doxyfile index bb43ce8a773..f31cbb9d868 100644 --- a/documentation/Doxyfile +++ b/documentation/Doxyfile @@ -939,8 +939,7 @@ EXCLUDE = \ lib/FreeRTOS-Kernel \ lib/microtar \ lib/mbedtls \ - lib/cxxheaderparser \ - applications/external/dap_link/lib/free-dap + lib/cxxheaderparser # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded diff --git a/site_scons/commandline.scons b/site_scons/commandline.scons index 776977cda74..0d3b1f92e1b 100644 --- a/site_scons/commandline.scons +++ b/site_scons/commandline.scons @@ -226,7 +226,6 @@ vars.AddVariables( ("applications/settings", False), ("applications/system", False), ("applications/debug", False), - ("applications/external", False), ("applications/examples", False), ("applications/drivers", False), ("applications_user", False), From c976ff11bf18c26cc889b0f7d0b555a6972651a8 Mon Sep 17 00:00:00 2001 From: twisted-pear Date: Thu, 10 Aug 2023 10:44:46 +0200 Subject: [PATCH 701/824] Expose additional functions of the crypto engine to user (#2923) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Allow loading user supplied keys and add CTR mode * Add GCM mode to furi_hal_crypto * Split up CTR and GCM code, add flag for adv crypto * Add convenience functions for GCM crypto * Run fbt format * Update GCM to support additional auth data * Update APIs * FuriHal: update crypto documentation, method names and usage * Clean up code for key (un)loading, GCM and CTR - get rid of goto - do not use furi_hal_bt_is_alive() when not using secure enclave - give defines a type and wrap in () * Add unit test for CTR and GCM crypto * FuriHal: const in crypto unit tests, cortex timer for crypto operations timeouts * FuriHal: update crypto docs Co-authored-by: twisted_pear Co-authored-by: hedger Co-authored-by: あく --- .../furi_hal/furi_hal_crypto_tests.c | 602 ++++++++++++++++++ applications/debug/unit_tests/test_index.c | 2 + applications/main/u2f/u2f_data.c | 28 +- applications/services/crypto/crypto_cli.c | 22 +- firmware/targets/f18/api_symbols.csv | 18 +- firmware/targets/f7/api_symbols.csv | 18 +- .../targets/f7/furi_hal/furi_hal_crypto.c | 446 ++++++++++++- firmware/targets/f7/furi_hal/furi_hal_info.c | 2 +- .../furi_hal_include/furi_hal_crypto.h | 235 ++++++- lib/subghz/subghz_keystore.c | 16 +- 10 files changed, 1299 insertions(+), 90 deletions(-) create mode 100644 applications/debug/unit_tests/furi_hal/furi_hal_crypto_tests.c diff --git a/applications/debug/unit_tests/furi_hal/furi_hal_crypto_tests.c b/applications/debug/unit_tests/furi_hal/furi_hal_crypto_tests.c new file mode 100644 index 00000000000..b06d51130f9 --- /dev/null +++ b/applications/debug/unit_tests/furi_hal/furi_hal_crypto_tests.c @@ -0,0 +1,602 @@ +#include +#include +#include +#include "../minunit.h" + +static const uint8_t key_ctr_1[32] = { + 0x77, 0x6B, 0xEF, 0xF2, 0x85, 0x1D, 0xB0, 0x6F, 0x4C, 0x8A, 0x05, 0x42, 0xC8, 0x69, 0x6F, 0x6C, + 0x6A, 0x81, 0xAF, 0x1E, 0xEC, 0x96, 0xB4, 0xD3, 0x7F, 0xC1, 0xD6, 0x89, 0xE6, 0xC1, 0xC1, 0x04, +}; +static const uint8_t iv_ctr_1[16] = { + 0x00, + 0x00, + 0x00, + 0x60, + 0xDB, + 0x56, + 0x72, + 0xC9, + 0x7A, + 0xA8, + 0xF0, + 0xB2, + 0x00, + 0x00, + 0x00, + 0x01, +}; +static const uint8_t pt_ctr_1[16] = { + 0x53, + 0x69, + 0x6E, + 0x67, + 0x6C, + 0x65, + 0x20, + 0x62, + 0x6C, + 0x6F, + 0x63, + 0x6B, + 0x20, + 0x6D, + 0x73, + 0x67, +}; +static const uint8_t tv_ctr_ct_1[16] = { + 0x14, + 0x5A, + 0xD0, + 0x1D, + 0xBF, + 0x82, + 0x4E, + 0xC7, + 0x56, + 0x08, + 0x63, + 0xDC, + 0x71, + 0xE3, + 0xE0, + 0xC0, +}; + +static const uint8_t key_ctr_2[32] = { + 0x77, 0x6B, 0xEF, 0xF2, 0x85, 0x1D, 0xB0, 0x6F, 0x4C, 0x8A, 0x05, 0x42, 0xC8, 0x69, 0x6F, 0x6C, + 0x6A, 0x81, 0xAF, 0x1E, 0xEC, 0x96, 0xB4, 0xD3, 0x7F, 0xC1, 0xD6, 0x89, 0xE6, 0xC1, 0xC1, 0x04, +}; +static const uint8_t iv_ctr_2[16] = { + 0x00, + 0x00, + 0x00, + 0x60, + 0xDB, + 0x56, + 0x72, + 0xC9, + 0x7A, + 0xA8, + 0xF0, + 0xB2, + 0x00, + 0x00, + 0x00, + 0x01, +}; +static const uint8_t pt_ctr_2[0] = {}; +//static const uint8_t tv_ctr_ct_2[0] = {}; + +static const uint8_t key_ctr_3[32] = { + 0xF6, 0xD6, 0x6D, 0x6B, 0xD5, 0x2D, 0x59, 0xBB, 0x07, 0x96, 0x36, 0x58, 0x79, 0xEF, 0xF8, 0x86, + 0xC6, 0x6D, 0xD5, 0x1A, 0x5B, 0x6A, 0x99, 0x74, 0x4B, 0x50, 0x59, 0x0C, 0x87, 0xA2, 0x38, 0x84, +}; +static const uint8_t iv_ctr_3[16] = { + 0x00, + 0xFA, + 0xAC, + 0x24, + 0xC1, + 0x58, + 0x5E, + 0xF1, + 0x5A, + 0x43, + 0xD8, + 0x75, + 0x00, + 0x00, + 0x00, + 0x01, +}; +static const uint8_t pt_ctr_3[32] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, +}; +static const uint8_t tv_ctr_ct_3[32] = { + 0xF0, 0x5E, 0x23, 0x1B, 0x38, 0x94, 0x61, 0x2C, 0x49, 0xEE, 0x00, 0x0B, 0x80, 0x4E, 0xB2, 0xA9, + 0xB8, 0x30, 0x6B, 0x50, 0x8F, 0x83, 0x9D, 0x6A, 0x55, 0x30, 0x83, 0x1D, 0x93, 0x44, 0xAF, 0x1C, +}; + +static const uint8_t key_ctr_4[32] = { + 0xFF, 0x7A, 0x61, 0x7C, 0xE6, 0x91, 0x48, 0xE4, 0xF1, 0x72, 0x6E, 0x2F, 0x43, 0x58, 0x1D, 0xE2, + 0xAA, 0x62, 0xD9, 0xF8, 0x05, 0x53, 0x2E, 0xDF, 0xF1, 0xEE, 0xD6, 0x87, 0xFB, 0x54, 0x15, 0x3D, +}; +static const uint8_t iv_ctr_4[16] = { + 0x00, + 0x1C, + 0xC5, + 0xB7, + 0x51, + 0xA5, + 0x1D, + 0x70, + 0xA1, + 0xC1, + 0x11, + 0x48, + 0x00, + 0x00, + 0x00, + 0x01, +}; +static const uint8_t pt_ctr_4[36] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, + 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, +}; +static const uint8_t tv_ctr_ct_4[36] = { + 0xEB, 0x6C, 0x52, 0x82, 0x1D, 0x0B, 0xBB, 0xF7, 0xCE, 0x75, 0x94, 0x46, + 0x2A, 0xCA, 0x4F, 0xAA, 0xB4, 0x07, 0xDF, 0x86, 0x65, 0x69, 0xFD, 0x07, + 0xF4, 0x8C, 0xC0, 0xB5, 0x83, 0xD6, 0x07, 0x1F, 0x1E, 0xC0, 0xE6, 0xB8, +}; + +static const uint8_t key_ctr_5[32] = { + 0xFF, 0x7A, 0x61, 0x7C, 0xE6, 0x91, 0x48, 0xE4, 0xF1, 0x72, 0x6E, 0x2F, 0x43, 0x58, 0x1D, 0xE2, + 0xAA, 0x62, 0xD9, 0xF8, 0x05, 0x53, 0x2E, 0xDF, 0xF1, 0xEE, 0xD6, 0x87, 0xFB, 0x54, 0x15, 0x3D, +}; +static const uint8_t iv_ctr_5[16] = { + 0x00, + 0x1C, + 0xC5, + 0xB7, + 0x51, + 0xA5, + 0x1D, + 0x70, + 0xA1, + 0xC1, + 0x11, + 0x48, + 0x00, + 0x00, + 0x00, + 0x01, +}; +static const uint8_t pt_ctr_5[0] = {}; +//static const uint8_t tv_ctr_ct_5[0] = {}; + +static const uint8_t key_gcm_1[32] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; +static const uint8_t iv_gcm_1[16] = { + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, +}; +static const uint8_t pt_gcm_1[0] = {}; +//static const uint8_t tv_gcm_ct_1[0] = {}; +static const uint8_t aad_gcm_1[0] = {}; +static const uint8_t tv_gcm_tag_1[16] = { + 0x53, + 0x0F, + 0x8A, + 0xFB, + 0xC7, + 0x45, + 0x36, + 0xB9, + 0xA9, + 0x63, + 0xB4, + 0xF1, + 0xC4, + 0xCB, + 0x73, + 0x8B, +}; + +static const uint8_t key_gcm_2[32] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; +static const uint8_t iv_gcm_2[16] = { + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, +}; +static const uint8_t pt_gcm_2[16] = { + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, +}; +static const uint8_t tv_gcm_ct_2[16] = { + 0xCE, + 0xA7, + 0x40, + 0x3D, + 0x4D, + 0x60, + 0x6B, + 0x6E, + 0x07, + 0x4E, + 0xC5, + 0xD3, + 0xBA, + 0xF3, + 0x9D, + 0x18, +}; +static const uint8_t aad_gcm_2[0] = {}; +static const uint8_t tv_gcm_tag_2[16] = { + 0xD0, + 0xD1, + 0xC8, + 0xA7, + 0x99, + 0x99, + 0x6B, + 0xF0, + 0x26, + 0x5B, + 0x98, + 0xB5, + 0xD4, + 0x8A, + 0xB9, + 0x19, +}; + +static const uint8_t key_gcm_3[32] = { + 0xFE, 0xFF, 0xE9, 0x92, 0x86, 0x65, 0x73, 0x1C, 0x6D, 0x6A, 0x8F, 0x94, 0x67, 0x30, 0x83, 0x08, + 0xFE, 0xFF, 0xE9, 0x92, 0x86, 0x65, 0x73, 0x1C, 0x6D, 0x6A, 0x8F, 0x94, 0x67, 0x30, 0x83, 0x08, +}; +static const uint8_t iv_gcm_3[16] = { + 0xCA, + 0xFE, + 0xBA, + 0xBE, + 0xFA, + 0xCE, + 0xDB, + 0xAD, + 0xDE, + 0xCA, + 0xF8, + 0x88, + 0x00, + 0x00, + 0x00, + 0x00, +}; +static const uint8_t pt_gcm_3[64] = { + 0xD9, 0x31, 0x32, 0x25, 0xF8, 0x84, 0x06, 0xE5, 0xA5, 0x59, 0x09, 0xC5, 0xAF, 0xF5, 0x26, 0x9A, + 0x86, 0xA7, 0xA9, 0x53, 0x15, 0x34, 0xF7, 0xDA, 0x2E, 0x4C, 0x30, 0x3D, 0x8A, 0x31, 0x8A, 0x72, + 0x1C, 0x3C, 0x0C, 0x95, 0x95, 0x68, 0x09, 0x53, 0x2F, 0xCF, 0x0E, 0x24, 0x49, 0xA6, 0xB5, 0x25, + 0xB1, 0x6A, 0xED, 0xF5, 0xAA, 0x0D, 0xE6, 0x57, 0xBA, 0x63, 0x7B, 0x39, 0x1A, 0xAF, 0xD2, 0x55, +}; +static const uint8_t tv_gcm_ct_3[64] = { + 0x52, 0x2D, 0xC1, 0xF0, 0x99, 0x56, 0x7D, 0x07, 0xF4, 0x7F, 0x37, 0xA3, 0x2A, 0x84, 0x42, 0x7D, + 0x64, 0x3A, 0x8C, 0xDC, 0xBF, 0xE5, 0xC0, 0xC9, 0x75, 0x98, 0xA2, 0xBD, 0x25, 0x55, 0xD1, 0xAA, + 0x8C, 0xB0, 0x8E, 0x48, 0x59, 0x0D, 0xBB, 0x3D, 0xA7, 0xB0, 0x8B, 0x10, 0x56, 0x82, 0x88, 0x38, + 0xC5, 0xF6, 0x1E, 0x63, 0x93, 0xBA, 0x7A, 0x0A, 0xBC, 0xC9, 0xF6, 0x62, 0x89, 0x80, 0x15, 0xAD, +}; +static const uint8_t aad_gcm_3[0] = {}; +static const uint8_t tv_gcm_tag_3[16] = { + 0xB0, + 0x94, + 0xDA, + 0xC5, + 0xD9, + 0x34, + 0x71, + 0xBD, + 0xEC, + 0x1A, + 0x50, + 0x22, + 0x70, + 0xE3, + 0xCC, + 0x6C, +}; + +static const uint8_t key_gcm_4[32] = { + 0xFE, 0xFF, 0xE9, 0x92, 0x86, 0x65, 0x73, 0x1C, 0x6D, 0x6A, 0x8F, 0x94, 0x67, 0x30, 0x83, 0x08, + 0xFE, 0xFF, 0xE9, 0x92, 0x86, 0x65, 0x73, 0x1C, 0x6D, 0x6A, 0x8F, 0x94, 0x67, 0x30, 0x83, 0x08, +}; +static const uint8_t iv_gcm_4[16] = { + 0xCA, + 0xFE, + 0xBA, + 0xBE, + 0xFA, + 0xCE, + 0xDB, + 0xAD, + 0xDE, + 0xCA, + 0xF8, + 0x88, + 0x00, + 0x00, + 0x00, + 0x00, +}; +static const uint8_t pt_gcm_4[60] = { + 0xD9, 0x31, 0x32, 0x25, 0xF8, 0x84, 0x06, 0xE5, 0xA5, 0x59, 0x09, 0xC5, 0xAF, 0xF5, 0x26, + 0x9A, 0x86, 0xA7, 0xA9, 0x53, 0x15, 0x34, 0xF7, 0xDA, 0x2E, 0x4C, 0x30, 0x3D, 0x8A, 0x31, + 0x8A, 0x72, 0x1C, 0x3C, 0x0C, 0x95, 0x95, 0x68, 0x09, 0x53, 0x2F, 0xCF, 0x0E, 0x24, 0x49, + 0xA6, 0xB5, 0x25, 0xB1, 0x6A, 0xED, 0xF5, 0xAA, 0x0D, 0xE6, 0x57, 0xBA, 0x63, 0x7B, 0x39, +}; +static const uint8_t tv_gcm_ct_4[60] = { + 0x52, 0x2D, 0xC1, 0xF0, 0x99, 0x56, 0x7D, 0x07, 0xF4, 0x7F, 0x37, 0xA3, 0x2A, 0x84, 0x42, + 0x7D, 0x64, 0x3A, 0x8C, 0xDC, 0xBF, 0xE5, 0xC0, 0xC9, 0x75, 0x98, 0xA2, 0xBD, 0x25, 0x55, + 0xD1, 0xAA, 0x8C, 0xB0, 0x8E, 0x48, 0x59, 0x0D, 0xBB, 0x3D, 0xA7, 0xB0, 0x8B, 0x10, 0x56, + 0x82, 0x88, 0x38, 0xC5, 0xF6, 0x1E, 0x63, 0x93, 0xBA, 0x7A, 0x0A, 0xBC, 0xC9, 0xF6, 0x62, +}; +static const uint8_t aad_gcm_4[20] = { + 0xFE, 0xED, 0xFA, 0xCE, 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED, + 0xFA, 0xCE, 0xDE, 0xAD, 0xBE, 0xEF, 0xAB, 0xAD, 0xDA, 0xD2, +}; +static const uint8_t tv_gcm_tag_4[16] = { + 0x76, + 0xFC, + 0x6E, + 0xCE, + 0x0F, + 0x4E, + 0x17, + 0x68, + 0xCD, + 0xDF, + 0x88, + 0x53, + 0xBB, + 0x2D, + 0x55, + 0x1B, +}; + +static void furi_hal_crypto_ctr_setup() { +} + +static void furi_hal_crypto_ctr_teardown() { +} + +static void furi_hal_crypto_gcm_setup() { +} + +static void furi_hal_crypto_gcm_teardown() { +} + +MU_TEST(furi_hal_crypto_ctr_1) { + bool ret = false; + uint8_t ct[sizeof(pt_ctr_1)]; + + ret = furi_hal_crypto_ctr(key_ctr_1, iv_ctr_1, pt_ctr_1, ct, sizeof(pt_ctr_1)); + mu_assert(ret, "CTR 1 failed"); + mu_assert_mem_eq(tv_ctr_ct_1, ct, sizeof(pt_ctr_1)); +} + +MU_TEST(furi_hal_crypto_ctr_2) { + bool ret = false; + uint8_t ct[sizeof(pt_ctr_2)]; + + ret = furi_hal_crypto_ctr(key_ctr_2, iv_ctr_2, pt_ctr_2, ct, sizeof(pt_ctr_2)); + mu_assert(ret, "CTR 2 failed"); + //mu_assert_mem_eq(tv_ctr_ct_2, ct, sizeof(pt_ctr_2)); +} + +MU_TEST(furi_hal_crypto_ctr_3) { + bool ret = false; + uint8_t ct[sizeof(pt_ctr_3)]; + + ret = furi_hal_crypto_ctr(key_ctr_3, iv_ctr_3, pt_ctr_3, ct, sizeof(pt_ctr_3)); + mu_assert(ret, "CTR 3 failed"); + mu_assert_mem_eq(tv_ctr_ct_3, ct, sizeof(pt_ctr_3)); +} + +MU_TEST(furi_hal_crypto_ctr_4) { + bool ret = false; + uint8_t ct[sizeof(pt_ctr_4)]; + + ret = furi_hal_crypto_ctr(key_ctr_4, iv_ctr_4, pt_ctr_4, ct, sizeof(pt_ctr_4)); + mu_assert(ret, "CTR 4 failed"); + mu_assert_mem_eq(tv_ctr_ct_4, ct, sizeof(pt_ctr_4)); +} + +MU_TEST(furi_hal_crypto_ctr_5) { + bool ret = false; + uint8_t ct[sizeof(pt_ctr_5)]; + + ret = furi_hal_crypto_ctr(key_ctr_5, iv_ctr_5, pt_ctr_5, ct, sizeof(pt_ctr_5)); + mu_assert(ret, "CTR 5 failed"); + //mu_assert_mem_eq(tv_ctr_ct_5, ct, sizeof(pt_ctr_5)); +} + +MU_TEST(furi_hal_crypto_gcm_1) { + bool ret = false; + uint8_t pt[sizeof(pt_gcm_1)]; + uint8_t ct[sizeof(pt_gcm_1)]; + uint8_t tag_enc[16]; + uint8_t tag_dec[16]; + + ret = furi_hal_crypto_gcm( + key_gcm_1, + iv_gcm_1, + aad_gcm_1, + sizeof(aad_gcm_1), + pt_gcm_1, + ct, + sizeof(pt_gcm_1), + tag_enc, + false); + mu_assert(ret, "GCM 1 encryption failed"); + //mu_assert_mem_eq(tv_gcm_ct_1, ct, sizeof(pt_gcm_1)); + mu_assert_mem_eq(tv_gcm_tag_1, tag_enc, 16); + + ret = furi_hal_crypto_gcm( + key_gcm_1, iv_gcm_1, aad_gcm_1, sizeof(aad_gcm_1), ct, pt, sizeof(pt_gcm_1), tag_dec, true); + mu_assert(ret, "GCM 1 decryption failed"); + //mu_assert_mem_eq(pt_gcm_1, pt, sizeof(pt_gcm_1)); + mu_assert_mem_eq(tv_gcm_tag_1, tag_dec, 16); +} + +MU_TEST(furi_hal_crypto_gcm_2) { + bool ret = false; + uint8_t pt[sizeof(pt_gcm_2)]; + uint8_t ct[sizeof(pt_gcm_2)]; + uint8_t tag_enc[16]; + uint8_t tag_dec[16]; + + ret = furi_hal_crypto_gcm( + key_gcm_2, + iv_gcm_2, + aad_gcm_2, + sizeof(aad_gcm_2), + pt_gcm_2, + ct, + sizeof(pt_gcm_2), + tag_enc, + false); + mu_assert(ret, "GCM 2 encryption failed"); + mu_assert_mem_eq(tv_gcm_ct_2, ct, sizeof(pt_gcm_2)); + mu_assert_mem_eq(tv_gcm_tag_2, tag_enc, 16); + + ret = furi_hal_crypto_gcm( + key_gcm_2, iv_gcm_2, aad_gcm_2, sizeof(aad_gcm_2), ct, pt, sizeof(pt_gcm_2), tag_dec, true); + mu_assert(ret, "GCM 2 decryption failed"); + mu_assert_mem_eq(pt_gcm_2, pt, sizeof(pt_gcm_2)); + mu_assert_mem_eq(tv_gcm_tag_2, tag_dec, 16); +} + +MU_TEST(furi_hal_crypto_gcm_3) { + bool ret = false; + uint8_t pt[sizeof(pt_gcm_3)]; + uint8_t ct[sizeof(pt_gcm_3)]; + uint8_t tag_enc[16]; + uint8_t tag_dec[16]; + + ret = furi_hal_crypto_gcm( + key_gcm_3, + iv_gcm_3, + aad_gcm_3, + sizeof(aad_gcm_3), + pt_gcm_3, + ct, + sizeof(pt_gcm_3), + tag_enc, + false); + mu_assert(ret, "GCM 3 encryption failed"); + mu_assert_mem_eq(tv_gcm_ct_3, ct, sizeof(pt_gcm_3)); + mu_assert_mem_eq(tv_gcm_tag_3, tag_enc, 16); + + ret = furi_hal_crypto_gcm( + key_gcm_3, iv_gcm_3, aad_gcm_3, sizeof(aad_gcm_3), ct, pt, sizeof(pt_gcm_3), tag_dec, true); + mu_assert(ret, "GCM 3 decryption failed"); + mu_assert_mem_eq(pt_gcm_3, pt, sizeof(pt_gcm_3)); + mu_assert_mem_eq(tv_gcm_tag_3, tag_dec, 16); +} + +MU_TEST(furi_hal_crypto_gcm_4) { + bool ret = false; + uint8_t pt[sizeof(pt_gcm_4)]; + uint8_t ct[sizeof(pt_gcm_4)]; + uint8_t tag_enc[16]; + uint8_t tag_dec[16]; + + ret = furi_hal_crypto_gcm( + key_gcm_4, + iv_gcm_4, + aad_gcm_4, + sizeof(aad_gcm_4), + pt_gcm_4, + ct, + sizeof(pt_gcm_4), + tag_enc, + false); + mu_assert(ret, "GCM 4 encryption failed"); + mu_assert_mem_eq(tv_gcm_ct_4, ct, sizeof(pt_gcm_4)); + mu_assert_mem_eq(tv_gcm_tag_4, tag_enc, 16); + + ret = furi_hal_crypto_gcm( + key_gcm_4, iv_gcm_4, aad_gcm_4, sizeof(aad_gcm_4), ct, pt, sizeof(pt_gcm_4), tag_dec, true); + mu_assert(ret, "GCM 4 decryption failed"); + mu_assert_mem_eq(pt_gcm_4, pt, sizeof(pt_gcm_4)); + mu_assert_mem_eq(tv_gcm_tag_4, tag_dec, 16); +} + +MU_TEST_SUITE(furi_hal_crypto_ctr_test) { + MU_SUITE_CONFIGURE(&furi_hal_crypto_ctr_setup, &furi_hal_crypto_ctr_teardown); + MU_RUN_TEST(furi_hal_crypto_ctr_1); + MU_RUN_TEST(furi_hal_crypto_ctr_2); + MU_RUN_TEST(furi_hal_crypto_ctr_3); + MU_RUN_TEST(furi_hal_crypto_ctr_4); + MU_RUN_TEST(furi_hal_crypto_ctr_5); +} + +MU_TEST_SUITE(furi_hal_crypto_gcm_test) { + MU_SUITE_CONFIGURE(&furi_hal_crypto_gcm_setup, &furi_hal_crypto_gcm_teardown); + MU_RUN_TEST(furi_hal_crypto_gcm_1); + MU_RUN_TEST(furi_hal_crypto_gcm_2); + MU_RUN_TEST(furi_hal_crypto_gcm_3); + MU_RUN_TEST(furi_hal_crypto_gcm_4); +} + +int run_minunit_test_furi_hal_crypto() { + MU_RUN_SUITE(furi_hal_crypto_ctr_test); + MU_RUN_SUITE(furi_hal_crypto_gcm_test); + return MU_EXIT_CODE; +} diff --git a/applications/debug/unit_tests/test_index.c b/applications/debug/unit_tests/test_index.c index 9d7631bfee3..f7a53d7c8f4 100644 --- a/applications/debug/unit_tests/test_index.c +++ b/applications/debug/unit_tests/test_index.c @@ -10,6 +10,7 @@ int run_minunit_test_furi(); int run_minunit_test_furi_hal(); +int run_minunit_test_furi_hal_crypto(); int run_minunit_test_furi_string(); int run_minunit_test_infrared(); int run_minunit_test_rpc(); @@ -39,6 +40,7 @@ typedef struct { const UnitTest unit_tests[] = { {.name = "furi", .entry = run_minunit_test_furi}, {.name = "furi_hal", .entry = run_minunit_test_furi_hal}, + {.name = "furi_hal_crypto", .entry = run_minunit_test_furi_hal_crypto}, {.name = "furi_string", .entry = run_minunit_test_furi_string}, {.name = "storage", .entry = run_minunit_test_storage}, {.name = "stream", .entry = run_minunit_test_stream}, diff --git a/applications/main/u2f/u2f_data.c b/applications/main/u2f/u2f_data.c index e5433544a15..8489ed91e3b 100644 --- a/applications/main/u2f/u2f_data.c +++ b/applications/main/u2f/u2f_data.c @@ -14,7 +14,7 @@ #define U2F_CNT_FILE U2F_DATA_FOLDER "cnt.u2f" #define U2F_DATA_FILE_ENCRYPTION_KEY_SLOT_FACTORY 2 -#define U2F_DATA_FILE_ENCRYPTION_KEY_SLOT_UNIQUE 11 +#define U2F_DATA_FILE_ENCRYPTION_KEY_SLOT_UNIQUE FURI_HAL_CRYPTO_ENCLAVE_UNIQUE_KEY_SLOT #define U2F_CERT_STOCK 0 // Stock certificate, private key is encrypted with factory key #define U2F_CERT_USER 1 // User certificate, private key is encrypted with unique key @@ -136,7 +136,7 @@ static bool u2f_data_cert_key_encrypt(uint8_t* cert_key) { // Generate random IV furi_hal_random_fill_buf(iv, 16); - if(!furi_hal_crypto_store_load_key(U2F_DATA_FILE_ENCRYPTION_KEY_SLOT_UNIQUE, iv)) { + if(!furi_hal_crypto_enclave_load_key(U2F_DATA_FILE_ENCRYPTION_KEY_SLOT_UNIQUE, iv)) { FURI_LOG_E(TAG, "Unable to load encryption key"); return false; } @@ -145,7 +145,7 @@ static bool u2f_data_cert_key_encrypt(uint8_t* cert_key) { FURI_LOG_E(TAG, "Encryption failed"); return false; } - furi_hal_crypto_store_unload_key(U2F_DATA_FILE_ENCRYPTION_KEY_SLOT_UNIQUE); + furi_hal_crypto_enclave_unload_key(U2F_DATA_FILE_ENCRYPTION_KEY_SLOT_UNIQUE); Storage* storage = furi_record_open(RECORD_STORAGE); FlipperFormat* flipper_format = flipper_format_file_alloc(storage); @@ -179,7 +179,7 @@ bool u2f_data_cert_key_load(uint8_t* cert_key) { uint32_t version = 0; // Check if unique key exists in secure eclave and generate it if missing - if(!furi_hal_crypto_verify_key(U2F_DATA_FILE_ENCRYPTION_KEY_SLOT_UNIQUE)) return false; + if(!furi_hal_crypto_enclave_ensure_key(U2F_DATA_FILE_ENCRYPTION_KEY_SLOT_UNIQUE)) return false; FuriString* filetype; filetype = furi_string_alloc(); @@ -226,7 +226,7 @@ bool u2f_data_cert_key_load(uint8_t* cert_key) { break; } - if(!furi_hal_crypto_store_load_key(key_slot, iv)) { + if(!furi_hal_crypto_enclave_load_key(key_slot, iv)) { FURI_LOG_E(TAG, "Unable to load encryption key"); break; } @@ -237,7 +237,7 @@ bool u2f_data_cert_key_load(uint8_t* cert_key) { FURI_LOG_E(TAG, "Decryption failed"); break; } - furi_hal_crypto_store_unload_key(key_slot); + furi_hal_crypto_enclave_unload_key(key_slot); } else { if(!flipper_format_read_hex(flipper_format, "Data", cert_key, 32)) { FURI_LOG_E(TAG, "Missing data"); @@ -292,7 +292,7 @@ bool u2f_data_key_load(uint8_t* device_key) { FURI_LOG_E(TAG, "Missing data"); break; } - if(!furi_hal_crypto_store_load_key(U2F_DATA_FILE_ENCRYPTION_KEY_SLOT_UNIQUE, iv)) { + if(!furi_hal_crypto_enclave_load_key(U2F_DATA_FILE_ENCRYPTION_KEY_SLOT_UNIQUE, iv)) { FURI_LOG_E(TAG, "Unable to load encryption key"); break; } @@ -302,7 +302,7 @@ bool u2f_data_key_load(uint8_t* device_key) { FURI_LOG_E(TAG, "Decryption failed"); break; } - furi_hal_crypto_store_unload_key(U2F_DATA_FILE_ENCRYPTION_KEY_SLOT_UNIQUE); + furi_hal_crypto_enclave_unload_key(U2F_DATA_FILE_ENCRYPTION_KEY_SLOT_UNIQUE); state = true; } while(0); } @@ -324,7 +324,7 @@ bool u2f_data_key_generate(uint8_t* device_key) { furi_hal_random_fill_buf(iv, 16); furi_hal_random_fill_buf(key, 32); - if(!furi_hal_crypto_store_load_key(U2F_DATA_FILE_ENCRYPTION_KEY_SLOT_UNIQUE, iv)) { + if(!furi_hal_crypto_enclave_load_key(U2F_DATA_FILE_ENCRYPTION_KEY_SLOT_UNIQUE, iv)) { FURI_LOG_E(TAG, "Unable to load encryption key"); return false; } @@ -333,7 +333,7 @@ bool u2f_data_key_generate(uint8_t* device_key) { FURI_LOG_E(TAG, "Encryption failed"); return false; } - furi_hal_crypto_store_unload_key(U2F_DATA_FILE_ENCRYPTION_KEY_SLOT_UNIQUE); + furi_hal_crypto_enclave_unload_key(U2F_DATA_FILE_ENCRYPTION_KEY_SLOT_UNIQUE); Storage* storage = furi_record_open(RECORD_STORAGE); FlipperFormat* flipper_format = flipper_format_file_alloc(storage); @@ -398,7 +398,7 @@ bool u2f_data_cnt_read(uint32_t* cnt_val) { FURI_LOG_E(TAG, "Missing data"); break; } - if(!furi_hal_crypto_store_load_key(U2F_DATA_FILE_ENCRYPTION_KEY_SLOT_UNIQUE, iv)) { + if(!furi_hal_crypto_enclave_load_key(U2F_DATA_FILE_ENCRYPTION_KEY_SLOT_UNIQUE, iv)) { FURI_LOG_E(TAG, "Unable to load encryption key"); break; } @@ -408,7 +408,7 @@ bool u2f_data_cnt_read(uint32_t* cnt_val) { FURI_LOG_E(TAG, "Decryption failed"); break; } - furi_hal_crypto_store_unload_key(U2F_DATA_FILE_ENCRYPTION_KEY_SLOT_UNIQUE); + furi_hal_crypto_enclave_unload_key(U2F_DATA_FILE_ENCRYPTION_KEY_SLOT_UNIQUE); if(cnt.control == U2F_COUNTER_CONTROL_VAL) { *cnt_val = cnt.counter; state = true; @@ -440,7 +440,7 @@ bool u2f_data_cnt_write(uint32_t cnt_val) { cnt.control = U2F_COUNTER_CONTROL_VAL; cnt.counter = cnt_val; - if(!furi_hal_crypto_store_load_key(U2F_DATA_FILE_ENCRYPTION_KEY_SLOT_UNIQUE, iv)) { + if(!furi_hal_crypto_enclave_load_key(U2F_DATA_FILE_ENCRYPTION_KEY_SLOT_UNIQUE, iv)) { FURI_LOG_E(TAG, "Unable to load encryption key"); return false; } @@ -449,7 +449,7 @@ bool u2f_data_cnt_write(uint32_t cnt_val) { FURI_LOG_E(TAG, "Encryption failed"); return false; } - furi_hal_crypto_store_unload_key(U2F_DATA_FILE_ENCRYPTION_KEY_SLOT_UNIQUE); + furi_hal_crypto_enclave_unload_key(U2F_DATA_FILE_ENCRYPTION_KEY_SLOT_UNIQUE); Storage* storage = furi_record_open(RECORD_STORAGE); FlipperFormat* flipper_format = flipper_format_file_alloc(storage); diff --git a/applications/services/crypto/crypto_cli.c b/applications/services/crypto/crypto_cli.c index a286f44571d..af52b84fae3 100644 --- a/applications/services/crypto/crypto_cli.c +++ b/applications/services/crypto/crypto_cli.c @@ -33,7 +33,7 @@ void crypto_cli_encrypt(Cli* cli, FuriString* args) { break; } - if(!furi_hal_crypto_store_load_key(key_slot, iv)) { + if(!furi_hal_crypto_enclave_load_key(key_slot, iv)) { printf("Unable to load key from slot %d", key_slot); break; } @@ -88,7 +88,7 @@ void crypto_cli_encrypt(Cli* cli, FuriString* args) { } while(0); if(key_loaded) { - furi_hal_crypto_store_unload_key(key_slot); + furi_hal_crypto_enclave_unload_key(key_slot); } } @@ -108,7 +108,7 @@ void crypto_cli_decrypt(Cli* cli, FuriString* args) { break; } - if(!furi_hal_crypto_store_load_key(key_slot, iv)) { + if(!furi_hal_crypto_enclave_load_key(key_slot, iv)) { printf("Unable to load key from slot %d", key_slot); break; } @@ -160,7 +160,7 @@ void crypto_cli_decrypt(Cli* cli, FuriString* args) { } while(0); if(key_loaded) { - furi_hal_crypto_store_unload_key(key_slot); + furi_hal_crypto_enclave_unload_key(key_slot); } } @@ -175,14 +175,14 @@ void crypto_cli_has_key(Cli* cli, FuriString* args) { break; } - if(!furi_hal_crypto_store_load_key(key_slot, iv)) { + if(!furi_hal_crypto_enclave_load_key(key_slot, iv)) { printf("Unable to load key from slot %d", key_slot); break; } printf("Successfully loaded key from slot %d", key_slot); - furi_hal_crypto_store_unload_key(key_slot); + furi_hal_crypto_enclave_unload_key(key_slot); } while(0); } @@ -251,25 +251,25 @@ void crypto_cli_store_key(Cli* cli, FuriString* args) { if(key_slot > 0) { uint8_t iv[16] = {0}; if(key_slot > 1) { - if(!furi_hal_crypto_store_load_key(key_slot - 1, iv)) { + if(!furi_hal_crypto_enclave_load_key(key_slot - 1, iv)) { printf( "Slot %d before %d is empty, which is not allowed", key_slot - 1, key_slot); break; } - furi_hal_crypto_store_unload_key(key_slot - 1); + furi_hal_crypto_enclave_unload_key(key_slot - 1); } - if(furi_hal_crypto_store_load_key(key_slot, iv)) { - furi_hal_crypto_store_unload_key(key_slot); + if(furi_hal_crypto_enclave_load_key(key_slot, iv)) { + furi_hal_crypto_enclave_unload_key(key_slot); printf("Key slot %d is already used", key_slot); break; } } uint8_t slot; - if(furi_hal_crypto_store_add_key(&key, &slot)) { + if(furi_hal_crypto_enclave_store_key(&key, &slot)) { printf("Success. Stored to slot: %d", slot); } else { printf("Failure"); diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index 529477b50a5..081fe502f98 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,34.4,, +Version,+,35.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -1036,14 +1036,20 @@ Function,+,furi_hal_cortex_instructions_per_microsecond,uint32_t, Function,+,furi_hal_cortex_timer_get,FuriHalCortexTimer,uint32_t Function,+,furi_hal_cortex_timer_is_expired,_Bool,FuriHalCortexTimer Function,+,furi_hal_cortex_timer_wait,void,FuriHalCortexTimer +Function,+,furi_hal_crypto_ctr,_Bool,"const uint8_t*, const uint8_t*, const uint8_t*, uint8_t*, size_t" Function,+,furi_hal_crypto_decrypt,_Bool,"const uint8_t*, uint8_t*, size_t" Function,+,furi_hal_crypto_encrypt,_Bool,"const uint8_t*, uint8_t*, size_t" +Function,+,furi_hal_crypto_gcm,_Bool,"const uint8_t*, const uint8_t*, const uint8_t*, size_t, const uint8_t*, uint8_t*, size_t, uint8_t*, _Bool" +Function,+,furi_hal_crypto_gcm_decrypt_and_verify,FuriHalCryptoGCMState,"const uint8_t*, const uint8_t*, const uint8_t*, size_t, const uint8_t*, uint8_t*, size_t, const uint8_t*" +Function,+,furi_hal_crypto_gcm_encrypt_and_tag,FuriHalCryptoGCMState,"const uint8_t*, const uint8_t*, const uint8_t*, size_t, const uint8_t*, uint8_t*, size_t, uint8_t*" Function,-,furi_hal_crypto_init,void, -Function,+,furi_hal_crypto_store_add_key,_Bool,"FuriHalCryptoKey*, uint8_t*" -Function,+,furi_hal_crypto_store_load_key,_Bool,"uint8_t, const uint8_t*" -Function,+,furi_hal_crypto_store_unload_key,_Bool,uint8_t -Function,+,furi_hal_crypto_verify_enclave,_Bool,"uint8_t*, uint8_t*" -Function,+,furi_hal_crypto_verify_key,_Bool,uint8_t +Function,+,furi_hal_crypto_load_key,_Bool,"const uint8_t*, const uint8_t*" +Function,+,furi_hal_crypto_enclave_store_key,_Bool,"FuriHalCryptoKey*, uint8_t*" +Function,+,furi_hal_crypto_enclave_load_key,_Bool,"uint8_t, const uint8_t*" +Function,+,furi_hal_crypto_enclave_unload_key,_Bool,uint8_t +Function,+,furi_hal_crypto_unload_key,_Bool, +Function,+,furi_hal_crypto_enclave_verify,_Bool,"uint8_t*, uint8_t*" +Function,+,furi_hal_crypto_enclave_ensure_key,_Bool,uint8_t Function,+,furi_hal_debug_disable,void, Function,+,furi_hal_debug_enable,void, Function,+,furi_hal_debug_is_gdb_session_active,_Bool, diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 6fc4356c54f..535582c0c97 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,34.4,, +Version,+,35.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -1107,14 +1107,20 @@ Function,+,furi_hal_cortex_instructions_per_microsecond,uint32_t, Function,+,furi_hal_cortex_timer_get,FuriHalCortexTimer,uint32_t Function,+,furi_hal_cortex_timer_is_expired,_Bool,FuriHalCortexTimer Function,+,furi_hal_cortex_timer_wait,void,FuriHalCortexTimer +Function,+,furi_hal_crypto_ctr,_Bool,"const uint8_t*, const uint8_t*, const uint8_t*, uint8_t*, size_t" Function,+,furi_hal_crypto_decrypt,_Bool,"const uint8_t*, uint8_t*, size_t" Function,+,furi_hal_crypto_encrypt,_Bool,"const uint8_t*, uint8_t*, size_t" +Function,+,furi_hal_crypto_gcm,_Bool,"const uint8_t*, const uint8_t*, const uint8_t*, size_t, const uint8_t*, uint8_t*, size_t, uint8_t*, _Bool" +Function,+,furi_hal_crypto_gcm_decrypt_and_verify,FuriHalCryptoGCMState,"const uint8_t*, const uint8_t*, const uint8_t*, size_t, const uint8_t*, uint8_t*, size_t, const uint8_t*" +Function,+,furi_hal_crypto_gcm_encrypt_and_tag,FuriHalCryptoGCMState,"const uint8_t*, const uint8_t*, const uint8_t*, size_t, const uint8_t*, uint8_t*, size_t, uint8_t*" Function,-,furi_hal_crypto_init,void, -Function,+,furi_hal_crypto_store_add_key,_Bool,"FuriHalCryptoKey*, uint8_t*" -Function,+,furi_hal_crypto_store_load_key,_Bool,"uint8_t, const uint8_t*" -Function,+,furi_hal_crypto_store_unload_key,_Bool,uint8_t -Function,+,furi_hal_crypto_verify_enclave,_Bool,"uint8_t*, uint8_t*" -Function,+,furi_hal_crypto_verify_key,_Bool,uint8_t +Function,+,furi_hal_crypto_load_key,_Bool,"const uint8_t*, const uint8_t*" +Function,+,furi_hal_crypto_enclave_store_key,_Bool,"FuriHalCryptoKey*, uint8_t*" +Function,+,furi_hal_crypto_enclave_load_key,_Bool,"uint8_t, const uint8_t*" +Function,+,furi_hal_crypto_enclave_unload_key,_Bool,uint8_t +Function,+,furi_hal_crypto_unload_key,_Bool, +Function,+,furi_hal_crypto_enclave_verify,_Bool,"uint8_t*, uint8_t*" +Function,+,furi_hal_crypto_enclave_ensure_key,_Bool,uint8_t Function,+,furi_hal_debug_disable,void, Function,+,furi_hal_debug_enable,void, Function,+,furi_hal_debug_is_gdb_session_active,_Bool, diff --git a/firmware/targets/f7/furi_hal/furi_hal_crypto.c b/firmware/targets/f7/furi_hal/furi_hal_crypto.c index eb5c3b782c8..b9a0feec99f 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_crypto.c +++ b/firmware/targets/f7/furi_hal/furi_hal_crypto.c @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -13,7 +14,7 @@ #define ENCLAVE_SIGNATURE_SIZE 16 #define CRYPTO_BLK_LEN (4 * sizeof(uint32_t)) -#define CRYPTO_TIMEOUT (1000) +#define CRYPTO_TIMEOUT_US (1000000) #define CRYPTO_MODE_ENCRYPT 0U #define CRYPTO_MODE_INIT (AES_CR_MODE_0) @@ -24,6 +25,19 @@ #define CRYPTO_KEYSIZE_256B (AES_CR_KEYSIZE) #define CRYPTO_AES_CBC (AES_CR_CHMOD_0) +#define CRYPTO_AES_CTR (AES_CR_CHMOD_1) +#define CRYPTO_CTR_IV_LEN (12U) +#define CRYPTO_CTR_CTR_LEN (4U) + +#define CRYPTO_AES_GCM (AES_CR_CHMOD_1 | AES_CR_CHMOD_0) +#define CRYPTO_GCM_IV_LEN (12U) +#define CRYPTO_GCM_CTR_LEN (4U) +#define CRYPTO_GCM_TAG_LEN (16U) +#define CRYPTO_GCM_PH_INIT (0x0U << AES_CR_GCMPH_Pos) +#define CRYPTO_GCM_PH_HEADER (AES_CR_GCMPH_0) +#define CRYPTO_GCM_PH_PAYLOAD (AES_CR_GCMPH_1) +#define CRYPTO_GCM_PH_FINAL (AES_CR_GCMPH_1 | AES_CR_GCMPH_0) + static FuriMutex* furi_hal_crypto_mutex = NULL; static bool furi_hal_crypto_mode_init_done = false; @@ -80,7 +94,7 @@ static bool furi_hal_crypto_generate_unique_keys(uint8_t start_slot, uint8_t end key.size = FuriHalCryptoKeySize256; key.data = key_data; furi_hal_random_fill_buf(key_data, 32); - if(!furi_hal_crypto_store_add_key(&key, &slot)) { + if(!furi_hal_crypto_enclave_store_key(&key, &slot)) { FURI_LOG_E(TAG, "Error writing key to slot %u", slot); return false; } @@ -88,21 +102,21 @@ static bool furi_hal_crypto_generate_unique_keys(uint8_t start_slot, uint8_t end return true; } -bool furi_hal_crypto_verify_key(uint8_t key_slot) { +bool furi_hal_crypto_enclave_ensure_key(uint8_t key_slot) { uint8_t keys_nb = 0; uint8_t valid_keys_nb = 0; uint8_t last_valid_slot = ENCLAVE_FACTORY_KEY_SLOTS; uint8_t empty_iv[16] = {0}; - furi_hal_crypto_verify_enclave(&keys_nb, &valid_keys_nb); + furi_hal_crypto_enclave_verify(&keys_nb, &valid_keys_nb); if(key_slot <= ENCLAVE_FACTORY_KEY_SLOTS) { // It's a factory key if(key_slot > keys_nb) return false; } else { // Unique key if(keys_nb < ENCLAVE_FACTORY_KEY_SLOTS) // Some factory keys are missing return false; for(uint8_t i = key_slot; i > ENCLAVE_FACTORY_KEY_SLOTS; i--) { - if(furi_hal_crypto_store_load_key(i, empty_iv)) { + if(furi_hal_crypto_enclave_load_key(i, empty_iv)) { last_valid_slot = i; - furi_hal_crypto_store_unload_key(i); + furi_hal_crypto_enclave_unload_key(i); break; } } @@ -114,14 +128,14 @@ bool furi_hal_crypto_verify_key(uint8_t key_slot) { return true; } -bool furi_hal_crypto_verify_enclave(uint8_t* keys_nb, uint8_t* valid_keys_nb) { +bool furi_hal_crypto_enclave_verify(uint8_t* keys_nb, uint8_t* valid_keys_nb) { furi_assert(keys_nb); furi_assert(valid_keys_nb); uint8_t keys = 0; uint8_t keys_valid = 0; uint8_t buffer[ENCLAVE_SIGNATURE_SIZE]; for(size_t key_slot = 0; key_slot < ENCLAVE_FACTORY_KEY_SLOTS; key_slot++) { - if(furi_hal_crypto_store_load_key(key_slot + 1, enclave_signature_iv[key_slot])) { + if(furi_hal_crypto_enclave_load_key(key_slot + 1, enclave_signature_iv[key_slot])) { keys++; if(furi_hal_crypto_encrypt( enclave_signature_input[key_slot], buffer, ENCLAVE_SIGNATURE_SIZE)) { @@ -129,7 +143,7 @@ bool furi_hal_crypto_verify_enclave(uint8_t* keys_nb, uint8_t* valid_keys_nb) { memcmp(buffer, enclave_signature_expected[key_slot], ENCLAVE_SIGNATURE_SIZE) == 0; } - furi_hal_crypto_store_unload_key(key_slot + 1); + furi_hal_crypto_enclave_unload_key(key_slot + 1); } } *keys_nb = keys; @@ -140,7 +154,7 @@ bool furi_hal_crypto_verify_enclave(uint8_t* keys_nb, uint8_t* valid_keys_nb) { return false; } -bool furi_hal_crypto_store_add_key(FuriHalCryptoKey* key, uint8_t* slot) { +bool furi_hal_crypto_enclave_store_key(FuriHalCryptoKey* key, uint8_t* slot) { furi_assert(key); furi_assert(slot); @@ -205,6 +219,16 @@ static void crypto_key_init(uint32_t* key, uint32_t* iv) { AES1->IVR0 = iv[3]; } +static bool furi_hal_crypto_wait_flag(uint32_t flag) { + FuriHalCortexTimer timer = furi_hal_cortex_timer_get(CRYPTO_TIMEOUT_US); + while(!READ_BIT(AES1->SR, flag)) { + if(furi_hal_cortex_timer_is_expired(timer)) { + return false; + } + } + return true; +} + static bool crypto_process_block(uint32_t* in, uint32_t* out, uint8_t blk_len) { furi_check((blk_len <= 4) && (blk_len > 0)); @@ -216,14 +240,8 @@ static bool crypto_process_block(uint32_t* in, uint32_t* out, uint8_t blk_len) { } } - uint32_t countdown = CRYPTO_TIMEOUT; - while(!READ_BIT(AES1->SR, AES_SR_CCF)) { - if(LL_SYSTICK_IsActiveCounterFlag()) { - countdown--; - } - if(countdown == 0) { - return false; - } + if(!furi_hal_crypto_wait_flag(AES_SR_CCF)) { + return false; } SET_BIT(AES1->CR, AES_CR_CCFC); @@ -237,7 +255,7 @@ static bool crypto_process_block(uint32_t* in, uint32_t* out, uint8_t blk_len) { return true; } -bool furi_hal_crypto_store_load_key(uint8_t slot, const uint8_t* iv) { +bool furi_hal_crypto_enclave_load_key(uint8_t slot, const uint8_t* iv) { furi_assert(slot > 0 && slot <= 100); furi_assert(furi_hal_crypto_mutex); furi_check(furi_mutex_acquire(furi_hal_crypto_mutex, FuriWaitForever) == FuriStatusOk); @@ -260,7 +278,7 @@ bool furi_hal_crypto_store_load_key(uint8_t slot, const uint8_t* iv) { } } -bool furi_hal_crypto_store_unload_key(uint8_t slot) { +bool furi_hal_crypto_enclave_unload_key(uint8_t slot) { if(!furi_hal_bt_is_alive()) { return false; } @@ -276,6 +294,27 @@ bool furi_hal_crypto_store_unload_key(uint8_t slot) { return (shci_state == SHCI_Success); } +bool furi_hal_crypto_load_key(const uint8_t* key, const uint8_t* iv) { + furi_assert(furi_hal_crypto_mutex); + furi_check(furi_mutex_acquire(furi_hal_crypto_mutex, FuriWaitForever) == FuriStatusOk); + + furi_hal_bus_enable(FuriHalBusAES1); + + furi_hal_crypto_mode_init_done = false; + crypto_key_init((uint32_t*)key, (uint32_t*)iv); + + return true; +} + +bool furi_hal_crypto_unload_key(void) { + CLEAR_BIT(AES1->CR, AES_CR_EN); + + furi_hal_bus_disable(FuriHalBusAES1); + + furi_check(furi_mutex_release(furi_hal_crypto_mutex) == FuriStatusOk); + return true; +} + bool furi_hal_crypto_encrypt(const uint8_t* input, uint8_t* output, size_t size) { bool state = false; @@ -307,14 +346,8 @@ bool furi_hal_crypto_decrypt(const uint8_t* input, uint8_t* output, size_t size) SET_BIT(AES1->CR, AES_CR_EN); - uint32_t countdown = CRYPTO_TIMEOUT; - while(!READ_BIT(AES1->SR, AES_SR_CCF)) { - if(LL_SYSTICK_IsActiveCounterFlag()) { - countdown--; - } - if(countdown == 0) { - return false; - } + if(!furi_hal_crypto_wait_flag(AES_SR_CCF)) { + return false; } SET_BIT(AES1->CR, AES_CR_CCFC); @@ -340,3 +373,360 @@ bool furi_hal_crypto_decrypt(const uint8_t* input, uint8_t* output, size_t size) return state; } + +static void crypto_key_init_bswap(uint32_t* key, uint32_t* iv, uint32_t chaining_mode) { + CLEAR_BIT(AES1->CR, AES_CR_EN); + MODIFY_REG( + AES1->CR, + AES_CR_DATATYPE | AES_CR_KEYSIZE | AES_CR_CHMOD, + CRYPTO_DATATYPE_32B | CRYPTO_KEYSIZE_256B | chaining_mode); + + if(key != NULL) { + AES1->KEYR7 = __builtin_bswap32(key[0]); + AES1->KEYR6 = __builtin_bswap32(key[1]); + AES1->KEYR5 = __builtin_bswap32(key[2]); + AES1->KEYR4 = __builtin_bswap32(key[3]); + AES1->KEYR3 = __builtin_bswap32(key[4]); + AES1->KEYR2 = __builtin_bswap32(key[5]); + AES1->KEYR1 = __builtin_bswap32(key[6]); + AES1->KEYR0 = __builtin_bswap32(key[7]); + } + + AES1->IVR3 = __builtin_bswap32(iv[0]); + AES1->IVR2 = __builtin_bswap32(iv[1]); + AES1->IVR1 = __builtin_bswap32(iv[2]); + AES1->IVR0 = __builtin_bswap32(iv[3]); +} + +static bool + furi_hal_crypto_load_key_bswap(const uint8_t* key, const uint8_t* iv, uint32_t chaining_mode) { + furi_assert(furi_hal_crypto_mutex); + furi_check(furi_mutex_acquire(furi_hal_crypto_mutex, FuriWaitForever) == FuriStatusOk); + + furi_hal_bus_enable(FuriHalBusAES1); + + crypto_key_init_bswap((uint32_t*)key, (uint32_t*)iv, chaining_mode); + + return true; +} + +static bool wait_for_crypto(void) { + if(!furi_hal_crypto_wait_flag(AES_SR_CCF)) { + return false; + } + + SET_BIT(AES1->CR, AES_CR_CCFC); + + return true; +} + +static bool furi_hal_crypto_process_block_bswap(const uint8_t* in, uint8_t* out, size_t bytes) { + uint32_t block[CRYPTO_BLK_LEN / 4]; + memset(block, 0, sizeof(block)); + + memcpy(block, in, bytes); + + block[0] = __builtin_bswap32(block[0]); + block[1] = __builtin_bswap32(block[1]); + block[2] = __builtin_bswap32(block[2]); + block[3] = __builtin_bswap32(block[3]); + + if(!crypto_process_block(block, block, CRYPTO_BLK_LEN / 4)) { + return false; + } + + block[0] = __builtin_bswap32(block[0]); + block[1] = __builtin_bswap32(block[1]); + block[2] = __builtin_bswap32(block[2]); + block[3] = __builtin_bswap32(block[3]); + + memcpy(out, block, bytes); + + return true; +} + +static bool furi_hal_crypto_process_block_no_read_bswap(const uint8_t* in, size_t bytes) { + uint32_t block[CRYPTO_BLK_LEN / 4]; + memset(block, 0, sizeof(block)); + + memcpy(block, in, bytes); + + AES1->DINR = __builtin_bswap32(block[0]); + AES1->DINR = __builtin_bswap32(block[1]); + AES1->DINR = __builtin_bswap32(block[2]); + AES1->DINR = __builtin_bswap32(block[3]); + + return wait_for_crypto(); +} + +static void furi_hal_crypto_ctr_prep_iv(uint8_t* iv) { + /* append counter to IV */ + iv[CRYPTO_CTR_IV_LEN] = 0; + iv[CRYPTO_CTR_IV_LEN + 1] = 0; + iv[CRYPTO_CTR_IV_LEN + 2] = 0; + iv[CRYPTO_CTR_IV_LEN + 3] = 1; +} + +static bool furi_hal_crypto_ctr_payload(const uint8_t* input, uint8_t* output, size_t length) { + SET_BIT(AES1->CR, AES_CR_EN); + MODIFY_REG(AES1->CR, AES_CR_MODE, CRYPTO_MODE_ENCRYPT); + + size_t last_block_bytes = length % CRYPTO_BLK_LEN; + + size_t i; + for(i = 0; i < length - last_block_bytes; i += CRYPTO_BLK_LEN) { + if(!furi_hal_crypto_process_block_bswap(&input[i], &output[i], CRYPTO_BLK_LEN)) { + CLEAR_BIT(AES1->CR, AES_CR_EN); + return false; + } + } + + if(last_block_bytes > 0) { + if(!furi_hal_crypto_process_block_bswap(&input[i], &output[i], last_block_bytes)) { + CLEAR_BIT(AES1->CR, AES_CR_EN); + return false; + } + } + + CLEAR_BIT(AES1->CR, AES_CR_EN); + return true; +} + +bool furi_hal_crypto_ctr( + const uint8_t* key, + const uint8_t* iv, + const uint8_t* input, + uint8_t* output, + size_t length) { + /* prepare IV and counter */ + uint8_t iv_and_counter[CRYPTO_CTR_IV_LEN + CRYPTO_CTR_CTR_LEN]; + memcpy(iv_and_counter, iv, CRYPTO_CTR_IV_LEN); + furi_hal_crypto_ctr_prep_iv(iv_and_counter); + + /* load key and IV and set the mode to CTR */ + if(!furi_hal_crypto_load_key_bswap(key, iv_and_counter, CRYPTO_AES_CTR)) { + furi_hal_crypto_unload_key(); + return false; + } + + /* process the input and write to output */ + bool state = furi_hal_crypto_ctr_payload(input, output, length); + + furi_hal_crypto_unload_key(); + + return state; +} + +static void furi_hal_crypto_gcm_prep_iv(uint8_t* iv) { + /* append counter to IV */ + iv[CRYPTO_GCM_IV_LEN] = 0; + iv[CRYPTO_GCM_IV_LEN + 1] = 0; + iv[CRYPTO_GCM_IV_LEN + 2] = 0; + iv[CRYPTO_GCM_IV_LEN + 3] = 2; +} + +static bool furi_hal_crypto_gcm_init(bool decrypt) { + /* GCM init phase */ + + MODIFY_REG(AES1->CR, AES_CR_GCMPH, CRYPTO_GCM_PH_INIT); + if(decrypt) { + MODIFY_REG(AES1->CR, AES_CR_MODE, CRYPTO_MODE_DECRYPT); + } else { + MODIFY_REG(AES1->CR, AES_CR_MODE, CRYPTO_MODE_ENCRYPT); + } + + SET_BIT(AES1->CR, AES_CR_EN); + + if(!wait_for_crypto()) { + CLEAR_BIT(AES1->CR, AES_CR_EN); + return false; + } + + return true; +} + +static bool furi_hal_crypto_gcm_header(const uint8_t* aad, size_t aad_length) { + /* GCM header phase */ + + MODIFY_REG(AES1->CR, AES_CR_GCMPH, CRYPTO_GCM_PH_HEADER); + SET_BIT(AES1->CR, AES_CR_EN); + + size_t last_block_bytes = aad_length % CRYPTO_BLK_LEN; + + size_t i; + for(i = 0; i < aad_length - last_block_bytes; i += CRYPTO_BLK_LEN) { + if(!furi_hal_crypto_process_block_no_read_bswap(&aad[i], CRYPTO_BLK_LEN)) { + CLEAR_BIT(AES1->CR, AES_CR_EN); + return false; + } + } + + if(last_block_bytes > 0) { + if(!furi_hal_crypto_process_block_no_read_bswap(&aad[i], last_block_bytes)) { + CLEAR_BIT(AES1->CR, AES_CR_EN); + return false; + } + } + + return true; +} + +static bool furi_hal_crypto_gcm_payload( + const uint8_t* input, + uint8_t* output, + size_t length, + bool decrypt) { + /* GCM payload phase */ + + MODIFY_REG(AES1->CR, AES_CR_GCMPH, CRYPTO_GCM_PH_PAYLOAD); + SET_BIT(AES1->CR, AES_CR_EN); + + size_t last_block_bytes = length % CRYPTO_BLK_LEN; + + size_t i; + for(i = 0; i < length - last_block_bytes; i += CRYPTO_BLK_LEN) { + if(!furi_hal_crypto_process_block_bswap(&input[i], &output[i], CRYPTO_BLK_LEN)) { + CLEAR_BIT(AES1->CR, AES_CR_EN); + return false; + } + } + + if(last_block_bytes > 0) { + if(!decrypt) { + MODIFY_REG( + AES1->CR, AES_CR_NPBLB, (CRYPTO_BLK_LEN - last_block_bytes) << AES_CR_NPBLB_Pos); + } + if(!furi_hal_crypto_process_block_bswap(&input[i], &output[i], last_block_bytes)) { + CLEAR_BIT(AES1->CR, AES_CR_EN); + return false; + } + } + + return true; +} + +static bool furi_hal_crypto_gcm_finish(size_t aad_length, size_t payload_length, uint8_t* tag) { + /* GCM final phase */ + + MODIFY_REG(AES1->CR, AES_CR_GCMPH, CRYPTO_GCM_PH_FINAL); + + uint32_t last_block[CRYPTO_BLK_LEN / 4]; + memset(last_block, 0, sizeof(last_block)); + last_block[1] = __builtin_bswap32((uint32_t)(aad_length * 8)); + last_block[3] = __builtin_bswap32((uint32_t)(payload_length * 8)); + + if(!furi_hal_crypto_process_block_bswap((uint8_t*)&last_block[0], tag, CRYPTO_BLK_LEN)) { + CLEAR_BIT(AES1->CR, AES_CR_EN); + return false; + } + + return true; +} + +static bool furi_hal_crypto_gcm_compare_tag(const uint8_t* tag1, const uint8_t* tag2) { + uint8_t diff = 0; + + size_t i; + for(i = 0; i < CRYPTO_GCM_TAG_LEN; i++) { + diff |= tag1[i] ^ tag2[i]; + } + + return (diff == 0); +} + +bool furi_hal_crypto_gcm( + const uint8_t* key, + const uint8_t* iv, + const uint8_t* aad, + size_t aad_length, + const uint8_t* input, + uint8_t* output, + size_t length, + uint8_t* tag, + bool decrypt) { + /* GCM init phase */ + + /* prepare IV and counter */ + uint8_t iv_and_counter[CRYPTO_GCM_IV_LEN + CRYPTO_GCM_CTR_LEN]; + memcpy(iv_and_counter, iv, CRYPTO_GCM_IV_LEN); + furi_hal_crypto_gcm_prep_iv(iv_and_counter); + + /* load key and IV and set the mode to CTR */ + if(!furi_hal_crypto_load_key_bswap(key, iv_and_counter, CRYPTO_AES_GCM)) { + furi_hal_crypto_unload_key(); + return false; + } + + if(!furi_hal_crypto_gcm_init(decrypt)) { + furi_hal_crypto_unload_key(); + return false; + } + + /* GCM header phase */ + + if(aad_length > 0) { + if(!furi_hal_crypto_gcm_header(aad, aad_length)) { + furi_hal_crypto_unload_key(); + return false; + } + } + + /* GCM payload phase */ + + if(!furi_hal_crypto_gcm_payload(input, output, length, decrypt)) { + furi_hal_crypto_unload_key(); + return false; + } + + /* GCM final phase */ + + if(!furi_hal_crypto_gcm_finish(aad_length, length, tag)) { + furi_hal_crypto_unload_key(); + return false; + } + + furi_hal_crypto_unload_key(); + return true; +} + +FuriHalCryptoGCMState furi_hal_crypto_gcm_encrypt_and_tag( + const uint8_t* key, + const uint8_t* iv, + const uint8_t* aad, + size_t aad_length, + const uint8_t* input, + uint8_t* output, + size_t length, + uint8_t* tag) { + if(!furi_hal_crypto_gcm(key, iv, aad, aad_length, input, output, length, tag, false)) { + memset(output, 0, length); + memset(tag, 0, CRYPTO_GCM_TAG_LEN); + return FuriHalCryptoGCMStateError; + } + + return FuriHalCryptoGCMStateOk; +} + +FuriHalCryptoGCMState furi_hal_crypto_gcm_decrypt_and_verify( + const uint8_t* key, + const uint8_t* iv, + const uint8_t* aad, + size_t aad_length, + const uint8_t* input, + uint8_t* output, + size_t length, + const uint8_t* tag) { + uint8_t dtag[CRYPTO_GCM_TAG_LEN]; + + if(!furi_hal_crypto_gcm(key, iv, aad, aad_length, input, output, length, dtag, true)) { + memset(output, 0, length); + return FuriHalCryptoGCMStateError; + } + + if(!furi_hal_crypto_gcm_compare_tag(dtag, tag)) { + memset(output, 0, length); + return FuriHalCryptoGCMStateAuthFailure; + } + + return FuriHalCryptoGCMStateOk; +} diff --git a/firmware/targets/f7/furi_hal/furi_hal_info.c b/firmware/targets/f7/furi_hal/furi_hal_info.c index cefb6a11bbf..4908cca69d1 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_info.c +++ b/firmware/targets/f7/furi_hal/furi_hal_info.c @@ -283,7 +283,7 @@ void furi_hal_info_get(PropertyValueCallback out, char sep, void* context) { // Signature verification uint8_t enclave_keys = 0; uint8_t enclave_valid_keys = 0; - bool enclave_valid = furi_hal_crypto_verify_enclave(&enclave_keys, &enclave_valid_keys); + bool enclave_valid = furi_hal_crypto_enclave_verify(&enclave_keys, &enclave_valid_keys); if(sep == '.') { property_value_out( &property_context, "%d", 3, "enclave", "keys", "valid", enclave_valid_keys); diff --git a/firmware/targets/furi_hal_include/furi_hal_crypto.h b/firmware/targets/furi_hal_include/furi_hal_crypto.h index 81788e96e11..5e3b4104041 100644 --- a/firmware/targets/furi_hal_include/furi_hal_crypto.h +++ b/firmware/targets/furi_hal_include/furi_hal_crypto.h @@ -1,6 +1,40 @@ /** * @file furi_hal_crypto.h + * * Cryptography HAL API + * + * !!! READ THIS FIRST !!! + * + * Flipper was never designed to be secure, nor it passed cryptography audit. + * Despite of the fact that keys are stored in secure enclave there are some + * types of attack that can be performed against AES engine to recover + * keys(theoretical). Also there is no way to securely deliver user keys to + * device and never will be. In addition device is fully open and there is no + * way to guarantee safety of your data, it can be easily dumped with debugger + * or modified code. + * + * Secure enclave on WB series is implemented on core2 FUS side and can be used + * only if core2 alive. Enclave is responsible for storing, loading and + * unloading keys to and from enclave/AES in secure manner(AES engine key + * registers will be locked when key from enclave loaded) + * + * There are 11 keys that we provision at factory: + * - 0 - Master key for secure key delivery. Impossible to use for anything but + * key provisioning. We don't plan to use it too. + * - 1 - 10 - Keys used by firmware. All devices got the same set of keys. You + * also can use them in your applications. + * + * Also there is a slot 11 that we use for device unique key. This slot is + * intentionally left blank till the moment of first use, so you can ensure that + * we don't know your unique key. Also you can provision this key by your self + * with crypto cli or API. + * + * Other slots can be used for your needs. But since enclave is sequential + * append only, we can not guarantee you that slots you want are free. NEVER USE + * THEM FOR PUBLIC APPLICATIONS. + * + * Also you can directly load raw keys into AES engine and use it for your + * needs. */ #pragma once @@ -12,10 +46,27 @@ extern "C" { #endif +/** Factory provisioned master key slot. Should never be used. */ +#define FURI_HAL_CRYPTO_ENCLAVE_MASTER_KEY_SLOT (0u) + +/** Factory provisioned keys slot range. All of them are exactly same on all flippers. */ +#define FURI_HAL_CRYPTO_ENCLAVE_FACTORY_KEY_SLOT_START (1u) +#define FURI_HAL_CRYPTO_ENCLAVE_FACTORY_KEY_SLOT_END (10u) + +/** Device unique key slot. This key generated on first use or provisioned by user. Use furi_hal_crypto_enclave_ensure_key before using this slot. */ +#define FURI_HAL_CRYPTO_ENCLAVE_UNIQUE_KEY_SLOT (11u) + +/** User key slot range. This slots can be used for your needs, but never use them in public apps. */ +#define FURI_HAL_CRYPTO_ENCLAVE_USER_KEY_SLOT_START (12u) +#define FURI_HAL_CRYPTO_ENCLAVE_USER_KEY_SLOT_END (100u) + +/** [Deprecated] Indicates availability of advanced crypto functions, will be dropped before v1.0 */ +#define FURI_HAL_CRYPTO_ADVANCED_AVAIL 1 + /** FuriHalCryptoKey Type */ typedef enum { FuriHalCryptoKeyTypeMaster, /**< Master key */ - FuriHalCryptoKeyTypeSimple, /**< Simple enencrypted key */ + FuriHalCryptoKeyTypeSimple, /**< Simple unencrypted key */ FuriHalCryptoKeyTypeEncrypted, /**< Encrypted with Master key */ } FuriHalCryptoKeyType; @@ -32,40 +83,89 @@ typedef struct { uint8_t* data; } FuriHalCryptoKey; -/** Initialize cryptography layer This includes AES engines, PKA and RNG - */ +/** FuriHalCryptoGCMState Result of a GCM operation */ +typedef enum { + FuriHalCryptoGCMStateOk, /**< operation successful */ + FuriHalCryptoGCMStateError, /**< error during encryption/decryption */ + FuriHalCryptoGCMStateAuthFailure, /**< tags do not match, auth failed */ +} FuriHalCryptoGCMState; + +/** Initialize cryptography layer(includes AES engines, PKA and RNG) */ void furi_hal_crypto_init(); -bool furi_hal_crypto_verify_enclave(uint8_t* keys_nb, uint8_t* valid_keys_nb); +/** Verify factory provisioned keys + * + * @param keys_nb The keys number of + * @param valid_keys_nb The valid keys number of + * + * @return true if all enclave keys are intact, false otherwise + */ +bool furi_hal_crypto_enclave_verify(uint8_t* keys_nb, uint8_t* valid_keys_nb); + +/** Ensure that requested slot and slots before this slot contains keys. + * + * This function is used to provision FURI_HAL_CRYPTO_ENCLAVE_UNIQUE_KEY_SLOT. Also you + * may want to use it to generate some unique keys in user key slot range. + * + * @warning Because of the sequential nature of the secure enclave this + * method will generate key for all slots from + * FURI_HAL_CRYPTO_ENCLAVE_FACTORY_KEY_SLOT_END to the slot your requested. + * Keys are generated using on-chip RNG. + * + * @param[in] key_slot The key slot to enclave + * + * @return true if key exists or created, false if enclave corrupted + */ +bool furi_hal_crypto_enclave_ensure_key(uint8_t key_slot); + +/** Store key in crypto enclave + * + * @param key FuriHalCryptoKey to be stored + * @param slot pointer to int where enclave slot will be stored + * + * @return true on success + */ +bool furi_hal_crypto_enclave_store_key(FuriHalCryptoKey* key, uint8_t* slot); -bool furi_hal_crypto_verify_key(uint8_t key_slot); +/** Init AES engine and load key from crypto enclave + * + * @warning Use only with furi_hal_crypto_enclave_unload_key() + * + * @param slot enclave slot + * @param[in] iv pointer to 16 bytes Initialization Vector data + * + * @return true on success + */ +bool furi_hal_crypto_enclave_load_key(uint8_t slot, const uint8_t* iv); -/** Store key in crypto storage +/** Unload key and deinit AES engine * - * @param key FuriHalCryptoKey to store. Only Master, Simple or - * Encrypted - * @param slot pinter to int where store slot number will be saved + * @warning Use only with furi_hal_crypto_enclave_load_key() + * + * @param slot enclave slot * * @return true on success */ -bool furi_hal_crypto_store_add_key(FuriHalCryptoKey* key, uint8_t* slot); +bool furi_hal_crypto_enclave_unload_key(uint8_t slot); -/** Init AES engine and load key from crypto store +/** Init AES engine and load supplied key + * + * @warning Use only with furi_hal_crypto_unload_key() * - * @param slot store slot number + * @param[in] key pointer to 32 bytes key data * @param[in] iv pointer to 16 bytes Initialization Vector data * * @return true on success */ -bool furi_hal_crypto_store_load_key(uint8_t slot, const uint8_t* iv); +bool furi_hal_crypto_load_key(const uint8_t* key, const uint8_t* iv); -/** Unload key engine and deinit AES engine +/** Unload key and de-init AES engine * - * @param slot store slot number + * @warning Use this function only with furi_hal_crypto_load_key() * * @return true on success */ -bool furi_hal_crypto_store_unload_key(uint8_t slot); +bool furi_hal_crypto_unload_key(void); /** Encrypt data * @@ -87,6 +187,109 @@ bool furi_hal_crypto_encrypt(const uint8_t* input, uint8_t* output, size_t size) */ bool furi_hal_crypto_decrypt(const uint8_t* input, uint8_t* output, size_t size); +/** Encrypt the input using AES-CTR + * + * Decryption can be performed by supplying the ciphertext as input. Inits and + * deinits the AES engine internally. + * + * @param[in] key pointer to 32 bytes key data + * @param[in] iv pointer to 12 bytes Initialization Vector data + * @param[in] input pointer to input data + * @param[out] output pointer to output data + * @param length length of the input and output in bytes + * + * @return true on success + */ +bool furi_hal_crypto_ctr( + const uint8_t* key, + const uint8_t* iv, + const uint8_t* input, + uint8_t* output, + size_t length); + +/** Encrypt/decrypt the input using AES-GCM + * + * When decrypting the tag generated needs to be compared to the tag attached to + * the ciphertext in a constant-time fashion. If the tags are not equal, the + * decryption failed and the plaintext returned needs to be discarded. Inits and + * deinits the AES engine internally. + * + * @param[in] key pointer to 32 bytes key data + * @param[in] iv pointer to 12 bytes Initialization Vector data + * @param[in] aad pointer to additional authentication data + * @param aad_length length of the additional authentication data in bytes + * @param[in] input pointer to input data + * @param[out] output pointer to output data + * @param length length of the input and output in bytes + * @param[out] tag pointer to 16 bytes space for the tag + * @param decrypt true for decryption, false otherwise + * + * @return true on success + */ +bool furi_hal_crypto_gcm( + const uint8_t* key, + const uint8_t* iv, + const uint8_t* aad, + size_t aad_length, + const uint8_t* input, + uint8_t* output, + size_t length, + uint8_t* tag, + bool decrypt); + +/** Encrypt the input using AES-GCM and generate a tag + * + * Inits and deinits the AES engine internally. + * + * @param[in] key pointer to 32 bytes key data + * @param[in] iv pointer to 12 bytes Initialization Vector data + * @param[in] aad pointer to additional authentication data + * @param aad_length length of the additional authentication data in bytes + * @param[in] input pointer to input data + * @param[out] output pointer to output data + * @param length length of the input and output in bytes + * @param[out] tag pointer to 16 bytes space for the tag + * + * @return FuriHalCryptoGCMStateOk on success, FuriHalCryptoGCMStateError on + * failure + */ +FuriHalCryptoGCMState furi_hal_crypto_gcm_encrypt_and_tag( + const uint8_t* key, + const uint8_t* iv, + const uint8_t* aad, + size_t aad_length, + const uint8_t* input, + uint8_t* output, + size_t length, + uint8_t* tag); + +/** Decrypt the input using AES-GCM and verify the provided tag + * + * Inits and deinits the AES engine internally. + * + * @param[in] key pointer to 32 bytes key data + * @param[in] iv pointer to 12 bytes Initialization Vector data + * @param[in] aad pointer to additional authentication data + * @param aad_length length of the additional authentication data in bytes + * @param[in] input pointer to input data + * @param[out] output pointer to output data + * @param length length of the input and output in bytes + * @param[out] tag pointer to 16 bytes tag + * + * @return FuriHalCryptoGCMStateOk on success, FuriHalCryptoGCMStateError on + * failure, FuriHalCryptoGCMStateAuthFailure if the tag does not + * match + */ +FuriHalCryptoGCMState furi_hal_crypto_gcm_decrypt_and_verify( + const uint8_t* key, + const uint8_t* iv, + const uint8_t* aad, + size_t aad_length, + const uint8_t* input, + uint8_t* output, + size_t length, + const uint8_t* tag); + #ifdef __cplusplus } #endif diff --git a/lib/subghz/subghz_keystore.c b/lib/subghz/subghz_keystore.c index e0b1cf6ca3d..4f602d2e276 100644 --- a/lib/subghz/subghz_keystore.c +++ b/lib/subghz/subghz_keystore.c @@ -116,7 +116,7 @@ static bool subghz_keystore_read_file(SubGhzKeystore* instance, Stream* stream, do { if(iv) { - if(!furi_hal_crypto_store_load_key(SUBGHZ_KEYSTORE_FILE_ENCRYPTION_KEY_SLOT, iv)) { + if(!furi_hal_crypto_enclave_load_key(SUBGHZ_KEYSTORE_FILE_ENCRYPTION_KEY_SLOT, iv)) { FURI_LOG_E(TAG, "Unable to load decryption key"); break; } @@ -175,7 +175,7 @@ static bool subghz_keystore_read_file(SubGhzKeystore* instance, Stream* stream, } } while(ret > 0 && result); - if(iv) furi_hal_crypto_store_unload_key(SUBGHZ_KEYSTORE_FILE_ENCRYPTION_KEY_SLOT); + if(iv) furi_hal_crypto_enclave_unload_key(SUBGHZ_KEYSTORE_FILE_ENCRYPTION_KEY_SLOT); } while(false); free(encrypted_line); @@ -274,7 +274,7 @@ bool subghz_keystore_save(SubGhzKeystore* instance, const char* file_name, uint8 subghz_keystore_mess_with_iv(iv); - if(!furi_hal_crypto_store_load_key(SUBGHZ_KEYSTORE_FILE_ENCRYPTION_KEY_SLOT, iv)) { + if(!furi_hal_crypto_enclave_load_key(SUBGHZ_KEYSTORE_FILE_ENCRYPTION_KEY_SLOT, iv)) { FURI_LOG_E(TAG, "Unable to load encryption key"); break; } @@ -320,7 +320,7 @@ bool subghz_keystore_save(SubGhzKeystore* instance, const char* file_name, uint8 stream_write_char(stream, '\n'); encrypted_line_count++; } - furi_hal_crypto_store_unload_key(SUBGHZ_KEYSTORE_FILE_ENCRYPTION_KEY_SLOT); + furi_hal_crypto_enclave_unload_key(SUBGHZ_KEYSTORE_FILE_ENCRYPTION_KEY_SLOT); size_t total_keys = SubGhzKeyArray_size(instance->data); result = encrypted_line_count == total_keys; if(result) { @@ -415,7 +415,7 @@ bool subghz_keystore_raw_encrypted_save( subghz_keystore_mess_with_iv(iv); - if(!furi_hal_crypto_store_load_key(SUBGHZ_KEYSTORE_FILE_ENCRYPTION_KEY_SLOT, iv)) { + if(!furi_hal_crypto_enclave_load_key(SUBGHZ_KEYSTORE_FILE_ENCRYPTION_KEY_SLOT, iv)) { FURI_LOG_E(TAG, "Unable to load encryption key"); break; } @@ -468,7 +468,7 @@ bool subghz_keystore_raw_encrypted_save( flipper_format_free(output_flipper_format); - furi_hal_crypto_store_unload_key(SUBGHZ_KEYSTORE_FILE_ENCRYPTION_KEY_SLOT); + furi_hal_crypto_enclave_unload_key(SUBGHZ_KEYSTORE_FILE_ENCRYPTION_KEY_SLOT); if(!result) break; @@ -570,7 +570,7 @@ bool subghz_keystore_raw_get_data(const char* file_name, size_t offset, uint8_t* } } - if(!furi_hal_crypto_store_load_key(SUBGHZ_KEYSTORE_FILE_ENCRYPTION_KEY_SLOT, iv)) { + if(!furi_hal_crypto_enclave_load_key(SUBGHZ_KEYSTORE_FILE_ENCRYPTION_KEY_SLOT, iv)) { FURI_LOG_E(TAG, "Unable to load encryption key"); break; } @@ -598,7 +598,7 @@ bool subghz_keystore_raw_get_data(const char* file_name, size_t offset, uint8_t* memcpy(data, (uint8_t*)decrypted_line + (offset - (offset / 16) * 16), len); } while(0); - furi_hal_crypto_store_unload_key(SUBGHZ_KEYSTORE_FILE_ENCRYPTION_KEY_SLOT); + furi_hal_crypto_enclave_unload_key(SUBGHZ_KEYSTORE_FILE_ENCRYPTION_KEY_SLOT); if(decrypted) result = true; } while(0); flipper_format_free(flipper_format); From 2702c00ba4c4f41075adca24bdb9d1899d7f5c37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Thu, 10 Aug 2023 18:45:17 +0900 Subject: [PATCH 702/824] Scripts: OB recovery (#2964) * Scripts: OB recovery * Scripts: slightly different ob * Scripts: remove excessive return * Scripts: simplifying work with registers * Make PVS happy Co-authored-by: SG --- .../targets/f7/furi_hal/furi_hal_crypto.c | 4 +- scripts/flipper/utils/programmer.py | 4 + scripts/flipper/utils/programmer_openocd.py | 55 ++++--- scripts/flipper/utils/register.py | 17 ++- scripts/flipper/utils/stm32wb55.py | 135 ++++++++++-------- scripts/ob.py | 20 +++ 6 files changed, 149 insertions(+), 86 deletions(-) diff --git a/firmware/targets/f7/furi_hal/furi_hal_crypto.c b/firmware/targets/f7/furi_hal/furi_hal_crypto.c index b9a0feec99f..a897648a354 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_crypto.c +++ b/firmware/targets/f7/furi_hal/furi_hal_crypto.c @@ -500,7 +500,7 @@ bool furi_hal_crypto_ctr( size_t length) { /* prepare IV and counter */ uint8_t iv_and_counter[CRYPTO_CTR_IV_LEN + CRYPTO_CTR_CTR_LEN]; - memcpy(iv_and_counter, iv, CRYPTO_CTR_IV_LEN); + memcpy(iv_and_counter, iv, CRYPTO_CTR_IV_LEN); //-V1086 furi_hal_crypto_ctr_prep_iv(iv_and_counter); /* load key and IV and set the mode to CTR */ @@ -648,7 +648,7 @@ bool furi_hal_crypto_gcm( /* prepare IV and counter */ uint8_t iv_and_counter[CRYPTO_GCM_IV_LEN + CRYPTO_GCM_CTR_LEN]; - memcpy(iv_and_counter, iv, CRYPTO_GCM_IV_LEN); + memcpy(iv_and_counter, iv, CRYPTO_GCM_IV_LEN); //-V1086 furi_hal_crypto_gcm_prep_iv(iv_and_counter); /* load key and IV and set the mode to CTR */ diff --git a/scripts/flipper/utils/programmer.py b/scripts/flipper/utils/programmer.py index 84452d15415..938065f7c8d 100644 --- a/scripts/flipper/utils/programmer.py +++ b/scripts/flipper/utils/programmer.py @@ -26,6 +26,10 @@ def option_bytes_validate(self, file_path: str) -> bool: def option_bytes_set(self, file_path: str) -> bool: pass + @abstractmethod + def option_bytes_recover(self) -> bool: + pass + @abstractmethod def otp_write(self, address: int, file_path: str) -> bool: pass diff --git a/scripts/flipper/utils/programmer_openocd.py b/scripts/flipper/utils/programmer_openocd.py index 5a8029f379b..77335ee5bcd 100644 --- a/scripts/flipper/utils/programmer_openocd.py +++ b/scripts/flipper/utils/programmer_openocd.py @@ -44,11 +44,11 @@ def __init__( self.logger = logging.getLogger() def reset(self, mode: Programmer.RunMode = Programmer.RunMode.Run) -> bool: - stm32 = STM32WB55() + stm32 = STM32WB55(self.openocd) if mode == Programmer.RunMode.Run: - stm32.reset(self.openocd, stm32.RunMode.Run) + stm32.reset(stm32.RunMode.Run) elif mode == Programmer.RunMode.Stop: - stm32.reset(self.openocd, stm32.RunMode.Init) + stm32.reset(stm32.RunMode.Init) else: raise Exception("Unknown mode") @@ -96,11 +96,11 @@ def _ob_print_diff_table(self, ob_reference: bytes, ob_read: bytes, print_fn): def option_bytes_validate(self, file_path: str) -> bool: # Registers - stm32 = STM32WB55() + stm32 = STM32WB55(self.openocd) # OpenOCD self.openocd.start() - stm32.reset(self.openocd, stm32.RunMode.Init) + stm32.reset(stm32.RunMode.Init) # Generate Option Bytes data ob_data = OptionBytesData(file_path) @@ -133,7 +133,7 @@ def option_bytes_validate(self, file_path: str) -> bool: self._ob_print_diff_table(ob_reference, ob_compare, self.logger.error) # Stop OpenOCD - stm32.reset(self.openocd, stm32.RunMode.Run) + stm32.reset(stm32.RunMode.Run) self.openocd.stop() return return_code @@ -143,11 +143,11 @@ def _unpack_u32(self, data: bytes, offset: int): def option_bytes_set(self, file_path: str) -> bool: # Registers - stm32 = STM32WB55() + stm32 = STM32WB55(self.openocd) # OpenOCD self.openocd.start() - stm32.reset(self.openocd, stm32.RunMode.Init) + stm32.reset(stm32.RunMode.Init) # Generate Option Bytes data ob_data = OptionBytesData(file_path) @@ -159,11 +159,11 @@ def option_bytes_set(self, file_path: str) -> bool: ob_dwords = int(ob_length / 8) # Clear flash errors - stm32.clear_flash_errors(self.openocd) + stm32.clear_flash_errors() # Unlock Flash and Option Bytes - stm32.flash_unlock(self.openocd) - stm32.option_bytes_unlock(self.openocd) + stm32.flash_unlock() + stm32.option_bytes_unlock() ob_need_to_apply = False @@ -194,16 +194,16 @@ def option_bytes_set(self, file_path: str) -> bool: self.openocd.write_32(device_reg_addr, ob_value) if ob_need_to_apply: - stm32.option_bytes_apply(self.openocd) + stm32.option_bytes_apply() else: self.logger.info("Option Bytes are already correct") # Load Option Bytes # That will reset and also lock the Option Bytes and the Flash - stm32.option_bytes_load(self.openocd) + stm32.option_bytes_load() # Stop OpenOCD - stm32.reset(self.openocd, stm32.RunMode.Run) + stm32.reset(stm32.RunMode.Run) self.openocd.stop() return True @@ -233,11 +233,10 @@ def otp_write(self, address: int, file_path: str) -> OpenOCDProgrammerResult: self.logger.debug(f"Data: {data.hex().upper()}") # Start OpenOCD - oocd = self.openocd - oocd.start() + self.openocd.start() # Registers - stm32 = STM32WB55() + stm32 = STM32WB55(self.openocd) try: # Check that OTP is empty for the given address @@ -245,7 +244,7 @@ def otp_write(self, address: int, file_path: str) -> OpenOCDProgrammerResult: already_written = True for i in range(0, data_size, 4): file_word = int.from_bytes(data[i : i + 4], "little") - device_word = oocd.read_32(address + i) + device_word = self.openocd.read_32(address + i) if device_word != 0xFFFFFFFF and device_word != file_word: self.logger.error( f"OTP memory at {address + i:08X} is not empty: {device_word:08X}" @@ -260,7 +259,7 @@ def otp_write(self, address: int, file_path: str) -> OpenOCDProgrammerResult: return OpenOCDProgrammerResult.Success self.reset(self.RunMode.Stop) - stm32.clear_flash_errors(oocd) + stm32.clear_flash_errors() # Write OTP memory by 8 bytes for i in range(0, data_size, 8): @@ -269,14 +268,14 @@ def otp_write(self, address: int, file_path: str) -> OpenOCDProgrammerResult: self.logger.debug( f"Writing {word_1:08X} {word_2:08X} to {address + i:08X}" ) - stm32.write_flash_64(oocd, address + i, word_1, word_2) + stm32.write_flash_64(address + i, word_1, word_2) # Validate OTP memory validation_result = True for i in range(0, data_size, 4): file_word = int.from_bytes(data[i : i + 4], "little") - device_word = oocd.read_32(address + i) + device_word = self.openocd.read_32(address + i) if file_word != device_word: self.logger.error( f"Validation failed: {file_word:08X} != {device_word:08X} at {address + i:08X}" @@ -284,11 +283,21 @@ def otp_write(self, address: int, file_path: str) -> OpenOCDProgrammerResult: validation_result = False finally: # Stop OpenOCD - stm32.reset(oocd, stm32.RunMode.Run) - oocd.stop() + stm32.reset(stm32.RunMode.Run) + self.openocd.stop() return ( OpenOCDProgrammerResult.Success if validation_result else OpenOCDProgrammerResult.ErrorValidation ) + + def option_bytes_recover(self) -> bool: + try: + self.openocd.start() + stm32 = STM32WB55(self.openocd) + stm32.reset(stm32.RunMode.Halt) + stm32.option_bytes_recover() + return True + finally: + self.openocd.stop() diff --git a/scripts/flipper/utils/register.py b/scripts/flipper/utils/register.py index 26d66730c3a..aad75eaca2b 100644 --- a/scripts/flipper/utils/register.py +++ b/scripts/flipper/utils/register.py @@ -16,6 +16,7 @@ def __init__(self, address: int, definition_list: list[RegisterBitDefinition]): self.names = [definition.name for definition in definition_list] # typecheck self.address = address self.definition_list = definition_list + self.openocd = None # Validate that the definitions are not overlapping for i in range(len(definition_list)): @@ -76,6 +77,14 @@ def __setattr__(self, attr, value): def __dir__(self): return self.names + def set_openocd(self, openocd: OpenOCD): + self.openocd = openocd + + def get_openocd(self) -> OpenOCD: + if self.openocd is None: + raise RuntimeError("OpenOCD is not installed") + return self.openocd + def set(self, value: int): for definition in self.definition_list: definition.value = (value >> definition.offset) & ( @@ -88,8 +97,8 @@ def get(self) -> int: value |= definition.value << definition.offset return value - def load(self, openocd: OpenOCD): - self.set(openocd.read_32(self.address)) + def load(self): + self.set(self.get_openocd().read_32(self.address)) - def store(self, openocd: OpenOCD): - openocd.write_32(self.address, self.get()) + def store(self): + self.get_openocd().write_32(self.address, self.get()) diff --git a/scripts/flipper/utils/stm32wb55.py b/scripts/flipper/utils/stm32wb55.py index 52a5ec4e3eb..4a47b8beafe 100644 --- a/scripts/flipper/utils/stm32wb55.py +++ b/scripts/flipper/utils/stm32wb55.py @@ -108,23 +108,27 @@ class STM32WB55: 15: None, # Core 2 Options } - def __init__(self): + def __init__(self, openocd: OpenOCD): + self.openocd = openocd self.logger = logging.getLogger("STM32WB55") + self.FLASH_CR.set_openocd(self.openocd) + self.FLASH_SR.set_openocd(self.openocd) + class RunMode(Enum): Init = "init" Run = "run" Halt = "halt" - def reset(self, oocd: OpenOCD, mode: RunMode): + def reset(self, mode: RunMode): self.logger.debug("Resetting device") - oocd.send_tcl(f"reset {mode.value}") + self.openocd.send_tcl(f"reset {mode.value}") - def clear_flash_errors(self, oocd: OpenOCD): + def clear_flash_errors(self): # Errata 2.2.9: Flash OPTVERR flag is always set after system reset # And also clear all other flash error flags self.logger.debug("Resetting flash errors") - self.FLASH_SR.load(oocd) + self.FLASH_SR.load() self.FLASH_SR.OP_ERR = 1 self.FLASH_SR.PROG_ERR = 1 self.FLASH_SR.WRP_ERR = 1 @@ -135,51 +139,51 @@ def clear_flash_errors(self, oocd: OpenOCD): self.FLASH_SR.FAST_ERR = 1 self.FLASH_SR.RD_ERR = 1 self.FLASH_SR.OPTV_ERR = 1 - self.FLASH_SR.store(oocd) + self.FLASH_SR.store() - def flash_unlock(self, oocd: OpenOCD): + def flash_unlock(self): # Check if flash is already unlocked - self.FLASH_CR.load(oocd) + self.FLASH_CR.load() if self.FLASH_CR.LOCK == 0: self.logger.debug("Flash is already unlocked") return # Unlock flash self.logger.debug("Unlocking Flash") - oocd.write_32(self.FLASH_KEYR, self.FLASH_UNLOCK_KEY1) - oocd.write_32(self.FLASH_KEYR, self.FLASH_UNLOCK_KEY2) + self.openocd.write_32(self.FLASH_KEYR, self.FLASH_UNLOCK_KEY1) + self.openocd.write_32(self.FLASH_KEYR, self.FLASH_UNLOCK_KEY2) # Check if flash is unlocked - self.FLASH_CR.load(oocd) + self.FLASH_CR.load() if self.FLASH_CR.LOCK == 0: self.logger.debug("Flash unlocked") else: self.logger.error("Flash unlock failed") raise Exception("Flash unlock failed") - def option_bytes_unlock(self, oocd: OpenOCD): + def option_bytes_unlock(self): # Check if options is already unlocked - self.FLASH_CR.load(oocd) + self.FLASH_CR.load() if self.FLASH_CR.OPT_LOCK == 0: self.logger.debug("Options is already unlocked") return # Unlock options self.logger.debug("Unlocking Options") - oocd.write_32(self.FLASH_OPTKEYR, self.FLASH_UNLOCK_OPTKEY1) - oocd.write_32(self.FLASH_OPTKEYR, self.FLASH_UNLOCK_OPTKEY2) + self.openocd.write_32(self.FLASH_OPTKEYR, self.FLASH_UNLOCK_OPTKEY1) + self.openocd.write_32(self.FLASH_OPTKEYR, self.FLASH_UNLOCK_OPTKEY2) # Check if options is unlocked - self.FLASH_CR.load(oocd) + self.FLASH_CR.load() if self.FLASH_CR.OPT_LOCK == 0: self.logger.debug("Options unlocked") else: self.logger.error("Options unlock failed") raise Exception("Options unlock failed") - def option_bytes_lock(self, oocd: OpenOCD): + def option_bytes_lock(self): # Check if options is already locked - self.FLASH_CR.load(oocd) + self.FLASH_CR.load() if self.FLASH_CR.OPT_LOCK == 1: self.logger.debug("Options is already locked") return @@ -187,19 +191,19 @@ def option_bytes_lock(self, oocd: OpenOCD): # Lock options self.logger.debug("Locking Options") self.FLASH_CR.OPT_LOCK = 1 - self.FLASH_CR.store(oocd) + self.FLASH_CR.store() # Check if options is locked - self.FLASH_CR.load(oocd) + self.FLASH_CR.load() if self.FLASH_CR.OPT_LOCK == 1: self.logger.debug("Options locked") else: self.logger.error("Options lock failed") raise Exception("Options lock failed") - def flash_lock(self, oocd: OpenOCD): + def flash_lock(self): # Check if flash is already locked - self.FLASH_CR.load(oocd) + self.FLASH_CR.load() if self.FLASH_CR.LOCK == 1: self.logger.debug("Flash is already locked") return @@ -207,31 +211,31 @@ def flash_lock(self, oocd: OpenOCD): # Lock flash self.logger.debug("Locking Flash") self.FLASH_CR.LOCK = 1 - self.FLASH_CR.store(oocd) + self.FLASH_CR.store() # Check if flash is locked - self.FLASH_CR.load(oocd) + self.FLASH_CR.load() if self.FLASH_CR.LOCK == 1: self.logger.debug("Flash locked") else: self.logger.error("Flash lock failed") raise Exception("Flash lock failed") - def option_bytes_apply(self, oocd: OpenOCD): + def option_bytes_apply(self): self.logger.debug("Applying Option Bytes") - self.FLASH_CR.load(oocd) + self.FLASH_CR.load() self.FLASH_CR.OPT_STRT = 1 - self.FLASH_CR.store(oocd) + self.FLASH_CR.store() # Wait for Option Bytes to be applied - self.flash_wait_for_operation(oocd) + self.flash_wait_for_operation() - def option_bytes_load(self, oocd: OpenOCD): + def option_bytes_load(self): self.logger.debug("Loading Option Bytes") - self.FLASH_CR.load(oocd) + self.FLASH_CR.load() self.FLASH_CR.OBL_LAUNCH = 1 - self.FLASH_CR.store(oocd) + self.FLASH_CR.store() def option_bytes_id_to_address(self, id: int) -> int: # Check if this option byte (dword) is mapped to a register @@ -241,16 +245,16 @@ def option_bytes_id_to_address(self, id: int) -> int: return device_reg_addr - def flash_wait_for_operation(self, oocd: OpenOCD): + def flash_wait_for_operation(self): # Wait for flash operation to complete # TODO: timeout while True: - self.FLASH_SR.load(oocd) + self.FLASH_SR.load() if self.FLASH_SR.BSY == 0: break - def flash_dump_status_register(self, oocd: OpenOCD): - self.FLASH_SR.load(oocd) + def flash_dump_status_register(self): + self.FLASH_SR.load() self.logger.info(f"FLASH_SR: {self.FLASH_SR.get():08x}") if self.FLASH_SR.EOP: self.logger.info(" End of operation") @@ -283,70 +287,87 @@ def flash_dump_status_register(self, oocd: OpenOCD): if self.FLASH_SR.PESD: self.logger.info(" Programming / erase operation suspended.") - def write_flash_64(self, oocd: OpenOCD, address: int, word_1: int, word_2: int): + def write_flash_64(self, address: int, word_1: int, word_2: int): self.logger.debug(f"Writing flash at address {address:08x}") if address % 8 != 0: self.logger.error("Address must be aligned to 8 bytes") raise Exception("Address must be aligned to 8 bytes") - if word_1 == oocd.read_32(address) and word_2 == oocd.read_32(address + 4): + if word_1 == self.openocd.read_32(address) and word_2 == self.openocd.read_32( + address + 4 + ): self.logger.debug("Data is already programmed") return - self.flash_unlock(oocd) + self.flash_unlock() # Check that no flash main memory operation is ongoing by checking the BSY bit - self.FLASH_SR.load(oocd) + self.FLASH_SR.load() if self.FLASH_SR.BSY: self.logger.error("Flash is busy") - self.flash_dump_status_register(oocd) + self.flash_dump_status_register() raise Exception("Flash is busy") # Enable end of operation interrupts and error interrupts - self.FLASH_CR.load(oocd) + self.FLASH_CR.load() self.FLASH_CR.EOPIE = 1 self.FLASH_CR.ERRIE = 1 - self.FLASH_CR.store(oocd) + self.FLASH_CR.store() # Check that flash memory program and erase operations are allowed if self.FLASH_SR.PESD: self.logger.error("Flash operations are not allowed") - self.flash_dump_status_register(oocd) + self.flash_dump_status_register() raise Exception("Flash operations are not allowed") # Check and clear all error programming flags due to a previous programming. - self.clear_flash_errors(oocd) + self.clear_flash_errors() # Set the PG bit in the Flash memory control register (FLASH_CR) - self.FLASH_CR.load(oocd) + self.FLASH_CR.load() self.FLASH_CR.PG = 1 - self.FLASH_CR.store(oocd) + self.FLASH_CR.store() # Perform the data write operation at the desired memory address, only double word (64 bits) can be programmed. # Write the first word - oocd.send_tcl(f"mww 0x{address:08x} 0x{word_1:08x}") + self.openocd.send_tcl(f"mww 0x{address:08x} 0x{word_1:08x}") # Write the second word - oocd.send_tcl(f"mww 0x{(address + 4):08x} 0x{word_2:08x}") + self.openocd.send_tcl(f"mww 0x{(address + 4):08x} 0x{word_2:08x}") # Wait for the BSY bit to be cleared - self.flash_wait_for_operation(oocd) + self.flash_wait_for_operation() # Check that EOP flag is set in the FLASH_SR register - self.FLASH_SR.load(oocd) + self.FLASH_SR.load() if not self.FLASH_SR.EOP: self.logger.error("Flash operation failed") - self.flash_dump_status_register(oocd) + self.flash_dump_status_register() raise Exception("Flash operation failed") # Clear the EOP flag - self.FLASH_SR.load(oocd) + self.FLASH_SR.load() self.FLASH_SR.EOP = 1 - self.FLASH_SR.store(oocd) + self.FLASH_SR.store() # Clear the PG bit in the FLASH_CR register - self.FLASH_CR.load(oocd) + self.FLASH_CR.load() self.FLASH_CR.PG = 0 - self.FLASH_CR.store(oocd) - - self.flash_lock(oocd) + self.FLASH_CR.store() + + self.flash_lock() + + def option_bytes_recover(self): + self.openocd.send_tcl("mww 0x58004010 0x8000") # set OPTVERR to reset + # Replace flash_unlock and option_bytes_unlock with the following lines, if this does not work + # self.openocd.send_tcl("mww 0x58004008 0x45670123") # unlock FLASH + # self.openocd.send_tcl("mww 0x58004008 0xCDEF89AB") + # self.openocd.send_tcl("mww 0x5800400c 0x08192A3B") # unlock OB + # self.openocd.send_tcl("mww 0x5800400c 0x4C5D6E7F") + self.flash_unlock() + self.option_bytes_unlock() + self.openocd.send_tcl("mmw 0x58004020 0x3ffff1aa 0xffffffff") # Reset OB + self.openocd.send_tcl("mww 0x5800402c 0xff") # Reset WRP1AR + self.openocd.send_tcl("mww 0x58004030 0xff") # Reset WRP1BR + self.openocd.send_tcl("mmw 0x58004014 0x00020000 0") # OPTSTRT + self.openocd.send_tcl("mmw 0x58004014 0x08000000 0") # OBL_LAUNCH diff --git a/scripts/ob.py b/scripts/ob.py index 7010bdec58b..b7a601612b3 100755 --- a/scripts/ob.py +++ b/scripts/ob.py @@ -22,6 +22,12 @@ def init(self): self.parser_set = self.subparsers.add_parser("set", help="Set Option Bytes") self._add_args(self.parser_set) self.parser_set.set_defaults(func=self.set) + # Set command + self.parser_recover = self.subparsers.add_parser( + "recover", help="Recover Option Bytes" + ) + self._add_args(self.parser_recover) + self.parser_recover.set_defaults(func=self.recover) def _add_args(self, parser): parser.add_argument( @@ -75,6 +81,20 @@ def set(self): return return_code + def recover(self): + self.logger.info("Setting Option Bytes") + + # OpenOCD + openocd = OpenOCDProgrammer( + self.args.interface, + self.args.port_base, + self.args.serial, + ) + + openocd.option_bytes_recover() + + return 0 + if __name__ == "__main__": Main()() From c40e4ba01e2ce8083e8c462d23ce2bd9b00ecc44 Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Thu, 10 Aug 2023 12:53:12 +0300 Subject: [PATCH 703/824] FBT: devboard_flash to update WiFi devboard (#2968) --- SConstruct | 3 +++ documentation/fbt.md | 1 + 2 files changed, 4 insertions(+) diff --git a/SConstruct b/SConstruct index b51154f7032..d968254b166 100644 --- a/SConstruct +++ b/SConstruct @@ -327,6 +327,9 @@ distenv.PhonyTarget( "cli", "${PYTHON3} ${FBT_SCRIPT_DIR}/serial_cli.py -p ${FLIP_PORT}" ) +# Update WiFi devboard firmware +distenv.PhonyTarget("devboard_flash", "${PYTHON3} ${FBT_SCRIPT_DIR}/wifi_board.py") + # Find blackmagic probe distenv.PhonyTarget( diff --git a/documentation/fbt.md b/documentation/fbt.md index c19780ef593..1ab67b4e607 100644 --- a/documentation/fbt.md +++ b/documentation/fbt.md @@ -69,6 +69,7 @@ To run cleanup (think of `make clean`) for specified targets, add the `-c` optio - `debug` - build and flash firmware, then attach with gdb with firmware's .elf loaded. - `debug_other`, `debug_other_blackmagic` - attach GDB without loading any `.elf`. It will allow you to manually add external `.elf` files with `add-symbol-file` in GDB. - `updater_debug` - attach GDB with the updater's `.elf` loaded. +- `devboard_flash` - update WiFi dev board with the latest firmware. - `blackmagic` - debug firmware with Blackmagic probe (WiFi dev board). - `openocd` - just start OpenOCD. - `get_blackmagic` - output the blackmagic address in the GDB remote format. Useful for IDE integration. From 498aee20a20d3e157c578780ab9df5fe05834632 Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Thu, 10 Aug 2023 15:29:44 +0300 Subject: [PATCH 704/824] uFBT: devboard_flash to update WiFi devboard (#2969) * uFBT: devboard_flash to update WiFi devboard * uFBT: help --- scripts/ufbt/SConstruct | 3 +++ scripts/ufbt/site_tools/ufbt_help.py | 2 ++ 2 files changed, 5 insertions(+) diff --git a/scripts/ufbt/SConstruct b/scripts/ufbt/SConstruct index 9a9e0938c1b..342cd253280 100644 --- a/scripts/ufbt/SConstruct +++ b/scripts/ufbt/SConstruct @@ -348,6 +348,9 @@ appenv.PhonyTarget( '${PYTHON3} "${FBT_SCRIPT_DIR}/serial_cli.py" -p ${FLIP_PORT}', ) +# Update WiFi devboard firmware +dist_env.PhonyTarget("devboard_flash", "${PYTHON3} ${FBT_SCRIPT_DIR}/wifi_board.py") + # Linter dist_env.PhonyTarget( diff --git a/scripts/ufbt/site_tools/ufbt_help.py b/scripts/ufbt/site_tools/ufbt_help.py index 3f13edcdbba..3d7f6f00214 100644 --- a/scripts/ufbt/site_tools/ufbt_help.py +++ b/scripts/ufbt/site_tools/ufbt_help.py @@ -26,6 +26,8 @@ Install firmware using self-update package debug, debug_other, blackmagic: Start GDB + devboard_flash: + Update WiFi dev board with the latest firmware Other: cli: From f75fcd4e34d921397c0fb23a1956ecd82af73b13 Mon Sep 17 00:00:00 2001 From: MMX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 10 Aug 2023 19:10:15 +0300 Subject: [PATCH 705/824] UI: Clock on Desktop (#2891) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Clock on desktop * Gui: gui_active_view_port_count * Gui: move gui_active_view_port_count to private header, update docs * Desktop: simplify desktop clock code * Desktop: refactor clock * Desktop: optimize clock code * Desktop: 3rd cleanup round * Desktop: 4th cleanup round, missing bits and pieces Co-authored-by: hedger Co-authored-by: あく --- applications/services/desktop/desktop.c | 91 ++++++++++++++++++- applications/services/desktop/desktop_i.h | 8 +- .../services/desktop/desktop_settings.h | 3 +- applications/services/gui/gui.c | 20 ++++ applications/services/gui/gui_i.h | 28 ++++++ .../scenes/desktop_settings_scene_start.c | 30 ++++++ firmware/targets/f18/api_symbols.csv | 1 + firmware/targets/f7/api_symbols.csv | 1 + 8 files changed, 179 insertions(+), 3 deletions(-) diff --git a/applications/services/desktop/desktop.c b/applications/services/desktop/desktop.c index 1233af8938f..30e7253dae6 100644 --- a/applications/services/desktop/desktop.c +++ b/applications/services/desktop/desktop.c @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -8,6 +9,7 @@ #include #include #include +#include #include "animations/animation_manager.h" #include "desktop/scenes/desktop_scene.h" @@ -36,7 +38,6 @@ static void desktop_loader_callback(const void* message, void* context) { view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopGlobalAfterAppFinished); } } - static void desktop_lock_icon_draw_callback(Canvas* canvas, void* context) { UNUSED(context); furi_assert(canvas); @@ -49,6 +50,65 @@ static void desktop_dummy_mode_icon_draw_callback(Canvas* canvas, void* context) canvas_draw_icon(canvas, 0, 0, &I_GameMode_11x8); } +static void desktop_clock_update(Desktop* desktop) { + furi_assert(desktop); + + FuriHalRtcDateTime curr_dt; + furi_hal_rtc_get_datetime(&curr_dt); + bool time_format_12 = locale_get_time_format() == LocaleTimeFormat12h; + + if(desktop->time_hour != curr_dt.hour || desktop->time_minute != curr_dt.minute || + desktop->time_format_12 != time_format_12) { + desktop->time_format_12 = time_format_12; + desktop->time_hour = curr_dt.hour; + desktop->time_minute = curr_dt.minute; + view_port_update(desktop->clock_viewport); + } +} + +static void desktop_clock_reconfigure(Desktop* desktop) { + furi_assert(desktop); + + desktop_clock_update(desktop); + + if(desktop->settings.display_clock) { + furi_timer_start(desktop->update_clock_timer, furi_ms_to_ticks(1000)); + } else { + furi_timer_stop(desktop->update_clock_timer); + } + + view_port_enabled_set(desktop->clock_viewport, desktop->settings.display_clock); +} + +static void desktop_clock_draw_callback(Canvas* canvas, void* context) { + furi_assert(context); + furi_assert(canvas); + + Desktop* desktop = context; + + canvas_set_font(canvas, FontPrimary); + + uint8_t hour = desktop->time_hour; + if(desktop->time_format_12) { + if(hour > 12) { + hour -= 12; + } + if(hour == 0) { + hour = 12; + } + } + + char buffer[20]; + snprintf(buffer, sizeof(buffer), "%02u:%02u", hour, desktop->time_minute); + + // ToDo: never do that, may cause visual glitches + view_port_set_width( + desktop->clock_viewport, + canvas_string_width(canvas, buffer) - 1 + (desktop->time_minute % 10 == 1)); + + canvas_draw_str_aligned(canvas, 0, 8, AlignLeft, AlignBottom, buffer); +} + static void desktop_stealth_mode_icon_draw_callback(Canvas* canvas, void* context) { UNUSED(context); furi_assert(canvas); @@ -69,6 +129,9 @@ static bool desktop_custom_event_callback(void* context, uint32_t event) { // TODO: Implement a message mechanism for loading settings and (optionally) // locking and unlocking DESKTOP_SETTINGS_LOAD(&desktop->settings); + + desktop_clock_reconfigure(desktop); + desktop_auto_lock_arm(desktop); return true; case DesktopGlobalAutoLock: @@ -134,6 +197,19 @@ static void desktop_auto_lock_inhibit(Desktop* desktop) { } } +static void desktop_clock_timer_callback(void* context) { + furi_assert(context); + Desktop* desktop = context; + + if(gui_active_view_port_count(desktop->gui, GuiLayerStatusBarLeft) < 6) { + desktop_clock_update(desktop); + + view_port_enabled_set(desktop->clock_viewport, true); + } else { + view_port_enabled_set(desktop->clock_viewport, false); + } +} + void desktop_lock(Desktop* desktop) { furi_hal_rtc_set_flag(FuriHalRtcFlagLock); @@ -286,6 +362,13 @@ Desktop* desktop_alloc() { view_port_enabled_set(desktop->dummy_mode_icon_viewport, false); gui_add_view_port(desktop->gui, desktop->dummy_mode_icon_viewport, GuiLayerStatusBarLeft); + // Clock + desktop->clock_viewport = view_port_alloc(); + view_port_set_width(desktop->clock_viewport, 25); + view_port_draw_callback_set(desktop->clock_viewport, desktop_clock_draw_callback, desktop); + view_port_enabled_set(desktop->clock_viewport, false); + gui_add_view_port(desktop->gui, desktop->clock_viewport, GuiLayerStatusBarRight); + // Stealth mode icon desktop->stealth_mode_icon_viewport = view_port_alloc(); view_port_set_width(desktop->stealth_mode_icon_viewport, icon_get_width(&I_Muted_8x8)); @@ -317,6 +400,9 @@ Desktop* desktop_alloc() { desktop->status_pubsub = furi_pubsub_alloc(); + desktop->update_clock_timer = + furi_timer_alloc(desktop_clock_timer_callback, FuriTimerTypePeriodic, desktop); + furi_record_create(RECORD_DESKTOP, desktop); return desktop; @@ -362,6 +448,9 @@ int32_t desktop_srv(void* p) { } view_port_enabled_set(desktop->dummy_mode_icon_viewport, desktop->settings.dummy_mode); + + desktop_clock_reconfigure(desktop); + desktop_main_set_dummy_mode_state(desktop->main_view, desktop->settings.dummy_mode); animation_manager_set_dummy_mode_state( desktop->animation_manager, desktop->settings.dummy_mode); diff --git a/applications/services/desktop/desktop_i.h b/applications/services/desktop/desktop_i.h index 0b3d568016b..bb495c92014 100644 --- a/applications/services/desktop/desktop_i.h +++ b/applications/services/desktop/desktop_i.h @@ -59,6 +59,7 @@ struct Desktop { ViewPort* lock_icon_viewport; ViewPort* dummy_mode_icon_viewport; + ViewPort* clock_viewport; ViewPort* stealth_mode_icon_viewport; AnimationManager* animation_manager; @@ -70,10 +71,15 @@ struct Desktop { FuriPubSub* input_events_pubsub; FuriPubSubSubscription* input_events_subscription; FuriTimer* auto_lock_timer; + FuriTimer* update_clock_timer; FuriPubSub* status_pubsub; - bool in_transition; + uint8_t time_hour; + uint8_t time_minute; + bool time_format_12 : 1; // 1 - 12 hour, 0 - 24H + + bool in_transition : 1; }; Desktop* desktop_alloc(); diff --git a/applications/services/desktop/desktop_settings.h b/applications/services/desktop/desktop_settings.h index 9b88868a8dc..a189f9f05e9 100644 --- a/applications/services/desktop/desktop_settings.h +++ b/applications/services/desktop/desktop_settings.h @@ -8,7 +8,7 @@ #include #include -#define DESKTOP_SETTINGS_VER (8) +#define DESKTOP_SETTINGS_VER (9) #define DESKTOP_SETTINGS_PATH INT_PATH(DESKTOP_SETTINGS_FILE_NAME) #define DESKTOP_SETTINGS_MAGIC (0x17) @@ -51,4 +51,5 @@ typedef struct { PinCode pin_code; uint32_t auto_lock_delay_ms; uint8_t dummy_mode; + uint8_t display_clock; } DesktopSettings; diff --git a/applications/services/gui/gui.c b/applications/services/gui/gui.c index 392011620ab..b96f89db9fd 100644 --- a/applications/services/gui/gui.c +++ b/applications/services/gui/gui.c @@ -17,6 +17,26 @@ ViewPort* gui_view_port_find_enabled(ViewPortArray_t array) { return NULL; } +size_t gui_active_view_port_count(Gui* gui, GuiLayer layer) { + furi_assert(gui); + furi_check(layer < GuiLayerMAX); + size_t ret = 0; + + gui_lock(gui); + ViewPortArray_it_t it; + ViewPortArray_it_last(it, gui->layers[layer]); + while(!ViewPortArray_end_p(it)) { + ViewPort* view_port = *ViewPortArray_ref(it); + if(view_port_is_enabled(view_port)) { + ret++; + } + ViewPortArray_previous(it); + } + gui_unlock(gui); + + return ret; +} + void gui_update(Gui* gui) { furi_assert(gui); if(!gui->direct_draw) furi_thread_flags_set(gui->thread_id, GUI_THREAD_FLAG_DRAW); diff --git a/applications/services/gui/gui_i.h b/applications/services/gui/gui_i.h index a5cd841202e..a5e269e034f 100644 --- a/applications/services/gui/gui_i.h +++ b/applications/services/gui/gui_i.h @@ -75,6 +75,12 @@ struct Gui { ViewPort* ongoing_input_view_port; }; +/** Find enabled ViewPort in ViewPortArray + * + * @param[in] array The ViewPortArray instance + * + * @return ViewPort instance or NULL + */ ViewPort* gui_view_port_find_enabled(ViewPortArray_t array); /** Update GUI, request redraw @@ -83,8 +89,30 @@ ViewPort* gui_view_port_find_enabled(ViewPortArray_t array); */ void gui_update(Gui* gui); +/** Input event callback + * + * Used to receive input from input service or to inject new input events + * + * @param[in] value The value pointer (InputEvent*) + * @param ctx The context (Gui instance) + */ void gui_input_events_callback(const void* value, void* ctx); +/** Get count of view ports in layer + * + * @param gui The Gui instance + * @param[in] layer GuiLayer that we want to get count of view ports + */ +size_t gui_active_view_port_count(Gui* gui, GuiLayer layer); + +/** Lock GUI + * + * @param gui The Gui instance + */ void gui_lock(Gui* gui); +/** Unlock GUI + * + * @param gui The Gui instance + */ void gui_unlock(Gui* gui); diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_start.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_start.c index 8530a1a7db3..02acd51ca1e 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_start.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_start.c @@ -8,6 +8,7 @@ #define SCENE_EVENT_SELECT_FAVORITE_SECONDARY 1 #define SCENE_EVENT_SELECT_PIN_SETUP 2 #define SCENE_EVENT_SELECT_AUTO_LOCK_DELAY 3 +#define SCENE_EVENT_SELECT_CLOCK_DISPLAY 4 #define AUTO_LOCK_DELAY_COUNT 6 const char* const auto_lock_delay_text[AUTO_LOCK_DELAY_COUNT] = { @@ -22,11 +23,27 @@ const char* const auto_lock_delay_text[AUTO_LOCK_DELAY_COUNT] = { const uint32_t auto_lock_delay_value[AUTO_LOCK_DELAY_COUNT] = {0, 30000, 60000, 120000, 300000, 600000}; +#define CLOCK_ENABLE_COUNT 2 +const char* const clock_enable_text[CLOCK_ENABLE_COUNT] = { + "OFF", + "ON", +}; + +const uint32_t clock_enable_value[CLOCK_ENABLE_COUNT] = {0, 1}; + static void desktop_settings_scene_start_var_list_enter_callback(void* context, uint32_t index) { DesktopSettingsApp* app = context; view_dispatcher_send_custom_event(app->view_dispatcher, index); } +static void desktop_settings_scene_start_clock_enable_changed(VariableItem* item) { + DesktopSettingsApp* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + variable_item_set_current_value_text(item, clock_enable_text[index]); + app->settings.display_clock = index; +} + static void desktop_settings_scene_start_auto_lock_delay_changed(VariableItem* item) { DesktopSettingsApp* app = variable_item_get_context(item); uint8_t index = variable_item_get_current_value_index(item); @@ -62,6 +79,18 @@ void desktop_settings_scene_start_on_enter(void* context) { variable_item_set_current_value_index(item, value_index); variable_item_set_current_value_text(item, auto_lock_delay_text[value_index]); + item = variable_item_list_add( + variable_item_list, + "Show Clock", + CLOCK_ENABLE_COUNT, + desktop_settings_scene_start_clock_enable_changed, // + app); + + value_index = + value_index_uint32(app->settings.display_clock, clock_enable_value, CLOCK_ENABLE_COUNT); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, clock_enable_text[value_index]); + view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewVarItemList); } @@ -86,6 +115,7 @@ bool desktop_settings_scene_start_on_event(void* context, SceneManagerEvent even consumed = true; break; case SCENE_EVENT_SELECT_AUTO_LOCK_DELAY: + case SCENE_EVENT_SELECT_CLOCK_DISPLAY: consumed = true; break; } diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index 081fe502f98..eab140d5cf9 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1471,6 +1471,7 @@ Function,+,gui_add_framebuffer_callback,void,"Gui*, GuiCanvasCommitCallback, voi Function,+,gui_add_view_port,void,"Gui*, ViewPort*, GuiLayer" Function,+,gui_direct_draw_acquire,Canvas*,Gui* Function,+,gui_direct_draw_release,void,Gui* +Function,-,gui_active_view_port_count,size_t,"Gui*, GuiLayer" Function,+,gui_get_framebuffer_size,size_t,const Gui* Function,+,gui_remove_framebuffer_callback,void,"Gui*, GuiCanvasCommitCallback, void*" Function,+,gui_remove_view_port,void,"Gui*, ViewPort*" diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 535582c0c97..8efe980a77d 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1642,6 +1642,7 @@ Function,+,gui_add_framebuffer_callback,void,"Gui*, GuiCanvasCommitCallback, voi Function,+,gui_add_view_port,void,"Gui*, ViewPort*, GuiLayer" Function,+,gui_direct_draw_acquire,Canvas*,Gui* Function,+,gui_direct_draw_release,void,Gui* +Function,-,gui_active_view_port_count,size_t,"Gui*, GuiLayer" Function,+,gui_get_framebuffer_size,size_t,const Gui* Function,+,gui_remove_framebuffer_callback,void,"Gui*, GuiCanvasCommitCallback, void*" Function,+,gui_remove_view_port,void,"Gui*, ViewPort*" From 7178bd20cf883228b81133d50ea174a19b246eb7 Mon Sep 17 00:00:00 2001 From: hedger Date: Thu, 10 Aug 2023 19:21:56 +0300 Subject: [PATCH 706/824] ufbt: fixed FAP_SRC_DIR (#2970) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fbt, ufbt: fixed "_appdir" internal property and FAP_SRC_DIR not working in ufbt environment * fbt, ufbt: reworked CompileIcons(); added app's own root to app's #include path * fbt: cleaner resolve_real_dir_node Co-authored-by: あく --- scripts/fbt/appmanifest.py | 3 ++- scripts/fbt/util.py | 12 ++---------- scripts/fbt_tools/fbt_assets.py | 18 ++++++++---------- scripts/fbt_tools/fbt_extapps.py | 9 +++++---- scripts/ufbt/SConstruct | 26 ++++++++++++-------------- site_scons/environ.scons | 4 ++-- 6 files changed, 31 insertions(+), 41 deletions(-) diff --git a/scripts/fbt/appmanifest.py b/scripts/fbt/appmanifest.py index 067b4a26f09..a677d841366 100644 --- a/scripts/fbt/appmanifest.py +++ b/scripts/fbt/appmanifest.py @@ -2,6 +2,7 @@ import re from dataclasses import dataclass, field from enum import Enum +from fbt.util import resolve_real_dir_node from typing import Callable, ClassVar, List, Optional, Tuple, Union @@ -152,7 +153,7 @@ def App(*args, **kw): FlipperApplication( *args, **kw, - _appdir=app_dir_node, + _appdir=resolve_real_dir_node(app_dir_node), _apppath=os.path.dirname(app_manifest_path), _appmanager=self, ), diff --git a/scripts/fbt/util.py b/scripts/fbt/util.py index 57e60aecfa4..fb36ef55ac3 100644 --- a/scripts/fbt/util.py +++ b/scripts/fbt/util.py @@ -45,7 +45,7 @@ def single_quote(arg_list): return " ".join(f"'{arg}'" if " " in arg else str(arg) for arg in arg_list) -def extract_abs_dir(node): +def resolve_real_dir_node(node): if isinstance(node, SCons.Node.FS.EntryProxy): node = node.get() @@ -53,15 +53,7 @@ def extract_abs_dir(node): if os.path.exists(repo_dir.abspath): return repo_dir - -def extract_abs_dir_path(node): - abs_dir_node = extract_abs_dir(node) - if abs_dir_node is None: - raise StopError(f"Can't find absolute path for {node.name}") - - # Don't return abspath attribute (type is str), it will break in - # OverrideEnvironment.subst_list() by splitting path on spaces - return abs_dir_node + raise StopError(f"Can't find absolute path for {node.name} ({node})") def path_as_posix(path): diff --git a/scripts/fbt_tools/fbt_assets.py b/scripts/fbt_tools/fbt_assets.py index b2b9310ba68..4f4d3bffdcd 100644 --- a/scripts/fbt_tools/fbt_assets.py +++ b/scripts/fbt_tools/fbt_assets.py @@ -8,11 +8,14 @@ def icons_emitter(target, source, env): + icons_src = env.GlobRecursive("*.png", env["ICON_SRC_DIR"]) + icons_src += env.GlobRecursive("frame_rate", env["ICON_SRC_DIR"]) + target = [ target[0].File(env.subst("${ICON_FILE_NAME}.c")), target[0].File(env.subst("${ICON_FILE_NAME}.h")), ] - return target, source + return target, icons_src def proto_emitter(target, source, env): @@ -104,17 +107,12 @@ def proto_ver_generator(target, source, env): def CompileIcons(env, target_dir, source_dir, *, icon_bundle_name="assets_icons"): - # Gathering icons sources - icons_src = env.GlobRecursive("*.png", source_dir) - icons_src += env.GlobRecursive("frame_rate", source_dir) - - icons = env.IconBuilder( + return env.IconBuilder( target_dir, - source_dir, + None, + ICON_SRC_DIR=source_dir, ICON_FILE_NAME=icon_bundle_name, ) - env.Depends(icons, icons_src) - return icons def generate(env): @@ -137,7 +135,7 @@ def generate(env): BUILDERS={ "IconBuilder": Builder( action=Action( - '${PYTHON3} ${ASSETS_COMPILER} icons ${ABSPATHGETTERFUNC(SOURCE)} ${TARGET.dir} --filename "${ICON_FILE_NAME}"', + '${PYTHON3} ${ASSETS_COMPILER} icons ${ICON_SRC_DIR} ${TARGET.dir} --filename "${ICON_FILE_NAME}"', "${ICONSCOMSTR}", ), emitter=icons_emitter, diff --git a/scripts/fbt_tools/fbt_extapps.py b/scripts/fbt_tools/fbt_extapps.py index 1766d4c4447..642c1c989d5 100644 --- a/scripts/fbt_tools/fbt_extapps.py +++ b/scripts/fbt_tools/fbt_extapps.py @@ -11,7 +11,7 @@ from fbt.elfmanifest import assemble_manifest_data from fbt.fapassets import FileBundler from fbt.sdk.cache import SdkCache -from fbt.util import extract_abs_dir_path +from fbt.util import resolve_real_dir_node from SCons.Action import Action from SCons.Builder import Builder from SCons.Errors import UserError @@ -50,7 +50,8 @@ def build(self): def _setup_app_env(self): self.app_env = self.fw_env.Clone( - FAP_SRC_DIR=self.app._appdir, FAP_WORK_DIR=self.app_work_dir + FAP_SRC_DIR=self.app._appdir, + FAP_WORK_DIR=self.app_work_dir, ) self.app_env.VariantDir(self.app_work_dir, self.app._appdir, duplicate=False) @@ -119,7 +120,7 @@ def _build_private_lib(self, lib_def): CPPDEFINES=lib_def.cdefines, CPPPATH=list( map( - lambda cpath: extract_abs_dir_path(self.app._appdir.Dir(cpath)), + lambda cpath: resolve_real_dir_node(self.app._appdir.Dir(cpath)), lib_def.cincludes, ) ), @@ -133,7 +134,7 @@ def _build_private_lib(self, lib_def): def _build_app(self): self.app_env.Append( LIBS=[*self.app.fap_libs, *self.private_libs], - CPPPATH=self.app_env.Dir(self.app_work_dir), + CPPPATH=[self.app_env.Dir(self.app_work_dir), self.app._appdir], ) app_sources = list( diff --git a/scripts/ufbt/SConstruct b/scripts/ufbt/SConstruct index 342cd253280..1c2f2bdf587 100644 --- a/scripts/ufbt/SConstruct +++ b/scripts/ufbt/SConstruct @@ -1,12 +1,11 @@ -from SCons.Platform import TempFileMunge -from SCons.Node import FS -from SCons.Errors import UserError - - -import os import multiprocessing +import os import pathlib +from SCons.Errors import UserError +from SCons.Node import FS +from SCons.Platform import TempFileMunge + SetOption("num_jobs", multiprocessing.cpu_count()) SetOption("max_drift", 1) # SetOption("silent", False) @@ -67,16 +66,15 @@ core_env.Append(CPPDEFINES=GetOption("extra_defines")) # Now we can import stuff bundled with SDK - it was added to sys.path by ufbt_state +from fbt.appmanifest import FlipperApplication, FlipperAppType +from fbt.sdk.cache import SdkCache from fbt.util import ( - tempfile_arg_esc_func, + path_as_posix, + resolve_real_dir_node, single_quote, - extract_abs_dir, - extract_abs_dir_path, + tempfile_arg_esc_func, wrap_tempfile, - path_as_posix, ) -from fbt.appmanifest import FlipperAppType, FlipperApplication -from fbt.sdk.cache import SdkCache # Base environment with all tools loaded from SDK env = core_env.Clone( @@ -107,7 +105,7 @@ env = core_env.Clone( PROGSUFFIX=".elf", TEMPFILEARGESCFUNC=tempfile_arg_esc_func, SINGLEQUOTEFUNC=single_quote, - ABSPATHGETTERFUNC=extract_abs_dir_path, + ABSPATHGETTERFUNC=resolve_real_dir_node, APPS=[], UFBT_API_VERSION=SdkCache( core_env.subst("$SDK_DEFINITION"), load_version_only=True @@ -277,7 +275,7 @@ for app in known_extapps: continue app_artifacts = appenv.BuildAppElf(app) - app_src_dir = extract_abs_dir(app_artifacts.app._appdir) + app_src_dir = resolve_real_dir_node(app_artifacts.app._appdir) app_artifacts.installer = [ appenv.Install(app_src_dir.Dir("dist"), app_artifacts.compact), appenv.Install(app_src_dir.Dir("dist").Dir("debug"), app_artifacts.debug), diff --git a/site_scons/environ.scons b/site_scons/environ.scons index acdc83e2ad7..b638b101859 100644 --- a/site_scons/environ.scons +++ b/site_scons/environ.scons @@ -3,7 +3,7 @@ from fbt.util import ( tempfile_arg_esc_func, single_quote, wrap_tempfile, - extract_abs_dir_path, + resolve_real_dir_node, ) import os @@ -58,7 +58,7 @@ coreenv = VAR_ENV.Clone( PROGSUFFIX=".elf", ENV=forward_os_env, SINGLEQUOTEFUNC=single_quote, - ABSPATHGETTERFUNC=extract_abs_dir_path, + ABSPATHGETTERFUNC=resolve_real_dir_node, # Setting up temp file parameters - to overcome command line length limits TEMPFILEARGESCFUNC=tempfile_arg_esc_func, ROOT_DIR=Dir("#"), From 12d9b1069cf2f449f19cf63cb1fad7ad6460da40 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Fri, 11 Aug 2023 01:31:01 +0900 Subject: [PATCH 707/824] [FL-3480] Add the Sad song animation (#2973) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- .../external/L1_Sad_song_128x64/frame_0.png | Bin 0 -> 1464 bytes .../external/L1_Sad_song_128x64/frame_1.png | Bin 0 -> 1442 bytes .../external/L1_Sad_song_128x64/frame_10.png | Bin 0 -> 1044 bytes .../external/L1_Sad_song_128x64/frame_11.png | Bin 0 -> 1069 bytes .../external/L1_Sad_song_128x64/frame_12.png | Bin 0 -> 980 bytes .../external/L1_Sad_song_128x64/frame_13.png | Bin 0 -> 1150 bytes .../external/L1_Sad_song_128x64/frame_14.png | Bin 0 -> 1153 bytes .../external/L1_Sad_song_128x64/frame_15.png | Bin 0 -> 1137 bytes .../external/L1_Sad_song_128x64/frame_16.png | Bin 0 -> 979 bytes .../external/L1_Sad_song_128x64/frame_17.png | Bin 0 -> 988 bytes .../external/L1_Sad_song_128x64/frame_18.png | Bin 0 -> 1049 bytes .../external/L1_Sad_song_128x64/frame_19.png | Bin 0 -> 259 bytes .../external/L1_Sad_song_128x64/frame_2.png | Bin 0 -> 1483 bytes .../external/L1_Sad_song_128x64/frame_20.png | Bin 0 -> 922 bytes .../external/L1_Sad_song_128x64/frame_21.png | Bin 0 -> 895 bytes .../external/L1_Sad_song_128x64/frame_22.png | Bin 0 -> 831 bytes .../external/L1_Sad_song_128x64/frame_23.png | Bin 0 -> 803 bytes .../external/L1_Sad_song_128x64/frame_24.png | Bin 0 -> 859 bytes .../external/L1_Sad_song_128x64/frame_25.png | Bin 0 -> 854 bytes .../external/L1_Sad_song_128x64/frame_26.png | Bin 0 -> 821 bytes .../external/L1_Sad_song_128x64/frame_27.png | Bin 0 -> 791 bytes .../external/L1_Sad_song_128x64/frame_28.png | Bin 0 -> 871 bytes .../external/L1_Sad_song_128x64/frame_29.png | Bin 0 -> 954 bytes .../external/L1_Sad_song_128x64/frame_3.png | Bin 0 -> 1470 bytes .../external/L1_Sad_song_128x64/frame_30.png | Bin 0 -> 871 bytes .../external/L1_Sad_song_128x64/frame_31.png | Bin 0 -> 926 bytes .../external/L1_Sad_song_128x64/frame_32.png | Bin 0 -> 889 bytes .../external/L1_Sad_song_128x64/frame_33.png | Bin 0 -> 933 bytes .../external/L1_Sad_song_128x64/frame_34.png | Bin 0 -> 873 bytes .../external/L1_Sad_song_128x64/frame_35.png | Bin 0 -> 907 bytes .../external/L1_Sad_song_128x64/frame_36.png | Bin 0 -> 803 bytes .../external/L1_Sad_song_128x64/frame_37.png | Bin 0 -> 769 bytes .../external/L1_Sad_song_128x64/frame_38.png | Bin 0 -> 909 bytes .../external/L1_Sad_song_128x64/frame_39.png | Bin 0 -> 915 bytes .../external/L1_Sad_song_128x64/frame_4.png | Bin 0 -> 1442 bytes .../external/L1_Sad_song_128x64/frame_40.png | Bin 0 -> 918 bytes .../external/L1_Sad_song_128x64/frame_41.png | Bin 0 -> 883 bytes .../external/L1_Sad_song_128x64/frame_42.png | Bin 0 -> 1020 bytes .../external/L1_Sad_song_128x64/frame_43.png | Bin 0 -> 788 bytes .../external/L1_Sad_song_128x64/frame_44.png | Bin 0 -> 997 bytes .../external/L1_Sad_song_128x64/frame_45.png | Bin 0 -> 1061 bytes .../external/L1_Sad_song_128x64/frame_46.png | Bin 0 -> 949 bytes .../external/L1_Sad_song_128x64/frame_47.png | Bin 0 -> 1084 bytes .../external/L1_Sad_song_128x64/frame_48.png | Bin 0 -> 1016 bytes .../external/L1_Sad_song_128x64/frame_49.png | Bin 0 -> 1092 bytes .../external/L1_Sad_song_128x64/frame_5.png | Bin 0 -> 1453 bytes .../external/L1_Sad_song_128x64/frame_50.png | Bin 0 -> 988 bytes .../external/L1_Sad_song_128x64/frame_51.png | Bin 0 -> 1159 bytes .../external/L1_Sad_song_128x64/frame_52.png | Bin 0 -> 991 bytes .../external/L1_Sad_song_128x64/frame_53.png | Bin 0 -> 778 bytes .../external/L1_Sad_song_128x64/frame_54.png | Bin 0 -> 830 bytes .../external/L1_Sad_song_128x64/frame_55.png | Bin 0 -> 720 bytes .../external/L1_Sad_song_128x64/frame_56.png | Bin 0 -> 576 bytes .../external/L1_Sad_song_128x64/frame_57.png | Bin 0 -> 398 bytes .../external/L1_Sad_song_128x64/frame_58.png | Bin 0 -> 474 bytes .../external/L1_Sad_song_128x64/frame_59.png | Bin 0 -> 970 bytes .../external/L1_Sad_song_128x64/frame_6.png | Bin 0 -> 1487 bytes .../external/L1_Sad_song_128x64/frame_60.png | Bin 0 -> 1370 bytes .../external/L1_Sad_song_128x64/frame_61.png | Bin 0 -> 1578 bytes .../external/L1_Sad_song_128x64/frame_62.png | Bin 0 -> 1508 bytes .../external/L1_Sad_song_128x64/frame_7.png | Bin 0 -> 1480 bytes .../external/L1_Sad_song_128x64/frame_8.png | Bin 0 -> 1438 bytes .../external/L1_Sad_song_128x64/frame_9.png | Bin 0 -> 1480 bytes .../external/L1_Sad_song_128x64/meta.txt | 284 ++++++++++++++++++ assets/dolphin/external/manifest.txt | 7 + 65 files changed, 291 insertions(+) create mode 100644 assets/dolphin/external/L1_Sad_song_128x64/frame_0.png create mode 100644 assets/dolphin/external/L1_Sad_song_128x64/frame_1.png create mode 100644 assets/dolphin/external/L1_Sad_song_128x64/frame_10.png create mode 100644 assets/dolphin/external/L1_Sad_song_128x64/frame_11.png create mode 100644 assets/dolphin/external/L1_Sad_song_128x64/frame_12.png create mode 100644 assets/dolphin/external/L1_Sad_song_128x64/frame_13.png create mode 100644 assets/dolphin/external/L1_Sad_song_128x64/frame_14.png create mode 100644 assets/dolphin/external/L1_Sad_song_128x64/frame_15.png create mode 100644 assets/dolphin/external/L1_Sad_song_128x64/frame_16.png create mode 100644 assets/dolphin/external/L1_Sad_song_128x64/frame_17.png create mode 100644 assets/dolphin/external/L1_Sad_song_128x64/frame_18.png create mode 100644 assets/dolphin/external/L1_Sad_song_128x64/frame_19.png create mode 100644 assets/dolphin/external/L1_Sad_song_128x64/frame_2.png create mode 100644 assets/dolphin/external/L1_Sad_song_128x64/frame_20.png create mode 100644 assets/dolphin/external/L1_Sad_song_128x64/frame_21.png create mode 100644 assets/dolphin/external/L1_Sad_song_128x64/frame_22.png create mode 100644 assets/dolphin/external/L1_Sad_song_128x64/frame_23.png create mode 100644 assets/dolphin/external/L1_Sad_song_128x64/frame_24.png create mode 100644 assets/dolphin/external/L1_Sad_song_128x64/frame_25.png create mode 100644 assets/dolphin/external/L1_Sad_song_128x64/frame_26.png create mode 100644 assets/dolphin/external/L1_Sad_song_128x64/frame_27.png create mode 100644 assets/dolphin/external/L1_Sad_song_128x64/frame_28.png create mode 100644 assets/dolphin/external/L1_Sad_song_128x64/frame_29.png create mode 100644 assets/dolphin/external/L1_Sad_song_128x64/frame_3.png create mode 100644 assets/dolphin/external/L1_Sad_song_128x64/frame_30.png create mode 100644 assets/dolphin/external/L1_Sad_song_128x64/frame_31.png create mode 100644 assets/dolphin/external/L1_Sad_song_128x64/frame_32.png create mode 100644 assets/dolphin/external/L1_Sad_song_128x64/frame_33.png create mode 100644 assets/dolphin/external/L1_Sad_song_128x64/frame_34.png create mode 100644 assets/dolphin/external/L1_Sad_song_128x64/frame_35.png create mode 100644 assets/dolphin/external/L1_Sad_song_128x64/frame_36.png create mode 100644 assets/dolphin/external/L1_Sad_song_128x64/frame_37.png create mode 100644 assets/dolphin/external/L1_Sad_song_128x64/frame_38.png create mode 100644 assets/dolphin/external/L1_Sad_song_128x64/frame_39.png create mode 100644 assets/dolphin/external/L1_Sad_song_128x64/frame_4.png create mode 100644 assets/dolphin/external/L1_Sad_song_128x64/frame_40.png create mode 100644 assets/dolphin/external/L1_Sad_song_128x64/frame_41.png create mode 100644 assets/dolphin/external/L1_Sad_song_128x64/frame_42.png create mode 100644 assets/dolphin/external/L1_Sad_song_128x64/frame_43.png create mode 100644 assets/dolphin/external/L1_Sad_song_128x64/frame_44.png create mode 100644 assets/dolphin/external/L1_Sad_song_128x64/frame_45.png create mode 100644 assets/dolphin/external/L1_Sad_song_128x64/frame_46.png create mode 100644 assets/dolphin/external/L1_Sad_song_128x64/frame_47.png create mode 100644 assets/dolphin/external/L1_Sad_song_128x64/frame_48.png create mode 100644 assets/dolphin/external/L1_Sad_song_128x64/frame_49.png create mode 100644 assets/dolphin/external/L1_Sad_song_128x64/frame_5.png create mode 100644 assets/dolphin/external/L1_Sad_song_128x64/frame_50.png create mode 100644 assets/dolphin/external/L1_Sad_song_128x64/frame_51.png create mode 100644 assets/dolphin/external/L1_Sad_song_128x64/frame_52.png create mode 100644 assets/dolphin/external/L1_Sad_song_128x64/frame_53.png create mode 100644 assets/dolphin/external/L1_Sad_song_128x64/frame_54.png create mode 100644 assets/dolphin/external/L1_Sad_song_128x64/frame_55.png create mode 100644 assets/dolphin/external/L1_Sad_song_128x64/frame_56.png create mode 100644 assets/dolphin/external/L1_Sad_song_128x64/frame_57.png create mode 100644 assets/dolphin/external/L1_Sad_song_128x64/frame_58.png create mode 100644 assets/dolphin/external/L1_Sad_song_128x64/frame_59.png create mode 100644 assets/dolphin/external/L1_Sad_song_128x64/frame_6.png create mode 100644 assets/dolphin/external/L1_Sad_song_128x64/frame_60.png create mode 100644 assets/dolphin/external/L1_Sad_song_128x64/frame_61.png create mode 100644 assets/dolphin/external/L1_Sad_song_128x64/frame_62.png create mode 100644 assets/dolphin/external/L1_Sad_song_128x64/frame_7.png create mode 100644 assets/dolphin/external/L1_Sad_song_128x64/frame_8.png create mode 100644 assets/dolphin/external/L1_Sad_song_128x64/frame_9.png create mode 100644 assets/dolphin/external/L1_Sad_song_128x64/meta.txt diff --git a/assets/dolphin/external/L1_Sad_song_128x64/frame_0.png b/assets/dolphin/external/L1_Sad_song_128x64/frame_0.png new file mode 100644 index 0000000000000000000000000000000000000000..71e85fe8f97f8a8de21ed4f969091bce2a428dfb GIT binary patch literal 1464 zcmV;p1xNacP)Csv{+h&~S&lHa1`1OAZdha+6)rQt5a^FIx?^W=zdjb#k;vv8v$J-O&?h-v4Z%=?~ z@Z4i|ze<}@`-cF39JeRHBk?)ho&YnX^)KP}1b7r6huah2QGgt7Pk=`Oa=1MK9tFtZ z_5^qo;1kh}aC-vmRDgFN-|Y#o(+TeoeqOsLkcH%ge(gNZsjqBDcKO^;!mK3qB?urb z6uJoA$=dbR3PIUhYwEi;)MzNd#lJhObBcF6hzk7i?68U^BBH^Px=z4gulmO_?r}10b z;dBAC9KbUtRB7n=yiYc6qq1L}+bkmh>U;|6!%r)-UXk!=A*XWek?dzlPw`r@XnnGl zWNN#qwPx=4>|#WAQy+77f@aBn6$vg;21*YTiJci}rKE<+T{wajaq3X^BvE# zZ`2W1;)y7eG*?f_I+`0YR&RqQ#-p~c)yJ7yTpu}J0_uFJvfy?`_D5}2{DFx#VWsw^ z>#Z%w0ko`{Pt5wOAITo=gODKloJje#I~*W=T9Xy<_Ai4fK8;2&a&nXeqXf7TedAEo z+DZT|mXFx&Np>nv31`C_3TP$bMJK}T6~IEZ!)K^KwTkS*j@%or+(52AZ?!+ zD~sxrxGX#gpM<4VKt`^td!83K6|hLKk|5Gp-ggE|s(=oSt^IdN1+?~(z0BNpqw-9F z+8f!L9s+o|W-$~g|DFjM2~|rwLhIh2HLtJbKG_iq{%05C*7mhGLD~x(Y&mX z6-fpyw%Rx+58e`F4oBK@WlK$C5+4eog96j56M^qhSMsw*Au zxFrOjF^B)GTG-j=>{09_j|B8^EPNEKIobL-16I7F|E^N6Q+Sf!EVymHCME$Q&gOE= ze`dks_~<3g2$|#5N>s&tD-4R2)Ont#$EhCYOAg4EPPE+NV~p-|Tl9XmLWn$9CjM*l z<+;(w_VIbW&gr9vh!Vge7qS@I9IBGA%yW6hM~LdNS*8HUZ%$^4h2UGkqeVbEJNnz3+BW1+b8D47ZVW3LgtqsK|I( z0%$h0Yoqx=Z5p5e95Z&$3Pe=O(>@BW0ue%t?5b@v1>o@R9JJ4BT$u#YI0zv$dr-SZ z6oA4jIi6%@HzH15MFc)()cLJpbA6!+oF!4bFC{~!?wso~5~KHa!8@lHR4W2F1RRHR+ z5eXtT5m={WeO@DIO3*_cwRu${MA!7ynqA^Wc9Ya+WhfYxsahYa#9c*%nMx^+$7?{O z#2XqN?8=UcOx$y#hlDz5UfNklg@dehL4A=S<$nao8l*NPWvM(L@H!#;0)^j8ExcgM zQZDU&M|iVp3Lp*YHa^%<9kb3Nyz@fsTF!{5Oe8c`g@<2dAb+q-D6ojMyEH@y?kR0O z&P4N*RVS(63J_fkwEZ**D^RIS_0!vSWR7dSb}I1eb+Fm6pv0R&f{J9!N?N_P5oN0y zs{*c*@6}yoKv0`Vp5kW;xA=#MinBC5At+jvrhSNKR SlLUDH0000 zQA#<>Vw~rH6OQA!{7gZg9mgT+kYqg1HAH0Zg4b#T9<0R^0Dl}`9{{^k^k{s20Az#L z9;4+lb+*Po0r1DMeE>WZAH((mFoIhD61ES3hXG>PJ^&sDh++Exco-mt?E~OpfEcz9 zfQJEo;N1w@2f#`O_=e!yJ^)ra;TzJ=d$j?j{9Z`QHS3)jp!HrEdNZ*!0JJ-NGziY~ zY%VPQ-OL5en0Kb_O9DXpsDlFu0%@0_b41YkKqbRr`~#URy)8@+fR>8P} zH2a3v0Gg9BDAdXT)+!n~eyvUih)gMMUo5>0;*At^C$(g(^Lk{oxxBVm`WjKx`iZ8l z~M4Ez5j^x9hP4QTUnq+umxGpAHf3NI}6Sl^jK=a77TEg;~1T9M*jqXR9C@8 zWm$X*;NwccdhEz}1YTK9F1u3q*n!$GtrN(1PRqdF#ZGHHEoxS!h74r(6sW$Qt|ciz zGEA$6GcQK?Zi}>oBg{i>h(Ov{h>h<%?E(rZ7$OTdx_4jETX?N0<|zg>=IGfn{S=UK z`BL@JXT8Ov=^_hAhci=W4<5PU0g$!e5j|+1eFY*nnHlq@1OUIF;5ldnG`gc0ePml1 zJT0JiBJM_z}f3JWs@_7UydYh&EnYb4c@Q_Oy5>$tsuDrqyn=igBHPe0P4@zsK&=GK`=|sis(3JI9t5T2wP5cAQ))ZG(Y+?b$n*6`MBA|=-epGauXrJmjL`kAOi*pBtVWs5+g)1; zt4(j}JZt-rPppj{SwLkljC2HEQT@-vB?Wk?=c3S%4l)*1>ylaKXk8Mm3IvZIyy@st z1h$TNnUH!2*=W%@BE#jWtW}>Ebp2=G*Jt59D(^v(AMrwnol=Vx(aHM-wC9nvL9`-t z)Y0!E+d2hg(4E%brgKE*u{t4ofx@3f)~TgSOF2FEbgv-~fS%(_%7MkK@GfG;;#>7< z&OmZCALrS@S?T$b0{DwX(m^kfR<>1<0JofIEY3s=%UQjX4+BIRJ;-=TM>G{=uvFyu zTgO&J8aCM{Bn0m|G1BO<7ExXuGojgK>48ug%XliYVr@Pjgg}Fhxek07*qoM6N<$f&saYGynhq literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sad_song_128x64/frame_10.png b/assets/dolphin/external/L1_Sad_song_128x64/frame_10.png new file mode 100644 index 0000000000000000000000000000000000000000..da8f13680c05b8d3fd5e93d57e1a1cdc7bf26444 GIT binary patch literal 1044 zcmV+v1nc{WP)N9~?nyN&WF1&}TH6>h-W=e&u}lQ%~V{`0H_ zTj|^WzVGwCFVu5rQ3+r*`jLY_q6T&(iktcH_z?8qQ)oy2O(GEsfHV40xj~p(jyu3j zoqVGUhy-xM=&$ri-%Wz0aMS^A?9AIla&#Gp1l5%|tAW|?WD*El08#djG_CAJlF22G zIRLimi{`@Gf*)TfOPbGb(&;Eax?z653;xbO8~pNe}($soZxFa(?e((tBGMr zAa|!NQ>RukxPXZOxT?=yJ6Ms;1;_%hvj42XUp-fb_Rk2d14IVjYLzm&PwN8c(}1f0 zM*Hit?>R_1*h+ZWEWamBB`~uEBfEfF0Ftul=slE?quq&S<%4zzM)}BxwaiX)F3M zGB^S3K(<=sw#`rpK$7`#M-AnE7L-zcedg%eZJ0U0uA|U9vdz@n!0!Z>eHsXv0<>0n zG8wEws4s2X&~X(2r(+^Ja`m~bLvW@DR2N9I7jS~r!B*~}*Q}MmuvGx!G+-6rOw)k7 z0MMoZ%KfcIH{vvqt`5**b&0Gi@MvHWz@HX3c$K1prI83CE&YcaMo@#7YIoL zQ4=Qa^mGGT0Z`8ZNYU=4_wTnzF0IILP9SU<1?8N8SO7@M9=QOA zSt6;~k3j056=r`JqJew5M$0NWldLLM4lrvsiVIx*cLAJE9aR9mcO)Z#DFs_6gOU-| zilE-}cESrmrz(TH>16b3+jC+yu(}>4HW&vuf#k4?65<-LCmI00DI1V)=Ps6CQdXHB>O`ruPHpF9#i zsTehfNxfYlQ~N8s_-r#EDuJst{R%6qY>$DR5IXhv zctm9|JO_xzV}zbQRHH%U0NPC#ZlZSz?|^XJz>DxHK#No)QKNB}<$F&9QvsZ^Ed)D6 zu;S>WU=?@=uucXMf?q+{CJy;Wkw`({uo9WoK^AX@0$2q~n*c^4?P$tqj6T>Q#cc}E zjDF9WKo7M(&?a}>c8uEYH4VV$0Fg+=ijv?__$2^T`;T-13EZ|sxxg2R8Wui-ccKUY O0000lK(03DIIdD~g8yE97lDMXUNZ_i{PBt+5n@j}RCP0+@Be|6oNiwN~ zn*!`E0J4EcbLp=m&4CLu0!VlNsYR3Q$lZv)U4twtutk7X-M@l6@g3u97xM(sGFB7A zg23IAwoHp!kwFDa2(Zig?6rd#vZ(-@09Nv!)%de{Wf=bmp>=>r<6C7Z)N@)DKz9R2 z2|%5%kAB~QWPz=Smre3}q^SgEmSAKRP!m8>Hl^<2?4;9%)?_AGlz~M6>-_&DG-Kk< zHH)z+9Crbn8ankp^_~^sq!mn#Vxb3vM_R&=VI2%+Ln;FFi=0t%(Evw)lRHUEAkJ9P z+sNPuumZBwB9CnZ6#*oX@9wVQJkJ89lwY4oUAqlt3b0xz^o49A_15q!f@OCDAud2G zm7c+1B|?2^$A*rh1lU=a$c3DJZmSSGE&^2r(wqf&VAj~W+w_{X5*U^RAi4o717~sr z#|ePu1~|{R+TDn5AYC1xg_R1lMyYnhm?^+$+$bt=_4fjH7L}?1o<5O`08A;^IvAAf zsFnovnYSan5ad-ET)7kK)sDv_8<<^>A{&eXctFyuVudVKK_~)fgTYgIH^;b=6gMIm zgdhM)%Jdh4z8(++u*B>vpL6eD0~`UYY68^Z3@Res00aRf)i>_*{xL^50ea(e51&p1 zd{Q!Mnn_QqKqvu5#*cZSt?uzsgYY151?ylC&G;iwbMgpML;g`DVlc$PjAS138D#~QYCsZTW+-g{ z7zruyIH?)+Hx@)6HL6&V;Ft^0?EaHAfgarYKwHA~7zb4%wr~mXF+e1wWc5tnD3WPN n@QVPb@*k-J5_rZqmTK??hay8P3g!AZ00000NkvXXu0mjfD@e-& literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sad_song_128x64/frame_12.png b/assets/dolphin/external/L1_Sad_song_128x64/frame_12.png new file mode 100644 index 0000000000000000000000000000000000000000..52ecb01c1ff201494f4c469a03ab2c7f7e060e91 GIT binary patch literal 980 zcmV;_11tQAP)7?9OPWFHkOQ zx_fFVr4=Pw`@5-H>%ad;zVFvswRX+odu+4gUA>o$Us@ZAlKk1eTWjR^vQdkI14K&x z2zS(v=b44y);mB=ekpswcD~2nTAR8ic)7C(cQ} zG?*`h7Ok7;Itk=nZjBPu5p)+&698BB(R&9wBDnxd09y7RHTk>qig5fh-1`8Y$=6z? z%(gSS0CE|4E`Ztf_0?+*Hv!fXUL?!^;_d((B3m%33#bU-rfoX=4rL^vCwe4;GmtL^ zX|MmYfjYuZJi8-k*D-i`6P5=>uj0~gzvLzY03_uFt1hPPok8Orn z05{7VN|3w)S!)fIKq;yYa0;AvU^&Uv76NJ+a4P}Q5ia?@wGbkef#(9?1jag$v*hkB z_-Ndag60AtCxN$UBsa>)rs669&oVH>s^%l()(WQL5lG96QD|i#dLC=wo zQU*i60F))A#SdE`10w)URxY49YO??-0BITMMEU_I zuQC85fbWB&n%n!&8VCWbo*LG00V!4&nO5Ixs(E5Q0ZKP}SF?lHGG~*}$%S*wC z$=7PiAfDR{m`>nsO}|6SD*K(m-F`Lc$4Fg;yzC6RmjDD_j8N;FYHqo9fbV3D3wNSt z#p{HiUCV$d0a9**>NOfq$NRDjY)gQZZ6Vm9_rAx!rTDEBLd*fQI)fdr*U(bj}p3084Jgsx3w<5&Qwd@NQc>685VA0000kfLsu{n7Yw&TLH{r{(@vO#w9-Ocp zbKl?hecty?H4ZIQ1i)5*X5&NXhJ{FRGya_TlI+H(U|jh(kwi=Y&g##K8;Gfey8_%a z$v3KihyXXN{*F25yNR$AxGKPnlX)9ST+_fzpsErl8(2M4i6CqOM9F_9x3VKiC6#bf z0PX~2Ht=dJ^Yu()pu&s*($#-z(j+UA8}W~GP(=k;1lZO6JE#Na7+*V?Cx}+Dniv)Y za(CJ)O=?F46)+(Hm-W?a2NuE8e;AJECRK4>#^{31kgw&b&GmuOw7%U_b7eMevB*~0d`nTdN*`yZF#nT zZl}MuD(1}XOZ%1?DgsD+9(UDH+Z(rGd*4<>f$l-qB_yZ@)AInk`==yOySlNl%yM2m z8wha$+DV`UgZ=vWc?<&Q=dk+_u=pX9fp<7c0M5i%Or$jUnhCKYhIBlcg28e@AQjkT zQ_uL#*RXm)rBy#7i~!zl09&=Y%m90qLa0iFI8K1mZUAai`>a&LGrED&E&x*jDse0c zR%O+X2ql1ZO`wCCa(bqwSZ0z*1wuB1ylVm)<8um7H(*Nu>RA9Ow7Y0fC2+MTFoRMg z(xAc)Vc2#sXJRB1$vFd{1(!&fDuZGIWMcMeKR^qOJIFLY7|ebcq5-#_&h3+QQ%6%4 z-npbKDsc7h0-){Tew~6(2nf?039}9cpXc8)Eq2HQYx8!56TmAN99KV7Fgl6MZXlch zo|s+0NyAsE3Z^1}HW-BNc^I8~JdA>96-YsVEGg3`#s(<}U0Dl4#51CRiip|k;DB&5Wnq-M}F7DS)tdW;P$=Kq)r(5(KG zHGvt_`oLJi_2>swBDQb|>0^LMNXhD%zCk3@kl;T8WR?F&6_CI)#<5g`Z>Chf8%=&^ Qk^lez07*qoM6N<$f;Fu700000 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sad_song_128x64/frame_14.png b/assets/dolphin/external/L1_Sad_song_128x64/frame_14.png new file mode 100644 index 0000000000000000000000000000000000000000..3ad1f1c2d64d6f0fb0074f9b386f73402e7221ea GIT binary patch literal 1153 zcmV-{1b+L8P){EXOhQ^#(@`xpt(&G-XSgTwc{3IClOysGh^ zXHD3F`Pskkd%o{0^&VE32(ViHm5q;K7#1VN&G->tWqfnNGV0&xS4ScCs3^1MpljhJM1R?eP8rE6$emb zt!fp!xS$hE1km_A>8g>A2MxPAj=2Ep8u%UTcxSlw1MJR6O&mpn`8<5K)o1M7T=(y~ z8%S{h9)6zF%{%*b7z94g@5aQ=uds3*l?=Q?C;=oBqn*gvSC6r%dr-%*KdXD~P&F8A z7XD= zX+B*F?M}fG{qJf8RaD^W-vwZXKe4Uru+k}%X$kWV2B}UVszOMOc{|bxa4HzYWEI;T zM4+-8NGHHaV0HhfN$!#rEJXloFu1!ua(AFnwG0&~L4Yb-roS;ZC_w;EP%7ZLYHvb0 z0(j*Fyh^wc$aDiG2mp<9s&nTbaHJDpHa@rb6tSJtl2P+aSt^i90Lu8;uojS3zb&YS zB>_&Bf-4%|t8!TJ-6*8Dgzfh9D|ksItPz|bIyV88(Jho717ss&g`RGzu|TB&nob)C zX`pusSv7%5Y2tJjV1S*vjukmBrCuz~Ul;2jKBF#Z7b+>t7f z@*hQ3GJ*svl6f_;g2A&s{BW020gHP7Eac2oQK=S6E%jPsd}k8 ThQkno00000NkvXXu0mjf0z(G^ literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sad_song_128x64/frame_15.png b/assets/dolphin/external/L1_Sad_song_128x64/frame_15.png new file mode 100644 index 0000000000000000000000000000000000000000..dace07e837079e675d4e68f82217f27cf94784f2 GIT binary patch literal 1137 zcmV-%1djWOP)%a8*gN8<;(li6CMEM9DwWva%ydCY89R z08#@WJ9svi@jB8RxHv`tZS;E?O%svQjK8-*78MW?U|08V;7(lPe5)}%h?cRM7?uP| zJ8hYa+K@p7ju1e~`s}rX6|$)Sn*gWeKdbXs^U5&(8A9s-ks`<0GN>j160>LhfE73mBg=AtWS10q+2bd{AX2)_7UI@BX8QeLqFBOkR z#w1-q?pRQ30x$}21018?GJKY*AQSI2+%v{#N?5kjfQXnjLzpapF9#iEg3aqic*140?eEr4eQ>V z(`P`zk^nbb!HCX3Rg^)T+YAtuz}239g;P@5%D|^9fTipkPSmsQvj@U+fM|F`=QPH@|^1YXVDFJTD7J?iiNOAO0um*SqIGqe4 zIKKg*O&s!%B9WXR!HQ(34zhTIB*4m1)&wvTT0?W9G5TPK7H&&`CHg&U0wcKffj+sD zj)NMz*D?T~14KegR+I#fgZ~JCD*urxpn-HOQ7Z5UL>G=LP}zsj00000NkvXXu0mjf DC4v5i literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sad_song_128x64/frame_16.png b/assets/dolphin/external/L1_Sad_song_128x64/frame_16.png new file mode 100644 index 0000000000000000000000000000000000000000..2f2321888977fb38fdac72e359d24e92c5f79ebf GIT binary patch literal 979 zcmV;^11$WBP)$Z6FdX zrvrc-;8|bB*OB@_MNI(Q+3z)IoEdqV@ju5PiwhhHU|09=P&8Kk}czYXLWzQg52 z&=YPAJ>wu}I6vSnfLqBZ&{C2KAnPC_11Esnf{eZlzzJXnvOtjAHbW!;&NhcKNErt* zD{2m~3m8H`Ypo&eKwbtymH_Dt*LDa^VPtbJR{@+1j75-lzgvRp0_l=K4%GVynP#pQ zVFIvT2D+_N4s>dZAk#7sQVOQR8OW$aj{@Q{ka`KwoM|Wl@g`IN(KP|;d4CSGcB8pK zhyW}t8evc~u@wOIEC842nt-(!QS*^b*b-ow45GUL(lWq45{7i#N$dC(N`E|2aR80S zwzQMMw=;+RXCz`7KyiVqp9@$WJ?q#1>%i0jB9g(CEBqD01rT|I$P46UAkBjz*~lVP zC;?C(4w=2j>tPH^aH(yGevS}A04Q5Zi!Zi72SNav%v?Zo*4_l10Hn)6C({==MJ@vn z0WvUbX8qGyG7Ku+Aii|{2t z3RfgivvHT@doKgiCBVwI5bUrIaAdK_>%PSf_#8m1GFZw!%l8(nWcY+c<0Y>a%Rr@+g34!Ef9KyuZ#&{M9@-YVb?h z3$`(~{rkT1eIL|yyz(S~-RMUS{)`sbnJ6;z$KoXD!KcuV{7fQY3*ct-qmn_Guc$jf zrcTc20xSV!82ycL(w9lF6jU7`V`pX)iRv;C392jItOi!!i6oF~0eIOzQd&8QB$7+0 zIlyfPAP0DLE#v1%*FZ%~0Nl}U)oGj&c{}6p`yh%7TnXS*_is=y{3iIXooOMoh}A^5 zN#O0Bwn&}Yh~NSu0=QLu^xDCWXf8k&fRz154gT)BBD8;nYaPHd_)@Es*>QRoKu-gv z0+`)jU%kJ9)4@{0i)Q&FVJd-;G&ZkMI*F zJA!(+HS~y`9AW=}xd5^fQJ|#+6F}5XMg~p*2tls3inRef4ZsQDgtkEXV{?i{-r~LG zNdTN>4yBWjIufNFT8KHosiP1&l49+rr-e8mrvaA~@H)b!9by#5OOTM7LCxUkvIx~x)*?pcTGTBjHvUGhFc2I zoD8D708$!YZwW)%?j*JSN}~*nj)((DR>@wzGk$-3-~L8C(g2DJq<${ow6u%-1hW({ z+rnRMob&tJ-OL$rF9dnsAhH5E4Zt`+Hlhd>N&wV{Lss9hdKiNeTxuJRfY&t87z2(RT+cKBApq%JIa0xC4NJHHm($XW zv$ObwmkYQG;PlA22b6ZK<`=UHBf>T9*a|@NR&dnd&lm%bcx@{nDuKH-{SGOsl-fr) zv+wqvx(czb47v-zn_hrYojwNLtH?uvC4h0VcFXNVPYX?Zesu#Y!aD$0Bv!fk)(x!F zz)}EO6F}`fh7_#Ny>kO^;2l7EGUzEhst7Dyz*GPTqu(1eCzuLg7pT_>peH-wSq8#8 z=sji*@G|->YXTn3+HREw;O78YK@sdP0ifEyg$q~*WgO)mF7N}=(|~f;U%_zz0000< KMNUMnLSTZJy1kwN literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sad_song_128x64/frame_18.png b/assets/dolphin/external/L1_Sad_song_128x64/frame_18.png new file mode 100644 index 0000000000000000000000000000000000000000..e4526da940cdca01fc44819f8672c6478b5a0e8e GIT binary patch literal 1049 zcmV+!1m^pRP)I=Dh!3;sPF&E>5n5SO1b!omuWw#(o7~hJHd;E zF{HJW@)afD_x~kot-pTm8IRXmwO^@XyvNynua3P&ElL4o3x0ze@cubp;)mzOQG;K~ zUa*bv-oNiF-}ga1hZdCpcD)}t_%m8yXQFtS|89HAIfnn+xyfk;qYi8C8mos&r*Yym{sKhm&r5=kbP zaB~1|10V-@^(^CYq-UUFCID&gztw1x9?8x4`!>kp0xSWX>i!MtgL8tfZOj`&%UDee zO9HtoZJ8Rik--H_1i)2&_S(UYY%V|+fR+7c4gT)AGTi@)&^kb5@U3Pkv+MLOfIbXd z6~JtLef2vBNdsF6FPr6$q^SgEwqRrzPzyj(Hl2Nj(v#2w-IK)`NQ*(%^Z(aC?%^k{ z>C6xdo8u_$yI6H6xK_Y zziI9biI7q-6-h%%O14@u4+E)(0Lz}XTc3EDPys~O1T=}=Nf@j9MstA>0eD(8LZfEl zC;;kS04dQm0dFy)Cxc-_fM+s@?gB`|fOtz7(sn1S?N^+oU-X_$#l!(DUfZ%x24{N? zXUn>!#0&!{F7Whw0jH^*)w=~127Ko z8Yn_N0VM$H%OR_CZoQ0QQ7P=ti+qm|LI5aJ%D-_r7(xJ+tX#md*S-Xt0JOtEV+=TQ za3hBS2mwfTmZXn?&tc$a0 T!W7^d00000NkvXXu0mjf_+{3o literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sad_song_128x64/frame_19.png b/assets/dolphin/external/L1_Sad_song_128x64/frame_19.png new file mode 100644 index 0000000000000000000000000000000000000000..b6e3de1acd2fcd3eb3215288e06d304d9130b170 GIT binary patch literal 259 zcmeAS@N?(olHy`uVBq!ia0vp^4M6O`!3HERU8}DLQk(@Ik;M!Q+`=Ht$S`Y;1W<6R zr;B4q#hka73>g^|cn)l6tUs@i>@Lh|Tb1^VH?rKiA(pMf*kKOiBOZbD1`FmRk_zV- d3I|%KHZ+B?e)o$HIY19Ec)I$ztaD0e0sxHPOo{*i literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sad_song_128x64/frame_2.png b/assets/dolphin/external/L1_Sad_song_128x64/frame_2.png new file mode 100644 index 0000000000000000000000000000000000000000..a76a000224fe9a1482500050448c90aced435ca6 GIT binary patch literal 1483 zcmV;+1vL7JP)TTL$3 z%}SV^q&`9b?oJ=WV!UZ&?(^Ld0_u|aoJB3vDeAIC+#T>I*7( zT*T|8_fd_l1h9HO^xpY*m>=Mm1?6RL>pVn*Yg0y(vXnJ{$JZ}R^+Usj)JM+{R zBe~v!1s0;5J`Sic5E4O`=KxxghU_}j8&|%- zu^OK$g)N{G@Z-0E-nx2vz7-XXBDl8JdiJgnIhNqOITx0t*7;+MdqjX}Ikv*vs~a&{ zc+PtYM+rc+RdFuBjc?6=u2&gNBEe3AaQ%VS?8nKLDgc&i5SoE08PsRoOTUVjT2R?j zfbuk1M>>I$_fT{2Gm9*bUYT}qcI}Gy&-GI*Iv!5~IZV%{ygnPXpr@^H081Im&5Nw1 z$&_^2+6Zbb=WUc6$nuJ=UPLb~npMfv5V7TDBLGI$Xp+b8?Brp1FQKY&8v47oBZ zKS>@6o;!usiU5N-lc#>Qtb&ic88E(6NARG7D{ zc?A!S>Okd2+W5D3)*Aoc#Bs*;{HZBm1tu3&GJ3mWmFI8tA}G4PWzQD*eT*@(Mz&_* zP|55@wP(^8%}X)_jm-0`@7c3OZvXzg#`v0ISV_-Dx#PvU7KBEI(@}uaL6RRObvOb< z7V{|0H8?WH;pSJ7AW8tdSC8?vqhNw1KyyR@N!aOn)&g%%kTGU-4p-H9^BeOREdoGj zoBIS3z%kmmN}cdgM?I515g?+NCnDUL1d;I-h!Vh~>({E1N6Si2!@KV>eBVOOBIEt7 z+4tva1^51bk3N65DseRuwC308YLN`XyC3tv4AR2Ar!#e>ml&;FqMbhrcAWhaUlzt# z!u?$%!PIR=H5l2nJzZ%Zt;OE!OKam=8KotKD_>Ye|ADK7v^`x7UY)+?B+5XM#KUUj zDl$Ow&Y$_Ybq%;joZDQ{(<||o1j5a(JmV&^FP4j35h7p`;K=A9)ColG^{WcdLb(Rc zV_@}A2`x8)M6duK1vtX2CIWQ89bn7Ai~^Vhkrr|#yk#pz0*VL~fC*=Gjf^h8%0TI2 z!Yy1$E+Hc8MD?@iWqFqA4pOqD7CZNRBHYk}1jyVx{SvGY=_CQP`I=!RV`%zV?oKSI zI!b>CkZJT-=G=({7SEQ-MhTLabMiG^GFbAjgLhOARiKhyc;1ib zAw(Q#G^!_(R5Wlrx_M+o&v$RhnOUm>uIpe+&)@sJESb3w2{LQ$hU+#^DordKv6ME^ llBS|G!!mX>!cHHR{{X@xiC$hD0zLo$002ovPDHLkV1lo8xLg1L literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sad_song_128x64/frame_20.png b/assets/dolphin/external/L1_Sad_song_128x64/frame_20.png new file mode 100644 index 0000000000000000000000000000000000000000..b33656867fe4b0e67a4fcfc22e30b63d8a3a28bc GIT binary patch literal 922 zcmV;L17-Y)P)5^IfYJUO5xjWZVz>6w^ z2%stZU(UxkQ;G;)2!KigwFh_dN?EEV;bqDJ5uh~|)^M0oF|^Kr?TyC#H;YSpU`@r|LOp(fwdz-+GwNt z#x3}73aC4GsWEHw7K2+Jr`ndV9a2a{=dHPb7y)iHZVRg_pr!z~Mw~T>5}6@ulzzEV?xo(4c4#2UYvnGF7h0)&bCJEI_V8vIqk8yrGO}uL3H&hj1XE!h8x)EV4 zMA;X#`=>&HQNiY9xz|Sh!yeRgfEKGSjE#Oba=bU$*Ca0ba3~g#bv-0MSULUFsM7BY zv`A})04a@0uv!MrK1z8Cjpe5X2}B97oAhDw11g8L$KN6!Co`1;qy&?H$h{Q(uyO{H z6Ji9|8JzU#;}s&;6U-?<#V%nuqNjDdwQ~|w5FxZXqq8}Q`qwAum zz|{)i0o;MaLI4lZUlApev3LsL0Gt6uP5{o%az!h&-H1x@6u=3%0*m|~M@C096?h8Z w2pj=Mb}}e(^v^yt5n;?v0g|teMo{07*qoM6N<$g1GIJG5`Po literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sad_song_128x64/frame_21.png b/assets/dolphin/external/L1_Sad_song_128x64/frame_21.png new file mode 100644 index 0000000000000000000000000000000000000000..6048810f0b3111dfceb9317717ed0d743e250c44 GIT binary patch literal 895 zcmV-_1AzRAP)1;8G3Bv1+f4a|b16999t5kV&aRImz@N&u`uMh2At(7`BB8UZi{mjN^a zU;&#@DFnb4R3=aefDKH7g%VM-|PZZ4&a$Z5h6fz0V)A} zSPvQjL}bq!T_9Be?Sag9D3gN}0knj-#CwRKCA{5AUd`=IE)aSW*njvLY)|4xCyYo0 zWFyQ0WeM$K;{~93zxC^|L7>x*?lK&PL z2w4L>&BDG+NZ)}FV046+fE8JF*7=AG-Q5MkN z)XC1E70A4dN-qKZs{JY$BQa^lwK&D@H-#Y`L6*aqD>u+A1XuL`?c6n_Dgk{Eq-|W5 z51W+*E%|J%W6=;hz{m>k?E3C`YwPzD_psn3KcbsB+65k$0I4wgrq0ptcaUVE=iNd? zH*vH89xnp>BV0>|uXen9I>F3>wZB`@3(Wf_GXm}ckk)JM zvv=coT>|YUg7n+KKA0MkXTN*i0FD;2%;yB@0w`rnp!Cq@N!@}%?dZbp%sS&gLv*d_a0SbG{6vcHti0Ys39AnNR4HI|(NWMn_$z8h&S5_}Gz#BTpt{0p9- Vkn2m%OVR)U002ovPDHLkV1mx{d7l6P literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sad_song_128x64/frame_22.png b/assets/dolphin/external/L1_Sad_song_128x64/frame_22.png new file mode 100644 index 0000000000000000000000000000000000000000..657788f2b0ca93da8d4d7e875b6899cfa0ef0267 GIT binary patch literal 831 zcmV-F1Hk-=P)i_>TeTqaQ*kFw98QAkusp`pvSsn`xDdH_x`BSQ25MClapr~x>|gh){X5Umpcqy^xF z#zzn>04QPykrDv*Mn@7Q0BB+ck`4go#zqt!0H|UGlL`RVMn)DD0O(=_lm-CC#$^x< z04!nyl>z{^MrG1(^aXfnw25OUWTh0EO(XCCXpE^%7#-*N+uPD6C-5y{D~4#y+zA3h z3P2~os(~8wo_~8wB2WwhXQeg2Rw+f@movZv3xKrocAEdb4u&XS6)XTq(m;To0+0Zp zRR0Zv1OQQ|rT`oO-|F`j8~~>Z6;dbwb~e&XTuUSU#3uqM0Ho^0zr>`Rvzw`iDF6e& zuBlgvne{n6K|m7pRIpSl5P4T6S~mib0Ep3eiy#2ds8vn7#sF58iUfRk7htjMDmms8 z$t|;yjIt7a0=!LX4eUk(yq3|{=A*zBngBbOZfiY{htedtQS=G$RCfK7#Y}|j3;VWCetp=M>Dc}l#NA-=&`6j}D9g_fc1gjLZe8J%=!luXw>;MPEvwWMQgZ+h|2Ge$Em_f` zO@Qdvx2^kXulq6uz`F0B{rzsCZS2&DHUSWHLJF7xU|IcM@pwv=e-c#!%q;d;o-0Du z*I)tw;>f?&%ooFE?}2?hK$8HHMIPyf(B5%SWLZ$#0q|P!tIyXnT)N#`@@QFCK646C ztOZDS`Si??EEA*23bL&LL=2i*U4!&nf08eXo+kJ)z$fZQFMtR#iH=$TnH&)XwE!Z?B0gFHWN|<=v;sgN zg9s@FkiqaM&?J)pf1ioS6EXG{E3#0&~4z+aB7ZEV^9&iF+GF}GY1R%wuazCLLfD!eTgsZ~Ok~D(wfi|a9Xq>0#O6(GK;EO>v9e7)RqRQVh>9JTB~<62d%W<+PP~B)p1(T*8rmk zW)2&jJD%4s1;4eGi4Xx;EZ=I~6LoFD3ile|gs5VIx(H+hEdX2v;PK;EM*v^1V<%M) z(6)!I=GxusGTs}XSReV46wqJu)Fy(%v`L!+dWT8Y`y)UQm1z$Uontj;2(yy52WZR+!P6bz zFcScpfzh6)5uFCo_W+F(mwF|b)Bs3^ecS92U4)eqYcF*HAkCynor@rXC{nEjFcZ}a zs0zUHN32Gv#re?%n+3lf;BeFg5YdHs6mgz~VuTH;hk@4b^f}>OK`K3f_bS@mv9B|I z^%P_m5#J_#VI@SCxd3Rax&h3EaL zi$E!}{ejVrMqpb2sK#r~mPB@c93}c2Ua5wn1{hhH<9~AO%2x19dB0~%04Py1L;8o& h@q6hXOTF;0^MA_K)*cHPfCK;l002ovPDHLkV1m&DVDA6` literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sad_song_128x64/frame_24.png b/assets/dolphin/external/L1_Sad_song_128x64/frame_24.png new file mode 100644 index 0000000000000000000000000000000000000000..d8497ee6aff403f17c973e5939ccdbba2393408e GIT binary patch literal 859 zcmV-h1El(NY>tkag$Yio} zN~t7LO0lha&e`;S7X0(Bz%JI}M}^h^9z--9r2!%e;RU4uJc(vLIs-%#z#A$9{KS`s z$^a-tG9rxuA~iY^6bAT-uLp$z5Q$<+cmqUfXjI@0fJ_8)LK`4L1ET|LfS>p_{CQVE zgRgB{NpqD_sJDqB4X`WFBg9wvTCm$A4V(dX1eq1ScaPPw(Z~txM9_*5lH%o@kFLX- z2A~FktKyeZ#Ck4!F5d;p0JCTbbz%b66ar&_*#^8lXG;TN03 zgiHb6Fhvm;128pEbj%I#6CVq@6j0~aw_GbR3pmZ=EJin-1xPo_XuiI3ubbxmEp!p! z;pyc)p-19B<=QCGmXRVMf zq}i|-G zEd*JC6kU!DpxuZP4K+GCmeSo3>?j39X*iVL=mGRBpkAFj{cg|>nxPt{^K2Wy>H(~J z=jc9>BLr*&xM6J`;rX@_6X^@$jd&$8?e?A~7Z+}Q}~89?((;~pvJb2A7{ l{Q7r2MxwWWm#!Bh{{aIa1tZNY2Ydhk002ovPDHLkV1nhFYBm4> literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sad_song_128x64/frame_25.png b/assets/dolphin/external/L1_Sad_song_128x64/frame_25.png new file mode 100644 index 0000000000000000000000000000000000000000..d647aae1bf1259e85c91d9a4bade68db2cf180c7 GIT binary patch literal 854 zcmV-c1F8IpP)i+*P-+fpiA(m!v8JnD^Eh!{3wrNSv z^L!u2e7}0lPbsP0ON$@t2ArTNepFl=K%0n$b8CQzVp!nP0I&FbxHNzz(ah)00MP`n z#+3nH@%eFM03`8|+H#5;1H8!K!T^yX4h?t%po)*$E-LT_c##F#08ye08CU~&5Z^-^ zXTTcZMF&U&M2IXeqz7z6$cgx~4I5I%^1M;xW8Hv~U4gOyv-3-#-YoM6lmV!$3ZE&D zqn;ZTm=Zt{f>XSd65Dk=0?`Sy55#}^e9bpuev-%0-~QG8>)Z{ea3uu4jaBy8*0 z+sc6IRIt`!jOE0VH`ELu`GR+@eXHO#p-KP>p3J3U0Pe+>UXAAH1Kid*6fXf1(J<<$ zQSNu^>|cq?63|=19f_Z_L!Bk+b2wKSJs_F^Bwj)-{?2BQ`dVJZl0Ym2NWWB$jT~hO zxV03B7(lA-OOQ?Asm+|Y>;v?mjsji;3?S|LUc{F5oN9!dM+2;#K&_NF8v*2>d(*t5 zsblVPfNn0=+rnqmMyUUuK^90FHUmfv`F78t4i!Cg*d$=-18CHJ>and!pB7RhfL5Cj z30VT_)ITx-vN#$XX8`0EIGg1|Zo3vopN477_G#RwbzhV|jWpXOz+3d3>>7#;>`Wk6 zwDdZHh|y@lmI0zfkG2UA-y7Kg8V_Op$u|1z9Vg*Sz{!1XFagN`o}wQWv}ap3fK|hL zJ9)J39X$)cmVgnO!bCU0k^!`$pOXGpgh5NU8Kibi4S>{GF6t@b-j3mNCNgRk7#cuR z{4)AiJ_323y)4jMCeEgaEOv{KtARBwQR{Vv*0%!n-#AAoC`RjM057S3@3^xs6`QSTIQEmk7MS6Mc$`bMD`@={;lz>@kT}i!lr*8EBopwnGk5C5w82~l< gsY66AtOb(h7u{G0ERjgwy8r+H07*qoM6N<$f?z&&{Qv*} literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sad_song_128x64/frame_26.png b/assets/dolphin/external/L1_Sad_song_128x64/frame_26.png new file mode 100644 index 0000000000000000000000000000000000000000..83bc94316b1b2d3c125a8f82485caceb46a6bf99 GIT binary patch literal 821 zcmV-51Iqk~P)W$4UGXd zI#3wkiIs-}yaBfOx18i)8 zG5}NcGsRj1#sE)AC>sz4*y8&EVStSv?gn74e&*OSt_FBQ0@-mhz!o0^_bGrKb|Zgz z+*C4Yt=qNPh0?;@08&C^;ajiQd;BWIVEDxliR8};sbvJ#FOE?4021P*ltN00RxoMI z3@}@9(v`bS@Pml~(8S-oQ8$2;_!>x#0<7kw=>b?1jE<@SkgGqldM}`60LhAFseYC? zkEj?x@_FBal}7>i9)Pi`m=HGrTlGuBW>`@JNPqa=>)jcL*;rbL8Nl25t>SJIeB$yt zxJHjaqhemIEapbg+sM|47{DuO=kEP34y!nso+_pdU@87pg|1ey(oFRLn(x$0*N!n> zR#!{KosTJ9YbD@%fEvf4^(+6b|_ z=tsH@pp`u7;qop{i%ljB(*XRtfD$te+}>gIrpo{p32VJaJ`TOY&}lY+cePvzJ!697 z=UH(DFwoPxi)8h_#;x@N z=i^ID#@6#cvgwKjKo0tjy)mR6h#nv_`<=fKwz$1q8o;Xhdxl@>4I9=*K^;;582~lV z5Zc}mAjgl$hXGbh9>aRQCT=}I9?p-+KmTnh%&I2%GXPrkAAiW9SDca0Vlfh+~6ib}b00000NkvXXu0mjfnK*Ln literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sad_song_128x64/frame_27.png b/assets/dolphin/external/L1_Sad_song_128x64/frame_27.png new file mode 100644 index 0000000000000000000000000000000000000000..9f4e0ce7b950775716434b1cfafd678a601b7230 GIT binary patch literal 791 zcmV+y1L*vTP)(RCt{2oZD{0AP_|DsQ>@V?n6c65WpDrGK2R>l_JHLH8ZB1vfe(hRXCSd;6koq7H zLHu+bW}&oDH2{kEK^?f6K+P7vo}*qqx{R@>3>k*bi@gy?{%=53gEwXsAeH}f z!la`ww%-T*1SET#=$ReY=M+QF;=A`P--Od24?KZiL3r1)nsWq=g_JZ21#;_G0- z03OBn*ya%?43OxdZh$B=$t$V`Nb&ViGeC-O0~G_L_;xsMfJov;=*#=$!&U;G%5X#xUAlR(Z{+ulp_ zZ5{9C6E(n2$i|3SYyDmy?Uk)lh#5e7W_X`@RuD13?z)ei#jE5pfx(^xawT^VMuO*7 z5eKUQR(pNO?5Bu7dv=;&4cihx8R%Z9yHMVm>iU_r>BO?&*<>scfHUl`1jB42GarCK zHUPE054|25&POK6rC<)p0MJ3@sPjX25Ggc{qn`%Opk(eTJdJGwa1A(B{1l$XmH{Mc z`8!*{-DGf7{h7orXajJG-`)pm0&KXSC4|s+Jpq&fv<7EqoyUsbUh^aqfDOPo zD5G#WyFaBuxZCC@nE+$}iNS!b-_*|rk~O!bU=K5)Jr}@vPb?nBs%L2ciLD+6NK%No zw?~1bnJxjA2Ma1Xb_r?E8TQYyJRTHGR05&g2@g3SGTX1;ZA+sjhC>5b?I>5f?us@e zv3Ckq9{9-tB6V;j^ekGeMD|+kTk+2T+~Qj|Q-7EQ`g+>xI_rJz`5M4m!#Qb<@U(Gw zl@hztoMBs{-k{Cvl=$8U&U^c3sDDS$DJbiEHt)*=L%ce93fIPctix~#hcA? z$d>UkQIws)qar{lzvbTk_oKhHqW5On@E!%&cLA8$--c4HYTNA8Pvq)f%O6cuDNq=tVT=F(002ovPDHLkV1gaih$8?1 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sad_song_128x64/frame_29.png b/assets/dolphin/external/L1_Sad_song_128x64/frame_29.png new file mode 100644 index 0000000000000000000000000000000000000000..63babe7937426267361e42c5248cbb1ae22bf217 GIT binary patch literal 954 zcmV;r14aCaP)=u9b`?+ytfYUnP0&WZt;>+O1 z0D%lH3=pGV4)X>G@ntb>0IT9#Z5IWz1~{pcEo0IEA-*2w3=rb$V#)v^zCLCQ5aRE_ zgaJH?@3GA-KD%CgN(XvJ)c~dFqr!?XhaM^h$by!lk3`Pbg%RWtH2_xl(S4Ag2YAvW zR1^5VFN;vErJ{aLN-63CiHI1W7Jo$?%(4+Cz=IA0XPt}{f7F4a=cJUre*!va17JE_ zYDCs;$fG~ZfQK~^gcSjLEi`-#@Q7ssp`*WdU4()MV(lghUuOBOV3hQO%tt zERTjEvkmt7w?H;PZ!~jstkyHa1VjUr!q3+H{O>CpwQM%cCE^xH1}HlXA}mI{dXre+ zS1%`V3b+P&23Q@{EimC>-G&tta2UwzrV?Edq*2U?ttKi>Ks`F<5YXdHgqi`gTwYH7 zkgA_jd`yi&ZI&A=jKBuiF~yaeKZ*%P8`Jvln0K87$N(1Bw{p$L-Z!GPkGaVNYy~0Q z^iCZqM(AZGA>k_q3j+nqFcpmbg*c;w(Ns?PK%~CsmJvRq8wF4WJv}i2>v{eKi7_cY#K)!##WBtsXuHTRv0*>-CYp@sqGy17Vx+~1yb5rWKMsx-kb-Z@<%_t|3 z+jAq!Vc=@GM9H+RFw6ekhRUa5Go)q$O#Hv&-4-}95j2A$4)t8XI!gwCPXExahon09 cB*D1(1%!yrKt#6=eEkePcGe4SR{!C5>6_~ZC^0^B`Bug1p{pc**$ zSUs;&CN=&cz#qr$3GjsY9Bxm56`}Pn;r0Z0B0vteC%_W{a=1MKo(Pb`?FsNifE;d5 zfF}ZcBkBmZC&0-F@CoF*1Oa;QKR@%c6(^1G3CGXt^QV#rX=MAk`cc<>#2|yVizYs!=lcELn*-A1^9;G83tLYi>dg*>tV=Doy zMwwnc#`yU+%n$I*vc4T8!Amr}Hf1!AF5DceBSf){01=O*4C^^pt!r728Z480MJ-|^ z&FkShl_c020XznQkUSoK#>TOL%ONb|xd@PjxGs`ZaY04bzdHdx_>()tz>RD@*f zTKIUNjy;bqeJl3Z;2q25P}JPl&Q99t7bEepVY%s+ z?ECa_K#hTr2)aB6(8?oR@8dI`lW7@8AsR8Xc~QA|PIZ|#b(^yBcgJ&p6WG}r_P`UB zB7Fxc0YCmU&|6ne&-bDtL~9Wz8j)J_>xYdA%Tnw7F~&V2K(-vKJfEu@FwETWk=wt&uQ9%+7*^7=QSNxLt_7iy;dB(>bdcmnNga*=k;Obpa}AD+ak%+a zB#06K@6}^`?I@UF3D6u7KoWMkp0&W66J(4Tox@c%-u%WqMvDLt+U7oi1aOQtu2Ls_ z)KS-@?>M=wDSK^11eowF>^aL$qw)q;K?>y>Atafq)6NJmVexXU1gko?SHWZ0og|>v zUTOT+Km>>==7|V*CP8F;1)>D-==!y)NcYqjBMJT zuC$NVV)yl>wQ;SC(vrfJH>{%nz*R!po~{P3PG55pWuQpnVKs6U86bJ*&-~uH2HYdg zZLa9)m3T`6;bvE!aTD1a%SEmT5ikjGWb_c~1fur(RRw6FTm$DZuzIM3mYYB#Sb&cL z9AQ=y0XpCguw`II0Zf8O3%L^BvXvqMMT82#gfqHEMwee@p!8tEEnG=1AtLKU^|R<@ zxt8e;QnI8LJ9m8|+|Yvr$UJ%aC0HTSNdjo|HN#5A(Dbo9JF%eZDE%QorqN@Wb0-p5 zJXML>y=|swa|EG;ln+ zd1OS-cW=p=S*rrB$HA7KzxR4sGIJpkWY*jb*V90$G_h>NQrbjInu^j4%h=HfJAG9C Y1MCQTcMCGG{{R3007*qoM6N<$g4LV0E&u=k literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sad_song_128x64/frame_30.png b/assets/dolphin/external/L1_Sad_song_128x64/frame_30.png new file mode 100644 index 0000000000000000000000000000000000000000..d062254d931920d1453c1aeb07d47fee8aa6b130 GIT binary patch literal 871 zcmV-t1DO1YP)j0{WOu1l@41?3VCIL&XRT%>!_+{t@}DeOHeVcBn`ueSlijTf>#1 zy8pGn2w@zPTL4u)XBY|bI*0=xx%%GZbHvx7LVrMpN(IoE=ckS!AX*nu0JsuHdhQTE z7j^)w)gQU#d(MJ4xn zuKaSF9mHwz8UPFV(iuYOxgiAqdAtR9mJf932Efx5QmzZo{7ldZz{pUK17-4u^Vyz4 z8-=evfQLq;O6T4}>eb44v|I&XbtAlqrz{ZZJ)*0 z<5=GLq~t@sgr~;uRf{Ld(Pc{bjL7#MoO(v_G`SSqt_s$q48mJzPpbmq18|x!ZPWq~ zUBZmXrU-& zoG>bIT>)CX^&TVTyB-bHKTorB2t)y}xL=m5gSis8!@-ok19VD%K(zw&CRsGzA(VuG zxEO;)fISM}T>ZTc*3ifTP&X0;7mU6MpfKBixRjs-SiI~9{ z@Wv3r6At2ebY3^csPf#^d}+S`2QBg6%rORV@z-#K0bKlbm^XlnzZTO5uq3{vcR^y- z0B`MZYnU{Ei@y(Z25|BBV#)w6{(j6Dz{S_WgaKT9J=6{0;_ISn02f~$H3OU|{)z8A zLd5`Y?8sC3-t{0C-6l&7kc-|csBm-WvBUsL&|LJM$mu#a!hWCzD21mAS~Cpg|IR!y zfNBDt=OK^y?h93>in6U3qt^z4fJA^?e55d~w$k%F63jEb4CIR68$rDY5@Y=Rad4<_ z1yd+RPorPM3~!4%QwBhe{<2-}J&ug+5ujxhUkd6VHUKvSOOiGp{p*25fRV7J_$^^M zJBPjsz_4Ws7zyiQ>LD_KGy|vJ`D1{cnQ}hj zDa9UnG(eT0-Oh&rv^r5@1a2fKrx&j<`r)@IbY_!hRkB7sWz4UY6qQQ%2;N|xuMlj8 z&wS43-q)ih_RWdqmlg3_BUfUC+K5lTN0r96F;x~`X6A^t(%f;xD?_u zsT?{F;AZwz^P#Ux*Pf2KvhZ4_UIP_*ZfD!q!#QxmH^KxrS0tN@06EC>fLfB5N0?ia zK+^_Dw2aVO`?uPuZ2+ZqZj1oaNni-NA#}FfthOO^QzCdo1n5CY8hZ(2ZI9rl>0mku zT-i~ab$|YO@4nhKH?Jm4t^z`+Pb)2Y!0wJ(5t>_5$Su==$s9nM zRL*=`sVqZ76rP-b*Z^AdCQ_8q*~1Mij{w}0Jn87?&flZwNQzEQL}UQY=;s{iqDS`V z^P5I`3upxBb$nUf+q)MbOm9yB^$cV?TCkUav&*GXNJV!jr9|vz;5h)r6uo(O2G085 z`9Wst z(kp~;hlRNBIo-XkYf8Jl`J3GV3A4oCnPm*%;`d<*1GxD8fH#1P-xp{DAc>FE7fWCb zuSY=Gr)@Cuh{1r zQw*@V}q#y?)z2?Nkt4d zLVwH*kc!_bTpC5(1bs270xZQ3;X4BCdKwpnW&=pXmxy^?*XMd96YLg|raudy*lm!J zX)-{s*-zaMDGKbwkd2^-WM)%>d#PxT+5pno-y%NsS+5Xm=$->Sg{p<})Ku}0!Yc$D zx-S7-@g-%X3vY+Y0GioX6JH|y(^S3`=xL4s7W@3%3c`i9N1OvlMQF|3;|xa_>LVv20)t~>A3*Qdpd8v4A5HYN#Jb$NbZeh+*SA( z0A&EV!fVbKJd?(j=z=m{t-M0O_%byiI0r;>&nVK*#%ug}!kZV$7q{w1?LieKHf4%a zfIJh(p(J!JT9iaS^?ZTa0O+%YTJxP>mW=>hQX|w%dtww=+5jgCW{t^_FslTrmcjSp z>LF|Re)8tJo;UO*k+HYv${c4W#Ecm z*y2d}j+iEaJ@*DUd;K-gUNS|19RXHe4_)*;=3Bv*yK0)pw3cn3uqFHtPPb5U!iRDy P00000NkvXXu0mjf1TB{w literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sad_song_128x64/frame_33.png b/assets/dolphin/external/L1_Sad_song_128x64/frame_33.png new file mode 100644 index 0000000000000000000000000000000000000000..e80a66743e0d2635b95b069b9ec45d9b470c6e60 GIT binary patch literal 933 zcmV;W16urvP)ifSk=SQ_Ehj6it!4AwPMXlQGr8|Q$ z#%T;8r0^xAWOJ)AMw9FA;AMRSRIJ25ov$ze#9xPd13>(>xHbU9UyoY@coN^!y=ddo z04G0iZ@4o6#NUf613>)!xG@04x50%0Aif8#7riI)^FA2CmT3c&!czsU8HVz6pi2xBEnz7o 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sad_song_128x64/frame_37.png b/assets/dolphin/external/L1_Sad_song_128x64/frame_37.png new file mode 100644 index 0000000000000000000000000000000000000000..9f20081e53b1ec688d552d2faacb1d31f6a7ab31 GIT binary patch literal 769 zcmV+c1OEJpP)ifTP_E#jzau~1&lguZrv`5=iJHy!F zXbd4-;UKOH|8`@H@N1&@?0y3y>=6Iye0RSA2^8KA_X$ApZE>3b6dr>+15kJ@u1WyK z$KxUaDEvOQCjiCY%eDld@IHVWfWrF%S^`cK|H;$9A%GI#MB(3pc>+*;-7JzvTT5wfVc2Hfm4If40xsuuqu4%bw-~{))_W{mhi3D zjxo{%AcmSiOY!sTJbebv3nBwt*98+ly(UdyPuBpF4xe#-E>#1xMm-9qfIa`3K&j{) z{m9%{J(gv_4zv9LxnMJC!O~=p zs|_I(9MAT10iJn7*;z6``VXr8vov~uHwdu{wGz;3)JCG64IdV@5>Q*h?X4xGU$y73 zH?TuB0ZQLXpL25?iK1grNq~FwYHZ|4O8`rOJxd0#8l|@hm;&~=n+YUXObw9M^HC%4 zZUBSYxqwn+mm0&`G;eF_nB~9@le+-P<9cfa9Q_!E4KO22Y1B&$!%=U~bPq5(yB9(z zPWqH!uTfR*de1iFX}@Wu}OEk>K_0ctyW5U>>JqSgc=g`o-b z=o(-o{w2b^t(17j&23;lMO@o4oUSY~1=eXAARYaZLm(l{r9f?&_%;Q_wqxsBKrU*& zZ=iHmAphMP?@%-`DFKoZnFHx7OT?}7hh&aw3Ft+u zBzj9916%vMlv-ax=IHhSy=mE%V_>sTcs>3AHaV+}r9W*_00000NkvXXu0mjfFFj9G literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sad_song_128x64/frame_38.png b/assets/dolphin/external/L1_Sad_song_128x64/frame_38.png new file mode 100644 index 0000000000000000000000000000000000000000..39e62095499cb68d06e5ea366358812372128e79 GIT binary patch literal 909 zcmV;819JR{P)AyT1^X3rK(C^#Ztwnh35I z!0hf%DY@;~=Q>ta1Xl}SmS-K$jrLYq*MkfG_`3k2@g&MC{@08>v!>ZGEc4(x*a54d zc1S$ST?9fv18+B8LWS1&(P1R#3<31;FoGpnT_Jf3(U#2w-UL2Q1}b)Z^fiv7i60nG*u&zy+~WLkFL$tjn@ign$at z?iw|IRAQ+K6}{Zx6HTE+ac|tPzT!pm9-*iv($+YVcp-oW{7Y+zKxP)4!bMAvReWQ@Iq!Jpa2a{ zwf+iXP-rbb1z2R)h&?(V3ZSsI{<;)ff8_HB5E00d1_f{+UvL$n_tLc}GemX;TnSX5 z`+x5S5NZRdClMon09E;|u3_gyxVuz{@>K~afFknXhZl62N+}dTQeUuTE&g@P={`<+zB6Pl-1vF??dgV@C142Yb07dVGMxF_r1z8S8fD2IT?@{u2qW~j7 z13eg=MUE5d4|>pu5{eO^!Ah&TYVCLFJ~R|SFvmyZnxByY{tIu1|-N38_6rcmD;#bq~FeAY$1c*4uiKzILVh*k9 z^mP=dzgzNt{Hfpy^4y(g4M4Kv#PTR_OBAC34a}`)!v~>cUe@mq>HLTH6%qw#G8({L z&Am@e5mzLj00w$5_Vi${g+<)|#7Gpt#p#>G8jF;&%-jup8J6x!4iH~44Ma0Ds-Ky1 z-HG4Z&52#WFP4p!tB{J$+jAztk`5!lKdifI5v~2|T6tu2;TRyDJ>q>;!Kl+jydDk& zB}`=N<~M>~MpP8|ya3)8+q*zwd2VKDH;T^-aA!Q<6&j@}p#Tf)nDgsAqP|=78UapU z){H=J=15l8EadY!XRZUx(%NB*l&>cNDk8il0wD0-|63Ud&LD3ZA>j7`Qu`X%=z=IZ z-xFPzl<3m}$Wiv}A&)0+0JZqs7@*gWMt|e4#qW;XL76@k0rZVsN$dBW8#EDJK`+f$ pP6My7RN83THA=@LgC);M{sLLhS9M2*b{7Bu002ovPDHLkV1m_3lc4|r literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sad_song_128x64/frame_4.png b/assets/dolphin/external/L1_Sad_song_128x64/frame_4.png new file mode 100644 index 0000000000000000000000000000000000000000..7377c6080366bf2ce64c43910380a48f055a2aba GIT binary patch literal 1442 zcmV;T1zq}yP)IZLpYA(@^1?I>^Kh5hh*b=+XFi0%U{l zJx1GQ`fRm-2=L|Do&b-;$FMyCMo8;7VS55R3J}Bg1b7r6hV2RPC_oI`6W~#R7`7+C zqX0kfE`;p~uu=g&fqdH&U}X?KA^p5p51^F46KT0-Z3UtJcAn?ZE8Cvm)_Z06&BRgy zXm|K%;A)Vi-)AcX9ZTdpgQyP_O9&u+)WLxSQ1dZ*@>(CLWc0~Dkje7f!gK<(g0H=B zZvwaiMDwy_-|Cjg>L#UqGLhG#YLv^4sd*&1UO36>rM(ZF*8(U;F!304wjuEHL)loM5><%J+Ery&z; z350g?koreRkdc$ozG|D^|8?NDV%sv|KZa;QYRMdOO5p8~36c7;*m~P&uvR{;{b=JG zz6a2Plp&$00(jpOAfY_AMj>Xc;AYMp8S5nIcg*Q;WzPu#Rj+5D_`zM;ZmhS;Z zBeP048s7VlNZn!ib+A!N1GEu#c!Uj;9c zviKCh+m(XV*pc=KytbNM_DSS^6^@T*jr-LFSG+8zb&SB=i=9N&suHY0zoT<$khWtW`(1=6P=gEgM;R zU-Ba@h~qrZ6A7v%_*wv>g_l+zQoq`GT1Ua&*pcyBZ8+cx9`Me{QTq7#eNOP{AfqqZ zdnUEfCa5ApP>-FZf6F@~KGPHc89X1PHE@x>te8at3kf~71 zSyqlGm=T+v+F4W(T_~^uP-p|`wOa`^O?gzN+C@ntooX!m8&jL2C;$bT+c|b6fwgUP zOgz=Ko@u}J{EBV30#sNTm${)DjjT;*CNwGU5km3@wJYR%0FPtsSZ47%6P}>XA_DKz z8~jqRvA$|U^u?%BNQD<|Lvja58_b>!x(U7m(0IN^6+U($!7Mu~qN^W;=`P?nj?1dA z|CVrf5|olxg1r+=sqF+?_lgiB&(|XnEys>{ml?Ug;)O67k^5bhppLDo8dWxLb!{cA zV|vrzS<8>SW3BDT4OE80NJromyZ@QEqyVqgac|ZJh!#@~oSc4F~K;h3K^VIUCrJSC6y62D+pcgokb6_$nypxzQ`BuML zFpyl$+j&-SR(igq0RCc;bkHNx%Cag#aLb9t5=?YsIjeT^s{oNg57J)p5lsadDit~Y z*0vRqf=%`b3BkK|j1+pTn<%f3nb5{%sew=%OM7awVs3sv2!R?Kb0hkEwF*c+W&~#S wqA*9}s7ZJf!n@r~m)}07*qoM6N<$f)B%%ng9R* literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sad_song_128x64/frame_40.png b/assets/dolphin/external/L1_Sad_song_128x64/frame_40.png new file mode 100644 index 0000000000000000000000000000000000000000..2540fd3831d0814f8e1779a204c84e883c5b09ac GIT binary patch literal 918 zcmV;H18Mw;P)hD}6pcXCZfMX&YJ*TrGeQ&Jclx;ahF45CM`vCD~BcLX?DfP2UiJ z0*RN-V43VuKme0?8$ZJp!R%NdfC*L^KjSF^2tdecir%Gob}280uUlKK#b8$2xXgAKmbf;UV2#{$U^X5*46!wKqZh#ZMdBkNXv3DB7ieo zulI}$J)s~1bQl>$y3?m^tJSVHDaCeh1J29Mo;@-Er--v^I%$ms0;u3F_3Dhg_n%(& zzI!(lQ$T^H%IC2q?A2!}#Oot~XK7aAv89*q_UaMkRB(L+2*JkdYQvo}Z;#MKo){yR zfi1Ypwv|O44^m1HfR@{Z009`WO9b)rKmY}GrOe{A z@99E-04lNwW^U{BSh3kN)5rCNAP-ta9qCK1MRRHtKm_P86Ae5riFg7549EuMt!3INAF{fB>c`qrvXG0vbLcfYV-TM!Lmp z*)JE=0V=4McXAkS!HrCxhyWIh=5(p|?(+V63?m?bz&B3)+)RRZ+O_iM1>kjobG7&P zrY_tJGUUXBlNwWbzJYoMNuV)3@3%g}E`eIIJ{ujzu?h-&?gB+wY{%indKTzXd#0#HCDS0{Z-phY=uB!L1bL!P)YC}~YkSpyTd(&y6=ptUrj!n!2EvN%cX(Qd1~Cj}ra=}MMaT6$yAUSDth_c;MjFXDuc zH*ka~`GCb!n?c&sIrV%!ddaP+<3>IsfDyBwep!XJXH$LDJR<-JQt!W+Cj>BSu#u-( zMTyb)>iq_7UnNfnz>;0wKXb_xN5Uqd%Da^Dy#-<2KK23v|KrW2;dh?#>#aCu#`4r& sK)rr$9olGR|0_d1XKJA?q-csme>}omt|Y@$djJ3c07*qoM6N<$f>%Y6&;S4c literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sad_song_128x64/frame_41.png b/assets/dolphin/external/L1_Sad_song_128x64/frame_41.png new file mode 100644 index 0000000000000000000000000000000000000000..63106c87ae76b2c9bbb94e3e127fe11cef0ea97f GIT binary patch literal 883 zcmV-(1C0EMP)s{jAX^d(e?EQ>E=0%4BSmnH$U9*ox* zu9Q+vo09XkLsxTMS4g`*A*~WPQDUb6HX=d<6u@SnML+=@4h66wNH6dUYl*xuirhVh z%sE5e3@89FwNXX})_nI4gb1#*aULM0b#ej45SHnSE_+DF<_N*dc5a{5# zu9S0ziLh=fD)ZMu0T?O9kDv%aBLeGKC;$VI#;=(n0t!IMEaMlVML+>4m~H&FZz-BF zQUC#<12}7rKmr9IL^Jz~+b(~|F`%IUf~duU5QF9QP$F2Sf(2OD{!b^V${3zRLl2-} zR&u6?HA(a&L<%4fbPV93Cj#yT3_$1S+t|R;HfrN3CB^_^P}{X5QOt%*fB?_F`)ISs zV|>e)+Wxj*0#b^dLG43cA+k@&zYSe}PY`!U;58pZ$*pi91@@Wdn(K!;E6{YycNj{>-12jin)3=l-mEk_B@rNxlrwcow<#C^zrb*tip=7f(pTJXO^vL`#jzQXlKui6T4nY93$zU zso(%+ZXn$WviwgXRtmsC&%m>H4kedRjlOgA5`ZY}TK%-}XgN4q0K3vN=v@hVecQ<0 zZ@2^~Ss^*p8^fsl{LX7o01i->H6q~+A%Uzg`?a)L$X2ivz>6Jf|D zt#JtpLMlcxuKm2X+vYWX%j}@@$XmkhDt(_WUF_uu5LdiCjqwwCha&_~=ozs699_{X z$w7>~i>A&`GoE7v&~v}i@76@R>{*`Rm|MbnYx!zv-jR2T;tyFq5s(yE9|8aX002ov JPDHLkV1mPDf%*Ud literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sad_song_128x64/frame_42.png b/assets/dolphin/external/L1_Sad_song_128x64/frame_42.png new file mode 100644 index 0000000000000000000000000000000000000000..21038b47a6615a617a7ededd51fdceb3b387f924 GIT binary patch literal 1020 zcmVHt0ui-qM)N3t+z$*YD&-48Jvl>rxkJJlX0T8L5 z{?=I7zVA`Tw+c3|0GN!7X%+lw#loHQ3cwJJ@g!myc_hwQR{+M0jIp}>OC9S9AP9A} z&#U*};|idnj_fs2KGzFasdx_*PTWH+)iEjqIZ;QDQMP58M}}|0ITcglKUbBodCaU zKVAUsp6QKdEAd#Lw*PRv09vTIy;m$yyYA?RRsj;Gfs~X;TXE~14dA3-)@KB3KGhCh z@r3oF^4j>+C*Avih?L{%09a$C-k-6wkW0__oO`A7Vqer5qT}PQar2x25_$Wx^8qeZ%FK7JGJeN;AqXBoqng0)cF!aXANAFy{DtVk;Wfd55Mem281 zhv(p#LZ&+vJfoK$YFo>vG5cJpJwTxs6lOJc3l_Ad_W%X1OO4po<3h<;!U@3hFhvHV zQlX}+;RN8#?t;wWP)S0IGqAr{{IDdz?Ke0E;|*h?`0W@Z2EbAvwM0LIA%z={lo3+Mn2U>pR1alQgz zr8Q$3*h75oAp|&p6{zUm`)B*feF)q|DmvY6cK`(497-Ag>Hy#lYNBTyU}q-61?1=v z9}aLCz2ZL(fDAmJ5%E& zVz3Mxz&NTLpp>cHVc-CDa;t;4cLc#r4$>m-)IIEST;Q9f4j?T)T!0668bBlHV3yYaJFTCh;Tm=$^)6tF15it^*}Db%pAoRjR{_1* zPdkILStQ9xE-)j=r5xZ#{K^PD?P1W4l=#jy{!?gg_NSlCO$ie31}q{ezH@G#1C&IS zT=rR9LQ0CitrJLt1KHmPM|A4s&8vV}OFsxd1~=oEz6)4k&lUrTlkrPCfwei>;v6nT zT1@wd-s}LQ_Mfwznap$ntqwy)PPZbRK(1)HAUG}C>BG_ncNOq#L+0)z*65`sP#OeM z4|i5@oia_e7J?~X0g!%qlsBPk9YU!wL7l0Mpj-zq9s@e~R|B+IMh~gbA@u)%6v#sh zMvMLc*a@FhA#FA+VTQqM zx0X_#vfz1^Z`q>OYSQn8&d=!qT+XsIfYs`^?)yUHCcwv;8o*8rMDfGbEmG(``W0GByd;!kS;Z*J=Ju@t|X z09Ob;R&!=8ey9W}C8zn?qm@O!ZV8Z4fYac~)WS>ra0!r=tm6Nd08v)I=QyG~GsaW= z&nG~{-y#wbuOAoKue-2T+1ky6dcz; z1krojNhwSNtc34P#@9@sRmgd@WCKu(ixzDYNVXM^+Qg!&w`^Tb0TGgbQz1*>R_Uir zKr#S#;!ca*gHjP&d{*>CP)~r_1mg#Vza4g0v8OS23bUR2+-d+0tw4CKauOtD-v+$D3}Pn$+`Pl+-dbfp z5wpbGP$3ckSN}axs3us63fG}Xl$gs3&+&MRFRB4nV&QA;`&!|($+``Ai{DKEd=bCK zeGs50hS#w$8>tci4;a%EpK2()^+?HRu;U5PTmAUjY~byCRs!^llTkEHP9(rDzQNI< TaGPU|00000NkvXXu0mjfZcWF6 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sad_song_128x64/frame_45.png b/assets/dolphin/external/L1_Sad_song_128x64/frame_45.png new file mode 100644 index 0000000000000000000000000000000000000000..0501b735ac7c723940e09c8415bcdcf91b6e627e GIT binary patch literal 1061 zcmV+=1ls$FP)Jz~dfW;2IF2F}iW+`Rs ziT$qwB!L9!z2{T_cW3%Tn)LP=$^ov}{)Z;{QwO+&_(Gffa1M|a8XLue{89=OAZ`LZ zcmKEF3|f(Y9Uveq$tP<7@7(YG`5aJVJQ=$+`PTssK&2^sow|V=3CEiJt^;J)ebK>L zd~DZi4*=HzG6}IA>nSNxakfqY(H1@HX&?*vwbm;DY3!0Zi=!o;mjI%XkH{o~8L3n| zDG_{B4UiEW@}KRVg&|_c0k&&^sJkDL#RWLz_u5O@`A)y<0D|c4WKMg^308OXPQRb% zyFU|F9RR>z1SuRKi%3@;0AOs_v-c%LP{J1gf5=h?0Law>tGS>0{PYe0kjDii)4&e- zZM=3oWDRgU& zh~VT@fc6h)KfBc;UxlAf35&li9M}^x#Nir&duFU94Qg71aMl+wBJ7`b%Jan zkczV_f?gceBJicMzpq;B+m{7_l*lYk6H;y{aWq`M2ap=P*YS-KnYDX(i-3BHI>2*L zwF|;5cLIorA{YulV!)%*f;EBOg5g}!a1qV{-p-bcqoNcUD{;JwMv2n^6;H(hR>7H^ z$Ig$0(foS_8#kV-18_{wMa2&?H~2257;l12jG}0<>)(Ajr=G zxQh{m7z><`)b`hQ2=l)SzzIgl(^^<1(ZM5uuHNJ=gNblAWIY>OF zjI2&EBXP&>5vHdAO9EQrvtyLosrNbL0~r5KU^^i#w7Xsso|}iJ>71OYD+vMgGtMW=iDb=$7D7KGTG^fFwvqNRv<202~{7wx?cOafsd+J^{S~ fuzjw?kru%})x7q7P*DQF00000NkvXXu0mjfNch_t literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sad_song_128x64/frame_46.png b/assets/dolphin/external/L1_Sad_song_128x64/frame_46.png new file mode 100644 index 0000000000000000000000000000000000000000..1edca6aed37b513c9fbcc890ca92b5fbd54f6a2a GIT binary patch literal 949 zcmV;m14{gfP)uK%btd zr4&|+udkd0Sb!O^DP{m*7~rf-UnDD?V}g_k@OHCVir%&)1H4@jPdvphrQ}S2x8f~6 zIRPSgTk1rJPfCEd2@ye*_^A^B;2q|R6#p>+-XK6E(c-61fZd4}|26@#to|(HupnCe z-vroslCx4p0EkV30-aw3Wiv#bs9l*APkQ|_H9$5)YEYj^BA)d6ZVj;GWM@Mwz5(Rh z$45J`tYjV=2hBaaVsZ*N*q~kb;eJI4u*(Wti3}!KDgkygX^QROdmQUb>~2Rw?IR$! z-=z`&V2u>ON3>pxk%-^BzTSsi1FSHiS)j}nHLfMG-EvL=PK>snnUyEqyGLW2J_W2q zrqqfo;6|JTa6-F^$9uen2`K#>0|?L(Us}jf#yJ}AvIfYo4OoaTH4S&XpW2hsE0i*b zrA-FWDecpE%cZ1Fz1dxbYGH&?Wgbbi=&V+10FJoSJ!|IvQOvHqsFmM-E~)`)$5c_< zL97m?FpI1SdiVb~?%4#8tY+;bh%}YfUTmENqx*-P0(MFQ>5BA7g6_)yX{QXpUBSDE zJ%TGnosaMW&?5ov#K#j+|7OrGEN9T#qrF>~Eua+$3yllO)whP(c#cd2wgEWtkH0%A zNZP(;O^_i40Om!+*Gu|Il>j~Q@rvISje@QABk3=#6Lt@_I#Jjiz7GvC>T$U^U~H8a4h@SGxZbl>dTNVMoK2{3wa0RK0J z8Ob2NX#$KMxc7PGE#g-)YzuDh0zAcE(Ma1TK>M(m6$P^(A`wDrfWtFhW^mgAM&eto z{+&|A(hMvENRDXN_4mT(a8I41Ub}1pv=8WE1dV%GCzOZCDNX^aVpX?9pZ`M>;P6ef z_^bpNt%U5Aedd}YT6~oRsGVtETZ-%_Vyh4>J~;t!_1_bPYJw+G>;LhIVP_LWi?3P( zJc)(>*M6@RUYo4jK(zRz1i)X$pK%|w(i6i<_!WpYf$AxsmjXTUsfNN^kH*+C5}g2F XbrdPN^_tTy00000NkvXXu0mjf@LsB9 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sad_song_128x64/frame_47.png b/assets/dolphin/external/L1_Sad_song_128x64/frame_47.png new file mode 100644 index 0000000000000000000000000000000000000000..467fe93e0c9e66258eacea93bc7f839f18e85802 GIT binary patch literal 1084 zcmV-C1jGA@P)k1+Pc&yOt0-t1- zQbbo=r-$nR$)G_xM+|HM+?8nu3({v|07&itZxWj)`B(%j0lbM2PehPkuc;m24TvPa zltLFk)aw6c*NG))8 z4ofLNuGaww$RY?cCMx znFU6B?ruGe?Lb`bX!apK3}zDnx9;5nHX+>x0Brx_=QSB1+Q2A#^a#5FP9qx}FMw!p zYOI)QxC41iG^oaKV{-qWBF{3b+T^wdZ?~9+m+xyuzQyHn6+*w{<+P;(r(+ozzl_8~I+?vHyw)^4&531|
hBUMP7KqxZ| zj&=(XU8LZ;RNcFjQe4J;`Q%UZ1YTl!4e)SO2Jni1@3CKgcoQH2bu+BC61EL+VuwCC z1-O`{l&zMJ)8kD5AS^^5nW4p&0iL`wz3>oSk_hk#@C37+5WklQ%LaIYASa0spPK+a z0e&-);@38X0nk%tXMPCcmr~XYfG$J?l1h8jC%R3M07SUv;|nSQLG-NzKotOitm3Ci zfb1ZM-D(R7eh+}O`=z@LiTe=|E&7`P2SFBJR0AOG{)`}tpE3cm8d5735xJOPXn?GX zjY3^N3$ZGXPXMAk#zV)=c7MbiHP5u#`E?#| z0t8US*uiV(_dYjy3qS`Ysrb(1Ek~OGy>YbT|7QS{MMa^v9+Eh|_utvEEiB&-QYXL( zS|aSm_|A?U&C8?~AogWY*4?i;n^&IusS|3AJGy4|Tx({97>fAP z&p&DaOCd@r-vjr?t=GK7@}O0S?G$pJ$H8umVO9gogufGE0*%=vi$A(ADj8dq<3& zeka?b2{00X@0j|xgXWxYXq|zV1>ItT=?|9xEAh|oZ3$aN zzkjx40`C+c(wP7$5soE5OMJfLGolrdDBcXAHNZ%CO&t({Rt%yTGaNGj7gHjtv2MlfRo~*83`>B!j6E-{ftHv%o7ANgnb)WN{;vZQ-+K( zLx=%-!h0>YX50vqrbvKZAt&Lwe!lQ|GWGv&wZA$6IH!!T@{HcSnt4Ww3|Rvp(~Eu0 zaeyE;WCA<|1hLH$z+;D;U?;+@jwb}M-4mb=Y}vQIE=HNl>bVGFnN<7b?ET#5s5w9+lB1wU>ZrEY z;5dj`571lqe-=hBqpAC}%0Mr!+6brw^sFlytjfR)`K|l4?3_Dq*#Q9FPq12`TybE;h?xBa7J^-MfO=kZCwRxe0ahIVpik21b8{zvjsrx_egNB&rPT|pW<2%$ z=Kx?QP)-@pBp=7a>(LlV0YuM!OK{2np2xL&t$5cu0DzrPZxumP05ALhf_oYOh%JKS z9l#1)2dL+3ia-)~*aMt|=w$#v*0`1Vqjjk`z_WiEGnq+2%eJ-SU*P~&7#-DC=5xn_ zmKekiaB}vaPzLaPX*rkT8GWvW9wAb~cEpm3KpH1~&z0F(S-|Z5J4ImO00rJ6P;&q-A<`oe{=41~37!b% z$4`&We{`hLbcvB=N}!Gcsm+K8jm9qm`8j8v0HkPbf^~l&fu`+ykS=xr>E_{0v;dWH z(s-wnZ|(pqq({wuY3;A$c#kOyc3GRGQAyB>EyWF)@7e=kP|c7ww93G4@&OL|Uw~v& zQ%Yv!Ye7O1o*&5}+D6}X^BM914*X4ES+pj4 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sad_song_128x64/frame_5.png b/assets/dolphin/external/L1_Sad_song_128x64/frame_5.png new file mode 100644 index 0000000000000000000000000000000000000000..70ed4f55900322b30ba524f45b48dc81abd88ea6 GIT binary patch literal 1453 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 KMNUMnLSTY%ti=xi literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sad_song_128x64/frame_51.png b/assets/dolphin/external/L1_Sad_song_128x64/frame_51.png new file mode 100644 index 0000000000000000000000000000000000000000..a81457ddc24d2fe1497619616898667439bf4aa5 GIT binary patch literal 1159 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 IM6N<$g4?E4Y5)KL literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sad_song_128x64/frame_54.png b/assets/dolphin/external/L1_Sad_song_128x64/frame_54.png new file mode 100644 index 0000000000000000000000000000000000000000..abab31964359435749bdb0c6a4579e9cd469cbcb GIT binary patch literal 830 zcmV-E1Ht@>P)V57)f|3AMIKJcv;XmR6u;BQTBZLoMl8sw*K45oTD-aWabt79J zpBADKz&b|+LIT*yuse-a0(JrHXwV(U%th!Fa3`t=SOpN(h&#h301wXhFo#}F3JA^- zKtZ%9z#;(4MmyWSZB7KJU|G=IxD>!y^>n5MV0pEql!nJ=d0w2jivVZ!_u)zarSp4N zaYXUB5I{=lkMp>~cZ4+XJ@e&N2CA)8uCdJ24LdR=fK^0tkTfn*02f(Uk!pb>fP!ez zW%yLEmZ4@Vd#--HvZMy+F>dK_EkRt@6+#560vIvqst6DT^%PJ(W<805x&oyI;XMkd z7lEZ?vjgvreEMDfxg+Ty@6HDXBLK&$dy52jq15{0+_0(j6T$hwLID7@bRg~gr+%Zb zQ~*GI^|k(vyZ>7IkyySJOx=B(DLOgY@yhZDz|C7kX+@HVQDAul;O0%7WzAt(%o=n@ z0PhBi76s1Bpx_(<{6c*i@rJt^V3a5?e@adSxDjpj14jV=h$ezA0YouQlz9Rt0z48$ z1SX3>$z$I=L?{eR37}6bxKA*TS1lQ+~p%9}uvco!RaD_GkQGY3bx@tg+C7J;Qx zpN@n9z8xfu*$9w%OAQ+D`#lTHYy`->RpMn-(5w$UK#BkwtDmycDdUiMg!C*ZGXel; zr+Ipw6l#pjiMnaH3h6R5cL^<ZfQ8BQT;9kcklqZs zJ3AMn z<}C$qw|Q@S#`#u7;7S01?EeSub>0x$iEP)hy<_* zJ_RD^!V*o7u#%&O03hc`0Z;|_d<5`()3EJ3k0UDpk#C_r z;Z^|j`Zc_WeD{_RLU;Q-e=aY$tN`f!>u@JP8osm_LmnA!1PCE~g{M`%F$~msTiu{s zPt|J6$h_fcs2&TKfPj49W(d$CiiNoVkK+U&LUoa_WC$3Y+4pc=7pee*ZUH1WHw}E1 z5RjmaSb&}zYEc0Ik8pLQkuI}JQgD0VY#~qu%5+Tv(_mwO_aIBZi$8Z}288hIbX9=Q zA2%w&U6kBqG&gK=*FpGgh6Etu4F|KsFI^Y385V#j-}`E>;^hlU?kdpU+0C~*j0KMhC_Xs-3v;0u#>@8x1EyLtgaP7NpKLJYXIZ#1#klzT{(a7NahU)Ud9jeWpgadSBJL%0000Ax&5g|9SQvf35M1BlJf?R~m0gxaMVS^w_LJqXk~x6% zAAMQO_>pJkRRZ>Ey8;A^!|lgY!jz?<<&**c=cfRB^b&($>sc~2fZQyiXE8f>MJob? z2s&Paprg9mJ0R{(&;zJW0;2-4inkp-LqGodh?w^PzQ11C6KnfEg0G|7GlFWbw}L=C z(tUunx#J4YLy8APkP+fYy%7ZcNh1JRAciyoKnx!Otp5nAn>0=baXifG0Z;~olR+GZ z8$l`&5FiF`-3L?5>XATCa}%gY)ARs(PMrd%)%WAlT6j+h$O2j}%>l>+_u)%`*1#gc zJ@^vfOkjISKpD`nTLR8XKsFeYssKkfg4W(oNf?ve46q#ww~BxSWAOw8F7rO*vnWIW O0000 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sad_song_128x64/frame_57.png b/assets/dolphin/external/L1_Sad_song_128x64/frame_57.png new file mode 100644 index 0000000000000000000000000000000000000000..d142a38db7a8c8f5b19c2cb7c8aaa779f3baad7d GIT binary patch literal 398 zcmeAS@N?(olHy`uVBq!ia0vp^4M6O`!3HERU8}DLQk(@Ik;M!Q+`=Ht$S`Y;1Oo%3 zgQtsQNX4ADcMbavIf$@aoVWA;+;m}u2*yL-Jg)r@)mLQQnbB-uFlh}(_Ptv65`E?d zV+IQniP@f;riHf!pWPaEOkzDpPh+Yb|G)pyhG)vz@4G(uv4cU6>F{+{gQkCVaSX~Y zUS435aI|k1KfqCCxs}1~!ts|tDLFme2Ei-km5e9IFK)8bQBXNOc%YUOT*N^#bP z#S8@^41G)o0vRm07~B~jXfVhOjG(uR%i7=e$G@JNe{5P{<>tq6Z?9M9mHoYG<#t5q zpSi%+kpOt;v-(y?1$6+Vj)v zj&C>H@UF6Z(%dg^T<3>gzdL=V>~nSblPiwy*%h%^?fW*C#lO$j@#pGny%y!wdA@SK z&;0zqx2l&;x%=1W-|o`bvm39KnU-5GS9a?)ZAy*)P*o$6y>|I`+}|JyM)Q&I~USqz@8 KelF{r5}E*`z{1!7 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sad_song_128x64/frame_59.png b/assets/dolphin/external/L1_Sad_song_128x64/frame_59.png new file mode 100644 index 0000000000000000000000000000000000000000..4ab56d31ed0c210026f3eb0bf636e5e530d9a94d GIT binary patch literal 970 zcmeAS@N?(olHy`uVBq!ia0vp^4M6O`!3HERU8}DLQk(@Ik;M!Q+`=Ht$S`Y;1Oo$e zji-xaNX4ADF|YgHC~z#BT=_q|tlr0}kmH2wmAnVuem1!iyX%^E-Q3Htz)?{*Zn><+ z99KaF%PxkKfh-<(m--v9hTD{XN2d>F=x8_n#2Ee(k2%e|i5Jc^x&T zrTxF`CdFS2n((IhV{qZu2hWl{IXl+PobzYR-kTO-Z&~+z-1X?^p7`0{G(9vYdtJQZ zkdYhMta!3A=$DVY|H~;}jLY92TP@z_kr@{G?*3bgsX{xy9N${Ou%pC-_t82d1FzS2 zemp8%^d;zTT~t5{Lyzd{batD_Q%dhy&oSLOR`$j3(&t8&hxXoiVe9p*BYU-6Mz3Hc?;aB>PwX%m0_y^DFqb#k-%Ibn4IO z@!p;{hcj*SfmKBtvcEDj?Ah!0watw$D}SXQ!+qldqj~wu6TZG&n>bxX`7Qg3xj8Cn zn|eyRe}36}Nax-x#tSU^6Avs7X7&X9fL3wAP%G-pn}b-0o9B?mv&-Ep_p^kP)+`*wESQlZe2< zqMGhwvgV6(GM=i=p7!^^l>@@N+-7=PmF*VSp3bPTIPK}7u3Z~{OQ`wINjq9N`Qb^U zd6y(YO*Pv3dvfe|_4_;zWO>&9*(4%1VQHG5{q{G# zGr6T#&*S*>$D%?*qToohV^hc38yT0guC6_q6Sz$&;aB2>t!7^L=TBm_khNAzyI$De z{L;MHZELjfi4|q{tZX`N1P54!`JZ`tlAYlPv)KB_b-ZOw(}0^XQAsmfBhbQi;RFbiTkZBNL zCr4}TY>RQ8|5Z4SrV~IiK7S!Z8fQfdZ>8QH`ktKs}#&@AWgy5BSZZ^0H6sB1EHWOGdNgS@I<^j3UA`0-(mHkY4+; z&ee?Zk)uXh&x>d%Jg=wgRFYt71V9aBz3<0KpydE4qVu`ekMK;(>#K8{rB%QMExbnO z_MZ9CdHh%!U1fO3UIys-m5!D!^jc*fz#^jL^;PRHj{sIkLpE4t zd@swFKqEoMks{?TKOUqUD5A@70$fT*7;YJrR|gSK@G@;pFJ#P&o&&FrG@ySo(OPTo zneEI~FGgy;1q4X;oe9TWE*f>bF?neMX~L4@N~1t5jW{ z1I)&54{&;;Qcv|GCE&}S21e^@@BHJaXcW=4z1H(Af^lQgvJ{yg`o9$Ri30XVWovpjxg`NpxjFL4_Ja6}9_ zvTR#P9tr3^OB+En>Clv@x=B)LsO1T>9@0b+seZEX93o5r$=Li1=2?GL{Y@bmR|@mU znxi{}2r>_oB65D*v)1%~6Q>zB^4(Ja1t*tOGDf>%(#@LrFekGZFkXC4?roHG2;)&FHkGy1uNCy;C#90&?SMDY@Ieq7lX$`?8EK36tth zYu;`*@B*UOY7!vF13Fj3%QHHMMi@SZE3*?KhNaPO)IN~JI)aF-coS!|_qP^o1@mz4dByILptZP8BT0-#VGGgyz!AV}LPR=KJ4J%t%4KvUnBo1c z{mTKeLcKF==SXnr#;YA9y6v=&*81-2n{Fi9ex#7jE$R9QRP-OT+TYvLZiwpix+ZBQ zUbN;!ja)?rN?u;SqifJT^4w-ePp`zcBv2~xMk0G-waC>&1Wp1T8Aq0Z73j6sk1D_m zdd!r8kRt3IZ34;a$J^$$SvSel>5@gn#jnLCTt29A2 pf|NGBB~3+X#%0WCgsna*{{Si6l5o!t=uZFu002ovPDHLkV1iowx`qG% literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sad_song_128x64/frame_60.png b/assets/dolphin/external/L1_Sad_song_128x64/frame_60.png new file mode 100644 index 0000000000000000000000000000000000000000..0cb8722e3ff732af6e1d70e2fe64d9fc6bfade9d GIT binary patch literal 1370 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sad_song_128x64/frame_9.png b/assets/dolphin/external/L1_Sad_song_128x64/frame_9.png new file mode 100644 index 0000000000000000000000000000000000000000..6062721194053f604d5429f76901e30549cfbbe8 GIT binary patch literal 1480 zcmV;(1vmPMP)78LzU4Q3Gm@~djecNM9;?C6QCP3 z_n1AeQzke5CcuZ|@&vd;d<~Z;z>LuPNw_=#?g&uB{fEq4OfI9-zaCrjU5uk?4 z6X1>jU&wBR%M)N{1b7GXU7i3to$wCF&ug~=Rd7xi*N)?u`pDLjOLg-SW+kalK>*HB zULy=AOV?LN2&_xxJ%g$YFSZgudx^vAJZ)FaHXiqaO2?Y?1(`0n9JUZZdaH-RLjoEB zs^_vapVI@ct&!OSY5oj?VJs&A=X{Ovo>#iKzoYOv()0RSCWKpay!MDo5q^66ErRCcah)}WgvGmnX|J3sg#t^xeG05M@}^wZ6$!$=yb=^ z?Az-IE%Ee3CQjE#$!h85S!;~(CdSs-*Xnkrbgo;D=iqfdS6NUwmV9f>iZ7T%6ILvr zyAH4*2avL6s+jhzA5I>g;~_!zK0W2v?&H8S2Y`=&mwy=4@oKaOmXot2uoB=-jE#e% z)>Z;YXSv1hNV3z}C7g}uQGhFvCyfHURhV5k_*Y)E%fOvdm zttz@ZaaBYTJ`PK(fJ(U1K8rlS>3~Lpl?36|ias+~QUwf1Y#o1dQ9v5c$*btL8=XA` zEYFfP{Rp7tn#NF1`Nv(0?mDh7kZlK7YJaRl1(IDoI&NzON#T=@<37XJDvqCph+dq- zGHYdzq~%qB*Z8<*#mHv6(y>xOHvgb=x*l$2=1nJn6t<%>sN=aVqGSMXpM58^TxsTd zEd;Y!j6L8E4aQ zOn+BF&vEM^jfF}(H4Ca2wR1!tEuWy**-Gj-jw9mKi1RrIR7)pnB#8`G#xL!>l}C*< z$D@lhhUP{X>{?&f`<(7kJY!^yTpnXcI&eFtqF>j%4su|K8=)%#SPOY>PP>_|onloC z1FJ~D+0H{;O%4Zf9yCXQh-0gci;@UKBUDsAo?n@hIXbQAC&~LBmyG}#DvqHtET=@B z>y1#FENxzuG;E?KT(k6ZjR8jlpqR0HGSCde>5z$1k?~p3D3Bq9C086HMF5KE&hgI4 z!h3{JJG- zc0Y%++z*G)(H7bMTy1VGI5QGd!*MkV%sLKM?S6p3D`A`xo^!El$IZad&C`#Fezpry zot_@&c*4Ech@E8UAQ#qcB#8LOBFNS8v~JQHej?5|D+z`jusyCqgzRzj1dvOks8G!r zGs9&k38dY>iU>pYDzg1a0zD<3Db#4u4xFVlv}8!Vjt>1byLf$3NoLt9prb~X`rBu& z3M@I9Do9&{%F-Ha(&q!|%_>CzZqcxCrN51FB@%F-X%C>dxpZ76+;`dR7Xd0yp8g0n zh!ineW$jsVtcOh!tTwtNXI2jNO@IjFO`#4VowZfW$pP>;yDANvnVUk? Date: Thu, 10 Aug 2023 19:34:51 +0200 Subject: [PATCH 708/824] feat(infrared): add universal remote (#2818) * feat(infrared): add universal remote * fix: fix channel commands names Co-authored-by: Aleksandr Kutuzov --- assets/resources/infrared/assets/tv.ir | 38 ++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/assets/resources/infrared/assets/tv.ir b/assets/resources/infrared/assets/tv.ir index c4b6f0c42b2..a223c97c97d 100644 --- a/assets/resources/infrared/assets/tv.ir +++ b/assets/resources/infrared/assets/tv.ir @@ -1717,3 +1717,41 @@ type: raw frequency: 38000 duty_cycle: 0.330000 data: 3981 3992 495 1994 495 1995 496 1996 494 1996 495 1005 495 1006 494 1995 495 1997 494 1996 495 1996 494 1997 494 1996 495 1006 494 1005 495 1004 496 1005 495 1995 496 1994 496 1005 495 1004 496 1005 495 1006 494 1004 496 1006 494 8466 3978 3991 495 1994 495 1997 493 1994 496 1995 495 1004 496 1004 496 1996 494 1997 493 1996 494 1995 495 1995 495 1997 493 1004 495 1004 495 1006 494 1005 494 1998 491 1996 494 1006 494 1004 496 1006 494 1006 493 1005 495 1005 571 +# +# Thomson Remote +# Model RC3000E02 +name: Power +type: parsed +protocol: RCA +address: 0F 00 00 00 +command: 54 00 00 00 +# +name: Vol_up +type: parsed +protocol: RCA +address: 0F 00 00 00 +command: F4 00 00 00 +# +name: Vol_dn +type: parsed +protocol: RCA +address: 0F 00 00 00 +command: 74 00 00 00 +# +name: Ch_next +type: parsed +protocol: RCA +address: 0F 00 00 00 +command: B4 00 00 00 +# +name: Ch_prev +type: parsed +protocol: RCA +address: 0F 00 00 00 +command: 34 00 00 00 +# +name: Mute +type: parsed +protocol: RCA +address: 0F 00 00 00 +command: FC 00 00 00 From a7f0f5ad27dfafa582d3209797620d44ac9de225 Mon Sep 17 00:00:00 2001 From: dogtopus Date: Tue, 15 Aug 2023 22:23:09 -0300 Subject: [PATCH 709/824] Improve vscode clangd experience (#2431) * Improve vscode clangd experience - Resolve and use absolute path for the toolchain. This allows clangd to use compile_commands.json file without running under fbtenv, simplifies setup for vscode clangd extension. As a side effect, a rebuild is needed to update compile_commands.json after moving the source tree. - Add the recommended default settings of the extension to settings.json. * Use build/latest for compile-commands-dir This makes it behave closer to c-cpp-properties. * Reformat crosscc.py This is a PEP-8 violation but black seems to enforce it * Bypass --query-driver This has some security implications as it grants clangd the ability to execute any executables anywhere while trying to probe a compiler based on CDB. However it's very hard to do this the safe and intended way without resorting to config generation due to reason listed in #2431. Besides that we already have workspace trust so what could go wrong? ;) * Add an option for vscode_dist to switch between clangd and cpptools This will install different extensions.json tuned for either clangd or cpptools based on user selection. It will also install c_cpp_properties.json when using cpptools since clangd doesn't use this file. The root .gitignore now also doesn't accidentally ignore everything under the .vscode directory. * Use absolute path for .vscode gitignore Turns out the previously used "relative" paths aren't even valid gitignore patterns and to actually do what it means one needs to use the absolute paths instead. * Handle variable parsing in commandline.scons commandline.scons is the place where all other command line parsing happens. Move LANG_SERVER variable parsing there and add a constraint to make the code more consistent. --------- Co-authored-by: hedger Co-authored-by: hedger --- .vscode/.gitignore | 9 +++++---- .vscode/example/clangd/extensions.json | 19 +++++++++++++++++++ .../{ => cpptools}/c_cpp_properties.json | 0 .../{ => example/cpptools}/extensions.json | 1 + .vscode/example/settings.json | 9 +++++++-- SConstruct | 10 +++++++++- documentation/fbt.md | 2 ++ scripts/fbt_tools/crosscc.py | 4 +++- site_scons/commandline.scons | 9 +++++++++ 9 files changed, 55 insertions(+), 8 deletions(-) create mode 100644 .vscode/example/clangd/extensions.json rename .vscode/example/{ => cpptools}/c_cpp_properties.json (100%) rename .vscode/{ => example/cpptools}/extensions.json (93%) diff --git a/.vscode/.gitignore b/.vscode/.gitignore index 670df7d48f6..481efcdef35 100644 --- a/.vscode/.gitignore +++ b/.vscode/.gitignore @@ -1,4 +1,5 @@ -./c_cpp_properties.json -./launch.json -./settings.json -./tasks.json +/c_cpp_properties.json +/extensions.json +/launch.json +/settings.json +/tasks.json diff --git a/.vscode/example/clangd/extensions.json b/.vscode/example/clangd/extensions.json new file mode 100644 index 00000000000..daab417cd01 --- /dev/null +++ b/.vscode/example/clangd/extensions.json @@ -0,0 +1,19 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. + // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp + // List of extensions which should be recommended for users of this workspace. + "recommendations": [ + "ms-python.black-formatter", + "llvm-vs-code-extensions.vscode-clangd", + "amiralizadeh9480.cpp-helper", + "marus25.cortex-debug", + "zxh404.vscode-proto3", + "augustocdias.tasks-shell-input" + ], + // List of extensions recommended by VS Code that should not be recommended for users of this workspace. + "unwantedRecommendations": [ + "twxs.cmake", + "ms-vscode.cpptools", + "ms-vscode.cmake-tools" + ] +} diff --git a/.vscode/example/c_cpp_properties.json b/.vscode/example/cpptools/c_cpp_properties.json similarity index 100% rename from .vscode/example/c_cpp_properties.json rename to .vscode/example/cpptools/c_cpp_properties.json diff --git a/.vscode/extensions.json b/.vscode/example/cpptools/extensions.json similarity index 93% rename from .vscode/extensions.json rename to .vscode/example/cpptools/extensions.json index ead935b08b4..a8babee1cd8 100644 --- a/.vscode/extensions.json +++ b/.vscode/example/cpptools/extensions.json @@ -13,6 +13,7 @@ ], // List of extensions recommended by VS Code that should not be recommended for users of this workspace. "unwantedRecommendations": [ + "llvm-vs-code-extensions.vscode-clangd", "twxs.cmake", "ms-vscode.cmake-tools" ] diff --git a/.vscode/example/settings.json b/.vscode/example/settings.json index 19a03b69d98..efa08157b62 100644 --- a/.vscode/example/settings.json +++ b/.vscode/example/settings.json @@ -21,5 +21,10 @@ "SConscript": "python", "SConstruct": "python", "*.fam": "python", - } -} + }, + "clangd.arguments": [ + // We might be able to tighten this a bit more to only include the correct toolchain. + "--query-driver=**", + "--compile-commands-dir=${workspaceFolder}/build/latest" + ] +} \ No newline at end of file diff --git a/SConstruct b/SConstruct index d968254b166..04cd057fd7e 100644 --- a/SConstruct +++ b/SConstruct @@ -45,6 +45,7 @@ distenv = coreenv.Clone( ], ENV=os.environ, UPDATE_BUNDLE_DIR="dist/${DIST_DIR}/f${TARGET_HW}-update-${DIST_SUFFIX}", + VSCODE_LANG_SERVER=ARGUMENTS.get("LANG_SERVER", "cpptools"), ) firmware_env = distenv.AddFwProject( @@ -348,7 +349,14 @@ distenv.PhonyTarget( ) # Prepare vscode environment -vscode_dist = distenv.Install("#.vscode", distenv.Glob("#.vscode/example/*")) +VSCODE_LANG_SERVER = cmd_environment["LANG_SERVER"] +vscode_dist = distenv.Install( + "#.vscode", + [ + distenv.Glob("#.vscode/example/*.json"), + distenv.Glob(f"#.vscode/example/{VSCODE_LANG_SERVER}/*.json"), + ], +) distenv.Precious(vscode_dist) distenv.NoClean(vscode_dist) distenv.Alias("vscode_dist", vscode_dist) diff --git a/documentation/fbt.md b/documentation/fbt.md index 1ab67b4e607..af588382b04 100644 --- a/documentation/fbt.md +++ b/documentation/fbt.md @@ -46,6 +46,8 @@ To run cleanup (think of `make clean`) for specified targets, add the `-c` optio `fbt` includes basic development environment configuration for VS Code. Run `./fbt vscode_dist` to deploy it. That will copy the initial environment configuration to the `.vscode` folder. After that, you can use that configuration by starting VS Code and choosing the firmware root folder in the "File > Open Folder" menu. +To use language servers other than the default VS Code C/C++ language server, use `./fbt vscode_dist LANG_SERVER=` instead. Currently `fbt` supports the default language server (`cpptools`) and `clangd`. + - On the first start, you'll be prompted to install recommended plugins. We highly recommend installing them for the best development experience. _You can find a list of them in `.vscode/extensions.json`._ - Basic build tasks are invoked in the Ctrl+Shift+B menu. - Debugging requires a supported probe. That includes: diff --git a/scripts/fbt_tools/crosscc.py b/scripts/fbt_tools/crosscc.py index d0631ca33ab..42fb4ce4b17 100644 --- a/scripts/fbt_tools/crosscc.py +++ b/scripts/fbt_tools/crosscc.py @@ -2,6 +2,8 @@ import gdb import objdump +import shutil + import strip from SCons.Action import _subproc from SCons.Errors import StopError @@ -11,7 +13,7 @@ def prefix_commands(env, command_prefix, cmd_list): for command in cmd_list: if command in env: - env[command] = command_prefix + env[command] + env[command] = shutil.which(command_prefix + env[command]) def _get_tool_version(env, tool): diff --git a/site_scons/commandline.scons b/site_scons/commandline.scons index 0d3b1f92e1b..096cbd54ffc 100644 --- a/site_scons/commandline.scons +++ b/site_scons/commandline.scons @@ -248,6 +248,15 @@ vars.AddVariables( "Full port name of Flipper to use, if multiple Flippers are connected", "auto", ), + EnumVariable( + "LANG_SERVER", + help="Language server type for vscode_dist.", + default="cpptools", + allowed_values=[ + "cpptools", + "clangd", + ], + ), ) Return("vars") From b90e2ca3426cf4c3e7bf6360e010b7aed71e6e41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Wed, 16 Aug 2023 18:16:42 +0900 Subject: [PATCH 710/824] SubGhz: add timeout to subghz_hal_async_tx_test_run (#2975) * SubGhz: add timeout to subghz_hal_async_tx_test_run * Removed full API from unit_test build config --------- Co-authored-by: hedger Co-authored-by: hedger --- applications/debug/unit_tests/application.fam | 1 + .../debug/unit_tests/subghz/subghz_test.c | 5 +++++ .../loader/firmware_api/firmware_api.cpp | 19 ++++++++++++++++--- 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/applications/debug/unit_tests/application.fam b/applications/debug/unit_tests/application.fam index 949bb3fc292..ad9a278f387 100644 --- a/applications/debug/unit_tests/application.fam +++ b/applications/debug/unit_tests/application.fam @@ -3,6 +3,7 @@ App( apptype=FlipperAppType.STARTUP, entry_point="unit_tests_on_system_start", cdefines=["APP_UNIT_TESTS"], + requires=["system_settings"], provides=["delay_test"], order=100, ) diff --git a/applications/debug/unit_tests/subghz/subghz_test.c b/applications/debug/unit_tests/subghz/subghz_test.c index 6bdaa641e8d..e32a5748296 100644 --- a/applications/debug/unit_tests/subghz/subghz_test.c +++ b/applications/debug/unit_tests/subghz/subghz_test.c @@ -330,7 +330,12 @@ bool subghz_hal_async_tx_test_run(SubGhzHalAsyncTxTestType type) { return false; } + FuriHalCortexTimer timer = furi_hal_cortex_timer_get(30000000); + while(!furi_hal_subghz_is_async_tx_complete()) { + if(furi_hal_cortex_timer_is_expired(timer)) { + return false; + } furi_delay_ms(10); } furi_hal_subghz_stop_async_tx(); diff --git a/applications/services/loader/firmware_api/firmware_api.cpp b/applications/services/loader/firmware_api/firmware_api.cpp index 6651bf11247..47554f6280c 100644 --- a/applications/services/loader/firmware_api/firmware_api.cpp +++ b/applications/services/loader/firmware_api/firmware_api.cpp @@ -10,6 +10,19 @@ static_assert(!has_hash_collisions(elf_api_table), "Detected API method hash collision!"); +#ifdef APP_UNIT_TESTS +constexpr HashtableApiInterface mock_elf_api_interface{ + { + .api_version_major = 0, + .api_version_minor = 0, + .resolver_callback = &elf_resolve_from_hashtable, + }, + .table_cbegin = nullptr, + .table_cend = nullptr, +}; + +const ElfApiInterface* const firmware_api_interface = &mock_elf_api_interface; +#else constexpr HashtableApiInterface elf_api_interface{ { .api_version_major = (elf_api_version >> 16), @@ -19,10 +32,10 @@ constexpr HashtableApiInterface elf_api_interface{ .table_cbegin = elf_api_table.cbegin(), .table_cend = elf_api_table.cend(), }; - const ElfApiInterface* const firmware_api_interface = &elf_api_interface; +#endif extern "C" void furi_hal_info_get_api_version(uint16_t* major, uint16_t* minor) { - *major = elf_api_interface.api_version_major; - *minor = elf_api_interface.api_version_minor; + *major = firmware_api_interface->api_version_major; + *minor = firmware_api_interface->api_version_minor; } \ No newline at end of file From 35cdefa1caba8f7f174ffe040173ae7fd55bbbc0 Mon Sep 17 00:00:00 2001 From: hedger Date: Tue, 22 Aug 2023 20:11:53 +0300 Subject: [PATCH 711/824] Removed explicit dependency on scons for external scripting (#2999) --- scripts/fbt/appmanifest.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/scripts/fbt/appmanifest.py b/scripts/fbt/appmanifest.py index a677d841366..b8b8a8d6870 100644 --- a/scripts/fbt/appmanifest.py +++ b/scripts/fbt/appmanifest.py @@ -2,9 +2,15 @@ import re from dataclasses import dataclass, field from enum import Enum -from fbt.util import resolve_real_dir_node from typing import Callable, ClassVar, List, Optional, Tuple, Union +try: + from fbt.util import resolve_real_dir_node +except ImportError: + # When running outside of SCons, we don't have access to SCons.Node + def resolve_real_dir_node(node): + return node + class FlipperManifestException(Exception): pass From 200c44bdca9905b5b26082b45378d903a782fddd Mon Sep 17 00:00:00 2001 From: hedger Date: Tue, 22 Aug 2023 20:38:45 +0300 Subject: [PATCH 712/824] loader: restored support for debug apps (#2993) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fbt: restored loader support for debug apps (no GUI tho) * desktop: fixed code for handling autorun apps * loader: fixed autorun app messing up last update stage * Loader: handle status without message Co-authored-by: あく --- applications/services/applications.h | 6 +++++ applications/services/desktop/desktop.c | 11 ++++---- applications/services/loader/loader.c | 35 +++++++++++++++---------- 3 files changed, 33 insertions(+), 19 deletions(-) diff --git a/applications/services/applications.h b/applications/services/applications.h index 426d6d2d241..7dbb7063f93 100644 --- a/applications/services/applications.h +++ b/applications/services/applications.h @@ -51,6 +51,12 @@ extern const size_t FLIPPER_ON_SYSTEM_START_COUNT; extern const FlipperInternalApplication FLIPPER_SYSTEM_APPS[]; extern const size_t FLIPPER_SYSTEM_APPS_COUNT; +/* Debug apps + * Can only be spawned by loader by name + */ +extern const FlipperInternalApplication FLIPPER_DEBUG_APPS[]; +extern const size_t FLIPPER_DEBUG_APPS_COUNT; + extern const FlipperInternalApplication FLIPPER_ARCHIVE; /* Settings list diff --git a/applications/services/desktop/desktop.c b/applications/services/desktop/desktop.c index 30e7253dae6..490f3686f7f 100644 --- a/applications/services/desktop/desktop.c +++ b/applications/services/desktop/desktop.c @@ -381,12 +381,7 @@ Desktop* desktop_alloc() { } gui_add_view_port(desktop->gui, desktop->stealth_mode_icon_viewport, GuiLayerStatusBarLeft); - // Special case: autostart application is already running desktop->loader = furi_record_open(RECORD_LOADER); - if(loader_is_locked(desktop->loader) && - animation_manager_is_animation_loaded(desktop->animation_manager)) { - animation_manager_unload_and_stall_animation(desktop->animation_manager); - } desktop->notification = furi_record_open(RECORD_NOTIFICATION); desktop->app_start_stop_subscription = furi_pubsub_subscribe( @@ -477,6 +472,12 @@ int32_t desktop_srv(void* p) { scene_manager_next_scene(desktop->scene_manager, DesktopSceneFault); } + // Special case: autostart application is already running + if(loader_is_locked(desktop->loader) && + animation_manager_is_animation_loaded(desktop->animation_manager)) { + animation_manager_unload_and_stall_animation(desktop->animation_manager); + } + view_dispatcher_run(desktop->view_dispatcher); furi_crash("That was unexpected"); diff --git a/applications/services/loader/loader.c b/applications/services/loader/loader.c index 41c0f95d420..ee5f87e9798 100644 --- a/applications/services/loader/loader.c +++ b/applications/services/loader/loader.c @@ -1,4 +1,5 @@ #include "loader.h" +#include "core/core_defines.h" #include "loader_i.h" #include #include @@ -186,18 +187,24 @@ static FlipperInternalApplication const* loader_find_application_by_name_in_list } static const FlipperInternalApplication* loader_find_application_by_name(const char* name) { - const FlipperInternalApplication* application = NULL; - application = loader_find_application_by_name_in_list(name, FLIPPER_APPS, FLIPPER_APPS_COUNT); - if(!application) { - application = loader_find_application_by_name_in_list( - name, FLIPPER_SETTINGS_APPS, FLIPPER_SETTINGS_APPS_COUNT); - } - if(!application) { - application = loader_find_application_by_name_in_list( - name, FLIPPER_SYSTEM_APPS, FLIPPER_SYSTEM_APPS_COUNT); + const struct { + const FlipperInternalApplication* list; + const uint32_t count; + } lists[] = { + {FLIPPER_SETTINGS_APPS, FLIPPER_SETTINGS_APPS_COUNT}, + {FLIPPER_SYSTEM_APPS, FLIPPER_SYSTEM_APPS_COUNT}, + {FLIPPER_DEBUG_APPS, FLIPPER_DEBUG_APPS_COUNT}, + }; + + for(size_t i = 0; i < COUNT_OF(lists); i++) { + const FlipperInternalApplication* application = + loader_find_application_by_name_in_list(name, lists[i].list, lists[i].count); + if(application) { + return application; + } } - return application; + return NULL; } static void loader_start_app_thread(Loader* loader, FlipperInternalApplicationFlag flags) { @@ -253,9 +260,7 @@ static void loader_log_status_error( furi_string_vprintf(error_message, format, args); FURI_LOG_E(TAG, "Status [%d]: %s", status, furi_string_get_cstr(error_message)); } else { - FuriString* tmp = furi_string_alloc(); - FURI_LOG_E(TAG, "Status [%d]: %s", status, furi_string_get_cstr(tmp)); - furi_string_free(tmp); + FURI_LOG_E(TAG, "Status [%d]", status); } } @@ -490,7 +495,9 @@ int32_t loader_srv(void* p) { FLIPPER_ON_SYSTEM_START[i](); } - if(FLIPPER_AUTORUN_APP_NAME && strlen(FLIPPER_AUTORUN_APP_NAME)) { + if((furi_hal_rtc_get_boot_mode() == FuriHalRtcBootModeNormal) && FLIPPER_AUTORUN_APP_NAME && + strlen(FLIPPER_AUTORUN_APP_NAME)) { + FURI_LOG_I(TAG, "Starting autorun app: %s", FLIPPER_AUTORUN_APP_NAME); loader_do_start_by_name(loader, FLIPPER_AUTORUN_APP_NAME, NULL, NULL); } From e353433cd831af015a496803c83579ee0c3b09e4 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Wed, 23 Aug 2023 02:56:27 +0900 Subject: [PATCH 713/824] [FL-3488] Assign tickets to all TODO items (#2988) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- applications/debug/accessor/helpers/wiegand.cpp | 2 +- applications/debug/subghz_test/views/subghz_test_packet.c | 1 - applications/debug/unit_tests/furi/furi_memmgr_test.c | 2 +- applications/debug/unit_tests/furi/furi_string_test.c | 2 +- applications/debug/unit_tests/lfrfid/bit_lib_test.c | 2 +- applications/debug/unit_tests/test_index.c | 2 +- applications/drivers/subghz/cc1101_ext/cc1101_ext.c | 2 +- applications/main/gpio/scenes/gpio_scene_usb_uart.c | 2 +- applications/main/infrared/infrared_cli.c | 4 ++-- applications/main/subghz/helpers/subghz_txrx.c | 1 - .../main/subghz/helpers/subghz_txrx_create_protocol_key.c | 2 +- .../main/subghz/scenes/subghz_scene_radio_setting.c | 2 +- .../main/subghz/scenes/subghz_scene_receiver_info.c | 2 +- applications/main/subghz/subghz_i.c | 4 ++-- applications/services/desktop/desktop.c | 4 ++-- applications/services/gui/modules/button_panel.c | 1 - applications/services/gui/view_dispatcher.c | 2 +- applications/services/gui/view_port.c | 2 +- applications/services/loader/loader.c | 2 +- applications/services/loader/loader_cli.c | 2 +- applications/services/power/power_service/power.c | 2 +- applications/services/rpc/rpc_gui.c | 2 +- applications/services/storage/storage_processing.c | 2 +- applications/services/storage/storages/storage_ext.c | 2 +- applications/system/updater/scenes/updater_scene_main.c | 2 +- firmware/targets/f18/furi_hal/furi_hal_resources.c | 2 +- firmware/targets/f7/ble_glue/ble_glue.c | 2 +- firmware/targets/f7/ble_glue/services/gatt_char.c | 2 +- firmware/targets/f7/fatfs/sd_spi_io.c | 6 +++--- firmware/targets/f7/furi_hal/furi_hal_nfc.h | 1 - firmware/targets/f7/furi_hal/furi_hal_subghz.c | 2 +- firmware/targets/f7/src/update.c | 2 +- lib/SConscript | 2 +- lib/drivers/bq27220.c | 2 +- lib/flipper_application/application_manifest.h | 2 +- lib/flipper_application/elf/elf_file.c | 6 +++--- lib/ibutton/ibutton_worker_modes.c | 4 ++-- lib/ibutton/protocols/blanks/rw1990.c | 4 ++-- lib/ibutton/protocols/blanks/tm2004.c | 4 ++-- lib/ibutton/protocols/dallas/dallas_common.c | 2 +- lib/ibutton/protocols/dallas/protocol_ds1971.c | 2 +- lib/ibutton/protocols/dallas/protocol_ds1992.c | 2 +- lib/ibutton/protocols/dallas/protocol_ds1996.c | 2 +- lib/lfrfid/lfrfid_dict_file.c | 4 ++-- lib/lfrfid/lfrfid_worker.h | 4 ++-- lib/lfrfid/protocols/protocol_hid_ex_generic.c | 2 +- lib/lfrfid/tools/bit_lib.c | 2 +- lib/nfc/protocols/nfcv.c | 4 ++-- lib/print/wrappers.c | 2 -- lib/subghz/devices/registry.c | 2 +- lib/subghz/protocols/bin_raw.c | 4 ++-- lib/subghz/protocols/keeloq.c | 6 +++--- lib/subghz/protocols/raw.c | 3 +-- lib/subghz/protocols/secplus_v2.c | 2 +- lib/subghz/subghz_setting.c | 2 +- lib/subghz/subghz_tx_rx_worker.c | 6 +++--- lib/toolbox/crc32_calc.c | 2 +- lib/toolbox/stream/file_stream.c | 6 +++--- lib/toolbox/stream/string_stream.c | 2 +- lib/update_util/dfu_file.c | 4 ++-- lib/update_util/update_manifest.c | 2 +- scripts/fbt_tools/compilation_db.py | 4 ++-- scripts/flipper/storage.py | 4 ++-- scripts/flipper/utils/openocd.py | 4 ++-- scripts/flipper/utils/stm32wb55.py | 2 +- scripts/toolchain/windows-toolchain-download.ps1 | 2 +- 66 files changed, 85 insertions(+), 92 deletions(-) diff --git a/applications/debug/accessor/helpers/wiegand.cpp b/applications/debug/accessor/helpers/wiegand.cpp index 5cb3a85f58a..10b284eaacd 100644 --- a/applications/debug/accessor/helpers/wiegand.cpp +++ b/applications/debug/accessor/helpers/wiegand.cpp @@ -174,7 +174,7 @@ bool WIEGAND::DoWiegandConversion() { return false; } - // TODO: Handle validation failure case! + // TODO FL-3490: Handle validation failure case! } else if(4 == _bitCount) { // 4-bit Wiegand codes have no data integrity check so we just // read the LOW nibble. diff --git a/applications/debug/subghz_test/views/subghz_test_packet.c b/applications/debug/subghz_test/views/subghz_test_packet.c index bab83ab5b57..1f345829617 100644 --- a/applications/debug/subghz_test/views/subghz_test_packet.c +++ b/applications/debug/subghz_test/views/subghz_test_packet.c @@ -56,7 +56,6 @@ static void subghz_test_packet_rx_callback(bool level, uint32_t duration, void* subghz_decoder_princeton_for_testing_parse(instance->decoder, level, duration); } -//todo static void subghz_test_packet_rx_pt_callback(SubGhzDecoderPrinceton* parser, void* context) { UNUSED(parser); furi_assert(context); diff --git a/applications/debug/unit_tests/furi/furi_memmgr_test.c b/applications/debug/unit_tests/furi/furi_memmgr_test.c index f7bc234a08c..05d967a9918 100644 --- a/applications/debug/unit_tests/furi/furi_memmgr_test.c +++ b/applications/debug/unit_tests/furi/furi_memmgr_test.c @@ -26,7 +26,7 @@ void test_furi_memmgr() { mu_assert_int_eq(66, ((uint8_t*)ptr)[i]); } - // TODO: fix realloc to copy only old size, and write testcase that leftover of reallocated memory is zero-initialized + // TODO FL-3492: fix realloc to copy only old size, and write testcase that leftover of reallocated memory is zero-initialized free(ptr); // allocate and zero-initialize array (calloc) diff --git a/applications/debug/unit_tests/furi/furi_string_test.c b/applications/debug/unit_tests/furi/furi_string_test.c index 3ea95b92b86..6cbcc0dcc7a 100644 --- a/applications/debug/unit_tests/furi/furi_string_test.c +++ b/applications/debug/unit_tests/furi/furi_string_test.c @@ -69,7 +69,7 @@ MU_TEST(mu_test_furi_string_mem) { mu_check(string != NULL); mu_check(!furi_string_empty(string)); - // TODO: how to test furi_string_reserve? + // TODO FL-3493: how to test furi_string_reserve? // test furi_string_reset furi_string_reset(string); diff --git a/applications/debug/unit_tests/lfrfid/bit_lib_test.c b/applications/debug/unit_tests/lfrfid/bit_lib_test.c index 7266157033d..dcb69de7f4f 100644 --- a/applications/debug/unit_tests/lfrfid/bit_lib_test.c +++ b/applications/debug/unit_tests/lfrfid/bit_lib_test.c @@ -311,7 +311,7 @@ MU_TEST(test_bit_lib_test_parity) { } MU_TEST(test_bit_lib_remove_bit_every_nth) { - // TODO: more tests + // TODO FL-3494: more tests uint8_t data_i[1] = {0b00001111}; uint8_t data_o[1] = {0b00011111}; size_t length; diff --git a/applications/debug/unit_tests/test_index.c b/applications/debug/unit_tests/test_index.c index f7a53d7c8f4..edaf950c54b 100644 --- a/applications/debug/unit_tests/test_index.c +++ b/applications/debug/unit_tests/test_index.c @@ -90,7 +90,7 @@ void unit_tests_cli(Cli* cli, FuriString* args, void* context) { Loader* loader = furi_record_open(RECORD_LOADER); NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); - // TODO: lock device while test running + // TODO FL-3491: lock device while test running if(loader_is_locked(loader)) { printf("RPC: stop all applications to run tests\r\n"); notification_message(notification, &sequence_blink_magenta_100); diff --git a/applications/drivers/subghz/cc1101_ext/cc1101_ext.c b/applications/drivers/subghz/cc1101_ext/cc1101_ext.c index e9560ce5f3e..eb0ec56a48f 100644 --- a/applications/drivers/subghz/cc1101_ext/cc1101_ext.c +++ b/applications/drivers/subghz/cc1101_ext/cc1101_ext.c @@ -333,7 +333,7 @@ bool subghz_device_cc1101_ext_rx_pipe_not_empty() { (CC1101_STATUS_RXBYTES) | CC1101_BURST, (uint8_t*)status); furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); - // TODO: you can add a buffer overflow flag if needed + // TODO FL-3520: you can add a buffer overflow flag if needed if(status->NUM_RXBYTES > 0) { return true; } else { diff --git a/applications/main/gpio/scenes/gpio_scene_usb_uart.c b/applications/main/gpio/scenes/gpio_scene_usb_uart.c index aa41aaf9878..52b2142dc6b 100644 --- a/applications/main/gpio/scenes/gpio_scene_usb_uart.c +++ b/applications/main/gpio/scenes/gpio_scene_usb_uart.c @@ -19,7 +19,7 @@ void gpio_scene_usb_uart_on_enter(void* context) { uint32_t prev_state = scene_manager_get_scene_state(app->scene_manager, GpioAppViewUsbUart); if(prev_state == 0) { scene_usb_uart = malloc(sizeof(SceneUsbUartBridge)); - scene_usb_uart->cfg.vcp_ch = 0; // TODO: settings load + scene_usb_uart->cfg.vcp_ch = 0; // TODO FL-3495: settings load scene_usb_uart->cfg.uart_ch = 0; scene_usb_uart->cfg.flow_pins = 0; scene_usb_uart->cfg.baudrate_mode = 0; diff --git a/applications/main/infrared/infrared_cli.c b/applications/main/infrared/infrared_cli.c index 3fa99cb02f3..54b5cabab76 100644 --- a/applications/main/infrared/infrared_cli.c +++ b/applications/main/infrared/infrared_cli.c @@ -85,7 +85,7 @@ static void infrared_cli_print_usage(void) { printf("\tir decode []\r\n"); printf("\tir universal \r\n"); printf("\tir universal list \r\n"); - // TODO: Do not hardcode universal remote names + // TODO FL-3496: Do not hardcode universal remote names printf("\tAvailable universal remotes: tv audio ac projector\r\n"); } @@ -211,7 +211,7 @@ static bool infrared_cli_decode_raw_signal( size_t i; for(i = 0; i < raw_signal->timings_size; ++i) { - // TODO: Any infrared_check_decoder_ready() magic? + // TODO FL-3523: Any infrared_check_decoder_ready() magic? const InfraredMessage* message = infrared_decode(decoder, level, raw_signal->timings[i]); if(message) { diff --git a/applications/main/subghz/helpers/subghz_txrx.c b/applications/main/subghz/helpers/subghz_txrx.c index cbd47f5e5ee..747ed73272f 100644 --- a/applications/main/subghz/helpers/subghz_txrx.c +++ b/applications/main/subghz/helpers/subghz_txrx.c @@ -334,7 +334,6 @@ static void subghz_txrx_tx_stop(SubGhzTxRx* instance) { } subghz_txrx_idle(instance); subghz_txrx_speaker_off(instance); - //Todo: Show message } FlipperFormat* subghz_txrx_get_fff_data(SubGhzTxRx* instance) { diff --git a/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.c b/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.c index 06a855c23c2..b5d47cea946 100644 --- a/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.c +++ b/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.c @@ -30,7 +30,7 @@ bool subghz_txrx_gen_data_protocol( subghz_receiver_search_decoder_base_by_name(instance->receiver, protocol_name); if(instance->decoder_result == NULL) { - //TODO: Error + //TODO FL-3502: Error // furi_string_set(error_str, "Protocol not\nfound!"); // scene_manager_next_scene(scene_manager, SubGhzSceneShowErrorSub); FURI_LOG_E(TAG, "Protocol not found!"); diff --git a/applications/main/subghz/scenes/subghz_scene_radio_setting.c b/applications/main/subghz/scenes/subghz_scene_radio_setting.c index 0a47d5bfdbd..de05417ca86 100644 --- a/applications/main/subghz/scenes/subghz_scene_radio_setting.c +++ b/applications/main/subghz/scenes/subghz_scene_radio_setting.c @@ -24,7 +24,7 @@ static void subghz_scene_radio_settings_set_device(VariableItem* item) { if(!subghz_txrx_radio_device_is_external_connected( subghz->txrx, SUBGHZ_DEVICE_CC1101_EXT_NAME) && radio_device_value[index] == SubGhzRadioDeviceTypeExternalCC1101) { - //ToDo correct if there is more than 1 module + // TODO FL-3501: correct if there is more than 1 module index = 0; } variable_item_set_current_value_text(item, radio_device_text[index]); diff --git a/applications/main/subghz/scenes/subghz_scene_receiver_info.c b/applications/main/subghz/scenes/subghz_scene_receiver_info.c index 9b57165e763..7180bb3a403 100644 --- a/applications/main/subghz/scenes/subghz_scene_receiver_info.c +++ b/applications/main/subghz/scenes/subghz_scene_receiver_info.c @@ -23,7 +23,7 @@ static bool subghz_scene_receiver_info_update_parser(void* context) { if(subghz_txrx_load_decoder_by_name_protocol( subghz->txrx, subghz_history_get_protocol_name(subghz->history, subghz->idx_menu_chosen))) { - //todo we are trying to deserialize without checking for errors, since it is assumed that we just received this chignal + // we are trying to deserialize without checking for errors, since it is assumed that we just received this chignal subghz_protocol_decoder_base_deserialize( subghz_txrx_get_decoder(subghz->txrx), subghz_history_get_raw_data(subghz->history, subghz->idx_menu_chosen)); diff --git a/applications/main/subghz/subghz_i.c b/applications/main/subghz/subghz_i.c index 55036846cae..55f33d200ba 100644 --- a/applications/main/subghz/subghz_i.c +++ b/applications/main/subghz/subghz_i.c @@ -134,7 +134,7 @@ bool subghz_key_load(SubGhz* subghz, const char* file_path, bool show_dialog) { SubGhzSetting* setting = subghz_txrx_get_setting(subghz->txrx); if(!strcmp(furi_string_get_cstr(temp_str), "CUSTOM")) { - //Todo add Custom_preset_module + //TODO FL-3551: add Custom_preset_module //delete preset if it already exists subghz_setting_delete_custom_preset(setting, furi_string_get_cstr(temp_str)); //load custom preset from file @@ -289,7 +289,7 @@ bool subghz_save_protocol_to_file( if(!storage_simply_remove(storage, dev_file_name)) { break; } - //ToDo check Write + //TODO FL-3552: check Write stream_seek(flipper_format_stream, 0, StreamOffsetFromStart); stream_save_to_file(flipper_format_stream, storage, dev_file_name, FSOM_CREATE_ALWAYS); diff --git a/applications/services/desktop/desktop.c b/applications/services/desktop/desktop.c index 490f3686f7f..333c444db98 100644 --- a/applications/services/desktop/desktop.c +++ b/applications/services/desktop/desktop.c @@ -101,7 +101,7 @@ static void desktop_clock_draw_callback(Canvas* canvas, void* context) { char buffer[20]; snprintf(buffer, sizeof(buffer), "%02u:%02u", hour, desktop->time_minute); - // ToDo: never do that, may cause visual glitches + // TODO FL-3515: never do that, may cause visual glitches view_port_set_width( desktop->clock_viewport, canvas_string_width(canvas, buffer) - 1 + (desktop->time_minute % 10 == 1)); @@ -126,7 +126,7 @@ static bool desktop_custom_event_callback(void* context, uint32_t event) { return true; case DesktopGlobalAfterAppFinished: animation_manager_load_and_continue_animation(desktop->animation_manager); - // TODO: Implement a message mechanism for loading settings and (optionally) + // TODO FL-3497: Implement a message mechanism for loading settings and (optionally) // locking and unlocking DESKTOP_SETTINGS_LOAD(&desktop->settings); diff --git a/applications/services/gui/modules/button_panel.c b/applications/services/gui/modules/button_panel.c index 9b816eed0c6..503be2dac42 100644 --- a/applications/services/gui/modules/button_panel.c +++ b/applications/services/gui/modules/button_panel.c @@ -103,7 +103,6 @@ void button_panel_reserve(ButtonPanel* button_panel, size_t reserve_x, size_t re ButtonArray_t* array = ButtonMatrix_get(model->button_matrix, i); ButtonArray_init(*array); ButtonArray_reserve(*array, reserve_x); - // TODO: do we need to clear allocated memory of ptr-s to ButtonItem ?? } LabelList_init(model->labels); }, diff --git a/applications/services/gui/view_dispatcher.c b/applications/services/gui/view_dispatcher.c index 920b3c13951..83f0edbeae9 100644 --- a/applications/services/gui/view_dispatcher.c +++ b/applications/services/gui/view_dispatcher.c @@ -272,7 +272,7 @@ void view_dispatcher_handle_input(ViewDispatcher* view_dispatcher, InputEvent* e } else if(view_dispatcher->navigation_event_callback) { // Dispatch navigation event if(!view_dispatcher->navigation_event_callback(view_dispatcher->event_context)) { - // TODO: should we allow view_dispatcher to stop without navigation_event_callback? + // TODO FL-3514: should we allow view_dispatcher to stop without navigation_event_callback? view_dispatcher_stop(view_dispatcher); return; } diff --git a/applications/services/gui/view_port.c b/applications/services/gui/view_port.c index 8c2ff6fe0b3..57c0fddb413 100644 --- a/applications/services/gui/view_port.c +++ b/applications/services/gui/view_port.c @@ -7,7 +7,7 @@ #include "gui.h" #include "gui_i.h" -// TODO add mutex to view_port ops +// TODO FL-3498: add mutex to view_port ops _Static_assert(ViewPortOrientationMAX == 4, "Incorrect ViewPortOrientation count"); _Static_assert( diff --git a/applications/services/loader/loader.c b/applications/services/loader/loader.c index ee5f87e9798..53f70a1ec6b 100644 --- a/applications/services/loader/loader.c +++ b/applications/services/loader/loader.c @@ -62,7 +62,7 @@ LoaderStatus loader_start_with_gui_error(Loader* loader, const char* name, const dialog_message_free(message); furi_record_close(RECORD_DIALOGS); } else if(status == LoaderStatusErrorUnknownApp || status == LoaderStatusErrorInternal) { - // TODO: we have many places where we can emit a double start, ex: desktop, menu + // TODO FL-3522: we have many places where we can emit a double start, ex: desktop, menu // so i prefer to not show LoaderStatusErrorAppStarted error message for now DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); DialogMessage* message = dialog_message_alloc(); diff --git a/applications/services/loader/loader_cli.c b/applications/services/loader/loader_cli.c index cbec4adca8a..489a576e5c0 100644 --- a/applications/services/loader/loader_cli.c +++ b/applications/services/loader/loader_cli.c @@ -28,7 +28,7 @@ static void loader_cli_info(Loader* loader) { if(!loader_is_locked(loader)) { printf("No application is running\r\n"); } else { - // TODO: print application name ??? + // TODO FL-3513: print application name ??? printf("Application is running\r\n"); } } diff --git a/applications/services/power/power_service/power.c b/applications/services/power/power_service/power.c index aadb5f46e40..e39a84eafcf 100644 --- a/applications/services/power/power_service/power.c +++ b/applications/services/power/power_service/power.c @@ -30,7 +30,7 @@ void power_draw_battery_callback(Canvas* canvas, void* context) { if(power->state == PowerStateCharging) { canvas_set_bitmap_mode(canvas, 1); canvas_set_color(canvas, ColorWhite); - // TODO: replace -1 magic for uint8_t with re-framing + // TODO FL-3510: replace -1 magic for uint8_t with re-framing canvas_draw_icon(canvas, 8, -1, &I_Charging_lightning_mask_9x10); canvas_set_color(canvas, ColorBlack); canvas_draw_icon(canvas, 8, -1, &I_Charging_lightning_9x10); diff --git a/applications/services/rpc/rpc_gui.c b/applications/services/rpc/rpc_gui.c index 9ba20a83208..9eff4bca6c5 100644 --- a/applications/services/rpc/rpc_gui.c +++ b/applications/services/rpc/rpc_gui.c @@ -279,7 +279,7 @@ static void rpc_system_gui_start_virtual_display_process(const PB_Main* request, return; } - // TODO: consider refactoring + // TODO FL-3511: consider refactoring // Using display framebuffer size as an XBM buffer size is like comparing apples and oranges // Glad they both are 1024 for now size_t buffer_size = canvas_get_buffer_size(rpc_gui->gui->canvas); diff --git a/applications/services/storage/storage_processing.c b/applications/services/storage/storage_processing.c index e6b42696106..70cb7b92f91 100644 --- a/applications/services/storage/storage_processing.c +++ b/applications/services/storage/storage_processing.c @@ -399,7 +399,7 @@ static FS_Error storage_process_common_fs_info( } /****************** Raw SD API ******************/ -// TODO think about implementing a custom storage API to split that kind of api linkage +// TODO FL-3521: think about implementing a custom storage API to split that kind of api linkage #include "storages/storage_ext.h" static FS_Error storage_process_sd_format(Storage* app) { diff --git a/applications/services/storage/storages/storage_ext.c b/applications/services/storage/storages/storage_ext.c index 15a355dc25a..35b3ee253a3 100644 --- a/applications/services/storage/storages/storage_ext.c +++ b/applications/services/storage/storages/storage_ext.c @@ -100,7 +100,7 @@ FS_Error sd_unmount_card(StorageData* storage) { storage->status = StorageStatusNotReady; error = FR_DISK_ERR; - // TODO do i need to close the files? + // TODO FL-3522: do i need to close the files? f_mount(0, sd_data->path, 0); return storage_ext_parse_error(error); diff --git a/applications/system/updater/scenes/updater_scene_main.c b/applications/system/updater/scenes/updater_scene_main.c index 2ef0732ca27..9fd68161f50 100644 --- a/applications/system/updater/scenes/updater_scene_main.c +++ b/applications/system/updater/scenes/updater_scene_main.c @@ -80,7 +80,7 @@ bool updater_scene_main_on_event(void* context, SceneManagerEvent event) { break; case UpdaterCustomEventSdUnmounted: - // TODO: error out, stop worker (it's probably dead actually) + // TODO FL-3499: error out, stop worker (it's probably dead actually) break; default: break; diff --git a/firmware/targets/f18/furi_hal/furi_hal_resources.c b/firmware/targets/f18/furi_hal/furi_hal_resources.c index 32c9b619c4c..f28f98b095b 100644 --- a/firmware/targets/f18/furi_hal/furi_hal_resources.c +++ b/firmware/targets/f18/furi_hal/furi_hal_resources.c @@ -225,7 +225,7 @@ void furi_hal_resources_init() { } int32_t furi_hal_resources_get_ext_pin_number(const GpioPin* gpio) { - // TODO: describe second ROW + // TODO FL-3500: describe second ROW if(gpio == &gpio_ext_pa7) return 2; else if(gpio == &gpio_ext_pa6) diff --git a/firmware/targets/f7/ble_glue/ble_glue.c b/firmware/targets/f7/ble_glue/ble_glue.c index 1cb5501d97e..746df71c7b6 100644 --- a/firmware/targets/f7/ble_glue/ble_glue.c +++ b/firmware/targets/f7/ble_glue/ble_glue.c @@ -222,7 +222,7 @@ bool ble_glue_wait_for_c2_start(int32_t timeout) { bool started = false; do { - // TODO: use mutex? + // TODO FL-3505: use mutex? started = ble_glue->status == BleGlueStatusC2Started; if(!started) { timeout--; diff --git a/firmware/targets/f7/ble_glue/services/gatt_char.c b/firmware/targets/f7/ble_glue/services/gatt_char.c index 9b6a44f61b4..e0539c34f30 100644 --- a/firmware/targets/f7/ble_glue/services/gatt_char.c +++ b/firmware/targets/f7/ble_glue/services/gatt_char.c @@ -14,7 +14,7 @@ void flipper_gatt_characteristic_init( furi_assert(char_instance); // Copy the descriptor to the instance, since it may point to stack memory - // TODO: only copy if really comes from stack + // TODO FL-3506: only copy if really comes from stack char_instance->characteristic = malloc(sizeof(FlipperGattCharacteristicParams)); memcpy( (void*)char_instance->characteristic, diff --git a/firmware/targets/f7/fatfs/sd_spi_io.c b/firmware/targets/f7/fatfs/sd_spi_io.c index e8e542b32ea..d420524df56 100644 --- a/firmware/targets/f7/fatfs/sd_spi_io.c +++ b/firmware/targets/f7/fatfs/sd_spi_io.c @@ -283,7 +283,7 @@ static SdSpiCmdAnswer cmd_answer.r1 = sd_spi_wait_for_data_and_read(); break; case SdSpiCmdAnswerTypeR1B: - // TODO: can be wrong, at least for SD_CMD12_STOP_TRANSMISSION you need to purge one byte before reading R1 + // TODO FL-3507: can be wrong, at least for SD_CMD12_STOP_TRANSMISSION you need to purge one byte before reading R1 cmd_answer.r1 = sd_spi_wait_for_data_and_read(); // In general this shenenigans seems suspicious, please double check SD specs if you are using SdSpiCmdAnswerTypeR1B @@ -322,7 +322,7 @@ static SdSpiDataResponce sd_spi_get_data_response(uint32_t timeout_ms) { switch(responce & 0x1F) { case SdSpiDataResponceOK: - // TODO: check timings + // TODO FL-3508: check timings sd_spi_deselect_card(); sd_spi_select_card(); @@ -684,7 +684,7 @@ static SdSpiStatus sd_spi_cmd_write_blocks( } // Send dummy byte for NWR timing : one byte between CMD_WRITE and TOKEN - // TODO: check bytes count + // TODO FL-3509: check bytes count sd_spi_write_byte(SD_DUMMY_BYTE); sd_spi_write_byte(SD_DUMMY_BYTE); diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc.h b/firmware/targets/f7/furi_hal/furi_hal_nfc.h index c87f04a9a0f..f4051926a58 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_nfc.h +++ b/firmware/targets/f7/furi_hal/furi_hal_nfc.h @@ -402,7 +402,6 @@ void furi_hal_nfc_ll_txrx_on(); void furi_hal_nfc_ll_txrx_off(); -// TODO rework all pollers with furi_hal_nfc_ll_txrx_bits FuriHalNfcReturn furi_hal_nfc_ll_txrx( uint8_t* txBuf, uint16_t txBufLen, diff --git a/firmware/targets/f7/furi_hal/furi_hal_subghz.c b/firmware/targets/f7/furi_hal/furi_hal_subghz.c index ac5adefb8df..327e42a870a 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_subghz.c +++ b/firmware/targets/f7/furi_hal/furi_hal_subghz.c @@ -207,7 +207,7 @@ bool furi_hal_subghz_rx_pipe_not_empty() { cc1101_read_reg( &furi_hal_spi_bus_handle_subghz, (CC1101_STATUS_RXBYTES) | CC1101_BURST, (uint8_t*)status); furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); - // TODO: you can add a buffer overflow flag if needed + // TODO FL-3503: you can add a buffer overflow flag if needed if(status->NUM_RXBYTES > 0) { return true; } else { diff --git a/firmware/targets/f7/src/update.c b/firmware/targets/f7/src/update.c index c6235a150f6..520305410e2 100644 --- a/firmware/targets/f7/src/update.c +++ b/firmware/targets/f7/src/update.c @@ -38,7 +38,7 @@ static bool flipper_update_mount_sd() { } static bool flipper_update_init() { - // TODO: Configure missing peripherals properly + // TODO FL-3504: Configure missing peripherals properly furi_hal_bus_enable(FuriHalBusHSEM); furi_hal_bus_enable(FuriHalBusIPCC); furi_hal_bus_enable(FuriHalBusRNG); diff --git a/lib/SConscript b/lib/SConscript index ab78c6ea4d4..907a5a41def 100644 --- a/lib/SConscript +++ b/lib/SConscript @@ -23,7 +23,7 @@ env.Append( env.Append( CPPPATH=[ "#/", - "#/lib", # TODO: remove! + "#/lib", # TODO FL-3553: remove! "#/lib/mlib", # Ugly hack Dir("../assets/compiled"), diff --git a/lib/drivers/bq27220.c b/lib/drivers/bq27220.c index 92dbfcd6a29..4a9feed9be0 100644 --- a/lib/drivers/bq27220.c +++ b/lib/drivers/bq27220.c @@ -69,7 +69,7 @@ static bool bq27220_parameter_check( uint8_t checksum = bq27220_get_checksum(buffer, size + 2); buffer[0] = checksum; - buffer[1] = 4 + size; // TODO: why 4? + buffer[1] = 4 + size; // TODO FL-3519: why 4? if(!furi_hal_i2c_write_mem( handle, BQ27220_ADDRESS, CommandMACDataSum, buffer, 2, BQ27220_I2C_TIMEOUT)) { FURI_LOG_I(TAG, "CRC write failed"); diff --git a/lib/flipper_application/application_manifest.h b/lib/flipper_application/application_manifest.h index 25e4f8d0a51..d09ec90044c 100644 --- a/lib/flipper_application/application_manifest.h +++ b/lib/flipper_application/application_manifest.h @@ -16,7 +16,7 @@ extern "C" { #define FAP_MANIFEST_SUPPORTED_VERSION 1 #define FAP_MANIFEST_MAX_APP_NAME_LENGTH 32 -#define FAP_MANIFEST_MAX_ICON_SIZE 32 // TODO: reduce size? +#define FAP_MANIFEST_MAX_ICON_SIZE 32 // TODO FL-3524: reduce size? #pragma pack(push, 1) diff --git a/lib/flipper_application/elf/elf_file.c b/lib/flipper_application/elf/elf_file.c index 539a48c85a7..7df3eb09006 100644 --- a/lib/flipper_application/elf/elf_file.c +++ b/lib/flipper_application/elf/elf_file.c @@ -507,7 +507,7 @@ static SectionType elf_preload_section( #endif // ignore .ARM and .rel.ARM sections - // TODO: how to do it not by name? + // TODO FL-3525: how to do it not by name? // .ARM: type 0x70000001, flags SHF_ALLOC | SHF_LINK_ORDER // .rel.ARM: type 0x9, flags SHT_REL if(str_prefix(name, ".ARM.") || str_prefix(name, ".rel.ARM.") || @@ -792,7 +792,7 @@ bool elf_file_load_section_table(ELFFile* elf) { FuriString* name = furi_string_alloc(); FURI_LOG_D(TAG, "Scan ELF indexs..."); - // TODO: why we start from 1? + // TODO FL-3526: why we start from 1? for(size_t section_idx = 1; section_idx < elf->sections_count; section_idx++) { Elf32_Shdr section_header; @@ -828,7 +828,7 @@ ElfProcessSectionResult elf_process_section( Elf32_Shdr section_header; // find section - // TODO: why we start from 1? + // TODO FL-3526: why we start from 1? for(size_t section_idx = 1; section_idx < elf->sections_count; section_idx++) { furi_string_reset(section_name); if(!elf_read_section(elf, section_idx, §ion_header, section_name)) { diff --git a/lib/ibutton/ibutton_worker_modes.c b/lib/ibutton/ibutton_worker_modes.c index 1b8e0a3b893..83e207de931 100644 --- a/lib/ibutton/ibutton_worker_modes.c +++ b/lib/ibutton/ibutton_worker_modes.c @@ -127,7 +127,7 @@ void ibutton_worker_mode_write_blank_tick(iButtonWorker* worker) { furi_assert(worker->key); const bool success = ibutton_protocols_write_blank(worker->protocols, worker->key); - // TODO: pass a proper result to the callback + // TODO FL-3527: pass a proper result to the callback const iButtonWorkerWriteResult result = success ? iButtonWorkerWriteOK : iButtonWorkerWriteNoDetect; if(worker->write_cb != NULL) { @@ -139,7 +139,7 @@ void ibutton_worker_mode_write_copy_tick(iButtonWorker* worker) { furi_assert(worker->key); const bool success = ibutton_protocols_write_copy(worker->protocols, worker->key); - // TODO: pass a proper result to the callback + // TODO FL-3527: pass a proper result to the callback const iButtonWorkerWriteResult result = success ? iButtonWorkerWriteOK : iButtonWorkerWriteNoDetect; if(worker->write_cb != NULL) { diff --git a/lib/ibutton/protocols/blanks/rw1990.c b/lib/ibutton/protocols/blanks/rw1990.c index d3350fcf00a..f86e43d99c0 100644 --- a/lib/ibutton/protocols/blanks/rw1990.c +++ b/lib/ibutton/protocols/blanks/rw1990.c @@ -62,7 +62,7 @@ bool rw1990_write_v1(OneWireHost* host, const uint8_t* data, size_t data_size) { onewire_host_write_bit(host, true); furi_delay_us(10000); - // TODO: Better error handling + // TODO FL-3528: Better error handling return rw1990_read_and_compare(host, data, data_size); } @@ -90,6 +90,6 @@ bool rw1990_write_v2(OneWireHost* host, const uint8_t* data, size_t data_size) { onewire_host_write_bit(host, false); furi_delay_us(10000); - // TODO: Better error handling + // TODO Fl-3528: Better error handling return rw1990_read_and_compare(host, data, data_size); } diff --git a/lib/ibutton/protocols/blanks/tm2004.c b/lib/ibutton/protocols/blanks/tm2004.c index ef6f0619e12..b020a218d7e 100644 --- a/lib/ibutton/protocols/blanks/tm2004.c +++ b/lib/ibutton/protocols/blanks/tm2004.c @@ -21,7 +21,7 @@ bool tm2004_write(OneWireHost* host, const uint8_t* data, size_t data_size) { onewire_host_write(host, data[i]); answer = onewire_host_read(host); - // TODO: check answer CRC + // TODO FL-3529: check answer CRC // pulse indicating that data is correct furi_delay_us(600); @@ -37,6 +37,6 @@ bool tm2004_write(OneWireHost* host, const uint8_t* data, size_t data_size) { } } - // TODO: Better error handling + // TODO FL-3529: Better error handling return i == data_size; } diff --git a/lib/ibutton/protocols/dallas/dallas_common.c b/lib/ibutton/protocols/dallas/dallas_common.c index ebf57e555fc..6e99a3be239 100644 --- a/lib/ibutton/protocols/dallas/dallas_common.c +++ b/lib/ibutton/protocols/dallas/dallas_common.c @@ -149,7 +149,7 @@ bool dallas_common_emulate_search_rom(OneWireSlave* bus, const DallasCommonRomDa if(!onewire_slave_send_bit(bus, !bit)) return false; onewire_slave_receive_bit(bus); - // TODO: check for errors and return if any + // TODO FL-3530: check for errors and return if any } } diff --git a/lib/ibutton/protocols/dallas/protocol_ds1971.c b/lib/ibutton/protocols/dallas/protocol_ds1971.c index a806acb22ee..b65e645846c 100644 --- a/lib/ibutton/protocols/dallas/protocol_ds1971.c +++ b/lib/ibutton/protocols/dallas/protocol_ds1971.c @@ -53,7 +53,7 @@ const iButtonProtocolDallasBase ibutton_protocol_ds1971 = { .name = DS1971_FAMILY_NAME, .read = dallas_ds1971_read, - .write_blank = NULL, // TODO: Implement writing to blank + .write_blank = NULL, // TODO FL-3531: Implement writing to blank .write_copy = dallas_ds1971_write_copy, .emulate = dallas_ds1971_emulate, .save = dallas_ds1971_save, diff --git a/lib/ibutton/protocols/dallas/protocol_ds1992.c b/lib/ibutton/protocols/dallas/protocol_ds1992.c index 0b4d4b34f23..7440882ea0d 100644 --- a/lib/ibutton/protocols/dallas/protocol_ds1992.c +++ b/lib/ibutton/protocols/dallas/protocol_ds1992.c @@ -73,7 +73,7 @@ bool dallas_ds1992_read(OneWireHost* host, iButtonProtocolData* protocol_data) { bool dallas_ds1992_write_blank(OneWireHost* host, iButtonProtocolData* protocol_data) { DS1992ProtocolData* data = protocol_data; - // TODO: Make this work, currently broken + // TODO FL-3532: Make this work, currently broken return tm2004_write(host, (uint8_t*)data, sizeof(DallasCommonRomData) + DS1992_SRAM_DATA_SIZE); } diff --git a/lib/ibutton/protocols/dallas/protocol_ds1996.c b/lib/ibutton/protocols/dallas/protocol_ds1996.c index 5358b63e267..5970a67bbfe 100644 --- a/lib/ibutton/protocols/dallas/protocol_ds1996.c +++ b/lib/ibutton/protocols/dallas/protocol_ds1996.c @@ -159,7 +159,7 @@ static bool dallas_ds1996_command_callback(uint8_t command, void* context) { case DALLAS_COMMON_CMD_MATCH_ROM: case DALLAS_COMMON_CMD_OVERDRIVE_MATCH_ROM: - /* TODO: Match ROM command support */ + /* TODO FL-3533: Match ROM command support */ default: return false; } diff --git a/lib/lfrfid/lfrfid_dict_file.c b/lib/lfrfid/lfrfid_dict_file.c index 7ae84f8b687..18bf505f0f5 100644 --- a/lib/lfrfid/lfrfid_dict_file.c +++ b/lib/lfrfid/lfrfid_dict_file.c @@ -17,13 +17,13 @@ bool lfrfid_dict_file_save(ProtocolDict* dict, ProtocolId protocol, const char* if(!flipper_format_file_open_always(file, filename)) break; if(!flipper_format_write_header_cstr(file, LFRFID_DICT_FILETYPE, 1)) break; - // TODO: write comment about protocol types into file + // TODO FL-3517: write comment about protocol types into file if(!flipper_format_write_string_cstr( file, "Key type", protocol_dict_get_name(dict, protocol))) break; - // TODO: write comment about protocol sizes into file + // TODO FL-3517: write comment about protocol sizes into file protocol_dict_get_data(dict, protocol, data, data_size); diff --git a/lib/lfrfid/lfrfid_worker.h b/lib/lfrfid/lfrfid_worker.h index def9f89a474..22135097e26 100644 --- a/lib/lfrfid/lfrfid_worker.h +++ b/lib/lfrfid/lfrfid_worker.h @@ -26,8 +26,8 @@ typedef enum { } LFRFIDWorkerReadType; typedef enum { - LFRFIDWorkerReadSenseStart, // TODO: not implemented - LFRFIDWorkerReadSenseEnd, // TODO: not implemented + LFRFIDWorkerReadSenseStart, // TODO FL-3516: not implemented + LFRFIDWorkerReadSenseEnd, // TODO FL-3516: not implemented LFRFIDWorkerReadSenseCardStart, LFRFIDWorkerReadSenseCardEnd, LFRFIDWorkerReadStartASK, diff --git a/lib/lfrfid/protocols/protocol_hid_ex_generic.c b/lib/lfrfid/protocols/protocol_hid_ex_generic.c index 240128cbec0..35500ab595b 100644 --- a/lib/lfrfid/protocols/protocol_hid_ex_generic.c +++ b/lib/lfrfid/protocols/protocol_hid_ex_generic.c @@ -193,7 +193,7 @@ bool protocol_hid_ex_generic_write_data(ProtocolHIDEx* protocol, void* data) { }; void protocol_hid_ex_generic_render_data(ProtocolHIDEx* protocol, FuriString* result) { - // TODO: parser and render functions + // TODO FL-3518: parser and render functions UNUSED(protocol); furi_string_printf(result, "Generic HID Extended\r\nData: Unknown"); }; diff --git a/lib/lfrfid/tools/bit_lib.c b/lib/lfrfid/tools/bit_lib.c index 54decb3e807..e0d0ff40213 100644 --- a/lib/lfrfid/tools/bit_lib.c +++ b/lib/lfrfid/tools/bit_lib.c @@ -38,7 +38,7 @@ uint8_t bit_lib_get_bits(const uint8_t* data, size_t position, uint8_t length) { if(shift == 0) { return data[position / 8] >> (8 - length); } else { - // TODO fix read out of bounds + // TODO FL-3534: fix read out of bounds uint8_t value = (data[position / 8] << (shift)); value |= data[position / 8 + 1] >> (8 - shift); value = value >> (8 - length); diff --git a/lib/nfc/protocols/nfcv.c b/lib/nfc/protocols/nfcv.c index 017b06cae02..28146328115 100644 --- a/lib/nfc/protocols/nfcv.c +++ b/lib/nfc/protocols/nfcv.c @@ -29,7 +29,7 @@ ReturnCode nfcv_inventory(uint8_t* uid) { ReturnCode ret = ERR_NONE; for(int tries = 0; tries < NFCV_COMMAND_RETRIES; tries++) { - /* TODO: needs proper abstraction via fury_hal(_ll)_* */ + /* TODO: needs proper abstraction via furi_hal(_ll)_* */ ret = rfalNfcvPollerInventory(RFAL_NFCV_NUM_SLOTS_1, 0, NULL, &res, &received); if(ret == ERR_NONE) { @@ -89,7 +89,7 @@ ReturnCode nfcv_read_sysinfo(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data) { FURI_LOG_D(TAG, "Read SYSTEM INFORMATION..."); for(int tries = 0; tries < NFCV_COMMAND_RETRIES; tries++) { - /* TODO: needs proper abstraction via fury_hal(_ll)_* */ + /* TODO: needs proper abstraction via furi_hal(_ll)_* */ ret = rfalNfcvPollerGetSystemInformation( RFAL_NFCV_REQ_FLAG_DEFAULT, NULL, rxBuf, sizeof(rxBuf), &received); diff --git a/lib/print/wrappers.c b/lib/print/wrappers.c index 5cfe1060043..b248aeb3dfe 100644 --- a/lib/print/wrappers.c +++ b/lib/print/wrappers.c @@ -59,7 +59,6 @@ int __wrap_fflush(FILE* stream) { __attribute__((__noreturn__)) void __wrap___assert(const char* file, int line, const char* e) { UNUSED(file); UNUSED(line); - // TODO: message file and line number furi_crash(e); } @@ -68,6 +67,5 @@ __attribute__((__noreturn__)) void UNUSED(file); UNUSED(line); UNUSED(func); - // TODO: message file and line number furi_crash(e); } \ No newline at end of file diff --git a/lib/subghz/devices/registry.c b/lib/subghz/devices/registry.c index c0d5bb292dc..779ba81d7a6 100644 --- a/lib/subghz/devices/registry.c +++ b/lib/subghz/devices/registry.c @@ -22,7 +22,7 @@ void subghz_device_registry_init(void) { SUBGHZ_RADIO_DEVICE_PLUGIN_API_VERSION, firmware_api_interface); - //ToDo: fix path to plugins + //TODO FL-3556: fix path to plugins if(plugin_manager_load_all(subghz_device->manager, "/any/apps_data/subghz/plugins") != //if(plugin_manager_load_all(subghz_device->manager, APP_DATA_PATH("plugins")) != PluginManagerErrorNone) { diff --git a/lib/subghz/protocols/bin_raw.c b/lib/subghz/protocols/bin_raw.c index 123a4ba9d31..c3dbf84fe1a 100644 --- a/lib/subghz/protocols/bin_raw.c +++ b/lib/subghz/protocols/bin_raw.c @@ -744,7 +744,7 @@ static bool bin_raw_debug("\r\n\r\n"); #endif - //todo can be optimized + //TODO FL-3557: can be optimized BinRAW_Markup markup_temp[BIN_RAW_MAX_MARKUP_COUNT]; memcpy( markup_temp, @@ -770,7 +770,7 @@ static bool } } } - //todo can be optimized + //TODO FL-3557: can be optimized if(bin_raw_type == BinRAWTypeGap) { if(data_temp != 0) { //there are sequences with the same number of bits diff --git a/lib/subghz/protocols/keeloq.c b/lib/subghz/protocols/keeloq.c index 80fecbbf934..8789a0f2001 100644 --- a/lib/subghz/protocols/keeloq.c +++ b/lib/subghz/protocols/keeloq.c @@ -122,7 +122,7 @@ static bool subghz_protocol_keeloq_gen_data(SubGhzProtocolEncoderKeeloq* instanc uint32_t fix = (uint32_t)btn << 28 | instance->generic.serial; uint32_t decrypt = (uint32_t)btn << 28 | (instance->generic.serial & 0x3FF) - << 16 | //ToDo in some protocols the discriminator is 0 + << 16 | //TODO FL-3558: in some protocols the discriminator is 0 instance->generic.cnt; uint32_t hop = 0; uint64_t man = 0; @@ -149,7 +149,7 @@ static bool subghz_protocol_keeloq_gen_data(SubGhzProtocolEncoderKeeloq* instanc hop = subghz_protocol_keeloq_common_encrypt(decrypt, man); break; case KEELOQ_LEARNING_UNKNOWN: - hop = 0; //todo + hop = 0; //TODO FL-3559 break; } break; @@ -200,7 +200,7 @@ static bool //gen new key if(subghz_protocol_keeloq_gen_data(instance, btn)) { - //ToDo if you need to add a callback to automatically update the data on the display + //TODO FL-3560: if you need to add a callback to automatically update the data on the display } else { return false; } diff --git a/lib/subghz/protocols/raw.c b/lib/subghz/protocols/raw.c index 1288c957371..c9d2b3017bf 100644 --- a/lib/subghz/protocols/raw.c +++ b/lib/subghz/protocols/raw.c @@ -265,7 +265,7 @@ SubGhzProtocolStatus furi_assert(context); UNUSED(context); UNUSED(flipper_format); - //ToDo stub, for backwards compatibility + // stub, for backwards compatibility return SubGhzProtocolStatusOk; } @@ -273,7 +273,6 @@ void subghz_protocol_decoder_raw_get_string(void* context, FuriString* output) { furi_assert(context); //SubGhzProtocolDecoderRAW* instance = context; UNUSED(context); - //ToDo no use furi_string_cat_printf(output, "RAW Date"); } diff --git a/lib/subghz/protocols/secplus_v2.c b/lib/subghz/protocols/secplus_v2.c index c8ecbea22e5..48ee6698202 100644 --- a/lib/subghz/protocols/secplus_v2.c +++ b/lib/subghz/protocols/secplus_v2.c @@ -380,7 +380,7 @@ static void subghz_protocol_secplus_v2_encode(SubGhzProtocolEncoderSecPlus_v2* i uint8_t roll_2[9] = {0}; instance->generic.cnt++; - //ToDo it is not known what value the counter starts + //TODO Fl-3548: 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); diff --git a/lib/subghz/subghz_setting.c b/lib/subghz/subghz_setting.c index 9804f827733..c0216913fbf 100644 --- a/lib/subghz/subghz_setting.c +++ b/lib/subghz/subghz_setting.c @@ -344,7 +344,7 @@ void subghz_setting_load(SubGhzSetting* instance, const char* file_path) { } while(flipper_format_read_uint32( fff_data_file, "Frequency", (uint32_t*)&temp_data32, 1)) { - //Todo: add a frequency support check depending on the selected radio device + //Todo FL-3535: add a frequency support check depending on the selected radio device if(furi_hal_subghz_is_frequency_valid(temp_data32)) { FURI_LOG_I(TAG, "Frequency loaded %lu", temp_data32); FrequencyList_push_back(instance->frequencies, temp_data32); diff --git a/lib/subghz/subghz_tx_rx_worker.c b/lib/subghz/subghz_tx_rx_worker.c index 250e6666f40..833d90bfdc5 100644 --- a/lib/subghz/subghz_tx_rx_worker.c +++ b/lib/subghz/subghz_tx_rx_worker.c @@ -165,7 +165,7 @@ static int32_t subghz_tx_rx_worker_thread(void* context) { SUBGHZ_TXRX_WORKER_TIMEOUT_READ_WRITE_BUF); subghz_tx_rx_worker_tx(instance, data, SUBGHZ_TXRX_WORKER_MAX_TXRX_SIZE); } else { - //todo checking that he managed to write all the data to the TX buffer + //TODO FL-3554: checking that it managed to write all the data to the TX buffer furi_stream_buffer_receive( instance->stream_tx, &data, size_tx, SUBGHZ_TXRX_WORKER_TIMEOUT_READ_WRITE_BUF); subghz_tx_rx_worker_tx(instance, data, size_tx); @@ -178,7 +178,7 @@ static int32_t subghz_tx_rx_worker_thread(void* context) { furi_stream_buffer_bytes_available(instance->stream_rx) == 0) { callback_rx = true; } - //todo checking that he managed to write all the data to the RX buffer + //TODO FL-3554: checking that it managed to write all the data to the RX buffer furi_stream_buffer_send( instance->stream_rx, &data, @@ -189,7 +189,7 @@ static int32_t subghz_tx_rx_worker_thread(void* context) { callback_rx = false; } } else { - //todo RX buffer overflow + //TODO FL-3555: RX buffer overflow } } } diff --git a/lib/toolbox/crc32_calc.c b/lib/toolbox/crc32_calc.c index c8ae3524a11..c0cd169b180 100644 --- a/lib/toolbox/crc32_calc.c +++ b/lib/toolbox/crc32_calc.c @@ -4,7 +4,7 @@ #define CRC_DATA_BUFFER_MAX_LEN 512 uint32_t crc32_calc_buffer(uint32_t crc, const void* buffer, size_t size) { - // TODO: consider removing dependency on LFS + // TODO FL-3547: consider removing dependency on LFS return ~lfs_crc(~crc, buffer, size); } diff --git a/lib/toolbox/stream/file_stream.c b/lib/toolbox/stream/file_stream.c index 064912168bb..095dce472ca 100644 --- a/lib/toolbox/stream/file_stream.c +++ b/lib/toolbox/stream/file_stream.c @@ -134,7 +134,7 @@ static size_t file_stream_size(FileStream* stream) { } static size_t file_stream_write(FileStream* stream, const uint8_t* data, size_t size) { - // TODO cache + // TODO FL-3545: cache size_t need_to_write = size; while(need_to_write > 0) { uint16_t was_written = @@ -148,7 +148,7 @@ static size_t file_stream_write(FileStream* stream, const uint8_t* data, size_t } static size_t file_stream_read(FileStream* stream, uint8_t* data, size_t size) { - // TODO cache + // TODO FL-3545: cache size_t need_to_read = size; while(need_to_read > 0) { uint16_t was_read = @@ -172,7 +172,7 @@ static bool file_stream_delete_and_insert( // open scratchpad Stream* scratch_stream = file_stream_alloc(_stream->storage); - // TODO: we need something like "storage_open_tmpfile and storage_close_tmpfile" + // TODO FL-3546: we need something like "storage_open_tmpfile and storage_close_tmpfile" FuriString* scratch_name; FuriString* tmp_name; tmp_name = furi_string_alloc(); diff --git a/lib/toolbox/stream/string_stream.c b/lib/toolbox/stream/string_stream.c index 075f0b26fe5..f8a360c03ea 100644 --- a/lib/toolbox/stream/string_stream.c +++ b/lib/toolbox/stream/string_stream.c @@ -106,7 +106,7 @@ static size_t string_stream_size(StringStream* stream) { } static size_t string_stream_write(StringStream* stream, const char* data, size_t size) { - // TODO: can be optimized for edge cases + // TODO FL-3544: can be optimized for edge cases size_t i; for(i = 0; i < size; i++) { string_stream_write_char(stream, data[i]); diff --git a/lib/update_util/dfu_file.c b/lib/update_util/dfu_file.c index 62b139e864f..eef9f064591 100644 --- a/lib/update_util/dfu_file.c +++ b/lib/update_util/dfu_file.c @@ -55,7 +55,7 @@ uint8_t dfu_file_validate_headers(File* dfuf, const DfuValidationParams* referen if((dfu_suffix.bLength != sizeof(DfuSuffix)) || (dfu_suffix.bcdDFU != DFU_SUFFIX_VERSION)) { return 0; } - /* TODO: check DfuSignature?.. */ + /* TODO FL-3561: check DfuSignature?.. */ if((dfu_suffix.idVendor != reference_params->vendor) || (dfu_suffix.idProduct != reference_params->product) || @@ -137,7 +137,7 @@ bool dfu_file_process_targets(const DfuUpdateTask* task, File* dfuf, const uint8 return UpdateBlockResult_Failed; } - /* TODO: look into TargetPrefix and validate/filter?.. */ + /* TODO FL-3562: look into TargetPrefix and validate/filter?.. */ for(uint32_t i_element = 0; i_element < target_prefix.dwNbElements; ++i_element) { bytes_read = storage_file_read(dfuf, &image_element, sizeof(ImageElementHeader)); if(bytes_read != sizeof(ImageElementHeader)) { diff --git a/lib/update_util/update_manifest.c b/lib/update_util/update_manifest.c index 795fdd5ebc9..47b2cc0b985 100644 --- a/lib/update_util/update_manifest.c +++ b/lib/update_util/update_manifest.c @@ -54,7 +54,7 @@ static bool FuriString* filetype; - // TODO: compare filetype? + // TODO FL-3543: compare filetype? filetype = furi_string_alloc(); update_manifest->valid = flipper_format_read_header(flipper_file, filetype, &update_manifest->manifest_version) && diff --git a/scripts/fbt_tools/compilation_db.py b/scripts/fbt_tools/compilation_db.py index 17ff6aaa3fa..1f829ddb411 100644 --- a/scripts/fbt_tools/compilation_db.py +++ b/scripts/fbt_tools/compilation_db.py @@ -38,7 +38,7 @@ from SCons.Tool.cc import CSuffixes from SCons.Tool.asm import ASSuffixes, ASPPSuffixes -# TODO: Is there a better way to do this than this global? Right now this exists so that the +# TODO FL-3542: Is there a better way to do this than this global? Right now this exists so that the # emitter we add can record all of the things it emits, so that the scanner for the top level # compilation database can access the complete list, and also so that the writer has easy # access to write all of the files. But it seems clunky. How can the emitter and the scanner @@ -91,7 +91,7 @@ def emit_compilation_db_entry(target, source, env): __COMPILATIONDB_ENV=env, ) - # TODO: Technically, these next two lines should not be required: it should be fine to + # TODO FL-3541: Technically, these next two lines should not be required: it should be fine to # cache the entries. However, they don't seem to update properly. Since they are quick # to re-generate disable caching and sidestep this problem. env.AlwaysBuild(entry) diff --git a/scripts/flipper/storage.py b/scripts/flipper/storage.py index 2c9c043d5a8..40af5cebc80 100644 --- a/scripts/flipper/storage.py +++ b/scripts/flipper/storage.py @@ -150,7 +150,7 @@ def list_tree(self, path: str = "/", level: int = 0): for line in lines: try: - # TODO: better decoding, considering non-ascii characters + # TODO FL-3539: better decoding, considering non-ascii characters line = line.decode("ascii") except Exception: continue @@ -193,7 +193,7 @@ def walk(self, path: str = "/"): for line in lines: try: - # TODO: better decoding, considering non-ascii characters + # TODO FL-3539: better decoding, considering non-ascii characters line = line.decode("ascii") except Exception: continue diff --git a/scripts/flipper/utils/openocd.py b/scripts/flipper/utils/openocd.py index 1309055b8d1..a43568090c5 100644 --- a/scripts/flipper/utils/openocd.py +++ b/scripts/flipper/utils/openocd.py @@ -78,7 +78,7 @@ def start(self, args: list[str] = []): def _wait_for_openocd_tcl(self): """Wait for OpenOCD to start""" - # TODO: timeout + # TODO Fl-3538: timeout while True: stderr = self.process.stderr if not stderr: @@ -128,7 +128,7 @@ def send_tcl(self, cmd) -> str: def _recv(self): """Read from the stream until the token (\x1a) was received.""" - # TODO: timeout + # TODO FL-3538: timeout data = bytes() while True: chunk = self.socket.recv(4096) diff --git a/scripts/flipper/utils/stm32wb55.py b/scripts/flipper/utils/stm32wb55.py index 4a47b8beafe..9ea80322055 100644 --- a/scripts/flipper/utils/stm32wb55.py +++ b/scripts/flipper/utils/stm32wb55.py @@ -247,7 +247,7 @@ def option_bytes_id_to_address(self, id: int) -> int: def flash_wait_for_operation(self): # Wait for flash operation to complete - # TODO: timeout + # TODO FL-3537: timeout while True: self.FLASH_SR.load() if self.FLASH_SR.BSY == 0: diff --git a/scripts/toolchain/windows-toolchain-download.ps1 b/scripts/toolchain/windows-toolchain-download.ps1 index 05ea4be2cf3..f30b157dab6 100644 --- a/scripts/toolchain/windows-toolchain-download.ps1 +++ b/scripts/toolchain/windows-toolchain-download.ps1 @@ -1,7 +1,7 @@ Set-StrictMode -Version 2.0 $ErrorActionPreference = "Stop" [Net.ServicePointManager]::SecurityProtocol = "tls12, tls11, tls" -# TODO: fix +# TODO FL-3536: fix path to download_dir $download_dir = (Get-Item "$PSScriptRoot\..\..").FullName $toolchain_version = $args[0] $toolchain_target_path = $args[1] From 991e58e405ade42663a4971921900a6b3ad4802a Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Tue, 22 Aug 2023 21:08:58 +0300 Subject: [PATCH 714/824] Littlefs updated to v2.7.0 (#2986) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- lib/littlefs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/littlefs b/lib/littlefs index 40dba4a556e..611c9b20db2 160000 --- a/lib/littlefs +++ b/lib/littlefs @@ -1 +1 @@ -Subproject commit 40dba4a556e0d81dfbe64301a6aa4e18ceca896c +Subproject commit 611c9b20db2b99faee261daa7cc9bbe175d3eaca From ace09011259f48c597c1f22312ccc5e23c6f4dc9 Mon Sep 17 00:00:00 2001 From: hedger Date: Tue, 22 Aug 2023 21:33:10 +0300 Subject: [PATCH 715/824] [FL-3486,FL-3392] fbt: various improvements and bug fixes (#2982) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fbt: extapps: compact debug format for .faps * fbt: sdk: fixed symbol cache regen logic for removed-only symbols * lib: elf_file: early .fap file handle release * fbt: extapps: added FAP_VERSION define for application environments * github: added appsymbols artifact * api: updates for f18 * github: fixed early fap_dist * fbt: added flash_dap * ufbt: added flash_dap * fbt: reworked flash target; scripts: program.py->fwflash.py and changes * vscode: updated configuration * scripts: fwflash.py: ugly fixes for ufbt * scripts: fwflash.py: cleanup * fbt: flash: always use .elf file * scripts: fwflash: fixed elf file path Co-authored-by: あく --- .github/workflows/build.yml | 4 +- .github/workflows/unit_tests.yml | 4 +- .github/workflows/updater_test.yml | 2 +- .vscode/example/tasks.json | 16 +- SConstruct | 20 +-- documentation/fbt.md | 7 +- firmware/targets/f18/api_symbols.csv | 11 +- firmware/targets/f7/api_symbols.csv | 11 +- lib/flipper_application/elf/elf_file.c | 12 +- scripts/fbt/sdk/cache.py | 1 + scripts/fbt_tools/fbt_debugopts.py | 2 +- scripts/fbt_tools/fbt_dist.py | 67 +++++---- scripts/fbt_tools/fbt_extapps.py | 5 + scripts/fbt_tools/fbt_help.py | 4 +- scripts/{program.py => fwflash.py} | 193 +++++++++++++------------ scripts/ufbt/SConstruct | 31 +--- scripts/ufbt/commandline.scons | 16 +- scripts/ufbt/site_tools/ufbt_help.py | 4 +- site_scons/commandline.scons | 16 +- site_scons/extapps.scons | 4 - 20 files changed, 225 insertions(+), 205 deletions(-) rename scripts/{program.py => fwflash.py} (71%) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b9eb4d7097d..bd85858a314 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -58,7 +58,7 @@ jobs: - name: 'Bundle scripts' if: ${{ !github.event.pull_request.head.repo.fork }} run: | - tar czpf artifacts/flipper-z-any-scripts-${SUFFIX}.tgz scripts + tar czpf "artifacts/flipper-z-any-scripts-${SUFFIX}.tgz" scripts - name: 'Build the firmware' run: | @@ -73,6 +73,8 @@ jobs: ./fbt TARGET_HW=$TARGET_HW fap_dist tar czpf "artifacts/flipper-z-${TARGET}-debugapps-${SUFFIX}.tgz" \ -C dist/${TARGET}-*/apps/Debug . + tar czpf "artifacts/flipper-z-${TARGET}-appsymbols-${SUFFIX}.tgz" \ + -C dist/${TARGET}-*/debug_elf . done - name: "Check for uncommitted changes" diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 9c6c6b2db6f..2e6336c0741 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -31,7 +31,7 @@ jobs: if: success() timeout-minutes: 10 run: | - ./fbt flash OPENOCD_ADAPTER_SERIAL=2A0906016415303030303032 FIRMWARE_APP_SET=unit_tests FORCE=1 + ./fbt flash SWD_TRANSPORT_SERIAL=2A0906016415303030303032 FIRMWARE_APP_SET=unit_tests FORCE=1 - name: 'Wait for flipper and format ext' id: format_ext @@ -64,4 +64,4 @@ jobs: - name: 'Check GDB output' if: failure() run: | - ./fbt gdb_trace_all OPENOCD_ADAPTER_SERIAL=2A0906016415303030303032 FIRMWARE_APP_SET=unit_tests FORCE=1 + ./fbt gdb_trace_all SWD_TRANSPORT_SERIAL=2A0906016415303030303032 FIRMWARE_APP_SET=unit_tests FORCE=1 diff --git a/.github/workflows/updater_test.yml b/.github/workflows/updater_test.yml index 27a181c4612..f4064e1cca3 100644 --- a/.github/workflows/updater_test.yml +++ b/.github/workflows/updater_test.yml @@ -64,7 +64,7 @@ jobs: - name: 'Flash last release' if: failure() run: | - ./fbt flash OPENOCD_ADAPTER_SERIAL=${{steps.device.outputs.stlink}} FORCE=1 + ./fbt flash SWD_TRANSPORT_SERIAL=${{steps.device.outputs.stlink}} FORCE=1 - name: 'Wait for flipper and format ext' if: failure() diff --git a/.vscode/example/tasks.json b/.vscode/example/tasks.json index 3c01506a8c1..e36a1fe47ab 100644 --- a/.vscode/example/tasks.json +++ b/.vscode/example/tasks.json @@ -16,29 +16,17 @@ "command": "./fbt" }, { - "label": "[Release] Flash (ST-Link)", + "label": "[Release] Flash (SWD)", "group": "build", "type": "shell", "command": "./fbt COMPACT=1 DEBUG=0 FORCE=1 flash" }, { - "label": "[Debug] Flash (ST-Link)", + "label": "[Debug] Flash (SWD)", "group": "build", "type": "shell", "command": "./fbt FORCE=1 flash" }, - { - "label": "[Release] Flash (blackmagic)", - "group": "build", - "type": "shell", - "command": "./fbt COMPACT=1 DEBUG=0 FORCE=1 flash_blackmagic" - }, - { - "label": "[Debug] Flash (blackmagic)", - "group": "build", - "type": "shell", - "command": "./fbt FORCE=1 flash_blackmagic" - }, { "label": "[Release] Flash (JLink)", "group": "build", diff --git a/SConstruct b/SConstruct index 04cd057fd7e..44ee7746731 100644 --- a/SConstruct +++ b/SConstruct @@ -185,27 +185,15 @@ copro_dist = distenv.CoproBuilder( distenv.AlwaysBuild(copro_dist) distenv.Alias("copro_dist", copro_dist) -firmware_flash = distenv.AddOpenOCDFlashTarget(firmware_env) + +firmware_flash = distenv.AddFwFlashTarget(firmware_env) distenv.Alias("flash", firmware_flash) +# To be implemented in fwflash.py firmware_jflash = distenv.AddJFlashTarget(firmware_env) distenv.Alias("jflash", firmware_jflash) -firmware_bm_flash = distenv.PhonyTarget( - "flash_blackmagic", - "$GDB $GDBOPTS $SOURCES $GDBFLASH", - source=firmware_env["FW_ELF"], - GDBOPTS="${GDBOPTS_BASE} ${GDBOPTS_BLACKMAGIC}", - GDBREMOTE="${BLACKMAGIC_ADDR}", - GDBFLASH=[ - "-ex", - "load", - "-ex", - "quit", - ], -) - -gdb_backtrace_all_threads = distenv.PhonyTarget( +distenv.PhonyTarget( "gdb_trace_all", "$GDB $GDBOPTS $SOURCES $GDBFLASH", source=firmware_env["FW_ELF"], diff --git a/documentation/fbt.md b/documentation/fbt.md index af588382b04..02de2949fa8 100644 --- a/documentation/fbt.md +++ b/documentation/fbt.md @@ -66,7 +66,7 @@ To use language servers other than the default VS Code C/C++ language server, us - `fap_dist` - build external plugins & publish to the `dist` folder. - `updater_package`, `updater_minpackage` - build a self-update package. The minimal version only includes the firmware's DFU file; the full version also includes a radio stack & resources for the SD card. - `copro_dist` - bundle Core2 FUS+stack binaries for qFlipper. -- `flash` - flash the attached device with OpenOCD over ST-Link. +- `flash` - flash the attached device over SWD interface with supported probes. Probe is detected automatically; you can override it with `SWD_TRANSPORT=...` variable. If multiple probes are attached, you can specify the serial number of the probe to use with `SWD_TRANSPORT_SERIAL=...`. - `flash_usb`, `flash_usb_full` - build, upload and install the update package to the device over USB. See details on `updater_package` and `updater_minpackage`. - `debug` - build and flash firmware, then attach with gdb with firmware's .elf loaded. - `debug_other`, `debug_other_blackmagic` - attach GDB without loading any `.elf`. It will allow you to manually add external `.elf` files with `add-symbol-file` in GDB. @@ -75,7 +75,7 @@ To use language servers other than the default VS Code C/C++ language server, us - `blackmagic` - debug firmware with Blackmagic probe (WiFi dev board). - `openocd` - just start OpenOCD. - `get_blackmagic` - output the blackmagic address in the GDB remote format. Useful for IDE integration. -- `get_stlink` - output serial numbers for attached STLink probes. Used for specifying an adapter with `OPENOCD_ADAPTER_SERIAL=...`. +- `get_stlink` - output serial numbers for attached STLink probes. Used for specifying an adapter with `SWD_TRANSPORT_SERIAL=...`. - `lint`, `format` - run clang-format on the C source code to check and reformat it according to the `.clang-format` specs. - `lint_py`, `format_py` - run [black](https://black.readthedocs.io/en/stable/index.html) on the Python source code, build system files & application manifests. - `firmware_pvs` - generate a PVS Studio report for the firmware. Requires PVS Studio to be available on your system's `PATH`. @@ -88,9 +88,8 @@ To use language servers other than the default VS Code C/C++ language server, us - `fap_snake_game`, etc. - build single app as `.fap` by its application ID. - Check out [`--extra-ext-apps`](#command-line-parameters) for force adding extra apps to external build. - `fap_snake_game_list`, etc - generate source + assembler listing for app's `.fap`. -- `flash`, `firmware_flash` - flash the current version to the attached device with OpenOCD over ST-Link. +- `flash`, `firmware_flash` - flash the current version to the attached device over SWD. - `jflash` - flash the current version to the attached device with JFlash using a J-Link probe. The JFlash executable must be on your `$PATH`. -- `flash_blackmagic` - flash the current version to the attached device with a Blackmagic probe. - `firmware_all`, `updater_all` - build a basic set of binaries. - `firmware_list`, `updater_list` - generate source + assembler listing. - `firmware_cdb`, `updater_cdb` - generate a `compilation_database.json` file for external tools and IDEs. It can be created without actually building the firmware. diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index eab140d5cf9..d935ee790dd 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1038,18 +1038,18 @@ Function,+,furi_hal_cortex_timer_is_expired,_Bool,FuriHalCortexTimer Function,+,furi_hal_cortex_timer_wait,void,FuriHalCortexTimer Function,+,furi_hal_crypto_ctr,_Bool,"const uint8_t*, const uint8_t*, const uint8_t*, uint8_t*, size_t" Function,+,furi_hal_crypto_decrypt,_Bool,"const uint8_t*, uint8_t*, size_t" +Function,+,furi_hal_crypto_enclave_ensure_key,_Bool,uint8_t +Function,+,furi_hal_crypto_enclave_load_key,_Bool,"uint8_t, const uint8_t*" +Function,+,furi_hal_crypto_enclave_store_key,_Bool,"FuriHalCryptoKey*, uint8_t*" +Function,+,furi_hal_crypto_enclave_unload_key,_Bool,uint8_t +Function,+,furi_hal_crypto_enclave_verify,_Bool,"uint8_t*, uint8_t*" Function,+,furi_hal_crypto_encrypt,_Bool,"const uint8_t*, uint8_t*, size_t" Function,+,furi_hal_crypto_gcm,_Bool,"const uint8_t*, const uint8_t*, const uint8_t*, size_t, const uint8_t*, uint8_t*, size_t, uint8_t*, _Bool" Function,+,furi_hal_crypto_gcm_decrypt_and_verify,FuriHalCryptoGCMState,"const uint8_t*, const uint8_t*, const uint8_t*, size_t, const uint8_t*, uint8_t*, size_t, const uint8_t*" Function,+,furi_hal_crypto_gcm_encrypt_and_tag,FuriHalCryptoGCMState,"const uint8_t*, const uint8_t*, const uint8_t*, size_t, const uint8_t*, uint8_t*, size_t, uint8_t*" Function,-,furi_hal_crypto_init,void, Function,+,furi_hal_crypto_load_key,_Bool,"const uint8_t*, const uint8_t*" -Function,+,furi_hal_crypto_enclave_store_key,_Bool,"FuriHalCryptoKey*, uint8_t*" -Function,+,furi_hal_crypto_enclave_load_key,_Bool,"uint8_t, const uint8_t*" -Function,+,furi_hal_crypto_enclave_unload_key,_Bool,uint8_t Function,+,furi_hal_crypto_unload_key,_Bool, -Function,+,furi_hal_crypto_enclave_verify,_Bool,"uint8_t*, uint8_t*" -Function,+,furi_hal_crypto_enclave_ensure_key,_Bool,uint8_t Function,+,furi_hal_debug_disable,void, Function,+,furi_hal_debug_enable,void, Function,+,furi_hal_debug_is_gdb_session_active,_Bool, @@ -1471,7 +1471,6 @@ Function,+,gui_add_framebuffer_callback,void,"Gui*, GuiCanvasCommitCallback, voi Function,+,gui_add_view_port,void,"Gui*, ViewPort*, GuiLayer" Function,+,gui_direct_draw_acquire,Canvas*,Gui* Function,+,gui_direct_draw_release,void,Gui* -Function,-,gui_active_view_port_count,size_t,"Gui*, GuiLayer" Function,+,gui_get_framebuffer_size,size_t,const Gui* Function,+,gui_remove_framebuffer_callback,void,"Gui*, GuiCanvasCommitCallback, void*" Function,+,gui_remove_view_port,void,"Gui*, ViewPort*" diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 8efe980a77d..d6c54e395d7 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1109,18 +1109,18 @@ Function,+,furi_hal_cortex_timer_is_expired,_Bool,FuriHalCortexTimer Function,+,furi_hal_cortex_timer_wait,void,FuriHalCortexTimer Function,+,furi_hal_crypto_ctr,_Bool,"const uint8_t*, const uint8_t*, const uint8_t*, uint8_t*, size_t" Function,+,furi_hal_crypto_decrypt,_Bool,"const uint8_t*, uint8_t*, size_t" +Function,+,furi_hal_crypto_enclave_ensure_key,_Bool,uint8_t +Function,+,furi_hal_crypto_enclave_load_key,_Bool,"uint8_t, const uint8_t*" +Function,+,furi_hal_crypto_enclave_store_key,_Bool,"FuriHalCryptoKey*, uint8_t*" +Function,+,furi_hal_crypto_enclave_unload_key,_Bool,uint8_t +Function,+,furi_hal_crypto_enclave_verify,_Bool,"uint8_t*, uint8_t*" Function,+,furi_hal_crypto_encrypt,_Bool,"const uint8_t*, uint8_t*, size_t" Function,+,furi_hal_crypto_gcm,_Bool,"const uint8_t*, const uint8_t*, const uint8_t*, size_t, const uint8_t*, uint8_t*, size_t, uint8_t*, _Bool" Function,+,furi_hal_crypto_gcm_decrypt_and_verify,FuriHalCryptoGCMState,"const uint8_t*, const uint8_t*, const uint8_t*, size_t, const uint8_t*, uint8_t*, size_t, const uint8_t*" Function,+,furi_hal_crypto_gcm_encrypt_and_tag,FuriHalCryptoGCMState,"const uint8_t*, const uint8_t*, const uint8_t*, size_t, const uint8_t*, uint8_t*, size_t, uint8_t*" Function,-,furi_hal_crypto_init,void, Function,+,furi_hal_crypto_load_key,_Bool,"const uint8_t*, const uint8_t*" -Function,+,furi_hal_crypto_enclave_store_key,_Bool,"FuriHalCryptoKey*, uint8_t*" -Function,+,furi_hal_crypto_enclave_load_key,_Bool,"uint8_t, const uint8_t*" -Function,+,furi_hal_crypto_enclave_unload_key,_Bool,uint8_t Function,+,furi_hal_crypto_unload_key,_Bool, -Function,+,furi_hal_crypto_enclave_verify,_Bool,"uint8_t*, uint8_t*" -Function,+,furi_hal_crypto_enclave_ensure_key,_Bool,uint8_t Function,+,furi_hal_debug_disable,void, Function,+,furi_hal_debug_enable,void, Function,+,furi_hal_debug_is_gdb_session_active,_Bool, @@ -1642,7 +1642,6 @@ Function,+,gui_add_framebuffer_callback,void,"Gui*, GuiCanvasCommitCallback, voi Function,+,gui_add_view_port,void,"Gui*, ViewPort*, GuiLayer" Function,+,gui_direct_draw_acquire,Canvas*,Gui* Function,+,gui_direct_draw_release,void,Gui* -Function,-,gui_active_view_port_count,size_t,"Gui*, GuiLayer" Function,+,gui_get_framebuffer_size,size_t,const Gui* Function,+,gui_remove_framebuffer_callback,void,"Gui*, GuiCanvasCommitCallback, void*" Function,+,gui_remove_view_port,void,"Gui*, ViewPort*" diff --git a/lib/flipper_application/elf/elf_file.c b/lib/flipper_application/elf/elf_file.c index 7df3eb09006..bea7c1231ff 100644 --- a/lib/flipper_application/elf/elf_file.c +++ b/lib/flipper_application/elf/elf_file.c @@ -1,3 +1,4 @@ +#include "storage/storage.h" #include #include "elf_file.h" #include "elf_file_i.h" @@ -57,6 +58,13 @@ static void address_cache_put(AddressCache_t cache, int symEntry, Elf32_Addr sym /********************************************** ELF ***********************************************/ /**************************************************************************************************/ +static void elf_file_maybe_release_fd(ELFFile* elf) { + if(elf->fd) { + storage_file_free(elf->fd); + elf->fd = NULL; + } +} + static ELFSection* elf_file_get_section(ELFFile* elf, const char* name) { return ELFSectionDict_get(elf->sections, name); } @@ -764,7 +772,7 @@ void elf_file_free(ELFFile* elf) { free(elf->debug_link_info.debug_link); } - storage_file_free(elf->fd); + elf_file_maybe_release_fd(elf); free(elf); } @@ -855,6 +863,7 @@ ElfProcessSectionResult elf_process_section( } ELFFileLoadStatus elf_file_load_sections(ELFFile* elf) { + furi_check(elf->fd != NULL); ELFFileLoadStatus status = ELFFileLoadStatusSuccess; ELFSectionDict_it_t it; @@ -895,6 +904,7 @@ ELFFileLoadStatus elf_file_load_sections(ELFFile* elf) { FURI_LOG_I(TAG, "Total size of loaded sections: %zu", total_size); } + elf_file_maybe_release_fd(elf); return status; } diff --git a/scripts/fbt/sdk/cache.py b/scripts/fbt/sdk/cache.py index b6f6edbe512..074cac6b918 100644 --- a/scripts/fbt/sdk/cache.py +++ b/scripts/fbt/sdk/cache.py @@ -237,6 +237,7 @@ def sync_sets( removed_entries = known_set - new_set if removed_entries: print(f"Removed: {removed_entries}") + self.loaded_dirty_version = True known_set -= removed_entries # If any of removed entries was a part of active API, that's a major bump if update_version and any( diff --git a/scripts/fbt_tools/fbt_debugopts.py b/scripts/fbt_tools/fbt_debugopts.py index d46ecd8f32c..392465a518f 100644 --- a/scripts/fbt_tools/fbt_debugopts.py +++ b/scripts/fbt_tools/fbt_debugopts.py @@ -21,7 +21,7 @@ def generate(env, **kw): FBT_DEBUG_DIR="${FBT_SCRIPT_DIR}/debug", ) - if (adapter_serial := env.subst("$OPENOCD_ADAPTER_SERIAL")) != "auto": + if (adapter_serial := env.subst("$SWD_TRANSPORT_SERIAL")) != "auto": env.Append( OPENOCD_OPTS=[ "-c", diff --git a/scripts/fbt_tools/fbt_dist.py b/scripts/fbt_tools/fbt_dist.py index e47898bd9ed..fdf66c0a719 100644 --- a/scripts/fbt_tools/fbt_dist.py +++ b/scripts/fbt_tools/fbt_dist.py @@ -52,22 +52,16 @@ def AddFwProject(env, base_env, fw_type, fw_env_key): return project_env -def AddOpenOCDFlashTarget(env, targetenv, **kw): - openocd_target = env.OpenOCDFlash( - "#build/oocd-${BUILD_CFG}-flash.flag", - targetenv["FW_BIN"], - OPENOCD_COMMAND=[ - "-c", - "program ${SOURCE.posix} reset exit ${BASE_ADDRESS}", - ], - BUILD_CFG=targetenv.subst("$FIRMWARE_BUILD_CFG"), - BASE_ADDRESS=targetenv.subst("$IMAGE_BASE_ADDRESS"), +def AddFwFlashTarget(env, targetenv, **kw): + fwflash_target = env.FwFlash( + "#build/flash.flag", + targetenv["FW_ELF"], **kw, ) - env.Alias(targetenv.subst("${FIRMWARE_BUILD_CFG}_flash"), openocd_target) + env.Alias(targetenv.subst("${FIRMWARE_BUILD_CFG}_flash"), fwflash_target) if env["FORCE"]: - env.AlwaysBuild(openocd_target) - return openocd_target + env.AlwaysBuild(fwflash_target) + return fwflash_target def AddJFlashTarget(env, targetenv, **kw): @@ -115,7 +109,7 @@ def generate(env): env.SetDefault(COPROCOMSTR="\tCOPRO\t${TARGET}") env.AddMethod(AddFwProject) env.AddMethod(DistCommand) - env.AddMethod(AddOpenOCDFlashTarget) + env.AddMethod(AddFwFlashTarget) env.AddMethod(GetProjetDirName) env.AddMethod(AddJFlashTarget) env.AddMethod(AddUsbFlashTarget) @@ -125,30 +119,53 @@ def generate(env): SELFUPDATE_SCRIPT="${FBT_SCRIPT_DIR}/selfupdate.py", DIST_SCRIPT="${FBT_SCRIPT_DIR}/sconsdist.py", COPRO_ASSETS_SCRIPT="${FBT_SCRIPT_DIR}/assets.py", + FW_FLASH_SCRIPT="${FBT_SCRIPT_DIR}/fwflash.py", ) env.Append( BUILDERS={ + "FwFlash": Builder( + action=[ + [ + "${PYTHON3}", + "${FW_FLASH_SCRIPT}", + "-d" if env["VERBOSE"] else "", + "--interface=${SWD_TRANSPORT}", + "--serial=${SWD_TRANSPORT_SERIAL}", + "${SOURCE}", + ], + Touch("${TARGET}"), + ] + ), "UsbInstall": Builder( action=[ - Action( - '${PYTHON3} "${SELFUPDATE_SCRIPT}" -p ${FLIP_PORT} ${UPDATE_BUNDLE_DIR}/update.fuf' - ), + [ + "${PYTHON3}", + "${SELFUPDATE_SCRIPT}", + "-p", + "${FLIP_PORT}", + "${UPDATE_BUNDLE_DIR}/update.fuf", + ], Touch("${TARGET}"), ] ), "CoproBuilder": Builder( action=Action( [ - '${PYTHON3} "${COPRO_ASSETS_SCRIPT}" ' - "copro ${COPRO_CUBE_DIR} " - "${TARGET} ${COPRO_MCU_FAMILY} " - "--cube_ver=${COPRO_CUBE_VERSION} " - "--stack_type=${COPRO_STACK_TYPE} " - '--stack_file="${COPRO_STACK_BIN}" ' - "--stack_addr=${COPRO_STACK_ADDR} ", + [ + "${PYTHON3}", + "${COPRO_ASSETS_SCRIPT}", + "copro", + "${COPRO_CUBE_DIR}", + "${TARGET}", + "${COPRO_MCU_FAMILY}", + "--cube_ver=${COPRO_CUBE_VERSION}", + "--stack_type=${COPRO_STACK_TYPE}", + "--stack_file=${COPRO_STACK_BIN}", + "--stack_addr=${COPRO_STACK_ADDR}", + ] ], - "$COPROCOMSTR", + "${COPROCOMSTR}", ) ), } diff --git a/scripts/fbt_tools/fbt_extapps.py b/scripts/fbt_tools/fbt_extapps.py index 642c1c989d5..6059628f004 100644 --- a/scripts/fbt_tools/fbt_extapps.py +++ b/scripts/fbt_tools/fbt_extapps.py @@ -53,6 +53,11 @@ def _setup_app_env(self): FAP_SRC_DIR=self.app._appdir, FAP_WORK_DIR=self.app_work_dir, ) + self.app_env.Append( + CPPDEFINES=[ + ("FAP_VERSION", f'"{".".join(map(str, self.app.fap_version))}"') + ], + ) self.app_env.VariantDir(self.app_work_dir, self.app._appdir, duplicate=False) def _build_external_files(self): diff --git a/scripts/fbt_tools/fbt_help.py b/scripts/fbt_tools/fbt_help.py index 68fc2aaf9ff..dcdce934efe 100644 --- a/scripts/fbt_tools/fbt_help.py +++ b/scripts/fbt_tools/fbt_help.py @@ -16,8 +16,8 @@ Flashing & debugging: - flash, flash_blackmagic, jflash: - Flash firmware to target using debug probe + flash, jflash: + Flash firmware to target using SWD probe. See also SWD_TRANSPORT, SWD_TRANSPORT_SERIAL flash_usb, flash_usb_full: Install firmware using self-update package debug, debug_other, blackmagic: diff --git a/scripts/program.py b/scripts/fwflash.py similarity index 71% rename from scripts/program.py rename to scripts/fwflash.py index f3e7e3e2d19..2b1ad543ba9 100755 --- a/scripts/program.py +++ b/scripts/fwflash.py @@ -6,14 +6,17 @@ import time import typing from abc import ABC, abstractmethod -from dataclasses import dataclass +from dataclasses import dataclass, field from flipper.app import App +# When adding an interface, also add it to SWD_TRANSPORT in fbt options + + class Programmer(ABC): @abstractmethod - def flash(self, bin: str) -> bool: + def flash(self, file_path: str, do_verify: bool) -> bool: pass @abstractmethod @@ -32,9 +35,9 @@ def set_serial(self, serial: str): @dataclass class OpenOCDInterface: name: str - file: str + config_file: str serial_cmd: str - additional_args: typing.Optional[list[str]] = None + additional_args: typing.Optional[list[str]] = field(default_factory=list) class OpenOCDProgrammer(Programmer): @@ -44,12 +47,10 @@ def __init__(self, interface: OpenOCDInterface): self.serial: typing.Optional[str] = None def _add_file(self, params: list[str], file: str): - params.append("-f") - params.append(file) + params += ["-f", file] def _add_command(self, params: list[str], command: str): - params.append("-c") - params.append(command) + params += ["-c", command] def _add_serial(self, params: list[str], serial: str): self._add_command(params, f"{self.interface.serial_cmd} {serial}") @@ -57,22 +58,27 @@ def _add_serial(self, params: list[str], serial: str): def set_serial(self, serial: str): self.serial = serial - def flash(self, bin: str) -> bool: - i = self.interface - + def flash(self, file_path: str, do_verify: bool) -> bool: if os.altsep: - bin = bin.replace(os.sep, os.altsep) + file_path = file_path.replace(os.sep, os.altsep) openocd_launch_params = ["openocd"] - self._add_file(openocd_launch_params, i.file) + self._add_file(openocd_launch_params, self.interface.config_file) if self.serial: self._add_serial(openocd_launch_params, self.serial) - if i.additional_args: - for a in i.additional_args: - self._add_command(openocd_launch_params, a) + for additional_arg in self.interface.additional_args: + self._add_command(openocd_launch_params, additional_arg) self._add_file(openocd_launch_params, "target/stm32wbx.cfg") self._add_command(openocd_launch_params, "init") - self._add_command(openocd_launch_params, f"program {bin} reset exit 0x8000000") + program_params = [ + "program", + f'"{file_path}"', + "verify" if do_verify else "", + "reset", + "exit", + "0x8000000" if file_path.endswith(".bin") else "", + ] + self._add_command(openocd_launch_params, " ".join(program_params)) # join the list of parameters into a string, but add quote if there are spaces openocd_launch_params_string = " ".join( @@ -105,7 +111,7 @@ def probe(self) -> bool: i = self.interface openocd_launch_params = ["openocd"] - self._add_file(openocd_launch_params, i.file) + self._add_file(openocd_launch_params, i.config_file) if self.serial: self._add_serial(openocd_launch_params, self.serial) if i.additional_args: @@ -187,7 +193,7 @@ def _resolve_hostname(hostname): def blackmagic_find_networked(serial: str): - if not serial: + if not serial or serial == "auto": serial = "blackmagic.local" # remove the tcp: prefix if it's there @@ -234,7 +240,7 @@ def set_serial(self, serial: str): else: self.port = serial - def flash(self, bin: str) -> bool: + def flash(self, file_path: str, do_verify: bool) -> bool: if not self.port: if not self.probe(): return False @@ -242,12 +248,14 @@ def flash(self, bin: str) -> bool: # We can convert .bin to .elf with objcopy: # arm-none-eabi-objcopy -I binary -O elf32-littlearm --change-section-address=.data=0x8000000 -B arm -S app.bin app.elf # But I choose to use the .elf file directly because we are flashing our own firmware and it always has an elf predecessor. - elf = bin.replace(".bin", ".elf") - if not os.path.exists(elf): - self.logger.error( - f"Sorry, but Blackmagic can't flash .bin file, and {elf} doesn't exist" - ) - return False + + if file_path.endswith(".bin"): + file_path = file_path[:-4] + ".elf" + if not os.path.exists(file_path): + self.logger.error( + f"Sorry, but Blackmagic can't flash .bin file, and {file_path} doesn't exist" + ) + return False # arm-none-eabi-gdb build/f7-firmware-D/firmware.bin # -ex 'set pagination off' @@ -260,7 +268,7 @@ def flash(self, bin: str) -> bool: # -ex 'compare-sections' # -ex 'quit' - gdb_launch_params = ["arm-none-eabi-gdb", elf] + gdb_launch_params = ["arm-none-eabi-gdb", file_path] self._add_command(gdb_launch_params, f"target extended-remote {self.port}") self._add_command(gdb_launch_params, "set pagination off") self._add_command(gdb_launch_params, "set confirm off") @@ -268,7 +276,8 @@ def flash(self, bin: str) -> bool: self._add_command(gdb_launch_params, "attach 1") self._add_command(gdb_launch_params, "set mem inaccessible-by-default off") self._add_command(gdb_launch_params, "load") - self._add_command(gdb_launch_params, "compare-sections") + if do_verify: + self._add_command(gdb_launch_params, "compare-sections") self._add_command(gdb_launch_params, "quit") self.logger.debug(f"Launching: {' '.join(gdb_launch_params)}") @@ -314,7 +323,9 @@ def get_name(self) -> str: return self.name -programmers: list[Programmer] = [ +#################### + +local_flash_interfaces: list[Programmer] = [ OpenOCDProgrammer( OpenOCDInterface( "cmsis-dap", @@ -325,69 +336,64 @@ def get_name(self) -> str: ), OpenOCDProgrammer( OpenOCDInterface( - "stlink", "interface/stlink.cfg", "hla_serial", ["transport select hla_swd"] + "stlink", + "interface/stlink.cfg", + "hla_serial", + ["transport select hla_swd"], ), ), BlackmagicProgrammer(blackmagic_find_serial, "blackmagic_usb"), ] -network_programmers = [ +network_flash_interfaces: list[Programmer] = [ BlackmagicProgrammer(blackmagic_find_networked, "blackmagic_wifi") ] +all_flash_interfaces = [*local_flash_interfaces, *network_flash_interfaces] + +#################### + class Main(App): + AUTO_INTERFACE = "auto" + def init(self): - self.subparsers = self.parser.add_subparsers(help="sub-command help") - self.parser_flash = self.subparsers.add_parser("flash", help="Flash a binary") - self.parser_flash.add_argument( - "bin", + self.parser.add_argument( + "filename", type=str, - help="Binary to flash", + help="File to flash", + ) + self.parser.add_argument( + "--verify", + "-v", + action="store_true", + help="Verify flash after programming", + default=False, ) - interfaces = [i.get_name() for i in programmers] - interfaces.extend([i.get_name() for i in network_programmers]) - self.parser_flash.add_argument( + self.parser.add_argument( "--interface", - choices=interfaces, + choices=( + self.AUTO_INTERFACE, + *[i.get_name() for i in all_flash_interfaces], + ), type=str, + default=self.AUTO_INTERFACE, help="Interface to use", ) - self.parser_flash.add_argument( + self.parser.add_argument( "--serial", type=str, + default=self.AUTO_INTERFACE, help="Serial number or port of the programmer", ) - self.parser_flash.set_defaults(func=self.flash) - - def _search_interface(self, serial: typing.Optional[str]) -> list[Programmer]: - found_programmers = [] + self.parser.set_defaults(func=self.flash) - for p in programmers: - name = p.get_name() - if serial: - p.set_serial(serial) - self.logger.debug(f"Trying {name} with {serial}") - else: - self.logger.debug(f"Trying {name}") - - if p.probe(): - self.logger.debug(f"Found {name}") - found_programmers += [p] - else: - self.logger.debug(f"Failed to probe {name}") - - return found_programmers - - def _search_network_interface( - self, serial: typing.Optional[str] - ) -> list[Programmer]: + def _search_interface(self, interface_list: list[Programmer]) -> list[Programmer]: found_programmers = [] - for p in network_programmers: + for p in interface_list: name = p.get_name() - - if serial: + if (serial := self.args.serial) != self.AUTO_INTERFACE: p.set_serial(serial) self.logger.debug(f"Trying {name} with {serial}") else: @@ -395,7 +401,7 @@ def _search_network_interface( if p.probe(): self.logger.debug(f"Found {name}") - found_programmers += [p] + found_programmers.append(p) else: self.logger.debug(f"Failed to probe {name}") @@ -403,55 +409,60 @@ def _search_network_interface( def flash(self): start_time = time.time() - bin_path = os.path.abspath(self.args.bin) + file_path = os.path.abspath(self.args.filename) - if not os.path.exists(bin_path): - self.logger.error(f"Binary file not found: {bin_path}") + if not os.path.exists(file_path): + self.logger.error(f"Binary file not found: {file_path}") return 1 - if self.args.interface: - i_name = self.args.interface - interfaces = [p for p in programmers if p.get_name() == i_name] - if len(interfaces) == 0: - interfaces = [p for p in network_programmers if p.get_name() == i_name] + if self.args.interface != self.AUTO_INTERFACE: + available_interfaces = list( + filter( + lambda p: p.get_name() == self.args.interface, + all_flash_interfaces, + ) + ) + else: - self.logger.info("Probing for interfaces...") - interfaces = self._search_interface(self.args.serial) + self.logger.info("Probing for local interfaces...") + available_interfaces = self._search_interface(local_flash_interfaces) - if len(interfaces) == 0: + if not available_interfaces: # Probe network blackmagic self.logger.info("Probing for network interfaces...") - interfaces = self._search_network_interface(self.args.serial) + available_interfaces = self._search_interface(network_flash_interfaces) - if len(interfaces) == 0: + if not available_interfaces: self.logger.error("No interface found") return 1 - - if len(interfaces) > 1: + elif len(available_interfaces) > 1: self.logger.error("Multiple interfaces found: ") self.logger.error( - f"Please specify '--interface={[i.get_name() for i in interfaces]}'" + f"Please specify '--interface={[i.get_name() for i in available_interfaces]}'" ) return 1 - interface = interfaces[0] + interface = available_interfaces.pop(0) - if self.args.serial: + if self.args.serial != self.AUTO_INTERFACE: interface.set_serial(self.args.serial) self.logger.info( - f"Flashing {bin_path} via {interface.get_name()} with {self.args.serial}" + f"Flashing {file_path} via {interface.get_name()} with {self.args.serial}" ) else: - self.logger.info(f"Flashing {bin_path} via {interface.get_name()}") + self.logger.info(f"Flashing {file_path} via {interface.get_name()}") - if not interface.flash(bin_path): + if not interface.flash(file_path, self.args.verify): self.logger.error(f"Failed to flash via {interface.get_name()}") return 1 flash_time = time.time() - start_time - bin_size = os.path.getsize(bin_path) self.logger.info(f"Flashed successfully in {flash_time:.2f}s") - self.logger.info(f"Effective speed: {bin_size / flash_time / 1024:.2f} KiB/s") + if file_path.endswith(".bin"): + bin_size = os.path.getsize(file_path) + self.logger.info( + f"Effective speed: {bin_size / flash_time / 1024:.2f} KiB/s" + ) return 0 diff --git a/scripts/ufbt/SConstruct b/scripts/ufbt/SConstruct index 1c2f2bdf587..1630135c2bc 100644 --- a/scripts/ufbt/SConstruct +++ b/scripts/ufbt/SConstruct @@ -144,24 +144,20 @@ dist_env = env.Clone( ], ) -openocd_target = dist_env.OpenOCDFlash( +flash_target = dist_env.FwFlash( dist_env["UFBT_STATE_DIR"].File("flash"), - dist_env["FW_BIN"], - OPENOCD_COMMAND=[ - "-c", - "program ${SOURCE.posix} reset exit 0x08000000", - ], + dist_env["FW_ELF"], ) -dist_env.Alias("firmware_flash", openocd_target) -dist_env.Alias("flash", openocd_target) +dist_env.Alias("firmware_flash", flash_target) +dist_env.Alias("flash", flash_target) if env["FORCE"]: - env.AlwaysBuild(openocd_target) + env.AlwaysBuild(flash_target) firmware_jflash = dist_env.JFlash( dist_env["UFBT_STATE_DIR"].File("jflash"), dist_env["FW_BIN"], - JFLASHADDR="0x20000000", + JFLASHADDR="0x08000000", ) dist_env.Alias("firmware_jflash", firmware_jflash) dist_env.Alias("jflash", firmware_jflash) @@ -213,21 +209,6 @@ dist_env.PhonyTarget( GDBPYOPTS=debug_other_opts, ) - -dist_env.PhonyTarget( - "flash_blackmagic", - "$GDB $GDBOPTS $SOURCES $GDBFLASH", - source=dist_env["FW_ELF"], - GDBOPTS="${GDBOPTS_BASE} ${GDBOPTS_BLACKMAGIC}", - GDBREMOTE="${BLACKMAGIC_ADDR}", - GDBFLASH=[ - "-ex", - "load", - "-ex", - "quit", - ], -) - flash_usb_full = dist_env.UsbInstall( dist_env["UFBT_STATE_DIR"].File("usbinstall"), [], diff --git a/scripts/ufbt/commandline.scons b/scripts/ufbt/commandline.scons index 349b4ef2524..99c34c35da3 100644 --- a/scripts/ufbt/commandline.scons +++ b/scripts/ufbt/commandline.scons @@ -55,9 +55,21 @@ vars.AddVariables( "Blackmagic probe location", "auto", ), + EnumVariable( + "SWD_TRANSPORT", + help="SWD interface adapter type", + default="auto", + allowed_values=[ + "auto", + "cmsis-dap", + "stlink", + "blackmagic_usb", + "blackmagic_wifi", + ], + ), ( - "OPENOCD_ADAPTER_SERIAL", - "OpenOCD adapter serial number", + "SWD_TRANSPORT_SERIAL", + "SWD interface adapter serial number", "auto", ), ( diff --git a/scripts/ufbt/site_tools/ufbt_help.py b/scripts/ufbt/site_tools/ufbt_help.py index 3d7f6f00214..1df6a0591ae 100644 --- a/scripts/ufbt/site_tools/ufbt_help.py +++ b/scripts/ufbt/site_tools/ufbt_help.py @@ -20,8 +20,8 @@ Build FAP app with appid={APPID}; upload & start it over USB Flashing & debugging: - flash, flash_blackmagic, *jflash: - Flash firmware to target using debug probe + flash, *jflash: + Flash firmware to target using SWD probe. See also SWD_TRANSPORT, SWD_TRANSPORT_SERIAL flash_usb, flash_usb_full: Install firmware using self-update package debug, debug_other, blackmagic: diff --git a/site_scons/commandline.scons b/site_scons/commandline.scons index 096cbd54ffc..f75d5e0e4da 100644 --- a/site_scons/commandline.scons +++ b/site_scons/commandline.scons @@ -175,9 +175,21 @@ vars.AddVariables( "Blackmagic probe location", "auto", ), + EnumVariable( + "SWD_TRANSPORT", + help="SWD interface adapter type", + default="auto", + allowed_values=[ + "auto", + "cmsis-dap", + "stlink", + "blackmagic_usb", + "blackmagic_wifi", + ], + ), ( - "OPENOCD_ADAPTER_SERIAL", - "OpenOCD adapter serial number", + "SWD_TRANSPORT_SERIAL", + "SWD interface adapter serial number", "auto", ), ( diff --git a/site_scons/extapps.scons b/site_scons/extapps.scons index 5c6f18d6884..97b7ac095e8 100644 --- a/site_scons/extapps.scons +++ b/site_scons/extapps.scons @@ -23,18 +23,14 @@ appenv.Replace( appenv.AppendUnique( CCFLAGS=[ - "-ggdb3", "-mword-relocations", "-mlong-calls", "-fno-common", "-nostdlib", - "-fvisibility=hidden", ], LINKFLAGS=[ "-Ur", "-Wl,-Ur", - # "-Wl,--orphan-handling=error", - "-Bsymbolic", "-nostartfiles", "-mlong-calls", "-fno-common", From 27b2808ade1e0cfb0736c0e148e2100812f3d895 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Wed, 23 Aug 2023 03:40:05 +0900 Subject: [PATCH 716/824] [FL-3481] Properly reset the NFC device data (#2980) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- applications/main/nfc/scenes/nfc_scene_file_select.c | 2 ++ lib/nfc/nfc_device.c | 3 +++ 2 files changed, 5 insertions(+) diff --git a/applications/main/nfc/scenes/nfc_scene_file_select.c b/applications/main/nfc/scenes/nfc_scene_file_select.c index 374a933d1c7..ce7ec92f461 100644 --- a/applications/main/nfc/scenes/nfc_scene_file_select.c +++ b/applications/main/nfc/scenes/nfc_scene_file_select.c @@ -3,6 +3,8 @@ void nfc_scene_file_select_on_enter(void* context) { Nfc* nfc = context; + nfc_device_data_clear(&nfc->dev->dev_data); + // Process file_select return nfc_device_set_loading_callback(nfc->dev, nfc_show_loading_popup, nfc); if(!furi_string_size(nfc->dev->load_path)) { diff --git a/lib/nfc/nfc_device.c b/lib/nfc/nfc_device.c index 8abf637d7ec..2c92b5bf31e 100644 --- a/lib/nfc/nfc_device.c +++ b/lib/nfc/nfc_device.c @@ -1625,6 +1625,9 @@ void nfc_device_data_clear(NfcDeviceData* dev_data) { } else if(dev_data->protocol == NfcDeviceProtocolEMV) { memset(&dev_data->emv_data, 0, sizeof(EmvData)); } + + furi_string_reset(dev_data->parsed_data); + memset(&dev_data->nfc_data, 0, sizeof(FuriHalNfcDevData)); dev_data->protocol = NfcDeviceProtocolUnknown; furi_string_reset(dev_data->parsed_data); From dc7517e5fdfa409eab53e4b2638a333ce82ed83e Mon Sep 17 00:00:00 2001 From: Dzhos Oleksii <35292229+Programistich@users.noreply.github.com> Date: Tue, 22 Aug 2023 22:15:38 +0300 Subject: [PATCH 717/824] Fix display last symbol in multiline text (#2967) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Revert submodules * Compare without round * Gui: slightly more integer logic in elements_get_max_chars_to_fit Co-authored-by: hedger Co-authored-by: あく --- applications/services/gui/elements.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/applications/services/gui/elements.c b/applications/services/gui/elements.c index 9b7c84ece19..37ecfde4c67 100644 --- a/applications/services/gui/elements.c +++ b/applications/services/gui/elements.c @@ -236,11 +236,11 @@ static size_t } if(len_px > px_left) { - uint8_t excess_symbols_approximately = - roundf((float)(len_px - px_left) / ((float)len_px / (float)text_size)); + size_t excess_symbols_approximately = + ceilf((float)(len_px - px_left) / ((float)len_px / (float)text_size)); // reduce to 5 to be sure dash fit, and next line will be at least 5 symbols long if(excess_symbols_approximately > 0) { - excess_symbols_approximately = MAX(excess_symbols_approximately, 5); + excess_symbols_approximately = MAX(excess_symbols_approximately, 5u); result = text_size - excess_symbols_approximately - 1; } else { result = text_size; From 15f92f765d4f3941795705ff6e1fa5b668d855c9 Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Wed, 23 Aug 2023 15:26:47 +0300 Subject: [PATCH 718/824] [FL-3479] Desktop: more favorites, configurable dummy mode (#2972) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Desktop: more favorite app shortcuts * Making PVS happy * Desktop settings submenu fix Co-authored-by: あく --- .../animations/views/bubble_animation_view.c | 2 +- .../views/one_shot_animation_view.c | 2 +- applications/services/desktop/desktop.c | 1 - .../services/desktop/desktop_settings.h | 22 +++- .../desktop/scenes/desktop_scene_locked.c | 4 + .../desktop/scenes/desktop_scene_main.c | 76 ++++++++---- .../services/desktop/views/desktop_events.h | 15 ++- .../desktop/views/desktop_view_locked.c | 1 + .../desktop/views/desktop_view_main.c | 20 +-- .../scenes/desktop_settings_scene_favorite.c | 77 ++++++++---- .../scenes/desktop_settings_scene_i.h | 3 + .../scenes/desktop_settings_scene_start.c | 116 ++++++++++++++---- 12 files changed, 243 insertions(+), 96 deletions(-) diff --git a/applications/services/desktop/animations/views/bubble_animation_view.c b/applications/services/desktop/animations/views/bubble_animation_view.c index 607862d1133..30a165087bb 100644 --- a/applications/services/desktop/animations/views/bubble_animation_view.c +++ b/applications/services/desktop/animations/views/bubble_animation_view.c @@ -128,8 +128,8 @@ static bool bubble_animation_input_callback(InputEvent* event, void* context) { if(event->key == InputKeyRight) { /* Right button reserved for animation activation, so consume */ - consumed = true; if(event->type == InputTypeShort) { + consumed = true; if(animation_view->interact_callback) { animation_view->interact_callback(animation_view->interact_callback_context); } diff --git a/applications/services/desktop/animations/views/one_shot_animation_view.c b/applications/services/desktop/animations/views/one_shot_animation_view.c index 9a4dff06c59..077f82d092e 100644 --- a/applications/services/desktop/animations/views/one_shot_animation_view.c +++ b/applications/services/desktop/animations/views/one_shot_animation_view.c @@ -65,8 +65,8 @@ static bool one_shot_view_input(InputEvent* event, void* context) { if(!consumed) { if(event->key == InputKeyRight) { /* Right button reserved for animation activation, so consume */ - consumed = true; if(event->type == InputTypeShort) { + consumed = true; if(view->interact_callback) { view->interact_callback(view->interact_callback_context); } diff --git a/applications/services/desktop/desktop.c b/applications/services/desktop/desktop.c index 333c444db98..0627652ab53 100644 --- a/applications/services/desktop/desktop.c +++ b/applications/services/desktop/desktop.c @@ -223,7 +223,6 @@ void desktop_lock(Desktop* desktop) { scene_manager_set_scene_state( desktop->scene_manager, DesktopSceneLocked, SCENE_LOCKED_FIRST_ENTER); scene_manager_next_scene(desktop->scene_manager, DesktopSceneLocked); - notification_message(desktop->notification, &sequence_display_backlight_off_delay_1000); DesktopStatus status = {.locked = true}; furi_pubsub_publish(desktop->status_pubsub, &status); diff --git a/applications/services/desktop/desktop_settings.h b/applications/services/desktop/desktop_settings.h index a189f9f05e9..5cf9cc4c648 100644 --- a/applications/services/desktop/desktop_settings.h +++ b/applications/services/desktop/desktop_settings.h @@ -8,7 +8,7 @@ #include #include -#define DESKTOP_SETTINGS_VER (9) +#define DESKTOP_SETTINGS_VER (10) #define DESKTOP_SETTINGS_PATH INT_PATH(DESKTOP_SETTINGS_FILE_NAME) #define DESKTOP_SETTINGS_MAGIC (0x17) @@ -36,6 +36,22 @@ #define MIN_PIN_SIZE 4 #define MAX_APP_LENGTH 128 +typedef enum { + FavoriteAppLeftShort = 0, + FavoriteAppLeftLong, + FavoriteAppRightShort, + FavoriteAppRightLong, + FavoriteAppNumber, +} FavoriteAppShortcut; + +typedef enum { + DummyAppLeft = 0, + DummyAppRight, + DummyAppDown, + DummyAppOk, + DummyAppNumber, +} DummyAppShortcut; + typedef struct { InputKey data[MAX_PIN_SIZE]; uint8_t length; @@ -46,10 +62,10 @@ typedef struct { } FavoriteApp; typedef struct { - FavoriteApp favorite_primary; - FavoriteApp favorite_secondary; PinCode pin_code; uint32_t auto_lock_delay_ms; uint8_t dummy_mode; uint8_t display_clock; + FavoriteApp favorite_apps[FavoriteAppNumber]; + FavoriteApp dummy_apps[DummyAppNumber]; } DesktopSettings; diff --git a/applications/services/desktop/scenes/desktop_scene_locked.c b/applications/services/desktop/scenes/desktop_scene_locked.c index f64ef83716e..bbed5600151 100644 --- a/applications/services/desktop/scenes/desktop_scene_locked.c +++ b/applications/services/desktop/scenes/desktop_scene_locked.c @@ -87,6 +87,10 @@ bool desktop_scene_locked_on_event(void* context, SceneManagerEvent event) { desktop_unlock(desktop); consumed = true; break; + case DesktopLockedEventDoorsClosed: + notification_message(desktop->notification, &sequence_display_backlight_off); + consumed = true; + break; case DesktopLockedEventUpdate: if(desktop_view_locked_is_locked_hint_visible(desktop->locked_view)) { notification_message(desktop->notification, &sequence_display_backlight_off); diff --git a/applications/services/desktop/scenes/desktop_scene_main.c b/applications/services/desktop/scenes/desktop_scene_main.c index ae39ec22376..a659ff4e358 100644 --- a/applications/services/desktop/scenes/desktop_scene_main.c +++ b/applications/services/desktop/scenes/desktop_scene_main.c @@ -12,10 +12,6 @@ #define TAG "DesktopSrv" -#define MUSIC_PLAYER_APP EXT_PATH("/apps/Media/music_player.fap") -#define SNAKE_GAME_APP EXT_PATH("/apps/Games/snake_game.fap") -#define CLOCK_APP EXT_PATH("/apps/Tools/clock.fap") - static void desktop_scene_main_new_idle_animation_callback(void* context) { furi_assert(context); Desktop* desktop = context; @@ -65,8 +61,15 @@ static void } #endif -static void desktop_scene_main_open_app_or_profile(Desktop* desktop, const char* path) { - if(loader_start_with_gui_error(desktop->loader, path, NULL) != LoaderStatusOk) { +static void desktop_scene_main_open_app_or_profile(Desktop* desktop, FavoriteApp* application) { + bool load_ok = false; + if(strlen(application->name_or_path) > 0) { + if(loader_start(desktop->loader, application->name_or_path, NULL, NULL) == + LoaderStatusOk) { + load_ok = true; + } + } + if(!load_ok) { loader_start(desktop->loader, "Passport", NULL, NULL); } } @@ -115,6 +118,11 @@ bool desktop_scene_main_on_event(void* context, SceneManagerEvent event) { consumed = true; } break; + case DesktopMainEventLock: + desktop_lock(desktop); + consumed = true; + break; + case DesktopMainEventOpenLockMenu: scene_manager_next_scene(desktop->scene_manager, DesktopSceneLockMenu); consumed = true; @@ -138,16 +146,31 @@ bool desktop_scene_main_on_event(void* context, SceneManagerEvent event) { break; } - case DesktopMainEventOpenFavoritePrimary: + case DesktopMainEventOpenFavoriteLeftShort: DESKTOP_SETTINGS_LOAD(&desktop->settings); - desktop_scene_main_start_favorite(desktop, &desktop->settings.favorite_primary); + desktop_scene_main_start_favorite( + desktop, &desktop->settings.favorite_apps[FavoriteAppLeftShort]); consumed = true; break; - case DesktopMainEventOpenFavoriteSecondary: + case DesktopMainEventOpenFavoriteLeftLong: DESKTOP_SETTINGS_LOAD(&desktop->settings); - desktop_scene_main_start_favorite(desktop, &desktop->settings.favorite_secondary); + desktop_scene_main_start_favorite( + desktop, &desktop->settings.favorite_apps[FavoriteAppLeftLong]); consumed = true; break; + case DesktopMainEventOpenFavoriteRightShort: + DESKTOP_SETTINGS_LOAD(&desktop->settings); + desktop_scene_main_start_favorite( + desktop, &desktop->settings.favorite_apps[FavoriteAppRightShort]); + consumed = true; + break; + case DesktopMainEventOpenFavoriteRightLong: + DESKTOP_SETTINGS_LOAD(&desktop->settings); + desktop_scene_main_start_favorite( + desktop, &desktop->settings.favorite_apps[FavoriteAppRightLong]); + consumed = true; + break; + case DesktopAnimationEventCheckAnimation: animation_manager_check_blocking_process(desktop->animation_manager); consumed = true; @@ -158,26 +181,31 @@ bool desktop_scene_main_on_event(void* context, SceneManagerEvent event) { break; case DesktopAnimationEventInteractAnimation: if(!animation_manager_interact_process(desktop->animation_manager)) { - loader_start(desktop->loader, "Passport", NULL, NULL); + DESKTOP_SETTINGS_LOAD(&desktop->settings); + if(!desktop->settings.dummy_mode) { + desktop_scene_main_open_app_or_profile( + desktop, &desktop->settings.favorite_apps[FavoriteAppRightShort]); + } else { + desktop_scene_main_open_app_or_profile( + desktop, &desktop->settings.dummy_apps[DummyAppRight]); + } } consumed = true; break; - case DesktopMainEventOpenPassport: { - loader_start(desktop->loader, "Passport", NULL, NULL); - break; - } - case DesktopMainEventOpenGame: { - desktop_scene_main_open_app_or_profile(desktop, SNAKE_GAME_APP); + + case DesktopDummyEventOpenLeft: + desktop_scene_main_open_app_or_profile( + desktop, &desktop->settings.dummy_apps[DummyAppLeft]); break; - } - case DesktopMainEventOpenClock: { - desktop_scene_main_open_app_or_profile(desktop, CLOCK_APP); + case DesktopDummyEventOpenDown: + desktop_scene_main_open_app_or_profile( + desktop, &desktop->settings.dummy_apps[DummyAppDown]); break; - } - case DesktopMainEventOpenMusicPlayer: { - desktop_scene_main_open_app_or_profile(desktop, MUSIC_PLAYER_APP); + case DesktopDummyEventOpenOk: + desktop_scene_main_open_app_or_profile( + desktop, &desktop->settings.dummy_apps[DummyAppOk]); break; - } + case DesktopLockedEventUpdate: desktop_view_locked_update(desktop->locked_view); consumed = true; diff --git a/applications/services/desktop/views/desktop_events.h b/applications/services/desktop/views/desktop_events.h index e366885fe65..5dc51fd85cf 100644 --- a/applications/services/desktop/views/desktop_events.h +++ b/applications/services/desktop/views/desktop_events.h @@ -1,22 +1,25 @@ #pragma once typedef enum { + DesktopMainEventLock, DesktopMainEventOpenLockMenu, DesktopMainEventOpenArchive, - DesktopMainEventOpenFavoritePrimary, - DesktopMainEventOpenFavoriteSecondary, + DesktopMainEventOpenFavoriteLeftShort, + DesktopMainEventOpenFavoriteLeftLong, + DesktopMainEventOpenFavoriteRightShort, + DesktopMainEventOpenFavoriteRightLong, DesktopMainEventOpenMenu, DesktopMainEventOpenDebug, - DesktopMainEventOpenPassport, DesktopMainEventOpenPowerOff, - DesktopMainEventOpenGame, - DesktopMainEventOpenClock, - DesktopMainEventOpenMusicPlayer, + DesktopDummyEventOpenLeft, + DesktopDummyEventOpenDown, + DesktopDummyEventOpenOk, DesktopLockedEventUnlocked, DesktopLockedEventUpdate, DesktopLockedEventShowPinInput, + DesktopLockedEventDoorsClosed, DesktopPinInputEventResetWrongPinLabel, DesktopPinInputEventUnlocked, diff --git a/applications/services/desktop/views/desktop_view_locked.c b/applications/services/desktop/views/desktop_view_locked.c index 0bf7570361e..3cee25425ef 100644 --- a/applications/services/desktop/views/desktop_view_locked.c +++ b/applications/services/desktop/views/desktop_view_locked.c @@ -99,6 +99,7 @@ void desktop_view_locked_update(DesktopViewLocked* locked_view) { if(view_state == DesktopViewLockedStateDoorsClosing && !desktop_view_locked_doors_move(model)) { + locked_view->callback(DesktopLockedEventDoorsClosed, locked_view->context); model->view_state = DesktopViewLockedStateLocked; } else if(view_state == DesktopViewLockedStateLockedHintShown) { model->view_state = DesktopViewLockedStateLocked; diff --git a/applications/services/desktop/views/desktop_view_main.c b/applications/services/desktop/views/desktop_view_main.c index 7d956489a6b..d323567e792 100644 --- a/applications/services/desktop/views/desktop_view_main.c +++ b/applications/services/desktop/views/desktop_view_main.c @@ -59,28 +59,32 @@ bool desktop_main_input_callback(InputEvent* event, void* context) { } else if(event->key == InputKeyDown) { main_view->callback(DesktopMainEventOpenArchive, main_view->context); } else if(event->key == InputKeyLeft) { - main_view->callback(DesktopMainEventOpenFavoritePrimary, main_view->context); + main_view->callback(DesktopMainEventOpenFavoriteLeftShort, main_view->context); } - // Right key is handled by animation manager + // Right key short is handled by animation manager } else if(event->type == InputTypeLong) { - if(event->key == InputKeyDown) { + if(event->key == InputKeyUp) { + main_view->callback(DesktopMainEventLock, main_view->context); + } else if(event->key == InputKeyDown) { main_view->callback(DesktopMainEventOpenDebug, main_view->context); } else if(event->key == InputKeyLeft) { - main_view->callback(DesktopMainEventOpenFavoriteSecondary, main_view->context); + main_view->callback(DesktopMainEventOpenFavoriteLeftLong, main_view->context); + } else if(event->key == InputKeyRight) { + main_view->callback(DesktopMainEventOpenFavoriteRightLong, main_view->context); } } } else { if(event->type == InputTypeShort) { if(event->key == InputKeyOk) { - main_view->callback(DesktopMainEventOpenGame, main_view->context); + main_view->callback(DesktopDummyEventOpenOk, main_view->context); } else if(event->key == InputKeyUp) { main_view->callback(DesktopMainEventOpenLockMenu, main_view->context); } else if(event->key == InputKeyDown) { - main_view->callback(DesktopMainEventOpenMusicPlayer, main_view->context); + main_view->callback(DesktopDummyEventOpenDown, main_view->context); } else if(event->key == InputKeyLeft) { - main_view->callback(DesktopMainEventOpenClock, main_view->context); + main_view->callback(DesktopDummyEventOpenLeft, main_view->context); } - // Right key is handled by animation manager + // Right key short is handled by animation manager } } diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c index e0b4c118e18..31826cae074 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c @@ -1,17 +1,19 @@ #include "../desktop_settings_app.h" #include "applications.h" #include "desktop_settings_scene.h" +#include "desktop_settings_scene_i.h" #include #include #include #define APPS_COUNT (FLIPPER_APPS_COUNT + FLIPPER_EXTERNAL_APPS_COUNT) -#define EXTERNAL_BROWSER_NAME ("Apps") -#define EXTERNAL_BROWSER_INDEX (APPS_COUNT + 1) +#define DEFAULT_INDEX (0) +#define EXTERNAL_BROWSER_NAME ("Apps Menu (Default)") +#define PASSPORT_NAME ("Passport (Default)") +#define EXTERNAL_APPLICATION_INDEX (1) #define EXTERNAL_APPLICATION_NAME ("[Select App]") -#define EXTERNAL_APPLICATION_INDEX (APPS_COUNT + 2) #define PRESELECTED_SPECIAL 0xffffffff @@ -55,28 +57,32 @@ void desktop_settings_scene_favorite_on_enter(void* context) { Submenu* submenu = app->submenu; submenu_reset(submenu); - uint32_t primary_favorite = + uint32_t favorite_id = scene_manager_get_scene_state(app->scene_manager, DesktopSettingsAppSceneFavorite); uint32_t pre_select_item = PRESELECTED_SPECIAL; - FavoriteApp* curr_favorite_app = primary_favorite ? &app->settings.favorite_primary : - &app->settings.favorite_secondary; - - for(size_t i = 0; i < APPS_COUNT; i++) { - const char* name = favorite_fap_get_app_name(i); - - submenu_add_item(submenu, name, i, desktop_settings_scene_favorite_submenu_callback, app); - - // Select favorite item in submenu - if(!strcmp(name, curr_favorite_app->name_or_path)) { - pre_select_item = i; + FavoriteApp* curr_favorite_app = NULL; + bool is_dummy_app = false; + bool default_passport = false; + + if((favorite_id & SCENE_STATE_SET_DUMMY_APP) == 0) { + furi_assert(favorite_id < FavoriteAppNumber); + curr_favorite_app = &app->settings.favorite_apps[favorite_id]; + if(favorite_id == FavoriteAppRightShort) { + default_passport = true; } + } else { + favorite_id &= ~(SCENE_STATE_SET_DUMMY_APP); + furi_assert(favorite_id < DummyAppNumber); + curr_favorite_app = &app->settings.dummy_apps[favorite_id]; + is_dummy_app = true; + default_passport = true; } // Special case: Application browser submenu_add_item( submenu, - EXTERNAL_BROWSER_NAME, - EXTERNAL_BROWSER_INDEX, + default_passport ? (PASSPORT_NAME) : (EXTERNAL_BROWSER_NAME), + DEFAULT_INDEX, desktop_settings_scene_favorite_submenu_callback, app); @@ -88,16 +94,29 @@ void desktop_settings_scene_favorite_on_enter(void* context) { desktop_settings_scene_favorite_submenu_callback, app); + if(!is_dummy_app) { + for(size_t i = 0; i < APPS_COUNT; i++) { + const char* name = favorite_fap_get_app_name(i); + + submenu_add_item( + submenu, name, i + 2, desktop_settings_scene_favorite_submenu_callback, app); + + // Select favorite item in submenu + if(!strcmp(name, curr_favorite_app->name_or_path)) { + pre_select_item = i + 2; + } + } + } + if(pre_select_item == PRESELECTED_SPECIAL) { if(curr_favorite_app->name_or_path[0] == '\0') { - pre_select_item = EXTERNAL_BROWSER_INDEX; + pre_select_item = DEFAULT_INDEX; } else { pre_select_item = EXTERNAL_APPLICATION_INDEX; } } - submenu_set_header( - submenu, primary_favorite ? "Primary favorite app:" : "Secondary favorite app:"); + submenu_set_header(submenu, is_dummy_app ? ("Dummy Mode app:") : ("Favorite app:")); submenu_set_selected_item(submenu, pre_select_item); // If set during loop, visual glitch. view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewMenu); @@ -108,13 +127,20 @@ bool desktop_settings_scene_favorite_on_event(void* context, SceneManagerEvent e bool consumed = false; FuriString* temp_path = furi_string_alloc_set_str(EXT_PATH("apps")); - uint32_t primary_favorite = + uint32_t favorite_id = scene_manager_get_scene_state(app->scene_manager, DesktopSettingsAppSceneFavorite); - FavoriteApp* curr_favorite_app = primary_favorite ? &app->settings.favorite_primary : - &app->settings.favorite_secondary; + FavoriteApp* curr_favorite_app = NULL; + if((favorite_id & SCENE_STATE_SET_DUMMY_APP) == 0) { + furi_assert(favorite_id < FavoriteAppNumber); + curr_favorite_app = &app->settings.favorite_apps[favorite_id]; + } else { + favorite_id &= ~(SCENE_STATE_SET_DUMMY_APP); + furi_assert(favorite_id < DummyAppNumber); + curr_favorite_app = &app->settings.dummy_apps[favorite_id]; + } if(event.type == SceneManagerEventTypeCustom) { - if(event.event == EXTERNAL_BROWSER_INDEX) { + if(event.event == DEFAULT_INDEX) { curr_favorite_app->name_or_path[0] = '\0'; consumed = true; } else if(event.event == EXTERNAL_APPLICATION_INDEX) { @@ -142,7 +168,8 @@ bool desktop_settings_scene_favorite_on_event(void* context, SceneManagerEvent e consumed = true; } } else { - const char* name = favorite_fap_get_app_name(event.event); + size_t app_index = event.event - 2; + const char* name = favorite_fap_get_app_name(app_index); if(name) strncpy(curr_favorite_app->name_or_path, name, MAX_APP_LENGTH); consumed = true; } diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_i.h b/applications/settings/desktop_settings/scenes/desktop_settings_scene_i.h index 230fec873da..657680bc351 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_i.h +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_i.h @@ -5,3 +5,6 @@ #define SCENE_STATE_PIN_ERROR_MISMATCH (0) #define SCENE_STATE_PIN_ERROR_WRONG (1) + +#define SCENE_STATE_SET_FAVORITE_APP (0) +#define SCENE_STATE_SET_DUMMY_APP (1 << 8) diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_start.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_start.c index 02acd51ca1e..3e77bd8a164 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_start.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_start.c @@ -3,15 +3,24 @@ #include "../desktop_settings_app.h" #include "desktop_settings_scene.h" - -#define SCENE_EVENT_SELECT_FAVORITE_PRIMARY 0 -#define SCENE_EVENT_SELECT_FAVORITE_SECONDARY 1 -#define SCENE_EVENT_SELECT_PIN_SETUP 2 -#define SCENE_EVENT_SELECT_AUTO_LOCK_DELAY 3 -#define SCENE_EVENT_SELECT_CLOCK_DISPLAY 4 +#include "desktop_settings_scene_i.h" + +typedef enum { + DesktopSettingsPinSetup = 0, + DesktopSettingsAutoLockDelay, + DesktopSettingsClockDisplay, + DesktopSettingsFavoriteLeftShort, + DesktopSettingsFavoriteLeftLong, + DesktopSettingsFavoriteRightShort, + DesktopSettingsFavoriteRightLong, + DesktopSettingsDummyLeft, + DesktopSettingsDummyRight, + DesktopSettingsDummyDown, + DesktopSettingsDummyOk, +} DesktopSettingsEntry; #define AUTO_LOCK_DELAY_COUNT 6 -const char* const auto_lock_delay_text[AUTO_LOCK_DELAY_COUNT] = { +static const char* const auto_lock_delay_text[AUTO_LOCK_DELAY_COUNT] = { "OFF", "30s", "60s", @@ -19,8 +28,7 @@ const char* const auto_lock_delay_text[AUTO_LOCK_DELAY_COUNT] = { "5min", "10min", }; - -const uint32_t auto_lock_delay_value[AUTO_LOCK_DELAY_COUNT] = +static const uint32_t auto_lock_delay_value[AUTO_LOCK_DELAY_COUNT] = {0, 30000, 60000, 120000, 300000, 600000}; #define CLOCK_ENABLE_COUNT 2 @@ -59,10 +67,6 @@ void desktop_settings_scene_start_on_enter(void* context) { VariableItem* item; uint8_t value_index; - variable_item_list_add(variable_item_list, "Primary Favorite App", 1, NULL, NULL); - - variable_item_list_add(variable_item_list, "Secondary Favorite App", 1, NULL, NULL); - variable_item_list_add(variable_item_list, "PIN Setup", 1, NULL, NULL); item = variable_item_list_add( @@ -72,8 +76,6 @@ void desktop_settings_scene_start_on_enter(void* context) { desktop_settings_scene_start_auto_lock_delay_changed, app); - variable_item_list_set_enter_callback( - variable_item_list, desktop_settings_scene_start_var_list_enter_callback, app); value_index = value_index_uint32( app->settings.auto_lock_delay_ms, auto_lock_delay_value, AUTO_LOCK_DELAY_COUNT); variable_item_set_current_value_index(item, value_index); @@ -91,6 +93,19 @@ void desktop_settings_scene_start_on_enter(void* context) { variable_item_set_current_value_index(item, value_index); variable_item_set_current_value_text(item, clock_enable_text[value_index]); + variable_item_list_add(variable_item_list, "Favorite App - Left Short", 1, NULL, NULL); + variable_item_list_add(variable_item_list, "Favorite App - Left Long", 1, NULL, NULL); + variable_item_list_add(variable_item_list, "Favorite App - Right Short", 1, NULL, NULL); + variable_item_list_add(variable_item_list, "Favorite App - Right Long", 1, NULL, NULL); + + variable_item_list_add(variable_item_list, "Dummy Mode App - Left", 1, NULL, NULL); + variable_item_list_add(variable_item_list, "Dummy Mode App - Right", 1, NULL, NULL); + variable_item_list_add(variable_item_list, "Dummy Mode App - Down", 1, NULL, NULL); + variable_item_list_add(variable_item_list, "Dummy Mode App - Ok", 1, NULL, NULL); + + variable_item_list_set_enter_callback( + variable_item_list, desktop_settings_scene_start_var_list_enter_callback, app); + view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewVarItemList); } @@ -100,25 +115,72 @@ bool desktop_settings_scene_start_on_event(void* context, SceneManagerEvent even if(event.type == SceneManagerEventTypeCustom) { switch(event.event) { - case SCENE_EVENT_SELECT_FAVORITE_PRIMARY: - scene_manager_set_scene_state(app->scene_manager, DesktopSettingsAppSceneFavorite, 1); + case DesktopSettingsPinSetup: + scene_manager_next_scene(app->scene_manager, DesktopSettingsAppScenePinMenu); + break; + + case DesktopSettingsFavoriteLeftShort: + scene_manager_set_scene_state( + app->scene_manager, + DesktopSettingsAppSceneFavorite, + SCENE_STATE_SET_FAVORITE_APP | FavoriteAppLeftShort); scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite); - consumed = true; break; - case SCENE_EVENT_SELECT_FAVORITE_SECONDARY: - scene_manager_set_scene_state(app->scene_manager, DesktopSettingsAppSceneFavorite, 0); + case DesktopSettingsFavoriteLeftLong: + scene_manager_set_scene_state( + app->scene_manager, + DesktopSettingsAppSceneFavorite, + SCENE_STATE_SET_FAVORITE_APP | FavoriteAppLeftLong); scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite); - consumed = true; break; - case SCENE_EVENT_SELECT_PIN_SETUP: - scene_manager_next_scene(app->scene_manager, DesktopSettingsAppScenePinMenu); - consumed = true; + case DesktopSettingsFavoriteRightShort: + scene_manager_set_scene_state( + app->scene_manager, + DesktopSettingsAppSceneFavorite, + SCENE_STATE_SET_FAVORITE_APP | FavoriteAppRightShort); + scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite); + break; + case DesktopSettingsFavoriteRightLong: + scene_manager_set_scene_state( + app->scene_manager, + DesktopSettingsAppSceneFavorite, + SCENE_STATE_SET_FAVORITE_APP | FavoriteAppRightLong); + scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite); + break; + + case DesktopSettingsDummyLeft: + scene_manager_set_scene_state( + app->scene_manager, + DesktopSettingsAppSceneFavorite, + SCENE_STATE_SET_DUMMY_APP | DummyAppLeft); + scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite); break; - case SCENE_EVENT_SELECT_AUTO_LOCK_DELAY: - case SCENE_EVENT_SELECT_CLOCK_DISPLAY: - consumed = true; + case DesktopSettingsDummyRight: + scene_manager_set_scene_state( + app->scene_manager, + DesktopSettingsAppSceneFavorite, + SCENE_STATE_SET_DUMMY_APP | DummyAppRight); + scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite); + break; + case DesktopSettingsDummyDown: + scene_manager_set_scene_state( + app->scene_manager, + DesktopSettingsAppSceneFavorite, + SCENE_STATE_SET_DUMMY_APP | DummyAppDown); + scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite); + break; + case DesktopSettingsDummyOk: + scene_manager_set_scene_state( + app->scene_manager, + DesktopSettingsAppSceneFavorite, + SCENE_STATE_SET_DUMMY_APP | DummyAppOk); + scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite); + break; + + default: break; } + consumed = true; } return consumed; } From 4ade0fc76d541ddeffb17f732383e3b8438c0949 Mon Sep 17 00:00:00 2001 From: Konstantin Volkov <72250702+doomwastaken@users.noreply.github.com> Date: Wed, 23 Aug 2023 15:51:02 +0300 Subject: [PATCH 719/824] [FL-3421] Unit bench: multiple attempts to find flipper (#2960) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Unit bench: added retry count for finding flipper for reboot/reboot2dfu and power off scripts. Changed returns to sys.exit() since scripts are being used standalone * fixed typo in await flipper and changed debug level to info for searching flipper in power.py * reversed return operator instead of sys.exit, changed app.py exit behavior * test run to see if exit(1) fails the run * reversed test changes, fixed flipper name and increased await flipper timeout * await after flash increase * increased serial timeout * increased serial timeout, apparently it is for entire run Co-authored-by: doomwastaken Co-authored-by: あく --- .github/workflows/unit_tests.yml | 3 ++- scripts/power.py | 31 +++++++++++++++++++++++-------- scripts/testing/await_flipper.py | 6 +++--- scripts/testing/units.py | 4 +--- 4 files changed, 29 insertions(+), 15 deletions(-) diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 2e6336c0741..794dce37f05 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -45,9 +45,10 @@ jobs: - name: 'Copy assets and unit data, reboot and wait for flipper' id: copy if: steps.format_ext.outcome == 'success' - timeout-minutes: 5 + timeout-minutes: 7 run: | source scripts/toolchain/fbtenv.sh + python3 scripts/testing/await_flipper.py ${{steps.device.outputs.flipper}} python3 scripts/storage.py -p ${{steps.device.outputs.flipper}} -f send assets/resources /ext python3 scripts/storage.py -p ${{steps.device.outputs.flipper}} -f send assets/unit_tests /ext/unit_tests python3 scripts/power.py -p ${{steps.device.outputs.flipper}} reboot diff --git a/scripts/power.py b/scripts/power.py index 45a130c5917..50bb2d4f700 100755 --- a/scripts/power.py +++ b/scripts/power.py @@ -1,5 +1,8 @@ #!/usr/bin/env python3 +import time +from typing import Optional + from flipper.app import App from flipper.storage import FlipperStorage from flipper.utils.cdc import resolve_port @@ -27,8 +30,20 @@ def init(self): ) self.parser_reboot2dfu.set_defaults(func=self.reboot2dfu) - def _get_flipper(self): - if not (port := resolve_port(self.logger, self.args.port)): + def _get_flipper(self, retry_count: Optional[int] = 1): + port = None + self.logger.info(f"Attempting to find flipper with {retry_count} attempts.") + + for i in range(retry_count): + time.sleep(1) + self.logger.info(f"Attempting to find flipper #{i}.") + + if port := resolve_port(self.logger, self.args.port): + self.logger.info(f"Found flipper at {port}") + break + + if not port: + self.logger.info(f"Failed to find flipper") return None flipper = FlipperStorage(port) @@ -36,28 +51,28 @@ def _get_flipper(self): return flipper def power_off(self): - if not (flipper := self._get_flipper()): + if not (flipper := self._get_flipper(retry_count=10)): return 1 - self.logger.debug("Powering off") + self.logger.info("Powering off") flipper.send("power off" + "\r") flipper.stop() return 0 def reboot(self): - if not (flipper := self._get_flipper()): + if not (flipper := self._get_flipper(retry_count=10)): return 1 - self.logger.debug("Rebooting") + self.logger.info("Rebooting") flipper.send("power reboot" + "\r") flipper.stop() return 0 def reboot2dfu(self): - if not (flipper := self._get_flipper()): + if not (flipper := self._get_flipper(retry_count=10)): return 1 - self.logger.debug("Rebooting to DFU") + self.logger.info("Rebooting to DFU") flipper.send("power reboot2dfu" + "\r") flipper.stop() diff --git a/scripts/testing/await_flipper.py b/scripts/testing/await_flipper.py index ea07d6be7ff..f8dffeb66bf 100755 --- a/scripts/testing/await_flipper.py +++ b/scripts/testing/await_flipper.py @@ -22,14 +22,14 @@ def flp_serial_by_name(flp_name): if os.path.exists(flp_serial): return flp_serial else: - logging.info(f"Couldn't find {logging.info} on this attempt.") + logging.info(f"Couldn't find {flp_name} on this attempt.") if os.path.exists(flp_name): return flp_name else: return "" -UPDATE_TIMEOUT = 60 * 4 # 4 minutes +UPDATE_TIMEOUT = 30 * 4 # 4 minutes def main(): @@ -50,7 +50,7 @@ def main(): if flipper == "": logging.error("Flipper not found!") - sys.exit(1) + exit(1) logging.info(f"Found Flipper at {flipper}") diff --git a/scripts/testing/units.py b/scripts/testing/units.py index fd8e29a7333..db302e9daeb 100755 --- a/scripts/testing/units.py +++ b/scripts/testing/units.py @@ -20,14 +20,12 @@ def main(): logging.error("Flipper not found!") sys.exit(1) - with serial.Serial(flp_serial, timeout=10) as flipper: + with serial.Serial(flp_serial, timeout=150) as flipper: logging.info(f"Found Flipper at {flp_serial}") flipper.baudrate = 230400 flipper.flushOutput() flipper.flushInput() - flipper.timeout = 300 - flipper.read_until(b">: ").decode("utf-8") flipper.write(b"unit_tests\r") data = flipper.read_until(b">: ").decode("utf-8") From beeeb9bbdc025d6f35bc6dd1b190d2a5008f7925 Mon Sep 17 00:00:00 2001 From: Synthethics <89489089+Synthethics@users.noreply.github.com> Date: Wed, 23 Aug 2023 15:50:19 +0200 Subject: [PATCH 720/824] Update tv.ir (#2942) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update tv.ir: Added VOX Electronics LED 43ADS316B TV Remote. * Assets: reorder and tag new tv remote * Assets: cleanup infrared universal tv Co-authored-by: あく --- assets/resources/infrared/assets/tv.ir | 627 +++++++++++++------------ 1 file changed, 334 insertions(+), 293 deletions(-) diff --git a/assets/resources/infrared/assets/tv.ir b/assets/resources/infrared/assets/tv.ir index a223c97c97d..a640b9a57c7 100644 --- a/assets/resources/infrared/assets/tv.ir +++ b/assets/resources/infrared/assets/tv.ir @@ -1,1675 +1,1676 @@ Filetype: IR library file Version: 1 -# +# name: Power type: parsed protocol: SIRC address: 01 00 00 00 command: 15 00 00 00 -# +# name: Power type: parsed protocol: SIRC address: 10 00 00 00 command: 15 00 00 00 -# +# name: Power type: parsed protocol: NEC address: 08 00 00 00 command: 05 00 00 00 -# +# name: Vol_up type: parsed protocol: NEC address: 08 00 00 00 command: 00 00 00 00 -# +# name: Vol_dn type: parsed protocol: NEC address: 08 00 00 00 command: 01 00 00 00 -# +# name: Ch_next type: parsed protocol: NEC address: 08 00 00 00 command: 02 00 00 00 -# +# name: Ch_prev type: parsed protocol: NEC address: 08 00 00 00 command: 03 00 00 00 -# +# name: Mute type: parsed protocol: NEC address: 08 00 00 00 command: 0B 00 00 00 -# +# name: Power type: parsed protocol: NECext address: 00 DF 00 00 command: 1C 00 00 00 -# +# name: Vol_up type: parsed protocol: NECext address: 00 DF 00 00 command: 4B 00 00 00 -# +# name: Vol_dn type: parsed protocol: NECext address: 00 DF 00 00 command: 4F 00 00 00 -# +# name: Ch_next type: parsed protocol: NECext address: 00 DF 00 00 command: 09 00 00 00 -# +# name: Ch_prev type: parsed protocol: NECext address: 00 DF 00 00 command: 05 00 00 00 -# +# name: Mute type: parsed protocol: NECext address: 00 DF 00 00 command: 08 00 00 00 -# +# name: Power type: parsed protocol: Samsung32 address: 0E 00 00 00 command: 0C 00 00 00 -# +# name: Mute type: parsed protocol: Samsung32 address: 0E 00 00 00 command: 0D 00 00 00 -# +# name: Vol_up type: parsed protocol: Samsung32 address: 0E 00 00 00 command: 14 00 00 00 -# +# name: Vol_dn type: parsed protocol: Samsung32 address: 0E 00 00 00 command: 15 00 00 00 -# +# name: Ch_next type: parsed protocol: Samsung32 address: 0E 00 00 00 command: 12 00 00 00 -# +# name: Ch_prev type: parsed protocol: Samsung32 address: 0E 00 00 00 command: 13 00 00 00 -# +# name: Power type: parsed protocol: RC6 address: 00 00 00 00 command: 0C 00 00 00 -# +# name: Power type: parsed protocol: Samsung32 address: 07 00 00 00 command: 02 00 00 00 -# +# name: Power type: parsed protocol: Samsung32 address: 07 00 00 00 command: E6 00 00 00 -# +# name: Vol_up type: parsed protocol: Samsung32 address: 07 00 00 00 command: 07 00 00 00 -# +# name: Vol_dn type: parsed protocol: Samsung32 address: 07 00 00 00 command: 0B 00 00 00 -# +# name: Ch_next type: parsed protocol: Samsung32 address: 07 00 00 00 command: 12 00 00 00 -# +# name: Ch_prev type: parsed protocol: Samsung32 address: 07 00 00 00 command: 10 00 00 00 -# +# name: Mute type: parsed protocol: Samsung32 address: 07 00 00 00 command: 0F 00 00 00 -# +# name: Power type: parsed protocol: NEC address: 50 00 00 00 command: 17 00 00 00 -# +# name: Power type: parsed protocol: NEC address: 40 00 00 00 command: 12 00 00 00 -# +# name: Power type: parsed protocol: NECext address: 31 49 00 00 command: 63 00 00 00 -# +# name: Power type: parsed protocol: NEC address: AA 00 00 00 command: 1C 00 00 00 -# +# name: Power type: parsed protocol: NEC address: 38 00 00 00 command: 1C 00 00 00 -# +# name: Power type: parsed protocol: NECext address: 83 7A 00 00 command: 08 00 00 00 -# +# name: Power type: parsed protocol: NEC address: 53 00 00 00 command: 17 00 00 00 -# +# name: Power type: parsed protocol: NECext address: 18 18 00 00 command: C0 00 00 00 -# +# name: Power type: parsed protocol: NEC address: 38 00 00 00 command: 10 00 00 00 -# +# name: Power type: parsed protocol: NEC address: AA 00 00 00 command: C5 00 00 00 -# +# name: Power type: parsed protocol: NEC address: 04 00 00 00 command: 08 00 00 00 -# +# name: Power type: parsed protocol: NEC address: 18 00 00 00 command: 08 00 00 00 -# +# name: Power type: parsed protocol: NEC address: 71 00 00 00 command: 08 00 00 00 -# +# name: Power type: parsed protocol: NECext address: 80 6F 00 00 command: 0A 00 00 00 -# +# name: Power type: parsed protocol: NEC address: 48 00 00 00 command: 00 00 00 00 -# +# name: Power type: parsed protocol: NECext address: 80 7B 00 00 command: 13 00 00 00 -# +# name: Power type: parsed protocol: Samsung32 address: 0E 00 00 00 command: 14 00 00 00 -# +# name: Power type: parsed protocol: NECext address: 80 7E 00 00 command: 18 00 00 00 -# +# name: Power type: parsed protocol: NEC address: 50 00 00 00 command: 08 00 00 00 -# +# name: Power type: parsed protocol: NECext address: 80 75 00 00 command: 0A 00 00 00 -# +# name: Power type: parsed protocol: NECext address: 80 57 00 00 command: 0A 00 00 00 -# +# name: Power type: parsed protocol: Samsung32 address: 0B 00 00 00 command: 0A 00 00 00 -# +# name: Power type: parsed protocol: NEC address: AA 00 00 00 command: 1B 00 00 00 -# +# name: Power type: parsed protocol: NECext address: 85 46 00 00 command: 12 00 00 00 -# +# name: Power type: parsed protocol: Samsung32 address: 05 00 00 00 command: 02 00 00 00 -# +# name: Power type: parsed protocol: Samsung32 address: 08 00 00 00 command: 0F 00 00 00 -# +# name: Power type: parsed protocol: NEC address: 00 00 00 00 command: 01 00 00 00 -# +# name: Power type: parsed protocol: NEC address: 00 00 00 00 command: 01 00 00 00 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 634 2571 505 519 479 519 479 518 480 518 480 518 480 518 480 517 481 547 481 517 481 20040 590 2555 501 1007 999 997 510 548 480 486 512 486 512 486 542 485 513 516 482 116758 593 2552 504 1004 992 1004 514 514 514 483 515 513 485 483 545 482 516 482 516 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 525 1955 449 1999 476 4545 446 4544 478 2032 443 2006 469 2011 444 4577 445 4545 447 4574 448 2002 473 4547 444 34913 447 2032 443 2007 478 4542 449 4541 471 2039 446 2004 471 2008 447 4574 448 4543 448 4572 450 2030 445 4545 446 -# +# name: Power type: parsed protocol: SIRC address: 01 00 00 00 command: 15 00 00 00 -# +# name: Power type: parsed protocol: Kaseikyo address: 80 02 20 00 command: D0 03 00 00 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 389 1737 280 796 253 744 295 754 274 775 274 776 273 1827 271 1828 270 805 254 1820 278 796 253 797 252 745 294 1806 302 773 245 48942 305 1821 277 798 251 746 303 747 271 778 271 1829 279 796 253 796 253 1821 277 798 251 1823 275 1824 274 1825 273 802 247 1827 271 42824 381 1745 272 804 245 752 297 753 275 773 276 774 275 1825 273 1826 272 803 246 1828 270 805 254 795 244 753 296 1804 294 781 247 48939 379 1746 271 804 245 779 270 753 275 774 275 1825 273 802 247 802 247 1827 271 804 245 1829 279 1820 278 1821 277 798 251 1823 275 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 562 1721 561 594 557 597 564 617 513 615 536 618 543 1715 566 1716 566 1692 559 594 567 588 563 618 543 611 540 615 536 618 543 1715 556 623 538 617 534 621 530 624 516 638 513 642 509 1722 560 620 541 1717 565 1692 559 1724 568 1715 556 1701 560 1723 559 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 8436 4189 538 1563 566 1559 539 510 559 543 516 507 542 560 509 540 509 567 512 1586 512 1562 567 1559 539 536 533 1566 542 507 562 513 536 540 509 22102 647 1478 559 1568 540 508 541 535 534 515 534 568 511 538 511 539 540 1585 513 1560 559 1567 541 534 535 1564 534 515 534 568 511 538 511 22125 644 1482 565 1561 537 511 538 564 515 508 541 535 534 541 508 516 563 1588 510 1563 556 1570 538 510 559 1567 541 534 515 535 534 541 508 -# +# name: Power type: parsed protocol: RC5 address: 00 00 00 00 command: 0C 00 00 00 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 448 2031 444 2005 480 4540 452 4539 473 2037 438 2011 474 2006 449 4571 451 4539 453 4568 444 2036 449 4541 451 34906 527 1953 451 1998 477 4543 449 4542 480 2030 445 2004 471 2009 446 4575 447 4543 449 4572 450 1999 476 4545 446 -# +# name: Power type: parsed protocol: NEC42 address: 7B 00 00 00 command: 00 00 00 00 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 8041 3979 513 536 482 515 513 1559 509 515 514 1560 508 515 514 509 509 514 484 4000 533 1566 512 537 481 1566 512 537 481 1566 512 537 481 516 513 537 481 24150 8044 3977 505 518 510 539 479 1567 511 512 506 1568 510 539 479 543 485 538 480 3977 536 1564 514 534 484 1563 515 508 510 1563 515 508 510 540 489 534 484 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 383 2027 295 2112 271 2138 266 890 271 887 294 926 235 2114 300 887 274 915 236 2145 269 887 274 884 297 923 238 920 241 917 264 895 266 26573 384 2026 296 2111 273 2136 268 889 272 886 295 924 237 2113 301 886 275 914 237 2144 270 886 275 914 267 921 240 919 242 916 265 924 237 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 177 8474 175 5510 174 8476 173 8477 177 8504 171 5515 175 8476 178 8472 177 8473 176 5541 174 8476 173 45583 171 8481 178 5507 177 8473 176 8474 175 8506 173 5512 172 8478 176 8475 174 8476 178 5538 177 8474 175 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 8044 3976 506 517 511 1563 505 517 511 538 480 517 511 538 460 563 455 568 460 3993 530 545 483 1564 514 1559 509 1564 514 509 509 540 488 535 483 513 505 24150 8043 3978 514 509 509 1564 514 509 509 540 478 519 509 540 458 565 464 559 459 3994 529 546 482 1565 513 1560 508 1565 513 510 508 541 487 536 482 541 477 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 558 2942 450 10021 482 2989 484 10015 447 3024 449 10021 472 3100 485 2986 477 2994 500 2999 475 2996 477 2994 479 2992 482 3018 476 2995 479 6464 484 36270 477 3023 450 10020 473 2999 485 10014 448 3022 452 10019 474 3098 478 2994 480 2991 503 2996 477 2994 480 2992 482 2990 484 3015 479 2992 481 6462 485 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 587 2407 476 1062 965 547 482 547 451 577 452 546 452 22108 590 2404 479 1060 967 1028 510 519 509 548 480 487 511 120791 645 2411 472 1066 961 1065 483 515 514 514 504 493 515 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 172 7439 171 7441 169 7443 177 7434 176 7462 178 4887 176 4916 177 4914 169 7469 171 4920 174 4918 175 55174 176 7436 174 7437 173 7439 171 7440 175 7463 172 4894 174 4917 171 4921 172 7465 175 4916 178 4914 169 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 589 2556 500 524 474 554 454 544 454 543 455 543 455 543 455 543 445 583 446 552 446 20046 584 2561 505 1033 485 514 963 518 480 1032 516 512 476 522 506 491 507 522 476 116758 586 2560 506 1033 484 513 964 548 450 1031 507 522 476 521 507 490 508 521 477 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 586 2407 476 1063 964 578 450 548 450 547 481 517 481 577 451 546 452 546 472 556 452 545 453 575 453 514 484 544 484 543 455 14954 510 2483 481 1058 480 548 959 552 446 1067 481 516 512 546 482 515 992 1003 535 493 515 543 486 513 475 522 506 552 446 111671 589 2405 478 1061 477 551 967 514 484 1059 479 549 479 548 480 517 990 1036 512 516 482 546 483 515 503 525 483 544 454 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 8444 4180 537 1564 565 1560 538 1561 557 1568 540 1559 539 536 533 542 517 559 510 1563 535 1564 565 1561 537 512 567 1558 540 535 534 1566 542 1557 562 23122 564 1562 557 1569 539 1560 538 1587 542 1558 540 534 535 515 534 541 538 1587 511 1563 566 1560 538 536 533 1567 541 534 515 1585 533 1566 542 23166 561 1565 564 1561 537 1563 535 1590 539 1561 537 538 541 534 515 535 534 1564 534 1566 563 1563 535 540 539 1560 538 511 538 1588 541 1559 539 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 527 1923 481 1998 477 4543 449 4542 470 2040 445 2004 471 2009 446 4575 447 4543 449 4572 450 1999 476 2034 441 34899 524 1956 448 2001 474 4546 446 4545 477 2033 442 2007 478 2002 443 4578 444 4546 445 4575 447 2003 472 2037 448 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 533 1356 437 3474 427 3483 429 3455 436 1454 430 1459 405 28168 510 1379 434 3477 434 3476 425 3459 432 1457 427 1462 402 -# +# name: Power type: parsed protocol: RC5 address: 00 00 00 00 command: 0C 00 00 00 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 179 7433 177 4915 178 7434 175 7436 174 7464 176 7435 175 4916 177 4915 173 4918 170 4922 171 4920 173 55174 175 7437 173 4919 174 7437 173 7439 171 7467 173 7438 172 4920 173 4919 174 4917 176 4915 178 4914 169 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 169 6731 176 6748 169 6730 177 6748 169 6755 172 4427 177 4447 178 6721 175 6749 178 4446 168 4456 169 54704 176 6723 174 6750 177 6723 173 6750 177 6747 170 4429 175 4449 176 6723 174 6751 176 4448 177 4447 178 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 3506 3494 876 830 840 2576 847 2568 844 862 819 2570 842 864 816 863 818 2570 842 836 844 2572 840 866 815 865 815 2573 839 867 813 866 814 2573 850 857 813 2575 847 2568 844 834 847 2569 843 835 845 2571 872 2571 842 32654 3512 3488 872 834 847 2570 842 2573 839 867 814 2574 849 858 822 857 813 2575 848 859 821 2566 846 832 848 860 821 2567 845 833 848 860 820 2568 844 834 847 2569 843 2572 840 838 843 2574 849 829 841 2575 868 2575 837 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 560 2939 453 10018 475 2996 477 10022 450 3021 452 10018 475 3097 478 6464 483 6460 477 6466 502 6469 479 2993 480 2990 484 36274 564 2936 456 10015 478 2993 481 10020 534 2936 456 10014 479 3093 482 6461 476 6466 482 6461 476 6495 473 2999 485 2986 477 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 10726 41047 10727 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 1617 4604 1559 1537 1560 1537 1560 4661 1533 33422 1613 4607 1566 1530 1556 1540 1536 4685 1539 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 174 4972 177 4910 173 4944 170 6988 174 6984 177 6951 175 6983 174 14269 177 4969 175 4912 176 4941 178 6979 172 6986 175 6953 178 6980 171 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 174 7067 176 10544 1055 719 1053 739 1054 738 953 822 1052 2566 169 3435 171 3431 175 3446 170 3432 174 3446 170 3432 174 3428 178 3442 174 3429 177 3425 171 39320 2323 4900 178 10543 1056 719 1053 739 1054 738 953 821 1053 2566 169 3435 171 3431 175 3445 171 3432 174 3446 170 3432 174 3428 178 3442 174 3428 178 3425 171 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 3506 3492 868 839 841 2575 848 858 822 2566 846 832 848 859 821 858 812 2576 847 860 820 2567 845 833 847 860 821 2568 844 862 818 2570 842 864 817 2571 841 2574 849 2567 845 861 820 2568 845 834 847 2570 873 2570 842 34395 3503 3496 874 833 847 2568 845 834 847 2570 842 864 816 835 846 862 819 2569 843 835 846 2571 841 865 816 864 816 2571 841 837 844 2573 850 857 813 2575 848 2567 845 2570 842 864 816 2572 840 838 842 2574 869 2574 838 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 170 8479 170 5516 178 8472 177 8474 175 8506 173 5513 171 8479 175 8476 178 8472 177 5540 175 8475 174 45584 177 8473 176 5509 175 8476 173 8477 176 8504 170 5516 178 8472 177 8474 175 8476 173 5543 172 8479 170 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 178 4969 170 6958 173 4944 175 6983 173 6956 174 6984 177 6980 171 16308 180 4966 173 6955 176 4941 172 6985 176 6982 169 6960 176 6982 174 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 585 2409 474 550 478 550 448 1033 994 1063 485 512 506 79916 585 2410 473 551 477 550 448 1034 993 1033 515 543 475 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 1192 1012 6649 26844 1192 1013 6648 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 3134 6105 6263 82963 3134 6105 6263 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 588 1511 567 1533 565 589 531 597 513 589 541 587 533 1565 533 569 541 1533 565 562 568 1531 537 1537 561 593 537 591 539 1507 561 1539 569 22152 586 1513 565 1535 563 590 540 562 538 564 566 588 542 1557 531 597 513 1534 564 590 540 1533 535 1539 559 568 562 592 538 1509 569 1531 567 -# +# name: Power type: parsed protocol: RC5 address: 00 00 00 00 command: 20 00 00 00 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 689 1461 566 1534 564 589 531 597 513 1534 564 564 566 1533 535 620 510 1537 561 592 538 1535 543 1531 567 587 533 595 535 1511 567 1533 565 22161 588 1512 566 1534 564 564 556 598 512 1535 563 565 565 1534 534 594 536 1538 560 593 537 1536 542 1532 566 587 543 585 535 1511 567 1534 564 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 588 2558 498 526 482 515 483 546 452 545 453 545 453 545 453 545 453 544 474 554 444 20047 583 2562 504 519 479 519 479 1003 515 483 1024 548 450 1001 995 1001 506 116771 593 2552 504 520 478 551 447 1004 513 514 993 549 449 1002 994 1002 516 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 587 2559 507 517 481 517 481 547 451 516 482 546 452 546 452 546 452 545 473 525 483 20038 592 2553 503 521 477 552 446 521 477 1004 514 515 992 1034 483 514 484 513 485 116769 593 2552 504 520 478 550 448 520 478 1003 515 514 993 1032 486 513 475 522 476 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 4558 4576 558 596 565 97881 4554 4579 565 589 562 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 1039 7202 958 4713 981 4713 961 7255 956 4739 955 16077 1038 7204 956 4714 980 4715 959 7256 954 4741 954 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 506 2638 510 516 482 516 482 546 452 546 452 545 453 545 453 545 453 575 443 554 444 20048 592 2554 502 1036 481 517 481 517 511 516 482 516 961 1035 513 515 483 514 484 116757 594 2552 504 1034 484 514 484 514 504 524 484 513 964 1032 506 522 476 492 506 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 170 7441 169 4924 174 7437 193 7444 171 7441 174 4918 170 7441 174 4917 171 7440 195 7443 172 7439 171 55187 174 7437 173 4919 175 7437 173 7438 172 7466 175 4891 172 7439 171 4921 172 7439 171 7441 174 7464 171 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 179 4967 172 4915 178 6980 171 4945 169 4948 176 4912 176 4941 178 16307 176 4969 175 4912 176 6982 174 4942 171 4945 169 4919 174 4943 176 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 1042 793 898 883 869 858 924 884 878 877 875 879 873 855 928 1717 901 854 1794 878 894 887 875 1716 902 89114 985 798 903 878 874 880 902 852 900 855 897 857 905 876 896 1722 906 849 1789 883 899 855 897 1721 897 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 170 8480 169 8481 178 8472 177 8474 175 8476 173 5513 176 8474 175 8476 173 8507 178 5509 175 8505 174 45558 175 8476 173 8476 173 8477 172 8478 171 8480 169 5517 177 8473 176 8474 175 8506 174 5512 172 8509 171 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 628 2577 499 528 480 545 453 544 454 544 454 544 454 544 454 543 455 543 475 553 445 20047 593 2553 503 521 477 551 447 1004 514 515 992 519 479 1003 515 513 485 543 455 116768 585 2561 505 519 479 519 479 1002 516 513 994 548 450 1001 506 522 476 521 477 -# +# name: Power type: parsed protocol: NECext address: 80 63 00 00 command: 0F 15 00 00 -# +# name: Power type: parsed protocol: NECext address: 80 64 00 00 command: 49 08 00 00 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 298 1828 270 804 245 1829 279 1820 278 797 252 771 278 1822 276 1824 274 800 249 1825 273 802 247 776 252 771 278 1822 276 799 250 48931 388 1737 280 796 253 1821 277 1822 276 798 251 1823 275 800 249 774 275 1825 273 776 273 1801 297 1828 270 1829 279 796 253 1821 277 42813 301 1825 273 801 248 1826 272 1827 271 804 245 805 244 1830 278 1821 277 798 251 1823 275 799 250 774 244 779 280 1820 278 796 253 48926 382 1744 354 696 271 1828 270 1829 279 796 253 1821 277 797 252 746 303 1823 275 774 275 1799 299 1826 272 1827 271 804 245 1829 279 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 177 5508 176 5539 176 8475 174 5542 173 8478 171 5545 170 8481 168 8482 177 8473 176 5541 174 8476 173 45573 169 5517 177 5538 177 8473 176 5541 174 8476 173 5544 171 8479 170 8481 178 8472 177 5540 175 8475 174 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 175 8474 175 8475 174 8477 172 8478 171 8480 169 8481 178 5538 177 5510 174 5542 173 5543 172 5544 171 45575 177 8472 177 8474 175 8476 173 8477 172 8478 171 8481 178 5507 177 5539 176 5540 175 5542 173 5543 172 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 8050 3971 511 1562 516 1558 510 539 490 1557 511 1563 515 533 485 538 490 533 485 3983 530 1569 509 1564 514 1559 509 1565 513 1560 508 515 513 536 482 541 488 24152 8042 3979 514 1560 508 1565 513 510 508 1565 513 1560 508 515 513 536 482 541 488 3980 533 1567 511 1562 516 1557 511 1563 515 1558 510 539 489 534 484 539 490 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 175 8475 174 5512 177 8473 176 8475 174 8506 179 5508 176 8474 175 8475 174 8477 172 5544 171 8480 169 45587 176 8475 174 5511 173 8477 177 8473 176 8504 170 5516 178 8472 177 8473 176 8475 174 5542 173 8478 171 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 176 7436 174 7438 172 7439 171 7441 174 7463 172 7440 170 4921 172 4919 174 4917 176 4916 177 4914 174 55176 175 7437 173 7439 191 7446 174 7438 177 7434 176 7435 175 4917 176 4915 179 4914 174 4917 171 4946 178 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 176 7435 175 4917 177 7435 175 7436 189 7449 176 7435 175 7437 173 4918 175 7436 174 4918 175 4916 178 55171 175 7436 174 4918 170 7441 174 7438 177 7460 170 7441 174 7438 177 4914 174 7437 178 4914 169 4922 171 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 8049 3973 509 1564 514 509 509 1564 514 535 483 1564 514 535 483 540 489 535 483 3980 533 1566 512 537 481 1566 512 511 507 1566 512 537 481 542 486 511 507 24149 8045 3976 516 1558 510 512 516 1557 511 512 516 1558 510 512 516 507 511 512 506 3984 539 1560 508 515 513 1560 508 541 488 1560 508 541 488 536 482 514 504 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 366 202 867 527 170 130516 343 227 863 529 168 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 176 5992 176 1372 176 1320 177 1345 172 1349 179 1370 178 1317 175 4444 176 1346 171 1351 177 1345 172 1349 179 1344 174 5963 175 65574 172 5995 178 1344 174 1348 175 1347 175 1347 170 1378 170 1325 172 4447 178 1344 173 1349 169 1353 175 1347 170 1351 177 5961 177 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 497 1478 488 511 467 1482 494 531 447 1477 499 501 466 533 445 530 468 507 471 529 438 12857 488 1485 491 509 469 1481 495 529 448 1476 490 510 468 532 445 529 469 480 498 502 465 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 982 6313 961 2662 903 2718 929 2719 907 6363 900 2722 904 6365 909 6361 903 6368 906 2742 905 46364 989 6307 906 2716 911 2712 925 2723 903 6367 907 2715 901 6369 905 6365 909 6361 903 2745 902 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 599 257 975 317 177 130649 308 228 974 319 175 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 176 4969 170 6958 173 6985 177 6981 171 6958 178 6980 177 6981 170 16308 180 4966 173 6955 176 6982 169 6988 174 6956 175 6983 173 6984 172 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 178 5990 173 1349 174 1348 175 1348 174 4444 176 1346 171 1351 177 4416 178 1344 173 1348 169 1353 175 1347 170 1351 177 9048 177 65573 173 5995 173 1348 175 1348 174 1347 176 4444 171 1351 177 1345 173 4421 173 1348 169 1352 176 1347 170 1351 177 1345 172 9053 177 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 174 4972 177 4910 173 6985 177 4940 174 6984 178 6951 175 6983 174 14269 177 4968 176 4912 176 6981 175 4941 173 6985 177 6953 178 6979 172 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 1318 225 177 130305 1609 231 171 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 585 2410 473 1065 962 581 447 519 479 580 448 549 449 579 449 518 480 548 480 517 481 517 481 577 451 516 482 576 452 545 453 14955 510 2483 481 1058 480 549 969 543 445 1037 511 547 481 546 482 516 482 545 484 545 483 514 484 544 963 1063 485 543 445 111612 626 2427 557 954 513 512 995 547 451 1031 507 521 508 551 477 520 478 550 478 549 479 488 510 548 969 1057 481 517 481 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 203 272 1215 302 171 130229 1423 275 178 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 176 7436 174 7438 192 7446 174 7437 178 7434 176 7435 175 4917 176 4915 178 4913 175 4917 197 7440 175 55130 286 7377 177 7435 175 7437 173 7438 172 7466 174 7411 178 4913 170 4921 172 4919 174 4918 175 7436 174 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 175 7437 173 4918 170 7441 174 7437 178 7460 170 7441 179 7433 177 4915 178 4913 170 4922 171 4920 173 55124 291 7372 172 4921 172 7439 171 7441 174 7463 172 7440 170 7442 178 4913 170 4922 171 4920 173 4918 175 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 172 8477 172 8478 171 8480 169 5516 179 8502 178 5509 175 8475 174 8476 173 8479 170 5545 170 8480 169 45588 176 8473 176 8474 175 8476 173 5513 177 8504 171 5515 174 8476 178 8472 177 8473 176 5541 174 8476 173 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 177 7436 174 4918 175 7436 174 4918 175 7462 178 4887 176 4916 177 4914 174 4917 171 4921 172 4919 175 55184 175 7435 175 4918 175 7436 174 4918 175 7462 178 4914 174 4917 171 4920 173 4919 174 4917 176 4915 178 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 508 2484 480 1060 967 544 485 544 454 574 454 543 455 573 445 553 445 522 506 552 446 552 446 551 477 551 447 581 447 520 478 550 478 15908 626 2427 476 1062 476 522 995 547 451 1061 477 520 508 550 478 519 479 519 999 1058 959 1037 990 582 446 1035 483 111666 590 2404 479 1059 479 549 968 543 455 1027 511 548 480 517 511 486 512 546 961 1065 962 1034 993 579 449 1032 486 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 175 8475 174 8475 174 8477 172 8478 171 8480 169 5517 178 8473 175 8475 179 8501 173 5513 176 8505 169 45562 179 8472 177 8473 176 8474 175 8476 173 8478 171 5515 174 8476 178 8473 176 8505 174 5512 172 8508 171 120769 178 93377 175 7437 173 4919 174 7437 173 7439 171 7467 173 7439 171 7441 174 4918 170 4921 172 7439 171 7467 173 55167 171 7440 170 4922 171 7440 195 7443 172 7439 171 7441 174 7438 177 4915 173 4918 195 7442 173 7439 170 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 295 1805 273 776 242 1808 270 754 244 1806 272 777 241 758 270 754 264 760 268 756 272 14149 297 1802 266 784 244 1805 263 762 246 1803 265 785 244 780 248 751 267 758 271 753 265 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 535 1723 569 585 566 615 536 619 542 586 565 616 535 1722 539 615 536 1721 561 620 541 587 564 617 534 621 540 588 563 618 543 1714 537 617 534 621 540 615 536 618 543 612 539 615 536 1722 560 594 567 1717 534 1723 569 1714 557 1700 643 1639 643 1641 559 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 502 2521 504 521 997 545 453 575 453 545 453 575 454 22097 591 2433 511 513 994 1062 476 552 476 522 476 552 476 120839 628 2455 509 515 992 1064 484 513 505 493 515 543 475 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 591 2404 479 1060 967 1029 509 519 509 549 479 518 480 79926 585 2408 556 956 989 1033 515 544 484 543 475 522 476 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 586 2560 506 518 480 548 450 548 450 517 481 517 481 517 481 547 451 546 482 516 482 20040 590 2556 500 1038 480 518 969 543 455 543 475 1006 511 517 481 517 511 486 481 116778 584 2561 505 1033 485 513 964 548 450 548 480 1001 506 522 476 522 506 521 446 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 917 206 175 186 170 21561 170 2280 175 2274 502 1929 174 2276 169 5178 170 2261 173 3724 498 1932 171 2279 176 2273 172 3709 172 2277 178 3720 171 17223 177 7619 174 2275 170 2279 176 2256 168 2280 175 5172 176 2256 168 3729 173 2276 179 2253 171 2278 177 3703 178 2271 174 3724 177 17251 170 7627 177 2272 173 2276 169 2263 171 2277 178 5169 169 2262 172 3726 175 2256 168 2280 175 2274 171 3710 171 2278 177 3720 171 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 565 233 653 313 170 130328 752 235 233 107 229 398 177 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 586 2439 505 549 968 1027 511 517 511 517 481 547 481 21583 584 2439 505 520 997 1059 479 549 479 518 480 548 480 120894 593 2432 501 522 995 1061 477 521 507 520 478 550 478 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 558 8032 474 8115 503 8116 482 8108 480 8141 477 5239 476 8114 504 8115 483 8107 481 5236 509 8111 477 45290 554 8036 481 8108 510 8110 478 8112 476 8145 473 5243 482 8107 511 8109 479 8111 477 5240 505 8115 473 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 173 7438 172 4920 173 7438 172 4920 173 7465 175 4890 173 7439 171 4920 173 4919 174 4917 176 4915 178 55179 170 7441 169 4924 174 7437 178 4913 175 7463 172 4893 170 7441 174 4918 170 4922 171 4920 173 4918 175 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 225 745 222 774 193 778 200 797 175 769 193 777 201 771 196 774 224 747 220 776 202 769 198 248 220 252 196 250 218 253 225 746 221 250 198 248 220 252 226 246 222 223 225 248 220 252 216 229 219 253 225 247 221 277 176 243 220 279 189 230 228 244 224 248 220 252 196 250 218 253 215 257 201 770 197 799 189 257 201 271 197 37716 222 749 218 778 200 771 196 801 172 772 200 771 196 774 193 777 221 750 217 780 197 773 194 251 217 255 193 279 199 273 195 749 218 254 194 252 226 245 223 275 178 242 221 277 201 245 193 253 225 247 221 250 218 254 194 252 226 272 196 223 225 248 220 251 217 255 193 253 225 247 221 251 197 773 194 803 174 297 176 244 219 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 3503 2655 197 642 876 2568 844 834 846 833 848 860 821 831 839 2576 847 2569 843 2572 841 2574 849 858 823 857 813 866 815 865 815 2572 841 2575 848 2568 844 2570 842 836 844 863 818 834 847 861 820 2568 845 2571 872 2571 842 32651 3505 3495 875 2567 845 861 820 832 849 859 811 840 840 2576 847 2568 844 2571 842 2574 849 830 840 867 814 838 842 865 815 2572 840 2575 848 2568 845 2571 841 865 815 864 817 834 846 861 819 2569 843 2572 871 2572 840 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 347 677 219 252 196 276 202 742 225 798 174 770 192 778 200 771 196 775 223 748 219 777 200 770 197 249 219 253 195 251 227 271 197 747 220 252 216 229 219 253 225 273 195 277 176 244 219 253 215 230 228 244 224 248 220 252 196 250 218 280 198 247 201 245 223 249 219 253 195 251 227 245 223 248 200 797 175 795 198 248 200 246 222 37666 344 678 228 245 223 222 226 771 196 800 198 747 220 750 217 753 224 773 194 776 202 769 198 799 173 246 227 245 223 249 199 247 221 749 218 280 198 247 201 245 223 249 219 253 195 251 227 244 224 248 200 272 196 250 218 254 194 252 226 245 223 249 199 274 194 251 227 245 193 253 225 273 195 251 217 753 225 746 221 251 197 275 193 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 174 7437 173 7440 174 7437 178 7433 177 7461 169 7442 178 4914 174 4917 171 4921 172 4919 174 7463 177 55163 177 7435 174 7437 173 7438 172 7440 175 7463 172 7413 176 4915 178 4913 175 4917 171 4920 174 7438 172 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 8042 3979 513 510 508 541 487 1559 509 515 513 1560 508 515 513 510 508 541 477 3981 532 1568 510 1563 515 1558 510 1564 514 1559 509 514 514 535 483 540 488 24151 8042 3979 513 536 482 541 487 1560 508 541 488 1560 508 541 487 536 482 541 477 3980 533 1566 512 1561 507 1566 512 1562 516 1557 511 538 491 533 485 538 490 -# +# name: Power type: parsed protocol: NECext address: 83 7A 00 00 command: 08 00 00 00 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 8988 4504 640 487 562 592 538 590 510 592 538 590 540 588 532 596 514 614 516 1689 562 1668 563 1695 536 1695 566 1664 567 1690 612 1618 643 1430 801 1613 648 481 558 570 540 589 541 587 533 595 535 592 508 594 536 592 538 1667 564 1693 558 1672 569 1688 563 1668 644 1586 563 1694 567 40630 8994 2265 557 96833 8987 2273 538 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 173 7439 171 4922 172 7439 171 4921 172 7466 174 4917 176 4915 173 4918 170 7441 174 7438 192 7445 175 55181 174 7437 173 4918 175 7437 173 4919 175 7463 177 4888 175 4917 176 4915 178 7460 170 7440 175 7437 178 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 1636 4610 1563 1533 1584 7760 1561 28769 1641 4606 1557 1539 1588 7757 1564 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 590 2404 479 1059 968 575 454 544 454 574 454 543 455 543 475 522 476 552 476 552 446 551 447 581 448 520 478 581 447 519 479 549 479 15967 587 2407 476 1062 476 522 995 547 451 1030 508 520 509 550 478 519 479 519 998 1028 999 1057 481 547 960 1066 482 111641 587 2407 476 1063 485 513 994 547 451 1031 507 551 477 551 477 520 478 550 968 1059 968 1027 511 548 959 1036 512 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 588 2406 477 1061 966 577 451 516 482 545 483 545 453 575 443 524 484 514 504 554 454 543 455 543 475 553 445 583 445 521 477 582 446 15969 585 2409 474 1065 483 514 993 549 449 1033 515 543 475 553 475 522 476 552 965 1030 997 576 452 1029 998 1028 479 111668 587 2407 475 1063 485 543 964 517 481 1031 507 552 476 551 477 520 478 550 968 1028 999 574 454 1027 990 1036 481 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 174 7439 196 7441 174 7437 173 7439 171 7441 174 7438 177 7460 170 7442 178 7433 177 4915 173 4918 170 55186 174 7438 172 7440 170 7441 174 7438 177 7461 169 7416 173 7464 176 7435 175 7437 173 4918 175 4917 176 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 173 7438 172 7441 174 7438 177 7434 176 7462 168 7443 177 7435 175 4917 176 4915 173 7438 177 4941 173 55166 174 7438 197 7441 174 7437 173 7439 171 7441 174 7438 177 7460 170 4922 171 4893 195 7443 172 4920 173 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 175 7436 179 4913 175 4917 171 4920 173 4945 169 4896 177 7435 175 7436 174 7464 176 4916 177 4914 169 55180 170 7441 169 4924 169 4922 171 4920 173 4919 174 4917 176 7435 175 7463 177 7434 176 4916 177 4914 174 -# +# name: Power type: parsed protocol: RC5 address: 02 00 00 00 command: 0C 00 00 00 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 176 5991 177 1372 176 1320 177 1344 173 1349 174 1374 169 1327 170 4449 176 1346 171 1351 177 1345 172 1349 168 1353 175 5963 175 65573 173 5995 178 1344 174 1348 175 1348 174 1347 170 1378 170 1325 172 4447 178 1344 174 1349 169 1353 175 1347 170 1352 176 5961 177 -# +# name: Power type: parsed protocol: NEC address: 71 00 00 00 command: 4A 00 00 00 -# +# name: Power type: parsed protocol: NEC address: 60 00 00 00 command: 03 00 00 00 -# +# name: Power type: parsed protocol: NEC address: 60 00 00 00 command: 00 00 00 00 -# +# name: Power type: parsed protocol: NEC address: 42 00 00 00 command: 01 00 00 00 -# +# name: Power type: parsed protocol: NECext address: 50 AD 00 00 command: 00 00 00 00 -# +# name: Power type: parsed protocol: NECext address: 50 AD 00 00 command: 02 00 00 00 -# +# name: Power type: parsed protocol: NEC address: 50 00 00 00 command: 3F 00 00 00 -# +# name: Power type: parsed protocol: Samsung32 address: 06 00 00 00 command: 0F 00 00 00 -# +# name: Power type: parsed protocol: NEC address: 08 00 00 00 command: 12 00 00 00 -# +# name: Power type: parsed protocol: Samsung32 address: 08 00 00 00 command: 0B 00 00 00 -# +# name: Power type: parsed protocol: NECext address: 83 55 00 00 command: C2 00 00 00 -# +# name: Power type: parsed protocol: NEC address: 00 00 00 00 command: 51 00 00 00 -# +# name: Power type: parsed protocol: NECext address: 00 BD 00 00 command: 01 00 00 00 -# +# name: Power type: parsed protocol: Samsung32 address: 00 00 00 00 command: 0F 00 00 00 -# +# name: Power type: parsed protocol: Samsung32 address: 16 00 00 00 command: 0F 00 00 00 -# +# name: Power type: parsed protocol: NEC address: 01 00 00 00 command: 01 00 00 00 -# +# name: Power type: parsed protocol: NECext address: 80 68 00 00 command: 49 00 00 00 -# +# name: Power type: parsed protocol: NECext address: 86 02 00 00 command: 49 00 00 00 -# +# name: Power type: parsed protocol: SIRC address: 01 00 00 00 command: 15 00 00 00 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 178 7761 176 11308 546 957 540 1958 538 970 537 1955 541 962 545 1953 543 964 543 962 545 957 540 970 548 960 547 1945 541 1950 546 965 542 1953 543 962 545 1945 540 970 537 1958 538 7881 172 7744 172 11318 536 971 536 1956 540 963 534 1964 542 966 541 1951 535 968 539 971 536 971 536 969 538 964 533 1965 541 1954 542 963 534 1956 540 971 536 1959 537 968 539 1951 535 -# +# name: Power type: parsed protocol: Kaseikyo address: 80 02 20 00 command: D0 03 00 00 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 278 1845 274 808 271 806 273 812 278 805 275 805 274 1840 279 1844 275 809 281 1836 272 806 274 812 278 805 274 1842 277 802 277 44956 279 1842 277 804 275 802 277 808 271 811 279 1838 281 798 271 814 276 1844 275 806 273 1841 278 1845 274 1846 273 808 271 1843 276 44959 275 1845 274 807 272 805 275 811 279 804 275 805 274 1839 280 1844 275 808 271 1845 274 805 274 811 279 804 275 1841 278 801 278 44955 280 1841 278 802 277 801 278 807 272 810 280 1837 271 807 272 813 277 1843 276 805 274 1839 280 1843 276 1845 274 807 272 1842 277 -# +# name: Power type: parsed protocol: RC5 address: 00 00 00 00 command: 0C 00 00 00 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 509 1717 504 629 512 631 510 627 504 633 508 633 508 1713 508 1719 512 1713 508 625 505 637 504 633 508 629 512 629 512 623 508 1719 512 626 505 628 513 631 510 627 514 623 507 632 509 1713 508 632 509 1716 505 1715 506 1724 507 1716 505 1719 512 1715 506 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 506 493 505 4059 505 5051 501 506 502 4065 499 511 497 4063 501 5058 504 504 504 4060 504 5052 500 507 501 4066 498 5056 506 5042 500 515 503 119614 504 505 503 4065 499 5051 501 511 497 4069 505 499 499 4072 502 5050 502 506 502 4066 498 5053 499 512 506 4060 504 5044 498 5061 501 508 500 -# +# name: Power type: parsed protocol: NECext address: 87 22 00 00 command: E0 1F 00 00 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 8313 4161 515 1574 514 1571 507 569 510 562 507 563 506 562 507 568 511 562 507 1580 508 1577 511 1582 506 567 513 1575 513 554 505 571 509 564 505 22604 513 1573 505 1589 509 563 506 564 505 562 507 569 511 562 507 563 506 1579 509 1584 514 1576 512 558 511 1574 514 562 507 565 515 556 513 22593 514 1581 507 1583 505 564 505 563 506 570 510 563 506 564 505 562 507 1586 512 1578 510 1577 511 557 512 1581 507 566 514 556 513 555 514 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 8735 4383 558 573 557 550 560 568 562 544 566 1722 560 540 560 1732 560 544 566 1719 563 1701 560 1723 559 1704 557 574 556 1699 562 574 556 1703 559 1727 565 1698 563 1721 561 546 564 1723 559 541 559 577 564 541 559 571 560 548 562 565 566 1697 565 567 564 1693 558 1733 559 1701 560 39926 8754 2247 565 92341 8758 2243 589 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 3298 3336 821 2506 825 881 820 2505 826 2529 823 856 825 2524 817 866 825 2528 813 2513 818 888 813 862 819 887 814 865 826 2522 820 864 827 2526 815 861 820 887 814 2510 821 885 816 863 818 2531 821 2512 819 2559 793 32401 3298 3349 818 2507 824 882 819 2509 822 2527 814 868 823 2530 821 855 826 2531 821 2504 827 879 822 857 824 875 816 867 824 2529 823 854 827 2530 821 853 817 889 822 2506 825 873 818 866 825 2527 814 2513 818 2564 788 -# +# name: Power type: parsed protocol: NEC42 address: 1C 01 00 00 command: 12 00 00 00 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 289 2112 261 2109 295 2101 262 918 294 912 259 915 297 2098 265 916 296 909 262 2107 297 905 266 913 289 918 263 910 292 909 262 918 294 24789 263 2106 298 2098 265 2110 294 913 258 916 296 905 266 2108 296 911 260 914 288 2107 266 914 298 908 263 911 291 910 261 919 293 913 258 -# +# name: Power type: parsed protocol: NEC address: 38 00 00 00 command: 12 00 00 00 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 3215 1637 410 430 405 439 406 1242 408 435 410 1242 408 428 407 440 405 435 410 1240 410 1243 407 431 404 440 405 437 408 1238 402 1254 406 434 401 439 406 438 407 431 404 440 405 437 408 428 407 439 406 434 401 439 406 438 407 1241 409 434 401 441 404 432 403 444 401 1249 401 439 406 438 407 1241 409 434 401 441 404 433 402 444 401 1249 401 439 406 1248 402 436 409 434 401 441 404 433 402 444 401 439 406 45471 3239 1614 403 435 400 444 401 1250 400 437 408 1248 402 438 407 433 402 442 403 1245 405 1248 423 420 405 431 404 443 402 1248 402 1248 422 421 404 435 400 443 402 440 405 431 404 443 402 438 407 433 402 442 403 435 400 443 402 1250 400 436 399 447 408 432 403 438 407 1246 404 434 401 443 402 1250 400 436 399 447 408 432 403 437 408 1246 404 434 401 1253 407 434 401 436 399 447 408 432 403 437 408 436 399 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 7928 3948 504 515 503 518 500 1583 505 516 502 1584 504 510 508 516 502 516 502 3952 510 1579 509 507 501 1587 501 519 510 1571 507 518 500 517 501 517 501 23073 7931 3943 509 514 504 515 503 1578 500 524 505 1580 508 510 508 514 504 511 507 3951 511 1576 502 513 505 1586 502 515 503 1582 506 515 503 513 505 516 502 -# +# name: Power type: parsed protocol: NECext address: 18 18 00 00 command: C0 3F 00 00 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 492 4975 496 4993 498 498 500 4056 498 504 494 4055 499 4963 497 532 496 4031 492 4996 495 502 496 4060 494 4972 499 4990 491 506 492 4063 491 511 497 4052 492 4970 490 539 500 4027 496 103358 500 4961 500 4995 496 505 493 4056 498 499 499 4057 497 4969 491 533 496 4026 497 4997 494 508 490 4059 495 4967 494 5001 490 511 497 4053 491 505 493 4063 491 4975 496 528 490 4032 491 -# +# name: Power type: parsed protocol: SIRC address: 01 00 00 00 command: 2F 00 00 00 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 3425 3482 848 2607 846 2639 845 2608 846 2639 845 2612 852 2624 850 2613 851 911 851 885 847 919 843 891 851 915 847 890 852 907 845 897 845 917 845 891 851 915 847 887 845 2639 845 2612 852 2625 849 2613 851 2630 844 34282 3455 3478 852 2601 852 2633 851 2605 848 2629 845 2617 847 2633 851 2604 849 917 845 890 852 913 849 889 843 915 847 896 846 916 846 890 852 914 848 885 847 919 843 895 847 2630 844 2617 847 2634 850 2605 848 2636 848 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 500 500 498 4066 498 5058 504 502 506 4061 503 508 500 4060 504 5055 497 5055 497 511 497 4071 503 5047 505 507 501 4065 499 5049 503 512 496 124314 501 508 500 4067 507 5043 499 513 505 4060 504 501 497 4073 501 5052 500 5052 500 512 496 4066 498 5058 504 506 502 4058 506 5053 499 509 499 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 176 7763 174 8817 169 10842 544 1955 541 967 540 1952 544 959 538 972 546 962 545 1947 538 1952 544 967 540 1955 541 964 543 1947 539 972 546 1949 547 7873 170 7746 170 10350 175 11811 543 960 537 1961 535 972 535 970 537 965 542 1956 540 1956 540 965 542 1947 539 973 534 1960 536 969 538 1952 534 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 2570 2682 1189 1208 1186 2665 1186 1217 1187 2668 1183 1215 1179 2671 1190 2695 1186 1187 1187 2692 1179 2671 1190 1213 1181 1193 1191 1207 1187 2663 1188 1215 1179 2676 1185 46941 2563 2685 1186 1191 1183 2698 1184 1188 1186 2691 1180 1197 1187 2694 1187 2666 1185 1210 1184 2674 1187 2695 1186 1185 1189 1206 1188 1189 1185 2696 1186 1186 1187 2689 1182 -# +# name: Power type: parsed protocol: RC5 address: 00 00 00 00 command: 26 00 00 00 -# +# name: Power type: parsed protocol: RC5 address: 00 00 00 00 command: 0C 00 00 00 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 3325 1560 406 444 401 453 402 1226 404 449 406 1226 404 443 402 454 401 449 406 1224 406 1228 402 446 409 444 401 451 404 1223 407 1230 410 440 405 445 410 443 402 446 409 444 401 451 404 442 403 453 402 448 407 443 402 451 404 1224 406 448 407 444 401 445 410 446 409 1221 409 442 403 1231 409 439 406 1227 403 450 405 441 404 452 403 1227 403 448 407 446 409 439 406 447 408 444 401 445 400 456 410 440 405 52348 3320 1553 403 445 400 454 401 1230 400 447 408 1228 402 449 406 444 401 452 403 1225 405 1229 431 421 404 442 403 454 401 1229 401 1229 431 423 402 446 399 455 400 451 404 442 403 453 402 448 407 443 402 451 404 444 401 452 403 1229 401 445 400 457 398 451 404 446 399 1235 405 443 402 1232 408 444 401 1225 405 452 403 447 398 452 403 1231 399 449 406 447 408 443 402 444 401 456 399 450 405 445 400 453 402 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 990 915 980 909 986 924 981 2829 981 934 981 2823 987 923 982 2828 982 933 982 906 989 33895 989 907 988 927 988 900 985 2841 979 915 980 2851 980 909 986 2840 980 914 981 934 981 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 821 5754 848 2490 841 2492 819 2524 817 5726 845 2492 839 5727 844 5757 845 5727 854 2483 848 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 5092 1621 406 2599 406 2598 407 2605 1653 1616 411 2619 1609 1606 1654 1618 409 2623 382 2600 405 -# +# name: Power type: parsed protocol: NEC address: 15 00 00 00 command: 12 00 00 00 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 1144 1010 6795 26754 1151 997 6798 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 1144 1009 1120 1006 1143 1991 1116 26758 1146 1006 1123 1003 1146 1988 1119 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 170 46238 169 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 8906 4165 572 1672 569 1678 563 640 572 637 565 643 569 633 569 643 569 1674 567 639 563 1684 567 637 565 1681 570 1675 566 1673 568 1681 570 636 566 640 572 637 565 639 563 1684 567 640 572 630 572 640 572 634 568 1675 566 1681 570 1670 571 638 564 1681 570 1669 572 1678 563 1680 571 40485 8898 2252 570 85621 8955 2194 567 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 446 1191 449 1194 446 1195 445 1204 446 1200 1316 459 447 1193 447 1202 448 1197 443 1201 449 1191 449 34491 443 1204 446 1197 443 1198 442 1207 443 1202 1314 436 440 1201 449 1199 441 1205 445 1198 442 1199 441 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 4268 4327 522 1593 516 1603 516 1597 522 519 520 520 519 515 514 531 518 520 519 519 520 521 518 519 520 1597 522 1595 514 1597 522 1599 520 1595 514 524 515 1604 515 521 518 523 516 524 515 519 520 525 514 524 515 1599 520 522 517 1596 513 1605 514 1602 517 1595 514 1607 522 1592 516 40481 8748 2187 523 93986 8721 2189 521 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 247 3006 244 153 178 1007 251 117 637 597 381 678 218 156 175 529 382 679 217 157 174 24963 247 3038 252 117 219 994 249 119 217 483 250 117 173 531 278 779 173 167 169 569 383 679 217 156 175 126538 246 3039 251 118 218 995 247 120 195 504 249 118 172 532 277 780 172 168 178 560 382 680 216 157 174 -# +# name: Power type: parsed protocol: NEC address: 10 00 00 00 command: EF 00 00 00 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 4278 4318 521 517 522 520 519 517 522 520 519 521 518 516 513 531 518 520 519 1595 514 1605 514 1599 520 1598 521 1595 513 1598 521 1600 519 1596 513 1602 517 1601 518 1595 514 1604 515 525 514 520 519 526 513 525 514 524 515 527 522 513 516 526 513 1603 516 1595 514 1607 522 1593 516 40481 8749 2186 524 93985 8722 2189 521 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 2746 8423 2744 19607 2746 19601 2742 8431 2745 8424 2742 19608 2745 8419 2747 19608 2745 19607 2746 8422 2744 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 170 6683 174 4889 175 10382 372 849 821 2498 176 10378 264 2479 842 2492 839 2475 846 2483 838 2481 840 2495 836 2477 844 845 846 37009 172 6681 176 4888 175 10381 373 848 924 2395 844 2490 174 10383 248 2492 172 10386 245 2495 169 -# +# name: Power type: parsed protocol: NEC address: C4 00 00 00 command: 18 00 00 00 -# +# name: Power type: parsed protocol: NEC address: 37 00 00 00 command: 12 00 00 00 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 179 2644 178 8174 170 2646 176 8181 173 2648 174 8177 177 2640 172 5419 174 5413 170 2649 173 2644 178 2646 176 2646 176 5409 174 28831 174 2651 171 8183 171 2648 174 8174 170 2655 177 8177 177 2642 170 5412 171 5420 173 2649 173 2646 176 2640 172 2653 169 5419 174 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 174 2648 174 8178 176 2640 171 8185 169 2653 169 8182 172 2645 177 2647 557 2264 558 5027 174 5410 173 5418 175 5413 170 28837 168 2649 173 8184 170 2652 170 8181 173 2643 169 8188 176 2646 176 2643 168 2648 174 5417 176 5411 172 5413 170 5413 170 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 178 706 266 139 268 137 173 173 198 234 178 126768 175 299 169 86 275 130 267 138 172 230 177 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 554 1922 584 3809 582 3782 578 3821 580 1920 555 1941 544 1922 574 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 476 1470 465 3479 474 3469 474 3477 475 1467 468 1471 474 27473 472 1474 472 3475 467 3478 475 3468 474 1471 475 1468 467 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 301 2188 236 2169 235 2205 230 1009 234 1070 183 1064 179 2198 206 1046 207 1069 173 2207 207 1042 211 1062 201 1043 210 1032 231 1005 237 1039 234 1006 236 -# +# name: Power type: parsed protocol: NEC address: 6D 00 00 00 command: 14 00 00 00 -# +# name: Power type: parsed protocol: NEC address: 20 00 00 00 command: 11 00 00 00 -# +# name: Power type: parsed protocol: NEC address: A0 00 00 00 command: 0B 00 00 00 -# +# name: Power type: parsed protocol: NEC address: 28 00 00 00 command: 0B 00 00 00 -# +# name: Power type: parsed protocol: NEC address: FF 00 00 00 command: 3F 00 00 00 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 176 7764 173 11361 544 1951 545 1948 176 8815 171 2319 177 2322 174 2321 175 2318 178 2313 173 18281 170 7746 170 11369 536 1954 542 1957 539 969 538 1954 542 1949 536 1962 534 1960 174 2320 176 2315 170 2328 178 2317 168 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 172 23035 176 2267 178 2285 170 2270 175 2288 177 11602 222 2218 216 2246 173 183 173 935 221 218 174 864 221 1250 171 1310 223 2219 216 2247 218 1244 223 216 176 869 170 1296 217 2239 216 1254 223 216 176 866 219 9127 169 7642 173 2284 171 2273 172 2290 175 2263 171 10143 178 10623 222 1245 222 217 175 1843 220 2226 219 1264 223 1237 174 183 178 952 219 2222 223 216 176 866 219 1249 172 9190 173 7637 178 2266 169 2286 169 2280 175 2284 171 10125 175 10622 224 215 177 868 216 2228 217 2238 171 1299 224 215 177 865 174 183 173 934 222 216 176 1848 215 1247 220 1265 222 762 170 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 7410 1482 382 2742 375 2747 380 2751 1630 1535 400 2724 1657 1529 1629 1538 407 2720 407 2716 401 2722 4125 1549 376 2751 376 2748 379 2744 1626 1541 405 2723 1658 1530 1628 1531 404 2726 401 2726 401 2723 4124 1542 383 2747 380 2747 380 2745 1626 1534 401 2729 1652 1539 1629 1532 403 2719 408 2722 405 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 4277 4319 520 518 521 521 518 518 521 1597 522 1594 515 1597 522 1599 520 1594 515 1600 519 1600 519 1594 515 526 513 527 522 512 517 528 521 517 522 516 513 1605 514 522 517 525 514 525 514 521 518 526 513 525 514 1600 519 523 516 1597 522 1596 513 1603 516 1595 514 1607 522 1593 516 40481 8749 2186 524 93986 8721 2188 522 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 1325 431 445 1199 1317 455 441 1207 443 1202 1294 457 449 1190 440 1209 441 1205 445 1198 1318 454 442 93237 1320 434 442 1201 1325 448 448 1200 440 1205 1291 460 446 1193 447 1202 448 1198 442 1201 1325 447 449 -# +# name: Power type: parsed protocol: NEC address: 00 00 00 00 command: 0B 00 00 00 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 300 1791 297 744 295 743 296 751 298 745 294 747 302 736 293 1837 271 745 294 747 302 1793 295 752 297 1802 296 1801 297 742 297 1806 302 31592 296 1801 297 742 297 749 300 744 295 745 294 745 294 752 297 1829 269 746 293 745 294 1809 300 744 295 1802 296 1799 299 748 301 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 178 2003 177 2899 177 1996 174 2908 168 2910 177 2000 170 2004 176 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 8898 4173 564 642 570 1677 564 1677 564 645 567 640 572 631 571 641 571 635 567 639 563 1683 568 1673 568 641 571 636 566 637 565 647 565 641 571 634 568 642 570 1671 570 639 563 1682 569 632 570 643 569 636 566 1677 564 1683 568 636 566 1680 571 636 566 1674 567 1682 569 1674 567 40489 8904 2246 566 85626 8902 2248 564 -# +# name: Power type: parsed protocol: NEC address: AA 00 00 00 command: 80 00 00 00 -# +# name: Power type: parsed protocol: NEC address: 51 00 00 00 command: 08 00 00 00 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 3892 3856 525 978 529 977 530 969 528 977 530 974 523 975 532 1924 531 971 526 1924 531 975 532 1916 529 976 531 1921 524 1947 508 1925 530 1920 525 1926 529 1925 530 970 527 1927 528 976 531 1915 530 978 529 1921 1033 9201 3871 3867 524 977 530 975 522 981 526 972 525 983 524 978 529 1921 524 982 525 1923 532 973 524 1928 527 971 526 1931 524 1950 505 1922 523 1931 524 1924 531 1923 532 972 525 1922 523 985 533 1918 527 975 532 1922 1032 -# +# name: Power type: parsed protocol: NEC address: 6E 00 00 00 command: 14 00 00 00 -# +# name: Power type: parsed protocol: RC5 address: 07 00 00 00 command: 0C 00 00 00 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 4758 1543 403 2731 407 2726 402 2739 1601 1514 401 2760 1580 1530 1577 1540 406 2758 400 2708 1602 1535 400 2740 408 2729 409 2726 401 2731 1599 1520 405 2758 1572 1540 1578 1532 403 2737 431 2706 1604 1535 411 2722 405 2734 403 2734 404 2731 1599 1512 403 2764 1576 1539 1578 1533 402 2730 428 2712 1608 1534 402 -# +# name: Power type: parsed protocol: NEC address: AA 00 00 00 command: A7 00 00 00 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 171 313 176 798 337 133 600 232 170 126777 176 65 169 496 176 200 609 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 500 527 491 4033 500 4986 495 510 498 4054 500 499 499 4048 496 4974 497 530 499 4026 497 4989 492 513 495 4057 497 4967 493 4992 499 506 492 4060 494 505 493 4054 500 98563 497 529 500 4025 498 4988 493 512 496 4056 498 501 497 4050 493 4976 495 532 497 4028 495 4991 500 505 493 4059 495 4969 491 4994 497 508 490 4062 492 507 491 4056 498 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 879 901 871 1796 1770 903 869 917 876 916 877 913 880 906 877 918 875 914 879 1788 1768 1784 875 87826 871 921 872 1797 1769 897 875 919 874 915 878 910 873 920 873 914 879 913 870 1800 1776 1767 871 -# +# name: Power type: parsed protocol: RC5 address: 00 00 00 00 command: 0C 00 00 00 -# +# name: Power type: parsed protocol: Kaseikyo address: 90 02 20 00 command: D0 03 00 00 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 4411 4332 558 1660 561 596 565 612 559 597 564 617 565 585 566 620 561 591 560 620 561 595 566 611 560 597 564 1653 558 592 559 627 565 589 562 617 564 1630 560 617 564 592 559 22591 4439 4322 558 1639 561 618 563 590 561 622 560 592 559 624 557 597 564 612 559 600 561 618 563 590 561 622 559 1628 562 621 561 594 567 609 562 597 564 1652 559 595 566 617 564 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 3444 1767 413 487 419 1274 417 481 415 1277 414 488 418 1267 414 492 414 1276 415 484 412 1282 419 478 418 1275 416 1276 415 480 416 1280 421 479 417 1272 419 1275 416 1272 419 1274 417 484 412 484 412 493 413 1277 414 485 421 1273 418 479 417 486 420 1271 420 476 420 486 420 479 417 482 414 1280 411 1276 415 488 418 1273 418 478 418 488 418 481 415 1275 416 487 419 478 418 485 411 1280 421 475 421 1275 416 1273 418 69071 3439 1759 441 456 440 1253 438 463 443 1243 438 468 438 1251 440 460 446 1247 444 454 442 1251 440 461 445 1241 440 1256 445 455 441 1248 443 461 445 1242 439 1254 447 1245 446 1240 441 465 441 458 438 462 444 1249 442 456 440 1252 439 463 443 452 444 1252 439 461 445 454 442 461 445 452 444 1249 442 1250 441 454 442 1254 437 463 443 456 440 463 443 1245 446 456 440 462 444 451 445 1251 440 460 436 1253 438 1256 445 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 8705 4317 583 682 581 688 585 678 585 1721 581 1722 580 681 582 690 583 682 581 1721 581 1725 587 1713 579 689 584 683 580 1718 584 1724 588 1714 588 677 586 683 580 683 580 1726 586 680 583 678 585 687 586 679 584 1718 584 1721 581 1719 583 685 588 1716 586 1713 579 1729 583 1719 583 41145 8706 2217 585 94686 8705 2217 584 -# +# name: Power type: parsed protocol: NECext address: 10 2D 00 00 command: 1F E0 00 00 -# +# name: Power type: parsed protocol: RC5 address: 05 00 00 00 command: 0C 00 00 00 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 178 7753 174 2308 178 2284 171 2316 170 2298 177 10228 174 10724 223 1256 221 217 175 1868 169 2293 172 1327 216 1263 178 1316 217 2245 220 218 174 887 218 1261 170 10707 174 7752 175 2296 169 2314 171 2293 172 2307 179 10216 176 6265 174 10729 219 1258 219 1272 215 1268 173 2310 221 1255 222 217 175 877 172 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 532 1692 559 561 529 574 567 559 531 566 564 555 535 1694 557 568 532 1691 560 560 530 573 557 568 532 565 565 555 535 567 563 1688 533 564 567 554 536 567 563 562 538 558 562 558 532 1697 565 561 539 1683 558 1689 532 1697 564 1687 534 1689 562 1684 537 -# +# name: Power type: parsed protocol: RC5 address: 03 00 00 00 command: 0C 00 00 00 -# +# name: Power type: parsed protocol: NECext address: 12 FF 00 00 command: 0E F1 00 00 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 446 1694 455 1706 453 1705 424 628 452 597 452 622 458 1673 456 625 455 594 455 1706 454 590 459 621 459 591 458 616 453 591 458 622 539 22750 459 1675 454 1733 427 1712 427 622 458 588 451 622 458 1681 458 618 451 595 454 1705 455 598 451 625 454 592 457 616 453 598 451 626 535 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 3429 3445 875 2555 868 875 867 871 871 2560 873 868 874 2551 872 874 868 871 871 869 873 870 872 865 867 2565 868 873 869 2556 867 2567 866 874 868 2560 873 870 872 2555 868 2564 869 2586 847 2577 846 2589 844 870 872 32983 3470 3430 869 2559 874 868 874 867 875 2550 873 873 869 2559 874 865 867 877 875 862 870 873 869 872 870 2555 868 877 875 2554 869 2559 874 869 873 2554 869 873 869 2562 871 2553 870 2590 843 2586 847 2581 842 876 866 -# +# name: Power type: parsed protocol: NEC address: 71 00 00 00 command: 08 00 00 00 -# +# name: Power type: parsed protocol: NEC address: 83 00 00 00 command: FF 00 00 00 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 4275 4320 519 520 519 523 516 520 519 1599 520 520 519 515 524 521 518 520 519 1595 524 1595 524 1588 520 521 518 1599 520 1591 517 1603 516 1599 520 1595 524 1594 525 1588 521 1597 522 518 521 513 526 519 520 518 521 517 522 520 519 517 522 519 520 1597 522 1589 519 1601 518 1597 522 40475 8724 2186 514 93995 8752 2183 516 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 10380 4892 599 620 592 605 597 620 592 604 598 623 589 2081 598 627 595 2080 599 2101 598 2105 574 2099 590 2088 591 2111 599 591 590 2116 594 599 593 626 596 2082 597 619 593 2086 593 626 596 594 598 627 595 598 594 2106 593 604 598 2100 589 607 595 2107 593 2079 590 2116 594 2082 750 41149 8722 2109 590 94682 8727 2104 596 -# +# name: Power type: parsed protocol: NEC address: 83 00 00 00 command: 08 00 00 00 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 4271 4324 515 1600 519 1600 519 1594 515 1603 516 1601 518 1593 516 1605 514 1601 518 520 519 522 517 520 519 522 517 523 516 518 521 523 516 522 517 1598 521 1597 522 1591 518 1600 519 521 518 516 523 522 517 521 518 520 519 523 516 520 519 522 517 1599 520 1591 518 1603 516 1599 520 40477 8753 2183 516 93993 8724 2185 515 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 7847 3931 470 1448 467 495 472 1443 472 490 467 1451 464 491 466 499 468 491 466 4413 467 1455 470 486 471 1449 466 495 472 1441 464 501 466 492 465 494 463 22093 7851 3934 467 1454 471 490 467 1446 469 496 471 1446 469 489 468 494 473 484 473 4410 470 1449 466 489 468 1455 470 489 468 1449 466 496 471 486 471 490 467 -# +# name: Power type: parsed protocol: RC5 address: 01 00 00 00 command: 0C 00 00 00 -# +# name: Mute type: parsed protocol: RC5 address: 01 00 00 00 command: 0D 00 00 00 -# +# name: Vol_up type: parsed protocol: RC5 address: 01 00 00 00 command: 10 00 00 00 -# +# name: Vol_dn type: parsed protocol: RC5 address: 01 00 00 00 command: 11 00 00 00 -# +# name: Ch_next type: parsed protocol: RC5 address: 01 00 00 00 command: 20 00 00 00 -# +# name: Ch_prev type: parsed protocol: RC5 address: 01 00 00 00 command: 21 00 00 00 -# +# # Model: VIZIO +# name: Mute type: parsed protocol: NEC address: 04 00 00 00 command: 09 00 00 00 -# +# name: Vol_up type: parsed protocol: NEC address: 04 00 00 00 command: 02 00 00 00 -# +# name: Vol_dn type: parsed protocol: NEC @@ -1681,77 +1682,117 @@ type: raw frequency: 38000 duty_cycle: 0.330000 data: 3462 1592 490 332 513 1200 489 331 514 1201 489 355 490 1201 489 356 512 1178 489 356 512 1178 512 334 487 1202 488 1202 488 357 512 1178 512 334 486 1203 487 1202 488 1203 487 1204 486 383 461 1228 488 357 488 357 487 357 487 1203 486 1204 486 359 485 1205 485 360 485 361 484 360 485 360 485 361 484 361 484 361 484 1206 484 360 484 361 484 361 484 1206 484 361 484 361 484 361 484 1206 484 361 484 1206 484 361 484 71543 3434 1620 486 359 485 1205 485 360 485 1206 484 360 485 1206 484 360 485 1206 484 360 485 1206 484 360 485 1205 485 1206 484 360 485 1206 484 360 485 1206 484 1206 484 1206 484 1206 484 361 484 1206 484 360 485 360 485 361 484 1206 484 1206 484 360 484 1206 484 360 485 361 484 361 484 360 485 361 484 361 484 361 484 1206 484 361 484 361 484 361 484 1206 484 361 484 361 484 361 484 1207 483 361 484 1206 484 361 484 71543 3435 1619 486 358 486 1204 486 359 486 1205 485 360 485 1205 485 360 485 1205 485 360 485 1205 485 360 484 1205 485 1205 485 360 485 1205 485 360 485 1205 485 1205 485 1205 485 1206 484 360 485 1205 485 360 485 360 485 360 485 1205 485 1206 484 360 485 1206 484 360 485 360 485 360 485 360 485 360 485 360 485 360 485 1206 484 360 485 360 485 360 485 1206 484 360 485 360 485 360 485 1205 485 360 485 1206 484 360 485 71542 3436 1619 486 358 487 1204 486 359 485 1205 485 360 485 1205 485 360 485 1205 485 360 485 1205 485 360 485 1205 485 1205 485 360 485 1205 485 360 485 1206 484 1206 484 1206 484 1206 484 360 485 1206 484 360 485 360 485 361 484 1206 484 1206 484 361 484 1206 484 361 484 361 484 361 484 361 484 361 484 361 484 360 485 1206 484 361 484 361 484 361 484 1206 484 361 484 361 484 360 485 1206 484 361 484 1206 484 361 484 71542 3437 1618 487 358 486 1204 486 359 486 1205 485 360 485 1205 485 360 485 1205 485 360 485 1205 485 360 485 1205 485 1206 484 360 485 1205 485 360 485 1206 484 1205 485 1206 484 1205 485 360 485 1205 485 360 485 360 485 360 485 1205 485 1205 485 360 485 1205 485 360 485 360 485 360 485 360 485 360 485 360 485 360 485 1205 485 360 485 360 485 360 485 1205 485 360 485 360 485 360 485 1205 485 360 485 1205 485 360 485 -# Model: TCL +# +# TCL +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 3977 3993 494 1994 495 1995 496 1997 494 1996 495 1004 496 1004 496 1995 496 1004 496 1997 494 1005 495 1995 495 1007 493 1006 494 1006 494 1005 495 1007 493 1997 494 1995 496 1004 496 1995 496 1005 495 1995 496 1003 497 1995 496 8467 3980 3993 494 1994 495 1996 495 1997 494 1995 496 1004 496 1006 494 1995 496 1004 496 1996 495 1004 496 1996 495 1005 495 1005 495 1004 496 1005 495 1005 495 1996 495 1995 496 1005 495 1996 495 1004 496 1995 496 1006 569 1920 571 8393 3980 3993 571 1918 572 1922 569 1920 571 1920 571 929 572 929 571 1920 571 929 571 1920 571 929 571 1921 570 930 570 929 571 929 571 929 571 928 572 1920 571 1921 570 930 571 1920 572 930 571 1921 571 929 571 1923 569 8396 3980 3994 570 1921 569 1923 569 1921 571 1920 572 929 572 930 571 1921 570 930 571 1922 570 930 571 1921 570 930 570 930 571 929 571 929 571 929 572 1922 570 1921 570 931 570 1922 570 930 571 1922 569 931 570 1921 570 -# +# name: Vol_up type: raw frequency: 38000 duty_cycle: 0.330000 data: 3979 3995 493 1994 496 1997 495 1999 492 1999 492 1006 495 1008 493 1998 493 1006 494 1997 495 1999 493 1997 494 1998 493 1006 495 1007 493 1005 495 1006 495 2025 467 1998 494 1006 494 1996 495 1006 494 1005 495 1006 494 1007 493 8468 3979 3995 492 1995 495 1999 492 1997 494 1997 494 1007 493 1006 494 1997 494 1006 494 1997 494 1996 571 1923 569 1921 570 930 570 929 571 931 569 931 575 1916 570 1920 571 930 570 1922 570 930 571 930 570 930 576 924 576 -# +# name: Vol_dn type: raw frequency: 38000 duty_cycle: 0.330000 data: 3951 3994 494 1997 493 1998 494 1998 493 1998 493 1005 496 1005 496 1996 495 1005 495 1997 495 1996 495 1996 495 1006 494 1005 495 1006 494 1005 495 1006 494 1996 495 1998 493 1005 495 1997 494 1008 492 1006 494 1006 494 1997 494 8471 3977 3996 493 1996 493 1997 494 1998 493 1997 494 1006 494 1007 493 1997 494 1009 492 1996 495 1996 495 1997 494 1006 494 1006 494 1006 494 1006 494 1006 494 1997 493 1997 494 1006 494 1996 494 1005 495 1004 495 1006 494 1996 494 -# +# name: Ch_next type: raw frequency: 38000 duty_cycle: 0.330000 data: 3978 3994 494 1995 495 1996 495 1996 495 1996 495 1006 494 1004 497 1997 494 1005 495 1997 520 1970 577 923 578 1914 577 924 576 924 576 925 575 924 576 1914 577 1915 576 924 576 1914 577 924 576 923 577 1915 576 926 574 8388 3978 3993 576 1913 576 1915 576 1915 576 1917 574 923 577 923 577 1943 548 925 575 1916 576 1915 575 924 576 1915 576 925 575 927 573 925 575 926 574 1916 574 1918 573 927 573 1918 573 928 572 927 573 1918 573 926 574 8389 4006 3966 572 1918 571 1919 572 1918 573 1920 570 929 571 929 571 1922 569 928 571 1920 571 1921 570 928 572 1919 572 929 571 929 571 929 571 929 571 1921 570 1921 521 980 569 1921 521 981 519 979 521 1971 520 979 521 -# +# name: Ch_prev type: raw frequency: 38000 duty_cycle: 0.330000 data: 3979 3994 494 1995 495 1997 494 1996 495 1998 493 1005 495 1006 494 1997 494 1005 495 1996 495 1995 496 1005 495 1005 495 1006 494 1005 495 1004 496 1005 495 1997 494 1997 494 1004 496 1996 495 1005 495 1005 495 1997 494 1996 495 8467 3976 3991 496 1995 495 1996 494 1994 496 1996 494 1005 495 1005 495 1996 495 1005 495 1995 495 1995 496 1006 494 1005 495 1006 494 1005 495 1004 496 1006 494 1994 496 1996 494 1005 495 1995 495 1004 496 1004 496 1995 495 1996 494 -# +# name: Mute type: raw frequency: 38000 duty_cycle: 0.330000 data: 3981 3992 495 1994 495 1995 496 1996 494 1996 495 1005 495 1006 494 1995 495 1997 494 1996 495 1996 494 1997 494 1996 495 1006 494 1005 495 1004 496 1005 495 1995 496 1994 496 1005 495 1004 496 1005 495 1006 494 1004 496 1006 494 8466 3978 3991 495 1994 495 1997 493 1994 496 1995 495 1004 496 1004 496 1996 494 1997 493 1996 494 1995 495 1995 495 1997 493 1004 495 1004 495 1006 494 1005 494 1998 491 1996 494 1006 494 1004 496 1006 494 1006 493 1005 495 1005 571 -# -# Thomson Remote -# Model RC3000E02 +# +# Thomson RC3000E02 +# name: Power type: parsed protocol: RCA address: 0F 00 00 00 command: 54 00 00 00 -# +# name: Vol_up type: parsed protocol: RCA address: 0F 00 00 00 command: F4 00 00 00 -# +# name: Vol_dn type: parsed protocol: RCA address: 0F 00 00 00 command: 74 00 00 00 -# +# name: Ch_next type: parsed protocol: RCA address: 0F 00 00 00 command: B4 00 00 00 -# +# name: Ch_prev type: parsed protocol: RCA address: 0F 00 00 00 command: 34 00 00 00 -# +# name: Mute type: parsed protocol: RCA address: 0F 00 00 00 command: FC 00 00 00 +# +# VOX Electronics 43ADS316B +# +name: Power +type: parsed +protocol: NEC +address: 40 00 00 00 +command: 0B 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 40 00 00 00 +command: 13 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 40 00 00 00 +command: 12 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 40 00 00 00 +command: 17 00 00 00 +# +name: Ch_next +type: parsed +protocol: NEC +address: 40 00 00 00 +command: 11 00 00 00 +# +name: Ch_prev +type: parsed +protocol: NEC +address: 40 00 00 00 +command: 10 00 00 00 From d808884b976ec0f7343230af268ff69f3acaf53e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Thu, 24 Aug 2023 01:17:39 +0900 Subject: [PATCH 721/824] FuriHal: explicitly pull display pins at early init stage, move PUPD config to early stage (#3004) Co-authored-by: hedger --- .../targets/f18/furi_hal/furi_hal_resources.c | 29 +++++++------------ .../targets/f7/furi_hal/furi_hal_resources.c | 29 +++++++------------ 2 files changed, 20 insertions(+), 38 deletions(-) diff --git a/firmware/targets/f18/furi_hal/furi_hal_resources.c b/firmware/targets/f18/furi_hal/furi_hal_resources.c index f28f98b095b..63da03e04ea 100644 --- a/firmware/targets/f18/furi_hal/furi_hal_resources.c +++ b/firmware/targets/f18/furi_hal/furi_hal_resources.c @@ -128,19 +128,22 @@ void furi_hal_resources_init_early() { furi_hal_resources_init_input_pins(GpioModeInput); + // Explicit, surviving reset, pulls + LL_PWR_EnablePUPDCfg(); + LL_PWR_EnableGPIOPullDown(LL_PWR_GPIO_A, LL_PWR_GPIO_BIT_8); // gpio_vibro + LL_PWR_EnableGPIOPullDown(LL_PWR_GPIO_B, LL_PWR_GPIO_BIT_8); // gpio_speaker + // SD Card stepdown control furi_hal_gpio_write(&gpio_periph_power, 1); furi_hal_gpio_init(&gpio_periph_power, GpioModeOutputOpenDrain, GpioPullNo, GpioSpeedLow); // Display pins - furi_hal_gpio_write(&gpio_display_rst_n, 1); + furi_hal_gpio_write(&gpio_display_rst_n, 0); furi_hal_gpio_init_simple(&gpio_display_rst_n, GpioModeOutputPushPull); - furi_hal_gpio_init(&gpio_display_di, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); - - // Pullup display reset pin for shutdown - SET_BIT(PWR->PUCRB, gpio_display_rst_n.pin); - CLEAR_BIT(PWR->PDCRB, gpio_display_rst_n.pin); - SET_BIT(PWR->CR3, PWR_CR3_APC); + LL_PWR_EnableGPIOPullUp(LL_PWR_GPIO_B, LL_PWR_GPIO_BIT_0); // gpio_display_rst_n + furi_hal_gpio_write(&gpio_display_di, 0); + furi_hal_gpio_init_simple(&gpio_display_di, GpioModeOutputPushPull); + LL_PWR_EnableGPIOPullDown(LL_PWR_GPIO_B, LL_PWR_GPIO_BIT_1); // gpio_display_di // Hard reset USB furi_hal_gpio_write(&gpio_usb_dm, 1); @@ -182,18 +185,6 @@ void furi_hal_resources_init() { // Button pins furi_hal_resources_init_input_pins(GpioModeInterruptRiseFall); - // Explicit pulls pins - LL_PWR_EnablePUPDCfg(); - LL_PWR_EnableGPIOPullDown(LL_PWR_GPIO_B, LL_PWR_GPIO_BIT_8); // gpio_speaker - LL_PWR_EnableGPIOPullDown(LL_PWR_GPIO_A, LL_PWR_GPIO_BIT_8); // gpio_vibro - - // Display pins - furi_hal_gpio_init(&gpio_display_rst_n, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); - furi_hal_gpio_write(&gpio_display_rst_n, 0); - - furi_hal_gpio_init(&gpio_display_di, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); - furi_hal_gpio_write(&gpio_display_di, 0); - // SD pins furi_hal_gpio_init(&gpio_sdcard_cd, GpioModeInput, GpioPullNo, GpioSpeedLow); furi_hal_gpio_write(&gpio_sdcard_cd, 0); diff --git a/firmware/targets/f7/furi_hal/furi_hal_resources.c b/firmware/targets/f7/furi_hal/furi_hal_resources.c index 34b26b831cf..4d52960d875 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_resources.c +++ b/firmware/targets/f7/furi_hal/furi_hal_resources.c @@ -117,19 +117,23 @@ void furi_hal_resources_init_early() { furi_hal_resources_init_input_pins(GpioModeInput); + // Explicit, surviving reset, pulls + LL_PWR_EnablePUPDCfg(); + LL_PWR_EnableGPIOPullDown(LL_PWR_GPIO_A, LL_PWR_GPIO_BIT_8); // gpio_vibro + LL_PWR_EnableGPIOPullDown(LL_PWR_GPIO_B, LL_PWR_GPIO_BIT_8); // gpio_speaker + LL_PWR_EnableGPIOPullDown(LL_PWR_GPIO_B, LL_PWR_GPIO_BIT_9); // gpio_infrared_tx + // SD Card stepdown control furi_hal_gpio_write(&gpio_periph_power, 1); furi_hal_gpio_init(&gpio_periph_power, GpioModeOutputOpenDrain, GpioPullNo, GpioSpeedLow); // Display pins - furi_hal_gpio_write(&gpio_display_rst_n, 1); + furi_hal_gpio_write(&gpio_display_rst_n, 0); furi_hal_gpio_init_simple(&gpio_display_rst_n, GpioModeOutputPushPull); + LL_PWR_EnableGPIOPullUp(LL_PWR_GPIO_B, LL_PWR_GPIO_BIT_0); // gpio_display_rst_n + furi_hal_gpio_write(&gpio_display_di, 0); furi_hal_gpio_init_simple(&gpio_display_di, GpioModeOutputPushPull); - - // Alternative pull configuration for shutdown - SET_BIT(PWR->PUCRB, DISPLAY_RST_Pin); - CLEAR_BIT(PWR->PDCRB, DISPLAY_RST_Pin); - SET_BIT(PWR->CR3, PWR_CR3_APC); + LL_PWR_EnableGPIOPullDown(LL_PWR_GPIO_B, LL_PWR_GPIO_BIT_1); // gpio_display_di // Hard reset USB furi_hal_gpio_write(&gpio_usb_dm, 1); @@ -171,19 +175,6 @@ void furi_hal_resources_init() { // Button pins furi_hal_resources_init_input_pins(GpioModeInterruptRiseFall); - // Explicit, surviving reset, pulls - LL_PWR_EnablePUPDCfg(); - LL_PWR_EnableGPIOPullDown(LL_PWR_GPIO_B, LL_PWR_GPIO_BIT_9); // gpio_infrared_tx - LL_PWR_EnableGPIOPullDown(LL_PWR_GPIO_B, LL_PWR_GPIO_BIT_8); // gpio_speaker - LL_PWR_EnableGPIOPullDown(LL_PWR_GPIO_A, LL_PWR_GPIO_BIT_8); // gpio_vibro - - // Display pins - furi_hal_gpio_init(&gpio_display_rst_n, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); - furi_hal_gpio_write(&gpio_display_rst_n, 0); - - furi_hal_gpio_init(&gpio_display_di, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); - furi_hal_gpio_write(&gpio_display_di, 0); - // SD pins furi_hal_gpio_init(&gpio_sdcard_cd, GpioModeInput, GpioPullNo, GpioSpeedLow); furi_hal_gpio_write(&gpio_sdcard_cd, 0); From b368660d48b938e213ebdf3025432cf5396a6db3 Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Wed, 23 Aug 2023 20:50:17 +0300 Subject: [PATCH 722/824] More apps moved to apps repo (#2978) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Clock, music player, snake game removed * Music player, picopass assets removed Co-authored-by: あく --- applications/ReadMe.md | 3 - applications/main/application.fam | 3 - applications/main/clock/application.fam | 10 - applications/main/clock/clock.png | Bin 1896 -> 0 bytes applications/main/clock/clock_app.c | 136 ------ .../main/music_player/application.fam | 16 - .../main/music_player/icons/music_10px.png | Bin 142 -> 0 bytes applications/main/music_player/music_player.c | 372 --------------- applications/main/snake_game/application.fam | 11 - applications/main/snake_game/snake_10px.png | Bin 158 -> 0 bytes applications/main/snake_game/snake_game.c | 434 ------------------ .../apps_data/music_player/Marble_Machine.fmf | 6 - .../picopass/assets/iclass_elite_dict.txt | 41 -- .../picopass/assets/iclass_standard_dict.txt | 47 -- 14 files changed, 1079 deletions(-) delete mode 100644 applications/main/clock/application.fam delete mode 100644 applications/main/clock/clock.png delete mode 100644 applications/main/clock/clock_app.c delete mode 100644 applications/main/music_player/application.fam delete mode 100644 applications/main/music_player/icons/music_10px.png delete mode 100644 applications/main/music_player/music_player.c delete mode 100644 applications/main/snake_game/application.fam delete mode 100644 applications/main/snake_game/snake_10px.png delete mode 100644 applications/main/snake_game/snake_game.c delete mode 100644 assets/resources/apps_data/music_player/Marble_Machine.fmf delete mode 100644 assets/resources/apps_data/picopass/assets/iclass_elite_dict.txt delete mode 100644 assets/resources/apps_data/picopass/assets/iclass_standard_dict.txt diff --git a/applications/ReadMe.md b/applications/ReadMe.md index 91ef02bbb6e..de465832aef 100644 --- a/applications/ReadMe.md +++ b/applications/ReadMe.md @@ -33,9 +33,6 @@ Applications for main Flipper menu. - `nfc` - NFC application, HF rfid, EMV and etc - `subghz` - SubGhz application, 433 fobs and etc - `u2f` - U2F Application -- `clock` - Clock application -- `music_player` - Music player app (demo) -- `snake_game` - Snake game application ## services diff --git a/applications/main/application.fam b/applications/main/application.fam index e3ceac34d10..0a90ee2243f 100644 --- a/applications/main/application.fam +++ b/applications/main/application.fam @@ -11,9 +11,6 @@ App( "subghz", "bad_usb", "u2f", - "clock", - "music_player", - "snake_game", "archive", "main_apps_on_start", ], diff --git a/applications/main/clock/application.fam b/applications/main/clock/application.fam deleted file mode 100644 index a6a2eff3e23..00000000000 --- a/applications/main/clock/application.fam +++ /dev/null @@ -1,10 +0,0 @@ -App( - appid="clock", - name="Clock", - apptype=FlipperAppType.EXTERNAL, - entry_point="clock_app", - requires=["gui"], - stack_size=2 * 1024, - fap_icon="clock.png", - fap_category="Tools", -) diff --git a/applications/main/clock/clock.png b/applications/main/clock/clock.png deleted file mode 100644 index 0d96df1020317ef6df0652bc6a0b0b5c1190fa59..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1896 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V6Od#IhXQ2>tmIipR1RclAn~S zSCLx)lxJYDv9BmdOwLX%QAkQn&&;z`dcS+Wl0s&Rtx~wDuYqrYb81GWM^#a3aFt(3 za#eP+Wr~u$9hXgo70`g()RIJnirk#MVyg;UC9t_xKsHENUr7P1q$Jx`DZ)2E!8yMu zRl!uxRL?-kj!VI&C?(A*$i)q+8OXC$$|xx*u+rBrFE7_CH`dE9O4m2Ew6xSWFw!?N z(gmu}Ew0QfNvzP#D^>;>0WrfRwK%ybv!En1KTiQQ?NYItfzCc^Z* zVyO3l0ih3)(KpmH&_`BYkda@KU!0L&0Cy3J9=J4y#*)l59QJ@@Fq8v>54#N&i3Qjc z`}*Qno|}u}jp7p5GGIVJ0~N&!Fbj%9DhpEegHnt0ON)|IUCUDQN|eDN0SXr@=lq=f zqF`XsNVQcmLXVw6UXlT~93c^&nSw43@?cIW zD20UPWdei52y8D{O9VpBR>|B*AIX|XtWv;8v+@O|?v%umM3=-8pi7MmfT`2aNY}_9 z#K6?b#K_9fRNKJP$^a57VDum{2EA0%6xpH@#=h%wQUZYC^la6WZH#5$!TUq(|>phQ6 zq1(EIr1y*cVa=JkHOR$l+9owKpYxT*!OAm>t-sg2`+l!@*Y}tWuA95EuKaX7`}C`a z#gln+XP2ArNjTowEw?MVYay?{uW!IryZ0+MK3aQzRp{S4`>)OMkm9;x!o{ySYo55A z@Vtn#*<$ZGc2DuqQLIsWbM=Vq<%rjUAd-T>6dHXx3uMISzG$D99VU67I;J!Gca%qgD@k*tT_@u zK^IRK#}J9Bt^J;S35`&tSQ2 tcwMJ+VNB(c2WNkm|NQ;lbiu<-j5A!_Z&kb&`3|ZeJzf1=);T3K0RUC#hDiVb diff --git a/applications/main/clock/clock_app.c b/applications/main/clock/clock_app.c deleted file mode 100644 index c938125b378..00000000000 --- a/applications/main/clock/clock_app.c +++ /dev/null @@ -1,136 +0,0 @@ -#include -#include - -#include -#include - -typedef enum { - ClockEventTypeTick, - ClockEventTypeKey, -} ClockEventType; - -typedef struct { - ClockEventType type; - InputEvent input; -} ClockEvent; - -typedef struct { - FuriString* buffer; - FuriHalRtcDateTime datetime; - LocaleTimeFormat timeformat; - LocaleDateFormat dateformat; -} ClockData; - -typedef struct { - FuriMutex* mutex; - FuriMessageQueue* queue; - ClockData* data; -} Clock; - -static void clock_input_callback(InputEvent* input_event, FuriMessageQueue* queue) { - furi_assert(queue); - ClockEvent event = {.type = ClockEventTypeKey, .input = *input_event}; - furi_message_queue_put(queue, &event, FuriWaitForever); -} - -static void clock_render_callback(Canvas* canvas, void* ctx) { - Clock* clock = ctx; - if(furi_mutex_acquire(clock->mutex, 200) != FuriStatusOk) { - return; - } - - ClockData* data = clock->data; - - canvas_set_font(canvas, FontBigNumbers); - locale_format_time(data->buffer, &data->datetime, data->timeformat, true); - canvas_draw_str_aligned( - canvas, 64, 28, AlignCenter, AlignCenter, furi_string_get_cstr(data->buffer)); - - // Special case to cover missing glyphs in FontBigNumbers - if(data->timeformat == LocaleTimeFormat12h) { - size_t time_width = canvas_string_width(canvas, furi_string_get_cstr(data->buffer)); - canvas_set_font(canvas, FontPrimary); - canvas_draw_str_aligned( - canvas, - 64 + (time_width / 2) - 10, - 31, - AlignLeft, - AlignCenter, - (data->datetime.hour > 11) ? "PM" : "AM"); - } - - canvas_set_font(canvas, FontSecondary); - locale_format_date(data->buffer, &data->datetime, data->dateformat, "/"); - canvas_draw_str_aligned( - canvas, 64, 42, AlignCenter, AlignTop, furi_string_get_cstr(data->buffer)); - - furi_mutex_release(clock->mutex); -} - -static void clock_tick(void* ctx) { - furi_assert(ctx); - FuriMessageQueue* queue = ctx; - ClockEvent event = {.type = ClockEventTypeTick}; - // It's OK to loose this event if system overloaded - furi_message_queue_put(queue, &event, 0); -} - -int32_t clock_app(void* p) { - UNUSED(p); - Clock* clock = malloc(sizeof(Clock)); - clock->data = malloc(sizeof(ClockData)); - clock->data->buffer = furi_string_alloc(); - - clock->queue = furi_message_queue_alloc(8, sizeof(ClockEvent)); - clock->mutex = furi_mutex_alloc(FuriMutexTypeNormal); - - furi_hal_rtc_get_datetime(&clock->data->datetime); - clock->data->timeformat = locale_get_time_format(); - clock->data->dateformat = locale_get_date_format(); - - // Set ViewPort callbacks - ViewPort* view_port = view_port_alloc(); - view_port_draw_callback_set(view_port, clock_render_callback, clock); - view_port_input_callback_set(view_port, clock_input_callback, clock->queue); - - FuriTimer* timer = furi_timer_alloc(clock_tick, FuriTimerTypePeriodic, clock->queue); - - // Open GUI and register view_port - Gui* gui = furi_record_open(RECORD_GUI); - gui_add_view_port(gui, view_port, GuiLayerFullscreen); - - furi_timer_start(timer, 100); - - // Main loop - ClockEvent event; - for(bool processing = true; processing;) { - furi_check(furi_message_queue_get(clock->queue, &event, FuriWaitForever) == FuriStatusOk); - furi_mutex_acquire(clock->mutex, FuriWaitForever); - if(event.type == ClockEventTypeKey) { - if(event.input.type == InputTypeShort && event.input.key == InputKeyBack) { - processing = false; - } - } else if(event.type == ClockEventTypeTick) { - furi_hal_rtc_get_datetime(&clock->data->datetime); - } - - furi_mutex_release(clock->mutex); - view_port_update(view_port); - } - - furi_timer_free(timer); - view_port_enabled_set(view_port, false); - gui_remove_view_port(gui, view_port); - view_port_free(view_port); - furi_record_close(RECORD_GUI); - - furi_message_queue_free(clock->queue); - furi_mutex_free(clock->mutex); - - furi_string_free(clock->data->buffer); - - free(clock->data); - free(clock); - - return 0; -} diff --git a/applications/main/music_player/application.fam b/applications/main/music_player/application.fam deleted file mode 100644 index c9cd5e44de7..00000000000 --- a/applications/main/music_player/application.fam +++ /dev/null @@ -1,16 +0,0 @@ -App( - appid="music_player", - name="Music Player", - apptype=FlipperAppType.EXTERNAL, - entry_point="music_player_app", - requires=[ - "gui", - "dialogs", - ], - stack_size=2 * 1024, - order=20, - fap_icon="icons/music_10px.png", - fap_category="Media", - fap_icon_assets="icons", - fap_libs=["music_worker"], -) diff --git a/applications/main/music_player/icons/music_10px.png b/applications/main/music_player/icons/music_10px.png deleted file mode 100644 index d41eb0db8c822c60be6c097393b3682680b81a6c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 142 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2xGmzZ=C-xtZVhivIasA%~WHIdey2}7aaTa() z7Bet#3xhBt!>l_x9Asc&(0a_=e)W&n8K5!-Pgg&ebxsLQ0Ao%f>i_@% diff --git a/applications/main/music_player/music_player.c b/applications/main/music_player/music_player.c deleted file mode 100644 index 8b0b758c1c1..00000000000 --- a/applications/main/music_player/music_player.c +++ /dev/null @@ -1,372 +0,0 @@ -#include - -#include -#include - -#include -#include -#include -#include - -#define TAG "MusicPlayer" - -#define MUSIC_PLAYER_APP_EXTENSION "*" - -#define MUSIC_PLAYER_SEMITONE_HISTORY_SIZE 4 - -typedef struct { - uint8_t semitone_history[MUSIC_PLAYER_SEMITONE_HISTORY_SIZE]; - uint8_t duration_history[MUSIC_PLAYER_SEMITONE_HISTORY_SIZE]; - - uint8_t volume; - uint8_t semitone; - uint8_t dots; - uint8_t duration; - float position; -} MusicPlayerModel; - -typedef struct { - MusicPlayerModel* model; - FuriMutex** model_mutex; - - FuriMessageQueue* input_queue; - - ViewPort* view_port; - Gui* gui; - - MusicWorker* worker; -} MusicPlayer; - -static const float MUSIC_PLAYER_VOLUMES[] = {0, .25, .5, .75, 1}; - -static const char* semitone_to_note(int8_t semitone) { - switch(semitone) { - case 0: - return "C"; - case 1: - return "C#"; - case 2: - return "D"; - case 3: - return "D#"; - case 4: - return "E"; - case 5: - return "F"; - case 6: - return "F#"; - case 7: - return "G"; - case 8: - return "G#"; - case 9: - return "A"; - case 10: - return "A#"; - case 11: - return "B"; - default: - return "--"; - } -} - -static bool is_white_note(uint8_t semitone, uint8_t id) { - switch(semitone) { - case 0: - if(id == 0) return true; - break; - case 2: - if(id == 1) return true; - break; - case 4: - if(id == 2) return true; - break; - case 5: - if(id == 3) return true; - break; - case 7: - if(id == 4) return true; - break; - case 9: - if(id == 5) return true; - break; - case 11: - if(id == 6) return true; - break; - default: - break; - } - - return false; -} - -static bool is_black_note(uint8_t semitone, uint8_t id) { - switch(semitone) { - case 1: - if(id == 0) return true; - break; - case 3: - if(id == 1) return true; - break; - case 6: - if(id == 3) return true; - break; - case 8: - if(id == 4) return true; - break; - case 10: - if(id == 5) return true; - break; - default: - break; - } - - return false; -} - -static void render_callback(Canvas* canvas, void* ctx) { - MusicPlayer* music_player = ctx; - furi_check(furi_mutex_acquire(music_player->model_mutex, FuriWaitForever) == FuriStatusOk); - - canvas_clear(canvas); - canvas_set_color(canvas, ColorBlack); - canvas_set_font(canvas, FontPrimary); - canvas_draw_str(canvas, 0, 12, "MusicPlayer"); - - uint8_t x_pos = 0; - uint8_t y_pos = 24; - const uint8_t white_w = 10; - const uint8_t white_h = 40; - - const int8_t black_x = 6; - const int8_t black_y = -5; - const uint8_t black_w = 8; - const uint8_t black_h = 32; - - // white keys - for(size_t i = 0; i < 7; i++) { - if(is_white_note(music_player->model->semitone, i)) { - canvas_draw_box(canvas, x_pos + white_w * i, y_pos, white_w + 1, white_h); - } else { - canvas_draw_frame(canvas, x_pos + white_w * i, y_pos, white_w + 1, white_h); - } - } - - // black keys - for(size_t i = 0; i < 7; i++) { - if(i != 2 && i != 6) { - canvas_set_color(canvas, ColorWhite); - canvas_draw_box( - canvas, x_pos + white_w * i + black_x, y_pos + black_y, black_w + 1, black_h); - canvas_set_color(canvas, ColorBlack); - if(is_black_note(music_player->model->semitone, i)) { - canvas_draw_box( - canvas, x_pos + white_w * i + black_x, y_pos + black_y, black_w + 1, black_h); - } else { - canvas_draw_frame( - canvas, x_pos + white_w * i + black_x, y_pos + black_y, black_w + 1, black_h); - } - } - } - - // volume view_port - x_pos = 124; - y_pos = 0; - const uint8_t volume_h = - (64 / (COUNT_OF(MUSIC_PLAYER_VOLUMES) - 1)) * music_player->model->volume; - canvas_draw_frame(canvas, x_pos, y_pos, 4, 64); - canvas_draw_box(canvas, x_pos, y_pos + (64 - volume_h), 4, volume_h); - - // note stack view_port - x_pos = 73; - y_pos = 0; //-V1048 - canvas_set_color(canvas, ColorBlack); - canvas_set_font(canvas, FontPrimary); - canvas_draw_frame(canvas, x_pos, y_pos, 49, 64); - canvas_draw_line(canvas, x_pos + 28, 0, x_pos + 28, 64); - - char duration_text[16]; - for(uint8_t i = 0; i < MUSIC_PLAYER_SEMITONE_HISTORY_SIZE; i++) { - if(music_player->model->duration_history[i] == 0xFF) { - snprintf(duration_text, 15, "--"); - } else { - snprintf(duration_text, 15, "%d", music_player->model->duration_history[i]); - } - - if(i == 0) { - canvas_draw_box(canvas, x_pos, y_pos + 48, 49, 16); - canvas_set_color(canvas, ColorWhite); - } else { - canvas_set_color(canvas, ColorBlack); - } - canvas_draw_str( - canvas, - x_pos + 4, - 64 - 16 * i - 3, - semitone_to_note(music_player->model->semitone_history[i])); - canvas_draw_str(canvas, x_pos + 31, 64 - 16 * i - 3, duration_text); - canvas_draw_line(canvas, x_pos, 64 - 16 * i, x_pos + 48, 64 - 16 * i); - } - - furi_mutex_release(music_player->model_mutex); -} - -static void input_callback(InputEvent* input_event, void* ctx) { - MusicPlayer* music_player = ctx; - if(input_event->type == InputTypeShort) { - furi_message_queue_put(music_player->input_queue, input_event, 0); - } -} - -static void music_worker_callback( - uint8_t semitone, - uint8_t dots, - uint8_t duration, - float position, - void* context) { - MusicPlayer* music_player = context; - furi_check(furi_mutex_acquire(music_player->model_mutex, FuriWaitForever) == FuriStatusOk); - - for(size_t i = 0; i < MUSIC_PLAYER_SEMITONE_HISTORY_SIZE - 1; i++) { - size_t r = MUSIC_PLAYER_SEMITONE_HISTORY_SIZE - 1 - i; - music_player->model->duration_history[r] = music_player->model->duration_history[r - 1]; - music_player->model->semitone_history[r] = music_player->model->semitone_history[r - 1]; - } - - semitone = (semitone == 0xFF) ? 0xFF : semitone % 12; - - music_player->model->semitone = semitone; - music_player->model->dots = dots; - music_player->model->duration = duration; - music_player->model->position = position; - - music_player->model->semitone_history[0] = semitone; - music_player->model->duration_history[0] = duration; - - furi_mutex_release(music_player->model_mutex); - view_port_update(music_player->view_port); -} - -void music_player_clear(MusicPlayer* instance) { - memset(instance->model->duration_history, 0xff, MUSIC_PLAYER_SEMITONE_HISTORY_SIZE); - memset(instance->model->semitone_history, 0xff, MUSIC_PLAYER_SEMITONE_HISTORY_SIZE); - music_worker_clear(instance->worker); -} - -MusicPlayer* music_player_alloc() { - MusicPlayer* instance = malloc(sizeof(MusicPlayer)); - - instance->model = malloc(sizeof(MusicPlayerModel)); - instance->model->volume = 3; - - instance->model_mutex = furi_mutex_alloc(FuriMutexTypeNormal); - - instance->input_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); - - instance->worker = music_worker_alloc(); - music_worker_set_volume(instance->worker, MUSIC_PLAYER_VOLUMES[instance->model->volume]); - music_worker_set_callback(instance->worker, music_worker_callback, instance); - - music_player_clear(instance); - - instance->view_port = view_port_alloc(); - view_port_draw_callback_set(instance->view_port, render_callback, instance); - view_port_input_callback_set(instance->view_port, input_callback, instance); - - // Open GUI and register view_port - instance->gui = furi_record_open(RECORD_GUI); - gui_add_view_port(instance->gui, instance->view_port, GuiLayerFullscreen); - - return instance; -} - -void music_player_free(MusicPlayer* instance) { - gui_remove_view_port(instance->gui, instance->view_port); - furi_record_close(RECORD_GUI); - view_port_free(instance->view_port); - - music_worker_free(instance->worker); - - furi_message_queue_free(instance->input_queue); - - furi_mutex_free(instance->model_mutex); - - free(instance->model); - free(instance); -} - -int32_t music_player_app(void* p) { - MusicPlayer* music_player = music_player_alloc(); - - FuriString* file_path; - file_path = furi_string_alloc(); - - do { - if(p && strlen(p)) { - furi_string_set(file_path, (const char*)p); - } else { - Storage* storage = furi_record_open(RECORD_STORAGE); - storage_common_migrate( - storage, EXT_PATH("music_player"), STORAGE_APP_DATA_PATH_PREFIX); - furi_record_close(RECORD_STORAGE); - - furi_string_set(file_path, STORAGE_APP_DATA_PATH_PREFIX); - - DialogsFileBrowserOptions browser_options; - dialog_file_browser_set_basic_options( - &browser_options, MUSIC_PLAYER_APP_EXTENSION, &I_music_10px); - browser_options.hide_ext = false; - browser_options.base_path = STORAGE_APP_DATA_PATH_PREFIX; - - DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); - bool res = dialog_file_browser_show(dialogs, file_path, file_path, &browser_options); - - furi_record_close(RECORD_DIALOGS); - - if(!res) { - FURI_LOG_E(TAG, "No file selected"); - break; - } - } - - if(!music_worker_load(music_player->worker, furi_string_get_cstr(file_path))) { - FURI_LOG_E(TAG, "Unable to load file"); - break; - } - - music_worker_start(music_player->worker); - - InputEvent input; - while(furi_message_queue_get(music_player->input_queue, &input, FuriWaitForever) == - FuriStatusOk) { - furi_check( - furi_mutex_acquire(music_player->model_mutex, FuriWaitForever) == FuriStatusOk); - - if(input.key == InputKeyBack) { - furi_mutex_release(music_player->model_mutex); - break; - } else if(input.key == InputKeyUp) { - if(music_player->model->volume < COUNT_OF(MUSIC_PLAYER_VOLUMES) - 1) - music_player->model->volume++; - music_worker_set_volume( - music_player->worker, MUSIC_PLAYER_VOLUMES[music_player->model->volume]); - } else if(input.key == InputKeyDown) { - if(music_player->model->volume > 0) music_player->model->volume--; - music_worker_set_volume( - music_player->worker, MUSIC_PLAYER_VOLUMES[music_player->model->volume]); - } - - furi_mutex_release(music_player->model_mutex); - view_port_update(music_player->view_port); - } - - music_worker_stop(music_player->worker); - if(p && strlen(p)) break; // Exit instead of going to browser if launched with arg - music_player_clear(music_player); - } while(1); - - furi_string_free(file_path); - music_player_free(music_player); - - return 0; -} diff --git a/applications/main/snake_game/application.fam b/applications/main/snake_game/application.fam deleted file mode 100644 index c736a4ddcf4..00000000000 --- a/applications/main/snake_game/application.fam +++ /dev/null @@ -1,11 +0,0 @@ -App( - appid="snake_game", - name="Snake Game", - apptype=FlipperAppType.EXTERNAL, - entry_point="snake_game_app", - requires=["gui"], - stack_size=1 * 1024, - order=30, - fap_icon="snake_10px.png", - fap_category="Games", -) diff --git a/applications/main/snake_game/snake_10px.png b/applications/main/snake_game/snake_10px.png deleted file mode 100644 index 52d9fa7e0e1b884774a6e58abb1965b1b1905767..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 158 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2VGmzZ%#=aj&u?6^qxc>kDAIJlX?eOh zhE&W+PDn^dVNp_G*rbrd!ONE5$kL$2+HH6!MA=j6#)(e`V#>-4Tp0`o%bUNP1L~43 zag8Vm&QB{TPb^AhaL6gmODsst%q!6^$V=Bv&QD2A{^~3#2UN)5>FVdQ&MBb@0GSOf APyhe` diff --git a/applications/main/snake_game/snake_game.c b/applications/main/snake_game/snake_game.c deleted file mode 100644 index 6852cb215b2..00000000000 --- a/applications/main/snake_game/snake_game.c +++ /dev/null @@ -1,434 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - -typedef struct { - // +-----x - // | - // | - // y - uint8_t x; - uint8_t y; -} Point; - -typedef enum { - GameStateLife, - - // https://melmagazine.com/en-us/story/snake-nokia-6110-oral-history-taneli-armanto - // Armanto: While testing the early versions of the game, I noticed it was hard - // to control the snake upon getting close to and edge but not crashing — especially - // in the highest speed levels. I wanted the highest level to be as fast as I could - // possibly make the device "run," but on the other hand, I wanted to be friendly - // and help the player manage that level. Otherwise it might not be fun to play. So - // I implemented a little delay. A few milliseconds of extra time right before - // the player crashes, during which she can still change the directions. And if - // she does, the game continues. - GameStateLastChance, - - GameStateGameOver, -} GameState; - -// Note: do not change without purpose. Current values are used in smart -// orthogonality calculation in `snake_game_get_turn_snake`. -typedef enum { - DirectionUp, - DirectionRight, - DirectionDown, - DirectionLeft, -} Direction; - -#define MAX_SNAKE_LEN 253 - -typedef struct { - Point points[MAX_SNAKE_LEN]; - uint16_t len; - Direction currentMovement; - Direction nextMovement; // if backward of currentMovement, ignore - Point fruit; - GameState state; - FuriMutex* mutex; -} SnakeState; - -typedef enum { - EventTypeTick, - EventTypeKey, -} EventType; - -typedef struct { - EventType type; - InputEvent input; -} SnakeEvent; - -const NotificationSequence sequence_fail = { - &message_vibro_on, - - &message_note_ds4, - &message_delay_10, - &message_sound_off, - &message_delay_10, - - &message_note_ds4, - &message_delay_10, - &message_sound_off, - &message_delay_10, - - &message_note_ds4, - &message_delay_10, - &message_sound_off, - &message_delay_10, - - &message_vibro_off, - NULL, -}; - -const NotificationSequence sequence_eat = { - &message_note_c7, - &message_delay_50, - &message_sound_off, - NULL, -}; - -static void snake_game_render_callback(Canvas* const canvas, void* ctx) { - furi_assert(ctx); - const SnakeState* snake_state = ctx; - - furi_mutex_acquire(snake_state->mutex, FuriWaitForever); - - // Frame - canvas_draw_frame(canvas, 0, 0, 128, 64); - - // Fruit - Point f = snake_state->fruit; - f.x = f.x * 4 + 1; - f.y = f.y * 4 + 1; - canvas_draw_rframe(canvas, f.x, f.y, 6, 6, 2); - - // Snake - for(uint16_t i = 0; i < snake_state->len; i++) { - Point p = snake_state->points[i]; - p.x = p.x * 4 + 2; - p.y = p.y * 4 + 2; - canvas_draw_box(canvas, p.x, p.y, 4, 4); - } - - // Game Over banner - if(snake_state->state == GameStateGameOver) { - // Screen is 128x64 px - canvas_set_color(canvas, ColorWhite); - canvas_draw_box(canvas, 34, 20, 62, 24); - - canvas_set_color(canvas, ColorBlack); - canvas_draw_frame(canvas, 34, 20, 62, 24); - - canvas_set_font(canvas, FontPrimary); - canvas_draw_str(canvas, 37, 31, "Game Over"); - - canvas_set_font(canvas, FontSecondary); - char buffer[12]; - snprintf(buffer, sizeof(buffer), "Score: %u", snake_state->len - 7U); - canvas_draw_str_aligned(canvas, 64, 41, AlignCenter, AlignBottom, buffer); - } - - furi_mutex_release(snake_state->mutex); -} - -static void snake_game_input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) { - furi_assert(event_queue); - - SnakeEvent event = {.type = EventTypeKey, .input = *input_event}; - furi_message_queue_put(event_queue, &event, FuriWaitForever); -} - -static void snake_game_update_timer_callback(FuriMessageQueue* event_queue) { - furi_assert(event_queue); - - SnakeEvent event = {.type = EventTypeTick}; - furi_message_queue_put(event_queue, &event, 0); -} - -static void snake_game_init_game(SnakeState* const snake_state) { - Point p[] = {{8, 6}, {7, 6}, {6, 6}, {5, 6}, {4, 6}, {3, 6}, {2, 6}}; - memcpy(snake_state->points, p, sizeof(p)); //-V1086 - - snake_state->len = 7; - - snake_state->currentMovement = DirectionRight; - - snake_state->nextMovement = DirectionRight; - - Point f = {18, 6}; - snake_state->fruit = f; - - snake_state->state = GameStateLife; -} - -static Point snake_game_get_new_fruit(SnakeState const* const snake_state) { - // 1 bit for each point on the playing field where the snake can turn - // and where the fruit can appear - uint16_t buffer[8]; - memset(buffer, 0, sizeof(buffer)); - uint8_t empty = 8 * 16; - - for(uint16_t i = 0; i < snake_state->len; i++) { - Point p = snake_state->points[i]; - - if(p.x % 2 != 0 || p.y % 2 != 0) { - continue; - } - p.x /= 2; - p.y /= 2; - - buffer[p.y] |= 1 << p.x; - empty--; - } - // Bit set if snake use that playing field - - uint16_t newFruit = rand() % empty; - - // Skip random number of _empty_ fields - for(uint8_t y = 0; y < 8; y++) { - for(uint16_t x = 0, mask = 1; x < 16; x += 1, mask <<= 1) { - if((buffer[y] & mask) == 0) { - if(newFruit == 0) { - Point p = { - .x = x * 2, - .y = y * 2, - }; - return p; - } - newFruit--; - } - } - } - // We will never be here - Point p = {0, 0}; - return p; -} - -static bool snake_game_collision_with_frame(Point const next_step) { - // if x == 0 && currentMovement == left then x - 1 == 255 , - // so check only x > right border - return next_step.x > 30 || next_step.y > 14; -} - -static bool - snake_game_collision_with_tail(SnakeState const* const snake_state, Point const next_step) { - for(uint16_t i = 0; i < snake_state->len; i++) { - Point p = snake_state->points[i]; - if(p.x == next_step.x && p.y == next_step.y) { - return true; - } - } - - return false; -} - -static Direction snake_game_get_turn_snake(SnakeState const* const snake_state) { - // Sum of two `Direction` lies between 0 and 6, odd values indicate orthogonality. - bool is_orthogonal = (snake_state->currentMovement + snake_state->nextMovement) % 2 == 1; - return is_orthogonal ? snake_state->nextMovement : snake_state->currentMovement; -} - -static Point snake_game_get_next_step(SnakeState const* const snake_state) { - Point next_step = snake_state->points[0]; - switch(snake_state->currentMovement) { - // +-----x - // | - // | - // y - case DirectionUp: - next_step.y--; - break; - case DirectionRight: - next_step.x++; - break; - case DirectionDown: - next_step.y++; - break; - case DirectionLeft: - next_step.x--; - break; - } - return next_step; -} - -static void snake_game_move_snake(SnakeState* const snake_state, Point const next_step) { - memmove(snake_state->points + 1, snake_state->points, snake_state->len * sizeof(Point)); - snake_state->points[0] = next_step; -} - -static void - snake_game_process_game_step(SnakeState* const snake_state, NotificationApp* notification) { - if(snake_state->state == GameStateGameOver) { - return; - } - - bool can_turn = (snake_state->points[0].x % 2 == 0) && (snake_state->points[0].y % 2 == 0); - if(can_turn) { - snake_state->currentMovement = snake_game_get_turn_snake(snake_state); - } - - Point next_step = snake_game_get_next_step(snake_state); - - bool crush = snake_game_collision_with_frame(next_step); - if(crush) { - if(snake_state->state == GameStateLife) { - snake_state->state = GameStateLastChance; - return; - } else if(snake_state->state == GameStateLastChance) { - snake_state->state = GameStateGameOver; - notification_message_block(notification, &sequence_fail); - return; - } - } else { - if(snake_state->state == GameStateLastChance) { - snake_state->state = GameStateLife; - } - } - - crush = snake_game_collision_with_tail(snake_state, next_step); - if(crush) { - snake_state->state = GameStateGameOver; - notification_message_block(notification, &sequence_fail); - return; - } - - bool eatFruit = (next_step.x == snake_state->fruit.x) && (next_step.y == snake_state->fruit.y); - if(eatFruit) { - snake_state->len++; - if(snake_state->len >= MAX_SNAKE_LEN) { - snake_state->state = GameStateGameOver; - notification_message_block(notification, &sequence_fail); - return; - } - } - - snake_game_move_snake(snake_state, next_step); - - if(eatFruit) { - snake_state->fruit = snake_game_get_new_fruit(snake_state); - notification_message(notification, &sequence_eat); - } -} - -int32_t snake_game_app(void* p) { - UNUSED(p); - - FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(SnakeEvent)); - - SnakeState* snake_state = malloc(sizeof(SnakeState)); - snake_game_init_game(snake_state); - - snake_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal); - - if(!snake_state->mutex) { - FURI_LOG_E("SnakeGame", "cannot create mutex\r\n"); - free(snake_state); - return 255; - } - - ViewPort* view_port = view_port_alloc(); - view_port_draw_callback_set(view_port, snake_game_render_callback, snake_state); - view_port_input_callback_set(view_port, snake_game_input_callback, event_queue); - - FuriTimer* timer = - furi_timer_alloc(snake_game_update_timer_callback, FuriTimerTypePeriodic, event_queue); - furi_timer_start(timer, furi_kernel_get_tick_frequency() / 4); - - // Open GUI and register view_port - Gui* gui = furi_record_open(RECORD_GUI); - gui_add_view_port(gui, view_port, GuiLayerFullscreen); - NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); - - notification_message_block(notification, &sequence_display_backlight_enforce_on); - - dolphin_deed(DolphinDeedPluginGameStart); - - SnakeEvent event; - for(bool processing = true; processing;) { - FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100); - - furi_mutex_acquire(snake_state->mutex, FuriWaitForever); - - if(event_status == FuriStatusOk) { - // press events - if(event.type == EventTypeKey) { - if(event.input.type == InputTypePress) { - switch(event.input.key) { - case InputKeyUp: - snake_state->nextMovement = DirectionUp; - break; - case InputKeyDown: - snake_state->nextMovement = DirectionDown; - break; - case InputKeyRight: - snake_state->nextMovement = DirectionRight; - break; - case InputKeyLeft: - snake_state->nextMovement = DirectionLeft; - break; - case InputKeyOk: - if(snake_state->state == GameStateGameOver) { - snake_game_init_game(snake_state); - } - break; - case InputKeyBack: - processing = false; - break; - default: - break; - } - } - } else if(event.type == EventTypeTick) { - snake_game_process_game_step(snake_state, notification); - } - } else { - // event timeout - } - - view_port_update(view_port); - furi_mutex_release(snake_state->mutex); - } - - // Return backlight to normal state - notification_message(notification, &sequence_display_backlight_enforce_auto); - - furi_timer_free(timer); - view_port_enabled_set(view_port, false); - gui_remove_view_port(gui, view_port); - furi_record_close(RECORD_GUI); - furi_record_close(RECORD_NOTIFICATION); - view_port_free(view_port); - furi_message_queue_free(event_queue); - furi_mutex_free(snake_state->mutex); - free(snake_state); - - return 0; -} - -// Screen is 128x64 px -// (4 + 4) * 16 - 4 + 2 + 2border == 128 -// (4 + 4) * 8 - 4 + 2 + 2border == 64 -// Game field from point{x: 0, y: 0} to point{x: 30, y: 14}. -// The snake turns only in even cells - intersections. -// ┌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┐ -// ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎ -// ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎ -// ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎ -// ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎ -// ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎ -// ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎ -// ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎ -// ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎ -// ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎ -// ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎ -// ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎ -// ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎ -// ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎ -// ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎ -// ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎ -// └╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘ diff --git a/assets/resources/apps_data/music_player/Marble_Machine.fmf b/assets/resources/apps_data/music_player/Marble_Machine.fmf deleted file mode 100644 index 7403c9a0f15..00000000000 --- a/assets/resources/apps_data/music_player/Marble_Machine.fmf +++ /dev/null @@ -1,6 +0,0 @@ -Filetype: Flipper Music Format -Version: 0 -BPM: 130 -Duration: 8 -Octave: 5 -Notes: E6, P, E, B, 4P, E, A, G, A, E, B, P, G, A, D6, 4P, D, B, 4P, D, A, G, A, D, F#, P, G, A, D6, 4P, F#, B, 4P, F#, D6, C6, B, F#, A, P, G, F#, E, P, C, E, B, B4, C, D, D6, C6, B, F#, A, P, G, A, E6, 4P, E, B, 4P, E, A, G, A, E, B, P, G, A, D6, 4P, D, B, 4P, D, A, G, A, D, F#, P, G, A, D6, 4P, F#, B, 4P, F#, D6, C6, B, F#, A, P, G, F#, E, P, C, E, B, B4, C, D, D6, C6, B, F#, A, P, G, A, E6 diff --git a/assets/resources/apps_data/picopass/assets/iclass_elite_dict.txt b/assets/resources/apps_data/picopass/assets/iclass_elite_dict.txt deleted file mode 100644 index 908889aecf6..00000000000 --- a/assets/resources/apps_data/picopass/assets/iclass_elite_dict.txt +++ /dev/null @@ -1,41 +0,0 @@ - -## From https://github.com/RfidResearchGroup/proxmark3/blob/master/client/dictionaries/iclass_default_keys.dic - -# key1/Kc from PicoPass 2k documentation -7665544332211000 -# SAGEM -0123456789ABCDEF -# PicoPass Default Exchange Key -5CBCF1DA45D5FB4F -# From HID multiclassSE reader -31ad7ebd2f282168 -# From pastebin: https://pastebin.com/uHqpjiuU -6EFD46EFCBB3C875 -E033CA419AEE43F9 - -# default picopass KD / Page 0 / Book 1 -FDCB5A52EA8F3090 -237FF9079863DF44 -5ADC25FB27181D32 -83B881F2936B2E49 -43644E61EE866BA5 -897034143D016080 -82D17B44C0122963 -4895CA7DE65E2025 -DADAD4C57BE271B7 -E41E9EDEF5719ABF -293D275EC3AF9C7F -C3C169251B8A70FB -F41DAF58B20C8B91 -28877A609EC0DD2B -66584C91EE80D5E5 -C1B74D7478053AE2 - -# default iCLASS RFIDeas -6B65797374726B72 - -# CTF key -5C100DF7042EAE64 - -# iCopy-X DRM key (iCE product) -2020666666668888 diff --git a/assets/resources/apps_data/picopass/assets/iclass_standard_dict.txt b/assets/resources/apps_data/picopass/assets/iclass_standard_dict.txt deleted file mode 100644 index 46808ef602e..00000000000 --- a/assets/resources/apps_data/picopass/assets/iclass_standard_dict.txt +++ /dev/null @@ -1,47 +0,0 @@ - -## From https://github.com/RfidResearchGroup/proxmark3/blob/master/client/dictionaries/iclass_default_keys.dic - -# AA1 -AEA684A6DAB23278 -# key1/Kc from PicoPass 2k documentation -7665544332211000 -# SAGEM -0123456789ABCDEF -# from loclass demo file. -5b7c62c491c11b39 -# Kd from PicoPass 2k documentation -F0E1D2C3B4A59687 -# PicoPass Default Exchange Key -5CBCF1DA45D5FB4F -# From HID multiclassSE reader -31ad7ebd2f282168 -# From pastebin: https://pastebin.com/uHqpjiuU -6EFD46EFCBB3C875 -E033CA419AEE43F9 - -# iCopy-x DRM keys -# iCL tags -2020666666668888 -# iCS tags reversed from the SOs -6666202066668888 - -# default picopass KD / Page 0 / Book 1 -FDCB5A52EA8F3090 -237FF9079863DF44 -5ADC25FB27181D32 -83B881F2936B2E49 -43644E61EE866BA5 -897034143D016080 -82D17B44C0122963 -4895CA7DE65E2025 -DADAD4C57BE271B7 -E41E9EDEF5719ABF -293D275EC3AF9C7F -C3C169251B8A70FB -F41DAF58B20C8B91 -28877A609EC0DD2B -66584C91EE80D5E5 -C1B74D7478053AE2 - -# default iCLASS RFIDeas -6B65797374726B72 From 56b5b3523634a347ecaa4333e69e5065d9dc34c3 Mon Sep 17 00:00:00 2001 From: hedger Date: Wed, 23 Aug 2023 21:04:12 +0300 Subject: [PATCH 723/824] Intelligent probing with warnings for fwflash.py (#3005) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * scripts: fwflash: intelligent probing with warnings * scripts: fwflash: better texting Co-authored-by: あく --- scripts/flipper/app.py | 3 +- scripts/fwflash.py | 157 +++++++++++++++++++++++------------------ 2 files changed, 91 insertions(+), 69 deletions(-) diff --git a/scripts/flipper/app.py b/scripts/flipper/app.py index 405c4c39907..da43a1f1151 100644 --- a/scripts/flipper/app.py +++ b/scripts/flipper/app.py @@ -15,10 +15,9 @@ def __init__(self, no_exit=False): # Application specific initialization self.init() - def __call__(self, args=None, skip_logger_init=False): + def __call__(self, args=None): self.args, self.other_args = self.parser.parse_known_args(args=args) # configure log output - # if skip_logger_init: self.log_level = logging.DEBUG if self.args.debug else logging.INFO self.logger.setLevel(self.log_level) if not self.logger.hasHandlers(): diff --git a/scripts/fwflash.py b/scripts/fwflash.py index 2b1ad543ba9..c119aaf800f 100755 --- a/scripts/fwflash.py +++ b/scripts/fwflash.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import logging import os +import re import socket import subprocess import time @@ -10,11 +11,12 @@ from flipper.app import App - -# When adding an interface, also add it to SWD_TRANSPORT in fbt options +# When adding an interface, also add it to SWD_TRANSPORT in fbt/ufbt options class Programmer(ABC): + root_logger = logging.getLogger("Programmer") + @abstractmethod def flash(self, file_path: str, do_verify: bool) -> bool: pass @@ -31,6 +33,26 @@ def get_name(self) -> str: def set_serial(self, serial: str): pass + @classmethod + def _spawn_and_await(cls, process_params, show_progress: bool = False): + cls.root_logger.debug(f"Launching: {' '.join(process_params)}") + + process = subprocess.Popen( + process_params, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + ) + + if show_progress: + while process.poll() is None: + time.sleep(0.25) + print(".", end="", flush=True) + print() + else: + process.wait() + + return process + @dataclass class OpenOCDInterface: @@ -43,7 +65,7 @@ class OpenOCDInterface: class OpenOCDProgrammer(Programmer): def __init__(self, interface: OpenOCDInterface): self.interface = interface - self.logger = logging.getLogger("OpenOCD") + self.logger = self.root_logger.getChild("OpenOCD") self.serial: typing.Optional[str] = None def _add_file(self, params: list[str], file: str): @@ -87,17 +109,7 @@ def flash(self, file_path: str, do_verify: bool) -> bool: self.logger.debug(f"Launching: {openocd_launch_params_string}") - process = subprocess.Popen( - openocd_launch_params, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - ) - - while process.poll() is None: - time.sleep(0.25) - print(".", end="", flush=True) - print() - + process = self._spawn_and_await(openocd_launch_params, True) success = process.returncode == 0 if not success: @@ -108,35 +120,41 @@ def flash(self, file_path: str, do_verify: bool) -> bool: return success def probe(self) -> bool: - i = self.interface - openocd_launch_params = ["openocd"] - self._add_file(openocd_launch_params, i.config_file) + self._add_file(openocd_launch_params, self.interface.config_file) if self.serial: self._add_serial(openocd_launch_params, self.serial) - if i.additional_args: - for a in i.additional_args: - self._add_command(openocd_launch_params, a) + for additional_arg in self.interface.additional_args: + self._add_command(openocd_launch_params, additional_arg) self._add_file(openocd_launch_params, "target/stm32wbx.cfg") self._add_command(openocd_launch_params, "init") self._add_command(openocd_launch_params, "exit") - self.logger.debug(f"Launching: {' '.join(openocd_launch_params)}") - - process = subprocess.Popen( - openocd_launch_params, - stderr=subprocess.STDOUT, - stdout=subprocess.PIPE, - ) - - # Wait for OpenOCD to end and get the return code - process.wait() - found = process.returncode == 0 + process = self._spawn_and_await(openocd_launch_params) + success = process.returncode == 0 - if process.stdout: - self.logger.debug(process.stdout.read().decode("utf-8").strip()) + output = process.stdout.read().decode("utf-8").strip() if process.stdout else "" + self.logger.debug(output) + # Find target voltage using regex + if match := re.search(r"Target voltage: (\d+\.\d+)", output): + voltage = float(match.group(1)) + if not success: + if voltage < 1: + self.logger.warning( + f"Found {self.get_name()}, but device is not connected" + ) + else: + self.logger.warning( + f"Device is connected, but {self.get_name()} failed to attach. Is System>Debug enabled?" + ) + + if "cannot read IDR" in output: + self.logger.warning( + f"Found {self.get_name()}, but failed to attach. Is device connected and is System>Debug enabled?" + ) + success = False - return found + return success def get_name(self) -> str: return self.interface.name @@ -218,7 +236,7 @@ def __init__( ): self.port_resolver = port_resolver self.name = name - self.logger = logging.getLogger("BlackmagicUSB") + self.logger = self.root_logger.getChild(f"Blackmagic{name}") self.port: typing.Optional[str] = None def _add_command(self, params: list[str], command: str): @@ -240,6 +258,14 @@ def set_serial(self, serial: str): else: self.port = serial + def _get_gdb_core_params(self) -> list[str]: + gdb_launch_params = ["arm-none-eabi-gdb"] + self._add_command(gdb_launch_params, f"target extended-remote {self.port}") + self._add_command(gdb_launch_params, "set pagination off") + self._add_command(gdb_launch_params, "set confirm off") + self._add_command(gdb_launch_params, "monitor swdp_scan") + return gdb_launch_params + def flash(self, file_path: str, do_verify: bool) -> bool: if not self.port: if not self.probe(): @@ -268,43 +294,25 @@ def flash(self, file_path: str, do_verify: bool) -> bool: # -ex 'compare-sections' # -ex 'quit' - gdb_launch_params = ["arm-none-eabi-gdb", file_path] - self._add_command(gdb_launch_params, f"target extended-remote {self.port}") - self._add_command(gdb_launch_params, "set pagination off") - self._add_command(gdb_launch_params, "set confirm off") - self._add_command(gdb_launch_params, "monitor swdp_scan") + gdb_launch_params = self._get_gdb_core_params() self._add_command(gdb_launch_params, "attach 1") self._add_command(gdb_launch_params, "set mem inaccessible-by-default off") self._add_command(gdb_launch_params, "load") if do_verify: self._add_command(gdb_launch_params, "compare-sections") self._add_command(gdb_launch_params, "quit") + gdb_launch_params.append(file_path) - self.logger.debug(f"Launching: {' '.join(gdb_launch_params)}") - - process = subprocess.Popen( - gdb_launch_params, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - ) - - while process.poll() is None: - time.sleep(0.5) - print(".", end="", flush=True) - print() - + process = self._spawn_and_await(gdb_launch_params, True) if not process.stdout: return False output = process.stdout.read().decode("utf-8").strip() - flashed = "Loading section .text," in output - - # Check flash verification - if "MIS-MATCHED!" in output: - flashed = False - - if "target image does not match the loaded file" in output: - flashed = False + flashed = ( + "Loading section .text," in output + and "MIS-MATCHED!" not in output + and "target image does not match the loaded file" not in output + ) if not flashed: self.logger.error("Blackmagic failed to flash") @@ -317,6 +325,20 @@ def probe(self) -> bool: return False self.port = port + + gdb_launch_params = self._get_gdb_core_params() + self._add_command(gdb_launch_params, "quit") + + process = self._spawn_and_await(gdb_launch_params) + if not process.stdout or process.returncode != 0: + return False + + output = process.stdout.read().decode("utf-8").strip() + if "SW-DP scan failed!" in output: + self.logger.warning( + f"Found {self.get_name()} at {self.port}, but failed to attach. Is device connected and is System>Debug enabled?" + ) + return False return True def get_name(self) -> str: @@ -358,6 +380,8 @@ class Main(App): AUTO_INTERFACE = "auto" def init(self): + Programmer.root_logger = self.logger + self.parser.add_argument( "filename", type=str, @@ -433,10 +457,10 @@ def flash(self): available_interfaces = self._search_interface(network_flash_interfaces) if not available_interfaces: - self.logger.error("No interface found") + self.logger.error("No availiable interfaces") return 1 elif len(available_interfaces) > 1: - self.logger.error("Multiple interfaces found: ") + self.logger.error("Multiple interfaces found:") self.logger.error( f"Please specify '--interface={[i.get_name() for i in available_interfaces]}'" ) @@ -446,11 +470,10 @@ def flash(self): if self.args.serial != self.AUTO_INTERFACE: interface.set_serial(self.args.serial) - self.logger.info( - f"Flashing {file_path} via {interface.get_name()} with {self.args.serial}" - ) + self.logger.info(f"Using {interface.get_name()} with {self.args.serial}") else: - self.logger.info(f"Flashing {file_path} via {interface.get_name()}") + self.logger.info(f"Using {interface.get_name()}") + self.logger.info(f"Flashing {file_path}") if not interface.flash(file_path, self.args.verify): self.logger.error(f"Failed to flash via {interface.get_name()}") From 60182aa2cd46e4192e2bfc80bbc16b080ad73574 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Thu, 24 Aug 2023 03:24:47 +0900 Subject: [PATCH 724/824] [FL-3564] New IR universal remote graphics (#3006) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * New IR universal remote layout * Remove redundant checks Co-authored-by: あく --- .../scenes/infrared_scene_universal_ac.c | 111 ++++++++++++------ .../scenes/infrared_scene_universal_audio.c | 85 ++++++++------ .../infrared_scene_universal_projector.c | 45 +++---- .../scenes/infrared_scene_universal_tv.c | 67 ++++++----- .../services/gui/modules/button_panel.c | 34 ++++++ .../services/gui/modules/button_panel.h | 13 ++ assets/icons/Infrared/CoolHi_25x27.png | Bin 3680 -> 0 bytes assets/icons/Infrared/CoolHi_hvr_25x27.png | Bin 3669 -> 0 bytes assets/icons/Infrared/CoolLo_25x27.png | Bin 3676 -> 0 bytes assets/icons/Infrared/CoolLo_hvr_25x27.png | Bin 3657 -> 0 bytes assets/icons/Infrared/Dehumidify_25x27.png | Bin 3665 -> 0 bytes .../icons/Infrared/Dehumidify_hvr_25x27.png | Bin 3652 -> 0 bytes assets/icons/Infrared/HeatHi_25x27.png | Bin 3676 -> 0 bytes assets/icons/Infrared/HeatHi_hvr_25x27.png | Bin 3661 -> 0 bytes assets/icons/Infrared/HeatLo_25x27.png | Bin 3670 -> 0 bytes assets/icons/Infrared/HeatLo_hvr_25x27.png | Bin 3655 -> 0 bytes assets/icons/Infrared/Mute_25x27.png | Bin 3670 -> 0 bytes assets/icons/Infrared/Mute_hvr_25x27.png | Bin 3657 -> 0 bytes assets/icons/Infrared/Off_25x27.png | Bin 9530 -> 0 bytes assets/icons/Infrared/Off_hvr_25x27.png | Bin 8460 -> 0 bytes assets/icons/Infrared/Pause_25x27.png | Bin 3634 -> 0 bytes assets/icons/Infrared/Pause_hvr_25x27.png | Bin 3623 -> 0 bytes assets/icons/Infrared/Play_25x27.png | Bin 3653 -> 0 bytes assets/icons/Infrared/Play_hvr_25x27.png | Bin 3643 -> 0 bytes assets/icons/Infrared/Power_25x27.png | Bin 3669 -> 0 bytes assets/icons/Infrared/Power_hvr_25x27.png | Bin 3651 -> 0 bytes assets/icons/Infrared/TrackNext_25x27.png | Bin 3651 -> 0 bytes assets/icons/Infrared/TrackNext_hvr_25x27.png | Bin 3639 -> 0 bytes assets/icons/Infrared/TrackPrev_25x27.png | Bin 3657 -> 0 bytes assets/icons/Infrared/TrackPrev_hvr_25x27.png | Bin 3644 -> 0 bytes assets/icons/Infrared/Up_25x27.png | Bin 3650 -> 0 bytes assets/icons/Infrared/Up_hvr_25x27.png | Bin 3630 -> 0 bytes assets/icons/Infrared/back_btn_10x8.png | Bin 0 -> 141 bytes assets/icons/Infrared/celsius_24x23.png | Bin 0 -> 257 bytes assets/icons/Infrared/celsius_hover_24x23.png | Bin 0 -> 204 bytes ...Vol_up_hvr_25x27.png => ch_down_24x21.png} | Bin 3617 -> 2931 bytes ...down_25x27.png => ch_down_hover_24x21.png} | Bin 3622 -> 2903 bytes .../{Vol_up_25x27.png => ch_text_31x34.png} | Bin 3628 -> 2925 bytes assets/icons/Infrared/ch_up_24x21.png | Bin 0 -> 2913 bytes assets/icons/Infrared/ch_up_hover_24x21.png | Bin 0 -> 2898 bytes .../{Down_25x27.png => cool_30x51.png} | Bin 3650 -> 3742 bytes assets/icons/Infrared/dry_19x20.png | Bin 0 -> 996 bytes assets/icons/Infrared/dry_hover_19x20.png | Bin 0 -> 978 bytes assets/icons/Infrared/dry_text_15x5.png | Bin 0 -> 968 bytes assets/icons/Infrared/fahren_24x23.png | Bin 0 -> 258 bytes assets/icons/Infrared/fahren_hover_24x23.png | Bin 0 -> 205 bytes .../{Down_hvr_25x27.png => heat_30x51.png} | Bin 3629 -> 3747 bytes assets/icons/Infrared/hourglass0_24x24.png | Bin 0 -> 231 bytes assets/icons/Infrared/hourglass1_24x24.png | Bin 0 -> 246 bytes assets/icons/Infrared/hourglass2_24x24.png | Bin 0 -> 237 bytes assets/icons/Infrared/hourglass3_24x24.png | Bin 0 -> 223 bytes assets/icons/Infrared/hourglass4_24x24.png | Bin 0 -> 239 bytes assets/icons/Infrared/hourglass5_24x24.png | Bin 0 -> 218 bytes assets/icons/Infrared/hourglass6_24x24.png | Bin 0 -> 238 bytes assets/icons/Infrared/max_24x23.png | Bin 0 -> 224 bytes assets/icons/Infrared/max_hover_24x23.png | Bin 0 -> 208 bytes assets/icons/Infrared/mute_19x20.png | Bin 0 -> 2918 bytes assets/icons/Infrared/mute_hover_19x20.png | Bin 0 -> 2877 bytes assets/icons/Infrared/mute_text_19x5.png | Bin 0 -> 2849 bytes assets/icons/Infrared/next_19x20.png | Bin 0 -> 2894 bytes assets/icons/Infrared/next_hover_19x20.png | Bin 0 -> 2873 bytes assets/icons/Infrared/next_text_19x6.png | Bin 0 -> 2852 bytes assets/icons/Infrared/pause_19x20.png | Bin 0 -> 2861 bytes assets/icons/Infrared/pause_hover_19x20.png | Bin 0 -> 2842 bytes assets/icons/Infrared/pause_text_23x5.png | Bin 0 -> 2856 bytes assets/icons/Infrared/play_19x20.png | Bin 0 -> 2894 bytes ...own_hvr_25x27.png => play_hover_19x20.png} | Bin 3611 -> 2869 bytes assets/icons/Infrared/play_text_19x5.png | Bin 0 -> 2850 bytes assets/icons/Infrared/power_19x20.png | Bin 0 -> 1006 bytes assets/icons/Infrared/power_hover_19x20.png | Bin 0 -> 996 bytes assets/icons/Infrared/power_text_24x5.png | Bin 0 -> 981 bytes assets/icons/Infrared/prev_19x20.png | Bin 0 -> 2893 bytes assets/icons/Infrared/prev_hover_19x20.png | Bin 0 -> 2877 bytes assets/icons/Infrared/prev_text_19x5.png | Bin 0 -> 2844 bytes assets/icons/Infrared/vol_ac_text_30x30.png | Bin 0 -> 2915 bytes assets/icons/Infrared/vol_tv_text_29x34.png | Bin 0 -> 2920 bytes assets/icons/Infrared/voldown_24x21.png | Bin 0 -> 2908 bytes assets/icons/Infrared/voldown_hover_24x21.png | Bin 0 -> 2884 bytes assets/icons/Infrared/volup_24x21.png | Bin 0 -> 2910 bytes assets/icons/Infrared/volup_hover_24x21.png | Bin 0 -> 2895 bytes firmware/targets/f18/api_symbols.csv | 3 +- firmware/targets/f7/api_symbols.csv | 3 +- 82 files changed, 230 insertions(+), 131 deletions(-) delete mode 100644 assets/icons/Infrared/CoolHi_25x27.png delete mode 100644 assets/icons/Infrared/CoolHi_hvr_25x27.png delete mode 100644 assets/icons/Infrared/CoolLo_25x27.png delete mode 100644 assets/icons/Infrared/CoolLo_hvr_25x27.png delete mode 100644 assets/icons/Infrared/Dehumidify_25x27.png delete mode 100644 assets/icons/Infrared/Dehumidify_hvr_25x27.png delete mode 100644 assets/icons/Infrared/HeatHi_25x27.png delete mode 100644 assets/icons/Infrared/HeatHi_hvr_25x27.png delete mode 100644 assets/icons/Infrared/HeatLo_25x27.png delete mode 100644 assets/icons/Infrared/HeatLo_hvr_25x27.png delete mode 100644 assets/icons/Infrared/Mute_25x27.png delete mode 100644 assets/icons/Infrared/Mute_hvr_25x27.png delete mode 100644 assets/icons/Infrared/Off_25x27.png delete mode 100644 assets/icons/Infrared/Off_hvr_25x27.png delete mode 100644 assets/icons/Infrared/Pause_25x27.png delete mode 100644 assets/icons/Infrared/Pause_hvr_25x27.png delete mode 100644 assets/icons/Infrared/Play_25x27.png delete mode 100644 assets/icons/Infrared/Play_hvr_25x27.png delete mode 100644 assets/icons/Infrared/Power_25x27.png delete mode 100644 assets/icons/Infrared/Power_hvr_25x27.png delete mode 100644 assets/icons/Infrared/TrackNext_25x27.png delete mode 100644 assets/icons/Infrared/TrackNext_hvr_25x27.png delete mode 100644 assets/icons/Infrared/TrackPrev_25x27.png delete mode 100644 assets/icons/Infrared/TrackPrev_hvr_25x27.png delete mode 100644 assets/icons/Infrared/Up_25x27.png delete mode 100644 assets/icons/Infrared/Up_hvr_25x27.png create mode 100644 assets/icons/Infrared/back_btn_10x8.png create mode 100644 assets/icons/Infrared/celsius_24x23.png create mode 100644 assets/icons/Infrared/celsius_hover_24x23.png rename assets/icons/Infrared/{Vol_up_hvr_25x27.png => ch_down_24x21.png} (70%) rename assets/icons/Infrared/{Vol_down_25x27.png => ch_down_hover_24x21.png} (70%) rename assets/icons/Infrared/{Vol_up_25x27.png => ch_text_31x34.png} (70%) create mode 100644 assets/icons/Infrared/ch_up_24x21.png create mode 100644 assets/icons/Infrared/ch_up_hover_24x21.png rename assets/icons/Infrared/{Down_25x27.png => cool_30x51.png} (73%) create mode 100644 assets/icons/Infrared/dry_19x20.png create mode 100644 assets/icons/Infrared/dry_hover_19x20.png create mode 100644 assets/icons/Infrared/dry_text_15x5.png create mode 100644 assets/icons/Infrared/fahren_24x23.png create mode 100644 assets/icons/Infrared/fahren_hover_24x23.png rename assets/icons/Infrared/{Down_hvr_25x27.png => heat_30x51.png} (73%) create mode 100644 assets/icons/Infrared/hourglass0_24x24.png create mode 100644 assets/icons/Infrared/hourglass1_24x24.png create mode 100644 assets/icons/Infrared/hourglass2_24x24.png create mode 100644 assets/icons/Infrared/hourglass3_24x24.png create mode 100644 assets/icons/Infrared/hourglass4_24x24.png create mode 100644 assets/icons/Infrared/hourglass5_24x24.png create mode 100644 assets/icons/Infrared/hourglass6_24x24.png create mode 100644 assets/icons/Infrared/max_24x23.png create mode 100644 assets/icons/Infrared/max_hover_24x23.png create mode 100644 assets/icons/Infrared/mute_19x20.png create mode 100644 assets/icons/Infrared/mute_hover_19x20.png create mode 100644 assets/icons/Infrared/mute_text_19x5.png create mode 100644 assets/icons/Infrared/next_19x20.png create mode 100644 assets/icons/Infrared/next_hover_19x20.png create mode 100644 assets/icons/Infrared/next_text_19x6.png create mode 100644 assets/icons/Infrared/pause_19x20.png create mode 100644 assets/icons/Infrared/pause_hover_19x20.png create mode 100644 assets/icons/Infrared/pause_text_23x5.png create mode 100644 assets/icons/Infrared/play_19x20.png rename assets/icons/Infrared/{Vol_down_hvr_25x27.png => play_hover_19x20.png} (70%) create mode 100644 assets/icons/Infrared/play_text_19x5.png create mode 100644 assets/icons/Infrared/power_19x20.png create mode 100644 assets/icons/Infrared/power_hover_19x20.png create mode 100644 assets/icons/Infrared/power_text_24x5.png create mode 100644 assets/icons/Infrared/prev_19x20.png create mode 100644 assets/icons/Infrared/prev_hover_19x20.png create mode 100644 assets/icons/Infrared/prev_text_19x5.png create mode 100644 assets/icons/Infrared/vol_ac_text_30x30.png create mode 100644 assets/icons/Infrared/vol_tv_text_29x34.png create mode 100644 assets/icons/Infrared/voldown_24x21.png create mode 100644 assets/icons/Infrared/voldown_hover_24x21.png create mode 100644 assets/icons/Infrared/volup_24x21.png create mode 100644 assets/icons/Infrared/volup_hover_24x21.png diff --git a/applications/main/infrared/scenes/infrared_scene_universal_ac.c b/applications/main/infrared/scenes/infrared_scene_universal_ac.c index 58f067735bc..8914e5ad000 100644 --- a/applications/main/infrared/scenes/infrared_scene_universal_ac.c +++ b/applications/main/infrared/scenes/infrared_scene_universal_ac.c @@ -1,6 +1,7 @@ #include "../infrared_i.h" #include "common/infrared_scene_universal_common.h" +#include void infrared_scene_universal_ac_on_enter(void* context) { infrared_scene_universal_common_on_enter(context); @@ -18,24 +19,26 @@ void infrared_scene_universal_ac_on_enter(void* context) { i, 0, 0, - 3, - 22, - &I_Off_25x27, - &I_Off_hvr_25x27, + 6, + 15, + &I_power_19x20, + &I_power_hover_19x20, infrared_scene_universal_common_item_callback, context); + button_panel_add_icon(button_panel, 4, 37, &I_power_text_24x5); infrared_brute_force_add_record(brute_force, i++, "Off"); button_panel_add_item( button_panel, i, 1, 0, - 36, - 22, - &I_Dehumidify_25x27, - &I_Dehumidify_hvr_25x27, + 39, + 15, + &I_dry_19x20, + &I_dry_hover_19x20, infrared_scene_universal_common_item_callback, context); + button_panel_add_icon(button_panel, 41, 37, &I_dry_text_15x5); infrared_brute_force_add_record(brute_force, i++, "Dh"); button_panel_add_item( button_panel, @@ -43,9 +46,9 @@ void infrared_scene_universal_ac_on_enter(void* context) { 0, 1, 3, - 59, - &I_CoolHi_25x27, - &I_CoolHi_hvr_25x27, + 49, + &I_max_24x23, + &I_max_hover_24x23, infrared_scene_universal_common_item_callback, context); infrared_brute_force_add_record(brute_force, i++, "Cool_hi"); @@ -54,39 +57,71 @@ void infrared_scene_universal_ac_on_enter(void* context) { i, 1, 1, - 36, - 59, - &I_HeatHi_25x27, - &I_HeatHi_hvr_25x27, + 37, + 49, + &I_max_24x23, + &I_max_hover_24x23, infrared_scene_universal_common_item_callback, context); infrared_brute_force_add_record(brute_force, i++, "Heat_hi"); - button_panel_add_item( - button_panel, - i, - 0, - 2, - 3, - 91, - &I_CoolLo_25x27, - &I_CoolLo_hvr_25x27, - infrared_scene_universal_common_item_callback, - context); + if(furi_hal_rtc_get_locale_units() == FuriHalRtcLocaleUnitsMetric) { + button_panel_add_item( + button_panel, + i, + 0, + 2, + 3, + 100, + &I_celsius_24x23, + &I_celsius_hover_24x23, + infrared_scene_universal_common_item_callback, + context); + } else { + button_panel_add_item( + button_panel, + i, + 0, + 2, + 3, + 100, + &I_fahren_24x23, + &I_fahren_hover_24x23, + infrared_scene_universal_common_item_callback, + context); + } infrared_brute_force_add_record(brute_force, i++, "Cool_lo"); - button_panel_add_item( - button_panel, - i, - 1, - 2, - 36, - 91, - &I_HeatLo_25x27, - &I_HeatLo_hvr_25x27, - infrared_scene_universal_common_item_callback, - context); + + if(furi_hal_rtc_get_locale_units() == FuriHalRtcLocaleUnitsMetric) { + button_panel_add_item( + button_panel, + i, + 1, + 2, + 37, + 100, + &I_celsius_24x23, + &I_celsius_hover_24x23, + infrared_scene_universal_common_item_callback, + context); + } else { + button_panel_add_item( + button_panel, + i, + 1, + 2, + 37, + 100, + &I_fahren_24x23, + &I_fahren_hover_24x23, + infrared_scene_universal_common_item_callback, + context); + } infrared_brute_force_add_record(brute_force, i++, "Heat_lo"); - button_panel_add_label(button_panel, 6, 10, FontPrimary, "AC remote"); + button_panel_add_icon(button_panel, 0, 60, &I_cool_30x51); + button_panel_add_icon(button_panel, 34, 60, &I_heat_30x51); + + button_panel_add_label(button_panel, 4, 10, FontPrimary, "AC remote"); view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical); view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack); diff --git a/applications/main/infrared/scenes/infrared_scene_universal_audio.c b/applications/main/infrared/scenes/infrared_scene_universal_audio.c index 00c86fff4a6..3938b6080d3 100644 --- a/applications/main/infrared/scenes/infrared_scene_universal_audio.c +++ b/applications/main/infrared/scenes/infrared_scene_universal_audio.c @@ -18,82 +18,88 @@ void infrared_scene_universal_audio_on_enter(void* context) { i, 0, 0, - 3, - 11, - &I_Power_25x27, - &I_Power_hvr_25x27, + 6, + 13, + &I_power_19x20, + &I_power_hover_19x20, infrared_scene_universal_common_item_callback, context); + button_panel_add_icon(button_panel, 4, 35, &I_power_text_24x5); infrared_brute_force_add_record(brute_force, i++, "Power"); button_panel_add_item( button_panel, i, 1, 0, - 36, - 11, - &I_Mute_25x27, - &I_Mute_hvr_25x27, + 39, + 13, + &I_mute_19x20, + &I_mute_hover_19x20, infrared_scene_universal_common_item_callback, context); + button_panel_add_icon(button_panel, 39, 35, &I_mute_text_19x5); infrared_brute_force_add_record(brute_force, i++, "Mute"); button_panel_add_item( button_panel, i, 0, 1, - 3, - 41, - &I_Play_25x27, - &I_Play_hvr_25x27, + 6, + 42, + &I_play_19x20, + &I_play_hover_19x20, infrared_scene_universal_common_item_callback, context); + button_panel_add_icon(button_panel, 6, 64, &I_play_text_19x5); infrared_brute_force_add_record(brute_force, i++, "Play"); button_panel_add_item( button_panel, i, - 1, - 1, - 36, - 41, - &I_Pause_25x27, - &I_Pause_hvr_25x27, + 0, + 2, + 6, + 71, + &I_pause_19x20, + &I_pause_hover_19x20, infrared_scene_universal_common_item_callback, context); + button_panel_add_icon(button_panel, 4, 93, &I_pause_text_23x5); infrared_brute_force_add_record(brute_force, i++, "Pause"); button_panel_add_item( button_panel, i, 0, - 2, 3, - 71, - &I_TrackPrev_25x27, - &I_TrackPrev_hvr_25x27, + 6, + 101, + &I_prev_19x20, + &I_prev_hover_19x20, infrared_scene_universal_common_item_callback, context); + button_panel_add_icon(button_panel, 6, 123, &I_prev_text_19x5); infrared_brute_force_add_record(brute_force, i++, "Prev"); button_panel_add_item( button_panel, i, 1, - 2, - 36, - 71, - &I_TrackNext_25x27, - &I_TrackNext_hvr_25x27, + 3, + 39, + 101, + &I_next_19x20, + &I_next_hover_19x20, infrared_scene_universal_common_item_callback, context); + button_panel_add_icon(button_panel, 39, 123, &I_next_text_19x6); infrared_brute_force_add_record(brute_force, i++, "Next"); button_panel_add_item( button_panel, i, - 0, - 3, - 3, - 101, - &I_Vol_down_25x27, - &I_Vol_down_hvr_25x27, + 1, + 2, + 37, + 77, + &I_voldown_24x21, + &I_voldown_hover_24x21, infrared_scene_universal_common_item_callback, context); infrared_brute_force_add_record(brute_force, i++, "Vol_dn"); @@ -101,16 +107,17 @@ void infrared_scene_universal_audio_on_enter(void* context) { button_panel, i, 1, - 3, - 36, - 101, - &I_Vol_up_25x27, - &I_Vol_up_hvr_25x27, + 1, + 37, + 43, + &I_volup_24x21, + &I_volup_hover_24x21, infrared_scene_universal_common_item_callback, context); infrared_brute_force_add_record(brute_force, i++, "Vol_up"); - button_panel_add_label(button_panel, 1, 8, FontPrimary, "Mus. remote"); + button_panel_add_label(button_panel, 1, 10, FontPrimary, "Mus. remote"); + button_panel_add_icon(button_panel, 34, 56, &I_vol_ac_text_30x30); view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical); view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack); diff --git a/applications/main/infrared/scenes/infrared_scene_universal_projector.c b/applications/main/infrared/scenes/infrared_scene_universal_projector.c index c1df91c3440..27ca46ea9de 100644 --- a/applications/main/infrared/scenes/infrared_scene_universal_projector.c +++ b/applications/main/infrared/scenes/infrared_scene_universal_projector.c @@ -11,59 +11,62 @@ void infrared_scene_universal_projector_on_enter(void* context) { infrared_brute_force_set_db_filename(brute_force, EXT_PATH("infrared/assets/projector.ir")); - button_panel_reserve(button_panel, 2, 2); + button_panel_reserve(button_panel, 2, 3); uint32_t i = 0; button_panel_add_item( button_panel, i, 0, 0, - 3, - 19, - &I_Power_25x27, - &I_Power_hvr_25x27, + 6, + 23, + &I_power_19x20, + &I_power_hover_19x20, infrared_scene_universal_common_item_callback, context); + button_panel_add_icon(button_panel, 4, 45, &I_power_text_24x5); infrared_brute_force_add_record(brute_force, i++, "Power"); button_panel_add_item( button_panel, i, 1, 0, - 36, - 19, - &I_Mute_25x27, - &I_Mute_hvr_25x27, + 39, + 23, + &I_mute_19x20, + &I_mute_hover_19x20, infrared_scene_universal_common_item_callback, context); + button_panel_add_icon(button_panel, 39, 45, &I_mute_text_19x5); infrared_brute_force_add_record(brute_force, i++, "Mute"); button_panel_add_item( button_panel, i, 0, 1, - 3, - 66, - &I_Vol_up_25x27, - &I_Vol_up_hvr_25x27, + 20, + 59, + &I_volup_24x21, + &I_volup_hover_24x21, infrared_scene_universal_common_item_callback, context); infrared_brute_force_add_record(brute_force, i++, "Vol_up"); + button_panel_add_item( button_panel, i, - 1, - 1, - 36, - 66, - &I_Vol_down_25x27, - &I_Vol_down_hvr_25x27, + 0, + 2, + 20, + 93, + &I_voldown_24x21, + &I_voldown_hover_24x21, infrared_scene_universal_common_item_callback, context); infrared_brute_force_add_record(brute_force, i++, "Vol_dn"); - button_panel_add_label(button_panel, 2, 11, FontPrimary, "Proj. remote"); - button_panel_add_label(button_panel, 17, 62, FontSecondary, "Volume"); + button_panel_add_label(button_panel, 3, 11, FontPrimary, "Proj. remote"); + button_panel_add_icon(button_panel, 17, 72, &I_vol_ac_text_30x30); view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical); view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack); diff --git a/applications/main/infrared/scenes/infrared_scene_universal_tv.c b/applications/main/infrared/scenes/infrared_scene_universal_tv.c index e21bf8f9062..f2958d887d5 100644 --- a/applications/main/infrared/scenes/infrared_scene_universal_tv.c +++ b/applications/main/infrared/scenes/infrared_scene_universal_tv.c @@ -18,77 +18,82 @@ void infrared_scene_universal_tv_on_enter(void* context) { i, 0, 0, - 3, - 19, - &I_Power_25x27, - &I_Power_hvr_25x27, + 6, + 16, + &I_power_19x20, + &I_power_hover_19x20, infrared_scene_universal_common_item_callback, context); + button_panel_add_icon(button_panel, 4, 38, &I_power_text_24x5); infrared_brute_force_add_record(brute_force, i++, "Power"); button_panel_add_item( button_panel, i, 1, 0, - 36, - 19, - &I_Mute_25x27, - &I_Mute_hvr_25x27, + 39, + 16, + &I_mute_19x20, + &I_mute_hover_19x20, infrared_scene_universal_common_item_callback, context); + button_panel_add_icon(button_panel, 39, 38, &I_mute_text_19x5); + + button_panel_add_icon(button_panel, 0, 66, &I_ch_text_31x34); + button_panel_add_icon(button_panel, 35, 66, &I_vol_tv_text_29x34); + infrared_brute_force_add_record(brute_force, i++, "Mute"); button_panel_add_item( button_panel, i, - 0, 1, - 3, - 66, - &I_Vol_up_25x27, - &I_Vol_up_hvr_25x27, + 1, + 38, + 53, + &I_volup_24x21, + &I_volup_hover_24x21, infrared_scene_universal_common_item_callback, context); + infrared_brute_force_add_record(brute_force, i++, "Vol_up"); button_panel_add_item( button_panel, i, + 0, 1, - 1, - 36, - 66, - &I_Up_25x27, - &I_Up_hvr_25x27, + 3, + 53, + &I_ch_up_24x21, + &I_ch_up_hover_24x21, infrared_scene_universal_common_item_callback, context); infrared_brute_force_add_record(brute_force, i++, "Ch_next"); button_panel_add_item( button_panel, i, - 0, + 1, 2, - 3, - 98, - &I_Vol_down_25x27, - &I_Vol_down_hvr_25x27, + 38, + 91, + &I_voldown_24x21, + &I_voldown_hover_24x21, infrared_scene_universal_common_item_callback, context); infrared_brute_force_add_record(brute_force, i++, "Vol_dn"); button_panel_add_item( button_panel, i, - 1, + 0, 2, - 36, - 98, - &I_Down_25x27, - &I_Down_hvr_25x27, + 3, + 91, + &I_ch_down_24x21, + &I_ch_down_hover_24x21, infrared_scene_universal_common_item_callback, context); infrared_brute_force_add_record(brute_force, i++, "Ch_prev"); - button_panel_add_label(button_panel, 6, 11, FontPrimary, "TV remote"); - button_panel_add_label(button_panel, 9, 64, FontSecondary, "Vol"); - button_panel_add_label(button_panel, 43, 64, FontSecondary, "Ch"); + button_panel_add_label(button_panel, 5, 10, FontPrimary, "TV remote"); view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical); view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack); diff --git a/applications/services/gui/modules/button_panel.c b/applications/services/gui/modules/button_panel.c index 503be2dac42..ded7891e6c5 100644 --- a/applications/services/gui/modules/button_panel.c +++ b/applications/services/gui/modules/button_panel.c @@ -29,6 +29,9 @@ typedef struct { const Icon* name_selected; } IconElement; +LIST_DEF(IconList, IconElement, M_POD_OPLIST) +#define M_OPL_IconList_t() LIST_OPLIST(IconList) + typedef struct ButtonItem { uint32_t index; ButtonItemCallback callback; @@ -47,6 +50,7 @@ struct ButtonPanel { typedef struct { ButtonMatrix_t button_matrix; + IconList_t icons; LabelList_t labels; uint16_t reserve_x; uint16_t reserve_y; @@ -146,6 +150,7 @@ void button_panel_reset(ButtonPanel* button_panel) { model->selected_item_x = 0; model->selected_item_y = 0; LabelList_reset(model->labels); + IconList_reset(model->icons); ButtonMatrix_reset(model->button_matrix); }, true); @@ -208,9 +213,17 @@ static void button_panel_view_draw_callback(Canvas* canvas, void* _model) { canvas_clear(canvas); canvas_set_color(canvas, ColorBlack); + for + M_EACH(icon, model->icons, IconList_t) { + canvas_draw_icon(canvas, icon->x, icon->y, icon->name); + } + for(size_t x = 0; x < model->reserve_x; ++x) { for(size_t y = 0; y < model->reserve_y; ++y) { ButtonItem* button_item = *button_panel_get_item(model, x, y); + if(!button_item) { + continue; + } const Icon* icon_name = button_item->icon.name; if((model->selected_item_x == x) && (model->selected_item_y == y)) { icon_name = button_item->icon.name_selected; @@ -406,3 +419,24 @@ void button_panel_add_label( }, true); } + +// Draw an icon but don't make it a button. +void button_panel_add_icon( + ButtonPanel* button_panel, + uint16_t x, + uint16_t y, + const Icon* icon_name) { + furi_assert(button_panel); + + with_view_model( //-V773 + button_panel->view, + ButtonPanelModel * model, + { + IconElement* icon = IconList_push_raw(model->icons); + icon->x = x; + icon->y = y; + icon->name = icon_name; + icon->name_selected = icon_name; + }, + true); +} \ No newline at end of file diff --git a/applications/services/gui/modules/button_panel.h b/applications/services/gui/modules/button_panel.h index f3b0bae703c..1218222b881 100644 --- a/applications/services/gui/modules/button_panel.h +++ b/applications/services/gui/modules/button_panel.h @@ -100,6 +100,19 @@ void button_panel_add_label( Font font, const char* label_str); +/** Add a non-button icon to button_panel module. + * + * @param button_panel ButtonPanel instance + * @param x x-coordinate to place icon + * @param y y-coordinate to place icon + * @param icon_name name of the icon to draw + */ +void button_panel_add_icon( + ButtonPanel* button_panel, + uint16_t x, + uint16_t y, + const Icon* icon_name); + #ifdef __cplusplus } #endif diff --git a/assets/icons/Infrared/CoolHi_25x27.png b/assets/icons/Infrared/CoolHi_25x27.png deleted file mode 100644 index cea29a5b9e764c88ae56f305368568d04e544876..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3680 zcmaJ@c{r478-E?LZ^@FRGlpz2W5zm@vCNF!5JtvQ8H_P$7Gr8Gk$tHSNm(0dkzGY8 zp==>SNM)~(pkkg!y^zr`*?G03d2b!C;QqVK5*DlO9MpMFN1Z5sn)f?=~loTAx@&JEX*1 zaiF`(34>hG7h+^H)U{Par0r8wZVb!0H1D>u5>VxpHP#qc$Tfci(!m-Df+0OO+4Uh&DAn1a1;~3h;#uiU|Wvxcnx){mERZFX&t!zL*5QCRT=tgK&&2 zU=fjqz5`fT^Tlv-)ZKtW0l>H0-){;yq6_$HoclBg#BerpBl!UDD=Kn)g&6>74=Du; z1RVw{`i`Er0tkA5Y@kCM0(hqj=-GJ$+5-0;0ZqNqV%31KIH2c}lBfj;L;}8@s;Xf? zLM|X{z3gH7+o3AyS#4gWa;r`2)DTv&-om;eLLMHF1Dd^d3WsEkh(8hYEFdl6xr*>u z1F82bF9D!1Lynj2%2rsfWL0mkQCh9!3EeNx1i4^8zp3q+zH){I0DNFY_iyV!Yxcz) z7L1{8-#oY|5OiFu@bvnHz-lRrhd|-nh{pB_(q+;M2b(em!yMG%$ATwY+Kyy`{(<#k2u-&Jc`C=p> zxLOEtMF(`KGjW@CXivk1Ap;r3C}wdH(+hR~`f1 zb2gu|Kl87$#U+yD;yY5vnu_^*h4zva*?aHiINnhlyr9^D*E5FA=gj6x<r3!ksI<`7vZo3rTKQYzN4ifWMtO?Um36>~NIvs1+rhdO?`0N?& z`kXE0`U|MC(i;ejzP-LjjqA#lKy#s~oRE!cEGLm!&Eo8p=<^e@OIjbbl57ABBkADN^OeTPHn%XE~u`e?tuG( zTg-FC)!os$bJ+2)V@J=+o|`>yat-rQu($Bp{Mr1s`IC=)y~4b7YD;P#lkBRez3zA} z);7=*y%3&71b44vHP)4!%7bs}E9;AQ7uPvuI+Yi^A-CT9t@cH2=_AE^Gw%dPt7@sW zQADzz0{PMNs@BEK#>}WEQNL`Vgd~!OCCin)l%qo*FlCWPkrR2n*A~sAp08%jLCJ(Z z>ArXRQ?+}#wc02gxBNjHvI4m-G=3%JLaIYtHzeB(lCRW0-q|>9&sqyP_90?mjgw!K z-?C6LdUw%ik+PUPcKxNnb*%zV{m@sfotXD7GyUdb*RSdYPgX=bW1M5j4`)@O{?H7M z%D49(6|u|KiAxG*U(J};r_82IjVIs}o+n-!H$Ccn)a~3#FF27ni8-gr4d6y_`+?$^ zgM4KE)L6?{@1Hg|BF?HjOEX7~lD<|CFIZkIth1D}OpmLKn`y383F`dyQl-lJY))@R zFGVu(Nc877uY`!7h!6Frj5(-`5qEX% z4Yke6ASX>njGq`hF>i+idcGVYa0qs%9QGq1+EqrhQ%@(qFRUbgHzJl_cFEuzyIF9Ed3*__8fT(a3vJn=4Ipb0Yx=aO^Sxf#x z{uFqoyMmNhz5Sea;Suz}RiPKbHJ2)OdFqPRqVIghduhJa7OEzbJOZEfq;?^)$_ozl zEWE7g8ogwEZRt99L8e9K!{yqdBnJ;&Wx}V%ij#N)w4_$`T}WG0t-zDjmf zAzSX;JI%4M8Kq=;*R$NQD-TM`+v)=P0tW);K27KrcuQuLWq-<+q)7~qJdl_?`e0%0 ztJNqpyGL`hKE4)Hck7wy;|5aki{75Y=J?zARs1)+(c-}PL*m;FTK}R_WW!g3Ux!A$ z8`ihXUOXLj=X1vm1rCum?KW%H&8t$&<~Fi6smCgvs38j-`~&=3LaLIrZ|hzUDG#9V zuIXwoiQBI3Kv=+9Eu3`{-4?N{(GC?j)mgPG*zzKfizoTzBX z?_<8BH)|tj{d*@*O3{~|NV7f+SaC1R9&R|?>$CirwOlmar2Iqzos}>8E!B5QvZF%&oca#hAW;KJ@~H?VH=(RSNRZ=#8QIx4r#2{Wr3gvooJj&lYWc zX_{BOJKEmTe&FegFn!qZ)uWpW&FnF@^3ttIDd*|4pmUnspx^JWxxFgh%v8+G~;|1`iDYrsiP`qJJ=n1@a@2DKr##ss0%lL#M9}geiZUZ5S>L24WctZaD502 zq=_T?Q)oLDtv?_*9MX;!$|BPINp@Him`4JkPy&z!*7}y#7$XC?iHS7~W`(u1G&VLt z7^4yTShTe@M*jyEOZPubCDB+vumS(U8val0&OlHZyvSG*lX8X>V9lgcL4Rb8r2K;x zODhu-LlZq1kI(9#wfu<<_y;Y2Vd4K13+0J{?)3J*di}%18=#%--^0s0{5}388gJ&A zyy5OnIOELwFa(b{;;pz`E^q7JG8oNUg6*)D_^^?L%XTt=if|i}%U#85Awh9ISpI5z z8Bc?@G!Wjmf$2r#1X+O{x5x!SpJY>K6i@rGX8jUW`N!zp+7rwKTvb%$hCxW)SXacz x0esYa#Ie5Oe2SOAc?nB)n|7hk^Ftc~djZK{pns(EF^m@$u(NW)R-t|4{tXgASK9yp diff --git a/assets/icons/Infrared/CoolHi_hvr_25x27.png b/assets/icons/Infrared/CoolHi_hvr_25x27.png deleted file mode 100644 index 692ac7b8be718dfe02b460c9ce9a710857feb562..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3669 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 diff --git a/assets/icons/Infrared/CoolLo_25x27.png b/assets/icons/Infrared/CoolLo_25x27.png deleted file mode 100644 index 23288e44f158d4034bebfd6a2a9005514b132a9d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3676 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

© Copyright (c) 2020 STMicroelectronics. - * All rights reserved.

- * - * This software component is licensed by ST under Ultimate Liberty license - * SLA0044, the "License"; You may not use this file except in compliance with - * the License. You may obtain a copy of the License at: - * www.st.com/SLA0044 - * - ****************************************************************************** - */ -/* USER CODE END Header */ -/* Define to prevent recursive inclusion -------------------------------------*/ -#ifndef APP_COMMON_H -#define APP_COMMON_H - -#ifdef __cplusplus -extern "C" { -#endif +#pragma once #include #include @@ -36,5 +10,3 @@ extern "C" { #include #include "app_conf.h" - -#endif diff --git a/firmware/targets/f7/ble_glue/app_conf.h b/firmware/targets/f7/ble_glue/app_conf.h index ee5115cfed3..25fa688c700 100644 --- a/firmware/targets/f7/ble_glue/app_conf.h +++ b/firmware/targets/f7/ble_glue/app_conf.h @@ -1,80 +1,32 @@ #pragma once -#include "hw_conf.h" -#include "hw_if.h" - -#include -#include +#include #define CFG_TX_POWER (0x19) /* +0dBm */ #define CFG_IDENTITY_ADDRESS GAP_PUBLIC_ADDR -/** - * Define Advertising parameters - */ -#define CFG_ADV_BD_ADDRESS (0x7257acd87a6c) -#define CFG_FAST_CONN_ADV_INTERVAL_MIN (0x80) /**< 80ms */ -#define CFG_FAST_CONN_ADV_INTERVAL_MAX (0xa0) /**< 100ms */ -#define CFG_LP_CONN_ADV_INTERVAL_MIN (0x640) /**< 1s */ -#define CFG_LP_CONN_ADV_INTERVAL_MAX (0xfa0) /**< 2.5s */ - /** * Define IO Authentication */ -#define CFG_BONDING_MODE (1) -#define CFG_FIXED_PIN (111111) -#define CFG_USED_FIXED_PIN (1) +#define CFG_USED_FIXED_PIN USE_FIXED_PIN_FOR_PAIRING_FORBIDDEN #define CFG_ENCRYPTION_KEY_SIZE_MAX (16) #define CFG_ENCRYPTION_KEY_SIZE_MIN (8) /** * Define IO capabilities */ -#define CFG_IO_CAPABILITY_DISPLAY_ONLY (0x00) -#define CFG_IO_CAPABILITY_DISPLAY_YES_NO (0x01) -#define CFG_IO_CAPABILITY_KEYBOARD_ONLY (0x02) -#define CFG_IO_CAPABILITY_NO_INPUT_NO_OUTPUT (0x03) -#define CFG_IO_CAPABILITY_KEYBOARD_DISPLAY (0x04) - -#define CFG_IO_CAPABILITY CFG_IO_CAPABILITY_DISPLAY_YES_NO +#define CFG_IO_CAPABILITY IO_CAP_DISPLAY_YES_NO /** * Define MITM modes */ -#define CFG_MITM_PROTECTION_NOT_REQUIRED (0x00) -#define CFG_MITM_PROTECTION_REQUIRED (0x01) - -#define CFG_MITM_PROTECTION CFG_MITM_PROTECTION_REQUIRED +#define CFG_MITM_PROTECTION MITM_PROTECTION_REQUIRED /** * Define Secure Connections Support */ -#define CFG_SECURE_NOT_SUPPORTED (0x00) -#define CFG_SECURE_OPTIONAL (0x01) -#define CFG_SECURE_MANDATORY (0x02) - -#define CFG_SC_SUPPORT CFG_SECURE_OPTIONAL - -/** - * Define Keypress Notification Support - */ -#define CFG_KEYPRESS_NOT_SUPPORTED (0x00) -#define CFG_KEYPRESS_SUPPORTED (0x01) - -#define CFG_KEYPRESS_NOTIFICATION_SUPPORT CFG_KEYPRESS_NOT_SUPPORTED - -/** - * Numeric Comparison Answers - */ -#define YES (0x01) -#define NO (0x00) - -/** - * Device name configuration for Generic Access Service - */ -#define CFG_GAP_DEVICE_NAME "TEMPLATE" -#define CFG_GAP_DEVICE_NAME_LENGTH (8) +#define CFG_SC_SUPPORT SC_PAIRING_OPTIONAL /** * Define PHY @@ -87,42 +39,6 @@ #define RX_1M 0x01 #define RX_2M 0x02 -/** -* Identity root key used to derive LTK and CSRK -*/ -#define CFG_BLE_IRK \ - { \ - 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, \ - 0xf0 \ - } - -/** -* Encryption root key used to derive LTK and CSRK -*/ -#define CFG_BLE_ERK \ - { \ - 0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21, 0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, \ - 0x21 \ - } - -/* USER CODE BEGIN Generic_Parameters */ -/** - * SMPS supply - * SMPS not used when Set to 0 - * SMPS used when Set to 1 - */ -#define CFG_USE_SMPS 1 -/* USER CODE END Generic_Parameters */ - -/**< specific parameters */ -/*****************************************************/ - -/** -* AD Element - Group B Feature -*/ -/* LSB - Second Byte */ -#define CFG_FEATURE_OTA_REBOOT (0x20) - /****************************************************************************** * BLE Stack ******************************************************************************/ @@ -203,7 +119,9 @@ * 1 : external high speed crystal HSE/32/32 * 0 : external low speed crystal ( no calibration ) */ -#define CFG_BLE_LSE_SOURCE 0 +#define CFG_BLE_LSE_SOURCE \ + SHCI_C2_BLE_INIT_CFG_BLE_LS_CLK_LSE | SHCI_C2_BLE_INIT_CFG_BLE_LS_OTHER_DEV | \ + SHCI_C2_BLE_INIT_CFG_BLE_LS_CALIB /** * Start up time of the high speed (16 or 32 MHz) crystal oscillator in units of 625/256 us (~2.44 us) @@ -253,8 +171,8 @@ */ #define CFG_BLE_OPTIONS \ (SHCI_C2_BLE_INIT_OPTIONS_LL_HOST | SHCI_C2_BLE_INIT_OPTIONS_WITH_SVC_CHANGE_DESC | \ - SHCI_C2_BLE_INIT_OPTIONS_DEVICE_NAME_RW | SHCI_C2_BLE_INIT_OPTIONS_NO_EXT_ADV | \ - SHCI_C2_BLE_INIT_OPTIONS_NO_CS_ALGO2 | SHCI_C2_BLE_INIT_OPTIONS_POWER_CLASS_2_3) + SHCI_C2_BLE_INIT_OPTIONS_DEVICE_NAME_RO | SHCI_C2_BLE_INIT_OPTIONS_EXT_ADV | \ + SHCI_C2_BLE_INIT_OPTIONS_CS_ALGO2 | SHCI_C2_BLE_INIT_OPTIONS_POWER_CLASS_2_3) /** * Queue length of BLE Event @@ -282,187 +200,3 @@ 255 /**< Set to 255 with the memory manager and the mailbox */ #define TL_BLE_EVENT_FRAME_SIZE (TL_EVT_HDR_SIZE + CFG_TLBLE_MOST_EVENT_PAYLOAD_SIZE) -/****************************************************************************** - * UART interfaces - ******************************************************************************/ - -/** - * Select UART interfaces - */ -#define CFG_DEBUG_TRACE_UART hw_uart1 -#define CFG_CONSOLE_MENU 0 - -/****************************************************************************** - * Low Power - ******************************************************************************/ -/** - * When set to 1, the low power mode is enable - * When set to 0, the device stays in RUN mode - */ -#define CFG_LPM_SUPPORTED 1 - -/****************************************************************************** - * Timer Server - ******************************************************************************/ -/** - * CFG_RTC_WUCKSEL_DIVIDER: This sets the RTCCLK divider to the wakeup timer. - * The lower is the value, the better is the power consumption and the accuracy of the timerserver - * The higher is the value, the finest is the granularity - * - * CFG_RTC_ASYNCH_PRESCALER: This sets the asynchronous prescaler of the RTC. It should as high as possible ( to ouput - * clock as low as possible) but the output clock should be equal or higher frequency compare to the clock feeding - * the wakeup timer. A lower clock speed would impact the accuracy of the timer server. - * - * CFG_RTC_SYNCH_PRESCALER: This sets the synchronous prescaler of the RTC. - * When the 1Hz calendar clock is required, it shall be sets according to other settings - * When the 1Hz calendar clock is not needed, CFG_RTC_SYNCH_PRESCALER should be set to 0x7FFF (MAX VALUE) - * - * CFG_RTCCLK_DIVIDER_CONF: - * Shall be set to either 0,2,4,8,16 - * When set to either 2,4,8,16, the 1Hhz calendar is supported - * When set to 0, the user sets its own configuration - * - * The following settings are computed with LSI as input to the RTC - */ -#define CFG_RTCCLK_DIVIDER_CONF 0 - -#if(CFG_RTCCLK_DIVIDER_CONF == 0) -/** - * Custom configuration - * It does not support 1Hz calendar - * It divides the RTC CLK by 16 - */ -#define CFG_RTCCLK_DIV (16) -#define CFG_RTC_WUCKSEL_DIVIDER (0) -#define CFG_RTC_ASYNCH_PRESCALER (CFG_RTCCLK_DIV - 1) -#define CFG_RTC_SYNCH_PRESCALER (0x7FFF) - -#else - -#if(CFG_RTCCLK_DIVIDER_CONF == 2) -/** - * It divides the RTC CLK by 2 - */ -#define CFG_RTC_WUCKSEL_DIVIDER (3) -#endif - -#if(CFG_RTCCLK_DIVIDER_CONF == 4) -/** - * It divides the RTC CLK by 4 - */ -#define CFG_RTC_WUCKSEL_DIVIDER (2) -#endif - -#if(CFG_RTCCLK_DIVIDER_CONF == 8) -/** - * It divides the RTC CLK by 8 - */ -#define CFG_RTC_WUCKSEL_DIVIDER (1) -#endif - -#if(CFG_RTCCLK_DIVIDER_CONF == 16) -/** - * It divides the RTC CLK by 16 - */ -#define CFG_RTC_WUCKSEL_DIVIDER (0) -#endif - -#define CFG_RTCCLK_DIV CFG_RTCCLK_DIVIDER_CONF -#define CFG_RTC_ASYNCH_PRESCALER (CFG_RTCCLK_DIV - 1) -#define CFG_RTC_SYNCH_PRESCALER (DIVR(LSE_VALUE, (CFG_RTC_ASYNCH_PRESCALER + 1)) - 1) - -#endif - -/** tick timer value in us */ -#define CFG_TS_TICK_VAL DIVR((CFG_RTCCLK_DIV * 1000000), LSE_VALUE) - -typedef enum { - CFG_TIM_PROC_ID_ISR, - /* USER CODE BEGIN CFG_TimProcID_t */ - - /* USER CODE END CFG_TimProcID_t */ -} CFG_TimProcID_t; - -/****************************************************************************** - * Debug - ******************************************************************************/ -/** - * When set, this resets some hw resources to set the device in the same state than the power up - * The FW resets only register that may prevent the FW to run properly - * - * This shall be set to 0 in a final product - * - */ -#define CFG_HW_RESET_BY_FW 0 - -/** - * keep debugger enabled while in any low power mode when set to 1 - * should be set to 0 in production - */ -#define CFG_DEBUGGER_SUPPORTED 1 - -/** - * When set to 1, the traces are enabled in the BLE services - */ -#define CFG_DEBUG_BLE_TRACE 0 - -/** - * Enable or Disable traces in application - */ -#define CFG_DEBUG_APP_TRACE 0 - -#if(CFG_DEBUG_APP_TRACE != 0) -#define APP_DBG_MSG PRINT_MESG_DBG -#else -#define APP_DBG_MSG PRINT_NO_MESG -#endif - -#if((CFG_DEBUG_BLE_TRACE != 0) || (CFG_DEBUG_APP_TRACE != 0)) -#define CFG_DEBUG_TRACE 1 -#endif - -#if(CFG_DEBUG_TRACE != 0) -#undef CFG_LPM_SUPPORTED -#undef CFG_DEBUGGER_SUPPORTED -#define CFG_LPM_SUPPORTED 0 -#define CFG_DEBUGGER_SUPPORTED 1 -#endif - -/** - * When CFG_DEBUG_TRACE_FULL is set to 1, the trace are output with the API name, the file name and the line number - * When CFG_DEBUG_TRACE_LIGHT is set to 1, only the debug message is output - * - * When both are set to 0, no trace are output - * When both are set to 1, CFG_DEBUG_TRACE_FULL is selected - */ -#define CFG_DEBUG_TRACE_LIGHT 0 -#define CFG_DEBUG_TRACE_FULL 0 - -#if((CFG_DEBUG_TRACE != 0) && (CFG_DEBUG_TRACE_LIGHT == 0) && (CFG_DEBUG_TRACE_FULL == 0)) -#undef CFG_DEBUG_TRACE_FULL -#undef CFG_DEBUG_TRACE_LIGHT -#define CFG_DEBUG_TRACE_FULL 0 -#define CFG_DEBUG_TRACE_LIGHT 1 -#endif - -#if(CFG_DEBUG_TRACE == 0) -#undef CFG_DEBUG_TRACE_FULL -#undef CFG_DEBUG_TRACE_LIGHT -#define CFG_DEBUG_TRACE_FULL 0 -#define CFG_DEBUG_TRACE_LIGHT 0 -#endif - -/** - * When not set, the traces is looping on sending the trace over UART - */ -#define DBG_TRACE_USE_CIRCULAR_QUEUE 0 - -/** - * max buffer Size to queue data traces and max data trace allowed. - * Only Used if DBG_TRACE_USE_CIRCULAR_QUEUE is defined - */ -#define DBG_TRACE_MSG_QUEUE_SIZE 4096 -#define MAX_DBG_TRACE_MSG_SIZE 1024 - -#define CFG_OTP_BASE_ADDRESS OTP_AREA_BASE -#define CFG_OTP_END_ADRESS OTP_AREA_END_ADDR diff --git a/firmware/targets/f7/ble_glue/app_debug.c b/firmware/targets/f7/ble_glue/app_debug.c index d2885282230..fe76687b9aa 100644 --- a/firmware/targets/f7/ble_glue/app_debug.c +++ b/firmware/targets/f7/ble_glue/app_debug.c @@ -6,6 +6,9 @@ #include #include +#include "stm32wbxx_ll_bus.h" +#include "stm32wbxx_ll_pwr.h" + #include typedef PACKED_STRUCT { @@ -108,10 +111,6 @@ static void APPD_SetCPU2GpioConfig(void); static void APPD_BleDtbCfg(void); void APPD_Init() { -#if(CFG_DEBUG_TRACE != 0) - DbgTraceInit(); -#endif - APPD_SetCPU2GpioConfig(); APPD_BleDtbCfg(); } @@ -252,13 +251,3 @@ static void APPD_BleDtbCfg(void) { } #endif } - -#if(CFG_DEBUG_TRACE != 0) -void DbgOutputInit(void) { -} - -void DbgOutputTraces(uint8_t* p_data, uint16_t size, void (*cb)(void)) { - furi_hal_console_tx(p_data, size); - cb(); -} -#endif diff --git a/firmware/targets/f7/ble_glue/app_debug.h b/firmware/targets/f7/ble_glue/app_debug.h index 92a54d75b03..213470bed5d 100644 --- a/firmware/targets/f7/ble_glue/app_debug.h +++ b/firmware/targets/f7/ble_glue/app_debug.h @@ -1,26 +1,4 @@ -/* USER CODE BEGIN Header */ -/** - ****************************************************************************** - * File Name : app_debug.h - * Description : Header for app_debug.c module - ****************************************************************************** - * @attention - * - *

© Copyright (c) 2020 STMicroelectronics. - * All rights reserved.

- * - * This software component is licensed by ST under Ultimate Liberty license - * SLA0044, the "License"; You may not use this file except in compliance with - * the License. You may obtain a copy of the License at: - * www.st.com/SLA0044 - * - ****************************************************************************** - */ -/* USER CODE END Header */ - -/* Define to prevent recursive inclusion -------------------------------------*/ -#ifndef __APP_DEBUG_H -#define __APP_DEBUG_H +#pragma once #ifdef __cplusplus extern "C" { @@ -32,7 +10,3 @@ void APPD_EnableCPU2(void); #ifdef __cplusplus } #endif - -#endif /*__APP_DEBUG_H */ - -/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ diff --git a/firmware/targets/f7/ble_glue/ble_app.c b/firmware/targets/f7/ble_glue/ble_app.c index c0418d9fe80..548a86e191a 100644 --- a/firmware/targets/f7/ble_glue/ble_app.c +++ b/firmware/targets/f7/ble_glue/ble_app.c @@ -18,8 +18,8 @@ PLACE_IN_SECTION("MB_MEM1") ALIGN(4) static TL_CmdPacket_t ble_app_cmd_buffer; PLACE_IN_SECTION("MB_MEM2") ALIGN(4) static uint32_t ble_app_nvm[BLE_NVM_SRAM_SIZE]; _Static_assert( - sizeof(SHCI_C2_Ble_Init_Cmd_Packet_t) == 57, - "Ble stack config structure size mismatch (check new config options - last updated for v.1.15.0)"); + sizeof(SHCI_C2_Ble_Init_Cmd_Packet_t) == 58, + "Ble stack config structure size mismatch (check new config options - last updated for v.1.17.2)"); typedef struct { FuriMutex* hci_mtx; @@ -72,10 +72,13 @@ static const SHCI_C2_Ble_Init_Cmd_Packet_t ble_init_cmd_packet = { .rx_model_config = 1, /* New stack (13.3->15.0) */ .max_adv_set_nbr = 1, // Only used if SHCI_C2_BLE_INIT_OPTIONS_EXT_ADV is set - .max_adv_data_len = 31, // Only used if SHCI_C2_BLE_INIT_OPTIONS_EXT_ADV is set + .max_adv_data_len = 1650, // Only used if SHCI_C2_BLE_INIT_OPTIONS_EXT_ADV is set .tx_path_compens = 0, // RF TX Path Compensation, * 0.1 dB .rx_path_compens = 0, // RF RX Path Compensation, * 0.1 dB - .ble_core_version = 11, // BLE Core Version: 11(5.2), 12(5.3) + .ble_core_version = SHCI_C2_BLE_INIT_BLE_CORE_5_4, + /*15.0->17.0*/ + .Options_extension = SHCI_C2_BLE_INIT_OPTIONS_ENHANCED_ATT_NOTSUPPORTED | + SHCI_C2_BLE_INIT_OPTIONS_APPEARANCE_READONLY, }}; bool ble_app_init() { diff --git a/firmware/targets/f7/ble_glue/ble_app.h b/firmware/targets/f7/ble_glue/ble_app.h index 495005ec484..2e6babab79d 100644 --- a/firmware/targets/f7/ble_glue/ble_app.h +++ b/firmware/targets/f7/ble_glue/ble_app.h @@ -1,12 +1,12 @@ #pragma once +#include +#include + #ifdef __cplusplus extern "C" { #endif -#include -#include - bool ble_app_init(); void ble_app_get_key_storage_buff(uint8_t** addr, uint16_t* size); void ble_app_thread_stop(); diff --git a/firmware/targets/f7/ble_glue/ble_conf.h b/firmware/targets/f7/ble_glue/ble_conf.h index a04d1def1bd..2b9c22dfe39 100644 --- a/firmware/targets/f7/ble_glue/ble_conf.h +++ b/firmware/targets/f7/ble_glue/ble_conf.h @@ -1,51 +1,7 @@ -/** - ****************************************************************************** - * File Name : App/ble_conf.h - * Description : Configuration file for BLE Middleware. - * - ****************************************************************************** - * @attention - * - *

© Copyright (c) 2020 STMicroelectronics. - * All rights reserved.

- * - * This software component is licensed by ST under Ultimate Liberty license - * SLA0044, the "License"; You may not use this file except in compliance with - * the License. You may obtain a copy of the License at: - * www.st.com/SLA0044 - * - ****************************************************************************** - */ - -/* Define to prevent recursive inclusion -------------------------------------*/ -#ifndef BLE_CONF_H -#define BLE_CONF_H +#pragma once #include "app_conf.h" -#ifndef __weak -#define __weak __attribute__((weak)) -#endif - -/****************************************************************************** - * - * BLE SERVICES CONFIGURATION - * blesvc - * - ******************************************************************************/ - -/** - * This setting shall be set to '1' if the device needs to support the Peripheral Role - * In the MS configuration, both BLE_CFG_PERIPHERAL and BLE_CFG_CENTRAL shall be set to '1' - */ -#define BLE_CFG_PERIPHERAL 1 - -/** - * This setting shall be set to '1' if the device needs to support the Central Role - * In the MS configuration, both BLE_CFG_PERIPHERAL and BLE_CFG_CENTRAL shall be set to '1' - */ -#define BLE_CFG_CENTRAL 0 - /** * There is one handler per service enabled * Note: There is no handler for the Device Information Service @@ -56,18 +12,3 @@ #define BLE_CFG_SVC_MAX_NBR_CB 7 #define BLE_CFG_CLT_MAX_NBR_CB 0 - -/****************************************************************************** - * GAP Service - Apprearance - ******************************************************************************/ - -#define BLE_CFG_UNKNOWN_APPEARANCE (0) -#define BLE_CFG_GAP_APPEARANCE (0x0086) - -/****************************************************************************** - * Over The Air Feature (OTA) - STM Proprietary - ******************************************************************************/ -#define BLE_CFG_OTA_REBOOT_CHAR 0 /**< REBOOT OTA MODE CHARACTERISTIC */ - -#endif /*BLE_CONF_H */ -/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ diff --git a/firmware/targets/f7/ble_glue/ble_const.h b/firmware/targets/f7/ble_glue/ble_const.h index 85f734b62c2..1e6647d90fa 100644 --- a/firmware/targets/f7/ble_glue/ble_const.h +++ b/firmware/targets/f7/ble_glue/ble_const.h @@ -1,22 +1,4 @@ -/***************************************************************************** - * @file ble_const.h - * @author MDG - * @brief This file contains the definitions which are compiler dependent. - ***************************************************************************** - * @attention - * - * Copyright (c) 2018-2022 STMicroelectronics. - * All rights reserved. - * - * This software is licensed under terms that can be found in the LICENSE file - * in the root directory of this software component. - * If no LICENSE file comes with this software, it is provided AS-IS. - * - ***************************************************************************** - */ - -#ifndef BLE_CONST_H__ -#define BLE_CONST_H__ +#pragma once #include #include @@ -115,5 +97,3 @@ extern int hci_send_req(struct hci_request* req, uint8_t async); #ifndef MAX #define MAX(a, b) (((a) > (b)) ? (a) : (b)) #endif - -#endif /* BLE_CONST_H__ */ diff --git a/firmware/targets/f7/ble_glue/ble_dbg_conf.h b/firmware/targets/f7/ble_glue/ble_dbg_conf.h index 8305f810615..6f70f09beec 100644 --- a/firmware/targets/f7/ble_glue/ble_dbg_conf.h +++ b/firmware/targets/f7/ble_glue/ble_dbg_conf.h @@ -1,199 +1 @@ -/** - ****************************************************************************** - * File Name : App/ble_dbg_conf.h - * Description : Debug configuration file for BLE Middleware. - * - ****************************************************************************** - * @attention - * - *

© Copyright (c) 2020 STMicroelectronics. - * All rights reserved.

- * - * This software component is licensed by ST under Ultimate Liberty license - * SLA0044, the "License"; You may not use this file except in compliance with - * the License. You may obtain a copy of the License at: - * www.st.com/SLA0044 - * - ****************************************************************************** - */ - -/* Define to prevent recursive inclusion -------------------------------------*/ -#ifndef __BLE_DBG_CONF_H -#define __BLE_DBG_CONF_H - -/** - * Enable or Disable traces from BLE - */ - -#define BLE_DBG_APP_EN 1 -#define BLE_DBG_DIS_EN 1 -#define BLE_DBG_HRS_EN 1 -#define BLE_DBG_SVCCTL_EN 1 -#define BLE_DBG_BLS_EN 1 -#define BLE_DBG_HTS_EN 1 -#define BLE_DBG_P2P_STM_EN 1 - -/** - * Macro definition - */ -#if(BLE_DBG_APP_EN != 0) -#define BLE_DBG_APP_MSG PRINT_MESG_DBG -#else -#define BLE_DBG_APP_MSG PRINT_NO_MESG -#endif - -#if(BLE_DBG_DIS_EN != 0) -#define BLE_DBG_DIS_MSG PRINT_MESG_DBG -#else -#define BLE_DBG_DIS_MSG PRINT_NO_MESG -#endif - -#if(BLE_DBG_HRS_EN != 0) -#define BLE_DBG_HRS_MSG PRINT_MESG_DBG -#else -#define BLE_DBG_HRS_MSG PRINT_NO_MESG -#endif - -#if(BLE_DBG_P2P_STM_EN != 0) -#define BLE_DBG_P2P_STM_MSG PRINT_MESG_DBG -#else -#define BLE_DBG_P2P_STM_MSG PRINT_NO_MESG -#endif - -#if(BLE_DBG_TEMPLATE_STM_EN != 0) -#define BLE_DBG_TEMPLATE_STM_MSG PRINT_MESG_DBG -#else -#define BLE_DBG_TEMPLATE_STM_MSG PRINT_NO_MESG -#endif - -#if(BLE_DBG_EDS_STM_EN != 0) -#define BLE_DBG_EDS_STM_MSG PRINT_MESG_DBG -#else -#define BLE_DBG_EDS_STM_MSG PRINT_NO_MESG -#endif - -#if(BLE_DBG_LBS_STM_EN != 0) -#define BLE_DBG_LBS_STM_MSG PRINT_MESG_DBG -#else -#define BLE_DBG_LBS_STM_MSG PRINT_NO_MESG -#endif - -#if(BLE_DBG_SVCCTL_EN != 0) -#define BLE_DBG_SVCCTL_MSG PRINT_MESG_DBG -#else -#define BLE_DBG_SVCCTL_MSG PRINT_NO_MESG -#endif - -#if(BLE_DBG_CTS_EN != 0) -#define BLE_DBG_CTS_MSG PRINT_MESG_DBG -#else -#define BLE_DBG_CTS_MSG PRINT_NO_MESG -#endif - -#if(BLE_DBG_HIDS_EN != 0) -#define BLE_DBG_HIDS_MSG PRINT_MESG_DBG -#else -#define BLE_DBG_HIDS_MSG PRINT_NO_MESG -#endif - -#if(BLE_DBG_PASS_EN != 0) -#define BLE_DBG_PASS_MSG PRINT_MESG_DBG -#else -#define BLE_DBG_PASS_MSG PRINT_NO_MESG -#endif - -#if(BLE_DBG_BLS_EN != 0) -#define BLE_DBG_BLS_MSG PRINT_MESG_DBG -#else -#define BLE_DBG_BLS_MSG PRINT_NO_MESG -#endif - -#if(BLE_DBG_HTS_EN != 0) -#define BLE_DBG_HTS_MSG PRINT_MESG_DBG -#else -#define BLE_DBG_HTS_MSG PRINT_NO_MESG -#endif - -#if(BLE_DBG_ANS_EN != 0) -#define BLE_DBG_ANS_MSG PRINT_MESG_DBG -#else -#define BLE_DBG_ANS_MSG PRINT_NO_MESG -#endif - -#if(BLE_DBG_ESS_EN != 0) -#define BLE_DBG_ESS_MSG PRINT_MESG_DBG -#else -#define BLE_DBG_ESS_MSG PRINT_NO_MESG -#endif - -#if(BLE_DBG_GLS_EN != 0) -#define BLE_DBG_GLS_MSG PRINT_MESG_DBG -#else -#define BLE_DBG_GLS_MSG PRINT_NO_MESG -#endif - -#if(BLE_DBG_BAS_EN != 0) -#define BLE_DBG_BAS_MSG PRINT_MESG_DBG -#else -#define BLE_DBG_BAS_MSG PRINT_NO_MESG -#endif - -#if(BLE_DBG_RTUS_EN != 0) -#define BLE_DBG_RTUS_MSG PRINT_MESG_DBG -#else -#define BLE_DBG_RTUS_MSG PRINT_NO_MESG -#endif - -#if(BLE_DBG_HPS_EN != 0) -#define BLE_DBG_HPS_MSG PRINT_MESG_DBG -#else -#define BLE_DBG_HPS_MSG PRINT_NO_MESG -#endif - -#if(BLE_DBG_TPS_EN != 0) -#define BLE_DBG_TPS_MSG PRINT_MESG_DBG -#else -#define BLE_DBG_TPS_MSG PRINT_NO_MESG -#endif - -#if(BLE_DBG_LLS_EN != 0) -#define BLE_DBG_LLS_MSG PRINT_MESG_DBG -#else -#define BLE_DBG_LLS_MSG PRINT_NO_MESG -#endif - -#if(BLE_DBG_IAS_EN != 0) -#define BLE_DBG_IAS_MSG PRINT_MESG_DBG -#else -#define BLE_DBG_IAS_MSG PRINT_NO_MESG -#endif - -#if(BLE_DBG_WSS_EN != 0) -#define BLE_DBG_WSS_MSG PRINT_MESG_DBG -#else -#define BLE_DBG_WSS_MSG PRINT_NO_MESG -#endif - -#if(BLE_DBG_LNS_EN != 0) -#define BLE_DBG_LNS_MSG PRINT_MESG_DBG -#else -#define BLE_DBG_LNS_MSG PRINT_NO_MESG -#endif - -#if(BLE_DBG_SCPS_EN != 0) -#define BLE_DBG_SCPS_MSG PRINT_MESG_DBG -#else -#define BLE_DBG_SCPS_MSG PRINT_NO_MESG -#endif - -#if(BLE_DBG_DTS_EN != 0) -#define BLE_DBG_DTS_MSG PRINT_MESG_DBG -#define BLE_DBG_DTS_BUF PRINT_LOG_BUFF_DBG -#else -#define BLE_DBG_DTS_MSG PRINT_NO_MESG -#define BLE_DBG_DTS_BUF PRINT_NO_MESG -#endif - -#endif /*__BLE_DBG_CONF_H */ - -/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ +#pragma once diff --git a/firmware/targets/f7/ble_glue/ble_glue.c b/firmware/targets/f7/ble_glue/ble_glue.c index 2c30612b54e..5f129ba8ce3 100644 --- a/firmware/targets/f7/ble_glue/ble_glue.c +++ b/firmware/targets/f7/ble_glue/ble_glue.c @@ -32,7 +32,6 @@ static uint8_t ble_glue_ble_spare_event_buff[sizeof(TL_PacketHeader_t) + TL_EVT_ typedef struct { FuriMutex* shci_mtx; - FuriSemaphore* shci_sem; FuriThread* thread; BleGlueStatus status; BleGlueKeyStorageChangedCallback callback; @@ -104,7 +103,6 @@ void ble_glue_init() { TL_Init(); ble_glue->shci_mtx = furi_mutex_alloc(FuriMutexTypeNormal); - ble_glue->shci_sem = furi_semaphore_alloc(1, 0); // FreeRTOS system task creation ble_glue->thread = furi_thread_alloc_ex("BleShciDriver", 1024, ble_glue_shci_thread, ble_glue); @@ -393,7 +391,6 @@ void ble_glue_thread_stop() { furi_thread_free(ble_glue->thread); // Free resources furi_mutex_free(ble_glue->shci_mtx); - furi_semaphore_free(ble_glue->shci_sem); ble_glue_clear_shared_memory(); free(ble_glue); ble_glue = NULL; @@ -427,22 +424,6 @@ void shci_notify_asynch_evt(void* pdata) { } } -void shci_cmd_resp_release(uint32_t flag) { - UNUSED(flag); - if(ble_glue) { - furi_semaphore_release(ble_glue->shci_sem); - } -} - -void shci_cmd_resp_wait(uint32_t timeout) { - UNUSED(timeout); - if(ble_glue) { - furi_hal_power_insomnia_enter(); - furi_semaphore_acquire(ble_glue->shci_sem, FuriWaitForever); - furi_hal_power_insomnia_exit(); - } -} - bool ble_glue_reinit_c2() { return SHCI_C2_Reinit() == SHCI_Success; } diff --git a/firmware/targets/f7/ble_glue/compiler.h b/firmware/targets/f7/ble_glue/compiler.h index 98a93d71264..1d32c2a2edc 100644 --- a/firmware/targets/f7/ble_glue/compiler.h +++ b/firmware/targets/f7/ble_glue/compiler.h @@ -1,22 +1,4 @@ -/***************************************************************************** - * @file compiler.h - * @author MDG - * @brief This file contains the definitions which are compiler dependent. - ***************************************************************************** - * @attention - * - * Copyright (c) 2018-2023 STMicroelectronics. - * All rights reserved. - * - * This software is licensed under terms that can be found in the LICENSE file - * in the root directory of this software component. - * If no LICENSE file comes with this software, it is provided AS-IS. - * - ***************************************************************************** - */ - -#ifndef COMPILER_H__ -#define COMPILER_H__ +#pragma once #ifndef __PACKED_STRUCT #define __PACKED_STRUCT PACKED(struct) @@ -154,5 +136,3 @@ #endif #endif #endif - -#endif /* COMPILER_H__ */ diff --git a/firmware/targets/f7/ble_glue/hsem_map.h b/firmware/targets/f7/ble_glue/hsem_map.h new file mode 100644 index 00000000000..9a5f51d204c --- /dev/null +++ b/firmware/targets/f7/ble_glue/hsem_map.h @@ -0,0 +1,81 @@ +#pragma once + +/****************************************************************************** + * Semaphores + * THIS SHALL NO BE CHANGED AS THESE SEMAPHORES ARE USED AS WELL ON THE CM0+ + *****************************************************************************/ +/** +* Index of the semaphore used the prevent conflicts after standby sleep. +* Each CPUs takes this semaphore at standby wakeup until conflicting elements are restored. +*/ +#define CFG_HW_PWR_STANDBY_SEMID 10 +/** +* The CPU2 may be configured to store the Thread persistent data either in internal NVM storage on CPU2 or in +* SRAM2 buffer provided by the user application. This can be configured with the system command SHCI_C2_Config() +* When the CPU2 is requested to store persistent data in SRAM2, it can write data in this buffer at any time when needed. +* In order to read consistent data with the CPU1 from the SRAM2 buffer, the flow should be: +* + CPU1 takes CFG_HW_THREAD_NVM_SRAM_SEMID semaphore +* + CPU1 reads all persistent data from SRAM2 (most of the time, the goal is to write these data into an NVM managed by CPU1) +* + CPU1 releases CFG_HW_THREAD_NVM_SRAM_SEMID semaphore +* CFG_HW_THREAD_NVM_SRAM_SEMID semaphore makes sure CPU2 does not update the persistent data in SRAM2 at the same time CPU1 is reading them. +* There is no timing constraint on how long this semaphore can be kept. +*/ +#define CFG_HW_THREAD_NVM_SRAM_SEMID 9 + +/** +* The CPU2 may be configured to store the BLE persistent data either in internal NVM storage on CPU2 or in +* SRAM2 buffer provided by the user application. This can be configured with the system command SHCI_C2_Config() +* When the CPU2 is requested to store persistent data in SRAM2, it can write data in this buffer at any time when needed. +* In order to read consistent data with the CPU1 from the SRAM2 buffer, the flow should be: +* + CPU1 takes CFG_HW_BLE_NVM_SRAM_SEMID semaphore +* + CPU1 reads all persistent data from SRAM2 (most of the time, the goal is to write these data into an NVM managed by CPU1) +* + CPU1 releases CFG_HW_BLE_NVM_SRAM_SEMID semaphore +* CFG_HW_BLE_NVM_SRAM_SEMID semaphore makes sure CPU2 does not update the persistent data in SRAM2 at the same time CPU1 is reading them. +* There is no timing constraint on how long this semaphore can be kept. +*/ +#define CFG_HW_BLE_NVM_SRAM_SEMID 8 + +/** +* Index of the semaphore used by CPU2 to prevent the CPU1 to either write or erase data in flash +* The CPU1 shall not either write or erase in flash when this semaphore is taken by the CPU2 +* When the CPU1 needs to either write or erase in flash, it shall first get the semaphore and release it just +* after writing a raw (64bits data) or erasing one sector. +* Once the Semaphore has been released, there shall be at least 1us before it can be taken again. This is required +* to give the opportunity to CPU2 to take it. +* On v1.4.0 and older CPU2 wireless firmware, this semaphore is unused and CPU2 is using PES bit. +* By default, CPU2 is using the PES bit to protect its timing. The CPU1 may request the CPU2 to use the semaphore +* instead of the PES bit by sending the system command SHCI_C2_SetFlashActivityControl() +*/ +#define CFG_HW_BLOCK_FLASH_REQ_BY_CPU2_SEMID 7 + +/** +* Index of the semaphore used by CPU1 to prevent the CPU2 to either write or erase data in flash +* In order to protect its timing, the CPU1 may get this semaphore to prevent the CPU2 to either +* write or erase in flash (as this will stall both CPUs) +* The PES bit shall not be used as this may stall the CPU2 in some cases. +*/ +#define CFG_HW_BLOCK_FLASH_REQ_BY_CPU1_SEMID 6 + +/** +* Index of the semaphore used to manage the CLK48 clock configuration +* When the USB is required, this semaphore shall be taken before configuring te CLK48 for USB +* and should be released after the application switch OFF the clock when the USB is not used anymore +* When using the RNG, it is good enough to use CFG_HW_RNG_SEMID to control CLK48. +* More details in AN5289 +*/ +#define CFG_HW_CLK48_CONFIG_SEMID 5 + +/* Index of the semaphore used to manage the entry Stop Mode procedure */ +#define CFG_HW_ENTRY_STOP_MODE_SEMID 4 + +/* Index of the semaphore used to access the RCC */ +#define CFG_HW_RCC_SEMID 3 + +/* Index of the semaphore used to access the FLASH */ +#define CFG_HW_FLASH_SEMID 2 + +/* Index of the semaphore used to access the PKA */ +#define CFG_HW_PKA_SEMID 1 + +/* Index of the semaphore used to access the RNG */ +#define CFG_HW_RNG_SEMID 0 diff --git a/firmware/targets/f7/ble_glue/hw_conf.h b/firmware/targets/f7/ble_glue/hw_conf.h deleted file mode 100644 index bf18a7d0e55..00000000000 --- a/firmware/targets/f7/ble_glue/hw_conf.h +++ /dev/null @@ -1,231 +0,0 @@ -/* USER CODE BEGIN Header */ -/** - ****************************************************************************** - * @file hw_conf.h - * @author MCD Application Team - * @brief Configuration of hardware interface - ****************************************************************************** - * @attention - * - *

© Copyright (c) 2019 STMicroelectronics. - * All rights reserved.

- * - * This software component is licensed by ST under Ultimate Liberty license - * SLA0044, the "License"; You may not use this file except in compliance with - * the License. You may obtain a copy of the License at: - * www.st.com/SLA0044 - * - ****************************************************************************** - */ -/* USER CODE END Header */ - -/* Define to prevent recursive inclusion -------------------------------------*/ -#ifndef HW_CONF_H -#define HW_CONF_H - -#include "FreeRTOSConfig.h" - -/****************************************************************************** - * Semaphores - * THIS SHALL NO BE CHANGED AS THESE SEMAPHORES ARE USED AS WELL ON THE CM0+ - *****************************************************************************/ -/** -* Index of the semaphore used the prevent conflicts after standby sleep. -* Each CPUs takes this semaphore at standby wakeup until conclicting elements are restored. -*/ -#define CFG_HW_PWR_STANDBY_SEMID 10 -/** -* The CPU2 may be configured to store the Thread persistent data either in internal NVM storage on CPU2 or in -* SRAM2 buffer provided by the user application. This can be configured with the system command SHCI_C2_Config() -* When the CPU2 is requested to store persistent data in SRAM2, it can write data in this buffer at any time when needed. -* In order to read consistent data with the CPU1 from the SRAM2 buffer, the flow should be: -* + CPU1 takes CFG_HW_THREAD_NVM_SRAM_SEMID semaphore -* + CPU1 reads all persistent data from SRAM2 (most of the time, the goal is to write these data into an NVM managed by CPU1) -* + CPU1 releases CFG_HW_THREAD_NVM_SRAM_SEMID semaphore -* CFG_HW_THREAD_NVM_SRAM_SEMID semaphore makes sure CPU2 does not update the persistent data in SRAM2 at the same time CPU1 is reading them. -* There is no timing constraint on how long this semaphore can be kept. -*/ -#define CFG_HW_THREAD_NVM_SRAM_SEMID 9 - -/** -* The CPU2 may be configured to store the BLE persistent data either in internal NVM storage on CPU2 or in -* SRAM2 buffer provided by the user application. This can be configured with the system command SHCI_C2_Config() -* When the CPU2 is requested to store persistent data in SRAM2, it can write data in this buffer at any time when needed. -* In order to read consistent data with the CPU1 from the SRAM2 buffer, the flow should be: -* + CPU1 takes CFG_HW_BLE_NVM_SRAM_SEMID semaphore -* + CPU1 reads all persistent data from SRAM2 (most of the time, the goal is to write these data into an NVM managed by CPU1) -* + CPU1 releases CFG_HW_BLE_NVM_SRAM_SEMID semaphore -* CFG_HW_BLE_NVM_SRAM_SEMID semaphore makes sure CPU2 does not update the persistent data in SRAM2 at the same time CPU1 is reading them. -* There is no timing constraint on how long this semaphore can be kept. -*/ -#define CFG_HW_BLE_NVM_SRAM_SEMID 8 - -/** -* Index of the semaphore used by CPU2 to prevent the CPU1 to either write or erase data in flash -* The CPU1 shall not either write or erase in flash when this semaphore is taken by the CPU2 -* When the CPU1 needs to either write or erase in flash, it shall first get the semaphore and release it just -* after writing a raw (64bits data) or erasing one sector. -* Once the Semaphore has been released, there shall be at least 1us before it can be taken again. This is required -* to give the opportunity to CPU2 to take it. -* On v1.4.0 and older CPU2 wireless firmware, this semaphore is unused and CPU2 is using PES bit. -* By default, CPU2 is using the PES bit to protect its timing. The CPU1 may request the CPU2 to use the semaphore -* instead of the PES bit by sending the system command SHCI_C2_SetFlashActivityControl() -*/ -#define CFG_HW_BLOCK_FLASH_REQ_BY_CPU2_SEMID 7 - -/** -* Index of the semaphore used by CPU1 to prevent the CPU2 to either write or erase data in flash -* In order to protect its timing, the CPU1 may get this semaphore to prevent the CPU2 to either -* write or erase in flash (as this will stall both CPUs) -* The PES bit shall not be used as this may stall the CPU2 in some cases. -*/ -#define CFG_HW_BLOCK_FLASH_REQ_BY_CPU1_SEMID 6 - -/** -* Index of the semaphore used to manage the CLK48 clock configuration -* When the USB is required, this semaphore shall be taken before configuring te CLK48 for USB -* and should be released after the application switch OFF the clock when the USB is not used anymore -* When using the RNG, it is good enough to use CFG_HW_RNG_SEMID to control CLK48. -* More details in AN5289 -*/ -#define CFG_HW_CLK48_CONFIG_SEMID 5 - -/* Index of the semaphore used to manage the entry Stop Mode procedure */ -#define CFG_HW_ENTRY_STOP_MODE_SEMID 4 - -/* Index of the semaphore used to access the RCC */ -#define CFG_HW_RCC_SEMID 3 - -/* Index of the semaphore used to access the FLASH */ -#define CFG_HW_FLASH_SEMID 2 - -/* Index of the semaphore used to access the PKA */ -#define CFG_HW_PKA_SEMID 1 - -/* Index of the semaphore used to access the RNG */ -#define CFG_HW_RNG_SEMID 0 - -/****************************************************************************** - * HW TIMER SERVER - *****************************************************************************/ -/** - * The user may define the maximum number of virtual timers supported. - * It shall not exceed 255 - */ -#define CFG_HW_TS_MAX_NBR_CONCURRENT_TIMER 6 - -/** - * The user may define the priority in the NVIC of the RTC_WKUP interrupt handler that is used to manage the - * wakeup timer. - * This setting is the preemptpriority part of the NVIC. - */ -#define CFG_HW_TS_NVIC_RTC_WAKEUP_IT_PREEMPTPRIO \ - (configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY + 1) /* FreeRTOS requirement */ - -/** - * The user may define the priority in the NVIC of the RTC_WKUP interrupt handler that is used to manage the - * wakeup timer. - * This setting is the subpriority part of the NVIC. It does not exist on all processors. When it is not supported - * on the CPU, the setting is ignored - */ -#define CFG_HW_TS_NVIC_RTC_WAKEUP_IT_SUBPRIO 0 - -/** - * Define a critical section in the Timer server - * The Timer server does not support the API to be nested - * The Application shall either: - * a) Ensure this will never happen - * b) Define the critical section - * The default implementations is masking all interrupts using the PRIMASK bit - * The TimerServer driver uses critical sections to avoid context corruption. This is achieved with the macro - * TIMER_ENTER_CRITICAL_SECTION and TIMER_EXIT_CRITICAL_SECTION. When CFG_HW_TS_USE_PRIMASK_AS_CRITICAL_SECTION is set - * to 1, all STM32 interrupts are masked with the PRIMASK bit of the CortexM CPU. It is possible to use the BASEPRI - * register of the CortexM CPU to keep allowed some interrupts with high priority. In that case, the user shall - * re-implement TIMER_ENTER_CRITICAL_SECTION and TIMER_EXIT_CRITICAL_SECTION and shall make sure that no TimerServer - * API are called when the TIMER critical section is entered - */ -#define CFG_HW_TS_USE_PRIMASK_AS_CRITICAL_SECTION 1 - -/** - * This value shall reflect the maximum delay there could be in the application between the time the RTC interrupt - * is generated by the Hardware and the time when the RTC interrupt handler is called. This time is measured in - * number of RTCCLK ticks. - * A relaxed timing would be 10ms - * When the value is too short, the timerserver will not be able to count properly and all timeout may be random. - * When the value is too long, the device may wake up more often than the most optimal configuration. However, the - * impact on power consumption would be marginal (unless the value selected is extremely too long). It is strongly - * recommended to select a value large enough to make sure it is not too short to ensure reliability of the system - * as this will have marginal impact on low power mode - */ -#define CFG_HW_TS_RTC_HANDLER_MAX_DELAY (10 * (LSI_VALUE / 1000)) - -/** - * Interrupt ID in the NVIC of the RTC Wakeup interrupt handler - * It shall be type of IRQn_Type - */ -#define CFG_HW_TS_RTC_WAKEUP_HANDLER_ID RTC_WKUP_IRQn - -/****************************************************************************** - * HW UART - *****************************************************************************/ -#define CFG_HW_LPUART1_ENABLED 0 -#define CFG_HW_LPUART1_DMA_TX_SUPPORTED 0 - -#define CFG_HW_USART1_ENABLED 1 -#define CFG_HW_USART1_DMA_TX_SUPPORTED 1 - -/** - * UART1 - */ -#define CFG_HW_USART1_PREEMPTPRIORITY 0x0F -#define CFG_HW_USART1_SUBPRIORITY 0 - -/** < The application shall check the selected source clock is enable */ -#define CFG_HW_USART1_SOURCE_CLOCK RCC_USART1CLKSOURCE_SYSCLK - -#define CFG_HW_USART1_BAUDRATE 115200 -#define CFG_HW_USART1_WORDLENGTH UART_WORDLENGTH_8B -#define CFG_HW_USART1_STOPBITS UART_STOPBITS_1 -#define CFG_HW_USART1_PARITY UART_PARITY_NONE -#define CFG_HW_USART1_HWFLOWCTL UART_HWCONTROL_NONE -#define CFG_HW_USART1_MODE UART_MODE_TX_RX -#define CFG_HW_USART1_ADVFEATUREINIT UART_ADVFEATURE_NO_INIT -#define CFG_HW_USART1_OVERSAMPLING UART_OVERSAMPLING_8 - -#define CFG_HW_USART1_TX_PORT_CLK_ENABLE __HAL_RCC_GPIOB_CLK_ENABLE -#define CFG_HW_USART1_TX_PORT GPIOB -#define CFG_HW_USART1_TX_PIN GPIO_PIN_6 -#define CFG_HW_USART1_TX_MODE GPIO_MODE_AF_PP -#define CFG_HW_USART1_TX_PULL GPIO_NOPULL -#define CFG_HW_USART1_TX_SPEED GPIO_SPEED_FREQ_VERY_HIGH -#define CFG_HW_USART1_TX_ALTERNATE GPIO_AF7_USART1 - -#define CFG_HW_USART1_RX_PORT_CLK_ENABLE __HAL_RCC_GPIOB_CLK_ENABLE -#define CFG_HW_USART1_RX_PORT GPIOB -#define CFG_HW_USART1_RX_PIN GPIO_PIN_7 -#define CFG_HW_USART1_RX_MODE GPIO_MODE_AF_PP -#define CFG_HW_USART1_RX_PULL GPIO_NOPULL -#define CFG_HW_USART1_RX_SPEED GPIO_SPEED_FREQ_VERY_HIGH -#define CFG_HW_USART1_RX_ALTERNATE GPIO_AF7_USART1 - -#define CFG_HW_USART1_CTS_PORT_CLK_ENABLE __HAL_RCC_GPIOA_CLK_ENABLE -#define CFG_HW_USART1_CTS_PORT GPIOA -#define CFG_HW_USART1_CTS_PIN GPIO_PIN_11 -#define CFG_HW_USART1_CTS_MODE GPIO_MODE_AF_PP -#define CFG_HW_USART1_CTS_PULL GPIO_PULLDOWN -#define CFG_HW_USART1_CTS_SPEED GPIO_SPEED_FREQ_VERY_HIGH -#define CFG_HW_USART1_CTS_ALTERNATE GPIO_AF7_USART1 - -#define CFG_HW_USART1_DMA_TX_PREEMPTPRIORITY 0x0F -#define CFG_HW_USART1_DMA_TX_SUBPRIORITY 0 - -#define CFG_HW_USART1_DMAMUX_CLK_ENABLE __HAL_RCC_DMAMUX1_CLK_ENABLE -#define CFG_HW_USART1_DMA_CLK_ENABLE __HAL_RCC_DMA2_CLK_ENABLE -#define CFG_HW_USART1_TX_DMA_REQ DMA_REQUEST_USART1_TX -#define CFG_HW_USART1_TX_DMA_CHANNEL DMA2_Channel4 -#define CFG_HW_USART1_TX_DMA_IRQn DMA2_Channel4_IRQn -#define CFG_HW_USART1_DMA_TX_IRQHandler DMA2_Channel4_IRQHandler - -#endif /*HW_CONF_H */ - -/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ diff --git a/firmware/targets/f7/ble_glue/hw_if.h b/firmware/targets/f7/ble_glue/hw_if.h deleted file mode 100644 index a0ac23df374..00000000000 --- a/firmware/targets/f7/ble_glue/hw_if.h +++ /dev/null @@ -1,102 +0,0 @@ -/* USER CODE BEGIN Header */ -/** - ****************************************************************************** - * @file hw_if.h - * @author MCD Application Team - * @brief Hardware Interface - ****************************************************************************** - * @attention - * - *

© Copyright (c) 2019 STMicroelectronics. - * All rights reserved.

- * - * This software component is licensed by ST under Ultimate Liberty license - * SLA0044, the "License"; You may not use this file except in compliance with - * the License. You may obtain a copy of the License at: - * www.st.com/SLA0044 - * - ****************************************************************************** - */ -/* USER CODE END Header */ - -/* Define to prevent recursive inclusion -------------------------------------*/ -#ifndef HW_IF_H -#define HW_IF_H - -#ifdef __cplusplus -extern "C" { -#endif - -/* Includes ------------------------------------------------------------------*/ -#include "stm32wbxx.h" -#include "stm32wbxx_ll_exti.h" -#include "stm32wbxx_ll_system.h" -#include "stm32wbxx_ll_rcc.h" -#include "stm32wbxx_ll_ipcc.h" -#include "stm32wbxx_ll_bus.h" -#include "stm32wbxx_ll_pwr.h" -#include "stm32wbxx_ll_cortex.h" -#include "stm32wbxx_ll_utils.h" -#include "stm32wbxx_ll_hsem.h" -#include "stm32wbxx_ll_gpio.h" -#include "stm32wbxx_ll_rtc.h" - -#ifdef USE_STM32WBXX_USB_DONGLE -#include "stm32wbxx_usb_dongle.h" -#endif -#ifdef USE_STM32WBXX_NUCLEO -#include "stm32wbxx_nucleo.h" -#endif -#ifdef USE_X_NUCLEO_EPD -#include "x_nucleo_epd.h" -#endif - -/* Private includes ----------------------------------------------------------*/ -/* USER CODE BEGIN Includes */ - -/* USER CODE END Includes */ - -/****************************************************************************** - * HW UART - ******************************************************************************/ -typedef enum { - hw_uart1, - hw_uart2, - hw_lpuart1, -} hw_uart_id_t; - -typedef enum { - hw_uart_ok, - hw_uart_error, - hw_uart_busy, - hw_uart_to, -} hw_status_t; - -void HW_UART_Init(hw_uart_id_t hw_uart_id); -void HW_UART_Receive_IT( - hw_uart_id_t hw_uart_id, - uint8_t* pData, - uint16_t Size, - void (*Callback)(void)); -void HW_UART_Transmit_IT( - hw_uart_id_t hw_uart_id, - uint8_t* pData, - uint16_t Size, - void (*Callback)(void)); -hw_status_t - HW_UART_Transmit(hw_uart_id_t hw_uart_id, uint8_t* p_data, uint16_t size, uint32_t timeout); -hw_status_t HW_UART_Transmit_DMA( - hw_uart_id_t hw_uart_id, - uint8_t* p_data, - uint16_t size, - void (*Callback)(void)); -void HW_UART_Interrupt_Handler(hw_uart_id_t hw_uart_id); -void HW_UART_DMA_Interrupt_Handler(hw_uart_id_t hw_uart_id); - -#ifdef __cplusplus -} -#endif - -#endif /*HW_IF_H */ - -/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ diff --git a/firmware/targets/f7/ble_glue/hw_ipcc.c b/firmware/targets/f7/ble_glue/hw_ipcc.c index 7c84df09fe5..c2397f35109 100644 --- a/firmware/targets/f7/ble_glue/hw_ipcc.c +++ b/firmware/targets/f7/ble_glue/hw_ipcc.c @@ -1,160 +1,52 @@ -/** - ****************************************************************************** - * File Name : Target/hw_ipcc.c - * Description : Hardware IPCC source file for STM32WPAN Middleware. - * - ****************************************************************************** - * @attention - * - *

© Copyright (c) 2020 STMicroelectronics. - * All rights reserved.

- * - * This software component is licensed by ST under Ultimate Liberty license - * SLA0044, the "License"; You may not use this file except in compliance with - * the License. You may obtain a copy of the License at: - * www.st.com/SLA0044 - * - ****************************************************************************** - */ - -/* Includes ------------------------------------------------------------------*/ #include "app_common.h" #include +#include +#include + +#include +#include + +#include + +#define HW_IPCC_TX_PENDING(channel) \ + ((!(LL_C1_IPCC_IsActiveFlag_CHx(IPCC, channel))) && \ + LL_C1_IPCC_IsEnabledTransmitChannel(IPCC, channel)) +#define HW_IPCC_RX_PENDING(channel) \ + (LL_C2_IPCC_IsActiveFlag_CHx(IPCC, channel) && \ + LL_C1_IPCC_IsEnabledReceiveChannel(IPCC, channel)) -/* Global variables ---------------------------------------------------------*/ -/* Private defines -----------------------------------------------------------*/ -#define HW_IPCC_TX_PENDING(channel) \ - (!(LL_C1_IPCC_IsActiveFlag_CHx(IPCC, channel))) && (((~(IPCC->C1MR)) & ((channel) << 16U))) -#define HW_IPCC_RX_PENDING(channel) \ - (LL_C2_IPCC_IsActiveFlag_CHx(IPCC, channel)) && (((~(IPCC->C1MR)) & ((channel) << 0U))) - -/* Private macros ------------------------------------------------------------*/ -/* Private typedef -----------------------------------------------------------*/ -/* Private variables ---------------------------------------------------------*/ -static void (*FreeBufCb)(void); - -/* Private function prototypes -----------------------------------------------*/ -static void HW_IPCC_BLE_EvtHandler(void); -static void HW_IPCC_BLE_AclDataEvtHandler(void); -static void HW_IPCC_MM_FreeBufHandler(void); -static void HW_IPCC_SYS_CmdEvtHandler(void); -static void HW_IPCC_SYS_EvtHandler(void); -static void HW_IPCC_TRACES_EvtHandler(void); - -#ifdef THREAD_WB -static void HW_IPCC_OT_CmdEvtHandler(void); -static void HW_IPCC_THREAD_NotEvtHandler(void); -static void HW_IPCC_THREAD_CliNotEvtHandler(void); -#endif - -#ifdef LLD_TESTS_WB -static void HW_IPCC_LLDTESTS_ReceiveCliRspHandler(void); -static void HW_IPCC_LLDTESTS_ReceiveM0CmdHandler(void); -#endif -#ifdef LLD_BLE_WB -/*static void HW_IPCC_LLD_BLE_ReceiveCliRspHandler( void );*/ -static void HW_IPCC_LLD_BLE_ReceiveRspHandler(void); -static void HW_IPCC_LLD_BLE_ReceiveM0CmdHandler(void); -#endif - -#ifdef MAC_802_15_4_WB -static void HW_IPCC_MAC_802_15_4_CmdEvtHandler(void); -static void HW_IPCC_MAC_802_15_4_NotEvtHandler(void); -#endif - -#ifdef ZIGBEE_WB -static void HW_IPCC_ZIGBEE_CmdEvtHandler(void); -static void HW_IPCC_ZIGBEE_StackNotifEvtHandler(void); -static void HW_IPCC_ZIGBEE_StackM0RequestHandler(void); -#endif - -/* Public function definition -----------------------------------------------*/ - -/****************************************************************************** - * INTERRUPT HANDLER - ******************************************************************************/ -void HW_IPCC_Rx_Handler(void) { +static void (*FreeBufCb)(); + +static void HW_IPCC_BLE_EvtHandler(); +static void HW_IPCC_BLE_AclDataEvtHandler(); +static void HW_IPCC_MM_FreeBufHandler(); +static void HW_IPCC_SYS_CmdEvtHandler(); +static void HW_IPCC_SYS_EvtHandler(); +static void HW_IPCC_TRACES_EvtHandler(); + +void HW_IPCC_Rx_Handler() { if(HW_IPCC_RX_PENDING(HW_IPCC_SYSTEM_EVENT_CHANNEL)) { HW_IPCC_SYS_EvtHandler(); - } -#ifdef MAC_802_15_4_WB - else if(HW_IPCC_RX_PENDING(HW_IPCC_MAC_802_15_4_NOTIFICATION_ACK_CHANNEL)) { - HW_IPCC_MAC_802_15_4_NotEvtHandler(); - } -#endif /* MAC_802_15_4_WB */ -#ifdef THREAD_WB - else if(HW_IPCC_RX_PENDING(HW_IPCC_THREAD_NOTIFICATION_ACK_CHANNEL)) { - HW_IPCC_THREAD_NotEvtHandler(); - } else if(HW_IPCC_RX_PENDING(HW_IPCC_THREAD_CLI_NOTIFICATION_ACK_CHANNEL)) { - HW_IPCC_THREAD_CliNotEvtHandler(); - } -#endif /* THREAD_WB */ -#ifdef LLD_TESTS_WB - else if(HW_IPCC_RX_PENDING(HW_IPCC_LLDTESTS_CLI_RSP_CHANNEL)) { - HW_IPCC_LLDTESTS_ReceiveCliRspHandler(); - } else if(HW_IPCC_RX_PENDING(HW_IPCC_LLDTESTS_M0_CMD_CHANNEL)) { - HW_IPCC_LLDTESTS_ReceiveM0CmdHandler(); - } -#endif /* LLD_TESTS_WB */ -#ifdef LLD_BLE_WB - else if(HW_IPCC_RX_PENDING(HW_IPCC_LLD_BLE_RSP_CHANNEL)) { - HW_IPCC_LLD_BLE_ReceiveRspHandler(); - } else if(HW_IPCC_RX_PENDING(HW_IPCC_LLD_BLE_M0_CMD_CHANNEL)) { - HW_IPCC_LLD_BLE_ReceiveM0CmdHandler(); - } -#endif /* LLD_TESTS_WB */ -#ifdef ZIGBEE_WB - else if(HW_IPCC_RX_PENDING(HW_IPCC_ZIGBEE_APPLI_NOTIF_ACK_CHANNEL)) { - HW_IPCC_ZIGBEE_StackNotifEvtHandler(); - } else if(HW_IPCC_RX_PENDING(HW_IPCC_ZIGBEE_M0_REQUEST_CHANNEL)) { - HW_IPCC_ZIGBEE_StackM0RequestHandler(); - } -#endif /* ZIGBEE_WB */ - else if(HW_IPCC_RX_PENDING(HW_IPCC_BLE_EVENT_CHANNEL)) { + } else if(HW_IPCC_RX_PENDING(HW_IPCC_BLE_EVENT_CHANNEL)) { HW_IPCC_BLE_EvtHandler(); } else if(HW_IPCC_RX_PENDING(HW_IPCC_TRACES_CHANNEL)) { HW_IPCC_TRACES_EvtHandler(); } - - return; } -void HW_IPCC_Tx_Handler(void) { +void HW_IPCC_Tx_Handler() { if(HW_IPCC_TX_PENDING(HW_IPCC_SYSTEM_CMD_RSP_CHANNEL)) { HW_IPCC_SYS_CmdEvtHandler(); - } -#ifdef MAC_802_15_4_WB - else if(HW_IPCC_TX_PENDING(HW_IPCC_MAC_802_15_4_CMD_RSP_CHANNEL)) { - HW_IPCC_MAC_802_15_4_CmdEvtHandler(); - } -#endif /* MAC_802_15_4_WB */ -#ifdef THREAD_WB - else if(HW_IPCC_TX_PENDING(HW_IPCC_THREAD_OT_CMD_RSP_CHANNEL)) { - HW_IPCC_OT_CmdEvtHandler(); - } -#endif /* THREAD_WB */ -#ifdef LLD_TESTS_WB -// No TX handler for LLD tests -#endif /* LLD_TESTS_WB */ -#ifdef ZIGBEE_WB - if(HW_IPCC_TX_PENDING(HW_IPCC_ZIGBEE_CMD_APPLI_CHANNEL)) { - HW_IPCC_ZIGBEE_CmdEvtHandler(); - } -#endif /* ZIGBEE_WB */ - else if(HW_IPCC_TX_PENDING(HW_IPCC_SYSTEM_CMD_RSP_CHANNEL)) { + } else if(HW_IPCC_TX_PENDING(HW_IPCC_SYSTEM_CMD_RSP_CHANNEL)) { HW_IPCC_SYS_CmdEvtHandler(); } else if(HW_IPCC_TX_PENDING(HW_IPCC_MM_RELEASE_BUFFER_CHANNEL)) { HW_IPCC_MM_FreeBufHandler(); } else if(HW_IPCC_TX_PENDING(HW_IPCC_HCI_ACL_DATA_CHANNEL)) { HW_IPCC_BLE_AclDataEvtHandler(); } - - return; } -/****************************************************************************** - * GENERAL - ******************************************************************************/ -void HW_IPCC_Enable(void) { + +void HW_IPCC_Enable() { /** * Such as IPCC IP available to the CPU2, it is required to keep the IPCC clock running when FUS is running on CPU2 and CPU1 enters deep sleep mode @@ -177,11 +69,9 @@ void HW_IPCC_Enable(void) { __SEV(); /* Set the internal event flag and send an event to the CPU2 */ __WFE(); /* Clear the internal event flag */ LL_PWR_EnableBootC2(); - - return; } -void HW_IPCC_Init(void) { +void HW_IPCC_Init() { LL_C1_IPCC_EnableIT_RXO(IPCC); LL_C1_IPCC_EnableIT_TXF(IPCC); @@ -189,366 +79,62 @@ void HW_IPCC_Init(void) { NVIC_EnableIRQ(IPCC_C1_RX_IRQn); NVIC_SetPriority(IPCC_C1_TX_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 6, 0)); NVIC_EnableIRQ(IPCC_C1_TX_IRQn); - - return; } -/****************************************************************************** - * BLE - ******************************************************************************/ -void HW_IPCC_BLE_Init(void) { +void HW_IPCC_BLE_Init() { LL_C1_IPCC_EnableReceiveChannel(IPCC, HW_IPCC_BLE_EVENT_CHANNEL); - - return; } -void HW_IPCC_BLE_SendCmd(void) { +void HW_IPCC_BLE_SendCmd() { LL_C1_IPCC_SetFlag_CHx(IPCC, HW_IPCC_BLE_CMD_CHANNEL); - - return; } -static void HW_IPCC_BLE_EvtHandler(void) { +static void HW_IPCC_BLE_EvtHandler() { HW_IPCC_BLE_RxEvtNot(); LL_C1_IPCC_ClearFlag_CHx(IPCC, HW_IPCC_BLE_EVENT_CHANNEL); - - return; } -void HW_IPCC_BLE_SendAclData(void) { +void HW_IPCC_BLE_SendAclData() { LL_C1_IPCC_SetFlag_CHx(IPCC, HW_IPCC_HCI_ACL_DATA_CHANNEL); LL_C1_IPCC_EnableTransmitChannel(IPCC, HW_IPCC_HCI_ACL_DATA_CHANNEL); - - return; } -static void HW_IPCC_BLE_AclDataEvtHandler(void) { +static void HW_IPCC_BLE_AclDataEvtHandler() { LL_C1_IPCC_DisableTransmitChannel(IPCC, HW_IPCC_HCI_ACL_DATA_CHANNEL); HW_IPCC_BLE_AclDataAckNot(); - - return; } -__weak void HW_IPCC_BLE_AclDataAckNot(void){}; -__weak void HW_IPCC_BLE_RxEvtNot(void){}; - -/****************************************************************************** - * SYSTEM - ******************************************************************************/ -void HW_IPCC_SYS_Init(void) { +void HW_IPCC_SYS_Init() { LL_C1_IPCC_EnableReceiveChannel(IPCC, HW_IPCC_SYSTEM_EVENT_CHANNEL); - - return; } -void HW_IPCC_SYS_SendCmd(void) { +void HW_IPCC_SYS_SendCmd() { LL_C1_IPCC_SetFlag_CHx(IPCC, HW_IPCC_SYSTEM_CMD_RSP_CHANNEL); - LL_C1_IPCC_EnableTransmitChannel(IPCC, HW_IPCC_SYSTEM_CMD_RSP_CHANNEL); - return; + FuriHalCortexTimer timer = furi_hal_cortex_timer_get(33000000); + + while(LL_C1_IPCC_IsActiveFlag_CHx(IPCC, HW_IPCC_SYSTEM_CMD_RSP_CHANNEL)) { + furi_check(!furi_hal_cortex_timer_is_expired(timer), "HW_IPCC_SYS_SendCmd timeout"); + } + + HW_IPCC_SYS_CmdEvtHandler(); } -static void HW_IPCC_SYS_CmdEvtHandler(void) { +static void HW_IPCC_SYS_CmdEvtHandler() { LL_C1_IPCC_DisableTransmitChannel(IPCC, HW_IPCC_SYSTEM_CMD_RSP_CHANNEL); HW_IPCC_SYS_CmdEvtNot(); - - return; } -static void HW_IPCC_SYS_EvtHandler(void) { +static void HW_IPCC_SYS_EvtHandler() { HW_IPCC_SYS_EvtNot(); LL_C1_IPCC_ClearFlag_CHx(IPCC, HW_IPCC_SYSTEM_EVENT_CHANNEL); - - return; -} - -__weak void HW_IPCC_SYS_CmdEvtNot(void){}; -__weak void HW_IPCC_SYS_EvtNot(void){}; - -/****************************************************************************** - * MAC 802.15.4 - ******************************************************************************/ -#ifdef MAC_802_15_4_WB -void HW_IPCC_MAC_802_15_4_Init(void) { - LL_C1_IPCC_EnableReceiveChannel(IPCC, HW_IPCC_MAC_802_15_4_NOTIFICATION_ACK_CHANNEL); - - return; -} - -void HW_IPCC_MAC_802_15_4_SendCmd(void) { - LL_C1_IPCC_SetFlag_CHx(IPCC, HW_IPCC_MAC_802_15_4_CMD_RSP_CHANNEL); - LL_C1_IPCC_EnableTransmitChannel(IPCC, HW_IPCC_MAC_802_15_4_CMD_RSP_CHANNEL); - - return; -} - -void HW_IPCC_MAC_802_15_4_SendAck(void) { - LL_C1_IPCC_ClearFlag_CHx(IPCC, HW_IPCC_MAC_802_15_4_NOTIFICATION_ACK_CHANNEL); - LL_C1_IPCC_EnableReceiveChannel(IPCC, HW_IPCC_MAC_802_15_4_NOTIFICATION_ACK_CHANNEL); - - return; -} - -static void HW_IPCC_MAC_802_15_4_CmdEvtHandler(void) { - LL_C1_IPCC_DisableTransmitChannel(IPCC, HW_IPCC_MAC_802_15_4_CMD_RSP_CHANNEL); - - HW_IPCC_MAC_802_15_4_CmdEvtNot(); - - return; -} - -static void HW_IPCC_MAC_802_15_4_NotEvtHandler(void) { - LL_C1_IPCC_DisableReceiveChannel(IPCC, HW_IPCC_MAC_802_15_4_NOTIFICATION_ACK_CHANNEL); - - HW_IPCC_MAC_802_15_4_EvtNot(); - - return; -} -__weak void HW_IPCC_MAC_802_15_4_CmdEvtNot(void){}; -__weak void HW_IPCC_MAC_802_15_4_EvtNot(void){}; -#endif - -/****************************************************************************** - * THREAD - ******************************************************************************/ -#ifdef THREAD_WB -void HW_IPCC_THREAD_Init(void) { - LL_C1_IPCC_EnableReceiveChannel(IPCC, HW_IPCC_THREAD_NOTIFICATION_ACK_CHANNEL); - LL_C1_IPCC_EnableReceiveChannel(IPCC, HW_IPCC_THREAD_CLI_NOTIFICATION_ACK_CHANNEL); - - return; -} - -void HW_IPCC_OT_SendCmd(void) { - LL_C1_IPCC_SetFlag_CHx(IPCC, HW_IPCC_THREAD_OT_CMD_RSP_CHANNEL); - LL_C1_IPCC_EnableTransmitChannel(IPCC, HW_IPCC_THREAD_OT_CMD_RSP_CHANNEL); - - return; -} - -void HW_IPCC_CLI_SendCmd(void) { - LL_C1_IPCC_SetFlag_CHx(IPCC, HW_IPCC_THREAD_CLI_CMD_CHANNEL); - - return; -} - -void HW_IPCC_THREAD_SendAck(void) { - LL_C1_IPCC_ClearFlag_CHx(IPCC, HW_IPCC_THREAD_NOTIFICATION_ACK_CHANNEL); - LL_C1_IPCC_EnableReceiveChannel(IPCC, HW_IPCC_THREAD_NOTIFICATION_ACK_CHANNEL); - - return; -} - -void HW_IPCC_THREAD_CliSendAck(void) { - LL_C1_IPCC_ClearFlag_CHx(IPCC, HW_IPCC_THREAD_CLI_NOTIFICATION_ACK_CHANNEL); - LL_C1_IPCC_EnableReceiveChannel(IPCC, HW_IPCC_THREAD_CLI_NOTIFICATION_ACK_CHANNEL); - - return; -} - -static void HW_IPCC_OT_CmdEvtHandler(void) { - LL_C1_IPCC_DisableTransmitChannel(IPCC, HW_IPCC_THREAD_OT_CMD_RSP_CHANNEL); - - HW_IPCC_OT_CmdEvtNot(); - - return; } -static void HW_IPCC_THREAD_NotEvtHandler(void) { - LL_C1_IPCC_DisableReceiveChannel(IPCC, HW_IPCC_THREAD_NOTIFICATION_ACK_CHANNEL); - - HW_IPCC_THREAD_EvtNot(); - - return; -} - -static void HW_IPCC_THREAD_CliNotEvtHandler(void) { - LL_C1_IPCC_DisableReceiveChannel(IPCC, HW_IPCC_THREAD_CLI_NOTIFICATION_ACK_CHANNEL); - - HW_IPCC_THREAD_CliEvtNot(); - - return; -} - -__weak void HW_IPCC_OT_CmdEvtNot(void){}; -__weak void HW_IPCC_CLI_CmdEvtNot(void){}; -__weak void HW_IPCC_THREAD_EvtNot(void){}; - -#endif /* THREAD_WB */ - -/****************************************************************************** - * LLD TESTS - ******************************************************************************/ -#ifdef LLD_TESTS_WB -void HW_IPCC_LLDTESTS_Init(void) { - LL_C1_IPCC_EnableReceiveChannel(IPCC, HW_IPCC_LLDTESTS_CLI_RSP_CHANNEL); - LL_C1_IPCC_EnableReceiveChannel(IPCC, HW_IPCC_LLDTESTS_M0_CMD_CHANNEL); - return; -} - -void HW_IPCC_LLDTESTS_SendCliCmd(void) { - LL_C1_IPCC_SetFlag_CHx(IPCC, HW_IPCC_LLDTESTS_CLI_CMD_CHANNEL); - return; -} - -static void HW_IPCC_LLDTESTS_ReceiveCliRspHandler(void) { - LL_C1_IPCC_DisableReceiveChannel(IPCC, HW_IPCC_LLDTESTS_CLI_RSP_CHANNEL); - HW_IPCC_LLDTESTS_ReceiveCliRsp(); - return; -} - -void HW_IPCC_LLDTESTS_SendCliRspAck(void) { - LL_C1_IPCC_ClearFlag_CHx(IPCC, HW_IPCC_LLDTESTS_CLI_RSP_CHANNEL); - LL_C1_IPCC_EnableReceiveChannel(IPCC, HW_IPCC_LLDTESTS_CLI_RSP_CHANNEL); - return; -} - -static void HW_IPCC_LLDTESTS_ReceiveM0CmdHandler(void) { - LL_C1_IPCC_DisableReceiveChannel(IPCC, HW_IPCC_LLDTESTS_M0_CMD_CHANNEL); - HW_IPCC_LLDTESTS_ReceiveM0Cmd(); - return; -} - -void HW_IPCC_LLDTESTS_SendM0CmdAck(void) { - LL_C1_IPCC_ClearFlag_CHx(IPCC, HW_IPCC_LLDTESTS_M0_CMD_CHANNEL); - LL_C1_IPCC_EnableReceiveChannel(IPCC, HW_IPCC_LLDTESTS_M0_CMD_CHANNEL); - return; -} -__weak void HW_IPCC_LLDTESTS_ReceiveCliRsp(void){}; -__weak void HW_IPCC_LLDTESTS_ReceiveM0Cmd(void){}; -#endif /* LLD_TESTS_WB */ - -/****************************************************************************** - * LLD BLE - ******************************************************************************/ -#ifdef LLD_BLE_WB -void HW_IPCC_LLD_BLE_Init(void) { - LL_C1_IPCC_EnableReceiveChannel(IPCC, HW_IPCC_LLD_BLE_RSP_CHANNEL); - LL_C1_IPCC_EnableReceiveChannel(IPCC, HW_IPCC_LLD_BLE_M0_CMD_CHANNEL); - return; -} - -void HW_IPCC_LLD_BLE_SendCliCmd(void) { - LL_C1_IPCC_SetFlag_CHx(IPCC, HW_IPCC_LLD_BLE_CLI_CMD_CHANNEL); - return; -} - -/*static void HW_IPCC_LLD_BLE_ReceiveCliRspHandler( void ) -{ - LL_C1_IPCC_DisableReceiveChannel( IPCC, HW_IPCC_LLD_BLE_CLI_RSP_CHANNEL ); - HW_IPCC_LLD_BLE_ReceiveCliRsp(); - return; -}*/ - -void HW_IPCC_LLD_BLE_SendCliRspAck(void) { - LL_C1_IPCC_ClearFlag_CHx(IPCC, HW_IPCC_LLD_BLE_CLI_RSP_CHANNEL); - LL_C1_IPCC_EnableReceiveChannel(IPCC, HW_IPCC_LLD_BLE_CLI_RSP_CHANNEL); - return; -} - -static void HW_IPCC_LLD_BLE_ReceiveM0CmdHandler(void) { - //LL_C1_IPCC_DisableReceiveChannel( IPCC, HW_IPCC_LLD_BLE_M0_CMD_CHANNEL ); - HW_IPCC_LLD_BLE_ReceiveM0Cmd(); - return; -} - -void HW_IPCC_LLD_BLE_SendM0CmdAck(void) { - LL_C1_IPCC_ClearFlag_CHx(IPCC, HW_IPCC_LLD_BLE_M0_CMD_CHANNEL); - //LL_C1_IPCC_EnableReceiveChannel( IPCC, HW_IPCC_LLD_BLE_M0_CMD_CHANNEL ); - return; -} -__weak void HW_IPCC_LLD_BLE_ReceiveCliRsp(void){}; -__weak void HW_IPCC_LLD_BLE_ReceiveM0Cmd(void){}; - -/* Transparent Mode */ -void HW_IPCC_LLD_BLE_SendCmd(void) { - LL_C1_IPCC_SetFlag_CHx(IPCC, HW_IPCC_LLD_BLE_CMD_CHANNEL); - return; -} - -static void HW_IPCC_LLD_BLE_ReceiveRspHandler(void) { - LL_C1_IPCC_DisableReceiveChannel(IPCC, HW_IPCC_LLD_BLE_RSP_CHANNEL); - HW_IPCC_LLD_BLE_ReceiveRsp(); - return; -} - -void HW_IPCC_LLD_BLE_SendRspAck(void) { - LL_C1_IPCC_ClearFlag_CHx(IPCC, HW_IPCC_LLD_BLE_RSP_CHANNEL); - LL_C1_IPCC_EnableReceiveChannel(IPCC, HW_IPCC_LLD_BLE_RSP_CHANNEL); - return; -} - -#endif /* LLD_BLE_WB */ - -/****************************************************************************** - * ZIGBEE - ******************************************************************************/ -#ifdef ZIGBEE_WB -void HW_IPCC_ZIGBEE_Init(void) { - LL_C1_IPCC_EnableReceiveChannel(IPCC, HW_IPCC_ZIGBEE_APPLI_NOTIF_ACK_CHANNEL); - LL_C1_IPCC_EnableReceiveChannel(IPCC, HW_IPCC_ZIGBEE_M0_REQUEST_CHANNEL); - - return; -} - -void HW_IPCC_ZIGBEE_SendM4RequestToM0(void) { - LL_C1_IPCC_SetFlag_CHx(IPCC, HW_IPCC_ZIGBEE_CMD_APPLI_CHANNEL); - LL_C1_IPCC_EnableTransmitChannel(IPCC, HW_IPCC_ZIGBEE_CMD_APPLI_CHANNEL); - - return; -} - -void HW_IPCC_ZIGBEE_SendM4AckToM0Notify(void) { - LL_C1_IPCC_ClearFlag_CHx(IPCC, HW_IPCC_ZIGBEE_APPLI_NOTIF_ACK_CHANNEL); - LL_C1_IPCC_EnableReceiveChannel(IPCC, HW_IPCC_ZIGBEE_APPLI_NOTIF_ACK_CHANNEL); - - return; -} - -static void HW_IPCC_ZIGBEE_CmdEvtHandler(void) { - LL_C1_IPCC_DisableTransmitChannel(IPCC, HW_IPCC_ZIGBEE_CMD_APPLI_CHANNEL); - - HW_IPCC_ZIGBEE_RecvAppliAckFromM0(); - - return; -} - -static void HW_IPCC_ZIGBEE_StackNotifEvtHandler(void) { - LL_C1_IPCC_DisableReceiveChannel(IPCC, HW_IPCC_ZIGBEE_APPLI_NOTIF_ACK_CHANNEL); - - HW_IPCC_ZIGBEE_RecvM0NotifyToM4(); - - return; -} - -static void HW_IPCC_ZIGBEE_StackM0RequestHandler(void) { - LL_C1_IPCC_DisableReceiveChannel(IPCC, HW_IPCC_ZIGBEE_M0_REQUEST_CHANNEL); - - HW_IPCC_ZIGBEE_RecvM0RequestToM4(); - - return; -} - -void HW_IPCC_ZIGBEE_SendM4AckToM0Request(void) { - LL_C1_IPCC_ClearFlag_CHx(IPCC, HW_IPCC_ZIGBEE_M0_REQUEST_CHANNEL); - LL_C1_IPCC_EnableReceiveChannel(IPCC, HW_IPCC_ZIGBEE_M0_REQUEST_CHANNEL); - - return; -} - -__weak void HW_IPCC_ZIGBEE_RecvAppliAckFromM0(void){}; -__weak void HW_IPCC_ZIGBEE_RecvM0NotifyToM4(void){}; -__weak void HW_IPCC_ZIGBEE_RecvM0RequestToM4(void){}; -#endif /* ZIGBEE_WB */ - -/****************************************************************************** - * MEMORY MANAGER - ******************************************************************************/ -void HW_IPCC_MM_SendFreeBuf(void (*cb)(void)) { +void HW_IPCC_MM_SendFreeBuf(void (*cb)()) { if(LL_C1_IPCC_IsActiveFlag_CHx(IPCC, HW_IPCC_MM_RELEASE_BUFFER_CHANNEL)) { FreeBufCb = cb; LL_C1_IPCC_EnableTransmitChannel(IPCC, HW_IPCC_MM_RELEASE_BUFFER_CHANNEL); @@ -557,37 +143,22 @@ void HW_IPCC_MM_SendFreeBuf(void (*cb)(void)) { LL_C1_IPCC_SetFlag_CHx(IPCC, HW_IPCC_MM_RELEASE_BUFFER_CHANNEL); } - - return; } -static void HW_IPCC_MM_FreeBufHandler(void) { +static void HW_IPCC_MM_FreeBufHandler() { LL_C1_IPCC_DisableTransmitChannel(IPCC, HW_IPCC_MM_RELEASE_BUFFER_CHANNEL); FreeBufCb(); LL_C1_IPCC_SetFlag_CHx(IPCC, HW_IPCC_MM_RELEASE_BUFFER_CHANNEL); - - return; } -/****************************************************************************** - * TRACES - ******************************************************************************/ -void HW_IPCC_TRACES_Init(void) { +void HW_IPCC_TRACES_Init() { LL_C1_IPCC_EnableReceiveChannel(IPCC, HW_IPCC_TRACES_CHANNEL); - - return; } -static void HW_IPCC_TRACES_EvtHandler(void) { +static void HW_IPCC_TRACES_EvtHandler() { HW_IPCC_TRACES_EvtNot(); LL_C1_IPCC_ClearFlag_CHx(IPCC, HW_IPCC_TRACES_CHANNEL); - - return; } - -__weak void HW_IPCC_TRACES_EvtNot(void){}; - -/******************* (C) COPYRIGHT 2019 STMicroelectronics *****END OF FILE****/ diff --git a/firmware/targets/f7/ble_glue/osal.h b/firmware/targets/f7/ble_glue/osal.h index e5e0c4f6896..0cde061794f 100644 --- a/firmware/targets/f7/ble_glue/osal.h +++ b/firmware/targets/f7/ble_glue/osal.h @@ -1,25 +1,4 @@ -/***************************************************************************** - * @file osal.h - * @author MDG - * @brief This header file defines the OS abstraction layer used by - * the BLE stack. OSAL defines the set of functions which needs to be - * ported to target operating system and target platform. - * Actually, only memset, memcpy and memcmp wrappers are defined. - ***************************************************************************** - * @attention - * - * Copyright (c) 2018-2022 STMicroelectronics. - * All rights reserved. - * - * This software is licensed under terms that can be found in the LICENSE file - * in the root directory of this software component. - * If no LICENSE file comes with this software, it is provided AS-IS. - * - ***************************************************************************** - */ - -#ifndef OSAL_H__ -#define OSAL_H__ +#pragma once /** * This function copies size number of bytes from a @@ -59,5 +38,3 @@ extern void* Osal_MemSet(void* ptr, int value, unsigned int size); * @return 0 if the two buffers are equal, 1 otherwise */ extern int Osal_MemCmp(const void* s1, const void* s2, unsigned int size); - -#endif /* OSAL_H__ */ diff --git a/firmware/targets/f7/ble_glue/tl_dbg_conf.h b/firmware/targets/f7/ble_glue/tl_dbg_conf.h index ce58af32b8a..daaa9d82ba8 100644 --- a/firmware/targets/f7/ble_glue/tl_dbg_conf.h +++ b/firmware/targets/f7/ble_glue/tl_dbg_conf.h @@ -1,39 +1,12 @@ -/* USER CODE BEGIN Header */ -/** - ****************************************************************************** - * File Name : App/tl_dbg_conf.h - * Description : Debug configuration file for stm32wpan transport layer interface. - * - ****************************************************************************** - * @attention - * - *

© Copyright (c) 2020 STMicroelectronics. - * All rights reserved.

- * - * This software component is licensed by ST under Ultimate Liberty license - * SLA0044, the "License"; You may not use this file except in compliance with - * the License. You may obtain a copy of the License at: - * www.st.com/SLA0044 - * - ****************************************************************************** - */ -/* USER CODE END Header */ +#pragma once -/* Define to prevent recursive inclusion -------------------------------------*/ -#ifndef __TL_DBG_CONF_H -#define __TL_DBG_CONF_H +#include "app_conf.h" /* required as some configuration used in dbg_trace.h are set there */ +#include "dbg_trace.h" #ifdef __cplusplus extern "C" { #endif -/* USER CODE BEGIN Tl_Conf */ - -/* Includes ------------------------------------------------------------------*/ -#include "app_conf.h" /* required as some configuration used in dbg_trace.h are set there */ -#include "dbg_trace.h" -#include "hw_if.h" - /** * Enable or Disable traces * The raw data output is the hci binary packet format as specified by the BT specification * @@ -124,12 +97,6 @@ extern "C" { #define TL_MM_DBG_MSG(...) #endif -/* USER CODE END Tl_Conf */ - #ifdef __cplusplus } #endif - -#endif /*__TL_DBG_CONF_H */ - -/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ diff --git a/firmware/targets/f7/ble_glue/utilities_conf.h b/firmware/targets/f7/ble_glue/utilities_conf.h deleted file mode 100644 index 9c15f2263f7..00000000000 --- a/firmware/targets/f7/ble_glue/utilities_conf.h +++ /dev/null @@ -1,68 +0,0 @@ -/* USER CODE BEGIN Header */ -/** - ****************************************************************************** - * File Name : utilities_conf.h - * Description : Configuration file for STM32 Utilities. - * - ****************************************************************************** - * @attention - * - *

© Copyright (c) 2019 STMicroelectronics. - * All rights reserved.

- * - * This software component is licensed by ST under BSD 3-Clause license, - * the "License"; You may not use this file except in compliance with the - * License. You may obtain a copy of the License at: - * opensource.org/licenses/BSD-3-Clause - * - ****************************************************************************** - */ -/* USER CODE END Header */ - -/* Define to prevent recursive inclusion -------------------------------------*/ -#ifndef UTILITIES_CONF_H -#define UTILITIES_CONF_H - -#ifdef __cplusplus -extern "C" { -#endif - -#include "cmsis_compiler.h" -#include "string.h" -#include - -/****************************************************************************** - * common - ******************************************************************************/ -#define UTILS_ENTER_CRITICAL_SECTION() FURI_CRITICAL_ENTER() - -#define UTILS_EXIT_CRITICAL_SECTION() FURI_CRITICAL_EXIT() - -#define UTILS_MEMSET8(dest, value, size) memset(dest, value, size); - -/****************************************************************************** - * tiny low power manager - * (any macro that does not need to be modified can be removed) - ******************************************************************************/ -#define UTIL_LPM_INIT_CRITICAL_SECTION() -#define UTIL_LPM_ENTER_CRITICAL_SECTION() UTILS_ENTER_CRITICAL_SECTION() -#define UTIL_LPM_EXIT_CRITICAL_SECTION() UTILS_EXIT_CRITICAL_SECTION() - -/****************************************************************************** - * sequencer - * (any macro that does not need to be modified can be removed) - ******************************************************************************/ -#define UTIL_SEQ_INIT_CRITICAL_SECTION() -#define UTIL_SEQ_ENTER_CRITICAL_SECTION() UTILS_ENTER_CRITICAL_SECTION() -#define UTIL_SEQ_EXIT_CRITICAL_SECTION() UTILS_EXIT_CRITICAL_SECTION() -#define UTIL_SEQ_CONF_TASK_NBR (32) -#define UTIL_SEQ_CONF_PRIO_NBR (2) -#define UTIL_SEQ_MEMSET8(dest, value, size) UTILS_MEMSET8(dest, value, size) - -#ifdef __cplusplus -} -#endif - -#endif /*UTILITIES_CONF_H */ - -/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ diff --git a/firmware/targets/f7/furi_hal/furi_hal_bt.c b/firmware/targets/f7/furi_hal/furi_hal_bt.c index 57aee0bf2a2..34818e56915 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_bt.c +++ b/firmware/targets/f7/furi_hal/furi_hal_bt.c @@ -2,7 +2,11 @@ #include #include + #include +#include + +#include #include #include diff --git a/firmware/targets/f7/furi_hal/furi_hal_clock.c b/firmware/targets/f7/furi_hal/furi_hal_clock.c index 736ad9f7c05..86c8fd46761 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_clock.c +++ b/firmware/targets/f7/furi_hal/furi_hal_clock.c @@ -4,19 +4,26 @@ #include #include +#include #include #include +#include +#include + #define TAG "FuriHalClock" -#define CPU_CLOCK_HZ_EARLY 4000000 -#define CPU_CLOCK_HZ_MAIN 64000000 +#define CPU_CLOCK_EARLY_HZ 4000000 +#define CPU_CLOCK_HSI16_HZ 16000000 +#define CPU_CLOCK_HSE_HZ 32000000 +#define CPU_CLOCK_PLL_HZ 64000000 + #define TICK_INT_PRIORITY 15U #define HS_CLOCK_IS_READY() (LL_RCC_HSE_IsReady() && LL_RCC_HSI_IsReady()) #define LS_CLOCK_IS_READY() (LL_RCC_LSE_IsReady() && LL_RCC_LSI1_IsReady()) void furi_hal_clock_init_early() { - LL_SetSystemCoreClock(CPU_CLOCK_HZ_EARLY); + LL_SetSystemCoreClock(CPU_CLOCK_EARLY_HZ); LL_Init1msTick(SystemCoreClock); } @@ -24,11 +31,6 @@ void furi_hal_clock_deinit_early() { } void furi_hal_clock_init() { - /* Prepare Flash memory for 64MHz system clock */ - LL_FLASH_SetLatency(LL_FLASH_LATENCY_3); - while(LL_FLASH_GetLatency() != LL_FLASH_LATENCY_3) - ; - /* HSE and HSI configuration and activation */ LL_RCC_HSE_SetCapacitorTuning(0x26); LL_RCC_HSE_Enable(); @@ -49,9 +51,6 @@ void furi_hal_clock_init() { while(!LS_CLOCK_IS_READY()) ; - /* RF wakeup */ - LL_RCC_SetRFWKPClockSource(LL_RCC_RFWKP_CLKSOURCE_LSE); - LL_EXTI_EnableIT_0_31( LL_EXTI_LINE_18); /* Why? Because that's why. See RM0434, Table 61. CPU1 vector table. */ LL_EXTI_EnableRisingTrig_0_31(LL_EXTI_LINE_18); @@ -79,12 +78,17 @@ void furi_hal_clock_init() { ; /* Sysclk activation on the main PLL */ - /* Set CPU1 prescaler*/ + /* Set CPU1 prescaler */ LL_RCC_SetAHBPrescaler(LL_RCC_SYSCLK_DIV_1); - /* Set CPU2 prescaler*/ + /* Set CPU2 prescaler, from this point we are not allowed to touch it. */ LL_C2_RCC_SetAHBPrescaler(LL_RCC_SYSCLK_DIV_2); + /* Prepare Flash memory for work on 64MHz system clock */ + LL_FLASH_SetLatency(LL_FLASH_LATENCY_3); + while(LL_FLASH_GetLatency() != LL_FLASH_LATENCY_3) + ; + LL_RCC_SetSysClkSource(LL_RCC_SYS_CLKSOURCE_PLL); while(LL_RCC_GetSysClkSource() != LL_RCC_SYS_CLKSOURCE_STATUS_PLL) ; @@ -104,7 +108,7 @@ void furi_hal_clock_init() { ; /* Update CMSIS variable (which can be updated also through SystemCoreClockUpdate function) */ - LL_SetSystemCoreClock(CPU_CLOCK_HZ_MAIN); + LL_SetSystemCoreClock(CPU_CLOCK_PLL_HZ); /* Update the time base */ LL_Init1msTick(SystemCoreClock); @@ -122,7 +126,7 @@ void furi_hal_clock_init() { FURI_LOG_I(TAG, "Init OK"); } -void furi_hal_clock_switch_to_hsi() { +void furi_hal_clock_switch_hse2hsi() { LL_RCC_HSI_Enable(); while(!LL_RCC_HSI_IsReady()) @@ -134,38 +138,27 @@ void furi_hal_clock_switch_to_hsi() { while(LL_RCC_GetSysClkSource() != LL_RCC_SYS_CLKSOURCE_STATUS_HSI) ; - LL_C2_RCC_SetAHBPrescaler(LL_RCC_SYSCLK_DIV_1); - LL_FLASH_SetLatency(LL_FLASH_LATENCY_0); while(LL_FLASH_GetLatency() != LL_FLASH_LATENCY_0) ; } -void furi_hal_clock_switch_to_pll() { +void furi_hal_clock_switch_hsi2hse() { #ifdef FURI_HAL_CLOCK_TRACK_STARTUP uint32_t clock_start_time = DWT->CYCCNT; #endif LL_RCC_HSE_Enable(); - LL_RCC_PLL_Enable(); - LL_RCC_PLLSAI1_Enable(); - while(!LL_RCC_HSE_IsReady()) ; - while(!LL_RCC_PLL_IsReady()) - ; - while(!LL_RCC_PLLSAI1_IsReady()) - ; - LL_C2_RCC_SetAHBPrescaler(LL_RCC_SYSCLK_DIV_2); - - LL_FLASH_SetLatency(LL_FLASH_LATENCY_3); - while(LL_FLASH_GetLatency() != LL_FLASH_LATENCY_3) + LL_FLASH_SetLatency(LL_FLASH_LATENCY_1); + while(LL_FLASH_GetLatency() != LL_FLASH_LATENCY_1) ; - LL_RCC_SetSysClkSource(LL_RCC_SYS_CLKSOURCE_PLL); + LL_RCC_SetSysClkSource(LL_RCC_SYS_CLKSOURCE_HSE); - while(LL_RCC_GetSysClkSource() != LL_RCC_SYS_CLKSOURCE_STATUS_PLL) + while(LL_RCC_GetSysClkSource() != LL_RCC_SYS_CLKSOURCE_STATUS_HSE) ; #ifdef FURI_HAL_CLOCK_TRACK_STARTUP @@ -176,6 +169,48 @@ void furi_hal_clock_switch_to_pll() { #endif } +bool furi_hal_clock_switch_hse2pll() { + furi_assert(LL_RCC_GetSysClkSource() == LL_RCC_SYS_CLKSOURCE_STATUS_HSE); + + LL_RCC_PLL_Enable(); + LL_RCC_PLLSAI1_Enable(); + + while(!LL_RCC_PLL_IsReady()) + ; + while(!LL_RCC_PLLSAI1_IsReady()) + ; + + if(SHCI_C2_SetSystemClock(SET_SYSTEM_CLOCK_HSE_TO_PLL) != SHCI_Success) { + return false; + } + + furi_check(LL_RCC_GetSysClkSource() == LL_RCC_SYS_CLKSOURCE_STATUS_PLL); + + LL_SetSystemCoreClock(CPU_CLOCK_PLL_HZ); + SysTick->LOAD = (uint32_t)((SystemCoreClock / 1000) - 1UL); + + return true; +} + +bool furi_hal_clock_switch_pll2hse() { + furi_assert(LL_RCC_GetSysClkSource() == LL_RCC_SYS_CLKSOURCE_STATUS_PLL); + + LL_RCC_HSE_Enable(); + while(!LL_RCC_HSE_IsReady()) + ; + + if(SHCI_C2_SetSystemClock(SET_SYSTEM_CLOCK_PLL_ON_TO_HSE) != SHCI_Success) { + return false; + } + + furi_check(LL_RCC_GetSysClkSource() == LL_RCC_SYS_CLKSOURCE_STATUS_HSE); + + LL_SetSystemCoreClock(CPU_CLOCK_HSE_HZ); + SysTick->LOAD = (uint32_t)((SystemCoreClock / 1000) - 1UL); + + return true; +} + void furi_hal_clock_suspend_tick() { CLEAR_BIT(SysTick->CTRL, SysTick_CTRL_ENABLE_Msk); } diff --git a/firmware/targets/f7/furi_hal/furi_hal_clock.h b/firmware/targets/f7/furi_hal/furi_hal_clock.h index 5e651bbd359..3100b619f60 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_clock.h +++ b/firmware/targets/f7/furi_hal/furi_hal_clock.h @@ -1,11 +1,12 @@ #pragma once +#include +#include + #ifdef __cplusplus extern "C" { #endif -#include - typedef enum { FuriHalClockMcoLse, FuriHalClockMcoSysclk, @@ -40,11 +41,23 @@ void furi_hal_clock_deinit_early(); /** Initialize clocks */ void furi_hal_clock_init(); -/** Switch to HSI clock */ -void furi_hal_clock_switch_to_hsi(); +/** Switch clock from HSE to HSI */ +void furi_hal_clock_switch_hse2hsi(); + +/** Switch clock from HSI to HSE */ +void furi_hal_clock_switch_hsi2hse(); + +/** Switch clock from HSE to PLL + * + * @return true if changed, false if failed or not possible at this moment + */ +bool furi_hal_clock_switch_hse2pll(); -/** Switch to PLL clock */ -void furi_hal_clock_switch_to_pll(); +/** Switch clock from PLL to HSE + * + * @return true if changed, false if failed or not possible at this moment + */ +bool furi_hal_clock_switch_pll2hse(); /** Stop SysTick counter without resetting */ void furi_hal_clock_suspend_tick(); diff --git a/firmware/targets/f7/furi_hal/furi_hal_console.c b/firmware/targets/f7/furi_hal/furi_hal_console.c index 2bdc57250ad..0b113d2dac5 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_console.c +++ b/firmware/targets/f7/furi_hal/furi_hal_console.c @@ -5,8 +5,6 @@ #include #include -#include - #include #define TAG "FuriHalConsole" diff --git a/firmware/targets/f7/furi_hal/furi_hal_flash.c b/firmware/targets/f7/furi_hal/furi_hal_flash.c index 796d20b19fa..bc65b29eb61 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_flash.c +++ b/firmware/targets/f7/furi_hal/furi_hal_flash.c @@ -7,6 +7,9 @@ #include #include +#include + +#include #define TAG "FuriHalFlash" diff --git a/firmware/targets/f7/furi_hal/furi_hal_memory.c b/firmware/targets/f7/furi_hal/furi_hal_memory.c index 7f69b90ca2b..3f8df1f4409 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_memory.c +++ b/firmware/targets/f7/furi_hal/furi_hal_memory.c @@ -4,9 +4,6 @@ #define TAG "FuriHalMemory" -// STM(TM) Copro(TM) bug(TM) workaround size -#define RAM2B_COPRO_GAP_SIZE_KB 2 - typedef enum { SRAM_A, SRAM_B, @@ -38,13 +35,20 @@ void furi_hal_memory_init() { uint32_t sbrsa = (FLASH->SRRVR & FLASH_SRRVR_SBRSA_Msk) >> FLASH_SRRVR_SBRSA_Pos; uint32_t snbrsa = (FLASH->SRRVR & FLASH_SRRVR_SNBRSA_Msk) >> FLASH_SRRVR_SNBRSA_Pos; + // STM(TM) Copro(TM) bug(TM): SNBRSA is incorrect if stack version is higher than 1.13 and lower than 1.17.2+ + // Radio core started, but not yet ready, so we'll try to guess + // This will be true only if BLE light radio stack used, + // 0x0D is known to be incorrect, 0x0B is known to be correct since 1.17.2+ + // Lower value by 2 pages to match real memory layout + if(snbrsa > 0x0B) { + FURI_LOG_E(TAG, "SNBRSA workaround"); + snbrsa -= 2; + } + uint32_t sram2a_busy_size = (uint32_t)&__sram2a_free__ - (uint32_t)&__sram2a_start__; uint32_t sram2a_unprotected_size = (sbrsa)*1024; uint32_t sram2b_unprotected_size = (snbrsa)*1024; - // STM(TM) Copro(TM) bug(TM) workaround - sram2b_unprotected_size -= 1024 * RAM2B_COPRO_GAP_SIZE_KB; - memory->region[SRAM_A].start = (uint8_t*)&__sram2a_free__; memory->region[SRAM_B].start = (uint8_t*)&__sram2b_start__; diff --git a/firmware/targets/f7/furi_hal/furi_hal_os.c b/firmware/targets/f7/furi_hal/furi_hal_os.c index 3fc1fbea848..046cf79dc1d 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_os.c +++ b/firmware/targets/f7/furi_hal/furi_hal_os.c @@ -170,27 +170,35 @@ void vPortSuppressTicksAndSleep(TickType_t expected_idle_ticks) { return; } + // Core2 shenanigans takes extra time, so we want to compensate tick skew by reducing sleep duration by 1 tick + TickType_t unexpected_idle_ticks = expected_idle_ticks - 1; + // Limit amount of ticks to maximum that timer can count - if(expected_idle_ticks > FURI_HAL_OS_MAX_SLEEP) { - expected_idle_ticks = FURI_HAL_OS_MAX_SLEEP; + if(unexpected_idle_ticks > FURI_HAL_OS_MAX_SLEEP) { + unexpected_idle_ticks = FURI_HAL_OS_MAX_SLEEP; } // Stop IRQ handling, no one should disturb us till we finish __disable_irq(); + do { + // Confirm OS that sleep is still possible + if(eTaskConfirmSleepModeStatus() == eAbortSleep || furi_hal_os_is_pending_irq()) { + break; + } - // Confirm OS that sleep is still possible - if(eTaskConfirmSleepModeStatus() == eAbortSleep || furi_hal_os_is_pending_irq()) { - __enable_irq(); - return; - } - - // Sleep and track how much ticks we spent sleeping - uint32_t completed_ticks = furi_hal_os_sleep(expected_idle_ticks); - // Notify system about time spent in sleep - if(completed_ticks > 0) { - vTaskStepTick(MIN(completed_ticks, expected_idle_ticks)); - } - + // Sleep and track how much ticks we spent sleeping + uint32_t completed_ticks = furi_hal_os_sleep(unexpected_idle_ticks); + // Notify system about time spent in sleep + if(completed_ticks > 0) { + if(completed_ticks > expected_idle_ticks) { +#ifdef FURI_HAL_OS_DEBUG + furi_hal_console_printf(">%lu\r\n", completed_ticks - expected_idle_ticks); +#endif + completed_ticks = expected_idle_ticks; + } + vTaskStepTick(completed_ticks); + } + } while(0); // Reenable IRQ __enable_irq(); } diff --git a/firmware/targets/f7/furi_hal/furi_hal_power.c b/firmware/targets/f7/furi_hal/furi_hal_power.c index 035919d784d..c14de856970 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_power.c +++ b/firmware/targets/f7/furi_hal/furi_hal_power.c @@ -13,7 +13,7 @@ #include #include -#include +#include #include #include #include @@ -162,6 +162,11 @@ static inline void furi_hal_power_resume_aux_periphs() { static inline void furi_hal_power_deep_sleep() { furi_hal_power_suspend_aux_periphs(); + if(!furi_hal_clock_switch_pll2hse()) { + // Hello core2 my old friend + return; + } + while(LL_HSEM_1StepLock(HSEM, CFG_HW_RCC_SEMID)) ; @@ -171,13 +176,13 @@ static inline void furi_hal_power_deep_sleep() { LL_HSEM_ReleaseLock(HSEM, CFG_HW_ENTRY_STOP_MODE_SEMID, 0); // The switch on HSI before entering Stop Mode is required - furi_hal_clock_switch_to_hsi(); + furi_hal_clock_switch_hse2hsi(); } } else { /** * The switch on HSI before entering Stop Mode is required */ - furi_hal_clock_switch_to_hsi(); + furi_hal_clock_switch_hse2hsi(); } /* Release RCC semaphore */ @@ -201,12 +206,14 @@ static inline void furi_hal_power_deep_sleep() { while(LL_HSEM_1StepLock(HSEM, CFG_HW_RCC_SEMID)) ; - if(LL_RCC_GetSysClkSource() != LL_RCC_SYS_CLKSOURCE_STATUS_PLL) { - furi_hal_clock_switch_to_pll(); + if(LL_RCC_GetSysClkSource() != LL_RCC_SYS_CLKSOURCE_STATUS_HSE) { + furi_hal_clock_switch_hsi2hse(); } LL_HSEM_ReleaseLock(HSEM, CFG_HW_RCC_SEMID, 0); + furi_check(furi_hal_clock_switch_hse2pll()); + furi_hal_power_resume_aux_periphs(); furi_hal_rtc_sync_shadow(); } diff --git a/firmware/targets/f7/furi_hal/furi_hal_random.c b/firmware/targets/f7/furi_hal/furi_hal_random.c index cf4b552f6d8..225519303b1 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_random.c +++ b/firmware/targets/f7/furi_hal/furi_hal_random.c @@ -6,7 +6,7 @@ #include #include -#include +#include #define TAG "FuriHalRandom" diff --git a/lib/stm32wb.scons b/lib/stm32wb.scons index 94a1c7075aa..8a8ad964496 100644 --- a/lib/stm32wb.scons +++ b/lib/stm32wb.scons @@ -64,7 +64,6 @@ sources += [ "stm32wb_copro/wpan/ble/core/auto/ble_l2cap_aci.c", "stm32wb_copro/wpan/ble/core/template/osal.c", "stm32wb_copro/wpan/utilities/dbg_trace.c", - "stm32wb_copro/wpan/utilities/otp.c", "stm32wb_copro/wpan/utilities/stm_list.c", ] diff --git a/lib/stm32wb_copro b/lib/stm32wb_copro index 6c9c54f0566..bbccbefae26 160000 --- a/lib/stm32wb_copro +++ b/lib/stm32wb_copro @@ -1 +1 @@ -Subproject commit 6c9c54f05669b2c4d436df58bb691d3b0d7c86df +Subproject commit bbccbefae26a2301b8a4b58e57ebdeb93c08269b diff --git a/scripts/ob.data b/scripts/ob.data index 605faccbfc6..9f6f1d08161 100644 --- a/scripts/ob.data +++ b/scripts/ob.data @@ -14,15 +14,15 @@ IWDGSTOP:0x1:rw IWDGSW:0x1:rw IPCCDBA:0x0:rw ESE:0x1:r -SFSA:0xD5:r +SFSA:0xD7:r FSD:0x0:r DDS:0x1:r C2OPT:0x1:r NBRSD:0x0:r -SNBRSA:0xD:r +SNBRSA:0xB:r BRSD:0x0:r SBRSA:0x12:r -SBRV:0x35400:r +SBRV:0x35C00:r PCROP1A_STRT:0x1FF:r PCROP1A_END:0x0:r PCROP_RDP:0x1:rw diff --git a/scripts/update.py b/scripts/update.py index 9f0d95d94ee..c315ef8adef 100755 --- a/scripts/update.py +++ b/scripts/update.py @@ -73,6 +73,9 @@ def init(self): self.parser_generate.add_argument( "--I-understand-what-I-am-doing", dest="disclaimer", required=False ) + self.parser_generate.add_argument( + "--stackversion", dest="stack_version", required=False, default="" + ) self.parser_generate.set_defaults(func=self.generate) @@ -93,6 +96,13 @@ def generate(self): if not self.args.radiotype: raise ValueError("Missing --radiotype") radio_meta = CoproBinary(self.args.radiobin) + if self.args.stack_version: + actual_stack_version_str = f"{radio_meta.img_sig.version_major}.{radio_meta.img_sig.version_minor}.{radio_meta.img_sig.version_sub}" + if actual_stack_version_str != self.args.stack_version: + self.logger.error( + f"Stack version mismatch: expected {self.args.stack_version}, actual {actual_stack_version_str}" + ) + return 1 radio_version = self.copro_version_as_int(radio_meta, self.args.radiotype) if ( get_stack_type(self.args.radiotype) not in self.WHITELISTED_STACK_TYPES From f46018b2046edc7e49546db4c5b7f375c828ab46 Mon Sep 17 00:00:00 2001 From: agarof <18119032+agarof@users.noreply.github.com> Date: Thu, 21 Sep 2023 09:54:25 +0200 Subject: [PATCH 759/824] Add extended I2C HAL functions (#3037) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add extended I2C HAL functions * Rename I2CEndClockStretch to I2CEndAwaitRestart * Address review comments * Update f18 api_symbols.csv * FuriHal: check input values in cortex timer * FuriHal: cleanup I2C documentation * Properly bump api symbols * FuriHal: fix incorrect cast in I2C write_reg methods, fix spelling and naming * FuriHal: cleanup const usage in I2C, sync declaration and implementation * Format Sources * FuriHal: more i2c docs * Add I2C Restart and Pause / Resume test * Add I2C auto-reload test * UnitTests: skip furi_hal_i2c_ext_eeprom if eeprom is not connected * UnitTest: cleanup subghz test output * FuriHal: classic timeouts in i2c Co-authored-by: hedger Co-authored-by: あく --- .../unit_tests/furi_hal/furi_hal_tests.c | 115 +++++ .../debug/unit_tests/subghz/subghz_test.c | 13 +- firmware/targets/f18/api_symbols.csv | 14 +- firmware/targets/f7/api_symbols.csv | 14 +- .../targets/f7/furi_hal/furi_hal_cortex.c | 5 + firmware/targets/f7/furi_hal/furi_hal_i2c.c | 431 ++++++++++-------- .../targets/furi_hal_include/furi_hal_i2c.h | 215 ++++++--- 7 files changed, 534 insertions(+), 273 deletions(-) diff --git a/applications/debug/unit_tests/furi_hal/furi_hal_tests.c b/applications/debug/unit_tests/furi_hal/furi_hal_tests.c index e942c5933b0..2dbaa4d8689 100644 --- a/applications/debug/unit_tests/furi_hal/furi_hal_tests.c +++ b/applications/debug/unit_tests/furi_hal/furi_hal_tests.c @@ -5,6 +5,11 @@ #include "../minunit.h" #define DATA_SIZE 4 +#define EEPROM_ADDRESS 0b10101000 +#define EEPROM_ADDRESS_HIGH (EEPROM_ADDRESS | 0b10) +#define EEPROM_SIZE 512 +#define EEPROM_PAGE_SIZE 16 +#define EEPROM_WRITE_DELAY_MS 6 static void furi_hal_i2c_int_setup() { furi_hal_i2c_acquire(&furi_hal_i2c_handle_power); @@ -14,6 +19,14 @@ static void furi_hal_i2c_int_teardown() { furi_hal_i2c_release(&furi_hal_i2c_handle_power); } +static void furi_hal_i2c_ext_setup() { + furi_hal_i2c_acquire(&furi_hal_i2c_handle_external); +} + +static void furi_hal_i2c_ext_teardown() { + furi_hal_i2c_release(&furi_hal_i2c_handle_external); +} + MU_TEST(furi_hal_i2c_int_1b) { bool ret = false; uint8_t data_one = 0; @@ -103,14 +116,116 @@ MU_TEST(furi_hal_i2c_int_1b_fail) { mu_assert(data_one != 0, "9 invalid data"); } +MU_TEST(furi_hal_i2c_int_ext_3b) { + bool ret = false; + uint8_t data_many[DATA_SIZE] = {0}; + + // 3 byte: read + data_many[0] = LP5562_CHANNEL_BLUE_CURRENT_REGISTER; + ret = furi_hal_i2c_tx_ext( + &furi_hal_i2c_handle_power, + LP5562_ADDRESS, + false, + data_many, + 1, + FuriHalI2cBeginStart, + FuriHalI2cEndAwaitRestart, + LP5562_I2C_TIMEOUT); + mu_assert(ret, "3 tx failed"); + + // Send a RESTART condition, then read the 3 bytes one after the other + ret = furi_hal_i2c_rx_ext( + &furi_hal_i2c_handle_power, + LP5562_ADDRESS, + false, + data_many + 1, + 1, + FuriHalI2cBeginRestart, + FuriHalI2cEndPause, + LP5562_I2C_TIMEOUT); + mu_assert(ret, "4 rx failed"); + mu_assert(data_many[1] != 0, "4 invalid data"); + ret = furi_hal_i2c_rx_ext( + &furi_hal_i2c_handle_power, + LP5562_ADDRESS, + false, + data_many + 2, + 1, + FuriHalI2cBeginResume, + FuriHalI2cEndPause, + LP5562_I2C_TIMEOUT); + mu_assert(ret, "5 rx failed"); + mu_assert(data_many[2] != 0, "5 invalid data"); + ret = furi_hal_i2c_rx_ext( + &furi_hal_i2c_handle_power, + LP5562_ADDRESS, + false, + data_many + 3, + 1, + FuriHalI2cBeginResume, + FuriHalI2cEndStop, + LP5562_I2C_TIMEOUT); + mu_assert(ret, "6 rx failed"); + mu_assert(data_many[3] != 0, "6 invalid data"); +} + +MU_TEST(furi_hal_i2c_ext_eeprom) { + if(!furi_hal_i2c_is_device_ready(&furi_hal_i2c_handle_external, EEPROM_ADDRESS, 100)) { + printf("no device connected, skipping\r\n"); + return; + } + + bool ret = false; + uint8_t buffer[EEPROM_SIZE] = {0}; + + for(size_t page = 0; page < (EEPROM_SIZE / EEPROM_PAGE_SIZE); ++page) { + // Fill page buffer + for(size_t page_byte = 0; page_byte < EEPROM_PAGE_SIZE; ++page_byte) { + // Each byte is its position in the EEPROM modulo 256 + uint8_t byte = ((page * EEPROM_PAGE_SIZE) + page_byte) % 256; + + buffer[page_byte] = byte; + } + + uint8_t address = (page < 16) ? EEPROM_ADDRESS : EEPROM_ADDRESS_HIGH; + + ret = furi_hal_i2c_write_mem( + &furi_hal_i2c_handle_external, + address, + page * EEPROM_PAGE_SIZE, + buffer, + EEPROM_PAGE_SIZE, + 20); + + mu_assert(ret, "EEPROM write failed"); + furi_delay_ms(EEPROM_WRITE_DELAY_MS); + } + + ret = furi_hal_i2c_read_mem( + &furi_hal_i2c_handle_external, EEPROM_ADDRESS, 0, buffer, EEPROM_SIZE, 100); + + mu_assert(ret, "EEPROM read failed"); + + for(size_t pos = 0; pos < EEPROM_SIZE; ++pos) { + mu_assert_int_eq(pos % 256, buffer[pos]); + } +} + MU_TEST_SUITE(furi_hal_i2c_int_suite) { MU_SUITE_CONFIGURE(&furi_hal_i2c_int_setup, &furi_hal_i2c_int_teardown); MU_RUN_TEST(furi_hal_i2c_int_1b); MU_RUN_TEST(furi_hal_i2c_int_3b); + MU_RUN_TEST(furi_hal_i2c_int_ext_3b); MU_RUN_TEST(furi_hal_i2c_int_1b_fail); } +MU_TEST_SUITE(furi_hal_i2c_ext_suite) { + MU_SUITE_CONFIGURE(&furi_hal_i2c_ext_setup, &furi_hal_i2c_ext_teardown); + MU_RUN_TEST(furi_hal_i2c_ext_eeprom); +} + int run_minunit_test_furi_hal() { MU_RUN_SUITE(furi_hal_i2c_int_suite); + MU_RUN_SUITE(furi_hal_i2c_ext_suite); return MU_EXIT_CODE; } diff --git a/applications/debug/unit_tests/subghz/subghz_test.c b/applications/debug/unit_tests/subghz/subghz_test.c index 1900f204554..64e0591dfa6 100644 --- a/applications/debug/unit_tests/subghz/subghz_test.c +++ b/applications/debug/unit_tests/subghz/subghz_test.c @@ -98,9 +98,9 @@ static bool subghz_decoder_test(const char* path, const char* name_decoder) { } subghz_file_encoder_worker_free(file_worker_encoder_handler); } - FURI_LOG_T(TAG, "\r\n Decoder count parse \033[0;33m%d\033[0m ", subghz_test_decoder_count); + FURI_LOG_T(TAG, "Decoder count parse %d", subghz_test_decoder_count); if(furi_get_tick() - test_start > TEST_TIMEOUT) { - printf("\033[0;31mTest decoder %s ERROR TimeOut\033[0m\r\n", name_decoder); + printf("Test decoder %s ERROR TimeOut\r\n", name_decoder); return false; } else { return subghz_test_decoder_count ? true : false; @@ -137,9 +137,9 @@ static bool subghz_decode_random_test(const char* path) { } subghz_file_encoder_worker_free(file_worker_encoder_handler); } - FURI_LOG_D(TAG, "\r\n Decoder count parse \033[0;33m%d\033[0m ", subghz_test_decoder_count); + FURI_LOG_D(TAG, "Decoder count parse %d", subghz_test_decoder_count); if(furi_get_tick() - test_start > TEST_TIMEOUT * 10) { - printf("\033[0;31mRandom test ERROR TimeOut\033[0m\r\n"); + printf("Random test ERROR TimeOut\r\n"); return false; } else if(subghz_test_decoder_count == TEST_RANDOM_COUNT_PARSE) { return true; @@ -200,10 +200,9 @@ static bool subghz_encoder_test(const char* path) { subghz_transmitter_free(transmitter); } flipper_format_free(fff_data_file); - FURI_LOG_T(TAG, "\r\n Decoder count parse \033[0;33m%d\033[0m ", subghz_test_decoder_count); + FURI_LOG_T(TAG, "Decoder count parse %d", subghz_test_decoder_count); if(furi_get_tick() - test_start > TEST_TIMEOUT) { - printf( - "\033[0;31mTest encoder %s ERROR TimeOut\033[0m\r\n", furi_string_get_cstr(temp_str)); + printf("Test encoder %s ERROR TimeOut\r\n", furi_string_get_cstr(temp_str)); subghz_test_decoder_count = 0; } furi_string_free(temp_str); diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index bc17ff2fa8a..f4e990becb5 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,38.0,, +Version,+,39.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -1105,14 +1105,16 @@ Function,-,furi_hal_i2c_deinit_early,void, Function,-,furi_hal_i2c_init,void, Function,-,furi_hal_i2c_init_early,void, Function,+,furi_hal_i2c_is_device_ready,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint32_t" -Function,+,furi_hal_i2c_read_mem,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint8_t*, uint8_t, uint32_t" +Function,+,furi_hal_i2c_read_mem,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint8_t*, size_t, uint32_t" Function,+,furi_hal_i2c_read_reg_16,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint16_t*, uint32_t" Function,+,furi_hal_i2c_read_reg_8,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint8_t*, uint32_t" Function,+,furi_hal_i2c_release,void,FuriHalI2cBusHandle* -Function,+,furi_hal_i2c_rx,_Bool,"FuriHalI2cBusHandle*, const uint8_t, uint8_t*, const uint8_t, uint32_t" -Function,+,furi_hal_i2c_trx,_Bool,"FuriHalI2cBusHandle*, const uint8_t, const uint8_t*, const uint8_t, uint8_t*, const uint8_t, uint32_t" -Function,+,furi_hal_i2c_tx,_Bool,"FuriHalI2cBusHandle*, const uint8_t, const uint8_t*, const uint8_t, uint32_t" -Function,+,furi_hal_i2c_write_mem,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint8_t*, uint8_t, uint32_t" +Function,+,furi_hal_i2c_rx,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t*, size_t, uint32_t" +Function,+,furi_hal_i2c_rx_ext,_Bool,"FuriHalI2cBusHandle*, uint16_t, _Bool, uint8_t*, size_t, FuriHalI2cBegin, FuriHalI2cEnd, uint32_t" +Function,+,furi_hal_i2c_trx,_Bool,"FuriHalI2cBusHandle*, uint8_t, const uint8_t*, size_t, uint8_t*, size_t, uint32_t" +Function,+,furi_hal_i2c_tx,_Bool,"FuriHalI2cBusHandle*, uint8_t, const uint8_t*, size_t, uint32_t" +Function,+,furi_hal_i2c_tx_ext,_Bool,"FuriHalI2cBusHandle*, uint16_t, _Bool, const uint8_t*, size_t, FuriHalI2cBegin, FuriHalI2cEnd, uint32_t" +Function,+,furi_hal_i2c_write_mem,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, const uint8_t*, size_t, uint32_t" Function,+,furi_hal_i2c_write_reg_16,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint16_t, uint32_t" Function,+,furi_hal_i2c_write_reg_8,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint8_t, uint32_t" Function,+,furi_hal_info_get,void,"PropertyValueCallback, char, void*" diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 26fad6b58fd..d37d5f333df 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,38.0,, +Version,+,39.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -1176,14 +1176,16 @@ Function,-,furi_hal_i2c_deinit_early,void, Function,-,furi_hal_i2c_init,void, Function,-,furi_hal_i2c_init_early,void, Function,+,furi_hal_i2c_is_device_ready,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint32_t" -Function,+,furi_hal_i2c_read_mem,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint8_t*, uint8_t, uint32_t" +Function,+,furi_hal_i2c_read_mem,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint8_t*, size_t, uint32_t" Function,+,furi_hal_i2c_read_reg_16,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint16_t*, uint32_t" Function,+,furi_hal_i2c_read_reg_8,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint8_t*, uint32_t" Function,+,furi_hal_i2c_release,void,FuriHalI2cBusHandle* -Function,+,furi_hal_i2c_rx,_Bool,"FuriHalI2cBusHandle*, const uint8_t, uint8_t*, const uint8_t, uint32_t" -Function,+,furi_hal_i2c_trx,_Bool,"FuriHalI2cBusHandle*, const uint8_t, const uint8_t*, const uint8_t, uint8_t*, const uint8_t, uint32_t" -Function,+,furi_hal_i2c_tx,_Bool,"FuriHalI2cBusHandle*, const uint8_t, const uint8_t*, const uint8_t, uint32_t" -Function,+,furi_hal_i2c_write_mem,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint8_t*, uint8_t, uint32_t" +Function,+,furi_hal_i2c_rx,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t*, size_t, uint32_t" +Function,+,furi_hal_i2c_rx_ext,_Bool,"FuriHalI2cBusHandle*, uint16_t, _Bool, uint8_t*, size_t, FuriHalI2cBegin, FuriHalI2cEnd, uint32_t" +Function,+,furi_hal_i2c_trx,_Bool,"FuriHalI2cBusHandle*, uint8_t, const uint8_t*, size_t, uint8_t*, size_t, uint32_t" +Function,+,furi_hal_i2c_tx,_Bool,"FuriHalI2cBusHandle*, uint8_t, const uint8_t*, size_t, uint32_t" +Function,+,furi_hal_i2c_tx_ext,_Bool,"FuriHalI2cBusHandle*, uint16_t, _Bool, const uint8_t*, size_t, FuriHalI2cBegin, FuriHalI2cEnd, uint32_t" +Function,+,furi_hal_i2c_write_mem,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, const uint8_t*, size_t, uint32_t" Function,+,furi_hal_i2c_write_reg_16,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint16_t, uint32_t" Function,+,furi_hal_i2c_write_reg_8,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint8_t, uint32_t" Function,+,furi_hal_ibutton_emulate_set_next,void,uint32_t diff --git a/firmware/targets/f7/furi_hal/furi_hal_cortex.c b/firmware/targets/f7/furi_hal/furi_hal_cortex.c index 3fbe384e3c8..6b5efc376c0 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_cortex.c +++ b/firmware/targets/f7/furi_hal/furi_hal_cortex.c @@ -15,8 +15,11 @@ void furi_hal_cortex_init_early() { } void furi_hal_cortex_delay_us(uint32_t microseconds) { + furi_check(microseconds < (UINT32_MAX / FURI_HAL_CORTEX_INSTRUCTIONS_PER_MICROSECOND)); + uint32_t start = DWT->CYCCNT; uint32_t time_ticks = FURI_HAL_CORTEX_INSTRUCTIONS_PER_MICROSECOND * microseconds; + while((DWT->CYCCNT - start) < time_ticks) { }; } @@ -26,6 +29,8 @@ uint32_t furi_hal_cortex_instructions_per_microsecond() { } FuriHalCortexTimer furi_hal_cortex_timer_get(uint32_t timeout_us) { + furi_check(timeout_us < (UINT32_MAX / FURI_HAL_CORTEX_INSTRUCTIONS_PER_MICROSECOND)); + FuriHalCortexTimer cortex_timer = {0}; cortex_timer.start = DWT->CYCCNT; cortex_timer.value = FURI_HAL_CORTEX_INSTRUCTIONS_PER_MICROSECOND * timeout_us; diff --git a/firmware/targets/f7/furi_hal/furi_hal_i2c.c b/firmware/targets/f7/furi_hal/furi_hal_i2c.c index bdfe0b0a3ee..8c7b054e3e4 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_i2c.c +++ b/firmware/targets/f7/furi_hal/furi_hal_i2c.c @@ -5,7 +5,6 @@ #include #include -#include #include #define TAG "FuriHalI2c" @@ -27,7 +26,7 @@ void furi_hal_i2c_acquire(FuriHalI2cBusHandle* handle) { furi_hal_power_insomnia_enter(); // Lock bus access handle->bus->callback(handle->bus, FuriHalI2cBusEventLock); - // Ensuree that no active handle set + // Ensure that no active handle set furi_check(handle->bus->current_handle == NULL); // Set current handle handle->bus->current_handle = handle; @@ -51,177 +50,265 @@ void furi_hal_i2c_release(FuriHalI2cBusHandle* handle) { furi_hal_power_insomnia_exit(); } -bool furi_hal_i2c_tx( - FuriHalI2cBusHandle* handle, - uint8_t address, - const uint8_t* data, - uint8_t size, - uint32_t timeout) { - furi_check(handle->bus->current_handle == handle); - furi_assert(timeout > 0); +static bool + furi_hal_i2c_wait_for_idle(I2C_TypeDef* i2c, FuriHalI2cBegin begin, FuriHalCortexTimer timer) { + do { + if(furi_hal_cortex_timer_is_expired(timer)) { + return false; + } + } while(begin == FuriHalI2cBeginStart && LL_I2C_IsActiveFlag_BUSY(i2c)); + // Only check if the bus is busy if starting a new transaction, if not we already control the bus + + return true; +} + +static bool + furi_hal_i2c_wait_for_end(I2C_TypeDef* i2c, FuriHalI2cEnd end, FuriHalCortexTimer timer) { + // If ending the transaction with a stop condition, wait for it to be detected, otherwise wait for a transfer complete flag + bool wait_for_stop = end == FuriHalI2cEndStop; + uint32_t end_mask = (wait_for_stop) ? I2C_ISR_STOPF : (I2C_ISR_TC | I2C_ISR_TCR); + + while((i2c->ISR & end_mask) == 0) { + if(furi_hal_cortex_timer_is_expired(timer)) { + return false; + } + } + return true; +} + +static uint32_t + furi_hal_i2c_get_start_signal(FuriHalI2cBegin begin, bool ten_bit_address, bool read) { + switch(begin) { + case FuriHalI2cBeginRestart: + if(read) { + return ten_bit_address ? LL_I2C_GENERATE_RESTART_10BIT_READ : + LL_I2C_GENERATE_RESTART_7BIT_READ; + } else { + return ten_bit_address ? LL_I2C_GENERATE_RESTART_10BIT_WRITE : + LL_I2C_GENERATE_RESTART_7BIT_WRITE; + } + case FuriHalI2cBeginResume: + return LL_I2C_GENERATE_NOSTARTSTOP; + case FuriHalI2cBeginStart: + default: + return read ? LL_I2C_GENERATE_START_READ : LL_I2C_GENERATE_START_WRITE; + } +} + +static uint32_t furi_hal_i2c_get_end_signal(FuriHalI2cEnd end) { + switch(end) { + case FuriHalI2cEndAwaitRestart: + return LL_I2C_MODE_SOFTEND; + case FuriHalI2cEndPause: + return LL_I2C_MODE_RELOAD; + case FuriHalI2cEndStop: + default: + return LL_I2C_MODE_AUTOEND; + } +} + +static bool furi_hal_i2c_transfer_is_aborted(I2C_TypeDef* i2c) { + return LL_I2C_IsActiveFlag_STOP(i2c) && + !(LL_I2C_IsActiveFlag_TC(i2c) || LL_I2C_IsActiveFlag_TCR(i2c)); +} + +static bool furi_hal_i2c_transfer( + I2C_TypeDef* i2c, + uint8_t* data, + uint32_t size, + FuriHalI2cEnd end, + bool read, + FuriHalCortexTimer timer) { bool ret = true; - FuriHalCortexTimer timer = furi_hal_cortex_timer_get(timeout * 1000); - do { - while(LL_I2C_IsActiveFlag_BUSY(handle->bus->i2c)) { - if(furi_hal_cortex_timer_is_expired(timer)) { - ret = false; - break; - } + while(size > 0) { + bool should_stop = furi_hal_cortex_timer_is_expired(timer) || + furi_hal_i2c_transfer_is_aborted(i2c); + + // Modifying the data pointer's data is UB if read is true + if(read && LL_I2C_IsActiveFlag_RXNE(i2c)) { + *data = LL_I2C_ReceiveData8(i2c); + data++; + size--; + } else if(!read && LL_I2C_IsActiveFlag_TXIS(i2c)) { + LL_I2C_TransmitData8(i2c, *data); + data++; + size--; } - if(!ret) { + // Exit on timeout or premature stop, probably caused by a nacked address or byte + if(should_stop) { + ret = size == 0; // If the transfer was over, still a success break; } + } - LL_I2C_HandleTransfer( - handle->bus->i2c, - address, - LL_I2C_ADDRSLAVE_7BIT, - size, - LL_I2C_MODE_AUTOEND, - LL_I2C_GENERATE_START_WRITE); - - while(!LL_I2C_IsActiveFlag_STOP(handle->bus->i2c) || size > 0) { - if(LL_I2C_IsActiveFlag_TXIS(handle->bus->i2c)) { - LL_I2C_TransmitData8(handle->bus->i2c, (*data)); - data++; - size--; - } - - if(furi_hal_cortex_timer_is_expired(timer)) { - ret = false; - break; - } - } + if(ret) { + ret = furi_hal_i2c_wait_for_end(i2c, end, timer); + } - LL_I2C_ClearFlag_STOP(handle->bus->i2c); - } while(0); + LL_I2C_ClearFlag_STOP(i2c); return ret; } -bool furi_hal_i2c_rx( +static bool furi_hal_i2c_transaction( + I2C_TypeDef* i2c, + uint16_t address, + bool ten_bit, + uint8_t* data, + size_t size, + FuriHalI2cBegin begin, + FuriHalI2cEnd end, + bool read, + FuriHalCortexTimer timer) { + uint32_t addr_size = ten_bit ? LL_I2C_ADDRSLAVE_10BIT : LL_I2C_ADDRSLAVE_7BIT; + uint32_t start_signal = furi_hal_i2c_get_start_signal(begin, ten_bit, read); + + if(!furi_hal_i2c_wait_for_idle(i2c, begin, timer)) { + return false; + } + + do { + uint8_t transfer_size = size; + FuriHalI2cEnd transfer_end = end; + + if(size > 255) { + transfer_size = 255; + transfer_end = FuriHalI2cEndPause; + } + + uint32_t end_signal = furi_hal_i2c_get_end_signal(transfer_end); + + LL_I2C_HandleTransfer(i2c, address, addr_size, transfer_size, end_signal, start_signal); + + if(!furi_hal_i2c_transfer(i2c, data, transfer_size, transfer_end, read, timer)) { + return false; + } + + size -= transfer_size; + data += transfer_size; + start_signal = LL_I2C_GENERATE_NOSTARTSTOP; + } while(size > 0); + + return true; +} + +bool furi_hal_i2c_rx_ext( FuriHalI2cBusHandle* handle, - uint8_t address, + uint16_t address, + bool ten_bit, uint8_t* data, - uint8_t size, + size_t size, + FuriHalI2cBegin begin, + FuriHalI2cEnd end, uint32_t timeout) { furi_check(handle->bus->current_handle == handle); - furi_assert(timeout > 0); - bool ret = true; FuriHalCortexTimer timer = furi_hal_cortex_timer_get(timeout * 1000); - do { - while(LL_I2C_IsActiveFlag_BUSY(handle->bus->i2c)) { - if(furi_hal_cortex_timer_is_expired(timer)) { - ret = false; - break; - } - } + return furi_hal_i2c_transaction( + handle->bus->i2c, address, ten_bit, data, size, begin, end, true, timer); +} - if(!ret) { - break; - } +bool furi_hal_i2c_tx_ext( + FuriHalI2cBusHandle* handle, + uint16_t address, + bool ten_bit, + const uint8_t* data, + size_t size, + FuriHalI2cBegin begin, + FuriHalI2cEnd end, + uint32_t timeout) { + furi_check(handle->bus->current_handle == handle); - LL_I2C_HandleTransfer( - handle->bus->i2c, - address, - LL_I2C_ADDRSLAVE_7BIT, - size, - LL_I2C_MODE_AUTOEND, - LL_I2C_GENERATE_START_READ); - - while(!LL_I2C_IsActiveFlag_STOP(handle->bus->i2c) || size > 0) { - if(LL_I2C_IsActiveFlag_RXNE(handle->bus->i2c)) { - *data = LL_I2C_ReceiveData8(handle->bus->i2c); - data++; - size--; - } - - if(furi_hal_cortex_timer_is_expired(timer)) { - ret = false; - break; - } - } + FuriHalCortexTimer timer = furi_hal_cortex_timer_get(timeout * 1000); - LL_I2C_ClearFlag_STOP(handle->bus->i2c); - } while(0); + return furi_hal_i2c_transaction( + handle->bus->i2c, address, ten_bit, (uint8_t*)data, size, begin, end, false, timer); +} - return ret; +bool furi_hal_i2c_tx( + FuriHalI2cBusHandle* handle, + uint8_t address, + const uint8_t* data, + size_t size, + uint32_t timeout) { + furi_assert(timeout > 0); + + return furi_hal_i2c_tx_ext( + handle, address, false, data, size, FuriHalI2cBeginStart, FuriHalI2cEndStop, timeout); +} + +bool furi_hal_i2c_rx( + FuriHalI2cBusHandle* handle, + uint8_t address, + uint8_t* data, + size_t size, + uint32_t timeout) { + furi_assert(timeout > 0); + + return furi_hal_i2c_rx_ext( + handle, address, false, data, size, FuriHalI2cBeginStart, FuriHalI2cEndStop, timeout); } bool furi_hal_i2c_trx( FuriHalI2cBusHandle* handle, uint8_t address, const uint8_t* tx_data, - uint8_t tx_size, + size_t tx_size, uint8_t* rx_data, - uint8_t rx_size, + size_t rx_size, uint32_t timeout) { - if(furi_hal_i2c_tx(handle, address, tx_data, tx_size, timeout) && - furi_hal_i2c_rx(handle, address, rx_data, rx_size, timeout)) { - return true; - } else { - return false; - } + return furi_hal_i2c_tx_ext( + handle, + address, + false, + tx_data, + tx_size, + FuriHalI2cBeginStart, + FuriHalI2cEndStop, + timeout) && + furi_hal_i2c_rx_ext( + handle, + address, + false, + rx_data, + rx_size, + FuriHalI2cBeginStart, + FuriHalI2cEndStop, + timeout); } bool furi_hal_i2c_is_device_ready(FuriHalI2cBusHandle* handle, uint8_t i2c_addr, uint32_t timeout) { furi_check(handle); - furi_check(handle->bus->current_handle == handle); furi_assert(timeout > 0); bool ret = true; FuriHalCortexTimer timer = furi_hal_cortex_timer_get(timeout * 1000); - do { - while(LL_I2C_IsActiveFlag_BUSY(handle->bus->i2c)) { - if(furi_hal_cortex_timer_is_expired(timer)) { - return false; - } - } - - handle->bus->i2c->CR2 = - ((((uint32_t)(i2c_addr) & (I2C_CR2_SADD)) | (I2C_CR2_START) | (I2C_CR2_AUTOEND)) & - (~I2C_CR2_RD_WRN)); - - while((!LL_I2C_IsActiveFlag_NACK(handle->bus->i2c)) && - (!LL_I2C_IsActiveFlag_STOP(handle->bus->i2c))) { - if(furi_hal_cortex_timer_is_expired(timer)) { - return false; - } - } - - if(LL_I2C_IsActiveFlag_NACK(handle->bus->i2c)) { - while(!LL_I2C_IsActiveFlag_STOP(handle->bus->i2c)) { - if(furi_hal_cortex_timer_is_expired(timer)) { - return false; - } - } - - LL_I2C_ClearFlag_NACK(handle->bus->i2c); - - // Clear STOP Flag generated by autoend - LL_I2C_ClearFlag_STOP(handle->bus->i2c); + if(!furi_hal_i2c_wait_for_idle(handle->bus->i2c, FuriHalI2cBeginStart, timer)) { + return false; + } - // Generate actual STOP - LL_I2C_GenerateStopCondition(handle->bus->i2c); + LL_I2C_HandleTransfer( + handle->bus->i2c, + i2c_addr, + LL_I2C_ADDRSLAVE_7BIT, + 0, + LL_I2C_MODE_AUTOEND, + LL_I2C_GENERATE_START_WRITE); - ret = false; - } + if(!furi_hal_i2c_wait_for_end(handle->bus->i2c, FuriHalI2cEndStop, timer)) { + return false; + } - while(!LL_I2C_IsActiveFlag_STOP(handle->bus->i2c)) { - if(furi_hal_cortex_timer_is_expired(timer)) { - return false; - } - } + ret = !LL_I2C_IsActiveFlag_NACK(handle->bus->i2c); - LL_I2C_ClearFlag_STOP(handle->bus->i2c); - } while(0); + LL_I2C_ClearFlag_NACK(handle->bus->i2c); + LL_I2C_ClearFlag_STOP(handle->bus->i2c); return ret; } @@ -257,7 +344,7 @@ bool furi_hal_i2c_read_mem( uint8_t i2c_addr, uint8_t mem_addr, uint8_t* data, - uint8_t len, + size_t len, uint32_t timeout) { furi_check(handle); @@ -272,11 +359,12 @@ bool furi_hal_i2c_write_reg_8( uint32_t timeout) { furi_check(handle); - uint8_t tx_data[2]; - tx_data[0] = reg_addr; - tx_data[1] = data; + const uint8_t tx_data[2] = { + reg_addr, + data, + }; - return furi_hal_i2c_tx(handle, i2c_addr, (const uint8_t*)&tx_data, 2, timeout); + return furi_hal_i2c_tx(handle, i2c_addr, tx_data, 2, timeout); } bool furi_hal_i2c_write_reg_16( @@ -287,69 +375,42 @@ bool furi_hal_i2c_write_reg_16( uint32_t timeout) { furi_check(handle); - uint8_t tx_data[3]; - tx_data[0] = reg_addr; - tx_data[1] = (data >> 8) & 0xFF; - tx_data[2] = data & 0xFF; + const uint8_t tx_data[3] = { + reg_addr, + (data >> 8) & 0xFF, + data & 0xFF, + }; - return furi_hal_i2c_tx(handle, i2c_addr, (const uint8_t*)&tx_data, 3, timeout); + return furi_hal_i2c_tx(handle, i2c_addr, tx_data, 3, timeout); } bool furi_hal_i2c_write_mem( FuriHalI2cBusHandle* handle, uint8_t i2c_addr, uint8_t mem_addr, - uint8_t* data, - uint8_t len, + const uint8_t* data, + size_t len, uint32_t timeout) { furi_check(handle); - furi_check(handle->bus->current_handle == handle); furi_assert(timeout > 0); - bool ret = true; - uint8_t size = len + 1; - FuriHalCortexTimer timer = furi_hal_cortex_timer_get(timeout * 1000); - - do { - while(LL_I2C_IsActiveFlag_BUSY(handle->bus->i2c)) { - if(furi_hal_cortex_timer_is_expired(timer)) { - ret = false; - break; - } - } - - if(!ret) { - break; - } - - LL_I2C_HandleTransfer( - handle->bus->i2c, - i2c_addr, - LL_I2C_ADDRSLAVE_7BIT, - size, - LL_I2C_MODE_AUTOEND, - LL_I2C_GENERATE_START_WRITE); - - while(!LL_I2C_IsActiveFlag_STOP(handle->bus->i2c) || size > 0) { - if(LL_I2C_IsActiveFlag_TXIS(handle->bus->i2c)) { - if(size == len + 1) { - LL_I2C_TransmitData8(handle->bus->i2c, mem_addr); - } else { - LL_I2C_TransmitData8(handle->bus->i2c, (*data)); - data++; - } - size--; - } - - if(furi_hal_cortex_timer_is_expired(timer)) { - ret = false; - break; - } - } - - LL_I2C_ClearFlag_STOP(handle->bus->i2c); - } while(0); - - return ret; + return furi_hal_i2c_tx_ext( + handle, + i2c_addr, + false, + &mem_addr, + 1, + FuriHalI2cBeginStart, + FuriHalI2cEndPause, + timeout) && + furi_hal_i2c_tx_ext( + handle, + i2c_addr, + false, + data, + len, + FuriHalI2cBeginResume, + FuriHalI2cEndStop, + timeout); } diff --git a/firmware/targets/furi_hal_include/furi_hal_i2c.h b/firmware/targets/furi_hal_include/furi_hal_i2c.h index 566574ab826..f493655b4d2 100644 --- a/firmware/targets/furi_hal_include/furi_hal_i2c.h +++ b/firmware/targets/furi_hal_include/furi_hal_i2c.h @@ -1,11 +1,11 @@ /** - * @file furi_hal_i2c.h - * I2C HAL API + * @file furi_hal_i2c.h I2C HAL API */ #pragma once #include +#include #include #include @@ -13,6 +13,35 @@ extern "C" { #endif +/** Transaction beginning signal */ +typedef enum { + /*!Begin the transaction by sending a START condition followed by the + * address */ + FuriHalI2cBeginStart, + /*!Begin the transaction by sending a RESTART condition followed by the + * address + * @note Must follow a transaction ended with + * FuriHalI2cEndAwaitRestart */ + FuriHalI2cBeginRestart, + /*!Continue the previous transaction with new data + * @note Must follow a transaction ended with FuriHalI2cEndPause and + * be of the same type (RX/TX) */ + FuriHalI2cBeginResume, +} FuriHalI2cBegin; + +/** Transaction end signal */ +typedef enum { + /*!End the transaction by sending a STOP condition */ + FuriHalI2cEndStop, + /*!End the transaction by clock stretching + * @note Must be followed by a transaction using + * FuriHalI2cBeginRestart */ + FuriHalI2cEndAwaitRestart, + /*!Pauses the transaction by clock stretching + * @note Must be followed by a transaction using FuriHalI2cBeginResume */ + FuriHalI2cEndPause, +} FuriHalI2cEnd; + /** Early Init I2C */ void furi_hal_i2c_init_early(); @@ -22,78 +51,126 @@ void furi_hal_i2c_deinit_early(); /** Init I2C */ void furi_hal_i2c_init(); -/** Acquire i2c bus handle +/** Acquire I2C bus handle * - * @return Instance of FuriHalI2cBus + * @param handle Pointer to FuriHalI2cBusHandle instance */ void furi_hal_i2c_acquire(FuriHalI2cBusHandle* handle); -/** Release i2c bus handle - * - * @param bus instance of FuriHalI2cBus aquired in `furi_hal_i2c_acquire` +/** Release I2C bus handle + * + * @param handle Pointer to FuriHalI2cBusHandle instance acquired in + * `furi_hal_i2c_acquire` */ void furi_hal_i2c_release(FuriHalI2cBusHandle* handle); -/** Perform I2C tx transfer +/** Perform I2C TX transfer * - * @param handle pointer to FuriHalI2cBusHandle instance + * @param handle Pointer to FuriHalI2cBusHandle instance * @param address I2C slave address - * @param data pointer to data buffer - * @param size size of data buffer - * @param timeout timeout in ticks + * @param data Pointer to data buffer + * @param size Size of data buffer + * @param timeout Timeout in milliseconds * * @return true on successful transfer, false otherwise */ bool furi_hal_i2c_tx( FuriHalI2cBusHandle* handle, - const uint8_t address, + uint8_t address, + const uint8_t* data, + size_t size, + uint32_t timeout); + +/** + * Perform I2C TX transfer, with additional settings. + * + * @param handle Pointer to FuriHalI2cBusHandle instance + * @param address I2C slave address + * @param ten_bit Whether the address is 10 bits wide + * @param data Pointer to data buffer + * @param size Size of data buffer + * @param begin How to begin the transaction + * @param end How to end the transaction + * @param timer Timeout timer + * + * @return true on successful transfer, false otherwise + */ +bool furi_hal_i2c_tx_ext( + FuriHalI2cBusHandle* handle, + uint16_t address, + bool ten_bit, const uint8_t* data, - const uint8_t size, + size_t size, + FuriHalI2cBegin begin, + FuriHalI2cEnd end, uint32_t timeout); -/** Perform I2C rx transfer +/** Perform I2C RX transfer * - * @param handle pointer to FuriHalI2cBusHandle instance + * @param handle Pointer to FuriHalI2cBusHandle instance * @param address I2C slave address - * @param data pointer to data buffer - * @param size size of data buffer - * @param timeout timeout in ticks + * @param data Pointer to data buffer + * @param size Size of data buffer + * @param timeout Timeout in milliseconds * * @return true on successful transfer, false otherwise */ bool furi_hal_i2c_rx( FuriHalI2cBusHandle* handle, - const uint8_t address, + uint8_t address, + uint8_t* data, + size_t size, + uint32_t timeout); + +/** Perform I2C RX transfer, with additional settings. + * + * @param handle Pointer to FuriHalI2cBusHandle instance + * @param address I2C slave address + * @param ten_bit Whether the address is 10 bits wide + * @param data Pointer to data buffer + * @param size Size of data buffer + * @param begin How to begin the transaction + * @param end How to end the transaction + * @param timer Timeout timer + * + * @return true on successful transfer, false otherwise + */ +bool furi_hal_i2c_rx_ext( + FuriHalI2cBusHandle* handle, + uint16_t address, + bool ten_bit, uint8_t* data, - const uint8_t size, + size_t size, + FuriHalI2cBegin begin, + FuriHalI2cEnd end, uint32_t timeout); -/** Perform I2C tx and rx transfers +/** Perform I2C TX and RX transfers * - * @param handle pointer to FuriHalI2cBusHandle instance + * @param handle Pointer to FuriHalI2cBusHandle instance * @param address I2C slave address - * @param tx_data pointer to tx data buffer - * @param tx_size size of tx data buffer - * @param rx_data pointer to rx data buffer - * @param rx_size size of rx data buffer - * @param timeout timeout in ticks + * @param tx_data Pointer to TX data buffer + * @param tx_size Size of TX data buffer + * @param rx_data Pointer to RX data buffer + * @param rx_size Size of RX data buffer + * @param timeout Timeout in milliseconds * * @return true on successful transfer, false otherwise */ bool furi_hal_i2c_trx( FuriHalI2cBusHandle* handle, - const uint8_t address, + uint8_t address, const uint8_t* tx_data, - const uint8_t tx_size, + size_t tx_size, uint8_t* rx_data, - const uint8_t rx_size, + size_t rx_size, uint32_t timeout); /** Check if I2C device presents on bus * - * @param handle pointer to FuriHalI2cBusHandle instance - * @param i2c_addr I2C slave address - * @param timeout timeout in ticks + * @param handle Pointer to FuriHalI2cBusHandle instance + * @param i2c_addr I2C slave address + * @param timeout Timeout in milliseconds * * @return true if device present and is ready, false otherwise */ @@ -101,11 +178,11 @@ bool furi_hal_i2c_is_device_ready(FuriHalI2cBusHandle* handle, uint8_t i2c_addr, /** Perform I2C device register read (8-bit) * - * @param handle pointer to FuriHalI2cBusHandle instance - * @param i2c_addr I2C slave address - * @param reg_addr register address - * @param data pointer to register value - * @param timeout timeout in ticks + * @param handle Pointer to FuriHalI2cBusHandle instance + * @param i2c_addr I2C slave address + * @param reg_addr Register address + * @param data Pointer to register value + * @param timeout Timeout in milliseconds * * @return true on successful transfer, false otherwise */ @@ -118,11 +195,11 @@ bool furi_hal_i2c_read_reg_8( /** Perform I2C device register read (16-bit) * - * @param handle pointer to FuriHalI2cBusHandle instance - * @param i2c_addr I2C slave address - * @param reg_addr register address - * @param data pointer to register value - * @param timeout timeout in ticks + * @param handle Pointer to FuriHalI2cBusHandle instance + * @param i2c_addr I2C slave address + * @param reg_addr Register address + * @param data Pointer to register value + * @param timeout Timeout in milliseconds * * @return true on successful transfer, false otherwise */ @@ -135,12 +212,12 @@ bool furi_hal_i2c_read_reg_16( /** Perform I2C device memory read * - * @param handle pointer to FuriHalI2cBusHandle instance - * @param i2c_addr I2C slave address - * @param mem_addr memory start address - * @param data pointer to data buffer - * @param len size of data buffer - * @param timeout timeout in ticks + * @param handle Pointer to FuriHalI2cBusHandle instance + * @param i2c_addr I2C slave address + * @param mem_addr Memory start address + * @param data Pointer to data buffer + * @param len Size of data buffer + * @param timeout Timeout in milliseconds * * @return true on successful transfer, false otherwise */ @@ -149,16 +226,16 @@ bool furi_hal_i2c_read_mem( uint8_t i2c_addr, uint8_t mem_addr, uint8_t* data, - uint8_t len, + size_t len, uint32_t timeout); /** Perform I2C device register write (8-bit) * - * @param handle pointer to FuriHalI2cBusHandle instance - * @param i2c_addr I2C slave address - * @param reg_addr register address - * @param data register value - * @param timeout timeout in ticks + * @param handle Pointer to FuriHalI2cBusHandle instance + * @param i2c_addr I2C slave address + * @param reg_addr Register address + * @param data Register value + * @param timeout Timeout in milliseconds * * @return true on successful transfer, false otherwise */ @@ -171,11 +248,11 @@ bool furi_hal_i2c_write_reg_8( /** Perform I2C device register write (16-bit) * - * @param handle pointer to FuriHalI2cBusHandle instance - * @param i2c_addr I2C slave address - * @param reg_addr register address - * @param data register value - * @param timeout timeout in ticks + * @param handle Pointer to FuriHalI2cBusHandle instance + * @param i2c_addr I2C slave address + * @param reg_addr Register address + * @param data Register value + * @param timeout Timeout in milliseconds * * @return true on successful transfer, false otherwise */ @@ -188,12 +265,12 @@ bool furi_hal_i2c_write_reg_16( /** Perform I2C device memory * - * @param handle pointer to FuriHalI2cBusHandle instance - * @param i2c_addr I2C slave address - * @param mem_addr memory start address - * @param data pointer to data buffer - * @param len size of data buffer - * @param timeout timeout in ticks + * @param handle Pointer to FuriHalI2cBusHandle instance + * @param i2c_addr I2C slave address + * @param mem_addr Memory start address + * @param data Pointer to data buffer + * @param len Size of data buffer + * @param timeout Timeout in milliseconds * * @return true on successful transfer, false otherwise */ @@ -201,8 +278,8 @@ bool furi_hal_i2c_write_mem( FuriHalI2cBusHandle* handle, uint8_t i2c_addr, uint8_t mem_addr, - uint8_t* data, - uint8_t len, + const uint8_t* data, + size_t len, uint32_t timeout); #ifdef __cplusplus From 182c8defb1af98ef5c11f3ece013061da4283112 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Thu, 21 Sep 2023 17:06:45 +0900 Subject: [PATCH 760/824] [FL-3458] Add confirmation before exiting USB-UART (#3043) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add confirmation before exiting USB-UART * Redo the confirm view to be a scene to ignore the back button Co-authored-by: hedger Co-authored-by: あく --- applications/main/gpio/gpio_app.c | 7 +++ applications/main/gpio/gpio_app_i.h | 3 ++ .../main/gpio/scenes/gpio_scene_config.h | 1 + .../gpio/scenes/gpio_scene_exit_confirm.c | 44 +++++++++++++++++++ .../main/gpio/scenes/gpio_scene_usb_uart.c | 3 ++ 5 files changed, 58 insertions(+) create mode 100644 applications/main/gpio/scenes/gpio_scene_exit_confirm.c diff --git a/applications/main/gpio/gpio_app.c b/applications/main/gpio/gpio_app.c index 1ecff1ec2ed..020fbf79a12 100644 --- a/applications/main/gpio/gpio_app.c +++ b/applications/main/gpio/gpio_app.c @@ -43,6 +43,11 @@ GpioApp* gpio_app_alloc() { app->notifications = furi_record_open(RECORD_NOTIFICATION); + // Dialog view + app->dialog = dialog_ex_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, GpioAppViewExitConfirm, dialog_ex_get_view(app->dialog)); + app->var_item_list = variable_item_list_alloc(); view_dispatcher_add_view( app->view_dispatcher, @@ -79,10 +84,12 @@ void gpio_app_free(GpioApp* app) { view_dispatcher_remove_view(app->view_dispatcher, GpioAppViewUsbUart); view_dispatcher_remove_view(app->view_dispatcher, GpioAppViewUsbUartCfg); view_dispatcher_remove_view(app->view_dispatcher, GpioAppViewUsbUartCloseRpc); + view_dispatcher_remove_view(app->view_dispatcher, GpioAppViewExitConfirm); variable_item_list_free(app->var_item_list); widget_free(app->widget); gpio_test_free(app->gpio_test); gpio_usb_uart_free(app->gpio_usb_uart); + dialog_ex_free(app->dialog); // View dispatcher view_dispatcher_free(app->view_dispatcher); diff --git a/applications/main/gpio/gpio_app_i.h b/applications/main/gpio/gpio_app_i.h index 03fe9f48949..d54ffd36826 100644 --- a/applications/main/gpio/gpio_app_i.h +++ b/applications/main/gpio/gpio_app_i.h @@ -13,6 +13,7 @@ #include #include #include +#include #include "views/gpio_test.h" #include "views/gpio_usb_uart.h" #include @@ -23,6 +24,7 @@ struct GpioApp { ViewDispatcher* view_dispatcher; SceneManager* scene_manager; Widget* widget; + DialogEx* dialog; VariableItemList* var_item_list; VariableItem* var_item_flow; @@ -39,4 +41,5 @@ typedef enum { GpioAppViewUsbUart, GpioAppViewUsbUartCfg, GpioAppViewUsbUartCloseRpc, + GpioAppViewExitConfirm, } GpioAppView; diff --git a/applications/main/gpio/scenes/gpio_scene_config.h b/applications/main/gpio/scenes/gpio_scene_config.h index 3406e42d3a2..d6fd24d19d3 100644 --- a/applications/main/gpio/scenes/gpio_scene_config.h +++ b/applications/main/gpio/scenes/gpio_scene_config.h @@ -3,3 +3,4 @@ ADD_SCENE(gpio, test, Test) ADD_SCENE(gpio, usb_uart, UsbUart) ADD_SCENE(gpio, usb_uart_cfg, UsbUartCfg) ADD_SCENE(gpio, usb_uart_close_rpc, UsbUartCloseRpc) +ADD_SCENE(gpio, exit_confirm, ExitConfirm) diff --git a/applications/main/gpio/scenes/gpio_scene_exit_confirm.c b/applications/main/gpio/scenes/gpio_scene_exit_confirm.c new file mode 100644 index 00000000000..efb0734a314 --- /dev/null +++ b/applications/main/gpio/scenes/gpio_scene_exit_confirm.c @@ -0,0 +1,44 @@ +#include "gpio_app_i.h" + +void gpio_scene_exit_confirm_dialog_callback(DialogExResult result, void* context) { + GpioApp* app = context; + + view_dispatcher_send_custom_event(app->view_dispatcher, result); +} + +void gpio_scene_exit_confirm_on_enter(void* context) { + GpioApp* app = context; + DialogEx* dialog = app->dialog; + + dialog_ex_set_context(dialog, app); + dialog_ex_set_left_button_text(dialog, "Exit"); + dialog_ex_set_right_button_text(dialog, "Stay"); + dialog_ex_set_header(dialog, "Exit USB-UART?", 22, 12, AlignLeft, AlignTop); + dialog_ex_set_result_callback(dialog, gpio_scene_exit_confirm_dialog_callback); + + view_dispatcher_switch_to_view(app->view_dispatcher, GpioAppViewExitConfirm); +} + +bool gpio_scene_exit_confirm_on_event(void* context, SceneManagerEvent event) { + GpioApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == DialogExResultRight) { + consumed = scene_manager_previous_scene(app->scene_manager); + } else if(event.event == DialogExResultLeft) { + scene_manager_search_and_switch_to_previous_scene(app->scene_manager, GpioSceneStart); + } + } else if(event.type == SceneManagerEventTypeBack) { + consumed = true; + } + + return consumed; +} + +void gpio_scene_exit_confirm_on_exit(void* context) { + GpioApp* app = context; + + // Clean view + dialog_ex_reset(app->dialog); +} diff --git a/applications/main/gpio/scenes/gpio_scene_usb_uart.c b/applications/main/gpio/scenes/gpio_scene_usb_uart.c index c5e085192b2..9a3514ca4f5 100644 --- a/applications/main/gpio/scenes/gpio_scene_usb_uart.c +++ b/applications/main/gpio/scenes/gpio_scene_usb_uart.c @@ -42,6 +42,9 @@ bool gpio_scene_usb_uart_on_event(void* context, SceneManagerEvent event) { scene_manager_set_scene_state(app->scene_manager, GpioSceneUsbUart, 1); scene_manager_next_scene(app->scene_manager, GpioSceneUsbUartCfg); return true; + } else if(event.type == SceneManagerEventTypeBack) { + scene_manager_next_scene(app->scene_manager, GpioSceneExitConfirm); + return true; } else if(event.type == SceneManagerEventTypeTick) { uint32_t tx_cnt_last = scene_usb_uart->state.tx_cnt; uint32_t rx_cnt_last = scene_usb_uart->state.rx_cnt; From a089aeb2bd60d4c39905a21af87bfb7603bb4e9d Mon Sep 17 00:00:00 2001 From: Filipe Paz Rodrigues Date: Thu, 21 Sep 2023 02:09:00 -0700 Subject: [PATCH 761/824] Add Initial CCID support (#3048) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add Initial CCID support * Sync api symbols * Format sources Co-authored-by: あく --- applications/debug/ccid_test/application.fam | 16 + applications/debug/ccid_test/ccid_test_app.c | 159 ++++++ .../debug/ccid_test/iso7816_t0_apdu.c | 36 ++ .../debug/ccid_test/iso7816_t0_apdu.h | 32 ++ firmware/targets/f18/api_symbols.csv | 8 +- firmware/targets/f7/api_symbols.csv | 8 +- .../targets/f7/furi_hal/furi_hal_usb_ccid.c | 498 ++++++++++++++++++ firmware/targets/furi_hal_include/furi_hal.h | 1 + .../targets/furi_hal_include/furi_hal_usb.h | 1 + .../furi_hal_include/furi_hal_usb_ccid.h | 31 ++ lib/libusb_stm32 | 2 +- 11 files changed, 789 insertions(+), 3 deletions(-) create mode 100644 applications/debug/ccid_test/application.fam create mode 100644 applications/debug/ccid_test/ccid_test_app.c create mode 100644 applications/debug/ccid_test/iso7816_t0_apdu.c create mode 100644 applications/debug/ccid_test/iso7816_t0_apdu.h create mode 100644 firmware/targets/f7/furi_hal/furi_hal_usb_ccid.c create mode 100644 firmware/targets/furi_hal_include/furi_hal_usb_ccid.h diff --git a/applications/debug/ccid_test/application.fam b/applications/debug/ccid_test/application.fam new file mode 100644 index 00000000000..e0cbc8d85e4 --- /dev/null +++ b/applications/debug/ccid_test/application.fam @@ -0,0 +1,16 @@ +App( + appid="ccid_test", + name="CCID Debug", + apptype=FlipperAppType.DEBUG, + entry_point="ccid_test_app", + cdefines=["CCID_TEST"], + requires=[ + "gui", + ], + provides=[ + "ccid_test", + ], + stack_size=1 * 1024, + order=120, + fap_category="Debug", +) diff --git a/applications/debug/ccid_test/ccid_test_app.c b/applications/debug/ccid_test/ccid_test_app.c new file mode 100644 index 00000000000..a2f936d742d --- /dev/null +++ b/applications/debug/ccid_test/ccid_test_app.c @@ -0,0 +1,159 @@ +#include +#include +#include + +#include +#include +#include +#include + +#include "iso7816_t0_apdu.h" + +typedef enum { + EventTypeInput, +} EventType; + +typedef struct { + Gui* gui; + ViewPort* view_port; + FuriMessageQueue* event_queue; + FuriHalUsbCcidConfig ccid_cfg; +} CcidTestApp; + +typedef struct { + union { + InputEvent input; + }; + EventType type; +} CcidTestAppEvent; + +typedef enum { + CcidTestSubmenuIndexInsertSmartcard, + CcidTestSubmenuIndexRemoveSmartcard, + CcidTestSubmenuIndexInsertSmartcardReader +} SubmenuIndex; + +void icc_power_on_callback(uint8_t* atrBuffer, uint32_t* atrlen, void* context) { + UNUSED(context); + + iso7816_answer_to_reset(atrBuffer, atrlen); +} + +void xfr_datablock_callback(uint8_t* dataBlock, uint32_t* dataBlockLen, void* context) { + UNUSED(context); + + struct ISO7816_Response_APDU responseAPDU; + //class not supported + responseAPDU.SW1 = 0x6E; + responseAPDU.SW2 = 0x00; + + iso7816_write_response_apdu(&responseAPDU, dataBlock, dataBlockLen); +} + +static const CcidCallbacks ccid_cb = { + icc_power_on_callback, + xfr_datablock_callback, +}; + +static void ccid_test_app_render_callback(Canvas* canvas, void* ctx) { + UNUSED(ctx); + canvas_clear(canvas); + + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 0, 10, "CCID Test App"); + + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 0, 63, "Hold [back] to exit"); +} + +static void ccid_test_app__input_callback(InputEvent* input_event, void* ctx) { + FuriMessageQueue* event_queue = ctx; + + CcidTestAppEvent event; + event.type = EventTypeInput; + event.input = *input_event; + furi_message_queue_put(event_queue, &event, FuriWaitForever); +} + +uint32_t ccid_test_exit(void* context) { + UNUSED(context); + return VIEW_NONE; +} + +CcidTestApp* ccid_test_app_alloc() { + CcidTestApp* app = malloc(sizeof(CcidTestApp)); + + // Gui + app->gui = furi_record_open(RECORD_GUI); + + //viewport + app->view_port = view_port_alloc(); + gui_add_view_port(app->gui, app->view_port, GuiLayerFullscreen); + view_port_draw_callback_set(app->view_port, ccid_test_app_render_callback, NULL); + + //message queue + app->event_queue = furi_message_queue_alloc(8, sizeof(CcidTestAppEvent)); + furi_check(app->event_queue); + view_port_input_callback_set(app->view_port, ccid_test_app__input_callback, app->event_queue); + + return app; +} + +void ccid_test_app_free(CcidTestApp* app) { + furi_assert(app); + + //message queue + furi_message_queue_free(app->event_queue); + + //view port + gui_remove_view_port(app->gui, app->view_port); + view_port_free(app->view_port); + + // Close gui record + furi_record_close(RECORD_GUI); + app->gui = NULL; + + // Free rest + free(app); +} + +int32_t ccid_test_app(void* p) { + UNUSED(p); + + //setup view + CcidTestApp* app = ccid_test_app_alloc(); + + //setup CCID USB + // On linux: set VID PID using: /usr/lib/pcsc/drivers/ifd-ccid.bundle/Contents/Info.plist + app->ccid_cfg.vid = 0x1234; + app->ccid_cfg.pid = 0x5678; + + FuriHalUsbInterface* usb_mode_prev = furi_hal_usb_get_config(); + furi_hal_usb_unlock(); + furi_hal_ccid_set_callbacks((CcidCallbacks*)&ccid_cb); + furi_check(furi_hal_usb_set_config(&usb_ccid, &app->ccid_cfg) == true); + + //handle button events + CcidTestAppEvent event; + while(1) { + FuriStatus event_status = + furi_message_queue_get(app->event_queue, &event, FuriWaitForever); + + if(event_status == FuriStatusOk) { + if(event.type == EventTypeInput) { + if(event.input.type == InputTypeLong && event.input.key == InputKeyBack) { + break; + } + } + } + view_port_update(app->view_port); + } + + //tear down USB + furi_hal_usb_set_config(usb_mode_prev, NULL); + furi_hal_ccid_set_callbacks(NULL); + + //teardown view + ccid_test_app_free(app); + return 0; +} diff --git a/applications/debug/ccid_test/iso7816_t0_apdu.c b/applications/debug/ccid_test/iso7816_t0_apdu.c new file mode 100644 index 00000000000..29f5f7a86c1 --- /dev/null +++ b/applications/debug/ccid_test/iso7816_t0_apdu.c @@ -0,0 +1,36 @@ +/* Implements rudimentary iso7816-3 support for APDU (T=0) */ +#include +#include +#include +#include "iso7816_t0_apdu.h" + +void iso7816_answer_to_reset(uint8_t* atrBuffer, uint32_t* atrlen) { + //minimum valid ATR: https://smartcard-atr.apdu.fr/parse?ATR=3B+00 + uint8_t AtrBuffer[2] = { + 0x3B, //TS (direct convention) + 0x00 // T0 (Y(1): b0000, K: 0 (historical bytes)) + }; + *atrlen = 2; + + memcpy(atrBuffer, AtrBuffer, sizeof(uint8_t) * (*atrlen)); +} + +void iso7816_read_command_apdu( + struct ISO7816_Command_APDU* command, + const uint8_t* dataBuffer, + uint32_t dataLen) { + furi_assert(dataLen <= 4); + command->CLA = dataBuffer[0]; + command->INS = dataBuffer[1]; + command->P1 = dataBuffer[2]; + command->P2 = dataBuffer[3]; +} + +void iso7816_write_response_apdu( + const struct ISO7816_Response_APDU* response, + uint8_t* dataBuffer, + uint32_t* dataLen) { + dataBuffer[0] = response->SW1; + dataBuffer[1] = response->SW2; + *dataLen = 2; +} \ No newline at end of file diff --git a/applications/debug/ccid_test/iso7816_t0_apdu.h b/applications/debug/ccid_test/iso7816_t0_apdu.h new file mode 100644 index 00000000000..8a8c99f85d4 --- /dev/null +++ b/applications/debug/ccid_test/iso7816_t0_apdu.h @@ -0,0 +1,32 @@ +#ifndef _ISO7816_T0_APDU_H_ +#define _ISO7816_T0_APDU_H_ + +#include + +struct ISO7816_Command_APDU { + //header + uint8_t CLA; + uint32_t INS; + uint8_t P1; + uint8_t P2; + + //body + uint8_t Nc; + uint8_t Ne; +} __attribute__((packed)); + +struct ISO7816_Response_APDU { + uint8_t SW1; + uint32_t SW2; +} __attribute__((packed)); + +void iso7816_answer_to_reset(uint8_t* atrBuffer, uint32_t* atrlen); +void iso7816_read_command_apdu( + struct ISO7816_Command_APDU* command, + const uint8_t* dataBuffer, + uint32_t dataLen); +void iso7816_write_response_apdu( + const struct ISO7816_Response_APDU* response, + uint8_t* dataBuffer, + uint32_t* dataLen); +#endif //_ISO7816_T0_APDU_H_ diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index f4e990becb5..338697ad701 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,39.0,, +Version,+,39.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -76,6 +76,7 @@ Header,+,firmware/targets/furi_hal_include/furi_hal_sd.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_speaker.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_spi.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_usb.h,, +Header,+,firmware/targets/furi_hal_include/furi_hal_usb_ccid.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_usb_hid.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_usb_hid_u2f.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_version.h,, @@ -105,6 +106,7 @@ Header,+,lib/libusb_stm32/inc/hid_usage_telephony.h,, Header,+,lib/libusb_stm32/inc/hid_usage_vr.h,, Header,-,lib/libusb_stm32/inc/stm32_compat.h,, Header,+,lib/libusb_stm32/inc/usb.h,, +Header,+,lib/libusb_stm32/inc/usb_ccid.h,, Header,+,lib/libusb_stm32/inc/usb_cdc.h,, Header,+,lib/libusb_stm32/inc/usb_cdca.h,, Header,+,lib/libusb_stm32/inc/usb_cdce.h,, @@ -1008,6 +1010,9 @@ Function,+,furi_hal_bus_enable,void,FuriHalBus Function,+,furi_hal_bus_init_early,void, Function,+,furi_hal_bus_is_enabled,_Bool,FuriHalBus Function,+,furi_hal_bus_reset,void,FuriHalBus +Function,+,furi_hal_ccid_ccid_insert_smartcard,void, +Function,+,furi_hal_ccid_ccid_remove_smartcard,void, +Function,+,furi_hal_ccid_set_callbacks,void,CcidCallbacks* Function,+,furi_hal_cdc_get_ctrl_line_state,uint8_t,uint8_t Function,+,furi_hal_cdc_get_port_settings,usb_cdc_line_coding*,uint8_t Function,+,furi_hal_cdc_receive,int32_t,"uint8_t, uint8_t*, uint16_t" @@ -2692,6 +2697,7 @@ Variable,+,sequence_single_vibro,const NotificationSequence, Variable,+,sequence_solid_yellow,const NotificationSequence, Variable,+,sequence_success,const NotificationSequence, Variable,-,suboptarg,char*, +Variable,+,usb_ccid,FuriHalUsbInterface, Variable,+,usb_cdc_dual,FuriHalUsbInterface, Variable,+,usb_cdc_single,FuriHalUsbInterface, Variable,+,usb_hid,FuriHalUsbInterface, diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index d37d5f333df..f3f42d0f099 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,39.0,, +Version,+,39.1,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -82,6 +82,7 @@ Header,+,firmware/targets/furi_hal_include/furi_hal_sd.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_speaker.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_spi.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_usb.h,, +Header,+,firmware/targets/furi_hal_include/furi_hal_usb_ccid.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_usb_hid.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_usb_hid_u2f.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_version.h,, @@ -123,6 +124,7 @@ Header,+,lib/libusb_stm32/inc/hid_usage_telephony.h,, Header,+,lib/libusb_stm32/inc/hid_usage_vr.h,, Header,-,lib/libusb_stm32/inc/stm32_compat.h,, Header,+,lib/libusb_stm32/inc/usb.h,, +Header,+,lib/libusb_stm32/inc/usb_ccid.h,, Header,+,lib/libusb_stm32/inc/usb_cdc.h,, Header,+,lib/libusb_stm32/inc/usb_cdca.h,, Header,+,lib/libusb_stm32/inc/usb_cdce.h,, @@ -1079,6 +1081,9 @@ Function,+,furi_hal_bus_enable,void,FuriHalBus Function,+,furi_hal_bus_init_early,void, Function,+,furi_hal_bus_is_enabled,_Bool,FuriHalBus Function,+,furi_hal_bus_reset,void,FuriHalBus +Function,+,furi_hal_ccid_ccid_insert_smartcard,void, +Function,+,furi_hal_ccid_ccid_remove_smartcard,void, +Function,+,furi_hal_ccid_set_callbacks,void,CcidCallbacks* Function,+,furi_hal_cdc_get_ctrl_line_state,uint8_t,uint8_t Function,+,furi_hal_cdc_get_port_settings,usb_cdc_line_coding*,uint8_t Function,+,furi_hal_cdc_receive,int32_t,"uint8_t, uint8_t*, uint16_t" @@ -3479,6 +3484,7 @@ Variable,+,subghz_protocol_raw_decoder,const SubGhzProtocolDecoder, Variable,+,subghz_protocol_raw_encoder,const SubGhzProtocolEncoder, Variable,+,subghz_protocol_registry,const SubGhzProtocolRegistry, Variable,-,suboptarg,char*, +Variable,+,usb_ccid,FuriHalUsbInterface, Variable,+,usb_cdc_dual,FuriHalUsbInterface, Variable,+,usb_cdc_single,FuriHalUsbInterface, Variable,+,usb_hid,FuriHalUsbInterface, diff --git a/firmware/targets/f7/furi_hal/furi_hal_usb_ccid.c b/firmware/targets/f7/furi_hal/furi_hal_usb_ccid.c new file mode 100644 index 00000000000..559713d015a --- /dev/null +++ b/firmware/targets/f7/furi_hal/furi_hal_usb_ccid.c @@ -0,0 +1,498 @@ +#include +#include +#include +#include +#include + +#include "usb.h" +#include "usb_ccid.h" + +static const uint8_t USB_DEVICE_NO_CLASS = 0x0; +static const uint8_t USB_DEVICE_NO_SUBCLASS = 0x0; +static const uint8_t USB_DEVICE_NO_PROTOCOL = 0x0; + +#define FIXED_CONTROL_ENDPOINT_SIZE 8 +#define IF_NUM_MAX 1 + +#define CCID_VID_DEFAULT 0x1234 +#define CCID_PID_DEFAULT 0xABCD +#define CCID_TOTAL_SLOTS 1 +#define CCID_SLOT_INDEX 0 + +#define CCID_DATABLOCK_SIZE 256 + +#define ENDPOINT_DIR_IN 0x80 +#define ENDPOINT_DIR_OUT 0x00 + +#define INTERFACE_ID_CCID 0 + +#define CCID_IN_EPADDR (ENDPOINT_DIR_IN | 2) + +/** Endpoint address of the CCID data OUT endpoint, for host-to-device data transfers. */ +#define CCID_OUT_EPADDR (ENDPOINT_DIR_OUT | 1) + +/** Endpoint size in bytes of the CCID data being sent between IN and OUT endpoints. */ +#define CCID_EPSIZE 64 + +struct CcidIntfDescriptor { + struct usb_interface_descriptor ccid; + struct usb_ccid_descriptor ccid_desc; + struct usb_endpoint_descriptor ccid_bulk_in; + struct usb_endpoint_descriptor ccid_bulk_out; +} __attribute__((packed)); + +struct CcidConfigDescriptor { + struct usb_config_descriptor config; + struct CcidIntfDescriptor intf_0; +} __attribute__((packed)); + +enum CCID_Features_Auto_t { + CCID_Features_Auto_None = 0x0, + CCID_Features_Auto_ParameterConfiguration = 0x2, + CCID_Features_Auto_ICCActivation = 0x4, + CCID_Features_Auto_VoltageSelection = 0x8, + + CCID_Features_Auto_ICCClockFrequencyChange = 0x10, + CCID_Features_Auto_ICCBaudRateChange = 0x20, + CCID_Features_Auto_ParameterNegotiation = 0x40, + CCID_Features_Auto_PPS = 0x80, +}; + +enum CCID_Features_ExchangeLevel_t { + CCID_Features_ExchangeLevel_TPDU = 0x00010000, + CCID_Features_ExchangeLevel_ShortAPDU = 0x00020000, + CCID_Features_ExchangeLevel_ShortExtendedAPDU = 0x00040000 +}; + +/* Device descriptor */ +static struct usb_device_descriptor ccid_device_desc = { + .bLength = sizeof(struct usb_device_descriptor), + .bDescriptorType = USB_DTYPE_DEVICE, + .bcdUSB = VERSION_BCD(2, 0, 0), + .bDeviceClass = USB_DEVICE_NO_CLASS, + .bDeviceSubClass = USB_DEVICE_NO_SUBCLASS, + .bDeviceProtocol = USB_DEVICE_NO_PROTOCOL, + .bMaxPacketSize0 = FIXED_CONTROL_ENDPOINT_SIZE, + .idVendor = CCID_VID_DEFAULT, + .idProduct = CCID_PID_DEFAULT, + .bcdDevice = VERSION_BCD(1, 0, 0), + .iManufacturer = UsbDevManuf, + .iProduct = UsbDevProduct, + .iSerialNumber = UsbDevSerial, + .bNumConfigurations = 1, +}; + +/* Device configuration descriptor*/ +static const struct CcidConfigDescriptor ccid_cfg_desc = { + .config = + { + .bLength = sizeof(struct usb_config_descriptor), + .bDescriptorType = USB_DTYPE_CONFIGURATION, + .wTotalLength = sizeof(struct CcidConfigDescriptor), + .bNumInterfaces = 1, + + .bConfigurationValue = 1, + .iConfiguration = NO_DESCRIPTOR, + .bmAttributes = USB_CFG_ATTR_RESERVED | USB_CFG_ATTR_SELFPOWERED, + .bMaxPower = USB_CFG_POWER_MA(100), + }, + .intf_0 = + { + .ccid = + {.bLength = sizeof(struct usb_interface_descriptor), + .bDescriptorType = USB_DTYPE_INTERFACE, + + .bInterfaceNumber = INTERFACE_ID_CCID, + .bAlternateSetting = 0x00, + .bNumEndpoints = 2, + + .bInterfaceClass = USB_CLASS_CCID, + .bInterfaceSubClass = 0, + .bInterfaceProtocol = 0, + + .iInterface = NO_DESCRIPTOR + + }, + .ccid_desc = + {.bLength = sizeof(struct usb_ccid_descriptor), + .bDescriptorType = USB_DTYPE_CCID_FUNCTIONAL, + .bcdCCID = CCID_CURRENT_SPEC_RELEASE_NUMBER, + .bMaxSlotIndex = 0x00, + .bVoltageSupport = CCID_VOLTAGESUPPORT_5V, + .dwProtocols = 0x01, //T0 + .dwDefaultClock = 16000, //16MHz + .dwMaximumClock = 16000, //16MHz + .bNumClockSupported = 0, + .dwDataRate = 307200, + .dwMaxDataRate = 307200, + .bNumDataRatesSupported = 0, + .dwMaxIFSD = 2038, + .dwSynchProtocols = 0, + .dwMechanical = 0, + .dwFeatures = CCID_Features_ExchangeLevel_ShortAPDU | + CCID_Features_Auto_ParameterConfiguration | + CCID_Features_Auto_ICCActivation | + CCID_Features_Auto_VoltageSelection, + .dwMaxCCIDMessageLength = 0x0c00, + .bClassGetResponse = 0xff, + .bClassEnvelope = 0xff, + .wLcdLayout = 0, + .bPINSupport = 0, + .bMaxCCIDBusySlots = 1}, + .ccid_bulk_in = + {.bLength = sizeof(struct usb_endpoint_descriptor), + .bDescriptorType = USB_DTYPE_ENDPOINT, + .bEndpointAddress = CCID_IN_EPADDR, + .bmAttributes = USB_EPTYPE_BULK, + .wMaxPacketSize = CCID_EPSIZE, + .bInterval = 0x05 + + }, + .ccid_bulk_out = + {.bLength = sizeof(struct usb_endpoint_descriptor), + .bDescriptorType = USB_DTYPE_ENDPOINT, + .bEndpointAddress = CCID_OUT_EPADDR, + .bmAttributes = USB_EPTYPE_BULK, + .wMaxPacketSize = CCID_EPSIZE, + .bInterval = 0x05}, + }, +}; + +static void ccid_init(usbd_device* dev, FuriHalUsbInterface* intf, void* ctx); +static void ccid_deinit(usbd_device* dev); +static void ccid_on_wakeup(usbd_device* dev); +static void ccid_on_suspend(usbd_device* dev); + +FuriHalUsbInterface usb_ccid = { + .init = ccid_init, + .deinit = ccid_deinit, + .wakeup = ccid_on_wakeup, + .suspend = ccid_on_suspend, + + .dev_descr = (struct usb_device_descriptor*)&ccid_device_desc, + + .str_manuf_descr = NULL, + .str_prod_descr = NULL, + .str_serial_descr = NULL, + + .cfg_descr = (void*)&ccid_cfg_desc, +}; + +static usbd_respond ccid_ep_config(usbd_device* dev, uint8_t cfg); +static usbd_respond ccid_control(usbd_device* dev, usbd_ctlreq* req, usbd_rqc_callback* callback); +static usbd_device* usb_dev; +static bool connected = false; +static bool smartcard_inserted = true; +static CcidCallbacks* callbacks[CCID_TOTAL_SLOTS] = {NULL}; + +static void* ccid_set_string_descr(char* str) { + furi_assert(str); + + size_t len = strlen(str); + struct usb_string_descriptor* dev_str_desc = malloc(len * 2 + 2); + dev_str_desc->bLength = len * 2 + 2; + dev_str_desc->bDescriptorType = USB_DTYPE_STRING; + for(size_t i = 0; i < len; i++) dev_str_desc->wString[i] = str[i]; + + return dev_str_desc; +} + +static void ccid_init(usbd_device* dev, FuriHalUsbInterface* intf, void* ctx) { + UNUSED(intf); + + FuriHalUsbCcidConfig* cfg = (FuriHalUsbCcidConfig*)ctx; + + usb_dev = dev; + + usb_ccid.dev_descr->iManufacturer = 0; + usb_ccid.dev_descr->iProduct = 0; + usb_ccid.str_manuf_descr = NULL; + usb_ccid.str_prod_descr = NULL; + usb_ccid.dev_descr->idVendor = CCID_VID_DEFAULT; + usb_ccid.dev_descr->idProduct = CCID_PID_DEFAULT; + + if(cfg != NULL) { + usb_ccid.dev_descr->idVendor = cfg->vid; + usb_ccid.dev_descr->idProduct = cfg->pid; + + if(cfg->manuf[0] != '\0') { + usb_ccid.str_manuf_descr = ccid_set_string_descr(cfg->manuf); + usb_ccid.dev_descr->iManufacturer = UsbDevManuf; + } + + if(cfg->product[0] != '\0') { + usb_ccid.str_prod_descr = ccid_set_string_descr(cfg->product); + usb_ccid.dev_descr->iProduct = UsbDevProduct; + } + } + + usbd_reg_config(dev, ccid_ep_config); + usbd_reg_control(dev, ccid_control); + + usbd_connect(dev, true); +} + +static void ccid_deinit(usbd_device* dev) { + usbd_reg_config(dev, NULL); + usbd_reg_control(dev, NULL); + + free(usb_ccid.str_prod_descr); + free(usb_ccid.str_serial_descr); +} + +static void ccid_on_wakeup(usbd_device* dev) { + UNUSED(dev); + connected = true; +} + +static void ccid_on_suspend(usbd_device* dev) { + UNUSED(dev); + connected = false; +} + +struct ccid_bulk_message_header { + uint8_t bMessageType; + uint32_t dwLength; + uint8_t bSlot; + uint8_t bSeq; +} __attribute__((packed)); + +static struct rdr_to_pc_slot_status responseSlotStatus; +static struct rdr_to_pc_data_block responseDataBlock; +static struct rdr_to_pc_parameters_t0 responseParameters; +uint8_t SendDataBlock[CCID_DATABLOCK_SIZE]; + +uint8_t CALLBACK_CCID_GetSlotStatus(uint8_t slot, uint8_t* error) { + if(slot == CCID_SLOT_INDEX) { + *error = CCID_ERROR_NOERROR; + if(smartcard_inserted) { + return CCID_COMMANDSTATUS_PROCESSEDWITHOUTERROR | CCID_ICCSTATUS_PRESENTANDACTIVE; + } else { + return CCID_COMMANDSTATUS_PROCESSEDWITHOUTERROR | CCID_ICCSTATUS_NOICCPRESENT; + } + } else { + *error = CCID_ERROR_SLOTNOTFOUND; + return CCID_COMMANDSTATUS_FAILED | CCID_ICCSTATUS_NOICCPRESENT; + } +} + +uint8_t + CALLBACK_CCID_IccPowerOn(uint8_t slot, uint8_t* atrBuffer, uint32_t* atrlen, uint8_t* error) { + if(slot == CCID_SLOT_INDEX) { + *error = CCID_ERROR_NOERROR; + if(smartcard_inserted) { + if(callbacks[CCID_SLOT_INDEX] != NULL) { + callbacks[CCID_SLOT_INDEX]->icc_power_on_callback(atrBuffer, atrlen, NULL); + } else { + return CCID_COMMANDSTATUS_PROCESSEDWITHOUTERROR | + CCID_ICCSTATUS_PRESENTANDINACTIVE; + } + + return CCID_COMMANDSTATUS_PROCESSEDWITHOUTERROR | CCID_ICCSTATUS_PRESENTANDACTIVE; + } else { + return CCID_COMMANDSTATUS_PROCESSEDWITHOUTERROR | CCID_ICCSTATUS_NOICCPRESENT; + } + } else { + *error = CCID_ERROR_SLOTNOTFOUND; + return CCID_COMMANDSTATUS_FAILED | CCID_ICCSTATUS_NOICCPRESENT; + } +} + +uint8_t CALLBACK_CCID_XfrBlock( + uint8_t slot, + uint8_t* dataBlock, + uint32_t* dataBlockLen, + uint8_t* error) { + if(slot == CCID_SLOT_INDEX) { + *error = CCID_ERROR_NOERROR; + if(smartcard_inserted) { + if(callbacks[CCID_SLOT_INDEX] != NULL) { + callbacks[CCID_SLOT_INDEX]->xfr_datablock_callback(dataBlock, dataBlockLen, NULL); + } else { + return CCID_COMMANDSTATUS_PROCESSEDWITHOUTERROR | + CCID_ICCSTATUS_PRESENTANDINACTIVE; + } + + return CCID_COMMANDSTATUS_PROCESSEDWITHOUTERROR | CCID_ICCSTATUS_PRESENTANDACTIVE; + } else { + return CCID_COMMANDSTATUS_PROCESSEDWITHOUTERROR | CCID_ICCSTATUS_NOICCPRESENT; + } + } else { + *error = CCID_ERROR_SLOTNOTFOUND; + return CCID_COMMANDSTATUS_FAILED | CCID_ICCSTATUS_NOICCPRESENT; + } +} + +void furi_hal_ccid_ccid_insert_smartcard() { + smartcard_inserted = true; +} + +void furi_hal_ccid_ccid_remove_smartcard() { + smartcard_inserted = false; +} + +void furi_hal_ccid_set_callbacks(CcidCallbacks* cb) { + callbacks[CCID_SLOT_INDEX] = cb; +} + +static void ccid_rx_ep_callback(usbd_device* dev, uint8_t event, uint8_t ep) { + UNUSED(dev); + UNUSED(event); + UNUSED(ep); +} + +static void ccid_tx_ep_callback(usbd_device* dev, uint8_t event, uint8_t ep) { + UNUSED(dev); + + if(event == usbd_evt_eprx) { + if(connected == false) return; + + struct ccid_bulk_message_header message; + usbd_ep_read(usb_dev, ep, &message, sizeof(message)); + + uint8_t Status; + uint8_t Error = CCID_ERROR_NOERROR; + + uint32_t dataBlockLen = 0; + uint8_t* dataBlockBuffer = NULL; + + if(message.bMessageType == PC_TO_RDR_GETSLOTSTATUS) { + responseSlotStatus.bMessageType = RDR_TO_PC_SLOTSTATUS; + responseSlotStatus.dwLength = 0; + responseSlotStatus.bSlot = message.bSlot; + responseSlotStatus.bSeq = message.bSeq; + + responseSlotStatus.bClockStatus = 0; + + Status = CALLBACK_CCID_GetSlotStatus(message.bSlot, &Error); + + responseSlotStatus.bStatus = Status; + responseSlotStatus.bError = Error; + + usbd_ep_write( + usb_dev, CCID_IN_EPADDR, &responseSlotStatus, sizeof(responseSlotStatus)); + } else if(message.bMessageType == PC_TO_RDR_ICCPOWERON) { + responseDataBlock.bMessageType = RDR_TO_PC_DATABLOCK; + responseDataBlock.bSlot = message.bSlot; + responseDataBlock.bSeq = message.bSeq; + responseDataBlock.bChainParameter = 0; + + dataBlockLen = 0; + dataBlockBuffer = (uint8_t*)SendDataBlock; + Status = CALLBACK_CCID_IccPowerOn( + message.bSlot, (uint8_t*)dataBlockBuffer, &dataBlockLen, &Error); + + furi_assert(dataBlockLen < CCID_DATABLOCK_SIZE); + responseDataBlock.dwLength = dataBlockLen; + + responseSlotStatus.bStatus = Status; + responseSlotStatus.bError = Error; + + memcpy(responseDataBlock.abData, SendDataBlock, dataBlockLen); + usbd_ep_write( + usb_dev, + CCID_IN_EPADDR, + &responseDataBlock, + sizeof(struct rdr_to_pc_data_block) + (sizeof(uint8_t) * dataBlockLen)); + } else if(message.bMessageType == PC_TO_RDR_ICCPOWEROFF) { + responseSlotStatus.bMessageType = RDR_TO_PC_SLOTSTATUS; + responseSlotStatus.dwLength = 0; + responseSlotStatus.bSlot = message.bSlot; + responseSlotStatus.bSeq = message.bSeq; + + responseSlotStatus.bClockStatus = 0; + + uint8_t Status; + uint8_t Error = CCID_ERROR_NOERROR; + Status = CALLBACK_CCID_GetSlotStatus(message.bSlot, &Error); + + responseSlotStatus.bStatus = Status; + responseSlotStatus.bError = Error; + + usbd_ep_write( + usb_dev, CCID_IN_EPADDR, &responseSlotStatus, sizeof(responseSlotStatus)); + } else if(message.bMessageType == PC_TO_RDR_SETPARAMETERS) { + responseParameters.bMessageType = RDR_TO_PC_PARAMETERS; + responseParameters.bSlot = message.bSlot; + responseParameters.bSeq = message.bSeq; + responseParameters.bProtocolNum = 0; //T0 + + uint8_t Status = CCID_COMMANDSTATUS_PROCESSEDWITHOUTERROR; + uint8_t Error = CCID_ERROR_NOERROR; + + responseParameters.bStatus = Status; + responseParameters.bError = Error; + + responseParameters.dwLength = sizeof(struct rdr_to_pc_parameters_t0); + + usbd_ep_write( + usb_dev, CCID_IN_EPADDR, &responseParameters, sizeof(responseParameters)); + } else if(message.bMessageType == PC_TO_RDR_XFRBLOCK) { + responseDataBlock.bMessageType = RDR_TO_PC_DATABLOCK; + responseDataBlock.bSlot = message.bSlot; + responseDataBlock.bSeq = message.bSeq; + responseDataBlock.bChainParameter = 0; + + dataBlockLen = 0; + dataBlockBuffer = (uint8_t*)SendDataBlock; + Status = CALLBACK_CCID_XfrBlock( + message.bSlot, (uint8_t*)dataBlockBuffer, &dataBlockLen, &Error); + + furi_assert(dataBlockLen < CCID_DATABLOCK_SIZE); + responseDataBlock.dwLength = dataBlockLen; + + responseSlotStatus.bStatus = Status; + responseSlotStatus.bError = Error; + + memcpy(responseDataBlock.abData, SendDataBlock, dataBlockLen); + usbd_ep_write( + usb_dev, + CCID_IN_EPADDR, + &responseDataBlock, + sizeof(struct rdr_to_pc_data_block) + (sizeof(uint8_t) * dataBlockLen)); + } + } +} + +/* Configure endpoints */ +static usbd_respond ccid_ep_config(usbd_device* dev, uint8_t cfg) { + switch(cfg) { + case 0: + /* deconfiguring device */ + usbd_ep_deconfig(dev, CCID_IN_EPADDR); + usbd_ep_deconfig(dev, CCID_OUT_EPADDR); + usbd_reg_endpoint(dev, CCID_IN_EPADDR, 0); + usbd_reg_endpoint(dev, CCID_OUT_EPADDR, 0); + return usbd_ack; + case 1: + /* configuring device */ + usbd_ep_config(dev, CCID_IN_EPADDR, USB_EPTYPE_BULK, CCID_EPSIZE); + usbd_ep_config(dev, CCID_OUT_EPADDR, USB_EPTYPE_BULK, CCID_EPSIZE); + usbd_reg_endpoint(dev, CCID_IN_EPADDR, ccid_rx_ep_callback); + usbd_reg_endpoint(dev, CCID_OUT_EPADDR, ccid_tx_ep_callback); + return usbd_ack; + default: + return usbd_fail; + } +} + +/* Control requests handler */ +static usbd_respond ccid_control(usbd_device* dev, usbd_ctlreq* req, usbd_rqc_callback* callback) { + UNUSED(callback); + /* CDC control requests */ + if(((USB_REQ_RECIPIENT | USB_REQ_TYPE) & req->bmRequestType) == + (USB_REQ_INTERFACE | USB_REQ_CLASS) && + (req->wIndex == 0 || req->wIndex == 2)) { + switch(req->bRequest) { + case CCID_ABORT: + return usbd_fail; + case CCID_GET_CLOCK_FREQUENCIES: + dev->status.data_ptr = (void*)&(ccid_cfg_desc.intf_0.ccid_desc.dwDefaultClock); + dev->status.data_count = sizeof(ccid_cfg_desc.intf_0.ccid_desc.dwDefaultClock); + return usbd_ack; + default: + return usbd_fail; + } + } + return usbd_fail; +} \ No newline at end of file diff --git a/firmware/targets/furi_hal_include/furi_hal.h b/firmware/targets/furi_hal_include/furi_hal.h index 9341dccecbe..e6fd9eb1cc0 100644 --- a/firmware/targets/furi_hal_include/furi_hal.h +++ b/firmware/targets/furi_hal_include/furi_hal.h @@ -35,6 +35,7 @@ struct STOP_EXTERNING_ME {}; #include #include #include +#include #include #include #include diff --git a/firmware/targets/furi_hal_include/furi_hal_usb.h b/firmware/targets/furi_hal_include/furi_hal_usb.h index 8b49f6c6537..a98797955da 100644 --- a/firmware/targets/furi_hal_include/furi_hal_usb.h +++ b/firmware/targets/furi_hal_include/furi_hal_usb.h @@ -28,6 +28,7 @@ extern FuriHalUsbInterface usb_cdc_single; extern FuriHalUsbInterface usb_cdc_dual; extern FuriHalUsbInterface usb_hid; extern FuriHalUsbInterface usb_hid_u2f; +extern FuriHalUsbInterface usb_ccid; typedef enum { FuriHalUsbStateEventReset, diff --git a/firmware/targets/furi_hal_include/furi_hal_usb_ccid.h b/firmware/targets/furi_hal_include/furi_hal_usb_ccid.h new file mode 100644 index 00000000000..e3ee0dfc389 --- /dev/null +++ b/firmware/targets/furi_hal_include/furi_hal_usb_ccid.h @@ -0,0 +1,31 @@ +#pragma once +#include "hid_usage_desktop.h" +#include "hid_usage_button.h" +#include "hid_usage_keyboard.h" +#include "hid_usage_consumer.h" +#include "hid_usage_led.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + uint16_t vid; + uint16_t pid; + char manuf[32]; + char product[32]; +} FuriHalUsbCcidConfig; + +typedef struct { + void (*icc_power_on_callback)(uint8_t* dataBlock, uint32_t* dataBlockLen, void* context); + void (*xfr_datablock_callback)(uint8_t* dataBlock, uint32_t* dataBlockLen, void* context); +} CcidCallbacks; + +void furi_hal_ccid_set_callbacks(CcidCallbacks* cb); + +void furi_hal_ccid_ccid_insert_smartcard(); +void furi_hal_ccid_ccid_remove_smartcard(); + +#ifdef __cplusplus +} +#endif diff --git a/lib/libusb_stm32 b/lib/libusb_stm32 index 9168e2a31db..6ca2857519f 160000 --- a/lib/libusb_stm32 +++ b/lib/libusb_stm32 @@ -1 +1 @@ -Subproject commit 9168e2a31db946326fb84016a74ea2ab5bf87f54 +Subproject commit 6ca2857519f996244f7b324dd227fdf0a075fffb From 3fbb9f24f841417505c540f142bd81d935c9be0e Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Thu, 21 Sep 2023 18:29:28 +0900 Subject: [PATCH 762/824] [FL-3583] Fix multiline aligned text going out of bounds (again) (#3084) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- applications/services/gui/elements.c | 1 + 1 file changed, 1 insertion(+) diff --git a/applications/services/gui/elements.c b/applications/services/gui/elements.c index 37ecfde4c67..a6ab84fb8d8 100644 --- a/applications/services/gui/elements.c +++ b/applications/services/gui/elements.c @@ -290,6 +290,7 @@ void elements_multiline_text_aligned( } else if((y + font_height) > canvas_height(canvas)) { line = furi_string_alloc_printf("%.*s...\n", chars_fit, start); } else { + chars_fit -= 1; // account for the dash line = furi_string_alloc_printf("%.*s-\n", chars_fit, start); } canvas_draw_str_aligned(canvas, x, y, horizontal, vertical, furi_string_get_cstr(line)); From a73a83f04d74e66efbef695b1c5ecd6d78b64bf2 Mon Sep 17 00:00:00 2001 From: Robin Gareus Date: Thu, 21 Sep 2023 11:36:46 +0200 Subject: [PATCH 763/824] IR Universal Audio Remote: Amend 98d4309 -NAD Amp (#2954) (#3092) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The original PR was cut short Co-authored-by: あく --- assets/resources/infrared/assets/audio.ir | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/assets/resources/infrared/assets/audio.ir b/assets/resources/infrared/assets/audio.ir index 5cdc9048a89..d5a0f86dc36 100644 --- a/assets/resources/infrared/assets/audio.ir +++ b/assets/resources/infrared/assets/audio.ir @@ -424,4 +424,5 @@ command: 05 FA 00 00 name: Next type: parsed protocol: NECext -address: 87 7C 00 0 +address: 87 7C 00 00 +command: 06 F9 00 00 From b80dfbe0c5a8803f46404310220755dc6dee552e Mon Sep 17 00:00:00 2001 From: hedger Date: Thu, 21 Sep 2023 12:44:55 +0300 Subject: [PATCH 764/824] github: fixed grep arg for RC builds (#3093) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * github: fixed grep arg for RC builds * scripts: fbt: checking for .git existence, not for it being a dir Co-authored-by: あく --- .github/actions/submit_sdk/action.yml | 2 +- fbt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/actions/submit_sdk/action.yml b/.github/actions/submit_sdk/action.yml index 269185d5ad3..b515b528559 100644 --- a/.github/actions/submit_sdk/action.yml +++ b/.github/actions/submit_sdk/action.yml @@ -58,7 +58,7 @@ runs: echo "API version is already released" exit 0 fi - if ! echo "${{ inputs.firmware-version }}" | grep -q "-rc" ; then + if ! echo "${{ inputs.firmware-version }}" | grep -q -- "-rc" ; then SDK_ID=$(jq -r ._id found_sdk.json) echo "Marking SDK $SDK_ID as released" curl -X 'POST' \ diff --git a/fbt b/fbt index 471285a76c1..26f325d4556 100755 --- a/fbt +++ b/fbt @@ -25,7 +25,7 @@ if [ -z "$FBT_VERBOSE" ]; then fi if [ -z "$FBT_NO_SYNC" ]; then - if [ ! -d "$SCRIPT_PATH/.git" ]; then + if [ ! -e "$SCRIPT_PATH/.git" ]; then echo "\".git\" directory not found, please clone repo via \"git clone\""; exit 1; fi From 1891d54baf962b954b1541b8109bf842aed88273 Mon Sep 17 00:00:00 2001 From: hedger Date: Thu, 21 Sep 2023 15:56:00 +0300 Subject: [PATCH 765/824] [FL-3600] Added `fal_embedded` parameter for PLUGIN apps (#3083) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fbt, ufbt: added `fal_embedded` parameter for PLIGIN apps, to embed them into .fap * fbt: fixed dependency settings for assets * fbt: extapps: Removed unneeded casts * fbt: extapps: code simplification * fbt: fal_embedded: fixed dependency relations Co-authored-by: あく --- documentation/AppManifests.md | 1 + firmware.scons | 2 +- scripts/fbt/appmanifest.py | 13 ++++ scripts/fbt/fapassets.py | 28 +++++---- scripts/fbt_tools/fbt_extapps.py | 105 ++++++++++++++++++------------- 5 files changed, 93 insertions(+), 56 deletions(-) diff --git a/documentation/AppManifests.md b/documentation/AppManifests.md index b48a6b8eddc..72c15ad48fe 100644 --- a/documentation/AppManifests.md +++ b/documentation/AppManifests.md @@ -56,6 +56,7 @@ The following parameters are used only for [FAPs](./AppsOnSDCard.md): - **fap_weburl**: string, may be empty. Application's homepage. - **fap_icon_assets**: string. If present, it defines a folder name to be used for gathering image assets for this application. These images will be preprocessed and built alongside the application. See [FAP assets](./AppsOnSDCard.md#fap-assets) for details. - **fap_extbuild**: provides support for parts of application sources to be built by external tools. Contains a list of `ExtFile(path="file name", command="shell command")` definitions. **`fbt`** will run the specified command for each file in the list. +- **fal_embedded**: boolean, default `False`. Applies only to PLUGIN type. If `True`, the plugin will be embedded into host application's .fap file as a resource and extracted to `apps_assets/APPID` folder on its start. This allows plugins to be distributed as a part of the host application. Note that commands are executed at the firmware root folder, and all intermediate files must be placed in an application's temporary build folder. For that, you can use pattern expansion by **`fbt`**: `${FAP_WORK_DIR}` will be replaced with the path to the application's temporary build folder, and `${FAP_SRC_DIR}` will be replaced with the path to the application's source folder. You can also use other variables defined internally by **`fbt`**. diff --git a/firmware.scons b/firmware.scons index 657822700d5..82f775d719c 100644 --- a/firmware.scons +++ b/firmware.scons @@ -143,7 +143,7 @@ fwenv.PrepareApplicationsBuild() # Build external apps + configure SDK if env["IS_BASE_FIRMWARE"]: - fwenv.SetDefault(FBT_FAP_DEBUG_ELF_ROOT="${BUILD_DIR}/.extapps") + fwenv.SetDefault(FBT_FAP_DEBUG_ELF_ROOT=fwenv["BUILD_DIR"].Dir(".extapps")) fwenv["FW_EXTAPPS"] = SConscript( "site_scons/extapps.scons", exports={"ENV": fwenv}, diff --git a/scripts/fbt/appmanifest.py b/scripts/fbt/appmanifest.py index 7bb8e40b2d1..ff49707b1d6 100644 --- a/scripts/fbt/appmanifest.py +++ b/scripts/fbt/appmanifest.py @@ -79,11 +79,19 @@ class Library: fap_extbuild: List[ExternallyBuiltFile] = field(default_factory=list) fap_private_libs: List[Library] = field(default_factory=list) fap_file_assets: Optional[str] = None + fal_embedded: bool = False # Internally used by fbt _appmanager: Optional["AppManager"] = None _appdir: Optional[object] = None _apppath: Optional[str] = None _plugins: List["FlipperApplication"] = field(default_factory=list) + _assets_dirs: List[object] = field(default_factory=list) + _section_fapmeta: Optional[object] = None + _section_fapfileassets: Optional[object] = None + + @property + def embeds_plugins(self): + return any(plugin.fal_embedded for plugin in self._plugins) def supports_hardware_target(self, target: str): return target in self.targets or "all" in self.targets @@ -137,6 +145,11 @@ def _validate_app_params(self, *args, **kw): raise FlipperManifestException( f"Plugin {kw.get('appid')} must have 'requires' in manifest" ) + else: + if kw.get("fal_embedded"): + raise FlipperManifestException( + f"App {kw.get('appid')} cannot have fal_embedded set" + ) # Harmless - cdefines for external apps are meaningless # if apptype == FlipperAppType.EXTERNAL and kw.get("cdefines"): # raise FlipperManifestException( diff --git a/scripts/fbt/fapassets.py b/scripts/fbt/fapassets.py index 9902fd79a16..cd65ad20fb0 100644 --- a/scripts/fbt/fapassets.py +++ b/scripts/fbt/fapassets.py @@ -1,7 +1,7 @@ import hashlib import os import struct -from typing import TypedDict +from typing import TypedDict, List class File(TypedDict): @@ -32,20 +32,19 @@ class FileBundler: u8[] file_content """ - def __init__(self, directory_path: str): - self.directory_path = directory_path - self.file_list: list[File] = [] - self.directory_list: list[Dir] = [] - self._gather() + def __init__(self, assets_dirs: List[object]): + self.src_dirs = list(assets_dirs) - def _gather(self): - for root, dirs, files in os.walk(self.directory_path): + def _gather(self, directory_path: str): + if not os.path.isdir(directory_path): + raise Exception(f"Assets directory {directory_path} does not exist") + for root, dirs, files in os.walk(directory_path): for file_info in files: file_path = os.path.join(root, file_info) file_size = os.path.getsize(file_path) self.file_list.append( { - "path": os.path.relpath(file_path, self.directory_path), + "path": os.path.relpath(file_path, directory_path), "size": file_size, "content_path": file_path, } @@ -57,15 +56,20 @@ def _gather(self): # os.path.getsize(os.path.join(dir_path, f)) for f in os.listdir(dir_path) # ) self.directory_list.append( - { - "path": os.path.relpath(dir_path, self.directory_path), - } + {"path": os.path.relpath(dir_path, directory_path)} ) self.file_list.sort(key=lambda f: f["path"]) self.directory_list.sort(key=lambda d: d["path"]) + def _process_src_dirs(self): + self.file_list: list[File] = [] + self.directory_list: list[Dir] = [] + for directory_path in self.src_dirs: + self._gather(directory_path) + def export(self, target_path: str): + self._process_src_dirs() self._md5_hash = hashlib.md5() with open(target_path, "wb") as f: # Write header magic and version diff --git a/scripts/fbt_tools/fbt_extapps.py b/scripts/fbt_tools/fbt_extapps.py index 6059628f004..aa6354c9e3a 100644 --- a/scripts/fbt_tools/fbt_extapps.py +++ b/scripts/fbt_tools/fbt_extapps.py @@ -3,7 +3,7 @@ import pathlib import shutil from dataclasses import dataclass, field -from typing import Optional, Dict, List +from typing import Dict, List, Optional import SCons.Warnings from ansi.color import fg @@ -32,11 +32,15 @@ class FlipperExternalAppInfo: class AppBuilder: + @staticmethod + def get_app_work_dir(env, app): + return env["EXT_APPS_WORK_DIR"].Dir(app.appid) + def __init__(self, env, app): self.fw_env = env self.app = app - self.ext_apps_work_dir = env.subst("$EXT_APPS_WORK_DIR") - self.app_work_dir = os.path.join(self.ext_apps_work_dir, self.app.appid) + self.ext_apps_work_dir = env["EXT_APPS_WORK_DIR"] + self.app_work_dir = self.get_app_work_dir(env, app) self.app_alias = f"fap_{self.app.appid}" self.externally_built_files = [] self.private_libs = [] @@ -83,9 +87,9 @@ def _compile_assets(self): return fap_icons = self.app_env.CompileIcons( - self.app_env.Dir(self.app_work_dir), + self.app_work_dir, self.app._appdir.Dir(self.app.fap_icon_assets), - icon_bundle_name=f"{self.app.fap_icon_assets_symbol if self.app.fap_icon_assets_symbol else self.app.appid }_icons", + icon_bundle_name=f"{self.app.fap_icon_assets_symbol or self.app.appid }_icons", ) self.app_env.Alias("_fap_icons", fap_icons) self.fw_env.Append(_APP_ICONS=[fap_icons]) @@ -95,7 +99,7 @@ def _build_private_libs(self): self.private_libs.append(self._build_private_lib(lib_def)) def _build_private_lib(self, lib_def): - lib_src_root_path = os.path.join(self.app_work_dir, "lib", lib_def.name) + lib_src_root_path = self.app_work_dir.Dir("lib").Dir(lib_def.name) self.app_env.AppendUnique( CPPPATH=list( self.app_env.Dir(lib_src_root_path) @@ -119,9 +123,7 @@ def _build_private_lib(self, lib_def): private_lib_env = self.app_env.Clone() private_lib_env.AppendUnique( - CCFLAGS=[ - *lib_def.cflags, - ], + CCFLAGS=lib_def.cflags, CPPDEFINES=lib_def.cdefines, CPPPATH=list( map( @@ -132,14 +134,17 @@ def _build_private_lib(self, lib_def): ) return private_lib_env.StaticLibrary( - os.path.join(self.app_work_dir, lib_def.name), + self.app_work_dir.File(lib_def.name), lib_sources, ) def _build_app(self): + if self.app.fap_file_assets: + self.app._assets_dirs = [self.app._appdir.Dir(self.app.fap_file_assets)] + self.app_env.Append( LIBS=[*self.app.fap_libs, *self.private_libs], - CPPPATH=[self.app_env.Dir(self.app_work_dir), self.app._appdir], + CPPPATH=[self.app_work_dir, self.app._appdir], ) app_sources = list( @@ -155,32 +160,46 @@ def _build_app(self): app_artifacts = FlipperExternalAppInfo(self.app) app_artifacts.debug = self.app_env.Program( - os.path.join(self.ext_apps_work_dir, f"{self.app.appid}_d"), + self.ext_apps_work_dir.File(f"{self.app.appid}_d.elf"), app_sources, APP_ENTRY=self.app.entry_point, )[0] app_artifacts.compact = self.app_env.EmbedAppMetadata( - os.path.join(self.ext_apps_work_dir, self.app.appid), + self.ext_apps_work_dir.File(f"{self.app.appid}.fap"), app_artifacts.debug, APP=self.app, )[0] + if self.app.embeds_plugins: + self.app._assets_dirs.append(self.app_work_dir.Dir("assets")) + app_artifacts.validator = self.app_env.ValidateAppImports( app_artifacts.compact )[0] if self.app.apptype == FlipperAppType.PLUGIN: for parent_app_id in self.app.requires: - fal_path = ( - f"apps_data/{parent_app_id}/plugins/{app_artifacts.compact.name}" - ) - deployable = True - # If it's a plugin for a non-deployable app, don't include it in the resources - if parent_app := self.app._appmanager.get(parent_app_id): - if not parent_app.is_default_deployable: - deployable = False - app_artifacts.dist_entries.append((deployable, fal_path)) + if self.app.fal_embedded: + parent_app = self.app._appmanager.get(parent_app_id) + if not parent_app: + raise UserError( + f"Embedded plugin {self.app.appid} requires unknown app {parent_app_id}" + ) + self.app_env.Install( + target=self.get_app_work_dir(self.app_env, parent_app) + .Dir("assets") + .Dir("plugins"), + source=app_artifacts.compact, + ) + else: + fal_path = f"apps_data/{parent_app_id}/plugins/{app_artifacts.compact.name}" + deployable = True + # If it's a plugin for a non-deployable app, don't include it in the resources + if parent_app := self.app._appmanager.get(parent_app_id): + if not parent_app.is_default_deployable: + deployable = False + app_artifacts.dist_entries.append((deployable, fal_path)) else: fap_path = f"apps/{self.app.fap_category}/{app_artifacts.compact.name}" app_artifacts.dist_entries.append( @@ -194,7 +213,7 @@ def _configure_deps_and_aliases(self, app_artifacts: FlipperExternalAppInfo): # Extra things to clean up along with the app self.app_env.Clean( app_artifacts.debug, - [*self.externally_built_files, self.app_env.Dir(self.app_work_dir)], + [*self.externally_built_files, self.app_work_dir], ) # Create listing of the app @@ -219,13 +238,10 @@ def _configure_deps_and_aliases(self, app_artifacts: FlipperExternalAppInfo): ) # Add dependencies on file assets - if self.app.fap_file_assets: + for assets_dir in self.app._assets_dirs: self.app_env.Depends( app_artifacts.compact, - self.app_env.GlobRecursive( - "*", - self.app._appdir.Dir(self.app.fap_file_assets), - ), + (assets_dir, self.app_env.GlobRecursive("*", assets_dir)), ) # Always run the validator for the app's binary when building the app @@ -344,25 +360,26 @@ def embed_app_metadata_emitter(target, source, env): if app.apptype == FlipperAppType.PLUGIN: target[0].name = target[0].name.replace(".fap", ".fal") - target.append(env.File(source[0].abspath + _FAP_META_SECTION)) + app_work_dir = AppBuilder.get_app_work_dir(env, app) + app._section_fapmeta = app_work_dir.File(_FAP_META_SECTION) + target.append(app._section_fapmeta) - if app.fap_file_assets: - target.append(env.File(source[0].abspath + _FAP_FILEASSETS_SECTION)) + # At this point, we haven't added dir with embedded plugins to _assets_dirs yet + if app._assets_dirs or app.embeds_plugins: + app._section_fapfileassets = app_work_dir.File(_FAP_FILEASSETS_SECTION) + target.append(app._section_fapfileassets) return (target, source) -def prepare_app_files(target, source, env): +def prepare_app_file_assets(target, source, env): files_section_node = next( filter(lambda t: t.name.endswith(_FAP_FILEASSETS_SECTION), target) ) - app = env["APP"] - directory = env.Dir(app._apppath).Dir(app.fap_file_assets) - if not directory.exists(): - raise UserError(f"File asset directory {directory} does not exist") - - bundler = FileBundler(directory.abspath) + bundler = FileBundler( + list(env.Dir(asset_dir).abspath for asset_dir in env["APP"]._assets_dirs) + ) bundler.export(files_section_node.abspath) @@ -376,12 +393,14 @@ def generate_embed_app_metadata_actions(source, target, env, for_signature): objcopy_str = ( "${OBJCOPY} " "--remove-section .ARM.attributes " - "--add-section ${_FAP_META_SECTION}=${SOURCE}${_FAP_META_SECTION} " + "--add-section ${_FAP_META_SECTION}=${APP._section_fapmeta} " ) - if app.fap_file_assets: - actions.append(Action(prepare_app_files, "$APPFILE_COMSTR")) - objcopy_str += "--add-section ${_FAP_FILEASSETS_SECTION}=${SOURCE}${_FAP_FILEASSETS_SECTION} " + if app._section_fapfileassets: + actions.append(Action(prepare_app_file_assets, "$APPFILE_COMSTR")) + objcopy_str += ( + "--add-section ${_FAP_FILEASSETS_SECTION}=${APP._section_fapfileassets} " + ) objcopy_str += ( "--set-section-flags ${_FAP_META_SECTION}=contents,noload,readonly,data " @@ -470,7 +489,7 @@ def AddAppBuildTarget(env, appname, build_target_name): def generate(env, **kw): env.SetDefault( - EXT_APPS_WORK_DIR="${FBT_FAP_DEBUG_ELF_ROOT}", + EXT_APPS_WORK_DIR=env.Dir(env["FBT_FAP_DEBUG_ELF_ROOT"]), APP_RUN_SCRIPT="${FBT_SCRIPT_DIR}/runfap.py", ) if not env["VERBOSE"]: From b98631c633d62eb5d1384b8101a3ce738ff53031 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Thu, 21 Sep 2023 23:34:48 +0900 Subject: [PATCH 766/824] Gui: handle view port lockup and notify developer about it (#3102) * Gui: handle view port lockup and notify developer about it * Gui: fix more viewport deadlock cases and add proper notification --- applications/services/gui/view_port.c | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/applications/services/gui/view_port.c b/applications/services/gui/view_port.c index 6723a777bbb..25f670a7c15 100644 --- a/applications/services/gui/view_port.c +++ b/applications/services/gui/view_port.c @@ -6,6 +6,8 @@ #include "gui.h" #include "gui_i.h" +#define TAG "ViewPort" + _Static_assert(ViewPortOrientationMAX == 4, "Incorrect ViewPortOrientation count"); _Static_assert( (ViewPortOrientationHorizontal == 0 && ViewPortOrientationHorizontalFlip == 1 && @@ -174,9 +176,15 @@ void view_port_input_callback_set( void view_port_update(ViewPort* view_port) { furi_assert(view_port); - furi_check(furi_mutex_acquire(view_port->mutex, FuriWaitForever) == FuriStatusOk); + + // We are not going to lockup system, but will notify you instead + // Make sure that you don't call viewport methods inside of another mutex, especially one that is used in draw call + if(furi_mutex_acquire(view_port->mutex, 2) != FuriStatusOk) { + FURI_LOG_W(TAG, "ViewPort lockup: see %s:%d", __FILE__, __LINE__ - 3); + } + if(view_port->gui && view_port->is_enabled) gui_update(view_port->gui); - furi_check(furi_mutex_release(view_port->mutex) == FuriStatusOk); + furi_mutex_release(view_port->mutex); } void view_port_gui_set(ViewPort* view_port, Gui* gui) { @@ -189,14 +197,21 @@ void view_port_gui_set(ViewPort* view_port, Gui* gui) { void view_port_draw(ViewPort* view_port, Canvas* canvas) { furi_assert(view_port); furi_assert(canvas); - furi_check(furi_mutex_acquire(view_port->mutex, FuriWaitForever) == FuriStatusOk); + + // We are not going to lockup system, but will notify you instead + // Make sure that you don't call viewport methods inside of another mutex, especially one that is used in draw call + if(furi_mutex_acquire(view_port->mutex, 2) != FuriStatusOk) { + FURI_LOG_W(TAG, "ViewPort lockup: see %s:%d", __FILE__, __LINE__ - 3); + } + furi_check(view_port->gui); if(view_port->draw_callback) { view_port_setup_canvas_orientation(view_port, canvas); view_port->draw_callback(canvas, view_port->draw_callback_context); } - furi_check(furi_mutex_release(view_port->mutex) == FuriStatusOk); + + furi_mutex_release(view_port->mutex); } void view_port_input(ViewPort* view_port, InputEvent* event) { From e1030e7999e014dcf19b5c4c5c3d4a5f3286b3d5 Mon Sep 17 00:00:00 2001 From: hedger Date: Mon, 25 Sep 2023 08:04:34 +0300 Subject: [PATCH 767/824] fbt: reworked tool path handling (#3105) * fbt: removed absolute paths from env setup; moved abs paths to cdb tool * fbt: moved tool lookup to cdb emitter * fbt: cdb: quote only tools with spaces in path * typo fix * fbt: pvs: suppress license expiration warning --- scripts/fbt_tools/compilation_db.py | 34 +++++++++++++++++++---------- scripts/fbt_tools/crosscc.py | 13 ++++++----- scripts/fbt_tools/pvsstudio.py | 1 + 3 files changed, 32 insertions(+), 16 deletions(-) diff --git a/scripts/fbt_tools/compilation_db.py b/scripts/fbt_tools/compilation_db.py index 1f829ddb411..3d5e469f4e0 100644 --- a/scripts/fbt_tools/compilation_db.py +++ b/scripts/fbt_tools/compilation_db.py @@ -29,22 +29,26 @@ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # -import json -import itertools import fnmatch -import SCons +import itertools +import json +from shlex import quote -from SCons.Tool.cxx import CXXSuffixes +import SCons +from SCons.Tool.asm import ASPPSuffixes, ASSuffixes from SCons.Tool.cc import CSuffixes -from SCons.Tool.asm import ASSuffixes, ASPPSuffixes +from SCons.Tool.cxx import CXXSuffixes -# TODO FL-3542: Is there a better way to do this than this global? Right now this exists so that the +# TODO: (-nofl) Is there a better way to do this than this global? Right now this exists so that the # emitter we add can record all of the things it emits, so that the scanner for the top level # compilation database can access the complete list, and also so that the writer has easy # access to write all of the files. But it seems clunky. How can the emitter and the scanner # communicate more gracefully? __COMPILATION_DB_ENTRIES = [] +# We cache the tool path lookups to avoid doing them over and over again. +_TOOL_PATH_CACHE = {} + # We make no effort to avoid rebuilding the entries. Someday, perhaps we could and even # integrate with the cache, but there doesn't seem to be much call for it. @@ -91,7 +95,7 @@ def emit_compilation_db_entry(target, source, env): __COMPILATIONDB_ENV=env, ) - # TODO FL-3541: Technically, these next two lines should not be required: it should be fine to + # TODO: (-nofl) Technically, these next two lines should not be required: it should be fine to # cache the entries. However, they don't seem to update properly. Since they are quick # to re-generate disable caching and sidestep this problem. env.AlwaysBuild(entry) @@ -122,6 +126,17 @@ def compilation_db_entry_action(target, source, env, **kw): env=env["__COMPILATIONDB_ENV"], ) + # We assume first non-space character is the executable + executable = command.split(" ", 1)[0] + if not (tool_path := _TOOL_PATH_CACHE.get(executable, None)): + tool_path = env.WhereIs(executable) or executable + _TOOL_PATH_CACHE[executable] = tool_path + # If there are spaces in the executable path, we need to quote it + if " " in tool_path: + tool_path = quote(tool_path) + # Replacing the executable with the full path + command = tool_path + command[len(executable) :] + entry = { "directory": env.Dir("#").abspath, "command": command, @@ -242,10 +257,7 @@ def generate(env, **kwargs): for entry in components_by_suffix: suffix = entry[0] builder, base_emitter, command = entry[1] - - # Assumes a dictionary emitter - emitter = builder.emitter.get(suffix, False) - if emitter: + if emitter := builder.emitter.get(suffix, False): # We may not have tools installed which initialize all or any of # cxx, cc, or assembly. If not skip resetting the respective emitter. builder.emitter[suffix] = SCons.Builder.ListEmitter( diff --git a/scripts/fbt_tools/crosscc.py b/scripts/fbt_tools/crosscc.py index 42fb4ce4b17..4890424e6aa 100644 --- a/scripts/fbt_tools/crosscc.py +++ b/scripts/fbt_tools/crosscc.py @@ -2,8 +2,6 @@ import gdb import objdump -import shutil - import strip from SCons.Action import _subproc from SCons.Errors import StopError @@ -13,20 +11,25 @@ def prefix_commands(env, command_prefix, cmd_list): for command in cmd_list: if command in env: - env[command] = shutil.which(command_prefix + env[command]) + prefixed_binary = command_prefix + env[command] + if not env.WhereIs(prefixed_binary): + raise StopError( + f"Toolchain binary {prefixed_binary} not found in PATH." + ) + env.Replace(**{command: prefixed_binary}) def _get_tool_version(env, tool): verstr = "version unknown" proc = _subproc( env, - env.subst("${%s} --version" % tool), + [env.subst("${%s}" % tool), "--version"], stdout=subprocess.PIPE, stderr="devnull", stdin="devnull", universal_newlines=True, error="raise", - shell=True, + shell=False, ) if proc: verstr = proc.stdout.readline() diff --git a/scripts/fbt_tools/pvsstudio.py b/scripts/fbt_tools/pvsstudio.py index 211f46aee8e..b2592eca606 100644 --- a/scripts/fbt_tools/pvsstudio.py +++ b/scripts/fbt_tools/pvsstudio.py @@ -48,6 +48,7 @@ def generate(env): "@.pvsoptions", "-j${PVSNCORES}", # "--incremental", # kinda broken on PVS side + "--disableLicenseExpirationCheck", ], PVSCONVOPTIONS=[ "-a", From 63d7d46bd3af7adfbcf7d95e8a1a7f4ebd6753bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Mon, 25 Sep 2023 14:12:12 +0900 Subject: [PATCH 768/824] FuriHal,BleGlue: prevent sleep while HCI command executed, proper bt api rpc locking. Fixes random system lockups. (#3107) --- applications/services/bt/bt_service/bt.c | 8 ++++---- applications/services/bt/bt_service/bt_api.c | 11 +++++++---- applications/services/bt/bt_service/bt_i.h | 4 ++-- applications/system/hid_app/hid.c | 8 ++------ firmware/targets/f7/ble_glue/ble_app.c | 2 ++ firmware/targets/f7/furi_hal/furi_hal_bt.c | 3 +++ 6 files changed, 20 insertions(+), 16 deletions(-) diff --git a/applications/services/bt/bt_service/bt.c b/applications/services/bt/bt_service/bt.c index 1b12ee303c6..95a9bd30428 100644 --- a/applications/services/bt/bt_service/bt.c +++ b/applications/services/bt/bt_service/bt.c @@ -359,13 +359,13 @@ static void bt_change_profile(Bt* bt, BtMessage* message) { *message->result = false; } } - furi_event_flag_set(bt->api_event, BT_API_UNLOCK_EVENT); + api_lock_unlock(message->lock); } -static void bt_close_connection(Bt* bt) { +static void bt_close_connection(Bt* bt, BtMessage* message) { bt_close_rpc_connection(bt); furi_hal_bt_stop_advertising(); - furi_event_flag_set(bt->api_event, BT_API_UNLOCK_EVENT); + api_lock_unlock(message->lock); } int32_t bt_srv(void* p) { @@ -432,7 +432,7 @@ int32_t bt_srv(void* p) { } else if(message.type == BtMessageTypeSetProfile) { bt_change_profile(bt, &message); } else if(message.type == BtMessageTypeDisconnect) { - bt_close_connection(bt); + bt_close_connection(bt, &message); } else if(message.type == BtMessageTypeForgetBondedDevices) { bt_keys_storage_delete(bt->keys_storage); } diff --git a/applications/services/bt/bt_service/bt_api.c b/applications/services/bt/bt_service/bt_api.c index 2f56b50a39a..e31031783b6 100644 --- a/applications/services/bt/bt_service/bt_api.c +++ b/applications/services/bt/bt_service/bt_api.c @@ -6,11 +6,14 @@ bool bt_set_profile(Bt* bt, BtProfile profile) { // Send message bool result = false; BtMessage message = { - .type = BtMessageTypeSetProfile, .data.profile = profile, .result = &result}; + .lock = api_lock_alloc_locked(), + .type = BtMessageTypeSetProfile, + .data.profile = profile, + .result = &result}; furi_check( furi_message_queue_put(bt->message_queue, &message, FuriWaitForever) == FuriStatusOk); // Wait for unlock - furi_event_flag_wait(bt->api_event, BT_API_UNLOCK_EVENT, FuriFlagWaitAny, FuriWaitForever); + api_lock_wait_unlock_and_free(message.lock); return result; } @@ -19,11 +22,11 @@ void bt_disconnect(Bt* bt) { furi_assert(bt); // Send message - BtMessage message = {.type = BtMessageTypeDisconnect}; + BtMessage message = {.lock = api_lock_alloc_locked(), .type = BtMessageTypeDisconnect}; furi_check( furi_message_queue_put(bt->message_queue, &message, FuriWaitForever) == FuriStatusOk); // Wait for unlock - furi_event_flag_wait(bt->api_event, BT_API_UNLOCK_EVENT, FuriFlagWaitAny, FuriWaitForever); + api_lock_wait_unlock_and_free(message.lock); } void bt_set_status_changed_callback(Bt* bt, BtStatusChangedCallback callback, void* context) { diff --git a/applications/services/bt/bt_service/bt_i.h b/applications/services/bt/bt_service/bt_i.h index c8a0e99654e..55bae76f3bf 100644 --- a/applications/services/bt/bt_service/bt_i.h +++ b/applications/services/bt/bt_service/bt_i.h @@ -4,6 +4,7 @@ #include #include +#include #include #include @@ -22,8 +23,6 @@ #define BT_KEYS_STORAGE_PATH INT_PATH(BT_KEYS_STORAGE_FILE_NAME) -#define BT_API_UNLOCK_EVENT (1UL << 0) - typedef enum { BtMessageTypeUpdateStatus, BtMessageTypeUpdateBatteryLevel, @@ -48,6 +47,7 @@ typedef union { } BtMessageData; typedef struct { + FuriApiLock lock; BtMessageType type; BtMessageData data; bool* result; diff --git a/applications/system/hid_app/hid.c b/applications/system/hid_app/hid.c index a969a933a12..6c4b928dea7 100644 --- a/applications/system/hid_app/hid.c +++ b/applications/system/hid_app/hid.c @@ -422,9 +422,7 @@ int32_t hid_ble_app(void* p) { furi_record_close(RECORD_STORAGE); - if(!bt_set_profile(app->bt, BtProfileHidKeyboard)) { - FURI_LOG_E(TAG, "Failed to switch to HID profile"); - } + furi_check(bt_set_profile(app->bt, BtProfileHidKeyboard)); furi_hal_bt_start_advertising(); bt_set_status_changed_callback(app->bt, bt_hid_connection_status_changed_callback, app); @@ -442,9 +440,7 @@ int32_t hid_ble_app(void* p) { bt_keys_storage_set_default_path(app->bt); - if(!bt_set_profile(app->bt, BtProfileSerial)) { - FURI_LOG_E(TAG, "Failed to switch to Serial profile"); - } + furi_check(bt_set_profile(app->bt, BtProfileSerial)); hid_free(app); diff --git a/firmware/targets/f7/ble_glue/ble_app.c b/firmware/targets/f7/ble_glue/ble_app.c index 548a86e191a..37ec3d0b9ed 100644 --- a/firmware/targets/f7/ble_glue/ble_app.c +++ b/firmware/targets/f7/ble_glue/ble_app.c @@ -181,9 +181,11 @@ static void ble_app_hci_event_handler(void* pPayload) { static void ble_app_hci_status_not_handler(HCI_TL_CmdStatus_t status) { if(status == HCI_TL_CmdBusy) { + furi_hal_power_insomnia_enter(); furi_mutex_acquire(ble_app->hci_mtx, FuriWaitForever); } else if(status == HCI_TL_CmdAvailable) { furi_mutex_release(ble_app->hci_mtx); + furi_hal_power_insomnia_exit(); } } diff --git a/firmware/targets/f7/furi_hal/furi_hal_bt.c b/firmware/targets/f7/furi_hal/furi_hal_bt.c index 34818e56915..48bce998eea 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_bt.c +++ b/firmware/targets/f7/furi_hal/furi_hal_bt.c @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -269,6 +270,7 @@ bool furi_hal_bt_start_app(FuriHalBtProfile profile, GapEventCallback event_cb, } void furi_hal_bt_reinit() { + furi_hal_power_insomnia_enter(); FURI_LOG_I(TAG, "Disconnect and stop advertising"); furi_hal_bt_stop_advertising(); @@ -298,6 +300,7 @@ void furi_hal_bt_reinit() { furi_hal_bt_init(); furi_hal_bt_start_radio_stack(); + furi_hal_power_insomnia_exit(); } bool furi_hal_bt_change_app(FuriHalBtProfile profile, GapEventCallback event_cb, void* context) { From a6bb9698ef67ab8680be46c2e465715a5c4d8de0 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Mon, 25 Sep 2023 17:03:51 +0900 Subject: [PATCH 769/824] [FL-3609] Add the coding in the shell animation (#3106) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- .../L2_Coding_in_the_shell_128x64/frame_0.png | Bin 0 -> 1074 bytes .../L2_Coding_in_the_shell_128x64/frame_1.png | Bin 0 -> 1076 bytes .../frame_10.png | Bin 0 -> 1209 bytes .../frame_11.png | Bin 0 -> 1231 bytes .../frame_12.png | Bin 0 -> 1229 bytes .../frame_13.png | Bin 0 -> 1182 bytes .../frame_14.png | Bin 0 -> 968 bytes .../frame_15.png | Bin 0 -> 971 bytes .../frame_16.png | Bin 0 -> 1160 bytes .../frame_17.png | Bin 0 -> 1082 bytes .../frame_18.png | Bin 0 -> 1068 bytes .../frame_19.png | Bin 0 -> 1123 bytes .../L2_Coding_in_the_shell_128x64/frame_2.png | Bin 0 -> 1122 bytes .../frame_20.png | Bin 0 -> 1153 bytes .../frame_21.png | Bin 0 -> 1134 bytes .../frame_22.png | Bin 0 -> 1021 bytes .../frame_23.png | Bin 0 -> 929 bytes .../frame_24.png | Bin 0 -> 856 bytes .../frame_25.png | Bin 0 -> 1499 bytes .../frame_26.png | Bin 0 -> 1408 bytes .../frame_27.png | Bin 0 -> 1367 bytes .../frame_28.png | Bin 0 -> 1882 bytes .../frame_29.png | Bin 0 -> 1757 bytes .../L2_Coding_in_the_shell_128x64/frame_3.png | Bin 0 -> 1115 bytes .../frame_30.png | Bin 0 -> 1426 bytes .../frame_31.png | Bin 0 -> 1423 bytes .../frame_32.png | Bin 0 -> 1482 bytes .../frame_33.png | Bin 0 -> 1518 bytes .../frame_34.png | Bin 0 -> 1539 bytes .../frame_35.png | Bin 0 -> 1536 bytes .../frame_36.png | Bin 0 -> 1539 bytes .../frame_37.png | Bin 0 -> 1528 bytes .../frame_38.png | Bin 0 -> 1837 bytes .../frame_39.png | Bin 0 -> 1780 bytes .../L2_Coding_in_the_shell_128x64/frame_4.png | Bin 0 -> 1144 bytes .../frame_40.png | Bin 0 -> 1778 bytes .../frame_41.png | Bin 0 -> 1770 bytes .../frame_42.png | Bin 0 -> 1741 bytes .../frame_43.png | Bin 0 -> 1004 bytes .../frame_44.png | Bin 0 -> 1025 bytes .../frame_45.png | Bin 0 -> 645 bytes .../frame_46.png | Bin 0 -> 837 bytes .../frame_47.png | Bin 0 -> 820 bytes .../frame_48.png | Bin 0 -> 925 bytes .../frame_49.png | Bin 0 -> 911 bytes .../L2_Coding_in_the_shell_128x64/frame_5.png | Bin 0 -> 1126 bytes .../frame_50.png | Bin 0 -> 880 bytes .../frame_51.png | Bin 0 -> 837 bytes .../frame_52.png | Bin 0 -> 876 bytes .../frame_53.png | Bin 0 -> 820 bytes .../frame_54.png | Bin 0 -> 913 bytes .../frame_55.png | Bin 0 -> 1080 bytes .../frame_56.png | Bin 0 -> 1026 bytes .../frame_57.png | Bin 0 -> 1132 bytes .../frame_58.png | Bin 0 -> 974 bytes .../frame_59.png | Bin 0 -> 1309 bytes .../L2_Coding_in_the_shell_128x64/frame_6.png | Bin 0 -> 1077 bytes .../frame_60.png | Bin 0 -> 1269 bytes .../frame_61.png | Bin 0 -> 1011 bytes .../L2_Coding_in_the_shell_128x64/frame_7.png | Bin 0 -> 988 bytes .../L2_Coding_in_the_shell_128x64/frame_8.png | Bin 0 -> 1201 bytes .../L2_Coding_in_the_shell_128x64/frame_9.png | Bin 0 -> 1214 bytes .../L2_Coding_in_the_shell_128x64/meta.txt | 23 ++++++++++++++++++ assets/dolphin/external/manifest.txt | 7 ++++++ 64 files changed, 30 insertions(+) create mode 100644 assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_0.png create mode 100644 assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_1.png create mode 100644 assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_10.png create mode 100644 assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_11.png create mode 100644 assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_12.png create mode 100644 assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_13.png create mode 100644 assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_14.png create mode 100644 assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_15.png create mode 100644 assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_16.png create mode 100644 assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_17.png create mode 100644 assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_18.png create mode 100644 assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_19.png create mode 100644 assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_2.png create mode 100644 assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_20.png create mode 100644 assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_21.png create mode 100644 assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_22.png create mode 100644 assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_23.png create mode 100644 assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_24.png create mode 100644 assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_25.png create mode 100644 assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_26.png create mode 100644 assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_27.png create mode 100644 assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_28.png create mode 100644 assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_29.png create mode 100644 assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_3.png create mode 100644 assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_30.png create mode 100644 assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_31.png create mode 100644 assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_32.png create mode 100644 assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_33.png create mode 100644 assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_34.png create mode 100644 assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_35.png create mode 100644 assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_36.png create mode 100644 assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_37.png create mode 100644 assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_38.png create mode 100644 assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_39.png create mode 100644 assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_4.png create mode 100644 assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_40.png create mode 100644 assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_41.png create mode 100644 assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_42.png create mode 100644 assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_43.png create mode 100644 assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_44.png create mode 100644 assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_45.png create mode 100644 assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_46.png create mode 100644 assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_47.png create mode 100644 assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_48.png create mode 100644 assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_49.png create mode 100644 assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_5.png create mode 100644 assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_50.png create mode 100644 assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_51.png create mode 100644 assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_52.png create mode 100644 assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_53.png create mode 100644 assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_54.png create mode 100644 assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_55.png create mode 100644 assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_56.png create mode 100644 assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_57.png create mode 100644 assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_58.png create mode 100644 assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_59.png create mode 100644 assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_6.png create mode 100644 assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_60.png create mode 100644 assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_61.png create mode 100644 assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_7.png create mode 100644 assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_8.png create mode 100644 assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_9.png create mode 100644 assets/dolphin/external/L2_Coding_in_the_shell_128x64/meta.txt diff --git a/assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_0.png b/assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_0.png new file mode 100644 index 0000000000000000000000000000000000000000..e34e7969077ef4ecc5a858c3924eb7b46b37b264 GIT binary patch literal 1074 zcmV-21kL-2P)dZg!=nB_N{B zd`c-=QFb%p`k{0wfB4;$G=A9NN^wZ{jT_)PkOSNbSC$6@(Zg; zXataAt)gt{1iqYK7ND|B2+)q{ zc`JWwScrl}<24I_hD&Qb^}0pSngXDWwI2WAO-LHwbB~JUUXsvh{nMNVTBH_v_Ph+z z{)q5#f7El*j!f$a{BJTQ-{{C83`Q3lbv7dvnr`(2~0x(kL=Owt!N#; zE}iep)16Q2^$ZC}2uv0LMe$Iy50#!JEV_D6ML7Fp1ucI_exhT%zZZriz z+nQyK0$R#h?wgFq>;$%=c*r3{Xyqb=lV<-5tm6FTBUqsV>$s$rR68J;3&gV$ab}14#(lO&@qISe;bVUOB zbuDUV-77dEQkVE~e;9%sGp@Zodj(VD=a5d|58A9_XHef#(g}Py?nq#CsAWsI-?DVOIrXbAaqwJXtSWG!@{xxFl#}kH5HB~ z1mX}d%hdI3*SZ5b{!8KP2rOj*R$GjrmLQVWQ_ka%cjG=yPv_s=UhjsI-S|HQ%zUsb zFZPLcM;rGk(>H}atyR)iwiZ!^VEca^>uvWFF@>El|KmtaQ}0M?p=}`bTt~qlscb}H s`I-f24>>yRF4K?d^XvwG!`z?p7c~-7@0VfV)c^nh07*qoM6N<$g2CwqH~;_u literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_1.png b/assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_1.png new file mode 100644 index 0000000000000000000000000000000000000000..cc6032ad33826b2a4aa05d0f644abc6d87efc949 GIT binary patch literal 1076 zcmV-41k3x0P)&dKX|=fPqt%esFNM7pWnyvzfrXk+(+-<5&QW~6QfyYaX)W< zVl)d~0cm25GTBN5Zq6SM&>6)-XJ!U(S{`6?R1Z*G5A_JBcevXVy9Ur7U#w%A_O7?; zSd~85na?U2%9VZQ8UPpISQ}iOy5HufT#Hly_v1e{#B78e~1^5`o!}9 zt+i8;##d{tNs+3W(Q&MSN8o*ql;y^t)umA)@FMuc$#^);wOe&*Mu#rT?qeWzFqUS=kj zPZ?oz#}!5p5`k78?+_mNp>=oya%4u^*^@B>3Sx?MiZdF)?ED(T5`j3Q z&HhfLP24k=2wZ@SC+!9Ui9qeQwlqlM&MP;%I2^4#S-7#d1|YrFlk&7OXgQ?bry0@x zp9L0yUKCFfgi@Y$U6Cid43r228C3vMGGeeq;O2xPfjuq#=Eu@GPe^*#Sl3I(me`EU zlYPkJ3~rFRAL08*G&%Jb6ReYCXZS&(~4Wmlce86j9A z5MZVyASwh)1Uk$x=x2ZWbXs7K=y`&1QFesYoC{Z$<6Uy@C~~dxr2A zL!`BY&)&>l!6#s2wBXPAZE;|FrXx z*wpT2o!gPV3_v2#i{yFNAyGiuDC<1Ui!K|P_RR(|R+P}Y0ByEfWoX(GT@gWs+*cB# zR@q(IIub}f^GcgjrSa4#5%`T3DO;Vb4M5T@^o7t4Y;6D(ZUnm+!uAGW>7E^yh3yUC zIQ&1Ova%7B0epiP!HSdt5V#SnI_oLw%b-;)-XpV*aAwK_5TwZA>b#FbkHPPR_uhO} zewKh;EM8-ESFf?eUklf^o)Zk}0000tl_mwyn|2^G-#Ir~O9F8Qw;Yb1;jwHb0NCHg2ec#u8-|$*1quG1xw0b;90!+f^ z^MTjeb2)j)#pGfMyK3A^w6&xDn0R*tQ5L?ACd*F%XQ#&~NmElf{f zEG2-KrCvrS5&E%Y3y8Y$;`XcwZgqQdYtcj&dWPyj*PilP{Z{*)etR*M06qJ6vGtH3 z+J>yt`d#1Gwk{<4m`Gz@PHF>BKUlvRrV(J2aPyEL+MZS?TCq%!X||{yZ=aa>lR{Cm zjbMrfn4#Ju159n;$8ZA74B2Ye=q;Pz7`g?_j@P}qAkr6)aI4$aM4884jw4bg($@%v z5nyGq#!G@oJ!OkQduw@Ke@keZ!m_K|*6KvU6ZN$P+hhV~fc+_!aPo*U)RwFhty*p3 zto~SIX~7T;uyXa+@Cd`y0vt;Tz$T+Pmz^Z&!mkyLc+`7q96L%6j0D;a>U~>&*LGU+ z2;~84dz2sWpql`m)z^{;u@DtND@meFVnlGt=hpEuyU#FH1Zt^M`6;kgb8UmdGKQyg zO5Jvk;6%c*=NP!4TLVPgItzE$pdtw%hTXP`9neL95tEOeYx|ppoZvW?usjjOG(Zh)FxGnlRz^;ijS5KZ_*WBs=)Sk# zNDOMWt+)y#WSzY!&fMqym`Z@_ z(&KVjCOY}ON6Sf)|1m8JVEZQ9E4}4=bwEp0P`hU>y6}`i<)j<8@)@JF!4v{mtC$UL z#$+>i|2R)J(IW(<9h-bvKWd}blT8tUkt|vLTl?&SNVbk;0P8ZAEh?vlj$35{--c5) z!DE@gC!o36lkv5A0PWNfiMA1$Jj+JQ=y|hx$cEkb;?*7?8VYKeIO~`eiB92#Ch0BvAV#rRs?#n^W-zZhwdFG0(-FY#MiKnjgJt4EqKcWBgk_E6fH=x*?J!Ebs9iBgnBa} zX5ylh%AOkx_jS%E?L1=n_6IR;^W7I$iaPl8>3WjX~ zt6%5Vz~0F9*qsKI0NN{6R;pE&*PiW&6G;FGFlxfk3z&($@Q((VF-awKd&BBFjikQ; XkO~x(GQCul00000NkvXXu0mjfg@`%- literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_11.png b/assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_11.png new file mode 100644 index 0000000000000000000000000000000000000000..3d94c89103c538422a75bf4cfcbb919f4997c1fe GIT binary patch literal 1231 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_14.png b/assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_14.png new file mode 100644 index 0000000000000000000000000000000000000000..42f844d3d73b33f409350c8b9bdb4b12d17202ea GIT binary patch literal 968 zcmV;(12_DMP)A%9 zh#sIJ-MiBD)GyF`UhDr{J<&bLsKBqjE?ogoXvc9`0Vh1TPpQluBi`GYQf8bH#Zham z+)P#U2z&wzj0P7(7Nh{`?W5t!0Ld5_RF)v)#L%n%kam`os6E}25tmj}hfju>8`)S9bf;m%&<#PL2)ULJf%CS|7 z_>nTOEm~R9Fr)RKIRkK&OO4T@TDsUCGia$7%jASL?J9xVDX}z8yffK=S20+t?iqwV@ zdMwTe_Ub{ji_aOytNB*v)B3I1fD?g90KJprB959ME;_MHi}B*h(oE%3tT2-~DuOhD z6U^K#+=aD0ZND^un`07yWZLL$Hl!=)@uJbt)h;XxWSfAFZQg(TgD1u89ILPYh-_y1+tT246Mppom@k536%$l~XX9}(6(NnM2EJej-0&#usu7KN5iqetC zvDUVE%IvZF`0pi6pp&-dHusJD!%}D`lP6_~sG|6LmBA5c$oD=}iq9#%%&`1F= zw5NYNh#&<(klsyieA!;#wf qx68JOLGm#KXeXK5;&py0rThSM<-`HmF98bx0000A%9 zh#sIJ-MiBD)GyF`UhDr{J<&bLsKBqjE?ogoXvc9`0Vh1TPpQluBi`GYQf8bH#Zham z+)P#U2z&wzj0P7(7Nh{`?W5t!0Ld5_RF)v)#L%n%kam`os6E}25tmj}hfju>8`)S9bf;m%&<#PL2)ULJf%CS|7 z_>nTOEm~R9Fr)RKIRkK&OO4T@TDsUCGia$7%jASL?J9xVDX}z8yffK=S20+t?iqwV@ zdMwTe_Ub{ji_aOytNB*v)B3I1fD?g90KJprB959ME;_MHi}B*h(oE%3tT2-~DuOhD z6U^K#+=aD0ZND^un`07yWZLL$Hl!=)@uJbt)h;XxWSfAFZQg(TgD1u89ILPYh-_y1+tT246Mppom@k536%$l~XX9}(6(NnM2EJej-0&#usu7KN5iqetC zvDUVE%IvZF`0pi6pp&-dHusJD!%}D`lP6_~sG|6LmBA5c$oZ&ZuqSB%!QYO&I z48YKy{_P-w6aYbbH!-phTrY$~MtYP@vq8jbkr{x{d+j~qu#f@(dau1j6M^_RcsKWt z(6k@<8%3D`6lf-h6!ny|l+Uyl^+ZR(YZEE@OCdu*v=h6MXyZO5hX5l{10;Vsc5}=> t9I1_eyKIXXBp*Y7c9OX*Ugwum$`2~Y#{v3|vJwCQ002ovPDHLkV1kB4%v}Hg literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_16.png b/assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_16.png new file mode 100644 index 0000000000000000000000000000000000000000..2f4b3b839c503640240c59136155cdfe758c9b68 GIT binary patch literal 1160 zcmV;31b6$1P)oDIln*A>5bA7&Qu41kAKo zt+kfKYw>6I01|ugxi7Bk+BuHUfJXK-Ie$v|JE+_V{BXsLt;-&dfK(3UHQ~p zupA`|falNE0VgQC7o~r$4l{=es_kF>M&5c-iaB)^IY{(9$qS6C12GGA+2i-ex)FZ^J>k+aX*rP zd-|a6L-d4yoRvFa5iNNq&_WhTxY8K|RYyAc<>Ypf_5sc(H zX80u9K1}ZfLWjw6@L+}Zcvi|pO6C@y{I%*!WgMuj?nIcJKwNH7uJ&lP)n`jF)DbW& z$K-0&$M>v+?j^#igC>&`2)}E?n_lcbW#cTyXn&S@^*FdzEO7!Y=Cox6D1lHWT`HIQ z)*Nt+5W>2$y|JQhh_y-{a_d!D}q)!=U@RCv#^-ZyV{Q z4Q=~V6z&PQ_*70HG(VcusaD|H!D`Jqr$oH6xhPaAFa%i!<&MVxafH&-=t1v^B9Le&hVT)j0 z{i`tD0-W+fg%+!Xf2u{CN`O-**}|JbK#XYwc-qRx*hK=*=V|7YKEOYCzZ!Nbk^lhs zxcd!A0wDCHyPu3C00Nczj!c9k005=@8&Qdne<+fG?%c1_qk*yyzyy0HNMe<_{kS3Y;;witB aQoaC!`qv1i-WbgQ0000 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_17.png b/assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_17.png new file mode 100644 index 0000000000000000000000000000000000000000..7cba6f7953b0656c3d11da53f55e2ce398f276ba GIT binary patch literal 1082 zcmV-A1jYM_P)`6pHRCt{2UD=N0APl5e`Tt*LA9j&EHMX${NrQ1ottJUqpnPEH znNO{?mc+66^X(2KzKg3RKA+Dw`w<#c$*wleS1JEGm6qT+wDDbWoG*H@8aoru$iSC1p5`6Z!bO}H~Cyvt!81Ue}r8-ZPc<*FNn(+=PKDE}$ zMXI7p;2S`}XyAm%0!e^+c{I2*Kw=CiR2CuQ$kCI2)DRXjn4CatTyhGfZ>_B6BwCzL zsa;Ha`Y!d&1DuGa1tfTSEY}fiX)3!E+3$Q+xGP~%GE=kS^8ix zL!8Ot>eziE5mqzMFdPY7Wx%{IEYz&RwqzM+mHo z>w{!YaJSEsKQhdF0o;AhAX)NSNYy)UK>H3~00!DlVKgKG2&D6Zk%^E5_z!8jm}-NFL`VVRE`ACQQLRMcE0GB+0>al&&5y7 zrqBpThBb?^We~VIe>^~C7KN_N3f?q5z~-a?Hm50oQp$%btH<;_zph>5_gnd`;65Xu z#sC#|uW8v}uNgv#(5F?Ys(XxK-ArLV@K_D+&3ktPQ>s~8JM1oJP zTz>4SyH+<3yd$a?&;n-uQ}3xiRyoo==rh1UU`tI_I_qApU@0i%`ATUJwOjQYIksvR z-;)QgiVIWk}Al2*#JYsfwPxK;m|tlx_(2B`3c>gm8*IIRfJ+d^7S)zWF# zX&&fG=PG!grF$iC7Uhqy!k%r}2*5!gcc1(iLD~uP?2bwGsrUBOh38>i*Yb!UgjcqM zy`9su#~`JVj$3&|=u}U9;(8~LH>kI<(4DO0JzVbu(trt7SWj?;ZqHgPk=6>OKH{Jt{|{}r=P8Rdx z=_3jQ@&MAyAQtG3RL?9zTFaN}s-35^m{y?2&q?)J9cUWJk3E|wBO45fC0fHlPd?E) zo$^Q!II_{c>2{@nlb(;jW3zzO$ID1_eGV0N{%V*&^v>kk|cU5ulXB+MT zR&AJ_876?0E&n*;t?_fTXUo44v^gn&%}D`lP6~jrdMrah7UWU@o09@y^t@%fGARIo zx>LD>2vPt9-swAdgA~9o+7>tU1`&mj0w7p#JVhNAQUHwBYj4p+pzZ}kT@y#KegP&@ z+oT6Dpq?O-)l;tH_;hPgAG8`7>0000JD7EIkh4~|*uxD8|0$>ry(!3F)N^~m*op0#EotrZRNj#orrD{A?E%lXn= zf~M##rEl3r%g+onQ{57Qd>;iej6%v-?P;~etL!6<1W>?>L3O)l=FW@Wb2P$uwTf0g zA~B#0AUzCX0bP;m8AV8Q`BGc8{d5%55_)`})SlG=4FmbMXYpiYhap;`bvx*>6V2%q zkBGpLo%YSND+x}zKLQ>b1+0F*j5OBgpv2Bs!vsWkKGieB39xc{L`CkZ3RNdSh{V{g$&psoc(ofAi4y#N!b zZ>E19K#RhW_B5OP-XF>F71wcmrnTA98}f?SU3->I-Z#-m@IM30bYZQI?peA;ZPsn= zSTsy00#V04;S@{h&lDgdG2Oq8^>*|V?T#Gr9PmGmv?6oFvEKWu^vE`{54Fo*D2axT pTR`izsHcvN6j!4i74?mviXX-Ej{7HGalZfn002ovPDHLkV1j2C2VnpJ literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_2.png b/assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_2.png new file mode 100644 index 0000000000000000000000000000000000000000..c259b5a5ad1e5a3013bfb1723c644192974fd6aa GIT binary patch literal 1122 zcmV-o1fBbdP)t<)mm$kb?zpF zJ0Ychqh1dgi&ZCzkOwH}&;+juOHvNY18|Ntstl$A&;d~QjN&_OC%Jb1zB3;`3p^ID zc>q3sR2K=HDnoQGLnKgp#+5+2M{QOHpZJmSAlI(F3n^WcL%H$*h`mw)S_~g86<4-? z?#-@JW-3U}{BXfXzN1LsAf!Qm6Nr9MXpr2Y)sANz zXXOTW3!nBI2?<^$Fcm;z_R=%d#@o*G-ly@D@M-^EZ{Sq|Hwn}d6P!Qr&e8I3l(;;J z8CEV=DnXGzCuZEo@P3b$VlNVy2$Hgdwu9+Q_%G_fYO9e&UPr4hU29}BV#zaFe*{{{ z2$4YRg`P>$2%#r)==lz*u64g6MiOaUJ}wU+?d?m?toz7^c|41!FR5+Bnpd@Y4rsJR z0(H#p{ztNeRJKI|7suH!;LZf{dx-@0dyV*c>+o{zJ)ZW`^K6imCA=Gm)|6W4u(Z_L z9=3=cYLB+43m>=?z?wMpj0;{84lU=_Z5^(NlFn#6pFRFxfg*tlR#AX6M6gKU1~@|m zPh|qHBxY8|uW${*%Ro=2P0QMOh?&&S(;+B}0)|2WB4*rrN6re)GU0SAPb=Xa2>)pq zFGJUwg9c6qkP=fXtn)`f6?^zRfM!t^vE%pJghzzo=~p77-f!{r zH9Pp&pmAo+1ny@2Xt|B*^y`$Fz}1KY0d=R)lcCkhCKi$eGksLma02qn-HL7`%t;H z?9IRppa)n%^P?83DTw3=v^Kk7Sr2fvd#0XXcdMs(9|t~&F*8PlZ;g%5!|vAKTKj~6 zl?EGC(ugoK#C?k8Lcq5zGn4R-W5ItNJ1Ttd_%dvUQMmtcq-J?{Jv$>5YnLoT%$M*Z o1Xu?zo_sbcAbv1ZOA2MgUtl%O-@u_QY5)KL07*qoM6N<$g5zNe)&Kwi literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_20.png b/assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_20.png new file mode 100644 index 0000000000000000000000000000000000000000..ae6e765320468d8e8f2d65e5f7733beb50428158 GIT binary patch literal 1153 zcmV-{1b+L8P)#%BkmJr|;KQF=waEoupim?hM!0c}SLXKz>{6ra+ zJlK(t;SGx6%`*ZTm-JfjG(Tmkwv9BQA__782Y+B`>nqEsMc z55N^)^Gv*Ld&>TyEutmQ)DM@xdtO5lyJCqer?LIvOo5dBg|=Hi!fUaoKg z-1B}c(Bldvz&-Du2N(ge-uQW9$x3A8*un^qvG^A}!W7^l7N|)EIR;wfpCic|RU~81 z5CUk5KfCFgwUe~X3{4)=u|WdzEI^_Fp5&~3QTq{#omKgIms__2aK)#RnFT+(2e0i< zPy;GXNzVP%cVy0P0&vB*r~&stBr^$+IjXWjK zl1j=3=W}(=ZCkaEVkCe>{v+1m>bw1I=Fjp5lB6%^Gcsp10nDcW>DsTg*+N?|nO@fJp%)YrhnnY>(PV>_kDcK||#> ziUF0c=@j5CdT*zN^r8eMsa9Avf)VFpDewvelCyvNt;Z_CM zjXI#&E|pfotpFBy27I*JkMgInkC>%iB3kO9CcsGkJsUvDZys1@xmO*cb3vF31n`Ogx_`#P;FE@lr3%ms9302t^Y~;B65ocOaUzW z0B#1Qx#09Sxn zke>B!)Gtf%D>CIAMgU3i+aPA-sJ5uStxu_K%8nOOe^kz#9cpYmODWm51Smio1o!{U z#|#pm5<6N*ZF|;e>sV@h{ApX93Lq^AVh_;U86~=ES+HzaJw7zHQYgSIAnSVA`u4;U z1ujmHEl(_L+Y+b%Tu|2K!5YIVcw}DE*5m(A+X7I4s11t5XSU+Eux>(;O9zKvmfqa{ T$gmm!00000NkvXXu0mjf=(QI2 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_21.png b/assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_21.png new file mode 100644 index 0000000000000000000000000000000000000000..e97affd7e33ad404e249fcc1f0c9e1251539a653 GIT binary patch literal 1134 zcmV-!1d;oRP)c%G;A-j_dDz@AYcY2SMn+dGY~=mAjhCSXqX{4)!l=lT0DJJJ$hm-JhZod~@p zTmq=_PKC4&3)`zcGa-!vtY*AN0-}*k5mFFuWbrMW0H2ur7a4pECBP>q|3$Q~ zVFak;|3jqDAq1%8|3sAU#|aRT{MQBt5rm8&!BGO#%-`d5ERGSNl26CUm>eNMC7+K| zu{lD3N`791DL`S56LE4UKqX%X?|8Wq09yMCJb048jQ~~n-2oQ@WJ|upb~d&0ik%Aq zG9|x&z^zewIIRPs9cfphZm3)6#$JCSnwdM1HXB^!Xph%#@N=YqMz>ENx{6~^N zvlK-W_=If}0$|C1fA77Q8W1tY3L>wS3YZXJC;M5{;0BEGx#lv6QvJ^)C?foiIVRV9 zAAqyTe7JU_FBiU5@4Yt-OG0K7p>{L0ty8MYhHsDOG2fJnemgVj5bEvOBZ zd{S_2NcpW_?FCS-?VYXvg}5EmgE*BkGZvx^Z{zES#?LbMHp)_(4QASr|1 z9iLlCsua88Q{tP{wgw^s>}I~#jKy{W8U>QJ79 zO#qAJtIn<$)xMWKPX89OG=hj~1!zrI$WT7hKsw8{>fr60)zL6}7ho;_zeRpmxWP9Q z1t{E$kQ$(n2c-ZXj#7XRhlv7kzZfz0#j$%6EPb;)LZ0EK0OPG0c5F*=Vt)U`r4I+t zm?XZ9@V*LeO|T85oCJ=~umh6u9qgEQY@fOA7{_Meo8|R3gk$Fb(u)x2`gb)Z$S#Gb zoM+H;?p1IGB{Ne1X<;b6G_hJ^rO#IHYHvrWuv`{gYXGlf-7IVWj2nG6JqMUSvkV9n zh{)|IB?cNm%b%5MwZSw%;#M{SZp|icOyV+bQ0_mjSW<W}?qB@44l86#z4K#<0g zW8=rWO{oCXglOfOavlJU(E}JEtk!R7{5S;o22^Z~6n|FY3;+NC07*qoM6N<$f>!_r Axc~qF literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_22.png b/assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_22.png new file mode 100644 index 0000000000000000000000000000000000000000..b5c615924320e84de411f7440a52ac039ccf72e9 GIT binary patch literal 1021 zcmVh_d{!&qSPKt;!{GsNI6MFjhX=sn@Bla*9sq~K1K@CY02~evfWzSda5y{w4#$;} ze-?ByJOdn#3;q&gT&0J@5HRa|1~?qw43O`B?+MM{b{u@ghe6iCXmxj5pt=acUOBW@ zGqeQHjyh53XKjfor9_Qp%`TPN@KAZRr+^mEj2$C-jq+r^xYH7Vb76MIlkB)cSU3N} zh7!I9SEIG2vn7Y4pL7N&bu0BRd0MuN=r+Sc-UMpRsHL-O&acVC-q+p(w0L^Xr`R&0 z+YHY~OE?7_=ZCO&_swxWq?58|WQU--2&KdqASgOQqJ%3T1)|uPpXWK+Ede!!eh1v z(75w==oK`28B5zPEJ|iJOeLRI!-tJyTphVQr4CYd^>zqcJuyD?%b}y=UyV;I2uc~+ zb90SnC%Z5`Yfza2ten3Ko@|%;9^mcH522j_q~zwGHGn=AKDzxo$@wAR_ReBZw~lY{*G)v(%Y3Ca=zn~OCr&gcR}f(Pdvs z>A03W%Mt+pW@R@gz5$hAhry|AT?D187|zPRaQLf!nBoIh*RK00000NkvXXu0mjfVmZ(w literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_23.png b/assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_23.png new file mode 100644 index 0000000000000000000000000000000000000000..ef4876275d1b45266fc7b2d6c72f04eb52b30bbd GIT binary patch literal 929 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 B)Or8_ literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_26.png b/assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_26.png new file mode 100644 index 0000000000000000000000000000000000000000..379e29b50da74d40cbe316425e68671a6047b9f4 GIT binary patch literal 1408 zcmV-`1%LX9P)1RCt{2TkUQvAqdrG-v5>7{^)hXGJK1!TRlm$=?p3e9KmgC zDdjE)+v4Zb3LJbECjd6b34qOU0$_8T0N5NS05-=7fX#6NU~`-R*c>MSHpdBo&2a)? zb6od*z6;tJhXFRn1%IiveoBvniGZws7+`a}7@%eUsR{lzYOST#`ttMJ5dfX99S1m2 zj{JAX-}n6{dr`tLLO4K~5%R0)^OxiyF6#s{03#FXvx073Q2KNB%|>H^`@Ww=;OK0Y z=4{GgnriRc&&uHRZ2QF7=LMX@G9M?yb^%S8`}D|D0R!jJ3Q%qHB06Q;i|x>yg5OgP z&GZ3>`n#UJ_-hJM3wz`-Ekas?G8Dt$XPnGv8}M^VXUPsu_fa-b`aX)l2%US!XwJ6+ zGDDV55qXcpsd+fr(KeJ4xC!iyIy{cr04*!v7%8(d?ci7l(k!HIEU!5_!eS9S@R-M! zK{E?LEmNZN&u+W8^BmR!qnI^f2b_P}RWL4=a27|AQBg<1=^t@8R~V1RD*#5ee_X}v zrT-Nn(LIJWSUri++YSzA0Ik)@rvDj`=)aVw1HI17Vn++eiy-OKB8%T!3nIUz z7R>=1D{2Lw72MH~Wy_Cc0Od&B%1L9m%6(LHXC=fsUV2-{oI^X zJBjs;-UnQs^VHG-NHpJ*1)lSNL`TEPNXJ`N);HjQ=&`5C{fu@#)#6FfxpMVSL8@uE zV<0lWHb=DUNNWS7%-87un;~U-PoXP~%`Bi(?*S~EA<^hJ!ovX6xKHE9+Acs)hL%DX zFHmMS{YPxF$l`Gx2FN%DsAmzIYOTM24`5LMPmHTkt$(#JK<|6Xc#lmUc}g#9As8uW zBkXAjw9vnXY0j6be{M6f;hi(8gJd^+&#l>Ozt(PQxTGlIvV^K^Et_EJATnUBfP2TZ zXV&1Co>|uRFb00cL1X}_{o{=0&L!)a>P3`MB-^ZOWd^@xZ7kd7gY)CHG~3Ak3$Owz zg^vXcDJrGlShYK#Hd$hTCWq&M;P!}<%Sfhh0C+DRtjgkS&Ss1e&={b};Aa?CTe%`T z0yY3N8o?Rtse&b=wCovGH1>dUK4%9+uN{;R;Cj@NfDyU~!cnI=-C}4{w*6oO;XEq^ z1Es50JVP6M*OvCn0`czwC}^!;Mr+gNr#6aWC60Er&#W=HFr@U&+7}Km+bOVi73LyH zYu83Pp97U;-nIvi&ln>y){K3@I#KJefpI&uvO0gXu`5tNg-`Db*24mG4X}#yOIhcO z6bR4$jD}~O$4#K*ycL{p!K@>ojg#&JR@)zJ=i??&I#X9+07+l#`dXVCS!<>Jq1K|- zR*!&{oxd7Z+aJqOV5$K;&Yy9{tr%#YV_+qS0pKnCRyA@aR@)!A9+fnYe-HOYvi1oT z*#Xg{;pVbJ+FxnCONvU1b8(TuDJ2=>2@V{jl8h$_3=^OL O0000txvo8u(F<~RwkIZgs>j*|eJ<0Qc5 zxJo(S1?`N(0Gs227dhvr^f;IZ$ohu?HpiO*>h3>1!EYnyoN~_ZIDeD?7<{c9;6yp{ z-yvU0`H;ORVVEHtpv;l~E_sNTb%KN60c3$&6F4IK4*CNI5!1RR55b-SsxbHIk*5*{ z&r{{7W|XUUEjo>|m(v-JgLc+?R6u*ifJ6Sfp1t{_jEgobFe&ggrxPC2Tc;LzN;hl= zcTF*AZyP9mZV50O)Q*=NvwF#ql9>zgQ}*PX{ci$WvyK{DDKFw2?)}9~2EfmtJ)CWn zEXIs@+`bGN(Ev*Hra4HP)4J1I?eOemSkoQO7^7o7Z;8>a`w23U)M@9`>-VgJq_C8N?lVTC*G*KMQbO=~(O4 z3C}Z?0q~-|&d%Jumbzy}Z(F2wtaNWpdMU9q{akjJQi?UxtQ{wJ9=3MJPw!m?hWSnIJ4qRP|9{D1jwFfQC19hW*rq=SX9X?iC z0p3Y1-7J;G-atk%ivj90in612Qhw6KeGX}cEGJ;;C)LY7~O0Z7LiVAl5HXbNE1stpW6W( zGxUUYPR(haFDy8z>ndDcPg@<){tc_cm+NXU{QsJmn{Dj#lQP zV!_FgcBMrBzW}XPjX3g9MnQe3wXNf+`Ff1us8|a4sFEcy0n0eWp~;NkOKVcsD?!qA zlz%uqtZTS?fSJLs>!n0@zN?}7fTI3c!S~`;0^BuZLK0AV;GeOKh8`sVj-z^kC;S8T zviRm!0=ULzh2Tp%QqQ7uSjI@#EBcALPDyza7%^^le4C%tSHYgf&qAZWj+OjmSxZa+ zZ}3;j?5D7rpR^?SNB~MXZx1jUD<_(#cX~=cy!P!^!YZN-;7sG#mmA0??z43ar^jl3 zg6qMbbIzZ~z)>08bH`#GRyhXN=aI50v6{!%nxCook#7R6i{ev)^%*CpVP*P<=-D{; z@G;iqLsu;UxJ9grNeXffkaokjB!DN_9zR%mG1P90Y)@1QH7=8ue|#!u&yKep%%c{ Z{Q<|V0t{JT*ZBYd002ovPDHLkV1jSOji3Mk literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_28.png b/assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_28.png new file mode 100644 index 0000000000000000000000000000000000000000..7685c3bc3f7e9dcc355b0509a3cc78787b3ce434 GIT binary patch literal 1882 zcmV-g2c`IlP)Y^Lq$%zQ47nz@^=XOD z?z%3^vMs;(v3Pu1fnR(UzX|Ze@tXiY9M4Y*-?mr!v99ZfH2DnfVE?*&fCNUvccb@v zjuW!u+<7x`REKPD$NrhPyahxZ>xgqcksOG$&FtmMm&(^&98LhnW;1f|T?16`(fU__1wDTr36iP;uCF_o zQUP{_km6BE6nrNc*cdmF;;xxFKH8&_Vid~eSqcA~RZ3LmyTtE^E>05d;~Y7i@yb3o zf!O`CxzXVY;AZ4#<{sVsajYx9!|Yj$I-AhVTL0x$aCgCMd~O9<$@n~Hf0pfXg%RTD z%**ASIUJ2gHdO6V+3)fz7jd283NVuG%wS}^LRC#%;@Fj;n7OMHlaeuW!lhc9HsXMQFRs|o;bCSi?xf&na0Mkjyd9eOmE#w&K zJkMiYupXYvB1_O*o+~?%zWI5U$v8kaaXWBg7xj1}fo>4k&Om)n5hfRmG@|=42~eP2 zT@b;o2w{FU%YJu!cg@zDKo(r5Y_i?jAy{la(ld`!wx}@`7hw7*0*Rg}KaBWDA+GcG zpFx!nml&ZE)Rm=_?>M9)oX%QK5}?XdB{|cr8iUqBdZ@LxZ5w+PTm|0|p6}(}jpTLp zn*~;}s-UuDamZ+OZx;S1KzBzeBXYTO`|!SAW$^3(yQtbhHQrf2{;d3#lc{UNj^PTx z6jS%Bd(mjtI;!BGl^j*@E5exV+dm^Y&b@g5NQHneg4A^eqjlFr!QZc}RI>5ts&qnQ zRnXOV1eEA*ARSIq*|Ou|OhBZBDS>vbQjR^3hAM8r&`IR3%a(E*QPH2VTl@bMbAg&vav^Pe=@}!;c;%oUX=`W9Lqbw zHO}EFN7g_Ufc1U!+#!aQ|CB%6n67`XwS!oh{=ZN`kkq8p23e z)+^56?~hiZEL&9!RL;>{r|ciW$of_Sus(K1@R985G=R|M4y>nloyvkf+b^#ye-uns)-&1==XKU(b4NDC z6%{~nkhihU5#VmY-*sYrnGrrmw{8+Jxp(vr!c%3)Xz#zW*X>YcU!4Hr{LG-yIOqP| z{G(!AR)9N}xvK25*1$eH>p42y#Xn3(1nU&=X#$vaarMmiU7*(C=x_)BbYUL@@08&c zTc0IgkxVA@+mb+jQDii%d%XggK|9lyxm2RNccoXh^rgtPmZee7995)rCkqe?m3M`$jKF?|uFN?%+6&8x;JsO(jJ z6z$TgROhwX7_=^u?d&UHo}eD~XfFHQ)q#z5cAnD$9KGD+ zWM!Cbt~@652-fF2zFQXXmqCc|I0`z#5Y$@ee4Xbw`ZC=Z$#dqgd8l2gU9Jde1+^Bc z+(_T@JpfV&5sQjwbr?C);m)N$Lz1qd3b4STxtZ*-F+=YG)I_)J*8>5^jx_KvMGk!t^xYDnG0J8J$ Ul%@YR0ssI207*qoM6N<$f+5DWo&W#< literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_29.png b/assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_29.png new file mode 100644 index 0000000000000000000000000000000000000000..5f114a4792484666f993282c843792cedddc0000 GIT binary patch literal 1757 zcmV<31|s>1P)~?d`#B@%ps@4}Oa$0B(*a0B(*a0B(*a0B(*a0B(*a0B(*a0B(*a0B(*a0B(*a z0B(-=uLWqWwSE8o=ZJ0FrqMs(*mh*iAHX_L5vfZ50OJxBpgVKl_qJ_Y+qNxqs$b9O zSK$8=upa?%W*O~9@Xqh=ULH%&40wk8UQYXtWbo^qhS8q^E6dU$aNqZjmluFJ-#a*R z{x7oMpKF6wk)=w%5`SlaRRS@-$oc&_8}cg8R|DylRi`Tiub2Cy1$bp5Fo)VTU(%5f ztDVpEh~!u3`!iGtRHrKRtX+)C@BIOXK-I8HX;z)j_*b*U8%fE?kxXQ5D{5ovb2ei{ zb40~o7B7`S1>X5l&u0RTC}a(Vf^Srp{<6;^IsDm5c4psu%p4Xizpo*1M)pU~D_LC$ z%oY7K8}zD^eB{V#5O{e;gN)t+{8bEH&7ys*zoXWG`doFOif9xa*jgla2YP;F(jT2` zMtrvYcb-1{ApudzXXPL9LcNq3+Megnk7QDGU}rI%&{@h_CAir>c2)+@`zrvBfDJEM z)mZ%4m71dN9YafJS<-s)q*L zs6whOPSpeH)$vA7$__?--5!o6;L7l)BFp~hEVU$i`pPVjWv&kjOPfJ$3rG*kZ zQKtH5)xj-WpB+pQltI?7A3e_}k*zRC`CI%AfFqMND(On!I_gAknAM2p8&(zl-qCZJ zo!Ic5>CiD;0g!{yz@iE^lKs{&zAvu;Yk3xGwNnGaJJ`-8GXTE`irx`zR2EnCeh;4^ znGTUI>^-`TD0Z;xs!pR$uIT+zHn54)3d$Uv^-KS=qFmNaqQMmDg4!B-x3p1U&zc1y zuy(8J(%;{nK^3ScqP}#b5{&Gxo}R93TZf*SAzy4eqeApQ?48ST+ zgd_A>5JbUI!4V#+Pv0tIM3*@j_0NdT^S0;CXcLU4Lf>U!70=U0cFTfL9V9)!iceov z=UX9BZ>$K9p0RTnztsaKLssUkY&~G?%fdcmHn4Pv2Wq>D4pXjy%Fp&0qG7{Sgey58 zRRZr6W{>PF_IbvTIqjRk=mx;n5m;4W?ZfZ!`&NQEKe`2=fLDT}l4KRU)a!zUSM&&2 zkMxG}{WrRT`CzH)glL$9BA!aNMis;JMinOFulROrE&nrdQU9HRtV*C61npsKnRD!3 zB$Kj-$-{J0bycdW*PH2tQ2wuBZ1@Q zoURx@D&g#WmhE0HB)6iQU$^|&#E;6jS~X|1(2Q5)|Zg`Q~M*=+RS zQiAYm_K84$AAn8QXP5s7YF|eBRcI8wu4Er{4%3f*5ma^hSrLvV@@V>vdbX!WG`^>^ zGp=l>{u$C`*TB}sf?)*|c{<%J8I&MHbXRBt%30|Qa24l!d0LB|Gy6PLP1r1Og*Kq# zs|BpcqFIO>q0nV}7#(W^Y)-gJvaqUU@%2B0luasDDrf_hmH+7cYRf=vCZqdx8&Fzf z`e)*fl&eYxj-o3W4QsQ`@Kpr`!|ZlBV*iQ_uw60`q+HR{hXtEr2L?4M*j~in zSDr}jk85CdBVad#YWY`qML#p1iVSSu$R58Bwat<8&19?aiiXUOmcA3J%#oSFBBiP` z!Y}&$5h#I-X!~b|kz~wl2Lm9B9_1Q?21o=Z@L6}|RmiT39-|4<_l{n<-%6%sy)Q#0 zP|%&aa@G~VhSpfJ9C^Gwdk|5=gPxwj3fZ7D1Pk^6TZ#3S0? z*PL^XMp^v$b_W{Y#Ty=8uh%#Gks8>^?l#WvF@ANbSAx$0#&^eYe&fV$*4g+x-t@(8 z7McOv#M))DH3=+^|2zO-7YjWZCwSBL0L9S;P#oeC001|Jw=(!Wmk0TD`-8VZ^tl4? zn0c3nPmhgh09Gd;j~W4&mSnQyilKAkbEi?b41msjPRaDQrw*xXMsS`pB8NfixH5*X zIp>sIbYl%%=+`r@E6c)FV{mzZh7X$JxiS<~c~V*FY+=qBp1|fV04n5uOQeS8dF1eW zPJVabfd@|-{RWVMAE?VKgUfr{HVuGd_FSGMPi`=!AZ2hfNe z&ZlWEPb+sKxbONz8xg`B8P_I(7x0AgDI#ro^x2WXMSMf~&~9W&usnf_1WKfhR?IbG z$!cVgmeI;{&l=ghqO@-*AGhya5-cMKkwC6eTGkp6jS$ix6D?zrORe8lI*J{ln0FX~ z%LCAll?a}dw%U=>l4tR>#mQb#UgVpez={!IGzko1Hv<^MZ_u26n*;{1qw*u*yns6r zNYfk%gtSKdyk+Qntsaj&m!AcZ7y*_UBM=AzNT+z((qVPbIv%!&o+K|FQLiA2@laj3 z48R&VDUUYBDF?r&v)ejQiIUE%F8pNoFQG}G6DO?yD`c=qU_oH-dMOfkCo!WkJI}OC zSc(MF_gd7>L(I5#c3Of?aQ~|aEddfSqq8?k%xHa$=g=h3fs;Hyln6EnEDA8{@SQ;E z*+cF)Sx@v&de4zfsjW!grSci!B7vSzqgKun1sp>@4yN^LN5&eo{&9v_VL1$)1xT}1 zlfh|iEsdQ|(CK zm|gjJX~S6F22#>TE7z71EGtC}ixHff-#t)uHrl9SLlVF@jgZxX2rRFGbX>h?_+Ky=5A4gggKxRTAmaj(|Br zXAwZHTs9Z}6hIHlkw7g_qOGl5bdKv-G|G4dnfQ*jW8%;xP>B4U3{Ia?xh8?*uOU=0 zdW_x$AV`t2>?myj94W2l4N4mT1~q~zhEU!BG~!!fStxG+W7z+fPG%!0X8<^A$px$6 znUl*1@j^(p54KJX>9Kak_yC6z9>9Cv&nZ2t>j%8YkZXknz4Cho8(!2eMq)xo7|Y002ovPDHLkV1l=Y16Tk6 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_30.png b/assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_30.png new file mode 100644 index 0000000000000000000000000000000000000000..645ffa6692bd5d8b4dfd364b80f5126d1a30ebc4 GIT binary patch literal 1426 zcmV;D1#S9?P))Xn>;PCCI{+5P4uHk617LCN09YJ5 z02aqP=lxmG%GeCBINtJyl+s;xY)ph?{mlT2{z?JT`B6I1 zfwJX)Lq6yHCHqFgGD0j!nJxcY@)9q0fQ`Qc$pWV$a76TN^oMk!ru9f(!aM~OVe45V zj{>H4{N6Ur9GmHb96H6O|N8jiuNa6F_mZEX<7x7D=twD@17Kqvq?GD!0)HX0N9cH> zO+EUYJ`5UB0FFN5%+T>fn_6`gLVdCwZzS!d`TeV$-buQjfc%{gZ_n(wGmnLcRuk3I#A&@r=5>6i8;z}k2p$Gph& zAPboudY-@lwR)`e8tEVUCh+PR)iX?APhQnC`W~Rww%T+0aqyd{b=|0M1@}7sMY}xb zy!6r1+Az$Tl#@wWL#AKO8Gj+91t@aSj*-=FY5&W23vxc*+z`XeK$ifJ&PC2d?ONtI zj+2b+kD%qAh+0ZVEE%QJEAxL{IcG}aEN5En7UU(1W()nY&d%D-yY2xbB}<#pIGVye z=y9Xe>fSvZ#J`F2@=_Fu(zu#J%A!O|M{NZ?@)TgPAR`m4hb6-n+!{@P8m1=|O`wJW ztZ_UYa(o?-tj;UzfDk#hrQWG^GBf_WW&pjL<$F|pdQqz#b|sH#%C&SbbMwHk3_Gr6 zSB-v_Q9V5_14L2mIaiP2v4ieY6AT{BdgHYmZJD$bZ4zV~bA0oPY)gijThph3tWzzc z5Ne7aYsaG$P`k@mAV-&0WE zr9)&JvjIxG$9T2#HSYneNqY1iF@vWI>1FJh35#+qCbH51lQunAvMmKTbrFQB{$9>U zhDSveTerwDdIb%dXXI3Co!B|m*j78{=TF1X6Aj+m`3Qv$^$ug5U$cYs@1iHtW6`^O z>ak>dFE*3whS&$r>o6{YyvlZ@^DQ7swPf^rrazN6qn#WD-<|7L-k;iYpuNWVvnPWb zjBGFV_q0Qf%M^Ih##!qn?X~o{0q`W}OPj3X>P|4s@Pu_c{r*rXz@p8k1OcKkD;?*G+qACJG0-cVT9tZenY`!x z5sPYV2BbpTiGv=P>XYXd^|kXMmi>24m#(t2-Nwq1W27`4j-KP;i`$jyjgXRuFlJ9B7?X zqR6j78!OzcAmZx<|(}bX`DyZrM-qN^mzKoEWRbeLWj8nUNqrC4qG_v37!=ydS`6$5ATp~tg5v?KoLWq|J z5LhF1UPkF8%H4=kE&7!@S=$&e1t($v?bO!dPtV=*6;uD5z7K#R&!SfJL@#OBRhB4y zd*t>Out6NPM}4I(4{}Ydc4U9SJBrSlr-i6u)pAkp^r%>~NN;G}UUAz}19)lnYT%>v z`gIdH!uhOW9;~#0cFHVvh}OZY0oWiNwccO${nv9oLd*^VX_y3J1%@3)?0%`k%HvEB gcw^37@08N<4-kuSBaar`fdBvi07*qoM6N<$f?RsI2><{9 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_31.png b/assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_31.png new file mode 100644 index 0000000000000000000000000000000000000000..a086ba60c4f2350f6f42933de30d1fa9baded7cb GIT binary patch literal 1423 zcmV;A1#tR_P)h}0NVXKC-`lYQjSu}>F1XtfIHti4kS^w z{CCLLT3=+JNSHH31DDzI-zCrShz_vvJK#~^EeRaKzK#CiPLyHYljl&Y020=o8F@sQ z?D)NHnmKl)4|3=foBr#ICx2uhQrt^^hK^5@ze7hU<(&W<>!6g9zX|*XW{=SEi8l4< z^Y&rThy?Iz$ue5UsKobEbr5Q;K%QTdsPEG^kuQg7|)>>;ctd7W1 zdALXNvagia`i!muT6K8i%&G*aU-^VffR@VOo8ggnCZ5M}o&9K6qJEJ+2-?n%+J`VB z0%&^B&8wAMkGu~E-vmZNu1t=+u+IBf6Uj!0(f0t+KAC=+J)`aeNbZpx9F-c0bMY`+!pZJ`R@3 zxe!Y1%6q~D3Q_Hc=lx0<0S&1u;1!*3);f8CXp>s zArgw^Jh`pbxN=^yEgJ12ZAe#UJ>V~cMmirEn_1kMAs@}t+hq}FW#*x+b0#0MaQ-ep zDjVf|3CilPj?p7m(Z-b#Kd{)aDVTw>=&SIY1F_x zqX&>vWqX7K96jdSGA29%yev{2T4TES`V4CSFb-nS+!L#Vfi zTHhJ4&UEelyN&@3=S^VriT()OVOJP!?RYKWx^odV`!de&v2Ia2Ii68wT8kUasJ#e* zIC5K8vKXFUhqy z8?BFQM-E5P2(|I~nK{4+AmdQGHbF#;5jK(zu2+jdXg*h*uQ|`M(;^CQleRx+g@4U> ztxZly+A|{xSP3!TQQEPk?NlzK>7(|mME3}2<^UsM?G7(<9gWQKBj;hA_2?+EbYzlQ dD@s^I`v>s;WD4OutKa|t002ovPDHLkV1oauug?Gg literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_32.png b/assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_32.png new file mode 100644 index 0000000000000000000000000000000000000000..4fdc011d11f1d6ea03fbee392d145548409840dc GIT binary patch literal 1482 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 UXfJx1^Z)<=07*qoM6N<$f};=K2LJ#7 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_34.png b/assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_34.png new file mode 100644 index 0000000000000000000000000000000000000000..e13f825fa75c0fc72347ebf71fc55b8c4ab086f0 GIT binary patch literal 1539 zcmV+e2K@PnP)_=A0kjdBMruVZIg5a|b1=o-44!JS(5aa72JRNb&;fnQ@GatQ8TC zgG=t5b86&!22Uwdw34B|RSr<4SC`Q-u>C?2fD#6UMwD6=zAybM9H2|TDx+)mNk;(d zgsjT0*t2(c06dPz_)&da5g-F?A}bQMCpiEdi$k>zWCW;&G^tgjW*y*e<9CjK9LG32 zCn?FzBskxMI~U2Ef9e3;gK+riPH=aG9Ak`Eq?6ZhQi8K`pWOx4{XW987iI^bl(7zI znaHaB_HJ0Y7{kwRq;4TPK*s1?ABli$H0XAsjQq|c9>uTK8Au1HET>{v&bwAexI1cC z#_vAcOLDL?P-Fb#0G)=9;K}*H4hb&KB`7{$@CEL;N-+(T;yVyL#%B~*k+^vwIl?i< zm{j9csJ!r?zN;(*RXgGxkqztn-U2$48%cKd){8?$e$aVuVUd~TmTM2?zV8A&@E$iT z?K7dXWA$@#Dx!e3I9A22)bADP6!AJu#){jpE>!5=$t~CKIF8?!L0aREY@Cy6sgl7} z*}3{Td3a3gb*^-8zk*u|Y9+9H55)4N69Q%YJE8#|A77_)?J-t!OW~AWt{_+}(`Dqe3b}ptgN< z09Wxch@APm`I7+WELH-g0VU;O^$vf>{ak5mykAP~D&<*WWJch2fK0AHIR zUOqhSU4<#a_49uTyCC(|4N=bKV#rz@}FM{wFv1>)+92lBv~`&>UNTjt4iNi+r1rNjPcRKN=P-d zm9Xpn4`I>2lRpX6xF-xl*YgVVoT5_VCsnYxHgMc08-EPBC`5&R_>THcYlGimKwDC_ zKA~4~dlTgh+}>l&gsJyWH~s~y6KIt{44C*yi;y!qW$PFBf-(x;3%!P=ve$~Fk3vP5 z?Q%NG2_&94+h85J??fB_ZtpXKG`N=pEQcD)llVdTS!88uy5mLQfo6C~ANWlDhY?c7 zYf;*&u=Jji-G#__k$GD9^T?ORd`EyNyb5@|mFsVj(Fuz|GQP#}k_S4URIYE@H1efHj6E8-EPjoeFHT1^1N^;1ZfWw&8v73C16TF7-;qEr?8Rl)>`f3dWv4 p=ln1}SH0R9QYDoFHSas-_zQ_vOBJt~Ay2_Xd6 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_39.png b/assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_39.png new file mode 100644 index 0000000000000000000000000000000000000000..7ca97b4a7de316242b68d76e29ae0e8db3c1b98c GIT binary patch literal 1780 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 U6Nnm72LJ#707*qoM6N<$g7&RSr~m)} literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_41.png b/assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_41.png new file mode 100644 index 0000000000000000000000000000000000000000..c33f03e927ae06d8adae5aadaf5259663c9b1d22 GIT binary patch literal 1770 zcmVM0AOH&rTKua@^xZ&v z_9FMD6Ej6TmfFq?mTLBS4a;~G%q4fr6s@$@XB`gnfl6h zh<0`awxo& zuTo-B%!)2x<}*jPEKXMPnH)+4*a2n+o_;cbvbd;1R76pf5dp3MJlbIukhAeB03Gd!?c1yNmPCA{g3P5m0QISHzh0Q4w^k zso;;%cnVONYLoNC7F({Pqmy^t6L%SLyw5hqdfg+WXDSIQ;E&O83b4T{?9G&R2GSg3 z%B!8vwg_)5JGrdacl)?9mu{;p0hrRR^wHH%NB@oT%I4bmY&Apoc~xR(W2kc^@^}Qu z09VCS1QJ^KF7=EodbSL-NGN;yL(R{|Q!A3R7Lx##gc%ZK{#fr?PxE4W3)r=*P#h*_rd=^7nQE@AFjnkiCK##d$XM|;|D5Of z`DYzpe?~dY`#KiY`mnKV&TF3bGs?fyGLc-3_xTS+swA+WM)N$-vYnL)s}4iJqB!1X zPdK8M(3z{3muk|#4{!y;(ITQs&>h#yWyVLOjCTIK9#yffs@1s(zMTTRhn>NplVnb` z2s`(7nkNELe$4L92vpHpp5Gly|9t=zbXB=CEhNgL%TQj^(Dew2Xif#ZtAynk;+sIW z6tg6V)a+elAH|Nw)MrqBdU2ApR0<7a%nY2NfgK;LGe6h()*_(1XNlG$+6YWFjpFZ={>Z+qFCxI4n2{iJ zZs#*C1XY6y_{^@(fX|-U%aQSCePr!(&AAG`jXOq>0LalyuxlPmB4q*BaTTaFhb$+> zhnJ(xbG_DS>3vd z_jcWRetlJ~v;ZaNsO?10z*b1>3lXi7JB&LkfYJ}YIM}EHicpGTBH?Ha?bzL(sXn{p~dGeyb16y?nrWmHvw+IYLa&zE#CzA#MDlFw9OiqgT=^c$5IIo?HggiT9o<`W|8hhiv*XXr=FV z?Ci!}JDc%2L4tM#_({80I==1PYy9=CWe08r_^HX&xI>t&t|L=LyVc-09WVcS zay9N!;MIEAIJ|^at-#K)qOe1Pb_IAf+hey>S_FIxWGi0I!U@&(u5Hp|`WM zWJemn-eao4OB3J~lPxmeBALW9*ANoC1Oc9SQ^qD}%2wusNj&;*6}``2{N71`r(TJ6 zl8u(zNiZz|)dkrYkxc}6`rY@`?#|BLU1UP7Y6B5LqH2JUV2J=vCt750DbK1wXW5uC z?`5JZ4iNVG`r+qwyRUcy-tcx5}Xq3`QBqogmj1WkZfNwi3>%&sOpEAgu& zUPM`IB zb#wtT9yz*IeyWm>#Lyx@=OJtmh@o|=UPntdLrzvxlmuW(Qxw?r4zCgz zDPwj{FGZ9X_QCrjjr6`FjHO;@YG}&l)g`Ra(nQ37a#(E_o3OoJ9Epr~U76H`g5F^o2_ z^CJC_`9%GrD1Sddv~U)kMJj`9mc^Ag5K(8V9s2Cqy0WExBKac8djQtqj{a2HU`Vi9 zO(sa-3GHrW9ZCN2`ELS|#nZuN3CInbD#`B%oK&vI2yP(rk&i46&yJp#k+WYP1Yd%Lir>NOC}5>-IYk7pY}rek&`UqtdtQ{>dBXUf{JGs80wm8r4-v)Zg?;f#M z0=CGGf>A7$xQhH$b}H?#iruCE!iUKBBVJc%O(5&FQR1_BqompKNQU`6%s(&=X6Fe0??9xEnP_RtNxtXMvhe-@s<&60^Yi^^P!W{MS!l5 zio_8@hAgqs0!PSvRQ@x8o7vX(ndh!BapHmrAm&XV8%$OK5anzW8!79mB_IOP8;^)y j)c49}$b7LCiXq=$4@^al%e$Z*00000NkvXXu0mjfJJC8U literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_43.png b/assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_43.png new file mode 100644 index 0000000000000000000000000000000000000000..ff83fe77130ac6925552ed2bdb93eb3d44a187f2 GIT binary patch literal 1004 zcmVX^GWb)Ljemj`I? zv<-L+%cwe7Ti|m+aK4sK{IDXh0RtFXhf_1VwV~SMb92|C+FI{vR(ieH=;y8Hd7f0H zYSoUN3D}oZB+%Q(oY7nvE7b!e4>jkKR##+d04CW<3I)-VjzP2nxgd2G2*(u|o z^6(gVjX^a4X|46>gWaG8LVEkbPy+c&4x_YXO^@d-)Ou>rHyGu0ta&U;J0gdmR!y2ss@0gcsO5+*5Q0Ey*f_}KEB=vBQSXY7{$Zx z#1qA%3@HLn4?etZt<_`*oDsN|0MElQV?s&TwE`0)BxZ$KGAiolCd5x%#u-UqCg zBJct;4Sp$hT>$G15rMc2rm0^}C??P25`j_~^`Qe=n&s2*O3XI)k~fyMWB;aUjMB3m?Cg+*rhf4;m8S_wt&UWmI+n!d z>@)Z(Rw3vTf!fI}ZbuS?-pHZ!kksdBDZMKu55Vnb=fs~iI*6~sr8&=Hxfa#7cMXr| zHBtl`;EaEEEQX$c7$UI4WH3hpaa#HeED1kr9h@(n$I_lEmuMcb&7!E{(fu`$`$G_E zwrGl!`=pG^rFK+%>XWopkHCSC2HZzz-VY#k1XqR!o*EpOk7qZx-u8-4izr{@GzKuEN~=R6`LBY~T9DH1qRm}wEeyNNdYG2F9tMFMfY6t%PN70gK8JGlQagc4?W z_V(-*j5_mX#}o*X;`7|GG{dzxf6o5rND+vLWPDourIe0HU=oY;002cPOYVs>TQLXki#66Zzhw0rIM%KY}= z^LisDK@$Q<0JJC`uNaBYX#t-MJT>^dezZR|AqY8vTI5d4KBTNxX7qk<^T^AaA^La+w{He%cHTQEIx|mi{YF9Bd3YJ9J=3n} zIe^H__VPzG1`2=AN&<^e+rbWAUu4k$^@kw_-cmNxMuquRcCNLy_tyNQU=VMx)}B!vkqBe9K#znAHgq0iNh%%r*C_S;fJ$T`E}enPu}mSZNhffoHkmU=R+xSw(YKX`$kmC?qQQ%>N^ zDFG1BY`=e1!Xl0B>;laQaGCsm2 v_#a1l_U|pvGES0jCcyj1dX{hSTWjqv+PANDt%2s000000NkvXXu0mjf+v?(t literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_45.png b/assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_45.png new file mode 100644 index 0000000000000000000000000000000000000000..86630e83aa56102061ad25113ff5fd8240abb336 GIT binary patch literal 645 zcmV;00($+4P)epZT3a35*^uDG8M`O9i{!u=fj533n! zf!x4aWw3D&=$wBQAhC*}JF`PIr2;qy0i0880dOx@+r#YEi~tYP8|HbQS#d_BK!6XD z!b^emR)Jj5T8ds75a0upg0F>@ljGg!zaj$y+(a%29bl~j=0t6UfwP{{gBtQV?Aizad8~wec z^+y0>^qd}sKn1wOuA^SVLEr#8mwyQY7^J5SAb@iaz&Qxe$?m6r8U%0-0vM(BKo9|( zg8&^^>H8Aq1U9hpCwz>802NxMRqqZ06sXOkvm^upWUzLwuaROSfWkxgvbAv*AZQJL zS2^^yaXT0gAmOR>OAx?02;dw9a1H_}EM4Sjxg&@G&Orc0&pU1{fB+eKKZIdt5I~?q z_fQQG;Dro1_i0;JML+<7zEL8v+7=K%QIjBq{x@H)4gzFok-!SY`2dzS@+!LbTihp9 zfHzp45E{a(0?58*aU(~u*7qg9Ex=-Vn~7BkXaUaQt7Dzh{Kb*A=~7-Evp7ipw1C=1 f@|eBwLkQs;lIyo@bh0UDVVe7gP8Y#>Ph0Ss7Sp+~I*5TJq8R{7LOFiZg2NvWFliEWupR{>(j zax>|H%jnL}wMEmO)keRT*#0g8@S;U7EsYRCQy-rtrWKmg!w@=UfST#ao-2@Q+f!11 z-x(YPzVMd0Jcs}R9HQ9`K!C%60EYtslDrX#Co@MBx(ZED+6`#tXWX*M&>06d6NaHT2Y zkt0fKBfa%j=;82;*Znkbr*v5w{9WbXpRMb3@UES78r=lgY2=4+R_dpq5_mY(0(c!M zr=b=gRkV@HrjTTPR02rn>nR~xDrc?Yw7h<|W0xEy^%a!>(wp_@FkXS1T~hYU(g%T3 zk%mj5S@U0l=89VnZ1oKxZxux=W?%6;lBF}iXmaxvUV#9=q~<2*_4gh{fB*z($~vO? zB8HuS0F0ajA?Po@Tpb7?P$GdBAV2~xfu~VVNuCCJvb+Upm5?Wp0SfRIbjlsVr{&-i zZwG&ozg>W5%Rw6K0uBd19eX&%KOC7m0zTz2kAvjjE+F?I@tD8hhY-Ry@~C%}w?mO> P00000NkvXXu0mjfa3ydJ literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_47.png b/assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_47.png new file mode 100644 index 0000000000000000000000000000000000000000..f5b4529e448844213c2482c22324c4ab69bfe0d3 GIT binary patch literal 820 zcmV-41Izr0P)mMk+$!O+xenTRbMY5eGt>jQ zj!yxc*J~BXJ6+?Gshir-ra5%*b0BmW^4x=+O1b7i`Fz@@$iqj(n1eiz)UJ95t z3*-XLrO?U%0VYrid@WcxIi7ldiwqFJh+H6e0Bae52e0(mQoyMUH4z|y0lld{_Z`Wy zO46k=bPfnG7#jSMbpWkYyd5P8PzM-@Rq**bYCSsl`IG{5=ae!o8qY^-R$IIScudTp&uGCqx^bI1Uw@Dcp@TcqPy|J_a zoHwo@@-6`m0||WWqPhNlS0KPIG|0KTZFv;|0ucC#5{mZTJi7n^7&Qq(@OO>_0%VX# z-~|X!fJ@-1*Hemzfu1byfzNXCwq1|`1b7QN(f?rqcNs3)Mr ztbR%<sP*KPsHUcU8@&*!sKM`&P>_14ed=lLTlGl3qpe?9i|7yjeTorykQ{DL=! zMu57oyj*NK3A~*DB0$B9Lr>;}Xqpk=a?${o!=`{lm$&I*b!tunY-DsWODUvrW=xR= zFr*G%9k9_TKn*lD#i$v~46xd{Ggxck2EKJpN@Gv=8#AaHpeIJs5gK}0T2|?yz7G#z zD*|8*+_9W)XogmMR16>;*pVwjTMzEM89lS1p8AaByExO;`w$e%4bVCn2Nb*71lD@s z;IAwMjhYJU;M%gh4lpx-8WF%!kJjMhbBq`h9!qJ;LeL^C1|Oeqg$Yy*a6&B{Gn22a z@Aub%l^B6DK}LW{b`3r}zcd<9CQvni#~m;&-cCY2wDYYA)C)nq&ao~At<>Ueo0n7% zJkF#LB&m358^MznPb^6RT-j1GL8-@RUPUXkP*prnSN|wg|B?s*I|A;2rA~@Jhrh$3 z5Y*CvwVW0eMvb{F1@tOZT$ys`9H6H|YUEQXrRb~zT2~x()pP&Pf(KBl1HVska`=a+ z*8zHW*(@t(9&}hR5?M_PLDGBNx9ISIc_k2b^v+KAqGLB(wuE`<@V9mUNa;d70!VU^ z#Y(`74sWRJ2N;=A<5~L3L7-L{5*->^tdZ(Bn*(q&Z)?2h@Pf)5fUD~D>hM}Sg7A0u z&J(==1W}s=O3SDgrPf(3(ll}+s;dCW21ct6eWT^H`v7k+Sq6IQz!m%GJhf+*d^C(8 z+FBn2js1ERAT6h!)`>R^KHNt*9091jD1FU4BU`FY1h;K?OBlDgB3cpUCWcagw1{%z z;NKGZF)p7As5xra=WoL0DqwW^6?vJajY|VmdYd`9;}SWp4S=z_Z5$oVj;zjaL3TQPl-#dHoxiu zHJ_YwPQkVO*=<0um#6&j`FwWT;To7^t@-&$>90{S6Ub5gYthdS{Kc9&19={Nz?wom zKn*M_gDr}{%lVZ73RV<)GACry$N-lU0=S$Iz~xkHfIybf`e1c!76RBvZ!mMtq;O_L z5du)81g`|FHw#dK#-ivILkK{DTHss3qQvnw`bWf|E`Zf3Yz9eNs6gQBwK4#fWDj7aE!G3xvph^`aS;f+P9w&^ z?^w;OEwI|QE&?t&=k-&-O#j;|lz6-dL9_;V8*#M_5BMg{3OL{OX3I(RWU6g_0K9Xx z)6te}(PoPPYFn;59B;{%9jFW7ZNFX9@guf)D+IT;f-NnxEZL&T_Pqe;3jdcTkum-+70Bihqau`wWakOohKovz8GT3ah|t&-+;o7SHloD1RvHj(=`CMfpUUF zxxqi-UMTRrO=SS8D&`&w>@Xw%s>{SZW|f;#KV)bMsBN5Zjs>>Zz8BDfTC;0@{sv61 z0(#fsp4aPAm=-{Rsx3I@jb6)3*5K4NM+g8DBYPdDYk(ba-yQHm2!NpHjoZ@+0RV8& zZ^#BAfPdg8f(tMkcoaei0Dw12AYP}15CEbjf|Sl`Z*!C9Rbh#p1OpE|5Jds8(WFDc^?u}028kRB>r{m<*@&8WNZGrGb l$=4Lny2<#Pzu>2o(honG=APxIh>rjO002ovPDHLkV1md>o{Ing literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_5.png b/assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_5.png new file mode 100644 index 0000000000000000000000000000000000000000..1545c0ee718aba8d4aaf13e3185976c31cc550ce GIT binary patch literal 1126 zcmV-s1eyDZP)Q;kIbHxGa~ugX36(1>tHtO$VR}4Bk1s%=)zS zAiexrvP;s1>UwP&h4yS`^dUzXY4=fUtteBKtAKu>2o#-qP(ScCdiu(Y0EsxXj<=>q z&pjUDdiEOJQvm<2gB(M{_^RGlihqo^?6WD=Ic7D_69hlxo(3J_4Lbp`L>uc?O z;)V9$)16QCMy3SI2}}pD2$*yoA!bO=%u~=^olo_9qk-iFp(#LWjE~+ZF_Vm&8FzI) z#Rw-z5*Wl)D~TR>?LkZUB!L@(I*W@0@=6jZls>h(8cFn!Pb-%oJ&$IyX4`C|KLRvm zxJaO9uF>Q?SwL=}wxfq+qH`g#J!1Dvh%66jol*i3fM28UmYg<%BBRTr7m-ozQ`>{k zbUqD#22+wiA7&5#XR?IHaU=;`1!t#$Y0dAe5Sx!d)v^TGv3y|tcnLtSodKfCl z>!Rz#FNSUhBeExrY(%m=dc1W;j`-Q<{~ja>bl@otaE1sb30w$gh+ybQ7|OiS`>2f9 zm&!E~=aotLacUXJNtHypv@Kxdr4|9y1U#=nvFd;bWdbEq79n+B66whbM(b$P2hUxs26T?lwG5Pq zA8HKM(b|*lq3<5@7}1Nem@9UCDeu2m2%iVg$e6V%KyuP3wYFn>Y}4B8)ipb$jO$PN z?j&%v(Jw8*C7G==fva^shbJm`kwA2B)t9*{hLJ$&USe*zB7nt~wFr@z6YjkL&uLtG z-#XwiBc5cd1+)Uvur&!xqyv;ven-tpk-!62CU6%>nX=WfwF96O^KQ{#YX_j4fuY_?ZG$en<+hgReaAQ%rv-bmZyW=xE--38w$+Skz&&+WJJe|2R?`J)_G!zY><@ sEPaW0ysZMXkoT#wNSM|ReOV07*qoM6N<$f|)QBApigX literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_50.png b/assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_50.png new file mode 100644 index 0000000000000000000000000000000000000000..1ff2a8874df65ef828d5d06387d42abd57357acf GIT binary patch literal 880 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_55.png b/assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_55.png new file mode 100644 index 0000000000000000000000000000000000000000..b8a8c651257dd42798c7d1ab1e1a46adaed2f06b GIT binary patch literal 1080 zcmV-81jqY{P)PbXFRCt{2ozaryAP7axR{#GmXC8Lla%fOcKoU7mJxN-L+$ci2 z=DP3uF3GX{*=<0wmrwn0UDr-KUQIj6MjPi(DSwU1mB5ZVz7fay)BX`norygkcp#cW z-vIVtMS0jV2t1sx8K4qHp-<)svuVx%hm!(0oD{&}qyP>l1#mdzE+FxRyJOJaouvRa zfjNBS-21-2zPBm}cLB`YxE0>EjGQ-`1+eQ^1>quqQ8lcd_Yy*{Ka0v8)#i-DN9Uzu zX(|A&G4;H)5IF5)4$`m(-lL>{3Q7eW0gkUCs z6$e_(0ABWBP8hYTT{DmfA$I|aw1d3E@jZL$-*7f_Nd!HiDh83-EO9;-y;}ku-}arE zKn0}E-vuTD7Xj?PaVe6Z?DSj z0;wWUT>`M1tQ~QZLDmDMVGEXgiRN4hssex!?rieKVnA1I=|$4qHlks&1aJmOyP~xM z#}+6axI{>cIvCU#Sc}Ir2H+N3-~hM3q|tzCOJWQBPVdR)+RaG;TPKZNGl5i9Oxxks zwv48A*fzXg;#BT7a~3xNjLNK@BAB&h#sS!lg1hX%zb!25eAL3CAlG*}4TS3|6(YMPskaI&`OTdgRy-3{>4FIGwe2xY;b^wC90YP|=G3v>%x3i3_X?y>=WGBMt{BvtI!DJMv69+t z+rW-Lfhhq{`E$;(H^NB{waWnZS<=3y$fg7^+CTS>MZxL`<^+}X7>5 zf0P_r8GLG1klYJq+bhb!Tza{74DNM3ET&sd5ojd5$G;G=0yiZ`q0 zyMKyD2ms<4TeMHF){@}WCx@ed8z@0#UmTHlm7xp#=KDo83YQ@iVq@@|2lSoC%*|)VgKXE y-X8hHbMS$Y)D$A|YZuUqdh^`c&)Ms)A{sw5PWhdLujB^+00001HC3!4=P8*P% z-qA2`FMez$*$vjR=Y~_jQhw|-6~L+&j#8`om5hu*yf&1t5I{8g;jx8fPcj!hmLs-MQNw zLF=CMc9=u}uCdy+cnw>qbG5c>9xBaRyCh?@s3(DK+Y!88Vf7yI)X^-HLC;M=6t~OG%v#TVkUqaoexm5>j=&RTd1Fp#GPjW z8(@!!{cBBev`&JvaQYD7nnEXm)C;|Bn~|asl~@trG~SMm@R*08VP7_cEbtEh-dq&{ zPP2QQe1hF0<`e*dotx_Y{NK;LJAMj)z-|W)hE>IoyH%wC7EsEmyW_P;1tIM!N&z5v zB!JQyhSa_kz=F0%z$3x)P9moQIGxQ#!&3yFWF24w5WzQ99$)oW(%*DZmSCraH}X#a zPesF#H;duXN#N*Ao#Q~PQz*J1@=wQhNbf^02I>*E1W5!U|KUh%b=>;dtdV8(Y|fbv wLvz7N#Pr`&0M^O#?Vn-)Mlzoz&>1A*FH6j9lh-Os$N&HU07*qoM6N<$f`lZ}Gynhq literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_57.png b/assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_57.png new file mode 100644 index 0000000000000000000000000000000000000000..f7c1e8023e5719b43bfed8b759b62ce5ccbf6eeb GIT binary patch literal 1132 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 Te-l@l00000NkvXXu0mjfz#eMP literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_6.png b/assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_6.png new file mode 100644 index 0000000000000000000000000000000000000000..f99662e70f0d928b252670eb51bed5fce26b3b38 GIT binary patch literal 1077 zcmV-51j_q~P)BRU~w0>|jmP0t812KyY*gfSa-Qoj++lO+)1@Wq7*Qj-@`z zPZ%u#4`j4GEh>Y!11e9^@}zyTRF~F6_4!y=+=ssnlK}KyoRr5G9xV<{=i1%sYMRa4 z(Z>2#Fs5g{_}`PbYONJTs&W@Fhne^2v4XP@w}FWH(w0B}i0mO?Vu zh%eCklI7Xa3TX?k%>xNX&jN5)G4r0f0-zByG(m}YY2Q&lyDYv%<24H)k!edmEqz8| zTrNP;bYu;rr_|<->>OFg(KZ?^ZAn7c+if8v2^<7+S$D!KRHJK`k-!0bd+}Qz9*x$v z%^J*+KxFb!K6+|(Ezv(I9Yq19zC2@>#zxCL<+i>p1YH5p{I|87B??%|&k0EBG!La| z(8g!kdq2JSwBPHx+;g;y(RCJJWw)AEOE1qu1}**a_IgigtIFGci?DiJf=39F1RAl@ zZpAtd%X%gWG+?F0FU8IaEagltz(br+eXXvA^xfG{R7mq5wg4yuj86VE(nX1lcI2x` zvgVt;BvERACGGd3X9aFO4n|~8(riSsEd5yP?7Co9c(#YM4o8we1#dI|9z5fcENPO! z4pv?KyWqMKh9Yk?9p&-*QocswtYxC%4maSk0O;5k<)for>SFkydjwD`ce;k&1xZ+j zBY|k~EfGHyokIh)MEj&R5_TjB6tK(oZ38JC?T(>1fJVl8y8={B($d~>?9AA-HX}w6DUt*dASqk9V){9TPXiNYBLyHh zQUC&B=q%t=th`I{V&-6L_%JAO2EtkZ8Ul_4uFzoxpp;^Yl${ROS-=TEmrEjBgU}X$ z3qce?pb8LD0D4#3BDn?eFen21=XU{6-s$flqy?bR?l+vi_@(@{0Mz6~F?|%=_eWUx zhJP*~(xef%5}f`%+I)|=6ndBWjzs<%j`-KHXt8&m&yz6!<4DaczLBSmwdF}aQ}6W` vNqBzj0yGo2N}lHyuN3jq%0^oJT5I_YO3H`L3Qxit00000NkvXXu0mjfA%pE! literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_60.png b/assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_60.png new file mode 100644 index 0000000000000000000000000000000000000000..950152b73796097626af76c287ef2e7fa8e703d6 GIT binary patch literal 1269 zcmV6M#0t&CP4-AjF=b`o(k zS%B)(R?vHzhstnn!P}zX&#iPK4pRXl0SxcTmfDv3LUm^hs;r2!lu}TnDp%Ka(7Ckc z*Q?CE3ylvz%TIhk zIt^?QxRoE>!d(`Crx6=(u?{=I``!@+krX1L2uT750IhgUm=U3Sfcli=nIy0?_yY0> zzPB|*V~`fMdke5kI3j!KIc@iCR))`&guAvuNfGG0yCQ)!ipLv`MDcit_)Uli|L!n0 z7d-7d6bgMWVse)(B)IFbEIp_bwh!2zYeR$VCFBtF$#Ba%a_Bi5yz1niFIc6r%wlPg|ob zvgMkM1?H*&sjTBd)bH4dK!@?&0J zCPEr%o3w2yU#o}e^Ra5}Yu*9R)80k$RC)@rCx>x=4`7iR>A4gFFXqJnKzhGC?2XA&6kQZ#6Bep&_*LGL_|Ab`h_k-#pH_yRA(-RFAmX}_Yk3wtRN z*pry?I-?nOs}JsJ*Rw@v$|8+(1l+$4)}HgI9U@*@8ufF>7KGl-W6nL0Vgr%)LSyT# z5TQ7LWV-m?rd_2rM{BwlBg`#dD3+&VEx4Z8mIQhguU9Z8BEVLz(1MHv8bHkbN;`M< z=|MOW_$Ea5WG`v!iQWMugl*lx)?myXLg9Um#I_1h(IU`Z)?fyn75XapZJ=iW(ft;Y zF8!B>b1FymmaLOL#%f?6~k z#VYf+JqeWX1mk}KXZcpuH@~R1ziVM?TDRW64L@7J8(%I0?frXg3o~N-Eg%{Vd?F$^ z60iQ(vC)<4nS+Siw*uV%I8yVGXU*dRZT+^9*F#pL<5z8r0GsbC~YBR-Bbk>A%8}k zUbWU*l4JSz>kcHpKd_pF zMgS?+D$15Y;NiSjfW|5kx-&ai)3gAGlLBx!CIO(PtZma@ZtAo;T79%FT^pSz@gv3n z8ts|3LAPlcs>7d!`MD^(F0Z$9oH8oBv+ubp05pNoyExM9)&{lb=a#O8+FJXxGTy#R zR_~oLxN5BxWvX@_*j1ooi7c7d3ZOaR=kU(kkPAqBaHX;+84`}3_Hzqi5eBmh(6;G* zuYS)hM8Tr*ngu}a(t4eC+#+aQ0nmJ{Cq6g{N#jS((XgCL5;|>ux{rYtsYQ`JZ-cZy zBHZuKx=%Wh7G>iTFZCmXz==pf-zV@~Lz~yk1Ww|cli#jqRZ`6om|OtQ?9t=BY#l!? z?eEODC!b!|;}Vb%m@ELw;-PFGYD4#tAnnetCZAs4Y7LMOxLkne2DPjps<*RntgzH; zR0uK%d;>maD`4$UtCvCG;YkFvYKB8X~7&lj>;_Uq6CM#(G>t~ zX_hq$XlZ9TZ!#Zq5ZKG&p@0yTY4vtsb^q@rgFq*{2!OSZ83YE!8VI^Ffo&iC@@;gi z7fAN%X!X&y7HqDr!Qa9a1U4k;vX~t$AoOMqH7Q50qqTht5rxKN0kn1PS~k$*FRhv_ z^-H=IYU}mk)M7-7jWP&S(8gj1YhV%d{4hj7dqj<-9K*}PQ4rYnwZP9>hacC@V`-1d zC8#yrLDRj^b%gG>fzpQ{J##toN|~2y?NEF4O4=HvMx6Lkp1)Uctq0HsL@MJGk0ys6 z=h`iq;L)J--EwpvJHhX*)_fiOL^SP}EFvW{fe)fhK1aH@w=#h<12f(x>O`!@P$rNc z*RpojwSqG;b%`JMhao61`UqF2&4s2 zpq3z#)>H0M)1rn{!)kwh<0N$iRSky=>m*IRgnDa*uydZair$|PAQA1Bu_3t hyU8qzH~FQM@(1XRBm-|Kx+MSr002ovPDHLkV1hx8*|-1z literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_7.png b/assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_7.png new file mode 100644 index 0000000000000000000000000000000000000000..cf09a7842a0a864332ebd0f10693e05fe46a883e GIT binary patch literal 988 zcmV<210(#2P)DC>(*OwvWB(yk zd-dLXE7J1ybOnlMImW^3^?I@%p@B|zwSGQY`>9k#g6kOlyJ9~d@XKn}SzOP7PpoF4 z5uhg4DwC~5U~>L>fWayjx-&a?)A9hxDFa9j8v+Kyjg63iA7|hr4$M!b;quhu5k8K< zXbqU(@FNYp)TdVCz~Pijv*Pjq1&?LMOM{FDkIT!2$=>_al%m`sXYy<1qxBAJ$95>;b5YTHF-;E(t!57_{6MUr+nw9#t*%xKhO8fwkoK z7(t1^N$wbYjOSj~v8!`0)PLkYF)_ zX%9dx0aMSYlIVig%r!_8q0)eOMWU4z7Ou`R0+Rt~4W?!sRK?Wn#MiV4FX~#7s5~2w zP`!g0Bfyjhbg-HMv}X54+}M@~G_vC0d*Hl)$pARf2GhATT%JppM4UBGMm%c-u*|^a z0jSr$R66x)7tSDDVNC!^`RJs7i93&Uu%s+1dR|ln6Ai zL;Rl=gCzo!fy01nC6E(k9#Ll%k(O0KZ<(jU$I6&q2g747L}+47O4CH4RXlp`>s>^N zYAh}hh|gg@*@#Qo!eoiSc%jYj?+XS zcWy_ZAxCKwhsgurcmCR1KR(`tG4NxbLiE9J&G^$We9UdMxM~B&9biL;?A8_!^}~?1 zm(rhsGVIaj^iKg91|NntOlkx8birwLxNUck)CN!_Hh?UyALD)-Sj46bAUS0K330s( zh)$$Mo|zrO=?x$|=?ws`{b!=~0|2Czvgo8W0BWCMMsJYT03g^1vKT_|J+}d9yPPwG zh13Snn!d{^x)G!|fYxkA5EXVqNaRLy;Mkv`56S}|tiN!I!qM7$oXgPY;*Z}Dkip$~ z$uJoz1SF^ab!>9X|2R_GJ{2`B!=!i%0n(yJwzOUM3sS$*lK2KMvcx%HG}t5n0000< KMNUMnLSTaZZ_|kY literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_8.png b/assets/dolphin/external/L2_Coding_in_the_shell_128x64/frame_8.png new file mode 100644 index 0000000000000000000000000000000000000000..d12fa6abaeb935d184c0a5f82713bebc827f1ef0 GIT binary patch literal 1201 zcmV;i1Wx;jP)0c@!;u7dIFbMlM-t%SNCGUtbzSAUF7-2NgX|eQ)DMFsz#_a} zFZDCJFN3Gqcy`FfLK0vDYLME1UDx#!u=?3*XU%erKrCM6kV26606pwZ__&P#){>WG z*oUoKKou}rfZKasfKea4ejt&h-9vs@;taJ%{d?mxezVv@fT(ko1Pl>+Ap7dezh5E2{L4>^~3sPi9ab6k!=LaG{DFbEx|rq zYJ)hI5MXBTDa_e?5(-{4O?^o zi5c0Z=K)S!2~Y(vA~@xJ?|9j|Zef@JUeMUUMypv-1$-w-+Gn$}1?#<(_Y$$yCyQYb z7@gH#36`mmd*fREBz|Md7Eod3yB?8B$z$Rb7!rYz+0W$M@^ib)u`}MQvT66wGikOF zF{X(;G(IC1EnDYiW}M|duj>j_!o&j?YcLH?AKe_Mzp}TG*Hgw z_Qf2{%6m60{SpPg<&^?qAdgC&_S{M?6>=wZOfWd3u+A!iW;iFpwjuu#E zq%ax~F=Q&0_>WtE7Jf=FECD7zYu7wmOE|6=CxWl}zX0?FFkg&SWs?~SvbR<CrK?Ynni`d_gu^prt6bW;+_E2i+o&nN=%9;3!Vnc22>vN?^-m zln${uaugY49?MpMp8<#*pII%Kej~p1kfbFt$}X5TT06Ond-aT2+F;oVU2)Q_iP>v6Ee?%<0D2(Ke%!=h>i&9$VGZY^>`nZe0OZ!V{XT zkn)ankplPr*7`>6wXCUL9~FcjO@N+M&`QPHHl9ec#+CL`@oPS!F(WBo&$f1}2xNgC zs!R3ezW)oytpF=>ey^`5lj1l6oRAit0=CL1w}=QHmjy_iKKs36<53a#SOU~4v6Ci`U9^outr}&$F_Mdo1 zhyZs5BZ;sMGyzTszzIHl8>l*fc4~DuyfcH;4;+pbffi06;WxnD0zUPN{#3tr!z4g{ ziQkXV7Qp_5sp4(|c<-TwBS}G&eN9sv-5F`PDa9tOB zEj_2s|G?;nTNYpt9%%Q*s}xXLDpAZq|U>`vHti~w55OERp( z(j}k@7|y}%Gt0q}joyApB8z(_^+OY9QhCX~)jra16iW!;?OY`RLWCYryVm#iz7)AQ z(Wi+tZQZ8Y0O^O;Zw;XY7@4V&(Ll1x%~(y4Np(3pXrDCkM}?wP*Ml$(FcP9E(7~lP z@FRo(Go4@IVR#Y&$Mhv2!ui!Mq6@rzK!jWUlmw!c)uZ<{J5m`>Up<&gfGFp;_7Pox z_NBE|BMi5;*5~afg`_F0dTra1O-^`neeJblRmeOm$myEh6!Lf4Rx$Ho8?u&Pm-h^o3>lfK1=yb5oCw_y&Q@m7|NdnS#EtLqVC zoXC^fN5rC5R~nCnCA)wgf#-F+mT{BT$(~$-bo?ucKJLEN2DNKNwh=9`{GC8_3#jP; zZJ?Z(q)UkQDP{7|{#kvIo=ICmt6ik88iqt5x&(0IO%sEN5Jbdf!bZ0U^s zh@lbhWIXm-_)d<{1Q@;M?pbv;;TX}52Ve7l0n!hE{4iRUjqXsS`dUQeb~TddqwUb8 zbav!?9RYfo?rnp+#|cEOFH#-O(bbkD4v}_x&@BRyS*3|OE)R}~z)|e9^z4AJOknF& z&-Q8t(#bf&BEU}rM()3s@y>oL7cf#^vrE@uy1PKiaOG$ixAGaYv_V(|(1epFmeBH} z5yZPKM6!v95Tx}q=TrM!8>7)KLi#qY)qyme53n zly#&lDRApMXIm;Se(TtHR0JMNfSP685z;n~ zmYH#a`U(*^3%fe}No=P9W+R=h%lZ${4q*;)i@KuBnn$+v<44Y0#( zkHqQvLM6aSKg>6bHT;br^cALxxBaLmE(sz5yw1~Jw0MFD+PnFQ|6rGZSwC9)PakiY cS*H=^9}&0!q9c2@qyPW_07*qoM6N<$g2|^eT>t<8 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Coding_in_the_shell_128x64/meta.txt b/assets/dolphin/external/L2_Coding_in_the_shell_128x64/meta.txt new file mode 100644 index 00000000000..0378846c4d4 --- /dev/null +++ b/assets/dolphin/external/L2_Coding_in_the_shell_128x64/meta.txt @@ -0,0 +1,23 @@ +Filetype: Flipper Animation +Version: 1 + +Width: 128 +Height: 64 +Passive frames: 21 +Active frames: 44 +Frames order: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 17 19 20 21 22 23 24 24 25 26 27 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 +Active cycles: 1 +Frame rate: 2 +Duration: 3600 +Active cooldown: 7 + +Bubble slots: 1 + +Slot: 0 +X: 7 +Y: 46 +Text: GOOD JOB! +AlignH: Center +AlignV: Top +StartFrame: 54 +EndFrame: 57 \ No newline at end of file diff --git a/assets/dolphin/external/manifest.txt b/assets/dolphin/external/manifest.txt index 4d4445030a0..1d3f3510689 100644 --- a/assets/dolphin/external/manifest.txt +++ b/assets/dolphin/external/manifest.txt @@ -168,3 +168,10 @@ Max butthurt: 13 Min level: 1 Max level: 3 Weight: 4 + +Name: L2_Coding_in_the_shell_128x64 +Min butthurt: 0 +Max butthurt: 12 +Min level: 2 +Max level: 3 +Weight: 4 From c924693a84abe88a6c53e1e3b062f0a9ab1c5886 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Tue, 26 Sep 2023 13:09:17 +0900 Subject: [PATCH 770/824] Ble: fix null-ptr dereference in bt_change_profile (#3110) --- applications/services/bt/bt_service/bt.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/applications/services/bt/bt_service/bt.c b/applications/services/bt/bt_service/bt.c index 95a9bd30428..36409fe5cd6 100644 --- a/applications/services/bt/bt_service/bt.c +++ b/applications/services/bt/bt_service/bt.c @@ -359,13 +359,13 @@ static void bt_change_profile(Bt* bt, BtMessage* message) { *message->result = false; } } - api_lock_unlock(message->lock); + if(message->lock) api_lock_unlock(message->lock); } static void bt_close_connection(Bt* bt, BtMessage* message) { bt_close_rpc_connection(bt); furi_hal_bt_stop_advertising(); - api_lock_unlock(message->lock); + if(message->lock) api_lock_unlock(message->lock); } int32_t bt_srv(void* p) { From 9898a5d0dd1c239e0b3ca428fbba7e17a381df09 Mon Sep 17 00:00:00 2001 From: Max Andreev Date: Mon, 2 Oct 2023 12:51:41 +0300 Subject: [PATCH 771/824] Enable PVS Studio license check (#3122) --- scripts/fbt_tools/pvsstudio.py | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/fbt_tools/pvsstudio.py b/scripts/fbt_tools/pvsstudio.py index b2592eca606..211f46aee8e 100644 --- a/scripts/fbt_tools/pvsstudio.py +++ b/scripts/fbt_tools/pvsstudio.py @@ -48,7 +48,6 @@ def generate(env): "@.pvsoptions", "-j${PVSNCORES}", # "--incremental", # kinda broken on PVS side - "--disableLicenseExpirationCheck", ], PVSCONVOPTIONS=[ "-a", From 699078d5a5840065f3741cf3b053e583aaa6ca02 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Fri, 6 Oct 2023 09:15:26 +0300 Subject: [PATCH 772/824] [FL-3576] HEX input UI improvements (#3112) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- .../services/gui/modules/byte_input.c | 24 ++++++++++++++---- assets/icons/Common/Hashmark_7x7.png | Bin 0 -> 957 bytes assets/icons/Common/arrow_nano_down.png | Bin 0 -> 2311 bytes assets/icons/Common/arrow_nano_up.png | Bin 0 -> 949 bytes 4 files changed, 19 insertions(+), 5 deletions(-) create mode 100644 assets/icons/Common/Hashmark_7x7.png create mode 100644 assets/icons/Common/arrow_nano_down.png create mode 100644 assets/icons/Common/arrow_nano_up.png diff --git a/applications/services/gui/modules/byte_input.c b/applications/services/gui/modules/byte_input.c index 3bca8fc744d..e0cbbb779d0 100644 --- a/applications/services/gui/modules/byte_input.c +++ b/applications/services/gui/modules/byte_input.c @@ -177,6 +177,12 @@ static void byte_input_draw_input(Canvas* canvas, ByteInputModel* model) { if(i == model->selected_byte) { canvas_draw_frame(canvas, text_x + byte_position * 14, text_y - 9, 15, 11); + if(model->selected_row == -2) { + canvas_draw_icon( + canvas, text_x + 6 + byte_position * 14, text_y - 14, &I_arrow_nano_up); + canvas_draw_icon( + canvas, text_x + 6 + byte_position * 14, text_y + 5, &I_arrow_nano_down); + } if(model->selected_high_nibble) { canvas_draw_glyph( @@ -233,6 +239,7 @@ static void byte_input_draw_input(Canvas* canvas, ByteInputModel* model) { } if(draw_index_line) { + canvas_draw_icon(canvas, 1, text_y + 8, &I_Hashmark_7x7); canvas_draw_glyph( canvas, text_x + 2 + byte_position * 14, text_y2, num_to_char[(i + 1) / 10]); @@ -600,9 +607,6 @@ static void byte_input_view_draw_callback(Canvas* canvas, void* _model) { canvas_clear(canvas); canvas_set_color(canvas, ColorBlack); - - canvas_draw_str(canvas, 2, 9, model->header); - canvas_set_font(canvas, FontKeyboard); if(model->selected_row == -1) { @@ -613,9 +617,19 @@ static void byte_input_view_draw_callback(Canvas* canvas, void* _model) { if(model->selected_row == -2) { canvas_set_font(canvas, FontSecondary); - canvas_draw_icon(canvas, 3, 52, &I_Pin_back_arrow_10x8); - canvas_draw_str_aligned(canvas, 16, 60, AlignLeft, AlignBottom, "back to keyboard"); + canvas_draw_icon(canvas, 3, 1, &I_Pin_back_arrow_10x8); + canvas_draw_str_aligned(canvas, 16, 9, AlignLeft, AlignBottom, "back to keyboard"); + elements_button_center(canvas, "Save"); } else { + // Draw the header + canvas_set_font(canvas, FontSecondary); + if(model->selected_row == -1) { + canvas_draw_str(canvas, 10, 9, "Move up for alternate input"); + canvas_draw_icon(canvas, 3, 4, &I_SmallArrowUp_3x5); + } else { + canvas_draw_str(canvas, 2, 9, model->header); + } + canvas_set_font(canvas, FontKeyboard); // Draw keyboard for(uint8_t row = 0; row < keyboard_row_count; row++) { const uint8_t column_count = byte_input_get_row_size(row); diff --git a/assets/icons/Common/Hashmark_7x7.png b/assets/icons/Common/Hashmark_7x7.png new file mode 100644 index 0000000000000000000000000000000000000000..93fb147be235c2030fb85ba55859cfc66d32b1fa GIT binary patch literal 957 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 literal 0 HcmV?d00001 diff --git a/assets/icons/Common/arrow_nano_up.png b/assets/icons/Common/arrow_nano_up.png new file mode 100644 index 0000000000000000000000000000000000000000..4a1d5be85cf83a3a1767d3b422054285f978c9eb GIT binary patch literal 949 zcmaJ=J#W)M7`9pos!AP@SYSH2OOS}qpGj=RR3VqPk?JT)Es-%7`;u6xea5~JClg|W zAHd4Q(gBH?ftjCBBz^;b04bdFRfYsh_Ir1JpXYt<-sj%8TNfwCPmcvbIB8y@4v&ZY zJvhq0x3?b)JUn5IF1w}!HpK}MYCi1~&3AHzo}9$uNj)@%OjyA_#n2d}TPY6W7ToxSS{Cdfsxq`Pegk@$3btW)>$1 z3Bw=@Z=%%7Y;nnJ@o~NkMX*d^V_RH_>N;&urwIY3q!c|_RzTU5aK+clhNhd(fhwyS zlr^YoMMbk@!_p0~eneg{@ds81ov+LBnJtbOi!BJJ)2TGoB$^DNVwxtDRj8^(o>5F^ z5yM$AN>4Woh@@T;#4Ml@$Qf~;PM9rnv8!E#@s>79*C)l#3}!fniX`W|+9W#8|5L+o zo1L-_x!1dsICW<+fgO_4N#gO|4Nmh?u~kn9W;Agrz1bXOdqf#cM>GbiE|oyR!CnyM zE6PsIp-wrJABN8Wp6-sAAM~T{ktPsT(Y!-XtUu z+hF~BY-ukxH$)io%7`Sv8{(fQGz80CTfq)3nyQ Date: Fri, 6 Oct 2023 10:11:02 +0300 Subject: [PATCH 773/824] fbt: glob improvements (#3117) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fbt: glob improvements, now treats entries with no special glob chars as files by default, not calling scons' globbing for them * fbt: further fixes for glob * fbt: less strict existence checks * fbt: fixed frame_rate collection; typo fixes & comments Co-authored-by: あく --- scripts/fbt_tools/fbt_assets.py | 2 +- scripts/fbt_tools/fbt_extapps.py | 8 +++++++- scripts/fbt_tools/sconsrecursiveglob.py | 23 +++++++++++++++-------- 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/scripts/fbt_tools/fbt_assets.py b/scripts/fbt_tools/fbt_assets.py index 4f4d3bffdcd..d923c328f21 100644 --- a/scripts/fbt_tools/fbt_assets.py +++ b/scripts/fbt_tools/fbt_assets.py @@ -9,7 +9,7 @@ def icons_emitter(target, source, env): icons_src = env.GlobRecursive("*.png", env["ICON_SRC_DIR"]) - icons_src += env.GlobRecursive("frame_rate", env["ICON_SRC_DIR"]) + icons_src += env.GlobRecursive("**/frame_rate", env["ICON_SRC_DIR"]) target = [ target[0].File(env.subst("${ICON_FILE_NAME}.c")), diff --git a/scripts/fbt_tools/fbt_extapps.py b/scripts/fbt_tools/fbt_extapps.py index aa6354c9e3a..bc8f9d5df74 100644 --- a/scripts/fbt_tools/fbt_extapps.py +++ b/scripts/fbt_tools/fbt_extapps.py @@ -157,6 +157,11 @@ def _build_app(self): for source_type in self.app.sources ) ) + if not app_sources: + raise UserError(f"No source files found for {self.app.appid}") + + ## Uncomment for debug + # print(f"App sources for {self.app.appid}: {list(f.path for f in app_sources)}") app_artifacts = FlipperExternalAppInfo(self.app) app_artifacts.debug = self.app_env.Program( @@ -239,9 +244,10 @@ def _configure_deps_and_aliases(self, app_artifacts: FlipperExternalAppInfo): # Add dependencies on file assets for assets_dir in self.app._assets_dirs: + glob_res = self.app_env.GlobRecursive("*", assets_dir) self.app_env.Depends( app_artifacts.compact, - (assets_dir, self.app_env.GlobRecursive("*", assets_dir)), + (*glob_res, assets_dir), ) # Always run the validator for the app's binary when building the app diff --git a/scripts/fbt_tools/sconsrecursiveglob.py b/scripts/fbt_tools/sconsrecursiveglob.py index 7dbde531b30..38aa9a839fe 100644 --- a/scripts/fbt_tools/sconsrecursiveglob.py +++ b/scripts/fbt_tools/sconsrecursiveglob.py @@ -1,6 +1,7 @@ import SCons from fbt.util import GLOB_FILE_EXCLUSION from SCons.Script import Flatten +from SCons.Node.FS import has_glob_magic def GlobRecursive(env, pattern, node=".", exclude=[]): @@ -9,14 +10,20 @@ def GlobRecursive(env, pattern, node=".", exclude=[]): results = [] if isinstance(node, str): node = env.Dir(node) - for f in node.glob("*", source=True, exclude=exclude): - if isinstance(f, SCons.Node.FS.Dir): - results += env.GlobRecursive(pattern, f, exclude) - results += node.glob( - pattern, - source=True, - exclude=exclude, - ) + # Only initiate actual recursion if special symbols can be found in 'pattern' + if has_glob_magic(pattern): + for f in node.glob("*", source=True, exclude=exclude): + if isinstance(f, SCons.Node.FS.Dir): + results += env.GlobRecursive(pattern, f, exclude) + results += node.glob( + pattern, + source=True, + exclude=exclude, + ) + # Otherwise, just check if that's an existing file path + # NB: still creates "virtual" nodes as part of existence check + elif (file_node := node.File(pattern)).exists() or file_node.rexists(): + results.append(file_node) # print(f"Glob result for {pattern} from {node}: {results}") return results From 62a4c0dd039fd32955e9d2b4d002f1ddf4c3dc75 Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Fri, 6 Oct 2023 11:20:32 +0400 Subject: [PATCH 774/824] [Documentation]: add documentation SubGhz Bin_RAW file format (#3133) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Documentation]: add documentation SubGhz Bin_RAW file format * [Documentation]: fix error Co-authored-by: あく --- .../file_formats/SubGhzFileFormats.md | 55 ++++++++++++++++--- 1 file changed, 46 insertions(+), 9 deletions(-) diff --git a/documentation/file_formats/SubGhzFileFormats.md b/documentation/file_formats/SubGhzFileFormats.md index 26863f56424..c22f97f8df1 100644 --- a/documentation/file_formats/SubGhzFileFormats.md +++ b/documentation/file_formats/SubGhzFileFormats.md @@ -6,9 +6,9 @@ Flipper uses `.sub` files to store SubGhz transmissions. These are text files in A `.sub` files consist of 3 parts: -- **header**: contains file type, version, and frequency -- **preset information**: preset type and, in case of a custom preset, transceiver configuration data -- **protocol and its data**: contains protocol name and its specific data, such as key, bit length, etc., or RAW data +- **header**, contains file type, version, and frequency +- **preset information**, preset type and, in case of a custom preset, transceiver configuration data +- **protocol and its data**, contains protocol name and its specific data, such as key, bit length, etc., or RAW data Flipper's SubGhz subsystem uses presets to configure the radio transceiver. Presets are used to configure modulation, bandwidth, filters, etc. There are several presets available in stock firmware, and there is a way to create custom presets. See [SubGhz Presets](#adding-a-custom-preset) for more details. @@ -45,10 +45,10 @@ Built-in presets: Transceiver configuration data is a string of bytes, encoded in hex format, separated by spaces. For CC1101 data structure is: `XX YY XX YY .. 00 00 ZZ ZZ ZZ ZZ ZZ ZZ ZZ ZZ`, where: -- XX holds register address, -- YY contains register value, -- 00 00: marks register block end, -- `ZZ ZZ ZZ ZZ ZZ ZZ ZZ ZZ`: 8 byte PA table (Power amplifier ramp table). +- **XX**, holds register address, +- **YY**, contains register value, +- **00 00**, marks register block end, +- **ZZ ZZ ZZ ZZ ZZ ZZ ZZ ZZ**, 8 byte PA table (Power amplifier ramp table). You can find more details in the [CC1101 datasheet](https://www.ti.com/lit/ds/symlink/cc1101.pdf) and `furi_hal_subghz` code. @@ -87,8 +87,8 @@ RAW `.sub` files contain raw signal data that is not processed through protocol- For RAW files, 2 fields are required: -- `Protocol`, must be `RAW` -- `RAW_Data`, contains an array of timings, specified in microseconds Values must be non-zero, start with a positive number, and interleaved (change sign with each value). Up to 512 values per line. Can be specified multiple times to store multiple lines of data. +- **Protocol**, must be `RAW` +- **RAW_Data**, contains an array of timings, specified in microseconds Values must be non-zero, start with a positive number, and interleaved (change sign with each value). Up to 512 values per line. Can be specified multiple times to store multiple lines of data. Example of RAW data: @@ -97,6 +97,43 @@ Example of RAW data: Long payload not fitting into internal memory buffer and consisting of short duration timings (< 10us) may not be read fast enough from the SD card. That might cause the signal transmission to stop before reaching the end of the payload. Ensure that your SD Card has good performance before transmitting long or complex RAW payloads. +### BIN_RAW Files + +BinRAW `.sub` files and `RAW` files both contain data that has not been decoded by any protocol. However, unlike `RAW`, `BinRAW` files only record a useful repeating sequence of durations with a restored byte transfer rate and without broadcast noise. These files can emulate nearly all static protocols, whether Flipper knows them or not. + +- Usually, you have to receive the signal a little longer so that Flipper accumulates sufficient data for correct analysis. + +For `BinRAW` files, the following parameters are required and must be aligned to the left: + +- **Protocol**, must be `BinRAW`. +- **Bit**, is the length of the payload of the entire file, in bits (max 4096). +- **TE**, is the quantization interval, in us. +- **Bit_RAW**, is the length of the payload in the next Data_RAW parameter, in bits. +- **Data_RAW**, is an encoded sequence of durations, where each bit in the sequence encodes one TE interval: 1 - high level (there is a carrier), 0 - low (no carrier). + For example, TE=100, Bit_RAW=8, Data_RAW=0x37 => 0b00110111, that is, `-200 200 -100 300` will be transmitted. + When sending uploads, `Bit_RAW` and `Data_RAW` form a repeating block. Several such blocks are necessary if you want to send different sequences sequentially. However, usually, there will be only one block. + +Example data from a `BinRAW` file: + +``` +... +Protocol: BinRAW +Bit: 1572 +TE: 597 +Bit_RAW: 260 +Data_RAW: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0F 4A B5 55 4C B3 52 AC D5 2D 53 52 AD 4A D5 35 00 +Bit_RAW: 263 +Data_RAW: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 00 04 D5 32 D2 AB 2B 33 32 CB 2C CC B3 52 D3 00 +Bit_RAW: 259 +Data_RAW: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03 4A AB 55 34 D5 2D 4C CD 33 4A CD 55 4C D2 B3 00 +Bit_RAW: 263 +Data_RAW: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0F 7F 4A AA D5 2A CC B2 B4 CB 34 CC AA AB 4D 53 53 00 +Bit_RAW: 264 +Data_RAW: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 FC 00 00 15 2C CB 34 D3 35 35 4D 4B 32 B2 D3 33 00 +Bit_RAW: 263 +Data_RAW: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 DE 02 D3 54 D5 4C D2 CC AD 4B 2C B2 B5 54 CC AB 00 +``` + ## File examples ### Key file, standard preset From 4ae9a02efaeae2be8d23f6c36670cbe8e39decce Mon Sep 17 00:00:00 2001 From: hedger Date: Mon, 9 Oct 2023 15:53:20 +0300 Subject: [PATCH 775/824] FuriHal ble: length fix for fw version prop (#3136) * hal ble: length fix for fw version prop * hal ble: dev_info char: setting data after setup is done * api: storage: enabled storage_file_sync --- firmware/targets/f18/api_symbols.csv | 4 ++-- firmware/targets/f7/api_symbols.csv | 4 ++-- firmware/targets/f7/ble_glue/services/dev_info_service.c | 6 ++++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index 338697ad701..a3eb2743d8c 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,39.1,, +Version,+,39.2,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -2007,7 +2007,7 @@ Function,+,storage_file_open,_Bool,"File*, const char*, FS_AccessMode, FS_OpenMo Function,+,storage_file_read,uint16_t,"File*, void*, uint16_t" Function,+,storage_file_seek,_Bool,"File*, uint32_t, _Bool" Function,+,storage_file_size,uint64_t,File* -Function,-,storage_file_sync,_Bool,File* +Function,+,storage_file_sync,_Bool,File* Function,+,storage_file_tell,uint64_t,File* Function,+,storage_file_truncate,_Bool,File* Function,+,storage_file_write,uint16_t,"File*, const void*, uint16_t" diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index f3f42d0f099..eb997e64b17 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,39.1,, +Version,+,39.2,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -2617,7 +2617,7 @@ Function,+,storage_file_open,_Bool,"File*, const char*, FS_AccessMode, FS_OpenMo Function,+,storage_file_read,uint16_t,"File*, void*, uint16_t" Function,+,storage_file_seek,_Bool,"File*, uint32_t, _Bool" Function,+,storage_file_size,uint64_t,File* -Function,-,storage_file_sync,_Bool,File* +Function,+,storage_file_sync,_Bool,File* Function,+,storage_file_tell,uint64_t,File* Function,+,storage_file_truncate,_Bool,File* Function,+,storage_file_write,uint16_t,"File*, const void*, uint16_t" diff --git a/firmware/targets/f7/ble_glue/services/dev_info_service.c b/firmware/targets/f7/ble_glue/services/dev_info_service.c index cc95bb2fc1c..59af23e5c24 100644 --- a/firmware/targets/f7/ble_glue/services/dev_info_service.c +++ b/firmware/targets/f7/ble_glue/services/dev_info_service.c @@ -39,7 +39,7 @@ static bool dev_info_char_firmware_rev_callback( const uint8_t** data, uint16_t* data_len) { const DevInfoSvc* dev_info_svc = *(DevInfoSvc**)context; - *data_len = sizeof(dev_info_svc->hardware_revision); + *data_len = strlen(dev_info_svc->hardware_revision); if(data) { *data = (const uint8_t*)&dev_info_svc->hardware_revision; } @@ -155,17 +155,19 @@ void dev_info_svc_start() { void dev_info_svc_stop() { tBleStatus status; if(dev_info_svc) { - furi_string_free(dev_info_svc->version_string); // Delete service characteristics for(size_t i = 0; i < DevInfoSvcGattCharacteristicCount; i++) { flipper_gatt_characteristic_delete( dev_info_svc->service_handle, &dev_info_svc->characteristics[i]); } + // Delete service status = aci_gatt_del_service(dev_info_svc->service_handle); if(status) { FURI_LOG_E(TAG, "Failed to delete device info service: %d", status); } + + furi_string_free(dev_info_svc->version_string); free(dev_info_svc); dev_info_svc = NULL; } From fbded1e4ee79b6347ed223c2260dfb60559fed62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Mon, 9 Oct 2023 22:03:27 +0900 Subject: [PATCH 776/824] Lib: update stm32wb_copro to 1.17.3 release (#3119) * Lib: update stm32wb_copro to 1.17.3 release * Bump copro versions * Lib: switch stm32_copro to release --- fbt_options.py | 2 +- firmware/targets/f7/ble_glue/ble_app.c | 2 +- lib/stm32wb_copro | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/fbt_options.py b/fbt_options.py index d13abbe5e29..277790a4038 100644 --- a/fbt_options.py +++ b/fbt_options.py @@ -22,7 +22,7 @@ COPRO_OB_DATA = "scripts/ob.data" # Must match lib/stm32wb_copro version -COPRO_CUBE_VERSION = "1.17.2" +COPRO_CUBE_VERSION = "1.17.3" COPRO_CUBE_DIR = "lib/stm32wb_copro" diff --git a/firmware/targets/f7/ble_glue/ble_app.c b/firmware/targets/f7/ble_glue/ble_app.c index 37ec3d0b9ed..05dd46e943a 100644 --- a/firmware/targets/f7/ble_glue/ble_app.c +++ b/firmware/targets/f7/ble_glue/ble_app.c @@ -19,7 +19,7 @@ PLACE_IN_SECTION("MB_MEM2") ALIGN(4) static uint32_t ble_app_nvm[BLE_NVM_SRAM_SI _Static_assert( sizeof(SHCI_C2_Ble_Init_Cmd_Packet_t) == 58, - "Ble stack config structure size mismatch (check new config options - last updated for v.1.17.2)"); + "Ble stack config structure size mismatch (check new config options - last updated for v.1.17.3)"); typedef struct { FuriMutex* hci_mtx; diff --git a/lib/stm32wb_copro b/lib/stm32wb_copro index bbccbefae26..d8a6f1feb0e 160000 --- a/lib/stm32wb_copro +++ b/lib/stm32wb_copro @@ -1 +1 @@ -Subproject commit bbccbefae26a2301b8a4b58e57ebdeb93c08269b +Subproject commit d8a6f1feb0ebb6798c44162c6ae5ea743f90f3df From 65a56cdb4acc0dc66d294fd2a8a2198b43487d0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Tue, 10 Oct 2023 02:55:38 +0900 Subject: [PATCH 777/824] Firmware: bigger thread name storage. Notification app: better BacklightEnforce edge cases handling. (#3137) --- .../services/notification/notification_app.c | 17 ++++++++++------- firmware/targets/f7/inc/FreeRTOSConfig.h | 2 +- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/applications/services/notification/notification_app.c b/applications/services/notification/notification_app.c index 6fa48e7f4ec..5769ced9268 100644 --- a/applications/services/notification/notification_app.c +++ b/applications/services/notification/notification_app.c @@ -228,7 +228,7 @@ static void notification_process_notification_message( } break; case NotificationMessageTypeLedDisplayBacklightEnforceOn: - furi_assert(app->display_led_lock < UINT8_MAX); + furi_check(app->display_led_lock < UINT8_MAX); app->display_led_lock++; if(app->display_led_lock == 1) { notification_apply_internal_led_layer( @@ -237,12 +237,15 @@ static void notification_process_notification_message( } break; case NotificationMessageTypeLedDisplayBacklightEnforceAuto: - furi_assert(app->display_led_lock > 0); - app->display_led_lock--; - if(app->display_led_lock == 0) { - notification_apply_internal_led_layer( - &app->display, - notification_message->data.led.value * display_brightness_setting); + if(app->display_led_lock > 0) { + app->display_led_lock--; + if(app->display_led_lock == 0) { + notification_apply_internal_led_layer( + &app->display, + notification_message->data.led.value * display_brightness_setting); + } + } else { + FURI_LOG_E(TAG, "Incorrect BacklightEnforce use"); } break; case NotificationMessageTypeLedRed: diff --git a/firmware/targets/f7/inc/FreeRTOSConfig.h b/firmware/targets/f7/inc/FreeRTOSConfig.h index 9486f501c7d..024d43a6de8 100644 --- a/firmware/targets/f7/inc/FreeRTOSConfig.h +++ b/firmware/targets/f7/inc/FreeRTOSConfig.h @@ -26,7 +26,7 @@ extern uint32_t SystemCoreClock; /* Heap size determined automatically by linker */ // #define configTOTAL_HEAP_SIZE ((size_t)0) -#define configMAX_TASK_NAME_LEN (16) +#define configMAX_TASK_NAME_LEN (32) #define configGENERATE_RUN_TIME_STATS 0 #define configUSE_TRACE_FACILITY 1 #define configUSE_16_BIT_TICKS 0 From 4308a5e377deb979154fcb046286a9ea84a65207 Mon Sep 17 00:00:00 2001 From: Filipe Paz Rodrigues Date: Mon, 9 Oct 2023 11:48:37 -0700 Subject: [PATCH 778/824] CCID: Support PC To Reader Transfer Block data (#3126) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * CCID: Support PC To Reader Transfer Block data * Format sources Co-authored-by: あく --- applications/debug/ccid_test/ccid_test_app.c | 18 +- .../debug/ccid_test/iso7816_t0_apdu.c | 7 +- .../debug/ccid_test/iso7816_t0_apdu.h | 8 +- .../targets/f7/furi_hal/furi_hal_usb_ccid.c | 282 ++++++++++-------- .../furi_hal_include/furi_hal_usb_ccid.h | 7 +- 5 files changed, 189 insertions(+), 133 deletions(-) diff --git a/applications/debug/ccid_test/ccid_test_app.c b/applications/debug/ccid_test/ccid_test_app.c index a2f936d742d..3d7fba39de5 100644 --- a/applications/debug/ccid_test/ccid_test_app.c +++ b/applications/debug/ccid_test/ccid_test_app.c @@ -39,15 +39,25 @@ void icc_power_on_callback(uint8_t* atrBuffer, uint32_t* atrlen, void* context) iso7816_answer_to_reset(atrBuffer, atrlen); } -void xfr_datablock_callback(uint8_t* dataBlock, uint32_t* dataBlockLen, void* context) { +//dataBlock points to the buffer +//dataBlockLen tells reader how nany bytes should be read +void xfr_datablock_callback( + const uint8_t* dataBlock, + uint32_t dataBlockLen, + uint8_t* responseDataBlock, + uint32_t* responseDataBlockLen, + void* context) { UNUSED(context); + struct ISO7816_Command_APDU commandAPDU; + iso7816_read_command_apdu(&commandAPDU, dataBlock, dataBlockLen); + struct ISO7816_Response_APDU responseAPDU; //class not supported responseAPDU.SW1 = 0x6E; responseAPDU.SW2 = 0x00; - iso7816_write_response_apdu(&responseAPDU, dataBlock, dataBlockLen); + iso7816_write_response_apdu(&responseAPDU, responseDataBlock, responseDataBlockLen); } static const CcidCallbacks ccid_cb = { @@ -66,7 +76,7 @@ static void ccid_test_app_render_callback(Canvas* canvas, void* ctx) { canvas_draw_str(canvas, 0, 63, "Hold [back] to exit"); } -static void ccid_test_app__input_callback(InputEvent* input_event, void* ctx) { +static void ccid_test_app_input_callback(InputEvent* input_event, void* ctx) { FuriMessageQueue* event_queue = ctx; CcidTestAppEvent event; @@ -94,7 +104,7 @@ CcidTestApp* ccid_test_app_alloc() { //message queue app->event_queue = furi_message_queue_alloc(8, sizeof(CcidTestAppEvent)); furi_check(app->event_queue); - view_port_input_callback_set(app->view_port, ccid_test_app__input_callback, app->event_queue); + view_port_input_callback_set(app->view_port, ccid_test_app_input_callback, app->event_queue); return app; } diff --git a/applications/debug/ccid_test/iso7816_t0_apdu.c b/applications/debug/ccid_test/iso7816_t0_apdu.c index 29f5f7a86c1..7c690a6944e 100644 --- a/applications/debug/ccid_test/iso7816_t0_apdu.c +++ b/applications/debug/ccid_test/iso7816_t0_apdu.c @@ -4,7 +4,7 @@ #include #include "iso7816_t0_apdu.h" -void iso7816_answer_to_reset(uint8_t* atrBuffer, uint32_t* atrlen) { +void iso7816_answer_to_reset(uint8_t* dataBuffer, uint32_t* atrlen) { //minimum valid ATR: https://smartcard-atr.apdu.fr/parse?ATR=3B+00 uint8_t AtrBuffer[2] = { 0x3B, //TS (direct convention) @@ -12,18 +12,19 @@ void iso7816_answer_to_reset(uint8_t* atrBuffer, uint32_t* atrlen) { }; *atrlen = 2; - memcpy(atrBuffer, AtrBuffer, sizeof(uint8_t) * (*atrlen)); + memcpy(dataBuffer, AtrBuffer, sizeof(uint8_t) * (*atrlen)); } void iso7816_read_command_apdu( struct ISO7816_Command_APDU* command, const uint8_t* dataBuffer, uint32_t dataLen) { - furi_assert(dataLen <= 4); + UNUSED(dataLen); command->CLA = dataBuffer[0]; command->INS = dataBuffer[1]; command->P1 = dataBuffer[2]; command->P2 = dataBuffer[3]; + command->Lc = dataBuffer[4]; } void iso7816_write_response_apdu( diff --git a/applications/debug/ccid_test/iso7816_t0_apdu.h b/applications/debug/ccid_test/iso7816_t0_apdu.h index 8a8c99f85d4..b66d66054da 100644 --- a/applications/debug/ccid_test/iso7816_t0_apdu.h +++ b/applications/debug/ccid_test/iso7816_t0_apdu.h @@ -6,18 +6,18 @@ struct ISO7816_Command_APDU { //header uint8_t CLA; - uint32_t INS; + uint8_t INS; uint8_t P1; uint8_t P2; //body - uint8_t Nc; - uint8_t Ne; + uint8_t Lc; + uint8_t Le; } __attribute__((packed)); struct ISO7816_Response_APDU { uint8_t SW1; - uint32_t SW2; + uint8_t SW2; } __attribute__((packed)); void iso7816_answer_to_reset(uint8_t* atrBuffer, uint32_t* atrlen); diff --git a/firmware/targets/f7/furi_hal/furi_hal_usb_ccid.c b/firmware/targets/f7/furi_hal/furi_hal_usb_ccid.c index 559713d015a..e9906fed462 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_usb_ccid.c +++ b/firmware/targets/f7/furi_hal/furi_hal_usb_ccid.c @@ -250,76 +250,136 @@ static void ccid_on_suspend(usbd_device* dev) { connected = false; } -struct ccid_bulk_message_header { +typedef struct ccid_bulk_message_header { uint8_t bMessageType; uint32_t dwLength; uint8_t bSlot; uint8_t bSeq; -} __attribute__((packed)); +} __attribute__((packed)) ccid_bulk_message_header_t; + +uint8_t SendBuffer[sizeof(ccid_bulk_message_header_t) + CCID_DATABLOCK_SIZE]; + +//stores the data p +uint8_t ReceiveBuffer[sizeof(ccid_bulk_message_header_t) + CCID_DATABLOCK_SIZE]; + +void CALLBACK_CCID_GetSlotStatus( + uint8_t slot, + uint8_t seq, + struct rdr_to_pc_slot_status* responseSlotStatus) { + responseSlotStatus->bMessageType = RDR_TO_PC_SLOTSTATUS; -static struct rdr_to_pc_slot_status responseSlotStatus; -static struct rdr_to_pc_data_block responseDataBlock; -static struct rdr_to_pc_parameters_t0 responseParameters; -uint8_t SendDataBlock[CCID_DATABLOCK_SIZE]; + responseSlotStatus->bSlot = slot; + responseSlotStatus->bSeq = seq; + responseSlotStatus->bClockStatus = 0; -uint8_t CALLBACK_CCID_GetSlotStatus(uint8_t slot, uint8_t* error) { - if(slot == CCID_SLOT_INDEX) { - *error = CCID_ERROR_NOERROR; + responseSlotStatus->dwLength = 0; + + if(responseSlotStatus->bSlot == CCID_SLOT_INDEX) { + responseSlotStatus->bError = CCID_ERROR_NOERROR; if(smartcard_inserted) { - return CCID_COMMANDSTATUS_PROCESSEDWITHOUTERROR | CCID_ICCSTATUS_PRESENTANDACTIVE; + responseSlotStatus->bStatus = CCID_COMMANDSTATUS_PROCESSEDWITHOUTERROR | + CCID_ICCSTATUS_PRESENTANDACTIVE; } else { - return CCID_COMMANDSTATUS_PROCESSEDWITHOUTERROR | CCID_ICCSTATUS_NOICCPRESENT; + responseSlotStatus->bStatus = CCID_COMMANDSTATUS_PROCESSEDWITHOUTERROR | + CCID_ICCSTATUS_NOICCPRESENT; } } else { - *error = CCID_ERROR_SLOTNOTFOUND; - return CCID_COMMANDSTATUS_FAILED | CCID_ICCSTATUS_NOICCPRESENT; + responseSlotStatus->bError = CCID_ERROR_SLOTNOTFOUND; + responseSlotStatus->bStatus = CCID_COMMANDSTATUS_FAILED | CCID_ICCSTATUS_NOICCPRESENT; } } -uint8_t - CALLBACK_CCID_IccPowerOn(uint8_t slot, uint8_t* atrBuffer, uint32_t* atrlen, uint8_t* error) { - if(slot == CCID_SLOT_INDEX) { - *error = CCID_ERROR_NOERROR; +void CALLBACK_CCID_SetParametersT0( + struct pc_to_rdr_set_parameters_t0* requestSetParametersT0, + struct rdr_to_pc_parameters_t0* responseSetParametersT0) { + furi_assert(requestSetParametersT0->bProtocolNum == 0x00); //T0 + responseSetParametersT0->bMessageType = RDR_TO_PC_PARAMETERS; + responseSetParametersT0->bSlot = requestSetParametersT0->bSlot; + responseSetParametersT0->bSeq = requestSetParametersT0->bSeq; + + responseSetParametersT0->dwLength = + sizeof(struct pc_to_rdr_set_parameters_t0) - sizeof(ccid_bulk_message_header_t); + + if(responseSetParametersT0->bSlot == CCID_SLOT_INDEX) { + responseSetParametersT0->bError = CCID_ERROR_NOERROR; + if(smartcard_inserted) { + responseSetParametersT0->bProtocolNum = requestSetParametersT0->bProtocolNum; + responseSetParametersT0->bStatus = CCID_COMMANDSTATUS_PROCESSEDWITHOUTERROR | + CCID_ICCSTATUS_PRESENTANDACTIVE; + } else { + responseSetParametersT0->bStatus = CCID_COMMANDSTATUS_PROCESSEDWITHOUTERROR | + CCID_ICCSTATUS_NOICCPRESENT; + } + } else { + responseSetParametersT0->bError = CCID_ERROR_SLOTNOTFOUND; + responseSetParametersT0->bStatus = CCID_COMMANDSTATUS_FAILED | CCID_ICCSTATUS_NOICCPRESENT; + } +} + +void CALLBACK_CCID_IccPowerOn( + uint8_t slot, + uint8_t seq, + struct rdr_to_pc_data_block* responseDataBlock) { + responseDataBlock->bMessageType = RDR_TO_PC_DATABLOCK; + responseDataBlock->dwLength = 0; + responseDataBlock->bSlot = slot; + responseDataBlock->bSeq = seq; + + if(responseDataBlock->bSlot == CCID_SLOT_INDEX) { + responseDataBlock->bError = CCID_ERROR_NOERROR; if(smartcard_inserted) { if(callbacks[CCID_SLOT_INDEX] != NULL) { - callbacks[CCID_SLOT_INDEX]->icc_power_on_callback(atrBuffer, atrlen, NULL); + callbacks[CCID_SLOT_INDEX]->icc_power_on_callback( + responseDataBlock->abData, &responseDataBlock->dwLength, NULL); } else { - return CCID_COMMANDSTATUS_PROCESSEDWITHOUTERROR | - CCID_ICCSTATUS_PRESENTANDINACTIVE; + responseDataBlock->bStatus = CCID_COMMANDSTATUS_PROCESSEDWITHOUTERROR | + CCID_ICCSTATUS_PRESENTANDINACTIVE; } - return CCID_COMMANDSTATUS_PROCESSEDWITHOUTERROR | CCID_ICCSTATUS_PRESENTANDACTIVE; + responseDataBlock->bStatus = CCID_COMMANDSTATUS_PROCESSEDWITHOUTERROR | + CCID_ICCSTATUS_PRESENTANDACTIVE; } else { - return CCID_COMMANDSTATUS_PROCESSEDWITHOUTERROR | CCID_ICCSTATUS_NOICCPRESENT; + responseDataBlock->bStatus = CCID_COMMANDSTATUS_PROCESSEDWITHOUTERROR | + CCID_ICCSTATUS_NOICCPRESENT; } } else { - *error = CCID_ERROR_SLOTNOTFOUND; - return CCID_COMMANDSTATUS_FAILED | CCID_ICCSTATUS_NOICCPRESENT; + responseDataBlock->bError = CCID_ERROR_SLOTNOTFOUND; + responseDataBlock->bStatus = CCID_COMMANDSTATUS_FAILED | CCID_ICCSTATUS_NOICCPRESENT; } } -uint8_t CALLBACK_CCID_XfrBlock( - uint8_t slot, - uint8_t* dataBlock, - uint32_t* dataBlockLen, - uint8_t* error) { - if(slot == CCID_SLOT_INDEX) { - *error = CCID_ERROR_NOERROR; +void CALLBACK_CCID_XfrBlock( + struct pc_to_rdr_xfr_block* receivedXfrBlock, + struct rdr_to_pc_data_block* responseDataBlock) { + responseDataBlock->bMessageType = RDR_TO_PC_DATABLOCK; + responseDataBlock->bSlot = receivedXfrBlock->bSlot; + responseDataBlock->bSeq = receivedXfrBlock->bSeq; + responseDataBlock->bChainParameter = 0; + + if(responseDataBlock->bSlot == CCID_SLOT_INDEX) { + responseDataBlock->bError = CCID_ERROR_NOERROR; if(smartcard_inserted) { if(callbacks[CCID_SLOT_INDEX] != NULL) { - callbacks[CCID_SLOT_INDEX]->xfr_datablock_callback(dataBlock, dataBlockLen, NULL); + callbacks[CCID_SLOT_INDEX]->xfr_datablock_callback( + (const uint8_t*)receivedXfrBlock->abData, + receivedXfrBlock->dwLength, + responseDataBlock->abData, + &responseDataBlock->dwLength, + NULL); } else { - return CCID_COMMANDSTATUS_PROCESSEDWITHOUTERROR | - CCID_ICCSTATUS_PRESENTANDINACTIVE; + responseDataBlock->bStatus = CCID_COMMANDSTATUS_PROCESSEDWITHOUTERROR | + CCID_ICCSTATUS_PRESENTANDINACTIVE; } - return CCID_COMMANDSTATUS_PROCESSEDWITHOUTERROR | CCID_ICCSTATUS_PRESENTANDACTIVE; + responseDataBlock->bStatus = CCID_COMMANDSTATUS_PROCESSEDWITHOUTERROR | + CCID_ICCSTATUS_PRESENTANDACTIVE; } else { - return CCID_COMMANDSTATUS_PROCESSEDWITHOUTERROR | CCID_ICCSTATUS_NOICCPRESENT; + responseDataBlock->bStatus = CCID_COMMANDSTATUS_PROCESSEDWITHOUTERROR | + CCID_ICCSTATUS_NOICCPRESENT; } } else { - *error = CCID_ERROR_SLOTNOTFOUND; - return CCID_COMMANDSTATUS_FAILED | CCID_ICCSTATUS_NOICCPRESENT; + responseDataBlock->bError = CCID_ERROR_SLOTNOTFOUND; + responseDataBlock->bStatus = CCID_COMMANDSTATUS_FAILED | CCID_ICCSTATUS_NOICCPRESENT; } } @@ -347,109 +407,89 @@ static void ccid_tx_ep_callback(usbd_device* dev, uint8_t event, uint8_t ep) { if(event == usbd_evt_eprx) { if(connected == false) return; - struct ccid_bulk_message_header message; - usbd_ep_read(usb_dev, ep, &message, sizeof(message)); - - uint8_t Status; - uint8_t Error = CCID_ERROR_NOERROR; - - uint32_t dataBlockLen = 0; - uint8_t* dataBlockBuffer = NULL; - - if(message.bMessageType == PC_TO_RDR_GETSLOTSTATUS) { - responseSlotStatus.bMessageType = RDR_TO_PC_SLOTSTATUS; - responseSlotStatus.dwLength = 0; - responseSlotStatus.bSlot = message.bSlot; - responseSlotStatus.bSeq = message.bSeq; - - responseSlotStatus.bClockStatus = 0; - - Status = CALLBACK_CCID_GetSlotStatus(message.bSlot, &Error); - - responseSlotStatus.bStatus = Status; - responseSlotStatus.bError = Error; - - usbd_ep_write( - usb_dev, CCID_IN_EPADDR, &responseSlotStatus, sizeof(responseSlotStatus)); - } else if(message.bMessageType == PC_TO_RDR_ICCPOWERON) { - responseDataBlock.bMessageType = RDR_TO_PC_DATABLOCK; - responseDataBlock.bSlot = message.bSlot; - responseDataBlock.bSeq = message.bSeq; - responseDataBlock.bChainParameter = 0; + //read initial CCID message header - dataBlockLen = 0; - dataBlockBuffer = (uint8_t*)SendDataBlock; - Status = CALLBACK_CCID_IccPowerOn( - message.bSlot, (uint8_t*)dataBlockBuffer, &dataBlockLen, &Error); + int32_t bytes_read = usbd_ep_read( + usb_dev, ep, &ReceiveBuffer, sizeof(ccid_bulk_message_header_t) + CCID_DATABLOCK_SIZE); + //minimum request size is header size + furi_assert((uint16_t)bytes_read >= sizeof(ccid_bulk_message_header_t)); + ccid_bulk_message_header_t* message = (ccid_bulk_message_header_t*)&ReceiveBuffer; - furi_assert(dataBlockLen < CCID_DATABLOCK_SIZE); - responseDataBlock.dwLength = dataBlockLen; + if(message->bMessageType == PC_TO_RDR_ICCPOWERON) { + struct pc_to_rdr_icc_power_on* requestDataBlock = + (struct pc_to_rdr_icc_power_on*)message; + struct rdr_to_pc_data_block* responseDataBlock = + (struct rdr_to_pc_data_block*)&SendBuffer; - responseSlotStatus.bStatus = Status; - responseSlotStatus.bError = Error; + CALLBACK_CCID_IccPowerOn( + requestDataBlock->bSlot, requestDataBlock->bSeq, responseDataBlock); - memcpy(responseDataBlock.abData, SendDataBlock, dataBlockLen); usbd_ep_write( usb_dev, CCID_IN_EPADDR, - &responseDataBlock, - sizeof(struct rdr_to_pc_data_block) + (sizeof(uint8_t) * dataBlockLen)); - } else if(message.bMessageType == PC_TO_RDR_ICCPOWEROFF) { - responseSlotStatus.bMessageType = RDR_TO_PC_SLOTSTATUS; - responseSlotStatus.dwLength = 0; - responseSlotStatus.bSlot = message.bSlot; - responseSlotStatus.bSeq = message.bSeq; - - responseSlotStatus.bClockStatus = 0; + responseDataBlock, + sizeof(struct rdr_to_pc_data_block) + + (sizeof(uint8_t) * responseDataBlock->dwLength)); + } else if(message->bMessageType == PC_TO_RDR_ICCPOWEROFF) { + struct pc_to_rdr_icc_power_off* requestIccPowerOff = + (struct pc_to_rdr_icc_power_off*)message; + struct rdr_to_pc_slot_status* responseSlotStatus = + (struct rdr_to_pc_slot_status*)&SendBuffer; + + CALLBACK_CCID_GetSlotStatus( + requestIccPowerOff->bSlot, requestIccPowerOff->bSeq, responseSlotStatus); - uint8_t Status; - uint8_t Error = CCID_ERROR_NOERROR; - Status = CALLBACK_CCID_GetSlotStatus(message.bSlot, &Error); + usbd_ep_write( + usb_dev, CCID_IN_EPADDR, responseSlotStatus, sizeof(struct rdr_to_pc_slot_status)); + } else if(message->bMessageType == PC_TO_RDR_GETSLOTSTATUS) { + struct pc_to_rdr_get_slot_status* requestSlotStatus = + (struct pc_to_rdr_get_slot_status*)message; + struct rdr_to_pc_slot_status* responseSlotStatus = + (struct rdr_to_pc_slot_status*)&SendBuffer; - responseSlotStatus.bStatus = Status; - responseSlotStatus.bError = Error; + CALLBACK_CCID_GetSlotStatus( + requestSlotStatus->bSlot, requestSlotStatus->bSeq, responseSlotStatus); usbd_ep_write( - usb_dev, CCID_IN_EPADDR, &responseSlotStatus, sizeof(responseSlotStatus)); - } else if(message.bMessageType == PC_TO_RDR_SETPARAMETERS) { - responseParameters.bMessageType = RDR_TO_PC_PARAMETERS; - responseParameters.bSlot = message.bSlot; - responseParameters.bSeq = message.bSeq; - responseParameters.bProtocolNum = 0; //T0 + usb_dev, CCID_IN_EPADDR, responseSlotStatus, sizeof(struct rdr_to_pc_slot_status)); + } else if(message->bMessageType == PC_TO_RDR_XFRBLOCK) { + struct pc_to_rdr_xfr_block* receivedXfrBlock = (struct pc_to_rdr_xfr_block*)message; + struct rdr_to_pc_data_block* responseDataBlock = + (struct rdr_to_pc_data_block*)&SendBuffer; - uint8_t Status = CCID_COMMANDSTATUS_PROCESSEDWITHOUTERROR; - uint8_t Error = CCID_ERROR_NOERROR; + furi_assert(receivedXfrBlock->dwLength <= CCID_DATABLOCK_SIZE); + furi_assert( + (uint16_t)bytes_read >= + sizeof(ccid_bulk_message_header_t) + receivedXfrBlock->dwLength); - responseParameters.bStatus = Status; - responseParameters.bError = Error; + CALLBACK_CCID_XfrBlock(receivedXfrBlock, responseDataBlock); - responseParameters.dwLength = sizeof(struct rdr_to_pc_parameters_t0); + furi_assert(responseDataBlock->dwLength <= CCID_DATABLOCK_SIZE); usbd_ep_write( - usb_dev, CCID_IN_EPADDR, &responseParameters, sizeof(responseParameters)); - } else if(message.bMessageType == PC_TO_RDR_XFRBLOCK) { - responseDataBlock.bMessageType = RDR_TO_PC_DATABLOCK; - responseDataBlock.bSlot = message.bSlot; - responseDataBlock.bSeq = message.bSeq; - responseDataBlock.bChainParameter = 0; - - dataBlockLen = 0; - dataBlockBuffer = (uint8_t*)SendDataBlock; - Status = CALLBACK_CCID_XfrBlock( - message.bSlot, (uint8_t*)dataBlockBuffer, &dataBlockLen, &Error); - - furi_assert(dataBlockLen < CCID_DATABLOCK_SIZE); - responseDataBlock.dwLength = dataBlockLen; - - responseSlotStatus.bStatus = Status; - responseSlotStatus.bError = Error; + usb_dev, + CCID_IN_EPADDR, + responseDataBlock, + sizeof(struct rdr_to_pc_data_block) + + (sizeof(uint8_t) * responseDataBlock->dwLength)); + } else if(message->bMessageType == PC_TO_RDR_SETPARAMETERS) { + struct pc_to_rdr_set_parameters_t0* requestSetParametersT0 = + (struct pc_to_rdr_set_parameters_t0*)message; + struct rdr_to_pc_parameters_t0* responseSetParametersT0 = + (struct rdr_to_pc_parameters_t0*)&SendBuffer; + + furi_assert(requestSetParametersT0->dwLength <= CCID_DATABLOCK_SIZE); + furi_assert( + (uint16_t)bytes_read >= + sizeof(ccid_bulk_message_header_t) + requestSetParametersT0->dwLength); + + CALLBACK_CCID_SetParametersT0(requestSetParametersT0, responseSetParametersT0); - memcpy(responseDataBlock.abData, SendDataBlock, dataBlockLen); usbd_ep_write( usb_dev, CCID_IN_EPADDR, - &responseDataBlock, - sizeof(struct rdr_to_pc_data_block) + (sizeof(uint8_t) * dataBlockLen)); + responseSetParametersT0, + sizeof(struct rdr_to_pc_parameters_t0)); } } } diff --git a/firmware/targets/furi_hal_include/furi_hal_usb_ccid.h b/firmware/targets/furi_hal_include/furi_hal_usb_ccid.h index e3ee0dfc389..a4880e4b6c4 100644 --- a/firmware/targets/furi_hal_include/furi_hal_usb_ccid.h +++ b/firmware/targets/furi_hal_include/furi_hal_usb_ccid.h @@ -18,7 +18,12 @@ typedef struct { typedef struct { void (*icc_power_on_callback)(uint8_t* dataBlock, uint32_t* dataBlockLen, void* context); - void (*xfr_datablock_callback)(uint8_t* dataBlock, uint32_t* dataBlockLen, void* context); + void (*xfr_datablock_callback)( + const uint8_t* dataBlock, + uint32_t dataBlockLen, + uint8_t* responseDataBlock, + uint32_t* responseDataBlockLen, + void* context); } CcidCallbacks; void furi_hal_ccid_set_callbacks(CcidCallbacks* cb); From 38792f2c936934b63bf6e2c863c2c47095add177 Mon Sep 17 00:00:00 2001 From: h00die Date: Mon, 9 Oct 2023 15:01:17 -0400 Subject: [PATCH 779/824] Fix spelling across some project files (#3128) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * codespell across project Co-authored-by: あく --- .../examples/example_apps_assets/README.md | 4 ++-- .../examples/example_apps_data/README.md | 4 ++-- .../example_plugins/plugin_interface.h | 2 +- .../plugin_interface.h | 2 +- .../examples/example_thermo/example_thermo.c | 11 +++++----- .../bad_usb/helpers/ducky_script_commands.c | 2 +- .../system/updater/util/update_task.c | 2 +- documentation/AppsOnSDCard.md | 2 +- .../file_formats/BadUsbScriptFormat.md | 10 ++++----- lib/ST25RFAL002/doc/Release_Notes.html | 8 +++---- .../doc/ST25R3916_MisraComplianceReport.html | 4 ++-- lib/ST25RFAL002/include/rfal_analogConfig.h | 2 +- lib/ST25RFAL002/include/rfal_dpo.h | 4 ++-- lib/ST25RFAL002/include/rfal_iso15693_2.h | 4 ++-- lib/ST25RFAL002/include/rfal_isoDep.h | 4 ++-- lib/ST25RFAL002/include/rfal_nfc.h | 6 ++--- lib/ST25RFAL002/include/rfal_nfcDep.h | 4 ++-- lib/ST25RFAL002/include/rfal_nfca.h | 6 ++--- lib/ST25RFAL002/include/rfal_nfcf.h | 12 +++++----- lib/ST25RFAL002/include/rfal_t4t.h | 4 ++-- lib/ST25RFAL002/source/rfal_analogConfig.c | 2 +- lib/ST25RFAL002/source/rfal_isoDep.c | 8 +++---- lib/ST25RFAL002/source/rfal_nfc.c | 20 ++++++++--------- lib/ST25RFAL002/source/rfal_st25tb.c | 2 +- lib/ST25RFAL002/source/rfal_t4t.c | 2 +- .../source/st25r3916/rfal_rfst25r3916.c | 22 +++++++++---------- lib/ST25RFAL002/source/st25r3916/st25r3916.c | 4 ++-- lib/ST25RFAL002/source/st25r3916/st25r3916.h | 4 ++-- .../source/st25r3916/st25r3916_com.h | 4 ++-- .../source/st25r3916/st25r3916_irq.h | 8 +++---- lib/fatfs/ff.c | 16 +++++++------- scripts/flipper/assets/dolphin.py | 2 +- scripts/fwflash.py | 2 +- scripts/get_env.py | 6 ++--- scripts/toolchain/fbtenv.sh | 4 ++-- .../app_template/.github/workflows/build.yml | 2 +- 36 files changed, 102 insertions(+), 103 deletions(-) diff --git a/applications/examples/example_apps_assets/README.md b/applications/examples/example_apps_assets/README.md index a24183e88f2..024c0877be7 100644 --- a/applications/examples/example_apps_assets/README.md +++ b/applications/examples/example_apps_assets/README.md @@ -19,7 +19,7 @@ We recommend to use the `APP_ASSETS_PATH` macro to get the path to the Apps Asse ## What is the difference between the Apps Assets folder and the Apps Data folder? -The Apps Assets folder is used to store the data provided with the application. For example, if you want to create a game, you can store game levels (contant data) in the Apps Assets folder. +The Apps Assets folder is used to store the data provided with the application. For example, if you want to create a game, you can store game levels (content data) in the Apps Assets folder. The Apps Data folder is used to store data generated by the application. For example, if you want to create a game, you can save the progress of the game (user-generated data) in the Apps Data folder. @@ -55,4 +55,4 @@ When app is launched, the `files` folder will be unpacked to the Apps Assets fol The data is unpacked when the application starts, if the application is launched for the first time, or if the data within the application is updated. -When an application is compiled, the contents of the "files" folder are hashed and stored within the application itself. When the application starts, this hash is compared to the hash stored in the `.assets.signature` file. If the hashes differ or the `.assets.signature` file does not exist, the application folder is deleted and the new data is unpacked. \ No newline at end of file +When an application is compiled, the contents of the "files" folder are hashed and stored within the application itself. When the application starts, this hash is compared to the hash stored in the `.assets.signature` file. If the hashes differ or the `.assets.signature` file does not exist, the application folder is deleted and the new data is unpacked. diff --git a/applications/examples/example_apps_data/README.md b/applications/examples/example_apps_data/README.md index c70ac055a41..0e51daf18cd 100644 --- a/applications/examples/example_apps_data/README.md +++ b/applications/examples/example_apps_data/README.md @@ -19,6 +19,6 @@ We recommend to use the `APP_DATA_PATH` macro to get the path to the Apps Data f ## What is the difference between the Apps Assets folder and the Apps Data folder? -The Apps Assets folder is used to store the data provided with the application. For example, if you want to create a game, you can store game levels (contant data) in the Apps Assets folder. +The Apps Assets folder is used to store the data provided with the application. For example, if you want to create a game, you can store game levels (content data) in the Apps Assets folder. -The Apps Data folder is used to store data generated by the application. For example, if you want to create a game, you can save the progress of the game (user-generated data) in the Apps Data folder. \ No newline at end of file +The Apps Data folder is used to store data generated by the application. For example, if you want to create a game, you can save the progress of the game (user-generated data) in the Apps Data folder. diff --git a/applications/examples/example_plugins/plugin_interface.h b/applications/examples/example_plugins/plugin_interface.h index e24bc47bfb1..25d95d29436 100644 --- a/applications/examples/example_plugins/plugin_interface.h +++ b/applications/examples/example_plugins/plugin_interface.h @@ -1,6 +1,6 @@ #pragma once -/* Common interface between a plugin and host applicaion */ +/* Common interface between a plugin and host application */ #define PLUGIN_APP_ID "example_plugins" #define PLUGIN_API_VERSION 1 diff --git a/applications/examples/example_plugins_advanced/plugin_interface.h b/applications/examples/example_plugins_advanced/plugin_interface.h index e8b5a22d647..d99b335ff05 100644 --- a/applications/examples/example_plugins_advanced/plugin_interface.h +++ b/applications/examples/example_plugins_advanced/plugin_interface.h @@ -1,6 +1,6 @@ #pragma once -/* Common interface between a plugin and host applicaion */ +/* Common interface between a plugin and host application */ #define PLUGIN_APP_ID "example_plugins_advanced" #define PLUGIN_API_VERSION 1 diff --git a/applications/examples/example_thermo/example_thermo.c b/applications/examples/example_thermo/example_thermo.c index 5cb8863a476..5abd963a190 100644 --- a/applications/examples/example_thermo/example_thermo.c +++ b/applications/examples/example_thermo/example_thermo.c @@ -90,7 +90,7 @@ static void example_thermo_request_temperature(ExampleThermoContext* context) { bool success = false; do { /* Each communication with a 1-wire device starts by a reset. - The functon will return true if a device responded with a presence pulse. */ + The function will return true if a device responded with a presence pulse. */ if(!onewire_host_reset(onewire)) break; /* After the reset, a ROM operation must follow. If there is only one device connected, the "Skip ROM" command is most appropriate @@ -130,7 +130,7 @@ static void example_thermo_read_temperature(ExampleThermoContext* context) { size_t attempts_left = 10; do { /* Each communication with a 1-wire device starts by a reset. - The functon will return true if a device responded with a presence pulse. */ + The function will return true if a device responded with a presence pulse. */ if(!onewire_host_reset(onewire)) continue; /* After the reset, a ROM operation must follow. @@ -221,8 +221,7 @@ static void example_thermo_draw_callback(Canvas* canvas, void* ctx) { canvas_draw_line(canvas, 0, 16, 128, 16); canvas_set_font(canvas, FontSecondary); - canvas_draw_str_aligned( - canvas, middle_x, 30, AlignCenter, AlignBottom, "Connnect thermometer"); + canvas_draw_str_aligned(canvas, middle_x, 30, AlignCenter, AlignBottom, "Connect thermometer"); snprintf( text_store, @@ -237,7 +236,7 @@ static void example_thermo_draw_callback(Canvas* canvas, void* ctx) { float temp; char temp_units; - /* The applicaton is locale-aware. + /* The application is locale-aware. Change Settings->System->Units to check it out. */ switch(locale_get_measurement_unit()) { case LocaleMeasurementUnitsMetric: @@ -355,7 +354,7 @@ int32_t example_thermo_main(void* p) { /* Allocate all of the necessary structures */ ExampleThermoContext* context = example_thermo_context_alloc(); - /* Start the applicaton's main loop. It won't return until the application was requested to exit. */ + /* Start the application's main loop. It won't return until the application was requested to exit. */ example_thermo_run(context); /* Release all unneeded resources */ diff --git a/applications/main/bad_usb/helpers/ducky_script_commands.c b/applications/main/bad_usb/helpers/ducky_script_commands.c index cc713135eef..a5bc7c8cf95 100644 --- a/applications/main/bad_usb/helpers/ducky_script_commands.c +++ b/applications/main/bad_usb/helpers/ducky_script_commands.c @@ -53,7 +53,7 @@ static int32_t ducky_fnc_string(BadUsbScript* bad_usb, const char* line, int32_t furi_string_cat(bad_usb->string_print, "\n"); } - if(bad_usb->stringdelay == 0) { // stringdelay not set - run command immidiately + if(bad_usb->stringdelay == 0) { // stringdelay not set - run command immediately bool state = ducky_string(bad_usb, furi_string_get_cstr(bad_usb->string_print)); if(!state) { return ducky_error(bad_usb, "Invalid string %s", line); diff --git a/applications/system/updater/util/update_task.c b/applications/system/updater/util/update_task.c index 74d752b434b..df1793a8009 100644 --- a/applications/system/updater/util/update_task.c +++ b/applications/system/updater/util/update_task.c @@ -137,7 +137,7 @@ static const struct { .stage = UpdateTaskStageRadioBusy, .percent_min = 11, .percent_max = 20, - .descr = "C2 FUS swich failed", + .descr = "C2 FUS switch failed", }, { .stage = UpdateTaskStageRadioBusy, diff --git a/documentation/AppsOnSDCard.md b/documentation/AppsOnSDCard.md index 051fbb84f5b..f04793b4f4c 100644 --- a/documentation/AppsOnSDCard.md +++ b/documentation/AppsOnSDCard.md @@ -14,7 +14,7 @@ To build your application as a FAP, create a folder with your app's source code - To build your application, run `./fbt fap_{APPID}`, where APPID is your application's ID in its manifest. - To build your app and upload it over USB to run on Flipper, use `./fbt launch APPSRC=applications_user/path/to/app`. This command is configured in the default [VS Code profile](../.vscode/ReadMe.md) as a "Launch App on Flipper" build action (Ctrl+Shift+B menu). -- To build an app without uploading it to Flipper, use `./fbt build APPSRC=applications_user/path/to/app`. This command is also availabe in VSCode configuration as "Build App". +- To build an app without uploading it to Flipper, use `./fbt build APPSRC=applications_user/path/to/app`. This command is also available in VSCode configuration as "Build App". - To build all FAPs, run `./fbt faps` or `./fbt fap_dist`. ## FAP assets diff --git a/documentation/file_formats/BadUsbScriptFormat.md b/documentation/file_formats/BadUsbScriptFormat.md index 9cb848e2b70..1eb8eb5180b 100644 --- a/documentation/file_formats/BadUsbScriptFormat.md +++ b/documentation/file_formats/BadUsbScriptFormat.md @@ -4,7 +4,7 @@ BadUsb app uses extended Duckyscript syntax. It is compatible with classic USB R # Script file format -BadUsb app can execute only text scrips from `.txt` files, no compilation is required. Both `\n` and `\r\n` line endings are supported. Empty lines are allowed. You can use spaces or tabs for line indentation. +BadUsb app can execute only text scripts from `.txt` files, no compilation is required. Both `\n` and `\r\n` line endings are supported. Empty lines are allowed. You can use spaces or tabs for line indentation. # Command set @@ -72,10 +72,10 @@ Can be combined with a special key command or a single character. ## Key hold and release Up to 5 keys can be hold simultaneously. -| Command | Parameters | Notes | -| ------- | ------------------------------- | ----------------------------------------- | -| HOLD | Special key or single character | Press and hold key untill RELEASE command | -| RELEASE | Special key or single character | Release key | +| Command | Parameters | Notes | +| ------- | ------------------------------- | ---------------------------------------- | +| HOLD | Special key or single character | Press and hold key until RELEASE command | +| RELEASE | Special key or single character | Release key | ## Wait for button press diff --git a/lib/ST25RFAL002/doc/Release_Notes.html b/lib/ST25RFAL002/doc/Release_Notes.html index c48e0a6c8c2..28d501c091a 100755 --- a/lib/ST25RFAL002/doc/Release_Notes.html +++ b/lib/ST25RFAL002/doc/Release_Notes.html @@ -199,7 +199,7 @@

Extended support for specific features of ST's ISO15693 Tags. New ST25Dx module added
  • Interrupt handling changed and further protection added
  • RFAL feature switches have been modified and features are now disabled if omitted
  • -
  • ST25R3916 AAT (Automatic Antenna Tunning) module added
  • +
  • ST25R3916 AAT (Automatic Antenna Tuning) module added
  • RFAL NFC Higher layer added
  • Several driver improvements
  • @@ -286,12 +286,12 @@

    Introduced a new IRQ status handling to read the registers only once
  • Several changes for supporting Linux platform
  • SPI Select/Deselect moved to platform.h
  • -
  • Aditional protection of the IRQ status reading, new macros available: platformProtectST25R391xIrqStatus / platformUnprotectST25R391xIrqStatus
  • +
  • Additional protection of the IRQ status reading, new macros available: platformProtectST25R391xIrqStatus / platformUnprotectST25R391xIrqStatus
  • Renamed the IRQ Enable/Disable macros to platformProtectST25R391xComm / platformUnprotectST25R391xComm
  • Renamed SPI pins from chip specific to ST25R391X
  • Introduced a new option ST25R391X_COM_SINGLETXRX which executes SPI in one single exchange (additional buffer required)
  • Updated and added errata handlings to latest ST25R3911 Errata version
  • -
  • Fixed inconsitency on Analog settings for NFC-V
  • +
  • Fixed inconsistency on Analog settings for NFC-V
  • Fixed issue on NFC-V 1of256 decoding
  • Changed the default NFC-A FDT Listen to be more strict
  • Added Wake-Up mode support
  • @@ -318,7 +318,7 @@

    Provided with ST25R3911B Disco v1.1.12

    Main Changes

      -
    • EMD supression enabled for ST25R3911B
    • +
    • EMD suppression enabled for ST25R3911B

    diff --git a/lib/ST25RFAL002/doc/ST25R3916_MisraComplianceReport.html b/lib/ST25RFAL002/doc/ST25R3916_MisraComplianceReport.html index 7076e7cc886..e4ffaa9336c 100755 --- a/lib/ST25RFAL002/doc/ST25R3916_MisraComplianceReport.html +++ b/lib/ST25RFAL002/doc/ST25R3916_MisraComplianceReport.html @@ -237,7 +237,7 @@

      STM32

  • Parent repository:
    • ST25R3916_nucleo

    -
  • RFAL informations:
  • +
  • RFAL information:
    • Path: .../ST25R3916_nucleo/rfal
      Version: v2.1.2

  • Project repositories SHA1:
  • @@ -8087,7 +8087,7 @@

    File: .../ST25R3916_nucleo/rfal/source/rfal_isoDep.c

    - MISRA 10.5 - Layout of enum rfalIsoDepFSxI is guaranteed whithin 4bit range + MISRA 10.5 - Layout of enum rfalIsoDepFSxI is guaranteed within 4bit range 2526-2526 diff --git a/lib/ST25RFAL002/include/rfal_analogConfig.h b/lib/ST25RFAL002/include/rfal_analogConfig.h index 009bc84842e..de9db7be9c6 100644 --- a/lib/ST25RFAL002/include/rfal_analogConfig.h +++ b/lib/ST25RFAL002/include/rfal_analogConfig.h @@ -337,7 +337,7 @@ ReturnCode rfalAnalogConfigListWriteRaw(const uint8_t* configTbl, uint16_t confi * * \param[in] more: 0x00 indicates it is last Configuration ID settings; * 0x01 indicates more Configuration ID setting(s) are coming. - * \param[in] *config: reference to the configuration list of current Configuraiton ID. + * \param[in] *config: reference to the configuration list of current Configuration ID. * * \return ERR_PARAM : if Configuration ID or parameter is invalid * \return ERR_NOMEM : if LUT is full diff --git a/lib/ST25RFAL002/include/rfal_dpo.h b/lib/ST25RFAL002/include/rfal_dpo.h index e86c48db8a8..7dd2cde5e24 100644 --- a/lib/ST25RFAL002/include/rfal_dpo.h +++ b/lib/ST25RFAL002/include/rfal_dpo.h @@ -81,7 +81,7 @@ typedef struct { uint8_t dec; /*!< Threshold for decrementing the output power */ } rfalDpoEntry; -/*! Function pointer to methode doing the reference measurement */ +/*! Function pointer to method doing the reference measurement */ typedef ReturnCode (*rfalDpoMeasureFunc)(uint8_t*); /* @@ -103,7 +103,7 @@ void rfalDpoInitialize(void); /*! ***************************************************************************** - * \brief Set the measurement methode + * \brief Set the measurement method * * This function sets the measurement method used for reference measurement. * Based on the measurement the power will then be adjusted diff --git a/lib/ST25RFAL002/include/rfal_iso15693_2.h b/lib/ST25RFAL002/include/rfal_iso15693_2.h index 4ddb6b2be2a..9949812050d 100644 --- a/lib/ST25RFAL002/include/rfal_iso15693_2.h +++ b/lib/ST25RFAL002/include/rfal_iso15693_2.h @@ -186,8 +186,8 @@ extern ReturnCode iso15693VCDCode( * \param[in] ignoreBits : number of bits in the beginning where collisions will be ignored * \param[in] picopassMode : if set to true, the decoding will be according to Picopass * - * \return ERR_COLLISION : collision occured, data uncorrect - * \return ERR_CRC : CRC error, data uncorrect + * \return ERR_COLLISION : collision occurred, data incorrect + * \return ERR_CRC : CRC error, data incorrect * \return ERR_TIMEOUT : timeout waiting for data. * \return ERR_NONE : No error. * diff --git a/lib/ST25RFAL002/include/rfal_isoDep.h b/lib/ST25RFAL002/include/rfal_isoDep.h index 34bd6172a5e..9b2d32c64d8 100644 --- a/lib/ST25RFAL002/include/rfal_isoDep.h +++ b/lib/ST25RFAL002/include/rfal_isoDep.h @@ -616,7 +616,7 @@ bool rfalIsoDepIsAttrib(const uint8_t* buf, uint8_t bufLen); * \param[in] atsParam : reference to ATS parameters * \param[in] attribResParam : reference to ATTRIB_RES parameters * \param[in] buf : reference to buffer containing RATS or ATTRIB - * \param[in] bufLen : length in bytes of the given bufffer + * \param[in] bufLen : length in bytes of the given buffer * \param[in] actParam : reference to incoming reception information will be placed * * @@ -940,7 +940,7 @@ ReturnCode rfalIsoDepPollBHandleActivation( ***************************************************************************** * \brief ISO-DEP Poller Handle S(Parameters) * - * This checks if PICC supports S(PARAMETERS), retieves PICC's + * This checks if PICC supports S(PARAMETERS), retrieves PICC's * capabilities and sets the Bit Rate at the highest supported by both * devices * diff --git a/lib/ST25RFAL002/include/rfal_nfc.h b/lib/ST25RFAL002/include/rfal_nfc.h index 49cbe5f9cf8..88a740a0a7b 100644 --- a/lib/ST25RFAL002/include/rfal_nfc.h +++ b/lib/ST25RFAL002/include/rfal_nfc.h @@ -189,7 +189,7 @@ typedef struct { /*! Discovery parameters */ typedef struct { - rfalComplianceMode compMode; /*!< Compliancy mode to be used */ + rfalComplianceMode compMode; /*!< Compliance mode to be used */ uint16_t techs2Find; /*!< Technologies to search for */ uint16_t totalDuration; /*!< Duration of a whole Poll + Listen cycle */ uint8_t devLimit; /*!< Max number of devices */ @@ -211,7 +211,7 @@ typedef struct { bool wakeupConfigDefault; /*!< Wake-Up mode default configuration */ rfalWakeUpConfig wakeupConfig; /*!< Wake-Up mode configuration */ - bool activate_after_sak; // Set device to Active mode after SAK responce + bool activate_after_sak; // Set device to Active mode after SAK response } rfalNfcDiscoverParam; /*! Buffer union, only one interface is used at a time */ @@ -323,7 +323,7 @@ ReturnCode rfalNfcGetActiveDevice(rfalNfcDevice** dev); * * It selects the device to be activated. * It shall be called when more than one device has been identified to - * indiacte which device shall be actived + * indiacte which device shall be active * * \param[in] devIdx : device index to be activated * diff --git a/lib/ST25RFAL002/include/rfal_nfcDep.h b/lib/ST25RFAL002/include/rfal_nfcDep.h index 8b33c6cc216..9cf770e53b7 100644 --- a/lib/ST25RFAL002/include/rfal_nfcDep.h +++ b/lib/ST25RFAL002/include/rfal_nfcDep.h @@ -282,7 +282,7 @@ enum { RFAL_NFCDEP_Bx_64_6780 = 0x08 /*!< Peer also supports 6780 */ }; -/*! Enumeration of NFC-DEP bit rate Dividor in PSL Digital 1.0 Table 100 */ +/*! Enumeration of NFC-DEP bit rate Divider in PSL Digital 1.0 Table 100 */ enum { RFAL_NFCDEP_Dx_01_106 = RFAL_BR_106, /*!< Divisor D = 1 : bit rate = 106 */ RFAL_NFCDEP_Dx_02_212 = RFAL_BR_212, /*!< Divisor D = 2 : bit rate = 212 */ @@ -655,7 +655,7 @@ ReturnCode rfalNfcDepInitiatorHandleActivation( * * \param[in] buf : buffer holding Initiator's received request * \param[in] bufLen : size of the msg contained on the buf in Bytes - * \param[out] nfcid3 : pointer to where the NFCID3 may be outputed, + * \param[out] nfcid3 : pointer to where the NFCID3 may be outputted, * nfcid3 has NFCF_SENSF_NFCID3_LEN as length * Pass NULL if output parameter not desired * diff --git a/lib/ST25RFAL002/include/rfal_nfca.h b/lib/ST25RFAL002/include/rfal_nfca.h index 2a265b16e3d..4772586cbbf 100644 --- a/lib/ST25RFAL002/include/rfal_nfca.h +++ b/lib/ST25RFAL002/include/rfal_nfca.h @@ -332,7 +332,7 @@ ReturnCode * This method executes anti collision loop and select the device with higher NFCID1 * * When devLimit = 0 it is configured to perform collision detection only. Once a collision - * is detected the collision resolution is aborted immidiatly. If only one device is found + * is detected the collision resolution is aborted immediately. If only one device is found * with no collisions, it will properly resolved. * * \param[in] devLimit : device limit value (CON_DEVICES_LIMIT) @@ -374,7 +374,7 @@ ReturnCode rfalNfcaPollerSingleCollisionResolution( * * * When devLimit = 0 it is configured to perform collision detection only. Once a collision - * is detected the collision resolution is aborted immidiatly. If only one device is found + * is detected the collision resolution is aborted immediately. If only one device is found * with no collisions, it will properly resolved. * * @@ -436,7 +436,7 @@ ReturnCode rfalNfcaPollerSleepFullCollisionResolution( * * * When devLimit = 0 it is configured to perform collision detection only. Once a collision - * is detected the collision resolution is aborted immidiatly. If only one device is found + * is detected the collision resolution is aborted immediately. If only one device is found * with no collisions, it will properly resolved. * * diff --git a/lib/ST25RFAL002/include/rfal_nfcf.h b/lib/ST25RFAL002/include/rfal_nfcf.h index d3dbfa3954e..30c34765db8 100644 --- a/lib/ST25RFAL002/include/rfal_nfcf.h +++ b/lib/ST25RFAL002/include/rfal_nfcf.h @@ -81,8 +81,8 @@ #define RFAL_NFCF_SENSF_PARAMS_TSN_POS 3U /*!< Time Slot Number position in the SENSF_REQ */ #define RFAL_NFCF_POLL_MAXCARDS 16U /*!< Max number slots/cards 16 */ -#define RFAL_NFCF_CMD_POS 0U /*!< Command/Responce code length */ -#define RFAL_NFCF_CMD_LEN 1U /*!< Command/Responce code length */ +#define RFAL_NFCF_CMD_POS 0U /*!< Command/Response code length */ +#define RFAL_NFCF_CMD_LEN 1U /*!< Command/Response code length */ #define RFAL_NFCF_LENGTH_LEN 1U /*!< LEN field length */ #define RFAL_NFCF_HEADER_LEN (RFAL_NFCF_LENGTH_LEN + RFAL_NFCF_CMD_LEN) /*!< Header length*/ @@ -315,8 +315,8 @@ ReturnCode rfalNfcfPollerCollisionResolution( ***************************************************************************** * \brief NFC-F Poller Check/Read * - * It computes a Check / Read command accoring to T3T 1.0 and JIS X6319-4 and - * sends it to PICC. If sucessfully, the rxBuf will contain the the number of + * It computes a Check / Read command according to T3T 1.0 and JIS X6319-4 and + * sends it to PICC. If successfully, the rxBuf will contain the the number of * blocks in the first byte followed by the blocks data. * * \param[in] nfcid2 : nfcid2 of the device @@ -344,7 +344,7 @@ ReturnCode rfalNfcfPollerCheck( ***************************************************************************** * \brief NFC-F Poller Update/Write * - * It computes a Update / Write command accoring to T3T 1.0 and JIS X6319-4 and + * It computes a Update / Write command according to T3T 1.0 and JIS X6319-4 and * sends it to PICC. * * \param[in] nfcid2 : nfcid2 of the device @@ -381,7 +381,7 @@ ReturnCode rfalNfcfPollerUpdate( * * \param[in] buf : buffer holding Initiator's received command * \param[in] bufLen : length of received command in bytes - * \param[out] nfcid2 : pointer to where the NFCID2 may be outputed, + * \param[out] nfcid2 : pointer to where the NFCID2 may be outputted, * nfcid2 has NFCF_SENSF_NFCID2_LEN as length * Pass NULL if output parameter not desired * diff --git a/lib/ST25RFAL002/include/rfal_t4t.h b/lib/ST25RFAL002/include/rfal_t4t.h index edee1cd8cd7..454cad8e004 100644 --- a/lib/ST25RFAL002/include/rfal_t4t.h +++ b/lib/ST25RFAL002/include/rfal_t4t.h @@ -84,7 +84,7 @@ #define RFAL_T4T_ISO7816_P1_SELECT_BY_FILEID \ 0x00U /*!< P1 value for Select by file identifier */ #define RFAL_T4T_ISO7816_P2_SELECT_FIRST_OR_ONLY_OCCURENCE \ - 0x00U /*!< b2b1 P2 value for First or only occurence */ + 0x00U /*!< b2b1 P2 value for First or only occurrence */ #define RFAL_T4T_ISO7816_P2_SELECT_RETURN_FCI_TEMPLATE \ 0x00U /*!< b4b3 P2 value for Return FCI template */ #define RFAL_T4T_ISO7816_P2_SELECT_NO_RESPONSE_DATA \ @@ -177,7 +177,7 @@ ReturnCode rfalT4TPollerComposeCAPDU(const rfalT4tCApduParam* apduParam); * \brief T4T Parse R-APDU * * This method parses a R-APDU according to NFC Forum T4T and ISO7816-4. - * It will extract the data length and check if the Satus word is expected. + * It will extract the data length and check if the Status word is expected. * * \param[in,out] apduParam : APDU parameters * apduParam.rApduBodyLen will contain the data length diff --git a/lib/ST25RFAL002/source/rfal_analogConfig.c b/lib/ST25RFAL002/source/rfal_analogConfig.c index 1237af18bda..1058dd32852 100644 --- a/lib/ST25RFAL002/source/rfal_analogConfig.c +++ b/lib/ST25RFAL002/source/rfal_analogConfig.c @@ -30,7 +30,7 @@ * * \author bkam * - * \brief Funcitons to manage and set analog settings. + * \brief Functions to manage and set analog settings. * */ diff --git a/lib/ST25RFAL002/source/rfal_isoDep.c b/lib/ST25RFAL002/source/rfal_isoDep.c index 77e67521dcd..13674788e84 100644 --- a/lib/ST25RFAL002/source/rfal_isoDep.c +++ b/lib/ST25RFAL002/source/rfal_isoDep.c @@ -434,7 +434,7 @@ ****************************************************************************** */ -/*! Internal structure to be used in handling of S(PARAMETRS) only */ +/*! Internal structure to be used in handling of S(PARAMETERS) only */ typedef struct { uint8_t pcb; /*!< PCB byte */ rfalIsoDepSParameter sParam; /*!< S(PARAMETERS) */ @@ -1053,7 +1053,7 @@ static ReturnCode isoDepDataExchangePCD(uint16_t* outActRxLen, bool* outIsChaini } return ERR_TIMEOUT; /* NFC Forum mandates timeout or transmission error depending on previous errors */ } - } else /* Unexcpected R-Block */ + } else /* Unexpected R-Block */ { return ERR_PROTO; } @@ -1899,7 +1899,7 @@ static ReturnCode isoDepDataExchangePICC(void) { return ERR_BUSY; } - /* Rule E - R(ACK) with not current bn -> toogle bn */ + /* Rule E - R(ACK) with not current bn -> toggle bn */ isoDep_ToggleBN(gIsoDep.blockNumber); /* This block has been transmitted and acknowledged, perform WTX until next data is provided */ @@ -2336,7 +2336,7 @@ ReturnCode rfalIsoDepPollAGetActivationStatus(void) { rfalSetGT(rfalGetFDTPoll()); rfalFieldOnAndStartGT(); - /* Send RATS retransmission */ /* PRQA S 4342 1 # MISRA 10.5 - Layout of enum rfalIsoDepFSxI is guaranteed whithin 4bit range */ + /* Send RATS retransmission */ /* PRQA S 4342 1 # MISRA 10.5 - Layout of enum rfalIsoDepFSxI is guaranteed within 4bit range */ EXIT_ON_ERR( ret, rfalIsoDepStartRATS( diff --git a/lib/ST25RFAL002/source/rfal_nfc.c b/lib/ST25RFAL002/source/rfal_nfc.c index 9040b7f9d2a..57ff2e2358a 100644 --- a/lib/ST25RFAL002/source/rfal_nfc.c +++ b/lib/ST25RFAL002/source/rfal_nfc.c @@ -90,14 +90,14 @@ typedef struct { rfalNfcDevice* activeDev; /* Active device pointer */ rfalNfcDiscoverParam disc; /* Discovery parameters */ rfalNfcDevice devList[RFAL_NFC_MAX_DEVICES]; /*!< Location of device list */ - uint8_t devCnt; /* Decices found counter */ + uint8_t devCnt; /* Devices found counter */ uint32_t discTmr; /* Discovery Total duration timer */ ReturnCode dataExErr; /* Last Data Exchange error */ bool discRestart; /* Restart discover after deactivation flag */ bool isRxChaining; /* Flag indicating Other device is chaining */ uint32_t lmMask; /* Listen Mode mask */ bool isTechInit; /* Flag indicating technology has been set */ - bool isOperOngoing; /* Flag indicating opration is ongoing */ + bool isOperOngoing; /* Flag indicating operation is ongoing */ rfalNfcBuffer txBuf; /* Tx buffer for Data Exchange */ rfalNfcBuffer rxBuf; /* Rx buffer for Data Exchange */ @@ -674,7 +674,7 @@ ReturnCode rfalNfcDataExchangeStart( break; } - /* If a transceive has succesfully started flag Data Exchange as ongoing */ + /* If a transceive has successfuly started flag Data Exchange as ongoing */ if(err == ERR_NONE) { gNfcDev.dataExErr = ERR_BUSY; gNfcDev.state = RFAL_NFC_STATE_DATAEXCHANGE; @@ -814,7 +814,7 @@ ReturnCode rfalNfcDataExchangeCustomStart( break; } - /* If a transceive has succesfully started flag Data Exchange as ongoing */ + /* If a transceive has successfuly started flag Data Exchange as ongoing */ if(err == ERR_NONE) { gNfcDev.dataExErr = ERR_BUSY; gNfcDev.state = RFAL_NFC_STATE_DATAEXCHANGE; @@ -897,7 +897,7 @@ ReturnCode rfalNfcDataExchangeGetStatus(void) { sizeof(gNfcDev.rxBuf.rfBuf), &gNfcDev.rxLen)); - /* If set Sleep was succesfull keep restore the Sleep request signal */ + /* If set Sleep was successful keep restore the Sleep request signal */ gNfcDev.dataExErr = ERR_SLEEP_REQ; } #endif /* RFAL_FEATURE_LISTEN_MODE */ @@ -924,7 +924,7 @@ static ReturnCode rfalNfcPollTechDetetection(void) { err = ERR_NONE; - /* Supress warning when specific RFAL features have been disabled */ + /* Suppress warning when specific RFAL features have been disabled */ NO_WARNING(err); /*******************************************************************************/ @@ -1154,7 +1154,7 @@ static ReturnCode rfalNfcPollCollResolution(void) { err = ERR_NONE; i = 0; - /* Supress warning when specific RFAL features have been disabled */ + /* Suppress warning when specific RFAL features have been disabled */ NO_WARNING(err); NO_WARNING(devCnt); NO_WARNING(i); @@ -1415,7 +1415,7 @@ static ReturnCode rfalNfcPollActivation(uint8_t devIt) { err = ERR_NONE; - /* Supress warning when specific RFAL features have been disabled */ + /* Suppress warning when specific RFAL features have been disabled */ NO_WARNING(err); if(devIt > gNfcDev.devCnt) { @@ -1428,7 +1428,7 @@ static ReturnCode rfalNfcPollActivation(uint8_t devIt) { /*******************************************************************************/ #if RFAL_FEATURE_NFC_DEP case RFAL_NFC_LISTEN_TYPE_AP2P: - /* Activation has already been perfomed (ATR_REQ) */ + /* Activation has already been performed (ATR_REQ) */ gNfcDev.devList[devIt].nfcid = gNfcDev.devList[devIt].proto.nfcDep.activation.Target.ATR_RES.NFCID3; @@ -1971,7 +1971,7 @@ static ReturnCode rfalNfcNfcDepActivate( uint16_t atrReqLen) { rfalNfcDepAtrParam initParam; - /* Supress warnings if Listen mode is disabled */ + /* Suppress warnings if Listen mode is disabled */ NO_WARNING(atrReq); NO_WARNING(atrReqLen); diff --git a/lib/ST25RFAL002/source/rfal_st25tb.c b/lib/ST25RFAL002/source/rfal_st25tb.c index 6e110c91e74..00f699104b5 100644 --- a/lib/ST25RFAL002/source/rfal_st25tb.c +++ b/lib/ST25RFAL002/source/rfal_st25tb.c @@ -509,7 +509,7 @@ ReturnCode rfalSt25tbPollerWriteBlock(uint8_t blockAddress, const rfalSt25tbBloc return ret; } - /* If a transmission error occurred (maybe noise while commiting data) wait maximum programming time and verify data afterwards */ + /* If a transmission error occurred (maybe noise while committing data) wait maximum programming time and verify data afterwards */ rfalSetGT((RFAL_ST25TB_FWT + RFAL_ST25TB_TW)); rfalFieldOnAndStartGT(); } diff --git a/lib/ST25RFAL002/source/rfal_t4t.c b/lib/ST25RFAL002/source/rfal_t4t.c index 8592daf8fbb..4c29d79b023 100644 --- a/lib/ST25RFAL002/source/rfal_t4t.c +++ b/lib/ST25RFAL002/source/rfal_t4t.c @@ -113,7 +113,7 @@ ReturnCode rfalT4TPollerComposeCAPDU(const rfalT4tCApduParam* apduParam) { /* Check if Data is present */ if(apduParam->LcFlag) { if(apduParam->Lc == 0U) { - /* Extented field coding not supported */ + /* Extended field coding not supported */ return ERR_PARAM; } diff --git a/lib/ST25RFAL002/source/st25r3916/rfal_rfst25r3916.c b/lib/ST25RFAL002/source/st25r3916/rfal_rfst25r3916.c index 0bad67a6dba..7b8c243b13b 100644 --- a/lib/ST25RFAL002/source/st25r3916/rfal_rfst25r3916.c +++ b/lib/ST25RFAL002/source/st25r3916/rfal_rfst25r3916.c @@ -52,7 +52,7 @@ /* ****************************************************************************** - * ENABLE SWITCHS + * ENABLE SWITCHES ****************************************************************************** */ @@ -137,7 +137,7 @@ typedef struct { /*! Struct that holds counters to control the FIFO on Tx and Rx */ typedef struct { uint16_t - expWL; /*!< The amount of bytes expected to be Tx when a WL interrupt occours */ + expWL; /*!< The amount of bytes expected to be Tx when a WL interrupt occurs */ uint16_t bytesTotal; /*!< Total bytes to be transmitted OR the total bytes received */ uint16_t @@ -398,7 +398,7 @@ typedef union { /* PRQA S 0750 # MISRA 19.2 - Both members are of the same type * ISO15693 2000 8.4 t1 MIN = 4192/fc * ISO15693 2009 9.1 t1 MIN = 4320/fc * Digital 2.1 B.5 FDTV,LISTEN,MIN = 4310/fc - * Set FDT Listen one step earlier than on the more recent spec versions for greater interoprability + * Set FDT Listen one step earlier than on the more recent spec versions for greater interoperability */ #define RFAL_FDT_LISTEN_V_ADJUSTMENT 64U @@ -1958,7 +1958,7 @@ static void rfalPrepareTransceive(void) { ST25R3916_IRQ_MASK_WU_F); /* Enable external Field interrupts to detect Link Loss and SENF_REQ auto responses */ } - /* In Active comms enable also External Field interrupts and set RF Collsion Avoindance */ + /* In Active comms enable also External Field interrupts and set RF Collision Avoindance */ if(rfalIsModeActiveComm(gRFAL.mode)) { maskInterrupts |= (ST25R3916_IRQ_MASK_EOF | ST25R3916_IRQ_MASK_EON | ST25R3916_IRQ_MASK_PPON2 | @@ -1990,7 +1990,7 @@ static void rfalTransceiveTx(void) { uint16_t tmp; ReturnCode ret; - /* Supress warning in case NFC-V feature is disabled */ + /* Suppress warning in case NFC-V feature is disabled */ ret = ERR_NONE; NO_WARNING(ret); @@ -2370,7 +2370,7 @@ static void rfalTransceiveRx(void) { } if((irqs & ST25R3916_IRQ_MASK_RX_REST) != 0U) { - /* RX_REST indicates that Receiver has been reseted due to EMD, therefore a RXS + RXE should * + /* RX_REST indicates that Receiver has been reset due to EMD, therefore a RXS + RXE should * * follow if a good reception is followed within the valid initial timeout */ /* Check whether NRT has expired already, if so signal a timeout */ @@ -2917,7 +2917,7 @@ ReturnCode rfalISO14443ATransceiveAnticollisionFrame( } /*******************************************************************************/ - /* Set speficic Analog Config for Anticolission if needed */ + /* Set specific Analog Config for Anticolission if needed */ rfalSetAnalogConfig( (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCA | RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_ANTICOL)); @@ -3030,7 +3030,7 @@ ReturnCode rfalISO15693TransceiveAnticollisionFrame( } /*******************************************************************************/ - /* Set speficic Analog Config for Anticolission if needed */ + /* Set specific Analog Config for Anticolission if needed */ rfalSetAnalogConfig( (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCV | RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_ANTICOL)); @@ -4053,7 +4053,7 @@ ReturnCode rfalListenSetState(rfalLmState newSt) { ST25R3916_REG_AUX_DISPLAY, ST25R3916_REG_AUX_DISPLAY_osc_ok, ST25R3916_REG_AUX_DISPLAY_osc_ok)) { - /* Wait for Oscilator ready */ + /* Wait for Oscillator ready */ if(st25r3916WaitForInterruptsTimed( ST25R3916_IRQ_MASK_OSC, ST25R3916_TOUT_OSC_STABLE) == 0U) { ret = ERR_IO; @@ -4074,7 +4074,7 @@ ReturnCode rfalListenSetState(rfalLmState newSt) { * Ensure that when upper layer calls SetState(IDLE), it restores initial * configuration and that check whether an external Field is still present */ if((gRFAL.Lm.mdMask & RFAL_LM_MASK_ACTIVE_P2P) != 0U) { - /* Ensure nfc_ar is reseted and back to only after Rx */ + /* Ensure nfc_ar is reset and back to only after Rx */ st25r3916ExecuteCommand(ST25R3916_CMD_STOP); st25r3916ChangeRegisterBits( ST25R3916_REG_MODE, @@ -4443,7 +4443,7 @@ static uint16_t rfalWakeUpModeFilter(uint16_t curRef, uint16_t curVal, uint8_t w /* Perform the averaging|filter as describded in ST25R3916 DS */ - /* Avoid signed arithmetics by spliting in two cases */ + /* Avoid signed arithmetics by splitting in two cases */ if(curVal > curRef) { newRef = curRef + ((curVal - curRef) / weight); diff --git a/lib/ST25RFAL002/source/st25r3916/st25r3916.c b/lib/ST25RFAL002/source/st25r3916/st25r3916.c index 6d8e07b1ab1..9b6c22bff64 100644 --- a/lib/ST25RFAL002/source/st25r3916/st25r3916.c +++ b/lib/ST25RFAL002/source/st25r3916/st25r3916.c @@ -274,7 +274,7 @@ ReturnCode st25r3916Initialize(void) { void st25r3916Deinitialize(void) { st25r3916DisableInterrupts(ST25R3916_IRQ_MASK_ALL); - /* Disabe Tx and Rx, Keep OSC On */ + /* Disable Tx and Rx, Keep OSC On */ st25r3916TxRxOff(); return; @@ -418,7 +418,7 @@ ReturnCode st25r3916CalibrateCapacitiveSensor(uint8_t* result) { ST25R3916_TOUT_CALIBRATE_CAP_SENSOR, &res); - /* Check wether the calibration was successull */ + /* Check whether the calibration was successull */ if(((res & ST25R3916_REG_CAP_SENSOR_RESULT_cs_cal_end) != ST25R3916_REG_CAP_SENSOR_RESULT_cs_cal_end) || ((res & ST25R3916_REG_CAP_SENSOR_RESULT_cs_cal_err) == diff --git a/lib/ST25RFAL002/source/st25r3916/st25r3916.h b/lib/ST25RFAL002/source/st25r3916/st25r3916.h index d546d79e31a..2ba202d86d5 100644 --- a/lib/ST25RFAL002/source/st25r3916/st25r3916.h +++ b/lib/ST25RFAL002/source/st25r3916/st25r3916.h @@ -117,7 +117,7 @@ struct st25r3916StreamConfig { #define ST25R3916_CMD_AM_MOD_STATE_CHANGE \ 0xD2U /*!< AM Modulation state change */ #define ST25R3916_CMD_MEASURE_AMPLITUDE \ - 0xD3U /*!< Measure singal amplitude on RFI inputs */ + 0xD3U /*!< Measure signal amplitude on RFI inputs */ #define ST25R3916_CMD_RESET_RXGAIN \ 0xD5U /*!< Reset RX Gain */ #define ST25R3916_CMD_ADJUST_REGULATORS \ @@ -299,7 +299,7 @@ ReturnCode st25r3916SetBitrate(uint8_t txrate, uint8_t rxrate); * * This function the power level is measured in maximum load conditions and * the regulated voltage reference is set to 250mV below this level. - * Execution of this function lasts arround 5ms. + * Execution of this function lasts around 5ms. * * The regulated voltages will be set to the result of Adjust Regulators * diff --git a/lib/ST25RFAL002/source/st25r3916/st25r3916_com.h b/lib/ST25RFAL002/source/st25r3916/st25r3916_com.h index 726758c83d8..6a47317e373 100644 --- a/lib/ST25RFAL002/source/st25r3916/st25r3916_com.h +++ b/lib/ST25RFAL002/source/st25r3916/st25r3916_com.h @@ -1053,7 +1053,7 @@ ReturnCode st25r3916ReadRegister(uint8_t reg, uint8_t* val); * auto-increment feature. That is, after each read the address pointer * inside the ST25R3916 gets incremented automatically. * - * \param[in] reg: Address of the frist register to read from. + * \param[in] reg: Address of the first register to read from. * \param[in] values: pointer to a buffer where the result shall be written to. * \param[in] length: Number of registers to be read out. * @@ -1088,7 +1088,7 @@ ReturnCode st25r3916WriteRegister(uint8_t reg, uint8_t val); * auto-increment feature. That is, after each write the address pointer * inside the ST25R3916 gets incremented automatically. * - * \param[in] reg: Address of the frist register to write. + * \param[in] reg: Address of the first register to write. * \param[in] values: pointer to a buffer containing the values to be written. * \param[in] length: Number of values to be written. * diff --git a/lib/ST25RFAL002/source/st25r3916/st25r3916_irq.h b/lib/ST25RFAL002/source/st25r3916/st25r3916_irq.h index 2433173718e..e8ce2d07a50 100644 --- a/lib/ST25RFAL002/source/st25r3916/st25r3916_irq.h +++ b/lib/ST25RFAL002/source/st25r3916/st25r3916_irq.h @@ -161,7 +161,7 @@ * \param[in] tmo : time in milliseconds until timeout occurs. If set to 0 * the functions waits forever. * - * \return : 0 if timeout occured otherwise a mask indicating the cleared + * \return : 0 if timeout occurred otherwise a mask indicating the cleared * interrupts. * ***************************************************************************** @@ -173,7 +173,7 @@ uint32_t st25r3916WaitForInterruptsTimed(uint32_t mask, uint16_t tmo); * \brief Get status for the given interrupt * * This function is used to check whether the interrupt given by \a mask - * has occured. If yes the interrupt gets cleared. This function returns + * has occurred. If yes the interrupt gets cleared. This function returns * only status bits which are inside \a mask. * * \param[in] mask : mask indicating the interrupt to check for. @@ -189,7 +189,7 @@ uint32_t st25r3916GetInterrupt(uint32_t mask); * \brief Init the 3916 interrupt * * This function is used to check whether the interrupt given by \a mask - * has occured. + * has occurred. * ***************************************************************************** */ @@ -220,7 +220,7 @@ void st25r3916CheckForReceivedInterrupts(void); ***************************************************************************** * \brief ISR Service routine * - * This function modiefies the interupt + * This function modiefies the interrupt ***************************************************************************** */ void st25r3916Isr(void); diff --git a/lib/fatfs/ff.c b/lib/fatfs/ff.c index d3908957888..86646c32ca6 100644 --- a/lib/fatfs/ff.c +++ b/lib/fatfs/ff.c @@ -1927,7 +1927,7 @@ DWORD xsum32 ( static void get_xdir_info ( - BYTE* dirb, /* Pointer to the direcotry entry block 85+C0+C1s */ + BYTE* dirb, /* Pointer to the directory entry block 85+C0+C1s */ FILINFO* fno /* Buffer to store the extracted file information */ ) { @@ -1971,17 +1971,17 @@ void get_xdir_info ( /*-----------------------------------*/ -/* exFAT: Get a directry entry block */ +/* exFAT: Get a directory entry block */ /*-----------------------------------*/ static FRESULT load_xdir ( /* FR_INT_ERR: invalid entry block */ - DIR* dp /* Pointer to the reading direcotry object pointing the 85 entry */ + DIR* dp /* Pointer to the reading directory object pointing the 85 entry */ ) { FRESULT res; UINT i, sz_ent; - BYTE* dirb = dp->obj.fs->dirbuf; /* Pointer to the on-memory direcotry entry block 85+C0+C1s */ + BYTE* dirb = dp->obj.fs->dirbuf; /* Pointer to the on-memory directory entry block 85+C0+C1s */ /* Load 85 entry */ @@ -2026,7 +2026,7 @@ FRESULT load_xdir ( /* FR_INT_ERR: invalid entry block */ /*------------------------------------------------*/ static FRESULT load_obj_dir ( - DIR* dp, /* Blank directory object to be used to access containing direcotry */ + DIR* dp, /* Blank directory object to be used to access containing directory */ const _FDID* obj /* Object with its containing directory information */ ) { @@ -2054,12 +2054,12 @@ FRESULT load_obj_dir ( /*-----------------------------------------------*/ static FRESULT store_xdir ( - DIR* dp /* Pointer to the direcotry object */ + DIR* dp /* Pointer to the directory object */ ) { FRESULT res; UINT nent; - BYTE* dirb = dp->obj.fs->dirbuf; /* Pointer to the direcotry entry block 85+C0+C1s */ + BYTE* dirb = dp->obj.fs->dirbuf; /* Pointer to the directory entry block 85+C0+C1s */ /* Create set sum */ st_word(dirb + XDIR_SetSum, xdir_sum(dirb)); @@ -2087,7 +2087,7 @@ FRESULT store_xdir ( static void create_xdir ( - BYTE* dirb, /* Pointer to the direcotry entry block buffer */ + BYTE* dirb, /* Pointer to the directory entry block buffer */ const WCHAR* lfn /* Pointer to the nul terminated file name */ ) { diff --git a/scripts/flipper/assets/dolphin.py b/scripts/flipper/assets/dolphin.py index b4a53a62df5..e9089a1b991 100644 --- a/scripts/flipper/assets/dolphin.py +++ b/scripts/flipper/assets/dolphin.py @@ -267,7 +267,7 @@ def load(self, source_directory: str): # Load animation data while True: try: - # Read animation spcification + # Read animation specification name = file.readKey("Name") min_butthurt = file.readKeyInt("Min butthurt") max_butthurt = file.readKeyInt("Max butthurt") diff --git a/scripts/fwflash.py b/scripts/fwflash.py index 6948bd7f51b..00a76322d48 100755 --- a/scripts/fwflash.py +++ b/scripts/fwflash.py @@ -462,7 +462,7 @@ def flash(self): available_interfaces = self._search_interface(network_flash_interfaces) if not available_interfaces: - self.logger.error("No availiable interfaces") + self.logger.error("No available interfaces") return 1 elif len(available_interfaces) > 1: self.logger.error("Multiple interfaces found:") diff --git a/scripts/get_env.py b/scripts/get_env.py index 5403bafeb28..e8f6b3b77fd 100755 --- a/scripts/get_env.py +++ b/scripts/get_env.py @@ -72,10 +72,10 @@ def get_details(event, args): def add_env(name, value, file): - delimeter = id_gen() - print(f"{name}<<{delimeter}", file=file) + delimiter = id_gen() + print(f"{name}<<{delimiter}", file=file) print(f"{value}", file=file) - print(f"{delimeter}", file=file) + print(f"{delimiter}", file=file) def add_set_output_var(name, value, file): diff --git a/scripts/toolchain/fbtenv.sh b/scripts/toolchain/fbtenv.sh index c5040ed81e7..990776b27a0 100755 --- a/scripts/toolchain/fbtenv.sh +++ b/scripts/toolchain/fbtenv.sh @@ -89,7 +89,7 @@ fbtenv_check_sourced() setopt +o nomatch; # disabling 'no match found' warning in zsh return 0;; esac - if [ ${0##*/} = "fbtenv.sh" ]; then # exluding script itself + if [ ${0##*/} = "fbtenv.sh" ]; then # excluding script itself fbtenv_show_usage; return 1; fi @@ -163,7 +163,7 @@ fbtenv_check_rosetta() if [ "$ARCH_TYPE" = "arm64" ]; then if ! pgrep -q oahd; then echo "Flipper Zero Toolchain needs Rosetta2 to run under Apple Silicon"; - echo "Please instal it by typing 'softwareupdate --install-rosetta --agree-to-license'"; + echo "Please install it by typing 'softwareupdate --install-rosetta --agree-to-license'"; return 1; fi fi diff --git a/scripts/ufbt/project_template/app_template/.github/workflows/build.yml b/scripts/ufbt/project_template/app_template/.github/workflows/build.yml index 0834f83798f..c11ffc180fd 100644 --- a/scripts/ufbt/project_template/app_template/.github/workflows/build.yml +++ b/scripts/ufbt/project_template/app_template/.github/workflows/build.yml @@ -5,7 +5,7 @@ name: "FAP: Build for multiple SDK sources" on: push: - ## put your main branch name under "braches" + ## put your main branch name under "branches" #branches: # - master pull_request: From f45a5dff430fb0f41c86f3c02bad93a1a4777997 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Fri, 13 Oct 2023 01:34:30 +0900 Subject: [PATCH 780/824] Fix various crashes if debug libraries used (#3144) * FuriHal: enable HSI in stop mode only if we use STOP0, proper SMPS selected clock assert * Furi: fix double crash caused by bkpt use outside of debug session * Libs: update ERC and MGG contrast * Fix various crashes with LIB_DEBUG=1 * BadUsb: size_t where it should be and proper printf types * Various fixes and make PVS happy * FuriHal: proper CCID status and make PVS happy * boot: update mode: graceful handling of corrupted stage file --------- Co-authored-by: hedger --- .../main/bad_usb/helpers/ducky_script.c | 4 +-- .../main/bad_usb/helpers/ducky_script.h | 6 ++--- .../main/bad_usb/views/bad_usb_view.c | 8 +++--- ...subghz_frequency_analyzer_log_item_array.h | 2 +- applications/main/subghz/subghz_cli.c | 2 +- .../services/storage/storage_external_api.c | 2 +- firmware/targets/f7/furi_hal/furi_hal_clock.c | 3 +-- firmware/targets/f7/furi_hal/furi_hal_power.c | 9 ++++++- .../targets/f7/furi_hal/furi_hal_usb_ccid.c | 26 +++++++++---------- firmware/targets/f7/src/update.c | 5 ++-- furi/core/check.c | 6 ++--- furi/core/memmgr_heap.c | 5 ++-- lib/flipper_application/elf/elf_file.c | 2 +- lib/flipper_application/elf/elf_file_i.h | 2 +- lib/lfrfid/lfrfid_worker.c | 1 - lib/nfc/protocols/slix.c | 2 +- lib/u8g2/u8g2_glue.c | 4 +-- 17 files changed, 47 insertions(+), 42 deletions(-) diff --git a/applications/main/bad_usb/helpers/ducky_script.c b/applications/main/bad_usb/helpers/ducky_script.c index 11c74c010f2..c4aa9106229 100644 --- a/applications/main/bad_usb/helpers/ducky_script.c +++ b/applications/main/bad_usb/helpers/ducky_script.c @@ -290,7 +290,7 @@ static int32_t ducky_script_execute_next(BadUsbScript* bad_usb, File* script_fil return delay_val; } else if(delay_val < 0) { // Script error bad_usb->st.error_line = bad_usb->st.line_cur - 1; - FURI_LOG_E(WORKER_TAG, "Unknown command at line %u", bad_usb->st.line_cur - 1U); + FURI_LOG_E(WORKER_TAG, "Unknown command at line %zu", bad_usb->st.line_cur - 1U); return SCRIPT_STATE_ERROR; } else { return (delay_val + bad_usb->defdelay); @@ -329,7 +329,7 @@ static int32_t ducky_script_execute_next(BadUsbScript* bad_usb, File* script_fil return delay_val; } else if(delay_val < 0) { bad_usb->st.error_line = bad_usb->st.line_cur; - FURI_LOG_E(WORKER_TAG, "Unknown command at line %u", bad_usb->st.line_cur); + FURI_LOG_E(WORKER_TAG, "Unknown command at line %zu", bad_usb->st.line_cur); return SCRIPT_STATE_ERROR; } else { return (delay_val + bad_usb->defdelay); diff --git a/applications/main/bad_usb/helpers/ducky_script.h b/applications/main/bad_usb/helpers/ducky_script.h index c8705dbdd10..dca61ed4e43 100644 --- a/applications/main/bad_usb/helpers/ducky_script.h +++ b/applications/main/bad_usb/helpers/ducky_script.h @@ -24,10 +24,10 @@ typedef enum { typedef struct { BadUsbWorkerState state; - uint16_t line_cur; - uint16_t line_nb; + size_t line_cur; + size_t line_nb; uint32_t delay_remain; - uint16_t error_line; + size_t error_line; char error[64]; } BadUsbState; diff --git a/applications/main/bad_usb/views/bad_usb_view.c b/applications/main/bad_usb/views/bad_usb_view.c index fa75b50d038..588b260c45e 100644 --- a/applications/main/bad_usb/views/bad_usb_view.c +++ b/applications/main/bad_usb/views/bad_usb_view.c @@ -82,7 +82,7 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) { canvas_set_font(canvas, FontPrimary); canvas_draw_str_aligned(canvas, 127, 33, AlignRight, AlignBottom, "ERROR:"); canvas_set_font(canvas, FontSecondary); - furi_string_printf(disp_str, "line %u", model->state.error_line); + furi_string_printf(disp_str, "line %zu", model->state.error_line); canvas_draw_str_aligned( canvas, 127, 46, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); furi_string_reset(disp_str); @@ -105,7 +105,7 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) { } canvas_set_font(canvas, FontBigNumbers); furi_string_printf( - disp_str, "%u", ((model->state.line_cur - 1) * 100) / model->state.line_nb); + disp_str, "%zu", ((model->state.line_cur - 1) * 100) / model->state.line_nb); canvas_draw_str_aligned( canvas, 114, 40, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); furi_string_reset(disp_str); @@ -124,7 +124,7 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) { } canvas_set_font(canvas, FontBigNumbers); furi_string_printf( - disp_str, "%u", ((model->state.line_cur - 1) * 100) / model->state.line_nb); + disp_str, "%zu", ((model->state.line_cur - 1) * 100) / model->state.line_nb); canvas_draw_str_aligned( canvas, 114, 40, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); furi_string_reset(disp_str); @@ -142,7 +142,7 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) { } canvas_set_font(canvas, FontBigNumbers); furi_string_printf( - disp_str, "%u", ((model->state.line_cur - 1) * 100) / model->state.line_nb); + disp_str, "%zu", ((model->state.line_cur - 1) * 100) / model->state.line_nb); canvas_draw_str_aligned( canvas, 114, 40, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); furi_string_reset(disp_str); diff --git a/applications/main/subghz/helpers/subghz_frequency_analyzer_log_item_array.h b/applications/main/subghz/helpers/subghz_frequency_analyzer_log_item_array.h index df53143d23f..2fa70284a42 100644 --- a/applications/main/subghz/helpers/subghz_frequency_analyzer_log_item_array.h +++ b/applications/main/subghz/helpers/subghz_frequency_analyzer_log_item_array.h @@ -19,7 +19,7 @@ typedef enum { const char* subghz_frequency_analyzer_log_get_order_name(SubGhzFrequencyAnalyzerLogOrderBy order_by); -TUPLE_DEF2( +TUPLE_DEF2( //-V1048 SubGhzFrequencyAnalyzerLogItem, (seq, uint8_t), (frequency, uint32_t), diff --git a/applications/main/subghz/subghz_cli.c b/applications/main/subghz/subghz_cli.c index f7d6b3a1c54..0a7b521273f 100644 --- a/applications/main/subghz/subghz_cli.c +++ b/applications/main/subghz/subghz_cli.c @@ -565,7 +565,7 @@ void subghz_cli_command_decode_raw(Cli* cli, FuriString* args, void* context) { } } - printf("\r\nPackets received \033[0;32m%u\033[0m\r\n", instance->packet_count); + printf("\r\nPackets received \033[0;32m%zu\033[0m\r\n", instance->packet_count); // Cleanup subghz_receiver_free(receiver); diff --git a/applications/services/storage/storage_external_api.c b/applications/services/storage/storage_external_api.c index 2ba58f9c665..ed69b49a57d 100644 --- a/applications/services/storage/storage_external_api.c +++ b/applications/services/storage/storage_external_api.c @@ -869,7 +869,7 @@ bool storage_simply_remove_recursive(Storage* storage, const char* path) { while(storage_dir_read(dir, &fileinfo, name, MAX_NAME_LENGTH)) { if(file_info_is_dir(&fileinfo)) { - furi_string_cat_printf(cur_dir, "/%s", name); + furi_string_cat_printf(cur_dir, "/%s", name); //-V576 go_deeper = true; break; } diff --git a/firmware/targets/f7/furi_hal/furi_hal_clock.c b/firmware/targets/f7/furi_hal/furi_hal_clock.c index 86c8fd46761..945dc323b2f 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_clock.c +++ b/firmware/targets/f7/furi_hal/furi_hal_clock.c @@ -118,7 +118,6 @@ void furi_hal_clock_init() { NVIC_EnableIRQ(SysTick_IRQn); LL_RCC_SetCLK48ClockSource(LL_RCC_CLK48_CLKSOURCE_PLLSAI1); - LL_RCC_HSI_EnableInStopMode(); // Ensure that MR is capable of work in STOP0 LL_RCC_SetSMPSClockSource(LL_RCC_SMPS_CLKSOURCE_HSI); LL_RCC_SetSMPSPrescaler(LL_RCC_SMPS_DIV_1); LL_RCC_SetRFWKPClockSource(LL_RCC_RFWKP_CLKSOURCE_LSE); @@ -133,7 +132,7 @@ void furi_hal_clock_switch_hse2hsi() { ; LL_RCC_SetSysClkSource(LL_RCC_SYS_CLKSOURCE_HSI); - furi_assert(LL_RCC_GetSMPSClockSource() == LL_RCC_SMPS_CLKSOURCE_HSI); + furi_assert(LL_RCC_GetSMPSClockSelection() == LL_RCC_SMPS_CLKSOURCE_HSI); while(LL_RCC_GetSysClkSource() != LL_RCC_SYS_CLKSOURCE_STATUS_HSI) ; diff --git a/firmware/targets/f7/furi_hal/furi_hal_power.c b/firmware/targets/f7/furi_hal/furi_hal_power.c index c14de856970..0eb93e664bd 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_power.c +++ b/firmware/targets/f7/furi_hal/furi_hal_power.c @@ -65,6 +65,10 @@ void furi_hal_power_init() { LL_PWR_SetPowerMode(FURI_HAL_POWER_STOP_MODE); LL_C2_PWR_SetPowerMode(FURI_HAL_POWER_STOP_MODE); +#if FURI_HAL_POWER_STOP_MODE == LL_PWR_MODE_STOP0 + LL_RCC_HSI_EnableInStopMode(); // Ensure that MR is capable of work in STOP0 +#endif + furi_hal_i2c_acquire(&furi_hal_i2c_handle_power); // Find and init gauge if(bq27220_init(&furi_hal_i2c_handle_power)) { @@ -206,8 +210,11 @@ static inline void furi_hal_power_deep_sleep() { while(LL_HSEM_1StepLock(HSEM, CFG_HW_RCC_SEMID)) ; - if(LL_RCC_GetSysClkSource() != LL_RCC_SYS_CLKSOURCE_STATUS_HSE) { + if(LL_RCC_GetSysClkSource() == LL_RCC_SYS_CLKSOURCE_STATUS_HSI) { furi_hal_clock_switch_hsi2hse(); + } else { + // Ensure that we are already on HSE + furi_check(LL_RCC_GetSysClkSource() == LL_RCC_SYS_CLKSOURCE_STATUS_HSE); } LL_HSEM_ReleaseLock(HSEM, CFG_HW_RCC_SEMID, 0); diff --git a/firmware/targets/f7/furi_hal/furi_hal_usb_ccid.c b/firmware/targets/f7/furi_hal/furi_hal_usb_ccid.c index e9906fed462..5c35c69f884 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_usb_ccid.c +++ b/firmware/targets/f7/furi_hal/furi_hal_usb_ccid.c @@ -331,13 +331,12 @@ void CALLBACK_CCID_IccPowerOn( if(callbacks[CCID_SLOT_INDEX] != NULL) { callbacks[CCID_SLOT_INDEX]->icc_power_on_callback( responseDataBlock->abData, &responseDataBlock->dwLength, NULL); + responseDataBlock->bStatus = CCID_COMMANDSTATUS_PROCESSEDWITHOUTERROR | + CCID_ICCSTATUS_PRESENTANDACTIVE; } else { responseDataBlock->bStatus = CCID_COMMANDSTATUS_PROCESSEDWITHOUTERROR | CCID_ICCSTATUS_PRESENTANDINACTIVE; } - - responseDataBlock->bStatus = CCID_COMMANDSTATUS_PROCESSEDWITHOUTERROR | - CCID_ICCSTATUS_PRESENTANDACTIVE; } else { responseDataBlock->bStatus = CCID_COMMANDSTATUS_PROCESSEDWITHOUTERROR | CCID_ICCSTATUS_NOICCPRESENT; @@ -366,13 +365,12 @@ void CALLBACK_CCID_XfrBlock( responseDataBlock->abData, &responseDataBlock->dwLength, NULL); + responseDataBlock->bStatus = CCID_COMMANDSTATUS_PROCESSEDWITHOUTERROR | + CCID_ICCSTATUS_PRESENTANDACTIVE; } else { responseDataBlock->bStatus = CCID_COMMANDSTATUS_PROCESSEDWITHOUTERROR | CCID_ICCSTATUS_PRESENTANDINACTIVE; } - - responseDataBlock->bStatus = CCID_COMMANDSTATUS_PROCESSEDWITHOUTERROR | - CCID_ICCSTATUS_PRESENTANDACTIVE; } else { responseDataBlock->bStatus = CCID_COMMANDSTATUS_PROCESSEDWITHOUTERROR | CCID_ICCSTATUS_NOICCPRESENT; @@ -413,11 +411,11 @@ static void ccid_tx_ep_callback(usbd_device* dev, uint8_t event, uint8_t ep) { usb_dev, ep, &ReceiveBuffer, sizeof(ccid_bulk_message_header_t) + CCID_DATABLOCK_SIZE); //minimum request size is header size furi_assert((uint16_t)bytes_read >= sizeof(ccid_bulk_message_header_t)); - ccid_bulk_message_header_t* message = (ccid_bulk_message_header_t*)&ReceiveBuffer; + ccid_bulk_message_header_t* message = (ccid_bulk_message_header_t*)&ReceiveBuffer; //-V641 if(message->bMessageType == PC_TO_RDR_ICCPOWERON) { struct pc_to_rdr_icc_power_on* requestDataBlock = - (struct pc_to_rdr_icc_power_on*)message; + (struct pc_to_rdr_icc_power_on*)message; //-V641 struct rdr_to_pc_data_block* responseDataBlock = (struct rdr_to_pc_data_block*)&SendBuffer; @@ -432,9 +430,9 @@ static void ccid_tx_ep_callback(usbd_device* dev, uint8_t event, uint8_t ep) { (sizeof(uint8_t) * responseDataBlock->dwLength)); } else if(message->bMessageType == PC_TO_RDR_ICCPOWEROFF) { struct pc_to_rdr_icc_power_off* requestIccPowerOff = - (struct pc_to_rdr_icc_power_off*)message; + (struct pc_to_rdr_icc_power_off*)message; //-V641 struct rdr_to_pc_slot_status* responseSlotStatus = - (struct rdr_to_pc_slot_status*)&SendBuffer; + (struct rdr_to_pc_slot_status*)&SendBuffer; //-V641 CALLBACK_CCID_GetSlotStatus( requestIccPowerOff->bSlot, requestIccPowerOff->bSeq, responseSlotStatus); @@ -443,9 +441,9 @@ static void ccid_tx_ep_callback(usbd_device* dev, uint8_t event, uint8_t ep) { usb_dev, CCID_IN_EPADDR, responseSlotStatus, sizeof(struct rdr_to_pc_slot_status)); } else if(message->bMessageType == PC_TO_RDR_GETSLOTSTATUS) { struct pc_to_rdr_get_slot_status* requestSlotStatus = - (struct pc_to_rdr_get_slot_status*)message; + (struct pc_to_rdr_get_slot_status*)message; //-V641 struct rdr_to_pc_slot_status* responseSlotStatus = - (struct rdr_to_pc_slot_status*)&SendBuffer; + (struct rdr_to_pc_slot_status*)&SendBuffer; //-V641 CALLBACK_CCID_GetSlotStatus( requestSlotStatus->bSlot, requestSlotStatus->bSeq, responseSlotStatus); @@ -474,9 +472,9 @@ static void ccid_tx_ep_callback(usbd_device* dev, uint8_t event, uint8_t ep) { (sizeof(uint8_t) * responseDataBlock->dwLength)); } else if(message->bMessageType == PC_TO_RDR_SETPARAMETERS) { struct pc_to_rdr_set_parameters_t0* requestSetParametersT0 = - (struct pc_to_rdr_set_parameters_t0*)message; + (struct pc_to_rdr_set_parameters_t0*)message; //-V641 struct rdr_to_pc_parameters_t0* responseSetParametersT0 = - (struct rdr_to_pc_parameters_t0*)&SendBuffer; + (struct rdr_to_pc_parameters_t0*)&SendBuffer; //-V641 furi_assert(requestSetParametersT0->dwLength <= CCID_DATABLOCK_SIZE); furi_assert( diff --git a/firmware/targets/f7/src/update.c b/firmware/targets/f7/src/update.c index 378e74a5cc3..e9228a6e956 100644 --- a/firmware/targets/f7/src/update.c +++ b/firmware/targets/f7/src/update.c @@ -70,7 +70,8 @@ static bool flipper_update_load_stage(const FuriString* work_dir, UpdateManifest if((f_stat(furi_string_get_cstr(loader_img_path), &stat) != FR_OK) || (f_open(&file, furi_string_get_cstr(loader_img_path), FA_OPEN_EXISTING | FA_READ) != - FR_OK)) { + FR_OK) || + (stat.fsize == 0)) { furi_string_free(loader_img_path); return false; } @@ -83,7 +84,7 @@ static bool flipper_update_load_stage(const FuriString* work_dir, UpdateManifest uint32_t crc = 0; do { uint16_t size_read = 0; - if(f_read(&file, img + bytes_read, MAX_READ, &size_read) != FR_OK) { + if(f_read(&file, img + bytes_read, MAX_READ, &size_read) != FR_OK) { //-V769 break; } crc = crc32_calc_buffer(crc, img + bytes_read, size_read); diff --git a/furi/core/check.c b/furi/core/check.c index f7dcfc59590..8888eddfb35 100644 --- a/furi/core/check.c +++ b/furi/core/check.c @@ -153,18 +153,18 @@ FURI_NORETURN void __furi_crash() { __furi_print_heap_info(); __furi_print_bt_stack_info(); -#ifndef FURI_DEBUG // Check if debug enabled by DAP // https://developer.arm.com/documentation/ddi0403/d/Debug-Architecture/ARMv7-M-Debug/Debug-register-support-in-the-SCS/Debug-Halting-Control-and-Status-Register--DHCSR?lang=en bool debug = CoreDebug->DHCSR & CoreDebug_DHCSR_C_DEBUGEN_Msk; +#ifdef FURI_NDEBUG if(debug) { #endif furi_hal_console_puts("\r\nSystem halted. Connect debugger for more info\r\n"); furi_hal_console_puts("\033[0m\r\n"); furi_hal_debug_enable(); - RESTORE_REGISTERS_AND_HALT_MCU(true); -#ifndef FURI_DEBUG + RESTORE_REGISTERS_AND_HALT_MCU(debug); +#ifdef FURI_NDEBUG } else { uint32_t ptr = (uint32_t)__furi_check_message; if(ptr < FLASH_BASE || ptr > (FLASH_BASE + FLASH_SIZE)) { diff --git a/furi/core/memmgr_heap.c b/furi/core/memmgr_heap.c index ca206cd3981..b8baf9c7c62 100644 --- a/furi/core/memmgr_heap.c +++ b/furi/core/memmgr_heap.c @@ -115,8 +115,9 @@ static size_t xBlockAllocatedBit = 0; #include /* Allocation tracking types */ -DICT_DEF2(MemmgrHeapAllocDict, uint32_t, uint32_t) -DICT_DEF2( +DICT_DEF2(MemmgrHeapAllocDict, uint32_t, uint32_t) //-V1048 + +DICT_DEF2( //-V1048 MemmgrHeapThreadDict, uint32_t, M_DEFAULT_OPLIST, diff --git a/lib/flipper_application/elf/elf_file.c b/lib/flipper_application/elf/elf_file.c index 7ac4c655dcb..9b8b4c8f5f5 100644 --- a/lib/flipper_application/elf/elf_file.c +++ b/lib/flipper_application/elf/elf_file.c @@ -853,7 +853,7 @@ ElfProcessSectionResult elf_process_section( if(process_section(elf->fd, section_header.sh_offset, section_header.sh_size, context)) { result = ElfProcessSectionResultSuccess; } else { - result = ElfProcessSectionResultCannotProcess; + result = ElfProcessSectionResultCannotProcess; //-V1048 } } diff --git a/lib/flipper_application/elf/elf_file_i.h b/lib/flipper_application/elf/elf_file_i.h index 39cadfdc6f6..e1b97b631cb 100644 --- a/lib/flipper_application/elf/elf_file_i.h +++ b/lib/flipper_application/elf/elf_file_i.h @@ -6,7 +6,7 @@ extern "C" { #endif -DICT_DEF2(AddressCache, int, M_DEFAULT_OPLIST, Elf32_Addr, M_DEFAULT_OPLIST) +DICT_DEF2(AddressCache, int, M_DEFAULT_OPLIST, Elf32_Addr, M_DEFAULT_OPLIST) //-V1048 /** * Callable elf entry type diff --git a/lib/lfrfid/lfrfid_worker.c b/lib/lfrfid/lfrfid_worker.c index 1e491c6b787..cbc7b02e37e 100644 --- a/lib/lfrfid/lfrfid_worker.c +++ b/lib/lfrfid/lfrfid_worker.c @@ -118,7 +118,6 @@ void lfrfid_worker_start_thread(LFRFIDWorker* worker) { } void lfrfid_worker_stop_thread(LFRFIDWorker* worker) { - furi_assert(worker->mode_index == LFRFIDWorkerIdle); furi_thread_flags_set(furi_thread_get_id(worker->thread), LFRFIDEventStopThread); furi_thread_join(worker->thread); } diff --git a/lib/nfc/protocols/slix.c b/lib/nfc/protocols/slix.c index dbff2f21884..4b15f4b9771 100644 --- a/lib/nfc/protocols/slix.c +++ b/lib/nfc/protocols/slix.c @@ -197,7 +197,7 @@ ReturnCode slix_get_random(NfcVData* data) { } ReturnCode slix_unlock(NfcVData* data, uint32_t password_id) { - furi_assert(rand); + furi_assert(data); uint16_t received = 0; uint8_t rxBuf[32]; diff --git a/lib/u8g2/u8g2_glue.c b/lib/u8g2/u8g2_glue.c index 9463d131838..230bd2a1bcf 100644 --- a/lib/u8g2/u8g2_glue.c +++ b/lib/u8g2/u8g2_glue.c @@ -2,8 +2,8 @@ #include -#define CONTRAST_ERC 31 -#define CONTRAST_MGG 27 +#define CONTRAST_ERC 32 +#define CONTRAST_MGG 28 uint8_t u8g2_gpio_and_delay_stm32(u8x8_t* u8x8, uint8_t msg, uint8_t arg_int, void* arg_ptr) { UNUSED(u8x8); From 9d6352e92f3f17fd48cc89f8d07716eec292fe0c Mon Sep 17 00:00:00 2001 From: Derek Jamison Date: Thu, 12 Oct 2023 13:39:14 -0700 Subject: [PATCH 781/824] fix #3141: 12-bits is 0xFFF (or 0xFF0) CAME/NICE 12-bit (#3142) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 12-bits is 0xFFF (or 0xFF0) * Mask with 0x7FFFF3FC for 850LM pairing Co-authored-by: あく --- applications/main/subghz/scenes/subghz_scene_set_type.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/applications/main/subghz/scenes/subghz_scene_set_type.c b/applications/main/subghz/scenes/subghz_scene_set_type.c index 8c040cc9cf2..f76bd9e27d2 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_type.c +++ b/applications/main/subghz/scenes/subghz_scene_set_type.c @@ -134,7 +134,7 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { subghz->txrx, "AM650", 315000000, SUBGHZ_PROTOCOL_PRINCETON_NAME, key, 24, 400); break; case SubmenuIndexNiceFlo12bit: - key = (key & 0x0000FFF0) | 0x1; //btn 0x1, 0x2, 0x4 + key = (key & 0x00000FF0) | 0x1; //btn 0x1, 0x2, 0x4 generated_protocol = subghz_txrx_gen_data_protocol( subghz->txrx, "AM650", 433920000, SUBGHZ_PROTOCOL_NICE_FLO_NAME, key, 12); break; @@ -144,7 +144,7 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { subghz->txrx, "AM650", 433920000, SUBGHZ_PROTOCOL_NICE_FLO_NAME, key, 24); break; case SubmenuIndexCAME12bit: - key = (key & 0x0000FFF0) | 0x1; //btn 0x1, 0x2, 0x4 + key = (key & 0x00000FF0) | 0x1; //btn 0x1, 0x2, 0x4 generated_protocol = subghz_txrx_gen_data_protocol( subghz->txrx, "AM650", 433920000, SUBGHZ_PROTOCOL_CAME_NAME, key, 12); break; @@ -198,14 +198,17 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { subghz_txrx_gen_secplus_v1_protocol(subghz->txrx, "AM650", 390000000); break; case SubmenuIndexSecPlus_v2_310_00: + key = (key & 0x7FFFF3FC); // 850LM pairing generated_protocol = subghz_txrx_gen_secplus_v2_protocol( subghz->txrx, "AM650", 310000000, key, 0x68, 0xE500000); break; case SubmenuIndexSecPlus_v2_315_00: + key = (key & 0x7FFFF3FC); // 850LM pairing generated_protocol = subghz_txrx_gen_secplus_v2_protocol( subghz->txrx, "AM650", 315000000, key, 0x68, 0xE500000); break; case SubmenuIndexSecPlus_v2_390_00: + key = (key & 0x7FFFF3FC); // 850LM pairing generated_protocol = subghz_txrx_gen_secplus_v2_protocol( subghz->txrx, "AM650", 390000000, key, 0x68, 0xE500000); break; From e664159188567e670b0b8527f405ae89ab18406f Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Fri, 13 Oct 2023 05:48:16 +0900 Subject: [PATCH 782/824] [FL-3621] Fix double arrows and add proper indication (#3146) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: hedger Co-authored-by: あく --- .../services/gui/modules/byte_input.c | 90 +++++++++++------- .../Common/More_data_placeholder_5x7.png | Bin 0 -> 92 bytes 2 files changed, 54 insertions(+), 36 deletions(-) create mode 100644 assets/icons/Common/More_data_placeholder_5x7.png diff --git a/applications/services/gui/modules/byte_input.c b/applications/services/gui/modules/byte_input.c index e0cbbb779d0..4846bbd8cc9 100644 --- a/applications/services/gui/modules/byte_input.c +++ b/applications/services/gui/modules/byte_input.c @@ -226,16 +226,33 @@ static void byte_input_draw_input(Canvas* canvas, ByteInputModel* model) { canvas_invert_color(canvas); } } else { - canvas_draw_glyph( - canvas, - text_x + 2 + byte_position * 14, - text_y, - byte_input_get_nibble_text(model->bytes[i], true)); - canvas_draw_glyph( - canvas, - text_x + 8 + byte_position * 14, - text_y, - byte_input_get_nibble_text(model->bytes[i], false)); + if(model->first_visible_byte > 0 && i == model->first_visible_byte) { + canvas_draw_icon( + canvas, + text_x + 2 + byte_position * 14, + text_y - 7, + &I_More_data_placeholder_5x7); + } else { + canvas_draw_glyph( + canvas, + text_x + 2 + byte_position * 14, + text_y, + byte_input_get_nibble_text(model->bytes[i], true)); + } + if(model->bytes_count - model->first_visible_byte > max_drawable_bytes && + i == model->first_visible_byte + MIN(model->bytes_count, max_drawable_bytes) - 1) { + canvas_draw_icon( + canvas, + text_x + 8 + byte_position * 14, + text_y - 7, + &I_More_data_placeholder_5x7); + } else { + canvas_draw_glyph( + canvas, + text_x + 8 + byte_position * 14, + text_y, + byte_input_get_nibble_text(model->bytes[i], false)); + } } if(draw_index_line) { @@ -260,14 +277,6 @@ static void byte_input_draw_input(Canvas* canvas, ByteInputModel* model) { snprintf(str, 20, "%u", (model->selected_byte + 1)); canvas_draw_str(canvas, text_x + 75, text_y2, str); } - - if(model->bytes_count - model->first_visible_byte > max_drawable_bytes) { - canvas_draw_icon(canvas, 123, 21, &I_ButtonRightSmall_3x5); - } - - if(model->first_visible_byte > 0) { - canvas_draw_icon(canvas, 1, 21, &I_ButtonLeftSmall_3x5); - } } /** Draw input box (selected view) @@ -306,27 +315,36 @@ static void byte_input_draw_input_selected(Canvas* canvas, ByteInputModel* model byte_input_get_nibble_text(model->bytes[i], false)); canvas_invert_color(canvas); } else { - canvas_draw_glyph( - canvas, - text_x + 2 + byte_position * 14, - text_y, - byte_input_get_nibble_text(model->bytes[i], true)); - canvas_draw_glyph( - canvas, - text_x + 8 + byte_position * 14, - text_y, - byte_input_get_nibble_text(model->bytes[i], false)); + if(model->first_visible_byte > 0 && i == model->first_visible_byte) { + canvas_draw_icon( + canvas, + text_x + 2 + byte_position * 14, + text_y - 7, + &I_More_data_placeholder_5x7); + } else { + canvas_draw_glyph( + canvas, + text_x + 2 + byte_position * 14, + text_y, + byte_input_get_nibble_text(model->bytes[i], true)); + } + if(model->bytes_count - model->first_visible_byte > max_drawable_bytes && + i == model->first_visible_byte + MIN(model->bytes_count, max_drawable_bytes) - 1) { + canvas_draw_icon( + canvas, + text_x + 8 + byte_position * 14, + text_y - 7, + &I_More_data_placeholder_5x7); + } else { + canvas_draw_glyph( + canvas, + text_x + 8 + byte_position * 14, + text_y, + byte_input_get_nibble_text(model->bytes[i], false)); + } } } - if(model->bytes_count - model->first_visible_byte > max_drawable_bytes) { - canvas_draw_icon(canvas, 123, 21, &I_ButtonRightSmall_3x5); - } - - if(model->first_visible_byte > 0) { - canvas_draw_icon(canvas, 1, 21, &I_ButtonLeftSmall_3x5); - } - canvas_invert_color(canvas); } diff --git a/assets/icons/Common/More_data_placeholder_5x7.png b/assets/icons/Common/More_data_placeholder_5x7.png new file mode 100644 index 0000000000000000000000000000000000000000..85025d9f0acc7275b44e8fdc45ea8e2c9293e68e GIT binary patch literal 92 zcmeAS@N?(olHy`uVBq!ia0vp^tU%1p!3-q7SC-!aQfvV}A+G=b{|7Qd4_&SUQi7f? njv*Ddk`fe{44M=i8W Date: Mon, 23 Oct 2023 12:49:16 +0300 Subject: [PATCH 783/824] [FL-3632] FastFAP: human readable error log (#3156) --- lib/flipper_application/elf/elf_file.c | 55 +++++++++++++++++++++----- 1 file changed, 45 insertions(+), 10 deletions(-) diff --git a/lib/flipper_application/elf/elf_file.c b/lib/flipper_application/elf/elf_file.c index 9b8b4c8f5f5..b2c9445ffe3 100644 --- a/lib/flipper_application/elf/elf_file.c +++ b/lib/flipper_application/elf/elf_file.c @@ -613,10 +613,31 @@ static Elf32_Addr elf_address_of_by_hash(ELFFile* elf, uint32_t hash) { return ELF_INVALID_ADDRESS; } +static bool elf_file_find_string_by_hash(ELFFile* elf, uint32_t hash, FuriString* out) { + bool result = false; + + FuriString* symbol_name = furi_string_alloc(); + Elf32_Sym sym; + for(size_t i = 0; i < elf->symbol_count; i++) { + furi_string_reset(symbol_name); + if(elf_read_symbol(elf, i, &sym, symbol_name)) { + if(elf_symbolname_hash(furi_string_get_cstr(symbol_name)) == hash) { + furi_string_set(out, symbol_name); + result = true; + break; + } + } + } + furi_string_free(symbol_name); + + return result; +} + static bool elf_relocate_fast(ELFFile* elf, ELFSection* s) { UNUSED(elf); const uint8_t* start = s->fast_rel->data; const uint8_t version = *start; + bool no_errors = true; if(version != FAST_RELOCATION_VERSION) { FURI_LOG_E(TAG, "Unsupported fast relocation version %d", version); @@ -664,16 +685,30 @@ static bool elf_relocate_fast(ELFFile* elf, ELFSection* s) { } if(address == ELF_INVALID_ADDRESS) { - FURI_LOG_E(TAG, "Failed to resolve address for hash %lX", hash_or_section_index); - return false; - } + FuriString* symbol_name = furi_string_alloc(); + if(elf_file_find_string_by_hash(elf, hash_or_section_index, symbol_name)) { + FURI_LOG_E( + TAG, + "Failed to resolve address for symbol %s (hash %lX)", + furi_string_get_cstr(symbol_name), + hash_or_section_index); + } else { + FURI_LOG_E( + TAG, + "Failed to resolve address for hash %lX (string not found)", + hash_or_section_index); + } + furi_string_free(symbol_name); - for(uint32_t j = 0; j < offsets_count; j++) { - uint32_t offset = *((uint32_t*)start) & 0x00FFFFFF; - start += 3; - // FURI_LOG_I(TAG, " Fast relocation offset %ld: %ld", j, offset); - Elf32_Addr relAddr = ((Elf32_Addr)s->data) + offset; - elf_relocate_symbol(elf, relAddr, type, address); + no_errors = false; + start += 3 * offsets_count; + } else { + for(uint32_t j = 0; j < offsets_count; j++) { + uint32_t offset = *((uint32_t*)start) & 0x00FFFFFF; + start += 3; + Elf32_Addr relAddr = ((Elf32_Addr)s->data) + offset; + elf_relocate_symbol(elf, relAddr, type, address); + } } } @@ -681,7 +716,7 @@ static bool elf_relocate_fast(ELFFile* elf, ELFSection* s) { free(s->fast_rel); s->fast_rel = NULL; - return true; + return no_errors; } static bool elf_relocate_section(ELFFile* elf, ELFSection* section) { From 35c903494c511f41a518dd045be5ab8991fa3991 Mon Sep 17 00:00:00 2001 From: hedger Date: Mon, 23 Oct 2023 13:55:36 +0400 Subject: [PATCH 784/824] [FL-3627, FL-3628, FL-3631] fbt: glob & git improvements (#3151) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fbt: optional shallow submodule checkout * fbt: more git threads by default * fbt: git condition fix * fbt: renamed FBT_SHALLOW to FBT_GIT_SUBMODULE_SHALLOW * github: enabled FBT_GIT_SUBMODULE_SHALLOW in flows * fbt: always compile icons' .c, even if user does not specify a proper source glob; changed glob to require files at user-specified paths to exist * fbt: fail build for missing imports in .faps * fbt: moved STRICT_FAP_IMPORT_CHECK to commandline options; enabled by default * ufbt: enabled STRICT_FAP_IMPORT_CHECK Co-authored-by: あく --- .github/workflows/build.yml | 1 + .github/workflows/build_compact.yml | 1 + .github/workflows/pvs_studio.yml | 1 + .github/workflows/unit_tests.yml | 1 + .github/workflows/updater_test.yml | 1 + fbt | 11 +++++++++-- fbt.cmd | 12 ++++++++++-- scripts/fbt/appmanifest.py | 4 ++++ scripts/fbt_tools/fbt_extapps.py | 15 +++++++++++++-- scripts/fbt_tools/sconsrecursiveglob.py | 7 +++---- scripts/ufbt/commandline.scons | 5 +++++ site_scons/commandline.scons | 5 +++++ 12 files changed, 54 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7a718883790..4c553506650 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,6 +11,7 @@ on: env: DEFAULT_TARGET: f7 FBT_TOOLCHAIN_PATH: /runner/_work + FBT_GIT_SUBMODULE_SHALLOW: 1 jobs: main: diff --git a/.github/workflows/build_compact.yml b/.github/workflows/build_compact.yml index 7556c15acda..c63be24a47d 100644 --- a/.github/workflows/build_compact.yml +++ b/.github/workflows/build_compact.yml @@ -5,6 +5,7 @@ on: env: FBT_TOOLCHAIN_PATH: /runner/_work + FBT_GIT_SUBMODULE_SHALLOW: 1 jobs: compact: diff --git a/.github/workflows/pvs_studio.yml b/.github/workflows/pvs_studio.yml index 24964a3094d..4f650f1b7c3 100644 --- a/.github/workflows/pvs_studio.yml +++ b/.github/workflows/pvs_studio.yml @@ -10,6 +10,7 @@ env: TARGETS: f7 DEFAULT_TARGET: f7 FBT_TOOLCHAIN_PATH: /runner/_work + FBT_GIT_SUBMODULE_SHALLOW: 1 jobs: analyse_c_cpp: diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 794dce37f05..35085ba57a1 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -7,6 +7,7 @@ env: TARGETS: f7 DEFAULT_TARGET: f7 FBT_TOOLCHAIN_PATH: /opt + FBT_GIT_SUBMODULE_SHALLOW: 1 jobs: run_units_on_bench: diff --git a/.github/workflows/updater_test.yml b/.github/workflows/updater_test.yml index f4064e1cca3..9987fdd3244 100644 --- a/.github/workflows/updater_test.yml +++ b/.github/workflows/updater_test.yml @@ -7,6 +7,7 @@ env: TARGETS: f7 DEFAULT_TARGET: f7 FBT_TOOLCHAIN_PATH: /opt + FBT_GIT_SUBMODULE_SHALLOW: 1 jobs: test_updater_on_bench: diff --git a/fbt b/fbt index 26f325d4556..e6133d07b17 100755 --- a/fbt +++ b/fbt @@ -5,7 +5,8 @@ set -eu; # private variables -N_GIT_THREADS="$(getconf _NPROCESSORS_ONLN)"; +N_CORES="$(getconf _NPROCESSORS_ONLN)"; +N_GIT_THREADS="$(($N_CORES * 2))"; SCRIPT_PATH="$(cd "$(dirname "$0")" && pwd -P)"; SCONS_DEFAULT_FLAGS="--warn=target-not-built"; SCONS_EP="python3 -m SCons"; @@ -15,6 +16,7 @@ FBT_NOENV="${FBT_NOENV:-""}"; FBT_NO_SYNC="${FBT_NO_SYNC:-""}"; FBT_TOOLCHAIN_PATH="${FBT_TOOLCHAIN_PATH:-$SCRIPT_PATH}"; FBT_VERBOSE="${FBT_VERBOSE:-""}"; +FBT_GIT_SUBMODULE_SHALLOW="${FBT_GIT_SUBMODULE_SHALLOW:-""}"; if [ -z "$FBT_NOENV" ]; then FBT_VERBOSE="$FBT_VERBOSE" . "$SCRIPT_PATH/scripts/toolchain/fbtenv.sh"; @@ -29,7 +31,12 @@ if [ -z "$FBT_NO_SYNC" ]; then echo "\".git\" directory not found, please clone repo via \"git clone\""; exit 1; fi - git submodule update --init --jobs "$N_GIT_THREADS"; + _FBT_CLONE_FLAGS="--jobs $N_GIT_THREADS"; + if [ ! -z "$FBT_GIT_SUBMODULE_SHALLOW" ]; then + _FBT_CLONE_FLAGS="$_FBT_CLONE_FLAGS --depth 1"; + fi + + git submodule update --init --recursive $_FBT_CLONE_FLAGS; fi $SCONS_EP $SCONS_DEFAULT_FLAGS "$@" diff --git a/fbt.cmd b/fbt.cmd index 03e4ec3d094..20432be1c53 100644 --- a/fbt.cmd +++ b/fbt.cmd @@ -4,10 +4,18 @@ call "%~dp0scripts\toolchain\fbtenv.cmd" env set SCONS_EP=python -m SCons if [%FBT_NO_SYNC%] == [] ( + set _FBT_CLONE_FLAGS=--jobs %NUMBER_OF_PROCESSORS% + if not [%FBT_GIT_SUBMODULE_SHALLOW%] == [] ( + set _FBT_CLONE_FLAGS=%_FBT_CLONE_FLAGS% --depth 1 + ) if exist ".git" ( - git submodule update --init --depth 1 --jobs %NUMBER_OF_PROCESSORS% + git submodule update --init --recursive %_FBT_CLONE_FLAGS% + if %ERRORLEVEL% neq 0 ( + echo Failed to update submodules, set FBT_NO_SYNC to skip + exit /b 1 + ) ) else ( - echo Not in a git repo, please clone with "git clone" + echo .git not found, please clone repo with "git clone" exit /b 1 ) ) diff --git a/scripts/fbt/appmanifest.py b/scripts/fbt/appmanifest.py index ff49707b1d6..1a6cae9b17c 100644 --- a/scripts/fbt/appmanifest.py +++ b/scripts/fbt/appmanifest.py @@ -100,6 +100,10 @@ def supports_hardware_target(self, target: str): def is_default_deployable(self): return self.apptype != FlipperAppType.DEBUG and self.fap_category != "Examples" + @property + def do_strict_import_checks(self): + return self.apptype != FlipperAppType.PLUGIN + def __post_init__(self): if self.apptype == FlipperAppType.PLUGIN: self.stack_size = 0 diff --git a/scripts/fbt_tools/fbt_extapps.py b/scripts/fbt_tools/fbt_extapps.py index bc8f9d5df74..963429f2451 100644 --- a/scripts/fbt_tools/fbt_extapps.py +++ b/scripts/fbt_tools/fbt_extapps.py @@ -42,6 +42,7 @@ def __init__(self, env, app): self.ext_apps_work_dir = env["EXT_APPS_WORK_DIR"] self.app_work_dir = self.get_app_work_dir(env, app) self.app_alias = f"fap_{self.app.appid}" + self.icons_src = None self.externally_built_files = [] self.private_libs = [] @@ -93,6 +94,7 @@ def _compile_assets(self): ) self.app_env.Alias("_fap_icons", fap_icons) self.fw_env.Append(_APP_ICONS=[fap_icons]) + self.icons_src = next(filter(lambda n: n.path.endswith(".c"), fap_icons)) def _build_private_libs(self): for lib_def in self.app.fap_private_libs: @@ -160,6 +162,10 @@ def _build_app(self): if not app_sources: raise UserError(f"No source files found for {self.app.appid}") + # Ensure that icons are included in the build, regardless of user-configured sources + if self.icons_src and not self.icons_src in app_sources: + app_sources.append(self.icons_src) + ## Uncomment for debug # print(f"App sources for {self.app.appid}: {list(f.path for f in app_sources)}") @@ -180,7 +186,9 @@ def _build_app(self): self.app._assets_dirs.append(self.app_work_dir.Dir("assets")) app_artifacts.validator = self.app_env.ValidateAppImports( - app_artifacts.compact + app_artifacts.compact, + _CHECK_APP=self.app.do_strict_import_checks + and self.app_env.get("STRICT_FAP_IMPORT_CHECK"), )[0] if self.app.apptype == FlipperAppType.PLUGIN: @@ -300,7 +308,10 @@ def validate_app_imports(target, source, env): + fg.brightmagenta(f"{disabled_api_syms}") + fg.brightyellow(")") ) - SCons.Warnings.warn(SCons.Warnings.LinkWarning, warning_msg), + if env.get("_CHECK_APP"): + raise UserError(warning_msg) + else: + SCons.Warnings.warn(SCons.Warnings.LinkWarning, warning_msg), def GetExtAppByIdOrPath(env, app_dir): diff --git a/scripts/fbt_tools/sconsrecursiveglob.py b/scripts/fbt_tools/sconsrecursiveglob.py index 38aa9a839fe..e7eb8fb7297 100644 --- a/scripts/fbt_tools/sconsrecursiveglob.py +++ b/scripts/fbt_tools/sconsrecursiveglob.py @@ -20,10 +20,9 @@ def GlobRecursive(env, pattern, node=".", exclude=[]): source=True, exclude=exclude, ) - # Otherwise, just check if that's an existing file path - # NB: still creates "virtual" nodes as part of existence check - elif (file_node := node.File(pattern)).exists() or file_node.rexists(): - results.append(file_node) + # Otherwise, just assume that file at path exists + else: + results.append(node.File(pattern)) # print(f"Glob result for {pattern} from {node}: {results}") return results diff --git a/scripts/ufbt/commandline.scons b/scripts/ufbt/commandline.scons index 99c34c35da3..a3ba7e08dae 100644 --- a/scripts/ufbt/commandline.scons +++ b/scripts/ufbt/commandline.scons @@ -88,6 +88,11 @@ vars.AddVariables( "CDC Port of Flipper to use, if multiple are connected", "auto", ), + BoolVariable( + "STRICT_FAP_IMPORT_CHECK", + help="Enable strict import check for .faps", + default=True, + ), ) Return("vars") diff --git a/site_scons/commandline.scons b/site_scons/commandline.scons index f75d5e0e4da..2d2abd5c676 100644 --- a/site_scons/commandline.scons +++ b/site_scons/commandline.scons @@ -269,6 +269,11 @@ vars.AddVariables( "clangd", ], ), + BoolVariable( + "STRICT_FAP_IMPORT_CHECK", + help="Enable strict import check for .faps", + default=True, + ), ) Return("vars") From d92b0a82cc4c79acebbcee6b8e650fbc0e1aa035 Mon Sep 17 00:00:00 2001 From: gornekich Date: Tue, 24 Oct 2023 07:08:09 +0400 Subject: [PATCH 785/824] NFC refactoring (#3050) "A long time ago in a galaxy far, far away...." we started NFC subsystem refactoring. Starring: - @gornekich - NFC refactoring project lead, architect, senior developer - @gsurkov - architect, senior developer - @RebornedBrain - senior developer Supporting roles: - @skotopes, @DrZlo13, @hedger - general architecture advisors, code review - @Astrrra, @doomwastaken, @Hellitron, @ImagineVagon333 - quality assurance Special thanks: @bettse, @pcunning, @nxv, @noproto, @AloneLiberty and everyone else who has been helping us all this time and contributing valuable knowledges, ideas and source code. --- .github/CODEOWNERS | 1 - .github/workflows/unit_tests.yml | 4 +- .pvsoptions | 2 +- applications/debug/unit_tests/nfc/nfc_test.c | 910 +- .../debug/unit_tests/nfc/nfc_transport.c | 458 + applications/main/nfc/application.fam | 50 + .../main/nfc/helpers/mf_classic_key_cache.c | 212 + .../main/nfc/helpers/mf_classic_key_cache.h | 31 + .../main/nfc/helpers/mf_ultralight_auth.c | 58 + .../main/nfc/helpers/mf_ultralight_auth.h | 35 + applications/main/nfc/helpers/mf_user_dict.c | 83 + applications/main/nfc/helpers/mf_user_dict.h | 23 + .../main/nfc/helpers/mfkey32_logger.c | 173 + .../main/nfc/helpers/mfkey32_logger.h | 25 + .../main/nfc/helpers/nfc_custom_event.h | 26 +- .../main/nfc/helpers/nfc_supported_cards.c | 147 + .../main/nfc/helpers/nfc_supported_cards.h | 50 + .../helpers/protocol_support/felica/felica.c | 108 + .../helpers/protocol_support/felica/felica.h | 5 + .../protocol_support/felica/felica_render.c | 19 + .../protocol_support/felica/felica_render.h | 10 + .../iso14443_3a/iso14443_3a.c | 145 + .../iso14443_3a/iso14443_3a.h | 5 + .../iso14443_3a/iso14443_3a_render.c | 34 + .../iso14443_3a/iso14443_3a_render.h | 16 + .../iso14443_3b/iso14443_3b.c | 113 + .../iso14443_3b/iso14443_3b.h | 5 + .../iso14443_3b/iso14443_3b_i.h | 7 + .../iso14443_3b/iso14443_3b_render.c | 78 + .../iso14443_3b/iso14443_3b_render.h | 10 + .../iso14443_4a/iso14443_4a.c | 149 + .../iso14443_4a/iso14443_4a.h | 5 + .../iso14443_4a/iso14443_4a_i.h | 7 + .../iso14443_4a/iso14443_4a_render.c | 84 + .../iso14443_4a/iso14443_4a_render.h | 14 + .../iso14443_4b/iso14443_4b.c | 118 + .../iso14443_4b/iso14443_4b.h | 5 + .../iso14443_4b/iso14443_4b_render.c | 10 + .../iso14443_4b/iso14443_4b_render.h | 10 + .../protocol_support/iso15693_3/iso15693_3.c | 144 + .../protocol_support/iso15693_3/iso15693_3.h | 5 + .../iso15693_3/iso15693_3_render.c | 92 + .../iso15693_3/iso15693_3_render.h | 14 + .../protocol_support/mf_classic/mf_classic.c | 252 + .../protocol_support/mf_classic/mf_classic.h | 5 + .../mf_classic/mf_classic_render.c | 30 + .../mf_classic/mf_classic_render.h | 12 + .../protocol_support/mf_desfire/mf_desfire.c | 120 + .../protocol_support/mf_desfire/mf_desfire.h | 5 + .../mf_desfire/mf_desfire_render.c | 249 + .../mf_desfire/mf_desfire_render.h | 34 + .../mf_ultralight/mf_ultralight.c | 196 + .../mf_ultralight/mf_ultralight.h | 5 + .../mf_ultralight/mf_ultralight_render.c | 45 + .../mf_ultralight/mf_ultralight_render.h | 14 + .../protocol_support/nfc_protocol_support.c | 786 ++ .../protocol_support/nfc_protocol_support.h | 113 + .../nfc_protocol_support_base.h | 116 + .../nfc_protocol_support_common.h | 36 + .../nfc_protocol_support_defs.c | 45 + .../nfc_protocol_support_defs.h | 12 + .../nfc_protocol_support_gui_common.c | 42 + .../nfc_protocol_support_gui_common.h | 85 + .../nfc_protocol_support_render_common.h | 13 + .../nfc/helpers/protocol_support/slix/slix.c | 141 + .../nfc/helpers/protocol_support/slix/slix.h | 5 + .../protocol_support/slix/slix_render.c | 74 + .../protocol_support/slix/slix_render.h | 7 + .../helpers/protocol_support/st25tb/st25tb.c | 103 + .../helpers/protocol_support/st25tb/st25tb.h | 5 + .../protocol_support/st25tb/st25tb_render.c | 22 + .../protocol_support/st25tb/st25tb_render.h | 10 + applications/main/nfc/nfc.c | 323 - applications/main/nfc/nfc.h | 3 - applications/main/nfc/nfc_app.c | 499 + applications/main/nfc/nfc_app.h | 19 + applications/main/nfc/nfc_app_i.h | 192 + applications/main/nfc/nfc_cli.c | 159 +- applications/main/nfc/nfc_i.h | 118 - .../nfc/plugins/supported_cards/all_in_one.c | 107 + .../main/nfc/plugins/supported_cards/myki.c | 116 + .../nfc_supported_card_plugin.h | 94 + .../main/nfc/plugins/supported_cards/opal.c | 233 + .../nfc/plugins/supported_cards/plantain.c | 219 + .../main/nfc/plugins/supported_cards/troika.c | 214 + .../nfc/plugins/supported_cards/two_cities.c | 189 + .../main/nfc/scenes/nfc_scene_config.h | 107 +- .../main/nfc/scenes/nfc_scene_debug.c | 18 +- .../main/nfc/scenes/nfc_scene_delete.c | 41 +- .../nfc/scenes/nfc_scene_delete_success.c | 10 +- .../main/nfc/scenes/nfc_scene_detect.c | 59 + .../main/nfc/scenes/nfc_scene_detect_reader.c | 103 - .../main/nfc/scenes/nfc_scene_device_info.c | 87 - .../nfc/scenes/nfc_scene_dict_not_found.c | 52 - .../main/nfc/scenes/nfc_scene_emulate.c | 13 + .../scenes/nfc_scene_emulate_apdu_sequence.c | 34 - .../main/nfc/scenes/nfc_scene_emulate_uid.c | 144 - .../main/nfc/scenes/nfc_scene_emv_menu.c | 46 - .../nfc/scenes/nfc_scene_emv_read_success.c | 113 - .../main/nfc/scenes/nfc_scene_exit_confirm.c | 14 +- .../main/nfc/scenes/nfc_scene_extra_actions.c | 60 +- .../main/nfc/scenes/nfc_scene_field.c | 12 +- .../main/nfc/scenes/nfc_scene_file_select.c | 22 +- .../main/nfc/scenes/nfc_scene_generate_info.c | 65 +- applications/main/nfc/scenes/nfc_scene_info.c | 13 + .../nfc/scenes/nfc_scene_mf_classic_data.c | 106 - .../nfc_scene_mf_classic_detect_reader.c | 154 + .../scenes/nfc_scene_mf_classic_dict_attack.c | 348 +- .../nfc/scenes/nfc_scene_mf_classic_emulate.c | 69 - .../nfc/scenes/nfc_scene_mf_classic_keys.c | 76 +- .../scenes/nfc_scene_mf_classic_keys_add.c | 56 +- .../scenes/nfc_scene_mf_classic_keys_delete.c | 62 +- .../scenes/nfc_scene_mf_classic_keys_list.c | 103 +- ...nfc_scene_mf_classic_keys_warn_duplicate.c | 26 +- .../nfc/scenes/nfc_scene_mf_classic_menu.c | 82 - .../nfc_scene_mf_classic_mfkey_complete.c | 58 + .../nfc_scene_mf_classic_mfkey_nonces_info.c | 72 + .../nfc_scene_mf_classic_read_success.c | 82 - .../nfc/scenes/nfc_scene_mf_classic_update.c | 98 - .../nfc_scene_mf_classic_update_initial.c | 144 + ..._scene_mf_classic_update_initial_success.c | 43 + .../nfc_scene_mf_classic_update_success.c | 44 - .../nfc/scenes/nfc_scene_mf_classic_write.c | 92 - .../scenes/nfc_scene_mf_classic_write_fail.c | 58 - .../nfc_scene_mf_classic_write_initial.c | 146 + .../nfc_scene_mf_classic_write_initial_fail.c | 62 + ...c_scene_mf_classic_write_initial_success.c | 43 + .../nfc_scene_mf_classic_write_success.c | 44 - .../scenes/nfc_scene_mf_classic_wrong_card.c | 28 +- .../nfc/scenes/nfc_scene_mf_desfire_app.c | 117 +- .../nfc/scenes/nfc_scene_mf_desfire_data.c | 104 - .../nfc/scenes/nfc_scene_mf_desfire_menu.c | 72 - .../scenes/nfc_scene_mf_desfire_more_info.c | 112 + .../nfc_scene_mf_desfire_read_success.c | 101 - .../nfc_scene_mf_ultralight_capture_pass.c | 67 + .../nfc/scenes/nfc_scene_mf_ultralight_data.c | 32 - .../scenes/nfc_scene_mf_ultralight_emulate.c | 74 - .../nfc_scene_mf_ultralight_key_input.c | 12 +- .../nfc/scenes/nfc_scene_mf_ultralight_menu.c | 89 - .../nfc_scene_mf_ultralight_read_auth.c | 116 - ...nfc_scene_mf_ultralight_read_auth_result.c | 116 - .../nfc_scene_mf_ultralight_read_success.c | 84 - .../nfc_scene_mf_ultralight_unlock_auto.c | 64 - .../nfc_scene_mf_ultralight_unlock_menu.c | 27 +- .../nfc_scene_mf_ultralight_unlock_warn.c | 45 +- .../nfc/scenes/nfc_scene_mfkey_complete.c | 49 - .../nfc/scenes/nfc_scene_mfkey_nonces_info.c | 55 - .../main/nfc/scenes/nfc_scene_more_info.c | 13 + .../main/nfc/scenes/nfc_scene_nfc_data_info.c | 309 - .../main/nfc/scenes/nfc_scene_nfca_menu.c | 68 - .../nfc/scenes/nfc_scene_nfca_read_success.c | 73 - .../main/nfc/scenes/nfc_scene_nfcv_emulate.c | 169 - .../nfc/scenes/nfc_scene_nfcv_key_input.c | 48 - .../main/nfc/scenes/nfc_scene_nfcv_menu.c | 68 - .../nfc/scenes/nfc_scene_nfcv_read_success.c | 92 - .../main/nfc/scenes/nfc_scene_nfcv_sniff.c | 155 - .../main/nfc/scenes/nfc_scene_nfcv_unlock.c | 154 - .../nfc/scenes/nfc_scene_nfcv_unlock_menu.c | 60 - applications/main/nfc/scenes/nfc_scene_read.c | 118 +- .../nfc/scenes/nfc_scene_read_card_success.c | 61 - .../nfc/scenes/nfc_scene_read_card_type.c | 85 - .../main/nfc/scenes/nfc_scene_read_menu.c | 13 + .../main/nfc/scenes/nfc_scene_read_success.c | 13 + .../nfc/scenes/nfc_scene_restore_original.c | 14 +- .../nfc_scene_restore_original_confirm.c | 16 +- .../main/nfc/scenes/nfc_scene_retry_confirm.c | 19 +- applications/main/nfc/scenes/nfc_scene_rpc.c | 88 +- .../main/nfc/scenes/nfc_scene_save_name.c | 88 +- .../main/nfc/scenes/nfc_scene_save_success.c | 13 +- .../main/nfc/scenes/nfc_scene_saved_menu.c | 181 +- .../nfc/scenes/nfc_scene_select_protocol.c | 67 + .../main/nfc/scenes/nfc_scene_set_atqa.c | 37 +- .../main/nfc/scenes/nfc_scene_set_sak.c | 36 +- .../main/nfc/scenes/nfc_scene_set_type.c | 74 +- .../main/nfc/scenes/nfc_scene_set_uid.c | 53 +- .../main/nfc/scenes/nfc_scene_start.c | 29 +- .../nfc/scenes/nfc_scene_supported_card.c | 50 + applications/main/nfc/views/detect_reader.c | 5 +- applications/main/nfc/views/dict_attack.c | 234 +- applications/main/nfc/views/dict_attack.h | 48 +- .../scenes/desktop_scene_pin_timeout.c | 1 - assets/unit_tests/nfc/Ntag213_locked.nfc | 66 + assets/unit_tests/nfc/Ntag215.nfc | 156 + assets/unit_tests/nfc/Ntag216.nfc | 252 + assets/unit_tests/nfc/Ultralight_11.nfc | 41 + assets/unit_tests/nfc/Ultralight_21.nfc | 62 + documentation/file_formats/NfcFileFormats.md | 120 +- firmware.scons | 9 + firmware/targets/f18/api_symbols.csv | 109 +- firmware/targets/f18/target.json | 15 +- firmware/targets/f7/api_symbols.csv | 836 +- firmware/targets/f7/furi_hal/furi_hal_nfc.c | 1252 +-- firmware/targets/f7/furi_hal/furi_hal_nfc.h | 431 - .../targets/f7/furi_hal/furi_hal_nfc_event.c | 116 + .../targets/f7/furi_hal/furi_hal_nfc_felica.c | 69 + firmware/targets/f7/furi_hal/furi_hal_nfc_i.h | 191 + .../targets/f7/furi_hal/furi_hal_nfc_irq.c | 28 + .../f7/furi_hal/furi_hal_nfc_iso14443a.c | 356 + .../f7/furi_hal/furi_hal_nfc_iso14443b.c | 108 + .../f7/furi_hal/furi_hal_nfc_iso15693.c | 463 + .../targets/f7/furi_hal/furi_hal_nfc_tech_i.h | 167 + .../targets/f7/furi_hal/furi_hal_nfc_timer.c | 229 + firmware/targets/f7/target.json | 5 +- .../targets/furi_hal_include/furi_hal_nfc.h | 457 + lib/ReadMe.md | 1 - lib/SConscript | 4 +- lib/ST25RFAL002/SConscript | 19 - lib/ST25RFAL002/doc/Release_Notes.html | 354 - .../doc/ST25R3916_MisraComplianceReport.html | 8867 ----------------- lib/ST25RFAL002/doc/_htmresc/st_logo.png | Bin 18616 -> 0 bytes lib/ST25RFAL002/doc/rfal.chm | Bin 2338282 -> 0 bytes lib/ST25RFAL002/include/rfal_analogConfig.h | 435 - lib/ST25RFAL002/include/rfal_chip.h | 287 - lib/ST25RFAL002/include/rfal_crc.h | 74 - lib/ST25RFAL002/include/rfal_dpo.h | 207 - lib/ST25RFAL002/include/rfal_iso15693_2.h | 206 - lib/ST25RFAL002/include/rfal_isoDep.h | 1092 -- lib/ST25RFAL002/include/rfal_nfc.h | 425 - lib/ST25RFAL002/include/rfal_nfcDep.h | 830 -- lib/ST25RFAL002/include/rfal_nfca.h | 497 - lib/ST25RFAL002/include/rfal_nfcb.h | 425 - lib/ST25RFAL002/include/rfal_nfcf.h | 403 - lib/ST25RFAL002/include/rfal_nfcv.h | 923 -- lib/ST25RFAL002/include/rfal_rf.h | 1722 ---- lib/ST25RFAL002/include/rfal_st25tb.h | 340 - lib/ST25RFAL002/include/rfal_st25xv.h | 844 -- lib/ST25RFAL002/include/rfal_t1t.h | 178 - lib/ST25RFAL002/include/rfal_t2t.h | 150 - lib/ST25RFAL002/include/rfal_t4t.h | 395 - lib/ST25RFAL002/platform.c | 103 - lib/ST25RFAL002/platform.h | 188 - lib/ST25RFAL002/source/custom_analog_config.c | 777 -- lib/ST25RFAL002/source/rfal_analogConfig.c | 476 - lib/ST25RFAL002/source/rfal_crc.c | 82 - lib/ST25RFAL002/source/rfal_dpo.c | 232 - lib/ST25RFAL002/source/rfal_iso15693_2.c | 514 - lib/ST25RFAL002/source/rfal_isoDep.c | 3032 ------ lib/ST25RFAL002/source/rfal_nfc.c | 2118 ---- lib/ST25RFAL002/source/rfal_nfcDep.c | 2730 ----- lib/ST25RFAL002/source/rfal_nfca.c | 886 -- lib/ST25RFAL002/source/rfal_nfcb.c | 519 - lib/ST25RFAL002/source/rfal_nfcf.c | 585 -- lib/ST25RFAL002/source/rfal_nfcv.c | 1057 -- lib/ST25RFAL002/source/rfal_st25tb.c | 563 -- lib/ST25RFAL002/source/rfal_st25xv.c | 818 -- lib/ST25RFAL002/source/rfal_t1t.c | 233 - lib/ST25RFAL002/source/rfal_t2t.c | 253 - lib/ST25RFAL002/source/rfal_t4t.c | 397 - .../source/st25r3916/rfal_analogConfigTbl.h | 1477 --- .../source/st25r3916/rfal_dpoTbl.h | 68 - .../source/st25r3916/rfal_features.h | 109 - .../source/st25r3916/rfal_rfst25r3916.c | 4815 --------- lib/ST25RFAL002/source/st25r3916/st25r3916.c | 792 -- lib/ST25RFAL002/source/st25r3916/st25r3916.h | 669 -- .../source/st25r3916/st25r3916_aat.c | 366 - .../source/st25r3916/st25r3916_aat.h | 109 - .../source/st25r3916/st25r3916_com.c | 618 -- .../source/st25r3916/st25r3916_irq.c | 231 - .../source/st25r3916/st25r3916_irq.h | 296 - .../source/st25r3916/st25r3916_led.c | 148 - .../source/st25r3916/st25r3916_led.h | 151 - lib/ST25RFAL002/st_errno.h | 158 - lib/ST25RFAL002/timer.c | 105 - lib/ST25RFAL002/timer.h | 125 - lib/ST25RFAL002/utils.h | 100 - lib/digital_signal/SConscript | 1 + lib/digital_signal/digital_sequence.c | 381 + lib/digital_signal/digital_sequence.h | 112 + lib/digital_signal/digital_signal.c | 655 +- lib/digital_signal/digital_signal.h | 157 +- lib/digital_signal/digital_signal_i.h | 23 + .../presets/nfc/iso14443_3a_signal.c | 139 + .../presets/nfc/iso14443_3a_signal.h | 51 + .../presets/nfc/iso15693_signal.c | 204 + .../presets/nfc/iso15693_signal.h | 73 + lib/drivers/st25r3916.c | 81 + lib/drivers/st25r3916.h | 113 + lib/drivers/st25r3916_reg.c | 257 + .../st25r3916_reg.h} | 826 +- lib/misc.scons | 3 + lib/nfc/SConscript | 47 +- lib/nfc/helpers/felica_crc.c | 52 + lib/nfc/helpers/felica_crc.h | 22 + lib/nfc/helpers/iso13239_crc.c | 62 + lib/nfc/helpers/iso13239_crc.h | 27 + lib/nfc/helpers/iso14443_4_layer.c | 64 + lib/nfc/helpers/iso14443_4_layer.h | 29 + lib/nfc/helpers/iso14443_crc.c | 57 + lib/nfc/helpers/iso14443_crc.h | 27 + lib/nfc/helpers/mf_classic_dict.c | 346 - lib/nfc/helpers/mf_classic_dict.h | 107 - lib/nfc/helpers/mfkey32.c | 228 - lib/nfc/helpers/mfkey32.h | 34 - lib/nfc/helpers/nfc_data_generator.c | 566 ++ lib/nfc/helpers/nfc_data_generator.h | 40 + lib/nfc/helpers/nfc_debug_log.c | 71 - lib/nfc/helpers/nfc_debug_log.h | 17 - lib/nfc/helpers/nfc_debug_pcap.c | 130 - lib/nfc/helpers/nfc_debug_pcap.h | 17 - lib/nfc/helpers/nfc_dict.c | 270 + lib/nfc/helpers/nfc_dict.h | 103 + lib/nfc/helpers/nfc_generators.c | 528 - lib/nfc/helpers/nfc_generators.h | 14 - lib/nfc/{protocols => helpers}/nfc_util.c | 0 lib/nfc/{protocols => helpers}/nfc_util.h | 0 lib/nfc/helpers/reader_analyzer.c | 265 - lib/nfc/helpers/reader_analyzer.h | 43 - lib/nfc/nfc.c | 649 ++ lib/nfc/nfc.h | 364 + lib/nfc/nfc_common.h | 22 + lib/nfc/nfc_device.c | 1833 +--- lib/nfc/nfc_device.h | 333 +- lib/nfc/nfc_device_i.c | 37 + lib/nfc/nfc_device_i.h | 46 + lib/nfc/nfc_listener.c | 144 + lib/nfc/nfc_listener.h | 94 + lib/nfc/nfc_poller.c | 211 + lib/nfc/nfc_poller.h | 104 + lib/nfc/nfc_scanner.c | 267 + lib/nfc/nfc_scanner.h | 97 + lib/nfc/nfc_types.c | 69 - lib/nfc/nfc_types.h | 19 - lib/nfc/nfc_worker.c | 1325 --- lib/nfc/nfc_worker.h | 108 - lib/nfc/nfc_worker_i.h | 59 - lib/nfc/parsers/all_in_one.c | 103 - lib/nfc/parsers/all_in_one.h | 9 - lib/nfc/parsers/nfc_supported_card.c | 82 - lib/nfc/parsers/nfc_supported_card.h | 47 - lib/nfc/parsers/opal.c | 204 - lib/nfc/parsers/opal.h | 5 - lib/nfc/parsers/plantain_4k_parser.c | 124 - lib/nfc/parsers/plantain_4k_parser.h | 9 - lib/nfc/parsers/plantain_parser.c | 97 - lib/nfc/parsers/plantain_parser.h | 11 - lib/nfc/parsers/troika_4k_parser.c | 106 - lib/nfc/parsers/troika_4k_parser.h | 9 - lib/nfc/parsers/troika_parser.c | 88 - lib/nfc/parsers/troika_parser.h | 9 - lib/nfc/parsers/two_cities.c | 146 - lib/nfc/parsers/two_cities.h | 9 - lib/nfc/protocols/crypto1.c | 128 - lib/nfc/protocols/emv.c | 444 - lib/nfc/protocols/emv.h | 80 - lib/nfc/protocols/felica/felica.c | 147 + lib/nfc/protocols/felica/felica.h | 77 + lib/nfc/protocols/felica/felica_poller.c | 117 + lib/nfc/protocols/felica/felica_poller.h | 30 + lib/nfc/protocols/felica/felica_poller_defs.h | 13 + lib/nfc/protocols/felica/felica_poller_i.c | 128 + lib/nfc/protocols/felica/felica_poller_i.h | 60 + lib/nfc/protocols/iso14443_3a/iso14443_3a.c | 186 + lib/nfc/protocols/iso14443_3a/iso14443_3a.h | 107 + .../iso14443_3a/iso14443_3a_device_defs.h | 5 + .../iso14443_3a/iso14443_3a_listener.c | 127 + .../iso14443_3a/iso14443_3a_listener.h | 31 + .../iso14443_3a/iso14443_3a_listener_defs.h | 5 + .../iso14443_3a/iso14443_3a_listener_i.c | 73 + .../iso14443_3a/iso14443_3a_listener_i.h | 42 + .../iso14443_3a/iso14443_3a_poller.c | 128 + .../iso14443_3a/iso14443_3a_poller.h | 42 + .../iso14443_3a/iso14443_3a_poller_defs.h | 5 + .../iso14443_3a/iso14443_3a_poller_i.c | 293 + .../iso14443_3a/iso14443_3a_poller_i.h | 81 + .../iso14443_3a/iso14443_3a_poller_sync_api.c | 58 + .../iso14443_3a/iso14443_3a_poller_sync_api.h | 14 + lib/nfc/protocols/iso14443_3b/iso14443_3b.c | 223 + lib/nfc/protocols/iso14443_3b/iso14443_3b.h | 83 + .../iso14443_3b/iso14443_3b_device_defs.h | 5 + lib/nfc/protocols/iso14443_3b/iso14443_3b_i.c | 1 + lib/nfc/protocols/iso14443_3b/iso14443_3b_i.h | 37 + .../iso14443_3b/iso14443_3b_poller.c | 121 + .../iso14443_3b/iso14443_3b_poller.h | 30 + .../iso14443_3b/iso14443_3b_poller_defs.h | 5 + .../iso14443_3b/iso14443_3b_poller_i.c | 194 + .../iso14443_3b/iso14443_3b_poller_i.h | 49 + lib/nfc/protocols/iso14443_4a/iso14443_4a.c | 300 + lib/nfc/protocols/iso14443_4a/iso14443_4a.h | 73 + .../iso14443_4a/iso14443_4a_device_defs.h | 5 + lib/nfc/protocols/iso14443_4a/iso14443_4a_i.c | 71 + lib/nfc/protocols/iso14443_4a/iso14443_4a_i.h | 42 + .../iso14443_4a/iso14443_4a_listener.c | 99 + .../iso14443_4a/iso14443_4a_listener.h | 29 + .../iso14443_4a/iso14443_4a_listener_defs.h | 5 + .../iso14443_4a/iso14443_4a_listener_i.c | 32 + .../iso14443_4a/iso14443_4a_listener_i.h | 36 + .../iso14443_4a/iso14443_4a_poller.c | 154 + .../iso14443_4a/iso14443_4a_poller.h | 29 + .../iso14443_4a/iso14443_4a_poller_defs.h | 5 + .../iso14443_4a/iso14443_4a_poller_i.c | 83 + .../iso14443_4a/iso14443_4a_poller_i.h | 62 + lib/nfc/protocols/iso14443_4b/iso14443_4b.c | 94 + lib/nfc/protocols/iso14443_4b/iso14443_4b.h | 46 + .../iso14443_4b/iso14443_4b_device_defs.h | 5 + lib/nfc/protocols/iso14443_4b/iso14443_4b_i.c | 18 + lib/nfc/protocols/iso14443_4b/iso14443_4b_i.h | 9 + .../iso14443_4b/iso14443_4b_poller.c | 138 + .../iso14443_4b/iso14443_4b_poller.h | 29 + .../iso14443_4b/iso14443_4b_poller_defs.h | 5 + .../iso14443_4b/iso14443_4b_poller_i.c | 45 + .../iso14443_4b/iso14443_4b_poller_i.h | 56 + lib/nfc/protocols/iso15693_3/iso15693_3.c | 358 + lib/nfc/protocols/iso15693_3/iso15693_3.h | 163 + .../iso15693_3/iso15693_3_device_defs.h | 5 + lib/nfc/protocols/iso15693_3/iso15693_3_i.c | 263 + lib/nfc/protocols/iso15693_3/iso15693_3_i.h | 58 + .../iso15693_3/iso15693_3_listener.c | 110 + .../iso15693_3/iso15693_3_listener.h | 30 + .../iso15693_3/iso15693_3_listener_defs.h | 5 + .../iso15693_3/iso15693_3_listener_i.c | 883 ++ .../iso15693_3/iso15693_3_listener_i.h | 70 + .../protocols/iso15693_3/iso15693_3_poller.c | 122 + .../protocols/iso15693_3/iso15693_3_poller.h | 29 + .../iso15693_3/iso15693_3_poller_defs.h | 5 + .../iso15693_3/iso15693_3_poller_i.c | 303 + .../iso15693_3/iso15693_3_poller_i.h | 70 + lib/nfc/protocols/mf_classic/crypto1.c | 172 + lib/nfc/protocols/{ => mf_classic}/crypto1.h | 31 +- lib/nfc/protocols/mf_classic/mf_classic.c | 740 ++ lib/nfc/protocols/mf_classic/mf_classic.h | 235 + .../mf_classic/mf_classic_listener.c | 656 ++ .../mf_classic/mf_classic_listener.h | 27 + .../mf_classic/mf_classic_listener_defs.h | 13 + .../mf_classic/mf_classic_listener_i.h | 62 + .../protocols/mf_classic/mf_classic_poller.c | 951 ++ .../protocols/mf_classic/mf_classic_poller.h | 109 + .../mf_classic/mf_classic_poller_defs.h | 13 + .../mf_classic/mf_classic_poller_i.c | 386 + .../mf_classic/mf_classic_poller_i.h | 205 + .../mf_classic/mf_classic_poller_sync_api.c | 524 + .../mf_classic/mf_classic_poller_sync_api.h | 59 + lib/nfc/protocols/mf_desfire/mf_desfire.c | 290 + lib/nfc/protocols/mf_desfire/mf_desfire.h | 191 + lib/nfc/protocols/mf_desfire/mf_desfire_i.c | 790 ++ lib/nfc/protocols/mf_desfire/mf_desfire_i.h | 140 + .../protocols/mf_desfire/mf_desfire_poller.c | 243 + .../protocols/mf_desfire/mf_desfire_poller.h | 31 + .../mf_desfire/mf_desfire_poller_defs.h | 5 + .../mf_desfire/mf_desfire_poller_i.c | 474 + .../mf_desfire/mf_desfire_poller_i.h | 126 + .../protocols/mf_ultralight/mf_ultralight.c | 627 ++ .../protocols/mf_ultralight/mf_ultralight.h | 229 + .../mf_ultralight/mf_ultralight_listener.c | 772 ++ .../mf_ultralight/mf_ultralight_listener.h | 28 + .../mf_ultralight_listener_defs.h | 13 + .../mf_ultralight/mf_ultralight_listener_i.c | 577 ++ .../mf_ultralight/mf_ultralight_listener_i.h | 123 + .../mf_ultralight/mf_ultralight_poller.c | 591 ++ .../mf_ultralight/mf_ultralight_poller.h | 41 + .../mf_ultralight/mf_ultralight_poller_defs.h | 5 + .../mf_ultralight/mf_ultralight_poller_i.c | 308 + .../mf_ultralight/mf_ultralight_poller_i.h | 148 + .../mf_ultralight_poller_sync_api.c | 287 + .../mf_ultralight_poller_sync_api.h | 30 + lib/nfc/protocols/mifare_classic.c | 1626 --- lib/nfc/protocols/mifare_classic.h | 251 - lib/nfc/protocols/mifare_common.c | 18 - lib/nfc/protocols/mifare_common.h | 12 - lib/nfc/protocols/mifare_desfire.c | 665 -- lib/nfc/protocols/mifare_desfire.h | 179 - lib/nfc/protocols/mifare_ultralight.c | 1946 ---- lib/nfc/protocols/mifare_ultralight.h | 269 - lib/nfc/protocols/nfc_device_base.h | 26 + lib/nfc/protocols/nfc_device_base_i.h | 161 + lib/nfc/protocols/nfc_device_defs.c | 46 + lib/nfc/protocols/nfc_device_defs.h | 13 + lib/nfc/protocols/nfc_generic_event.h | 79 + lib/nfc/protocols/nfc_listener_base.h | 98 + lib/nfc/protocols/nfc_listener_defs.c | 21 + lib/nfc/protocols/nfc_listener_defs.h | 14 + lib/nfc/protocols/nfc_poller_base.h | 132 + lib/nfc/protocols/nfc_poller_defs.c | 28 + lib/nfc/protocols/nfc_poller_defs.h | 14 + lib/nfc/protocols/nfc_protocol.c | 174 + lib/nfc/protocols/nfc_protocol.h | 219 + lib/nfc/protocols/nfca.c | 140 - lib/nfc/protocols/nfca.h | 31 - lib/nfc/protocols/nfcv.c | 1438 --- lib/nfc/protocols/nfcv.h | 334 - lib/nfc/protocols/slix.c | 784 -- lib/nfc/protocols/slix.h | 66 - lib/nfc/protocols/slix/slix.c | 433 + lib/nfc/protocols/slix/slix.h | 146 + lib/nfc/protocols/slix/slix_device_defs.h | 5 + lib/nfc/protocols/slix/slix_i.c | 127 + lib/nfc/protocols/slix/slix_i.h | 86 + lib/nfc/protocols/slix/slix_listener.c | 79 + lib/nfc/protocols/slix/slix_listener.h | 29 + lib/nfc/protocols/slix/slix_listener_defs.h | 5 + lib/nfc/protocols/slix/slix_listener_i.c | 617 ++ lib/nfc/protocols/slix/slix_listener_i.h | 37 + lib/nfc/protocols/slix/slix_poller.c | 159 + lib/nfc/protocols/slix/slix_poller.h | 29 + lib/nfc/protocols/slix/slix_poller_defs.h | 5 + lib/nfc/protocols/slix/slix_poller_i.c | 69 + lib/nfc/protocols/slix/slix_poller_i.h | 48 + lib/nfc/protocols/st25tb/st25tb.c | 234 + lib/nfc/protocols/st25tb/st25tb.h | 80 + lib/nfc/protocols/st25tb/st25tb_poller.c | 123 + lib/nfc/protocols/st25tb/st25tb_poller.h | 30 + lib/nfc/protocols/st25tb/st25tb_poller_defs.h | 13 + lib/nfc/protocols/st25tb/st25tb_poller_i.c | 297 + lib/nfc/protocols/st25tb/st25tb_poller_i.h | 56 + lib/signal_reader/SConscript | 20 + .../parsers/iso15693/iso15693_parser.c | 300 + .../parsers/iso15693/iso15693_parser.h | 42 + lib/signal_reader/signal_reader.c | 317 + lib/signal_reader/signal_reader.h | 67 + lib/subghz/protocols/bin_raw.c | 2 + lib/toolbox/SConscript | 2 + lib/toolbox/bit_buffer.c | 336 + lib/toolbox/bit_buffer.h | 325 + lib/toolbox/simple_array.c | 127 + lib/toolbox/simple_array.h | 148 + 514 files changed, 41333 insertions(+), 67970 deletions(-) create mode 100644 applications/debug/unit_tests/nfc/nfc_transport.c create mode 100644 applications/main/nfc/helpers/mf_classic_key_cache.c create mode 100644 applications/main/nfc/helpers/mf_classic_key_cache.h create mode 100644 applications/main/nfc/helpers/mf_ultralight_auth.c create mode 100644 applications/main/nfc/helpers/mf_ultralight_auth.h create mode 100644 applications/main/nfc/helpers/mf_user_dict.c create mode 100644 applications/main/nfc/helpers/mf_user_dict.h create mode 100644 applications/main/nfc/helpers/mfkey32_logger.c create mode 100644 applications/main/nfc/helpers/mfkey32_logger.h create mode 100644 applications/main/nfc/helpers/nfc_supported_cards.c create mode 100644 applications/main/nfc/helpers/nfc_supported_cards.h create mode 100644 applications/main/nfc/helpers/protocol_support/felica/felica.c create mode 100644 applications/main/nfc/helpers/protocol_support/felica/felica.h create mode 100644 applications/main/nfc/helpers/protocol_support/felica/felica_render.c create mode 100644 applications/main/nfc/helpers/protocol_support/felica/felica_render.h create mode 100644 applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a.c create mode 100644 applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a.h create mode 100644 applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a_render.c create mode 100644 applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a_render.h create mode 100644 applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b.c create mode 100644 applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b.h create mode 100644 applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b_i.h create mode 100644 applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b_render.c create mode 100644 applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b_render.h create mode 100644 applications/main/nfc/helpers/protocol_support/iso14443_4a/iso14443_4a.c create mode 100644 applications/main/nfc/helpers/protocol_support/iso14443_4a/iso14443_4a.h create mode 100644 applications/main/nfc/helpers/protocol_support/iso14443_4a/iso14443_4a_i.h create mode 100644 applications/main/nfc/helpers/protocol_support/iso14443_4a/iso14443_4a_render.c create mode 100644 applications/main/nfc/helpers/protocol_support/iso14443_4a/iso14443_4a_render.h create mode 100644 applications/main/nfc/helpers/protocol_support/iso14443_4b/iso14443_4b.c create mode 100644 applications/main/nfc/helpers/protocol_support/iso14443_4b/iso14443_4b.h create mode 100644 applications/main/nfc/helpers/protocol_support/iso14443_4b/iso14443_4b_render.c create mode 100644 applications/main/nfc/helpers/protocol_support/iso14443_4b/iso14443_4b_render.h create mode 100644 applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3.c create mode 100644 applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3.h create mode 100644 applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3_render.c create mode 100644 applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3_render.h create mode 100644 applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c create mode 100644 applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.h create mode 100644 applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic_render.c create mode 100644 applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic_render.h create mode 100644 applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire.c create mode 100644 applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire.h create mode 100644 applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire_render.c create mode 100644 applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire_render.h create mode 100644 applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c create mode 100644 applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.h create mode 100644 applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight_render.c create mode 100644 applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight_render.h create mode 100644 applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c create mode 100644 applications/main/nfc/helpers/protocol_support/nfc_protocol_support.h create mode 100644 applications/main/nfc/helpers/protocol_support/nfc_protocol_support_base.h create mode 100644 applications/main/nfc/helpers/protocol_support/nfc_protocol_support_common.h create mode 100644 applications/main/nfc/helpers/protocol_support/nfc_protocol_support_defs.c create mode 100644 applications/main/nfc/helpers/protocol_support/nfc_protocol_support_defs.h create mode 100644 applications/main/nfc/helpers/protocol_support/nfc_protocol_support_gui_common.c create mode 100644 applications/main/nfc/helpers/protocol_support/nfc_protocol_support_gui_common.h create mode 100644 applications/main/nfc/helpers/protocol_support/nfc_protocol_support_render_common.h create mode 100644 applications/main/nfc/helpers/protocol_support/slix/slix.c create mode 100644 applications/main/nfc/helpers/protocol_support/slix/slix.h create mode 100644 applications/main/nfc/helpers/protocol_support/slix/slix_render.c create mode 100644 applications/main/nfc/helpers/protocol_support/slix/slix_render.h create mode 100644 applications/main/nfc/helpers/protocol_support/st25tb/st25tb.c create mode 100644 applications/main/nfc/helpers/protocol_support/st25tb/st25tb.h create mode 100644 applications/main/nfc/helpers/protocol_support/st25tb/st25tb_render.c create mode 100644 applications/main/nfc/helpers/protocol_support/st25tb/st25tb_render.h delete mode 100644 applications/main/nfc/nfc.c delete mode 100644 applications/main/nfc/nfc.h create mode 100644 applications/main/nfc/nfc_app.c create mode 100644 applications/main/nfc/nfc_app.h create mode 100644 applications/main/nfc/nfc_app_i.h delete mode 100644 applications/main/nfc/nfc_i.h create mode 100644 applications/main/nfc/plugins/supported_cards/all_in_one.c create mode 100644 applications/main/nfc/plugins/supported_cards/myki.c create mode 100644 applications/main/nfc/plugins/supported_cards/nfc_supported_card_plugin.h create mode 100644 applications/main/nfc/plugins/supported_cards/opal.c create mode 100644 applications/main/nfc/plugins/supported_cards/plantain.c create mode 100644 applications/main/nfc/plugins/supported_cards/troika.c create mode 100644 applications/main/nfc/plugins/supported_cards/two_cities.c create mode 100644 applications/main/nfc/scenes/nfc_scene_detect.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_detect_reader.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_device_info.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_dict_not_found.c create mode 100644 applications/main/nfc/scenes/nfc_scene_emulate.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_emulate_apdu_sequence.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_emulate_uid.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_emv_menu.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_emv_read_success.c create mode 100644 applications/main/nfc/scenes/nfc_scene_info.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_mf_classic_data.c create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_classic_detect_reader.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_mf_classic_emulate.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_mf_classic_menu.c create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_classic_mfkey_complete.c create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_classic_mfkey_nonces_info.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_mf_classic_read_success.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_mf_classic_update.c create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_classic_update_initial.c create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_classic_update_initial_success.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_mf_classic_update_success.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_mf_classic_write.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_mf_classic_write_fail.c create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial.c create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial_fail.c create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial_success.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_mf_classic_write_success.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_mf_desfire_data.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_mf_desfire_menu.c create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_desfire_more_info.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_mf_desfire_read_success.c create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_ultralight_capture_pass.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_mf_ultralight_data.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_mf_ultralight_emulate.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_mf_ultralight_menu.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_auth.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_auth_result.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_success.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_auto.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_mfkey_complete.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_mfkey_nonces_info.c create mode 100644 applications/main/nfc/scenes/nfc_scene_more_info.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_nfc_data_info.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_nfca_menu.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_nfca_read_success.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_nfcv_emulate.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_nfcv_key_input.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_nfcv_menu.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_nfcv_read_success.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_nfcv_sniff.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_nfcv_unlock.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_nfcv_unlock_menu.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_read_card_success.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_read_card_type.c create mode 100644 applications/main/nfc/scenes/nfc_scene_read_menu.c create mode 100644 applications/main/nfc/scenes/nfc_scene_read_success.c create mode 100644 applications/main/nfc/scenes/nfc_scene_select_protocol.c create mode 100644 applications/main/nfc/scenes/nfc_scene_supported_card.c create mode 100644 assets/unit_tests/nfc/Ntag213_locked.nfc create mode 100644 assets/unit_tests/nfc/Ntag215.nfc create mode 100644 assets/unit_tests/nfc/Ntag216.nfc create mode 100644 assets/unit_tests/nfc/Ultralight_11.nfc create mode 100644 assets/unit_tests/nfc/Ultralight_21.nfc delete mode 100644 firmware/targets/f7/furi_hal/furi_hal_nfc.h create mode 100644 firmware/targets/f7/furi_hal/furi_hal_nfc_event.c create mode 100644 firmware/targets/f7/furi_hal/furi_hal_nfc_felica.c create mode 100644 firmware/targets/f7/furi_hal/furi_hal_nfc_i.h create mode 100644 firmware/targets/f7/furi_hal/furi_hal_nfc_irq.c create mode 100644 firmware/targets/f7/furi_hal/furi_hal_nfc_iso14443a.c create mode 100644 firmware/targets/f7/furi_hal/furi_hal_nfc_iso14443b.c create mode 100644 firmware/targets/f7/furi_hal/furi_hal_nfc_iso15693.c create mode 100644 firmware/targets/f7/furi_hal/furi_hal_nfc_tech_i.h create mode 100644 firmware/targets/f7/furi_hal/furi_hal_nfc_timer.c create mode 100644 firmware/targets/furi_hal_include/furi_hal_nfc.h delete mode 100644 lib/ST25RFAL002/SConscript delete mode 100755 lib/ST25RFAL002/doc/Release_Notes.html delete mode 100755 lib/ST25RFAL002/doc/ST25R3916_MisraComplianceReport.html delete mode 100755 lib/ST25RFAL002/doc/_htmresc/st_logo.png delete mode 100755 lib/ST25RFAL002/doc/rfal.chm delete mode 100644 lib/ST25RFAL002/include/rfal_analogConfig.h delete mode 100644 lib/ST25RFAL002/include/rfal_chip.h delete mode 100644 lib/ST25RFAL002/include/rfal_crc.h delete mode 100644 lib/ST25RFAL002/include/rfal_dpo.h delete mode 100644 lib/ST25RFAL002/include/rfal_iso15693_2.h delete mode 100644 lib/ST25RFAL002/include/rfal_isoDep.h delete mode 100644 lib/ST25RFAL002/include/rfal_nfc.h delete mode 100644 lib/ST25RFAL002/include/rfal_nfcDep.h delete mode 100644 lib/ST25RFAL002/include/rfal_nfca.h delete mode 100644 lib/ST25RFAL002/include/rfal_nfcb.h delete mode 100644 lib/ST25RFAL002/include/rfal_nfcf.h delete mode 100644 lib/ST25RFAL002/include/rfal_nfcv.h delete mode 100644 lib/ST25RFAL002/include/rfal_rf.h delete mode 100644 lib/ST25RFAL002/include/rfal_st25tb.h delete mode 100644 lib/ST25RFAL002/include/rfal_st25xv.h delete mode 100644 lib/ST25RFAL002/include/rfal_t1t.h delete mode 100644 lib/ST25RFAL002/include/rfal_t2t.h delete mode 100644 lib/ST25RFAL002/include/rfal_t4t.h delete mode 100644 lib/ST25RFAL002/platform.c delete mode 100644 lib/ST25RFAL002/platform.h delete mode 100644 lib/ST25RFAL002/source/custom_analog_config.c delete mode 100644 lib/ST25RFAL002/source/rfal_analogConfig.c delete mode 100644 lib/ST25RFAL002/source/rfal_crc.c delete mode 100644 lib/ST25RFAL002/source/rfal_dpo.c delete mode 100644 lib/ST25RFAL002/source/rfal_iso15693_2.c delete mode 100644 lib/ST25RFAL002/source/rfal_isoDep.c delete mode 100644 lib/ST25RFAL002/source/rfal_nfc.c delete mode 100644 lib/ST25RFAL002/source/rfal_nfcDep.c delete mode 100644 lib/ST25RFAL002/source/rfal_nfca.c delete mode 100644 lib/ST25RFAL002/source/rfal_nfcb.c delete mode 100644 lib/ST25RFAL002/source/rfal_nfcf.c delete mode 100644 lib/ST25RFAL002/source/rfal_nfcv.c delete mode 100644 lib/ST25RFAL002/source/rfal_st25tb.c delete mode 100644 lib/ST25RFAL002/source/rfal_st25xv.c delete mode 100644 lib/ST25RFAL002/source/rfal_t1t.c delete mode 100644 lib/ST25RFAL002/source/rfal_t2t.c delete mode 100644 lib/ST25RFAL002/source/rfal_t4t.c delete mode 100644 lib/ST25RFAL002/source/st25r3916/rfal_analogConfigTbl.h delete mode 100644 lib/ST25RFAL002/source/st25r3916/rfal_dpoTbl.h delete mode 100644 lib/ST25RFAL002/source/st25r3916/rfal_features.h delete mode 100644 lib/ST25RFAL002/source/st25r3916/rfal_rfst25r3916.c delete mode 100644 lib/ST25RFAL002/source/st25r3916/st25r3916.c delete mode 100644 lib/ST25RFAL002/source/st25r3916/st25r3916.h delete mode 100644 lib/ST25RFAL002/source/st25r3916/st25r3916_aat.c delete mode 100644 lib/ST25RFAL002/source/st25r3916/st25r3916_aat.h delete mode 100644 lib/ST25RFAL002/source/st25r3916/st25r3916_com.c delete mode 100644 lib/ST25RFAL002/source/st25r3916/st25r3916_irq.c delete mode 100644 lib/ST25RFAL002/source/st25r3916/st25r3916_irq.h delete mode 100644 lib/ST25RFAL002/source/st25r3916/st25r3916_led.c delete mode 100644 lib/ST25RFAL002/source/st25r3916/st25r3916_led.h delete mode 100644 lib/ST25RFAL002/st_errno.h delete mode 100644 lib/ST25RFAL002/timer.c delete mode 100644 lib/ST25RFAL002/timer.h delete mode 100644 lib/ST25RFAL002/utils.h create mode 100644 lib/digital_signal/digital_sequence.c create mode 100644 lib/digital_signal/digital_sequence.h create mode 100644 lib/digital_signal/digital_signal_i.h create mode 100644 lib/digital_signal/presets/nfc/iso14443_3a_signal.c create mode 100644 lib/digital_signal/presets/nfc/iso14443_3a_signal.h create mode 100644 lib/digital_signal/presets/nfc/iso15693_signal.c create mode 100644 lib/digital_signal/presets/nfc/iso15693_signal.h create mode 100644 lib/drivers/st25r3916.c create mode 100644 lib/drivers/st25r3916.h create mode 100644 lib/drivers/st25r3916_reg.c rename lib/{ST25RFAL002/source/st25r3916/st25r3916_com.h => drivers/st25r3916_reg.h} (62%) create mode 100644 lib/nfc/helpers/felica_crc.c create mode 100644 lib/nfc/helpers/felica_crc.h create mode 100644 lib/nfc/helpers/iso13239_crc.c create mode 100644 lib/nfc/helpers/iso13239_crc.h create mode 100644 lib/nfc/helpers/iso14443_4_layer.c create mode 100644 lib/nfc/helpers/iso14443_4_layer.h create mode 100644 lib/nfc/helpers/iso14443_crc.c create mode 100644 lib/nfc/helpers/iso14443_crc.h delete mode 100644 lib/nfc/helpers/mf_classic_dict.c delete mode 100644 lib/nfc/helpers/mf_classic_dict.h delete mode 100644 lib/nfc/helpers/mfkey32.c delete mode 100644 lib/nfc/helpers/mfkey32.h create mode 100644 lib/nfc/helpers/nfc_data_generator.c create mode 100644 lib/nfc/helpers/nfc_data_generator.h delete mode 100644 lib/nfc/helpers/nfc_debug_log.c delete mode 100644 lib/nfc/helpers/nfc_debug_log.h delete mode 100644 lib/nfc/helpers/nfc_debug_pcap.c delete mode 100644 lib/nfc/helpers/nfc_debug_pcap.h create mode 100644 lib/nfc/helpers/nfc_dict.c create mode 100644 lib/nfc/helpers/nfc_dict.h delete mode 100644 lib/nfc/helpers/nfc_generators.c delete mode 100644 lib/nfc/helpers/nfc_generators.h rename lib/nfc/{protocols => helpers}/nfc_util.c (100%) rename lib/nfc/{protocols => helpers}/nfc_util.h (100%) delete mode 100644 lib/nfc/helpers/reader_analyzer.c delete mode 100644 lib/nfc/helpers/reader_analyzer.h create mode 100644 lib/nfc/nfc.c create mode 100644 lib/nfc/nfc.h create mode 100644 lib/nfc/nfc_common.h create mode 100644 lib/nfc/nfc_device_i.c create mode 100644 lib/nfc/nfc_device_i.h create mode 100644 lib/nfc/nfc_listener.c create mode 100644 lib/nfc/nfc_listener.h create mode 100644 lib/nfc/nfc_poller.c create mode 100644 lib/nfc/nfc_poller.h create mode 100644 lib/nfc/nfc_scanner.c create mode 100644 lib/nfc/nfc_scanner.h delete mode 100644 lib/nfc/nfc_types.c delete mode 100644 lib/nfc/nfc_types.h delete mode 100644 lib/nfc/nfc_worker.c delete mode 100644 lib/nfc/nfc_worker.h delete mode 100644 lib/nfc/nfc_worker_i.h delete mode 100644 lib/nfc/parsers/all_in_one.c delete mode 100644 lib/nfc/parsers/all_in_one.h delete mode 100644 lib/nfc/parsers/nfc_supported_card.c delete mode 100644 lib/nfc/parsers/nfc_supported_card.h delete mode 100644 lib/nfc/parsers/opal.c delete mode 100644 lib/nfc/parsers/opal.h delete mode 100644 lib/nfc/parsers/plantain_4k_parser.c delete mode 100644 lib/nfc/parsers/plantain_4k_parser.h delete mode 100644 lib/nfc/parsers/plantain_parser.c delete mode 100644 lib/nfc/parsers/plantain_parser.h delete mode 100644 lib/nfc/parsers/troika_4k_parser.c delete mode 100644 lib/nfc/parsers/troika_4k_parser.h delete mode 100644 lib/nfc/parsers/troika_parser.c delete mode 100644 lib/nfc/parsers/troika_parser.h delete mode 100644 lib/nfc/parsers/two_cities.c delete mode 100644 lib/nfc/parsers/two_cities.h delete mode 100644 lib/nfc/protocols/crypto1.c delete mode 100644 lib/nfc/protocols/emv.c delete mode 100644 lib/nfc/protocols/emv.h create mode 100644 lib/nfc/protocols/felica/felica.c create mode 100644 lib/nfc/protocols/felica/felica.h create mode 100644 lib/nfc/protocols/felica/felica_poller.c create mode 100644 lib/nfc/protocols/felica/felica_poller.h create mode 100644 lib/nfc/protocols/felica/felica_poller_defs.h create mode 100644 lib/nfc/protocols/felica/felica_poller_i.c create mode 100644 lib/nfc/protocols/felica/felica_poller_i.h create mode 100644 lib/nfc/protocols/iso14443_3a/iso14443_3a.c create mode 100644 lib/nfc/protocols/iso14443_3a/iso14443_3a.h create mode 100644 lib/nfc/protocols/iso14443_3a/iso14443_3a_device_defs.h create mode 100644 lib/nfc/protocols/iso14443_3a/iso14443_3a_listener.c create mode 100644 lib/nfc/protocols/iso14443_3a/iso14443_3a_listener.h create mode 100644 lib/nfc/protocols/iso14443_3a/iso14443_3a_listener_defs.h create mode 100644 lib/nfc/protocols/iso14443_3a/iso14443_3a_listener_i.c create mode 100644 lib/nfc/protocols/iso14443_3a/iso14443_3a_listener_i.h create mode 100644 lib/nfc/protocols/iso14443_3a/iso14443_3a_poller.c create mode 100644 lib/nfc/protocols/iso14443_3a/iso14443_3a_poller.h create mode 100644 lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_defs.h create mode 100644 lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_i.c create mode 100644 lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_i.h create mode 100644 lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_sync_api.c create mode 100644 lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_sync_api.h create mode 100644 lib/nfc/protocols/iso14443_3b/iso14443_3b.c create mode 100644 lib/nfc/protocols/iso14443_3b/iso14443_3b.h create mode 100644 lib/nfc/protocols/iso14443_3b/iso14443_3b_device_defs.h create mode 100644 lib/nfc/protocols/iso14443_3b/iso14443_3b_i.c create mode 100644 lib/nfc/protocols/iso14443_3b/iso14443_3b_i.h create mode 100644 lib/nfc/protocols/iso14443_3b/iso14443_3b_poller.c create mode 100644 lib/nfc/protocols/iso14443_3b/iso14443_3b_poller.h create mode 100644 lib/nfc/protocols/iso14443_3b/iso14443_3b_poller_defs.h create mode 100644 lib/nfc/protocols/iso14443_3b/iso14443_3b_poller_i.c create mode 100644 lib/nfc/protocols/iso14443_3b/iso14443_3b_poller_i.h create mode 100644 lib/nfc/protocols/iso14443_4a/iso14443_4a.c create mode 100644 lib/nfc/protocols/iso14443_4a/iso14443_4a.h create mode 100644 lib/nfc/protocols/iso14443_4a/iso14443_4a_device_defs.h create mode 100644 lib/nfc/protocols/iso14443_4a/iso14443_4a_i.c create mode 100644 lib/nfc/protocols/iso14443_4a/iso14443_4a_i.h create mode 100644 lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.c create mode 100644 lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.h create mode 100644 lib/nfc/protocols/iso14443_4a/iso14443_4a_listener_defs.h create mode 100644 lib/nfc/protocols/iso14443_4a/iso14443_4a_listener_i.c create mode 100644 lib/nfc/protocols/iso14443_4a/iso14443_4a_listener_i.h create mode 100644 lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.c create mode 100644 lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.h create mode 100644 lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_defs.h create mode 100644 lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.c create mode 100644 lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.h create mode 100644 lib/nfc/protocols/iso14443_4b/iso14443_4b.c create mode 100644 lib/nfc/protocols/iso14443_4b/iso14443_4b.h create mode 100644 lib/nfc/protocols/iso14443_4b/iso14443_4b_device_defs.h create mode 100644 lib/nfc/protocols/iso14443_4b/iso14443_4b_i.c create mode 100644 lib/nfc/protocols/iso14443_4b/iso14443_4b_i.h create mode 100644 lib/nfc/protocols/iso14443_4b/iso14443_4b_poller.c create mode 100644 lib/nfc/protocols/iso14443_4b/iso14443_4b_poller.h create mode 100644 lib/nfc/protocols/iso14443_4b/iso14443_4b_poller_defs.h create mode 100644 lib/nfc/protocols/iso14443_4b/iso14443_4b_poller_i.c create mode 100644 lib/nfc/protocols/iso14443_4b/iso14443_4b_poller_i.h create mode 100644 lib/nfc/protocols/iso15693_3/iso15693_3.c create mode 100644 lib/nfc/protocols/iso15693_3/iso15693_3.h create mode 100644 lib/nfc/protocols/iso15693_3/iso15693_3_device_defs.h create mode 100644 lib/nfc/protocols/iso15693_3/iso15693_3_i.c create mode 100644 lib/nfc/protocols/iso15693_3/iso15693_3_i.h create mode 100644 lib/nfc/protocols/iso15693_3/iso15693_3_listener.c create mode 100644 lib/nfc/protocols/iso15693_3/iso15693_3_listener.h create mode 100644 lib/nfc/protocols/iso15693_3/iso15693_3_listener_defs.h create mode 100644 lib/nfc/protocols/iso15693_3/iso15693_3_listener_i.c create mode 100644 lib/nfc/protocols/iso15693_3/iso15693_3_listener_i.h create mode 100644 lib/nfc/protocols/iso15693_3/iso15693_3_poller.c create mode 100644 lib/nfc/protocols/iso15693_3/iso15693_3_poller.h create mode 100644 lib/nfc/protocols/iso15693_3/iso15693_3_poller_defs.h create mode 100644 lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.c create mode 100644 lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.h create mode 100644 lib/nfc/protocols/mf_classic/crypto1.c rename lib/nfc/protocols/{ => mf_classic}/crypto1.h (56%) create mode 100644 lib/nfc/protocols/mf_classic/mf_classic.c create mode 100644 lib/nfc/protocols/mf_classic/mf_classic.h create mode 100644 lib/nfc/protocols/mf_classic/mf_classic_listener.c create mode 100644 lib/nfc/protocols/mf_classic/mf_classic_listener.h create mode 100644 lib/nfc/protocols/mf_classic/mf_classic_listener_defs.h create mode 100644 lib/nfc/protocols/mf_classic/mf_classic_listener_i.h create mode 100644 lib/nfc/protocols/mf_classic/mf_classic_poller.c create mode 100644 lib/nfc/protocols/mf_classic/mf_classic_poller.h create mode 100644 lib/nfc/protocols/mf_classic/mf_classic_poller_defs.h create mode 100644 lib/nfc/protocols/mf_classic/mf_classic_poller_i.c create mode 100644 lib/nfc/protocols/mf_classic/mf_classic_poller_i.h create mode 100644 lib/nfc/protocols/mf_classic/mf_classic_poller_sync_api.c create mode 100644 lib/nfc/protocols/mf_classic/mf_classic_poller_sync_api.h create mode 100644 lib/nfc/protocols/mf_desfire/mf_desfire.c create mode 100644 lib/nfc/protocols/mf_desfire/mf_desfire.h create mode 100644 lib/nfc/protocols/mf_desfire/mf_desfire_i.c create mode 100644 lib/nfc/protocols/mf_desfire/mf_desfire_i.h create mode 100644 lib/nfc/protocols/mf_desfire/mf_desfire_poller.c create mode 100644 lib/nfc/protocols/mf_desfire/mf_desfire_poller.h create mode 100644 lib/nfc/protocols/mf_desfire/mf_desfire_poller_defs.h create mode 100644 lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c create mode 100644 lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.h create mode 100644 lib/nfc/protocols/mf_ultralight/mf_ultralight.c create mode 100644 lib/nfc/protocols/mf_ultralight/mf_ultralight.h create mode 100644 lib/nfc/protocols/mf_ultralight/mf_ultralight_listener.c create mode 100644 lib/nfc/protocols/mf_ultralight/mf_ultralight_listener.h create mode 100644 lib/nfc/protocols/mf_ultralight/mf_ultralight_listener_defs.h create mode 100644 lib/nfc/protocols/mf_ultralight/mf_ultralight_listener_i.c create mode 100644 lib/nfc/protocols/mf_ultralight/mf_ultralight_listener_i.h create mode 100644 lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c create mode 100644 lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h create mode 100644 lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_defs.h create mode 100644 lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.c create mode 100644 lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.h create mode 100644 lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync_api.c create mode 100644 lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync_api.h delete mode 100644 lib/nfc/protocols/mifare_classic.c delete mode 100644 lib/nfc/protocols/mifare_classic.h delete mode 100644 lib/nfc/protocols/mifare_common.c delete mode 100644 lib/nfc/protocols/mifare_common.h delete mode 100644 lib/nfc/protocols/mifare_desfire.c delete mode 100644 lib/nfc/protocols/mifare_desfire.h delete mode 100644 lib/nfc/protocols/mifare_ultralight.c delete mode 100644 lib/nfc/protocols/mifare_ultralight.h create mode 100644 lib/nfc/protocols/nfc_device_base.h create mode 100644 lib/nfc/protocols/nfc_device_base_i.h create mode 100644 lib/nfc/protocols/nfc_device_defs.c create mode 100644 lib/nfc/protocols/nfc_device_defs.h create mode 100644 lib/nfc/protocols/nfc_generic_event.h create mode 100644 lib/nfc/protocols/nfc_listener_base.h create mode 100644 lib/nfc/protocols/nfc_listener_defs.c create mode 100644 lib/nfc/protocols/nfc_listener_defs.h create mode 100644 lib/nfc/protocols/nfc_poller_base.h create mode 100644 lib/nfc/protocols/nfc_poller_defs.c create mode 100644 lib/nfc/protocols/nfc_poller_defs.h create mode 100644 lib/nfc/protocols/nfc_protocol.c create mode 100644 lib/nfc/protocols/nfc_protocol.h delete mode 100644 lib/nfc/protocols/nfca.c delete mode 100644 lib/nfc/protocols/nfca.h delete mode 100644 lib/nfc/protocols/nfcv.c delete mode 100644 lib/nfc/protocols/nfcv.h delete mode 100644 lib/nfc/protocols/slix.c delete mode 100644 lib/nfc/protocols/slix.h create mode 100644 lib/nfc/protocols/slix/slix.c create mode 100644 lib/nfc/protocols/slix/slix.h create mode 100644 lib/nfc/protocols/slix/slix_device_defs.h create mode 100644 lib/nfc/protocols/slix/slix_i.c create mode 100644 lib/nfc/protocols/slix/slix_i.h create mode 100644 lib/nfc/protocols/slix/slix_listener.c create mode 100644 lib/nfc/protocols/slix/slix_listener.h create mode 100644 lib/nfc/protocols/slix/slix_listener_defs.h create mode 100644 lib/nfc/protocols/slix/slix_listener_i.c create mode 100644 lib/nfc/protocols/slix/slix_listener_i.h create mode 100644 lib/nfc/protocols/slix/slix_poller.c create mode 100644 lib/nfc/protocols/slix/slix_poller.h create mode 100644 lib/nfc/protocols/slix/slix_poller_defs.h create mode 100644 lib/nfc/protocols/slix/slix_poller_i.c create mode 100644 lib/nfc/protocols/slix/slix_poller_i.h create mode 100644 lib/nfc/protocols/st25tb/st25tb.c create mode 100644 lib/nfc/protocols/st25tb/st25tb.h create mode 100644 lib/nfc/protocols/st25tb/st25tb_poller.c create mode 100644 lib/nfc/protocols/st25tb/st25tb_poller.h create mode 100644 lib/nfc/protocols/st25tb/st25tb_poller_defs.h create mode 100644 lib/nfc/protocols/st25tb/st25tb_poller_i.c create mode 100644 lib/nfc/protocols/st25tb/st25tb_poller_i.h create mode 100644 lib/signal_reader/SConscript create mode 100644 lib/signal_reader/parsers/iso15693/iso15693_parser.c create mode 100644 lib/signal_reader/parsers/iso15693/iso15693_parser.h create mode 100644 lib/signal_reader/signal_reader.c create mode 100644 lib/signal_reader/signal_reader.h create mode 100644 lib/toolbox/bit_buffer.c create mode 100644 lib/toolbox/bit_buffer.h create mode 100644 lib/toolbox/simple_array.c create mode 100644 lib/toolbox/simple_array.h diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 6626f9ae2a4..e02c931af98 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -52,7 +52,6 @@ /scripts/toolchain/ @skotopes @DrZlo13 @hedger @drunkbatya # Lib -/lib/ST25RFAL002/ @skotopes @DrZlo13 @hedger @gornekich /lib/stm32wb_copro/ @skotopes @DrZlo13 @hedger @gornekich /lib/digital_signal/ @skotopes @DrZlo13 @hedger @gornekich /lib/infrared/ @skotopes @DrZlo13 @hedger @gsurkov diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 35085ba57a1..2680f520c1c 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -32,7 +32,7 @@ jobs: if: success() timeout-minutes: 10 run: | - ./fbt flash SWD_TRANSPORT_SERIAL=2A0906016415303030303032 FIRMWARE_APP_SET=unit_tests FORCE=1 + ./fbt flash SWD_TRANSPORT_SERIAL=2A0906016415303030303032 LIB_DEBUG=1 FIRMWARE_APP_SET=unit_tests FORCE=1 - name: 'Wait for flipper and format ext' id: format_ext @@ -66,4 +66,4 @@ jobs: - name: 'Check GDB output' if: failure() run: | - ./fbt gdb_trace_all SWD_TRANSPORT_SERIAL=2A0906016415303030303032 FIRMWARE_APP_SET=unit_tests FORCE=1 + ./fbt gdb_trace_all SWD_TRANSPORT_SERIAL=2A0906016415303030303032 LIB_DEBUG=1 FIRMWARE_APP_SET=unit_tests FORCE=1 diff --git a/.pvsoptions b/.pvsoptions index 2e0b2fb8d24..0312180924d 100644 --- a/.pvsoptions +++ b/.pvsoptions @@ -1 +1 @@ ---ignore-ccache -C gccarm --rules-config .pvsconfig -e lib/cmsis_core -e lib/fatfs -e lib/fnv1a-hash -e lib/FreeRTOS-Kernel -e lib/heatshrink -e lib/libusb_stm32 -e lib/littlefs -e lib/mbedtls -e lib/micro-ecc -e lib/microtar -e lib/mlib -e lib/qrcode -e lib/ST25RFAL002 -e lib/stm32wb_cmsis -e lib/stm32wb_copro -e lib/stm32wb_hal -e lib/u8g2 -e lib/nanopb -e */arm-none-eabi/* +--ignore-ccache -C gccarm --rules-config .pvsconfig -e lib/cmsis_core -e lib/fatfs -e lib/fnv1a-hash -e lib/FreeRTOS-Kernel -e lib/heatshrink -e lib/libusb_stm32 -e lib/littlefs -e lib/mbedtls -e lib/micro-ecc -e lib/microtar -e lib/mlib -e lib/qrcode -e lib/stm32wb_cmsis -e lib/stm32wb_copro -e lib/stm32wb_hal -e lib/u8g2 -e lib/nanopb -e */arm-none-eabi/* diff --git a/applications/debug/unit_tests/nfc/nfc_test.c b/applications/debug/unit_tests/nfc/nfc_test.c index bc2f7887b1e..e7406fab8af 100644 --- a/applications/debug/unit_tests/nfc/nfc_test.c +++ b/applications/debug/unit_tests/nfc/nfc_test.c @@ -1,52 +1,35 @@ #include #include #include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include #include "../minunit.h" #define TAG "NfcTest" -#define NFC_TEST_RESOURCES_DIR EXT_PATH("unit_tests/nfc/") -#define NFC_TEST_SIGNAL_SHORT_FILE "nfc_nfca_signal_short.nfc" -#define NFC_TEST_SIGNAL_LONG_FILE "nfc_nfca_signal_long.nfc" -#define NFC_TEST_DICT_PATH EXT_PATH("unit_tests/mf_classic_dict.nfc") -#define NFC_TEST_NFC_DEV_PATH EXT_PATH("unit_tests/nfc/nfc_dev_test.nfc") - -static const char* nfc_test_file_type = "Flipper NFC test"; -static const uint32_t nfc_test_file_version = 1; - -#define NFC_TEST_DATA_MAX_LEN 18 -#define NFC_TETS_TIMINGS_MAX_LEN 1350 - -// Maximum allowed time for buffer preparation to fit 500us nt message timeout -#define NFC_TEST_4_BYTE_BUILD_BUFFER_TIM_MAX (150) -#define NFC_TEST_16_BYTE_BUILD_BUFFER_TIM_MAX (640) -#define NFC_TEST_4_BYTE_BUILD_SIGNAL_TIM_MAX (110) -#define NFC_TEST_16_BYTE_BUILD_SIGNAL_TIM_MAX (440) +#define NFC_TEST_NFC_DEV_PATH EXT_PATH("unit_tests/nfc/nfc_device_test.nfc") +#define NFC_APP_MF_CLASSIC_DICT_UNIT_TEST_PATH EXT_PATH("unit_tests/mf_dict.nfc") typedef struct { Storage* storage; - NfcaSignal* signal; - uint32_t test_data_len; - uint8_t test_data[NFC_TEST_DATA_MAX_LEN]; - uint32_t test_timings_len; - uint32_t test_timings[NFC_TETS_TIMINGS_MAX_LEN]; } NfcTest; static NfcTest* nfc_test = NULL; static void nfc_test_alloc() { nfc_test = malloc(sizeof(NfcTest)); - nfc_test->signal = nfca_signal_alloc(); nfc_test->storage = furi_record_open(RECORD_STORAGE); } @@ -54,517 +37,500 @@ static void nfc_test_free() { furi_assert(nfc_test); furi_record_close(RECORD_STORAGE); - nfca_signal_free(nfc_test->signal); free(nfc_test); nfc_test = NULL; } -static bool nfc_test_read_signal_from_file(const char* file_name) { - bool success = false; - - FlipperFormat* file = flipper_format_file_alloc(nfc_test->storage); - FuriString* file_type; - file_type = furi_string_alloc(); - uint32_t file_version = 0; - - do { - if(!flipper_format_file_open_existing(file, file_name)) break; - if(!flipper_format_read_header(file, file_type, &file_version)) break; - if(furi_string_cmp_str(file_type, nfc_test_file_type) || - file_version != nfc_test_file_version) - break; - if(!flipper_format_read_uint32(file, "Data length", &nfc_test->test_data_len, 1)) break; - if(nfc_test->test_data_len > NFC_TEST_DATA_MAX_LEN) break; - if(!flipper_format_read_hex( - file, "Plain data", nfc_test->test_data, nfc_test->test_data_len)) - break; - if(!flipper_format_read_uint32(file, "Timings length", &nfc_test->test_timings_len, 1)) - break; - if(nfc_test->test_timings_len > NFC_TETS_TIMINGS_MAX_LEN) break; - if(!flipper_format_read_uint32( - file, "Timings", nfc_test->test_timings, nfc_test->test_timings_len)) - break; - success = true; - } while(false); - - furi_string_free(file_type); - flipper_format_free(file); - - return success; -} - -static bool nfc_test_digital_signal_test_encode( - const char* file_name, - uint32_t build_signal_max_time_us, - uint32_t build_buffer_max_time_us, - uint32_t timing_tolerance, - uint32_t timings_sum_tolerance) { - furi_assert(nfc_test); +static void nfc_test_save_and_load(NfcDevice* nfc_device_ref) { + NfcDevice* nfc_device_dut = nfc_device_alloc(); - bool success = false; - uint32_t dut_timings_sum = 0; - uint32_t ref_timings_sum = 0; - uint8_t parity[10] = {}; - - do { - // Read test data - if(!nfc_test_read_signal_from_file(file_name)) { - FURI_LOG_E(TAG, "Failed to read signal from file"); - break; - } - - // Encode signal - FURI_CRITICAL_ENTER(); - uint32_t time_start = DWT->CYCCNT; - - nfca_signal_encode( - nfc_test->signal, nfc_test->test_data, nfc_test->test_data_len * 8, parity); - - uint32_t time_signal = - (DWT->CYCCNT - time_start) / furi_hal_cortex_instructions_per_microsecond(); - - time_start = DWT->CYCCNT; - - digital_signal_prepare_arr(nfc_test->signal->tx_signal); - - uint32_t time_buffer = - (DWT->CYCCNT - time_start) / furi_hal_cortex_instructions_per_microsecond(); - FURI_CRITICAL_EXIT(); - - // Check timings - if(time_signal > build_signal_max_time_us) { - FURI_LOG_E( - TAG, - "Build signal time: %ld us while accepted value: %ld us", - time_signal, - build_signal_max_time_us); - break; - } - if(time_buffer > build_buffer_max_time_us) { - FURI_LOG_E( - TAG, - "Build buffer time: %ld us while accepted value: %ld us", - time_buffer, - build_buffer_max_time_us); - break; - } - - // Check data - if(nfc_test->signal->tx_signal->edge_cnt != nfc_test->test_timings_len) { - FURI_LOG_E(TAG, "Not equal timings buffers length"); - break; - } - - uint32_t timings_diff = 0; - uint32_t* ref = nfc_test->test_timings; - uint32_t* dut = nfc_test->signal->tx_signal->reload_reg_buff; - bool timing_check_success = true; - for(size_t i = 0; i < nfc_test->test_timings_len; i++) { - timings_diff = dut[i] > ref[i] ? dut[i] - ref[i] : ref[i] - dut[i]; - dut_timings_sum += dut[i]; - ref_timings_sum += ref[i]; - if(timings_diff > timing_tolerance) { - FURI_LOG_E( - TAG, "Too big difference in %d timings. Ref: %ld, DUT: %ld", i, ref[i], dut[i]); - timing_check_success = false; - break; - } - } - if(!timing_check_success) break; - uint32_t sum_diff = dut_timings_sum > ref_timings_sum ? dut_timings_sum - ref_timings_sum : - ref_timings_sum - dut_timings_sum; - if(sum_diff > timings_sum_tolerance) { - FURI_LOG_E( - TAG, - "Too big difference in timings sum. Ref: %ld, DUT: %ld", - ref_timings_sum, - dut_timings_sum); - break; - } - - FURI_LOG_I( - TAG, - "Build signal time: %ld us. Acceptable time: %ld us", - time_signal, - build_signal_max_time_us); - FURI_LOG_I( - TAG, - "Build buffer time: %ld us. Acceptable time: %ld us", - time_buffer, - build_buffer_max_time_us); - FURI_LOG_I( - TAG, - "Timings sum difference: %ld [1/64MHZ]. Acceptable difference: %ld [1/64MHz]", - sum_diff, - timings_sum_tolerance); - success = true; - } while(false); - - return success; -} - -MU_TEST(nfc_digital_signal_test) { mu_assert( - nfc_test_digital_signal_test_encode( - NFC_TEST_RESOURCES_DIR NFC_TEST_SIGNAL_SHORT_FILE, - NFC_TEST_4_BYTE_BUILD_SIGNAL_TIM_MAX, - NFC_TEST_4_BYTE_BUILD_BUFFER_TIM_MAX, - 1, - 37), - "NFC short digital signal test failed\r\n"); + nfc_device_save(nfc_device_ref, NFC_TEST_NFC_DEV_PATH), "nfc_device_save() failed\r\n"); + + mu_assert( + nfc_device_load(nfc_device_dut, NFC_TEST_NFC_DEV_PATH), "nfc_device_load() failed\r\n"); + + mu_assert( + nfc_device_is_equal(nfc_device_ref, nfc_device_dut), + "nfc_device_data_dut != nfc_device_data_ref\r\n"); + mu_assert( - nfc_test_digital_signal_test_encode( - NFC_TEST_RESOURCES_DIR NFC_TEST_SIGNAL_LONG_FILE, - NFC_TEST_16_BYTE_BUILD_SIGNAL_TIM_MAX, - NFC_TEST_16_BYTE_BUILD_BUFFER_TIM_MAX, - 1, - 37), - "NFC long digital signal test failed\r\n"); + storage_simply_remove(nfc_test->storage, NFC_TEST_NFC_DEV_PATH), + "storage_simply_remove() failed\r\n"); + + nfc_device_free(nfc_device_dut); } -MU_TEST(mf_classic_dict_test) { - MfClassicDict* instance = NULL; - uint64_t key = 0; - FuriString* temp_str; - temp_str = furi_string_alloc(); +static void iso14443_3a_file_test(uint8_t uid_len) { + NfcDevice* nfc_device = nfc_device_alloc(); - instance = mf_classic_dict_alloc(MfClassicDictTypeUnitTest); - mu_assert(instance != NULL, "mf_classic_dict_alloc\r\n"); + Iso14443_3aData* data = iso14443_3a_alloc(); + data->uid_len = uid_len; + furi_hal_random_fill_buf(data->uid, uid_len); + furi_hal_random_fill_buf(data->atqa, sizeof(data->atqa)); + furi_hal_random_fill_buf(&data->sak, 1); - mu_assert( - mf_classic_dict_get_total_keys(instance) == 0, - "mf_classic_dict_get_total_keys == 0 assert failed\r\n"); + nfc_device_set_data(nfc_device, NfcProtocolIso14443_3a, data); + nfc_test_save_and_load(nfc_device); - furi_string_set(temp_str, "2196FAD8115B"); - mu_assert( - mf_classic_dict_add_key_str(instance, temp_str), - "mf_classic_dict_add_key == true assert failed\r\n"); + iso14443_3a_free(data); + nfc_device_free(nfc_device); +} - mu_assert( - mf_classic_dict_get_total_keys(instance) == 1, - "mf_classic_dict_get_total_keys == 1 assert failed\r\n"); +static void nfc_file_test_with_generator(NfcDataGeneratorType type) { + NfcDevice* nfc_device_ref = nfc_device_alloc(); - mu_assert(mf_classic_dict_rewind(instance), "mf_classic_dict_rewind == 1 assert failed\r\n"); + nfc_data_generator_fill_data(type, nfc_device_ref); + nfc_test_save_and_load(nfc_device_ref); - mu_assert( - mf_classic_dict_get_key_at_index_str(instance, temp_str, 0), - "mf_classic_dict_get_key_at_index_str == true assert failed\r\n"); - mu_assert( - furi_string_cmp(temp_str, "2196FAD8115B") == 0, - "string_cmp(temp_str, \"2196FAD8115B\") == 0 assert failed\r\n"); + nfc_device_free(nfc_device_ref); +} - mu_assert(mf_classic_dict_rewind(instance), "mf_classic_dict_rewind == 1 assert failed\r\n"); +MU_TEST(iso14443_3a_4b_file_test) { + iso14443_3a_file_test(4); +} - mu_assert( - mf_classic_dict_get_key_at_index(instance, &key, 0), - "mf_classic_dict_get_key_at_index == true assert failed\r\n"); - mu_assert(key == 0x2196FAD8115B, "key == 0x2196FAD8115B assert failed\r\n"); +MU_TEST(iso14443_3a_7b_file_test) { + iso14443_3a_file_test(7); +} - mu_assert(mf_classic_dict_rewind(instance), "mf_classic_dict_rewind == 1 assert failed\r\n"); +MU_TEST(mf_ultralight_file_test) { + nfc_file_test_with_generator(NfcDataGeneratorTypeMfUltralight); +} - mu_assert( - mf_classic_dict_delete_index(instance, 0), - "mf_classic_dict_delete_index == true assert failed\r\n"); +MU_TEST(mf_ultralight_ev1_11_file_test) { + nfc_file_test_with_generator(NfcDataGeneratorTypeMfUltralightEV1_11); +} - mf_classic_dict_free(instance); - furi_string_free(temp_str); +MU_TEST(mf_ultralight_ev1_h11_file_test) { + nfc_file_test_with_generator(NfcDataGeneratorTypeMfUltralightEV1_H11); } -MU_TEST(mf_classic_dict_load_test) { - Storage* storage = furi_record_open(RECORD_STORAGE); - mu_assert(storage != NULL, "storage != NULL assert failed\r\n"); +MU_TEST(mf_ultralight_ev1_21_file_test) { + nfc_file_test_with_generator(NfcDataGeneratorTypeMfUltralightEV1_21); +} - // Delete unit test dict file if exists - if(storage_file_exists(storage, NFC_TEST_DICT_PATH)) { - mu_assert( - storage_simply_remove(storage, NFC_TEST_DICT_PATH), - "remove == true assert failed\r\n"); - } +MU_TEST(mf_ultralight_ev1_h21_file_test) { + nfc_file_test_with_generator(NfcDataGeneratorTypeMfUltralightEV1_H21); +} - // Create unit test dict file - Stream* file_stream = file_stream_alloc(storage); - mu_assert(file_stream != NULL, "file_stream != NULL assert failed\r\n"); - mu_assert( - file_stream_open(file_stream, NFC_TEST_DICT_PATH, FSAM_WRITE, FSOM_OPEN_ALWAYS), - "file_stream_open == true assert failed\r\n"); +MU_TEST(mf_ultralight_ntag_203_file_test) { + nfc_file_test_with_generator(NfcDataGeneratorTypeNTAG203); +} - // Write unit test dict file - char key_str[] = "a0a1a2a3a4a5"; - mu_assert( - stream_write_cstring(file_stream, key_str) == strlen(key_str), - "write == true assert failed\r\n"); - // Close unit test dict file - mu_assert(file_stream_close(file_stream), "file_stream_close == true assert failed\r\n"); - - // Load unit test dict file - MfClassicDict* instance = NULL; - instance = mf_classic_dict_alloc(MfClassicDictTypeUnitTest); - mu_assert(instance != NULL, "mf_classic_dict_alloc\r\n"); - uint32_t total_keys = mf_classic_dict_get_total_keys(instance); - mu_assert(total_keys == 1, "total_keys == 1 assert failed\r\n"); - - // Read key - uint64_t key_ref = 0xa0a1a2a3a4a5; - uint64_t key_dut = 0; - FuriString* temp_str = furi_string_alloc(); - mu_assert( - mf_classic_dict_get_next_key_str(instance, temp_str), - "get_next_key_str == true assert failed\r\n"); - mu_assert(furi_string_cmp_str(temp_str, key_str) == 0, "invalid key loaded\r\n"); - mu_assert(mf_classic_dict_rewind(instance), "mf_classic_dict_rewind == 1 assert failed\r\n"); - mu_assert( - mf_classic_dict_get_next_key(instance, &key_dut), - "get_next_key == true assert failed\r\n"); - mu_assert(key_dut == key_ref, "invalid key loaded\r\n"); - furi_string_free(temp_str); - mf_classic_dict_free(instance); +MU_TEST(mf_ultralight_ntag_213_file_test) { + nfc_file_test_with_generator(NfcDataGeneratorTypeNTAG213); +} - // Check that MfClassicDict added new line to the end of the file - mu_assert( - file_stream_open(file_stream, NFC_TEST_DICT_PATH, FSAM_READ, FSOM_OPEN_EXISTING), - "file_stream_open == true assert failed\r\n"); - mu_assert(stream_seek(file_stream, -1, StreamOffsetFromEnd), "seek == true assert failed\r\n"); - uint8_t last_char = 0; - mu_assert(stream_read(file_stream, &last_char, 1) == 1, "read == true assert failed\r\n"); - mu_assert(last_char == '\n', "last_char == '\\n' assert failed\r\n"); - mu_assert(file_stream_close(file_stream), "file_stream_close == true assert failed\r\n"); - - // Delete unit test dict file - mu_assert( - storage_simply_remove(storage, NFC_TEST_DICT_PATH), "remove == true assert failed\r\n"); - stream_free(file_stream); - furi_record_close(RECORD_STORAGE); +MU_TEST(mf_ultralight_ntag_215_file_test) { + nfc_file_test_with_generator(NfcDataGeneratorTypeNTAG215); } -MU_TEST(nfca_file_test) { - NfcDevice* nfc = nfc_device_alloc(); - mu_assert(nfc != NULL, "nfc_device_data != NULL assert failed\r\n"); - nfc->format = NfcDeviceSaveFormatUid; +MU_TEST(mf_ultralight_ntag_216_file_test) { + nfc_file_test_with_generator(NfcDataGeneratorTypeNTAG216); +} - // Fill the UID, sak, ATQA and type - uint8_t uid[7] = {0x04, 0x01, 0x23, 0x45, 0x67, 0x89, 0x00}; - memcpy(nfc->dev_data.nfc_data.uid, uid, 7); - nfc->dev_data.nfc_data.uid_len = 7; +MU_TEST(mf_ultralight_ntag_i2c_1k_file_test) { + nfc_file_test_with_generator(NfcDataGeneratorTypeNTAGI2C1k); +} - nfc->dev_data.nfc_data.sak = 0x08; - nfc->dev_data.nfc_data.atqa[0] = 0x00; - nfc->dev_data.nfc_data.atqa[1] = 0x04; - nfc->dev_data.nfc_data.type = FuriHalNfcTypeA; +MU_TEST(mf_ultralight_ntag_i2c_2k_file_test) { + nfc_file_test_with_generator(NfcDataGeneratorTypeNTAGI2C2k); +} - // Save the NFC device data to the file - mu_assert( - nfc_device_save(nfc, NFC_TEST_NFC_DEV_PATH), "nfc_device_save == true assert failed\r\n"); - nfc_device_free(nfc); +MU_TEST(mf_ultralight_ntag_i2c_plus_1k_file_test) { + nfc_file_test_with_generator(NfcDataGeneratorTypeNTAGI2CPlus1k); +} - // Load the NFC device data from the file - NfcDevice* nfc_validate = nfc_device_alloc(); - mu_assert( - nfc_device_load(nfc_validate, NFC_TEST_NFC_DEV_PATH, true), - "nfc_device_load == true assert failed\r\n"); +MU_TEST(mf_ultralight_ntag_i2c_plus_2k_file_test) { + nfc_file_test_with_generator(NfcDataGeneratorTypeNTAGI2CPlus2k); +} - // Check the UID, sak, ATQA and type - mu_assert(memcmp(nfc_validate->dev_data.nfc_data.uid, uid, 7) == 0, "uid assert failed\r\n"); - mu_assert(nfc_validate->dev_data.nfc_data.sak == 0x08, "sak == 0x08 assert failed\r\n"); - mu_assert( - nfc_validate->dev_data.nfc_data.atqa[0] == 0x00, "atqa[0] == 0x00 assert failed\r\n"); - mu_assert( - nfc_validate->dev_data.nfc_data.atqa[1] == 0x04, "atqa[1] == 0x04 assert failed\r\n"); +MU_TEST(mf_classic_mini_file_test) { + nfc_file_test_with_generator(NfcDataGeneratorTypeMfClassicMini); +} + +MU_TEST(mf_classic_1k_4b_file_test) { + nfc_file_test_with_generator(NfcDataGeneratorTypeMfClassic1k_4b); +} + +MU_TEST(mf_classic_1k_7b_file_test) { + nfc_file_test_with_generator(NfcDataGeneratorTypeMfClassic1k_7b); +} + +MU_TEST(mf_classic_4k_4b_file_test) { + nfc_file_test_with_generator(NfcDataGeneratorTypeMfClassic4k_4b); +} + +MU_TEST(mf_classic_4k_7b_file_test) { + nfc_file_test_with_generator(NfcDataGeneratorTypeMfClassic4k_7b); +} + +MU_TEST(iso14443_3a_reader) { + Nfc* poller = nfc_alloc(); + Nfc* listener = nfc_alloc(); + + Iso14443_3aData iso14443_3a_listener_data = { + .uid_len = 7, + .uid = {0x04, 0x51, 0x5C, 0xFA, 0x6F, 0x73, 0x81}, + .atqa = {0x44, 0x00}, + .sak = 0x00, + }; + NfcListener* iso3_listener = + nfc_listener_alloc(listener, NfcProtocolIso14443_3a, &iso14443_3a_listener_data); + nfc_listener_start(iso3_listener, NULL, NULL); + + Iso14443_3aData iso14443_3a_poller_data = {}; mu_assert( - nfc_validate->dev_data.nfc_data.type == FuriHalNfcTypeA, - "type == FuriHalNfcTypeA assert failed\r\n"); - nfc_device_free(nfc_validate); -} - -static void mf_classic_generator_test(uint8_t uid_len, MfClassicType type) { - NfcDevice* nfc_dev = nfc_device_alloc(); - mu_assert(nfc_dev != NULL, "nfc_device_data != NULL assert failed\r\n"); - nfc_dev->format = NfcDeviceSaveFormatMifareClassic; - - // Create a test file - nfc_generate_mf_classic(&nfc_dev->dev_data, uid_len, type); - - // Get the uid from generated MFC - uint8_t uid[7] = {0}; - memcpy(uid, nfc_dev->dev_data.nfc_data.uid, uid_len); - uint8_t sak = nfc_dev->dev_data.nfc_data.sak; - uint8_t atqa[2] = {}; - memcpy(atqa, nfc_dev->dev_data.nfc_data.atqa, 2); - - MfClassicData* mf_data = &nfc_dev->dev_data.mf_classic_data; - // Check the manufacturer block (should be uid[uid_len] + BCC (for 4byte only) + SAK + ATQA0 + ATQA1 + 0xFF[rest]) - uint8_t manufacturer_block[16] = {0}; - memcpy(manufacturer_block, nfc_dev->dev_data.mf_classic_data.block[0].value, 16); + iso14443_3a_poller_read(poller, &iso14443_3a_poller_data) == Iso14443_3aErrorNone, + "iso14443_3a_poller_read() failed"); + + nfc_listener_stop(iso3_listener); mu_assert( - memcmp(manufacturer_block, uid, uid_len) == 0, - "manufacturer_block uid doesn't match the file\r\n"); + iso14443_3a_is_equal(&iso14443_3a_poller_data, &iso14443_3a_listener_data), + "Data not matches"); - uint8_t position = 0; - if(uid_len == 4) { - position = uid_len; + nfc_listener_free(iso3_listener); + nfc_free(listener); + nfc_free(poller); +} - uint8_t bcc = 0; +static void mf_ultralight_reader_test(const char* path) { + FURI_LOG_I(TAG, "Testing file: %s", path); + Nfc* poller = nfc_alloc(); + Nfc* listener = nfc_alloc(); - for(int i = 0; i < uid_len; i++) { - bcc ^= uid[i]; - } + NfcDevice* nfc_device = nfc_device_alloc(); + mu_assert(nfc_device_load(nfc_device, path), "nfc_device_load() failed\r\n"); - mu_assert(manufacturer_block[position] == bcc, "manufacturer_block bcc assert failed\r\n"); - } else { - position = uid_len - 1; - } + NfcListener* mfu_listener = nfc_listener_alloc( + listener, + NfcProtocolMfUltralight, + nfc_device_get_data(nfc_device, NfcProtocolMfUltralight)); + nfc_listener_start(mfu_listener, NULL, NULL); - mu_assert(manufacturer_block[position + 1] == sak, "manufacturer_block sak assert failed\r\n"); + MfUltralightData* mfu_data = mf_ultralight_alloc(); + MfUltralightError error = mf_ultralight_poller_read_card(poller, mfu_data); + mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_read_card() failed"); - mu_assert( - manufacturer_block[position + 2] == atqa[0], "manufacturer_block atqa0 assert failed\r\n"); + nfc_listener_stop(mfu_listener); + nfc_listener_free(mfu_listener); mu_assert( - manufacturer_block[position + 3] == atqa[1], "manufacturer_block atqa1 assert failed\r\n"); + mf_ultralight_is_equal(mfu_data, nfc_device_get_data(nfc_device, NfcProtocolMfUltralight)), + "Data not matches"); - for(uint8_t i = position + 4; i < 16; i++) { - mu_assert( - manufacturer_block[i] == 0xFF, "manufacturer_block[i] == 0xFF assert failed\r\n"); - } + mf_ultralight_free(mfu_data); + nfc_device_free(nfc_device); + nfc_free(listener); + nfc_free(poller); +} - // Reference sector trailers (should be 0xFF[6] + 0xFF + 0x07 + 0x80 + 0x69 + 0xFF[6]) - uint8_t sector_trailer[16] = { - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0x07, - 0x80, - 0x69, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF}; - // Reference block data - uint8_t block_data[16] = {}; - memset(block_data, 0xff, sizeof(block_data)); - uint16_t total_blocks = mf_classic_get_total_block_num(type); - for(size_t i = 1; i < total_blocks; i++) { - if(mf_classic_is_sector_trailer(i)) { - mu_assert( - memcmp(mf_data->block[i].value, sector_trailer, 16) == 0, - "Failed sector trailer compare"); - } else { - mu_assert(memcmp(mf_data->block[i].value, block_data, 16) == 0, "Failed data compare"); - } - } - // Save the NFC device data to the file - mu_assert( - nfc_device_save(nfc_dev, NFC_TEST_NFC_DEV_PATH), - "nfc_device_save == true assert failed\r\n"); - // Verify that key cache is saved - FuriString* key_cache_name = furi_string_alloc(); - furi_string_set_str(key_cache_name, "/ext/nfc/.cache/"); - for(size_t i = 0; i < uid_len; i++) { - furi_string_cat_printf(key_cache_name, "%02X", uid[i]); - } - furi_string_cat_printf(key_cache_name, ".keys"); - mu_assert( - storage_common_stat(nfc_dev->storage, furi_string_get_cstr(key_cache_name), NULL) == - FSE_OK, - "Key cache file save failed"); - nfc_device_free(nfc_dev); - - // Load the NFC device data from the file - NfcDevice* nfc_validate = nfc_device_alloc(); - mu_assert(nfc_validate, "Nfc device alloc assert"); - mu_assert( - nfc_device_load(nfc_validate, NFC_TEST_NFC_DEV_PATH, false), - "nfc_device_load == true assert failed\r\n"); +MU_TEST(mf_ultralight_11_reader) { + mf_ultralight_reader_test(EXT_PATH("unit_tests/nfc/Ultralight_11.nfc")); +} - // Check the UID, sak, ATQA and type - mu_assert( - memcmp(nfc_validate->dev_data.nfc_data.uid, uid, uid_len) == 0, - "uid compare assert failed\r\n"); - mu_assert(nfc_validate->dev_data.nfc_data.sak == sak, "sak compare assert failed\r\n"); +MU_TEST(mf_ultralight_21_reader) { + mf_ultralight_reader_test(EXT_PATH("unit_tests/nfc/Ultralight_21.nfc")); +} + +MU_TEST(ntag_215_reader) { + mf_ultralight_reader_test(EXT_PATH("unit_tests/nfc/Ntag215.nfc")); +} + +MU_TEST(ntag_216_reader) { + mf_ultralight_reader_test(EXT_PATH("unit_tests/nfc/Ntag216.nfc")); +} + +MU_TEST(ntag_213_locked_reader) { + FURI_LOG_I(TAG, "Testing Ntag215 locked file"); + Nfc* poller = nfc_alloc(); + Nfc* listener = nfc_alloc(); + + NfcDeviceData* nfc_device = nfc_device_alloc(); mu_assert( - memcmp(nfc_validate->dev_data.nfc_data.atqa, atqa, 2) == 0, - "atqa compare assert failed\r\n"); + nfc_device_load(nfc_device, EXT_PATH("unit_tests/nfc/Ntag213_locked.nfc")), + "nfc_device_load() failed\r\n"); + + NfcListener* mfu_listener = nfc_listener_alloc( + listener, + NfcProtocolMfUltralight, + nfc_device_get_data(nfc_device, NfcProtocolMfUltralight)); + nfc_listener_start(mfu_listener, NULL, NULL); + + MfUltralightData* mfu_data = mf_ultralight_alloc(); + MfUltralightError error = mf_ultralight_poller_read_card(poller, mfu_data); + mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_read_card() failed"); + + nfc_listener_stop(mfu_listener); + nfc_listener_free(mfu_listener); + + MfUltralightConfigPages* config = NULL; + const MfUltralightData* mfu_ref_data = + nfc_device_get_data(nfc_device, NfcProtocolMfUltralight); mu_assert( - nfc_validate->dev_data.nfc_data.type == FuriHalNfcTypeA, - "type == FuriHalNfcTypeA assert failed\r\n"); + mf_ultralight_get_config_page(mfu_ref_data, &config), + "mf_ultralight_get_config_page() failed"); + uint16_t pages_locked = config->auth0; + + mu_assert(mfu_data->pages_read == pages_locked, "Unexpected pages read"); + + mf_ultralight_free(mfu_data); + nfc_device_free(nfc_device); + nfc_free(listener); + nfc_free(poller); +} + +static void mf_ultralight_write() { + Nfc* poller = nfc_alloc(); + Nfc* listener = nfc_alloc(); + + NfcDevice* nfc_device = nfc_device_alloc(); + nfc_data_generator_fill_data(NfcDataGeneratorTypeMfUltralightEV1_21, nfc_device); + + NfcListener* mfu_listener = nfc_listener_alloc( + listener, + NfcProtocolMfUltralight, + nfc_device_get_data(nfc_device, NfcProtocolMfUltralight)); + nfc_listener_start(mfu_listener, NULL, NULL); + + MfUltralightData* mfu_data = mf_ultralight_alloc(); + + // Initial read + MfUltralightError error = mf_ultralight_poller_read_card(poller, mfu_data); + mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_read_card() failed"); - // Check the manufacturer block mu_assert( - memcmp(nfc_validate->dev_data.mf_classic_data.block[0].value, manufacturer_block, 16) == 0, - "manufacturer_block assert failed\r\n"); - // Check other blocks - for(size_t i = 1; i < total_blocks; i++) { - if(mf_classic_is_sector_trailer(i)) { - mu_assert( - memcmp(mf_data->block[i].value, sector_trailer, 16) == 0, - "Failed sector trailer compare"); - } else { - mu_assert(memcmp(mf_data->block[i].value, block_data, 16) == 0, "Failed data compare"); - } - } - nfc_device_free(nfc_validate); - - // Check saved key cache - NfcDevice* nfc_keys = nfc_device_alloc(); - mu_assert(nfc_validate, "Nfc device alloc assert"); - nfc_keys->dev_data.nfc_data.uid_len = uid_len; - memcpy(nfc_keys->dev_data.nfc_data.uid, uid, uid_len); - mu_assert(nfc_device_load_key_cache(nfc_keys), "Failed to load key cache"); - uint8_t total_sec = mf_classic_get_total_sectors_num(type); - uint8_t default_key[6] = {}; - memset(default_key, 0xff, 6); - for(size_t i = 0; i < total_sec; i++) { - MfClassicSectorTrailer* sec_tr = - mf_classic_get_sector_trailer_by_sector(&nfc_keys->dev_data.mf_classic_data, i); - mu_assert(memcmp(sec_tr->key_a, default_key, 6) == 0, "Failed key compare"); - mu_assert(memcmp(sec_tr->key_b, default_key, 6) == 0, "Failed key compare"); + mf_ultralight_is_equal(mfu_data, nfc_device_get_data(nfc_device, NfcProtocolMfUltralight)), + "Data not matches"); + + // Write random data + for(size_t i = 5; i < 15; i++) { + MfUltralightPage page = {}; + FURI_LOG_D(TAG, "Writing page %d", i); + furi_hal_random_fill_buf(page.data, sizeof(MfUltralightPage)); + mfu_data->page[i] = page; + error = mf_ultralight_poller_write_page(poller, i, &page); + mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_write_page() failed"); } - // Delete key cache file - mu_assert( - storage_common_remove(nfc_keys->storage, furi_string_get_cstr(key_cache_name)) == FSE_OK, - "Failed to remove key cache file"); - furi_string_free(key_cache_name); - nfc_device_free(nfc_keys); -} + // Verification read + error = mf_ultralight_poller_read_card(poller, mfu_data); + mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_read_card() failed"); + + nfc_listener_stop(mfu_listener); + const MfUltralightData* mfu_listener_data = + nfc_listener_get_data(mfu_listener, NfcProtocolMfUltralight); -MU_TEST(mf_mini_file_test) { - mf_classic_generator_test(4, MfClassicTypeMini); + mu_assert(mf_ultralight_is_equal(mfu_data, mfu_listener_data), "Data not matches"); + + nfc_listener_free(mfu_listener); + mf_ultralight_free(mfu_data); + nfc_device_free(nfc_device); + nfc_free(listener); + nfc_free(poller); } -MU_TEST(mf_classic_1k_4b_file_test) { - mf_classic_generator_test(4, MfClassicType1k); +static void mf_classic_reader() { + Nfc* poller = nfc_alloc(); + Nfc* listener = nfc_alloc(); + + NfcDevice* nfc_device = nfc_device_alloc(); + nfc_data_generator_fill_data(NfcDataGeneratorTypeMfClassic4k_7b, nfc_device); + NfcListener* mfc_listener = nfc_listener_alloc( + listener, NfcProtocolMfClassic, nfc_device_get_data(nfc_device, NfcProtocolMfClassic)); + nfc_listener_start(mfc_listener, NULL, NULL); + + MfClassicBlock block = {}; + MfClassicKey key = {.data = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}; + + mf_classic_poller_read_block(poller, 0, &key, MfClassicKeyTypeA, &block); + + nfc_listener_stop(mfc_listener); + nfc_listener_free(mfc_listener); + + const MfClassicData* mfc_data = nfc_device_get_data(nfc_device, NfcProtocolMfClassic); + mu_assert(memcmp(&mfc_data->block[0], &block, sizeof(MfClassicBlock)) == 0, "Data mismatch"); + + nfc_device_free(nfc_device); + nfc_free(listener); + nfc_free(poller); } -MU_TEST(mf_classic_4k_4b_file_test) { - mf_classic_generator_test(4, MfClassicType4k); +static void mf_classic_write() { + Nfc* poller = nfc_alloc(); + Nfc* listener = nfc_alloc(); + + NfcDevice* nfc_device = nfc_device_alloc(); + nfc_data_generator_fill_data(NfcDataGeneratorTypeMfClassic4k_7b, nfc_device); + NfcListener* mfc_listener = nfc_listener_alloc( + listener, NfcProtocolMfClassic, nfc_device_get_data(nfc_device, NfcProtocolMfClassic)); + nfc_listener_start(mfc_listener, NULL, NULL); + + MfClassicBlock block_write = {}; + MfClassicBlock block_read = {}; + furi_hal_random_fill_buf(block_write.data, sizeof(MfClassicBlock)); + MfClassicKey key = {.data = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}; + + mf_classic_poller_write_block(poller, 1, &key, MfClassicKeyTypeA, &block_write); + mf_classic_poller_read_block(poller, 1, &key, MfClassicKeyTypeA, &block_read); + + nfc_listener_stop(mfc_listener); + nfc_listener_free(mfc_listener); + + mu_assert(memcmp(&block_read, &block_write, sizeof(MfClassicBlock)) == 0, "Data mismatch"); + + nfc_device_free(nfc_device); + nfc_free(listener); + nfc_free(poller); } -MU_TEST(mf_classic_1k_7b_file_test) { - mf_classic_generator_test(7, MfClassicType1k); +static void mf_classic_value_block() { + Nfc* poller = nfc_alloc(); + Nfc* listener = nfc_alloc(); + + NfcDevice* nfc_device = nfc_device_alloc(); + nfc_data_generator_fill_data(NfcDataGeneratorTypeMfClassic4k_7b, nfc_device); + NfcListener* mfc_listener = nfc_listener_alloc( + listener, NfcProtocolMfClassic, nfc_device_get_data(nfc_device, NfcProtocolMfClassic)); + nfc_listener_start(mfc_listener, NULL, NULL); + + MfClassicKey key = {.data = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}; + + int32_t value = 228; + MfClassicBlock block_write = {}; + mf_classic_value_to_block(value, 1, &block_write); + + MfClassicError error = MfClassicErrorNone; + error = mf_classic_poller_write_block(poller, 1, &key, MfClassicKeyTypeA, &block_write); + mu_assert(error == MfClassicErrorNone, "Write failed"); + + int32_t data = 200; + int32_t new_value = 0; + error = mf_classic_poller_change_value(poller, 1, &key, MfClassicKeyTypeA, data, &new_value); + mu_assert(error == MfClassicErrorNone, "Value increment failed"); + mu_assert(new_value == value + data, "Value not match"); + + error = mf_classic_poller_change_value(poller, 1, &key, MfClassicKeyTypeA, -data, &new_value); + mu_assert(error == MfClassicErrorNone, "Value decrement failed"); + mu_assert(new_value == value, "Value not match"); + + nfc_listener_stop(mfc_listener); + nfc_listener_free(mfc_listener); + nfc_device_free(nfc_device); + nfc_free(listener); + nfc_free(poller); } -MU_TEST(mf_classic_4k_7b_file_test) { - mf_classic_generator_test(7, MfClassicType4k); +MU_TEST(mf_classic_dict_test) { + Storage* storage = furi_record_open(RECORD_STORAGE); + if(storage_common_stat(storage, NFC_APP_MF_CLASSIC_DICT_UNIT_TEST_PATH, NULL) == FSE_OK) { + mu_assert( + storage_simply_remove(storage, NFC_APP_MF_CLASSIC_DICT_UNIT_TEST_PATH), + "Remove test dict failed"); + } + + NfcDict* dict = nfc_dict_alloc( + NFC_APP_MF_CLASSIC_DICT_UNIT_TEST_PATH, NfcDictModeOpenAlways, sizeof(MfClassicKey)); + mu_assert(dict != NULL, "nfc_dict_alloc() failed"); + + size_t dict_keys_total = nfc_dict_get_total_keys(dict); + mu_assert(dict_keys_total == 0, "nfc_dict_keys_total() failed"); + + const uint32_t test_key_num = 30; + MfClassicKey* key_arr_ref = malloc(test_key_num * sizeof(MfClassicKey)); + for(size_t i = 0; i < test_key_num; i++) { + furi_hal_random_fill_buf(key_arr_ref[i].data, sizeof(MfClassicKey)); + mu_assert( + nfc_dict_add_key(dict, key_arr_ref[i].data, sizeof(MfClassicKey)), "add key failed"); + + size_t dict_keys_total = nfc_dict_get_total_keys(dict); + mu_assert(dict_keys_total == (i + 1), "nfc_dict_keys_total() failed"); + } + + nfc_dict_free(dict); + + dict = nfc_dict_alloc( + NFC_APP_MF_CLASSIC_DICT_UNIT_TEST_PATH, NfcDictModeOpenAlways, sizeof(MfClassicKey)); + mu_assert(dict != NULL, "nfc_dict_alloc() failed"); + + dict_keys_total = nfc_dict_get_total_keys(dict); + mu_assert(dict_keys_total == test_key_num, "nfc_dict_keys_total() failed"); + + MfClassicKey key_dut = {}; + size_t key_idx = 0; + while(nfc_dict_get_next_key(dict, key_dut.data, sizeof(MfClassicKey))) { + mu_assert( + memcmp(key_arr_ref[key_idx].data, key_dut.data, sizeof(MfClassicKey)) == 0, + "Loaded key data mismatch"); + key_idx++; + } + + uint32_t delete_keys_idx[] = {1, 3, 9, 11, 19, 27}; + + for(size_t i = 0; i < COUNT_OF(delete_keys_idx); i++) { + MfClassicKey* key = &key_arr_ref[delete_keys_idx[i]]; + mu_assert( + nfc_dict_is_key_present(dict, key->data, sizeof(MfClassicKey)), + "nfc_dict_is_key_present() failed"); + mu_assert( + nfc_dict_delete_key(dict, key->data, sizeof(MfClassicKey)), + "nfc_dict_delete_key() failed"); + } + + dict_keys_total = nfc_dict_get_total_keys(dict); + mu_assert( + dict_keys_total == test_key_num - COUNT_OF(delete_keys_idx), + "nfc_dict_keys_total() failed"); + + nfc_dict_free(dict); + free(key_arr_ref); + + mu_assert( + storage_simply_remove(storage, NFC_APP_MF_CLASSIC_DICT_UNIT_TEST_PATH), + "Remove test dict failed"); } MU_TEST_SUITE(nfc) { nfc_test_alloc(); - MU_RUN_TEST(nfca_file_test); - MU_RUN_TEST(mf_mini_file_test); + MU_RUN_TEST(iso14443_3a_reader); + MU_RUN_TEST(mf_ultralight_11_reader); + MU_RUN_TEST(mf_ultralight_21_reader); + MU_RUN_TEST(ntag_215_reader); + MU_RUN_TEST(ntag_216_reader); + MU_RUN_TEST(ntag_213_locked_reader); + + MU_RUN_TEST(mf_ultralight_write); + + MU_RUN_TEST(iso14443_3a_4b_file_test); + MU_RUN_TEST(iso14443_3a_7b_file_test); + + MU_RUN_TEST(mf_ultralight_file_test); + MU_RUN_TEST(mf_ultralight_ev1_11_file_test); + MU_RUN_TEST(mf_ultralight_ev1_h11_file_test); + MU_RUN_TEST(mf_ultralight_ev1_21_file_test); + MU_RUN_TEST(mf_ultralight_ev1_h21_file_test); + MU_RUN_TEST(mf_ultralight_ntag_203_file_test); + MU_RUN_TEST(mf_ultralight_ntag_213_file_test); + MU_RUN_TEST(mf_ultralight_ntag_215_file_test); + MU_RUN_TEST(mf_ultralight_ntag_216_file_test); + MU_RUN_TEST(mf_ultralight_ntag_i2c_1k_file_test); + MU_RUN_TEST(mf_ultralight_ntag_i2c_2k_file_test); + MU_RUN_TEST(mf_ultralight_ntag_i2c_plus_1k_file_test); + MU_RUN_TEST(mf_ultralight_ntag_i2c_plus_2k_file_test); + + MU_RUN_TEST(mf_classic_mini_file_test); MU_RUN_TEST(mf_classic_1k_4b_file_test); - MU_RUN_TEST(mf_classic_4k_4b_file_test); MU_RUN_TEST(mf_classic_1k_7b_file_test); + MU_RUN_TEST(mf_classic_4k_4b_file_test); MU_RUN_TEST(mf_classic_4k_7b_file_test); - MU_RUN_TEST(nfc_digital_signal_test); + MU_RUN_TEST(mf_classic_reader); + + MU_RUN_TEST(mf_classic_write); + MU_RUN_TEST(mf_classic_value_block); + MU_RUN_TEST(mf_classic_dict_test); - MU_RUN_TEST(mf_classic_dict_load_test); nfc_test_free(); } diff --git a/applications/debug/unit_tests/nfc/nfc_transport.c b/applications/debug/unit_tests/nfc/nfc_transport.c new file mode 100644 index 00000000000..ee2657bea17 --- /dev/null +++ b/applications/debug/unit_tests/nfc/nfc_transport.c @@ -0,0 +1,458 @@ +#ifdef FW_CFG_unit_tests + +#include +#include +#include + +#include + +#define NFC_MAX_BUFFER_SIZE (256) + +typedef enum { + NfcTransportLogLevelWarning, + NfcTransportLogLevelInfo, +} NfcTransportLogLevel; + +FuriMessageQueue* poller_queue = NULL; +FuriMessageQueue* listener_queue = NULL; + +typedef enum { + NfcMessageTypeTx, + NfcMessageTypeTimeout, + NfcMessageTypeAbort, +} NfcMessageType; + +typedef struct { + uint16_t data_bits; + uint8_t data[NFC_MAX_BUFFER_SIZE]; +} NfcMessageData; + +typedef struct { + NfcMessageType type; + NfcMessageData data; +} NfcMessage; + +typedef enum { + NfcStateIdle, + NfcStateReady, + NfcStateReset, +} NfcState; + +typedef enum { + Iso14443_3aColResStatusIdle, + Iso14443_3aColResStatusInProgress, + Iso14443_3aColResStatusDone, +} Iso14443_3aColResStatus; + +typedef struct { + Iso14443_3aSensResp sens_resp; + Iso14443_3aSddResp sdd_resp[2]; + Iso14443_3aSelResp sel_resp[2]; +} Iso14443_3aColResData; + +struct Nfc { + NfcState state; + + Iso14443_3aColResStatus col_res_status; + Iso14443_3aColResData col_res_data; + + NfcEventCallback callback; + void* context; + + NfcMode mode; + + FuriThread* worker_thread; +}; + +static void nfc_test_print( + NfcTransportLogLevel log_level, + const char* message, + uint8_t* buffer, + uint16_t bits) { + FuriString* str = furi_string_alloc(); + size_t bytes = (bits + 7) / 8; + + for(size_t i = 0; i < bytes; i++) { + furi_string_cat_printf(str, " %02X", buffer[i]); + } + if(log_level == NfcTransportLogLevelWarning) { + FURI_LOG_W(message, "%s", furi_string_get_cstr(str)); + } else { + FURI_LOG_I(message, "%s", furi_string_get_cstr(str)); + } + + furi_string_free(str); +} + +static void nfc_prepare_col_res_data( + Nfc* instance, + uint8_t* uid, + uint8_t uid_len, + uint8_t* atqa, + uint8_t sak) { + memcpy(instance->col_res_data.sens_resp.sens_resp, atqa, 2); + + if(uid_len == 7) { + instance->col_res_data.sdd_resp[0].nfcid[0] = 0x88; + memcpy(&instance->col_res_data.sdd_resp[0].nfcid[1], uid, 3); + uint8_t bss = 0; + for(size_t i = 0; i < 4; i++) { + bss ^= instance->col_res_data.sdd_resp[0].nfcid[i]; + } + instance->col_res_data.sdd_resp[0].bss = bss; + instance->col_res_data.sel_resp[0].sak = 0x04; + + memcpy(instance->col_res_data.sdd_resp[1].nfcid, &uid[3], 4); + bss = 0; + for(size_t i = 0; i < 4; i++) { + bss ^= instance->col_res_data.sdd_resp[1].nfcid[i]; + } + instance->col_res_data.sdd_resp[1].bss = bss; + instance->col_res_data.sel_resp[1].sak = sak; + + } else { + furi_crash("Not supporting not 7 bytes"); + } +} + +Nfc* nfc_alloc() { + Nfc* instance = malloc(sizeof(Nfc)); + + return instance; +} + +void nfc_free(Nfc* instance) { + furi_assert(instance); + + free(instance); +} + +void nfc_config(Nfc* instance, NfcMode mode, NfcTech tech) { + UNUSED(instance); + UNUSED(tech); + + instance->mode = mode; +} + +void nfc_set_fdt_poll_fc(Nfc* instance, uint32_t fdt_poll_fc) { + UNUSED(instance); + UNUSED(fdt_poll_fc); +} + +void nfc_set_fdt_listen_fc(Nfc* instance, uint32_t fdt_listen_fc) { + UNUSED(instance); + UNUSED(fdt_listen_fc); +} + +void nfc_set_mask_receive_time_fc(Nfc* instance, uint32_t mask_rx_time_fc) { + UNUSED(instance); + UNUSED(mask_rx_time_fc); +} + +void nfc_set_fdt_poll_poll_us(Nfc* instance, uint32_t fdt_poll_poll_us) { + UNUSED(instance); + UNUSED(fdt_poll_poll_us); +} + +void nfc_set_guard_time_us(Nfc* instance, uint32_t guard_time_us) { + UNUSED(instance); + UNUSED(guard_time_us); +} + +NfcError nfc_iso14443a_listener_set_col_res_data( + Nfc* instance, + uint8_t* uid, + uint8_t uid_len, + uint8_t* atqa, + uint8_t sak) { + furi_assert(instance); + furi_assert(uid); + furi_assert(atqa); + + nfc_prepare_col_res_data(instance, uid, uid_len, atqa, sak); + + return NfcErrorNone; +} + +static int32_t nfc_worker_poller(void* context) { + Nfc* instance = context; + furi_assert(instance->callback); + + instance->state = NfcStateReady; + NfcCommand command = NfcCommandContinue; + NfcEvent event = {}; + + while(true) { + event.type = NfcEventTypePollerReady; + command = instance->callback(event, instance->context); + if(command == NfcCommandStop) { + break; + } + } + + instance->state = NfcStateIdle; + + return 0; +} + +static void nfc_worker_listener_pass_col_res(Nfc* instance, uint8_t* rx_data, uint16_t rx_bits) { + furi_assert(instance->col_res_status != Iso14443_3aColResStatusDone); + BitBuffer* tx_buffer = bit_buffer_alloc(NFC_MAX_BUFFER_SIZE); + + bool processed = false; + + if((rx_bits == 7) && (rx_data[0] == 0x52)) { + instance->col_res_status = Iso14443_3aColResStatusInProgress; + bit_buffer_copy_bytes( + tx_buffer, + instance->col_res_data.sens_resp.sens_resp, + sizeof(instance->col_res_data.sens_resp.sens_resp)); + nfc_listener_tx(instance, tx_buffer); + processed = true; + } else if(rx_bits == 2 * 8) { + if((rx_data[0] == 0x93) && (rx_data[1] == 0x20)) { + bit_buffer_copy_bytes( + tx_buffer, + (const uint8_t*)&instance->col_res_data.sdd_resp[0], + sizeof(Iso14443_3aSddResp)); + nfc_listener_tx(instance, tx_buffer); + processed = true; + } else if((rx_data[0] == 0x95) && (rx_data[1] == 0x20)) { + bit_buffer_copy_bytes( + tx_buffer, + (const uint8_t*)&instance->col_res_data.sdd_resp[1], + sizeof(Iso14443_3aSddResp)); + nfc_listener_tx(instance, tx_buffer); + processed = true; + } + } else if(rx_bits == 9 * 8) { + if((rx_data[0] == 0x93) && (rx_data[1] == 0x70)) { + bit_buffer_set_size_bytes(tx_buffer, 1); + bit_buffer_set_byte(tx_buffer, 0, instance->col_res_data.sel_resp[0].sak); + iso14443_crc_append(Iso14443CrcTypeA, tx_buffer); + nfc_listener_tx(instance, tx_buffer); + processed = true; + } else if((rx_data[0] == 0x95) && (rx_data[1] == 0x70)) { + bit_buffer_set_size_bytes(tx_buffer, 1); + bit_buffer_set_byte(tx_buffer, 0, instance->col_res_data.sel_resp[1].sak); + iso14443_crc_append(Iso14443CrcTypeA, tx_buffer); + nfc_listener_tx(instance, tx_buffer); + instance->col_res_status = Iso14443_3aColResStatusDone; + NfcEvent event = {.type = NfcEventTypeListenerActivated}; + instance->callback(event, instance->context); + + processed = true; + } + } + + if(!processed) { + NfcMessage message = {.type = NfcMessageTypeTimeout}; + furi_message_queue_put(poller_queue, &message, FuriWaitForever); + } + + bit_buffer_free(tx_buffer); +} + +static int32_t nfc_worker_listener(void* context) { + Nfc* instance = context; + furi_assert(instance->callback); + + NfcMessage message = {}; + + NfcEventData event_data = {}; + event_data.buffer = bit_buffer_alloc(NFC_MAX_BUFFER_SIZE); + NfcEvent nfc_event = {.data = event_data}; + + while(true) { + furi_message_queue_get(listener_queue, &message, FuriWaitForever); + bit_buffer_copy_bits(event_data.buffer, message.data.data, message.data.data_bits); + if((message.data.data[0] == 0x52) && (message.data.data_bits == 7)) { + instance->col_res_status = Iso14443_3aColResStatusIdle; + } + + if(message.type == NfcMessageTypeAbort) { + break; + } else if(message.type == NfcMessageTypeTx) { + nfc_test_print( + NfcTransportLogLevelInfo, "RDR", message.data.data, message.data.data_bits); + if(instance->col_res_status != Iso14443_3aColResStatusDone) { + nfc_worker_listener_pass_col_res( + instance, message.data.data, message.data.data_bits); + } else { + instance->state = NfcStateReady; + nfc_event.type = NfcEventTypeRxEnd; + instance->callback(nfc_event, instance->context); + } + } + } + + instance->state = NfcStateIdle; + instance->col_res_status = Iso14443_3aColResStatusIdle; + memset(&instance->col_res_data, 0, sizeof(instance->col_res_data)); + bit_buffer_free(nfc_event.data.buffer); + + return 0; +} + +void nfc_start(Nfc* instance, NfcEventCallback callback, void* context) { + furi_assert(instance); + furi_assert(instance->worker_thread == NULL); + + if(instance->mode == NfcModeListener) { + furi_assert(listener_queue == NULL); + // Check that poller didn't start + furi_assert(poller_queue == NULL); + } else { + furi_assert(poller_queue == NULL); + // Check that poller is started after listener + furi_assert(listener_queue); + } + + instance->callback = callback; + instance->context = context; + + if(instance->mode == NfcModeListener) { + listener_queue = furi_message_queue_alloc(4, sizeof(NfcMessage)); + } else { + poller_queue = furi_message_queue_alloc(4, sizeof(NfcMessage)); + } + + instance->worker_thread = furi_thread_alloc(); + furi_thread_set_context(instance->worker_thread, instance); + furi_thread_set_priority(instance->worker_thread, FuriThreadPriorityHigh); + furi_thread_set_stack_size(instance->worker_thread, 8 * 1024); + + if(instance->mode == NfcModeListener) { + furi_thread_set_name(instance->worker_thread, "NfcWorkerListener"); + furi_thread_set_callback(instance->worker_thread, nfc_worker_listener); + } else { + furi_thread_set_name(instance->worker_thread, "NfcWorkerPoller"); + furi_thread_set_callback(instance->worker_thread, nfc_worker_poller); + } + + furi_thread_start(instance->worker_thread); +} + +void nfc_stop(Nfc* instance) { + furi_assert(instance); + furi_assert(instance->worker_thread); + + if(instance->mode == NfcModeListener) { + NfcMessage message = {.type = NfcMessageTypeAbort}; + furi_message_queue_put(listener_queue, &message, FuriWaitForever); + furi_thread_join(instance->worker_thread); + + furi_message_queue_free(listener_queue); + listener_queue = NULL; + + furi_thread_free(instance->worker_thread); + instance->worker_thread = NULL; + } else { + furi_thread_join(instance->worker_thread); + + furi_message_queue_free(poller_queue); + poller_queue = NULL; + + furi_thread_free(instance->worker_thread); + instance->worker_thread = NULL; + } +} + +// Called from worker thread + +NfcError nfc_listener_tx(Nfc* instance, const BitBuffer* tx_buffer) { + furi_assert(instance); + furi_assert(poller_queue); + furi_assert(listener_queue); + furi_assert(tx_buffer); + + NfcMessage message = {}; + message.type = NfcMessageTypeTx; + message.data.data_bits = bit_buffer_get_size(tx_buffer); + bit_buffer_write_bytes(tx_buffer, message.data.data, bit_buffer_get_size_bytes(tx_buffer)); + + furi_message_queue_put(poller_queue, &message, FuriWaitForever); + + return NfcErrorNone; +} + +NfcError nfc_iso14443a_listener_tx_custom_parity(Nfc* instance, const BitBuffer* tx_buffer) { + return nfc_listener_tx(instance, tx_buffer); +} + +NfcError + nfc_poller_trx(Nfc* instance, const BitBuffer* tx_buffer, BitBuffer* rx_buffer, uint32_t fwt) { + furi_assert(instance); + furi_assert(tx_buffer); + furi_assert(rx_buffer); + furi_assert(poller_queue); + furi_assert(listener_queue); + UNUSED(fwt); + + NfcError error = NfcErrorNone; + + NfcMessage message = {}; + message.type = NfcMessageTypeTx; + message.data.data_bits = bit_buffer_get_size(tx_buffer); + bit_buffer_write_bytes(tx_buffer, message.data.data, bit_buffer_get_size_bytes(tx_buffer)); + // Tx + furi_assert(furi_message_queue_put(listener_queue, &message, FuriWaitForever) == FuriStatusOk); + // Rx + FuriStatus status = furi_message_queue_get(poller_queue, &message, 50); + + if(status == FuriStatusErrorTimeout) { + error = NfcErrorTimeout; + } else if(message.type == NfcMessageTypeTx) { + bit_buffer_copy_bits(rx_buffer, message.data.data, message.data.data_bits); + nfc_test_print( + NfcTransportLogLevelWarning, "TAG", message.data.data, message.data.data_bits); + } else if(message.type == NfcMessageTypeTimeout) { + error = NfcErrorTimeout; + } + + return error; +} + +NfcError nfc_iso14443a_poller_trx_custom_parity( + Nfc* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt) { + return nfc_poller_trx(instance, tx_buffer, rx_buffer, fwt); +} + +// Technology specific API + +NfcError nfc_iso14443a_poller_trx_short_frame( + Nfc* instance, + NfcIso14443aShortFrame frame, + BitBuffer* rx_buffer, + uint32_t fwt) { + UNUSED(frame); + + BitBuffer* tx_buffer = bit_buffer_alloc(32); + bit_buffer_set_size(tx_buffer, 7); + bit_buffer_set_byte(tx_buffer, 0, 0x52); + + NfcError error = nfc_poller_trx(instance, tx_buffer, rx_buffer, fwt); + + bit_buffer_free(tx_buffer); + + return error; +} + +NfcError nfc_iso14443a_poller_trx_sdd_frame( + Nfc* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt) { + return nfc_poller_trx(instance, tx_buffer, rx_buffer, fwt); +} + +NfcError nfc_iso15693_listener_tx_sof(Nfc* instance) { + UNUSED(instance); + + return NfcErrorNone; +} + +#endif diff --git a/applications/main/nfc/application.fam b/applications/main/nfc/application.fam index bc34d4e6588..20ebd4ca081 100644 --- a/applications/main/nfc/application.fam +++ b/applications/main/nfc/application.fam @@ -12,6 +12,56 @@ App( fap_category="NFC", ) +# Parser plugins + +App( + appid="all_in_one_parser", + apptype=FlipperAppType.PLUGIN, + entry_point="all_in_one_plugin_ep", + targets=["f7"], + requires=["nfc"], +) + +App( + appid="opal_parser", + apptype=FlipperAppType.PLUGIN, + entry_point="opal_plugin_ep", + targets=["f7"], + requires=["nfc"], +) + +App( + appid="myki_parser", + apptype=FlipperAppType.PLUGIN, + entry_point="myki_plugin_ep", + targets=["f7"], + requires=["nfc"], +) + +App( + appid="troika_parser", + apptype=FlipperAppType.PLUGIN, + entry_point="troika_plugin_ep", + targets=["f7"], + requires=["nfc"], +) + +App( + appid="plantain_parser", + apptype=FlipperAppType.PLUGIN, + entry_point="plantain_plugin_ep", + targets=["f7"], + requires=["nfc"], +) + +App( + appid="two_cities_parser", + apptype=FlipperAppType.PLUGIN, + entry_point="two_cities_plugin_ep", + targets=["f7"], + requires=["nfc"], +) + App( appid="nfc_start", targets=["f7"], diff --git a/applications/main/nfc/helpers/mf_classic_key_cache.c b/applications/main/nfc/helpers/mf_classic_key_cache.c new file mode 100644 index 00000000000..5127e645209 --- /dev/null +++ b/applications/main/nfc/helpers/mf_classic_key_cache.c @@ -0,0 +1,212 @@ +#include "mf_classic_key_cache.h" + +#include +#include + +#define NFC_APP_KEYS_EXTENSION ".keys" +#define NFC_APP_KEY_CACHE_FOLDER "/ext/nfc/.cache" + +static const char* mf_classic_key_cache_file_header = "Flipper NFC keys"; +static const uint32_t mf_classic_key_cache_file_version = 1; + +struct MfClassicKeyCache { + MfClassicDeviceKeys keys; + MfClassicKeyType current_key_type; + uint8_t current_sector; +}; + +static void nfc_get_key_cache_file_path(const uint8_t* uid, size_t uid_len, FuriString* path) { + furi_string_printf(path, "%s/", NFC_APP_KEY_CACHE_FOLDER); + for(size_t i = 0; i < uid_len; i++) { + furi_string_cat_printf(path, "%02X", uid[i]); + } + furi_string_cat_printf(path, "%s", NFC_APP_KEYS_EXTENSION); +} + +MfClassicKeyCache* mf_classic_key_cache_alloc() { + MfClassicKeyCache* instance = malloc(sizeof(MfClassicKeyCache)); + + return instance; +} + +void mf_classic_key_cache_free(MfClassicKeyCache* instance) { + furi_assert(instance); + + free(instance); +} + +bool mf_classic_key_cache_save(MfClassicKeyCache* instance, const MfClassicData* data) { + UNUSED(instance); + furi_assert(data); + + size_t uid_len = 0; + const uint8_t* uid = mf_classic_get_uid(data, &uid_len); + FuriString* file_path = furi_string_alloc(); + nfc_get_key_cache_file_path(uid, uid_len, file_path); + + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* ff = flipper_format_buffered_file_alloc(storage); + + FuriString* temp_str = furi_string_alloc(); + bool save_success = false; + do { + if(!storage_simply_mkdir(storage, NFC_APP_KEY_CACHE_FOLDER)) break; + if(!storage_simply_remove(storage, furi_string_get_cstr(file_path))) break; + if(!flipper_format_buffered_file_open_always(ff, furi_string_get_cstr(file_path))) break; + + if(!flipper_format_write_header_cstr( + ff, mf_classic_key_cache_file_header, mf_classic_key_cache_file_version)) + break; + if(!flipper_format_write_string_cstr( + ff, "Mifare Classic type", mf_classic_get_device_name(data, NfcDeviceNameTypeShort))) + break; + if(!flipper_format_write_hex_uint64(ff, "Key A map", &data->key_a_mask, 1)) break; + if(!flipper_format_write_hex_uint64(ff, "Key B map", &data->key_b_mask, 1)) break; + + uint8_t sector_num = mf_classic_get_total_sectors_num(data->type); + bool key_save_success = true; + for(size_t i = 0; (i < sector_num) && (key_save_success); i++) { + MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, i); + if(FURI_BIT(data->key_a_mask, i)) { + furi_string_printf(temp_str, "Key A sector %d", i); + key_save_success = flipper_format_write_hex( + ff, furi_string_get_cstr(temp_str), sec_tr->key_a.data, sizeof(MfClassicKey)); + } + if(!key_save_success) break; + if(FURI_BIT(data->key_b_mask, i)) { + furi_string_printf(temp_str, "Key B sector %d", i); + key_save_success = flipper_format_write_hex( + ff, furi_string_get_cstr(temp_str), sec_tr->key_b.data, sizeof(MfClassicKey)); + } + } + save_success = key_save_success; + } while(false); + + flipper_format_free(ff); + furi_string_free(temp_str); + furi_string_free(file_path); + furi_record_close(RECORD_STORAGE); + + return save_success; +} + +bool mf_classic_key_cache_load(MfClassicKeyCache* instance, const uint8_t* uid, size_t uid_len) { + furi_assert(instance); + furi_assert(uid); + + mf_classic_key_cache_reset(instance); + + FuriString* file_path = furi_string_alloc(); + nfc_get_key_cache_file_path(uid, uid_len, file_path); + + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* ff = flipper_format_buffered_file_alloc(storage); + + FuriString* temp_str = furi_string_alloc(); + bool load_success = false; + do { + if(!flipper_format_buffered_file_open_existing(ff, furi_string_get_cstr(file_path))) break; + + uint32_t version = 0; + if(!flipper_format_read_header(ff, temp_str, &version)) break; + if(furi_string_cmp_str(temp_str, mf_classic_key_cache_file_header)) break; + if(version != mf_classic_key_cache_file_version) break; + + if(!flipper_format_read_hex_uint64(ff, "Key A map", &instance->keys.key_a_mask, 1)) break; + if(!flipper_format_read_hex_uint64(ff, "Key B map", &instance->keys.key_b_mask, 1)) break; + + bool key_read_success = true; + for(size_t i = 0; (i < MF_CLASSIC_TOTAL_SECTORS_MAX) && (key_read_success); i++) { + if(FURI_BIT(instance->keys.key_a_mask, i)) { + furi_string_printf(temp_str, "Key A sector %d", i); + key_read_success = flipper_format_read_hex( + ff, + furi_string_get_cstr(temp_str), + instance->keys.key_a[i].data, + sizeof(MfClassicKey)); + } + if(!key_read_success) break; + if(FURI_BIT(instance->keys.key_b_mask, i)) { + furi_string_printf(temp_str, "Key B sector %d", i); + key_read_success = flipper_format_read_hex( + ff, + furi_string_get_cstr(temp_str), + instance->keys.key_b[i].data, + sizeof(MfClassicKey)); + } + } + load_success = key_read_success; + } while(false); + + flipper_format_buffered_file_close(ff); + flipper_format_free(ff); + furi_string_free(temp_str); + furi_string_free(file_path); + furi_record_close(RECORD_STORAGE); + + return load_success; +} + +void mf_classic_key_cache_load_from_data(MfClassicKeyCache* instance, const MfClassicData* data) { + furi_assert(instance); + furi_assert(data); + + mf_classic_key_cache_reset(instance); + instance->keys.key_a_mask = data->key_a_mask; + instance->keys.key_b_mask = data->key_b_mask; + for(size_t i = 0; i < MF_CLASSIC_TOTAL_SECTORS_MAX; i++) { + MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, i); + + if(FURI_BIT(data->key_a_mask, i)) { + instance->keys.key_a[i] = sec_tr->key_a; + } + if(FURI_BIT(data->key_b_mask, i)) { + instance->keys.key_b[i] = sec_tr->key_b; + } + } +} + +bool mf_classic_key_cahce_get_next_key( + MfClassicKeyCache* instance, + uint8_t* sector_num, + MfClassicKey* key, + MfClassicKeyType* key_type) { + furi_assert(instance); + furi_assert(sector_num); + furi_assert(key); + furi_assert(key_type); + + bool next_key_found = false; + for(uint8_t i = instance->current_sector; i < MF_CLASSIC_TOTAL_SECTORS_MAX; i++) { + if(FURI_BIT(instance->keys.key_a_mask, i)) { + FURI_BIT_CLEAR(instance->keys.key_a_mask, i); + *key = instance->keys.key_a[i]; + *key_type = MfClassicKeyTypeA; + *sector_num = i; + + next_key_found = true; + break; + } + if(FURI_BIT(instance->keys.key_b_mask, i)) { + FURI_BIT_CLEAR(instance->keys.key_b_mask, i); + *key = instance->keys.key_b[i]; + *key_type = MfClassicKeyTypeB; + *sector_num = i; + + next_key_found = true; + instance->current_sector = i; + break; + } + } + + return next_key_found; +} + +void mf_classic_key_cache_reset(MfClassicKeyCache* instance) { + furi_assert(instance); + + instance->current_key_type = MfClassicKeyTypeA; + instance->current_sector = 0; + instance->keys.key_a_mask = 0; + instance->keys.key_b_mask = 0; +} diff --git a/applications/main/nfc/helpers/mf_classic_key_cache.h b/applications/main/nfc/helpers/mf_classic_key_cache.h new file mode 100644 index 00000000000..407c6e28bd4 --- /dev/null +++ b/applications/main/nfc/helpers/mf_classic_key_cache.h @@ -0,0 +1,31 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct MfClassicKeyCache MfClassicKeyCache; + +MfClassicKeyCache* mf_classic_key_cache_alloc(); + +void mf_classic_key_cache_free(MfClassicKeyCache* instance); + +bool mf_classic_key_cache_load(MfClassicKeyCache* instance, const uint8_t* uid, size_t uid_len); + +void mf_classic_key_cache_load_from_data(MfClassicKeyCache* instance, const MfClassicData* data); + +bool mf_classic_key_cahce_get_next_key( + MfClassicKeyCache* instance, + uint8_t* sector_num, + MfClassicKey* key, + MfClassicKeyType* key_type); + +bool mf_classic_key_cache_save(MfClassicKeyCache* instance, const MfClassicData* data); + +void mf_classic_key_cache_reset(MfClassicKeyCache* instance); + +#ifdef __cplusplus +} +#endif diff --git a/applications/main/nfc/helpers/mf_ultralight_auth.c b/applications/main/nfc/helpers/mf_ultralight_auth.c new file mode 100644 index 00000000000..5e0c67887f6 --- /dev/null +++ b/applications/main/nfc/helpers/mf_ultralight_auth.c @@ -0,0 +1,58 @@ +#include "mf_ultralight_auth.h" + +#include +#include + +MfUltralightAuth* mf_ultralight_auth_alloc() { + MfUltralightAuth* instance = malloc(sizeof(MfUltralightAuth)); + + return instance; +} + +void mf_ultralight_auth_free(MfUltralightAuth* instance) { + furi_assert(instance); + + free(instance); +} + +void mf_ultralight_auth_reset(MfUltralightAuth* instance) { + furi_assert(instance); + + instance->type = MfUltralightAuthTypeNone; + memset(&instance->password, 0, sizeof(MfUltralightAuthPassword)); + memset(&instance->pack, 0, sizeof(MfUltralightAuthPack)); +} + +bool mf_ultralight_generate_amiibo_pass(MfUltralightAuth* instance, uint8_t* uid, uint16_t uid_len) { + furi_assert(instance); + furi_assert(uid); + + bool generated = false; + if(uid_len == 7) { + instance->password.data[0] = uid[1] ^ uid[3] ^ 0xAA; + instance->password.data[1] = uid[2] ^ uid[4] ^ 0x55; + instance->password.data[2] = uid[3] ^ uid[5] ^ 0xAA; + instance->password.data[3] = uid[4] ^ uid[6] ^ 0x55; + generated = true; + } + + return generated; +} + +bool mf_ultralight_generate_xiaomi_pass(MfUltralightAuth* instance, uint8_t* uid, uint16_t uid_len) { + furi_assert(instance); + furi_assert(uid); + + uint8_t hash[20]; + bool generated = false; + if(uid_len == 7) { + mbedtls_sha1(uid, uid_len, hash); + instance->password.data[0] = (hash[hash[0] % 20]); + instance->password.data[1] = (hash[(hash[0] + 5) % 20]); + instance->password.data[2] = (hash[(hash[0] + 13) % 20]); + instance->password.data[3] = (hash[(hash[0] + 17) % 20]); + generated = true; + } + + return generated; +} diff --git a/applications/main/nfc/helpers/mf_ultralight_auth.h b/applications/main/nfc/helpers/mf_ultralight_auth.h new file mode 100644 index 00000000000..2ddfcafe12b --- /dev/null +++ b/applications/main/nfc/helpers/mf_ultralight_auth.h @@ -0,0 +1,35 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + MfUltralightAuthTypeNone, + MfUltralightAuthTypeReader, + MfUltralightAuthTypeManual, + MfUltralightAuthTypeXiaomi, + MfUltralightAuthTypeAmiibo, +} MfUltralightAuthType; + +typedef struct { + MfUltralightAuthType type; + MfUltralightAuthPassword password; + MfUltralightAuthPack pack; +} MfUltralightAuth; + +MfUltralightAuth* mf_ultralight_auth_alloc(); + +void mf_ultralight_auth_free(MfUltralightAuth* instance); + +void mf_ultralight_auth_reset(MfUltralightAuth* instance); + +bool mf_ultralight_generate_amiibo_pass(MfUltralightAuth* instance, uint8_t* uid, uint16_t uid_len); + +bool mf_ultralight_generate_xiaomi_pass(MfUltralightAuth* instance, uint8_t* uid, uint16_t uid_len); + +#ifdef __cplusplus +} +#endif diff --git a/applications/main/nfc/helpers/mf_user_dict.c b/applications/main/nfc/helpers/mf_user_dict.c new file mode 100644 index 00000000000..09f0c1506e6 --- /dev/null +++ b/applications/main/nfc/helpers/mf_user_dict.c @@ -0,0 +1,83 @@ +#include "mf_user_dict.h" + +#include +#include +#include + +#define NFC_APP_FOLDER ANY_PATH("nfc") +#define NFC_APP_MF_CLASSIC_DICT_USER_PATH (NFC_APP_FOLDER "/assets/mf_classic_dict_user.nfc") + +struct MfUserDict { + size_t keys_num; + MfClassicKey* keys_arr; +}; + +MfUserDict* mf_user_dict_alloc(size_t max_keys_to_load) { + MfUserDict* instance = malloc(sizeof(MfUserDict)); + + NfcDict* dict = nfc_dict_alloc( + NFC_APP_MF_CLASSIC_DICT_USER_PATH, NfcDictModeOpenAlways, sizeof(MfClassicKey)); + furi_assert(dict); + + size_t dict_keys_num = nfc_dict_get_total_keys(dict); + instance->keys_num = MIN(max_keys_to_load, dict_keys_num); + + if(instance->keys_num > 0) { + instance->keys_arr = malloc(instance->keys_num * sizeof(MfClassicKey)); + for(size_t i = 0; i < instance->keys_num; i++) { + bool key_loaded = + nfc_dict_get_next_key(dict, instance->keys_arr[i].data, sizeof(MfClassicKey)); + furi_assert(key_loaded); + } + } + nfc_dict_free(dict); + + return instance; +} + +void mf_user_dict_free(MfUserDict* instance) { + furi_assert(instance); + + if(instance->keys_num > 0) { + free(instance->keys_arr); + } + free(instance); +} + +size_t mf_user_dict_get_keys_cnt(MfUserDict* instance) { + furi_assert(instance); + + return instance->keys_num; +} + +void mf_user_dict_get_key_str(MfUserDict* instance, uint32_t index, FuriString* str) { + furi_assert(instance); + furi_assert(str); + furi_assert(index < instance->keys_num); + furi_assert(instance->keys_arr); + + furi_string_reset(str); + for(size_t i = 0; i < sizeof(MfClassicKey); i++) { + furi_string_cat_printf(str, "%02X", instance->keys_arr[index].data[i]); + } +} + +bool mf_user_dict_delete_key(MfUserDict* instance, uint32_t index) { + furi_assert(instance); + furi_assert(index < instance->keys_num); + furi_assert(instance->keys_arr); + + NfcDict* dict = nfc_dict_alloc( + NFC_APP_MF_CLASSIC_DICT_USER_PATH, NfcDictModeOpenAlways, sizeof(MfClassicKey)); + furi_assert(dict); + + bool key_delete_success = + nfc_dict_delete_key(dict, instance->keys_arr[index].data, sizeof(MfClassicKey)); + nfc_dict_free(dict); + + if(key_delete_success) { + instance->keys_num--; + } + + return key_delete_success; +} diff --git a/applications/main/nfc/helpers/mf_user_dict.h b/applications/main/nfc/helpers/mf_user_dict.h new file mode 100644 index 00000000000..d482d2d5f78 --- /dev/null +++ b/applications/main/nfc/helpers/mf_user_dict.h @@ -0,0 +1,23 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct MfUserDict MfUserDict; + +MfUserDict* mf_user_dict_alloc(size_t max_keys_to_load); + +void mf_user_dict_free(MfUserDict* instance); + +size_t mf_user_dict_get_keys_cnt(MfUserDict* instance); + +void mf_user_dict_get_key_str(MfUserDict* instance, uint32_t index, FuriString* str); + +bool mf_user_dict_delete_key(MfUserDict* instance, uint32_t index); + +#ifdef __cplusplus +} +#endif diff --git a/applications/main/nfc/helpers/mfkey32_logger.c b/applications/main/nfc/helpers/mfkey32_logger.c new file mode 100644 index 00000000000..8024179f809 --- /dev/null +++ b/applications/main/nfc/helpers/mfkey32_logger.c @@ -0,0 +1,173 @@ +#include "mfkey32_logger.h" + +#include + +#include +#include +#include + +#define MFKEY32_LOGGER_MAX_NONCES_SAVED (100) + +typedef struct { + bool is_filled; + uint32_t cuid; + uint8_t sector_num; + MfClassicKeyType key_type; + uint32_t nt0; + uint32_t nr0; + uint32_t ar0; + uint32_t nt1; + uint32_t nr1; + uint32_t ar1; +} Mfkey32LoggerParams; + +ARRAY_DEF(Mfkey32LoggerParams, Mfkey32LoggerParams, M_POD_OPLIST); + +struct Mfkey32Logger { + uint32_t cuid; + Mfkey32LoggerParams_t params_arr; + size_t nonces_saves; + size_t params_collected; +}; + +Mfkey32Logger* mfkey32_logger_alloc(uint32_t cuid) { + Mfkey32Logger* instance = malloc(sizeof(Mfkey32Logger)); + instance->cuid = cuid; + Mfkey32LoggerParams_init(instance->params_arr); + + return instance; +} + +void mfkey32_logger_free(Mfkey32Logger* instance) { + furi_assert(instance); + furi_assert(instance->params_arr); + + Mfkey32LoggerParams_clear(instance->params_arr); + free(instance); +} + +static bool mfkey32_logger_add_nonce_to_existing_params( + Mfkey32Logger* instance, + MfClassicAuthContext* auth_context) { + bool nonce_added = false; + do { + if(Mfkey32LoggerParams_size(instance->params_arr) == 0) break; + + Mfkey32LoggerParams_it_t it; + for(Mfkey32LoggerParams_it(it, instance->params_arr); !Mfkey32LoggerParams_end_p(it); + Mfkey32LoggerParams_next(it)) { + Mfkey32LoggerParams* params = Mfkey32LoggerParams_ref(it); + if(params->is_filled) continue; + + uint8_t sector_num = mf_classic_get_sector_by_block(auth_context->block_num); + if(params->sector_num != sector_num) continue; + if(params->key_type != auth_context->key_type) continue; + + params->nt1 = nfc_util_bytes2num(auth_context->nt.data, sizeof(MfClassicNt)); + params->nr1 = nfc_util_bytes2num(auth_context->nr.data, sizeof(MfClassicNr)); + params->ar1 = nfc_util_bytes2num(auth_context->ar.data, sizeof(MfClassicAr)); + params->is_filled = true; + + instance->params_collected++; + nonce_added = true; + break; + } + + } while(false); + + return nonce_added; +} + +void mfkey32_logger_add_nonce(Mfkey32Logger* instance, MfClassicAuthContext* auth_context) { + furi_assert(instance); + furi_assert(auth_context); + + bool nonce_added = mfkey32_logger_add_nonce_to_existing_params(instance, auth_context); + if(!nonce_added && (instance->nonces_saves < MFKEY32_LOGGER_MAX_NONCES_SAVED)) { + uint8_t sector_num = mf_classic_get_sector_by_block(auth_context->block_num); + Mfkey32LoggerParams params = { + .is_filled = false, + .cuid = instance->cuid, + .sector_num = sector_num, + .key_type = auth_context->key_type, + .nt0 = nfc_util_bytes2num(auth_context->nt.data, sizeof(MfClassicNt)), + .nr0 = nfc_util_bytes2num(auth_context->nr.data, sizeof(MfClassicNr)), + .ar0 = nfc_util_bytes2num(auth_context->ar.data, sizeof(MfClassicAr)), + }; + Mfkey32LoggerParams_push_back(instance->params_arr, params); + instance->nonces_saves++; + } +} + +size_t mfkey32_logger_get_params_num(Mfkey32Logger* instance) { + furi_assert(instance); + + return instance->params_collected; +} + +bool mfkey32_logger_save_params(Mfkey32Logger* instance, const char* path) { + furi_assert(instance); + furi_assert(path); + furi_assert(instance->params_collected > 0); + furi_assert(instance->params_arr); + + bool params_saved = false; + Storage* storage = furi_record_open(RECORD_STORAGE); + Stream* stream = buffered_file_stream_alloc(storage); + FuriString* temp_str = furi_string_alloc(); + + do { + if(!buffered_file_stream_open(stream, path, FSAM_WRITE, FSOM_OPEN_APPEND)) break; + + bool params_write_success = true; + Mfkey32LoggerParams_it_t it; + for(Mfkey32LoggerParams_it(it, instance->params_arr); !Mfkey32LoggerParams_end_p(it); + Mfkey32LoggerParams_next(it)) { + Mfkey32LoggerParams* params = Mfkey32LoggerParams_ref(it); + if(!params->is_filled) continue; + furi_string_printf( + temp_str, + "Sec %d key %c cuid %08lx nt0 %08lx nr0 %08lx ar0 %08lx nt1 %08lx nr1 %08lx ar1 %08lx\n", + params->sector_num, + params->key_type == MfClassicKeyTypeA ? 'A' : 'B', + params->cuid, + params->nt0, + params->nr0, + params->ar0, + params->nt1, + params->nr1, + params->ar1); + if(!stream_write_string(stream, temp_str)) { + params_write_success = false; + break; + } + } + if(!params_write_success) break; + + params_saved = true; + } while(false); + + furi_string_free(temp_str); + buffered_file_stream_close(stream); + stream_free(stream); + furi_record_close(RECORD_STORAGE); + + return params_saved; +} + +void mfkey32_logger_get_params_data(Mfkey32Logger* instance, FuriString* str) { + furi_assert(instance); + furi_assert(str); + furi_assert(instance->params_collected > 0); + + furi_string_reset(str); + Mfkey32LoggerParams_it_t it; + for(Mfkey32LoggerParams_it(it, instance->params_arr); !Mfkey32LoggerParams_end_p(it); + Mfkey32LoggerParams_next(it)) { + Mfkey32LoggerParams* params = Mfkey32LoggerParams_ref(it); + if(!params->is_filled) continue; + + char key_char = params->key_type == MfClassicKeyTypeA ? 'A' : 'B'; + furi_string_cat_printf(str, "Sector %d, key %c\n", params->sector_num, key_char); + } +} diff --git a/applications/main/nfc/helpers/mfkey32_logger.h b/applications/main/nfc/helpers/mfkey32_logger.h new file mode 100644 index 00000000000..12b42e1fb51 --- /dev/null +++ b/applications/main/nfc/helpers/mfkey32_logger.h @@ -0,0 +1,25 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct Mfkey32Logger Mfkey32Logger; + +Mfkey32Logger* mfkey32_logger_alloc(uint32_t cuid); + +void mfkey32_logger_free(Mfkey32Logger* instance); + +void mfkey32_logger_add_nonce(Mfkey32Logger* instance, MfClassicAuthContext* auth_context); + +size_t mfkey32_logger_get_params_num(Mfkey32Logger* instance); + +bool mfkey32_logger_save_params(Mfkey32Logger* instance, const char* path); + +void mfkey32_logger_get_params_data(Mfkey32Logger* instance, FuriString* str); + +#ifdef __cplusplus +} +#endif diff --git a/applications/main/nfc/helpers/nfc_custom_event.h b/applications/main/nfc/helpers/nfc_custom_event.h index aa932a3d857..2a3b13e1a41 100644 --- a/applications/main/nfc/helpers/nfc_custom_event.h +++ b/applications/main/nfc/helpers/nfc_custom_event.h @@ -1,17 +1,33 @@ #pragma once -enum NfcCustomEvent { +typedef enum { // Reserve first 100 events for button types and indexes, starting from 0 NfcCustomEventReserved = 100, + // Mf classic dict attack events + NfcCustomEventDictAttackComplete, + NfcCustomEventDictAttackSkip, + NfcCustomEventDictAttackDataUpdate, + + NfcCustomEventCardDetected, + NfcCustomEventCardLost, + NfcCustomEventViewExit, NfcCustomEventWorkerExit, + NfcCustomEventWorkerUpdate, + NfcCustomEventWrongCard, + NfcCustomEventTimerExpired, NfcCustomEventByteInputDone, NfcCustomEventTextInputDone, NfcCustomEventDictAttackDone, - NfcCustomEventDictAttackSkip, + NfcCustomEventRpcLoad, + NfcCustomEventRpcExit, NfcCustomEventRpcSessionClose, - NfcCustomEventUpdateLog, - NfcCustomEventSaveShadow, -}; + + NfcCustomEventPollerSuccess, + NfcCustomEventPollerIncomplete, + NfcCustomEventPollerFailure, + + NfcCustomEventListenerUpdate, +} NfcCustomEvent; diff --git a/applications/main/nfc/helpers/nfc_supported_cards.c b/applications/main/nfc/helpers/nfc_supported_cards.c new file mode 100644 index 00000000000..a242ba3ae6d --- /dev/null +++ b/applications/main/nfc/helpers/nfc_supported_cards.c @@ -0,0 +1,147 @@ +#include "nfc_supported_cards.h" +#include "../plugins/supported_cards/nfc_supported_card_plugin.h" + +#include +#include +#include + +#include +#include + +#define TAG "NfcSupportedCards" + +#define NFC_SUPPORTED_CARDS_PLUGINS_PATH APP_DATA_PATH("plugins") +#define NFC_SUPPORTED_CARDS_PLUGIN_SUFFIX "_parser.fal" + +typedef struct { + Storage* storage; + File* directory; + FuriString* file_path; + char file_name[256]; + FlipperApplication* app; +} NfcSupportedCards; + +static NfcSupportedCards* nfc_supported_cards_alloc() { + NfcSupportedCards* instance = malloc(sizeof(NfcSupportedCards)); + + instance->storage = furi_record_open(RECORD_STORAGE); + instance->directory = storage_file_alloc(instance->storage); + instance->file_path = furi_string_alloc(); + + if(!storage_dir_open(instance->directory, NFC_SUPPORTED_CARDS_PLUGINS_PATH)) { + FURI_LOG_D(TAG, "Failed to open directory: %s", NFC_SUPPORTED_CARDS_PLUGINS_PATH); + } + + return instance; +} + +static void nfc_supported_cards_free(NfcSupportedCards* instance) { + if(instance->app) { + flipper_application_free(instance->app); + } + + furi_string_free(instance->file_path); + + storage_dir_close(instance->directory); + storage_file_free(instance->directory); + + furi_record_close(RECORD_STORAGE); + free(instance); +} + +static const NfcSupportedCardsPlugin* + nfc_supported_cards_get_next_plugin(NfcSupportedCards* instance) { + const NfcSupportedCardsPlugin* plugin = NULL; + + do { + if(!storage_file_is_open(instance->directory)) break; + if(!storage_dir_read( + instance->directory, NULL, instance->file_name, sizeof(instance->file_name))) + break; + + furi_string_set(instance->file_path, instance->file_name); + if(!furi_string_end_with_str(instance->file_path, NFC_SUPPORTED_CARDS_PLUGIN_SUFFIX)) + continue; + + path_concat(NFC_SUPPORTED_CARDS_PLUGINS_PATH, instance->file_name, instance->file_path); + + if(instance->app) flipper_application_free(instance->app); + instance->app = flipper_application_alloc(instance->storage, firmware_api_interface); + + if(flipper_application_preload(instance->app, furi_string_get_cstr(instance->file_path)) != + FlipperApplicationPreloadStatusSuccess) + continue; + if(!flipper_application_is_plugin(instance->app)) continue; + + if(flipper_application_map_to_memory(instance->app) != FlipperApplicationLoadStatusSuccess) + continue; + + const FlipperAppPluginDescriptor* descriptor = + flipper_application_plugin_get_descriptor(instance->app); + + if(descriptor == NULL) continue; + + if(strcmp(descriptor->appid, NFC_SUPPORTED_CARD_PLUGIN_APP_ID) != 0) continue; + if(descriptor->ep_api_version != NFC_SUPPORTED_CARD_PLUGIN_API_VERSION) continue; + + plugin = descriptor->entry_point; + } while(plugin == NULL); //-V654 + + return plugin; +} + +bool nfc_supported_cards_read(NfcDevice* device, Nfc* nfc) { + furi_assert(device); + furi_assert(nfc); + + bool card_read = false; + + NfcSupportedCards* supported_cards = nfc_supported_cards_alloc(); + + do { + const NfcSupportedCardsPlugin* plugin = + nfc_supported_cards_get_next_plugin(supported_cards); + if(plugin == NULL) break; //-V547 + + const NfcProtocol protocol = nfc_device_get_protocol(device); //-V779 + if(plugin->protocol != protocol) continue; + + if(plugin->verify) { + if(!plugin->verify(nfc)) continue; + } + + if(plugin->read) { + card_read = plugin->read(nfc, device); + } + + } while(!card_read); + + nfc_supported_cards_free(supported_cards); + return card_read; +} + +bool nfc_supported_cards_parse(const NfcDevice* device, FuriString* parsed_data) { + furi_assert(device); + furi_assert(parsed_data); + + bool parsed = false; + + NfcSupportedCards* supported_cards = nfc_supported_cards_alloc(); + + do { + const NfcSupportedCardsPlugin* plugin = + nfc_supported_cards_get_next_plugin(supported_cards); + if(plugin == NULL) break; //-V547 + + const NfcProtocol protocol = nfc_device_get_protocol(device); //-V779 + if(plugin->protocol != protocol) continue; + + if(plugin->parse) { + parsed = plugin->parse(device, parsed_data); + } + + } while(!parsed); + + nfc_supported_cards_free(supported_cards); + return parsed; +} diff --git a/applications/main/nfc/helpers/nfc_supported_cards.h b/applications/main/nfc/helpers/nfc_supported_cards.h new file mode 100644 index 00000000000..0c25a5b1180 --- /dev/null +++ b/applications/main/nfc/helpers/nfc_supported_cards.h @@ -0,0 +1,50 @@ +/** + * @file nfc_supported_cards.h + * @brief Supported card plugin loader interface. + * + * @see nfc_supported_card_plugin.h for instructions on adding a new plugin. + */ +#pragma once + +#include + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Read the card using a custom procedure. + * + * This function will load all suitable supported card plugins one by one and + * try to execute the custom read procedure specified in each. Upon first success, + * no further attempts will be made and the function will return. + * + * @param[in,out] device pointer to a device instance to hold the read data. + * @param[in,out] nfc pointer to an Nfc instance. + * @returns true if the card was successfully read, false otherwise. + * + * @see NfcSupportedCardPluginRead for detailed description. + */ +bool nfc_supported_cards_read(NfcDevice* device, Nfc* nfc); + +/** + * @brief Parse raw data into human-readable representation. + * + * This function will load all suitable supported card plugins one by one and + * try to parse the data according to each implementation. Upon first success, + * no further attempts will be made and the function will return. + * + * @param[in] device pointer to a device instance holding the data is to be parsed. + * @param[out] parsed_data pointer to the string to contain the formatted result. + * @returns true if the card was successfully parsed, false otherwise. + * + * @see NfcSupportedCardPluginParse for detailed description. + */ +bool nfc_supported_cards_parse(const NfcDevice* device, FuriString* parsed_data); + +#ifdef __cplusplus +} +#endif diff --git a/applications/main/nfc/helpers/protocol_support/felica/felica.c b/applications/main/nfc/helpers/protocol_support/felica/felica.c new file mode 100644 index 00000000000..b3e629f4a52 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/felica/felica.c @@ -0,0 +1,108 @@ +#include "felica.h" +#include "felica_render.h" + +#include + +#include "nfc/nfc_app_i.h" + +#include "../nfc_protocol_support_common.h" +#include "../nfc_protocol_support_gui_common.h" + +static void nfc_scene_info_on_enter_felica(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const FelicaData* data = nfc_device_get_data(device, NfcProtocolFelica); + + FuriString* temp_str = furi_string_alloc(); + furi_string_cat_printf( + temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); + nfc_render_felica_info(data, NfcProtocolFormatTypeFull, temp_str); + + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 64, furi_string_get_cstr(temp_str)); + + furi_string_free(temp_str); +} + +static NfcCommand nfc_scene_read_poller_callback_felica(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolFelica); + + NfcApp* instance = context; + const FelicaPollerEvent* felica_event = event.event_data; + + if(felica_event->type == FelicaPollerEventTypeReady) { + nfc_device_set_data( + instance->nfc_device, NfcProtocolFelica, nfc_poller_get_data(instance->poller)); + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess); + return NfcCommandStop; + } + + return NfcCommandContinue; +} + +static void nfc_scene_read_on_enter_felica(NfcApp* instance) { + nfc_poller_start(instance->poller, nfc_scene_read_poller_callback_felica, instance); +} + +static void nfc_scene_read_success_on_enter_felica(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const FelicaData* data = nfc_device_get_data(device, NfcProtocolFelica); + + FuriString* temp_str = furi_string_alloc(); + furi_string_cat_printf( + temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); + nfc_render_felica_info(data, NfcProtocolFormatTypeShort, temp_str); + + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); + + furi_string_free(temp_str); +} + +static bool nfc_scene_saved_menu_on_event_felica(NfcApp* instance, uint32_t event) { + if(event == SubmenuIndexCommonEdit) { + scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid); + return true; + } + + return false; +} + +const NfcProtocolSupportBase nfc_protocol_support_felica = { + .features = NfcProtocolFeatureNone, + + .scene_info = + { + .on_enter = nfc_scene_info_on_enter_felica, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read = + { + .on_enter = nfc_scene_read_on_enter_felica, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read_menu = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read_success = + { + .on_enter = nfc_scene_read_success_on_enter_felica, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_saved_menu = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_scene_saved_menu_on_event_felica, + }, + .scene_save_name = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_emulate = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, +}; diff --git a/applications/main/nfc/helpers/protocol_support/felica/felica.h b/applications/main/nfc/helpers/protocol_support/felica/felica.h new file mode 100644 index 00000000000..e581b6709e7 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/felica/felica.h @@ -0,0 +1,5 @@ +#pragma once + +#include "../nfc_protocol_support_base.h" + +extern const NfcProtocolSupportBase nfc_protocol_support_felica; diff --git a/applications/main/nfc/helpers/protocol_support/felica/felica_render.c b/applications/main/nfc/helpers/protocol_support/felica/felica_render.c new file mode 100644 index 00000000000..3142b2c6dbf --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/felica/felica_render.c @@ -0,0 +1,19 @@ +#include "felica_render.h" + +void nfc_render_felica_info( + const FelicaData* data, + NfcProtocolFormatType format_type, + FuriString* str) { + furi_string_cat_printf(str, "IDm:"); + + for(size_t i = 0; i < FELICA_IDM_SIZE; i++) { + furi_string_cat_printf(str, " %02X", data->idm.data[i]); + } + + if(format_type == NfcProtocolFormatTypeFull) { + furi_string_cat_printf(str, "\nPMm:"); + for(size_t i = 0; i < FELICA_PMM_SIZE; ++i) { + furi_string_cat_printf(str, " %02X", data->pmm.data[i]); + } + } +} diff --git a/applications/main/nfc/helpers/protocol_support/felica/felica_render.h b/applications/main/nfc/helpers/protocol_support/felica/felica_render.h new file mode 100644 index 00000000000..6d9816fc663 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/felica/felica_render.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +#include "../nfc_protocol_support_render_common.h" + +void nfc_render_felica_info( + const FelicaData* data, + NfcProtocolFormatType format_type, + FuriString* str); diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a.c b/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a.c new file mode 100644 index 00000000000..c0d502d0380 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a.c @@ -0,0 +1,145 @@ +#include "iso14443_3a.h" +#include "iso14443_3a_render.h" + +#include + +#include "nfc/nfc_app_i.h" + +#include "../nfc_protocol_support_common.h" +#include "../nfc_protocol_support_gui_common.h" + +static void nfc_scene_info_on_enter_iso14443_3a(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const Iso14443_3aData* data = nfc_device_get_data(device, NfcProtocolIso14443_3a); + + FuriString* temp_str = furi_string_alloc(); + furi_string_cat_printf( + temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); + nfc_render_iso14443_3a_info(data, NfcProtocolFormatTypeFull, temp_str); + + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 64, furi_string_get_cstr(temp_str)); + + furi_string_free(temp_str); +} + +static NfcCommand + nfc_scene_read_poller_callback_iso14443_3a(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolIso14443_3a); + + NfcApp* instance = context; + const Iso14443_3aPollerEvent* iso14443_3a_event = event.event_data; + + if(iso14443_3a_event->type == Iso14443_3aPollerEventTypeReady) { + nfc_device_set_data( + instance->nfc_device, NfcProtocolIso14443_3a, nfc_poller_get_data(instance->poller)); + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess); + return NfcCommandStop; + } + + return NfcCommandContinue; +} + +static void nfc_scene_read_on_enter_iso14443_3a(NfcApp* instance) { + nfc_poller_start(instance->poller, nfc_scene_read_poller_callback_iso14443_3a, instance); +} + +static void nfc_scene_read_success_on_enter_iso14443_3a(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const Iso14443_3aData* data = nfc_device_get_data(device, NfcProtocolIso14443_3a); + + FuriString* temp_str = furi_string_alloc(); + furi_string_cat_printf( + temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); + nfc_render_iso14443_3a_info(data, NfcProtocolFormatTypeShort, temp_str); + + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); + + furi_string_free(temp_str); +} + +static NfcCommand + nfc_scene_emulate_listener_callback_iso14443_3a(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.protocol == NfcProtocolIso14443_3a); + furi_assert(event.event_data); + + NfcApp* nfc = context; + Iso14443_3aListenerEvent* iso14443_3a_event = event.event_data; + + if(iso14443_3a_event->type == Iso14443_3aListenerEventTypeReceivedStandardFrame) { + if(furi_string_size(nfc->text_box_store) < NFC_LOG_SIZE_MAX) { + furi_string_cat_printf(nfc->text_box_store, "R:"); + for(size_t i = 0; i < bit_buffer_get_size_bytes(iso14443_3a_event->data->buffer); + i++) { + furi_string_cat_printf( + nfc->text_box_store, + " %02X", + bit_buffer_get_byte(iso14443_3a_event->data->buffer, i)); + } + furi_string_push_back(nfc->text_box_store, '\n'); + view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventListenerUpdate); + } + } + + return NfcCommandContinue; +} + +static void nfc_scene_emulate_on_enter_iso14443_3a(NfcApp* instance) { + const Iso14443_3aData* data = + nfc_device_get_data(instance->nfc_device, NfcProtocolIso14443_3a); + + instance->listener = nfc_listener_alloc(instance->nfc, NfcProtocolIso14443_3a, data); + nfc_listener_start( + instance->listener, nfc_scene_emulate_listener_callback_iso14443_3a, instance); +} + +static bool nfc_scene_read_menu_on_event_iso14443_3a(NfcApp* instance, uint32_t event) { + if(event == SubmenuIndexCommonEmulate) { + scene_manager_next_scene(instance->scene_manager, NfcSceneEmulate); + return true; + } + + return false; +} + +const NfcProtocolSupportBase nfc_protocol_support_iso14443_3a = { + .features = NfcProtocolFeatureEmulateUid | NfcProtocolFeatureEditUid, + + .scene_info = + { + .on_enter = nfc_scene_info_on_enter_iso14443_3a, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read = + { + .on_enter = nfc_scene_read_on_enter_iso14443_3a, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read_menu = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_scene_read_menu_on_event_iso14443_3a, + }, + .scene_read_success = + { + .on_enter = nfc_scene_read_success_on_enter_iso14443_3a, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_saved_menu = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_save_name = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_emulate = + { + .on_enter = nfc_scene_emulate_on_enter_iso14443_3a, + .on_event = nfc_protocol_support_common_on_event_empty, + }, +}; diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a.h b/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a.h new file mode 100644 index 00000000000..d085f25c77b --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a.h @@ -0,0 +1,5 @@ +#pragma once + +#include "../nfc_protocol_support_base.h" + +extern const NfcProtocolSupportBase nfc_protocol_support_iso14443_3a; diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a_render.c b/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a_render.c new file mode 100644 index 00000000000..7306f1072d6 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a_render.c @@ -0,0 +1,34 @@ +#include "iso14443_3a_render.h" + +void nfc_render_iso14443_3a_format_bytes(FuriString* str, const uint8_t* const data, size_t size) { + for(size_t i = 0; i < size; i++) { + furi_string_cat_printf(str, " %02X", data[i]); + } +} + +void nfc_render_iso14443_3a_info( + const Iso14443_3aData* data, + NfcProtocolFormatType format_type, + FuriString* str) { + if(format_type == NfcProtocolFormatTypeFull) { + const char iso_type = iso14443_3a_supports_iso14443_4(data) ? '4' : '3'; + furi_string_cat_printf(str, "ISO 14443-%c (NFC-A)\n", iso_type); + } + + nfc_render_iso14443_3a_brief(data, str); + + if(format_type == NfcProtocolFormatTypeFull) { + nfc_render_iso14443_3a_extra(data, str); + } +} + +void nfc_render_iso14443_3a_brief(const Iso14443_3aData* data, FuriString* str) { + furi_string_cat_printf(str, "UID:"); + + nfc_render_iso14443_3a_format_bytes(str, data->uid, data->uid_len); +} + +void nfc_render_iso14443_3a_extra(const Iso14443_3aData* data, FuriString* str) { + furi_string_cat_printf(str, "\nATQA: %02X %02X ", data->atqa[1], data->atqa[0]); + furi_string_cat_printf(str, "SAK: %02X", data->sak); +} diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a_render.h b/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a_render.h new file mode 100644 index 00000000000..14b91d221d7 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a_render.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +#include "../nfc_protocol_support_render_common.h" + +void nfc_render_iso14443_3a_info( + const Iso14443_3aData* data, + NfcProtocolFormatType format_type, + FuriString* str); + +void nfc_render_iso14443_3a_format_bytes(FuriString* str, const uint8_t* const data, size_t size); + +void nfc_render_iso14443_3a_brief(const Iso14443_3aData* data, FuriString* str); + +void nfc_render_iso14443_3a_extra(const Iso14443_3aData* data, FuriString* str); diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b.c b/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b.c new file mode 100644 index 00000000000..fee23184624 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b.c @@ -0,0 +1,113 @@ +#include "iso14443_3b.h" +#include "iso14443_3b_render.h" + +#include + +#include "nfc/nfc_app_i.h" + +#include "../nfc_protocol_support_common.h" +#include "../nfc_protocol_support_gui_common.h" + +static void nfc_scene_info_on_enter_iso14443_3b(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const Iso14443_3bData* data = nfc_device_get_data(device, NfcProtocolIso14443_3b); + + FuriString* temp_str = furi_string_alloc(); + furi_string_cat_printf( + temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); + nfc_render_iso14443_3b_info(data, NfcProtocolFormatTypeFull, temp_str); + + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 64, furi_string_get_cstr(temp_str)); + + furi_string_free(temp_str); +} + +static NfcCommand + nfc_scene_read_poller_callback_iso14443_3b(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolIso14443_3b); + + NfcApp* instance = context; + const Iso14443_3bPollerEvent* iso14443_3b_event = event.event_data; + + if(iso14443_3b_event->type == Iso14443_3bPollerEventTypeReady) { + nfc_device_set_data( + instance->nfc_device, NfcProtocolIso14443_3b, nfc_poller_get_data(instance->poller)); + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess); + return NfcCommandStop; + } + + return NfcCommandContinue; +} + +static void nfc_scene_read_on_enter_iso14443_3b(NfcApp* instance) { + nfc_poller_start(instance->poller, nfc_scene_read_poller_callback_iso14443_3b, instance); +} + +static void nfc_scene_read_success_on_enter_iso14443_3b(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const Iso14443_3bData* data = nfc_device_get_data(device, NfcProtocolIso14443_3b); + + FuriString* temp_str = furi_string_alloc(); + furi_string_cat_printf( + temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); + nfc_render_iso14443_3b_info(data, NfcProtocolFormatTypeShort, temp_str); + + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); + + furi_string_free(temp_str); +} + +bool nfc_scene_saved_menu_on_event_iso14443_3b_common(NfcApp* instance, uint32_t event) { + if(event == SubmenuIndexCommonEdit) { + scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid); + return true; + } + + return false; +} + +static bool nfc_scene_saved_menu_on_event_iso14443_3b(NfcApp* instance, uint32_t event) { + return nfc_scene_saved_menu_on_event_iso14443_3b_common(instance, event); +} + +const NfcProtocolSupportBase nfc_protocol_support_iso14443_3b = { + .features = NfcProtocolFeatureNone, + + .scene_info = + { + .on_enter = nfc_scene_info_on_enter_iso14443_3b, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read = + { + .on_enter = nfc_scene_read_on_enter_iso14443_3b, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read_menu = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read_success = + { + .on_enter = nfc_scene_read_success_on_enter_iso14443_3b, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_saved_menu = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_scene_saved_menu_on_event_iso14443_3b, + }, + .scene_save_name = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_emulate = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, +}; diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b.h b/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b.h new file mode 100644 index 00000000000..bf3caa0c0e3 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b.h @@ -0,0 +1,5 @@ +#pragma once + +#include "../nfc_protocol_support_base.h" + +extern const NfcProtocolSupportBase nfc_protocol_support_iso14443_3b; diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b_i.h b/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b_i.h new file mode 100644 index 00000000000..53fe6b3927a --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b_i.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +#include "iso14443_3b.h" + +bool nfc_scene_saved_menu_on_event_iso14443_3b_common(NfcApp* instance, uint32_t event); diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b_render.c b/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b_render.c new file mode 100644 index 00000000000..2e81d57a48b --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b_render.c @@ -0,0 +1,78 @@ +#include "iso14443_3b_render.h" + +void nfc_render_iso14443_3b_info( + const Iso14443_3bData* data, + NfcProtocolFormatType format_type, + FuriString* str) { + if(format_type == NfcProtocolFormatTypeFull) { + const char iso_type = iso14443_3b_supports_iso14443_4(data) ? '4' : '3'; + furi_string_cat_printf(str, "ISO 14443-%c (NFC-B)\n", iso_type); + } + + furi_string_cat_printf(str, "UID:"); + + size_t uid_size; + const uint8_t* uid = iso14443_3b_get_uid(data, &uid_size); + + for(size_t i = 0; i < uid_size; i++) { + furi_string_cat_printf(str, " %02X", uid[i]); + } + + if(format_type != NfcProtocolFormatTypeFull) return; + + furi_string_cat_printf(str, "\n\e#Protocol info\n"); + + if(iso14443_3b_supports_bit_rate(data, Iso14443_3bBitRateBoth106Kbit)) { + furi_string_cat(str, "Bit rate PICC <-> PCD:\n 106 kBit/s supported\n"); + } else { + furi_string_cat(str, "Bit rate PICC -> PCD:\n"); + if(iso14443_3b_supports_bit_rate(data, Iso14443_3bBitRatePiccToPcd212Kbit)) { + furi_string_cat(str, " 212 kBit/s supported\n"); + } + if(iso14443_3b_supports_bit_rate(data, Iso14443_3bBitRatePiccToPcd424Kbit)) { + furi_string_cat(str, " 424 kBit/s supported\n"); + } + if(iso14443_3b_supports_bit_rate(data, Iso14443_3bBitRatePiccToPcd848Kbit)) { + furi_string_cat(str, " 848 kBit/s supported\n"); + } + + furi_string_cat(str, "Bit rate PICC <- PCD:\n"); + if(iso14443_3b_supports_bit_rate(data, Iso14443_3bBitRatePcdToPicc212Kbit)) { + furi_string_cat(str, " 212 kBit/s supported\n"); + } + if(iso14443_3b_supports_bit_rate(data, Iso14443_3bBitRatePcdToPicc424Kbit)) { + furi_string_cat(str, " 424 kBit/s supported\n"); + } + if(iso14443_3b_supports_bit_rate(data, Iso14443_3bBitRatePcdToPicc848Kbit)) { + furi_string_cat(str, " 848 kBit/s supported\n"); + } + } + + furi_string_cat(str, "Max frame size: "); + + const uint16_t max_frame_size = iso14443_3b_get_frame_size_max(data); + if(max_frame_size != 0) { + furi_string_cat_printf(str, "%u bytes\n", max_frame_size); + } else { + furi_string_cat(str, "? (RFU)\n"); + } + + const double fwt = iso14443_3b_get_fwt_fc_max(data) / 13.56e6; + furi_string_cat_printf(str, "Max waiting time: %4.2g s\n", fwt); + + const char* nad_support_str = + iso14443_3b_supports_frame_option(data, Iso14443_3bFrameOptionNad) ? "" : "not "; + furi_string_cat_printf(str, "NAD: %ssupported\n", nad_support_str); + + const char* cid_support_str = + iso14443_3b_supports_frame_option(data, Iso14443_3bFrameOptionCid) ? "" : "not "; + furi_string_cat_printf(str, "CID: %ssupported", cid_support_str); + + furi_string_cat_printf(str, "\n\e#Application data\nRaw:"); + + size_t app_data_size; + const uint8_t* app_data = iso14443_3b_get_application_data(data, &app_data_size); + for(size_t i = 0; i < app_data_size; ++i) { + furi_string_cat_printf(str, " %02X", app_data[i]); + } +} diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b_render.h b/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b_render.h new file mode 100644 index 00000000000..ee50f6acf5a --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b_render.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +#include "../nfc_protocol_support_render_common.h" + +void nfc_render_iso14443_3b_info( + const Iso14443_3bData* data, + NfcProtocolFormatType format_type, + FuriString* str); diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_4a/iso14443_4a.c b/applications/main/nfc/helpers/protocol_support/iso14443_4a/iso14443_4a.c new file mode 100644 index 00000000000..0a3a592e172 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/iso14443_4a/iso14443_4a.c @@ -0,0 +1,149 @@ +#include "iso14443_4a.h" +#include "iso14443_4a_render.h" + +#include +#include + +#include "nfc/nfc_app_i.h" + +#include "../nfc_protocol_support_common.h" +#include "../nfc_protocol_support_gui_common.h" + +static void nfc_scene_info_on_enter_iso14443_4a(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const Iso14443_4aData* data = nfc_device_get_data(device, NfcProtocolIso14443_4a); + + FuriString* temp_str = furi_string_alloc(); + furi_string_cat_printf( + temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); + nfc_render_iso14443_4a_info(data, NfcProtocolFormatTypeFull, temp_str); + + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 64, furi_string_get_cstr(temp_str)); + + furi_string_free(temp_str); +} + +static NfcCommand + nfc_scene_read_poller_callback_iso14443_4a(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolIso14443_4a); + + NfcApp* instance = context; + const Iso14443_4aPollerEvent* iso14443_4a_event = event.event_data; + + if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeReady) { + nfc_device_set_data( + instance->nfc_device, NfcProtocolIso14443_4a, nfc_poller_get_data(instance->poller)); + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess); + return NfcCommandStop; + } + + return NfcCommandContinue; +} + +static void nfc_scene_read_on_enter_iso14443_4a(NfcApp* instance) { + nfc_poller_start(instance->poller, nfc_scene_read_poller_callback_iso14443_4a, instance); +} + +static void nfc_scene_read_success_on_enter_iso14443_4a(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const Iso14443_4aData* data = nfc_device_get_data(device, NfcProtocolIso14443_4a); + + FuriString* temp_str = furi_string_alloc(); + furi_string_cat_printf( + temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); + nfc_render_iso14443_4a_info(data, NfcProtocolFormatTypeShort, temp_str); + + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); + + furi_string_free(temp_str); +} + +static void nfc_scene_saved_menu_on_enter_iso14443_4a(NfcApp* instance) { + UNUSED(instance); +} + +NfcCommand nfc_scene_emulate_listener_callback_iso14443_4a(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.protocol == NfcProtocolIso14443_4a); + furi_assert(event.event_data); + + NfcApp* nfc = context; + Iso14443_4aListenerEvent* iso14443_4a_event = event.event_data; + + if(iso14443_4a_event->type == Iso14443_4aListenerEventTypeReceivedData) { + if(furi_string_size(nfc->text_box_store) < NFC_LOG_SIZE_MAX) { + furi_string_cat_printf(nfc->text_box_store, "R:"); + for(size_t i = 0; i < bit_buffer_get_size_bytes(iso14443_4a_event->data->buffer); + i++) { + furi_string_cat_printf( + nfc->text_box_store, + " %02X", + bit_buffer_get_byte(iso14443_4a_event->data->buffer, i)); + } + furi_string_push_back(nfc->text_box_store, '\n'); + view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventListenerUpdate); + } + } + + return NfcCommandContinue; +} + +static void nfc_scene_emulate_on_enter_iso14443_4a(NfcApp* instance) { + const Iso14443_4aData* data = + nfc_device_get_data(instance->nfc_device, NfcProtocolIso14443_4a); + + instance->listener = nfc_listener_alloc(instance->nfc, NfcProtocolIso14443_4a, data); + nfc_listener_start( + instance->listener, nfc_scene_emulate_listener_callback_iso14443_4a, instance); +} + +static bool nfc_scene_read_menu_on_event_iso14443_4a(NfcApp* instance, uint32_t event) { + if(event == SubmenuIndexCommonEmulate) { + scene_manager_next_scene(instance->scene_manager, NfcSceneEmulate); + return true; + } + + return false; +} + +const NfcProtocolSupportBase nfc_protocol_support_iso14443_4a = { + .features = NfcProtocolFeatureEmulateUid | NfcProtocolFeatureEditUid, + + .scene_info = + { + .on_enter = nfc_scene_info_on_enter_iso14443_4a, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read = + { + .on_enter = nfc_scene_read_on_enter_iso14443_4a, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read_menu = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_scene_read_menu_on_event_iso14443_4a, + }, + .scene_read_success = + { + .on_enter = nfc_scene_read_success_on_enter_iso14443_4a, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_saved_menu = + { + .on_enter = nfc_scene_saved_menu_on_enter_iso14443_4a, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_save_name = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_emulate = + { + .on_enter = nfc_scene_emulate_on_enter_iso14443_4a, + .on_event = nfc_protocol_support_common_on_event_empty, + }, +}; diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_4a/iso14443_4a.h b/applications/main/nfc/helpers/protocol_support/iso14443_4a/iso14443_4a.h new file mode 100644 index 00000000000..1b173d7b091 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/iso14443_4a/iso14443_4a.h @@ -0,0 +1,5 @@ +#pragma once + +#include "../nfc_protocol_support_base.h" + +extern const NfcProtocolSupportBase nfc_protocol_support_iso14443_4a; diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_4a/iso14443_4a_i.h b/applications/main/nfc/helpers/protocol_support/iso14443_4a/iso14443_4a_i.h new file mode 100644 index 00000000000..7e13403da13 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/iso14443_4a/iso14443_4a_i.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +#include "iso14443_4a.h" + +NfcCommand nfc_scene_emulate_listener_callback_iso14443_4a(NfcGenericEvent event, void* context); diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_4a/iso14443_4a_render.c b/applications/main/nfc/helpers/protocol_support/iso14443_4a/iso14443_4a_render.c new file mode 100644 index 00000000000..a963e744b9e --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/iso14443_4a/iso14443_4a_render.c @@ -0,0 +1,84 @@ +#include "iso14443_4a_render.h" + +#include "../iso14443_3a/iso14443_3a_render.h" + +void nfc_render_iso14443_4a_info( + const Iso14443_4aData* data, + NfcProtocolFormatType format_type, + FuriString* str) { + nfc_render_iso14443_4a_brief(data, str); + + if(format_type != NfcProtocolFormatTypeFull) return; + + nfc_render_iso14443_4a_extra(data, str); +} + +void nfc_render_iso14443_4a_brief(const Iso14443_4aData* data, FuriString* str) { + nfc_render_iso14443_3a_brief(iso14443_4a_get_base_data(data), str); +} + +void nfc_render_iso14443_4a_extra(const Iso14443_4aData* data, FuriString* str) { + furi_string_cat_printf(str, "\n\e#Protocol info\n"); + + if(iso14443_4a_supports_bit_rate(data, Iso14443_4aBitRateBoth106Kbit)) { + furi_string_cat(str, "Bit rate PICC <-> PCD:\n 106 kBit/s supported\n"); + } else { + furi_string_cat(str, "Bit rate PICC -> PCD:\n"); + if(iso14443_4a_supports_bit_rate(data, Iso14443_4aBitRatePiccToPcd212Kbit)) { + furi_string_cat(str, " 212 kBit/s supported\n"); + } + if(iso14443_4a_supports_bit_rate(data, Iso14443_4aBitRatePiccToPcd424Kbit)) { + furi_string_cat(str, " 424 kBit/s supported\n"); + } + if(iso14443_4a_supports_bit_rate(data, Iso14443_4aBitRatePiccToPcd848Kbit)) { + furi_string_cat(str, " 848 kBit/s supported\n"); + } + + furi_string_cat(str, "Bit rate PICC <- PCD:\n"); + if(iso14443_4a_supports_bit_rate(data, Iso14443_4aBitRatePcdToPicc212Kbit)) { + furi_string_cat(str, " 212 kBit/s supported\n"); + } + if(iso14443_4a_supports_bit_rate(data, Iso14443_4aBitRatePcdToPicc424Kbit)) { + furi_string_cat(str, " 424 kBit/s supported\n"); + } + if(iso14443_4a_supports_bit_rate(data, Iso14443_4aBitRatePcdToPicc848Kbit)) { + furi_string_cat(str, " 848 kBit/s supported\n"); + } + } + + furi_string_cat(str, "Max frame size: "); + + const uint16_t max_frame_size = iso14443_4a_get_frame_size_max(data); + if(max_frame_size != 0) { + furi_string_cat_printf(str, "%u bytes\n", max_frame_size); + } else { + furi_string_cat(str, "? (RFU)\n"); + } + + const uint32_t fwt_fc = iso14443_4a_get_fwt_fc_max(data); + if(fwt_fc != 0) { + furi_string_cat_printf(str, "Max waiting time: %4.2g s\n", (double)(fwt_fc / 13.56e6)); + } + + const char* nad_support_str = + iso14443_4a_supports_frame_option(data, Iso14443_4aFrameOptionNad) ? "" : "not "; + furi_string_cat_printf(str, "NAD: %ssupported\n", nad_support_str); + + const char* cid_support_str = + iso14443_4a_supports_frame_option(data, Iso14443_4aFrameOptionCid) ? "" : "not "; + furi_string_cat_printf(str, "CID: %ssupported", cid_support_str); + + uint32_t hist_bytes_count; + const uint8_t* hist_bytes = iso14443_4a_get_historical_bytes(data, &hist_bytes_count); + + if(hist_bytes_count > 0) { + furi_string_cat_printf(str, "\n\e#Historical bytes\nRaw:"); + + for(size_t i = 0; i < hist_bytes_count; ++i) { + furi_string_cat_printf(str, " %02X", hist_bytes[i]); + } + } + + furi_string_cat(str, "\n\e#ISO14443-3A data"); + nfc_render_iso14443_3a_extra(iso14443_4a_get_base_data(data), str); +} diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_4a/iso14443_4a_render.h b/applications/main/nfc/helpers/protocol_support/iso14443_4a/iso14443_4a_render.h new file mode 100644 index 00000000000..fdcd7066eaf --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/iso14443_4a/iso14443_4a_render.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +#include "../nfc_protocol_support_render_common.h" + +void nfc_render_iso14443_4a_info( + const Iso14443_4aData* data, + NfcProtocolFormatType format_type, + FuriString* str); + +void nfc_render_iso14443_4a_brief(const Iso14443_4aData* data, FuriString* str); + +void nfc_render_iso14443_4a_extra(const Iso14443_4aData* data, FuriString* str); diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_4b/iso14443_4b.c b/applications/main/nfc/helpers/protocol_support/iso14443_4b/iso14443_4b.c new file mode 100644 index 00000000000..a0c70a22e91 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/iso14443_4b/iso14443_4b.c @@ -0,0 +1,118 @@ +#include "iso14443_4b.h" +#include "iso14443_4b_render.h" + +#include + +#include "nfc/nfc_app_i.h" + +#include "../nfc_protocol_support_common.h" +#include "../nfc_protocol_support_gui_common.h" +#include "../iso14443_3b/iso14443_3b_i.h" + +static void nfc_scene_info_on_enter_iso14443_4b(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const Iso14443_4bData* data = nfc_device_get_data(device, NfcProtocolIso14443_4b); + + FuriString* temp_str = furi_string_alloc(); + furi_string_cat_printf( + temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); + nfc_render_iso14443_4b_info(data, NfcProtocolFormatTypeFull, temp_str); + + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 64, furi_string_get_cstr(temp_str)); + + furi_string_free(temp_str); +} + +static NfcCommand + nfc_scene_read_poller_callback_iso14443_4b(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolIso14443_4b); + + NfcApp* instance = context; + const Iso14443_4bPollerEvent* iso14443_4b_event = event.event_data; + + if(iso14443_4b_event->type == Iso14443_4bPollerEventTypeReady) { + nfc_device_set_data( + instance->nfc_device, NfcProtocolIso14443_4b, nfc_poller_get_data(instance->poller)); + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess); + return NfcCommandStop; + } + + return NfcCommandContinue; +} + +static void nfc_scene_read_on_enter_iso14443_4b(NfcApp* instance) { + nfc_poller_start(instance->poller, nfc_scene_read_poller_callback_iso14443_4b, instance); +} + +static void nfc_scene_read_success_on_enter_iso14443_4b(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const Iso14443_4bData* data = nfc_device_get_data(device, NfcProtocolIso14443_4b); + + FuriString* temp_str = furi_string_alloc(); + furi_string_cat_printf( + temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); + nfc_render_iso14443_4b_info(data, NfcProtocolFormatTypeShort, temp_str); + + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); + + furi_string_free(temp_str); +} + +static void nfc_scene_saved_menu_on_enter_iso14443_4b(NfcApp* instance) { + UNUSED(instance); +} + +static bool nfc_scene_read_menu_on_event_iso14443_4b(NfcApp* instance, uint32_t event) { + if(event == SubmenuIndexCommonEmulate) { + scene_manager_next_scene(instance->scene_manager, NfcSceneEmulate); + return true; + } + + return false; +} + +static bool nfc_scene_saved_menu_on_event_iso14443_4b(NfcApp* instance, uint32_t event) { + return nfc_scene_saved_menu_on_event_iso14443_3b_common(instance, event); +} + +const NfcProtocolSupportBase nfc_protocol_support_iso14443_4b = { + .features = NfcProtocolFeatureNone, + + .scene_info = + { + .on_enter = nfc_scene_info_on_enter_iso14443_4b, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read = + { + .on_enter = nfc_scene_read_on_enter_iso14443_4b, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read_menu = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_scene_read_menu_on_event_iso14443_4b, + }, + .scene_read_success = + { + .on_enter = nfc_scene_read_success_on_enter_iso14443_4b, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_saved_menu = + { + .on_enter = nfc_scene_saved_menu_on_enter_iso14443_4b, + .on_event = nfc_scene_saved_menu_on_event_iso14443_4b, + }, + .scene_save_name = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_emulate = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, +}; diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_4b/iso14443_4b.h b/applications/main/nfc/helpers/protocol_support/iso14443_4b/iso14443_4b.h new file mode 100644 index 00000000000..84a3456d4d1 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/iso14443_4b/iso14443_4b.h @@ -0,0 +1,5 @@ +#pragma once + +#include "../nfc_protocol_support_base.h" + +extern const NfcProtocolSupportBase nfc_protocol_support_iso14443_4b; diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_4b/iso14443_4b_render.c b/applications/main/nfc/helpers/protocol_support/iso14443_4b/iso14443_4b_render.c new file mode 100644 index 00000000000..e15b289a083 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/iso14443_4b/iso14443_4b_render.c @@ -0,0 +1,10 @@ +#include "iso14443_4b_render.h" + +#include "../iso14443_3b/iso14443_3b_render.h" + +void nfc_render_iso14443_4b_info( + const Iso14443_4bData* data, + NfcProtocolFormatType format_type, + FuriString* str) { + nfc_render_iso14443_3b_info(iso14443_4b_get_base_data(data), format_type, str); +} diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_4b/iso14443_4b_render.h b/applications/main/nfc/helpers/protocol_support/iso14443_4b/iso14443_4b_render.h new file mode 100644 index 00000000000..094892f83a4 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/iso14443_4b/iso14443_4b_render.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +#include "../nfc_protocol_support_render_common.h" + +void nfc_render_iso14443_4b_info( + const Iso14443_4bData* data, + NfcProtocolFormatType format_type, + FuriString* str); diff --git a/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3.c b/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3.c new file mode 100644 index 00000000000..7f861a03265 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3.c @@ -0,0 +1,144 @@ +#include "iso15693_3.h" +#include "iso15693_3_render.h" + +#include +#include + +#include "nfc/nfc_app_i.h" + +#include "../nfc_protocol_support_common.h" +#include "../nfc_protocol_support_gui_common.h" + +static void nfc_scene_info_on_enter_iso15693_3(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const Iso15693_3Data* data = nfc_device_get_data(device, NfcProtocolIso15693_3); + + FuriString* temp_str = furi_string_alloc(); + furi_string_cat_printf( + temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); + nfc_render_iso15693_3_info(data, NfcProtocolFormatTypeFull, temp_str); + + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 64, furi_string_get_cstr(temp_str)); + + furi_string_free(temp_str); +} + +static NfcCommand nfc_scene_read_poller_callback_iso15693_3(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolIso15693_3); + + NfcApp* instance = context; + const Iso15693_3PollerEvent* iso15693_3_event = event.event_data; + + if(iso15693_3_event->type == Iso15693_3PollerEventTypeReady) { + nfc_device_set_data( + instance->nfc_device, NfcProtocolIso15693_3, nfc_poller_get_data(instance->poller)); + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess); + return NfcCommandStop; + } + + return NfcCommandContinue; +} + +static void nfc_scene_read_on_enter_iso15693_3(NfcApp* instance) { + UNUSED(instance); + nfc_poller_start(instance->poller, nfc_scene_read_poller_callback_iso15693_3, instance); +} + +static void nfc_scene_read_success_on_enter_iso15693_3(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const Iso15693_3Data* data = nfc_device_get_data(device, NfcProtocolIso15693_3); + + FuriString* temp_str = furi_string_alloc(); + furi_string_cat_printf( + temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); + nfc_render_iso15693_3_info(data, NfcProtocolFormatTypeShort, temp_str); + + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); + + furi_string_free(temp_str); +} + +static NfcCommand + nfc_scene_emulate_listener_callback_iso15693_3(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.protocol == NfcProtocolIso15693_3); + furi_assert(event.event_data); + + NfcApp* nfc = context; + Iso15693_3ListenerEvent* iso15693_3_event = event.event_data; + + if(iso15693_3_event->type == Iso15693_3ListenerEventTypeCustomCommand) { + if(furi_string_size(nfc->text_box_store) < NFC_LOG_SIZE_MAX) { + furi_string_cat_printf(nfc->text_box_store, "R:"); + for(size_t i = 0; i < bit_buffer_get_size_bytes(iso15693_3_event->data->buffer); i++) { + furi_string_cat_printf( + nfc->text_box_store, + " %02X", + bit_buffer_get_byte(iso15693_3_event->data->buffer, i)); + } + furi_string_push_back(nfc->text_box_store, '\n'); + view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventListenerUpdate); + } + } + + return NfcCommandContinue; +} + +static void nfc_scene_emulate_on_enter_iso15693_3(NfcApp* instance) { + const Iso15693_3Data* data = nfc_device_get_data(instance->nfc_device, NfcProtocolIso15693_3); + + instance->listener = nfc_listener_alloc(instance->nfc, NfcProtocolIso15693_3, data); + nfc_listener_start( + instance->listener, nfc_scene_emulate_listener_callback_iso15693_3, instance); +} + +static bool nfc_scene_saved_menu_on_event_iso15693_3(NfcApp* instance, uint32_t event) { + if(event == SubmenuIndexCommonEdit) { + scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid); + return true; + } + + return false; +} + +const NfcProtocolSupportBase nfc_protocol_support_iso15693_3 = { + .features = NfcProtocolFeatureEmulateFull | NfcProtocolFeatureEditUid, + + .scene_info = + { + .on_enter = nfc_scene_info_on_enter_iso15693_3, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read = + { + .on_enter = nfc_scene_read_on_enter_iso15693_3, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read_menu = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read_success = + { + .on_enter = nfc_scene_read_success_on_enter_iso15693_3, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_saved_menu = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_scene_saved_menu_on_event_iso15693_3, + }, + .scene_save_name = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_emulate = + { + .on_enter = nfc_scene_emulate_on_enter_iso15693_3, + .on_event = nfc_protocol_support_common_on_event_empty, + }, +}; diff --git a/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3.h b/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3.h new file mode 100644 index 00000000000..a26633ee610 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3.h @@ -0,0 +1,5 @@ +#pragma once + +#include "../nfc_protocol_support_base.h" + +extern const NfcProtocolSupportBase nfc_protocol_support_iso15693_3; diff --git a/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3_render.c b/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3_render.c new file mode 100644 index 00000000000..92bdb22dc97 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3_render.c @@ -0,0 +1,92 @@ +#include "iso15693_3_render.h" + +#define NFC_RENDER_ISO15693_3_MAX_BYTES (128U) + +void nfc_render_iso15693_3_info( + const Iso15693_3Data* data, + NfcProtocolFormatType format_type, + FuriString* str) { + if(format_type == NfcProtocolFormatTypeFull) { + furi_string_cat(str, "ISO15693-3 (NFC-V)\n"); + } + + nfc_render_iso15693_3_brief(data, str); + + if(format_type == NfcProtocolFormatTypeFull) { + nfc_render_iso15693_3_extra(data, str); + } +} + +void nfc_render_iso15693_3_brief(const Iso15693_3Data* data, FuriString* str) { + furi_string_cat_printf(str, "UID:"); + + size_t uid_len; + const uint8_t* uid = iso15693_3_get_uid(data, &uid_len); + + for(size_t i = 0; i < uid_len; i++) { + furi_string_cat_printf(str, " %02X", uid[i]); + } + + if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_MEMORY) { + const uint16_t block_count = iso15693_3_get_block_count(data); + const uint8_t block_size = iso15693_3_get_block_size(data); + + furi_string_cat_printf(str, "Memory: %u bytes\n", block_count * block_size); + furi_string_cat_printf(str, "(%u blocks x %u bytes)", block_count, block_size); + } +} + +void nfc_render_iso15693_3_extra(const Iso15693_3Data* data, FuriString* str) { + furi_string_cat(str, "\n\e#General info\n"); + if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_DSFID) { + furi_string_cat_printf(str, "DSFID: %02X\n", data->system_info.ic_ref); + } + + if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_AFI) { + furi_string_cat_printf(str, "AFI: %02X\n", data->system_info.afi); + } + + if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_IC_REF) { + furi_string_cat_printf(str, "IC Reference: %02X\n", data->system_info.ic_ref); + } + + furi_string_cat(str, "\e#Lock bits\n"); + + if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_DSFID) { + furi_string_cat_printf( + str, "DSFID: %s locked\n", data->settings.lock_bits.dsfid ? "" : "not"); + } + + if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_AFI) { + furi_string_cat_printf( + str, "AFI: %s locked\n", data->settings.lock_bits.dsfid ? "" : "not"); + } + + if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_MEMORY) { + furi_string_cat(str, "\e#Memory data\n\e*--------------------\n"); + + const uint16_t block_count = iso15693_3_get_block_count(data); + const uint8_t block_size = iso15693_3_get_block_size(data); + const uint16_t display_block_count = + MIN(NFC_RENDER_ISO15693_3_MAX_BYTES / block_size, block_count); + + for(uint32_t i = 0; i < display_block_count; ++i) { + furi_string_cat(str, "\e*"); + + const uint8_t* block_data = iso15693_3_get_block_data(data, i); + for(uint32_t j = 0; j < block_size; ++j) { + furi_string_cat_printf(str, "%02X ", block_data[j]); + } + + const char* lock_str = iso15693_3_is_block_locked(data, i) ? "[LOCK]" : ""; + furi_string_cat_printf(str, "| %s\n", lock_str); + } + + if(block_count != display_block_count) { + furi_string_cat_printf( + str, + "(Data is too big. Showing only the first %u bytes.)", + display_block_count * block_size); + } + } +} diff --git a/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3_render.h b/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3_render.h new file mode 100644 index 00000000000..d531fd2eb07 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3_render.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +#include "../nfc_protocol_support_render_common.h" + +void nfc_render_iso15693_3_info( + const Iso15693_3Data* data, + NfcProtocolFormatType format_type, + FuriString* str); + +void nfc_render_iso15693_3_brief(const Iso15693_3Data* data, FuriString* str); + +void nfc_render_iso15693_3_extra(const Iso15693_3Data* data, FuriString* str); diff --git a/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c b/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c new file mode 100644 index 00000000000..3e0468cd91e --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c @@ -0,0 +1,252 @@ +#include "mf_classic.h" +#include "mf_classic_render.h" + +#include + +#include "nfc/nfc_app_i.h" + +#include "../nfc_protocol_support_common.h" +#include "../nfc_protocol_support_gui_common.h" + +#define TAG "MfClassicApp" + +enum { + SubmenuIndexDetectReader = SubmenuIndexCommonMax, + SubmenuIndexWrite, + SubmenuIndexUpdate, +}; + +static void nfc_scene_info_on_enter_mf_classic(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic); + + FuriString* temp_str = furi_string_alloc(); + furi_string_cat_printf( + temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); + nfc_render_mf_classic_info(data, NfcProtocolFormatTypeFull, temp_str); + + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); + + furi_string_free(temp_str); +} + +static void nfc_scene_more_info_on_enter_mf_classic(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const MfClassicData* mfc_data = nfc_device_get_data(device, NfcProtocolMfClassic); + + furi_string_reset(instance->text_box_store); + nfc_render_mf_classic_dump(mfc_data, instance->text_box_store); + + text_box_set_font(instance->text_box, TextBoxFontHex); + text_box_set_text(instance->text_box, furi_string_get_cstr(instance->text_box_store)); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewTextBox); +} + +static NfcCommand nfc_scene_read_poller_callback_mf_classic(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolMfClassic); + + NfcApp* instance = context; + const MfClassicPollerEvent* mfc_event = event.event_data; + NfcCommand command = NfcCommandContinue; + + if(mfc_event->type == MfClassicPollerEventTypeRequestMode) { + nfc_device_set_data( + instance->nfc_device, NfcProtocolMfClassic, nfc_poller_get_data(instance->poller)); + size_t uid_len = 0; + const uint8_t* uid = nfc_device_get_uid(instance->nfc_device, &uid_len); + if(mf_classic_key_cache_load(instance->mfc_key_cache, uid, uid_len)) { + FURI_LOG_I(TAG, "Key cache found"); + mfc_event->data->poller_mode.mode = MfClassicPollerModeRead; + } else { + FURI_LOG_I(TAG, "Key cache not found"); + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcCustomEventPollerIncomplete); + command = NfcCommandStop; + } + } else if(mfc_event->type == MfClassicPollerEventTypeRequestReadSector) { + uint8_t sector_num = 0; + MfClassicKey key = {}; + MfClassicKeyType key_type = MfClassicKeyTypeA; + if(mf_classic_key_cahce_get_next_key( + instance->mfc_key_cache, §or_num, &key, &key_type)) { + mfc_event->data->read_sector_request_data.sector_num = sector_num; + mfc_event->data->read_sector_request_data.key = key; + mfc_event->data->read_sector_request_data.key_type = key_type; + mfc_event->data->read_sector_request_data.key_provided = true; + } else { + mfc_event->data->read_sector_request_data.key_provided = false; + } + } else if(mfc_event->type == MfClassicPollerEventTypeSuccess) { + nfc_device_set_data( + instance->nfc_device, NfcProtocolMfClassic, nfc_poller_get_data(instance->poller)); + const MfClassicData* mfc_data = + nfc_device_get_data(instance->nfc_device, NfcProtocolMfClassic); + NfcCustomEvent custom_event = mf_classic_is_card_read(mfc_data) ? + NfcCustomEventPollerSuccess : + NfcCustomEventPollerIncomplete; + view_dispatcher_send_custom_event(instance->view_dispatcher, custom_event); + command = NfcCommandStop; + } + + return command; +} + +static void nfc_scene_read_on_enter_mf_classic(NfcApp* instance) { + mf_classic_key_cache_reset(instance->mfc_key_cache); + nfc_poller_start(instance->poller, nfc_scene_read_poller_callback_mf_classic, instance); +} + +static bool nfc_scene_read_on_event_mf_classic(NfcApp* instance, uint32_t event) { + if(event == NfcCustomEventPollerIncomplete) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicDictAttack); + } + + return true; +} + +static void nfc_scene_read_menu_on_enter_mf_classic(NfcApp* instance) { + Submenu* submenu = instance->submenu; + const MfClassicData* data = nfc_device_get_data(instance->nfc_device, NfcProtocolMfClassic); + + if(!mf_classic_is_card_read(data)) { + submenu_add_item( + submenu, + "Detect Reader", + SubmenuIndexDetectReader, + nfc_protocol_support_common_submenu_callback, + instance); + } +} + +static void nfc_scene_read_success_on_enter_mf_classic(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic); + + FuriString* temp_str = furi_string_alloc(); + furi_string_cat_printf( + temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); + nfc_render_mf_classic_info(data, NfcProtocolFormatTypeShort, temp_str); + + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); + + furi_string_free(temp_str); +} + +static void nfc_scene_saved_menu_on_enter_mf_classic(NfcApp* instance) { + Submenu* submenu = instance->submenu; + const MfClassicData* data = nfc_device_get_data(instance->nfc_device, NfcProtocolMfClassic); + + if(!mf_classic_is_card_read(data)) { + submenu_add_item( + submenu, + "Detect Reader", + SubmenuIndexDetectReader, + nfc_protocol_support_common_submenu_callback, + instance); + } + submenu_add_item( + submenu, + "Write to Initial Card", + SubmenuIndexWrite, + nfc_protocol_support_common_submenu_callback, + instance); + submenu_add_item( + submenu, + "Update from Initial Card", + SubmenuIndexUpdate, + nfc_protocol_support_common_submenu_callback, + instance); +} + +static void nfc_scene_emulate_on_enter_mf_classic(NfcApp* instance) { + const MfClassicData* data = nfc_device_get_data(instance->nfc_device, NfcProtocolMfClassic); + instance->listener = nfc_listener_alloc(instance->nfc, NfcProtocolMfClassic, data); + nfc_listener_start(instance->listener, NULL, NULL); +} + +static bool nfc_scene_read_menu_on_event_mf_classic(NfcApp* instance, uint32_t event) { + if(event == SubmenuIndexDetectReader) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicDetectReader); + dolphin_deed(DolphinDeedNfcDetectReader); + return true; + } + + return false; +} + +static bool nfc_scene_saved_menu_on_event_mf_classic(NfcApp* instance, uint32_t event) { + bool consumed = false; + + if(event == SubmenuIndexDetectReader) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicDetectReader); + consumed = true; + } else if(event == SubmenuIndexWrite) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicWriteInitial); + consumed = true; + } else if(event == SubmenuIndexUpdate) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicUpdateInitial); + consumed = true; + } + + return consumed; +} + +static bool nfc_scene_save_name_on_event_mf_classic(NfcApp* instance, uint32_t event) { + bool consumed = false; + + if(event == NfcCustomEventTextInputDone) { + mf_classic_key_cache_save( + instance->mfc_key_cache, + nfc_device_get_data(instance->nfc_device, NfcProtocolMfClassic)); + consumed = true; + } + + return consumed; +} + +const NfcProtocolSupportBase nfc_protocol_support_mf_classic = { + .features = NfcProtocolFeatureEmulateFull | NfcProtocolFeatureMoreInfo, + + .scene_info = + { + .on_enter = nfc_scene_info_on_enter_mf_classic, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_more_info = + { + .on_enter = nfc_scene_more_info_on_enter_mf_classic, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read = + { + .on_enter = nfc_scene_read_on_enter_mf_classic, + .on_event = nfc_scene_read_on_event_mf_classic, + }, + .scene_read_menu = + { + .on_enter = nfc_scene_read_menu_on_enter_mf_classic, + .on_event = nfc_scene_read_menu_on_event_mf_classic, + }, + .scene_read_success = + { + .on_enter = nfc_scene_read_success_on_enter_mf_classic, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_saved_menu = + { + .on_enter = nfc_scene_saved_menu_on_enter_mf_classic, + .on_event = nfc_scene_saved_menu_on_event_mf_classic, + }, + .scene_save_name = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_scene_save_name_on_event_mf_classic, + }, + .scene_emulate = + { + .on_enter = nfc_scene_emulate_on_enter_mf_classic, + .on_event = nfc_protocol_support_common_on_event_empty, + }, +}; diff --git a/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.h b/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.h new file mode 100644 index 00000000000..d0f09f5d78e --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.h @@ -0,0 +1,5 @@ +#pragma once + +#include "../nfc_protocol_support_base.h" + +extern const NfcProtocolSupportBase nfc_protocol_support_mf_classic; diff --git a/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic_render.c b/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic_render.c new file mode 100644 index 00000000000..5bd4a6b6ddd --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic_render.c @@ -0,0 +1,30 @@ +#include "mf_classic_render.h" + +#include "../iso14443_3a/iso14443_3a_render.h" + +void nfc_render_mf_classic_info( + const MfClassicData* data, + NfcProtocolFormatType format_type, + FuriString* str) { + nfc_render_iso14443_3a_info(data->iso14443_3a_data, format_type, str); + + uint8_t sectors_total = mf_classic_get_total_sectors_num(data->type); + uint8_t keys_total = sectors_total * 2; + uint8_t keys_found = 0; + uint8_t sectors_read = 0; + mf_classic_get_read_sectors_and_keys(data, §ors_read, &keys_found); + + furi_string_cat_printf(str, "\nKeys Found: %u/%u", keys_found, keys_total); + furi_string_cat_printf(str, "\nSectors Read: %u/%u", sectors_read, sectors_total); +} + +void nfc_render_mf_classic_dump(const MfClassicData* data, FuriString* str) { + uint16_t total_blocks = mf_classic_get_total_block_num(data->type); + + for(size_t i = 0; i < total_blocks; i++) { + for(size_t j = 0; j < sizeof(MfClassicBlock); j += 2) { + furi_string_cat_printf( + str, "%02X%02X ", data->block[i].data[j], data->block[i].data[j + 1]); + } + } +} diff --git a/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic_render.h b/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic_render.h new file mode 100644 index 00000000000..7e1619761f4 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic_render.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +#include "../nfc_protocol_support_render_common.h" + +void nfc_render_mf_classic_info( + const MfClassicData* data, + NfcProtocolFormatType format_type, + FuriString* str); + +void nfc_render_mf_classic_dump(const MfClassicData* data, FuriString* str); diff --git a/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire.c b/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire.c new file mode 100644 index 00000000000..bc05c2a4c36 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire.c @@ -0,0 +1,120 @@ +#include "mf_desfire.h" +#include "mf_desfire_render.h" + +#include + +#include "nfc/nfc_app_i.h" + +#include "../nfc_protocol_support_common.h" +#include "../nfc_protocol_support_gui_common.h" +#include "../iso14443_4a/iso14443_4a_i.h" + +static void nfc_scene_info_on_enter_mf_desfire(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const MfDesfireData* data = nfc_device_get_data(device, NfcProtocolMfDesfire); + + FuriString* temp_str = furi_string_alloc(); + furi_string_cat_printf( + temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); + nfc_render_mf_desfire_info(data, NfcProtocolFormatTypeFull, temp_str); + + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); + + furi_string_free(temp_str); +} + +static void nfc_scene_more_info_on_enter_mf_desfire(NfcApp* instance) { + // Jump to advanced scene right away + scene_manager_next_scene(instance->scene_manager, NfcSceneMfDesfireMoreInfo); +} + +static NfcCommand nfc_scene_read_poller_callback_mf_desfire(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolMfDesfire); + + NfcApp* instance = context; + const MfDesfirePollerEvent* mf_desfire_event = event.event_data; + + if(mf_desfire_event->type == MfDesfirePollerEventTypeReadSuccess) { + nfc_device_set_data( + instance->nfc_device, NfcProtocolMfDesfire, nfc_poller_get_data(instance->poller)); + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess); + return NfcCommandStop; + } + + return NfcCommandContinue; +} + +static void nfc_scene_read_on_enter_mf_desfire(NfcApp* instance) { + nfc_poller_start(instance->poller, nfc_scene_read_poller_callback_mf_desfire, instance); +} + +static void nfc_scene_read_success_on_enter_mf_desfire(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const MfDesfireData* data = nfc_device_get_data(device, NfcProtocolMfDesfire); + + FuriString* temp_str = furi_string_alloc(); + furi_string_cat_printf( + temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); + nfc_render_mf_desfire_info(data, NfcProtocolFormatTypeShort, temp_str); + + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); + + furi_string_free(temp_str); +} + +static void nfc_scene_emulate_on_enter_mf_desfire(NfcApp* instance) { + const Iso14443_4aData* iso14443_4a_data = + nfc_device_get_data(instance->nfc_device, NfcProtocolIso14443_4a); + + instance->listener = + nfc_listener_alloc(instance->nfc, NfcProtocolIso14443_4a, iso14443_4a_data); + nfc_listener_start( + instance->listener, nfc_scene_emulate_listener_callback_iso14443_4a, instance); +} + +const NfcProtocolSupportBase nfc_protocol_support_mf_desfire = { + .features = NfcProtocolFeatureEmulateUid | NfcProtocolFeatureMoreInfo, + + .scene_info = + { + .on_enter = nfc_scene_info_on_enter_mf_desfire, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_more_info = + { + .on_enter = nfc_scene_more_info_on_enter_mf_desfire, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read = + { + .on_enter = nfc_scene_read_on_enter_mf_desfire, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read_menu = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read_success = + { + .on_enter = nfc_scene_read_success_on_enter_mf_desfire, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_saved_menu = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_save_name = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_emulate = + { + .on_enter = nfc_scene_emulate_on_enter_mf_desfire, + .on_event = nfc_protocol_support_common_on_event_empty, + }, +}; diff --git a/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire.h b/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire.h new file mode 100644 index 00000000000..504860f16a4 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire.h @@ -0,0 +1,5 @@ +#pragma once + +#include "../nfc_protocol_support_base.h" + +extern const NfcProtocolSupportBase nfc_protocol_support_mf_desfire; diff --git a/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire_render.c b/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire_render.c new file mode 100644 index 00000000000..883ea720b05 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire_render.c @@ -0,0 +1,249 @@ +#include "mf_desfire_render.h" + +#include "../iso14443_4a/iso14443_4a_render.h" + +void nfc_render_mf_desfire_info( + const MfDesfireData* data, + NfcProtocolFormatType format_type, + FuriString* str) { + nfc_render_iso14443_4a_brief(mf_desfire_get_base_data(data), str); + + const uint32_t bytes_total = 1UL << (data->version.sw_storage >> 1); + const uint32_t bytes_free = data->free_memory.is_present ? data->free_memory.bytes_free : 0; + + furi_string_cat_printf(str, "\n%lu", bytes_total); + + if(data->version.sw_storage & 1) { + furi_string_push_back(str, '+'); + } + + furi_string_cat_printf(str, " bytes, %lu bytes free\n", bytes_free); + + const uint32_t app_count = simple_array_get_count(data->applications); + uint32_t file_count = 0; + + for(uint32_t i = 0; i < app_count; ++i) { + const MfDesfireApplication* app = simple_array_cget(data->applications, i); + file_count += simple_array_get_count(app->file_ids); + } + + furi_string_cat_printf(str, "%lu Application%s", app_count, app_count != 1 ? "s" : ""); + furi_string_cat_printf(str, ", %lu File%s", file_count, file_count != 1 ? "s" : ""); + + if(format_type != NfcProtocolFormatTypeFull) return; + + furi_string_cat(str, "\n\e#ISO14443-4 data"); + nfc_render_iso14443_4a_extra(mf_desfire_get_base_data(data), str); +} + +void nfc_render_mf_desfire_data(const MfDesfireData* data, FuriString* str) { + nfc_render_mf_desfire_version(&data->version, str); + nfc_render_mf_desfire_free_memory(&data->free_memory, str); + nfc_render_mf_desfire_key_settings(&data->master_key_settings, str); + + for(uint32_t i = 0; i < simple_array_get_count(data->master_key_versions); ++i) { + nfc_render_mf_desfire_key_version(simple_array_cget(data->master_key_versions, i), i, str); + } +} + +void nfc_render_mf_desfire_version(const MfDesfireVersion* data, FuriString* str) { + furi_string_cat_printf( + str, + "%02x:%02x:%02x:%02x:%02x:%02x:%02x\n", + data->uid[0], + data->uid[1], + data->uid[2], + data->uid[3], + data->uid[4], + data->uid[5], + data->uid[6]); + furi_string_cat_printf( + str, + "hw %02x type %02x sub %02x\n" + " maj %02x min %02x\n" + " size %02x proto %02x\n", + data->hw_vendor, + data->hw_type, + data->hw_subtype, + data->hw_major, + data->hw_minor, + data->hw_storage, + data->hw_proto); + furi_string_cat_printf( + str, + "sw %02x type %02x sub %02x\n" + " maj %02x min %02x\n" + " size %02x proto %02x\n", + data->sw_vendor, + data->sw_type, + data->sw_subtype, + data->sw_major, + data->sw_minor, + data->sw_storage, + data->sw_proto); + furi_string_cat_printf( + str, + "batch %02x:%02x:%02x:%02x:%02x\n" + "week %d year %d\n", + data->batch[0], + data->batch[1], + data->batch[2], + data->batch[3], + data->batch[4], + data->prod_week, + data->prod_year); +} + +void nfc_render_mf_desfire_free_memory(const MfDesfireFreeMemory* data, FuriString* str) { + if(data->is_present) { + furi_string_cat_printf(str, "freeMem %lu\n", data->bytes_free); + } +} + +void nfc_render_mf_desfire_key_settings(const MfDesfireKeySettings* data, FuriString* str) { + furi_string_cat_printf(str, "changeKeyID %d\n", data->change_key_id); + furi_string_cat_printf(str, "configChangeable %d\n", data->is_config_changeable); + furi_string_cat_printf(str, "freeCreateDelete %d\n", data->is_free_create_delete); + furi_string_cat_printf(str, "freeDirectoryList %d\n", data->is_free_directory_list); + furi_string_cat_printf(str, "masterChangeable %d\n", data->is_master_key_changeable); + + if(data->flags) { + furi_string_cat_printf(str, "flags %d\n", data->flags); + } + + furi_string_cat_printf(str, "maxKeys %d\n", data->max_keys); +} + +void nfc_render_mf_desfire_key_version( + const MfDesfireKeyVersion* data, + uint32_t index, + FuriString* str) { + furi_string_cat_printf(str, "key %lu version %u\n", index, *data); +} + +void nfc_render_mf_desfire_application_id(const MfDesfireApplicationId* data, FuriString* str) { + const uint8_t* app_id = data->data; + furi_string_cat_printf(str, "Application %02x%02x%02x\n", app_id[0], app_id[1], app_id[2]); +} + +void nfc_render_mf_desfire_application(const MfDesfireApplication* data, FuriString* str) { + nfc_render_mf_desfire_key_settings(&data->key_settings, str); + + for(uint32_t i = 0; i < simple_array_get_count(data->key_versions); ++i) { + nfc_render_mf_desfire_key_version(simple_array_cget(data->key_versions, i), i, str); + } +} + +void nfc_render_mf_desfire_file_id(const MfDesfireFileId* data, FuriString* str) { + furi_string_cat_printf(str, "File %d\n", *data); +} + +void nfc_render_mf_desfire_file_settings_data( + const MfDesfireFileSettings* settings, + const MfDesfireFileData* data, + FuriString* str) { + const char* type; + switch(settings->type) { + case MfDesfireFileTypeStandard: + type = "standard"; + break; + case MfDesfireFileTypeBackup: + type = "backup"; + break; + case MfDesfireFileTypeValue: + type = "value"; + break; + case MfDesfireFileTypeLinearRecord: + type = "linear"; + break; + case MfDesfireFileTypeCyclicRecord: + type = "cyclic"; + break; + default: + type = "unknown"; + } + + const char* comm; + switch(settings->comm) { + case MfDesfireFileCommunicationSettingsPlaintext: + comm = "plain"; + break; + case MfDesfireFileCommunicationSettingsAuthenticated: + comm = "auth"; + break; + case MfDesfireFileCommunicationSettingsEnciphered: + comm = "enciphered"; + break; + default: + comm = "unknown"; + } + + furi_string_cat_printf(str, "%s %s\n", type, comm); + furi_string_cat_printf( + str, + "r %d w %d rw %d c %d\n", + settings->access_rights >> 12 & 0xF, + settings->access_rights >> 8 & 0xF, + settings->access_rights >> 4 & 0xF, + settings->access_rights & 0xF); + + uint32_t record_count = 1; + uint32_t record_size = 0; + + switch(settings->type) { + case MfDesfireFileTypeStandard: + case MfDesfireFileTypeBackup: + record_size = settings->data.size; + furi_string_cat_printf(str, "size %lu\n", record_size); + break; + case MfDesfireFileTypeValue: + furi_string_cat_printf( + str, "lo %lu hi %lu\n", settings->value.lo_limit, settings->value.hi_limit); + furi_string_cat_printf( + str, + "limit %lu enabled %d\n", + settings->value.limited_credit_value, + settings->value.limited_credit_enabled); + break; + case MfDesfireFileTypeLinearRecord: + case MfDesfireFileTypeCyclicRecord: + record_count = settings->record.cur; + record_size = settings->record.size; + furi_string_cat_printf(str, "size %lu\n", record_size); + furi_string_cat_printf(str, "num %lu max %lu\n", record_count, settings->record.max); + break; + } + + if(simple_array_get_count(data->data) == 0) { + return; + } + + for(uint32_t rec = 0; rec < record_count; rec++) { + furi_string_cat_printf(str, "record %lu\n", rec); + for(uint32_t ch = 0; ch < record_size; ch += 4) { + furi_string_cat_printf(str, "%03lx|", ch); + for(uint32_t i = 0; i < 4; i++) { + if(ch + i < record_size) { + const uint32_t data_index = rec * record_size + ch + i; + const uint8_t data_byte = + *(const uint8_t*)simple_array_cget(data->data, data_index); + furi_string_cat_printf(str, "%02x ", data_byte); + } else { + furi_string_cat_printf(str, " "); + } + } + for(uint32_t i = 0; i < 4 && ch + i < record_size; i++) { + const uint32_t data_index = rec * record_size + ch + i; + const uint8_t data_byte = + *(const uint8_t*)simple_array_cget(data->data, data_index); + if(isprint(data_byte)) { + furi_string_cat_printf(str, "%c", data_byte); + } else { + furi_string_cat_printf(str, "."); + } + } + furi_string_push_back(str, '\n'); + } + furi_string_push_back(str, '\n'); + } +} diff --git a/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire_render.h b/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire_render.h new file mode 100644 index 00000000000..ff5e815bff0 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire_render.h @@ -0,0 +1,34 @@ +#pragma once + +#include + +#include "../nfc_protocol_support_render_common.h" + +void nfc_render_mf_desfire_info( + const MfDesfireData* data, + NfcProtocolFormatType format_type, + FuriString* str); + +void nfc_render_mf_desfire_data(const MfDesfireData* data, FuriString* str); + +void nfc_render_mf_desfire_version(const MfDesfireVersion* data, FuriString* str); + +void nfc_render_mf_desfire_free_memory(const MfDesfireFreeMemory* data, FuriString* str); + +void nfc_render_mf_desfire_key_settings(const MfDesfireKeySettings* data, FuriString* str); + +void nfc_render_mf_desfire_key_version( + const MfDesfireKeyVersion* data, + uint32_t index, + FuriString* str); + +void nfc_render_mf_desfire_application_id(const MfDesfireApplicationId* data, FuriString* str); + +void nfc_render_mf_desfire_application(const MfDesfireApplication* data, FuriString* str); + +void nfc_render_mf_desfire_file_id(const MfDesfireFileId* data, FuriString* str); + +void nfc_render_mf_desfire_file_settings_data( + const MfDesfireFileSettings* settings, + const MfDesfireFileData* data, + FuriString* str); diff --git a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c new file mode 100644 index 00000000000..c4fd04c7e5a --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c @@ -0,0 +1,196 @@ +#include "mf_ultralight.h" +#include "mf_ultralight_render.h" + +#include + +#include "nfc/nfc_app_i.h" + +#include "../nfc_protocol_support_common.h" +#include "../nfc_protocol_support_gui_common.h" + +enum { + SubmenuIndexUnlock = SubmenuIndexCommonMax, + SubmenuIndexUnlockByReader, + SubmenuIndexUnlockByPassword, +}; + +static void nfc_scene_info_on_enter_mf_ultralight(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const MfUltralightData* data = nfc_device_get_data(device, NfcProtocolMfUltralight); + + FuriString* temp_str = furi_string_alloc(); + furi_string_cat_printf( + temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); + nfc_render_mf_ultralight_info(data, NfcProtocolFormatTypeFull, temp_str); + + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); + + furi_string_free(temp_str); +} + +static void nfc_scene_more_info_on_enter_mf_ultralight(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const MfUltralightData* mfu = nfc_device_get_data(device, NfcProtocolMfUltralight); + + furi_string_reset(instance->text_box_store); + nfc_render_mf_ultralight_dump(mfu, instance->text_box_store); + + text_box_set_font(instance->text_box, TextBoxFontHex); + text_box_set_text(instance->text_box, furi_string_get_cstr(instance->text_box_store)); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewTextBox); +} + +static NfcCommand + nfc_scene_read_poller_callback_mf_ultralight(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolMfUltralight); + + NfcApp* instance = context; + const MfUltralightPollerEvent* mf_ultralight_event = event.event_data; + + if(mf_ultralight_event->type == MfUltralightPollerEventTypeReadSuccess) { + nfc_device_set_data( + instance->nfc_device, NfcProtocolMfUltralight, nfc_poller_get_data(instance->poller)); + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess); + return NfcCommandStop; + } else if(mf_ultralight_event->type == MfUltralightPollerEventTypeAuthRequest) { + nfc_device_set_data( + instance->nfc_device, NfcProtocolMfUltralight, nfc_poller_get_data(instance->poller)); + const MfUltralightData* data = + nfc_device_get_data(instance->nfc_device, NfcProtocolMfUltralight); + if(instance->mf_ul_auth->type == MfUltralightAuthTypeXiaomi) { + if(mf_ultralight_generate_xiaomi_pass( + instance->mf_ul_auth, + data->iso14443_3a_data->uid, + data->iso14443_3a_data->uid_len)) { + mf_ultralight_event->data->auth_context.skip_auth = false; + } + } else if(instance->mf_ul_auth->type == MfUltralightAuthTypeAmiibo) { + if(mf_ultralight_generate_amiibo_pass( + instance->mf_ul_auth, + data->iso14443_3a_data->uid, + data->iso14443_3a_data->uid_len)) { + mf_ultralight_event->data->auth_context.skip_auth = false; + } + } else if( + instance->mf_ul_auth->type == MfUltralightAuthTypeManual || + instance->mf_ul_auth->type == MfUltralightAuthTypeReader) { + mf_ultralight_event->data->auth_context.skip_auth = false; + } else { + mf_ultralight_event->data->auth_context.skip_auth = true; + } + if(!mf_ultralight_event->data->auth_context.skip_auth) { + mf_ultralight_event->data->auth_context.password = instance->mf_ul_auth->password; + } + } else if(mf_ultralight_event->type == MfUltralightPollerEventTypeAuthSuccess) { + instance->mf_ul_auth->pack = mf_ultralight_event->data->auth_context.pack; + } + + return NfcCommandContinue; +} + +static void nfc_scene_read_on_enter_mf_ultralight(NfcApp* instance) { + nfc_poller_start(instance->poller, nfc_scene_read_poller_callback_mf_ultralight, instance); +} + +static void nfc_scene_read_and_saved_menu_on_enter_mf_ultralight(NfcApp* instance) { + Submenu* submenu = instance->submenu; + + const MfUltralightData* data = + nfc_device_get_data(instance->nfc_device, NfcProtocolMfUltralight); + + if(!mf_ultralight_is_all_data_read(data)) { + submenu_add_item( + submenu, + "Unlock", + SubmenuIndexUnlock, + nfc_protocol_support_common_submenu_callback, + instance); + } +} + +static void nfc_scene_read_success_on_enter_mf_ultralight(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const MfUltralightData* data = nfc_device_get_data(device, NfcProtocolMfUltralight); + + FuriString* temp_str = furi_string_alloc(); + + bool unlocked = + scene_manager_has_previous_scene(instance->scene_manager, NfcSceneMfUltralightUnlockWarn); + if(unlocked) { + nfc_render_mf_ultralight_pwd_pack(data, temp_str); + } else { + furi_string_cat_printf( + temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); + + nfc_render_mf_ultralight_info(data, NfcProtocolFormatTypeShort, temp_str); + } + + mf_ultralight_auth_reset(instance->mf_ul_auth); + + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); + + furi_string_free(temp_str); +} + +static void nfc_scene_emulate_on_enter_mf_ultralight(NfcApp* instance) { + const MfUltralightData* data = + nfc_device_get_data(instance->nfc_device, NfcProtocolMfUltralight); + instance->listener = nfc_listener_alloc(instance->nfc, NfcProtocolMfUltralight, data); + nfc_listener_start(instance->listener, NULL, NULL); +} + +static bool + nfc_scene_read_and_saved_menu_on_event_mf_ultralight(NfcApp* instance, uint32_t event) { + if(event == SubmenuIndexUnlock) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightUnlockMenu); + return true; + } + return false; +} + +const NfcProtocolSupportBase nfc_protocol_support_mf_ultralight = { + .features = NfcProtocolFeatureEmulateFull | NfcProtocolFeatureMoreInfo, + + .scene_info = + { + .on_enter = nfc_scene_info_on_enter_mf_ultralight, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_more_info = + { + .on_enter = nfc_scene_more_info_on_enter_mf_ultralight, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read = + { + .on_enter = nfc_scene_read_on_enter_mf_ultralight, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read_menu = + { + .on_enter = nfc_scene_read_and_saved_menu_on_enter_mf_ultralight, + .on_event = nfc_scene_read_and_saved_menu_on_event_mf_ultralight, + }, + .scene_read_success = + { + .on_enter = nfc_scene_read_success_on_enter_mf_ultralight, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_saved_menu = + { + .on_enter = nfc_scene_read_and_saved_menu_on_enter_mf_ultralight, + .on_event = nfc_scene_read_and_saved_menu_on_event_mf_ultralight, + }, + .scene_save_name = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_emulate = + { + .on_enter = nfc_scene_emulate_on_enter_mf_ultralight, + .on_event = nfc_protocol_support_common_on_event_empty, + }, +}; diff --git a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.h b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.h new file mode 100644 index 00000000000..83e58732e41 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.h @@ -0,0 +1,5 @@ +#pragma once + +#include "../nfc_protocol_support_base.h" + +extern const NfcProtocolSupportBase nfc_protocol_support_mf_ultralight; diff --git a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight_render.c b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight_render.c new file mode 100644 index 00000000000..5296f480712 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight_render.c @@ -0,0 +1,45 @@ +#include "mf_ultralight_render.h" + +#include "../iso14443_3a/iso14443_3a_render.h" + +static void nfc_render_mf_ultralight_pages_count(const MfUltralightData* data, FuriString* str) { + furi_string_cat_printf(str, "\nPages Read: %u/%u", data->pages_read, data->pages_total); + if(data->pages_read != data->pages_total) { + furi_string_cat_printf(str, "\nPassword-protected pages!"); + } +} + +void nfc_render_mf_ultralight_pwd_pack(const MfUltralightData* data, FuriString* str) { + bool all_pages = mf_ultralight_is_all_data_read(data); + furi_string_cat_printf(str, "\e#%s pages unlocked!", all_pages ? "All" : "Not all"); + + MfUltralightConfigPages* config; + mf_ultralight_get_config_page(data, &config); + + furi_string_cat_printf(str, "\nPassword: "); + nfc_render_iso14443_3a_format_bytes( + str, config->password.data, MF_ULTRALIGHT_AUTH_PASSWORD_SIZE); + + furi_string_cat_printf(str, "\nPACK: "); + nfc_render_iso14443_3a_format_bytes(str, config->pack.data, MF_ULTRALIGHT_AUTH_PACK_SIZE); + + nfc_render_mf_ultralight_pages_count(data, str); +} + +void nfc_render_mf_ultralight_info( + const MfUltralightData* data, + NfcProtocolFormatType format_type, + FuriString* str) { + nfc_render_iso14443_3a_info(data->iso14443_3a_data, format_type, str); + + nfc_render_mf_ultralight_pages_count(data, str); +} + +void nfc_render_mf_ultralight_dump(const MfUltralightData* data, FuriString* str) { + for(size_t i = 0; i < data->pages_read; i++) { + const uint8_t* page_data = data->page[i].data; + for(size_t j = 0; j < MF_ULTRALIGHT_PAGE_SIZE; j += 2) { + furi_string_cat_printf(str, "%02X%02X ", page_data[j], page_data[j + 1]); + } + } +} diff --git a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight_render.h b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight_render.h new file mode 100644 index 00000000000..5b15bb59110 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight_render.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +#include "../nfc_protocol_support_render_common.h" + +void nfc_render_mf_ultralight_info( + const MfUltralightData* data, + NfcProtocolFormatType format_type, + FuriString* str); + +void nfc_render_mf_ultralight_dump(const MfUltralightData* data, FuriString* str); + +void nfc_render_mf_ultralight_pwd_pack(const MfUltralightData* data, FuriString* str); \ No newline at end of file diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c new file mode 100644 index 00000000000..bf6c0842fe5 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c @@ -0,0 +1,786 @@ +/** + * @file nfc_protocol_support.c + * @brief Common implementation of application-level protocol support. + * + * @see nfc_protocol_support_base.h + * @see nfc_protocol_support_common.h + */ +#include "nfc_protocol_support.h" + +#include "nfc/nfc_app_i.h" +#include "nfc/helpers/nfc_supported_cards.h" + +#include "nfc_protocol_support_defs.h" +#include "nfc_protocol_support_gui_common.h" + +/** + * @brief Common scene entry handler. + * + * @param[in,out] instance pointer to the NFC application instance. + */ +typedef void (*NfcProtocolSupportCommonOnEnter)(NfcApp* instance); + +/** + * @brief Common scene custom event handler. + * + * @param[in,out] instance pointer to the NFC application instance. + * @param[in] event custom event to be handled. + * @returns true if the event was handled, false otherwise. + */ +typedef bool (*NfcProtocolSupportCommonOnEvent)(NfcApp* instance, SceneManagerEvent event); + +/** + * @brief Common scene exit handler. + * + * @param[in,out] instance pointer to the NFC application instance. + */ +typedef void (*NfcProtocolSupportCommonOnExit)(NfcApp* instance); + +/** + * @brief Structure containing common scene handler pointers. + */ +typedef struct { + NfcProtocolSupportCommonOnEnter on_enter; /**< Pointer to the on_enter() function. */ + NfcProtocolSupportCommonOnEvent on_event; /**< Pointer to the on_event() function. */ + NfcProtocolSupportCommonOnExit on_exit; /**< Pointer to the on_exit() function. */ +} NfcProtocolSupportCommonSceneBase; + +static const NfcProtocolSupportCommonSceneBase nfc_protocol_support_scenes[]; + +// Interface functions +void nfc_protocol_support_on_enter(NfcProtocolSupportScene scene, void* context) { + furi_assert(scene < NfcProtocolSupportSceneCount); + furi_assert(context); + + NfcApp* instance = context; + nfc_protocol_support_scenes[scene].on_enter(instance); +} + +bool nfc_protocol_support_on_event( + NfcProtocolSupportScene scene, + void* context, + SceneManagerEvent event) { + furi_assert(scene < NfcProtocolSupportSceneCount); + furi_assert(context); + + NfcApp* instance = context; + return nfc_protocol_support_scenes[scene].on_event(instance, event); +} + +void nfc_protocol_support_on_exit(NfcProtocolSupportScene scene, void* context) { + furi_assert(scene < NfcProtocolSupportSceneCount); + furi_assert(context); + + NfcApp* instance = context; + nfc_protocol_support_scenes[scene].on_exit(instance); +} + +static bool nfc_protocol_support_has_feature(NfcProtocol protocol, NfcProtocolFeature feature) { + return nfc_protocol_support[protocol]->features & feature; +} + +// Common scene handlers +// SceneInfo +static void nfc_protocol_support_scene_info_on_enter(NfcApp* instance) { + const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device); + nfc_protocol_support[protocol]->scene_info.on_enter(instance); + + if(nfc_protocol_support_has_feature(protocol, NfcProtocolFeatureMoreInfo)) { + widget_add_button_element( + instance->widget, + GuiButtonTypeRight, + "More", + nfc_protocol_support_common_widget_callback, + instance); + } + + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget); +} + +static bool nfc_protocol_support_scene_info_on_event(NfcApp* instance, SceneManagerEvent event) { + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == GuiButtonTypeRight) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMoreInfo); + consumed = true; + } + } else if(event.type == SceneManagerEventTypeBack) { + // If the card could not be parsed, return to the respective menu + if(!scene_manager_get_scene_state(instance->scene_manager, NfcSceneSupportedCard)) { + const uint32_t scenes[] = {NfcSceneSavedMenu, NfcSceneReadMenu}; + scene_manager_search_and_switch_to_previous_scene_one_of( + instance->scene_manager, scenes, COUNT_OF(scenes)); + consumed = true; + } + } + + return consumed; +} + +static void nfc_protocol_support_scene_info_on_exit(NfcApp* instance) { + widget_reset(instance->widget); +} + +// SceneMoreInfo +static void nfc_protocol_support_scene_more_info_on_enter(NfcApp* instance) { + const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device); + nfc_protocol_support[protocol]->scene_more_info.on_enter(instance); +} + +static bool + nfc_protocol_support_scene_more_info_on_event(NfcApp* instance, SceneManagerEvent event) { + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device); + consumed = nfc_protocol_support[protocol]->scene_more_info.on_event(instance, event.event); + } + + return consumed; +} + +static void nfc_protocol_support_scene_more_info_on_exit(NfcApp* instance) { + text_box_reset(instance->text_box); + furi_string_reset(instance->text_box_store); +} + +// SceneRead +static void nfc_protocol_support_scene_read_on_enter(NfcApp* instance) { + popup_set_header( + instance->popup, "Reading card\nDon't move...", 85, 24, AlignCenter, AlignTop); + popup_set_icon(instance->popup, 12, 23, &A_Loading_24); + + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup); + + const NfcProtocol protocol = + instance->protocols_detected[instance->protocols_detected_selected_idx]; + instance->poller = nfc_poller_alloc(instance->nfc, protocol); + + // Start poller with the appropriate callback + nfc_protocol_support[protocol]->scene_read.on_enter(instance); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup); + + nfc_blink_detect_start(instance); +} + +static bool nfc_protocol_support_scene_read_on_event(NfcApp* instance, SceneManagerEvent event) { + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcCustomEventPollerSuccess) { + nfc_poller_stop(instance->poller); + nfc_poller_free(instance->poller); + notification_message(instance->notifications, &sequence_success); + scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); + dolphin_deed(DolphinDeedNfcReadSuccess); + consumed = true; + } else if(event.event == NfcCustomEventPollerIncomplete) { + nfc_poller_stop(instance->poller); + nfc_poller_free(instance->poller); + bool card_read = nfc_supported_cards_read(instance->nfc_device, instance->nfc); + if(card_read) { + notification_message(instance->notifications, &sequence_success); + scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); + dolphin_deed(DolphinDeedNfcReadSuccess); + consumed = true; + } else { + const NfcProtocol protocol = + instance->protocols_detected[instance->protocols_detected_selected_idx]; + consumed = + nfc_protocol_support[protocol]->scene_read.on_event(instance, event.event); + } + } else if(event.event == NfcCustomEventPollerFailure) { + nfc_poller_stop(instance->poller); + nfc_poller_free(instance->poller); + if(scene_manager_has_previous_scene(instance->scene_manager, NfcSceneDetect)) { + scene_manager_search_and_switch_to_previous_scene( + instance->scene_manager, NfcSceneDetect); + } + consumed = true; + } + } else if(event.type == SceneManagerEventTypeBack) { + nfc_poller_stop(instance->poller); + nfc_poller_free(instance->poller); + static const uint32_t possible_scenes[] = {NfcSceneSelectProtocol, NfcSceneStart}; + scene_manager_search_and_switch_to_previous_scene_one_of( + instance->scene_manager, possible_scenes, COUNT_OF(possible_scenes)); + consumed = true; + } + + return consumed; +} + +static void nfc_protocol_support_scene_read_on_exit(NfcApp* instance) { + popup_reset(instance->popup); + + nfc_blink_stop(instance); +} + +// SceneReadMenu +static void nfc_protocol_support_scene_read_menu_on_enter(NfcApp* instance) { + const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device); + + Submenu* submenu = instance->submenu; + + submenu_add_item( + submenu, + "Save", + SubmenuIndexCommonSave, + nfc_protocol_support_common_submenu_callback, + instance); + + if(nfc_protocol_support_has_feature(protocol, NfcProtocolFeatureEmulateUid)) { + submenu_add_item( + submenu, + "Emulate UID", + SubmenuIndexCommonEmulate, + nfc_protocol_support_common_submenu_callback, + instance); + + } else if(nfc_protocol_support_has_feature(protocol, NfcProtocolFeatureEmulateFull)) { + submenu_add_item( + submenu, + "Emulate", + SubmenuIndexCommonEmulate, + nfc_protocol_support_common_submenu_callback, + instance); + } + + nfc_protocol_support[protocol]->scene_read_menu.on_enter(instance); + + submenu_add_item( + submenu, + "Info", + SubmenuIndexCommonInfo, + nfc_protocol_support_common_submenu_callback, + instance); + + submenu_set_selected_item( + instance->submenu, + scene_manager_get_scene_state(instance->scene_manager, NfcSceneReadMenu)); + + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewMenu); +} + +static bool + nfc_protocol_support_scene_read_menu_on_event(NfcApp* instance, SceneManagerEvent event) { + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + scene_manager_set_scene_state(instance->scene_manager, NfcSceneReadMenu, event.event); + + if(event.event == SubmenuIndexCommonSave) { + scene_manager_next_scene(instance->scene_manager, NfcSceneSaveName); + consumed = true; + } else if(event.event == SubmenuIndexCommonInfo) { + scene_manager_next_scene(instance->scene_manager, NfcSceneInfo); + consumed = true; + } else if(event.event == SubmenuIndexCommonEmulate) { + dolphin_deed(DolphinDeedNfcEmulate); + scene_manager_next_scene(instance->scene_manager, NfcSceneEmulate); + consumed = true; + } else { + const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device); + consumed = + nfc_protocol_support[protocol]->scene_read_menu.on_event(instance, event.event); + } + + } else if(event.type == SceneManagerEventTypeBack) { + scene_manager_set_scene_state(instance->scene_manager, NfcSceneSavedMenu, 0); + } + + return consumed; +} + +// Same for read_menu and saved_menu +static void nfc_protocol_support_scene_read_saved_menu_on_exit(NfcApp* instance) { + submenu_reset(instance->submenu); +} + +// SceneReadSuccess +static void nfc_protocol_support_scene_read_success_on_enter(NfcApp* instance) { + Widget* widget = instance->widget; + + FuriString* temp_str = furi_string_alloc(); + if(nfc_supported_cards_parse(instance->nfc_device, temp_str)) { + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); + } else { + const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device); + nfc_protocol_support[protocol]->scene_read_success.on_enter(instance); + } + + furi_string_free(temp_str); + + widget_add_button_element( + widget, GuiButtonTypeLeft, "Retry", nfc_protocol_support_common_widget_callback, instance); + widget_add_button_element( + widget, GuiButtonTypeRight, "More", nfc_protocol_support_common_widget_callback, instance); + + notification_message_block(instance->notifications, &sequence_set_green_255); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget); +} + +static bool + nfc_protocol_support_scene_read_success_on_event(NfcApp* instance, SceneManagerEvent event) { + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == GuiButtonTypeLeft) { + scene_manager_next_scene(instance->scene_manager, NfcSceneRetryConfirm); + consumed = true; + + } else if(event.event == GuiButtonTypeRight) { + scene_manager_next_scene(instance->scene_manager, NfcSceneReadMenu); + consumed = true; + } + + } else if(event.type == SceneManagerEventTypeBack) { + scene_manager_next_scene(instance->scene_manager, NfcSceneExitConfirm); + consumed = true; + } + + return consumed; +} + +static void nfc_protocol_support_scene_read_success_on_exit(NfcApp* instance) { + notification_message_block(instance->notifications, &sequence_reset_green); + widget_reset(instance->widget); +} + +// SceneSavedMenu +static void nfc_protocol_support_scene_saved_menu_on_enter(NfcApp* instance) { + const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device); + + Submenu* submenu = instance->submenu; + + // Header submenu items + if(nfc_protocol_support_has_feature(protocol, NfcProtocolFeatureEmulateUid)) { + submenu_add_item( + submenu, + "Emulate UID", + SubmenuIndexCommonEmulate, + nfc_protocol_support_common_submenu_callback, + instance); + + } else if(nfc_protocol_support_has_feature(protocol, NfcProtocolFeatureEmulateFull)) { + submenu_add_item( + submenu, + "Emulate", + SubmenuIndexCommonEmulate, + nfc_protocol_support_common_submenu_callback, + instance); + } + + if(nfc_protocol_support_has_feature(protocol, NfcProtocolFeatureEditUid)) { + submenu_add_item( + submenu, + "Edit UID", + SubmenuIndexCommonEdit, + nfc_protocol_support_common_submenu_callback, + instance); + } + + // Protocol-dependent menu items + nfc_protocol_support[protocol]->scene_saved_menu.on_enter(instance); + + // Trailer submenu items + submenu_add_item( + submenu, + "Info", + SubmenuIndexCommonInfo, + nfc_protocol_support_common_submenu_callback, + instance); + submenu_add_item( + submenu, + "Rename", + SubmenuIndexCommonRename, + nfc_protocol_support_common_submenu_callback, + instance); + submenu_add_item( + submenu, + "Delete", + SubmenuIndexCommonDelete, + nfc_protocol_support_common_submenu_callback, + instance); + + if(nfc_has_shadow_file(instance)) { + submenu_add_item( + submenu, + "Restore Data Changes", + SubmenuIndexCommonRestore, + nfc_protocol_support_common_submenu_callback, + instance); + } + + submenu_set_selected_item( + instance->submenu, + scene_manager_get_scene_state(instance->scene_manager, NfcSceneSavedMenu)); + + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewMenu); +} + +static bool + nfc_protocol_support_scene_saved_menu_on_event(NfcApp* instance, SceneManagerEvent event) { + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + scene_manager_set_scene_state(instance->scene_manager, NfcSceneSavedMenu, event.event); + + if(event.event == SubmenuIndexCommonRestore) { + scene_manager_next_scene(instance->scene_manager, NfcSceneRestoreOriginalConfirm); + consumed = true; + } else if(event.event == SubmenuIndexCommonInfo) { + scene_manager_next_scene(instance->scene_manager, NfcSceneSupportedCard); + consumed = true; + } else if(event.event == SubmenuIndexCommonRename) { + scene_manager_next_scene(instance->scene_manager, NfcSceneSaveName); + consumed = true; + } else if(event.event == SubmenuIndexCommonDelete) { + scene_manager_next_scene(instance->scene_manager, NfcSceneDelete); + consumed = true; + } else if(event.event == SubmenuIndexCommonEmulate) { + const bool is_added = + scene_manager_has_previous_scene(instance->scene_manager, NfcSceneSetType); + dolphin_deed(is_added ? DolphinDeedNfcAddEmulate : DolphinDeedNfcEmulate); + scene_manager_next_scene(instance->scene_manager, NfcSceneEmulate); + consumed = true; + } else if(event.event == SubmenuIndexCommonEdit) { + scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid); + consumed = true; + } else { + const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device); + consumed = + nfc_protocol_support[protocol]->scene_saved_menu.on_event(instance, event.event); + } + + } else if(event.type == SceneManagerEventTypeBack) { + scene_manager_set_scene_state(instance->scene_manager, NfcSceneSavedMenu, 0); + } + + return consumed; +} + +// SceneSaveName + +static void nfc_protocol_support_scene_save_name_on_enter(NfcApp* instance) { + FuriString* folder_path = furi_string_alloc(); + TextInput* text_input = instance->text_input; + + bool name_is_empty = furi_string_empty(instance->file_name); + if(name_is_empty) { + furi_string_set(instance->file_path, NFC_APP_FOLDER); + name_generator_make_auto( + instance->text_store, NFC_TEXT_STORE_SIZE, NFC_APP_FILENAME_PREFIX); + furi_string_set(folder_path, NFC_APP_FOLDER); + } else { + nfc_text_store_set(instance, "%s", furi_string_get_cstr(instance->file_name)); + path_extract_dirname(furi_string_get_cstr(instance->file_path), folder_path); + } + + text_input_set_header_text(text_input, "Name the card"); + text_input_set_result_callback( + text_input, + nfc_protocol_support_common_text_input_done_callback, + instance, + instance->text_store, + NFC_NAME_SIZE, + name_is_empty); + + ValidatorIsFile* validator_is_file = validator_is_file_alloc_init( + furi_string_get_cstr(folder_path), + NFC_APP_EXTENSION, + furi_string_get_cstr(instance->file_name)); + text_input_set_validator(text_input, validator_is_file_callback, validator_is_file); + + furi_string_free(folder_path); + + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewTextInput); +} + +static bool + nfc_protocol_support_scene_save_name_on_event(NfcApp* instance, SceneManagerEvent event) { + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcCustomEventTextInputDone) { + if(!furi_string_empty(instance->file_name)) { + nfc_delete(instance); + } + furi_string_set(instance->file_name, instance->text_store); + + if(nfc_save(instance)) { + scene_manager_next_scene(instance->scene_manager, NfcSceneSaveSuccess); + dolphin_deed( + scene_manager_has_previous_scene(instance->scene_manager, NfcSceneSetType) ? + DolphinDeedNfcAddSave : + DolphinDeedNfcSave); + const NfcProtocol protocol = + instance->protocols_detected[instance->protocols_detected_selected_idx]; + consumed = nfc_protocol_support[protocol]->scene_save_name.on_event( + instance, event.event); + } else { + consumed = scene_manager_search_and_switch_to_previous_scene( + instance->scene_manager, NfcSceneStart); + } + } + } + + return consumed; +} + +static void nfc_protocol_support_scene_save_name_on_exit(NfcApp* instance) { + void* validator_context = text_input_get_validator_callback_context(instance->text_input); + text_input_set_validator(instance->text_input, NULL, NULL); + validator_is_file_free(validator_context); + + text_input_reset(instance->text_input); +} + +// SceneEmulate +/** + * @brief Current view displayed on the emulation scene. + * + * The emulation scehe has two states: the default one showing information about + * the card being emulated, and the logs which show the raw data received from the reader. + * + * The user has the ability to switch betweeen these two scenes, however the prompt to switch is + * only shown after some information had appered in the log view. + */ +enum { + NfcSceneEmulateStateWidget, /**< Widget view is displayed. */ + NfcSceneEmulateStateTextBox, /**< TextBox view is displayed. */ +}; + +static void nfc_protocol_support_scene_emulate_on_enter(NfcApp* instance) { + Widget* widget = instance->widget; + TextBox* text_box = instance->text_box; + + FuriString* temp_str = furi_string_alloc(); + const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device); + + widget_add_icon_element(widget, 0, 3, &I_NFC_dolphin_emulation_47x61); + + if(nfc_protocol_support_has_feature(protocol, NfcProtocolFeatureEmulateUid)) { + widget_add_string_element( + widget, 90, 13, AlignCenter, AlignTop, FontPrimary, "Emulating UID"); + + size_t uid_len; + const uint8_t* uid = nfc_device_get_uid(instance->nfc_device, &uid_len); + + for(size_t i = 0; i < uid_len; ++i) { + furi_string_cat_printf(temp_str, "%02X ", uid[i]); + } + + furi_string_trim(temp_str); + + } else { + widget_add_string_element(widget, 90, 13, AlignCenter, AlignTop, FontPrimary, "Emulating"); + furi_string_set( + temp_str, nfc_device_get_name(instance->nfc_device, NfcDeviceNameTypeFull)); + } + + widget_add_text_box_element( + widget, 56, 28, 71, 25, AlignCenter, AlignTop, furi_string_get_cstr(temp_str), false); + + furi_string_free(temp_str); + + text_box_set_font(text_box, TextBoxFontHex); + text_box_set_focus(text_box, TextBoxFocusEnd); + furi_string_reset(instance->text_box_store); + + // instance->listener is allocated in the respective on_enter() handler + nfc_protocol_support[protocol]->scene_emulate.on_enter(instance); + + scene_manager_set_scene_state( + instance->scene_manager, NfcSceneEmulate, NfcSceneEmulateStateWidget); + + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget); + nfc_blink_emulate_start(instance); +} + +static bool + nfc_protocol_support_scene_emulate_on_event(NfcApp* instance, SceneManagerEvent event) { + bool consumed = false; + + const uint32_t state = scene_manager_get_scene_state(instance->scene_manager, NfcSceneEmulate); + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcCustomEventListenerUpdate) { + // Add data button to widget if data is received for the first time + if(furi_string_size(instance->text_box_store)) { + widget_add_button_element( + instance->widget, + GuiButtonTypeCenter, + "Log", + nfc_protocol_support_common_widget_callback, + instance); + } + // Update TextBox data + text_box_set_text(instance->text_box, furi_string_get_cstr(instance->text_box_store)); + consumed = true; + } else if(event.event == GuiButtonTypeCenter) { + if(state == NfcSceneEmulateStateWidget) { + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewTextBox); + scene_manager_set_scene_state( + instance->scene_manager, NfcSceneEmulate, NfcSceneEmulateStateTextBox); + consumed = true; + } + } + } else if(event.type == SceneManagerEventTypeBack) { + if(state == NfcSceneEmulateStateTextBox) { + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget); + scene_manager_set_scene_state( + instance->scene_manager, NfcSceneEmulate, NfcSceneEmulateStateWidget); + consumed = true; + } + } + + return consumed; +} + +static void nfc_protocol_support_scene_emulate_stop_listener(NfcApp* instance) { + nfc_listener_stop(instance->listener); + + const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device); + + if(protocol == nfc_listener_get_protocol(instance->listener)) { + const NfcDeviceData* data = nfc_listener_get_data(instance->listener, protocol); + + if(!nfc_device_is_equal_data(instance->nfc_device, protocol, data)) { + nfc_device_set_data(instance->nfc_device, protocol, data); + nfc_save_shadow_file(instance); + } + } + + nfc_listener_free(instance->listener); +} + +static void nfc_protocol_support_scene_emulate_on_exit(NfcApp* instance) { + nfc_protocol_support_scene_emulate_stop_listener(instance); + + // Clear view + widget_reset(instance->widget); + text_box_reset(instance->text_box); + furi_string_reset(instance->text_box_store); + + nfc_blink_stop(instance); +} + +static void nfc_protocol_support_scene_rpc_on_enter(NfcApp* instance) { + UNUSED(instance); +} + +static void nfc_protocol_support_scene_rpc_setup_ui_and_emulate(NfcApp* instance) { + nfc_text_store_set(instance, "emulating\n%s", furi_string_get_cstr(instance->file_name)); + + popup_set_header(instance->popup, "NFC", 89, 42, AlignCenter, AlignBottom); + popup_set_text(instance->popup, instance->text_store, 89, 44, AlignCenter, AlignTop); + popup_set_icon(instance->popup, 0, 12, &I_RFIDDolphinSend_97x61); + + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup); + + notification_message(instance->notifications, &sequence_display_backlight_on); + nfc_blink_emulate_start(instance); + + const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device); + nfc_protocol_support[protocol]->scene_emulate.on_enter(instance); + + instance->rpc_state = NfcRpcStateEmulating; +} + +static bool nfc_protocol_support_scene_rpc_on_event(NfcApp* instance, SceneManagerEvent event) { + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcCustomEventRpcLoad && instance->rpc_state == NfcRpcStateIdle) { + furi_string_set(instance->file_path, rpc_system_app_get_data(instance->rpc_ctx)); + const bool load_success = nfc_load_file(instance, instance->file_path, false); + if(load_success) { + nfc_protocol_support_scene_rpc_setup_ui_and_emulate(instance); + } + rpc_system_app_confirm(instance->rpc_ctx, RpcAppEventLoadFile, load_success); + } else if(event.event == NfcCustomEventRpcExit) { + rpc_system_app_confirm(instance->rpc_ctx, RpcAppEventAppExit, true); + scene_manager_stop(instance->scene_manager); + view_dispatcher_stop(instance->view_dispatcher); + } else if(event.event == NfcCustomEventRpcSessionClose) { + scene_manager_stop(instance->scene_manager); + view_dispatcher_stop(instance->view_dispatcher); + } + consumed = true; + } + + return consumed; +} + +static void nfc_protocol_support_scene_rpc_on_exit(NfcApp* instance) { + if(instance->rpc_state == NfcRpcStateEmulating) { + nfc_protocol_support_scene_emulate_stop_listener(instance); + } + + popup_reset(instance->popup); + text_box_reset(instance->text_box); + furi_string_reset(instance->text_box_store); + + nfc_blink_stop(instance); +} + +static const NfcProtocolSupportCommonSceneBase + nfc_protocol_support_scenes[NfcProtocolSupportSceneCount] = { + [NfcProtocolSupportSceneInfo] = + { + .on_enter = nfc_protocol_support_scene_info_on_enter, + .on_event = nfc_protocol_support_scene_info_on_event, + .on_exit = nfc_protocol_support_scene_info_on_exit, + }, + [NfcProtocolSupportSceneMoreInfo] = + { + .on_enter = nfc_protocol_support_scene_more_info_on_enter, + .on_event = nfc_protocol_support_scene_more_info_on_event, + .on_exit = nfc_protocol_support_scene_more_info_on_exit, + }, + [NfcProtocolSupportSceneRead] = + { + .on_enter = nfc_protocol_support_scene_read_on_enter, + .on_event = nfc_protocol_support_scene_read_on_event, + .on_exit = nfc_protocol_support_scene_read_on_exit, + }, + [NfcProtocolSupportSceneReadMenu] = + { + .on_enter = nfc_protocol_support_scene_read_menu_on_enter, + .on_event = nfc_protocol_support_scene_read_menu_on_event, + .on_exit = nfc_protocol_support_scene_read_saved_menu_on_exit, + }, + [NfcProtocolSupportSceneReadSuccess] = + { + .on_enter = nfc_protocol_support_scene_read_success_on_enter, + .on_event = nfc_protocol_support_scene_read_success_on_event, + .on_exit = nfc_protocol_support_scene_read_success_on_exit, + }, + [NfcProtocolSupportSceneSavedMenu] = + { + .on_enter = nfc_protocol_support_scene_saved_menu_on_enter, + .on_event = nfc_protocol_support_scene_saved_menu_on_event, + .on_exit = nfc_protocol_support_scene_read_saved_menu_on_exit, + }, + [NfcProtocolSupportSceneSaveName] = + { + .on_enter = nfc_protocol_support_scene_save_name_on_enter, + .on_event = nfc_protocol_support_scene_save_name_on_event, + .on_exit = nfc_protocol_support_scene_save_name_on_exit, + }, + [NfcProtocolSupportSceneEmulate] = + { + .on_enter = nfc_protocol_support_scene_emulate_on_enter, + .on_event = nfc_protocol_support_scene_emulate_on_event, + .on_exit = nfc_protocol_support_scene_emulate_on_exit, + }, + [NfcProtocolSupportSceneRpc] = + { + .on_enter = nfc_protocol_support_scene_rpc_on_enter, + .on_event = nfc_protocol_support_scene_rpc_on_event, + .on_exit = nfc_protocol_support_scene_rpc_on_exit, + }, +}; diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.h b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.h new file mode 100644 index 00000000000..b6bfde45c04 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.h @@ -0,0 +1,113 @@ +/** + * @file nfc_protocol_support.h + * @brief Interface for application-level protocol support. + * + * NFC protocol support helper abstracts common scenes with a single interface + * and lets each protocol decide on concrete implementation. + * + * # Integrating a new protocol into the application + * + * Most of the scenes in the NFC application work through abstract APIs, so they do not need + * protocol-specific versions of themselves. However, when such a situation + * occurs, the protocol support helper provides another level of abstraction to hide + * the protocol-specific details and isolate them to separate modules. + * + * @see nfc_protocol.h for more information on adding library protocols. + * + * The steps for adding support for a library protocol are described below. + * + * ## 1. Create the files + * + * ### 1.1 Recommended file structure + * + * The recommended file structure for a protocol support is as follows: + * + * ```text + * protocol_support + * | + * +- protocol_name + * | + * +- protocol_name.h + * | + * +- protocol_name.c + * | + * +- protocol_name_render.h + * | + * +- protocol_name_render.c + * | + * ``` + * ### 1.2 File structure explanation + * + * | Filename | Explanation | + * |:-----------------------|:------------| + * | protocol_name.h | Interface structure declaration used in `nfc_protocol_support_defs.c`. | + * | protocol_name.c | Protocol-specific scene implemenatations and definitions. | + * | protocol_name_render.h | Protocol-specific rendering (formatting) functions. Used for converting protocol data into textual descriptions. | + * | protocol_name_render.c | Implementations for functions declared in `protocol_name_render.h`.| + * + * ## 2. Implement the code + * + * ### 2.1 Features + * + * Decide what features the protocol will be providing. The features can be combined using bitwise OR (`"|"`). + * This choice influences which scenes will have to be implemented in step 2.2. + * + * @see NfcProtocolFeature for the enumeration of possible features to implement. + * + * ### 2.2 Scenes + * + * If a particular scene is not implemented, its empty placeholder from nfc_protocol_support_gui_common.h must be used instead. + * + * @see nfc_protocol_support_common.h for the enumeration of all scenes that can be implemented. + * @see nfc_protocol_support_base.h for the scene implementation details. + * + * ### 2.3. Registering the protocol support + * + * After completing the protocol support, it must be registered within the application in order for it to be usable. + * + * In nfc_protocol_support_defs.c, include the `protocol_name.h` file and add a new entry in the `nfc_protocol_support[]` + * array under the appropriate index. + * + * ## Done! + * + * @note It will not always be possible to abstract all of the protocol's functionality using the protocol support helper. + * In such cases, creating separate protocol-specific scenes is okay (as an example, note the `nfc/scenes/nfc_scene_mf_classic_*` scenes which didn't fit this paradigm). + */ +#pragma once + +#include + +#include "nfc_protocol_support_common.h" + +/** + * @brief Abstract interface for on_enter() scene handler. + * + * Is to be called whenever a scene is entered to. + * + * @param[in] scene identifier of the scene associated with the handler. + * @param[in,out] context pointer to a user-specified context (will be passed to concrete handler). + */ +void nfc_protocol_support_on_enter(NfcProtocolSupportScene scene, void* context); + +/** + * @brief Abstract interface for on_event() scene handler. + * + * @param[in] scene identifier of the scene associated with the handler. + * @param[in,out] context pointer to a user-specified context (will be passed to concrete handler). + * @param[in] event SceneManager event to be handled by the scene. + * @returns true if the event was consumed, false otherwise. + */ +bool nfc_protocol_support_on_event( + NfcProtocolSupportScene scene, + void* context, + SceneManagerEvent event); + +/** + * @brief Abstract interface for on_exit() scene handler. + * + * Is to be called whenever a scene is exited from. + * + * @param[in] scene identifier of the scene associated with the handler. + * @param[in,out] context pointer to a user-specified context (will be passed to concrete handler). + */ +void nfc_protocol_support_on_exit(NfcProtocolSupportScene scene, void* context); diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_base.h b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_base.h new file mode 100644 index 00000000000..69a6d34d291 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_base.h @@ -0,0 +1,116 @@ +/** + * @file nfc_protocol_support_base.h + * @brief Abstract interface for application-level protocol support. + */ +#pragma once + +#include + +#include "../../nfc_app.h" + +/** + * @brief Scene entry handler. + * + * @param[in,out] instance pointer to the NFC application instance. + */ +typedef void (*NfcProtocolSupportOnEnter)(NfcApp* instance); + +/** + * @brief Scene event handler. + * + * @param[in,out] instance pointer to the NFC application instance. + * @param[in] event custom event that has occurred. + * @returns true if the event was handled, false otherwise. + */ +typedef bool (*NfcProtocolSupportOnEvent)(NfcApp* instance, uint32_t event); + +/** + * @brief Abstract scene interface. + * + * on_exit() handler is not implemented due to being redundant. + */ +typedef struct { + NfcProtocolSupportOnEnter on_enter; /**< Pointer to the on_enter() function. */ + NfcProtocolSupportOnEvent on_event; /**< Pointer to the on_event() function. */ +} NfcProtocolSupportSceneBase; + +/** + * @brief Abstract protocol support interface. + */ +typedef struct { + const uint32_t features; /**< Feature bitmask supported by the protocol. */ + + /** + * @brief Handlers for protocol-specific info scene. + * + * This scene displays general information about a saved or recently read card. + * It may include a button that will lead to more information being shown. + */ + NfcProtocolSupportSceneBase scene_info; + + /** + * @brief Handlers for protocol-specific extended info scene. + * + * This scene shows more information about a saved or + * recently read card, such as memory dumps. + * + * It may include (a) button(s) and/or menu(s) that will lead to + * protocol-specific scenes not covered in this helper. + */ + NfcProtocolSupportSceneBase scene_more_info; + + /** + * @brief Handlers for protocol-specific read scene. + * + * This scene is activated when a read operation is in progress. + * It is responsible for creating a poller and for handling its events. + */ + NfcProtocolSupportSceneBase scene_read; + + /** + * @brief Handlers for protocol-specific read menu scene. + * + * This scene presents the user with options available for the + * recenly read card. Such options may include: + * * Saving + * * Getting information + * * Emulating etc. + */ + NfcProtocolSupportSceneBase scene_read_menu; + + /** + * @brief Handlers for protocol-specific read success scene. + * + * This scene is activated after a successful read operation. + * It is responsible for displaying a very short summary about + * the card that was just read. + */ + NfcProtocolSupportSceneBase scene_read_success; + + /** + * @brief Handlers for protocol-specific saved file menu scene. + * + * This scene presents the user with options available for a + * card loaded from file. Such options may include: + * * Renaming + * * Deleting + * * Getting information + * * Emulating etc. + */ + NfcProtocolSupportSceneBase scene_saved_menu; + + /** + * @brief Handlers for protocol-specific name entry scene. + * + * This scene is used to enter a file name when saving or renaming a file. + */ + NfcProtocolSupportSceneBase scene_save_name; + + /** + * @brief Handlers for protocol-specific emulate scene. + * + * This scene is activated when an emulation operation is in progress. + * It is responsible for creating a listener and for handling its events. + */ + NfcProtocolSupportSceneBase scene_emulate; +} NfcProtocolSupportBase; diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_common.h b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_common.h new file mode 100644 index 00000000000..6e3214106ad --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_common.h @@ -0,0 +1,36 @@ +/** + * @file nfc_protocol_support_common.h + * @brief Common application-level protocol support definitions. + */ +#pragma once + +/** + * @brief Enumeration of protocol features. + */ +typedef enum { + NfcProtocolFeatureNone = 0, /**< No features are supported. */ + NfcProtocolFeatureEmulateUid = 1UL << 0, /**< Partial emulation is supported. */ + NfcProtocolFeatureEmulateFull = 1UL << 1, /**< Complete emulation is supported. */ + NfcProtocolFeatureEditUid = 1UL << 2, /**< UID editing is supported. */ + NfcProtocolFeatureMoreInfo = 1UL << 3, /**< More information is provided. */ +} NfcProtocolFeature; + +/** + * @brief Enumeration of protocol-aware scenes. + * + * These are the scenes that are common to all protocols, but require + * a protocol-specific implementation. + */ +typedef enum { + NfcProtocolSupportSceneInfo, /**< Display general card information. */ + NfcProtocolSupportSceneMoreInfo, /**< Display more card information. */ + NfcProtocolSupportSceneRead, /**< Shown when reading a card. */ + NfcProtocolSupportSceneReadMenu, /**< Menu with options available for the recently read card. */ + NfcProtocolSupportSceneReadSuccess, /**< Shown after having successfully read a card. */ + NfcProtocolSupportSceneSavedMenu, /**< Menu for the card that was loaded from file. */ + NfcProtocolSupportSceneSaveName, /**< Shown when saving or renaming a file. */ + NfcProtocolSupportSceneEmulate, /**< Shown when emulating a card. */ + NfcProtocolSupportSceneRpc, /**< Shown in remote-controlled (RPC) mode. */ + + NfcProtocolSupportSceneCount, /**< Special value equal to total scene count. Internal use. */ +} NfcProtocolSupportScene; diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_defs.c b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_defs.c new file mode 100644 index 00000000000..215ffc4553a --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_defs.c @@ -0,0 +1,45 @@ +/** + * @file nfc_protocol_support_defs.c + * @brief Application-level protocol support definitions. + * + * This file is to be modified whenever support for + * a new protocol is to be added. + */ +#include "nfc_protocol_support_base.h" + +#include + +#include "iso14443_3a/iso14443_3a.h" +#include "iso14443_3b/iso14443_3b.h" +#include "iso14443_4a/iso14443_4a.h" +#include "iso14443_4b/iso14443_4b.h" +#include "iso15693_3/iso15693_3.h" +#include "felica/felica.h" +#include "mf_ultralight/mf_ultralight.h" +#include "mf_classic/mf_classic.h" +#include "mf_desfire/mf_desfire.h" +#include "slix/slix.h" +#include "st25tb/st25tb.h" + +/** + * @brief Array of pointers to concrete protocol support implementations. + * + * When adding support for a new protocol, add it to the end of this array + * under its respective index. + * + * @see nfc_protocol.h + */ +const NfcProtocolSupportBase* nfc_protocol_support[NfcProtocolNum] = { + [NfcProtocolIso14443_3a] = &nfc_protocol_support_iso14443_3a, + [NfcProtocolIso14443_3b] = &nfc_protocol_support_iso14443_3b, + [NfcProtocolIso14443_4a] = &nfc_protocol_support_iso14443_4a, + [NfcProtocolIso14443_4b] = &nfc_protocol_support_iso14443_4b, + [NfcProtocolIso15693_3] = &nfc_protocol_support_iso15693_3, + [NfcProtocolFelica] = &nfc_protocol_support_felica, + [NfcProtocolMfUltralight] = &nfc_protocol_support_mf_ultralight, + [NfcProtocolMfClassic] = &nfc_protocol_support_mf_classic, + [NfcProtocolMfDesfire] = &nfc_protocol_support_mf_desfire, + [NfcProtocolSlix] = &nfc_protocol_support_slix, + [NfcProtocolSt25tb] = &nfc_protocol_support_st25tb, + /* Add new protocol support implementations here */ +}; diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_defs.h b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_defs.h new file mode 100644 index 00000000000..7a9d5b6374d --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_defs.h @@ -0,0 +1,12 @@ +/** + * @file nfc_protocol_support_defs.h + * @brief Application-level protocol support declarations. + */ +#pragma once + +#include "nfc_protocol_support_base.h" + +/** + * @brief Declaraion of array of pointers to protocol support implementations. + */ +extern const NfcProtocolSupportBase* nfc_protocol_support[]; diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_gui_common.c b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_gui_common.c new file mode 100644 index 00000000000..f3a85512555 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_gui_common.c @@ -0,0 +1,42 @@ +#include "nfc_protocol_support_gui_common.h" + +#include "nfc/nfc_app_i.h" + +void nfc_protocol_support_common_submenu_callback(void* context, uint32_t index) { + furi_assert(context); + NfcApp* instance = context; + view_dispatcher_send_custom_event(instance->view_dispatcher, index); +} + +void nfc_protocol_support_common_widget_callback( + GuiButtonType result, + InputType type, + void* context) { + furi_assert(context); + NfcApp* instance = context; + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(instance->view_dispatcher, result); + } +} + +void nfc_protocol_support_common_byte_input_done_callback(void* context) { + furi_assert(context); + NfcApp* instance = context; + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventByteInputDone); +} + +void nfc_protocol_support_common_text_input_done_callback(void* context) { + NfcApp* nfc = context; + + view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventTextInputDone); +} + +void nfc_protocol_support_common_on_enter_empty(NfcApp* instance) { + UNUSED(instance); +} + +bool nfc_protocol_support_common_on_event_empty(NfcApp* instance, uint32_t event) { + UNUSED(instance); + UNUSED(event); + return true; +} diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_gui_common.h b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_gui_common.h new file mode 100644 index 00000000000..40ba40c8ec1 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_gui_common.h @@ -0,0 +1,85 @@ +/** + * @file nfc_protocol_support_gui_common.h + * @brief Common GUI functions and definitions. + */ +#pragma once + +#include + +#include "nfc/nfc_app.h" + +/** + * @brief Common submenu indices. + */ +enum { + SubmenuIndexCommonSave, /**< Save menu option. */ + SubmenuIndexCommonEmulate, /**< Emulate menu option. */ + SubmenuIndexCommonEdit, /**< Edit menu option. */ + SubmenuIndexCommonInfo, /**< Info menu option. */ + SubmenuIndexCommonRename, /**< Rename menu option. */ + SubmenuIndexCommonDelete, /**< Delete menu option. */ + SubmenuIndexCommonRestore, /**< Restore menu option. */ + SubmenuIndexCommonMax, /**< Special value, internal use. */ +}; + +/** + * @brief Common submenu callback. + * + * Called each time the user presses on a selected submenu item. + * + * @param[in,out] context pointer to a user-defined context object. + * @param[in] index index of the item that was activated. + */ +void nfc_protocol_support_common_submenu_callback(void* context, uint32_t index); + +/** + * @brief Common widget callback. + * + * Called each time the user presses on a selected widget element. + * + * @param[in] result identifier of the activated button. + * @param[in] type type of press action. + * @param[in,out] context pointer to a user-defined context object. + */ +void nfc_protocol_support_common_widget_callback( + GuiButtonType result, + InputType type, + void* context); + +/** + * @brief Common byte input callback. + * + * Called each time the user accepts the byte input. + * + * @param[in,out] context pointer to a user-defined context object. + */ +void nfc_protocol_support_common_byte_input_done_callback(void* context); + +/** + * @brief Common text input callback. + * + * Called each time the user accepts the text input. + * + * @param[in,out] context pointer to a user-defined context object. + */ +void nfc_protocol_support_common_text_input_done_callback(void* context); + +/** + * @brief Empty on_enter() handler. + * + * Does nothing. + * + * @param[in] instance pointer to the NFC application instance. + */ +void nfc_protocol_support_common_on_enter_empty(NfcApp* instance); + +/** + * @brief Empty on_event() handler. + * + * Does nothing and returns true. + * + * @param[in] instance pointer to the NFC application instance. + * @param[in] event custom event type that has occurred. + * @returns always true. + */ +bool nfc_protocol_support_common_on_event_empty(NfcApp* instance, uint32_t event); diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_render_common.h b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_render_common.h new file mode 100644 index 00000000000..a2e82ea1535 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_render_common.h @@ -0,0 +1,13 @@ +/** + * @file nfc_protocol_support_render_common.h + * @brief Common formatting-related defines. + */ +#pragma once + +/** + * @brief Displayed information verbosity level. + */ +typedef enum { + NfcProtocolFormatTypeShort, /**< Short format, terse info. */ + NfcProtocolFormatTypeFull, /**< Full format, verbose info. */ +} NfcProtocolFormatType; diff --git a/applications/main/nfc/helpers/protocol_support/slix/slix.c b/applications/main/nfc/helpers/protocol_support/slix/slix.c new file mode 100644 index 00000000000..ad858a75fce --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/slix/slix.c @@ -0,0 +1,141 @@ +#include "slix.h" +#include "slix_render.h" + +#include +#include + +#include "nfc/nfc_app_i.h" + +#include "../nfc_protocol_support_common.h" +#include "../nfc_protocol_support_gui_common.h" + +static void nfc_scene_info_on_enter_slix(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const SlixData* data = nfc_device_get_data(device, NfcProtocolSlix); + + FuriString* temp_str = furi_string_alloc(); + furi_string_cat_printf( + temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); + nfc_render_slix_info(data, NfcProtocolFormatTypeFull, temp_str); + + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 64, furi_string_get_cstr(temp_str)); + + furi_string_free(temp_str); +} + +static NfcCommand nfc_scene_read_poller_callback_slix(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolSlix); + + NfcApp* instance = context; + const SlixPollerEvent* slix_event = event.event_data; + + if(slix_event->type == SlixPollerEventTypeReady) { + nfc_device_set_data( + instance->nfc_device, NfcProtocolSlix, nfc_poller_get_data(instance->poller)); + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess); + return NfcCommandStop; + } + + return NfcCommandContinue; +} + +static void nfc_scene_read_on_enter_slix(NfcApp* instance) { + nfc_poller_start(instance->poller, nfc_scene_read_poller_callback_slix, instance); +} + +static void nfc_scene_read_success_on_enter_slix(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const SlixData* data = nfc_device_get_data(device, NfcProtocolSlix); + + FuriString* temp_str = furi_string_alloc(); + furi_string_cat_printf( + temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); + nfc_render_slix_info(data, NfcProtocolFormatTypeShort, temp_str); + + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); + + furi_string_free(temp_str); +} + +static NfcCommand nfc_scene_emulate_listener_callback_slix(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.protocol == NfcProtocolSlix); + furi_assert(event.event_data); + + NfcApp* nfc = context; + SlixListenerEvent* slix_event = event.event_data; + + if(slix_event->type == SlixListenerEventTypeCustomCommand) { + if(furi_string_size(nfc->text_box_store) < NFC_LOG_SIZE_MAX) { + furi_string_cat_printf(nfc->text_box_store, "R:"); + for(size_t i = 0; i < bit_buffer_get_size_bytes(slix_event->data->buffer); i++) { + furi_string_cat_printf( + nfc->text_box_store, + " %02X", + bit_buffer_get_byte(slix_event->data->buffer, i)); + } + furi_string_push_back(nfc->text_box_store, '\n'); + view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventListenerUpdate); + } + } + + return NfcCommandContinue; +} + +static void nfc_scene_emulate_on_enter_slix(NfcApp* instance) { + const SlixData* data = nfc_device_get_data(instance->nfc_device, NfcProtocolSlix); + + instance->listener = nfc_listener_alloc(instance->nfc, NfcProtocolSlix, data); + nfc_listener_start(instance->listener, nfc_scene_emulate_listener_callback_slix, instance); +} + +static bool nfc_scene_saved_menu_on_event_slix(NfcApp* instance, uint32_t event) { + if(event == SubmenuIndexCommonEdit) { + scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid); + return true; + } + + return false; +} + +const NfcProtocolSupportBase nfc_protocol_support_slix = { + .features = NfcProtocolFeatureEmulateFull, + + .scene_info = + { + .on_enter = nfc_scene_info_on_enter_slix, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read = + { + .on_enter = nfc_scene_read_on_enter_slix, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read_menu = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read_success = + { + .on_enter = nfc_scene_read_success_on_enter_slix, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_saved_menu = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_scene_saved_menu_on_event_slix, + }, + .scene_save_name = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_emulate = + { + .on_enter = nfc_scene_emulate_on_enter_slix, + .on_event = nfc_protocol_support_common_on_event_empty, + }, +}; diff --git a/applications/main/nfc/helpers/protocol_support/slix/slix.h b/applications/main/nfc/helpers/protocol_support/slix/slix.h new file mode 100644 index 00000000000..9c7504ebafa --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/slix/slix.h @@ -0,0 +1,5 @@ +#pragma once + +#include "../nfc_protocol_support_base.h" + +extern const NfcProtocolSupportBase nfc_protocol_support_slix; diff --git a/applications/main/nfc/helpers/protocol_support/slix/slix_render.c b/applications/main/nfc/helpers/protocol_support/slix/slix_render.c new file mode 100644 index 00000000000..80f953db973 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/slix/slix_render.c @@ -0,0 +1,74 @@ +#include "slix_render.h" + +#include "../iso15693_3/iso15693_3_render.h" + +void nfc_render_slix_info(const SlixData* data, NfcProtocolFormatType format_type, FuriString* str) { + nfc_render_iso15693_3_brief(slix_get_base_data(data), str); + + if(format_type != NfcProtocolFormatTypeFull) return; + const SlixType slix_type = slix_get_type(data); + + furi_string_cat(str, "\n\e#Passwords\n"); + + static const char* slix_password_names[] = { + "Read", + "Write", + "Privacy", + "Destroy", + "EAS/AFI", + }; + + for(uint32_t i = 0; i < SlixPasswordTypeCount; ++i) { + if(slix_type_supports_password(slix_type, i)) { + furi_string_cat_printf( + str, "%s : %08lX\n", slix_password_names[i], data->passwords[i]); + } + } + + furi_string_cat(str, "\e#Lock bits\n"); + + if(slix_type_has_features(slix_type, SLIX_TYPE_FEATURE_EAS)) { + furi_string_cat_printf( + str, "EAS: %s locked\n", data->system_info.lock_bits.eas ? "" : "not"); + } + + if(slix_type_has_features(slix_type, SLIX_TYPE_FEATURE_PROTECTION)) { + furi_string_cat_printf( + str, "PPL: %s locked\n", data->system_info.lock_bits.ppl ? "" : "not"); + + const SlixProtection protection = data->system_info.protection; + + furi_string_cat(str, "\e#Page protection\n"); + furi_string_cat_printf(str, "Pointer: H >= %02X\n", protection.pointer); + + const char* rh = (protection.condition & SLIX_PP_CONDITION_RH) ? "" : "un"; + const char* rl = (protection.condition & SLIX_PP_CONDITION_RL) ? "" : "un"; + + const char* wh = (protection.condition & SLIX_PP_CONDITION_WH) ? "" : "un"; + const char* wl = (protection.condition & SLIX_PP_CONDITION_WL) ? "" : "un"; + + furi_string_cat_printf(str, "R: H %sprotec. L %sprotec.\n", rh, rl); + furi_string_cat_printf(str, "W: H %sprotec. L %sprotec.\n", wh, wl); + } + + if(slix_type_has_features(slix_type, SLIX_TYPE_FEATURE_PRIVACY)) { + furi_string_cat(str, "\e#Privacy\n"); + furi_string_cat_printf(str, "Privacy mode: %sabled\n", data->privacy ? "en" : "dis"); + } + + if(slix_type_has_features(slix_type, SLIX_TYPE_FEATURE_SIGNATURE)) { + furi_string_cat(str, "\e#Signature\n"); + for(uint32_t i = 0; i < 4; ++i) { + furi_string_cat_printf(str, "%02X ", data->signature[i]); + } + + furi_string_cat(str, "[ ... ]"); + + for(uint32_t i = 0; i < 3; ++i) { + furi_string_cat_printf(str, " %02X", data->signature[sizeof(SlixSignature) - i - 1]); + } + } + + furi_string_cat(str, "\n\e#ISO15693-3 data"); + nfc_render_iso15693_3_extra(slix_get_base_data(data), str); +} diff --git a/applications/main/nfc/helpers/protocol_support/slix/slix_render.h b/applications/main/nfc/helpers/protocol_support/slix/slix_render.h new file mode 100644 index 00000000000..98ae6dc97fd --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/slix/slix_render.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +#include "../nfc_protocol_support_render_common.h" + +void nfc_render_slix_info(const SlixData* data, NfcProtocolFormatType format_type, FuriString* str); diff --git a/applications/main/nfc/helpers/protocol_support/st25tb/st25tb.c b/applications/main/nfc/helpers/protocol_support/st25tb/st25tb.c new file mode 100644 index 00000000000..eef723fed39 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/st25tb/st25tb.c @@ -0,0 +1,103 @@ +#include "st25tb.h" +#include "st25tb_render.h" + +#include + +#include "nfc/nfc_app_i.h" + +#include "../nfc_protocol_support_common.h" +#include "../nfc_protocol_support_gui_common.h" + +static void nfc_scene_info_on_enter_st25tb(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const St25tbData* data = nfc_device_get_data(device, NfcProtocolSt25tb); + + FuriString* temp_str = furi_string_alloc(); + furi_string_cat_printf( + temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); + nfc_render_st25tb_info(data, NfcProtocolFormatTypeFull, temp_str); + + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 64, furi_string_get_cstr(temp_str)); + + furi_string_free(temp_str); +} + +static NfcCommand nfc_scene_read_poller_callback_st25tb(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolSt25tb); + + NfcApp* instance = context; + const St25tbPollerEvent* st25tb_event = event.event_data; + + if(st25tb_event->type == St25tbPollerEventTypeReady) { + nfc_device_set_data( + instance->nfc_device, NfcProtocolSt25tb, nfc_poller_get_data(instance->poller)); + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess); + return NfcCommandStop; + } + + return NfcCommandContinue; +} + +static void nfc_scene_read_on_enter_st25tb(NfcApp* instance) { + nfc_poller_start(instance->poller, nfc_scene_read_poller_callback_st25tb, instance); +} + +static void nfc_scene_read_success_on_enter_st25tb(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const St25tbData* data = nfc_device_get_data(device, NfcProtocolSt25tb); + + FuriString* temp_str = furi_string_alloc(); + furi_string_cat_printf( + temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); + nfc_render_st25tb_info(data, NfcProtocolFormatTypeShort, temp_str); + + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); + + furi_string_free(temp_str); +} + +static bool nfc_scene_saved_menu_on_event_st25tb(NfcApp* instance, uint32_t event) { + if(event == SubmenuIndexCommonEdit) { + scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid); + return true; + } + + return false; +} + +const NfcProtocolSupportBase nfc_protocol_support_st25tb = { + .features = NfcProtocolFeatureNone, + + .scene_info = + { + .on_enter = nfc_scene_info_on_enter_st25tb, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read = + { + .on_enter = nfc_scene_read_on_enter_st25tb, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read_menu = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read_success = + { + .on_enter = nfc_scene_read_success_on_enter_st25tb, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_saved_menu = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_scene_saved_menu_on_event_st25tb, + }, + .scene_emulate = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, +}; diff --git a/applications/main/nfc/helpers/protocol_support/st25tb/st25tb.h b/applications/main/nfc/helpers/protocol_support/st25tb/st25tb.h new file mode 100644 index 00000000000..3b635fdefc3 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/st25tb/st25tb.h @@ -0,0 +1,5 @@ +#pragma once + +#include "../nfc_protocol_support_base.h" + +extern const NfcProtocolSupportBase nfc_protocol_support_st25tb; diff --git a/applications/main/nfc/helpers/protocol_support/st25tb/st25tb_render.c b/applications/main/nfc/helpers/protocol_support/st25tb/st25tb_render.c new file mode 100644 index 00000000000..e3a0f3c50f8 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/st25tb/st25tb_render.c @@ -0,0 +1,22 @@ +#include "st25tb_render.h" +#include + +void nfc_render_st25tb_info( + const St25tbData* data, + NfcProtocolFormatType format_type, + FuriString* str) { + furi_string_cat_printf(str, "UID"); + + for(size_t i = 0; i < ST25TB_UID_SIZE; i++) { + furi_string_cat_printf(str, " %02X", data->uid[i]); + } + + if(format_type == NfcProtocolFormatTypeFull) { + furi_string_cat_printf(str, "\nSys. OTP: %08lX", data->system_otp_block); + furi_string_cat_printf(str, "\nBlocks:"); + for(size_t i = 0; i < st25tb_get_block_count(data->type); i += 2) { + furi_string_cat_printf( + str, "\n %02X %08lX %08lX", i, data->blocks[i], data->blocks[i + 1]); + } + } +} diff --git a/applications/main/nfc/helpers/protocol_support/st25tb/st25tb_render.h b/applications/main/nfc/helpers/protocol_support/st25tb/st25tb_render.h new file mode 100644 index 00000000000..9f7be34e9b0 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/st25tb/st25tb_render.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +#include "../nfc_protocol_support_render_common.h" + +void nfc_render_st25tb_info( + const St25tbData* data, + NfcProtocolFormatType format_type, + FuriString* str); diff --git a/applications/main/nfc/nfc.c b/applications/main/nfc/nfc.c deleted file mode 100644 index 4ac793b5b12..00000000000 --- a/applications/main/nfc/nfc.c +++ /dev/null @@ -1,323 +0,0 @@ -#include "nfc_i.h" -#include -#include - -bool nfc_custom_event_callback(void* context, uint32_t event) { - furi_assert(context); - Nfc* nfc = context; - return scene_manager_handle_custom_event(nfc->scene_manager, event); -} - -bool nfc_back_event_callback(void* context) { - furi_assert(context); - Nfc* nfc = context; - return scene_manager_handle_back_event(nfc->scene_manager); -} - -static void nfc_rpc_command_callback(RpcAppSystemEvent event, void* context) { - furi_assert(context); - Nfc* nfc = context; - - furi_assert(nfc->rpc_ctx); - - if(event == RpcAppEventSessionClose) { - view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventRpcSessionClose); - rpc_system_app_set_callback(nfc->rpc_ctx, NULL, NULL); - nfc->rpc_ctx = NULL; - } else if(event == RpcAppEventAppExit) { - view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit); - } else if(event == RpcAppEventLoadFile) { - view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventRpcLoad); - } else { - rpc_system_app_confirm(nfc->rpc_ctx, event, false); - } -} - -Nfc* nfc_alloc() { - Nfc* nfc = malloc(sizeof(Nfc)); - - nfc->worker = nfc_worker_alloc(); - nfc->view_dispatcher = view_dispatcher_alloc(); - nfc->scene_manager = scene_manager_alloc(&nfc_scene_handlers, nfc); - view_dispatcher_enable_queue(nfc->view_dispatcher); - view_dispatcher_set_event_callback_context(nfc->view_dispatcher, nfc); - view_dispatcher_set_custom_event_callback(nfc->view_dispatcher, nfc_custom_event_callback); - view_dispatcher_set_navigation_event_callback(nfc->view_dispatcher, nfc_back_event_callback); - - // Nfc device - nfc->dev = nfc_device_alloc(); - furi_string_set(nfc->dev->folder, NFC_APP_FOLDER); - - // Open GUI record - nfc->gui = furi_record_open(RECORD_GUI); - - // Open Notification record - nfc->notifications = furi_record_open(RECORD_NOTIFICATION); - - // Submenu - nfc->submenu = submenu_alloc(); - view_dispatcher_add_view(nfc->view_dispatcher, NfcViewMenu, submenu_get_view(nfc->submenu)); - - // Dialog - nfc->dialog_ex = dialog_ex_alloc(); - view_dispatcher_add_view( - nfc->view_dispatcher, NfcViewDialogEx, dialog_ex_get_view(nfc->dialog_ex)); - - // Popup - nfc->popup = popup_alloc(); - view_dispatcher_add_view(nfc->view_dispatcher, NfcViewPopup, popup_get_view(nfc->popup)); - - // Loading - nfc->loading = loading_alloc(); - view_dispatcher_add_view(nfc->view_dispatcher, NfcViewLoading, loading_get_view(nfc->loading)); - - // Text Input - nfc->text_input = text_input_alloc(); - view_dispatcher_add_view( - nfc->view_dispatcher, NfcViewTextInput, text_input_get_view(nfc->text_input)); - - // Byte Input - nfc->byte_input = byte_input_alloc(); - view_dispatcher_add_view( - nfc->view_dispatcher, NfcViewByteInput, byte_input_get_view(nfc->byte_input)); - - // TextBox - nfc->text_box = text_box_alloc(); - view_dispatcher_add_view( - nfc->view_dispatcher, NfcViewTextBox, text_box_get_view(nfc->text_box)); - nfc->text_box_store = furi_string_alloc(); - - // Custom Widget - nfc->widget = widget_alloc(); - view_dispatcher_add_view(nfc->view_dispatcher, NfcViewWidget, widget_get_view(nfc->widget)); - - // Mifare Classic Dict Attack - nfc->dict_attack = dict_attack_alloc(); - view_dispatcher_add_view( - nfc->view_dispatcher, NfcViewDictAttack, dict_attack_get_view(nfc->dict_attack)); - - // Detect Reader - nfc->detect_reader = detect_reader_alloc(); - view_dispatcher_add_view( - nfc->view_dispatcher, NfcViewDetectReader, detect_reader_get_view(nfc->detect_reader)); - - // Generator - nfc->generator = NULL; - - return nfc; -} - -void nfc_free(Nfc* nfc) { - furi_assert(nfc); - - if(nfc->rpc_state == NfcRpcStateEmulating) { - // Stop worker - nfc_worker_stop(nfc->worker); - } else if(nfc->rpc_state == NfcRpcStateEmulated) { - // Stop worker - nfc_worker_stop(nfc->worker); - // Save data in shadow file - if(furi_string_size(nfc->dev->load_path)) { - nfc_device_save_shadow(nfc->dev, furi_string_get_cstr(nfc->dev->load_path)); - } - } - if(nfc->rpc_ctx) { - rpc_system_app_send_exited(nfc->rpc_ctx); - rpc_system_app_set_callback(nfc->rpc_ctx, NULL, NULL); - nfc->rpc_ctx = NULL; - } - - // Nfc device - nfc_device_free(nfc->dev); - - // Submenu - view_dispatcher_remove_view(nfc->view_dispatcher, NfcViewMenu); - submenu_free(nfc->submenu); - - // DialogEx - view_dispatcher_remove_view(nfc->view_dispatcher, NfcViewDialogEx); - dialog_ex_free(nfc->dialog_ex); - - // Popup - view_dispatcher_remove_view(nfc->view_dispatcher, NfcViewPopup); - popup_free(nfc->popup); - - // Loading - view_dispatcher_remove_view(nfc->view_dispatcher, NfcViewLoading); - loading_free(nfc->loading); - - // TextInput - view_dispatcher_remove_view(nfc->view_dispatcher, NfcViewTextInput); - text_input_free(nfc->text_input); - - // ByteInput - view_dispatcher_remove_view(nfc->view_dispatcher, NfcViewByteInput); - byte_input_free(nfc->byte_input); - - // TextBox - view_dispatcher_remove_view(nfc->view_dispatcher, NfcViewTextBox); - text_box_free(nfc->text_box); - furi_string_free(nfc->text_box_store); - - // Custom Widget - view_dispatcher_remove_view(nfc->view_dispatcher, NfcViewWidget); - widget_free(nfc->widget); - - // Mifare Classic Dict Attack - view_dispatcher_remove_view(nfc->view_dispatcher, NfcViewDictAttack); - dict_attack_free(nfc->dict_attack); - - // Detect Reader - view_dispatcher_remove_view(nfc->view_dispatcher, NfcViewDetectReader); - detect_reader_free(nfc->detect_reader); - - // Worker - nfc_worker_stop(nfc->worker); - nfc_worker_free(nfc->worker); - - // View Dispatcher - view_dispatcher_free(nfc->view_dispatcher); - - // Scene Manager - scene_manager_free(nfc->scene_manager); - - // GUI - furi_record_close(RECORD_GUI); - nfc->gui = NULL; - - // Notifications - furi_record_close(RECORD_NOTIFICATION); - nfc->notifications = NULL; - - free(nfc); -} - -void nfc_text_store_set(Nfc* nfc, const char* text, ...) { - va_list args; - va_start(args, text); - - vsnprintf(nfc->text_store, sizeof(nfc->text_store), text, args); - - va_end(args); -} - -void nfc_text_store_clear(Nfc* nfc) { - memset(nfc->text_store, 0, sizeof(nfc->text_store)); -} - -void nfc_blink_read_start(Nfc* nfc) { - notification_message(nfc->notifications, &sequence_blink_start_cyan); -} - -void nfc_blink_emulate_start(Nfc* nfc) { - notification_message(nfc->notifications, &sequence_blink_start_magenta); -} - -void nfc_blink_detect_start(Nfc* nfc) { - notification_message(nfc->notifications, &sequence_blink_start_yellow); -} - -void nfc_blink_stop(Nfc* nfc) { - notification_message(nfc->notifications, &sequence_blink_stop); -} - -bool nfc_save_file(Nfc* nfc) { - furi_string_printf( - nfc->dev->load_path, - "%s/%s%s", - NFC_APP_FOLDER, - nfc->dev->dev_name, - NFC_APP_FILENAME_EXTENSION); - bool file_saved = nfc_device_save(nfc->dev, furi_string_get_cstr(nfc->dev->load_path)); - return file_saved; -} - -void nfc_show_loading_popup(void* context, bool show) { - Nfc* nfc = context; - TaskHandle_t timer_task = xTaskGetHandle(configTIMER_SERVICE_TASK_NAME); - - if(show) { - // Raise timer priority so that animations can play - vTaskPrioritySet(timer_task, configMAX_PRIORITIES - 1); - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewLoading); - } else { - // Restore default timer priority - vTaskPrioritySet(timer_task, configTIMER_TASK_PRIORITY); - } -} - -static bool nfc_is_hal_ready() { - if(!furi_hal_nfc_is_init()) { - // No connection to the chip, show an error screen - DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); - DialogMessage* message = dialog_message_alloc(); - dialog_message_set_text( - message, - "Error!\nNFC chip failed to start\n\n\nSend a photo of this to:\nsupport@flipperzero.one", - 0, - 0, - AlignLeft, - AlignTop); - dialog_message_show(dialogs, message); - dialog_message_free(message); - furi_record_close(RECORD_DIALOGS); - return false; - } else { - return true; - } -} - -int32_t nfc_app(void* p) { - if(!nfc_is_hal_ready()) return 0; - - Nfc* nfc = nfc_alloc(); - char* args = p; - - // Check argument and run corresponding scene - if(args && strlen(args)) { - nfc_device_set_loading_callback(nfc->dev, nfc_show_loading_popup, nfc); - uint32_t rpc_ctx = 0; - if(sscanf(p, "RPC %lX", &rpc_ctx) == 1) { - nfc->rpc_ctx = (void*)rpc_ctx; - rpc_system_app_set_callback(nfc->rpc_ctx, nfc_rpc_command_callback, nfc); - rpc_system_app_send_started(nfc->rpc_ctx); - view_dispatcher_attach_to_gui( - nfc->view_dispatcher, nfc->gui, ViewDispatcherTypeDesktop); - scene_manager_next_scene(nfc->scene_manager, NfcSceneRpc); - } else { - view_dispatcher_attach_to_gui( - nfc->view_dispatcher, nfc->gui, ViewDispatcherTypeFullscreen); - if(nfc_device_load(nfc->dev, p, true)) { - if(nfc->dev->format == NfcDeviceSaveFormatMifareUl) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightEmulate); - dolphin_deed(DolphinDeedNfcEmulate); - } else if(nfc->dev->format == NfcDeviceSaveFormatMifareClassic) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicEmulate); - dolphin_deed(DolphinDeedNfcEmulate); - } else if(nfc->dev->format == NfcDeviceSaveFormatNfcV) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcVEmulate); - dolphin_deed(DolphinDeedNfcEmulate); - } else if(nfc->dev->format == NfcDeviceSaveFormatBankCard) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneDeviceInfo); - } else { - scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateUid); - dolphin_deed(DolphinDeedNfcEmulate); - } - } else { - // Exit app - view_dispatcher_stop(nfc->view_dispatcher); - } - } - nfc_device_set_loading_callback(nfc->dev, NULL, nfc); - } else { - view_dispatcher_attach_to_gui( - nfc->view_dispatcher, nfc->gui, ViewDispatcherTypeFullscreen); - scene_manager_next_scene(nfc->scene_manager, NfcSceneStart); - } - - view_dispatcher_run(nfc->view_dispatcher); - - nfc_free(nfc); - - return 0; -} diff --git a/applications/main/nfc/nfc.h b/applications/main/nfc/nfc.h deleted file mode 100644 index e08be6a3aa9..00000000000 --- a/applications/main/nfc/nfc.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -typedef struct Nfc Nfc; diff --git a/applications/main/nfc/nfc_app.c b/applications/main/nfc/nfc_app.c new file mode 100644 index 00000000000..fe680aa32df --- /dev/null +++ b/applications/main/nfc/nfc_app.c @@ -0,0 +1,499 @@ +#include "nfc_app_i.h" + +#include + +bool nfc_custom_event_callback(void* context, uint32_t event) { + furi_assert(context); + NfcApp* nfc = context; + return scene_manager_handle_custom_event(nfc->scene_manager, event); +} + +bool nfc_back_event_callback(void* context) { + furi_assert(context); + NfcApp* nfc = context; + return scene_manager_handle_back_event(nfc->scene_manager); +} + +static void nfc_app_rpc_command_callback(RpcAppSystemEvent rpc_event, void* context) { + furi_assert(context); + NfcApp* nfc = (NfcApp*)context; + + furi_assert(nfc->rpc_ctx); + + if(rpc_event == RpcAppEventSessionClose) { + view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventRpcSessionClose); + rpc_system_app_set_callback(nfc->rpc_ctx, NULL, NULL); + nfc->rpc_ctx = NULL; + } else if(rpc_event == RpcAppEventAppExit) { + view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventRpcExit); + } else if(rpc_event == RpcAppEventLoadFile) { + view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventRpcLoad); + } else { + rpc_system_app_confirm(nfc->rpc_ctx, rpc_event, false); + } +} + +NfcApp* nfc_app_alloc() { + NfcApp* instance = malloc(sizeof(NfcApp)); + + instance->view_dispatcher = view_dispatcher_alloc(); + instance->scene_manager = scene_manager_alloc(&nfc_scene_handlers, instance); + view_dispatcher_enable_queue(instance->view_dispatcher); + view_dispatcher_set_event_callback_context(instance->view_dispatcher, instance); + view_dispatcher_set_custom_event_callback( + instance->view_dispatcher, nfc_custom_event_callback); + view_dispatcher_set_navigation_event_callback( + instance->view_dispatcher, nfc_back_event_callback); + + instance->nfc = nfc_alloc(); + + instance->mf_ul_auth = mf_ultralight_auth_alloc(); + instance->mfc_key_cache = mf_classic_key_cache_alloc(); + + // Nfc device + instance->nfc_device = nfc_device_alloc(); + nfc_device_set_loading_callback(instance->nfc_device, nfc_show_loading_popup, instance); + + // Open GUI record + instance->gui = furi_record_open(RECORD_GUI); + + // Open Notification record + instance->notifications = furi_record_open(RECORD_NOTIFICATION); + + // Open Storage record + instance->storage = furi_record_open(RECORD_STORAGE); + + // Open Dialogs record + instance->dialogs = furi_record_open(RECORD_DIALOGS); + + // Submenu + instance->submenu = submenu_alloc(); + view_dispatcher_add_view( + instance->view_dispatcher, NfcViewMenu, submenu_get_view(instance->submenu)); + + // Dialog + instance->dialog_ex = dialog_ex_alloc(); + view_dispatcher_add_view( + instance->view_dispatcher, NfcViewDialogEx, dialog_ex_get_view(instance->dialog_ex)); + + // Popup + instance->popup = popup_alloc(); + view_dispatcher_add_view( + instance->view_dispatcher, NfcViewPopup, popup_get_view(instance->popup)); + + // Loading + instance->loading = loading_alloc(); + view_dispatcher_add_view( + instance->view_dispatcher, NfcViewLoading, loading_get_view(instance->loading)); + + // Text Input + instance->text_input = text_input_alloc(); + view_dispatcher_add_view( + instance->view_dispatcher, NfcViewTextInput, text_input_get_view(instance->text_input)); + + // Byte Input + instance->byte_input = byte_input_alloc(); + view_dispatcher_add_view( + instance->view_dispatcher, NfcViewByteInput, byte_input_get_view(instance->byte_input)); + + // TextBox + instance->text_box = text_box_alloc(); + view_dispatcher_add_view( + instance->view_dispatcher, NfcViewTextBox, text_box_get_view(instance->text_box)); + instance->text_box_store = furi_string_alloc(); + + // Custom Widget + instance->widget = widget_alloc(); + view_dispatcher_add_view( + instance->view_dispatcher, NfcViewWidget, widget_get_view(instance->widget)); + + // Dict attack + + instance->dict_attack = dict_attack_alloc(); + view_dispatcher_add_view( + instance->view_dispatcher, NfcViewDictAttack, dict_attack_get_view(instance->dict_attack)); + + // Detect Reader + instance->detect_reader = detect_reader_alloc(); + view_dispatcher_add_view( + instance->view_dispatcher, + NfcViewDetectReader, + detect_reader_get_view(instance->detect_reader)); + + instance->iso14443_3a_edit_data = iso14443_3a_alloc(); + instance->file_path = furi_string_alloc_set(NFC_APP_FOLDER); + instance->file_name = furi_string_alloc(); + + return instance; +} + +void nfc_app_free(NfcApp* instance) { + furi_assert(instance); + + if(instance->rpc_ctx) { + rpc_system_app_send_exited(instance->rpc_ctx); + rpc_system_app_set_callback(instance->rpc_ctx, NULL, NULL); + } + + nfc_free(instance->nfc); + + mf_ultralight_auth_free(instance->mf_ul_auth); + mf_classic_key_cache_free(instance->mfc_key_cache); + + // Nfc device + nfc_device_free(instance->nfc_device); + + // Submenu + view_dispatcher_remove_view(instance->view_dispatcher, NfcViewMenu); + submenu_free(instance->submenu); + + // DialogEx + view_dispatcher_remove_view(instance->view_dispatcher, NfcViewDialogEx); + dialog_ex_free(instance->dialog_ex); + + // Popup + view_dispatcher_remove_view(instance->view_dispatcher, NfcViewPopup); + popup_free(instance->popup); + + // Loading + view_dispatcher_remove_view(instance->view_dispatcher, NfcViewLoading); + loading_free(instance->loading); + + // TextInput + view_dispatcher_remove_view(instance->view_dispatcher, NfcViewTextInput); + text_input_free(instance->text_input); + + // ByteInput + view_dispatcher_remove_view(instance->view_dispatcher, NfcViewByteInput); + byte_input_free(instance->byte_input); + + // TextBox + view_dispatcher_remove_view(instance->view_dispatcher, NfcViewTextBox); + text_box_free(instance->text_box); + furi_string_free(instance->text_box_store); + + // Custom Widget + view_dispatcher_remove_view(instance->view_dispatcher, NfcViewWidget); + widget_free(instance->widget); + + // Dict attack + view_dispatcher_remove_view(instance->view_dispatcher, NfcViewDictAttack); + dict_attack_free(instance->dict_attack); + + // Detect reader + view_dispatcher_remove_view(instance->view_dispatcher, NfcViewDetectReader); + detect_reader_free(instance->detect_reader); + + // View Dispatcher + view_dispatcher_free(instance->view_dispatcher); + + // Scene Manager + scene_manager_free(instance->scene_manager); + + furi_record_close(RECORD_DIALOGS); + furi_record_close(RECORD_STORAGE); + furi_record_close(RECORD_NOTIFICATION); + // GUI + furi_record_close(RECORD_GUI); + instance->gui = NULL; + + instance->notifications = NULL; + + iso14443_3a_free(instance->iso14443_3a_edit_data); + furi_string_free(instance->file_path); + furi_string_free(instance->file_name); + + free(instance); +} + +void nfc_text_store_set(NfcApp* nfc, const char* text, ...) { + va_list args; + va_start(args, text); + + vsnprintf(nfc->text_store, sizeof(nfc->text_store), text, args); + + va_end(args); +} + +void nfc_text_store_clear(NfcApp* nfc) { + memset(nfc->text_store, 0, sizeof(nfc->text_store)); +} + +void nfc_blink_read_start(NfcApp* nfc) { + notification_message(nfc->notifications, &sequence_blink_start_cyan); +} + +void nfc_blink_emulate_start(NfcApp* nfc) { + notification_message(nfc->notifications, &sequence_blink_start_magenta); +} + +void nfc_blink_detect_start(NfcApp* nfc) { + notification_message(nfc->notifications, &sequence_blink_start_yellow); +} + +void nfc_blink_stop(NfcApp* nfc) { + notification_message(nfc->notifications, &sequence_blink_stop); +} + +void nfc_make_app_folders(NfcApp* instance) { + furi_assert(instance); + + if(!storage_simply_mkdir(instance->storage, NFC_APP_FOLDER)) { + dialog_message_show_storage_error(instance->dialogs, "Cannot create\napp folder"); + } +} + +bool nfc_save_file(NfcApp* instance, FuriString* path) { + furi_assert(instance); + furi_assert(path); + + bool result = nfc_device_save(instance->nfc_device, furi_string_get_cstr(instance->file_path)); + + if(!result) { + dialog_message_show_storage_error(instance->dialogs, "Cannot save\nkey file"); + } + + return result; +} + +static bool nfc_set_shadow_file_path(FuriString* file_path, FuriString* shadow_file_path) { + furi_assert(file_path); + furi_assert(shadow_file_path); + + bool shadow_file_path_set = false; + if(furi_string_end_with(file_path, NFC_APP_SHADOW_EXTENSION)) { + furi_string_set(shadow_file_path, file_path); + shadow_file_path_set = true; + } else if(furi_string_end_with(file_path, NFC_APP_EXTENSION)) { + size_t path_len = furi_string_size(file_path); + // Cut .nfc + furi_string_set_n(shadow_file_path, file_path, 0, path_len - 4); + furi_string_cat_printf(shadow_file_path, "%s", NFC_APP_SHADOW_EXTENSION); + shadow_file_path_set = true; + } + + return shadow_file_path_set; +} + +static bool nfc_has_shadow_file_internal(NfcApp* instance, FuriString* path) { + furi_assert(path); + + bool has_shadow_file = false; + FuriString* shadow_file_path = furi_string_alloc(); + do { + if(furi_string_empty(path)) break; + if(!nfc_set_shadow_file_path(path, shadow_file_path)) break; + has_shadow_file = + storage_common_exists(instance->storage, furi_string_get_cstr(shadow_file_path)); + } while(false); + + furi_string_free(shadow_file_path); + + return has_shadow_file; +} + +bool nfc_has_shadow_file(NfcApp* instance) { + furi_assert(instance); + + return nfc_has_shadow_file_internal(instance, instance->file_path); +} + +static bool nfc_save_internal(NfcApp* instance, const char* extension) { + furi_assert(instance); + furi_assert(extension); + + bool result = false; + + nfc_make_app_folders(instance); + + if(furi_string_end_with(instance->file_path, NFC_APP_EXTENSION) || + (furi_string_end_with(instance->file_path, NFC_APP_SHADOW_EXTENSION))) { + size_t filename_start = furi_string_search_rchar(instance->file_path, '/'); + furi_string_left(instance->file_path, filename_start); + } + + furi_string_cat_printf( + instance->file_path, "/%s%s", furi_string_get_cstr(instance->file_name), extension); + + result = nfc_save_file(instance, instance->file_path); + + return result; +} + +bool nfc_save_shadow_file(NfcApp* instance) { + furi_assert(instance); + + return nfc_save_internal(instance, NFC_APP_SHADOW_EXTENSION); +} + +bool nfc_save(NfcApp* instance) { + furi_assert(instance); + + return nfc_save_internal(instance, NFC_APP_EXTENSION); +} + +bool nfc_load_file(NfcApp* instance, FuriString* path, bool show_dialog) { + furi_assert(instance); + furi_assert(path); + bool result = false; + + FuriString* load_path = furi_string_alloc(); + if(nfc_has_shadow_file_internal(instance, path)) { + nfc_set_shadow_file_path(path, load_path); + } else if(furi_string_end_with(path, NFC_APP_SHADOW_EXTENSION)) { + size_t path_len = furi_string_size(path); + furi_string_set_n(load_path, path, 0, path_len - 4); + furi_string_cat_printf(load_path, "%s", NFC_APP_EXTENSION); + } else { + furi_string_set(load_path, path); + } + + result = nfc_device_load(instance->nfc_device, furi_string_get_cstr(load_path)); + + if(result) { + path_extract_filename(load_path, instance->file_name, true); + } + + if((!result) && (show_dialog)) { + dialog_message_show_storage_error(instance->dialogs, "Cannot load\nkey file"); + } + + furi_string_free(load_path); + + return result; +} + +bool nfc_delete(NfcApp* instance) { + furi_assert(instance); + + if(nfc_has_shadow_file(instance)) { + nfc_delete_shadow_file(instance); + } + + if(furi_string_end_with_str(instance->file_path, NFC_APP_SHADOW_EXTENSION)) { + size_t path_len = furi_string_size(instance->file_path); + furi_string_replace_at(instance->file_path, path_len - 4, 4, NFC_APP_EXTENSION); + } + + return storage_simply_remove(instance->storage, furi_string_get_cstr(instance->file_path)); +} + +bool nfc_delete_shadow_file(NfcApp* instance) { + furi_assert(instance); + + FuriString* shadow_file_path = furi_string_alloc(); + + bool result = nfc_set_shadow_file_path(instance->file_path, shadow_file_path) && + storage_simply_remove(instance->storage, furi_string_get_cstr(shadow_file_path)); + + furi_string_free(shadow_file_path); + return result; +} + +bool nfc_load_from_file_select(NfcApp* instance) { + furi_assert(instance); + + DialogsFileBrowserOptions browser_options; + dialog_file_browser_set_basic_options(&browser_options, NFC_APP_EXTENSION, &I_Nfc_10px); + browser_options.base_path = NFC_APP_FOLDER; + browser_options.hide_dot_files = true; + + // Input events and views are managed by file_browser + bool result = dialog_file_browser_show( + instance->dialogs, instance->file_path, instance->file_path, &browser_options); + + if(result) { + result = nfc_load_file(instance, instance->file_path, true); + } + + return result; +} + +void nfc_show_loading_popup(void* context, bool show) { + NfcApp* nfc = context; + TaskHandle_t timer_task = xTaskGetHandle(configTIMER_SERVICE_TASK_NAME); + + if(show) { + // Raise timer priority so that animations can play + vTaskPrioritySet(timer_task, configMAX_PRIORITIES - 1); + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewLoading); + } else { + // Restore default timer priority + vTaskPrioritySet(timer_task, configTIMER_TASK_PRIORITY); + } +} + +void nfc_app_set_detected_protocols(NfcApp* instance, const NfcProtocol* types, uint32_t count) { + furi_assert(instance); + furi_assert(types); + furi_assert(count < NfcProtocolNum); + + memcpy(instance->protocols_detected, types, count); + instance->protocols_detected_num = count; + instance->protocols_detected_selected_idx = 0; +} + +void nfc_app_reset_detected_protocols(NfcApp* instance) { + furi_assert(instance); + + instance->protocols_detected_selected_idx = 0; + instance->protocols_detected_num = 0; +} + +static bool nfc_is_hal_ready() { + if(furi_hal_nfc_is_hal_ready() != FuriHalNfcErrorNone) { + // No connection to the chip, show an error screen + DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); + DialogMessage* message = dialog_message_alloc(); + dialog_message_set_text( + message, + "Error!\nNFC chip failed to start\n\n\nSend a photo of this to:\nsupport@flipperzero.one", + 0, + 0, + AlignLeft, + AlignTop); + dialog_message_show(dialogs, message); + dialog_message_free(message); + furi_record_close(RECORD_DIALOGS); + return false; + } else { + return true; + } +} + +int32_t nfc_app(void* p) { + if(!nfc_is_hal_ready()) return 0; + + NfcApp* nfc = nfc_app_alloc(); + const char* args = p; + + if(args && strlen(args)) { + if(sscanf(args, "RPC %p", &nfc->rpc_ctx) == 1) { + rpc_system_app_set_callback(nfc->rpc_ctx, nfc_app_rpc_command_callback, nfc); + rpc_system_app_send_started(nfc->rpc_ctx); + view_dispatcher_attach_to_gui( + nfc->view_dispatcher, nfc->gui, ViewDispatcherTypeDesktop); + scene_manager_next_scene(nfc->scene_manager, NfcSceneRpc); + } else { + view_dispatcher_attach_to_gui( + nfc->view_dispatcher, nfc->gui, ViewDispatcherTypeFullscreen); + + furi_string_set(nfc->file_path, args); + if(nfc_load_file(nfc, nfc->file_path, false)) { + scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulate); + } else { + view_dispatcher_stop(nfc->view_dispatcher); + } + } + } else { + view_dispatcher_attach_to_gui( + nfc->view_dispatcher, nfc->gui, ViewDispatcherTypeFullscreen); + scene_manager_next_scene(nfc->scene_manager, NfcSceneStart); + } + + view_dispatcher_run(nfc->view_dispatcher); + + nfc_app_free(nfc); + + return 0; +} diff --git a/applications/main/nfc/nfc_app.h b/applications/main/nfc/nfc_app.h new file mode 100644 index 00000000000..c3026d30287 --- /dev/null +++ b/applications/main/nfc/nfc_app.h @@ -0,0 +1,19 @@ +/** + * @file nfc_app.h + * @brief NFC application -- start here. + * + * Application for interfacing with NFC cards and other devices via Flipper's built-in NFC hardware. + * + * Main features: + * * Multiple protocols support + * * Card emulation + * * Shadow file support + * * Dynamically loaded parser plugins + * + * @see nfc_protocol.h for information on adding a new library protocol. + * @see nfc_protocol_support.h for information on integrating a library protocol into the app. + * @see nfc_supported_card_plugin.h for information on adding supported card plugins (parsers). + */ +#pragma once + +typedef struct NfcApp NfcApp; diff --git a/applications/main/nfc/nfc_app_i.h b/applications/main/nfc/nfc_app_i.h new file mode 100644 index 00000000000..3c0092007af --- /dev/null +++ b/applications/main/nfc/nfc_app_i.h @@ -0,0 +1,192 @@ +#pragma once + +#include "nfc_app.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include "views/dict_attack.h" +#include "views/detect_reader.h" +#include "views/dict_attack.h" + +#include +#include "helpers/nfc_custom_event.h" +#include "helpers/mf_ultralight_auth.h" +#include "helpers/mf_user_dict.h" +#include "helpers/mfkey32_logger.h" +#include "helpers/mf_classic_key_cache.h" + +#include +#include +#include + +#include "rpc/rpc_app.h" + +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#define NFC_NAME_SIZE 22 +#define NFC_TEXT_STORE_SIZE 128 +#define NFC_BYTE_INPUT_STORE_SIZE 10 +#define NFC_LOG_SIZE_MAX (1024) +#define NFC_APP_FOLDER ANY_PATH("nfc") +#define NFC_APP_EXTENSION ".nfc" +#define NFC_APP_SHADOW_EXTENSION ".shd" +#define NFC_APP_FILENAME_PREFIX "NFC" + +#define NFC_APP_MFKEY32_LOGS_FILE_NAME ".mfkey32.log" +#define NFC_APP_MFKEY32_LOGS_FILE_PATH (NFC_APP_FOLDER "/" NFC_APP_MFKEY32_LOGS_FILE_NAME) + +#define NFC_APP_MF_CLASSIC_DICT_USER_PATH (NFC_APP_FOLDER "/assets/mf_classic_dict_user.nfc") +#define NFC_APP_MF_CLASSIC_DICT_SYSTEM_PATH (NFC_APP_FOLDER "/assets/mf_classic_dict.nfc") + +typedef enum { + NfcRpcStateIdle, + NfcRpcStateEmulating, +} NfcRpcState; + +typedef struct { + NfcDict* dict; + uint8_t sectors_total; + uint8_t sectors_read; + uint8_t current_sector; + uint8_t keys_found; + size_t dict_keys_total; + size_t dict_keys_current; + bool is_key_attack; + uint8_t key_attack_current_sector; + bool is_card_present; +} NfcMfClassicDictAttackContext; + +struct NfcApp { + DialogsApp* dialogs; + Storage* storage; + Gui* gui; + ViewDispatcher* view_dispatcher; + NotificationApp* notifications; + SceneManager* scene_manager; + + char text_store[NFC_TEXT_STORE_SIZE + 1]; + FuriString* text_box_store; + uint8_t byte_input_store[NFC_BYTE_INPUT_STORE_SIZE]; + + uint32_t protocols_detected_num; + NfcProtocol protocols_detected[NfcProtocolNum]; + uint32_t protocols_detected_selected_idx; + + RpcAppSystem* rpc_ctx; + NfcRpcState rpc_state; + + // Common Views + Submenu* submenu; + DialogEx* dialog_ex; + Popup* popup; + Loading* loading; + TextInput* text_input; + ByteInput* byte_input; + TextBox* text_box; + Widget* widget; + DetectReader* detect_reader; + DictAttack* dict_attack; + + Nfc* nfc; + NfcPoller* poller; + NfcScanner* scanner; + NfcListener* listener; + + MfUltralightAuth* mf_ul_auth; + NfcMfClassicDictAttackContext nfc_dict_context; + Mfkey32Logger* mfkey32_logger; + MfUserDict* mf_user_dict; + MfClassicKeyCache* mfc_key_cache; + + NfcDevice* nfc_device; + Iso14443_3aData* iso14443_3a_edit_data; + FuriString* file_path; + FuriString* file_name; + FuriTimer* timer; +}; + +typedef enum { + NfcViewMenu, + NfcViewDialogEx, + NfcViewPopup, + NfcViewLoading, + NfcViewTextInput, + NfcViewByteInput, + NfcViewTextBox, + NfcViewWidget, + NfcViewDictAttack, + NfcViewDetectReader, +} NfcView; + +int32_t nfc_task(void* p); + +void nfc_text_store_set(NfcApp* nfc, const char* text, ...); + +void nfc_text_store_clear(NfcApp* nfc); + +void nfc_blink_read_start(NfcApp* nfc); + +void nfc_blink_emulate_start(NfcApp* nfc); + +void nfc_blink_detect_start(NfcApp* nfc); + +void nfc_blink_stop(NfcApp* nfc); + +void nfc_show_loading_popup(void* context, bool show); + +bool nfc_has_shadow_file(NfcApp* instance); + +bool nfc_save_shadow_file(NfcApp* instance); + +bool nfc_delete_shadow_file(NfcApp* instance); + +bool nfc_save(NfcApp* instance); + +bool nfc_delete(NfcApp* instance); + +bool nfc_load_from_file_select(NfcApp* instance); + +bool nfc_load_file(NfcApp* instance, FuriString* path, bool show_dialog); + +bool nfc_save_file(NfcApp* instance, FuriString* path); + +void nfc_make_app_folder(NfcApp* instance); + +void nfc_app_set_detected_protocols(NfcApp* instance, const NfcProtocol* types, uint32_t count); + +void nfc_app_reset_detected_protocols(NfcApp* instance); diff --git a/applications/main/nfc/nfc_cli.c b/applications/main/nfc/nfc_cli.c index e9617438124..b5a40b122da 100644 --- a/applications/main/nfc/nfc_cli.c +++ b/applications/main/nfc/nfc_cli.c @@ -4,90 +4,30 @@ #include #include -#include -#include +#include + +#define FLAG_EVENT (1 << 10) static void nfc_cli_print_usage() { printf("Usage:\r\n"); printf("nfc \r\n"); printf("Cmd list:\r\n"); - printf("\tdetect\t - detect nfc device\r\n"); - printf("\temulate\t - emulate predefined nfca card\r\n"); - printf("\tapdu\t - Send APDU and print response \r\n"); if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { printf("\tfield\t - turn field on\r\n"); } } -static void nfc_cli_detect(Cli* cli, FuriString* args) { - UNUSED(args); - // Check if nfc worker is not busy - if(furi_hal_nfc_is_busy()) { - printf("Nfc is busy\r\n"); - return; - } - - FuriHalNfcDevData dev_data = {}; - bool cmd_exit = false; - furi_hal_nfc_exit_sleep(); - printf("Detecting nfc...\r\nPress Ctrl+C to abort\r\n"); - while(!cmd_exit) { - cmd_exit |= cli_cmd_interrupt_received(cli); - if(furi_hal_nfc_detect(&dev_data, 400)) { - printf("Found: %s ", nfc_get_dev_type(dev_data.type)); - printf("UID length: %d, UID:", dev_data.uid_len); - for(size_t i = 0; i < dev_data.uid_len; i++) { - printf("%02X", dev_data.uid[i]); - } - printf("\r\n"); - break; - } - furi_hal_nfc_sleep(); - furi_delay_ms(50); - } - furi_hal_nfc_sleep(); -} - -static void nfc_cli_emulate(Cli* cli, FuriString* args) { - UNUSED(args); - // Check if nfc worker is not busy - if(furi_hal_nfc_is_busy()) { - printf("Nfc is busy\r\n"); - return; - } - - furi_hal_nfc_exit_sleep(); - printf("Emulating NFC-A Type: T2T UID: 36 9C E7 B1 0A C1 34 SAK: 00 ATQA: 00/44\r\n"); - printf("Press Ctrl+C to abort\r\n"); - - FuriHalNfcDevData params = { - .uid = {0x36, 0x9C, 0xe7, 0xb1, 0x0A, 0xC1, 0x34}, - .uid_len = 7, - .atqa = {0x44, 0x00}, - .sak = 0x00, - .type = FuriHalNfcTypeA, - }; - - while(!cli_cmd_interrupt_received(cli)) { - if(furi_hal_nfc_listen(params.uid, params.uid_len, params.atqa, params.sak, false, 100)) { - printf("Reader detected\r\n"); - furi_hal_nfc_sleep(); - } - furi_delay_ms(50); - } - furi_hal_nfc_sleep(); -} - static void nfc_cli_field(Cli* cli, FuriString* args) { UNUSED(args); // Check if nfc worker is not busy - if(furi_hal_nfc_is_busy()) { - printf("Nfc is busy\r\n"); + if(furi_hal_nfc_is_hal_ready() != FuriHalNfcErrorNone) { + printf("NFC chip failed to start\r\n"); return; } - furi_hal_nfc_exit_sleep(); - furi_hal_nfc_field_on(); + furi_hal_nfc_acquire(); + furi_hal_nfc_low_power_mode_stop(); + furi_hal_nfc_poller_field_on(); printf("Field is on. Don't leave device in this mode for too long.\r\n"); printf("Press Ctrl+C to abort\r\n"); @@ -96,73 +36,8 @@ static void nfc_cli_field(Cli* cli, FuriString* args) { furi_delay_ms(50); } - furi_hal_nfc_field_off(); - furi_hal_nfc_sleep(); -} - -static void nfc_cli_apdu(Cli* cli, FuriString* args) { - UNUSED(cli); - if(furi_hal_nfc_is_busy()) { - printf("Nfc is busy\r\n"); - return; - } - - furi_hal_nfc_exit_sleep(); - FuriString* data = NULL; - data = furi_string_alloc(); - FuriHalNfcTxRxContext tx_rx = {}; - FuriHalNfcDevData dev_data = {}; - uint8_t* req_buffer = NULL; - uint8_t* resp_buffer = NULL; - size_t apdu_size = 0; - size_t resp_size = 0; - - do { - if(!args_read_string_and_trim(args, data)) { - printf( - "Use like `nfc apdu 00a404000e325041592e5359532e444446303100 00a4040008a0000003010102` \r\n"); - break; - } - - printf("detecting tag\r\n"); - if(!furi_hal_nfc_detect(&dev_data, 300)) { - printf("Failed to detect tag\r\n"); - break; - } - do { - apdu_size = furi_string_size(data) / 2; - req_buffer = malloc(apdu_size); - hex_chars_to_uint8(furi_string_get_cstr(data), req_buffer); - - memcpy(tx_rx.tx_data, req_buffer, apdu_size); - tx_rx.tx_bits = apdu_size * 8; - tx_rx.tx_rx_type = FuriHalNfcTxRxTypeDefault; - - printf("Sending APDU:%s to Tag\r\n", furi_string_get_cstr(data)); - if(!furi_hal_nfc_tx_rx(&tx_rx, 300)) { - printf("Failed to tx_rx\r\n"); - break; - } - resp_size = (tx_rx.rx_bits / 8) * 2; - if(!resp_size) { - printf("No response\r\n"); - continue; - } - resp_buffer = malloc(resp_size); - uint8_to_hex_chars(tx_rx.rx_data, resp_buffer, resp_size); - resp_buffer[resp_size] = 0; - printf("Response: %s\r\n", resp_buffer); - free(req_buffer); - free(resp_buffer); - req_buffer = NULL; - resp_buffer = NULL; - } while(args_read_string_and_trim(args, data)); - } while(false); - - free(req_buffer); - free(resp_buffer); - furi_string_free(data); - furi_hal_nfc_sleep(); + furi_hal_nfc_low_power_mode_start(); + furi_hal_nfc_release(); } static void nfc_cli(Cli* cli, FuriString* args, void* context) { @@ -175,20 +50,6 @@ static void nfc_cli(Cli* cli, FuriString* args, void* context) { nfc_cli_print_usage(); break; } - if(furi_string_cmp_str(cmd, "detect") == 0) { - nfc_cli_detect(cli, args); - break; - } - if(furi_string_cmp_str(cmd, "emulate") == 0) { - nfc_cli_emulate(cli, args); - break; - } - - if(furi_string_cmp_str(cmd, "apdu") == 0) { - nfc_cli_apdu(cli, args); - break; - } - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { if(furi_string_cmp_str(cmd, "field") == 0) { nfc_cli_field(cli, args); diff --git a/applications/main/nfc/nfc_i.h b/applications/main/nfc/nfc_i.h deleted file mode 100644 index f7e48990292..00000000000 --- a/applications/main/nfc/nfc_i.h +++ /dev/null @@ -1,118 +0,0 @@ -#pragma once - -#include "nfc.h" - -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include "views/dict_attack.h" -#include "views/detect_reader.h" - -#include -#include - -#include - -#include "rpc/rpc_app.h" - -#include - -ARRAY_DEF(MfClassicUserKeys, char*, M_PTR_OPLIST); - -#define NFC_TEXT_STORE_SIZE 128 -#define NFC_APP_FOLDER ANY_PATH("nfc") - -typedef enum { - NfcRpcStateIdle, - NfcRpcStateEmulating, - NfcRpcStateEmulated, -} NfcRpcState; - -struct Nfc { - NfcWorker* worker; - ViewDispatcher* view_dispatcher; - Gui* gui; - NotificationApp* notifications; - SceneManager* scene_manager; - NfcDevice* dev; - FuriHalNfcDevData dev_edit_data; - - char text_store[NFC_TEXT_STORE_SIZE + 1]; - FuriString* text_box_store; - uint8_t byte_input_store[6]; - MfClassicUserKeys_t mfc_key_strs; // Used in MFC key listing - - void* rpc_ctx; - NfcRpcState rpc_state; - - // Common Views - Submenu* submenu; - DialogEx* dialog_ex; - Popup* popup; - Loading* loading; - TextInput* text_input; - ByteInput* byte_input; - TextBox* text_box; - Widget* widget; - DictAttack* dict_attack; - DetectReader* detect_reader; - - const NfcGenerator* generator; -}; - -typedef enum { - NfcViewMenu, - NfcViewDialogEx, - NfcViewPopup, - NfcViewLoading, - NfcViewTextInput, - NfcViewByteInput, - NfcViewTextBox, - NfcViewWidget, - NfcViewDictAttack, - NfcViewDetectReader, -} NfcView; - -Nfc* nfc_alloc(); - -int32_t nfc_task(void* p); - -void nfc_text_store_set(Nfc* nfc, const char* text, ...); - -void nfc_text_store_clear(Nfc* nfc); - -void nfc_blink_read_start(Nfc* nfc); - -void nfc_blink_emulate_start(Nfc* nfc); - -void nfc_blink_detect_start(Nfc* nfc); - -void nfc_blink_stop(Nfc* nfc); - -bool nfc_save_file(Nfc* nfc); - -void nfc_show_loading_popup(void* context, bool show); diff --git a/applications/main/nfc/plugins/supported_cards/all_in_one.c b/applications/main/nfc/plugins/supported_cards/all_in_one.c new file mode 100644 index 00000000000..1be23d1f332 --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/all_in_one.c @@ -0,0 +1,107 @@ +#include "nfc_supported_card_plugin.h" + +#include +#include + +#define TAG "AllInOne" + +typedef enum { + AllInOneLayoutTypeA, + AllInOneLayoutTypeD, + AllInOneLayoutTypeE2, + AllInOneLayoutTypeE3, + AllInOneLayoutTypeE5, + AllInOneLayoutType2, + AllInOneLayoutTypeUnknown, +} AllInOneLayoutType; + +static AllInOneLayoutType all_in_one_get_layout(const MfUltralightData* data) { + // Switch on the second half of the third byte of page 5 + const uint8_t layout_byte = data->page[5].data[2]; + const uint8_t layout_half_byte = data->page[5].data[2] & 0x0F; + + FURI_LOG_I(TAG, "Layout byte: %02x", layout_byte); + FURI_LOG_I(TAG, "Layout half-byte: %02x", layout_half_byte); + + switch(layout_half_byte) { + // If it is A, the layout type is a type A layout + case 0x0A: + return AllInOneLayoutTypeA; + case 0x0D: + return AllInOneLayoutTypeD; + case 0x02: + return AllInOneLayoutType2; + default: + FURI_LOG_I(TAG, "Unknown layout type: %d", layout_half_byte); + return AllInOneLayoutTypeUnknown; + } +} + +static bool all_in_one_parse(const NfcDevice* device, FuriString* parsed_data) { + furi_assert(device); + furi_assert(parsed_data); + + const MfUltralightData* data = nfc_device_get_data(device, NfcProtocolMfUltralight); + + bool parsed = false; + + do { + if(data->page[4].data[0] != 0x45 || data->page[4].data[1] != 0xD9) { + FURI_LOG_I(TAG, "Pass not verified"); + break; + } + + uint8_t ride_count = 0; + uint32_t serial = 0; + + const AllInOneLayoutType layout_type = all_in_one_get_layout(data); + + if(layout_type == AllInOneLayoutTypeA) { + // If the layout is A then the ride count is stored in the first byte of page 8 + ride_count = data->page[8].data[0]; + } else if(layout_type == AllInOneLayoutTypeD) { + // If the layout is D, the ride count is stored in the second byte of page 9 + ride_count = data->page[9].data[1]; + } else { + FURI_LOG_I(TAG, "Unknown layout: %d", layout_type); + ride_count = 137; + } + + // // The number starts at the second half of the third byte on page 4, and is 32 bits long + // // So we get the second half of the third byte, then bytes 4-6, and then the first half of the 7th byte + // // B8 17 A2 A4 BD becomes 81 7A 2A 4B + const uint8_t* serial_data_lo = data->page[4].data; + const uint8_t* serial_data_hi = data->page[5].data; + + serial = (serial_data_lo[2] & 0x0F) << 28 | serial_data_lo[3] << 20 | + serial_data_hi[0] << 12 | serial_data_hi[1] << 4 | serial_data_hi[2] >> 4; + + // Format string for rides count + furi_string_printf( + parsed_data, "\e#All-In-One\nNumber: %lu\nRides left: %u", serial, ride_count); + + parsed = true; + } while(false); + + return parsed; +} + +/* Actual implementation of app<>plugin interface */ +static const NfcSupportedCardsPlugin all_in_one_plugin = { + .protocol = NfcProtocolMfUltralight, + .verify = NULL, + .read = NULL, + .parse = all_in_one_parse, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor all_in_one_plugin_descriptor = { + .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID, + .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION, + .entry_point = &all_in_one_plugin, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* all_in_one_plugin_ep() { + return &all_in_one_plugin_descriptor; +} diff --git a/applications/main/nfc/plugins/supported_cards/myki.c b/applications/main/nfc/plugins/supported_cards/myki.c new file mode 100644 index 00000000000..70a6963710f --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/myki.c @@ -0,0 +1,116 @@ +/* myki.c - Parser for myki cards (Melbourne, Australia). + * + * Based on the code by Emily Trau (https://github.com/emilytrau) + * Original pull request URL: https://github.com/flipperdevices/flipperzero-firmware/pull/2326 + * Reference: https://github.com/metrodroid/metrodroid/wiki/Myki + */ +#include "nfc_supported_card_plugin.h" + +#include +#include + +static const MfDesfireApplicationId myki_app_id = {.data = {0x00, 0x11, 0xf2}}; +static const MfDesfireFileId myki_file_id = 0x0f; + +static uint8_t myki_calculate_luhn(uint64_t number) { + // https://en.wikipedia.org/wiki/Luhn_algorithm + // Drop existing check digit to form payload + uint64_t payload = number / 10; + int sum = 0; + int position = 0; + + while(payload > 0) { + int digit = payload % 10; + if(position % 2 == 0) { + digit *= 2; + } + if(digit > 9) { + digit = (digit / 10) + (digit % 10); + } + sum += digit; + payload /= 10; + position++; + } + + return (10 - (sum % 10)) % 10; +} + +static bool myki_parse(const NfcDevice* device, FuriString* parsed_data) { + furi_assert(device); + furi_assert(parsed_data); + + bool parsed = false; + + do { + const MfDesfireData* data = nfc_device_get_data(device, NfcProtocolMfDesfire); + + const MfDesfireApplication* app = mf_desfire_get_application(data, &myki_app_id); + if(app == NULL) break; + + typedef struct { + uint32_t top; + uint32_t bottom; + } MykiFile; + + const MfDesfireFileSettings* file_settings = + mf_desfire_get_file_settings(app, &myki_file_id); + + if(file_settings == NULL || file_settings->type != MfDesfireFileTypeStandard || + file_settings->data.size < sizeof(MykiFile)) + break; + + const MfDesfireFileData* file_data = mf_desfire_get_file_data(app, &myki_file_id); + if(file_data == NULL) break; + + const MykiFile* myki_file = simple_array_cget_data(file_data->data); + + // All myki card numbers are prefixed with "308425" + if(myki_file->top != 308425UL) break; + // Card numbers are always 15 digits in length + if(myki_file->bottom < 10000000UL || myki_file->bottom >= 100000000UL) break; + + uint64_t card_number = myki_file->top * 1000000000ULL + myki_file->bottom * 10UL; + // Stored card number doesn't include check digit + card_number += myki_calculate_luhn(card_number); + + furi_string_set(parsed_data, "\e#myki\n"); + + // Stylise card number according to the physical card + char card_string[20]; + snprintf(card_string, sizeof(card_string), "%llu", card_number); + + // Digit count in each space-separated group + static const uint8_t digit_count[] = {1, 5, 4, 4, 1}; + + for(uint32_t i = 0, k = 0; i < COUNT_OF(digit_count); k += digit_count[i++]) { + for(uint32_t j = 0; j < digit_count[i]; ++j) { + furi_string_push_back(parsed_data, card_string[j + k]); + } + furi_string_push_back(parsed_data, ' '); + } + + parsed = true; + } while(false); + + return parsed; +} + +/* Actual implementation of app<>plugin interface */ +static const NfcSupportedCardsPlugin myki_plugin = { + .protocol = NfcProtocolMfDesfire, + .verify = NULL, + .read = NULL, + .parse = myki_parse, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor myki_plugin_descriptor = { + .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID, + .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION, + .entry_point = &myki_plugin, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* myki_plugin_ep() { + return &myki_plugin_descriptor; +} diff --git a/applications/main/nfc/plugins/supported_cards/nfc_supported_card_plugin.h b/applications/main/nfc/plugins/supported_cards/nfc_supported_card_plugin.h new file mode 100644 index 00000000000..7883aeb6b07 --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/nfc_supported_card_plugin.h @@ -0,0 +1,94 @@ +/** + * @file nfc_supported_card_plugin.h + * @brief Supported card plugin abstract interface. + * + * Supported card plugins are dynamically loaded libraries that help making sense of + * a particular card's raw data, if a suitable plugin exists. + * + * For example, if some card serves as a bus ticket, instead of just displaying a data dump, + * a suitable plugin will transform that data into a human-readable format, showing the number + * of rides or balance left. + * Because of the highly specialised nature of application-specific cards, a separate plugin + * for each such card type must be implemented. + * + * To add a new plugin, create a uniquely-named .c file in the `supported_cards` directory + * and implement at least the parse() function in the NfcSupportedCardsPlugin structure. + * Then, register the plugin in the `application.fam` file in the `nfc` directory. Use the existing + * entries as an example. After being registered, the plugin will be automatically deployed with the application. + * + * @note the APPID field MUST end with `_parser` so the applicaton would know that this particular file + * is a supported card plugin. + * + * @see nfc_supported_cards.h + */ +#pragma once + +#include + +#include +#include + +/** + * @brief Unique string identifier for supported card plugins. + */ +#define NFC_SUPPORTED_CARD_PLUGIN_APP_ID "NfcSupportedCardPlugin" + +/** + * @brief Currently supported plugin API version. + */ +#define NFC_SUPPORTED_CARD_PLUGIN_API_VERSION 1 + +/** + * @brief Verify that the card is of a supported type. + * + * This function should be implemented if a quick check exists + * allowing to verify that the plugin is working with the appropriate card type. + * Such checks may include, but are not limited to: reading a specific sector, + * performing a certain read operation, etc. + * + * @param[in,out] nfc pointer to an Nfc instance. + * @returns true if the card was successfully verified, false otherwise. + */ +typedef bool (*NfcSupportedCardPluginVerify)(Nfc* nfc); + +/** + * @brief Read the card using a custom procedure. + * + * This function should be implemented if a card requires some special reading + * procedure not covered in the vanilla poller. Examples include, but are not + * limited to: reading with particular security keys, mandatory order of read + * operations, etc. + * + * @param[in,out] nfc pointer to an Nfc instance. + * @param[in,out] device pointer to a device instance to hold the read data. + * @returns true if the card was successfully read, false otherwise. + */ +typedef bool (*NfcSupportedCardPluginRead)(Nfc* nfc, NfcDevice* device); + +/** + * @brief Parse raw data into human-readable representation. + * + * A supported card plugin may contain only this function, if no special verification + * or reading procedures are not required. In any case, the data must be complete and + * available through the `device` parameter at the time of calling. + * + * The output format is free and application-dependent. Multiple lines should + * be separated by newline character. + * + * @param[in] device pointer to a device instance holding the data is to be parsed. + * @param[out] parsed_data pointer to the string to contain the formatted result. + * @returns true if the card was successfully parsed, false otherwise. + */ +typedef bool (*NfcSupportedCardPluginParse)(const NfcDevice* device, FuriString* parsed_data); + +/** + * @brief Supported card plugin interface. + * + * For a minimally functional plugin, only the parse() function must be implemented. + */ +typedef struct { + NfcProtocol protocol; /**< Identifier of the protocol this card type works on top of. */ + NfcSupportedCardPluginVerify verify; /**< Pointer to the verify() function. */ + NfcSupportedCardPluginRead read; /**< Pointer to the read() function. */ + NfcSupportedCardPluginParse parse; /**< Pointer to the parse() function. */ +} NfcSupportedCardsPlugin; diff --git a/applications/main/nfc/plugins/supported_cards/opal.c b/applications/main/nfc/plugins/supported_cards/opal.c new file mode 100644 index 00000000000..c71635d53eb --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/opal.c @@ -0,0 +1,233 @@ +/* + * opal.c - Parser for Opal card (Sydney, Australia). + * + * Copyright 2023 Michael Farrell + * + * This will only read "standard" MIFARE DESFire-based Opal cards. Free travel + * cards (including School Opal cards, veteran, vision-impaired persons and + * TfNSW employees' cards) and single-trip tickets are MIFARE Ultralight C + * cards and not supported. + * + * Reference: https://github.com/metrodroid/metrodroid/wiki/Opal + * + * Note: The card values are all little-endian (like Flipper), but the above + * reference was originally written based on Java APIs, which are big-endian. + * This implementation presumes a little-endian system. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "nfc_supported_card_plugin.h" + +#include +#include +#include + +#include + +static const MfDesfireApplicationId opal_app_id = {.data = {0x31, 0x45, 0x53}}; + +static const MfDesfireFileId opal_file_id = 0x07; + +static const char* opal_modes[5] = + {"Rail / Metro", "Ferry / Light Rail", "Bus", "Unknown mode", "Manly Ferry"}; + +static const char* opal_usages[14] = { + "New / Unused", + "Tap on: new journey", + "Tap on: transfer from same mode", + "Tap on: transfer from other mode", + NULL, // Manly Ferry: new journey + NULL, // Manly Ferry: transfer from ferry + NULL, // Manly Ferry: transfer from other + "Tap off: distance fare", + "Tap off: flat fare", + "Automated tap off: failed to tap off", + "Tap off: end of trip without start", + "Tap off: reversal", + "Tap on: rejected", + "Unknown usage", +}; + +// Opal file 0x7 structure. Assumes a little-endian CPU. +typedef struct __attribute__((__packed__)) { + uint32_t serial : 32; + uint8_t check_digit : 4; + bool blocked : 1; + uint16_t txn_number : 16; + int32_t balance : 21; + uint16_t days : 15; + uint16_t minutes : 11; + uint8_t mode : 3; + uint16_t usage : 4; + bool auto_topup : 1; + uint8_t weekly_journeys : 4; + uint16_t checksum : 16; +} OpalFile; + +static_assert(sizeof(OpalFile) == 16, "OpalFile"); + +// Converts an Opal timestamp to FuriHalRtcDateTime. +// +// Opal measures days since 1980-01-01 and minutes since midnight, and presumes +// all days are 1440 minutes. +static void opal_date_time_to_furi(uint16_t days, uint16_t minutes, FuriHalRtcDateTime* out) { + out->year = 1980; + out->month = 1; + // 1980-01-01 is a Tuesday + out->weekday = ((days + 1) % 7) + 1; + out->hour = minutes / 60; + out->minute = minutes % 60; + out->second = 0; + + // What year is it? + for(;;) { + const uint16_t num_days_in_year = furi_hal_rtc_get_days_per_year(out->year); + if(days < num_days_in_year) break; + days -= num_days_in_year; + out->year++; + } + + // 1-index the day of the year + days++; + + for(;;) { + // What month is it? + const bool is_leap = furi_hal_rtc_is_leap_year(out->year); + const uint8_t num_days_in_month = furi_hal_rtc_get_days_per_month(is_leap, out->month); + if(days <= num_days_in_month) break; + days -= num_days_in_month; + out->month++; + } + + out->day = days; +} + +static bool opal_parse(const NfcDevice* device, FuriString* parsed_data) { + furi_assert(device); + furi_assert(parsed_data); + + const MfDesfireData* data = nfc_device_get_data(device, NfcProtocolMfDesfire); + + bool parsed = false; + + do { + const MfDesfireApplication* app = mf_desfire_get_application(data, &opal_app_id); + if(app == NULL) break; + + const MfDesfireFileSettings* file_settings = + mf_desfire_get_file_settings(app, &opal_file_id); + if(file_settings == NULL || file_settings->type != MfDesfireFileTypeStandard || + file_settings->data.size != sizeof(OpalFile)) + break; + + const MfDesfireFileData* file_data = mf_desfire_get_file_data(app, &opal_file_id); + if(file_data == NULL) break; + + const OpalFile* opal_file = simple_array_cget_data(file_data->data); + + const uint8_t serial2 = opal_file->serial / 10000000; + const uint16_t serial3 = (opal_file->serial / 1000) % 10000; + const uint16_t serial4 = (opal_file->serial % 1000); + + if(opal_file->check_digit > 9) break; + + // Negative balance. Make this a positive value again and record the + // sign separately, because then we can handle balances of -99..-1 + // cents, as the "dollars" division below would result in a positive + // zero value. + const bool is_negative_balance = (opal_file->balance < 0); + const char* sign = is_negative_balance ? "-" : ""; + const int32_t balance = is_negative_balance ? labs(opal_file->balance) : //-V1081 + opal_file->balance; + const uint8_t balance_cents = balance % 100; + const int32_t balance_dollars = balance / 100; + + FuriHalRtcDateTime timestamp; + opal_date_time_to_furi(opal_file->days, opal_file->minutes, ×tamp); + + // Usages 4..6 associated with the Manly Ferry, which correspond to + // usages 1..3 for other modes. + const bool is_manly_ferry = (opal_file->usage >= 4) && (opal_file->usage <= 6); + + // 3..7 are "reserved", but we use 4 to indicate the Manly Ferry. + const uint8_t mode = is_manly_ferry ? 4 : opal_file->mode; + const uint8_t usage = is_manly_ferry ? opal_file->usage - 3 : opal_file->usage; + + const char* mode_str = opal_modes[mode > 4 ? 3 : mode]; + const char* usage_str = opal_usages[usage > 12 ? 13 : usage]; + + furi_string_printf( + parsed_data, + "\e#Opal: $%s%ld.%02hu\n3085 22%02hhu %04hu %03hu%01hhu\n%s, %s\n", + sign, + balance_dollars, + balance_cents, + serial2, + serial3, + serial4, + opal_file->check_digit, + mode_str, + usage_str); + + FuriString* timestamp_str = furi_string_alloc(); + + locale_format_date(timestamp_str, ×tamp, locale_get_date_format(), "-"); + furi_string_cat(parsed_data, timestamp_str); + furi_string_cat(parsed_data, " at "); + + locale_format_time(timestamp_str, ×tamp, locale_get_time_format(), false); + furi_string_cat(parsed_data, timestamp_str); + + furi_string_free(timestamp_str); + + furi_string_cat_printf( + parsed_data, + "\nWeekly journeys: %hhu, Txn #%hu\n", + opal_file->weekly_journeys, + opal_file->txn_number); + + if(opal_file->auto_topup) { + furi_string_cat_str(parsed_data, "Auto-topup enabled\n"); + } + + if(opal_file->blocked) { + furi_string_cat_str(parsed_data, "Card blocked\n"); + } + + parsed = true; + } while(false); + + return parsed; +} + +/* Actual implementation of app<>plugin interface */ +static const NfcSupportedCardsPlugin opal_plugin = { + .protocol = NfcProtocolMfDesfire, + .verify = NULL, + .read = NULL, + .parse = opal_parse, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor opal_plugin_descriptor = { + .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID, + .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION, + .entry_point = &opal_plugin, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* opal_plugin_ep() { + return &opal_plugin_descriptor; +} diff --git a/applications/main/nfc/plugins/supported_cards/plantain.c b/applications/main/nfc/plugins/supported_cards/plantain.c new file mode 100644 index 00000000000..cb8c0093d04 --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/plantain.c @@ -0,0 +1,219 @@ +#include "nfc_supported_card_plugin.h" + +#include + +#include +#include +#include + +#define TAG "Plantain" + +typedef struct { + uint64_t a; + uint64_t b; +} MfClassicKeyPair; + +typedef struct { + const MfClassicKeyPair* keys; + uint32_t data_sector; +} PlantainCardConfig; + +static const MfClassicKeyPair plantain_1k_keys[] = { + {.a = 0xffffffffffff, .b = 0xffffffffffff}, + {.a = 0xffffffffffff, .b = 0xffffffffffff}, + {.a = 0xffffffffffff, .b = 0xffffffffffff}, + {.a = 0xffffffffffff, .b = 0xffffffffffff}, + {.a = 0xe56ac127dd45, .b = 0x19fc84a3784b}, + {.a = 0x77dabc9825e1, .b = 0x9764fec3154a}, + {.a = 0xffffffffffff, .b = 0xffffffffffff}, + {.a = 0xffffffffffff, .b = 0xffffffffffff}, + {.a = 0x26973ea74321, .b = 0xd27058c6e2c7}, + {.a = 0xeb0a8ff88ade, .b = 0x578a9ada41e3}, + {.a = 0xea0fd73cb149, .b = 0x29c35fa068fb}, + {.a = 0xc76bf71a2509, .b = 0x9ba241db3f56}, + {.a = 0xacffffffffff, .b = 0x71f3a315ad26}, + {.a = 0xffffffffffff, .b = 0xffffffffffff}, + {.a = 0xffffffffffff, .b = 0xffffffffffff}, + {.a = 0xffffffffffff, .b = 0xffffffffffff}, +}; + +static const MfClassicKeyPair plantain_4k_keys[] = { + {.a = 0xffffffffffff, .b = 0xffffffffffff}, {.a = 0xffffffffffff, .b = 0xffffffffffff}, + {.a = 0xffffffffffff, .b = 0xffffffffffff}, {.a = 0xffffffffffff, .b = 0xffffffffffff}, + {.a = 0xe56ac127dd45, .b = 0x19fc84a3784b}, {.a = 0x77dabc9825e1, .b = 0x9764fec3154a}, + {.a = 0xffffffffffff, .b = 0xffffffffffff}, {.a = 0xffffffffffff, .b = 0xffffffffffff}, + {.a = 0x26973ea74321, .b = 0xd27058c6e2c7}, {.a = 0xeb0a8ff88ade, .b = 0x578a9ada41e3}, + {.a = 0xea0fd73cb149, .b = 0x29c35fa068fb}, {.a = 0xc76bf71a2509, .b = 0x9ba241db3f56}, + {.a = 0xacffffffffff, .b = 0x71f3a315ad26}, {.a = 0xffffffffffff, .b = 0xffffffffffff}, + {.a = 0xffffffffffff, .b = 0xffffffffffff}, {.a = 0xffffffffffff, .b = 0xffffffffffff}, + {.a = 0x72f96bdd3714, .b = 0x462225cd34cf}, {.a = 0x044ce1872bc3, .b = 0x8c90c70cff4a}, + {.a = 0xbc2d1791dec1, .b = 0xca96a487de0b}, {.a = 0x8791b2ccb5c4, .b = 0xc956c3b80da3}, + {.a = 0x8e26e45e7d65, .b = 0x8e65b3af7d22}, {.a = 0x0f318130ed18, .b = 0x0c420a20e056}, + {.a = 0x045ceca15535, .b = 0x31bec3d9e510}, {.a = 0x9d993c5d4ef4, .b = 0x86120e488abf}, + {.a = 0xc65d4eaa645b, .b = 0xb69d40d1a439}, {.a = 0x3a8a139c20b4, .b = 0x8818a9c5d406}, + {.a = 0xbaff3053b496, .b = 0x4b7cb25354d3}, {.a = 0x7413b599c4ea, .b = 0xb0a2AAF3A1BA}, + {.a = 0x0ce7cd2cc72b, .b = 0xfa1fbb3f0f1f}, {.a = 0x0be5fac8b06a, .b = 0x6f95887a4fd3}, + {.a = 0x0eb23cc8110b, .b = 0x04dc35277635}, {.a = 0xbc4580b7f20b, .b = 0xd0a4131fb290}, + {.a = 0x7a396f0d633d, .b = 0xad2bdc097023}, {.a = 0xa3faa6daff67, .b = 0x7600e889adf9}, + {.a = 0xfd8705e721b0, .b = 0x296fc317a513}, {.a = 0x22052b480d11, .b = 0xe19504c39461}, + {.a = 0xa7141147d430, .b = 0xff16014fefc7}, {.a = 0x8a8d88151a00, .b = 0x038b5f9b5a2a}, + {.a = 0xb27addfb64b0, .b = 0x152fd0c420a7}, {.a = 0x7259fa0197c6, .b = 0x5583698df085}, +}; + +static bool plantain_get_card_config(PlantainCardConfig* config, MfClassicType type) { + bool success = true; + + if(type == MfClassicType1k) { + config->data_sector = 8; + config->keys = plantain_1k_keys; + } else if(type == MfClassicType4k) { + config->data_sector = 8; + config->keys = plantain_4k_keys; + } else { + success = false; + } + + return success; +} + +static bool plantain_verify_type(Nfc* nfc, MfClassicType type) { + bool verified = false; + + do { + PlantainCardConfig cfg = {}; + if(!plantain_get_card_config(&cfg, type)) break; + + const uint8_t block_num = mf_classic_get_first_block_num_of_sector(cfg.data_sector); + FURI_LOG_D(TAG, "Verifying sector %lu", cfg.data_sector); + + MfClassicKey key = {0}; + nfc_util_num2bytes(cfg.keys[cfg.data_sector].a, COUNT_OF(key.data), key.data); + + MfClassicAuthContext auth_context; + MfClassicError error = + mf_classic_poller_auth(nfc, block_num, &key, MfClassicKeyTypeA, &auth_context); + if(error != MfClassicErrorNone) { + FURI_LOG_D(TAG, "Failed to read block %u: %d", block_num, error); + break; + } + + verified = true; + } while(false); + + return verified; +} + +static bool plantain_verify(Nfc* nfc) { + return plantain_verify_type(nfc, MfClassicType1k) || + plantain_verify_type(nfc, MfClassicType4k); +} + +static bool plantain_read(Nfc* nfc, NfcDevice* device) { + furi_assert(nfc); + furi_assert(device); + + bool is_read = false; + + MfClassicData* data = mf_classic_alloc(); + nfc_device_copy_data(device, NfcProtocolMfClassic, data); + + do { + MfClassicType type = MfClassicTypeMini; + MfClassicError error = mf_classic_poller_detect_type(nfc, &type); + if(error != MfClassicErrorNone) break; + + data->type = type; + PlantainCardConfig cfg = {}; + if(!plantain_get_card_config(&cfg, data->type)) break; + + MfClassicDeviceKeys keys = {}; + for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { + nfc_util_num2bytes(cfg.keys[i].a, sizeof(MfClassicKey), keys.key_a[i].data); + FURI_BIT_SET(keys.key_a_mask, i); + nfc_util_num2bytes(cfg.keys[i].b, sizeof(MfClassicKey), keys.key_b[i].data); + FURI_BIT_SET(keys.key_b_mask, i); + } + + error = mf_classic_poller_read(nfc, &keys, data); + if(error != MfClassicErrorNone) { + FURI_LOG_W(TAG, "Failed to read data"); + break; + } + + nfc_device_set_data(device, NfcProtocolMfClassic, data); + + is_read = true; + } while(false); + + mf_classic_free(data); + + return is_read; +} + +static bool plantain_parse(const NfcDevice* device, FuriString* parsed_data) { + furi_assert(device); + + const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic); + + bool parsed = false; + + do { + // Verify card type + PlantainCardConfig cfg = {}; + if(!plantain_get_card_config(&cfg, data->type)) break; + + // Verify key + const MfClassicSectorTrailer* sec_tr = + mf_classic_get_sector_trailer_by_sector(data, cfg.data_sector); + + const uint64_t key = nfc_util_bytes2num(sec_tr->key_a.data, COUNT_OF(sec_tr->key_a.data)); + if(key != cfg.keys[cfg.data_sector].a) break; + + // Point to block 0 of sector 4, value 0 + const uint8_t* temp_ptr = data->block[16].data; + // Read first 4 bytes of block 0 of sector 4 from last to first and convert them to uint32_t + // 38 18 00 00 becomes 00 00 18 38, and equals to 6200 decimal + uint32_t balance = + ((temp_ptr[3] << 24) | (temp_ptr[2] << 16) | (temp_ptr[1] << 8) | temp_ptr[0]) / 100; + // Read card number + // Point to block 0 of sector 0, value 0 + temp_ptr = data->block[0].data; + // Read first 7 bytes of block 0 of sector 0 from last to first and convert them to uint64_t + // 04 31 16 8A 23 5C 80 becomes 80 5C 23 8A 16 31 04, and equals to 36130104729284868 decimal + uint8_t card_number_arr[7]; + for(size_t i = 0; i < 7; i++) { + card_number_arr[i] = temp_ptr[6 - i]; + } + // Copy card number to uint64_t + uint64_t card_number = 0; + for(size_t i = 0; i < 7; i++) { + card_number = (card_number << 8) | card_number_arr[i]; + } + + furi_string_printf( + parsed_data, "\e#Plantain\nN:%llu-\nBalance:%lu\n", card_number, balance); + parsed = true; + } while(false); + + return parsed; +} + +/* Actual implementation of app<>plugin interface */ +static const NfcSupportedCardsPlugin plantain_plugin = { + .protocol = NfcProtocolMfClassic, + .verify = plantain_verify, + .read = plantain_read, + .parse = plantain_parse, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor plantain_plugin_descriptor = { + .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID, + .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION, + .entry_point = &plantain_plugin, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* plantain_plugin_ep() { + return &plantain_plugin_descriptor; +} diff --git a/applications/main/nfc/plugins/supported_cards/troika.c b/applications/main/nfc/plugins/supported_cards/troika.c new file mode 100644 index 00000000000..d42b977c6cf --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/troika.c @@ -0,0 +1,214 @@ +#include "nfc_supported_card_plugin.h" + +#include + +#include +#include +#include + +#define TAG "Troika" + +typedef struct { + uint64_t a; + uint64_t b; +} MfClassicKeyPair; + +typedef struct { + const MfClassicKeyPair* keys; + uint32_t data_sector; +} TroikaCardConfig; + +static const MfClassicKeyPair troika_1k_keys[] = { + {.a = 0xa0a1a2a3a4a5, .b = 0xfbf225dc5d58}, + {.a = 0xa82607b01c0d, .b = 0x2910989b6880}, + {.a = 0x2aa05ed1856f, .b = 0xeaac88e5dc99}, + {.a = 0x2aa05ed1856f, .b = 0xeaac88e5dc99}, + {.a = 0x73068f118c13, .b = 0x2b7f3253fac5}, + {.a = 0xfbc2793d540b, .b = 0xd3a297dc2698}, + {.a = 0x2aa05ed1856f, .b = 0xeaac88e5dc99}, + {.a = 0xae3d65a3dad4, .b = 0x0f1c63013dba}, + {.a = 0xa73f5dc1d333, .b = 0xe35173494a81}, + {.a = 0x69a32f1c2f19, .b = 0x6b8bd9860763}, + {.a = 0x9becdf3d9273, .b = 0xf8493407799d}, + {.a = 0x08b386463229, .b = 0x5efbaecef46b}, + {.a = 0xcd4c61c26e3d, .b = 0x31c7610de3b0}, + {.a = 0xa82607b01c0d, .b = 0x2910989b6880}, + {.a = 0x0e8f64340ba4, .b = 0x4acec1205d75}, + {.a = 0x2aa05ed1856f, .b = 0xeaac88e5dc99}, +}; + +static const MfClassicKeyPair troika_4k_keys[] = { + {.a = 0xa0a1a2a3a4a5, .b = 0xfbf225dc5d58}, {.a = 0xa82607b01c0d, .b = 0x2910989b6880}, + {.a = 0x2aa05ed1856f, .b = 0xeaac88e5dc99}, {.a = 0x2aa05ed1856f, .b = 0xeaac88e5dc99}, + {.a = 0x73068f118c13, .b = 0x2b7f3253fac5}, {.a = 0xfbc2793d540b, .b = 0xd3a297dc2698}, + {.a = 0x2aa05ed1856f, .b = 0xeaac88e5dc99}, {.a = 0xae3d65a3dad4, .b = 0x0f1c63013dbb}, + {.a = 0xa73f5dc1d333, .b = 0xe35173494a81}, {.a = 0x69a32f1c2f19, .b = 0x6b8bd9860763}, + {.a = 0x9becdf3d9273, .b = 0xf8493407799d}, {.a = 0x08b386463229, .b = 0x5efbaecef46b}, + {.a = 0xcd4c61c26e3d, .b = 0x31c7610de3b0}, {.a = 0xa82607b01c0d, .b = 0x2910989b6880}, + {.a = 0x0e8f64340ba4, .b = 0x4acec1205d75}, {.a = 0x2aa05ed1856f, .b = 0xeaac88e5dc99}, + {.a = 0x6b02733bb6ec, .b = 0x7038cd25c408}, {.a = 0x403d706ba880, .b = 0xb39d19a280df}, + {.a = 0xc11f4597efb5, .b = 0x70d901648cb9}, {.a = 0x0db520c78c1c, .b = 0x73e5b9d9d3a4}, + {.a = 0x3ebce0925b2f, .b = 0x372cc880f216}, {.a = 0x16a27af45407, .b = 0x9868925175ba}, + {.a = 0xaba208516740, .b = 0xce26ecb95252}, {.a = 0xcd64e567abcd, .b = 0x8f79c4fd8a01}, + {.a = 0x764cd061f1e6, .b = 0xa74332f74994}, {.a = 0x1cc219e9fec1, .b = 0xb90de525ceb6}, + {.a = 0x2fe3cb83ea43, .b = 0xfba88f109b32}, {.a = 0x07894ffec1d6, .b = 0xefcb0e689db3}, + {.a = 0x04c297b91308, .b = 0xc8454c154cb5}, {.a = 0x7a38e3511a38, .b = 0xab16584c972a}, + {.a = 0x7545df809202, .b = 0xecf751084a80}, {.a = 0x5125974cd391, .b = 0xd3eafb5df46d}, + {.a = 0x7a86aa203788, .b = 0xe41242278ca2}, {.a = 0xafcef64c9913, .b = 0x9db96dca4324}, + {.a = 0x04eaa462f70b, .b = 0xac17b93e2fae}, {.a = 0xe734c210f27e, .b = 0x29ba8c3e9fda}, + {.a = 0xd5524f591eed, .b = 0x5daf42861b4d}, {.a = 0xe4821a377b75, .b = 0xe8709e486465}, + {.a = 0x518dc6eea089, .b = 0x97c64ac98ca4}, {.a = 0xbb52f8cce07f, .b = 0x6b6119752c70}, +}; + +static bool troika_get_card_config(TroikaCardConfig* config, MfClassicType type) { + bool success = true; + + if(type == MfClassicType1k) { + config->data_sector = 8; + config->keys = troika_1k_keys; + } else if(type == MfClassicType4k) { + config->data_sector = 4; + config->keys = troika_4k_keys; + } else { + success = false; + } + + return success; +} + +static bool troika_verify_type(Nfc* nfc, MfClassicType type) { + bool verified = false; + + do { + TroikaCardConfig cfg = {}; + if(!troika_get_card_config(&cfg, type)) break; + + const uint8_t block_num = mf_classic_get_first_block_num_of_sector(cfg.data_sector); + FURI_LOG_D(TAG, "Verifying sector %lu", cfg.data_sector); + + MfClassicKey key = {0}; + nfc_util_num2bytes(cfg.keys[cfg.data_sector].a, COUNT_OF(key.data), key.data); + + MfClassicAuthContext auth_context; + MfClassicError error = + mf_classic_poller_auth(nfc, block_num, &key, MfClassicKeyTypeA, &auth_context); + if(error != MfClassicErrorNone) { + FURI_LOG_D(TAG, "Failed to read block %u: %d", block_num, error); + break; + } + + verified = true; + } while(false); + + return verified; +} + +static bool troika_verify(Nfc* nfc) { + return troika_verify_type(nfc, MfClassicType1k) || troika_verify_type(nfc, MfClassicType4k); +} + +static bool troika_read(Nfc* nfc, NfcDevice* device) { + furi_assert(nfc); + furi_assert(device); + + bool is_read = false; + + MfClassicData* data = mf_classic_alloc(); + nfc_device_copy_data(device, NfcProtocolMfClassic, data); + + do { + MfClassicType type = MfClassicTypeMini; + MfClassicError error = mf_classic_poller_detect_type(nfc, &type); + if(error != MfClassicErrorNone) break; + + data->type = type; + TroikaCardConfig cfg = {}; + if(!troika_get_card_config(&cfg, data->type)) break; + + MfClassicDeviceKeys keys = { + .key_a_mask = 0, + .key_b_mask = 0, + }; + for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { + nfc_util_num2bytes(cfg.keys[i].a, sizeof(MfClassicKey), keys.key_a[i].data); + FURI_BIT_SET(keys.key_a_mask, i); + nfc_util_num2bytes(cfg.keys[i].b, sizeof(MfClassicKey), keys.key_b[i].data); + FURI_BIT_SET(keys.key_b_mask, i); + } + + error = mf_classic_poller_read(nfc, &keys, data); + if(error != MfClassicErrorNone) { + FURI_LOG_W(TAG, "Failed to read data"); + break; + } + + nfc_device_set_data(device, NfcProtocolMfClassic, data); + + is_read = true; + } while(false); + + mf_classic_free(data); + + return is_read; +} + +static bool troika_parse(const NfcDevice* device, FuriString* parsed_data) { + furi_assert(device); + + const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic); + + bool parsed = false; + + do { + // Verify card type + TroikaCardConfig cfg = {}; + if(!troika_get_card_config(&cfg, data->type)) break; + + // Verify key + const MfClassicSectorTrailer* sec_tr = + mf_classic_get_sector_trailer_by_sector(data, cfg.data_sector); + + const uint64_t key = nfc_util_bytes2num(sec_tr->key_a.data, COUNT_OF(sec_tr->key_a.data)); + if(key != cfg.keys[cfg.data_sector].a) break; + + // Parse data + const uint8_t start_block_num = mf_classic_get_first_block_num_of_sector(cfg.data_sector); + + const uint8_t* temp_ptr = &data->block[start_block_num + 1].data[5]; + uint16_t balance = ((temp_ptr[0] << 8) | temp_ptr[1]) / 25; + temp_ptr = &data->block[start_block_num].data[2]; + + uint32_t number = 0; + for(size_t i = 1; i < 5; i++) { + number <<= 8; + number |= temp_ptr[i]; + } + number >>= 4; + number |= (temp_ptr[0] & 0xf) << 28; + + furi_string_printf(parsed_data, "\e#Troika\nNum: %lu\nBalance: %u RUR", number, balance); + parsed = true; + } while(false); + + return parsed; +} + +/* Actual implementation of app<>plugin interface */ +static const NfcSupportedCardsPlugin troika_plugin = { + .protocol = NfcProtocolMfClassic, + .verify = troika_verify, + .read = troika_read, + .parse = troika_parse, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor troika_plugin_descriptor = { + .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID, + .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION, + .entry_point = &troika_plugin, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* troika_plugin_ep() { + return &troika_plugin_descriptor; +} diff --git a/applications/main/nfc/plugins/supported_cards/two_cities.c b/applications/main/nfc/plugins/supported_cards/two_cities.c new file mode 100644 index 00000000000..fb964103ee8 --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/two_cities.c @@ -0,0 +1,189 @@ +#include "nfc_supported_card_plugin.h" + +#include + +#include +#include +#include + +#define TAG "TwoCities" + +typedef struct { + uint64_t a; + uint64_t b; +} MfClassicKeyPair; + +static const MfClassicKeyPair two_cities_4k_keys[] = { + {.a = 0xffffffffffff, .b = 0xffffffffffff}, {.a = 0xffffffffffff, .b = 0xffffffffffff}, + {.a = 0x2aa05ed1856f, .b = 0xeaac88e5dc99}, {.a = 0x2aa05ed1856f, .b = 0xeaac88e5dc99}, + {.a = 0xe56ac127dd45, .b = 0x19fc84a3784b}, {.a = 0x77dabc9825e1, .b = 0x9764fec3154a}, + {.a = 0x2aa05ed1856f, .b = 0xeaac88e5dc99}, {.a = 0xffffffffffff, .b = 0xffffffffffff}, + {.a = 0xa73f5dc1d333, .b = 0xe35173494a81}, {.a = 0x69a32f1c2f19, .b = 0x6b8bd9860763}, + {.a = 0xea0fd73cb149, .b = 0x29c35fa068fb}, {.a = 0xc76bf71a2509, .b = 0x9ba241db3f56}, + {.a = 0xacffffffffff, .b = 0x71f3a315ad26}, {.a = 0xffffffffffff, .b = 0xffffffffffff}, + {.a = 0xffffffffffff, .b = 0xffffffffffff}, {.a = 0x2aa05ed1856f, .b = 0xeaac88e5dc99}, + {.a = 0x72f96bdd3714, .b = 0x462225cd34cf}, {.a = 0x044ce1872bc3, .b = 0x8c90c70cff4a}, + {.a = 0xbc2d1791dec1, .b = 0xca96a487de0b}, {.a = 0x8791b2ccb5c4, .b = 0xc956c3b80da3}, + {.a = 0x8e26e45e7d65, .b = 0x8e65b3af7d22}, {.a = 0x0f318130ed18, .b = 0x0c420a20e056}, + {.a = 0x045ceca15535, .b = 0x31bec3d9e510}, {.a = 0x9d993c5d4ef4, .b = 0x86120e488abf}, + {.a = 0xc65d4eaa645b, .b = 0xb69d40d1a439}, {.a = 0x3a8a139c20b4, .b = 0x8818a9c5d406}, + {.a = 0xbaff3053b496, .b = 0x4b7cb25354d3}, {.a = 0x7413b599c4ea, .b = 0xb0a2AAF3A1BA}, + {.a = 0x0ce7cd2cc72b, .b = 0xfa1fbb3f0f1f}, {.a = 0x0be5fac8b06a, .b = 0x6f95887a4fd3}, + {.a = 0x26973ea74321, .b = 0xd27058c6e2c7}, {.a = 0xeb0a8ff88ade, .b = 0x578a9ada41e3}, + {.a = 0x7a396f0d633d, .b = 0xad2bdc097023}, {.a = 0xa3faa6daff67, .b = 0x7600e889adf9}, + {.a = 0x2aa05ed1856f, .b = 0xeaac88e5dc99}, {.a = 0x2aa05ed1856f, .b = 0xeaac88e5dc99}, + {.a = 0xa7141147d430, .b = 0xff16014fefc7}, {.a = 0x8a8d88151a00, .b = 0x038b5f9b5a2a}, + {.a = 0xb27addfb64b0, .b = 0x152fd0c420a7}, {.a = 0x7259fa0197c6, .b = 0x5583698df085}, +}; + +bool two_cities_verify(Nfc* nfc) { + bool verified = false; + + do { + const uint8_t verify_sector = 4; + uint8_t block_num = mf_classic_get_first_block_num_of_sector(verify_sector); + FURI_LOG_D(TAG, "Verifying sector %u", verify_sector); + + MfClassicKey key = {}; + nfc_util_num2bytes(two_cities_4k_keys[verify_sector].a, COUNT_OF(key.data), key.data); + + MfClassicAuthContext auth_ctx = {}; + MfClassicError error = + mf_classic_poller_auth(nfc, block_num, &key, MfClassicKeyTypeA, &auth_ctx); + if(error != MfClassicErrorNone) { + FURI_LOG_D(TAG, "Failed to read block %u: %d", block_num, error); + break; + } + + verified = true; + } while(false); + + return verified; +} + +static bool two_cities_read(Nfc* nfc, NfcDevice* device) { + furi_assert(nfc); + furi_assert(device); + + bool is_read = false; + + MfClassicData* data = mf_classic_alloc(); + nfc_device_copy_data(device, NfcProtocolMfClassic, data); + + do { + MfClassicType type = MfClassicTypeMini; + MfClassicError error = mf_classic_poller_detect_type(nfc, &type); + if(error != MfClassicErrorNone) break; + + data->type = type; + MfClassicDeviceKeys keys = {}; + for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { + nfc_util_num2bytes(two_cities_4k_keys[i].a, sizeof(MfClassicKey), keys.key_a[i].data); + FURI_BIT_SET(keys.key_a_mask, i); + nfc_util_num2bytes(two_cities_4k_keys[i].b, sizeof(MfClassicKey), keys.key_b[i].data); + FURI_BIT_SET(keys.key_b_mask, i); + } + + error = mf_classic_poller_read(nfc, &keys, data); + if(error != MfClassicErrorNone) { + FURI_LOG_W(TAG, "Failed to read data"); + break; + } + + nfc_device_set_data(device, NfcProtocolMfClassic, data); + + is_read = true; + } while(false); + + mf_classic_free(data); + + return is_read; +} + +static bool two_cities_parse(const NfcDevice* device, FuriString* parsed_data) { + furi_assert(device); + + const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic); + + bool parsed = false; + + do { + // Verify key + MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, 4); + uint64_t key = nfc_util_bytes2num(sec_tr->key_a.data, 6); + if(key != two_cities_4k_keys[4].a) return false; + + // ===== + // PLANTAIN + // ===== + + // Point to block 0 of sector 4, value 0 + const uint8_t* temp_ptr = data->block[16].data; + // Read first 4 bytes of block 0 of sector 4 from last to first and convert them to uint32_t + // 38 18 00 00 becomes 00 00 18 38, and equals to 6200 decimal + uint32_t balance = + ((temp_ptr[3] << 24) | (temp_ptr[2] << 16) | (temp_ptr[1] << 8) | temp_ptr[0]) / 100; + // Read card number + // Point to block 0 of sector 0, value 0 + temp_ptr = data->block[0].data; + // Read first 7 bytes of block 0 of sector 0 from last to first and convert them to uint64_t + // 04 31 16 8A 23 5C 80 becomes 80 5C 23 8A 16 31 04, and equals to 36130104729284868 decimal + uint8_t card_number_arr[7]; + for(size_t i = 0; i < 7; i++) { + card_number_arr[i] = temp_ptr[6 - i]; + } + // Copy card number to uint64_t + uint64_t card_number = 0; + for(size_t i = 0; i < 7; i++) { + card_number = (card_number << 8) | card_number_arr[i]; + } + + // ===== + // --PLANTAIN-- + // ===== + // TROIKA + // ===== + + const uint8_t* troika_temp_ptr = &data->block[33].data[5]; + uint16_t troika_balance = ((troika_temp_ptr[0] << 8) | troika_temp_ptr[1]) / 25; + troika_temp_ptr = &data->block[32].data[2]; + uint32_t troika_number = 0; + for(size_t i = 0; i < 4; i++) { + troika_number <<= 8; + troika_number |= troika_temp_ptr[i]; + } + troika_number >>= 4; + + furi_string_printf( + parsed_data, + "\e#Troika+Plantain\nPN: %llu-\nPB: %lu rur.\nTN: %lu\nTB: %u rur.\n", + card_number, + balance, + troika_number, + troika_balance); + + parsed = true; + } while(false); + + return parsed; +} + +/* Actual implementation of app<>plugin interface */ +static const NfcSupportedCardsPlugin two_cities_plugin = { + .protocol = NfcProtocolMfClassic, + .verify = two_cities_verify, + .read = two_cities_read, + .parse = two_cities_parse, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor two_cities_plugin_descriptor = { + .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID, + .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION, + .entry_point = &two_cities_plugin, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* two_cities_plugin_ep() { + return &two_cities_plugin_descriptor; +} diff --git a/applications/main/nfc/scenes/nfc_scene_config.h b/applications/main/nfc/scenes/nfc_scene_config.h index f11d1479838..f415c66a6fa 100644 --- a/applications/main/nfc/scenes/nfc_scene_config.h +++ b/applications/main/nfc/scenes/nfc_scene_config.h @@ -1,72 +1,57 @@ ADD_SCENE(nfc, start, Start) -ADD_SCENE(nfc, read, Read) +ADD_SCENE(nfc, file_select, FileSelect) ADD_SCENE(nfc, saved_menu, SavedMenu) -ADD_SCENE(nfc, extra_actions, ExtraActions) -ADD_SCENE(nfc, set_type, SetType) -ADD_SCENE(nfc, set_sak, SetSak) -ADD_SCENE(nfc, set_atqa, SetAtqa) -ADD_SCENE(nfc, set_uid, SetUid) -ADD_SCENE(nfc, generate_info, GenerateInfo) -ADD_SCENE(nfc, read_card_success, ReadCardSuccess) ADD_SCENE(nfc, save_name, SaveName) ADD_SCENE(nfc, save_success, SaveSuccess) -ADD_SCENE(nfc, file_select, FileSelect) -ADD_SCENE(nfc, emulate_uid, EmulateUid) -ADD_SCENE(nfc, nfca_read_success, NfcaReadSuccess) -ADD_SCENE(nfc, nfca_menu, NfcaMenu) -ADD_SCENE(nfc, nfcv_menu, NfcVMenu) -ADD_SCENE(nfc, nfcv_unlock_menu, NfcVUnlockMenu) -ADD_SCENE(nfc, nfcv_key_input, NfcVKeyInput) -ADD_SCENE(nfc, nfcv_unlock, NfcVUnlock) -ADD_SCENE(nfc, nfcv_emulate, NfcVEmulate) -ADD_SCENE(nfc, nfcv_sniff, NfcVSniff) -ADD_SCENE(nfc, nfcv_read_success, NfcVReadSuccess) -ADD_SCENE(nfc, mf_ultralight_read_success, MfUltralightReadSuccess) -ADD_SCENE(nfc, mf_ultralight_data, MfUltralightData) -ADD_SCENE(nfc, mf_ultralight_menu, MfUltralightMenu) -ADD_SCENE(nfc, mf_ultralight_emulate, MfUltralightEmulate) -ADD_SCENE(nfc, mf_ultralight_read_auth, MfUltralightReadAuth) -ADD_SCENE(nfc, mf_ultralight_read_auth_result, MfUltralightReadAuthResult) -ADD_SCENE(nfc, mf_ultralight_key_input, MfUltralightKeyInput) -ADD_SCENE(nfc, mf_ultralight_unlock_auto, MfUltralightUnlockAuto) -ADD_SCENE(nfc, mf_ultralight_unlock_menu, MfUltralightUnlockMenu) -ADD_SCENE(nfc, mf_ultralight_unlock_warn, MfUltralightUnlockWarn) -ADD_SCENE(nfc, mf_desfire_read_success, MfDesfireReadSuccess) -ADD_SCENE(nfc, mf_desfire_menu, MfDesfireMenu) -ADD_SCENE(nfc, mf_desfire_data, MfDesfireData) -ADD_SCENE(nfc, mf_desfire_app, MfDesfireApp) -ADD_SCENE(nfc, mf_classic_read_success, MfClassicReadSuccess) -ADD_SCENE(nfc, mf_classic_data, MfClassicData) -ADD_SCENE(nfc, mf_classic_menu, MfClassicMenu) -ADD_SCENE(nfc, mf_classic_emulate, MfClassicEmulate) -ADD_SCENE(nfc, mf_classic_keys, MfClassicKeys) -ADD_SCENE(nfc, mf_classic_keys_add, MfClassicKeysAdd) -ADD_SCENE(nfc, mf_classic_keys_list, MfClassicKeysList) -ADD_SCENE(nfc, mf_classic_keys_delete, MfClassicKeysDelete) -ADD_SCENE(nfc, mf_classic_keys_warn_duplicate, MfClassicKeysWarnDuplicate) -ADD_SCENE(nfc, mf_classic_dict_attack, MfClassicDictAttack) -ADD_SCENE(nfc, mf_classic_write, MfClassicWrite) -ADD_SCENE(nfc, mf_classic_write_success, MfClassicWriteSuccess) -ADD_SCENE(nfc, mf_classic_write_fail, MfClassicWriteFail) -ADD_SCENE(nfc, mf_classic_update, MfClassicUpdate) -ADD_SCENE(nfc, mf_classic_update_success, MfClassicUpdateSuccess) -ADD_SCENE(nfc, mf_classic_wrong_card, MfClassicWrongCard) -ADD_SCENE(nfc, emv_read_success, EmvReadSuccess) -ADD_SCENE(nfc, emv_menu, EmvMenu) -ADD_SCENE(nfc, emulate_apdu_sequence, EmulateApduSequence) -ADD_SCENE(nfc, device_info, DeviceInfo) ADD_SCENE(nfc, delete, Delete) ADD_SCENE(nfc, delete_success, DeleteSuccess) ADD_SCENE(nfc, restore_original_confirm, RestoreOriginalConfirm) ADD_SCENE(nfc, restore_original, RestoreOriginal) + +ADD_SCENE(nfc, detect, Detect) +ADD_SCENE(nfc, read, Read) +ADD_SCENE(nfc, info, Info) +ADD_SCENE(nfc, more_info, MoreInfo) +ADD_SCENE(nfc, supported_card, SupportedCard) +ADD_SCENE(nfc, select_protocol, SelectProtocol) +ADD_SCENE(nfc, extra_actions, ExtraActions) +ADD_SCENE(nfc, read_success, ReadSuccess) +ADD_SCENE(nfc, read_menu, ReadMenu) +ADD_SCENE(nfc, emulate, Emulate) +ADD_SCENE(nfc, rpc, Rpc) ADD_SCENE(nfc, debug, Debug) ADD_SCENE(nfc, field, Field) -ADD_SCENE(nfc, dict_not_found, DictNotFound) -ADD_SCENE(nfc, rpc, Rpc) -ADD_SCENE(nfc, exit_confirm, ExitConfirm) ADD_SCENE(nfc, retry_confirm, RetryConfirm) -ADD_SCENE(nfc, detect_reader, DetectReader) -ADD_SCENE(nfc, mfkey_nonces_info, MfkeyNoncesInfo) -ADD_SCENE(nfc, mfkey_complete, MfkeyComplete) -ADD_SCENE(nfc, nfc_data_info, NfcDataInfo) -ADD_SCENE(nfc, read_card_type, ReadCardType) +ADD_SCENE(nfc, exit_confirm, ExitConfirm) + +ADD_SCENE(nfc, mf_ultralight_unlock_menu, MfUltralightUnlockMenu) +ADD_SCENE(nfc, mf_ultralight_unlock_warn, MfUltralightUnlockWarn) +ADD_SCENE(nfc, mf_ultralight_key_input, MfUltralightKeyInput) +ADD_SCENE(nfc, mf_ultralight_capture_pass, MfUltralightCapturePass) + +ADD_SCENE(nfc, mf_desfire_more_info, MfDesfireMoreInfo) +ADD_SCENE(nfc, mf_desfire_app, MfDesfireApp) + +ADD_SCENE(nfc, mf_classic_dict_attack, MfClassicDictAttack) +ADD_SCENE(nfc, mf_classic_detect_reader, MfClassicDetectReader) +ADD_SCENE(nfc, mf_classic_mfkey_nonces_info, MfClassicMfkeyNoncesInfo) +ADD_SCENE(nfc, mf_classic_mfkey_complete, MfClassicMfkeyComplete) +ADD_SCENE(nfc, mf_classic_update_initial, MfClassicUpdateInitial) +ADD_SCENE(nfc, mf_classic_update_initial_success, MfClassicUpdateInitialSuccess) +ADD_SCENE(nfc, mf_classic_write_initial, MfClassicWriteInitial) +ADD_SCENE(nfc, mf_classic_write_initial_success, MfClassicWriteInitialSuccess) +ADD_SCENE(nfc, mf_classic_write_initial_fail, MfClassicWriteInitialFail) +ADD_SCENE(nfc, mf_classic_wrong_card, MfClassicWrongCard) + +ADD_SCENE(nfc, mf_classic_keys, MfClassicKeys) +ADD_SCENE(nfc, mf_classic_keys_list, MfClassicKeysList) +ADD_SCENE(nfc, mf_classic_keys_delete, MfClassicKeysDelete) +ADD_SCENE(nfc, mf_classic_keys_add, MfClassicKeysAdd) +ADD_SCENE(nfc, mf_classic_keys_warn_duplicate, MfClassicKeysWarnDuplicate) + +ADD_SCENE(nfc, set_type, SetType) +ADD_SCENE(nfc, set_sak, SetSak) +ADD_SCENE(nfc, set_atqa, SetAtqa) +ADD_SCENE(nfc, set_uid, SetUid) + +ADD_SCENE(nfc, generate_info, GenerateInfo) diff --git a/applications/main/nfc/scenes/nfc_scene_debug.c b/applications/main/nfc/scenes/nfc_scene_debug.c index ed079c2ed9a..97592f2e270 100644 --- a/applications/main/nfc/scenes/nfc_scene_debug.c +++ b/applications/main/nfc/scenes/nfc_scene_debug.c @@ -1,4 +1,4 @@ -#include "../nfc_i.h" +#include "../nfc_app_i.h" enum SubmenuDebugIndex { SubmenuDebugIndexField, @@ -6,29 +6,26 @@ enum SubmenuDebugIndex { }; void nfc_scene_debug_submenu_callback(void* context, uint32_t index) { - Nfc* nfc = context; + NfcApp* nfc = context; view_dispatcher_send_custom_event(nfc->view_dispatcher, index); } void nfc_scene_debug_on_enter(void* context) { - Nfc* nfc = context; + NfcApp* nfc = context; Submenu* submenu = nfc->submenu; submenu_add_item( submenu, "Field", SubmenuDebugIndexField, nfc_scene_debug_submenu_callback, nfc); - submenu_add_item( - submenu, "Apdu", SubmenuDebugIndexApdu, nfc_scene_debug_submenu_callback, nfc); submenu_set_selected_item( submenu, scene_manager_get_scene_state(nfc->scene_manager, NfcSceneDebug)); - nfc_device_clear(nfc->dev); view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); } bool nfc_scene_debug_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; + NfcApp* nfc = context; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { @@ -37,18 +34,13 @@ bool nfc_scene_debug_on_event(void* context, SceneManagerEvent event) { nfc->scene_manager, NfcSceneDebug, SubmenuDebugIndexField); scene_manager_next_scene(nfc->scene_manager, NfcSceneField); consumed = true; - } else if(event.event == SubmenuDebugIndexApdu) { - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneDebug, SubmenuDebugIndexApdu); - scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateApduSequence); - consumed = true; } } return consumed; } void nfc_scene_debug_on_exit(void* context) { - Nfc* nfc = context; + NfcApp* nfc = context; submenu_reset(nfc->submenu); } diff --git a/applications/main/nfc/scenes/nfc_scene_delete.c b/applications/main/nfc/scenes/nfc_scene_delete.c index 0808db45a32..c1a676168ad 100644 --- a/applications/main/nfc/scenes/nfc_scene_delete.c +++ b/applications/main/nfc/scenes/nfc_scene_delete.c @@ -1,21 +1,20 @@ -#include "../nfc_i.h" +#include "../nfc_app_i.h" void nfc_scene_delete_widget_callback(GuiButtonType result, InputType type, void* context) { - Nfc* nfc = context; + NfcApp* nfc = context; if(type == InputTypeShort) { view_dispatcher_send_custom_event(nfc->view_dispatcher, result); } } void nfc_scene_delete_on_enter(void* context) { - Nfc* nfc = context; - FuriHalNfcDevData* nfc_data = &nfc->dev->dev_data.nfc_data; + NfcApp* nfc = context; // Setup Custom Widget view FuriString* temp_str; temp_str = furi_string_alloc(); - furi_string_printf(temp_str, "\e#Delete %s?\e#", nfc->dev->dev_name); + furi_string_printf(temp_str, "\e#Delete %s?\e#", furi_string_get_cstr(nfc->file_name)); widget_add_text_box_element( nfc->widget, 0, 0, 128, 23, AlignCenter, AlignCenter, furi_string_get_cstr(temp_str), false); widget_add_button_element( @@ -23,47 +22,33 @@ void nfc_scene_delete_on_enter(void* context) { widget_add_button_element( nfc->widget, GuiButtonTypeRight, "Delete", nfc_scene_delete_widget_callback, nfc); + size_t uid_len; + const uint8_t* uid = nfc_device_get_uid(nfc->nfc_device, &uid_len); + furi_string_set(temp_str, "UID:"); - for(size_t i = 0; i < nfc_data->uid_len; i++) { - furi_string_cat_printf(temp_str, " %02X", nfc_data->uid[i]); + for(size_t i = 0; i < uid_len; i++) { + furi_string_cat_printf(temp_str, " %02X", uid[i]); } widget_add_string_element( nfc->widget, 64, 24, AlignCenter, AlignTop, FontSecondary, furi_string_get_cstr(temp_str)); - NfcProtocol protocol = nfc->dev->dev_data.protocol; - const char* nfc_type = "NFC-A"; - - if(protocol == NfcDeviceProtocolEMV) { - furi_string_set(temp_str, "EMV bank card"); - } else if(protocol == NfcDeviceProtocolMifareUl) { - furi_string_set(temp_str, nfc_mf_ul_type(nfc->dev->dev_data.mf_ul_data.type, true)); - } else if(protocol == NfcDeviceProtocolMifareClassic) { - furi_string_set(temp_str, nfc_mf_classic_type(nfc->dev->dev_data.mf_classic_data.type)); - } else if(protocol == NfcDeviceProtocolMifareDesfire) { - furi_string_set(temp_str, "MIFARE DESFire"); - } else if(protocol == NfcDeviceProtocolNfcV) { - furi_string_set(temp_str, "ISO15693 tag"); - nfc_type = "NFC-V"; - } else { - furi_string_set(temp_str, "Unknown ISO tag"); - } + furi_string_set_str(temp_str, nfc_device_get_name(nfc->nfc_device, NfcDeviceNameTypeFull)); widget_add_string_element( nfc->widget, 64, 34, AlignCenter, AlignTop, FontSecondary, furi_string_get_cstr(temp_str)); - widget_add_string_element(nfc->widget, 64, 44, AlignCenter, AlignTop, FontSecondary, nfc_type); furi_string_free(temp_str); view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); } bool nfc_scene_delete_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; + NfcApp* nfc = context; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { if(event.event == GuiButtonTypeLeft) { consumed = scene_manager_previous_scene(nfc->scene_manager); } else if(event.event == GuiButtonTypeRight) { - if(nfc_device_delete(nfc->dev, true)) { + if(nfc_delete(nfc)) { scene_manager_next_scene(nfc->scene_manager, NfcSceneDeleteSuccess); } else { scene_manager_search_and_switch_to_previous_scene( @@ -76,7 +61,7 @@ bool nfc_scene_delete_on_event(void* context, SceneManagerEvent event) { } void nfc_scene_delete_on_exit(void* context) { - Nfc* nfc = context; + NfcApp* nfc = context; widget_reset(nfc->widget); } diff --git a/applications/main/nfc/scenes/nfc_scene_delete_success.c b/applications/main/nfc/scenes/nfc_scene_delete_success.c index 795363527fc..f0c22eec4d5 100644 --- a/applications/main/nfc/scenes/nfc_scene_delete_success.c +++ b/applications/main/nfc/scenes/nfc_scene_delete_success.c @@ -1,12 +1,12 @@ -#include "../nfc_i.h" +#include "../nfc_app_i.h" void nfc_scene_delete_success_popup_callback(void* context) { - Nfc* nfc = context; + NfcApp* nfc = context; view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit); } void nfc_scene_delete_success_on_enter(void* context) { - Nfc* nfc = context; + NfcApp* nfc = context; // Setup view Popup* popup = nfc->popup; @@ -20,7 +20,7 @@ void nfc_scene_delete_success_on_enter(void* context) { } bool nfc_scene_delete_success_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; + NfcApp* nfc = context; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { @@ -38,7 +38,7 @@ bool nfc_scene_delete_success_on_event(void* context, SceneManagerEvent event) { } void nfc_scene_delete_success_on_exit(void* context) { - Nfc* nfc = context; + NfcApp* nfc = context; // Clear view popup_reset(nfc->popup); diff --git a/applications/main/nfc/scenes/nfc_scene_detect.c b/applications/main/nfc/scenes/nfc_scene_detect.c new file mode 100644 index 00000000000..326b1458c0d --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_detect.c @@ -0,0 +1,59 @@ +#include "../nfc_app_i.h" +#include + +void nfc_scene_detect_scan_callback(NfcScannerEvent event, void* context) { + furi_assert(context); + + NfcApp* instance = context; + + if(event.type == NfcScannerEventTypeDetected) { + nfc_app_set_detected_protocols(instance, event.data.protocols, event.data.protocol_num); + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventWorkerExit); + } +} + +void nfc_scene_detect_on_enter(void* context) { + NfcApp* instance = context; + + // Setup view + popup_reset(instance->popup); + popup_set_text( + instance->popup, "Apply card to\nFlipper's back", 97, 24, AlignCenter, AlignTop); + popup_set_icon(instance->popup, 0, 8, &I_NFC_manual_60x50); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup); + + nfc_app_reset_detected_protocols(instance); + + instance->scanner = nfc_scanner_alloc(instance->nfc); + nfc_scanner_start(instance->scanner, nfc_scene_detect_scan_callback, instance); + + nfc_blink_detect_start(instance); +} + +bool nfc_scene_detect_on_event(void* context, SceneManagerEvent event) { + NfcApp* instance = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcCustomEventWorkerExit) { + if(instance->protocols_detected_num > 1) { + scene_manager_next_scene(instance->scene_manager, NfcSceneSelectProtocol); + } else { + scene_manager_next_scene(instance->scene_manager, NfcSceneRead); + } + consumed = true; + } + } + + return consumed; +} + +void nfc_scene_detect_on_exit(void* context) { + NfcApp* instance = context; + + nfc_scanner_stop(instance->scanner); + nfc_scanner_free(instance->scanner); + popup_reset(instance->popup); + + nfc_blink_stop(instance); +} diff --git a/applications/main/nfc/scenes/nfc_scene_detect_reader.c b/applications/main/nfc/scenes/nfc_scene_detect_reader.c deleted file mode 100644 index 745946157b0..00000000000 --- a/applications/main/nfc/scenes/nfc_scene_detect_reader.c +++ /dev/null @@ -1,103 +0,0 @@ -#include "../nfc_i.h" - -#define NFC_SCENE_DETECT_READER_PAIR_NONCES_MAX (10U) - -static const NotificationSequence sequence_detect_reader = { - &message_green_255, - &message_blue_255, - &message_do_not_reset, - NULL, -}; - -bool nfc_detect_reader_worker_callback(NfcWorkerEvent event, void* context) { - UNUSED(event); - furi_assert(context); - Nfc* nfc = context; - view_dispatcher_send_custom_event(nfc->view_dispatcher, event); - return true; -} - -void nfc_scene_detect_reader_callback(void* context) { - furi_assert(context); - Nfc* nfc = context; - view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit); -} - -void nfc_scene_detect_reader_on_enter(void* context) { - Nfc* nfc = context; - - detect_reader_set_callback(nfc->detect_reader, nfc_scene_detect_reader_callback, nfc); - detect_reader_set_nonces_max(nfc->detect_reader, NFC_SCENE_DETECT_READER_PAIR_NONCES_MAX); - NfcDeviceData* dev_data = &nfc->dev->dev_data; - if(dev_data->nfc_data.uid_len) { - detect_reader_set_uid( - nfc->detect_reader, dev_data->nfc_data.uid, dev_data->nfc_data.uid_len); - } - - // Store number of collected nonces in scene state - scene_manager_set_scene_state(nfc->scene_manager, NfcSceneDetectReader, 0); - notification_message(nfc->notifications, &sequence_detect_reader); - - nfc_worker_start( - nfc->worker, - NfcWorkerStateAnalyzeReader, - &nfc->dev->dev_data, - nfc_detect_reader_worker_callback, - nfc); - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewDetectReader); -} - -bool nfc_scene_detect_reader_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - uint32_t nonces_collected = - scene_manager_get_scene_state(nfc->scene_manager, NfcSceneDetectReader); - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == NfcCustomEventViewExit) { - nfc_worker_stop(nfc->worker); - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfkeyNoncesInfo); - consumed = true; - } else if(event.event == NfcWorkerEventDetectReaderMfkeyCollected) { - nonces_collected += 2; - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneDetectReader, nonces_collected); - detect_reader_set_nonces_collected(nfc->detect_reader, nonces_collected); - if(nonces_collected >= NFC_SCENE_DETECT_READER_PAIR_NONCES_MAX) { - detect_reader_set_state(nfc->detect_reader, DetectReaderStateDone); - nfc_blink_stop(nfc); - notification_message(nfc->notifications, &sequence_single_vibro); - notification_message(nfc->notifications, &sequence_set_green_255); - nfc_worker_stop(nfc->worker); - } - consumed = true; - } else if(event.event == NfcWorkerEventDetectReaderDetected) { - if(nonces_collected < NFC_SCENE_DETECT_READER_PAIR_NONCES_MAX) { - notification_message(nfc->notifications, &sequence_blink_start_cyan); - detect_reader_set_state(nfc->detect_reader, DetectReaderStateReaderDetected); - } - } else if(event.event == NfcWorkerEventDetectReaderLost) { - if(nonces_collected < NFC_SCENE_DETECT_READER_PAIR_NONCES_MAX) { - nfc_blink_stop(nfc); - notification_message(nfc->notifications, &sequence_detect_reader); - detect_reader_set_state(nfc->detect_reader, DetectReaderStateReaderLost); - } - } - } - - return consumed; -} - -void nfc_scene_detect_reader_on_exit(void* context) { - Nfc* nfc = context; - - // Stop worker - nfc_worker_stop(nfc->worker); - - // Clear view - detect_reader_reset(nfc->detect_reader); - - // Stop notifications - nfc_blink_stop(nfc); - notification_message(nfc->notifications, &sequence_reset_green); -} diff --git a/applications/main/nfc/scenes/nfc_scene_device_info.c b/applications/main/nfc/scenes/nfc_scene_device_info.c deleted file mode 100644 index 5d51c0816c8..00000000000 --- a/applications/main/nfc/scenes/nfc_scene_device_info.c +++ /dev/null @@ -1,87 +0,0 @@ -#include "../nfc_i.h" -#include "../helpers/nfc_emv_parser.h" - -void nfc_scene_device_info_widget_callback(GuiButtonType result, InputType type, void* context) { - Nfc* nfc = context; - if(type == InputTypeShort) { - view_dispatcher_send_custom_event(nfc->view_dispatcher, result); - } -} - -void nfc_scene_device_info_on_enter(void* context) { - Nfc* nfc = context; - NfcDeviceData* dev_data = &nfc->dev->dev_data; - - FuriString* temp_str; - temp_str = furi_string_alloc(); - - if(dev_data->protocol == NfcDeviceProtocolEMV) { - EmvData* emv_data = &dev_data->emv_data; - furi_string_printf(temp_str, "\e#%s\n", emv_data->name); - for(uint8_t i = 0; i < emv_data->number_len; i += 2) { - furi_string_cat_printf( - temp_str, "%02X%02X ", emv_data->number[i], emv_data->number[i + 1]); - } - furi_string_trim(temp_str); - - // Add expiration date - if(emv_data->exp_mon) { - furi_string_cat_printf( - temp_str, "\nExp: %02X/%02X", emv_data->exp_mon, emv_data->exp_year); - } - // Parse currency code - if((emv_data->currency_code)) { - FuriString* currency_name; - currency_name = furi_string_alloc(); - if(nfc_emv_parser_get_currency_name( - nfc->dev->storage, emv_data->currency_code, currency_name)) { - furi_string_cat_printf( - temp_str, "\nCur: %s ", furi_string_get_cstr(currency_name)); - } - furi_string_free(currency_name); - } - // Parse country code - if((emv_data->country_code)) { - FuriString* country_name; - country_name = furi_string_alloc(); - if(nfc_emv_parser_get_country_name( - nfc->dev->storage, emv_data->country_code, country_name)) { - furi_string_cat_printf(temp_str, "Reg: %s", furi_string_get_cstr(country_name)); - } - furi_string_free(country_name); - } - } else if( - dev_data->protocol == NfcDeviceProtocolMifareClassic || - dev_data->protocol == NfcDeviceProtocolMifareDesfire || - dev_data->protocol == NfcDeviceProtocolMifareUl) { - furi_string_set(temp_str, nfc->dev->dev_data.parsed_data); - } - - widget_add_text_scroll_element(nfc->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); - furi_string_free(temp_str); - - widget_add_button_element( - nfc->widget, GuiButtonTypeRight, "More", nfc_scene_device_info_widget_callback, nfc); - - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); -} - -bool nfc_scene_device_info_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == GuiButtonTypeRight) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcDataInfo); - consumed = true; - } - } - return consumed; -} - -void nfc_scene_device_info_on_exit(void* context) { - Nfc* nfc = context; - - // Clear views - widget_reset(nfc->widget); -} diff --git a/applications/main/nfc/scenes/nfc_scene_dict_not_found.c b/applications/main/nfc/scenes/nfc_scene_dict_not_found.c deleted file mode 100644 index 781c5a93254..00000000000 --- a/applications/main/nfc/scenes/nfc_scene_dict_not_found.c +++ /dev/null @@ -1,52 +0,0 @@ -#include "../nfc_i.h" - -void nfc_scene_dict_not_found_popup_callback(void* context) { - Nfc* nfc = context; - view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit); -} - -void nfc_scene_dict_not_found_on_enter(void* context) { - Nfc* nfc = context; - - // Setup view - Popup* popup = nfc->popup; - popup_set_text( - popup, - "Function requires\nan SD card with\nfresh databases.", - 82, - 24, - AlignCenter, - AlignCenter); - popup_set_icon(popup, 6, 10, &I_SDQuestion_35x43); - popup_set_timeout(popup, 2500); - popup_set_context(popup, nfc); - popup_set_callback(popup, nfc_scene_dict_not_found_popup_callback); - popup_enable_timeout(popup); - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); -} - -bool nfc_scene_dict_not_found_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == NfcCustomEventViewExit) { - if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneMfClassicKeys)) { - consumed = scene_manager_search_and_switch_to_previous_scene( - nfc->scene_manager, NfcSceneMfClassicKeys); - } else if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneExtraActions)) { - consumed = scene_manager_search_and_switch_to_previous_scene( - nfc->scene_manager, NfcSceneExtraActions); - } else { - consumed = scene_manager_search_and_switch_to_previous_scene( - nfc->scene_manager, NfcSceneStart); - } - } - } - return consumed; -} - -void nfc_scene_dict_not_found_on_exit(void* context) { - Nfc* nfc = context; - popup_reset(nfc->popup); -} diff --git a/applications/main/nfc/scenes/nfc_scene_emulate.c b/applications/main/nfc/scenes/nfc_scene_emulate.c new file mode 100644 index 00000000000..6f217f31548 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_emulate.c @@ -0,0 +1,13 @@ +#include "../helpers/protocol_support/nfc_protocol_support.h" + +void nfc_scene_emulate_on_enter(void* context) { + nfc_protocol_support_on_enter(NfcProtocolSupportSceneEmulate, context); +} + +bool nfc_scene_emulate_on_event(void* context, SceneManagerEvent event) { + return nfc_protocol_support_on_event(NfcProtocolSupportSceneEmulate, context, event); +} + +void nfc_scene_emulate_on_exit(void* context) { + nfc_protocol_support_on_exit(NfcProtocolSupportSceneEmulate, context); +} diff --git a/applications/main/nfc/scenes/nfc_scene_emulate_apdu_sequence.c b/applications/main/nfc/scenes/nfc_scene_emulate_apdu_sequence.c deleted file mode 100644 index 358ad2ab6f5..00000000000 --- a/applications/main/nfc/scenes/nfc_scene_emulate_apdu_sequence.c +++ /dev/null @@ -1,34 +0,0 @@ -#include "../nfc_i.h" -#include - -void nfc_scene_emulate_apdu_sequence_on_enter(void* context) { - Nfc* nfc = context; - - // Setup view - Popup* popup = nfc->popup; - popup_set_header(popup, "Run APDU reader", 64, 31, AlignCenter, AlignTop); - - // Setup and start worker - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); - nfc_worker_start(nfc->worker, NfcWorkerStateEmulateApdu, &nfc->dev->dev_data, NULL, nfc); - - nfc_blink_emulate_start(nfc); -} - -bool nfc_scene_emulate_apdu_sequence_on_event(void* context, SceneManagerEvent event) { - UNUSED(context); - UNUSED(event); - bool consumed = false; - return consumed; -} - -void nfc_scene_emulate_apdu_sequence_on_exit(void* context) { - Nfc* nfc = context; - - // Stop worker - nfc_worker_stop(nfc->worker); - // Clear view - popup_reset(nfc->popup); - - nfc_blink_stop(nfc); -} diff --git a/applications/main/nfc/scenes/nfc_scene_emulate_uid.c b/applications/main/nfc/scenes/nfc_scene_emulate_uid.c deleted file mode 100644 index 7316eebe015..00000000000 --- a/applications/main/nfc/scenes/nfc_scene_emulate_uid.c +++ /dev/null @@ -1,144 +0,0 @@ -#include "../nfc_i.h" - -#define NFC_SCENE_EMULATE_UID_LOG_SIZE_MAX (200) - -enum { - NfcSceneEmulateUidStateWidget, - NfcSceneEmulateUidStateTextBox, -}; - -bool nfc_emulate_uid_worker_callback(NfcWorkerEvent event, void* context) { - UNUSED(event); - furi_assert(context); - Nfc* nfc = context; - view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventWorkerExit); - return true; -} - -void nfc_scene_emulate_uid_widget_callback(GuiButtonType result, InputType type, void* context) { - furi_assert(context); - Nfc* nfc = context; - if(type == InputTypeShort) { - view_dispatcher_send_custom_event(nfc->view_dispatcher, result); - } -} - -void nfc_emulate_uid_textbox_callback(void* context) { - furi_assert(context); - Nfc* nfc = context; - view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit); -} - -// Add widget with device name or inform that data received -static void nfc_scene_emulate_uid_widget_config(Nfc* nfc, bool data_received) { - FuriHalNfcDevData* data = &nfc->dev->dev_data.nfc_data; - Widget* widget = nfc->widget; - widget_reset(widget); - FuriString* info_str; - info_str = furi_string_alloc(); - - widget_add_icon_element(widget, 0, 3, &I_NFC_dolphin_emulation_47x61); - widget_add_string_element(widget, 57, 13, AlignLeft, AlignTop, FontPrimary, "Emulating UID"); - if(strcmp(nfc->dev->dev_name, "") != 0) { - furi_string_printf(info_str, "%s", nfc->dev->dev_name); - } else { - for(uint8_t i = 0; i < data->uid_len; i++) { - furi_string_cat_printf(info_str, "%02X ", data->uid[i]); - } - } - furi_string_trim(info_str); - widget_add_text_box_element( - widget, 57, 28, 67, 25, AlignCenter, AlignTop, furi_string_get_cstr(info_str), true); - furi_string_free(info_str); - if(data_received) { - widget_add_button_element( - widget, GuiButtonTypeCenter, "Log", nfc_scene_emulate_uid_widget_callback, nfc); - } -} - -void nfc_scene_emulate_uid_on_enter(void* context) { - Nfc* nfc = context; - - // Setup Widget - nfc_scene_emulate_uid_widget_config(nfc, false); - // Setup TextBox - TextBox* text_box = nfc->text_box; - text_box_set_font(text_box, TextBoxFontHex); - text_box_set_focus(text_box, TextBoxFocusEnd); - furi_string_reset(nfc->text_box_store); - - // Set Widget state and view - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneEmulateUid, NfcSceneEmulateUidStateWidget); - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); - // Start worker - memset(&nfc->dev->dev_data.reader_data, 0, sizeof(NfcReaderRequestData)); - nfc_worker_start( - nfc->worker, - NfcWorkerStateUidEmulate, - &nfc->dev->dev_data, - nfc_emulate_uid_worker_callback, - nfc); - - nfc_blink_emulate_start(nfc); -} - -bool nfc_scene_emulate_uid_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - NfcReaderRequestData* reader_data = &nfc->dev->dev_data.reader_data; - uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneEmulateUid); - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == NfcCustomEventWorkerExit) { - // Add data button to widget if data is received for the first time - if(!furi_string_size(nfc->text_box_store)) { - nfc_scene_emulate_uid_widget_config(nfc, true); - } - // Update TextBox data - if(furi_string_size(nfc->text_box_store) < NFC_SCENE_EMULATE_UID_LOG_SIZE_MAX) { - furi_string_cat_printf(nfc->text_box_store, "R:"); - for(uint16_t i = 0; i < reader_data->size; i++) { - furi_string_cat_printf(nfc->text_box_store, " %02X", reader_data->data[i]); - } - furi_string_push_back(nfc->text_box_store, '\n'); - text_box_set_text(nfc->text_box, furi_string_get_cstr(nfc->text_box_store)); - } - memset(reader_data, 0, sizeof(NfcReaderRequestData)); - consumed = true; - } else if(event.event == GuiButtonTypeCenter && state == NfcSceneEmulateUidStateWidget) { - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewTextBox); - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneEmulateUid, NfcSceneEmulateUidStateTextBox); - consumed = true; - } else if(event.event == NfcCustomEventViewExit && state == NfcSceneEmulateUidStateTextBox) { - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneEmulateUid, NfcSceneEmulateUidStateWidget); - consumed = true; - } - } else if(event.type == SceneManagerEventTypeBack) { - if(state == NfcSceneEmulateUidStateTextBox) { - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneEmulateUid, NfcSceneEmulateUidStateWidget); - consumed = true; - } - } - - return consumed; -} - -void nfc_scene_emulate_uid_on_exit(void* context) { - Nfc* nfc = context; - - // Stop worker - nfc_worker_stop(nfc->worker); - - // Clear view - widget_reset(nfc->widget); - text_box_reset(nfc->text_box); - furi_string_reset(nfc->text_box_store); - - nfc_blink_stop(nfc); -} diff --git a/applications/main/nfc/scenes/nfc_scene_emv_menu.c b/applications/main/nfc/scenes/nfc_scene_emv_menu.c deleted file mode 100644 index eb1e10043bd..00000000000 --- a/applications/main/nfc/scenes/nfc_scene_emv_menu.c +++ /dev/null @@ -1,46 +0,0 @@ -#include "../nfc_i.h" - -enum SubmenuIndex { - SubmenuIndexInfo, -}; - -void nfc_scene_emv_menu_submenu_callback(void* context, uint32_t index) { - Nfc* nfc = context; - - view_dispatcher_send_custom_event(nfc->view_dispatcher, index); -} - -void nfc_scene_emv_menu_on_enter(void* context) { - Nfc* nfc = context; - Submenu* submenu = nfc->submenu; - - submenu_add_item(submenu, "Info", SubmenuIndexInfo, nfc_scene_emv_menu_submenu_callback, nfc); - submenu_set_selected_item( - nfc->submenu, scene_manager_get_scene_state(nfc->scene_manager, NfcSceneEmvMenu)); - - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); -} - -bool nfc_scene_emv_menu_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SubmenuIndexInfo) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcDataInfo); - consumed = true; - } - scene_manager_set_scene_state(nfc->scene_manager, NfcSceneEmvMenu, event.event); - } else if(event.type == SceneManagerEventTypeBack) { - consumed = scene_manager_previous_scene(nfc->scene_manager); - } - - return consumed; -} - -void nfc_scene_emv_menu_on_exit(void* context) { - Nfc* nfc = context; - - // Clear view - submenu_reset(nfc->submenu); -} diff --git a/applications/main/nfc/scenes/nfc_scene_emv_read_success.c b/applications/main/nfc/scenes/nfc_scene_emv_read_success.c deleted file mode 100644 index 005b76cb211..00000000000 --- a/applications/main/nfc/scenes/nfc_scene_emv_read_success.c +++ /dev/null @@ -1,113 +0,0 @@ -#include "../nfc_i.h" -#include "../helpers/nfc_emv_parser.h" - -void nfc_scene_emv_read_success_widget_callback( - GuiButtonType result, - InputType type, - void* context) { - Nfc* nfc = context; - if(type == InputTypeShort) { - view_dispatcher_send_custom_event(nfc->view_dispatcher, result); - } -} - -void nfc_scene_emv_read_success_on_enter(void* context) { - Nfc* nfc = context; - EmvData* emv_data = &nfc->dev->dev_data.emv_data; - - // Setup Custom Widget view - widget_add_button_element( - nfc->widget, GuiButtonTypeLeft, "Retry", nfc_scene_emv_read_success_widget_callback, nfc); - widget_add_button_element( - nfc->widget, GuiButtonTypeRight, "More", nfc_scene_emv_read_success_widget_callback, nfc); - - FuriString* temp_str; - if(emv_data->name[0] != '\0') { - temp_str = furi_string_alloc_printf("\e#%s\n", emv_data->name); - } else { - temp_str = furi_string_alloc_printf("\e#Unknown Bank Card\n"); - } - if(emv_data->number_len) { - for(uint8_t i = 0; i < emv_data->number_len; i += 2) { - furi_string_cat_printf( - temp_str, "%02X%02X ", emv_data->number[i], emv_data->number[i + 1]); - } - furi_string_trim(temp_str); - } else if(emv_data->aid_len) { - furi_string_cat_printf(temp_str, "Can't parse data from app\n"); - // Parse AID name - FuriString* aid_name; - aid_name = furi_string_alloc(); - if(nfc_emv_parser_get_aid_name( - nfc->dev->storage, emv_data->aid, emv_data->aid_len, aid_name)) { - furi_string_cat_printf(temp_str, "AID: %s", furi_string_get_cstr(aid_name)); - } else { - furi_string_cat_printf(temp_str, "AID: "); - for(uint8_t i = 0; i < emv_data->aid_len; i++) { - furi_string_cat_printf(temp_str, "%02X", emv_data->aid[i]); - } - } - furi_string_free(aid_name); - } - - // Add expiration date - if(emv_data->exp_mon) { - furi_string_cat_printf( - temp_str, "\nExp: %02X/%02X", emv_data->exp_mon, emv_data->exp_year); - } - // Parse currency code - if((emv_data->currency_code)) { - FuriString* currency_name; - currency_name = furi_string_alloc(); - if(nfc_emv_parser_get_currency_name( - nfc->dev->storage, emv_data->currency_code, currency_name)) { - furi_string_cat_printf(temp_str, "\nCur: %s ", furi_string_get_cstr(currency_name)); - } - furi_string_free(currency_name); - } - // Parse country code - if((emv_data->country_code)) { - FuriString* country_name; - country_name = furi_string_alloc(); - if(nfc_emv_parser_get_country_name( - nfc->dev->storage, emv_data->country_code, country_name)) { - furi_string_cat_printf(temp_str, "Reg: %s", furi_string_get_cstr(country_name)); - } - furi_string_free(country_name); - } - - notification_message_block(nfc->notifications, &sequence_set_green_255); - - widget_add_text_scroll_element(nfc->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); - furi_string_free(temp_str); - - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); -} - -bool nfc_scene_emv_read_success_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == GuiButtonTypeLeft) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneRetryConfirm); - consumed = true; - } else if(event.event == GuiButtonTypeRight) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneEmvMenu); - consumed = true; - } - } else if(event.type == SceneManagerEventTypeBack) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneExitConfirm); - consumed = true; - } - return consumed; -} - -void nfc_scene_emv_read_success_on_exit(void* context) { - Nfc* nfc = context; - - notification_message_block(nfc->notifications, &sequence_reset_green); - - // Clear view - widget_reset(nfc->widget); -} diff --git a/applications/main/nfc/scenes/nfc_scene_exit_confirm.c b/applications/main/nfc/scenes/nfc_scene_exit_confirm.c index 3ce4f6de839..c024d31295a 100644 --- a/applications/main/nfc/scenes/nfc_scene_exit_confirm.c +++ b/applications/main/nfc/scenes/nfc_scene_exit_confirm.c @@ -1,13 +1,13 @@ -#include "../nfc_i.h" +#include "../nfc_app_i.h" void nfc_scene_exit_confirm_dialog_callback(DialogExResult result, void* context) { - Nfc* nfc = context; + NfcApp* nfc = context; view_dispatcher_send_custom_event(nfc->view_dispatcher, result); } void nfc_scene_exit_confirm_on_enter(void* context) { - Nfc* nfc = context; + NfcApp* nfc = context; DialogEx* dialog_ex = nfc->dialog_ex; dialog_ex_set_left_button_text(dialog_ex, "Exit"); @@ -22,16 +22,16 @@ void nfc_scene_exit_confirm_on_enter(void* context) { } bool nfc_scene_exit_confirm_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; + NfcApp* nfc = context; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { if(event.event == DialogExResultRight) { consumed = scene_manager_previous_scene(nfc->scene_manager); } else if(event.event == DialogExResultLeft) { - if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneReadCardType)) { + if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSelectProtocol)) { consumed = scene_manager_search_and_switch_to_previous_scene( - nfc->scene_manager, NfcSceneReadCardType); + nfc->scene_manager, NfcSceneSelectProtocol); } else { consumed = scene_manager_search_and_switch_to_previous_scene( nfc->scene_manager, NfcSceneStart); @@ -45,7 +45,7 @@ bool nfc_scene_exit_confirm_on_event(void* context, SceneManagerEvent event) { } void nfc_scene_exit_confirm_on_exit(void* context) { - Nfc* nfc = context; + NfcApp* nfc = context; // Clean view dialog_ex_reset(nfc->dialog_ex); diff --git a/applications/main/nfc/scenes/nfc_scene_extra_actions.c b/applications/main/nfc/scenes/nfc_scene_extra_actions.c index 7f5bc7e7585..7f51b717415 100644 --- a/applications/main/nfc/scenes/nfc_scene_extra_actions.c +++ b/applications/main/nfc/scenes/nfc_scene_extra_actions.c @@ -1,92 +1,72 @@ -#include "../nfc_i.h" +#include "../nfc_app_i.h" enum SubmenuIndex { SubmenuIndexReadCardType, SubmenuIndexMfClassicKeys, SubmenuIndexMfUltralightUnlock, - SubmenuIndexNfcVUnlock, - SubmenuIndexNfcVSniff, }; void nfc_scene_extra_actions_submenu_callback(void* context, uint32_t index) { - Nfc* nfc = context; + NfcApp* instance = context; - view_dispatcher_send_custom_event(nfc->view_dispatcher, index); + view_dispatcher_send_custom_event(instance->view_dispatcher, index); } void nfc_scene_extra_actions_on_enter(void* context) { - Nfc* nfc = context; - Submenu* submenu = nfc->submenu; + NfcApp* instance = context; + Submenu* submenu = instance->submenu; submenu_add_item( submenu, "Read Specific Card Type", SubmenuIndexReadCardType, nfc_scene_extra_actions_submenu_callback, - nfc); + instance); submenu_add_item( submenu, "Mifare Classic Keys", SubmenuIndexMfClassicKeys, nfc_scene_extra_actions_submenu_callback, - nfc); + instance); submenu_add_item( submenu, "Unlock NTAG/Ultralight", SubmenuIndexMfUltralightUnlock, nfc_scene_extra_actions_submenu_callback, - nfc); - submenu_add_item( - submenu, - "Unlock SLIX-L", - SubmenuIndexNfcVUnlock, - nfc_scene_extra_actions_submenu_callback, - nfc); - submenu_add_item( - submenu, - "Listen NfcV Reader", - SubmenuIndexNfcVSniff, - nfc_scene_extra_actions_submenu_callback, - nfc); + instance); submenu_set_selected_item( - submenu, scene_manager_get_scene_state(nfc->scene_manager, NfcSceneExtraActions)); - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); + submenu, scene_manager_get_scene_state(instance->scene_manager, NfcSceneExtraActions)); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewMenu); } bool nfc_scene_extra_actions_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; + NfcApp* instance = context; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { if(event.event == SubmenuIndexMfClassicKeys) { - if(mf_classic_dict_check_presence(MfClassicDictTypeSystem)) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicKeys); + if(nfc_dict_check_presence(NFC_APP_MF_CLASSIC_DICT_USER_PATH)) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicKeys); } else { - scene_manager_next_scene(nfc->scene_manager, NfcSceneDictNotFound); + scene_manager_previous_scene(instance->scene_manager); } consumed = true; } else if(event.event == SubmenuIndexMfUltralightUnlock) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightUnlockMenu); + mf_ultralight_auth_reset(instance->mf_ul_auth); + scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightUnlockMenu); consumed = true; } else if(event.event == SubmenuIndexReadCardType) { - scene_manager_set_scene_state(nfc->scene_manager, NfcSceneReadCardType, 0); - scene_manager_next_scene(nfc->scene_manager, NfcSceneReadCardType); - consumed = true; - } else if(event.event == SubmenuIndexNfcVUnlock) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcVUnlockMenu); - consumed = true; - } else if(event.event == SubmenuIndexNfcVSniff) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcVSniff); + scene_manager_next_scene(instance->scene_manager, NfcSceneSelectProtocol); consumed = true; } - scene_manager_set_scene_state(nfc->scene_manager, NfcSceneExtraActions, event.event); + scene_manager_set_scene_state(instance->scene_manager, NfcSceneExtraActions, event.event); } return consumed; } void nfc_scene_extra_actions_on_exit(void* context) { - Nfc* nfc = context; + NfcApp* instance = context; - submenu_reset(nfc->submenu); + submenu_reset(instance->submenu); } diff --git a/applications/main/nfc/scenes/nfc_scene_field.c b/applications/main/nfc/scenes/nfc_scene_field.c index e3eb6a7088b..d5eb916a851 100644 --- a/applications/main/nfc/scenes/nfc_scene_field.c +++ b/applications/main/nfc/scenes/nfc_scene_field.c @@ -1,10 +1,10 @@ -#include "../nfc_i.h" +#include "../nfc_app_i.h" void nfc_scene_field_on_enter(void* context) { - Nfc* nfc = context; - - furi_hal_nfc_field_on(); + NfcApp* nfc = context; + furi_hal_nfc_low_power_mode_stop(); + furi_hal_nfc_poller_field_on(); Popup* popup = nfc->popup; popup_set_header( popup, @@ -25,9 +25,9 @@ bool nfc_scene_field_on_event(void* context, SceneManagerEvent event) { } void nfc_scene_field_on_exit(void* context) { - Nfc* nfc = context; + NfcApp* nfc = context; - furi_hal_nfc_field_off(); + furi_hal_nfc_low_power_mode_start(); notification_internal_message(nfc->notifications, &sequence_reset_blue); popup_reset(nfc->popup); } diff --git a/applications/main/nfc/scenes/nfc_scene_file_select.c b/applications/main/nfc/scenes/nfc_scene_file_select.c index ce7ec92f461..e1edcadb1be 100644 --- a/applications/main/nfc/scenes/nfc_scene_file_select.c +++ b/applications/main/nfc/scenes/nfc_scene_file_select.c @@ -1,28 +1,20 @@ -#include "../nfc_i.h" -#include "nfc/nfc_device.h" +#include "../nfc_app_i.h" void nfc_scene_file_select_on_enter(void* context) { - Nfc* nfc = context; - nfc_device_data_clear(&nfc->dev->dev_data); + NfcApp* instance = context; - // Process file_select return - nfc_device_set_loading_callback(nfc->dev, nfc_show_loading_popup, nfc); - if(!furi_string_size(nfc->dev->load_path)) { - furi_string_set_str(nfc->dev->load_path, NFC_APP_FOLDER); - } - if(nfc_file_select(nfc->dev)) { - scene_manager_set_scene_state(nfc->scene_manager, NfcSceneSavedMenu, 0); - scene_manager_next_scene(nfc->scene_manager, NfcSceneSavedMenu); + if(nfc_load_from_file_select(instance)) { + scene_manager_next_scene(instance->scene_manager, NfcSceneSavedMenu); } else { - scene_manager_search_and_switch_to_previous_scene(nfc->scene_manager, NfcSceneStart); + scene_manager_previous_scene(instance->scene_manager); } - nfc_device_set_loading_callback(nfc->dev, NULL, nfc); } bool nfc_scene_file_select_on_event(void* context, SceneManagerEvent event) { UNUSED(context); UNUSED(event); - return false; + bool consumed = false; + return consumed; } void nfc_scene_file_select_on_exit(void* context) { diff --git a/applications/main/nfc/scenes/nfc_scene_generate_info.c b/applications/main/nfc/scenes/nfc_scene_generate_info.c index 7b84ae43b11..c4f86ff4a6d 100644 --- a/applications/main/nfc/scenes/nfc_scene_generate_info.c +++ b/applications/main/nfc/scenes/nfc_scene_generate_info.c @@ -1,50 +1,55 @@ -#include "../nfc_i.h" -#include "lib/nfc/helpers/nfc_generators.h" +#include "../nfc_app_i.h" -void nfc_scene_generate_info_dialog_callback(DialogExResult result, void* context) { - Nfc* nfc = context; +void nfc_scene_generate_info_widget_callback(GuiButtonType result, InputType type, void* context) { + NfcApp* instance = context; - view_dispatcher_send_custom_event(nfc->view_dispatcher, result); + if(type == InputTypeShort) { + if(result == GuiButtonTypeRight) { + view_dispatcher_send_custom_event(instance->view_dispatcher, result); + } + } } void nfc_scene_generate_info_on_enter(void* context) { - Nfc* nfc = context; + NfcApp* instance = context; + + NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device); + furi_assert((protocol == NfcProtocolMfUltralight) || (protocol == NfcProtocolMfClassic)); + + const Iso14443_3aData* iso14443_3a_data = + nfc_device_get_data(instance->nfc_device, NfcProtocolIso14443_3a); // Setup dialog view - FuriHalNfcDevData* data = &nfc->dev->dev_data.nfc_data; - DialogEx* dialog_ex = nfc->dialog_ex; - dialog_ex_set_right_button_text(dialog_ex, "More"); + Widget* widget = instance->widget; + widget_add_button_element( + widget, GuiButtonTypeRight, "More", nfc_scene_generate_info_widget_callback, instance); // Create info text - FuriString* info_str = furi_string_alloc_printf( - "%s\n%s\nUID:", nfc->generator->name, nfc_get_dev_type(data->type)); + NfcDataGeneratorType type = + scene_manager_get_scene_state(instance->scene_manager, NfcSceneGenerateInfo); + const char* name = nfc_data_generator_get_name(type); + widget_add_string_element(widget, 0, 0, AlignLeft, AlignTop, FontPrimary, name); + widget_add_string_element(widget, 0, 13, AlignLeft, AlignTop, FontSecondary, "NFC-A"); + FuriString* temp_str = furi_string_alloc_printf("UID:"); // Append UID - for(int i = 0; i < data->uid_len; ++i) { - furi_string_cat_printf(info_str, " %02X", data->uid[i]); + for(int i = 0; i < iso14443_3a_data->uid_len; i++) { + furi_string_cat_printf(temp_str, " %02X", iso14443_3a_data->uid[i]); } - nfc_text_store_set(nfc, furi_string_get_cstr(info_str)); - furi_string_free(info_str); - - dialog_ex_set_text(dialog_ex, nfc->text_store, 0, 0, AlignLeft, AlignTop); - dialog_ex_set_context(dialog_ex, nfc); - dialog_ex_set_result_callback(dialog_ex, nfc_scene_generate_info_dialog_callback); + widget_add_string_element( + widget, 0, 25, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(temp_str)); + furi_string_free(temp_str); - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewDialogEx); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget); } bool nfc_scene_generate_info_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; + NfcApp* instance = context; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { - if(event.event == DialogExResultRight) { - // Switch either to NfcSceneMfClassicMenu or NfcSceneMfUltralightMenu - if(nfc->dev->dev_data.protocol == NfcDeviceProtocolMifareClassic) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicMenu); - } else if(nfc->dev->dev_data.protocol == NfcDeviceProtocolMifareUl) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightMenu); - } + if(event.event == GuiButtonTypeRight) { + scene_manager_next_scene(instance->scene_manager, NfcSceneReadMenu); consumed = true; } } @@ -53,8 +58,8 @@ bool nfc_scene_generate_info_on_event(void* context, SceneManagerEvent event) { } void nfc_scene_generate_info_on_exit(void* context) { - Nfc* nfc = context; + NfcApp* instance = context; // Clean views - dialog_ex_reset(nfc->dialog_ex); + widget_reset(instance->widget); } diff --git a/applications/main/nfc/scenes/nfc_scene_info.c b/applications/main/nfc/scenes/nfc_scene_info.c new file mode 100644 index 00000000000..6e9d504975f --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_info.c @@ -0,0 +1,13 @@ +#include "../helpers/protocol_support/nfc_protocol_support.h" + +void nfc_scene_info_on_enter(void* context) { + nfc_protocol_support_on_enter(NfcProtocolSupportSceneInfo, context); +} + +bool nfc_scene_info_on_event(void* context, SceneManagerEvent event) { + return nfc_protocol_support_on_event(NfcProtocolSupportSceneInfo, context, event); +} + +void nfc_scene_info_on_exit(void* context) { + nfc_protocol_support_on_exit(NfcProtocolSupportSceneInfo, context); +} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_data.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_data.c deleted file mode 100644 index dcb02d3645a..00000000000 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_data.c +++ /dev/null @@ -1,106 +0,0 @@ -#include "../nfc_i.h" - -void nfc_scene_mf_classic_data_on_enter(void* context) { - Nfc* nfc = context; - MfClassicType type = nfc->dev->dev_data.mf_classic_data.type; - MfClassicData* data = &nfc->dev->dev_data.mf_classic_data; - TextBox* text_box = nfc->text_box; - - text_box_set_font(text_box, TextBoxFontHex); - - int card_blocks = 0; - if(type == MfClassicType1k) { - card_blocks = MF_CLASSIC_1K_TOTAL_SECTORS_NUM * 4; - } else if(type == MfClassicType4k) { - // 16 sectors of 4 blocks each plus 8 sectors of 16 blocks each - card_blocks = MF_CLASSIC_1K_TOTAL_SECTORS_NUM * 4 + 8 * 16; - } else if(type == MfClassicTypeMini) { - card_blocks = MF_MINI_TOTAL_SECTORS_NUM * 4; - } - - int bytes_written = 0; - for(int block_num = 0; block_num < card_blocks; block_num++) { - bool is_sec_trailer = mf_classic_is_sector_trailer(block_num); - if(is_sec_trailer) { - uint8_t sector_num = mf_classic_get_sector_by_block(block_num); - MfClassicSectorTrailer* sec_tr = - mf_classic_get_sector_trailer_by_sector(data, sector_num); - // Key A - for(size_t i = 0; i < sizeof(sec_tr->key_a); i += 2) { - if((bytes_written % 8 == 0) && (bytes_written != 0)) { - furi_string_push_back(nfc->text_box_store, '\n'); - } - if(mf_classic_is_key_found(data, sector_num, MfClassicKeyA)) { - furi_string_cat_printf( - nfc->text_box_store, "%02X%02X ", sec_tr->key_a[i], sec_tr->key_a[i + 1]); - } else { - furi_string_cat_printf(nfc->text_box_store, "???? "); - } - bytes_written += 2; - } - // Access bytes - for(size_t i = 0; i < MF_CLASSIC_ACCESS_BYTES_SIZE; i += 2) { - if((bytes_written % 8 == 0) && (bytes_written != 0)) { - furi_string_push_back(nfc->text_box_store, '\n'); - } - if(mf_classic_is_block_read(data, block_num)) { - furi_string_cat_printf( - nfc->text_box_store, - "%02X%02X ", - sec_tr->access_bits[i], - sec_tr->access_bits[i + 1]); - } else { - furi_string_cat_printf(nfc->text_box_store, "???? "); - } - bytes_written += 2; - } - // Key B - for(size_t i = 0; i < sizeof(sec_tr->key_b); i += 2) { - if((bytes_written % 8 == 0) && (bytes_written != 0)) { - furi_string_push_back(nfc->text_box_store, '\n'); - } - if(mf_classic_is_key_found(data, sector_num, MfClassicKeyB)) { - furi_string_cat_printf( - nfc->text_box_store, "%02X%02X ", sec_tr->key_b[i], sec_tr->key_b[i + 1]); - } else { - furi_string_cat_printf(nfc->text_box_store, "???? "); - } - bytes_written += 2; - } - } else { - // Write data block - for(size_t i = 0; i < MF_CLASSIC_BLOCK_SIZE; i += 2) { - if((bytes_written % 8 == 0) && (bytes_written != 0)) { - furi_string_push_back(nfc->text_box_store, '\n'); - } - if(mf_classic_is_block_read(data, block_num)) { - furi_string_cat_printf( - nfc->text_box_store, - "%02X%02X ", - data->block[block_num].value[i], - data->block[block_num].value[i + 1]); - } else { - furi_string_cat_printf(nfc->text_box_store, "???? "); - } - bytes_written += 2; - } - } - } - text_box_set_text(text_box, furi_string_get_cstr(nfc->text_box_store)); - - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewTextBox); -} - -bool nfc_scene_mf_classic_data_on_event(void* context, SceneManagerEvent event) { - UNUSED(context); - UNUSED(event); - return false; -} - -void nfc_scene_mf_classic_data_on_exit(void* context) { - Nfc* nfc = context; - - // Clean view - text_box_reset(nfc->text_box); - furi_string_reset(nfc->text_box_store); -} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_detect_reader.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_detect_reader.c new file mode 100644 index 00000000000..987f81837a5 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_detect_reader.c @@ -0,0 +1,154 @@ +#include "../nfc_app_i.h" + +#include + +#define NXP_MANUFACTURER_ID (0x04) + +#define NFC_SCENE_DETECT_READER_PAIR_NONCES_MAX (10U) +#define NFC_SCENE_DETECT_READER_WAIT_NONCES_TIMEOUT_MS (1000) + +static const NotificationSequence sequence_detect_reader = { + &message_green_255, + &message_blue_255, + &message_do_not_reset, + NULL, +}; + +void nfc_scene_mf_classic_detect_reader_view_callback(void* context) { + NfcApp* instance = context; + + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventViewExit); +} + +NfcCommand nfc_scene_mf_classic_detect_listener_callback(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.event_data); + furi_assert(event.protocol == NfcProtocolMfClassic); + + NfcApp* instance = context; + MfClassicListenerEvent* mfc_event = event.event_data; + + if(mfc_event->type == MfClassicListenerEventTypeAuthContextPartCollected) { + MfClassicAuthContext* auth_ctx = &mfc_event->data->auth_context; + mfkey32_logger_add_nonce(instance->mfkey32_logger, auth_ctx); + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventWorkerUpdate); + } + + return NfcCommandContinue; +} + +void nfc_scene_mf_classic_timer_callback(void* context) { + NfcApp* instance = context; + + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventTimerExpired); +} + +void nfc_scene_mf_classic_detect_reader_on_enter(void* context) { + NfcApp* instance = context; + + if(nfc_device_get_protocol(instance->nfc_device) == NfcProtocolInvalid) { + Iso14443_3aData iso3_data = { + .uid_len = 7, + .uid = {0}, + .atqa = {0x44, 0x00}, + .sak = 0x08, + }; + iso3_data.uid[0] = NXP_MANUFACTURER_ID; + furi_hal_random_fill_buf(&iso3_data.uid[1], iso3_data.uid_len - 1); + MfClassicData* mfc_data = mf_classic_alloc(); + + mfc_data->type = MfClassicType4k; + iso14443_3a_copy(mfc_data->iso14443_3a_data, &iso3_data); + nfc_device_set_data(instance->nfc_device, NfcProtocolMfClassic, mfc_data); + + mf_classic_free(mfc_data); + } + + const Iso14443_3aData* iso3_data = + nfc_device_get_data(instance->nfc_device, NfcProtocolIso14443_3a); + uint32_t cuid = iso14443_3a_get_cuid(iso3_data); + + instance->mfkey32_logger = mfkey32_logger_alloc(cuid); + instance->timer = + furi_timer_alloc(nfc_scene_mf_classic_timer_callback, FuriTimerTypeOnce, instance); + + detect_reader_set_nonces_max(instance->detect_reader, NFC_SCENE_DETECT_READER_PAIR_NONCES_MAX); + detect_reader_set_callback( + instance->detect_reader, nfc_scene_mf_classic_detect_reader_view_callback, instance); + + notification_message(instance->notifications, &sequence_detect_reader); + + instance->listener = nfc_listener_alloc( + instance->nfc, + NfcProtocolMfClassic, + nfc_device_get_data(instance->nfc_device, NfcProtocolMfClassic)); + nfc_listener_start( + instance->listener, nfc_scene_mf_classic_detect_listener_callback, instance); + + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewDetectReader); +} + +bool nfc_scene_mf_classic_detect_reader_on_event(void* context, SceneManagerEvent event) { + NfcApp* instance = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcCustomEventWorkerUpdate) { + furi_timer_stop(instance->timer); + notification_message(instance->notifications, &sequence_blink_start_cyan); + + size_t nonces_pairs = 2 * mfkey32_logger_get_params_num(instance->mfkey32_logger); + detect_reader_set_state(instance->detect_reader, DetectReaderStateReaderDetected); + detect_reader_set_nonces_collected(instance->detect_reader, nonces_pairs); + if(nonces_pairs >= NFC_SCENE_DETECT_READER_PAIR_NONCES_MAX) { + if(instance->listener) { + nfc_listener_stop(instance->listener); + nfc_listener_free(instance->listener); + instance->listener = NULL; + } + detect_reader_set_state(instance->detect_reader, DetectReaderStateDone); + nfc_blink_stop(instance); + notification_message(instance->notifications, &sequence_single_vibro); + notification_message(instance->notifications, &sequence_set_green_255); + } else { + furi_timer_start(instance->timer, NFC_SCENE_DETECT_READER_WAIT_NONCES_TIMEOUT_MS); + } + consumed = true; + } else if(event.event == NfcCustomEventTimerExpired) { + detect_reader_set_state(instance->detect_reader, DetectReaderStateReaderLost); + nfc_blink_stop(instance); + notification_message(instance->notifications, &sequence_detect_reader); + } else if(event.event == NfcCustomEventViewExit) { + if(instance->listener) { + nfc_listener_stop(instance->listener); + nfc_listener_free(instance->listener); + instance->listener = NULL; + } + scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicMfkeyNoncesInfo); + consumed = true; + } + } else if(event.type == SceneManagerEventTypeBack) { + if(instance->listener) { + nfc_listener_stop(instance->listener); + nfc_listener_free(instance->listener); + instance->listener = NULL; + } + mfkey32_logger_free(instance->mfkey32_logger); + } + + return consumed; +} + +void nfc_scene_mf_classic_detect_reader_on_exit(void* context) { + NfcApp* instance = context; + + // Clear view + detect_reader_reset(instance->detect_reader); + + furi_timer_stop(instance->timer); + furi_timer_free(instance->timer); + + // Stop notifications + nfc_blink_stop(instance); + notification_message(instance->notifications, &sequence_reset_green); +} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c index 5bd24d7eac1..ff7af9e1e8b 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c @@ -1,186 +1,270 @@ -#include "../nfc_i.h" +#include "../nfc_app_i.h" + #include +#include #define TAG "NfcMfClassicDictAttack" typedef enum { - DictAttackStateIdle, DictAttackStateUserDictInProgress, - DictAttackStateFlipperDictInProgress, + DictAttackStateSystemDictInProgress, } DictAttackState; -bool nfc_dict_attack_worker_callback(NfcWorkerEvent event, void* context) { +NfcCommand nfc_dict_attack_worker_callback(NfcGenericEvent event, void* context) { furi_assert(context); - Nfc* nfc = context; - view_dispatcher_send_custom_event(nfc->view_dispatcher, event); - return true; -} + furi_assert(event.event_data); + furi_assert(event.instance); + furi_assert(event.protocol == NfcProtocolMfClassic); -void nfc_dict_attack_dict_attack_result_callback(void* context) { - furi_assert(context); - Nfc* nfc = context; - view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventDictAttackSkip); + NfcCommand command = NfcCommandContinue; + MfClassicPollerEvent* mfc_event = event.event_data; + + NfcApp* instance = context; + if(mfc_event->type == MfClassicPollerEventTypeCardDetected) { + instance->nfc_dict_context.is_card_present = true; + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventCardDetected); + } else if(mfc_event->type == MfClassicPollerEventTypeCardLost) { + instance->nfc_dict_context.is_card_present = false; + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventCardLost); + } else if(mfc_event->type == MfClassicPollerEventTypeRequestMode) { + const MfClassicData* mfc_data = + nfc_device_get_data(instance->nfc_device, NfcProtocolMfClassic); + mfc_event->data->poller_mode.mode = MfClassicPollerModeDictAttack; + mfc_event->data->poller_mode.data = mfc_data; + instance->nfc_dict_context.sectors_total = + mf_classic_get_total_sectors_num(mfc_data->type); + mf_classic_get_read_sectors_and_keys( + mfc_data, + &instance->nfc_dict_context.sectors_read, + &instance->nfc_dict_context.keys_found); + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcCustomEventDictAttackDataUpdate); + } else if(mfc_event->type == MfClassicPollerEventTypeRequestKey) { + MfClassicKey key = {}; + if(nfc_dict_get_next_key(instance->nfc_dict_context.dict, key.data, sizeof(MfClassicKey))) { + mfc_event->data->key_request_data.key = key; + mfc_event->data->key_request_data.key_provided = true; + instance->nfc_dict_context.dict_keys_current++; + if(instance->nfc_dict_context.dict_keys_current % 10 == 0) { + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcCustomEventDictAttackDataUpdate); + } + } else { + mfc_event->data->key_request_data.key_provided = false; + } + } else if(mfc_event->type == MfClassicPollerEventTypeDataUpdate) { + MfClassicPollerEventDataUpdate* data_update = &mfc_event->data->data_update; + instance->nfc_dict_context.sectors_read = data_update->sectors_read; + instance->nfc_dict_context.keys_found = data_update->keys_found; + instance->nfc_dict_context.current_sector = data_update->current_sector; + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcCustomEventDictAttackDataUpdate); + } else if(mfc_event->type == MfClassicPollerEventTypeNextSector) { + nfc_dict_rewind(instance->nfc_dict_context.dict); + instance->nfc_dict_context.dict_keys_current = 0; + instance->nfc_dict_context.current_sector = + mfc_event->data->next_sector_data.current_sector; + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcCustomEventDictAttackDataUpdate); + } else if(mfc_event->type == MfClassicPollerEventTypeFoundKeyA) { + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcCustomEventDictAttackDataUpdate); + } else if(mfc_event->type == MfClassicPollerEventTypeFoundKeyB) { + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcCustomEventDictAttackDataUpdate); + } else if(mfc_event->type == MfClassicPollerEventTypeKeyAttackStart) { + instance->nfc_dict_context.key_attack_current_sector = + mfc_event->data->key_attack_data.current_sector; + instance->nfc_dict_context.is_key_attack = true; + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcCustomEventDictAttackDataUpdate); + } else if(mfc_event->type == MfClassicPollerEventTypeKeyAttackStop) { + nfc_dict_rewind(instance->nfc_dict_context.dict); + instance->nfc_dict_context.is_key_attack = false; + instance->nfc_dict_context.dict_keys_current = 0; + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcCustomEventDictAttackDataUpdate); + } else if(mfc_event->type == MfClassicPollerEventTypeSuccess) { + const MfClassicData* mfc_data = nfc_poller_get_data(instance->poller); + nfc_device_set_data(instance->nfc_device, NfcProtocolMfClassic, mfc_data); + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcCustomEventDictAttackComplete); + command = NfcCommandStop; + } + + return command; } -static void nfc_scene_mf_classic_dict_attack_update_view(Nfc* nfc) { - MfClassicData* data = &nfc->dev->dev_data.mf_classic_data; - uint8_t sectors_read = 0; - uint8_t keys_found = 0; +void nfc_dict_attack_dict_attack_result_callback(DictAttackEvent event, void* context) { + furi_assert(context); + NfcApp* instance = context; - // Calculate found keys and read sectors - mf_classic_get_read_sectors_and_keys(data, §ors_read, &keys_found); - dict_attack_set_keys_found(nfc->dict_attack, keys_found); - dict_attack_set_sector_read(nfc->dict_attack, sectors_read); + if(event == DictAttackEventSkipPressed) { + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventDictAttackSkip); + } } -static void nfc_scene_mf_classic_dict_attack_prepare_view(Nfc* nfc, DictAttackState state) { - MfClassicData* data = &nfc->dev->dev_data.mf_classic_data; - NfcMfClassicDictAttackData* dict_attack_data = &nfc->dev->dev_data.mf_classic_dict_attack_data; - NfcWorkerState worker_state = NfcWorkerStateReady; - MfClassicDict* dict = NULL; +static void nfc_scene_mf_classic_dict_attack_update_view(NfcApp* instance) { + NfcMfClassicDictAttackContext* mfc_dict = &instance->nfc_dict_context; - // Identify scene state - if(state == DictAttackStateIdle) { - if(mf_classic_dict_check_presence(MfClassicDictTypeUser)) { - state = DictAttackStateUserDictInProgress; - } else { - state = DictAttackStateFlipperDictInProgress; - } - } else if(state == DictAttackStateUserDictInProgress) { - state = DictAttackStateFlipperDictInProgress; + if(mfc_dict->is_key_attack) { + dict_attack_set_key_attack(instance->dict_attack, mfc_dict->key_attack_current_sector); + } else { + dict_attack_reset_key_attack(instance->dict_attack); + dict_attack_set_sectors_total(instance->dict_attack, mfc_dict->sectors_total); + dict_attack_set_sectors_read(instance->dict_attack, mfc_dict->sectors_read); + dict_attack_set_keys_found(instance->dict_attack, mfc_dict->keys_found); + dict_attack_set_current_dict_key(instance->dict_attack, mfc_dict->dict_keys_current); + dict_attack_set_current_sector(instance->dict_attack, mfc_dict->current_sector); } +} - // Setup view +static void nfc_scene_mf_classic_dict_attack_prepare_view(NfcApp* instance) { + uint32_t state = + scene_manager_get_scene_state(instance->scene_manager, NfcSceneMfClassicDictAttack); if(state == DictAttackStateUserDictInProgress) { - worker_state = NfcWorkerStateMfClassicDictAttack; - dict_attack_set_header(nfc->dict_attack, "MF Classic User Dictionary"); - dict = mf_classic_dict_alloc(MfClassicDictTypeUser); - - // If failed to load user dictionary - try the system dictionary - if(!dict) { - FURI_LOG_E(TAG, "User dictionary not found"); - state = DictAttackStateFlipperDictInProgress; - } - } - if(state == DictAttackStateFlipperDictInProgress) { - worker_state = NfcWorkerStateMfClassicDictAttack; - dict_attack_set_header(nfc->dict_attack, "MF Classic System Dictionary"); - dict = mf_classic_dict_alloc(MfClassicDictTypeSystem); - if(!dict) { - FURI_LOG_E(TAG, "Flipper dictionary not found"); - // Pass through to let the worker handle the failure - } + do { + if(!nfc_dict_check_presence(NFC_APP_MF_CLASSIC_DICT_USER_PATH)) { + state = DictAttackStateSystemDictInProgress; + break; + } + + instance->nfc_dict_context.dict = nfc_dict_alloc( + NFC_APP_MF_CLASSIC_DICT_USER_PATH, NfcDictModeOpenAlways, sizeof(MfClassicKey)); + if(nfc_dict_get_total_keys(instance->nfc_dict_context.dict) == 0) { + nfc_dict_free(instance->nfc_dict_context.dict); + state = DictAttackStateSystemDictInProgress; + break; + } + + dict_attack_set_header(instance->dict_attack, "MF Classic User Dictionary"); + } while(false); } - // Free previous dictionary - if(dict_attack_data->dict) { - mf_classic_dict_free(dict_attack_data->dict); + if(state == DictAttackStateSystemDictInProgress) { + instance->nfc_dict_context.dict = nfc_dict_alloc( + NFC_APP_MF_CLASSIC_DICT_SYSTEM_PATH, NfcDictModeOpenExisting, sizeof(MfClassicKey)); + dict_attack_set_header(instance->dict_attack, "MF Classic System Dictionary"); } - dict_attack_data->dict = dict; - scene_manager_set_scene_state(nfc->scene_manager, NfcSceneMfClassicDictAttack, state); - dict_attack_set_callback(nfc->dict_attack, nfc_dict_attack_dict_attack_result_callback, nfc); - dict_attack_set_current_sector(nfc->dict_attack, 0); - dict_attack_set_card_detected(nfc->dict_attack, data->type); + + instance->nfc_dict_context.dict_keys_total = + nfc_dict_get_total_keys(instance->nfc_dict_context.dict); dict_attack_set_total_dict_keys( - nfc->dict_attack, dict ? mf_classic_dict_get_total_keys(dict) : 0); - nfc_scene_mf_classic_dict_attack_update_view(nfc); - nfc_worker_start( - nfc->worker, worker_state, &nfc->dev->dev_data, nfc_dict_attack_worker_callback, nfc); + instance->dict_attack, instance->nfc_dict_context.dict_keys_total); + instance->nfc_dict_context.dict_keys_current = 0; + + dict_attack_set_callback( + instance->dict_attack, nfc_dict_attack_dict_attack_result_callback, instance); + nfc_scene_mf_classic_dict_attack_update_view(instance); + + scene_manager_set_scene_state(instance->scene_manager, NfcSceneMfClassicDictAttack, state); } void nfc_scene_mf_classic_dict_attack_on_enter(void* context) { - Nfc* nfc = context; - nfc_scene_mf_classic_dict_attack_prepare_view(nfc, DictAttackStateIdle); - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewDictAttack); - nfc_blink_read_start(nfc); - notification_message(nfc->notifications, &sequence_display_backlight_enforce_on); + NfcApp* instance = context; + + scene_manager_set_scene_state( + instance->scene_manager, NfcSceneMfClassicDictAttack, DictAttackStateUserDictInProgress); + nfc_scene_mf_classic_dict_attack_prepare_view(instance); + dict_attack_set_card_state(instance->dict_attack, true); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewDictAttack); + nfc_blink_read_start(instance); + notification_message(instance->notifications, &sequence_display_backlight_enforce_on); + + instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfClassic); + nfc_poller_start(instance->poller, nfc_dict_attack_worker_callback, instance); } bool nfc_scene_mf_classic_dict_attack_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - MfClassicData* data = &nfc->dev->dev_data.mf_classic_data; + NfcApp* instance = context; bool consumed = false; uint32_t state = - scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfClassicDictAttack); + scene_manager_get_scene_state(instance->scene_manager, NfcSceneMfClassicDictAttack); if(event.type == SceneManagerEventTypeCustom) { - if(event.event == NfcWorkerEventSuccess) { + if(event.event == NfcCustomEventDictAttackComplete) { if(state == DictAttackStateUserDictInProgress) { - nfc_worker_stop(nfc->worker); - nfc_scene_mf_classic_dict_attack_prepare_view(nfc, state); + nfc_poller_stop(instance->poller); + nfc_poller_free(instance->poller); + nfc_dict_free(instance->nfc_dict_context.dict); + scene_manager_set_scene_state( + instance->scene_manager, + NfcSceneMfClassicDictAttack, + DictAttackStateSystemDictInProgress); + nfc_scene_mf_classic_dict_attack_prepare_view(instance); + instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfClassic); + nfc_poller_start(instance->poller, nfc_dict_attack_worker_callback, instance); consumed = true; } else { - notification_message(nfc->notifications, &sequence_success); - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicReadSuccess); + notification_message(instance->notifications, &sequence_success); + scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); dolphin_deed(DolphinDeedNfcReadSuccess); consumed = true; } - } else if(event.event == NfcWorkerEventAborted) { - if(state == DictAttackStateUserDictInProgress && - dict_attack_get_card_state(nfc->dict_attack)) { - nfc_scene_mf_classic_dict_attack_prepare_view(nfc, state); - consumed = true; - } else { - notification_message(nfc->notifications, &sequence_success); - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicReadSuccess); - // Counting failed attempts too - dolphin_deed(DolphinDeedNfcReadSuccess); - consumed = true; - } - } else if(event.event == NfcWorkerEventCardDetected) { - dict_attack_set_card_detected(nfc->dict_attack, data->type); - consumed = true; - } else if(event.event == NfcWorkerEventNoCardDetected) { - dict_attack_set_card_removed(nfc->dict_attack); + } else if(event.event == NfcCustomEventCardDetected) { + dict_attack_set_card_state(instance->dict_attack, true); consumed = true; - } else if(event.event == NfcWorkerEventFoundKeyA) { - dict_attack_inc_keys_found(nfc->dict_attack); - consumed = true; - } else if(event.event == NfcWorkerEventFoundKeyB) { - dict_attack_inc_keys_found(nfc->dict_attack); - consumed = true; - } else if(event.event == NfcWorkerEventNewSector) { - nfc_scene_mf_classic_dict_attack_update_view(nfc); - dict_attack_inc_current_sector(nfc->dict_attack); - consumed = true; - } else if(event.event == NfcWorkerEventNewDictKeyBatch) { - nfc_scene_mf_classic_dict_attack_update_view(nfc); - dict_attack_inc_current_dict_key(nfc->dict_attack, NFC_DICT_KEY_BATCH_SIZE); + } else if(event.event == NfcCustomEventCardLost) { + dict_attack_set_card_state(instance->dict_attack, false); consumed = true; + } else if(event.event == NfcCustomEventDictAttackDataUpdate) { + nfc_scene_mf_classic_dict_attack_update_view(instance); } else if(event.event == NfcCustomEventDictAttackSkip) { + const MfClassicData* mfc_data = nfc_poller_get_data(instance->poller); + nfc_device_set_data(instance->nfc_device, NfcProtocolMfClassic, mfc_data); if(state == DictAttackStateUserDictInProgress) { - nfc_worker_stop(nfc->worker); + if(instance->nfc_dict_context.is_card_present) { + nfc_poller_stop(instance->poller); + nfc_poller_free(instance->poller); + nfc_dict_free(instance->nfc_dict_context.dict); + scene_manager_set_scene_state( + instance->scene_manager, + NfcSceneMfClassicDictAttack, + DictAttackStateSystemDictInProgress); + nfc_scene_mf_classic_dict_attack_prepare_view(instance); + instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfClassic); + nfc_poller_start(instance->poller, nfc_dict_attack_worker_callback, instance); + } else { + notification_message(instance->notifications, &sequence_success); + scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); + dolphin_deed(DolphinDeedNfcReadSuccess); + } consumed = true; - } else if(state == DictAttackStateFlipperDictInProgress) { - nfc_worker_stop(nfc->worker); + } else if(state == DictAttackStateSystemDictInProgress) { + notification_message(instance->notifications, &sequence_success); + scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); + dolphin_deed(DolphinDeedNfcReadSuccess); consumed = true; } - } else if(event.event == NfcWorkerEventKeyAttackStart) { - dict_attack_set_key_attack( - nfc->dict_attack, - true, - nfc->dev->dev_data.mf_classic_dict_attack_data.current_sector); - } else if(event.event == NfcWorkerEventKeyAttackStop) { - dict_attack_set_key_attack(nfc->dict_attack, false, 0); - } else if(event.event == NfcWorkerEventKeyAttackNextSector) { - dict_attack_inc_key_attack_current_sector(nfc->dict_attack); } } else if(event.type == SceneManagerEventTypeBack) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneExitConfirm); + scene_manager_next_scene(instance->scene_manager, NfcSceneExitConfirm); consumed = true; } return consumed; } void nfc_scene_mf_classic_dict_attack_on_exit(void* context) { - Nfc* nfc = context; - NfcMfClassicDictAttackData* dict_attack_data = &nfc->dev->dev_data.mf_classic_dict_attack_data; - // Stop worker - nfc_worker_stop(nfc->worker); - if(dict_attack_data->dict) { - mf_classic_dict_free(dict_attack_data->dict); - dict_attack_data->dict = NULL; - } - dict_attack_reset(nfc->dict_attack); - nfc_blink_stop(nfc); - notification_message(nfc->notifications, &sequence_display_backlight_enforce_auto); + NfcApp* instance = context; + + nfc_poller_stop(instance->poller); + nfc_poller_free(instance->poller); + + dict_attack_reset(instance->dict_attack); + scene_manager_set_scene_state( + instance->scene_manager, NfcSceneMfClassicDictAttack, DictAttackStateUserDictInProgress); + + nfc_dict_free(instance->nfc_dict_context.dict); + + instance->nfc_dict_context.current_sector = 0; + instance->nfc_dict_context.sectors_total = 0; + instance->nfc_dict_context.sectors_read = 0; + instance->nfc_dict_context.keys_found = 0; + instance->nfc_dict_context.dict_keys_total = 0; + instance->nfc_dict_context.dict_keys_current = 0; + instance->nfc_dict_context.is_key_attack = false; + instance->nfc_dict_context.key_attack_current_sector = 0; + instance->nfc_dict_context.is_card_present = false; + + nfc_blink_stop(instance); + notification_message(instance->notifications, &sequence_display_backlight_enforce_auto); } diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_emulate.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_emulate.c deleted file mode 100644 index 8c0f493e12b..00000000000 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_emulate.c +++ /dev/null @@ -1,69 +0,0 @@ -#include "../nfc_i.h" - -#define NFC_MF_CLASSIC_DATA_NOT_CHANGED (0UL) -#define NFC_MF_CLASSIC_DATA_CHANGED (1UL) - -bool nfc_mf_classic_emulate_worker_callback(NfcWorkerEvent event, void* context) { - UNUSED(event); - Nfc* nfc = context; - - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneMfClassicEmulate, NFC_MF_CLASSIC_DATA_CHANGED); - return true; -} - -void nfc_scene_mf_classic_emulate_on_enter(void* context) { - Nfc* nfc = context; - - // Setup view - Popup* popup = nfc->popup; - popup_set_header(popup, "Emulating", 67, 13, AlignLeft, AlignTop); - if(strcmp(nfc->dev->dev_name, "") != 0) { - nfc_text_store_set(nfc, "%s", nfc->dev->dev_name); - } else { - nfc_text_store_set(nfc, "MIFARE\nClassic"); - } - popup_set_icon(popup, 0, 3, &I_NFC_dolphin_emulation_47x61); - popup_set_text(popup, nfc->text_store, 90, 28, AlignCenter, AlignTop); - - // Setup and start worker - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); - nfc_worker_start( - nfc->worker, - NfcWorkerStateMfClassicEmulate, - &nfc->dev->dev_data, - nfc_mf_classic_emulate_worker_callback, - nfc); - nfc_blink_emulate_start(nfc); -} - -bool nfc_scene_mf_classic_emulate_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - - 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, NfcSceneMfClassicEmulate) == - NFC_MF_CLASSIC_DATA_CHANGED) { - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneMfClassicEmulate, NFC_MF_CLASSIC_DATA_NOT_CHANGED); - // Save shadow file - if(furi_string_size(nfc->dev->load_path)) { - nfc_device_save_shadow(nfc->dev, furi_string_get_cstr(nfc->dev->load_path)); - } - } - consumed = false; - } - return consumed; -} - -void nfc_scene_mf_classic_emulate_on_exit(void* context) { - Nfc* nfc = context; - - // Clear view - popup_reset(nfc->popup); - - nfc_blink_stop(nfc); -} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys.c index 8a7dc2c1839..3106c740aea 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys.c @@ -1,62 +1,86 @@ -#include "../nfc_i.h" +#include "../nfc_app_i.h" + +#define NFC_SCENE_MF_CLASSIC_KEYS_MAX (100) void nfc_scene_mf_classic_keys_widget_callback(GuiButtonType result, InputType type, void* context) { - Nfc* nfc = context; + NfcApp* instance = context; if(type == InputTypeShort) { - view_dispatcher_send_custom_event(nfc->view_dispatcher, result); + view_dispatcher_send_custom_event(instance->view_dispatcher, result); } } void nfc_scene_mf_classic_keys_on_enter(void* context) { - Nfc* nfc = context; + NfcApp* instance = context; // Load flipper dict keys total uint32_t flipper_dict_keys_total = 0; - MfClassicDict* dict = mf_classic_dict_alloc(MfClassicDictTypeSystem); + NfcDict* dict = nfc_dict_alloc( + NFC_APP_MF_CLASSIC_DICT_SYSTEM_PATH, NfcDictModeOpenExisting, sizeof(MfClassicKey)); if(dict) { - flipper_dict_keys_total = mf_classic_dict_get_total_keys(dict); - mf_classic_dict_free(dict); + flipper_dict_keys_total = nfc_dict_get_total_keys(dict); + nfc_dict_free(dict); } + // Load user dict keys total uint32_t user_dict_keys_total = 0; - dict = mf_classic_dict_alloc(MfClassicDictTypeUser); + dict = nfc_dict_alloc( + NFC_APP_MF_CLASSIC_DICT_USER_PATH, NfcDictModeOpenAlways, sizeof(MfClassicKey)); if(dict) { - user_dict_keys_total = mf_classic_dict_get_total_keys(dict); - mf_classic_dict_free(dict); + user_dict_keys_total = nfc_dict_get_total_keys(dict); + nfc_dict_free(dict); } + FuriString* temp_str = furi_string_alloc(); + widget_add_string_element( + instance->widget, 0, 0, AlignLeft, AlignTop, FontPrimary, "MIFARE Classic Keys"); + furi_string_printf(temp_str, "System dict: %lu", flipper_dict_keys_total); + widget_add_string_element( + instance->widget, + 0, + 20, + AlignLeft, + AlignTop, + FontSecondary, + furi_string_get_cstr(temp_str)); + furi_string_printf(temp_str, "User dict: %lu", user_dict_keys_total); widget_add_string_element( - nfc->widget, 0, 0, AlignLeft, AlignTop, FontPrimary, "MIFARE Classic Keys"); - char temp_str[32]; - snprintf(temp_str, sizeof(temp_str), "System dict: %lu", flipper_dict_keys_total); - widget_add_string_element(nfc->widget, 0, 20, AlignLeft, AlignTop, FontSecondary, temp_str); - snprintf(temp_str, sizeof(temp_str), "User dict: %lu", user_dict_keys_total); - widget_add_string_element(nfc->widget, 0, 32, AlignLeft, AlignTop, FontSecondary, temp_str); + instance->widget, + 0, + 32, + AlignLeft, + AlignTop, + FontSecondary, + furi_string_get_cstr(temp_str)); + widget_add_icon_element(instance->widget, 87, 13, &I_Keychain_39x36); widget_add_button_element( - nfc->widget, GuiButtonTypeCenter, "Add", nfc_scene_mf_classic_keys_widget_callback, nfc); - widget_add_icon_element(nfc->widget, 87, 13, &I_Keychain_39x36); + instance->widget, + GuiButtonTypeCenter, + "Add", + nfc_scene_mf_classic_keys_widget_callback, + instance); if(user_dict_keys_total > 0) { widget_add_button_element( - nfc->widget, + instance->widget, GuiButtonTypeRight, "List", nfc_scene_mf_classic_keys_widget_callback, - nfc); + instance); } + furi_string_free(temp_str); - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget); } bool nfc_scene_mf_classic_keys_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; + NfcApp* instance = context; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { if(event.event == GuiButtonTypeCenter) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicKeysAdd); + scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicKeysAdd); consumed = true; } else if(event.event == GuiButtonTypeRight) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicKeysList); + scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicKeysList); consumed = true; } } @@ -65,7 +89,7 @@ bool nfc_scene_mf_classic_keys_on_event(void* context, SceneManagerEvent event) } void nfc_scene_mf_classic_keys_on_exit(void* context) { - Nfc* nfc = context; + NfcApp* instance = context; - widget_reset(nfc->widget); + widget_reset(instance->widget); } diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_add.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_add.c index 3a999f03114..82400034359 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_add.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_add.c @@ -1,60 +1,62 @@ -#include "../nfc_i.h" -#include +#include "../nfc_app_i.h" void nfc_scene_mf_classic_keys_add_byte_input_callback(void* context) { - Nfc* nfc = context; + NfcApp* instance = context; - view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventByteInputDone); + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventByteInputDone); } void nfc_scene_mf_classic_keys_add_on_enter(void* context) { - Nfc* nfc = context; + NfcApp* instance = context; // Setup view - ByteInput* byte_input = nfc->byte_input; + ByteInput* byte_input = instance->byte_input; byte_input_set_header_text(byte_input, "Enter the key in hex"); byte_input_set_result_callback( byte_input, nfc_scene_mf_classic_keys_add_byte_input_callback, NULL, - nfc, - nfc->byte_input_store, - 6); - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewByteInput); + instance, + instance->byte_input_store, + sizeof(MfClassicKey)); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewByteInput); } bool nfc_scene_mf_classic_keys_add_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; + NfcApp* instance = context; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { if(event.event == NfcCustomEventByteInputDone) { // Add key to dict - MfClassicDict* dict = mf_classic_dict_alloc(MfClassicDictTypeUser); - if(dict) { - if(mf_classic_dict_is_key_present(dict, nfc->byte_input_store)) { - scene_manager_next_scene( - nfc->scene_manager, NfcSceneMfClassicKeysWarnDuplicate); - } else if(mf_classic_dict_add_key(dict, nfc->byte_input_store)) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveSuccess); - dolphin_deed(DolphinDeedNfcMfcAdd); - } else { - scene_manager_next_scene(nfc->scene_manager, NfcSceneDictNotFound); - } + NfcDict* dict = nfc_dict_alloc( + NFC_APP_MF_CLASSIC_DICT_USER_PATH, NfcDictModeOpenAlways, sizeof(MfClassicKey)); + furi_assert(dict); + + MfClassicKey key = {}; + memcpy(key.data, instance->byte_input_store, sizeof(MfClassicKey)); + if(nfc_dict_is_key_present(dict, key.data, sizeof(MfClassicKey))) { + scene_manager_next_scene( + instance->scene_manager, NfcSceneMfClassicKeysWarnDuplicate); + } else if(nfc_dict_add_key(dict, key.data, sizeof(MfClassicKey))) { + scene_manager_next_scene(instance->scene_manager, NfcSceneSaveSuccess); + dolphin_deed(DolphinDeedNfcMfcAdd); } else { - scene_manager_next_scene(nfc->scene_manager, NfcSceneDictNotFound); + scene_manager_previous_scene(instance->scene_manager); } - mf_classic_dict_free(dict); + + nfc_dict_free(dict); consumed = true; } } + return consumed; } void nfc_scene_mf_classic_keys_add_on_exit(void* context) { - Nfc* nfc = context; + NfcApp* instance = context; // Clear view - byte_input_set_result_callback(nfc->byte_input, NULL, NULL, NULL, NULL, 0); - byte_input_set_header_text(nfc->byte_input, ""); + byte_input_set_result_callback(instance->byte_input, NULL, NULL, NULL, NULL, 0); + byte_input_set_header_text(instance->byte_input, ""); } diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_delete.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_delete.c index 0ea3f59a45e..b245a2a552d 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_delete.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_delete.c @@ -1,42 +1,40 @@ -#include "../nfc_i.h" +#include "../nfc_app_i.h" void nfc_scene_mf_classic_keys_delete_widget_callback( GuiButtonType result, InputType type, void* context) { - Nfc* nfc = context; + NfcApp* instance = context; if(type == InputTypeShort) { - view_dispatcher_send_custom_event(nfc->view_dispatcher, result); + view_dispatcher_send_custom_event(instance->view_dispatcher, result); } } void nfc_scene_mf_classic_keys_delete_on_enter(void* context) { - Nfc* nfc = context; - MfClassicDict* dict = mf_classic_dict_alloc(MfClassicDictTypeUser); + NfcApp* instance = context; + uint32_t key_index = - scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfClassicKeysDelete); - // Setup Custom Widget view - FuriString* key_str; - key_str = furi_string_alloc(); + scene_manager_get_scene_state(instance->scene_manager, NfcSceneMfClassicKeysDelete); + FuriString* key_str = furi_string_alloc(); widget_add_string_element( - nfc->widget, 64, 0, AlignCenter, AlignTop, FontPrimary, "Delete this key?"); + instance->widget, 64, 0, AlignCenter, AlignTop, FontPrimary, "Delete this key?"); widget_add_button_element( - nfc->widget, + instance->widget, GuiButtonTypeLeft, "Cancel", nfc_scene_mf_classic_keys_delete_widget_callback, - nfc); + instance); widget_add_button_element( - nfc->widget, + instance->widget, GuiButtonTypeRight, "Delete", nfc_scene_mf_classic_keys_delete_widget_callback, - nfc); + instance); - mf_classic_dict_get_key_at_index_str(dict, key_str, key_index); + mf_user_dict_get_key_str(instance->mf_user_dict, key_index, key_str); widget_add_string_element( - nfc->widget, + instance->widget, 64, 32, AlignCenter, @@ -45,39 +43,35 @@ void nfc_scene_mf_classic_keys_delete_on_enter(void* context) { furi_string_get_cstr(key_str)); furi_string_free(key_str); - mf_classic_dict_free(dict); - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget); } bool nfc_scene_mf_classic_keys_delete_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; + NfcApp* instance = context; bool consumed = false; - uint32_t key_index = - scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfClassicKeysDelete); if(event.type == SceneManagerEventTypeCustom) { - if(event.event == GuiButtonTypeLeft) { - consumed = scene_manager_search_and_switch_to_previous_scene( - nfc->scene_manager, NfcSceneMfClassicKeys); - } else if(event.event == GuiButtonTypeRight) { - MfClassicDict* dict = mf_classic_dict_alloc(MfClassicDictTypeUser); - if(mf_classic_dict_delete_index(dict, key_index)) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneDeleteSuccess); + if(event.event == GuiButtonTypeRight) { + uint32_t key_index = scene_manager_get_scene_state( + instance->scene_manager, NfcSceneMfClassicKeysDelete); + if(mf_user_dict_delete_key(instance->mf_user_dict, key_index)) { + scene_manager_next_scene(instance->scene_manager, NfcSceneDeleteSuccess); } else { - scene_manager_search_and_switch_to_previous_scene( - nfc->scene_manager, NfcSceneMfClassicKeys); + scene_manager_previous_scene(instance->scene_manager); } - mf_classic_dict_free(dict); - consumed = true; + } else if(event.event == GuiButtonTypeLeft) { + scene_manager_previous_scene(instance->scene_manager); } + consumed = true; } return consumed; } void nfc_scene_mf_classic_keys_delete_on_exit(void* context) { - Nfc* nfc = context; + NfcApp* instance = context; - widget_reset(nfc->widget); + mf_user_dict_free(instance->mf_user_dict); + widget_reset(instance->widget); } diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_list.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_list.c index 57f9fe65624..7370c06840e 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_list.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_list.c @@ -1,100 +1,51 @@ -#include "../nfc_i.h" +#include "../nfc_app_i.h" #define NFC_SCENE_MF_CLASSIC_KEYS_LIST_MAX (100) void nfc_scene_mf_classic_keys_list_submenu_callback(void* context, uint32_t index) { - furi_assert(context); + NfcApp* instance = context; - Nfc* nfc = context; - view_dispatcher_send_custom_event(nfc->view_dispatcher, index); + view_dispatcher_send_custom_event(instance->view_dispatcher, index); } -void nfc_scene_mf_classic_keys_list_popup_callback(void* context) { - furi_assert(context); - - Nfc* nfc = context; - view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit); -} +void nfc_scene_mf_classic_keys_list_on_enter(void* context) { + NfcApp* instance = context; -void nfc_scene_mf_classic_keys_list_prepare(Nfc* nfc, MfClassicDict* dict) { - Submenu* submenu = nfc->submenu; - uint32_t index = 0; - FuriString* temp_key; - temp_key = furi_string_alloc(); + instance->mf_user_dict = mf_user_dict_alloc(NFC_SCENE_MF_CLASSIC_KEYS_LIST_MAX); - submenu_set_header(submenu, "Select key to delete:"); - while(mf_classic_dict_get_next_key_str(dict, temp_key)) { - char* current_key = (char*)malloc(sizeof(char) * 13); - strncpy(current_key, furi_string_get_cstr(temp_key), 12); - MfClassicUserKeys_push_back(nfc->mfc_key_strs, current_key); - FURI_LOG_D("ListKeys", "Key %lu: %s", index, current_key); + submenu_set_header(instance->submenu, "Select key to delete:"); + FuriString* temp_str = furi_string_alloc(); + for(size_t i = 0; i < mf_user_dict_get_keys_cnt(instance->mf_user_dict); i++) { + mf_user_dict_get_key_str(instance->mf_user_dict, i, temp_str); submenu_add_item( - submenu, current_key, index++, nfc_scene_mf_classic_keys_list_submenu_callback, nfc); + instance->submenu, + furi_string_get_cstr(temp_str), + i, + nfc_scene_mf_classic_keys_list_submenu_callback, + instance); } - furi_string_free(temp_key); -} + furi_string_free(temp_str); -void nfc_scene_mf_classic_keys_list_on_enter(void* context) { - Nfc* nfc = context; - MfClassicDict* dict = mf_classic_dict_alloc(MfClassicDictTypeUser); - MfClassicUserKeys_init(nfc->mfc_key_strs); - if(dict) { - uint32_t total_user_keys = mf_classic_dict_get_total_keys(dict); - if(total_user_keys < NFC_SCENE_MF_CLASSIC_KEYS_LIST_MAX) { - nfc_scene_mf_classic_keys_list_prepare(nfc, dict); - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); - } else { - popup_set_header(nfc->popup, "Too many keys!", 64, 0, AlignCenter, AlignTop); - popup_set_text( - nfc->popup, - "Edit user dictionary\nwith file browser", - 64, - 12, - AlignCenter, - AlignTop); - popup_set_callback(nfc->popup, nfc_scene_mf_classic_keys_list_popup_callback); - popup_set_context(nfc->popup, nfc); - popup_set_timeout(nfc->popup, 3000); - popup_enable_timeout(nfc->popup); - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); - } - mf_classic_dict_free(dict); - } else { - popup_set_header( - nfc->popup, "Failed to load dictionary", 64, 32, AlignCenter, AlignCenter); - popup_set_callback(nfc->popup, nfc_scene_mf_classic_keys_list_popup_callback); - popup_set_context(nfc->popup, nfc); - popup_set_timeout(nfc->popup, 3000); - popup_enable_timeout(nfc->popup); - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); - } + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewMenu); } bool nfc_scene_mf_classic_keys_list_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; + NfcApp* instance = context; + bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { - if(event.event == NfcCustomEventViewExit) { - consumed = scene_manager_previous_scene(nfc->scene_manager); - } else { - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneMfClassicKeysDelete, event.event); - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicKeysDelete); - consumed = true; - } + scene_manager_set_scene_state( + instance->scene_manager, NfcSceneMfClassicKeysDelete, event.event); + scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicKeysDelete); + } else if(event.type == SceneManagerEventTypeBack) { + mf_user_dict_free(instance->mf_user_dict); } + return consumed; } void nfc_scene_mf_classic_keys_list_on_exit(void* context) { - Nfc* nfc = context; + NfcApp* instance = context; - MfClassicUserKeys_it_t it; - for(MfClassicUserKeys_it(it, nfc->mfc_key_strs); !MfClassicUserKeys_end_p(it); - MfClassicUserKeys_next(it)) { - free(*MfClassicUserKeys_ref(it)); - } - MfClassicUserKeys_clear(nfc->mfc_key_strs); - submenu_reset(nfc->submenu); - popup_reset(nfc->popup); + submenu_reset(instance->submenu); } diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_warn_duplicate.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_warn_duplicate.c index ab41989b2c7..991c956c1c0 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_warn_duplicate.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_warn_duplicate.c @@ -1,15 +1,16 @@ -#include "../nfc_i.h" +#include "../nfc_app_i.h" void nfc_scene_mf_classic_keys_warn_duplicate_popup_callback(void* context) { - Nfc* nfc = context; - view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit); + NfcApp* instance = context; + + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventViewExit); } void nfc_scene_mf_classic_keys_warn_duplicate_on_enter(void* context) { - Nfc* nfc = context; + NfcApp* instance = context; // Setup view - Popup* popup = nfc->popup; + Popup* popup = instance->popup; popup_set_icon(popup, 72, 16, &I_DolphinCommon_56x48); popup_set_header(popup, "Key already exists!", 64, 3, AlignCenter, AlignTop); popup_set_text( @@ -20,28 +21,29 @@ void nfc_scene_mf_classic_keys_warn_duplicate_on_enter(void* context) { 24, AlignLeft, AlignTop); - popup_set_timeout(popup, 5000); - popup_set_context(popup, nfc); + popup_set_timeout(popup, 1500); + popup_set_context(popup, instance); popup_set_callback(popup, nfc_scene_mf_classic_keys_warn_duplicate_popup_callback); popup_enable_timeout(popup); - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup); } bool nfc_scene_mf_classic_keys_warn_duplicate_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; + NfcApp* instance = context; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { if(event.event == NfcCustomEventViewExit) { consumed = scene_manager_search_and_switch_to_previous_scene( - nfc->scene_manager, NfcSceneMfClassicKeysAdd); + instance->scene_manager, NfcSceneMfClassicKeysAdd); } } + return consumed; } void nfc_scene_mf_classic_keys_warn_duplicate_on_exit(void* context) { - Nfc* nfc = context; + NfcApp* instance = context; - popup_reset(nfc->popup); + popup_reset(instance->popup); } diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_menu.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_menu.c deleted file mode 100644 index 9c416367646..00000000000 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_menu.c +++ /dev/null @@ -1,82 +0,0 @@ -#include "../nfc_i.h" -#include - -enum SubmenuIndex { - SubmenuIndexSave, - SubmenuIndexEmulate, - SubmenuIndexDetectReader, - SubmenuIndexInfo, -}; - -void nfc_scene_mf_classic_menu_submenu_callback(void* context, uint32_t index) { - Nfc* nfc = context; - - view_dispatcher_send_custom_event(nfc->view_dispatcher, index); -} - -void nfc_scene_mf_classic_menu_on_enter(void* context) { - Nfc* nfc = context; - Submenu* submenu = nfc->submenu; - - submenu_add_item( - submenu, "Save", SubmenuIndexSave, nfc_scene_mf_classic_menu_submenu_callback, nfc); - submenu_add_item( - submenu, "Emulate", SubmenuIndexEmulate, nfc_scene_mf_classic_menu_submenu_callback, nfc); - if(!mf_classic_is_card_read(&nfc->dev->dev_data.mf_classic_data)) { - submenu_add_item( - submenu, - "Detect Reader", - SubmenuIndexDetectReader, - nfc_scene_mf_classic_menu_submenu_callback, - nfc); - } - submenu_add_item( - submenu, "Info", SubmenuIndexInfo, nfc_scene_mf_classic_menu_submenu_callback, nfc); - - submenu_set_selected_item( - nfc->submenu, scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfClassicMenu)); - - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); -} - -bool nfc_scene_mf_classic_menu_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - scene_manager_set_scene_state(nfc->scene_manager, NfcSceneMfClassicMenu, event.event); - if(event.event == 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_next_scene(nfc->scene_manager, NfcSceneMfClassicEmulate); - if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSetType)) { - dolphin_deed(DolphinDeedNfcAddEmulate); - } else { - dolphin_deed(DolphinDeedNfcEmulate); - } - consumed = true; - } else if(event.event == SubmenuIndexDetectReader) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneDetectReader); - dolphin_deed(DolphinDeedNfcDetectReader); - consumed = true; - } else if(event.event == SubmenuIndexInfo) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcDataInfo); - consumed = true; - } - } else if(event.type == SceneManagerEventTypeBack) { - consumed = scene_manager_previous_scene(nfc->scene_manager); - } - - return consumed; -} - -void nfc_scene_mf_classic_menu_on_exit(void* context) { - Nfc* nfc = context; - - // Clear view - submenu_reset(nfc->submenu); -} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_mfkey_complete.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_mfkey_complete.c new file mode 100644 index 00000000000..8e07043e25b --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_mfkey_complete.c @@ -0,0 +1,58 @@ +#include "../nfc_app_i.h" + +void nfc_scene_mf_classic_mfkey_complete_callback( + GuiButtonType result, + InputType type, + void* context) { + NfcApp* instance = context; + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(instance->view_dispatcher, result); + } +} + +void nfc_scene_mf_classic_mfkey_complete_on_enter(void* context) { + NfcApp* instance = context; + + widget_add_string_element( + instance->widget, 64, 0, AlignCenter, AlignTop, FontPrimary, "Complete!"); + widget_add_string_multiline_element( + instance->widget, + 64, + 32, + AlignCenter, + AlignCenter, + FontSecondary, + "Now use Mfkey32\nto extract keys"); + widget_add_button_element( + instance->widget, + GuiButtonTypeCenter, + "OK", + nfc_scene_mf_classic_mfkey_complete_callback, + instance); + + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget); +} + +bool nfc_scene_mf_classic_mfkey_complete_on_event(void* context, SceneManagerEvent event) { + NfcApp* instance = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == GuiButtonTypeCenter) { + consumed = scene_manager_search_and_switch_to_previous_scene( + instance->scene_manager, NfcSceneStart); + } + } else if(event.type == SceneManagerEventTypeBack) { + const uint32_t prev_scenes[] = {NfcSceneSavedMenu, NfcSceneStart}; + consumed = scene_manager_search_and_switch_to_previous_scene_one_of( + instance->scene_manager, prev_scenes, COUNT_OF(prev_scenes)); + } + + return consumed; +} + +void nfc_scene_mf_classic_mfkey_complete_on_exit(void* context) { + NfcApp* instance = context; + + widget_reset(instance->widget); +} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_mfkey_nonces_info.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_mfkey_nonces_info.c new file mode 100644 index 00000000000..7f968b1dd68 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_mfkey_nonces_info.c @@ -0,0 +1,72 @@ +#include "../nfc_app_i.h" + +void nfc_scene_mf_classic_mfkey_nonces_info_callback( + GuiButtonType result, + InputType type, + void* context) { + NfcApp* instance = context; + + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(instance->view_dispatcher, result); + } +} + +void nfc_scene_mf_classic_mfkey_nonces_info_on_enter(void* context) { + NfcApp* instance = context; + + FuriString* temp_str = furi_string_alloc(); + + size_t mfkey_params_saved = mfkey32_logger_get_params_num(instance->mfkey32_logger); + furi_string_printf(temp_str, "Nonce pairs saved: %zu\n", mfkey_params_saved); + widget_add_string_element( + instance->widget, 0, 0, AlignLeft, AlignTop, FontPrimary, furi_string_get_cstr(temp_str)); + widget_add_string_element( + instance->widget, 0, 12, AlignLeft, AlignTop, FontSecondary, "Authenticated sectors:"); + + mfkey32_logger_get_params_data(instance->mfkey32_logger, temp_str); + widget_add_text_scroll_element( + instance->widget, 0, 22, 128, 42, furi_string_get_cstr(temp_str)); + widget_add_button_element( + instance->widget, + GuiButtonTypeCenter, + "OK", + nfc_scene_mf_classic_mfkey_nonces_info_callback, + instance); + + furi_string_free(temp_str); + + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget); +} + +bool nfc_scene_mf_classic_mfkey_nonces_info_on_event(void* context, SceneManagerEvent event) { + NfcApp* instance = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == GuiButtonTypeCenter) { + if(mfkey32_logger_save_params( + instance->mfkey32_logger, NFC_APP_MFKEY32_LOGS_FILE_PATH)) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicMfkeyComplete); + } else { + scene_manager_search_and_switch_to_previous_scene( + instance->scene_manager, NfcSceneStart); + } + consumed = true; + } + } else if(event.type == SceneManagerEventTypeBack) { + const uint32_t prev_scenes[] = {NfcSceneSavedMenu, NfcSceneStart}; + consumed = scene_manager_search_and_switch_to_previous_scene_one_of( + instance->scene_manager, prev_scenes, COUNT_OF(prev_scenes)); + } + + return consumed; +} + +void nfc_scene_mf_classic_mfkey_nonces_info_on_exit(void* context) { + NfcApp* instance = context; + + mfkey32_logger_free(instance->mfkey32_logger); + + // Clear view + widget_reset(instance->widget); +} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_read_success.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_read_success.c deleted file mode 100644 index 444c9a540e7..00000000000 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_read_success.c +++ /dev/null @@ -1,82 +0,0 @@ -#include "../nfc_i.h" - -void nfc_scene_mf_classic_read_success_widget_callback( - GuiButtonType result, - InputType type, - void* context) { - furi_assert(context); - Nfc* nfc = context; - - if(type == InputTypeShort) { - view_dispatcher_send_custom_event(nfc->view_dispatcher, result); - } -} - -void nfc_scene_mf_classic_read_success_on_enter(void* context) { - Nfc* nfc = context; - NfcDeviceData* dev_data = &nfc->dev->dev_data; - MfClassicData* mf_data = &dev_data->mf_classic_data; - - // Setup view - Widget* widget = nfc->widget; - widget_add_button_element( - widget, GuiButtonTypeLeft, "Retry", nfc_scene_mf_classic_read_success_widget_callback, nfc); - widget_add_button_element( - widget, GuiButtonTypeRight, "More", nfc_scene_mf_classic_read_success_widget_callback, nfc); - - FuriString* temp_str = NULL; - if(furi_string_size(nfc->dev->dev_data.parsed_data)) { - temp_str = furi_string_alloc_set(nfc->dev->dev_data.parsed_data); - } else { - temp_str = furi_string_alloc_printf("\e#%s\n", nfc_mf_classic_type(mf_data->type)); - furi_string_cat_printf(temp_str, "UID:"); - for(size_t i = 0; i < dev_data->nfc_data.uid_len; i++) { - furi_string_cat_printf(temp_str, " %02X", dev_data->nfc_data.uid[i]); - } - uint8_t sectors_total = mf_classic_get_total_sectors_num(mf_data->type); - uint8_t keys_total = sectors_total * 2; - uint8_t keys_found = 0; - uint8_t sectors_read = 0; - mf_classic_get_read_sectors_and_keys(mf_data, §ors_read, &keys_found); - furi_string_cat_printf(temp_str, "\nKeys Found: %d/%d", keys_found, keys_total); - furi_string_cat_printf(temp_str, "\nSectors Read: %d/%d", sectors_read, sectors_total); - } - - widget_add_text_scroll_element(widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); - furi_string_free(temp_str); - - notification_message_block(nfc->notifications, &sequence_set_green_255); - - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); -} - -bool nfc_scene_mf_classic_read_success_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == GuiButtonTypeLeft) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneRetryConfirm); - consumed = true; - } else if(event.event == GuiButtonTypeRight) { - // Clear device name - nfc_device_set_name(nfc->dev, ""); - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicMenu); - consumed = true; - } - } else if(event.type == SceneManagerEventTypeBack) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneExitConfirm); - consumed = true; - } - - return consumed; -} - -void nfc_scene_mf_classic_read_success_on_exit(void* context) { - Nfc* nfc = context; - - notification_message_block(nfc->notifications, &sequence_reset_green); - - // Clear view - widget_reset(nfc->widget); -} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_update.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_update.c deleted file mode 100644 index ffef1b7b9f0..00000000000 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_update.c +++ /dev/null @@ -1,98 +0,0 @@ -#include "../nfc_i.h" -#include - -enum { - NfcSceneMfClassicUpdateStateCardSearch, - NfcSceneMfClassicUpdateStateCardFound, -}; - -bool nfc_mf_classic_update_worker_callback(NfcWorkerEvent event, void* context) { - furi_assert(context); - - Nfc* nfc = context; - view_dispatcher_send_custom_event(nfc->view_dispatcher, event); - - return true; -} - -static void nfc_scene_mf_classic_update_setup_view(Nfc* nfc) { - Popup* popup = nfc->popup; - popup_reset(popup); - uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfClassicUpdate); - - if(state == NfcSceneMfClassicUpdateStateCardSearch) { - popup_set_text( - nfc->popup, "Apply the initial\ncard only", 128, 32, AlignRight, AlignCenter); - popup_set_icon(nfc->popup, 0, 8, &I_NFC_manual_60x50); - } else { - popup_set_header(popup, "Updating\nDon't move...", 52, 32, AlignLeft, AlignCenter); - popup_set_icon(popup, 12, 23, &A_Loading_24); - } - - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); -} - -void nfc_scene_mf_classic_update_on_enter(void* context) { - Nfc* nfc = context; - dolphin_deed(DolphinDeedNfcEmulate); - - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneMfClassicUpdate, NfcSceneMfClassicUpdateStateCardSearch); - nfc_scene_mf_classic_update_setup_view(nfc); - - // Setup and start worker - nfc_worker_start( - nfc->worker, - NfcWorkerStateMfClassicUpdate, - &nfc->dev->dev_data, - nfc_mf_classic_update_worker_callback, - nfc); - nfc_blink_emulate_start(nfc); -} - -bool nfc_scene_mf_classic_update_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == NfcWorkerEventSuccess) { - nfc_worker_stop(nfc->worker); - if(nfc_device_save_shadow(nfc->dev, furi_string_get_cstr(nfc->dev->load_path))) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicUpdateSuccess); - } else { - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicWrongCard); - } - consumed = true; - } else if(event.event == NfcWorkerEventWrongCard) { - nfc_worker_stop(nfc->worker); - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicWrongCard); - consumed = true; - } else if(event.event == NfcWorkerEventCardDetected) { - scene_manager_set_scene_state( - nfc->scene_manager, - NfcSceneMfClassicUpdate, - NfcSceneMfClassicUpdateStateCardFound); - nfc_scene_mf_classic_update_setup_view(nfc); - consumed = true; - } else if(event.event == NfcWorkerEventNoCardDetected) { - scene_manager_set_scene_state( - nfc->scene_manager, - NfcSceneMfClassicUpdate, - NfcSceneMfClassicUpdateStateCardSearch); - nfc_scene_mf_classic_update_setup_view(nfc); - consumed = true; - } - } - return consumed; -} - -void nfc_scene_mf_classic_update_on_exit(void* context) { - Nfc* nfc = context; - nfc_worker_stop(nfc->worker); - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneMfClassicUpdate, NfcSceneMfClassicUpdateStateCardSearch); - // Clear view - popup_reset(nfc->popup); - - nfc_blink_stop(nfc); -} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_update_initial.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_update_initial.c new file mode 100644 index 00000000000..961afdf5311 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_update_initial.c @@ -0,0 +1,144 @@ +#include "../nfc_app_i.h" + +#include + +enum { + NfcSceneMfClassicUpdateInitialStateCardSearch, + NfcSceneMfClassicUpdateInitialStateCardFound, +}; + +NfcCommand nfc_mf_classic_update_initial_worker_callback(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.event_data); + furi_assert(event.protocol == NfcProtocolMfClassic); + + NfcCommand command = NfcCommandContinue; + const MfClassicPollerEvent* mfc_event = event.event_data; + NfcApp* instance = context; + + if(mfc_event->type == MfClassicPollerEventTypeCardDetected) { + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventCardDetected); + } else if(mfc_event->type == MfClassicPollerEventTypeCardLost) { + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventCardLost); + } else if(mfc_event->type == MfClassicPollerEventTypeRequestMode) { + const MfClassicData* updated_data = nfc_poller_get_data(instance->poller); + const MfClassicData* old_data = + nfc_device_get_data(instance->nfc_device, NfcProtocolMfClassic); + if(iso14443_3a_is_equal(updated_data->iso14443_3a_data, old_data->iso14443_3a_data)) { + mfc_event->data->poller_mode.mode = MfClassicPollerModeRead; + } else { + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventWrongCard); + command = NfcCommandStop; + } + } else if(mfc_event->type == MfClassicPollerEventTypeRequestReadSector) { + uint8_t sector_num = 0; + MfClassicKey key = {}; + MfClassicKeyType key_type = MfClassicKeyTypeA; + if(mf_classic_key_cahce_get_next_key( + instance->mfc_key_cache, §or_num, &key, &key_type)) { + mfc_event->data->read_sector_request_data.sector_num = sector_num; + mfc_event->data->read_sector_request_data.key = key; + mfc_event->data->read_sector_request_data.key_type = key_type; + mfc_event->data->read_sector_request_data.key_provided = true; + } else { + mfc_event->data->read_sector_request_data.key_provided = false; + } + } else if(mfc_event->type == MfClassicPollerEventTypeSuccess) { + const MfClassicData* updated_data = nfc_poller_get_data(instance->poller); + nfc_device_set_data(instance->nfc_device, NfcProtocolMfClassic, updated_data); + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventWorkerExit); + command = NfcCommandStop; + } + + return command; +} + +static void nfc_scene_mf_classic_update_initial_setup_view(NfcApp* instance) { + Popup* popup = instance->popup; + popup_reset(popup); + uint32_t state = + scene_manager_get_scene_state(instance->scene_manager, NfcSceneMfClassicUpdateInitial); + + if(state == NfcSceneMfClassicUpdateInitialStateCardSearch) { + popup_set_text( + instance->popup, "Apply the initial\ncard only", 128, 32, AlignRight, AlignCenter); + popup_set_icon(instance->popup, 0, 8, &I_NFC_manual_60x50); + } else { + popup_set_header(popup, "Updating\nDon't move...", 52, 32, AlignLeft, AlignCenter); + popup_set_icon(popup, 12, 23, &A_Loading_24); + } + + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup); +} + +void nfc_scene_mf_classic_update_initial_on_enter(void* context) { + NfcApp* instance = context; + dolphin_deed(DolphinDeedNfcEmulate); + + const MfClassicData* mfc_data = + nfc_device_get_data(instance->nfc_device, NfcProtocolMfClassic); + mf_classic_key_cache_load_from_data(instance->mfc_key_cache, mfc_data); + + scene_manager_set_scene_state( + instance->scene_manager, + NfcSceneMfClassicUpdateInitial, + NfcSceneMfClassicUpdateInitialStateCardSearch); + nfc_scene_mf_classic_update_initial_setup_view(instance); + + // Setup and start worker + instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfClassic); + nfc_poller_start(instance->poller, nfc_mf_classic_update_initial_worker_callback, instance); + nfc_blink_emulate_start(instance); +} + +bool nfc_scene_mf_classic_update_initial_on_event(void* context, SceneManagerEvent event) { + NfcApp* instance = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcCustomEventCardDetected) { + scene_manager_set_scene_state( + instance->scene_manager, + NfcSceneMfClassicUpdateInitial, + NfcSceneMfClassicUpdateInitialStateCardFound); + nfc_scene_mf_classic_update_initial_setup_view(instance); + consumed = true; + } else if(event.event == NfcCustomEventCardLost) { + scene_manager_set_scene_state( + instance->scene_manager, + NfcSceneMfClassicUpdateInitial, + NfcSceneMfClassicUpdateInitialStateCardSearch); + nfc_scene_mf_classic_update_initial_setup_view(instance); + consumed = true; + } else if(event.event == NfcCustomEventWrongCard) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicWrongCard); + consumed = true; + } else if(event.event == NfcCustomEventWorkerExit) { + if(nfc_save_shadow_file(instance)) { + scene_manager_next_scene( + instance->scene_manager, NfcSceneMfClassicUpdateInitialSuccess); + } else { + scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicWrongCard); + consumed = true; + } + } + } + + return consumed; +} + +void nfc_scene_mf_classic_update_initial_on_exit(void* context) { + NfcApp* instance = context; + + nfc_poller_stop(instance->poller); + nfc_poller_free(instance->poller); + + scene_manager_set_scene_state( + instance->scene_manager, + NfcSceneMfClassicUpdateInitial, + NfcSceneMfClassicUpdateInitialStateCardSearch); + // Clear view + popup_reset(instance->popup); + + nfc_blink_stop(instance); +} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_update_initial_success.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_update_initial_success.c new file mode 100644 index 00000000000..02e307b01ba --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_update_initial_success.c @@ -0,0 +1,43 @@ +#include "../nfc_app_i.h" + +void nfc_scene_mf_classic_update_initial_success_popup_callback(void* context) { + NfcApp* instance = context; + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventViewExit); +} + +void nfc_scene_mf_classic_update_initial_success_on_enter(void* context) { + NfcApp* instance = context; + dolphin_deed(DolphinDeedNfcSave); + + notification_message(instance->notifications, &sequence_success); + + Popup* popup = instance->popup; + popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); + popup_set_header(popup, "Updated!", 11, 20, AlignLeft, AlignBottom); + popup_set_timeout(popup, 1500); + popup_set_context(popup, instance); + popup_set_callback(popup, nfc_scene_mf_classic_update_initial_success_popup_callback); + popup_enable_timeout(popup); + + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup); +} + +bool nfc_scene_mf_classic_update_initial_success_on_event(void* context, SceneManagerEvent event) { + NfcApp* instance = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcCustomEventViewExit) { + consumed = scene_manager_search_and_switch_to_previous_scene( + instance->scene_manager, NfcSceneSavedMenu); + } + } + return consumed; +} + +void nfc_scene_mf_classic_update_initial_success_on_exit(void* context) { + NfcApp* instance = context; + + // Clear view + popup_reset(instance->popup); +} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_update_success.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_update_success.c deleted file mode 100644 index fb1868459d4..00000000000 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_update_success.c +++ /dev/null @@ -1,44 +0,0 @@ -#include "../nfc_i.h" -#include - -void nfc_scene_mf_classic_update_success_popup_callback(void* context) { - Nfc* nfc = context; - view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit); -} - -void nfc_scene_mf_classic_update_success_on_enter(void* context) { - Nfc* nfc = context; - dolphin_deed(DolphinDeedNfcSave); - - notification_message(nfc->notifications, &sequence_success); - - Popup* popup = nfc->popup; - popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); - popup_set_header(popup, "Updated!", 11, 20, AlignLeft, AlignBottom); - popup_set_timeout(popup, 1500); - popup_set_context(popup, nfc); - popup_set_callback(popup, nfc_scene_mf_classic_update_success_popup_callback); - popup_enable_timeout(popup); - - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); -} - -bool nfc_scene_mf_classic_update_success_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == NfcCustomEventViewExit) { - consumed = scene_manager_search_and_switch_to_previous_scene( - nfc->scene_manager, NfcSceneFileSelect); - } - } - return consumed; -} - -void nfc_scene_mf_classic_update_success_on_exit(void* context) { - Nfc* nfc = context; - - // Clear view - popup_reset(nfc->popup); -} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_write.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_write.c deleted file mode 100644 index 20ebfcc70a2..00000000000 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_write.c +++ /dev/null @@ -1,92 +0,0 @@ -#include "../nfc_i.h" -#include - -enum { - NfcSceneMfClassicWriteStateCardSearch, - NfcSceneMfClassicWriteStateCardFound, -}; - -bool nfc_mf_classic_write_worker_callback(NfcWorkerEvent event, void* context) { - furi_assert(context); - - Nfc* nfc = context; - view_dispatcher_send_custom_event(nfc->view_dispatcher, event); - - return true; -} - -static void nfc_scene_mf_classic_write_setup_view(Nfc* nfc) { - Popup* popup = nfc->popup; - popup_reset(popup); - uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfClassicWrite); - - if(state == NfcSceneMfClassicWriteStateCardSearch) { - popup_set_text( - nfc->popup, "Apply the initial\ncard only", 128, 32, AlignRight, AlignCenter); - popup_set_icon(nfc->popup, 0, 8, &I_NFC_manual_60x50); - } else { - popup_set_header(popup, "Writing\nDon't move...", 52, 32, AlignLeft, AlignCenter); - popup_set_icon(popup, 12, 23, &A_Loading_24); - } - - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); -} - -void nfc_scene_mf_classic_write_on_enter(void* context) { - Nfc* nfc = context; - dolphin_deed(DolphinDeedNfcEmulate); - - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneMfClassicWrite, NfcSceneMfClassicWriteStateCardSearch); - nfc_scene_mf_classic_write_setup_view(nfc); - - // Setup and start worker - nfc_worker_start( - nfc->worker, - NfcWorkerStateMfClassicWrite, - &nfc->dev->dev_data, - nfc_mf_classic_write_worker_callback, - nfc); - nfc_blink_emulate_start(nfc); -} - -bool nfc_scene_mf_classic_write_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == NfcWorkerEventSuccess) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicWriteSuccess); - consumed = true; - } else if(event.event == NfcWorkerEventFail) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicWriteFail); - consumed = true; - } else if(event.event == NfcWorkerEventWrongCard) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicWrongCard); - consumed = true; - } else if(event.event == NfcWorkerEventCardDetected) { - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneMfClassicWrite, NfcSceneMfClassicWriteStateCardFound); - nfc_scene_mf_classic_write_setup_view(nfc); - consumed = true; - } else if(event.event == NfcWorkerEventNoCardDetected) { - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneMfClassicWrite, NfcSceneMfClassicWriteStateCardSearch); - nfc_scene_mf_classic_write_setup_view(nfc); - consumed = true; - } - } - return consumed; -} - -void nfc_scene_mf_classic_write_on_exit(void* context) { - Nfc* nfc = context; - - nfc_worker_stop(nfc->worker); - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneMfClassicWrite, NfcSceneMfClassicWriteStateCardSearch); - // Clear view - popup_reset(nfc->popup); - - nfc_blink_stop(nfc); -} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_write_fail.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_write_fail.c deleted file mode 100644 index aeea6eef069..00000000000 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_write_fail.c +++ /dev/null @@ -1,58 +0,0 @@ -#include "../nfc_i.h" - -void nfc_scene_mf_classic_write_fail_widget_callback( - GuiButtonType result, - InputType type, - void* context) { - Nfc* nfc = context; - if(type == InputTypeShort) { - view_dispatcher_send_custom_event(nfc->view_dispatcher, result); - } -} - -void nfc_scene_mf_classic_write_fail_on_enter(void* context) { - Nfc* nfc = context; - Widget* widget = nfc->widget; - - notification_message(nfc->notifications, &sequence_error); - - widget_add_icon_element(widget, 72, 17, &I_DolphinCommon_56x48); - widget_add_string_element( - widget, 7, 4, AlignLeft, AlignTop, FontPrimary, "Writing gone wrong!"); - widget_add_string_multiline_element( - widget, - 7, - 17, - AlignLeft, - AlignTop, - FontSecondary, - "Not all sectors\nwere written\ncorrectly."); - - widget_add_button_element( - widget, GuiButtonTypeLeft, "Finish", nfc_scene_mf_classic_write_fail_widget_callback, nfc); - - // Setup and start worker - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); -} - -bool nfc_scene_mf_classic_write_fail_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == GuiButtonTypeLeft) { - consumed = scene_manager_search_and_switch_to_previous_scene( - nfc->scene_manager, NfcSceneFileSelect); - } - } else if(event.type == SceneManagerEventTypeBack) { - consumed = scene_manager_search_and_switch_to_previous_scene( - nfc->scene_manager, NfcSceneSavedMenu); - } - return consumed; -} - -void nfc_scene_mf_classic_write_fail_on_exit(void* context) { - Nfc* nfc = context; - - widget_reset(nfc->widget); -} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial.c new file mode 100644 index 00000000000..79f1def1d12 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial.c @@ -0,0 +1,146 @@ +#include "../nfc_app_i.h" + +#include + +enum { + NfcSceneMfClassicWriteInitialStateCardSearch, + NfcSceneMfClassicWriteInitialStateCardFound, +}; + +NfcCommand + nfc_scene_mf_classic_write_initial_worker_callback(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.event_data); + furi_assert(event.protocol == NfcProtocolMfClassic); + + NfcCommand command = NfcCommandContinue; + NfcApp* instance = context; + MfClassicPollerEvent* mfc_event = event.event_data; + const MfClassicData* write_data = + nfc_device_get_data(instance->nfc_device, NfcProtocolMfClassic); + + if(mfc_event->type == MfClassicPollerEventTypeCardDetected) { + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventCardDetected); + } else if(mfc_event->type == MfClassicPollerEventTypeCardLost) { + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventCardLost); + } else if(mfc_event->type == MfClassicPollerEventTypeRequestMode) { + const MfClassicData* tag_data = nfc_poller_get_data(instance->poller); + if(iso14443_3a_is_equal(tag_data->iso14443_3a_data, write_data->iso14443_3a_data)) { + mfc_event->data->poller_mode.mode = MfClassicPollerModeWrite; + } else { + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventWrongCard); + command = NfcCommandStop; + } + } else if(mfc_event->type == MfClassicPollerEventTypeRequestSectorTrailer) { + uint8_t sector = mfc_event->data->sec_tr_data.sector_num; + uint8_t sec_tr = mf_classic_get_sector_trailer_num_by_sector(sector); + if(mf_classic_is_block_read(write_data, sec_tr)) { + mfc_event->data->sec_tr_data.sector_trailer = write_data->block[sec_tr]; + mfc_event->data->sec_tr_data.sector_trailer_provided = true; + } else { + mfc_event->data->sec_tr_data.sector_trailer_provided = false; + } + } else if(mfc_event->type == MfClassicPollerEventTypeRequestWriteBlock) { + uint8_t block_num = mfc_event->data->write_block_data.block_num; + if(mf_classic_is_block_read(write_data, block_num)) { + mfc_event->data->write_block_data.write_block = write_data->block[block_num]; + mfc_event->data->write_block_data.write_block_provided = true; + } else { + mfc_event->data->write_block_data.write_block_provided = false; + } + } else if(mfc_event->type == MfClassicPollerEventTypeSuccess) { + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess); + command = NfcCommandStop; + } else if(mfc_event->type == MfClassicPollerEventTypeFail) { + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerFailure); + command = NfcCommandStop; + } + return command; +} + +static void nfc_scene_mf_classic_write_initial_setup_view(NfcApp* instance) { + Popup* popup = instance->popup; + popup_reset(popup); + uint32_t state = + scene_manager_get_scene_state(instance->scene_manager, NfcSceneMfClassicWriteInitial); + + if(state == NfcSceneMfClassicWriteInitialStateCardSearch) { + popup_set_text( + instance->popup, "Apply the initial\ncard only", 128, 32, AlignRight, AlignCenter); + popup_set_icon(instance->popup, 0, 8, &I_NFC_manual_60x50); + } else { + popup_set_header(popup, "Writing\nDon't move...", 52, 32, AlignLeft, AlignCenter); + popup_set_icon(popup, 12, 23, &A_Loading_24); + } + + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup); +} + +void nfc_scene_mf_classic_write_initial_on_enter(void* context) { + NfcApp* instance = context; + dolphin_deed(DolphinDeedNfcEmulate); + + scene_manager_set_scene_state( + instance->scene_manager, + NfcSceneMfClassicWriteInitial, + NfcSceneMfClassicWriteInitialStateCardSearch); + nfc_scene_mf_classic_write_initial_setup_view(instance); + + // Setup and start worker + instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfClassic); + nfc_poller_start( + instance->poller, nfc_scene_mf_classic_write_initial_worker_callback, instance); + + nfc_blink_emulate_start(instance); +} + +bool nfc_scene_mf_classic_write_initial_on_event(void* context, SceneManagerEvent event) { + NfcApp* instance = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcCustomEventCardDetected) { + scene_manager_set_scene_state( + instance->scene_manager, + NfcSceneMfClassicWriteInitial, + NfcSceneMfClassicWriteInitialStateCardFound); + nfc_scene_mf_classic_write_initial_setup_view(instance); + consumed = true; + } else if(event.event == NfcCustomEventCardLost) { + scene_manager_set_scene_state( + instance->scene_manager, + NfcSceneMfClassicWriteInitial, + NfcSceneMfClassicWriteInitialStateCardSearch); + nfc_scene_mf_classic_write_initial_setup_view(instance); + consumed = true; + } else if(event.event == NfcCustomEventWrongCard) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicWrongCard); + consumed = true; + } else if(event.event == NfcCustomEventPollerSuccess) { + scene_manager_next_scene( + instance->scene_manager, NfcSceneMfClassicWriteInitialSuccess); + consumed = true; + } else if(event.event == NfcCustomEventPollerFailure) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicWriteInitialFail); + consumed = true; + } + } + + return consumed; +} + +void nfc_scene_mf_classic_write_initial_on_exit(void* context) { + NfcApp* instance = context; + + nfc_poller_stop(instance->poller); + nfc_poller_free(instance->poller); + + scene_manager_set_scene_state( + instance->scene_manager, + NfcSceneMfClassicWriteInitial, + NfcSceneMfClassicWriteInitialStateCardSearch); + // Clear view + popup_reset(instance->popup); + + nfc_blink_stop(instance); +} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial_fail.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial_fail.c new file mode 100644 index 00000000000..f85e5a80c3f --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial_fail.c @@ -0,0 +1,62 @@ +#include "../nfc_app_i.h" + +void nfc_scene_mf_classic_write_initial_fail_widget_callback( + GuiButtonType result, + InputType type, + void* context) { + NfcApp* instance = context; + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(instance->view_dispatcher, result); + } +} + +void nfc_scene_mf_classic_write_initial_fail_on_enter(void* context) { + NfcApp* instance = context; + Widget* widget = instance->widget; + + notification_message(instance->notifications, &sequence_error); + + widget_add_icon_element(widget, 72, 17, &I_DolphinCommon_56x48); + widget_add_string_element( + widget, 7, 4, AlignLeft, AlignTop, FontPrimary, "Writing gone wrong!"); + widget_add_string_multiline_element( + widget, + 7, + 17, + AlignLeft, + AlignTop, + FontSecondary, + "Not all sectors\nwere written\ncorrectly."); + + widget_add_button_element( + widget, + GuiButtonTypeLeft, + "Finish", + nfc_scene_mf_classic_write_initial_fail_widget_callback, + instance); + + // Setup and start worker + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget); +} + +bool nfc_scene_mf_classic_write_initial_fail_on_event(void* context, SceneManagerEvent event) { + NfcApp* instance = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == GuiButtonTypeLeft) { + consumed = scene_manager_search_and_switch_to_previous_scene( + instance->scene_manager, NfcSceneSavedMenu); + } + } else if(event.type == SceneManagerEventTypeBack) { + consumed = scene_manager_search_and_switch_to_previous_scene( + instance->scene_manager, NfcSceneSavedMenu); + } + return consumed; +} + +void nfc_scene_mf_classic_write_initial_fail_on_exit(void* context) { + NfcApp* instance = context; + + widget_reset(instance->widget); +} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial_success.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial_success.c new file mode 100644 index 00000000000..acb75cd2e9f --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial_success.c @@ -0,0 +1,43 @@ +#include "../nfc_app_i.h" + +void nfc_scene_mf_classic_write_initial_success_popup_callback(void* context) { + NfcApp* instance = context; + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventViewExit); +} + +void nfc_scene_mf_classic_write_initial_success_on_enter(void* context) { + NfcApp* instance = context; + dolphin_deed(DolphinDeedNfcSave); + + notification_message(instance->notifications, &sequence_success); + + Popup* popup = instance->popup; + popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); + popup_set_header(popup, "Successfully\nwritten", 13, 22, AlignLeft, AlignBottom); + popup_set_timeout(popup, 1500); + popup_set_context(popup, instance); + popup_set_callback(popup, nfc_scene_mf_classic_write_initial_success_popup_callback); + popup_enable_timeout(popup); + + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup); +} + +bool nfc_scene_mf_classic_write_initial_success_on_event(void* context, SceneManagerEvent event) { + NfcApp* instance = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcCustomEventViewExit) { + consumed = scene_manager_search_and_switch_to_previous_scene( + instance->scene_manager, NfcSceneSavedMenu); + } + } + return consumed; +} + +void nfc_scene_mf_classic_write_initial_success_on_exit(void* context) { + NfcApp* instance = context; + + // Clear view + popup_reset(instance->popup); +} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_write_success.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_write_success.c deleted file mode 100644 index 00030d4fe8c..00000000000 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_write_success.c +++ /dev/null @@ -1,44 +0,0 @@ -#include "../nfc_i.h" -#include - -void nfc_scene_mf_classic_write_success_popup_callback(void* context) { - Nfc* nfc = context; - view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit); -} - -void nfc_scene_mf_classic_write_success_on_enter(void* context) { - Nfc* nfc = context; - dolphin_deed(DolphinDeedNfcSave); - - notification_message(nfc->notifications, &sequence_success); - - Popup* popup = nfc->popup; - popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); - popup_set_header(popup, "Successfully\nwritten", 13, 22, AlignLeft, AlignBottom); - popup_set_timeout(popup, 1500); - popup_set_context(popup, nfc); - popup_set_callback(popup, nfc_scene_mf_classic_write_success_popup_callback); - popup_enable_timeout(popup); - - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); -} - -bool nfc_scene_mf_classic_write_success_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == NfcCustomEventViewExit) { - consumed = scene_manager_search_and_switch_to_previous_scene( - nfc->scene_manager, NfcSceneFileSelect); - } - } - return consumed; -} - -void nfc_scene_mf_classic_write_success_on_exit(void* context) { - Nfc* nfc = context; - - // Clear view - popup_reset(nfc->popup); -} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_wrong_card.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_wrong_card.c index 2c56270e36d..50025048af4 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_wrong_card.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_wrong_card.c @@ -1,20 +1,20 @@ -#include "../nfc_i.h" +#include "../nfc_app_i.h" void nfc_scene_mf_classic_wrong_card_widget_callback( GuiButtonType result, InputType type, void* context) { - Nfc* nfc = context; + NfcApp* instance = context; if(type == InputTypeShort) { - view_dispatcher_send_custom_event(nfc->view_dispatcher, result); + view_dispatcher_send_custom_event(instance->view_dispatcher, result); } } void nfc_scene_mf_classic_wrong_card_on_enter(void* context) { - Nfc* nfc = context; - Widget* widget = nfc->widget; + NfcApp* instance = context; + Widget* widget = instance->widget; - notification_message(nfc->notifications, &sequence_error); + notification_message(instance->notifications, &sequence_error); widget_add_icon_element(widget, 73, 17, &I_DolphinCommon_56x48); widget_add_string_element( @@ -28,26 +28,30 @@ void nfc_scene_mf_classic_wrong_card_on_enter(void* context) { FontSecondary, "Data management\nis only possible\nwith initial card"); widget_add_button_element( - widget, GuiButtonTypeLeft, "Retry", nfc_scene_mf_classic_wrong_card_widget_callback, nfc); + widget, + GuiButtonTypeLeft, + "Retry", + nfc_scene_mf_classic_wrong_card_widget_callback, + instance); // Setup and start worker - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget); } bool nfc_scene_mf_classic_wrong_card_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; + NfcApp* instance = context; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { if(event.event == GuiButtonTypeLeft) { - consumed = scene_manager_previous_scene(nfc->scene_manager); + consumed = scene_manager_previous_scene(instance->scene_manager); } } return consumed; } void nfc_scene_mf_classic_wrong_card_on_exit(void* context) { - Nfc* nfc = context; + NfcApp* instance = context; - widget_reset(nfc->widget); + widget_reset(instance->widget); } \ No newline at end of file diff --git a/applications/main/nfc/scenes/nfc_scene_mf_desfire_app.c b/applications/main/nfc/scenes/nfc_scene_mf_desfire_app.c index 882dc5fea8f..8d6a92b6c7e 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_desfire_app.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_desfire_app.c @@ -1,103 +1,93 @@ -#include "../nfc_i.h" +#include "../nfc_app_i.h" -#define TAG "NfcSceneMfDesfireApp" +#include "../helpers/protocol_support/mf_desfire/mf_desfire_render.h" enum SubmenuIndex { SubmenuIndexAppInfo, SubmenuIndexDynamic, // dynamic indexes start here }; -void nfc_scene_mf_desfire_popup_callback(void* context) { - furi_assert(context); +static void nfc_scene_mf_desfire_app_submenu_callback(void* context, uint32_t index) { + NfcApp* nfc = context; - Nfc* nfc = context; - view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit); + view_dispatcher_send_custom_event(nfc->view_dispatcher, index); } -MifareDesfireApplication* nfc_scene_mf_desfire_app_get_app(Nfc* nfc) { - uint32_t app_idx = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfDesfireApp) >> - 1; - MifareDesfireApplication* app = nfc->dev->dev_data.mf_df_data.app_head; - for(uint32_t i = 0; i < app_idx && app; i++) { - app = app->next; - } - return app; -} +void nfc_scene_mf_desfire_app_on_enter(void* context) { + NfcApp* nfc = context; -void nfc_scene_mf_desfire_app_submenu_callback(void* context, uint32_t index) { - Nfc* nfc = context; + text_box_set_font(nfc->text_box, TextBoxFontHex); + submenu_add_item( + nfc->submenu, + "App info", + SubmenuIndexAppInfo, + nfc_scene_mf_desfire_app_submenu_callback, + nfc); - view_dispatcher_send_custom_event(nfc->view_dispatcher, index); -} + const uint32_t app_idx = + scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfDesfireApp) >> 1; -void nfc_scene_mf_desfire_app_on_enter(void* context) { - Nfc* nfc = context; - MifareDesfireApplication* app = nfc_scene_mf_desfire_app_get_app(nfc); - if(!app) { - popup_set_icon(nfc->popup, 5, 5, &I_WarningDolphin_45x42); - popup_set_header(nfc->popup, "Empty card!", 55, 12, AlignLeft, AlignBottom); - popup_set_callback(nfc->popup, nfc_scene_mf_desfire_popup_callback); - popup_set_context(nfc->popup, nfc); - popup_set_timeout(nfc->popup, 3000); - popup_enable_timeout(nfc->popup); - popup_set_text(nfc->popup, "No application\nfound.", 55, 15, AlignLeft, AlignTop); - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); - } else { - text_box_set_font(nfc->text_box, TextBoxFontHex); + const MfDesfireData* data = nfc_device_get_data(nfc->nfc_device, NfcProtocolMfDesfire); + const MfDesfireApplication* app = simple_array_cget(data->applications, app_idx); + + FuriString* label = furi_string_alloc(); + + for(uint32_t i = 0; i < simple_array_get_count(app->file_ids); ++i) { + const MfDesfireFileId file_id = + *(const MfDesfireFileId*)simple_array_cget(app->file_ids, i); + furi_string_printf(label, "File %d", file_id); submenu_add_item( nfc->submenu, - "App info", - SubmenuIndexAppInfo, + furi_string_get_cstr(label), + i + SubmenuIndexDynamic, nfc_scene_mf_desfire_app_submenu_callback, nfc); + } - FuriString* label = furi_string_alloc(); - int idx = SubmenuIndexDynamic; - for(MifareDesfireFile* file = app->file_head; file; file = file->next) { - furi_string_printf(label, "File %d", file->id); - submenu_add_item( - nfc->submenu, - furi_string_get_cstr(label), - idx++, - nfc_scene_mf_desfire_app_submenu_callback, - nfc); - } - furi_string_free(label); + furi_string_free(label); - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); - } + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); } bool nfc_scene_mf_desfire_app_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; + NfcApp* nfc = context; bool consumed = false; - uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfDesfireApp); + + const uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfDesfireApp); if(event.type == SceneManagerEventTypeCustom) { if(event.event == NfcCustomEventViewExit) { consumed = scene_manager_previous_scene(nfc->scene_manager); } else { - MifareDesfireApplication* app = nfc_scene_mf_desfire_app_get_app(nfc); + const MfDesfireData* data = nfc_device_get_data(nfc->nfc_device, NfcProtocolMfDesfire); + + const uint32_t app_index = + scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfDesfireApp) >> 1; + const MfDesfireApplication* app = simple_array_cget(data->applications, app_index); + TextBox* text_box = nfc->text_box; furi_string_reset(nfc->text_box_store); if(event.event == SubmenuIndexAppInfo) { - mf_df_cat_application_info(app, nfc->text_box_store); + const MfDesfireApplicationId* app_id = + simple_array_cget(data->application_ids, app_index); + nfc_render_mf_desfire_application_id(app_id, nfc->text_box_store); + nfc_render_mf_desfire_application(app, nfc->text_box_store); } else { - uint16_t index = event.event - SubmenuIndexDynamic; - MifareDesfireFile* file = app->file_head; - for(int i = 0; file && i < index; i++) { - file = file->next; - } - if(!file) { - return false; - } - mf_df_cat_file(file, nfc->text_box_store); + const uint32_t file_index = event.event - SubmenuIndexDynamic; + const MfDesfireFileId* file_id = simple_array_cget(app->file_ids, file_index); + const MfDesfireFileSettings* file_settings = + simple_array_cget(app->file_settings, file_index); + const MfDesfireFileData* file_data = simple_array_cget(app->file_data, file_index); + nfc_render_mf_desfire_file_id(file_id, nfc->text_box_store); + nfc_render_mf_desfire_file_settings_data( + file_settings, file_data, nfc->text_box_store); } text_box_set_text(text_box, furi_string_get_cstr(nfc->text_box_store)); scene_manager_set_scene_state(nfc->scene_manager, NfcSceneMfDesfireApp, state | 1); view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewTextBox); consumed = true; } + } else if(event.type == SceneManagerEventTypeBack) { if(state & 1) { view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); @@ -110,10 +100,9 @@ bool nfc_scene_mf_desfire_app_on_event(void* context, SceneManagerEvent event) { } void nfc_scene_mf_desfire_app_on_exit(void* context) { - Nfc* nfc = context; + NfcApp* nfc = context; // Clear views - popup_reset(nfc->popup); text_box_reset(nfc->text_box); furi_string_reset(nfc->text_box_store); submenu_reset(nfc->submenu); diff --git a/applications/main/nfc/scenes/nfc_scene_mf_desfire_data.c b/applications/main/nfc/scenes/nfc_scene_mf_desfire_data.c deleted file mode 100644 index c7caee8dc6d..00000000000 --- a/applications/main/nfc/scenes/nfc_scene_mf_desfire_data.c +++ /dev/null @@ -1,104 +0,0 @@ -#include "../nfc_i.h" - -#define TAG "NfcSceneMfDesfireData" - -enum { - MifareDesfireDataStateMenu, - MifareDesfireDataStateItem, // MUST be last, states >= this correspond with submenu index -}; - -enum SubmenuIndex { - SubmenuIndexCardInfo, - SubmenuIndexDynamic, // dynamic indexes start here -}; - -void nfc_scene_mf_desfire_data_submenu_callback(void* context, uint32_t index) { - Nfc* nfc = (Nfc*)context; - - view_dispatcher_send_custom_event(nfc->view_dispatcher, index); -} - -void nfc_scene_mf_desfire_data_on_enter(void* context) { - Nfc* nfc = context; - Submenu* submenu = nfc->submenu; - uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfDesfireData); - MifareDesfireData* data = &nfc->dev->dev_data.mf_df_data; - - text_box_set_font(nfc->text_box, TextBoxFontHex); - - submenu_add_item( - submenu, - "Card info", - SubmenuIndexCardInfo, - nfc_scene_mf_desfire_data_submenu_callback, - nfc); - - FuriString* label = furi_string_alloc(); - int idx = SubmenuIndexDynamic; - for(MifareDesfireApplication* app = data->app_head; app; app = app->next) { - furi_string_printf(label, "App %02x%02x%02x", app->id[0], app->id[1], app->id[2]); - submenu_add_item( - submenu, - furi_string_get_cstr(label), - idx++, - nfc_scene_mf_desfire_data_submenu_callback, - nfc); - } - furi_string_free(label); - - if(state >= MifareDesfireDataStateItem) { - submenu_set_selected_item( - nfc->submenu, state - MifareDesfireDataStateItem + SubmenuIndexDynamic); - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneMfDesfireData, MifareDesfireDataStateMenu); - } - - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); -} - -bool nfc_scene_mf_desfire_data_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfDesfireData); - MifareDesfireData* data = &nfc->dev->dev_data.mf_df_data; - - if(event.type == SceneManagerEventTypeCustom) { - TextBox* text_box = nfc->text_box; - furi_string_reset(nfc->text_box_store); - if(event.event == SubmenuIndexCardInfo) { - mf_df_cat_card_info(data, nfc->text_box_store); - text_box_set_text(text_box, furi_string_get_cstr(nfc->text_box_store)); - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewTextBox); - scene_manager_set_scene_state( - nfc->scene_manager, - NfcSceneMfDesfireData, - MifareDesfireDataStateItem + SubmenuIndexCardInfo); - consumed = true; - } else { - uint16_t index = event.event - SubmenuIndexDynamic; - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneMfDesfireData, MifareDesfireDataStateItem + index); - scene_manager_set_scene_state(nfc->scene_manager, NfcSceneMfDesfireApp, index << 1); - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfDesfireApp); - consumed = true; - } - } else if(event.type == SceneManagerEventTypeBack) { - if(state >= MifareDesfireDataStateItem) { - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneMfDesfireData, MifareDesfireDataStateMenu); - consumed = true; - } - } - - return consumed; -} - -void nfc_scene_mf_desfire_data_on_exit(void* context) { - Nfc* nfc = context; - - // Clear views - text_box_reset(nfc->text_box); - furi_string_reset(nfc->text_box_store); - submenu_reset(nfc->submenu); -} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_desfire_menu.c b/applications/main/nfc/scenes/nfc_scene_mf_desfire_menu.c deleted file mode 100644 index 9cebefedfa1..00000000000 --- a/applications/main/nfc/scenes/nfc_scene_mf_desfire_menu.c +++ /dev/null @@ -1,72 +0,0 @@ -#include "../nfc_i.h" -#include - -enum SubmenuIndex { - SubmenuIndexSave, - SubmenuIndexEmulateUid, - SubmenuIndexInfo, -}; - -void nfc_scene_mf_desfire_menu_submenu_callback(void* context, uint32_t index) { - Nfc* nfc = context; - - view_dispatcher_send_custom_event(nfc->view_dispatcher, index); -} - -void nfc_scene_mf_desfire_menu_on_enter(void* context) { - Nfc* nfc = context; - Submenu* submenu = nfc->submenu; - - submenu_add_item( - submenu, "Save", SubmenuIndexSave, nfc_scene_mf_desfire_menu_submenu_callback, nfc); - submenu_add_item( - submenu, - "Emulate UID", - SubmenuIndexEmulateUid, - nfc_scene_mf_desfire_menu_submenu_callback, - nfc); - submenu_add_item( - submenu, "Info", SubmenuIndexInfo, nfc_scene_mf_desfire_menu_submenu_callback, nfc); - - submenu_set_selected_item( - nfc->submenu, scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfDesfireMenu)); - - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); -} - -bool nfc_scene_mf_desfire_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, NfcSceneMfDesfireMenu, SubmenuIndexSave); - nfc->dev->format = NfcDeviceSaveFormatMifareDesfire; - // Clear device name - nfc_device_set_name(nfc->dev, ""); - scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveName); - consumed = true; - } else if(event.event == SubmenuIndexEmulateUid) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateUid); - if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSetType)) { - dolphin_deed(DolphinDeedNfcAddEmulate); - } else { - dolphin_deed(DolphinDeedNfcEmulate); - } - consumed = true; - } else if(event.event == SubmenuIndexInfo) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcDataInfo); - consumed = true; - } - } - - return consumed; -} - -void nfc_scene_mf_desfire_menu_on_exit(void* context) { - Nfc* nfc = context; - - // Clear view - submenu_reset(nfc->submenu); -} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_desfire_more_info.c b/applications/main/nfc/scenes/nfc_scene_mf_desfire_more_info.c new file mode 100644 index 00000000000..76834e3f4f7 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_desfire_more_info.c @@ -0,0 +1,112 @@ +#include "../nfc_app_i.h" + +#include "../helpers/protocol_support/nfc_protocol_support_gui_common.h" +#include "../helpers/protocol_support/mf_desfire/mf_desfire_render.h" + +enum { + MifareDesfireMoreInfoStateMenu, + MifareDesfireMoreInfoStateItem, // MUST be last, states >= this correspond with submenu index +}; + +enum SubmenuIndex { + SubmenuIndexCardInfo, + SubmenuIndexDynamic, // dynamic indices start here +}; + +void nfc_scene_mf_desfire_more_info_on_enter(void* context) { + NfcApp* nfc = context; + Submenu* submenu = nfc->submenu; + + const uint32_t state = + scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfDesfireMoreInfo); + const MfDesfireData* data = nfc_device_get_data(nfc->nfc_device, NfcProtocolMfDesfire); + + text_box_set_font(nfc->text_box, TextBoxFontHex); + + submenu_add_item( + submenu, + "Card info", + SubmenuIndexCardInfo, + nfc_protocol_support_common_submenu_callback, + nfc); + + FuriString* label = furi_string_alloc(); + + for(uint32_t i = 0; i < simple_array_get_count(data->application_ids); ++i) { + const MfDesfireApplicationId* app_id = simple_array_cget(data->application_ids, i); + furi_string_printf( + label, "App %02x%02x%02x", app_id->data[0], app_id->data[1], app_id->data[2]); + submenu_add_item( + submenu, + furi_string_get_cstr(label), + i + SubmenuIndexDynamic, + nfc_protocol_support_common_submenu_callback, + nfc); + } + + furi_string_free(label); + + if(state >= MifareDesfireMoreInfoStateItem) { + submenu_set_selected_item( + nfc->submenu, state - MifareDesfireMoreInfoStateItem + SubmenuIndexDynamic); + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneMfDesfireMoreInfo, MifareDesfireMoreInfoStateMenu); + } + + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); +} + +bool nfc_scene_mf_desfire_more_info_on_event(void* context, SceneManagerEvent event) { + NfcApp* nfc = context; + bool consumed = false; + + const uint32_t state = + scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfDesfireMoreInfo); + const MfDesfireData* data = nfc_device_get_data(nfc->nfc_device, NfcProtocolMfDesfire); + + if(event.type == SceneManagerEventTypeCustom) { + TextBox* text_box = nfc->text_box; + furi_string_reset(nfc->text_box_store); + + if(event.event == SubmenuIndexCardInfo) { + nfc_render_mf_desfire_data(data, nfc->text_box_store); + text_box_set_text(text_box, furi_string_get_cstr(nfc->text_box_store)); + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewTextBox); + scene_manager_set_scene_state( + nfc->scene_manager, + NfcSceneMfDesfireMoreInfo, + MifareDesfireMoreInfoStateItem + SubmenuIndexCardInfo); + consumed = true; + } else { + const uint32_t index = event.event - SubmenuIndexDynamic; + scene_manager_set_scene_state( + nfc->scene_manager, + NfcSceneMfDesfireMoreInfo, + MifareDesfireMoreInfoStateItem + index); + scene_manager_set_scene_state(nfc->scene_manager, NfcSceneMfDesfireApp, index << 1); + scene_manager_next_scene(nfc->scene_manager, NfcSceneMfDesfireApp); + consumed = true; + } + } else if(event.type == SceneManagerEventTypeBack) { + if(state >= MifareDesfireMoreInfoStateItem) { + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneMfDesfireMoreInfo, MifareDesfireMoreInfoStateMenu); + } else { + // Return directly to the Info scene + scene_manager_search_and_switch_to_previous_scene(nfc->scene_manager, NfcSceneInfo); + } + consumed = true; + } + + return consumed; +} + +void nfc_scene_mf_desfire_more_info_on_exit(void* context) { + NfcApp* nfc = context; + + // Clear views + text_box_reset(nfc->text_box); + furi_string_reset(nfc->text_box_store); + submenu_reset(nfc->submenu); +} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_desfire_read_success.c b/applications/main/nfc/scenes/nfc_scene_mf_desfire_read_success.c deleted file mode 100644 index 633549eb5d8..00000000000 --- a/applications/main/nfc/scenes/nfc_scene_mf_desfire_read_success.c +++ /dev/null @@ -1,101 +0,0 @@ -#include "../nfc_i.h" -#include - -void nfc_scene_mf_desfire_read_success_widget_callback( - GuiButtonType result, - InputType type, - void* context) { - Nfc* nfc = context; - - if(type == InputTypeShort) { - view_dispatcher_send_custom_event(nfc->view_dispatcher, result); - } -} - -void nfc_scene_mf_desfire_read_success_on_enter(void* context) { - Nfc* nfc = context; - - FuriHalNfcDevData* nfc_data = &nfc->dev->dev_data.nfc_data; - MifareDesfireData* data = &nfc->dev->dev_data.mf_df_data; - Widget* widget = nfc->widget; - - // Prepare string for data display - FuriString* temp_str = NULL; - if(furi_string_size(nfc->dev->dev_data.parsed_data)) { - temp_str = furi_string_alloc_set(nfc->dev->dev_data.parsed_data); - } else { - temp_str = furi_string_alloc_printf("\e#MIFARE DESFire\n"); - furi_string_cat_printf(temp_str, "UID:"); - for(size_t i = 0; i < nfc_data->uid_len; i++) { - furi_string_cat_printf(temp_str, " %02X", nfc_data->uid[i]); - } - - uint32_t bytes_total = 1UL << (data->version.sw_storage >> 1); - uint32_t bytes_free = data->free_memory ? data->free_memory->bytes : 0; - furi_string_cat_printf(temp_str, "\n%lu", bytes_total); - if(data->version.sw_storage & 1) { - furi_string_push_back(temp_str, '+'); - } - furi_string_cat_printf(temp_str, " bytes, %lu bytes free\n", bytes_free); - - uint16_t n_apps = 0; - uint16_t n_files = 0; - for(MifareDesfireApplication* app = data->app_head; app; app = app->next) { - n_apps++; - for(MifareDesfireFile* file = app->file_head; file; file = file->next) { - n_files++; - } - } - furi_string_cat_printf(temp_str, "%d Application", n_apps); - if(n_apps != 1) { - furi_string_push_back(temp_str, 's'); - } - furi_string_cat_printf(temp_str, ", %d file", n_files); - if(n_files != 1) { - furi_string_push_back(temp_str, 's'); - } - } - - notification_message_block(nfc->notifications, &sequence_set_green_255); - - // Add text scroll element - widget_add_text_scroll_element(widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); - furi_string_free(temp_str); - - // Add button elements - widget_add_button_element( - widget, GuiButtonTypeLeft, "Retry", nfc_scene_mf_desfire_read_success_widget_callback, nfc); - widget_add_button_element( - widget, GuiButtonTypeRight, "More", nfc_scene_mf_desfire_read_success_widget_callback, nfc); - - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); -} - -bool nfc_scene_mf_desfire_read_success_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == GuiButtonTypeLeft) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneRetryConfirm); - consumed = true; - } else if(event.event == GuiButtonTypeRight) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfDesfireMenu); - consumed = true; - } - } else if(event.type == SceneManagerEventTypeBack) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneExitConfirm); - consumed = true; - } - - return consumed; -} - -void nfc_scene_mf_desfire_read_success_on_exit(void* context) { - Nfc* nfc = context; - - notification_message_block(nfc->notifications, &sequence_reset_green); - - // Clean dialog - widget_reset(nfc->widget); -} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_capture_pass.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_capture_pass.c new file mode 100644 index 00000000000..1c4be463066 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_capture_pass.c @@ -0,0 +1,67 @@ +#include "../nfc_app_i.h" + +NfcCommand + nfc_scene_mf_ultralight_capture_pass_worker_callback(NfcGenericEvent event, void* context) { + NfcApp* instance = context; + MfUltralightListenerEvent* mfu_event = event.event_data; + MfUltralightAuth* mauth = instance->mf_ul_auth; + + if(mfu_event->type == MfUltralightListenerEventTypeAuth) { + mauth->password = mfu_event->data->password; + view_dispatcher_send_custom_event( + instance->view_dispatcher, MfUltralightListenerEventTypeAuth); + } + + return NfcCommandContinue; +} + +void nfc_scene_mf_ultralight_capture_pass_on_enter(void* context) { + NfcApp* instance = context; + + // Setup view + widget_add_string_multiline_element( + instance->widget, + 54, + 30, + AlignLeft, + AlignCenter, + FontPrimary, + "Touch the\nreader to get\npassword..."); + widget_add_icon_element(instance->widget, 0, 15, &I_Modern_reader_18x34); + widget_add_icon_element(instance->widget, 20, 12, &I_Move_flipper_26x39); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget); + + // Start worker + const MfUltralightData* data = + nfc_device_get_data(instance->nfc_device, NfcProtocolMfUltralight); + instance->listener = nfc_listener_alloc(instance->nfc, NfcProtocolMfUltralight, data); + nfc_listener_start( + instance->listener, nfc_scene_mf_ultralight_capture_pass_worker_callback, instance); + + nfc_blink_read_start(instance); +} + +bool nfc_scene_mf_ultralight_capture_pass_on_event(void* context, SceneManagerEvent event) { + NfcApp* instance = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == MfUltralightListenerEventTypeAuth) { + notification_message(instance->notifications, &sequence_success); + scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightUnlockWarn); + consumed = true; + } + } + return consumed; +} + +void nfc_scene_mf_ultralight_capture_pass_on_exit(void* context) { + NfcApp* instance = context; + + // Clear view + nfc_listener_stop(instance->listener); + nfc_listener_free(instance->listener); + widget_reset(instance->widget); + + nfc_blink_stop(instance); +} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_data.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_data.c deleted file mode 100644 index 8cd223ee64d..00000000000 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_data.c +++ /dev/null @@ -1,32 +0,0 @@ -#include "../nfc_i.h" - -void nfc_scene_mf_ultralight_data_on_enter(void* context) { - Nfc* nfc = context; - MfUltralightData* data = &nfc->dev->dev_data.mf_ul_data; - TextBox* text_box = nfc->text_box; - - text_box_set_font(text_box, TextBoxFontHex); - for(uint16_t i = 0; i < data->data_size; i += 2) { - if(!(i % 8) && i) { - furi_string_push_back(nfc->text_box_store, '\n'); - } - furi_string_cat_printf(nfc->text_box_store, "%02X%02X ", data->data[i], data->data[i + 1]); - } - text_box_set_text(text_box, furi_string_get_cstr(nfc->text_box_store)); - - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewTextBox); -} - -bool nfc_scene_mf_ultralight_data_on_event(void* context, SceneManagerEvent event) { - UNUSED(context); - UNUSED(event); - return false; -} - -void nfc_scene_mf_ultralight_data_on_exit(void* context) { - Nfc* nfc = context; - - // Clean view - text_box_reset(nfc->text_box); - furi_string_reset(nfc->text_box_store); -} \ No newline at end of file diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_emulate.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_emulate.c deleted file mode 100644 index 9d8f17f9a2c..00000000000 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_emulate.c +++ /dev/null @@ -1,74 +0,0 @@ -#include "../nfc_i.h" - -#define NFC_MF_UL_DATA_NOT_CHANGED (0UL) -#define NFC_MF_UL_DATA_CHANGED (1UL) - -bool nfc_mf_ultralight_emulate_worker_callback(NfcWorkerEvent event, void* context) { - UNUSED(event); - Nfc* nfc = context; - - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneMfUltralightEmulate, NFC_MF_UL_DATA_CHANGED); - return true; -} - -void nfc_scene_mf_ultralight_emulate_on_enter(void* context) { - Nfc* nfc = context; - - // Setup view - MfUltralightType type = nfc->dev->dev_data.mf_ul_data.type; - bool is_ultralight = (type == MfUltralightTypeUL11) || (type == MfUltralightTypeUL21) || - (type == MfUltralightTypeUnknown); - Popup* popup = nfc->popup; - popup_set_header(popup, "Emulating", 67, 13, AlignLeft, AlignTop); - if(strcmp(nfc->dev->dev_name, "") != 0) { - nfc_text_store_set(nfc, "%s", nfc->dev->dev_name); - } else if(is_ultralight) { - nfc_text_store_set(nfc, "MIFARE\nUltralight"); - } else { - nfc_text_store_set(nfc, "MIFARE\nNTAG"); - } - popup_set_icon(popup, 0, 3, &I_NFC_dolphin_emulation_47x61); - popup_set_text(popup, nfc->text_store, 90, 28, AlignCenter, AlignTop); - - // Setup and start worker - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); - nfc_worker_start( - nfc->worker, - NfcWorkerStateMfUltralightEmulate, - &nfc->dev->dev_data, - nfc_mf_ultralight_emulate_worker_callback, - nfc); - nfc_blink_emulate_start(nfc); -} - -bool nfc_scene_mf_ultralight_emulate_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - - 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, NfcSceneMfUltralightEmulate) == - NFC_MF_UL_DATA_CHANGED) { - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneMfUltralightEmulate, NFC_MF_UL_DATA_NOT_CHANGED); - // Save shadow file - if(furi_string_size(nfc->dev->load_path)) { - nfc_device_save_shadow(nfc->dev, furi_string_get_cstr(nfc->dev->load_path)); - } - } - consumed = false; - } - return consumed; -} - -void nfc_scene_mf_ultralight_emulate_on_exit(void* context) { - Nfc* nfc = context; - - // Clear view - popup_reset(nfc->popup); - - nfc_blink_stop(nfc); -} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_key_input.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_key_input.c index 089187d5bc3..6db6023d274 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_key_input.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_key_input.c @@ -1,13 +1,13 @@ -#include "../nfc_i.h" +#include "../nfc_app_i.h" void nfc_scene_mf_ultralight_key_input_byte_input_callback(void* context) { - Nfc* nfc = context; + NfcApp* nfc = context; view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventByteInputDone); } void nfc_scene_mf_ultralight_key_input_on_enter(void* context) { - Nfc* nfc = context; + NfcApp* nfc = context; // Setup view ByteInput* byte_input = nfc->byte_input; @@ -17,13 +17,13 @@ void nfc_scene_mf_ultralight_key_input_on_enter(void* context) { nfc_scene_mf_ultralight_key_input_byte_input_callback, NULL, nfc, - nfc->byte_input_store, + nfc->mf_ul_auth->password.data, 4); view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewByteInput); } bool nfc_scene_mf_ultralight_key_input_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; + NfcApp* nfc = context; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { @@ -36,7 +36,7 @@ bool nfc_scene_mf_ultralight_key_input_on_event(void* context, SceneManagerEvent } void nfc_scene_mf_ultralight_key_input_on_exit(void* context) { - Nfc* nfc = context; + NfcApp* nfc = context; // Clear view byte_input_set_result_callback(nfc->byte_input, NULL, NULL, NULL, NULL, 0); diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_menu.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_menu.c deleted file mode 100644 index b3bd780f4b4..00000000000 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_menu.c +++ /dev/null @@ -1,89 +0,0 @@ -#include "../nfc_i.h" -#include - -enum SubmenuIndex { - SubmenuIndexUnlock, - SubmenuIndexSave, - SubmenuIndexEmulate, - SubmenuIndexInfo, -}; - -void nfc_scene_mf_ultralight_menu_submenu_callback(void* context, uint32_t index) { - Nfc* nfc = context; - - view_dispatcher_send_custom_event(nfc->view_dispatcher, index); -} - -void nfc_scene_mf_ultralight_menu_on_enter(void* context) { - Nfc* nfc = context; - Submenu* submenu = nfc->submenu; - MfUltralightData* data = &nfc->dev->dev_data.mf_ul_data; - - if(!mf_ul_is_full_capture(data) && data->type != MfUltralightTypeULC) { - submenu_add_item( - submenu, - "Unlock", - SubmenuIndexUnlock, - nfc_scene_mf_ultralight_menu_submenu_callback, - nfc); - } - submenu_add_item( - submenu, "Save", SubmenuIndexSave, nfc_scene_mf_ultralight_menu_submenu_callback, nfc); - if(mf_ul_emulation_supported(data)) { - submenu_add_item( - submenu, - "Emulate", - SubmenuIndexEmulate, - nfc_scene_mf_ultralight_menu_submenu_callback, - nfc); - } - submenu_add_item( - submenu, "Info", SubmenuIndexInfo, nfc_scene_mf_ultralight_menu_submenu_callback, nfc); - - submenu_set_selected_item( - nfc->submenu, scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfUltralightMenu)); - - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); -} - -bool nfc_scene_mf_ultralight_menu_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SubmenuIndexSave) { - nfc->dev->format = NfcDeviceSaveFormatMifareUl; - // 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_next_scene(nfc->scene_manager, NfcSceneMfUltralightEmulate); - if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSetType)) { - dolphin_deed(DolphinDeedNfcAddEmulate); - } else { - dolphin_deed(DolphinDeedNfcEmulate); - } - consumed = true; - } else if(event.event == SubmenuIndexUnlock) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightUnlockMenu); - consumed = true; - } else if(event.event == SubmenuIndexInfo) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcDataInfo); - consumed = true; - } - scene_manager_set_scene_state(nfc->scene_manager, NfcSceneMfUltralightMenu, event.event); - - } else if(event.type == SceneManagerEventTypeBack) { - consumed = scene_manager_previous_scene(nfc->scene_manager); - } - - return consumed; -} - -void nfc_scene_mf_ultralight_menu_on_exit(void* context) { - Nfc* nfc = context; - - // Clear view - submenu_reset(nfc->submenu); -} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_auth.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_auth.c deleted file mode 100644 index 2ab5e3f3f44..00000000000 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_auth.c +++ /dev/null @@ -1,116 +0,0 @@ -#include "../nfc_i.h" - -typedef enum { - NfcSceneMfUlReadStateIdle, - NfcSceneMfUlReadStateDetecting, - NfcSceneMfUlReadStateReading, - NfcSceneMfUlReadStateNotSupportedCard, -} NfcSceneMfUlReadState; - -bool nfc_scene_mf_ultralight_read_auth_worker_callback(NfcWorkerEvent event, void* context) { - Nfc* nfc = context; - - if(event == NfcWorkerEventMfUltralightPassKey) { - memcpy(nfc->dev->dev_data.mf_ul_data.auth_key, nfc->byte_input_store, 4); - } else { - view_dispatcher_send_custom_event(nfc->view_dispatcher, event); - } - return true; -} - -void nfc_scene_mf_ultralight_read_auth_set_state(Nfc* nfc, NfcSceneMfUlReadState state) { - uint32_t curr_state = - scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfUltralightReadAuth); - if(curr_state != state) { - if(state == NfcSceneMfUlReadStateDetecting) { - popup_reset(nfc->popup); - popup_set_text(nfc->popup, "Apply the\ntarget card", 97, 24, AlignCenter, AlignTop); - popup_set_icon(nfc->popup, 0, 8, &I_NFC_manual_60x50); - nfc_blink_read_start(nfc); - } else if(state == NfcSceneMfUlReadStateReading) { - popup_reset(nfc->popup); - popup_set_header( - nfc->popup, "Reading card\nDon't move...", 85, 24, AlignCenter, AlignTop); - popup_set_icon(nfc->popup, 12, 23, &A_Loading_24); - nfc_blink_detect_start(nfc); - } else if(state == NfcSceneMfUlReadStateNotSupportedCard) { - popup_reset(nfc->popup); - popup_set_header(nfc->popup, "Wrong type of card!", 64, 3, AlignCenter, AlignTop); - popup_set_text( - nfc->popup, - "Only MIFARE\nUltralight & NTAG\nare supported", - 4, - 22, - AlignLeft, - AlignTop); - popup_set_icon(nfc->popup, 73, 20, &I_DolphinCommon_56x48); - nfc_blink_stop(nfc); - notification_message(nfc->notifications, &sequence_error); - notification_message(nfc->notifications, &sequence_set_red_255); - } - scene_manager_set_scene_state(nfc->scene_manager, NfcSceneMfUltralightReadAuth, state); - } -} - -void nfc_scene_mf_ultralight_read_auth_on_enter(void* context) { - Nfc* nfc = context; - - nfc_device_clear(nfc->dev); - // Setup view - nfc_scene_mf_ultralight_read_auth_set_state(nfc, NfcSceneMfUlReadStateDetecting); - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); - // Start worker - nfc_worker_start( - nfc->worker, - NfcWorkerStateReadMfUltralightReadAuth, - &nfc->dev->dev_data, - nfc_scene_mf_ultralight_read_auth_worker_callback, - nfc); -} - -bool nfc_scene_mf_ultralight_read_auth_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if((event.event == NfcWorkerEventSuccess) || (event.event == NfcWorkerEventFail)) { - notification_message(nfc->notifications, &sequence_success); - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightReadAuthResult); - consumed = true; - } else if(event.event == NfcWorkerEventCardDetected) { - nfc_scene_mf_ultralight_read_auth_set_state(nfc, NfcSceneMfUlReadStateReading); - consumed = true; - } else if(event.event == NfcWorkerEventNoCardDetected) { - nfc_scene_mf_ultralight_read_auth_set_state(nfc, NfcSceneMfUlReadStateDetecting); - consumed = true; - } else if(event.event == NfcWorkerEventWrongCardDetected) { - nfc_scene_mf_ultralight_read_auth_set_state( - nfc, NfcSceneMfUlReadStateNotSupportedCard); - } - } else if(event.type == SceneManagerEventTypeBack) { - MfUltralightData* mf_ul_data = &nfc->dev->dev_data.mf_ul_data; - NfcScene next_scene; - if(mf_ul_data->auth_method == MfUltralightAuthMethodManual) { - next_scene = NfcSceneMfUltralightKeyInput; - } else if(mf_ul_data->auth_method == MfUltralightAuthMethodAuto) { - next_scene = NfcSceneMfUltralightUnlockAuto; - } else { - next_scene = NfcSceneMfUltralightUnlockMenu; - } - consumed = - scene_manager_search_and_switch_to_previous_scene(nfc->scene_manager, next_scene); - } - return consumed; -} - -void nfc_scene_mf_ultralight_read_auth_on_exit(void* context) { - Nfc* nfc = context; - - // Stop worker - nfc_worker_stop(nfc->worker); - // Clear view - popup_reset(nfc->popup); - nfc_blink_stop(nfc); - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneMfUltralightReadAuth, NfcSceneMfUlReadStateIdle); -} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_auth_result.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_auth_result.c deleted file mode 100644 index b125e999127..00000000000 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_auth_result.c +++ /dev/null @@ -1,116 +0,0 @@ -#include "../nfc_i.h" - -void nfc_scene_mf_ultralight_read_auth_result_widget_callback( - GuiButtonType result, - InputType type, - void* context) { - Nfc* nfc = context; - - if(type == InputTypeShort) { - view_dispatcher_send_custom_event(nfc->view_dispatcher, result); - } -} - -void nfc_scene_mf_ultralight_read_auth_result_on_enter(void* context) { - Nfc* nfc = context; - - // Setup dialog view - FuriHalNfcDevData* nfc_data = &nfc->dev->dev_data.nfc_data; - MfUltralightData* mf_ul_data = &nfc->dev->dev_data.mf_ul_data; - MfUltralightConfigPages* config_pages = mf_ultralight_get_config_pages(mf_ul_data); - Widget* widget = nfc->widget; - const char* title; - FuriString* temp_str; - temp_str = furi_string_alloc(); - - if((mf_ul_data->data_read == mf_ul_data->data_size) && (mf_ul_data->data_read > 0)) { - if(mf_ul_data->auth_success) { - title = "All pages are unlocked!"; - } else { - title = "All unlocked but failed auth!"; - } - } else { - title = "Not all pages unlocked!"; - } - widget_add_string_element(widget, 64, 0, AlignCenter, AlignTop, FontPrimary, title); - furi_string_set(temp_str, "UID:"); - for(size_t i = 0; i < nfc_data->uid_len; i++) { - furi_string_cat_printf(temp_str, " %02X", nfc_data->uid[i]); - } - widget_add_string_element( - widget, 0, 17, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(temp_str)); - if(mf_ul_data->auth_success) { - furi_string_printf( - temp_str, - "Password: %02X %02X %02X %02X", - config_pages->auth_data.pwd.raw[0], - config_pages->auth_data.pwd.raw[1], - config_pages->auth_data.pwd.raw[2], - config_pages->auth_data.pwd.raw[3]); - widget_add_string_element( - widget, 0, 28, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(temp_str)); - furi_string_printf( - temp_str, - "PACK: %02X %02X", - config_pages->auth_data.pack.raw[0], - config_pages->auth_data.pack.raw[1]); - widget_add_string_element( - widget, 0, 39, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(temp_str)); - } - furi_string_printf( - temp_str, "Pages Read: %d/%d", mf_ul_data->data_read / 4, mf_ul_data->data_size / 4); - widget_add_string_element( - widget, 0, 50, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(temp_str)); - widget_add_button_element( - widget, - GuiButtonTypeRight, - "Save", - nfc_scene_mf_ultralight_read_auth_result_widget_callback, - nfc); - - furi_string_free(temp_str); - notification_message(nfc->notifications, &sequence_set_green_255); - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); -} - -bool nfc_scene_mf_ultralight_read_auth_result_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == GuiButtonTypeRight) { - nfc->dev->format = NfcDeviceSaveFormatMifareUl; - // Clear device name - nfc_device_set_name(nfc->dev, ""); - scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveName); - consumed = true; - } - } else if(event.type == SceneManagerEventTypeBack) { - MfUltralightData* mf_ul_data = &nfc->dev->dev_data.mf_ul_data; - if(mf_ul_data->auth_method == MfUltralightAuthMethodManual || - mf_ul_data->auth_method == MfUltralightAuthMethodAuto) { - consumed = scene_manager_previous_scene(nfc->scene_manager); - } else { - NfcScene next_scene; - if((mf_ul_data->data_read == mf_ul_data->data_size) && (mf_ul_data->data_read > 0)) { - next_scene = NfcSceneMfUltralightMenu; - } else { - next_scene = NfcSceneMfUltralightUnlockMenu; - } - - consumed = - scene_manager_search_and_switch_to_previous_scene(nfc->scene_manager, next_scene); - } - } - - return consumed; -} - -void nfc_scene_mf_ultralight_read_auth_result_on_exit(void* context) { - Nfc* nfc = context; - - // Clean views - widget_reset(nfc->widget); - - notification_message_block(nfc->notifications, &sequence_reset_green); -} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_success.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_success.c deleted file mode 100644 index cb5ccd6e826..00000000000 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_success.c +++ /dev/null @@ -1,84 +0,0 @@ -#include "../nfc_i.h" - -void nfc_scene_mf_ultralight_read_success_widget_callback( - GuiButtonType result, - InputType type, - void* context) { - Nfc* nfc = context; - - if(type == InputTypeShort) { - view_dispatcher_send_custom_event(nfc->view_dispatcher, result); - } -} - -void nfc_scene_mf_ultralight_read_success_on_enter(void* context) { - Nfc* nfc = context; - - // Setup widget view - FuriHalNfcDevData* data = &nfc->dev->dev_data.nfc_data; - MfUltralightData* mf_ul_data = &nfc->dev->dev_data.mf_ul_data; - Widget* widget = nfc->widget; - widget_add_button_element( - widget, - GuiButtonTypeLeft, - "Retry", - nfc_scene_mf_ultralight_read_success_widget_callback, - nfc); - widget_add_button_element( - widget, - GuiButtonTypeRight, - "More", - nfc_scene_mf_ultralight_read_success_widget_callback, - nfc); - - FuriString* temp_str = NULL; - if(furi_string_size(nfc->dev->dev_data.parsed_data)) { - temp_str = furi_string_alloc_set(nfc->dev->dev_data.parsed_data); - } else { - temp_str = furi_string_alloc_printf("\e#%s\n", nfc_mf_ul_type(mf_ul_data->type, true)); - furi_string_cat_printf(temp_str, "UID:"); - for(size_t i = 0; i < data->uid_len; i++) { - furi_string_cat_printf(temp_str, " %02X", data->uid[i]); - } - furi_string_cat_printf( - temp_str, "\nPages Read: %d/%d", mf_ul_data->data_read / 4, mf_ul_data->data_size / 4); - if(mf_ul_data->data_read != mf_ul_data->data_size) { - furi_string_cat_printf(temp_str, "\nPassword-protected pages!"); - } - } - widget_add_text_scroll_element(widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); - furi_string_free(temp_str); - - notification_message_block(nfc->notifications, &sequence_set_green_255); - - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); -} - -bool nfc_scene_mf_ultralight_read_success_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == GuiButtonTypeLeft) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneRetryConfirm); - consumed = true; - } else if(event.event == GuiButtonTypeRight) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightMenu); - consumed = true; - } - } else if(event.type == SceneManagerEventTypeBack) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneExitConfirm); - consumed = true; - } - - return consumed; -} - -void nfc_scene_mf_ultralight_read_success_on_exit(void* context) { - Nfc* nfc = context; - - notification_message_block(nfc->notifications, &sequence_reset_green); - - // Clean view - widget_reset(nfc->widget); -} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_auto.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_auto.c deleted file mode 100644 index c59fe3a7d09..00000000000 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_auto.c +++ /dev/null @@ -1,64 +0,0 @@ -#include "../nfc_i.h" - -bool nfc_scene_mf_ultralight_unlock_auto_worker_callback(NfcWorkerEvent event, void* context) { - Nfc* nfc = context; - - view_dispatcher_send_custom_event(nfc->view_dispatcher, event); - return true; -} - -void nfc_scene_mf_ultralight_unlock_auto_on_enter(void* context) { - Nfc* nfc = context; - - // Setup view - widget_add_string_multiline_element( - nfc->widget, - 54, - 30, - AlignLeft, - AlignCenter, - FontPrimary, - "Touch the\nreader to get\npassword..."); - widget_add_icon_element(nfc->widget, 0, 15, &I_Modern_reader_18x34); - widget_add_icon_element(nfc->widget, 20, 12, &I_Move_flipper_26x39); - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); - - // Start worker - nfc_worker_start( - nfc->worker, - NfcWorkerStateMfUltralightEmulate, - &nfc->dev->dev_data, - nfc_scene_mf_ultralight_unlock_auto_worker_callback, - nfc); - - nfc_blink_read_start(nfc); -} - -bool nfc_scene_mf_ultralight_unlock_auto_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if((event.event == NfcWorkerEventMfUltralightPwdAuth)) { - MfUltralightAuth* auth = &nfc->dev->dev_data.mf_ul_auth; - memcpy(nfc->byte_input_store, auth->pwd.raw, sizeof(auth->pwd.raw)); - nfc->dev->dev_data.mf_ul_data.auth_method = MfUltralightAuthMethodAuto; - nfc_worker_stop(nfc->worker); - notification_message(nfc->notifications, &sequence_success); - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightUnlockWarn); - consumed = true; - } - } - return consumed; -} - -void nfc_scene_mf_ultralight_unlock_auto_on_exit(void* context) { - Nfc* nfc = context; - - // Stop worker - nfc_worker_stop(nfc->worker); - // Clear view - widget_reset(nfc->widget); - - nfc_blink_stop(nfc); -} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_menu.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_menu.c index 484629b0bbe..4d97040ee20 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_menu.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_menu.c @@ -1,29 +1,29 @@ -#include "../nfc_i.h" +#include "../nfc_app_i.h" enum SubmenuIndex { - SubmenuIndexMfUlUnlockMenuAuto, + SubmenuIndexMfUlUnlockMenuReader, SubmenuIndexMfUlUnlockMenuAmeebo, SubmenuIndexMfUlUnlockMenuXiaomi, SubmenuIndexMfUlUnlockMenuManual, }; void nfc_scene_mf_ultralight_unlock_menu_submenu_callback(void* context, uint32_t index) { - Nfc* nfc = context; + NfcApp* nfc = context; view_dispatcher_send_custom_event(nfc->view_dispatcher, index); } void nfc_scene_mf_ultralight_unlock_menu_on_enter(void* context) { - Nfc* nfc = context; + NfcApp* nfc = context; Submenu* submenu = nfc->submenu; uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfUltralightUnlockMenu); - if(nfc->dev->dev_data.protocol == NfcDeviceProtocolMifareUl) { + if(nfc_device_get_protocol(nfc->nfc_device) == NfcProtocolMfUltralight) { submenu_add_item( submenu, "Unlock With Reader", - SubmenuIndexMfUlUnlockMenuAuto, + SubmenuIndexMfUlUnlockMenuReader, nfc_scene_mf_ultralight_unlock_menu_submenu_callback, nfc); } @@ -50,24 +50,25 @@ void nfc_scene_mf_ultralight_unlock_menu_on_enter(void* context) { } bool nfc_scene_mf_ultralight_unlock_menu_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; + NfcApp* nfc = context; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { if(event.event == SubmenuIndexMfUlUnlockMenuManual) { - nfc->dev->dev_data.mf_ul_data.auth_method = MfUltralightAuthMethodManual; + nfc->mf_ul_auth->type = MfUltralightAuthTypeManual; scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightKeyInput); consumed = true; } else if(event.event == SubmenuIndexMfUlUnlockMenuAmeebo) { - nfc->dev->dev_data.mf_ul_data.auth_method = MfUltralightAuthMethodAmeebo; + nfc->mf_ul_auth->type = MfUltralightAuthTypeAmiibo; scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightUnlockWarn); consumed = true; } else if(event.event == SubmenuIndexMfUlUnlockMenuXiaomi) { - nfc->dev->dev_data.mf_ul_data.auth_method = MfUltralightAuthMethodXiaomi; + nfc->mf_ul_auth->type = MfUltralightAuthTypeXiaomi; scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightUnlockWarn); consumed = true; - } else if(event.event == SubmenuIndexMfUlUnlockMenuAuto) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightUnlockAuto); + } else if(event.event == SubmenuIndexMfUlUnlockMenuReader) { + nfc->mf_ul_auth->type = MfUltralightAuthTypeReader; + scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightCapturePass); consumed = true; } scene_manager_set_scene_state( @@ -77,7 +78,7 @@ bool nfc_scene_mf_ultralight_unlock_menu_on_event(void* context, SceneManagerEve } void nfc_scene_mf_ultralight_unlock_menu_on_exit(void* context) { - Nfc* nfc = context; + NfcApp* nfc = context; submenu_reset(nfc->submenu); } diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_warn.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_warn.c index af2eca0ce5d..6be051cedd8 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_warn.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_warn.c @@ -1,45 +1,41 @@ -#include "../nfc_i.h" +#include "../nfc_app_i.h" #include void nfc_scene_mf_ultralight_unlock_warn_dialog_callback(DialogExResult result, void* context) { - Nfc* nfc = context; + NfcApp* nfc = context; view_dispatcher_send_custom_event(nfc->view_dispatcher, result); } void nfc_scene_mf_ultralight_unlock_warn_on_enter(void* context) { - Nfc* nfc = context; + NfcApp* nfc = context; DialogEx* dialog_ex = nfc->dialog_ex; - MfUltralightAuthMethod auth_method = nfc->dev->dev_data.mf_ul_data.auth_method; dialog_ex_set_context(dialog_ex, nfc); dialog_ex_set_result_callback(dialog_ex, nfc_scene_mf_ultralight_unlock_warn_dialog_callback); - if(auth_method == MfUltralightAuthMethodManual || auth_method == MfUltralightAuthMethodAuto) { + MfUltralightAuthType type = nfc->mf_ul_auth->type; + if((type == MfUltralightAuthTypeReader) || (type == MfUltralightAuthTypeManual)) { // Build dialog text - MfUltralightAuth* auth = &nfc->dev->dev_data.mf_ul_auth; FuriString* password_str = furi_string_alloc_set_str("Try to unlock the card with\npassword: "); - for(size_t i = 0; i < sizeof(auth->pwd.raw); ++i) { - furi_string_cat_printf(password_str, "%02X ", nfc->byte_input_store[i]); + for(size_t i = 0; i < sizeof(nfc->mf_ul_auth->password.data); i++) { + furi_string_cat_printf(password_str, "%02X ", nfc->mf_ul_auth->password.data[i]); } furi_string_cat_str(password_str, "?\nCaution, a wrong password\ncan block the card!"); nfc_text_store_set(nfc, furi_string_get_cstr(password_str)); furi_string_free(password_str); - dialog_ex_set_header( - dialog_ex, - auth_method == MfUltralightAuthMethodAuto ? "Password captured!" : "Risky function!", - 64, - 0, - AlignCenter, - AlignTop); + const char* message = (type == MfUltralightAuthTypeReader) ? "Password captured!" : + "Risky function!"; + dialog_ex_set_header(dialog_ex, message, 64, 0, AlignCenter, AlignTop); dialog_ex_set_text(dialog_ex, nfc->text_store, 64, 12, AlignCenter, AlignTop); dialog_ex_set_left_button_text(dialog_ex, "Cancel"); dialog_ex_set_right_button_text(dialog_ex, "Continue"); - if(auth_method == MfUltralightAuthMethodAuto) + if(type == MfUltralightAuthTypeReader) { notification_message(nfc->notifications, &sequence_set_green_255); + } } else { dialog_ex_set_header(dialog_ex, "Risky function!", 64, 4, AlignCenter, AlignTop); dialog_ex_set_text( @@ -52,19 +48,20 @@ void nfc_scene_mf_ultralight_unlock_warn_on_enter(void* context) { } bool nfc_scene_mf_ultralight_unlock_warn_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; + NfcApp* nfc = context; bool consumed = false; - MfUltralightAuthMethod auth_method = nfc->dev->dev_data.mf_ul_data.auth_method; - if(auth_method == MfUltralightAuthMethodManual || auth_method == MfUltralightAuthMethodAuto) { + nfc->protocols_detected[0] = nfc_device_get_protocol(nfc->nfc_device); + MfUltralightAuthType type = nfc->mf_ul_auth->type; + if((type == MfUltralightAuthTypeReader) || (type == MfUltralightAuthTypeManual)) { if(event.type == SceneManagerEventTypeCustom) { if(event.event == DialogExResultRight) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightReadAuth); + scene_manager_next_scene(nfc->scene_manager, NfcSceneRead); dolphin_deed(DolphinDeedNfcRead); consumed = true; } else if(event.event == DialogExResultLeft) { - if(auth_method == MfUltralightAuthMethodAuto) { + if(type == MfUltralightAuthTypeReader) { consumed = scene_manager_search_and_switch_to_previous_scene( nfc->scene_manager, NfcSceneMfUltralightUnlockMenu); } else { @@ -78,7 +75,9 @@ bool nfc_scene_mf_ultralight_unlock_warn_on_event(void* context, SceneManagerEve } else { if(event.type == SceneManagerEventTypeCustom) { if(event.event == DialogExResultCenter) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightReadAuth); + const NfcProtocol mfu_protocol[] = {NfcProtocolMfUltralight}; + nfc_app_set_detected_protocols(nfc, mfu_protocol, COUNT_OF(mfu_protocol)); + scene_manager_next_scene(nfc->scene_manager, NfcSceneRead); dolphin_deed(DolphinDeedNfcRead); consumed = true; } @@ -89,7 +88,7 @@ bool nfc_scene_mf_ultralight_unlock_warn_on_event(void* context, SceneManagerEve } void nfc_scene_mf_ultralight_unlock_warn_on_exit(void* context) { - Nfc* nfc = context; + NfcApp* nfc = context; dialog_ex_reset(nfc->dialog_ex); nfc_text_store_clear(nfc); diff --git a/applications/main/nfc/scenes/nfc_scene_mfkey_complete.c b/applications/main/nfc/scenes/nfc_scene_mfkey_complete.c deleted file mode 100644 index 04515f24ff6..00000000000 --- a/applications/main/nfc/scenes/nfc_scene_mfkey_complete.c +++ /dev/null @@ -1,49 +0,0 @@ -#include "../nfc_i.h" - -void nfc_scene_mfkey_complete_callback(GuiButtonType result, InputType type, void* context) { - Nfc* nfc = context; - if(type == InputTypeShort) { - view_dispatcher_send_custom_event(nfc->view_dispatcher, result); - } -} - -void nfc_scene_mfkey_complete_on_enter(void* context) { - Nfc* nfc = context; - - widget_add_string_element(nfc->widget, 64, 0, AlignCenter, AlignTop, FontPrimary, "Complete!"); - widget_add_string_multiline_element( - nfc->widget, - 64, - 32, - AlignCenter, - AlignCenter, - FontSecondary, - "Now use Mfkey32\nto extract keys"); - widget_add_button_element( - nfc->widget, GuiButtonTypeCenter, "OK", nfc_scene_mfkey_complete_callback, nfc); - - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); -} - -bool nfc_scene_mfkey_complete_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == GuiButtonTypeCenter) { - consumed = scene_manager_search_and_switch_to_previous_scene( - nfc->scene_manager, NfcSceneStart); - } - } else if(event.event == SceneManagerEventTypeBack) { - consumed = - scene_manager_search_and_switch_to_previous_scene(nfc->scene_manager, NfcSceneStart); - } - - return consumed; -} - -void nfc_scene_mfkey_complete_on_exit(void* context) { - Nfc* nfc = context; - - widget_reset(nfc->widget); -} diff --git a/applications/main/nfc/scenes/nfc_scene_mfkey_nonces_info.c b/applications/main/nfc/scenes/nfc_scene_mfkey_nonces_info.c deleted file mode 100644 index 6d9852f3e66..00000000000 --- a/applications/main/nfc/scenes/nfc_scene_mfkey_nonces_info.c +++ /dev/null @@ -1,55 +0,0 @@ -#include "../nfc_i.h" -#include - -void nfc_scene_mfkey_nonces_info_callback(GuiButtonType result, InputType type, void* context) { - Nfc* nfc = context; - if(type == InputTypeShort) { - view_dispatcher_send_custom_event(nfc->view_dispatcher, result); - } -} - -void nfc_scene_mfkey_nonces_info_on_enter(void* context) { - Nfc* nfc = context; - - FuriString* temp_str; - temp_str = furi_string_alloc(); - - uint16_t nonces_saved = mfkey32_get_auth_sectors(temp_str); - widget_add_text_scroll_element(nfc->widget, 0, 22, 128, 42, furi_string_get_cstr(temp_str)); - furi_string_printf(temp_str, "Nonce pairs saved: %d", nonces_saved); - widget_add_string_element( - nfc->widget, 0, 0, AlignLeft, AlignTop, FontPrimary, furi_string_get_cstr(temp_str)); - widget_add_string_element( - nfc->widget, 0, 12, AlignLeft, AlignTop, FontSecondary, "Authenticated sectors:"); - - widget_add_button_element( - nfc->widget, GuiButtonTypeCenter, "OK", nfc_scene_mfkey_nonces_info_callback, nfc); - - furi_string_free(temp_str); - - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); -} - -bool nfc_scene_mfkey_nonces_info_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == GuiButtonTypeCenter) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfkeyComplete); - 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_mfkey_nonces_info_on_exit(void* context) { - Nfc* nfc = context; - - // Clear view - widget_reset(nfc->widget); -} diff --git a/applications/main/nfc/scenes/nfc_scene_more_info.c b/applications/main/nfc/scenes/nfc_scene_more_info.c new file mode 100644 index 00000000000..b74e30295d3 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_more_info.c @@ -0,0 +1,13 @@ +#include "../helpers/protocol_support/nfc_protocol_support.h" + +void nfc_scene_more_info_on_enter(void* context) { + nfc_protocol_support_on_enter(NfcProtocolSupportSceneMoreInfo, context); +} + +bool nfc_scene_more_info_on_event(void* context, SceneManagerEvent event) { + return nfc_protocol_support_on_event(NfcProtocolSupportSceneMoreInfo, context, event); +} + +void nfc_scene_more_info_on_exit(void* context) { + nfc_protocol_support_on_exit(NfcProtocolSupportSceneMoreInfo, context); +} diff --git a/applications/main/nfc/scenes/nfc_scene_nfc_data_info.c b/applications/main/nfc/scenes/nfc_scene_nfc_data_info.c deleted file mode 100644 index 66a9174df47..00000000000 --- a/applications/main/nfc/scenes/nfc_scene_nfc_data_info.c +++ /dev/null @@ -1,309 +0,0 @@ -#include "../nfc_i.h" - -void nfc_scene_nfc_data_info_widget_callback(GuiButtonType result, InputType type, void* context) { - Nfc* nfc = context; - if(type == InputTypeShort) { - view_dispatcher_send_custom_event(nfc->view_dispatcher, result); - } -} - -void nfc_scene_slix_build_string( - FuriString* temp_str, - NfcVData* nfcv_data, - SlixTypeFeatures features, - const char* type) { - furi_string_cat_printf(temp_str, "Type: %s\n", type); - furi_string_cat_printf(temp_str, "Keys:\n"); - if(features & SlixFeatureRead) { - furi_string_cat_printf( - temp_str, - " Read %08llX%s\n", - nfc_util_bytes2num(nfcv_data->sub_data.slix.key_read, 4), - (nfcv_data->sub_data.slix.flags & NfcVSlixDataFlagsHasKeyRead) ? "" : " (unset)"); - } - if(features & SlixFeatureWrite) { - furi_string_cat_printf( - temp_str, - " Write %08llX%s\n", - nfc_util_bytes2num(nfcv_data->sub_data.slix.key_write, 4), - (nfcv_data->sub_data.slix.flags & NfcVSlixDataFlagsHasKeyWrite) ? "" : " (unset)"); - } - if(features & SlixFeaturePrivacy) { - furi_string_cat_printf( - temp_str, - " Privacy %08llX%s\n", - nfc_util_bytes2num(nfcv_data->sub_data.slix.key_privacy, 4), - (nfcv_data->sub_data.slix.flags & NfcVSlixDataFlagsHasKeyPrivacy) ? "" : " (unset)"); - furi_string_cat_printf( - temp_str, - " Privacy mode %s\n", - (nfcv_data->sub_data.slix.flags & NfcVSlixDataFlagsPrivacy) ? "ENABLED" : "DISABLED"); - } - if(features & SlixFeatureDestroy) { - furi_string_cat_printf( - temp_str, - " Destroy %08llX%s\n", - nfc_util_bytes2num(nfcv_data->sub_data.slix.key_destroy, 4), - (nfcv_data->sub_data.slix.flags & NfcVSlixDataFlagsHasKeyDestroy) ? "" : " (unset)"); - } - if(features & SlixFeatureEas) { - furi_string_cat_printf( - temp_str, - " EAS %08llX%s\n", - nfc_util_bytes2num(nfcv_data->sub_data.slix.key_eas, 4), - (nfcv_data->sub_data.slix.flags & NfcVSlixDataFlagsHasKeyEas) ? "" : " (unset)"); - } - if(features & SlixFeatureSignature) { - furi_string_cat_printf( - temp_str, - "Signature %08llX...\n", - nfc_util_bytes2num(nfcv_data->sub_data.slix.signature, 4)); - } - furi_string_cat_printf( - temp_str, - "DSFID: %02X %s\n", - nfcv_data->dsfid, - (nfcv_data->security_status[0] & NfcVLockBitDsfid) ? "(locked)" : ""); - furi_string_cat_printf( - temp_str, - "AFI: %02X %s\n", - nfcv_data->afi, - (nfcv_data->security_status[0] & NfcVLockBitAfi) ? "(locked)" : ""); - furi_string_cat_printf( - temp_str, - "EAS: %s\n", - (nfcv_data->security_status[0] & NfcVLockBitEas) ? "locked" : "not locked"); - - if(features & SlixFeatureProtection) { - furi_string_cat_printf( - temp_str, - "PPL: %s\n", - (nfcv_data->security_status[0] & NfcVLockBitPpl) ? "locked" : "not locked"); - furi_string_cat_printf(temp_str, "Prot.ptr %02X\n", nfcv_data->sub_data.slix.pp_pointer); - furi_string_cat_printf(temp_str, "Prot.con %02X\n", nfcv_data->sub_data.slix.pp_condition); - } -} - -void nfc_scene_nfc_data_info_on_enter(void* context) { - Nfc* nfc = context; - Widget* widget = nfc->widget; - FuriHalNfcDevData* nfc_data = &nfc->dev->dev_data.nfc_data; - NfcDeviceData* dev_data = &nfc->dev->dev_data; - NfcProtocol protocol = dev_data->protocol; - uint8_t text_scroll_height = 0; - if((protocol == NfcDeviceProtocolMifareDesfire) || (protocol == NfcDeviceProtocolMifareUl) || - (protocol == NfcDeviceProtocolMifareClassic)) { - widget_add_button_element( - widget, GuiButtonTypeRight, "More", nfc_scene_nfc_data_info_widget_callback, nfc); - text_scroll_height = 52; - } else { - text_scroll_height = 64; - } - - FuriString* temp_str; - temp_str = furi_string_alloc(); - // Set name if present - if(nfc->dev->dev_name[0] != '\0') { - furi_string_printf(temp_str, "\ec%s\n", nfc->dev->dev_name); - } - - // Set tag type - if(protocol == NfcDeviceProtocolEMV) { - furi_string_cat_printf(temp_str, "\e#EMV Bank Card\n"); - } else if(protocol == NfcDeviceProtocolMifareUl) { - furi_string_cat_printf( - temp_str, "\e#%s\n", nfc_mf_ul_type(dev_data->mf_ul_data.type, true)); - } else if(protocol == NfcDeviceProtocolMifareClassic) { - furi_string_cat_printf( - temp_str, "\e#%s\n", nfc_mf_classic_type(dev_data->mf_classic_data.type)); - } else if(protocol == NfcDeviceProtocolMifareDesfire) { - furi_string_cat_printf(temp_str, "\e#MIFARE DESFire\n"); - } else if(protocol == NfcDeviceProtocolNfcV) { - switch(dev_data->nfcv_data.sub_type) { - case NfcVTypePlain: - furi_string_cat_printf(temp_str, "\e#ISO15693\n"); - break; - case NfcVTypeSlix: - furi_string_cat_printf(temp_str, "\e#ISO15693 SLIX\n"); - break; - case NfcVTypeSlixS: - furi_string_cat_printf(temp_str, "\e#ISO15693 SLIX-S\n"); - break; - case NfcVTypeSlixL: - furi_string_cat_printf(temp_str, "\e#ISO15693 SLIX-L\n"); - break; - case NfcVTypeSlix2: - furi_string_cat_printf(temp_str, "\e#ISO15693 SLIX2\n"); - break; - default: - furi_string_cat_printf(temp_str, "\e#ISO15693 (unknown)\n"); - break; - } - } else { - furi_string_cat_printf(temp_str, "\e#Unknown ISO tag\n"); - } - - // Set tag iso data - if(protocol == NfcDeviceProtocolNfcV) { - NfcVData* nfcv_data = &nfc->dev->dev_data.nfcv_data; - - furi_string_cat_printf(temp_str, "UID:\n"); - for(size_t i = 0; i < nfc_data->uid_len; i++) { - furi_string_cat_printf(temp_str, " %02X", nfc_data->uid[i]); - } - furi_string_cat_printf(temp_str, "\n"); - - furi_string_cat_printf(temp_str, "IC Ref: %d\n", nfcv_data->ic_ref); - furi_string_cat_printf(temp_str, "Blocks: %d\n", nfcv_data->block_num); - furi_string_cat_printf(temp_str, "Blocksize: %d\n", nfcv_data->block_size); - - switch(dev_data->nfcv_data.sub_type) { - case NfcVTypePlain: - furi_string_cat_printf(temp_str, "Type: Plain\n"); - break; - case NfcVTypeSlix: - nfc_scene_slix_build_string(temp_str, nfcv_data, SlixFeatureSlix, "SLIX"); - break; - case NfcVTypeSlixS: - nfc_scene_slix_build_string(temp_str, nfcv_data, SlixFeatureSlixS, "SLIX-S"); - break; - case NfcVTypeSlixL: - nfc_scene_slix_build_string(temp_str, nfcv_data, SlixFeatureSlixL, "SLIX-L"); - break; - case NfcVTypeSlix2: - nfc_scene_slix_build_string(temp_str, nfcv_data, SlixFeatureSlix2, "SLIX2"); - break; - default: - furi_string_cat_printf(temp_str, "\e#ISO15693 (unknown)\n"); - break; - } - - furi_string_cat_printf( - temp_str, "Data (%d byte)\n", nfcv_data->block_num * nfcv_data->block_size); - - int maxBlocks = nfcv_data->block_num; - if(maxBlocks > 32) { - maxBlocks = 32; - furi_string_cat_printf(temp_str, "(truncated to %d blocks)\n", maxBlocks); - } - - for(int block = 0; block < maxBlocks; block++) { - const char* status = (nfcv_data->security_status[block] & 0x01) ? "(lck)" : ""; - for(int pos = 0; pos < nfcv_data->block_size; pos++) { - furi_string_cat_printf( - temp_str, " %02X", nfcv_data->data[block * nfcv_data->block_size + pos]); - } - furi_string_cat_printf(temp_str, " %s\n", status); - } - - } else { - char iso_type = FURI_BIT(nfc_data->sak, 5) ? '4' : '3'; - furi_string_cat_printf(temp_str, "ISO 14443-%c (NFC-A)\n", iso_type); - furi_string_cat_printf(temp_str, "UID:"); - for(size_t i = 0; i < nfc_data->uid_len; i++) { - furi_string_cat_printf(temp_str, " %02X", nfc_data->uid[i]); - } - furi_string_cat_printf( - temp_str, "\nATQA: %02X %02X ", nfc_data->atqa[1], nfc_data->atqa[0]); - furi_string_cat_printf(temp_str, " SAK: %02X", nfc_data->sak); - } - - // Set application specific data - if(protocol == NfcDeviceProtocolMifareDesfire) { - MifareDesfireData* data = &dev_data->mf_df_data; - uint32_t bytes_total = 1UL << (data->version.sw_storage >> 1); - uint32_t bytes_free = data->free_memory ? data->free_memory->bytes : 0; - furi_string_cat_printf(temp_str, "\n%lu", bytes_total); - if(data->version.sw_storage & 1) { - furi_string_push_back(temp_str, '+'); - } - furi_string_cat_printf(temp_str, " bytes, %lu bytes free\n", bytes_free); - - uint16_t n_apps = 0; - uint16_t n_files = 0; - for(MifareDesfireApplication* app = data->app_head; app; app = app->next) { - n_apps++; - for(MifareDesfireFile* file = app->file_head; file; file = file->next) { - n_files++; - } - } - furi_string_cat_printf(temp_str, "%d Application", n_apps); - if(n_apps != 1) { - furi_string_push_back(temp_str, 's'); - } - furi_string_cat_printf(temp_str, ", %d file", n_files); - if(n_files != 1) { - furi_string_push_back(temp_str, 's'); - } - } else if(protocol == NfcDeviceProtocolMifareUl) { - MfUltralightData* data = &dev_data->mf_ul_data; - furi_string_cat_printf( - temp_str, "\nPages Read %d/%d", data->data_read / 4, data->data_size / 4); - if(data->data_size > data->data_read) { - furi_string_cat_printf(temp_str, "\nPassword-protected"); - } else if(data->auth_success) { - MfUltralightConfigPages* config_pages = mf_ultralight_get_config_pages(data); - if(config_pages) { - furi_string_cat_printf( - temp_str, - "\nPassword: %02X %02X %02X %02X", - config_pages->auth_data.pwd.raw[0], - config_pages->auth_data.pwd.raw[1], - config_pages->auth_data.pwd.raw[2], - config_pages->auth_data.pwd.raw[3]); - furi_string_cat_printf( - temp_str, - "\nPACK: %02X %02X", - config_pages->auth_data.pack.raw[0], - config_pages->auth_data.pack.raw[1]); - } - } - } else if(protocol == NfcDeviceProtocolMifareClassic) { - MfClassicData* data = &dev_data->mf_classic_data; - uint8_t sectors_total = mf_classic_get_total_sectors_num(data->type); - uint8_t keys_total = sectors_total * 2; - uint8_t keys_found = 0; - uint8_t sectors_read = 0; - mf_classic_get_read_sectors_and_keys(data, §ors_read, &keys_found); - furi_string_cat_printf(temp_str, "\nKeys Found %d/%d", keys_found, keys_total); - furi_string_cat_printf(temp_str, "\nSectors Read %d/%d", sectors_read, sectors_total); - } - - // Add text scroll widget - widget_add_text_scroll_element( - widget, 0, 0, 128, text_scroll_height, furi_string_get_cstr(temp_str)); - furi_string_free(temp_str); - - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); -} - -bool nfc_scene_nfc_data_info_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - NfcProtocol protocol = nfc->dev->dev_data.protocol; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == GuiButtonTypeRight) { - if(protocol == NfcDeviceProtocolMifareDesfire) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfDesfireData); - consumed = true; - } else if(protocol == NfcDeviceProtocolMifareUl) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightData); - consumed = true; - } else if(protocol == NfcDeviceProtocolMifareClassic) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicData); - } else if(protocol == NfcDeviceProtocolNfcV) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcVMenu); - consumed = true; - } - } - } - - return consumed; -} - -void nfc_scene_nfc_data_info_on_exit(void* context) { - Nfc* nfc = context; - - widget_reset(nfc->widget); -} diff --git a/applications/main/nfc/scenes/nfc_scene_nfca_menu.c b/applications/main/nfc/scenes/nfc_scene_nfca_menu.c deleted file mode 100644 index 9779470a387..00000000000 --- a/applications/main/nfc/scenes/nfc_scene_nfca_menu.c +++ /dev/null @@ -1,68 +0,0 @@ -#include "../nfc_i.h" -#include - -enum SubmenuIndex { - SubmenuIndexSaveUid, - SubmenuIndexEmulateUid, - SubmenuIndexInfo, -}; - -void nfc_scene_nfca_menu_submenu_callback(void* context, uint32_t index) { - Nfc* nfc = context; - - view_dispatcher_send_custom_event(nfc->view_dispatcher, index); -} - -void nfc_scene_nfca_menu_on_enter(void* context) { - Nfc* nfc = context; - Submenu* submenu = nfc->submenu; - - submenu_add_item( - submenu, "Save UID", SubmenuIndexSaveUid, nfc_scene_nfca_menu_submenu_callback, nfc); - submenu_add_item( - submenu, "Emulate UID", SubmenuIndexEmulateUid, nfc_scene_nfca_menu_submenu_callback, nfc); - submenu_add_item(submenu, "Info", SubmenuIndexInfo, nfc_scene_nfca_menu_submenu_callback, nfc); - - submenu_set_selected_item( - nfc->submenu, scene_manager_get_scene_state(nfc->scene_manager, NfcSceneNfcaMenu)); - - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); -} - -bool nfc_scene_nfca_menu_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SubmenuIndexSaveUid) { - nfc->dev->format = NfcDeviceSaveFormatUid; - // Clear device name - nfc_device_set_name(nfc->dev, ""); - scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveName); - consumed = true; - } else if(event.event == SubmenuIndexEmulateUid) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateUid); - if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSetType)) { - dolphin_deed(DolphinDeedNfcAddEmulate); - } else { - dolphin_deed(DolphinDeedNfcEmulate); - } - consumed = true; - } else if(event.event == SubmenuIndexInfo) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcDataInfo); - consumed = true; - } - scene_manager_set_scene_state(nfc->scene_manager, NfcSceneNfcaMenu, event.event); - } else if(event.type == SceneManagerEventTypeBack) { - consumed = scene_manager_previous_scene(nfc->scene_manager); - } - - return consumed; -} - -void nfc_scene_nfca_menu_on_exit(void* context) { - Nfc* nfc = context; - - // Clear view - submenu_reset(nfc->submenu); -} diff --git a/applications/main/nfc/scenes/nfc_scene_nfca_read_success.c b/applications/main/nfc/scenes/nfc_scene_nfca_read_success.c deleted file mode 100644 index a38f31a9813..00000000000 --- a/applications/main/nfc/scenes/nfc_scene_nfca_read_success.c +++ /dev/null @@ -1,73 +0,0 @@ -#include "../nfc_i.h" - -void nfc_scene_nfca_read_success_widget_callback( - GuiButtonType result, - InputType type, - void* context) { - furi_assert(context); - Nfc* nfc = context; - - if(type == InputTypeShort) { - view_dispatcher_send_custom_event(nfc->view_dispatcher, result); - } -} - -void nfc_scene_nfca_read_success_on_enter(void* context) { - Nfc* nfc = context; - - // Setup view - FuriHalNfcDevData* data = &nfc->dev->dev_data.nfc_data; - Widget* widget = nfc->widget; - - FuriString* temp_str; - temp_str = furi_string_alloc_set("\e#Unknown ISO tag\n"); - - notification_message_block(nfc->notifications, &sequence_set_green_255); - - char iso_type = FURI_BIT(data->sak, 5) ? '4' : '3'; - furi_string_cat_printf(temp_str, "ISO 14443-%c (NFC-A)\n", iso_type); - furi_string_cat_printf(temp_str, "UID:"); - for(size_t i = 0; i < data->uid_len; i++) { - furi_string_cat_printf(temp_str, " %02X", data->uid[i]); - } - furi_string_cat_printf(temp_str, "\nATQA: %02X %02X ", data->atqa[1], data->atqa[0]); - furi_string_cat_printf(temp_str, " SAK: %02X", data->sak); - - widget_add_text_scroll_element(widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); - furi_string_free(temp_str); - - widget_add_button_element( - widget, GuiButtonTypeLeft, "Retry", nfc_scene_nfca_read_success_widget_callback, nfc); - widget_add_button_element( - widget, GuiButtonTypeRight, "More", nfc_scene_nfca_read_success_widget_callback, nfc); - - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); -} - -bool nfc_scene_nfca_read_success_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == GuiButtonTypeLeft) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneRetryConfirm); - consumed = true; - } else if(event.event == GuiButtonTypeRight) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcaMenu); - consumed = true; - } - } else if(event.type == SceneManagerEventTypeBack) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneExitConfirm); - consumed = true; - } - return consumed; -} - -void nfc_scene_nfca_read_success_on_exit(void* context) { - Nfc* nfc = context; - - notification_message_block(nfc->notifications, &sequence_reset_green); - - // Clear view - widget_reset(nfc->widget); -} diff --git a/applications/main/nfc/scenes/nfc_scene_nfcv_emulate.c b/applications/main/nfc/scenes/nfc_scene_nfcv_emulate.c deleted file mode 100644 index d812988bdfd..00000000000 --- a/applications/main/nfc/scenes/nfc_scene_nfcv_emulate.c +++ /dev/null @@ -1,169 +0,0 @@ -#include "../nfc_i.h" - -#define NFC_SCENE_EMULATE_NFCV_LOG_SIZE_MAX (200) - -enum { - NfcSceneNfcVEmulateStateWidget, - NfcSceneNfcVEmulateStateTextBox, -}; - -bool nfc_scene_nfcv_emulate_worker_callback(NfcWorkerEvent event, void* context) { - furi_assert(context); - Nfc* nfc = context; - - switch(event) { - case NfcWorkerEventNfcVCommandExecuted: - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventUpdateLog); - } - break; - case NfcWorkerEventNfcVContentChanged: - view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventSaveShadow); - break; - default: - break; - } - return true; -} - -void nfc_scene_nfcv_emulate_widget_callback(GuiButtonType result, InputType type, void* context) { - furi_assert(context); - Nfc* nfc = context; - if(type == InputTypeShort) { - view_dispatcher_send_custom_event(nfc->view_dispatcher, result); - } -} - -void nfc_scene_nfcv_emulate_textbox_callback(void* context) { - furi_assert(context); - Nfc* nfc = context; - view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit); -} - -static void nfc_scene_nfcv_emulate_widget_config(Nfc* nfc, bool data_received) { - FuriHalNfcDevData* data = &nfc->dev->dev_data.nfc_data; - Widget* widget = nfc->widget; - widget_reset(widget); - FuriString* info_str; - info_str = furi_string_alloc(); - - widget_add_icon_element(widget, 0, 3, &I_NFC_dolphin_emulation_47x61); - widget_add_string_multiline_element( - widget, 87, 13, AlignCenter, AlignTop, FontPrimary, "Emulating\nNFC V"); - if(strcmp(nfc->dev->dev_name, "") != 0) { - furi_string_printf(info_str, "%s", nfc->dev->dev_name); - } else { - for(uint8_t i = 0; i < data->uid_len; i++) { - furi_string_cat_printf(info_str, "%02X ", data->uid[i]); - } - } - furi_string_trim(info_str); - widget_add_text_box_element( - widget, 52, 40, 70, 21, AlignCenter, AlignTop, furi_string_get_cstr(info_str), true); - furi_string_free(info_str); - if(data_received) { - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - widget_add_button_element( - widget, GuiButtonTypeCenter, "Log", nfc_scene_nfcv_emulate_widget_callback, nfc); - } - } -} - -void nfc_scene_nfcv_emulate_on_enter(void* context) { - Nfc* nfc = context; - - // Setup Widget - nfc_scene_nfcv_emulate_widget_config(nfc, false); - // Setup TextBox - TextBox* text_box = nfc->text_box; - text_box_set_font(text_box, TextBoxFontHex); - text_box_set_focus(text_box, TextBoxFocusEnd); - text_box_set_text(text_box, ""); - furi_string_reset(nfc->text_box_store); - - // Set Widget state and view - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneNfcVEmulate, NfcSceneNfcVEmulateStateWidget); - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); - // Start worker - memset(&nfc->dev->dev_data.reader_data, 0, sizeof(NfcReaderRequestData)); - nfc_worker_start( - nfc->worker, - NfcWorkerStateNfcVEmulate, - &nfc->dev->dev_data, - nfc_scene_nfcv_emulate_worker_callback, - nfc); - - nfc_blink_emulate_start(nfc); -} - -bool nfc_scene_nfcv_emulate_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - NfcVData* nfcv_data = &nfc->dev->dev_data.nfcv_data; - uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneNfcVEmulate); - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == NfcCustomEventUpdateLog) { - // Add data button to widget if data is received for the first time - if(strlen(nfcv_data->last_command) > 0) { - if(!furi_string_size(nfc->text_box_store)) { - nfc_scene_nfcv_emulate_widget_config(nfc, true); - } - /* use the last n bytes from the log so there's enough space for the new log entry */ - size_t maxSize = - NFC_SCENE_EMULATE_NFCV_LOG_SIZE_MAX - (strlen(nfcv_data->last_command) + 1); - if(furi_string_size(nfc->text_box_store) >= maxSize) { - furi_string_right(nfc->text_box_store, (strlen(nfcv_data->last_command) + 1)); - } - furi_string_cat_printf(nfc->text_box_store, "%s", nfcv_data->last_command); - furi_string_push_back(nfc->text_box_store, '\n'); - text_box_set_text(nfc->text_box, furi_string_get_cstr(nfc->text_box_store)); - - /* clear previously logged command */ - strcpy(nfcv_data->last_command, ""); - } - consumed = true; - } else if(event.event == NfcCustomEventSaveShadow) { - if(furi_string_size(nfc->dev->load_path)) { - nfc_device_save_shadow(nfc->dev, furi_string_get_cstr(nfc->dev->load_path)); - } - consumed = true; - } else if(event.event == GuiButtonTypeCenter && state == NfcSceneNfcVEmulateStateWidget) { - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewTextBox); - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneNfcVEmulate, NfcSceneNfcVEmulateStateTextBox); - } - consumed = true; - } else if(event.event == NfcCustomEventViewExit && state == NfcSceneNfcVEmulateStateTextBox) { - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneNfcVEmulate, NfcSceneNfcVEmulateStateWidget); - consumed = true; - } - } else if(event.type == SceneManagerEventTypeBack) { - if(state == NfcSceneNfcVEmulateStateTextBox) { - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneNfcVEmulate, NfcSceneNfcVEmulateStateWidget); - consumed = true; - } - } - - return consumed; -} - -void nfc_scene_nfcv_emulate_on_exit(void* context) { - Nfc* nfc = context; - - // Stop worker - nfc_worker_stop(nfc->worker); - - // Clear view - widget_reset(nfc->widget); - text_box_reset(nfc->text_box); - furi_string_reset(nfc->text_box_store); - - nfc_blink_stop(nfc); -} diff --git a/applications/main/nfc/scenes/nfc_scene_nfcv_key_input.c b/applications/main/nfc/scenes/nfc_scene_nfcv_key_input.c deleted file mode 100644 index 13d903c4b77..00000000000 --- a/applications/main/nfc/scenes/nfc_scene_nfcv_key_input.c +++ /dev/null @@ -1,48 +0,0 @@ -#include "../nfc_i.h" -#include - -void nfc_scene_nfcv_key_input_byte_input_callback(void* context) { - Nfc* nfc = context; - NfcVSlixData* data = &nfc->dev->dev_data.nfcv_data.sub_data.slix; - - memcpy(data->key_privacy, nfc->byte_input_store, 4); - view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventByteInputDone); -} - -void nfc_scene_nfcv_key_input_on_enter(void* context) { - Nfc* nfc = context; - - // Setup view - ByteInput* byte_input = nfc->byte_input; - byte_input_set_header_text(byte_input, "Enter The Password In Hex"); - byte_input_set_result_callback( - byte_input, - nfc_scene_nfcv_key_input_byte_input_callback, - NULL, - nfc, - nfc->byte_input_store, - 4); - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewByteInput); -} - -bool nfc_scene_nfcv_key_input_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == NfcCustomEventByteInputDone) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcVUnlock); - dolphin_deed(DolphinDeedNfcRead); - consumed = true; - } - } - return consumed; -} - -void nfc_scene_nfcv_key_input_on_exit(void* context) { - Nfc* nfc = context; - - // Clear view - byte_input_set_result_callback(nfc->byte_input, NULL, NULL, NULL, NULL, 0); - byte_input_set_header_text(nfc->byte_input, ""); -} diff --git a/applications/main/nfc/scenes/nfc_scene_nfcv_menu.c b/applications/main/nfc/scenes/nfc_scene_nfcv_menu.c deleted file mode 100644 index 60eb354e858..00000000000 --- a/applications/main/nfc/scenes/nfc_scene_nfcv_menu.c +++ /dev/null @@ -1,68 +0,0 @@ -#include "../nfc_i.h" -#include - -enum SubmenuIndex { - SubmenuIndexSave, - SubmenuIndexEmulate, - SubmenuIndexInfo, -}; - -void nfc_scene_nfcv_menu_submenu_callback(void* context, uint32_t index) { - Nfc* nfc = context; - - view_dispatcher_send_custom_event(nfc->view_dispatcher, index); -} - -void nfc_scene_nfcv_menu_on_enter(void* context) { - Nfc* nfc = context; - Submenu* submenu = nfc->submenu; - - submenu_add_item( - submenu, "Emulate", SubmenuIndexEmulate, nfc_scene_nfcv_menu_submenu_callback, nfc); - submenu_add_item(submenu, "Save", SubmenuIndexSave, nfc_scene_nfcv_menu_submenu_callback, nfc); - submenu_add_item(submenu, "Info", SubmenuIndexInfo, nfc_scene_nfcv_menu_submenu_callback, nfc); - - submenu_set_selected_item( - nfc->submenu, scene_manager_get_scene_state(nfc->scene_manager, NfcSceneNfcVMenu)); - - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); -} - -bool nfc_scene_nfcv_menu_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SubmenuIndexSave) { - nfc->dev->format = NfcDeviceSaveFormatNfcV; - // 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_next_scene(nfc->scene_manager, NfcSceneNfcVEmulate); - if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSetType)) { - dolphin_deed(DolphinDeedNfcAddEmulate); - } else { - dolphin_deed(DolphinDeedNfcEmulate); - } - consumed = true; - } else if(event.event == SubmenuIndexInfo) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcDataInfo); - consumed = true; - } - scene_manager_set_scene_state(nfc->scene_manager, NfcSceneNfcVMenu, event.event); - - } else if(event.type == SceneManagerEventTypeBack) { - consumed = scene_manager_previous_scene(nfc->scene_manager); - } - - return consumed; -} - -void nfc_scene_nfcv_menu_on_exit(void* context) { - Nfc* nfc = context; - - // Clear view - submenu_reset(nfc->submenu); -} diff --git a/applications/main/nfc/scenes/nfc_scene_nfcv_read_success.c b/applications/main/nfc/scenes/nfc_scene_nfcv_read_success.c deleted file mode 100644 index 04e60611d00..00000000000 --- a/applications/main/nfc/scenes/nfc_scene_nfcv_read_success.c +++ /dev/null @@ -1,92 +0,0 @@ -#include "../nfc_i.h" - -void nfc_scene_nfcv_read_success_widget_callback( - GuiButtonType result, - InputType type, - void* context) { - furi_assert(context); - Nfc* nfc = context; - - if(type == InputTypeShort) { - view_dispatcher_send_custom_event(nfc->view_dispatcher, result); - } -} - -void nfc_scene_nfcv_read_success_on_enter(void* context) { - Nfc* nfc = context; - NfcDeviceData* dev_data = &nfc->dev->dev_data; - FuriHalNfcDevData* nfc_data = &nfc->dev->dev_data.nfc_data; - // Setup view - Widget* widget = nfc->widget; - widget_add_button_element( - widget, GuiButtonTypeLeft, "Retry", nfc_scene_nfcv_read_success_widget_callback, nfc); - widget_add_button_element( - widget, GuiButtonTypeRight, "More", nfc_scene_nfcv_read_success_widget_callback, nfc); - - FuriString* temp_str = furi_string_alloc(); - - switch(dev_data->nfcv_data.sub_type) { - case NfcVTypePlain: - furi_string_cat_printf(temp_str, "\e#ISO15693\n"); - break; - case NfcVTypeSlix: - furi_string_cat_printf(temp_str, "\e#ISO15693 SLIX\n"); - break; - case NfcVTypeSlixS: - furi_string_cat_printf(temp_str, "\e#ISO15693 SLIX-S\n"); - break; - case NfcVTypeSlixL: - furi_string_cat_printf(temp_str, "\e#ISO15693 SLIX-L\n"); - break; - case NfcVTypeSlix2: - furi_string_cat_printf(temp_str, "\e#ISO15693 SLIX2\n"); - break; - default: - furi_string_cat_printf(temp_str, "\e#ISO15693 (unknown)\n"); - break; - } - furi_string_cat_printf(temp_str, "UID:\n"); - for(size_t i = 0; i < nfc_data->uid_len; i++) { - furi_string_cat_printf(temp_str, " %02X", nfc_data->uid[i]); - } - furi_string_cat_printf(temp_str, "\n"); - furi_string_cat_printf(temp_str, "(see More->Info for details)\n"); - - widget_add_text_scroll_element(widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); - furi_string_free(temp_str); - - notification_message_block(nfc->notifications, &sequence_set_green_255); - - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); -} - -bool nfc_scene_nfcv_read_success_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == GuiButtonTypeLeft) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneRetryConfirm); - consumed = true; - } else if(event.event == GuiButtonTypeRight) { - // Clear device name - nfc_device_set_name(nfc->dev, ""); - scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcVMenu); - consumed = true; - } - } else if(event.type == SceneManagerEventTypeBack) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneExitConfirm); - consumed = true; - } - - return consumed; -} - -void nfc_scene_nfcv_read_success_on_exit(void* context) { - Nfc* nfc = context; - - notification_message_block(nfc->notifications, &sequence_reset_green); - - // Clear view - widget_reset(nfc->widget); -} diff --git a/applications/main/nfc/scenes/nfc_scene_nfcv_sniff.c b/applications/main/nfc/scenes/nfc_scene_nfcv_sniff.c deleted file mode 100644 index 2c0f17981b4..00000000000 --- a/applications/main/nfc/scenes/nfc_scene_nfcv_sniff.c +++ /dev/null @@ -1,155 +0,0 @@ -#include "../nfc_i.h" - -#define NFC_SCENE_EMULATE_NFCV_LOG_SIZE_MAX (800) - -enum { - NfcSceneNfcVSniffStateWidget, - NfcSceneNfcVSniffStateTextBox, -}; - -bool nfc_scene_nfcv_sniff_worker_callback(NfcWorkerEvent event, void* context) { - UNUSED(event); - furi_assert(context); - Nfc* nfc = context; - - switch(event) { - case NfcWorkerEventNfcVCommandExecuted: - view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventUpdateLog); - break; - case NfcWorkerEventNfcVContentChanged: - view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventSaveShadow); - break; - default: - break; - } - return true; -} - -void nfc_scene_nfcv_sniff_widget_callback(GuiButtonType result, InputType type, void* context) { - furi_assert(context); - Nfc* nfc = context; - if(type == InputTypeShort) { - view_dispatcher_send_custom_event(nfc->view_dispatcher, result); - } -} - -void nfc_scene_nfcv_sniff_textbox_callback(void* context) { - furi_assert(context); - Nfc* nfc = context; - view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit); -} - -static void nfc_scene_nfcv_sniff_widget_config(Nfc* nfc, bool data_received) { - Widget* widget = nfc->widget; - widget_reset(widget); - FuriString* info_str; - info_str = furi_string_alloc(); - - widget_add_icon_element(widget, 0, 3, &I_RFIDDolphinSend_97x61); - widget_add_string_element(widget, 89, 32, AlignCenter, AlignTop, FontPrimary, "Listen NfcV"); - furi_string_trim(info_str); - widget_add_text_box_element( - widget, 56, 43, 70, 21, AlignCenter, AlignTop, furi_string_get_cstr(info_str), true); - furi_string_free(info_str); - if(data_received) { - widget_add_button_element( - widget, GuiButtonTypeCenter, "Log", nfc_scene_nfcv_sniff_widget_callback, nfc); - } -} - -void nfc_scene_nfcv_sniff_on_enter(void* context) { - Nfc* nfc = context; - - // Setup Widget - nfc_scene_nfcv_sniff_widget_config(nfc, false); - // Setup TextBox - TextBox* text_box = nfc->text_box; - text_box_set_font(text_box, TextBoxFontHex); - text_box_set_focus(text_box, TextBoxFocusEnd); - text_box_set_text(text_box, ""); - furi_string_reset(nfc->text_box_store); - - // Set Widget state and view - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneNfcVSniff, NfcSceneNfcVSniffStateWidget); - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); - // Start worker - memset(&nfc->dev->dev_data.reader_data, 0, sizeof(NfcReaderRequestData)); - nfc_worker_start( - nfc->worker, - NfcWorkerStateNfcVSniff, - &nfc->dev->dev_data, - nfc_scene_nfcv_sniff_worker_callback, - nfc); - - nfc_blink_emulate_start(nfc); -} - -bool nfc_scene_nfcv_sniff_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - NfcVData* nfcv_data = &nfc->dev->dev_data.nfcv_data; - uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneNfcVSniff); - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == NfcCustomEventUpdateLog) { - // Add data button to widget if data is received for the first time - if(strlen(nfcv_data->last_command) > 0) { - if(!furi_string_size(nfc->text_box_store)) { - nfc_scene_nfcv_sniff_widget_config(nfc, true); - } - /* use the last n bytes from the log so there's enough space for the new log entry */ - size_t maxSize = - NFC_SCENE_EMULATE_NFCV_LOG_SIZE_MAX - (strlen(nfcv_data->last_command) + 1); - if(furi_string_size(nfc->text_box_store) >= maxSize) { - furi_string_right(nfc->text_box_store, (strlen(nfcv_data->last_command) + 1)); - } - furi_string_cat_printf(nfc->text_box_store, "%s", nfcv_data->last_command); - furi_string_push_back(nfc->text_box_store, '\n'); - text_box_set_text(nfc->text_box, furi_string_get_cstr(nfc->text_box_store)); - - /* clear previously logged command */ - strcpy(nfcv_data->last_command, ""); - } - consumed = true; - } else if(event.event == NfcCustomEventSaveShadow) { - if(furi_string_size(nfc->dev->load_path)) { - nfc_device_save_shadow(nfc->dev, furi_string_get_cstr(nfc->dev->load_path)); - } - consumed = true; - } else if(event.event == GuiButtonTypeCenter && state == NfcSceneNfcVSniffStateWidget) { - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewTextBox); - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneNfcVSniff, NfcSceneNfcVSniffStateTextBox); - consumed = true; - } else if(event.event == NfcCustomEventViewExit && state == NfcSceneNfcVSniffStateTextBox) { - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneNfcVSniff, NfcSceneNfcVSniffStateWidget); - consumed = true; - } - } else if(event.type == SceneManagerEventTypeBack) { - if(state == NfcSceneNfcVSniffStateTextBox) { - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneNfcVSniff, NfcSceneNfcVSniffStateWidget); - consumed = true; - } - } - - return consumed; -} - -void nfc_scene_nfcv_sniff_on_exit(void* context) { - Nfc* nfc = context; - - // Stop worker - nfc_worker_stop(nfc->worker); - - // Clear view - widget_reset(nfc->widget); - text_box_reset(nfc->text_box); - furi_string_reset(nfc->text_box_store); - - nfc_blink_stop(nfc); -} diff --git a/applications/main/nfc/scenes/nfc_scene_nfcv_unlock.c b/applications/main/nfc/scenes/nfc_scene_nfcv_unlock.c deleted file mode 100644 index 38d7ad563d8..00000000000 --- a/applications/main/nfc/scenes/nfc_scene_nfcv_unlock.c +++ /dev/null @@ -1,154 +0,0 @@ -#include "../nfc_i.h" -#include - -typedef enum { - NfcSceneNfcVUnlockStateIdle, - NfcSceneNfcVUnlockStateDetecting, - NfcSceneNfcVUnlockStateUnlocked, - NfcSceneNfcVUnlockStateAlreadyUnlocked, - NfcSceneNfcVUnlockStateNotSupportedCard, -} NfcSceneNfcVUnlockState; - -static bool nfc_scene_nfcv_unlock_worker_callback(NfcWorkerEvent event, void* context) { - Nfc* nfc = context; - NfcVSlixData* data = &nfc->dev->dev_data.nfcv_data.sub_data.slix; - - if(event == NfcWorkerEventNfcVPassKey) { - memcpy(data->key_privacy, nfc->byte_input_store, 4); - } else { - view_dispatcher_send_custom_event(nfc->view_dispatcher, event); - } - return true; -} - -void nfc_scene_nfcv_unlock_popup_callback(void* context) { - Nfc* nfc = context; - view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit); -} - -void nfc_scene_nfcv_unlock_set_state(Nfc* nfc, NfcSceneNfcVUnlockState state) { - FuriHalNfcDevData* nfc_data = &(nfc->dev->dev_data.nfc_data); - NfcVData* nfcv_data = &(nfc->dev->dev_data.nfcv_data); - - uint32_t curr_state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneNfcVUnlock); - if(curr_state != state) { - Popup* popup = nfc->popup; - if(state == NfcSceneNfcVUnlockStateDetecting) { - popup_reset(popup); - popup_set_text( - popup, "Put figurine on\nFlipper's back", 97, 24, AlignCenter, AlignTop); - popup_set_icon(popup, 0, 8, &I_NFC_manual_60x50); - } else if(state == NfcSceneNfcVUnlockStateUnlocked) { - popup_reset(popup); - - if(nfc_worker_get_state(nfc->worker) == NfcWorkerStateNfcVUnlockAndSave) { - snprintf( - nfc->dev->dev_name, - sizeof(nfc->dev->dev_name), - "SLIX_%02X%02X%02X%02X%02X%02X%02X%02X", - nfc_data->uid[0], - nfc_data->uid[1], - nfc_data->uid[2], - nfc_data->uid[3], - nfc_data->uid[4], - nfc_data->uid[5], - nfc_data->uid[6], - nfc_data->uid[7]); - - nfc->dev->format = NfcDeviceSaveFormatNfcV; - - if(nfc_save_file(nfc)) { - popup_set_header(popup, "Successfully\nsaved", 94, 3, AlignCenter, AlignTop); - } else { - popup_set_header( - popup, "Unlocked but\nsave failed!", 94, 3, AlignCenter, AlignTop); - } - } else { - popup_set_header(popup, "Successfully\nunlocked", 94, 3, AlignCenter, AlignTop); - } - - notification_message(nfc->notifications, &sequence_single_vibro); - //notification_message(nfc->notifications, &sequence_success); - - popup_set_icon(popup, 0, 6, &I_RFIDDolphinSuccess_108x57); - popup_set_context(popup, nfc); - popup_set_callback(popup, nfc_scene_nfcv_unlock_popup_callback); - popup_set_timeout(popup, 1500); - - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); - dolphin_deed(DolphinDeedNfcReadSuccess); - - } else if(state == NfcSceneNfcVUnlockStateAlreadyUnlocked) { - popup_reset(popup); - - popup_set_header(popup, "Already\nUnlocked!", 94, 3, AlignCenter, AlignTop); - popup_set_icon(popup, 0, 6, &I_RFIDDolphinSuccess_108x57); - popup_set_context(popup, nfc); - popup_set_callback(popup, nfc_scene_nfcv_unlock_popup_callback); - popup_set_timeout(popup, 1500); - - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); - } else if(state == NfcSceneNfcVUnlockStateNotSupportedCard) { - popup_reset(popup); - popup_set_header(popup, "Wrong Type Of Card!", 64, 3, AlignCenter, AlignTop); - popup_set_text(popup, nfcv_data->error, 4, 22, AlignLeft, AlignTop); - popup_set_icon(popup, 73, 20, &I_DolphinCommon_56x48); - } - scene_manager_set_scene_state(nfc->scene_manager, NfcSceneNfcVUnlock, state); - } -} - -void nfc_scene_nfcv_unlock_on_enter(void* context) { - Nfc* nfc = context; - - nfc_device_clear(nfc->dev); - // Setup view - nfc_scene_nfcv_unlock_set_state(nfc, NfcSceneNfcVUnlockStateDetecting); - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); - - // Start worker - nfc_worker_start( - nfc->worker, - NfcWorkerStateNfcVUnlockAndSave, - &nfc->dev->dev_data, - nfc_scene_nfcv_unlock_worker_callback, - nfc); - - nfc_blink_read_start(nfc); -} - -bool nfc_scene_nfcv_unlock_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == NfcWorkerEventCardDetected) { - nfc_scene_nfcv_unlock_set_state(nfc, NfcSceneNfcVUnlockStateUnlocked); - consumed = true; - } else if(event.event == NfcWorkerEventAborted) { - nfc_scene_nfcv_unlock_set_state(nfc, NfcSceneNfcVUnlockStateAlreadyUnlocked); - consumed = true; - } else if(event.event == NfcWorkerEventNoCardDetected) { - nfc_scene_nfcv_unlock_set_state(nfc, NfcSceneNfcVUnlockStateDetecting); - consumed = true; - } else if(event.event == NfcWorkerEventWrongCardDetected) { - nfc_scene_nfcv_unlock_set_state(nfc, NfcSceneNfcVUnlockStateNotSupportedCard); - } - } else if(event.type == SceneManagerEventTypeBack) { - consumed = scene_manager_search_and_switch_to_previous_scene( - nfc->scene_manager, NfcSceneNfcVUnlockMenu); - } - return consumed; -} - -void nfc_scene_nfcv_unlock_on_exit(void* context) { - Nfc* nfc = context; - - // Stop worker - nfc_worker_stop(nfc->worker); - // Clear view - popup_reset(nfc->popup); - nfc_blink_stop(nfc); - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneNfcVUnlock, NfcSceneNfcVUnlockStateIdle); -} diff --git a/applications/main/nfc/scenes/nfc_scene_nfcv_unlock_menu.c b/applications/main/nfc/scenes/nfc_scene_nfcv_unlock_menu.c deleted file mode 100644 index 2f736725674..00000000000 --- a/applications/main/nfc/scenes/nfc_scene_nfcv_unlock_menu.c +++ /dev/null @@ -1,60 +0,0 @@ -#include "../nfc_i.h" -#include - -enum SubmenuIndex { - SubmenuIndexNfcVUnlockMenuManual, - SubmenuIndexNfcVUnlockMenuTonieBox, -}; - -void nfc_scene_nfcv_unlock_menu_submenu_callback(void* context, uint32_t index) { - Nfc* nfc = context; - - view_dispatcher_send_custom_event(nfc->view_dispatcher, index); -} - -void nfc_scene_nfcv_unlock_menu_on_enter(void* context) { - Nfc* nfc = context; - Submenu* submenu = nfc->submenu; - - uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneNfcVUnlockMenu); - submenu_add_item( - submenu, - "Enter PWD Manually", - SubmenuIndexNfcVUnlockMenuManual, - nfc_scene_nfcv_unlock_menu_submenu_callback, - nfc); - submenu_add_item( - submenu, - "Auth As TonieBox", - SubmenuIndexNfcVUnlockMenuTonieBox, - nfc_scene_nfcv_unlock_menu_submenu_callback, - nfc); - submenu_set_selected_item(submenu, state); - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); -} - -bool nfc_scene_nfcv_unlock_menu_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SubmenuIndexNfcVUnlockMenuManual) { - nfc->dev->dev_data.nfcv_data.auth_method = NfcVAuthMethodManual; - scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcVKeyInput); - consumed = true; - } else if(event.event == SubmenuIndexNfcVUnlockMenuTonieBox) { - nfc->dev->dev_data.nfcv_data.auth_method = NfcVAuthMethodTonieBox; - scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcVUnlock); - dolphin_deed(DolphinDeedNfcRead); - consumed = true; - } - scene_manager_set_scene_state(nfc->scene_manager, NfcSceneNfcVUnlockMenu, event.event); - } - return consumed; -} - -void nfc_scene_nfcv_unlock_menu_on_exit(void* context) { - Nfc* nfc = context; - - submenu_reset(nfc->submenu); -} diff --git a/applications/main/nfc/scenes/nfc_scene_read.c b/applications/main/nfc/scenes/nfc_scene_read.c index 1690a955757..e9603afd1ad 100644 --- a/applications/main/nfc/scenes/nfc_scene_read.c +++ b/applications/main/nfc/scenes/nfc_scene_read.c @@ -1,123 +1,13 @@ -#include "../nfc_i.h" -#include - -typedef enum { - NfcSceneReadStateIdle, - NfcSceneReadStateDetecting, - NfcSceneReadStateReading, -} NfcSceneReadState; - -bool nfc_scene_read_worker_callback(NfcWorkerEvent event, void* context) { - Nfc* nfc = context; - bool consumed = false; - if(event == NfcWorkerEventReadMfClassicLoadKeyCache) { - consumed = nfc_device_load_key_cache(nfc->dev); - } else { - view_dispatcher_send_custom_event(nfc->view_dispatcher, event); - consumed = true; - } - return consumed; -} - -void nfc_scene_read_set_state(Nfc* nfc, NfcSceneReadState state) { - uint32_t curr_state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneRead); - if(curr_state != state) { - if(state == NfcSceneReadStateDetecting) { - popup_reset(nfc->popup); - popup_set_text( - nfc->popup, "Apply card to\nFlipper's back", 97, 24, AlignCenter, AlignTop); - popup_set_icon(nfc->popup, 0, 8, &I_NFC_manual_60x50); - } else if(state == NfcSceneReadStateReading) { - popup_reset(nfc->popup); - popup_set_header( - nfc->popup, "Reading card\nDon't move...", 85, 24, AlignCenter, AlignTop); - popup_set_icon(nfc->popup, 12, 23, &A_Loading_24); - } - scene_manager_set_scene_state(nfc->scene_manager, NfcSceneRead, state); - } -} +#include "../helpers/protocol_support/nfc_protocol_support.h" void nfc_scene_read_on_enter(void* context) { - Nfc* nfc = context; - - nfc_device_clear(nfc->dev); - // Setup view - nfc_scene_read_set_state(nfc, NfcSceneReadStateDetecting); - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); - // Start worker - nfc_worker_start( - nfc->worker, NfcWorkerStateRead, &nfc->dev->dev_data, nfc_scene_read_worker_callback, nfc); - - nfc_blink_read_start(nfc); + nfc_protocol_support_on_enter(NfcProtocolSupportSceneRead, context); } bool nfc_scene_read_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if((event.event == NfcWorkerEventReadUidNfcB) || - (event.event == NfcWorkerEventReadUidNfcF) || - (event.event == NfcWorkerEventReadUidNfcV)) { - notification_message(nfc->notifications, &sequence_success); - scene_manager_next_scene(nfc->scene_manager, NfcSceneReadCardSuccess); - dolphin_deed(DolphinDeedNfcReadSuccess); - consumed = true; - } else if(event.event == NfcWorkerEventReadUidNfcA) { - notification_message(nfc->notifications, &sequence_success); - scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcaReadSuccess); - dolphin_deed(DolphinDeedNfcReadSuccess); - consumed = true; - } else if(event.event == NfcWorkerEventReadNfcV) { - notification_message(nfc->notifications, &sequence_success); - scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcVReadSuccess); - dolphin_deed(DolphinDeedNfcReadSuccess); - consumed = true; - } else if(event.event == NfcWorkerEventReadMfUltralight) { - notification_message(nfc->notifications, &sequence_success); - // Set unlock password input to 0xFFFFFFFF only on fresh read - memset(nfc->byte_input_store, 0xFF, sizeof(nfc->byte_input_store)); - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightReadSuccess); - dolphin_deed(DolphinDeedNfcReadSuccess); - consumed = true; - } else if(event.event == NfcWorkerEventReadMfClassicDone) { - notification_message(nfc->notifications, &sequence_success); - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicReadSuccess); - dolphin_deed(DolphinDeedNfcReadSuccess); - consumed = true; - } else if(event.event == NfcWorkerEventReadMfDesfire) { - notification_message(nfc->notifications, &sequence_success); - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfDesfireReadSuccess); - dolphin_deed(DolphinDeedNfcReadSuccess); - consumed = true; - } else if(event.event == NfcWorkerEventReadMfClassicDictAttackRequired) { - if(mf_classic_dict_check_presence(MfClassicDictTypeSystem)) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicDictAttack); - } else { - scene_manager_next_scene(nfc->scene_manager, NfcSceneDictNotFound); - } - consumed = true; - } else if(event.event == NfcWorkerEventCardDetected) { - nfc_scene_read_set_state(nfc, NfcSceneReadStateReading); - nfc_blink_detect_start(nfc); - consumed = true; - } else if(event.event == NfcWorkerEventNoCardDetected) { - nfc_scene_read_set_state(nfc, NfcSceneReadStateDetecting); - nfc_blink_read_start(nfc); - consumed = true; - } - } - return consumed; + return nfc_protocol_support_on_event(NfcProtocolSupportSceneRead, context, event); } void nfc_scene_read_on_exit(void* context) { - Nfc* nfc = context; - - // Stop worker - nfc_worker_stop(nfc->worker); - // Clear view - popup_reset(nfc->popup); - scene_manager_set_scene_state(nfc->scene_manager, NfcSceneRead, NfcSceneReadStateIdle); - - nfc_blink_stop(nfc); + nfc_protocol_support_on_exit(NfcProtocolSupportSceneRead, context); } diff --git a/applications/main/nfc/scenes/nfc_scene_read_card_success.c b/applications/main/nfc/scenes/nfc_scene_read_card_success.c deleted file mode 100644 index ee80ee76883..00000000000 --- a/applications/main/nfc/scenes/nfc_scene_read_card_success.c +++ /dev/null @@ -1,61 +0,0 @@ -#include "../nfc_i.h" - -void nfc_scene_read_card_success_widget_callback( - GuiButtonType result, - InputType type, - void* context) { - furi_assert(context); - Nfc* nfc = context; - - if(type == InputTypeShort) { - view_dispatcher_send_custom_event(nfc->view_dispatcher, result); - } -} - -void nfc_scene_read_card_success_on_enter(void* context) { - Nfc* nfc = context; - - FuriString* temp_str; - temp_str = furi_string_alloc(); - - // Setup view - FuriHalNfcDevData* data = &nfc->dev->dev_data.nfc_data; - Widget* widget = nfc->widget; - furi_string_set(temp_str, nfc_get_dev_type(data->type)); - widget_add_string_element( - widget, 64, 12, AlignCenter, AlignBottom, FontPrimary, furi_string_get_cstr(temp_str)); - furi_string_set(temp_str, "UID:"); - for(uint8_t i = 0; i < data->uid_len; i++) { - furi_string_cat_printf(temp_str, " %02X", data->uid[i]); - } - widget_add_string_element( - widget, 64, 32, AlignCenter, AlignCenter, FontSecondary, furi_string_get_cstr(temp_str)); - widget_add_button_element( - widget, GuiButtonTypeLeft, "Retry", nfc_scene_read_card_success_widget_callback, nfc); - - furi_string_free(temp_str); - - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); -} - -bool nfc_scene_read_card_success_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == GuiButtonTypeLeft) { - consumed = scene_manager_previous_scene(nfc->scene_manager); - } - } else if(event.type == SceneManagerEventTypeBack) { - consumed = - scene_manager_search_and_switch_to_previous_scene(nfc->scene_manager, NfcSceneStart); - } - return consumed; -} - -void nfc_scene_read_card_success_on_exit(void* context) { - Nfc* nfc = context; - - // Clear view - widget_reset(nfc->widget); -} diff --git a/applications/main/nfc/scenes/nfc_scene_read_card_type.c b/applications/main/nfc/scenes/nfc_scene_read_card_type.c deleted file mode 100644 index 8023026c3da..00000000000 --- a/applications/main/nfc/scenes/nfc_scene_read_card_type.c +++ /dev/null @@ -1,85 +0,0 @@ -#include "../nfc_i.h" -#include "nfc_worker_i.h" - -enum SubmenuIndex { - SubmenuIndexReadMifareClassic, - SubmenuIndexReadMifareDesfire, - SubmenuIndexReadMfUltralight, - SubmenuIndexReadNFCA, -}; - -void nfc_scene_read_card_type_submenu_callback(void* context, uint32_t index) { - Nfc* nfc = context; - - view_dispatcher_send_custom_event(nfc->view_dispatcher, index); -} - -void nfc_scene_read_card_type_on_enter(void* context) { - Nfc* nfc = context; - Submenu* submenu = nfc->submenu; - - submenu_add_item( - submenu, - "Read Mifare Classic", - SubmenuIndexReadMifareClassic, - nfc_scene_read_card_type_submenu_callback, - nfc); - submenu_add_item( - submenu, - "Read Mifare DESFire", - SubmenuIndexReadMifareDesfire, - nfc_scene_read_card_type_submenu_callback, - nfc); - submenu_add_item( - submenu, - "Read NTAG/Ultralight", - SubmenuIndexReadMfUltralight, - nfc_scene_read_card_type_submenu_callback, - nfc); - submenu_add_item( - submenu, - "Read NFC-A data", - SubmenuIndexReadNFCA, - nfc_scene_read_card_type_submenu_callback, - nfc); - uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneReadCardType); - submenu_set_selected_item(submenu, state); - - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); -} - -bool nfc_scene_read_card_type_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SubmenuIndexReadMifareClassic) { - nfc->dev->dev_data.read_mode = NfcReadModeMfClassic; - scene_manager_next_scene(nfc->scene_manager, NfcSceneRead); - consumed = true; - } - if(event.event == SubmenuIndexReadMifareDesfire) { - nfc->dev->dev_data.read_mode = NfcReadModeMfDesfire; - scene_manager_next_scene(nfc->scene_manager, NfcSceneRead); - consumed = true; - } - if(event.event == SubmenuIndexReadMfUltralight) { - nfc->dev->dev_data.read_mode = NfcReadModeMfUltralight; - scene_manager_next_scene(nfc->scene_manager, NfcSceneRead); - consumed = true; - } - if(event.event == SubmenuIndexReadNFCA) { - nfc->dev->dev_data.read_mode = NfcReadModeNFCA; - scene_manager_next_scene(nfc->scene_manager, NfcSceneRead); - consumed = true; - } - scene_manager_set_scene_state(nfc->scene_manager, NfcSceneReadCardType, event.event); - } - return consumed; -} - -void nfc_scene_read_card_type_on_exit(void* context) { - Nfc* nfc = context; - - submenu_reset(nfc->submenu); -} diff --git a/applications/main/nfc/scenes/nfc_scene_read_menu.c b/applications/main/nfc/scenes/nfc_scene_read_menu.c new file mode 100644 index 00000000000..cba07a485ae --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_read_menu.c @@ -0,0 +1,13 @@ +#include "../helpers/protocol_support/nfc_protocol_support.h" + +void nfc_scene_read_menu_on_enter(void* context) { + nfc_protocol_support_on_enter(NfcProtocolSupportSceneReadMenu, context); +} + +bool nfc_scene_read_menu_on_event(void* context, SceneManagerEvent event) { + return nfc_protocol_support_on_event(NfcProtocolSupportSceneReadMenu, context, event); +} + +void nfc_scene_read_menu_on_exit(void* context) { + nfc_protocol_support_on_exit(NfcProtocolSupportSceneReadMenu, context); +} diff --git a/applications/main/nfc/scenes/nfc_scene_read_success.c b/applications/main/nfc/scenes/nfc_scene_read_success.c new file mode 100644 index 00000000000..5ceada48b5a --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_read_success.c @@ -0,0 +1,13 @@ +#include "../helpers/protocol_support/nfc_protocol_support.h" + +void nfc_scene_read_success_on_enter(void* context) { + nfc_protocol_support_on_enter(NfcProtocolSupportSceneReadSuccess, context); +} + +bool nfc_scene_read_success_on_event(void* context, SceneManagerEvent event) { + return nfc_protocol_support_on_event(NfcProtocolSupportSceneReadSuccess, context, event); +} + +void nfc_scene_read_success_on_exit(void* context) { + nfc_protocol_support_on_exit(NfcProtocolSupportSceneReadSuccess, context); +} diff --git a/applications/main/nfc/scenes/nfc_scene_restore_original.c b/applications/main/nfc/scenes/nfc_scene_restore_original.c index 3ecf5c048e4..612e6041e69 100644 --- a/applications/main/nfc/scenes/nfc_scene_restore_original.c +++ b/applications/main/nfc/scenes/nfc_scene_restore_original.c @@ -1,12 +1,12 @@ -#include "../nfc_i.h" +#include "../nfc_app_i.h" void nfc_scene_restore_original_popup_callback(void* context) { - Nfc* nfc = context; + NfcApp* nfc = context; view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit); } void nfc_scene_restore_original_on_enter(void* context) { - Nfc* nfc = context; + NfcApp* nfc = context; // Setup view Popup* popup = nfc->popup; @@ -20,17 +20,17 @@ void nfc_scene_restore_original_on_enter(void* context) { } bool nfc_scene_restore_original_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; + NfcApp* nfc = context; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { if(event.event == NfcCustomEventViewExit) { - if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSavedMenu)) { + if(nfc_load_file(nfc, nfc->file_path, false)) { consumed = scene_manager_search_and_switch_to_previous_scene( nfc->scene_manager, NfcSceneSavedMenu); } else { consumed = scene_manager_search_and_switch_to_previous_scene( - nfc->scene_manager, NfcSceneStart); + nfc->scene_manager, NfcSceneFileSelect); } } } @@ -38,7 +38,7 @@ bool nfc_scene_restore_original_on_event(void* context, SceneManagerEvent event) } void nfc_scene_restore_original_on_exit(void* context) { - Nfc* nfc = context; + NfcApp* nfc = context; // Clear view popup_reset(nfc->popup); diff --git a/applications/main/nfc/scenes/nfc_scene_restore_original_confirm.c b/applications/main/nfc/scenes/nfc_scene_restore_original_confirm.c index 16b0953f809..6e260da2a27 100644 --- a/applications/main/nfc/scenes/nfc_scene_restore_original_confirm.c +++ b/applications/main/nfc/scenes/nfc_scene_restore_original_confirm.c @@ -1,13 +1,13 @@ -#include "../nfc_i.h" +#include "../nfc_app_i.h" void nfc_scene_restore_original_confirm_dialog_callback(DialogExResult result, void* context) { - Nfc* nfc = context; + NfcApp* nfc = context; view_dispatcher_send_custom_event(nfc->view_dispatcher, result); } void nfc_scene_restore_original_confirm_on_enter(void* context) { - Nfc* nfc = context; + NfcApp* nfc = context; DialogEx* dialog_ex = nfc->dialog_ex; dialog_ex_set_header(dialog_ex, "Restore Card Data?", 64, 0, AlignCenter, AlignTop); @@ -23,16 +23,16 @@ void nfc_scene_restore_original_confirm_on_enter(void* context) { } bool nfc_scene_restore_original_confirm_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; + NfcApp* nfc = context; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { if(event.event == DialogExResultRight) { - if(!nfc_device_restore(nfc->dev, true)) { + if(nfc_delete_shadow_file(nfc)) { + scene_manager_next_scene(nfc->scene_manager, NfcSceneRestoreOriginal); + } else { scene_manager_search_and_switch_to_previous_scene( nfc->scene_manager, NfcSceneStart); - } else { - scene_manager_next_scene(nfc->scene_manager, NfcSceneRestoreOriginal); } consumed = true; } else if(event.event == DialogExResultLeft) { @@ -46,7 +46,7 @@ bool nfc_scene_restore_original_confirm_on_event(void* context, SceneManagerEven } void nfc_scene_restore_original_confirm_on_exit(void* context) { - Nfc* nfc = context; + NfcApp* nfc = context; // Clean view dialog_ex_reset(nfc->dialog_ex); diff --git a/applications/main/nfc/scenes/nfc_scene_retry_confirm.c b/applications/main/nfc/scenes/nfc_scene_retry_confirm.c index 5f4f7985e77..b80f1bdcc14 100644 --- a/applications/main/nfc/scenes/nfc_scene_retry_confirm.c +++ b/applications/main/nfc/scenes/nfc_scene_retry_confirm.c @@ -1,13 +1,13 @@ -#include "../nfc_i.h" +#include "../nfc_app_i.h" void nfc_scene_retry_confirm_dialog_callback(DialogExResult result, void* context) { - Nfc* nfc = context; + NfcApp* nfc = context; view_dispatcher_send_custom_event(nfc->view_dispatcher, result); } void nfc_scene_retry_confirm_on_enter(void* context) { - Nfc* nfc = context; + NfcApp* nfc = context; DialogEx* dialog_ex = nfc->dialog_ex; dialog_ex_set_left_button_text(dialog_ex, "Retry"); @@ -22,15 +22,20 @@ void nfc_scene_retry_confirm_on_enter(void* context) { } bool nfc_scene_retry_confirm_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; + NfcApp* nfc = context; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { if(event.event == DialogExResultRight) { consumed = scene_manager_previous_scene(nfc->scene_manager); } else if(event.event == DialogExResultLeft) { - consumed = scene_manager_search_and_switch_to_previous_scene( - nfc->scene_manager, NfcSceneRead); + if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneDetect)) { + consumed = scene_manager_search_and_switch_to_previous_scene( + nfc->scene_manager, NfcSceneDetect); + } else if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneRead)) { + consumed = scene_manager_search_and_switch_to_previous_scene( + nfc->scene_manager, NfcSceneRead); + } } } else if(event.type == SceneManagerEventTypeBack) { consumed = true; @@ -40,7 +45,7 @@ bool nfc_scene_retry_confirm_on_event(void* context, SceneManagerEvent event) { } void nfc_scene_retry_confirm_on_exit(void* context) { - Nfc* nfc = context; + NfcApp* nfc = context; // Clean view dialog_ex_reset(nfc->dialog_ex); diff --git a/applications/main/nfc/scenes/nfc_scene_rpc.c b/applications/main/nfc/scenes/nfc_scene_rpc.c index d06ee756465..e12e84605bc 100644 --- a/applications/main/nfc/scenes/nfc_scene_rpc.c +++ b/applications/main/nfc/scenes/nfc_scene_rpc.c @@ -1,93 +1,13 @@ -#include "../nfc_i.h" +#include "../helpers/protocol_support/nfc_protocol_support.h" void nfc_scene_rpc_on_enter(void* context) { - Nfc* nfc = context; - Popup* popup = nfc->popup; - - popup_set_header(popup, "NFC", 89, 42, AlignCenter, AlignBottom); - popup_set_text(popup, "RPC mode", 89, 44, AlignCenter, AlignTop); - - popup_set_icon(popup, 0, 12, &I_NFC_dolphin_emulation_47x61); - - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); - - notification_message(nfc->notifications, &sequence_display_backlight_on); -} - -static bool nfc_scene_rpc_emulate_callback(NfcWorkerEvent event, void* context) { - UNUSED(event); - Nfc* nfc = context; - - nfc->rpc_state = NfcRpcStateEmulated; - return true; + nfc_protocol_support_on_enter(NfcProtocolSupportSceneRpc, context); } bool nfc_scene_rpc_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - Popup* popup = nfc->popup; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - consumed = true; - if(event.event == NfcCustomEventViewExit) { - rpc_system_app_confirm(nfc->rpc_ctx, RpcAppEventAppExit, true); - scene_manager_stop(nfc->scene_manager); - view_dispatcher_stop(nfc->view_dispatcher); - } else if(event.event == NfcCustomEventRpcSessionClose) { - scene_manager_stop(nfc->scene_manager); - view_dispatcher_stop(nfc->view_dispatcher); - } else if(event.event == NfcCustomEventRpcLoad) { - bool result = false; - const char* arg = rpc_system_app_get_data(nfc->rpc_ctx); - if((arg) && (nfc->rpc_state == NfcRpcStateIdle)) { - if(nfc_device_load(nfc->dev, arg, false)) { - if(nfc->dev->format == NfcDeviceSaveFormatMifareUl) { - nfc_worker_start( - nfc->worker, - NfcWorkerStateMfUltralightEmulate, - &nfc->dev->dev_data, - nfc_scene_rpc_emulate_callback, - nfc); - } else if(nfc->dev->format == NfcDeviceSaveFormatMifareClassic) { - nfc_worker_start( - nfc->worker, - NfcWorkerStateMfClassicEmulate, - &nfc->dev->dev_data, - nfc_scene_rpc_emulate_callback, - nfc); - } else if(nfc->dev->format == NfcDeviceSaveFormatNfcV) { - nfc_worker_start( - nfc->worker, - NfcWorkerStateNfcVEmulate, - &nfc->dev->dev_data, - nfc_scene_rpc_emulate_callback, - nfc); - } else { - nfc_worker_start( - nfc->worker, NfcWorkerStateUidEmulate, &nfc->dev->dev_data, NULL, nfc); - } - nfc->rpc_state = NfcRpcStateEmulating; - result = true; - - nfc_blink_emulate_start(nfc); - nfc_text_store_set(nfc, "emulating\n%s", nfc->dev->dev_name); - popup_set_text(popup, nfc->text_store, 89, 44, AlignCenter, AlignTop); - } - } - - rpc_system_app_confirm(nfc->rpc_ctx, RpcAppEventLoadFile, result); - } - } - return consumed; + return nfc_protocol_support_on_event(NfcProtocolSupportSceneRpc, context, event); } void nfc_scene_rpc_on_exit(void* context) { - Nfc* nfc = context; - Popup* popup = nfc->popup; - - nfc_blink_stop(nfc); - - popup_set_header(popup, NULL, 0, 0, AlignCenter, AlignBottom); - popup_set_text(popup, NULL, 0, 0, AlignCenter, AlignTop); - popup_set_icon(popup, 0, 0, NULL); + nfc_protocol_support_on_exit(NfcProtocolSupportSceneRpc, context); } diff --git a/applications/main/nfc/scenes/nfc_scene_save_name.c b/applications/main/nfc/scenes/nfc_scene_save_name.c index b18e176333e..c23f097e144 100644 --- a/applications/main/nfc/scenes/nfc_scene_save_name.c +++ b/applications/main/nfc/scenes/nfc_scene_save_name.c @@ -1,93 +1,13 @@ -#include "../nfc_i.h" -#include -#include -#include -#include - -void nfc_scene_save_name_text_input_callback(void* context) { - Nfc* nfc = context; - - view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventTextInputDone); -} +#include "../helpers/protocol_support/nfc_protocol_support.h" void nfc_scene_save_name_on_enter(void* context) { - Nfc* nfc = context; - - // Setup view - TextInput* text_input = nfc->text_input; - bool dev_name_empty = false; - if(!strcmp(nfc->dev->dev_name, "")) { - name_generator_make_auto(nfc->text_store, NFC_DEV_NAME_MAX_LEN, NFC_APP_FILENAME_PREFIX); - dev_name_empty = true; - } else { - nfc_text_store_set(nfc, nfc->dev->dev_name); - } - text_input_set_header_text(text_input, "Name the card"); - text_input_set_result_callback( - text_input, - nfc_scene_save_name_text_input_callback, - nfc, - nfc->text_store, - NFC_DEV_NAME_MAX_LEN, - dev_name_empty); - - FuriString* folder_path; - folder_path = furi_string_alloc(); - - if(furi_string_end_with(nfc->dev->load_path, NFC_APP_FILENAME_EXTENSION)) { - path_extract_dirname(furi_string_get_cstr(nfc->dev->load_path), folder_path); - } else { - furi_string_set(folder_path, NFC_APP_FOLDER); - } - - ValidatorIsFile* validator_is_file = validator_is_file_alloc_init( - furi_string_get_cstr(folder_path), NFC_APP_FILENAME_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); - - furi_string_free(folder_path); + nfc_protocol_support_on_enter(NfcProtocolSupportSceneSaveName, context); } bool nfc_scene_save_name_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == NfcCustomEventTextInputDone) { - if(strcmp(nfc->dev->dev_name, "") != 0) { - 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; - } - strlcpy(nfc->dev->dev_name, nfc->text_store, strlen(nfc->text_store) + 1); - if(nfc_save_file(nfc)) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveSuccess); - if(!scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSavedMenu)) { - // Nothing, do not count editing as saving - } else if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSetType)) { - dolphin_deed(DolphinDeedNfcAddSave); - } else { - dolphin_deed(DolphinDeedNfcSave); - } - consumed = true; - } else { - consumed = scene_manager_search_and_switch_to_previous_scene( - nfc->scene_manager, NfcSceneStart); - } - } - } - return consumed; + return nfc_protocol_support_on_event(NfcProtocolSupportSceneSaveName, context, event); } void nfc_scene_save_name_on_exit(void* context) { - Nfc* nfc = context; - - // Clear view - void* validator_context = text_input_get_validator_callback_context(nfc->text_input); - text_input_set_validator(nfc->text_input, NULL, NULL); - validator_is_file_free(validator_context); - - text_input_reset(nfc->text_input); + nfc_protocol_support_on_exit(NfcProtocolSupportSceneSaveName, context); } diff --git a/applications/main/nfc/scenes/nfc_scene_save_success.c b/applications/main/nfc/scenes/nfc_scene_save_success.c index 34919cbd863..0cb26c0d45a 100644 --- a/applications/main/nfc/scenes/nfc_scene_save_success.c +++ b/applications/main/nfc/scenes/nfc_scene_save_success.c @@ -1,12 +1,12 @@ -#include "../nfc_i.h" +#include "../nfc_app_i.h" void nfc_scene_save_success_popup_callback(void* context) { - Nfc* nfc = context; + NfcApp* nfc = context; view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit); } void nfc_scene_save_success_on_enter(void* context) { - Nfc* nfc = context; + NfcApp* nfc = context; // Setup view Popup* popup = nfc->popup; @@ -20,7 +20,7 @@ void nfc_scene_save_success_on_enter(void* context) { } bool nfc_scene_save_success_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; + NfcApp* nfc = context; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { @@ -28,9 +28,6 @@ bool nfc_scene_save_success_on_event(void* context, SceneManagerEvent event) { if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneMfClassicKeys)) { consumed = scene_manager_search_and_switch_to_previous_scene( nfc->scene_manager, NfcSceneMfClassicKeys); - } else if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSavedMenu)) { - consumed = scene_manager_search_and_switch_to_previous_scene( - nfc->scene_manager, NfcSceneSavedMenu); } else { consumed = scene_manager_search_and_switch_to_another_scene( nfc->scene_manager, NfcSceneFileSelect); @@ -41,7 +38,7 @@ bool nfc_scene_save_success_on_event(void* context, SceneManagerEvent event) { } void nfc_scene_save_success_on_exit(void* context) { - Nfc* nfc = context; + NfcApp* nfc = context; // Clear view popup_reset(nfc->popup); diff --git a/applications/main/nfc/scenes/nfc_scene_saved_menu.c b/applications/main/nfc/scenes/nfc_scene_saved_menu.c index b3205554a43..d367e8ab861 100644 --- a/applications/main/nfc/scenes/nfc_scene_saved_menu.c +++ b/applications/main/nfc/scenes/nfc_scene_saved_menu.c @@ -1,186 +1,13 @@ -#include "../nfc_i.h" -#include - -enum SubmenuIndex { - SubmenuIndexEmulate, - SubmenuIndexEditUid, - SubmenuIndexDetectReader, - SubmenuIndexWrite, - SubmenuIndexUpdate, - SubmenuIndexRename, - SubmenuIndexDelete, - SubmenuIndexInfo, - SubmenuIndexRestoreOriginal, - SubmenuIndexMfUlUnlockByReader, - SubmenuIndexMfUlUnlockByPassword, -}; - -void nfc_scene_saved_menu_submenu_callback(void* context, uint32_t index) { - Nfc* nfc = context; - - view_dispatcher_send_custom_event(nfc->view_dispatcher, index); -} +#include "../helpers/protocol_support/nfc_protocol_support.h" void nfc_scene_saved_menu_on_enter(void* context) { - Nfc* nfc = context; - Submenu* submenu = nfc->submenu; - - if(nfc->dev->format == NfcDeviceSaveFormatUid || - nfc->dev->format == NfcDeviceSaveFormatMifareDesfire) { - submenu_add_item( - submenu, - "Emulate UID", - SubmenuIndexEmulate, - nfc_scene_saved_menu_submenu_callback, - nfc); - if(nfc->dev->dev_data.protocol == NfcDeviceProtocolUnknown) { - submenu_add_item( - submenu, - "Edit UID", - SubmenuIndexEditUid, - nfc_scene_saved_menu_submenu_callback, - nfc); - } - } else if( - (nfc->dev->format == NfcDeviceSaveFormatMifareUl && - mf_ul_emulation_supported(&nfc->dev->dev_data.mf_ul_data)) || - nfc->dev->format == NfcDeviceSaveFormatNfcV || - nfc->dev->format == NfcDeviceSaveFormatMifareClassic) { - submenu_add_item( - submenu, "Emulate", SubmenuIndexEmulate, nfc_scene_saved_menu_submenu_callback, nfc); - } - if(nfc->dev->format == NfcDeviceSaveFormatMifareClassic) { - if(!mf_classic_is_card_read(&nfc->dev->dev_data.mf_classic_data)) { - submenu_add_item( - submenu, - "Detect Reader", - SubmenuIndexDetectReader, - nfc_scene_saved_menu_submenu_callback, - nfc); - } - submenu_add_item( - submenu, - "Write to Initial Card", - SubmenuIndexWrite, - nfc_scene_saved_menu_submenu_callback, - nfc); - submenu_add_item( - submenu, - "Update from Initial Card", - SubmenuIndexUpdate, - nfc_scene_saved_menu_submenu_callback, - nfc); - } - submenu_add_item( - submenu, "Info", SubmenuIndexInfo, nfc_scene_saved_menu_submenu_callback, nfc); - if(nfc->dev->format == NfcDeviceSaveFormatMifareUl && - nfc->dev->dev_data.mf_ul_data.type != MfUltralightTypeULC && - !mf_ul_is_full_capture(&nfc->dev->dev_data.mf_ul_data)) { - submenu_add_item( - submenu, - "Unlock with Reader", - SubmenuIndexMfUlUnlockByReader, - nfc_scene_saved_menu_submenu_callback, - nfc); - submenu_add_item( - submenu, - "Unlock with Password", - SubmenuIndexMfUlUnlockByPassword, - nfc_scene_saved_menu_submenu_callback, - nfc); - } - if(nfc->dev->shadow_file_exist) { - submenu_add_item( - submenu, - "Restore to original", - SubmenuIndexRestoreOriginal, - nfc_scene_saved_menu_submenu_callback, - nfc); - } - submenu_add_item( - submenu, "Rename", SubmenuIndexRename, nfc_scene_saved_menu_submenu_callback, nfc); - submenu_add_item( - submenu, "Delete", SubmenuIndexDelete, nfc_scene_saved_menu_submenu_callback, nfc); - submenu_set_selected_item( - nfc->submenu, scene_manager_get_scene_state(nfc->scene_manager, NfcSceneSavedMenu)); - - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); + nfc_protocol_support_on_enter(NfcProtocolSupportSceneSavedMenu, context); } bool nfc_scene_saved_menu_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; - NfcDeviceData* dev_data = &nfc->dev->dev_data; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - scene_manager_set_scene_state(nfc->scene_manager, NfcSceneSavedMenu, event.event); - if(event.event == SubmenuIndexEmulate) { - if(nfc->dev->format == NfcDeviceSaveFormatMifareUl) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightEmulate); - } else if(nfc->dev->format == NfcDeviceSaveFormatMifareClassic) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicEmulate); - } else if(nfc->dev->format == NfcDeviceSaveFormatNfcV) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcVEmulate); - } else { - scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateUid); - } - dolphin_deed(DolphinDeedNfcEmulate); - consumed = true; - } else if(event.event == SubmenuIndexDetectReader) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneDetectReader); - dolphin_deed(DolphinDeedNfcDetectReader); - consumed = true; - } else if(event.event == SubmenuIndexWrite) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicWrite); - consumed = true; - } else if(event.event == SubmenuIndexUpdate) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicUpdate); - consumed = true; - } else if(event.event == SubmenuIndexRename) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveName); - consumed = true; - } else if(event.event == SubmenuIndexEditUid) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneSetUid); - consumed = true; - } else if(event.event == SubmenuIndexDelete) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneDelete); - consumed = true; - } else if(event.event == SubmenuIndexInfo) { - bool application_info_present = false; - if(dev_data->protocol == NfcDeviceProtocolEMV) { - application_info_present = true; - } else if( - dev_data->protocol == NfcDeviceProtocolMifareClassic || - dev_data->protocol == NfcDeviceProtocolMifareDesfire || - dev_data->protocol == NfcDeviceProtocolMifareUl) { - application_info_present = nfc_supported_card_verify_and_parse(dev_data); - } - - FURI_LOG_I("nfc", "application_info_present: %d", application_info_present); - - if(application_info_present) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneDeviceInfo); - } else { - scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcDataInfo); - } - consumed = true; - } else if(event.event == SubmenuIndexRestoreOriginal) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneRestoreOriginalConfirm); - consumed = true; - } else if(event.event == SubmenuIndexMfUlUnlockByReader) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightUnlockAuto); - consumed = true; - } else if(event.event == SubmenuIndexMfUlUnlockByPassword) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightUnlockMenu); - consumed = true; - } - } - - return consumed; + return nfc_protocol_support_on_event(NfcProtocolSupportSceneSavedMenu, context, event); } void nfc_scene_saved_menu_on_exit(void* context) { - Nfc* nfc = context; - - submenu_reset(nfc->submenu); + nfc_protocol_support_on_exit(NfcProtocolSupportSceneSavedMenu, context); } diff --git a/applications/main/nfc/scenes/nfc_scene_select_protocol.c b/applications/main/nfc/scenes/nfc_scene_select_protocol.c new file mode 100644 index 00000000000..86b9982fc69 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_select_protocol.c @@ -0,0 +1,67 @@ +#include "../nfc_app_i.h" + +void nfc_scene_select_protocol_submenu_callback(void* context, uint32_t index) { + NfcApp* instance = context; + + view_dispatcher_send_custom_event(instance->view_dispatcher, index); +} + +void nfc_scene_select_protocol_on_enter(void* context) { + NfcApp* instance = context; + Submenu* submenu = instance->submenu; + + FuriString* temp_str = furi_string_alloc(); + const char* prefix; + if(scene_manager_has_previous_scene(instance->scene_manager, NfcSceneExtraActions)) { + prefix = "Read"; + instance->protocols_detected_num = NfcProtocolNum; + for(uint32_t i = 0; i < NfcProtocolNum; i++) { + instance->protocols_detected[i] = i; + } + } else { + prefix = "Read as"; + submenu_set_header(submenu, "Multi-protocol card"); + notification_message(instance->notifications, &sequence_single_vibro); + } + + for(uint32_t i = 0; i < instance->protocols_detected_num; i++) { + furi_string_printf( + temp_str, + "%s %s", + prefix, + nfc_device_get_protocol_name(instance->protocols_detected[i])); + submenu_add_item( + submenu, + furi_string_get_cstr(temp_str), + i, + nfc_scene_select_protocol_submenu_callback, + instance); + } + furi_string_free(temp_str); + + const uint32_t state = + scene_manager_get_scene_state(instance->scene_manager, NfcSceneSelectProtocol); + submenu_set_selected_item(submenu, state); + + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewMenu); +} + +bool nfc_scene_select_protocol_on_event(void* context, SceneManagerEvent event) { + NfcApp* instance = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + instance->protocols_detected_selected_idx = event.event; + scene_manager_next_scene(instance->scene_manager, NfcSceneRead); + scene_manager_set_scene_state( + instance->scene_manager, NfcSceneSelectProtocol, event.event); + consumed = true; + } + return consumed; +} + +void nfc_scene_select_protocol_on_exit(void* context) { + NfcApp* nfc = context; + + submenu_reset(nfc->submenu); +} diff --git a/applications/main/nfc/scenes/nfc_scene_set_atqa.c b/applications/main/nfc/scenes/nfc_scene_set_atqa.c index d079b380475..17a07b8b537 100644 --- a/applications/main/nfc/scenes/nfc_scene_set_atqa.c +++ b/applications/main/nfc/scenes/nfc_scene_set_atqa.c @@ -1,34 +1,39 @@ -#include "../nfc_i.h" +#include "../nfc_app_i.h" -void nfc_scene_set_atqa_byte_input_callback(void* context) { - Nfc* nfc = context; +#include "../helpers/protocol_support/nfc_protocol_support_gui_common.h" - view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventByteInputDone); +static void nfc_scene_set_atqa_byte_input_changed_callback(void* context) { + NfcApp* instance = context; + iso14443_3a_set_atqa(instance->iso14443_3a_edit_data, instance->byte_input_store); } void nfc_scene_set_atqa_on_enter(void* context) { - Nfc* nfc = context; + NfcApp* instance = context; + + iso14443_3a_get_atqa(instance->iso14443_3a_edit_data, instance->byte_input_store); // Setup view - ByteInput* byte_input = nfc->byte_input; + ByteInput* byte_input = instance->byte_input; byte_input_set_header_text(byte_input, "Enter ATQA in hex"); byte_input_set_result_callback( byte_input, - nfc_scene_set_atqa_byte_input_callback, - NULL, - nfc, - nfc->dev->dev_data.nfc_data.atqa, + nfc_protocol_support_common_byte_input_done_callback, + nfc_scene_set_atqa_byte_input_changed_callback, + instance, + instance->byte_input_store, 2); - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewByteInput); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewByteInput); } bool nfc_scene_set_atqa_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; + NfcApp* instance = context; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { if(event.event == NfcCustomEventByteInputDone) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneSetUid); + nfc_device_set_data( + instance->nfc_device, NfcProtocolIso14443_3a, instance->iso14443_3a_edit_data); + scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid); consumed = true; } } @@ -36,9 +41,9 @@ bool nfc_scene_set_atqa_on_event(void* context, SceneManagerEvent event) { } void nfc_scene_set_atqa_on_exit(void* context) { - Nfc* nfc = context; + NfcApp* instance = context; // Clear view - byte_input_set_result_callback(nfc->byte_input, NULL, NULL, NULL, NULL, 0); - byte_input_set_header_text(nfc->byte_input, ""); + byte_input_set_result_callback(instance->byte_input, NULL, NULL, NULL, NULL, 0); + byte_input_set_header_text(instance->byte_input, ""); } diff --git a/applications/main/nfc/scenes/nfc_scene_set_sak.c b/applications/main/nfc/scenes/nfc_scene_set_sak.c index 60a1e1494b8..c55cee1c21d 100644 --- a/applications/main/nfc/scenes/nfc_scene_set_sak.c +++ b/applications/main/nfc/scenes/nfc_scene_set_sak.c @@ -1,44 +1,48 @@ -#include "../nfc_i.h" +#include "../nfc_app_i.h" -void nfc_scene_set_sak_byte_input_callback(void* context) { - Nfc* nfc = context; +#include "../helpers/protocol_support/nfc_protocol_support_gui_common.h" - view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventByteInputDone); +static void nfc_scene_set_sak_byte_input_changed_callback(void* context) { + NfcApp* instance = context; + iso14443_3a_set_sak(instance->iso14443_3a_edit_data, instance->byte_input_store[0]); } void nfc_scene_set_sak_on_enter(void* context) { - Nfc* nfc = context; + NfcApp* instance = context; + + instance->byte_input_store[0] = iso14443_3a_get_sak(instance->iso14443_3a_edit_data); // Setup view - ByteInput* byte_input = nfc->byte_input; + ByteInput* byte_input = instance->byte_input; byte_input_set_header_text(byte_input, "Enter SAK in hex"); byte_input_set_result_callback( byte_input, - nfc_scene_set_sak_byte_input_callback, - NULL, - nfc, - &nfc->dev->dev_data.nfc_data.sak, + nfc_protocol_support_common_byte_input_done_callback, + nfc_scene_set_sak_byte_input_changed_callback, + instance, + instance->byte_input_store, 1); - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewByteInput); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewByteInput); } bool nfc_scene_set_sak_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; + NfcApp* instance = context; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { if(event.event == NfcCustomEventByteInputDone) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneSetAtqa); + scene_manager_next_scene(instance->scene_manager, NfcSceneSetAtqa); consumed = true; } } + return consumed; } void nfc_scene_set_sak_on_exit(void* context) { - Nfc* nfc = context; + NfcApp* instance = context; // Clear view - byte_input_set_result_callback(nfc->byte_input, NULL, NULL, NULL, NULL, 0); - byte_input_set_header_text(nfc->byte_input, ""); + byte_input_set_result_callback(instance->byte_input, NULL, NULL, NULL, NULL, 0); + byte_input_set_header_text(instance->byte_input, ""); } diff --git a/applications/main/nfc/scenes/nfc_scene_set_type.c b/applications/main/nfc/scenes/nfc_scene_set_type.c index cadf2eb69ef..e3366008078 100644 --- a/applications/main/nfc/scenes/nfc_scene_set_type.c +++ b/applications/main/nfc/scenes/nfc_scene_set_type.c @@ -1,68 +1,72 @@ -#include "../nfc_i.h" -#include "lib/nfc/helpers/nfc_generators.h" +#include "../nfc_app_i.h" + +#include "../helpers/protocol_support/nfc_protocol_support_gui_common.h" enum SubmenuIndex { - SubmenuIndexNFCA4, - SubmenuIndexNFCA7, SubmenuIndexGeneratorsStart, + SubmenuIndexNFCA4 = NfcDataGeneratorTypeNum, + SubmenuIndexNFCA7, }; -void nfc_scene_set_type_submenu_callback(void* context, uint32_t index) { - Nfc* nfc = context; - - view_dispatcher_send_custom_event(nfc->view_dispatcher, index); +static void nfc_scene_set_type_init_edit_data(Iso14443_3aData* data, size_t uid_len) { + // Easiest way to create a zero'd buffer of given length + uint8_t* uid = malloc(uid_len); + iso14443_3a_set_uid(data, uid, uid_len); + free(uid); } void nfc_scene_set_type_on_enter(void* context) { - Nfc* nfc = context; - Submenu* submenu = nfc->submenu; - // Clear device name - nfc_device_set_name(nfc->dev, ""); - furi_string_set(nfc->dev->load_path, ""); + NfcApp* instance = context; + + Submenu* submenu = instance->submenu; submenu_add_item( - submenu, "NFC-A 7-bytes UID", SubmenuIndexNFCA7, nfc_scene_set_type_submenu_callback, nfc); + submenu, + "NFC-A 7-bytes UID", + SubmenuIndexNFCA7, + nfc_protocol_support_common_submenu_callback, + instance); submenu_add_item( - submenu, "NFC-A 4-bytes UID", SubmenuIndexNFCA4, nfc_scene_set_type_submenu_callback, nfc); + submenu, + "NFC-A 4-bytes UID", + SubmenuIndexNFCA4, + nfc_protocol_support_common_submenu_callback, + instance); - // Generators - int i = SubmenuIndexGeneratorsStart; - for(const NfcGenerator* const* generator = nfc_generators; *generator != NULL; - ++generator, ++i) { - submenu_add_item(submenu, (*generator)->name, i, nfc_scene_set_type_submenu_callback, nfc); + for(size_t i = 0; i < NfcDataGeneratorTypeNum; i++) { + const char* name = nfc_data_generator_get_name(i); + submenu_add_item(submenu, name, i, nfc_protocol_support_common_submenu_callback, instance); } - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewMenu); } bool nfc_scene_set_type_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; + NfcApp* instance = context; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { if(event.event == SubmenuIndexNFCA7) { - nfc->dev->dev_data.nfc_data.uid_len = 7; - nfc->dev->format = NfcDeviceSaveFormatUid; - scene_manager_next_scene(nfc->scene_manager, NfcSceneSetSak); + nfc_scene_set_type_init_edit_data(instance->iso14443_3a_edit_data, 7); + scene_manager_next_scene(instance->scene_manager, NfcSceneSetSak); consumed = true; } else if(event.event == SubmenuIndexNFCA4) { - nfc->dev->dev_data.nfc_data.uid_len = 4; - nfc->dev->format = NfcDeviceSaveFormatUid; - scene_manager_next_scene(nfc->scene_manager, NfcSceneSetSak); + nfc_scene_set_type_init_edit_data(instance->iso14443_3a_edit_data, 4); + scene_manager_next_scene(instance->scene_manager, NfcSceneSetSak); consumed = true; } else { - nfc_device_clear(nfc->dev); - nfc->generator = nfc_generators[event.event - SubmenuIndexGeneratorsStart]; - nfc->generator->generator_func(&nfc->dev->dev_data); - - scene_manager_next_scene(nfc->scene_manager, NfcSceneGenerateInfo); + nfc_data_generator_fill_data(event.event, instance->nfc_device); + scene_manager_set_scene_state( + instance->scene_manager, NfcSceneGenerateInfo, event.event); + scene_manager_next_scene(instance->scene_manager, NfcSceneGenerateInfo); consumed = true; } } + return consumed; } void nfc_scene_set_type_on_exit(void* context) { - Nfc* nfc = context; + NfcApp* instance = context; - submenu_reset(nfc->submenu); + submenu_reset(instance->submenu); } diff --git a/applications/main/nfc/scenes/nfc_scene_set_uid.c b/applications/main/nfc/scenes/nfc_scene_set_uid.c index 54606b68eec..df8a4dc72cc 100644 --- a/applications/main/nfc/scenes/nfc_scene_set_uid.c +++ b/applications/main/nfc/scenes/nfc_scene_set_uid.c @@ -1,42 +1,51 @@ -#include "../nfc_i.h" +#include "../nfc_app_i.h" -void nfc_scene_set_uid_byte_input_callback(void* context) { - Nfc* nfc = context; +#include "../helpers/protocol_support/nfc_protocol_support_gui_common.h" - view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventByteInputDone); +static void nfc_scene_set_uid_byte_input_changed_callback(void* context) { + NfcApp* instance = context; + // Retrieve previously saved UID length + const size_t uid_len = scene_manager_get_scene_state(instance->scene_manager, NfcSceneSetUid); + nfc_device_set_uid(instance->nfc_device, instance->byte_input_store, uid_len); } void nfc_scene_set_uid_on_enter(void* context) { - Nfc* nfc = context; + NfcApp* instance = context; + + size_t uid_len; + const uint8_t* uid = nfc_device_get_uid(instance->nfc_device, &uid_len); + + memcpy(instance->byte_input_store, uid, uid_len); + // Save UID length for use in callback + scene_manager_set_scene_state(instance->scene_manager, NfcSceneSetUid, uid_len); // Setup view - ByteInput* byte_input = nfc->byte_input; + ByteInput* byte_input = instance->byte_input; byte_input_set_header_text(byte_input, "Enter UID in hex"); - nfc->dev_edit_data = nfc->dev->dev_data.nfc_data; byte_input_set_result_callback( byte_input, - nfc_scene_set_uid_byte_input_callback, - NULL, - nfc, - nfc->dev_edit_data.uid, - nfc->dev_edit_data.uid_len); - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewByteInput); + nfc_protocol_support_common_byte_input_done_callback, + nfc_scene_set_uid_byte_input_changed_callback, + instance, + instance->byte_input_store, + uid_len); + + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewByteInput); } bool nfc_scene_set_uid_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = (Nfc*)context; + NfcApp* instance = context; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { if(event.event == NfcCustomEventByteInputDone) { - if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSavedMenu)) { - nfc->dev->dev_data.nfc_data = nfc->dev_edit_data; - if(nfc_save_file(nfc)) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveSuccess); + if(scene_manager_has_previous_scene(instance->scene_manager, NfcSceneSavedMenu)) { + if(nfc_save(instance)) { + scene_manager_next_scene(instance->scene_manager, NfcSceneSaveSuccess); consumed = true; } } else { - scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveName); + scene_manager_next_scene(instance->scene_manager, NfcSceneSaveName); consumed = true; } } @@ -46,9 +55,9 @@ bool nfc_scene_set_uid_on_event(void* context, SceneManagerEvent event) { } void nfc_scene_set_uid_on_exit(void* context) { - Nfc* nfc = context; + NfcApp* instance = context; // Clear view - byte_input_set_result_callback(nfc->byte_input, NULL, NULL, NULL, NULL, 0); - byte_input_set_header_text(nfc->byte_input, ""); + byte_input_set_result_callback(instance->byte_input, NULL, NULL, NULL, NULL, 0); + byte_input_set_header_text(instance->byte_input, ""); } diff --git a/applications/main/nfc/scenes/nfc_scene_start.c b/applications/main/nfc/scenes/nfc_scene_start.c index c9e8bf78cf5..c923226fc7e 100644 --- a/applications/main/nfc/scenes/nfc_scene_start.c +++ b/applications/main/nfc/scenes/nfc_scene_start.c @@ -1,5 +1,4 @@ -#include "../nfc_i.h" -#include "nfc_worker_i.h" +#include "../nfc_app_i.h" #include enum SubmenuIndex { @@ -12,15 +11,20 @@ enum SubmenuIndex { }; void nfc_scene_start_submenu_callback(void* context, uint32_t index) { - Nfc* nfc = context; + NfcApp* nfc = context; view_dispatcher_send_custom_event(nfc->view_dispatcher, index); } void nfc_scene_start_on_enter(void* context) { - Nfc* nfc = context; + NfcApp* nfc = context; Submenu* submenu = nfc->submenu; + // Clear file name and device contents + furi_string_reset(nfc->file_name); + nfc_device_clear(nfc->nfc_device); + iso14443_3a_reset(nfc->iso14443_3a_edit_data); + submenu_add_item(submenu, "Read", SubmenuIndexRead, nfc_scene_start_submenu_callback, nfc); submenu_add_item( submenu, "Detect Reader", SubmenuIndexDetectReader, nfc_scene_start_submenu_callback, nfc); @@ -38,32 +42,23 @@ void nfc_scene_start_on_enter(void* context) { submenu_set_selected_item( submenu, scene_manager_get_scene_state(nfc->scene_manager, NfcSceneStart)); - nfc_device_clear(nfc->dev); view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); } bool nfc_scene_start_on_event(void* context, SceneManagerEvent event) { - Nfc* nfc = context; + NfcApp* nfc = context; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { if(event.event == SubmenuIndexRead) { scene_manager_set_scene_state(nfc->scene_manager, NfcSceneStart, SubmenuIndexRead); - nfc->dev->dev_data.read_mode = NfcReadModeAuto; - scene_manager_next_scene(nfc->scene_manager, NfcSceneRead); + scene_manager_next_scene(nfc->scene_manager, NfcSceneDetect); dolphin_deed(DolphinDeedNfcRead); consumed = true; } else if(event.event == SubmenuIndexDetectReader) { scene_manager_set_scene_state( nfc->scene_manager, NfcSceneStart, SubmenuIndexDetectReader); - bool sd_exist = storage_sd_status(nfc->dev->storage) == FSE_OK; - if(sd_exist) { - nfc_device_data_clear(&nfc->dev->dev_data); - scene_manager_next_scene(nfc->scene_manager, NfcSceneDetectReader); - dolphin_deed(DolphinDeedNfcDetectReader); - } else { - scene_manager_next_scene(nfc->scene_manager, NfcSceneDictNotFound); - } + scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicDetectReader); consumed = true; } else if(event.event == SubmenuIndexSaved) { // Save the scene state explicitly in each branch, so that @@ -92,7 +87,7 @@ bool nfc_scene_start_on_event(void* context, SceneManagerEvent event) { } void nfc_scene_start_on_exit(void* context) { - Nfc* nfc = context; + NfcApp* nfc = context; submenu_reset(nfc->submenu); } diff --git a/applications/main/nfc/scenes/nfc_scene_supported_card.c b/applications/main/nfc/scenes/nfc_scene_supported_card.c new file mode 100644 index 00000000000..cea55b783bc --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_supported_card.c @@ -0,0 +1,50 @@ +#include "nfc/nfc_app_i.h" + +#include "nfc/helpers/nfc_supported_cards.h" +#include "nfc/helpers/protocol_support/nfc_protocol_support_gui_common.h" + +void nfc_scene_supported_card_on_enter(void* context) { + NfcApp* instance = context; + + FuriString* temp_str = furi_string_alloc(); + + if(nfc_supported_cards_parse(instance->nfc_device, temp_str)) { + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); + widget_add_button_element( + instance->widget, + GuiButtonTypeRight, + "More", + nfc_protocol_support_common_widget_callback, + instance); + + scene_manager_set_scene_state(instance->scene_manager, NfcSceneSupportedCard, true); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget); + + } else { + scene_manager_set_scene_state(instance->scene_manager, NfcSceneSupportedCard, false); + scene_manager_next_scene(instance->scene_manager, NfcSceneInfo); + } + + furi_string_free(temp_str); +} + +bool nfc_scene_supported_card_on_event(void* context, SceneManagerEvent event) { + NfcApp* instance = context; + + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == GuiButtonTypeRight) { + scene_manager_next_scene(instance->scene_manager, NfcSceneInfo); + consumed = true; + } + } + + return consumed; +} + +void nfc_scene_supported_card_on_exit(void* context) { + NfcApp* instance = context; + widget_reset(instance->widget); +} diff --git a/applications/main/nfc/views/detect_reader.c b/applications/main/nfc/views/detect_reader.c index e5951beb267..ebcda7caf1b 100644 --- a/applications/main/nfc/views/detect_reader.c +++ b/applications/main/nfc/views/detect_reader.c @@ -163,7 +163,10 @@ void detect_reader_set_nonces_collected(DetectReader* detect_reader, uint16_t no with_view_model( detect_reader->view, DetectReaderViewModel * model, - { model->nonces = nonces_collected; }, + { + model->nonces = nonces_collected; + model->state = DetectReaderStateReaderDetected; + }, false); } diff --git a/applications/main/nfc/views/dict_attack.c b/applications/main/nfc/views/dict_attack.c index 8f4bd063e8b..b656c2dc5b5 100644 --- a/applications/main/nfc/views/dict_attack.c +++ b/applications/main/nfc/views/dict_attack.c @@ -2,42 +2,36 @@ #include -typedef enum { - DictAttackStateRead, - DictAttackStateCardRemoved, -} DictAttackState; +#define NFC_CLASSIC_KEYS_PER_SECTOR 2 struct DictAttack { View* view; DictAttackCallback callback; void* context; - bool card_present; }; typedef struct { - DictAttackState state; - MfClassicType type; FuriString* header; + bool card_detected; uint8_t sectors_total; uint8_t sectors_read; - uint8_t sector_current; - uint8_t keys_total; + uint8_t current_sector; uint8_t keys_found; - uint16_t dict_keys_total; - uint16_t dict_keys_current; + size_t dict_keys_total; + size_t dict_keys_current; bool is_key_attack; uint8_t key_attack_current_sector; } DictAttackViewModel; static void dict_attack_draw_callback(Canvas* canvas, void* model) { DictAttackViewModel* m = model; - if(m->state == DictAttackStateCardRemoved) { + if(!m->card_detected) { canvas_set_font(canvas, FontPrimary); canvas_draw_str_aligned(canvas, 64, 4, AlignCenter, AlignTop, "Lost the tag!"); canvas_set_font(canvas, FontSecondary); elements_multiline_text_aligned( canvas, 64, 23, AlignCenter, AlignTop, "Make sure the tag is\npositioned correctly."); - } else if(m->state == DictAttackStateRead) { + } else { char draw_str[32] = {}; canvas_set_font(canvas, FontSecondary); canvas_draw_str_aligned( @@ -49,28 +43,33 @@ static void dict_attack_draw_callback(Canvas* canvas, void* model) { "Reuse key check for sector: %d", m->key_attack_current_sector); } else { - snprintf(draw_str, sizeof(draw_str), "Unlocking sector: %d", m->sector_current); + snprintf(draw_str, sizeof(draw_str), "Unlocking sector: %d", m->current_sector); } canvas_draw_str_aligned(canvas, 0, 10, AlignLeft, AlignTop, draw_str); float dict_progress = m->dict_keys_total == 0 ? 0 : (float)(m->dict_keys_current) / (float)(m->dict_keys_total); float progress = m->sectors_total == 0 ? 0 : - ((float)(m->sector_current) + dict_progress) / + ((float)(m->current_sector) + dict_progress) / (float)(m->sectors_total); - if(progress > 1.0) { - progress = 1.0; + if(progress > 1.0f) { + progress = 1.0f; } if(m->dict_keys_current == 0) { // Cause when people see 0 they think it's broken - snprintf(draw_str, sizeof(draw_str), "%d/%d", 1, m->dict_keys_total); + snprintf(draw_str, sizeof(draw_str), "%d/%zu", 1, m->dict_keys_total); } else { snprintf( - draw_str, sizeof(draw_str), "%d/%d", m->dict_keys_current, m->dict_keys_total); + draw_str, sizeof(draw_str), "%zu/%zu", m->dict_keys_current, m->dict_keys_total); } elements_progress_bar_with_text(canvas, 0, 20, 128, dict_progress, draw_str); canvas_set_font(canvas, FontSecondary); - snprintf(draw_str, sizeof(draw_str), "Keys found: %d/%d", m->keys_found, m->keys_total); + snprintf( + draw_str, + sizeof(draw_str), + "Keys found: %d/%d", + m->keys_found, + m->sectors_total * NFC_CLASSIC_KEYS_PER_SECTOR); canvas_draw_str_aligned(canvas, 0, 33, AlignLeft, AlignTop, draw_str); snprintf( draw_str, sizeof(draw_str), "Sectors Read: %d/%d", m->sectors_read, m->sectors_total); @@ -80,55 +79,56 @@ static void dict_attack_draw_callback(Canvas* canvas, void* model) { } static bool dict_attack_input_callback(InputEvent* event, void* context) { - DictAttack* dict_attack = context; + DictAttack* instance = context; bool consumed = false; + if(event->type == InputTypeShort && event->key == InputKeyOk) { - if(dict_attack->callback) { - dict_attack->callback(dict_attack->context); + if(instance->callback) { + instance->callback(DictAttackEventSkipPressed, instance->context); } consumed = true; } + return consumed; } DictAttack* dict_attack_alloc() { - DictAttack* dict_attack = malloc(sizeof(DictAttack)); - dict_attack->view = view_alloc(); - view_allocate_model(dict_attack->view, ViewModelTypeLocking, sizeof(DictAttackViewModel)); - view_set_draw_callback(dict_attack->view, dict_attack_draw_callback); - view_set_input_callback(dict_attack->view, dict_attack_input_callback); - view_set_context(dict_attack->view, dict_attack); + DictAttack* instance = malloc(sizeof(DictAttack)); + instance->view = view_alloc(); + view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(DictAttackViewModel)); + view_set_draw_callback(instance->view, dict_attack_draw_callback); + view_set_input_callback(instance->view, dict_attack_input_callback); + view_set_context(instance->view, instance); with_view_model( - dict_attack->view, + instance->view, DictAttackViewModel * model, { model->header = furi_string_alloc(); }, false); - return dict_attack; + + return instance; } -void dict_attack_free(DictAttack* dict_attack) { - furi_assert(dict_attack); +void dict_attack_free(DictAttack* instance) { + furi_assert(instance); + with_view_model( - dict_attack->view, - DictAttackViewModel * model, - { furi_string_free(model->header); }, - false); - view_free(dict_attack->view); - free(dict_attack); + instance->view, DictAttackViewModel * model, { furi_string_free(model->header); }, false); + + view_free(instance->view); + free(instance); } -void dict_attack_reset(DictAttack* dict_attack) { - furi_assert(dict_attack); +void dict_attack_reset(DictAttack* instance) { + furi_assert(instance); + with_view_model( - dict_attack->view, + instance->view, DictAttackViewModel * model, { - model->state = DictAttackStateRead; - model->type = MfClassicType1k; + model->card_detected = false; model->sectors_total = 0; model->sectors_read = 0; - model->sector_current = 0; - model->keys_total = 0; + model->current_sector = 0; model->keys_found = 0; model->dict_keys_total = 0; model->dict_keys_current = 0; @@ -138,152 +138,108 @@ void dict_attack_reset(DictAttack* dict_attack) { false); } -View* dict_attack_get_view(DictAttack* dict_attack) { - furi_assert(dict_attack); - return dict_attack->view; +View* dict_attack_get_view(DictAttack* instance) { + furi_assert(instance); + + return instance->view; } -void dict_attack_set_callback(DictAttack* dict_attack, DictAttackCallback callback, void* context) { - furi_assert(dict_attack); +void dict_attack_set_callback(DictAttack* instance, DictAttackCallback callback, void* context) { + furi_assert(instance); furi_assert(callback); - dict_attack->callback = callback; - dict_attack->context = context; + + instance->callback = callback; + instance->context = context; } -void dict_attack_set_header(DictAttack* dict_attack, const char* header) { - furi_assert(dict_attack); +void dict_attack_set_header(DictAttack* instance, const char* header) { + furi_assert(instance); furi_assert(header); with_view_model( - dict_attack->view, + instance->view, DictAttackViewModel * model, { furi_string_set(model->header, header); }, true); } -void dict_attack_set_card_detected(DictAttack* dict_attack, MfClassicType type) { - furi_assert(dict_attack); - dict_attack->card_present = true; +void dict_attack_set_card_state(DictAttack* instance, bool detected) { + furi_assert(instance); + with_view_model( - dict_attack->view, - DictAttackViewModel * model, - { - model->state = DictAttackStateRead; - model->sectors_total = mf_classic_get_total_sectors_num(type); - model->keys_total = model->sectors_total * 2; - }, - true); + instance->view, DictAttackViewModel * model, { model->card_detected = detected; }, true); } -void dict_attack_set_card_removed(DictAttack* dict_attack) { - furi_assert(dict_attack); - dict_attack->card_present = false; +void dict_attack_set_sectors_total(DictAttack* instance, uint8_t sectors_total) { + furi_assert(instance); + with_view_model( - dict_attack->view, + instance->view, DictAttackViewModel * model, - { model->state = DictAttackStateCardRemoved; }, + { model->sectors_total = sectors_total; }, true); } -bool dict_attack_get_card_state(DictAttack* dict_attack) { - furi_assert(dict_attack); - return dict_attack->card_present; -} +void dict_attack_set_sectors_read(DictAttack* instance, uint8_t sectors_read) { + furi_assert(instance); -void dict_attack_set_sector_read(DictAttack* dict_attack, uint8_t sec_read) { - furi_assert(dict_attack); with_view_model( - dict_attack->view, DictAttackViewModel * model, { model->sectors_read = sec_read; }, true); + instance->view, DictAttackViewModel * model, { model->sectors_read = sectors_read; }, true); } -void dict_attack_set_keys_found(DictAttack* dict_attack, uint8_t keys_found) { - furi_assert(dict_attack); - with_view_model( - dict_attack->view, DictAttackViewModel * model, { model->keys_found = keys_found; }, true); -} +void dict_attack_set_keys_found(DictAttack* instance, uint8_t keys_found) { + furi_assert(instance); -void dict_attack_set_current_sector(DictAttack* dict_attack, uint8_t curr_sec) { - furi_assert(dict_attack); with_view_model( - dict_attack->view, - DictAttackViewModel * model, - { - model->sector_current = curr_sec; - model->dict_keys_current = 0; - }, - true); + instance->view, DictAttackViewModel * model, { model->keys_found = keys_found; }, true); } -void dict_attack_inc_current_sector(DictAttack* dict_attack) { - furi_assert(dict_attack); - with_view_model( - dict_attack->view, - DictAttackViewModel * model, - { - if(model->sector_current < model->sectors_total) { - model->sector_current++; - model->dict_keys_current = 0; - } - }, - true); -} +void dict_attack_set_current_sector(DictAttack* instance, uint8_t current_sector) { + furi_assert(instance); -void dict_attack_inc_keys_found(DictAttack* dict_attack) { - furi_assert(dict_attack); with_view_model( - dict_attack->view, + instance->view, DictAttackViewModel * model, - { - if(model->keys_found < model->keys_total) { - model->keys_found++; - } - }, + { model->current_sector = current_sector; }, true); } -void dict_attack_set_total_dict_keys(DictAttack* dict_attack, uint16_t dict_keys_total) { - furi_assert(dict_attack); +void dict_attack_set_total_dict_keys(DictAttack* instance, size_t dict_keys_total) { + furi_assert(instance); + with_view_model( - dict_attack->view, + instance->view, DictAttackViewModel * model, { model->dict_keys_total = dict_keys_total; }, true); } -void dict_attack_inc_current_dict_key(DictAttack* dict_attack, uint16_t keys_tried) { - furi_assert(dict_attack); +void dict_attack_set_current_dict_key(DictAttack* instance, size_t cur_key_num) { + furi_assert(instance); + with_view_model( - dict_attack->view, + instance->view, DictAttackViewModel * model, - { - if(model->dict_keys_current + keys_tried < model->dict_keys_total) { - model->dict_keys_current += keys_tried; - } - }, + { model->dict_keys_current = cur_key_num; }, true); } -void dict_attack_set_key_attack(DictAttack* dict_attack, bool is_key_attack, uint8_t sector) { - furi_assert(dict_attack); +void dict_attack_set_key_attack(DictAttack* instance, uint8_t sector) { + furi_assert(instance); + with_view_model( - dict_attack->view, + instance->view, DictAttackViewModel * model, { - model->is_key_attack = is_key_attack; + model->is_key_attack = true; model->key_attack_current_sector = sector; }, true); } -void dict_attack_inc_key_attack_current_sector(DictAttack* dict_attack) { - furi_assert(dict_attack); +void dict_attack_reset_key_attack(DictAttack* instance) { + furi_assert(instance); + with_view_model( - dict_attack->view, - DictAttackViewModel * model, - { - if(model->key_attack_current_sector < model->sectors_total) { - model->key_attack_current_sector++; - } - }, - true); + instance->view, DictAttackViewModel * model, { model->is_key_attack = false; }, true); } diff --git a/applications/main/nfc/views/dict_attack.h b/applications/main/nfc/views/dict_attack.h index 73b98a1b827..54a0220fe59 100644 --- a/applications/main/nfc/views/dict_attack.h +++ b/applications/main/nfc/views/dict_attack.h @@ -1,46 +1,50 @@ #pragma once + #include #include -#include -#include +#ifdef __cplusplus +extern "C" { +#endif typedef struct DictAttack DictAttack; -typedef void (*DictAttackCallback)(void* context); - -DictAttack* dict_attack_alloc(); +typedef enum { + DictAttackEventSkipPressed, +} DictAttackEvent; -void dict_attack_free(DictAttack* dict_attack); +typedef void (*DictAttackCallback)(DictAttackEvent event, void* context); -void dict_attack_reset(DictAttack* dict_attack); +DictAttack* dict_attack_alloc(); -View* dict_attack_get_view(DictAttack* dict_attack); +void dict_attack_free(DictAttack* instance); -void dict_attack_set_callback(DictAttack* dict_attack, DictAttackCallback callback, void* context); +void dict_attack_reset(DictAttack* instance); -void dict_attack_set_header(DictAttack* dict_attack, const char* header); +View* dict_attack_get_view(DictAttack* instance); -void dict_attack_set_card_detected(DictAttack* dict_attack, MfClassicType type); +void dict_attack_set_callback(DictAttack* instance, DictAttackCallback callback, void* context); -void dict_attack_set_card_removed(DictAttack* dict_attack); +void dict_attack_set_header(DictAttack* instance, const char* header); -bool dict_attack_get_card_state(DictAttack* dict_attack); +void dict_attack_set_card_state(DictAttack* instance, bool detected); -void dict_attack_set_sector_read(DictAttack* dict_attack, uint8_t sec_read); +void dict_attack_set_sectors_total(DictAttack* instance, uint8_t sectors_total); -void dict_attack_set_keys_found(DictAttack* dict_attack, uint8_t keys_found); +void dict_attack_set_sectors_read(DictAttack* instance, uint8_t sectors_read); -void dict_attack_set_current_sector(DictAttack* dict_attack, uint8_t curr_sec); +void dict_attack_set_keys_found(DictAttack* instance, uint8_t keys_found); -void dict_attack_inc_current_sector(DictAttack* dict_attack); +void dict_attack_set_current_sector(DictAttack* instance, uint8_t curr_sec); -void dict_attack_inc_keys_found(DictAttack* dict_attack); +void dict_attack_set_total_dict_keys(DictAttack* instance, size_t dict_keys_total); -void dict_attack_set_total_dict_keys(DictAttack* dict_attack, uint16_t dict_keys_total); +void dict_attack_set_current_dict_key(DictAttack* instance, size_t cur_key_num); -void dict_attack_inc_current_dict_key(DictAttack* dict_attack, uint16_t keys_tried); +void dict_attack_set_key_attack(DictAttack* instance, uint8_t sector); -void dict_attack_set_key_attack(DictAttack* dict_attack, bool is_key_attack, uint8_t sector); +void dict_attack_reset_key_attack(DictAttack* instance); -void dict_attack_inc_key_attack_current_sector(DictAttack* dict_attack); \ No newline at end of file +#ifdef __cplusplus +} +#endif diff --git a/applications/services/desktop/scenes/desktop_scene_pin_timeout.c b/applications/services/desktop/scenes/desktop_scene_pin_timeout.c index ebe825e6a91..2f009e7d2aa 100644 --- a/applications/services/desktop/scenes/desktop_scene_pin_timeout.c +++ b/applications/services/desktop/scenes/desktop_scene_pin_timeout.c @@ -1,7 +1,6 @@ #include #include #include -#include #include #include "../desktop_i.h" diff --git a/assets/unit_tests/nfc/Ntag213_locked.nfc b/assets/unit_tests/nfc/Ntag213_locked.nfc new file mode 100644 index 00000000000..32f7771165f --- /dev/null +++ b/assets/unit_tests/nfc/Ntag213_locked.nfc @@ -0,0 +1,66 @@ +Filetype: Flipper NFC device +Version: 3 +# Nfc device type can be UID, Mifare Ultralight, Mifare Classic +Device type: NTAG213 +# UID, ATQA and SAK are common for all formats +UID: 04 AC 6B 72 BA 6C 80 +ATQA: 00 44 +SAK: 00 +# Mifare Ultralight specific data +Data format version: 1 +Signature: 2D AE BC AF 84 B8 85 87 C2 FB FE 76 13 58 86 72 8E 1D 3C B5 DA 24 23 44 E5 63 4D 4C 82 FB D7 18 +Mifare version: 00 04 04 02 01 00 0F 03 +Counter 0: 0 +Tearing 0: 00 +Counter 1: 0 +Tearing 1: 00 +Counter 2: 0 +Tearing 2: 00 +Pages total: 45 +Pages read: 45 +Page 0: 04 AC 6B 4B +Page 1: 72 BA 6C 80 +Page 2: 24 48 00 00 +Page 3: E1 10 12 00 +Page 4: 00 00 41 50 +Page 5: 00 00 31 31 +Page 6: 00 20 09 28 +Page 7: 00 03 31 59 +Page 8: 91 DF D3 00 +Page 9: 00 00 00 00 +Page 10: 00 00 00 00 +Page 11: 00 00 00 00 +Page 12: 00 00 00 00 +Page 13: 00 00 00 00 +Page 14: 00 00 00 00 +Page 15: 00 00 00 00 +Page 16: 00 00 00 00 +Page 17: 00 00 00 00 +Page 18: 00 00 00 00 +Page 19: 00 00 00 00 +Page 20: 00 00 00 00 +Page 21: 00 00 00 00 +Page 22: 00 00 00 00 +Page 23: 00 00 00 00 +Page 24: 00 00 00 00 +Page 25: 00 00 00 00 +Page 26: 00 00 00 00 +Page 27: 00 00 00 00 +Page 28: 00 00 00 00 +Page 29: 00 00 00 00 +Page 30: 00 00 00 00 +Page 31: 00 00 00 00 +Page 32: 00 00 00 00 +Page 33: 00 00 00 00 +Page 34: 00 00 00 00 +Page 35: 00 00 00 00 +Page 36: 00 00 00 00 +Page 37: 00 00 00 00 +Page 38: 00 00 00 00 +Page 39: 00 00 00 00 +Page 40: 00 00 00 BD +Page 41: 04 00 00 04 +Page 42: C0 05 00 00 +Page 43: 95 3F 52 FF +Page 44: 00 00 00 00 +Failed authentication attempts: 0 diff --git a/assets/unit_tests/nfc/Ntag215.nfc b/assets/unit_tests/nfc/Ntag215.nfc new file mode 100644 index 00000000000..420af08a66d --- /dev/null +++ b/assets/unit_tests/nfc/Ntag215.nfc @@ -0,0 +1,156 @@ +Filetype: Flipper NFC device +Version: 3 +# Nfc device type can be UID, Mifare Ultralight, Mifare Classic +Device type: NTAG215 +# UID, ATQA and SAK are common for all formats +UID: 04 51 5C FA 6F 73 81 +ATQA: 00 44 +SAK: 00 +# Mifare Ultralight specific data +Data format version: 1 +Signature: 42 21 E4 6C 79 6A 81 5E EA 0D 93 6D 85 EE 4B 0C 2A 00 D5 77 F1 C5 67 F3 63 75 F8 EB 86 48 5E 6B +Mifare version: 00 04 04 02 01 00 11 03 +Counter 0: 0 +Tearing 0: 00 +Counter 1: 0 +Tearing 1: 00 +Counter 2: 00 +Tearing 2: 00 +Pages total: 135 +Pages read: 135 +Page 0: 04 51 5C 81 +Page 1: FA 6F 73 81 +Page 2: 67 48 0F E0 +Page 3: F1 10 FF EE +Page 4: A5 00 00 00 +Page 5: 90 42 74 71 +Page 6: FD 8F 50 61 +Page 7: C5 65 1B 54 +Page 8: EF 68 D0 8E +Page 9: 3D 35 DB 83 +Page 10: D3 00 29 F6 +Page 11: 42 2A A5 5C +Page 12: F1 69 0A FC +Page 13: B6 44 E9 6B +Page 14: 77 41 88 81 +Page 15: 86 31 CB AD +Page 16: B1 DE F1 AB +Page 17: DF 96 C2 C5 +Page 18: C1 26 99 96 +Page 19: 85 AF 9F 0E +Page 20: 58 FE ED DC +Page 21: 0A 0A 00 01 +Page 22: 03 C1 05 02 +Page 23: 38 39 34 33 +Page 24: 49 2D 4E 5C +Page 25: 5B 21 0F 44 +Page 26: 3F 3F 76 69 +Page 27: B4 72 D8 38 +Page 28: A0 35 53 51 +Page 29: 53 EB A6 7C +Page 30: 3E 8B 97 C0 +Page 31: 00 7A 45 13 +Page 32: 3A 8B D4 0F +Page 33: 31 C2 32 CC +Page 34: B4 24 A6 1B +Page 35: D3 F5 4A 1F +Page 36: CD 8F 1D 64 +Page 37: 01 F4 DF C2 +Page 38: 11 16 C2 C5 +Page 39: 30 6D 49 AF +Page 40: 10 D4 7C 3C +Page 41: 6E 36 4E 08 +Page 42: 95 76 BC 84 +Page 43: 35 50 DD F0 +Page 44: 21 0F EE D9 +Page 45: 85 19 54 5F +Page 46: 3E A9 04 20 +Page 47: 1B 97 E4 39 +Page 48: FF 0A 45 F6 +Page 49: 13 D4 3E DD +Page 50: 97 42 FC 67 +Page 51: 6A AC 78 96 +Page 52: D1 DA 25 23 +Page 53: BF 4D B3 76 +Page 54: F1 21 ED 15 +Page 55: BD 55 11 C4 +Page 56: 4E 8C E9 23 +Page 57: C0 C4 6D 5A +Page 58: 58 25 FF 95 +Page 59: 3C 2B 7A 57 +Page 60: 66 BE A0 61 +Page 61: BC FC 4A 31 +Page 62: 4D AC EE 81 +Page 63: BE 1A 86 04 +Page 64: F6 D7 5E B3 +Page 65: E7 A8 A2 86 +Page 66: E9 40 AB 47 +Page 67: C8 36 E4 3E +Page 68: A7 4D D3 EA +Page 69: 83 9A 64 F7 +Page 70: 96 6B 5D BF +Page 71: 4E A2 A6 0F +Page 72: BD 3D BE 7C +Page 73: 22 0C 68 51 +Page 74: 0F 9A B8 AE +Page 75: 38 2C C4 CD +Page 76: 53 D8 DD 18 +Page 77: A6 5D 35 87 +Page 78: C9 6D 99 59 +Page 79: 61 9F B6 DC +Page 80: E6 22 0F 99 +Page 81: 39 82 79 60 +Page 82: 58 2E BE F7 +Page 83: EF F7 95 62 +Page 84: D5 06 1B 58 +Page 85: 65 05 A9 08 +Page 86: 75 ED 5D 90 +Page 87: 5A E1 7E C9 +Page 88: 35 D6 29 BB +Page 89: D0 67 6C F9 +Page 90: A0 FF 0B 93 +Page 91: 22 EA A3 3F +Page 92: E2 BD BD 58 +Page 93: BE 93 D9 94 +Page 94: 41 CC 7E 40 +Page 95: E6 8C 5A 43 +Page 96: 65 C1 24 94 +Page 97: B9 97 61 13 +Page 98: AD 74 FF 21 +Page 99: 0F EC F6 03 +Page 100: 89 5D 89 E5 +Page 101: 8D 11 F8 D7 +Page 102: 33 43 79 2E +Page 103: 23 E5 29 B5 +Page 104: 53 98 13 FF +Page 105: E8 79 8B 33 +Page 106: 45 6C 34 38 +Page 107: 3B 69 28 D7 +Page 108: D2 80 B0 2F +Page 109: D0 18 D5 DD +Page 110: 6C 2D D9 97 +Page 111: CA 78 B4 A2 +Page 112: B7 3E B8 79 +Page 113: A2 BE 54 E4 +Page 114: C8 28 0C 4A +Page 115: 81 E7 EC 1C +Page 116: 39 93 6F 70 +Page 117: 75 77 5C FC +Page 118: 66 58 0C 1C +Page 119: 9F 70 2E C8 +Page 120: 52 4A 52 BD +Page 121: 56 D5 6A 15 +Page 122: 54 1B 33 90 +Page 123: 44 11 C1 07 +Page 124: 11 5C BA 80 +Page 125: 10 14 20 9A +Page 126: 4A D8 E6 36 +Page 127: DA B8 59 E5 +Page 128: 5E 48 95 DA +Page 129: 96 6A 26 85 +Page 130: 01 00 0F BD +Page 131: 00 00 00 04 +Page 132: 5F 00 00 00 +Page 133: 00 00 00 00 +Page 134: 00 00 00 00 +Failed authentication attempts: 0 diff --git a/assets/unit_tests/nfc/Ntag216.nfc b/assets/unit_tests/nfc/Ntag216.nfc new file mode 100644 index 00000000000..debe43858fd --- /dev/null +++ b/assets/unit_tests/nfc/Ntag216.nfc @@ -0,0 +1,252 @@ +Filetype: Flipper NFC device +Version: 2 +# Nfc device type can be UID, Mifare Ultralight, Mifare Classic, Bank card +Device type: NTAG216 +# UID, ATQA and SAK are common for all formats +UID: 04 D9 65 0A 32 5E 80 +ATQA: 44 00 +SAK: 00 +# Mifare Ultralight specific data +Data format version: 1 +Signature: 48 2A F2 01 0F F2 F5 A7 9A D5 79 6E CB 14 54 48 98 D1 57 5D 8A 23 A9 B0 E8 20 02 3E CD C8 16 DB +Mifare version: 00 04 04 02 01 00 13 03 +Counter 0: 0 +Tearing 0: 00 +Counter 1: 0 +Tearing 1: 00 +Counter 2: 0 +Tearing 2: 00 +Pages total: 231 +Pages read: 231 +Page 0: 04 D9 65 30 +Page 1: 0A 32 5E 80 +Page 2: E6 48 00 00 +Page 3: E1 10 6D 00 +Page 4: 03 37 D1 01 +Page 5: 33 55 04 6D +Page 6: 2E 79 6F 75 +Page 7: 74 75 62 65 +Page 8: 2E 63 6F 6D +Page 9: 2F 77 61 74 +Page 10: 63 68 3F 76 +Page 11: 3D 62 78 71 +Page 12: 4C 73 72 6C +Page 13: 61 6B 4B 38 +Page 14: 26 66 65 61 +Page 15: 74 75 72 65 +Page 16: 3D 79 6F 75 +Page 17: 74 75 2E 62 +Page 18: 65 FE 00 00 +Page 19: 00 00 00 00 +Page 20: 00 00 00 00 +Page 21: 00 00 00 00 +Page 22: 00 00 00 00 +Page 23: 00 00 00 00 +Page 24: 00 00 00 00 +Page 25: 00 00 00 00 +Page 26: 00 00 00 00 +Page 27: 00 00 00 00 +Page 28: 00 00 00 00 +Page 29: 00 00 00 00 +Page 30: 00 00 00 00 +Page 31: 00 00 00 00 +Page 32: 00 00 00 00 +Page 33: 00 00 00 00 +Page 34: 00 00 00 00 +Page 35: 00 00 00 00 +Page 36: 00 00 00 00 +Page 37: 00 00 00 00 +Page 38: 00 00 00 00 +Page 39: 00 00 00 00 +Page 40: 00 00 00 00 +Page 41: 00 00 00 00 +Page 42: 00 00 00 00 +Page 43: 00 00 00 00 +Page 44: 00 00 00 00 +Page 45: 00 00 00 00 +Page 46: 00 00 00 00 +Page 47: 00 00 00 00 +Page 48: 00 00 00 00 +Page 49: 00 00 00 00 +Page 50: 00 00 00 00 +Page 51: 00 00 00 00 +Page 52: 00 00 00 00 +Page 53: 00 00 00 00 +Page 54: 00 00 00 00 +Page 55: 00 00 00 00 +Page 56: 00 00 00 00 +Page 57: 00 00 00 00 +Page 58: 00 00 00 00 +Page 59: 00 00 00 00 +Page 60: 00 00 00 00 +Page 61: 00 00 00 00 +Page 62: 00 00 00 00 +Page 63: 00 00 00 00 +Page 64: 00 00 00 00 +Page 65: 00 00 00 00 +Page 66: 00 00 00 00 +Page 67: 00 00 00 00 +Page 68: 00 00 00 00 +Page 69: 00 00 00 00 +Page 70: 00 00 00 00 +Page 71: 00 00 00 00 +Page 72: 00 00 00 00 +Page 73: 00 00 00 00 +Page 74: 00 00 00 00 +Page 75: 00 00 00 00 +Page 76: 00 00 00 00 +Page 77: 00 00 00 00 +Page 78: 00 00 00 00 +Page 79: 00 00 00 00 +Page 80: 00 00 00 00 +Page 81: 00 00 00 00 +Page 82: 00 00 00 00 +Page 83: 00 00 00 00 +Page 84: 00 00 00 00 +Page 85: 00 00 00 00 +Page 86: 00 00 00 00 +Page 87: 00 00 00 00 +Page 88: 00 00 00 00 +Page 89: 00 00 00 00 +Page 90: 00 00 00 00 +Page 91: 00 00 00 00 +Page 92: 00 00 00 00 +Page 93: 00 00 00 00 +Page 94: 00 00 00 00 +Page 95: 00 00 00 00 +Page 96: 00 00 00 00 +Page 97: 00 00 00 00 +Page 98: 00 00 00 00 +Page 99: 00 00 00 00 +Page 100: 00 00 00 00 +Page 101: 00 00 00 00 +Page 102: 00 00 00 00 +Page 103: 00 00 00 00 +Page 104: 00 00 00 00 +Page 105: 00 00 00 00 +Page 106: 00 00 00 00 +Page 107: 00 00 00 00 +Page 108: 00 00 00 00 +Page 109: 00 00 00 00 +Page 110: 00 00 00 00 +Page 111: 00 00 00 00 +Page 112: 00 00 00 00 +Page 113: 00 00 00 00 +Page 114: 00 00 00 00 +Page 115: 00 00 00 00 +Page 116: 00 00 00 00 +Page 117: 00 00 00 00 +Page 118: 00 00 00 00 +Page 119: 00 00 00 00 +Page 120: 00 00 00 00 +Page 121: 00 00 00 00 +Page 122: 00 00 00 00 +Page 123: 00 00 00 00 +Page 124: 00 00 00 00 +Page 125: 00 00 00 00 +Page 126: 00 00 00 00 +Page 127: 00 00 00 00 +Page 128: 00 00 00 00 +Page 129: 00 00 00 00 +Page 130: 00 00 00 00 +Page 131: 00 00 00 00 +Page 132: 00 00 00 00 +Page 133: 00 00 00 00 +Page 134: 00 00 00 00 +Page 135: 00 00 00 00 +Page 136: 00 00 00 00 +Page 137: 00 00 00 00 +Page 138: 00 00 00 00 +Page 139: 00 00 00 00 +Page 140: 00 00 00 00 +Page 141: 00 00 00 00 +Page 142: 00 00 00 00 +Page 143: 00 00 00 00 +Page 144: 00 00 00 00 +Page 145: 00 00 00 00 +Page 146: 00 00 00 00 +Page 147: 00 00 00 00 +Page 148: 00 00 00 00 +Page 149: 00 00 00 00 +Page 150: 00 00 00 00 +Page 151: 00 00 00 00 +Page 152: 00 00 00 00 +Page 153: 00 00 00 00 +Page 154: 00 00 00 00 +Page 155: 00 00 00 00 +Page 156: 00 00 00 00 +Page 157: 00 00 00 00 +Page 158: 00 00 00 00 +Page 159: 00 00 00 00 +Page 160: 00 00 00 00 +Page 161: 00 00 00 00 +Page 162: 00 00 00 00 +Page 163: 00 00 00 00 +Page 164: 00 00 00 00 +Page 165: 00 00 00 00 +Page 166: 00 00 00 00 +Page 167: 00 00 00 00 +Page 168: 00 00 00 00 +Page 169: 00 00 00 00 +Page 170: 00 00 00 00 +Page 171: 00 00 00 00 +Page 172: 00 00 00 00 +Page 173: 00 00 00 00 +Page 174: 00 00 00 00 +Page 175: 00 00 00 00 +Page 176: 00 00 00 00 +Page 177: 00 00 00 00 +Page 178: 00 00 00 00 +Page 179: 00 00 00 00 +Page 180: 00 00 00 00 +Page 181: 00 00 00 00 +Page 182: 00 00 00 00 +Page 183: 00 00 00 00 +Page 184: 00 00 00 00 +Page 185: 00 00 00 00 +Page 186: 00 00 00 00 +Page 187: 00 00 00 00 +Page 188: 00 00 00 00 +Page 189: 00 00 00 00 +Page 190: 00 00 00 00 +Page 191: 00 00 00 00 +Page 192: 00 00 00 00 +Page 193: 00 00 00 00 +Page 194: 00 00 00 00 +Page 195: 00 00 00 00 +Page 196: 00 00 00 00 +Page 197: 00 00 00 00 +Page 198: 00 00 00 00 +Page 199: 00 00 00 00 +Page 200: 00 00 00 00 +Page 201: 00 00 00 00 +Page 202: 00 00 00 00 +Page 203: 00 00 00 00 +Page 204: 00 00 00 00 +Page 205: 00 00 00 00 +Page 206: 00 00 00 00 +Page 207: 00 00 00 00 +Page 208: 00 00 00 00 +Page 209: 00 00 00 00 +Page 210: 00 00 00 00 +Page 211: 00 00 00 00 +Page 212: 00 00 00 00 +Page 213: 00 00 00 00 +Page 214: 00 00 00 00 +Page 215: 00 00 00 00 +Page 216: 00 00 00 00 +Page 217: 00 00 00 00 +Page 218: 00 00 00 00 +Page 219: 00 00 00 00 +Page 220: 00 00 00 00 +Page 221: 00 00 00 00 +Page 222: 00 00 00 00 +Page 223: 00 00 00 00 +Page 224: 00 00 00 00 +Page 225: 00 00 00 00 +Page 226: 00 00 00 BD +Page 227: 04 00 00 FF +Page 228: 00 05 00 00 +Page 229: 00 00 00 00 +Page 230: 00 00 00 00 +Failed authentication attempts: 0 diff --git a/assets/unit_tests/nfc/Ultralight_11.nfc b/assets/unit_tests/nfc/Ultralight_11.nfc new file mode 100644 index 00000000000..22441289dd0 --- /dev/null +++ b/assets/unit_tests/nfc/Ultralight_11.nfc @@ -0,0 +1,41 @@ +Filetype: Flipper NFC device +Version: 3 +# Nfc device type can be UID, Mifare Ultralight, Mifare Classic +Device type: Mifare Ultralight 11 +# UID, ATQA and SAK are common for all formats +UID: 04 15 74 F2 B0 5E 81 +ATQA: 00 44 +SAK: 00 +# Mifare Ultralight specific data +Data format version: 1 +Signature: A4 37 7D E5 8C 2F 88 D8 04 60 41 6E 3A C8 CD DB 19 94 26 12 C5 D0 12 B0 EB 88 05 72 89 F2 A5 61 +Mifare version: 00 04 03 01 01 00 0B 03 +Counter 0: 0 +Tearing 0: BD +Counter 1: 0 +Tearing 1: BD +Counter 2: 0 +Tearing 2: BD +Pages total: 20 +Pages read: 20 +Page 0: 04 15 74 ED +Page 1: F2 B0 5E 81 +Page 2: 9D 48 F8 FF +Page 3: C1 31 3E 3F +Page 4: B0 00 F0 02 +Page 5: 2F B3 45 A0 +Page 6: D4 9C 02 F2 +Page 7: 4A B1 ED FF +Page 8: C8 01 00 02 +Page 9: 4F B3 46 70 +Page 10: EE F6 60 B0 +Page 11: B6 C6 12 1B +Page 12: B9 1E 49 C3 +Page 13: 49 DF 7A 57 +Page 14: 08 52 2A 11 +Page 15: 28 0A 28 59 +Page 16: 00 00 00 FF +Page 17: 00 05 00 00 +Page 18: FF FF FF FF +Page 19: 00 00 00 00 +Failed authentication attempts: 0 diff --git a/assets/unit_tests/nfc/Ultralight_21.nfc b/assets/unit_tests/nfc/Ultralight_21.nfc new file mode 100644 index 00000000000..dc01e93a602 --- /dev/null +++ b/assets/unit_tests/nfc/Ultralight_21.nfc @@ -0,0 +1,62 @@ +Filetype: Flipper NFC device +Version: 3 +# Nfc device type can be UID, Mifare Ultralight, Mifare Classic +Device type: Mifare Ultralight 21 +# UID, ATQA and SAK are common for all formats +UID: 34 BF AB B1 AE 73 D6 +ATQA: 00 44 +SAK: 00 +# Mifare Ultralight specific data +Data format version: 1 +Signature: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +Mifare version: 00 34 21 01 01 00 0E 03 +Counter 0: 0 +Tearing 0: 00 +Counter 1: 0 +Tearing 1: 00 +Counter 2: 0 +Tearing 2: 00 +Pages total: 41 +Pages read: 41 +Page 0: 34 BF AB A8 +Page 1: B1 AE 73 D6 +Page 2: BA 00 70 08 +Page 3: FF FF FF FC +Page 4: 45 D9 BB A0 +Page 5: 5D 9D FA 00 +Page 6: 80 70 38 40 +Page 7: 12 30 02 00 +Page 8: 00 00 00 00 +Page 9: 00 00 00 00 +Page 10: AC A1 0D E4 +Page 11: 80 70 38 40 +Page 12: 00 57 A0 01 +Page 13: 00 08 C1 40 +Page 14: 00 00 00 00 +Page 15: AC A1 0D E4 +Page 16: 00 00 00 00 +Page 17: 00 00 00 00 +Page 18: 00 00 00 00 +Page 19: 00 00 00 00 +Page 20: 00 00 00 00 +Page 21: 00 00 00 00 +Page 22: 00 00 00 00 +Page 23: 00 00 00 00 +Page 24: 00 00 00 00 +Page 25: 00 00 00 00 +Page 26: 00 00 00 00 +Page 27: 00 00 00 00 +Page 28: 00 00 00 00 +Page 29: 00 00 00 00 +Page 30: 00 00 00 00 +Page 31: 00 00 00 00 +Page 32: 00 00 00 00 +Page 33: 00 00 00 00 +Page 34: 00 00 00 00 +Page 35: 00 00 00 00 +Page 36: 00 00 00 BD +Page 37: 00 00 00 FF +Page 38: 00 05 00 00 +Page 39: FF FF FF FF +Page 40: 00 00 00 00 +Failed authentication attempts: 0 diff --git a/documentation/file_formats/NfcFileFormats.md b/documentation/file_formats/NfcFileFormats.md index 78c6420ee03..f752cdb901b 100644 --- a/documentation/file_formats/NfcFileFormats.md +++ b/documentation/file_formats/NfcFileFormats.md @@ -1,44 +1,109 @@ # NFC Flipper File Formats -## NFC-A (UID) + Header +## UID + Header (General format) ### Example Filetype: Flipper NFC device - Version: 3 - # Nfc device type can be UID, Mifare Ultralight, Mifare Classic, Bank card - Device type: UID - # UID, ATQA and SAK are common for all formats - UID: 04 85 92 8A A0 61 81 - ATQA: 00 44 - SAK: 00 + Version: 4 + # Device type can be ISO14443-3A, ISO14443-3B, ISO14443-4A, NTAG/Ultralight, Mifare Classic, Mifare DESFire + Device type: ISO14443-4A + # UID is common for all formats + UID: 04 48 6A 32 33 58 80 + ------------------------- + (Device-specific data) ### Description -This file format is used to store the UID, SAK and ATQA of a NFC-A device. It does not store any internal data, so it can be used for multiple different card types. Also used as a header for other formats. +This file format is used to store the device type and the UID of an NFC device. It does not store any internal data, so it is only used as a header for other formats. Version differences: 1. Initial version, deprecated 2. LSB ATQA (e.g. 4400 instead of 0044) 3. MSB ATQA (current version) +4. Replace UID device type with ISO14443-3A -UID can be either 4 or 7 bytes long. ATQA is 2 bytes long. SAK is 1 byte long. +## ISO14443-3A -## Mifare Ultralight/NTAG + Filetype: Flipper NFC device + Version: 4 + # Device type can be ISO14443-3A, ISO14443-3B, ISO14443-4A, NTAG/Ultralight, Mifare Classic, Mifare DESFire + Device type: ISO14443-3A + # UID is common for all formats + UID: 34 19 6D 41 14 56 E6 + # ISO14443-3A specific data + ATQA: 00 44 + SAK: 00 + +### Description + +This file format is used to store the UID, SAK and ATQA of a ISO14443-3A device. +UID must be either 4 or 7 bytes long. ATQA is 2 bytes long. SAK is 1 byte long. + +Version differences: +None, there are no versions yet. + +## ISO14443-3B + + Filetype: Flipper NFC device + Version: 4 + # Device type can be ISO14443-3A, ISO14443-3B, ISO14443-4A, NTAG/Ultralight, Mifare Classic, Mifare DESFire + Device type: ISO14443-3B + # UID is common for all formats + UID: 30 1D B3 28 + # ISO14443-3B specific data + Application data: 00 12 34 FF + Protocol info: 11 81 E1 + +### Description + +This file format is used to store the UID, Application data and Protocol info of a ISO14443-3B device. +UID must be 4 bytes long. Application data is 4 bytes long. Protocol info is 3 bytes long. + +Version differences: +None, there are no versions yet. + +## ISO14443-4A + +### Example + + Filetype: Flipper NFC device + Version: 4 + # Device type can be ISO14443-3A, ISO14443-3B, ISO14443-4A, NTAG/Ultralight, Mifare Classic, Mifare DESFire + Device type: ISO14443-4A + # UID is common for all formats + UID: 04 48 6A 32 33 58 80 + # ISO14443-3A specific data + ATQA: 03 44 + SAK: 20 + # ISO14443-4A specific data + ATS: 06 75 77 81 02 80 + +### Description + +This file format is used to store the UID, SAK and ATQA of a ISO14443-4A device. It also stores the Answer to Select (ATS) data of the card. +ATS must be no less than 5 bytes long. + +Version differences: +None, there are no versions yet. + +## NTAG/Ultralight ### Example Filetype: Flipper NFC device - Version: 3 - # Nfc device type can be UID, Mifare Ultralight, Mifare Classic - Device type: NTAG216 - # UID, ATQA and SAK are common for all formats + Version: 4 + # Device type can be ISO14443-3A, ISO14443-3B, ISO14443-4A, NTAG/Ultralight, Mifare Classic, Mifare DESFire + Device type: NTAG/Ultralight + # UID is common for all formats UID: 04 85 90 54 12 98 23 + # ISO14443-3A specific data ATQA: 00 44 SAK: 00 - # Mifare Ultralight specific data - Data format version: 1 + # NTAG/Ultralight specific data + Data format version: 2 + NTAG/Ultralight type: NTAG216 Signature: 1B 84 EB 70 BD 4C BD 1B 1D E4 98 0B 18 58 BD 7C 72 85 B4 E4 7B 38 8E 96 CF 88 6B EE A3 43 AD 90 Mifare version: 00 04 04 02 01 00 13 03 Counter 0: 0 @@ -66,6 +131,8 @@ UID can be either 4 or 7 bytes long. ATQA is 2 bytes long. SAK is 1 byte long. This file format is used to store the UID, SAK and ATQA of a Mifare Ultralight/NTAG device. It also stores the internal data of the card, the signature, the version, and the counters. The data is stored in pages, just like on the card itself. +The "NTAG/Ultralight type" field contains the concrete device type. It must be one of: Mifare Ultralight, Mifare Ultralight 11, Mifare Ultralight 21, NTAG203, NTAG213, NTAG215, NTAG216, NTAG I2C 1K, NTAG I2C 2K, NTAG I2C Plus 1K, NTAG I2C Plus 2K. + The "Signature" field contains the reply of the tag to the READ_SIG command. More on that can be found here: (page 31) The "Mifare version" field is not related to the file format version but to the Mifare Ultralight version. It contains the response of the tag to the GET_VERSION command. More on that can be found here: (page 21) @@ -74,18 +141,20 @@ Other fields are the direct representation of the card's internal state. Learn m Version differences: -1. Current version +1. Mifare Ultralight type is stored directly in Device type field +2. Current version, Mifare Ultralight type is stored in the same-named field ## Mifare Classic ### Example Filetype: Flipper NFC device - Version: 3 - # Nfc device type can be UID, Mifare Ultralight, Mifare Classic + Version: 4 + # Device type can be ISO14443-3A, ISO14443-3B, ISO14443-4A, NTAG/Ultralight, Mifare Classic, Mifare DESFire Device type: Mifare Classic - # UID, ATQA and SAK are common for all formats + # UID is common for all formats UID: BA E2 7C 9D + # ISO14443-3A specific data ATQA: 00 02 SAK: 18 # Mifare Classic specific data @@ -145,13 +214,16 @@ Example: ### Example Filetype: Flipper NFC device - Version: 3 - # Nfc device type can be UID, Mifare Ultralight, Mifare Classic + Version: 4 + # Device type can be ISO14443-3A, ISO14443-3B, ISO14443-4A, NTAG/Ultralight, Mifare Classic, Mifare DESFire Device type: Mifare DESFire - # UID, ATQA and SAK are common for all formats + # UID is common for all formats UID: 04 2F 19 0A CD 66 80 + # ISO14443-3A specific data ATQA: 03 44 SAK: 20 + # ISO14443-4A specific data + ATS: 06 75 77 81 02 80 # Mifare DESFire specific data PICC Version: 04 01 01 12 00 1A 05 04 01 01 02 01 1A 05 04 2F 19 0A CD 66 80 CE ED D4 51 80 31 19 PICC Free Memory: 7520 diff --git a/firmware.scons b/firmware.scons index 82f775d719c..d8e96ad7d03 100644 --- a/firmware.scons +++ b/firmware.scons @@ -71,6 +71,15 @@ env = ENV.Clone( "FURI_DEBUG" if ENV["DEBUG"] else "FURI_NDEBUG", ], }, + "nfc": { + "CCFLAGS": [ + "-Og", + ], + "CPPDEFINES": [ + "NDEBUG", + "FURI_DEBUG", + ], + }, }, FW_API_TABLE=None, _APP_ICONS=None, diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index a3eb2743d8c..8e15030a94a 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,39.2,, +Version,+,40.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -81,6 +81,7 @@ Header,+,firmware/targets/furi_hal_include/furi_hal_usb_hid.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_usb_hid_u2f.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_version.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_vibro.h,, +Header,+,lib/digital_signal/digital_sequence.h,, Header,+,lib/digital_signal/digital_signal.h,, Header,+,lib/drivers/cc1101_regs.h,, Header,+,lib/flipper_application/api_hashtable/api_hashtable.h,, @@ -132,11 +133,15 @@ Header,+,lib/mlib/m-rbtree.h,, Header,+,lib/mlib/m-tuple.h,, Header,+,lib/mlib/m-variant.h,, Header,+,lib/music_worker/music_worker.h,, +Header,+,lib/nanopb/pb.h,, +Header,+,lib/nanopb/pb_decode.h,, +Header,+,lib/nanopb/pb_encode.h,, Header,+,lib/one_wire/maxim_crc.h,, Header,+,lib/one_wire/one_wire_host.h,, Header,+,lib/one_wire/one_wire_slave.h,, Header,+,lib/print/wrappers.h,, Header,+,lib/pulse_reader/pulse_reader.h,, +Header,+,lib/signal_reader/signal_reader.h,, Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_adc.h,, Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_bus.h,, Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_comp.h,, @@ -166,6 +171,7 @@ Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_utils.h,, Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_wwdg.h,, Header,+,lib/toolbox/api_lock.h,, Header,+,lib/toolbox/args.h,, +Header,+,lib/toolbox/bit_buffer.h,, Header,+,lib/toolbox/compress.h,, Header,+,lib/toolbox/crc32_calc.h,, Header,+,lib/toolbox/dir_walk.h,, @@ -180,6 +186,7 @@ Header,+,lib/toolbox/pretty_format.h,, Header,+,lib/toolbox/protocols/protocol_dict.h,, Header,+,lib/toolbox/saved_struct.h,, Header,+,lib/toolbox/sha256.h,, +Header,+,lib/toolbox/simple_array.h,, Header,+,lib/toolbox/stream/buffered_file_stream.h,, Header,+,lib/toolbox/stream/file_stream.h,, Header,+,lib/toolbox/stream/stream.h,, @@ -522,6 +529,36 @@ Function,-,atoll,long long,const char* Function,-,basename,char*,const char* Function,-,bcmp,int,"const void*, const void*, size_t" Function,-,bcopy,void,"const void*, void*, size_t" +Function,+,bit_buffer_alloc,BitBuffer*,size_t +Function,+,bit_buffer_append,void,"BitBuffer*, const BitBuffer*" +Function,+,bit_buffer_append_bit,void,"BitBuffer*, _Bool" +Function,+,bit_buffer_append_byte,void,"BitBuffer*, uint8_t" +Function,+,bit_buffer_append_bytes,void,"BitBuffer*, const uint8_t*, size_t" +Function,+,bit_buffer_append_right,void,"BitBuffer*, const BitBuffer*, size_t" +Function,+,bit_buffer_copy,void,"BitBuffer*, const BitBuffer*" +Function,+,bit_buffer_copy_bits,void,"BitBuffer*, const uint8_t*, size_t" +Function,+,bit_buffer_copy_bytes,void,"BitBuffer*, const uint8_t*, size_t" +Function,+,bit_buffer_copy_bytes_with_parity,void,"BitBuffer*, const uint8_t*, size_t" +Function,+,bit_buffer_copy_left,void,"BitBuffer*, const BitBuffer*, size_t" +Function,+,bit_buffer_copy_right,void,"BitBuffer*, const BitBuffer*, size_t" +Function,+,bit_buffer_free,void,BitBuffer* +Function,+,bit_buffer_get_byte,uint8_t,"const BitBuffer*, size_t" +Function,+,bit_buffer_get_byte_from_bit,uint8_t,"const BitBuffer*, size_t" +Function,+,bit_buffer_get_capacity_bytes,size_t,const BitBuffer* +Function,+,bit_buffer_get_data,const uint8_t*,const BitBuffer* +Function,+,bit_buffer_get_parity,const uint8_t*,const BitBuffer* +Function,+,bit_buffer_get_size,size_t,const BitBuffer* +Function,+,bit_buffer_get_size_bytes,size_t,const BitBuffer* +Function,+,bit_buffer_has_partial_byte,_Bool,const BitBuffer* +Function,+,bit_buffer_reset,void,BitBuffer* +Function,+,bit_buffer_set_byte,void,"BitBuffer*, size_t, uint8_t" +Function,+,bit_buffer_set_byte_with_parity,void,"BitBuffer*, size_t, uint8_t, _Bool" +Function,+,bit_buffer_set_size,void,"BitBuffer*, size_t" +Function,+,bit_buffer_set_size_bytes,void,"BitBuffer*, size_t" +Function,+,bit_buffer_starts_with_byte,_Bool,"const BitBuffer*, uint8_t" +Function,+,bit_buffer_write_bytes,void,"const BitBuffer*, void*, size_t" +Function,+,bit_buffer_write_bytes_mid,void,"const BitBuffer*, void*, size_t, size_t" +Function,+,bit_buffer_write_bytes_with_parity,void,"const BitBuffer*, void*, size_t, size_t*" Function,+,ble_app_get_key_storage_buff,void,"uint8_t**, uint16_t*" Function,+,ble_app_init,_Bool, Function,+,ble_app_thread_stop,void, @@ -679,24 +716,19 @@ Function,+,dialog_message_set_text,void,"DialogMessage*, const char*, uint8_t, u Function,+,dialog_message_show,DialogMessageButton,"DialogsApp*, const DialogMessage*" Function,+,dialog_message_show_storage_error,void,"DialogsApp*, const char*" Function,-,difftime,double,"time_t, time_t" -Function,-,digital_sequence_add,void,"DigitalSequence*, uint8_t" +Function,+,digital_sequence_add_signal,void,"DigitalSequence*, uint8_t" Function,-,digital_sequence_alloc,DigitalSequence*,"uint32_t, const GpioPin*" Function,-,digital_sequence_clear,void,DigitalSequence* Function,-,digital_sequence_free,void,DigitalSequence* -Function,-,digital_sequence_send,_Bool,DigitalSequence* -Function,-,digital_sequence_set_sendtime,void,"DigitalSequence*, uint32_t" -Function,-,digital_sequence_set_signal,void,"DigitalSequence*, uint8_t, DigitalSignal*" -Function,-,digital_sequence_timebase_correction,void,"DigitalSequence*, float" -Function,-,digital_signal_add,void,"DigitalSignal*, uint32_t" -Function,-,digital_signal_add_pulse,void,"DigitalSignal*, uint32_t, _Bool" +Function,+,digital_sequence_register_signal,void,"DigitalSequence*, uint8_t, const DigitalSignal*" +Function,+,digital_sequence_transmit,void,DigitalSequence* +Function,+,digital_signal_add_period,void,"DigitalSignal*, uint32_t" +Function,+,digital_signal_add_period_with_level,void,"DigitalSignal*, uint32_t, _Bool" Function,-,digital_signal_alloc,DigitalSignal*,uint32_t -Function,-,digital_signal_append,_Bool,"DigitalSignal*, DigitalSignal*" Function,-,digital_signal_free,void,DigitalSignal* -Function,-,digital_signal_get_edge,uint32_t,"DigitalSignal*, uint32_t" -Function,-,digital_signal_get_edges_cnt,uint32_t,DigitalSignal* -Function,-,digital_signal_get_start_level,_Bool,DigitalSignal* -Function,-,digital_signal_prepare_arr,void,DigitalSignal* -Function,-,digital_signal_send,void,"DigitalSignal*, const GpioPin*" +Function,+,digital_signal_get_size,uint32_t,const DigitalSignal* +Function,+,digital_signal_get_start_level,_Bool,const DigitalSignal* +Function,+,digital_signal_set_start_level,void,"DigitalSignal*, _Bool" Function,-,diprintf,int,"int, const char*, ..." Function,+,dir_walk_alloc,DirWalk*,Storage* Function,+,dir_walk_close,void,DirWalk* @@ -1777,6 +1809,35 @@ Function,+,path_extract_dirname,void,"const char*, FuriString*" Function,+,path_extract_extension,void,"FuriString*, char*, size_t" Function,+,path_extract_filename,void,"FuriString*, FuriString*, _Bool" Function,+,path_extract_filename_no_ext,void,"const char*, FuriString*" +Function,+,pb_close_string_substream,_Bool,"pb_istream_t*, pb_istream_t*" +Function,+,pb_decode,_Bool,"pb_istream_t*, const pb_msgdesc_t*, void*" +Function,+,pb_decode_bool,_Bool,"pb_istream_t*, _Bool*" +Function,+,pb_decode_ex,_Bool,"pb_istream_t*, const pb_msgdesc_t*, void*, unsigned int" +Function,+,pb_decode_fixed32,_Bool,"pb_istream_t*, void*" +Function,+,pb_decode_fixed64,_Bool,"pb_istream_t*, void*" +Function,+,pb_decode_svarint,_Bool,"pb_istream_t*, int64_t*" +Function,+,pb_decode_tag,_Bool,"pb_istream_t*, pb_wire_type_t*, uint32_t*, _Bool*" +Function,+,pb_decode_varint,_Bool,"pb_istream_t*, uint64_t*" +Function,+,pb_decode_varint32,_Bool,"pb_istream_t*, uint32_t*" +Function,+,pb_default_field_callback,_Bool,"pb_istream_t*, pb_ostream_t*, const pb_field_t*" +Function,+,pb_encode,_Bool,"pb_ostream_t*, const pb_msgdesc_t*, const void*" +Function,+,pb_encode_ex,_Bool,"pb_ostream_t*, const pb_msgdesc_t*, const void*, unsigned int" +Function,+,pb_encode_fixed32,_Bool,"pb_ostream_t*, const void*" +Function,+,pb_encode_fixed64,_Bool,"pb_ostream_t*, const void*" +Function,+,pb_encode_string,_Bool,"pb_ostream_t*, const pb_byte_t*, size_t" +Function,+,pb_encode_submessage,_Bool,"pb_ostream_t*, const pb_msgdesc_t*, const void*" +Function,+,pb_encode_svarint,_Bool,"pb_ostream_t*, int64_t" +Function,+,pb_encode_tag,_Bool,"pb_ostream_t*, pb_wire_type_t, uint32_t" +Function,+,pb_encode_tag_for_field,_Bool,"pb_ostream_t*, const pb_field_iter_t*" +Function,+,pb_encode_varint,_Bool,"pb_ostream_t*, uint64_t" +Function,+,pb_get_encoded_size,_Bool,"size_t*, const pb_msgdesc_t*, const void*" +Function,+,pb_istream_from_buffer,pb_istream_t,"const pb_byte_t*, size_t" +Function,+,pb_make_string_substream,_Bool,"pb_istream_t*, pb_istream_t*" +Function,+,pb_ostream_from_buffer,pb_ostream_t,"pb_byte_t*, size_t" +Function,+,pb_read,_Bool,"pb_istream_t*, pb_byte_t*, size_t" +Function,+,pb_release,void,"const pb_msgdesc_t*, void*" +Function,+,pb_skip_field,_Bool,"pb_istream_t*, pb_wire_type_t" +Function,+,pb_write,_Bool,"pb_ostream_t*, const pb_byte_t*, size_t" Function,-,pcTaskGetName,char*,TaskHandle_t Function,-,pcTimerGetName,const char*,TimerHandle_t Function,-,pclose,int,FILE* @@ -1955,6 +2016,25 @@ Function,+,sha256_finish,void,"sha256_context*, unsigned char[32]" Function,+,sha256_process,void,sha256_context* Function,+,sha256_start,void,sha256_context* Function,+,sha256_update,void,"sha256_context*, const unsigned char*, unsigned int" +Function,+,signal_reader_alloc,SignalReader*,"const GpioPin*, uint32_t" +Function,+,signal_reader_free,void,SignalReader* +Function,+,signal_reader_set_polarity,void,"SignalReader*, SignalReaderPolarity" +Function,+,signal_reader_set_pull,void,"SignalReader*, GpioPull" +Function,+,signal_reader_set_sample_rate,void,"SignalReader*, SignalReaderTimeUnit, uint32_t" +Function,+,signal_reader_set_trigger,void,"SignalReader*, SignalReaderTrigger" +Function,+,signal_reader_start,void,"SignalReader*, SignalReaderCallback, void*" +Function,+,signal_reader_stop,void,SignalReader* +Function,+,simple_array_alloc,SimpleArray*,const SimpleArrayConfig* +Function,+,simple_array_cget,const SimpleArrayElement*,"const SimpleArray*, uint32_t" +Function,+,simple_array_cget_data,const SimpleArrayData*,const SimpleArray* +Function,+,simple_array_copy,void,"SimpleArray*, const SimpleArray*" +Function,+,simple_array_free,void,SimpleArray* +Function,+,simple_array_get,SimpleArrayElement*,"SimpleArray*, uint32_t" +Function,+,simple_array_get_count,uint32_t,const SimpleArray* +Function,+,simple_array_get_data,SimpleArrayData*,SimpleArray* +Function,+,simple_array_init,void,"SimpleArray*, uint32_t" +Function,+,simple_array_is_equal,_Bool,"const SimpleArray*, const SimpleArray*" +Function,+,simple_array_reset,void,SimpleArray* Function,-,sin,double,double Function,-,sincos,void,"double, double*, double*" Function,-,sincosf,void,"float, float*, float*" @@ -2696,6 +2776,7 @@ Variable,+,sequence_set_vibro_on,const NotificationSequence, Variable,+,sequence_single_vibro,const NotificationSequence, Variable,+,sequence_solid_yellow,const NotificationSequence, Variable,+,sequence_success,const NotificationSequence, +Variable,+,simple_array_config_uint8_t,const SimpleArrayConfig, Variable,-,suboptarg,char*, Variable,+,usb_ccid,FuriHalUsbInterface, Variable,+,usb_cdc_dual,FuriHalUsbInterface, diff --git a/firmware/targets/f18/target.json b/firmware/targets/f18/target.json index 2d14813f6da..19de9dd6156 100644 --- a/firmware/targets/f18/target.json +++ b/firmware/targets/f18/target.json @@ -20,6 +20,8 @@ "littlefs", "flipperformat", "toolbox", + "digital_signal", + "signal_reader", "microtar", "usb_stm32", "appframe", @@ -31,10 +33,18 @@ "flipperformat", "toolbox", "flipper18" + ], "excluded_sources": [ "furi_hal_infrared.c", "furi_hal_nfc.c", + "furi_hal_nfc_timer.c", + "furi_hal_nfc_irq.c", + "furi_hal_nfc_event.c", + "furi_hal_nfc_iso15693.c", + "furi_hal_nfc_iso14443a.c", + "furi_hal_nfc_iso14443b.c", + "furi_hal_nfc_felica.c", "furi_hal_rfid.c", "furi_hal_subghz.c" ], @@ -51,7 +61,6 @@ "lfrfid", "subghz", "ibutton", - "infrared", - "st25rfal002" + "infrared" ] -} \ No newline at end of file +} diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index eb997e64b17..3ae099b5f14 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,39.2,, +Version,+,40.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -48,7 +48,6 @@ Header,+,firmware/targets/f7/furi_hal/furi_hal_i2c_types.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_ibutton.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_idle_timer.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_interrupt.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_nfc.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_os.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_pwm.h,, Header,+,firmware/targets/f7/furi_hal/furi_hal_resources.h,, @@ -74,6 +73,7 @@ Header,+,firmware/targets/furi_hal_include/furi_hal_infrared.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_light.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_memory.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_mpu.h,, +Header,+,firmware/targets/furi_hal_include/furi_hal_nfc.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_power.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_random.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_region.h,, @@ -87,6 +87,7 @@ Header,+,firmware/targets/furi_hal_include/furi_hal_usb_hid.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_usb_hid_u2f.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_version.h,, Header,+,firmware/targets/furi_hal_include/furi_hal_vibro.h,, +Header,+,lib/digital_signal/digital_sequence.h,, Header,+,lib/digital_signal/digital_signal.h,, Header,+,lib/drivers/cc1101_regs.h,, Header,+,lib/flipper_application/api_hashtable/api_hashtable.h,, @@ -150,18 +151,49 @@ Header,+,lib/mlib/m-rbtree.h,, Header,+,lib/mlib/m-tuple.h,, Header,+,lib/mlib/m-variant.h,, Header,+,lib/music_worker/music_worker.h,, -Header,+,lib/nfc/helpers/mfkey32.h,, -Header,+,lib/nfc/helpers/nfc_generators.h,, +Header,+,lib/nanopb/pb.h,, +Header,+,lib/nanopb/pb_decode.h,, +Header,+,lib/nanopb/pb_encode.h,, +Header,+,lib/nfc/helpers/iso13239_crc.h,, +Header,+,lib/nfc/helpers/iso14443_crc.h,, +Header,+,lib/nfc/helpers/nfc_data_generator.h,, +Header,+,lib/nfc/helpers/nfc_dict.h,, +Header,+,lib/nfc/helpers/nfc_util.h,, +Header,+,lib/nfc/nfc.h,, Header,+,lib/nfc/nfc_device.h,, -Header,+,lib/nfc/nfc_types.h,, -Header,+,lib/nfc/nfc_worker.h,, -Header,+,lib/nfc/parsers/nfc_supported_card.h,, -Header,+,lib/nfc/protocols/nfc_util.h,, +Header,+,lib/nfc/nfc_listener.h,, +Header,+,lib/nfc/nfc_poller.h,, +Header,+,lib/nfc/nfc_scanner.h,, +Header,+,lib/nfc/protocols/iso14443_3a/iso14443_3a.h,, +Header,+,lib/nfc/protocols/iso14443_3a/iso14443_3a_listener.h,, +Header,+,lib/nfc/protocols/iso14443_3a/iso14443_3a_poller.h,, +Header,+,lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_sync_api.h,, +Header,+,lib/nfc/protocols/iso14443_3b/iso14443_3b.h,, +Header,+,lib/nfc/protocols/iso14443_3b/iso14443_3b_poller.h,, +Header,+,lib/nfc/protocols/iso14443_4a/iso14443_4a.h,, +Header,+,lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.h,, +Header,+,lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.h,, +Header,+,lib/nfc/protocols/iso14443_4b/iso14443_4b.h,, +Header,+,lib/nfc/protocols/iso14443_4b/iso14443_4b_poller.h,, +Header,+,lib/nfc/protocols/mf_classic/mf_classic.h,, +Header,+,lib/nfc/protocols/mf_classic/mf_classic_listener.h,, +Header,+,lib/nfc/protocols/mf_classic/mf_classic_poller.h,, +Header,+,lib/nfc/protocols/mf_classic/mf_classic_poller_sync_api.h,, +Header,+,lib/nfc/protocols/mf_desfire/mf_desfire.h,, +Header,+,lib/nfc/protocols/mf_desfire/mf_desfire_poller.h,, +Header,+,lib/nfc/protocols/mf_ultralight/mf_ultralight.h,, +Header,+,lib/nfc/protocols/mf_ultralight/mf_ultralight_listener.h,, +Header,+,lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h,, +Header,+,lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync_api.h,, +Header,+,lib/nfc/protocols/slix/slix.h,, +Header,+,lib/nfc/protocols/st25tb/st25tb.h,, +Header,+,lib/nfc/protocols/st25tb/st25tb_poller.h,, Header,+,lib/one_wire/maxim_crc.h,, Header,+,lib/one_wire/one_wire_host.h,, Header,+,lib/one_wire/one_wire_slave.h,, Header,+,lib/print/wrappers.h,, Header,+,lib/pulse_reader/pulse_reader.h,, +Header,+,lib/signal_reader/signal_reader.h,, Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_adc.h,, Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_bus.h,, Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_comp.h,, @@ -207,6 +239,7 @@ Header,+,lib/subghz/subghz_worker.h,, Header,+,lib/subghz/transmitter.h,, Header,+,lib/toolbox/api_lock.h,, Header,+,lib/toolbox/args.h,, +Header,+,lib/toolbox/bit_buffer.h,, Header,+,lib/toolbox/compress.h,, Header,+,lib/toolbox/crc32_calc.h,, Header,+,lib/toolbox/dir_walk.h,, @@ -221,6 +254,7 @@ Header,+,lib/toolbox/pretty_format.h,, Header,+,lib/toolbox/protocols/protocol_dict.h,, Header,+,lib/toolbox/saved_struct.h,, Header,+,lib/toolbox/sha256.h,, +Header,+,lib/toolbox/simple_array.h,, Header,+,lib/toolbox/stream/buffered_file_stream.h,, Header,+,lib/toolbox/stream/file_stream.h,, Header,+,lib/toolbox/stream/stream.h,, @@ -563,6 +597,36 @@ Function,-,atoll,long long,const char* Function,-,basename,char*,const char* Function,-,bcmp,int,"const void*, const void*, size_t" Function,-,bcopy,void,"const void*, void*, size_t" +Function,+,bit_buffer_alloc,BitBuffer*,size_t +Function,+,bit_buffer_append,void,"BitBuffer*, const BitBuffer*" +Function,+,bit_buffer_append_bit,void,"BitBuffer*, _Bool" +Function,+,bit_buffer_append_byte,void,"BitBuffer*, uint8_t" +Function,+,bit_buffer_append_bytes,void,"BitBuffer*, const uint8_t*, size_t" +Function,+,bit_buffer_append_right,void,"BitBuffer*, const BitBuffer*, size_t" +Function,+,bit_buffer_copy,void,"BitBuffer*, const BitBuffer*" +Function,+,bit_buffer_copy_bits,void,"BitBuffer*, const uint8_t*, size_t" +Function,+,bit_buffer_copy_bytes,void,"BitBuffer*, const uint8_t*, size_t" +Function,+,bit_buffer_copy_bytes_with_parity,void,"BitBuffer*, const uint8_t*, size_t" +Function,+,bit_buffer_copy_left,void,"BitBuffer*, const BitBuffer*, size_t" +Function,+,bit_buffer_copy_right,void,"BitBuffer*, const BitBuffer*, size_t" +Function,+,bit_buffer_free,void,BitBuffer* +Function,+,bit_buffer_get_byte,uint8_t,"const BitBuffer*, size_t" +Function,+,bit_buffer_get_byte_from_bit,uint8_t,"const BitBuffer*, size_t" +Function,+,bit_buffer_get_capacity_bytes,size_t,const BitBuffer* +Function,+,bit_buffer_get_data,const uint8_t*,const BitBuffer* +Function,+,bit_buffer_get_parity,const uint8_t*,const BitBuffer* +Function,+,bit_buffer_get_size,size_t,const BitBuffer* +Function,+,bit_buffer_get_size_bytes,size_t,const BitBuffer* +Function,+,bit_buffer_has_partial_byte,_Bool,const BitBuffer* +Function,+,bit_buffer_reset,void,BitBuffer* +Function,+,bit_buffer_set_byte,void,"BitBuffer*, size_t, uint8_t" +Function,+,bit_buffer_set_byte_with_parity,void,"BitBuffer*, size_t, uint8_t, _Bool" +Function,+,bit_buffer_set_size,void,"BitBuffer*, size_t" +Function,+,bit_buffer_set_size_bytes,void,"BitBuffer*, size_t" +Function,+,bit_buffer_starts_with_byte,_Bool,"const BitBuffer*, uint8_t" +Function,+,bit_buffer_write_bytes,void,"const BitBuffer*, void*, size_t" +Function,+,bit_buffer_write_bytes_mid,void,"const BitBuffer*, void*, size_t, size_t" +Function,+,bit_buffer_write_bytes_with_parity,void,"const BitBuffer*, void*, size_t, size_t*" Function,+,bit_lib_add_parity,size_t,"const uint8_t*, size_t, uint8_t*, size_t, uint8_t, uint8_t, BitLibParity" Function,+,bit_lib_copy_bits,void,"uint8_t*, size_t, size_t, const uint8_t*, size_t" Function,+,bit_lib_crc16,uint16_t,"const uint8_t*, size_t, uint16_t, uint16_t, _Bool, _Bool, uint16_t" @@ -711,14 +775,6 @@ Function,-,coshl,long double,long double Function,-,cosl,long double,long double Function,+,crc32_calc_buffer,uint32_t,"uint32_t, const void*, size_t" Function,+,crc32_calc_file,uint32_t,"File*, const FileCrcProgressCb, void*" -Function,-,crypto1_bit,uint8_t,"Crypto1*, uint8_t, int" -Function,-,crypto1_byte,uint8_t,"Crypto1*, uint8_t, int" -Function,-,crypto1_decrypt,void,"Crypto1*, uint8_t*, uint16_t, uint8_t*" -Function,-,crypto1_encrypt,void,"Crypto1*, uint8_t*, uint8_t*, uint16_t, uint8_t*, uint8_t*" -Function,-,crypto1_filter,uint32_t,uint32_t -Function,-,crypto1_init,void,"Crypto1*, uint64_t" -Function,-,crypto1_reset,void,Crypto1* -Function,-,crypto1_word,uint32_t,"Crypto1*, uint32_t, int" Function,-,ctermid,char*,char* Function,-,ctime,char*,const time_t* Function,-,ctime_r,char*,"const time_t*, char*" @@ -748,24 +804,19 @@ Function,+,dialog_message_set_text,void,"DialogMessage*, const char*, uint8_t, u Function,+,dialog_message_show,DialogMessageButton,"DialogsApp*, const DialogMessage*" Function,+,dialog_message_show_storage_error,void,"DialogsApp*, const char*" Function,-,difftime,double,"time_t, time_t" -Function,-,digital_sequence_add,void,"DigitalSequence*, uint8_t" +Function,+,digital_sequence_add_signal,void,"DigitalSequence*, uint8_t" Function,-,digital_sequence_alloc,DigitalSequence*,"uint32_t, const GpioPin*" Function,-,digital_sequence_clear,void,DigitalSequence* Function,-,digital_sequence_free,void,DigitalSequence* -Function,-,digital_sequence_send,_Bool,DigitalSequence* -Function,-,digital_sequence_set_sendtime,void,"DigitalSequence*, uint32_t" -Function,-,digital_sequence_set_signal,void,"DigitalSequence*, uint8_t, DigitalSignal*" -Function,-,digital_sequence_timebase_correction,void,"DigitalSequence*, float" -Function,-,digital_signal_add,void,"DigitalSignal*, uint32_t" -Function,-,digital_signal_add_pulse,void,"DigitalSignal*, uint32_t, _Bool" +Function,+,digital_sequence_register_signal,void,"DigitalSequence*, uint8_t, const DigitalSignal*" +Function,+,digital_sequence_transmit,void,DigitalSequence* +Function,+,digital_signal_add_period,void,"DigitalSignal*, uint32_t" +Function,+,digital_signal_add_period_with_level,void,"DigitalSignal*, uint32_t, _Bool" Function,-,digital_signal_alloc,DigitalSignal*,uint32_t -Function,-,digital_signal_append,_Bool,"DigitalSignal*, DigitalSignal*" Function,-,digital_signal_free,void,DigitalSignal* -Function,-,digital_signal_get_edge,uint32_t,"DigitalSignal*, uint32_t" -Function,-,digital_signal_get_edges_cnt,uint32_t,DigitalSignal* -Function,-,digital_signal_get_start_level,_Bool,DigitalSignal* -Function,-,digital_signal_prepare_arr,void,DigitalSignal* -Function,-,digital_signal_send,void,"DigitalSignal*, const GpioPin*" +Function,+,digital_signal_get_size,uint32_t,const DigitalSignal* +Function,+,digital_signal_get_start_level,_Bool,const DigitalSignal* +Function,+,digital_signal_set_start_level,void,"DigitalSignal*, _Bool" Function,-,diprintf,int,"int, const char*, ..." Function,+,dir_walk_alloc,DirWalk*,Storage* Function,+,dir_walk_close,void,DirWalk* @@ -814,8 +865,6 @@ Function,+,elf_symbolname_hash,uint32_t,const char* Function,+,empty_screen_alloc,EmptyScreen*, Function,+,empty_screen_free,void,EmptyScreen* Function,+,empty_screen_get_view,View*,EmptyScreen* -Function,-,emv_card_emulation,_Bool,FuriHalNfcTxRxContext* -Function,-,emv_read_bank_card,_Bool,"FuriHalNfcTxRxContext*, EmvApplication*" Function,-,erand48,double,unsigned short[3] Function,-,erf,double,double Function,-,erfc,double,double @@ -1234,38 +1283,44 @@ Function,-,furi_hal_mpu_init,void, Function,+,furi_hal_mpu_protect_disable,void,FuriHalMpuRegion Function,+,furi_hal_mpu_protect_no_access,void,"FuriHalMpuRegion, uint32_t, FuriHalMPURegionSize" Function,+,furi_hal_mpu_protect_read_only,void,"FuriHalMpuRegion, uint32_t, FuriHalMPURegionSize" -Function,+,furi_hal_nfc_activate_nfca,_Bool,"uint32_t, uint32_t*" -Function,-,furi_hal_nfc_deinit,void, -Function,+,furi_hal_nfc_detect,_Bool,"FuriHalNfcDevData*, uint32_t" -Function,+,furi_hal_nfc_emulate_nfca,_Bool,"uint8_t*, uint8_t, uint8_t*, uint8_t, FuriHalNfcEmulateCallback, void*, uint32_t" -Function,+,furi_hal_nfc_exit_sleep,void, -Function,+,furi_hal_nfc_field_detect_start,void, +Function,+,furi_hal_nfc_abort,FuriHalNfcError, +Function,+,furi_hal_nfc_acquire,FuriHalNfcError, +Function,+,furi_hal_nfc_event_start,FuriHalNfcError, +Function,+,furi_hal_nfc_event_stop,FuriHalNfcError, +Function,+,furi_hal_nfc_field_detect_start,FuriHalNfcError, +Function,+,furi_hal_nfc_field_detect_stop,FuriHalNfcError, Function,+,furi_hal_nfc_field_is_present,_Bool, -Function,+,furi_hal_nfc_field_off,void, -Function,+,furi_hal_nfc_field_on,void, -Function,-,furi_hal_nfc_init,void, -Function,+,furi_hal_nfc_is_busy,_Bool, -Function,+,furi_hal_nfc_is_init,_Bool, -Function,+,furi_hal_nfc_listen,_Bool,"uint8_t*, uint8_t, uint8_t*, uint8_t, _Bool, uint32_t" -Function,+,furi_hal_nfc_listen_rx,_Bool,"FuriHalNfcTxRxContext*, uint32_t" -Function,+,furi_hal_nfc_listen_sleep,void, -Function,+,furi_hal_nfc_listen_start,void,FuriHalNfcDevData* -Function,+,furi_hal_nfc_ll_poll,void, -Function,+,furi_hal_nfc_ll_set_error_handling,void,FuriHalNfcErrorHandling -Function,+,furi_hal_nfc_ll_set_fdt_listen,void,uint32_t -Function,+,furi_hal_nfc_ll_set_fdt_poll,void,uint32_t -Function,+,furi_hal_nfc_ll_set_guard_time,void,uint32_t -Function,+,furi_hal_nfc_ll_set_mode,FuriHalNfcReturn,"FuriHalNfcMode, FuriHalNfcBitrate, FuriHalNfcBitrate" -Function,+,furi_hal_nfc_ll_txrx,FuriHalNfcReturn,"uint8_t*, uint16_t, uint8_t*, uint16_t, uint16_t*, uint32_t, uint32_t" -Function,+,furi_hal_nfc_ll_txrx_bits,FuriHalNfcReturn,"uint8_t*, uint16_t, uint8_t*, uint16_t, uint16_t*, uint32_t, uint32_t" -Function,+,furi_hal_nfc_ll_txrx_off,void, -Function,+,furi_hal_nfc_ll_txrx_on,void, -Function,+,furi_hal_nfc_sleep,void, -Function,+,furi_hal_nfc_start_sleep,void, -Function,+,furi_hal_nfc_stop,void, -Function,+,furi_hal_nfc_stop_cmd,void, -Function,+,furi_hal_nfc_tx_rx,_Bool,"FuriHalNfcTxRxContext*, uint16_t" -Function,+,furi_hal_nfc_tx_rx_full,_Bool,FuriHalNfcTxRxContext* +Function,+,furi_hal_nfc_init,FuriHalNfcError, +Function,+,furi_hal_nfc_is_hal_ready,FuriHalNfcError, +Function,+,furi_hal_nfc_iso14443a_listener_set_col_res_data,FuriHalNfcError,"uint8_t*, uint8_t, uint8_t*, uint8_t" +Function,+,furi_hal_nfc_iso14443a_listener_tx_custom_parity,FuriHalNfcError,"const uint8_t*, const uint8_t*, size_t" +Function,+,furi_hal_nfc_iso14443a_poller_trx_short_frame,FuriHalNfcError,FuriHalNfcaShortFrame +Function,+,furi_hal_nfc_iso14443a_poller_tx_custom_parity,FuriHalNfcError,"const uint8_t*, size_t" +Function,+,furi_hal_nfc_iso14443a_rx_sdd_frame,FuriHalNfcError,"uint8_t*, size_t, size_t*" +Function,+,furi_hal_nfc_iso14443a_tx_sdd_frame,FuriHalNfcError,"const uint8_t*, size_t" +Function,+,furi_hal_nfc_iso15693_listener_tx_sof,FuriHalNfcError, +Function,+,furi_hal_nfc_listener_enable_rx,FuriHalNfcError, +Function,+,furi_hal_nfc_listener_idle,FuriHalNfcError, +Function,+,furi_hal_nfc_listener_rx,FuriHalNfcError,"uint8_t*, size_t, size_t*" +Function,+,furi_hal_nfc_listener_sleep,FuriHalNfcError, +Function,+,furi_hal_nfc_listener_tx,FuriHalNfcError,"const uint8_t*, size_t" +Function,+,furi_hal_nfc_listener_wait_event,FuriHalNfcEvent,uint32_t +Function,+,furi_hal_nfc_low_power_mode_start,FuriHalNfcError, +Function,+,furi_hal_nfc_low_power_mode_stop,FuriHalNfcError, +Function,+,furi_hal_nfc_poller_field_on,FuriHalNfcError, +Function,+,furi_hal_nfc_poller_rx,FuriHalNfcError,"uint8_t*, size_t, size_t*" +Function,+,furi_hal_nfc_poller_tx,FuriHalNfcError,"const uint8_t*, size_t" +Function,+,furi_hal_nfc_poller_wait_event,FuriHalNfcEvent,uint32_t +Function,+,furi_hal_nfc_release,FuriHalNfcError, +Function,+,furi_hal_nfc_reset_mode,FuriHalNfcError, +Function,+,furi_hal_nfc_set_mode,FuriHalNfcError,"FuriHalNfcMode, FuriHalNfcTech" +Function,+,furi_hal_nfc_timer_block_tx_is_running,_Bool, +Function,+,furi_hal_nfc_timer_block_tx_start,void,uint32_t +Function,+,furi_hal_nfc_timer_block_tx_start_us,void,uint32_t +Function,+,furi_hal_nfc_timer_block_tx_stop,void, +Function,+,furi_hal_nfc_timer_fwt_start,void,uint32_t +Function,+,furi_hal_nfc_timer_fwt_stop,void, +Function,+,furi_hal_nfc_trx_reset,FuriHalNfcError, Function,-,furi_hal_os_init,void, Function,+,furi_hal_os_tick,void, Function,+,furi_hal_power_check_otg_fault,_Bool, @@ -1791,6 +1846,97 @@ Function,-,islower,int,int Function,-,islower_l,int,"int, locale_t" Function,-,isnan,int,double Function,-,isnanf,int,float +Function,+,iso13239_crc_append,void,"Iso13239CrcType, BitBuffer*" +Function,+,iso13239_crc_check,_Bool,"Iso13239CrcType, const BitBuffer*" +Function,+,iso13239_crc_trim,void,BitBuffer* +Function,+,iso14443_3a_alloc,Iso14443_3aData*, +Function,+,iso14443_3a_copy,void,"Iso14443_3aData*, const Iso14443_3aData*" +Function,+,iso14443_3a_free,void,Iso14443_3aData* +Function,+,iso14443_3a_get_atqa,void,"const Iso14443_3aData*, uint8_t[2]" +Function,+,iso14443_3a_get_base_data,Iso14443_3aData*,const Iso14443_3aData* +Function,+,iso14443_3a_get_cuid,uint32_t,const Iso14443_3aData* +Function,+,iso14443_3a_get_device_name,const char*,"const Iso14443_3aData*, NfcDeviceNameType" +Function,+,iso14443_3a_get_sak,uint8_t,const Iso14443_3aData* +Function,+,iso14443_3a_get_uid,const uint8_t*,"const Iso14443_3aData*, size_t*" +Function,+,iso14443_3a_is_equal,_Bool,"const Iso14443_3aData*, const Iso14443_3aData*" +Function,+,iso14443_3a_load,_Bool,"Iso14443_3aData*, FlipperFormat*, uint32_t" +Function,+,iso14443_3a_poller_read,Iso14443_3aError,"Nfc*, Iso14443_3aData*" +Function,+,iso14443_3a_poller_send_standard_frame,Iso14443_3aError,"Iso14443_3aPoller*, const BitBuffer*, BitBuffer*, uint32_t" +Function,+,iso14443_3a_poller_txrx,Iso14443_3aError,"Iso14443_3aPoller*, const BitBuffer*, BitBuffer*, uint32_t" +Function,+,iso14443_3a_reset,void,Iso14443_3aData* +Function,+,iso14443_3a_save,_Bool,"const Iso14443_3aData*, FlipperFormat*" +Function,+,iso14443_3a_set_atqa,void,"Iso14443_3aData*, const uint8_t[2]" +Function,+,iso14443_3a_set_sak,void,"Iso14443_3aData*, uint8_t" +Function,+,iso14443_3a_set_uid,_Bool,"Iso14443_3aData*, const uint8_t*, size_t" +Function,+,iso14443_3a_supports_iso14443_4,_Bool,const Iso14443_3aData* +Function,+,iso14443_3a_verify,_Bool,"Iso14443_3aData*, const FuriString*" +Function,+,iso14443_3b_alloc,Iso14443_3bData*, +Function,+,iso14443_3b_copy,void,"Iso14443_3bData*, const Iso14443_3bData*" +Function,+,iso14443_3b_free,void,Iso14443_3bData* +Function,+,iso14443_3b_get_application_data,const uint8_t*,"const Iso14443_3bData*, size_t*" +Function,+,iso14443_3b_get_base_data,Iso14443_3bData*,const Iso14443_3bData* +Function,+,iso14443_3b_get_device_name,const char*,"const Iso14443_3bData*, NfcDeviceNameType" +Function,+,iso14443_3b_get_frame_size_max,uint16_t,const Iso14443_3bData* +Function,+,iso14443_3b_get_fwt_fc_max,uint32_t,const Iso14443_3bData* +Function,+,iso14443_3b_get_uid,const uint8_t*,"const Iso14443_3bData*, size_t*" +Function,+,iso14443_3b_is_equal,_Bool,"const Iso14443_3bData*, const Iso14443_3bData*" +Function,+,iso14443_3b_load,_Bool,"Iso14443_3bData*, FlipperFormat*, uint32_t" +Function,+,iso14443_3b_reset,void,Iso14443_3bData* +Function,+,iso14443_3b_save,_Bool,"const Iso14443_3bData*, FlipperFormat*" +Function,+,iso14443_3b_set_uid,_Bool,"Iso14443_3bData*, const uint8_t*, size_t" +Function,+,iso14443_3b_supports_bit_rate,_Bool,"const Iso14443_3bData*, Iso14443_3bBitRate" +Function,+,iso14443_3b_supports_frame_option,_Bool,"const Iso14443_3bData*, Iso14443_3bFrameOption" +Function,+,iso14443_3b_supports_iso14443_4,_Bool,const Iso14443_3bData* +Function,+,iso14443_3b_verify,_Bool,"Iso14443_3bData*, const FuriString*" +Function,+,iso14443_4a_alloc,Iso14443_4aData*, +Function,+,iso14443_4a_copy,void,"Iso14443_4aData*, const Iso14443_4aData*" +Function,+,iso14443_4a_free,void,Iso14443_4aData* +Function,+,iso14443_4a_get_base_data,Iso14443_3aData*,const Iso14443_4aData* +Function,+,iso14443_4a_get_device_name,const char*,"const Iso14443_4aData*, NfcDeviceNameType" +Function,+,iso14443_4a_get_frame_size_max,uint16_t,const Iso14443_4aData* +Function,+,iso14443_4a_get_fwt_fc_max,uint32_t,const Iso14443_4aData* +Function,+,iso14443_4a_get_historical_bytes,const uint8_t*,"const Iso14443_4aData*, uint32_t*" +Function,+,iso14443_4a_get_uid,const uint8_t*,"const Iso14443_4aData*, size_t*" +Function,+,iso14443_4a_is_equal,_Bool,"const Iso14443_4aData*, const Iso14443_4aData*" +Function,+,iso14443_4a_load,_Bool,"Iso14443_4aData*, FlipperFormat*, uint32_t" +Function,+,iso14443_4a_reset,void,Iso14443_4aData* +Function,+,iso14443_4a_save,_Bool,"const Iso14443_4aData*, FlipperFormat*" +Function,+,iso14443_4a_set_uid,_Bool,"Iso14443_4aData*, const uint8_t*, size_t" +Function,+,iso14443_4a_supports_bit_rate,_Bool,"const Iso14443_4aData*, Iso14443_4aBitRate" +Function,+,iso14443_4a_supports_frame_option,_Bool,"const Iso14443_4aData*, Iso14443_4aFrameOption" +Function,+,iso14443_4a_verify,_Bool,"Iso14443_4aData*, const FuriString*" +Function,+,iso14443_4b_alloc,Iso14443_4bData*, +Function,+,iso14443_4b_copy,void,"Iso14443_4bData*, const Iso14443_4bData*" +Function,+,iso14443_4b_free,void,Iso14443_4bData* +Function,+,iso14443_4b_get_base_data,Iso14443_3bData*,const Iso14443_4bData* +Function,+,iso14443_4b_get_device_name,const char*,"const Iso14443_4bData*, NfcDeviceNameType" +Function,+,iso14443_4b_get_uid,const uint8_t*,"const Iso14443_4bData*, size_t*" +Function,+,iso14443_4b_is_equal,_Bool,"const Iso14443_4bData*, const Iso14443_4bData*" +Function,+,iso14443_4b_load,_Bool,"Iso14443_4bData*, FlipperFormat*, uint32_t" +Function,+,iso14443_4b_reset,void,Iso14443_4bData* +Function,+,iso14443_4b_save,_Bool,"const Iso14443_4bData*, FlipperFormat*" +Function,+,iso14443_4b_set_uid,_Bool,"Iso14443_4bData*, const uint8_t*, size_t" +Function,+,iso14443_4b_verify,_Bool,"Iso14443_4bData*, const FuriString*" +Function,+,iso14443_crc_append,void,"Iso14443CrcType, BitBuffer*" +Function,+,iso14443_crc_check,_Bool,"Iso14443CrcType, const BitBuffer*" +Function,+,iso14443_crc_trim,void,BitBuffer* +Function,+,iso15693_3_alloc,Iso15693_3Data*, +Function,+,iso15693_3_copy,void,"Iso15693_3Data*, const Iso15693_3Data*" +Function,+,iso15693_3_free,void,Iso15693_3Data* +Function,+,iso15693_3_get_base_data,Iso15693_3Data*,const Iso15693_3Data* +Function,+,iso15693_3_get_block_count,uint16_t,const Iso15693_3Data* +Function,+,iso15693_3_get_block_data,const uint8_t*,"const Iso15693_3Data*, uint8_t" +Function,+,iso15693_3_get_block_size,uint8_t,const Iso15693_3Data* +Function,+,iso15693_3_get_device_name,const char*,"const Iso15693_3Data*, NfcDeviceNameType" +Function,+,iso15693_3_get_manufacturer_id,uint8_t,const Iso15693_3Data* +Function,+,iso15693_3_get_uid,const uint8_t*,"const Iso15693_3Data*, size_t*" +Function,+,iso15693_3_is_block_locked,_Bool,"const Iso15693_3Data*, uint8_t" +Function,+,iso15693_3_is_equal,_Bool,"const Iso15693_3Data*, const Iso15693_3Data*" +Function,+,iso15693_3_load,_Bool,"Iso15693_3Data*, FlipperFormat*, uint32_t" +Function,+,iso15693_3_reset,void,Iso15693_3Data* +Function,+,iso15693_3_save,_Bool,"const Iso15693_3Data*, FlipperFormat*" +Function,+,iso15693_3_set_uid,_Bool,"Iso15693_3Data*, const uint8_t*, size_t" +Function,+,iso15693_3_verify,_Bool,"Iso15693_3Data*, const FuriString*" Function,-,isprint,int,int Function,-,isprint_l,int,"int, locale_t" Function,-,ispunct,int,int @@ -1926,7 +2072,7 @@ Function,-,mbedtls_des_setkey_enc,int,"mbedtls_des_context*, const unsigned char Function,-,mbedtls_internal_sha1_process,int,"mbedtls_sha1_context*, const unsigned char[64]" Function,-,mbedtls_platform_gmtime_r,tm*,"const mbedtls_time_t*, tm*" Function,-,mbedtls_platform_zeroize,void,"void*, size_t" -Function,-,mbedtls_sha1,int,"const unsigned char*, size_t, unsigned char[20]" +Function,+,mbedtls_sha1,int,"const unsigned char*, size_t, unsigned char[20]" Function,-,mbedtls_sha1_clone,void,"mbedtls_sha1_context*, const mbedtls_sha1_context*" Function,-,mbedtls_sha1_finish,int,"mbedtls_sha1_context*, unsigned char[20]" Function,-,mbedtls_sha1_free,void,mbedtls_sha1_context* @@ -1968,120 +2114,93 @@ Function,+,menu_free,void,Menu* Function,+,menu_get_view,View*,Menu* Function,+,menu_reset,void,Menu* Function,+,menu_set_selected_item,void,"Menu*, uint32_t" -Function,-,mf_classic_auth_attempt,_Bool,"FuriHalNfcTxRxContext*, Crypto1*, MfClassicAuthContext*, uint64_t" -Function,-,mf_classic_auth_init_context,void,"MfClassicAuthContext*, uint8_t" -Function,-,mf_classic_auth_write_block,_Bool,"FuriHalNfcTxRxContext*, MfClassicBlock*, uint8_t, MfClassicKey, uint64_t" -Function,-,mf_classic_authenticate,_Bool,"FuriHalNfcTxRxContext*, uint8_t, uint64_t, MfClassicKey" -Function,-,mf_classic_authenticate_skip_activate,_Bool,"FuriHalNfcTxRxContext*, uint8_t, uint64_t, MfClassicKey, _Bool, uint32_t" -Function,-,mf_classic_block_to_value,_Bool,"const uint8_t*, int32_t*, uint8_t*" -Function,-,mf_classic_check_card_type,_Bool,"uint8_t, uint8_t, uint8_t" -Function,+,mf_classic_dict_add_key,_Bool,"MfClassicDict*, uint8_t*" -Function,-,mf_classic_dict_add_key_str,_Bool,"MfClassicDict*, FuriString*" -Function,+,mf_classic_dict_alloc,MfClassicDict*,MfClassicDictType -Function,+,mf_classic_dict_check_presence,_Bool,MfClassicDictType -Function,+,mf_classic_dict_delete_index,_Bool,"MfClassicDict*, uint32_t" -Function,-,mf_classic_dict_find_index,_Bool,"MfClassicDict*, uint8_t*, uint32_t*" -Function,-,mf_classic_dict_find_index_str,_Bool,"MfClassicDict*, FuriString*, uint32_t*" -Function,+,mf_classic_dict_free,void,MfClassicDict* -Function,-,mf_classic_dict_get_key_at_index,_Bool,"MfClassicDict*, uint64_t*, uint32_t" -Function,+,mf_classic_dict_get_key_at_index_str,_Bool,"MfClassicDict*, FuriString*, uint32_t" -Function,-,mf_classic_dict_get_next_key,_Bool,"MfClassicDict*, uint64_t*" -Function,+,mf_classic_dict_get_next_key_str,_Bool,"MfClassicDict*, FuriString*" -Function,+,mf_classic_dict_get_total_keys,uint32_t,MfClassicDict* -Function,+,mf_classic_dict_is_key_present,_Bool,"MfClassicDict*, uint8_t*" -Function,-,mf_classic_dict_is_key_present_str,_Bool,"MfClassicDict*, FuriString*" -Function,-,mf_classic_dict_rewind,_Bool,MfClassicDict* -Function,-,mf_classic_emulator,_Bool,"MfClassicEmulator*, FuriHalNfcTxRxContext*, _Bool" -Function,-,mf_classic_get_classic_type,MfClassicType,"uint8_t, uint8_t, uint8_t" -Function,+,mf_classic_get_read_sectors_and_keys,void,"MfClassicData*, uint8_t*, uint8_t*" +Function,+,mf_classic_alloc,MfClassicData*, +Function,+,mf_classic_block_to_value,_Bool,"const MfClassicBlock*, int32_t*, uint8_t*" +Function,+,mf_classic_copy,void,"MfClassicData*, const MfClassicData*" +Function,+,mf_classic_free,void,MfClassicData* +Function,+,mf_classic_get_base_data,Iso14443_3aData*,const MfClassicData* +Function,+,mf_classic_get_blocks_num_in_sector,uint8_t,uint8_t +Function,+,mf_classic_get_device_name,const char*,"const MfClassicData*, NfcDeviceNameType" +Function,+,mf_classic_get_first_block_num_of_sector,uint8_t,uint8_t +Function,+,mf_classic_get_read_sectors_and_keys,void,"const MfClassicData*, uint8_t*, uint8_t*" Function,+,mf_classic_get_sector_by_block,uint8_t,uint8_t -Function,-,mf_classic_get_sector_trailer_block_num_by_sector,uint8_t,uint8_t -Function,+,mf_classic_get_sector_trailer_by_sector,MfClassicSectorTrailer*,"MfClassicData*, uint8_t" -Function,-,mf_classic_get_total_block_num,uint16_t,MfClassicType +Function,+,mf_classic_get_sector_trailer_by_sector,MfClassicSectorTrailer*,"const MfClassicData*, uint8_t" +Function,+,mf_classic_get_sector_trailer_num_by_block,uint8_t,uint8_t +Function,+,mf_classic_get_sector_trailer_num_by_sector,uint8_t,uint8_t +Function,+,mf_classic_get_total_block_num,uint16_t,MfClassicType Function,+,mf_classic_get_total_sectors_num,uint8_t,MfClassicType -Function,-,mf_classic_get_type_str,const char*,MfClassicType -Function,-,mf_classic_halt,void,"FuriHalNfcTxRxContext*, Crypto1*" -Function,-,mf_classic_is_allowed_access_data_block,_Bool,"MfClassicData*, uint8_t, MfClassicKey, MfClassicAction" -Function,-,mf_classic_is_allowed_access_sector_trailer,_Bool,"MfClassicData*, uint8_t, MfClassicKey, MfClassicAction" -Function,+,mf_classic_is_block_read,_Bool,"MfClassicData*, uint8_t" -Function,+,mf_classic_is_card_read,_Bool,MfClassicData* -Function,+,mf_classic_is_key_found,_Bool,"MfClassicData*, uint8_t, MfClassicKey" -Function,-,mf_classic_is_sector_data_read,_Bool,"MfClassicData*, uint8_t" -Function,-,mf_classic_is_sector_read,_Bool,"MfClassicData*, uint8_t" +Function,+,mf_classic_get_uid,const uint8_t*,"const MfClassicData*, size_t*" +Function,+,mf_classic_is_allowed_access,_Bool,"MfClassicData*, uint8_t, MfClassicKeyType, MfClassicAction" +Function,+,mf_classic_is_allowed_access_data_block,_Bool,"MfClassicSectorTrailer*, uint8_t, MfClassicKeyType, MfClassicAction" +Function,+,mf_classic_is_block_read,_Bool,"const MfClassicData*, uint8_t" +Function,+,mf_classic_is_card_read,_Bool,const MfClassicData* +Function,+,mf_classic_is_equal,_Bool,"const MfClassicData*, const MfClassicData*" +Function,+,mf_classic_is_key_found,_Bool,"const MfClassicData*, uint8_t, MfClassicKeyType" +Function,+,mf_classic_is_sector_read,_Bool,"const MfClassicData*, uint8_t" Function,+,mf_classic_is_sector_trailer,_Bool,uint8_t -Function,-,mf_classic_is_value_block,_Bool,"MfClassicData*, uint8_t" -Function,-,mf_classic_read_block,_Bool,"FuriHalNfcTxRxContext*, Crypto1*, uint8_t, MfClassicBlock*" -Function,-,mf_classic_read_card,uint8_t,"FuriHalNfcTxRxContext*, MfClassicReader*, MfClassicData*" -Function,-,mf_classic_read_sector,void,"FuriHalNfcTxRxContext*, MfClassicData*, uint8_t" -Function,-,mf_classic_reader_add_sector,void,"MfClassicReader*, uint8_t, uint64_t, uint64_t" -Function,-,mf_classic_set_block_read,void,"MfClassicData*, uint8_t, MfClassicBlock*" -Function,-,mf_classic_set_key_found,void,"MfClassicData*, uint8_t, MfClassicKey, uint64_t" -Function,-,mf_classic_set_key_not_found,void,"MfClassicData*, uint8_t, MfClassicKey" -Function,-,mf_classic_set_sector_data_not_read,void,MfClassicData* -Function,-,mf_classic_transfer,_Bool,"FuriHalNfcTxRxContext*, Crypto1*, uint8_t" -Function,-,mf_classic_update_card,uint8_t,"FuriHalNfcTxRxContext*, MfClassicData*" -Function,-,mf_classic_value_cmd,_Bool,"FuriHalNfcTxRxContext*, Crypto1*, uint8_t, uint8_t, int32_t" -Function,-,mf_classic_value_cmd_full,_Bool,"FuriHalNfcTxRxContext*, MfClassicBlock*, uint8_t, MfClassicKey, uint64_t, int32_t" -Function,-,mf_classic_value_to_block,void,"int32_t, uint8_t, uint8_t*" -Function,-,mf_classic_write_block,_Bool,"FuriHalNfcTxRxContext*, Crypto1*, uint8_t, MfClassicBlock*" -Function,-,mf_classic_write_sector,_Bool,"FuriHalNfcTxRxContext*, MfClassicData*, MfClassicData*, uint8_t" -Function,-,mf_df_cat_application,void,"MifareDesfireApplication*, FuriString*" -Function,+,mf_df_cat_application_info,void,"MifareDesfireApplication*, FuriString*" -Function,+,mf_df_cat_card_info,void,"MifareDesfireData*, FuriString*" -Function,-,mf_df_cat_data,void,"MifareDesfireData*, FuriString*" -Function,+,mf_df_cat_file,void,"MifareDesfireFile*, FuriString*" -Function,-,mf_df_cat_free_mem,void,"MifareDesfireFreeMemory*, FuriString*" -Function,-,mf_df_cat_key_settings,void,"MifareDesfireKeySettings*, FuriString*" -Function,-,mf_df_cat_version,void,"MifareDesfireVersion*, FuriString*" -Function,-,mf_df_check_card_type,_Bool,"uint8_t, uint8_t, uint8_t" -Function,-,mf_df_clear,void,MifareDesfireData* -Function,-,mf_df_get_application,MifareDesfireApplication*,"MifareDesfireData*, const uint8_t[3]*" -Function,-,mf_df_get_file,MifareDesfireFile*,"MifareDesfireApplication*, uint8_t" -Function,-,mf_df_parse_get_application_ids_response,_Bool,"uint8_t*, uint16_t, MifareDesfireApplication**" -Function,-,mf_df_parse_get_file_ids_response,_Bool,"uint8_t*, uint16_t, MifareDesfireFile**" -Function,-,mf_df_parse_get_file_settings_response,_Bool,"uint8_t*, uint16_t, MifareDesfireFile*" -Function,-,mf_df_parse_get_free_memory_response,_Bool,"uint8_t*, uint16_t, MifareDesfireFreeMemory*" -Function,-,mf_df_parse_get_key_settings_response,_Bool,"uint8_t*, uint16_t, MifareDesfireKeySettings*" -Function,-,mf_df_parse_get_key_version_response,_Bool,"uint8_t*, uint16_t, MifareDesfireKeyVersion*" -Function,-,mf_df_parse_get_version_response,_Bool,"uint8_t*, uint16_t, MifareDesfireVersion*" -Function,-,mf_df_parse_read_data_response,_Bool,"uint8_t*, uint16_t, MifareDesfireFile*" -Function,-,mf_df_parse_select_application_response,_Bool,"uint8_t*, uint16_t" -Function,-,mf_df_prepare_get_application_ids,uint16_t,uint8_t* -Function,-,mf_df_prepare_get_file_ids,uint16_t,uint8_t* -Function,-,mf_df_prepare_get_file_settings,uint16_t,"uint8_t*, uint8_t" -Function,-,mf_df_prepare_get_free_memory,uint16_t,uint8_t* -Function,-,mf_df_prepare_get_key_settings,uint16_t,uint8_t* -Function,-,mf_df_prepare_get_key_version,uint16_t,"uint8_t*, uint8_t" -Function,-,mf_df_prepare_get_value,uint16_t,"uint8_t*, uint8_t" -Function,-,mf_df_prepare_get_version,uint16_t,uint8_t* -Function,-,mf_df_prepare_read_data,uint16_t,"uint8_t*, uint8_t, uint32_t, uint32_t" -Function,-,mf_df_prepare_read_records,uint16_t,"uint8_t*, uint8_t, uint32_t, uint32_t" -Function,-,mf_df_prepare_select_application,uint16_t,"uint8_t*, uint8_t[3]" -Function,-,mf_df_read_card,_Bool,"FuriHalNfcTxRxContext*, MifareDesfireData*" -Function,-,mf_ul_check_card_type,_Bool,"uint8_t, uint8_t, uint8_t" -Function,+,mf_ul_emulation_supported,_Bool,MfUltralightData* -Function,+,mf_ul_is_full_capture,_Bool,MfUltralightData* -Function,-,mf_ul_prepare_emulation,void,"MfUltralightEmulator*, MfUltralightData*" -Function,-,mf_ul_prepare_emulation_response,_Bool,"uint8_t*, uint16_t, uint8_t*, uint16_t*, uint32_t*, void*" -Function,-,mf_ul_pwdgen_amiibo,uint32_t,FuriHalNfcDevData* -Function,-,mf_ul_pwdgen_xiaomi,uint32_t,FuriHalNfcDevData* -Function,-,mf_ul_read_card,_Bool,"FuriHalNfcTxRxContext*, MfUltralightReader*, MfUltralightData*" -Function,-,mf_ul_reset,void,MfUltralightData* -Function,-,mf_ul_reset_emulation,void,"MfUltralightEmulator*, _Bool" -Function,-,mf_ultralight_authenticate,_Bool,"FuriHalNfcTxRxContext*, uint32_t, uint16_t*" -Function,-,mf_ultralight_fast_read_pages,_Bool,"FuriHalNfcTxRxContext*, MfUltralightReader*, MfUltralightData*" -Function,+,mf_ultralight_get_config_pages,MfUltralightConfigPages*,MfUltralightData* -Function,-,mf_ultralight_read_counters,_Bool,"FuriHalNfcTxRxContext*, MfUltralightData*" -Function,-,mf_ultralight_read_pages,_Bool,"FuriHalNfcTxRxContext*, MfUltralightReader*, MfUltralightData*" -Function,-,mf_ultralight_read_pages_direct,_Bool,"FuriHalNfcTxRxContext*, uint8_t, uint8_t*" -Function,-,mf_ultralight_read_signature,_Bool,"FuriHalNfcTxRxContext*, MfUltralightData*" -Function,-,mf_ultralight_read_tearing_flags,_Bool,"FuriHalNfcTxRxContext*, MfUltralightData*" -Function,-,mf_ultralight_read_version,_Bool,"FuriHalNfcTxRxContext*, MfUltralightReader*, MfUltralightData*" -Function,-,mfkey32_alloc,Mfkey32*,uint32_t -Function,-,mfkey32_free,void,Mfkey32* -Function,+,mfkey32_get_auth_sectors,uint16_t,FuriString* -Function,-,mfkey32_process_data,void,"Mfkey32*, uint8_t*, uint16_t, _Bool, _Bool" -Function,-,mfkey32_set_callback,void,"Mfkey32*, Mfkey32ParseDataCallback, void*" +Function,+,mf_classic_is_value_block,_Bool,"MfClassicSectorTrailer*, uint8_t" +Function,+,mf_classic_load,_Bool,"MfClassicData*, FlipperFormat*, uint32_t" +Function,+,mf_classic_poller_auth,MfClassicError,"Nfc*, uint8_t, MfClassicKey*, MfClassicKeyType, MfClassicAuthContext*" +Function,+,mf_classic_poller_change_value,MfClassicError,"Nfc*, uint8_t, MfClassicKey*, MfClassicKeyType, int32_t, int32_t*" +Function,+,mf_classic_poller_collect_nt,MfClassicError,"Nfc*, uint8_t, MfClassicKeyType, MfClassicNt*" +Function,+,mf_classic_poller_detect_type,MfClassicError,"Nfc*, MfClassicType*" +Function,+,mf_classic_poller_read,MfClassicError,"Nfc*, const MfClassicDeviceKeys*, MfClassicData*" +Function,+,mf_classic_poller_read_block,MfClassicError,"Nfc*, uint8_t, MfClassicKey*, MfClassicKeyType, MfClassicBlock*" +Function,+,mf_classic_poller_read_value,MfClassicError,"Nfc*, uint8_t, MfClassicKey*, MfClassicKeyType, int32_t*" +Function,+,mf_classic_poller_write_block,MfClassicError,"Nfc*, uint8_t, MfClassicKey*, MfClassicKeyType, MfClassicBlock*" +Function,+,mf_classic_reset,void,MfClassicData* +Function,+,mf_classic_save,_Bool,"const MfClassicData*, FlipperFormat*" +Function,+,mf_classic_set_block_read,void,"MfClassicData*, uint8_t, MfClassicBlock*" +Function,+,mf_classic_set_key_found,void,"MfClassicData*, uint8_t, MfClassicKeyType, uint64_t" +Function,+,mf_classic_set_key_not_found,void,"MfClassicData*, uint8_t, MfClassicKeyType" +Function,+,mf_classic_set_uid,_Bool,"MfClassicData*, const uint8_t*, size_t" +Function,+,mf_classic_value_to_block,void,"int32_t, uint8_t, MfClassicBlock*" +Function,+,mf_classic_verify,_Bool,"MfClassicData*, const FuriString*" +Function,+,mf_desfire_alloc,MfDesfireData*, +Function,+,mf_desfire_copy,void,"MfDesfireData*, const MfDesfireData*" +Function,+,mf_desfire_free,void,MfDesfireData* +Function,+,mf_desfire_get_application,const MfDesfireApplication*,"const MfDesfireData*, const MfDesfireApplicationId*" +Function,+,mf_desfire_get_base_data,Iso14443_4aData*,const MfDesfireData* +Function,+,mf_desfire_get_device_name,const char*,"const MfDesfireData*, NfcDeviceNameType" +Function,+,mf_desfire_get_file_data,const MfDesfireFileData*,"const MfDesfireApplication*, const MfDesfireFileId*" +Function,+,mf_desfire_get_file_settings,const MfDesfireFileSettings*,"const MfDesfireApplication*, const MfDesfireFileId*" +Function,+,mf_desfire_get_uid,const uint8_t*,"const MfDesfireData*, size_t*" +Function,+,mf_desfire_is_equal,_Bool,"const MfDesfireData*, const MfDesfireData*" +Function,+,mf_desfire_load,_Bool,"MfDesfireData*, FlipperFormat*, uint32_t" +Function,+,mf_desfire_reset,void,MfDesfireData* +Function,+,mf_desfire_save,_Bool,"const MfDesfireData*, FlipperFormat*" +Function,+,mf_desfire_set_uid,_Bool,"MfDesfireData*, const uint8_t*, size_t" +Function,+,mf_desfire_verify,_Bool,"MfDesfireData*, const FuriString*" +Function,+,mf_ultralight_alloc,MfUltralightData*, +Function,+,mf_ultralight_copy,void,"MfUltralightData*, const MfUltralightData*" +Function,+,mf_ultralight_detect_protocol,_Bool,const Iso14443_3aData* +Function,+,mf_ultralight_free,void,MfUltralightData* +Function,+,mf_ultralight_get_base_data,Iso14443_3aData*,const MfUltralightData* +Function,+,mf_ultralight_get_config_page,_Bool,"const MfUltralightData*, MfUltralightConfigPages**" +Function,+,mf_ultralight_get_config_page_num,uint16_t,MfUltralightType +Function,+,mf_ultralight_get_device_name,const char*,"const MfUltralightData*, NfcDeviceNameType" +Function,+,mf_ultralight_get_feature_support_set,uint32_t,MfUltralightType +Function,+,mf_ultralight_get_pages_total,uint16_t,MfUltralightType +Function,+,mf_ultralight_get_pwd_page_num,uint8_t,MfUltralightType +Function,+,mf_ultralight_get_type_by_version,MfUltralightType,MfUltralightVersion* +Function,+,mf_ultralight_get_uid,const uint8_t*,"const MfUltralightData*, size_t*" +Function,+,mf_ultralight_is_all_data_read,_Bool,const MfUltralightData* +Function,+,mf_ultralight_is_counter_configured,_Bool,const MfUltralightData* +Function,+,mf_ultralight_is_equal,_Bool,"const MfUltralightData*, const MfUltralightData*" +Function,+,mf_ultralight_is_page_pwd_or_pack,_Bool,"MfUltralightType, uint16_t" +Function,+,mf_ultralight_load,_Bool,"MfUltralightData*, FlipperFormat*, uint32_t" +Function,+,mf_ultralight_poller_read_card,MfUltralightError,"Nfc*, MfUltralightData*" +Function,+,mf_ultralight_poller_read_counter,MfUltralightError,"Nfc*, uint8_t, MfUltralightCounter*" +Function,+,mf_ultralight_poller_read_page,MfUltralightError,"Nfc*, uint16_t, MfUltralightPage*" +Function,+,mf_ultralight_poller_read_signature,MfUltralightError,"Nfc*, MfUltralightSignature*" +Function,+,mf_ultralight_poller_read_tearing_flag,MfUltralightError,"Nfc*, uint8_t, MfUltralightTearingFlag*" +Function,+,mf_ultralight_poller_read_version,MfUltralightError,"Nfc*, MfUltralightVersion*" +Function,+,mf_ultralight_poller_write_page,MfUltralightError,"Nfc*, uint16_t, MfUltralightPage*" +Function,+,mf_ultralight_reset,void,MfUltralightData* +Function,+,mf_ultralight_save,_Bool,"const MfUltralightData*, FlipperFormat*" +Function,+,mf_ultralight_set_uid,_Bool,"MfUltralightData*, const uint8_t*, size_t" +Function,+,mf_ultralight_support_feature,_Bool,"const uint32_t, const uint32_t" +Function,+,mf_ultralight_verify,_Bool,"MfUltralightData*, const FuriString*" Function,-,mkdtemp,char*,char* Function,-,mkostemp,int,"char*, int" Function,-,mkostemps,int,"char*, int, int" @@ -2120,52 +2239,76 @@ Function,-,nextafterl,long double,"long double, long double" Function,-,nexttoward,double,"double, long double" Function,-,nexttowardf,float,"float, long double" Function,-,nexttowardl,long double,"long double, long double" +Function,+,nfc_alloc,Nfc*, +Function,+,nfc_config,void,"Nfc*, NfcMode, NfcTech" +Function,+,nfc_data_generator_fill_data,void,"NfcDataGeneratorType, NfcDevice*" +Function,+,nfc_data_generator_get_name,const char*,NfcDataGeneratorType Function,+,nfc_device_alloc,NfcDevice*, Function,+,nfc_device_clear,void,NfcDevice* -Function,+,nfc_device_data_clear,void,NfcDeviceData* -Function,+,nfc_device_delete,_Bool,"NfcDevice*, _Bool" +Function,+,nfc_device_copy_data,void,"const NfcDevice*, NfcProtocol, NfcDeviceData*" Function,+,nfc_device_free,void,NfcDevice* -Function,+,nfc_device_load,_Bool,"NfcDevice*, const char*, _Bool" -Function,+,nfc_device_load_key_cache,_Bool,NfcDevice* -Function,+,nfc_device_restore,_Bool,"NfcDevice*, _Bool" +Function,+,nfc_device_get_data,const NfcDeviceData*,"const NfcDevice*, NfcProtocol" +Function,+,nfc_device_get_name,const char*,"const NfcDevice*, NfcDeviceNameType" +Function,+,nfc_device_get_protocol,NfcProtocol,const NfcDevice* +Function,+,nfc_device_get_protocol_name,const char*,NfcProtocol +Function,+,nfc_device_get_uid,const uint8_t*,"const NfcDevice*, size_t*" +Function,+,nfc_device_is_equal,_Bool,"const NfcDevice*, const NfcDevice*" +Function,+,nfc_device_is_equal_data,_Bool,"const NfcDevice*, NfcProtocol, const NfcDeviceData*" +Function,+,nfc_device_load,_Bool,"NfcDevice*, const char*" +Function,+,nfc_device_reset,void,NfcDevice* Function,+,nfc_device_save,_Bool,"NfcDevice*, const char*" -Function,+,nfc_device_save_shadow,_Bool,"NfcDevice*, const char*" +Function,+,nfc_device_set_data,void,"NfcDevice*, NfcProtocol, const NfcDeviceData*" Function,+,nfc_device_set_loading_callback,void,"NfcDevice*, NfcLoadingCallback, void*" -Function,+,nfc_device_set_name,void,"NfcDevice*, const char*" -Function,+,nfc_file_select,_Bool,NfcDevice* -Function,-,nfc_generate_mf_classic,void,"NfcDeviceData*, uint8_t, MfClassicType" -Function,+,nfc_get_dev_type,const char*,FuriHalNfcType -Function,-,nfc_guess_protocol,const char*,NfcProtocol -Function,+,nfc_mf_classic_type,const char*,MfClassicType -Function,+,nfc_mf_ul_type,const char*,"MfUltralightType, _Bool" -Function,+,nfc_supported_card_verify_and_parse,_Bool,NfcDeviceData* +Function,+,nfc_device_set_uid,_Bool,"NfcDevice*, const uint8_t*, size_t" +Function,+,nfc_dict_add_key,_Bool,"NfcDict*, const uint8_t*, size_t" +Function,+,nfc_dict_alloc,NfcDict*,"const char*, NfcDictMode, size_t" +Function,+,nfc_dict_check_presence,_Bool,const char* +Function,+,nfc_dict_delete_key,_Bool,"NfcDict*, const uint8_t*, size_t" +Function,+,nfc_dict_free,void,NfcDict* +Function,+,nfc_dict_get_next_key,_Bool,"NfcDict*, uint8_t*, size_t" +Function,+,nfc_dict_get_total_keys,uint32_t,NfcDict* +Function,+,nfc_dict_is_key_present,_Bool,"NfcDict*, const uint8_t*, size_t" +Function,+,nfc_dict_rewind,_Bool,NfcDict* +Function,+,nfc_free,void,Nfc* +Function,+,nfc_iso14443a_listener_set_col_res_data,NfcError,"Nfc*, uint8_t*, uint8_t, uint8_t*, uint8_t" +Function,+,nfc_iso14443a_listener_tx_custom_parity,NfcError,"Nfc*, const BitBuffer*" +Function,+,nfc_iso14443a_poller_trx_custom_parity,NfcError,"Nfc*, const BitBuffer*, BitBuffer*, uint32_t" +Function,+,nfc_iso14443a_poller_trx_sdd_frame,NfcError,"Nfc*, const BitBuffer*, BitBuffer*, uint32_t" +Function,+,nfc_iso14443a_poller_trx_short_frame,NfcError,"Nfc*, NfcIso14443aShortFrame, BitBuffer*, uint32_t" +Function,+,nfc_iso15693_listener_tx_sof,NfcError,Nfc* +Function,+,nfc_listener_alloc,NfcListener*,"Nfc*, NfcProtocol, const NfcDeviceData*" +Function,+,nfc_listener_free,void,NfcListener* +Function,+,nfc_listener_get_data,const NfcDeviceData*,"const NfcListener*, NfcProtocol" +Function,+,nfc_listener_get_protocol,NfcProtocol,const NfcListener* +Function,+,nfc_listener_start,void,"NfcListener*, NfcGenericCallback, void*" +Function,+,nfc_listener_stop,void,NfcListener* +Function,+,nfc_listener_tx,NfcError,"Nfc*, const BitBuffer*" +Function,+,nfc_poller_alloc,NfcPoller*,"Nfc*, NfcProtocol" +Function,+,nfc_poller_detect,_Bool,NfcPoller* +Function,+,nfc_poller_free,void,NfcPoller* +Function,+,nfc_poller_get_data,const NfcDeviceData*,const NfcPoller* +Function,+,nfc_poller_get_protocol,NfcProtocol,const NfcPoller* +Function,+,nfc_poller_start,void,"NfcPoller*, NfcGenericCallback, void*" +Function,+,nfc_poller_stop,void,NfcPoller* +Function,+,nfc_poller_trx,NfcError,"Nfc*, const BitBuffer*, BitBuffer*, uint32_t" +Function,+,nfc_protocol_get_parent,NfcProtocol,NfcProtocol +Function,+,nfc_protocol_has_parent,_Bool,"NfcProtocol, NfcProtocol" +Function,+,nfc_scanner_alloc,NfcScanner*,Nfc* +Function,+,nfc_scanner_free,void,NfcScanner* +Function,+,nfc_scanner_start,void,"NfcScanner*, NfcScannerCallback, void*" +Function,+,nfc_scanner_stop,void,NfcScanner* +Function,+,nfc_set_fdt_listen_fc,void,"Nfc*, uint32_t" +Function,+,nfc_set_fdt_poll_fc,void,"Nfc*, uint32_t" +Function,+,nfc_set_fdt_poll_poll_us,void,"Nfc*, uint32_t" +Function,+,nfc_set_guard_time_us,void,"Nfc*, uint32_t" +Function,+,nfc_set_mask_receive_time_fc,void,"Nfc*, uint32_t" +Function,+,nfc_start,void,"Nfc*, NfcEventCallback, void*" +Function,+,nfc_stop,void,Nfc* Function,+,nfc_util_bytes2num,uint64_t,"const uint8_t*, uint8_t" Function,+,nfc_util_even_parity32,uint8_t,uint32_t Function,+,nfc_util_num2bytes,void,"uint64_t, uint8_t, uint8_t*" Function,+,nfc_util_odd_parity,void,"const uint8_t*, uint8_t*, uint8_t" Function,+,nfc_util_odd_parity8,uint8_t,uint8_t -Function,+,nfc_worker_alloc,NfcWorker*, -Function,+,nfc_worker_free,void,NfcWorker* -Function,+,nfc_worker_get_state,NfcWorkerState,NfcWorker* -Function,-,nfc_worker_nfcv_emulate,void,NfcWorker* -Function,-,nfc_worker_nfcv_sniff,void,NfcWorker* -Function,-,nfc_worker_nfcv_unlock,void,NfcWorker* -Function,+,nfc_worker_start,void,"NfcWorker*, NfcWorkerState, NfcDeviceData*, NfcWorkerCallback, void*" -Function,+,nfc_worker_stop,void,NfcWorker* -Function,-,nfca_append_crc16,void,"uint8_t*, uint16_t" -Function,-,nfca_emulation_handler,_Bool,"uint8_t*, uint16_t, uint8_t*, uint16_t*" -Function,-,nfca_get_crc16,uint16_t,"uint8_t*, uint16_t" -Function,-,nfca_signal_alloc,NfcaSignal*, -Function,-,nfca_signal_encode,void,"NfcaSignal*, uint8_t*, uint16_t, uint8_t*" -Function,-,nfca_signal_free,void,NfcaSignal* -Function,+,nfcv_emu_deinit,void,NfcVData* -Function,+,nfcv_emu_init,void,"FuriHalNfcDevData*, NfcVData*" -Function,+,nfcv_emu_loop,_Bool,"FuriHalNfcTxRxContext*, FuriHalNfcDevData*, NfcVData*, uint32_t" -Function,+,nfcv_emu_send,void,"FuriHalNfcTxRxContext*, NfcVData*, uint8_t*, uint8_t, NfcVSendFlags, uint32_t" -Function,-,nfcv_inventory,ReturnCode,uint8_t* -Function,-,nfcv_read_blocks,ReturnCode,"NfcVReader*, NfcVData*" -Function,-,nfcv_read_card,_Bool,"NfcVReader*, FuriHalNfcDevData*, NfcVData*" -Function,-,nfcv_read_sysinfo,ReturnCode,"FuriHalNfcDevData*, NfcVData*" Function,+,notification_internal_message,void,"NotificationApp*, const NotificationSequence*" Function,+,notification_internal_message_block,void,"NotificationApp*, const NotificationSequence*" Function,+,notification_message,void,"NotificationApp*, const NotificationSequence*" @@ -2208,16 +2351,39 @@ Function,+,path_extract_dirname,void,"const char*, FuriString*" Function,+,path_extract_extension,void,"FuriString*, char*, size_t" Function,+,path_extract_filename,void,"FuriString*, FuriString*, _Bool" Function,+,path_extract_filename_no_ext,void,"const char*, FuriString*" +Function,+,pb_close_string_substream,_Bool,"pb_istream_t*, pb_istream_t*" +Function,+,pb_decode,_Bool,"pb_istream_t*, const pb_msgdesc_t*, void*" +Function,+,pb_decode_bool,_Bool,"pb_istream_t*, _Bool*" +Function,+,pb_decode_ex,_Bool,"pb_istream_t*, const pb_msgdesc_t*, void*, unsigned int" +Function,+,pb_decode_fixed32,_Bool,"pb_istream_t*, void*" +Function,+,pb_decode_fixed64,_Bool,"pb_istream_t*, void*" +Function,+,pb_decode_svarint,_Bool,"pb_istream_t*, int64_t*" +Function,+,pb_decode_tag,_Bool,"pb_istream_t*, pb_wire_type_t*, uint32_t*, _Bool*" +Function,+,pb_decode_varint,_Bool,"pb_istream_t*, uint64_t*" +Function,+,pb_decode_varint32,_Bool,"pb_istream_t*, uint32_t*" +Function,+,pb_default_field_callback,_Bool,"pb_istream_t*, pb_ostream_t*, const pb_field_t*" +Function,+,pb_encode,_Bool,"pb_ostream_t*, const pb_msgdesc_t*, const void*" +Function,+,pb_encode_ex,_Bool,"pb_ostream_t*, const pb_msgdesc_t*, const void*, unsigned int" +Function,+,pb_encode_fixed32,_Bool,"pb_ostream_t*, const void*" +Function,+,pb_encode_fixed64,_Bool,"pb_ostream_t*, const void*" +Function,+,pb_encode_string,_Bool,"pb_ostream_t*, const pb_byte_t*, size_t" +Function,+,pb_encode_submessage,_Bool,"pb_ostream_t*, const pb_msgdesc_t*, const void*" +Function,+,pb_encode_svarint,_Bool,"pb_ostream_t*, int64_t" +Function,+,pb_encode_tag,_Bool,"pb_ostream_t*, pb_wire_type_t, uint32_t" +Function,+,pb_encode_tag_for_field,_Bool,"pb_ostream_t*, const pb_field_iter_t*" +Function,+,pb_encode_varint,_Bool,"pb_ostream_t*, uint64_t" +Function,+,pb_get_encoded_size,_Bool,"size_t*, const pb_msgdesc_t*, const void*" +Function,+,pb_istream_from_buffer,pb_istream_t,"const pb_byte_t*, size_t" +Function,+,pb_make_string_substream,_Bool,"pb_istream_t*, pb_istream_t*" +Function,+,pb_ostream_from_buffer,pb_ostream_t,"pb_byte_t*, size_t" +Function,+,pb_read,_Bool,"pb_istream_t*, pb_byte_t*, size_t" +Function,+,pb_release,void,"const pb_msgdesc_t*, void*" +Function,+,pb_skip_field,_Bool,"pb_istream_t*, pb_wire_type_t" +Function,+,pb_write,_Bool,"pb_ostream_t*, const pb_byte_t*, size_t" Function,-,pcTaskGetName,char*,TaskHandle_t Function,-,pcTimerGetName,const char*,TimerHandle_t Function,-,pclose,int,FILE* Function,-,perror,void,const char* -Function,-,platformDisableIrqCallback,void, -Function,-,platformEnableIrqCallback,void, -Function,-,platformProtectST25RComm,void, -Function,-,platformSetIrqCallback,void,PlatformIrqCallback -Function,-,platformSpiTxRx,_Bool,"const uint8_t*, uint8_t*, uint16_t" -Function,-,platformUnprotectST25RComm,void, Function,+,plugin_manager_alloc,PluginManager*,"const char*, uint32_t, const ElfApiInterface*" Function,+,plugin_manager_free,void,PluginManager* Function,+,plugin_manager_get,const FlipperAppPluginDescriptor*,"PluginManager*, uint32_t" @@ -2252,7 +2418,6 @@ Function,+,powf,float,"float, float" Function,-,powl,long double,"long double, long double" Function,+,pretty_format_bytes_hex_canonical,void,"FuriString*, size_t, const char*, const uint8_t*, size_t" Function,-,printf,int,"const char*, ..." -Function,-,prng_successor,uint32_t,"uint32_t, uint32_t" Function,+,property_value_out,void,"PropertyValueContext*, const char*, unsigned int, ..." Function,+,protocol_dict_alloc,ProtocolDict*,"const ProtocolBase**, size_t" Function,+,protocol_dict_decoders_feed,ProtocolId,"ProtocolDict*, _Bool, uint32_t" @@ -2318,178 +2483,6 @@ Function,-,remquol,long double,"long double, long double, int*" Function,-,rename,int,"const char*, const char*" Function,-,renameat,int,"int, const char*, int, const char*" Function,-,rewind,void,FILE* -Function,-,rfalAdjustRegulators,ReturnCode,uint16_t* -Function,-,rfalCalibrate,ReturnCode, -Function,-,rfalDeinitialize,ReturnCode, -Function,-,rfalDisableObsvMode,void, -Function,-,rfalFeliCaPoll,ReturnCode,"rfalFeliCaPollSlots, uint16_t, uint8_t, rfalFeliCaPollRes*, uint8_t, uint8_t*, uint8_t*" -Function,-,rfalFieldOff,ReturnCode, -Function,+,rfalFieldOnAndStartGT,ReturnCode, -Function,-,rfalGetBitRate,ReturnCode,"rfalBitRate*, rfalBitRate*" -Function,-,rfalGetErrorHandling,rfalEHandling, -Function,-,rfalGetFDTListen,uint32_t, -Function,-,rfalGetFDTPoll,uint32_t, -Function,-,rfalGetGT,uint32_t, -Function,-,rfalGetMode,rfalMode, -Function,-,rfalGetObsvMode,void,"uint8_t*, uint8_t*" -Function,-,rfalGetTransceiveRSSI,ReturnCode,uint16_t* -Function,-,rfalGetTransceiveState,rfalTransceiveState, -Function,-,rfalGetTransceiveStatus,ReturnCode, -Function,-,rfalISO14443ATransceiveAnticollisionFrame,ReturnCode,"uint8_t*, uint8_t*, uint8_t*, uint16_t*, uint32_t" -Function,-,rfalISO14443ATransceiveShortFrame,ReturnCode,"rfal14443AShortFrameCmd, uint8_t*, uint8_t, uint16_t*, uint32_t" -Function,-,rfalISO15693TransceiveAnticollisionFrame,ReturnCode,"uint8_t*, uint8_t, uint8_t*, uint8_t, uint16_t*" -Function,-,rfalISO15693TransceiveEOF,ReturnCode,"uint8_t*, uint8_t, uint16_t*" -Function,-,rfalISO15693TransceiveEOFAnticollision,ReturnCode,"uint8_t*, uint8_t, uint16_t*" -Function,-,rfalInitialize,ReturnCode, -Function,-,rfalIsExtFieldOn,_Bool, -Function,-,rfalIsGTExpired,_Bool, -Function,-,rfalIsTransceiveInRx,_Bool, -Function,-,rfalIsTransceiveInTx,_Bool, -Function,-,rfalIsoDepATTRIB,ReturnCode,"const uint8_t*, uint8_t, rfalBitRate, rfalBitRate, rfalIsoDepFSxI, uint8_t, uint8_t, const uint8_t*, uint8_t, uint32_t, rfalIsoDepAttribRes*, uint8_t*" -Function,-,rfalIsoDepDeselect,ReturnCode, -Function,-,rfalIsoDepFSxI2FSx,uint16_t,uint8_t -Function,-,rfalIsoDepFWI2FWT,uint32_t,uint8_t -Function,-,rfalIsoDepGetApduTransceiveStatus,ReturnCode, -Function,-,rfalIsoDepGetMaxInfLen,uint16_t, -Function,-,rfalIsoDepGetTransceiveStatus,ReturnCode, -Function,-,rfalIsoDepInitialize,void, -Function,-,rfalIsoDepInitializeWithParams,void,"rfalComplianceMode, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t" -Function,-,rfalIsoDepIsAttrib,_Bool,"const uint8_t*, uint8_t" -Function,-,rfalIsoDepIsRats,_Bool,"const uint8_t*, uint8_t" -Function,-,rfalIsoDepListenGetActivationStatus,ReturnCode, -Function,-,rfalIsoDepListenStartActivation,ReturnCode,"rfalIsoDepAtsParam*, const rfalIsoDepAttribResParam*, const uint8_t*, uint16_t, rfalIsoDepListenActvParam" -Function,-,rfalIsoDepPPS,ReturnCode,"uint8_t, rfalBitRate, rfalBitRate, rfalIsoDepPpsRes*" -Function,-,rfalIsoDepPollAGetActivationStatus,ReturnCode, -Function,-,rfalIsoDepPollAHandleActivation,ReturnCode,"rfalIsoDepFSxI, uint8_t, rfalBitRate, rfalIsoDepDevice*" -Function,-,rfalIsoDepPollAStartActivation,ReturnCode,"rfalIsoDepFSxI, uint8_t, rfalBitRate, rfalIsoDepDevice*" -Function,-,rfalIsoDepPollBGetActivationStatus,ReturnCode, -Function,-,rfalIsoDepPollBHandleActivation,ReturnCode,"rfalIsoDepFSxI, uint8_t, rfalBitRate, uint8_t, const rfalNfcbListenDevice*, const uint8_t*, uint8_t, rfalIsoDepDevice*" -Function,-,rfalIsoDepPollBStartActivation,ReturnCode,"rfalIsoDepFSxI, uint8_t, rfalBitRate, uint8_t, const rfalNfcbListenDevice*, const uint8_t*, uint8_t, rfalIsoDepDevice*" -Function,-,rfalIsoDepPollHandleSParameters,ReturnCode,"rfalIsoDepDevice*, rfalBitRate, rfalBitRate" -Function,-,rfalIsoDepRATS,ReturnCode,"rfalIsoDepFSxI, uint8_t, rfalIsoDepAts*, uint8_t*" -Function,-,rfalIsoDepStartApduTransceive,ReturnCode,rfalIsoDepApduTxRxParam -Function,-,rfalIsoDepStartTransceive,ReturnCode,rfalIsoDepTxRxParam -Function,-,rfalListenGetState,rfalLmState,"_Bool*, rfalBitRate*" -Function,-,rfalListenSetState,ReturnCode,rfalLmState -Function,-,rfalListenSleepStart,ReturnCode,"rfalLmState, uint8_t*, uint16_t, uint16_t*" -Function,-,rfalListenStart,ReturnCode,"uint32_t, const rfalLmConfPA*, const rfalLmConfPB*, const rfalLmConfPF*, uint8_t*, uint16_t, uint16_t*" -Function,-,rfalListenStop,ReturnCode, -Function,+,rfalLowPowerModeStart,ReturnCode, -Function,+,rfalLowPowerModeStop,ReturnCode, -Function,-,rfalNfcDataExchangeCustomStart,ReturnCode,"uint8_t*, uint16_t, uint8_t**, uint16_t**, uint32_t, uint32_t" -Function,-,rfalNfcDataExchangeGetStatus,ReturnCode, -Function,-,rfalNfcDataExchangeStart,ReturnCode,"uint8_t*, uint16_t, uint8_t**, uint16_t**, uint32_t, uint32_t" -Function,-,rfalNfcDeactivate,ReturnCode,_Bool -Function,-,rfalNfcDepATR,ReturnCode,"const rfalNfcDepAtrParam*, rfalNfcDepAtrRes*, uint8_t*" -Function,-,rfalNfcDepCalculateRWT,uint32_t,uint8_t -Function,-,rfalNfcDepDSL,ReturnCode, -Function,-,rfalNfcDepGetPduTransceiveStatus,ReturnCode, -Function,-,rfalNfcDepGetTransceiveStatus,ReturnCode, -Function,-,rfalNfcDepInitialize,void, -Function,-,rfalNfcDepInitiatorHandleActivation,ReturnCode,"rfalNfcDepAtrParam*, rfalBitRate, rfalNfcDepDevice*" -Function,-,rfalNfcDepIsAtrReq,_Bool,"const uint8_t*, uint16_t, uint8_t*" -Function,-,rfalNfcDepListenGetActivationStatus,ReturnCode, -Function,-,rfalNfcDepListenStartActivation,ReturnCode,"const rfalNfcDepTargetParam*, const uint8_t*, uint16_t, rfalNfcDepListenActvParam" -Function,-,rfalNfcDepPSL,ReturnCode,"uint8_t, uint8_t" -Function,-,rfalNfcDepRLS,ReturnCode, -Function,-,rfalNfcDepSetDeactivatingCallback,void,rfalNfcDepDeactCallback -Function,-,rfalNfcDepStartPduTransceive,ReturnCode,rfalNfcDepPduTxRxParam -Function,-,rfalNfcDepStartTransceive,ReturnCode,const rfalNfcDepTxRxParam* -Function,-,rfalNfcDepTargetRcvdATR,_Bool, -Function,-,rfalNfcDiscover,ReturnCode,const rfalNfcDiscoverParam* -Function,-,rfalNfcGetActiveDevice,ReturnCode,rfalNfcDevice** -Function,-,rfalNfcGetDevicesFound,ReturnCode,"rfalNfcDevice**, uint8_t*" -Function,-,rfalNfcGetState,rfalNfcState, -Function,-,rfalNfcInitialize,ReturnCode, -Function,-,rfalNfcSelect,ReturnCode,uint8_t -Function,-,rfalNfcWorker,void, -Function,-,rfalNfcaListenerIsSleepReq,_Bool,"const uint8_t*, uint16_t" -Function,-,rfalNfcaPollerCheckPresence,ReturnCode,"rfal14443AShortFrameCmd, rfalNfcaSensRes*" -Function,-,rfalNfcaPollerFullCollisionResolution,ReturnCode,"rfalComplianceMode, uint8_t, rfalNfcaListenDevice*, uint8_t*" -Function,-,rfalNfcaPollerGetFullCollisionResolutionStatus,ReturnCode, -Function,-,rfalNfcaPollerInitialize,ReturnCode, -Function,-,rfalNfcaPollerSelect,ReturnCode,"const uint8_t*, uint8_t, rfalNfcaSelRes*" -Function,-,rfalNfcaPollerSingleCollisionResolution,ReturnCode,"uint8_t, _Bool*, rfalNfcaSelRes*, uint8_t*, uint8_t*" -Function,-,rfalNfcaPollerSleep,ReturnCode, -Function,-,rfalNfcaPollerSleepFullCollisionResolution,ReturnCode,"uint8_t, rfalNfcaListenDevice*, uint8_t*" -Function,-,rfalNfcaPollerStartFullCollisionResolution,ReturnCode,"rfalComplianceMode, uint8_t, rfalNfcaListenDevice*, uint8_t*" -Function,-,rfalNfcaPollerTechnologyDetection,ReturnCode,"rfalComplianceMode, rfalNfcaSensRes*" -Function,-,rfalNfcbPollerCheckPresence,ReturnCode,"rfalNfcbSensCmd, rfalNfcbSlots, rfalNfcbSensbRes*, uint8_t*" -Function,-,rfalNfcbPollerCollisionResolution,ReturnCode,"rfalComplianceMode, uint8_t, rfalNfcbListenDevice*, uint8_t*" -Function,-,rfalNfcbPollerInitialize,ReturnCode, -Function,-,rfalNfcbPollerInitializeWithParams,ReturnCode,"uint8_t, uint8_t" -Function,-,rfalNfcbPollerSleep,ReturnCode,const uint8_t* -Function,-,rfalNfcbPollerSlotMarker,ReturnCode,"uint8_t, rfalNfcbSensbRes*, uint8_t*" -Function,-,rfalNfcbPollerSlottedCollisionResolution,ReturnCode,"rfalComplianceMode, uint8_t, rfalNfcbSlots, rfalNfcbSlots, rfalNfcbListenDevice*, uint8_t*, _Bool*" -Function,-,rfalNfcbPollerTechnologyDetection,ReturnCode,"rfalComplianceMode, rfalNfcbSensbRes*, uint8_t*" -Function,-,rfalNfcbTR2ToFDT,uint32_t,uint8_t -Function,-,rfalNfcfListenerIsT3TReq,_Bool,"const uint8_t*, uint16_t, uint8_t*" -Function,-,rfalNfcfPollerCheck,ReturnCode,"const uint8_t*, const rfalNfcfServBlockListParam*, uint8_t*, uint16_t, uint16_t*" -Function,-,rfalNfcfPollerCheckPresence,ReturnCode, -Function,-,rfalNfcfPollerCollisionResolution,ReturnCode,"rfalComplianceMode, uint8_t, rfalNfcfListenDevice*, uint8_t*" -Function,-,rfalNfcfPollerInitialize,ReturnCode,rfalBitRate -Function,-,rfalNfcfPollerPoll,ReturnCode,"rfalFeliCaPollSlots, uint16_t, uint8_t, rfalFeliCaPollRes*, uint8_t*, uint8_t*" -Function,-,rfalNfcfPollerUpdate,ReturnCode,"const uint8_t*, const rfalNfcfServBlockListParam*, uint8_t*, uint16_t, const uint8_t*, uint8_t*, uint16_t" -Function,-,rfalNfcvPollerCheckPresence,ReturnCode,rfalNfcvInventoryRes* -Function,-,rfalNfcvPollerCollisionResolution,ReturnCode,"rfalComplianceMode, uint8_t, rfalNfcvListenDevice*, uint8_t*" -Function,-,rfalNfcvPollerExtendedGetSystemInformation,ReturnCode,"uint8_t, const uint8_t*, uint8_t, uint8_t*, uint16_t, uint16_t*" -Function,-,rfalNfcvPollerExtendedLockSingleBlock,ReturnCode,"uint8_t, const uint8_t*, uint16_t" -Function,-,rfalNfcvPollerExtendedReadMultipleBlocks,ReturnCode,"uint8_t, const uint8_t*, uint16_t, uint16_t, uint8_t*, uint16_t, uint16_t*" -Function,-,rfalNfcvPollerExtendedReadSingleBlock,ReturnCode,"uint8_t, const uint8_t*, uint16_t, uint8_t*, uint16_t, uint16_t*" -Function,-,rfalNfcvPollerExtendedWriteMultipleBlocks,ReturnCode,"uint8_t, const uint8_t*, uint16_t, uint16_t, uint8_t*, uint16_t, uint8_t, const uint8_t*, uint16_t" -Function,-,rfalNfcvPollerExtendedWriteSingleBlock,ReturnCode,"uint8_t, const uint8_t*, uint16_t, const uint8_t*, uint8_t" -Function,-,rfalNfcvPollerGetSystemInformation,ReturnCode,"uint8_t, const uint8_t*, uint8_t*, uint16_t, uint16_t*" -Function,-,rfalNfcvPollerInitialize,ReturnCode, -Function,-,rfalNfcvPollerInventory,ReturnCode,"rfalNfcvNumSlots, uint8_t, const uint8_t*, rfalNfcvInventoryRes*, uint16_t*" -Function,-,rfalNfcvPollerLockBlock,ReturnCode,"uint8_t, const uint8_t*, uint8_t" -Function,-,rfalNfcvPollerReadMultipleBlocks,ReturnCode,"uint8_t, const uint8_t*, uint8_t, uint8_t, uint8_t*, uint16_t, uint16_t*" -Function,-,rfalNfcvPollerReadSingleBlock,ReturnCode,"uint8_t, const uint8_t*, uint8_t, uint8_t*, uint16_t, uint16_t*" -Function,-,rfalNfcvPollerSelect,ReturnCode,"uint8_t, const uint8_t*" -Function,-,rfalNfcvPollerSleep,ReturnCode,"uint8_t, const uint8_t*" -Function,-,rfalNfcvPollerSleepCollisionResolution,ReturnCode,"uint8_t, rfalNfcvListenDevice*, uint8_t*" -Function,-,rfalNfcvPollerTransceiveReq,ReturnCode,"uint8_t, uint8_t, uint8_t, const uint8_t*, const uint8_t*, uint16_t, uint8_t*, uint16_t, uint16_t*" -Function,-,rfalNfcvPollerWriteMultipleBlocks,ReturnCode,"uint8_t, const uint8_t*, uint8_t, uint8_t, uint8_t*, uint16_t, uint8_t, const uint8_t*, uint16_t" -Function,-,rfalNfcvPollerWriteSingleBlock,ReturnCode,"uint8_t, const uint8_t*, uint8_t, const uint8_t*, uint8_t" -Function,-,rfalSetBitRate,ReturnCode,"rfalBitRate, rfalBitRate" -Function,-,rfalSetErrorHandling,void,rfalEHandling -Function,-,rfalSetFDTListen,void,uint32_t -Function,-,rfalSetFDTPoll,void,uint32_t -Function,-,rfalSetGT,void,uint32_t -Function,-,rfalSetMode,ReturnCode,"rfalMode, rfalBitRate, rfalBitRate" -Function,-,rfalSetObsvMode,void,"uint8_t, uint8_t" -Function,-,rfalSetPostTxRxCallback,void,rfalPostTxRxCallback -Function,-,rfalSetPreTxRxCallback,void,rfalPreTxRxCallback -Function,-,rfalSetUpperLayerCallback,void,rfalUpperLayerCallback -Function,-,rfalSt25tbPollerCheckPresence,ReturnCode,uint8_t* -Function,-,rfalSt25tbPollerCollisionResolution,ReturnCode,"uint8_t, rfalSt25tbListenDevice*, uint8_t*" -Function,-,rfalSt25tbPollerCompletion,ReturnCode, -Function,-,rfalSt25tbPollerGetUID,ReturnCode,rfalSt25tbUID* -Function,-,rfalSt25tbPollerInitialize,ReturnCode, -Function,-,rfalSt25tbPollerInitiate,ReturnCode,uint8_t* -Function,-,rfalSt25tbPollerPcall,ReturnCode,uint8_t* -Function,-,rfalSt25tbPollerReadBlock,ReturnCode,"uint8_t, rfalSt25tbBlock*" -Function,-,rfalSt25tbPollerResetToInventory,ReturnCode, -Function,-,rfalSt25tbPollerSelect,ReturnCode,uint8_t -Function,-,rfalSt25tbPollerSlotMarker,ReturnCode,"uint8_t, uint8_t*" -Function,-,rfalSt25tbPollerWriteBlock,ReturnCode,"uint8_t, const rfalSt25tbBlock*" -Function,-,rfalStartTransceive,ReturnCode,const rfalTransceiveContext* -Function,-,rfalT1TPollerInitialize,ReturnCode, -Function,-,rfalT1TPollerRall,ReturnCode,"const uint8_t*, uint8_t*, uint16_t, uint16_t*" -Function,-,rfalT1TPollerRid,ReturnCode,rfalT1TRidRes* -Function,-,rfalT1TPollerWrite,ReturnCode,"const uint8_t*, uint8_t, uint8_t" -Function,-,rfalTransceiveBitsBlockingTx,ReturnCode,"uint8_t*, uint16_t, uint8_t*, uint16_t, uint16_t*, uint32_t, uint32_t" -Function,-,rfalTransceiveBitsBlockingTxRx,ReturnCode,"uint8_t*, uint16_t, uint8_t*, uint16_t, uint16_t*, uint32_t, uint32_t" -Function,-,rfalTransceiveBlockingRx,ReturnCode, -Function,-,rfalTransceiveBlockingTx,ReturnCode,"uint8_t*, uint16_t, uint8_t*, uint16_t, uint16_t*, uint32_t, uint32_t" -Function,-,rfalTransceiveBlockingTxRx,ReturnCode,"uint8_t*, uint16_t, uint8_t*, uint16_t, uint16_t*, uint32_t, uint32_t" -Function,-,rfalWakeUpModeHasWoke,_Bool, -Function,-,rfalWakeUpModeStart,ReturnCode,const rfalWakeUpConfig* -Function,-,rfalWakeUpModeStop,ReturnCode, -Function,+,rfalWorker,void, -Function,-,rfal_platform_spi_acquire,void, -Function,-,rfal_platform_spi_release,void, -Function,-,rfal_set_callback_context,void,void* -Function,-,rfal_set_state_changed_callback,void,RfalStateChangedCallback Function,-,rindex,char*,"const char*, int" Function,-,rint,double,double Function,-,rintf,float,float @@ -2565,6 +2558,25 @@ Function,+,sha256_finish,void,"sha256_context*, unsigned char[32]" Function,+,sha256_process,void,sha256_context* Function,+,sha256_start,void,sha256_context* Function,+,sha256_update,void,"sha256_context*, const unsigned char*, unsigned int" +Function,+,signal_reader_alloc,SignalReader*,"const GpioPin*, uint32_t" +Function,+,signal_reader_free,void,SignalReader* +Function,+,signal_reader_set_polarity,void,"SignalReader*, SignalReaderPolarity" +Function,+,signal_reader_set_pull,void,"SignalReader*, GpioPull" +Function,+,signal_reader_set_sample_rate,void,"SignalReader*, SignalReaderTimeUnit, uint32_t" +Function,+,signal_reader_set_trigger,void,"SignalReader*, SignalReaderTrigger" +Function,+,signal_reader_start,void,"SignalReader*, SignalReaderCallback, void*" +Function,+,signal_reader_stop,void,SignalReader* +Function,+,simple_array_alloc,SimpleArray*,const SimpleArrayConfig* +Function,+,simple_array_cget,const SimpleArrayElement*,"const SimpleArray*, uint32_t" +Function,+,simple_array_cget_data,const SimpleArrayData*,const SimpleArray* +Function,+,simple_array_copy,void,"SimpleArray*, const SimpleArray*" +Function,+,simple_array_free,void,SimpleArray* +Function,+,simple_array_get,SimpleArrayElement*,"SimpleArray*, uint32_t" +Function,+,simple_array_get_count,uint32_t,const SimpleArray* +Function,+,simple_array_get_data,SimpleArrayData*,SimpleArray* +Function,+,simple_array_init,void,"SimpleArray*, uint32_t" +Function,+,simple_array_is_equal,_Bool,"const SimpleArray*, const SimpleArray*" +Function,+,simple_array_reset,void,SimpleArray* Function,-,sin,double,double Function,-,sincos,void,"double, double*, double*" Function,-,sincosf,void,"float, float*, float*" @@ -2575,6 +2587,26 @@ Function,-,sinhl,long double,long double Function,-,sinl,long double,long double Function,-,siprintf,int,"char*, const char*, ..." Function,-,siscanf,int,"const char*, const char*, ..." +Function,+,slix_alloc,SlixData*, +Function,+,slix_copy,void,"SlixData*, const SlixData*" +Function,+,slix_free,void,SlixData* +Function,+,slix_get_base_data,const Iso15693_3Data*,const SlixData* +Function,+,slix_get_counter,uint16_t,const SlixData* +Function,+,slix_get_device_name,const char*,"const SlixData*, NfcDeviceNameType" +Function,+,slix_get_password,SlixPassword,"const SlixData*, SlixPasswordType" +Function,+,slix_get_type,SlixType,const SlixData* +Function,+,slix_get_uid,const uint8_t*,"const SlixData*, size_t*" +Function,+,slix_is_block_protected,_Bool,"const SlixData*, SlixPasswordType, uint8_t" +Function,+,slix_is_counter_increment_protected,_Bool,const SlixData* +Function,+,slix_is_equal,_Bool,"const SlixData*, const SlixData*" +Function,+,slix_is_privacy_mode,_Bool,const SlixData* +Function,+,slix_load,_Bool,"SlixData*, FlipperFormat*, uint32_t" +Function,+,slix_reset,void,SlixData* +Function,+,slix_save,_Bool,"const SlixData*, FlipperFormat*" +Function,+,slix_set_uid,_Bool,"SlixData*, const uint8_t*, size_t" +Function,+,slix_type_has_features,_Bool,"SlixType, SlixTypeFeatures" +Function,+,slix_type_supports_password,_Bool,"SlixType, SlixPasswordType" +Function,+,slix_verify,_Bool,"SlixData*, const FuriString*" Function,-,sniprintf,int,"char*, size_t, const char*, ..." Function,+,snprintf,int,"char*, size_t, const char*, ..." Function,-,sprintf,int,"char*, const char*, ..." @@ -2585,6 +2617,19 @@ Function,+,srand,void,unsigned Function,-,srand48,void,long Function,-,srandom,void,unsigned Function,+,sscanf,int,"const char*, const char*, ..." +Function,+,st25tb_alloc,St25tbData*, +Function,+,st25tb_copy,void,"St25tbData*, const St25tbData*" +Function,+,st25tb_free,void,St25tbData* +Function,+,st25tb_get_base_data,St25tbData*,const St25tbData* +Function,+,st25tb_get_block_count,uint8_t,St25tbType +Function,+,st25tb_get_device_name,const char*,"const St25tbData*, NfcDeviceNameType" +Function,+,st25tb_get_uid,const uint8_t*,"const St25tbData*, size_t*" +Function,+,st25tb_is_equal,_Bool,"const St25tbData*, const St25tbData*" +Function,+,st25tb_load,_Bool,"St25tbData*, FlipperFormat*, uint32_t" +Function,+,st25tb_reset,void,St25tbData* +Function,+,st25tb_save,_Bool,"const St25tbData*, FlipperFormat*" +Function,+,st25tb_set_uid,_Bool,"St25tbData*, const uint8_t*, size_t" +Function,+,st25tb_verify,_Bool,"St25tbData*, const FuriString*" Function,+,storage_common_copy,FS_Error,"Storage*, const char*, const char*" Function,+,storage_common_exists,_Bool,"Storage*, const char*" Function,+,storage_common_fs_info,FS_Error,"Storage*, const char*, uint64_t*, uint64_t*" @@ -2728,7 +2773,6 @@ Function,-,strupr,char*,char* Function,-,strverscmp,int,"const char*, const char*" Function,-,strxfrm,size_t,"char*, const char*, size_t" Function,-,strxfrm_l,size_t,"char*, const char*, size_t, locale_t" -Function,-,stub_parser_verify_read,_Bool,"NfcWorker*, FuriHalNfcTxRxContext*" Function,+,subghz_block_generic_deserialize,SubGhzProtocolStatus,"SubGhzBlockGeneric*, FlipperFormat*" Function,+,subghz_block_generic_deserialize_check_count_bit,SubGhzProtocolStatus,"SubGhzBlockGeneric*, FlipperFormat*, uint16_t" Function,+,subghz_block_generic_get_preset_name,void,"const char*, FuriString*" @@ -2934,11 +2978,6 @@ Function,-,tgamma,double,double Function,-,tgammaf,float,float Function,-,tgammal,long double,long double Function,-,time,time_t,time_t* -Function,+,timerCalculateTimer,uint32_t,uint16_t -Function,-,timerDelay,void,uint16_t -Function,+,timerIsExpired,_Bool,uint32_t -Function,-,timerStopwatchMeasure,uint32_t, -Function,-,timerStopwatchStart,void, Function,-,timingsafe_bcmp,int,"const void*, const void*, size_t" Function,-,timingsafe_memcmp,int,"const void*, const void*, size_t" Function,-,tmpfile,FILE*, @@ -3421,8 +3460,10 @@ Variable,+,message_red_255,const NotificationMessage, Variable,+,message_sound_off,const NotificationMessage, Variable,+,message_vibro_off,const NotificationMessage, Variable,+,message_vibro_on,const NotificationMessage, -Variable,+,nfc_generators,const NfcGenerator*[], -Variable,-,nfc_supported_card,NfcSupportedCard[NfcSupportedCardTypeEnd], +Variable,-,nfc_device_mf_classic,const NfcDeviceBase, +Variable,-,nfc_device_mf_desfire,const NfcDeviceBase, +Variable,-,nfc_device_mf_ultralight,const NfcDeviceBase, +Variable,-,nfc_device_st25tb,const NfcDeviceBase, Variable,+,sequence_audiovisual_alert,const NotificationSequence, Variable,+,sequence_blink_blue_10,const NotificationSequence, Variable,+,sequence_blink_blue_100,const NotificationSequence, @@ -3472,6 +3513,7 @@ Variable,+,sequence_set_vibro_on,const NotificationSequence, Variable,+,sequence_single_vibro,const NotificationSequence, Variable,+,sequence_solid_yellow,const NotificationSequence, Variable,+,sequence_success,const NotificationSequence, +Variable,+,simple_array_config_uint8_t,const SimpleArrayConfig, Variable,-,subghz_device_cc1101_int,const SubGhzDevice, Variable,+,subghz_device_cc1101_preset_2fsk_dev2_38khz_async_regs,const uint8_t[], Variable,+,subghz_device_cc1101_preset_2fsk_dev47_6khz_async_regs,const uint8_t[], diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc.c b/firmware/targets/f7/furi_hal/furi_hal_nfc.c index baffde1ebf7..e8a1033d515 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_nfc.c +++ b/firmware/targets/f7/furi_hal/furi_hal_nfc.c @@ -1,837 +1,621 @@ -#include -#include -#include -#include -#include -#include +#include "furi_hal_nfc_i.h" +#include "furi_hal_nfc_tech_i.h" + +#include -#include +#include #include -#include -#include -#include #define TAG "FuriHalNfc" -static const uint32_t clocks_in_ms = 64 * 1000; - -FuriEventFlag* event = NULL; -#define EVENT_FLAG_INTERRUPT (1UL << 0) -#define EVENT_FLAG_STATE_CHANGED (1UL << 1) -#define EVENT_FLAG_STOP (1UL << 2) -#define EVENT_FLAG_ALL (EVENT_FLAG_INTERRUPT | EVENT_FLAG_STATE_CHANGED | EVENT_FLAG_STOP) - -#define FURI_HAL_NFC_UID_INCOMPLETE (0x04) - -void furi_hal_nfc_init() { - furi_assert(!event); - event = furi_event_flag_alloc(); - - ReturnCode ret = rfalNfcInitialize(); - if(ret == ERR_NONE) { - furi_hal_nfc_start_sleep(); - FURI_LOG_I(TAG, "Init OK"); - } else { - FURI_LOG_W(TAG, "Init Failed, RFAL returned: %d", ret); - } -} - -void furi_hal_nfc_deinit() { - ReturnCode ret = rfalDeinitialize(); - if(ret == ERR_NONE) { - FURI_LOG_I(TAG, "Deinit OK"); - } else { - FURI_LOG_W(TAG, "Deinit Failed, RFAL returned: %d", ret); +const FuriHalNfcTechBase* furi_hal_nfc_tech[FuriHalNfcTechNum] = { + [FuriHalNfcTechIso14443a] = &furi_hal_nfc_iso14443a, + [FuriHalNfcTechIso14443b] = &furi_hal_nfc_iso14443b, + [FuriHalNfcTechIso15693] = &furi_hal_nfc_iso15693, + [FuriHalNfcTechFelica] = &furi_hal_nfc_felica, + // Add new technologies here +}; + +FuriHalNfc furi_hal_nfc; + +static FuriHalNfcError furi_hal_nfc_turn_on_osc(FuriHalSpiBusHandle* handle) { + FuriHalNfcError error = FuriHalNfcErrorNone; + furi_hal_nfc_event_start(); + + if(!st25r3916_check_reg( + handle, + ST25R3916_REG_OP_CONTROL, + ST25R3916_REG_OP_CONTROL_en, + ST25R3916_REG_OP_CONTROL_en)) { + st25r3916_mask_irq(handle, ~ST25R3916_IRQ_MASK_OSC); + st25r3916_set_reg_bits(handle, ST25R3916_REG_OP_CONTROL, ST25R3916_REG_OP_CONTROL_en); + furi_hal_nfc_event_wait_for_specific_irq(handle, ST25R3916_IRQ_MASK_OSC, 10); + } + // Disable IRQs + st25r3916_mask_irq(handle, ST25R3916_IRQ_MASK_ALL); + + bool osc_on = st25r3916_check_reg( + handle, + ST25R3916_REG_AUX_DISPLAY, + ST25R3916_REG_AUX_DISPLAY_osc_ok, + ST25R3916_REG_AUX_DISPLAY_osc_ok); + if(!osc_on) { + error = FuriHalNfcErrorOscillator; } - if(event) { - furi_event_flag_free(event); - event = NULL; - } + return error; } -bool furi_hal_nfc_is_busy() { - return rfalNfcGetState() != RFAL_NFC_STATE_IDLE; -} +FuriHalNfcError furi_hal_nfc_is_hal_ready() { + FuriHalNfcError error = FuriHalNfcErrorNone; -bool furi_hal_nfc_is_init() { - return rfalNfcGetState() != RFAL_NFC_STATE_NOTINIT; -} + do { + error = furi_hal_nfc_acquire(); + if(error != FuriHalNfcErrorNone) break; -void furi_hal_nfc_field_on() { - furi_hal_nfc_exit_sleep(); - st25r3916TxRxOn(); -} + FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + uint8_t chip_id = 0; + st25r3916_read_reg(handle, ST25R3916_REG_IC_IDENTITY, &chip_id); + if((chip_id & ST25R3916_REG_IC_IDENTITY_ic_type_mask) != + ST25R3916_REG_IC_IDENTITY_ic_type_st25r3916) { + FURI_LOG_E(TAG, "Wrong chip id"); + error = FuriHalNfcErrorCommunication; + } -void furi_hal_nfc_field_off() { - st25r3916TxRxOff(); - furi_hal_nfc_start_sleep(); -} + furi_hal_nfc_release(); + } while(false); -void furi_hal_nfc_start_sleep() { - rfalLowPowerModeStart(); + return error; } -void furi_hal_nfc_exit_sleep() { - rfalLowPowerModeStop(); -} +FuriHalNfcError furi_hal_nfc_init() { + furi_assert(furi_hal_nfc.mutex == NULL); -bool furi_hal_nfc_detect(FuriHalNfcDevData* nfc_data, uint32_t timeout) { - furi_assert(nfc_data); + furi_hal_nfc.mutex = furi_mutex_alloc(FuriMutexTypeNormal); + FuriHalNfcError error = FuriHalNfcErrorNone; - rfalNfcDevice* dev_list = NULL; - uint8_t dev_cnt = 0; - bool detected = false; + furi_hal_nfc_event_init(); + furi_hal_nfc_event_start(); - rfalLowPowerModeStop(); - rfalNfcState state = rfalNfcGetState(); - rfalNfcState state_old = 0; - if(state == RFAL_NFC_STATE_NOTINIT) { - rfalNfcInitialize(); - } - rfalNfcDiscoverParam params; - params.compMode = RFAL_COMPLIANCE_MODE_EMV; - params.techs2Find = RFAL_NFC_POLL_TECH_A | RFAL_NFC_POLL_TECH_B | RFAL_NFC_POLL_TECH_F | - RFAL_NFC_POLL_TECH_V | RFAL_NFC_POLL_TECH_AP2P | RFAL_NFC_POLL_TECH_ST25TB; - params.totalDuration = 1000; - params.devLimit = 3; - params.wakeupEnabled = false; - params.wakeupConfigDefault = true; - params.nfcfBR = RFAL_BR_212; - params.ap2pBR = RFAL_BR_424; - params.maxBR = RFAL_BR_KEEP; - params.GBLen = RFAL_NFCDEP_GB_MAX_LEN; - params.notifyCb = NULL; - - uint32_t start = DWT->CYCCNT; - rfalNfcDiscover(¶ms); - while(true) { - rfalNfcWorker(); - state = rfalNfcGetState(); - if(state != state_old) { - FURI_LOG_T(TAG, "State change %d -> %d", state_old, state); + do { + error = furi_hal_nfc_acquire(); + if(error != FuriHalNfcErrorNone) { + furi_hal_nfc_low_power_mode_start(); } - state_old = state; - if(state == RFAL_NFC_STATE_ACTIVATED) { - detected = true; + + FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + // Set default state + st25r3916_direct_cmd(handle, ST25R3916_CMD_SET_DEFAULT); + // Increase IO driver strength of MISO and IRQ + st25r3916_write_reg(handle, ST25R3916_REG_IO_CONF2, ST25R3916_REG_IO_CONF2_io_drv_lvl); + // Check chip ID + uint8_t chip_id = 0; + st25r3916_read_reg(handle, ST25R3916_REG_IC_IDENTITY, &chip_id); + if((chip_id & ST25R3916_REG_IC_IDENTITY_ic_type_mask) != + ST25R3916_REG_IC_IDENTITY_ic_type_st25r3916) { + FURI_LOG_E(TAG, "Wrong chip id"); + error = FuriHalNfcErrorCommunication; + furi_hal_nfc_low_power_mode_start(); + furi_hal_nfc_release(); break; } - if(state == RFAL_NFC_STATE_POLL_ACTIVATION) { - start = DWT->CYCCNT; - continue; - } - if(state == RFAL_NFC_STATE_POLL_SELECT) { - rfalNfcSelect(0); - } - if(DWT->CYCCNT - start > timeout * clocks_in_ms) { - rfalNfcDeactivate(true); - FURI_LOG_T(TAG, "Timeout"); + // Clear interrupts + st25r3916_get_irq(handle); + // Mask all interrupts + st25r3916_mask_irq(handle, ST25R3916_IRQ_MASK_ALL); + // Enable interrupts + furi_hal_nfc_init_gpio_isr(); + // Disable internal overheat protection + st25r3916_change_test_reg_bits(handle, 0x04, 0x10, 0x10); + + error = furi_hal_nfc_turn_on_osc(handle); + if(error != FuriHalNfcErrorNone) { + furi_hal_nfc_low_power_mode_start(); + furi_hal_nfc_release(); break; } - furi_delay_tick(1); - } - rfalNfcGetDevicesFound(&dev_list, &dev_cnt); - if(detected) { - if(dev_list[0].type == RFAL_NFC_LISTEN_TYPE_NFCA) { - nfc_data->type = FuriHalNfcTypeA; - nfc_data->atqa[0] = dev_list[0].dev.nfca.sensRes.anticollisionInfo; - nfc_data->atqa[1] = dev_list[0].dev.nfca.sensRes.platformInfo; - nfc_data->sak = dev_list[0].dev.nfca.selRes.sak; - uint8_t* cuid_start = dev_list[0].nfcid; - if(dev_list[0].nfcidLen == 7) { - cuid_start = &dev_list[0].nfcid[3]; - } - nfc_data->cuid = (cuid_start[0] << 24) | (cuid_start[1] << 16) | (cuid_start[2] << 8) | - (cuid_start[3]); - } else if( - dev_list[0].type == RFAL_NFC_LISTEN_TYPE_NFCB || - dev_list[0].type == RFAL_NFC_LISTEN_TYPE_ST25TB) { - nfc_data->type = FuriHalNfcTypeB; - } else if(dev_list[0].type == RFAL_NFC_LISTEN_TYPE_NFCF) { - nfc_data->type = FuriHalNfcTypeF; - } else if(dev_list[0].type == RFAL_NFC_LISTEN_TYPE_NFCV) { - nfc_data->type = FuriHalNfcTypeV; - } - if(dev_list[0].rfInterface == RFAL_NFC_INTERFACE_RF) { - nfc_data->interface = FuriHalNfcInterfaceRf; - } else if(dev_list[0].rfInterface == RFAL_NFC_INTERFACE_ISODEP) { - nfc_data->interface = FuriHalNfcInterfaceIsoDep; - } else if(dev_list[0].rfInterface == RFAL_NFC_INTERFACE_NFCDEP) { - nfc_data->interface = FuriHalNfcInterfaceNfcDep; + + // Measure voltage + // Set measure power supply voltage source + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_REGULATOR_CONTROL, + ST25R3916_REG_REGULATOR_CONTROL_mpsv_mask, + ST25R3916_REG_REGULATOR_CONTROL_mpsv_vdd); + // Enable timer and interrupt register + st25r3916_mask_irq(handle, ~ST25R3916_IRQ_MASK_DCT); + st25r3916_direct_cmd(handle, ST25R3916_CMD_MEASURE_VDD); + furi_hal_nfc_event_wait_for_specific_irq(handle, ST25R3916_IRQ_MASK_DCT, 100); + st25r3916_mask_irq(handle, ST25R3916_IRQ_MASK_ALL); + uint8_t ad_res = 0; + st25r3916_read_reg(handle, ST25R3916_REG_AD_RESULT, &ad_res); + uint16_t mV = ((uint16_t)ad_res) * 23U; + mV += (((((uint16_t)ad_res) * 4U) + 5U) / 10U); + + if(mV < 3600) { + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_IO_CONF2, + ST25R3916_REG_IO_CONF2_sup3V, + ST25R3916_REG_IO_CONF2_sup3V_3V); + } else { + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_IO_CONF2, + ST25R3916_REG_IO_CONF2_sup3V, + ST25R3916_REG_IO_CONF2_sup3V_5V); } - nfc_data->uid_len = dev_list[0].nfcidLen; - memcpy(nfc_data->uid, dev_list[0].nfcid, nfc_data->uid_len); - } - return detected; -} + // Disable MCU CLK + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_IO_CONF1, + ST25R3916_REG_IO_CONF1_out_cl_mask | ST25R3916_REG_IO_CONF1_lf_clk_off, + 0x07); + // Disable MISO pull-down + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_IO_CONF2, + ST25R3916_REG_IO_CONF2_miso_pd1 | ST25R3916_REG_IO_CONF2_miso_pd2, + 0x00); + // Set tx driver resistance to 1 Om + st25r3916_change_reg_bits( + handle, ST25R3916_REG_TX_DRIVER, ST25R3916_REG_TX_DRIVER_d_res_mask, 0x00); + // Use minimum non-overlap + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_RES_AM_MOD, + ST25R3916_REG_RES_AM_MOD_fa3_f, + ST25R3916_REG_RES_AM_MOD_fa3_f); + + // Set activation threashold + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_FIELD_THRESHOLD_ACTV, + ST25R3916_REG_FIELD_THRESHOLD_ACTV_trg_mask, + ST25R3916_REG_FIELD_THRESHOLD_ACTV_trg_105mV); + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_FIELD_THRESHOLD_ACTV, + ST25R3916_REG_FIELD_THRESHOLD_ACTV_rfe_mask, + ST25R3916_REG_FIELD_THRESHOLD_ACTV_rfe_105mV); + // Set deactivation threashold + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_FIELD_THRESHOLD_DEACTV, + ST25R3916_REG_FIELD_THRESHOLD_DEACTV_trg_mask, + ST25R3916_REG_FIELD_THRESHOLD_DEACTV_trg_75mV); + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_FIELD_THRESHOLD_DEACTV, + ST25R3916_REG_FIELD_THRESHOLD_DEACTV_rfe_mask, + ST25R3916_REG_FIELD_THRESHOLD_DEACTV_rfe_75mV); + // Enable external load modulation + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_AUX_MOD, + ST25R3916_REG_AUX_MOD_lm_ext, + ST25R3916_REG_AUX_MOD_lm_ext); + // Enable internal load modulation + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_AUX_MOD, + ST25R3916_REG_AUX_MOD_lm_dri, + ST25R3916_REG_AUX_MOD_lm_dri); + // Adjust FDT + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_PASSIVE_TARGET, + ST25R3916_REG_PASSIVE_TARGET_fdel_mask, + (5U << ST25R3916_REG_PASSIVE_TARGET_fdel_shift)); + // Reduce RFO resistance in Modulated state + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_PT_MOD, + ST25R3916_REG_PT_MOD_ptm_res_mask | ST25R3916_REG_PT_MOD_pt_res_mask, + 0x0f); + // Enable RX start on first 4 bits + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_EMD_SUP_CONF, + ST25R3916_REG_EMD_SUP_CONF_rx_start_emv, + ST25R3916_REG_EMD_SUP_CONF_rx_start_emv_on); + // Set antena tunning + st25r3916_change_reg_bits(handle, ST25R3916_REG_ANT_TUNE_A, 0xff, 0x82); + st25r3916_change_reg_bits(handle, ST25R3916_REG_ANT_TUNE_B, 0xff, 0x82); + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_OP_CONTROL, + ST25R3916_REG_OP_CONTROL_en_fd_mask, + ST25R3916_REG_OP_CONTROL_en_fd_auto_efd); -bool furi_hal_nfc_activate_nfca(uint32_t timeout, uint32_t* cuid) { - rfalNfcDevice* dev_list; - uint8_t dev_cnt = 0; - rfalLowPowerModeStop(); - rfalNfcState state = rfalNfcGetState(); - if(state == RFAL_NFC_STATE_NOTINIT) { - rfalNfcInitialize(); - } - rfalNfcDiscoverParam params = { - .compMode = RFAL_COMPLIANCE_MODE_NFC, - .techs2Find = RFAL_NFC_POLL_TECH_A, - .totalDuration = 1000, - .devLimit = 3, - .wakeupEnabled = false, - .wakeupConfigDefault = true, - .nfcfBR = RFAL_BR_212, - .ap2pBR = RFAL_BR_424, - .maxBR = RFAL_BR_KEEP, - .GBLen = RFAL_NFCDEP_GB_MAX_LEN, - .notifyCb = NULL, - }; - uint32_t start = DWT->CYCCNT; - rfalNfcDiscover(¶ms); - while(state != RFAL_NFC_STATE_ACTIVATED) { - rfalNfcWorker(); - state = rfalNfcGetState(); - FURI_LOG_T(TAG, "Current state %d", state); - if(state == RFAL_NFC_STATE_POLL_ACTIVATION) { - start = DWT->CYCCNT; - continue; - } - if(state == RFAL_NFC_STATE_POLL_SELECT) { - rfalNfcSelect(0); - } - if(DWT->CYCCNT - start > timeout * clocks_in_ms) { - rfalNfcDeactivate(true); - FURI_LOG_T(TAG, "Timeout"); - return false; + // Perform calibration + if(st25r3916_check_reg( + handle, + ST25R3916_REG_REGULATOR_CONTROL, + ST25R3916_REG_REGULATOR_CONTROL_reg_s, + 0x00)) { + FURI_LOG_I(TAG, "Adjusting regulators"); + // Reset logic + st25r3916_set_reg_bits( + handle, ST25R3916_REG_REGULATOR_CONTROL, ST25R3916_REG_REGULATOR_CONTROL_reg_s); + st25r3916_clear_reg_bits( + handle, ST25R3916_REG_REGULATOR_CONTROL, ST25R3916_REG_REGULATOR_CONTROL_reg_s); + st25r3916_direct_cmd(handle, ST25R3916_CMD_ADJUST_REGULATORS); + furi_delay_ms(6); } - furi_thread_yield(); - } - rfalNfcGetDevicesFound(&dev_list, &dev_cnt); - // Take first device and set cuid - if(cuid) { - uint8_t* cuid_start = dev_list[0].nfcid; - if(dev_list[0].nfcidLen == 7) { - cuid_start = &dev_list[0].nfcid[3]; - } - *cuid = (cuid_start[0] << 24) | (cuid_start[1] << 16) | (cuid_start[2] << 8) | - (cuid_start[3]); - FURI_LOG_T(TAG, "Activated tag with cuid: %lX", *cuid); - } - return true; -} -bool furi_hal_nfc_listen( - uint8_t* uid, - uint8_t uid_len, - uint8_t* atqa, - uint8_t sak, - bool activate_after_sak, - uint32_t timeout) { - rfalNfcState state = rfalNfcGetState(); - if(state == RFAL_NFC_STATE_NOTINIT) { - rfalNfcInitialize(); - } else if(state >= RFAL_NFC_STATE_ACTIVATED) { - rfalNfcDeactivate(false); - } - rfalLowPowerModeStop(); - rfalNfcDiscoverParam params = { - .techs2Find = RFAL_NFC_LISTEN_TECH_A, - .totalDuration = 1000, - .devLimit = 1, - .wakeupEnabled = false, - .wakeupConfigDefault = true, - .nfcfBR = RFAL_BR_212, - .ap2pBR = RFAL_BR_424, - .maxBR = RFAL_BR_KEEP, - .GBLen = RFAL_NFCDEP_GB_MAX_LEN, - .notifyCb = NULL, - .activate_after_sak = activate_after_sak, - }; - if(FURI_BIT(sak, 5)) { - params.compMode = RFAL_COMPLIANCE_MODE_EMV; - } else { - params.compMode = RFAL_COMPLIANCE_MODE_NFC; - } - params.lmConfigPA.nfcidLen = uid_len; - memcpy(params.lmConfigPA.nfcid, uid, uid_len); - params.lmConfigPA.SENS_RES[0] = atqa[0]; - params.lmConfigPA.SENS_RES[1] = atqa[1]; - params.lmConfigPA.SEL_RES = sak; - rfalNfcDiscover(¶ms); - - // Disable EMD suppression. - st25r3916ModifyRegister(ST25R3916_REG_EMD_SUP_CONF, ST25R3916_REG_EMD_SUP_CONF_emd_emv, 0); - - uint32_t start = DWT->CYCCNT; - while(state != RFAL_NFC_STATE_ACTIVATED) { - rfalNfcWorker(); - state = rfalNfcGetState(); - if(DWT->CYCCNT - start > timeout * clocks_in_ms) { - rfalNfcDeactivate(true); - return false; - } - furi_delay_tick(1); - } - return true; -} + furi_hal_nfc_low_power_mode_start(); + furi_hal_nfc_release(); + } while(false); -static void furi_hal_nfc_read_fifo(uint8_t* data, uint16_t* bits) { - uint8_t fifo_status[2]; - uint8_t rx_buff[64]; - - st25r3916ReadMultipleRegisters( - ST25R3916_REG_FIFO_STATUS1, fifo_status, ST25R3916_FIFO_STATUS_LEN); - uint16_t rx_bytes = - ((((uint16_t)fifo_status[1] & ST25R3916_REG_FIFO_STATUS2_fifo_b_mask) >> - ST25R3916_REG_FIFO_STATUS2_fifo_b_shift) - << 8); - rx_bytes |= (((uint16_t)fifo_status[0]) & 0x00FFU); - st25r3916ReadFifo(rx_buff, rx_bytes); - - memcpy(data, rx_buff, rx_bytes); - *bits = rx_bytes * 8; + return error; } -void furi_hal_nfc_listen_sleep() { - st25r3916ExecuteCommand(ST25R3916_CMD_GOTO_SLEEP); +static bool furi_hal_nfc_is_mine() { + return (furi_mutex_get_owner(furi_hal_nfc.mutex) == furi_thread_get_current_id()); } -void furi_hal_nfc_stop_cmd() { - st25r3916ExecuteCommand(ST25R3916_CMD_STOP); -} +FuriHalNfcError furi_hal_nfc_acquire() { + furi_check(furi_hal_nfc.mutex); -bool furi_hal_nfc_listen_rx(FuriHalNfcTxRxContext* tx_rx, uint32_t timeout_ms) { - furi_assert(tx_rx); - - // Wait for interrupts - uint32_t start = furi_get_tick(); - bool data_received = false; - while(true) { - if(furi_hal_gpio_read(&gpio_nfc_irq_rfid_pull) == true) { - st25r3916CheckForReceivedInterrupts(); - if(st25r3916GetInterrupt(ST25R3916_IRQ_MASK_RXE)) { - furi_hal_nfc_read_fifo(tx_rx->rx_data, &tx_rx->rx_bits); - data_received = true; - if(tx_rx->sniff_rx) { - tx_rx->sniff_rx(tx_rx->rx_data, tx_rx->rx_bits, false, tx_rx->sniff_context); - } - break; - } - continue; - } - if(furi_get_tick() - start > timeout_ms) { - FURI_LOG_T(TAG, "Interrupt waiting timeout"); - furi_delay_tick(1); - break; - } + furi_hal_spi_acquire(&furi_hal_spi_bus_handle_nfc); + + FuriHalNfcError error = FuriHalNfcErrorNone; + if(furi_mutex_acquire(furi_hal_nfc.mutex, 100) != FuriStatusOk) { + furi_hal_spi_release(&furi_hal_spi_bus_handle_nfc); + error = FuriHalNfcErrorBusy; } - return data_received; + return error; } -void furi_hal_nfc_listen_start(FuriHalNfcDevData* nfc_data) { - furi_assert(nfc_data); +FuriHalNfcError furi_hal_nfc_release() { + furi_check(furi_hal_nfc.mutex); + furi_check(furi_hal_nfc_is_mine()); + furi_check(furi_mutex_release(furi_hal_nfc.mutex) == FuriStatusOk); - furi_hal_gpio_init(&gpio_nfc_irq_rfid_pull, GpioModeInput, GpioPullDown, GpioSpeedVeryHigh); - // Clear interrupts - st25r3916ClearInterrupts(); - // Mask all interrupts - st25r3916DisableInterrupts(ST25R3916_IRQ_MASK_ALL); - // RESET - st25r3916ExecuteCommand(ST25R3916_CMD_STOP); - // Setup registers - st25r3916WriteRegister( + furi_hal_spi_release(&furi_hal_spi_bus_handle_nfc); + + return FuriHalNfcErrorNone; +} + +FuriHalNfcError furi_hal_nfc_low_power_mode_start() { + FuriHalNfcError error = FuriHalNfcErrorNone; + FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + + st25r3916_direct_cmd(handle, ST25R3916_CMD_STOP); + st25r3916_clear_reg_bits( + handle, ST25R3916_REG_OP_CONTROL, - ST25R3916_REG_OP_CONTROL_en | ST25R3916_REG_OP_CONTROL_rx_en | + (ST25R3916_REG_OP_CONTROL_en | ST25R3916_REG_OP_CONTROL_rx_en | + ST25R3916_REG_OP_CONTROL_wu | ST25R3916_REG_OP_CONTROL_tx_en | + ST25R3916_REG_OP_CONTROL_en_fd_mask)); + furi_hal_nfc_deinit_gpio_isr(); + furi_hal_nfc_timers_deinit(); + furi_hal_nfc_event_stop(); + + return error; +} + +FuriHalNfcError furi_hal_nfc_low_power_mode_stop() { + FuriHalNfcError error = FuriHalNfcErrorNone; + FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + + do { + furi_hal_nfc_init_gpio_isr(); + furi_hal_nfc_timers_init(); + error = furi_hal_nfc_turn_on_osc(handle); + if(error != FuriHalNfcErrorNone) break; + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_OP_CONTROL, + ST25R3916_REG_OP_CONTROL_en_fd_mask, ST25R3916_REG_OP_CONTROL_en_fd_auto_efd); - st25r3916WriteRegister( - ST25R3916_REG_MODE, - ST25R3916_REG_MODE_targ_targ | ST25R3916_REG_MODE_om3 | ST25R3916_REG_MODE_om0); - st25r3916WriteRegister( - ST25R3916_REG_PASSIVE_TARGET, - ST25R3916_REG_PASSIVE_TARGET_fdel_2 | ST25R3916_REG_PASSIVE_TARGET_fdel_0 | - ST25R3916_REG_PASSIVE_TARGET_d_ac_ap2p | ST25R3916_REG_PASSIVE_TARGET_d_212_424_1r); - st25r3916WriteRegister(ST25R3916_REG_MASK_RX_TIMER, 0x02); - - // Mask interrupts - uint32_t clear_irq_mask = - (ST25R3916_IRQ_MASK_RXE | ST25R3916_IRQ_MASK_RXE_PTA | ST25R3916_IRQ_MASK_WU_A_X | - ST25R3916_IRQ_MASK_WU_A); - st25r3916EnableInterrupts(clear_irq_mask); - - // Set 4 or 7 bytes UID - if(nfc_data->uid_len == 4) { - st25r3916ChangeRegisterBits( - ST25R3916_REG_AUX, ST25R3916_REG_AUX_nfc_id_mask, ST25R3916_REG_AUX_nfc_id_4bytes); - } else { - st25r3916ChangeRegisterBits( - ST25R3916_REG_AUX, ST25R3916_REG_AUX_nfc_id_mask, ST25R3916_REG_AUX_nfc_id_7bytes); - } - // Write PT Memory - uint8_t pt_memory[15] = {}; - memcpy(pt_memory, nfc_data->uid, nfc_data->uid_len); - pt_memory[10] = nfc_data->atqa[0]; - pt_memory[11] = nfc_data->atqa[1]; - if(nfc_data->uid_len == 4) { - pt_memory[12] = nfc_data->sak & ~FURI_HAL_NFC_UID_INCOMPLETE; - } else { - pt_memory[12] = FURI_HAL_NFC_UID_INCOMPLETE; - } - pt_memory[13] = nfc_data->sak & ~FURI_HAL_NFC_UID_INCOMPLETE; - pt_memory[14] = nfc_data->sak & ~FURI_HAL_NFC_UID_INCOMPLETE; - st25r3916WritePTMem(pt_memory, sizeof(pt_memory)); - // Go to sense - st25r3916ExecuteCommand(ST25R3916_CMD_GOTO_SENSE); -} + } while(false); -void rfal_interrupt_callback_handler() { - furi_event_flag_set(event, EVENT_FLAG_INTERRUPT); + return error; } -void rfal_state_changed_callback(void* context) { - UNUSED(context); - furi_event_flag_set(event, EVENT_FLAG_STATE_CHANGED); +static FuriHalNfcError furi_hal_nfc_poller_init_common(FuriHalSpiBusHandle* handle) { + // Disable wake up + st25r3916_clear_reg_bits(handle, ST25R3916_REG_OP_CONTROL, ST25R3916_REG_OP_CONTROL_wu); + // Enable correlator + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_AUX, + ST25R3916_REG_AUX_dis_corr, + ST25R3916_REG_AUX_dis_corr_correlator); + + st25r3916_change_reg_bits(handle, ST25R3916_REG_ANT_TUNE_A, 0xff, 0x82); + st25r3916_change_reg_bits(handle, ST25R3916_REG_ANT_TUNE_B, 0xFF, 0x82); + + st25r3916_write_reg(handle, ST25R3916_REG_OVERSHOOT_CONF1, 0x00); + st25r3916_write_reg(handle, ST25R3916_REG_OVERSHOOT_CONF2, 0x00); + st25r3916_write_reg(handle, ST25R3916_REG_UNDERSHOOT_CONF1, 0x00); + st25r3916_write_reg(handle, ST25R3916_REG_UNDERSHOOT_CONF2, 0x00); + + return FuriHalNfcErrorNone; } -void furi_hal_nfc_stop() { - if(event) { - furi_event_flag_set(event, EVENT_FLAG_STOP); - } +static FuriHalNfcError furi_hal_nfc_listener_init_common(FuriHalSpiBusHandle* handle) { + UNUSED(handle); + // No common listener configuration + return FuriHalNfcErrorNone; } -bool furi_hal_nfc_emulate_nfca( - uint8_t* uid, - uint8_t uid_len, - uint8_t* atqa, - uint8_t sak, - FuriHalNfcEmulateCallback callback, - void* context, - uint32_t timeout) { - rfalSetUpperLayerCallback(rfal_interrupt_callback_handler); - rfal_set_state_changed_callback(rfal_state_changed_callback); - - rfalLmConfPA config; - config.nfcidLen = uid_len; - memcpy(config.nfcid, uid, uid_len); - memcpy(config.SENS_RES, atqa, RFAL_LM_SENS_RES_LEN); - config.SEL_RES = sak; - uint8_t buff_rx[256]; - uint16_t buff_rx_size = 256; - uint16_t buff_rx_len = 0; - uint8_t buff_tx[1040]; - uint16_t buff_tx_len = 0; - uint32_t data_type = FURI_HAL_NFC_TXRX_DEFAULT; - - rfalLowPowerModeStop(); - if(rfalListenStart( - RFAL_LM_MASK_NFCA, - &config, - NULL, - NULL, - buff_rx, - rfalConvBytesToBits(buff_rx_size), - &buff_rx_len)) { - rfalListenStop(); - FURI_LOG_E(TAG, "Failed to start listen mode"); - return false; - } - while(true) { - buff_rx_len = 0; - buff_tx_len = 0; - uint32_t flag = furi_event_flag_wait(event, EVENT_FLAG_ALL, FuriFlagWaitAny, timeout); - if(flag == (unsigned)FuriFlagErrorTimeout || flag == EVENT_FLAG_STOP) { - break; - } - bool data_received = false; - buff_rx_len = 0; - rfalWorker(); - rfalLmState state = rfalListenGetState(&data_received, NULL); - if(data_received) { - rfalTransceiveBlockingRx(); - if(nfca_emulation_handler(buff_rx, buff_rx_len, buff_tx, &buff_tx_len)) { - if(rfalListenSleepStart( - RFAL_LM_STATE_SLEEP_A, - buff_rx, - rfalConvBytesToBits(buff_rx_size), - &buff_rx_len)) { - FURI_LOG_E(TAG, "Failed to enter sleep mode"); - break; - } else { - continue; - } - } - if(buff_tx_len) { - ReturnCode ret = rfalTransceiveBitsBlockingTx( - buff_tx, - buff_tx_len, - buff_rx, - rfalConvBytesToBits(buff_rx_size), - &buff_rx_len, - data_type, - RFAL_FWT_NONE); - if(ret) { - FURI_LOG_E(TAG, "Tranceive failed with status %d", ret); - break; - } - continue; - } - if((state == RFAL_LM_STATE_ACTIVE_A || state == RFAL_LM_STATE_ACTIVE_Ax)) { - if(callback) { - callback(buff_rx, buff_rx_len, buff_tx, &buff_tx_len, &data_type, context); - } - if(!rfalIsExtFieldOn()) { - break; - } - if(buff_tx_len) { - if(buff_tx_len == UINT16_MAX) buff_tx_len = 0; - - ReturnCode ret = rfalTransceiveBitsBlockingTx( - buff_tx, - buff_tx_len, - buff_rx, - rfalConvBytesToBits(buff_rx_size), - &buff_rx_len, - data_type, - RFAL_FWT_NONE); - if(ret) { - FURI_LOG_E(TAG, "Tranceive failed with status %d", ret); - continue; - } - } else { - break; - } - } - } +FuriHalNfcError furi_hal_nfc_set_mode(FuriHalNfcMode mode, FuriHalNfcTech tech) { + furi_assert(mode < FuriHalNfcModeNum); + furi_assert(tech < FuriHalNfcTechNum); + + FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + + FuriHalNfcError error = FuriHalNfcErrorNone; + + if(mode == FuriHalNfcModePoller) { + do { + error = furi_hal_nfc_poller_init_common(handle); + if(error != FuriHalNfcErrorNone) break; + error = furi_hal_nfc_tech[tech]->poller.init(handle); + } while(false); + + } else if(mode == FuriHalNfcModeListener) { + do { + error = furi_hal_nfc_listener_init_common(handle); + if(error != FuriHalNfcErrorNone) break; + error = furi_hal_nfc_tech[tech]->listener.init(handle); + } while(false); } - rfalListenStop(); - return true; + + furi_hal_nfc.mode = mode; + furi_hal_nfc.tech = tech; + return error; } -static bool furi_hal_nfc_transparent_tx_rx(FuriHalNfcTxRxContext* tx_rx, uint16_t timeout_ms) { - furi_assert(tx_rx->nfca_signal); +FuriHalNfcError furi_hal_nfc_reset_mode() { + FuriHalNfcError error = FuriHalNfcErrorNone; + FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; - bool ret = false; + st25r3916_direct_cmd(handle, ST25R3916_CMD_STOP); - // Start transparent mode - st25r3916ExecuteCommand(ST25R3916_CMD_TRANSPARENT_MODE); - // Reconfigure gpio for Transparent mode - furi_hal_spi_bus_handle_deinit(&furi_hal_spi_bus_handle_nfc); + const FuriHalNfcMode mode = furi_hal_nfc.mode; + const FuriHalNfcTech tech = furi_hal_nfc.tech; + if(mode == FuriHalNfcModePoller) { + error = furi_hal_nfc_tech[tech]->poller.deinit(handle); + } else if(mode == FuriHalNfcModeListener) { + error = furi_hal_nfc_tech[tech]->listener.deinit(handle); + } + // Set default value in mode register + st25r3916_write_reg(handle, ST25R3916_REG_MODE, ST25R3916_REG_MODE_om0); + st25r3916_write_reg(handle, ST25R3916_REG_STREAM_MODE, 0); + st25r3916_clear_reg_bits(handle, ST25R3916_REG_AUX, ST25R3916_REG_AUX_no_crc_rx); + st25r3916_clear_reg_bits( + handle, + ST25R3916_REG_BIT_RATE, + ST25R3916_REG_BIT_RATE_txrate_mask | ST25R3916_REG_BIT_RATE_rxrate_mask); - // Send signal - FURI_CRITICAL_ENTER(); - 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_CRITICAL_EXIT(); - furi_hal_gpio_write(&gpio_spi_r_mosi, false); + // Write default values + st25r3916_write_reg(handle, ST25R3916_REG_RX_CONF1, 0); + st25r3916_write_reg( + handle, + ST25R3916_REG_RX_CONF2, + ST25R3916_REG_RX_CONF2_sqm_dyn | ST25R3916_REG_RX_CONF2_agc_en | + ST25R3916_REG_RX_CONF2_agc_m); - // 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); + st25r3916_write_reg( + handle, + ST25R3916_REG_CORR_CONF1, + ST25R3916_REG_CORR_CONF1_corr_s7 | ST25R3916_REG_CORR_CONF1_corr_s4 | + ST25R3916_REG_CORR_CONF1_corr_s1 | ST25R3916_REG_CORR_CONF1_corr_s0); + st25r3916_write_reg(handle, ST25R3916_REG_CORR_CONF2, 0); - // Manually wait for interrupt - furi_hal_gpio_init(&gpio_nfc_irq_rfid_pull, GpioModeInput, GpioPullDown, GpioSpeedVeryHigh); - st25r3916ClearAndEnableInterrupts(ST25R3916_IRQ_MASK_RXE); + return error; +} - if(tx_rx->sniff_tx) { - tx_rx->sniff_tx(tx_rx->tx_data, tx_rx->tx_bits, false, tx_rx->sniff_context); - } +FuriHalNfcError furi_hal_nfc_field_detect_start() { + FuriHalNfcError error = FuriHalNfcErrorNone; + FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; - uint32_t irq = 0; - uint8_t rxe = 0; - uint32_t start = DWT->CYCCNT; - while(true) { - if(!rfalIsExtFieldOn()) { - return false; - } - if(furi_hal_gpio_read(&gpio_nfc_irq_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_cortex_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); - - if(tx_rx->sniff_rx) { - tx_rx->sniff_rx(tx_rx->rx_data, tx_rx->rx_bits, false, tx_rx->sniff_context); - } + st25r3916_write_reg( + handle, + ST25R3916_REG_OP_CONTROL, + ST25R3916_REG_OP_CONTROL_en | ST25R3916_REG_OP_CONTROL_en_fd_mask); + st25r3916_write_reg( + handle, ST25R3916_REG_MODE, ST25R3916_REG_MODE_targ | ST25R3916_REG_MODE_om0); - ret = true; - } else { - FURI_LOG_E(TAG, "Timeout error"); - ret = false; - } + return error; +} + +FuriHalNfcError furi_hal_nfc_field_detect_stop() { + FuriHalNfcError error = FuriHalNfcErrorNone; + FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; - st25r3916ClearInterrupts(); + st25r3916_clear_reg_bits( + handle, + ST25R3916_REG_OP_CONTROL, + (ST25R3916_REG_OP_CONTROL_en | ST25R3916_REG_OP_CONTROL_en_fd_mask)); - return ret; + return error; } -static uint32_t furi_hal_nfc_tx_rx_get_flag(FuriHalNfcTxRxType type) { - uint32_t flags = 0; - - if(type == FuriHalNfcTxRxTypeRxNoCrc) { - flags = RFAL_TXRX_FLAGS_CRC_RX_KEEP; - } else if(type == FuriHalNfcTxRxTypeRxKeepPar) { - flags = RFAL_TXRX_FLAGS_CRC_TX_MANUAL | RFAL_TXRX_FLAGS_CRC_RX_KEEP | - RFAL_TXRX_FLAGS_PAR_RX_KEEP; - } 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; - } +bool furi_hal_nfc_field_is_present() { + bool is_present = false; + FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + + if(st25r3916_check_reg( + handle, + ST25R3916_REG_AUX_DISPLAY, + ST25R3916_REG_AUX_DISPLAY_efd_o, + ST25R3916_REG_AUX_DISPLAY_efd_o)) { + is_present = true; + } + + return is_present; +} + +FuriHalNfcError furi_hal_nfc_poller_field_on() { + FuriHalNfcError error = FuriHalNfcErrorNone; + FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + + if(!st25r3916_check_reg( + handle, + ST25R3916_REG_OP_CONTROL, + ST25R3916_REG_OP_CONTROL_tx_en, + ST25R3916_REG_OP_CONTROL_tx_en)) { + // Set min guard time + st25r3916_write_reg(handle, ST25R3916_REG_FIELD_ON_GT, 0); + // Enable tx rx + st25r3916_set_reg_bits( + handle, + ST25R3916_REG_OP_CONTROL, + (ST25R3916_REG_OP_CONTROL_rx_en | ST25R3916_REG_OP_CONTROL_tx_en)); + } + + return error; +} + +FuriHalNfcError furi_hal_nfc_poller_tx_common( + FuriHalSpiBusHandle* handle, + const uint8_t* tx_data, + size_t tx_bits) { + furi_assert(tx_data); + + FuriHalNfcError err = FuriHalNfcErrorNone; + + // Prepare tx + st25r3916_direct_cmd(handle, ST25R3916_CMD_CLEAR_FIFO); + st25r3916_clear_reg_bits( + handle, ST25R3916_REG_TIMER_EMV_CONTROL, ST25R3916_REG_TIMER_EMV_CONTROL_nrt_emv); + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_ISO14443A_NFC, + (ST25R3916_REG_ISO14443A_NFC_no_tx_par | ST25R3916_REG_ISO14443A_NFC_no_rx_par), + (ST25R3916_REG_ISO14443A_NFC_no_tx_par_off | ST25R3916_REG_ISO14443A_NFC_no_rx_par_off)); + uint32_t interrupts = + (ST25R3916_IRQ_MASK_FWL | ST25R3916_IRQ_MASK_TXE | ST25R3916_IRQ_MASK_RXS | + ST25R3916_IRQ_MASK_RXE | ST25R3916_IRQ_MASK_PAR | ST25R3916_IRQ_MASK_CRC | + ST25R3916_IRQ_MASK_ERR1 | ST25R3916_IRQ_MASK_ERR2 | ST25R3916_IRQ_MASK_NRE); + // Clear interrupts + st25r3916_get_irq(handle); + // Enable interrupts + st25r3916_mask_irq(handle, ~interrupts); - return flags; + st25r3916_write_fifo(handle, tx_data, tx_bits); + st25r3916_direct_cmd(handle, ST25R3916_CMD_TRANSMIT_WITHOUT_CRC); + + return err; } -static uint16_t furi_hal_nfc_data_and_parity_to_bitstream( - uint8_t* data, - uint16_t len, - uint8_t* parity, - uint8_t* out) { - furi_assert(data); - furi_assert(out); - - uint8_t next_par_bit = 0; - uint16_t curr_bit_pos = 0; - for(uint16_t i = 0; i < len; i++) { - next_par_bit = FURI_BIT(parity[i / 8], 7 - (i % 8)); - if(curr_bit_pos % 8 == 0) { - out[curr_bit_pos / 8] = data[i]; - curr_bit_pos += 8; - out[curr_bit_pos / 8] = next_par_bit; - curr_bit_pos++; - } else { - out[curr_bit_pos / 8] |= data[i] << (curr_bit_pos % 8); - out[curr_bit_pos / 8 + 1] = data[i] >> (8 - curr_bit_pos % 8); - out[curr_bit_pos / 8 + 1] |= next_par_bit << (curr_bit_pos % 8); - curr_bit_pos += 9; - } - } - return curr_bit_pos; +FuriHalNfcError furi_hal_nfc_common_fifo_tx( + FuriHalSpiBusHandle* handle, + const uint8_t* tx_data, + size_t tx_bits) { + FuriHalNfcError err = FuriHalNfcErrorNone; + + st25r3916_direct_cmd(handle, ST25R3916_CMD_CLEAR_FIFO); + st25r3916_write_fifo(handle, tx_data, tx_bits); + st25r3916_direct_cmd(handle, ST25R3916_CMD_TRANSMIT_WITHOUT_CRC); + + return err; } -uint16_t furi_hal_nfc_bitstream_to_data_and_parity( - uint8_t* in_buff, - uint16_t in_buff_bits, - uint8_t* out_data, - uint8_t* out_parity) { - if(in_buff_bits < 8) { - out_data[0] = in_buff[0]; - return in_buff_bits; - } - if(in_buff_bits % 9 != 0) { - return 0; - } +FuriHalNfcError furi_hal_nfc_poller_tx(const uint8_t* tx_data, size_t tx_bits) { + furi_assert(furi_hal_nfc.mode == FuriHalNfcModePoller); + furi_assert(furi_hal_nfc.tech < FuriHalNfcTechNum); + FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; - uint8_t curr_byte = 0; - uint16_t bit_processed = 0; - memset(out_parity, 0, in_buff_bits / 9); - while(bit_processed < in_buff_bits) { - out_data[curr_byte] = in_buff[bit_processed / 8] >> (bit_processed % 8); - out_data[curr_byte] |= in_buff[bit_processed / 8 + 1] << (8 - bit_processed % 8); - out_parity[curr_byte / 8] |= FURI_BIT(in_buff[bit_processed / 8 + 1], bit_processed % 8) - << (7 - curr_byte % 8); - bit_processed += 9; - curr_byte++; - } - return curr_byte * 8; + return furi_hal_nfc_tech[furi_hal_nfc.tech]->poller.tx(handle, tx_data, tx_bits); } -bool furi_hal_nfc_tx_rx(FuriHalNfcTxRxContext* tx_rx, uint16_t timeout_ms) { - furi_assert(tx_rx); +FuriHalNfcError furi_hal_nfc_poller_rx(uint8_t* rx_data, size_t rx_data_size, size_t* rx_bits) { + furi_assert(furi_hal_nfc.mode == FuriHalNfcModePoller); + furi_assert(furi_hal_nfc.tech < FuriHalNfcTechNum); + FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; - ReturnCode ret; - rfalNfcState state = RFAL_NFC_STATE_ACTIVATED; - uint8_t temp_tx_buff[FURI_HAL_NFC_DATA_BUFF_SIZE] = {}; - uint16_t temp_tx_bits = 0; - uint8_t* temp_rx_buff = NULL; - uint16_t* temp_rx_bits = NULL; + return furi_hal_nfc_tech[furi_hal_nfc.tech]->poller.rx(handle, rx_data, rx_data_size, rx_bits); +} - if(tx_rx->tx_rx_type == FuriHalNfcTxRxTransparent) { - return furi_hal_nfc_transparent_tx_rx(tx_rx, timeout_ms); - } +FuriHalNfcEvent furi_hal_nfc_poller_wait_event(uint32_t timeout_ms) { + furi_assert(furi_hal_nfc.mode == FuriHalNfcModePoller); + furi_assert(furi_hal_nfc.tech < FuriHalNfcTechNum); - // 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) { - temp_tx_bits = furi_hal_nfc_data_and_parity_to_bitstream( - tx_rx->tx_data, tx_rx->tx_bits / 8, tx_rx->tx_parity, temp_tx_buff); - ret = rfalNfcDataExchangeCustomStart( - temp_tx_buff, temp_tx_bits, &temp_rx_buff, &temp_rx_bits, RFAL_FWT_NONE, flags); - } else { - ret = rfalNfcDataExchangeCustomStart( - tx_rx->tx_data, tx_rx->tx_bits, &temp_rx_buff, &temp_rx_bits, RFAL_FWT_NONE, flags); - } - if(ret != ERR_NONE) { - FURI_LOG_E(TAG, "Failed to start data exchange"); - return false; - } + return furi_hal_nfc_tech[furi_hal_nfc.tech]->poller.wait_event(timeout_ms); +} - if(tx_rx->sniff_tx) { - bool crc_dropped = !(flags & RFAL_TXRX_FLAGS_CRC_TX_MANUAL); - tx_rx->sniff_tx(tx_rx->tx_data, tx_rx->tx_bits, crc_dropped, tx_rx->sniff_context); - } +FuriHalNfcEvent furi_hal_nfc_listener_wait_event(uint32_t timeout_ms) { + furi_assert(furi_hal_nfc.mode == FuriHalNfcModeListener); + furi_assert(furi_hal_nfc.tech < FuriHalNfcTechNum); - uint32_t start = DWT->CYCCNT; - while(state != RFAL_NFC_STATE_DATAEXCHANGE_DONE) { - rfalNfcWorker(); - state = rfalNfcGetState(); - ret = rfalNfcDataExchangeGetStatus(); - if(ret == ERR_WRONG_STATE) { - return false; - } else if(ret == ERR_BUSY) { - if(DWT->CYCCNT - start > timeout_ms * clocks_in_ms) { - FURI_LOG_D(TAG, "Timeout during data exchange"); - return false; - } - continue; - } else { - start = DWT->CYCCNT; - } - furi_delay_tick(1); - } + return furi_hal_nfc_tech[furi_hal_nfc.tech]->listener.wait_event(timeout_ms); +} - if(tx_rx->tx_rx_type == FuriHalNfcTxRxTypeRaw || - tx_rx->tx_rx_type == FuriHalNfcTxRxTypeRxRaw) { - tx_rx->rx_bits = furi_hal_nfc_bitstream_to_data_and_parity( - temp_rx_buff, *temp_rx_bits, tx_rx->rx_data, tx_rx->rx_parity); - } else { - memcpy(tx_rx->rx_data, temp_rx_buff, MIN(*temp_rx_bits / 8, FURI_HAL_NFC_DATA_BUFF_SIZE)); - tx_rx->rx_bits = *temp_rx_bits; - } +FuriHalNfcError furi_hal_nfc_listener_tx(const uint8_t* tx_data, size_t tx_bits) { + furi_assert(tx_data); - if(tx_rx->sniff_rx) { - bool crc_dropped = !(flags & RFAL_TXRX_FLAGS_CRC_RX_KEEP); - tx_rx->sniff_rx(tx_rx->rx_data, tx_rx->rx_bits, crc_dropped, tx_rx->sniff_context); - } + furi_assert(furi_hal_nfc.mode == FuriHalNfcModeListener); + furi_assert(furi_hal_nfc.tech < FuriHalNfcTechNum); - return true; + FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + return furi_hal_nfc_tech[furi_hal_nfc.tech]->listener.tx(handle, tx_data, tx_bits); } -bool furi_hal_nfc_tx_rx_full(FuriHalNfcTxRxContext* tx_rx) { - uint16_t part_len_bytes; +FuriHalNfcError furi_hal_nfc_common_fifo_rx( + FuriHalSpiBusHandle* handle, + uint8_t* rx_data, + size_t rx_data_size, + size_t* rx_bits) { + FuriHalNfcError error = FuriHalNfcErrorNone; - if(!furi_hal_nfc_tx_rx(tx_rx, 1000)) { - return false; - } - while(tx_rx->rx_bits && tx_rx->rx_data[0] == 0xAF) { - FuriHalNfcTxRxContext tmp = *tx_rx; - tmp.tx_data[0] = 0xAF; - tmp.tx_bits = 8; - if(!furi_hal_nfc_tx_rx(&tmp, 1000)) { - return false; - } - part_len_bytes = tmp.rx_bits / 8; - if(part_len_bytes > FURI_HAL_NFC_DATA_BUFF_SIZE - tx_rx->rx_bits / 8) { - FURI_LOG_W(TAG, "Overrun rx buf"); - return false; - } - if(part_len_bytes == 0) { - FURI_LOG_W(TAG, "Empty 0xAF response"); - return false; - } - memcpy(tx_rx->rx_data + tx_rx->rx_bits / 8, tmp.rx_data + 1, part_len_bytes - 1); - tx_rx->rx_data[0] = tmp.rx_data[0]; - tx_rx->rx_bits += 8 * (part_len_bytes - 1); + if(!st25r3916_read_fifo(handle, rx_data, rx_data_size, rx_bits)) { + error = FuriHalNfcErrorBufferOverflow; } - return true; + return error; } -void furi_hal_nfc_sleep() { - rfalNfcDeactivate(false); - rfalLowPowerModeStart(); -} +FuriHalNfcError furi_hal_nfc_listener_rx(uint8_t* rx_data, size_t rx_data_size, size_t* rx_bits) { + furi_assert(rx_data); + furi_assert(rx_bits); -FuriHalNfcReturn - furi_hal_nfc_ll_set_mode(FuriHalNfcMode mode, FuriHalNfcBitrate txBR, FuriHalNfcBitrate rxBR) { - return rfalSetMode((rfalMode)mode, (rfalBitRate)txBR, (rfalBitRate)rxBR); -} + furi_assert(furi_hal_nfc.mode == FuriHalNfcModeListener); + furi_assert(furi_hal_nfc.tech < FuriHalNfcTechNum); -void furi_hal_nfc_ll_set_error_handling(FuriHalNfcErrorHandling eHandling) { - rfalSetErrorHandling((rfalEHandling)eHandling); + FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + return furi_hal_nfc_tech[furi_hal_nfc.tech]->listener.rx( + handle, rx_data, rx_data_size, rx_bits); } -void furi_hal_nfc_ll_set_guard_time(uint32_t cycles) { - rfalSetGT(cycles); -} +FuriHalNfcError furi_hal_nfc_trx_reset() { + FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; -void furi_hal_nfc_ll_set_fdt_listen(uint32_t cycles) { - rfalSetFDTListen(cycles); -} + st25r3916_direct_cmd(handle, ST25R3916_CMD_STOP); -void furi_hal_nfc_ll_set_fdt_poll(uint32_t FDTPoll) { - rfalSetFDTPoll(FDTPoll); + return FuriHalNfcErrorNone; } -void furi_hal_nfc_ll_txrx_on() { - st25r3916TxRxOn(); -} +FuriHalNfcError furi_hal_nfc_listener_sleep() { + furi_assert(furi_hal_nfc.mode == FuriHalNfcModeListener); + furi_assert(furi_hal_nfc.tech < FuriHalNfcTechNum); -void furi_hal_nfc_ll_txrx_off() { - st25r3916TxRxOff(); -} + FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; -FuriHalNfcReturn furi_hal_nfc_ll_txrx( - uint8_t* txBuf, - uint16_t txBufLen, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* actLen, - uint32_t flags, - uint32_t fwt) { - return rfalTransceiveBlockingTxRx(txBuf, txBufLen, rxBuf, rxBufLen, actLen, flags, fwt); + return furi_hal_nfc_tech[furi_hal_nfc.tech]->listener.sleep(handle); } -FuriHalNfcReturn furi_hal_nfc_ll_txrx_bits( - uint8_t* txBuf, - uint16_t txBufLen, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* actLen, - uint32_t flags, - uint32_t fwt) { - return rfalTransceiveBitsBlockingTxRx(txBuf, txBufLen, rxBuf, rxBufLen, actLen, flags, fwt); -} +FuriHalNfcError furi_hal_nfc_listener_idle() { + furi_assert(furi_hal_nfc.mode == FuriHalNfcModeListener); + furi_assert(furi_hal_nfc.tech < FuriHalNfcTechNum); -void furi_hal_nfc_ll_poll() { - rfalWorker(); -} + FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; -void furi_hal_nfc_field_detect_start() { - st25r3916WriteRegister( - ST25R3916_REG_OP_CONTROL, - ST25R3916_REG_OP_CONTROL_en | ST25R3916_REG_OP_CONTROL_en_fd_mask); - st25r3916WriteRegister(ST25R3916_REG_MODE, ST25R3916_REG_MODE_targ | ST25R3916_REG_MODE_om0); + return furi_hal_nfc_tech[furi_hal_nfc.tech]->listener.idle(handle); } -bool furi_hal_nfc_field_is_present() { - return st25r3916CheckReg( - ST25R3916_REG_AUX_DISPLAY, - ST25R3916_REG_AUX_DISPLAY_efd_o, - ST25R3916_REG_AUX_DISPLAY_efd_o); -} \ No newline at end of file +FuriHalNfcError furi_hal_nfc_listener_enable_rx() { + FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + + st25r3916_direct_cmd(handle, ST25R3916_CMD_UNMASK_RECEIVE_DATA); + + return FuriHalNfcErrorNone; +} diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc.h b/firmware/targets/f7/furi_hal/furi_hal_nfc.h deleted file mode 100644 index f4051926a58..00000000000 --- a/firmware/targets/f7/furi_hal/furi_hal_nfc.h +++ /dev/null @@ -1,431 +0,0 @@ -/** - * @file furi_hal_nfc.h - * NFC HAL API - */ - -#pragma once - -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif -#include -#include - -#define FURI_HAL_NFC_UID_MAX_LEN 10 -#define FURI_HAL_NFC_DATA_BUFF_SIZE (512) -#define FURI_HAL_NFC_PARITY_BUFF_SIZE (FURI_HAL_NFC_DATA_BUFF_SIZE / 8) - -#define FURI_HAL_NFC_TXRX_DEFAULT \ - ((uint32_t)RFAL_TXRX_FLAGS_CRC_TX_AUTO | (uint32_t)RFAL_TXRX_FLAGS_CRC_RX_REMV | \ - (uint32_t)RFAL_TXRX_FLAGS_PAR_RX_REMV | (uint32_t)RFAL_TXRX_FLAGS_PAR_TX_AUTO) - -#define FURI_HAL_NFC_TX_DEFAULT_RX_NO_CRC \ - ((uint32_t)RFAL_TXRX_FLAGS_CRC_TX_AUTO | (uint32_t)RFAL_TXRX_FLAGS_CRC_RX_KEEP | \ - (uint32_t)RFAL_TXRX_FLAGS_PAR_RX_REMV | (uint32_t)RFAL_TXRX_FLAGS_PAR_TX_AUTO) - -#define FURI_HAL_NFC_TXRX_WITH_PAR \ - ((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_AUTO) - -#define FURI_HAL_NFC_TXRX_RAW \ - ((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)( - uint8_t* buff_rx, - uint16_t buff_rx_len, - uint8_t* buff_tx, - uint16_t* buff_tx_len, - uint32_t* flags, - void* context); - -typedef enum { - FuriHalNfcTypeA, - FuriHalNfcTypeB, - FuriHalNfcTypeF, - FuriHalNfcTypeV, -} FuriHalNfcType; - -typedef enum { - FuriHalNfcInterfaceRf, - FuriHalNfcInterfaceIsoDep, - FuriHalNfcInterfaceNfcDep, -} FuriHalNfcInterface; - -typedef struct { - FuriHalNfcType type; - FuriHalNfcInterface interface; - uint8_t uid_len; - uint8_t uid[10]; - uint32_t cuid; - uint8_t atqa[2]; - uint8_t sak; -} FuriHalNfcDevData; - -typedef void ( - *FuriHalNfcTxRxSniffCallback)(uint8_t* data, uint16_t bits, bool crc_dropped, void* context); - -typedef struct { - uint8_t tx_data[FURI_HAL_NFC_DATA_BUFF_SIZE]; - uint8_t tx_parity[FURI_HAL_NFC_PARITY_BUFF_SIZE]; - uint16_t tx_bits; - uint8_t rx_data[FURI_HAL_NFC_DATA_BUFF_SIZE]; - uint8_t rx_parity[FURI_HAL_NFC_PARITY_BUFF_SIZE]; - uint16_t rx_bits; - FuriHalNfcTxRxType tx_rx_type; - NfcaSignal* nfca_signal; - - FuriHalNfcTxRxSniffCallback sniff_tx; - FuriHalNfcTxRxSniffCallback sniff_rx; - void* sniff_context; -} FuriHalNfcTxRxContext; - -/** Init nfc - */ -void furi_hal_nfc_init(); - -/** Deinit nfc - */ -void furi_hal_nfc_deinit(); - -/** Check if nfc worker is busy - * - * @return true if busy - */ -bool furi_hal_nfc_is_busy(); - -/** Check if nfc is initialized - * - * @return true if initialized - */ -bool furi_hal_nfc_is_init(); - -/** NFC field on - */ -void furi_hal_nfc_field_on(); - -/** NFC field off - */ -void furi_hal_nfc_field_off(); - -/** NFC start sleep - */ -void furi_hal_nfc_start_sleep(); - -void furi_hal_nfc_stop_cmd(); - -/** NFC stop sleep - */ -void furi_hal_nfc_exit_sleep(); - -/** NFC poll - * - * @param dev_list pointer to rfalNfcDevice buffer - * @param dev_cnt pointer device count - * @param timeout timeout in ms - * @param deactivate deactivate flag - * - * @return true on success - */ -bool furi_hal_nfc_detect(FuriHalNfcDevData* nfc_data, uint32_t timeout); - -/** Activate NFC-A tag - * - * @param timeout timeout in ms - * @param cuid pointer to 32bit uid - * - * @return true on succeess - */ -bool furi_hal_nfc_activate_nfca(uint32_t timeout, uint32_t* cuid); - -/** NFC listen - * - * @param uid pointer to uid buffer - * @param uid_len uid length - * @param atqa pointer to atqa - * @param sak sak - * @param activate_after_sak activate after sak flag - * @param timeout timeout in ms - * - * @return true on success - */ -bool furi_hal_nfc_listen( - uint8_t* uid, - uint8_t uid_len, - uint8_t* atqa, - uint8_t sak, - bool activate_after_sak, - uint32_t timeout); - -/** Start Target Listen mode - * @note RFAL free implementation - * - * @param nfc_data FuriHalNfcDevData instance - */ -void furi_hal_nfc_listen_start(FuriHalNfcDevData* nfc_data); - -/** Read data in Target Listen mode - * @note Must be called only after furi_hal_nfc_listen_start() - * - * @param tx_rx FuriHalNfcTxRxContext instance - * @param timeout_ms timeout im ms - * - * @return true on not empty receive - */ -bool furi_hal_nfc_listen_rx(FuriHalNfcTxRxContext* tx_rx, uint32_t timeout_ms); - -/** Set Target in Sleep state */ -void furi_hal_nfc_listen_sleep(); - -/** Emulate NFC-A Target - * @note RFAL based implementation - * - * @param uid NFC-A UID - * @param uid_len NFC-A UID length - * @param atqa NFC-A ATQA - * @param sak NFC-A SAK - * @param callback FuriHalNfcEmulateCallback instance - * @param context pointer to context for callback - * @param timeout timeout in ms - * - * @return true on success - */ -bool furi_hal_nfc_emulate_nfca( - uint8_t* uid, - uint8_t uid_len, - uint8_t* atqa, - uint8_t sak, - FuriHalNfcEmulateCallback callback, - void* context, - uint32_t timeout); - -/** NFC data exchange - * - * @param tx_rx_ctx FuriHalNfcTxRxContext instance - * - * @return true on success - */ -bool furi_hal_nfc_tx_rx(FuriHalNfcTxRxContext* tx_rx, uint16_t timeout_ms); - -/** NFC data full exhange - * - * @param tx_rx_ctx FuriHalNfcTxRxContext instance - * - * @return true on success - */ -bool furi_hal_nfc_tx_rx_full(FuriHalNfcTxRxContext* tx_rx); - -/** NFC deactivate and start sleep - */ -void furi_hal_nfc_sleep(); - -void furi_hal_nfc_stop(); - -/* Low level transport API, use it to implement your own transport layers */ - -#define furi_hal_nfc_ll_ms2fc rfalConvMsTo1fc - -#define FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_TX_MANUAL RFAL_TXRX_FLAGS_CRC_TX_MANUAL -#define FURI_HAL_NFC_LL_TXRX_FLAGS_AGC_ON RFAL_TXRX_FLAGS_AGC_ON -#define FURI_HAL_NFC_LL_TXRX_FLAGS_PAR_RX_REMV RFAL_TXRX_FLAGS_PAR_RX_REMV -#define FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_RX_KEEP RFAL_TXRX_FLAGS_CRC_RX_KEEP - -typedef enum { - FuriHalNfcReturnOk = 0, /*!< no error occurred */ - FuriHalNfcReturnNomem = 1, /*!< not enough memory to perform the requested operation */ - FuriHalNfcReturnBusy = 2, /*!< device or resource busy */ - FuriHalNfcReturnIo = 3, /*!< generic IO error */ - FuriHalNfcReturnTimeout = 4, /*!< error due to timeout */ - FuriHalNfcReturnRequest = - 5, /*!< invalid request or requested function can't be executed at the moment */ - FuriHalNfcReturnNomsg = 6, /*!< No message of desired type */ - FuriHalNfcReturnParam = 7, /*!< Parameter error */ - FuriHalNfcReturnSystem = 8, /*!< System error */ - FuriHalNfcReturnFraming = 9, /*!< Framing error */ - FuriHalNfcReturnOverrun = 10, /*!< lost one or more received bytes */ - FuriHalNfcReturnProto = 11, /*!< protocol error */ - FuriHalNfcReturnInternal = 12, /*!< Internal Error */ - FuriHalNfcReturnAgain = 13, /*!< Call again */ - FuriHalNfcReturnMemCorrupt = 14, /*!< memory corruption */ - FuriHalNfcReturnNotImplemented = 15, /*!< not implemented */ - FuriHalNfcReturnPcCorrupt = - 16, /*!< Program Counter has been manipulated or spike/noise trigger illegal operation */ - FuriHalNfcReturnSend = 17, /*!< error sending*/ - FuriHalNfcReturnIgnore = 18, /*!< indicates error detected but to be ignored */ - FuriHalNfcReturnSemantic = 19, /*!< indicates error in state machine (unexpected cmd) */ - FuriHalNfcReturnSyntax = 20, /*!< indicates error in state machine (unknown cmd) */ - FuriHalNfcReturnCrc = 21, /*!< crc error */ - FuriHalNfcReturnNotfound = 22, /*!< transponder not found */ - FuriHalNfcReturnNotunique = - 23, /*!< transponder not unique - more than one transponder in field */ - FuriHalNfcReturnNotsupp = 24, /*!< requested operation not supported */ - FuriHalNfcReturnWrite = 25, /*!< write error */ - FuriHalNfcReturnFifo = 26, /*!< fifo over or underflow error */ - FuriHalNfcReturnPar = 27, /*!< parity error */ - FuriHalNfcReturnDone = 28, /*!< transfer has already finished */ - FuriHalNfcReturnRfCollision = - 29, /*!< collision error (Bit Collision or during RF Collision avoidance ) */ - FuriHalNfcReturnHwOverrun = 30, /*!< lost one or more received bytes */ - FuriHalNfcReturnReleaseReq = 31, /*!< device requested release */ - FuriHalNfcReturnSleepReq = 32, /*!< device requested sleep */ - FuriHalNfcReturnWrongState = 33, /*!< incorrent state for requested operation */ - FuriHalNfcReturnMaxReruns = 34, /*!< blocking procedure reached maximum runs */ - FuriHalNfcReturnDisabled = 35, /*!< operation aborted due to disabled configuration */ - FuriHalNfcReturnHwMismatch = 36, /*!< expected hw do not match */ - FuriHalNfcReturnLinkLoss = - 37, /*!< Other device's field didn't behave as expected: turned off by Initiator in Passive mode, or AP2P did not turn on field */ - FuriHalNfcReturnInvalidHandle = 38, /*!< invalid or not initalized device handle */ - FuriHalNfcReturnIncompleteByte = 40, /*!< Incomplete byte rcvd */ - FuriHalNfcReturnIncompleteByte01 = 41, /*!< Incomplete byte rcvd - 1 bit */ - FuriHalNfcReturnIncompleteByte02 = 42, /*!< Incomplete byte rcvd - 2 bit */ - FuriHalNfcReturnIncompleteByte03 = 43, /*!< Incomplete byte rcvd - 3 bit */ - FuriHalNfcReturnIncompleteByte04 = 44, /*!< Incomplete byte rcvd - 4 bit */ - FuriHalNfcReturnIncompleteByte05 = 45, /*!< Incomplete byte rcvd - 5 bit */ - FuriHalNfcReturnIncompleteByte06 = 46, /*!< Incomplete byte rcvd - 6 bit */ - FuriHalNfcReturnIncompleteByte07 = 47, /*!< Incomplete byte rcvd - 7 bit */ -} FuriHalNfcReturn; - -typedef enum { - FuriHalNfcModeNone = 0, /*!< No mode selected/defined */ - FuriHalNfcModePollNfca = 1, /*!< Mode to perform as NFCA (ISO14443A) Poller (PCD) */ - FuriHalNfcModePollNfcaT1t = 2, /*!< Mode to perform as NFCA T1T (Topaz) Poller (PCD) */ - FuriHalNfcModePollNfcb = 3, /*!< Mode to perform as NFCB (ISO14443B) Poller (PCD) */ - FuriHalNfcModePollBPrime = 4, /*!< Mode to perform as B' Calypso (Innovatron) (PCD) */ - FuriHalNfcModePollBCts = 5, /*!< Mode to perform as CTS Poller (PCD) */ - FuriHalNfcModePollNfcf = 6, /*!< Mode to perform as NFCF (FeliCa) Poller (PCD) */ - FuriHalNfcModePollNfcv = 7, /*!< Mode to perform as NFCV (ISO15963) Poller (PCD) */ - FuriHalNfcModePollPicopass = 8, /*!< Mode to perform as PicoPass / iClass Poller (PCD) */ - FuriHalNfcModePollActiveP2p = 9, /*!< Mode to perform as Active P2P (ISO18092) Initiator */ - FuriHalNfcModeListenNfca = 10, /*!< Mode to perform as NFCA (ISO14443A) Listener (PICC) */ - FuriHalNfcModeListenNfcb = 11, /*!< Mode to perform as NFCA (ISO14443B) Listener (PICC) */ - FuriHalNfcModeListenNfcf = 12, /*!< Mode to perform as NFCA (ISO15963) Listener (PICC) */ - FuriHalNfcModeListenActiveP2p = 13 /*!< Mode to perform as Active P2P (ISO18092) Target */ -} FuriHalNfcMode; - -typedef enum { - FuriHalNfcBitrate106 = 0, /*!< Bit Rate 106 kbit/s (fc/128) */ - FuriHalNfcBitrate212 = 1, /*!< Bit Rate 212 kbit/s (fc/64) */ - FuriHalNfcBitrate424 = 2, /*!< Bit Rate 424 kbit/s (fc/32) */ - FuriHalNfcBitrate848 = 3, /*!< Bit Rate 848 kbit/s (fc/16) */ - FuriHalNfcBitrate1695 = 4, /*!< Bit Rate 1695 kbit/s (fc/8) */ - FuriHalNfcBitrate3390 = 5, /*!< Bit Rate 3390 kbit/s (fc/4) */ - FuriHalNfcBitrate6780 = 6, /*!< Bit Rate 6780 kbit/s (fc/2) */ - FuriHalNfcBitrate13560 = 7, /*!< Bit Rate 13560 kbit/s (fc) */ - FuriHalNfcBitrate52p97 = 0xEB, /*!< Bit Rate 52.97 kbit/s (fc/256) Fast Mode VICC->VCD */ - FuriHalNfcBitrate26p48 = - 0xEC, /*!< Bit Rate 26,48 kbit/s (fc/512) NFCV VICC->VCD & VCD->VICC 1of4 */ - FuriHalNfcBitrate1p66 = 0xED, /*!< Bit Rate 1,66 kbit/s (fc/8192) NFCV VCD->VICC 1of256 */ - FuriHalNfcBitrateKeep = 0xFF /*!< Value indicating to keep the same previous bit rate */ -} FuriHalNfcBitrate; - -FuriHalNfcReturn - furi_hal_nfc_ll_set_mode(FuriHalNfcMode mode, FuriHalNfcBitrate txBR, FuriHalNfcBitrate rxBR); - -#define FURI_HAL_NFC_LL_GT_NFCA furi_hal_nfc_ll_ms2fc(5U) /*!< GTA Digital 2.0 6.10.4.1 & B.2 */ -#define FURI_HAL_NFC_LL_GT_NFCB furi_hal_nfc_ll_ms2fc(5U) /*!< GTB Digital 2.0 7.9.4.1 & B.3 */ -#define FURI_HAL_NFC_LL_GT_NFCF furi_hal_nfc_ll_ms2fc(20U) /*!< GTF Digital 2.0 8.7.4.1 & B.4 */ -#define FURI_HAL_NFC_LL_GT_NFCV furi_hal_nfc_ll_ms2fc(5U) /*!< GTV Digital 2.0 9.7.5.1 & B.5 */ -#define FURI_HAL_NFC_LL_GT_PICOPASS furi_hal_nfc_ll_ms2fc(1U) /*!< GT Picopass */ -#define FURI_HAL_NFC_LL_GT_AP2P furi_hal_nfc_ll_ms2fc(5U) /*!< TIRFG Ecma 340 11.1.1 */ -#define FURI_HAL_NFC_LL_GT_AP2P_ADJUSTED \ - furi_hal_nfc_ll_ms2fc( \ - 5U + \ - 25U) /*!< Adjusted GT for greater interoperability (Sony XPERIA P, Nokia N9, Huawei P2) */ - -void furi_hal_nfc_ll_set_guard_time(uint32_t cycles); - -typedef enum { - FuriHalNfcErrorHandlingNone = 0, /*!< No special error handling will be performed */ - FuriHalNfcErrorHandlingNfc = 1, /*!< Error handling set to perform as NFC compliant device */ - FuriHalNfcErrorHandlingEmvco = - 2 /*!< Error handling set to perform as EMVCo compliant device */ -} FuriHalNfcErrorHandling; - -void furi_hal_nfc_ll_set_error_handling(FuriHalNfcErrorHandling eHandling); - -/* RFAL Frame Delay Time (FDT) Listen default values */ -#define FURI_HAL_NFC_LL_FDT_LISTEN_NFCA_POLLER \ - 1172U /*!< FDTA,LISTEN,MIN (n=9) Last bit: Logic "1" - tnn,min/2 Digital 1.1 6.10 ; EMV CCP Spec Book D v2.01 4.8.1.3 */ -#define FURI_HAL_NFC_LL_FDT_LISTEN_NFCB_POLLER \ - 1008U /*!< TR0B,MIN Digital 1.1 7.1.3 & A.3 ; EMV CCP Spec Book D v2.01 4.8.1.3 & Table A.5 */ -#define FURI_HAL_NFC_LL_FDT_LISTEN_NFCF_POLLER \ - 2672U /*!< TR0F,LISTEN,MIN Digital 1.1 8.7.1.1 & A.4 */ -#define FURI_HAL_NFC_LL_FDT_LISTEN_NFCV_POLLER \ - 4310U /*!< FDTV,LISTEN,MIN t1 min Digital 2.1 B.5 ; ISO15693-3 2009 9.1 */ -#define FURI_HAL_NFC_LL_FDT_LISTEN_PICOPASS_POLLER \ - 3400U /*!< ISO15693 t1 min - observed adjustment */ -#define FURI_HAL_NFC_LL_FDT_LISTEN_AP2P_POLLER \ - 64U /*!< FDT AP2P No actual FDTListen is required as fields switch and collision avoidance */ -#define FURI_HAL_NFC_LL_FDT_LISTEN_NFCA_LISTENER 1172U /*!< FDTA,LISTEN,MIN Digital 1.1 6.10 */ -#define FURI_HAL_NFC_LL_FDT_LISTEN_NFCB_LISTENER \ - 1024U /*!< TR0B,MIN Digital 1.1 7.1.3 & A.3 ; EMV CCP Spec Book D v2.01 4.8.1.3 & Table A.5 */ -#define FURI_HAL_NFC_LL_FDT_LISTEN_NFCF_LISTENER \ - 2688U /*!< TR0F,LISTEN,MIN Digital 2.1 8.7.1.1 & B.4 */ -#define FURI_HAL_NFC_LL_FDT_LISTEN_AP2P_LISTENER \ - 64U /*!< FDT AP2P No actual FDTListen exists as fields switch and collision avoidance */ - -void furi_hal_nfc_ll_set_fdt_listen(uint32_t cycles); - -/* RFAL Frame Delay Time (FDT) Poll default values */ -#define FURI_HAL_NFC_LL_FDT_POLL_NFCA_POLLER \ - 6780U /*!< FDTA,POLL,MIN Digital 1.1 6.10.3.1 & A.2 */ -#define FURI_HAL_NFC_LL_FDT_POLL_NFCA_T1T_POLLER \ - 384U /*!< RRDDT1T,MIN,B1 Digital 1.1 10.7.1 & A.5 */ -#define FURI_HAL_NFC_LL_FDT_POLL_NFCB_POLLER \ - 6780U /*!< FDTB,POLL,MIN = TR2B,MIN,DEFAULT Digital 1.1 7.9.3 & A.3 ; EMVCo 3.0 FDTB,PCD,MIN Table A.5 */ -#define FURI_HAL_NFC_LL_FDT_POLL_NFCF_POLLER 6800U /*!< FDTF,POLL,MIN Digital 2.1 8.7.3 & B.4 */ -#define FURI_HAL_NFC_LL_FDT_POLL_NFCV_POLLER 4192U /*!< FDTV,POLL Digital 2.1 9.7.3.1 & B.5 */ -#define FURI_HAL_NFC_LL_FDT_POLL_PICOPASS_POLLER 1790U /*!< FDT Max */ -#define FURI_HAL_NFC_LL_FDT_POLL_AP2P_POLLER \ - 0U /*!< FDT AP2P No actual FDTPoll exists as fields switch and collision avoidance */ - -void furi_hal_nfc_ll_set_fdt_poll(uint32_t FDTPoll); - -void furi_hal_nfc_ll_txrx_on(); - -void furi_hal_nfc_ll_txrx_off(); - -FuriHalNfcReturn furi_hal_nfc_ll_txrx( - uint8_t* txBuf, - uint16_t txBufLen, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* actLen, - uint32_t flags, - uint32_t fwt); - -FuriHalNfcReturn furi_hal_nfc_ll_txrx_bits( - uint8_t* txBuf, - uint16_t txBufLen, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* actLen, - uint32_t flags, - uint32_t fwt); - -void furi_hal_nfc_ll_poll(); - -void furi_hal_nfc_field_detect_start(); - -bool furi_hal_nfc_field_is_present(); - -#ifdef __cplusplus -} -#endif diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc_event.c b/firmware/targets/f7/furi_hal/furi_hal_nfc_event.c new file mode 100644 index 00000000000..cce16c5dc9b --- /dev/null +++ b/firmware/targets/f7/furi_hal/furi_hal_nfc_event.c @@ -0,0 +1,116 @@ +#include + +FuriHalNfcEventInternal* furi_hal_nfc_event = NULL; + +void furi_hal_nfc_event_init() { + furi_hal_nfc_event = malloc(sizeof(FuriHalNfcEventInternal)); +} + +FuriHalNfcError furi_hal_nfc_event_start() { + furi_assert(furi_hal_nfc_event); + + furi_hal_nfc_event->thread = furi_thread_get_current_id(); + furi_thread_flags_clear(FURI_HAL_NFC_EVENT_INTERNAL_ALL); + + return FuriHalNfcErrorNone; +} + +FuriHalNfcError furi_hal_nfc_event_stop() { + furi_assert(furi_hal_nfc_event); + + furi_hal_nfc_event->thread = NULL; + + return FuriHalNfcErrorNone; +} + +void furi_hal_nfc_event_set(FuriHalNfcEventInternalType event) { + furi_assert(furi_hal_nfc_event); + furi_assert(furi_hal_nfc_event->thread); + + furi_thread_flags_set(furi_hal_nfc_event->thread, event); +} + +FuriHalNfcError furi_hal_nfc_abort() { + furi_hal_nfc_event_set(FuriHalNfcEventInternalTypeAbort); + return FuriHalNfcErrorNone; +} + +FuriHalNfcEvent furi_hal_nfc_wait_event_common(uint32_t timeout_ms) { + furi_assert(furi_hal_nfc_event); + furi_assert(furi_hal_nfc_event->thread); + + FuriHalNfcEvent event = 0; + uint32_t event_timeout = timeout_ms == FURI_HAL_NFC_EVENT_WAIT_FOREVER ? FuriWaitForever : + timeout_ms; + uint32_t event_flag = + furi_thread_flags_wait(FURI_HAL_NFC_EVENT_INTERNAL_ALL, FuriFlagWaitAny, event_timeout); + if(event_flag != (unsigned)FuriFlagErrorTimeout) { + if(event_flag & FuriHalNfcEventInternalTypeIrq) { + furi_thread_flags_clear(FuriHalNfcEventInternalTypeIrq); + FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + uint32_t irq = furi_hal_nfc_get_irq(handle); + if(irq & ST25R3916_IRQ_MASK_OSC) { + event |= FuriHalNfcEventOscOn; + } + if(irq & ST25R3916_IRQ_MASK_TXE) { + event |= FuriHalNfcEventTxEnd; + } + if(irq & ST25R3916_IRQ_MASK_RXS) { + event |= FuriHalNfcEventRxStart; + } + if(irq & ST25R3916_IRQ_MASK_RXE) { + event |= FuriHalNfcEventRxEnd; + } + if(irq & ST25R3916_IRQ_MASK_COL) { + event |= FuriHalNfcEventCollision; + } + if(irq & ST25R3916_IRQ_MASK_EON) { + event |= FuriHalNfcEventFieldOn; + } + if(irq & ST25R3916_IRQ_MASK_EOF) { + event |= FuriHalNfcEventFieldOff; + } + if(irq & ST25R3916_IRQ_MASK_WU_A) { + event |= FuriHalNfcEventListenerActive; + } + if(irq & ST25R3916_IRQ_MASK_WU_A_X) { + event |= FuriHalNfcEventListenerActive; + } + } + if(event_flag & FuriHalNfcEventInternalTypeTimerFwtExpired) { + event |= FuriHalNfcEventTimerFwtExpired; + furi_thread_flags_clear(FuriHalNfcEventInternalTypeTimerFwtExpired); + } + if(event_flag & FuriHalNfcEventInternalTypeTimerBlockTxExpired) { + event |= FuriHalNfcEventTimerBlockTxExpired; + furi_thread_flags_clear(FuriHalNfcEventInternalTypeTimerBlockTxExpired); + } + if(event_flag & FuriHalNfcEventInternalTypeAbort) { + event |= FuriHalNfcEventAbortRequest; + furi_thread_flags_clear(FuriHalNfcEventInternalTypeAbort); + } + } else { + event = FuriHalNfcEventTimeout; + } + + return event; +} + +bool furi_hal_nfc_event_wait_for_specific_irq( + FuriHalSpiBusHandle* handle, + uint32_t mask, + uint32_t timeout_ms) { + furi_assert(furi_hal_nfc_event); + furi_assert(furi_hal_nfc_event->thread); + + bool irq_received = false; + uint32_t event_flag = + furi_thread_flags_wait(FuriHalNfcEventInternalTypeIrq, FuriFlagWaitAny, timeout_ms); + if(event_flag == FuriHalNfcEventInternalTypeIrq) { + uint32_t irq = furi_hal_nfc_get_irq(handle); + irq_received = ((irq & mask) == mask); + furi_thread_flags_clear(FuriHalNfcEventInternalTypeIrq); + } + + return irq_received; +} diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc_felica.c b/firmware/targets/f7/furi_hal/furi_hal_nfc_felica.c new file mode 100644 index 00000000000..e4b8ac0ee6b --- /dev/null +++ b/firmware/targets/f7/furi_hal/furi_hal_nfc_felica.c @@ -0,0 +1,69 @@ +#include "furi_hal_nfc_i.h" +#include "furi_hal_nfc_tech_i.h" + +static FuriHalNfcError furi_hal_nfc_felica_poller_init(FuriHalSpiBusHandle* handle) { + // Enable Felica mode, AM modulation + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_MODE, + ST25R3916_REG_MODE_om_mask | ST25R3916_REG_MODE_tr_am, + ST25R3916_REG_MODE_om_felica | ST25R3916_REG_MODE_tr_am_am); + + // 10% ASK modulation + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_TX_DRIVER, + ST25R3916_REG_TX_DRIVER_am_mod_mask, + ST25R3916_REG_TX_DRIVER_am_mod_10percent); + + // Use regulator AM, resistive AM disabled + st25r3916_clear_reg_bits( + handle, + ST25R3916_REG_AUX_MOD, + ST25R3916_REG_AUX_MOD_dis_reg_am | ST25R3916_REG_AUX_MOD_res_am); + + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_BIT_RATE, + ST25R3916_REG_BIT_RATE_txrate_mask | ST25R3916_REG_BIT_RATE_rxrate_mask, + ST25R3916_REG_BIT_RATE_txrate_212 | ST25R3916_REG_BIT_RATE_rxrate_212); + + // Receive configuration + st25r3916_write_reg( + handle, + ST25R3916_REG_RX_CONF1, + ST25R3916_REG_RX_CONF1_lp0 | ST25R3916_REG_RX_CONF1_hz_12_80khz); + + // Correlator setup + st25r3916_write_reg( + handle, + ST25R3916_REG_CORR_CONF1, + ST25R3916_REG_CORR_CONF1_corr_s6 | ST25R3916_REG_CORR_CONF1_corr_s4 | + ST25R3916_REG_CORR_CONF1_corr_s3); + + return FuriHalNfcErrorNone; +} + +static FuriHalNfcError furi_hal_nfc_felica_poller_deinit(FuriHalSpiBusHandle* handle) { + UNUSED(handle); + + return FuriHalNfcErrorNone; +} + +const FuriHalNfcTechBase furi_hal_nfc_felica = { + .poller = + { + .compensation = + { + .fdt = FURI_HAL_NFC_POLLER_FDT_COMP_FC, + .fwt = FURI_HAL_NFC_POLLER_FWT_COMP_FC, + }, + .init = furi_hal_nfc_felica_poller_init, + .deinit = furi_hal_nfc_felica_poller_deinit, + .wait_event = furi_hal_nfc_wait_event_common, + .tx = furi_hal_nfc_poller_tx_common, + .rx = furi_hal_nfc_common_fifo_rx, + }, + + .listener = {}, +}; diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc_i.h b/firmware/targets/f7/furi_hal/furi_hal_nfc_i.h new file mode 100644 index 00000000000..53a25644d0d --- /dev/null +++ b/firmware/targets/f7/furi_hal/furi_hal_nfc_i.h @@ -0,0 +1,191 @@ +/** + * @file furi_hal_nfc_i.h + * @brief NFC HAL library (private definitions). + * + * This file is an implementation detail. It must not be included in + * any public API-related headers. + */ +#pragma once + +#include +#include +#include + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** @brief Common frame delay time compensation for pollers. */ +#define FURI_HAL_NFC_POLLER_FDT_COMP_FC (-500) +/** @brief Common frame wait time compensation for pollers. */ +#define FURI_HAL_NFC_POLLER_FWT_COMP_FC (FURI_HAL_NFC_POLLER_FDT_COMP_FC) + +/** + * @brief Enumeration containing bitmask values for NFC HAL internal events. + */ +typedef enum { + FuriHalNfcEventInternalTypeAbort = (1U << 0), /**< Abort waiting for hardware events. */ + FuriHalNfcEventInternalTypeIrq = (1U << 1), /**< NFC hardware interrupt has occurred. */ + FuriHalNfcEventInternalTypeTimerFwtExpired = + (1U << 2), /**< Frame wait time timeout has expired. */ + FuriHalNfcEventInternalTypeTimerBlockTxExpired = + (1U << 3), /**< Transmission block timeout has expired. */ + FuriHalNfcEventInternalTypeTransparentDataReceived = + (1U << 4), /**< Data was received in transparent mode. */ +} FuriHalNfcEventInternalType; + +/** @brief Special bitmask value of all internal events. */ +#define FURI_HAL_NFC_EVENT_INTERNAL_ALL \ + ((FuriHalNfcEventInternalTypeAbort | FuriHalNfcEventInternalTypeIrq | \ + FuriHalNfcEventInternalTypeTimerFwtExpired | \ + FuriHalNfcEventInternalTypeTimerBlockTxExpired | \ + FuriHalNfcEventInternalTypeTransparentDataReceived)) + +/** + * @brief NFC HAL internal event structure. + */ +typedef struct { + FuriThreadId thread; /**< Identifier of the thread that will be receiving events. */ + void* context; /**< Pointer to the user-provided context (will be passed to the event callback). */ +} FuriHalNfcEventInternal; + +/** + * @brief NFC HAL global state structure. + */ +typedef struct { + FuriMutex* mutex; /**< Pointer to the mutex serving as global NFC HAL lock. */ + FuriHalNfcMode mode; /**< Currently selected operating mode. */ + FuriHalNfcTech tech; /**< Currently selected NFC technology. */ +} FuriHalNfc; + +/** + * @brief NFC HAL global state object declaration. + */ +extern FuriHalNfc furi_hal_nfc; + +/** + * @brief Initialise NFC HAL event system. + */ +void furi_hal_nfc_event_init(); + +/** + * @brief Forcibly emit (a) particular internal event(s). + * + * @param[in] event bitmask of one or more events to be emitted. + */ +void furi_hal_nfc_event_set(FuriHalNfcEventInternalType event); + +/** + * @brief Initialise GPIO to generate an interrupt from the NFC hardware. + */ +void furi_hal_nfc_init_gpio_isr(); + +/** + * @brief Disable interrupts from the NFC hardware. + */ +void furi_hal_nfc_deinit_gpio_isr(); + +/** + * @brief Initialise all NFC timers. + */ +void furi_hal_nfc_timers_init(); + +/** + * @brief Disable all NFC timers. + */ +void furi_hal_nfc_timers_deinit(); + +/** + * @brief Get the interrupt bitmask from the NFC hardware. + * + * @param[in,out] handle pointer to the SPI handle associated with the NFC chip. + * @returns bitmask of zero or more occurred interrupts. + */ +uint32_t furi_hal_nfc_get_irq(FuriHalSpiBusHandle* handle); + +/** + * @brief Wait until a specified type of interrupt occurs. + * + * @param[in,out] handle pointer to the SPI handle associated with the NFC chip. + * @param[in] mask bitmask of one or more interrupts to wait for. + * @param[in] timeout_ms maximum time to wait for an interrupt, in milliseconds. + * @returns true if specified interrupt(s) have occured within timeout, false otherwise. + */ +bool furi_hal_nfc_event_wait_for_specific_irq( + FuriHalSpiBusHandle* handle, + uint32_t mask, + uint32_t timeout_ms); + +/** + * @brief Wait for any event to occur. + * + * This function is common to all technologies. + * + * @param[in] timeout_ms maximum time to wait for an event, in milliseconds. + * @returns bitmask of zero or more occurred events. + */ +FuriHalNfcEvent furi_hal_nfc_wait_event_common(uint32_t timeout_ms); + +/** + * @brief Start reception in listener mode. + * + * This function is common to all technologies. + * + * @param[in,out] handle pointer to the SPI handle associated with the NFC chip. + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +FuriHalNfcError furi_hal_nfc_common_listener_rx_start(FuriHalSpiBusHandle* handle); + +/** + * @brief Transmit data using on-chip FIFO. + * + * This function is common to all technologies. + * + * @param[in,out] handle pointer to the SPI handle associated with the NFC chip. + * @param[in] tx_data pointer to a byte array containing the data to be transmitted. + * @param[in] tx_bits transmit data size, in bits. + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +FuriHalNfcError furi_hal_nfc_common_fifo_tx( + FuriHalSpiBusHandle* handle, + const uint8_t* tx_data, + size_t tx_bits); + +/** + * @brief Receive data using on-chip FIFO. + * + * This function is common to all technologies. + * + * @param[in,out] handle pointer to the SPI handle associated with the NFC chip. + * @param[out] rx_data pointer to a byte array to be filled with received data. + * @param[in] rx_data_size maximum received data size, in bytes. + * @param[out] rx_bits pointer to the variable to contain the received data size, in bits. + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +FuriHalNfcError furi_hal_nfc_common_fifo_rx( + FuriHalSpiBusHandle* handle, + uint8_t* rx_data, + size_t rx_data_size, + size_t* rx_bits); + +/** + * @brief Transmit data in poller mode. + * + * This function is common to all technologies. + * + * @param[in,out] handle pointer to the SPI handle associated with the NFC chip. + * @param[in] tx_data pointer to a byte array containing the data to be transmitted. + * @param[in] tx_bits transmit data size, in bits. + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +FuriHalNfcError furi_hal_nfc_poller_tx_common( + FuriHalSpiBusHandle* handle, + const uint8_t* tx_data, + size_t tx_bits); + +#ifdef __cplusplus +} +#endif diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc_irq.c b/firmware/targets/f7/furi_hal/furi_hal_nfc_irq.c new file mode 100644 index 00000000000..63dc3541550 --- /dev/null +++ b/firmware/targets/f7/furi_hal/furi_hal_nfc_irq.c @@ -0,0 +1,28 @@ +#include "furi_hal_nfc_i.h" + +#include +#include + +static void furi_hal_nfc_int_callback() { + furi_hal_nfc_event_set(FuriHalNfcEventInternalTypeIrq); +} + +uint32_t furi_hal_nfc_get_irq(FuriHalSpiBusHandle* handle) { + uint32_t irq = 0; + while(furi_hal_gpio_read_port_pin(gpio_nfc_irq_rfid_pull.port, gpio_nfc_irq_rfid_pull.pin)) { + irq |= st25r3916_get_irq(handle); + } + return irq; +} + +void furi_hal_nfc_init_gpio_isr() { + furi_hal_gpio_init( + &gpio_nfc_irq_rfid_pull, GpioModeInterruptRise, GpioPullDown, GpioSpeedVeryHigh); + furi_hal_gpio_add_int_callback(&gpio_nfc_irq_rfid_pull, furi_hal_nfc_int_callback, NULL); + furi_hal_gpio_enable_int_callback(&gpio_nfc_irq_rfid_pull); +} + +void furi_hal_nfc_deinit_gpio_isr() { + furi_hal_gpio_init(&gpio_nfc_irq_rfid_pull, GpioModeOutputOpenDrain, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_remove_int_callback(&gpio_nfc_irq_rfid_pull); +} diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc_iso14443a.c b/firmware/targets/f7/furi_hal/furi_hal_nfc_iso14443a.c new file mode 100644 index 00000000000..278ecf38496 --- /dev/null +++ b/firmware/targets/f7/furi_hal/furi_hal_nfc_iso14443a.c @@ -0,0 +1,356 @@ +#include "furi_hal_nfc_i.h" +#include "furi_hal_nfc_tech_i.h" + +#include +#include + +#include + +#define TAG "FuriHalIso14443a" + +// Prevent FDT timer from starting +#define FURI_HAL_NFC_ISO14443A_LISTENER_FDT_COMP_FC (INT32_MAX) + +static Iso14443_3aSignal* iso14443_3a_signal = NULL; + +static FuriHalNfcError furi_hal_nfc_iso14443a_common_init(FuriHalSpiBusHandle* handle) { + // Common NFC-A settings, 106 kbps + + // 1st stage zero = 600kHz, 3rd stage zero = 200 kHz + st25r3916_write_reg(handle, ST25R3916_REG_RX_CONF1, ST25R3916_REG_RX_CONF1_z600k); + // AGC enabled, ratio 3:1, squelch after TX + st25r3916_write_reg( + handle, + ST25R3916_REG_RX_CONF2, + ST25R3916_REG_RX_CONF2_agc6_3 | ST25R3916_REG_RX_CONF2_agc_m | + ST25R3916_REG_RX_CONF2_agc_en | ST25R3916_REG_RX_CONF2_sqm_dyn); + // HF operation, full gain on AM and PM channels + st25r3916_write_reg(handle, ST25R3916_REG_RX_CONF3, 0x00); + // No gain reduction on AM and PM channels + st25r3916_write_reg(handle, ST25R3916_REG_RX_CONF4, 0x00); + // Correlator config + st25r3916_write_reg( + handle, + ST25R3916_REG_CORR_CONF1, + ST25R3916_REG_CORR_CONF1_corr_s0 | ST25R3916_REG_CORR_CONF1_corr_s4 | + ST25R3916_REG_CORR_CONF1_corr_s6); + // Sleep mode disable, 424kHz mode off + st25r3916_write_reg(handle, ST25R3916_REG_CORR_CONF2, 0x00); + + return FuriHalNfcErrorNone; +} + +static FuriHalNfcError furi_hal_nfc_iso14443a_poller_init(FuriHalSpiBusHandle* handle) { + // Enable ISO14443A mode, OOK modulation + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_MODE, + ST25R3916_REG_MODE_om_mask | ST25R3916_REG_MODE_tr_am, + ST25R3916_REG_MODE_om_iso14443a | ST25R3916_REG_MODE_tr_am_ook); + + // Overshoot protection - is this necessary here? + st25r3916_change_reg_bits(handle, ST25R3916_REG_OVERSHOOT_CONF1, 0xff, 0x40); + st25r3916_change_reg_bits(handle, ST25R3916_REG_OVERSHOOT_CONF2, 0xff, 0x03); + st25r3916_change_reg_bits(handle, ST25R3916_REG_UNDERSHOOT_CONF1, 0xff, 0x40); + st25r3916_change_reg_bits(handle, ST25R3916_REG_UNDERSHOOT_CONF2, 0xff, 0x03); + + return furi_hal_nfc_iso14443a_common_init(handle); +} + +static FuriHalNfcError furi_hal_nfc_iso14443a_poller_deinit(FuriHalSpiBusHandle* handle) { + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_ISO14443A_NFC, + (ST25R3916_REG_ISO14443A_NFC_no_tx_par | ST25R3916_REG_ISO14443A_NFC_no_rx_par), + (ST25R3916_REG_ISO14443A_NFC_no_tx_par_off | ST25R3916_REG_ISO14443A_NFC_no_rx_par_off)); + + return FuriHalNfcErrorNone; +} + +static FuriHalNfcError furi_hal_nfc_iso14443a_listener_init(FuriHalSpiBusHandle* handle) { + furi_check(iso14443_3a_signal == NULL); + iso14443_3a_signal = iso14443_3a_signal_alloc(&gpio_spi_r_mosi); + + st25r3916_write_reg( + handle, + ST25R3916_REG_OP_CONTROL, + ST25R3916_REG_OP_CONTROL_en | ST25R3916_REG_OP_CONTROL_rx_en | + ST25R3916_REG_OP_CONTROL_en_fd_auto_efd); + st25r3916_write_reg( + handle, ST25R3916_REG_MODE, ST25R3916_REG_MODE_targ_targ | ST25R3916_REG_MODE_om0); + st25r3916_write_reg( + handle, + ST25R3916_REG_PASSIVE_TARGET, + ST25R3916_REG_PASSIVE_TARGET_fdel_2 | ST25R3916_REG_PASSIVE_TARGET_fdel_0 | + ST25R3916_REG_PASSIVE_TARGET_d_ac_ap2p | ST25R3916_REG_PASSIVE_TARGET_d_212_424_1r); + + st25r3916_write_reg(handle, ST25R3916_REG_MASK_RX_TIMER, 0x02); + + st25r3916_direct_cmd(handle, ST25R3916_CMD_STOP); + uint32_t interrupts = + (ST25R3916_IRQ_MASK_FWL | ST25R3916_IRQ_MASK_TXE | ST25R3916_IRQ_MASK_RXS | + ST25R3916_IRQ_MASK_RXE | ST25R3916_IRQ_MASK_PAR | ST25R3916_IRQ_MASK_CRC | + ST25R3916_IRQ_MASK_ERR1 | ST25R3916_IRQ_MASK_ERR2 | ST25R3916_IRQ_MASK_NRE | + ST25R3916_IRQ_MASK_EON | ST25R3916_IRQ_MASK_EOF | ST25R3916_IRQ_MASK_WU_A_X | + ST25R3916_IRQ_MASK_WU_A); + // Clear interrupts + st25r3916_get_irq(handle); + // Enable interrupts + st25r3916_mask_irq(handle, ~interrupts); + // Enable auto collision resolution + st25r3916_clear_reg_bits( + handle, ST25R3916_REG_PASSIVE_TARGET, ST25R3916_REG_PASSIVE_TARGET_d_106_ac_a); + st25r3916_direct_cmd(handle, ST25R3916_CMD_GOTO_SENSE); + + return furi_hal_nfc_iso14443a_common_init(handle); +} + +static FuriHalNfcError furi_hal_nfc_iso14443a_listener_deinit(FuriHalSpiBusHandle* handle) { + UNUSED(handle); + + if(iso14443_3a_signal) { + iso14443_3a_signal_free(iso14443_3a_signal); + iso14443_3a_signal = NULL; + } + + return FuriHalNfcErrorNone; +} + +static FuriHalNfcEvent furi_hal_nfc_iso14443_3a_listener_wait_event(uint32_t timeout_ms) { + FuriHalNfcEvent event = furi_hal_nfc_wait_event_common(timeout_ms); + FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + + if(event & FuriHalNfcEventListenerActive) { + st25r3916_set_reg_bits( + handle, ST25R3916_REG_PASSIVE_TARGET, ST25R3916_REG_PASSIVE_TARGET_d_106_ac_a); + } + + return event; +} + +FuriHalNfcError furi_hal_nfc_iso14443a_poller_trx_short_frame(FuriHalNfcaShortFrame frame) { + FuriHalNfcError error = FuriHalNfcErrorNone; + + FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + + // Disable crc check + st25r3916_set_reg_bits(handle, ST25R3916_REG_AUX, ST25R3916_REG_AUX_no_crc_rx); + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_ISO14443A_NFC, + (ST25R3916_REG_ISO14443A_NFC_no_tx_par | ST25R3916_REG_ISO14443A_NFC_no_rx_par), + (ST25R3916_REG_ISO14443A_NFC_no_tx_par_off | ST25R3916_REG_ISO14443A_NFC_no_rx_par_off)); + + st25r3916_write_reg(handle, ST25R3916_REG_NUM_TX_BYTES2, 0); + uint32_t interrupts = + (ST25R3916_IRQ_MASK_FWL | ST25R3916_IRQ_MASK_TXE | ST25R3916_IRQ_MASK_RXS | + ST25R3916_IRQ_MASK_RXE | ST25R3916_IRQ_MASK_PAR | ST25R3916_IRQ_MASK_CRC | + ST25R3916_IRQ_MASK_ERR1 | ST25R3916_IRQ_MASK_ERR2 | ST25R3916_IRQ_MASK_NRE); + // Clear interrupts + st25r3916_get_irq(handle); + // Enable interrupts + st25r3916_mask_irq(handle, ~interrupts); + if(frame == FuriHalNfcaShortFrameAllReq) { + st25r3916_direct_cmd(handle, ST25R3916_CMD_TRANSMIT_REQA); + } else { + st25r3916_direct_cmd(handle, ST25R3916_CMD_TRANSMIT_WUPA); + } + + return error; +} + +FuriHalNfcError furi_hal_nfc_iso14443a_tx_sdd_frame(const uint8_t* tx_data, size_t tx_bits) { + FuriHalNfcError error = FuriHalNfcErrorNone; + // No anticollision is supported in this version of library + error = furi_hal_nfc_poller_tx(tx_data, tx_bits); + + return error; +} + +FuriHalNfcError + furi_hal_nfc_iso14443a_rx_sdd_frame(uint8_t* rx_data, size_t rx_data_size, size_t* rx_bits) { + FuriHalNfcError error = FuriHalNfcErrorNone; + UNUSED(rx_data); + UNUSED(rx_bits); + UNUSED(rx_data_size); + + error = furi_hal_nfc_poller_rx(rx_data, rx_data_size, rx_bits); + // No anticollision is supported in this version of library + + return error; +} + +FuriHalNfcError + furi_hal_nfc_iso14443a_poller_tx_custom_parity(const uint8_t* tx_data, size_t tx_bits) { + furi_assert(tx_data); + + FuriHalNfcError err = FuriHalNfcErrorNone; + FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + + // Prepare tx + st25r3916_direct_cmd(handle, ST25R3916_CMD_CLEAR_FIFO); + st25r3916_clear_reg_bits( + handle, ST25R3916_REG_TIMER_EMV_CONTROL, ST25R3916_REG_TIMER_EMV_CONTROL_nrt_emv); + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_ISO14443A_NFC, + (ST25R3916_REG_ISO14443A_NFC_no_tx_par | ST25R3916_REG_ISO14443A_NFC_no_rx_par), + (ST25R3916_REG_ISO14443A_NFC_no_tx_par | ST25R3916_REG_ISO14443A_NFC_no_rx_par)); + uint32_t interrupts = + (ST25R3916_IRQ_MASK_FWL | ST25R3916_IRQ_MASK_TXE | ST25R3916_IRQ_MASK_RXS | + ST25R3916_IRQ_MASK_RXE | ST25R3916_IRQ_MASK_PAR | ST25R3916_IRQ_MASK_CRC | + ST25R3916_IRQ_MASK_ERR1 | ST25R3916_IRQ_MASK_ERR2 | ST25R3916_IRQ_MASK_NRE); + // Clear interrupts + st25r3916_get_irq(handle); + // Enable interrupts + st25r3916_mask_irq(handle, ~interrupts); + + st25r3916_write_fifo(handle, tx_data, tx_bits); + st25r3916_direct_cmd(handle, ST25R3916_CMD_TRANSMIT_WITHOUT_CRC); + return err; +} + +FuriHalNfcError furi_hal_nfc_iso14443a_listener_set_col_res_data( + uint8_t* uid, + uint8_t uid_len, + uint8_t* atqa, + uint8_t sak) { + furi_assert(uid); + furi_assert(atqa); + UNUSED(uid_len); + UNUSED(sak); + FuriHalNfcError error = FuriHalNfcErrorNone; + + FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + + // Set 4 or 7 bytes UID + if(uid_len == 4) { + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_AUX, + ST25R3916_REG_AUX_nfc_id_mask, + ST25R3916_REG_AUX_nfc_id_4bytes); + } else { + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_AUX, + ST25R3916_REG_AUX_nfc_id_mask, + ST25R3916_REG_AUX_nfc_id_7bytes); + } + // Write PT Memory + uint8_t pt_memory[15] = {}; + memcpy(pt_memory, uid, uid_len); + pt_memory[10] = atqa[0]; + pt_memory[11] = atqa[1]; + if(uid_len == 4) { + pt_memory[12] = sak & ~0x04; + } else { + pt_memory[12] = 0x04; + } + pt_memory[13] = sak & ~0x04; + pt_memory[14] = sak & ~0x04; + + st25r3916_write_pta_mem(handle, pt_memory, sizeof(pt_memory)); + + return error; +} + +FuriHalNfcError furi_hal_nfc_iso4443a_listener_tx( + FuriHalSpiBusHandle* handle, + const uint8_t* tx_data, + size_t tx_bits) { + FuriHalNfcError error = FuriHalNfcErrorNone; + + do { + error = furi_hal_nfc_common_fifo_tx(handle, tx_data, tx_bits); + if(error != FuriHalNfcErrorNone) break; + + bool tx_end = furi_hal_nfc_event_wait_for_specific_irq(handle, ST25R3916_IRQ_MASK_TXE, 10); + if(!tx_end) { + error = FuriHalNfcErrorCommunicationTimeout; + break; + } + + } while(false); + + return error; +} + +FuriHalNfcError furi_hal_nfc_iso14443a_listener_tx_custom_parity( + const uint8_t* tx_data, + const uint8_t* tx_parity, + size_t tx_bits) { + furi_assert(tx_data); + furi_assert(tx_parity); + + furi_assert(iso14443_3a_signal); + + FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + + st25r3916_direct_cmd(handle, ST25R3916_CMD_TRANSPARENT_MODE); + // Reconfigure gpio for Transparent mode + furi_hal_spi_bus_handle_deinit(&furi_hal_spi_bus_handle_nfc); + + // Send signal + iso14443_3a_signal_tx(iso14443_3a_signal, tx_data, tx_parity, tx_bits); + + // Exit transparent mode + 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); + st25r3916_direct_cmd(handle, ST25R3916_CMD_UNMASK_RECEIVE_DATA); + + return FuriHalNfcErrorNone; +} + +FuriHalNfcError furi_hal_nfc_iso14443_3a_listener_sleep(FuriHalSpiBusHandle* handle) { + // Enable auto collision resolution + st25r3916_clear_reg_bits( + handle, ST25R3916_REG_PASSIVE_TARGET, ST25R3916_REG_PASSIVE_TARGET_d_106_ac_a); + st25r3916_direct_cmd(handle, ST25R3916_CMD_STOP); + st25r3916_direct_cmd(handle, ST25R3916_CMD_GOTO_SLEEP); + + return FuriHalNfcErrorNone; +} + +FuriHalNfcError furi_hal_nfc_iso14443_3a_listener_idle(FuriHalSpiBusHandle* handle) { + // Enable auto collision resolution + st25r3916_clear_reg_bits( + handle, ST25R3916_REG_PASSIVE_TARGET, ST25R3916_REG_PASSIVE_TARGET_d_106_ac_a); + st25r3916_direct_cmd(handle, ST25R3916_CMD_STOP); + st25r3916_direct_cmd(handle, ST25R3916_CMD_GOTO_SENSE); + + return FuriHalNfcErrorNone; +} + +const FuriHalNfcTechBase furi_hal_nfc_iso14443a = { + .poller = + { + .compensation = + { + .fdt = FURI_HAL_NFC_POLLER_FDT_COMP_FC, + .fwt = FURI_HAL_NFC_POLLER_FWT_COMP_FC, + }, + .init = furi_hal_nfc_iso14443a_poller_init, + .deinit = furi_hal_nfc_iso14443a_poller_deinit, + .wait_event = furi_hal_nfc_wait_event_common, + .tx = furi_hal_nfc_poller_tx_common, + .rx = furi_hal_nfc_common_fifo_rx, + }, + + .listener = + { + .compensation = + { + .fdt = FURI_HAL_NFC_ISO14443A_LISTENER_FDT_COMP_FC, + }, + .init = furi_hal_nfc_iso14443a_listener_init, + .deinit = furi_hal_nfc_iso14443a_listener_deinit, + .wait_event = furi_hal_nfc_iso14443_3a_listener_wait_event, + .tx = furi_hal_nfc_iso4443a_listener_tx, + .rx = furi_hal_nfc_common_fifo_rx, + .sleep = furi_hal_nfc_iso14443_3a_listener_sleep, + .idle = furi_hal_nfc_iso14443_3a_listener_idle, + }, +}; diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc_iso14443b.c b/firmware/targets/f7/furi_hal/furi_hal_nfc_iso14443b.c new file mode 100644 index 00000000000..bb1a63515b2 --- /dev/null +++ b/firmware/targets/f7/furi_hal/furi_hal_nfc_iso14443b.c @@ -0,0 +1,108 @@ +#include "furi_hal_nfc_i.h" +#include "furi_hal_nfc_tech_i.h" + +static FuriHalNfcError furi_hal_nfc_iso14443b_common_init(FuriHalSpiBusHandle* handle) { + // Common NFC-B settings, 106kbps + + // 1st stage zero = 60kHz, 3rd stage zero = 200 kHz + st25r3916_write_reg(handle, ST25R3916_REG_RX_CONF1, ST25R3916_REG_RX_CONF1_h200); + + // Enable AGC + // AGC Ratio 6 + // AGC algorithm with RESET (recommended for ISO14443-B) + // AGC operation during complete receive period + // Squelch ratio 6/3 (recommended for ISO14443-B) + // Squelch automatic activation on TX end + st25r3916_write_reg( + handle, + ST25R3916_REG_RX_CONF2, + ST25R3916_REG_RX_CONF2_agc6_3 | ST25R3916_REG_RX_CONF2_agc_alg | + ST25R3916_REG_RX_CONF2_agc_m | ST25R3916_REG_RX_CONF2_agc_en | + ST25R3916_REG_RX_CONF2_pulz_61 | ST25R3916_REG_RX_CONF2_sqm_dyn); + + // HF operation, full gain on AM and PM channels + st25r3916_write_reg(handle, ST25R3916_REG_RX_CONF3, 0x00); + // No gain reduction on AM and PM channels + st25r3916_write_reg(handle, ST25R3916_REG_RX_CONF4, 0x00); + + // Subcarrier end detector enabled + // Subcarrier end detection level = 66% + // BPSK start 33 pilot pulses + // AM & PM summation before digitizing on + st25r3916_write_reg( + handle, + ST25R3916_REG_CORR_CONF1, + ST25R3916_REG_CORR_CONF1_corr_s0 | ST25R3916_REG_CORR_CONF1_corr_s1 | + ST25R3916_REG_CORR_CONF1_corr_s3 | ST25R3916_REG_CORR_CONF1_corr_s4); + // Sleep mode disable, 424kHz mode off + st25r3916_write_reg(handle, ST25R3916_REG_CORR_CONF2, 0x00); + + return FuriHalNfcErrorNone; +} + +static FuriHalNfcError furi_hal_nfc_iso14443b_poller_init(FuriHalSpiBusHandle* handle) { + // Enable ISO14443B mode, AM modulation + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_MODE, + ST25R3916_REG_MODE_om_mask | ST25R3916_REG_MODE_tr_am, + ST25R3916_REG_MODE_om_iso14443b | ST25R3916_REG_MODE_tr_am_am); + + // 10% ASK modulation + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_TX_DRIVER, + ST25R3916_REG_TX_DRIVER_am_mod_mask, + ST25R3916_REG_TX_DRIVER_am_mod_10percent); + + // Use regulator AM, resistive AM disabled + st25r3916_clear_reg_bits( + handle, + ST25R3916_REG_AUX_MOD, + ST25R3916_REG_AUX_MOD_dis_reg_am | ST25R3916_REG_AUX_MOD_res_am); + + // EGT = 0 etu + // SOF = 10 etu LOW + 2 etu HIGH + // EOF = 10 etu + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_ISO14443B_1, + ST25R3916_REG_ISO14443B_1_egt_mask | ST25R3916_REG_ISO14443B_1_sof_mask | + ST25R3916_REG_ISO14443B_1_eof, + (0U << ST25R3916_REG_ISO14443B_1_egt_shift) | ST25R3916_REG_ISO14443B_1_sof_0_10etu | + ST25R3916_REG_ISO14443B_1_sof_1_2etu | ST25R3916_REG_ISO14443B_1_eof_10etu); + + // TR1 = 80 / fs + // B' mode off (no_sof & no_eof = 0) + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_ISO14443B_2, + ST25R3916_REG_ISO14443B_2_tr1_mask | ST25R3916_REG_ISO14443B_2_no_sof | + ST25R3916_REG_ISO14443B_2_no_eof, + ST25R3916_REG_ISO14443B_2_tr1_80fs80fs); + + return furi_hal_nfc_iso14443b_common_init(handle); +} + +static FuriHalNfcError furi_hal_nfc_iso14443b_poller_deinit(FuriHalSpiBusHandle* handle) { + UNUSED(handle); + return FuriHalNfcErrorNone; +} + +const FuriHalNfcTechBase furi_hal_nfc_iso14443b = { + .poller = + { + .compensation = + { + .fdt = FURI_HAL_NFC_POLLER_FDT_COMP_FC, + .fwt = FURI_HAL_NFC_POLLER_FWT_COMP_FC, + }, + .init = furi_hal_nfc_iso14443b_poller_init, + .deinit = furi_hal_nfc_iso14443b_poller_deinit, + .wait_event = furi_hal_nfc_wait_event_common, + .tx = furi_hal_nfc_poller_tx_common, + .rx = furi_hal_nfc_common_fifo_rx, + }, + + .listener = {}, +}; diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc_iso15693.c b/firmware/targets/f7/furi_hal/furi_hal_nfc_iso15693.c new file mode 100644 index 00000000000..19ac6dc034f --- /dev/null +++ b/firmware/targets/f7/furi_hal/furi_hal_nfc_iso15693.c @@ -0,0 +1,463 @@ +#include "furi_hal_nfc_i.h" +#include "furi_hal_nfc_tech_i.h" + +#include +#include + +#include + +#define FURI_HAL_NFC_ISO15693_MAX_FRAME_SIZE (1024U) +#define FURI_HAL_NFC_ISO15693_POLLER_MAX_BUFFER_SIZE (64) + +#define FURI_HAL_NFC_ISO15693_RESP_SOF_SIZE (5) +#define FURI_HAL_NFC_ISO15693_RESP_EOF_SIZE (5) +#define FURI_HAL_NFC_ISO15693_RESP_SOF_MASK (0x1FU) +#define FURI_HAL_NFC_ISO15693_RESP_SOF_PATTERN (0x17U) +#define FURI_HAL_NFC_ISO15693_RESP_EOF_PATTERN (0x1DU) + +#define FURI_HAL_NFC_ISO15693_RESP_PATTERN_MASK (0x03U) +#define FURI_HAL_NFC_ISO15693_RESP_PATTERN_0 (0x01U) +#define FURI_HAL_NFC_ISO15693_RESP_PATTERN_1 (0x02U) + +// Derived experimentally +#define FURI_HAL_NFC_ISO15693_POLLER_FWT_COMP_FC (-1300) +#define FURI_HAL_NFC_ISO15693_LISTENER_FDT_COMP_FC (2850) + +#define BITS_IN_BYTE (8U) + +#define TAG "FuriHalIso15693" + +typedef struct { + Iso15693Signal* signal; + Iso15693Parser* parser; +} FuriHalNfcIso15693Listener; + +typedef struct { + // 4 bits per data bit on transmit + uint8_t fifo_buf[FURI_HAL_NFC_ISO15693_POLLER_MAX_BUFFER_SIZE * 4]; + size_t fifo_buf_bits; + uint8_t frame_buf[FURI_HAL_NFC_ISO15693_POLLER_MAX_BUFFER_SIZE * 2]; + size_t frame_buf_bits; +} FuriHalNfcIso15693Poller; + +static FuriHalNfcIso15693Listener* furi_hal_nfc_iso15693_listener = NULL; +static FuriHalNfcIso15693Poller* furi_hal_nfc_iso15693_poller = NULL; + +static FuriHalNfcIso15693Listener* furi_hal_nfc_iso15693_listener_alloc() { + FuriHalNfcIso15693Listener* instance = malloc(sizeof(FuriHalNfcIso15693Listener)); + + instance->signal = iso15693_signal_alloc(&gpio_spi_r_mosi); + instance->parser = + iso15693_parser_alloc(&gpio_spi_r_miso, FURI_HAL_NFC_ISO15693_MAX_FRAME_SIZE); + + return instance; +} + +static void furi_hal_nfc_iso15693_listener_free(FuriHalNfcIso15693Listener* instance) { + furi_assert(instance); + + iso15693_signal_free(instance->signal); + iso15693_parser_free(instance->parser); + + free(instance); +} + +static FuriHalNfcIso15693Poller* furi_hal_nfc_iso15693_poller_alloc() { + FuriHalNfcIso15693Poller* instance = malloc(sizeof(FuriHalNfcIso15693Poller)); + + return instance; +} + +static void furi_hal_nfc_iso15693_poller_free(FuriHalNfcIso15693Poller* instance) { + furi_assert(instance); + + free(instance); +} + +static FuriHalNfcError furi_hal_nfc_iso15693_common_init(FuriHalSpiBusHandle* handle) { + // Common NFC-V settings, 26.48 kbps + + // 1st stage zero = 12 kHz, 3rd stage zero = 80 kHz, low-pass = 600 kHz + st25r3916_write_reg( + handle, + ST25R3916_REG_RX_CONF1, + ST25R3916_REG_RX_CONF1_z12k | ST25R3916_REG_RX_CONF1_h80 | + ST25R3916_REG_RX_CONF1_lp_600khz); + + // Enable AGC + // AGC Ratio 6 + // AGC algorithm with RESET (recommended for ISO15693) + // AGC operation during complete receive period + // Squelch automatic activation on TX end + st25r3916_write_reg( + handle, + ST25R3916_REG_RX_CONF2, + ST25R3916_REG_RX_CONF2_agc6_3 | ST25R3916_REG_RX_CONF2_agc_m | + ST25R3916_REG_RX_CONF2_agc_en | ST25R3916_REG_RX_CONF2_sqm_dyn); + + // HF operation, full gain on AM and PM channels + st25r3916_write_reg(handle, ST25R3916_REG_RX_CONF3, 0x00); + // No gain reduction on AM and PM channels + st25r3916_write_reg(handle, ST25R3916_REG_RX_CONF4, 0x00); + + // Collision detection level 53% + // AM & PM summation before digitizing on + st25r3916_write_reg( + handle, + ST25R3916_REG_CORR_CONF1, + ST25R3916_REG_CORR_CONF1_corr_s0 | ST25R3916_REG_CORR_CONF1_corr_s1 | + ST25R3916_REG_CORR_CONF1_corr_s4); + // 424 kHz subcarrier stream mode on + st25r3916_write_reg(handle, ST25R3916_REG_CORR_CONF2, ST25R3916_REG_CORR_CONF2_corr_s8); + return FuriHalNfcErrorNone; +} + +static FuriHalNfcError furi_hal_nfc_iso15693_poller_init(FuriHalSpiBusHandle* handle) { + furi_assert(furi_hal_nfc_iso15693_poller == NULL); + + furi_hal_nfc_iso15693_poller = furi_hal_nfc_iso15693_poller_alloc(); + + // Enable Subcarrier Stream mode, OOK modulation + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_MODE, + ST25R3916_REG_MODE_om_mask | ST25R3916_REG_MODE_tr_am, + ST25R3916_REG_MODE_om_subcarrier_stream | ST25R3916_REG_MODE_tr_am_ook); + + // Subcarrier 424 kHz mode + // 8 sub-carrier pulses in report period + st25r3916_write_reg( + handle, + ST25R3916_REG_STREAM_MODE, + ST25R3916_REG_STREAM_MODE_scf_sc424 | ST25R3916_REG_STREAM_MODE_stx_106 | + ST25R3916_REG_STREAM_MODE_scp_8pulses); + + // Use regulator AM, resistive AM disabled + st25r3916_clear_reg_bits( + handle, + ST25R3916_REG_AUX_MOD, + ST25R3916_REG_AUX_MOD_dis_reg_am | ST25R3916_REG_AUX_MOD_res_am); + + return furi_hal_nfc_iso15693_common_init(handle); +} + +static FuriHalNfcError furi_hal_nfc_iso15693_poller_deinit(FuriHalSpiBusHandle* handle) { + UNUSED(handle); + furi_assert(furi_hal_nfc_iso15693_poller); + + furi_hal_nfc_iso15693_poller_free(furi_hal_nfc_iso15693_poller); + furi_hal_nfc_iso15693_poller = NULL; + + return FuriHalNfcErrorNone; +} + +static void iso15693_3_poller_encode_frame( + const uint8_t* tx_data, + size_t tx_bits, + uint8_t* frame_buf, + size_t frame_buf_size, + size_t* frame_buf_bits) { + static const uint8_t bit_patterns_1_out_of_4[] = {0x02, 0x08, 0x20, 0x80}; + size_t frame_buf_size_calc = (tx_bits / 2) + 2; + furi_assert(frame_buf_size >= frame_buf_size_calc); + + // Add SOF 1 out of 4 + frame_buf[0] = 0x21; + + size_t byte_pos = 1; + for(size_t i = 0; i < tx_bits / BITS_IN_BYTE; ++i) { + for(size_t j = 0; j < BITS_IN_BYTE; j += (BITS_IN_BYTE) / 4) { + const uint8_t bit_pair = (tx_data[i] >> j) & 0x03; + frame_buf[byte_pos++] = bit_patterns_1_out_of_4[bit_pair]; + } + } + // Add EOF + frame_buf[byte_pos++] = 0x04; + *frame_buf_bits = byte_pos * BITS_IN_BYTE; +} + +static FuriHalNfcError iso15693_3_poller_decode_frame( + const uint8_t* buf, + size_t buf_bits, + uint8_t* buf_decoded, + size_t buf_decoded_size, + size_t* buf_decoded_bits) { + FuriHalNfcError ret = FuriHalNfcErrorDataFormat; + size_t bit_pos = 0; + memset(buf_decoded, 0, buf_decoded_size); + + do { + if(buf_bits == 0) break; + // Check SOF + if((buf[0] & FURI_HAL_NFC_ISO15693_RESP_SOF_MASK) != + FURI_HAL_NFC_ISO15693_RESP_SOF_PATTERN) + break; + + if(buf_bits == BITS_IN_BYTE) { + ret = FuriHalNfcErrorIncompleteFrame; + break; + } + + // 2 response bits = 1 data bit + for(uint32_t i = FURI_HAL_NFC_ISO15693_RESP_SOF_SIZE; + i < buf_bits - FURI_HAL_NFC_ISO15693_RESP_SOF_SIZE; + i += BITS_IN_BYTE / 4) { + const size_t byte_index = i / BITS_IN_BYTE; + const size_t bit_offset = i % BITS_IN_BYTE; + const uint8_t resp_byte = (buf[byte_index] >> bit_offset) | + (buf[byte_index + 1] << (BITS_IN_BYTE - bit_offset)); + + // Check EOF + if(resp_byte == FURI_HAL_NFC_ISO15693_RESP_EOF_PATTERN) { + ret = FuriHalNfcErrorNone; + break; + } + + const uint8_t bit_pattern = resp_byte & FURI_HAL_NFC_ISO15693_RESP_PATTERN_MASK; + + if(bit_pattern == FURI_HAL_NFC_ISO15693_RESP_PATTERN_0) { + bit_pos++; + } else if(bit_pattern == FURI_HAL_NFC_ISO15693_RESP_PATTERN_1) { + buf_decoded[bit_pos / BITS_IN_BYTE] |= 1 << (bit_pos % BITS_IN_BYTE); + bit_pos++; + } else { + break; + } + if(bit_pos / BITS_IN_BYTE > buf_decoded_size) { + break; + } + } + + } while(false); + + if(ret == FuriHalNfcErrorNone) { + *buf_decoded_bits = bit_pos; + } + + return ret; +} + +static FuriHalNfcError furi_hal_nfc_iso15693_poller_tx( + FuriHalSpiBusHandle* handle, + const uint8_t* tx_data, + size_t tx_bits) { + FuriHalNfcIso15693Poller* instance = furi_hal_nfc_iso15693_poller; + iso15693_3_poller_encode_frame( + tx_data, + tx_bits, + instance->frame_buf, + sizeof(instance->frame_buf), + &instance->frame_buf_bits); + return furi_hal_nfc_poller_tx_common(handle, instance->frame_buf, instance->frame_buf_bits); +} + +static FuriHalNfcError furi_hal_nfc_iso15693_poller_rx( + FuriHalSpiBusHandle* handle, + uint8_t* rx_data, + size_t rx_data_size, + size_t* rx_bits) { + FuriHalNfcError error = FuriHalNfcErrorNone; + FuriHalNfcIso15693Poller* instance = furi_hal_nfc_iso15693_poller; + + do { + error = furi_hal_nfc_common_fifo_rx( + handle, instance->fifo_buf, sizeof(instance->fifo_buf), &instance->fifo_buf_bits); + if(error != FuriHalNfcErrorNone) break; + + error = iso15693_3_poller_decode_frame( + instance->fifo_buf, + instance->fifo_buf_bits, + instance->frame_buf, + sizeof(instance->frame_buf), + &instance->frame_buf_bits); + if(error != FuriHalNfcErrorNone) break; + + if(rx_data_size < instance->frame_buf_bits / BITS_IN_BYTE) { + error = FuriHalNfcErrorBufferOverflow; + break; + } + + memcpy(rx_data, instance->frame_buf, instance->frame_buf_bits / BITS_IN_BYTE); + *rx_bits = instance->frame_buf_bits; + } while(false); + + return error; +} + +static void furi_hal_nfc_iso15693_listener_transparent_mode_enter(FuriHalSpiBusHandle* handle) { + st25r3916_direct_cmd(handle, ST25R3916_CMD_TRANSPARENT_MODE); + + furi_hal_spi_bus_handle_deinit(handle); + furi_hal_nfc_deinit_gpio_isr(); +} + +static void furi_hal_nfc_iso15693_listener_transparent_mode_exit(FuriHalSpiBusHandle* handle) { + // Configure gpio back to SPI and exit transparent mode + furi_hal_nfc_init_gpio_isr(); + furi_hal_spi_bus_handle_init(handle); + + st25r3916_direct_cmd(handle, ST25R3916_CMD_UNMASK_RECEIVE_DATA); +} + +static FuriHalNfcError furi_hal_nfc_iso15693_listener_init(FuriHalSpiBusHandle* handle) { + furi_assert(furi_hal_nfc_iso15693_listener == NULL); + + furi_hal_nfc_iso15693_listener = furi_hal_nfc_iso15693_listener_alloc(); + + // Set default operation mode + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_MODE, + ST25R3916_REG_MODE_om_mask | ST25R3916_REG_MODE_tr_am, + ST25R3916_REG_MODE_om_targ_nfca | ST25R3916_REG_MODE_tr_am_ook); + + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_OP_CONTROL, + ST25R3916_REG_OP_CONTROL_rx_en, + ST25R3916_REG_OP_CONTROL_rx_en); + + // Enable passive target mode + st25r3916_change_reg_bits( + handle, ST25R3916_REG_MODE, ST25R3916_REG_MODE_targ, ST25R3916_REG_MODE_targ_targ); + + FuriHalNfcError error = furi_hal_nfc_iso15693_common_init(handle); + + furi_hal_nfc_iso15693_listener_transparent_mode_enter(handle); + + return error; +} + +static FuriHalNfcError furi_hal_nfc_iso15693_listener_deinit(FuriHalSpiBusHandle* handle) { + furi_assert(furi_hal_nfc_iso15693_listener); + + furi_hal_nfc_iso15693_listener_transparent_mode_exit(handle); + + furi_hal_nfc_iso15693_listener_free(furi_hal_nfc_iso15693_listener); + furi_hal_nfc_iso15693_listener = NULL; + + return FuriHalNfcErrorNone; +} + +static FuriHalNfcError + furi_hal_nfc_iso15693_listener_tx_transparent(const uint8_t* data, size_t data_size) { + iso15693_signal_tx( + furi_hal_nfc_iso15693_listener->signal, Iso15693SignalDataRateHi, data, data_size); + + return FuriHalNfcErrorNone; +} + +static void furi_hal_nfc_iso15693_parser_callback(Iso15693ParserEvent event, void* context) { + furi_assert(context); + + if(event == Iso15693ParserEventDataReceived) { + FuriThreadId thread_id = context; + furi_thread_flags_set(thread_id, FuriHalNfcEventInternalTypeTransparentDataReceived); + } +} + +static FuriHalNfcEvent furi_hal_nfc_iso15693_wait_event(uint32_t timeout_ms) { + FuriHalNfcEvent event = 0; + + FuriThreadId thread_id = furi_thread_get_current_id(); + iso15693_parser_start( + furi_hal_nfc_iso15693_listener->parser, furi_hal_nfc_iso15693_parser_callback, thread_id); + + while(true) { + uint32_t flag = furi_thread_flags_wait( + FuriHalNfcEventInternalTypeAbort | FuriHalNfcEventInternalTypeTransparentDataReceived, + FuriFlagWaitAny, + timeout_ms); + furi_thread_flags_clear(flag); + + if(flag & FuriHalNfcEventInternalTypeAbort) { + event = FuriHalNfcEventAbortRequest; + break; + } + if(flag & FuriHalNfcEventInternalTypeTransparentDataReceived) { + if(iso15693_parser_run(furi_hal_nfc_iso15693_listener->parser)) { + event = FuriHalNfcEventRxEnd; + break; + } + } + } + iso15693_parser_stop(furi_hal_nfc_iso15693_listener->parser); + + return event; +} + +static FuriHalNfcError furi_hal_nfc_iso15693_listener_tx( + FuriHalSpiBusHandle* handle, + const uint8_t* tx_data, + size_t tx_bits) { + UNUSED(handle); + furi_assert(furi_hal_nfc_iso15693_listener); + + FuriHalNfcError error = FuriHalNfcErrorNone; + + error = furi_hal_nfc_iso15693_listener_tx_transparent(tx_data, tx_bits / BITS_IN_BYTE); + + return error; +} + +FuriHalNfcError furi_hal_nfc_iso15693_listener_tx_sof() { + iso15693_signal_tx_sof(furi_hal_nfc_iso15693_listener->signal, Iso15693SignalDataRateHi); + + return FuriHalNfcErrorNone; +} + +static FuriHalNfcError furi_hal_nfc_iso15693_listener_rx( + FuriHalSpiBusHandle* handle, + uint8_t* rx_data, + size_t rx_data_size, + size_t* rx_bits) { + furi_assert(furi_hal_nfc_iso15693_listener); + UNUSED(handle); + + if(rx_data_size < + iso15693_parser_get_data_size_bytes(furi_hal_nfc_iso15693_listener->parser)) { + return FuriHalNfcErrorBufferOverflow; + } + + iso15693_parser_get_data( + furi_hal_nfc_iso15693_listener->parser, rx_data, rx_data_size, rx_bits); + + return FuriHalNfcErrorNone; +} + +FuriHalNfcError furi_hal_nfc_iso15693_listener_sleep(FuriHalSpiBusHandle* handle) { + UNUSED(handle); + + return FuriHalNfcErrorNone; +} + +const FuriHalNfcTechBase furi_hal_nfc_iso15693 = { + .poller = + { + .compensation = + { + .fdt = FURI_HAL_NFC_POLLER_FDT_COMP_FC, + .fwt = FURI_HAL_NFC_ISO15693_POLLER_FWT_COMP_FC, + }, + .init = furi_hal_nfc_iso15693_poller_init, + .deinit = furi_hal_nfc_iso15693_poller_deinit, + .wait_event = furi_hal_nfc_wait_event_common, + .tx = furi_hal_nfc_iso15693_poller_tx, + .rx = furi_hal_nfc_iso15693_poller_rx, + }, + + .listener = + { + .compensation = + { + .fdt = FURI_HAL_NFC_ISO15693_LISTENER_FDT_COMP_FC, + }, + .init = furi_hal_nfc_iso15693_listener_init, + .deinit = furi_hal_nfc_iso15693_listener_deinit, + .wait_event = furi_hal_nfc_iso15693_wait_event, + .tx = furi_hal_nfc_iso15693_listener_tx, + .rx = furi_hal_nfc_iso15693_listener_rx, + .sleep = furi_hal_nfc_iso15693_listener_sleep, + .idle = furi_hal_nfc_iso15693_listener_sleep, + }, +}; diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc_tech_i.h b/firmware/targets/f7/furi_hal/furi_hal_nfc_tech_i.h new file mode 100644 index 00000000000..e36dc852e8c --- /dev/null +++ b/firmware/targets/f7/furi_hal/furi_hal_nfc_tech_i.h @@ -0,0 +1,167 @@ +/** + * @file furi_hal_nfc_tech_i.h + * @brief NFC HAL technology-related private definitions. + * + * This file is an implementation detail. It must not be included in + * any public API-related headers. + * + * This file is to be changed in an unlikely event of adding support + * for a new NFC technology. + */ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Configure the NFC chip for use with this technology. + * + * Used for init() and deinit() functions. + * + * @param[in,out] handle pointer to the NFC chip SPI handle. + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +typedef FuriHalNfcError (*FuriHalNfcChipConfig)(FuriHalSpiBusHandle* handle); + +/** + * @brief Transmit data using technology-specific framing and timings. + * + * @param[in,out] handle pointer to the NFC chip SPI handle. + * @param[in] tx_data pointer to a byte array containing the data to be transmitted. + * @param[in] tx_bits transmit data size, in bits. + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +typedef FuriHalNfcError ( + *FuriHalNfcTx)(FuriHalSpiBusHandle* handle, const uint8_t* tx_data, size_t tx_bits); + +/** + * @brief Receive data using technology-specific framing and timings. + * + * @param[in,out] handle pointer to the NFC chip SPI handle. + * @param[out] rx_data pointer to a byte array to be filled with received data. + * @param[in] rx_data_size maximum received data length, in bytes. + * @param[out] rx_bits pointer to a variable to contain received data length, in bits. + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +typedef FuriHalNfcError (*FuriHalNfcRx)( + FuriHalSpiBusHandle* handle, + uint8_t* rx_data, + size_t rx_data_size, + size_t* rx_bits); + +/** + * @brief Wait for an event using technology-specific method. + * + * @param[in] timeout_ms maximum time to wait, in milliseconds. + * @return bitmask of occurred events. + */ +typedef FuriHalNfcEvent (*FuriHalNfcWaitEvent)(uint32_t timeout_ms); + +/** + * @brief Go to sleep in listener mode. + * + * Puts the passive target logic into Sleep (Halt) state. + * + * @param[in,out] handle pointer to the NFC chip SPI handle. + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +typedef FuriHalNfcError (*FuriHalNfcSleep)(FuriHalSpiBusHandle* handle); + +/** + * @brief Go to idle in listener mode. + * + * Puts the passive target logic into Sense (Idle) state. + * + * @param[in,out] handle pointer to the NFC chip SPI handle. + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +typedef FuriHalNfcError (*FuriHalNfcIdle)(FuriHalSpiBusHandle* handle); + +/** + * @brief Technology-specific compenstaion values for pollers. + * + * Timing compensations are needed due to execution delays not accounted for + * in standards, they are usually found out experimentally. + * + * The compensation value will be subtracted from the respective timer running + * time, so positive values shorten timeouts, and negative ones make them longer. + */ +typedef struct { + int32_t fdt; /**< Frame delay time compensation, in carrier cycles. */ + int32_t fwt; /**< Frame wait time compensaton, in carrier cycles. */ +} FuriHalNfcPollerCompensation; + +/** + * @brief Abstract technology-specific poller structure. + */ +typedef struct { + FuriHalNfcPollerCompensation compensation; /**< Compensation values in poller mode. */ + FuriHalNfcChipConfig init; /**< Pointer to the init() function. */ + FuriHalNfcChipConfig deinit; /**< Pointer to the deinit() function. */ + FuriHalNfcWaitEvent wait_event; /**< Pointer to the wait_event() function. */ + FuriHalNfcTx tx; /**< Pointer to the tx() function. */ + FuriHalNfcRx rx; /**< Pointer to the rx() function. */ +} FuriHalNfcTechPollerBase; + +/** + * @brief Technology-specific compenstaion values for listeners. + * + * Same considerations apply as with FuriHalNfcPollerCompensation. + */ +typedef struct { + int32_t fdt; /**< Frame delay time compensation, in carrier cycles. */ +} FuriHalNfcListenerCompensation; + +/** + * @brief Abstract technology-specific listener structure. + * + * If the listener operating mode is not supported for a particular + * technology, fill this structure with zeroes. + */ +typedef struct { + FuriHalNfcListenerCompensation compensation; /**< Compensation values in listener mode. */ + FuriHalNfcChipConfig init; /**< Pointer to the init() function. */ + FuriHalNfcChipConfig deinit; /**< Pointer to the deinit() function. */ + FuriHalNfcWaitEvent wait_event; /**< Pointer to the wait_event() function. */ + FuriHalNfcTx tx; /**< Pointer to the tx() function. */ + FuriHalNfcRx rx; /**< Pointer to the rx() function. */ + FuriHalNfcSleep sleep; /**< Pointer to the sleep() function. */ + FuriHalNfcIdle idle; /**< Pointer to the idle() function. */ +} FuriHalNfcTechListenerBase; + +/** + * @brief Abstract NFC technology definition structure. + * + * Each concrete technology implementation must fill this structure + * with its proper functions and constants. + */ +typedef struct { + FuriHalNfcTechPollerBase poller; /**< Structure containing the poller definition. */ + FuriHalNfcTechListenerBase listener; /**< Structure containing the listener definition. */ +} FuriHalNfcTechBase; + +/** @brief Technology declaration for ISO14443 (Type A). */ +extern const FuriHalNfcTechBase furi_hal_nfc_iso14443a; +/** @brief Technology declaration for ISO14443 (Type B). */ +extern const FuriHalNfcTechBase furi_hal_nfc_iso14443b; +/** @brief Technology declaration for ISO15693. */ +extern const FuriHalNfcTechBase furi_hal_nfc_iso15693; +/** @brief Technology declaration for FeliCa. */ +extern const FuriHalNfcTechBase furi_hal_nfc_felica; +/* Declare new tehcnologies here. */ + +/** + * @brief Array of pointers to every supported technology. + * + * This variable is defined in furi_hal_nfc.c. It will need to be modified + * in case when a new technology is to be added. + */ +extern const FuriHalNfcTechBase* furi_hal_nfc_tech[]; + +#ifdef __cplusplus +} +#endif diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc_timer.c b/firmware/targets/f7/furi_hal/furi_hal_nfc_timer.c new file mode 100644 index 00000000000..c9de9dfe856 --- /dev/null +++ b/firmware/targets/f7/furi_hal/furi_hal_nfc_timer.c @@ -0,0 +1,229 @@ +#include "furi_hal_nfc_i.h" +#include "furi_hal_nfc_tech_i.h" + +#include + +#include +#include +#include + +#define TAG "FuriHalNfcTimer" + +#define FURI_HAL_NFC_TIMER_US_IN_S (1000000UL) + +/** + * To enable timer debug output on GPIO, define the FURI_HAL_NFC_TIMER_DEBUG macro + * Example: ./fbt --extra-define=FURI_HAL_NFC_TIMER_DEBUG + */ + +typedef enum { + FuriHalNfcTimerFwt, + FuriHalNfcTimerBlockTx, + FuriHalNfcTimerCount, +} FuriHalNfcTimer; + +typedef struct { + TIM_TypeDef* timer; + FuriHalBus bus; + uint32_t prescaler; + uint32_t freq_khz; + FuriHalNfcEventInternalType event; + FuriHalInterruptId irq_id; + IRQn_Type irq_type; +#ifdef FURI_HAL_NFC_TIMER_DEBUG + const GpioPin* pin; +#endif +} FuriHalNfcTimerConfig; + +static const FuriHalNfcTimerConfig furi_hal_nfc_timers[FuriHalNfcTimerCount] = { + [FuriHalNfcTimerFwt] = + { + .timer = TIM1, + .bus = FuriHalBusTIM1, + .event = FuriHalNfcEventInternalTypeTimerFwtExpired, + .irq_id = FuriHalInterruptIdTim1UpTim16, + .irq_type = TIM1_UP_TIM16_IRQn, +#ifdef FURI_HAL_NFC_TIMER_DEBUG + .pin = &gpio_ext_pa7, +#endif + }, + [FuriHalNfcTimerBlockTx] = + { + .timer = TIM17, + .bus = FuriHalBusTIM17, + .event = FuriHalNfcEventInternalTypeTimerBlockTxExpired, + .irq_id = FuriHalInterruptIdTim1TrgComTim17, + .irq_type = TIM1_TRG_COM_TIM17_IRQn, +#ifdef FURI_HAL_NFC_TIMER_DEBUG + .pin = &gpio_ext_pa6, +#endif + }, +}; + +static void furi_hal_nfc_timer_irq_callback(void* context) { + // Returning removed const-ness + const FuriHalNfcTimerConfig* config = context; + if(LL_TIM_IsActiveFlag_UPDATE(config->timer)) { + LL_TIM_ClearFlag_UPDATE(config->timer); + furi_hal_nfc_event_set(config->event); +#ifdef FURI_HAL_NFC_TIMER_DEBUG + furi_hal_gpio_write(timer_config->pin, false); +#endif + } +} + +static void furi_hal_nfc_timer_init(FuriHalNfcTimer timer) { + const FuriHalNfcTimerConfig* config = &furi_hal_nfc_timers[timer]; + + furi_hal_bus_enable(config->bus); + + LL_TIM_SetOnePulseMode(config->timer, LL_TIM_ONEPULSEMODE_SINGLE); + LL_TIM_EnableUpdateEvent(config->timer); + LL_TIM_SetCounterMode(config->timer, LL_TIM_COUNTERMODE_UP); + LL_TIM_SetClockSource(config->timer, LL_TIM_CLOCKSOURCE_INTERNAL); + + furi_hal_interrupt_set_isr( + config->irq_id, + furi_hal_nfc_timer_irq_callback, + // Warning: casting const-ness away + (FuriHalNfcTimerConfig*)config); + NVIC_SetPriority(config->irq_type, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 5, 0)); + NVIC_EnableIRQ(config->irq_type); +#ifdef FURI_HAL_NFC_TIMER_DEBUG + furi_hal_gpio_init(config->pin, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); + furi_hal_gpio_write(config->pin, false); +#endif +} + +static void furi_hal_nfc_timer_deinit(FuriHalNfcTimer timer) { + const FuriHalNfcTimerConfig* config = &furi_hal_nfc_timers[timer]; + + LL_TIM_ClearFlag_UPDATE(config->timer); + furi_hal_interrupt_set_isr(config->irq_id, NULL, NULL); + NVIC_DisableIRQ(config->irq_type); + + if(furi_hal_bus_is_enabled(config->bus)) { + furi_hal_bus_disable(config->bus); + } +#ifdef FURI_HAL_NFC_TIMER_DEBUG + furi_hal_gpio_init_simple(config->pin, GpioModeAnalog); + furi_hal_gpio_write(config->pin, false); +#endif +} + +static int32_t furi_hal_nfc_timer_get_compensation(FuriHalNfcTimer timer) { + const FuriHalNfcTechBase* current_tech = furi_hal_nfc_tech[furi_hal_nfc.tech]; + + if(furi_hal_nfc.mode == FuriHalNfcModePoller) { + const FuriHalNfcPollerCompensation* comp = ¤t_tech->poller.compensation; + if(timer == FuriHalNfcTimerFwt) + return comp->fwt; + else if(timer == FuriHalNfcTimerBlockTx) + return comp->fdt; + + } else if(furi_hal_nfc.mode == FuriHalNfcModeListener) { + const FuriHalNfcListenerCompensation* comp = ¤t_tech->listener.compensation; + if(timer == FuriHalNfcTimerBlockTx) return comp->fdt; + } + + return 0; +} + +static inline bool furi_hal_nfc_timer_is_running(FuriHalNfcTimer timer) { + return LL_TIM_IsEnabledCounter(furi_hal_nfc_timers[timer].timer) != 0; +} + +static void furi_hal_nfc_timer_start_core_ticks(FuriHalNfcTimer timer, uint64_t core_ticks) { + furi_check(!furi_hal_nfc_timer_is_running(timer)); + + const FuriHalNfcTimerConfig* config = &furi_hal_nfc_timers[timer]; + furi_check(furi_hal_bus_is_enabled(config->bus)); + + const uint32_t prescaler = (core_ticks - 1) / UINT16_MAX; + furi_check(prescaler <= UINT16_MAX); + + const uint32_t arr_reg = core_ticks / (prescaler + 1); + furi_check(arr_reg <= UINT16_MAX); + + LL_TIM_DisableIT_UPDATE(config->timer); + + LL_TIM_SetPrescaler(config->timer, prescaler); + LL_TIM_SetAutoReload(config->timer, arr_reg); + + LL_TIM_GenerateEvent_UPDATE(config->timer); + while(!LL_TIM_IsActiveFlag_UPDATE(config->timer)) + ; + LL_TIM_ClearFlag_UPDATE(config->timer); + + LL_TIM_EnableIT_UPDATE(config->timer); + LL_TIM_EnableCounter(config->timer); +#ifdef FURI_HAL_NFC_TIMER_DEBUG + furi_hal_gpio_write(config->pin, true); +#endif +} + +static void furi_hal_nfc_timer_start_us(FuriHalNfcTimer timer, uint32_t time_us) { + furi_hal_nfc_timer_start_core_ticks( + timer, SystemCoreClock / FURI_HAL_NFC_TIMER_US_IN_S * time_us); +} + +static void furi_hal_nfc_timer_start_fc(FuriHalNfcTimer timer, uint32_t time_fc) { + const int32_t comp_fc = furi_hal_nfc_timer_get_compensation(timer); + // Not starting the timer if the compensation value is greater than the requested delay + if(comp_fc >= (int32_t)time_fc) return; + + furi_hal_nfc_timer_start_core_ticks( + timer, ((uint64_t)SystemCoreClock * (time_fc - comp_fc)) / FURI_HAL_NFC_CARRIER_HZ); +} + +static void furi_hal_nfc_timer_stop(FuriHalNfcTimer timer) { + const FuriHalNfcTimerConfig* config = &furi_hal_nfc_timers[timer]; + + LL_TIM_DisableIT_UPDATE(config->timer); + LL_TIM_DisableCounter(config->timer); + LL_TIM_SetCounter(config->timer, 0); + LL_TIM_SetAutoReload(config->timer, 0); + + if(LL_TIM_IsActiveFlag_UPDATE(config->timer)) { + LL_TIM_ClearFlag_UPDATE(config->timer); + } +#ifdef FURI_HAL_NFC_TIMER_DEBUG + furi_hal_gpio_write(config->pin, false); +#endif +} + +void furi_hal_nfc_timers_init() { + for(size_t i = 0; i < FuriHalNfcTimerCount; i++) { + furi_hal_nfc_timer_init(i); + } +} + +void furi_hal_nfc_timers_deinit() { + for(size_t i = 0; i < FuriHalNfcTimerCount; i++) { + furi_hal_nfc_timer_deinit(i); + } +} + +void furi_hal_nfc_timer_fwt_start(uint32_t time_fc) { + furi_hal_nfc_timer_start_fc(FuriHalNfcTimerFwt, time_fc); +} + +void furi_hal_nfc_timer_fwt_stop() { + furi_hal_nfc_timer_stop(FuriHalNfcTimerFwt); +} + +void furi_hal_nfc_timer_block_tx_start(uint32_t time_fc) { + furi_hal_nfc_timer_start_fc(FuriHalNfcTimerBlockTx, time_fc); +} + +void furi_hal_nfc_timer_block_tx_start_us(uint32_t time_us) { + furi_hal_nfc_timer_start_us(FuriHalNfcTimerBlockTx, time_us); +} + +void furi_hal_nfc_timer_block_tx_stop() { + furi_hal_nfc_timer_stop(FuriHalNfcTimerBlockTx); +} + +bool furi_hal_nfc_timer_block_tx_is_running() { + return furi_hal_nfc_timer_is_running(FuriHalNfcTimerBlockTx); +} diff --git a/firmware/targets/f7/target.json b/firmware/targets/f7/target.json index 9bb87000c70..63b5cdb9276 100644 --- a/firmware/targets/f7/target.json +++ b/firmware/targets/f7/target.json @@ -30,9 +30,9 @@ "nfc", "digital_signal", "pulse_reader", + "signal_reader", "microtar", "usb_stm32", - "st25rfal002", "infrared", "appframe", "assets", @@ -44,6 +44,7 @@ "lfrfid", "flipper_application", "flipperformat", - "toolbox" + "toolbox", + "flipper7" ] } \ No newline at end of file diff --git a/firmware/targets/furi_hal_include/furi_hal_nfc.h b/firmware/targets/furi_hal_include/furi_hal_nfc.h new file mode 100644 index 00000000000..ad4080e2647 --- /dev/null +++ b/firmware/targets/furi_hal_include/furi_hal_nfc.h @@ -0,0 +1,457 @@ +/** + * @file furi_hal_nfc.h + * @brief NFC HAL library. + * + * This library contains functions and definitions needed for NFC hardware low-level access. + * + * Application developers should first consider using the NFC protocol stack or + * the NFC transport layer and see if the APIs provided there sufficient + * for the applicaton's intended purpose. + * + * @see nfc.h + * @see nfc_protocol.h + * + * If any of the above mentioned options is used, calling any of the functions provided by this + * library is hardly necessary, as it will be taken care of under the hood. + * + */ +#pragma once + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief NFC carrier frequency, in Hz. + */ +#define FURI_HAL_NFC_CARRIER_HZ (13560000UL) + +/** + * @brief Special value indicating that waiting for an event shall never time out. + */ +#define FURI_HAL_NFC_EVENT_WAIT_FOREVER (0xFFFFFFFFU) + +/** + * @brief Enumeration of possible NFC HAL events. + */ +typedef enum { + FuriHalNfcEventOscOn = (1U << 0), /**< Oscillator has been started. */ + FuriHalNfcEventFieldOn = (1U << 1), /**< External field (carrier) has been detected. */ + FuriHalNfcEventFieldOff = (1U << 2), /**< External field (carrier) has been lost. */ + FuriHalNfcEventListenerActive = (1U << 3), /**< Reader has issued a wake-up command. */ + FuriHalNfcEventTxStart = (1U << 4), /**< Transmission has started. */ + FuriHalNfcEventTxEnd = (1U << 5), /**< Transmission has ended. */ + FuriHalNfcEventRxStart = (1U << 6), /**< Reception has started. */ + FuriHalNfcEventRxEnd = (1U << 7), /**< Reception has ended. */ + FuriHalNfcEventCollision = (1U << 8), /**< A collision has occurred. */ + FuriHalNfcEventTimerFwtExpired = (1U << 9), /**< Frame wait timer has expired. */ + FuriHalNfcEventTimerBlockTxExpired = (1U << 10), /**< Transmission block timer has expired. */ + FuriHalNfcEventTimeout = + (1U << 11), /**< No events have occurred in a specified time period. */ + FuriHalNfcEventAbortRequest = + (1U << 12), /**< User has requested to abort current operation. */ +} FuriHalNfcEvent; + +/** + * @brief Enumeration of possible NFC HAL errors. + */ +typedef enum { + FuriHalNfcErrorNone, /**< No error has occurred. */ + FuriHalNfcErrorBusy, /**< The communication bus is busy. */ + FuriHalNfcErrorCommunication, /**< NFC hardware did not respond or responded unexpectedly. */ + FuriHalNfcErrorOscillator, /**< Oscillator failed to start. */ + FuriHalNfcErrorCommunicationTimeout, /**< NFC hardware did not respond in time. */ + FuriHalNfcErrorBufferOverflow, /**< Receive buffer was too small for the received data. */ + FuriHalNfcErrorIncompleteFrame, /**< Not enough data was received to parse a valid frame. */ + FuriHalNfcErrorDataFormat, /**< Cannot parse a frame due to unexpected/invalid data. */ +} FuriHalNfcError; + +/** + * @brief Enumeration of possible NFC HAL operating modes. + */ +typedef enum { + FuriHalNfcModePoller, /**< Configure NFC HAL to operate as a poller. */ + FuriHalNfcModeListener, /**< Configure NFC HAL to operate as a listener. */ + + FuriHalNfcModeNum, /**< Special value equal to the operating modes count. Internal use. */ +} FuriHalNfcMode; + +/** + * @brief Enumeration of supported NFC technologies. + */ +typedef enum { + FuriHalNfcTechIso14443a, /**< Configure NFC HAL to use the ISO14443 (type A) technology. */ + FuriHalNfcTechIso14443b, /**< Configure NFC HAL to use the ISO14443 (type B) technology. */ + FuriHalNfcTechIso15693, /**< Configure NFC HAL to use the ISO15693 technology. */ + FuriHalNfcTechFelica, /**< Configure NFC HAL to use the FeliCa technology. */ + + FuriHalNfcTechNum, /**< Special value equal to the supported technologies count. Internal use. */ + FuriHalNfcTechInvalid, /**< Special value indicating the unconfigured state. Internal use. */ +} FuriHalNfcTech; + +/** + * @brief Initialise the NFC HAL and associated hardware. + * + * This function is called automatically during the firmware initialisation, + * so there is no need to call it explicitly. + * + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +FuriHalNfcError furi_hal_nfc_init(); + +/** + * @brief Check whether the NFC HAL was properly initialised and is ready. + * + * @returns FuriHalNfcErrorNone if ready, any other error code if not ready. + */ +FuriHalNfcError furi_hal_nfc_is_hal_ready(); + +/** + * @brief Exclusively take over the NFC HAL and associated hardware. + * + * This function needs to be called whenever an interaction with the NFC HAL + * is to take place (usually once upon the application start). + * + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +FuriHalNfcError furi_hal_nfc_acquire(); + +/** + * @brief Release the exclusive lock and make the NFC HAL available for others. + * + * This function needs to be called when the user code is done working + * with the NFC HAL (usually once upon application exit). It must be called + * from the same thread that has called furi_hal_nfc_acquire(). + * + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +FuriHalNfcError furi_hal_nfc_release(); + +/** + * @brief Configure the NFC hardware to enter the low-power mode. + * + * This function must be called each time when the user code is done working + * with the NFC HAL for the time being (e.g. waiting on user input). + * + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +FuriHalNfcError furi_hal_nfc_low_power_mode_start(); + +/** + * @brief Configure the NFC hardware to exit the low-power mode. + * + * This function must be called each time when the user code begins working + * with the NFC HAL, as the default state is low-power mode. + * + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +FuriHalNfcError furi_hal_nfc_low_power_mode_stop(); + +/** + * @brief Configure the NFC HAL to work in a particular mode. + * + * Not all technologies implement the listener operating mode. + * + * @param[in] mode required operating mode. + * @param[in] tech required technology configuration. + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +FuriHalNfcError furi_hal_nfc_set_mode(FuriHalNfcMode mode, FuriHalNfcTech tech); + +/** + * @brief Reset the NFC HAL to its default (unconfigured) state. + * + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +FuriHalNfcError furi_hal_nfc_reset_mode(); + +/** + * @brief Enable field (carrier) detection by the NFC hardware. + * + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +FuriHalNfcError furi_hal_nfc_field_detect_start(); + +/** + * @brief Disable field (carrier) detection by the NFC hardware. + * + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +FuriHalNfcError furi_hal_nfc_field_detect_stop(); + +/** + * @brief Check if the reader field (carrier) was detected by the NFC hardware. + * + * @returns true if the field was detected, false otherwise. + */ +bool furi_hal_nfc_field_is_present(); + +/** + * @brief Enable field (carrier) generation by the NFC hardware. + * + * No carrier modulation will occur unless a transmission is explicitly started. + * + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +FuriHalNfcError furi_hal_nfc_poller_field_on(); + +/** + * @brief Wait for an NFC HAL event in poller mode. + * + * @param[in] timeout_ms time to wait (timeout) in milliseconds. + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +FuriHalNfcEvent furi_hal_nfc_poller_wait_event(uint32_t timeout_ms); + +/** + * @brief Wait for an NFC HAL event in listener mode. + * @param[in] timeout_ms time to wait (timeout) in milliseconds. + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +FuriHalNfcEvent furi_hal_nfc_listener_wait_event(uint32_t timeout_ms); + +/** + * @brief Transmit data in poller mode. + * + * @param[in] tx_data pointer to a byte array containing the data to be transmitted. + * @param[in] tx_bits transmit data size, in bits. + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +FuriHalNfcError furi_hal_nfc_poller_tx(const uint8_t* tx_data, size_t tx_bits); + +/** + * @brief Receive data in poller mode. + * + * The receive buffer must be big enough to accomodate all of the expected data. + * + * @param rx_data[out] pointer to a byte array to be filled with received data. + * @param rx_data_size[in] maximum received data size, in bytes. + * @param rx_bits[out] pointer to the variable to hold received data size, in bits. + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +FuriHalNfcError furi_hal_nfc_poller_rx(uint8_t* rx_data, size_t rx_data_size, size_t* rx_bits); + +/** + * @brief Transmit data in listener mode. + * + * @param[in] tx_data pointer to a byte array containing the data to be transmitted. + * @param[in] tx_bits transmit data size, in bits. + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +FuriHalNfcError furi_hal_nfc_listener_tx(const uint8_t* tx_data, size_t tx_bits); + +/** + * @brief Receive data in listener mode. + * + * The receive buffer must be big enough to accomodate all of the expected data. + * + * @param rx_data[out] pointer to a byte array to be filled with received data. + * @param rx_data_size[in] maximum received data size, in bytes. + * @param rx_bits[out] pointer to the variable to hold received data size, in bits. + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +FuriHalNfcError furi_hal_nfc_listener_rx(uint8_t* rx_data, size_t rx_data_size, size_t* rx_bits); + +/** + * @brief Go to sleep in listener mode. + * + * Puts the passive target logic into Sleep (Halt) state. + * + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +FuriHalNfcError furi_hal_nfc_listener_sleep(); + +/** + * @brief Go to idle in listener mode. + * + * Puts the passive target logic into Sense (Idle) state. + * + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +FuriHalNfcError furi_hal_nfc_listener_idle(); + +/** + * @brief Enable reception in listener mode. + * + * Starts hardware receivers and receive decoders. + * + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +FuriHalNfcError furi_hal_nfc_listener_enable_rx(); + +/** + * @brief Reset communication. + * + * Resets the communication state and stops all activities: transmission, reception, etc. + * + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +FuriHalNfcError furi_hal_nfc_trx_reset(); + +/** + * @brief Enable generation of NFC HAL events. + * + * @warning This function must be called from the same thread from which + * the the furi_hal_nfc_*_wait_event() calls will be made. + * + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +FuriHalNfcError furi_hal_nfc_event_start(); + +/** + * @brief Disable generation of NFC HAL events. + * + * Unlike furi_hal_nfc_event_start(), this function may be called from any thread. + * + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +FuriHalNfcError furi_hal_nfc_event_stop(); + +/** + * @brief Manually emit the FuriHalNfcEventAbortRequest event. + * + * @returns FuriHalNfcErrorNone on success, any other error code on failure. +*/ +FuriHalNfcError furi_hal_nfc_abort(); + +/** + * @brief Start frame wait timeout timer. + * + * @param[in] time_fc time to wait, in carrier cycles. + */ +void furi_hal_nfc_timer_fwt_start(uint32_t time_fc); + +/** + * @brief Stop frame wait timeout timer. + */ +void furi_hal_nfc_timer_fwt_stop(); + +/** + * @brief Start block transmit (frame delay) timer. + * + * @param[in] time_fc time to wait, in carrier cycles. +*/ +void furi_hal_nfc_timer_block_tx_start(uint32_t time_fc); + +/** + * @brief Start block transmit (frame delay) timer. + * + * @param[in] time_us time to wait, in microseconds. + */ +void furi_hal_nfc_timer_block_tx_start_us(uint32_t time_us); + +/** + * @brief Stop block transmit (frame delay) timer. + */ +void furi_hal_nfc_timer_block_tx_stop(); + +/** + * @brief Check whether block transmit (frame delay) timer is running. + * + * @returns true if timer is running, false otherwise. + */ +bool furi_hal_nfc_timer_block_tx_is_running(); + +/* + * Technology-specific functions. + * + * In a perfect world, this would not be necessary. + * However, the current implementation employs NFC hardware that partially implements + * certain protocols (e.g. ISO14443-3A), thus requiring methods to access such features. + */ + +/******************* Iso14443a specific API *******************/ + +/** + * @brief Enumeration of ISO14443 (Type A) short frame types. + */ +typedef enum { + FuriHalNfcaShortFrameAllReq, + FuriHalNfcaShortFrameSensReq, +} FuriHalNfcaShortFrame; + +/** + * @brief Transmit ISO14443 (Type A) short frame in poller mode. + * + * @param[in] frame short frame type to be transmitted. + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +FuriHalNfcError furi_hal_nfc_iso14443a_poller_trx_short_frame(FuriHalNfcaShortFrame frame); + +/** Transmit ISO14443 (Type A) SDD frame in poller mode. + * + * @param[in] tx_data pointer to a byte array containing the data to be transmitted. + * @param[in] tx_bits transmit data size, in bits. + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +FuriHalNfcError furi_hal_nfc_iso14443a_tx_sdd_frame(const uint8_t* tx_data, size_t tx_bits); + +/** + * Receive ISO14443 (Type A) SDD frame in poller mode. + * + * The receive buffer must be big enough to accomodate all of the expected data. + * + * @param rx_data[out] pointer to a byte array to be filled with received data. + * @param rx_data_size[in] maximum received data size, in bytes. + * @param rx_bits[out] pointer to the variable to hold received data size, in bits. + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +FuriHalNfcError + furi_hal_nfc_iso14443a_rx_sdd_frame(uint8_t* rx_data, size_t rx_data_size, size_t* rx_bits); + +/** + * @brief Transmit ISO14443 (Type A) frame with custom parity bits in poller mode. + * + * Same as furi_hal_nfc_poller_tx(), but uses the parity bits provided + * by the user code instead of calculating them automatically. + * + * @param[in] tx_data pointer to a byte array containing the data to be transmitted. + * @param[in] tx_bits transmit data size, in bits. + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +FuriHalNfcError + furi_hal_nfc_iso14443a_poller_tx_custom_parity(const uint8_t* tx_data, size_t tx_bits); + +/** + * @brief Set ISO14443 (Type A) collision resolution parameters in listener mode. + * + * Configures the NFC hardware for automatic collision resolution. + * + * @param[in] uid pointer to a byte array containing the UID. + * @param[in] uid_len UID length in bytes (must be supported by the protocol). + * @param[in] atqa ATQA byte value. + * @param[in] sak SAK byte value. + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +FuriHalNfcError furi_hal_nfc_iso14443a_listener_set_col_res_data( + uint8_t* uid, + uint8_t uid_len, + uint8_t* atqa, + uint8_t sak); + +/** + * @brief Transmit ISO14443 (Type A) frame with custom parity bits in listener mode. + * + * @param[in] tx_data pointer to a byte array containing the data to be transmitted. + * @param[in] tx_parity pointer to a (bit-packed) byte array containing the parity to be transmitted. + * @param[in] tx_bits transmit data size, in bits. + * @returns FuriHalNfcErrorNone on success, any other error code on failure. + */ +FuriHalNfcError furi_hal_nfc_iso14443a_listener_tx_custom_parity( + const uint8_t* tx_data, + const uint8_t* tx_parity, + size_t tx_bits); + +/** Send ISO15693 SOF in listener mode + * + * @return FuriHalNfcError +*/ +FuriHalNfcError furi_hal_nfc_iso15693_listener_tx_sof(); + +#ifdef __cplusplus +} +#endif diff --git a/lib/ReadMe.md b/lib/ReadMe.md index 138bef2b343..326d933aa5d 100644 --- a/lib/ReadMe.md +++ b/lib/ReadMe.md @@ -2,7 +2,6 @@ - `FreeRTOS-Kernel` - FreeRTOS kernel source code - `FreeRTOS-glue` - Extra glue to hold together FreeRTOS kernel and flipper firmware -- `ST25RFAL002` - ST25R3916 Driver and protocol stack - `app-scened-template` - C++ app library - `callback-connector` - Callback connector library - `cmsis_core` - CMSIS Core package, contain cortex-m core headers diff --git a/lib/SConscript b/lib/SConscript index 907a5a41def..f2cc9d18a0d 100644 --- a/lib/SConscript +++ b/lib/SConscript @@ -5,11 +5,11 @@ env.Append( Dir("app-scened-template"), Dir("digital_signal"), Dir("pulse_reader"), + Dir("signal_reader"), Dir("drivers"), Dir("flipper_format"), Dir("infrared"), Dir("nfc"), - Dir("ST25RFAL002"), Dir("subghz"), Dir("toolbox"), Dir("u8g2"), @@ -83,7 +83,6 @@ libs = env.BuildModules( "print", "microtar", "toolbox", - "ST25RFAL002", "libusb_stm32", "drivers", "fatfs", @@ -97,6 +96,7 @@ libs = env.BuildModules( "nfc", "digital_signal", "pulse_reader", + "signal_reader", "appframe", "misc", "lfrfid", diff --git a/lib/ST25RFAL002/SConscript b/lib/ST25RFAL002/SConscript deleted file mode 100644 index d86d2d002e8..00000000000 --- a/lib/ST25RFAL002/SConscript +++ /dev/null @@ -1,19 +0,0 @@ -Import("env") - -env.Append( - CPPPATH=[ - "#/lib/ST25RFAL002", - "#/lib/ST25RFAL002/include", - "#/lib/ST25RFAL002/source/st25r3916", - ], -) - - -libenv = env.Clone(FW_LIB_NAME="st25rfal002") -libenv.ApplyLibFlags() - -sources = libenv.GlobRecursive("*.c") - -lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources) -libenv.Install("${LIB_DIST_DIR}", lib) -Return("lib") diff --git a/lib/ST25RFAL002/doc/Release_Notes.html b/lib/ST25RFAL002/doc/Release_Notes.html deleted file mode 100755 index 28d501c091a..00000000000 --- a/lib/ST25RFAL002/doc/Release_Notes.html +++ /dev/null @@ -1,354 +0,0 @@ - - - - - - - - - - - - - Release Notes for RFAL Library - - - - - - - - - -
    -


    -

    -
    - - - - - - -
    - - - - - - - - - -

    -
    -

    Release -Notes for RFAL software Library

    -

    Copyright -2019 STMicroelectronics

    -

    -
    -

     

    - - - - - - -

    -
    The RFAL Library -(RF Abstraction Layer) provides several functionalities required to perform RF/NFC communications. -The RFAL encapsulates the different RF ICs (ST25R3911, ST25R3916, ST25R95 and future ST25R devices) into a common and easy to use interface.
    -
    - The technologies currently supported by RFAL are: -
      -
    • NFC-A \ ISO14443A (T1T, T2T, T4TA)
    • -
    • NFC-B \ ISO14443B (T4TB)
    • -
    • NFC-F \ FeliCa (T3T)
    • -
    • NFC-V \ ISO15693 (T5T)
    • -
    • P2P \ ISO18092 (NFCIP1, Passive-Active P2P)
    • -
    • ST25TB (ISO14443-2 Type B with Proprietary Protocol)
    • -
    • PicoPass \ iClass
    • -
    • B' \ Calypso
    • -
    • CTS \ CTM
    • -
    -

    - The protocols provided by RFAL are: -
      -
    • ISO-DEP (ISO14443-4)
    • -
    • NFC-DEP (ISO18092)
    • -
    -
    -
    -
      -
    - -

    Update History

    -
    - -

    V2.2.0 / 22-May-2020

    -

    -

    Main Changes

    -
      -
    • Better alignment to NFC Forum latest requirements (CR12)
    • -
    • Extended NFC-V module with non-addressed mode support and improved aticollision
    • -
    • Feature Switches changed to be not mandatory. Modules disabled by default
    • -
    • Aligned APIs on platform.h (breaks compatibility with previous versions, see example in rfal.chm)
    • -
    • Added API for release/deletion of timers
    • -
    • ST25R3916 default analog table modified to X-NUCLEO-NFC06A1 board
    • -
    • Improved AP2P operation
    • -
    • Fixed issues introduced on previous release linked to SFGT and anticollision retries
    • -
    • Introduced Low-Power mode
    • -
    • Several driver improvements
    • -
    -
    - -

    V2.1.2 / 27-Jan-2020

    -

    -

    Main Changes

    -
      -
    • Extended ISO-DEP and NFC-A module to support non-blocking activation interfaces
    • -
    • Extended NFC/HL module to make use of the new APIs further splitting the execution of the worker during the different activities
    • -
    • Modified NFC-A anticollision to strictly comply to NFC Forum DP. A separate proprietary method is now available.
    • -
    • NFC-V changed to use OOK (100% AM) by default
    • -
    • Fixed FWT used by NFC-V Sleep
    • -
    • Fixed NFC-F FDT Poll value
    • -
    • Fixed incorrect register access on ST25R3911B RFO Get/Set method
    • -
    • SPI driver modified to clear Rx buffer prior to operation
    • -
    • Added further code size optimizations based on enabled features
    • -
    • Updated ST25R3916 driver to DS Rev2
    • -
    • Updated SW Tag Detection as describded in AN Rev3
    • -
    • Several driver improvements
    • -
    -
    - -

    V2.1.0 / 30-Sep-2019

    -

    -

    Main Changes

    -
      -
    • Extended RFAL NFC Higher Layer for increased functionality and configurations
    • -
    • Several improvements on the ISO-DEP protocol layer
    • -
    • Protocol buffer sizes made fully configurable for increased memory management
    • -
    • Introduced option for Collision Avoidance with Automatic Gain Control
    • -
    • Several driver improvements
    • -
    • ST25R3916 overheat protection disabled
    • -
    • RF Transceive modified for transmission errors to precede other errors
    • -
    • Analog Configs extended to support different DPO power levels
    • -
    -
    - -

    V2.0.10 / 25-Jun-2019

    -

    -

    Main Changes

    -
      -
    • Various improvements on RFAL NFC Higher layer
    • -
    • Added alternative NFC-V anticollision method (non NFC Forum compliant)
    • -
    • Several minor improvements and fixes
    • -
    -
    - -

    V2.0.6 / 10-Apr-2019

    -

    -

    Main Changes

    -
      -
    • Several NFC-V interoperability improvements
    • -
    • Extended support for specific features of ST's ISO15693 Tags. New ST25Dx module added
    • -
    • Interrupt handling changed and further protection added
    • -
    • RFAL feature switches have been modified and features are now disabled if omitted
    • -
    • ST25R3916 AAT (Automatic Antenna Tuning) module added
    • -
    • RFAL NFC Higher layer added
    • -
    • Several driver improvements
    • -
    -
    - -

    V2.0.4 / 06-Fev-2019

    -

    Provided with ST25R3916 DISCO v1.0.0 / EMVCo v1.2.0

    -

    Main Changes

    -
      -
    • Minor improvements on NFC-F module
    • -
    • Several improvements on NFC-V module including support for ST proprietary features
    • -
    • Fixed issue with Delta RWT calculation
    • -
    • Fixed incorrect usage of NFCB dTbPoll / DdFWT
    • -
    • Added compile switch for Listen Mode
    • -
    • Low power Listen Mode support added
    • -
    • Listen Mode aligned to NFC Forum Digital 2.1
    • -
    • Added handling for EMVCo 3.0 static FDTListen
    • -
    • Introduced SW Tag Detection
    • -
    -
    - -

    V2.0.2 / 31-Oct-2018

    -

    Provided with ST25R3916 DISCO v0.9.4 (binary only)

    -

    Main Changes

    -
      -
    • New T4T module added
    • -
    • Added support for T3T Check and Update commands
    • -
    • Improved NFC-V module and added Write Multiple Blocks support
    • -
    • New rfalWorker protection added for improved control in multi-thread environments
    • -
    • Added support for user defined Analog Config tables
    • -
    • Several driver improvements and protections added
    • -
    -
    - -

    V2.0.0 / 28-Aug-2018

    - -

    Main Changes

    -
      -
    • MISRA C 2012 compliant
    • -
    • ST25R3916 support added
    • -
    • ST25R95 support added
    • -
    • Fix unwanted Field Detector disable when entering Wake-up mode
    • -
    • Extended Analog Config to have specific events
    • -
    • Fixed NFC-DEP potential issue if DID used
    • -
    • Extended NFC-V commands
    • -
    • T2T module added
    • -
    • Improved initial Listen mode handling
    • -
    • Extended Wake-Up mode to support Capacitive measurement
    • -
    -
    - -

    V1.3.6 / 08-May-2018

    -

    Provided with ST25R3911B DISCO v1.2.0

    -

    Main Changes

    -
      -
    • Added ISO15693 x4 and x8 mode support
    • -
    • Added S(PARAMETERS) support
    • -
    • Interface changes for measurement, Wake-Up and DPO methods
    • -
    • Added further feature switches to enable/disable individual modules
    • -
    • Changed communication protection
    • -
    • Improved NFC-A anti-collision
    • -
    • Several driver improvements
    • -
    -
    -

    V1.3.4 / 07-May-2018

    - -

    Main Changes

    -
      -
    • Fixed NFC-V Read operation in addressed mode
    • -
    -
    -

    V1.3.2 / 31-January-2018

    - -

    Main Changes

    -
      -
    • Modified Wake-Up mode interface
    • -
    • Fixed SFGI calculation in ISO-DEP
    • -
    -
    -

    V1.3.0 / 22-January-2018

    - -

    Main Changes

    -
      -
    • Introduced a new IRQ status handling to read the registers only once
    • -
    • Several changes for supporting Linux platform
    • -
    • SPI Select/Deselect moved to platform.h
    • -
    • Additional protection of the IRQ status reading, new macros available: platformProtectST25R391xIrqStatus / platformUnprotectST25R391xIrqStatus
    • -
    • Renamed the IRQ Enable/Disable macros to platformProtectST25R391xComm / platformUnprotectST25R391xComm
    • -
    • Renamed SPI pins from chip specific to ST25R391X
    • -
    • Introduced a new option ST25R391X_COM_SINGLETXRX which executes SPI in one single exchange (additional buffer required)
    • -
    • Updated and added errata handlings to latest ST25R3911 Errata version
    • -
    • Fixed inconsistency on Analog settings for NFC-V
    • -
    • Fixed issue on NFC-V 1of256 decoding
    • -
    • Changed the default NFC-A FDT Listen to be more strict
    • -
    • Added Wake-Up mode support
    • -
    • Added RFAL version definition
    • -
    -
    -

    V1.2.0 / 17-August-2017

    -

    Provided with ST25R3911B Disco v1.1.16

    -

    Main Changes

    -
      -
    • Aligned Technology modules to NFC Activity v1.1 and EMVCo v2.6
    • -
    • Extended NFC-B Collision Resolution allowing user define Slots
    • -
    • Added feature switches to enable/disable individual modules
    • -
    • ISO-DEP Interface changes allowing more user configurations and further EMVCo alignment
    • -
    • Changed ST25TB detection to always perform Anti Collision loop regardeless of the result of the Poll
    • -
    • Fixed FIFO WL handling
    • -
    • Modified FDT Poll handling
    • -
    • changed rfalCalibrate() to not overwrite dynamic configs
    • -
    • Added adjustment for TR1PUTMIN
    • -
    - -
    -

    V1.1.0 / 30-June-2017

    -

    Provided with ST25R3911B Disco v1.1.12

    -

    Main Changes

    -
      -
    • EMD suppression enabled for ST25R3911B
    • -
    - -
    -

    V1.0.0 / 16-May-2017

    -

    Provided with X-NUCLEO-NFC05A1 v1.0.0

    -

    Main Changes

    -
      -
    • Added support for B', CTS and PicoPass/iClass mode
    • -
    • Several impromvements for NFC-V mode
    • -
    • Improved error detection during NFC-B collision resolution
    • -
    • Extended T1T module
    • -
    - -
    -

    V0.9.0 / 02-March-2017

    -

    Provided with ST25R3911B Discovery Kit on Embedded World Conference (binary only)

    -

    Main Changes

    - -
    - -
    -

    -
    -
    -

     

    -
    - diff --git a/lib/ST25RFAL002/doc/ST25R3916_MisraComplianceReport.html b/lib/ST25RFAL002/doc/ST25R3916_MisraComplianceReport.html deleted file mode 100755 index e4ffaa9336c..00000000000 --- a/lib/ST25RFAL002/doc/ST25R3916_MisraComplianceReport.html +++ /dev/null @@ -1,8867 +0,0 @@ - - - - - -PRQA GEP/GCS/GRP Report - - - - -
    -
    -
    -
    -
    - -This section targets to provide an overview of Guidelines Enforcement Plan (GEP).
    -
    -This document will only focus on STMicroelectronics NFC RF Abstraction Layer (RFAL).
    -
    -The project has been designed to comply with the standard ISO/IEC 9899:1999 ([C99]). -
    -
    -

    1. Tools version

    -The tool used for MISRA compliance is:
    -
    -PRQA Framework - v2.2.2
    -

    -It is composed of the following subcomponents: -

    -
      -
    • Component: qacpp

    • -
        Version: 4.2.0
      -
        Target: C++
      -
    • Component: rcma

    • -
        Version: 1.6.0
      -
        Target: C_CPP
      -
    • Component: m3cm

    • -
        Version: 2.3.1
      -
        Target: C
      -
    • Component: qac

    • -
        Version: 9.3.1
      -
        Target: C
      -
        -
      • Options:
      • -
          -d : __schedule_barrier=_ignore_semi
        -
          -namelength : 63
        -
      -
    -

    2. Configuration

    -This section targets to provide the main configuration options used for MISRA compliance.
    -
    -The project complies to [C99],
    -the variables length has been consequently set to a dedicated value (cf 'namelength' option in table above). -
    -
    -Repository/components:
    -
      -
    • MCU target:
    • -
        STM32

      -
    • Parent repository:
    • -
        ST25R3916_nucleo

      -
    • RFAL information:
    • -
        Path: .../ST25R3916_nucleo/rfal
      -
        Version: v2.1.2
      -
    • Project repositories SHA1:
    • -
        .../ST25R3916_nucleo: 959b80e
      -
        .../ST25R3916_nucleo/common: 09bc5ef
      -
        .../ST25R3916_nucleo/nucleo: 22a04ae
      -
        .../ST25R3916_nucleo/rfal: f08099c
      -
      -
    -

    3. Assistance/Enforcement

    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    GuidelineCategoryDescriptionAssistance/Enforcement Sub Rules
    Dir-1.1RequiredAny implementation-defined behaviour on which the output of the program depends shall be documented and understood
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    QacDescription
    0202 [I] '-' character in '[]' conversion specification is implementation defined.
    0284 [I] Multiple character constants have implementation defined values.
    0285 [I] Character constant contains character which is not a member of the basic source character set.
    0286 [I] String literal contains character which is not a member of the basic source character set.
    0287 [I] Header name contains character which is not a member of the basic source character set.
    0288 [I] Source file '%s' has comments containing characters which are not members of the basic source character set.
    0289 [I] Source file '%s' has preprocessing tokens containing characters which are not members of the basic source character set.
    0292 [I] Source file '%s' has comments containing one of the characters '$', '@' or '`'.
    0299 [I] Source file '%s' includes #pragma directives containing characters which are not members of the basic source character set.
    0314 [I] Cast from a pointer to object type to a pointer to void.
    0315 [I] Implicit conversion from a pointer to object type to a pointer to void.
    0371 [L] Nesting levels of blocks exceeds 127 - program does not conform strictly to ISO:C99.
    0372 [L] More than 63 levels of nested conditional inclusion - program does not conform strictly to ISO:C99.
    0375 [L] Nesting of parenthesized expressions exceeds 63 - program does not conform strictly to ISO:C99.
    0380 [L] Number of macro definitions exceeds 4095 - program does not conform strictly to ISO:C99.
    0388 [L] '#include "%s"' causes nesting to exceed 15 levels - program does not conform strictly to ISO:C99.
    0390 [L] Number of members in 'struct' or 'union' exceeds 1023 - program does not conform strictly to ISO:C99.
    0391 [L] Number of enumeration constants exceeds 1023 - program does not conform strictly to ISO:C99.
    0392 [L] Nesting of 'struct' or 'union' types exceeds 63 - program does not conform strictly to ISO:C99.
    0581 [I] Floating-point constant may be too small to be representable.
    0634 [I] Bit-fields in this struct/union have not been declared explicitly as unsigned or signed.
    2850 Constant: Implicit conversion to a signed integer type of insufficient size.
    2851 Definite: Implicit conversion to a signed integer type of insufficient size.
    2852 Apparent: Implicit conversion to a signed integer type of insufficient size.
    2855 Constant: Casting to a signed integer type of insufficient size.
    2856 Definite: Casting to a signed integer type of insufficient size.
    2857 Apparent: Casting to a signed integer type of insufficient size.
    2860 Constant: Implementation-defined value resulting from left shift operation on expression of signed type.
    2861 Definite: Implementation-defined value resulting from left shift operation on expression of signed type.
    2862 Apparent: Implementation-defined value resulting from left shift operation on expression of signed type.
    2895 Constant: Negative value cast to an unsigned type.
    2896 Definite: Negative value cast to an unsigned type.
    2897 Apparent: Negative value cast to an unsigned type.
    3116 Unrecognized #pragma arguments '%s' This #pragma directive has been ignored.
    -
    Dir-2.1RequiredAll source files shall compile without any compilation errorsUnassisted

    -Remarks:
    -Dedicated checks deployed in Jenkins.
    Dir-3.1RequiredAll code shall be traceable to documented requirementsUnassisted

    -Remarks:
    -Limited management of requirements.
    Dir-4.1RequiredRun-time failures shall be minimized
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    QacDescription
    2791 Definite: Right hand operand of shift operator is negative or too large.
    2792 Apparent: Right hand operand of shift operator is negative or too large.
    2801 Definite: Overflow in signed arithmetic operation.
    2802 Apparent: Overflow in signed arithmetic operation.
    2811 Definite: Dereference of NULL pointer.
    2812 Apparent: Dereference of NULL pointer.
    2821 Definite: Arithmetic operation on NULL pointer.
    2822 Apparent: Arithmetic operation on NULL pointer.
    2831 Definite: Division by zero.
    2832 Apparent: Division by zero.
    2841 Definite: Dereference of an invalid pointer value.
    2842 Apparent: Dereference of an invalid pointer value.
    2845 Constant: Maximum number of characters to be written is larger than the target buffer size.
    2846 Definite: Maximum number of characters to be written is larger than the target buffer size.
    2847 Apparent: Maximum number of characters to be written is larger than the target buffer size.
    2871 Infinite loop identified.
    2872 This loop, if entered, will never terminate.
    2877 This loop will never be executed more than once.
    -
    Dir-4.10RequiredPrecautions shall be taken in order to prevent the contents of a header file being included more then once
    - - - - - -
    QacDescription
    0883 Include file code is not protected against repeated inclusion
    -
    Dir-4.11RequiredThe validity of values passed to library functions shall be checkedUnassisted

    -Remarks:
    -No automated check deployed.
    -Manual checks done by developers.
    Dir-4.12RequiredDynamic memory allocation shall not be usedUnassisted

    -Remarks:
    -No memory allocation functions (malloc(), calloc(), realloc()) being called in RFAL.
    Dir-4.13AdvisoryFunctions which are designed to provide operations on a resource should be called in an appropriate sequenceUnassisted
    Dir-4.14RequiredThe validity of values received from external sources shall be checked
    - - - - - -
    QacDescription
    2956 Definite: Using object '%s' with tainted value.
    -
    Dir-4.2AdvisoryAll usage of assembly language should be documented
    - - - - - - - - - -
    QacDescription
    1003 [E] '#%s' is a language extension for in-line assembler. All statements located between #asm and #endasm will be ignored.
    1006 [E] This in-line assembler construct is a language extension. The code has been ignored.
    -
    Dir-4.3RequiredAssembly language shall be encapsulated and isolated
    - - - - - - - - - -
    QacDescription
    3006 This function contains a mixture of in-line assembler statements and C statements.
    3008 This function contains a mixture of in-line assembler statements and C code.
    -
    Dir-4.4AdvisorySections of code should not be "commented out"Unassisted
    Dir-4.5AdvisoryIdentifiers in the same name space with overlapping visibility should be typographically unambiguousUnassisted
    Dir-4.6Advisorytypedefs that indicate size and signedness should be used in place of the basic numerical types
    - - - - - -
    QacDescription
    5209 Use of basic type '%s'.
    -
    Dir-4.7RequiredIf a function returns error information, then that error information shall be testedUnassisted

    -Remarks:
    -Dir-4.7 is similar to Rule-17.7 which is currently dismissed.
    -This directive is consequently considered as disapplied.
    Dir-4.8AdvisoryIf a pointer to a structure or union is never dereferenced within a translation unit, then the implementation of the object should be hiddenUnassisted
    Dir-4.9AdvisoryA function should be used in preference to a function-like macro where they are interchangeable
    - - - - - -
    QacDescription
    3453 A function could probably be used instead of this function-like macro.
    -
    Rule-1.1RequiredThe program shall contain no violations of the standard C syntax and constraints, and shall not exceed the implementation's translation limits
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    QacDescription
    0232 [C] Value of hex escape sequence is not representable in type 'unsigned char'.
    0233 [C] Value of octal escape sequence is not representable in type 'unsigned char'.
    0244 [C] Value of character constant is not representable in type 'int'.
    0268 [S] Comment open at end of translation unit.
    0321 [C] Declaration within 'for' statement defines an identifier '%s' which is not an object.
    0322 [C] Illegal storage class specifier used in 'for' statement declaration.
    0338 [C] Octal or hex escape sequence value is too large for 'unsigned char' or 'wchar_t' type.
    0422 [C] Function call contains fewer arguments than prototype specifies.
    0423 [C] Function call contains more arguments than prototype specifies.
    0426 [C] Called function has incomplete return type.
    0427 [C] Object identifier used as if it were a function or a function pointer identifier.
    0429 [C] Function argument is not of arithmetic type.
    0430 [C] Function argument is not of compatible 'struct'/'union' type.
    0431 [C] Function argument points to a more heavily qualified type.
    0432 [C] Function argument is not of compatible pointer type.
    0435 [C] The 'struct'/'union' member '%s' does not exist.
    0436 [C] Left operand of '.' must be a 'struct' or 'union' object.
    0437 [C] Left operand of '->' must be a pointer to a 'struct' or 'union' object.
    0446 [C] Operand of ++/-- must have scalar (arithmetic or pointer) type.
    0447 [C] Operand of ++/-- must be a modifiable object.
    0448 [C] Operand of ++/-- must not be a pointer to an object of unknown size.
    0449 [C] Operand of ++/-- must not be a pointer to a function.
    0450 [C] An expression of array type cannot be cast.
    0451 [C] Subscripting requires a pointer (or array lvalue).
    0452 [C] Cannot subscript a pointer to an object of unknown size.
    0453 [C] An array subscript must have integral type.
    0454 [C] The address-of operator '&' cannot be applied to an object declared with 'register'.
    0456 [C] This expression does not have an address - '&' may only be applied to an lvalue or a function designator.
    0457 [C] The address-of operator '&' cannot be applied to a bit-field.
    0458 [C] Indirection operator '*' requires operand of pointer type.
    0460 [C] The keyword static is used in the declaration of the index of an array which is not a function parameter.
    0461 [C] The keyword static is used in the declaration of an inner index of a multi-dimensional array.
    0462 [C] A type qualifier (const, volatile or restrict) is used in the declaration of the index of an array which is not a function parameter.
    0463 [C] A type qualifier (const, volatile or restrict) is used in the declaration of an inner index of a multi-dimensional array.
    0466 [C] Unary '+' requires arithmetic operand.
    0467 [C] Operand of '!' must have scalar (arithmetic or pointer) type.
    0468 [C] Unary '-' requires arithmetic operand.
    0469 [C] Bitwise not '~' requires integral operand.
    0476 [C] 'sizeof' cannot be applied to a bit-field.
    0477 [C] 'sizeof' cannot be applied to a function.
    0478 [C] 'sizeof' cannot be applied to an object of unknown size.
    0481 [C] Only scalar expressions may be cast to other types.
    0482 [C] Expressions may only be cast to 'void' or scalar types.
    0483 [C] A pointer to an object of unknown size cannot be the operand of an addition operator.
    0484 [C] A pointer to an object of unknown size cannot be the operand of a subtraction operator.
    0485 [C] Only integral expressions may be added to pointers.
    0486 [C] Only integral expressions and compatible pointers may be subtracted from pointers.
    0487 [C] If two pointers are subtracted, they must be pointers that address compatible types.
    0493 [C] Type of left operand is not compatible with this operator.
    0494 [C] Type of right operand is not compatible with this operator.
    0495 [C] Left operand of '%', '<<', '>>', '&', '^' or '|' must have integral type.
    0496 [C] Right operand of '%', '<<', '>>', '&', '^' or '|' must have integral type.
    0513 [C] Relational operator used to compare pointers to incompatible types.
    0514 [C] Relational operator used to compare a pointer with an incompatible operand.
    0515 [C] Equality operator used to compare a pointer with an incompatible operand.
    0536 [C] First operand of '&&', '||' or '?' must have scalar (arithmetic or pointer) type.
    0537 [C] Second operand of '&&' or '||' must have scalar (arithmetic or pointer) type.
    0540 [C] 2nd and 3rd operands of conditional operator '?' must have compatible types.
    0541 [C] Argument no. %s does not have object type.
    0542 [C] Controlling expression must have scalar (arithmetic or pointer) type.
    0546 [C] 'enum %s' has unknown content. Use of an enum tag with undefined content is not permitted.
    0547 [C] This declaration of tag '%s' conflicts with a previous declaration.
    0550 [C] Left operand of '+=' or '-=' is a pointer to an object of unknown size.
    0554 [C] 'static %s()' has been declared and called but no definition has been given.
    0555 [C] Invalid assignment to object of void type or array type.
    0556 [C] Left operand of assignment must be a modifiable object.
    0557 [C] Right operand of assignment is not of arithmetic type.
    0558 [C] Right operand of '+=' or '-=' must have integral type when left operand is a pointer.
    0559 [C] Right operand of '<<=', '>>=', '&=', '|=', '^=' or '%=' must have integral type.
    0560 [C] Left operand of '<<=', '>>=', '&=', '|=', '^=' or '%=' must have integral type.
    0561 [C] Right operand of assignment is not of compatible 'struct'/'union' type.
    0562 [C] Right operand of assignment points to a more heavily qualified type.
    0563 [C] Right operand of assignment is not of compatible pointer type.
    0564 [C] Left operand of assignment must be an lvalue (it must designate an object).
    0565 [C] Left operand of '+=' or '-=' must be of arithmetic or pointer to object type.
    0580 [C] Constant is too large to be representable.
    0588 [C] Width of bit-field must be an integral constant expression.
    0589 [C] Enumeration constant must be an integral constant expression.
    0590 [C] Array bound must be an integral constant expression.
    0591 [C] A 'case' label must be an integral constant expression.
    0605 [C] A declaration must declare a tag or an identifier.
    0616 [C] Illegal combination of type specifiers or storage class specifiers.
    0619 [C] The identifier '%s' has already been defined in the current scope within the ordinary identifier namespace.
    0620 [C] Cannot initialize '%s' because it has unknown size.
    0621 [C] The struct/union '%s' cannot be initialized because it has unknown size.
    0622 [C] The identifier '%s' has been declared both with and without linkage in the same scope.
    0627 [C] '%s' has different type to previous declaration in the same scope.
    0628 [C] '%s' has different type to previous declaration at wider scope.
    0629 [C] More than one definition of '%s' (with internal linkage).
    0631 [C] More than one declaration of '%s' (with no linkage).
    0638 [C] Duplicate member name '%s' in 'struct' or 'union'.
    0640 [C] '%s' in 'struct' or 'union' type may not have 'void' type.
    0641 [C] '%s' in 'struct' or 'union' type may not have function type.
    0642 [C] '%s' in 'struct' or 'union' type may not be an array of unknown size.
    0643 [C] '%s' in 'struct' or 'union' type may not be a 'struct' or 'union' with unknown content.
    0644 [C] Width of bit-field must be no bigger than the width of an 'int'.
    0645 [C] A zero width bit-field cannot be given a name.
    0646 [C] Enumeration constants must have values representable as 'int's.
    0649 [C] K&R style declaration of parameters is not legal after a function header that includes a parameter list.
    0650 [C] Illegal storage class specifier on named function parameter.
    0651 [C] Missing type specifiers in function declaration.
    0653 [C] Duplicate definition of 'struct', 'union' or 'enum' tag '%s'.
    0655 [C] Illegal storage class specifier on unnamed function parameter.
    0656 [C] Function return type cannot be function or array type, or an incomplete struct/union (for function definition).
    0657 [C] Unnamed parameter specified in function definition.
    0659 [C] The identifier '%s' was not given in the parameter list.
    0664 [C] Parameter specified with type 'void'.
    0665 [C] Two parameters have been declared with the same name '%s'.
    0669 [C] The restrict qualifier can only be applied to pointer types derived from object or incomplete types.
    0671 [C] Initializer for object of arithmetic type is not of arithmetic type.
    0673 [C] Initializer points to a more heavily qualified type.
    0674 [C] Initializer for pointer is of incompatible type.
    0675 [C] Initializer is not of compatible 'struct'/'union' type.
    0677 [C] Array size is negative, or unrepresentable.
    0682 [C] Initializer for object of a character type is a string literal.
    0683 [C] Initializer for object of a character type is a wide string literal.
    0684 [C] Too many initializers.
    0685 [C] Initializer for any object with static storage duration must be a constant expression.
    0690 [C] String literal contains too many characters to initialize object.
    0698 [C] String literal used to initialize an object of incompatible type.
    0699 [C] String literal used to initialize a pointer of incompatible type.
    0708 [C] No definition found for the label '%s' in this function.
    0709 [C] Initialization of locally declared 'extern %s' is illegal.
    0736 [C] 'case' label does not have unique value within this 'switch' statement.
    0737 [C] More than one 'default' label found in 'switch' statement.
    0738 [C] Controlling expression in a 'switch' statement must have integral type.
    0746 [C] 'return exp;' found in '%s()' whose return type is 'void'.
    0747 [C] 'return exp;' found in '%s()' whose return type is qualified 'void'.
    0755 [C] 'return' expression is not of arithmetic type.
    0756 [C] 'return' expression is not of compatible 'struct'/'union' type.
    0757 [C] 'return' expression points to a more heavily qualified type.
    0758 [C] 'return' expression is not of compatible pointer type.
    0766 [C] 'continue' statement found outside an iteration statement.
    0767 [C] 'break' statement found outside a 'switch' or iteration statement.
    0768 [C] 'case' or 'default' found outside a 'switch' statement.
    0774 [C] 'auto' may not be specified on global declaration of '%s'.
    0775 [C] 'register' may not be specified on global declaration of '%s'.
    0801 [C] The '##' operator may not be the first token in a macro replacement list.
    0802 [C] The '##' operator may not be the last token in a macro replacement list.
    0803 [C] The '#' operator may only appear before a macro parameter.
    0804 [C] Macro parameter '%s' is not unique.
    0811 [C] The glue operator '##' may only appear in a '#define' preprocessing directive.
    0812 [C] Header name token '' found outside '#include' preprocessing directive.
    0817 [S] Closing quote or bracket '>' missing from include filename.
    0818 [Q] Cannot find '%s' - Perhaps the appropriate search path was not given ?
    0821 [C] '#include' does not identify a header or source file that can be processed.
    0834 [C] Function-like macro '%s()' is being redefined as an object-like macro.
    0835 [C] Macro '%s' is being redefined with different parameter names.
    0844 [C] Macro '%s' is being redefined with a different replacement list.
    0845 [C] Object-like macro '%s' is being redefined as a function-like macro.
    0851 [C] More arguments in macro call than specified in definition.
    0852 [S] Unable to find the ')' that marks the end of the macro call.
    0866 [C] The string literal in a '#line' directive cannot be a 'wide string literal'.
    0873 [C] Preprocessing token cannot be converted to an actual token.
    0877 [C] '#if' and '#elif' expressions may contain only integral constants.
    0940 [C] Illegal usage of a variably modified type.
    0941 [C] A variable length array may not be initialized.
    0943 [C] Jump to label '%s' is a jump into the scope of an identifier with variably modified type.
    0944 [C] The label '%s' is inside the scope of an identifier with variably modified type.
    1023 [C] Using '__alignof__' on function types is illegal.
    1024 [C] Using '__alignof__' on incomplete types is illegal.
    1025 [C] Using '__alignof__' on bit-fields is illegal.
    1033 [C] The identifier __VA_ARGS__ may only be used in the replacement list of a variadic macro.
    1047 [C] Function is being declared with default argument syntax after a previous call to the function. This is not allowed.
    1048 [C] Default argument values are missing for some parameters in this function declaration. This is not allowed.
    1050 [C] Nested functions cannot be 'extern' or 'static'.
    1061 [C] Structure '%1s' with flexible array member '%2s' cannot be used in the declaration of structure member '%3s'.
    1062 [C] Structure '%1s' with flexible array member '%2s' cannot be used in the declaration of array elements.
    3236 [C] 'inline' may not be applied to function 'main'.
    3237 [C] inline function '%1s' has external linkage and is defining an object, '%2s', with static storage duration.
    3238 [C] inline function '%1s' has external linkage and is referring to an object, '%2s', with internal linkage.
    3244 [C] 'inline' may only be used in the declaration of a function identifier.
    -
    Rule-1.2AdvisoryLanguage extensions should not be used
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    QacDescription
    0240 [E] This file contains the control-M character at the end of a line.
    0241 [E] This file contains the control-Z character - was this transferred from a PC?
    0246 [E] Binary integer constants are a language extension.
    0551 [E] Cast may not operate on the left operand of the assignment operator.
    0601 [E] Function 'main()' is not of type 'int (void)' or 'int (int, char *[])'.
    0633 [E] Empty structures and unions are a language extension.
    0635 [E] Bit-fields in this struct/union have been declared with types other than int, signed int, unsigned int or _Bool.
    0660 [E] Defining an unnamed member in a struct or union. This is a language extension.
    0662 [E] Accessing a member of an unnamed struct or union member in this way is a language extension.
    0830 [E] Unrecognized text encountered after a preprocessing directive.
    0831 [E] Use of '\\' in this '#include' line is a PC extension - this usage is non-portable.
    0840 [E] Extra tokens at end of #include directive.
    0883 Include file code is not protected against repeated inclusion
    0899 [E] Unrecognized preprocessing directive has been ignored - assumed to be a language extension.
    0981 [E] Redundant semicolon in 'struct' or 'union' member declaration list is a language extension.
    1001 [E] '#include %s' is a VMS extension.
    1002 [E] '%s' is not a legal identifier in ISO C.
    1003 [E] '#%s' is a language extension for in-line assembler. All statements located between #asm and #endasm will be ignored.
    1006 [E] This in-line assembler construct is a language extension. The code has been ignored.
    1008 [E] '#%s' is not a legal ISO C preprocessing directive.
    1012 [E] Use of a C++ reference type ('type &') will be treated as a language extension.
    1014 [E] Non-standard type specifier - this will be treated as a language extension.
    1015 [E] '%s' is not a legal keyword in ISO C - this will be treated as a language extension.
    1019 [E] '@ address' is not supported in ISO C - this will be treated as a language extension.
    1020 [E] '__typeof__' is not supported in ISO C, and is treated as a language extension.
    1021 [E] A statement expression is not supported in ISO C, and is treated as a language extension.
    1022 [E] '__alignof__' is not supported in ISO C, and is treated as a language extension.
    1026 [E] The indicated @word construct has been ignored.
    1028 [E] Use of the sizeof operator in a preprocessing directive is a language extension.
    1029 [E] Whitespace encountered between backslash and new-line has been ignored.
    1034 [E] Macro defined with named variable argument list. This is a language extension.
    1035 [E] No macro arguments supplied for variable argument list. This is a language extension.
    1036 [E] Comma before ## ignored in expansion of variadic macro. This is a language extension.
    1037 [E] Arrays of length zero are a language extension.
    1038 [E] The sequence ", ##__VA_ARGS__" is a language extension.
    1039 [E] Treating array of length one as potentially flexible member.
    1041 [E] Empty aggregate initializers are a language extension.
    1042 [E] Using I64 or UI64 as an integer constant suffix. This is a language extension.
    1043 [E] Defining an anonymous union object. This is a language extension.
    1044 [E] Defining an anonymous struct object. This is a language extension.
    1045 [E] Use of the #include_next preprocessing directive is a language extension.
    1046 [E] Function is being declared with default argument syntax. This is a language extension.
    1049 [E] Nested functions are a language extension.
    3445 [E] Conditional expression with middle operand omitted is a language extension.
    3664 [E] Using a dot operator to access an individual bit is a language extension.
    -
    Rule-1.3RequiredThere shall be no occurrence of undefined or critical unspecified behaviour
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    QacDescription
    0160 [U] Using unsupported conversion specifier number %s.
    0161 [U] Unknown length modifier used with 'i' or 'd' conversion specifier, number %s.
    0162 [U] Unknown length modifier used with 'o' conversion specifier, number %s.
    0163 [U] Unknown length modifier used with 'u' conversion specifier, number %s.
    0164 [U] Unknown length modifier used with 'x' conversion specifier, number %s.
    0165 [U] Unknown length modifier used with 'X' conversion specifier, number %s.
    0166 [U] Unknown length modifier used with 'f' conversion specifier, number %s.
    0167 [U] Unknown length modifier used with 'e' conversion specifier, number %s.
    0168 [U] Unknown length modifier used with 'E' conversion specifier, number %s.
    0169 [U] Unknown length modifier used with 'g' conversion specifier, number %s.
    0170 [U] Unknown length modifier used with 'G' conversion specifier, number %s.
    0171 [U] Unknown length modifier used with 'c' conversion specifier, number %s.
    0172 [U] Unknown length modifier used with '%%' conversion specifier, number %s.
    0173 [U] Unknown length modifier used with 's' conversion specifier, number %s.
    0174 [U] Unknown length modifier used with 'n' conversion specifier, number %s.
    0175 [U] Unknown length modifier used with 'p' conversion specifier, number %s.
    0176 [U] Incomplete conversion specifier, number %s.
    0177 [U] Field width of format conversion specifier exceeds 509 characters.
    0178 [U] Precision of format conversion specifier exceeds 509 characters.
    0179 [U] Argument type does not match conversion specifier number %s.
    0184 [U] Insufficient arguments to satisfy conversion specifier, number %s.
    0185 [U] Call contains more arguments than conversion specifiers.
    0186 [U] A call to this function must include at least one argument.
    0190 [U] Using unsupported conversion specifier number %s.
    0191 [U] Unknown length modifier used with 'd/i/n' conversion specifier, number %s.
    0192 [U] Unknown length modifier used with 'o' conversion specifier, number %s.
    0193 [U] Unknown length modifier used with 'u' conversion specifier, number %s.
    0194 [U] Unknown length modifier used with 'x/X' conversion specifier, number %s.
    0195 [U] Unknown length modifier used with 'e/E/f/F/g/G' conversion specifier, number %s.
    0196 [U] Unknown length modifier used with 's' conversion specifier, number %s.
    0197 [U] Unknown length modifier used with 'p' conversion specifier, number %s.
    0198 [U] Unknown length modifier used with '%%' conversion specifier, number %s.
    0199 [U] Unknown length modifier used with '[' conversion specifier, number %s.
    0200 [U] Unknown length modifier used with 'c' conversion specifier, number %s.
    0201 [U] Incomplete conversion specifier, number %s.
    0203 [U] Value of character prior to '-' in '[]' is greater than following character.
    0204 [U] Field width of format conversion specifier exceeds 509 characters.
    0206 [U] Argument type does not match conversion specifier number %s.
    0207 [U] 'scanf' expects address of objects being stored into.
    0208 [U] Same character occurs in scanset more than once.
    0235 [U] Unknown escape sequence.
    0275 [U] Floating value is out of range for conversion to destination type.
    0301 [u] Cast between a pointer to object and a floating type.
    0302 [u] Cast between a pointer to function and a floating type.
    0304 [U] The address of an array declared 'register' may not be computed.
    0307 [u] Cast between a pointer to object and a pointer to function.
    0309 [U] Integral type is not large enough to hold a pointer value.
    0327 [I] Cast between a pointer to void and an floating type.
    0337 [U] String literal has undefined value. This may be a result of using '#' on \\.
    0400 [U] '%s' is modified more than once between sequence points - evaluation order unspecified.
    0401 [U] '%s' may be modified more than once between sequence points - evaluation order unspecified.
    0402 [U] '%s' is modified and accessed between sequence points - evaluation order unspecified.
    0403 [U] '%s' may be modified and accessed between sequence points - evaluation order unspecified.
    0404 More than one read access to volatile objects between sequence points.
    0405 More than one modification of volatile objects between sequence points.
    0475 [u] Operand of 'sizeof' is an expression designating a bit-field.
    0543 [U] 'void' expressions have no value and may not be used in expressions.
    0544 [U] The value of an incomplete 'union' may not be used.
    0545 [U] The value of an incomplete 'struct' may not be used.
    0602 [U] The identifier '%s' is reserved for use by the library.
    0603 [U] The macro identifier '%s' is reserved.
    0623 [U] '%s' has incomplete type and no linkage - this is undefined.
    0625 [U] '%s' has been declared with both internal and external linkage - the behaviour is undefined.
    0626 [U] '%s' has different type to previous declaration (which is no longer in scope).
    0630 [U] More than one definition of '%s' (with external linkage).
    0632 [U] Tentative definition of '%s' with internal linkage cannot have unknown size.
    0636 [U] There are no named members in this 'struct' or 'union'.
    0654 [U] Using 'const' or 'volatile' in a function return type is undefined.
    0658 [U] Parameter cannot have 'void' type.
    0661 [U] '%s()' may not have a storage class specifier of 'static' when declared at block scope.
    0667 [U] '%s' is declared as a typedef and may not be redeclared as an object at an inner scope without an explicit type specifier.
    0668 [U] '%s' is declared as a typedef and may not be redeclared as a member of a 'struct' or 'union' without an explicit type specifier.
    0672 [U] The initializer for a 'struct', 'union' or array is not enclosed in braces.
    0676 [u] Array element is of function type. Arrays cannot be constructed from function types.
    0678 [u] Array element is array of unknown size. Arrays cannot be constructed from incomplete types.
    0680 [u] Array element is 'void' or an incomplete 'struct' or 'union'. Arrays cannot be constructed from incomplete types.
    0706 [U] Label '%s' is not unique within this function.
    0745 [U] 'return;' found in '%s()', which has been defined with a non-'void' return type.
    0777 [U] External identifier does not differ from other identifier(s) (e.g. '%s') within the specified number of significant characters.
    0779 [U] Identifier does not differ from other identifier(s) (e.g. '%s') within the specified number of significant characters.
    0813 [U] Using any of the characters ' " or /* in '#include <%s>' gives undefined behaviour.
    0814 [U] Using the characters ' or /* in '#include "%s"' gives undefined behaviour.
    0836 [U] Definition of macro named 'defined'.
    0837 [U] Use of '#undef' to remove the operator 'defined'.
    0840 [E] Extra tokens at end of #include directive.
    0848 [U] Attempting to #undef '%s', which is a predefined macro name.
    0853 [U] Macro arguments contain a sequence of tokens that has the form of a preprocessing directive.
    0854 [U] Attempting to #define '%s', which is a predefined macro name.
    0864 [U] '#line' directive specifies line number which is not in the range 1 to 32767.
    0865 [U] '#line' directive is badly formed.
    0867 [U] '#line' has not been followed by a line number.
    0872 [U] Result of '##' operator is not a legal preprocessing token.
    0874 [U] Character string literal and wide character string literal are adjacent.
    0885 [U] The token 'defined' is generated in the expansion of this macro.
    0887 [U] Use of 'defined' must match either 'defined(identifier)' or 'defined identifier'.
    0888 [U] 'defined' requires an identifier as an argument.
    0914 [U] Source file does not end with a newline character.
    0915 [U] Source file ends with a backslash character followed by a newline.
    0942 [U] A * can only be used to specify array size within function prototype scope.
    1331 Type or number of arguments doesn't match previous use of the function.
    1332 Type or number of arguments doesn't match prototype found later.
    1333 Type or number of arguments doesn't match function definition found later.
    2800 Constant: Overflow in signed arithmetic operation.
    2810 Constant: Dereference of NULL pointer.
    2820 Constant: Arithmetic operation on NULL pointer.
    2830 Constant: Division by zero.
    2840 Constant: Dereference of an invalid pointer value.
    3113 [U] 'return' statement includes no expression but function '%s()' is implicitly of type 'int'.
    3114 [U] Function '%s()' is implicitly of type 'int' but ends without returning a value.
    3239 [U] inline function '%1s' has external linkage, but is not defined within this translation unit.
    3311 [u] An earlier jump to this statement will bypass the initialization of local variables.
    3312 [u] This goto statement will jump into a previous block and bypass the initialization of local variables.
    3319 [U] Function called with number of arguments which differs from number of parameters in definition.
    3320 Type of argument no. %s differs from its type in definition of function.
    3437 [u] The assert macro has been suppressed to call a function of that name.
    3438 [U] #undef'ing the assert macro to call a function of that name causes undefined behaviour.
    1509 '%1s' has external linkage and has multiple definitions.
    1510 '%1s' has external linkage and has incompatible declarations.
    -
    Rule-10.1RequiredOperands shall not be of an inappropriate essential type.
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    QacDescription
    3101 Unary '-' applied to an operand of type unsigned int or unsigned long gives an unsigned result.
    3102 Unary '-' applied to an operand whose underlying type is unsigned.
    4500 An expression of 'essentially Boolean' type (%1s) is being used as an array subscript.
    4501 An expression of 'essentially Boolean' type (%1s) is being used as the %2s operand of this arithmetic operator (%3s).
    4502 An expression of 'essentially Boolean' type (%1s) is being used as the %2s operand of this bitwise operator (%3s).
    4503 An expression of 'essentially Boolean' type (%1s) is being used as the left-hand operand of this shift operator (%2s).
    4504 An expression of 'essentially Boolean' type (%1s) is being used as the right-hand operand of this shift operator (%2s).
    4505 An expression of 'essentially Boolean' type (%1s) is being used as the %2s operand of this relational operator (%3s).
    4507 An expression of 'essentially Boolean' type (%1s) is being used as the operand of this increment/decrement operator (%2s).
    4510 An expression of 'essentially character' type (%1s) is being used as an array subscript.
    4511 An expression of 'essentially character' type (%1s) is being used as the %2s operand of this arithmetic operator (%3s).
    4512 An expression of 'essentially character' type (%1s) is being used as the %2s operand of this bitwise operator (%3s).
    4513 An expression of 'essentially character' type (%1s) is being used as the left-hand operand of this shift operator (%2s).
    4514 An expression of 'essentially character' type (%1s) is being used as the right-hand operand of this shift operator (%2s).
    4518 An expression of 'essentially character' type (%1s) is being used as the %2s operand of this logical operator (%3s).
    4519 An expression of 'essentially character' type (%1s) is being used as the first operand of this conditional operator (%2s).
    4521 An expression of 'essentially enum' type (%1s) is being used as the %2s operand of this arithmetic operator (%3s).
    4522 An expression of 'essentially enum' type (%1s) is being used as the %2s operand of this bitwise operator (%3s).
    4523 An expression of 'essentially enum' type (%1s) is being used as the left-hand operand of this shift operator (%2s).
    4524 An expression of 'essentially enum' type (%1s) is being used as the right-hand operand of this shift operator (%2s).
    4527 An expression of 'essentially enum' type is being used as the operand of this increment/decrement operator.
    4528 An expression of 'essentially enum' type (%1s) is being used as the %2s operand of this logical operator (%3s).
    4529 An expression of 'essentially enum' type (%1s) is being used as the first operand of this conditional operator (%2s).
    4532 An expression of 'essentially signed' type (%1s) is being used as the %2s operand of this bitwise operator (%3s).
    4533 An expression of 'essentially signed' type (%1s) is being used as the left-hand operand of this shift operator (%2s).
    4534 An expression of 'essentially signed' type (%1s) is being used as the right-hand operand of this shift operator (%2s).
    4538 An expression of 'essentially signed' type (%1s) is being used as the %2s operand of this logical operator (%3s).
    4539 An expression of 'essentially signed' type (%1s) is being used as the first operand of this conditional operator (%2s).
    4542 A non-negative constant expression of 'essentially signed' type (%1s) is being used as the %2s operand of this bitwise operator (%3s).
    4543 A non-negative constant expression of 'essentially signed' type (%1s) is being used as the left-hand operand of this shift operator (%2s).
    4548 A non-negative constant expression of 'essentially signed' type (%1s) is being used as the %2s operand of this logical operator (%3s).
    4549 A non-negative constant expression of 'essentially signed' type (%1s) is being used as the first operand of this conditional operator (%2s).
    4558 An expression of 'essentially unsigned' type (%1s) is being used as the %2s operand of this logical operator (%3s).
    4559 An expression of 'essentially unsigned' type (%1s) is being used as the first operand of this conditional operator (%2s).
    4568 An expression of 'essentially floating' type (%1s) is being used as the %2s operand of this logical operator (%3s).
    4569 An expression of 'essentially floating' type (%1s) is being used as the first operand of this conditional operator (%2s).
    -
    Rule-10.2RequiredExpressions of essentially character type shall not be used inappropriately in addition and subtraction operations
    - - - - - - - - - - - - - - - - - -
    QacDescription
    1810 An operand of 'essentially character' type is being added to another operand of 'essentially character' type.
    1811 An operand of 'essentially character' type is being subtracted from an operand of 'essentially signed' type.
    1812 An operand of 'essentially character' type is being subtracted from an operand of 'essentially unsigned' type.
    1813 An operand of 'essentially character' type is being balanced with an operand of 'essentially floating' type in this arithmetic operation.
    -
    Rule-10.3RequiredThe value of an expression shall not be assigned to an object with a narrower essential type or of a different essential type category.
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    QacDescription
    0570 This switch case label of 'essential type' '%1s', is not consistent with a controlling expression of essential type '%2s'.
    0572 This switch case label of 'essential type' '%1s' is not consistent with a controlling expression which has an essential type of lower rank (%2s).
    1257 An integer constant suffixed with L or LL is being converted to a type of lower rank on assignment.
    1264 A suffixed floating constant is being converted to a different floating type on assignment.
    1265 An unsuffixed floating constant is being converted to a different floating type on assignment.
    1266 A floating constant is being converted to integral type on assignment.
    1291 An integer constant of 'essentially unsigned' type is being converted to signed type on assignment.
    1292 An integer constant of 'essentially signed' type is being converted to type char on assignment.
    1293 An integer constant of 'essentially unsigned' type is being converted to type char on assignment.
    1294 An integer constant of 'essentially signed' type is being converted to type _Bool on assignment.
    1295 An integer constant of 'essentially unsigned' type is being converted to type _Bool on assignment.
    1296 An integer constant of 'essentially signed' type is being converted to enum type on assignment.
    1297 An integer constant of 'essentially unsigned' type is being converted to enum type on assignment.
    1298 An integer constant of 'essentially signed' type is being converted to floating type on assignment.
    1299 An integer constant of 'essentially unsigned' type is being converted to floating type on assignment.
    2850 Constant: Implicit conversion to a signed integer type of insufficient size.
    2890 Constant: Negative value implicitly converted to an unsigned type.
    2900 Constant: Positive integer value truncated by implicit conversion to a smaller unsigned type.
    4401 An expression of 'essentially Boolean' type (%1s) is being converted to character type, '%2s' on assignment.
    4402 An expression of 'essentially Boolean' type (%1s) is being converted to enum type, '%2s' on assignment.
    4403 An expression of 'essentially Boolean' type (%1s) is being converted to signed type, '%2s' on assignment.
    4404 An expression of 'essentially Boolean' type (%1s) is being converted to unsigned type, '%2s' on assignment.
    4405 An expression of 'essentially Boolean' type (%1s) is being converted to floating type, '%2s' on assignment.
    4410 An expression of 'essentially character' type (%1s) is being converted to Boolean type, '%2s' on assignment.
    4412 An expression of 'essentially character' type (%1s) is being converted to enum type, '%2s' on assignment.
    4413 An expression of 'essentially character' type (%1s) is being converted to signed type, '%2s' on assignment.
    4414 An expression of 'essentially character' type (%1s) is being converted to unsigned type, '%2s' on assignment.
    4415 An expression of 'essentially character' type (%1s) is being converted to floating type, '%2s' on assignment.
    4420 An expression of 'essentially enum' type (%1s) is being converted to Boolean type, '%2s' on assignment.
    4421 An expression of 'essentially enum' type (%1s) is being converted to character type, '%2s' on assignment.
    4422 An expression of 'essentially enum' type (%1s) is being converted to a different enum type, '%2s' on assignment.
    4423 An expression of 'essentially enum' type (%1s) is being converted to signed type, '%2s' on assignment.
    4424 An expression of 'essentially enum' type (%1s) is being converted to unsigned type, '%2s' on assignment.
    4425 An expression of 'essentially enum' type (%1s) is being converted to floating type, '%2s' on assignment.
    4430 An expression of 'essentially signed' type (%1s) is being converted to Boolean type, '%2s' on assignment.
    4431 An expression of 'essentially signed' type (%1s) is being converted to character type, '%2s' on assignment.
    4432 An expression of 'essentially signed' type (%1s) is being converted to enum type, '%2s' on assignment.
    4434 A non-constant expression of 'essentially signed' type (%1s) is being converted to unsigned type, '%2s' on assignment.
    4435 A non-constant expression of 'essentially signed' type (%1s) is being converted to floating type, '%2s' on assignment.
    4437 A constant expression of 'essentially signed' type (%1s) is being converted to floating type, '%2s' on assignment.
    4440 An expression of 'essentially unsigned' type (%1s) is being converted to Boolean type, '%2s' on assignment.
    4441 An expression of 'essentially unsigned' type (%1s) is being converted to character type, '%2s' on assignment.
    4442 An expression of 'essentially unsigned' type (%1s) is being converted to enum type, '%2s' on assignment.
    4443 A non-constant expression of 'essentially unsigned' type (%1s) is being converted to a wider signed type, '%2s' on assignment.
    4445 An expression of 'essentially unsigned' type (%1s) is being converted to floating type, '%2s' on assignment.
    4446 A non-constant expression of 'essentially unsigned' type (%1s) is being converted to signed type, '%2s' on assignment.
    4447 A constant expression of 'essentially unsigned' type (%1s) is being converted to signed type, '%2s' on assignment.
    4450 An expression of 'essentially floating' type (%1s) is being converted to Boolean type, '%2s' on assignment.
    4451 An expression of 'essentially floating' type (%1s) is being converted to character type, '%2s' on assignment.
    4452 An expression of 'essentially floating' type (%1s) is being converted to enum type, '%2s' on assignment.
    4453 An expression of 'essentially floating' type (%1s) is being converted to signed type, '%2s' on assignment.
    4454 An expression of 'essentially floating' type (%1s) is being converted to unsigned type, '%2s' on assignment.
    4460 A non-constant expression of 'essentially signed' type (%1s) is being converted to narrower signed type, '%2s' on assignment.
    4461 A non-constant expression of 'essentially unsigned' type (%1s) is being converted to narrower unsigned type, '%2s' on assignment.
    4462 A non-constant expression of 'essentially floating' type (%1s) is being converted to narrower floating type, '%2s' on assignment.
    4463 A constant expression of 'essentially signed' type (%1s) is being converted to narrower signed type, '%2s' on assignment.
    4464 A constant expression of 'essentially unsigned' type (%1s) is being converted to narrower unsigned type, '%2s' on assignment.
    4465 A constant expression of 'essentially floating' type (%1s) is being converted to narrower floating type, '%2s' on assignment.
    -
    Rule-10.4RequiredBoth operands of an operator in which the usual arithmetic conversions are performed shall have the same essential type category
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    QacDescription
    1800 The %1s operand (essential type: '%2s') will be implicitly converted to a floating type, '%3s', in this arithmetic operation.
    1802 The %1s operand (essential type: '%2s') will be implicitly converted to a floating type, '%3s', in this relational operation.
    1803 The %1s operand (essential type: '%2s') will be implicitly converted to a floating type, '%3s', in this equality operation.
    1804 The %1s operand (essential type: '%2s') will be implicitly converted to a floating type, '%3s', in this conditional operation.
    1820 The %1s operand is non-constant and 'essentially signed' (%2s) but will be implicitly converted to an unsigned type (%3s) in this arithmetic operation.
    1821 The %1s operand is non-constant and 'essentially signed' (%2s) but will be implicitly converted to an unsigned type (%3s) in this bitwise operation.
    1822 The %1s operand is non-constant and 'essentially signed' (%2s) but will be implicitly converted to an unsigned type (%3s) in this relational operation.
    1823 The %1s operand is non-constant and 'essentially signed' (%2s) but will be implicitly converted to an unsigned type (%3s) in this equality operation.
    1824 The %1s operand is non-constant and 'essentially signed' (%2s) but will be implicitly converted to an unsigned type (%3s) in this conditional operation.
    1830 The %1s operand is constant, 'essentially signed' (%2s) and negative but will be implicitly converted to an unsigned type (%3s) in this arithmetic operation.
    1831 The %1s operand is constant, 'essentially signed' (%2s) and negative but will be implicitly converted to an unsigned type (%3s) in this bitwise operation.
    1832 The %1s operand is constant, 'essentially signed' (%2s) and negative but will be implicitly converted to an unsigned type (%3s) in this relational operation.
    1833 The %1s operand is constant, 'essentially signed' (%2s) and negative but will be implicitly converted to an unsigned type (%3s) in this equality operation.
    1834 The %1s operand is constant, 'essentially signed' (%2s) and negative but will be implicitly converted to an unsigned type (%3s) in this conditional operation.
    1840 The %1s operand is constant, 'essentially signed' (%2s) and non-negative but will be implicitly converted to an unsigned type (%3s) in this arithmetic operation.
    1841 The %1s operand is constant, 'essentially signed' (%2s) and non-negative but will be implicitly converted to an unsigned type (%3s) in this bitwise operation.
    1842 The %1s operand is constant, 'essentially signed' (%2s) and non-negative but will be implicitly converted to an unsigned type (%3s) in this relational operation.
    1843 The %1s operand is constant, 'essentially signed' (%2s) and non-negative but will be implicitly converted to an unsigned type (%3s) in this equality operation.
    1844 The %1s operand is constant, 'essentially signed' (%2s) and non-negative but will be implicitly converted to an unsigned type (%3s) in this conditional operation.
    1850 The %1s operand is 'essentially unsigned' (%2s) but will be implicitly converted to a signed type (%3s) in this arithmetic operation.
    1851 The %1s operand is 'essentially unsigned' (%2s) but will be implicitly converted to a signed type (%3s) in this bitwise operation.
    1852 The %1s operand is 'essentially unsigned' (%2s) but will be implicitly converted to a signed type (%3s) in this relational operation.
    1853 The %1s operand is 'essentially unsigned' (%2s) but will be implicitly converted to a signed type (%3s) in this equality operation.
    1854 The %1s operand is 'essentially unsigned' (%2s) but will be implicitly converted to a signed type (%3s) in this conditional operation.
    1860 The operands of this arithmetic operator are of different 'essential signedness' but will generate a result of type 'signed int'.
    1861 The operands of this bitwise operator are of different 'essential signedness' but will generate a result of type 'signed int'.
    1862 The operands of this relational operator are of different 'essential signedness' but will both be promoted to 'signed int' for comparison.
    1863 The operands of this equality operator are of different 'essential signedness' but will both be promoted to 'signed int' for comparison.
    1864 The 2nd and 3rd operands of this conditional operator are of different 'essential signedness'. The result will be in the promoted type 'signed int'.
    1880 The operands of this relational operator are expressions of different 'essential type' categories (%1s and %2s).
    1881 The operands of this equality operator are expressions of different 'essential type' categories (%1s and %2s).
    1882 The 2nd and 3rd operands of this conditional operator are expressions of different 'essential type' categories (%1s and %2s).
    -
    Rule-10.5AdvisoryThe value of an expression should not be cast to an inappropriate essential type
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    QacDescription
    4301 An expression of 'essentially Boolean' type (%1s) is being cast to character type '%2s'.
    4302 An expression of 'essentially Boolean' type (%1s) is being cast to enum type '%2s'.
    4303 An expression of 'essentially Boolean' type (%1s) is being cast to signed type '%2s'.
    4304 An expression of 'essentially Boolean' type (%1s) is being cast to unsigned type '%2s'.
    4305 An expression of 'essentially Boolean' type (%1s) is being cast to floating type '%2s'.
    4310 An expression of 'essentially character' type (%1s) is being cast to Boolean type, '%2s'.
    4312 An expression of 'essentially character' type (%1s) is being cast to enum type, '%2s'.
    4315 An expression of 'essentially character' type (%1s) is being cast to floating type, '%2s'.
    4320 An expression of 'essentially enum' type (%1s) is being cast to Boolean type, '%2s'.
    4322 An expression of 'essentially enum' type (%1s) is being cast to a different enum type, '%2s'.
    4330 An expression of 'essentially signed' type (%1s) is being cast to Boolean type '%2s'.
    4332 An expression of 'essentially signed' type (%1s) is being cast to enum type, '%2s'.
    4340 An expression of 'essentially unsigned' type (%1s) is being cast to Boolean type '%2s'.
    4342 An expression of 'essentially unsigned' type (%1s) is being cast to enum type '%2s'.
    4350 An expression of 'essentially floating' type (%1s) is being cast to Boolean type '%2s'.
    4351 An expression of 'essentially floating' type (%1s) is being cast to character type '%2s'.
    4352 An expression of 'essentially floating' type (%1s) is being cast to enum type, '%2s'.
    -
    Rule-10.6RequiredThe value of a composite expression shall not be assigned to an object with wider essential type
    - - - - - - - - - - - - - - - - - -
    QacDescription
    4490 A composite expression of 'essentially signed' type (%1s) is being converted to wider signed type, '%2s' on assignment.
    4491 A composite expression of 'essentially unsigned' type (%1s) is being converted to wider unsigned type, '%2s' on assignment.
    4492 A composite expression of 'essentially floating' type (%1s) is being converted to wider floating type, '%2s' on assignment.
    4499 An expression which is the result of a ~ or << operation has been converted to a wider essential type on assignment.
    -
    Rule-10.7RequiredIf a composite expression is used as one operand of an operator in which the usual arithmetic conversions are performed then the other operand shall not have wider essential type
    - - - - - - - - - - - - - - - - - - - - - - - - - -
    QacDescription
    1890 A composite expression of 'essentially signed' type (%1s) is being implicitly converted to a wider signed type, '%2s'.
    1891 A composite expression of 'essentially unsigned' type (%1s) is being implicitly converted to a wider unsigned type, '%2s'.
    1892 A composite expression of 'essentially floating' type (%1s) is being implicitly converted to a wider floating type, '%2s'.
    1893 The 2nd and 3rd operands of this conditional operator are both 'essentially signed' ('%1s' and '%2s') but one is a composite expression of a narrower type than the other.
    1894 The 2nd and 3rd operands of this conditional operator are both 'essentially unsigned' ('%1s' and '%2s') but one is a composite expression of a narrower type than the other.
    1895 The 2nd and 3rd operands of this conditional operator are both 'essentially floating' ('%1s' and '%2s') but one is a composite expression of a narrower type than the other.
    -
    Rule-10.8RequiredThe value of a composite expression shall not be cast to a different essential type category or a wider essential type
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    QacDescription
    4389 A composite expression of 'essentially char' type (%1s) is being cast to a different type category, '%2s'.
    4390 A composite expression of 'essentially signed' type (%1s) is being cast to a wider signed type, '%2s'.
    4391 A composite expression of 'essentially unsigned' type (%1s) is being cast to a wider unsigned type, '%2s'.
    4392 A composite expression of 'essentially floating' type (%1s) is being cast to a wider floating type, '%2s'.
    4393 A composite expression of 'essentially signed' type (%1s) is being cast to a different type category, '%2s'.
    4394 A composite expression of 'essentially unsigned' type (%1s) is being cast to a different type category, '%2s'.
    4395 A composite expression of 'essentially floating' type (%1s) is being cast to a different type category, '%2s'.
    4398 An expression which is the result of a ~ or << operation has been cast to a different essential type category.
    4399 An expression which is the result of a ~ or << operation has been cast to a wider type.
    -
    Rule-11.1RequiredConversions shall not be performed between a pointer to a function and any other type
    - - - - - - - - - - - - - - - - - -
    QacDescription
    0302 [u] Cast between a pointer to function and a floating type.
    0305 [I] Cast between a pointer to function and an integral type.
    0307 [u] Cast between a pointer to object and a pointer to function.
    0313 Casting to different function pointer type.
    -
    Rule-11.2RequiredConversions shall not be performed between a pointer to an incomplete type and any other type
    - - - - - - - - - - - - - - - - - -
    QacDescription
    0308 Non-portable cast involving pointer to an incomplete type.
    0323 [u] Cast between a pointer to incomplete type and a floating type.
    0324 [u] Cast between a pointer to incomplete type and an integral type.
    0325 [u] Cast between a pointer to incomplete type and a pointer to function.
    -
    Rule-11.3RequiredA cast shall not be performed between a pointer to object type and a pointer to a different object type
    - - - - - - - - - -
    QacDescription
    0310 Casting to different object pointer type.
    3305 Pointer cast to stricter alignment.
    -
    Rule-11.4AdvisoryA conversion should not be performed between a pointer to object and an integer type
    - - - - - - - - - - - - - - - - - - - - - -
    QacDescription
    0303 [I] Cast between a pointer to volatile object and an integral type.
    0306 [I] Cast between a pointer to object and an integral type.
    0360 An expression of pointer type is being converted to type _Bool on assignment.
    0361 An expression of pointer type is being cast to type _Bool.
    0362 An expression of essentially Boolean type is being cast to a pointer.
    -
    Rule-11.5AdvisoryA conversion should not be performed from pointer to void into pointer to object
    - - - - - - - - - -
    QacDescription
    0316 [I] Cast from a pointer to void to a pointer to object type.
    0317 [I] Implicit conversion from a pointer to void to a pointer to object type.
    -
    Rule-11.6RequiredA cast shall not be performed between pointer to void and an arithmetic type
    - - - - - - - - - -
    QacDescription
    0326 [I] Cast between a pointer to void and an integral type.
    0327 [I] Cast between a pointer to void and an floating type.
    -
    Rule-11.7RequiredA cast shall not be performed between pointer to object and a non-integer arithmetic type
    - - - - - - - - - -
    QacDescription
    0301 [u] Cast between a pointer to object and a floating type.
    0328 [u] Cast between a pointer to object and an essential type other than signed/unsigned.
    -
    Rule-11.8RequiredA cast shall not remove any const or volatile qualification from the type pointed to by a pointer
    - - - - - - - - - -
    QacDescription
    0311 Dangerous pointer cast results in loss of const qualification.
    0312 Dangerous pointer cast results in loss of volatile qualification.
    -
    Rule-11.9RequiredThe macro NULL shall be the only permitted form of integer null pointer constant
    - - - - - - - - - -
    QacDescription
    3003 This character constant is being interpreted as a NULL pointer constant.
    3004 This integral constant expression is being interpreted as a NULL pointer constant.
    -
    Rule-12.1AdvisoryThe precedence of operators within expressions should be made explicit
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    QacDescription
    3389 Extra parentheses recommended to clarify the ordering of a % operator and another arithmetic operator (* / % + -).
    3391 Extra parentheses recommended. A conditional operation is the operand of another conditional operator.
    3392 Extra parentheses recommended. A shift, relational or equality operation is the operand of a second identical operator.
    3394 Extra parentheses recommended. A shift, relational or equality operation is the operand of a different operator with the same precedence.
    3395 Extra parentheses recommended. A * or / operation is the operand of a + or - operator.
    3396 Extra parentheses recommended. A binary operation is the operand of a conditional operator.
    3397 Extra parentheses recommended. A binary operation is the operand of a binary operator with different precedence.
    -
    Rule-12.2RequiredThe right hand operand of a shift operator shall lie in the range zero to one less than the width in bits of the essential type of the left hand operand
    - - - - - - - - - - - - - - - - - -
    QacDescription
    0499 Right operand of shift operator is greater than or equal to the width of the essential type of the left operand.
    2790 Constant: Right hand operand of shift operator is negative or too large.
    2791 Definite: Right hand operand of shift operator is negative or too large.
    2792 Apparent: Right hand operand of shift operator is negative or too large.
    -
    Rule-12.3AdvisoryThe comma operator should not be used
    - - - - - - - - - -
    QacDescription
    3417 The comma operator has been used outside a 'for' statement.
    3418 The comma operator has been used in a 'for' statement.
    -
    Rule-12.4AdvisoryEvaluation of constant expressions should not lead to unsigned integer wrap-around
    - - - - - -
    QacDescription
    2910 Constant: Wraparound in unsigned arithmetic operation.
    -
    Rule-12.5MandatoryThe sizeof operator shall not have an operand which is a function parameter declared as 'array of type'
    - - - - - -
    QacDescription
    1321 Operand of sizeof is a function parameter of array type.
    -
    Rule-13.1RequiredInitializer lists shall not contain persistent side-effects
    - - - - - -
    QacDescription
    3421 Expression with possible side effects is used in an initializer list.
    -
    Rule-13.2RequiredThe value of an expression and its persistent side-effects shall be the same under all permitted evaluation orders
    - - - - - - - - - - - - - - - - - - - - - - - - - -
    QacDescription
    0400 [U] '%s' is modified more than once between sequence points - evaluation order unspecified.
    0401 [U] '%s' may be modified more than once between sequence points - evaluation order unspecified.
    0402 [U] '%s' is modified and accessed between sequence points - evaluation order unspecified.
    0403 [U] '%s' may be modified and accessed between sequence points - evaluation order unspecified.
    0404 More than one read access to volatile objects between sequence points.
    0405 More than one modification of volatile objects between sequence points.
    -
    Rule-13.3AdvisoryA full expression containing an increment (++) or decrement (--) operator should have no other potential side effects other than that caused by the increment or decrement operator
    - - - - - -
    QacDescription
    3440 Using the value resulting from a ++ or -- operation.
    -
    Rule-13.4AdvisoryThe result of an assignment operator should not be used
    - - - - - - - - - -
    QacDescription
    3226 The result of an assignment is being used in an arithmetic operation or another assigning operation.
    3326 The result of an assignment is being used in a logical operation.
    -
    Rule-13.5RequiredThe right hand operand of a logical && or || operator shall not contain persistent side effects
    - - - - - -
    QacDescription
    3415 Right hand operand of '&&' or '||' is an expression with possible side effects.
    -
    Rule-13.6MandatoryThe operand of the sizeof operator shall not contain any expression which has potential side-effects
    - - - - - - - - - -
    QacDescription
    0945 [C99] Operand of sizeof is an expression of variable length array type with side effects.
    3307 The operand of 'sizeof' is an expression with implied side effects, but they will not be evaluated.
    -
    Rule-14.1RequiredA loop counter shall not have essentially floating type
    - - - - - - - - - - - - - -
    QacDescription
    3339 Floating point variable used as 'while' loop control variable.
    3340 Floating point variable used as 'for' loop control variable.
    3342 Controlling expression of 'for' loop is a floating point comparison.
    -
    Rule-14.2RequiredA for loop shall be well-formed
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    QacDescription
    2461 Loop control variable in this 'for' statement, %s, has file scope.
    2462 The variable initialized in the first expression of this 'for' statement is not the variable identified as the 'loop control variable' (%s).
    2463 The variable incremented in the third expression of this 'for' statement is not the variable identified as the 'loop control variable' (%s).
    2464 Loop control variable, %s, modified twice in for-loop header.
    2467 Loop control variable in this 'for' statement, %s, is not modified inside loop.
    2468 Loop control variable in this 'for' statement, %s, is not modified inside loop but has file scope.
    2469 Loop control variable in this 'for' statement, %s, is modified in the body of the loop.
    2471 Unable to identify a loop control variable.
    2472 More than one possible loop control variable.
    -
    Rule-14.3RequiredControlling expressions shall not be invariant
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    QacDescription
    2741 This 'if' controlling expression is a constant expression and its value is 'true'.
    2742 This 'if' controlling expression is a constant expression and its value is 'false'.
    2990 The value of this loop controlling expression is always 'true'.
    2991 The value of this 'if' controlling expression is always 'true'.
    2992 The value of this 'if' controlling expression is always 'false'.
    2993 The value of this 'do - while' loop controlling expression is always 'false'. The loop will only be executed once.
    2994 The value of this 'while' or 'for' loop controlling expression is always 'false'. The loop will not be entered.
    2997 The first operand of this conditional operator is always 'true'.
    2998 The first operand of this conditional operator is always 'false'.
    3493 The first operand of this conditional operator is always constant 'true'.
    3494 The first operand of this conditional operator is always constant 'false'.
    -
    Rule-14.4RequiredThe controlling expression of an if-statement and the controlling expression of an iteration-statement shall have essentially Boolean type
    - - - - - -
    QacDescription
    3344 Controlling expression is not an 'essentially Boolean' expression.
    -
    Rule-15.1AdvisoryThe goto statement should not be used
    - - - - - -
    QacDescription
    2001 A 'goto' statement has been used.
    -
    Rule-15.2RequiredThe goto statement shall jump to a label declared later in the same function
    - - - - - -
    QacDescription
    3310 This 'goto' statement involves a backward jump.
    -
    Rule-15.3RequiredAny label referenced by a goto statement shall be declared in the same block, or in any block enclosing the goto statement
    - - - - - -
    QacDescription
    3327 This goto statement references a label that is declared in a separate block.
    -
    Rule-15.4AdvisoryThere should be no more than one break or goto statement used to terminate any iteration statement
    - - - - - -
    QacDescription
    0771 More than one 'break' statement has been used to terminate this iteration statement.
    -
    Rule-15.5AdvisoryA function should have a single point of exit at the end
    - - - - - -
    QacDescription
    2889 This function has more than one 'return' path.
    -
    Rule-15.6RequiredThe body of an iteration-statement or a selection-statement shall be a compound-statement
    - - - - - - - - - - - - - - - - - - - - - -
    QacDescription
    2212 Body of control statement is not enclosed within braces.
    2214 Body of control statement is on the same line and is not enclosed within braces.
    2218 Body of switch statement is not enclosed within braces.
    2219 Body of switch statement is on the same line and is not enclosed within braces.
    3402 Braces are needed to clarify the structure of this 'if'-'if'-'else' statement.
    -
    Rule-15.7RequiredAll if ... else if constructs shall be terminated with an else statement
    - - - - - - - - - -
    QacDescription
    2004 No concluding 'else' exists in this 'if'-'else'-'if' statement.
    2013 This 'if .. else if ' construct 'else' statement is empty.
    -
    Rule-16.1RequiredAll switch statements shall be well-formed
    - - - - - - - - - -
    QacDescription
    2008 Code statements precede the first label in this 'switch' construct.
    3234 Declarations precede the first label in this 'switch' construct.
    -
    Rule-16.2RequiredA switch label shall only be used when the most closely-enclosing compound statement is the body of a switch statement
    - - - - - -
    QacDescription
    2019 'Switch' label is located within a nested code block.
    -
    Rule-16.3RequiredAn unconditional break statement shall terminate every switch-clause
    - - - - - - - - - -
    QacDescription
    2003 The preceding 'switch' clause is not empty and does not end with a 'jump' statement. Execution will fall through.
    2020 Final 'switch' clause does not end with an explicit 'jump' statement.
    -
    Rule-16.4RequiredEvery switch statement shall have a default label
    - - - - - - - - - -
    QacDescription
    2002 No 'default' label found in this 'switch' statement.
    2016 This 'switch' statement 'default' clause is empty.
    -
    Rule-16.5RequiredA default label shall appear as either the first or the last switch label of a switch statement
    - - - - - -
    QacDescription
    2012 This 'default' label is neither the first nor the last label within the 'switch' block.
    -
    Rule-16.6RequiredEvery switch statement shall have at least two switch-clauses
    - - - - - -
    QacDescription
    3315 This 'switch' statement is redundant.
    -
    Rule-16.7RequiredA switch-expression shall not have essentially Boolean type
    - - - - - -
    QacDescription
    0735 Switch expression is of essentially Boolean type.
    -
    Rule-17.1RequiredThe features of shall not be used
    - - - - - - - - - -
    QacDescription
    5130 Use of standard header file .
    1337 Function defined with a variable number of parameters.
    -
    Rule-17.2RequiredFunctions shall not call themselves, either directly or indirectly
    - - - - - - - - - -
    QacDescription
    3670 Recursive call to function containing this call.
    1520 Functions are indirectly recursive.
    -
    Rule-17.3MandatoryA function shall not be declared implicitly
    - - - - - -
    QacDescription
    3335 No function declaration. Implicit declaration inserted: 'extern int %s();'.
    -
    Rule-17.4MandatoryAll exit paths from a function with non-void return type shall have an explicit return statement with an expression
    - - - - - - - - - - - - - - - - - - - - - -
    QacDescription
    0745 [U] 'return;' found in '%s()', which has been defined with a non-'void' return type.
    2887 Function 'main' ends with an implicit 'return' statement.
    2888 This function has been declared with a non-void 'return' type but ends with an implicit 'return ;' statement.
    3113 [U] 'return' statement includes no expression but function '%s()' is implicitly of type 'int'.
    3114 [U] Function '%s()' is implicitly of type 'int' but ends without returning a value.
    -
    Rule-17.5AdvisoryThe function argument corresponding to a parameter declared to have an array type shall have an appropriate number of elements
    - - - - - - - - - - - - - - - - - -
    QacDescription
    2781 Definite: Function argument has fewer elements than the array dimension in the parameter declaration for non-inlined call.
    2782 Apparent: Function argument has fewer elements than the array dimension in the parameter declaration for non-inlined call.
    2783 Suspicious: Function argument has fewer elements than the array dimension in the parameter declaration for non-inlined call.
    2784 Possible: Function argument has fewer elements than the array dimension in the parameter declaration for non-inlined call.
    -
    Rule-17.6MandatoryThe declaration of an array parameter shall not contain the static keyword between the [ ]
    - - - - - -
    QacDescription
    1058 [C99] The keyword 'static' is used in the declaration of a function parameter of array type.
    -
    Rule-17.7RequiredThe value returned by a function having non-void return type shall be used
    - - - - - -
    QacDescription
    3200 '%s' returns a value which is not being used.
    -
    Rule-17.8AdvisoryA function parameter should not be modified
    - - - - - - - - - - - - - -
    QacDescription
    1338 The parameter '%s' is being modified.
    1339 Evaluating the address of the parameter '%s'.
    1340 Storing the address of the parameter '%s' in a constant pointer.
    -
    Rule-18.1RequiredA pointer resulting from arithmetic on a pointer operand shall address an element of the same array as that pointer operand
    - - - - - - - - - - - - - - - - - - - - - - - - - -
    QacDescription
    2840 Constant: Dereference of an invalid pointer value.
    2841 Definite: Dereference of an invalid pointer value.
    2842 Apparent: Dereference of an invalid pointer value.
    2930 Constant: Computing an invalid pointer value.
    2931 Definite: Computing an invalid pointer value.
    2932 Apparent: Computing an invalid pointer value.
    -
    Rule-18.2RequiredSubtraction between pointers shall only be applied to pointers that address elements of the same array
    - - - - - - - - - - - - - -
    QacDescription
    2668 Subtraction of a pointer to an array and a pointer to a non array.
    2761 Definite: Subtracting pointers that address different objects.
    2762 Apparent: Subtracting pointers that address different objects.
    -
    Rule-18.3RequiredThe relational operators >, >=, < and <= shall not be applied to objects of pointer type except where they point into the same object
    - - - - - - - - - - - - - -
    QacDescription
    2669 Comparison of a pointer to an array and a pointer to a non array.
    2771 Definite: Comparing pointers that address different objects.
    2772 Apparent: Comparing pointers that address different objects.
    -
    Rule-18.4AdvisoryThe +, -, += and -= operators should not be applied to an expression of pointer type
    - - - - - -
    QacDescription
    0488 Performing pointer arithmetic.
    -
    Rule-18.5AdvisoryDeclarations should contain no more than two levels of pointer nesting
    - - - - - - - - - - - - - - - - - -
    QacDescription
    3260 Typedef defined with more than 2 levels of indirection.
    3261 Member of struct/union defined with more than 2 levels of indirection.
    3262 Object defined or declared with more than 2 levels of indirection.
    3263 Function defined or declared with a return type which has more than 2 levels of indirection.
    -
    Rule-18.6RequiredThe address of an object with automatic storage shall not be copied to another object that persists after the first object has ceased to exist
    - - - - - - - - - - - - - - - - - -
    QacDescription
    3217 Address of automatic object exported to a pointer with linkage or wider scope.
    3225 Address of automatic object exported using a function parameter.
    3230 Address of automatic object assigned to local pointer with static storage duration.
    4140 Address of automatic object exported in function return value.
    -
    Rule-18.7RequiredFlexible array members shall not be declared
    - - - - - -
    QacDescription
    1060 [C99] A flexible array member has been declared.
    -
    Rule-18.8RequiredVariable-length array types shall not be used
    - - - - - - - - - -
    QacDescription
    1051 [C99] A variable length array has been declared.
    1052 [C99] A variable length array of unspecified size has been declared.
    -
    Rule-19.1MandatoryAn object shall not be assigned or copied to an overlapping object
    - - - - - - - - - - - - - -
    QacDescription
    0681 [U] Assignment between two incompatible members of the same union.
    2776 Definite: Copy between overlapping objects.
    2777 Apparent: Copy between overlapping objects.
    -
    Rule-19.2AdvisoryThe union keyword should not be used
    - - - - - - - - - -
    QacDescription
    0750 A union type specifier has been defined.
    0759 An object of union type has been defined.
    -
    Rule-2.1RequiredA project shall not contain unreachable code
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    QacDescription
    0594 Negative 'case' label expression is incompatible with unsigned controlling expression in 'switch' statement.
    1460 'Switch' label value, %s, not contained in enum type.
    2744 This 'while' or 'for' loop controlling expression is a constant expression and its value is 'false'. The loop will not be entered.
    2880 This code is unreachable.
    2882 This 'switch' statement will bypass the initialization of local variables.
    3219 Static function '%s()' is not used within this translation unit.
    1503 The function '%1s' is defined but is not used within this project.
    -
    Rule-2.2RequiredThere shall be no dead code
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    QacDescription
    2980 The value of this function parameter is never used before being modified.
    2981 This initialization is redundant. The value of this object is never used before being modified.
    2982 This assignment is redundant. The value of this object is never used before being modified.
    2983 This assignment is redundant. The value of this object is never subsequently used.
    2985 This operation is redundant. The value of the result is always that of the left-hand operand.
    2986 This operation is redundant. The value of the result is always that of the right-hand operand.
    2987 This function call produces no side effects and is redundant.
    2995 The result of this logical operation is always 'true'.
    2996 The result of this logical operation is always 'false'.
    3110 The left-hand operand of this ',' has no side effects.
    3112 This statement has no side-effect - it can be removed.
    3404 Statement contains a redundant * operator at top level. *p++ means *(p++) not (*p)++.
    3422 Statement contains a redundant operator at top level.
    3423 Statement contains a redundant cast at top level.
    3424 Statement contains a redundant & or | at top level.
    3425 One branch of this conditional operation is a redundant expression.
    3426 Right hand side of comma expression has no side effect and its value is not used.
    3427 Right hand side of logical operator has no side effect and its value is not used.
    -
    Rule-2.3AdvisoryA project should not contain unused type declarations
    - - - - - -
    QacDescription
    3205 The identifier '%s' is not used and could be removed.
    -
    Rule-2.4AdvisoryA project should not contain unused tag declarations
    - - - - - - - - - -
    QacDescription
    3213 The tag '%s' is not used and could be removed.
    1755 The tag '%1s' is declared but not used within this project.
    -
    Rule-2.5AdvisoryA project should not contain unused macro declarations
    - - - - - -
    QacDescription
    3214 The macro '%s' is not used and could be removed.
    -
    Rule-2.6AdvisoryA function should not contain unused label declarations
    - - - - - -
    QacDescription
    3202 The label '%s:' is not used in this function and could be removed.
    -
    Rule-2.7AdvisoryThere should be no unused parameters in functions
    - - - - - -
    QacDescription
    3206 The parameter '%s' is not used in this function.
    -
    Rule-20.1Advisory#include directives should only be preceded by preprocessor directives or comments
    - - - - - -
    QacDescription
    5087 Use of #include directive after code fragment.
    -
    Rule-20.10AdvisoryThe # and ## preprocessor operators should not be used
    - - - - - - - - - -
    QacDescription
    0341 Using the stringify operator '#'.
    0342 Using the glue operator '##'.
    -
    Rule-20.11RequiredA macro parameter immediately following a # operator shall not immediately be followed by a ## operator
    - - - - - -
    QacDescription
    0892 This macro parameter is preceded by '#' and followed by '##'.
    -
    Rule-20.12RequiredA macro parameter used as an operand to the # or ## operators, which is itself subject to further macro replacement, shall only be used as an operand to these operators
    - - - - - -
    QacDescription
    0893 Macro parameter '%s' is inconsistently subject to macro replacement.
    -
    Rule-20.13RequiredA line whose first token is # shall be a valid preprocessing directive
    - - - - - -
    QacDescription
    3115 Unrecognized preprocessing directive has been ignored because of conditional inclusion directives.
    -
    Rule-20.14RequiredAll #else, #elif and #endif preprocessor directives shall reside in the same file as the #if, #ifdef or #ifndef directive to which they are related
    - - - - - - - - - -
    QacDescription
    3317 '#if...' not matched by '#endif' in included file. This is probably an error.
    3318 '#else'/'#elif'/'#endif' in included file matched '#if...' in parent file. This is probably an error.
    -
    Rule-20.2RequiredThe ', " or \ characters and the /* or // character sequences shall not occur in a header file name
    - - - - - - - - - - - - - -
    QacDescription
    0813 [U] Using any of the characters ' " or /* in '#include <%s>' gives undefined behaviour.
    0814 [U] Using the characters ' or /* in '#include "%s"' gives undefined behaviour.
    0831 [E] Use of '\\' in this '#include' line is a PC extension - this usage is non-portable.
    -
    Rule-20.3RequiredThe #include directive shall be followed by either a or "filename" sequence
    - - - - - - - - - - - - - -
    QacDescription
    0817 [S] Closing quote or bracket '>' missing from include filename.
    0821 [C] '#include' does not identify a header or source file that can be processed.
    0840 [E] Extra tokens at end of #include directive.
    -
    Rule-20.4RequiredA macro shall not be defined with the same name as a keyword
    - - - - - - - - - -
    QacDescription
    3439 Macro redefines a keyword.
    3468 The name of this macro is a reserved identifier in C90 and a keyword in C99.
    -
    Rule-20.5Advisory#undef should not be used
    - - - - - -
    QacDescription
    0841 Using '#undef'.
    -
    Rule-20.6RequiredTokens that look like a preprocessing directive shall not occur within a macro argument
    - - - - - -
    QacDescription
    0853 [U] Macro arguments contain a sequence of tokens that has the form of a preprocessing directive.
    -
    Rule-20.7RequiredExpressions resulting from the expansion of macro parameters shall be enclosed in parentheses
    - - - - - - - - - -
    QacDescription
    3430 Macro argument expression may require parentheses.
    3432 Simple macro argument expression is not parenthesized.
    -
    Rule-20.8RequiredThe controlling expression of a #if or #elif preprocessing directive shall evaluate to 0 or 1
    - - - - - -
    QacDescription
    0894 '#%s' directive controlling expression does not evaluate to zero or one.
    -
    Rule-20.9RequiredAll identifiers used in the controlling expression of #if or #elif preprocessing directives shall be #define'd before evaluation
    - - - - - -
    QacDescription
    3332 The macro '%s' used in this '#if' or '#elif' expression is not defined.
    -
    Rule-21.1Required#define and #undef shall not be used on a reserved identifier or reserved macro name
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    QacDescription
    0603 [U] The macro identifier '%s' is reserved.
    0836 [U] Definition of macro named 'defined'.
    0848 [U] Attempting to #undef '%s', which is a predefined macro name.
    0854 [U] Attempting to #define '%s', which is a predefined macro name.
    4600 The macro '%1s' is also defined in '<%2s>'.
    4601 The macro '%1s' is the name of an identifier in '<%2s>'.
    4620 The macro '%1s' may also be defined as a macro in '<%2s>'.
    4621 The macro '%1s' may also be defined as a typedef in '<%2s>'.
    -
    Rule-21.10RequiredThe Standard Library time and date functions shall not be used
    - - - - - -
    QacDescription
    5127 Use of standard header file .
    -
    Rule-21.11RequiredThe standard header file shall not be used
    - - - - - -
    QacDescription
    5131 Use of standard header file .
    -
    Rule-21.12AdvisoryThe exception handling features of should not be used
    - - - - - -
    QacDescription
    5136 Use of exception handling identifier: feclearexcept, fegetexceptflag, feraiseexcept, fesetexceptflag or fetestexcept.
    -
    Rule-21.13MandatoryAny value passed to a function in shall be representable as an unsigned char or be the value EOF
    - - - - - - - - - -
    QacDescription
    2796 Definite: Calling a standard library character handling function with an invalid character value.
    2797 Apparent: Calling a standard library character handling function with an invalid character value.
    -
    Rule-21.14RequiredThe Standard Library function memcmp shall not be used to compare null terminated strings
    - - - - - - - - - -
    QacDescription
    2785 Constant: Null terminated string is being passed as argument to Standard Library function memcmp.
    2786 Definite: Null terminated string is being passed as argument to Standard Library function memcmp.
    -
    Rule-21.15RequiredThe pointer arguments to the Standard Library functions memcpy, memmove and memcmp shall be pointers to qualified or unqualified versions of compatible types
    - - - - - - - - - - - - - -
    QacDescription
    1487 Comparing the representations of objects of different types.
    1495 Destination and source objects have incompatible types.
    1496 Destination and source objects may have incompatible types.
    -
    Rule-21.16RequiredThe pointer arguments to the Standard Library function memcpy shall point to either a pointer type, an essentially signed type, an essentially unsigned type, an essentially Boolean type or an essentially enum type
    - - - - - - - - - - - - - - - - - - - - - -
    QacDescription
    1488 Comparison of a struct object representation.
    1489 Comparison of a union object representation.
    1490 Comparison of a floating point object representation.
    1491 Comparison of an object representation.
    1497 Comparison of a string object representation.
    -
    Rule-21.17MandatoryUse of the string handling functions from shall not result in accesses beyond the bounds of the objects referenced by their pointer parameters
    - - - - - - - - - -
    QacDescription
    2835 Constant: Non null terminated string used in a string function.
    2836 Definite: Non null terminated string used in a string function.
    -
    Rule-21.18MandatoryThe size_t argument passed to any function in shall have an appropriate value
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    QacDescription
    2840 Constant: Dereference of an invalid pointer value.
    2841 Definite: Dereference of an invalid pointer value.
    2842 Apparent: Dereference of an invalid pointer value.
    2865 Constant: Using 0 as size parameter of a function call.
    2866 Definite: Using 0 as size parameter of a function call.
    2867 Apparent: Using 0 as size parameter of a function call.
    2868 Suspicious: Using 0 as size parameter of a function call.
    2869 Possible: Using 0 as size parameter of a function call.
    -
    Rule-21.19MandatoryThe pointers returned by the Standard Library functions lovaleconv, getenv, setlocale or strerror shall only be used as if they have pointer to const-qualified type
    - - - - - - - - - - - - - - - - - -
    QacDescription
    1492 The result of library function '%s' is used to modify the referenced object.
    1493 The result of library function '%s' is used as a pointer to a modifiable object.
    1494 The result of library function '%s' might be modified.
    1498 The string referenced by type 'struct lconv' member '%s' is being modified.
    -
    Rule-21.2RequiredA reserved identifier or macro name shall not be declared
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    QacDescription
    0602 [U] The identifier '%s' is reserved for use by the library.
    4602 The identifier '%1s' is declared as a macro in '<%2s>'.
    4603 The object/function '%1s'is being defined with the same name as an ordinary identifier defined in '<%2s>'.
    4604 The object/function '%1s' is being declared with the same name as an ordinary identifier defined in '<%2s>'.
    4605 The typedef '%1s' is also defined in '<%2s>'.
    4606 The typedef '%1s' has the same name as another ordinary identifier in '<%2s>'.
    4607 The enum constant '%1s' has the same name as another ordinary identifier in '<%2s>'.
    4608 The tag '%1s' is also defined in '<%2s>'.
    -
    Rule-21.20MandatoryThe pointer returned by the Standard Library functions asctime, ctime, gmtime, localtime, localeconv, getenv, setlocale, or strerror shall not be used following a subsequent call to the same function
    - - - - - - - - - -
    QacDescription
    2681 Definite: Using an invalidated value '%s' returned from a Standard Library function.
    2682 Apparent: Using an invalidated value '%s' returned from a Standard Library function.
    -
    Rule-21.3RequiredThe memory allocation and deallocation functions of shall not be used
    - - - - - -
    QacDescription
    5118 Use of memory allocation or deallocation function: calloc, malloc, realloc or free.
    -
    Rule-21.4RequiredThe standard header file shall not be used
    - - - - - -
    QacDescription
    5132 Use of standard header file .
    -
    Rule-21.5RequiredThe standard header file shall not be used
    - - - - - -
    QacDescription
    5123 Use of standard header file .
    -
    Rule-21.6RequiredThe Standard Library input/output functions shall not be used
    - - - - - -
    QacDescription
    5124 The Standard Library input/output functions shall not be used
    -
    Rule-21.7RequiredThe atof, atoi, atol and atoll functions of shall not be used
    - - - - - -
    QacDescription
    5125 Use of function: atof, atoi, atol or atoll.
    -
    Rule-21.8RequiredThe library functions abort, exit and system of shall not be used
    - - - - - - - - - -
    QacDescription
    5126 Use of function: abort, exit or system.
    5128 Use of function: getenv.
    -
    Rule-21.9RequiredThe library functions bsearch and qsort of shall not be used
    - - - - - -
    QacDescription
    5135 Use of function: bsearch or qsort.
    -
    Rule-22.1RequiredAll resources obtained dynamically by means of Standard Library functions shall be explicitly released
    - - - - - - - - - - - - - - - - - - - - - - - - - -
    QacDescription
    2701 Definite: Opened file is not closed.
    2702 Apparent: Opened file is not closed.
    2706 Definite: Allocated memory is not deallocated.
    2707 Apparent: Allocated memory is not deallocated.
    2736 Definite: Created resource is not destroyed.
    2737 Apparent: Created resource is not destroyed.
    -
    Rule-22.10RequiredThe value of errno shall only be tested when the last function to be called was an errno-setting-function
    - - - - - -
    QacDescription
    2503 Testing of 'errno' is not immediately preceded by a call to an 'errno' setting function.
    -
    Rule-22.2MandatoryA block of memory shall only be freed if it was allocated by means of a Standard Library function
    - - - - - - - - - -
    QacDescription
    2721 Definite: Deallocation of non dynamic memory.
    2722 Apparent: Deallocation of non dynamic memory.
    -
    Rule-22.3RequiredThe same file shall not be open for read and write access at the same time on different streams
    - - - - - - - - - - - - - -
    QacDescription
    2691 Definite: The same file will be open with write access and another mode.
    2692 Apparent: The same file will be open with write access and another mode.
    2693 Suspicious: The same file will be open with write access and another mode.
    -
    Rule-22.4MandatoryThere shall be no attempt to write to a stream which has been opened as read-only
    - - - - - - - - - - - - - -
    QacDescription
    2686 Definite: Writing to a file opened for reading.
    2687 Apparent: Writing to a file opened for reading.
    2688 Suspicious: Writing to a file opened for reading.
    -
    Rule-22.5MandatoryA pointer to a FILE object shall not be dereferenced
    - - - - - - - - - -
    QacDescription
    1485 A pointer to a FILE object is dereferenced.
    1486 A pointer to a FILE object is converted to a different type.
    -
    Rule-22.6MandatoryThe value of a pointer to a FILE shall not be used after the associated stream has been closed
    - - - - - - - - - -
    QacDescription
    2696 Definite: Attempt to access a file which has been closed.
    2697 Apparent: Attempt to access a file which has been closed.
    -
    Rule-22.7RequiredThe macro EOF shall on ly be compared with the unmodified return value from any Standard Library function capable of returning EOF
    - - - - - - - - - -
    QacDescription
    2671 Definite: The value being compared with macro EOF does not originate from an EOF returning function.
    2676 Definite: The value originating from an EOF returning function was modified before being compared with macro EOF.
    -
    Rule-22.8RequiredThe value of errno shall be set to zero prior to a call to an errno-setting-function
    - - - - - -
    QacDescription
    2500 Call to '%s' is not immediately preceded by the zeroing of 'errno'.
    -
    Rule-22.9RequiredThe value of errno shall be tested against zero after calling an errno-setting-function
    - - - - - -
    QacDescription
    2501 Call to '%s' is not immediately followed by the testing of 'errno'.
    -
    Rule-3.1RequiredThe character sequences /* and // shall not be used within a comment.
    - - - - - -
    QacDescription
    3108 Nested comments are not recognized in the ISO standard.
    -
    Rule-3.2RequiredLine-splicing shall not be used in // comments.
    - - - - - -
    QacDescription
    5134 C++ style comment uses line splicing.
    -
    Rule-4.1RequiredOctal and hexadecimal escape sequences shall be terminated
    - - - - - - - - - -
    QacDescription
    3636 Octal escape sequence '%s' is not terminated.
    3637 Hexadecimal escape sequence '%s' is not terminated.
    -
    Rule-4.2AdvisoryTrigraphs should not be used
    - - - - - -
    QacDescription
    3601 Trigraphs (??x) are an ISO feature.
    -
    Rule-5.1RequiredExternal identifiers shall be distinct
    - - - - - -
    QacDescription
    0777 [U] External identifier does not differ from other identifier(s) (e.g. '%s') within the specified number of significant characters.
    -
    Rule-5.2RequiredIdentifiers declared in the same scope and name space shall be distinct
    - - - - - -
    QacDescription
    0779 [U] Identifier does not differ from other identifier(s) (e.g. '%s') within the specified number of significant characters.
    -
    Rule-5.3RequiredAn identifier declared in an inner scope shall not hide an identifier declared in an outer scope
    - - - - - - - - - - - - - -
    QacDescription
    0795 Identifier matches other identifier(s) (e.g. '%s') in an outer scope within the specified number of significant characters.
    2547 This declaration of tag '%s' hides a more global declaration.
    3334 This declaration of '%s' hides a more global declaration.
    -
    Rule-5.4RequiredMacro identifiers shall be distinct
    - - - - - - - - - -
    QacDescription
    0788 This identifier, '%s', is used as both a macro name and a function-like macro parameter name.
    0791 [U] Macro identifier does not differ from other macro identifier(s) (e.g. '%s') within the specified number of significant characters.
    -
    Rule-5.5RequiredIdentifiers shall be distinct from macro names
    - - - - - - - - - - - - - - - - - -
    QacDescription
    0784 Identifier '%s' is also used as a macro name.
    0785 Identifier matches other macro name(s) (e.g. '%s') in first 31 characters.
    0786 Identifier matches other macro name(s) (e.g. '%s') in first 63 characters.
    0787 Identifier does not differ from other macro name(s) (e.g. '%s') within the specified number of significant characters.
    -
    Rule-5.6RequiredA typedef name shall be a unique identifier
    - - - - - - - - - - - - - -
    QacDescription
    1506 The identifier '%1s' is declared as a typedef and is used elsewhere for a different kind of declaration.
    1507 '%1s' is used as a typedef for different types.
    1508 The typedef '%1s' is declared in more than one location.
    -
    Rule-5.7RequiredA tag name shall be a unique identifier
    - - - - - - - - - -
    QacDescription
    2547 This declaration of tag '%s' hides a more global declaration.
    1750 '%1s' has multiple definitions.
    -
    Rule-5.8RequiredIdentifiers that define objects or functions with external linkage shall be unique
    - - - - - - - - - - - - - -
    QacDescription
    1525 Object/function with external linkage has same identifier as another object/function with internal linkage.
    1526 Object with no linkage has same identifier as another object/function with external linkage.
    1756 External identifier '%1s' shall be unique.
    -
    Rule-5.9AdvisoryIdentifiers that define objects or functions with internal linkage should be unique
    - - - - - - - - - - - - - -
    QacDescription
    1525 Object/function with external linkage has same identifier as another object/function with internal linkage.
    1527 Object/function with internal linkage has same identifier as another object/function with internal linkage.
    1528 Object with no linkage has same identifier as another object/function with internal linkage.
    -
    Rule-6.1RequiredBit-fields shall only be declared with an appropriate type
    - - - - - - - - - -
    QacDescription
    0634 [I] Bit-fields in this struct/union have not been declared explicitly as unsigned or signed.
    0635 [E] Bit-fields in this struct/union have been declared with types other than int, signed int, unsigned int or _Bool.
    -
    Rule-6.2RequiredSingle-bit named bit fields shall not be of a signed type
    - - - - - -
    QacDescription
    3660 Named bit-field consisting of a single bit declared with a signed type.
    -
    Rule-7.1RequiredOctal constants shall not be used
    - - - - - - - - - -
    QacDescription
    0336 Macro defined as an octal constant.
    0339 Octal constant used.
    -
    Rule-7.2RequiredA "u" or "U" suffix shall be applied to all integer constants that are represented in an unsigned type
    - - - - - -
    QacDescription
    1281 Integer literal constant is of an unsigned type but does not include a "U" suffix.
    -
    Rule-7.3RequiredThe lowercase character "l" shall not be used in a literal suffix
    - - - - - -
    QacDescription
    1280 A lowercase letter L (l) has been used in an integer or floating suffix.
    -
    Rule-7.4RequiredA string literal shall not be assigned to an object unless the object's type is "pointer to const-qualified char"
    - - - - - - - - - -
    QacDescription
    0752 String literal passed as argument to function whose parameter is not a 'pointer to const'.
    0753 String literal assigned to pointer which is not a 'pointer to const'.
    -
    Rule-8.1RequiredTypes shall be explicitly specified
    - - - - - - - - - - - - - -
    QacDescription
    2050 The 'int' type specifier has been omitted from a function declaration.
    2051 The 'int' type specifier has been omitted from an object declaration.
    1525 Object/function with external linkage has same identifier as another object/function with internal linkage.
    -
    Rule-8.10RequiredAn inline function shall be declared with the static storage class
    - - - - - - - - - -
    QacDescription
    3240 inline function '%s' is being defined with external linkage.
    3243 inline function '%s' is also an 'external definition'.
    -
    Rule-8.11AdvisoryWhen an array with external linkage is declared, its size should be explicitly specified
    - - - - - -
    QacDescription
    3684 Array declared with unknown size.
    -
    Rule-8.12RequiredWithin an enumerator list, the value of an implicitly-specified enumeration constant shall be unique
    - - - - - -
    QacDescription
    0724 The value of this implicitly-specified enumeration constant is not unique.
    -
    Rule-8.13AdvisoryA pointer should point to a const-qualified type whenever possible
    - - - - - -
    QacDescription
    3673 The object addressed by the pointer parameter '%s' is not modified and so the pointer could be of type 'pointer to const'.
    -
    Rule-8.14RequiredThe restrict type qualifier shall not be used
    - - - - - -
    QacDescription
    1057 [C99] The keyword 'restrict' has been used.
    -
    Rule-8.2RequiredFunction types shall be in prototype form with named parameters
    - - - - - - - - - - - - - - - - - - - - - -
    QacDescription
    1335 Parameter identifiers missing in function prototype declaration.
    1336 Parameter identifiers missing in declaration of a function type.
    3001 Function has been declared with an empty parameter list.
    3002 Defining '%s()' with an identifier list and separate parameter declarations is an obsolescent feature.
    3007 "void" has been omitted when defining a function with no parameters.
    -
    Rule-8.3RequiredAll declarations of an object or function shall use the same names and type qualifiers
    - - - - - - - - - - - - - -
    QacDescription
    0624 Function '%s' is declared using typedefs which are different to those in a previous declaration.
    1330 The parameter identifiers in this function declaration differ from those in a previous declaration.
    3675 Function parameter declared with type qualification which differs from previous declaration.
    -
    Rule-8.4RequiredA compatible declaration shall be visible when an object or function with external linkage is defined
    - - - - - -
    QacDescription
    3408 '%s' has external linkage and is being defined without any previous declaration.
    -
    Rule-8.5RequiredAn external object or function shall be declared once in one and only one file
    - - - - - - - - - - - - - -
    QacDescription
    3449 Multiple declarations of external object or function.
    3451 The global identifier '%s' has been declared in more than one file.
    1513 Identifier '%1s' with external linkage has separate non-defining declarations in more than one location.
    -
    Rule-8.6RequiredAn identifier with external linkage shall have exactly one external definition
    - - - - - - - - - - - - - - - - - - - - - -
    QacDescription
    0630 [U] More than one definition of '%s' (with external linkage).
    3406 Object/function '%s', with external linkage, has been defined in a header file.
    1509 '%1s' has external linkage and has multiple definitions.
    1752 The object '%1s' with external linkage is declared but not defined within this project.
    1753 The function '%1s' with external linkage is declared but not defined within this project.
    -
    Rule-8.7AdvisoryFunctions and objects should not be defined with external linkage if they are referenced in only one translation unit
    - - - - - - - - - - - - - - - - - -
    QacDescription
    1504 The object '%1s' is only referenced in the translation unit where it is defined.
    1505 The function '%1s' is only referenced in the translation unit where it is defined.
    1531 The object '%1s' is referenced in only one translation unit - but not the one in which it is defined.
    1532 The function '%1s' is only referenced in one translation unit - but not the one in which it is defined.
    -
    Rule-8.8RequiredThe static storage class specifier shall be used in all declarations of objects and functions that have internal linkage
    - - - - - -
    QacDescription
    3224 This identifier has previously been declared with internal linkage but is not declared here with the static storage class specifier.
    -
    Rule-8.9AdvisoryAn object should be defined at block scope if its identifier only appears in a single function
    - - - - - - - - - - - - - -
    QacDescription
    3218 File scope static, '%s', is only accessed in one function.
    1514 The object '%1s' is only referenced by function '%2s', in the translation unit where it is defined
    1533 The object '%1s' is only referenced by function '%2s'.
    -
    Rule-9.1MandatoryThe value of an object with automatic storage duration shall not be read before it has been set
    - - - - - - - - - - - - - - - - - - - - - -
    QacDescription
    2883 This 'goto' statement will always bypass the initialization of local variables.
    2961 Definite: Using value of uninitialized automatic object '%s'.
    2962 Apparent: Using value of uninitialized automatic object '%s'.
    2971 Definite: Passing address of uninitialized object '%s' to a function parameter declared as a pointer to const.
    2972 Apparent: Passing address of uninitialized object '%s' to a function parameter declared as a pointer to const.
    -
    Rule-9.2RequiredThe initializer for an aggregate or union shall be enclosed in braces
    - - - - - - - - - -
    QacDescription
    0693 Struct initializer is missing the optional {.
    0694 Array initializer is missing the optional {.
    -
    Rule-9.3RequiredArrays shall not be partially initialized
    - - - - - -
    QacDescription
    0686 Array has fewer initializers than its declared size. Default initialization is applied to the remainder of the array elements.
    -
    Rule-9.4RequiredAn element of an object shall not be initialized more than once
    - - - - - - - - - - - - - -
    QacDescription
    1397 Array element '%s' has already been initialized.
    1398 Structure member '%s' has already been initialized.
    1399 A union member has already been initialized.
    -
    Rule-9.5RequiredWhere designated initializers are used to initialize an array object the size of the array shall be specified explicitly
    - - - - - -
    QacDescription
    3676 Designators are used to initialize an array of unspecified size.
    -
    -
    -
    - -This section targets to provide an overview of Guidelines Recategorization Plan. -
    -
    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    GuidelineDescriptionCategoryRevised Category
    Dir-1.1Any implementation-defined behaviour on which the output of the program depends shall be documented and understoodRequiredRequired
    Dir-2.1All source files shall compile without any compilation errorsRequiredDisapplied
    Dir-3.1All code shall be traceable to documented requirementsRequiredDisapplied
    Dir-4.1Run-time failures shall be minimizedRequiredRequired
    Dir-4.10Precautions shall be taken in order to prevent the contents of a header file being included more then onceRequiredRequired
    Dir-4.11The validity of values passed to library functions shall be checkedRequiredDisapplied
    Dir-4.12Dynamic memory allocation shall not be usedRequiredDisapplied
    Dir-4.13Functions which are designed to provide operations on a resource should be called in an appropriate sequenceAdvisoryDisapplied
    Dir-4.14The validity of values received from external sources shall be checkedRequiredRequired
    Dir-4.2All usage of assembly language should be documentedAdvisoryAdvisory
    Dir-4.3Assembly language shall be encapsulated and isolatedRequiredRequired
    Dir-4.4Sections of code should not be "commented out"AdvisoryDisapplied
    Dir-4.5Identifiers in the same name space with overlapping visibility should be typographically unambiguousAdvisoryDisapplied
    Dir-4.6typedefs that indicate size and signedness should be used in place of the basic numerical typesAdvisoryAdvisory
    Dir-4.7If a function returns error information, then that error information shall be testedRequiredDisapplied
    Dir-4.8If a pointer to a structure or union is never dereferenced within a translation unit, then the implementation of the object should be hiddenAdvisoryDisapplied
    Dir-4.9A function should be used in preference to a function-like macro where they are interchangeableAdvisoryDisapplied
    Rule-1.1The program shall contain no violations of the standard C syntax and constraints, and shall not exceed the implementation's translation limitsRequiredRequired
    Rule-1.2Language extensions should not be usedAdvisoryAdvisory
    Rule-1.3There shall be no occurrence of undefined or critical unspecified behaviourRequiredRequired
    Rule-10.1Operands shall not be of an inappropriate essential type.RequiredRequired
    Rule-10.2Expressions of essentially character type shall not be used inappropriately in addition and subtraction operationsRequiredRequired
    Rule-10.3The value of an expression shall not be assigned to an object with a narrower essential type or of a different essential type category.RequiredRequired
    Rule-10.4Both operands of an operator in which the usual arithmetic conversions are performed shall have the same essential type categoryRequiredRequired
    Rule-10.5The value of an expression should not be cast to an inappropriate essential typeAdvisoryAdvisory
    Rule-10.6The value of a composite expression shall not be assigned to an object with wider essential typeRequiredRequired
    Rule-10.7If a composite expression is used as one operand of an operator in which the usual arithmetic conversions are performed then the other operand shall not have wider essential typeRequiredRequired
    Rule-10.8The value of a composite expression shall not be cast to a different essential type category or a wider essential typeRequiredRequired
    Rule-11.1Conversions shall not be performed between a pointer to a function and any other typeRequiredRequired
    Rule-11.2Conversions shall not be performed between a pointer to an incomplete type and any other typeRequiredRequired
    Rule-11.3A cast shall not be performed between a pointer to object type and a pointer to a different object typeRequiredRequired
    Rule-11.4A conversion should not be performed between a pointer to object and an integer typeAdvisoryAdvisory
    Rule-11.5A conversion should not be performed from pointer to void into pointer to objectAdvisoryAdvisory
    Rule-11.6A cast shall not be performed between pointer to void and an arithmetic typeRequiredRequired
    Rule-11.7A cast shall not be performed between pointer to object and a non-integer arithmetic typeRequiredRequired
    Rule-11.8A cast shall not remove any const or volatile qualification from the type pointed to by a pointerRequiredRequired
    Rule-11.9The macro NULL shall be the only permitted form of integer null pointer constantRequiredDisapplied
    Rule-12.1The precedence of operators within expressions should be made explicitAdvisoryAdvisory
    Rule-12.2The right hand operand of a shift operator shall lie in the range zero to one less than the width in bits of the essential type of the left hand operandRequiredRequired
    Rule-12.3The comma operator should not be usedAdvisoryAdvisory
    Rule-12.4Evaluation of constant expressions should not lead to unsigned integer wrap-aroundAdvisoryAdvisory
    Rule-12.5The sizeof operator shall not have an operand which is a function parameter declared as 'array of type'MandatoryMandatory
    Rule-13.1Initializer lists shall not contain persistent side-effectsRequiredRequired
    Rule-13.2The value of an expression and its persistent side-effects shall be the same under all permitted evaluation ordersRequiredRequired
    Rule-13.3A full expression containing an increment (++) or decrement (--) operator should have no other potential side effects other than that caused by the increment or decrement operatorAdvisoryDisapplied
    Rule-13.4The result of an assignment operator should not be usedAdvisoryAdvisory
    Rule-13.5The right hand operand of a logical && or || operator shall not contain persistent side effectsRequiredRequired
    Rule-13.6The operand of the sizeof operator shall not contain any expression which has potential side-effectsMandatoryMandatory
    Rule-14.1A loop counter shall not have essentially floating typeRequiredRequired
    Rule-14.2A for loop shall be well-formedRequiredRequired
    Rule-14.3Controlling expressions shall not be invariantRequiredRequired
    Rule-14.4The controlling expression of an if-statement and the controlling expression of an iteration-statement shall have essentially Boolean typeRequiredRequired
    Rule-15.1The goto statement should not be usedAdvisoryAdvisory
    Rule-15.2The goto statement shall jump to a label declared later in the same functionRequiredRequired
    Rule-15.3Any label referenced by a goto statement shall be declared in the same block, or in any block enclosing the goto statementRequiredRequired
    Rule-15.4There should be no more than one break or goto statement used to terminate any iteration statementAdvisoryAdvisory
    Rule-15.5A function should have a single point of exit at the endAdvisoryDisapplied
    Rule-15.6The body of an iteration-statement or a selection-statement shall be a compound-statementRequiredRequired
    Rule-15.7All if ... else if constructs shall be terminated with an else statementRequiredRequired
    Rule-16.1All switch statements shall be well-formedRequiredRequired
    Rule-16.2A switch label shall only be used when the most closely-enclosing compound statement is the body of a switch statementRequiredRequired
    Rule-16.3An unconditional break statement shall terminate every switch-clauseRequiredRequired
    Rule-16.4Every switch statement shall have a default labelRequiredRequired
    Rule-16.5A default label shall appear as either the first or the last switch label of a switch statementRequiredRequired
    Rule-16.6Every switch statement shall have at least two switch-clausesRequiredRequired
    Rule-16.7A switch-expression shall not have essentially Boolean typeRequiredRequired
    Rule-17.1The features of shall not be usedRequiredRequired
    Rule-17.2Functions shall not call themselves, either directly or indirectlyRequiredRequired
    Rule-17.3A function shall not be declared implicitlyMandatoryMandatory
    Rule-17.4All exit paths from a function with non-void return type shall have an explicit return statement with an expressionMandatoryMandatory
    Rule-17.5The function argument corresponding to a parameter declared to have an array type shall have an appropriate number of elementsAdvisoryAdvisory
    Rule-17.6The declaration of an array parameter shall not contain the static keyword between the [ ]MandatoryMandatory
    Rule-17.7The value returned by a function having non-void return type shall be usedRequiredDisapplied
    Rule-17.8A function parameter should not be modifiedAdvisoryAdvisory
    Rule-18.1A pointer resulting from arithmetic on a pointer operand shall address an element of the same array as that pointer operandRequiredRequired
    Rule-18.2Subtraction between pointers shall only be applied to pointers that address elements of the same arrayRequiredRequired
    Rule-18.3The relational operators >, >=, < and <= shall not be applied to objects of pointer type except where they point into the same objectRequiredRequired
    Rule-18.4The +, -, += and -= operators should not be applied to an expression of pointer typeAdvisoryAdvisory
    Rule-18.5Declarations should contain no more than two levels of pointer nestingAdvisoryAdvisory
    Rule-18.6The address of an object with automatic storage shall not be copied to another object that persists after the first object has ceased to existRequiredRequired
    Rule-18.7Flexible array members shall not be declaredRequiredRequired
    Rule-18.8Variable-length array types shall not be usedRequiredRequired
    Rule-19.1An object shall not be assigned or copied to an overlapping objectMandatoryMandatory
    Rule-19.2The union keyword should not be usedAdvisoryAdvisory
    Rule-2.1A project shall not contain unreachable codeRequiredRequired
    Rule-2.2There shall be no dead codeRequiredRequired
    Rule-2.3A project should not contain unused type declarationsAdvisoryDisapplied
    Rule-2.4A project should not contain unused tag declarationsAdvisoryAdvisory
    Rule-2.5A project should not contain unused macro declarationsAdvisoryDisapplied
    Rule-2.6A function should not contain unused label declarationsAdvisoryAdvisory
    Rule-2.7There should be no unused parameters in functionsAdvisoryAdvisory
    Rule-20.1#include directives should only be preceded by preprocessor directives or commentsAdvisoryAdvisory
    Rule-20.10The # and ## preprocessor operators should not be usedAdvisoryAdvisory
    Rule-20.11A macro parameter immediately following a # operator shall not immediately be followed by a ## operatorRequiredRequired
    Rule-20.12A macro parameter used as an operand to the # or ## operators, which is itself subject to further macro replacement, shall only be used as an operand to these operatorsRequiredRequired
    Rule-20.13A line whose first token is # shall be a valid preprocessing directiveRequiredRequired
    Rule-20.14All #else, #elif and #endif preprocessor directives shall reside in the same file as the #if, #ifdef or #ifndef directive to which they are relatedRequiredRequired
    Rule-20.2The ', " or \ characters and the /* or // character sequences shall not occur in a header file nameRequiredRequired
    Rule-20.3The #include directive shall be followed by either a or "filename" sequenceRequiredRequired
    Rule-20.4A macro shall not be defined with the same name as a keywordRequiredRequired
    Rule-20.5#undef should not be usedAdvisoryAdvisory
    Rule-20.6Tokens that look like a preprocessing directive shall not occur within a macro argumentRequiredRequired
    Rule-20.7Expressions resulting from the expansion of macro parameters shall be enclosed in parenthesesRequiredRequired
    Rule-20.8The controlling expression of a #if or #elif preprocessing directive shall evaluate to 0 or 1RequiredRequired
    Rule-20.9All identifiers used in the controlling expression of #if or #elif preprocessing directives shall be #define'd before evaluationRequiredRequired
    Rule-21.1#define and #undef shall not be used on a reserved identifier or reserved macro nameRequiredRequired
    Rule-21.10The Standard Library time and date functions shall not be usedRequiredRequired
    Rule-21.11The standard header file shall not be usedRequiredRequired
    Rule-21.12The exception handling features of should not be usedAdvisoryAdvisory
    Rule-21.13Any value passed to a function in shall be representable as an unsigned char or be the value EOFMandatoryMandatory
    Rule-21.14The Standard Library function memcmp shall not be used to compare null terminated stringsRequiredRequired
    Rule-21.15The pointer arguments to the Standard Library functions memcpy, memmove and memcmp shall be pointers to qualified or unqualified versions of compatible typesRequiredRequired
    Rule-21.16The pointer arguments to the Standard Library function memcpy shall point to either a pointer type, an essentially signed type, an essentially unsigned type, an essentially Boolean type or an essentially enum typeRequiredRequired
    Rule-21.17Use of the string handling functions from shall not result in accesses beyond the bounds of the objects referenced by their pointer parametersMandatoryMandatory
    Rule-21.18The size_t argument passed to any function in shall have an appropriate valueMandatoryMandatory
    Rule-21.19The pointers returned by the Standard Library functions lovaleconv, getenv, setlocale or strerror shall only be used as if they have pointer to const-qualified typeMandatoryMandatory
    Rule-21.2A reserved identifier or macro name shall not be declaredRequiredRequired
    Rule-21.20The pointer returned by the Standard Library functions asctime, ctime, gmtime, localtime, localeconv, getenv, setlocale, or strerror shall not be used following a subsequent call to the same functionMandatoryMandatory
    Rule-21.3The memory allocation and deallocation functions of shall not be usedRequiredRequired
    Rule-21.4The standard header file shall not be usedRequiredRequired
    Rule-21.5The standard header file shall not be usedRequiredRequired
    Rule-21.6The Standard Library input/output functions shall not be usedRequiredRequired
    Rule-21.7The atof, atoi, atol and atoll functions of shall not be usedRequiredRequired
    Rule-21.8The library functions abort, exit and system of shall not be usedRequiredRequired
    Rule-21.9The library functions bsearch and qsort of shall not be usedRequiredRequired
    Rule-22.1All resources obtained dynamically by means of Standard Library functions shall be explicitly releasedRequiredRequired
    Rule-22.10The value of errno shall only be tested when the last function to be called was an errno-setting-functionRequiredRequired
    Rule-22.2A block of memory shall only be freed if it was allocated by means of a Standard Library functionMandatoryMandatory
    Rule-22.3The same file shall not be open for read and write access at the same time on different streamsRequiredRequired
    Rule-22.4There shall be no attempt to write to a stream which has been opened as read-onlyMandatoryMandatory
    Rule-22.5A pointer to a FILE object shall not be dereferencedMandatoryMandatory
    Rule-22.6The value of a pointer to a FILE shall not be used after the associated stream has been closedMandatoryMandatory
    Rule-22.7The macro EOF shall on ly be compared with the unmodified return value from any Standard Library function capable of returning EOFRequiredRequired
    Rule-22.8The value of errno shall be set to zero prior to a call to an errno-setting-functionRequiredRequired
    Rule-22.9The value of errno shall be tested against zero after calling an errno-setting-functionRequiredRequired
    Rule-3.1The character sequences /* and // shall not be used within a comment.RequiredRequired
    Rule-3.2Line-splicing shall not be used in // comments.RequiredRequired
    Rule-4.1Octal and hexadecimal escape sequences shall be terminatedRequiredRequired
    Rule-4.2Trigraphs should not be usedAdvisoryAdvisory
    Rule-5.1External identifiers shall be distinctRequiredRequired
    Rule-5.2Identifiers declared in the same scope and name space shall be distinctRequiredRequired
    Rule-5.3An identifier declared in an inner scope shall not hide an identifier declared in an outer scopeRequiredRequired
    Rule-5.4Macro identifiers shall be distinctRequiredRequired
    Rule-5.5Identifiers shall be distinct from macro namesRequiredRequired
    Rule-5.6A typedef name shall be a unique identifierRequiredRequired
    Rule-5.7A tag name shall be a unique identifierRequiredRequired
    Rule-5.8Identifiers that define objects or functions with external linkage shall be uniqueRequiredRequired
    Rule-5.9Identifiers that define objects or functions with internal linkage should be uniqueAdvisoryAdvisory
    Rule-6.1Bit-fields shall only be declared with an appropriate typeRequiredRequired
    Rule-6.2Single-bit named bit fields shall not be of a signed typeRequiredRequired
    Rule-7.1Octal constants shall not be usedRequiredRequired
    Rule-7.2A "u" or "U" suffix shall be applied to all integer constants that are represented in an unsigned typeRequiredRequired
    Rule-7.3The lowercase character "l" shall not be used in a literal suffixRequiredRequired
    Rule-7.4A string literal shall not be assigned to an object unless the object's type is "pointer to const-qualified char"RequiredRequired
    Rule-8.1Types shall be explicitly specifiedRequiredRequired
    Rule-8.10An inline function shall be declared with the static storage classRequiredRequired
    Rule-8.11When an array with external linkage is declared, its size should be explicitly specifiedAdvisoryAdvisory
    Rule-8.12Within an enumerator list, the value of an implicitly-specified enumeration constant shall be uniqueRequiredRequired
    Rule-8.13A pointer should point to a const-qualified type whenever possibleAdvisoryAdvisory
    Rule-8.14The restrict type qualifier shall not be usedRequiredRequired
    Rule-8.2Function types shall be in prototype form with named parametersRequiredRequired
    Rule-8.3All declarations of an object or function shall use the same names and type qualifiersRequiredRequired
    Rule-8.4A compatible declaration shall be visible when an object or function with external linkage is definedRequiredRequired
    Rule-8.5An external object or function shall be declared once in one and only one fileRequiredRequired
    Rule-8.6An identifier with external linkage shall have exactly one external definitionRequiredRequired
    Rule-8.7Functions and objects should not be defined with external linkage if they are referenced in only one translation unitAdvisoryDisapplied
    Rule-8.8The static storage class specifier shall be used in all declarations of objects and functions that have internal linkageRequiredRequired
    Rule-8.9An object should be defined at block scope if its identifier only appears in a single functionAdvisoryAdvisory
    Rule-9.1The value of an object with automatic storage duration shall not be read before it has been setMandatoryMandatory
    Rule-9.2The initializer for an aggregate or union shall be enclosed in bracesRequiredRequired
    Rule-9.3Arrays shall not be partially initializedRequiredRequired
    Rule-9.4An element of an object shall not be initialized more than onceRequiredRequired
    Rule-9.5Where designated initializers are used to initialize an array object the size of the array shall be specified explicitlyRequiredRequired
    -
    -
    - -This section targets to provide an overview of Guidelines Compliance Summary. -
    -
    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    GuidelineCategoryDescriptionCompliance
    Dir-1.1RequiredAny implementation-defined behaviour on which the output of the program depends shall be documented and understoodCompliant

    with deviations:
    -
    - - - - - - - - - - - - - -
    QacDescription
    0292 [I] Source file '%s' has comments containing one of the characters '$', '@' or '`'.
    0315 [I] Implicit conversion from a pointer to object type to a pointer to void.
    0380 [L] Number of macro definitions exceeds 4095 - program does not conform strictly to ISO:C99.
    -
    Dir-2.1RequiredAll source files shall compile without any compilation errorsDisapplied
    Dir-3.1RequiredAll code shall be traceable to documented requirementsDisapplied
    Dir-4.1RequiredRun-time failures shall be minimizedCompliant
    Dir-4.10RequiredPrecautions shall be taken in order to prevent the contents of a header file being included more then onceCompliant
    Dir-4.11RequiredThe validity of values passed to library functions shall be checkedDisapplied
    Dir-4.12RequiredDynamic memory allocation shall not be usedDisapplied
    Dir-4.13AdvisoryFunctions which are designed to provide operations on a resource should be called in an appropriate sequenceDisapplied
    Dir-4.14RequiredThe validity of values received from external sources shall be checkedCompliant
    Dir-4.2AdvisoryAll usage of assembly language should be documentedCompliant
    Dir-4.3RequiredAssembly language shall be encapsulated and isolatedCompliant
    Dir-4.4AdvisorySections of code should not be "commented out"Disapplied
    Dir-4.5AdvisoryIdentifiers in the same name space with overlapping visibility should be typographically unambiguousDisapplied
    Dir-4.6Advisorytypedefs that indicate size and signedness should be used in place of the basic numerical typesCompliant
    Dir-4.7RequiredIf a function returns error information, then that error information shall be testedDisapplied
    Dir-4.8AdvisoryIf a pointer to a structure or union is never dereferenced within a translation unit, then the implementation of the object should be hiddenDisapplied
    Dir-4.9AdvisoryA function should be used in preference to a function-like macro where they are interchangeableDisapplied
    Rule-1.1RequiredThe program shall contain no violations of the standard C syntax and constraints, and shall not exceed the implementation's translation limitsCompliant
    Rule-1.2AdvisoryLanguage extensions should not be usedCompliant
    Rule-1.3RequiredThere shall be no occurrence of undefined or critical unspecified behaviourCompliant
    Rule-10.1RequiredOperands shall not be of an inappropriate essential type.Compliant
    Rule-10.2RequiredExpressions of essentially character type shall not be used inappropriately in addition and subtraction operationsCompliant
    Rule-10.3RequiredThe value of an expression shall not be assigned to an object with a narrower essential type or of a different essential type category.Compliant
    Rule-10.4RequiredBoth operands of an operator in which the usual arithmetic conversions are performed shall have the same essential type categoryCompliant
    Rule-10.5AdvisoryThe value of an expression should not be cast to an inappropriate essential typeCompliant
    Rule-10.6RequiredThe value of a composite expression shall not be assigned to an object with wider essential typeCompliant
    Rule-10.7RequiredIf a composite expression is used as one operand of an operator in which the usual arithmetic conversions are performed then the other operand shall not have wider essential typeCompliant
    Rule-10.8RequiredThe value of a composite expression shall not be cast to a different essential type category or a wider essential typeCompliant
    Rule-11.1RequiredConversions shall not be performed between a pointer to a function and any other typeCompliant
    Rule-11.2RequiredConversions shall not be performed between a pointer to an incomplete type and any other typeCompliant
    Rule-11.3RequiredA cast shall not be performed between a pointer to object type and a pointer to a different object typeCompliant
    Rule-11.4AdvisoryA conversion should not be performed between a pointer to object and an integer typeCompliant

    with deviations:
    -
    - - - - - -
    QacDescription
    0306 [I] Cast between a pointer to object and an integral type.
    -
    Rule-11.5AdvisoryA conversion should not be performed from pointer to void into pointer to objectCompliant
    Rule-11.6RequiredA cast shall not be performed between pointer to void and an arithmetic typeCompliant
    Rule-11.7RequiredA cast shall not be performed between pointer to object and a non-integer arithmetic typeCompliant
    Rule-11.8RequiredA cast shall not remove any const or volatile qualification from the type pointed to by a pointerCompliant
    Rule-11.9RequiredThe macro NULL shall be the only permitted form of integer null pointer constantDisapplied
    Rule-12.1AdvisoryThe precedence of operators within expressions should be made explicitCompliant
    Rule-12.2RequiredThe right hand operand of a shift operator shall lie in the range zero to one less than the width in bits of the essential type of the left hand operandCompliant
    Rule-12.3AdvisoryThe comma operator should not be usedCompliant
    Rule-12.4AdvisoryEvaluation of constant expressions should not lead to unsigned integer wrap-aroundCompliant
    Rule-12.5MandatoryThe sizeof operator shall not have an operand which is a function parameter declared as 'array of type'Compliant
    Rule-13.1RequiredInitializer lists shall not contain persistent side-effectsCompliant
    Rule-13.2RequiredThe value of an expression and its persistent side-effects shall be the same under all permitted evaluation ordersCompliant
    Rule-13.3AdvisoryA full expression containing an increment (++) or decrement (--) operator should have no other potential side effects other than that caused by the increment or decrement operatorDisapplied
    Rule-13.4AdvisoryThe result of an assignment operator should not be usedCompliant
    Rule-13.5RequiredThe right hand operand of a logical && or || operator shall not contain persistent side effectsCompliant
    Rule-13.6MandatoryThe operand of the sizeof operator shall not contain any expression which has potential side-effectsCompliant
    Rule-14.1RequiredA loop counter shall not have essentially floating typeCompliant
    Rule-14.2RequiredA for loop shall be well-formedCompliant
    Rule-14.3RequiredControlling expressions shall not be invariantCompliant

    with deviations:
    -
    - - - - - - - - - - - - - - - - - - - - - -
    QacDescription
    2991 The value of this 'if' controlling expression is always 'true'.
    2992 The value of this 'if' controlling expression is always 'false'.
    2998 The first operand of this conditional operator is always 'false'.
    3493 The first operand of this conditional operator is always constant 'true'.
    3494 The first operand of this conditional operator is always constant 'false'.
    -
    Rule-14.4RequiredThe controlling expression of an if-statement and the controlling expression of an iteration-statement shall have essentially Boolean typeCompliant
    Rule-15.1AdvisoryThe goto statement should not be usedCompliant
    Rule-15.2RequiredThe goto statement shall jump to a label declared later in the same functionCompliant
    Rule-15.3RequiredAny label referenced by a goto statement shall be declared in the same block, or in any block enclosing the goto statementCompliant
    Rule-15.4AdvisoryThere should be no more than one break or goto statement used to terminate any iteration statementCompliant
    Rule-15.5AdvisoryA function should have a single point of exit at the endDisapplied
    Rule-15.6RequiredThe body of an iteration-statement or a selection-statement shall be a compound-statementCompliant
    Rule-15.7RequiredAll if ... else if constructs shall be terminated with an else statementCompliant
    Rule-16.1RequiredAll switch statements shall be well-formedCompliant
    Rule-16.2RequiredA switch label shall only be used when the most closely-enclosing compound statement is the body of a switch statementCompliant
    Rule-16.3RequiredAn unconditional break statement shall terminate every switch-clauseCompliant
    Rule-16.4RequiredEvery switch statement shall have a default labelCompliant
    Rule-16.5RequiredA default label shall appear as either the first or the last switch label of a switch statementCompliant
    Rule-16.6RequiredEvery switch statement shall have at least two switch-clausesCompliant
    Rule-16.7RequiredA switch-expression shall not have essentially Boolean typeCompliant
    Rule-17.1RequiredThe features of shall not be usedCompliant
    Rule-17.2RequiredFunctions shall not call themselves, either directly or indirectlyCompliant
    Rule-17.3MandatoryA function shall not be declared implicitlyCompliant
    Rule-17.4MandatoryAll exit paths from a function with non-void return type shall have an explicit return statement with an expressionCompliant
    Rule-17.5AdvisoryThe function argument corresponding to a parameter declared to have an array type shall have an appropriate number of elementsCompliant
    Rule-17.6MandatoryThe declaration of an array parameter shall not contain the static keyword between the [ ]Compliant
    Rule-17.7RequiredThe value returned by a function having non-void return type shall be usedDisapplied
    Rule-17.8AdvisoryA function parameter should not be modifiedCompliant
    Rule-18.1RequiredA pointer resulting from arithmetic on a pointer operand shall address an element of the same array as that pointer operandCompliant
    Rule-18.2RequiredSubtraction between pointers shall only be applied to pointers that address elements of the same arrayCompliant
    Rule-18.3RequiredThe relational operators >, >=, < and <= shall not be applied to objects of pointer type except where they point into the same objectCompliant
    Rule-18.4AdvisoryThe +, -, += and -= operators should not be applied to an expression of pointer typeCompliant
    Rule-18.5AdvisoryDeclarations should contain no more than two levels of pointer nestingCompliant
    Rule-18.6RequiredThe address of an object with automatic storage shall not be copied to another object that persists after the first object has ceased to existCompliant
    Rule-18.7RequiredFlexible array members shall not be declaredCompliant
    Rule-18.8RequiredVariable-length array types shall not be usedCompliant
    Rule-19.1MandatoryAn object shall not be assigned or copied to an overlapping objectCompliant
    Rule-19.2AdvisoryThe union keyword should not be usedCompliant
    Rule-2.1RequiredA project shall not contain unreachable codeCompliant

    with deviations:
    -
    - - - - - -
    QacDescription
    1503 The function '%1s' is defined but is not used within this project.
    -
    Rule-2.2RequiredThere shall be no dead codeCompliant

    with deviations:
    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    QacDescription
    2982 This assignment is redundant. The value of this object is never used before being modified.
    2983 This assignment is redundant. The value of this object is never subsequently used.
    2985 This operation is redundant. The value of the result is always that of the left-hand operand.
    2986 This operation is redundant. The value of the result is always that of the right-hand operand.
    2995 The result of this logical operation is always 'true'.
    2996 The result of this logical operation is always 'false'.
    3112 This statement has no side-effect - it can be removed.
    -
    Rule-2.3AdvisoryA project should not contain unused type declarationsDisapplied
    Rule-2.4AdvisoryA project should not contain unused tag declarationsCompliant
    Rule-2.5AdvisoryA project should not contain unused macro declarationsDisapplied
    Rule-2.6AdvisoryA function should not contain unused label declarationsCompliant
    Rule-2.7AdvisoryThere should be no unused parameters in functionsCompliant
    Rule-20.1Advisory#include directives should only be preceded by preprocessor directives or commentsCompliant
    Rule-20.10AdvisoryThe # and ## preprocessor operators should not be usedCompliant
    Rule-20.11RequiredA macro parameter immediately following a # operator shall not immediately be followed by a ## operatorCompliant
    Rule-20.12RequiredA macro parameter used as an operand to the # or ## operators, which is itself subject to further macro replacement, shall only be used as an operand to these operatorsCompliant
    Rule-20.13RequiredA line whose first token is # shall be a valid preprocessing directiveCompliant
    Rule-20.14RequiredAll #else, #elif and #endif preprocessor directives shall reside in the same file as the #if, #ifdef or #ifndef directive to which they are relatedCompliant
    Rule-20.2RequiredThe ', " or \ characters and the /* or // character sequences shall not occur in a header file nameCompliant
    Rule-20.3RequiredThe #include directive shall be followed by either a or "filename" sequenceCompliant
    Rule-20.4RequiredA macro shall not be defined with the same name as a keywordCompliant
    Rule-20.5Advisory#undef should not be usedCompliant
    Rule-20.6RequiredTokens that look like a preprocessing directive shall not occur within a macro argumentCompliant
    Rule-20.7RequiredExpressions resulting from the expansion of macro parameters shall be enclosed in parenthesesCompliant
    Rule-20.8RequiredThe controlling expression of a #if or #elif preprocessing directive shall evaluate to 0 or 1Compliant
    Rule-20.9RequiredAll identifiers used in the controlling expression of #if or #elif preprocessing directives shall be #define'd before evaluationCompliant
    Rule-21.1Required#define and #undef shall not be used on a reserved identifier or reserved macro nameCompliant
    Rule-21.10RequiredThe Standard Library time and date functions shall not be usedCompliant
    Rule-21.11RequiredThe standard header file shall not be usedCompliant
    Rule-21.12AdvisoryThe exception handling features of should not be usedCompliant
    Rule-21.13MandatoryAny value passed to a function in shall be representable as an unsigned char or be the value EOFCompliant
    Rule-21.14RequiredThe Standard Library function memcmp shall not be used to compare null terminated stringsCompliant
    Rule-21.15RequiredThe pointer arguments to the Standard Library functions memcpy, memmove and memcmp shall be pointers to qualified or unqualified versions of compatible typesCompliant
    Rule-21.16RequiredThe pointer arguments to the Standard Library function memcpy shall point to either a pointer type, an essentially signed type, an essentially unsigned type, an essentially Boolean type or an essentially enum typeCompliant
    Rule-21.17MandatoryUse of the string handling functions from shall not result in accesses beyond the bounds of the objects referenced by their pointer parametersCompliant
    Rule-21.18MandatoryThe size_t argument passed to any function in shall have an appropriate valueCompliant
    Rule-21.19MandatoryThe pointers returned by the Standard Library functions lovaleconv, getenv, setlocale or strerror shall only be used as if they have pointer to const-qualified typeCompliant
    Rule-21.2RequiredA reserved identifier or macro name shall not be declaredCompliant
    Rule-21.20MandatoryThe pointer returned by the Standard Library functions asctime, ctime, gmtime, localtime, localeconv, getenv, setlocale, or strerror shall not be used following a subsequent call to the same functionCompliant
    Rule-21.3RequiredThe memory allocation and deallocation functions of shall not be usedCompliant
    Rule-21.4RequiredThe standard header file shall not be usedCompliant
    Rule-21.5RequiredThe standard header file shall not be usedCompliant
    Rule-21.6RequiredThe Standard Library input/output functions shall not be usedCompliant
    Rule-21.7RequiredThe atof, atoi, atol and atoll functions of shall not be usedCompliant
    Rule-21.8RequiredThe library functions abort, exit and system of shall not be usedCompliant

    with deviations:
    -
    - - - - - -
    QacDescription
    5128 Use of function: getenv.
    -
    Rule-21.9RequiredThe library functions bsearch and qsort of shall not be usedCompliant
    Rule-22.1RequiredAll resources obtained dynamically by means of Standard Library functions shall be explicitly releasedCompliant
    Rule-22.10RequiredThe value of errno shall only be tested when the last function to be called was an errno-setting-functionCompliant
    Rule-22.2MandatoryA block of memory shall only be freed if it was allocated by means of a Standard Library functionCompliant
    Rule-22.3RequiredThe same file shall not be open for read and write access at the same time on different streamsCompliant
    Rule-22.4MandatoryThere shall be no attempt to write to a stream which has been opened as read-onlyCompliant
    Rule-22.5MandatoryA pointer to a FILE object shall not be dereferencedCompliant
    Rule-22.6MandatoryThe value of a pointer to a FILE shall not be used after the associated stream has been closedCompliant
    Rule-22.7RequiredThe macro EOF shall on ly be compared with the unmodified return value from any Standard Library function capable of returning EOFCompliant
    Rule-22.8RequiredThe value of errno shall be set to zero prior to a call to an errno-setting-functionCompliant
    Rule-22.9RequiredThe value of errno shall be tested against zero after calling an errno-setting-functionCompliant
    Rule-3.1RequiredThe character sequences /* and // shall not be used within a comment.Compliant
    Rule-3.2RequiredLine-splicing shall not be used in // comments.Compliant
    Rule-4.1RequiredOctal and hexadecimal escape sequences shall be terminatedCompliant
    Rule-4.2AdvisoryTrigraphs should not be usedCompliant
    Rule-5.1RequiredExternal identifiers shall be distinctCompliant
    Rule-5.2RequiredIdentifiers declared in the same scope and name space shall be distinctCompliant
    Rule-5.3RequiredAn identifier declared in an inner scope shall not hide an identifier declared in an outer scopeCompliant
    Rule-5.4RequiredMacro identifiers shall be distinctCompliant
    Rule-5.5RequiredIdentifiers shall be distinct from macro namesCompliant
    Rule-5.6RequiredA typedef name shall be a unique identifierCompliant
    Rule-5.7RequiredA tag name shall be a unique identifierCompliant
    Rule-5.8RequiredIdentifiers that define objects or functions with external linkage shall be uniqueCompliant
    Rule-5.9AdvisoryIdentifiers that define objects or functions with internal linkage should be uniqueCompliant
    Rule-6.1RequiredBit-fields shall only be declared with an appropriate typeCompliant
    Rule-6.2RequiredSingle-bit named bit fields shall not be of a signed typeCompliant
    Rule-7.1RequiredOctal constants shall not be usedCompliant
    Rule-7.2RequiredA "u" or "U" suffix shall be applied to all integer constants that are represented in an unsigned typeCompliant
    Rule-7.3RequiredThe lowercase character "l" shall not be used in a literal suffixCompliant
    Rule-7.4RequiredA string literal shall not be assigned to an object unless the object's type is "pointer to const-qualified char"Compliant
    Rule-8.1RequiredTypes shall be explicitly specifiedCompliant
    Rule-8.10RequiredAn inline function shall be declared with the static storage classCompliant
    Rule-8.11AdvisoryWhen an array with external linkage is declared, its size should be explicitly specifiedCompliant
    Rule-8.12RequiredWithin an enumerator list, the value of an implicitly-specified enumeration constant shall be uniqueCompliant
    Rule-8.13AdvisoryA pointer should point to a const-qualified type whenever possibleCompliant
    Rule-8.14RequiredThe restrict type qualifier shall not be usedCompliant
    Rule-8.2RequiredFunction types shall be in prototype form with named parametersCompliant
    Rule-8.3RequiredAll declarations of an object or function shall use the same names and type qualifiersCompliant
    Rule-8.4RequiredA compatible declaration shall be visible when an object or function with external linkage is definedCompliant
    Rule-8.5RequiredAn external object or function shall be declared once in one and only one fileCompliant
    Rule-8.6RequiredAn identifier with external linkage shall have exactly one external definitionCompliant
    Rule-8.7AdvisoryFunctions and objects should not be defined with external linkage if they are referenced in only one translation unitDisapplied
    Rule-8.8RequiredThe static storage class specifier shall be used in all declarations of objects and functions that have internal linkageCompliant
    Rule-8.9AdvisoryAn object should be defined at block scope if its identifier only appears in a single functionCompliant
    Rule-9.1MandatoryThe value of an object with automatic storage duration shall not be read before it has been setCompliant
    Rule-9.2RequiredThe initializer for an aggregate or union shall be enclosed in bracesCompliant
    Rule-9.3RequiredArrays shall not be partially initializedCompliant
    Rule-9.4RequiredAn element of an object shall not be initialized more than onceCompliant
    Rule-9.5RequiredWhere designated initializers are used to initialize an array object the size of the array shall be specified explicitlyCompliant
    -
    -
    - -This section targets to provide an overview of Deviation Permits.
    -All the rules corresponding to the deviation permits are disabled inside PRQA and will not cause any violation or deviation in the Deviation records section below. -
    -
    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    GuidelineCategoryDescriptionRatioSub RulesCharacteristicsReason
    Dir-1.1RequiredAny implementation-defined behaviour on which the output of the program depends shall be documented and understood3/34
    - - - - - - - - - - - - - -
    QacDescription
    0292 [I] Source file '%s' has comments containing one of the characters '$', '@' or '`'.
    0315 [I] Implicit conversion from a pointer to object type to a pointer to void.
    0380 [L] Number of macro definitions exceeds 4095 - program does not conform strictly to ISO:C99.
    -
    Maintainability / Analysability0292: Invalid characters in comments: Doxygen comments are used.
    -0315: Library string.h functions (memcpy, etc.) are used and trigger this implicit conversion.
    -0380: Already CMSIS and STM32HAL trigger this.
    -
    Dir-4.9AdvisoryA function should be used in preference to a function-like macro where they are interchangeable1/1
    - - - - - -
    QacDescription
    3453 A function could probably be used instead of this function-like macro.
    -
    Performance / Resource utilizationSuppressed due to code optimization and efficiency.
    Rule-11.4AdvisoryA conversion should not be performed between a pointer to object and an integer type1/5
    - - - - - -
    QacDescription
    0306 [I] Cast between a pointer to object and an integral type.
    -
    Maintainability / ModifiabilityUsing STM32 HAL already creates many violations. Also needed to do pointer arithmetic, calculating offsets inside a buffer.
    Rule-11.9RequiredThe macro NULL shall be the only permitted form of integer null pointer constant1/2
    - - - - - -
    QacDescription
    3004 This integral constant expression is being interpreted as a NULL pointer constant.
    -
    Keil stddef.h: "define NULL 0" causes violations. PRQA acknowledged this as a false positive.
    Rule-13.3AdvisoryA full expression containing an increment (++) or decrement (--) operator should have no other potential side effects other than that caused by the increment or decrement operator1/1
    - - - - - -
    QacDescription
    3440 Using the value resulting from a ++ or -- operation.
    -
    Maintainability / AnalysabilityRFAL uses the increment often for building buffers (array[i++] = 42; ...). Splitting this would decrease readability.
    Rule-14.3RequiredControlling expressions shall not be invariant6/11
    - - - - - - - - - - - - - - - - - - - - - - - - - -
    QacDescription
    3440 Using the value resulting from a ++ or -- operation.
    2991 The value of this 'if' controlling expression is always 'true'.
    2992 The value of this 'if' controlling expression is always 'false'.
    2998 The first operand of this conditional operator is always 'false'.
    3493 The first operand of this conditional operator is always constant 'true'.
    3494 The first operand of this conditional operator is always constant 'false'.
    -
    Portability / AdaptabilityRFAL is configurable through compile time switches. This causes some ifs to have invariant conditions at the used configuration. Suppress 14.3 for if statements.
    Rule-15.5AdvisoryA function should have a single point of exit at the end1/1
    - - - - - -
    QacDescription
    2889 This function has more than one 'return' path.
    -
    Maintainability / AnalysabilitySuppressed due to readability and simplicity of code logic.
    Rule-17.7RequiredThe value returned by a function having non-void return type shall be used1/1
    - - - - - -
    QacDescription
    3200 '%s' returns a value which is not being used.
    -
    Maintainability / AnalysabilityTreating the return codes of functions in all places without exception handling would makes the code hard to read and maintain. Error checking has been reduced to the places where needed.
    Rule-2.1RequiredA project shall not contain unreachable code1/7
    - - - - - -
    QacDescription
    1503 The function '%1s' is defined but is not used within this project.
    -
    Maintainability / ModularityRFAL provides many functions - some are not used within the checked project.
    Rule-2.2RequiredThere shall be no dead code7/18
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    QacDescription
    2982 This assignment is redundant. The value of this object is never used before being modified.
    2983 This assignment is redundant. The value of this object is never subsequently used.
    2985 This operation is redundant. The value of the result is always that of the left-hand operand.
    2986 This operation is redundant. The value of the result is always that of the right-hand operand.
    2996 The result of this logical operation is always 'false'.
    2997 The first operand of this conditional operator is always 'true'.
    3112 This statement has no side-effect - it can be removed.
    -
    Usability / User error protectionAll the violations were checked and fixing the violation would deteriorate robustness: Removing checks which are unnecessary at the given position, removing trailing iterator increment, etc.
    Rule-2.3AdvisoryA project should not contain unused type declarations1/1
    - - - - - -
    QacDescription
    3205 The identifier '%s' is not used and could be removed.
    -
    Compatibility / InteroperabilityRFAL defines enums for all identifiers available in NFC Forum - some are unused.
    Rule-2.5AdvisoryA project should not contain unused macro declarations1/1
    - - - - - -
    QacDescription
    3214 The macro '%s' is not used and could be removed.
    -
    Compatibility / InteroperabilityRFAL defines macros for all identifiers of NFC Forum and RF chip register map - some are not used.
    Rule-8.7AdvisoryFunctions and objects should not be defined with external linkage if they are referenced in only one translation unit4/4
    - - - - - - - - - - - - - - - - - -
    QacDescription
    1504 The object '%1s' is only referenced in the translation unit where it is defined.
    1505 The function '%1s' is only referenced in the translation unit where it is defined.
    1531 The object '%1s' is referenced in only one translation unit - but not the one in which it is defined.
    1532 The function '%1s' is only referenced in one translation unit - but not the one in which it is defined.
    -
    Maintainability / ModularityRFAL defines functions which could be called by the user but are not called in the current project.
    -
    -
    - -This section targets to provide an overview of Deviation Records. -
    -
    -
    - -

    File: .../ST25R3916_nucleo/rfal/source/rfal_isoDep.c

    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    LinesCountSuppressed QacsComment
    2266-22671
    - - - - -
    0310 Casting to different object pointer type.
    -
    MISRA 11.3 - Intentional safe cast to avoiding buffer duplication
    421-4211
    - - - - -
    0750 A union type specifier has been defined.
    -
    MISRA 19.2 - Members of the union will not be used concurrently, only one frame at a time
    797-7971
    - - - - -
    2003 The preceding 'switch' clause is not empty and does not end with a 'jump' statement. Execution will fall through.
    -
    MISRA 16.3 - Intentional fall through
    2519-25191
    - - - - -
    4342 An expression of 'essentially unsigned' type (%1s) is being cast to enum type '%2s'.
    -
    MISRA 10.5 - Layout of enum rfalBitRate and above clamping of maxTxBR guarantee no invalid enum values to be created
    2693-26931
    - - - - -
    0310 Casting to different object pointer type.
    -
    MISRA 11.3 - Intentional safe cast to avoiding large buffer duplication
    1351-13511
    - - - - -
    2003 The preceding 'switch' clause is not empty and does not end with a 'jump' statement. Execution will fall through.
    -
    MISRA 16.3 - Intentional fall through
    1028-10281
    - - - - -
    2003 The preceding 'switch' clause is not empty and does not end with a 'jump' statement. Execution will fall through.
    -
    MISRA 16.3 - Intentional fall through
    2756-27561
    - - - - -
    2003 The preceding 'switch' clause is not empty and does not end with a 'jump' statement. Execution will fall through.
    -
    MISRA 16.3 - Intentional fall through
    2615-26151
    - - - - -
    4342 An expression of 'essentially unsigned' type (%1s) is being cast to enum type '%2s'.
    -
    MISRA 10.5 - Layout of enum rfalBitRate and range of loop variable guarantee no invalid enum values to be created
    2602-26021
    - - - - -
    4342 An expression of 'essentially unsigned' type (%1s) is being cast to enum type '%2s'.
    -
    MISRA 10.5 - Layout of enum rfalBitRate and range of loop variable guarantee no invalid enum values to be created
    2175-21761
    - - - - -
    4342 An expression of 'essentially unsigned' type (%1s) is being cast to enum type '%2s'.
    -
    MISRA 10.5 - Layout of enum rfalIsoDepFSxI is guaranteed within 4bit range
    2526-25261
    - - - - -
    4342 An expression of 'essentially unsigned' type (%1s) is being cast to enum type '%2s'.
    -
    MISRA 10.5 - Layout of enum rfalBitRate and above clamping of maxTxBR guarantee no invalid enum values to be created
    1391-13932
    - - - - -
    4342 An expression of 'essentially unsigned' type (%1s) is being cast to enum type '%2s'.
    -
    MISRA 10.5 - Layout of enum rfalBitRate and above masks guarantee no invalid enum values to be created
    -

    File: .../ST25R3916_nucleo/rfal/source/rfal_nfc.c

    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    LinesCountSuppressed QacsComment
    1612-16121
    - - - - -
    0310 Casting to different object pointer type.
    -
    MISRA 11.3 - Intentional safe cast to avoiding large buffer duplication
    81-811
    - - - - -
    0750 A union type specifier has been defined.
    -
    MISRA 19.2 - Members of the union will not be used concurrently, only one interface at a time
    190-1901
    - - - - -
    2880 This code is unreachable.
    -
    MISRA 2.1 - Unreachable code due to configuration option being set/unset
    1828-18281
    - - - - -
    0310 Casting to different object pointer type.
    -
    MISRA 11.3 - Intentional safe cast to avoiding large buffer duplication
    -

    File: .../ST25R3916_nucleo/rfal/source/rfal_nfcDep.c

    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    LinesCountSuppressed QacsComment
    1901-19032
    - - - - -
    4342 An expression of 'essentially unsigned' type (%1s) is being cast to enum type '%2s'.
    -
    MISRA 10.5 - Layout of enum rfalBitRate and definition of rfalNfcDepBRS2DSI guarantee no invalid enum values to be created
    2595-25951
    - - - - -
    0310 Casting to different object pointer type.
    -
    MISRA 11.3 - Intentional safe cast to avoiding large buffer duplication
    1589-15891
    - - - - -
    2003 The preceding 'switch' clause is not empty and does not end with a 'jump' statement. Execution will fall through.
    -
    MISRA 16.3 - Intentional fall through
    902-9021
    - - - - -
    2880 This code is unreachable.
    -
    MISRA 2.1 - Guard code to prevent unexpected behavior
    1661-16611
    - - - - -
    2003 The preceding 'switch' clause is not empty and does not end with a 'jump' statement. Execution will fall through.
    -
    MISRA 16.3 - Intentional fall through
    2654-26541
    - - - - -
    2003 The preceding 'switch' clause is not empty and does not end with a 'jump' statement. Execution will fall through.
    -
    MISRA 16.3 - Intentional fall through
    1269-12691
    - - - - -
    2880 This code is unreachable.
    -
    MISRA 2.1 - Guard code to prevent unexpected behavior
    -

    File: .../ST25R3916_nucleo/rfal/source/rfal_nfca.c

    -
    - - - - - - - - - - - - - - - -
    LinesCountSuppressed QacsComment
    278-2781
    - - - - -
    2003 The preceding 'switch' clause is not empty and does not end with a 'jump' statement. Execution will fall through.
    -
    MISRA 16.3 - Intentional fall through
    637-6381
    - - - - -
    4342 An expression of 'essentially unsigned' type (%1s) is being cast to enum type '%2s'.
    -
    MISRA 10.5 - Guaranteed that no invalid enum values are created: see guard_eq_RFAL_NFCA_T2T, ....
    -

    File: .../ST25R3916_nucleo/rfal/source/rfal_nfcb.c

    -
    - - - - - - - - - -
    LinesCountSuppressed QacsComment
    391-3921
    - - - - -
    4342 An expression of 'essentially unsigned' type (%1s) is being cast to enum type '%2s'.
    -
    MISRA 10.5 - Layout of rfalNfcbSlots and above loop guarantee that no invalid enum values are created.
    -

    File: .../ST25R3916_nucleo/rfal/source/st25r3916/rfal_rfst25r3916.c

    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    LinesCountSuppressed QacsComment
    3344-33441
    - - - - -
    2003 The preceding 'switch' clause is not empty and does not end with a 'jump' statement. Execution will fall through.
    -
    MISRA 16.3 - Intentional fall through
    3108-31081
    - - - - -
    0759 An object of union type has been defined.
    -
    MISRA 19.2 - Allocating Union where members are of the same type, just different names. Thus no problem can occur.
    227-2271
    - - - - -
    0750 A union type specifier has been defined.
    -
    MISRA 19.2 - Both members are of the same type, just different names. Thus no problem can occur.
    2046-20461
    - - - - -
    2003 The preceding 'switch' clause is not empty and does not end with a 'jump' statement. Execution will fall through.
    -
    MISRA 16.3 - Intentional fall through
    3364-33641
    - - - - -
    4342 An expression of 'essentially unsigned' type (%1s) is being cast to enum type '%2s'.
    -
    MISRA 10.5 - Guaranteed that no invalid enum values may be created. See also equalityGuard_RFAL_BR_106 ff.
    2179-21791
    - - - - -
    2003 The preceding 'switch' clause is not empty and does not end with a 'jump' statement. Execution will fall through.
    -
    MISRA 16.3 - Intentional fall through
    1867-18671
    - - - - -
    2003 The preceding 'switch' clause is not empty and does not end with a 'jump' statement. Execution will fall through.
    -
    MISRA 16.3 - Intentional fall through
    1851-18511
    - - - - -
    2003 The preceding 'switch' clause is not empty and does not end with a 'jump' statement. Execution will fall through.
    -
    MISRA 16.3 - Intentional fall through
    2447-24471
    - - - - -
    2003 The preceding 'switch' clause is not empty and does not end with a 'jump' statement. Execution will fall through.
    -
    MISRA 16.3 - Intentional fall through
    1972-19721
    - - - - -
    2003 The preceding 'switch' clause is not empty and does not end with a 'jump' statement. Execution will fall through.
    -
    MISRA 16.3 - Intentional fall through
    1837-18371
    - - - - -
    2003 The preceding 'switch' clause is not empty and does not end with a 'jump' statement. Execution will fall through.
    -
    MISRA 16.3 - Intentional fall through
    2341-23411
    - - - - -
    2003 The preceding 'switch' clause is not empty and does not end with a 'jump' statement. Execution will fall through.
    -
    MISRA 16.3 - Intentional fall through
    2254-22541
    - - - - -
    2003 The preceding 'switch' clause is not empty and does not end with a 'jump' statement. Execution will fall through.
    -
    MISRA 16.3 - Intentional fall through
    3563-35631
    - - - - -
    4342 An expression of 'essentially unsigned' type (%1s) is being cast to enum type '%2s'.
    -
    MISRA 10.5 - Guaranteed that no invalid enum values may be created. See also equalityGuard_RFAL_BR_106 ff.
    1494-14941
    - - - - -
    5209 Use of basic type '%s'.
    -
    MISRA 4.9 - External function (sqrt()) requires double
    -
    -
    - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    FileRequiredAdvisoryTotal
    .../ST25R3916_nucleo/rfal/include/rfal_nfcv.h011
    .../ST25R3916_nucleo/rfal/include/rfal_nfcDep.h011
    .../ST25R3916_nucleo/rfal/include/rfal_isoDep.h011
    .../ST25R3916_nucleo/rfal/include/rfal_nfc.h033
    .../ST25R3916_nucleo/rfal/include/rfal_analogConfig.h101
    .../ST25R3916_nucleo/rfal/source/rfal_nfca.c112
    .../ST25R3916_nucleo/rfal/source/rfal_nfc.c314
    .../ST25R3916_nucleo/rfal/source/rfal_nfcDep.c628
    .../ST25R3916_nucleo/rfal/source/rfal_isoDep.c6814
    .../ST25R3916_nucleo/rfal/source/st25r3916/rfal_rfst25r3916.c10515
    .../ST25R3916_nucleo/rfal/source/st25r3916/rfal_analogConfigTbl.h112
    .../ST25R3916_nucleo/rfal/source/rfal_nfcb.c011
    Total282553
    -
    -
    - - -There are no duplicated suppressions. - -

    File: .../ST25R3916_nucleo/rfal/source/rfal_isoDep.c

    -
    - - - - - - - -
    LineUnused QacsComment
    1414
    - - - - -
    2880 This code is unreachable.
    -
    MISRA 2.1 - Unreachable code due to configuration option being set/unset above (RFAL_SUPPORT_BR_CE_A_xxx)
    -
    -
    - -There are no continuous suppressions by file. -
    -
    - -Active Diagnostics refers to diagnostics that are not suppressed (note: no suppressed diagnostics have been taken into account for the calculation of information in this document). -
    -
    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    FilesActive DiagnosticsViolated RulesViolation CountCompliance Index
    .../ST25R3916_nucleo/rfal/include/rfal_analogConfig.h000100.00
    .../ST25R3916_nucleo/rfal/include/rfal_chip.h000100.00
    .../ST25R3916_nucleo/rfal/include/rfal_isoDep.h000100.00
    .../ST25R3916_nucleo/rfal/include/rfal_nfc.h000100.00
    .../ST25R3916_nucleo/rfal/include/rfal_nfcDep.h000100.00
    .../ST25R3916_nucleo/rfal/include/rfal_nfca.h000100.00
    .../ST25R3916_nucleo/rfal/include/rfal_nfcb.h000100.00
    .../ST25R3916_nucleo/rfal/include/rfal_nfcf.h000100.00
    .../ST25R3916_nucleo/rfal/include/rfal_nfcv.h000100.00
    .../ST25R3916_nucleo/rfal/include/rfal_rf.h000100.00
    .../ST25R3916_nucleo/rfal/include/rfal_st25tb.h000100.00
    .../ST25R3916_nucleo/rfal/include/rfal_t1t.h000100.00
    .../ST25R3916_nucleo/rfal/source/rfal_analogConfig.c000100.00
    .../ST25R3916_nucleo/rfal/source/rfal_crc.c000100.00
    .../ST25R3916_nucleo/rfal/source/rfal_crc.h000100.00
    .../ST25R3916_nucleo/rfal/source/rfal_iso15693_2.c000100.00
    .../ST25R3916_nucleo/rfal/source/rfal_iso15693_2.h000100.00
    .../ST25R3916_nucleo/rfal/source/rfal_isoDep.c000100.00
    .../ST25R3916_nucleo/rfal/source/rfal_nfc.c000100.00
    .../ST25R3916_nucleo/rfal/source/rfal_nfcDep.c000100.00
    .../ST25R3916_nucleo/rfal/source/rfal_nfca.c000100.00
    .../ST25R3916_nucleo/rfal/source/rfal_nfcb.c000100.00
    .../ST25R3916_nucleo/rfal/source/rfal_nfcf.c000100.00
    .../ST25R3916_nucleo/rfal/source/rfal_nfcv.c000100.00
    .../ST25R3916_nucleo/rfal/source/rfal_st25tb.c000100.00
    .../ST25R3916_nucleo/rfal/source/rfal_t1t.c000100.00
    .../ST25R3916_nucleo/rfal/source/st25r3916/rfal_analogConfigTbl.h000100.00
    .../ST25R3916_nucleo/rfal/source/st25r3916/rfal_features.h000100.00
    .../ST25R3916_nucleo/rfal/source/st25r3916/rfal_rfst25r3916.c000100.00
    .../ST25R3916_nucleo/rfal/source/st25r3916/st25R3916_irq.h000100.00
    .../ST25R3916_nucleo/rfal/source/st25r3916/st25r3916.c000100.00
    .../ST25R3916_nucleo/rfal/source/st25r3916/st25r3916.h000100.00
    .../ST25R3916_nucleo/rfal/source/st25r3916/st25r3916_com.c000100.00
    .../ST25R3916_nucleo/rfal/source/st25r3916/st25r3916_com.h000100.00
    .../ST25R3916_nucleo/rfal/source/st25r3916/st25r3916_irq.c000100.00
    .../ST25R3916_nucleo/rfal/source/st25r3916/st25r3916_irq.h000100.00
    .../ST25R3916_nucleo/rfal/source/st25r3916/st25r3916_led.c000100.00
    .../ST25R3916_nucleo/rfal/source/st25r3916/st25r3916_led.h000100.00
    Total000100.00
    - -

    -Nota: Calculation of Compliance Index
    -The Compliance Index is the percentage of groups which have no messages in them.
    -For each file it is calculated as follows:
    -
    -( Ntotal - Nerror ) / Ntotal x 100
    -
    -Ntotal is the total number of enforced rules (i.e. the number of rules that have at least one message mapped to it directly).
    -Nerror is the number of rules for which messages appear in that file.
    -The File Compliance Index is the mean of all the individual file compliances.
    - -
    -
    -
    -
    - - diff --git a/lib/ST25RFAL002/doc/_htmresc/st_logo.png b/lib/ST25RFAL002/doc/_htmresc/st_logo.png deleted file mode 100755 index 8b80057fd3a454a97de1c9d732b7fede82c83227..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18616 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 diff --git a/lib/ST25RFAL002/doc/rfal.chm b/lib/ST25RFAL002/doc/rfal.chm deleted file mode 100755 index d417709895d27efd4723173d57f95e14a3f95515..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2338282 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 )c{V2 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

    $@|JBfyf!N5O7G!n#CcIkcJCEe>4&X6JAVon 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%0D>&`nuYe|j47 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 z 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 z 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

    e3U$dYktF>8Igr2cv8 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%0Dy*&>v9qsQ!tC_8yH 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

    {zMy}s|dzcBgU*!rCa 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ħ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 ~jb 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≪+QgTSe}vl818dk88#V@UDWbyFqrSC=Xk=9S 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 +7E7kmkbjjt+k(z9FQ#%T`*U*OW< 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

    1>F2r7Gg(xj$iqJ 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 z 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

    viDw|8 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

    - * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R391x firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file rfal_rf.h - * - * \author Gustavo Patricio - * - * \brief RF Abstraction Layer (RFAL) - * - * RFAL (RF Abstraction Layer) provides several functionalities required to - * perform RF/NFC communications.
    The RFAL encapsulates the different - * RF ICs (ST25R3911, ST25R391x, etc) into a common and easy to use interface. - * - * It provides interfaces to configure the RF IC, set/get timings, modes, bit rates, - * specific handlings, execute listen mode, etc. - * - * Furthermore it provides a common interface to perform a Transceive operations. - * The Transceive can be executed in a blocking or non blocking way.
    - * Additionally few specific Transceive methods are available to cope with the - * specifics of these particular operations. - * - * The most common interfaces are: - *
      rfalInitialize() - *
      rfalSetFDTPoll() - *
      rfalSetFDTListen() - *
      rfalSetGT() - *
      rfalSetBitRate() - *
      rfalSetMode() - *
      rfalFieldOnAndStartGT() - *
      rfalFieldOff() - *
      rfalStartTransceive() - *
      rfalGetTransceiveStatus() - *
      rfalTransceiveBlockingTxRx() - * - * An usage example is provided here: \ref exampleRfalPoller.c - * \example exampleRfalPoller.c - * - * \addtogroup RFAL - * @{ - * - * \addtogroup RFAL-HAL - * \brief RFAL Hardware Abstraction Layer - * @{ - * - * \addtogroup RF - * \brief RFAL RF Abstraction Layer - * @{ - * - */ - -#ifndef RFAL_RF_H -#define RFAL_RF_H - -/* -****************************************************************************** -* INCLUDES -****************************************************************************** -*/ -#include "platform.h" -#include "st_errno.h" -#include "rfal_features.h" - -/* -****************************************************************************** -* GLOBAL DEFINES -****************************************************************************** -*/ -#define RFAL_VERSION 0x020200U /*!< RFAL Current Version: v2.2.0 */ - -#define RFAL_FWT_NONE 0xFFFFFFFFU /*!< Disabled FWT: Wait forever for a response */ -#define RFAL_GT_NONE RFAL_TIMING_NONE /*!< Disabled GT: No GT will be applied after Field On */ - -#define RFAL_TIMING_NONE 0x00U /*!< Timing disabled | Don't apply */ - -#define RFAL_1FC_IN_4096FC \ - (uint32_t)4096U /*!< Number of 1/fc cycles in one 4096/fc */ -#define RFAL_1FC_IN_512FC (uint32_t)512U /*!< Number of 1/fc cycles in one 512/fc */ -#define RFAL_1FC_IN_64FC (uint32_t)64U /*!< Number of 1/fc cycles in one 64/fc */ -#define RFAL_1FC_IN_8FC (uint32_t)8U /*!< Number of 1/fc cycles in one 8/fc */ -#define RFAL_US_IN_MS (uint32_t)1000U /*!< Number of us in one ms */ -#define RFAL_1MS_IN_1FC (uint32_t)13560U /*!< Number of 1/fc cycles in 1ms */ -#define RFAL_BITS_IN_BYTE (uint16_t)8U /*!< Number of bits in one byte */ - -#define RFAL_CRC_LEN 2U /*!< RF CRC LEN */ - -/*! Default TxRx flags: Tx CRC automatic, Rx CRC removed, NFCIP1 mode off, AGC On, Tx Parity automatic, Rx Parity removed */ -#define RFAL_TXRX_FLAGS_DEFAULT \ - ((uint32_t)RFAL_TXRX_FLAGS_CRC_TX_AUTO | (uint32_t)RFAL_TXRX_FLAGS_CRC_RX_REMV | \ - (uint32_t)RFAL_TXRX_FLAGS_NFCIP1_OFF | (uint32_t)RFAL_TXRX_FLAGS_AGC_ON | \ - (uint32_t)RFAL_TXRX_FLAGS_PAR_RX_REMV | (uint32_t)RFAL_TXRX_FLAGS_PAR_TX_AUTO | \ - (uint32_t)RFAL_TXRX_FLAGS_NFCV_FLAG_AUTO) -#define RFAL_TXRX_FLAGS_RAW \ - ((uint32_t)RFAL_TXRX_FLAGS_CRC_TX_MANUAL | (uint32_t)RFAL_TXRX_FLAGS_CRC_RX_KEEP | \ - (uint32_t)RFAL_TXRX_FLAGS_NFCIP1_OFF | (uint32_t)RFAL_TXRX_FLAGS_AGC_ON | \ - (uint32_t)RFAL_TXRX_FLAGS_PAR_RX_KEEP | (uint32_t)RFAL_TXRX_FLAGS_PAR_TX_NONE | \ - (uint32_t)RFAL_TXRX_FLAGS_NFCV_FLAG_AUTO) - -#define RFAL_LM_MASK_NFCA \ - ((uint32_t)1U \ - << (uint8_t)RFAL_MODE_LISTEN_NFCA) /*!< Bitmask for Listen Mode enabling NFCA */ -#define RFAL_LM_MASK_NFCB \ - ((uint32_t)1U \ - << (uint8_t)RFAL_MODE_LISTEN_NFCB) /*!< Bitmask for Listen Mode enabling NFCB */ -#define RFAL_LM_MASK_NFCF \ - ((uint32_t)1U \ - << (uint8_t)RFAL_MODE_LISTEN_NFCF) /*!< Bitmask for Listen Mode enabling NFCF */ -#define RFAL_LM_MASK_ACTIVE_P2P \ - ((uint32_t)1U \ - << (uint8_t)RFAL_MODE_LISTEN_ACTIVE_P2P) /*!< Bitmask for Listen Mode enabling AP2P */ - -#define RFAL_LM_SENS_RES_LEN 2U /*!< NFC-A SENS_RES (ATQA) length */ -#define RFAL_LM_SENSB_RES_LEN 13U /*!< NFC-B SENSB_RES (ATQB) length */ -#define RFAL_LM_SENSF_RES_LEN 19U /*!< NFC-F SENSF_RES length */ -#define RFAL_LM_SENSF_SC_LEN 2U /*!< NFC-F System Code length */ - -#define RFAL_NFCID3_LEN 10U /*!< NFCID3 length */ -#define RFAL_NFCID2_LEN 8U /*!< NFCID2 length */ -#define RFAL_NFCID1_TRIPLE_LEN 10U /*!< NFCID1 length */ -#define RFAL_NFCID1_DOUBLE_LEN 7U /*!< NFCID1 length */ -#define RFAL_NFCID1_SINGLE_LEN 4U /*!< NFCID1 length */ - -/* -****************************************************************************** -* GLOBAL MACROS -****************************************************************************** -*/ - -/*! Returns the maximum supported bit rate for RW mode. Caller must check if mode is supported before, as even if mode is not supported will return the min */ -#define rfalGetMaxBrRW() \ - (((RFAL_SUPPORT_BR_RW_6780) ? \ - RFAL_BR_6780 : \ - ((RFAL_SUPPORT_BR_RW_3390) ? \ - RFAL_BR_3390 : \ - ((RFAL_SUPPORT_BR_RW_1695) ? \ - RFAL_BR_1695 : \ - ((RFAL_SUPPORT_BR_RW_848) ? \ - RFAL_BR_848 : \ - ((RFAL_SUPPORT_BR_RW_424) ? \ - RFAL_BR_424 : \ - ((RFAL_SUPPORT_BR_RW_212) ? RFAL_BR_212 : RFAL_BR_106))))))) - -/*! Returns the maximum supported bit rate for AP2P mode. Caller must check if mode is supported before, as even if mode is not supported will return the min */ -#define rfalGetMaxBrAP2P() \ - (((RFAL_SUPPORT_BR_AP2P_848) ? \ - RFAL_BR_848 : \ - ((RFAL_SUPPORT_BR_AP2P_424) ? \ - RFAL_BR_424 : \ - ((RFAL_SUPPORT_BR_AP2P_212) ? RFAL_BR_212 : RFAL_BR_106)))) - -/*! Returns the maximum supported bit rate for CE-A mode. Caller must check if mode is supported before, as even if mode is not supported will return the min */ -#define rfalGetMaxBrCEA() \ - (((RFAL_SUPPORT_BR_CE_A_848) ? \ - RFAL_BR_848 : \ - ((RFAL_SUPPORT_BR_CE_A_424) ? \ - RFAL_BR_424 : \ - ((RFAL_SUPPORT_BR_CE_A_212) ? RFAL_BR_212 : RFAL_BR_106)))) - -/*! Returns the maximum supported bit rate for CE-B mode. Caller must check if mode is supported before, as even if mode is not supported will return the min */ -#define rfalGetMaxBrCEB() \ - (((RFAL_SUPPORT_BR_CE_B_848) ? \ - RFAL_BR_848 : \ - ((RFAL_SUPPORT_BR_CE_B_424) ? \ - RFAL_BR_424 : \ - ((RFAL_SUPPORT_BR_CE_B_212) ? RFAL_BR_212 : RFAL_BR_106)))) - -/*! Returns the maximum supported bit rate for CE-F mode. Caller must check if mode is supported before, as even if mode is not supported will return the min */ -#define rfalGetMaxBrCEF() (((RFAL_SUPPORT_BR_CE_F_424) ? RFAL_BR_424 : RFAL_BR_212)) - -#define rfalIsModeActiveComm(md) \ - (((md) == RFAL_MODE_POLL_ACTIVE_P2P) || \ - ((md) == RFAL_MODE_LISTEN_ACTIVE_P2P)) /*!< Checks if mode md is Active Communication */ -#define rfalIsModePassiveComm(md) \ - (!rfalIsModeActiveComm(md)) /*!< Checks if mode md is Passive Communication */ -#define rfalIsModePassiveListen(md) \ - (((md) == RFAL_MODE_LISTEN_NFCA) || ((md) == RFAL_MODE_LISTEN_NFCB) || \ - ((md) == RFAL_MODE_LISTEN_NFCF)) /*!< Checks if mode md is Passive Listen */ -#define rfalIsModePassivePoll(md) \ - (rfalIsModePassiveComm(md) && \ - !rfalIsModePassiveListen(md)) /*!< Checks if mode md is Passive Poll */ - -#define rfalConv1fcTo8fc(t) \ - (uint32_t)((uint32_t)(t) / RFAL_1FC_IN_8FC) /*!< Converts the given t from 1/fc to 8/fc */ -#define rfalConv8fcTo1fc(t) \ - (uint32_t)((uint32_t)(t)*RFAL_1FC_IN_8FC) /*!< Converts the given t from 8/fc to 1/fc */ - -#define rfalConv1fcTo64fc(t) \ - (uint32_t)((uint32_t)(t) / RFAL_1FC_IN_64FC) /*!< Converts the given t from 1/fc to 64/fc */ -#define rfalConv64fcTo1fc(t) \ - (uint32_t)((uint32_t)(t)*RFAL_1FC_IN_64FC) /*!< Converts the given t from 64/fc to 1/fc */ - -#define rfalConv1fcTo512fc(t) \ - (uint32_t)( \ - (uint32_t)(t) / RFAL_1FC_IN_512FC) /*!< Converts the given t from 1/fc to 512/fc */ -#define rfalConv512fcTo1fc(t) \ - (uint32_t)((uint32_t)(t)*RFAL_1FC_IN_512FC) /*!< Converts the given t from 512/fc to 1/fc */ - -#define rfalConv1fcTo4096fc(t) \ - (uint32_t)( \ - (uint32_t)(t) / RFAL_1FC_IN_4096FC) /*!< Converts the given t from 1/fc to 4096/fc */ -#define rfalConv4096fcTo1fc(t) \ - (uint32_t)((uint32_t)(t)*RFAL_1FC_IN_4096FC) /*!< Converts the given t from 4096/fc to 1/fc */ - -#define rfalConv1fcToMs(t) \ - (uint32_t)((uint32_t)(t) / RFAL_1MS_IN_1FC) /*!< Converts the given t from 1/fc to ms */ -#define rfalConvMsTo1fc(t) \ - (uint32_t)((uint32_t)(t)*RFAL_1MS_IN_1FC) /*!< Converts the given t from ms to 1/fc */ - -#define rfalConv1fcToUs(t) \ - (uint32_t)( \ - ((uint32_t)(t)*RFAL_US_IN_MS) / \ - RFAL_1MS_IN_1FC) /*!< Converts the given t from 1/fc to us */ -#define rfalConvUsTo1fc(t) \ - (uint32_t)( \ - ((uint32_t)(t)*RFAL_1MS_IN_1FC) / \ - RFAL_US_IN_MS) /*!< Converts the given t from us to 1/fc */ - -#define rfalConv64fcToMs(t) \ - (uint32_t)( \ - (uint32_t)(t) / \ - (RFAL_1MS_IN_1FC / RFAL_1FC_IN_64FC)) /*!< Converts the given t from 64/fc to ms */ -#define rfalConvMsTo64fc(t) \ - (uint32_t)( \ - (uint32_t)(t) * \ - (RFAL_1MS_IN_1FC / RFAL_1FC_IN_64FC)) /*!< Converts the given t from ms to 64/fc */ - -#define rfalConvBitsToBytes(n) \ - (uint16_t)( \ - ((uint16_t)(n) + (RFAL_BITS_IN_BYTE - 1U)) / \ - (RFAL_BITS_IN_BYTE)) /*!< Converts the given n from bits to bytes */ -#define rfalConvBytesToBits(n) \ - (uint32_t)( \ - (uint32_t)(n) * (RFAL_BITS_IN_BYTE)) /*!< Converts the given n from bytes to bits */ - -/*! Computes a Transceive context \a ctx with default flags and the lengths - * in bytes with the given arguments - * \a ctx : Transceive context to be assigned - * \a tB : txBuf the pointer to the buffer to be sent - * \a tBL : txBuf length in bytes - * \a rB : rxBuf the pointer to the buffer to place the received frame - * \a rBL : rxBuf length in bytes - * \a rBL : rxBuf length in bytes - * \a t : FWT to be used on this transceive in 1/fc - */ -#define rfalCreateByteTxRxContext(ctx, tB, tBL, rB, rBL, rdL, t) \ - (ctx).txBuf = (uint8_t*)(tB); \ - (ctx).txBufLen = (uint16_t)rfalConvBytesToBits(tBL); \ - (ctx).rxBuf = (uint8_t*)(rB); \ - (ctx).rxBufLen = (uint16_t)rfalConvBytesToBits(rBL); \ - (ctx).rxRcvdLen = (uint16_t*)(rdL); \ - (ctx).flags = (uint32_t)RFAL_TXRX_FLAGS_DEFAULT; \ - (ctx).fwt = (uint32_t)(t); - -/*! Computes a Transceive context \a ctx using lengths in bytes - * with the given flags and arguments - * \a ctx : Transceive context to be assigned - * \a tB : txBuf the pointer to the buffer to be sent - * \a tBL : txBuf length in bytes - * \a rB : rxBuf the pointer to the buffer to place the received frame - * \a rBL : rxBuf length in bytes - * \a rBL : rxBuf length in bytes - * \a t : FWT to be used on this transceive in 1/fc - */ -#define rfalCreateByteFlagsTxRxContext(ctx, tB, tBL, rB, rBL, rdL, fl, t) \ - (ctx).txBuf = (uint8_t*)(tB); \ - (ctx).txBufLen = (uint16_t)rfalConvBytesToBits(tBL); \ - (ctx).rxBuf = (uint8_t*)(rB); \ - (ctx).rxBufLen = (uint16_t)rfalConvBytesToBits(rBL); \ - (ctx).rxRcvdLen = (uint16_t*)(rdL); \ - (ctx).flags = (uint32_t)(fl); \ - (ctx).fwt = (uint32_t)(t); - -#define rfalLogE(...) \ - platformLog(__VA_ARGS__) /*!< Macro for the error log method */ -#define rfalLogW(...) \ - platformLog(__VA_ARGS__) /*!< Macro for the warning log method */ -#define rfalLogI(...) \ - platformLog(__VA_ARGS__) /*!< Macro for the info log method */ -#define rfalLogD(...) \ - platformLog(__VA_ARGS__) /*!< Macro for the debug log method */ - -/* -****************************************************************************** -* GLOBAL ENUMS -****************************************************************************** -*/ - -/* RFAL Guard Time (GT) default values */ -#define RFAL_GT_NFCA \ - rfalConvMsTo1fc( \ - 5U) /*!< GTA Digital 2.0 6.10.4.1 & B.2 */ -#define RFAL_GT_NFCB \ - rfalConvMsTo1fc( \ - 5U) /*!< GTB Digital 2.0 7.9.4.1 & B.3 */ -#define RFAL_GT_NFCF \ - rfalConvMsTo1fc( \ - 20U) /*!< GTF Digital 2.0 8.7.4.1 & B.4 */ -#define RFAL_GT_NFCV \ - rfalConvMsTo1fc( \ - 5U) /*!< GTV Digital 2.0 9.7.5.1 & B.5 */ -#define RFAL_GT_PICOPASS \ - rfalConvMsTo1fc( \ - 1U) /*!< GT Picopass */ -#define RFAL_GT_AP2P \ - rfalConvMsTo1fc( \ - 5U) /*!< TIRFG Ecma 340 11.1.1 */ -#define RFAL_GT_AP2P_ADJUSTED \ - rfalConvMsTo1fc( \ - 5U + \ - 25U) /*!< Adjusted GT for greater interoperability (Sony XPERIA P, Nokia N9, Huawei P2) */ - -/* RFAL Frame Delay Time (FDT) Listen default values */ -#define RFAL_FDT_LISTEN_NFCA_POLLER \ - 1172U /*!< FDTA,LISTEN,MIN (n=9) Last bit: Logic "1" - tnn,min/2 Digital 1.1 6.10 ; EMV CCP Spec Book D v2.01 4.8.1.3 */ -#define RFAL_FDT_LISTEN_NFCB_POLLER \ - 1008U /*!< TR0B,MIN Digital 1.1 7.1.3 & A.3 ; EMV CCP Spec Book D v2.01 4.8.1.3 & Table A.5 */ -#define RFAL_FDT_LISTEN_NFCF_POLLER \ - 2672U /*!< TR0F,LISTEN,MIN Digital 1.1 8.7.1.1 & A.4 */ -#define RFAL_FDT_LISTEN_NFCV_POLLER \ - 4310U /*!< FDTV,LISTEN,MIN t1 min Digital 2.1 B.5 ; ISO15693-3 2009 9.1 */ -#define RFAL_FDT_LISTEN_PICOPASS_POLLER \ - 3400U /*!< ISO15693 t1 min - observed adjustment */ -#define RFAL_FDT_LISTEN_AP2P_POLLER \ - 64U /*!< FDT AP2P No actual FDTListen is required as fields switch and collision avoidance */ -#define RFAL_FDT_LISTEN_NFCA_LISTENER \ - 1172U /*!< FDTA,LISTEN,MIN Digital 1.1 6.10 */ -#define RFAL_FDT_LISTEN_NFCB_LISTENER \ - 1024U /*!< TR0B,MIN Digital 1.1 7.1.3 & A.3 ; EMV CCP Spec Book D v2.01 4.8.1.3 & Table A.5 */ -#define RFAL_FDT_LISTEN_NFCF_LISTENER \ - 2688U /*!< TR0F,LISTEN,MIN Digital 2.1 8.7.1.1 & B.4 */ -#define RFAL_FDT_LISTEN_AP2P_LISTENER \ - 64U /*!< FDT AP2P No actual FDTListen exists as fields switch and collision avoidance */ - -/* RFAL Frame Delay Time (FDT) Poll default values */ -#define RFAL_FDT_POLL_NFCA_POLLER \ - 6780U /*!< FDTA,POLL,MIN Digital 1.1 6.10.3.1 & A.2 */ -#define RFAL_FDT_POLL_NFCA_T1T_POLLER \ - 384U /*!< RRDDT1T,MIN,B1 Digital 1.1 10.7.1 & A.5 */ -#define RFAL_FDT_POLL_NFCB_POLLER \ - 6780U /*!< FDTB,POLL,MIN = TR2B,MIN,DEFAULT Digital 1.1 7.9.3 & A.3 ; EMVCo 3.0 FDTB,PCD,MIN Table A.5 */ -#define RFAL_FDT_POLL_NFCF_POLLER \ - 6800U /*!< FDTF,POLL,MIN Digital 2.1 8.7.3 & B.4 */ -#define RFAL_FDT_POLL_NFCV_POLLER \ - 4192U /*!< FDTV,POLL Digital 2.1 9.7.3.1 & B.5 */ -#define RFAL_FDT_POLL_PICOPASS_POLLER \ - 1790U /*!< FDT Max */ -#define RFAL_FDT_POLL_AP2P_POLLER \ - 0U /*!< FDT AP2P No actual FDTPoll exists as fields switch and collision avoidance */ - -/* -****************************************************************************** -* GLOBAL TYPES -****************************************************************************** -*/ - -/*! RFAL modes */ -typedef enum { - RFAL_MODE_NONE = 0, /*!< No mode selected/defined */ - RFAL_MODE_POLL_NFCA = - 1, /*!< Mode to perform as NFCA (ISO14443A) Poller (PCD) */ - RFAL_MODE_POLL_NFCA_T1T = - 2, /*!< Mode to perform as NFCA T1T (Topaz) Poller (PCD) */ - RFAL_MODE_POLL_NFCB = - 3, /*!< Mode to perform as NFCB (ISO14443B) Poller (PCD) */ - RFAL_MODE_POLL_B_PRIME = - 4, /*!< Mode to perform as B' Calypso (Innovatron) (PCD) */ - RFAL_MODE_POLL_B_CTS = - 5, /*!< Mode to perform as CTS Poller (PCD) */ - RFAL_MODE_POLL_NFCF = - 6, /*!< Mode to perform as NFCF (FeliCa) Poller (PCD) */ - RFAL_MODE_POLL_NFCV = - 7, /*!< Mode to perform as NFCV (ISO15963) Poller (PCD) */ - RFAL_MODE_POLL_PICOPASS = - 8, /*!< Mode to perform as PicoPass / iClass Poller (PCD) */ - RFAL_MODE_POLL_ACTIVE_P2P = - 9, /*!< Mode to perform as Active P2P (ISO18092) Initiator */ - RFAL_MODE_LISTEN_NFCA = - 10, /*!< Mode to perform as NFCA (ISO14443A) Listener (PICC) */ - RFAL_MODE_LISTEN_NFCB = - 11, /*!< Mode to perform as NFCA (ISO14443B) Listener (PICC) */ - RFAL_MODE_LISTEN_NFCF = - 12, /*!< Mode to perform as NFCA (ISO15963) Listener (PICC) */ - RFAL_MODE_LISTEN_ACTIVE_P2P = - 13 /*!< Mode to perform as Active P2P (ISO18092) Target */ -} rfalMode; - -/*! RFAL Bit rates */ -typedef enum { - RFAL_BR_106 = 0, /*!< Bit Rate 106 kbit/s (fc/128) */ - RFAL_BR_212 = 1, /*!< Bit Rate 212 kbit/s (fc/64) */ - RFAL_BR_424 = 2, /*!< Bit Rate 424 kbit/s (fc/32) */ - RFAL_BR_848 = 3, /*!< Bit Rate 848 kbit/s (fc/16) */ - RFAL_BR_1695 = 4, /*!< Bit Rate 1695 kbit/s (fc/8) */ - RFAL_BR_3390 = 5, /*!< Bit Rate 3390 kbit/s (fc/4) */ - RFAL_BR_6780 = 6, /*!< Bit Rate 6780 kbit/s (fc/2) */ - RFAL_BR_13560 = 7, /*!< Bit Rate 13560 kbit/s (fc) */ - RFAL_BR_52p97 = 0xEB, /*!< Bit Rate 52.97 kbit/s (fc/256) Fast Mode VICC->VCD */ - RFAL_BR_26p48 = 0xEC, /*!< Bit Rate 26,48 kbit/s (fc/512) NFCV VICC->VCD & VCD->VICC 1of4 */ - RFAL_BR_1p66 = 0xED, /*!< Bit Rate 1,66 kbit/s (fc/8192) NFCV VCD->VICC 1of256 */ - RFAL_BR_KEEP = 0xFF /*!< Value indicating to keep the same previous bit rate */ -} rfalBitRate; - -/*! RFAL Compliance modes for upper modules */ -typedef enum { - RFAL_COMPLIANCE_MODE_NFC, /*!< Perform with NFC Forum 1.1 compliance */ - RFAL_COMPLIANCE_MODE_EMV, /*!< Perform with EMVCo compliance */ - RFAL_COMPLIANCE_MODE_ISO /*!< Perform with ISO10373 compliance */ -} rfalComplianceMode; - -/*! RFAL main states flags */ -typedef enum { - RFAL_STATE_IDLE = 0, - RFAL_STATE_INIT = 1, - RFAL_STATE_MODE_SET = 2, - - RFAL_STATE_TXRX = 3, - RFAL_STATE_LM = 4, - RFAL_STATE_WUM = 5 - -} rfalState; - -/*! RFAL transceive states */ -typedef enum { - RFAL_TXRX_STATE_IDLE = 0, - RFAL_TXRX_STATE_INIT = 1, - RFAL_TXRX_STATE_START = 2, - - RFAL_TXRX_STATE_TX_IDLE = 11, - RFAL_TXRX_STATE_TX_WAIT_GT = 12, - RFAL_TXRX_STATE_TX_WAIT_FDT = 13, - RFAL_TXRX_STATE_TX_TRANSMIT = 14, - RFAL_TXRX_STATE_TX_WAIT_WL = 15, - RFAL_TXRX_STATE_TX_RELOAD_FIFO = 16, - RFAL_TXRX_STATE_TX_WAIT_TXE = 17, - RFAL_TXRX_STATE_TX_DONE = 18, - RFAL_TXRX_STATE_TX_FAIL = 19, - - RFAL_TXRX_STATE_RX_IDLE = 81, - RFAL_TXRX_STATE_RX_WAIT_EON = 82, - RFAL_TXRX_STATE_RX_WAIT_RXS = 83, - RFAL_TXRX_STATE_RX_WAIT_RXE = 84, - RFAL_TXRX_STATE_RX_READ_FIFO = 85, - RFAL_TXRX_STATE_RX_ERR_CHECK = 86, - RFAL_TXRX_STATE_RX_READ_DATA = 87, - RFAL_TXRX_STATE_RX_WAIT_EOF = 88, - RFAL_TXRX_STATE_RX_DONE = 89, - RFAL_TXRX_STATE_RX_FAIL = 90, - -} rfalTransceiveState; - -/*! RFAL transceive flags */ -enum { - RFAL_TXRX_FLAGS_CRC_TX_AUTO = - (0U - << 0), /*!< CRC will be generated automatic upon transmission */ - RFAL_TXRX_FLAGS_CRC_TX_MANUAL = - (1U - << 0), /*!< CRC was calculated manually, included in txBuffer */ - RFAL_TXRX_FLAGS_CRC_RX_KEEP = - (1U - << 1), /*!< Upon Reception keep the CRC in rxBuffer (reflected on rcvd length) */ - RFAL_TXRX_FLAGS_CRC_RX_REMV = - (0U - << 1), /*!< Enable CRC check and remove the CRC from rxBuffer */ - RFAL_TXRX_FLAGS_NFCIP1_ON = - (1U - << 2), /*!< Enable NFCIP1 mode: Add SB(F0) and LEN bytes during Tx and skip SB(F0) byte during Rx */ - RFAL_TXRX_FLAGS_NFCIP1_OFF = - (0U - << 2), /*!< Disable NFCIP1 mode: do not append protocol bytes while Tx nor skip while Rx */ - RFAL_TXRX_FLAGS_AGC_OFF = - (1U - << 3), /*!< Disable Automatic Gain Control, improving multiple devices collision detection */ - RFAL_TXRX_FLAGS_AGC_ON = - (0U - << 3), /*!< Enable Automatic Gain Control, improving single device reception */ - RFAL_TXRX_FLAGS_PAR_RX_KEEP = - (1U - << 4), /*!< Disable Parity and CRC check and keep the Parity and CRC bits in the received buffer */ - RFAL_TXRX_FLAGS_PAR_RX_REMV = - (0U - << 0), /*!< Enable Parity check and remove the parity bits from the received buffer */ - RFAL_TXRX_FLAGS_PAR_TX_NONE = - (1U - << 5), /*!< Disable automatic Parity generation (ISO14443A) and use the one provided in the buffer*/ - RFAL_TXRX_FLAGS_PAR_TX_AUTO = - (0U - << 5), /*!< Enable automatic Parity generation (ISO14443A) */ - RFAL_TXRX_FLAGS_NFCV_FLAG_MANUAL = - (1U - << 6), /*!< Disable automatic adaption of flag byte (ISO15693) according to current comm params */ - RFAL_TXRX_FLAGS_NFCV_FLAG_AUTO = - (0U - << 6), /*!< Enable automatic adaption of flag byte (ISO115693) according to current comm params */ -}; - -/*! RFAL error handling */ -typedef enum { - RFAL_ERRORHANDLING_NONE = - 0, /*!< No special error handling will be performed */ - RFAL_ERRORHANDLING_NFC = - 1, /*!< Error handling set to perform as NFC compliant device */ - RFAL_ERRORHANDLING_EMVCO = - 2 /*!< Error handling set to perform as EMVCo compliant device */ -} rfalEHandling; - -/*! Struct that holds all context to be used on a Transceive */ -typedef struct { - uint8_t* txBuf; /*!< (In) Buffer where outgoing message is located */ - uint16_t txBufLen; /*!< (In) Length of the outgoing message in bits */ - - uint8_t* rxBuf; /*!< (Out) Buffer where incoming message will be placed */ - uint16_t rxBufLen; /*!< (In) Maximum length of the incoming message in bits */ - uint16_t* rxRcvdLen; /*!< (Out) Actual received length in bits */ - - uint32_t flags; /*!< (In) TransceiveFlags indication special handling */ - uint32_t fwt; /*!< (In) Frame Waiting Time in 1/fc */ -} rfalTransceiveContext; - -/*! System callback to indicate an event that requires a system reRun */ -typedef void (*rfalUpperLayerCallback)(void); - -/*! Callback to be executed before a Transceive */ -typedef void (*rfalPreTxRxCallback)(void* context); - -/*! Callback to be executed after a Transceive */ -typedef void (*rfalPostTxRxCallback)(void* context); - -/** Callback to be executed on each RFAL state change */ -typedef void (*RfalStateChangedCallback)(void* context); - -/*******************************************************************************/ -/* ISO14443A */ -/*******************************************************************************/ - -/*! RFAL ISO 14443A Short Frame Command */ -typedef enum { - RFAL_14443A_SHORTFRAME_CMD_WUPA = 0x52, /*!< ISO14443A WUPA / NFC-A ALL_REQ */ - RFAL_14443A_SHORTFRAME_CMD_REQA = 0x26 /*!< ISO14443A REQA / NFC-A SENS_REQ */ -} rfal14443AShortFrameCmd; - -/*******************************************************************************/ - -/*******************************************************************************/ -/* FeliCa */ -/*******************************************************************************/ - -#define RFAL_FELICA_LEN_LEN \ - 1U /*!< FeliCa LEN byte length */ -#define RFAL_FELICA_POLL_REQ_LEN \ - (RFAL_FELICA_LEN_LEN + 1U + 2U + 1U + \ - 1U) /*!< FeliCa Poll Request length (LEN + CMD + SC + RC + TSN) */ -#define RFAL_FELICA_POLL_RES_LEN \ - (RFAL_FELICA_LEN_LEN + 1U + 8U + 8U + \ - 2U) /*!< Maximum FeliCa Poll Response length (LEN + CMD + NFCID2 + PAD + RD) */ -#define RFAL_FELICA_POLL_MAX_SLOTS \ - 16U /*!< Maximum number of slots (TSN) on FeliCa Poll */ - -/*! NFC-F RC (Request Code) codes NFC Forum Digital 1.1 Table 42 */ -enum { - RFAL_FELICA_POLL_RC_NO_REQUEST = - 0x00, /*!< RC: No System Code information requested */ - RFAL_FELICA_POLL_RC_SYSTEM_CODE = - 0x01, /*!< RC: System Code information requested */ - RFAL_FELICA_POLL_RC_COM_PERFORMANCE = - 0x02 /*!< RC: Advanced protocol features supported */ -}; - -/*! NFC-F TSN (Time Slot Number) codes NFC Forum Digital 1.1 Table 43 */ -typedef enum { - RFAL_FELICA_1_SLOT = 0, /*!< TSN with number of Time Slots: 1 */ - RFAL_FELICA_2_SLOTS = 1, /*!< TSN with number of Time Slots: 2 */ - RFAL_FELICA_4_SLOTS = 3, /*!< TSN with number of Time Slots: 4 */ - RFAL_FELICA_8_SLOTS = 7, /*!< TSN with number of Time Slots: 8 */ - RFAL_FELICA_16_SLOTS = 15 /*!< TSN with number of Time Slots: 16 */ -} rfalFeliCaPollSlots; - -/*! NFCF Poll Response NFC Forum Digital 1.1 Table 44 */ -typedef uint8_t rfalFeliCaPollRes[RFAL_FELICA_POLL_RES_LEN]; - -/*******************************************************************************/ - -/*******************************************************************************/ -/* Listen Mode */ -/*******************************************************************************/ - -/*! RFAL Listen Mode NFCID Length */ -typedef enum { - RFAL_LM_NFCID_LEN_04 = RFAL_NFCID1_SINGLE_LEN, /*!< Listen mode indicates 4 byte NFCID */ - RFAL_LM_NFCID_LEN_07 = RFAL_NFCID1_DOUBLE_LEN, /*!< Listen mode indicates 7 byte NFCID */ - RFAL_LM_NFCID_LEN_10 = RFAL_NFCID1_TRIPLE_LEN, /*!< Listen mode indicates 10 byte NFCID */ -} rfalLmNfcidLen; - -/*! RFAL Listen Mode States */ -typedef enum { - RFAL_LM_STATE_NOT_INIT = 0x00, /*!< Not Initialized state */ - RFAL_LM_STATE_POWER_OFF = 0x01, /*!< Power Off state */ - RFAL_LM_STATE_IDLE = 0x02, /*!< Idle state Activity 1.1 5.2 */ - RFAL_LM_STATE_READY_A = 0x03, /*!< Ready A state Activity 1.1 5.3 5.4 & 5.5 */ - RFAL_LM_STATE_READY_B = 0x04, /*!< Ready B state Activity 1.1 5.11 5.12 */ - RFAL_LM_STATE_READY_F = 0x05, /*!< Ready F state Activity 1.1 5.15 */ - RFAL_LM_STATE_ACTIVE_A = 0x06, /*!< Active A state Activity 1.1 5.6 */ - RFAL_LM_STATE_CARDEMU_4A = 0x07, /*!< Card Emulation 4A state Activity 1.1 5.10 */ - RFAL_LM_STATE_CARDEMU_4B = 0x08, /*!< Card Emulation 4B state Activity 1.1 5.14 */ - RFAL_LM_STATE_CARDEMU_3 = 0x09, /*!< Card Emulation 3 state Activity 1.1 5.18 */ - RFAL_LM_STATE_TARGET_A = 0x0A, /*!< Target A state Activity 1.1 5.9 */ - RFAL_LM_STATE_TARGET_F = 0x0B, /*!< Target F state Activity 1.1 5.17 */ - RFAL_LM_STATE_SLEEP_A = 0x0C, /*!< Sleep A state Activity 1.1 5.7 */ - RFAL_LM_STATE_SLEEP_B = 0x0D, /*!< Sleep B state Activity 1.1 5.13 */ - RFAL_LM_STATE_READY_Ax = 0x0E, /*!< Ready A* state Activity 1.1 5.3 5.4 & 5.5 */ - RFAL_LM_STATE_ACTIVE_Ax = 0x0F, /*!< Active A* state Activity 1.1 5.6 */ - RFAL_LM_STATE_SLEEP_AF = 0x10, /*!< Sleep AF state Activity 1.1 5.19 */ -} rfalLmState; - -/*! RFAL Listen Mode Passive A configs */ -typedef struct { - rfalLmNfcidLen nfcidLen; /*!< NFCID Len (4, 7 or 10 bytes) */ - uint8_t nfcid[RFAL_NFCID1_TRIPLE_LEN]; /*!< NFCID */ - uint8_t SENS_RES[RFAL_LM_SENS_RES_LEN]; /*!< NFC-106k; SENS_REQ Response */ - uint8_t SEL_RES; /*!< SEL_RES (SAK) with complete NFCID1 (UID) */ -} rfalLmConfPA; - -/*! RFAL Listen Mode Passive B configs */ -typedef struct { - uint8_t SENSB_RES[RFAL_LM_SENSB_RES_LEN]; /*!< SENSF_RES */ -} rfalLmConfPB; - -/*! RFAL Listen Mode Passive F configs */ -typedef struct { - uint8_t SC[RFAL_LM_SENSF_SC_LEN]; /*!< System Code to listen for */ - uint8_t SENSF_RES[RFAL_LM_SENSF_RES_LEN]; /*!< SENSF_RES */ -} rfalLmConfPF; - -/*******************************************************************************/ - -/*******************************************************************************/ -/* Wake-Up Mode */ -/*******************************************************************************/ - -#define RFAL_WUM_REFERENCE_AUTO 0xFFU /*!< Indicates new reference is set by the driver*/ - -/*! RFAL Wake-Up Mode States */ -typedef enum { - RFAL_WUM_STATE_NOT_INIT = 0x00, /*!< Not Initialized state */ - RFAL_WUM_STATE_ENABLED = 0x01, /*!< Wake-Up mode is enabled */ - RFAL_WUM_STATE_ENABLED_WOKE = 0x02, /*!< Wake-Up mode enabled and has received IRQ(s)*/ -} rfalWumState; - -/*! RFAL Wake-Up Period/Timer */ -typedef enum { - RFAL_WUM_PERIOD_10MS = 0x00, /*!< Wake-Up timer 10ms */ - RFAL_WUM_PERIOD_20MS = 0x01, /*!< Wake-Up timer 20ms */ - RFAL_WUM_PERIOD_30MS = 0x02, /*!< Wake-Up timer 30ms */ - RFAL_WUM_PERIOD_40MS = 0x03, /*!< Wake-Up timer 40ms */ - RFAL_WUM_PERIOD_50MS = 0x04, /*!< Wake-Up timer 50ms */ - RFAL_WUM_PERIOD_60MS = 0x05, /*!< Wake-Up timer 60ms */ - RFAL_WUM_PERIOD_70MS = 0x06, /*!< Wake-Up timer 70ms */ - RFAL_WUM_PERIOD_80MS = 0x07, /*!< Wake-Up timer 80ms */ - RFAL_WUM_PERIOD_100MS = 0x10, /*!< Wake-Up timer 100ms */ - RFAL_WUM_PERIOD_200MS = 0x11, /*!< Wake-Up timer 200ms */ - RFAL_WUM_PERIOD_300MS = 0x12, /*!< Wake-Up timer 300ms */ - RFAL_WUM_PERIOD_400MS = 0x13, /*!< Wake-Up timer 400ms */ - RFAL_WUM_PERIOD_500MS = 0x14, /*!< Wake-Up timer 500ms */ - RFAL_WUM_PERIOD_600MS = 0x15, /*!< Wake-Up timer 600ms */ - RFAL_WUM_PERIOD_700MS = 0x16, /*!< Wake-Up timer 700ms */ - RFAL_WUM_PERIOD_800MS = 0x17, /*!< Wake-Up timer 800ms */ -} rfalWumPeriod; - -/*! RFAL Wake-Up Period/Timer */ -typedef enum { - RFAL_WUM_AA_WEIGHT_4 = 0x00, /*!< Wake-Up Auto Average Weight 4 */ - RFAL_WUM_AA_WEIGHT_8 = 0x01, /*!< Wake-Up Auto Average Weight 8 */ - RFAL_WUM_AA_WEIGHT_16 = 0x02, /*!< Wake-Up Auto Average Weight 16 */ - RFAL_WUM_AA_WEIGHT_32 = 0x03, /*!< Wake-Up Auto Average Weight 32 */ -} rfalWumAAWeight; - -/*! RFAL Wake-Up Mode configuration */ -typedef struct { - rfalWumPeriod period; /*!< Wake-Up Timer period;how often measurement(s) is performed */ - bool irqTout; /*!< IRQ at every timeout will refresh the measurement(s) */ - bool swTagDetect; /*!< Use SW Tag Detection instead of HW Wake-Up mode */ - - struct { - bool enabled; /*!< Inductive Amplitude measurement enabled */ - uint8_t delta; /*!< Delta between the reference and measurement to wake-up */ - uint16_t reference; /*!< Reference to be used;RFAL_WUM_REFERENCE_AUTO sets it auto */ - bool autoAvg; /*!< Use the HW Auto Averaging feature */ - bool aaInclMeas; /*!< When AutoAvg is enabled, include IRQ measurement */ - rfalWumAAWeight aaWeight; /*!< When AutoAvg is enabled, last measure weight */ - } indAmp; /*!< Inductive Amplitude Configuration */ - struct { - bool enabled; /*!< Inductive Phase measurement enabled */ - uint8_t delta; /*!< Delta between the reference and measurement to wake-up */ - uint16_t reference; /*!< Reference to be used;RFAL_WUM_REFERENCE_AUTO sets it auto */ - bool autoAvg; /*!< Use the HW Auto Averaging feature */ - bool aaInclMeas; /*!< When AutoAvg is enabled, include IRQ measurement */ - rfalWumAAWeight aaWeight; /*!< When AutoAvg is enabled, last measure weight */ - } indPha; /*!< Inductive Phase Configuration */ - struct { - bool enabled; /*!< Capacitive measurement enabled */ - uint8_t delta; /*!< Delta between the reference and measurement to wake-up */ - uint16_t reference; /*!< Reference to be used;RFAL_WUM_REFERENCE_AUTO sets it auto */ - bool autoAvg; /*!< Use the HW Auto Averaging feature */ - bool aaInclMeas; /*!< When AutoAvg is enabled, include IRQ measurement */ - rfalWumAAWeight aaWeight; /*!< When AutoAvg is enabled, last measure weight */ - } cap; /*!< Capacitive Configuration */ -} rfalWakeUpConfig; - -/*******************************************************************************/ - -/* -****************************************************************************** -* GLOBAL FUNCTION PROTOTYPES -****************************************************************************** -*/ - -/*! - ***************************************************************************** - * \brief RFAL Initialize - * - * Initializes RFAL layer and the ST25R391x - * Ensures that ST25R391x is properly connected and returns error if any problem - * is detected - * - * \warning rfalAnalogConfigInitialize() should be called before so that - * the Analog config table has been previously initialized. - * - * \return ERR_HW_MISMATCH : Expected HW do not match or communication error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalInitialize(void); - -/*! - ***************************************************************************** - * \brief RFAL Calibrate - * - * Performs necessary calibration of RF chip in case it is indicated by current - * register settings. E.g. antenna calibration and regulator calibration - * - * \return ERR_WRONG_STATE : RFAL not initialized - * \return ERR_NONE : No error - * - ***************************************************************************** - */ -ReturnCode rfalCalibrate(void); - -/*! - ***************************************************************************** - * \brief RFAL Adjust Regulators - * - * Adjusts ST25R391x regulators - * - * \param[out] result : the result of the calibrate antenna in mV - * NULL if result not requested - * - * \return ERR_WRONG_STATE : RFAL not initialized - * \return ERR_NONE : No error - * - ***************************************************************************** - */ -ReturnCode rfalAdjustRegulators(uint16_t* result); - -/*! - ***************************************************************************** - * \brief RFAL Set System Callback - * - * Sets a callback for the driver to call when an event has occurred that - * may require the system to be notified - * - * \param[in] pFunc : method pointer for the upper layer callback - * - ***************************************************************************** - */ -void rfalSetUpperLayerCallback(rfalUpperLayerCallback pFunc); - -/*! - ***************************************************************************** - * \brief RFAL Set Pre Tx Callback - * - * Sets a callback for the driver to call before a Transceive - * - * \param[in] pFunc : method pointer for the Pre Tx callback - * - ***************************************************************************** - */ -void rfalSetPreTxRxCallback(rfalPreTxRxCallback pFunc); - -/*! - ***************************************************************************** - * \brief RFAL Set Post Tx Callback - * - * Sets a callback for the driver to call after a Transceive - * - * \param[in] pFunc : method pointer for the Post Tx callback - * - ***************************************************************************** - */ -void rfalSetPostTxRxCallback(rfalPostTxRxCallback pFunc); - -/** Set RFAL state changed callback - * - * @param cb RfalStateChangedCallback instance - * @param ctx pointer to context - */ -void rfal_set_state_changed_callback(RfalStateChangedCallback callback); - -/** Set callback context - * - * @param ctx pointer to context - */ -void rfal_set_callback_context(void* context); - -/*! - ***************************************************************************** - * \brief RFAL Deinitialize - * - * Deinitializes RFAL layer and the ST25R391x - * - * \return ERR_NONE : No error - * - ***************************************************************************** - */ -ReturnCode rfalDeinitialize(void); - -/*! - ***************************************************************************** - * \brief RFAL Set Mode - * - * Sets the mode that RFAL will operate on the following communications. - * Proper initializations will be performed on the ST25R391x - * - * \warning bit rate value RFAL_BR_KEEP is not allowed, only in rfalSetBitRate() - * - * \warning the mode will be applied immediately on the RFchip regardless of - * any ongoing operations like Transceive, ListenMode - * - * \param[in] mode : mode for the RFAL/RFchip to perform - * \param[in] txBR : transmit bit rate - * \param[in] rxBR : receive bit rate - * - * \see rfalIsGTExpired - * \see rfalMode - * - * \return ERR_WRONG_STATE : RFAL not initialized - * \return ERR_PARAM : Invalid parameter - * \return ERR_NONE : No error - * - ***************************************************************************** - */ -ReturnCode rfalSetMode(rfalMode mode, rfalBitRate txBR, rfalBitRate rxBR); - -/*! - ***************************************************************************** - * \brief RFAL Get Mode - * - * Gets the mode that RFAL is set to operate - * - * \see rfalMode - * - * \return rfalMode : The current RFAL mode - ***************************************************************************** - */ -rfalMode rfalGetMode(void); - -/*! - ***************************************************************************** - * \brief RFAL Set Bit Rate - * - * Sets the Tx and Rx bit rates with the given values - * The bit rate change is applied on the RF chip remaining in the same - * mode previous defined with rfalSetMode() - * - * If no mode is defined bit rates will not be applied and an error - * is returned - * - * \param[in] txBR : transmit bit rate - * \param[in] rxBR : receive bit rate - * - * \see rfalSetMode - * \see rfalMode - * \see rfalBitRate - * - * \return ERR_WRONG_STATE : RFAL not initialized - * \return ERR_PARAM : Invalid parameter - * \return ERR_NOT_IMPLEMENTED : Mode not implemented - * \return ERR_NONE : No error - * - ***************************************************************************** - */ -ReturnCode rfalSetBitRate(rfalBitRate txBR, rfalBitRate rxBR); - -/*! - ***************************************************************************** - * \brief RFAL Get Bit Rate - * - * Gets the Tx and Rx current bit rates - * - * If RFAL is not initialized or mode not set the bit rates return will - * be invalid RFAL_BR_KEEP - * - * \param[out] txBR : RFAL's current Tx Bit Rate - * \param[out] rxBR : RFAL's current Rx Bit Rate - * - * \see rfalSetBitRate - * \see rfalBitRate - * - * \return ERR_WRONG_STATE : RFAL not initialized or mode not set - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalGetBitRate(rfalBitRate* txBR, rfalBitRate* rxBR); - -/*! - ***************************************************************************** - * \brief Set Error Handling Mode - * - * Sets the error handling mode to be used by the RFAL - * - * \param[in] eHandling : the error handling mode - * - ***************************************************************************** - */ -void rfalSetErrorHandling(rfalEHandling eHandling); - -/*! - ***************************************************************************** - * \brief Get Error Handling Mode - * - * Gets the error handling mode currently used by the RFAL - * - * \return rfalEHandling : Current error handling mode - ***************************************************************************** - */ -rfalEHandling rfalGetErrorHandling(void); - -/*! - ***************************************************************************** - * \brief Set Observation Mode - * - * Sets ST25R391x observation modes for RF debug purposes - * - * \param[in] txMode : the observation mode to be used during transmission - * \param[in] rxMode : the observation mode to be used during reception - * - * \warning The Observation Mode is an advanced feature and should be set - * according to the documentation of the part number in use. - * Please refer to the corresponding Datasheet or Application Note(s) - ***************************************************************************** - */ -void rfalSetObsvMode(uint8_t txMode, uint8_t rxMode); - -/*! - ***************************************************************************** - * \brief Get Observation Mode - * - * Gets ST25R391x the current configured observation modes - * - * \param[in] txMode : the current observation mode configured for transmission - * \param[in] rxMode : the current observation mode configured for reception - * - ***************************************************************************** - */ -void rfalGetObsvMode(uint8_t* txMode, uint8_t* rxMode); - -/*! - ***************************************************************************** - * \brief Disable Observation Mode - * - * Disables the ST25R391x observation mode - ***************************************************************************** - */ -void rfalDisableObsvMode(void); - -/*! - ***************************************************************************** - * \brief RFAL Set FDT Poll - * - * Sets the Frame Delay Time (FDT) to be used on the following - * communications. - * - * FDT Poll is the minimum time following a Poll Frame during - * which no subsequent Poll Frame can be sent (without a response from - * the Listener in between) - * FDTx,PP,MIN - Digital 1.1 6.10.2 & 7.9.2 & 8.7.2 - * - * \param[in] FDTPoll : Frame Delay Time in 1/fc cycles - * - ***************************************************************************** - */ -void rfalSetFDTPoll(uint32_t FDTPoll); - -/*! - ***************************************************************************** - * \brief RFAL Set FDT Poll - * - * Gets the current Frame Delay Time (FDT) - * - * FDT Poll is the minimum time following a Poll Frame during - * which no subsequent Poll Frame can be sent (without a response from - * the Listener in between) - * FDTx,PP,MIN - Digital 1.1 6.10.2 & 7.9.2 & 8.7.2 - * - * \return FDT : current FDT value in 1/fc cycles - * - ***************************************************************************** - */ -uint32_t rfalGetFDTPoll(void); - -/*! - ***************************************************************************** - * \brief RFAL Set FDT Listen - * - * Sets the Frame Delay Time (FDT) Listen minimum to be used on the - * following communications. - * - * FDT Listen is the minimum time between a Poll Frame and a Listen Frame - * FDTx,LISTEN,MIN - Digital 1.1 6.10.1 & 7.9.1 & 8.7.1 - * - * \param[in] FDTListen : Frame Delay Time in 1/fc cycles - * - ***************************************************************************** - */ -void rfalSetFDTListen(uint32_t FDTListen); - -/*! - ***************************************************************************** - * \brief RFAL Set FDT Listen - * - * Gets the Frame Delay Time (FDT) Listen minimum - * - * FDT Listen is the minimum time between a Poll Frame and a Listen Frame - * FDTx,LISTEN,MIN - Digital 1.1 6.10.1 & 7.9.1 & 8.7.1 - * - * \return FDT : current FDT value in 1/fc cycles - * - ***************************************************************************** - */ -uint32_t rfalGetFDTListen(void); - -/*! - ***************************************************************************** - * \brief RFAL Get GT - * - * Gets the current Guard Time (GT) - * - * GT is the minimum time when a device in Listen Mode is exposed to an - * unmodulated carrier - * - * \return GT : Guard Time in 1/fc cycles - * - ***************************************************************************** - */ -uint32_t rfalGetGT(void); - -/*! - ***************************************************************************** - * \brief RFAL Set GT - * - * Sets the Guard Time (GT) to be used on the following communications. - * - * GT is the minimum time when a device in Listen Mode is exposed to an - * unmodulated carrier - * - * \param[in] GT : Guard Time in 1/fc cycles - * RFAL_GT_NONE if no GT should be applied - * - ***************************************************************************** - */ -void rfalSetGT(uint32_t GT); - -/*! - ***************************************************************************** - * \brief RFAL Is GT expired - * - * Checks whether the GT timer has expired - * - * \return true : GT has expired or not running - * \return false : GT is still running - * - ***************************************************************************** - */ -bool rfalIsGTExpired(void); - -/*! - ***************************************************************************** - * \brief RFAL Turn Field On and Start GT - * - * Turns the Field On, performing Initial Collision Avoidance - * - * After Field On, if GT was set before, it starts the GT timer to be - * used on the following communications. - * - * \return ERR_RF_COLLISION : External field detected - * \return ERR_NONE : Field turned On - * - ***************************************************************************** - */ -ReturnCode rfalFieldOnAndStartGT(void); - -/*! - ***************************************************************************** - * \brief RFAL Turn Field Off - * - * Turns the Field Off - * - * \return ERR_NONE : Field turned Off - ***************************************************************************** - */ -ReturnCode rfalFieldOff(void); - -/***************************************************************************** - * Transceive * - *****************************************************************************/ - -/*! - ***************************************************************************** - * \brief RFAL Set transceive context - * - * Set the context that will be used for the following Transceive - * Output and input buffers have to be passed and all other details prior to - * the Transceive itself has been started - * - * This method only sets the context, once set rfalWorker has - * to be executed until is done - * - * \param[in] ctx : the context for the following Transceive - * - * \see rfalWorker - * \see rfalGetTransceiveStatus - * - * \return ERR_NONE : Done with no error - * \return ERR_WRONG_STATE : Not initialized properly - * \return ERR_PARAM : Invalid parameter or configuration - ***************************************************************************** - */ -ReturnCode rfalStartTransceive(const rfalTransceiveContext* ctx); - -/*! - ***************************************************************************** - * \brief Get Transceive State - * - * Gets current Transceive internal State - * - * \return rfalTransceiveState : the current Transceive internal State - ***************************************************************************** - */ -rfalTransceiveState rfalGetTransceiveState(void); - -/*! - ***************************************************************************** - * \brief Get Transceive Status - * - * Gets current Transceive status - * - * \return ERR_NONE : Transceive done with no error - * \return ERR_BUSY : Transceive ongoing - * \return ERR_XXXX : Error occurred - * \return ERR_TIMEOUT : No response - * \return ERR_FRAMING : Framing error detected - * \return ERR_PAR : Parity error detected - * \return ERR_CRC : CRC error detected - * \return ERR_LINK_LOSS : Link Loss - External Field is Off - * \return ERR_RF_COLLISION : Collision detected - * \return ERR_IO : Internal error - ***************************************************************************** - */ -ReturnCode rfalGetTransceiveStatus(void); - -/*! - ***************************************************************************** - * \brief Is Transceive in Tx - * - * Checks if Transceive is in Transmission state - * - * \return true Transmission ongoing - * \return false Not in transmission state - ***************************************************************************** - */ -bool rfalIsTransceiveInTx(void); - -/*! - ***************************************************************************** - * \brief Is Transceive in Rx - * - * Checks if Transceive is in Reception state - * - * \return true Transmission done/reception ongoing - * \return false Not in reception state - ***************************************************************************** - */ -bool rfalIsTransceiveInRx(void); - -/*! - ***************************************************************************** - * \brief Get Transceive RSSI - * - * Gets the RSSI value of the last executed Transceive in mV - * - * \param[out] rssi : RSSI value - * - * \return ERR_NOTSUPP : Feature not supported - * \return ERR_PARAM : Invalid parameter - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalGetTransceiveRSSI(uint16_t* rssi); - -/*! - ***************************************************************************** - * \brief RFAL Worker - * - * This runs RFAL layer, which drives the actual Transceive procedure - * It MUST be executed frequently in order to execute the RFAL internal - * states and perform the requested operations - * - ***************************************************************************** - */ -void rfalWorker(void); - -/***************************************************************************** - * ISO1443A * - *****************************************************************************/ - -/*! - ***************************************************************************** - * \brief Transceives an ISO14443A ShortFrame - * - * Sends REQA to detect if there is any PICC in the field - * - * \param[in] txCmd: Command to be sent: - * 0x52 WUPA / ALL_REQ - * 0x26 REQA / SENS_REQ - * - * \param[in] txCmd : type of short frame to be sent REQA or WUPA - * \param[out] rxBuf : buffer to place the response - * \param[in] rxBufLen : length of rxBuf - * \param[out] rxRcvdLen: received length - * \param[in] fwt : Frame Waiting Time in 1/fc - * - * \warning If fwt is set to RFAL_FWT_NONE it will make endlessly for - * a response, which on a blocking method may not be the - * desired usage - * - * \return ERR_NONE if there is response - * \return ERR_TIMEOUT if there is no response - * \return ERR_COLLISION collision has occurred - * - ***************************************************************************** - */ -ReturnCode rfalISO14443ATransceiveShortFrame( - rfal14443AShortFrameCmd txCmd, - uint8_t* rxBuf, - uint8_t rxBufLen, - uint16_t* rxRcvdLen, - uint32_t fwt); - -/*! - ***************************************************************************** - * \brief Sends an ISO14443A Anticollision Frame - * - * This is use to perform ISO14443A anti-collision. - * \note Anticollision is sent without CRC - * - * - * \param[in] buf : reference to ANTICOLLISION command (with known UID if any) to be sent (also out param) - * reception will be place on this buf after bytesToSend - * \param[in] bytesToSend: reference number of full bytes to be sent (including CMD byte and SEL_PAR) - * if a collision occurs will contain the number of clear bytes - * \param[in] bitsToSend : reference to number of bits (0-7) to be sent; and received (also out param) - * if a collision occurs will indicate the number of clear bits (also out param) - * \param[out] rxLength : reference to the return the received length - * \param[in] fwt : Frame Waiting Time in 1/fc - * - * \return ERR_NONE if there is no error - ***************************************************************************** - */ -ReturnCode rfalISO14443ATransceiveAnticollisionFrame( - uint8_t* buf, - uint8_t* bytesToSend, - uint8_t* bitsToSend, - uint16_t* rxLength, - uint32_t fwt); - -/***************************************************************************** - * FeliCa * - *****************************************************************************/ - -/*! - ***************************************************************************** - * \brief FeliCa Poll - * - * Sends a Poll Request and collects all Poll Responses according to the - * given slots - * - * - * \param[in] slots : number of slots for the Poll Request - * \param[in] sysCode : system code (SC) for the Poll Request - * \param[in] reqCode : request code (RC) for the Poll Request - * \param[out] pollResList : list of all responses - * \param[in] pollResListSize : number of responses that can be placed in pollResList - * \param[out] devicesDetected : number of cards found - * \param[out] collisionsDetected: number of collisions detected - * - * \return ERR_NONE if there is no error - * \return ERR_TIMEOUT if there is no response - ***************************************************************************** - */ -ReturnCode rfalFeliCaPoll( - rfalFeliCaPollSlots slots, - uint16_t sysCode, - uint8_t reqCode, - rfalFeliCaPollRes* pollResList, - uint8_t pollResListSize, - uint8_t* devicesDetected, - uint8_t* collisionsDetected); - -/***************************************************************************** - * ISO15693 * - *****************************************************************************/ - -/*! - ***************************************************************************** - * \brief Sends an ISO15693 Anticollision Frame - * - * This send the Anticollision|Inventory frame (INVENTORY_REQ) - * - * \warning rxBuf must be able to contain the payload and CRC - * - * \param[in] txBuf : Buffer where outgoing message is located - * \param[in] txBufLen : Length of the outgoing message in bytes - * \param[out] rxBuf : Buffer where incoming message will be placed - * \param[in] rxBufLen : Maximum length of the incoming message in bytes - * \param[out] actLen : Actual received length in bits - * - * \return ERR_NONE : Transceive done with no error - * \return ERR_WRONG_STATE : RFAL not initialized or mode not set - * \return ERR_IO : Internal error - ***************************************************************************** - */ -ReturnCode rfalISO15693TransceiveAnticollisionFrame( - uint8_t* txBuf, - uint8_t txBufLen, - uint8_t* rxBuf, - uint8_t rxBufLen, - uint16_t* actLen); - -/*! - ***************************************************************************** - * \brief Sends an ISO15693 Anticollision EOF - * - * This sends the Anticollision|Inventory EOF used as a slot marker - * - * \warning rxBuf must be able to contain the payload and CRC - * - * \param[out] rxBuf : Buffer where incoming message will be placed - * \param[in] rxBufLen : Maximum length of the incoming message in bytes - * \param[out] actLen : Actual received length in bits - * - * \return ERR_NONE : Transceive done with no error - * \return ERR_WRONG_STATE : RFAL not initialized or mode not set - * \return ERR_IO : Internal error - ***************************************************************************** - */ -ReturnCode - rfalISO15693TransceiveEOFAnticollision(uint8_t* rxBuf, uint8_t rxBufLen, uint16_t* actLen); - -/*! - ***************************************************************************** - * \brief Sends an ISO15693 EOF - * - * This is method sends an ISO15693 (EoF) used for a Write operation - * - * \warning rxBuf must be able to contain the payload and CRC - * - * \param[out] rxBuf : Buffer where incoming message will be placed - * \param[in] rxBufLen : Maximum length of the incoming message in bytes - * \param[out] actLen : Actual received length in bytes - * - * \return ERR_NONE : Transceive done with no error - * \return ERR_IO : Internal error - ***************************************************************************** - */ -ReturnCode rfalISO15693TransceiveEOF(uint8_t* rxBuf, uint8_t rxBufLen, uint16_t* actLen); - -/*! - ***************************************************************************** - * \brief Transceive Blocking Tx - * - * This is method triggers a Transceive and executes it blocking until the - * Tx has been completed - * - * \param[in] txBuf : Buffer where outgoing message is located - * \param[in] txBufLen : Length of the outgoing message in bytes - * \param[out] rxBuf : Buffer where incoming message will be placed - * \param[in] rxBufLen : Maximum length of the incoming message in bytes - * \param[out] actLen : Actual received length in bits - * \param[in] flags : TransceiveFlags indication special handling - * \param[in] fwt : Frame Waiting Time in 1/fc - * - * \return ERR_NONE : Transceive done with no error - * \return ERR_BUSY : Transceive ongoing - * \return ERR_XXXX : Error occurred - * \return ERR_LINK_LOSS : Link Loss - External Field is Off - * \return ERR_RF_COLLISION : Collision detected - * \return ERR_IO : Internal error - ***************************************************************************** - */ -ReturnCode rfalTransceiveBlockingTx( - uint8_t* txBuf, - uint16_t txBufLen, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* actLen, - uint32_t flags, - uint32_t fwt); - -/*! - ***************************************************************************** - * \brief Transceive Blocking Rx - * - * This is method executes the reception of an ongoing Transceive triggered - * before by rfalTransceiveBlockingTx() - * - * \return ERR_NONE : Transceive done with no error - * \return ERR_BUSY : Transceive ongoing - * \return ERR_XXXX : Error occurred - * \return ERR_TIMEOUT : No response - * \return ERR_FRAMING : Framing error detected - * \return ERR_PAR : Parity error detected - * \return ERR_CRC : CRC error detected - * \return ERR_LINK_LOSS : Link Loss - External Field is Off - * \return ERR_RF_COLLISION : Collision detected - * \return ERR_IO : Internal error - ***************************************************************************** - */ -ReturnCode rfalTransceiveBlockingRx(void); - -/*! - ***************************************************************************** - * \brief Transceive Blocking - * - * This is method triggers a Transceive and executes it blocking until it - * has been completed - * - * \param[in] txBuf : Buffer where outgoing message is located - * \param[in] txBufLen : Length of the outgoing message in bytes - * \param[out] rxBuf : Buffer where incoming message will be placed - * \param[in] rxBufLen : Maximum length of the incoming message in bytes - * \param[out] actLen : Actual received length in bytes - * \param[in] flags : TransceiveFlags indication special handling - * \param[in] fwt : Frame Waiting Time in 1/fc - * - * \return ERR_NONE : Transceive done with no error - * \return ERR_BUSY : Transceive ongoing - * \return ERR_XXXX : Error occurred - * \return ERR_TIMEOUT : No response - * \return ERR_FRAMING : Framing error detected - * \return ERR_PAR : Parity error detected - * \return ERR_CRC : CRC error detected - * \return ERR_LINK_LOSS : Link Loss - External Field is Off - * \return ERR_RF_COLLISION : Collision detected - * \return ERR_IO : Internal error - ***************************************************************************** - */ -ReturnCode rfalTransceiveBlockingTxRx( - uint8_t* txBuf, - uint16_t txBufLen, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* actLen, - uint32_t flags, - uint32_t fwt); - -ReturnCode rfalTransceiveBitsBlockingTxRx( - uint8_t* txBuf, - uint16_t txBufLen, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* actLen, - uint32_t flags, - uint32_t fwt); - -ReturnCode rfalTransceiveBitsBlockingTx( - uint8_t* txBuf, - uint16_t txBufLen, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* actLen, - uint32_t flags, - uint32_t fwt); - -/***************************************************************************** - * Listen Mode * - *****************************************************************************/ - -/*! - ***************************************************************************** - * \brief Is external Field On - * - * Checks if external field (other peer/device) is on/detected - * - * \return true External field is On - * \return false No external field is detected - * - ***************************************************************************** - */ -bool rfalIsExtFieldOn(void); - -/*! - ***************************************************************************** - * \brief Listen Mode start - * - * Configures RF Chip to go into listen mode enabling the given technologies - * - * - * \param[in] lmMask: mask with the enabled/disabled listen modes - * use: RFAL_LM_MASK_NFCA ; RFAL_LM_MASK_NFCB ; - * RFAL_LM_MASK_NFCF ; RFAL_LM_MASK_ACTIVE_P2P - * \param[in] confA: pointer to Passive A configurations (NULL if disabled) - * \param[in] confB: pointer to Passive B configurations (NULL if disabled) - * \param[in] confF: pointer to Passive F configurations (NULL if disabled) - * \param[in] rxBuf: buffer to place incoming data - * \param[in] rxBufLen: length in bits of rxBuf - * \param[in] rxLen: pointer to write the data length in bits placed into rxBuf - * - * - * \return ERR_PARAM Invalid parameter - * \return ERR_REQUEST Invalid listen mode mask - * \return ERR_NONE Done with no error - * - ***************************************************************************** - */ -ReturnCode rfalListenStart( - uint32_t lmMask, - const rfalLmConfPA* confA, - const rfalLmConfPB* confB, - const rfalLmConfPF* confF, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rxLen); - -/*! - ***************************************************************************** - * \brief Listen Mode start Sleeping - * - * - ***************************************************************************** - */ -ReturnCode - rfalListenSleepStart(rfalLmState sleepSt, uint8_t* rxBuf, uint16_t rxBufLen, uint16_t* rxLen); - -/*! - ***************************************************************************** - * \brief Listen Mode Stop - * - * Disables the listen mode on the RF Chip - * - * \warning the listen mode will be disabled immediately on the RFchip regardless - * of any ongoing operations like Transceive - * - * \return ERR_NONE Done with no error - * - ***************************************************************************** - */ -ReturnCode rfalListenStop(void); - -/*! - ***************************************************************************** - * \brief Listen Mode get state - * - * Sets the new state of the Listen Mode and applies the necessary changes - * on the RF Chip - * - * \param[out] dataFlag: indicates that Listen Mode has rcvd data and caller - * must process it. The received message is located - * at the rxBuf passed on rfalListenStart(). - * rfalListenSetState() will clear this flag - * if NULL output parameter will no be written/returned - * \param[out] lastBR: bit rate detected of the last initiator request - * if NULL output parameter will no be written/returned - * - * \return rfalLmState RFAL_LM_STATE_NOT_INIT : LM not initialized properly - * Any Other : LM State - * - ***************************************************************************** - */ -rfalLmState rfalListenGetState(bool* dataFlag, rfalBitRate* lastBR); - -/*! - ***************************************************************************** - * \brief Listen Mode set state - * - * Sets the new state of the Listen Mode and applies the necessary changes - * on the RF Chip - * - * \param[in] newSt : New state to go to - * - * \return ERR_WRONG_STATE : Not initialized properly - * \return ERR_PARAM : Invalid parameter - * \return ERR_NONE : Done with no error - * - ***************************************************************************** - */ -ReturnCode rfalListenSetState(rfalLmState newSt); - -/***************************************************************************** - * Wake-Up Mode * - *****************************************************************************/ - -/*! - ***************************************************************************** - * \brief Wake-Up Mode Start - * - * Sets the RF Chip in Low Power Wake-Up Mode according to the given - * configuration. - * - * \param[in] config : Generic Wake-Up configuration provided by lower - * layers. If NULL will automatically configure the - * Wake-Up mode - * - * \return ERR_WRONG_STATE : Not initialized properly - * \return ERR_PARAM : Invalid parameter - * \return ERR_NONE : Done with no error - * - ***************************************************************************** - */ -ReturnCode rfalWakeUpModeStart(const rfalWakeUpConfig* config); - -/*! - ***************************************************************************** - * \brief Wake-Up Has Woke - * - * Returns true if the Wake-Up mode is enabled and it has already received - * the indication from the RF Chip that the surrounding environment has changed - * and flagged at least one wake-Up interrupt - * - * \return true : Wake-Up mode enabled and has received a wake-up IRQ - * \return false : no Wake-Up IRQ has been received - * - ***************************************************************************** - */ -bool rfalWakeUpModeHasWoke(void); - -/*! - ***************************************************************************** - * \brief Wake-Up Mode Stop - * - * Stops the Wake-Up Mode - * - * \return ERR_WRONG_STATE : Not initialized properly - * \return ERR_PARAM : Invalid parameter - * \return ERR_NONE : Done with no error - * - ***************************************************************************** - */ -ReturnCode rfalWakeUpModeStop(void); - -/*! - ***************************************************************************** - * \brief Low Power Mode Start - * - * Sets the RF Chip in Low Power Mode. - * In this mode the RF Chip is placed in Low Power Mode, similar to Wake-up - * mode but no operation nor period measurement is performed. - * Mode must be terminated by rfalLowPowerModeStop() - * - * \return ERR_WRONG_STATE : Not initialized properly - * \return ERR_PARAM : Invalid parameter - * \return ERR_NONE : Done with no error - * - ***************************************************************************** - */ -ReturnCode rfalLowPowerModeStart(void); - -/*! - ***************************************************************************** - * \brief Low Power Mode Stop - * - * Stops the Low Power Mode re-enabling the device - * - * \return ERR_WRONG_STATE : Not initialized properly - * \return ERR_PARAM : Invalid parameter - * \return ERR_NONE : Done with no error - * - ***************************************************************************** - */ -ReturnCode rfalLowPowerModeStop(void); - -#endif /* RFAL_RF_H */ - -/** - * @} - * - * @} - * - * @} - */ diff --git a/lib/ST25RFAL002/include/rfal_st25tb.h b/lib/ST25RFAL002/include/rfal_st25tb.h deleted file mode 100644 index dcd7baa5789..00000000000 --- a/lib/ST25RFAL002/include/rfal_st25tb.h +++ /dev/null @@ -1,340 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

    © COPYRIGHT 2020 STMicroelectronics

    - * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R391x firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file rfal_st25tb.h - * - * \author Gustavo Patricio - * - * \brief Implementation of ST25TB interface - * - * - * \addtogroup RFAL - * @{ - * - * \addtogroup RFAL-AL - * \brief RFAL Abstraction Layer - * @{ - * - * \addtogroup ST25TB - * \brief RFAL ST25TB Module - * @{ - * - */ - -#ifndef RFAL_ST25TB_H -#define RFAL_ST25TB_H - -/* - ****************************************************************************** - * INCLUDES - ****************************************************************************** - */ -#include "platform.h" -#include "st_errno.h" -#include "rfal_rf.h" -#include "rfal_nfcb.h" - -/* - ****************************************************************************** - * GLOBAL DEFINES - ****************************************************************************** - */ - -#define RFAL_ST25TB_CHIP_ID_LEN 1U /*!< ST25TB chip ID length */ -#define RFAL_ST25TB_CRC_LEN 2U /*!< ST25TB CRC length */ -#define RFAL_ST25TB_UID_LEN 8U /*!< ST25TB Unique ID length */ -#define RFAL_ST25TB_BLOCK_LEN 4U /*!< ST25TB Data Block length */ - -/* -****************************************************************************** -* GLOBAL MACROS -****************************************************************************** -*/ - -/* -****************************************************************************** -* GLOBAL TYPES -****************************************************************************** -*/ -typedef uint8_t rfalSt25tbUID[RFAL_ST25TB_UID_LEN]; /*!< ST25TB UID type */ -typedef uint8_t rfalSt25tbBlock[RFAL_ST25TB_BLOCK_LEN]; /*!< ST25TB Block type */ - -/*! ST25TB listener device (PICC) struct */ -typedef struct { - uint8_t chipID; /*!< Device's session Chip ID */ - rfalSt25tbUID UID; /*!< Device's UID */ - bool isDeselected; /*!< Device deselect flag */ -} rfalSt25tbListenDevice; - -/* -****************************************************************************** -* GLOBAL FUNCTION PROTOTYPES -****************************************************************************** -*/ - -/*! - ***************************************************************************** - * \brief Initialize ST25TB Poller mode - * - * This methods configures RFAL RF layer to perform as a - * ST25TB Poller/RW including all default timings - * - * \return ERR_WRONG_STATE : RFAL not initialized or mode not set - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalSt25tbPollerInitialize(void); - -/*! - ***************************************************************************** - * \brief ST25TB Poller Check Presence - * - * This method checks if a ST25TB Listen device (PICC) is present on the field - * by sending an Initiate command - * - * \param[out] chipId : if successfully retrieved, the device's chip ID - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_TIMEOUT : Timeout error, no listener device detected - * \return ERR_RF_COLLISION : Collision detected one or more device in the field - * \return ERR_PROTO : Protocol error detected - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalSt25tbPollerCheckPresence(uint8_t* chipId); - -/*! - ***************************************************************************** - * \brief ST25TB Poller Collision Resolution - * - * This method performs ST25TB Collision resolution, selects the each device, - * retrieves its UID and then deselects. - * In case only one device is identified the ST25TB device is left in select - * state. - * - * \param[in] devLimit : device limit value, and size st25tbDevList - * \param[out] st25tbDevList : ST35TB listener device info - * \param[out] devCnt : Devices found counter - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_TIMEOUT : Timeout error, no listener device detected - * \return ERR_RF_COLLISION : Collision detected one or more device in the field - * \return ERR_PROTO : Protocol error detected - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalSt25tbPollerCollisionResolution( - uint8_t devLimit, - rfalSt25tbListenDevice* st25tbDevList, - uint8_t* devCnt); - -/*! - ***************************************************************************** - * \brief ST25TB Poller Initiate - * - * This method sends an Initiate command - * - * If a single device responds the chip ID will be retrieved - * - * \param[out] chipId : chip ID of the device - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_TIMEOUT : Timeout error, no listener device detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalSt25tbPollerInitiate(uint8_t* chipId); - -/*! - ***************************************************************************** - * \brief ST25TB Poller Pcall - * - * This method sends a Pcall command - * If successful the device's chip ID will be retrieved - * - * \param[out] chipId : Chip ID of the device - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_TIMEOUT : Timeout error, no listener device detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalSt25tbPollerPcall(uint8_t* chipId); - -/*! - ***************************************************************************** - * \brief ST25TB Poller Slot Marker - * - * This method sends a Slot Marker - * - * If a single device responds the chip ID will be retrieved - * - * \param[in] slotNum : Slot Number - * \param[out] chipIdRes : Chip ID of the device - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_TIMEOUT : Timeout error, no listener device detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalSt25tbPollerSlotMarker(uint8_t slotNum, uint8_t* chipIdRes); - -/*! - ***************************************************************************** - * \brief ST25TB Poller Select - * - * This method sends a ST25TB Select command with the given chip ID. - * - * If the device is already in Selected state and receives an incorrect chip - * ID, it goes into Deselected state - * - * \param[in] chipId : chip ID of the device to be selected - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_TIMEOUT : Timeout error, no listener device detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalSt25tbPollerSelect(uint8_t chipId); - -/*! - ***************************************************************************** - * \brief ST25TB Get UID - * - * This method sends a Get_UID command - * - * If a single device responds the chip UID will be retrieved - * - * \param[out] UID : UID of the found device - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_TIMEOUT : Timeout error, no listener device detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalSt25tbPollerGetUID(rfalSt25tbUID* UID); - -/*! - ***************************************************************************** - * \brief ST25TB Poller Read Block - * - * This method reads a block of the ST25TB - * - * \param[in] blockAddress : address of the block to be read - * \param[out] blockData : location to place the data read from block - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_TIMEOUT : Timeout error, no listener device detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalSt25tbPollerReadBlock(uint8_t blockAddress, rfalSt25tbBlock* blockData); - -/*! - ***************************************************************************** - * \brief ST25TB Poller Write Block - * - * This method writes a block of the ST25TB - * - * \param[in] blockAddress : address of the block to be written - * \param[in] blockData : data to be written on the block - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_TIMEOUT : Timeout error, no listener device detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalSt25tbPollerWriteBlock(uint8_t blockAddress, const rfalSt25tbBlock* blockData); - -/*! - ***************************************************************************** - * \brief ST25TB Poller Completion - * - * This method sends a completion command to the ST25TB. After the - * completion the card no longer will reply to any command. - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_TIMEOUT : Timeout error, no listener device detected - * \return ERR_PROTO : Protocol error detected, invalid SENSB_RES received - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalSt25tbPollerCompletion(void); - -/*! - ***************************************************************************** - * \brief ST25TB Poller Reset to Inventory - * - * This method sends a Reset to Inventory command to the ST25TB. - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_TIMEOUT : Timeout error, no listener device detected - * \return ERR_PROTO : Protocol error detected, invalid SENSB_RES received - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalSt25tbPollerResetToInventory(void); - -#endif /* RFAL_ST25TB_H */ - -/** - * @} - * - * @} - * - * @} - */ diff --git a/lib/ST25RFAL002/include/rfal_st25xv.h b/lib/ST25RFAL002/include/rfal_st25xv.h deleted file mode 100644 index d7bec837795..00000000000 --- a/lib/ST25RFAL002/include/rfal_st25xv.h +++ /dev/null @@ -1,844 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

    © COPYRIGHT 2020 STMicroelectronics

    - * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R391x firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file rfal_st25xv.h - * - * \author Gustavo Patricio - * - * \brief NFC-V ST25 NFC-V Tag specific features - * - * This module provides support for ST's specific features available on - * NFC-V (ISO15693) tag families: ST25D, ST25TV, M24LR - * - * - * \addtogroup RFAL - * @{ - * - * \addtogroup RFAL-AL - * \brief RFAL Abstraction Layer - * @{ - * - * \addtogroup ST25xV - * \brief RFAL ST25xV Module - * @{ - * - */ - -#ifndef RFAL_ST25xV_H -#define RFAL_ST25xV_H - -/* - ****************************************************************************** - * INCLUDES - ****************************************************************************** - */ -#include "platform.h" -#include "st_errno.h" -#include "rfal_nfc.h" -#include "rfal_rf.h" - -/* - ****************************************************************************** - * GLOBAL DEFINES - ****************************************************************************** - */ - -#define RFAL_NFCV_BLOCKNUM_M24LR_LEN \ - 2U /*!< Block Number length of MR24LR tags: 16 bits */ -#define RFAL_NFCV_ST_IC_MFG_CODE \ - 0x02 /*!< ST IC Mfg code (used for custom commands) */ - -/*! - ***************************************************************************** - * \brief NFC-V Poller Read Single Block (M24LR) - * - * Reads a Single Block from a M24LR tag which has the number of blocks - * bigger than 256 (M24LR16 ; M24LR64) - * - * \param[in] flags : Flags to be used: Sub-carrier; Data_rate; Option - * default: RFAL_NFCV_REQ_FLAG_DEFAULT - * \param[in] uid : UID of the device to be put to be read - * if not provided Select mode will be used - * \param[in] blockNum : Number of the block to read (16 bits) - * \param[out] rxBuf : buffer to store response (also with RES_FLAGS) - * \param[in] rxBufLen : length of rxBuf - * \param[out] rcvLen : number of bytes received - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_TIMEOUT : Timeout error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalST25xVPollerM24LRReadSingleBlock( - uint8_t flags, - const uint8_t* uid, - uint16_t blockNum, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rcvLen); - -/*! - ***************************************************************************** - * \brief NFC-V Poller Fast Read Single Block (M24LR) - * - * Reads a Single Block from a M24LR tag which has the number of blocks - * bigger than 256 (M24LR16 ; M24LR64) using ST Fast mode - * - * \param[in] flags : Flags to be used: Sub-carrier; Data_rate; Option - * default: RFAL_NFCV_REQ_FLAG_DEFAULT - * \param[in] uid : UID of the device to be put to be read - * if not provided Select mode will be used - * \param[in] blockNum : Number of the block to read (16 bits) - * \param[out] rxBuf : buffer to store response (also with RES_FLAGS) - * \param[in] rxBufLen : length of rxBuf - * \param[out] rcvLen : number of bytes received - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_TIMEOUT : Timeout error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalST25xVPollerM24LRFastReadSingleBlock( - uint8_t flags, - const uint8_t* uid, - uint16_t blockNum, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rcvLen); - -/*! - ***************************************************************************** - * \brief NFC-V Poller Write Single Block (M24LR) - * - * Writes a Single Block from a M24LR tag which has the number of blocks - * bigger than 256 (M24LR16 ; M24LR64) - * - * \param[in] flags : Flags to be used: Sub-carrier; Data_rate; Option - * for NFC-Forum use: RFAL_NFCV_REQ_FLAG_DEFAULT - * \param[in] uid : UID of the device to be put to be written - * if not provided Select mode will be used - * \param[in] blockNum : Number of the block to write (16 bits) - * \param[in] wrData : data to be written on the given block - * \param[in] blockLen : number of bytes of a block - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_TIMEOUT : Timeout error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalST25xVPollerM24LRWriteSingleBlock( - uint8_t flags, - const uint8_t* uid, - uint16_t blockNum, - const uint8_t* wrData, - uint8_t blockLen); - -/*! - ***************************************************************************** - * \brief NFC-V Poller Read Multiple Blocks (M24LR) - * - * Reads Multiple Blocks from a device from a M24LR tag which has the number of blocks - * bigger than 256 (M24LR16 ; M24LR64) - * - * \param[in] flags : Flags to be used: Sub-carrier; Data_rate; Option - * for NFC-Forum use: RFAL_NFCV_REQ_FLAG_DEFAULT - * \param[in] uid : UID of the device to be put to be read - * if not provided Select mode will be used - * \param[in] firstBlockNum : first block to be read (16 bits) - * \param[in] numOfBlocks : number of block to read - * \param[out] rxBuf : buffer to store response (also with RES_FLAGS) - * \param[in] rxBufLen : length of rxBuf - * \param[out] rcvLen : number of bytes received - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_TIMEOUT : Timeout error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalST25xVPollerM24LRReadMultipleBlocks( - uint8_t flags, - const uint8_t* uid, - uint16_t firstBlockNum, - uint8_t numOfBlocks, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rcvLen); - -/*! - ***************************************************************************** - * \brief NFC-V Poller Fast Read Multiple Blocks (M24LR) - * - * Reads Multiple Blocks from a device from a M24LR tag which has the number of blocks - * bigger than 256 (M24LR16 ; M24LR64) using ST Fast mode - * - * \param[in] flags : Flags to be used: Sub-carrier; Data_rate; Option - * for NFC-Forum use: RFAL_NFCV_REQ_FLAG_DEFAULT - * \param[in] uid : UID of the device to be put to be read - * if not provided Select mode will be used - * \param[in] firstBlockNum : first block to be read (16 bits) - * \param[in] numOfBlocks : number of block to read - * \param[out] rxBuf : buffer to store response (also with RES_FLAGS) - * \param[in] rxBufLen : length of rxBuf - * \param[out] rcvLen : number of bytes received - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_TIMEOUT : Timeout error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalST25xVPollerM24LRFastReadMultipleBlocks( - uint8_t flags, - const uint8_t* uid, - uint16_t firstBlockNum, - uint8_t numOfBlocks, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rcvLen); - -/*! - ***************************************************************************** - * \brief NFC-V Poller Fast Read Single Block - * - * Reads a Single Block from a device (VICC) using ST Fast mode - * - * \param[in] flags : Flags to be used: Sub-carrier; Data_rate; Option - * for NFC-Forum use: RFAL_NFCV_REQ_FLAG_DEFAULT - * \param[in] uid : UID of the device to be put to be read - * if not provided Select mode will be used - * \param[in] blockNum : Number of the block to read - * \param[out] rxBuf : buffer to store response (also with RES_FLAGS) - * \param[in] rxBufLen : length of rxBuf - * \param[out] rcvLen : number of bytes received - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_TIMEOUT : Timeout error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalST25xVPollerFastReadSingleBlock( - uint8_t flags, - const uint8_t* uid, - uint8_t blockNum, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rcvLen); - -/*! - ***************************************************************************** - * \brief NFC-V Poller Fast Read Multiple Blocks - * - * Reads Multiple Blocks from a device (VICC) using ST Fast mode - * - * \param[in] flags : Flags to be used: Sub-carrier; Data_rate; Option - * for NFC-Forum use: RFAL_NFCV_REQ_FLAG_DEFAULT - * \param[in] uid : UID of the device to be put to be read - * if not provided Select mode will be used - * \param[in] firstBlockNum : first block to be read - * \param[in] numOfBlocks : number of block to read - * \param[out] rxBuf : buffer to store response (also with RES_FLAGS) - * \param[in] rxBufLen : length of rxBuf - * \param[out] rcvLen : number of bytes received - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_TIMEOUT : Timeout error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalST25xVPollerFastReadMultipleBlocks( - uint8_t flags, - const uint8_t* uid, - uint8_t firstBlockNum, - uint8_t numOfBlocks, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rcvLen); - -/*! - ***************************************************************************** - * \brief NFC-V Poller Fast Extended Read Single Block - * - * Reads a Single Block from a device (VICC) supporting extended commands using ST Fast mode - * - * \param[in] flags : Flags to be used: Sub-carrier; Data_rate; Option - * for NFC-Forum use: RFAL_NFCV_REQ_FLAG_DEFAULT - * \param[in] uid : UID of the device to be put to be read - * if not provided Select mode will be used - * \param[in] blockNum : Number of the block to read (16 bits) - * \param[out] rxBuf : buffer to store response (also with RES_FLAGS) - * \param[in] rxBufLen : length of rxBuf - * \param[out] rcvLen : number of bytes received - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_TIMEOUT : Timeout error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalST25xVPollerFastExtendedReadSingleBlock( - uint8_t flags, - const uint8_t* uid, - uint16_t blockNum, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rcvLen); - -/*! - ***************************************************************************** - * \brief NFC-V Poller Fast Extended Read Multiple Blocks - * - * Reads Multiple Blocks from a device (VICC) supporting extended commands using ST Fast mode - * - * \param[in] flags : Flags to be used: Sub-carrier; Data_rate; Option - * for NFC-Forum use: RFAL_NFCV_REQ_FLAG_DEFAULT - * \param[in] uid : UID of the device to be put to be read - * if not provided Select mode will be used - * \param[in] firstBlockNum : first block to be read (16 bits) - * \param[in] numOfBlocks : number of consecutive blocks to read (16 bits) - * \param[out] rxBuf : buffer to store response (also with RES_FLAGS) - * \param[in] rxBufLen : length of rxBuf - * \param[out] rcvLen : number of bytes received - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_TIMEOUT : Timeout error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalST25xVPollerFastExtReadMultipleBlocks( - uint8_t flags, - const uint8_t* uid, - uint16_t firstBlockNum, - uint16_t numOfBlocks, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rcvLen); - -/*! - ***************************************************************************** - * \brief NFC-V Poller Read Configuration - * - * Reads static configuration registers at the Pointer address - * - * \param[in] flags : Flags to be used: Sub-carrier; Data_rate; Option - * for NFC-Forum use: RFAL_NFCV_REQ_FLAG_DEFAULT - * \param[in] uid : UID of the device to be put to be read - * if not provided Select mode will be used - * \param[in] pointer : Pointer address - * \param[out] regValue : Register value - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_TIMEOUT : Timeout error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalST25xVPollerReadConfiguration( - uint8_t flags, - const uint8_t* uid, - uint8_t pointer, - uint8_t* regValue); - -/*! - ***************************************************************************** - * \brief NFC-V Poller Write Configuration - * - * Writes static configuration registers at the Pointer address - * - * \param[in] flags : Flags to be used: Sub-carrier; Data_rate; Option - * for NFC-Forum use: RFAL_NFCV_REQ_FLAG_DEFAULT - * \param[in] uid : UID of the device to be put to be read - * if not provided Select mode will be used - * \param[in] pointer : Pointer address - * \param[in] regValue : Register value - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_TIMEOUT : Timeout error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalST25xVPollerWriteConfiguration( - uint8_t flags, - const uint8_t* uid, - uint8_t pointer, - uint8_t regValue); - -/*! - ***************************************************************************** - * \brief NFC-V Poller Read Dynamic Configuration - * - * Reads dynamic registers at the Pointer address - * - * \param[in] flags : Flags to be used: Sub-carrier; Data_rate; Option - * for NFC-Forum use: RFAL_NFCV_REQ_FLAG_DEFAULT - * \param[in] uid : UID of the device to be put to be read - * if not provided Select mode will be used - * \param[in] pointer : Pointer address - * \param[out] regValue : Register value - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_TIMEOUT : Timeout error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalST25xVPollerReadDynamicConfiguration( - uint8_t flags, - const uint8_t* uid, - uint8_t pointer, - uint8_t* regValue); - -/*! - ***************************************************************************** - * \brief NFC-V Poller Write Dynamic Configuration - * - * Writes dynamic registers at the Pointer address - * - * \param[in] flags : Flags to be used: Sub-carrier; Data_rate; Option - * for NFC-Forum use: RFAL_NFCV_REQ_FLAG_DEFAULT - * \param[in] uid : UID of the device to be put to be read - * if not provided Select mode will be used - * \param[in] pointer : Pointer address - * \param[in] regValue : Register value - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_TIMEOUT : Timeout error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalST25xVPollerWriteDynamicConfiguration( - uint8_t flags, - const uint8_t* uid, - uint8_t pointer, - uint8_t regValue); - -/*! - ***************************************************************************** - * \brief NFC-V Poller Fast Read Dynamic Configuration - * - * Reads dynamic registers at the Pointer address using ST Fast mode - * - * \param[in] flags : Flags to be used: Sub-carrier; Data_rate; Option - * for NFC-Forum use: RFAL_NFCV_REQ_FLAG_DEFAULT - * \param[in] uid : UID of the device to be put to be read - * if not provided Select mode will be used - * \param[in] pointer : Pointer address - * \param[out] regValue : Register value - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_TIMEOUT : Timeout error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalST25xVPollerFastReadDynamicConfiguration( - uint8_t flags, - const uint8_t* uid, - uint8_t pointer, - uint8_t* regValue); - -/*! - ***************************************************************************** - * \brief NFC-V Poller Fast Write Dynamic Configuration - * - * Writes dynamic registers at the Pointer address using ST Fast mode - * - * \param[in] flags : Flags to be used: Sub-carrier; Data_rate; Option - * for NFC-Forum use: RFAL_NFCV_REQ_FLAG_DEFAULT - * \param[in] uid : UID of the device to be put to be read - * if not provided Select mode will be used - * \param[in] pointer : Pointer address - * \param[in] regValue : Register value - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_TIMEOUT : Timeout error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalST25xVPollerFastWriteDynamicConfiguration( - uint8_t flags, - const uint8_t* uid, - uint8_t pointer, - uint8_t regValue); - -/*! - ***************************************************************************** - * \brief NFC-V Poller Present Password - * - * Sends the Present Password command - * - * \param[in] flags : Flags to be used: Sub-carrier; Data_rate; Option - * for NFC-Forum use: RFAL_NFCV_REQ_FLAG_DEFAULT - * \param[in] uid : UID of the device to be put to be read - * if not provided Select mode will be used - * \param[in] pwdNum : Password number - * \param[in] pwd : Password - * \param[in] pwdLen : Password length - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_TIMEOUT : Timeout error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalST25xVPollerPresentPassword( - uint8_t flags, - const uint8_t* uid, - uint8_t pwdNum, - const uint8_t* pwd, - uint8_t pwdLen); - -/*! - ***************************************************************************** - * \brief NFC-V Poller Get Random Number - * - * Returns a 16 bit random number - * - * \param[in] flags : Flags to be used: Sub-carrier; Data_rate; Option - * for NFC-Forum use: RFAL_NFCV_REQ_FLAG_DEFAULT - * \param[in] uid : UID of the device to be put to be read - * if not provided Select mode will be used - * \param[out] rxBuf : buffer to store response (also with RES_FLAGS) - * \param[in] rxBufLen : length of rxBuf - * \param[out] rcvLen : number of bytes received - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_TIMEOUT : Timeout error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalST25xVPollerGetRandomNumber( - uint8_t flags, - const uint8_t* uid, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rcvLen); - -/*! - ***************************************************************************** - * \brief NFC-V Poller Read Message length - * - * Sends a Read Message Length message to retrieve the value of MB_LEN_Dyn - * - * \param[in] flags : Flags to be used: Sub-carrier; Data_rate; Option - * for NFC-Forum use: RFAL_NFCV_REQ_FLAG_DEFAULT - * \param[in] uid : UID of the device to be put to be read - * if not provided Select mode will be used - * \param[out] msgLen : Message Length - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_TIMEOUT : Timeout error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalST25xVPollerReadMessageLength(uint8_t flags, const uint8_t* uid, uint8_t* msgLen); - -/*! - ***************************************************************************** - * \brief NFC-V Poller Fast Read Message length - * - * Sends a Fast Read Message Length message to retrieve the value of MB_LEN_Dyn using ST Fast mode. - * - * \param[in] flags : Flags to be used: Sub-carrier; Data_rate; Option - * for NFC-Forum use: RFAL_NFCV_REQ_FLAG_DEFAULT - * \param[in] uid : UID of the device to be put to be read - * if not provided Select mode will be used - * \param[out] msgLen : Message Length - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_TIMEOUT : Timeout error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalST25xVPollerFastReadMsgLength(uint8_t flags, const uint8_t* uid, uint8_t* msgLen); - -/*! - ***************************************************************************** - * \brief NFC-V Poller Read Message - * - * Reads up to 256 bytes in the Mailbox from the location - * specified by MBpointer and sends back their value in the rxBuf response. - * First MailBox location is '00'. When Number of bytes is set to 00h - * and MBPointer is equals to 00h, the MB_LEN bytes of the full message - * are returned. Otherwise, Read Message command returns (Number of Bytes + 1) bytes - * (i.e. 01h returns 2 bytes, FFh returns 256 bytes). - * An error is reported if (Pointer + Nb of bytes + 1) is greater than the message length. - * RF Reading of the last byte of the mailbox message automatically clears b1 - * of MB_CTRL_Dyn HOST_PUT_MSG, and allows RF to put a new message. - * - * \param[in] flags : Flags to be used: Sub-carrier; Data_rate; Option - * for NFC-Forum use: RFAL_NFCV_REQ_FLAG_DEFAULT - * \param[in] uid : UID of the device to be put to be read - * if not provided Select mode will be used - * \param[in] mbPointer : MPpointer - * \param[in] numBytes : number of bytes - * \param[out] rxBuf : buffer to store response (also with RES_FLAGS) - * \param[in] rxBufLen : length of rxBuf - * \param[out] rcvLen : number of bytes received - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_TIMEOUT : Timeout error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalST25xVPollerReadMessage( - uint8_t flags, - const uint8_t* uid, - uint8_t mbPointer, - uint8_t numBytes, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rcvLen); - -/*! - ***************************************************************************** - * \brief NFC-V Poller Fast Read Message - * - * Reads up to 256 bytes in the Mailbox from the location - * specified by MBpointer and sends back their value in the rxBuf response using ST Fast mode. - * First MailBox location is '00'. When Number of bytes is set to 00h - * and MBPointer is equals to 00h, the MB_LEN bytes of the full message - * are returned. Otherwise, Read Message command returns (Number of Bytes + 1) bytes - * (i.e. 01h returns 2 bytes, FFh returns 256 bytes). - * An error is reported if (Pointer + Nb of bytes + 1) is greater than the message length. - * RF Reading of the last byte of the mailbox message automatically clears b1 - * of MB_CTRL_Dyn HOST_PUT_MSG, and allows RF to put a new message. - * - * \param[in] flags : Flags to be used: Sub-carrier; Data_rate; Option - * for NFC-Forum use: RFAL_NFCV_REQ_FLAG_DEFAULT - * \param[in] uid : UID of the device to be put to be read - * if not provided Select mode will be used - * \param[in] mbPointer : MPpointer - * \param[in] numBytes : number of bytes - * \param[out] rxBuf : buffer to store response (also with RES_FLAGS) - * \param[in] rxBufLen : length of rxBuf - * \param[out] rcvLen : number of bytes received - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_TIMEOUT : Timeout error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalST25xVPollerFastReadMessage( - uint8_t flags, - const uint8_t* uid, - uint8_t mbPointer, - uint8_t numBytes, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rcvLen); - -/*! - ***************************************************************************** - * \brief NFC-V Poller Write Message - * - * Sends Write message Command - * - * On receiving the Write Message command, the ST25DVxxx puts the data contained - * in the request into the Mailbox buffer, update the MB_LEN_Dyn register, and - * set bit RF_PUT_MSG in MB_CTRL_Dyn register. It then reports if the write operation was successful - * in the response. The ST25DVxxx Mailbox contains up to 256 data bytes which are filled from the - * first location '00'. MSGlength parameter of the command is the number of - * Data bytes minus 1 (00 for 1 byte of data, FFh for 256 bytes of data). - * Write Message could be executed only when Mailbox is accessible by RF. - * - * \param[in] flags : Flags to be used: Sub-carrier; Data_rate; Option - * for NFC-Forum use: RFAL_NFCV_REQ_FLAG_DEFAULT - * \param[in] uid : UID of the device to be put to be read - * if not provided Select mode will be used - * \param[in] msgLen : MSGLen number of Data bytes minus 1 - * \param[in] msgData : Message Data - * \param[out] txBuf : buffer to used to build the Write Message command - * \param[in] txBufLen : length of txBuf - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_TIMEOUT : Timeout error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalST25xVPollerWriteMessage( - uint8_t flags, - const uint8_t* uid, - uint8_t msgLen, - const uint8_t* msgData, - uint8_t* txBuf, - uint16_t txBufLen); - -/*! - ***************************************************************************** - * \brief NFC-V Poller Fast Write Message - * - * Sends Fast Write message Command using ST Fast mode - * - * On receiving the Write Message command, the ST25DVxxx puts the data contained - * in the request into the Mailbox buffer, update the MB_LEN_Dyn register, and - * set bit RF_PUT_MSG in MB_CTRL_Dyn register. It then reports if the write operation was successful - * in the response. The ST25DVxxx Mailbox contains up to 256 data bytes which are filled from the - * first location '00'. MSGlength parameter of the command is the number of - * Data bytes minus 1 (00 for 1 byte of data, FFh for 256 bytes of data). - * Write Message could be executed only when Mailbox is accessible by RF. - * - * \param[in] flags : Flags to be used: Sub-carrier; Data_rate; Option - * for NFC-Forum use: RFAL_NFCV_REQ_FLAG_DEFAULT - * \param[in] uid : UID of the device to be put to be read - * if not provided Select mode will be used - * \param[in] msgLen : MSGLen number of Data bytes minus 1 - * \param[in] msgData : Message Data - * \param[out] txBuf : buffer to used to build the Write Message command - * \param[in] txBufLen : length of txBuf - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_TIMEOUT : Timeout error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalST25xVPollerFastWriteMessage( - uint8_t flags, - const uint8_t* uid, - uint8_t msgLen, - const uint8_t* msgData, - uint8_t* txBuf, - uint16_t txBufLen); - -#endif /* RFAL_ST25xV_H */ - -/** - * @} - * - * @} - * - * @} - */ diff --git a/lib/ST25RFAL002/include/rfal_t1t.h b/lib/ST25RFAL002/include/rfal_t1t.h deleted file mode 100644 index e9e39d23c2c..00000000000 --- a/lib/ST25RFAL002/include/rfal_t1t.h +++ /dev/null @@ -1,178 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

    © COPYRIGHT 2020 STMicroelectronics

    - * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R391x firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file rfal_t1t.h - * - * \author Gustavo Patricio - * - * \brief Provides NFC-A T1T convenience methods and definitions - * - * This module provides an interface to perform as a NFC-A Reader/Writer - * to handle a Type 1 Tag T1T (Topaz) - * - * - * \addtogroup RFAL - * @{ - * - * \addtogroup RFAL-AL - * \brief RFAL Abstraction Layer - * @{ - * - * \addtogroup T1T - * \brief RFAL T1T Module - * @{ - * - */ - -#ifndef RFAL_T1T_H -#define RFAL_T1T_H - -/* - ****************************************************************************** - * INCLUDES - ****************************************************************************** - */ -#include "platform.h" -#include "st_errno.h" -#include "rfal_rf.h" - -/* - ****************************************************************************** - * GLOBAL DEFINES - ****************************************************************************** - */ -#define RFAL_T1T_UID_LEN 4 /*!< T1T UID length of cascade level 1 only tag */ -#define RFAL_T1T_HR_LENGTH 2 /*!< T1T HR(Header ROM) length */ - -#define RFAL_T1T_HR0_NDEF_MASK 0xF0 /*!< T1T HR0 NDEF capability mask T1T 1.2 2.2.2 */ -#define RFAL_T1T_HR0_NDEF_SUPPORT 0x10 /*!< T1T HR0 NDEF capable value T1T 1.2 2.2.2 */ - -/*! NFC-A T1T (Topaz) command set */ -typedef enum { - RFAL_T1T_CMD_RID = 0x78, /*!< T1T Read UID */ - RFAL_T1T_CMD_RALL = 0x00, /*!< T1T Read All */ - RFAL_T1T_CMD_READ = 0x01, /*!< T1T Read */ - RFAL_T1T_CMD_WRITE_E = 0x53, /*!< T1T Write with erase (single byte) */ - RFAL_T1T_CMD_WRITE_NE = 0x1A /*!< T1T Write with no erase (single byte) */ -} rfalT1Tcmds; - -/* -****************************************************************************** -* GLOBAL TYPES -****************************************************************************** -*/ - -/*! NFC-A T1T (Topaz) RID_RES Digital 1.1 10.6.2 & Table 50 */ -typedef struct { - uint8_t hr0; /*!< T1T Header ROM: HR0 */ - uint8_t hr1; /*!< T1T Header ROM: HR1 */ - uint8_t uid[RFAL_T1T_UID_LEN]; /*!< T1T UID */ -} rfalT1TRidRes; - -/* -****************************************************************************** -* GLOBAL FUNCTION PROTOTYPES -****************************************************************************** -*/ - -/*! - ***************************************************************************** - * \brief Initialize NFC-A T1T Poller mode - * - * This methods configures RFAL RF layer to perform as a - * NFC-A T1T Poller/RW (Topaz) including all default timings - * - * \return ERR_WRONG_STATE : RFAL not initialized or mode not set - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalT1TPollerInitialize(void); - -/*! - ***************************************************************************** - * \brief NFC-A T1T Poller RID - * - * This method reads the UID of a NFC-A T1T Listener device - * - * - * \param[out] ridRes : pointer to place the RID_RES - * - * \return ERR_WRONG_STATE : RFAL not initialized or mode not set - * \return ERR_PARAM : Invalid parameter - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalT1TPollerRid(rfalT1TRidRes* ridRes); - -/*! - ***************************************************************************** - * \brief NFC-A T1T Poller RALL - * - * This method send a Read All command to a NFC-A T1T Listener device - * - * - * \param[in] uid : the UID of the device to read data - * \param[out] rxBuf : pointer to place the read data - * \param[in] rxBufLen : size of rxBuf - * \param[out] rxRcvdLen : actual received data - * - * \return ERR_WRONG_STATE : RFAL not initialized or mode not set - * \return ERR_PARAM : Invalid parameter - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode - rfalT1TPollerRall(const uint8_t* uid, uint8_t* rxBuf, uint16_t rxBufLen, uint16_t* rxRcvdLen); - -/*! - ***************************************************************************** - * \brief NFC-A T1T Poller Write - * - * This method writes the given data on the address of a NFC-A T1T Listener device - * - * - * \param[in] uid : the UID of the device to read data - * \param[in] address : address to write the data - * \param[in] data : the data to be written - * - * \return ERR_WRONG_STATE : RFAL not initialized or mode not set - * \return ERR_PARAM : Invalid parameter - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalT1TPollerWrite(const uint8_t* uid, uint8_t address, uint8_t data); - -#endif /* RFAL_T1T_H */ - -/** - * @} - * - * @} - * - * @} - */ diff --git a/lib/ST25RFAL002/include/rfal_t2t.h b/lib/ST25RFAL002/include/rfal_t2t.h deleted file mode 100644 index b90462fd2c4..00000000000 --- a/lib/ST25RFAL002/include/rfal_t2t.h +++ /dev/null @@ -1,150 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

    © COPYRIGHT 2020 STMicroelectronics

    - * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R391x firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file rfal_t2t.h - * - * \author Gustavo Patricio - * - * \brief Provides NFC-A T2T convenience methods and definitions - * - * This module provides an interface to perform as a NFC-A Reader/Writer - * to handle a Type 2 Tag T2T - * - * - * \addtogroup RFAL - * @{ - * - * \addtogroup RFAL-AL - * \brief RFAL Abstraction Layer - * @{ - * - * \addtogroup T2T - * \brief RFAL T2T Module - * @{ - * - */ - -#ifndef RFAL_T2T_H -#define RFAL_T2T_H - -/* - ****************************************************************************** - * INCLUDES - ****************************************************************************** - */ -#include "platform.h" -#include "st_errno.h" -#include "rfal_rf.h" - -/* - ****************************************************************************** - * GLOBAL DEFINES - ****************************************************************************** - */ - -#define RFAL_T2T_BLOCK_LEN 4U /*!< T2T block length */ -#define RFAL_T2T_READ_DATA_LEN (4U * RFAL_T2T_BLOCK_LEN) /*!< T2T READ data length */ -#define RFAL_T2T_WRITE_DATA_LEN RFAL_T2T_BLOCK_LEN /*!< T2T WRITE data length */ - -/* -****************************************************************************** -* GLOBAL TYPES -****************************************************************************** -*/ - -/* -****************************************************************************** -* GLOBAL FUNCTION PROTOTYPES -****************************************************************************** -*/ - -/*! - ***************************************************************************** - * \brief NFC-A T2T Poller Read - * - * This method sends a Read command to a NFC-A T2T Listener device - * - * - * \param[in] blockNum : Number of the block to read - * \param[out] rxBuf : pointer to place the read data - * \param[in] rxBufLen : size of rxBuf (RFAL_T2T_READ_DATA_LEN) - * \param[out] rcvLen : actual received data - * - * \return ERR_WRONG_STATE : RFAL not initialized or mode not set - * \return ERR_PARAM : Invalid parameter - * \return ERR_PROTO : Protocol error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode - rfalT2TPollerRead(uint8_t blockNum, uint8_t* rxBuf, uint16_t rxBufLen, uint16_t* rcvLen); - -/*! - ***************************************************************************** - * \brief NFC-A T2T Poller Write - * - * This method sends a Write command to a NFC-A T2T Listener device - * - * - * \param[in] blockNum : Number of the block to write - * \param[in] wrData : data to be written on the given block - * size must be of RFAL_T2T_WRITE_DATA_LEN - * - * \return ERR_WRONG_STATE : RFAL not initialized or mode not set - * \return ERR_PARAM : Invalid parameter - * \return ERR_PROTO : Protocol error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalT2TPollerWrite(uint8_t blockNum, const uint8_t* wrData); - -/*! - ***************************************************************************** - * \brief NFC-A T2T Poller Sector Select - * - * This method sends a Sector Select commands to a NFC-A T2T Listener device - * - * \param[in] sectorNum : Sector Number - * - * \return ERR_WRONG_STATE : RFAL not initialized or mode not set - * \return ERR_PARAM : Invalid parameter - * \return ERR_PROTO : Protocol error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalT2TPollerSectorSelect(uint8_t sectorNum); - -#endif /* RFAL_T2T_H */ - -/** - * @} - * - * @} - * - * @} - */ diff --git a/lib/ST25RFAL002/include/rfal_t4t.h b/lib/ST25RFAL002/include/rfal_t4t.h deleted file mode 100644 index 454cad8e004..00000000000 --- a/lib/ST25RFAL002/include/rfal_t4t.h +++ /dev/null @@ -1,395 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

    © COPYRIGHT 2020 STMicroelectronics

    - * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R391x firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file rfal_t4t.h - * - * \author Gustavo Patricio - * - * \brief Provides convenience methods and definitions for T4T (ISO7816-4) - * - * This module provides an interface to exchange T4T APDUs according to - * NFC Forum T4T and ISO7816-4 - * - * This implementation was based on the following specs: - * - ISO/IEC 7816-4 3rd Edition 2013-04-15 - * - NFC Forum T4T Technical Specification 1.0 2017-08-28 - * - * \addtogroup RFAL - * @{ - * - * \addtogroup RFAL-AL - * \brief RFAL Abstraction Layer - * @{ - * - * \addtogroup T4T - * \brief RFAL T4T Module - * @{ - * - */ - -#ifndef RFAL_T4T_H -#define RFAL_T4T_H - -/* - ****************************************************************************** - * INCLUDES - ****************************************************************************** - */ -#include "platform.h" -#include "st_errno.h" -#include "rfal_rf.h" -#include "rfal_isoDep.h" - -/* - ****************************************************************************** - * GLOBAL DEFINES - ****************************************************************************** - */ - -#define RFAL_T4T_MAX_CAPDU_PROLOGUE_LEN \ - 4U /*!< Command-APDU prologue length (CLA INS P1 P2) */ -#define RFAL_T4T_LE_LEN 1U /*!< Le Expected Response Length (short field coding) */ -#define RFAL_T4T_LC_LEN 1U /*!< Lc Data field length (short field coding) */ -#define RFAL_T4T_MAX_RAPDU_SW1SW2_LEN \ - 2U /*!< SW1 SW2 length */ -#define RFAL_T4T_CLA 0x00U /*!< Class byte (contains 00h because secure message are not used) */ - -#define RFAL_T4T_ISO7816_P1_SELECT_BY_DF_NAME \ - 0x04U /*!< P1 value for Select by name */ -#define RFAL_T4T_ISO7816_P1_SELECT_BY_FILEID \ - 0x00U /*!< P1 value for Select by file identifier */ -#define RFAL_T4T_ISO7816_P2_SELECT_FIRST_OR_ONLY_OCCURENCE \ - 0x00U /*!< b2b1 P2 value for First or only occurrence */ -#define RFAL_T4T_ISO7816_P2_SELECT_RETURN_FCI_TEMPLATE \ - 0x00U /*!< b4b3 P2 value for Return FCI template */ -#define RFAL_T4T_ISO7816_P2_SELECT_NO_RESPONSE_DATA \ - 0x0CU /*!< b4b3 P2 value for No response data */ - -#define RFAL_T4T_ISO7816_STATUS_COMPLETE \ - 0x9000U /*!< Command completed \ Normal processing - No further qualification*/ - -/* -****************************************************************************** -* GLOBAL VARIABLES -****************************************************************************** -*/ - -/* -****************************************************************************** -* GLOBAL TYPES -****************************************************************************** -*/ -/*! NFC-A T4T Command-APDU structure */ -typedef struct { - uint8_t CLA; /*!< Class byte */ - uint8_t INS; /*!< Instruction byte */ - uint8_t P1; /*!< Parameter byte 1 */ - uint8_t P2; /*!< Parameter byte 2 */ - uint8_t Lc; /*!< Data field length */ - bool LcFlag; /*!< Lc flag (append Lc when true) */ - uint8_t Le; /*!< Expected Response Length */ - bool LeFlag; /*!< Le flag (append Le when true) */ - - rfalIsoDepApduBufFormat* cApduBuf; /*!< Command-APDU buffer (Tx) */ - uint16_t* cApduLen; /*!< Command-APDU Length */ -} rfalT4tCApduParam; - -/*! NFC-A T4T Response-APDU structure */ -typedef struct { - rfalIsoDepApduBufFormat* rApduBuf; /*!< Response-APDU buffer (Rx) */ - uint16_t rcvdLen; /*!< Full response length */ - uint16_t rApduBodyLen; /*!< Response body length */ - uint16_t statusWord; /*!< R-APDU Status Word SW1|SW2 */ -} rfalT4tRApduParam; - -/*! NFC-A T4T command set T4T 1.0 & ISO7816-4 2013 Table 4 */ -typedef enum { - RFAL_T4T_INS_SELECT = 0xA4U, /*!< T4T Select */ - RFAL_T4T_INS_READBINARY = 0xB0U, /*!< T4T ReadBinary */ - RFAL_T4T_INS_UPDATEBINARY = 0xD6U, /*!< T4T UpdateBinay */ - RFAL_T4T_INS_READBINARY_ODO = 0xB1U, /*!< T4T ReadBinary using ODO */ - RFAL_T4T_INS_UPDATEBINARY_ODO = - 0xD7U /*!< T4T UpdateBinay using ODO */ -} rfalT4tCmds; - -/* -****************************************************************************** -* GLOBAL FUNCTION PROTOTYPES -****************************************************************************** -*/ - -/*! - ***************************************************************************** - * \brief T4T Compose APDU - * - * This method computes a C-APDU according to NFC Forum T4T and ISO7816-4. - * - * If C-APDU contains data to be sent, it must be placed inside the buffer - * rfalT4tTxRxApduParam.txRx.cApduBuf.apdu and signaled by Lc - * - * To transceive the formed APDU the ISO-DEP layer shall be used - * - * \see rfalIsoDepStartApduTransceive() - * \see rfalIsoDepGetApduTransceiveStatus() - * \see rfalT4TPollerParseRAPDU() - * - * \warning The ISO-DEP module is used to perform the tranceive. Usually - * activation has been done via ISO-DEP activatiavtion. If not - * please call rfalIsoDepInitialize() before. - * - * \param[in,out] apduParam : APDU parameters - * apduParam.cApduLen will contain the APDU length - * - * \return ERR_PARAM : Invalid parameter - * \return ERR_PROTO : Protocol error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalT4TPollerComposeCAPDU(const rfalT4tCApduParam* apduParam); - -/*! - ***************************************************************************** - * \brief T4T Parse R-APDU - * - * This method parses a R-APDU according to NFC Forum T4T and ISO7816-4. - * It will extract the data length and check if the Status word is expected. - * - * \param[in,out] apduParam : APDU parameters - * apduParam.rApduBodyLen will contain the data length - * apduParam.statusWord will contain the SW1 and SW2 - * - * \return ERR_REQUEST : Status word (SW1 SW2) different from 9000 - * \return ERR_PARAM : Invalid parameter - * \return ERR_PROTO : Protocol error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalT4TPollerParseRAPDU(rfalT4tRApduParam* apduParam); - -/*! - ***************************************************************************** - * \brief T4T Compose Select Application APDU - * - * This method computes a Select Application APDU according to NFC Forum T4T - * - * To transceive the formed APDU the ISO-DEP layer shall be used - * - * \see rfalIsoDepStartApduTransceive() - * \see rfalIsoDepGetApduTransceiveStatus() - * - * \param[out] cApduBuf : buffer where the C-APDU will be placed - * \param[in] aid : Application ID to be used - * \param[in] aidLen : Application ID length - * \param[out] cApduLen : Composed C-APDU length - * - * \return ERR_PARAM : Invalid parameter - * \return ERR_PROTO : Protocol error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalT4TPollerComposeSelectAppl( - rfalIsoDepApduBufFormat* cApduBuf, - const uint8_t* aid, - uint8_t aidLen, - uint16_t* cApduLen); - -/*! - ***************************************************************************** - * \brief T4T Compose Select File APDU - * - * This method computes a Select File APDU according to NFC Forum T4T - * - * To transceive the formed APDU the ISO-DEP layer shall be used - * - * \see rfalIsoDepStartApduTransceive() - * \see rfalIsoDepGetApduTransceiveStatus() - * - * \param[out] cApduBuf : buffer where the C-APDU will be placed - * \param[in] fid : File ID to be used - * \param[in] fidLen : File ID length - * \param[out] cApduLen : Composed C-APDU length - * - * \return ERR_PARAM : Invalid parameter - * \return ERR_PROTO : Protocol error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalT4TPollerComposeSelectFile( - rfalIsoDepApduBufFormat* cApduBuf, - const uint8_t* fid, - uint8_t fidLen, - uint16_t* cApduLen); - -/*! - ***************************************************************************** - * \brief T4T Compose Select File APDU for Mapping Version 1 - * - * This method computes a Select File APDU according to NFC Forum T4TOP_v1.0 - * - * To transceive the formed APDU the ISO-DEP layer shall be used - * - * \see rfalIsoDepStartApduTransceive() - * \see rfalIsoDepGetApduTransceiveStatus() - * - * \param[out] cApduBuf : buffer where the C-APDU will be placed - * \param[in] fid : File ID to be used - * \param[in] fidLen : File ID length - * \param[out] cApduLen : Composed C-APDU length - * - * \return ERR_PARAM : Invalid parameter - * \return ERR_PROTO : Protocol error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalT4TPollerComposeSelectFileV1Mapping( - rfalIsoDepApduBufFormat* cApduBuf, - const uint8_t* fid, - uint8_t fidLen, - uint16_t* cApduLen); - -/*! - ***************************************************************************** - * \brief T4T Compose Read Data APDU - * - * This method computes a Read Data APDU according to NFC Forum T4T - * - * To transceive the formed APDU the ISO-DEP layer shall be used - * - * \see rfalIsoDepStartApduTransceive() - * \see rfalIsoDepGetApduTransceiveStatus() - * - * \param[out] cApduBuf : buffer where the C-APDU will be placed - * \param[in] offset : File offset - * \param[in] expLen : Expected length (Le) - * \param[out] cApduLen : Composed C-APDU length - * - * \return ERR_PARAM : Invalid parameter - * \return ERR_PROTO : Protocol error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalT4TPollerComposeReadData( - rfalIsoDepApduBufFormat* cApduBuf, - uint16_t offset, - uint8_t expLen, - uint16_t* cApduLen); - -/*! - ***************************************************************************** - * \brief T4T Compose Read Data ODO APDU - * - * This method computes a Read Data ODO APDU according to NFC Forum T4T - * - * To transceive the formed APDU the ISO-DEP layer shall be used - * - * \see rfalIsoDepStartApduTransceive() - * \see rfalIsoDepGetApduTransceiveStatus() - * - * \param[out] cApduBuf : buffer where the C-APDU will be placed - * \param[in] offset : File offset - * \param[in] expLen : Expected length (Le) - * \param[out] cApduLen : Composed C-APDU length - * - * \return ERR_PARAM : Invalid parameter - * \return ERR_PROTO : Protocol error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalT4TPollerComposeReadDataODO( - rfalIsoDepApduBufFormat* cApduBuf, - uint32_t offset, - uint8_t expLen, - uint16_t* cApduLen); - -/*! - ***************************************************************************** - * \brief T4T Compose Write Data APDU - * - * This method computes a Write Data APDU according to NFC Forum T4T - * - * To transceive the formed APDU the ISO-DEP layer shall be used - * - * \see rfalIsoDepStartApduTransceive() - * \see rfalIsoDepGetApduTransceiveStatus() - * - * \param[out] cApduBuf : buffer where the C-APDU will be placed - * \param[in] offset : File offset - * \param[in] data : Data to be written - * \param[in] dataLen : Data length to be written (Lc) - * \param[out] cApduLen : Composed C-APDU length - * - * \return ERR_PARAM : Invalid parameter - * \return ERR_PROTO : Protocol error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalT4TPollerComposeWriteData( - rfalIsoDepApduBufFormat* cApduBuf, - uint16_t offset, - const uint8_t* data, - uint8_t dataLen, - uint16_t* cApduLen); - -/*! - ***************************************************************************** - * \brief T4T Compose Write Data ODO APDU - * - * This method computes a Write Data ODO sAPDU according to NFC Forum T4T - * - * To transceive the formed APDU the ISO-DEP layer shall be used - * - * \see rfalIsoDepStartApduTransceive() - * \see rfalIsoDepGetApduTransceiveStatus() - * - * \param[out] cApduBuf : buffer where the C-APDU will be placed - * \param[in] offset : File offset - * \param[in] data : Data to be written - * \param[in] dataLen : Data length to be written (Lc) - * \param[out] cApduLen : Composed C-APDU length - * - * \return ERR_PARAM : Invalid parameter - * \return ERR_PROTO : Protocol error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalT4TPollerComposeWriteDataODO( - rfalIsoDepApduBufFormat* cApduBuf, - uint32_t offset, - const uint8_t* data, - uint8_t dataLen, - uint16_t* cApduLen); - -#endif /* RFAL_T4T_H */ - -/** - * @} - * - * @} - * - * @} - */ diff --git a/lib/ST25RFAL002/platform.c b/lib/ST25RFAL002/platform.c deleted file mode 100644 index 5fe65ef880e..00000000000 --- a/lib/ST25RFAL002/platform.c +++ /dev/null @@ -1,103 +0,0 @@ -#include "platform.h" -#include -#include -#include - -typedef struct { - FuriThread* thread; - volatile PlatformIrqCallback callback; - bool need_spi_lock; -} RfalPlatform; - -static volatile RfalPlatform rfal_platform = { - .thread = NULL, - .callback = NULL, - .need_spi_lock = true, -}; - -void nfc_isr(void* _ctx) { - UNUSED(_ctx); - if(rfal_platform.callback && platformGpioIsHigh(ST25R_INT_PORT, ST25R_INT_PIN)) { - furi_thread_flags_set(furi_thread_get_id(rfal_platform.thread), 0x1); - } -} - -int32_t rfal_platform_irq_thread(void* context) { - UNUSED(context); - - while(1) { - uint32_t flags = furi_thread_flags_wait(0x1, FuriFlagWaitAny, FuriWaitForever); - if(flags & 0x1) { - rfal_platform.callback(); - } - } -} - -void platformEnableIrqCallback() { - furi_hal_gpio_init(&gpio_nfc_irq_rfid_pull, GpioModeInterruptRise, GpioPullDown, GpioSpeedLow); - furi_hal_gpio_enable_int_callback(&gpio_nfc_irq_rfid_pull); -} - -void platformDisableIrqCallback() { - furi_hal_gpio_init(&gpio_nfc_irq_rfid_pull, GpioModeOutputOpenDrain, GpioPullNo, GpioSpeedLow); - furi_hal_gpio_disable_int_callback(&gpio_nfc_irq_rfid_pull); -} - -void platformSetIrqCallback(PlatformIrqCallback callback) { - rfal_platform.callback = callback; - - if(!rfal_platform.thread) { - rfal_platform.thread = - furi_thread_alloc_ex("RfalIrqDriver", 1024, rfal_platform_irq_thread, NULL); - furi_thread_mark_as_service(rfal_platform.thread); - furi_thread_set_priority(rfal_platform.thread, FuriThreadPriorityIsr); - furi_thread_start(rfal_platform.thread); - - furi_hal_gpio_add_int_callback(&gpio_nfc_irq_rfid_pull, nfc_isr, NULL); - // Disable interrupt callback as the pin is shared between 2 apps - // It is enabled in rfalLowPowerModeStop() - furi_hal_gpio_disable_int_callback(&gpio_nfc_irq_rfid_pull); - } -} - -bool platformSpiTxRx(const uint8_t* txBuf, uint8_t* rxBuf, uint16_t len) { - bool ret = false; - if(txBuf && rxBuf) { - ret = - furi_hal_spi_bus_trx(&furi_hal_spi_bus_handle_nfc, (uint8_t*)txBuf, rxBuf, len, 1000); - } else if(txBuf) { - ret = furi_hal_spi_bus_tx(&furi_hal_spi_bus_handle_nfc, (uint8_t*)txBuf, len, 1000); - } else if(rxBuf) { - ret = furi_hal_spi_bus_rx(&furi_hal_spi_bus_handle_nfc, (uint8_t*)rxBuf, len, 1000); - } - - return ret; -} - -// Until we completely remove RFAL, NFC works with SPI from rfal_platform_irq_thread and nfc_worker -// threads. Some nfc features already stop using RFAL and work with SPI from nfc_worker only. -// rfal_platform_spi_acquire() and rfal_platform_spi_release() functions are used to lock SPI for a -// long term without locking it for each SPI transaction. This is needed for time critical communications. -void rfal_platform_spi_acquire() { - platformDisableIrqCallback(); - rfal_platform.need_spi_lock = false; - furi_hal_spi_acquire(&furi_hal_spi_bus_handle_nfc); -} - -void rfal_platform_spi_release() { - furi_hal_spi_release(&furi_hal_spi_bus_handle_nfc); - rfal_platform.need_spi_lock = true; - platformEnableIrqCallback(); -} - -void platformProtectST25RComm() { - if(rfal_platform.need_spi_lock) { - furi_hal_spi_acquire(&furi_hal_spi_bus_handle_nfc); - } -} - -void platformUnprotectST25RComm() { - if(rfal_platform.need_spi_lock) { - furi_hal_spi_release(&furi_hal_spi_bus_handle_nfc); - } -} diff --git a/lib/ST25RFAL002/platform.h b/lib/ST25RFAL002/platform.h deleted file mode 100644 index d86bba73e36..00000000000 --- a/lib/ST25RFAL002/platform.h +++ /dev/null @@ -1,188 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include "timer.h" -#include "math.h" -#include -#include -#include - -typedef void (*PlatformIrqCallback)(); -void platformSetIrqCallback(PlatformIrqCallback cb); -void platformEnableIrqCallback(); -void platformDisableIrqCallback(); - -bool platformSpiTxRx(const uint8_t* txBuf, uint8_t* rxBuf, uint16_t len); -void platformProtectST25RComm(); -void platformUnprotectST25RComm(); -void rfal_platform_spi_acquire(); -void rfal_platform_spi_release(); - -#define ST25R_SS_PIN NFC_CS_Pin -#define ST25R_SS_PORT NFC_CS_GPIO_Port - -#define ST25R_INT_PIN NFC_IRQ_Pin -#define ST25R_INT_PORT NFC_IRQ_GPIO_Port - -#define RFAL_ANALOG_CONFIG_CUSTOM \ - true /*!< Enable/Disable RFAL custom analog configuration */ - -#define RFAL_FEATURE_LISTEN_MODE \ - true /*!< Enable/Disable RFAL support for Listen Mode */ -#define RFAL_FEATURE_WAKEUP_MODE \ - true /*!< Enable/Disable RFAL support for the Wake-Up mode */ -#define RFAL_FEATURE_LOWPOWER_MODE \ - true /*!< Enable/Disable RFAL support for the Low Power mode */ -#define RFAL_FEATURE_NFCA \ - true /*!< Enable/Disable RFAL support for NFC-A (ISO14443A) */ -#define RFAL_FEATURE_NFCB \ - true /*!< Enable/Disable RFAL support for NFC-B (ISO14443B) */ -#define RFAL_FEATURE_NFCF \ - true /*!< Enable/Disable RFAL support for NFC-F (FeliCa) */ -#define RFAL_FEATURE_NFCV \ - true /*!< Enable/Disable RFAL support for NFC-V (ISO15693) */ -#define RFAL_FEATURE_T1T \ - true /*!< Enable/Disable RFAL support for T1T (Topaz) */ -#define RFAL_FEATURE_T2T \ - true /*!< Enable/Disable RFAL support for T2T (MIFARE Ultralight) */ -#define RFAL_FEATURE_T4T \ - true /*!< Enable/Disable RFAL support for T4T */ -#define RFAL_FEATURE_ST25TB \ - true /*!< Enable/Disable RFAL support for ST25TB */ -#define RFAL_FEATURE_ST25xV \ - true /*!< Enable/Disable RFAL support for ST25TV/ST25DV */ -#define RFAL_FEATURE_DYNAMIC_ANALOG_CONFIG \ - false /*!< Enable/Disable Analog Configs to be dynamically updated (RAM) */ -#define RFAL_FEATURE_DPO \ - false /*!< Enable/Disable RFAL Dynamic Power Output upport */ -#define RFAL_FEATURE_ISO_DEP \ - true /*!< Enable/Disable RFAL support for ISO-DEP (ISO14443-4) */ -#define RFAL_FEATURE_ISO_DEP_POLL \ - true /*!< Enable/Disable RFAL support for Poller mode (PCD) ISO-DEP (ISO14443-4) */ -#define RFAL_FEATURE_ISO_DEP_LISTEN \ - true /*!< Enable/Disable RFAL support for Listen mode (PICC) ISO-DEP (ISO14443-4) */ -#define RFAL_FEATURE_NFC_DEP \ - true /*!< Enable/Disable RFAL support for NFC-DEP (NFCIP1/P2P) */ - -#define RFAL_FEATURE_ISO_DEP_IBLOCK_MAX_LEN \ - 256U /*!< ISO-DEP I-Block max length. Please use values as defined by rfalIsoDepFSx */ -#define RFAL_FEATURE_NFC_DEP_BLOCK_MAX_LEN \ - 254U /*!< NFC-DEP Block/Payload length. Allowed values: 64, 128, 192, 254 */ -#define RFAL_FEATURE_NFC_RF_BUF_LEN \ - 256U /*!< RF buffer length used by RFAL NFC layer */ - -#define RFAL_FEATURE_ISO_DEP_APDU_MAX_LEN \ - 512U /*!< ISO-DEP APDU max length. */ -#define RFAL_FEATURE_NFC_DEP_PDU_MAX_LEN \ - 512U /*!< NFC-DEP PDU max length. */ - -#define platformIrqST25RSetCallback(cb) platformSetIrqCallback(cb) - -#define platformProtectST25RIrqStatus() \ - platformProtectST25RComm() /*!< Protect unique access to IRQ status var - IRQ disable on single thread environment (MCU) ; Mutex lock on a multi thread environment */ -#define platformUnprotectST25RIrqStatus() \ - platformUnprotectST25RComm() /*!< Unprotect the IRQ status var - IRQ enable on a single thread environment (MCU) ; Mutex unlock on a multi thread environment */ - -#define platformGpioSet(port, pin) \ - furi_hal_gpio_write_port_pin( \ - port, pin, true) /*!< Turns the given GPIO High */ -#define platformGpioClear(port, pin) \ - furi_hal_gpio_write_port_pin( \ - port, pin, false) /*!< Turns the given GPIO Low */ - -#define platformGpioIsHigh(port, pin) \ - (furi_hal_gpio_read_port_pin(port, pin) == \ - true) /*!< Checks if the given LED is High */ -#define platformGpioIsLow(port, pin) \ - (!platformGpioIsHigh(port, pin)) /*!< Checks if the given LED is Low */ - -#define platformTimerCreate(t) \ - timerCalculateTimer(t) /*!< Create a timer with the given time (ms) */ -#define platformTimerIsExpired(timer) \ - timerIsExpired(timer) /*!< Checks if the given timer is expired */ -#define platformDelay(t) furi_delay_ms(t) /*!< Performs a delay for the given time (ms) */ - -#define platformGetSysTick() furi_get_tick() /*!< Get System Tick (1 tick = 1 ms) */ - -#define platformAssert(exp) assert_param(exp) /*!< Asserts whether the given expression is true*/ - -#define platformSpiSelect() \ - platformGpioClear( \ - ST25R_SS_PORT, ST25R_SS_PIN) /*!< SPI SS\CS: Chip|Slave Select */ -#define platformSpiDeselect() \ - platformGpioSet( \ - ST25R_SS_PORT, ST25R_SS_PIN) /*!< SPI SS\CS: Chip|Slave Deselect */ - -#define platformI2CTx(txBuf, len, last, txOnly) /*!< I2C Transmit */ -#define platformI2CRx(txBuf, len) /*!< I2C Receive */ -#define platformI2CStart() /*!< I2C Start condition */ -#define platformI2CStop() /*!< I2C Stop condition */ -#define platformI2CRepeatStart() /*!< I2C Repeat Start */ -#define platformI2CSlaveAddrWR(add) /*!< I2C Slave address for Write operation */ -#define platformI2CSlaveAddrRD(add) /*!< I2C Slave address for Read operation */ - -#define platformLog(...) /*!< Log method */ - -/* - ****************************************************************************** - * RFAL OPTIONAL MACROS (Do not change) - ****************************************************************************** - */ -#ifndef platformProtectST25RIrqStatus -#define platformProtectST25RIrqStatus() /*!< Protect unique access to IRQ status var - IRQ disable on single thread environment (MCU) ; Mutex lock on a multi thread environment */ -#endif /* platformProtectST25RIrqStatus */ - -#ifndef platformUnprotectST25RIrqStatus -#define platformUnprotectST25RIrqStatus() /*!< Unprotect the IRQ status var - IRQ enable on a single thread environment (MCU) ; Mutex unlock on a multi thread environment */ -#endif /* platformUnprotectST25RIrqStatus */ - -#ifndef platformProtectWorker -#define platformProtectWorker() /* Protect RFAL Worker/Task/Process from concurrent execution on multi thread platforms */ -#endif /* platformProtectWorker */ - -#ifndef platformUnprotectWorker -#define platformUnprotectWorker() /* Unprotect RFAL Worker/Task/Process from concurrent execution on multi thread platforms */ -#endif /* platformUnprotectWorker */ - -#ifndef platformIrqST25RPinInitialize -#define platformIrqST25RPinInitialize() /*!< Initializes ST25R IRQ pin */ -#endif /* platformIrqST25RPinInitialize */ - -#ifndef platformIrqST25RSetCallback -#define platformIrqST25RSetCallback(cb) /*!< Sets ST25R ISR callback */ -#endif /* platformIrqST25RSetCallback */ - -#ifndef platformLedsInitialize -#define platformLedsInitialize() /*!< Initializes the pins used as LEDs to outputs */ -#endif /* platformLedsInitialize */ - -#ifndef platformLedOff -#define platformLedOff(port, pin) /*!< Turns the given LED Off */ -#endif /* platformLedOff */ - -#ifndef platformLedOn -#define platformLedOn(port, pin) /*!< Turns the given LED On */ -#endif /* platformLedOn */ - -#ifndef platformLedToogle -#define platformLedToogle(port, pin) /*!< Toggles the given LED */ -#endif /* platformLedToogle */ - -#ifndef platformGetSysTick -#define platformGetSysTick() /*!< Get System Tick (1 tick = 1 ms) */ -#endif /* platformGetSysTick */ - -#ifndef platformTimerDestroy -#define platformTimerDestroy(timer) /*!< Stops and released the given timer */ -#endif /* platformTimerDestroy */ - -#ifndef platformAssert -#define platformAssert(exp) /*!< Asserts whether the given expression is true */ -#endif /* platformAssert */ - -#ifndef platformErrorHandle -#define platformErrorHandle() /*!< Global error handler or trap */ -#endif /* platformErrorHandle */ diff --git a/lib/ST25RFAL002/source/custom_analog_config.c b/lib/ST25RFAL002/source/custom_analog_config.c deleted file mode 100644 index 00a54db2605..00000000000 --- a/lib/ST25RFAL002/source/custom_analog_config.c +++ /dev/null @@ -1,777 +0,0 @@ -#include "rfal_analogConfigTbl.h" - -const uint8_t rfalAnalogConfigCustomSettings[] = { - /****** Default Analog Configuration for Chip-Specific Reset ******/ - MODE_ENTRY_16_REG( - (RFAL_ANALOG_CONFIG_TECH_CHIP | RFAL_ANALOG_CONFIG_CHIP_INIT), - ST25R3916_REG_IO_CONF1, - (ST25R3916_REG_IO_CONF1_out_cl_mask | ST25R3916_REG_IO_CONF1_lf_clk_off), - 0x07 /* Disable MCU_CLK */ - , - ST25R3916_REG_IO_CONF2, - (ST25R3916_REG_IO_CONF2_miso_pd1 | ST25R3916_REG_IO_CONF2_miso_pd2), - 0x18 /* SPI Pull downs */ - // , ST25R3916_REG_IO_CONF2, ST25R3916_REG_IO_CONF2_aat_en, ST25R3916_REG_IO_CONF2_aat_en /* Enable AAT */ - , - ST25R3916_REG_TX_DRIVER, - ST25R3916_REG_TX_DRIVER_d_res_mask, - 0x00 /* Set RFO resistance Active Tx */ - , - ST25R3916_REG_RES_AM_MOD, - 0xFF, - 0x80 /* Use minimum non-overlap */ - , - ST25R3916_REG_FIELD_THRESHOLD_ACTV, - ST25R3916_REG_FIELD_THRESHOLD_ACTV_trg_mask, - ST25R3916_REG_FIELD_THRESHOLD_ACTV_trg_105mV /* Lower activation threshold (higher than deactivation)*/ - , - ST25R3916_REG_FIELD_THRESHOLD_ACTV, - ST25R3916_REG_FIELD_THRESHOLD_ACTV_rfe_mask, - ST25R3916_REG_FIELD_THRESHOLD_ACTV_rfe_105mV /* Lower activation threshold (higher than deactivation)*/ - , - ST25R3916_REG_FIELD_THRESHOLD_DEACTV, - ST25R3916_REG_FIELD_THRESHOLD_DEACTV_trg_mask, - ST25R3916_REG_FIELD_THRESHOLD_DEACTV_trg_75mV /* Lower deactivation threshold */ - , - ST25R3916_REG_FIELD_THRESHOLD_DEACTV, - ST25R3916_REG_FIELD_THRESHOLD_DEACTV_rfe_mask, - ST25R3916_REG_FIELD_THRESHOLD_DEACTV_rfe_75mV /* Lower deactivation threshold */ - , - ST25R3916_REG_AUX_MOD, - ST25R3916_REG_AUX_MOD_lm_ext, - ST25R3916_REG_AUX_MOD_lm_ext /* Disable External Load Modulation */ - , - ST25R3916_REG_AUX_MOD, - ST25R3916_REG_AUX_MOD_lm_dri, - ST25R3916_REG_AUX_MOD_lm_dri /* Use internal Load Modulation */ - , - ST25R3916_REG_PASSIVE_TARGET, - ST25R3916_REG_PASSIVE_TARGET_fdel_mask, - (5U - << ST25R3916_REG_PASSIVE_TARGET_fdel_shift) /* Adjust the FDT to be aligned with the bitgrid */ - , - ST25R3916_REG_PT_MOD, - (ST25R3916_REG_PT_MOD_ptm_res_mask | ST25R3916_REG_PT_MOD_pt_res_mask), - 0x0f /* Reduce RFO resistance in Modulated state */ - , - ST25R3916_REG_EMD_SUP_CONF, - ST25R3916_REG_EMD_SUP_CONF_rx_start_emv, - ST25R3916_REG_EMD_SUP_CONF_rx_start_emv_on /* Enable start on first 4 bits */ - , - ST25R3916_REG_ANT_TUNE_A, - 0xFF, - 0x82 /* Set Antenna Tuning (Poller): ANTL */ - , - ST25R3916_REG_ANT_TUNE_B, - 0xFF, - 0x82 /* Set Antenna Tuning (Poller): ANTL */ - , - 0x84U, - 0x10, - 0x10 /* Avoid chip internal overheat protection */ - ) - - /****** Default Analog Configuration for Chip-Specific Poll Common ******/ - , - MODE_ENTRY_9_REG( - (RFAL_ANALOG_CONFIG_TECH_CHIP | RFAL_ANALOG_CONFIG_CHIP_POLL_COMMON), - ST25R3916_REG_MODE, - ST25R3916_REG_MODE_tr_am, - ST25R3916_REG_MODE_tr_am_am /* Use AM modulation */ - , - ST25R3916_REG_TX_DRIVER, - ST25R3916_REG_TX_DRIVER_am_mod_mask, - ST25R3916_REG_TX_DRIVER_am_mod_12percent /* Set Modulation index */ - , - ST25R3916_REG_AUX_MOD, - (ST25R3916_REG_AUX_MOD_dis_reg_am | ST25R3916_REG_AUX_MOD_res_am), - 0x00 /* Use AM via regulator */ - , - ST25R3916_REG_ANT_TUNE_A, - 0xFF, - 0x82 /* Set Antenna Tuning (Poller): ANTL */ - , - ST25R3916_REG_ANT_TUNE_B, - 0xFF, - 0x82 /* Set Antenna Tuning (Poller): ANTL */ - , - ST25R3916_REG_OVERSHOOT_CONF1, - 0xFF, - 0x00 /* Disable Overshoot Protection */ - , - ST25R3916_REG_OVERSHOOT_CONF2, - 0xFF, - 0x00 /* Disable Overshoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF1, - 0xFF, - 0x00 /* Disable Undershoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF2, - 0xFF, - 0x00 /* Disable Undershoot Protection */ - ) - - /****** Default Analog Configuration for Poll NFC-A Rx Common ******/ - , - MODE_ENTRY_1_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCA | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_RX), - ST25R3916_REG_AUX, - ST25R3916_REG_AUX_dis_corr, - ST25R3916_REG_AUX_dis_corr_correlator /* Use Correlator Receiver */ - ) - - /****** Default Analog Configuration for Poll NFC-A Tx 106 ******/ - , - MODE_ENTRY_5_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCA | RFAL_ANALOG_CONFIG_BITRATE_106 | - RFAL_ANALOG_CONFIG_TX), - ST25R3916_REG_MODE, - ST25R3916_REG_MODE_tr_am, - ST25R3916_REG_MODE_tr_am_ook /* Use OOK */ - , - ST25R3916_REG_OVERSHOOT_CONF1, - 0xFF, - 0x40 /* Set default Overshoot Protection */ - , - ST25R3916_REG_OVERSHOOT_CONF2, - 0xFF, - 0x03 /* Set default Overshoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF1, - 0xFF, - 0x40 /* Set default Undershoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF2, - 0xFF, - 0x03 /* Set default Undershoot Protection */ - ) - - /****** Default Analog Configuration for Poll NFC-A Rx 106 ******/ - , - MODE_ENTRY_6_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCA | RFAL_ANALOG_CONFIG_BITRATE_106 | - RFAL_ANALOG_CONFIG_RX), - ST25R3916_REG_RX_CONF1, - 0xFF, - 0x08, - ST25R3916_REG_RX_CONF2, - 0xFF, - 0x2D, - ST25R3916_REG_RX_CONF3, - 0xFF, - 0x00, - ST25R3916_REG_RX_CONF4, - 0xFF, - 0x00, - ST25R3916_REG_CORR_CONF1, - 0xFF, - 0x51, - ST25R3916_REG_CORR_CONF2, - 0xFF, - 0x00) - - /****** Default Analog Configuration for Poll NFC-A Tx 212 ******/ - , - MODE_ENTRY_7_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCA | RFAL_ANALOG_CONFIG_BITRATE_212 | - RFAL_ANALOG_CONFIG_TX), - ST25R3916_REG_MODE, - ST25R3916_REG_MODE_tr_am, - ST25R3916_REG_MODE_tr_am_am /* Use AM modulation */ - , - ST25R3916_REG_AUX_MOD, - (ST25R3916_REG_AUX_MOD_dis_reg_am | ST25R3916_REG_AUX_MOD_res_am), - 0x88 /* Use Resistive AM */ - , - ST25R3916_REG_RES_AM_MOD, - ST25R3916_REG_RES_AM_MOD_md_res_mask, - 0x7F /* Set Resistive modulation */ - , - ST25R3916_REG_OVERSHOOT_CONF1, - 0xFF, - 0x40 /* Set default Overshoot Protection */ - , - ST25R3916_REG_OVERSHOOT_CONF2, - 0xFF, - 0x03 /* Set default Overshoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF1, - 0xFF, - 0x40 /* Set default Undershoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF2, - 0xFF, - 0x03 /* Set default Undershoot Protection */ - ) - - /****** Default Analog Configuration for Poll NFC-A Rx 212 ******/ - , - MODE_ENTRY_6_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCA | RFAL_ANALOG_CONFIG_BITRATE_212 | - RFAL_ANALOG_CONFIG_RX), - ST25R3916_REG_RX_CONF1, - 0xFF, - 0x02, - ST25R3916_REG_RX_CONF2, - 0xFF, - 0x3D, - ST25R3916_REG_RX_CONF3, - 0xFF, - 0x00, - ST25R3916_REG_RX_CONF4, - 0xFF, - 0x00, - ST25R3916_REG_CORR_CONF1, - 0xFF, - 0x14, - ST25R3916_REG_CORR_CONF2, - 0xFF, - 0x00) - - /****** Default Analog Configuration for Poll NFC-A Tx 424 ******/ - , - MODE_ENTRY_7_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCA | RFAL_ANALOG_CONFIG_BITRATE_424 | - RFAL_ANALOG_CONFIG_TX), - ST25R3916_REG_MODE, - ST25R3916_REG_MODE_tr_am, - ST25R3916_REG_MODE_tr_am_am /* Use AM modulation */ - , - ST25R3916_REG_AUX_MOD, - (ST25R3916_REG_AUX_MOD_dis_reg_am | ST25R3916_REG_AUX_MOD_res_am), - 0x88 /* Use Resistive AM */ - , - ST25R3916_REG_RES_AM_MOD, - ST25R3916_REG_RES_AM_MOD_md_res_mask, - 0x7F /* Set Resistive modulation */ - , - ST25R3916_REG_OVERSHOOT_CONF1, - 0xFF, - 0x40 /* Set default Overshoot Protection */ - , - ST25R3916_REG_OVERSHOOT_CONF2, - 0xFF, - 0x03 /* Set default Overshoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF1, - 0xFF, - 0x40 /* Set default Undershoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF2, - 0xFF, - 0x03 /* Set default Undershoot Protection */ - ) - - /****** Default Analog Configuration for Poll NFC-A Rx 424 ******/ - , - MODE_ENTRY_6_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCA | RFAL_ANALOG_CONFIG_BITRATE_424 | - RFAL_ANALOG_CONFIG_RX), - ST25R3916_REG_RX_CONF1, - 0xFF, - 0x42, - ST25R3916_REG_RX_CONF2, - 0xFF, - 0x3D, - ST25R3916_REG_RX_CONF3, - 0xFF, - 0x00, - ST25R3916_REG_RX_CONF4, - 0xFF, - 0x00, - ST25R3916_REG_CORR_CONF1, - 0xFF, - 0x54, - ST25R3916_REG_CORR_CONF2, - 0xFF, - 0x00) - - /****** Default Analog Configuration for Poll NFC-A Tx 848 ******/ - , - MODE_ENTRY_7_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCA | RFAL_ANALOG_CONFIG_BITRATE_848 | - RFAL_ANALOG_CONFIG_TX), - ST25R3916_REG_MODE, - ST25R3916_REG_MODE_tr_am, - ST25R3916_REG_MODE_tr_am_am /* Use AM modulation */ - , - ST25R3916_REG_TX_DRIVER, - ST25R3916_REG_TX_DRIVER_am_mod_mask, - ST25R3916_REG_TX_DRIVER_am_mod_40percent /* Set Modulation index */ - , - ST25R3916_REG_AUX_MOD, - (ST25R3916_REG_AUX_MOD_dis_reg_am | ST25R3916_REG_AUX_MOD_res_am), - 0x00 /* Use AM via regulator */ - , - ST25R3916_REG_OVERSHOOT_CONF1, - 0xFF, - 0x00 /* Disable Overshoot Protection */ - , - ST25R3916_REG_OVERSHOOT_CONF2, - 0xFF, - 0x00 /* Disable Overshoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF1, - 0xFF, - 0x00 /* Disable Undershoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF2, - 0xFF, - 0x00 /* Disable Undershoot Protection */ - ) - - /****** Default Analog Configuration for Poll NFC-A Rx 848 ******/ - , - MODE_ENTRY_6_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCA | RFAL_ANALOG_CONFIG_BITRATE_848 | - RFAL_ANALOG_CONFIG_RX), - ST25R3916_REG_RX_CONF1, - 0xFF, - 0x42, - ST25R3916_REG_RX_CONF2, - 0xFF, - 0x3D, - ST25R3916_REG_RX_CONF3, - 0xFF, - 0x00, - ST25R3916_REG_RX_CONF4, - 0xFF, - 0x00, - ST25R3916_REG_CORR_CONF1, - 0xFF, - 0x44, - ST25R3916_REG_CORR_CONF2, - 0xFF, - 0x00) - - /****** Default Analog Configuration for Poll NFC-A Anticolision setting ******/ - , - MODE_ENTRY_1_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCA | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_ANTICOL), - ST25R3916_REG_CORR_CONF1, - ST25R3916_REG_CORR_CONF1_corr_s6, - 0x00 /* Set collision detection level different from data */ - ) - -#ifdef RFAL_USE_COHE - /****** Default Analog Configuration for Poll NFC-B Rx Common ******/ - , - MODE_ENTRY_1_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCB | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_RX), - ST25R3916_REG_AUX, - ST25R3916_REG_AUX_dis_corr, - ST25R3916_REG_AUX_dis_corr_coherent /* Use Coherent Receiver */ - ) -#else - /****** Default Analog Configuration for Poll NFC-B Rx Common ******/ - , - MODE_ENTRY_1_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCB | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_RX), - ST25R3916_REG_AUX, - ST25R3916_REG_AUX_dis_corr, - ST25R3916_REG_AUX_dis_corr_correlator /* Use Correlator Receiver */ - ) -#endif /*RFAL_USE_COHE*/ - - /****** Default Analog Configuration for Poll NFC-B Rx 106 ******/ - , - MODE_ENTRY_6_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCB | RFAL_ANALOG_CONFIG_BITRATE_106 | - RFAL_ANALOG_CONFIG_RX), - ST25R3916_REG_RX_CONF1, - 0xFF, - 0x04, - ST25R3916_REG_RX_CONF2, - 0xFF, - 0x3D, - ST25R3916_REG_RX_CONF3, - 0xFF, - 0x00, - ST25R3916_REG_RX_CONF4, - 0xFF, - 0x00, - ST25R3916_REG_CORR_CONF1, - 0xFF, - 0x1B, - ST25R3916_REG_CORR_CONF2, - 0xFF, - 0x00) - - /****** Default Analog Configuration for Poll NFC-B Rx 212 ******/ - , - MODE_ENTRY_6_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCB | RFAL_ANALOG_CONFIG_BITRATE_212 | - RFAL_ANALOG_CONFIG_RX), - ST25R3916_REG_RX_CONF1, - 0xFF, - 0x02, - ST25R3916_REG_RX_CONF2, - 0xFF, - 0x3D, - ST25R3916_REG_RX_CONF3, - 0xFF, - 0x00, - ST25R3916_REG_RX_CONF4, - 0xFF, - 0x00, - ST25R3916_REG_CORR_CONF1, - 0xFF, - 0x14, - ST25R3916_REG_CORR_CONF2, - 0xFF, - 0x00) - - /****** Default Analog Configuration for Poll NFC-B Rx 424 ******/ - , - MODE_ENTRY_6_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCB | RFAL_ANALOG_CONFIG_BITRATE_424 | - RFAL_ANALOG_CONFIG_RX), - ST25R3916_REG_RX_CONF1, - 0xFF, - 0x42, - ST25R3916_REG_RX_CONF2, - 0xFF, - 0x3D, - ST25R3916_REG_RX_CONF3, - 0xFF, - 0x00, - ST25R3916_REG_RX_CONF4, - 0xFF, - 0x00, - ST25R3916_REG_CORR_CONF1, - 0xFF, - 0x54, - ST25R3916_REG_CORR_CONF2, - 0xFF, - 0x00) - - /****** Default Analog Configuration for Poll NFC-B Rx 848 ******/ - , - MODE_ENTRY_6_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCB | RFAL_ANALOG_CONFIG_BITRATE_848 | - RFAL_ANALOG_CONFIG_RX), - ST25R3916_REG_RX_CONF1, - 0xFF, - 0x42, - ST25R3916_REG_RX_CONF2, - 0xFF, - 0x3D, - ST25R3916_REG_RX_CONF3, - 0xFF, - 0x00, - ST25R3916_REG_RX_CONF4, - 0xFF, - 0x00, - ST25R3916_REG_CORR_CONF1, - 0xFF, - 0x44, - ST25R3916_REG_CORR_CONF2, - 0xFF, - 0x00) -#ifdef RFAL_USE_COHE - - /****** Default Analog Configuration for Poll NFC-F Rx Common ******/ - , - MODE_ENTRY_7_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCF | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_RX), - ST25R3916_REG_AUX, - ST25R3916_REG_AUX_dis_corr, - ST25R3916_REG_AUX_dis_corr_coherent /* Use Pulse Receiver */ - , - ST25R3916_REG_RX_CONF1, - 0xFF, - 0x13, - ST25R3916_REG_RX_CONF2, - 0xFF, - 0x3D, - ST25R3916_REG_RX_CONF3, - 0xFF, - 0x00, - ST25R3916_REG_RX_CONF4, - 0xFF, - 0x00, - ST25R3916_REG_CORR_CONF1, - 0xFF, - 0x54, - ST25R3916_REG_CORR_CONF2, - 0xFF, - 0x00) -#else - /****** Default Analog Configuration for Poll NFC-F Rx Common ******/ - , - MODE_ENTRY_7_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCF | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_RX), - ST25R3916_REG_AUX, - ST25R3916_REG_AUX_dis_corr, - ST25R3916_REG_AUX_dis_corr_correlator /* Use Correlator Receiver */ - , - ST25R3916_REG_RX_CONF1, - 0xFF, - 0x13, - ST25R3916_REG_RX_CONF2, - 0xFF, - 0x3D, - ST25R3916_REG_RX_CONF3, - 0xFF, - 0x00, - ST25R3916_REG_RX_CONF4, - 0xFF, - 0x00, - ST25R3916_REG_CORR_CONF1, - 0xFF, - 0x54, - ST25R3916_REG_CORR_CONF2, - 0xFF, - 0x00) -#endif /*RFAL_USE_COHE*/ - - , - MODE_ENTRY_1_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCV | RFAL_ANALOG_CONFIG_BITRATE_1OF4 | - RFAL_ANALOG_CONFIG_TX), - ST25R3916_REG_MODE, - ST25R3916_REG_MODE_tr_am, - ST25R3916_REG_MODE_tr_am_ook /* Use OOK */ - ) - -#ifdef RFAL_USE_COHE - /****** Default Analog Configuration for Poll NFC-V Rx Common ******/ - , - MODE_ENTRY_7_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCV | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_RX), - ST25R3916_REG_AUX, - ST25R3916_REG_AUX_dis_corr, - ST25R3916_REG_AUX_dis_corr_coherent /* Use Pulse Receiver */ - , - ST25R3916_REG_RX_CONF1, - 0xFF, - 0x13, - ST25R3916_REG_RX_CONF2, - 0xFF, - 0x2D, - ST25R3916_REG_RX_CONF3, - 0xFF, - 0x00, - ST25R3916_REG_RX_CONF4, - 0xFF, - 0x00, - ST25R3916_REG_CORR_CONF1, - 0xFF, - 0x13, - ST25R3916_REG_CORR_CONF2, - 0xFF, - 0x01) -#else - /****** Default Analog Configuration for Poll NFC-V Rx Common ******/ - , - MODE_ENTRY_7_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCV | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_RX), - ST25R3916_REG_AUX, - ST25R3916_REG_AUX_dis_corr, - ST25R3916_REG_AUX_dis_corr_correlator /* Use Correlator Receiver */ - , - ST25R3916_REG_RX_CONF1, - 0xFF, - 0x13, - ST25R3916_REG_RX_CONF2, - 0xFF, - 0x2D, - ST25R3916_REG_RX_CONF3, - 0xFF, - 0x00, - ST25R3916_REG_RX_CONF4, - 0xFF, - 0x00, - ST25R3916_REG_CORR_CONF1, - 0xFF, - 0x13, - ST25R3916_REG_CORR_CONF2, - 0xFF, - 0x01) -#endif /*RFAL_USE_COHE*/ - - /****** Default Analog Configuration for Poll AP2P Tx 106 ******/ - , - MODE_ENTRY_5_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_AP2P | RFAL_ANALOG_CONFIG_BITRATE_106 | - RFAL_ANALOG_CONFIG_TX), - ST25R3916_REG_MODE, - ST25R3916_REG_MODE_tr_am, - ST25R3916_REG_MODE_tr_am_ook /* Use OOK modulation */ - , - ST25R3916_REG_OVERSHOOT_CONF1, - 0xFF, - 0x40 /* Set default Overshoot Protection */ - , - ST25R3916_REG_OVERSHOOT_CONF2, - 0xFF, - 0x03 /* Set default Overshoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF1, - 0xFF, - 0x40 /* Set default Undershoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF2, - 0xFF, - 0x03 /* Set default Undershoot Protection */ - ) - - /****** Default Analog Configuration for Poll AP2P Tx 212 ******/ - , - MODE_ENTRY_1_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_AP2P | RFAL_ANALOG_CONFIG_BITRATE_212 | - RFAL_ANALOG_CONFIG_TX), - ST25R3916_REG_MODE, - ST25R3916_REG_MODE_tr_am, - ST25R3916_REG_MODE_tr_am_am /* Use AM modulation */ - ) - - /****** Default Analog Configuration for Poll AP2P Tx 424 ******/ - , - MODE_ENTRY_1_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_AP2P | RFAL_ANALOG_CONFIG_BITRATE_424 | - RFAL_ANALOG_CONFIG_TX), - ST25R3916_REG_MODE, - ST25R3916_REG_MODE_tr_am, - ST25R3916_REG_MODE_tr_am_am /* Use AM modulation */ - ) - - /****** Default Analog Configuration for Chip-Specific Listen On ******/ - , - MODE_ENTRY_6_REG( - (RFAL_ANALOG_CONFIG_TECH_CHIP | RFAL_ANALOG_CONFIG_CHIP_LISTEN_ON), - ST25R3916_REG_ANT_TUNE_A, - 0xFF, - 0x00 /* Set Antenna Tuning (Listener): ANTL */ - , - ST25R3916_REG_ANT_TUNE_B, - 0xFF, - 0xff /* Set Antenna Tuning (Listener): ANTL */ - , - ST25R3916_REG_OVERSHOOT_CONF1, - 0xFF, - 0x00 /* Disable Overshoot Protection */ - , - ST25R3916_REG_OVERSHOOT_CONF2, - 0xFF, - 0x00 /* Disable Overshoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF1, - 0xFF, - 0x00 /* Disable Undershoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF2, - 0xFF, - 0x00 /* Disable Undershoot Protection */ - ) - - /****** Default Analog Configuration for Listen AP2P Tx Common ******/ - , - MODE_ENTRY_7_REG( - (RFAL_ANALOG_CONFIG_LISTEN | RFAL_ANALOG_CONFIG_TECH_AP2P | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_TX), - ST25R3916_REG_ANT_TUNE_A, - 0xFF, - 0x82 /* Set Antenna Tuning (Poller): ANTL */ - , - ST25R3916_REG_ANT_TUNE_B, - 0xFF, - 0x82 /* Set Antenna Tuning (Poller): ANTL */ - , - ST25R3916_REG_TX_DRIVER, - ST25R3916_REG_TX_DRIVER_am_mod_mask, - ST25R3916_REG_TX_DRIVER_am_mod_12percent /* Set Modulation index */ - , - ST25R3916_REG_OVERSHOOT_CONF1, - 0xFF, - 0x00 /* Disable Overshoot Protection */ - , - ST25R3916_REG_OVERSHOOT_CONF2, - 0xFF, - 0x00 /* Disable Overshoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF1, - 0xFF, - 0x00 /* Disable Undershoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF2, - 0xFF, - 0x00 /* Disable Undershoot Protection */ - ) - - /****** Default Analog Configuration for Listen AP2P Rx Common ******/ - , - MODE_ENTRY_3_REG( - (RFAL_ANALOG_CONFIG_LISTEN | RFAL_ANALOG_CONFIG_TECH_AP2P | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_RX), - ST25R3916_REG_RX_CONF1, - ST25R3916_REG_RX_CONF1_lp_mask, - ST25R3916_REG_RX_CONF1_lp_1200khz /* Set Rx filter configuration */ - , - ST25R3916_REG_RX_CONF1, - ST25R3916_REG_RX_CONF1_hz_mask, - ST25R3916_REG_RX_CONF1_hz_12_200khz /* Set Rx filter configuration */ - , - ST25R3916_REG_RX_CONF2, - ST25R3916_REG_RX_CONF2_amd_sel, - ST25R3916_REG_RX_CONF2_amd_sel_mixer /* AM demodulator: mixer */ - ) - - /****** Default Analog Configuration for Listen AP2P Tx 106 ******/ - , - MODE_ENTRY_5_REG( - (RFAL_ANALOG_CONFIG_LISTEN | RFAL_ANALOG_CONFIG_TECH_AP2P | - RFAL_ANALOG_CONFIG_BITRATE_106 | RFAL_ANALOG_CONFIG_TX), - ST25R3916_REG_MODE, - ST25R3916_REG_MODE_tr_am, - ST25R3916_REG_MODE_tr_am_ook /* Use OOK modulation */ - , - ST25R3916_REG_OVERSHOOT_CONF1, - 0xFF, - 0x40 /* Set default Overshoot Protection */ - , - ST25R3916_REG_OVERSHOOT_CONF2, - 0xFF, - 0x03 /* Set default Overshoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF1, - 0xFF, - 0x40 /* Set default Undershoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF2, - 0xFF, - 0x03 /* Set default Undershoot Protection */ - ) - - /****** Default Analog Configuration for Listen AP2P Tx 212 ******/ - , - MODE_ENTRY_1_REG( - (RFAL_ANALOG_CONFIG_LISTEN | RFAL_ANALOG_CONFIG_TECH_AP2P | - RFAL_ANALOG_CONFIG_BITRATE_212 | RFAL_ANALOG_CONFIG_TX), - ST25R3916_REG_MODE, - ST25R3916_REG_MODE_tr_am, - ST25R3916_REG_MODE_tr_am_am /* Use AM modulation */ - ) - - /****** Default Analog Configuration for Listen AP2P Tx 424 ******/ - , - MODE_ENTRY_1_REG( - (RFAL_ANALOG_CONFIG_LISTEN | RFAL_ANALOG_CONFIG_TECH_AP2P | - RFAL_ANALOG_CONFIG_BITRATE_424 | RFAL_ANALOG_CONFIG_TX), - ST25R3916_REG_MODE, - ST25R3916_REG_MODE_tr_am, - ST25R3916_REG_MODE_tr_am_am /* Use AM modulation */ - ) - -}; - -const uint16_t rfalAnalogConfigCustomSettingsLength = sizeof(rfalAnalogConfigCustomSettings); diff --git a/lib/ST25RFAL002/source/rfal_analogConfig.c b/lib/ST25RFAL002/source/rfal_analogConfig.c deleted file mode 100644 index 1058dd32852..00000000000 --- a/lib/ST25RFAL002/source/rfal_analogConfig.c +++ /dev/null @@ -1,476 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

    © COPYRIGHT 2020 STMicroelectronics

    - * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R391x firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file rfal_analogConfig.c - * - * \author bkam - * - * \brief Functions to manage and set analog settings. - * - */ - -/* - ****************************************************************************** - * INCLUDES - ****************************************************************************** - */ -#include "rfal_analogConfig.h" -#include "rfal_chip.h" -#include "st_errno.h" -#include "platform.h" -#include "utils.h" - -/* Check whether the Default Analog settings are to be used or custom ones */ -#ifdef RFAL_ANALOG_CONFIG_CUSTOM -extern const uint8_t* rfalAnalogConfigCustomSettings; -extern const uint16_t rfalAnalogConfigCustomSettingsLength; -#else -#include "rfal_analogConfigTbl.h" -#endif - -/* - ****************************************************************************** - * DEFINES - ****************************************************************************** - */ - -#define RFAL_TEST_REG 0x0080U /*!< Test Register indicator */ - -/* - ****************************************************************************** - * MACROS - ****************************************************************************** - */ - -/* - ****************************************************************************** - * LOCAL DATA TYPES - ****************************************************************************** - */ - -#if RFAL_FEATURE_DYNAMIC_ANALOG_CONFIG -static uint8_t - gRfalAnalogConfig[RFAL_ANALOG_CONFIG_TBL_SIZE]; /*!< Analog Configuration Settings List */ -#endif /* RFAL_FEATURE_DYNAMIC_ANALOG_CONFIG */ - -/*! Struct for Analog Config Look Up Table Update */ -typedef struct { - const uint8_t* - currentAnalogConfigTbl; /*!< Reference to start of current Analog Configuration */ - uint16_t configTblSize; /*!< Total size of Analog Configuration */ - bool ready; /*!< Indicate if Look Up Table is complete and ready for use */ -} rfalAnalogConfigMgmt; - -static rfalAnalogConfigMgmt gRfalAnalogConfigMgmt; /*!< Analog Configuration LUT management */ - -/* - ****************************************************************************** - * LOCAL TABLES - ****************************************************************************** - */ - -/* - ****************************************************************************** - * LOCAL FUNCTION PROTOTYPES - ****************************************************************************** - */ -static rfalAnalogConfigNum - rfalAnalogConfigSearch(rfalAnalogConfigId configId, uint16_t* configOffset); - -#if RFAL_FEATURE_DYNAMIC_ANALOG_CONFIG -static void rfalAnalogConfigPtrUpdate(const uint8_t* analogConfigTbl); -#endif /* RFAL_FEATURE_DYNAMIC_ANALOG_CONFIG */ - -/* - ****************************************************************************** - * GLOBAL VARIABLE DEFINITIONS - ****************************************************************************** - */ - -/* - ****************************************************************************** - * GLOBAL FUNCTIONS - ****************************************************************************** - */ - -void rfalAnalogConfigInitialize(void) { - /* Use default Analog configuration settings in Flash by default. */ - -/* Check whether the Default Analog settings are to be used or custom ones */ -#ifdef RFAL_ANALOG_CONFIG_CUSTOM - gRfalAnalogConfigMgmt.currentAnalogConfigTbl = (const uint8_t*)&rfalAnalogConfigCustomSettings; - gRfalAnalogConfigMgmt.configTblSize = rfalAnalogConfigCustomSettingsLength; -#else - gRfalAnalogConfigMgmt.currentAnalogConfigTbl = - (const uint8_t*)&rfalAnalogConfigDefaultSettings; - gRfalAnalogConfigMgmt.configTblSize = sizeof(rfalAnalogConfigDefaultSettings); -#endif - - gRfalAnalogConfigMgmt.ready = true; -} /* rfalAnalogConfigInitialize() */ - -bool rfalAnalogConfigIsReady(void) { - return gRfalAnalogConfigMgmt.ready; -} - -ReturnCode rfalAnalogConfigListWriteRaw(const uint8_t* configTbl, uint16_t configTblSize) { -#if RFAL_FEATURE_DYNAMIC_ANALOG_CONFIG - - /* Check if the Configuration Table exceed the Table size */ - if(configTblSize >= RFAL_ANALOG_CONFIG_TBL_SIZE) { - rfalAnalogConfigInitialize(); /* Revert to default Analog Configuration */ - return ERR_NOMEM; - } - - /* Check for invalid parameters */ - if((configTbl == NULL) || (configTblSize == 0U)) { - return ERR_PARAM; - } - - /* NOTE: Function does not check for the validity of the Table contents (conf IDs, conf sets, register address) */ - ST_MEMCPY(gRfalAnalogConfig, configTbl, configTblSize); - - /* Update the total size of configuration settings */ - gRfalAnalogConfigMgmt.configTblSize = configTblSize; - - rfalAnalogConfigPtrUpdate(gRfalAnalogConfig); - return ERR_NONE; - -#else - - // If Analog Configuration Update is to be disabled - NO_WARNING(configTbl); - NO_WARNING(configTblSize); - return ERR_REQUEST; - -#endif /* RFAL_FEATURE_DYNAMIC_ANALOG_CONFIG */ -} - -ReturnCode rfalAnalogConfigListWrite(uint8_t more, const rfalAnalogConfig* config) { -#if RFAL_FEATURE_DYNAMIC_ANALOG_CONFIG - - rfalAnalogConfigId configId; - rfalAnalogConfigNum numConfig; - uint8_t configSize; - - if(true == gRfalAnalogConfigMgmt.ready) { /* First Update to the Configuration list. */ - gRfalAnalogConfigMgmt.ready = false; // invalidate the config List - gRfalAnalogConfigMgmt.configTblSize = 0; // Clear the config List - } - - configId = GETU16(config->id); - - /* Check validity of the Configuration ID. */ - if((RFAL_ANALOG_CONFIG_TECH_RFU <= RFAL_ANALOG_CONFIG_ID_GET_TECH(configId)) || - ((RFAL_ANALOG_CONFIG_BITRATE_6780 < RFAL_ANALOG_CONFIG_ID_GET_BITRATE(configId)) && - (RFAL_ANALOG_CONFIG_BITRATE_1OF4 > RFAL_ANALOG_CONFIG_ID_GET_BITRATE(configId))) || - (RFAL_ANALOG_CONFIG_BITRATE_1OF256 < RFAL_ANALOG_CONFIG_ID_GET_BITRATE(configId))) { - rfalAnalogConfigInitialize(); /* Revert to default Analog Configuration */ - return ERR_PARAM; - } - - numConfig = config->num; - configSize = - (uint8_t)(sizeof(rfalAnalogConfigId) + sizeof(rfalAnalogConfigNum) + (numConfig * sizeof(rfalAnalogConfigRegAddrMaskVal))); - - /* Check if the Configuration Set exceed the Table size. */ - if(RFAL_ANALOG_CONFIG_TBL_SIZE <= (gRfalAnalogConfigMgmt.configTblSize + configSize)) { - rfalAnalogConfigInitialize(); /* Revert to default Analog Configuration */ - return ERR_NOMEM; - } - - /* NOTE: Function does not check for the validity of the Register Address. */ - ST_MEMCPY( - &gRfalAnalogConfig[gRfalAnalogConfigMgmt.configTblSize], - (const uint8_t*)config, - configSize); - - /* Increment the total size of configuration settings. */ - gRfalAnalogConfigMgmt.configTblSize += configSize; - - /* Check if it is the last Analog Configuration to load. */ - if(RFAL_ANALOG_CONFIG_UPDATE_LAST == - more) { /* Update the Analog Configuration to the new settings. */ - rfalAnalogConfigPtrUpdate(gRfalAnalogConfig); - } - - return ERR_NONE; - -#else - - // If Analog Configuration Update is to be disabled - NO_WARNING(config); - NO_WARNING(more); - return ERR_DISABLED; - -#endif /* RFAL_FEATURE_DYNAMIC_ANALOG_CONFIG */ - -} /* rfalAnalogConfigListUpdate() */ - -ReturnCode - rfalAnalogConfigListReadRaw(uint8_t* tblBuf, uint16_t tblBufLen, uint16_t* configTblSize) { - /* Check if the the current table will fit into the given buffer */ - if(tblBufLen < gRfalAnalogConfigMgmt.configTblSize) { - return ERR_NOMEM; - } - - /* Check for invalid parameters */ - if(configTblSize == NULL) { - return ERR_PARAM; - } - - /* Copy the whole Table to the given buffer */ - if(gRfalAnalogConfigMgmt.configTblSize > 0U) /* MISRA 21.18 */ - { - ST_MEMCPY( - tblBuf, - gRfalAnalogConfigMgmt.currentAnalogConfigTbl, - gRfalAnalogConfigMgmt.configTblSize); - } - *configTblSize = gRfalAnalogConfigMgmt.configTblSize; - - return ERR_NONE; -} - -ReturnCode rfalAnalogConfigListRead( - rfalAnalogConfigOffset* configOffset, - uint8_t* more, - rfalAnalogConfig* config, - rfalAnalogConfigNum numConfig) { - uint16_t configSize; - rfalAnalogConfigOffset offset = *configOffset; - rfalAnalogConfigNum numConfigSet; - - /* Check if the number of register-mask-value settings for the respective Configuration ID will fit into the buffer passed in. */ - if(gRfalAnalogConfigMgmt.currentAnalogConfigTbl[offset + sizeof(rfalAnalogConfigId)] > - numConfig) { - return ERR_NOMEM; - } - - /* Get the number of Configuration set */ - numConfigSet = - gRfalAnalogConfigMgmt.currentAnalogConfigTbl[offset + sizeof(rfalAnalogConfigId)]; - - /* Pass Configuration Register-Mask-Value sets */ - configSize = - (sizeof(rfalAnalogConfigId) + sizeof(rfalAnalogConfigNum) + - (uint16_t)(numConfigSet * sizeof(rfalAnalogConfigRegAddrMaskVal))); - ST_MEMCPY((uint8_t*)config, &gRfalAnalogConfigMgmt.currentAnalogConfigTbl[offset], configSize); - *configOffset = offset + configSize; - - /* Check if it is the last Analog Configuration in the Table.*/ - *more = - (uint8_t)((*configOffset >= gRfalAnalogConfigMgmt.configTblSize) ? RFAL_ANALOG_CONFIG_UPDATE_LAST : RFAL_ANALOG_CONFIG_UPDATE_MORE); - - return ERR_NONE; -} /* rfalAnalogConfigListRead() */ - -ReturnCode rfalSetAnalogConfig(rfalAnalogConfigId configId) { - rfalAnalogConfigOffset configOffset = 0; - rfalAnalogConfigNum numConfigSet; - const rfalAnalogConfigRegAddrMaskVal* configTbl; - ReturnCode retCode = ERR_NONE; - rfalAnalogConfigNum i; - - if(true != gRfalAnalogConfigMgmt.ready) { - return ERR_REQUEST; - } - - /* Search LUT for the specific Configuration ID. */ - while(true) { - numConfigSet = rfalAnalogConfigSearch(configId, &configOffset); - if(RFAL_ANALOG_CONFIG_LUT_NOT_FOUND == numConfigSet) { - break; - } - - configTbl = - (rfalAnalogConfigRegAddrMaskVal*)((uint32_t)gRfalAnalogConfigMgmt.currentAnalogConfigTbl + (uint32_t)configOffset); - /* Increment the offset to the next index to search from. */ - configOffset += (uint16_t)(numConfigSet * sizeof(rfalAnalogConfigRegAddrMaskVal)); - - if((gRfalAnalogConfigMgmt.configTblSize + 1U) < - configOffset) { /* Error check make sure that the we do not access outside the configuration Table Size */ - return ERR_NOMEM; - } - - for(i = 0; i < numConfigSet; i++) { - if((GETU16(configTbl[i].addr) & RFAL_TEST_REG) != 0U) { - EXIT_ON_ERR( - retCode, - rfalChipChangeTestRegBits( - (GETU16(configTbl[i].addr) & ~RFAL_TEST_REG), - configTbl[i].mask, - configTbl[i].val)); - } else { - EXIT_ON_ERR( - retCode, - rfalChipChangeRegBits( - GETU16(configTbl[i].addr), configTbl[i].mask, configTbl[i].val)); - } - } - - } /* while(found Analog Config Id) */ - - return retCode; - -} /* rfalSetAnalogConfig() */ - -uint16_t rfalAnalogConfigGenModeID(rfalMode md, rfalBitRate br, uint16_t dir) { - uint16_t id; - - /* Assign Poll/Listen Mode */ - id = ((md >= RFAL_MODE_LISTEN_NFCA) ? RFAL_ANALOG_CONFIG_LISTEN : RFAL_ANALOG_CONFIG_POLL); - - /* Assign Technology */ - switch(md) { - case RFAL_MODE_POLL_NFCA: - case RFAL_MODE_POLL_NFCA_T1T: - case RFAL_MODE_LISTEN_NFCA: - id |= RFAL_ANALOG_CONFIG_TECH_NFCA; - break; - - case RFAL_MODE_POLL_NFCB: - case RFAL_MODE_POLL_B_PRIME: - case RFAL_MODE_POLL_B_CTS: - case RFAL_MODE_LISTEN_NFCB: - id |= RFAL_ANALOG_CONFIG_TECH_NFCB; - break; - - case RFAL_MODE_POLL_NFCF: - case RFAL_MODE_LISTEN_NFCF: - id |= RFAL_ANALOG_CONFIG_TECH_NFCF; - break; - - case RFAL_MODE_POLL_NFCV: - case RFAL_MODE_POLL_PICOPASS: - id |= RFAL_ANALOG_CONFIG_TECH_NFCV; - break; - - case RFAL_MODE_POLL_ACTIVE_P2P: - case RFAL_MODE_LISTEN_ACTIVE_P2P: - id |= RFAL_ANALOG_CONFIG_TECH_AP2P; - break; - - default: - id = RFAL_ANALOG_CONFIG_TECH_CHIP; - break; - } - - /* Assign Bitrate */ - id |= - (((((uint16_t)(br) >= (uint16_t)RFAL_BR_52p97) ? (uint16_t)(br) : ((uint16_t)(br) + 1U)) - << RFAL_ANALOG_CONFIG_BITRATE_SHIFT) & - RFAL_ANALOG_CONFIG_BITRATE_MASK); - - /* Assign Direction */ - id |= ((dir << RFAL_ANALOG_CONFIG_DIRECTION_SHIFT) & RFAL_ANALOG_CONFIG_DIRECTION_MASK); - - return id; - -} /* rfalAnalogConfigGenModeID() */ - -/* - ****************************************************************************** - * LOCAL FUNCTIONS - ****************************************************************************** - */ - -/*! - ***************************************************************************** - * \brief Update the link to Analog Configuration LUT - * - * Update the link to the Analog Configuration LUT for the subsequent search - * of Analog Settings. - * - * \param[in] analogConfigTbl: reference to the start of the new Analog Configuration Table - * - ***************************************************************************** - */ -#if RFAL_FEATURE_DYNAMIC_ANALOG_CONFIG -static void rfalAnalogConfigPtrUpdate(const uint8_t* analogConfigTbl) { - gRfalAnalogConfigMgmt.currentAnalogConfigTbl = analogConfigTbl; - gRfalAnalogConfigMgmt.ready = true; - -} /* rfalAnalogConfigPtrUpdate() */ -#endif /* RFAL_FEATURE_DYNAMIC_ANALOG_CONFIG */ - -/*! - ***************************************************************************** - * \brief Search the Analog Configuration LUT for a specific Configuration ID. - * - * Search the Analog Configuration LUT for the Configuration ID. - * - * \param[in] configId: Configuration ID to search for. - * \param[in] configOffset: Configuration Offset in Table - * - * \return number of Configuration Sets - * \return #RFAL_ANALOG_CONFIG_LUT_NOT_FOUND in case Configuration ID is not found. - ***************************************************************************** - */ -static rfalAnalogConfigNum - rfalAnalogConfigSearch(rfalAnalogConfigId configId, uint16_t* configOffset) { - rfalAnalogConfigId foundConfigId; - rfalAnalogConfigId configIdMaskVal; - const uint8_t* configTbl; - const uint8_t* currentConfigTbl; - uint16_t i; - - currentConfigTbl = gRfalAnalogConfigMgmt.currentAnalogConfigTbl; - configIdMaskVal = - ((RFAL_ANALOG_CONFIG_POLL_LISTEN_MODE_MASK | RFAL_ANALOG_CONFIG_BITRATE_MASK) | - ((RFAL_ANALOG_CONFIG_TECH_CHIP == RFAL_ANALOG_CONFIG_ID_GET_TECH(configId)) ? - (RFAL_ANALOG_CONFIG_TECH_MASK | RFAL_ANALOG_CONFIG_CHIP_SPECIFIC_MASK) : - configId) | - ((RFAL_ANALOG_CONFIG_NO_DIRECTION == RFAL_ANALOG_CONFIG_ID_GET_DIRECTION(configId)) ? - RFAL_ANALOG_CONFIG_DIRECTION_MASK : - configId)); - - /* When specific ConfigIDs are to be used, override search mask */ - if((RFAL_ANALOG_CONFIG_ID_GET_DIRECTION(configId) == RFAL_ANALOG_CONFIG_DPO)) { - configIdMaskVal = - (RFAL_ANALOG_CONFIG_POLL_LISTEN_MODE_MASK | RFAL_ANALOG_CONFIG_TECH_MASK | - RFAL_ANALOG_CONFIG_BITRATE_MASK | RFAL_ANALOG_CONFIG_DIRECTION_MASK); - } - - i = *configOffset; - while(i < gRfalAnalogConfigMgmt.configTblSize) { - configTbl = ¤tConfigTbl[i]; - foundConfigId = GETU16(configTbl); - if(configId == (foundConfigId & configIdMaskVal)) { - *configOffset = - (uint16_t)(i + sizeof(rfalAnalogConfigId) + sizeof(rfalAnalogConfigNum)); - return configTbl[sizeof(rfalAnalogConfigId)]; - } - - /* If Config Id does not match, increment to next Configuration Id */ - i += - (uint16_t)(sizeof(rfalAnalogConfigId) + sizeof(rfalAnalogConfigNum) + (configTbl[sizeof(rfalAnalogConfigId)] * sizeof(rfalAnalogConfigRegAddrMaskVal))); - } /* for */ - - return RFAL_ANALOG_CONFIG_LUT_NOT_FOUND; -} /* rfalAnalogConfigSearch() */ diff --git a/lib/ST25RFAL002/source/rfal_crc.c b/lib/ST25RFAL002/source/rfal_crc.c deleted file mode 100644 index bcf5ad593c8..00000000000 --- a/lib/ST25RFAL002/source/rfal_crc.c +++ /dev/null @@ -1,82 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

    © COPYRIGHT 2020 STMicroelectronics

    - * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R391x firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file rfal_crc.c - * - * \author Oliver Regenfelder - * - * \brief CRC calculation implementation - * - */ - -/* -****************************************************************************** -* INCLUDES -****************************************************************************** -*/ -#include "rfal_crc.h" - -/* -****************************************************************************** -* LOCAL FUNCTION PROTOTYPES -****************************************************************************** -*/ -static uint16_t rfalCrcUpdateCcitt(uint16_t crcSeed, uint8_t dataByte); - -/* -****************************************************************************** -* GLOBAL FUNCTIONS -****************************************************************************** -*/ -uint16_t rfalCrcCalculateCcitt(uint16_t preloadValue, const uint8_t* buf, uint16_t length) { - uint16_t crc = preloadValue; - uint16_t index; - - for(index = 0; index < length; index++) { - crc = rfalCrcUpdateCcitt(crc, buf[index]); - } - - return crc; -} - -/* -****************************************************************************** -* LOCAL FUNCTIONS -****************************************************************************** -*/ -static uint16_t rfalCrcUpdateCcitt(uint16_t crcSeed, uint8_t dataByte) { - uint16_t crc = crcSeed; - uint8_t dat = dataByte; - - dat ^= (uint8_t)(crc & 0xFFU); - dat ^= (dat << 4); - - crc = (crc >> 8) ^ (((uint16_t)dat) << 8) ^ (((uint16_t)dat) << 3) ^ (((uint16_t)dat) >> 4); - - return crc; -} diff --git a/lib/ST25RFAL002/source/rfal_dpo.c b/lib/ST25RFAL002/source/rfal_dpo.c deleted file mode 100644 index 48dd89c77cc..00000000000 --- a/lib/ST25RFAL002/source/rfal_dpo.c +++ /dev/null @@ -1,232 +0,0 @@ - -/****************************************************************************** - * @attention - * - *

    © COPYRIGHT 2020 STMicroelectronics

    - * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R391x firmware - * $Revision: $ - * LANGUAGE: ISO C99 - */ - -/*! \file rfal_dpo.c - * - * \author Martin Zechleitner - * - * \brief Functions to manage and set dynamic power settings. - * - */ - -/* - ****************************************************************************** - * INCLUDES - ****************************************************************************** - */ -#include "rfal_dpoTbl.h" -#include "rfal_dpo.h" -#include "platform.h" -#include "rfal_rf.h" -#include "rfal_chip.h" -#include "rfal_analogConfig.h" -#include "utils.h" - -/* - ****************************************************************************** - * ENABLE SWITCH - ****************************************************************************** - */ - -#ifndef RFAL_FEATURE_DPO -#define RFAL_FEATURE_DPO \ - false /* Dynamic Power Module configuration missing. Disabled by default */ -#endif - -#if RFAL_FEATURE_DPO - -/* - ****************************************************************************** - * DEFINES - ****************************************************************************** - */ -#define RFAL_DPO_ANALOGCONFIG_SHIFT 13U -#define RFAL_DPO_ANALOGCONFIG_MASK 0x6000U - -/* - ****************************************************************************** - * LOCAL DATA TYPES - ****************************************************************************** - */ - -static bool gRfalDpoIsEnabled = false; -static uint8_t* gRfalCurrentDpo; -static uint8_t gRfalDpoTableEntries; -static uint8_t gRfalDpo[RFAL_DPO_TABLE_SIZE_MAX]; -static uint8_t gRfalDpoTableEntry; -static rfalDpoMeasureFunc gRfalDpoMeasureCallback = NULL; - -/* - ****************************************************************************** - * GLOBAL FUNCTIONS - ****************************************************************************** - */ -void rfalDpoInitialize(void) { - /* Use the default Dynamic Power values */ - gRfalCurrentDpo = (uint8_t*)rfalDpoDefaultSettings; - gRfalDpoTableEntries = (sizeof(rfalDpoDefaultSettings) / RFAL_DPO_TABLE_PARAMETER); - - ST_MEMCPY(gRfalDpo, gRfalCurrentDpo, sizeof(rfalDpoDefaultSettings)); - - /* by default use amplitude measurement */ - gRfalDpoMeasureCallback = rfalChipMeasureAmplitude; - - /* by default DPO is disabled */ - gRfalDpoIsEnabled = false; - - gRfalDpoTableEntry = 0; -} - -void rfalDpoSetMeasureCallback(rfalDpoMeasureFunc pMeasureFunc) { - gRfalDpoMeasureCallback = pMeasureFunc; -} - -/*******************************************************************************/ -ReturnCode rfalDpoTableWrite(rfalDpoEntry* powerTbl, uint8_t powerTblEntries) { - uint8_t entry = 0; - - /* check if the table size parameter is too big */ - if((powerTblEntries * RFAL_DPO_TABLE_PARAMETER) > RFAL_DPO_TABLE_SIZE_MAX) { - return ERR_NOMEM; - } - - /* check if the first increase entry is 0xFF */ - if((powerTblEntries == 0) || (powerTbl == NULL)) { - return ERR_PARAM; - } - - /* check if the entries of the dynamic power table are valid */ - for(entry = 0; entry < powerTblEntries; entry++) { - if(powerTbl[entry].inc < powerTbl[entry].dec) { - return ERR_PARAM; - } - } - - /* copy the data set */ - ST_MEMCPY(gRfalDpo, powerTbl, (powerTblEntries * RFAL_DPO_TABLE_PARAMETER)); - gRfalCurrentDpo = gRfalDpo; - gRfalDpoTableEntries = powerTblEntries; - - if(gRfalDpoTableEntry > powerTblEntries) { - /* is always greater then zero, otherwise we already returned ERR_PARAM */ - gRfalDpoTableEntry = (powerTblEntries - 1); - } - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalDpoTableRead(rfalDpoEntry* tblBuf, uint8_t tblBufEntries, uint8_t* tableEntries) { - /* wrong request */ - if((tblBuf == NULL) || (tblBufEntries < gRfalDpoTableEntries) || (tableEntries == NULL)) { - return ERR_PARAM; - } - - /* Copy the whole Table to the given buffer */ - ST_MEMCPY(tblBuf, gRfalCurrentDpo, (tblBufEntries * RFAL_DPO_TABLE_PARAMETER)); - *tableEntries = gRfalDpoTableEntries; - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalDpoAdjust(void) { - uint8_t refValue = 0; - uint16_t modeID; - rfalBitRate br; - rfalDpoEntry* dpoTable = (rfalDpoEntry*)gRfalCurrentDpo; - - /* Check if the Power Adjustment is disabled and * - * if the callback to the measurement method is properly set */ - if((gRfalCurrentDpo == NULL) || (!gRfalDpoIsEnabled) || (gRfalDpoMeasureCallback == NULL)) { - return ERR_PARAM; - } - - /* Ensure that the current mode is Passive Poller */ - if(!rfalIsModePassivePoll(rfalGetMode())) { - return ERR_WRONG_STATE; - } - - /* Ensure a proper measure reference value */ - if(ERR_NONE != gRfalDpoMeasureCallback(&refValue)) { - return ERR_IO; - } - - if(refValue >= dpoTable[gRfalDpoTableEntry].inc) { /* Increase the output power */ - /* the top of the table represents the highest amplitude value*/ - if(gRfalDpoTableEntry == 0) { - /* maximum driver value has been reached */ - } else { - /* go up in the table to decrease the driver resistance */ - gRfalDpoTableEntry--; - } - } else if(refValue <= dpoTable[gRfalDpoTableEntry].dec) { /* decrease the output power */ - /* The bottom is the highest possible value */ - if((gRfalDpoTableEntry + 1) >= gRfalDpoTableEntries) { - /* minimum driver value has been reached */ - } else { - /* go down in the table to increase the driver resistance */ - gRfalDpoTableEntry++; - } - } else { - /* Fall through to always write dpo and its associated analog configs */ - } - - /* Get the new value for RFO resistance form the table and apply the new RFO resistance setting */ - rfalChipSetRFO(dpoTable[gRfalDpoTableEntry].rfoRes); - - /* Apply the DPO Analog Config according to this treshold */ - /* Technology field is being extended for DPO: 2msb are used for treshold step (only 4 allowed) */ - rfalGetBitRate(&br, NULL); /* Obtain current Tx bitrate */ - modeID = rfalAnalogConfigGenModeID( - rfalGetMode(), br, RFAL_ANALOG_CONFIG_DPO); /* Generate Analog Config mode ID */ - modeID |= - ((gRfalDpoTableEntry << RFAL_DPO_ANALOGCONFIG_SHIFT) & - RFAL_DPO_ANALOGCONFIG_MASK); /* Add DPO treshold step|level */ - rfalSetAnalogConfig(modeID); /* Apply DPO Analog Config */ - - return ERR_NONE; -} - -/*******************************************************************************/ -rfalDpoEntry* rfalDpoGetCurrentTableEntry(void) { - rfalDpoEntry* dpoTable = (rfalDpoEntry*)gRfalCurrentDpo; - return &dpoTable[gRfalDpoTableEntry]; -} - -/*******************************************************************************/ -void rfalDpoSetEnabled(bool enable) { - gRfalDpoIsEnabled = enable; -} - -/*******************************************************************************/ -bool rfalDpoIsEnabled(void) { - return gRfalDpoIsEnabled; -} - -#endif /* RFAL_FEATURE_DPO */ diff --git a/lib/ST25RFAL002/source/rfal_iso15693_2.c b/lib/ST25RFAL002/source/rfal_iso15693_2.c deleted file mode 100644 index 34ecd41113c..00000000000 --- a/lib/ST25RFAL002/source/rfal_iso15693_2.c +++ /dev/null @@ -1,514 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

    © COPYRIGHT 2020 STMicroelectronics

    - * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R391x firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file rfal_iso15693_2.c - * - * \author Ulrich Herrmann - * - * \brief Implementation of ISO-15693-2 - * - */ - -/* -****************************************************************************** -* INCLUDES -****************************************************************************** -*/ -#include "rfal_iso15693_2.h" -#include "rfal_crc.h" -#include "utils.h" - -/* - ****************************************************************************** - * ENABLE SWITCH - ****************************************************************************** - */ - -#ifndef RFAL_FEATURE_NFCV -#define RFAL_FEATURE_NFCV false /* NFC-V module configuration missing. Disabled by default */ -#endif - -#if RFAL_FEATURE_NFCV - -/* -****************************************************************************** -* LOCAL MACROS -****************************************************************************** -*/ - -#define ISO_15693_DEBUG(...) /*!< Macro for the log method */ - -/* -****************************************************************************** -* LOCAL DEFINES -****************************************************************************** -*/ -#define ISO15693_DAT_SOF_1_4 0x21 /* LSB constants */ -#define ISO15693_DAT_EOF_1_4 0x04 -#define ISO15693_DAT_00_1_4 0x02 -#define ISO15693_DAT_01_1_4 0x08 -#define ISO15693_DAT_10_1_4 0x20 -#define ISO15693_DAT_11_1_4 0x80 - -#define ISO15693_DAT_SOF_1_256 0x81 -#define ISO15693_DAT_EOF_1_256 0x04 -#define ISO15693_DAT_SLOT0_1_256 0x02 -#define ISO15693_DAT_SLOT1_1_256 0x08 -#define ISO15693_DAT_SLOT2_1_256 0x20 -#define ISO15693_DAT_SLOT3_1_256 0x80 - -#define ISO15693_PHY_DAT_MANCHESTER_1 0xaaaa - -#define ISO15693_PHY_BIT_BUFFER_SIZE \ - 1000 /*!< size of the receiving buffer. Might be adjusted if longer datastreams are expected. */ - -/* -****************************************************************************** -* LOCAL VARIABLES -****************************************************************************** -*/ -static iso15693PhyConfig_t iso15693PhyConfig; /*!< current phy configuration */ - -/* -****************************************************************************** -* LOCAL FUNCTION PROTOTYPES -****************************************************************************** -*/ -static ReturnCode iso15693PhyVCDCode1Of4( - const uint8_t data, - uint8_t* outbuffer, - uint16_t maxOutBufLen, - uint16_t* outBufLen); -static ReturnCode iso15693PhyVCDCode1Of256( - const uint8_t data, - uint8_t* outbuffer, - uint16_t maxOutBufLen, - uint16_t* outBufLen); - -/* -****************************************************************************** -* GLOBAL FUNCTIONS -****************************************************************************** -*/ -ReturnCode iso15693PhyConfigure( - const iso15693PhyConfig_t* config, - const struct iso15693StreamConfig** needed_stream_config) { - static struct iso15693StreamConfig stream_config = { - /* MISRA 8.9 */ - .useBPSK = 0, /* 0: subcarrier, 1:BPSK */ - .din = 5, /* 2^5*fc = 423750 Hz: divider for the in subcarrier frequency */ - .dout = 7, /*!< 2^7*fc = 105937 : divider for the in subcarrier frequency */ - .report_period_length = 3, /*!< 8=2^3 the length of the reporting period */ - }; - - /* make a copy of the configuration */ - ST_MEMCPY((uint8_t*)&iso15693PhyConfig, (const uint8_t*)config, sizeof(iso15693PhyConfig_t)); - - if(config->speedMode <= 3U) { /* If valid speed mode adjust report period accordingly */ - stream_config.report_period_length = (3U - (uint8_t)config->speedMode); - } else { /* If invalid default to normal (high) speed */ - stream_config.report_period_length = 3; - } - - *needed_stream_config = &stream_config; - - return ERR_NONE; -} - -ReturnCode iso15693PhyGetConfiguration(iso15693PhyConfig_t* config) { - ST_MEMCPY(config, &iso15693PhyConfig, sizeof(iso15693PhyConfig_t)); - - return ERR_NONE; -} - -ReturnCode iso15693VCDCode( - uint8_t* buffer, - uint16_t length, - bool sendCrc, - bool sendFlags, - bool picopassMode, - uint16_t* subbit_total_length, - uint16_t* offset, - uint8_t* outbuf, - uint16_t outBufSize, - uint16_t* actOutBufSize) { - ReturnCode err = ERR_NONE; - uint8_t eof, sof; - uint8_t transbuf[2]; - uint16_t crc = 0; - ReturnCode (*txFunc)( - const uint8_t data, uint8_t* outbuffer, uint16_t maxOutBufLen, uint16_t* outBufLen); - uint8_t crc_len; - uint8_t* outputBuf; - uint16_t outputBufSize; - - crc_len = (uint8_t)((sendCrc) ? 2 : 0); - - *actOutBufSize = 0; - - if(ISO15693_VCD_CODING_1_4 == iso15693PhyConfig.coding) { - sof = ISO15693_DAT_SOF_1_4; - eof = ISO15693_DAT_EOF_1_4; - txFunc = iso15693PhyVCDCode1Of4; - *subbit_total_length = - ((1U /* SOF */ - + ((length + (uint16_t)crc_len) * 4U) + 1U) /* EOF */ - ); - if(outBufSize < 5U) { /* 5 should be safe: enough for sof + 1byte data in 1of4 */ - return ERR_NOMEM; - } - } else { - sof = ISO15693_DAT_SOF_1_256; - eof = ISO15693_DAT_EOF_1_256; - txFunc = iso15693PhyVCDCode1Of256; - *subbit_total_length = - ((1U /* SOF */ - + ((length + (uint16_t)crc_len) * 64U) + 1U) /* EOF */ - ); - - if(*offset != 0U) { - if(outBufSize < 64U) { /* 64 should be safe: enough a single byte data in 1of256 */ - return ERR_NOMEM; - } - } else { - if(outBufSize < - 65U) { /* At beginning of a frame we need at least 65 bytes to start: enough for sof + 1byte data in 1of256 */ - return ERR_NOMEM; - } - } - } - - if(length == 0U) { - *subbit_total_length = 1; - } - - if((length != 0U) && (0U == *offset) && sendFlags && !picopassMode) { - /* set high datarate flag */ - buffer[0] |= (uint8_t)ISO15693_REQ_FLAG_HIGH_DATARATE; - /* clear sub-carrier flag - we only support single sub-carrier */ - buffer[0] = (uint8_t)(buffer[0] & ~ISO15693_REQ_FLAG_TWO_SUBCARRIERS); /* MISRA 10.3 */ - } - - outputBuf = outbuf; /* MISRA 17.8: Use intermediate variable */ - outputBufSize = outBufSize; /* MISRA 17.8: Use intermediate variable */ - - /* Send SOF if at 0 offset */ - if((length != 0U) && (0U == *offset)) { - *outputBuf = sof; - (*actOutBufSize)++; - outputBufSize--; - outputBuf++; - } - - while((*offset < length) && (err == ERR_NONE)) { - uint16_t filled_size; - /* send data */ - err = txFunc(buffer[*offset], outputBuf, outputBufSize, &filled_size); - (*actOutBufSize) += filled_size; - outputBuf = &outputBuf[filled_size]; /* MISRA 18.4: Avoid pointer arithmetic */ - outputBufSize -= filled_size; - if(err == ERR_NONE) { - (*offset)++; - } - } - if(err != ERR_NONE) { - return ERR_AGAIN; - } - - while((err == ERR_NONE) && sendCrc && (*offset < (length + 2U))) { - uint16_t filled_size; - if(0U == crc) { - crc = rfalCrcCalculateCcitt( - (uint16_t)((picopassMode) ? 0xE012U : 0xFFFFU), /* In PicoPass Mode a different Preset Value is used */ - ((picopassMode) ? - (buffer + 1U) : - buffer), /* CMD byte is not taken into account in PicoPass mode */ - ((picopassMode) ? - (length - 1U) : - length)); /* CMD byte is not taken into account in PicoPass mode */ - - crc = (uint16_t)((picopassMode) ? crc : ~crc); - } - /* send crc */ - transbuf[0] = (uint8_t)(crc & 0xffU); - transbuf[1] = (uint8_t)((crc >> 8) & 0xffU); - err = txFunc(transbuf[*offset - length], outputBuf, outputBufSize, &filled_size); - (*actOutBufSize) += filled_size; - outputBuf = &outputBuf[filled_size]; /* MISRA 18.4: Avoid pointer arithmetic */ - outputBufSize -= filled_size; - if(err == ERR_NONE) { - (*offset)++; - } - } - if(err != ERR_NONE) { - return ERR_AGAIN; - } - - if((!sendCrc && (*offset == length)) || (sendCrc && (*offset == (length + 2U)))) { - *outputBuf = eof; - (*actOutBufSize)++; - outputBufSize--; - outputBuf++; - } else { - return ERR_AGAIN; - } - - return err; -} - -ReturnCode iso15693VICCDecode( - const uint8_t* inBuf, - uint16_t inBufLen, - uint8_t* outBuf, - uint16_t outBufLen, - uint16_t* outBufPos, - uint16_t* bitsBeforeCol, - uint16_t ignoreBits, - bool picopassMode) { - ReturnCode err = ERR_NONE; - uint16_t crc; - uint16_t mp; /* Current bit position in manchester bit inBuf*/ - uint16_t bp; /* Current bit position in outBuf */ - - *bitsBeforeCol = 0; - *outBufPos = 0; - - /* first check for valid SOF. Since it starts with 3 unmodulated pulses it is 0x17. */ - if((inBuf[0] & 0x1fU) != 0x17U) { - ISO_15693_DEBUG("0x%x\n", iso15693PhyBitBuffer[0]); - return ERR_FRAMING; - } - ISO_15693_DEBUG("SOF\n"); - - if(outBufLen == 0U) { - return ERR_NONE; - } - - mp = 5; /* 5 bits were SOF, now manchester starts: 2 bits per payload bit */ - bp = 0; - - ST_MEMSET(outBuf, 0, outBufLen); - - if(inBufLen == 0U) { - return ERR_CRC; - } - - for(; mp < ((inBufLen * 8U) - 2U); mp += 2U) { - bool isEOF = false; - - uint8_t man; - man = (inBuf[mp / 8U] >> (mp % 8U)) & 0x1U; - man |= ((inBuf[(mp + 1U) / 8U] >> ((mp + 1U) % 8U)) & 0x1U) << 1; - if(1U == man) { - bp++; - } - if(2U == man) { - outBuf[bp / 8U] = (uint8_t)(outBuf[bp / 8U] | (1U << (bp % 8U))); /* MISRA 10.3 */ - bp++; - } - if((bp % 8U) == 0U) { /* Check for EOF */ - ISO_15693_DEBUG("ceof %hhx %hhx\n", inBuf[mp / 8U], inBuf[mp / 8 + 1]); - if(((inBuf[mp / 8U] & 0xe0U) == 0xa0U) && - (inBuf[(mp / 8U) + 1U] == 0x03U)) { /* Now we know that it was 10111000 = EOF */ - ISO_15693_DEBUG("EOF\n"); - isEOF = true; - } - } - if(((0U == man) || (3U == man)) && !isEOF) { - if(bp >= ignoreBits) { - err = ERR_RF_COLLISION; - } else { - /* ignored collision: leave as 0 */ - bp++; - } - } - if((bp >= (outBufLen * 8U)) || (err == ERR_RF_COLLISION) || - isEOF) { /* Don't write beyond the end */ - break; - } - } - - *outBufPos = (bp / 8U); - *bitsBeforeCol = bp; - - if(err != ERR_NONE) { - return err; - } - - if((bp % 8U) != 0U) { - return ERR_CRC; - } - - if(*outBufPos > 2U) { - /* finally, check crc */ - ISO_15693_DEBUG("Calculate CRC, val: 0x%x, outBufLen: ", *outBuf); - ISO_15693_DEBUG("0x%x ", *outBufPos - 2); - - crc = rfalCrcCalculateCcitt(((picopassMode) ? 0xE012U : 0xFFFFU), outBuf, *outBufPos - 2U); - crc = (uint16_t)((picopassMode) ? crc : ~crc); - - if(((crc & 0xffU) == outBuf[*outBufPos - 2U]) && - (((crc >> 8U) & 0xffU) == outBuf[*outBufPos - 1U])) { - err = ERR_NONE; - ISO_15693_DEBUG("OK\n"); - } else { - ISO_15693_DEBUG("error! Expected: 0x%x, got ", crc); - ISO_15693_DEBUG("0x%hhx 0x%hhx\n", outBuf[*outBufPos - 2], outBuf[*outBufPos - 1]); - err = ERR_CRC; - } - } else { - err = ERR_CRC; - } - - return err; -} - -/* -****************************************************************************** -* LOCAL FUNCTIONS -****************************************************************************** -*/ -/*! - ***************************************************************************** - * \brief Perform 1 of 4 coding and send coded data - * - * This function takes \a length bytes from \a buffer, perform 1 of 4 coding - * (see ISO15693-2 specification) and sends the data using stream mode. - * - * \param[in] sendSof : send SOF prior to data. - * \param[in] buffer : data to send. - * \param[in] length : number of bytes to send. - * - * \return ERR_IO : Error during communication. - * \return ERR_NONE : No error. - * - ***************************************************************************** - */ -static ReturnCode iso15693PhyVCDCode1Of4( - const uint8_t data, - uint8_t* outbuffer, - uint16_t maxOutBufLen, - uint16_t* outBufLen) { - uint8_t tmp; - ReturnCode err = ERR_NONE; - uint16_t a; - uint8_t* outbuf = outbuffer; - - *outBufLen = 0; - - if(maxOutBufLen < 4U) { - return ERR_NOMEM; - } - - tmp = data; - for(a = 0; a < 4U; a++) { - switch(tmp & 0x3U) { - case 0: - *outbuf = ISO15693_DAT_00_1_4; - break; - case 1: - *outbuf = ISO15693_DAT_01_1_4; - break; - case 2: - *outbuf = ISO15693_DAT_10_1_4; - break; - case 3: - *outbuf = ISO15693_DAT_11_1_4; - break; - default: - /* MISRA 16.4: mandatory default statement */ - break; - } - outbuf++; - (*outBufLen)++; - tmp >>= 2; - } - return err; -} - -/*! - ***************************************************************************** - * \brief Perform 1 of 256 coding and send coded data - * - * This function takes \a length bytes from \a buffer, perform 1 of 256 coding - * (see ISO15693-2 specification) and sends the data using stream mode. - * \note This function sends SOF prior to the data. - * - * \param[in] sendSof : send SOF prior to data. - * \param[in] buffer : data to send. - * \param[in] length : number of bytes to send. - * - * \return ERR_IO : Error during communication. - * \return ERR_NONE : No error. - * - ***************************************************************************** - */ -static ReturnCode iso15693PhyVCDCode1Of256( - const uint8_t data, - uint8_t* outbuffer, - uint16_t maxOutBufLen, - uint16_t* outBufLen) { - uint8_t tmp; - ReturnCode err = ERR_NONE; - uint16_t a; - uint8_t* outbuf = outbuffer; - - *outBufLen = 0; - - if(maxOutBufLen < 64U) { - return ERR_NOMEM; - } - - tmp = data; - for(a = 0; a < 64U; a++) { - switch(tmp) { - case 0: - *outbuf = ISO15693_DAT_SLOT0_1_256; - break; - case 1: - *outbuf = ISO15693_DAT_SLOT1_1_256; - break; - case 2: - *outbuf = ISO15693_DAT_SLOT2_1_256; - break; - case 3: - *outbuf = ISO15693_DAT_SLOT3_1_256; - break; - default: - *outbuf = 0; - break; - } - outbuf++; - (*outBufLen)++; - tmp -= 4U; - } - - return err; -} - -#endif /* RFAL_FEATURE_NFCV */ diff --git a/lib/ST25RFAL002/source/rfal_isoDep.c b/lib/ST25RFAL002/source/rfal_isoDep.c deleted file mode 100644 index 13674788e84..00000000000 --- a/lib/ST25RFAL002/source/rfal_isoDep.c +++ /dev/null @@ -1,3032 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

    © COPYRIGHT 2020 STMicroelectronics

    - * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: NFCC firmware - * LANGUAGE: ISO C99 - */ - -/*! \file rfal_isoDep.c - * - * \author Gustavo Patricio - * - * \brief Implementation of ISO-DEP protocol - * - * This implementation was based on the following specs: - * - ISO/IEC 14443-4 2nd Edition 2008-07-15 - * - NFC Forum Digital Protocol 1.1 2014-01-14 - * - */ - -/* - ****************************************************************************** - * INCLUDES - ****************************************************************************** - */ - -#include "rfal_isoDep.h" -#include "rfal_rf.h" -#include "utils.h" - -/* - ****************************************************************************** - * ENABLE SWITCH - ****************************************************************************** - */ - -#if RFAL_FEATURE_ISO_DEP - -#if(!RFAL_FEATURE_ISO_DEP_POLL && !RFAL_FEATURE_ISO_DEP_LISTEN) -#error \ - " RFAL: Invalid ISO-DEP Configuration. Please select at least one mode: Poller and/or Listener. " -#endif - -/* Check for valid I-Block length [RFAL_ISODEP_FSX_16 ; RFAL_ISODEP_FSX_4096]*/ -#if((RFAL_FEATURE_ISO_DEP_IBLOCK_MAX_LEN > 4096) || (RFAL_FEATURE_ISO_DEP_IBLOCK_MAX_LEN < 16)) -#error \ - " RFAL: Invalid ISO-DEP IBlock Max length. Please change RFAL_FEATURE_ISO_DEP_IBLOCK_MAX_LEN. " -#endif - -/* Check for valid APDU length. */ -#if((RFAL_FEATURE_ISO_DEP_APDU_MAX_LEN < RFAL_FEATURE_ISO_DEP_IBLOCK_MAX_LEN)) -#error " RFAL: Invalid ISO-DEP APDU Max length. Please change RFAL_FEATURE_ISO_DEP_APDU_MAX_LEN. " -#endif - -/* - ****************************************************************************** - * DEFINES - ****************************************************************************** - */ -#define ISODEP_CRC_LEN RFAL_CRC_LEN /*!< ISO1443 CRC Length */ - -#define ISODEP_PCB_POS (0U) /*!< PCB position on message header*/ -#define ISODEP_SWTX_INF_POS (1U) /*!< INF position in a S-WTX */ - -#define ISODEP_DID_POS (1U) /*!< DID position on message header*/ -#define ISODEP_SWTX_PARAM_LEN (1U) /*!< SWTX parameter length */ - -#define ISODEP_DSL_MAX_LEN \ - (RFAL_ISODEP_PCB_LEN + RFAL_ISODEP_DID_LEN) /*!< Deselect Req/Res length */ - -#define ISODEP_PCB_xBLOCK_MASK (0xC0U) /*!< Bit mask for Block type */ -#define ISODEP_PCB_IBLOCK (0x00U) /*!< Bit mask indicating a I-Block */ -#define ISODEP_PCB_RBLOCK (0x80U) /*!< Bit mask indicating a R-Block */ -#define ISODEP_PCB_SBLOCK (0xC0U) /*!< Bit mask indicating a S-Block */ -#define ISODEP_PCB_INVALID (0x40U) /*!< Bit mask of an Invalid PCB */ - -#define ISODEP_HDR_MAX_LEN \ - (RFAL_ISODEP_PCB_LEN + RFAL_ISODEP_DID_LEN + \ - RFAL_ISODEP_NAD_LEN) /*!< Max header length (PCB + DID + NAD) */ - -#define ISODEP_PCB_IB_VALID_MASK \ - (ISODEP_PCB_B6_BIT | ISODEP_PCB_B2_BIT) /*!< Bit mask for the MUST bits on I-Block */ -#define ISODEP_PCB_IB_VALID_VAL \ - (ISODEP_PCB_B2_BIT) /*!< Value for the MUST bits on I-Block */ -#define ISODEP_PCB_RB_VALID_MASK \ - (ISODEP_PCB_B6_BIT | ISODEP_PCB_B3_BIT | \ - ISODEP_PCB_B2_BIT) /*!< Bit mask for the MUST bits on R-Block */ -#define ISODEP_PCB_RB_VALID_VAL \ - (ISODEP_PCB_B6_BIT | ISODEP_PCB_B2_BIT) /*!< Value for the MUST bits on R-Block */ -#define ISODEP_PCB_SB_VALID_MASK \ - (ISODEP_PCB_B3_BIT | ISODEP_PCB_B2_BIT | \ - ISODEP_PCB_B1_BIT) /*!< Bit mask for the MUST bits on I-Block */ -#define ISODEP_PCB_SB_VALID_VAL \ - (ISODEP_PCB_B2_BIT) /*!< Value for the MUST bits on I-Block */ - -#define ISODEP_PCB_B1_BIT \ - (0x01U) /*!< Bit mask for the RFU S Blocks */ -#define ISODEP_PCB_B2_BIT \ - (0x02U) /*!< Bit mask for the RFU bit2 in I,S,R Blocks */ -#define ISODEP_PCB_B3_BIT \ - (0x04U) /*!< Bit mask for the RFU bit3 in R Blocks */ -#define ISODEP_PCB_B6_BIT \ - (0x20U) /*!< Bit mask for the RFU bit2 in R Blocks */ -#define ISODEP_PCB_CHAINING_BIT \ - (0x10U) /*!< Bit mask for the chaining bit of an ISO DEP I-Block in PCB. */ -#define ISODEP_PCB_DID_BIT \ - (0x08U) /*!< Bit mask for the DID presence bit of an ISO DEP I,S,R Blocks PCB. */ -#define ISODEP_PCB_NAD_BIT \ - (0x04U) /*!< Bit mask for the NAD presence bit of an ISO DEP I,S,R Blocks in PCB */ -#define ISODEP_PCB_BN_MASK \ - (0x01U) /*!< Bit mask for the block number of an ISO DEP I,R Block in PCB */ - -#define ISODEP_SWTX_PL_MASK \ - (0xC0U) /*!< Bit mask for the Power Level bits of the inf byte of an WTX request or response */ -#define ISODEP_SWTX_WTXM_MASK \ - (0x3FU) /*!< Bit mask for the WTXM bits of the inf byte of an WTX request or response */ - -#define ISODEP_RBLOCK_INF_LEN (0U) /*!< INF length of R-Block Digital 1.1 15.1.3 */ -#define ISODEP_SDSL_INF_LEN (0U) /*!< INF length of S(DSL) Digital 1.1 15.1.3 */ -#define ISODEP_SWTX_INF_LEN (1U) /*!< INF length of S(WTX) Digital 1.1 15.2.2 */ - -#define ISODEP_WTXM_MIN (1U) /*!< Minimum allowed value for the WTXM, Digital 1.0 13.2.2 */ -#define ISODEP_WTXM_MAX (59U) /*!< Maximum allowed value for the WTXM, Digital 1.0 13.2.2 */ - -#define ISODEP_PCB_Sxx_MASK (0x30U) /*!< Bit mask for the S-Block type */ -#define ISODEP_PCB_DESELECT (0x00U) /*!< Bit mask for S-Block indicating Deselect */ -#define ISODEP_PCB_WTX (0x30U) /*!< Bit mask for S-Block indicating Waiting Time eXtension */ - -#define ISODEP_PCB_Rx_MASK (0x10U) /*!< Bit mask for the R-Block type */ -#define ISODEP_PCB_ACK (0x00U) /*!< Bit mask for R-Block indicating ACK */ -#define ISODEP_PCB_NAK (0x10U) /*!< Bit mask for R-Block indicating NAK */ - -/*! Maximum length of control message (no INF) */ -#define ISODEP_CONTROLMSG_BUF_LEN \ - (RFAL_ISODEP_PCB_LEN + RFAL_ISODEP_DID_LEN + RFAL_ISODEP_NAD_LEN + ISODEP_SWTX_PARAM_LEN) - -#define ISODEP_FWT_DEACTIVATION \ - (71680U) /*!< FWT used for DESELECT Digital 2.2 B10 ISO1444-4 7.2 & 8.1 */ -#define ISODEP_MAX_RERUNS (0x0FFFFFFFU) /*!< Maximum rerun retrys for a blocking protocol run*/ - -#define ISODEP_PCBSBLOCK \ - (0x00U | ISODEP_PCB_SBLOCK | ISODEP_PCB_B2_BIT) /*!< PCB Value of a S-Block */ -#define ISODEP_PCB_SDSL \ - (ISODEP_PCBSBLOCK | ISODEP_PCB_DESELECT) /*!< PCB Value of a S-Block with DESELECT */ -#define ISODEP_PCB_SWTX \ - (ISODEP_PCBSBLOCK | ISODEP_PCB_WTX) /*!< PCB Value of a S-Block with WTX */ -#define ISODEP_PCB_SPARAMETERS \ - (ISODEP_PCB_SBLOCK | ISODEP_PCB_WTX) /*!< PCB Value of a S-Block with PARAMETERS */ - -#define ISODEP_FWI_LIS_MAX_NFC \ - 8U /*!< FWT Listener Max FWIT4ATmax FWIBmax Digital 1.1 A6 & A3 */ -#define ISODEP_FWI_LIS_MAX_EMVCO \ - 7U /*!< FWT Listener Max FWIMAX EMVCo 2.6 A.5 */ -#define ISODEP_FWI_LIS_MAX \ - (uint8_t)( \ - (gIsoDep.compMode == RFAL_COMPLIANCE_MODE_EMV) ? \ - ISODEP_FWI_LIS_MAX_EMVCO : \ - ISODEP_FWI_LIS_MAX_NFC) /*!< FWI Listener Max as NFC / EMVCo */ -#define ISODEP_FWT_LIS_MAX \ - rfalIsoDepFWI2FWT(ISODEP_FWI_LIS_MAX) /*!< FWT Listener Max */ - -#define ISODEP_FWI_MIN_10 (1U) /*!< Minimum value for FWI Digital 1.0 11.6.2.17 */ -#define ISODEP_FWI_MIN_11 (0U) /*!< Default value for FWI Digital 1.1 13.6.2 */ -#define ISODEP_FWI_MAX (14U) /*!< Maximum value for FWI Digital 1.0 11.6.2.17 */ -#define ISODEP_SFGI_MIN (0U) /*!< Default value for FWI Digital 1.1 13.6.2.22 */ -#define ISODEP_SFGI_MAX (14U) /*!< Maximum value for FWI Digital 1.1 13.6.2.22 */ - -#define RFAL_ISODEP_SPARAM_TVL_HDR_LEN (2U) /*!< S(PARAMETERS) TVL header length: Tag + Len */ -#define RFAL_ISODEP_SPARAM_HDR_LEN \ - (RFAL_ISODEP_PCB_LEN + \ - RFAL_ISODEP_SPARAM_TVL_HDR_LEN) /*!< S(PARAMETERS) header length: PCB + Tag + Len */ - -/**********************************************************************************************************************/ -/**********************************************************************************************************************/ -#define RFAL_ISODEP_NO_PARAM (0U) /*!< No parameter flag for isoDepHandleControlMsg() */ - -#define RFAL_ISODEP_CMD_RATS (0xE0U) /*!< RATS command Digital 1.1 13.6.1 */ - -#define RFAL_ISODEP_ATS_MIN_LEN (1U) /*!< Minimum ATS length Digital 1.1 13.6.2 */ -#define RFAL_ISODEP_ATS_HDR_LEN (5U) /*!< ATS headerlength Digital 1.1 13.6.2 */ -#define RFAL_ISODEP_ATS_MAX_LEN \ - (RFAL_ISODEP_ATS_HDR_LEN + \ - RFAL_ISODEP_ATS_HB_MAX_LEN) /*!< Maximum ATS length Digital 1.1 13.6.2 */ -#define RFAL_ISODEP_ATS_T0_FSCI_MASK (0x0FU) /*!< ATS T0's FSCI mask Digital 1.1 13.6.2 */ -#define RFAL_ISODEP_ATS_TB_FWI_SHIFT (4U) /*!< ATS TB's FWI shift Digital 1.1 13.6.2 */ -#define RFAL_ISODEP_ATS_FWI_MASK (0x0FU) /*!< ATS TB's FWI shift Digital 1.1 13.6.2 */ -#define RFAL_ISODEP_ATS_TL_POS (0x00U) /*!< ATS TL's position Digital 1.1 13.6.2 */ - -#define RFAL_ISODEP_PPS_SB (0xD0U) /*!< PPS REQ PPSS's SB value (no CID) ISO14443-4 5.3 */ -#define RFAL_ISODEP_PPS_MASK (0xF0U) /*!< PPS REQ PPSS's SB mask ISO14443-4 5.3 */ -#define RFAL_ISODEP_PPS_SB_DID_MASK \ - (0x0FU) /*!< PPS REQ PPSS's DID|CID mask ISO14443-4 5.3 */ -#define RFAL_ISODEP_PPS_PPS0_PPS1_PRESENT \ - (0x11U) /*!< PPS REQ PPS0 indicating that PPS1 is present */ -#define RFAL_ISODEP_PPS_PPS1 (0x00U) /*!< PPS REQ PPS1 fixed value ISO14443-4 5.3 */ -#define RFAL_ISODEP_PPS_PPS1_DSI_SHIFT \ - (2U) /*!< PPS REQ PPS1 fixed value ISO14443-4 5.3 */ -#define RFAL_ISODEP_PPS_PPS1_DXI_MASK \ - (0x0FU) /*!< PPS REQ PPS1 fixed value ISO14443-4 5.3 */ -#define RFAL_ISODEP_PPS_RES_LEN (1U) /*!< PPS Response length ISO14443-4 5.4 */ -#define RFAL_ISODEP_PPS_STARTBYTE_POS \ - (0U) /*!< PPS REQ PPSS's byte position ISO14443-4 5.4 */ -#define RFAL_ISODEP_PPS_PPS0_POS (1U) /*!< PPS REQ PPS0's byte position ISO14443-4 5.4 */ -#define RFAL_ISODEP_PPS_PPS1_POS (2U) /*!< PPS REQ PPS1's byte position ISO14443-4 5.4 */ -#define RFAL_ISODEP_PPS0_VALID_MASK \ - (0xEFU) /*!< PPS REQ PPS0 valid coding mask ISO14443-4 5.4 */ - -#define RFAL_ISODEP_CMD_ATTRIB (0x1DU) /*!< ATTRIB command Digital 1.1 14.6.1 */ -#define RFAL_ISODEP_ATTRIB_PARAM2_DSI_SHIFT \ - (6U) /*!< ATTRIB PARAM2 DSI shift Digital 1.1 14.6.1 */ -#define RFAL_ISODEP_ATTRIB_PARAM2_DRI_SHIFT \ - (4U) /*!< ATTRIB PARAM2 DRI shift Digital 1.1 14.6.1 */ -#define RFAL_ISODEP_ATTRIB_PARAM2_DXI_MASK \ - (0xF0U) /*!< ATTRIB PARAM2 DxI mask Digital 1.1 14.6.1 */ -#define RFAL_ISODEP_ATTRIB_PARAM2_FSDI_MASK \ - (0x0FU) /*!< ATTRIB PARAM2 FSDI mask Digital 1.1 14.6.1 */ -#define RFAL_ISODEP_ATTRIB_PARAM4_DID_MASK \ - (0x0FU) /*!< ATTRIB PARAM4 DID mask Digital 1.1 14.6.1 */ -#define RFAL_ISODEP_ATTRIB_HDR_LEN (9U) /*!< ATTRIB REQ header length Digital 1.1 14.6.1 */ - -#define RFAL_ISODEP_ATTRIB_RES_HDR_LEN \ - (1U) /*!< ATTRIB RES header length Digital 1.1 14.6.2 */ -#define RFAL_ISODEP_ATTRIB_RES_MBLIDID_POS \ - (0U) /*!< ATTRIB RES MBLI|DID position Digital 1.1 14.6.2 */ -#define RFAL_ISODEP_ATTRIB_RES_DID_MASK \ - (0x0FU) /*!< ATTRIB RES DID mask Digital 1.1 14.6.2 */ -#define RFAL_ISODEP_ATTRIB_RES_MBLI_MASK \ - (0x0FU) /*!< ATTRIB RES MBLI mask Digital 1.1 14.6.2 */ -#define RFAL_ISODEP_ATTRIB_RES_MBLI_SHIFT \ - (4U) /*!< ATTRIB RES MBLI shift Digital 1.1 14.6.2 */ - -#define RFAL_ISODEP_DID_MASK (0x0FU) /*!< ISODEP's DID mask */ -#define RFAL_ISODEP_DID_00 (0U) /*!< ISODEP's DID value 0 */ - -#define RFAL_ISODEP_FSDI_MAX_NFC (8U) /*!< Max FSDI value Digital 2.0 14.6.1.9 & B7 & B8 */ -#define RFAL_ISODEP_FSDI_MAX_NFC_21 \ - (0x0CU) /*!< Max FSDI value Digital 2.1 14.6.1.9 & Table 72 */ -#define RFAL_ISODEP_FSDI_MAX_EMV (0x0CU) /*!< Max FSDI value EMVCo 3.0 5.7.2.5 */ - -#define RFAL_ISODEP_RATS_PARAM_FSDI_MASK \ - (0xF0U) /*!< Mask bits for FSDI in RATS */ -#define RFAL_ISODEP_RATS_PARAM_FSDI_SHIFT \ - (4U) /*!< Shift for FSDI in RATS */ -#define RFAL_ISODEP_RATS_PARAM_DID_MASK \ - (0x0FU) /*!< Mask bits for DID in RATS */ - -#define RFAL_ISODEP_ATS_TL_OFFSET \ - (0x00U) /*!< Offset of TL on ATS */ -#define RFAL_ISODEP_ATS_TA_OFFSET \ - (0x02U) /*!< Offset of TA if it is present on ATS */ -#define RFAL_ISODEP_ATS_TB_OFFSET \ - (0x03U) /*!< Offset of TB if both TA and TB is present on ATS */ -#define RFAL_ISODEP_ATS_TC_OFFSET \ - (0x04U) /*!< Offset of TC if both TA,TB & TC are present on ATS */ -#define RFAL_ISODEP_ATS_HIST_OFFSET \ - (0x05U) /*!< Offset of Historical Bytes if TA, TB & TC are present on ATS */ -#define RFAL_ISODEP_ATS_TC_ADV_FEAT \ - (0x10U) /*!< Bit mask indicating support for Advanced protocol features: DID & NAD */ -#define RFAL_ISODEP_ATS_TC_DID (0x02U) /*!< Bit mask indicating support for DID */ -#define RFAL_ISODEP_ATS_TC_NAD (0x01U) /*!< Bit mask indicating support for NAD */ - -#define RFAL_ISODEP_PPS0_PPS1_PRESENT \ - (0x11U) /*!< PPS0 byte indicating that PPS1 is present */ -#define RFAL_ISODEP_PPS0_PPS1_NOT_PRESENT \ - (0x01U) /*!< PPS0 byte indicating that PPS1 is NOT present */ -#define RFAL_ISODEP_PPS1_DRI_MASK \ - (0x03U) /*!< PPS1 byte DRI mask bits */ -#define RFAL_ISODEP_PPS1_DSI_MASK \ - (0x0CU) /*!< PPS1 byte DSI mask bits */ -#define RFAL_ISODEP_PPS1_DSI_SHIFT \ - (2U) /*!< PPS1 byte DSI shift */ -#define RFAL_ISODEP_PPS1_DxI_MASK \ - (0x03U) /*!< PPS1 byte DSI/DRS mask bits */ - -/*! Delta Time for polling during Activation (ATS) : 20ms Digital 1.0 11.7.1.1 & A.7 */ -#define RFAL_ISODEP_T4T_DTIME_POLL_10 rfalConvMsTo1fc(20) - -/*! Delta Time for polling during Activation (ATS) : 16.4ms Digital 1.1 13.8.1.1 & A.6 - * Use 16 ms as testcase T4AT_BI_10_03 sends a frame exactly at the border */ -#define RFAL_ISODEP_T4T_DTIME_POLL_11 216960U - -/*! Activation frame waiting time FWT(act) = 71680/fc (~5286us) Digital 1.1 13.8.1.1 & A.6 */ -#define RFAL_ISODEP_T4T_FWT_ACTIVATION (71680U + RFAL_ISODEP_T4T_DTIME_POLL_11) - -/*! Delta frame waiting time = 16/fc Digital 1.0 11.7.1.3 & A.7*/ -#define RFAL_ISODEP_DFWT_10 16U - -/*! Delta frame waiting time = 16/fc Digital 2.0 14.8.1.3 & B.7*/ -#define RFAL_ISODEP_DFWT_20 49152U - -/* - ****************************************************************************** - * MACROS - ****************************************************************************** - */ - -#define isoDep_PCBisIBlock(pcb) \ - (((pcb) & (ISODEP_PCB_xBLOCK_MASK | ISODEP_PCB_IB_VALID_MASK)) == \ - (ISODEP_PCB_IBLOCK | ISODEP_PCB_IB_VALID_VAL)) /*!< Checks if pcb is a I-Block */ -#define isoDep_PCBisRBlock(pcb) \ - (((pcb) & (ISODEP_PCB_xBLOCK_MASK | ISODEP_PCB_RB_VALID_MASK)) == \ - (ISODEP_PCB_RBLOCK | ISODEP_PCB_RB_VALID_VAL)) /*!< Checks if pcb is a R-Block */ -#define isoDep_PCBisSBlock(pcb) \ - (((pcb) & (ISODEP_PCB_xBLOCK_MASK | ISODEP_PCB_SB_VALID_MASK)) == \ - (ISODEP_PCB_SBLOCK | ISODEP_PCB_SB_VALID_VAL)) /*!< Checks if pcb is a S-Block */ - -#define isoDep_PCBisChaining(pcb) \ - (((pcb)&ISODEP_PCB_CHAINING_BIT) == \ - ISODEP_PCB_CHAINING_BIT) /*!< Checks if pcb is indicating chaining */ - -#define isoDep_PCBisDeselect(pcb) \ - (((pcb)&ISODEP_PCB_Sxx_MASK) == \ - ISODEP_PCB_DESELECT) /*!< Checks if pcb is indicating DESELECT */ -#define isoDep_PCBisWTX(pcb) \ - (((pcb)&ISODEP_PCB_Sxx_MASK) == ISODEP_PCB_WTX) /*!< Checks if pcb is indicating WTX */ - -#define isoDep_PCBisACK(pcb) \ - (((pcb)&ISODEP_PCB_Rx_MASK) == ISODEP_PCB_ACK) /*!< Checks if pcb is indicating ACK */ -#define isoDep_PCBisNAK(pcb) \ - (((pcb)&ISODEP_PCB_Rx_MASK) == ISODEP_PCB_NAK) /*!< Checks if pcb is indicating ACK */ - -#define isoDep_PCBhasDID(pcb) \ - (((pcb)&ISODEP_PCB_DID_BIT) == ISODEP_PCB_DID_BIT) /*!< Checks if pcb is indicating DID */ -#define isoDep_PCBhasNAD(pcb) \ - (((pcb)&ISODEP_PCB_NAD_BIT) == ISODEP_PCB_NAD_BIT) /*!< Checks if pcb is indicating NAD */ - -#define isoDep_PCBisIChaining(pcb) \ - (isoDep_PCBisIBlock(pcb) && \ - isoDep_PCBisChaining(pcb)) /*!< Checks if pcb is I-Block indicating chaining*/ - -#define isoDep_PCBisSDeselect(pcb) \ - (isoDep_PCBisSBlock(pcb) && \ - isoDep_PCBisDeselect(pcb)) /*!< Checks if pcb is S-Block indicating DESELECT*/ -#define isoDep_PCBisSWTX(pcb) \ - (isoDep_PCBisSBlock(pcb) && \ - isoDep_PCBisWTX(pcb)) /*!< Checks if pcb is S-Block indicating WTX */ - -#define isoDep_PCBisRACK(pcb) \ - (isoDep_PCBisRBlock(pcb) && \ - isoDep_PCBisACK(pcb)) /*!< Checks if pcb is R-Block indicating ACK */ -#define isoDep_PCBisRNAK(pcb) \ - (isoDep_PCBisRBlock(pcb) && \ - isoDep_PCBisNAK(pcb)) /*!< Checks if pcb is R-Block indicating NAK */ - -#define isoDep_PCBIBlock(bn) \ - ((uint8_t)(0x00U | ISODEP_PCB_IBLOCK | ISODEP_PCB_B2_BIT | ((bn)&ISODEP_PCB_BN_MASK))) /*!< Returns an I-Block with the given block number (bn) */ -#define isoDep_PCBIBlockChaining(bn) \ - ((uint8_t)(isoDep_PCBIBlock(bn) | ISODEP_PCB_CHAINING_BIT)) /*!< Returns an I-Block with the given block number (bn) indicating chaining */ - -#define isoDep_PCBRBlock(bn) \ - ((uint8_t)(0x00U | ISODEP_PCB_RBLOCK | ISODEP_PCB_B6_BIT | ISODEP_PCB_B2_BIT | ((bn)&ISODEP_PCB_BN_MASK))) /*!< Returns an R-Block with the given block number (bn) */ -#define isoDep_PCBRACK(bn) \ - ((uint8_t)(isoDep_PCBRBlock(bn) | ISODEP_PCB_ACK)) /*!< Returns an R-Block with the given block number (bn) indicating ACK */ -#define isoDep_PCBRNAK(bn) \ - ((uint8_t)(isoDep_PCBRBlock(bn) | ISODEP_PCB_NAK)) /*!< Returns an R-Block with the given block number (bn) indicating NAK */ - -#define isoDep_GetBN(pcb) \ - ((uint8_t)((pcb)&ISODEP_PCB_BN_MASK)) /*!< Returns the block number (bn) from the given pcb */ -#define isoDep_GetWTXM(inf) \ - ((uint8_t)(( \ - inf)&ISODEP_SWTX_WTXM_MASK)) /*!< Returns the WTX value from the given inf byte */ -#define isoDep_isWTXMValid(wtxm) \ - (((wtxm) >= ISODEP_WTXM_MIN) && \ - ((wtxm) <= ISODEP_WTXM_MAX)) /*!< Checks if the given wtxm is valid */ - -#define isoDep_WTXMListenerMax(fwt) \ - (MIN( \ - (uint8_t)(ISODEP_FWT_LIS_MAX / (fwt)), \ - ISODEP_WTXM_MAX)) /*!< Calculates the Max WTXM value for the given fwt as a Listener */ - -#define isoDepCalcdSGFT(s) \ - (384U * ((uint32_t)1U \ - << (s))) /*!< Calculates the dSFGT with given SFGI Digital 1.1 13.8.2.1 & A.6*/ -#define isoDepCalcSGFT(s) \ - (4096U * ((uint32_t)1U \ - << (s))) /*!< Calculates the SFGT with given SFGI Digital 1.1 13.8.2 */ - -#define isoDep_PCBNextBN(bn) \ - (((uint8_t)(bn) ^ 0x01U) & \ - ISODEP_PCB_BN_MASK) /*!< Returns the value of the next block number based on bn */ -#define isoDep_PCBPrevBN(bn) \ - isoDep_PCBNextBN(bn) /*!< Returns the value of the previous block number based on bn */ -#define isoDep_ToggleBN(bn) \ - ((bn) = \ - (((bn) ^ 0x01U) & \ - ISODEP_PCB_BN_MASK)) /*!< Toggles the block number value of the given bn */ - -#define isoDep_WTXAdjust(v) \ - ((v) - ((v) >> 3)) /*!< Adjust WTX timer value to a percentage of the total, current 88% */ - -/*! ISO 14443-4 7.5.6.2 & Digital 1.1 - 15.2.6.2 The CE SHALL NOT attempt error recovery and remains in Rx mode upon Transmission or a Protocol Error */ -#define isoDepReEnableRx(rxB, rxBL, rxL) \ - rfalTransceiveBlockingTx(NULL, 0, rxB, rxBL, rxL, RFAL_TXRX_FLAGS_DEFAULT, RFAL_FWT_NONE) - -/*! Macro used for the blocking methods */ -#define rfalIsoDepRunBlocking(e, fn) \ - do { \ - (e) = (fn); \ - rfalWorker(); \ - } while((e) == ERR_BUSY) - -#define isoDepTimerStart(timer, time_ms) \ - do { \ - platformTimerDestroy(timer); \ - (timer) = platformTimerCreate((uint16_t)(time_ms)); \ - } while(0) /*!< Configures and starts the WTX timer */ -#define isoDepTimerisExpired(timer) \ - platformTimerIsExpired(timer) /*!< Checks WTX timer has expired */ -#define isoDepTimerDestroy(timer) \ - platformTimerDestroy(timer) /*!< Destroys WTX timer */ - -/* - ****************************************************************************** - * LOCAL DATA TYPES - ****************************************************************************** - */ - -/*! Internal structure to be used in handling of S(PARAMETERS) only */ -typedef struct { - uint8_t pcb; /*!< PCB byte */ - rfalIsoDepSParameter sParam; /*!< S(PARAMETERS) */ -} rfalIsoDepControlMsgSParam; - -/*! Enumeration of the possible control message types */ -typedef enum { - ISODEP_R_ACK, /*!< R-ACK Acknowledge */ - ISODEP_R_NAK, /*!< R-NACK Negative acknowledge */ - ISODEP_S_WTX, /*!< S-WTX Waiting Time Extension */ - ISODEP_S_DSL /*!< S-DSL Deselect */ -} rfalIsoDepControlMsg; - -/*! Enumeration of the IsoDep roles */ -typedef enum { - ISODEP_ROLE_PCD, /*!< Perform as Reader/PCD */ - ISODEP_ROLE_PICC /*!< Perform as Card/PICC */ -} rfalIsoDepRole; - -/*! ISO-DEP layer states */ -typedef enum { - ISODEP_ST_IDLE, /*!< Idle State */ - ISODEP_ST_PCD_TX, /*!< PCD Transmission State */ - ISODEP_ST_PCD_RX, /*!< PCD Reception State */ - ISODEP_ST_PCD_WAIT_DSL, /*!< PCD Wait for DSL response */ - - ISODEP_ST_PICC_ACT_ATS, /*!< PICC has replied to RATS (ATS) */ - ISODEP_ST_PICC_ACT_ATTRIB, /*!< PICC has replied to ATTRIB */ - ISODEP_ST_PICC_RX, /*!< PICC Reception State */ - ISODEP_ST_PICC_SWTX, /*!< PICC Waiting Time eXtension */ - ISODEP_ST_PICC_SDSL, /*!< PICC S(DSL) response ongoing */ - ISODEP_ST_PICC_TX, /*!< PICC Transmission State */ - - ISODEP_ST_PCD_ACT_RATS, /*!< PCD activation (RATS) */ - ISODEP_ST_PCD_ACT_PPS, /*!< PCD activation (PPS) */ - -} rfalIsoDepState; - -/*! Holds all ISO-DEP data(counters, buffers, ID, timeouts, frame size) */ -typedef struct { - rfalIsoDepState state; /*!< ISO-DEP module state */ - rfalIsoDepRole role; /*!< Current ISO-DEP role */ - - uint8_t blockNumber; /*!< Current block number */ - uint8_t did; /*!< Current DID */ - uint8_t nad; /*!< Current DID */ - uint8_t cntIRetrys; /*!< I-Block retry counter */ - uint8_t cntRRetrys; /*!< R-Block retry counter */ - uint8_t cntSDslRetrys; /*!< S(DESELECT) retry counter */ - uint8_t cntSWtxRetrys; /*!< Overall S(WTX) retry counter */ - uint8_t cntSWtxNack; /*!< R(NACK) answered with S(WTX) counter */ - uint32_t fwt; /*!< Current FWT (Frame Waiting Time) */ - uint32_t dFwt; /*!< Current delta FWT */ - uint16_t fsx; /*!< Current FSx FSC or FSD (max Frame size) */ - bool isTxChaining; /*!< Flag for chaining on Tx */ - bool isRxChaining; /*!< Flag for chaining on Rx */ - uint8_t* txBuf; /*!< Tx buffer pointer */ - uint8_t* rxBuf; /*!< Rx buffer pointer */ - uint16_t txBufLen; /*!< Tx buffer length */ - uint16_t rxBufLen; /*!< Rx buffer length */ - uint8_t txBufInfPos; /*!< Start of payload in txBuf */ - uint8_t rxBufInfPos; /*!< Start of payload in rxBuf */ - - uint16_t ourFsx; /*!< Our current FSx FSC or FSD (Frame size) */ - uint8_t lastPCB; /*!< Last PCB sent */ - uint8_t lastWTXM; /*!< Last WTXM sent */ - uint8_t atsTA; /*!< TA on ATS */ - uint8_t hdrLen; /*!< Current ISO-DEP length */ - rfalBitRate txBR; /*!< Current Tx Bit Rate */ - rfalBitRate rxBR; /*!< Current Rx Bit Rate */ - uint16_t* rxLen; /*!< Output parameter ptr to Rx length */ - bool* rxChaining; /*!< Output parameter ptr to Rx chaining flag */ - uint32_t WTXTimer; /*!< Timer used for WTX */ - bool lastDID00; /*!< Last PCD block had DID flag (for DID = 0) */ - - bool isTxPending; /*!< Flag pending Block while waiting WTX Ack */ - bool isWait4WTX; /*!< Flag for waiting WTX Ack */ - - uint8_t maxRetriesI; /*!< Number of retries for a I-Block */ - uint8_t maxRetriesR; /*!< Number of retries for a R-Block */ - uint8_t maxRetriesSDSL; /*!< Number of retries for S(DESELECT) errors */ - uint8_t maxRetriesSWTX; /*!< Number of retries for S(WTX) errors */ - uint8_t maxRetriesSnWTX; /*!< Number of retries S(WTX) replied w NACK */ - uint8_t maxRetriesRATS; /*!< Number of retries for RATS */ - - rfalComplianceMode compMode; /*!< Compliance mode */ - - uint8_t ctrlBuf[ISODEP_CONTROLMSG_BUF_LEN]; /*!< Control msg buf */ - uint16_t ctrlRxLen; /*!< Control msg rcvd len */ - - union { /* PRQA S 0750 # MISRA 19.2 - Members of the union will not be used concurrently, only one frame at a time */ -#if RFAL_FEATURE_NFCA - rfalIsoDepRats ratsReq; - rfalIsoDepPpsReq ppsReq; -#endif /* RFAL_FEATURE_NFCA */ - -#if RFAL_FEATURE_NFCB - rfalIsoDepAttribCmd attribReq; -#endif /* RFAL_FEATURE_NFCB */ - } actv; /*!< Activation buffer */ - - uint8_t* rxLen8; /*!< Receive length (8-bit) */ - rfalIsoDepDevice* actvDev; /*!< Activation Device Info */ - rfalIsoDepListenActvParam actvParam; /*!< Listen Activation context */ - - rfalIsoDepApduTxRxParam APDUParam; /*!< APDU TxRx params */ - uint16_t APDUTxPos; /*!< APDU Tx position */ - uint16_t APDURxPos; /*!< APDU Rx position */ - bool isAPDURxChaining; /*!< APDU Transceive chaining flag */ - -} rfalIsoDep; - -/* - ****************************************************************************** - * LOCAL VARIABLES - ****************************************************************************** - */ - -static rfalIsoDep gIsoDep; /*!< ISO-DEP Module instance */ - -/* - ****************************************************************************** - * LOCAL FUNCTION PROTOTYPES - ****************************************************************************** - */ -static void isoDepClearCounters(void); -static ReturnCode - isoDepTx(uint8_t pcb, const uint8_t* txBuf, uint8_t* infBuf, uint16_t infLen, uint32_t fwt); -static ReturnCode isoDepHandleControlMsg(rfalIsoDepControlMsg controlMsg, uint8_t param); -static void rfalIsoDepApdu2IBLockParam( - rfalIsoDepApduTxRxParam apduParam, - rfalIsoDepTxRxParam* iBlockParam, - uint16_t txPos, - uint16_t rxPos); - -#if RFAL_FEATURE_ISO_DEP_POLL -static ReturnCode isoDepDataExchangePCD(uint16_t* outActRxLen, bool* outIsChaining); -static void rfalIsoDepCalcBitRate( - rfalBitRate maxAllowedBR, - uint8_t piccBRCapability, - rfalBitRate* dsi, - rfalBitRate* dri); -static uint32_t rfalIsoDepSFGI2SFGT(uint8_t sfgi); - -#if RFAL_FEATURE_NFCA -static ReturnCode - rfalIsoDepStartRATS(rfalIsoDepFSxI FSDI, uint8_t DID, rfalIsoDepAts* ats, uint8_t* atsLen); -static ReturnCode rfalIsoDepGetRATSStatus(void); -static ReturnCode - rfalIsoDepStartPPS(uint8_t DID, rfalBitRate DSI, rfalBitRate DRI, rfalIsoDepPpsRes* ppsRes); -static ReturnCode rfalIsoDepGetPPSSTatus(void); -#endif /* RFAL_FEATURE_NFCA */ - -#if RFAL_FEATURE_NFCB -static ReturnCode rfalIsoDepStartATTRIB( - const uint8_t* nfcid0, - uint8_t PARAM1, - rfalBitRate DSI, - rfalBitRate DRI, - rfalIsoDepFSxI FSDI, - uint8_t PARAM3, - uint8_t DID, - const uint8_t* HLInfo, - uint8_t HLInfoLen, - uint32_t fwt, - rfalIsoDepAttribRes* attribRes, - uint8_t* attribResLen); -static ReturnCode rfalIsoDepGetATTRIBStatus(void); -#endif /* RFAL_FEATURE_NFCB */ - -#endif /* RFAL_FEATURE_ISO_DEP_POLL */ - -#if RFAL_FEATURE_ISO_DEP_LISTEN -static ReturnCode isoDepDataExchangePICC(void); -static ReturnCode isoDepReSendControlMsg(void); -#endif - -/* - ****************************************************************************** - * LOCAL FUNCTIONS - ****************************************************************************** - */ - -/*******************************************************************************/ -static void isoDepClearCounters(void) { - gIsoDep.cntIRetrys = 0; - gIsoDep.cntRRetrys = 0; - gIsoDep.cntSDslRetrys = 0; - gIsoDep.cntSWtxRetrys = 0; - gIsoDep.cntSWtxNack = 0; -} - -/*******************************************************************************/ -static ReturnCode - isoDepTx(uint8_t pcb, const uint8_t* txBuf, uint8_t* infBuf, uint16_t infLen, uint32_t fwt) { - uint8_t* txBlock; - uint16_t txBufLen; - uint8_t computedPcb; - rfalTransceiveContext ctx; - - txBlock = infBuf; /* Point to beginning of the INF, and go backwards */ - gIsoDep.lastPCB = pcb; /* Store the last PCB sent */ - - if(infLen > 0U) { - if(((uint32_t)infBuf - (uint32_t)txBuf) < - gIsoDep.hdrLen) /* Check that we can fit the header in the given space */ - { - return ERR_NOMEM; - } - } - - /*******************************************************************************/ - /* Compute optional PCB bits */ - computedPcb = pcb; - if((gIsoDep.did != RFAL_ISODEP_NO_DID) || - ((gIsoDep.did == RFAL_ISODEP_DID_00) && gIsoDep.lastDID00)) { - computedPcb |= ISODEP_PCB_DID_BIT; - } - if(gIsoDep.nad != RFAL_ISODEP_NO_NAD) { - computedPcb |= ISODEP_PCB_NAD_BIT; - } - if((gIsoDep.isTxChaining) && (isoDep_PCBisIBlock(computedPcb))) { - computedPcb |= ISODEP_PCB_CHAINING_BIT; - } - - /*******************************************************************************/ - /* Compute Payload on the given txBuf, start by the PCB | DID | NAD | before INF */ - - if(gIsoDep.nad != RFAL_ISODEP_NO_NAD) { - *(--txBlock) = gIsoDep.nad; /* NAD is optional */ - } - - if((gIsoDep.did != RFAL_ISODEP_NO_DID) || - ((gIsoDep.did == RFAL_ISODEP_DID_00) && gIsoDep.lastDID00)) { - *(--txBlock) = gIsoDep.did; /* DID is optional */ - } - - *(--txBlock) = computedPcb; /* PCB always present */ - - txBufLen = - (infLen + - (uint16_t)((uint32_t)infBuf - (uint32_t)txBlock)); /* Calculate overall buffer size */ - - if(txBufLen > (gIsoDep.fsx - - ISODEP_CRC_LEN)) /* Check if msg length violates the maximum frame size FSC */ - { - return ERR_NOTSUPP; - } - - rfalCreateByteFlagsTxRxContext( - ctx, - txBlock, - txBufLen, - gIsoDep.rxBuf, - gIsoDep.rxBufLen, - gIsoDep.rxLen, - RFAL_TXRX_FLAGS_DEFAULT, - ((gIsoDep.role == ISODEP_ROLE_PICC) ? RFAL_FWT_NONE : fwt)); - return rfalStartTransceive(&ctx); -} - -/*******************************************************************************/ -static ReturnCode isoDepHandleControlMsg(rfalIsoDepControlMsg controlMsg, uint8_t param) { - uint8_t pcb; - uint8_t infLen; - uint32_t fwtTemp; - - infLen = 0; - fwtTemp = (gIsoDep.fwt + gIsoDep.dFwt); - ST_MEMSET(gIsoDep.ctrlBuf, 0x00, ISODEP_CONTROLMSG_BUF_LEN); - - switch(controlMsg) { - /*******************************************************************************/ - case ISODEP_R_ACK: - - if(gIsoDep.cntRRetrys++ > gIsoDep.maxRetriesR) { - return ERR_TIMEOUT; /* NFC Forum mandates timeout or transmission error depending on previous errors */ - } - - pcb = isoDep_PCBRACK(gIsoDep.blockNumber); - break; - - /*******************************************************************************/ - case ISODEP_R_NAK: - - if((gIsoDep.cntRRetrys++ > gIsoDep.maxRetriesR) || /* Max R Block retries reached */ - (gIsoDep.cntSWtxNack >= - gIsoDep - .maxRetriesSnWTX)) /* Max number PICC is allowed to respond with S(WTX) to R(NAK) */ - { - return ERR_TIMEOUT; - } - - pcb = isoDep_PCBRNAK(gIsoDep.blockNumber); - break; - - /*******************************************************************************/ - case ISODEP_S_WTX: - - if((gIsoDep.cntSWtxRetrys++ > gIsoDep.maxRetriesSWTX) && - (gIsoDep.maxRetriesSWTX != RFAL_ISODEP_MAX_WTX_RETRYS_ULTD)) { - return ERR_PROTO; - } - - /* Check if WTXM is valid */ - if(!isoDep_isWTXMValid(param)) { - return ERR_PROTO; - } - - if(gIsoDep.role == ISODEP_ROLE_PCD) { - /* Calculate temp Wait Time eXtension */ - fwtTemp = (gIsoDep.fwt * param); - fwtTemp = MIN(RFAL_ISODEP_MAX_FWT, fwtTemp); - fwtTemp += gIsoDep.dFwt; - } - - pcb = ISODEP_PCB_SWTX; - gIsoDep.ctrlBuf[RFAL_ISODEP_PCB_LEN + RFAL_ISODEP_DID_LEN + infLen++] = param; - break; - - /*******************************************************************************/ - case ISODEP_S_DSL: - - if(gIsoDep.cntSDslRetrys++ > gIsoDep.maxRetriesSDSL) { - return ERR_TIMEOUT; /* NFC Forum mandates timeout or transmission error depending on previous errors */ - } - - if(gIsoDep.role == ISODEP_ROLE_PCD) { - /* Digital 1.0 - 13.2.7.3 Poller must wait fwtDEACTIVATION */ - fwtTemp = ISODEP_FWT_DEACTIVATION; - gIsoDep.state = ISODEP_ST_PCD_WAIT_DSL; - } - pcb = ISODEP_PCB_SDSL; - break; - - /*******************************************************************************/ - default: - return ERR_INTERNAL; - } - - return isoDepTx( - pcb, - gIsoDep.ctrlBuf, - &gIsoDep.ctrlBuf[RFAL_ISODEP_PCB_LEN + RFAL_ISODEP_DID_LEN], - infLen, - fwtTemp); -} - -#if RFAL_FEATURE_ISO_DEP_LISTEN -/*******************************************************************************/ -static ReturnCode isoDepReSendControlMsg(void) { - if(isoDep_PCBisRACK(gIsoDep.lastPCB)) { - return isoDepHandleControlMsg(ISODEP_R_ACK, RFAL_ISODEP_NO_PARAM); - } - - if(isoDep_PCBisRNAK(gIsoDep.lastPCB)) { - return isoDepHandleControlMsg(ISODEP_R_NAK, RFAL_ISODEP_NO_PARAM); - } - - if(isoDep_PCBisSDeselect(gIsoDep.lastPCB)) { - return isoDepHandleControlMsg(ISODEP_S_DSL, RFAL_ISODEP_NO_PARAM); - } - - if(isoDep_PCBisSWTX(gIsoDep.lastPCB)) { - return isoDepHandleControlMsg(ISODEP_S_WTX, gIsoDep.lastWTXM); - } - return ERR_WRONG_STATE; -} -#endif /* RFAL_FEATURE_ISO_DEP_LISTEN */ - -/* - ****************************************************************************** - * GLOBAL FUNCTIONS - ****************************************************************************** - */ - -/*******************************************************************************/ -void rfalIsoDepInitialize(void) { - gIsoDep.state = ISODEP_ST_IDLE; - gIsoDep.role = ISODEP_ROLE_PCD; - gIsoDep.did = RFAL_ISODEP_NO_DID; - gIsoDep.nad = RFAL_ISODEP_NO_NAD; - gIsoDep.blockNumber = 0; - gIsoDep.isTxChaining = false; - gIsoDep.isRxChaining = false; - gIsoDep.lastDID00 = false; - gIsoDep.lastPCB = ISODEP_PCB_INVALID; - gIsoDep.fsx = (uint16_t)RFAL_ISODEP_FSX_16; - gIsoDep.ourFsx = (uint16_t)RFAL_ISODEP_FSX_16; - gIsoDep.hdrLen = RFAL_ISODEP_PCB_LEN; - - gIsoDep.rxLen = NULL; - gIsoDep.rxBuf = NULL; - gIsoDep.rxBufInfPos = 0U; - gIsoDep.txBufInfPos = 0U; - - gIsoDep.isTxPending = false; - gIsoDep.isWait4WTX = false; - - gIsoDep.compMode = RFAL_COMPLIANCE_MODE_NFC; - gIsoDep.maxRetriesR = RFAL_ISODEP_MAX_R_RETRYS; - gIsoDep.maxRetriesI = RFAL_ISODEP_MAX_I_RETRYS; - gIsoDep.maxRetriesSDSL = RFAL_ISODEP_MAX_DSL_RETRYS; - gIsoDep.maxRetriesSWTX = RFAL_ISODEP_MAX_WTX_RETRYS; - gIsoDep.maxRetriesSnWTX = RFAL_ISODEP_MAX_WTX_NACK_RETRYS; - gIsoDep.maxRetriesRATS = RFAL_ISODEP_RATS_RETRIES; - - gIsoDep.APDURxPos = 0; - gIsoDep.APDUTxPos = 0; - gIsoDep.APDUParam.rxLen = NULL; - gIsoDep.APDUParam.rxBuf = NULL; - gIsoDep.APDUParam.txBuf = NULL; - - isoDepClearCounters(); - - /* Destroy any ongoing WTX timer */ - isoDepTimerDestroy(gIsoDep.WTXTimer); - gIsoDep.WTXTimer = 0U; -} - -/*******************************************************************************/ -void rfalIsoDepInitializeWithParams( - rfalComplianceMode compMode, - uint8_t maxRetriesR, - uint8_t maxRetriesSnWTX, - uint8_t maxRetriesSWTX, - uint8_t maxRetriesSDSL, - uint8_t maxRetriesI, - uint8_t maxRetriesRATS) { - rfalIsoDepInitialize(); - - gIsoDep.compMode = compMode; - gIsoDep.maxRetriesR = maxRetriesR; - gIsoDep.maxRetriesSnWTX = maxRetriesSnWTX; - gIsoDep.maxRetriesSWTX = maxRetriesSWTX; - gIsoDep.maxRetriesSDSL = maxRetriesSDSL; - gIsoDep.maxRetriesI = maxRetriesI; - gIsoDep.maxRetriesRATS = maxRetriesRATS; -} - -#if RFAL_FEATURE_ISO_DEP_POLL -/*******************************************************************************/ -static ReturnCode isoDepDataExchangePCD(uint16_t* outActRxLen, bool* outIsChaining) { - ReturnCode ret; - uint8_t rxPCB; - - /* Check out parameters */ - if((outActRxLen == NULL) || (outIsChaining == NULL)) { - return ERR_PARAM; - } - - *outIsChaining = false; - - /* Calculate header required and check if the buffers InfPositions are suitable */ - gIsoDep.hdrLen = RFAL_ISODEP_PCB_LEN; - if(gIsoDep.did != RFAL_ISODEP_NO_DID) { - gIsoDep.hdrLen += RFAL_ISODEP_DID_LEN; - } - if(gIsoDep.nad != RFAL_ISODEP_NO_NAD) { - gIsoDep.hdrLen += RFAL_ISODEP_NAD_LEN; - } - - /* Check if there is enough space before the infPos to append ISO-DEP headers on rx and tx */ - if((gIsoDep.rxBufInfPos < gIsoDep.hdrLen) || (gIsoDep.txBufInfPos < gIsoDep.hdrLen)) { - return ERR_PARAM; - } - - /*******************************************************************************/ - switch(gIsoDep.state) { - /*******************************************************************************/ - case ISODEP_ST_IDLE: - return ERR_NONE; - - /*******************************************************************************/ - case ISODEP_ST_PCD_TX: - ret = isoDepTx( - isoDep_PCBIBlock(gIsoDep.blockNumber), - gIsoDep.txBuf, - &gIsoDep.txBuf[gIsoDep.txBufInfPos], - gIsoDep.txBufLen, - (gIsoDep.fwt + gIsoDep.dFwt)); - switch(ret) { - case ERR_NONE: - gIsoDep.state = ISODEP_ST_PCD_RX; - break; - - default: - return ret; - } - /* fall through */ - - /*******************************************************************************/ - case ISODEP_ST_PCD_WAIT_DSL: /* PRQA S 2003 # MISRA 16.3 - Intentional fall through */ - case ISODEP_ST_PCD_RX: - - ret = rfalGetTransceiveStatus(); - switch(ret) { - /* Data rcvd with error or timeout -> Send R-NAK */ - case ERR_TIMEOUT: - case ERR_CRC: - case ERR_PAR: - case ERR_FRAMING: /* added to handle test cases scenario TC_POL_NFCB_T4AT_BI_82_x_y & TC_POL_NFCB_T4BT_BI_82_x_y */ - case ERR_INCOMPLETE_BYTE: /* added to handle test cases scenario TC_POL_NFCB_T4AT_BI_82_x_y & TC_POL_NFCB_T4BT_BI_82_x_y */ - - if(gIsoDep - .isRxChaining) { /* Rule 5 - In PICC chaining when a invalid/timeout occurs -> R-ACK */ - EXIT_ON_ERR(ret, isoDepHandleControlMsg(ISODEP_R_ACK, RFAL_ISODEP_NO_PARAM)); - } else if( - gIsoDep.state == - ISODEP_ST_PCD_WAIT_DSL) { /* Rule 8 - If s-Deselect response fails MAY retransmit */ - EXIT_ON_ERR(ret, isoDepHandleControlMsg(ISODEP_S_DSL, RFAL_ISODEP_NO_PARAM)); - } else { /* Rule 4 - When a invalid block or timeout occurs -> R-NACK */ - EXIT_ON_ERR(ret, isoDepHandleControlMsg(ISODEP_R_NAK, RFAL_ISODEP_NO_PARAM)); - } - return ERR_BUSY; - - case ERR_NONE: - break; - - case ERR_BUSY: - return ERR_BUSY; /* Debug purposes */ - - default: - return ret; - } - - /*******************************************************************************/ - /* No error, process incoming msg */ - /*******************************************************************************/ - - (*outActRxLen) = rfalConvBitsToBytes(*outActRxLen); - - /* Check rcvd msg length, cannot be less then the expected header */ - if(((*outActRxLen) < gIsoDep.hdrLen) || ((*outActRxLen) >= gIsoDep.ourFsx)) { - return ERR_PROTO; - } - - /* Grab rcvd PCB */ - rxPCB = gIsoDep.rxBuf[ISODEP_PCB_POS]; - - /* EMVCo doesn't allow usage of for CID or NAD EMVCo 2.6 TAble 10.2 */ - if((gIsoDep.compMode == RFAL_COMPLIANCE_MODE_EMV) && - (isoDep_PCBhasDID(rxPCB) || isoDep_PCBhasNAD(rxPCB))) { - return ERR_PROTO; - } - - /* If we are expecting DID, check if PCB signals its presence and if device ID match*/ - if((gIsoDep.did != RFAL_ISODEP_NO_DID) && - (!isoDep_PCBhasDID(rxPCB) || (gIsoDep.did != gIsoDep.rxBuf[ISODEP_DID_POS]))) { - return ERR_PROTO; - } - - /*******************************************************************************/ - /* Process S-Block */ - /*******************************************************************************/ - if(isoDep_PCBisSBlock(rxPCB)) { - /* Check if is a Wait Time eXtension */ - if(isoDep_PCBisSWTX(rxPCB)) { - /* Check if PICC has requested S(WTX) as response to R(NAK) EMVCo 3.0 10.3.5.5 / Digital 2.0 16.2.6.5 */ - if(isoDep_PCBisRNAK(gIsoDep.lastPCB)) { - gIsoDep.cntSWtxNack++; /* Count S(WTX) upon R(NAK) */ - gIsoDep.cntRRetrys = 0; /* Reset R-Block counter has PICC has responded */ - } else { - gIsoDep.cntSWtxNack = 0; /* Reset R(NACK)->S(WTX) counter */ - } - - /* Rule 3 - respond to S-block: get 1st INF byte S(STW): Power + WTXM */ - EXIT_ON_ERR( - ret, - isoDepHandleControlMsg( - ISODEP_S_WTX, isoDep_GetWTXM(gIsoDep.rxBuf[gIsoDep.hdrLen]))); - return ERR_BUSY; - } - - /* Check if is a deselect response */ - if(isoDep_PCBisSDeselect(rxPCB)) { - if(gIsoDep.state == ISODEP_ST_PCD_WAIT_DSL) { - rfalIsoDepInitialize(); /* Session finished reInit vars */ - return ERR_NONE; - } - - /* Deselect response not expected */ - /* fall through to PROTO error */ - } - /* Unexpected S-Block */ - return ERR_PROTO; - } - - /*******************************************************************************/ - /* Process R-Block */ - /*******************************************************************************/ - else if(isoDep_PCBisRBlock(rxPCB)) { - if(isoDep_PCBisRACK(rxPCB)) /* Check if is a R-ACK */ - { - if(isoDep_GetBN(rxPCB) == gIsoDep.blockNumber) /* Expected block number */ - { - /* Rule B - ACK with expected bn -> Increment block number */ - gIsoDep.blockNumber = isoDep_PCBNextBN(gIsoDep.blockNumber); - - /* R-ACK only allowed when PCD chaining */ - if(!gIsoDep.isTxChaining) { - return ERR_PROTO; - } - - /* Rule 7 - Chaining transaction done, continue chaining */ - isoDepClearCounters(); - return ERR_NONE; /* This block has been transmitted */ - } else { - /* Rule 6 - R-ACK with wrong block number retransmit */ - /* Digital 2.0 16.2.5.4 - Retransmit maximum two times */ - /* EMVCo 3.0 10.3.4.3 - PCD may re-transmit the last I-Block or report error */ - if(gIsoDep.cntIRetrys++ < gIsoDep.maxRetriesI) { - gIsoDep.cntRRetrys = 0; /* Clear R counter only */ - gIsoDep.state = ISODEP_ST_PCD_TX; - return ERR_BUSY; - } - return ERR_TIMEOUT; /* NFC Forum mandates timeout or transmission error depending on previous errors */ - } - } else /* Unexpected R-Block */ - { - return ERR_PROTO; - } - } - - /*******************************************************************************/ - /* Process I-Block */ - /*******************************************************************************/ - else if(isoDep_PCBisIBlock(rxPCB)) { - /*******************************************************************************/ - /* is PICC performing chaining */ - if(isoDep_PCBisChaining(rxPCB)) { - gIsoDep.isRxChaining = true; - *outIsChaining = true; - - if(isoDep_GetBN(rxPCB) == gIsoDep.blockNumber) { - /* Rule B - ACK with correct block number -> Increase Block number */ - isoDep_ToggleBN(gIsoDep.blockNumber); - - isoDepClearCounters(); /* Clear counters in case R counter is already at max */ - - /* Rule 2 - Send ACK */ - EXIT_ON_ERR(ret, isoDepHandleControlMsg(ISODEP_R_ACK, RFAL_ISODEP_NO_PARAM)); - - /* Received I-Block with chaining, send current data to DH */ - - /* remove ISO DEP header, check is necessary to move the INF data on the buffer */ - *outActRxLen -= gIsoDep.hdrLen; - if((gIsoDep.hdrLen != gIsoDep.rxBufInfPos) && (*outActRxLen > 0U)) { - ST_MEMMOVE( - &gIsoDep.rxBuf[gIsoDep.rxBufInfPos], - &gIsoDep.rxBuf[gIsoDep.hdrLen], - *outActRxLen); - } - - isoDepClearCounters(); - return ERR_AGAIN; /* Send Again signalling to run again, but some chaining data has arrived */ - } else { - /* Rule 5 - PICC chaining invalid I-Block -> R-ACK */ - EXIT_ON_ERR(ret, isoDepHandleControlMsg(ISODEP_R_ACK, RFAL_ISODEP_NO_PARAM)); - } - return ERR_BUSY; - } - - gIsoDep.isRxChaining = false; /* clear PICC chaining flag */ - - if(isoDep_GetBN(rxPCB) == gIsoDep.blockNumber) { - /* Rule B - I-Block with correct block number -> Increase Block number */ - isoDep_ToggleBN(gIsoDep.blockNumber); - - /* I-Block transaction done successfully */ - - /* remove ISO DEP header, check is necessary to move the INF data on the buffer */ - *outActRxLen -= gIsoDep.hdrLen; - if((gIsoDep.hdrLen != gIsoDep.rxBufInfPos) && (*outActRxLen > 0U)) { - ST_MEMMOVE( - &gIsoDep.rxBuf[gIsoDep.rxBufInfPos], - &gIsoDep.rxBuf[gIsoDep.hdrLen], - *outActRxLen); - } - - gIsoDep.state = ISODEP_ST_IDLE; - isoDepClearCounters(); - return ERR_NONE; - } else { - if((gIsoDep.compMode != RFAL_COMPLIANCE_MODE_ISO)) { - /* Invalid Block (not chaining) -> Raise error Digital 1.1 15.2.6.4 EMVCo 2.6 10.3.5.4 */ - return ERR_PROTO; - } - - /* Rule 4 - Invalid Block -> R-NAK */ - EXIT_ON_ERR(ret, isoDepHandleControlMsg(ISODEP_R_NAK, RFAL_ISODEP_NO_PARAM)); - return ERR_BUSY; - } - } else /* not S/R/I - Block */ - { - return ERR_PROTO; - } - /* fall through */ - - /*******************************************************************************/ - default: /* PRQA S 2003 # MISRA 16.3 - Intentional fall through */ - /* MISRA 16.4: no empty default (comment will suffice) */ - break; - } - - return ERR_INTERNAL; -} - -/*******************************************************************************/ -ReturnCode rfalIsoDepDeselect(void) { - ReturnCode ret; - uint32_t cntRerun; - bool dummyB; - - /*******************************************************************************/ - /* Using local static vars and static config to cope with a Deselect after * - * RATS\ATTRIB without any I-Block exchanged */ - gIsoDep.rxLen = &gIsoDep.ctrlRxLen; - gIsoDep.rxBuf = gIsoDep.ctrlBuf; - gIsoDep.rxBufLen = ISODEP_CONTROLMSG_BUF_LEN - (RFAL_ISODEP_PCB_LEN + RFAL_ISODEP_DID_LEN); - gIsoDep.rxBufInfPos = (RFAL_ISODEP_PCB_LEN + RFAL_ISODEP_DID_LEN); - gIsoDep.txBufInfPos = (RFAL_ISODEP_PCB_LEN + RFAL_ISODEP_DID_LEN); - - /*******************************************************************************/ - /* The Deselect process is being done blocking, Digital 1.0 - 13.2.7.1 MUST wait response and retry*/ - /* Set the maximum reruns while we will wait for a response */ - cntRerun = ISODEP_MAX_RERUNS; - - /* Send DSL request and run protocol until get a response, error or "timeout" */ - EXIT_ON_ERR(ret, isoDepHandleControlMsg(ISODEP_S_DSL, RFAL_ISODEP_NO_PARAM)); - do { - ret = isoDepDataExchangePCD(gIsoDep.rxLen, &dummyB); - rfalWorker(); - } while(((cntRerun--) != 0U) && (ret == ERR_BUSY)); - - rfalIsoDepInitialize(); - return ((cntRerun == 0U) ? ERR_TIMEOUT : ret); -} - -#endif /* RFAL_FEATURE_ISO_DEP_POLL */ - -/*******************************************************************************/ -uint32_t rfalIsoDepFWI2FWT(uint8_t fwi) { - uint32_t result; - uint8_t tmpFWI; - - tmpFWI = fwi; - - /* RFU values -> take the default value - * Digital 1.0 11.6.2.17 FWI[1,14] - * Digital 1.1 7.6.2.22 FWI[0,14] - * EMVCo 2.6 Table A.5 FWI[0,14] */ - if(tmpFWI > ISODEP_FWI_MAX) { - tmpFWI = RFAL_ISODEP_FWI_DEFAULT; - } - - /* FWT = (256 x 16/fC) x 2^FWI => 2^(FWI+12) Digital 1.1 13.8.1 & 7.9.1 */ - - result = ((uint32_t)1U << (tmpFWI + 12U)); - result = MIN(RFAL_ISODEP_MAX_FWT, result); /* Maximum Frame Waiting Time must be fulfilled */ - - return result; -} - -/*******************************************************************************/ -uint16_t rfalIsoDepFSxI2FSx(uint8_t FSxI) { - uint16_t fsx; - uint8_t fsi; - - /* Enforce maximum FSxI/FSx allowed - NFC Forum and EMVCo differ */ - fsi = - ((gIsoDep.compMode == RFAL_COMPLIANCE_MODE_EMV) ? MIN(FSxI, RFAL_ISODEP_FSDI_MAX_EMV) : - MIN(FSxI, RFAL_ISODEP_FSDI_MAX_NFC)); - - switch(fsi) { - case(uint8_t)RFAL_ISODEP_FSXI_16: - fsx = (uint16_t)RFAL_ISODEP_FSX_16; - break; - case(uint8_t)RFAL_ISODEP_FSXI_24: - fsx = (uint16_t)RFAL_ISODEP_FSX_24; - break; - case(uint8_t)RFAL_ISODEP_FSXI_32: - fsx = (uint16_t)RFAL_ISODEP_FSX_32; - break; - case(uint8_t)RFAL_ISODEP_FSXI_40: - fsx = (uint16_t)RFAL_ISODEP_FSX_40; - break; - case(uint8_t)RFAL_ISODEP_FSXI_48: - fsx = (uint16_t)RFAL_ISODEP_FSX_48; - break; - case(uint8_t)RFAL_ISODEP_FSXI_64: - fsx = (uint16_t)RFAL_ISODEP_FSX_64; - break; - case(uint8_t)RFAL_ISODEP_FSXI_96: - fsx = (uint16_t)RFAL_ISODEP_FSX_96; - break; - case(uint8_t)RFAL_ISODEP_FSXI_128: - fsx = (uint16_t)RFAL_ISODEP_FSX_128; - break; - case(uint8_t)RFAL_ISODEP_FSXI_256: - fsx = (uint16_t)RFAL_ISODEP_FSX_256; - break; - case(uint8_t)RFAL_ISODEP_FSXI_512: - fsx = (uint16_t)RFAL_ISODEP_FSX_512; - break; - case(uint8_t)RFAL_ISODEP_FSXI_1024: - fsx = (uint16_t)RFAL_ISODEP_FSX_1024; - break; - case(uint8_t)RFAL_ISODEP_FSXI_2048: - fsx = (uint16_t)RFAL_ISODEP_FSX_2048; - break; - case(uint8_t)RFAL_ISODEP_FSXI_4096: - fsx = (uint16_t)RFAL_ISODEP_FSX_4096; - break; - default: - fsx = (uint16_t)RFAL_ISODEP_FSX_256; - break; - } - return fsx; -} - -#if RFAL_FEATURE_ISO_DEP_LISTEN - -/*******************************************************************************/ -bool rfalIsoDepIsRats(const uint8_t* buf, uint8_t bufLen) { - if(buf != NULL) { - if((RFAL_ISODEP_CMD_RATS == (uint8_t)*buf) && (sizeof(rfalIsoDepRats) == bufLen)) { - return true; - } - } - return false; -} - -/*******************************************************************************/ -bool rfalIsoDepIsAttrib(const uint8_t* buf, uint8_t bufLen) { - if(buf != NULL) { - if((RFAL_ISODEP_CMD_ATTRIB == (uint8_t)*buf) && - (RFAL_ISODEP_ATTRIB_REQ_MIN_LEN <= bufLen) && - ((RFAL_ISODEP_ATTRIB_REQ_MIN_LEN + RFAL_ISODEP_ATTRIB_HLINFO_LEN) >= bufLen)) { - return true; - } - } - return false; -} - -/*******************************************************************************/ -ReturnCode rfalIsoDepListenStartActivation( - rfalIsoDepAtsParam* atsParam, - const rfalIsoDepAttribResParam* attribResParam, - const uint8_t* buf, - uint16_t bufLen, - rfalIsoDepListenActvParam actParam) { - uint8_t* txBuf; - uint8_t bufIt; - const uint8_t* buffer = buf; - - /*******************************************************************************/ - bufIt = 0; - txBuf = - (uint8_t*)actParam - .rxBuf; /* Use the rxBuf as TxBuf as well, the struct enforces a size enough MAX( NFCA_ATS_MAX_LEN, NFCB_ATTRIB_RES_MAX_LEN ) */ - gIsoDep.txBR = RFAL_BR_106; - gIsoDep.rxBR = RFAL_BR_106; - - /* Check for a valid buffer pointer */ - if(buffer == NULL) { - return ERR_PARAM; - } - - /*******************************************************************************/ - if(*buffer == RFAL_ISODEP_CMD_RATS) { - /* Check ATS parameters */ - if(atsParam == NULL) { - return ERR_PARAM; - } - - /* If requested copy RATS to device info */ - if(actParam.isoDepDev != NULL) { - ST_MEMCPY( - (uint8_t*)&actParam.isoDepDev->activation.A.Poller.RATS, - buffer, - sizeof(rfalIsoDepRats)); /* Copy RATS' CMD + PARAM */ - } - - /*******************************************************************************/ - /* Process RATS */ - buffer++; - gIsoDep.fsx = rfalIsoDepFSxI2FSx( - (((*buffer) & RFAL_ISODEP_RATS_PARAM_FSDI_MASK) >> RFAL_ISODEP_RATS_PARAM_FSDI_SHIFT)); - gIsoDep.did = (*buffer & RFAL_ISODEP_DID_MASK); - - /*******************************************************************************/ - /* Digital 1.1 13.6.1.8 - DID as to between 0 and 14 */ - if(gIsoDep.did > RFAL_ISODEP_DID_MAX) { - return ERR_PROTO; - } - - /* Check if we are configured to support DID */ - if((gIsoDep.did != RFAL_ISODEP_DID_00) && (!atsParam->didSupport)) { - return ERR_NOTSUPP; - } - - /*******************************************************************************/ - /* Check RFAL supported bit rates */ - if((!(RFAL_SUPPORT_BR_CE_A_212) && - (((atsParam->ta & RFAL_ISODEP_ATS_TA_DPL_212) != 0U) || - ((atsParam->ta & RFAL_ISODEP_ATS_TA_DLP_212) != 0U))) || - (!(RFAL_SUPPORT_BR_CE_A_424) && - (((atsParam->ta & RFAL_ISODEP_ATS_TA_DPL_424) != 0U) || - ((atsParam->ta & RFAL_ISODEP_ATS_TA_DLP_424) != 0U))) || - (!(RFAL_SUPPORT_BR_CE_A_848) && - (((atsParam->ta & RFAL_ISODEP_ATS_TA_DPL_848) != 0U) || - ((atsParam->ta & RFAL_ISODEP_ATS_TA_DLP_848) != 0U)))) { - return ERR_NOTSUPP; - } - - /* Enforce proper FWI configuration */ - if(atsParam->fwi > ISODEP_FWI_LIS_MAX) { - atsParam->fwi = ISODEP_FWI_LIS_MAX; - } - - gIsoDep.atsTA = atsParam->ta; - gIsoDep.fwt = rfalIsoDepFWI2FWT(atsParam->fwi); - gIsoDep.ourFsx = rfalIsoDepFSxI2FSx(atsParam->fsci); - - /* Ensure proper/maximum Historical Bytes length */ - atsParam->hbLen = MIN(RFAL_ISODEP_ATS_HB_MAX_LEN, atsParam->hbLen); - - /*******************************************************************************/ - /* Compute ATS */ - - txBuf[bufIt++] = (RFAL_ISODEP_ATS_HIST_OFFSET + atsParam->hbLen); /* TL */ - txBuf[bufIt++] = - ((RFAL_ISODEP_ATS_T0_TA_PRESENCE_MASK | RFAL_ISODEP_ATS_T0_TB_PRESENCE_MASK | - RFAL_ISODEP_ATS_T0_TC_PRESENCE_MASK) | - atsParam->fsci); /* T0 */ - txBuf[bufIt++] = atsParam->ta; /* TA */ - txBuf[bufIt++] = - ((atsParam->fwi << RFAL_ISODEP_RATS_PARAM_FSDI_SHIFT) | - (atsParam->sfgi & RFAL_ISODEP_RATS_PARAM_FSDI_MASK)); /* TB */ - txBuf[bufIt++] = (uint8_t)((atsParam->didSupport) ? RFAL_ISODEP_ATS_TC_DID : 0U); /* TC */ - - if(atsParam->hbLen > 0U) /* MISRA 21.18 */ - { - ST_MEMCPY(&txBuf[bufIt], atsParam->hb, atsParam->hbLen); /* T1-Tk */ - bufIt += atsParam->hbLen; - } - - gIsoDep.state = ISODEP_ST_PICC_ACT_ATS; - - } - /*******************************************************************************/ - else if(*buffer == RFAL_ISODEP_CMD_ATTRIB) { - /* Check ATTRIB parameters */ - if(attribResParam == NULL) { - return ERR_PARAM; - } - - /* REMARK: ATTRIB handling */ - NO_WARNING(attribResParam); - NO_WARNING(bufLen); - return ERR_NOT_IMPLEMENTED; - } else { - return ERR_PARAM; - } - - gIsoDep.actvParam = actParam; - - /*******************************************************************************/ - /* If requested copy to ISO-DEP device info */ - if(actParam.isoDepDev != NULL) { - actParam.isoDepDev->info.DID = gIsoDep.did; - actParam.isoDepDev->info.FSx = gIsoDep.fsx; - actParam.isoDepDev->info.FWT = gIsoDep.fwt; - actParam.isoDepDev->info.dFWT = 0; - actParam.isoDepDev->info.DSI = gIsoDep.txBR; - actParam.isoDepDev->info.DRI = gIsoDep.rxBR; - } - - return rfalTransceiveBlockingTx( - txBuf, - bufIt, - (uint8_t*)actParam.rxBuf, - sizeof(rfalIsoDepBufFormat), - actParam.rxLen, - RFAL_TXRX_FLAGS_DEFAULT, - RFAL_FWT_NONE); -} - -/*******************************************************************************/ -ReturnCode rfalIsoDepListenGetActivationStatus(void) { - ReturnCode err; - uint8_t* txBuf; - uint8_t bufIt; - - rfalBitRate dsi; - rfalBitRate dri; - - /* Check if Activation is running */ - if(gIsoDep.state < ISODEP_ST_PICC_ACT_ATS) { - return ERR_WRONG_STATE; - } - - /* Check if Activation has finished already */ - if(gIsoDep.state >= ISODEP_ST_PICC_RX) { - return ERR_NONE; - } - - /*******************************************************************************/ - /* Check for incoming msg */ - err = rfalGetTransceiveStatus(); - switch(err) { - /*******************************************************************************/ - case ERR_NONE: - break; - - /*******************************************************************************/ - case ERR_LINK_LOSS: - case ERR_BUSY: - return err; - - /*******************************************************************************/ - case ERR_CRC: - case ERR_PAR: - case ERR_FRAMING: - - /* ISO14443 4 5.6.2.2 2 If ATS has been replied upon a invalid block, PICC disables the PPS responses */ - if(gIsoDep.state == ISODEP_ST_PICC_ACT_ATS) { - gIsoDep.state = ISODEP_ST_PICC_RX; - break; - } - /* fall through */ - - /*******************************************************************************/ - default: /* PRQA S 2003 # MISRA 16.3 - Intentional fall through */ - /* ReEnable the receiver and wait for another frame */ - isoDepReEnableRx( - (uint8_t*)gIsoDep.actvParam.rxBuf, - sizeof(rfalIsoDepBufFormat), - gIsoDep.actvParam.rxLen); - - return ERR_BUSY; - } - - txBuf = - (uint8_t*)gIsoDep.actvParam - .rxBuf; /* Use the rxBuf as TxBuf as well, the struct enforces a size enough MAX(NFCA_PPS_RES_LEN, ISODEP_DSL_MAX_LEN) */ - dri = RFAL_BR_KEEP; /* The RFAL_BR_KEEP is used to check if PPS with BR change was requested */ - dsi = RFAL_BR_KEEP; /* MISRA 9.1 */ - bufIt = 0; - - /*******************************************************************************/ - gIsoDep.role = ISODEP_ROLE_PICC; - - /*******************************************************************************/ - if(gIsoDep.state == ISODEP_ST_PICC_ACT_ATS) { - /* Check for a PPS ISO 14443-4 5.3 */ - if((((uint8_t*)gIsoDep.actvParam.rxBuf)[RFAL_ISODEP_PPS_STARTBYTE_POS] & - RFAL_ISODEP_PPS_MASK) == RFAL_ISODEP_PPS_SB) { - /* ISO 14443-4 5.3.1 Check if the we are the addressed DID/CID */ - /* ISO 14443-4 5.3.2 Check for a valid PPS0 */ - if(((((uint8_t*)gIsoDep.actvParam.rxBuf)[RFAL_ISODEP_PPS_STARTBYTE_POS] & - RFAL_ISODEP_DID_MASK) != gIsoDep.did) || - ((((uint8_t*)gIsoDep.actvParam.rxBuf)[RFAL_ISODEP_PPS_PPS0_POS] & - RFAL_ISODEP_PPS0_VALID_MASK) != RFAL_ISODEP_PPS0_PPS1_NOT_PRESENT)) { - /* Invalid DID on PPS request or Invalid PPS0, reEnable the receiver and wait another frame */ - isoDepReEnableRx( - (uint8_t*)gIsoDep.actvParam.rxBuf, - sizeof(rfalIsoDepBufFormat), - gIsoDep.actvParam.rxLen); - - return ERR_BUSY; - } - - /*******************************************************************************/ - /* Check PPS1 presence */ - if(((uint8_t*)gIsoDep.actvParam.rxBuf)[RFAL_ISODEP_PPS_PPS0_POS] == - RFAL_ISODEP_PPS0_PPS1_PRESENT) { - uint8_t newdri = ((uint8_t*)gIsoDep.actvParam.rxBuf)[RFAL_ISODEP_PPS_PPS1_POS] & - RFAL_ISODEP_PPS1_DxI_MASK; /* MISRA 10.8 */ - uint8_t newdsi = (((uint8_t*)gIsoDep.actvParam.rxBuf)[RFAL_ISODEP_PPS_PPS1_POS] >> - RFAL_ISODEP_PPS1_DSI_SHIFT) & - RFAL_ISODEP_PPS1_DxI_MASK; /* MISRA 10.8 */ - /* PRQA S 4342 2 # MISRA 10.5 - Layout of enum rfalBitRate and above masks guarantee no invalid enum values to be created */ - dri = (rfalBitRate)(newdri); - dsi = (rfalBitRate)(newdsi); - - if((!(RFAL_SUPPORT_BR_CE_A_106) && - ((dsi == RFAL_BR_106) || (dri == RFAL_BR_106))) || - (!(RFAL_SUPPORT_BR_CE_A_212) && - ((dsi == RFAL_BR_212) || (dri == RFAL_BR_212))) || - (!(RFAL_SUPPORT_BR_CE_A_424) && - ((dsi == RFAL_BR_424) || (dri == RFAL_BR_424))) || - (!(RFAL_SUPPORT_BR_CE_A_848) && - ((dsi == RFAL_BR_848) || (dri == RFAL_BR_848)))) { - return ERR_PROTO; - } - } - - /*******************************************************************************/ - /* Compute and send PPS RES / Ack */ - txBuf[bufIt++] = ((uint8_t*)gIsoDep.actvParam.rxBuf)[RFAL_ISODEP_PPS_STARTBYTE_POS]; - - rfalTransceiveBlockingTx( - txBuf, - bufIt, - (uint8_t*)gIsoDep.actvParam.rxBuf, - sizeof(rfalIsoDepBufFormat), - gIsoDep.actvParam.rxLen, - RFAL_TXRX_FLAGS_DEFAULT, - RFAL_FWT_NONE); - - /*******************************************************************************/ - /* Exchange the bit rates if requested */ - if(dri != RFAL_BR_KEEP) { - rfalSetBitRate( - dsi, - dri); /* PRQA S 2880 # MISRA 2.1 - Unreachable code due to configuration option being set/unset above (RFAL_SUPPORT_BR_CE_A_xxx) */ - - gIsoDep.txBR = dsi; /* DSI codes the divisor from PICC to PCD */ - gIsoDep.rxBR = dri; /* DRI codes the divisor from PCD to PICC */ - - if(gIsoDep.actvParam.isoDepDev != NULL) { - gIsoDep.actvParam.isoDepDev->info.DSI = dsi; - gIsoDep.actvParam.isoDepDev->info.DRI = dri; - } - } - } - /* Check for a S-Deselect is done on Data Exchange Activity */ - } - - /*******************************************************************************/ - gIsoDep.hdrLen = RFAL_ISODEP_PCB_LEN; - gIsoDep.hdrLen += - RFAL_ISODEP_DID_LEN; /* Always assume DID to be aligned with Digital 1.1 15.1.2 and ISO14443 4 5.6.3 #454 */ - gIsoDep.hdrLen += (uint8_t)((gIsoDep.nad != RFAL_ISODEP_NO_NAD) ? RFAL_ISODEP_NAD_LEN : 0U); - - /*******************************************************************************/ - /* Rule C - The PICC block number shall be initialized to 1 at activation */ - gIsoDep.blockNumber = 1; - - /* Activation done, keep the rcvd data in, reMap the activation buffer to the global to be retrieved by the DEP method */ - gIsoDep.rxBuf = (uint8_t*)gIsoDep.actvParam.rxBuf; - gIsoDep.rxBufLen = sizeof(rfalIsoDepBufFormat); - gIsoDep.rxBufInfPos = - (uint8_t)((uint32_t)gIsoDep.actvParam.rxBuf->inf - (uint32_t)gIsoDep.actvParam.rxBuf->prologue); - gIsoDep.rxLen = gIsoDep.actvParam.rxLen; - gIsoDep.rxChaining = gIsoDep.actvParam.isRxChaining; - - gIsoDep.state = ISODEP_ST_PICC_RX; - return ERR_NONE; -} - -#endif /* RFAL_FEATURE_ISO_DEP_LISTEN */ - -/*******************************************************************************/ -uint16_t rfalIsoDepGetMaxInfLen(void) { - /* Check whether all parameters are valid, otherwise return minimum default value */ - if((gIsoDep.fsx < (uint16_t)RFAL_ISODEP_FSX_16) || - (gIsoDep.fsx > (uint16_t)RFAL_ISODEP_FSX_4096) || (gIsoDep.hdrLen > ISODEP_HDR_MAX_LEN)) { - uint16_t isodepFsx16 = (uint16_t)RFAL_ISODEP_FSX_16; /* MISRA 10.1 */ - return (isodepFsx16 - RFAL_ISODEP_PCB_LEN - ISODEP_CRC_LEN); - } - - return (gIsoDep.fsx - gIsoDep.hdrLen - ISODEP_CRC_LEN); -} - -/*******************************************************************************/ -ReturnCode rfalIsoDepStartTransceive(rfalIsoDepTxRxParam param) { - gIsoDep.txBuf = param.txBuf->prologue; - gIsoDep.txBufInfPos = (uint8_t)((uint32_t)param.txBuf->inf - (uint32_t)param.txBuf->prologue); - gIsoDep.txBufLen = param.txBufLen; - gIsoDep.isTxChaining = param.isTxChaining; - - gIsoDep.rxBuf = param.rxBuf->prologue; - gIsoDep.rxBufInfPos = (uint8_t)((uint32_t)param.rxBuf->inf - (uint32_t)param.rxBuf->prologue); - gIsoDep.rxBufLen = sizeof(rfalIsoDepBufFormat); - - gIsoDep.rxLen = param.rxLen; - gIsoDep.rxChaining = param.isRxChaining; - - gIsoDep.fwt = param.FWT; - gIsoDep.dFwt = param.dFWT; - gIsoDep.fsx = param.FSx; - gIsoDep.did = param.DID; - - /* Only change the FSx from activation if no to Keep */ - gIsoDep.ourFsx = ((param.ourFSx != RFAL_ISODEP_FSX_KEEP) ? param.ourFSx : gIsoDep.ourFsx); - - /* Clear inner control params for next dataExchange */ - gIsoDep.isRxChaining = false; - isoDepClearCounters(); - - if(gIsoDep.role == ISODEP_ROLE_PICC) { - if(gIsoDep.txBufLen > 0U) { - /* Ensure that an RTOX Ack is not being expected at moment */ - if(!gIsoDep.isWait4WTX) { - gIsoDep.state = ISODEP_ST_PICC_TX; - return ERR_NONE; - } else { - /* If RTOX Ack is expected, signal a pending Tx to be transmitted right after */ - gIsoDep.isTxPending = true; - } - } - - /* Digital 1.1 15.2.5.1 The first block SHALL be sent by the Reader/Writer */ - gIsoDep.state = ISODEP_ST_PICC_RX; - return ERR_NONE; - } - - gIsoDep.state = ISODEP_ST_PCD_TX; - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalIsoDepGetTransceiveStatus(void) { - if(gIsoDep.role == ISODEP_ROLE_PICC) { -#if RFAL_FEATURE_ISO_DEP_LISTEN - return isoDepDataExchangePICC(); -#else - return ERR_NOTSUPP; -#endif /* RFAL_FEATURE_ISO_DEP_LISTEN */ - } else { -#if RFAL_FEATURE_ISO_DEP_POLL - return isoDepDataExchangePCD(gIsoDep.rxLen, gIsoDep.rxChaining); -#else - return ERR_NOTSUPP; -#endif /* RFAL_FEATURE_ISO_DEP_POLL */ - } -} - -#if RFAL_FEATURE_ISO_DEP_LISTEN - -/*******************************************************************************/ -static ReturnCode isoDepDataExchangePICC(void) { - uint8_t rxPCB; - ReturnCode ret; - - switch(gIsoDep.state) { - /*******************************************************************************/ - case ISODEP_ST_IDLE: - return ERR_NONE; - - /*******************************************************************************/ - case ISODEP_ST_PICC_TX: - - ret = isoDepTx( - isoDep_PCBIBlock(gIsoDep.blockNumber), - gIsoDep.txBuf, - &gIsoDep.txBuf[gIsoDep.txBufInfPos], - gIsoDep.txBufLen, - RFAL_FWT_NONE); - - /* Clear pending Tx flag */ - gIsoDep.isTxPending = false; - - switch(ret) { - case ERR_NONE: - gIsoDep.state = ISODEP_ST_PICC_RX; - return ERR_BUSY; - - default: - /* MISRA 16.4: no empty default statement (a comment being enough) */ - break; - } - return ret; - - /*******************************************************************************/ - case ISODEP_ST_PICC_RX: - - ret = rfalGetTransceiveStatus(); - switch(ret) { - /*******************************************************************************/ - /* Data rcvd with error or timeout -> mute */ - case ERR_TIMEOUT: - case ERR_CRC: - case ERR_PAR: - case ERR_FRAMING: - - /* Digital 1.1 - 15.2.6.2 The CE SHALL NOT attempt error recovery and remains in Rx mode upon Transmission or a Protocol Error */ - isoDepReEnableRx((uint8_t*)gIsoDep.rxBuf, sizeof(rfalIsoDepBufFormat), gIsoDep.rxLen); - - return ERR_BUSY; - - /*******************************************************************************/ - case ERR_LINK_LOSS: - return ret; /* Debug purposes */ - - case ERR_BUSY: - return ret; /* Debug purposes */ - - /*******************************************************************************/ - case ERR_NONE: - *gIsoDep.rxLen = rfalConvBitsToBytes(*gIsoDep.rxLen); - break; - - /*******************************************************************************/ - default: - return ret; - } - break; - - /*******************************************************************************/ - case ISODEP_ST_PICC_SWTX: - - if(!isoDepTimerisExpired(gIsoDep.WTXTimer)) /* Do nothing until WTX timer has expired */ - { - return ERR_BUSY; - } - - /* Set waiting for WTX Ack Flag */ - gIsoDep.isWait4WTX = true; - - /* Digital 1.1 15.2.2.9 - Calculate the WTXM such that FWTtemp <= FWTmax */ - gIsoDep.lastWTXM = (uint8_t)isoDep_WTXMListenerMax(gIsoDep.fwt); - EXIT_ON_ERR(ret, isoDepHandleControlMsg(ISODEP_S_WTX, gIsoDep.lastWTXM)); - - gIsoDep.state = ISODEP_ST_PICC_RX; /* Go back to Rx to process WTX ack */ - return ERR_BUSY; - - /*******************************************************************************/ - case ISODEP_ST_PICC_SDSL: - - if(rfalIsTransceiveInRx()) /* Wait until DSL response has been sent */ - { - rfalIsoDepInitialize(); /* Session finished reInit vars */ - return ERR_SLEEP_REQ; /* Notify Deselect request */ - } - return ERR_BUSY; - - /*******************************************************************************/ - default: - return ERR_INTERNAL; - } - - /* ISO 14443-4 7.5.6.2 CE SHALL NOT attempt error recovery -> clear counters */ - isoDepClearCounters(); - - /*******************************************************************************/ - /* No error, process incoming msg */ - /*******************************************************************************/ - - /* Grab rcvd PCB */ - rxPCB = gIsoDep.rxBuf[ISODEP_PCB_POS]; - - /*******************************************************************************/ - /* When DID=0 PCD may or may not use DID, therefore check whether current PCD request - * has DID present to be reflected on max INF length #454 */ - - /* ReCalculate Header Length */ - gIsoDep.hdrLen = RFAL_ISODEP_PCB_LEN; - gIsoDep.hdrLen += (uint8_t)((isoDep_PCBhasDID(rxPCB)) ? RFAL_ISODEP_DID_LEN : 0U); - gIsoDep.hdrLen += (uint8_t)((isoDep_PCBhasNAD(rxPCB)) ? RFAL_ISODEP_NAD_LEN : 0U); - - /* Store whether last PCD block had DID. for PICC special handling of DID = 0 */ - if(gIsoDep.did == RFAL_ISODEP_DID_00) { - gIsoDep.lastDID00 = ((isoDep_PCBhasDID(rxPCB)) ? true : false); - } - - /*******************************************************************************/ - /* Check rcvd msg length, cannot be less then the expected header OR * - * if the rcvd msg exceeds our announced frame size (FSD) */ - if(((*gIsoDep.rxLen) < gIsoDep.hdrLen) || - ((*gIsoDep.rxLen) > (gIsoDep.ourFsx - ISODEP_CRC_LEN))) { - isoDepReEnableRx( - (uint8_t*)gIsoDep.actvParam.rxBuf, - sizeof(rfalIsoDepBufFormat), - gIsoDep.actvParam.rxLen); - return ERR_BUSY; /* ERR_PROTO Ignore this protocol request */ - } - - /* If we are expecting DID, check if PCB signals its presence and if device ID match OR - * If our DID=0 and DID is sent but with an incorrect value */ - if(((gIsoDep.did != RFAL_ISODEP_DID_00) && - (!isoDep_PCBhasDID(rxPCB) || (gIsoDep.did != gIsoDep.rxBuf[ISODEP_DID_POS]))) || - ((gIsoDep.did == RFAL_ISODEP_DID_00) && isoDep_PCBhasDID(rxPCB) && - (RFAL_ISODEP_DID_00 != gIsoDep.rxBuf[ISODEP_DID_POS]))) { - isoDepReEnableRx( - (uint8_t*)gIsoDep.actvParam.rxBuf, - sizeof(rfalIsoDepBufFormat), - gIsoDep.actvParam.rxLen); - return ERR_BUSY; /* Ignore a wrong DID request */ - } - - /* If we aren't expecting NAD and it's received */ - if((gIsoDep.nad == RFAL_ISODEP_NO_NAD) && isoDep_PCBhasNAD(rxPCB)) { - isoDepReEnableRx( - (uint8_t*)gIsoDep.actvParam.rxBuf, - sizeof(rfalIsoDepBufFormat), - gIsoDep.actvParam.rxLen); - return ERR_BUSY; /* Ignore a unexpected NAD request */ - } - - /*******************************************************************************/ - /* Process S-Block */ - /*******************************************************************************/ - if(isoDep_PCBisSBlock(rxPCB)) { - /* Check if is a Wait Time eXtension */ - if(isoDep_PCBisSWTX(rxPCB)) { - /* Check if we're expecting a S-WTX */ - if(isoDep_PCBisWTX(gIsoDep.lastPCB)) { - /* Digital 1.1 15.2.2.11 S(WTX) Ack with different WTXM -> Protocol Error * - * Power level indication also should be set to 0 */ - if((gIsoDep.rxBuf[gIsoDep.hdrLen] == gIsoDep.lastWTXM) && - ((*gIsoDep.rxLen - gIsoDep.hdrLen) == ISODEP_SWTX_INF_LEN)) { - /* Clear waiting for RTOX Ack Flag */ - gIsoDep.isWait4WTX = false; - - /* Check if a Tx is already pending */ - if(gIsoDep.isTxPending) { - /* Has a pending Tx, go immediately to TX */ - gIsoDep.state = ISODEP_ST_PICC_TX; - return ERR_BUSY; - } - - /* Set WTX timer */ - isoDepTimerStart( - gIsoDep.WTXTimer, - isoDep_WTXAdjust((gIsoDep.lastWTXM * rfalConv1fcToMs(gIsoDep.fwt)))); - - gIsoDep.state = ISODEP_ST_PICC_SWTX; - return ERR_BUSY; - } - } - /* Unexpected/Incorrect S-WTX, fall into reRenable */ - } - - /* Check if is a Deselect request */ - if(isoDep_PCBisSDeselect(rxPCB) && - ((*gIsoDep.rxLen - gIsoDep.hdrLen) == ISODEP_SDSL_INF_LEN)) { - EXIT_ON_ERR(ret, isoDepHandleControlMsg(ISODEP_S_DSL, RFAL_ISODEP_NO_PARAM)); - - /* S-DSL transmission ongoing, wait until complete */ - gIsoDep.state = ISODEP_ST_PICC_SDSL; - return ERR_BUSY; - } - - /* Unexpected S-Block, fall into reRenable */ - } - - /*******************************************************************************/ - /* Process R-Block */ - /*******************************************************************************/ - else if(isoDep_PCBisRBlock(rxPCB) && ((*gIsoDep.rxLen - gIsoDep.hdrLen) == ISODEP_RBLOCK_INF_LEN)) { - if(isoDep_PCBisRACK(rxPCB)) /* Check if is a R-ACK */ - { - if(isoDep_GetBN(rxPCB) == gIsoDep.blockNumber) /* Check block number */ - { - /* Rule 11 - R(ACK) with current bn -> re-transmit */ - if(!isoDep_PCBisIBlock(gIsoDep.lastPCB)) { - isoDepReSendControlMsg(); - } else { - gIsoDep.state = ISODEP_ST_PICC_TX; - } - - return ERR_BUSY; - } else { - if(!gIsoDep.isTxChaining) { - /* Rule 13 violation R(ACK) without performing chaining */ - isoDepReEnableRx( - (uint8_t*)gIsoDep.rxBuf, sizeof(rfalIsoDepBufFormat), gIsoDep.rxLen); - return ERR_BUSY; - } - - /* Rule E - R(ACK) with not current bn -> toggle bn */ - isoDep_ToggleBN(gIsoDep.blockNumber); - - /* This block has been transmitted and acknowledged, perform WTX until next data is provided */ - - /* Rule 9 - PICC is allowed to send an S(WTX) instead of an I-block or an R(ACK) */ - isoDepTimerStart(gIsoDep.WTXTimer, isoDep_WTXAdjust(rfalConv1fcToMs(gIsoDep.fwt))); - gIsoDep.state = ISODEP_ST_PICC_SWTX; - - /* Rule 13 - R(ACK) with not current bn -> continue chaining */ - return ERR_NONE; /* This block has been transmitted */ - } - } else if(isoDep_PCBisRNAK(rxPCB)) /* Check if is a R-NACK */ - { - if(isoDep_GetBN(rxPCB) == gIsoDep.blockNumber) /* Check block number */ - { - /* Rule 11 - R(NAK) with current bn -> re-transmit last x-Block */ - if(!isoDep_PCBisIBlock(gIsoDep.lastPCB)) { - isoDepReSendControlMsg(); - } else { - gIsoDep.state = ISODEP_ST_PICC_TX; - } - - return ERR_BUSY; - } else { - /* Rule 12 - R(NAK) with not current bn -> R(ACK) */ - EXIT_ON_ERR(ret, isoDepHandleControlMsg(ISODEP_R_ACK, RFAL_ISODEP_NO_PARAM)); - - return ERR_BUSY; - } - } else { - /* MISRA 15.7 - Empty else */ - } - - /* Unexpected R-Block, fall into reRenable */ - } - - /*******************************************************************************/ - /* Process I-Block */ - /*******************************************************************************/ - else if(isoDep_PCBisIBlock(rxPCB)) { - /* Rule D - When an I-block is received, the PICC shall toggle its block number before sending a block */ - isoDep_ToggleBN(gIsoDep.blockNumber); - - /*******************************************************************************/ - /* Check if the block number is the one expected */ - /* Check if PCD sent an I-Block instead ACK/NACK when we are chaining */ - if((isoDep_GetBN(rxPCB) != gIsoDep.blockNumber) || (gIsoDep.isTxChaining)) { - /* Remain in the same Block Number */ - isoDep_ToggleBN(gIsoDep.blockNumber); - - /* ISO 14443-4 7.5.6.2 & Digital 1.1 - 15.2.6.2 The CE SHALL NOT attempt error recovery and remains in Rx mode upon Transmission or a Protocol Error */ - isoDepReEnableRx((uint8_t*)gIsoDep.rxBuf, sizeof(rfalIsoDepBufFormat), gIsoDep.rxLen); - return ERR_BUSY; - } - - /*******************************************************************************/ - /* is PCD performing chaining ? */ - if(isoDep_PCBisChaining(rxPCB)) { - gIsoDep.isRxChaining = true; - *gIsoDep.rxChaining = true; /* Output Parameter*/ - - EXIT_ON_ERR(ret, isoDepHandleControlMsg(ISODEP_R_ACK, RFAL_ISODEP_NO_PARAM)); - - /* Received I-Block with chaining, send current data to DH */ - - /* remove ISO DEP header, check is necessary to move the INF data on the buffer */ - *gIsoDep.rxLen -= gIsoDep.hdrLen; - if((gIsoDep.hdrLen != gIsoDep.rxBufInfPos) && (*gIsoDep.rxLen > 0U)) { - ST_MEMMOVE( - &gIsoDep.rxBuf[gIsoDep.rxBufInfPos], - &gIsoDep.rxBuf[gIsoDep.hdrLen], - *gIsoDep.rxLen); - } - return ERR_AGAIN; /* Send Again signalling to run again, but some chaining data has arrived*/ - } - - /*******************************************************************************/ - /* PCD is not performing chaining */ - gIsoDep.isRxChaining = false; /* clear PCD chaining flag */ - *gIsoDep.rxChaining = false; /* Output Parameter */ - - /* remove ISO DEP header, check is necessary to move the INF data on the buffer */ - *gIsoDep.rxLen -= gIsoDep.hdrLen; - if((gIsoDep.hdrLen != gIsoDep.rxBufInfPos) && (*gIsoDep.rxLen > 0U)) { - ST_MEMMOVE( - &gIsoDep.rxBuf[gIsoDep.rxBufInfPos], - &gIsoDep.rxBuf[gIsoDep.hdrLen], - *gIsoDep.rxLen); - } - - /*******************************************************************************/ - /* Reception done, send data back and start WTX timer */ - isoDepTimerStart(gIsoDep.WTXTimer, isoDep_WTXAdjust(rfalConv1fcToMs(gIsoDep.fwt))); - - gIsoDep.state = ISODEP_ST_PICC_SWTX; - return ERR_NONE; - } else { - /* MISRA 15.7 - Empty else */ - } - - /* Unexpected/Unknown Block */ - /* ISO 14443-4 7.5.6.2 & Digital 1.1 - 15.2.6.2 The CE SHALL NOT attempt error recovery and remains in Rx mode upon Transmission or a Protocol Error */ - isoDepReEnableRx((uint8_t*)gIsoDep.rxBuf, sizeof(rfalIsoDepBufFormat), gIsoDep.rxLen); - - return ERR_BUSY; -} -#endif /* RFAL_FEATURE_ISO_DEP_LISTEN */ - -#if RFAL_FEATURE_ISO_DEP_POLL - -#if RFAL_FEATURE_NFCA - -/*******************************************************************************/ -static ReturnCode - rfalIsoDepStartRATS(rfalIsoDepFSxI FSDI, uint8_t DID, rfalIsoDepAts* ats, uint8_t* atsLen) { - rfalTransceiveContext ctx; - - if(ats == NULL) { - return ERR_PARAM; - } - - gIsoDep.rxBuf = (uint8_t*)ats; - gIsoDep.rxLen8 = atsLen; - gIsoDep.did = DID; - - /*******************************************************************************/ - /* Compose RATS */ - gIsoDep.actv.ratsReq.CMD = RFAL_ISODEP_CMD_RATS; - gIsoDep.actv.ratsReq.PARAM = - (((uint8_t)FSDI << RFAL_ISODEP_RATS_PARAM_FSDI_SHIFT) & RFAL_ISODEP_RATS_PARAM_FSDI_MASK) | - (DID & RFAL_ISODEP_RATS_PARAM_DID_MASK); - - rfalCreateByteFlagsTxRxContext( - ctx, - (uint8_t*)&gIsoDep.actv.ratsReq, - sizeof(rfalIsoDepRats), - (uint8_t*)ats, - sizeof(rfalIsoDepAts), - &gIsoDep.rxBufLen, - RFAL_TXRX_FLAGS_DEFAULT, - RFAL_ISODEP_T4T_FWT_ACTIVATION); - return rfalStartTransceive(&ctx); -} - -/*******************************************************************************/ -static ReturnCode rfalIsoDepGetRATSStatus(void) { - ReturnCode ret; - - ret = rfalGetTransceiveStatus(); - if(ret == ERR_NONE) { - gIsoDep.rxBufLen = rfalConvBitsToBytes(gIsoDep.rxBufLen); - - /* Check for valid ATS length Digital 1.1 13.6.2.1 & 13.6.2.3 */ - if((gIsoDep.rxBufLen < RFAL_ISODEP_ATS_MIN_LEN) || - (gIsoDep.rxBufLen > RFAL_ISODEP_ATS_MAX_LEN) || - (gIsoDep.rxBuf[RFAL_ISODEP_ATS_TL_POS] != gIsoDep.rxBufLen)) { - return ERR_PROTO; - } - - /* Assign our FSx, in case the a Deselect is send without Transceive */ - gIsoDep.ourFsx = rfalIsoDepFSxI2FSx( - (uint8_t)(gIsoDep.actv.ratsReq.PARAM >> RFAL_ISODEP_RATS_PARAM_FSDI_SHIFT)); - - /* Check and assign if ATS length was requested (length also available on TL) */ - if(gIsoDep.rxLen8 != NULL) { - *gIsoDep.rxLen8 = (uint8_t)gIsoDep.rxBufLen; - } - } - - return ret; -} - -/*******************************************************************************/ -ReturnCode rfalIsoDepRATS(rfalIsoDepFSxI FSDI, uint8_t DID, rfalIsoDepAts* ats, uint8_t* atsLen) { - ReturnCode ret; - - EXIT_ON_ERR(ret, rfalIsoDepStartRATS(FSDI, DID, ats, atsLen)); - rfalIsoDepRunBlocking(ret, rfalIsoDepGetRATSStatus()); - - return ret; -} - -/*******************************************************************************/ -static ReturnCode - rfalIsoDepStartPPS(uint8_t DID, rfalBitRate DSI, rfalBitRate DRI, rfalIsoDepPpsRes* ppsRes) { - rfalTransceiveContext ctx; - - if((ppsRes == NULL) || (DSI > RFAL_BR_848) || (DRI > RFAL_BR_848) || - (DID > RFAL_ISODEP_DID_MAX)) { - return ERR_PARAM; - } - - gIsoDep.rxBuf = (uint8_t*)ppsRes; - - /*******************************************************************************/ - /* Compose PPS Request */ - gIsoDep.actv.ppsReq.PPSS = (RFAL_ISODEP_PPS_SB | (DID & RFAL_ISODEP_PPS_SB_DID_MASK)); - gIsoDep.actv.ppsReq.PPS0 = RFAL_ISODEP_PPS_PPS0_PPS1_PRESENT; - gIsoDep.actv.ppsReq.PPS1 = - (RFAL_ISODEP_PPS_PPS1 | - ((((uint8_t)DSI << RFAL_ISODEP_PPS_PPS1_DSI_SHIFT) | (uint8_t)DRI) & - RFAL_ISODEP_PPS_PPS1_DXI_MASK)); - - rfalCreateByteFlagsTxRxContext( - ctx, - (uint8_t*)&gIsoDep.actv.ppsReq, - sizeof(rfalIsoDepPpsReq), - (uint8_t*)ppsRes, - sizeof(rfalIsoDepPpsRes), - &gIsoDep.rxBufLen, - RFAL_TXRX_FLAGS_DEFAULT, - RFAL_ISODEP_T4T_FWT_ACTIVATION); - return rfalStartTransceive(&ctx); -} - -/*******************************************************************************/ -static ReturnCode rfalIsoDepGetPPSSTatus(void) { - ReturnCode ret; - - ret = rfalGetTransceiveStatus(); - if(ret == ERR_NONE) { - gIsoDep.rxBufLen = rfalConvBitsToBytes(gIsoDep.rxBufLen); - - /* Check for valid PPS Response */ - if((gIsoDep.rxBufLen != RFAL_ISODEP_PPS_RES_LEN) || - (*gIsoDep.rxBuf != gIsoDep.actv.ppsReq.PPSS)) { - return ERR_PROTO; - } - } - return ret; -} - -/*******************************************************************************/ -ReturnCode rfalIsoDepPPS(uint8_t DID, rfalBitRate DSI, rfalBitRate DRI, rfalIsoDepPpsRes* ppsRes) { - ReturnCode ret; - - EXIT_ON_ERR(ret, rfalIsoDepStartPPS(DID, DSI, DRI, ppsRes)); - rfalIsoDepRunBlocking(ret, rfalIsoDepGetPPSSTatus()); - - return ret; -} - -#endif /* RFAL_FEATURE_NFCA */ - -#if RFAL_FEATURE_NFCB - -static ReturnCode rfalIsoDepStartATTRIB( - const uint8_t* nfcid0, - uint8_t PARAM1, - rfalBitRate DSI, - rfalBitRate DRI, - rfalIsoDepFSxI FSDI, - uint8_t PARAM3, - uint8_t DID, - const uint8_t* HLInfo, - uint8_t HLInfoLen, - uint32_t fwt, - rfalIsoDepAttribRes* attribRes, - uint8_t* attribResLen) { - rfalTransceiveContext ctx; - - if((attribRes == NULL) || (attribResLen == NULL) || (DSI > RFAL_BR_848) || - (DRI > RFAL_BR_848) || (DID > RFAL_ISODEP_DID_MAX)) { - return ERR_NONE; - } - - gIsoDep.rxBuf = (uint8_t*)attribRes; - gIsoDep.rxLen8 = attribResLen; - gIsoDep.did = DID; - - /*******************************************************************************/ - /* Compose ATTRIB command */ - gIsoDep.actv.attribReq.cmd = RFAL_ISODEP_CMD_ATTRIB; - gIsoDep.actv.attribReq.Param.PARAM1 = PARAM1; - gIsoDep.actv.attribReq.Param.PARAM2 = - (((((uint8_t)DSI << RFAL_ISODEP_ATTRIB_PARAM2_DSI_SHIFT) | - ((uint8_t)DRI << RFAL_ISODEP_ATTRIB_PARAM2_DRI_SHIFT)) & - RFAL_ISODEP_ATTRIB_PARAM2_DXI_MASK) | - ((uint8_t)FSDI & RFAL_ISODEP_ATTRIB_PARAM2_FSDI_MASK)); - gIsoDep.actv.attribReq.Param.PARAM3 = PARAM3; - gIsoDep.actv.attribReq.Param.PARAM4 = (DID & RFAL_ISODEP_ATTRIB_PARAM4_DID_MASK); - ST_MEMCPY(gIsoDep.actv.attribReq.nfcid0, nfcid0, RFAL_NFCB_NFCID0_LEN); - - /* Append the Higher layer Info if provided */ - if((HLInfo != NULL) && (HLInfoLen > 0U)) { - ST_MEMCPY( - gIsoDep.actv.attribReq.HLInfo, HLInfo, MIN(HLInfoLen, RFAL_ISODEP_ATTRIB_HLINFO_LEN)); - } - - rfalCreateByteFlagsTxRxContext( - ctx, - (uint8_t*)&gIsoDep.actv.attribReq, - (uint16_t)(RFAL_ISODEP_ATTRIB_HDR_LEN + MIN((uint16_t)HLInfoLen, RFAL_ISODEP_ATTRIB_HLINFO_LEN)), - (uint8_t*)gIsoDep.rxBuf, - sizeof(rfalIsoDepAttribRes), - &gIsoDep.rxBufLen, - RFAL_TXRX_FLAGS_DEFAULT, - fwt); - return rfalStartTransceive(&ctx); -} - -/*******************************************************************************/ -static ReturnCode rfalIsoDepGetATTRIBStatus(void) { - ReturnCode ret; - - ret = rfalGetTransceiveStatus(); - if(ret == ERR_NONE) { - gIsoDep.rxBufLen = rfalConvBitsToBytes(gIsoDep.rxBufLen); - - /* Check a for valid ATTRIB Response Digital 1.1 15.6.2.1 */ - if((gIsoDep.rxBufLen < RFAL_ISODEP_ATTRIB_RES_HDR_LEN) || - ((gIsoDep.rxBuf[RFAL_ISODEP_ATTRIB_RES_MBLIDID_POS] & - RFAL_ISODEP_ATTRIB_RES_DID_MASK) != gIsoDep.did)) { - return ERR_PROTO; - } - - if(gIsoDep.rxLen8 != NULL) { - *gIsoDep.rxLen8 = (uint8_t)gIsoDep.rxBufLen; - } - - gIsoDep.ourFsx = rfalIsoDepFSxI2FSx( - (uint8_t)(gIsoDep.actv.attribReq.Param.PARAM2 & RFAL_ISODEP_ATTRIB_PARAM2_FSDI_MASK)); - } - - return ret; -} - -/*******************************************************************************/ -ReturnCode rfalIsoDepATTRIB( - const uint8_t* nfcid0, - uint8_t PARAM1, - rfalBitRate DSI, - rfalBitRate DRI, - rfalIsoDepFSxI FSDI, - uint8_t PARAM3, - uint8_t DID, - const uint8_t* HLInfo, - uint8_t HLInfoLen, - uint32_t fwt, - rfalIsoDepAttribRes* attribRes, - uint8_t* attribResLen) { - ReturnCode ret; - - EXIT_ON_ERR( - ret, - rfalIsoDepStartATTRIB( - nfcid0, - PARAM1, - DSI, - DRI, - FSDI, - PARAM3, - DID, - HLInfo, - HLInfoLen, - fwt, - attribRes, - attribResLen)); - rfalIsoDepRunBlocking(ret, rfalIsoDepGetATTRIBStatus()); - - return ret; -} - -#endif /* RFAL_FEATURE_NFCB */ - -#if RFAL_FEATURE_NFCA - -/*******************************************************************************/ -ReturnCode rfalIsoDepPollAHandleActivation( - rfalIsoDepFSxI FSDI, - uint8_t DID, - rfalBitRate maxBR, - rfalIsoDepDevice* isoDepDev) { - ReturnCode ret; - - EXIT_ON_ERR(ret, rfalIsoDepPollAStartActivation(FSDI, DID, maxBR, isoDepDev)); - rfalIsoDepRunBlocking(ret, rfalIsoDepPollAGetActivationStatus()); - - return ret; -} - -/*******************************************************************************/ -ReturnCode rfalIsoDepPollAStartActivation( - rfalIsoDepFSxI FSDI, - uint8_t DID, - rfalBitRate maxBR, - rfalIsoDepDevice* isoDepDev) { - ReturnCode ret; - - if(isoDepDev == NULL) { - return ERR_PARAM; - } - - /* Enable EMD handling according Digital 1.1 4.1.1.1 ; EMVCo 2.6 4.9.2 */ - rfalSetErrorHandling(RFAL_ERRORHANDLING_EMVCO); - - /* Start RATS Transceive */ - EXIT_ON_ERR( - ret, - rfalIsoDepStartRATS( - FSDI, - DID, - &isoDepDev->activation.A.Listener.ATS, - &isoDepDev->activation.A.Listener.ATSLen)); - - isoDepDev->info.DSI = maxBR; - gIsoDep.actvDev = isoDepDev; - gIsoDep.cntRRetrys = gIsoDep.maxRetriesRATS; - gIsoDep.state = ISODEP_ST_PCD_ACT_RATS; - - return ret; -} - -/*******************************************************************************/ -ReturnCode rfalIsoDepPollAGetActivationStatus(void) { - ReturnCode ret; - uint8_t msgIt; - rfalBitRate maxBR; - - switch(gIsoDep.state) { - /*******************************************************************************/ - case ISODEP_ST_PCD_ACT_RATS: - - ret = rfalIsoDepGetRATSStatus(); - if(ret != ERR_BUSY) { - if(ret != ERR_NONE) { - /* EMVCo 2.6 9.6.1.1 & 9.6.1.2 If a timeout error is detected retransmit, on transmission error abort */ - if((gIsoDep.compMode == RFAL_COMPLIANCE_MODE_EMV) && (ret != ERR_TIMEOUT)) { - break; - } - - if(gIsoDep.cntRRetrys != 0U) { - /* Ensure FDT before retransmission (reuse RFAL GT timer) */ - rfalSetGT(rfalGetFDTPoll()); - rfalFieldOnAndStartGT(); - - /* Send RATS retransmission */ /* PRQA S 4342 1 # MISRA 10.5 - Layout of enum rfalIsoDepFSxI is guaranteed within 4bit range */ - EXIT_ON_ERR( - ret, - rfalIsoDepStartRATS( - (rfalIsoDepFSxI)(uint8_t)(gIsoDep.actv.ratsReq.PARAM >> RFAL_ISODEP_RATS_PARAM_FSDI_SHIFT), - gIsoDep.did, - &gIsoDep.actvDev->activation.A.Listener.ATS, - &gIsoDep.actvDev->activation.A.Listener.ATSLen)); - gIsoDep.cntRRetrys--; - ret = ERR_BUSY; - } - /* Switch between NFC Forum and ISO14443-4 behaviour #595 - * ISO14443-4 5.6.1 If RATS fails, a Deactivation sequence should be performed as defined on clause 8 - * Activity 1.1 9.6 Device Deactivation Activity is to be only performed when there's an active device */ - else if(gIsoDep.compMode == RFAL_COMPLIANCE_MODE_ISO) { - rfalIsoDepDeselect(); - } else { - /* MISRA 15.7 - Empty else */ - } - } else /* ATS received */ - { - maxBR = gIsoDep.actvDev->info.DSI; /* Retrieve requested max bitrate */ - - /*******************************************************************************/ - /* Process ATS Response */ - gIsoDep.actvDev->info.FWI = - RFAL_ISODEP_FWI_DEFAULT; /* Default value EMVCo 2.6 5.7.2.6 */ - gIsoDep.actvDev->info.SFGI = 0U; - gIsoDep.actvDev->info.MBL = 0U; - gIsoDep.actvDev->info.DSI = RFAL_BR_106; - gIsoDep.actvDev->info.DRI = RFAL_BR_106; - gIsoDep.actvDev->info.FSxI = (uint8_t) - RFAL_ISODEP_FSXI_32; /* FSC default value is 32 bytes ISO14443-A 5.2.3 */ - - /*******************************************************************************/ - /* Check for ATS optional fields */ - if(gIsoDep.actvDev->activation.A.Listener.ATS.TL > RFAL_ISODEP_ATS_MIN_LEN) { - msgIt = RFAL_ISODEP_ATS_MIN_LEN; - - /* Format byte T0 is optional, if present assign FSDI */ - gIsoDep.actvDev->info.FSxI = - (gIsoDep.actvDev->activation.A.Listener.ATS.T0 & - RFAL_ISODEP_ATS_T0_FSCI_MASK); - - /* T0 has already been processed, always the same position */ - msgIt++; - - /* Check if TA is present */ - if((gIsoDep.actvDev->activation.A.Listener.ATS.T0 & - RFAL_ISODEP_ATS_T0_TA_PRESENCE_MASK) != 0U) { - rfalIsoDepCalcBitRate( - maxBR, - ((uint8_t*)&gIsoDep.actvDev->activation.A.Listener.ATS)[msgIt++], - &gIsoDep.actvDev->info.DSI, - &gIsoDep.actvDev->info.DRI); - } - - /* Check if TB is present */ - if((gIsoDep.actvDev->activation.A.Listener.ATS.T0 & - RFAL_ISODEP_ATS_T0_TB_PRESENCE_MASK) != 0U) { - gIsoDep.actvDev->info.SFGI = - ((uint8_t*)&gIsoDep.actvDev->activation.A.Listener.ATS)[msgIt++]; - gIsoDep.actvDev->info.FWI = - (uint8_t)((gIsoDep.actvDev->info.SFGI >> RFAL_ISODEP_ATS_TB_FWI_SHIFT) & RFAL_ISODEP_ATS_FWI_MASK); - gIsoDep.actvDev->info.SFGI &= RFAL_ISODEP_ATS_TB_SFGI_MASK; - } - - /* Check if TC is present */ - if((gIsoDep.actvDev->activation.A.Listener.ATS.T0 & - RFAL_ISODEP_ATS_T0_TC_PRESENCE_MASK) != 0U) { - /* Check for Protocol features support */ - /* Advanced protocol features defined on Digital 1.0 Table 69, removed after */ - gIsoDep.actvDev->info.supAdFt = - (((((uint8_t*)&gIsoDep.actvDev->activation.A.Listener.ATS)[msgIt] & - RFAL_ISODEP_ATS_TC_ADV_FEAT) != 0U) ? - true : - false); - gIsoDep.actvDev->info.supDID = - (((((uint8_t*)&gIsoDep.actvDev->activation.A.Listener.ATS)[msgIt] & - RFAL_ISODEP_ATS_TC_DID) != 0U) ? - true : - false); - gIsoDep.actvDev->info.supNAD = - (((((uint8_t*)&gIsoDep.actvDev->activation.A.Listener.ATS)[msgIt++] & - RFAL_ISODEP_ATS_TC_NAD) != 0U) ? - true : - false); - } - } - - gIsoDep.actvDev->info.FSx = rfalIsoDepFSxI2FSx(gIsoDep.actvDev->info.FSxI); - gIsoDep.fsx = gIsoDep.actvDev->info.FSx; - - gIsoDep.actvDev->info.SFGT = - rfalIsoDepSFGI2SFGT((uint8_t)gIsoDep.actvDev->info.SFGI); - - /* Ensure SFGT before following frame (reuse RFAL GT timer) */ - rfalSetGT(rfalConvMsTo1fc(gIsoDep.actvDev->info.SFGT)); - rfalFieldOnAndStartGT(); - - gIsoDep.actvDev->info.FWT = rfalIsoDepFWI2FWT(gIsoDep.actvDev->info.FWI); - gIsoDep.actvDev->info.dFWT = RFAL_ISODEP_DFWT_20; - - gIsoDep.actvDev->info.DID = - ((gIsoDep.actvDev->info.supDID) ? gIsoDep.did : RFAL_ISODEP_NO_DID); - gIsoDep.actvDev->info.NAD = RFAL_ISODEP_NO_NAD; - - /*******************************************************************************/ - /* If higher bit rates are supported by both devices, send PPS */ - if((gIsoDep.actvDev->info.DSI != RFAL_BR_106) || - (gIsoDep.actvDev->info.DRI != RFAL_BR_106)) { - /* Send PPS */ /* PRQA S 0310 1 # MISRA 11.3 - Intentional safe cast to avoiding buffer duplication */ - EXIT_ON_ERR( - ret, - rfalIsoDepStartPPS( - gIsoDep.actvDev->info.DID, - gIsoDep.actvDev->info.DSI, - gIsoDep.actvDev->info.DRI, - (rfalIsoDepPpsRes*)&gIsoDep.ctrlBuf)); - - gIsoDep.state = ISODEP_ST_PCD_ACT_PPS; - return ERR_BUSY; - } - - return ERR_NONE; - } - } - break; - - /*******************************************************************************/ - case ISODEP_ST_PCD_ACT_PPS: - ret = rfalIsoDepGetPPSSTatus(); - if(ret != ERR_BUSY) { - /* Check whether PPS has been acknowledge */ - if(ret == ERR_NONE) { - /* DSI code the divisor from PICC to PCD */ - /* DRI code the divisor from PCD to PICC */ - rfalSetBitRate(gIsoDep.actvDev->info.DRI, gIsoDep.actvDev->info.DSI); - } else { - /* If PPS has faled keep activation bit rate */ - gIsoDep.actvDev->info.DSI = RFAL_BR_106; - gIsoDep.actvDev->info.DRI = RFAL_BR_106; - } - } - break; - - /*******************************************************************************/ - default: - ret = ERR_WRONG_STATE; - break; - } - - return ret; -} -#endif /* RFAL_FEATURE_NFCA */ - -#if RFAL_FEATURE_NFCB - -/*******************************************************************************/ -ReturnCode rfalIsoDepPollBHandleActivation( - rfalIsoDepFSxI FSDI, - uint8_t DID, - rfalBitRate maxBR, - uint8_t PARAM1, - const rfalNfcbListenDevice* nfcbDev, - const uint8_t* HLInfo, - uint8_t HLInfoLen, - rfalIsoDepDevice* isoDepDev) { - ReturnCode ret; - - EXIT_ON_ERR( - ret, - rfalIsoDepPollBStartActivation( - FSDI, DID, maxBR, PARAM1, nfcbDev, HLInfo, HLInfoLen, isoDepDev)); - rfalIsoDepRunBlocking(ret, rfalIsoDepPollBGetActivationStatus()); - - return ret; -} - -/*******************************************************************************/ -ReturnCode rfalIsoDepPollBStartActivation( - rfalIsoDepFSxI FSDI, - uint8_t DID, - rfalBitRate maxBR, - uint8_t PARAM1, - const rfalNfcbListenDevice* nfcbDev, - const uint8_t* HLInfo, - uint8_t HLInfoLen, - rfalIsoDepDevice* isoDepDev) { - ReturnCode ret; - - /***************************************************************************/ - /* Initialize ISO-DEP Device with info from SENSB_RES */ - isoDepDev->info.FWI = - ((nfcbDev->sensbRes.protInfo.FwiAdcFo >> RFAL_NFCB_SENSB_RES_FWI_SHIFT) & - RFAL_NFCB_SENSB_RES_FWI_MASK); - isoDepDev->info.FWT = rfalIsoDepFWI2FWT(isoDepDev->info.FWI); - isoDepDev->info.dFWT = RFAL_NFCB_DFWT; - isoDepDev->info.SFGI = - (((uint32_t)nfcbDev->sensbRes.protInfo.SFGI >> RFAL_NFCB_SENSB_RES_SFGI_SHIFT) & - RFAL_NFCB_SENSB_RES_SFGI_MASK); - isoDepDev->info.SFGT = rfalIsoDepSFGI2SFGT((uint8_t)isoDepDev->info.SFGI); - isoDepDev->info.FSxI = - ((nfcbDev->sensbRes.protInfo.FsciProType >> RFAL_NFCB_SENSB_RES_FSCI_SHIFT) & - RFAL_NFCB_SENSB_RES_FSCI_MASK); - isoDepDev->info.FSx = rfalIsoDepFSxI2FSx(isoDepDev->info.FSxI); - isoDepDev->info.DID = DID; - isoDepDev->info.supDID = - (((nfcbDev->sensbRes.protInfo.FwiAdcFo & RFAL_NFCB_SENSB_RES_FO_DID_MASK) != 0U) ? true : - false); - isoDepDev->info.supNAD = - (((nfcbDev->sensbRes.protInfo.FwiAdcFo & RFAL_NFCB_SENSB_RES_FO_NAD_MASK) != 0U) ? true : - false); - - /* Check if DID requested is supported by PICC */ - if((DID != RFAL_ISODEP_NO_DID) && (!isoDepDev->info.supDID)) { - return ERR_PARAM; - } - - /* Enable EMD handling according Digital 2.1 4.1.1.1 ; EMVCo 3.0 4.9.2 */ - rfalSetErrorHandling(RFAL_ERRORHANDLING_EMVCO); - - /***************************************************************************/ - /* Set FDT Poll to be used on upcoming communications */ - if(gIsoDep.compMode == RFAL_COMPLIANCE_MODE_EMV) { - /* Disregard Minimum TR2 returned by PICC, always use FDTb MIN EMVCo 3.0 6.3.2.10 */ - rfalSetFDTPoll(RFAL_FDT_POLL_NFCB_POLLER); - } else { - /* Apply minimum TR2 from SENSB_RES Digital 2.1 7.6.2.23 */ - rfalSetFDTPoll(rfalNfcbTR2ToFDT( - ((nfcbDev->sensbRes.protInfo.FsciProType >> RFAL_NFCB_SENSB_RES_PROTO_TR2_SHIFT) & - RFAL_NFCB_SENSB_RES_PROTO_TR2_MASK))); - } - - /* Calculate max Bit Rate */ - rfalIsoDepCalcBitRate( - maxBR, nfcbDev->sensbRes.protInfo.BRC, &isoDepDev->info.DSI, &isoDepDev->info.DRI); - - /***************************************************************************/ - /* Send ATTRIB Command */ - EXIT_ON_ERR( - ret, - rfalIsoDepStartATTRIB( - (const uint8_t*)&nfcbDev->sensbRes.nfcid0, - (((nfcbDev->sensbRes.protInfo.FwiAdcFo & RFAL_NFCB_SENSB_RES_ADC_ADV_FEATURE_MASK) != - 0U) ? - PARAM1 : - RFAL_ISODEP_ATTRIB_REQ_PARAM1_DEFAULT), - isoDepDev->info.DSI, - isoDepDev->info.DRI, - FSDI, - (gIsoDep.compMode == RFAL_COMPLIANCE_MODE_EMV) ? - RFAL_NFCB_SENSB_RES_PROTO_ISO_MASK : - (nfcbDev->sensbRes.protInfo.FsciProType & - ((RFAL_NFCB_SENSB_RES_PROTO_TR2_MASK << RFAL_NFCB_SENSB_RES_PROTO_TR2_SHIFT) | - RFAL_NFCB_SENSB_RES_PROTO_ISO_MASK)), /* EMVCo 2.6 6.4.1.9 */ - DID, - HLInfo, - HLInfoLen, - (isoDepDev->info.FWT + isoDepDev->info.dFWT), - &isoDepDev->activation.B.Listener.ATTRIB_RES, - &isoDepDev->activation.B.Listener.ATTRIB_RESLen)); - - gIsoDep.actvDev = isoDepDev; - return ret; -} - -/*******************************************************************************/ -ReturnCode rfalIsoDepPollBGetActivationStatus(void) { - ReturnCode ret; - uint8_t mbli; - - /***************************************************************************/ - /* Process ATTRIB Response */ - ret = rfalIsoDepGetATTRIBStatus(); - if(ret != ERR_BUSY) { - if(ret == ERR_NONE) { - /* Digital 1.1 14.6.2.3 - Check if received DID match */ - if((gIsoDep.actvDev->activation.B.Listener.ATTRIB_RES.mbliDid & - RFAL_ISODEP_ATTRIB_RES_DID_MASK) != gIsoDep.did) { - return ERR_PROTO; - } - - /* Retrieve MBLI and calculate new FDS/MBL (Maximum Buffer Length) */ - mbli = - ((gIsoDep.actvDev->activation.B.Listener.ATTRIB_RES.mbliDid >> - RFAL_ISODEP_ATTRIB_RES_MBLI_SHIFT) & - RFAL_ISODEP_ATTRIB_RES_MBLI_MASK); - if(mbli > 0U) { - /* Digital 1.1 14.6.2 Calculate Maximum Buffer Length MBL = FSC x 2^(MBLI-1) */ - gIsoDep.actvDev->info.MBL = - (gIsoDep.actvDev->info.FSx * ((uint32_t)1U << (mbli - 1U))); - } - - /* DSI code the divisor from PICC to PCD */ - /* DRI code the divisor from PCD to PICC */ - rfalSetBitRate(gIsoDep.actvDev->info.DRI, gIsoDep.actvDev->info.DSI); - - /* REMARK: SoF EoF TR0 and TR1 are not passed on to RF layer */ - - /* Start the SFGT timer (reuse RFAL GT timer) */ - rfalSetGT(rfalConvMsTo1fc(gIsoDep.actvDev->info.SFGT)); - rfalFieldOnAndStartGT(); - } else { - gIsoDep.actvDev->info.DSI = RFAL_BR_106; - gIsoDep.actvDev->info.DRI = RFAL_BR_106; - } - - /*******************************************************************************/ - /* Store already FS info, rfalIsoDepGetMaxInfLen() may be called before setting TxRx params */ - gIsoDep.fsx = gIsoDep.actvDev->info.FSx; - } - - return ret; -} - -#endif /* RFAL_FEATURE_NFCB */ - -/*******************************************************************************/ -ReturnCode rfalIsoDepPollHandleSParameters( - rfalIsoDepDevice* isoDepDev, - rfalBitRate maxTxBR, - rfalBitRate maxRxBR) { - uint8_t it; - uint8_t supPCD2PICC; - uint8_t supPICC2PCD; - uint8_t currenttxBR; - uint8_t currentrxBR; - rfalBitRate txBR; - rfalBitRate rxBR; - uint16_t rcvLen; - ReturnCode ret; - rfalIsoDepControlMsgSParam sParam; - - if((isoDepDev == NULL) || (maxTxBR > RFAL_BR_13560) || (maxRxBR > RFAL_BR_13560)) { - return ERR_PARAM; - } - - it = 0; - supPICC2PCD = 0x00; - supPCD2PICC = 0x00; - txBR = RFAL_BR_106; - rxBR = RFAL_BR_106; - sParam.pcb = ISODEP_PCB_SPARAMETERS; - - /*******************************************************************************/ - /* Send S(PARAMETERS) - Block Info */ - sParam.sParam.tag = RFAL_ISODEP_SPARAM_TAG_BLOCKINFO; - sParam.sParam.value[it++] = RFAL_ISODEP_SPARAM_TAG_BRREQ; - sParam.sParam.value[it++] = RFAL_ISODEP_SPARAM_TAG_BRREQ_LEN; - sParam.sParam.length = it; - - /* Send S(PARAMETERS). Use a fixed FWI of 4 ISO14443-4 2016 7.2 */ - EXIT_ON_ERR( - ret, - rfalTransceiveBlockingTxRx( - (uint8_t*)&sParam, - (RFAL_ISODEP_SPARAM_HDR_LEN + (uint16_t)it), - (uint8_t*)&sParam, - sizeof(rfalIsoDepControlMsgSParam), - &rcvLen, - RFAL_TXRX_FLAGS_DEFAULT, - ISODEP_FWT_DEACTIVATION)); - - it = 0; - - /*******************************************************************************/ - /* Check S(PARAMETERS) response */ - if((sParam.pcb != ISODEP_PCB_SPARAMETERS) || - (sParam.sParam.tag != RFAL_ISODEP_SPARAM_TAG_BLOCKINFO) || - (sParam.sParam.value[it] != RFAL_ISODEP_SPARAM_TAG_BRIND) || - (rcvLen < RFAL_ISODEP_SPARAM_HDR_LEN) || - (rcvLen != ((uint16_t)sParam.sParam.length + RFAL_ISODEP_SPARAM_HDR_LEN))) { - return ERR_PROTO; - } - - /* Retrieve PICC's bit rate PICC capabilities */ - for(it = 0; it < (rcvLen - (uint16_t)RFAL_ISODEP_SPARAM_TAG_LEN); it++) { - if((sParam.sParam.value[it] == RFAL_ISODEP_SPARAM_TAG_SUP_PCD2PICC) && - (sParam.sParam.value[it + (uint16_t)RFAL_ISODEP_SPARAM_TAG_LEN] == - RFAL_ISODEP_SPARAM_TAG_PCD2PICC_LEN)) { - supPCD2PICC = sParam.sParam.value[it + RFAL_ISODEP_SPARAM_TAG_PCD2PICC_LEN]; - } - - if((sParam.sParam.value[it] == RFAL_ISODEP_SPARAM_TAG_SUP_PICC2PCD) && - (sParam.sParam.value[it + (uint16_t)RFAL_ISODEP_SPARAM_TAG_LEN] == - RFAL_ISODEP_SPARAM_TAG_PICC2PCD_LEN)) { - supPICC2PCD = sParam.sParam.value[it + RFAL_ISODEP_SPARAM_TAG_PICC2PCD_LEN]; - } - } - - /*******************************************************************************/ - /* Check if requested bit rates are supported by PICC */ - if((supPICC2PCD == 0x00U) || (supPCD2PICC == 0x00U)) { - return ERR_PROTO; - } - - for(it = 0; it <= (uint8_t)maxTxBR; it++) { - if((supPCD2PICC & (0x01U << it)) != 0U) { - txBR = (rfalBitRate) - it; /* PRQA S 4342 # MISRA 10.5 - Layout of enum rfalBitRate and above clamping of maxTxBR guarantee no invalid enum values to be created */ - } - } - for(it = 0; it <= (uint8_t)maxRxBR; it++) { - if((supPICC2PCD & (0x01U << it)) != 0U) { - rxBR = (rfalBitRate) - it; /* PRQA S 4342 # MISRA 10.5 - Layout of enum rfalBitRate and above clamping of maxTxBR guarantee no invalid enum values to be created */ - } - } - - it = 0; - currenttxBR = (uint8_t)txBR; - currentrxBR = (uint8_t)rxBR; - - /*******************************************************************************/ - /* Send S(PARAMETERS) - Bit rates Activation */ - sParam.sParam.tag = RFAL_ISODEP_SPARAM_TAG_BLOCKINFO; - sParam.sParam.value[it++] = RFAL_ISODEP_SPARAM_TAG_BRACT; - sParam.sParam.value[it++] = - (RFAL_ISODEP_SPARAM_TVL_HDR_LEN + RFAL_ISODEP_SPARAM_TAG_PCD2PICC_LEN + - RFAL_ISODEP_SPARAM_TVL_HDR_LEN + RFAL_ISODEP_SPARAM_TAG_PICC2PCD_LEN); - sParam.sParam.value[it++] = RFAL_ISODEP_SPARAM_TAG_SEL_PCD2PICC; - sParam.sParam.value[it++] = RFAL_ISODEP_SPARAM_TAG_PCD2PICC_LEN; - sParam.sParam.value[it++] = ((uint8_t)0x01U << currenttxBR); - sParam.sParam.value[it++] = 0x00U; - sParam.sParam.value[it++] = RFAL_ISODEP_SPARAM_TAG_SEL_PICC2PCD; - sParam.sParam.value[it++] = RFAL_ISODEP_SPARAM_TAG_PICC2PCD_LEN; - sParam.sParam.value[it++] = ((uint8_t)0x01U << currentrxBR); - sParam.sParam.value[it++] = 0x00U; - sParam.sParam.length = it; - - EXIT_ON_ERR( - ret, - rfalTransceiveBlockingTxRx( - (uint8_t*)&sParam, - (RFAL_ISODEP_SPARAM_HDR_LEN + (uint16_t)it), - (uint8_t*)&sParam, - sizeof(rfalIsoDepControlMsgSParam), - &rcvLen, - RFAL_TXRX_FLAGS_DEFAULT, - (isoDepDev->info.FWT + isoDepDev->info.dFWT))); - - it = 0; - - /*******************************************************************************/ - /* Check S(PARAMETERS) Acknowledge */ - if((sParam.pcb != ISODEP_PCB_SPARAMETERS) || - (sParam.sParam.tag != RFAL_ISODEP_SPARAM_TAG_BLOCKINFO) || - (sParam.sParam.value[it] != RFAL_ISODEP_SPARAM_TAG_BRACK) || - (rcvLen < RFAL_ISODEP_SPARAM_HDR_LEN)) { - return ERR_PROTO; - } - - EXIT_ON_ERR(ret, rfalSetBitRate(txBR, rxBR)); - - isoDepDev->info.DRI = txBR; - isoDepDev->info.DSI = rxBR; - - return ERR_NONE; -} - -/*******************************************************************************/ -static void rfalIsoDepCalcBitRate( - rfalBitRate maxAllowedBR, - uint8_t piccBRCapability, - rfalBitRate* dsi, - rfalBitRate* dri) { - uint8_t driMask; - uint8_t dsiMask; - int8_t i; - bool bitrateFound; - rfalBitRate curMaxBR; - - curMaxBR = maxAllowedBR; - - do { - bitrateFound = true; - - (*dsi) = RFAL_BR_106; - (*dri) = RFAL_BR_106; - - /* Digital 1.0 5.6.2.5 & 11.6.2.14: A received RFU value of b4 = 1b MUST be interpreted as if b7 to b1 ? 0000000b (only 106 kbits/s in both direction) */ - if(((RFAL_ISODEP_BITRATE_RFU_MASK & piccBRCapability) != 0U) || (curMaxBR > RFAL_BR_848) || - (curMaxBR == RFAL_BR_KEEP)) { - return; - } - - /***************************************************************************/ - /* Determine Listen->Poll bit rate */ - dsiMask = (piccBRCapability & RFAL_ISODEP_BSI_MASK); - for(i = 2; i >= 0; i--) // Check supported bit rate from the highest - { - if(((dsiMask & (0x10U << (uint8_t)i)) != 0U) && - (((uint8_t)i + 1U) <= (uint8_t)curMaxBR)) { - uint8_t newdsi = ((uint8_t)i) + 1U; - (*dsi) = (rfalBitRate) - newdsi; /* PRQA S 4342 # MISRA 10.5 - Layout of enum rfalBitRate and range of loop variable guarantee no invalid enum values to be created */ - break; - } - } - - /***************************************************************************/ - /* Determine Poll->Listen bit rate */ - driMask = (piccBRCapability & RFAL_ISODEP_BRI_MASK); - for(i = 2; i >= 0; i--) /* Check supported bit rate from the highest */ - { - if(((driMask & (0x01U << (uint8_t)i)) != 0U) && - (((uint8_t)i + 1U) <= (uint8_t)curMaxBR)) { - uint8_t newdri = ((uint8_t)i) + 1U; - (*dri) = (rfalBitRate) - newdri; /* PRQA S 4342 # MISRA 10.5 - Layout of enum rfalBitRate and range of loop variable guarantee no invalid enum values to be created */ - break; - } - } - - /***************************************************************************/ - /* Check if different bit rate is supported */ - - /* Digital 1.0 Table 67: if b8=1b, then only the same bit rate divisor for both directions is supported */ - if((piccBRCapability & RFAL_ISODEP_SAME_BITRATE_MASK) != 0U) { - (*dsi) = MIN((*dsi), (*dri)); - (*dri) = (*dsi); - /* Check that the baudrate is supported */ - if((RFAL_BR_106 != (*dsi)) && - (!(((dsiMask & (0x10U << ((uint8_t)(*dsi) - 1U))) != 0U) && - ((driMask & (0x01U << ((uint8_t)(*dri) - 1U))) != 0U)))) { - bitrateFound = false; - curMaxBR = - (*dsi); /* set allowed bitrate to be lowest and determine bit rate again */ - } - } - } while(!(bitrateFound)); -} - -/*******************************************************************************/ -static uint32_t rfalIsoDepSFGI2SFGT(uint8_t sfgi) { - uint32_t sfgt; - uint8_t tmpSFGI; - - tmpSFGI = sfgi; - - if(tmpSFGI > ISODEP_SFGI_MAX) { - tmpSFGI = ISODEP_SFGI_MIN; - } - - if(tmpSFGI != ISODEP_SFGI_MIN) { - /* If sfgi != 0 wait SFGT + dSFGT Digital 1.1 13.8.2.1 */ - sfgt = isoDepCalcSGFT(sfgi) + isoDepCalcdSGFT(sfgi); - } - /* Otherwise use FDTPoll min Digital 1.1 13.8.2.3*/ - else { - sfgt = RFAL_FDT_POLL_NFCA_POLLER; - } - - /* Convert carrier cycles to milli seconds */ - return (rfalConv1fcToMs(sfgt) + 1U); -} - -#endif /* RFAL_FEATURE_ISO_DEP_POLL */ - -/*******************************************************************************/ -static void rfalIsoDepApdu2IBLockParam( - rfalIsoDepApduTxRxParam apduParam, - rfalIsoDepTxRxParam* iBlockParam, - uint16_t txPos, - uint16_t rxPos) { - NO_WARNING(rxPos); /* Keep this param for future use */ - - iBlockParam->DID = apduParam.DID; - iBlockParam->FSx = apduParam.FSx; - iBlockParam->ourFSx = apduParam.ourFSx; - iBlockParam->FWT = apduParam.FWT; - iBlockParam->dFWT = apduParam.dFWT; - - if((apduParam.txBufLen - txPos) > rfalIsoDepGetMaxInfLen()) { - iBlockParam->isTxChaining = true; - iBlockParam->txBufLen = rfalIsoDepGetMaxInfLen(); - } else { - iBlockParam->isTxChaining = false; - iBlockParam->txBufLen = (apduParam.txBufLen - txPos); - } - - /* TxBuf is moved to the beginning for every I-Block */ - iBlockParam->txBuf = - (rfalIsoDepBufFormat*)apduParam - .txBuf; /* PRQA S 0310 # MISRA 11.3 - Intentional safe cast to avoiding large buffer duplication */ - iBlockParam->rxBuf = - apduParam - .tmpBuf; /* Simply using the apdu buffer is not possible because of current ACK handling */ - iBlockParam->isRxChaining = &gIsoDep.isAPDURxChaining; - iBlockParam->rxLen = apduParam.rxLen; -} - -/*******************************************************************************/ -ReturnCode rfalIsoDepStartApduTransceive(rfalIsoDepApduTxRxParam param) { - rfalIsoDepTxRxParam txRxParam; - - /* Initialize and store APDU context */ - gIsoDep.APDUParam = param; - gIsoDep.APDUTxPos = 0; - gIsoDep.APDURxPos = 0; - - /* Assign current FSx to calculate INF length (only change the FSx from activation if no to Keep) */ - gIsoDep.ourFsx = ((param.ourFSx != RFAL_ISODEP_FSX_KEEP) ? param.ourFSx : gIsoDep.ourFsx); - gIsoDep.fsx = param.FSx; - - /* Convert APDU TxRxParams to I-Block TxRxParams */ - rfalIsoDepApdu2IBLockParam( - gIsoDep.APDUParam, &txRxParam, gIsoDep.APDUTxPos, gIsoDep.APDURxPos); - - return rfalIsoDepStartTransceive(txRxParam); -} - -/*******************************************************************************/ -ReturnCode rfalIsoDepGetApduTransceiveStatus(void) { - ReturnCode ret; - rfalIsoDepTxRxParam txRxParam; - - ret = rfalIsoDepGetTransceiveStatus(); - switch(ret) { - /*******************************************************************************/ - case ERR_NONE: - - /* Check if we are still doing chaining on Tx */ - if(gIsoDep.isTxChaining) { - /* Add already Tx bytes */ - gIsoDep.APDUTxPos += gIsoDep.txBufLen; - - /* Convert APDU TxRxParams to I-Block TxRxParams */ - rfalIsoDepApdu2IBLockParam( - gIsoDep.APDUParam, &txRxParam, gIsoDep.APDUTxPos, gIsoDep.APDURxPos); - - if(txRxParam.txBufLen > 0U) /* MISRA 21.18 */ - { - /* Move next I-Block to beginning of APDU Tx buffer */ - ST_MEMCPY( - gIsoDep.APDUParam.txBuf->apdu, - &gIsoDep.APDUParam.txBuf->apdu[gIsoDep.APDUTxPos], - txRxParam.txBufLen); - } - - EXIT_ON_ERR(ret, rfalIsoDepStartTransceive(txRxParam)); - return ERR_BUSY; - } - - /* APDU TxRx is done */ - /* fall through */ - - /*******************************************************************************/ - case ERR_AGAIN: /* PRQA S 2003 # MISRA 16.3 - Intentional fall through */ - - /* Check if no APDU transceive has been started before (data from rfalIsoDepListenStartActivation) */ - if(gIsoDep.APDUParam.rxLen == NULL) { - if(ret == ERR_AGAIN) { - /* In Listen mode first chained packet cannot be retrieved via APDU interface */ - return ERR_NOTSUPP; - } - - /* TxRx is complete and full data is already available */ - return ERR_NONE; - } - - if(*gIsoDep.APDUParam.rxLen > 0U) /* MISRA 21.18 */ - { - /* Ensure that data in tmpBuf still fits into APDU buffer */ - if((gIsoDep.APDURxPos + (*gIsoDep.APDUParam.rxLen)) > - (uint16_t)RFAL_FEATURE_ISO_DEP_APDU_MAX_LEN) { - return ERR_NOMEM; - } - - /* Copy chained packet from tmp buffer to APDU buffer */ - ST_MEMCPY( - &gIsoDep.APDUParam.rxBuf->apdu[gIsoDep.APDURxPos], - gIsoDep.APDUParam.tmpBuf->inf, - *gIsoDep.APDUParam.rxLen); - gIsoDep.APDURxPos += *gIsoDep.APDUParam.rxLen; - } - - /* Update output param rxLen */ - *gIsoDep.APDUParam.rxLen = gIsoDep.APDURxPos * 8; - - /* Wait for following I-Block or APDU TxRx has finished */ - return ((ret == ERR_AGAIN) ? ERR_BUSY : ERR_NONE); - - /*******************************************************************************/ - default: - /* MISRA 16.4: no empty default statement (a comment being enough) */ - break; - } - - return ret; -} - -#endif /* RFAL_FEATURE_ISO_DEP */ diff --git a/lib/ST25RFAL002/source/rfal_nfc.c b/lib/ST25RFAL002/source/rfal_nfc.c deleted file mode 100644 index 57ff2e2358a..00000000000 --- a/lib/ST25RFAL002/source/rfal_nfc.c +++ /dev/null @@ -1,2118 +0,0 @@ -/** - ****************************************************************************** - * - * COPYRIGHT(c) 2020 STMicroelectronics - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. Neither the name of STMicroelectronics nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - ****************************************************************************** - */ - -/*! \file rfal_nfc.c - * - * \author Gustavo Patricio - * - * \brief RFAL NFC device - * - * This module provides the required features to behave as an NFC Poller - * or Listener device. It grants an easy to use interface for the following - * activities: Technology Detection, Collision Resollution, Activation, - * Data Exchange, and Deactivation - * - * This layer is influenced by (but not fully aligned with) the NFC Forum - * specifications, in particular: Activity 2.0 and NCI 2.0 - * - */ - -/* - ****************************************************************************** - * INCLUDES - ****************************************************************************** - */ -#include "rfal_nfc.h" -#include "utils.h" -#include "rfal_analogConfig.h" - -/* -****************************************************************************** -* GLOBAL DEFINES -****************************************************************************** -*/ -#define RFAL_NFC_MAX_DEVICES 5U /* Max number of devices supported */ - -/* -****************************************************************************** -* GLOBAL MACROS -****************************************************************************** -*/ - -#define rfalNfcNfcNotify(st) \ - if(gNfcDev.disc.notifyCb != NULL) gNfcDev.disc.notifyCb(st) - -/* -****************************************************************************** -* GLOBAL TYPES -****************************************************************************** -*/ - -/*! Buffer union, only one interface is used at a time */ -typedef union { /* PRQA S 0750 # MISRA 19.2 - Members of the union will not be used concurrently, only one interface at a time */ - rfalIsoDepBufFormat isoDepBuf; /*!< ISO-DEP buffer format (with header/prologue) */ - rfalNfcDepBufFormat nfcDepBuf; /*!< NFC-DEP buffer format (with header/prologue) */ -} rfalNfcTmpBuffer; - -typedef struct { - rfalNfcState state; /* Main state */ - uint16_t techsFound; /* Technologies found bitmask */ - uint16_t techs2do; /* Technologies still to be performed */ - rfalBitRate ap2pBR; /* Bit rate to poll for AP2P */ - uint8_t selDevIdx; /* Selected device index */ - rfalNfcDevice* activeDev; /* Active device pointer */ - rfalNfcDiscoverParam disc; /* Discovery parameters */ - rfalNfcDevice devList[RFAL_NFC_MAX_DEVICES]; /*!< Location of device list */ - uint8_t devCnt; /* Devices found counter */ - uint32_t discTmr; /* Discovery Total duration timer */ - ReturnCode dataExErr; /* Last Data Exchange error */ - bool discRestart; /* Restart discover after deactivation flag */ - bool isRxChaining; /* Flag indicating Other device is chaining */ - uint32_t lmMask; /* Listen Mode mask */ - bool isTechInit; /* Flag indicating technology has been set */ - bool isOperOngoing; /* Flag indicating operation is ongoing */ - - rfalNfcBuffer txBuf; /* Tx buffer for Data Exchange */ - rfalNfcBuffer rxBuf; /* Rx buffer for Data Exchange */ - uint16_t rxLen; /* Length of received data on Data Exchange */ - -#if RFAL_FEATURE_NFC_DEP || RFAL_FEATURE_ISO_DEP - rfalNfcTmpBuffer tmpBuf; /* Tmp buffer for Data Exchange */ -#endif /* RFAL_FEATURE_NFC_DEP || RFAL_FEATURE_ISO_DEP */ - -} rfalNfc; - -/* - ****************************************************************************** - * LOCAL VARIABLES - ****************************************************************************** - */ -#ifdef RFAL_TEST_MODE -rfalNfc gNfcDev; -#else /* RFAL_TEST_MODE */ -static rfalNfc gNfcDev; -#endif /* RFAL_TEST_MODE */ - -/* -****************************************************************************** -* LOCAL FUNCTION PROTOTYPES -****************************************************************************** -*/ -static ReturnCode rfalNfcPollTechDetetection(void); -static ReturnCode rfalNfcPollCollResolution(void); -static ReturnCode rfalNfcPollActivation(uint8_t devIt); -static ReturnCode rfalNfcDeactivation(void); - -#if RFAL_FEATURE_NFC_DEP -static ReturnCode rfalNfcNfcDepActivate( - rfalNfcDevice* device, - rfalNfcDepCommMode commMode, - const uint8_t* atrReq, - uint16_t atrReqLen); -#endif /* RFAL_FEATURE_NFC_DEP */ - -#if RFAL_FEATURE_LISTEN_MODE -static ReturnCode rfalNfcListenActivation(void); -#endif /* RFAL_FEATURE_LISTEN_MODE*/ - -/*******************************************************************************/ -ReturnCode rfalNfcInitialize(void) { - ReturnCode err; - - gNfcDev.state = RFAL_NFC_STATE_NOTINIT; - - rfalAnalogConfigInitialize(); /* Initialize RFAL's Analog Configs */ - EXIT_ON_ERR(err, rfalInitialize()); /* Initialize RFAL */ - - gNfcDev.state = RFAL_NFC_STATE_IDLE; /* Go to initialized */ - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalNfcDiscover(const rfalNfcDiscoverParam* disParams) { - /* Check if initialization has been performed */ - if(gNfcDev.state != RFAL_NFC_STATE_IDLE) { - return ERR_WRONG_STATE; - } - - /* Check valid parameters */ - if((disParams == NULL) || (disParams->devLimit > RFAL_NFC_MAX_DEVICES) || - (disParams->devLimit == 0U) || - ((disParams->maxBR > RFAL_BR_1695) && (disParams->maxBR != RFAL_BR_KEEP)) || - (((disParams->techs2Find & RFAL_NFC_POLL_TECH_F) != 0U) && - (disParams->nfcfBR != RFAL_BR_212) && (disParams->nfcfBR != RFAL_BR_424)) || - ((((disParams->techs2Find & RFAL_NFC_POLL_TECH_AP2P) != 0U) && - (disParams->ap2pBR > RFAL_BR_424)) || - (disParams->GBLen > RFAL_NFCDEP_GB_MAX_LEN))) { - return ERR_PARAM; - } - - if((((disParams->techs2Find & RFAL_NFC_POLL_TECH_A) != 0U) && !((bool)RFAL_FEATURE_NFCA)) || - (((disParams->techs2Find & RFAL_NFC_POLL_TECH_B) != 0U) && !((bool)RFAL_FEATURE_NFCB)) || - (((disParams->techs2Find & RFAL_NFC_POLL_TECH_F) != 0U) && !((bool)RFAL_FEATURE_NFCF)) || - (((disParams->techs2Find & RFAL_NFC_POLL_TECH_V) != 0U) && !((bool)RFAL_FEATURE_NFCV)) || - (((disParams->techs2Find & RFAL_NFC_POLL_TECH_ST25TB) != 0U) && - !((bool)RFAL_FEATURE_ST25TB)) || - (((disParams->techs2Find & RFAL_NFC_POLL_TECH_AP2P) != 0U) && - !((bool)RFAL_FEATURE_NFC_DEP)) || - (((disParams->techs2Find & RFAL_NFC_LISTEN_TECH_A) != 0U) && !((bool)RFAL_FEATURE_NFCA)) || - (((disParams->techs2Find & RFAL_NFC_LISTEN_TECH_B) != 0U) && !((bool)RFAL_FEATURE_NFCB)) || - (((disParams->techs2Find & RFAL_NFC_LISTEN_TECH_F) != 0U) && !((bool)RFAL_FEATURE_NFCF)) || - (((disParams->techs2Find & RFAL_NFC_LISTEN_TECH_AP2P) != 0U) && - !((bool)RFAL_FEATURE_NFC_DEP))) { - return ERR_DISABLED; /* PRQA S 2880 # MISRA 2.1 - Unreachable code due to configuration option being set/unset */ - } - - /* Initialize context for discovery */ - gNfcDev.activeDev = NULL; - gNfcDev.techsFound = RFAL_NFC_TECH_NONE; - gNfcDev.devCnt = 0; - gNfcDev.discRestart = true; - gNfcDev.isTechInit = false; - gNfcDev.disc = *disParams; - - /* Calculate Listen Mask */ - gNfcDev.lmMask = 0U; - gNfcDev.lmMask |= - (((gNfcDev.disc.techs2Find & RFAL_NFC_LISTEN_TECH_A) != 0U) ? RFAL_LM_MASK_NFCA : 0U); - gNfcDev.lmMask |= - (((gNfcDev.disc.techs2Find & RFAL_NFC_LISTEN_TECH_B) != 0U) ? RFAL_LM_MASK_NFCB : 0U); - gNfcDev.lmMask |= - (((gNfcDev.disc.techs2Find & RFAL_NFC_LISTEN_TECH_F) != 0U) ? RFAL_LM_MASK_NFCF : 0U); - gNfcDev.lmMask |= - (((gNfcDev.disc.techs2Find & RFAL_NFC_LISTEN_TECH_AP2P) != 0U) ? RFAL_LM_MASK_ACTIVE_P2P : - 0U); - -#if !RFAL_FEATURE_LISTEN_MODE - /* Check if Listen Mode is supported/Enabled */ - if(gNfcDev.lmMask != 0U) { - return ERR_DISABLED; - } -#endif - - gNfcDev.state = RFAL_NFC_STATE_START_DISCOVERY; - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalNfcDeactivate(bool discovery) { - /* Check for valid state */ - if(gNfcDev.state <= RFAL_NFC_STATE_IDLE) { - return ERR_WRONG_STATE; - } - - /* Check if discovery is to continue afterwards */ - if((discovery == true) && (gNfcDev.disc.techs2Find != RFAL_NFC_TECH_NONE)) { - /* If so let the state machine continue*/ - gNfcDev.discRestart = discovery; - gNfcDev.state = RFAL_NFC_STATE_DEACTIVATION; - } else { - /* Otherwise deactivate immediately and go to IDLE */ - rfalNfcDeactivation(); - gNfcDev.state = RFAL_NFC_STATE_IDLE; - } - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalNfcSelect(uint8_t devIdx) { - /* Check for valid state */ - if(gNfcDev.state != RFAL_NFC_STATE_POLL_SELECT) { - return ERR_WRONG_STATE; - } - - gNfcDev.selDevIdx = devIdx; - gNfcDev.state = RFAL_NFC_STATE_POLL_ACTIVATION; - - return ERR_NONE; -} - -/*******************************************************************************/ -rfalNfcState rfalNfcGetState(void) { - return gNfcDev.state; -} - -/*******************************************************************************/ -ReturnCode rfalNfcGetDevicesFound(rfalNfcDevice** devList, uint8_t* devCnt) { - /* Check for valid state */ - if(gNfcDev.state < RFAL_NFC_STATE_POLL_SELECT) { - return ERR_WRONG_STATE; - } - - /* Check valid parameters */ - if((devList == NULL) || (devCnt == NULL)) { - return ERR_PARAM; - } - - *devCnt = gNfcDev.devCnt; - *devList = gNfcDev.devList; - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalNfcGetActiveDevice(rfalNfcDevice** dev) { - /* Check for valid state */ - if(gNfcDev.state < RFAL_NFC_STATE_ACTIVATED) { - return ERR_WRONG_STATE; - } - - /* Check valid parameter */ - if(dev == NULL) { - return ERR_PARAM; - } - - /* Check for valid state */ - if((gNfcDev.devCnt == 0U) || (gNfcDev.activeDev == NULL)) { - return ERR_REQUEST; - } - - *dev = gNfcDev.activeDev; - return ERR_NONE; -} - -/*******************************************************************************/ -void rfalNfcWorker(void) { - ReturnCode err; - - rfalWorker(); /* Execute RFAL process */ - - switch(gNfcDev.state) { - /*******************************************************************************/ - case RFAL_NFC_STATE_NOTINIT: - case RFAL_NFC_STATE_IDLE: - break; - - /*******************************************************************************/ - case RFAL_NFC_STATE_START_DISCOVERY: - - /* Initialize context for discovery cycle */ - gNfcDev.devCnt = 0; - gNfcDev.selDevIdx = 0; - gNfcDev.techsFound = RFAL_NFC_TECH_NONE; - gNfcDev.techs2do = gNfcDev.disc.techs2Find; - gNfcDev.state = RFAL_NFC_STATE_POLL_TECHDETECT; - -#if RFAL_FEATURE_WAKEUP_MODE - /* Check if Low power Wake-Up is to be performed */ - if(gNfcDev.disc.wakeupEnabled) { - /* Initialize Low power Wake-up mode and wait */ - err = rfalWakeUpModeStart( - (gNfcDev.disc.wakeupConfigDefault ? NULL : &gNfcDev.disc.wakeupConfig)); - if(err == ERR_NONE) { - gNfcDev.state = RFAL_NFC_STATE_WAKEUP_MODE; - rfalNfcNfcNotify(gNfcDev.state); /* Notify caller that WU was started */ - } - } -#endif /* RFAL_FEATURE_WAKEUP_MODE */ - break; - - /*******************************************************************************/ - case RFAL_NFC_STATE_WAKEUP_MODE: - -#if RFAL_FEATURE_WAKEUP_MODE - /* Check if the Wake-up mode has woke */ - if(rfalWakeUpModeHasWoke()) { - rfalWakeUpModeStop(); /* Disable Wake-up mode */ - gNfcDev.state = RFAL_NFC_STATE_POLL_TECHDETECT; /* Go to Technology detection */ - - rfalNfcNfcNotify(gNfcDev.state); /* Notify caller that WU has woke */ - } -#endif /* RFAL_FEATURE_WAKEUP_MODE */ - - break; - - /*******************************************************************************/ - case RFAL_NFC_STATE_POLL_TECHDETECT: - - /* Start total duration timer */ - platformTimerDestroy(gNfcDev.discTmr); - gNfcDev.discTmr = (uint32_t)platformTimerCreate(gNfcDev.disc.totalDuration); - - err = - rfalNfcPollTechDetetection(); /* Perform Technology Detection */ - if(err != ERR_BUSY) /* Wait until all technologies are performed */ - { - if((err != ERR_NONE) || - (gNfcDev.techsFound == - RFAL_NFC_TECH_NONE)) /* Check if any error occurred or no techs were found */ - { - rfalFieldOff(); - gNfcDev.state = - RFAL_NFC_STATE_LISTEN_TECHDETECT; /* Nothing found as poller, go to listener */ - break; - } - - gNfcDev.techs2do = - gNfcDev.techsFound; /* Store the found technologies for collision resolution */ - gNfcDev.state = - RFAL_NFC_STATE_POLL_COLAVOIDANCE; /* One or more devices found, go to Collision Avoidance */ - } - break; - - /*******************************************************************************/ - case RFAL_NFC_STATE_POLL_COLAVOIDANCE: - - err = - rfalNfcPollCollResolution(); /* Resolve any eventual collision */ - if(err != ERR_BUSY) /* Wait until all technologies are performed */ - { - if((err != ERR_NONE) || - (gNfcDev.devCnt == 0U)) /* Check if any error occurred or no devices were found */ - { - gNfcDev.state = RFAL_NFC_STATE_DEACTIVATION; - break; /* Unable to retrieve any device, restart loop */ - } - - /* Check if more than one device has been found */ - if(gNfcDev.devCnt > 1U) { - /* If more than one device was found inform upper layer to choose which one to activate */ - if(gNfcDev.disc.notifyCb != NULL) { - gNfcDev.state = RFAL_NFC_STATE_POLL_SELECT; - gNfcDev.disc.notifyCb(gNfcDev.state); - break; - } - } - - /* If only one device or no callback has been set, activate the first device found */ - gNfcDev.selDevIdx = 0U; - gNfcDev.state = RFAL_NFC_STATE_POLL_ACTIVATION; - } - break; - - /*******************************************************************************/ - case RFAL_NFC_STATE_POLL_ACTIVATION: - - err = rfalNfcPollActivation(gNfcDev.selDevIdx); - if(err != ERR_BUSY) /* Wait until all Activation is complete */ - { - if(err != ERR_NONE) /* Activation failed selected device */ - { - gNfcDev.state = - RFAL_NFC_STATE_DEACTIVATION; /* If Activation failed, restart loop */ - break; - } - - gNfcDev.state = RFAL_NFC_STATE_ACTIVATED; /* Device has been properly activated */ - rfalNfcNfcNotify( - gNfcDev.state); /* Inform upper layer that a device has been activated */ - } - break; - - /*******************************************************************************/ - case RFAL_NFC_STATE_DATAEXCHANGE: - - rfalNfcDataExchangeGetStatus(); /* Run the internal state machine */ - - if(gNfcDev.dataExErr != ERR_BUSY) /* If Dataexchange has terminated */ - { - gNfcDev.state = RFAL_NFC_STATE_DATAEXCHANGE_DONE; /* Go to done state */ - rfalNfcNfcNotify(gNfcDev.state); /* And notify caller */ - } - if(gNfcDev.dataExErr == ERR_SLEEP_REQ) /* Check if Listen mode has to go to Sleep */ - { - gNfcDev.state = RFAL_NFC_STATE_LISTEN_SLEEP; /* Go to Listen Sleep state */ - rfalNfcNfcNotify(gNfcDev.state); /* And notify caller */ - } - break; - - /*******************************************************************************/ - case RFAL_NFC_STATE_DEACTIVATION: - - rfalNfcDeactivation(); /* Deactivate current device */ - - gNfcDev.state = - ((gNfcDev.discRestart) ? RFAL_NFC_STATE_START_DISCOVERY : RFAL_NFC_STATE_IDLE); - rfalNfcNfcNotify(gNfcDev.state); /* Notify caller */ - break; - - /*******************************************************************************/ - case RFAL_NFC_STATE_LISTEN_TECHDETECT: - - if(platformTimerIsExpired(gNfcDev.discTmr)) { -#if RFAL_FEATURE_LISTEN_MODE - rfalListenStop(); -#else - rfalFieldOff(); -#endif /* RFAL_FEATURE_LISTEN_MODE */ - - gNfcDev.state = RFAL_NFC_STATE_START_DISCOVERY; /* Restart the discovery loop */ - rfalNfcNfcNotify(gNfcDev.state); /* Notify caller */ - break; - } - -#if RFAL_FEATURE_LISTEN_MODE - - if(gNfcDev.lmMask != 0U) /* Check if configured to perform Listen mode */ - { - err = rfalListenStart( - gNfcDev.lmMask, - &gNfcDev.disc.lmConfigPA, - NULL, - &gNfcDev.disc.lmConfigPF, - (uint8_t*)&gNfcDev.rxBuf.rfBuf, - (uint16_t)rfalConvBytesToBits(sizeof(gNfcDev.rxBuf.rfBuf)), - &gNfcDev.rxLen); - if(err == ERR_NONE) { - gNfcDev.state = - RFAL_NFC_STATE_LISTEN_COLAVOIDANCE; /* Wait for listen mode to be activated */ - } - } - break; - - /*******************************************************************************/ - case RFAL_NFC_STATE_LISTEN_COLAVOIDANCE: - - if(platformTimerIsExpired( - gNfcDev.discTmr)) /* Check if the total duration has been reached */ - { - rfalListenStop(); - gNfcDev.state = RFAL_NFC_STATE_START_DISCOVERY; /* Restart the discovery loop */ - rfalNfcNfcNotify(gNfcDev.state); /* Notify caller */ - break; - } - - /* Check for external field */ - if(rfalListenGetState(NULL, NULL) >= RFAL_LM_STATE_IDLE) { - gNfcDev.state = - RFAL_NFC_STATE_LISTEN_ACTIVATION; /* Wait for listen mode to be activated */ - } - break; - - /*******************************************************************************/ - case RFAL_NFC_STATE_LISTEN_ACTIVATION: - case RFAL_NFC_STATE_LISTEN_SLEEP: - - err = rfalNfcListenActivation(); - if(err != ERR_BUSY) { - if(err == ERR_NONE) { - gNfcDev.activeDev = - gNfcDev.devList; /* Assign the active device to be used further on */ - gNfcDev.devCnt++; - - gNfcDev.state = RFAL_NFC_STATE_ACTIVATED; /* Device has been properly activated */ - rfalNfcNfcNotify( - gNfcDev.state); /* Inform upper layer that a device has been activated */ - } else { - rfalListenStop(); - gNfcDev.state = RFAL_NFC_STATE_START_DISCOVERY; /* Restart the discovery loop */ - rfalNfcNfcNotify(gNfcDev.state); /* Notify caller */ - } - } -#endif /* RFAL_FEATURE_LISTEN_MODE */ - break; - - /*******************************************************************************/ - case RFAL_NFC_STATE_ACTIVATED: - case RFAL_NFC_STATE_POLL_SELECT: - case RFAL_NFC_STATE_DATAEXCHANGE_DONE: - default: - return; - } -} - -/*******************************************************************************/ -ReturnCode rfalNfcDataExchangeStart( - uint8_t* txData, - uint16_t txDataLen, - uint8_t** rxData, - uint16_t** rvdLen, - uint32_t fwt, - uint32_t flags) { - ReturnCode err; - rfalTransceiveContext ctx; - - /*******************************************************************************/ - /* The Data Exchange is divided in two different moments, the trigger/Start of * - * the transfer followed by the check until its completion */ - if((gNfcDev.state >= RFAL_NFC_STATE_ACTIVATED) && (gNfcDev.activeDev != NULL)) { - /*******************************************************************************/ - /* In Listen mode is the Poller that initiates the communicatation */ - /* Assign output parameters and rfalNfcDataExchangeGetStatus will return */ - /* incoming data from Poller/Initiator */ - if((gNfcDev.state == RFAL_NFC_STATE_ACTIVATED) && - rfalNfcIsRemDevPoller(gNfcDev.activeDev->type)) { - if(txDataLen > 0U) { - return ERR_WRONG_STATE; - } - - *rvdLen = (uint16_t*)&gNfcDev.rxLen; - *rxData = (uint8_t*)( (gNfcDev.activeDev->rfInterface == RFAL_NFC_INTERFACE_ISODEP) ? gNfcDev.rxBuf.isoDepBuf.apdu : - ((gNfcDev.activeDev->rfInterface == RFAL_NFC_INTERFACE_NFCDEP) ? gNfcDev.rxBuf.nfcDepBuf.pdu : gNfcDev.rxBuf.rfBuf)); - if(gNfcDev.disc.activate_after_sak) { - gNfcDev.state = RFAL_NFC_STATE_DATAEXCHANGE_DONE; - } - return ERR_NONE; - } - - /*******************************************************************************/ - switch(gNfcDev.activeDev - ->rfInterface) /* Check which RF interface shall be used/has been activated */ - { - /*******************************************************************************/ - case RFAL_NFC_INTERFACE_RF: - - rfalCreateByteFlagsTxRxContext( - ctx, - (uint8_t*)txData, - txDataLen, - gNfcDev.rxBuf.rfBuf, - sizeof(gNfcDev.rxBuf.rfBuf), - &gNfcDev.rxLen, - flags, - fwt); - if(flags == RFAL_TXRX_FLAGS_RAW) { - ctx.txBufLen = txDataLen; - } - *rxData = (uint8_t*)gNfcDev.rxBuf.rfBuf; - *rvdLen = (uint16_t*)&gNfcDev.rxLen; - err = rfalStartTransceive(&ctx); - break; - -#if RFAL_FEATURE_ISO_DEP - /*******************************************************************************/ - case RFAL_NFC_INTERFACE_ISODEP: { - rfalIsoDepApduTxRxParam isoDepTxRx; - - if(txDataLen > sizeof(gNfcDev.txBuf.isoDepBuf.apdu)) { - return ERR_NOMEM; - } - - if(txDataLen > 0U) { - ST_MEMCPY((uint8_t*)gNfcDev.txBuf.isoDepBuf.apdu, txData, txDataLen); - } - - isoDepTxRx.DID = RFAL_ISODEP_NO_DID; - isoDepTxRx.ourFSx = RFAL_ISODEP_FSX_KEEP; - isoDepTxRx.FSx = gNfcDev.activeDev->proto.isoDep.info.FSx; - isoDepTxRx.dFWT = gNfcDev.activeDev->proto.isoDep.info.dFWT; - isoDepTxRx.FWT = gNfcDev.activeDev->proto.isoDep.info.FWT; - isoDepTxRx.txBuf = &gNfcDev.txBuf.isoDepBuf; - isoDepTxRx.txBufLen = txDataLen; - isoDepTxRx.rxBuf = &gNfcDev.rxBuf.isoDepBuf; - isoDepTxRx.rxLen = &gNfcDev.rxLen; - isoDepTxRx.tmpBuf = &gNfcDev.tmpBuf.isoDepBuf; - *rxData = (uint8_t*)gNfcDev.rxBuf.isoDepBuf.apdu; - *rvdLen = (uint16_t*)&gNfcDev.rxLen; - - /*******************************************************************************/ - /* Trigger a RFAL ISO-DEP Transceive */ - err = rfalIsoDepStartApduTransceive(isoDepTxRx); - break; - } -#endif /* RFAL_FEATURE_ISO_DEP */ - -#if RFAL_FEATURE_NFC_DEP - /*******************************************************************************/ - case RFAL_NFC_INTERFACE_NFCDEP: { - rfalNfcDepPduTxRxParam nfcDepTxRx; - - if(txDataLen > sizeof(gNfcDev.txBuf.nfcDepBuf.pdu)) { - return ERR_NOMEM; - } - - if(txDataLen > 0U) { - ST_MEMCPY((uint8_t*)gNfcDev.txBuf.nfcDepBuf.pdu, txData, txDataLen); - } - - nfcDepTxRx.DID = RFAL_NFCDEP_DID_KEEP; - nfcDepTxRx.FSx = - rfalNfcIsRemDevListener(gNfcDev.activeDev->type) ? - rfalNfcDepLR2FS((uint8_t)rfalNfcDepPP2LR( - gNfcDev.activeDev->proto.nfcDep.activation.Target.ATR_RES.PPt)) : - rfalNfcDepLR2FS((uint8_t)rfalNfcDepPP2LR( - gNfcDev.activeDev->proto.nfcDep.activation.Initiator.ATR_REQ.PPi)); - nfcDepTxRx.dFWT = gNfcDev.activeDev->proto.nfcDep.info.dFWT; - nfcDepTxRx.FWT = gNfcDev.activeDev->proto.nfcDep.info.FWT; - nfcDepTxRx.txBuf = &gNfcDev.txBuf.nfcDepBuf; - nfcDepTxRx.txBufLen = txDataLen; - nfcDepTxRx.rxBuf = &gNfcDev.rxBuf.nfcDepBuf; - nfcDepTxRx.rxLen = &gNfcDev.rxLen; - nfcDepTxRx.tmpBuf = &gNfcDev.tmpBuf.nfcDepBuf; - *rxData = (uint8_t*)gNfcDev.rxBuf.nfcDepBuf.pdu; - *rvdLen = (uint16_t*)&gNfcDev.rxLen; - - /*******************************************************************************/ - /* Trigger a RFAL NFC-DEP Transceive */ - err = rfalNfcDepStartPduTransceive(nfcDepTxRx); - break; - } -#endif /* RFAL_FEATURE_NFC_DEP */ - - /*******************************************************************************/ - default: - err = ERR_PARAM; - break; - } - - /* If a transceive has successfuly started flag Data Exchange as ongoing */ - if(err == ERR_NONE) { - gNfcDev.dataExErr = ERR_BUSY; - gNfcDev.state = RFAL_NFC_STATE_DATAEXCHANGE; - } - - return err; - } - - return ERR_WRONG_STATE; -} - -ReturnCode rfalNfcDataExchangeCustomStart( - uint8_t* txData, - uint16_t txDataLen, - uint8_t** rxData, - uint16_t** rvdLen, - uint32_t fwt, - uint32_t flags) { - ReturnCode err; - rfalTransceiveContext ctx; - - /*******************************************************************************/ - /* The Data Exchange is divided in two different moments, the trigger/Start of * - * the transfer followed by the check until its completion */ - if((gNfcDev.state >= RFAL_NFC_STATE_ACTIVATED) && (gNfcDev.activeDev != NULL)) { - /*******************************************************************************/ - /* In Listen mode is the Poller that initiates the communicatation */ - /* Assign output parameters and rfalNfcDataExchangeGetStatus will return */ - /* incoming data from Poller/Initiator */ - if((gNfcDev.state == RFAL_NFC_STATE_ACTIVATED) && - rfalNfcIsRemDevPoller(gNfcDev.activeDev->type)) { - if(txDataLen > 0U) { - return ERR_WRONG_STATE; - } - - *rvdLen = (uint16_t*)&gNfcDev.rxLen; - *rxData = (uint8_t*)( (gNfcDev.activeDev->rfInterface == RFAL_NFC_INTERFACE_ISODEP) ? gNfcDev.rxBuf.isoDepBuf.apdu : - ((gNfcDev.activeDev->rfInterface == RFAL_NFC_INTERFACE_NFCDEP) ? gNfcDev.rxBuf.nfcDepBuf.pdu : gNfcDev.rxBuf.rfBuf)); - if(gNfcDev.disc.activate_after_sak) { - gNfcDev.state = RFAL_NFC_STATE_DATAEXCHANGE_DONE; - } - return ERR_NONE; - } - - /*******************************************************************************/ - switch(gNfcDev.activeDev - ->rfInterface) /* Check which RF interface shall be used/has been activated */ - { - /*******************************************************************************/ - case RFAL_NFC_INTERFACE_RF: - ctx.rxBuf = gNfcDev.rxBuf.rfBuf; - ctx.rxBufLen = 8 * sizeof(gNfcDev.rxBuf.rfBuf); - ctx.rxRcvdLen = &gNfcDev.rxLen; - ctx.txBuf = txData; - ctx.txBufLen = txDataLen; - ctx.flags = flags; - ctx.fwt = fwt; - *rxData = (uint8_t*)gNfcDev.rxBuf.rfBuf; - *rvdLen = (uint16_t*)&gNfcDev.rxLen; - err = rfalStartTransceive(&ctx); - break; - -#if RFAL_FEATURE_ISO_DEP - /*******************************************************************************/ - case RFAL_NFC_INTERFACE_ISODEP: { - rfalIsoDepApduTxRxParam isoDepTxRx; - uint16_t tx_bytes = txDataLen / 8; - - if(tx_bytes > sizeof(gNfcDev.txBuf.isoDepBuf.apdu)) { - return ERR_NOMEM; - } - - if(tx_bytes > 0U) { - ST_MEMCPY((uint8_t*)gNfcDev.txBuf.isoDepBuf.apdu, txData, tx_bytes); - } - - isoDepTxRx.DID = RFAL_ISODEP_NO_DID; - isoDepTxRx.ourFSx = RFAL_ISODEP_FSX_KEEP; - isoDepTxRx.FSx = gNfcDev.activeDev->proto.isoDep.info.FSx; - isoDepTxRx.dFWT = gNfcDev.activeDev->proto.isoDep.info.dFWT; - isoDepTxRx.FWT = gNfcDev.activeDev->proto.isoDep.info.FWT; - isoDepTxRx.txBuf = &gNfcDev.txBuf.isoDepBuf; - isoDepTxRx.txBufLen = tx_bytes; - isoDepTxRx.rxBuf = &gNfcDev.rxBuf.isoDepBuf; - isoDepTxRx.rxLen = &gNfcDev.rxLen; - isoDepTxRx.tmpBuf = &gNfcDev.tmpBuf.isoDepBuf; - *rxData = (uint8_t*)gNfcDev.rxBuf.isoDepBuf.apdu; - *rvdLen = (uint16_t*)&gNfcDev.rxLen; - - /*******************************************************************************/ - /* Trigger a RFAL ISO-DEP Transceive */ - err = rfalIsoDepStartApduTransceive(isoDepTxRx); - break; - } -#endif /* RFAL_FEATURE_ISO_DEP */ - -#if RFAL_FEATURE_NFC_DEP - /*******************************************************************************/ - case RFAL_NFC_INTERFACE_NFCDEP: { - rfalNfcDepPduTxRxParam nfcDepTxRx; - - if(txDataLen > sizeof(gNfcDev.txBuf.nfcDepBuf.pdu)) { - return ERR_NOMEM; - } - - if(txDataLen > 0U) { - ST_MEMCPY((uint8_t*)gNfcDev.txBuf.nfcDepBuf.pdu, txData, txDataLen); - } - - nfcDepTxRx.DID = RFAL_NFCDEP_DID_KEEP; - nfcDepTxRx.FSx = - rfalNfcIsRemDevListener(gNfcDev.activeDev->type) ? - rfalNfcDepLR2FS((uint8_t)rfalNfcDepPP2LR( - gNfcDev.activeDev->proto.nfcDep.activation.Target.ATR_RES.PPt)) : - rfalNfcDepLR2FS((uint8_t)rfalNfcDepPP2LR( - gNfcDev.activeDev->proto.nfcDep.activation.Initiator.ATR_REQ.PPi)); - nfcDepTxRx.dFWT = gNfcDev.activeDev->proto.nfcDep.info.dFWT; - nfcDepTxRx.FWT = gNfcDev.activeDev->proto.nfcDep.info.FWT; - nfcDepTxRx.txBuf = &gNfcDev.txBuf.nfcDepBuf; - nfcDepTxRx.txBufLen = txDataLen; - nfcDepTxRx.rxBuf = &gNfcDev.rxBuf.nfcDepBuf; - nfcDepTxRx.rxLen = &gNfcDev.rxLen; - nfcDepTxRx.tmpBuf = &gNfcDev.tmpBuf.nfcDepBuf; - *rxData = (uint8_t*)gNfcDev.rxBuf.nfcDepBuf.pdu; - *rvdLen = (uint16_t*)&gNfcDev.rxLen; - - /*******************************************************************************/ - /* Trigger a RFAL NFC-DEP Transceive */ - err = rfalNfcDepStartPduTransceive(nfcDepTxRx); - break; - } -#endif /* RFAL_FEATURE_NFC_DEP */ - - /*******************************************************************************/ - default: - err = ERR_PARAM; - break; - } - - /* If a transceive has successfuly started flag Data Exchange as ongoing */ - if(err == ERR_NONE) { - gNfcDev.dataExErr = ERR_BUSY; - gNfcDev.state = RFAL_NFC_STATE_DATAEXCHANGE; - } - - return err; - } - - return ERR_WRONG_STATE; -} - -/*******************************************************************************/ -ReturnCode rfalNfcDataExchangeGetStatus(void) { - /*******************************************************************************/ - /* Check if it's the first frame received in Listen mode */ - if(gNfcDev.state == RFAL_NFC_STATE_ACTIVATED) { - /* Continue data exchange as normal */ - gNfcDev.dataExErr = ERR_BUSY; - gNfcDev.state = RFAL_NFC_STATE_DATAEXCHANGE; - - /* Check if we performing in T3T CE */ - if((gNfcDev.activeDev->type == RFAL_NFC_POLL_TYPE_NFCF) && - (gNfcDev.activeDev->rfInterface == RFAL_NFC_INTERFACE_RF)) { - /* The first frame has been retrieved by rfalListenMode, flag data immediately */ - /* Can only call rfalGetTransceiveStatus() after starting a transceive with rfalStartTransceive */ - gNfcDev.dataExErr = ERR_NONE; - } - } - - /*******************************************************************************/ - /* Check if we are in we have been placed to sleep, and return last error */ - if(gNfcDev.state == RFAL_NFC_STATE_LISTEN_SLEEP) { - return gNfcDev.dataExErr; /* ERR_SLEEP_REQ */ - } - - /*******************************************************************************/ - /* Check if Data exchange has been started */ - if((gNfcDev.state != RFAL_NFC_STATE_DATAEXCHANGE) && - (gNfcDev.state != RFAL_NFC_STATE_DATAEXCHANGE_DONE)) { - return ERR_WRONG_STATE; - } - - /* Check if Data exchange is still ongoing */ - if(gNfcDev.dataExErr == ERR_BUSY) { - switch(gNfcDev.activeDev->rfInterface) { - /*******************************************************************************/ - case RFAL_NFC_INTERFACE_RF: - gNfcDev.dataExErr = rfalGetTransceiveStatus(); - break; - -#if RFAL_FEATURE_ISO_DEP - /*******************************************************************************/ - case RFAL_NFC_INTERFACE_ISODEP: - gNfcDev.dataExErr = rfalIsoDepGetApduTransceiveStatus(); - break; -#endif /* RFAL_FEATURE_ISO_DEP */ - - /*******************************************************************************/ -#if RFAL_FEATURE_NFC_DEP - case RFAL_NFC_INTERFACE_NFCDEP: - gNfcDev.dataExErr = rfalNfcDepGetPduTransceiveStatus(); - break; -#endif /* RFAL_FEATURE_NFC_DEP */ - - /*******************************************************************************/ - default: - gNfcDev.dataExErr = ERR_PARAM; - break; - } - -#if RFAL_FEATURE_LISTEN_MODE - /*******************************************************************************/ - /* If a Sleep request has been received (Listen Mode) go to sleep immediately */ - if(gNfcDev.dataExErr == ERR_SLEEP_REQ) { - EXIT_ON_ERR( - gNfcDev.dataExErr, - rfalListenSleepStart( - RFAL_LM_STATE_SLEEP_A, - gNfcDev.rxBuf.rfBuf, - sizeof(gNfcDev.rxBuf.rfBuf), - &gNfcDev.rxLen)); - - /* If set Sleep was successful keep restore the Sleep request signal */ - gNfcDev.dataExErr = ERR_SLEEP_REQ; - } -#endif /* RFAL_FEATURE_LISTEN_MODE */ - } - - return gNfcDev.dataExErr; -} - -/*! - ****************************************************************************** - * \brief Poller Technology Detection - * - * This method implements the Technology Detection / Poll for different - * device technologies. - * - * \return ERR_NONE : Operation completed with no error - * \return ERR_BUSY : Operation ongoing - * \return ERR_XXXX : Error occurred - * - ****************************************************************************** - */ -static ReturnCode rfalNfcPollTechDetetection(void) { - ReturnCode err; - - err = ERR_NONE; - - /* Suppress warning when specific RFAL features have been disabled */ - NO_WARNING(err); - - /*******************************************************************************/ - /* AP2P Technology Detection */ - /*******************************************************************************/ - if(((gNfcDev.disc.techs2Find & RFAL_NFC_POLL_TECH_AP2P) != 0U) && - ((gNfcDev.techs2do & RFAL_NFC_POLL_TECH_AP2P) != 0U)) { -#if RFAL_FEATURE_NFC_DEP - - if(!gNfcDev.isTechInit) { - EXIT_ON_ERR( - err, - rfalSetMode(RFAL_MODE_POLL_ACTIVE_P2P, gNfcDev.disc.ap2pBR, gNfcDev.disc.ap2pBR)); - rfalSetErrorHandling(RFAL_ERRORHANDLING_NFC); - rfalSetFDTListen(RFAL_FDT_LISTEN_AP2P_POLLER); - rfalSetFDTPoll(RFAL_TIMING_NONE); - rfalSetGT(RFAL_GT_AP2P_ADJUSTED); - EXIT_ON_ERR(err, rfalFieldOnAndStartGT()); /* Turns the Field On and starts GT timer */ - gNfcDev.isTechInit = true; - } - - if(rfalIsGTExpired()) /* Wait until Guard Time is fulfilled */ - { - gNfcDev.techs2do &= ~RFAL_NFC_POLL_TECH_AP2P; - - err = rfalNfcNfcDepActivate( - gNfcDev.devList, RFAL_NFCDEP_COMM_ACTIVE, NULL, 0); /* Poll for NFC-A devices */ - if(err == ERR_NONE) { - gNfcDev.techsFound |= RFAL_NFC_POLL_TECH_AP2P; - - gNfcDev.devList->type = RFAL_NFC_LISTEN_TYPE_AP2P; - gNfcDev.devList->rfInterface = RFAL_NFC_INTERFACE_NFCDEP; - gNfcDev.devCnt++; - - return ERR_NONE; - } - - gNfcDev.isTechInit = false; - rfalFieldOff(); - } - return ERR_BUSY; - -#endif /* RFAL_FEATURE_NFC_DEP */ - } - - /*******************************************************************************/ - /* Passive NFC-A Technology Detection */ - /*******************************************************************************/ - if(((gNfcDev.disc.techs2Find & RFAL_NFC_POLL_TECH_A) != 0U) && - ((gNfcDev.techs2do & RFAL_NFC_POLL_TECH_A) != 0U)) { -#if RFAL_FEATURE_NFCA - - rfalNfcaSensRes sensRes; - - if(!gNfcDev.isTechInit) { - EXIT_ON_ERR(err, rfalNfcaPollerInitialize()); /* Initialize RFAL for NFC-A */ - EXIT_ON_ERR(err, rfalFieldOnAndStartGT()); /* Turns the Field On and starts GT timer */ - gNfcDev.isTechInit = true; - } - - if(rfalIsGTExpired()) /* Wait until Guard Time is fulfilled */ - { - err = rfalNfcaPollerTechnologyDetection( - gNfcDev.disc.compMode, &sensRes); /* Poll for NFC-A devices */ - if(err == ERR_NONE) { - gNfcDev.techsFound |= RFAL_NFC_POLL_TECH_A; - } - - gNfcDev.isTechInit = false; - gNfcDev.techs2do &= ~RFAL_NFC_POLL_TECH_A; - } - - return ERR_BUSY; - -#endif /* RFAL_FEATURE_NFCA */ - } - - /*******************************************************************************/ - /* Passive NFC-B Technology Detection */ - /*******************************************************************************/ - if(((gNfcDev.disc.techs2Find & RFAL_NFC_POLL_TECH_B) != 0U) && - ((gNfcDev.techs2do & RFAL_NFC_POLL_TECH_B) != 0U)) { -#if RFAL_FEATURE_NFCB - - rfalNfcbSensbRes sensbRes; - uint8_t sensbResLen; - - if(!gNfcDev.isTechInit) { - EXIT_ON_ERR(err, rfalNfcbPollerInitialize()); /* Initialize RFAL for NFC-B */ - EXIT_ON_ERR( - err, rfalFieldOnAndStartGT()); /* As field is already On only starts GT timer */ - gNfcDev.isTechInit = true; - } - - if(rfalIsGTExpired()) /* Wait until Guard Time is fulfilled */ - { - err = rfalNfcbPollerTechnologyDetection( - gNfcDev.disc.compMode, &sensbRes, &sensbResLen); /* Poll for NFC-B devices */ - if(err == ERR_NONE) { - gNfcDev.techsFound |= RFAL_NFC_POLL_TECH_B; - } - - gNfcDev.isTechInit = false; - gNfcDev.techs2do &= ~RFAL_NFC_POLL_TECH_B; - } - - return ERR_BUSY; - -#endif /* RFAL_FEATURE_NFCB */ - } - - /*******************************************************************************/ - /* Passive NFC-F Technology Detection */ - /*******************************************************************************/ - if(((gNfcDev.disc.techs2Find & RFAL_NFC_POLL_TECH_F) != 0U) && - ((gNfcDev.techs2do & RFAL_NFC_POLL_TECH_F) != 0U)) { -#if RFAL_FEATURE_NFCF - - if(!gNfcDev.isTechInit) { - EXIT_ON_ERR( - err, - rfalNfcfPollerInitialize(gNfcDev.disc.nfcfBR)); /* Initialize RFAL for NFC-F */ - EXIT_ON_ERR( - err, rfalFieldOnAndStartGT()); /* As field is already On only starts GT timer */ - gNfcDev.isTechInit = true; - } - - if(rfalIsGTExpired()) /* Wait until Guard Time is fulfilled */ - { - err = rfalNfcfPollerCheckPresence(); /* Poll for NFC-F devices */ - if(err == ERR_NONE) { - gNfcDev.techsFound |= RFAL_NFC_POLL_TECH_F; - } - - gNfcDev.isTechInit = false; - gNfcDev.techs2do &= ~RFAL_NFC_POLL_TECH_F; - } - - return ERR_BUSY; - -#endif /* RFAL_FEATURE_NFCF */ - } - - /*******************************************************************************/ - /* Passive NFC-V Technology Detection */ - /*******************************************************************************/ - if(((gNfcDev.disc.techs2Find & RFAL_NFC_POLL_TECH_V) != 0U) && - ((gNfcDev.techs2do & RFAL_NFC_POLL_TECH_V) != 0U)) { -#if RFAL_FEATURE_NFCV - - rfalNfcvInventoryRes invRes; - - if(!gNfcDev.isTechInit) { - EXIT_ON_ERR(err, rfalNfcvPollerInitialize()); /* Initialize RFAL for NFC-V */ - EXIT_ON_ERR( - err, rfalFieldOnAndStartGT()); /* As field is already On only starts GT timer */ - gNfcDev.isTechInit = true; - } - - if(rfalIsGTExpired()) /* Wait until Guard Time is fulfilled */ - { - err = rfalNfcvPollerCheckPresence(&invRes); /* Poll for NFC-V devices */ - if(err == ERR_NONE) { - gNfcDev.techsFound |= RFAL_NFC_POLL_TECH_V; - } - - gNfcDev.isTechInit = false; - gNfcDev.techs2do &= ~RFAL_NFC_POLL_TECH_V; - } - - return ERR_BUSY; - -#endif /* RFAL_FEATURE_NFCV */ - } - - /*******************************************************************************/ - /* Passive Proprietary Technology ST25TB */ - /*******************************************************************************/ - if(((gNfcDev.disc.techs2Find & RFAL_NFC_POLL_TECH_ST25TB) != 0U) && - ((gNfcDev.techs2do & RFAL_NFC_POLL_TECH_ST25TB) != 0U)) { -#if RFAL_FEATURE_ST25TB - - if(!gNfcDev.isTechInit) { - EXIT_ON_ERR(err, rfalSt25tbPollerInitialize()); /* Initialize RFAL for NFC-V */ - EXIT_ON_ERR( - err, rfalFieldOnAndStartGT()); /* As field is already On only starts GT timer */ - gNfcDev.isTechInit = true; - } - - if(rfalIsGTExpired()) /* Wait until Guard Time is fulfilled */ - { - err = rfalSt25tbPollerCheckPresence(NULL); /* Poll for ST25TB devices */ - if(err == ERR_NONE) { - gNfcDev.techsFound |= RFAL_NFC_POLL_TECH_ST25TB; - } - - gNfcDev.isTechInit = false; - gNfcDev.techs2do &= ~RFAL_NFC_POLL_TECH_ST25TB; - } - - return ERR_BUSY; - -#endif /* RFAL_FEATURE_ST25TB */ - } - - return ERR_NONE; -} - -/*! - ****************************************************************************** - * \brief Poller Collision Resolution - * - * This method implements the Collision Resolution on all technologies that - * have been detected before. - * - * \return ERR_NONE : Operation completed with no error - * \return ERR_BUSY : Operation ongoing - * \return ERR_XXXX : Error occurred - * - ****************************************************************************** - */ -static ReturnCode rfalNfcPollCollResolution(void) { - uint8_t i; - static uint8_t devCnt; - ReturnCode err; - - err = ERR_NONE; - i = 0; - - /* Suppress warning when specific RFAL features have been disabled */ - NO_WARNING(err); - NO_WARNING(devCnt); - NO_WARNING(i); - - /* Check if device limit has been reached */ - if(gNfcDev.devCnt >= gNfcDev.disc.devLimit) { - return ERR_NONE; - } - - /*******************************************************************************/ - /* NFC-A Collision Resolution */ - /*******************************************************************************/ -#if RFAL_FEATURE_NFCA - if(((gNfcDev.techsFound & RFAL_NFC_POLL_TECH_A) != 0U) && - ((gNfcDev.techs2do & RFAL_NFC_POLL_TECH_A) != - 0U)) /* If a NFC-A device was found/detected, perform Collision Resolution */ - { - static rfalNfcaListenDevice nfcaDevList[RFAL_NFC_MAX_DEVICES]; - - if(!gNfcDev.isTechInit) { - EXIT_ON_ERR(err, rfalNfcaPollerInitialize()); /* Initialize RFAL for NFC-A */ - EXIT_ON_ERR(err, rfalFieldOnAndStartGT()); /* Turns the Field On and starts GT timer */ - - gNfcDev.isTechInit = true; /* Technology has been initialized */ - gNfcDev.isOperOngoing = false; /* No operation currently ongoing */ - } - - if(!rfalIsGTExpired()) { - return ERR_BUSY; - } - - if(!gNfcDev.isOperOngoing) { - EXIT_ON_ERR( - err, - rfalNfcaPollerStartFullCollisionResolution( - gNfcDev.disc.compMode, - (gNfcDev.disc.devLimit - gNfcDev.devCnt), - nfcaDevList, - &devCnt)); - - gNfcDev.isOperOngoing = true; - return ERR_BUSY; - } - - err = rfalNfcaPollerGetFullCollisionResolutionStatus(); - if(err != ERR_BUSY) { - gNfcDev.isTechInit = false; - gNfcDev.techs2do &= ~RFAL_NFC_POLL_TECH_A; - - if((err == ERR_NONE) && (devCnt != 0U)) { - for(i = 0; i < devCnt; - i++) /* Copy devices found form local Nfca list into global device list */ - { - gNfcDev.devList[gNfcDev.devCnt].type = RFAL_NFC_LISTEN_TYPE_NFCA; - gNfcDev.devList[gNfcDev.devCnt].dev.nfca = nfcaDevList[i]; - gNfcDev.devCnt++; - } - } - } - - return ERR_BUSY; - } -#endif /* RFAL_FEATURE_NFCA */ - - /*******************************************************************************/ - /* NFC-B Collision Resolution */ - /*******************************************************************************/ -#if RFAL_FEATURE_NFCB - if(((gNfcDev.techsFound & RFAL_NFC_POLL_TECH_B) != 0U) && - ((gNfcDev.techs2do & RFAL_NFC_POLL_TECH_B) != - 0U)) /* If a NFC-B device was found/detected, perform Collision Resolution */ - { - rfalNfcbListenDevice nfcbDevList[RFAL_NFC_MAX_DEVICES]; - - if(!gNfcDev.isTechInit) { - EXIT_ON_ERR(err, rfalNfcbPollerInitialize()); /* Initialize RFAL for NFC-B */ - EXIT_ON_ERR( - err, - rfalFieldOnAndStartGT()); /* Ensure GT again as other technologies have also been polled */ - gNfcDev.isTechInit = true; - } - - if(!rfalIsGTExpired()) { - return ERR_BUSY; - } - - devCnt = 0; - gNfcDev.isTechInit = false; - gNfcDev.techs2do &= ~RFAL_NFC_POLL_TECH_B; - - err = rfalNfcbPollerCollisionResolution( - gNfcDev.disc.compMode, (gNfcDev.disc.devLimit - gNfcDev.devCnt), nfcbDevList, &devCnt); - if((err == ERR_NONE) && (devCnt != 0U)) { - for(i = 0; i < devCnt; - i++) /* Copy devices found form local Nfcb list into global device list */ - { - gNfcDev.devList[gNfcDev.devCnt].type = RFAL_NFC_LISTEN_TYPE_NFCB; - gNfcDev.devList[gNfcDev.devCnt].dev.nfcb = nfcbDevList[i]; - gNfcDev.devCnt++; - } - } - - return ERR_BUSY; - } -#endif /* RFAL_FEATURE_NFCB*/ - - /*******************************************************************************/ - /* NFC-F Collision Resolution */ - /*******************************************************************************/ -#if RFAL_FEATURE_NFCF - if(((gNfcDev.techsFound & RFAL_NFC_POLL_TECH_F) != 0U) && - ((gNfcDev.techs2do & RFAL_NFC_POLL_TECH_F) != - 0U)) /* If a NFC-F device was found/detected, perform Collision Resolution */ - { - rfalNfcfListenDevice nfcfDevList[RFAL_NFC_MAX_DEVICES]; - - if(!gNfcDev.isTechInit) { - EXIT_ON_ERR( - err, - rfalNfcfPollerInitialize(gNfcDev.disc.nfcfBR)); /* Initialize RFAL for NFC-F */ - EXIT_ON_ERR( - err, - rfalFieldOnAndStartGT()); /* Ensure GT again as other technologies have also been polled */ - gNfcDev.isTechInit = true; - } - - if(!rfalIsGTExpired()) { - return ERR_BUSY; - } - - devCnt = 0; - gNfcDev.isTechInit = false; - gNfcDev.techs2do &= ~RFAL_NFC_POLL_TECH_F; - - err = rfalNfcfPollerCollisionResolution( - gNfcDev.disc.compMode, (gNfcDev.disc.devLimit - gNfcDev.devCnt), nfcfDevList, &devCnt); - if((err == ERR_NONE) && (devCnt != 0U)) { - for(i = 0; i < devCnt; - i++) /* Copy devices found form local Nfcf list into global device list */ - { - gNfcDev.devList[gNfcDev.devCnt].type = RFAL_NFC_LISTEN_TYPE_NFCF; - gNfcDev.devList[gNfcDev.devCnt].dev.nfcf = nfcfDevList[i]; - gNfcDev.devCnt++; - } - } - - return ERR_BUSY; - } -#endif /* RFAL_FEATURE_NFCF */ - - /*******************************************************************************/ - /* NFC-V Collision Resolution */ - /*******************************************************************************/ -#if RFAL_FEATURE_NFCV - if(((gNfcDev.techsFound & RFAL_NFC_POLL_TECH_V) != 0U) && - ((gNfcDev.techs2do & RFAL_NFC_POLL_TECH_V) != - 0U)) /* If a NFC-V device was found/detected, perform Collision Resolution */ - { - rfalNfcvListenDevice nfcvDevList[RFAL_NFC_MAX_DEVICES]; - - if(!gNfcDev.isTechInit) { - EXIT_ON_ERR(err, rfalNfcvPollerInitialize()); /* Initialize RFAL for NFC-V */ - EXIT_ON_ERR( - err, - rfalFieldOnAndStartGT()); /* Ensure GT again as other technologies have also been polled */ - gNfcDev.isTechInit = true; - } - - if(!rfalIsGTExpired()) { - return ERR_BUSY; - } - - devCnt = 0; - gNfcDev.isTechInit = false; - gNfcDev.techs2do &= ~RFAL_NFC_POLL_TECH_V; - - err = rfalNfcvPollerCollisionResolution( - RFAL_COMPLIANCE_MODE_NFC, - (gNfcDev.disc.devLimit - gNfcDev.devCnt), - nfcvDevList, - &devCnt); - if((err == ERR_NONE) && (devCnt != 0U)) { - for(i = 0; i < devCnt; - i++) /* Copy devices found form local Nfcf list into global device list */ - { - gNfcDev.devList[gNfcDev.devCnt].type = RFAL_NFC_LISTEN_TYPE_NFCV; - gNfcDev.devList[gNfcDev.devCnt].dev.nfcv = nfcvDevList[i]; - gNfcDev.devCnt++; - } - } - - return ERR_BUSY; - } -#endif /* RFAL_FEATURE_NFCV */ - - /*******************************************************************************/ - /* ST25TB Collision Resolution */ - /*******************************************************************************/ -#if RFAL_FEATURE_ST25TB - if(((gNfcDev.techsFound & RFAL_NFC_POLL_TECH_ST25TB) != 0U) && - ((gNfcDev.techs2do & RFAL_NFC_POLL_TECH_ST25TB) != - 0U)) /* If a ST25TB device was found/detected, perform Collision Resolution */ - { - rfalSt25tbListenDevice st25tbDevList[RFAL_NFC_MAX_DEVICES]; - - if(!gNfcDev.isTechInit) { - EXIT_ON_ERR(err, rfalSt25tbPollerInitialize()); /* Initialize RFAL for ST25TB */ - EXIT_ON_ERR( - err, - rfalFieldOnAndStartGT()); /* Ensure GT again as other technologies have also been polled */ - gNfcDev.isTechInit = true; - } - - if(!rfalIsGTExpired()) { - return ERR_BUSY; - } - - devCnt = 0; - gNfcDev.isTechInit = false; - gNfcDev.techs2do &= ~RFAL_NFC_POLL_TECH_ST25TB; - - err = rfalSt25tbPollerCollisionResolution( - (gNfcDev.disc.devLimit - gNfcDev.devCnt), st25tbDevList, &devCnt); - if((err == ERR_NONE) && (devCnt != 0U)) { - for(i = 0; i < devCnt; - i++) /* Copy devices found form local Nfcf list into global device list */ - { - gNfcDev.devList[gNfcDev.devCnt].type = RFAL_NFC_LISTEN_TYPE_ST25TB; - gNfcDev.devList[gNfcDev.devCnt].dev.st25tb = st25tbDevList[i]; - gNfcDev.devCnt++; - } - } - - return ERR_BUSY; - } -#endif /* RFAL_FEATURE_ST25TB */ - - return ERR_NONE; /* All technologies have been performed */ -} - -/*! - ****************************************************************************** - * \brief Poller Activation - * - * This method Activates a given device according to it's type and - * protocols supported - * - * \param[in] devIt : device's position on the list to be activated - * - * \return ERR_NONE : Operation completed with no error - * \return ERR_BUSY : Operation ongoing - * \return ERR_XXXX : Error occurred - * - ****************************************************************************** - */ -static ReturnCode rfalNfcPollActivation(uint8_t devIt) { - ReturnCode err; - - err = ERR_NONE; - - /* Suppress warning when specific RFAL features have been disabled */ - NO_WARNING(err); - - if(devIt > gNfcDev.devCnt) { - return ERR_WRONG_STATE; - } - - switch(gNfcDev.devList[devIt].type) { - /*******************************************************************************/ - /* AP2P Activation */ - /*******************************************************************************/ -#if RFAL_FEATURE_NFC_DEP - case RFAL_NFC_LISTEN_TYPE_AP2P: - /* Activation has already been performed (ATR_REQ) */ - - gNfcDev.devList[devIt].nfcid = - gNfcDev.devList[devIt].proto.nfcDep.activation.Target.ATR_RES.NFCID3; - gNfcDev.devList[devIt].nfcidLen = RFAL_NFCDEP_NFCID3_LEN; - break; -#endif /* RFAL_FEATURE_NFC_DEP */ - - /*******************************************************************************/ - /* Passive NFC-A Activation */ - /*******************************************************************************/ -#if RFAL_FEATURE_NFCA - case RFAL_NFC_LISTEN_TYPE_NFCA: - - if(!gNfcDev.isTechInit) { - rfalNfcaPollerInitialize(); - gNfcDev.isTechInit = true; - gNfcDev.isOperOngoing = false; - return ERR_BUSY; - } - - if(gNfcDev.devList[devIt].dev.nfca.isSleep) /* Check if desired device is in Sleep */ - { - rfalNfcaSensRes sensRes; - rfalNfcaSelRes selRes; - - if(!gNfcDev.isOperOngoing) { - /* Wake up all cards */ - EXIT_ON_ERR( - err, rfalNfcaPollerCheckPresence(RFAL_14443A_SHORTFRAME_CMD_WUPA, &sensRes)); - gNfcDev.isOperOngoing = true; - } else { - /* Select specific device */ - EXIT_ON_ERR( - err, - rfalNfcaPollerSelect( - gNfcDev.devList[devIt].dev.nfca.nfcId1, - gNfcDev.devList[devIt].dev.nfca.nfcId1Len, - &selRes)); - gNfcDev.devList[devIt].dev.nfca.isSleep = false; - gNfcDev.isOperOngoing = false; - } - return ERR_BUSY; - } - - /* Set NFCID */ - gNfcDev.devList[devIt].nfcid = gNfcDev.devList[devIt].dev.nfca.nfcId1; - gNfcDev.devList[devIt].nfcidLen = gNfcDev.devList[devIt].dev.nfca.nfcId1Len; - - /*******************************************************************************/ - /* Perform protocol specific activation */ - switch(gNfcDev.devList[devIt].dev.nfca.type) { - /*******************************************************************************/ - case RFAL_NFCA_T1T: - - /* No further activation needed for T1T (RID already performed) */ - - gNfcDev.devList[devIt].nfcid = gNfcDev.devList[devIt].dev.nfca.ridRes.uid; - gNfcDev.devList[devIt].nfcidLen = RFAL_T1T_UID_LEN; - - gNfcDev.devList[devIt].rfInterface = RFAL_NFC_INTERFACE_RF; - break; - - case RFAL_NFCA_T2T: - - /* No further activation needed for a T2T */ - - gNfcDev.devList[devIt].rfInterface = RFAL_NFC_INTERFACE_RF; - break; - - /*******************************************************************************/ - case RFAL_NFCA_T4T: /* Device supports ISO-DEP */ - -#if RFAL_FEATURE_ISO_DEP && RFAL_FEATURE_ISO_DEP_POLL - if(!gNfcDev.isOperOngoing) { - /* Perform ISO-DEP (ISO14443-4) activation: RATS and PPS if supported */ - rfalIsoDepInitialize(); - EXIT_ON_ERR( - err, - rfalIsoDepPollAStartActivation( - (rfalIsoDepFSxI)RFAL_ISODEP_FSDI_DEFAULT, - RFAL_ISODEP_NO_DID, - gNfcDev.disc.maxBR, - &gNfcDev.devList[devIt].proto.isoDep)); - - gNfcDev.isOperOngoing = true; - return ERR_BUSY; - } - - err = rfalIsoDepPollAGetActivationStatus(); - if(err != ERR_NONE) { - return err; - } - - gNfcDev.devList[devIt].rfInterface = - RFAL_NFC_INTERFACE_ISODEP; /* NFC-A T4T device activated */ -#else - gNfcDev.devList[devIt].rfInterface = - RFAL_NFC_INTERFACE_RF; /* No ISO-DEP supported activate using RF interface */ -#endif /* RFAL_FEATURE_ISO_DEP_POLL */ - break; - - /*******************************************************************************/ - case RFAL_NFCA_T4T_NFCDEP: /* Device supports both T4T and NFC-DEP */ - case RFAL_NFCA_NFCDEP: /* Device supports NFC-DEP */ - -#if RFAL_FEATURE_NFC_DEP - /* Perform NFC-DEP (P2P) activation: ATR and PSL if supported */ - EXIT_ON_ERR( - err, - rfalNfcNfcDepActivate(&gNfcDev.devList[devIt], RFAL_NFCDEP_COMM_PASSIVE, NULL, 0)); - - gNfcDev.devList[devIt].nfcid = - gNfcDev.devList[devIt].proto.nfcDep.activation.Target.ATR_RES.NFCID3; - gNfcDev.devList[devIt].nfcidLen = RFAL_NFCDEP_NFCID3_LEN; - - gNfcDev.devList[devIt].rfInterface = - RFAL_NFC_INTERFACE_NFCDEP; /* NFC-A P2P device activated */ -#else - gNfcDev.devList[devIt].rfInterface = - RFAL_NFC_INTERFACE_RF; /* No NFC-DEP supported activate using RF interface */ -#endif /* RFAL_FEATURE_NFC_DEP */ - break; - - /*******************************************************************************/ - default: - return ERR_WRONG_STATE; - } - break; -#endif /* RFAL_FEATURE_NFCA */ - - /*******************************************************************************/ - /* Passive NFC-B Activation */ - /*******************************************************************************/ -#if RFAL_FEATURE_NFCB - case RFAL_NFC_LISTEN_TYPE_NFCB: - - if(!gNfcDev.isTechInit) { - rfalNfcbPollerInitialize(); - gNfcDev.isTechInit = true; - gNfcDev.isOperOngoing = false; - return ERR_BUSY; - } - - if(gNfcDev.devList[devIt].dev.nfcb.isSleep) /* Check if desired device is in Sleep */ - { - rfalNfcbSensbRes sensbRes; - uint8_t sensbResLen; - - /* Wake up all cards. SENSB_RES may return collision but the NFCID0 is available to explicitly select NFC-B card via ATTRIB; so error will be ignored here */ - rfalNfcbPollerCheckPresence( - RFAL_NFCB_SENS_CMD_ALLB_REQ, RFAL_NFCB_SLOT_NUM_1, &sensbRes, &sensbResLen); - } - - /* Set NFCID */ - gNfcDev.devList[devIt].nfcid = gNfcDev.devList[devIt].dev.nfcb.sensbRes.nfcid0; - gNfcDev.devList[devIt].nfcidLen = RFAL_NFCB_NFCID0_LEN; - -#if RFAL_FEATURE_ISO_DEP && RFAL_FEATURE_ISO_DEP_POLL - /* Check if device supports ISO-DEP (ISO14443-4) */ - if((gNfcDev.devList[devIt].dev.nfcb.sensbRes.protInfo.FsciProType & - RFAL_NFCB_SENSB_RES_PROTO_ISO_MASK) != 0U) { - if(!gNfcDev.isOperOngoing) { - rfalIsoDepInitialize(); - /* Perform ISO-DEP (ISO14443-4) activation: ATTRIB */ - EXIT_ON_ERR( - err, - rfalIsoDepPollBStartActivation( - (rfalIsoDepFSxI)RFAL_ISODEP_FSDI_DEFAULT, - RFAL_ISODEP_NO_DID, - gNfcDev.disc.maxBR, - 0x00, - &gNfcDev.devList[devIt].dev.nfcb, - NULL, - 0, - &gNfcDev.devList[devIt].proto.isoDep)); - - gNfcDev.isOperOngoing = true; - return ERR_BUSY; - } - - err = rfalIsoDepPollBGetActivationStatus(); - if(err != ERR_NONE) { - return err; - } - - gNfcDev.devList[devIt].rfInterface = - RFAL_NFC_INTERFACE_ISODEP; /* NFC-B T4T device activated */ - break; - } - -#endif /* RFAL_FEATURE_ISO_DEP_POLL */ - - gNfcDev.devList[devIt].rfInterface = - RFAL_NFC_INTERFACE_RF; /* NFC-B device activated */ - break; - -#endif /* RFAL_FEATURE_NFCB */ - - /*******************************************************************************/ - /* Passive NFC-F Activation */ - /*******************************************************************************/ -#if RFAL_FEATURE_NFCF - case RFAL_NFC_LISTEN_TYPE_NFCF: - - rfalNfcfPollerInitialize(gNfcDev.disc.nfcfBR); - -#if RFAL_FEATURE_NFC_DEP - if(rfalNfcfIsNfcDepSupported(&gNfcDev.devList[devIt].dev.nfcf)) { - /* Perform NFC-DEP (P2P) activation: ATR and PSL if supported */ - EXIT_ON_ERR( - err, - rfalNfcNfcDepActivate(&gNfcDev.devList[devIt], RFAL_NFCDEP_COMM_PASSIVE, NULL, 0)); - - /* Set NFCID */ - gNfcDev.devList[devIt].nfcid = - gNfcDev.devList[devIt].proto.nfcDep.activation.Target.ATR_RES.NFCID3; - gNfcDev.devList[devIt].nfcidLen = RFAL_NFCDEP_NFCID3_LEN; - - gNfcDev.devList[devIt].rfInterface = - RFAL_NFC_INTERFACE_NFCDEP; /* NFC-F P2P device activated */ - break; - } -#endif /* RFAL_FEATURE_NFC_DEP */ - - /* Set NFCID */ - gNfcDev.devList[devIt].nfcid = gNfcDev.devList[devIt].dev.nfcf.sensfRes.NFCID2; - gNfcDev.devList[devIt].nfcidLen = RFAL_NFCF_NFCID2_LEN; - - gNfcDev.devList[devIt].rfInterface = - RFAL_NFC_INTERFACE_RF; /* NFC-F T3T device activated */ - break; -#endif /* RFAL_FEATURE_NFCF */ - - /*******************************************************************************/ - /* Passive NFC-V Activation */ - /*******************************************************************************/ -#if RFAL_FEATURE_NFCV - case RFAL_NFC_LISTEN_TYPE_NFCV: - - rfalNfcvPollerInitialize(); - - /* No specific activation needed for a T5T */ - - /* Set NFCID */ - gNfcDev.devList[devIt].nfcid = gNfcDev.devList[devIt].dev.nfcv.InvRes.UID; - gNfcDev.devList[devIt].nfcidLen = RFAL_NFCV_UID_LEN; - - gNfcDev.devList[devIt].rfInterface = - RFAL_NFC_INTERFACE_RF; /* NFC-V T5T device activated */ - break; -#endif /* RFAL_FEATURE_NFCV */ - - /*******************************************************************************/ - /* Passive ST25TB Activation */ - /*******************************************************************************/ -#if RFAL_FEATURE_ST25TB - case RFAL_NFC_LISTEN_TYPE_ST25TB: - - rfalSt25tbPollerInitialize(); - - /* No specific activation needed for a ST25TB */ - - /* Set NFCID */ - gNfcDev.devList[devIt].nfcid = gNfcDev.devList[devIt].dev.st25tb.UID; - gNfcDev.devList[devIt].nfcidLen = RFAL_ST25TB_UID_LEN; - - gNfcDev.devList[devIt].rfInterface = RFAL_NFC_INTERFACE_RF; /* ST25TB device activated */ - break; -#endif /* RFAL_FEATURE_ST25TB */ - - /*******************************************************************************/ - default: - return ERR_WRONG_STATE; - } - - gNfcDev.activeDev = &gNfcDev.devList[devIt]; /* Assign active device to be used further on */ - return ERR_NONE; -} - -/*! - ****************************************************************************** - * \brief Listener Activation - * - * This method handles the listen mode Activation according to the different - * protocols the Reader/Initiator performs - * - * \return ERR_NONE : Operation completed with no error - * \return ERR_BUSY : Operation ongoing - * \return ERR_PROTO : Unexpected frame received - * \return ERR_XXXX : Error occurred - * - ****************************************************************************** - */ -#if RFAL_FEATURE_LISTEN_MODE -static ReturnCode rfalNfcListenActivation(void) { - bool isDataRcvd; - ReturnCode ret; - rfalLmState lmSt; - rfalBitRate bitRate; -#if RFAL_FEATURE_NFC_DEP - uint8_t hdrLen; - - /* Set the header length in NFC-A */ - hdrLen = (RFAL_NFCDEP_SB_LEN + RFAL_NFCDEP_LEN_LEN); -#endif /* RFAL_FEATURE_NFC_DEP */ - - lmSt = rfalListenGetState(&isDataRcvd, &bitRate); - - switch(lmSt) { -#if RFAL_FEATURE_NFCA - /*******************************************************************************/ - case RFAL_LM_STATE_ACTIVE_A: /* NFC-A CE activation */ - case RFAL_LM_STATE_ACTIVE_Ax: - - if(isDataRcvd) /* Check if Reader/Initator has sent some data */ - { - /* Check if received data is a Sleep request */ - if(rfalNfcaListenerIsSleepReq( - gNfcDev.rxBuf.rfBuf, - rfalConvBitsToBytes(gNfcDev.rxLen))) /* Check if received data is a SLP_REQ */ - { - /* Set the Listen Mode in Sleep state */ - EXIT_ON_ERR( - ret, - rfalListenSleepStart( - RFAL_LM_STATE_SLEEP_A, - gNfcDev.rxBuf.rfBuf, - sizeof(gNfcDev.rxBuf.rfBuf), - &gNfcDev.rxLen)); - } - - else if(gNfcDev.disc.activate_after_sak) { - gNfcDev.devList->type = RFAL_NFC_POLL_TYPE_NFCA; - rfalListenSetState(RFAL_LM_STATE_ACTIVE_A); - return ERR_NONE; - } -#if RFAL_FEATURE_ISO_DEP && RFAL_FEATURE_ISO_DEP_LISTEN - /* Check if received data is a valid RATS */ - else if(rfalIsoDepIsRats( - gNfcDev.rxBuf.rfBuf, (uint8_t)rfalConvBitsToBytes(gNfcDev.rxLen))) { - rfalIsoDepAtsParam atsParam; - rfalIsoDepListenActvParam rxParam; - - /* Set ATS parameters */ - atsParam.fsci = (uint8_t)RFAL_ISODEP_DEFAULT_FSCI; - atsParam.fwi = RFAL_ISODEP_DEFAULT_FWI; - atsParam.sfgi = RFAL_ISODEP_DEFAULT_SFGI; - atsParam.didSupport = false; - atsParam.ta = RFAL_ISODEP_ATS_TA_SAME_D; - atsParam.hb = NULL; - atsParam.hbLen = 0; - - /* Set Rx parameters */ - rxParam.rxBuf = - (rfalIsoDepBufFormat*)&gNfcDev.rxBuf - .isoDepBuf; /* PRQA S 0310 # MISRA 11.3 - Intentional safe cast to avoiding large buffer duplication */ - rxParam.rxLen = &gNfcDev.rxLen; - rxParam.isoDepDev = &gNfcDev.devList->proto.isoDep; - rxParam.isRxChaining = &gNfcDev.isRxChaining; - - rfalListenSetState(RFAL_LM_STATE_CARDEMU_4A); /* Set next state CE T4T */ - rfalIsoDepInitialize(); /* Initialize ISO-DEP layer to handle ISO14443-a activation / RATS */ - - /* Set ISO-DEP layer to digest RATS and handle activation */ - EXIT_ON_ERR( - ret, - rfalIsoDepListenStartActivation( - &atsParam, NULL, gNfcDev.rxBuf.rfBuf, gNfcDev.rxLen, rxParam)); - } -#endif /* RFAL_FEATURE_ISO_DEP_LISTEN */ - -#if RFAL_FEATURE_NFC_DEP - - /* Check if received data is a valid ATR_REQ */ - else if(rfalNfcDepIsAtrReq( - &gNfcDev.rxBuf.rfBuf[hdrLen], - (rfalConvBitsToBytes(gNfcDev.rxLen) - hdrLen), - gNfcDev.devList->nfcid)) { - gNfcDev.devList->type = RFAL_NFC_POLL_TYPE_NFCA; - EXIT_ON_ERR( - ret, - rfalNfcNfcDepActivate( - gNfcDev.devList, - RFAL_NFCDEP_COMM_PASSIVE, - &gNfcDev.rxBuf.rfBuf[hdrLen], - (rfalConvBitsToBytes(gNfcDev.rxLen) - hdrLen))); - } -#endif /* RFAL_FEATURE_NFC_DEP */ - - else { - return ERR_PROTO; - } - } - return ERR_BUSY; - -#endif /* RFAL_FEATURE_NFCA */ - -#if RFAL_FEATURE_ISO_DEP && RFAL_FEATURE_ISO_DEP_LISTEN - /*******************************************************************************/ - case RFAL_LM_STATE_CARDEMU_4A: /* T4T ISO-DEP activation */ - - ret = rfalIsoDepListenGetActivationStatus(); - if(ret == ERR_NONE) { - gNfcDev.devList->type = RFAL_NFC_POLL_TYPE_NFCA; - gNfcDev.devList->rfInterface = RFAL_NFC_INTERFACE_ISODEP; - gNfcDev.devList->nfcid = NULL; - gNfcDev.devList->nfcidLen = 0; - } - return ret; -#endif /* RFAL_FEATURE_ISO_DEP_LISTEN */ - - /*******************************************************************************/ - case RFAL_LM_STATE_READY_F: /* NFC-F CE activation */ - - if(isDataRcvd) /* Wait for the first received data */ - { -#if RFAL_FEATURE_NFC_DEP - /* Set the header length in NFC-F */ - hdrLen = RFAL_NFCDEP_LEN_LEN; - - if(rfalNfcDepIsAtrReq( - &gNfcDev.rxBuf.rfBuf[hdrLen], - (rfalConvBitsToBytes(gNfcDev.rxLen) - hdrLen), - gNfcDev.devList->nfcid)) { - gNfcDev.devList->type = RFAL_NFC_POLL_TYPE_NFCF; - EXIT_ON_ERR( - ret, - rfalNfcNfcDepActivate( - gNfcDev.devList, - RFAL_NFCDEP_COMM_PASSIVE, - &gNfcDev.rxBuf.rfBuf[hdrLen], - (rfalConvBitsToBytes(gNfcDev.rxLen) - hdrLen))); - } else -#endif /* RFAL_FEATURE_NFC_DEP */ - { - rfalListenSetState( - RFAL_LM_STATE_CARDEMU_3); /* First data already received - set T3T CE */ - } - } - return ERR_BUSY; - - /*******************************************************************************/ - case RFAL_LM_STATE_CARDEMU_3: /* T3T activated */ - - gNfcDev.devList->type = RFAL_NFC_POLL_TYPE_NFCF; - gNfcDev.devList->rfInterface = RFAL_NFC_INTERFACE_RF; - gNfcDev.devList->nfcid = NULL; - gNfcDev.devList->nfcidLen = 0; - - return ERR_NONE; - -#if RFAL_FEATURE_NFC_DEP - /*******************************************************************************/ - case RFAL_LM_STATE_TARGET_A: /* NFC-DEP activation */ - case RFAL_LM_STATE_TARGET_F: - - ret = rfalNfcDepListenGetActivationStatus(); - if(ret == ERR_NONE) { - gNfcDev.devList->rfInterface = RFAL_NFC_INTERFACE_NFCDEP; - gNfcDev.devList->nfcidLen = RFAL_NFCDEP_NFCID3_LEN; - } - return ret; -#endif /* RFAL_FEATURE_NFC_DEP */ - - /*******************************************************************************/ - case RFAL_LM_STATE_IDLE: /* AP2P activation */ - if(isDataRcvd) /* Check if Reader/Initator has sent some data */ - { - if((gNfcDev.lmMask & RFAL_LM_MASK_ACTIVE_P2P) != 0U) /* Check if AP2P is enabled */ - { -#if RFAL_FEATURE_NFC_DEP - /* Calculate the header length in NFC-A or NFC-F mode*/ - hdrLen = - ((bitRate == RFAL_BR_106) ? (RFAL_NFCDEP_SB_LEN + RFAL_NFCDEP_LEN_LEN) : - RFAL_NFCDEP_LEN_LEN); - - if(rfalNfcDepIsAtrReq( - &gNfcDev.rxBuf.rfBuf[hdrLen], - (rfalConvBitsToBytes(gNfcDev.rxLen) - hdrLen), - NULL)) { - gNfcDev.devList->type = RFAL_NFC_POLL_TYPE_AP2P; - rfalSetMode((RFAL_MODE_LISTEN_ACTIVE_P2P), bitRate, bitRate); - EXIT_ON_ERR( - ret, - rfalNfcNfcDepActivate( - gNfcDev.devList, - RFAL_NFCDEP_COMM_ACTIVE, - &gNfcDev.rxBuf.rfBuf[hdrLen], - (rfalConvBitsToBytes(gNfcDev.rxLen) - hdrLen))); - } else -#endif /* RFAL_FEATURE_NFC_DEP */ - { - return ERR_PROTO; - } - } - } - return ERR_BUSY; - - /*******************************************************************************/ - case RFAL_LM_STATE_READY_A: - case RFAL_LM_STATE_READY_Ax: - case RFAL_LM_STATE_SLEEP_A: - case RFAL_LM_STATE_SLEEP_AF: - return ERR_BUSY; - - /*******************************************************************************/ - case RFAL_LM_STATE_POWER_OFF: - return ERR_LINK_LOSS; - - default: /* Wait for activation */ - break; - } - - return ERR_INTERNAL; -} -#endif /* RFAL_FEATURE_LISTEN_MODE */ - -/*! - ****************************************************************************** - * \brief Poller NFC DEP Activate - * - * This method performs NFC-DEP Activation - * - * \param[in] device : device info - * \param[in] commMode : communication mode (Passive/Active) - * \param[in] atrReq : received ATR_REQ - * \param[in] atrReqLen : received ATR_REQ size - * - * \return ERR_NONE : Operation completed with no error - * \return ERR_BUSY : Operation ongoing - * \return ERR_XXXX : Error occurred - * - ****************************************************************************** - */ -#if RFAL_FEATURE_NFC_DEP -static ReturnCode rfalNfcNfcDepActivate( - rfalNfcDevice* device, - rfalNfcDepCommMode commMode, - const uint8_t* atrReq, - uint16_t atrReqLen) { - rfalNfcDepAtrParam initParam; - - /* Suppress warnings if Listen mode is disabled */ - NO_WARNING(atrReq); - NO_WARNING(atrReqLen); - - /* If we are in Poll mode */ - if(rfalNfcIsRemDevListener(device->type)) { - /*******************************************************************************/ - /* If Passive F use the NFCID2 retrieved from SENSF */ - if(device->type == RFAL_NFC_LISTEN_TYPE_NFCF) { - initParam.nfcid = device->dev.nfcf.sensfRes.NFCID2; - initParam.nfcidLen = RFAL_NFCF_NFCID2_LEN; - } else { - initParam.nfcid = gNfcDev.disc.nfcid3; - initParam.nfcidLen = RFAL_NFCDEP_NFCID3_LEN; - } - - initParam.BS = RFAL_NFCDEP_Bx_NO_HIGH_BR; - initParam.BR = RFAL_NFCDEP_Bx_NO_HIGH_BR; - initParam.DID = RFAL_NFCDEP_DID_NO; - initParam.NAD = RFAL_NFCDEP_NAD_NO; - initParam.LR = RFAL_NFCDEP_LR_254; - initParam.GB = gNfcDev.disc.GB; - initParam.GBLen = gNfcDev.disc.GBLen; - initParam.commMode = commMode; - initParam.operParam = - (RFAL_NFCDEP_OPER_FULL_MI_EN | RFAL_NFCDEP_OPER_EMPTY_DEP_DIS | - RFAL_NFCDEP_OPER_ATN_EN | RFAL_NFCDEP_OPER_RTOX_REQ_EN); - - rfalNfcDepInitialize(); - /* Perform NFC-DEP (P2P) activation: ATR and PSL if supported */ - return rfalNfcDepInitiatorHandleActivation( - &initParam, gNfcDev.disc.maxBR, &device->proto.nfcDep); - } - /* If we are in Listen mode */ -#if RFAL_FEATURE_LISTEN_MODE - else if(rfalNfcIsRemDevPoller(device->type)) { - rfalNfcDepListenActvParam actvParams; - rfalNfcDepTargetParam targetParam; - - ST_MEMCPY(targetParam.nfcid3, (uint8_t*)gNfcDev.disc.nfcid3, RFAL_NFCDEP_NFCID3_LEN); - targetParam.bst = RFAL_NFCDEP_Bx_NO_HIGH_BR; - targetParam.brt = RFAL_NFCDEP_Bx_NO_HIGH_BR; - targetParam.to = RFAL_NFCDEP_WT_TRG_MAX_L13; /* [LLCP] 1.3 6.2.1 */ - targetParam.ppt = rfalNfcDepLR2PP(RFAL_NFCDEP_LR_254); - if(gNfcDev.disc.GBLen >= RFAL_NFCDEP_GB_MAX_LEN) { - return ERR_PARAM; - } - targetParam.GBtLen = gNfcDev.disc.GBLen; - if(gNfcDev.disc.GBLen > 0U) { - ST_MEMCPY(targetParam.GBt, gNfcDev.disc.GB, gNfcDev.disc.GBLen); - } - targetParam.operParam = - (RFAL_NFCDEP_OPER_FULL_MI_EN | RFAL_NFCDEP_OPER_EMPTY_DEP_DIS | - RFAL_NFCDEP_OPER_ATN_EN | RFAL_NFCDEP_OPER_RTOX_REQ_EN); - targetParam.commMode = commMode; - - /* Set activation buffer (including header) for NFC-DEP */ - actvParams.rxBuf = - (rfalNfcDepBufFormat*)&gNfcDev.rxBuf - .nfcDepBuf; /* PRQA S 0310 # MISRA 11.3 - Intentional safe cast to avoiding large buffer duplication */ - actvParams.rxLen = &gNfcDev.rxLen; - actvParams.isRxChaining = &gNfcDev.isRxChaining; - actvParams.nfcDepDev = &gNfcDev.devList->proto.nfcDep; - - rfalListenSetState( - ((device->type == RFAL_NFC_POLL_TYPE_NFCA) ? RFAL_LM_STATE_TARGET_A : - RFAL_LM_STATE_TARGET_F)); - - rfalNfcDepInitialize(); - /* Perform NFC-DEP (P2P) activation: send ATR_RES and handle activation */ - return rfalNfcDepListenStartActivation(&targetParam, atrReq, atrReqLen, actvParams); - } -#endif /* RFAL_FEATURE_LISTEN_MODE */ - - else { - return ERR_INTERNAL; - } -} -#endif /* RFAL_FEATURE_NFC_DEP */ - -/*! - ****************************************************************************** - * \brief Poller NFC Deactivate - * - * This method Deactivates the device if a deactivation procedure exists - * - * \return ERR_NONE : Operation completed with no error - * \return ERR_BUSY : Operation ongoing - * \return ERR_XXXX : Error occurred - * - ****************************************************************************** - */ -static ReturnCode rfalNfcDeactivation(void) { - /* Check if a device has been activated */ - if(gNfcDev.activeDev != NULL) { - if(rfalNfcIsRemDevListener( - gNfcDev.activeDev->type)) /* Listen mode no additional deactivation to be performed*/ - { -#ifndef RFAL_NFC_SKIP_DEACT - switch(gNfcDev.activeDev->rfInterface) { - /*******************************************************************************/ - case RFAL_NFC_INTERFACE_RF: - break; /* No specific deactivation to be performed */ - - /*******************************************************************************/ -#if RFAL_FEATURE_ISO_DEP && RFAL_FEATURE_ISO_DEP_POLL - case RFAL_NFC_INTERFACE_ISODEP: - rfalIsoDepDeselect(); /* Send a Deselect to device */ - break; -#endif /* RFAL_FEATURE_ISO_DEP_POLL */ - - /*******************************************************************************/ -#if RFAL_FEATURE_NFC_DEP - case RFAL_NFC_INTERFACE_NFCDEP: - switch(gNfcDev.activeDev->type) { - case RFAL_NFC_LISTEN_TYPE_AP2P: - rfalNfcDepRLS(); /* Send a Release to device */ - break; - default: - rfalNfcDepDSL(); /* Send a Deselect to device */ - break; - } - break; -#endif /* RFAL_FEATURE_NFC_DEP */ - - default: - return ERR_REQUEST; - } -#endif /* RFAL_NFC_SKIP_DEACT */ - } - } - -#if RFAL_FEATURE_WAKEUP_MODE - rfalWakeUpModeStop(); -#endif /* RFAL_FEATURE_WAKEUP_MODE */ - -#if RFAL_FEATURE_LISTEN_MODE - rfalListenStop(); -#else - rfalFieldOff(); -#endif - - gNfcDev.activeDev = NULL; - return ERR_NONE; -} diff --git a/lib/ST25RFAL002/source/rfal_nfcDep.c b/lib/ST25RFAL002/source/rfal_nfcDep.c deleted file mode 100644 index ad851ac5d2a..00000000000 --- a/lib/ST25RFAL002/source/rfal_nfcDep.c +++ /dev/null @@ -1,2730 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

    © COPYRIGHT 2020 STMicroelectronics

    - * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: NFCC firmware - * LANGUAGE: ISO C99 - */ - -/*! \file rfal_nfcDep.c - * - * \author Gustavo Patricio - * - * \brief Implementation of NFC-DEP protocol - * - * NFC-DEP is also known as NFCIP - Near Field Communication - * Interface and Protocol - * - * This implementation was based on the following specs: - * - NFC Forum Digital 1.1 - * - ECMA 340 3rd Edition 2013 - * - */ - -/* - ****************************************************************************** - * INCLUDES - ****************************************************************************** - */ -#include "rfal_nfcDep.h" -#include "rfal_nfcf.h" -#include "utils.h" - -/* - ****************************************************************************** - * ENABLE SWITCH - ****************************************************************************** - */ - -#if RFAL_FEATURE_NFC_DEP - -/* Check for valid Block/Payload length Digital 2.0 Table 90*/ -#if((RFAL_FEATURE_NFC_DEP_BLOCK_MAX_LEN != 64) && (RFAL_FEATURE_NFC_DEP_BLOCK_MAX_LEN != 128) && \ - (RFAL_FEATURE_NFC_DEP_BLOCK_MAX_LEN != 192) && (RFAL_FEATURE_NFC_DEP_BLOCK_MAX_LEN != 254)) -#error \ - " RFAL: Invalid NFC-DEP Block Max length. Please change RFAL_FEATURE_NFC_DEP_BLOCK_MAX_LEN. " -#endif - -/* Check for valid PDU length */ -#if((RFAL_FEATURE_NFC_DEP_PDU_MAX_LEN < RFAL_FEATURE_NFC_DEP_BLOCK_MAX_LEN)) -#error " RFAL: Invalid NFC-DEP PDU Max length. Please change RFAL_FEATURE_NFC_DEP_PDU_MAX_LEN. " -#endif - -/* - ****************************************************************************** - * DEFINES - ****************************************************************************** - */ -#define NFCIP_ATR_RETRY_MAX 2U /*!< Max consecutive retrys of an ATR REQ with transm error*/ - -#define NFCIP_PSLPAY_LEN (2U) /*!< PSL Payload length (BRS + FSL) */ -#define NFCIP_PSLREQ_LEN \ - (3U + RFAL_NFCDEP_LEN_LEN) /*!< PSL REQ length (incl LEN) */ -#define NFCIP_PSLRES_LEN \ - (3U + RFAL_NFCDEP_LEN_LEN) /*!< PSL RES length (incl LEN) */ - -#define NFCIP_ATRREQ_BUF_LEN \ - (RFAL_NFCDEP_ATRREQ_MAX_LEN + RFAL_NFCDEP_LEN_LEN) /*!< ATR REQ max length (incl LEN) */ -#define NFCIP_ATRRES_BUF_LEN \ - (RFAL_NFCDEP_ATRRES_MAX_LEN + RFAL_NFCDEP_LEN_LEN) /*!< ATR RES max length (incl LEN) */ - -#define NFCIP_RLSREQ_LEN \ - (3U + RFAL_NFCDEP_LEN_LEN) /*!< RLS REQ length (incl LEN) */ -#define NFCIP_RLSRES_LEN \ - (3U + RFAL_NFCDEP_LEN_LEN) /*!< RSL RES length (incl LEN) */ -#define NFCIP_RLSRES_MIN \ - (2U + RFAL_NFCDEP_LEN_LEN) /*!< Minimum length for a RLS RES (incl LEN) */ - -#define NFCIP_DSLREQ_LEN \ - (3U + RFAL_NFCDEP_LEN_LEN) /*!< DSL REQ length (incl LEN) */ -#define NFCIP_DSLRES_LEN \ - (3U + RFAL_NFCDEP_LEN_LEN) /*!< DSL RES length (incl LEN) */ -#define NFCIP_DSLRES_MIN \ - (2U + RFAL_NFCDEP_LEN_LEN) /*!< Minimum length for a DSL RES (incl LEN) */ - -#define NFCIP_DSLRES_MAX_LEN \ - (3U + RFAL_NFCDEP_LEN_LEN) /*!< Maximum length for a DSL RES (incl LEN) */ -#define NFCIP_RLSRES_MAX_LEN \ - (3U + RFAL_NFCDEP_LEN_LEN) /*!< Minimum length for a RLS RES (incl LEN) */ -#define NFCIP_TARGET_RES_MAX \ - (MAX(NFCIP_RLSRES_MAX_LEN, NFCIP_DSLRES_MAX_LEN)) /*!< Max target control res length */ - -#define NFCIP_NO_FWT RFAL_FWT_NONE /*!< No FWT value - Target Mode */ -#define NFCIP_INIT_MIN_RTOX 1U /*!< Minimum RTOX value Digital 1.0 14.8.4.1 */ -#define NFCIP_INIT_MAX_RTOX 59U /*!< Maximum RTOX value Digital 1.0 14.8.4.1 */ - -#define NFCIP_TARG_MIN_RTOX 1U /*!< Minimum target RTOX value Digital 1.0 14.8.4.1 */ -#define NFCIP_TARG_MAX_RTOX 59U /*!< Maximum target RTOX value Digital 1.0 14.8.4.1 */ - -#define NFCIP_TRECOV 1280U /*!< Digital 1.0 A.10 Trecov */ - -#define NFCIP_TIMEOUT_ADJUSTMENT \ - 3072U /*!< Timeout Adjustment to compensate timing from end of Tx to end of frame */ -#define NFCIP_RWT_ACTIVATION \ - (0x1000001U + \ - NFCIP_TIMEOUT_ADJUSTMENT) /*!< Digital 2.2 B.11 RWT ACTIVATION 2^24 + RWT Delta + Adjustment*/ -#define NFCIP_RWT_ACM_ACTIVATION \ - (0x200001U + \ - NFCIP_TIMEOUT_ADJUSTMENT) /*!< Digital 2.2 B.11 RWT ACTIVATION 2^21 + RWT Delta + Adjustment*/ - -#define RFAL_NFCDEP_HEADER_PAD \ - (RFAL_NFCDEP_DEPREQ_HEADER_LEN - \ - RFAL_NFCDEP_LEN_MIN) /*!< Difference between expected rcvd header len and max foreseen */ - -#ifndef RFAL_NFCDEP_MAX_TX_RETRYS -#define RFAL_NFCDEP_MAX_TX_RETRYS \ - (uint8_t)3U /*!< Number of retransmit retyrs */ -#endif /* RFAL_NFCDEP_MAX_TX_RETRYS */ - -#ifndef RFAL_NFCDEP_TO_RETRYS -#define RFAL_NFCDEP_TO_RETRYS \ - (uint8_t)3U /*!< Number of retrys for Timeout */ -#endif /* RFAL_NFCDEP_TO_RETRYS */ - -#ifndef RFAL_NFCDEP_MAX_RTOX_RETRYS -#define RFAL_NFCDEP_MAX_RTOX_RETRYS \ - (uint8_t)10U /*!< Number of retrys for RTOX Digital 2.0 17.12.4.3 */ -#endif /* RFAL_NFCDEP_MAX_RTOX_RETRYS */ - -#ifndef RFAL_NFCDEP_MAX_NACK_RETRYS -#define RFAL_NFCDEP_MAX_NACK_RETRYS \ - (uint8_t)3U /*!< Number of retrys for NACK */ -#endif /* RFAL_NFCDEP_MAX_NACK_RETRYS */ - -#ifndef RFAL_NFCDEP_MAX_ATN_RETRYS -#define RFAL_NFCDEP_MAX_ATN_RETRYS \ - (uint8_t)3U /*!< Number of retrys for ATN */ -#endif /* RFAL_NFCDEP_MAX_ATN_RETRYS */ - -#define NFCIP_MIN_TXERROR_LEN \ - 4U /*!< Minimum frame length with error to be ignored Digital 1.0 14.12.5.4 */ - -#define NFCIP_REQ (uint8_t)0xD4U /*!= NFCIP_ST_INIT_IDLE) && \ - ((st) <= \ - NFCIP_ST_INIT_RLS)) /*!< Checks if module is set as Initiator */ -#define nfcipIsTarget(st) \ - (!nfcipIsInitiator( \ - st)) /*!< Checks if module is set as Target */ - -#define nfcipIsBRAllowed(br, mBR) \ - (((1U << (br)) & (mBR)) != \ - 0U) /*!< Checks bit rate is allowed by given mask */ - -#define nfcipIsEmptyDEPEnabled(op) \ - (!nfcipIsEmptyDEPDisabled( \ - op)) /*!< Checks if empty payload is allowed by operation config NCI 1.0 Table 81 */ -#define nfcipIsEmptyDEPDisabled(op) \ - (((op)&RFAL_NFCDEP_OPER_EMPTY_DEP_DIS) != \ - 0U) /*!< Checks if empty payload is not allowed by operation config NCI 1.0 Table 81 */ - -#define nfcipIsRTOXReqEnabled(op) \ - (!nfcipIsRTOXReqDisabled( \ - op)) /*!< Checks if send a RTOX_REQ is allowed by operation config NCI 1.0 Table 81 */ -#define nfcipIsRTOXReqDisabled(op) \ - (((op)&RFAL_NFCDEP_OPER_RTOX_REQ_DIS) != \ - 0U) /*!< Checks if send a RTOX_REQ is not allowed by operation config NCI 1.0 Table 81 */ - -/*! Checks if isDeactivating callback is set and calls it, otherwise returns false */ -#define nfcipIsDeactivationPending() \ - ((gNfcip.isDeactivating == NULL) ? false : gNfcip.isDeactivating()) - -/*! Returns the RWT Activation according to the current communication mode */ -#define nfcipRWTActivation() \ - ((gNfcip.cfg.commMode == RFAL_NFCDEP_COMM_ACTIVE) ? NFCIP_RWT_ACM_ACTIVATION : \ - NFCIP_RWT_ACTIVATION) - -#define nfcipRTOXAdjust(v) \ - ((v) - ((v) >> 3)) /*!< Adjust RTOX timer value to a percentage of the total, current 88% */ - -/*******************************************************************************/ - -// timerPollTimeoutValue is necessary after timerCalculateTimeout so that system will wake up upon timer timeout. -#define nfcipTimerStart(timer, time_ms) \ - do { \ - platformTimerDestroy(timer); \ - (timer) = platformTimerCreate((uint16_t)(time_ms)); \ - } while(0) /*!< Configures and starts the RTOX timer */ -#define nfcipTimerisExpired(timer) \ - platformTimerIsExpired(timer) /*!< Checks RTOX timer has expired */ -#define nfcipTimerDestroy(timer) \ - platformTimerDestroy(timer) /*!< Destroys RTOX timer */ - -#define nfcipLogE(...) /*!< Macro for the error log method */ -#define nfcipLogW(...) /*!< Macro for the warning log method */ -#define nfcipLogI(...) /*!< Macro for the info log method */ -#define nfcipLogD(...) /*!< Macro for the debug log method */ - -/*! Digital 1.1 - 16.12.5.2 The Target SHALL NOT attempt any error recovery and remains in Rx mode upon Transmission or a Protocol Error */ -#define nfcDepReEnableRx(rxB, rxBL, rxL) \ - rfalTransceiveBlockingTx( \ - NULL, \ - 0, \ - (rxB), \ - (rxBL), \ - (rxL), \ - (RFAL_TXRX_FLAGS_DEFAULT | (uint32_t)RFAL_TXRX_FLAGS_NFCIP1_ON), \ - RFAL_FWT_NONE) - -/* - ****************************************************************************** - * LOCAL DATA TYPES - ****************************************************************************** - */ - -/*! Struct that holds all DEP parameters/configs for the following communications */ -typedef struct { - uint8_t did; /*!< Device ID (DID) to be used */ - - uint8_t* txBuf; /*!< Pointer to the Tx buffer to be sent */ - uint16_t txBufLen; /*!< Length of the data in the txBuf */ - uint8_t txBufPaylPos; /*!< Position inside txBuf where data starts */ - bool txChaining; /*!< Flag indicating chaining on transmission */ - - uint8_t* rxBuf; /*!< Pointer to the Rx buffer for incoming data */ - uint16_t rxBufLen; /*!< Length of the data in the rxBuf */ - uint8_t rxBufPaylPos; /*!< Position inside rxBuf where data is to be placed*/ - - uint32_t fwt; /*!< Frame Waiting Time (FWT) to be used */ - uint32_t dFwt; /*!< Delta Frame Waiting Time (dFWT) to be used */ - uint16_t fsc; /*!< Frame Size (FSC) to be used */ - -} rfalNfcDepDEPParams; - -/*! NFCIP module states */ -typedef enum { - NFCIP_ST_IDLE, - NFCIP_ST_INIT_IDLE, - NFCIP_ST_INIT_ATR, - NFCIP_ST_INIT_PSL, - NFCIP_ST_INIT_DEP_IDLE, - NFCIP_ST_INIT_DEP_TX, - NFCIP_ST_INIT_DEP_RX, - NFCIP_ST_INIT_DEP_ATN, - NFCIP_ST_INIT_DSL, - NFCIP_ST_INIT_RLS, - - NFCIP_ST_TARG_WAIT_ATR, - NFCIP_ST_TARG_WAIT_ACTV, - NFCIP_ST_TARG_DEP_IDLE, - NFCIP_ST_TARG_DEP_RX, - NFCIP_ST_TARG_DEP_RTOX, - NFCIP_ST_TARG_DEP_TX, - NFCIP_ST_TARG_DEP_SLEEP -} rfalNfcDepState; - -/*! NFCIP commands (Request, Response) */ -typedef enum { - NFCIP_CMD_ATR_REQ = 0x00, - NFCIP_CMD_ATR_RES = 0x01, - NFCIP_CMD_WUP_REQ = 0x02, - NFCIP_CMD_WUP_RES = 0x03, - NFCIP_CMD_PSL_REQ = 0x04, - NFCIP_CMD_PSL_RES = 0x05, - NFCIP_CMD_DEP_REQ = 0x06, - NFCIP_CMD_DEP_RES = 0x07, - NFCIP_CMD_DSL_REQ = 0x08, - NFCIP_CMD_DSL_RES = 0x09, - NFCIP_CMD_RLS_REQ = 0x0A, - NFCIP_CMD_RLS_RES = 0x0B -} rfalNfcDepCmd; - -/*! Struct that holds all NFCIP data */ -typedef struct { - rfalNfcDepConfigs cfg; /*!< Holds the current configuration to be used */ - - rfalNfcDepState state; /*!< Current state of the NFCIP module */ - uint8_t pni; /*!< Packet Number Information (PNI) counter */ - - uint8_t lastCmd; /*!< Last command sent */ - uint8_t lastPFB; /*!< Last PFB sent */ - uint8_t lastPFBnATN; /*!< Last PFB sent (excluding ATN) */ - uint8_t lastRTOX; /*!< Last RTOX value sent */ - - uint8_t cntTxRetrys; /*!< Retransmissions counter */ - uint8_t cntTORetrys; /*!< Timeouts counter */ - uint8_t cntRTOXRetrys; /*!< RTOX counter */ - uint8_t cntNACKRetrys; /*!< NACK counter */ - uint8_t cntATNRetrys; /*!< Attention (ATN) counter */ - - uint16_t fsc; /*!< Current Frame Size (FSC) to be used */ - bool isTxChaining; /*!< Flag for chaining on Transmission */ - bool isRxChaining; /*!< Flag for chaining on Reception */ - uint8_t* txBuf; /*!< Pointer to the Tx buffer to be sent */ - uint8_t* rxBuf; /*!< Pointer to the Rx buffer for incoming data */ - uint16_t txBufLen; /*!< Length of the data in the txBuf */ - uint16_t rxBufLen; /*!< Length of rxBuf buffer */ - uint16_t* rxRcvdLen; /*!< Length of the data in the rxBuf */ - uint8_t txBufPaylPos; /*!< Position in txBuf where data starts */ - uint8_t rxBufPaylPos; /*!< Position in rxBuf where data is to be placed */ - bool* isChaining; /*!< Flag for chaining on Reception */ - - rfalNfcDepDevice* nfcDepDev; /*!< Pointer to NFC-DEP device info */ - - uint32_t RTOXTimer; /*!< Timer used for RTOX */ - rfalNfcDepDeactCallback isDeactivating; /*!< Deactivating flag check callback */ - - bool isReqPending; /*!< Flag pending REQ from Target activation */ - bool isTxPending; /*!< Flag pending DEP Block while waiting RTOX Ack */ - bool isWait4RTOX; /*!< Flag for waiting RTOX Ack */ - - rfalNfcDepPduTxRxParam PDUParam; /*!< PDU TxRx params */ - uint16_t PDUTxPos; /*!< PDU Tx position */ - uint16_t PDURxPos; /*!< PDU Rx position */ - bool isPDURxChaining; /*!< PDU Transceive chaining flag */ -} rfalNfcDep; - -/* - ****************************************************************************** - * LOCAL VARIABLES - ****************************************************************************** - */ - -static rfalNfcDep gNfcip; /*!< NFCIP module instance */ - -/* - ****************************************************************************** - * LOCAL FUNCTION PROTOTYPES - ****************************************************************************** - */ - -static ReturnCode nfcipTxRx( - rfalNfcDepCmd cmd, - uint8_t* txBuf, - uint32_t fwt, - uint8_t* paylBuf, - uint8_t paylBufLen, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rxActLen); -static ReturnCode nfcipTx( - rfalNfcDepCmd cmd, - uint8_t* txBuf, - uint8_t* paylBuf, - uint16_t paylLen, - uint8_t pfbData, - uint32_t fwt); -static ReturnCode nfcipDEPControlMsg(uint8_t pfb, uint8_t RTOX); -static ReturnCode nfcipInitiatorHandleDEP( - ReturnCode rxRes, - uint16_t rxLen, - uint16_t* outActRxLen, - bool* outIsChaining); -static ReturnCode - nfcipTargetHandleRX(ReturnCode rxRes, uint16_t* outActRxLen, bool* outIsChaining); -static ReturnCode nfcipTargetHandleActivation(rfalNfcDepDevice* nfcDepDev, uint8_t* outBRS); - -/*! - ****************************************************************************** - * \brief NFCIP Configure - * - * Configures the nfcip layer with the given configurations - * - * \param[in] cfg : nfcip configuration for following communication - ****************************************************************************** - */ -static void nfcipConfig(const rfalNfcDepConfigs* cfg); - -/*! - ****************************************************************************** - * \brief Set DEP parameters - * - * This method sets the parameters/configs for following Data Exchange - * Sets the nfcip module state according to the role it is configured - * - * - * \warning To be used only after proper Initiator/Target activation: - * nfcipTargetHandleActivation() or nfcipInitiatorActivate() has - * returned success - * - * This must be called before nfcipRun() in case of Target to pass - * rxBuffer - * - * Everytime some data needs to be transmitted call this to set it and - * call nfcipRun() until done or error - * - * \param[in] DEPParams : the parameters to be used during Data Exchange - ****************************************************************************** - */ -static void nfcipSetDEPParams(const rfalNfcDepDEPParams* DEPParams); - -/*! - ****************************************************************************** - * \brief NFCIP run protocol - * - * This method handles all the nfcip protocol during Data Exchange (DEP - * requests and responses). - * - * A data exchange cycle is considered a DEP REQ and a DEP RES. - * - * In case of Tx chaining(MI) must signal it with nfcipSetDEPParams() - * In case of Rx chaining(MI) outIsChaining will be set to true and the - * current data returned - * - * \param[out] outActRxLen : data received length - * \param[out] outIsChaining : true if other peer is performing chaining(MI) - * - * \return ERR_NONE : Data exchange cycle completed successfully - * \return ERR_TIMEOUT : Timeout occurred - * \return ERR_PROTO : Protocol error occurred - * \return ERR_AGAIN : Other peer is doing chaining(MI), current block - * was received successfully call again until complete - * - ****************************************************************************** - */ -static ReturnCode nfcipRun(uint16_t* outActRxLen, bool* outIsChaining); - -/*! - ****************************************************************************** - * \brief Transmission method - * - * This method checks if the current communication is Active or Passive - * and performs the necessary procedures for each communication type - * - * Transmits the data hold in txBuf - * - * \param[in] txBuf : buffer to transmit - * \param[in] txBufLen : txBuffer capacity - * \param[in] fwt : fwt for current Tx - * - * \return ERR_NONE : No error - ****************************************************************************** - */ -static ReturnCode nfcipDataTx(uint8_t* txBuf, uint16_t txBufLen, uint32_t fwt); - -/*! - ****************************************************************************** - * \brief Reception method - * - * This method checks if the current communication is Active or Passive - * and calls the appropriate reception method - * - * Copies incoming data to rxBuf - * - * \param[in] blocking : reception is to be done blocking or non-blocking - * - * \return ERR_BUSY : Busy - * \return ERR_NONE : No error - ****************************************************************************** - */ -static ReturnCode nfcipDataRx(bool blocking); - -/* - ****************************************************************************** - * LOCAL FUNCTIONS - ****************************************************************************** - */ - -/*******************************************************************************/ - -/*******************************************************************************/ -static bool nfcipDxIsSupported(uint8_t Dx, uint8_t BRx, uint8_t BSx) { - uint8_t Bx; - - /* Take the min of the possible bit rates, we'll use one for both directions */ - Bx = MIN(BRx, BSx); - - /* Lower bit rates must be supported for P2P */ - if((Dx <= (uint8_t)RFAL_NFCDEP_Dx_04_424)) { - return true; - } - - if((Dx == (uint8_t)RFAL_NFCDEP_Dx_08_848) && (Bx >= (uint8_t)RFAL_NFCDEP_Bx_08_848)) { - return true; - } - - return false; -} - -/*******************************************************************************/ -static ReturnCode nfcipTxRx( - rfalNfcDepCmd cmd, - uint8_t* txBuf, - uint32_t fwt, - uint8_t* paylBuf, - uint8_t paylBufLen, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rxActLen) { - ReturnCode ret; - - if((cmd == NFCIP_CMD_DEP_REQ) || - (cmd == NFCIP_CMD_DEP_RES)) /* this method cannot be used for DEPs */ - { - return ERR_PARAM; - } - - /* Assign the global params for this TxRx */ - gNfcip.rxBuf = rxBuf; - gNfcip.rxBufLen = rxBufLen; - gNfcip.rxRcvdLen = rxActLen; - - /*******************************************************************************/ - /* Transmission */ - /*******************************************************************************/ - if(txBuf != NULL) /* if nothing to Tx, just do Rx */ - { - EXIT_ON_ERR(ret, nfcipTx(cmd, txBuf, paylBuf, paylBufLen, 0, fwt)); - } - - /*******************************************************************************/ - /* Reception */ - /*******************************************************************************/ - ret = nfcipDataRx(true); - if(ret != ERR_NONE) { - return ret; - } - - /*******************************************************************************/ - *rxActLen = *rxBuf; /* Use LEN byte instead due to with/without CRC modes */ - return ERR_NONE; /* Tx and Rx completed successfully */ -} - -/*******************************************************************************/ -static ReturnCode nfcipDEPControlMsg(uint8_t pfb, uint8_t RTOX) { - uint8_t ctrlMsg[20]; - rfalNfcDepCmd depCmd; - uint32_t fwt; - - /*******************************************************************************/ - /* Calculate Cmd and fwt to be used */ - /*******************************************************************************/ - depCmd = - ((gNfcip.cfg.role == RFAL_NFCDEP_ROLE_TARGET) ? NFCIP_CMD_DEP_RES : NFCIP_CMD_DEP_REQ); - fwt = - ((gNfcip.cfg.role == RFAL_NFCDEP_ROLE_TARGET) ? - NFCIP_NO_FWT : - (nfcip_PFBisSTO(pfb) ? ((RTOX * gNfcip.cfg.fwt) + gNfcip.cfg.dFwt) : - (gNfcip.cfg.fwt + gNfcip.cfg.dFwt))); - - if(nfcip_PFBisSTO(pfb)) { - ctrlMsg[RFAL_NFCDEP_DEPREQ_HEADER_LEN] = RTOX; - return nfcipTx( - depCmd, ctrlMsg, &ctrlMsg[RFAL_NFCDEP_DEPREQ_HEADER_LEN], sizeof(uint8_t), pfb, fwt); - } else { - return nfcipTx(depCmd, ctrlMsg, NULL, 0, pfb, fwt); - } -} - -/*******************************************************************************/ -static void nfcipClearCounters(void) { - gNfcip.cntATNRetrys = 0; - gNfcip.cntNACKRetrys = 0; - gNfcip.cntTORetrys = 0; - gNfcip.cntTxRetrys = 0; - gNfcip.cntRTOXRetrys = 0; -} - -/*******************************************************************************/ -static ReturnCode nfcipInitiatorHandleDEP( - ReturnCode rxRes, - uint16_t rxLen, - uint16_t* outActRxLen, - bool* outIsChaining) { - ReturnCode ret; - uint8_t nfcDepLen; - uint8_t rxMsgIt; - uint8_t rxPFB; - uint8_t rxRTOX; - uint8_t optHdrLen; - - ret = ERR_INTERNAL; - rxMsgIt = 0; - optHdrLen = 0; - - *outActRxLen = 0; - *outIsChaining = false; - - /*******************************************************************************/ - /* Handle reception errors */ - /*******************************************************************************/ - switch(rxRes) { - /*******************************************************************************/ - /* Timeout -> Digital 1.0 14.15.5.6 */ - case ERR_TIMEOUT: - - nfcipLogI(" NFCIP(I) TIMEOUT TORetrys:%d \r\n", gNfcip.cntTORetrys); - - /* Digital 1.0 14.15.5.6 - If nTO >= Max raise protocol error */ - if(gNfcip.cntTORetrys++ >= RFAL_NFCDEP_TO_RETRYS) { - return ERR_PROTO; - } - - /*******************************************************************************/ - /* Upon Timeout error, if Deactivation is pending, no more error recovery - * will be done #54. - * This is used to address the issue some devices that havea big TO. - * Normally LLCP layer has timeout already, and NFCIP layer is still - * running error handling, retrying ATN/NACKs */ - /*******************************************************************************/ - if(nfcipIsDeactivationPending()) { - nfcipLogI(" skipping error recovery due deactivation pending \r\n"); - return ERR_TIMEOUT; - } - - /* Digital 1.0 14.15.5.6 1) If last PDU was NACK */ - if(nfcip_PFBisRNACK(gNfcip.lastPFB)) { - /* Digital 1.0 14.15.5.6 2) if NACKs failed raise protocol error */ - if(gNfcip.cntNACKRetrys++ >= RFAL_NFCDEP_MAX_NACK_RETRYS) { - return ERR_PROTO; - } - - /* Send NACK */ - nfcipLogI(" NFCIP(I) Sending NACK retry: %d \r\n", gNfcip.cntNACKRetrys); - EXIT_ON_ERR(ret, nfcipDEPControlMsg(nfcip_PFBRPDU_NACK(gNfcip.pni), 0)); - return ERR_BUSY; - } - - nfcipLogI(" NFCIP(I) Checking if to send ATN ATNRetrys: %d \r\n", gNfcip.cntATNRetrys); - - /* Digital 1.0 14.15.5.6 3) Otherwise send ATN */ - if(gNfcip.cntATNRetrys++ >= RFAL_NFCDEP_MAX_NACK_RETRYS) { - return ERR_PROTO; - } - - /* Send ATN */ - nfcipLogI(" NFCIP(I) Sending ATN \r\n"); - EXIT_ON_ERR(ret, nfcipDEPControlMsg(nfcip_PFBSPDU_ATN(), 0)); - return ERR_BUSY; - - /*******************************************************************************/ - /* Data rcvd with error -> Digital 1.0 14.12.5.4 */ - case ERR_CRC: - case ERR_PAR: - case ERR_FRAMING: - case ERR_RF_COLLISION: - - nfcipLogI(" NFCIP(I) rx Error: %d \r\n", rxRes); - - /* Digital 1.0 14.12.5.4 Tx Error with data, ignore */ - if(rxLen < NFCIP_MIN_TXERROR_LEN) { - nfcipLogI(" NFCIP(I) Transmission error w data \r\n"); -#if 0 - if(gNfcip.cfg.commMode == RFAL_NFCDEP_COMM_PASSIVE) - { - nfcipLogI( " NFCIP(I) Transmission error w data -> reEnabling Rx \r\n" ); - nfcipReEnableRxTout( NFCIP_TRECOV ); - return ERR_BUSY; - } -#endif /* 0 */ - } - - /* Digital 1.1 16.12.5.4 if NACKs failed raise Transmission error */ - if(gNfcip.cntNACKRetrys++ >= RFAL_NFCDEP_MAX_NACK_RETRYS) { - return ERR_FRAMING; - } - - /* Send NACK */ - nfcipLogI(" NFCIP(I) Sending NACK \r\n"); - EXIT_ON_ERR(ret, nfcipDEPControlMsg(nfcip_PFBRPDU_NACK(gNfcip.pni), 0)); - return ERR_BUSY; - - case ERR_NONE: - break; - - case ERR_BUSY: - return ERR_BUSY; /* Debug purposes */ - - default: - nfcipLogW(" NFCIP(I) Error: %d \r\n", rxRes); - return rxRes; - } - - /*******************************************************************************/ - /* Rx OK check if valid DEP PDU */ - /*******************************************************************************/ - - /* Due to different modes on ST25R391x (with/without CRC) use NFC-DEP LEN instead of bytes retrieved */ - nfcDepLen = gNfcip.rxBuf[rxMsgIt++]; - - nfcipLogD(" NFCIP(I) rx OK: %d bytes \r\n", nfcDepLen); - - /* Digital 1.0 14.15.5.5 Protocol Error */ - if(gNfcip.rxBuf[rxMsgIt++] != NFCIP_RES) { - nfcipLogW(" NFCIP(I) error %02X instead of %02X \r\n", gNfcip.rxBuf[--rxMsgIt], NFCIP_RES); - return ERR_PROTO; - } - - /* Digital 1.0 14.15.5.5 Protocol Error */ - if(gNfcip.rxBuf[rxMsgIt++] != (uint8_t)NFCIP_CMD_DEP_RES) { - nfcipLogW( - " NFCIP(I) error %02X instead of %02X \r\n", - gNfcip.rxBuf[--rxMsgIt], - NFCIP_CMD_DEP_RES); - return ERR_PROTO; - } - - rxPFB = gNfcip.rxBuf[rxMsgIt++]; - - /*******************************************************************************/ - /* Check for valid PFB type */ - if(!(nfcip_PFBisSPDU(rxPFB) || nfcip_PFBisRPDU(rxPFB) || nfcip_PFBisIPDU(rxPFB))) { - return ERR_PROTO; - } - - /*******************************************************************************/ - /* Digital 1.0 14.8.2.1 check if DID is expected and match -> Protocol Error */ - if(gNfcip.cfg.did != RFAL_NFCDEP_DID_NO) { - if((gNfcip.rxBuf[rxMsgIt++] != gNfcip.cfg.did) || !nfcip_PFBhasDID(rxPFB)) { - return ERR_PROTO; - } - optHdrLen++; /* Inc header optional field cnt*/ - } else if(nfcip_PFBhasDID(rxPFB)) /* DID not expected but rcv */ - { - return ERR_PROTO; - } else { - /* MISRA 15.7 - Empty else */ - } - - /*******************************************************************************/ - /* Digital 1.0 14.6.2.8 & 14.6.3.11 NAD must not be used */ - if(gNfcip.cfg.nad != RFAL_NFCDEP_NAD_NO) { - if((gNfcip.rxBuf[rxMsgIt++] != gNfcip.cfg.nad) || !nfcip_PFBhasNAD(rxPFB)) { - return ERR_PROTO; - } - optHdrLen++; /* Inc header optional field cnt*/ - } else if(nfcip_PFBhasNAD(rxPFB)) /* NAD not expected but rcv */ - { - return ERR_PROTO; - } else { - /* MISRA 15.7 - Empty else */ - } - - /*******************************************************************************/ - /* Process R-PDU */ - /*******************************************************************************/ - if(nfcip_PFBisRPDU(rxPFB)) { - /*******************************************************************************/ - /* R ACK */ - /*******************************************************************************/ - if(nfcip_PFBisRACK(rxPFB)) { - nfcipLogI(" NFCIP(I) Rcvd ACK \r\n"); - if(gNfcip.pni == nfcip_PBF_PNI(rxPFB)) { - /* 14.12.3.3 R-ACK with correct PNI -> Increment */ - gNfcip.pni = nfcip_PNIInc(gNfcip.pni); - - /* R-ACK while not performing chaining -> Protocol error*/ - if(!gNfcip.isTxChaining) { - return ERR_PROTO; - } - - nfcipClearCounters(); - gNfcip.state = NFCIP_ST_INIT_DEP_IDLE; - return ERR_NONE; /* This block has been transmitted */ - } else /* Digital 1.0 14.12.4.5 ACK with wrong PNI Initiator may retransmit */ - { - if(gNfcip.cntTxRetrys++ >= RFAL_NFCDEP_MAX_TX_RETRYS) { - return ERR_PROTO; - } - - /* Extended the MAY in Digital 1.0 14.12.4.5 to only reTransmit if the ACK - * is for the previous DEP, otherwise raise Protocol immediately - * If the PNI difference is more than 1 it is worthless to reTransmit 3x - * and after raise the error */ - - if(nfcip_PNIDec(gNfcip.pni) == nfcip_PBF_PNI(rxPFB)) { - /* ReTransmit */ - nfcipLogI(" NFCIP(I) Rcvd ACK prev PNI -> reTx \r\n"); - gNfcip.state = NFCIP_ST_INIT_DEP_TX; - return ERR_BUSY; - } - - nfcipLogI(" NFCIP(I) Rcvd ACK unexpected far PNI -> Error \r\n"); - return ERR_PROTO; - } - } else /* Digital 1.0 - 14.12.5.2 Target must never send NACK */ - { - return ERR_PROTO; - } - } - - /*******************************************************************************/ - /* Process S-PDU */ - /*******************************************************************************/ - if(nfcip_PFBisSPDU(rxPFB)) { - nfcipLogI(" NFCIP(I) Rcvd S-PDU \r\n"); - /*******************************************************************************/ - /* S ATN */ - /*******************************************************************************/ - if(nfcip_PFBisSATN(rxPFB)) /* If is a S-ATN */ - { - nfcipLogI(" NFCIP(I) Rcvd ATN \r\n"); - if(nfcip_PFBisSATN(gNfcip.lastPFB)) /* Check if is expected */ - { - gNfcip.cntATNRetrys = 0; /* Clear ATN counter */ - - /* Although spec is not clear NFC Forum Digital test is expecting to - * retransmit upon receiving ATN_RES */ - if(nfcip_PFBisSTO(gNfcip.lastPFBnATN)) { - nfcipLogI(" NFCIP(I) Rcvd ATN -> reTx RTOX_RES \r\n"); - EXIT_ON_ERR(ret, nfcipDEPControlMsg(nfcip_PFBSPDU_TO(), gNfcip.lastRTOX)); - } else { - /* ReTransmit ? */ - if(gNfcip.cntTxRetrys++ >= RFAL_NFCDEP_MAX_TX_RETRYS) { - return ERR_PROTO; - } - - nfcipLogI(" NFCIP(I) Rcvd ATN -> reTx PNI: %d \r\n", gNfcip.pni); - gNfcip.state = NFCIP_ST_INIT_DEP_TX; - } - - return ERR_BUSY; - } else /* Digital 1.0 14.12.4.4 & 14.12.4.8 */ - { - return ERR_PROTO; - } - } - /*******************************************************************************/ - /* S TO */ - /*******************************************************************************/ - else if(nfcip_PFBisSTO(rxPFB)) /* If is a S-TO (RTOX) */ - { - nfcipLogI(" NFCIP(I) Rcvd TO \r\n"); - - rxRTOX = gNfcip.rxBuf[rxMsgIt++]; - - /* Digital 1.1 16.12.4.3 - Initiator MAY stop accepting subsequent RTOX Req * - * - RTOX request to an ATN -> Protocol error */ - if((gNfcip.cntRTOXRetrys++ > RFAL_NFCDEP_MAX_RTOX_RETRYS) || - nfcip_PFBisSATN(gNfcip.lastPFB)) { - return ERR_PROTO; - } - - /* Digital 1.1 16.8.4.1 RTOX must be between [1,59] */ - if((rxRTOX < NFCIP_INIT_MIN_RTOX) || (rxRTOX > NFCIP_INIT_MAX_RTOX)) { - return ERR_PROTO; - } - - EXIT_ON_ERR(ret, nfcipDEPControlMsg(nfcip_PFBSPDU_TO(), rxRTOX)); - gNfcip.lastRTOX = rxRTOX; - - return ERR_BUSY; - } else { - /* Unexpected S-PDU */ - return ERR_PROTO; /* PRQA S 2880 # MISRA 2.1 - Guard code to prevent unexpected behavior */ - } - } - - /*******************************************************************************/ - /* Process I-PDU */ - /*******************************************************************************/ - if(nfcip_PFBisIPDU(rxPFB)) { - if(gNfcip.pni != nfcip_PBF_PNI(rxPFB)) { - nfcipLogI( - " NFCIP(I) Rcvd IPDU wrong PNI curPNI: %d rxPNI: %d \r\n", - gNfcip.pni, - nfcip_PBF_PNI(rxPFB)); - return ERR_PROTO; - } - - nfcipLogD(" NFCIP(I) Rcvd IPDU OK PNI: %d \r\n", gNfcip.pni); - - /* 14.12.3.3 I-PDU with correct PNI -> Increment */ - gNfcip.pni = nfcip_PNIInc(gNfcip.pni); - - /* Successful data Exchange */ - nfcipClearCounters(); - *outActRxLen = ((uint16_t)nfcDepLen - RFAL_NFCDEP_DEP_HEADER - (uint16_t)optHdrLen); - - if((&gNfcip.rxBuf[gNfcip.rxBufPaylPos] != - &gNfcip.rxBuf[RFAL_NFCDEP_DEP_HEADER + optHdrLen]) && - (*outActRxLen > 0U)) { - ST_MEMMOVE( - &gNfcip.rxBuf[gNfcip.rxBufPaylPos], - &gNfcip.rxBuf[RFAL_NFCDEP_DEP_HEADER + optHdrLen], - *outActRxLen); - } - - /*******************************************************************************/ - /* Check if target is indicating chaining MI */ - /*******************************************************************************/ - if(nfcip_PFBisIMI(rxPFB)) { - gNfcip.isRxChaining = true; - *outIsChaining = true; - - nfcipLogD(" NFCIP(I) Rcvd IPDU OK w MI -> ACK \r\n"); - EXIT_ON_ERR( - ret, nfcipDEPControlMsg(nfcip_PFBRPDU_ACK(gNfcip.pni), gNfcip.rxBuf[rxMsgIt++])); - - return ERR_AGAIN; /* Send Again signalling to run again, but some chaining data has arrived*/ - } else { - gNfcip.isRxChaining = false; - gNfcip.state = NFCIP_ST_INIT_DEP_IDLE; - - ret = ERR_NONE; /* Data exchange done */ - } - } - return ret; -} - -/*******************************************************************************/ -static ReturnCode - nfcipTargetHandleRX(ReturnCode rxRes, uint16_t* outActRxLen, bool* outIsChaining) { - ReturnCode ret; - uint8_t nfcDepLen; - uint8_t rxMsgIt; - uint8_t rxPFB; - uint8_t optHdrLen; - uint8_t resBuf[RFAL_NFCDEP_HEADER_PAD + NFCIP_TARGET_RES_MAX]; - - ret = ERR_INTERNAL; - rxMsgIt = 0; - optHdrLen = 0; - - *outActRxLen = 0; - *outIsChaining = false; - - /*******************************************************************************/ - /* Handle reception errors */ - /*******************************************************************************/ - switch(rxRes) { - /*******************************************************************************/ - case ERR_NONE: - break; - - case ERR_LINK_LOSS: - nfcipLogW(" NFCIP(T) Error: %d \r\n", rxRes); - return rxRes; - - case ERR_BUSY: - return ERR_BUSY; /* Debug purposes */ - - case ERR_TIMEOUT: - case ERR_CRC: - case ERR_PAR: - case ERR_FRAMING: - case ERR_PROTO: - default: - /* Digital 1.1 16.12.5.2 The Target MUST NOT attempt any error recovery. * - * The Target MUST always stay in receive mode when a * - * Transmission Error or a Protocol Error occurs. * - * * - * Do not push Transmission/Protocol Errors to upper layer in Listen Mode #766 */ - - nfcDepReEnableRx(gNfcip.rxBuf, gNfcip.rxBufLen, gNfcip.rxRcvdLen); - return ERR_BUSY; - } - - /*******************************************************************************/ - /* Rx OK check if valid DEP PDU */ - /*******************************************************************************/ - - /* Due to different modes on ST25R391x (with/without CRC) use NFC-DEP LEN instead of bytes retrieved */ - nfcDepLen = gNfcip.rxBuf[rxMsgIt++]; - - nfcipLogD(" NFCIP(T) rx OK: %d bytes \r\n", nfcDepLen); - - if(gNfcip.rxBuf[rxMsgIt++] != NFCIP_REQ) { - nfcDepReEnableRx(gNfcip.rxBuf, gNfcip.rxBufLen, gNfcip.rxRcvdLen); - return ERR_BUSY; /* ERR_PROTO - Ignore bad request */ - } - - /*******************************************************************************/ - /* Check whether target rcvd a normal DEP or deactivation request */ - /*******************************************************************************/ - switch(gNfcip.rxBuf[rxMsgIt++]) { - /*******************************************************************************/ - case(uint8_t)NFCIP_CMD_DEP_REQ: - break; /* Continue to normal DEP processing */ - - /*******************************************************************************/ - case(uint8_t)NFCIP_CMD_DSL_REQ: - - nfcipLogI(" NFCIP(T) rx DSL \r\n"); - - /* Digital 1.0 14.9.1.2 If DID is used and incorrect ignore it */ - /* [Digital 1.0, 16.9.1.2]: If DID == 0, Target SHALL ignore DSL_REQ with DID */ - if((((gNfcip.rxBuf[rxMsgIt++] != gNfcip.cfg.did) || - (nfcDepLen != RFAL_NFCDEP_DSL_RLS_LEN_DID)) && - (gNfcip.cfg.did != RFAL_NFCDEP_DID_NO)) || - ((gNfcip.cfg.did == RFAL_NFCDEP_DID_NO) && - (nfcDepLen != RFAL_NFCDEP_DSL_RLS_LEN_NO_DID))) { - nfcipLogI(" NFCIP(T) DSL wrong DID, ignoring \r\n"); - return ERR_BUSY; - } - - nfcipTx(NFCIP_CMD_DSL_RES, resBuf, NULL, 0, 0, NFCIP_NO_FWT); - - gNfcip.state = NFCIP_ST_TARG_DEP_SLEEP; - return ERR_SLEEP_REQ; - - /*******************************************************************************/ - case(uint8_t)NFCIP_CMD_RLS_REQ: - - nfcipLogI(" NFCIP(T) rx RLS \r\n"); - - /* Digital 1.0 14.10.1.2 If DID is used and incorrect ignore it */ - /* [Digital 1.0, 16.10.2.2]: If DID == 0, Target SHALL ignore DSL_REQ with DID */ - if((((gNfcip.rxBuf[rxMsgIt++] != gNfcip.cfg.did) || - (nfcDepLen != RFAL_NFCDEP_DSL_RLS_LEN_DID)) && - (gNfcip.cfg.did != RFAL_NFCDEP_DID_NO)) || - ((gNfcip.cfg.did == RFAL_NFCDEP_DID_NO) && - (nfcDepLen > RFAL_NFCDEP_DSL_RLS_LEN_NO_DID))) { - nfcipLogI(" NFCIP(T) RLS wrong DID, ignoring \r\n"); - return ERR_BUSY; - } - - nfcipTx(NFCIP_CMD_RLS_RES, resBuf, NULL, 0, 0, NFCIP_NO_FWT); - - gNfcip.state = NFCIP_ST_TARG_DEP_IDLE; - return ERR_RELEASE_REQ; - - /*******************************************************************************/ - /*case NFCIP_CMD_PSL_REQ: PSL must be handled in Activation only */ - /*case NFCIP_CMD_WUP_REQ: WUP not in NFC Forum Digital 1.0 */ - default: - - /* Don't go to NFCIP_ST_TARG_DEP_IDLE state as it needs to ignore this * - * invalid frame, and keep waiting for more frames */ - - nfcDepReEnableRx(gNfcip.rxBuf, gNfcip.rxBufLen, gNfcip.rxRcvdLen); - return ERR_BUSY; /* ERR_PROTO - Ignore bad frame */ - } - - /*******************************************************************************/ - - rxPFB = gNfcip.rxBuf[rxMsgIt++]; /* Store rcvd PFB */ - - /*******************************************************************************/ - /* Check for valid PFB type */ - if(!(nfcip_PFBisSPDU(rxPFB) || nfcip_PFBisRPDU(rxPFB) || nfcip_PFBisIPDU(rxPFB))) { - nfcDepReEnableRx(gNfcip.rxBuf, gNfcip.rxBufLen, gNfcip.rxRcvdLen); - return ERR_BUSY; /* ERR_PROTO - Ignore invalid PFB */ - } - - /*******************************************************************************/ - if(gNfcip.cfg.did != RFAL_NFCDEP_DID_NO) { - if(!nfcip_PFBhasDID(rxPFB)) { - nfcDepReEnableRx(gNfcip.rxBuf, gNfcip.rxBufLen, gNfcip.rxRcvdLen); - return ERR_BUSY; /* ERR_PROTO - Ignore bad/missing DID */ - } - if(gNfcip.rxBuf[rxMsgIt++] != gNfcip.cfg.did) /* MISRA 13.5 */ - { - nfcDepReEnableRx(gNfcip.rxBuf, gNfcip.rxBufLen, gNfcip.rxRcvdLen); - return ERR_BUSY; /* ERR_PROTO - Ignore bad/missing DID */ - } - optHdrLen++; /* Inc header optional field cnt*/ - } else if(nfcip_PFBhasDID(rxPFB)) /* DID not expected but rcv */ - { - nfcDepReEnableRx(gNfcip.rxBuf, gNfcip.rxBufLen, gNfcip.rxRcvdLen); - return ERR_BUSY; /* ERR_PROTO - Ignore unexpected DID */ - } else { - /* MISRA 15.7 - Empty else */ - } - - /*******************************************************************************/ - if(gNfcip.cfg.nad != RFAL_NFCDEP_NAD_NO) { - if((gNfcip.rxBuf[rxMsgIt++] != gNfcip.cfg.did) || !nfcip_PFBhasDID(rxPFB)) { - nfcDepReEnableRx(gNfcip.rxBuf, gNfcip.rxBufLen, gNfcip.rxRcvdLen); - return ERR_BUSY; /* ERR_PROTO - Ignore bad/missing DID */ - } - optHdrLen++; /* Inc header optional field cnt*/ - } else if(nfcip_PFBhasNAD(rxPFB)) /* NAD not expected but rcv */ - { - nfcDepReEnableRx(gNfcip.rxBuf, gNfcip.rxBufLen, gNfcip.rxRcvdLen); - return ERR_BUSY; /* ERR_PROTO - Ignore unexpected NAD */ - } else { - /* MISRA 15.7 - Empty else */ - } - - /*******************************************************************************/ - /* Process R-PDU */ - /*******************************************************************************/ - if(nfcip_PFBisRPDU(rxPFB)) { - nfcipLogD(" NFCIP(T) Rcvd R-PDU \r\n"); - /*******************************************************************************/ - /* R ACK */ - /*******************************************************************************/ - if(nfcip_PFBisRACK(rxPFB)) { - nfcipLogI(" NFCIP(T) Rcvd ACK \r\n"); - if(gNfcip.pni == nfcip_PBF_PNI(rxPFB)) { - /* R-ACK while not performing chaining -> Protocol error */ - if(!gNfcip.isTxChaining) { - nfcDepReEnableRx(gNfcip.rxBuf, gNfcip.rxBufLen, gNfcip.rxRcvdLen); - return ERR_BUSY; /* ERR_PROTO - Ignore unexpected ACK */ - } - - /* This block has been transmitted and acknowledged, perform RTOX until next data is provided */ - - /* Digital 1.1 16.12.4.7 - If ACK rcvd continue with chaining or an RTOX */ - nfcipTimerStart( - gNfcip.RTOXTimer, - nfcipRTOXAdjust(nfcipConv1FcToMs(rfalNfcDepWT2RWT(gNfcip.cfg.to)))); - gNfcip.state = NFCIP_ST_TARG_DEP_RTOX; - - return ERR_NONE; /* This block has been transmitted */ - } - - /* Digital 1.0 14.12.3.4 - If last send was ATN and rx PNI is minus 1 */ - else if( - nfcip_PFBisSATN(gNfcip.lastPFB) && - (nfcip_PNIDec(gNfcip.pni) == nfcip_PBF_PNI(rxPFB))) { - nfcipLogI(" NFCIP(T) wrong PNI, last was ATN reTx \r\n"); - /* Spec says to leave current PNI as is, but will be Inc after Tx, remaining the same */ - gNfcip.pni = nfcip_PNIDec(gNfcip.pni); - - gNfcip.state = NFCIP_ST_TARG_DEP_TX; - return ERR_BUSY; - } else { - /* MISRA 15.7 - Empty else */ - } - } - /*******************************************************************************/ - /* R NACK */ - /*******************************************************************************/ - /* ISO 18092 12.6.1.3.3 When rcv NACK if PNI = prev PNI sent -> reTx */ - else if(nfcip_PFBisRNACK(rxPFB) && (nfcip_PNIDec(gNfcip.pni) == nfcip_PBF_PNI(rxPFB))) { - nfcipLogI(" NFCIP(T) Rcvd NACK \r\n"); - - gNfcip.pni = nfcip_PNIDec(gNfcip.pni); /* Dec so that has the prev PNI */ - - gNfcip.state = NFCIP_ST_TARG_DEP_TX; - return ERR_BUSY; - } else { - nfcipLogI(" NFCIP(T) Unexpected R-PDU \r\n"); - - nfcDepReEnableRx(gNfcip.rxBuf, gNfcip.rxBufLen, gNfcip.rxRcvdLen); - return ERR_BUSY; /* ERR_PROTO - Ignore unexpected R-PDU */ - } - } - - /*******************************************************************************/ - /* Process S-PDU */ - /*******************************************************************************/ - if(nfcip_PFBisSPDU(rxPFB)) { - nfcipLogD(" NFCIP(T) Rcvd S-PDU \r\n"); - - /*******************************************************************************/ - /* S ATN */ - /*******************************************************************************/ - /* ISO 18092 12.6.3 Attention */ - if(nfcip_PFBisSATN(rxPFB)) /* If is a S-ATN */ - { - nfcipLogI(" NFCIP(T) Rcvd ATN curPNI: %d \r\n", gNfcip.pni); - EXIT_ON_ERR(ret, nfcipDEPControlMsg(nfcip_PFBSPDU_ATN(), 0)); - return ERR_BUSY; - } - - /*******************************************************************************/ - /* S TO */ - /*******************************************************************************/ - else if(nfcip_PFBisSTO(rxPFB)) /* If is a S-TO (RTOX) */ - { - if(nfcip_PFBisSTO(gNfcip.lastPFBnATN)) { - nfcipLogI(" NFCIP(T) Rcvd TO \r\n"); - - /* Digital 1.1 16.8.4.6 RTOX value in RES different that in REQ -> Protocol Error */ - if(gNfcip.lastRTOX != gNfcip.rxBuf[rxMsgIt++]) { - nfcipLogI(" NFCIP(T) Mismatched RTOX value \r\n"); - - nfcDepReEnableRx(gNfcip.rxBuf, gNfcip.rxBufLen, gNfcip.rxRcvdLen); - return ERR_BUSY; /* ERR_PROTO - Ignore unexpected RTOX value */ - } - - /* Clear waiting for RTOX Ack Flag */ - gNfcip.isWait4RTOX = false; - - /* Check if a Tx is already pending */ - if(gNfcip.isTxPending) { - nfcipLogW(" NFCIP(T) Tx pending, go immediately to TX \r\n"); - - gNfcip.state = NFCIP_ST_TARG_DEP_TX; - return ERR_BUSY; - } - - /* Start RTOX timer and change to check state */ - nfcipTimerStart( - gNfcip.RTOXTimer, - nfcipRTOXAdjust( - nfcipConv1FcToMs(gNfcip.lastRTOX * rfalNfcDepWT2RWT(gNfcip.cfg.to)))); - gNfcip.state = NFCIP_ST_TARG_DEP_RTOX; - - return ERR_BUSY; - } - } else { - /* Unexpected S-PDU */ - nfcipLogI( - " NFCIP(T) Unexpected S-PDU \r\n"); /* PRQA S 2880 # MISRA 2.1 - Guard code to prevent unexpected behavior */ - - nfcDepReEnableRx(gNfcip.rxBuf, gNfcip.rxBufLen, gNfcip.rxRcvdLen); - return ERR_BUSY; /* ERR_PROTO - Ignore unexpected S-PDU */ - } - } - - /*******************************************************************************/ - /* Process I-PDU */ - /*******************************************************************************/ - if(nfcip_PFBisIPDU(rxPFB)) { - if(gNfcip.pni != nfcip_PBF_PNI(rxPFB)) { - nfcipLogI( - " NFCIP(T) Rcvd IPDU wrong PNI curPNI: %d rxPNI: %d \r\n", - gNfcip.pni, - nfcip_PBF_PNI(rxPFB)); - - /* Digital 1.1 16.12.3.4 - If last send was ATN and rx PNI is minus 1 */ - if(nfcip_PFBisSATN(gNfcip.lastPFB) && - (nfcip_PNIDec(gNfcip.pni) == nfcip_PBF_PNI(rxPFB))) { - /* Spec says to leave current PNI as is, but will be Inc after Data Tx, remaining the same */ - gNfcip.pni = nfcip_PNIDec(gNfcip.pni); - - if(nfcip_PFBisIMI(rxPFB)) { - nfcipLogI( - " NFCIP(T) PNI = prevPNI && ATN before && chaining -> send ACK \r\n"); - EXIT_ON_ERR( - ret, - nfcipDEPControlMsg(nfcip_PFBRPDU_ACK(gNfcip.pni), gNfcip.rxBuf[rxMsgIt++])); - - /* Digital 1.1 16.12.3.4 (...) leave the current PNI unchanged afterwards */ - gNfcip.pni = nfcip_PNIInc(gNfcip.pni); - } else { - nfcipLogI(" NFCIP(T) PNI = prevPNI && ATN before -> reTx last I-PDU \r\n"); - gNfcip.state = NFCIP_ST_TARG_DEP_TX; - } - - return ERR_BUSY; - } - - nfcDepReEnableRx(gNfcip.rxBuf, gNfcip.rxBufLen, gNfcip.rxRcvdLen); - return ERR_BUSY; /* ERR_PROTO - Ignore bad PNI value */ - } - - nfcipLogD(" NFCIP(T) Rcvd IPDU OK PNI: %d \r\n", gNfcip.pni); - - /*******************************************************************************/ - /* Successful data exchange */ - /*******************************************************************************/ - *outActRxLen = ((uint16_t)nfcDepLen - RFAL_NFCDEP_DEP_HEADER - (uint16_t)optHdrLen); - - nfcipClearCounters(); - - if((&gNfcip.rxBuf[gNfcip.rxBufPaylPos] != - &gNfcip.rxBuf[RFAL_NFCDEP_DEP_HEADER + optHdrLen]) && - (*outActRxLen > 0U)) { - ST_MEMMOVE( - &gNfcip.rxBuf[gNfcip.rxBufPaylPos], - &gNfcip.rxBuf[RFAL_NFCDEP_DEP_HEADER + optHdrLen], - *outActRxLen); - } - - /*******************************************************************************/ - /* Check if Initiator is indicating chaining MI */ - /*******************************************************************************/ - if(nfcip_PFBisIMI(rxPFB)) { - gNfcip.isRxChaining = true; - *outIsChaining = true; - - nfcipLogD(" NFCIP(T) Rcvd IPDU OK w MI -> ACK \r\n"); - EXIT_ON_ERR( - ret, nfcipDEPControlMsg(nfcip_PFBRPDU_ACK(gNfcip.pni), gNfcip.rxBuf[rxMsgIt++])); - - gNfcip.pni = nfcip_PNIInc(gNfcip.pni); - - return ERR_AGAIN; /* Send Again signalling to run again, but some chaining data has arrived*/ - } else { - if(gNfcip.isRxChaining) { - nfcipLogI(" NFCIP(T) Rcvd last IPDU chaining finished \r\n"); - } - - /*******************************************************************************/ - /* Reception done, send to DH and start RTOX timer */ - /*******************************************************************************/ - nfcipTimerStart( - gNfcip.RTOXTimer, - nfcipRTOXAdjust(nfcipConv1FcToMs(rfalNfcDepWT2RWT(gNfcip.cfg.to)))); - gNfcip.state = NFCIP_ST_TARG_DEP_RTOX; - - gNfcip.isRxChaining = false; - ret = ERR_NONE; /* Data exchange done */ - } - } - return ret; -} - -/*******************************************************************************/ -static ReturnCode nfcipTx( - rfalNfcDepCmd cmd, - uint8_t* txBuf, - uint8_t* paylBuf, - uint16_t paylLen, - uint8_t pfbData, - uint32_t fwt) { - uint16_t txBufIt; - uint8_t* txBlock; - uint8_t* payloadBuf; - uint8_t pfb; - - if(txBuf == NULL) { - return ERR_PARAM; - } - - payloadBuf = paylBuf; /* MISRA 17.8: Use intermediate variable */ - - if((paylLen == 0U) || (payloadBuf == NULL)) { - payloadBuf = (uint8_t*)&txBuf - [RFAL_NFCDEP_DEPREQ_HEADER_LEN]; /* If not a DEP (no Data) ensure enough space for header */ - } - - txBufIt = 0; - pfb = pfbData; /* MISRA 17.8: Use intermediate variable */ - - txBlock = payloadBuf; /* Point to beginning of the Data, and go backwards */ - - gNfcip.lastCmd = (uint8_t)cmd; /* Store last cmd sent */ - gNfcip.lastPFB = NFCIP_PFB_INVALID; /* Reset last pfb sent */ - - /*******************************************************************************/ - /* Compute outgoing NFCIP message */ - /*******************************************************************************/ - switch(cmd) { - /*******************************************************************************/ - case NFCIP_CMD_ATR_RES: - case NFCIP_CMD_ATR_REQ: - - rfalNfcDepSetNFCID(payloadBuf, gNfcip.cfg.nfcid, gNfcip.cfg.nfcidLen); /* NFCID */ - txBufIt += RFAL_NFCDEP_NFCID3_LEN; - - payloadBuf[txBufIt++] = gNfcip.cfg.did; /* DID */ - payloadBuf[txBufIt++] = gNfcip.cfg.bs; /* BS */ - payloadBuf[txBufIt++] = gNfcip.cfg.br; /* BR */ - - if(cmd == NFCIP_CMD_ATR_RES) { - payloadBuf[txBufIt++] = gNfcip.cfg.to; /* ATR_RES[ TO ] */ - } - - if(gNfcip.cfg.gbLen > 0U) { - payloadBuf[txBufIt++] = nfcip_PPwGB(gNfcip.cfg.lr); /* PP signalling GB */ - ST_MEMCPY( - &payloadBuf[txBufIt], gNfcip.cfg.gb, gNfcip.cfg.gbLen); /* set General Bytes */ - txBufIt += gNfcip.cfg.gbLen; - } else { - payloadBuf[txBufIt++] = rfalNfcDepLR2PP(gNfcip.cfg.lr); /* PP without GB */ - } - - if((txBufIt + RFAL_NFCDEP_CMDTYPE_LEN + RFAL_NFCDEP_CMD_LEN) > - RFAL_NFCDEP_ATRREQ_MAX_LEN) /* Check max ATR length (ATR_REQ = ATR_RES)*/ - { - return ERR_PARAM; - } - break; - - /*******************************************************************************/ - case NFCIP_CMD_WUP_REQ: /* ISO 18092 - 12.5.2.1 */ - - rfalNfcDepSetNFCID((payloadBuf), gNfcip.cfg.nfcid, gNfcip.cfg.nfcidLen); /* NFCID */ - txBufIt += RFAL_NFCDEP_NFCID3_LEN; - - *(--txBlock) = gNfcip.cfg.did; /* DID */ - break; - - /*******************************************************************************/ - case NFCIP_CMD_WUP_RES: /* ISO 18092 - 12.5.2.2 */ - case NFCIP_CMD_PSL_REQ: - case NFCIP_CMD_PSL_RES: - - *(--txBlock) = gNfcip.cfg.did; /* DID */ - break; - - /*******************************************************************************/ - case NFCIP_CMD_RLS_REQ: - case NFCIP_CMD_RLS_RES: - case NFCIP_CMD_DSL_REQ: - case NFCIP_CMD_DSL_RES: - - /* Digital 1.0 - 14.8.1.1 & 14.9.1.1 & 14.10.1.1 Only add DID if not 0 */ - if(gNfcip.cfg.did != RFAL_NFCDEP_DID_NO) { - *(--txBlock) = gNfcip.cfg.did; /* DID */ - } - break; - - /*******************************************************************************/ - case NFCIP_CMD_DEP_REQ: - case NFCIP_CMD_DEP_RES: - - /* Compute optional PFB bits */ - if(gNfcip.cfg.did != RFAL_NFCDEP_DID_NO) { - pfb |= NFCIP_PFB_DID_BIT; - } - if(gNfcip.cfg.nad != RFAL_NFCDEP_NAD_NO) { - pfb |= NFCIP_PFB_NAD_BIT; - } - if((gNfcip.isTxChaining) && (nfcip_PFBisIPDU(pfb))) { - pfb |= NFCIP_PFB_MI_BIT; - } - - /* Store PFB for future handling */ - gNfcip.lastPFB = pfb; /* store PFB sent */ - - if(!nfcip_PFBisSATN(pfb)) { - gNfcip.lastPFBnATN = pfb; /* store last PFB different then ATN */ - } - - /* Add NAD if it is to be supported */ - if(gNfcip.cfg.nad != RFAL_NFCDEP_NAD_NO) { - *(--txBlock) = gNfcip.cfg.nad; /* NAD */ - } - - /* Digital 1.0 - 14.8.1.1 & 14.8.1.1 Only add DID if not 0 */ - if(gNfcip.cfg.did != RFAL_NFCDEP_DID_NO) { - *(--txBlock) = gNfcip.cfg.did; /* DID */ - } - - *(--txBlock) = pfb; /* PFB */ - - /* NCI 1.0 - Check if Empty frames are allowed */ - if((paylLen == 0U) && nfcipIsEmptyDEPDisabled(gNfcip.cfg.oper) && nfcip_PFBisIPDU(pfb)) { - return ERR_PARAM; - } - break; - - /*******************************************************************************/ - default: - return ERR_PARAM; - } - - /*******************************************************************************/ - /* Prepend Header */ - /*******************************************************************************/ - *(--txBlock) = (uint8_t)cmd; /* CMD */ - *(--txBlock) = (uint8_t)(nfcipCmdIsReq(cmd) ? NFCIP_REQ : NFCIP_RES); /* CMDType */ - - txBufIt += - paylLen + - (uint16_t)((uint32_t)payloadBuf - (uint32_t)txBlock); /* Calculate overall buffer size */ - - if(txBufIt > gNfcip.fsc) /* Check if msg length violates the maximum payload size FSC */ - { - return ERR_NOTSUPP; - } - - /*******************************************************************************/ - return nfcipDataTx(txBlock, txBufIt, fwt); -} - -/* - ****************************************************************************** - * GLOBAL FUNCTIONS - ****************************************************************************** - */ - -/*******************************************************************************/ -static void nfcipConfig(const rfalNfcDepConfigs* cfg) { - if(cfg == NULL) { - return; - } - - ST_MEMCPY(&gNfcip.cfg, cfg, sizeof(rfalNfcDepConfigs)); /* Copy given config to local */ - - gNfcip.cfg.to = - MIN(RFAL_NFCDEP_WT_TRG_MAX, gNfcip.cfg.to); /* Ensure proper WT value */ - gNfcip.cfg.did = nfcip_DIDMax(gNfcip.cfg.did); /* Ensure proper DID value */ - gNfcip.fsc = rfalNfcDepLR2FS(gNfcip.cfg.lr); /* Calculate FSC based on given LR */ - - gNfcip.state = - ((gNfcip.cfg.role == RFAL_NFCDEP_ROLE_TARGET) ? NFCIP_ST_TARG_WAIT_ATR : - NFCIP_ST_INIT_IDLE); -} - -/*******************************************************************************/ -static ReturnCode nfcipRun(uint16_t* outActRxLen, bool* outIsChaining) { - ReturnCode ret; - - ret = ERR_SYNTAX; - - nfcipLogD(" NFCIP Run() state: %d \r\n", gNfcip.state); - - switch(gNfcip.state) { - /*******************************************************************************/ - case NFCIP_ST_IDLE: - case NFCIP_ST_INIT_DEP_IDLE: - case NFCIP_ST_TARG_DEP_IDLE: - case NFCIP_ST_TARG_DEP_SLEEP: - return ERR_NONE; - - /*******************************************************************************/ - case NFCIP_ST_INIT_DEP_TX: - - nfcipLogD(" NFCIP(I) Tx PNI: %d txLen: %d \r\n", gNfcip.pni, gNfcip.txBufLen); - ret = nfcipTx( - NFCIP_CMD_DEP_REQ, - gNfcip.txBuf, - &gNfcip.txBuf[gNfcip.txBufPaylPos], - gNfcip.txBufLen, - nfcip_PFBIPDU(gNfcip.pni), - (gNfcip.cfg.fwt + gNfcip.cfg.dFwt)); - - switch(ret) { - case ERR_NONE: - gNfcip.state = NFCIP_ST_INIT_DEP_RX; - break; - - case ERR_PARAM: - default: - gNfcip.state = NFCIP_ST_INIT_DEP_IDLE; - return ret; - } - /* fall through */ - - /*******************************************************************************/ - case NFCIP_ST_INIT_DEP_RX: /* PRQA S 2003 # MISRA 16.3 - Intentional fall through */ - - ret = nfcipDataRx(false); - - if(ret != ERR_BUSY) { - ret = nfcipInitiatorHandleDEP(ret, *gNfcip.rxRcvdLen, outActRxLen, outIsChaining); - } - - break; - - /*******************************************************************************/ - case NFCIP_ST_TARG_DEP_RTOX: - - if(!nfcipTimerisExpired(gNfcip.RTOXTimer)) /* Do nothing until RTOX timer has expired */ - { - return ERR_BUSY; - } - - /* If we cannot send a RTOX raise a Timeout error so that we do not - * hold the field On forever in AP2P */ - if(nfcipIsRTOXReqDisabled(gNfcip.cfg.oper)) { - /* We should reEnable Rx, and measure time between our field Off to - * either report link loss or recover #287 */ - nfcipLogI(" NFCIP(T) RTOX not sent due to config, NOT reenabling Rx \r\n"); - return ERR_TIMEOUT; - } - - if(gNfcip.cntRTOXRetrys++ > - RFAL_NFCDEP_MAX_RTOX_RETRYS) /* Check maximum consecutive RTOX requests */ - { - return ERR_PROTO; - } - - nfcipLogI(" NFCIP(T) RTOX sent \r\n"); - - gNfcip.lastRTOX = - nfcip_RTOXTargMax(gNfcip.cfg.to); /* Calculate requested RTOX value, and send it */ - EXIT_ON_ERR(ret, nfcipDEPControlMsg(nfcip_PFBSPDU_TO(), gNfcip.lastRTOX)); - - /* Set waiting for RTOX Ack Flag */ - gNfcip.isWait4RTOX = true; - - gNfcip.state = NFCIP_ST_TARG_DEP_RX; /* Go back to Rx to process RTOX ack */ - return ERR_BUSY; - - /*******************************************************************************/ - case NFCIP_ST_TARG_DEP_TX: - - nfcipLogD(" NFCIP(T) Tx PNI: %d txLen: %d \r\n", gNfcip.pni, gNfcip.txBufLen); - ret = nfcipTx( - NFCIP_CMD_DEP_RES, - gNfcip.txBuf, - &gNfcip.txBuf[gNfcip.txBufPaylPos], - gNfcip.txBufLen, - nfcip_PFBIPDU(gNfcip.pni), - NFCIP_NO_FWT); - - /* Clear flags */ - gNfcip.isTxPending = false; - gNfcip.isWait4RTOX = false; - - /* Digital 1.0 14.12.3.4 Increment the current PNI after Tx */ - gNfcip.pni = nfcip_PNIInc(gNfcip.pni); - - switch(ret) { - case ERR_NONE: - gNfcip.state = NFCIP_ST_TARG_DEP_RX; /* All OK, goto Rx state */ - break; - - case ERR_PARAM: - default: - gNfcip.state = NFCIP_ST_TARG_DEP_IDLE; /* Upon Tx error, goto IDLE state */ - return ret; - } - /* fall through */ - - /*******************************************************************************/ - case NFCIP_ST_TARG_DEP_RX: /* PRQA S 2003 # MISRA 16.3 - Intentional fall through */ - - if(gNfcip.isReqPending) /* if already has Data should be from a DEP from nfcipTargetHandleActivation() */ - { - nfcipLogD(" NFCIP(T) Skipping Rx Using DEP from Activation \r\n"); - - gNfcip.isReqPending = false; - ret = ERR_NONE; - } else { - ret = nfcipDataRx(false); - } - - if(ret != ERR_BUSY) { - ret = nfcipTargetHandleRX(ret, outActRxLen, outIsChaining); - } - - break; - - /*******************************************************************************/ - default: - /* MISRA 16.4: no empty default statement (a comment being enough) */ - break; - } - return ret; -} - -/*******************************************************************************/ -void rfalNfcDepSetDeactivatingCallback(rfalNfcDepDeactCallback pFunc) { - gNfcip.isDeactivating = pFunc; -} - -/*******************************************************************************/ -void rfalNfcDepInitialize(void) { - nfcipLogD(" NFCIP Ini() \r\n"); - - gNfcip.state = NFCIP_ST_IDLE; - gNfcip.isDeactivating = NULL; - - gNfcip.isTxPending = false; - gNfcip.isWait4RTOX = false; - gNfcip.isReqPending = false; - - gNfcip.cfg.oper = - (RFAL_NFCDEP_OPER_FULL_MI_DIS | RFAL_NFCDEP_OPER_EMPTY_DEP_EN | RFAL_NFCDEP_OPER_ATN_EN | - RFAL_NFCDEP_OPER_RTOX_REQ_EN); - - gNfcip.cfg.did = RFAL_NFCDEP_DID_NO; - gNfcip.cfg.nad = RFAL_NFCDEP_NAD_NO; - - gNfcip.cfg.br = RFAL_NFCDEP_Bx_NO_HIGH_BR; - gNfcip.cfg.bs = RFAL_NFCDEP_Bx_NO_HIGH_BR; - - gNfcip.cfg.lr = RFAL_NFCDEP_LR_254; - gNfcip.fsc = rfalNfcDepLR2FS(gNfcip.cfg.lr); - - gNfcip.cfg.gbLen = 0; - - gNfcip.cfg.fwt = RFAL_NFCDEP_MAX_FWT; - gNfcip.cfg.dFwt = RFAL_NFCDEP_MAX_FWT; - - gNfcip.pni = 0; - - /* Destroy any ongoing RTOX timer*/ - nfcipTimerDestroy(gNfcip.RTOXTimer); - gNfcip.RTOXTimer = 0U; - - gNfcip.PDUTxPos = 0; - gNfcip.PDURxPos = 0; - gNfcip.PDUParam.rxLen = NULL; - gNfcip.PDUParam.rxBuf = NULL; - gNfcip.PDUParam.txBuf = NULL; - - nfcipClearCounters(); -} - -/*******************************************************************************/ -static void nfcipSetDEPParams(const rfalNfcDepDEPParams* DEPParams) { - nfcipLogD(" NFCIP SetDEP() txLen: %d \r\n", DEPParams->txBufLen); - - gNfcip.isTxChaining = DEPParams->txChaining; - gNfcip.txBuf = DEPParams->txBuf; - gNfcip.rxBuf = DEPParams->rxBuf; - gNfcip.txBufLen = DEPParams->txBufLen; - gNfcip.rxBufLen = DEPParams->rxBufLen; - gNfcip.txBufPaylPos = DEPParams->txBufPaylPos; - gNfcip.rxBufPaylPos = DEPParams->rxBufPaylPos; - - if(DEPParams->did != RFAL_NFCDEP_DID_KEEP) { - gNfcip.cfg.did = nfcip_DIDMax(DEPParams->did); - } - - gNfcip.cfg.fwt = DEPParams->fwt; - gNfcip.cfg.dFwt = DEPParams->dFwt; - gNfcip.fsc = DEPParams->fsc; - - if(gNfcip.cfg.role == RFAL_NFCDEP_ROLE_TARGET) { - /* If there's any data to be sent go for Tx */ - if(DEPParams->txBufLen > 0U) { - /* Ensure that an RTOX Ack is not being expected at moment */ - if(!gNfcip.isWait4RTOX) { - gNfcip.state = NFCIP_ST_TARG_DEP_TX; - return; - } else { - /* If RTOX Ack is expected, signal a pending Tx to be transmitted right after */ - gNfcip.isTxPending = true; - nfcipLogW(" NFCIP(T) Waiting RTOX, queueing outgoing DEP Block \r\n"); - } - } - - /*Digital 1.0 14.12.4.1 In target mode the first PDU MUST be sent by the Initiator */ - gNfcip.state = NFCIP_ST_TARG_DEP_RX; - return; - } - - /* New data TxRx request clear previous error counters for consecutive TxRx without reseting communication/protocol layer*/ - nfcipClearCounters(); - - gNfcip.state = NFCIP_ST_INIT_DEP_TX; -} - -/*******************************************************************************/ -bool rfalNfcDepTargetRcvdATR(void) { - return ( - (gNfcip.cfg.role == RFAL_NFCDEP_ROLE_TARGET) && nfcipIsTarget(gNfcip.state) && - (gNfcip.state > NFCIP_ST_TARG_WAIT_ATR)); -} - -/*******************************************************************************/ -bool rfalNfcDepIsAtrReq(const uint8_t* buf, uint16_t bufLen, uint8_t* nfcid3) { - uint8_t msgIt; - - msgIt = 0; - - if((bufLen < RFAL_NFCDEP_ATRREQ_MIN_LEN) || (bufLen > RFAL_NFCDEP_ATRREQ_MAX_LEN)) { - return false; - } - - if(buf[msgIt++] != NFCIP_REQ) { - return false; - } - - if(buf[msgIt++] != (uint8_t)NFCIP_CMD_ATR_REQ) { - return false; - } - - /* Output NFID3 if requested */ - if(nfcid3 != NULL) { - ST_MEMCPY(nfcid3, &buf[RFAL_NFCDEP_ATR_REQ_NFCID3_POS], RFAL_NFCDEP_NFCID3_LEN); - } - - return true; -} - -/*******************************************************************************/ -static ReturnCode nfcipTargetHandleActivation(rfalNfcDepDevice* nfcDepDev, uint8_t* outBRS) { - ReturnCode ret; - uint8_t msgIt; - uint8_t txBuf[RFAL_NFCDEP_HEADER_PAD + NFCIP_PSLRES_LEN]; - - /*******************************************************************************/ - /* Check if we are in correct state */ - /*******************************************************************************/ - if(gNfcip.state != NFCIP_ST_TARG_WAIT_ACTV) { - return ERR_WRONG_STATE; - } - - /*******************************************************************************/ - /* Check required parameters */ - /*******************************************************************************/ - if(outBRS == NULL) { - return ERR_PARAM; - } - - /*******************************************************************************/ - /* Wait and process incoming cmd (PSL / DEP) */ - /*******************************************************************************/ - ret = nfcipDataRx(false); - - if(ret != ERR_NONE) { - return ret; - } - - msgIt = 0; - *outBRS = RFAL_NFCDEP_BRS_MAINTAIN; /* set out BRS to be maintained */ - - msgIt++; /* Skip LEN byte */ - - if(gNfcip.rxBuf[msgIt++] != NFCIP_REQ) { - return ERR_PROTO; - } - - if(gNfcip.rxBuf[msgIt] == (uint8_t)NFCIP_CMD_PSL_REQ) { - msgIt++; - - if(gNfcip.rxBuf[msgIt++] != gNfcip.cfg.did) /* Checking DID */ - { - return ERR_PROTO; - } - - nfcipLogI(" NFCIP(T) PSL REQ rcvd \r\n"); - - *outBRS = gNfcip.rxBuf[msgIt++]; /* assign output BRS value */ - - /* Store FSL(LR) and update current config */ - gNfcip.cfg.lr = (gNfcip.rxBuf[msgIt++] & RFAL_NFCDEP_LR_VAL_MASK); - gNfcip.fsc = rfalNfcDepLR2FS(gNfcip.cfg.lr); - - /*******************************************************************************/ - /* Update NFC-DDE Device info */ - if(nfcDepDev != NULL) { - /* Update Bitrate info */ - /* PRQA S 4342 2 # MISRA 10.5 - Layout of enum rfalBitRate and definition of rfalNfcDepBRS2DSI guarantee no invalid enum values to be created */ - nfcDepDev->info.DSI = (rfalBitRate)rfalNfcDepBRS2DSI( - *outBRS); /* DSI codes the bit rate from Initiator to Target */ - nfcDepDev->info.DRI = (rfalBitRate)rfalNfcDepBRS2DRI( - *outBRS); /* DRI codes the bit rate from Target to Initiator */ - - /* Update Length Reduction and Frame Size */ - nfcDepDev->info.LR = gNfcip.cfg.lr; - nfcDepDev->info.FS = gNfcip.fsc; - - /* Update PPi byte */ - nfcDepDev->activation.Initiator.ATR_REQ.PPi &= ~RFAL_NFCDEP_PP_LR_MASK; - nfcDepDev->activation.Initiator.ATR_REQ.PPi |= rfalNfcDepLR2PP(gNfcip.cfg.lr); - } - - rfalSetBitRate(RFAL_BR_KEEP, gNfcip.nfcDepDev->info.DSI); - - EXIT_ON_ERR(ret, nfcipTx(NFCIP_CMD_PSL_RES, txBuf, NULL, 0, 0, NFCIP_NO_FWT)); - } else { - if(gNfcip.rxBuf[msgIt] == (uint8_t)NFCIP_CMD_DEP_REQ) { - msgIt++; - - /*******************************************************************************/ - /* Digital 1.0 14.12.3.1 PNI must be initialized to 0 */ - if(nfcip_PBF_PNI(gNfcip.rxBuf[msgIt]) != 0U) { - return ERR_PROTO; - } - - /*******************************************************************************/ - /* Digital 1.0 14.8.2.1 check if DID is expected and match -> Protocol Error */ - if(nfcip_PFBhasDID(gNfcip.rxBuf[msgIt])) { - if(gNfcip.rxBuf[++msgIt] != gNfcip.cfg.did) { - return ERR_PROTO; - } - } else if(gNfcip.cfg.did != RFAL_NFCDEP_DID_NO) /* DID expected but not rcv */ - { - return ERR_PROTO; - } else { - /* MISRA 15.7 - Empty else */ - } - } - - /* Signal Request pending to be digested on normal Handling (DEP_REQ, DSL_REQ, RLS_REQ) */ - gNfcip.isReqPending = true; - } - - gNfcip.state = NFCIP_ST_TARG_DEP_RX; - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode - rfalNfcDepATR(const rfalNfcDepAtrParam* param, rfalNfcDepAtrRes* atrRes, uint8_t* atrResLen) { - ReturnCode ret; - rfalNfcDepConfigs cfg; - uint16_t rxLen; - uint8_t msgIt; - uint8_t txBuf[RFAL_NFCDEP_ATRREQ_MAX_LEN]; - uint8_t rxBuf[NFCIP_ATRRES_BUF_LEN]; - - if((param == NULL) || (atrRes == NULL) || (atrResLen == NULL)) { - return ERR_PARAM; - } - - /*******************************************************************************/ - /* Configure NFC-DEP layer */ - /*******************************************************************************/ - - cfg.did = param->DID; - cfg.nad = param->NAD; - cfg.fwt = RFAL_NFCDEP_MAX_FWT; - cfg.dFwt = RFAL_NFCDEP_MAX_FWT; - cfg.br = param->BR; - cfg.bs = param->BS; - cfg.lr = param->LR; - cfg.to = RFAL_NFCDEP_WT_TRG_MAX; /* Not used in Initiator mode */ - - cfg.gbLen = param->GBLen; - if(cfg.gbLen > 0U) /* MISRA 21.18 */ - { - ST_MEMCPY(cfg.gb, param->GB, cfg.gbLen); - } - - cfg.nfcidLen = param->nfcidLen; - if(cfg.nfcidLen > 0U) /* MISRA 21.18 */ - { - ST_MEMCPY(cfg.nfcid, param->nfcid, cfg.nfcidLen); - } - - cfg.role = RFAL_NFCDEP_ROLE_INITIATOR; - cfg.oper = param->operParam; - cfg.commMode = param->commMode; - - rfalNfcDepInitialize(); - nfcipConfig(&cfg); - - /*******************************************************************************/ - /* Send ATR_REQ */ - /*******************************************************************************/ - - EXIT_ON_ERR( - ret, - nfcipTxRx( - NFCIP_CMD_ATR_REQ, - txBuf, - nfcipRWTActivation(), - NULL, - 0, - rxBuf, - NFCIP_ATRRES_BUF_LEN, - &rxLen)); - - /*******************************************************************************/ - /* ATR sent, check response */ - /*******************************************************************************/ - msgIt = 0; - rxLen = ((uint16_t)rxBuf[msgIt++] - RFAL_NFCDEP_LEN_LEN); /* use LEN byte */ - - if((rxLen < RFAL_NFCDEP_ATRRES_MIN_LEN) || - (rxLen > RFAL_NFCDEP_ATRRES_MAX_LEN)) /* Checking length: ATR_RES */ - { - return ERR_PROTO; - } - - if(rxBuf[msgIt++] != NFCIP_RES) /* Checking if is a response*/ - { - return ERR_PROTO; - } - - if(rxBuf[msgIt++] != (uint8_t)NFCIP_CMD_ATR_RES) /* Checking if is a ATR RES */ - { - return ERR_PROTO; - } - - ST_MEMCPY((uint8_t*)atrRes, (rxBuf + RFAL_NFCDEP_LEN_LEN), rxLen); - *atrResLen = (uint8_t)rxLen; - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalNfcDepPSL(uint8_t BRS, uint8_t FSL) { - ReturnCode ret; - uint16_t rxLen; - uint8_t msgIt; - uint8_t txBuf[NFCIP_PSLREQ_LEN + NFCIP_PSLPAY_LEN]; - uint8_t rxBuf[NFCIP_PSLRES_LEN]; - - msgIt = NFCIP_PSLREQ_LEN; - - txBuf[msgIt++] = BRS; - txBuf[msgIt++] = FSL; - - /*******************************************************************************/ - /* Send PSL REQ and wait for response */ - /*******************************************************************************/ - EXIT_ON_ERR( - ret, - nfcipTxRx( - NFCIP_CMD_PSL_REQ, - txBuf, - nfcipRWTActivation(), - &txBuf[NFCIP_PSLREQ_LEN], - (msgIt - NFCIP_PSLREQ_LEN), - rxBuf, - NFCIP_PSLRES_LEN, - &rxLen)); - - /*******************************************************************************/ - /* PSL sent, check response */ - /*******************************************************************************/ - msgIt = 0; - rxLen = (uint16_t)(rxBuf[msgIt++]); /* use LEN byte */ - - if(rxLen < NFCIP_PSLRES_LEN) /* Checking length: LEN + RLS_RES */ - { - return ERR_PROTO; - } - - if(rxBuf[msgIt++] != NFCIP_RES) /* Checking if is a response */ - { - return ERR_PROTO; - } - - if(rxBuf[msgIt++] != (uint8_t)NFCIP_CMD_PSL_RES) /* Checking if is a PSL RES */ - { - return ERR_PROTO; - } - - if(rxBuf[msgIt++] != gNfcip.cfg.did) /* Checking DID */ - { - return ERR_PROTO; - } - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalNfcDepDSL(void) { - ReturnCode ret; - uint8_t txBuf[RFAL_NFCDEP_HEADER_PAD + NFCIP_DSLREQ_LEN]; - uint8_t rxBuf[NFCIP_DSLRES_LEN]; - uint8_t rxMsgIt; - uint16_t rxLen = 0; - - if(gNfcip.cfg.role == RFAL_NFCDEP_ROLE_TARGET) { - return ERR_NONE; /* Target has no deselect procedure */ - } - - /* Repeating a DSL REQ is optional, not doing it */ - EXIT_ON_ERR( - ret, - nfcipTxRx( - NFCIP_CMD_DSL_REQ, - txBuf, - nfcipRWTActivation(), - NULL, - 0, - rxBuf, - (uint16_t)sizeof(rxBuf), - &rxLen)); - - /*******************************************************************************/ - rxMsgIt = 0; - - if(rxBuf[rxMsgIt++] < NFCIP_DSLRES_MIN) /* Checking length: LEN + DSL_RES */ - { - return ERR_PROTO; - } - - if(rxBuf[rxMsgIt++] != NFCIP_RES) /* Checking if is a response */ - { - return ERR_PROTO; - } - - if(rxBuf[rxMsgIt++] != (uint8_t)NFCIP_CMD_DSL_RES) /* Checking if is DSL RES */ - { - return ERR_PROTO; - } - - if(gNfcip.cfg.did != RFAL_NFCDEP_DID_NO) { - if(rxBuf[rxMsgIt++] != gNfcip.cfg.did) { - return ERR_PROTO; - } - } - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalNfcDepRLS(void) { - ReturnCode ret; - uint8_t txBuf[RFAL_NFCDEP_HEADER_PAD + NFCIP_RLSREQ_LEN]; - uint8_t rxBuf[NFCIP_RLSRES_LEN]; - uint8_t rxMsgIt; - uint16_t rxLen = 0; - - if(gNfcip.cfg.role == RFAL_NFCDEP_ROLE_TARGET) /* Target has no release procedure */ - { - return ERR_NONE; - } - - /* Repeating a RLS REQ is optional, not doing it */ - EXIT_ON_ERR( - ret, - nfcipTxRx( - NFCIP_CMD_RLS_REQ, - txBuf, - nfcipRWTActivation(), - NULL, - 0, - rxBuf, - (uint16_t)sizeof(rxBuf), - &rxLen)); - - /*******************************************************************************/ - rxMsgIt = 0; - - if(rxBuf[rxMsgIt++] < NFCIP_RLSRES_MIN) /* Checking length: LEN + RLS_RES */ - { - return ERR_PROTO; - } - - if(rxBuf[rxMsgIt++] != NFCIP_RES) /* Checking if is a response */ - { - return ERR_PROTO; - } - - if(rxBuf[rxMsgIt++] != (uint8_t)NFCIP_CMD_RLS_RES) /* Checking if is RLS RES */ - { - return ERR_PROTO; - } - - if(gNfcip.cfg.did != RFAL_NFCDEP_DID_NO) { - if(rxBuf[rxMsgIt++] != gNfcip.cfg.did) { - return ERR_PROTO; - } - } - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalNfcDepInitiatorHandleActivation( - rfalNfcDepAtrParam* param, - rfalBitRate desiredBR, - rfalNfcDepDevice* nfcDepDev) { - ReturnCode ret; - uint8_t maxRetyrs; - uint8_t PSL_BRS; - uint8_t PSL_FSL; - bool sendPSL; - - if((param == NULL) || (nfcDepDev == NULL)) { - return ERR_PARAM; - } - - param->NAD = RFAL_NFCDEP_NAD_NO; /* Digital 1.1 16.6.2.9 Initiator SHALL NOT use NAD */ - maxRetyrs = NFCIP_ATR_RETRY_MAX; - - /*******************************************************************************/ - /* Send ATR REQ and wait for response */ - /*******************************************************************************/ - do { /* Upon transmission error ATR REQ should be retried */ - - ret = rfalNfcDepATR( - param, - &nfcDepDev->activation.Target.ATR_RES, - &nfcDepDev->activation.Target.ATR_RESLen); - - if(nfcipIsTransmissionError(ret)) { - continue; - } - break; - } while((maxRetyrs--) != 0U); - - if(ret != ERR_NONE) { - return ret; - } - - /*******************************************************************************/ - /* Compute NFC-DEP device with ATR_RES */ - /*******************************************************************************/ - nfcDepDev->info.GBLen = (nfcDepDev->activation.Target.ATR_RESLen - RFAL_NFCDEP_ATRRES_MIN_LEN); - nfcDepDev->info.DID = nfcDepDev->activation.Target.ATR_RES.DID; - nfcDepDev->info.NAD = - RFAL_NFCDEP_NAD_NO; /* Digital 1.1 16.6.3.11 Initiator SHALL ignore b1 of PPt */ - nfcDepDev->info.LR = rfalNfcDepPP2LR(nfcDepDev->activation.Target.ATR_RES.PPt); - nfcDepDev->info.FS = rfalNfcDepLR2FS(nfcDepDev->info.LR); - nfcDepDev->info.WT = (nfcDepDev->activation.Target.ATR_RES.TO & RFAL_NFCDEP_WT_MASK); - nfcDepDev->info.FWT = rfalNfcDepCalculateRWT(nfcDepDev->info.WT); - nfcDepDev->info.dFWT = RFAL_NFCDEP_WT_DELTA; - - rfalGetBitRate(&nfcDepDev->info.DSI, &nfcDepDev->info.DRI); - - /*******************************************************************************/ - /* Check if a PSL needs to be sent */ - /*******************************************************************************/ - sendPSL = false; - PSL_BRS = rfalNfcDepDx2BRS( - nfcDepDev->info.DSI); /* Set current bit rate divisor on both directions */ - PSL_FSL = nfcDepDev->info.LR; /* Set current Frame Size */ - - /* Activity 1.0 9.4.4.15 & 9.4.6.3 NFC-DEP Activation PSL - * Activity 2.0 9.4.4.17 & 9.4.6.6 NFC-DEP Activation PSL - * - * PSL_REQ shall only be sent if desired bit rate is different from current (Activity 1.0) - * PSL_REQ shall be sent to update LR or bit rate (Activity 2.0) - * */ - -#if 0 /* PSL due to LR is disabled, can be enabled if desired*/ - /*******************************************************************************/ - /* Check Frame Size */ - /*******************************************************************************/ - if( gNfcip.cfg.lr < nfcDepDev->info.LR ) /* If our Length reduction is smaller */ - { - sendPSL = true; - - nfcDepDev->info.LR = MIN( nfcDepDev->info.LR, gNfcip.cfg.lr ); - - gNfcip.cfg.lr = nfcDepDev->info.LR; /* Update nfcip LR to be used */ - gNfcip.fsc = rfalNfcDepLR2FS( gNfcip.cfg.lr ); /* Update nfcip FSC to be used */ - - PSL_FSL = gNfcip.cfg.lr; /* Set LR to be sent */ - - nfcipLogI( " NFCIP(I) Frame Size differ, PSL new fsc: %d \r\n", gNfcip.fsc ); - } -#endif - - /*******************************************************************************/ - /* Check Baud rates */ - /*******************************************************************************/ - if((nfcDepDev->info.DSI != desiredBR) && - (desiredBR != RFAL_BR_KEEP)) /* if desired BR is different */ - { - if(nfcipDxIsSupported( - (uint8_t)desiredBR, - nfcDepDev->activation.Target.ATR_RES.BRt, - nfcDepDev->activation.Target.ATR_RES - .BSt)) /* if desired BR is supported */ /* MISRA 13.5 */ - { - sendPSL = true; - PSL_BRS = rfalNfcDepDx2BRS(desiredBR); - - nfcipLogI(" NFCIP(I) BR differ, PSL BR: 0x%02X \r\n", PSL_BRS); - } - } - - /*******************************************************************************/ - if(sendPSL) { - /*******************************************************************************/ - /* Send PSL REQ and wait for response */ - /*******************************************************************************/ - EXIT_ON_ERR(ret, rfalNfcDepPSL(PSL_BRS, PSL_FSL)); - - /* Check if bit rate has been changed */ - if(nfcDepDev->info.DSI != desiredBR) { - /* Check if device was in Passive NFC-A and went to higher bit rates, use NFC-F */ - if((nfcDepDev->info.DSI == RFAL_BR_106) && - (gNfcip.cfg.commMode == RFAL_NFCDEP_COMM_PASSIVE)) { -#if RFAL_FEATURE_NFCF - /* If Passive initialize NFC-F module */ - rfalNfcfPollerInitialize(desiredBR); -#else /* RFAL_FEATURE_NFCF */ - return ERR_NOTSUPP; -#endif /* RFAL_FEATURE_NFCF */ - } - - nfcDepDev->info.DRI = desiredBR; /* DSI Bit Rate coding from Initiator to Target */ - nfcDepDev->info.DSI = desiredBR; /* DRI Bit Rate coding from Target to Initiator */ - - rfalSetBitRate(nfcDepDev->info.DSI, nfcDepDev->info.DRI); - } - - return ERR_NONE; /* PSL has been sent */ - } - - return ERR_NONE; /* No PSL has been sent */ -} - -/*******************************************************************************/ -uint32_t rfalNfcDepCalculateRWT(uint8_t wt) { - /* Digital 1.0 14.6.3.8 & Digital 1.1 16.6.3.9 */ - /* Digital 1.1 16.6.3.9 treat all RFU values as WT=14 */ - uint8_t responseWaitTime = MIN(RFAL_NFCDEP_WT_INI_MAX, wt); - - return (uint32_t)rfalNfcDepWT2RWT(responseWaitTime); -} - -/*******************************************************************************/ -static ReturnCode nfcipDataTx(uint8_t* txBuf, uint16_t txBufLen, uint32_t fwt) { - return rfalTransceiveBlockingTx( - txBuf, - txBufLen, - gNfcip.rxBuf, - gNfcip.rxBufLen, - gNfcip.rxRcvdLen, - (RFAL_TXRX_FLAGS_DEFAULT | (uint32_t)RFAL_TXRX_FLAGS_NFCIP1_ON), - ((fwt == NFCIP_NO_FWT) ? RFAL_FWT_NONE : fwt)); -} - -/*******************************************************************************/ -static ReturnCode nfcipDataRx(bool blocking) { - ReturnCode ret; - - /* Perform Rx either blocking or non-blocking */ - if(blocking) { - ret = rfalTransceiveBlockingRx(); - } else { - ret = rfalGetTransceiveStatus(); - } - - if(ret != ERR_BUSY) { - if(gNfcip.rxRcvdLen != NULL) { - (*gNfcip.rxRcvdLen) = rfalConvBitsToBytes(*gNfcip.rxRcvdLen); - - if((ret == ERR_NONE) && (gNfcip.rxBuf != NULL)) { - /* Digital 1.1 16.4.1.3 - Length byte LEN SHALL have a value between 3 and 255 -> otherwise treat as Transmission Error * - * - Ensure that actual received and frame length do match, otherwise treat as Transmission error */ - if((*gNfcip.rxRcvdLen != (uint16_t)*gNfcip.rxBuf) || - (*gNfcip.rxRcvdLen < RFAL_NFCDEP_LEN_MIN) || - (*gNfcip.rxRcvdLen > RFAL_NFCDEP_LEN_MAX)) { - return ERR_FRAMING; - } - } - } - } - - return ret; -} - -/*******************************************************************************/ -ReturnCode rfalNfcDepListenStartActivation( - const rfalNfcDepTargetParam* param, - const uint8_t* atrReq, - uint16_t atrReqLength, - rfalNfcDepListenActvParam rxParam) { - ReturnCode ret; - rfalNfcDepConfigs cfg; - - if((param == NULL) || (atrReq == NULL) || (rxParam.rxLen == NULL)) { - return ERR_PARAM; - } - - /*******************************************************************************/ - /* Check whether is a valid ATR_REQ Compute NFC-DEP device */ - if(!rfalNfcDepIsAtrReq(atrReq, atrReqLength, NULL)) { - return ERR_PARAM; - } - - rxParam.nfcDepDev->activation.Initiator.ATR_REQLen = - (uint8_t)atrReqLength; /* nfcipIsAtrReq() is already checking Min and Max buffer lengths */ - if(atrReqLength > 0U) /* MISRA 21.18 */ - { - ST_MEMCPY( - (uint8_t*)&rxParam.nfcDepDev->activation.Initiator.ATR_REQ, atrReq, atrReqLength); - } - - rxParam.nfcDepDev->info.GBLen = (uint8_t)(atrReqLength - RFAL_NFCDEP_ATRREQ_MIN_LEN); - rxParam.nfcDepDev->info.DID = rxParam.nfcDepDev->activation.Initiator.ATR_REQ.DID; - rxParam.nfcDepDev->info.NAD = - RFAL_NFCDEP_NAD_NO; /* Digital 1.1 16.6.2.9 Initiator SHALL NOT use NAD */ - rxParam.nfcDepDev->info.LR = - rfalNfcDepPP2LR(rxParam.nfcDepDev->activation.Initiator.ATR_REQ.PPi); - rxParam.nfcDepDev->info.FS = rfalNfcDepLR2FS(rxParam.nfcDepDev->info.LR); - rxParam.nfcDepDev->info.WT = 0; - rxParam.nfcDepDev->info.FWT = NFCIP_NO_FWT; - rxParam.nfcDepDev->info.dFWT = NFCIP_NO_FWT; - - rfalGetBitRate(&rxParam.nfcDepDev->info.DSI, &rxParam.nfcDepDev->info.DRI); - - /* Store Device Info location, updated upon a PSL */ - gNfcip.nfcDepDev = rxParam.nfcDepDev; - - /*******************************************************************************/ - cfg.did = rxParam.nfcDepDev->activation.Initiator.ATR_REQ.DID; - cfg.nad = RFAL_NFCDEP_NAD_NO; - - cfg.fwt = RFAL_NFCDEP_MAX_FWT; - cfg.dFwt = RFAL_NFCDEP_MAX_FWT; - - cfg.br = param->brt; - cfg.bs = param->bst; - - cfg.lr = rfalNfcDepPP2LR(param->ppt); - - cfg.gbLen = param->GBtLen; - if(cfg.gbLen > 0U) /* MISRA 21.18 */ - { - ST_MEMCPY(cfg.gb, param->GBt, cfg.gbLen); - } - - cfg.nfcidLen = RFAL_NFCDEP_NFCID3_LEN; - ST_MEMCPY(cfg.nfcid, param->nfcid3, RFAL_NFCDEP_NFCID3_LEN); - - cfg.to = param->to; - - cfg.role = RFAL_NFCDEP_ROLE_TARGET; - cfg.oper = param->operParam; - cfg.commMode = param->commMode; - - rfalNfcDepInitialize(); - nfcipConfig(&cfg); - - /*******************************************************************************/ - /* Reply with ATR RES to Initiator */ - /*******************************************************************************/ - gNfcip.rxBuf = (uint8_t*)rxParam.rxBuf; - gNfcip.rxBufLen = sizeof(rfalNfcDepBufFormat); - gNfcip.rxRcvdLen = rxParam.rxLen; - gNfcip.rxBufPaylPos = RFAL_NFCDEP_DEPREQ_HEADER_LEN; - gNfcip.isChaining = rxParam.isRxChaining; - gNfcip.txBufPaylPos = RFAL_NFCDEP_DEPREQ_HEADER_LEN; - - EXIT_ON_ERR(ret, nfcipTx(NFCIP_CMD_ATR_RES, (uint8_t*)gNfcip.rxBuf, NULL, 0, 0, NFCIP_NO_FWT)); - - gNfcip.state = NFCIP_ST_TARG_WAIT_ACTV; - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalNfcDepListenGetActivationStatus(void) { - ReturnCode err; - uint8_t BRS; - - BRS = RFAL_NFCDEP_BRS_MAINTAIN; - - err = nfcipTargetHandleActivation(gNfcip.nfcDepDev, &BRS); - - switch(err) { - case ERR_NONE: - - if(BRS != RFAL_NFCDEP_BRS_MAINTAIN) { - /* DSI codes the bit rate from Initiator to Target */ - /* DRI codes the bit rate from Target to Initiator */ - - if(gNfcip.cfg.commMode == RFAL_NFCDEP_COMM_ACTIVE) { - EXIT_ON_ERR( - err, - rfalSetMode( - RFAL_MODE_LISTEN_ACTIVE_P2P, - gNfcip.nfcDepDev->info.DRI, - gNfcip.nfcDepDev->info.DSI)); - } else { - EXIT_ON_ERR( - err, - rfalSetMode( - ((RFAL_BR_106 == gNfcip.nfcDepDev->info.DRI) ? RFAL_MODE_LISTEN_NFCA : - RFAL_MODE_LISTEN_NFCF), - gNfcip.nfcDepDev->info.DRI, - gNfcip.nfcDepDev->info.DSI)); - } - } - break; - - case ERR_BUSY: - // do nothing - break; - - case ERR_PROTO: - default: - // re-enable receiving of data - nfcDepReEnableRx(gNfcip.rxBuf, gNfcip.rxBufLen, gNfcip.rxRcvdLen); - break; - } - - return err; -} - -/*******************************************************************************/ -ReturnCode rfalNfcDepStartTransceive(const rfalNfcDepTxRxParam* param) { - rfalNfcDepDEPParams nfcDepParams; - - nfcDepParams.txBuf = (uint8_t*)param->txBuf; - nfcDepParams.txBufLen = param->txBufLen; - nfcDepParams.txChaining = param->isTxChaining; - nfcDepParams.txBufPaylPos = - RFAL_NFCDEP_DEPREQ_HEADER_LEN; /* position in txBuf where actual outgoing data is located */ - nfcDepParams.did = RFAL_NFCDEP_DID_KEEP; - nfcDepParams.rxBufPaylPos = RFAL_NFCDEP_DEPREQ_HEADER_LEN; - nfcDepParams.rxBuf = (uint8_t*)param->rxBuf; - nfcDepParams.rxBufLen = sizeof(rfalNfcDepBufFormat); - nfcDepParams.fsc = param->FSx; - nfcDepParams.fwt = param->FWT; - nfcDepParams.dFwt = param->dFWT; - - gNfcip.rxRcvdLen = param->rxLen; - gNfcip.isChaining = param->isRxChaining; - - nfcipSetDEPParams(&nfcDepParams); - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalNfcDepGetTransceiveStatus(void) { - return nfcipRun(gNfcip.rxRcvdLen, gNfcip.isChaining); -} - -/*******************************************************************************/ -static void rfalNfcDepPdu2BLockParam( - rfalNfcDepPduTxRxParam pduParam, - rfalNfcDepTxRxParam* blockParam, - uint16_t txPos, - uint16_t rxPos) { - uint16_t maxInfLen; - - NO_WARNING(rxPos); /* Keep this param for future use */ - - blockParam->DID = pduParam.DID; - blockParam->FSx = pduParam.FSx; - blockParam->FWT = pduParam.FWT; - blockParam->dFWT = pduParam.dFWT; - - /* Calculate max INF/Payload to be sent to other device */ - maxInfLen = (blockParam->FSx - (RFAL_NFCDEP_HEADER + RFAL_NFCDEP_DEP_PFB_LEN)); - maxInfLen += ((blockParam->DID != RFAL_NFCDEP_DID_NO) ? RFAL_NFCDEP_DID_LEN : 0U); - - if((pduParam.txBufLen - txPos) > maxInfLen) { - blockParam->isTxChaining = true; - blockParam->txBufLen = maxInfLen; - } else { - blockParam->isTxChaining = false; - blockParam->txBufLen = (pduParam.txBufLen - txPos); - } - - /* TxBuf is moved to the beginning for every Block */ - blockParam->txBuf = - (rfalNfcDepBufFormat*)pduParam - .txBuf; /* PRQA S 0310 # MISRA 11.3 - Intentional safe cast to avoiding large buffer duplication */ - blockParam->rxBuf = - pduParam - .tmpBuf; /* Simply using the pdu buffer is not possible because of current ACK handling */ - blockParam->isRxChaining = &gNfcip.isPDURxChaining; - blockParam->rxLen = pduParam.rxLen; -} - -/*******************************************************************************/ -ReturnCode rfalNfcDepStartPduTransceive(rfalNfcDepPduTxRxParam param) { - rfalNfcDepTxRxParam txRxParam; - - /* Initialize and store APDU context */ - gNfcip.PDUParam = param; - gNfcip.PDUTxPos = 0; - gNfcip.PDURxPos = 0; - - /* Convert PDU TxRxParams to Block TxRxParams */ - rfalNfcDepPdu2BLockParam(gNfcip.PDUParam, &txRxParam, gNfcip.PDUTxPos, gNfcip.PDURxPos); - - return rfalNfcDepStartTransceive(&txRxParam); -} - -/*******************************************************************************/ -ReturnCode rfalNfcDepGetPduTransceiveStatus(void) { - ReturnCode ret; - rfalNfcDepTxRxParam txRxParam; - - ret = rfalNfcDepGetTransceiveStatus(); - switch(ret) { - /*******************************************************************************/ - case ERR_NONE: - - /* Check if we are still doing chaining on Tx */ - if(gNfcip.isTxChaining) { - /* Add already Tx bytes */ - gNfcip.PDUTxPos += gNfcip.txBufLen; - - /* Convert APDU TxRxParams to I-Block TxRxParams */ - rfalNfcDepPdu2BLockParam( - gNfcip.PDUParam, &txRxParam, gNfcip.PDUTxPos, gNfcip.PDURxPos); - - if(txRxParam.txBufLen > 0U) /* MISRA 21.18 */ - { - /* Move next Block to beginning of APDU Tx buffer */ - ST_MEMCPY( - gNfcip.PDUParam.txBuf->pdu, - &gNfcip.PDUParam.txBuf->pdu[gNfcip.PDUTxPos], - txRxParam.txBufLen); - } - - EXIT_ON_ERR(ret, rfalNfcDepStartTransceive(&txRxParam)); - return ERR_BUSY; - } - - /* PDU TxRx is done */ - /* fall through */ - - /*******************************************************************************/ - case ERR_AGAIN: /* PRQA S 2003 # MISRA 16.3 - Intentional fall through */ - - /* Check if no PDU transceive has been started before (data from rfalNfcDepListenStartActivation) */ - if(gNfcip.PDUParam.rxLen == NULL) { - /* In Listen mode first chained packet cannot be retrieved via APDU interface */ - if(ret == ERR_AGAIN) { - return ERR_NOTSUPP; - } - - /* TxRx is complete and full data is already available */ - return ERR_NONE; - } - - if((*gNfcip.PDUParam.rxLen) > 0U) /* MISRA 21.18 */ - { - /* Ensure that data in tmpBuf still fits into PDU buffer */ - if((uint16_t)((uint16_t)gNfcip.PDURxPos + (*gNfcip.PDUParam.rxLen)) > - RFAL_FEATURE_NFC_DEP_PDU_MAX_LEN) { - return ERR_NOMEM; - } - - /* Copy chained packet from tmp buffer to PDU buffer */ - ST_MEMCPY( - &gNfcip.PDUParam.rxBuf->pdu[gNfcip.PDURxPos], - gNfcip.PDUParam.tmpBuf->inf, - *gNfcip.PDUParam.rxLen); - gNfcip.PDURxPos += *gNfcip.PDUParam.rxLen; - } - - /* Update output param rxLen */ - *gNfcip.PDUParam.rxLen = gNfcip.PDURxPos; - - /* Wait for following Block or PDU TxRx is done */ - return ((ret == ERR_AGAIN) ? ERR_BUSY : ERR_NONE); - - /*******************************************************************************/ - default: - /* MISRA 16.4: no empty default statement (a comment being enough) */ - break; - } - - return ret; -} - -#endif /* RFAL_FEATURE_NFC_DEP */ diff --git a/lib/ST25RFAL002/source/rfal_nfca.c b/lib/ST25RFAL002/source/rfal_nfca.c deleted file mode 100644 index d4e4c93880f..00000000000 --- a/lib/ST25RFAL002/source/rfal_nfca.c +++ /dev/null @@ -1,886 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

    © COPYRIGHT 2020 STMicroelectronics

    - * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R391x firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file rfal_nfca.c - * - * \author Gustavo Patricio - * - * \brief Provides several NFC-A convenience methods and definitions - * - * It provides a Poller (ISO14443A PCD) interface and as well as - * some NFC-A Listener (ISO14443A PICC) helpers. - * - * The definitions and helpers methods provided by this module are only - * up to ISO14443-3 layer - * - */ - -/* - ****************************************************************************** - * INCLUDES - ****************************************************************************** - */ -#include "rfal_nfca.h" -#include "utils.h" - -/* - ****************************************************************************** - * ENABLE SWITCH - ****************************************************************************** - */ - -#ifndef RFAL_FEATURE_NFCA -#define RFAL_FEATURE_NFCA false /* NFC-A module configuration missing. Disabled by default */ -#endif - -#if RFAL_FEATURE_NFCA - -/* - ****************************************************************************** - * GLOBAL DEFINES - ****************************************************************************** - */ - -#define RFAL_NFCA_SLP_FWT \ - rfalConvMsTo1fc(1) /*!< Check 1ms for any modulation ISO14443-3 6.4.3 */ -#define RFAL_NFCA_SLP_CMD 0x50U /*!< SLP cmd (byte1) Digital 1.1 6.9.1 & Table 20 */ -#define RFAL_NFCA_SLP_BYTE2 0x00U /*!< SLP byte2 Digital 1.1 6.9.1 & Table 20 */ -#define RFAL_NFCA_SLP_CMD_POS 0U /*!< SLP cmd position Digital 1.1 6.9.1 & Table 20 */ -#define RFAL_NFCA_SLP_BYTE2_POS 1U /*!< SLP byte2 position Digital 1.1 6.9.1 & Table 20 */ - -#define RFAL_NFCA_SDD_CT 0x88U /*!< Cascade Tag value Digital 1.1 6.7.2 */ -#define RFAL_NFCA_SDD_CT_LEN 1U /*!< Cascade Tag length */ - -#define RFAL_NFCA_SLP_REQ_LEN 2U /*!< SLP_REQ length */ - -#define RFAL_NFCA_SEL_CMD_LEN 1U /*!< SEL_CMD length */ -#define RFAL_NFCA_SEL_PAR_LEN 1U /*!< SEL_PAR length */ -#define RFAL_NFCA_SEL_SELPAR \ - rfalNfcaSelPar(7U, 0U) /*!< SEL_PAR on Select is always with 4 data/nfcid */ -#define RFAL_NFCA_BCC_LEN 1U /*!< BCC length */ - -#define RFAL_NFCA_SDD_REQ_LEN \ - (RFAL_NFCA_SEL_CMD_LEN + RFAL_NFCA_SEL_PAR_LEN) /*!< SDD_REQ length */ -#define RFAL_NFCA_SDD_RES_LEN \ - (RFAL_NFCA_CASCADE_1_UID_LEN + RFAL_NFCA_BCC_LEN) /*!< SDD_RES length */ - -#define RFAL_NFCA_T_RETRANS 5U /*!< t RETRANSMISSION [3, 33]ms EMVCo 2.6 A.5 */ -#define RFAL_NFCA_N_RETRANS 2U /*!< Number of retries EMVCo 2.6 9.6.1.3 */ - -/*! SDD_REQ (Select) Cascade Levels */ -enum { - RFAL_NFCA_SEL_CASCADE_L1 = 0, /*!< SDD_REQ Cascade Level 1 */ - RFAL_NFCA_SEL_CASCADE_L2 = 1, /*!< SDD_REQ Cascade Level 2 */ - RFAL_NFCA_SEL_CASCADE_L3 = 2 /*!< SDD_REQ Cascade Level 3 */ -}; - -/*! SDD_REQ (Select) request Cascade Level command Digital 1.1 Table 15 */ -enum { - RFAL_NFCA_CMD_SEL_CL1 = 0x93, /*!< SDD_REQ command Cascade Level 1 */ - RFAL_NFCA_CMD_SEL_CL2 = 0x95, /*!< SDD_REQ command Cascade Level 2 */ - RFAL_NFCA_CMD_SEL_CL3 = 0x97, /*!< SDD_REQ command Cascade Level 3 */ -}; - -/* -****************************************************************************** -* GLOBAL MACROS -****************************************************************************** -*/ -#define rfalNfcaSelPar(nBy, nbi) \ - (uint8_t)( \ - (((nBy) << 4U) & 0xF0U) | \ - ((nbi)&0x0FU)) /*!< Calculates SEL_PAR with the bytes/bits to be sent */ -#define rfalNfcaCLn2SELCMD(cl) \ - (uint8_t)( \ - (uint8_t)(RFAL_NFCA_CMD_SEL_CL1) + \ - (2U * (cl))) /*!< Calculates SEL_CMD with the given cascade level */ -#define rfalNfcaNfcidLen2CL(len) \ - ((len) / 5U) /*!< Calculates cascade level by the NFCID length */ -#define rfalNfcaRunBlocking(e, fn) \ - do { \ - (e) = (fn); \ - rfalWorker(); \ - } while((e) == ERR_BUSY) /*!< Macro used for the blocking methods */ - -/* -****************************************************************************** -* GLOBAL TYPES -****************************************************************************** -*/ - -/*! Colission Resolution states */ -typedef enum { - RFAL_NFCA_CR_IDLE, /*!< IDLE state */ - RFAL_NFCA_CR_CL, /*!< New Cascading Level state */ - RFAL_NFCA_CR_SDD, /*!< Perform anticollsion state */ - RFAL_NFCA_CR_SEL, /*!< Perform CL Selection state */ - RFAL_NFCA_CR_DONE /*!< Collision Resolution done state */ -} colResState; - -/*! Colission Resolution context */ -typedef struct { - uint8_t devLimit; /*!< Device limit to be used */ - rfalComplianceMode compMode; /*!< Compliancy mode to be used */ - rfalNfcaListenDevice* - nfcaDevList; /*!< Location of the device list */ - uint8_t* devCnt; /*!< Location of the device counter */ - bool collPending; /*!< Collision pending flag */ - - bool* collPend; /*!< Location of collision pending flag (Single CR) */ - rfalNfcaSelReq selReq; /*!< SelReqused during anticollision (Single CR) */ - rfalNfcaSelRes* selRes; /*!< Location to place of the SEL_RES(SAK) (Single CR) */ - uint8_t* nfcId1; /*!< Location to place the NFCID1 (Single CR) */ - uint8_t* nfcId1Len; /*!< Location to place the NFCID1 length (Single CR) */ - uint8_t cascadeLv; /*!< Current Cascading Level (Single CR) */ - colResState state; /*!< Single Collision Resolution state (Single CR) */ - uint8_t bytesTxRx; /*!< TxRx bytes used during anticollision loop (Single CR) */ - uint8_t bitsTxRx; /*!< TxRx bits used during anticollision loop (Single CR) */ - uint16_t rxLen; - uint32_t tmrFDT; /*!< FDT timer used between SED_REQs (Single CR) */ - uint8_t retries; /*!< Retries to be performed upon a timeout error (Single CR)*/ - uint8_t backtrackCnt; /*!< Backtrack retries (Single CR) */ - bool doBacktrack; /*!< Backtrack flag (Single CR) */ -} colResParams; - -/*! RFAL NFC-A instance */ -typedef struct { - colResParams CR; /*!< Collision Resolution context */ -} rfalNfca; - -/*! SLP_REQ (HLTA) format Digital 1.1 6.9.1 & Table 20 */ -typedef struct { - uint8_t frame[RFAL_NFCA_SLP_REQ_LEN]; /*!< SLP: 0x50 0x00 */ -} rfalNfcaSlpReq; - -/* -****************************************************************************** -* LOCAL VARIABLES -****************************************************************************** -*/ -static rfalNfca gNfca; /*!< RFAL NFC-A instance */ - -/* -****************************************************************************** -* LOCAL FUNCTION PROTOTYPES -****************************************************************************** -*/ -static uint8_t rfalNfcaCalculateBcc(const uint8_t* buf, uint8_t bufLen); -static ReturnCode rfalNfcaPollerStartSingleCollisionResolution( - uint8_t devLimit, - bool* collPending, - rfalNfcaSelRes* selRes, - uint8_t* nfcId1, - uint8_t* nfcId1Len); -static ReturnCode rfalNfcaPollerGetSingleCollisionResolutionStatus(void); - -/* - ****************************************************************************** - * LOCAL FUNCTIONS - ****************************************************************************** - */ - -static uint8_t rfalNfcaCalculateBcc(const uint8_t* buf, uint8_t bufLen) { - uint8_t i; - uint8_t BCC; - - BCC = 0; - - /* BCC is XOR over first 4 bytes of the SDD_RES Digital 1.1 6.7.2 */ - for(i = 0; i < bufLen; i++) { - BCC ^= buf[i]; - } - - return BCC; -} - -/*******************************************************************************/ -static ReturnCode rfalNfcaPollerStartSingleCollisionResolution( - uint8_t devLimit, - bool* collPending, - rfalNfcaSelRes* selRes, - uint8_t* nfcId1, - uint8_t* nfcId1Len) { - /* Check parameters */ - if((collPending == NULL) || (selRes == NULL) || (nfcId1 == NULL) || (nfcId1Len == NULL)) { - return ERR_PARAM; - } - - /* Initialize output parameters */ - *collPending = false; /* Activity 1.1 9.3.4.6 */ - *nfcId1Len = 0; - ST_MEMSET(nfcId1, 0x00, RFAL_NFCA_CASCADE_3_UID_LEN); - - /* Save parameters */ - gNfca.CR.devLimit = devLimit; - gNfca.CR.collPend = collPending; - gNfca.CR.selRes = selRes; - gNfca.CR.nfcId1 = nfcId1; - gNfca.CR.nfcId1Len = nfcId1Len; - - platformTimerDestroy(gNfca.CR.tmrFDT); - gNfca.CR.tmrFDT = 0U; - gNfca.CR.retries = RFAL_NFCA_N_RETRANS; - gNfca.CR.cascadeLv = (uint8_t)RFAL_NFCA_SEL_CASCADE_L1; - gNfca.CR.state = RFAL_NFCA_CR_CL; - - gNfca.CR.doBacktrack = false; - gNfca.CR.backtrackCnt = 3U; - - return ERR_NONE; -} - -/*******************************************************************************/ -static ReturnCode rfalNfcaPollerGetSingleCollisionResolutionStatus(void) { - ReturnCode ret; - uint8_t collBit = 1U; /* standards mandate or recommend collision bit to be set to One. */ - - /* Check if FDT timer is still running */ - if(!platformTimerIsExpired(gNfca.CR.tmrFDT) && (gNfca.CR.tmrFDT != 0U)) { - return ERR_BUSY; - } - - /*******************************************************************************/ - /* Go through all Cascade Levels Activity 1.1 9.3.4 */ - if(gNfca.CR.cascadeLv > (uint8_t)RFAL_NFCA_SEL_CASCADE_L3) { - return ERR_INTERNAL; - } - - switch(gNfca.CR.state) { - /*******************************************************************************/ - case RFAL_NFCA_CR_CL: - - /* Initialize the SDD_REQ to send for the new cascade level */ - ST_MEMSET((uint8_t*)&gNfca.CR.selReq, 0x00, sizeof(rfalNfcaSelReq)); - - gNfca.CR.bytesTxRx = RFAL_NFCA_SDD_REQ_LEN; - gNfca.CR.bitsTxRx = 0U; - gNfca.CR.state = RFAL_NFCA_CR_SDD; - - /* fall through */ - - /*******************************************************************************/ - case RFAL_NFCA_CR_SDD: /* PRQA S 2003 # MISRA 16.3 - Intentional fall through */ - - /* Calculate SEL_CMD and SEL_PAR with the bytes/bits to be sent */ - gNfca.CR.selReq.selCmd = rfalNfcaCLn2SELCMD(gNfca.CR.cascadeLv); - gNfca.CR.selReq.selPar = rfalNfcaSelPar(gNfca.CR.bytesTxRx, gNfca.CR.bitsTxRx); - - /* Send SDD_REQ (Anticollision frame) */ - ret = rfalISO14443ATransceiveAnticollisionFrame( - (uint8_t*)&gNfca.CR.selReq, - &gNfca.CR.bytesTxRx, - &gNfca.CR.bitsTxRx, - &gNfca.CR.rxLen, - RFAL_NFCA_FDTMIN); - - /* Retry upon timeout EMVCo 2.6 9.6.1.3 */ - if((ret == ERR_TIMEOUT) && (gNfca.CR.devLimit == 0U) && (gNfca.CR.retries != 0U)) { - gNfca.CR.retries--; - platformTimerDestroy(gNfca.CR.tmrFDT); - gNfca.CR.tmrFDT = platformTimerCreate(RFAL_NFCA_T_RETRANS); - break; - } - - /* Covert rxLen into bytes */ - gNfca.CR.rxLen = rfalConvBitsToBytes(gNfca.CR.rxLen); - - if((ret == ERR_TIMEOUT) && (gNfca.CR.backtrackCnt != 0U) && (!gNfca.CR.doBacktrack) && - !((RFAL_NFCA_SDD_REQ_LEN == gNfca.CR.bytesTxRx) && (0U == gNfca.CR.bitsTxRx))) { - /* In multiple card scenarios it may always happen that some - * collisions of a weaker tag go unnoticed. If then a later - * collision is recognized and the strong tag has a 0 at the - * collision position then no tag will respond. Catch this - * corner case and then try with the bit being sent as zero. */ - rfalNfcaSensRes sensRes; - ret = ERR_RF_COLLISION; - rfalNfcaPollerCheckPresence(RFAL_14443A_SHORTFRAME_CMD_REQA, &sensRes); - /* Algorithm below does a post-increment, decrement to go back to current position */ - if(0U == gNfca.CR.bitsTxRx) { - gNfca.CR.bitsTxRx = 7; - gNfca.CR.bytesTxRx--; - } else { - gNfca.CR.bitsTxRx--; - } - collBit = - (uint8_t)(((uint8_t*)&gNfca.CR.selReq)[gNfca.CR.bytesTxRx] & (1U << gNfca.CR.bitsTxRx)); - collBit = (uint8_t)((0U == collBit) ? 1U : 0U); // invert the collision bit - gNfca.CR.doBacktrack = true; - gNfca.CR.backtrackCnt--; - } else { - gNfca.CR.doBacktrack = false; - } - - if(ret == ERR_RF_COLLISION) { - /* Check received length */ - if((gNfca.CR.bytesTxRx + ((gNfca.CR.bitsTxRx != 0U) ? 1U : 0U)) > - (RFAL_NFCA_SDD_RES_LEN + RFAL_NFCA_SDD_REQ_LEN)) { - return ERR_PROTO; - } - - if(((gNfca.CR.bytesTxRx + ((gNfca.CR.bitsTxRx != 0U) ? 1U : 0U)) > - (RFAL_NFCA_CASCADE_1_UID_LEN + RFAL_NFCA_SDD_REQ_LEN)) && - (gNfca.CR.backtrackCnt != 0U)) { /* Collision in BCC: Anticollide only UID part */ - gNfca.CR.backtrackCnt--; - gNfca.CR.bytesTxRx = RFAL_NFCA_CASCADE_1_UID_LEN + RFAL_NFCA_SDD_REQ_LEN - 1U; - gNfca.CR.bitsTxRx = 7; - collBit = - (uint8_t)(((uint8_t*)&gNfca.CR.selReq)[gNfca.CR.bytesTxRx] & (1U << gNfca.CR.bitsTxRx)); /* Not a real collision, extract the actual bit for the subsequent code */ - } - - if((gNfca.CR.devLimit == 0U) && !(*gNfca.CR.collPend)) { - /* Activity 1.0 & 1.1 9.3.4.12: If CON_DEVICES_LIMIT has a value of 0, then - * NFC Forum Device is configured to perform collision detection only */ - *gNfca.CR.collPend = true; - return ERR_IGNORE; - } - - *gNfca.CR.collPend = true; - - /* Set and select the collision bit, with the number of bytes/bits successfully TxRx */ - if(collBit != 0U) { - ((uint8_t*)&gNfca.CR.selReq)[gNfca.CR.bytesTxRx] = - (uint8_t)(((uint8_t*)&gNfca.CR.selReq)[gNfca.CR.bytesTxRx] | (1U << gNfca.CR.bitsTxRx)); /* MISRA 10.3 */ - } else { - ((uint8_t*)&gNfca.CR.selReq)[gNfca.CR.bytesTxRx] = - (uint8_t)(((uint8_t*)&gNfca.CR.selReq)[gNfca.CR.bytesTxRx] & ~(1U << gNfca.CR.bitsTxRx)); /* MISRA 10.3 */ - } - - gNfca.CR.bitsTxRx++; - - /* Check if number of bits form a byte */ - if(gNfca.CR.bitsTxRx == RFAL_BITS_IN_BYTE) { - gNfca.CR.bitsTxRx = 0; - gNfca.CR.bytesTxRx++; - } - break; - } - - /*******************************************************************************/ - /* Check if Collision loop has failed */ - if(ret != ERR_NONE) { - return ret; - } - - /* If collisions are to be reported check whether the response is complete */ - if((gNfca.CR.devLimit == 0U) && (gNfca.CR.rxLen != sizeof(rfalNfcaSddRes))) { - return ERR_PROTO; - } - - /* Check if the received BCC match */ - if(gNfca.CR.selReq.bcc != - rfalNfcaCalculateBcc(gNfca.CR.selReq.nfcid1, RFAL_NFCA_CASCADE_1_UID_LEN)) { - return ERR_PROTO; - } - - /*******************************************************************************/ - /* Anticollision OK, Select this Cascade Level */ - gNfca.CR.selReq.selPar = RFAL_NFCA_SEL_SELPAR; - - gNfca.CR.retries = RFAL_NFCA_N_RETRANS; - gNfca.CR.state = RFAL_NFCA_CR_SEL; - break; - - /*******************************************************************************/ - case RFAL_NFCA_CR_SEL: - - /* Send SEL_REQ (Select command) - Retry upon timeout EMVCo 2.6 9.6.1.3 */ - ret = rfalTransceiveBlockingTxRx( - (uint8_t*)&gNfca.CR.selReq, - sizeof(rfalNfcaSelReq), - (uint8_t*)gNfca.CR.selRes, - sizeof(rfalNfcaSelRes), - &gNfca.CR.rxLen, - RFAL_TXRX_FLAGS_DEFAULT, - RFAL_NFCA_FDTMIN); - - /* Retry upon timeout EMVCo 2.6 9.6.1.3 */ - if((ret == ERR_TIMEOUT) && (gNfca.CR.devLimit == 0U) && (gNfca.CR.retries != 0U)) { - gNfca.CR.retries--; - platformTimerDestroy(gNfca.CR.tmrFDT); - gNfca.CR.tmrFDT = platformTimerCreate(RFAL_NFCA_T_RETRANS); - break; - } - - if(ret != ERR_NONE) { - return ret; - } - - /* Ensure proper response length */ - if(gNfca.CR.rxLen != sizeof(rfalNfcaSelRes)) { - return ERR_PROTO; - } - - /*******************************************************************************/ - /* Check cascade byte, if cascade tag then go next cascade level */ - if(*gNfca.CR.selReq.nfcid1 == RFAL_NFCA_SDD_CT) { - /* Cascade Tag present, store nfcid1 bytes (excluding cascade tag) and continue for next CL */ - ST_MEMCPY( - &gNfca.CR.nfcId1[*gNfca.CR.nfcId1Len], - &((uint8_t*)&gNfca.CR.selReq.nfcid1)[RFAL_NFCA_SDD_CT_LEN], - (RFAL_NFCA_CASCADE_1_UID_LEN - RFAL_NFCA_SDD_CT_LEN)); - *gNfca.CR.nfcId1Len += (RFAL_NFCA_CASCADE_1_UID_LEN - RFAL_NFCA_SDD_CT_LEN); - - /* Go to next cascade level */ - gNfca.CR.state = RFAL_NFCA_CR_CL; - gNfca.CR.cascadeLv++; - } else { - /* UID Selection complete, Stop Cascade Level loop */ - ST_MEMCPY( - &gNfca.CR.nfcId1[*gNfca.CR.nfcId1Len], - (uint8_t*)&gNfca.CR.selReq.nfcid1, - RFAL_NFCA_CASCADE_1_UID_LEN); - *gNfca.CR.nfcId1Len += RFAL_NFCA_CASCADE_1_UID_LEN; - - gNfca.CR.state = RFAL_NFCA_CR_DONE; - break; /* Only flag operation complete on the next execution */ - } - break; - - /*******************************************************************************/ - case RFAL_NFCA_CR_DONE: - return ERR_NONE; - - /*******************************************************************************/ - default: - return ERR_WRONG_STATE; - } - return ERR_BUSY; -} - -/* -****************************************************************************** -* GLOBAL FUNCTIONS -****************************************************************************** -*/ - -/*******************************************************************************/ -ReturnCode rfalNfcaPollerInitialize(void) { - ReturnCode ret; - - EXIT_ON_ERR(ret, rfalSetMode(RFAL_MODE_POLL_NFCA, RFAL_BR_106, RFAL_BR_106)); - rfalSetErrorHandling(RFAL_ERRORHANDLING_NFC); - - rfalSetGT(RFAL_GT_NFCA); - rfalSetFDTListen(RFAL_FDT_LISTEN_NFCA_POLLER); - rfalSetFDTPoll(RFAL_FDT_POLL_NFCA_POLLER); - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalNfcaPollerCheckPresence(rfal14443AShortFrameCmd cmd, rfalNfcaSensRes* sensRes) { - ReturnCode ret; - uint16_t rcvLen; - - /* Digital 1.1 6.10.1.3 For Commands ALL_REQ, SENS_REQ, SDD_REQ, and SEL_REQ, the NFC Forum Device * - * MUST treat receipt of a Listen Frame at a time after FDT(Listen, min) as a Timeour Error */ - - ret = rfalISO14443ATransceiveShortFrame( - cmd, - (uint8_t*)sensRes, - (uint8_t)rfalConvBytesToBits(sizeof(rfalNfcaSensRes)), - &rcvLen, - RFAL_NFCA_FDTMIN); - if((ret == ERR_RF_COLLISION) || (ret == ERR_CRC) || (ret == ERR_NOMEM) || - (ret == ERR_FRAMING) || (ret == ERR_PAR)) { - ret = ERR_NONE; - } - - return ret; -} - -/*******************************************************************************/ -ReturnCode - rfalNfcaPollerTechnologyDetection(rfalComplianceMode compMode, rfalNfcaSensRes* sensRes) { - ReturnCode ret; - - EXIT_ON_ERR( - ret, - rfalNfcaPollerCheckPresence( - ((compMode == RFAL_COMPLIANCE_MODE_EMV) ? RFAL_14443A_SHORTFRAME_CMD_WUPA : - RFAL_14443A_SHORTFRAME_CMD_REQA), - sensRes)); - - /* Send SLP_REQ as Activity 1.1 9.2.3.6 and EMVCo 2.6 9.2.1.3 */ - if(compMode != RFAL_COMPLIANCE_MODE_ISO) { - rfalNfcaPollerSleep(); - } - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalNfcaPollerSingleCollisionResolution( - uint8_t devLimit, - bool* collPending, - rfalNfcaSelRes* selRes, - uint8_t* nfcId1, - uint8_t* nfcId1Len) { - ReturnCode ret; - - EXIT_ON_ERR( - ret, - rfalNfcaPollerStartSingleCollisionResolution( - devLimit, collPending, selRes, nfcId1, nfcId1Len)); - rfalNfcaRunBlocking(ret, rfalNfcaPollerGetSingleCollisionResolutionStatus()); - - return ret; -} - -/*******************************************************************************/ -ReturnCode rfalNfcaPollerStartFullCollisionResolution( - rfalComplianceMode compMode, - uint8_t devLimit, - rfalNfcaListenDevice* nfcaDevList, - uint8_t* devCnt) { - ReturnCode ret; - rfalNfcaSensRes sensRes; - uint16_t rcvLen; - - if((nfcaDevList == NULL) || (devCnt == NULL)) { - return ERR_PARAM; - } - - *devCnt = 0; - ret = ERR_NONE; - - /*******************************************************************************/ - /* Send ALL_REQ before Anticollision if a Sleep was sent before Activity 1.1 9.3.4.1 and EMVco 2.6 9.3.2.1 */ - if(compMode != RFAL_COMPLIANCE_MODE_ISO) { - ret = rfalISO14443ATransceiveShortFrame( - RFAL_14443A_SHORTFRAME_CMD_WUPA, - (uint8_t*)&nfcaDevList->sensRes, - (uint8_t)rfalConvBytesToBits(sizeof(rfalNfcaSensRes)), - &rcvLen, - RFAL_NFCA_FDTMIN); - if(ret != ERR_NONE) { - if((compMode == RFAL_COMPLIANCE_MODE_EMV) || - ((ret != ERR_RF_COLLISION) && (ret != ERR_CRC) && (ret != ERR_FRAMING) && - (ret != ERR_PAR))) { - return ret; - } - } - - /* Check proper SENS_RES/ATQA size */ - if((ret == ERR_NONE) && (rfalConvBytesToBits(sizeof(rfalNfcaSensRes)) != rcvLen)) { - return ERR_PROTO; - } - } - - /*******************************************************************************/ - /* Store the SENS_RES from Technology Detection or from WUPA */ - sensRes = nfcaDevList->sensRes; - - if(devLimit > 0U) /* MISRA 21.18 */ - { - ST_MEMSET(nfcaDevList, 0x00, (sizeof(rfalNfcaListenDevice) * devLimit)); - } - - /* Restore the prev SENS_RES, assuming that the SENS_RES received is from first device - * When only one device is detected it's not woken up then we'll have no SENS_RES (ATQA) */ - nfcaDevList->sensRes = sensRes; - - /* Save parameters */ - gNfca.CR.devCnt = devCnt; - gNfca.CR.devLimit = devLimit; - gNfca.CR.nfcaDevList = nfcaDevList; - gNfca.CR.compMode = compMode; - -#if RFAL_FEATURE_T1T - /*******************************************************************************/ - /* Only check for T1T if previous SENS_RES was received without a transmission * - * error. When collisions occur bits in the SENS_RES may look like a T1T */ - /* If T1T Anticollision is not supported Activity 1.1 9.3.4.3 */ - if(rfalNfcaIsSensResT1T(&nfcaDevList->sensRes) && (devLimit != 0U) && (ret == ERR_NONE) && - (compMode != RFAL_COMPLIANCE_MODE_EMV)) { - /* RID_REQ shall be performed Activity 1.1 9.3.4.24 */ - rfalT1TPollerInitialize(); - EXIT_ON_ERR(ret, rfalT1TPollerRid(&nfcaDevList->ridRes)); - - *devCnt = 1U; - nfcaDevList->isSleep = false; - nfcaDevList->type = RFAL_NFCA_T1T; - nfcaDevList->nfcId1Len = RFAL_NFCA_CASCADE_1_UID_LEN; - ST_MEMCPY(&nfcaDevList->nfcId1, &nfcaDevList->ridRes.uid, RFAL_NFCA_CASCADE_1_UID_LEN); - - return ERR_NONE; - } -#endif /* RFAL_FEATURE_T1T */ - - return rfalNfcaPollerStartSingleCollisionResolution( - devLimit, - &gNfca.CR.collPending, - &nfcaDevList->selRes, - (uint8_t*)&nfcaDevList->nfcId1, - &nfcaDevList->nfcId1Len); -} - -/*******************************************************************************/ -ReturnCode rfalNfcaPollerGetFullCollisionResolutionStatus(void) { - ReturnCode ret; - uint8_t newDevType; - - if((gNfca.CR.nfcaDevList == NULL) || (gNfca.CR.devCnt == NULL)) { - return ERR_WRONG_STATE; - } - - /*******************************************************************************/ - /* Check whether a T1T has already been detected */ - if(rfalNfcaIsSensResT1T(&gNfca.CR.nfcaDevList->sensRes) && - (gNfca.CR.nfcaDevList->type == RFAL_NFCA_T1T)) { - /* T1T doesn't support Anticollision */ - return ERR_NONE; - } - - /*******************************************************************************/ - EXIT_ON_ERR(ret, rfalNfcaPollerGetSingleCollisionResolutionStatus()); - - /* Assign Listen Device */ - newDevType = ((uint8_t)gNfca.CR.nfcaDevList[*gNfca.CR.devCnt].selRes.sak) & - RFAL_NFCA_SEL_RES_CONF_MASK; /* MISRA 10.8 */ - /* PRQA S 4342 1 # MISRA 10.5 - Guaranteed that no invalid enum values are created: see guard_eq_RFAL_NFCA_T2T, .... */ - gNfca.CR.nfcaDevList[*gNfca.CR.devCnt].type = (rfalNfcaListenDeviceType)newDevType; - gNfca.CR.nfcaDevList[*gNfca.CR.devCnt].isSleep = false; - (*gNfca.CR.devCnt)++; - - /* If a collision was detected and device counter is lower than limit Activity 1.1 9.3.4.21 */ - if((*gNfca.CR.devCnt < gNfca.CR.devLimit) && (gNfca.CR.collPending)) { - /* Put this device to Sleep Activity 1.1 9.3.4.22 */ - rfalNfcaPollerSleep(); - gNfca.CR.nfcaDevList[(*gNfca.CR.devCnt - 1U)].isSleep = true; - - /* Send a new SENS_REQ to check for other cards Activity 1.1 9.3.4.23 */ - ret = rfalNfcaPollerCheckPresence( - RFAL_14443A_SHORTFRAME_CMD_REQA, &gNfca.CR.nfcaDevList[*gNfca.CR.devCnt].sensRes); - if(ret == ERR_TIMEOUT) { - /* No more devices found, exit */ - gNfca.CR.collPending = false; - } else { - /* Another device found, continue loop */ - gNfca.CR.collPending = true; - } - } else { - /* Exit loop */ - gNfca.CR.collPending = false; - } - - /*******************************************************************************/ - /* Check if collision resolution shall continue */ - if((*gNfca.CR.devCnt < gNfca.CR.devLimit) && (gNfca.CR.collPending)) { - EXIT_ON_ERR( - ret, - rfalNfcaPollerStartSingleCollisionResolution( - gNfca.CR.devLimit, - &gNfca.CR.collPending, - &gNfca.CR.nfcaDevList[*gNfca.CR.devCnt].selRes, - (uint8_t*)&gNfca.CR.nfcaDevList[*gNfca.CR.devCnt].nfcId1, - &gNfca.CR.nfcaDevList[*gNfca.CR.devCnt].nfcId1Len)); - - return ERR_BUSY; - } - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalNfcaPollerFullCollisionResolution( - rfalComplianceMode compMode, - uint8_t devLimit, - rfalNfcaListenDevice* nfcaDevList, - uint8_t* devCnt) { - ReturnCode ret; - - EXIT_ON_ERR( - ret, rfalNfcaPollerStartFullCollisionResolution(compMode, devLimit, nfcaDevList, devCnt)); - rfalNfcaRunBlocking(ret, rfalNfcaPollerGetFullCollisionResolutionStatus()); - - return ret; -} - -ReturnCode rfalNfcaPollerSleepFullCollisionResolution( - uint8_t devLimit, - rfalNfcaListenDevice* nfcaDevList, - uint8_t* devCnt) { - bool firstRound; - uint8_t tmpDevCnt; - ReturnCode ret; - - if((nfcaDevList == NULL) || (devCnt == NULL)) { - return ERR_PARAM; - } - - /* Only use ALL_REQ (WUPA) on the first round */ - firstRound = true; - *devCnt = 0; - - /* Perform collision resolution until no new device is found */ - do { - tmpDevCnt = 0; - ret = rfalNfcaPollerFullCollisionResolution( - (firstRound ? RFAL_COMPLIANCE_MODE_NFC : RFAL_COMPLIANCE_MODE_ISO), - (devLimit - *devCnt), - &nfcaDevList[*devCnt], - &tmpDevCnt); - - if((ret == ERR_NONE) && (tmpDevCnt > 0U)) { - *devCnt += tmpDevCnt; - - /* Check whether to seacrh for more devices */ - if(*devCnt < devLimit) { - /* Set last found device to sleep (all others are slept already) */ - rfalNfcaPollerSleep(); - nfcaDevList[((*devCnt) - 1U)].isSleep = true; - - /* Check if any other device is present */ - ret = rfalNfcaPollerCheckPresence( - RFAL_14443A_SHORTFRAME_CMD_REQA, &nfcaDevList[*devCnt].sensRes); - if(ret == ERR_NONE) { - firstRound = false; - continue; - } - } - } - break; - } while(true); - - return ((*devCnt > 0U) ? ERR_NONE : ret); -} - -/*******************************************************************************/ -ReturnCode rfalNfcaPollerSelect(const uint8_t* nfcid1, uint8_t nfcidLen, rfalNfcaSelRes* selRes) { - uint8_t i; - uint8_t cl; - uint8_t nfcidOffset; - uint16_t rxLen; - ReturnCode ret; - rfalNfcaSelReq selReq; - - if((nfcid1 == NULL) || (nfcidLen > RFAL_NFCA_CASCADE_3_UID_LEN) || (selRes == NULL)) { - return ERR_PARAM; - } - - /* Calculate Cascate Level */ - cl = rfalNfcaNfcidLen2CL(nfcidLen); - nfcidOffset = 0; - - /*******************************************************************************/ - /* Go through all Cascade Levels Activity 1.1 9.4.4 */ - for(i = RFAL_NFCA_SEL_CASCADE_L1; i <= cl; i++) { - /* Assign SEL_CMD according to the CLn and SEL_PAR*/ - selReq.selCmd = rfalNfcaCLn2SELCMD(i); - selReq.selPar = RFAL_NFCA_SEL_SELPAR; - - /* Compute NFCID/Data on the SEL_REQ command Digital 1.1 Table 18 */ - if(cl != i) { - *selReq.nfcid1 = RFAL_NFCA_SDD_CT; - ST_MEMCPY( - &selReq.nfcid1[RFAL_NFCA_SDD_CT_LEN], - &nfcid1[nfcidOffset], - (RFAL_NFCA_CASCADE_1_UID_LEN - RFAL_NFCA_SDD_CT_LEN)); - nfcidOffset += (RFAL_NFCA_CASCADE_1_UID_LEN - RFAL_NFCA_SDD_CT_LEN); - } else { - ST_MEMCPY(selReq.nfcid1, &nfcid1[nfcidOffset], RFAL_NFCA_CASCADE_1_UID_LEN); - } - - /* Calculate nfcid's BCC */ - selReq.bcc = rfalNfcaCalculateBcc((uint8_t*)&selReq.nfcid1, sizeof(selReq.nfcid1)); - - /*******************************************************************************/ - /* Send SEL_REQ */ - EXIT_ON_ERR( - ret, - rfalTransceiveBlockingTxRx( - (uint8_t*)&selReq, - sizeof(rfalNfcaSelReq), - (uint8_t*)selRes, - sizeof(rfalNfcaSelRes), - &rxLen, - RFAL_TXRX_FLAGS_DEFAULT, - RFAL_NFCA_FDTMIN)); - - /* Ensure proper response length */ - if(rxLen != sizeof(rfalNfcaSelRes)) { - return ERR_PROTO; - } - } - - /* REMARK: Could check if NFCID1 is complete */ - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalNfcaPollerSleep(void) { - rfalNfcaSlpReq slpReq; - uint8_t rxBuf; /* dummy buffer, just to perform Rx */ - - slpReq.frame[RFAL_NFCA_SLP_CMD_POS] = RFAL_NFCA_SLP_CMD; - slpReq.frame[RFAL_NFCA_SLP_BYTE2_POS] = RFAL_NFCA_SLP_BYTE2; - - rfalTransceiveBlockingTxRx( - (uint8_t*)&slpReq, - sizeof(rfalNfcaSlpReq), - &rxBuf, - sizeof(rxBuf), - NULL, - RFAL_TXRX_FLAGS_DEFAULT, - RFAL_NFCA_SLP_FWT); - - /* ISO14443-3 6.4.3 HLTA - If PICC responds with any modulation during 1 ms this response shall be interpreted as not acknowledge - Digital 2.0 6.9.2.1 & EMVCo 3.0 5.6.2.1 - consider the HLTA command always acknowledged - No check to be compliant with NFC and EMVCo, and to improve interoprability (Kovio RFID Tag) - */ - - return ERR_NONE; -} - -/*******************************************************************************/ -bool rfalNfcaListenerIsSleepReq(const uint8_t* buf, uint16_t bufLen) { - /* Check if length and payload match */ - if((bufLen != sizeof(rfalNfcaSlpReq)) || (buf[RFAL_NFCA_SLP_CMD_POS] != RFAL_NFCA_SLP_CMD) || - (buf[RFAL_NFCA_SLP_BYTE2_POS] != RFAL_NFCA_SLP_BYTE2)) { - return false; - } - - return true; -} - -/* If the guards here don't compile then the code above cannot work anymore. */ -extern uint8_t guard_eq_RFAL_NFCA_T2T - [((RFAL_NFCA_SEL_RES_CONF_MASK & (uint8_t)RFAL_NFCA_T2T) == (uint8_t)RFAL_NFCA_T2T) ? 1 : (-1)]; -extern uint8_t guard_eq_RFAL_NFCA_T4T - [((RFAL_NFCA_SEL_RES_CONF_MASK & (uint8_t)RFAL_NFCA_T4T) == (uint8_t)RFAL_NFCA_T4T) ? 1 : (-1)]; -extern uint8_t guard_eq_RFAL_NFCA_NFCDEP - [((RFAL_NFCA_SEL_RES_CONF_MASK & (uint8_t)RFAL_NFCA_NFCDEP) == (uint8_t)RFAL_NFCA_NFCDEP) ? - 1 : - (-1)]; -extern uint8_t guard_eq_RFAL_NFCA_T4T_NFCDEP - [((RFAL_NFCA_SEL_RES_CONF_MASK & (uint8_t)RFAL_NFCA_T4T_NFCDEP) == - (uint8_t)RFAL_NFCA_T4T_NFCDEP) ? - 1 : - (-1)]; -#endif /* RFAL_FEATURE_NFCA */ diff --git a/lib/ST25RFAL002/source/rfal_nfcb.c b/lib/ST25RFAL002/source/rfal_nfcb.c deleted file mode 100644 index 841b2554aad..00000000000 --- a/lib/ST25RFAL002/source/rfal_nfcb.c +++ /dev/null @@ -1,519 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

    © COPYRIGHT 2020 STMicroelectronics

    - * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R391x firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file rfal_nfcb.c - * - * \author Gustavo Patricio - * - * \brief Implementation of NFC-B (ISO14443B) helpers - * - */ - -/* - ****************************************************************************** - * INCLUDES - ****************************************************************************** - */ -#include "rfal_nfcb.h" -#include "utils.h" - -/* - ****************************************************************************** - * ENABLE SWITCH - ****************************************************************************** - */ - -#ifndef RFAL_FEATURE_NFCB -#define RFAL_FEATURE_NFCB false /* NFC-B module configuration missing. Disabled by default */ -#endif - -#if RFAL_FEATURE_NFCB - -/* - ****************************************************************************** - * GLOBAL DEFINES - ****************************************************************************** - */ - -#define RFAL_NFCB_SENSB_REQ_EXT_SENSB_RES_SUPPORTED \ - 0x10U /*!< Bit mask for Extended SensB Response support in SENSB_REQ */ -#define RFAL_NFCB_SENSB_RES_PROT_TYPE_RFU \ - 0x08U /*!< Bit mask for Protocol Type RFU in SENSB_RES */ -#define RFAL_NFCB_SLOT_MARKER_SC_SHIFT \ - 4U /*!< Slot Code position on SLOT_MARKER APn */ - -#define RFAL_NFCB_SLOTMARKER_SLOTCODE_MIN \ - 1U /*!< SLOT_MARKER Slot Code minimum Digital 1.1 Table 37 */ -#define RFAL_NFCB_SLOTMARKER_SLOTCODE_MAX \ - 16U /*!< SLOT_MARKER Slot Code maximum Digital 1.1 Table 37 */ - -#define RFAL_NFCB_ACTIVATION_FWT \ - (RFAL_NFCB_FWTSENSB + RFAL_NFCB_DTPOLL_20) /*!< FWT(SENSB) + dTbPoll Digital 2.0 7.9.1.3 */ - -/*! Advanced and Extended bit mask in Parameter of SENSB_REQ */ -#define RFAL_NFCB_SENSB_REQ_PARAM \ - (RFAL_NFCB_SENSB_REQ_ADV_FEATURE | RFAL_NFCB_SENSB_REQ_EXT_SENSB_RES_SUPPORTED) - -/*! NFC-B commands definition */ -enum { - RFAL_NFCB_CMD_SENSB_REQ = 0x05, /*!< SENSB_REQ (REQB) & SLOT_MARKER Digital 1.1 Table 24 */ - RFAL_NFCB_CMD_SENSB_RES = 0x50, /*!< SENSB_RES (ATQB) & SLOT_MARKER Digital 1.1 Table 27 */ - RFAL_NFCB_CMD_SLPB_REQ = 0x50, /*!< SLPB_REQ (HLTB command) Digital 1.1 Table 38 */ - RFAL_NFCB_CMD_SLPB_RES = 0x00 /*!< SLPB_RES (HLTB Answer) Digital 1.1 Table 39 */ -}; - -/* - ****************************************************************************** - * GLOBAL MACROS - ****************************************************************************** - */ - -#define rfalNfcbNI2NumberOfSlots(ni) \ - (uint8_t)(1U << (ni)) /*!< Converts the Number of slots Identifier to slot number */ - -/* -****************************************************************************** -* GLOBAL TYPES -****************************************************************************** -*/ - -/*! ALLB_REQ (WUPB) and SENSB_REQ (REQB) Command Format Digital 1.1 7.6.1 */ -typedef struct { - uint8_t cmd; /*!< xxxxB_REQ: 05h */ - uint8_t AFI; /*!< NFC Identifier */ - uint8_t PARAM; /*!< Application Data */ -} rfalNfcbSensbReq; - -/*! SLOT_MARKER Command format Digital 1.1 7.7.1 */ -typedef struct { - uint8_t APn; /*!< Slot number 2..16 | 0101b */ -} rfalNfcbSlotMarker; - -/*! SLPB_REQ (HLTB) Command Format Digital 1.1 7.8.1 */ -typedef struct { - uint8_t cmd; /*!< SLPB_REQ: 50h */ - uint8_t nfcid0[RFAL_NFCB_NFCID0_LEN]; /*!< NFC Identifier (PUPI)*/ -} rfalNfcbSlpbReq; - -/*! SLPB_RES (HLTB) Response Format Digital 1.1 7.8.2 */ -typedef struct { - uint8_t cmd; /*!< SLPB_RES: 00h */ -} rfalNfcbSlpbRes; - -/*! RFAL NFC-B instance */ -typedef struct { - uint8_t AFI; /*!< AFI to be used */ - uint8_t PARAM; /*!< PARAM to be used */ -} rfalNfcb; - -/* -****************************************************************************** -* LOCAL FUNCTION PROTOTYPES -****************************************************************************** -*/ -static ReturnCode rfalNfcbCheckSensbRes(const rfalNfcbSensbRes* sensbRes, uint8_t sensbResLen); - -/* -****************************************************************************** -* LOCAL VARIABLES -****************************************************************************** -*/ - -static rfalNfcb gRfalNfcb; /*!< RFAL NFC-B Instance */ - -/* -****************************************************************************** -* LOCAL FUNCTIONS -****************************************************************************** -*/ - -/*******************************************************************************/ -static ReturnCode rfalNfcbCheckSensbRes(const rfalNfcbSensbRes* sensbRes, uint8_t sensbResLen) { - /* Check response length */ - if(((sensbResLen != RFAL_NFCB_SENSB_RES_LEN) && - (sensbResLen != RFAL_NFCB_SENSB_RES_EXT_LEN))) { - return ERR_PROTO; - } - - /* Check SENSB_RES and Protocol Type Digital 1.1 7.6.2.19 */ - if(((sensbRes->protInfo.FsciProType & RFAL_NFCB_SENSB_RES_PROT_TYPE_RFU) != 0U) || - (sensbRes->cmd != (uint8_t)RFAL_NFCB_CMD_SENSB_RES)) { - return ERR_PROTO; - } - return ERR_NONE; -} - -/* -****************************************************************************** -* GLOBAL FUNCTIONS -****************************************************************************** -*/ - -/*******************************************************************************/ -ReturnCode rfalNfcbPollerInitialize(void) { - ReturnCode ret; - - EXIT_ON_ERR(ret, rfalSetMode(RFAL_MODE_POLL_NFCB, RFAL_BR_106, RFAL_BR_106)); - rfalSetErrorHandling(RFAL_ERRORHANDLING_NFC); - - rfalSetGT(RFAL_GT_NFCB); - rfalSetFDTListen(RFAL_FDT_LISTEN_NFCB_POLLER); - rfalSetFDTPoll(RFAL_FDT_POLL_NFCB_POLLER); - - gRfalNfcb.AFI = RFAL_NFCB_AFI; - gRfalNfcb.PARAM = RFAL_NFCB_PARAM; - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalNfcbPollerInitializeWithParams(uint8_t AFI, uint8_t PARAM) { - ReturnCode ret; - - EXIT_ON_ERR(ret, rfalNfcbPollerInitialize()); - - gRfalNfcb.AFI = AFI; - gRfalNfcb.PARAM = (PARAM & RFAL_NFCB_SENSB_REQ_PARAM); - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalNfcbPollerCheckPresence( - rfalNfcbSensCmd cmd, - rfalNfcbSlots slots, - rfalNfcbSensbRes* sensbRes, - uint8_t* sensbResLen) { - uint16_t rxLen; - ReturnCode ret; - rfalNfcbSensbReq sensbReq; - - /* Check if the command requested and given the slot number are valid */ - if(((RFAL_NFCB_SENS_CMD_SENSB_REQ != cmd) && (RFAL_NFCB_SENS_CMD_ALLB_REQ != cmd)) || - (slots > RFAL_NFCB_SLOT_NUM_16) || (sensbRes == NULL) || (sensbResLen == NULL)) { - return ERR_PARAM; - } - - *sensbResLen = 0; - ST_MEMSET(sensbRes, 0x00, sizeof(rfalNfcbSensbRes)); - - /* Compute SENSB_REQ */ - sensbReq.cmd = RFAL_NFCB_CMD_SENSB_REQ; - sensbReq.AFI = gRfalNfcb.AFI; - sensbReq.PARAM = - (((uint8_t)gRfalNfcb.PARAM & RFAL_NFCB_SENSB_REQ_PARAM) | (uint8_t)cmd | (uint8_t)slots); - - /* Send SENSB_REQ and disable AGC to detect collisions */ - ret = rfalTransceiveBlockingTxRx( - (uint8_t*)&sensbReq, - sizeof(rfalNfcbSensbReq), - (uint8_t*)sensbRes, - sizeof(rfalNfcbSensbRes), - &rxLen, - RFAL_TXRX_FLAGS_DEFAULT, - RFAL_NFCB_FWTSENSB); - - *sensbResLen = (uint8_t)rxLen; - - /* Check if a transmission error was detected */ - if((ret == ERR_CRC) || (ret == ERR_FRAMING)) { - /* Invalidate received frame as an error was detected (CollisionResolution checks if valid) */ - *sensbResLen = 0; - return ERR_NONE; - } - - if(ret == ERR_NONE) { - return rfalNfcbCheckSensbRes(sensbRes, *sensbResLen); - } - - return ret; -} - -/*******************************************************************************/ -ReturnCode rfalNfcbPollerSleep(const uint8_t* nfcid0) { - uint16_t rxLen; - ReturnCode ret; - rfalNfcbSlpbReq slpbReq; - rfalNfcbSlpbRes slpbRes; - - if(nfcid0 == NULL) { - return ERR_PARAM; - } - - /* Compute SLPB_REQ */ - slpbReq.cmd = RFAL_NFCB_CMD_SLPB_REQ; - ST_MEMCPY(slpbReq.nfcid0, nfcid0, RFAL_NFCB_NFCID0_LEN); - - EXIT_ON_ERR( - ret, - rfalTransceiveBlockingTxRx( - (uint8_t*)&slpbReq, - sizeof(rfalNfcbSlpbReq), - (uint8_t*)&slpbRes, - sizeof(rfalNfcbSlpbRes), - &rxLen, - RFAL_TXRX_FLAGS_DEFAULT, - RFAL_NFCB_ACTIVATION_FWT)); - - /* Check SLPB_RES */ - if((rxLen != sizeof(rfalNfcbSlpbRes)) || (slpbRes.cmd != (uint8_t)RFAL_NFCB_CMD_SLPB_RES)) { - return ERR_PROTO; - } - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode - rfalNfcbPollerSlotMarker(uint8_t slotCode, rfalNfcbSensbRes* sensbRes, uint8_t* sensbResLen) { - ReturnCode ret; - rfalNfcbSlotMarker slotMarker; - uint16_t rxLen; - - /* Check parameters */ - if((sensbRes == NULL) || (sensbResLen == NULL) || - (slotCode < RFAL_NFCB_SLOTMARKER_SLOTCODE_MIN) || - (slotCode > RFAL_NFCB_SLOTMARKER_SLOTCODE_MAX)) { - return ERR_PARAM; - } - /* Compose and send SLOT_MARKER with disabled AGC to detect collisions */ - slotMarker.APn = - ((slotCode << RFAL_NFCB_SLOT_MARKER_SC_SHIFT) | (uint8_t)RFAL_NFCB_CMD_SENSB_REQ); - - ret = rfalTransceiveBlockingTxRx( - (uint8_t*)&slotMarker, - sizeof(rfalNfcbSlotMarker), - (uint8_t*)sensbRes, - sizeof(rfalNfcbSensbRes), - &rxLen, - RFAL_TXRX_FLAGS_DEFAULT, - RFAL_NFCB_ACTIVATION_FWT); - - *sensbResLen = (uint8_t)rxLen; - - /* Check if a transmission error was detected */ - if((ret == ERR_CRC) || (ret == ERR_FRAMING)) { - return ERR_RF_COLLISION; - } - - if(ret == ERR_NONE) { - return rfalNfcbCheckSensbRes(sensbRes, *sensbResLen); - } - - return ret; -} - -ReturnCode rfalNfcbPollerTechnologyDetection( - rfalComplianceMode compMode, - rfalNfcbSensbRes* sensbRes, - uint8_t* sensbResLen) { - NO_WARNING(compMode); - - return rfalNfcbPollerCheckPresence( - RFAL_NFCB_SENS_CMD_SENSB_REQ, RFAL_NFCB_SLOT_NUM_1, sensbRes, sensbResLen); -} - -/*******************************************************************************/ -ReturnCode rfalNfcbPollerCollisionResolution( - rfalComplianceMode compMode, - uint8_t devLimit, - rfalNfcbListenDevice* nfcbDevList, - uint8_t* devCnt) { - bool colPending; /* dummy */ - return rfalNfcbPollerSlottedCollisionResolution( - compMode, - devLimit, - RFAL_NFCB_SLOT_NUM_1, - RFAL_NFCB_SLOT_NUM_16, - nfcbDevList, - devCnt, - &colPending); -} - -/*******************************************************************************/ -ReturnCode rfalNfcbPollerSlottedCollisionResolution( - rfalComplianceMode compMode, - uint8_t devLimit, - rfalNfcbSlots initSlots, - rfalNfcbSlots endSlots, - rfalNfcbListenDevice* nfcbDevList, - uint8_t* devCnt, - bool* colPending) { - ReturnCode ret; - uint8_t slotsNum; - uint8_t slotCode; - uint8_t curDevCnt; - - /* Check parameters. In ISO | Activity 1.0 mode the initial slots must be 1 as continuation of Technology Detection */ - if((nfcbDevList == NULL) || (devCnt == NULL) || (colPending == NULL) || - (initSlots > RFAL_NFCB_SLOT_NUM_16) || (endSlots > RFAL_NFCB_SLOT_NUM_16) || - ((compMode == RFAL_COMPLIANCE_MODE_ISO) && (initSlots != RFAL_NFCB_SLOT_NUM_1))) { - return ERR_PARAM; - } - - /* Initialise as no error in case Activity 1.0 where the previous SENSB_RES from technology detection should be used */ - ret = ERR_NONE; - *devCnt = 0; - curDevCnt = 0; - *colPending = false; - - /* Send ALLB_REQ Activity 1.1 9.3.5.2 and 9.3.5.3 (Symbol 1 and 2) */ - if(compMode != RFAL_COMPLIANCE_MODE_ISO) { - ret = rfalNfcbPollerCheckPresence( - RFAL_NFCB_SENS_CMD_ALLB_REQ, - initSlots, - &nfcbDevList->sensbRes, - &nfcbDevList->sensbResLen); - if((ret != ERR_NONE) && (initSlots == RFAL_NFCB_SLOT_NUM_1)) { - return ret; - } - } - - /* Check if there was a transmission error on WUPB EMVCo 2.6 9.3.3.1 */ - if((compMode == RFAL_COMPLIANCE_MODE_EMV) && (nfcbDevList->sensbResLen == 0U)) { - return ERR_FRAMING; - } - - for(slotsNum = (uint8_t)initSlots; slotsNum <= (uint8_t)endSlots; slotsNum++) { - do { - /* Activity 1.1 9.3.5.23 - Symbol 22 */ - if((compMode == RFAL_COMPLIANCE_MODE_NFC) && (curDevCnt != 0U)) { - rfalNfcbPollerSleep(nfcbDevList[((*devCnt) - (uint8_t)1U)].sensbRes.nfcid0); - nfcbDevList[((*devCnt) - (uint8_t)1U)].isSleep = true; - } - - /* Send SENSB_REQ with number of slots if not the first Activity 1.1 9.3.5.24 - Symbol 23 */ - if((slotsNum != (uint8_t)initSlots) || *colPending) { - /* PRQA S 4342 1 # MISRA 10.5 - Layout of rfalNfcbSlots and above loop guarantee that no invalid enum values are created. */ - ret = rfalNfcbPollerCheckPresence( - RFAL_NFCB_SENS_CMD_SENSB_REQ, - (rfalNfcbSlots)slotsNum, - &nfcbDevList[*devCnt].sensbRes, - &nfcbDevList[*devCnt].sensbResLen); - } - - /* Activity 1.1 9.3.5.6 - Symbol 5 */ - slotCode = 0; - curDevCnt = 0; - *colPending = false; - - do { - /* Activity 1.1 9.3.5.26 - Symbol 25 */ - if(slotCode != 0U) { - ret = rfalNfcbPollerSlotMarker( - slotCode, - &nfcbDevList[*devCnt].sensbRes, - &nfcbDevList[*devCnt].sensbResLen); - } - - /* Activity 1.1 9.3.5.7 and 9.3.5.8 - Symbol 6 */ - if(ret != ERR_TIMEOUT) { - /* Activity 1.1 9.3.5.8 - Symbol 7 */ - if((rfalNfcbCheckSensbRes( - &nfcbDevList[*devCnt].sensbRes, nfcbDevList[*devCnt].sensbResLen) == - ERR_NONE) && - (ret == ERR_NONE)) { - nfcbDevList[*devCnt].isSleep = false; - - if(compMode == RFAL_COMPLIANCE_MODE_EMV) { - (*devCnt)++; - return ret; - } else if(compMode == RFAL_COMPLIANCE_MODE_ISO) { - /* Activity 1.0 9.3.5.8 - Symbol 7 */ - (*devCnt)++; - curDevCnt++; - - /* Activity 1.0 9.3.5.10 - Symbol 9 */ - if((*devCnt >= devLimit) || - (slotsNum == (uint8_t)RFAL_NFCB_SLOT_NUM_1)) { - return ret; - } - - /* Activity 1.0 9.3.5.11 - Symbol 10 */ - rfalNfcbPollerSleep(nfcbDevList[*devCnt - 1U].sensbRes.nfcid0); - nfcbDevList[*devCnt - 1U].isSleep = true; - } else if(compMode == RFAL_COMPLIANCE_MODE_NFC) { - /* Activity 1.1 9.3.5.10 and 9.3.5.11 - Symbol 9 and Symbol 11*/ - if(curDevCnt != 0U) { - rfalNfcbPollerSleep( - nfcbDevList[(*devCnt) - (uint8_t)1U].sensbRes.nfcid0); - nfcbDevList[(*devCnt) - (uint8_t)1U].isSleep = true; - } - - /* Activity 1.1 9.3.5.12 - Symbol 11 */ - (*devCnt)++; - curDevCnt++; - - /* Activity 1.1 9.3.5.6 - Symbol 13 */ - if((*devCnt >= devLimit) || - (slotsNum == (uint8_t)RFAL_NFCB_SLOT_NUM_1)) { - return ret; - } - } else { - /* MISRA 15.7 - Empty else */ - } - } else { - /* If deviceLimit is set to 0 the NFC Forum Device is configured to perform collision detection only Activity 1.0 and 1.1 9.3.5.5 - Symbol 4 */ - if((devLimit == 0U) && (slotsNum == (uint8_t)RFAL_NFCB_SLOT_NUM_1)) { - return ERR_RF_COLLISION; - } - - /* Activity 1.1 9.3.5.9 - Symbol 8 */ - *colPending = true; - } - } - - /* Activity 1.1 9.3.5.15 - Symbol 14 */ - slotCode++; - } while(slotCode < rfalNfcbNI2NumberOfSlots(slotsNum)); - - /* Activity 1.1 9.3.5.17 - Symbol 16 */ - if(!(*colPending)) { - return ERR_NONE; - } - - /* Activity 1.1 9.3.5.18 - Symbol 17 */ - } while( - curDevCnt != - 0U); /* If a collision is detected and card(s) were found on this loop keep the same number of available slots */ - } - - return ERR_NONE; -} - -/*******************************************************************************/ -uint32_t rfalNfcbTR2ToFDT(uint8_t tr2Code) { - /*******************************************************************************/ - /* MISRA 8.9 An object should be defined at block scope if its identifier only appears in a single function */ - /*! TR2 Table according to Digital 1.1 Table 33 */ - const uint16_t rfalNfcbTr2Table[] = {1792, 3328, 5376, 9472}; - /*******************************************************************************/ - - return rfalNfcbTr2Table[(tr2Code & RFAL_NFCB_SENSB_RES_PROTO_TR2_MASK)]; -} - -#endif /* RFAL_FEATURE_NFCB */ diff --git a/lib/ST25RFAL002/source/rfal_nfcf.c b/lib/ST25RFAL002/source/rfal_nfcf.c deleted file mode 100644 index 7b5e4d58fc5..00000000000 --- a/lib/ST25RFAL002/source/rfal_nfcf.c +++ /dev/null @@ -1,585 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

    © COPYRIGHT 2020 STMicroelectronics

    - * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R391x firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file rfal_nfcf.c - * - * \author Gustavo Patricio - * - * \brief Implementation of NFC-F Poller (FeliCa PCD) device - * - * The definitions and helpers methods provided by this module are - * aligned with NFC-F (FeliCa - JIS X6319-4) - * - */ - -/* - ****************************************************************************** - * INCLUDES - ****************************************************************************** - */ -#include "rfal_nfcf.h" -#include "utils.h" - -/* - ****************************************************************************** - * ENABLE SWITCH - ****************************************************************************** - */ - -#ifndef RFAL_FEATURE_NFCF -#define RFAL_FEATURE_NFCF false /* NFC-F module configuration missing. Disabled by default */ -#endif - -#if RFAL_FEATURE_NFCF - -/* - ****************************************************************************** - * GLOBAL DEFINES - ****************************************************************************** - */ -#define RFAL_NFCF_SENSF_REQ_LEN_MIN \ - 5U /*!< SENSF_RES minimum length */ - -#define RFAL_NFCF_READ_WO_ENCRYPTION_MIN_LEN \ - 15U /*!< Minimum length for a Check Command T3T 5.4.1 */ -#define RFAL_NFCF_WRITE_WO_ENCRYPTION_MIN_LEN \ - 31U /*!< Minimum length for an Update Command T3T 5.5.1 */ - -#define RFAL_NFCF_CHECK_RES_MIN_LEN \ - 11U /*!< CHECK Response minimum length T3T 1.0 Table 8 */ -#define RFAL_NFCF_UPDATE_RES_MIN_LEN \ - 11U /*!< UPDATE Response minimum length T3T 1.0 Table 8 */ - -#define RFAL_NFCF_CHECK_REQ_MAX_LEN \ - 86U /*!< Max length of a Check request T3T 1.0 Table 7 */ -#define RFAL_NFCF_CHECK_REQ_MAX_SERV \ - 15U /*!< Max Services number on Check request T3T 1.0 5.4.1.5 */ -#define RFAL_NFCF_CHECK_REQ_MAX_BLOCK \ - 15U /*!< Max Blocks number on Check request T3T 1.0 5.4.1.10 */ -#define RFAL_NFCF_UPDATE_REQ_MAX_SERV \ - 15U /*!< Max Services number Update request T3T 1.0 5.4.1.5 */ -#define RFAL_NFCF_UPDATE_REQ_MAX_BLOCK \ - 13U /*!< Max Blocks number on Update request T3T 1.0 5.4.1.10 */ - -/*! MRT Check | Uupdate = (Tt3t x ((A+1) + n (B+1)) x 4^E) + dRWTt3t T3T 5.8 - Max values used: A = 7 ; B = 7 ; E = 3 ; n = 15 (NFC Forum n = 15, JIS n = 32) -*/ -#define RFAL_NFCF_MRT_CHECK_UPDATE ((4096 * (8 + (15 * 8)) * 64) + 16) - -/* - ****************************************************************************** - * GLOBAL MACROS - ****************************************************************************** - */ -#define rfalNfcfSlots2CardNum(s) \ - ((uint8_t)(s) + 1U) /*!< Converts Time Slot Number (TSN) into num of slots */ - -/* -****************************************************************************** -* GLOBAL TYPES -****************************************************************************** -*/ - -/*! Structure/Buffer to hold the SENSF_RES with LEN byte prepended */ -typedef struct { - uint8_t LEN; /*!< NFC-F LEN byte */ - rfalNfcfSensfRes SENSF_RES; /*!< SENSF_RES */ -} rfalNfcfSensfResBuf; - -/*! Greedy collection for NFCF GRE_POLL_F Activity 1.0 Table 10 */ -typedef struct { - uint8_t pollFound; /*!< Number of devices found by the Poll */ - uint8_t pollCollision; /*!< Number of collisions detected */ - rfalFeliCaPollRes POLL_F[RFAL_NFCF_POLL_MAXCARDS]; /*!< GRE_POLL_F Activity 1.0 Table 10 */ -} rfalNfcfGreedyF; - -/*! NFC-F SENSF_REQ format Digital 1.1 8.6.1 */ -typedef struct { - uint8_t CMD; /*!< Command code: 00h */ - uint8_t SC[RFAL_NFCF_SENSF_SC_LEN]; /*!< System Code */ - uint8_t RC; /*!< Request Code */ - uint8_t TSN; /*!< Time Slot Number */ -} rfalNfcfSensfReq; - -/* -****************************************************************************** -* LOCAL VARIABLES -****************************************************************************** -*/ -static rfalNfcfGreedyF gRfalNfcfGreedyF; /*!< Activity's NFCF Greedy collection */ - -/* -****************************************************************************** -* LOCAL FUNCTION PROTOTYPES -****************************************************************************** -*/ -static void rfalNfcfComputeValidSENF( - rfalNfcfListenDevice* outDevInfo, - uint8_t* curDevIdx, - uint8_t devLimit, - bool overwrite, - bool* nfcDepFound); - -/* -****************************************************************************** -* LOCAL VARIABLES -****************************************************************************** -*/ - -/*******************************************************************************/ -static void rfalNfcfComputeValidSENF( - rfalNfcfListenDevice* outDevInfo, - uint8_t* curDevIdx, - uint8_t devLimit, - bool overwrite, - bool* nfcDepFound) { - uint8_t tmpIdx; - bool duplicate; - const rfalNfcfSensfResBuf* sensfBuf; - rfalNfcfSensfResBuf sensfCopy; - - /*******************************************************************************/ - /* Go through all responses check if valid and duplicates */ - /*******************************************************************************/ - while((gRfalNfcfGreedyF.pollFound > 0U) && ((*curDevIdx) < devLimit)) { - duplicate = false; - gRfalNfcfGreedyF.pollFound--; - - /* MISRA 11.3 - Cannot point directly into different object type, use local copy */ - ST_MEMCPY( - (uint8_t*)&sensfCopy, - (uint8_t*)&gRfalNfcfGreedyF.POLL_F[gRfalNfcfGreedyF.pollFound], - sizeof(rfalNfcfSensfResBuf)); - - /* Point to received SENSF_RES */ - sensfBuf = &sensfCopy; - - /* Check for devices that are already in device list */ - for(tmpIdx = 0; tmpIdx < (*curDevIdx); tmpIdx++) { - if(ST_BYTECMP( - sensfBuf->SENSF_RES.NFCID2, - outDevInfo[tmpIdx].sensfRes.NFCID2, - RFAL_NFCF_NFCID2_LEN) == 0) { - duplicate = true; - break; - } - } - - /* If is a duplicate skip this (and not to overwrite)*/ - if(duplicate && !overwrite) { - continue; - } - - /* Check if response length is OK */ - if(((sensfBuf->LEN - RFAL_NFCF_HEADER_LEN) < RFAL_NFCF_SENSF_RES_LEN_MIN) || - ((sensfBuf->LEN - RFAL_NFCF_HEADER_LEN) > RFAL_NFCF_SENSF_RES_LEN_MAX)) { - continue; - } - - /* Check if the response is a SENSF_RES / Polling response */ - if(sensfBuf->SENSF_RES.CMD != (uint8_t)RFAL_NFCF_CMD_POLLING_RES) { - continue; - } - - /* Check if is an overwrite request or new device*/ - if(duplicate && overwrite) { - /* overwrite deviceInfo/GRE_SENSF_RES with SENSF_RES */ - outDevInfo[tmpIdx].sensfResLen = (sensfBuf->LEN - RFAL_NFCF_LENGTH_LEN); - ST_MEMCPY( - &outDevInfo[tmpIdx].sensfRes, - &sensfBuf->SENSF_RES, - outDevInfo[tmpIdx].sensfResLen); - continue; - } else { - /* fill deviceInfo/GRE_SENSF_RES with new SENSF_RES */ - outDevInfo[(*curDevIdx)].sensfResLen = (sensfBuf->LEN - RFAL_NFCF_LENGTH_LEN); - ST_MEMCPY( - &outDevInfo[(*curDevIdx)].sensfRes, - &sensfBuf->SENSF_RES, - outDevInfo[(*curDevIdx)].sensfResLen); - } - - /* Check if this device supports NFC-DEP and signal it (ACTIVITY 1.1 9.3.6.63) */ - *nfcDepFound = rfalNfcfIsNfcDepSupported(&outDevInfo[(*curDevIdx)]); - - (*curDevIdx)++; - } -} - -/* -****************************************************************************** -* GLOBAL FUNCTIONS -****************************************************************************** -*/ - -/*******************************************************************************/ -ReturnCode rfalNfcfPollerInitialize(rfalBitRate bitRate) { - ReturnCode ret; - - if((bitRate != RFAL_BR_212) && (bitRate != RFAL_BR_424)) { - return ERR_PARAM; - } - - EXIT_ON_ERR(ret, rfalSetMode(RFAL_MODE_POLL_NFCF, bitRate, bitRate)); - rfalSetErrorHandling(RFAL_ERRORHANDLING_NFC); - - rfalSetGT(RFAL_GT_NFCF); - rfalSetFDTListen(RFAL_FDT_LISTEN_NFCF_POLLER); - rfalSetFDTPoll(RFAL_FDT_POLL_NFCF_POLLER); - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalNfcfPollerPoll( - rfalFeliCaPollSlots slots, - uint16_t sysCode, - uint8_t reqCode, - rfalFeliCaPollRes* cardList, - uint8_t* devCnt, - uint8_t* collisions) { - return rfalFeliCaPoll( - slots, sysCode, reqCode, cardList, rfalNfcfSlots2CardNum(slots), devCnt, collisions); -} - -/*******************************************************************************/ -ReturnCode rfalNfcfPollerCheckPresence(void) { - gRfalNfcfGreedyF.pollFound = 0; - gRfalNfcfGreedyF.pollCollision = 0; - - /* ACTIVITY 1.0 & 1.1 - 9.2.3.17 SENSF_REQ must be with number of slots equal to 4 - * SC must be 0xFFFF - * RC must be 0x00 (No system code info required) */ - return rfalFeliCaPoll( - RFAL_FELICA_4_SLOTS, - RFAL_NFCF_SYSTEMCODE, - RFAL_FELICA_POLL_RC_NO_REQUEST, - gRfalNfcfGreedyF.POLL_F, - rfalNfcfSlots2CardNum(RFAL_FELICA_4_SLOTS), - &gRfalNfcfGreedyF.pollFound, - &gRfalNfcfGreedyF.pollCollision); -} - -/*******************************************************************************/ -ReturnCode rfalNfcfPollerCollisionResolution( - rfalComplianceMode compMode, - uint8_t devLimit, - rfalNfcfListenDevice* nfcfDevList, - uint8_t* devCnt) { - ReturnCode ret; - bool nfcDepFound; - - if((nfcfDevList == NULL) || (devCnt == NULL)) { - return ERR_PARAM; - } - - *devCnt = 0; - nfcDepFound = false; - - /*******************************************************************************************/ - /* ACTIVITY 1.0 - 9.3.6.3 Copy valid SENSF_RES in GRE_POLL_F into GRE_SENSF_RES */ - /* ACTIVITY 1.0 - 9.3.6.6 The NFC Forum Device MUST remove all entries from GRE_SENSF_RES[]*/ - /* ACTIVITY 1.1 - 9.3.63.59 Populate GRE_SENSF_RES with data from GRE_POLL_F */ - /* */ - /* CON_DEVICES_LIMIT = 0 Just check if devices from Tech Detection exceeds -> always true */ - /* Allow the number of slots open on Technology Detection */ - /*******************************************************************************************/ - rfalNfcfComputeValidSENF( - nfcfDevList, - devCnt, - ((devLimit == 0U) ? rfalNfcfSlots2CardNum(RFAL_FELICA_4_SLOTS) : devLimit), - false, - &nfcDepFound); - - /*******************************************************************************/ - /* ACTIVITY 1.0 - 9.3.6.4 */ - /* ACTIVITY 1.1 - 9.3.63.60 Check if devices found are lower than the limit */ - /* and send a SENSF_REQ if so */ - /*******************************************************************************/ - if(*devCnt < devLimit) { - /* ACTIVITY 1.0 - 9.3.6.5 Copy valid SENSF_RES and then to remove it - * ACTIVITY 1.1 - 9.3.6.65 Copy and filter duplicates - * For now, due to some devices keep generating different nfcid2, we use 1.0 - * Phones detected: Samsung Galaxy Nexus,Samsung Galaxy S3,Samsung Nexus S */ - *devCnt = 0; - - ret = rfalNfcfPollerPoll( - RFAL_FELICA_16_SLOTS, - RFAL_NFCF_SYSTEMCODE, - RFAL_FELICA_POLL_RC_NO_REQUEST, - gRfalNfcfGreedyF.POLL_F, - &gRfalNfcfGreedyF.pollFound, - &gRfalNfcfGreedyF.pollCollision); - if(ret == ERR_NONE) { - rfalNfcfComputeValidSENF(nfcfDevList, devCnt, devLimit, false, &nfcDepFound); - } - - /*******************************************************************************/ - /* ACTIVITY 1.1 - 9.3.6.63 Check if any device supports NFC DEP */ - /*******************************************************************************/ - if(nfcDepFound && (compMode == RFAL_COMPLIANCE_MODE_NFC)) { - ret = rfalNfcfPollerPoll( - RFAL_FELICA_16_SLOTS, - RFAL_NFCF_SYSTEMCODE, - RFAL_FELICA_POLL_RC_SYSTEM_CODE, - gRfalNfcfGreedyF.POLL_F, - &gRfalNfcfGreedyF.pollFound, - &gRfalNfcfGreedyF.pollCollision); - if(ret == ERR_NONE) { - rfalNfcfComputeValidSENF(nfcfDevList, devCnt, devLimit, true, &nfcDepFound); - } - } - } - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalNfcfPollerCheck( - const uint8_t* nfcid2, - const rfalNfcfServBlockListParam* servBlock, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rcvdLen) { - uint8_t txBuf[RFAL_NFCF_CHECK_REQ_MAX_LEN]; - uint8_t msgIt; - uint8_t i; - ReturnCode ret; - const uint8_t* checkRes; - - /* Check parameters */ - if((nfcid2 == NULL) || (rxBuf == NULL) || (servBlock == NULL) || (servBlock->numBlock == 0U) || - (servBlock->numBlock > RFAL_NFCF_CHECK_REQ_MAX_BLOCK) || (servBlock->numServ == 0U) || - (servBlock->numServ > RFAL_NFCF_CHECK_REQ_MAX_SERV) || - (rxBufLen < (RFAL_NFCF_LENGTH_LEN + RFAL_NFCF_CHECK_RES_MIN_LEN))) { - return ERR_PARAM; - } - - msgIt = 0; - - /*******************************************************************************/ - /* Compose CHECK command/request */ - - txBuf[msgIt++] = RFAL_NFCF_CMD_READ_WITHOUT_ENCRYPTION; /* Command Code */ - - ST_MEMCPY(&txBuf[msgIt], nfcid2, RFAL_NFCF_NFCID2_LEN); /* NFCID2 */ - msgIt += RFAL_NFCF_NFCID2_LEN; - - txBuf[msgIt++] = servBlock->numServ; /* NoS */ - for(i = 0; i < servBlock->numServ; i++) { - txBuf[msgIt++] = (uint8_t)((servBlock->servList[i] >> 0U) & 0xFFU); /* Service Code */ - txBuf[msgIt++] = (uint8_t)((servBlock->servList[i] >> 8U) & 0xFFU); - } - - txBuf[msgIt++] = servBlock->numBlock; /* NoB */ - for(i = 0; i < servBlock->numBlock; i++) { - txBuf[msgIt++] = - servBlock->blockList[i].conf; /* Block list element conf (Flag|Access|Service) */ - if((servBlock->blockList[i].conf & 0x80U) != - 0U) /* Check if 2 or 3 byte block list element */ - { - txBuf[msgIt++] = - (uint8_t)(servBlock->blockList[i].blockNum & 0xFFU); /* 1byte Block Num */ - } else { - txBuf[msgIt++] = - (uint8_t)((servBlock->blockList[i].blockNum >> 0U) & 0xFFU); /* 2byte Block Num */ - txBuf[msgIt++] = (uint8_t)((servBlock->blockList[i].blockNum >> 8U) & 0xFFU); - } - } - - /*******************************************************************************/ - /* Transceive CHECK command/request */ - ret = rfalTransceiveBlockingTxRx( - txBuf, - msgIt, - rxBuf, - rxBufLen, - rcvdLen, - RFAL_TXRX_FLAGS_DEFAULT, - RFAL_NFCF_MRT_CHECK_UPDATE); - - if(ret == ERR_NONE) { - /* Skip LEN byte */ - checkRes = (rxBuf + RFAL_NFCF_LENGTH_LEN); - - /* Check response length */ - if(*rcvdLen < (RFAL_NFCF_LENGTH_LEN + RFAL_NFCF_CHECKUPDATE_RES_ST2_POS)) { - ret = ERR_PROTO; - } - /* Check for a valid response */ - else if( - (checkRes[RFAL_NFCF_CMD_POS] != (uint8_t)RFAL_NFCF_CMD_READ_WITHOUT_ENCRYPTION_RES) || - (checkRes[RFAL_NFCF_CHECKUPDATE_RES_ST1_POS] != RFAL_NFCF_STATUS_FLAG_SUCCESS) || - (checkRes[RFAL_NFCF_CHECKUPDATE_RES_ST2_POS] != RFAL_NFCF_STATUS_FLAG_SUCCESS)) { - ret = ERR_REQUEST; - } - /* CHECK succesfull, remove header */ - else { - (*rcvdLen) -= (RFAL_NFCF_LENGTH_LEN + RFAL_NFCF_CHECKUPDATE_RES_NOB_POS); - - if(*rcvdLen > 0U) { - ST_MEMMOVE(rxBuf, &checkRes[RFAL_NFCF_CHECKUPDATE_RES_NOB_POS], (*rcvdLen)); - } - } - } - - return ret; -} - -/*******************************************************************************/ -ReturnCode rfalNfcfPollerUpdate( - const uint8_t* nfcid2, - const rfalNfcfServBlockListParam* servBlock, - uint8_t* txBuf, - uint16_t txBufLen, - const uint8_t* blockData, - uint8_t* rxBuf, - uint16_t rxBufLen) { - uint8_t i; - uint16_t msgIt; - uint16_t rcvdLen; - uint16_t auxLen; - const uint8_t* updateRes; - ReturnCode ret; - - /* Check parameters */ - if((nfcid2 == NULL) || (rxBuf == NULL) || (servBlock == NULL) || (txBuf == NULL) || - (servBlock->numBlock == 0U) || (servBlock->numBlock > RFAL_NFCF_UPDATE_REQ_MAX_BLOCK) || - (servBlock->numServ == 0U) || (servBlock->numServ > RFAL_NFCF_UPDATE_REQ_MAX_SERV) || - (rxBufLen < (RFAL_NFCF_LENGTH_LEN + RFAL_NFCF_UPDATE_RES_MIN_LEN))) { - return ERR_PARAM; - } - - /* Calculate required txBuffer lenth */ - auxLen = (uint16_t)( RFAL_NFCF_CMD_LEN + RFAL_NFCF_NFCID2_LEN + ( servBlock->numServ * sizeof(rfalNfcfServ) ) + - (servBlock->numBlock * sizeof(rfalNfcfBlockListElem)) + (uint16_t)((uint16_t)servBlock->numBlock * RFAL_NFCF_BLOCK_LEN) ); - - /* Check whether the provided buffer is sufficient for this request */ - if(txBufLen < auxLen) { - return ERR_PARAM; - } - - msgIt = 0; - - /*******************************************************************************/ - /* Compose UPDATE command/request */ - - txBuf[msgIt++] = RFAL_NFCF_CMD_WRITE_WITHOUT_ENCRYPTION; /* Command Code */ - - ST_MEMCPY(&txBuf[msgIt], nfcid2, RFAL_NFCF_NFCID2_LEN); /* NFCID2 */ - msgIt += RFAL_NFCF_NFCID2_LEN; - - txBuf[msgIt++] = servBlock->numServ; /* NoS */ - for(i = 0; i < servBlock->numServ; i++) { - txBuf[msgIt++] = (uint8_t)((servBlock->servList[i] >> 0U) & 0xFFU); /* Service Code */ - txBuf[msgIt++] = (uint8_t)((servBlock->servList[i] >> 8U) & 0xFFU); - } - - txBuf[msgIt++] = servBlock->numBlock; /* NoB */ - for(i = 0; i < servBlock->numBlock; i++) { - txBuf[msgIt++] = - servBlock->blockList[i].conf; /* Block list element conf (Flag|Access|Service) */ - if((servBlock->blockList[i].conf & 0x80U) != - 0U) /* Check if 2 or 3 byte block list element */ - { - txBuf[msgIt++] = - (uint8_t)(servBlock->blockList[i].blockNum & 0xFFU); /* 1byte Block Num */ - } else { - txBuf[msgIt++] = - (uint8_t)((servBlock->blockList[i].blockNum >> 0U) & 0xFFU); /* 2byte Block Num */ - txBuf[msgIt++] = (uint8_t)((servBlock->blockList[i].blockNum >> 8U) & 0xFFU); - } - } - - auxLen = ((uint16_t)servBlock->numBlock * RFAL_NFCF_BLOCK_LEN); - ST_MEMCPY(&txBuf[msgIt], blockData, auxLen); /* Block Data */ - msgIt += auxLen; - - /*******************************************************************************/ - /* Transceive UPDATE command/request */ - ret = rfalTransceiveBlockingTxRx( - txBuf, - msgIt, - rxBuf, - rxBufLen, - &rcvdLen, - RFAL_TXRX_FLAGS_DEFAULT, - RFAL_NFCF_MRT_CHECK_UPDATE); - - if(ret == ERR_NONE) { - /* Skip LEN byte */ - updateRes = (rxBuf + RFAL_NFCF_LENGTH_LEN); - - /* Check response length */ - if(rcvdLen < (RFAL_NFCF_LENGTH_LEN + RFAL_NFCF_CHECKUPDATE_RES_ST2_POS)) { - ret = ERR_PROTO; - } - /* Check for a valid response */ - else if( - (updateRes[RFAL_NFCF_CMD_POS] != - (uint8_t)RFAL_NFCF_CMD_WRITE_WITHOUT_ENCRYPTION_RES) || - (updateRes[RFAL_NFCF_CHECKUPDATE_RES_ST1_POS] != RFAL_NFCF_STATUS_FLAG_SUCCESS) || - (updateRes[RFAL_NFCF_CHECKUPDATE_RES_ST2_POS] != RFAL_NFCF_STATUS_FLAG_SUCCESS)) { - ret = ERR_REQUEST; - } else { - /* MISRA 15.7 - Empty else */ - } - } - - return ret; -} - -/*******************************************************************************/ -bool rfalNfcfListenerIsT3TReq(const uint8_t* buf, uint16_t bufLen, uint8_t* nfcid2) { - /* Check cmd byte */ - switch(*buf) { - case RFAL_NFCF_CMD_READ_WITHOUT_ENCRYPTION: - if(bufLen < RFAL_NFCF_READ_WO_ENCRYPTION_MIN_LEN) { - return false; - } - break; - - case RFAL_NFCF_CMD_WRITE_WITHOUT_ENCRYPTION: - if(bufLen < RFAL_NFCF_WRITE_WO_ENCRYPTION_MIN_LEN) { - return false; - } - break; - - default: - return false; - } - - /* Output NFID2 if requested */ - if(nfcid2 != NULL) { - ST_MEMCPY(nfcid2, &buf[RFAL_NFCF_CMD_LEN], RFAL_NFCF_NFCID2_LEN); - } - - return true; -} - -#endif /* RFAL_FEATURE_NFCF */ diff --git a/lib/ST25RFAL002/source/rfal_nfcv.c b/lib/ST25RFAL002/source/rfal_nfcv.c deleted file mode 100644 index 22c9b874169..00000000000 --- a/lib/ST25RFAL002/source/rfal_nfcv.c +++ /dev/null @@ -1,1057 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

    © COPYRIGHT 2020 STMicroelectronics

    - * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R391x firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file rfal_nfcv.c - * - * \author Gustavo Patricio - * - * \brief Implementation of NFC-V Poller (ISO15693) device - * - * The definitions and helpers methods provided by this module are - * aligned with NFC-V (ISO15693) - * - * The definitions and helpers methods provided by this module - * are aligned with NFC-V Digital 2.1 - * - */ - -/* - ****************************************************************************** - * INCLUDES - ****************************************************************************** - */ -#include "rfal_nfcv.h" -#include "utils.h" - -/* - ****************************************************************************** - * ENABLE SWITCH - ****************************************************************************** - */ - -#ifndef RFAL_FEATURE_NFCV -#define RFAL_FEATURE_NFCV false /* NFC-V module configuration missing. Disabled by default */ -#endif - -#if RFAL_FEATURE_NFCV - -/* - ****************************************************************************** - * GLOBAL DEFINES - ****************************************************************************** - */ - -#define RFAL_NFCV_INV_REQ_FLAG \ - 0x06U /*!< INVENTORY_REQ INV_FLAG Digital 2.1 9.6.1 */ -#define RFAL_NFCV_MASKVAL_MAX_LEN \ - 8U /*!< Mask value max length: 64 bits (UID length) */ -#define RFAL_NFCV_MASKVAL_MAX_1SLOT_LEN \ - 64U /*!< Mask value max length in 1 Slot mode in bits Digital 2.1 9.6.1.6 */ -#define RFAL_NFCV_MASKVAL_MAX_16SLOT_LEN \ - 60U /*!< Mask value max length in 16 Slot mode in bits Digital 2.1 9.6.1.6 */ -#define RFAL_NFCV_MAX_SLOTS \ - 16U /*!< NFC-V max number of Slots */ -#define RFAL_NFCV_INV_REQ_HEADER_LEN \ - 3U /*!< INVENTORY_REQ header length (INV_FLAG, CMD, MASK_LEN) */ -#define RFAL_NFCV_INV_RES_LEN \ - 10U /*!< INVENTORY_RES length */ -#define RFAL_NFCV_WR_MUL_REQ_HEADER_LEN \ - 4U /*!< Write Multiple header length (INV_FLAG, CMD, [UID], BNo, Bno) */ - -#define RFAL_NFCV_CMD_LEN \ - 1U /*!< Commandbyte length */ -#define RFAL_NFCV_FLAG_POS \ - 0U /*!< Flag byte position */ -#define RFAL_NFCV_FLAG_LEN \ - 1U /*!< Flag byte length */ -#define RFAL_NFCV_DATASTART_POS \ - 1U /*!< Position of start of data */ -#define RFAL_NFCV_DSFI_LEN \ - 1U /*!< DSFID length */ -#define RFAL_NFCV_SLPREQ_REQ_FLAG \ - 0x22U /*!< SLPV_REQ request flags Digital 2.0 (Candidate) 9.7.1.1 */ -#define RFAL_NFCV_RES_FLAG_NOERROR \ - 0x00U /*!< RES_FLAG indicating no error (checked during activation) */ - -#define RFAL_NFCV_MAX_COLL_SUPPORTED \ - 16U /*!< Maximum number of collisions supported by the Anticollision loop */ - -#define RFAL_NFCV_FDT_MAX \ - rfalConvMsTo1fc(20) /*!< Maximum Wait time FDTV,EOF and MAX2 Digital 2.1 B.5*/ -#define RFAL_NFCV_FDT_MAX1 \ - 4394U /*!< Read alike command FWT FDTV,LISTEN,MAX1 Digital 2.0 B.5 */ - -/*! Time from special frame to EOF - * ISO15693 2009 10.4.2 : 20ms - * NFC Forum defines Digital 2.0 9.7.4 : FDTV,EOF = [10 ; 20]ms - */ -#define RFAL_NFCV_FDT_EOF 20U - -/*! Time between slots - ISO 15693 defines t3min depending on modulation depth and data rate. - * With only high-bitrate supported, AM modulation and a length of 12 bytes (96bits) for INV_RES we get: - * - ISO t3min = 96/26 ms + 300us = 4 ms - * - NFC Forum defines FDTV,INVENT_NORES = (4394 + 2048)/fc. Digital 2.0 B.5*/ -#define RFAL_NFCV_FDT_V_INVENT_NORES 4U - -/* - ****************************************************************************** - * GLOBAL MACROS - ****************************************************************************** - */ - -/*! Checks if a valid INVENTORY_RES is valid Digital 2.2 9.6.2.1 & 9.6.2.3 */ -#define rfalNfcvCheckInvRes(f, l) \ - (((l) == rfalConvBytesToBits(RFAL_NFCV_INV_RES_LEN + RFAL_NFCV_CRC_LEN)) && \ - ((f) == RFAL_NFCV_RES_FLAG_NOERROR)) - -/* -****************************************************************************** -* GLOBAL TYPES -****************************************************************************** -*/ - -/*! NFC-V INVENTORY_REQ format Digital 2.0 9.6.1 */ -typedef struct { - uint8_t INV_FLAG; /*!< Inventory Flags */ - uint8_t CMD; /*!< Command code: 01h */ - uint8_t MASK_LEN; /*!< Mask Value Length */ - uint8_t MASK_VALUE[RFAL_NFCV_MASKVAL_MAX_LEN]; /*!< Mask Value */ -} rfalNfcvInventoryReq; - -/*! NFC-V SLP_REQ format Digital 2.0 (Candidate) 9.7.1 */ -typedef struct { - uint8_t REQ_FLAG; /*!< Request Flags */ - uint8_t CMD; /*!< Command code: 02h */ - uint8_t UID[RFAL_NFCV_UID_LEN]; /*!< Mask Value */ -} rfalNfcvSlpvReq; - -/*! Container for a collision found during Anticollision loop */ -typedef struct { - uint8_t maskLen; - uint8_t maskVal[RFAL_NFCV_MASKVAL_MAX_LEN]; -} rfalNfcvCollision; - -/* -****************************************************************************** -* LOCAL FUNCTION PROTOTYPES -****************************************************************************** -*/ -static ReturnCode rfalNfcvParseError(uint8_t err); - -/* -****************************************************************************** -* LOCAL VARIABLES -****************************************************************************** -*/ - -/* -****************************************************************************** -* LOCAL FUNCTIONS -****************************************************************************** -*/ - -/*******************************************************************************/ -static ReturnCode rfalNfcvParseError(uint8_t err) { - switch(err) { - case RFAL_NFCV_ERROR_CMD_NOT_SUPPORTED: - case RFAL_NFCV_ERROR_OPTION_NOT_SUPPORTED: - return ERR_NOTSUPP; - - case RFAL_NFCV_ERROR_CMD_NOT_RECOGNIZED: - return ERR_PROTO; - - case RFAL_NFCV_ERROR_WRITE_FAILED: - return ERR_WRITE; - - default: - return ERR_REQUEST; - } -} - -/* -****************************************************************************** -* GLOBAL FUNCTIONS -****************************************************************************** -*/ - -/*******************************************************************************/ -ReturnCode rfalNfcvPollerInitialize(void) { - ReturnCode ret; - - EXIT_ON_ERR(ret, rfalSetMode(RFAL_MODE_POLL_NFCV, RFAL_BR_26p48, RFAL_BR_26p48)); - rfalSetErrorHandling(RFAL_ERRORHANDLING_NFC); - - rfalSetGT(RFAL_GT_NFCV); - rfalSetFDTListen(RFAL_FDT_LISTEN_NFCV_POLLER); - rfalSetFDTPoll(RFAL_FDT_POLL_NFCV_POLLER); - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalNfcvPollerCheckPresence(rfalNfcvInventoryRes* invRes) { - ReturnCode ret; - - /* INVENTORY_REQ with 1 slot and no Mask Activity 2.0 (Candidate) 9.2.3.32 */ - ret = rfalNfcvPollerInventory(RFAL_NFCV_NUM_SLOTS_1, 0, NULL, invRes, NULL); - - if((ret == ERR_RF_COLLISION) || (ret == ERR_CRC) || (ret == ERR_FRAMING) || - (ret == ERR_PROTO)) { - ret = ERR_NONE; - } - - return ret; -} - -/*******************************************************************************/ -ReturnCode rfalNfcvPollerInventory( - rfalNfcvNumSlots nSlots, - uint8_t maskLen, - const uint8_t* maskVal, - rfalNfcvInventoryRes* invRes, - uint16_t* rcvdLen) { - ReturnCode ret; - rfalNfcvInventoryReq invReq; - uint16_t rxLen; - - if(((maskVal == NULL) && (maskLen != 0U)) || (invRes == NULL)) { - return ERR_PARAM; - } - - invReq.INV_FLAG = (RFAL_NFCV_INV_REQ_FLAG | (uint8_t)nSlots); - invReq.CMD = RFAL_NFCV_CMD_INVENTORY; - invReq.MASK_LEN = (uint8_t)MIN( - maskLen, - ((nSlots == RFAL_NFCV_NUM_SLOTS_1) ? - RFAL_NFCV_MASKVAL_MAX_1SLOT_LEN : - RFAL_NFCV_MASKVAL_MAX_16SLOT_LEN)); /* Digital 2.0 9.6.1.6 */ - - if((rfalConvBitsToBytes(invReq.MASK_LEN) > 0U) && (maskVal != NULL)) /* MISRA 21.18 & 1.3 */ - { - ST_MEMCPY(invReq.MASK_VALUE, maskVal, rfalConvBitsToBytes(invReq.MASK_LEN)); - } - - ret = rfalISO15693TransceiveAnticollisionFrame( - (uint8_t*)&invReq, - (uint8_t)(RFAL_NFCV_INV_REQ_HEADER_LEN + rfalConvBitsToBytes(invReq.MASK_LEN)), - (uint8_t*)invRes, - sizeof(rfalNfcvInventoryRes), - &rxLen); - - /* Check for optional output parameter */ - if(rcvdLen != NULL) { - *rcvdLen = rxLen; - } - - if(ret == ERR_NONE) { - /* Check for valid INVENTORY_RES Digital 2.2 9.6.2.1 & 9.6.2.3 */ - if(!rfalNfcvCheckInvRes(invRes->RES_FLAG, rxLen)) { - return ERR_PROTO; - } - } - - return ret; -} - -/*******************************************************************************/ -ReturnCode rfalNfcvPollerCollisionResolution( - rfalComplianceMode compMode, - uint8_t devLimit, - rfalNfcvListenDevice* nfcvDevList, - uint8_t* devCnt) { - ReturnCode ret; - uint8_t slotNum; - uint16_t rcvdLen; - uint8_t colIt; - uint8_t colCnt; - uint8_t colPos; - bool colPending; - rfalNfcvCollision colFound[RFAL_NFCV_MAX_COLL_SUPPORTED]; - - if((nfcvDevList == NULL) || (devCnt == NULL)) { - return ERR_PARAM; - } - - /* Initialize parameters */ - *devCnt = 0; - colIt = 0; - colCnt = 0; - colPending = false; - ST_MEMSET(colFound, 0x00, (sizeof(rfalNfcvCollision) * RFAL_NFCV_MAX_COLL_SUPPORTED)); - - if(devLimit > 0U) /* MISRA 21.18 */ - { - ST_MEMSET(nfcvDevList, 0x00, (sizeof(rfalNfcvListenDevice) * devLimit)); - } - - NO_WARNING( - colPending); /* colPending is not exposed externally, in future it might become exposed/ouput parameter */ - - if(compMode == RFAL_COMPLIANCE_MODE_NFC) { - /* Send INVENTORY_REQ with one slot Activity 2.1 9.3.7.1 (Symbol 0) */ - ret = rfalNfcvPollerInventory(RFAL_NFCV_NUM_SLOTS_1, 0, NULL, &nfcvDevList->InvRes, NULL); - - if(ret == ERR_TIMEOUT) /* Exit if no device found Activity 2.1 9.3.7.2 (Symbol 1) */ - { - return ERR_NONE; - } - if(ret == - ERR_NONE) /* Device found without transmission error/collision Activity 2.1 9.3.7.3 (Symbol 2) */ - { - (*devCnt)++; - return ERR_NONE; - } - - /* A Collision has been identified Activity 2.1 9.3.7.4 (Symbol 3) */ - colPending = true; - colCnt = 1; - - /* Check if the Collision Resolution is set to perform only Collision detection Activity 2.1 9.3.7.5 (Symbol 4)*/ - if(devLimit == 0U) { - return ERR_RF_COLLISION; - } - - platformDelay(RFAL_NFCV_FDT_V_INVENT_NORES); - - /*******************************************************************************/ - /* Collisions pending, Anticollision loop must be executed */ - /*******************************************************************************/ - } else { - /* Advance to 16 slots below without mask. Will give a good chance to identify multiple cards */ - colPending = true; - colCnt = 1; - } - - /* Execute until all collisions are resolved Activity 2.1 9.3.7.18 (Symbol 17) */ - do { - /* Activity 2.1 9.3.7.7 (Symbol 6 / 7) */ - colPending = false; - slotNum = 0; - - do { - if(slotNum == 0U) { - /* Send INVENTORY_REQ with 16 slots Activity 2.1 9.3.7.9 (Symbol 8) */ - ret = rfalNfcvPollerInventory( - RFAL_NFCV_NUM_SLOTS_16, - colFound[colIt].maskLen, - colFound[colIt].maskVal, - &nfcvDevList[(*devCnt)].InvRes, - &rcvdLen); - } else { - ret = rfalISO15693TransceiveEOFAnticollision( - (uint8_t*)&nfcvDevList[(*devCnt)].InvRes, - sizeof(rfalNfcvInventoryRes), - &rcvdLen); - } - slotNum++; - - /*******************************************************************************/ - if(ret != ERR_TIMEOUT) { - if(rcvdLen < - rfalConvBytesToBits( - RFAL_NFCV_INV_RES_LEN + - RFAL_NFCV_CRC_LEN)) { /* If only a partial frame was received make sure the FDT_V_INVENT_NORES is fulfilled */ - platformDelay(RFAL_NFCV_FDT_V_INVENT_NORES); - } - - /* Check if response is a correct frame (no TxRx error) Activity 2.1 9.3.7.11 (Symbol 10)*/ - if((ret == ERR_NONE) || (ret == ERR_PROTO)) { - /* Check if the device found is already on the list and its response is a valid INVENTORY_RES */ - if(rfalNfcvCheckInvRes(nfcvDevList[(*devCnt)].InvRes.RES_FLAG, rcvdLen)) { - /* Activity 2.1 9.3.7.12 (Symbol 11) */ - (*devCnt)++; - } - } else /* Treat everything else as collision */ - { - /* Activity 2.1 9.3.7.17 (Symbol 16) */ - colPending = true; - - /*******************************************************************************/ - /* Ensure that this collision still fits on the container */ - if(colCnt < RFAL_NFCV_MAX_COLL_SUPPORTED) { - /* Store this collision on the container to be resolved later */ - /* Activity 2.1 9.3.7.17 (Symbol 16): add the collision information - * (MASK_VAL + SN) to the list containing the collision information */ - ST_MEMCPY( - colFound[colCnt].maskVal, colFound[colIt].maskVal, RFAL_NFCV_UID_LEN); - colPos = colFound[colIt].maskLen; - colFound[colCnt].maskVal[(colPos / RFAL_BITS_IN_BYTE)] &= - (uint8_t)((1U << (colPos % RFAL_BITS_IN_BYTE)) - 1U); - colFound[colCnt].maskVal[(colPos / RFAL_BITS_IN_BYTE)] |= - (uint8_t)((slotNum - 1U) << (colPos % RFAL_BITS_IN_BYTE)); - colFound[colCnt].maskVal[((colPos / RFAL_BITS_IN_BYTE) + 1U)] = - (uint8_t)((slotNum - 1U) >> (RFAL_BITS_IN_BYTE - (colPos % RFAL_BITS_IN_BYTE))); - - colFound[colCnt].maskLen = (colFound[colIt].maskLen + 4U); - - colCnt++; - } - } - } else { - /* Timeout */ - platformDelay(RFAL_NFCV_FDT_V_INVENT_NORES); - } - - /* Check if devices found have reached device limit Activity 2.1 9.3.7.13 (Symbol 12) */ - if(*devCnt >= devLimit) { - return ERR_NONE; - } - - } while(slotNum < RFAL_NFCV_MAX_SLOTS); /* Slot loop */ - colIt++; - } while(colIt < colCnt); /* Collisions found loop */ - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalNfcvPollerSleepCollisionResolution( - uint8_t devLimit, - rfalNfcvListenDevice* nfcvDevList, - uint8_t* devCnt) { - uint8_t tmpDevCnt; - ReturnCode ret; - uint8_t i; - - if((nfcvDevList == NULL) || (devCnt == NULL)) { - return ERR_PARAM; - } - - *devCnt = 0; - - do { - tmpDevCnt = 0; - ret = rfalNfcvPollerCollisionResolution( - RFAL_COMPLIANCE_MODE_ISO, (devLimit - *devCnt), &nfcvDevList[*devCnt], &tmpDevCnt); - - for(i = *devCnt; i < (*devCnt + tmpDevCnt); i++) { - rfalNfcvPollerSleep(0x00, nfcvDevList[i].InvRes.UID); - nfcvDevList[i].isSleep = true; - } - *devCnt += tmpDevCnt; - } while((ret == ERR_NONE) && (tmpDevCnt > 0U) && (*devCnt < devLimit)); - - return ret; -} - -/*******************************************************************************/ -ReturnCode rfalNfcvPollerSleep(uint8_t flags, const uint8_t* uid) { - ReturnCode ret; - rfalNfcvSlpvReq slpReq; - uint8_t rxBuf; /* dummy buffer, just to perform Rx */ - - if(uid == NULL) { - return ERR_PARAM; - } - - /* Compute SLPV_REQ */ - slpReq.REQ_FLAG = - (flags | - (uint8_t) - RFAL_NFCV_REQ_FLAG_ADDRESS); /* Should be with UID according Digital 2.0 (Candidate) 9.7.1.1 */ - slpReq.CMD = RFAL_NFCV_CMD_SLPV; - ST_MEMCPY(slpReq.UID, uid, RFAL_NFCV_UID_LEN); - - /* NFC Forum device SHALL wait at least FDTVpp to consider the SLPV acknowledged (FDTVpp = FDTVpoll) Digital 2.0 (Candidate) 9.7 9.8.2 */ - ret = rfalTransceiveBlockingTxRx( - (uint8_t*)&slpReq, - sizeof(rfalNfcvSlpvReq), - &rxBuf, - sizeof(rxBuf), - NULL, - RFAL_TXRX_FLAGS_DEFAULT, - RFAL_NFCV_FDT_MAX1); - if(ret != ERR_TIMEOUT) { - return ret; - } - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalNfcvPollerSelect(uint8_t flags, const uint8_t* uid) { - uint16_t rcvLen; - rfalNfcvGenericRes res; - - if(uid == NULL) { - return ERR_PARAM; - } - - return rfalNfcvPollerTransceiveReq( - RFAL_NFCV_CMD_SELECT, - flags, - RFAL_NFCV_PARAM_SKIP, - uid, - NULL, - 0U, - (uint8_t*)&res, - sizeof(rfalNfcvGenericRes), - &rcvLen); -} - -/*******************************************************************************/ -ReturnCode rfalNfcvPollerReadSingleBlock( - uint8_t flags, - const uint8_t* uid, - uint8_t blockNum, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rcvLen) { - uint8_t bn; - - bn = blockNum; - - return rfalNfcvPollerTransceiveReq( - RFAL_NFCV_CMD_READ_SINGLE_BLOCK, - flags, - RFAL_NFCV_PARAM_SKIP, - uid, - &bn, - sizeof(uint8_t), - rxBuf, - rxBufLen, - rcvLen); -} - -/*******************************************************************************/ -ReturnCode rfalNfcvPollerWriteSingleBlock( - uint8_t flags, - const uint8_t* uid, - uint8_t blockNum, - const uint8_t* wrData, - uint8_t blockLen) { - uint8_t data[(RFAL_NFCV_BLOCKNUM_LEN + RFAL_NFCV_MAX_BLOCK_LEN)]; - uint8_t dataLen; - uint16_t rcvLen; - rfalNfcvGenericRes res; - - /* Check for valid parameters */ - if((blockLen == 0U) || (blockLen > (uint8_t)RFAL_NFCV_MAX_BLOCK_LEN) || (wrData == NULL)) { - return ERR_PARAM; - } - - dataLen = 0U; - - /* Compute Request Data */ - data[dataLen++] = blockNum; /* Set Block Number (8 bits) */ - ST_MEMCPY(&data[dataLen], wrData, blockLen); /* Append Block data to write */ - dataLen += blockLen; - - return rfalNfcvPollerTransceiveReq( - RFAL_NFCV_CMD_WRITE_SINGLE_BLOCK, - flags, - RFAL_NFCV_PARAM_SKIP, - uid, - data, - dataLen, - (uint8_t*)&res, - sizeof(rfalNfcvGenericRes), - &rcvLen); -} - -/*******************************************************************************/ -ReturnCode rfalNfcvPollerLockBlock(uint8_t flags, const uint8_t* uid, uint8_t blockNum) { - uint16_t rcvLen; - rfalNfcvGenericRes res; - uint8_t bn; - - bn = blockNum; - - return rfalNfcvPollerTransceiveReq( - RFAL_NFCV_CMD_LOCK_BLOCK, - flags, - RFAL_NFCV_PARAM_SKIP, - uid, - &bn, - sizeof(uint8_t), - (uint8_t*)&res, - sizeof(rfalNfcvGenericRes), - &rcvLen); -} - -/*******************************************************************************/ -ReturnCode rfalNfcvPollerReadMultipleBlocks( - uint8_t flags, - const uint8_t* uid, - uint8_t firstBlockNum, - uint8_t numOfBlocks, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rcvLen) { - uint8_t data[(RFAL_NFCV_BLOCKNUM_LEN + RFAL_NFCV_BLOCKNUM_LEN)]; - uint8_t dataLen; - - dataLen = 0U; - - /* Compute Request Data */ - data[dataLen++] = firstBlockNum; /* Set first Block Number */ - data[dataLen++] = numOfBlocks; /* Set number of blocks to read */ - - return rfalNfcvPollerTransceiveReq( - RFAL_NFCV_CMD_READ_MULTIPLE_BLOCKS, - flags, - RFAL_NFCV_PARAM_SKIP, - uid, - data, - dataLen, - rxBuf, - rxBufLen, - rcvLen); -} - -/*******************************************************************************/ -ReturnCode rfalNfcvPollerWriteMultipleBlocks( - uint8_t flags, - const uint8_t* uid, - uint8_t firstBlockNum, - uint8_t numOfBlocks, - uint8_t* txBuf, - uint16_t txBufLen, - uint8_t blockLen, - const uint8_t* wrData, - uint16_t wrDataLen) { - ReturnCode ret; - uint16_t rcvLen; - uint16_t reqLen; - rfalNfcvGenericRes res; - uint16_t msgIt; - - /* Calculate required buffer length */ - reqLen = - (uint16_t)((uid != NULL) ? (RFAL_NFCV_WR_MUL_REQ_HEADER_LEN + RFAL_NFCV_UID_LEN + wrDataLen) : (RFAL_NFCV_WR_MUL_REQ_HEADER_LEN + wrDataLen)); - - if((reqLen > txBufLen) || (blockLen > (uint8_t)RFAL_NFCV_MAX_BLOCK_LEN) || - ((((uint16_t)numOfBlocks) * (uint16_t)blockLen) != wrDataLen) || (numOfBlocks == 0U) || - (wrData == NULL)) { - return ERR_PARAM; - } - - msgIt = 0; - - /* Compute Request Command */ - txBuf[msgIt++] = (uint8_t)(flags & (~((uint32_t)RFAL_NFCV_REQ_FLAG_ADDRESS))); - txBuf[msgIt++] = RFAL_NFCV_CMD_WRITE_MULTIPLE_BLOCKS; - - /* Check if Request is to be sent in Addressed mode. Select mode flag shall be set by user */ - if(uid != NULL) { - txBuf[RFAL_NFCV_FLAG_POS] |= (uint8_t)RFAL_NFCV_REQ_FLAG_ADDRESS; - ST_MEMCPY(&txBuf[msgIt], uid, RFAL_NFCV_UID_LEN); - msgIt += (uint8_t)RFAL_NFCV_UID_LEN; - } - - txBuf[msgIt++] = firstBlockNum; - txBuf[msgIt++] = (numOfBlocks - 1U); - - if(wrDataLen > 0U) /* MISRA 21.18 */ - { - ST_MEMCPY(&txBuf[msgIt], wrData, wrDataLen); - msgIt += wrDataLen; - } - - /* Transceive Command */ - ret = rfalTransceiveBlockingTxRx( - txBuf, - msgIt, - (uint8_t*)&res, - sizeof(rfalNfcvGenericRes), - &rcvLen, - RFAL_TXRX_FLAGS_DEFAULT, - RFAL_NFCV_FDT_MAX); - - if(ret != ERR_NONE) { - return ret; - } - - /* Check if the response minimum length has been received */ - if(rcvLen < (uint8_t)RFAL_NFCV_FLAG_LEN) { - return ERR_PROTO; - } - - /* Check if an error has been signalled */ - if((res.RES_FLAG & (uint8_t)RFAL_NFCV_RES_FLAG_ERROR) != 0U) { - return rfalNfcvParseError(*res.data); - } - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalNfcvPollerExtendedReadSingleBlock( - uint8_t flags, - const uint8_t* uid, - uint16_t blockNum, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rcvLen) { - uint8_t data[RFAL_NFCV_BLOCKNUM_EXTENDED_LEN]; - uint8_t dataLen; - - dataLen = 0U; - - /* Compute Request Data */ - data[dataLen++] = (uint8_t) - blockNum; /* TS T5T 1.0 BNo is considered as a multi-byte field. TS T5T 1.0 5.1.1.13 multi-byte field follows [DIGITAL]. [DIGITAL] 9.3.1 A multiple byte field is transmitted LSB first. */ - data[dataLen++] = (uint8_t)((blockNum >> 8U) & 0xFFU); - - return rfalNfcvPollerTransceiveReq( - RFAL_NFCV_CMD_EXTENDED_READ_SINGLE_BLOCK, - flags, - RFAL_NFCV_PARAM_SKIP, - uid, - data, - dataLen, - rxBuf, - rxBufLen, - rcvLen); -} - -/*******************************************************************************/ -ReturnCode rfalNfcvPollerExtendedWriteSingleBlock( - uint8_t flags, - const uint8_t* uid, - uint16_t blockNum, - const uint8_t* wrData, - uint8_t blockLen) { - uint8_t data[(RFAL_NFCV_BLOCKNUM_EXTENDED_LEN + RFAL_NFCV_MAX_BLOCK_LEN)]; - uint8_t dataLen; - uint16_t rcvLen; - rfalNfcvGenericRes res; - - /* Check for valid parameters */ - if((blockLen == 0U) || (blockLen > (uint8_t)RFAL_NFCV_MAX_BLOCK_LEN)) { - return ERR_PARAM; - } - - dataLen = 0U; - - /* Compute Request Data */ - data[dataLen++] = (uint8_t) - blockNum; /* TS T5T 1.0 BNo is considered as a multi-byte field. TS T5T 1.0 5.1.1.13 multi-byte field follows [DIGITAL]. [DIGITAL] 9.3.1 A multiple byte field is transmitted LSB first. */ - data[dataLen++] = (uint8_t)((blockNum >> 8U) & 0xFFU); - ST_MEMCPY(&data[dataLen], wrData, blockLen); /* Append Block data to write */ - dataLen += blockLen; - - return rfalNfcvPollerTransceiveReq( - RFAL_NFCV_CMD_EXTENDED_WRITE_SINGLE_BLOCK, - flags, - RFAL_NFCV_PARAM_SKIP, - uid, - data, - dataLen, - (uint8_t*)&res, - sizeof(rfalNfcvGenericRes), - &rcvLen); -} - -/*******************************************************************************/ -ReturnCode - rfalNfcvPollerExtendedLockSingleBlock(uint8_t flags, const uint8_t* uid, uint16_t blockNum) { - uint8_t data[RFAL_NFCV_BLOCKNUM_EXTENDED_LEN]; - uint8_t dataLen; - uint16_t rcvLen; - rfalNfcvGenericRes res; - - dataLen = 0U; - - /* Compute Request Data */ - data[dataLen++] = (uint8_t) - blockNum; /* TS T5T 1.0 BNo is considered as a multi-byte field. TS T5T 1.0 5.1.1.13 multi-byte field follows [DIGITAL]. [DIGITAL] 9.3.1 A multiple byte field is transmitted LSB first. */ - data[dataLen++] = (uint8_t)((blockNum >> 8U) & 0xFFU); - - return rfalNfcvPollerTransceiveReq( - RFAL_NFCV_CMD_EXTENDED_LOCK_SINGLE_BLOCK, - flags, - RFAL_NFCV_PARAM_SKIP, - uid, - data, - dataLen, - (uint8_t*)&res, - sizeof(rfalNfcvGenericRes), - &rcvLen); -} - -/*******************************************************************************/ -ReturnCode rfalNfcvPollerExtendedReadMultipleBlocks( - uint8_t flags, - const uint8_t* uid, - uint16_t firstBlockNum, - uint16_t numOfBlocks, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rcvLen) { - uint8_t data[(RFAL_NFCV_BLOCKNUM_EXTENDED_LEN + RFAL_NFCV_BLOCKNUM_EXTENDED_LEN)]; - uint8_t dataLen; - - dataLen = 0U; - - /* Compute Request Data */ - data[dataLen++] = (uint8_t)((firstBlockNum >> 0U) & 0xFFU); - data[dataLen++] = (uint8_t)((firstBlockNum >> 8U) & 0xFFU); - data[dataLen++] = (uint8_t)((numOfBlocks >> 0U) & 0xFFU); - data[dataLen++] = (uint8_t)((numOfBlocks >> 8U) & 0xFFU); - - return rfalNfcvPollerTransceiveReq( - RFAL_NFCV_CMD_EXTENDED_READ_MULTIPLE_BLOCK, - flags, - RFAL_NFCV_PARAM_SKIP, - uid, - data, - dataLen, - rxBuf, - rxBufLen, - rcvLen); -} - -/*******************************************************************************/ -ReturnCode rfalNfcvPollerExtendedWriteMultipleBlocks( - uint8_t flags, - const uint8_t* uid, - uint16_t firstBlockNum, - uint16_t numOfBlocks, - uint8_t* txBuf, - uint16_t txBufLen, - uint8_t blockLen, - const uint8_t* wrData, - uint16_t wrDataLen) { - ReturnCode ret; - uint16_t rcvLen; - uint16_t reqLen; - rfalNfcvGenericRes res; - uint16_t msgIt; - uint16_t nBlocks; - - /* Calculate required buffer length */ - reqLen = - ((uid != NULL) ? (RFAL_NFCV_WR_MUL_REQ_HEADER_LEN + RFAL_NFCV_UID_LEN + wrDataLen) : - (RFAL_NFCV_WR_MUL_REQ_HEADER_LEN + wrDataLen)); - - if((reqLen > txBufLen) || (blockLen > (uint8_t)RFAL_NFCV_MAX_BLOCK_LEN) || - (((uint16_t)numOfBlocks * (uint16_t)blockLen) != wrDataLen) || (numOfBlocks == 0U)) { - return ERR_PARAM; - } - - msgIt = 0; - nBlocks = (numOfBlocks - 1U); - - /* Compute Request Command */ - txBuf[msgIt++] = (uint8_t)(flags & (~((uint32_t)RFAL_NFCV_REQ_FLAG_ADDRESS))); - txBuf[msgIt++] = RFAL_NFCV_CMD_EXTENDED_WRITE_MULTIPLE_BLOCK; - - /* Check if Request is to be sent in Addressed mode. Select mode flag shall be set by user */ - if(uid != NULL) { - txBuf[RFAL_NFCV_FLAG_POS] |= (uint8_t)RFAL_NFCV_REQ_FLAG_ADDRESS; - ST_MEMCPY(&txBuf[msgIt], uid, RFAL_NFCV_UID_LEN); - msgIt += (uint8_t)RFAL_NFCV_UID_LEN; - } - - txBuf[msgIt++] = (uint8_t)((firstBlockNum >> 0) & 0xFFU); - txBuf[msgIt++] = (uint8_t)((firstBlockNum >> 8) & 0xFFU); - txBuf[msgIt++] = (uint8_t)((nBlocks >> 0) & 0xFFU); - txBuf[msgIt++] = (uint8_t)((nBlocks >> 8) & 0xFFU); - - if(wrDataLen > 0U) /* MISRA 21.18 */ - { - ST_MEMCPY(&txBuf[msgIt], wrData, wrDataLen); - msgIt += wrDataLen; - } - - /* Transceive Command */ - ret = rfalTransceiveBlockingTxRx( - txBuf, - msgIt, - (uint8_t*)&res, - sizeof(rfalNfcvGenericRes), - &rcvLen, - RFAL_TXRX_FLAGS_DEFAULT, - RFAL_NFCV_FDT_MAX); - - if(ret != ERR_NONE) { - return ret; - } - - /* Check if the response minimum length has been received */ - if(rcvLen < (uint8_t)RFAL_NFCV_FLAG_LEN) { - return ERR_PROTO; - } - - /* Check if an error has been signalled */ - if((res.RES_FLAG & (uint8_t)RFAL_NFCV_RES_FLAG_ERROR) != 0U) { - return rfalNfcvParseError(*res.data); - } - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalNfcvPollerGetSystemInformation( - uint8_t flags, - const uint8_t* uid, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rcvLen) { - return rfalNfcvPollerTransceiveReq( - RFAL_NFCV_CMD_GET_SYS_INFO, - flags, - RFAL_NFCV_PARAM_SKIP, - uid, - NULL, - 0U, - rxBuf, - rxBufLen, - rcvLen); -} - -/*******************************************************************************/ -ReturnCode rfalNfcvPollerExtendedGetSystemInformation( - uint8_t flags, - const uint8_t* uid, - uint8_t requestField, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rcvLen) { - return rfalNfcvPollerTransceiveReq( - RFAL_NFCV_CMD_EXTENDED_GET_SYS_INFO, - flags, - requestField, - uid, - NULL, - 0U, - rxBuf, - rxBufLen, - rcvLen); -} - -/*******************************************************************************/ -ReturnCode rfalNfcvPollerTransceiveReq( - uint8_t cmd, - uint8_t flags, - uint8_t param, - const uint8_t* uid, - const uint8_t* data, - uint16_t dataLen, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rcvLen) { - ReturnCode ret; - rfalNfcvGenericReq req; - uint8_t msgIt; - rfalBitRate rxBR; - bool fastMode; - - msgIt = 0; - fastMode = false; - - /* Check for valid parameters */ - if((rxBuf == NULL) || (rcvLen == NULL) || ((dataLen > 0U) && (data == NULL)) || - (dataLen > ((uid != NULL) ? RFAL_NFCV_MAX_GEN_DATA_LEN : - (RFAL_NFCV_MAX_GEN_DATA_LEN - RFAL_NFCV_UID_LEN)))) { - return ERR_PARAM; - } - - /* Check if the command is an ST's Fast command */ - if((cmd == (uint8_t)RFAL_NFCV_CMD_FAST_READ_SINGLE_BLOCK) || - (cmd == (uint8_t)RFAL_NFCV_CMD_FAST_EXTENDED_READ_SINGLE_BLOCK) || - (cmd == (uint8_t)RFAL_NFCV_CMD_FAST_READ_MULTIPLE_BLOCKS) || - (cmd == (uint8_t)RFAL_NFCV_CMD_FAST_EXTENDED_READ_MULTIPLE_BLOCKS) || - (cmd == (uint8_t)RFAL_NFCV_CMD_FAST_WRITE_MESSAGE) || - (cmd == (uint8_t)RFAL_NFCV_CMD_FAST_READ_MESSAGE_LENGTH) || - (cmd == (uint8_t)RFAL_NFCV_CMD_FAST_READ_MESSAGE) || - (cmd == (uint8_t)RFAL_NFCV_CMD_FAST_READ_DYN_CONFIGURATION) || - (cmd == (uint8_t)RFAL_NFCV_CMD_FAST_WRITE_DYN_CONFIGURATION)) { - /* Store current Rx bit rate and move to fast mode */ - rfalGetBitRate(NULL, &rxBR); - rfalSetBitRate(RFAL_BR_KEEP, RFAL_BR_52p97); - - fastMode = true; - } - - /* Compute Request Command */ - req.REQ_FLAG = (uint8_t)(flags & (~((uint32_t)RFAL_NFCV_REQ_FLAG_ADDRESS))); - req.CMD = cmd; - - /* Prepend parameter on ceratin proprietary requests: IC Manuf, Parameters */ - if(param != RFAL_NFCV_PARAM_SKIP) { - req.payload.data[msgIt++] = param; - } - - /* Check if Request is to be sent in Addressed mode. Select mode flag shall be set by user */ - if(uid != NULL) { - req.REQ_FLAG |= (uint8_t)RFAL_NFCV_REQ_FLAG_ADDRESS; - ST_MEMCPY(&req.payload.data[msgIt], uid, RFAL_NFCV_UID_LEN); - msgIt += RFAL_NFCV_UID_LEN; - } - - if(dataLen > 0U) { - ST_MEMCPY(&req.payload.data[msgIt], data, dataLen); - msgIt += (uint8_t)dataLen; - } - - /* Transceive Command */ - ret = rfalTransceiveBlockingTxRx( - (uint8_t*)&req, - (RFAL_NFCV_CMD_LEN + RFAL_NFCV_FLAG_LEN + (uint16_t)msgIt), - rxBuf, - rxBufLen, - rcvLen, - RFAL_TXRX_FLAGS_DEFAULT, - RFAL_NFCV_FDT_MAX); - - /* If the Option Flag is set in certain commands an EOF needs to be sent after 20ms to retrieve the VICC response ISO15693-3 2009 10.4.2 & 10.4.3 & 10.4.5 */ - if(((flags & (uint8_t)RFAL_NFCV_REQ_FLAG_OPTION) != 0U) && - ((cmd == (uint8_t)RFAL_NFCV_CMD_WRITE_SINGLE_BLOCK) || - (cmd == (uint8_t)RFAL_NFCV_CMD_WRITE_MULTIPLE_BLOCKS) || - (cmd == (uint8_t)RFAL_NFCV_CMD_LOCK_BLOCK) || - (cmd == (uint8_t)RFAL_NFCV_CMD_EXTENDED_WRITE_SINGLE_BLOCK) || - (cmd == (uint8_t)RFAL_NFCV_CMD_EXTENDED_LOCK_SINGLE_BLOCK) || - (cmd == (uint8_t)RFAL_NFCV_CMD_EXTENDED_WRITE_MULTIPLE_BLOCK))) { - ret = rfalISO15693TransceiveEOF(rxBuf, (uint8_t)rxBufLen, rcvLen); - } - - /* Restore Rx BitRate */ - if(fastMode) { - rfalSetBitRate(RFAL_BR_KEEP, rxBR); - } - - if(ret != ERR_NONE) { - return ret; - } - - /* Check if the response minimum length has been received */ - if((*rcvLen) < (uint8_t)RFAL_NFCV_FLAG_LEN) { - return ERR_PROTO; - } - - /* Check if an error has been signalled */ - if((rxBuf[RFAL_NFCV_FLAG_POS] & (uint8_t)RFAL_NFCV_RES_FLAG_ERROR) != 0U) { - return rfalNfcvParseError(rxBuf[RFAL_NFCV_DATASTART_POS]); - } - - return ERR_NONE; -} - -#endif /* RFAL_FEATURE_NFCV */ diff --git a/lib/ST25RFAL002/source/rfal_st25tb.c b/lib/ST25RFAL002/source/rfal_st25tb.c deleted file mode 100644 index 00f699104b5..00000000000 --- a/lib/ST25RFAL002/source/rfal_st25tb.c +++ /dev/null @@ -1,563 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

    © COPYRIGHT 2020 STMicroelectronics

    - * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R391x firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file rfal_st25tb.c - * - * \author Gustavo Patricio - * - * \brief Implementation of ST25TB interface - * - */ - -/* - ****************************************************************************** - * INCLUDES - ****************************************************************************** - */ -#include "rfal_st25tb.h" -#include "utils.h" - -/* - ****************************************************************************** - * ENABLE SWITCH - ****************************************************************************** - */ -#ifndef RFAL_FEATURE_ST25TB -#define RFAL_FEATURE_ST25TB false /* ST25TB module configuration missing. Disabled by default */ -#endif - -#if RFAL_FEATURE_ST25TB - -/* - ****************************************************************************** - * GLOBAL DEFINES - ****************************************************************************** - */ - -#define RFAL_ST25TB_CMD_LEN 1U /*!< ST25TB length of a command */ -#define RFAL_ST25TB_SLOTS 16U /*!< ST25TB number of slots */ -#define RFAL_ST25TB_SLOTNUM_MASK 0x0FU /*!< ST25TB Slot Number bit mask on SlotMarker */ -#define RFAL_ST25TB_SLOTNUM_SHIFT 4U /*!< ST25TB Slot Number shift on SlotMarker */ - -#define RFAL_ST25TB_INITIATE_CMD1 0x06U /*!< ST25TB Initiate command byte1 */ -#define RFAL_ST25TB_INITIATE_CMD2 0x00U /*!< ST25TB Initiate command byte2 */ -#define RFAL_ST25TB_PCALL_CMD1 0x06U /*!< ST25TB Pcall16 command byte1 */ -#define RFAL_ST25TB_PCALL_CMD2 0x04U /*!< ST25TB Pcall16 command byte2 */ -#define RFAL_ST25TB_SELECT_CMD 0x0EU /*!< ST25TB Select command */ -#define RFAL_ST25TB_GET_UID_CMD 0x0BU /*!< ST25TB Get UID command */ -#define RFAL_ST25TB_COMPLETION_CMD 0x0FU /*!< ST25TB Completion command */ -#define RFAL_ST25TB_RESET_INV_CMD 0x0CU /*!< ST25TB Reset to Inventory command */ -#define RFAL_ST25TB_READ_BLOCK_CMD 0x08U /*!< ST25TB Read Block command */ -#define RFAL_ST25TB_WRITE_BLOCK_CMD 0x09U /*!< ST25TB Write Block command */ - -#define RFAL_ST25TB_T0 2157U /*!< ST25TB t0 159 us ST25TB RF characteristics */ -#define RFAL_ST25TB_T1 2048U /*!< ST25TB t1 151 us ST25TB RF characteristics */ - -#define RFAL_ST25TB_FWT \ - (RFAL_ST25TB_T0 + RFAL_ST25TB_T1) /*!< ST25TB FWT = T0 + T1 */ -#define RFAL_ST25TB_TW rfalConvMsTo1fc(7U) /*!< ST25TB TW : Programming time for write max 7ms */ - -/* - ****************************************************************************** - * GLOBAL MACROS - ****************************************************************************** - */ - -/* -****************************************************************************** -* GLOBAL TYPES -****************************************************************************** -*/ - -/*! Initiate Request */ -typedef struct { - uint8_t cmd1; /*!< Initiate Request cmd1: 0x06 */ - uint8_t cmd2; /*!< Initiate Request cmd2: 0x00 */ -} rfalSt25tbInitiateReq; - -/*! Pcall16 Request */ -typedef struct { - uint8_t cmd1; /*!< Pcal16 Request cmd1: 0x06 */ - uint8_t cmd2; /*!< Pcal16 Request cmd2: 0x04 */ -} rfalSt25tbPcallReq; - -/*! Select Request */ -typedef struct { - uint8_t cmd; /*!< Select Request cmd: 0x0E */ - uint8_t chipId; /*!< Chip ID */ -} rfalSt25tbSelectReq; - -/*! Read Block Request */ -typedef struct { - uint8_t cmd; /*!< Select Request cmd: 0x08 */ - uint8_t address; /*!< Block address */ -} rfalSt25tbReadBlockReq; - -/*! Write Block Request */ -typedef struct { - uint8_t cmd; /*!< Select Request cmd: 0x09 */ - uint8_t address; /*!< Block address */ - rfalSt25tbBlock data; /*!< Block Data */ -} rfalSt25tbWriteBlockReq; - -/* -****************************************************************************** -* LOCAL FUNCTION PROTOTYPES -****************************************************************************** -*/ -/*! - ***************************************************************************** - * \brief ST25TB Poller Do Collision Resolution - * - * This method performs ST25TB Collision resolution loop for each slot - * - * \param[in] devLimit : device limit value, and size st25tbDevList - * \param[out] st25tbDevList : ST35TB listener device info - * \param[out] devCnt : Devices found counter - * - * \return colPending : true if a collision was detected - ***************************************************************************** - */ -static bool rfalSt25tbPollerDoCollisionResolution( - uint8_t devLimit, - rfalSt25tbListenDevice* st25tbDevList, - uint8_t* devCnt); - -/* -****************************************************************************** -* LOCAL FUNCTION PROTOTYPES -****************************************************************************** -*/ - -static bool rfalSt25tbPollerDoCollisionResolution( - uint8_t devLimit, - rfalSt25tbListenDevice* st25tbDevList, - uint8_t* devCnt) { - uint8_t i; - uint8_t chipId; - ReturnCode ret; - bool col; - - col = false; - - for(i = 0; i < RFAL_ST25TB_SLOTS; i++) { - platformDelay(1); /* Wait t2: Answer to new request delay */ - - if(i == 0U) { - /* Step 2: Send Pcall16 */ - ret = rfalSt25tbPollerPcall(&chipId); - } else { - /* Step 3-17: Send Pcall16 */ - ret = rfalSt25tbPollerSlotMarker(i, &chipId); - } - - if(ret == ERR_NONE) { - /* Found another device */ - st25tbDevList[*devCnt].chipID = chipId; - st25tbDevList[*devCnt].isDeselected = false; - - /* Select Device, retrieve its UID */ - ret = rfalSt25tbPollerSelect(chipId); - - /* By Selecting this device, the previous gets Deselected */ - if((*devCnt) > 0U) { - st25tbDevList[(*devCnt) - 1U].isDeselected = true; - } - - if(ERR_NONE == ret) { - rfalSt25tbPollerGetUID(&st25tbDevList[*devCnt].UID); - } - - if(ERR_NONE == ret) { - (*devCnt)++; - } - } else if((ret == ERR_CRC) || (ret == ERR_FRAMING)) { - col = true; - } else { - /* MISRA 15.7 - Empty else */ - } - - if(*devCnt >= devLimit) { - break; - } - } - return col; -} - -/* -****************************************************************************** -* LOCAL VARIABLES -****************************************************************************** -*/ - -/* -****************************************************************************** -* GLOBAL FUNCTIONS -****************************************************************************** -*/ - -/*******************************************************************************/ -ReturnCode rfalSt25tbPollerInitialize(void) { - return rfalNfcbPollerInitialize(); -} - -/*******************************************************************************/ -ReturnCode rfalSt25tbPollerCheckPresence(uint8_t* chipId) { - ReturnCode ret; - uint8_t chipIdRes; - - chipIdRes = 0x00; - - /* Send Initiate Request */ - ret = rfalSt25tbPollerInitiate(&chipIdRes); - - /* Check if a transmission error was detected */ - if((ret == ERR_CRC) || (ret == ERR_FRAMING)) { - return ERR_NONE; - } - - /* Copy chip ID if requested */ - if(chipId != NULL) { - *chipId = chipIdRes; - } - - return ret; -} - -/*******************************************************************************/ -ReturnCode rfalSt25tbPollerInitiate(uint8_t* chipId) { - ReturnCode ret; - uint16_t rxLen; - rfalSt25tbInitiateReq initiateReq; - uint8_t rxBuf - [RFAL_ST25TB_CHIP_ID_LEN + - RFAL_ST25TB_CRC_LEN]; /* In case we receive less data that CRC, RF layer will not remove the CRC from buffer */ - - /* Compute Initiate Request */ - initiateReq.cmd1 = RFAL_ST25TB_INITIATE_CMD1; - initiateReq.cmd2 = RFAL_ST25TB_INITIATE_CMD2; - - /* Send Initiate Request */ - ret = rfalTransceiveBlockingTxRx( - (uint8_t*)&initiateReq, - sizeof(rfalSt25tbInitiateReq), - (uint8_t*)rxBuf, - sizeof(rxBuf), - &rxLen, - RFAL_TXRX_FLAGS_DEFAULT, - RFAL_ST25TB_FWT); - - /* Check for valid Select Response */ - if((ret == ERR_NONE) && (rxLen != RFAL_ST25TB_CHIP_ID_LEN)) { - return ERR_PROTO; - } - - /* Copy chip ID if requested */ - if(chipId != NULL) { - *chipId = *rxBuf; - } - - return ret; -} - -/*******************************************************************************/ -ReturnCode rfalSt25tbPollerPcall(uint8_t* chipId) { - ReturnCode ret; - uint16_t rxLen; - rfalSt25tbPcallReq pcallReq; - - /* Compute Pcal16 Request */ - pcallReq.cmd1 = RFAL_ST25TB_PCALL_CMD1; - pcallReq.cmd2 = RFAL_ST25TB_PCALL_CMD2; - - /* Send Pcal16 Request */ - ret = rfalTransceiveBlockingTxRx( - (uint8_t*)&pcallReq, - sizeof(rfalSt25tbPcallReq), - (uint8_t*)chipId, - RFAL_ST25TB_CHIP_ID_LEN, - &rxLen, - RFAL_TXRX_FLAGS_DEFAULT, - RFAL_ST25TB_FWT); - - /* Check for valid Select Response */ - if((ret == ERR_NONE) && (rxLen != RFAL_ST25TB_CHIP_ID_LEN)) { - return ERR_PROTO; - } - - return ret; -} - -/*******************************************************************************/ -ReturnCode rfalSt25tbPollerSlotMarker(uint8_t slotNum, uint8_t* chipIdRes) { - ReturnCode ret; - uint16_t rxLen; - uint8_t slotMarker; - - if((slotNum == 0U) || (slotNum > 15U)) { - return ERR_PARAM; - } - - /* Compute SlotMarker */ - slotMarker = - (((slotNum & RFAL_ST25TB_SLOTNUM_MASK) << RFAL_ST25TB_SLOTNUM_SHIFT) | - RFAL_ST25TB_PCALL_CMD1); - - /* Send SlotMarker */ - ret = rfalTransceiveBlockingTxRx( - (uint8_t*)&slotMarker, - RFAL_ST25TB_CMD_LEN, - (uint8_t*)chipIdRes, - RFAL_ST25TB_CHIP_ID_LEN, - &rxLen, - RFAL_TXRX_FLAGS_DEFAULT, - RFAL_ST25TB_FWT); - - /* Check for valid ChipID Response */ - if((ret == ERR_NONE) && (rxLen != RFAL_ST25TB_CHIP_ID_LEN)) { - return ERR_PROTO; - } - - return ret; -} - -/*******************************************************************************/ -ReturnCode rfalSt25tbPollerSelect(uint8_t chipId) { - ReturnCode ret; - uint16_t rxLen; - rfalSt25tbSelectReq selectReq; - uint8_t chipIdRes; - - /* Compute Select Request */ - selectReq.cmd = RFAL_ST25TB_SELECT_CMD; - selectReq.chipId = chipId; - - /* Send Select Request */ - ret = rfalTransceiveBlockingTxRx( - (uint8_t*)&selectReq, - sizeof(rfalSt25tbSelectReq), - (uint8_t*)&chipIdRes, - RFAL_ST25TB_CHIP_ID_LEN, - &rxLen, - RFAL_TXRX_FLAGS_DEFAULT, - RFAL_ST25TB_FWT); - - /* Check for valid Select Response */ - if((ret == ERR_NONE) && ((rxLen != RFAL_ST25TB_CHIP_ID_LEN) || (chipIdRes != chipId))) { - return ERR_PROTO; - } - - return ret; -} - -/*******************************************************************************/ -ReturnCode rfalSt25tbPollerGetUID(rfalSt25tbUID* UID) { - ReturnCode ret; - uint16_t rxLen; - uint8_t getUidReq; - - /* Compute Get UID Request */ - getUidReq = RFAL_ST25TB_GET_UID_CMD; - - /* Send Select Request */ - ret = rfalTransceiveBlockingTxRx( - (uint8_t*)&getUidReq, - RFAL_ST25TB_CMD_LEN, - (uint8_t*)UID, - sizeof(rfalSt25tbUID), - &rxLen, - RFAL_TXRX_FLAGS_DEFAULT, - RFAL_ST25TB_FWT); - - /* Check for valid UID Response */ - if((ret == ERR_NONE) && (rxLen != RFAL_ST25TB_UID_LEN)) { - return ERR_PROTO; - } - - return ret; -} - -/*******************************************************************************/ -ReturnCode rfalSt25tbPollerCollisionResolution( - uint8_t devLimit, - rfalSt25tbListenDevice* st25tbDevList, - uint8_t* devCnt) { - uint8_t chipId; - ReturnCode ret; - bool detected; /* collision or device was detected */ - - if((st25tbDevList == NULL) || (devCnt == NULL) || (devLimit == 0U)) { - return ERR_PARAM; - } - - *devCnt = 0; - - /* Step 1: Send Initiate */ - ret = rfalSt25tbPollerInitiate(&chipId); - if(ret == ERR_NONE) { - /* If only 1 answer is detected */ - st25tbDevList[*devCnt].chipID = chipId; - st25tbDevList[*devCnt].isDeselected = false; - - /* Retrieve its UID and keep it Selected*/ - ret = rfalSt25tbPollerSelect(chipId); - - if(ERR_NONE == ret) { - ret = rfalSt25tbPollerGetUID(&st25tbDevList[*devCnt].UID); - } - - if(ERR_NONE == ret) { - (*devCnt)++; - } - } - /* Always proceed to Pcall16 anticollision as phase differences of tags can lead to no tag recognized, even if there is one */ - if(*devCnt < devLimit) { - /* Multiple device responses */ - do { - detected = rfalSt25tbPollerDoCollisionResolution(devLimit, st25tbDevList, devCnt); - } while((detected == true) && (*devCnt < devLimit)); - } - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalSt25tbPollerReadBlock(uint8_t blockAddress, rfalSt25tbBlock* blockData) { - ReturnCode ret; - uint16_t rxLen; - rfalSt25tbReadBlockReq readBlockReq; - - /* Compute Read Block Request */ - readBlockReq.cmd = RFAL_ST25TB_READ_BLOCK_CMD; - readBlockReq.address = blockAddress; - - /* Send Read Block Request */ - ret = rfalTransceiveBlockingTxRx( - (uint8_t*)&readBlockReq, - sizeof(rfalSt25tbReadBlockReq), - (uint8_t*)blockData, - sizeof(rfalSt25tbBlock), - &rxLen, - RFAL_TXRX_FLAGS_DEFAULT, - RFAL_ST25TB_FWT); - - /* Check for valid UID Response */ - if((ret == ERR_NONE) && (rxLen != RFAL_ST25TB_BLOCK_LEN)) { - return ERR_PROTO; - } - - return ret; -} - -/*******************************************************************************/ -ReturnCode rfalSt25tbPollerWriteBlock(uint8_t blockAddress, const rfalSt25tbBlock* blockData) { - ReturnCode ret; - uint16_t rxLen; - rfalSt25tbWriteBlockReq writeBlockReq; - rfalSt25tbBlock tmpBlockData; - - /* Compute Write Block Request */ - writeBlockReq.cmd = RFAL_ST25TB_WRITE_BLOCK_CMD; - writeBlockReq.address = blockAddress; - ST_MEMCPY(&writeBlockReq.data, blockData, RFAL_ST25TB_BLOCK_LEN); - - /* Send Write Block Request */ - ret = rfalTransceiveBlockingTxRx( - (uint8_t*)&writeBlockReq, - sizeof(rfalSt25tbWriteBlockReq), - tmpBlockData, - RFAL_ST25TB_BLOCK_LEN, - &rxLen, - RFAL_TXRX_FLAGS_DEFAULT, - (RFAL_ST25TB_FWT + RFAL_ST25TB_TW)); - - /* Check if there was any error besides timeout */ - if(ret != ERR_TIMEOUT) { - /* Check if an unexpected answer was received */ - if(ret == ERR_NONE) { - return ERR_PROTO; - } - - /* Check whether a transmission error occurred */ - if((ret != ERR_CRC) && (ret != ERR_FRAMING) && (ret != ERR_NOMEM) && - (ret != ERR_RF_COLLISION)) { - return ret; - } - - /* If a transmission error occurred (maybe noise while committing data) wait maximum programming time and verify data afterwards */ - rfalSetGT((RFAL_ST25TB_FWT + RFAL_ST25TB_TW)); - rfalFieldOnAndStartGT(); - } - - ret = rfalSt25tbPollerReadBlock(blockAddress, &tmpBlockData); - if(ret == ERR_NONE) { - if(ST_BYTECMP(&tmpBlockData, blockData, RFAL_ST25TB_BLOCK_LEN) == 0) { - return ERR_NONE; - } - return ERR_PROTO; - } - return ret; -} - -/*******************************************************************************/ -ReturnCode rfalSt25tbPollerCompletion(void) { - uint8_t completionReq; - - /* Compute Completion Request */ - completionReq = RFAL_ST25TB_COMPLETION_CMD; - - /* Send Completion Request, no response is expected */ - return rfalTransceiveBlockingTxRx( - (uint8_t*)&completionReq, - RFAL_ST25TB_CMD_LEN, - NULL, - 0, - NULL, - RFAL_TXRX_FLAGS_DEFAULT, - RFAL_ST25TB_FWT); -} - -/*******************************************************************************/ -ReturnCode rfalSt25tbPollerResetToInventory(void) { - uint8_t resetInvReq; - - /* Compute Completion Request */ - resetInvReq = RFAL_ST25TB_RESET_INV_CMD; - - /* Send Completion Request, no response is expected */ - return rfalTransceiveBlockingTxRx( - (uint8_t*)&resetInvReq, - RFAL_ST25TB_CMD_LEN, - NULL, - 0, - NULL, - RFAL_TXRX_FLAGS_DEFAULT, - RFAL_ST25TB_FWT); -} - -#endif /* RFAL_FEATURE_ST25TB */ diff --git a/lib/ST25RFAL002/source/rfal_st25xv.c b/lib/ST25RFAL002/source/rfal_st25xv.c deleted file mode 100644 index 56c9ccb6fae..00000000000 --- a/lib/ST25RFAL002/source/rfal_st25xv.c +++ /dev/null @@ -1,818 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

    © COPYRIGHT 2020 STMicroelectronics

    - * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R391x firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file rfal_st25xv.c - * - * \author Gustavo Patricio - * - * \brief NFC-V ST25 NFC-V Tag specific features - * - * This module provides support for ST's specific features available on - * NFC-V (ISO15693) tag families: ST25D, ST25TV, M24LR - * - */ - -/* - ****************************************************************************** - * INCLUDES - ****************************************************************************** - */ -#include "rfal_st25xv.h" -#include "rfal_nfcv.h" -#include "utils.h" - -/* - ****************************************************************************** - * ENABLE SWITCH - ****************************************************************************** - */ - -#ifndef RFAL_FEATURE_ST25xV -#define RFAL_FEATURE_ST25xV false /* ST25xV module configuration missing. Disabled by default */ -#endif - -#if RFAL_FEATURE_ST25xV - -/* - ****************************************************************************** - * GLOBAL DEFINES - ****************************************************************************** - */ - -#define RFAL_ST25xV_READ_CONFIG_LEN \ - 2U /*!< READ CONFIGURATION length */ -#define RFAL_ST25xV_READ_MSG_LEN_LEN \ - 2U /*!< READ MESSAGE LENGTH length */ -#define RFAL_ST25xV_CONF_POINTER_LEN \ - 1U /*!< READ/WRITE CONFIGURATION Pointer length */ -#define RFAL_ST25xV_CONF_REGISTER_LEN \ - 1U /*!< READ/WRITE CONFIGURATION Register length */ -#define RFAL_ST25xV_PWDNUM_LEN \ - 1U /*!< Password Number length */ -#define RFAL_ST25xV_PWD_LEN \ - 8U /*!< Password length */ -#define RFAL_ST25xV_MBPOINTER_LEN \ - 1U /*!< Read Message MBPointer length */ -#define RFAL_ST25xV_NUMBYTES_LEN \ - 1U /*!< Read Message Number of Bytes length */ - -#define RFAL_ST25TV02K_TBOOT_RF \ - 1U /*!< RF Boot time (Minimum time from carrier generation to first data) */ -#define RFAL_ST25TV02K_TRF_OFF \ - 2U /*!< RF OFF time */ - -#define RFAL_ST25xV_FDT_POLL_MAX \ - rfalConvMsTo1fc(20) /*!< Maximum Wait time FDTV,EOF 20 ms Digital 2.1 B.5 */ -#define RFAL_NFCV_FLAG_POS \ - 0U /*!< Flag byte position */ -#define RFAL_NFCV_FLAG_LEN \ - 1U /*!< Flag byte length */ - -/* -****************************************************************************** -* LOCAL FUNCTION PROTOTYPES -****************************************************************************** -*/ - -static ReturnCode rfalST25xVPollerGenericReadConfiguration( - uint8_t cmd, - uint8_t flags, - const uint8_t* uid, - uint8_t pointer, - uint8_t* regValue); -static ReturnCode rfalST25xVPollerGenericWriteConfiguration( - uint8_t cmd, - uint8_t flags, - const uint8_t* uid, - uint8_t pointer, - uint8_t regValue); -static ReturnCode rfalST25xVPollerGenericReadMessageLength( - uint8_t cmd, - uint8_t flags, - const uint8_t* uid, - uint8_t* msgLen); -static ReturnCode rfalST25xVPollerGenericReadMessage( - uint8_t cmd, - uint8_t flags, - const uint8_t* uid, - uint8_t mbPointer, - uint8_t numBytes, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rcvLen); -static ReturnCode rfalST25xVPollerGenericWriteMessage( - uint8_t cmd, - uint8_t flags, - const uint8_t* uid, - uint8_t msgLen, - const uint8_t* msgData, - uint8_t* txBuf, - uint16_t txBufLen); -/* -****************************************************************************** -* LOCAL FUNCTIONS -****************************************************************************** -*/ - -/*******************************************************************************/ -static ReturnCode rfalST25xVPollerGenericReadConfiguration( - uint8_t cmd, - uint8_t flags, - const uint8_t* uid, - uint8_t pointer, - uint8_t* regValue) { - ReturnCode ret; - uint8_t p; - uint16_t rcvLen; - rfalNfcvGenericRes res; - - if(regValue == NULL) { - return ERR_PARAM; - } - - p = pointer; - - ret = rfalNfcvPollerTransceiveReq( - cmd, - flags, - RFAL_NFCV_ST_IC_MFG_CODE, - uid, - &p, - sizeof(uint8_t), - (uint8_t*)&res, - sizeof(rfalNfcvGenericRes), - &rcvLen); - if(ret == ERR_NONE) { - if(rcvLen < RFAL_ST25xV_READ_CONFIG_LEN) { - ret = ERR_PROTO; - } else { - *regValue = res.data[0]; - } - } - return ret; -} - -/*******************************************************************************/ -static ReturnCode rfalST25xVPollerGenericWriteConfiguration( - uint8_t cmd, - uint8_t flags, - const uint8_t* uid, - uint8_t pointer, - uint8_t regValue) { - uint8_t data[RFAL_ST25xV_CONF_POINTER_LEN + RFAL_ST25xV_CONF_REGISTER_LEN]; - uint8_t dataLen; - uint16_t rcvLen; - rfalNfcvGenericRes res; - - dataLen = 0U; - - data[dataLen++] = pointer; - data[dataLen++] = regValue; - - return rfalNfcvPollerTransceiveReq( - cmd, - flags, - RFAL_NFCV_ST_IC_MFG_CODE, - uid, - data, - dataLen, - (uint8_t*)&res, - sizeof(rfalNfcvGenericRes), - &rcvLen); -} - -/*******************************************************************************/ -static ReturnCode rfalST25xVPollerGenericReadMessageLength( - uint8_t cmd, - uint8_t flags, - const uint8_t* uid, - uint8_t* msgLen) { - ReturnCode ret; - uint16_t rcvLen; - rfalNfcvGenericRes res; - - if(msgLen == NULL) { - return ERR_PARAM; - } - - ret = rfalNfcvPollerTransceiveReq( - cmd, - flags, - RFAL_NFCV_ST_IC_MFG_CODE, - uid, - NULL, - 0, - (uint8_t*)&res, - sizeof(rfalNfcvGenericRes), - &rcvLen); - if(ret == ERR_NONE) { - if(rcvLen < RFAL_ST25xV_READ_MSG_LEN_LEN) { - ret = ERR_PROTO; - } else { - *msgLen = res.data[0]; - } - } - return ret; -} - -/*******************************************************************************/ -static ReturnCode rfalST25xVPollerGenericReadMessage( - uint8_t cmd, - uint8_t flags, - const uint8_t* uid, - uint8_t mbPointer, - uint8_t numBytes, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rcvLen) { - uint8_t data[RFAL_ST25xV_MBPOINTER_LEN + RFAL_ST25xV_NUMBYTES_LEN]; - uint8_t dataLen; - - dataLen = 0; - - /* Compute Request Data */ - data[dataLen++] = mbPointer; - data[dataLen++] = numBytes; - - return rfalNfcvPollerTransceiveReq( - cmd, flags, RFAL_NFCV_ST_IC_MFG_CODE, uid, data, dataLen, rxBuf, rxBufLen, rcvLen); -} - -/*******************************************************************************/ -static ReturnCode rfalST25xVPollerGenericWriteMessage( - uint8_t cmd, - uint8_t flags, - const uint8_t* uid, - uint8_t msgLen, - const uint8_t* msgData, - uint8_t* txBuf, - uint16_t txBufLen) { - ReturnCode ret; - uint8_t reqFlag; - uint16_t msgIt; - rfalBitRate rxBR; - bool fastMode; - rfalNfcvGenericRes res; - uint16_t rcvLen; - - /* Calculate required Tx buf length: Mfg Code UID MSGLen MSGLen+1 */ - msgIt = - (uint16_t)(msgLen + sizeof(flags) + sizeof(cmd) + 1U + ((uid != NULL) ? RFAL_NFCV_UID_LEN : 0U) + 1U + 1U); - /* Note: MSGlength parameter of the command is the number of Data bytes minus - 1 (00 for 1 byte of data, FFh for 256 bytes of data) */ - - /* Check for valid parameters */ - if((txBuf == NULL) || (msgData == NULL) || (txBufLen < msgIt)) { - return ERR_PARAM; - } - - msgIt = 0; - fastMode = false; - - /* Check if the command is an ST's Fast command */ - if(cmd == (uint8_t)RFAL_NFCV_CMD_FAST_WRITE_MESSAGE) { - /* Store current Rx bit rate and move to fast mode */ - rfalGetBitRate(NULL, &rxBR); - rfalSetBitRate(RFAL_BR_KEEP, RFAL_BR_52p97); - - fastMode = true; - } - - /* Compute Request Command */ - reqFlag = - (uint8_t)(flags & (~((uint32_t)RFAL_NFCV_REQ_FLAG_ADDRESS) & ~((uint32_t)RFAL_NFCV_REQ_FLAG_SELECT))); - reqFlag |= - ((uid != NULL) ? (uint8_t)RFAL_NFCV_REQ_FLAG_ADDRESS : (uint8_t)RFAL_NFCV_REQ_FLAG_SELECT); - - txBuf[msgIt++] = reqFlag; - txBuf[msgIt++] = cmd; - txBuf[msgIt++] = RFAL_NFCV_ST_IC_MFG_CODE; - - if(uid != NULL) { - ST_MEMCPY(&txBuf[msgIt], uid, RFAL_NFCV_UID_LEN); - msgIt += RFAL_NFCV_UID_LEN; - } - txBuf[msgIt++] = msgLen; - ST_MEMCPY( - &txBuf[msgIt], - msgData, - (uint16_t)(msgLen + (uint16_t)1U)); /* Message Data contains (MSGLength + 1) bytes */ - msgIt += (uint16_t)(msgLen + (uint16_t)1U); - - /* Transceive Command */ - ret = rfalTransceiveBlockingTxRx( - txBuf, - msgIt, - (uint8_t*)&res, - sizeof(rfalNfcvGenericRes), - &rcvLen, - RFAL_TXRX_FLAGS_DEFAULT, - RFAL_ST25xV_FDT_POLL_MAX); - - /* Restore Rx BitRate */ - if(fastMode) { - rfalSetBitRate(RFAL_BR_KEEP, rxBR); - } - - if(ret != ERR_NONE) { - return ret; - } - - /* Check if the response minimum length has been received */ - if(rcvLen < (uint8_t)RFAL_NFCV_FLAG_LEN) { - return ERR_PROTO; - } - - /* Check if an error has been signalled */ - if((res.RES_FLAG & (uint8_t)RFAL_NFCV_RES_FLAG_ERROR) != 0U) { - return ERR_PROTO; - } - - return ERR_NONE; -} - -/* -****************************************************************************** -* GLOBAL FUNCTIONS -****************************************************************************** -*/ - -/*******************************************************************************/ -ReturnCode rfalST25xVPollerM24LRReadSingleBlock( - uint8_t flags, - const uint8_t* uid, - uint16_t blockNum, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rcvLen) { - uint8_t data[RFAL_NFCV_BLOCKNUM_M24LR_LEN]; - uint8_t dataLen; - - dataLen = 0; - - /* Compute Request Data */ - data[dataLen++] = (uint8_t)blockNum; /* Set M24LR Block Number (16 bits) LSB */ - data[dataLen++] = (uint8_t)(blockNum >> 8U); /* Set M24LR Block Number (16 bits) MSB */ - - return rfalNfcvPollerTransceiveReq( - RFAL_NFCV_CMD_READ_SINGLE_BLOCK, - (flags | (uint8_t)RFAL_NFCV_REQ_FLAG_PROTOCOL_EXT), - RFAL_NFCV_PARAM_SKIP, - uid, - data, - dataLen, - rxBuf, - rxBufLen, - rcvLen); -} - -/*******************************************************************************/ -ReturnCode rfalST25xVPollerM24LRWriteSingleBlock( - uint8_t flags, - const uint8_t* uid, - uint16_t blockNum, - const uint8_t* wrData, - uint8_t blockLen) { - uint8_t data[(RFAL_NFCV_BLOCKNUM_M24LR_LEN + RFAL_NFCV_MAX_BLOCK_LEN)]; - uint8_t dataLen; - uint16_t rcvLen; - rfalNfcvGenericRes res; - - /* Check for valid parameters */ - if((blockLen == 0U) || (blockLen > (uint8_t)RFAL_NFCV_MAX_BLOCK_LEN) || (wrData == NULL)) { - return ERR_PARAM; - } - - dataLen = 0U; - - /* Compute Request Data */ - data[dataLen++] = (uint8_t)blockNum; /* Set M24LR Block Number (16 bits) LSB */ - data[dataLen++] = (uint8_t)(blockNum >> 8U); /* Set M24LR Block Number (16 bits) MSB */ - ST_MEMCPY(&data[dataLen], wrData, blockLen); /* Append Block data to write */ - dataLen += blockLen; - - return rfalNfcvPollerTransceiveReq( - RFAL_NFCV_CMD_WRITE_SINGLE_BLOCK, - (flags | (uint8_t)RFAL_NFCV_REQ_FLAG_PROTOCOL_EXT), - RFAL_NFCV_PARAM_SKIP, - uid, - data, - dataLen, - (uint8_t*)&res, - sizeof(rfalNfcvGenericRes), - &rcvLen); -} - -/*******************************************************************************/ -ReturnCode rfalST25xVPollerM24LRReadMultipleBlocks( - uint8_t flags, - const uint8_t* uid, - uint16_t firstBlockNum, - uint8_t numOfBlocks, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rcvLen) { - uint8_t data[(RFAL_NFCV_BLOCKNUM_M24LR_LEN + RFAL_NFCV_BLOCKNUM_M24LR_LEN)]; - uint8_t dataLen; - - dataLen = 0U; - - /* Compute Request Data */ - data[dataLen++] = (uint8_t)firstBlockNum; /* Set M24LR Block Number (16 bits) LSB */ - data[dataLen++] = (uint8_t)(firstBlockNum >> 8U); /* Set M24LR Block Number (16 bits) MSB */ - data[dataLen++] = numOfBlocks; /* Set number of blocks to read */ - - return rfalNfcvPollerTransceiveReq( - RFAL_NFCV_CMD_READ_MULTIPLE_BLOCKS, - (flags | (uint8_t)RFAL_NFCV_REQ_FLAG_PROTOCOL_EXT), - RFAL_NFCV_PARAM_SKIP, - uid, - data, - dataLen, - rxBuf, - rxBufLen, - rcvLen); -} - -/*******************************************************************************/ -ReturnCode rfalST25xVPollerFastReadSingleBlock( - uint8_t flags, - const uint8_t* uid, - uint8_t blockNum, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rcvLen) { - uint8_t bn; - - bn = blockNum; - - return rfalNfcvPollerTransceiveReq( - RFAL_NFCV_CMD_FAST_READ_SINGLE_BLOCK, - flags, - RFAL_NFCV_ST_IC_MFG_CODE, - uid, - &bn, - sizeof(uint8_t), - rxBuf, - rxBufLen, - rcvLen); -} - -/*******************************************************************************/ -ReturnCode rfalST25xVPollerM24LRFastReadSingleBlock( - uint8_t flags, - const uint8_t* uid, - uint16_t blockNum, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rcvLen) { - uint8_t data[RFAL_NFCV_BLOCKNUM_M24LR_LEN]; - uint8_t dataLen; - - dataLen = 0; - - /* Compute Request Data */ - data[dataLen++] = (uint8_t)blockNum; /* Set M24LR Block Number (16 bits) LSB */ - data[dataLen++] = (uint8_t)(blockNum >> 8U); /* Set M24LR Block Number (16 bits) MSB */ - - return rfalNfcvPollerTransceiveReq( - RFAL_NFCV_CMD_FAST_READ_SINGLE_BLOCK, - (flags | (uint8_t)RFAL_NFCV_REQ_FLAG_PROTOCOL_EXT), - RFAL_NFCV_ST_IC_MFG_CODE, - uid, - data, - dataLen, - rxBuf, - rxBufLen, - rcvLen); -} - -/*******************************************************************************/ -ReturnCode rfalST25xVPollerM24LRFastReadMultipleBlocks( - uint8_t flags, - const uint8_t* uid, - uint16_t firstBlockNum, - uint8_t numOfBlocks, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rcvLen) { - uint8_t data[(RFAL_NFCV_BLOCKNUM_M24LR_LEN + RFAL_NFCV_BLOCKNUM_M24LR_LEN)]; - uint8_t dataLen; - - dataLen = 0U; - - /* Compute Request Data */ - data[dataLen++] = (uint8_t)firstBlockNum; /* Set M24LR Block Number (16 bits) LSB */ - data[dataLen++] = (uint8_t)(firstBlockNum >> 8U); /* Set M24LR Block Number (16 bits) MSB */ - data[dataLen++] = numOfBlocks; /* Set number of blocks to read */ - - return rfalNfcvPollerTransceiveReq( - RFAL_NFCV_CMD_FAST_READ_MULTIPLE_BLOCKS, - (flags | (uint8_t)RFAL_NFCV_REQ_FLAG_PROTOCOL_EXT), - RFAL_NFCV_ST_IC_MFG_CODE, - uid, - data, - dataLen, - rxBuf, - rxBufLen, - rcvLen); -} - -/*******************************************************************************/ -ReturnCode rfalST25xVPollerFastReadMultipleBlocks( - uint8_t flags, - const uint8_t* uid, - uint8_t firstBlockNum, - uint8_t numOfBlocks, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rcvLen) { - uint8_t data[(RFAL_NFCV_BLOCKNUM_LEN + RFAL_NFCV_BLOCKNUM_LEN)]; - uint8_t dataLen; - - dataLen = 0U; - - /* Compute Request Data */ - data[dataLen++] = firstBlockNum; /* Set first Block Number */ - data[dataLen++] = numOfBlocks; /* Set number of blocks to read */ - - return rfalNfcvPollerTransceiveReq( - RFAL_NFCV_CMD_FAST_READ_MULTIPLE_BLOCKS, - flags, - RFAL_NFCV_ST_IC_MFG_CODE, - uid, - data, - dataLen, - rxBuf, - rxBufLen, - rcvLen); -} - -/*******************************************************************************/ -ReturnCode rfalST25xVPollerFastExtendedReadSingleBlock( - uint8_t flags, - const uint8_t* uid, - uint16_t blockNum, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rcvLen) { - uint8_t data[RFAL_NFCV_BLOCKNUM_EXTENDED_LEN]; - uint8_t dataLen; - - dataLen = 0U; - - /* Compute Request Data */ - data[dataLen++] = (uint8_t) - blockNum; /* TS T5T 1.0 BNo is considered as a multi-byte field. TS T5T 1.0 5.1.1.13 multi-byte field follows [DIGITAL]. [DIGITAL] 9.3.1 A multiple byte field is transmitted LSB first. */ - data[dataLen++] = (uint8_t)((blockNum >> 8U) & 0xFFU); - - return rfalNfcvPollerTransceiveReq( - RFAL_NFCV_CMD_FAST_EXTENDED_READ_SINGLE_BLOCK, - flags, - RFAL_NFCV_ST_IC_MFG_CODE, - uid, - data, - dataLen, - rxBuf, - rxBufLen, - rcvLen); -} - -/*******************************************************************************/ -ReturnCode rfalST25xVPollerFastExtReadMultipleBlocks( - uint8_t flags, - const uint8_t* uid, - uint16_t firstBlockNum, - uint16_t numOfBlocks, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rcvLen) { - uint8_t data[(RFAL_NFCV_BLOCKNUM_EXTENDED_LEN + RFAL_NFCV_BLOCKNUM_EXTENDED_LEN)]; - uint8_t dataLen; - - dataLen = 0U; - - /* Compute Request Data */ - data[dataLen++] = (uint8_t)((firstBlockNum >> 0U) & 0xFFU); - data[dataLen++] = (uint8_t)((firstBlockNum >> 8U) & 0xFFU); - data[dataLen++] = (uint8_t)((numOfBlocks >> 0U) & 0xFFU); - data[dataLen++] = (uint8_t)((numOfBlocks >> 8U) & 0xFFU); - - return rfalNfcvPollerTransceiveReq( - RFAL_NFCV_CMD_FAST_EXTENDED_READ_MULTIPLE_BLOCKS, - flags, - RFAL_NFCV_ST_IC_MFG_CODE, - uid, - data, - dataLen, - rxBuf, - rxBufLen, - rcvLen); -} - -/*******************************************************************************/ -ReturnCode rfalST25xVPollerReadConfiguration( - uint8_t flags, - const uint8_t* uid, - uint8_t pointer, - uint8_t* regValue) { - return rfalST25xVPollerGenericReadConfiguration( - RFAL_NFCV_CMD_READ_CONFIGURATION, flags, uid, pointer, regValue); -} - -/*******************************************************************************/ -ReturnCode rfalST25xVPollerWriteConfiguration( - uint8_t flags, - const uint8_t* uid, - uint8_t pointer, - uint8_t regValue) { - return rfalST25xVPollerGenericWriteConfiguration( - RFAL_NFCV_CMD_WRITE_CONFIGURATION, flags, uid, pointer, regValue); -} - -/*******************************************************************************/ -ReturnCode rfalST25xVPollerReadDynamicConfiguration( - uint8_t flags, - const uint8_t* uid, - uint8_t pointer, - uint8_t* regValue) { - return rfalST25xVPollerGenericReadConfiguration( - RFAL_NFCV_CMD_READ_DYN_CONFIGURATION, flags, uid, pointer, regValue); -} - -/*******************************************************************************/ -ReturnCode rfalST25xVPollerWriteDynamicConfiguration( - uint8_t flags, - const uint8_t* uid, - uint8_t pointer, - uint8_t regValue) { - return rfalST25xVPollerGenericWriteConfiguration( - RFAL_NFCV_CMD_WRITE_DYN_CONFIGURATION, flags, uid, pointer, regValue); -} - -/*******************************************************************************/ -ReturnCode rfalST25xVPollerFastReadDynamicConfiguration( - uint8_t flags, - const uint8_t* uid, - uint8_t pointer, - uint8_t* regValue) { - return rfalST25xVPollerGenericReadConfiguration( - RFAL_NFCV_CMD_FAST_READ_DYN_CONFIGURATION, flags, uid, pointer, regValue); -} - -/*******************************************************************************/ -ReturnCode rfalST25xVPollerFastWriteDynamicConfiguration( - uint8_t flags, - const uint8_t* uid, - uint8_t pointer, - uint8_t regValue) { - return rfalST25xVPollerGenericWriteConfiguration( - RFAL_NFCV_CMD_FAST_WRITE_DYN_CONFIGURATION, flags, uid, pointer, regValue); -} - -/*******************************************************************************/ -ReturnCode rfalST25xVPollerPresentPassword( - uint8_t flags, - const uint8_t* uid, - uint8_t pwdNum, - const uint8_t* pwd, - uint8_t pwdLen) { - uint8_t data[RFAL_ST25xV_PWDNUM_LEN + RFAL_ST25xV_PWD_LEN]; - uint8_t dataLen; - uint16_t rcvLen; - rfalNfcvGenericRes res; - - if((pwdLen > RFAL_ST25xV_PWD_LEN) || (pwd == NULL)) { - return ERR_PARAM; - } - - dataLen = 0U; - data[dataLen++] = pwdNum; - if(pwdLen > 0U) { - ST_MEMCPY(&data[dataLen], pwd, pwdLen); - } - dataLen += pwdLen; - - return rfalNfcvPollerTransceiveReq( - RFAL_NFCV_CMD_PRESENT_PASSWORD, - flags, - RFAL_NFCV_ST_IC_MFG_CODE, - uid, - data, - dataLen, - (uint8_t*)&res, - sizeof(rfalNfcvGenericRes), - &rcvLen); -} - -/*******************************************************************************/ -ReturnCode rfalST25xVPollerGetRandomNumber( - uint8_t flags, - const uint8_t* uid, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rcvLen) { - rfalFieldOff(); - platformDelay(RFAL_ST25TV02K_TRF_OFF); - rfalNfcvPollerInitialize(); - rfalFieldOnAndStartGT(); - platformDelay(RFAL_ST25TV02K_TBOOT_RF); - return rfalNfcvPollerTransceiveReq( - RFAL_NFCV_CMD_GET_RANDOM_NUMBER, - flags, - RFAL_NFCV_ST_IC_MFG_CODE, - uid, - NULL, - 0U, - rxBuf, - rxBufLen, - rcvLen); -} - -/*******************************************************************************/ -ReturnCode rfalST25xVPollerWriteMessage( - uint8_t flags, - const uint8_t* uid, - uint8_t msgLen, - const uint8_t* msgData, - uint8_t* txBuf, - uint16_t txBufLen) { - return rfalST25xVPollerGenericWriteMessage( - RFAL_NFCV_CMD_WRITE_MESSAGE, flags, uid, msgLen, msgData, txBuf, txBufLen); -} - -/*******************************************************************************/ -ReturnCode rfalST25xVPollerFastWriteMessage( - uint8_t flags, - const uint8_t* uid, - uint8_t msgLen, - const uint8_t* msgData, - uint8_t* txBuf, - uint16_t txBufLen) { - return rfalST25xVPollerGenericWriteMessage( - RFAL_NFCV_CMD_FAST_WRITE_MESSAGE, flags, uid, msgLen, msgData, txBuf, txBufLen); -} - -/*******************************************************************************/ -ReturnCode rfalST25xVPollerReadMessageLength(uint8_t flags, const uint8_t* uid, uint8_t* msgLen) { - return rfalST25xVPollerGenericReadMessageLength( - RFAL_NFCV_CMD_READ_MESSAGE_LENGTH, flags, uid, msgLen); -} - -/*******************************************************************************/ -ReturnCode rfalST25xVPollerFastReadMsgLength(uint8_t flags, const uint8_t* uid, uint8_t* msgLen) { - return rfalST25xVPollerGenericReadMessageLength( - RFAL_NFCV_CMD_FAST_READ_MESSAGE_LENGTH, flags, uid, msgLen); -} - -/*******************************************************************************/ -ReturnCode rfalST25xVPollerReadMessage( - uint8_t flags, - const uint8_t* uid, - uint8_t mbPointer, - uint8_t numBytes, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rcvLen) { - return rfalST25xVPollerGenericReadMessage( - RFAL_NFCV_CMD_READ_MESSAGE, flags, uid, mbPointer, numBytes, rxBuf, rxBufLen, rcvLen); -} - -/*******************************************************************************/ -ReturnCode rfalST25xVPollerFastReadMessage( - uint8_t flags, - const uint8_t* uid, - uint8_t mbPointer, - uint8_t numBytes, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rcvLen) { - return rfalST25xVPollerGenericReadMessage( - RFAL_NFCV_CMD_FAST_READ_MESSAGE, flags, uid, mbPointer, numBytes, rxBuf, rxBufLen, rcvLen); -} - -#endif /* RFAL_FEATURE_ST25xV */ diff --git a/lib/ST25RFAL002/source/rfal_t1t.c b/lib/ST25RFAL002/source/rfal_t1t.c deleted file mode 100644 index be990efcd2e..00000000000 --- a/lib/ST25RFAL002/source/rfal_t1t.c +++ /dev/null @@ -1,233 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

    © COPYRIGHT 2020 STMicroelectronics

    - * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R391x firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file rfal_t1t.c - * - * \author Gustavo Patricio - * - * \brief Provides NFC-A T1T convenience methods and definitions - * - * This module provides an interface to perform as a NFC-A Reader/Writer - * to handle a Type 1 Tag T1T (Topaz) - * - */ - -/* - ****************************************************************************** - * INCLUDES - ****************************************************************************** - */ -#include "rfal_t1t.h" -#include "utils.h" - -/* - ****************************************************************************** - * ENABLE SWITCH - ****************************************************************************** - */ - -#ifndef RFAL_FEATURE_T1T -#define RFAL_FEATURE_T1T false /* T1T module configuration missing. Disabled by default */ -#endif - -#if RFAL_FEATURE_T1T - -/* - ****************************************************************************** - * GLOBAL DEFINES - ****************************************************************************** - */ - -#define RFAL_T1T_DRD_READ \ - (1236U * 2U) /*!< DRD for Reads with n=9 => 1236/fc ~= 91 us T1T 1.2 4.4.2 */ -#define RFAL_T1T_DRD_WRITE \ - 36052U /*!< DRD for Write with n=281 => 36052/fc ~= 2659 us T1T 1.2 4.4.2 */ -#define RFAL_T1T_DRD_WRITE_E \ - 70996U /*!< DRD for Write/Erase with n=554 => 70996/fc ~= 5236 us T1T 1.2 4.4.2 */ - -#define RFAL_T1T_RID_RES_HR0_VAL \ - 0x10U /*!< HR0 indicating NDEF support Digital 2.0 (Candidate) 11.6.2.1 */ -#define RFAL_T1T_RID_RES_HR0_MASK \ - 0xF0U /*!< HR0 most significant nibble mask */ - -/* -****************************************************************************** -* GLOBAL TYPES -****************************************************************************** -*/ - -/*! NFC-A T1T (Topaz) RID_REQ Digital 1.1 10.6.1 & Table 49 */ -typedef struct { - uint8_t cmd; /*!< T1T cmd: RID */ - uint8_t add; /*!< ADD: undefined value */ - uint8_t data; /*!< DATA: undefined value */ - uint8_t uid[RFAL_T1T_UID_LEN]; /*!< UID-echo: undefined value */ -} rfalT1TRidReq; - -/*! NFC-A T1T (Topaz) RALL_REQ T1T 1.2 Table 4 */ -typedef struct { - uint8_t cmd; /*!< T1T cmd: RALL */ - uint8_t add1; /*!< ADD: 0x00 */ - uint8_t add0; /*!< ADD: 0x00 */ - uint8_t uid[RFAL_T1T_UID_LEN]; /*!< UID */ -} rfalT1TRallReq; - -/*! NFC-A T1T (Topaz) WRITE_REQ T1T 1.2 Table 4 */ -typedef struct { - uint8_t cmd; /*!< T1T cmd: RALL */ - uint8_t add; /*!< ADD */ - uint8_t data; /*!< DAT */ - uint8_t uid[RFAL_T1T_UID_LEN]; /*!< UID */ -} rfalT1TWriteReq; - -/*! NFC-A T1T (Topaz) WRITE_RES T1T 1.2 Table 4 */ -typedef struct { - uint8_t add; /*!< ADD */ - uint8_t data; /*!< DAT */ -} rfalT1TWriteRes; - -/* -****************************************************************************** -* LOCAL FUNCTION PROTOTYPES -****************************************************************************** -*/ - -/* -****************************************************************************** -* GLOBAL FUNCTIONS -****************************************************************************** -*/ - -ReturnCode rfalT1TPollerInitialize(void) { - ReturnCode ret; - - EXIT_ON_ERR(ret, rfalSetMode(RFAL_MODE_POLL_NFCA_T1T, RFAL_BR_106, RFAL_BR_106)); - rfalSetErrorHandling(RFAL_ERRORHANDLING_NFC); - - rfalSetGT( - RFAL_GT_NONE); /* T1T should only be initialized after NFC-A mode, therefore the GT has been fulfilled */ - rfalSetFDTListen( - RFAL_FDT_LISTEN_NFCA_POLLER); /* T1T uses NFC-A FDT Listen with n=9 Digital 1.1 10.7.2 */ - rfalSetFDTPoll(RFAL_FDT_POLL_NFCA_T1T_POLLER); - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalT1TPollerRid(rfalT1TRidRes* ridRes) { - ReturnCode ret; - rfalT1TRidReq ridReq; - uint16_t rcvdLen; - - if(ridRes == NULL) { - return ERR_PARAM; - } - - /* Compute RID command and set Undefined Values to 0x00 Digital 1.1 10.6.1 */ - ST_MEMSET(&ridReq, 0x00, sizeof(rfalT1TRidReq)); - ridReq.cmd = (uint8_t)RFAL_T1T_CMD_RID; - - EXIT_ON_ERR( - ret, - rfalTransceiveBlockingTxRx( - (uint8_t*)&ridReq, - sizeof(rfalT1TRidReq), - (uint8_t*)ridRes, - sizeof(rfalT1TRidRes), - &rcvdLen, - RFAL_TXRX_FLAGS_DEFAULT, - RFAL_T1T_DRD_READ)); - - /* Check expected RID response length and the HR0 Digital 2.0 (Candidate) 11.6.2.1 */ - if((rcvdLen != sizeof(rfalT1TRidRes)) || - ((ridRes->hr0 & RFAL_T1T_RID_RES_HR0_MASK) != RFAL_T1T_RID_RES_HR0_VAL)) { - return ERR_PROTO; - } - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode - rfalT1TPollerRall(const uint8_t* uid, uint8_t* rxBuf, uint16_t rxBufLen, uint16_t* rxRcvdLen) { - rfalT1TRallReq rallReq; - - if((rxBuf == NULL) || (uid == NULL) || (rxRcvdLen == NULL)) { - return ERR_PARAM; - } - - /* Compute RALL command and set Add to 0x00 */ - ST_MEMSET(&rallReq, 0x00, sizeof(rfalT1TRallReq)); - rallReq.cmd = (uint8_t)RFAL_T1T_CMD_RALL; - ST_MEMCPY(rallReq.uid, uid, RFAL_T1T_UID_LEN); - - return rfalTransceiveBlockingTxRx( - (uint8_t*)&rallReq, - sizeof(rfalT1TRallReq), - (uint8_t*)rxBuf, - rxBufLen, - rxRcvdLen, - RFAL_TXRX_FLAGS_DEFAULT, - RFAL_T1T_DRD_READ); -} - -/*******************************************************************************/ -ReturnCode rfalT1TPollerWrite(const uint8_t* uid, uint8_t address, uint8_t data) { - rfalT1TWriteReq writeReq; - rfalT1TWriteRes writeRes; - uint16_t rxRcvdLen; - ReturnCode err; - - if(uid == NULL) { - return ERR_PARAM; - } - - writeReq.cmd = (uint8_t)RFAL_T1T_CMD_WRITE_E; - writeReq.add = address; - writeReq.data = data; - ST_MEMCPY(writeReq.uid, uid, RFAL_T1T_UID_LEN); - - err = rfalTransceiveBlockingTxRx( - (uint8_t*)&writeReq, - sizeof(rfalT1TWriteReq), - (uint8_t*)&writeRes, - sizeof(rfalT1TWriteRes), - &rxRcvdLen, - RFAL_TXRX_FLAGS_DEFAULT, - RFAL_T1T_DRD_WRITE_E); - - if(err == ERR_NONE) { - if((writeReq.add != writeRes.add) || (writeReq.data != writeRes.data) || - (rxRcvdLen != sizeof(rfalT1TWriteRes))) { - return ERR_PROTO; - } - } - return err; -} - -#endif /* RFAL_FEATURE_T1T */ diff --git a/lib/ST25RFAL002/source/rfal_t2t.c b/lib/ST25RFAL002/source/rfal_t2t.c deleted file mode 100644 index 2837898a7a5..00000000000 --- a/lib/ST25RFAL002/source/rfal_t2t.c +++ /dev/null @@ -1,253 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

    © COPYRIGHT 2020 STMicroelectronics

    - * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R391x firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file rfal_t2t.c - * - * \author - * - * \brief Provides NFC-A T2T convenience methods and definitions - * - * This module provides an interface to perform as a NFC-A Reader/Writer - * to handle a Type 2 Tag T2T - * - */ - -/* - ****************************************************************************** - * INCLUDES - ****************************************************************************** - */ -#include "rfal_t2t.h" -#include "utils.h" - -/* - ****************************************************************************** - * ENABLE SWITCH - ****************************************************************************** - */ - -#ifndef RFAL_FEATURE_T2T -#define RFAL_FEATURE_T2T false /* T2T module configuration missing. Disabled by default */ -#endif - -#if RFAL_FEATURE_T2T - -/* - ****************************************************************************** - * GLOBAL DEFINES - ****************************************************************************** - */ -#define RFAL_FDT_POLL_READ_MAX \ - rfalConvMsTo1fc( \ - 5U) /*!< Maximum Wait time for Read command as defined in TS T2T 1.0 table 18 */ -#define RFAL_FDT_POLL_WRITE_MAX \ - rfalConvMsTo1fc( \ - 10U) /*!< Maximum Wait time for Write command as defined in TS T2T 1.0 table 18 */ -#define RFAL_FDT_POLL_SL_MAX \ - rfalConvMsTo1fc( \ - 1U) /*!< Maximum Wait time for Sector Select as defined in TS T2T 1.0 table 18 */ -#define RFAL_T2T_ACK_NACK_LEN \ - 1U /*!< Len of NACK in bytes (4 bits) */ -#define RFAL_T2T_ACK \ - 0x0AU /*!< ACK value */ -#define RFAL_T2T_ACK_MASK \ - 0x0FU /*!< ACK value */ - -#define RFAL_T2T_SECTOR_SELECT_P1_BYTE2 \ - 0xFFU /*!< Sector Select Packet 1 byte 2 */ -#define RFAL_T2T_SECTOR_SELECT_P2_RFU_LEN \ - 3U /*!< Sector Select RFU length */ - -/* -****************************************************************************** -* GLOBAL TYPES -****************************************************************************** -*/ - -/*! NFC-A T2T command set T2T 1.0 5.1 */ -typedef enum { - RFAL_T2T_CMD_READ = 0x30, /*!< T2T Read */ - RFAL_T2T_CMD_WRITE = 0xA2, /*!< T2T Write */ - RFAL_T2T_CMD_SECTOR_SELECT = 0xC2 /*!< T2T Sector Select */ -} rfalT2Tcmds; - -/*! NFC-A T2T READ T2T 1.0 5.2 and table 11 */ -typedef struct { - uint8_t code; /*!< Command code */ - uint8_t blNo; /*!< Block number */ -} rfalT2TReadReq; - -/*! NFC-A T2T WRITE T2T 1.0 5.3 and table 12 */ -typedef struct { - uint8_t code; /*!< Command code */ - uint8_t blNo; /*!< Block number */ - uint8_t data[RFAL_T2T_WRITE_DATA_LEN]; /*!< Data */ -} rfalT2TWriteReq; - -/*! NFC-A T2T SECTOR SELECT Packet 1 T2T 1.0 5.4 and table 13 */ -typedef struct { - uint8_t code; /*!< Command code */ - uint8_t byte2; /*!< Sector Select Packet 1 byte 2 */ -} rfalT2TSectorSelectP1Req; - -/*! NFC-A T2T SECTOR SELECT Packet 2 T2T 1.0 5.4 and table 13 */ -typedef struct { - uint8_t secNo; /*!< Block number */ - uint8_t rfu[RFAL_T2T_SECTOR_SELECT_P2_RFU_LEN]; /*!< Sector Select Packet RFU */ -} rfalT2TSectorSelectP2Req; - -/* - ****************************************************************************** - * GLOBAL FUNCTIONS - ****************************************************************************** - */ - -ReturnCode - rfalT2TPollerRead(uint8_t blockNum, uint8_t* rxBuf, uint16_t rxBufLen, uint16_t* rcvLen) { - ReturnCode ret; - rfalT2TReadReq req; - - if((rxBuf == NULL) || (rcvLen == NULL)) { - return ERR_PARAM; - } - - req.code = (uint8_t)RFAL_T2T_CMD_READ; - req.blNo = blockNum; - - /* Transceive Command */ - ret = rfalTransceiveBlockingTxRx( - (uint8_t*)&req, - sizeof(rfalT2TReadReq), - rxBuf, - rxBufLen, - rcvLen, - RFAL_TXRX_FLAGS_DEFAULT, - RFAL_FDT_POLL_READ_MAX); - - /* T2T 1.0 5.2.1.7 The Reader/Writer SHALL treat a NACK in response to a READ Command as a Protocol Error */ - if((ret == ERR_INCOMPLETE_BYTE) && (*rcvLen == RFAL_T2T_ACK_NACK_LEN) && - ((*rxBuf & RFAL_T2T_ACK_MASK) != RFAL_T2T_ACK)) { - return ERR_PROTO; - } - return ret; -} - -/*******************************************************************************/ -ReturnCode rfalT2TPollerWrite(uint8_t blockNum, const uint8_t* wrData) { - ReturnCode ret; - rfalT2TWriteReq req; - uint8_t res; - uint16_t rxLen; - - req.code = (uint8_t)RFAL_T2T_CMD_WRITE; - req.blNo = blockNum; - ST_MEMCPY(req.data, wrData, RFAL_T2T_WRITE_DATA_LEN); - - /* Transceive WRITE Command */ - ret = rfalTransceiveBlockingTxRx( - (uint8_t*)&req, - sizeof(rfalT2TWriteReq), - &res, - sizeof(uint8_t), - &rxLen, - RFAL_TXRX_FLAGS_DEFAULT, - RFAL_FDT_POLL_READ_MAX); - - /* Check for a valid ACK */ - if((ret == ERR_INCOMPLETE_BYTE) || (ret == ERR_NONE)) { - ret = ERR_PROTO; - - if((rxLen == RFAL_T2T_ACK_NACK_LEN) && ((res & RFAL_T2T_ACK_MASK) == RFAL_T2T_ACK)) { - ret = ERR_NONE; - } - } - - return ret; -} - -/*******************************************************************************/ -ReturnCode rfalT2TPollerSectorSelect(uint8_t sectorNum) { - rfalT2TSectorSelectP1Req p1Req; - rfalT2TSectorSelectP2Req p2Req; - ReturnCode ret; - uint8_t res; - uint16_t rxLen; - - /* Compute SECTOR SELECT Packet 1 */ - p1Req.code = (uint8_t)RFAL_T2T_CMD_SECTOR_SELECT; - p1Req.byte2 = RFAL_T2T_SECTOR_SELECT_P1_BYTE2; - - /* Transceive SECTOR SELECT Packet 1 */ - ret = rfalTransceiveBlockingTxRx( - (uint8_t*)&p1Req, - sizeof(rfalT2TSectorSelectP1Req), - &res, - sizeof(uint8_t), - &rxLen, - RFAL_TXRX_FLAGS_DEFAULT, - RFAL_FDT_POLL_SL_MAX); - - /* Check and report any transmission error */ - if((ret != ERR_INCOMPLETE_BYTE) && (ret != ERR_NONE)) { - return ret; - } - - /* Ensure that an ACK was received */ - if((ret != ERR_INCOMPLETE_BYTE) || (rxLen != RFAL_T2T_ACK_NACK_LEN) || - ((res & RFAL_T2T_ACK_MASK) != RFAL_T2T_ACK)) { - return ERR_PROTO; - } - - /* Compute SECTOR SELECT Packet 2 */ - p2Req.secNo = sectorNum; - ST_MEMSET(&p2Req.rfu, 0x00, RFAL_T2T_SECTOR_SELECT_P2_RFU_LEN); - - /* Transceive SECTOR SELECT Packet 2 */ - ret = rfalTransceiveBlockingTxRx( - (uint8_t*)&p2Req, - sizeof(rfalT2TSectorSelectP2Req), - &res, - sizeof(uint8_t), - &rxLen, - RFAL_TXRX_FLAGS_DEFAULT, - RFAL_FDT_POLL_SL_MAX); - - /* T2T 1.0 5.4.1.14 The Reader/Writer SHALL treat any response received before the end of PATT2T,SL,MAX as a Protocol Error */ - if((ret == ERR_NONE) || (ret == ERR_INCOMPLETE_BYTE)) { - return ERR_PROTO; - } - - /* T2T 1.0 5.4.1.13 The Reader/Writer SHALL treat the transmission of the SECTOR SELECT Command Packet 2 as being successful when it receives no response until PATT2T,SL,MAX. */ - if(ret == ERR_TIMEOUT) { - return ERR_NONE; - } - - return ret; -} - -#endif /* RFAL_FEATURE_T2T */ diff --git a/lib/ST25RFAL002/source/rfal_t4t.c b/lib/ST25RFAL002/source/rfal_t4t.c deleted file mode 100644 index 4c29d79b023..00000000000 --- a/lib/ST25RFAL002/source/rfal_t4t.c +++ /dev/null @@ -1,397 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

    © COPYRIGHT 2020 STMicroelectronics

    - * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R391x firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file rfal_t4t.h - * - * \author Gustavo Patricio - * - * \brief Provides convenience methods and definitions for T4T (ISO7816-4) - * - * This module provides an interface to exchange T4T APDUs according to - * NFC Forum T4T and ISO7816-4 - * - * This implementation was based on the following specs: - * - ISO/IEC 7816-4 3rd Edition 2013-04-15 - * - NFC Forum T4T Technical Specification 1.0 2017-08-28 - * - */ - -/* - ****************************************************************************** - * INCLUDES - ****************************************************************************** - */ -#include "rfal_t4t.h" -#include "utils.h" - -/* - ****************************************************************************** - * ENABLE SWITCH - ****************************************************************************** - */ - -#ifndef RFAL_FEATURE_T4T -#define RFAL_FEATURE_T4T false /* T4T module configuration missing. Disabled by default */ -#endif - -#if RFAL_FEATURE_T4T - -/* - ****************************************************************************** - * GLOBAL DEFINES - ****************************************************************************** - */ -#define RFAL_T4T_OFFSET_DO 0x54U /*!< Tag value for offset BER-TLV data object */ -#define RFAL_T4T_LENGTH_DO 0x03U /*!< Len value for offset BER-TLV data object */ -#define RFAL_T4T_DATA_DO 0x53U /*!< Tag value for data BER-TLV data object */ - -#define RFAL_T4T_MAX_LC 255U /*!< Maximum Lc value for short Lc coding */ -/* -****************************************************************************** -* GLOBAL TYPES -****************************************************************************** -*/ - -/* -****************************************************************************** -* GLOBAL MACROS -****************************************************************************** -*/ - -/* - ****************************************************************************** - * LOCAL VARIABLES - ****************************************************************************** - */ - -/* - ****************************************************************************** - * GLOBAL FUNCTIONS - ****************************************************************************** - */ - -/*******************************************************************************/ -ReturnCode rfalT4TPollerComposeCAPDU(const rfalT4tCApduParam* apduParam) { - uint8_t hdrLen; - uint16_t msgIt; - - if((apduParam == NULL) || (apduParam->cApduBuf == NULL) || (apduParam->cApduLen == NULL)) { - return ERR_PARAM; - } - - msgIt = 0; - *(apduParam->cApduLen) = 0; - - /*******************************************************************************/ - /* Compute Command-APDU according to the format T4T 1.0 5.1.2 & ISO7816-4 2013 Table 1 */ - - /* Check if Data is present */ - if(apduParam->LcFlag) { - if(apduParam->Lc == 0U) { - /* Extended field coding not supported */ - return ERR_PARAM; - } - - /* Check whether requested Lc fits */ -#pragma GCC diagnostic ignored "-Wtype-limits" - if((uint16_t)apduParam->Lc > - (uint16_t)(RFAL_FEATURE_ISO_DEP_APDU_MAX_LEN - RFAL_T4T_LE_LEN)) { - return ERR_PARAM; /* PRQA S 2880 # MISRA 2.1 - Unreachable code due to configuration option being set/unset */ - } - - /* Calculate the header length a place the data/body where it should be */ - hdrLen = RFAL_T4T_MAX_CAPDU_PROLOGUE_LEN + RFAL_T4T_LC_LEN; - - /* make sure not to exceed buffer size */ - if(((uint16_t)hdrLen + (uint16_t)apduParam->Lc + - (apduParam->LeFlag ? RFAL_T4T_LC_LEN : 0U)) > RFAL_FEATURE_ISO_DEP_APDU_MAX_LEN) { - return ERR_NOMEM; /* PRQA S 2880 # MISRA 2.1 - Unreachable code due to configuration option being set/unset */ - } - ST_MEMMOVE(&apduParam->cApduBuf->apdu[hdrLen], apduParam->cApduBuf->apdu, apduParam->Lc); - } - - /* Prepend the ADPDU's header */ - apduParam->cApduBuf->apdu[msgIt++] = apduParam->CLA; - apduParam->cApduBuf->apdu[msgIt++] = apduParam->INS; - apduParam->cApduBuf->apdu[msgIt++] = apduParam->P1; - apduParam->cApduBuf->apdu[msgIt++] = apduParam->P2; - - /* Check if Data field length is to be added */ - if(apduParam->LcFlag) { - apduParam->cApduBuf->apdu[msgIt++] = apduParam->Lc; - msgIt += apduParam->Lc; - } - - /* Check if Expected Response Length is to be added */ - if(apduParam->LeFlag) { - apduParam->cApduBuf->apdu[msgIt++] = apduParam->Le; - } - - *(apduParam->cApduLen) = msgIt; - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalT4TPollerParseRAPDU(rfalT4tRApduParam* apduParam) { - if((apduParam == NULL) || (apduParam->rApduBuf == NULL)) { - return ERR_PARAM; - } - - if(apduParam->rcvdLen < RFAL_T4T_MAX_RAPDU_SW1SW2_LEN) { - return ERR_PROTO; - } - - apduParam->rApduBodyLen = (apduParam->rcvdLen - (uint16_t)RFAL_T4T_MAX_RAPDU_SW1SW2_LEN); - apduParam->statusWord = GETU16((&apduParam->rApduBuf->apdu[apduParam->rApduBodyLen])); - - /* Check SW1 SW2 T4T 1.0 5.1.3 NOTE */ - if(apduParam->statusWord == RFAL_T4T_ISO7816_STATUS_COMPLETE) { - return ERR_NONE; - } - - return ERR_REQUEST; -} - -/*******************************************************************************/ -ReturnCode rfalT4TPollerComposeSelectAppl( - rfalIsoDepApduBufFormat* cApduBuf, - const uint8_t* aid, - uint8_t aidLen, - uint16_t* cApduLen) { - rfalT4tCApduParam cAPDU; - - /* CLA INS P1 P2 Lc Data Le */ - /* 00h A4h 00h 00h 07h AID 00h */ - cAPDU.CLA = RFAL_T4T_CLA; - cAPDU.INS = (uint8_t)RFAL_T4T_INS_SELECT; - cAPDU.P1 = RFAL_T4T_ISO7816_P1_SELECT_BY_DF_NAME; - cAPDU.P2 = RFAL_T4T_ISO7816_P2_SELECT_FIRST_OR_ONLY_OCCURENCE | - RFAL_T4T_ISO7816_P2_SELECT_RETURN_FCI_TEMPLATE; - cAPDU.Lc = aidLen; - cAPDU.Le = 0x00; - cAPDU.LcFlag = true; - cAPDU.LeFlag = true; - cAPDU.cApduBuf = cApduBuf; - cAPDU.cApduLen = cApduLen; - - if(aidLen > 0U) { - ST_MEMCPY(cAPDU.cApduBuf->apdu, aid, aidLen); - } - - return rfalT4TPollerComposeCAPDU(&cAPDU); -} - -/*******************************************************************************/ -ReturnCode rfalT4TPollerComposeSelectFile( - rfalIsoDepApduBufFormat* cApduBuf, - const uint8_t* fid, - uint8_t fidLen, - uint16_t* cApduLen) { - rfalT4tCApduParam cAPDU; - - /* CLA INS P1 P2 Lc Data Le */ - /* 00h A4h 00h 0Ch 02h FID - */ - cAPDU.CLA = RFAL_T4T_CLA; - cAPDU.INS = (uint8_t)RFAL_T4T_INS_SELECT; - cAPDU.P1 = RFAL_T4T_ISO7816_P1_SELECT_BY_FILEID; - cAPDU.P2 = RFAL_T4T_ISO7816_P2_SELECT_FIRST_OR_ONLY_OCCURENCE | - RFAL_T4T_ISO7816_P2_SELECT_NO_RESPONSE_DATA; - cAPDU.Lc = fidLen; - cAPDU.Le = 0x00; - cAPDU.LcFlag = true; - cAPDU.LeFlag = false; - cAPDU.cApduBuf = cApduBuf; - cAPDU.cApduLen = cApduLen; - - if(fidLen > 0U) { - ST_MEMCPY(cAPDU.cApduBuf->apdu, fid, fidLen); - } - - return rfalT4TPollerComposeCAPDU(&cAPDU); -} - -/*******************************************************************************/ -ReturnCode rfalT4TPollerComposeSelectFileV1Mapping( - rfalIsoDepApduBufFormat* cApduBuf, - const uint8_t* fid, - uint8_t fidLen, - uint16_t* cApduLen) { - rfalT4tCApduParam cAPDU; - - /* CLA INS P1 P2 Lc Data Le */ - /* 00h A4h 00h 00h 02h FID - */ - cAPDU.CLA = RFAL_T4T_CLA; - cAPDU.INS = (uint8_t)RFAL_T4T_INS_SELECT; - cAPDU.P1 = RFAL_T4T_ISO7816_P1_SELECT_BY_FILEID; - cAPDU.P2 = RFAL_T4T_ISO7816_P2_SELECT_FIRST_OR_ONLY_OCCURENCE | - RFAL_T4T_ISO7816_P2_SELECT_RETURN_FCI_TEMPLATE; - cAPDU.Lc = fidLen; - cAPDU.Le = 0x00; - cAPDU.LcFlag = true; - cAPDU.LeFlag = false; - cAPDU.cApduBuf = cApduBuf; - cAPDU.cApduLen = cApduLen; - - if(fidLen > 0U) { - ST_MEMCPY(cAPDU.cApduBuf->apdu, fid, fidLen); - } - - return rfalT4TPollerComposeCAPDU(&cAPDU); -} - -/*******************************************************************************/ -ReturnCode rfalT4TPollerComposeReadData( - rfalIsoDepApduBufFormat* cApduBuf, - uint16_t offset, - uint8_t expLen, - uint16_t* cApduLen) { - rfalT4tCApduParam cAPDU; - - /* CLA INS P1 P2 Lc Data Le */ - /* 00h B0h [Offset] - - len */ - cAPDU.CLA = RFAL_T4T_CLA; - cAPDU.INS = (uint8_t)RFAL_T4T_INS_READBINARY; - cAPDU.P1 = (uint8_t)((offset >> 8U) & 0xFFU); - cAPDU.P2 = (uint8_t)((offset >> 0U) & 0xFFU); - cAPDU.Le = expLen; - cAPDU.LcFlag = false; - cAPDU.LeFlag = true; - cAPDU.cApduBuf = cApduBuf; - cAPDU.cApduLen = cApduLen; - - return rfalT4TPollerComposeCAPDU(&cAPDU); -} - -/*******************************************************************************/ -ReturnCode rfalT4TPollerComposeReadDataODO( - rfalIsoDepApduBufFormat* cApduBuf, - uint32_t offset, - uint8_t expLen, - uint16_t* cApduLen) { - rfalT4tCApduParam cAPDU; - uint8_t dataIt; - - /* CLA INS P1 P2 Lc Data Le */ - /* 00h B1h 00h 00h Lc 54 03 xxyyzz len */ - /* [Offset] */ - cAPDU.CLA = RFAL_T4T_CLA; - cAPDU.INS = (uint8_t)RFAL_T4T_INS_READBINARY_ODO; - cAPDU.P1 = 0x00U; - cAPDU.P2 = 0x00U; - cAPDU.Le = expLen; - cAPDU.LcFlag = true; - cAPDU.LeFlag = true; - cAPDU.cApduBuf = cApduBuf; - cAPDU.cApduLen = cApduLen; - - dataIt = 0U; - cApduBuf->apdu[dataIt++] = RFAL_T4T_OFFSET_DO; - cApduBuf->apdu[dataIt++] = RFAL_T4T_LENGTH_DO; - cApduBuf->apdu[dataIt++] = (uint8_t)(offset >> 16U); - cApduBuf->apdu[dataIt++] = (uint8_t)(offset >> 8U); - cApduBuf->apdu[dataIt++] = (uint8_t)(offset); - cAPDU.Lc = dataIt; - - return rfalT4TPollerComposeCAPDU(&cAPDU); -} - -/*******************************************************************************/ -ReturnCode rfalT4TPollerComposeWriteData( - rfalIsoDepApduBufFormat* cApduBuf, - uint16_t offset, - const uint8_t* data, - uint8_t dataLen, - uint16_t* cApduLen) { - rfalT4tCApduParam cAPDU; - - /* CLA INS P1 P2 Lc Data Le */ - /* 00h D6h [Offset] len Data - */ - cAPDU.CLA = RFAL_T4T_CLA; - cAPDU.INS = (uint8_t)RFAL_T4T_INS_UPDATEBINARY; - cAPDU.P1 = (uint8_t)((offset >> 8U) & 0xFFU); - cAPDU.P2 = (uint8_t)((offset >> 0U) & 0xFFU); - cAPDU.Lc = dataLen; - cAPDU.LcFlag = true; - cAPDU.LeFlag = false; - cAPDU.cApduBuf = cApduBuf; - cAPDU.cApduLen = cApduLen; - - if(dataLen > 0U) { - ST_MEMCPY(cAPDU.cApduBuf->apdu, data, dataLen); - } - - return rfalT4TPollerComposeCAPDU(&cAPDU); -} - -/*******************************************************************************/ -ReturnCode rfalT4TPollerComposeWriteDataODO( - rfalIsoDepApduBufFormat* cApduBuf, - uint32_t offset, - const uint8_t* data, - uint8_t dataLen, - uint16_t* cApduLen) { - rfalT4tCApduParam cAPDU; - uint8_t dataIt; - - /* CLA INS P1 P2 Lc Data Le */ - /* 00h D7h 00h 00h len 54 03 xxyyzz 53 Ld data - */ - /* [offset] [data] */ - cAPDU.CLA = RFAL_T4T_CLA; - cAPDU.INS = (uint8_t)RFAL_T4T_INS_UPDATEBINARY_ODO; - cAPDU.P1 = 0x00U; - cAPDU.P2 = 0x00U; - cAPDU.LcFlag = true; - cAPDU.LeFlag = false; - cAPDU.cApduBuf = cApduBuf; - cAPDU.cApduLen = cApduLen; - - dataIt = 0U; - cApduBuf->apdu[dataIt++] = RFAL_T4T_OFFSET_DO; - cApduBuf->apdu[dataIt++] = RFAL_T4T_LENGTH_DO; - cApduBuf->apdu[dataIt++] = (uint8_t)(offset >> 16U); - cApduBuf->apdu[dataIt++] = (uint8_t)(offset >> 8U); - cApduBuf->apdu[dataIt++] = (uint8_t)(offset); - cApduBuf->apdu[dataIt++] = RFAL_T4T_DATA_DO; - cApduBuf->apdu[dataIt++] = dataLen; - - if((((uint32_t)dataLen + (uint32_t)dataIt) >= RFAL_T4T_MAX_LC) || - (((uint32_t)dataLen + (uint32_t)dataIt) >= RFAL_FEATURE_ISO_DEP_APDU_MAX_LEN)) { - return (ERR_NOMEM); - } - - if(dataLen > 0U) { - ST_MEMCPY(&cAPDU.cApduBuf->apdu[dataIt], data, dataLen); - } - dataIt += dataLen; - cAPDU.Lc = dataIt; - - return rfalT4TPollerComposeCAPDU(&cAPDU); -} - -#endif /* RFAL_FEATURE_T4T */ diff --git a/lib/ST25RFAL002/source/st25r3916/rfal_analogConfigTbl.h b/lib/ST25RFAL002/source/st25r3916/rfal_analogConfigTbl.h deleted file mode 100644 index 554d6539a64..00000000000 --- a/lib/ST25RFAL002/source/st25r3916/rfal_analogConfigTbl.h +++ /dev/null @@ -1,1477 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

    © COPYRIGHT 2020 STMicroelectronics

    - * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R391x firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file rfal_analogConfig.h - * - * \author bkam - * - * \brief ST25R3916 Analog Configuration Settings - * - */ - -#ifndef ST25R3916_ANALOGCONFIG_H -#define ST25R3916_ANALOGCONFIG_H - -/* - ****************************************************************************** - * INCLUDES - ****************************************************************************** - */ -#include "rfal_analogConfig.h" -#include "st25r3916_com.h" - -/* - ****************************************************************************** - * DEFINES - ****************************************************************************** - */ - -/* - ****************************************************************************** - * GLOBAL MACROS - ****************************************************************************** - */ - -/*! Macro for Configuration Setting with only one register-mask-value set: - * - Configuration ID[2], Number of Register sets to follow[1], Register[2], Mask[1], Value[1] */ -#define MODE_ENTRY_1_REG(MODE, R0, M0, V0) \ - (uint8_t)((uint16_t)(MODE) >> 8U), (uint8_t)((MODE)&0xFFU), 1, \ - (uint8_t)((uint16_t)(R0) >> 8U), (uint8_t)((R0)&0xFFU), (uint8_t)(M0), (uint8_t)(V0) - -/*! Macro for Configuration Setting with only two register-mask-value sets: - * - Configuration ID[2], Number of Register sets to follow[1], Register[2], Mask[1], Value[1], Register[2], Mask[1], Value[1] */ -#define MODE_ENTRY_2_REG(MODE, R0, M0, V0, R1, M1, V1) \ - (uint8_t)((uint16_t)(MODE) >> 8U), (uint8_t)((MODE)&0xFFU), 2, \ - (uint8_t)((uint16_t)(R0) >> 8U), (uint8_t)((R0)&0xFFU), (uint8_t)(M0), (uint8_t)(V0), \ - (uint8_t)((uint16_t)(R1) >> 8U), (uint8_t)((R1)&0xFFU), (uint8_t)(M1), (uint8_t)(V1) - -/*! Macro for Configuration Setting with only three register-mask-value sets: - * - Configuration ID[2], Number of Register sets to follow[1], Register[2], Mask[1], Value[1], Register[2], Mask[1], Value[1], Register[2]... */ -#define MODE_ENTRY_3_REG(MODE, R0, M0, V0, R1, M1, V1, R2, M2, V2) \ - (uint8_t)((uint16_t)(MODE) >> 8U), (uint8_t)((MODE)&0xFFU), 3, \ - (uint8_t)((uint16_t)(R0) >> 8U), (uint8_t)((R0)&0xFFU), (uint8_t)(M0), (uint8_t)(V0), \ - (uint8_t)((uint16_t)(R1) >> 8U), (uint8_t)((R1)&0xFFU), (uint8_t)(M1), (uint8_t)(V1), \ - (uint8_t)((uint16_t)(R2) >> 8U), (uint8_t)((R2)&0xFFU), (uint8_t)(M2), (uint8_t)(V2) - -/*! Macro for Configuration Setting with only four register-mask-value sets: - * - Configuration ID[2], Number of Register sets to follow[1], Register[2], Mask[1], Value[1], Register[2], Mask[1], Value[1], Register[2]... */ -#define MODE_ENTRY_4_REG(MODE, R0, M0, V0, R1, M1, V1, R2, M2, V2, R3, M3, V3) \ - (uint8_t)((uint16_t)(MODE) >> 8U), (uint8_t)((MODE)&0xFFU), 4, \ - (uint8_t)((uint16_t)(R0) >> 8U), (uint8_t)((R0)&0xFFU), (uint8_t)(M0), (uint8_t)(V0), \ - (uint8_t)((uint16_t)(R1) >> 8U), (uint8_t)((R1)&0xFFU), (uint8_t)(M1), (uint8_t)(V1), \ - (uint8_t)((uint16_t)(R2) >> 8U), (uint8_t)((R2)&0xFFU), (uint8_t)(M2), (uint8_t)(V2), \ - (uint8_t)((uint16_t)(R3) >> 8U), (uint8_t)((R3)&0xFFU), (uint8_t)(M3), (uint8_t)(V3) - -/*! Macro for Configuration Setting with only five register-mask-value sets: - * - Configuration ID[2], Number of Register sets to follow[1], Register[2], Mask[1], Value[1], Register[2], Mask[1], Value[1], Register[2]... */ -#define MODE_ENTRY_5_REG(MODE, R0, M0, V0, R1, M1, V1, R2, M2, V2, R3, M3, V3, R4, M4, V4) \ - (uint8_t)((uint16_t)(MODE) >> 8U), (uint8_t)((MODE)&0xFFU), 5, \ - (uint8_t)((uint16_t)(R0) >> 8U), (uint8_t)((R0)&0xFFU), (uint8_t)(M0), (uint8_t)(V0), \ - (uint8_t)((uint16_t)(R1) >> 8U), (uint8_t)((R1)&0xFFU), (uint8_t)(M1), (uint8_t)(V1), \ - (uint8_t)((uint16_t)(R2) >> 8U), (uint8_t)((R2)&0xFFU), (uint8_t)(M2), (uint8_t)(V2), \ - (uint8_t)((uint16_t)(R3) >> 8U), (uint8_t)((R3)&0xFFU), (uint8_t)(M3), (uint8_t)(V3), \ - (uint8_t)((uint16_t)(R4) >> 8U), (uint8_t)((R4)&0xFFU), (uint8_t)(M4), (uint8_t)(V4) - -/*! Macro for Configuration Setting with only six register-mask-value sets: - * - Configuration ID[2], Number of Register sets to follow[1], Register[2], Mask[1], Value[1], Register[2], Mask[1], Value[1], Register[2]... */ -#define MODE_ENTRY_6_REG( \ - MODE, R0, M0, V0, R1, M1, V1, R2, M2, V2, R3, M3, V3, R4, M4, V4, R5, M5, V5) \ - (uint8_t)((uint16_t)(MODE) >> 8U), (uint8_t)((MODE)&0xFFU), 6, \ - (uint8_t)((uint16_t)(R0) >> 8U), (uint8_t)((R0)&0xFFU), (uint8_t)(M0), (uint8_t)(V0), \ - (uint8_t)((uint16_t)(R1) >> 8U), (uint8_t)((R1)&0xFFU), (uint8_t)(M1), (uint8_t)(V1), \ - (uint8_t)((uint16_t)(R2) >> 8U), (uint8_t)((R2)&0xFFU), (uint8_t)(M2), (uint8_t)(V2), \ - (uint8_t)((uint16_t)(R3) >> 8U), (uint8_t)((R3)&0xFFU), (uint8_t)(M3), (uint8_t)(V3), \ - (uint8_t)((uint16_t)(R4) >> 8U), (uint8_t)((R4)&0xFFU), (uint8_t)(M4), (uint8_t)(V4), \ - (uint8_t)((uint16_t)(R5) >> 8U), (uint8_t)((R5)&0xFFU), (uint8_t)(M5), (uint8_t)(V5) - -/*! Macro for Configuration Setting with only seven register-mask-value sets: - * - Configuration ID[2], Number of Register sets to follow[1], Register[2], Mask[1], Value[1], Register[2], Mask[1], Value[1], Register[2]... */ -#define MODE_ENTRY_7_REG( \ - MODE, R0, M0, V0, R1, M1, V1, R2, M2, V2, R3, M3, V3, R4, M4, V4, R5, M5, V5, R6, M6, V6) \ - (uint8_t)((uint16_t)(MODE) >> 8U), (uint8_t)((MODE)&0xFFU), 7, \ - (uint8_t)((uint16_t)(R0) >> 8U), (uint8_t)((R0)&0xFFU), (uint8_t)(M0), (uint8_t)(V0), \ - (uint8_t)((uint16_t)(R1) >> 8U), (uint8_t)((R1)&0xFFU), (uint8_t)(M1), (uint8_t)(V1), \ - (uint8_t)((uint16_t)(R2) >> 8U), (uint8_t)((R2)&0xFFU), (uint8_t)(M2), (uint8_t)(V2), \ - (uint8_t)((uint16_t)(R3) >> 8U), (uint8_t)((R3)&0xFFU), (uint8_t)(M3), (uint8_t)(V3), \ - (uint8_t)((uint16_t)(R4) >> 8U), (uint8_t)((R4)&0xFFU), (uint8_t)(M4), (uint8_t)(V4), \ - (uint8_t)((uint16_t)(R5) >> 8U), (uint8_t)((R5)&0xFFU), (uint8_t)(M5), (uint8_t)(V5), \ - (uint8_t)((uint16_t)(R6) >> 8U), (uint8_t)((R6)&0xFFU), (uint8_t)(M6), (uint8_t)(V6) - -/*! Macro for Configuration Setting with only eight register-mask-value sets: - * - Configuration ID[2], Number of Register sets to follow[1], Register[2], Mask[1], Value[1], Register[2], Mask[1], Value[1], Register[2]... */ -#define MODE_ENTRY_8_REG( \ - MODE, \ - R0, \ - M0, \ - V0, \ - R1, \ - M1, \ - V1, \ - R2, \ - M2, \ - V2, \ - R3, \ - M3, \ - V3, \ - R4, \ - M4, \ - V4, \ - R5, \ - M5, \ - V5, \ - R6, \ - M6, \ - V6, \ - R7, \ - M7, \ - V7) \ - (uint8_t)((uint16_t)(MODE) >> 8U), (uint8_t)((MODE)&0xFFU), 8, \ - (uint8_t)((uint16_t)(R0) >> 8U), (uint8_t)((R0)&0xFFU), (uint8_t)(M0), (uint8_t)(V0), \ - (uint8_t)((uint16_t)(R1) >> 8U), (uint8_t)((R1)&0xFFU), (uint8_t)(M1), (uint8_t)(V1), \ - (uint8_t)((uint16_t)(R2) >> 8U), (uint8_t)((R2)&0xFFU), (uint8_t)(M2), (uint8_t)(V2), \ - (uint8_t)((uint16_t)(R3) >> 8U), (uint8_t)((R3)&0xFFU), (uint8_t)(M3), (uint8_t)(V3), \ - (uint8_t)((uint16_t)(R4) >> 8U), (uint8_t)((R4)&0xFFU), (uint8_t)(M4), (uint8_t)(V4), \ - (uint8_t)((uint16_t)(R5) >> 8U), (uint8_t)((R5)&0xFFU), (uint8_t)(M5), (uint8_t)(V5), \ - (uint8_t)((uint16_t)(R6) >> 8U), (uint8_t)((R6)&0xFFU), (uint8_t)(M6), (uint8_t)(V6), \ - (uint8_t)((uint16_t)(R7) >> 8U), (uint8_t)((R7)&0xFFU), (uint8_t)(M7), (uint8_t)(V7) - -/*! Macro for Configuration Setting with only nine register-mask-value sets: - * - Configuration ID[2], Number of Register sets to follow[1], Register[2], Mask[1], Value[1], Register[2], Mask[1], Value[1], Register[2]... */ -#define MODE_ENTRY_9_REG( \ - MODE, \ - R0, \ - M0, \ - V0, \ - R1, \ - M1, \ - V1, \ - R2, \ - M2, \ - V2, \ - R3, \ - M3, \ - V3, \ - R4, \ - M4, \ - V4, \ - R5, \ - M5, \ - V5, \ - R6, \ - M6, \ - V6, \ - R7, \ - M7, \ - V7, \ - R8, \ - M8, \ - V8) \ - (uint8_t)((uint16_t)(MODE) >> 8U), (uint8_t)((MODE)&0xFFU), 9, \ - (uint8_t)((uint16_t)(R0) >> 8U), (uint8_t)((R0)&0xFFU), (uint8_t)(M0), (uint8_t)(V0), \ - (uint8_t)((uint16_t)(R1) >> 8U), (uint8_t)((R1)&0xFFU), (uint8_t)(M1), (uint8_t)(V1), \ - (uint8_t)((uint16_t)(R2) >> 8U), (uint8_t)((R2)&0xFFU), (uint8_t)(M2), (uint8_t)(V2), \ - (uint8_t)((uint16_t)(R3) >> 8U), (uint8_t)((R3)&0xFFU), (uint8_t)(M3), (uint8_t)(V3), \ - (uint8_t)((uint16_t)(R4) >> 8U), (uint8_t)((R4)&0xFFU), (uint8_t)(M4), (uint8_t)(V4), \ - (uint8_t)((uint16_t)(R5) >> 8U), (uint8_t)((R5)&0xFFU), (uint8_t)(M5), (uint8_t)(V5), \ - (uint8_t)((uint16_t)(R6) >> 8U), (uint8_t)((R6)&0xFFU), (uint8_t)(M6), (uint8_t)(V6), \ - (uint8_t)((uint16_t)(R7) >> 8U), (uint8_t)((R7)&0xFFU), (uint8_t)(M7), (uint8_t)(V7), \ - (uint8_t)((uint16_t)(R8) >> 8U), (uint8_t)((R8)&0xFFU), (uint8_t)(M8), (uint8_t)(V8) - -/*! Macro for Configuration Setting with only ten register-mask-value sets: - * - Configuration ID[2], Number of Register sets to follow[1], Register[2], Mask[1], Value[1], Register[2], Mask[1], Value[1], Register[2]... */ -#define MODE_ENTRY_10_REG( \ - MODE, \ - R0, \ - M0, \ - V0, \ - R1, \ - M1, \ - V1, \ - R2, \ - M2, \ - V2, \ - R3, \ - M3, \ - V3, \ - R4, \ - M4, \ - V4, \ - R5, \ - M5, \ - V5, \ - R6, \ - M6, \ - V6, \ - R7, \ - M7, \ - V7, \ - R8, \ - M8, \ - V8, \ - R9, \ - M9, \ - V9) \ - (uint8_t)((uint16_t)(MODE) >> 8U), (uint8_t)((MODE)&0xFFU), 10, \ - (uint8_t)((uint16_t)(R0) >> 8U), (uint8_t)((R0)&0xFFU), (uint8_t)(M0), (uint8_t)(V0), \ - (uint8_t)((uint16_t)(R1) >> 8U), (uint8_t)((R1)&0xFFU), (uint8_t)(M1), (uint8_t)(V1), \ - (uint8_t)((uint16_t)(R2) >> 8U), (uint8_t)((R2)&0xFFU), (uint8_t)(M2), (uint8_t)(V2), \ - (uint8_t)((uint16_t)(R3) >> 8U), (uint8_t)((R3)&0xFFU), (uint8_t)(M3), (uint8_t)(V3), \ - (uint8_t)((uint16_t)(R4) >> 8U), (uint8_t)((R4)&0xFFU), (uint8_t)(M4), (uint8_t)(V4), \ - (uint8_t)((uint16_t)(R5) >> 8U), (uint8_t)((R5)&0xFFU), (uint8_t)(M5), (uint8_t)(V5), \ - (uint8_t)((uint16_t)(R6) >> 8U), (uint8_t)((R6)&0xFFU), (uint8_t)(M6), (uint8_t)(V6), \ - (uint8_t)((uint16_t)(R7) >> 8U), (uint8_t)((R7)&0xFFU), (uint8_t)(M7), (uint8_t)(V7), \ - (uint8_t)((uint16_t)(R8) >> 8U), (uint8_t)((R8)&0xFFU), (uint8_t)(M8), (uint8_t)(V8), \ - (uint8_t)((uint16_t)(R9) >> 8U), (uint8_t)((R9)&0xFFU), (uint8_t)(M9), (uint8_t)(V9) - -/*! Macro for Configuration Setting with eleven register-mask-value sets: - * - Configuration ID[2], Number of Register sets to follow[1], Register[2], Mask[1], Value[1], Register[2], Mask[1], Value[1], Register[2]... */ -#define MODE_ENTRY_11_REG( \ - MODE, \ - R0, \ - M0, \ - V0, \ - R1, \ - M1, \ - V1, \ - R2, \ - M2, \ - V2, \ - R3, \ - M3, \ - V3, \ - R4, \ - M4, \ - V4, \ - R5, \ - M5, \ - V5, \ - R6, \ - M6, \ - V6, \ - R7, \ - M7, \ - V7, \ - R8, \ - M8, \ - V8, \ - R9, \ - M9, \ - V9, \ - R10, \ - M10, \ - V10) \ - (uint8_t)((uint16_t)(MODE) >> 8U), (uint8_t)((MODE)&0xFFU), 11, \ - (uint8_t)((uint16_t)(R0) >> 8U), (uint8_t)((R0)&0xFFU), (uint8_t)(M0), (uint8_t)(V0), \ - (uint8_t)((uint16_t)(R1) >> 8U), (uint8_t)((R1)&0xFFU), (uint8_t)(M1), (uint8_t)(V1), \ - (uint8_t)((uint16_t)(R2) >> 8U), (uint8_t)((R2)&0xFFU), (uint8_t)(M2), (uint8_t)(V2), \ - (uint8_t)((uint16_t)(R3) >> 8U), (uint8_t)((R3)&0xFFU), (uint8_t)(M3), (uint8_t)(V3), \ - (uint8_t)((uint16_t)(R4) >> 8U), (uint8_t)((R4)&0xFFU), (uint8_t)(M4), (uint8_t)(V4), \ - (uint8_t)((uint16_t)(R5) >> 8U), (uint8_t)((R5)&0xFFU), (uint8_t)(M5), (uint8_t)(V5), \ - (uint8_t)((uint16_t)(R6) >> 8U), (uint8_t)((R6)&0xFFU), (uint8_t)(M6), (uint8_t)(V6), \ - (uint8_t)((uint16_t)(R7) >> 8U), (uint8_t)((R7)&0xFFU), (uint8_t)(M7), (uint8_t)(V7), \ - (uint8_t)((uint16_t)(R8) >> 8U), (uint8_t)((R8)&0xFFU), (uint8_t)(M8), (uint8_t)(V8), \ - (uint8_t)((uint16_t)(R9) >> 8U), (uint8_t)((R9)&0xFFU), (uint8_t)(M9), (uint8_t)(V9), \ - (uint8_t)((uint16_t)(R10) >> 8U), (uint8_t)((R10)&0xFFU), (uint8_t)(M10), (uint8_t)(V10) - -/*! Macro for Configuration Setting with twelve register-mask-value sets: - * - Configuration ID[2], Number of Register sets to follow[1], Register[2], Mask[1], Value[1], Register[2], Mask[1], Value[1], Register[2]... */ -#define MODE_ENTRY_12_REG( \ - MODE, \ - R0, \ - M0, \ - V0, \ - R1, \ - M1, \ - V1, \ - R2, \ - M2, \ - V2, \ - R3, \ - M3, \ - V3, \ - R4, \ - M4, \ - V4, \ - R5, \ - M5, \ - V5, \ - R6, \ - M6, \ - V6, \ - R7, \ - M7, \ - V7, \ - R8, \ - M8, \ - V8, \ - R9, \ - M9, \ - V9, \ - R10, \ - M10, \ - V10, \ - R11, \ - M11, \ - V11) \ - (uint8_t)((uint16_t)(MODE) >> 8U), (uint8_t)((MODE)&0xFFU), 12, \ - (uint8_t)((uint16_t)(R0) >> 8U), (uint8_t)((R0)&0xFFU), (uint8_t)(M0), (uint8_t)(V0), \ - (uint8_t)((uint16_t)(R1) >> 8U), (uint8_t)((R1)&0xFFU), (uint8_t)(M1), (uint8_t)(V1), \ - (uint8_t)((uint16_t)(R2) >> 8U), (uint8_t)((R2)&0xFFU), (uint8_t)(M2), (uint8_t)(V2), \ - (uint8_t)((uint16_t)(R3) >> 8U), (uint8_t)((R3)&0xFFU), (uint8_t)(M3), (uint8_t)(V3), \ - (uint8_t)((uint16_t)(R4) >> 8U), (uint8_t)((R4)&0xFFU), (uint8_t)(M4), (uint8_t)(V4), \ - (uint8_t)((uint16_t)(R5) >> 8U), (uint8_t)((R5)&0xFFU), (uint8_t)(M5), (uint8_t)(V5), \ - (uint8_t)((uint16_t)(R6) >> 8U), (uint8_t)((R6)&0xFFU), (uint8_t)(M6), (uint8_t)(V6), \ - (uint8_t)((uint16_t)(R7) >> 8U), (uint8_t)((R7)&0xFFU), (uint8_t)(M7), (uint8_t)(V7), \ - (uint8_t)((uint16_t)(R8) >> 8U), (uint8_t)((R8)&0xFFU), (uint8_t)(M8), (uint8_t)(V8), \ - (uint8_t)((uint16_t)(R9) >> 8U), (uint8_t)((R9)&0xFFU), (uint8_t)(M9), (uint8_t)(V9), \ - (uint8_t)((uint16_t)(R10) >> 8U), (uint8_t)((R10)&0xFFU), (uint8_t)(M10), (uint8_t)(V10), \ - (uint8_t)((uint16_t)(R11) >> 8U), (uint8_t)((R11)&0xFFU), (uint8_t)(M11), (uint8_t)(V11) - -/*! Macro for Configuration Setting with thirteen register-mask-value sets: - * - Configuration ID[2], Number of Register sets to follow[1], Register[2], Mask[1], Value[1], Register[2], Mask[1], Value[1], Register[2]... */ -#define MODE_ENTRY_13_REG( \ - MODE, \ - R0, \ - M0, \ - V0, \ - R1, \ - M1, \ - V1, \ - R2, \ - M2, \ - V2, \ - R3, \ - M3, \ - V3, \ - R4, \ - M4, \ - V4, \ - R5, \ - M5, \ - V5, \ - R6, \ - M6, \ - V6, \ - R7, \ - M7, \ - V7, \ - R8, \ - M8, \ - V8, \ - R9, \ - M9, \ - V9, \ - R10, \ - M10, \ - V10, \ - R11, \ - M11, \ - V11, \ - R12, \ - M12, \ - V12) \ - (uint8_t)((uint16_t)(MODE) >> 8U), (uint8_t)((MODE)&0xFFU), 13, \ - (uint8_t)((uint16_t)(R0) >> 8U), (uint8_t)((R0)&0xFFU), (uint8_t)(M0), (uint8_t)(V0), \ - (uint8_t)((uint16_t)(R1) >> 8U), (uint8_t)((R1)&0xFFU), (uint8_t)(M1), (uint8_t)(V1), \ - (uint8_t)((uint16_t)(R2) >> 8U), (uint8_t)((R2)&0xFFU), (uint8_t)(M2), (uint8_t)(V2), \ - (uint8_t)((uint16_t)(R3) >> 8U), (uint8_t)((R3)&0xFFU), (uint8_t)(M3), (uint8_t)(V3), \ - (uint8_t)((uint16_t)(R4) >> 8U), (uint8_t)((R4)&0xFFU), (uint8_t)(M4), (uint8_t)(V4), \ - (uint8_t)((uint16_t)(R5) >> 8U), (uint8_t)((R5)&0xFFU), (uint8_t)(M5), (uint8_t)(V5), \ - (uint8_t)((uint16_t)(R6) >> 8U), (uint8_t)((R6)&0xFFU), (uint8_t)(M6), (uint8_t)(V6), \ - (uint8_t)((uint16_t)(R7) >> 8U), (uint8_t)((R7)&0xFFU), (uint8_t)(M7), (uint8_t)(V7), \ - (uint8_t)((uint16_t)(R8) >> 8U), (uint8_t)((R8)&0xFFU), (uint8_t)(M8), (uint8_t)(V8), \ - (uint8_t)((uint16_t)(R9) >> 8U), (uint8_t)((R9)&0xFFU), (uint8_t)(M9), (uint8_t)(V9), \ - (uint8_t)((uint16_t)(R10) >> 8U), (uint8_t)((R10)&0xFFU), (uint8_t)(M10), (uint8_t)(V10), \ - (uint8_t)((uint16_t)(R11) >> 8U), (uint8_t)((R11)&0xFFU), (uint8_t)(M11), (uint8_t)(V11), \ - (uint8_t)((uint16_t)(R12) >> 8U), (uint8_t)((R12)&0xFFU), (uint8_t)(M12), (uint8_t)(V12) - -/*! Macro for Configuration Setting with fourteen register-mask-value sets: - * - Configuration ID[2], Number of Register sets to follow[1], Register[2], Mask[1], Value[1], Register[2], Mask[1], Value[1], Register[2]... */ -#define MODE_ENTRY_14_REG( \ - MODE, \ - R0, \ - M0, \ - V0, \ - R1, \ - M1, \ - V1, \ - R2, \ - M2, \ - V2, \ - R3, \ - M3, \ - V3, \ - R4, \ - M4, \ - V4, \ - R5, \ - M5, \ - V5, \ - R6, \ - M6, \ - V6, \ - R7, \ - M7, \ - V7, \ - R8, \ - M8, \ - V8, \ - R9, \ - M9, \ - V9, \ - R10, \ - M10, \ - V10, \ - R11, \ - M11, \ - V11, \ - R12, \ - M12, \ - V12, \ - R13, \ - M13, \ - V13, \ - R14, \ - M14, \ - V14, \ - R15, \ - M15, \ - V15) \ - (uint8_t)((uint16_t)(MODE) >> 8), (uint8_t)((MODE)&0xFFU), 14, \ - (uint8_t)((uint16_t)(R0) >> 8), (uint8_t)((R0)&0xFFU), (uint8_t)(M0), (uint8_t)(V0), \ - (uint8_t)((uint16_t)(R1) >> 8), (uint8_t)((R1)&0xFFU), (uint8_t)(M1), (uint8_t)(V1), \ - (uint8_t)((uint16_t)(R2) >> 8), (uint8_t)((R2)&0xFFU), (uint8_t)(M2), (uint8_t)(V2), \ - (uint8_t)((uint16_t)(R3) >> 8), (uint8_t)((R3)&0xFFU), (uint8_t)(M3), (uint8_t)(V3), \ - (uint8_t)((uint16_t)(R4) >> 8), (uint8_t)((R4)&0xFFU), (uint8_t)(M4), (uint8_t)(V4), \ - (uint8_t)((uint16_t)(R5) >> 8), (uint8_t)((R5)&0xFFU), (uint8_t)(M5), (uint8_t)(V5), \ - (uint8_t)((uint16_t)(R6) >> 8), (uint8_t)((R6)&0xFFU), (uint8_t)(M6), (uint8_t)(V6), \ - (uint8_t)((uint16_t)(R7) >> 8), (uint8_t)((R7)&0xFFU), (uint8_t)(M7), (uint8_t)(V7), \ - (uint8_t)((uint16_t)(R8) >> 8), (uint8_t)((R8)&0xFFU), (uint8_t)(M8), (uint8_t)(V8), \ - (uint8_t)((uint16_t)(R9) >> 8), (uint8_t)((R9)&0xFFU), (uint8_t)(M9), (uint8_t)(V9), \ - (uint8_t)((uint16_t)(R10) >> 8), (uint8_t)((R10)&0xFFU), (uint8_t)(M10), (uint8_t)(V10), \ - (uint8_t)((uint16_t)(R11) >> 8), (uint8_t)((R11)&0xFFU), (uint8_t)(M11), (uint8_t)(V11), \ - (uint8_t)((uint16_t)(R12) >> 8), (uint8_t)((R12)&0xFFU), (uint8_t)(M12), (uint8_t)(V12), \ - (uint8_t)((uint16_t)(R13) >> 8), (uint8_t)((R13)&0xFFU), (uint8_t)(M13), (uint8_t)(V13) - -/*! Macro for Configuration Setting with fifteen register-mask-value sets: - * - Configuration ID[2], Number of Register sets to follow[1], Register[2], Mask[1], Value[1], Register[2], Mask[1], Value[1], Register[2]... */ -#define MODE_ENTRY_15_REG( \ - MODE, \ - R0, \ - M0, \ - V0, \ - R1, \ - M1, \ - V1, \ - R2, \ - M2, \ - V2, \ - R3, \ - M3, \ - V3, \ - R4, \ - M4, \ - V4, \ - R5, \ - M5, \ - V5, \ - R6, \ - M6, \ - V6, \ - R7, \ - M7, \ - V7, \ - R8, \ - M8, \ - V8, \ - R9, \ - M9, \ - V9, \ - R10, \ - M10, \ - V10, \ - R11, \ - M11, \ - V11, \ - R12, \ - M12, \ - V12, \ - R13, \ - M13, \ - V13, \ - R14, \ - M14, \ - V14, \ - R15, \ - M15, \ - V15) \ - (uint8_t)((uint16_t)(MODE) >> 8), (uint8_t)((MODE)&0xFFU), 15, \ - (uint8_t)((uint16_t)(R0) >> 8), (uint8_t)((R0)&0xFFU), (uint8_t)(M0), (uint8_t)(V0), \ - (uint8_t)((uint16_t)(R1) >> 8), (uint8_t)((R1)&0xFFU), (uint8_t)(M1), (uint8_t)(V1), \ - (uint8_t)((uint16_t)(R2) >> 8), (uint8_t)((R2)&0xFFU), (uint8_t)(M2), (uint8_t)(V2), \ - (uint8_t)((uint16_t)(R3) >> 8), (uint8_t)((R3)&0xFFU), (uint8_t)(M3), (uint8_t)(V3), \ - (uint8_t)((uint16_t)(R4) >> 8), (uint8_t)((R4)&0xFFU), (uint8_t)(M4), (uint8_t)(V4), \ - (uint8_t)((uint16_t)(R5) >> 8), (uint8_t)((R5)&0xFFU), (uint8_t)(M5), (uint8_t)(V5), \ - (uint8_t)((uint16_t)(R6) >> 8), (uint8_t)((R6)&0xFFU), (uint8_t)(M6), (uint8_t)(V6), \ - (uint8_t)((uint16_t)(R7) >> 8), (uint8_t)((R7)&0xFFU), (uint8_t)(M7), (uint8_t)(V7), \ - (uint8_t)((uint16_t)(R8) >> 8), (uint8_t)((R8)&0xFFU), (uint8_t)(M8), (uint8_t)(V8), \ - (uint8_t)((uint16_t)(R9) >> 8), (uint8_t)((R9)&0xFFU), (uint8_t)(M9), (uint8_t)(V9), \ - (uint8_t)((uint16_t)(R10) >> 8), (uint8_t)((R10)&0xFFU), (uint8_t)(M10), (uint8_t)(V10), \ - (uint8_t)((uint16_t)(R11) >> 8), (uint8_t)((R11)&0xFFU), (uint8_t)(M11), (uint8_t)(V11), \ - (uint8_t)((uint16_t)(R12) >> 8), (uint8_t)((R12)&0xFFU), (uint8_t)(M12), (uint8_t)(V12), \ - (uint8_t)((uint16_t)(R13) >> 8), (uint8_t)((R13)&0xFFU), (uint8_t)(M13), (uint8_t)(V13), \ - (uint8_t)((uint16_t)(R14) >> 8), (uint8_t)((R14)&0xFFU), (uint8_t)(M14), (uint8_t)(V14) - -/*! Macro for Configuration Setting with sixteen register-mask-value sets: - * - Configuration ID[2], Number of Register sets to follow[1], Register[2], Mask[1], Value[1], Register[2], Mask[1], Value[1], Register[2]... */ -#define MODE_ENTRY_16_REG( \ - MODE, \ - R0, \ - M0, \ - V0, \ - R1, \ - M1, \ - V1, \ - R2, \ - M2, \ - V2, \ - R3, \ - M3, \ - V3, \ - R4, \ - M4, \ - V4, \ - R5, \ - M5, \ - V5, \ - R6, \ - M6, \ - V6, \ - R7, \ - M7, \ - V7, \ - R8, \ - M8, \ - V8, \ - R9, \ - M9, \ - V9, \ - R10, \ - M10, \ - V10, \ - R11, \ - M11, \ - V11, \ - R12, \ - M12, \ - V12, \ - R13, \ - M13, \ - V13, \ - R14, \ - M14, \ - V14, \ - R15, \ - M15, \ - V15) \ - (uint8_t)((uint16_t)(MODE) >> 8), (uint8_t)((MODE)&0xFFU), 16, \ - (uint8_t)((uint16_t)(R0) >> 8), (uint8_t)((R0)&0xFFU), (uint8_t)(M0), (uint8_t)(V0), \ - (uint8_t)((uint16_t)(R1) >> 8), (uint8_t)((R1)&0xFFU), (uint8_t)(M1), (uint8_t)(V1), \ - (uint8_t)((uint16_t)(R2) >> 8), (uint8_t)((R2)&0xFFU), (uint8_t)(M2), (uint8_t)(V2), \ - (uint8_t)((uint16_t)(R3) >> 8), (uint8_t)((R3)&0xFFU), (uint8_t)(M3), (uint8_t)(V3), \ - (uint8_t)((uint16_t)(R4) >> 8), (uint8_t)((R4)&0xFFU), (uint8_t)(M4), (uint8_t)(V4), \ - (uint8_t)((uint16_t)(R5) >> 8), (uint8_t)((R5)&0xFFU), (uint8_t)(M5), (uint8_t)(V5), \ - (uint8_t)((uint16_t)(R6) >> 8), (uint8_t)((R6)&0xFFU), (uint8_t)(M6), (uint8_t)(V6), \ - (uint8_t)((uint16_t)(R7) >> 8), (uint8_t)((R7)&0xFFU), (uint8_t)(M7), (uint8_t)(V7), \ - (uint8_t)((uint16_t)(R8) >> 8), (uint8_t)((R8)&0xFFU), (uint8_t)(M8), (uint8_t)(V8), \ - (uint8_t)((uint16_t)(R9) >> 8), (uint8_t)((R9)&0xFFU), (uint8_t)(M9), (uint8_t)(V9), \ - (uint8_t)((uint16_t)(R10) >> 8), (uint8_t)((R10)&0xFFU), (uint8_t)(M10), (uint8_t)(V10), \ - (uint8_t)((uint16_t)(R11) >> 8), (uint8_t)((R11)&0xFFU), (uint8_t)(M11), (uint8_t)(V11), \ - (uint8_t)((uint16_t)(R12) >> 8), (uint8_t)((R12)&0xFFU), (uint8_t)(M12), (uint8_t)(V12), \ - (uint8_t)((uint16_t)(R13) >> 8), (uint8_t)((R13)&0xFFU), (uint8_t)(M13), (uint8_t)(V13), \ - (uint8_t)((uint16_t)(R14) >> 8), (uint8_t)((R14)&0xFFU), (uint8_t)(M14), (uint8_t)(V14), \ - (uint8_t)((uint16_t)(R15) >> 8), (uint8_t)((R15)&0xFFU), (uint8_t)(M15), (uint8_t)(V15) - -/*! Macro for Configuration Setting with seventeen register-mask-value sets: - * - Configuration ID[2], Number of Register sets to follow[1], Register[2], Mask[1], Value[1], Register[2], Mask[1], Value[1], Register[2]... */ -#define MODE_ENTRY_17_REG( \ - MODE, \ - R0, \ - M0, \ - V0, \ - R1, \ - M1, \ - V1, \ - R2, \ - M2, \ - V2, \ - R3, \ - M3, \ - V3, \ - R4, \ - M4, \ - V4, \ - R5, \ - M5, \ - V5, \ - R6, \ - M6, \ - V6, \ - R7, \ - M7, \ - V7, \ - R8, \ - M8, \ - V8, \ - R9, \ - M9, \ - V9, \ - R10, \ - M10, \ - V10, \ - R11, \ - M11, \ - V11, \ - R12, \ - M12, \ - V12, \ - R13, \ - M13, \ - V13, \ - R14, \ - M14, \ - V14, \ - R15, \ - M15, \ - V15, \ - R16, \ - M16, \ - V16) \ - (uint8_t)((uint16_t)(MODE) >> 8), (uint8_t)((MODE)&0xFFU), 17, \ - (uint8_t)((uint16_t)(R0) >> 8), (uint8_t)((R0)&0xFFU), (uint8_t)(M0), (uint8_t)(V0), \ - (uint8_t)((uint16_t)(R1) >> 8), (uint8_t)((R1)&0xFFU), (uint8_t)(M1), (uint8_t)(V1), \ - (uint8_t)((uint16_t)(R2) >> 8), (uint8_t)((R2)&0xFFU), (uint8_t)(M2), (uint8_t)(V2), \ - (uint8_t)((uint16_t)(R3) >> 8), (uint8_t)((R3)&0xFFU), (uint8_t)(M3), (uint8_t)(V3), \ - (uint8_t)((uint16_t)(R4) >> 8), (uint8_t)((R4)&0xFFU), (uint8_t)(M4), (uint8_t)(V4), \ - (uint8_t)((uint16_t)(R5) >> 8), (uint8_t)((R5)&0xFFU), (uint8_t)(M5), (uint8_t)(V5), \ - (uint8_t)((uint16_t)(R6) >> 8), (uint8_t)((R6)&0xFFU), (uint8_t)(M6), (uint8_t)(V6), \ - (uint8_t)((uint16_t)(R7) >> 8), (uint8_t)((R7)&0xFFU), (uint8_t)(M7), (uint8_t)(V7), \ - (uint8_t)((uint16_t)(R8) >> 8), (uint8_t)((R8)&0xFFU), (uint8_t)(M8), (uint8_t)(V8), \ - (uint8_t)((uint16_t)(R9) >> 8), (uint8_t)((R9)&0xFFU), (uint8_t)(M9), (uint8_t)(V9), \ - (uint8_t)((uint16_t)(R10) >> 8), (uint8_t)((R10)&0xFFU), (uint8_t)(M10), (uint8_t)(V10), \ - (uint8_t)((uint16_t)(R11) >> 8), (uint8_t)((R11)&0xFFU), (uint8_t)(M11), (uint8_t)(V11), \ - (uint8_t)((uint16_t)(R12) >> 8), (uint8_t)((R12)&0xFFU), (uint8_t)(M12), (uint8_t)(V12), \ - (uint8_t)((uint16_t)(R13) >> 8), (uint8_t)((R13)&0xFFU), (uint8_t)(M13), (uint8_t)(V13), \ - (uint8_t)((uint16_t)(R14) >> 8), (uint8_t)((R14)&0xFFU), (uint8_t)(M14), (uint8_t)(V14), \ - (uint8_t)((uint16_t)(R15) >> 8), (uint8_t)((R15)&0xFFU), (uint8_t)(M15), (uint8_t)(V15), \ - (uint8_t)((uint16_t)(R16) >> 8), (uint8_t)((R16)&0xFFU), (uint8_t)(M16), (uint8_t)(V16) -/* - ****************************************************************************** - * GLOBAL DATA TYPES - ****************************************************************************** - */ -/* PRQA S 3406 1 # MISRA 8.6 - Externally generated table included by the library */ /* PRQA S 1514 1 # MISRA 8.9 - Externally generated table included by the library */ -const uint8_t rfalAnalogConfigDefaultSettings[] = { - - /****** Default Analog Configuration for Chip-Specific Reset ******/ - MODE_ENTRY_17_REG( - (RFAL_ANALOG_CONFIG_TECH_CHIP | RFAL_ANALOG_CONFIG_CHIP_INIT), - ST25R3916_REG_IO_CONF1, - (ST25R3916_REG_IO_CONF1_out_cl_mask | ST25R3916_REG_IO_CONF1_lf_clk_off), - 0x07 /* Disable MCU_CLK */ - , - ST25R3916_REG_IO_CONF2, - (ST25R3916_REG_IO_CONF2_miso_pd1 | ST25R3916_REG_IO_CONF2_miso_pd2), - 0x18 /* SPI Pull downs */ - , - ST25R3916_REG_IO_CONF2, - ST25R3916_REG_IO_CONF2_aat_en, - ST25R3916_REG_IO_CONF2_aat_en /* Enable AAT */ - , - ST25R3916_REG_TX_DRIVER, - ST25R3916_REG_TX_DRIVER_d_res_mask, - 0x00 /* Set RFO resistance Active Tx */ - , - ST25R3916_REG_RES_AM_MOD, - 0xFF, - 0x80 /* Use minimum non-overlap */ - , - ST25R3916_REG_FIELD_THRESHOLD_ACTV, - ST25R3916_REG_FIELD_THRESHOLD_ACTV_trg_mask, - ST25R3916_REG_FIELD_THRESHOLD_ACTV_trg_105mV /* Lower activation threshold (higher than deactivation)*/ - , - ST25R3916_REG_FIELD_THRESHOLD_ACTV, - ST25R3916_REG_FIELD_THRESHOLD_ACTV_rfe_mask, - ST25R3916_REG_FIELD_THRESHOLD_ACTV_rfe_105mV /* Lower activation threshold (higher than deactivation)*/ - , - ST25R3916_REG_FIELD_THRESHOLD_DEACTV, - ST25R3916_REG_FIELD_THRESHOLD_DEACTV_trg_mask, - ST25R3916_REG_FIELD_THRESHOLD_DEACTV_trg_75mV /* Lower deactivation threshold */ - , - ST25R3916_REG_FIELD_THRESHOLD_DEACTV, - ST25R3916_REG_FIELD_THRESHOLD_DEACTV_rfe_mask, - ST25R3916_REG_FIELD_THRESHOLD_DEACTV_rfe_75mV /* Lower deactivation threshold */ - , - ST25R3916_REG_AUX_MOD, - ST25R3916_REG_AUX_MOD_lm_ext, - 0x00 /* Disable External Load Modulation */ - , - ST25R3916_REG_AUX_MOD, - ST25R3916_REG_AUX_MOD_lm_dri, - ST25R3916_REG_AUX_MOD_lm_dri /* Use internal Load Modulation */ - , - ST25R3916_REG_PASSIVE_TARGET, - ST25R3916_REG_PASSIVE_TARGET_fdel_mask, - (5U - << ST25R3916_REG_PASSIVE_TARGET_fdel_shift) /* Adjust the FDT to be aligned with the bitgrid */ - , - ST25R3916_REG_PT_MOD, - (ST25R3916_REG_PT_MOD_ptm_res_mask | ST25R3916_REG_PT_MOD_pt_res_mask), - 0x5f /* Reduce RFO resistance in Modulated state */ - , - ST25R3916_REG_EMD_SUP_CONF, - ST25R3916_REG_EMD_SUP_CONF_rx_start_emv, - ST25R3916_REG_EMD_SUP_CONF_rx_start_emv_on /* Enable start on first 4 bits */ - , - ST25R3916_REG_ANT_TUNE_A, - 0xFF, - 0x82 /* Set Antenna Tuning (Poller): ANTL */ - , - ST25R3916_REG_ANT_TUNE_B, - 0xFF, - 0x82 /* Set Antenna Tuning (Poller): ANTL */ - , - 0x84U, - 0x10, - 0x10 /* Avoid chip internal overheat protection */ - ) - - /****** Default Analog Configuration for Chip-Specific Poll Common ******/ - , - MODE_ENTRY_9_REG( - (RFAL_ANALOG_CONFIG_TECH_CHIP | RFAL_ANALOG_CONFIG_CHIP_POLL_COMMON), - ST25R3916_REG_MODE, - ST25R3916_REG_MODE_tr_am, - ST25R3916_REG_MODE_tr_am_am /* Use AM modulation */ - , - ST25R3916_REG_TX_DRIVER, - ST25R3916_REG_TX_DRIVER_am_mod_mask, - ST25R3916_REG_TX_DRIVER_am_mod_12percent /* Set Modulation index */ - , - ST25R3916_REG_AUX_MOD, - (ST25R3916_REG_AUX_MOD_dis_reg_am | ST25R3916_REG_AUX_MOD_res_am), - 0x00 /* Use AM via regulator */ - , - ST25R3916_REG_ANT_TUNE_A, - 0xFF, - 0x82 /* Set Antenna Tuning (Poller): ANTL */ - , - ST25R3916_REG_ANT_TUNE_B, - 0xFF, - 0x82 /* Set Antenna Tuning (Poller): ANTL */ - , - ST25R3916_REG_OVERSHOOT_CONF1, - 0xFF, - 0x00 /* Disable Overshoot Protection */ - , - ST25R3916_REG_OVERSHOOT_CONF2, - 0xFF, - 0x00 /* Disable Overshoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF1, - 0xFF, - 0x00 /* Disable Undershoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF2, - 0xFF, - 0x00 /* Disable Undershoot Protection */ - ) - - /****** Default Analog Configuration for Poll NFC-A Rx Common ******/ - , - MODE_ENTRY_1_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCA | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_RX), - ST25R3916_REG_AUX, - ST25R3916_REG_AUX_dis_corr, - ST25R3916_REG_AUX_dis_corr_correlator /* Use Correlator Receiver */ - ) - - /****** Default Analog Configuration for Poll NFC-A Tx 106 ******/ - , - MODE_ENTRY_5_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCA | RFAL_ANALOG_CONFIG_BITRATE_106 | - RFAL_ANALOG_CONFIG_TX), - ST25R3916_REG_MODE, - ST25R3916_REG_MODE_tr_am, - ST25R3916_REG_MODE_tr_am_ook /* Use OOK */ - , - ST25R3916_REG_OVERSHOOT_CONF1, - 0xFF, - 0x40 /* Set default Overshoot Protection */ - , - ST25R3916_REG_OVERSHOOT_CONF2, - 0xFF, - 0x03 /* Set default Overshoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF1, - 0xFF, - 0x40 /* Set default Undershoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF2, - 0xFF, - 0x03 /* Set default Undershoot Protection */ - ) - - /****** Default Analog Configuration for Poll NFC-A Rx 106 ******/ - , - MODE_ENTRY_6_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCA | RFAL_ANALOG_CONFIG_BITRATE_106 | - RFAL_ANALOG_CONFIG_RX), - ST25R3916_REG_RX_CONF1, - 0xFF, - 0x08, - ST25R3916_REG_RX_CONF2, - 0xFF, - 0x2D, - ST25R3916_REG_RX_CONF3, - 0xFF, - 0x00, - ST25R3916_REG_RX_CONF4, - 0xFF, - 0x00, - ST25R3916_REG_CORR_CONF1, - 0xFF, - 0x51, - ST25R3916_REG_CORR_CONF2, - 0xFF, - 0x00) - - /****** Default Analog Configuration for Poll NFC-A Tx 212 ******/ - , - MODE_ENTRY_7_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCA | RFAL_ANALOG_CONFIG_BITRATE_212 | - RFAL_ANALOG_CONFIG_TX), - ST25R3916_REG_MODE, - ST25R3916_REG_MODE_tr_am, - ST25R3916_REG_MODE_tr_am_am /* Use AM modulation */ - , - ST25R3916_REG_AUX_MOD, - (ST25R3916_REG_AUX_MOD_dis_reg_am | ST25R3916_REG_AUX_MOD_res_am), - 0x88 /* Use Resistive AM */ - , - ST25R3916_REG_RES_AM_MOD, - ST25R3916_REG_RES_AM_MOD_md_res_mask, - 0x7F /* Set Resistive modulation */ - , - ST25R3916_REG_OVERSHOOT_CONF1, - 0xFF, - 0x40 /* Set default Overshoot Protection */ - , - ST25R3916_REG_OVERSHOOT_CONF2, - 0xFF, - 0x03 /* Set default Overshoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF1, - 0xFF, - 0x40 /* Set default Undershoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF2, - 0xFF, - 0x03 /* Set default Undershoot Protection */ - ) - - /****** Default Analog Configuration for Poll NFC-A Rx 212 ******/ - , - MODE_ENTRY_6_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCA | RFAL_ANALOG_CONFIG_BITRATE_212 | - RFAL_ANALOG_CONFIG_RX), - ST25R3916_REG_RX_CONF1, - 0xFF, - 0x02, - ST25R3916_REG_RX_CONF2, - 0xFF, - 0x3D, - ST25R3916_REG_RX_CONF3, - 0xFF, - 0x00, - ST25R3916_REG_RX_CONF4, - 0xFF, - 0x00, - ST25R3916_REG_CORR_CONF1, - 0xFF, - 0x14, - ST25R3916_REG_CORR_CONF2, - 0xFF, - 0x00) - - /****** Default Analog Configuration for Poll NFC-A Tx 424 ******/ - , - MODE_ENTRY_7_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCA | RFAL_ANALOG_CONFIG_BITRATE_424 | - RFAL_ANALOG_CONFIG_TX), - ST25R3916_REG_MODE, - ST25R3916_REG_MODE_tr_am, - ST25R3916_REG_MODE_tr_am_am /* Use AM modulation */ - , - ST25R3916_REG_AUX_MOD, - (ST25R3916_REG_AUX_MOD_dis_reg_am | ST25R3916_REG_AUX_MOD_res_am), - 0x88 /* Use Resistive AM */ - , - ST25R3916_REG_RES_AM_MOD, - ST25R3916_REG_RES_AM_MOD_md_res_mask, - 0x7F /* Set Resistive modulation */ - , - ST25R3916_REG_OVERSHOOT_CONF1, - 0xFF, - 0x40 /* Set default Overshoot Protection */ - , - ST25R3916_REG_OVERSHOOT_CONF2, - 0xFF, - 0x03 /* Set default Overshoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF1, - 0xFF, - 0x40 /* Set default Undershoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF2, - 0xFF, - 0x03 /* Set default Undershoot Protection */ - ) - - /****** Default Analog Configuration for Poll NFC-A Rx 424 ******/ - , - MODE_ENTRY_6_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCA | RFAL_ANALOG_CONFIG_BITRATE_424 | - RFAL_ANALOG_CONFIG_RX), - ST25R3916_REG_RX_CONF1, - 0xFF, - 0x42, - ST25R3916_REG_RX_CONF2, - 0xFF, - 0x3D, - ST25R3916_REG_RX_CONF3, - 0xFF, - 0x00, - ST25R3916_REG_RX_CONF4, - 0xFF, - 0x00, - ST25R3916_REG_CORR_CONF1, - 0xFF, - 0x54, - ST25R3916_REG_CORR_CONF2, - 0xFF, - 0x00) - - /****** Default Analog Configuration for Poll NFC-A Tx 848 ******/ - , - MODE_ENTRY_7_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCA | RFAL_ANALOG_CONFIG_BITRATE_848 | - RFAL_ANALOG_CONFIG_TX), - ST25R3916_REG_MODE, - ST25R3916_REG_MODE_tr_am, - ST25R3916_REG_MODE_tr_am_am /* Use AM modulation */ - , - ST25R3916_REG_TX_DRIVER, - ST25R3916_REG_TX_DRIVER_am_mod_mask, - ST25R3916_REG_TX_DRIVER_am_mod_40percent /* Set Modulation index */ - , - ST25R3916_REG_AUX_MOD, - (ST25R3916_REG_AUX_MOD_dis_reg_am | ST25R3916_REG_AUX_MOD_res_am), - 0x00 /* Use AM via regulator */ - , - ST25R3916_REG_OVERSHOOT_CONF1, - 0xFF, - 0x00 /* Disable Overshoot Protection */ - , - ST25R3916_REG_OVERSHOOT_CONF2, - 0xFF, - 0x00 /* Disable Overshoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF1, - 0xFF, - 0x00 /* Disable Undershoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF2, - 0xFF, - 0x00 /* Disable Undershoot Protection */ - ) - - /****** Default Analog Configuration for Poll NFC-A Rx 848 ******/ - , - MODE_ENTRY_6_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCA | RFAL_ANALOG_CONFIG_BITRATE_848 | - RFAL_ANALOG_CONFIG_RX), - ST25R3916_REG_RX_CONF1, - 0xFF, - 0x42, - ST25R3916_REG_RX_CONF2, - 0xFF, - 0x3D, - ST25R3916_REG_RX_CONF3, - 0xFF, - 0x00, - ST25R3916_REG_RX_CONF4, - 0xFF, - 0x00, - ST25R3916_REG_CORR_CONF1, - 0xFF, - 0x44, - ST25R3916_REG_CORR_CONF2, - 0xFF, - 0x00) - - /****** Default Analog Configuration for Poll NFC-A Anticolision setting ******/ - , - MODE_ENTRY_1_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCA | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_ANTICOL), - ST25R3916_REG_CORR_CONF1, - ST25R3916_REG_CORR_CONF1_corr_s6, - 0x00 /* Set collision detection level different from data */ - ) - -#ifdef RFAL_USE_COHE - /****** Default Analog Configuration for Poll NFC-B Rx Common ******/ - , - MODE_ENTRY_1_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCB | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_RX), - ST25R3916_REG_AUX, - ST25R3916_REG_AUX_dis_corr, - ST25R3916_REG_AUX_dis_corr_coherent /* Use Coherent Receiver */ - ) -#else - /****** Default Analog Configuration for Poll NFC-B Rx Common ******/ - , - MODE_ENTRY_1_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCB | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_RX), - ST25R3916_REG_AUX, - ST25R3916_REG_AUX_dis_corr, - ST25R3916_REG_AUX_dis_corr_correlator /* Use Correlator Receiver */ - ) -#endif /*RFAL_USE_COHE*/ - - /****** Default Analog Configuration for Poll NFC-B Rx 106 ******/ - , - MODE_ENTRY_6_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCB | RFAL_ANALOG_CONFIG_BITRATE_106 | - RFAL_ANALOG_CONFIG_RX), - ST25R3916_REG_RX_CONF1, - 0xFF, - 0x04, - ST25R3916_REG_RX_CONF2, - 0xFF, - 0x3D, - ST25R3916_REG_RX_CONF3, - 0xFF, - 0x00, - ST25R3916_REG_RX_CONF4, - 0xFF, - 0x00, - ST25R3916_REG_CORR_CONF1, - 0xFF, - 0x1B, - ST25R3916_REG_CORR_CONF2, - 0xFF, - 0x00) - - /****** Default Analog Configuration for Poll NFC-B Rx 212 ******/ - , - MODE_ENTRY_6_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCB | RFAL_ANALOG_CONFIG_BITRATE_212 | - RFAL_ANALOG_CONFIG_RX), - ST25R3916_REG_RX_CONF1, - 0xFF, - 0x02, - ST25R3916_REG_RX_CONF2, - 0xFF, - 0x3D, - ST25R3916_REG_RX_CONF3, - 0xFF, - 0x00, - ST25R3916_REG_RX_CONF4, - 0xFF, - 0x00, - ST25R3916_REG_CORR_CONF1, - 0xFF, - 0x14, - ST25R3916_REG_CORR_CONF2, - 0xFF, - 0x00) - - /****** Default Analog Configuration for Poll NFC-B Rx 424 ******/ - , - MODE_ENTRY_6_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCB | RFAL_ANALOG_CONFIG_BITRATE_424 | - RFAL_ANALOG_CONFIG_RX), - ST25R3916_REG_RX_CONF1, - 0xFF, - 0x42, - ST25R3916_REG_RX_CONF2, - 0xFF, - 0x3D, - ST25R3916_REG_RX_CONF3, - 0xFF, - 0x00, - ST25R3916_REG_RX_CONF4, - 0xFF, - 0x00, - ST25R3916_REG_CORR_CONF1, - 0xFF, - 0x54, - ST25R3916_REG_CORR_CONF2, - 0xFF, - 0x00) - - /****** Default Analog Configuration for Poll NFC-B Rx 848 ******/ - , - MODE_ENTRY_6_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCB | RFAL_ANALOG_CONFIG_BITRATE_848 | - RFAL_ANALOG_CONFIG_RX), - ST25R3916_REG_RX_CONF1, - 0xFF, - 0x42, - ST25R3916_REG_RX_CONF2, - 0xFF, - 0x3D, - ST25R3916_REG_RX_CONF3, - 0xFF, - 0x00, - ST25R3916_REG_RX_CONF4, - 0xFF, - 0x00, - ST25R3916_REG_CORR_CONF1, - 0xFF, - 0x44, - ST25R3916_REG_CORR_CONF2, - 0xFF, - 0x00) -#ifdef RFAL_USE_COHE - - /****** Default Analog Configuration for Poll NFC-F Rx Common ******/ - , - MODE_ENTRY_7_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCF | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_RX), - ST25R3916_REG_AUX, - ST25R3916_REG_AUX_dis_corr, - ST25R3916_REG_AUX_dis_corr_coherent /* Use Pulse Receiver */ - , - ST25R3916_REG_RX_CONF1, - 0xFF, - 0x13, - ST25R3916_REG_RX_CONF2, - 0xFF, - 0x3D, - ST25R3916_REG_RX_CONF3, - 0xFF, - 0x00, - ST25R3916_REG_RX_CONF4, - 0xFF, - 0x00, - ST25R3916_REG_CORR_CONF1, - 0xFF, - 0x54, - ST25R3916_REG_CORR_CONF2, - 0xFF, - 0x00) -#else - /****** Default Analog Configuration for Poll NFC-F Rx Common ******/ - , - MODE_ENTRY_7_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCF | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_RX), - ST25R3916_REG_AUX, - ST25R3916_REG_AUX_dis_corr, - ST25R3916_REG_AUX_dis_corr_correlator /* Use Correlator Receiver */ - , - ST25R3916_REG_RX_CONF1, - 0xFF, - 0x13, - ST25R3916_REG_RX_CONF2, - 0xFF, - 0x3D, - ST25R3916_REG_RX_CONF3, - 0xFF, - 0x00, - ST25R3916_REG_RX_CONF4, - 0xFF, - 0x00, - ST25R3916_REG_CORR_CONF1, - 0xFF, - 0x54, - ST25R3916_REG_CORR_CONF2, - 0xFF, - 0x00) -#endif /*RFAL_USE_COHE*/ - - , - MODE_ENTRY_1_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCV | RFAL_ANALOG_CONFIG_BITRATE_1OF4 | - RFAL_ANALOG_CONFIG_TX), - ST25R3916_REG_MODE, - ST25R3916_REG_MODE_tr_am, - ST25R3916_REG_MODE_tr_am_ook /* Use OOK */ - ) - -#ifdef RFAL_USE_COHE - /****** Default Analog Configuration for Poll NFC-V Rx Common ******/ - , - MODE_ENTRY_7_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCV | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_RX), - ST25R3916_REG_AUX, - ST25R3916_REG_AUX_dis_corr, - ST25R3916_REG_AUX_dis_corr_coherent /* Use Pulse Receiver */ - , - ST25R3916_REG_RX_CONF1, - 0xFF, - 0x13, - ST25R3916_REG_RX_CONF2, - 0xFF, - 0x2D, - ST25R3916_REG_RX_CONF3, - 0xFF, - 0x00, - ST25R3916_REG_RX_CONF4, - 0xFF, - 0x00, - ST25R3916_REG_CORR_CONF1, - 0xFF, - 0x13, - ST25R3916_REG_CORR_CONF2, - 0xFF, - 0x01) -#else - /****** Default Analog Configuration for Poll NFC-V Rx Common ******/ - , - MODE_ENTRY_7_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCV | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_RX), - ST25R3916_REG_AUX, - ST25R3916_REG_AUX_dis_corr, - ST25R3916_REG_AUX_dis_corr_correlator /* Use Correlator Receiver */ - , - ST25R3916_REG_RX_CONF1, - 0xFF, - 0x13, - ST25R3916_REG_RX_CONF2, - 0xFF, - 0x2D, - ST25R3916_REG_RX_CONF3, - 0xFF, - 0x00, - ST25R3916_REG_RX_CONF4, - 0xFF, - 0x00, - ST25R3916_REG_CORR_CONF1, - 0xFF, - 0x13, - ST25R3916_REG_CORR_CONF2, - 0xFF, - 0x01) -#endif /*RFAL_USE_COHE*/ - - /****** Default Analog Configuration for Poll AP2P Tx 106 ******/ - , - MODE_ENTRY_5_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_AP2P | RFAL_ANALOG_CONFIG_BITRATE_106 | - RFAL_ANALOG_CONFIG_TX), - ST25R3916_REG_MODE, - ST25R3916_REG_MODE_tr_am, - ST25R3916_REG_MODE_tr_am_ook /* Use OOK modulation */ - , - ST25R3916_REG_OVERSHOOT_CONF1, - 0xFF, - 0x40 /* Set default Overshoot Protection */ - , - ST25R3916_REG_OVERSHOOT_CONF2, - 0xFF, - 0x03 /* Set default Overshoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF1, - 0xFF, - 0x40 /* Set default Undershoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF2, - 0xFF, - 0x03 /* Set default Undershoot Protection */ - ) - - /****** Default Analog Configuration for Poll AP2P Tx 212 ******/ - , - MODE_ENTRY_1_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_AP2P | RFAL_ANALOG_CONFIG_BITRATE_212 | - RFAL_ANALOG_CONFIG_TX), - ST25R3916_REG_MODE, - ST25R3916_REG_MODE_tr_am, - ST25R3916_REG_MODE_tr_am_am /* Use AM modulation */ - ) - - /****** Default Analog Configuration for Poll AP2P Tx 424 ******/ - , - MODE_ENTRY_1_REG( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_AP2P | RFAL_ANALOG_CONFIG_BITRATE_424 | - RFAL_ANALOG_CONFIG_TX), - ST25R3916_REG_MODE, - ST25R3916_REG_MODE_tr_am, - ST25R3916_REG_MODE_tr_am_am /* Use AM modulation */ - ) - - /****** Default Analog Configuration for Chip-Specific Listen On ******/ - , - MODE_ENTRY_6_REG( - (RFAL_ANALOG_CONFIG_TECH_CHIP | RFAL_ANALOG_CONFIG_CHIP_LISTEN_ON), - ST25R3916_REG_ANT_TUNE_A, - 0xFF, - 0x00 /* Set Antenna Tuning (Listener): ANTL */ - , - ST25R3916_REG_ANT_TUNE_B, - 0xFF, - 0xff /* Set Antenna Tuning (Listener): ANTL */ - , - ST25R3916_REG_OVERSHOOT_CONF1, - 0xFF, - 0x00 /* Disable Overshoot Protection */ - , - ST25R3916_REG_OVERSHOOT_CONF2, - 0xFF, - 0x00 /* Disable Overshoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF1, - 0xFF, - 0x00 /* Disable Undershoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF2, - 0xFF, - 0x00 /* Disable Undershoot Protection */ - ) - - /****** Default Analog Configuration for Listen AP2P Tx Common ******/ - , - MODE_ENTRY_7_REG( - (RFAL_ANALOG_CONFIG_LISTEN | RFAL_ANALOG_CONFIG_TECH_AP2P | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_TX), - ST25R3916_REG_ANT_TUNE_A, - 0xFF, - 0x82 /* Set Antenna Tuning (Poller): ANTL */ - , - ST25R3916_REG_ANT_TUNE_B, - 0xFF, - 0x82 /* Set Antenna Tuning (Poller): ANTL */ - , - ST25R3916_REG_TX_DRIVER, - ST25R3916_REG_TX_DRIVER_am_mod_mask, - ST25R3916_REG_TX_DRIVER_am_mod_12percent /* Set Modulation index */ - , - ST25R3916_REG_OVERSHOOT_CONF1, - 0xFF, - 0x00 /* Disable Overshoot Protection */ - , - ST25R3916_REG_OVERSHOOT_CONF2, - 0xFF, - 0x00 /* Disable Overshoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF1, - 0xFF, - 0x00 /* Disable Undershoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF2, - 0xFF, - 0x00 /* Disable Undershoot Protection */ - ) - - /****** Default Analog Configuration for Listen AP2P Rx Common ******/ - , - MODE_ENTRY_3_REG( - (RFAL_ANALOG_CONFIG_LISTEN | RFAL_ANALOG_CONFIG_TECH_AP2P | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_RX), - ST25R3916_REG_RX_CONF1, - ST25R3916_REG_RX_CONF1_lp_mask, - ST25R3916_REG_RX_CONF1_lp_1200khz /* Set Rx filter configuration */ - , - ST25R3916_REG_RX_CONF1, - ST25R3916_REG_RX_CONF1_hz_mask, - ST25R3916_REG_RX_CONF1_hz_12_200khz /* Set Rx filter configuration */ - , - ST25R3916_REG_RX_CONF2, - ST25R3916_REG_RX_CONF2_amd_sel, - ST25R3916_REG_RX_CONF2_amd_sel_mixer /* AM demodulator: mixer */ - ) - - /****** Default Analog Configuration for Listen AP2P Tx 106 ******/ - , - MODE_ENTRY_5_REG( - (RFAL_ANALOG_CONFIG_LISTEN | RFAL_ANALOG_CONFIG_TECH_AP2P | - RFAL_ANALOG_CONFIG_BITRATE_106 | RFAL_ANALOG_CONFIG_TX), - ST25R3916_REG_MODE, - ST25R3916_REG_MODE_tr_am, - ST25R3916_REG_MODE_tr_am_ook /* Use OOK modulation */ - , - ST25R3916_REG_OVERSHOOT_CONF1, - 0xFF, - 0x40 /* Set default Overshoot Protection */ - , - ST25R3916_REG_OVERSHOOT_CONF2, - 0xFF, - 0x03 /* Set default Overshoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF1, - 0xFF, - 0x40 /* Set default Undershoot Protection */ - , - ST25R3916_REG_UNDERSHOOT_CONF2, - 0xFF, - 0x03 /* Set default Undershoot Protection */ - ) - - /****** Default Analog Configuration for Listen AP2P Tx 212 ******/ - , - MODE_ENTRY_1_REG( - (RFAL_ANALOG_CONFIG_LISTEN | RFAL_ANALOG_CONFIG_TECH_AP2P | - RFAL_ANALOG_CONFIG_BITRATE_212 | RFAL_ANALOG_CONFIG_TX), - ST25R3916_REG_MODE, - ST25R3916_REG_MODE_tr_am, - ST25R3916_REG_MODE_tr_am_am /* Use AM modulation */ - ) - - /****** Default Analog Configuration for Listen AP2P Tx 424 ******/ - , - MODE_ENTRY_1_REG( - (RFAL_ANALOG_CONFIG_LISTEN | RFAL_ANALOG_CONFIG_TECH_AP2P | - RFAL_ANALOG_CONFIG_BITRATE_424 | RFAL_ANALOG_CONFIG_TX), - ST25R3916_REG_MODE, - ST25R3916_REG_MODE_tr_am, - ST25R3916_REG_MODE_tr_am_am /* Use AM modulation */ - ) - -}; - -#endif /* ST25R3916_ANALOGCONFIG_H */ diff --git a/lib/ST25RFAL002/source/st25r3916/rfal_dpoTbl.h b/lib/ST25RFAL002/source/st25r3916/rfal_dpoTbl.h deleted file mode 100644 index 3075776c55e..00000000000 --- a/lib/ST25RFAL002/source/st25r3916/rfal_dpoTbl.h +++ /dev/null @@ -1,68 +0,0 @@ - -/****************************************************************************** - * @attention - * - *

    © COPYRIGHT 2020 STMicroelectronics

    - * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R391x firmware - * $Revision: $ - * LANGUAGE: ISO C99 - */ - -/*! \file - * - * \author Martin Zechleitner - * - * \brief RF Dynamic Power Table default values - */ - -#ifndef ST25R3916_DPO_H -#define ST25R3916_DPO_H - -/* - ****************************************************************************** - * INCLUDES - ****************************************************************************** - */ -#include "rfal_dpo.h" - -/* - ****************************************************************************** - * GLOBAL DATA TYPES - ****************************************************************************** - */ - -/*! Default DPO table */ -const uint8_t rfalDpoDefaultSettings[] = { - 0x00, - 255, - 200, - 0x01, - 210, - 150, - 0x02, - 160, - 100, - 0x03, - 110, - 50, -}; - -#endif /* ST25R3916_DPO_H */ diff --git a/lib/ST25RFAL002/source/st25r3916/rfal_features.h b/lib/ST25RFAL002/source/st25r3916/rfal_features.h deleted file mode 100644 index 92f26acf575..00000000000 --- a/lib/ST25RFAL002/source/st25r3916/rfal_features.h +++ /dev/null @@ -1,109 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

    © COPYRIGHT 2020 STMicroelectronics

    - * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R391x firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file - * - * \author Gustavo Patricio - * - * \brief RFAL Features/Capabilities Definition for ST25R3916 - */ - -#ifndef RFAL_FEATURES_H -#define RFAL_FEATURES_H - -/* -****************************************************************************** -* INCLUDES -****************************************************************************** -*/ -#include "platform.h" - -/* -****************************************************************************** -* GLOBAL DEFINES -****************************************************************************** -*/ - -#define RFAL_SUPPORT_MODE_POLL_NFCA true /*!< RFAL Poll NFCA mode support switch */ -#define RFAL_SUPPORT_MODE_POLL_NFCB true /*!< RFAL Poll NFCB mode support switch */ -#define RFAL_SUPPORT_MODE_POLL_NFCF true /*!< RFAL Poll NFCF mode support switch */ -#define RFAL_SUPPORT_MODE_POLL_NFCV true /*!< RFAL Poll NFCV mode support switch */ -#define RFAL_SUPPORT_MODE_POLL_ACTIVE_P2P true /*!< RFAL Poll AP2P mode support switch */ -#define RFAL_SUPPORT_MODE_LISTEN_NFCA true /*!< RFAL Listen NFCA mode support switch */ -#define RFAL_SUPPORT_MODE_LISTEN_NFCB false /*!< RFAL Listen NFCB mode support switch */ -#define RFAL_SUPPORT_MODE_LISTEN_NFCF true /*!< RFAL Listen NFCF mode support switch */ -#define RFAL_SUPPORT_MODE_LISTEN_ACTIVE_P2P true /*!< RFAL Listen AP2P mode support switch */ - -/*******************************************************************************/ -/*! RFAL supported Card Emulation (CE) */ -#define RFAL_SUPPORT_CE \ - (RFAL_SUPPORT_MODE_LISTEN_NFCA || RFAL_SUPPORT_MODE_LISTEN_NFCB || \ - RFAL_SUPPORT_MODE_LISTEN_NFCF) - -/*! RFAL supported Reader/Writer (RW) */ -#define RFAL_SUPPORT_RW \ - (RFAL_SUPPORT_MODE_POLL_NFCA || RFAL_SUPPORT_MODE_POLL_NFCB || RFAL_SUPPORT_MODE_POLL_NFCF || \ - RFAL_SUPPORT_MODE_POLL_NFCV) - -/*! RFAL support for Active P2P (AP2P) */ -#define RFAL_SUPPORT_AP2P \ - (RFAL_SUPPORT_MODE_POLL_ACTIVE_P2P || RFAL_SUPPORT_MODE_LISTEN_ACTIVE_P2P) - -/*******************************************************************************/ -#define RFAL_SUPPORT_BR_RW_106 true /*!< RFAL RW 106 Bit Rate support switch */ -#define RFAL_SUPPORT_BR_RW_212 true /*!< RFAL RW 212 Bit Rate support switch */ -#define RFAL_SUPPORT_BR_RW_424 true /*!< RFAL RW 424 Bit Rate support switch */ -#define RFAL_SUPPORT_BR_RW_848 true /*!< RFAL RW 848 Bit Rate support switch */ -#define RFAL_SUPPORT_BR_RW_1695 false /*!< RFAL RW 1695 Bit Rate support switch */ -#define RFAL_SUPPORT_BR_RW_3390 false /*!< RFAL RW 3390 Bit Rate support switch */ -#define RFAL_SUPPORT_BR_RW_6780 false /*!< RFAL RW 6780 Bit Rate support switch */ -#define RFAL_SUPPORT_BR_RW_13560 false /*!< RFAL RW 6780 Bit Rate support switch */ - -/*******************************************************************************/ -#define RFAL_SUPPORT_BR_AP2P_106 true /*!< RFAL AP2P 106 Bit Rate support switch */ -#define RFAL_SUPPORT_BR_AP2P_212 true /*!< RFAL AP2P 212 Bit Rate support switch */ -#define RFAL_SUPPORT_BR_AP2P_424 true /*!< RFAL AP2P 424 Bit Rate support switch */ -#define RFAL_SUPPORT_BR_AP2P_848 false /*!< RFAL AP2P 848 Bit Rate support switch */ - -/*******************************************************************************/ -#define RFAL_SUPPORT_BR_CE_A_106 true /*!< RFAL CE A 106 Bit Rate support switch */ -#define RFAL_SUPPORT_BR_CE_A_212 false /*!< RFAL CE A 212 Bit Rate support switch */ -#define RFAL_SUPPORT_BR_CE_A_424 false /*!< RFAL CE A 424 Bit Rate support switch */ -#define RFAL_SUPPORT_BR_CE_A_848 false /*!< RFAL CE A 848 Bit Rate support switch */ - -/*******************************************************************************/ -#define RFAL_SUPPORT_BR_CE_B_106 false /*!< RFAL CE B 106 Bit Rate support switch */ -#define RFAL_SUPPORT_BR_CE_B_212 false /*!< RFAL CE B 212 Bit Rate support switch */ -#define RFAL_SUPPORT_BR_CE_B_424 false /*!< RFAL CE B 424 Bit Rate support switch */ -#define RFAL_SUPPORT_BR_CE_B_848 false /*!< RFAL CE B 848 Bit Rate support switch */ - -/*******************************************************************************/ -#define RFAL_SUPPORT_BR_CE_F_212 true /*!< RFAL CE F 212 Bit Rate support switch */ -#define RFAL_SUPPORT_BR_CE_F_424 true /*!< RFAL CE F 424 Bit Rate support switch */ - -#endif /* RFAL_FEATURES_H */ diff --git a/lib/ST25RFAL002/source/st25r3916/rfal_rfst25r3916.c b/lib/ST25RFAL002/source/st25r3916/rfal_rfst25r3916.c deleted file mode 100644 index 7b8c243b13b..00000000000 --- a/lib/ST25RFAL002/source/st25r3916/rfal_rfst25r3916.c +++ /dev/null @@ -1,4815 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

    © COPYRIGHT 2020 STMicroelectronics

    - * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R3916 firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file - * - * \author Gustavo Patricio - * - * \brief RF Abstraction Layer (RFAL) - * - * RFAL implementation for ST25R3916 - */ - -/* -****************************************************************************** -* INCLUDES -****************************************************************************** -*/ - -#include "rfal_chip.h" -#include "utils.h" -#include "st25r3916.h" -#include "st25r3916_com.h" -#include "st25r3916_irq.h" -#include "rfal_analogConfig.h" -#include "rfal_iso15693_2.h" -#include "rfal_crc.h" - -/* - ****************************************************************************** - * ENABLE SWITCHES - ****************************************************************************** - */ - -#ifndef RFAL_FEATURE_LISTEN_MODE -#define RFAL_FEATURE_LISTEN_MODE false /* Listen Mode configuration missing. Disabled by default */ -#endif /* RFAL_FEATURE_LISTEN_MODE */ - -#ifndef RFAL_FEATURE_WAKEUP_MODE -#define RFAL_FEATURE_WAKEUP_MODE \ - false /* Wake-Up mode configuration missing. Disabled by default */ -#endif /* RFAL_FEATURE_WAKEUP_MODE */ - -#ifndef RFAL_FEATURE_LOWPOWER_MODE -#define RFAL_FEATURE_LOWPOWER_MODE \ - false /* Low Power mode configuration missing. Disabled by default */ -#endif /* RFAL_FEATURE_LOWPOWER_MODE */ - -/* -****************************************************************************** -* GLOBAL TYPES -****************************************************************************** -*/ - -/*! Struct that holds all involved on a Transceive including the context passed by the caller */ -typedef struct { - rfalTransceiveState state; /*!< Current transceive state */ - rfalTransceiveState lastState; /*!< Last transceive state (debug purposes) */ - ReturnCode status; /*!< Current status/error of the transceive */ - - rfalTransceiveContext ctx; /*!< The transceive context given by the caller */ -} rfalTxRx; - -/*! Struct that holds all context for the Listen Mode */ -typedef struct { - rfalLmState state; /*!< Current Listen Mode state */ - uint32_t mdMask; /*!< Listen Mode mask used */ - uint32_t mdReg; /*!< Listen Mode register value used */ - uint32_t mdIrqs; /*!< Listen Mode IRQs used */ - rfalBitRate brDetected; /*!< Last bit rate detected */ - - uint8_t* rxBuf; /*!< Location to store incoming data in Listen Mode */ - uint16_t rxBufLen; /*!< Length of rxBuf */ - uint16_t* rxLen; /*!< Pointer to write the data length placed into rxBuf */ - bool dataFlag; /*!< Listen Mode current Data Flag */ - bool iniFlag; /*!< Listen Mode initialized Flag (FeliCa slots) */ -} rfalLm; - -/*! Struct that holds all context for the Wake-Up Mode */ -typedef struct { - rfalWumState state; /*!< Current Wake-Up Mode state */ - rfalWakeUpConfig cfg; /*!< Current Wake-Up Mode context */ -} rfalWum; - -/*! Struct that holds all context for the Low Power Mode */ -typedef struct { - bool isRunning; -} rfalLpm; - -/*! Struct that holds the timings GT and FDTs */ -typedef struct { - uint32_t GT; /*!< GT in 1/fc */ - uint32_t FDTListen; /*!< FDTListen in 1/fc */ - uint32_t FDTPoll; /*!< FDTPoll in 1/fc */ - uint8_t nTRFW; /*!< n*TRFW used during RF CA */ -} rfalTimings; - -/*! Struct that holds the software timers */ -typedef struct { - uint32_t GT; /*!< RFAL's GT timer */ - uint32_t RXE; /*!< Timer between RXS and RXE */ - uint32_t txRx; /*!< Transceive sanity timer */ -} rfalTimers; - -/*! Struct that holds the RFAL's callbacks */ -typedef struct { - rfalPreTxRxCallback preTxRx; /*!< RFAL's Pre TxRx callback */ - rfalPostTxRxCallback postTxRx; /*!< RFAL's Post TxRx callback */ - RfalStateChangedCallback state_changed_cb; - void* ctx; -} rfalCallbacks; - -/*! Struct that holds counters to control the FIFO on Tx and Rx */ -typedef struct { - uint16_t - expWL; /*!< The amount of bytes expected to be Tx when a WL interrupt occurs */ - uint16_t - bytesTotal; /*!< Total bytes to be transmitted OR the total bytes received */ - uint16_t - bytesWritten; /*!< Amount of bytes already written on FIFO (Tx) OR read (RX) from FIFO and written on rxBuffer*/ - uint8_t status - [ST25R3916_FIFO_STATUS_LEN]; /*!< FIFO Status Registers */ -} rfalFIFO; - -/*! Struct that holds RFAL's configuration settings */ -typedef struct { - uint8_t obsvModeTx; /*!< RFAL's config of the ST25R3916's observation mode while Tx */ - uint8_t obsvModeRx; /*!< RFAL's config of the ST25R3916's observation mode while Rx */ - rfalEHandling eHandling; /*!< RFAL's error handling config/mode */ -} rfalConfigs; - -/*! Struct that holds NFC-F data - Used only inside rfalFelicaPoll() (static to avoid adding it into stack) */ -typedef struct { - rfalFeliCaPollRes - pollResponses[RFAL_FELICA_POLL_MAX_SLOTS]; /* FeliCa Poll response container for 16 slots */ -} rfalNfcfWorkingData; - -/*! Struct that holds NFC-V current context - * - * This buffer has to be big enough for coping with maximum response size (hamming coded) - * - inventory requests responses: 14*2+2 bytes - * - read single block responses: (32+4)*2+2 bytes - * - read multiple block could be very long... -> not supported - * - current implementation expects it be written in one bulk into FIFO - * - needs to be above FIFO water level of ST25R3916 (200) - * - the coding function needs to be able to - * put more than FIFO water level bytes into it (n*64+1)>200 */ -typedef struct { - uint8_t codingBuffer[( - (2 + 255 + 3) * 2)]; /*!< Coding buffer, length MUST be above 257: [257; ...] */ - uint16_t - nfcvOffset; /*!< Offset needed for ISO15693 coding function */ - rfalTransceiveContext - origCtx; /*!< context provided by user */ - uint16_t - ignoreBits; /*!< Number of bits at the beginning of a frame to be ignored when decoding */ -} rfalNfcvWorkingData; - -/*! RFAL instance */ -typedef struct { - rfalState state; /*!< RFAL's current state */ - rfalMode mode; /*!< RFAL's current mode */ - rfalBitRate txBR; /*!< RFAL's current Tx Bit Rate */ - rfalBitRate rxBR; /*!< RFAL's current Rx Bit Rate */ - bool field; /*!< Current field state (On / Off) */ - - rfalConfigs conf; /*!< RFAL's configuration settings */ - rfalTimings timings; /*!< RFAL's timing setting */ - rfalTxRx TxRx; /*!< RFAL's transceive management */ - rfalFIFO fifo; /*!< RFAL's FIFO management */ - rfalTimers tmr; /*!< RFAL's Software timers */ - rfalCallbacks callbacks; /*!< RFAL's callbacks */ - -#if RFAL_FEATURE_LISTEN_MODE - rfalLm Lm; /*!< RFAL's listen mode management */ -#endif /* RFAL_FEATURE_LISTEN_MODE */ - -#if RFAL_FEATURE_WAKEUP_MODE - rfalWum wum; /*!< RFAL's Wake-up mode management */ -#endif /* RFAL_FEATURE_WAKEUP_MODE */ - -#if RFAL_FEATURE_LOWPOWER_MODE - rfalLpm lpm; /*!< RFAL's Low power mode management */ -#endif /* RFAL_FEATURE_LOWPOWER_MODE */ - -#if RFAL_FEATURE_NFCF - rfalNfcfWorkingData nfcfData; /*!< RFAL's working data when supporting NFC-F */ -#endif /* RFAL_FEATURE_NFCF */ - -#if RFAL_FEATURE_NFCV - rfalNfcvWorkingData nfcvData; /*!< RFAL's working data when performing NFC-V */ -#endif /* RFAL_FEATURE_NFCV */ - -} rfal; - -/*! Felica's command set */ -typedef enum { - FELICA_CMD_POLLING = - 0x00, /*!< Felica Poll/REQC command (aka SENSF_REQ) to identify a card */ - FELICA_CMD_POLLING_RES = - 0x01, /*!< Felica Poll/REQC command (aka SENSF_RES) response */ - FELICA_CMD_REQUEST_SERVICE = - 0x02, /*!< verify the existence of Area and Service */ - FELICA_CMD_REQUEST_RESPONSE = - 0x04, /*!< verify the existence of a card */ - FELICA_CMD_READ_WITHOUT_ENCRYPTION = - 0x06, /*!< read Block Data from a Service that requires no authentication */ - FELICA_CMD_WRITE_WITHOUT_ENCRYPTION = - 0x08, /*!< write Block Data to a Service that requires no authentication */ - FELICA_CMD_REQUEST_SYSTEM_CODE = - 0x0C, /*!< acquire the System Code registered to a card */ - FELICA_CMD_AUTHENTICATION1 = - 0x10, /*!< authenticate a card */ - FELICA_CMD_AUTHENTICATION2 = - 0x12, /*!< allow a card to authenticate a Reader/Writer */ - FELICA_CMD_READ = 0x14, /*!< read Block Data from a Service that requires authentication */ - FELICA_CMD_WRITE = 0x16, /*!< write Block Data to a Service that requires authentication */ -} t_rfalFeliCaCmd; - -/*! Union representing all PTMem sections */ -typedef union { /* PRQA S 0750 # MISRA 19.2 - Both members are of the same type, just different names. Thus no problem can occur. */ - uint8_t PTMem_A - [ST25R3916_PTM_A_LEN]; /*!< PT_Memory area allocated for NFC-A configuration */ - uint8_t PTMem_F - [ST25R3916_PTM_F_LEN]; /*!< PT_Memory area allocated for NFC-F configuration */ - uint8_t - TSN[ST25R3916_PTM_TSN_LEN]; /*!< PT_Memory area allocated for TSN - Random numbers */ -} t_rfalPTMem; - -/* -****************************************************************************** -* GLOBAL DEFINES -****************************************************************************** -*/ - -#define RFAL_FIFO_IN_WL \ - 200U /*!< Number of bytes in the FIFO when WL interrupt occurs while Tx */ -#define RFAL_FIFO_OUT_WL \ - (ST25R3916_FIFO_DEPTH - \ - RFAL_FIFO_IN_WL) /*!< Number of bytes sent/out of the FIFO when WL interrupt occurs while Tx */ - -#define RFAL_FIFO_STATUS_REG1 \ - 0U /*!< Location of FIFO status register 1 in local copy */ -#define RFAL_FIFO_STATUS_REG2 \ - 1U /*!< Location of FIFO status register 2 in local copy */ -#define RFAL_FIFO_STATUS_INVALID \ - 0xFFU /*!< Value indicating that the local FIFO status in invalid|cleared */ - -#define RFAL_ST25R3916_GPT_MAX_1FC \ - rfalConv8fcTo1fc( \ - 0xFFFFU) /*!< Max GPT steps in 1fc (0xFFFF steps of 8/fc => 0xFFFF * 590ns = 38,7ms) */ -#define RFAL_ST25R3916_NRT_MAX_1FC \ - rfalConv4096fcTo1fc( \ - 0xFFFFU) /*!< Max NRT steps in 1fc (0xFFFF steps of 4096/fc => 0xFFFF * 302us = 19.8s ) */ -#define RFAL_ST25R3916_NRT_DISABLED \ - 0U /*!< NRT Disabled: All 0 No-response timer is not started, wait forever */ -#define RFAL_ST25R3916_MRT_MAX_1FC \ - rfalConv64fcTo1fc( \ - 0x00FFU) /*!< Max MRT steps in 1fc (0x00FF steps of 64/fc => 0x00FF * 4.72us = 1.2ms ) */ -#define RFAL_ST25R3916_MRT_MIN_1FC \ - rfalConv64fcTo1fc( \ - 0x0004U) /*!< Min MRT steps in 1fc ( 0<=mrt<=4 ; 4 (64/fc) => 0x0004 * 4.72us = 18.88us ) */ -#define RFAL_ST25R3916_GT_MAX_1FC \ - rfalConvMsTo1fc( \ - 6000U) /*!< Max GT value allowed in 1/fc (SFGI=14 => SFGT + dSFGT = 5.4s) */ -#define RFAL_ST25R3916_GT_MIN_1FC \ - rfalConvMsTo1fc( \ - RFAL_ST25R3916_SW_TMR_MIN_1MS) /*!< Min GT value allowed in 1/fc */ -#define RFAL_ST25R3916_SW_TMR_MIN_1MS \ - 1U /*!< Min value of a SW timer in ms */ - -#define RFAL_OBSMODE_DISABLE \ - 0x00U /*!< Observation Mode disabled */ - -#define RFAL_RX_INCOMPLETE_MAXLEN \ - (uint8_t)1U /*!< Threshold value where incoming rx may be considered as incomplete */ -#define RFAL_EMVCO_RX_MAXLEN \ - (uint8_t)4U /*!< Maximum value where EMVCo to apply special error handling */ - -#define RFAL_NORXE_TOUT \ - 50U /*!< Timeout to be used on a potential missing RXE - Silicon ST25R3916 Errata #TBD */ - -#define RFAL_ISO14443A_SDD_RES_LEN \ - 5U /*!< SDD_RES | Anticollision (UID CLn) length - rfalNfcaSddRes */ -#define RFAL_ISO14443A_CRC_INTVAL \ - 0x6363 /*!< ISO14443 CRC Initial Value|Register */ - -#define RFAL_FELICA_POLL_DELAY_TIME \ - 512U /*!< FeliCa Poll Processing time is 2.417 ms ~512*64/fc Digital 1.1 A4 */ -#define RFAL_FELICA_POLL_SLOT_TIME \ - 256U /*!< FeliCa Poll Time Slot duration is 1.208 ms ~256*64/fc Digital 1.1 A4 */ - -#define RFAL_LM_SENSF_RD0_POS \ - 17U /*!< FeliCa SENSF_RES Request Data RD0 position */ -#define RFAL_LM_SENSF_RD1_POS \ - 18U /*!< FeliCa SENSF_RES Request Data RD1 position */ - -#define RFAL_LM_NFCID_INCOMPLETE \ - 0x04U /*!< NFCA NFCID not complete bit in SEL_RES (SAK) */ - -#define RFAL_ISO15693_IGNORE_BITS \ - rfalConvBytesToBits( \ - 2U) /*!< Ignore collisions before the UID (RES_FLAG + DSFID) */ -#define RFAL_ISO15693_INV_RES_LEN \ - 12U /*!< ISO15693 Inventory response length with CRC (bytes) */ -#define RFAL_ISO15693_INV_RES_DUR \ - 4U /*!< ISO15693 Inventory response duration @ 26 kbps (ms) */ - -#define RFAL_WU_MIN_WEIGHT_VAL \ - 4U /*!< ST25R3916 minimum Wake-up weight value */ - -/*******************************************************************************/ - -#define RFAL_LM_GT \ - rfalConvUsTo1fc( \ - 100U) /*!< Listen Mode Guard Time enforced (GT - Passive; TIRFG - Active) */ -#define RFAL_FDT_POLL_ADJUSTMENT \ - rfalConvUsTo1fc( \ - 80U) /*!< FDT Poll adjustment: Time between the expiration of GPT to the actual Tx */ -#define RFAL_FDT_LISTEN_MRT_ADJUSTMENT \ - 64U /*!< MRT jitter adjustment: timeout will be between [ tout ; tout + 64 cycles ] */ -#define RFAL_AP2P_FIELDOFF_TRFW \ - rfalConv8fcTo1fc( \ - 64U) /*!< Time after TXE and Field Off in AP2P Trfw: 37.76us -> 64 (8/fc) */ - -#ifndef RFAL_ST25R3916_AAT_SETTLE -#define RFAL_ST25R3916_AAT_SETTLE \ - 5U /*!< Time in ms required for AAT pins and Osc to settle after en bit set */ -#endif /* RFAL_ST25R3916_AAT_SETTLE */ - -/*! FWT adjustment: - * 64 : NRT jitter between TXE and NRT start */ -#define RFAL_FWT_ADJUSTMENT 64U - -/*! FWT ISO14443A adjustment: - * 512 : 4bit length - * 64 : Half a bit duration due to ST25R3916 Coherent receiver (1/fc) */ -#define RFAL_FWT_A_ADJUSTMENT (512U + 64U) - -/*! FWT ISO14443B adjustment: - * SOF (14etu) + 1Byte (10etu) + 1etu (IRQ comes 1etu after first byte) - 3etu (ST25R3916 sends TXE 3etu after) */ -#define RFAL_FWT_B_ADJUSTMENT ((14U + 10U + 1U - 3U) * 128U) - -/*! FWT FeliCa 212 adjustment: - * 1024 : Length of the two Sync bytes at 212kbps */ -#define RFAL_FWT_F_212_ADJUSTMENT 1024U - -/*! FWT FeliCa 424 adjustment: - * 512 : Length of the two Sync bytes at 424kbps */ -#define RFAL_FWT_F_424_ADJUSTMENT 512U - -/*! Time between our field Off and other peer field On : Tadt + (n x Trfw) - * Ecma 340 11.1.2 - Tadt: [56.64 , 188.72] us ; n: [0 , 3] ; Trfw = 37.76 us - * Should be: 189 + (3*38) = 303us ; we'll use a more relaxed setting: 605 us */ -#define RFAL_AP2P_FIELDON_TADTTRFW rfalConvUsTo1fc(605U) - -/*! FDT Listen adjustment for ISO14443A EMVCo 2.6 4.8.1.3 ; Digital 1.1 6.10 - * - * 276: Time from the rising pulse of the pause of the logic '1' (i.e. the time point to measure the deaftime from), - * to the actual end of the EOF sequence (the point where the MRT starts). Please note that the ST25R391x uses the - * ISO14443-2 definition where the EOF consists of logic '0' followed by sequence Y. - * -64: Further adjustment for receiver to be ready just before first bit - */ -#define RFAL_FDT_LISTEN_A_ADJUSTMENT (276U - 64U) - -/*! FDT Listen adjustment for ISO14443B EMVCo 2.6 4.8.1.6 ; Digital 1.1 7.9 - * - * 340: Time from the rising edge of the EoS to the starting point of the MRT timer (sometime after the final high - * part of the EoS is completed) - */ -#define RFAL_FDT_LISTEN_B_ADJUSTMENT 340U - -/*! FDT Listen adjustment for ISO15693 - * ISO15693 2000 8.4 t1 MIN = 4192/fc - * ISO15693 2009 9.1 t1 MIN = 4320/fc - * Digital 2.1 B.5 FDTV,LISTEN,MIN = 4310/fc - * Set FDT Listen one step earlier than on the more recent spec versions for greater interoperability - */ -#define RFAL_FDT_LISTEN_V_ADJUSTMENT 64U - -/*! FDT Poll adjustment for ISO14443B Correlator - sst 5 etu */ -#define RFAL_FDT_LISTEN_B_ADJT_CORR 128U - -/*! FDT Poll adjustment for ISO14443B Correlator sst window - 5 etu */ -#define RFAL_FDT_LISTEN_B_ADJT_CORR_SST 20U - -/* -****************************************************************************** -* GLOBAL MACROS -****************************************************************************** -*/ - -/*! Calculates Transceive Sanity Timer. It accounts for the slowest bit rate and the longest data format - * 1s for transmission and reception of a 4K message at 106kpbs (~425ms each direction) - * plus TxRx preparation and FIFO load over Serial Interface */ -#define rfalCalcSanityTmr(fwt) (uint16_t)(1000U + rfalConv1fcToMs((fwt))) - -#define rfalGennTRFW(n) \ - (((n) + 1U) & \ - ST25R3916_REG_AUX_nfc_n_mask) /*!< Generates the next n*TRRW used for RFCA */ - -#define rfalCalcNumBytes(nBits) \ - (((uint32_t)(nBits) + 7U) / \ - 8U) /*!< Returns the number of bytes required to fit given the number of bits */ - -#define rfalTimerStart(timer, time_ms) \ - do { \ - platformTimerDestroy(timer); \ - (timer) = platformTimerCreate((uint16_t)(time_ms)); \ - } while(0) /*!< Configures and starts timer */ -#define rfalTimerisExpired(timer) \ - platformTimerIsExpired( \ - timer) /*!< Checks if timer has expired */ -#define rfalTimerDestroy(timer) \ - platformTimerDestroy( \ - timer) /*!< Destroys timer */ - -#define rfalST25R3916ObsModeDisable() \ - st25r3916WriteTestRegister( \ - 0x01U, \ - (0x40U)) /*!< Disable ST25R3916 Observation mode */ -#define rfalST25R3916ObsModeTx() \ - st25r3916WriteTestRegister( \ - 0x01U, \ - (0x40U | \ - gRFAL.conf \ - .obsvModeTx)) /*!< Enable Tx Observation mode */ -#define rfalST25R3916ObsModeRx() \ - st25r3916WriteTestRegister( \ - 0x01U, \ - (0x40U | \ - gRFAL.conf \ - .obsvModeRx)) /*!< Enable Rx Observation mode */ - -#define rfalCheckDisableObsMode() \ - if(gRFAL.conf.obsvModeRx != 0U) { \ - rfalST25R3916ObsModeDisable(); \ - } /*!< Checks if the observation mode is enabled, and applies on ST25R3916 */ -#define rfalCheckEnableObsModeTx() \ - if(gRFAL.conf.obsvModeTx != 0U) { \ - rfalST25R3916ObsModeTx(); \ - } /*!< Checks if the observation mode is enabled, and applies on ST25R3916 */ -#define rfalCheckEnableObsModeRx() \ - if(gRFAL.conf.obsvModeRx != 0U) { \ - rfalST25R3916ObsModeRx(); \ - } /*!< Checks if the observation mode is enabled, and applies on ST25R3916 */ - -#define rfalGetIncmplBits(FIFOStatus2) \ - (((FIFOStatus2) >> 1) & \ - 0x07U) /*!< Returns the number of bits from fifo status */ -#define rfalIsIncompleteByteError(error) \ - (((error) >= ERR_INCOMPLETE_BYTE) && \ - ((error) <= \ - ERR_INCOMPLETE_BYTE_07)) /*!< Checks if given error is a Incomplete error */ - -#define rfalAdjACBR(b) \ - (((uint16_t)(b) >= (uint16_t)RFAL_BR_52p97) ? \ - (uint16_t)(b) : \ - ((uint16_t)(b) + \ - 1U)) /*!< Adjusts ST25R391x Bit rate to Analog Configuration */ -#define rfalConvBR2ACBR(b) \ - (((rfalAdjACBR((b))) << RFAL_ANALOG_CONFIG_BITRATE_SHIFT) & \ - RFAL_ANALOG_CONFIG_BITRATE_MASK) /*!< Converts ST25R391x Bit rate to Analog Configuration bit rate id */ - -#define rfalConvTDFormat(v) \ - ((uint16_t)(v) << 8U) /*!< Converts a uint8_t to the format used in SW Tag Detection */ - -/* - ****************************************************************************** - * LOCAL VARIABLES - ****************************************************************************** - */ - -static rfal gRFAL; /*!< RFAL module instance */ - -/* -****************************************************************************** -* LOCAL FUNCTION PROTOTYPES -****************************************************************************** -*/ - -static void rfalTransceiveTx(void); -static void rfalTransceiveRx(void); -static ReturnCode rfalTransceiveRunBlockingTx(void); -static void rfalPrepareTransceive(void); -static void rfalCleanupTransceive(void); -static void rfalErrorHandling(void); - -static ReturnCode rfalRunTransceiveWorker(void); -#if RFAL_FEATURE_LISTEN_MODE -static ReturnCode rfalRunListenModeWorker(void); -#endif /* RFAL_FEATURE_LISTEN_MODE */ -#if RFAL_FEATURE_WAKEUP_MODE -static void rfalRunWakeUpModeWorker(void); -static uint16_t rfalWakeUpModeFilter(uint16_t curRef, uint16_t curVal, uint8_t weight); -#endif /* RFAL_FEATURE_WAKEUP_MODE */ - -static void rfalFIFOStatusUpdate(void); -static void rfalFIFOStatusClear(void); -static bool rfalFIFOStatusIsMissingPar(void); -static bool rfalFIFOStatusIsIncompleteByte(void); -static uint16_t rfalFIFOStatusGetNumBytes(void); -static uint8_t rfalFIFOGetNumIncompleteBits(void); - -/* -****************************************************************************** -* GLOBAL FUNCTIONS -****************************************************************************** -*/ - -/*******************************************************************************/ -ReturnCode rfalInitialize(void) { - ReturnCode err; - - EXIT_ON_ERR(err, st25r3916Initialize()); - - st25r3916ClearInterrupts(); - - /* Disable any previous observation mode */ - rfalST25R3916ObsModeDisable(); - - /*******************************************************************************/ - /* Apply RF Chip generic initialization */ - rfalSetAnalogConfig((RFAL_ANALOG_CONFIG_TECH_CHIP | RFAL_ANALOG_CONFIG_CHIP_INIT)); - - // TODO: - // I don't want to mess with config table ("Default Analog Configuration for Chip-Specific Reset", rfal_analogConfigTbl.h) - // so with every rfalSetAnalogConfig((RFAL_ANALOG_CONFIG_CHIP_INIT)) currently we need to clear pulldown bits - // luckily for us this is done only here - - // disable pulldowns - st25r3916ClrRegisterBits( - ST25R3916_REG_IO_CONF2, - (ST25R3916_REG_IO_CONF2_miso_pd1 | ST25R3916_REG_IO_CONF2_miso_pd2)); - - /*******************************************************************************/ - /* Enable External Field Detector as: Automatics */ - st25r3916ChangeRegisterBits( - ST25R3916_REG_OP_CONTROL, - ST25R3916_REG_OP_CONTROL_en_fd_mask, - ST25R3916_REG_OP_CONTROL_en_fd_auto_efd); - - /* Clear FIFO status local copy */ - rfalFIFOStatusClear(); - - /*******************************************************************************/ - gRFAL.state = RFAL_STATE_INIT; - gRFAL.mode = RFAL_MODE_NONE; - gRFAL.field = false; - - /* Set RFAL default configs */ - gRFAL.conf.obsvModeRx = RFAL_OBSMODE_DISABLE; - gRFAL.conf.obsvModeTx = RFAL_OBSMODE_DISABLE; - gRFAL.conf.eHandling = RFAL_ERRORHANDLING_NONE; - - /* Transceive set to IDLE */ - gRFAL.TxRx.lastState = RFAL_TXRX_STATE_IDLE; - gRFAL.TxRx.state = RFAL_TXRX_STATE_IDLE; - - /* Disable all timings */ - gRFAL.timings.FDTListen = RFAL_TIMING_NONE; - gRFAL.timings.FDTPoll = RFAL_TIMING_NONE; - gRFAL.timings.GT = RFAL_TIMING_NONE; - gRFAL.timings.nTRFW = 0U; - - /* Destroy any previous pending timers */ - rfalTimerDestroy(gRFAL.tmr.GT); - rfalTimerDestroy(gRFAL.tmr.txRx); - rfalTimerDestroy(gRFAL.tmr.RXE); - gRFAL.tmr.GT = RFAL_TIMING_NONE; - gRFAL.tmr.txRx = RFAL_TIMING_NONE; - gRFAL.tmr.RXE = RFAL_TIMING_NONE; - - gRFAL.callbacks.preTxRx = NULL; - gRFAL.callbacks.postTxRx = NULL; - gRFAL.callbacks.state_changed_cb = NULL; - gRFAL.callbacks.ctx = NULL; - -#if RFAL_FEATURE_NFCV - /* Initialize NFC-V Data */ - gRFAL.nfcvData.ignoreBits = 0; -#endif /* RFAL_FEATURE_NFCV */ - -#if RFAL_FEATURE_LISTEN_MODE - /* Initialize Listen Mode */ - gRFAL.Lm.state = RFAL_LM_STATE_NOT_INIT; - gRFAL.Lm.brDetected = RFAL_BR_KEEP; - gRFAL.Lm.iniFlag = false; -#endif /* RFAL_FEATURE_LISTEN_MODE */ - -#if RFAL_FEATURE_WAKEUP_MODE - /* Initialize Wake-Up Mode */ - gRFAL.wum.state = RFAL_WUM_STATE_NOT_INIT; -#endif /* RFAL_FEATURE_WAKEUP_MODE */ - -#if RFAL_FEATURE_LOWPOWER_MODE - /* Initialize Low Power Mode */ - gRFAL.lpm.isRunning = false; -#endif /* RFAL_FEATURE_LOWPOWER_MODE */ - - /*******************************************************************************/ - /* Perform Automatic Calibration (if configured to do so). * - * Registers set by rfalSetAnalogConfig will tell rfalCalibrate what to perform*/ - rfalCalibrate(); - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalCalibrate(void) { - uint16_t resValue; - - /* Check if RFAL is not initialized */ - if(gRFAL.state == RFAL_STATE_IDLE) { - return ERR_WRONG_STATE; - } - - /*******************************************************************************/ - /* Perform ST25R3916 regulators and antenna calibration */ - /*******************************************************************************/ - - /* Automatic regulator adjustment only performed if not set manually on Analog Configs */ - if(st25r3916CheckReg( - ST25R3916_REG_REGULATOR_CONTROL, ST25R3916_REG_REGULATOR_CONTROL_reg_s, 0x00)) { - /* Adjust the regulators so that Antenna Calibrate has better Regulator values */ - st25r3916AdjustRegulators(&resValue); - } - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalAdjustRegulators(uint16_t* result) { - return st25r3916AdjustRegulators(result); -} - -/*******************************************************************************/ -void rfalSetUpperLayerCallback(rfalUpperLayerCallback pFunc) { - st25r3916IRQCallbackSet(pFunc); -} - -/*******************************************************************************/ -void rfalSetPreTxRxCallback(rfalPreTxRxCallback pFunc) { - gRFAL.callbacks.preTxRx = pFunc; -} - -/*******************************************************************************/ -void rfalSetPostTxRxCallback(rfalPostTxRxCallback pFunc) { - gRFAL.callbacks.postTxRx = pFunc; -} - -void rfal_set_state_changed_callback(RfalStateChangedCallback callback) { - gRFAL.callbacks.state_changed_cb = callback; -} - -void rfal_set_callback_context(void* context) { - gRFAL.callbacks.ctx = context; -} - -/*******************************************************************************/ -ReturnCode rfalDeinitialize(void) { - /* Deinitialize chip */ - st25r3916Deinitialize(); - - /* Set Analog configurations for deinitialization */ - rfalSetAnalogConfig((RFAL_ANALOG_CONFIG_TECH_CHIP | RFAL_ANALOG_CONFIG_CHIP_DEINIT)); - - gRFAL.state = RFAL_STATE_IDLE; - return ERR_NONE; -} - -/*******************************************************************************/ -void rfalSetObsvMode(uint8_t txMode, uint8_t rxMode) { - gRFAL.conf.obsvModeTx = txMode; - gRFAL.conf.obsvModeRx = rxMode; -} - -/*******************************************************************************/ -void rfalGetObsvMode(uint8_t* txMode, uint8_t* rxMode) { - if(txMode != NULL) { - *txMode = gRFAL.conf.obsvModeTx; - } - - if(rxMode != NULL) { - *rxMode = gRFAL.conf.obsvModeRx; - } -} - -/*******************************************************************************/ -void rfalDisableObsvMode(void) { - gRFAL.conf.obsvModeTx = RFAL_OBSMODE_DISABLE; - gRFAL.conf.obsvModeRx = RFAL_OBSMODE_DISABLE; -} - -/*******************************************************************************/ -ReturnCode rfalSetMode(rfalMode mode, rfalBitRate txBR, rfalBitRate rxBR) { - /* Check if RFAL is not initialized */ - if(gRFAL.state == RFAL_STATE_IDLE) { - return ERR_WRONG_STATE; - } - - /* Check allowed bit rate value */ - if((txBR == RFAL_BR_KEEP) || (rxBR == RFAL_BR_KEEP)) { - return ERR_PARAM; - } - - switch(mode) { - /*******************************************************************************/ - case RFAL_MODE_POLL_NFCA: - - /* Disable wake up mode, if set */ - st25r3916ClrRegisterBits(ST25R3916_REG_OP_CONTROL, ST25R3916_REG_OP_CONTROL_wu); - - /* Enable ISO14443A mode */ - st25r3916WriteRegister(ST25R3916_REG_MODE, ST25R3916_REG_MODE_om_iso14443a); - - /* Set Analog configurations for this mode and bit rate */ - rfalSetAnalogConfig( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCA | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_TX)); - rfalSetAnalogConfig( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCA | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_RX)); - break; - - /*******************************************************************************/ - case RFAL_MODE_POLL_NFCA_T1T: - /* Disable wake up mode, if set */ - st25r3916ClrRegisterBits(ST25R3916_REG_OP_CONTROL, ST25R3916_REG_OP_CONTROL_wu); - - /* Enable Topaz mode */ - st25r3916WriteRegister(ST25R3916_REG_MODE, ST25R3916_REG_MODE_om_topaz); - - /* Set Analog configurations for this mode and bit rate */ - rfalSetAnalogConfig( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCA | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_TX)); - rfalSetAnalogConfig( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCA | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_RX)); - break; - - /*******************************************************************************/ - case RFAL_MODE_POLL_NFCB: - - /* Disable wake up mode, if set */ - st25r3916ClrRegisterBits(ST25R3916_REG_OP_CONTROL, ST25R3916_REG_OP_CONTROL_wu); - - /* Enable ISO14443B mode */ - st25r3916WriteRegister(ST25R3916_REG_MODE, ST25R3916_REG_MODE_om_iso14443b); - - /* Set the EGT, SOF, EOF and EOF */ - st25r3916ChangeRegisterBits( - ST25R3916_REG_ISO14443B_1, - (ST25R3916_REG_ISO14443B_1_egt_mask | ST25R3916_REG_ISO14443B_1_sof_mask | - ST25R3916_REG_ISO14443B_1_eof), - ((0U << ST25R3916_REG_ISO14443B_1_egt_shift) | ST25R3916_REG_ISO14443B_1_sof_0_10etu | - ST25R3916_REG_ISO14443B_1_sof_1_2etu | ST25R3916_REG_ISO14443B_1_eof_10etu)); - - /* Set the minimum TR1, SOF, EOF and EOF12 */ - st25r3916ChangeRegisterBits( - ST25R3916_REG_ISO14443B_2, - (ST25R3916_REG_ISO14443B_2_tr1_mask | ST25R3916_REG_ISO14443B_2_no_sof | - ST25R3916_REG_ISO14443B_2_no_eof), - (ST25R3916_REG_ISO14443B_2_tr1_80fs80fs)); - - /* Set Analog configurations for this mode and bit rate */ - rfalSetAnalogConfig( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCB | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_TX)); - rfalSetAnalogConfig( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCB | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_RX)); - break; - - /*******************************************************************************/ - case RFAL_MODE_POLL_B_PRIME: - - /* Disable wake up mode, if set */ - st25r3916ClrRegisterBits(ST25R3916_REG_OP_CONTROL, ST25R3916_REG_OP_CONTROL_wu); - - /* Enable ISO14443B mode */ - st25r3916WriteRegister(ST25R3916_REG_MODE, ST25R3916_REG_MODE_om_iso14443b); - - /* Set the EGT, SOF, EOF and EOF */ - st25r3916ChangeRegisterBits( - ST25R3916_REG_ISO14443B_1, - (ST25R3916_REG_ISO14443B_1_egt_mask | ST25R3916_REG_ISO14443B_1_sof_mask | - ST25R3916_REG_ISO14443B_1_eof), - ((0U << ST25R3916_REG_ISO14443B_1_egt_shift) | ST25R3916_REG_ISO14443B_1_sof_0_10etu | - ST25R3916_REG_ISO14443B_1_sof_1_2etu | ST25R3916_REG_ISO14443B_1_eof_10etu)); - - /* Set the minimum TR1, EOF and EOF12 */ - st25r3916ChangeRegisterBits( - ST25R3916_REG_ISO14443B_2, - (ST25R3916_REG_ISO14443B_2_tr1_mask | ST25R3916_REG_ISO14443B_2_no_sof | - ST25R3916_REG_ISO14443B_2_no_eof), - (ST25R3916_REG_ISO14443B_2_tr1_80fs80fs | ST25R3916_REG_ISO14443B_2_no_sof)); - - /* Set Analog configurations for this mode and bit rate */ - rfalSetAnalogConfig( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCB | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_TX)); - rfalSetAnalogConfig( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCB | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_RX)); - break; - - /*******************************************************************************/ - case RFAL_MODE_POLL_B_CTS: - - /* Disable wake up mode, if set */ - st25r3916ClrRegisterBits(ST25R3916_REG_OP_CONTROL, ST25R3916_REG_OP_CONTROL_wu); - - /* Enable ISO14443B mode */ - st25r3916WriteRegister(ST25R3916_REG_MODE, ST25R3916_REG_MODE_om_iso14443b); - - /* Set the EGT, SOF, EOF and EOF */ - st25r3916ChangeRegisterBits( - ST25R3916_REG_ISO14443B_1, - (ST25R3916_REG_ISO14443B_1_egt_mask | ST25R3916_REG_ISO14443B_1_sof_mask | - ST25R3916_REG_ISO14443B_1_eof), - ((0U << ST25R3916_REG_ISO14443B_1_egt_shift) | ST25R3916_REG_ISO14443B_1_sof_0_10etu | - ST25R3916_REG_ISO14443B_1_sof_1_2etu | ST25R3916_REG_ISO14443B_1_eof_10etu)); - - /* Set the minimum TR1, clear SOF, EOF and EOF12 */ - st25r3916ChangeRegisterBits( - ST25R3916_REG_ISO14443B_2, - (ST25R3916_REG_ISO14443B_2_tr1_mask | ST25R3916_REG_ISO14443B_2_no_sof | - ST25R3916_REG_ISO14443B_2_no_eof), - (ST25R3916_REG_ISO14443B_2_tr1_80fs80fs | ST25R3916_REG_ISO14443B_2_no_sof | - ST25R3916_REG_ISO14443B_2_no_eof)); - - /* Set Analog configurations for this mode and bit rate */ - rfalSetAnalogConfig( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCB | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_TX)); - rfalSetAnalogConfig( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCB | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_RX)); - break; - - /*******************************************************************************/ - case RFAL_MODE_POLL_NFCF: - - /* Disable wake up mode, if set */ - st25r3916ClrRegisterBits(ST25R3916_REG_OP_CONTROL, ST25R3916_REG_OP_CONTROL_wu); - - /* Enable FeliCa mode */ - st25r3916WriteRegister(ST25R3916_REG_MODE, ST25R3916_REG_MODE_om_felica); - - /* Set Analog configurations for this mode and bit rate */ - rfalSetAnalogConfig( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCF | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_TX)); - rfalSetAnalogConfig( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCF | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_RX)); - break; - - /*******************************************************************************/ - case RFAL_MODE_POLL_NFCV: - case RFAL_MODE_POLL_PICOPASS: - -#if !RFAL_FEATURE_NFCV - return ERR_DISABLED; -#else - - /* Disable wake up mode, if set */ - st25r3916ClrRegisterBits(ST25R3916_REG_OP_CONTROL, ST25R3916_REG_OP_CONTROL_wu); - - /* Set Analog configurations for this mode and bit rate */ - rfalSetAnalogConfig( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCV | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_TX)); - rfalSetAnalogConfig( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCV | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_RX)); - break; - -#endif /* RFAL_FEATURE_NFCV */ - - /*******************************************************************************/ - case RFAL_MODE_POLL_ACTIVE_P2P: - - /* Set NFCIP1 active communication Initiator mode and Automatic Response RF Collision Avoidance to always after EOF */ - st25r3916WriteRegister( - ST25R3916_REG_MODE, - (ST25R3916_REG_MODE_targ_init | ST25R3916_REG_MODE_om_nfc | - ST25R3916_REG_MODE_nfc_ar_eof)); - - /* External Field Detector enabled as Automatics on rfalInitialize() */ - - /* Set NRT to start at end of TX (own) field */ - st25r3916ChangeRegisterBits( - ST25R3916_REG_TIMER_EMV_CONTROL, - ST25R3916_REG_TIMER_EMV_CONTROL_nrt_nfc, - ST25R3916_REG_TIMER_EMV_CONTROL_nrt_nfc_off); - - /* Set GPT to start after end of TX, as GPT is used in active communication mode to timeout the field switching off */ - /* The field is turned off 37.76us after the end of the transmission Trfw */ - st25r3916SetStartGPTimer( - (uint16_t)rfalConv1fcTo8fc(RFAL_AP2P_FIELDOFF_TRFW), - ST25R3916_REG_TIMER_EMV_CONTROL_gptc_etx_nfc); - - /* Set PPon2 timer with the max time between our field Off and other peer field On : Tadt + (n x Trfw) */ - st25r3916WriteRegister( - ST25R3916_REG_PPON2, (uint8_t)rfalConv1fcTo64fc(RFAL_AP2P_FIELDON_TADTTRFW)); - - /* Set Analog configurations for this mode and bit rate */ - rfalSetAnalogConfig( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_AP2P | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_TX)); - rfalSetAnalogConfig( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_AP2P | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_RX)); - break; - - /*******************************************************************************/ - case RFAL_MODE_LISTEN_ACTIVE_P2P: - - /* Set NFCIP1 active communication Target mode and Automatic Response RF Collision Avoidance to always after EOF */ - st25r3916WriteRegister( - ST25R3916_REG_MODE, - (ST25R3916_REG_MODE_targ_targ | ST25R3916_REG_MODE_om_targ_nfcip | - ST25R3916_REG_MODE_nfc_ar_eof)); - - /* Set TARFG: 0 (75us+0ms=75us), as Target no Guard time needed */ - st25r3916WriteRegister(ST25R3916_REG_FIELD_ON_GT, 0U); - - /* External Field Detector enabled as Automatics on rfalInitialize() */ - - /* Set NRT to start at end of TX (own) field */ - st25r3916ChangeRegisterBits( - ST25R3916_REG_TIMER_EMV_CONTROL, - ST25R3916_REG_TIMER_EMV_CONTROL_nrt_nfc, - ST25R3916_REG_TIMER_EMV_CONTROL_nrt_nfc_off); - - /* Set GPT to start after end of TX, as GPT is used in active communication mode to timeout the field switching off */ - /* The field is turned off 37.76us after the end of the transmission Trfw */ - st25r3916SetStartGPTimer( - (uint16_t)rfalConv1fcTo8fc(RFAL_AP2P_FIELDOFF_TRFW), - ST25R3916_REG_TIMER_EMV_CONTROL_gptc_etx_nfc); - - /* Set PPon2 timer with the max time between our field Off and other peer field On : Tadt + (n x Trfw) */ - st25r3916WriteRegister( - ST25R3916_REG_PPON2, (uint8_t)rfalConv1fcTo64fc(RFAL_AP2P_FIELDON_TADTTRFW)); - - /* Set Analog configurations for this mode and bit rate */ - rfalSetAnalogConfig( - (RFAL_ANALOG_CONFIG_LISTEN | RFAL_ANALOG_CONFIG_TECH_AP2P | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_TX)); - rfalSetAnalogConfig( - (RFAL_ANALOG_CONFIG_LISTEN | RFAL_ANALOG_CONFIG_TECH_AP2P | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_RX)); - break; - - /*******************************************************************************/ - case RFAL_MODE_LISTEN_NFCA: - - /* Disable wake up mode, if set */ - st25r3916ClrRegisterBits(ST25R3916_REG_OP_CONTROL, ST25R3916_REG_OP_CONTROL_wu); - - /* Enable Passive Target NFC-A mode, disable any Collision Avoidance */ - st25r3916WriteRegister( - ST25R3916_REG_MODE, - (ST25R3916_REG_MODE_targ | ST25R3916_REG_MODE_om_targ_nfca | - ST25R3916_REG_MODE_nfc_ar_off)); - - /* Set Analog configurations for this mode */ - rfalSetAnalogConfig( - (RFAL_ANALOG_CONFIG_LISTEN | RFAL_ANALOG_CONFIG_TECH_NFCA | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_TX)); - rfalSetAnalogConfig( - (RFAL_ANALOG_CONFIG_LISTEN | RFAL_ANALOG_CONFIG_TECH_NFCA | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_RX)); - break; - - /*******************************************************************************/ - case RFAL_MODE_LISTEN_NFCF: - - /* Disable wake up mode, if set */ - st25r3916ClrRegisterBits(ST25R3916_REG_OP_CONTROL, ST25R3916_REG_OP_CONTROL_wu); - - /* Enable Passive Target NFC-F mode, disable any Collision Avoidance */ - st25r3916WriteRegister( - ST25R3916_REG_MODE, - (ST25R3916_REG_MODE_targ | ST25R3916_REG_MODE_om_targ_nfcf | - ST25R3916_REG_MODE_nfc_ar_off)); - - /* Set Analog configurations for this mode */ - rfalSetAnalogConfig( - (RFAL_ANALOG_CONFIG_LISTEN | RFAL_ANALOG_CONFIG_TECH_NFCF | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_TX)); - rfalSetAnalogConfig( - (RFAL_ANALOG_CONFIG_LISTEN | RFAL_ANALOG_CONFIG_TECH_NFCF | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_RX)); - break; - - /*******************************************************************************/ - case RFAL_MODE_LISTEN_NFCB: - return ERR_NOTSUPP; - - /*******************************************************************************/ - default: - return ERR_NOT_IMPLEMENTED; - } - - /* Set state as STATE_MODE_SET only if not initialized yet (PSL) */ - gRFAL.state = ((gRFAL.state < RFAL_STATE_MODE_SET) ? RFAL_STATE_MODE_SET : gRFAL.state); - gRFAL.mode = mode; - - /* Apply the given bit rate */ - return rfalSetBitRate(txBR, rxBR); -} - -/*******************************************************************************/ -rfalMode rfalGetMode(void) { - return gRFAL.mode; -} - -/*******************************************************************************/ -ReturnCode rfalSetBitRate(rfalBitRate txBR, rfalBitRate rxBR) { - ReturnCode ret; - - /* Check if RFAL is not initialized */ - if(gRFAL.state == RFAL_STATE_IDLE) { - return ERR_WRONG_STATE; - } - - /* Store the new Bit Rates */ - gRFAL.txBR = ((txBR == RFAL_BR_KEEP) ? gRFAL.txBR : txBR); - gRFAL.rxBR = ((rxBR == RFAL_BR_KEEP) ? gRFAL.rxBR : rxBR); - - /* Update the bitrate reg if not in NFCV mode (streaming) */ - if((RFAL_MODE_POLL_NFCV != gRFAL.mode) && (RFAL_MODE_POLL_PICOPASS != gRFAL.mode)) { - /* Set bit rate register */ - EXIT_ON_ERR(ret, st25r3916SetBitrate((uint8_t)gRFAL.txBR, (uint8_t)gRFAL.rxBR)); - } - - switch(gRFAL.mode) { - /*******************************************************************************/ - case RFAL_MODE_POLL_NFCA: - case RFAL_MODE_POLL_NFCA_T1T: - - /* Set Analog configurations for this bit rate */ - rfalSetAnalogConfig((RFAL_ANALOG_CONFIG_TECH_CHIP | RFAL_ANALOG_CONFIG_CHIP_POLL_COMMON)); - rfalSetAnalogConfig( (rfalAnalogConfigId)(RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCA | rfalConvBR2ACBR(gRFAL.txBR) | RFAL_ANALOG_CONFIG_TX ) ); - rfalSetAnalogConfig( (rfalAnalogConfigId)(RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCA | rfalConvBR2ACBR(gRFAL.rxBR) | RFAL_ANALOG_CONFIG_RX ) ); - break; - - /*******************************************************************************/ - case RFAL_MODE_POLL_NFCB: - case RFAL_MODE_POLL_B_PRIME: - case RFAL_MODE_POLL_B_CTS: - - /* Set Analog configurations for this bit rate */ - rfalSetAnalogConfig((RFAL_ANALOG_CONFIG_TECH_CHIP | RFAL_ANALOG_CONFIG_CHIP_POLL_COMMON)); - rfalSetAnalogConfig( (rfalAnalogConfigId)(RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCB | rfalConvBR2ACBR(gRFAL.txBR) | RFAL_ANALOG_CONFIG_TX ) ); - rfalSetAnalogConfig( (rfalAnalogConfigId)(RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCB | rfalConvBR2ACBR(gRFAL.rxBR) | RFAL_ANALOG_CONFIG_RX ) ); - break; - - /*******************************************************************************/ - case RFAL_MODE_POLL_NFCF: - - /* Set Analog configurations for this bit rate */ - rfalSetAnalogConfig((RFAL_ANALOG_CONFIG_TECH_CHIP | RFAL_ANALOG_CONFIG_CHIP_POLL_COMMON)); - rfalSetAnalogConfig( (rfalAnalogConfigId)(RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCF | rfalConvBR2ACBR(gRFAL.txBR) | RFAL_ANALOG_CONFIG_TX ) ); - rfalSetAnalogConfig( (rfalAnalogConfigId)(RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCF | rfalConvBR2ACBR(gRFAL.rxBR) | RFAL_ANALOG_CONFIG_RX ) ); - break; - - /*******************************************************************************/ - case RFAL_MODE_POLL_NFCV: - case RFAL_MODE_POLL_PICOPASS: - -#if !RFAL_FEATURE_NFCV - return ERR_DISABLED; -#else - - if(((gRFAL.rxBR != RFAL_BR_26p48) && (gRFAL.rxBR != RFAL_BR_52p97)) || - ((gRFAL.txBR != RFAL_BR_1p66) && (gRFAL.txBR != RFAL_BR_26p48))) { - return ERR_PARAM; - } - - { - const struct iso15693StreamConfig* isoStreamConfig; - struct st25r3916StreamConfig streamConf; - iso15693PhyConfig_t config; - - config.coding = - ((gRFAL.txBR == RFAL_BR_1p66) ? ISO15693_VCD_CODING_1_256 : - ISO15693_VCD_CODING_1_4); - switch(gRFAL.rxBR) { - case RFAL_BR_52p97: - config.speedMode = 1; - break; - default: - config.speedMode = 0; - break; - } - - iso15693PhyConfigure(&config, &isoStreamConfig); - - /* MISRA 11.3 - Cannot point directly into different object type, copy to local var */ - streamConf.din = isoStreamConfig->din; - streamConf.dout = isoStreamConfig->dout; - streamConf.report_period_length = isoStreamConfig->report_period_length; - streamConf.useBPSK = isoStreamConfig->useBPSK; - st25r3916StreamConfigure(&streamConf); - } - - /* Set Analog configurations for this bit rate */ - rfalSetAnalogConfig((RFAL_ANALOG_CONFIG_TECH_CHIP | RFAL_ANALOG_CONFIG_CHIP_POLL_COMMON)); - rfalSetAnalogConfig( (rfalAnalogConfigId)(RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCV | rfalConvBR2ACBR(gRFAL.txBR) | RFAL_ANALOG_CONFIG_TX ) ); - rfalSetAnalogConfig( (rfalAnalogConfigId)(RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCV | rfalConvBR2ACBR(gRFAL.rxBR) | RFAL_ANALOG_CONFIG_RX ) ); - break; - -#endif /* RFAL_FEATURE_NFCV */ - - /*******************************************************************************/ - case RFAL_MODE_POLL_ACTIVE_P2P: - - /* Set Analog configurations for this bit rate */ - rfalSetAnalogConfig((RFAL_ANALOG_CONFIG_TECH_CHIP | RFAL_ANALOG_CONFIG_CHIP_POLL_COMMON)); - rfalSetAnalogConfig( (rfalAnalogConfigId)(RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_AP2P | rfalConvBR2ACBR(gRFAL.txBR) | RFAL_ANALOG_CONFIG_TX ) ); - rfalSetAnalogConfig( (rfalAnalogConfigId)(RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_AP2P | rfalConvBR2ACBR(gRFAL.rxBR) | RFAL_ANALOG_CONFIG_RX ) ); - break; - - /*******************************************************************************/ - case RFAL_MODE_LISTEN_ACTIVE_P2P: - - /* Set Analog configurations for this bit rate */ - rfalSetAnalogConfig( - (RFAL_ANALOG_CONFIG_TECH_CHIP | RFAL_ANALOG_CONFIG_CHIP_LISTEN_COMMON)); - rfalSetAnalogConfig( (rfalAnalogConfigId)(RFAL_ANALOG_CONFIG_LISTEN | RFAL_ANALOG_CONFIG_TECH_AP2P | rfalConvBR2ACBR(gRFAL.txBR) | RFAL_ANALOG_CONFIG_TX ) ); - rfalSetAnalogConfig( (rfalAnalogConfigId)(RFAL_ANALOG_CONFIG_LISTEN | RFAL_ANALOG_CONFIG_TECH_AP2P | rfalConvBR2ACBR(gRFAL.rxBR) | RFAL_ANALOG_CONFIG_RX ) ); - break; - - /*******************************************************************************/ - case RFAL_MODE_LISTEN_NFCA: - - /* Set Analog configurations for this bit rate */ - rfalSetAnalogConfig( - (RFAL_ANALOG_CONFIG_TECH_CHIP | RFAL_ANALOG_CONFIG_CHIP_LISTEN_COMMON)); - rfalSetAnalogConfig( (rfalAnalogConfigId)(RFAL_ANALOG_CONFIG_LISTEN | RFAL_ANALOG_CONFIG_TECH_NFCA | rfalConvBR2ACBR(gRFAL.txBR) | RFAL_ANALOG_CONFIG_TX ) ); - rfalSetAnalogConfig( (rfalAnalogConfigId)(RFAL_ANALOG_CONFIG_LISTEN | RFAL_ANALOG_CONFIG_TECH_NFCA | rfalConvBR2ACBR(gRFAL.rxBR) | RFAL_ANALOG_CONFIG_RX ) ); - break; - - /*******************************************************************************/ - case RFAL_MODE_LISTEN_NFCF: - - /* Set Analog configurations for this bit rate */ - rfalSetAnalogConfig( - (RFAL_ANALOG_CONFIG_TECH_CHIP | RFAL_ANALOG_CONFIG_CHIP_LISTEN_COMMON)); - rfalSetAnalogConfig( (rfalAnalogConfigId)(RFAL_ANALOG_CONFIG_LISTEN | RFAL_ANALOG_CONFIG_TECH_NFCF | rfalConvBR2ACBR(gRFAL.txBR) | RFAL_ANALOG_CONFIG_TX ) ); - rfalSetAnalogConfig( (rfalAnalogConfigId)(RFAL_ANALOG_CONFIG_LISTEN | RFAL_ANALOG_CONFIG_TECH_NFCF | rfalConvBR2ACBR(gRFAL.rxBR) | RFAL_ANALOG_CONFIG_RX ) ); - break; - - /*******************************************************************************/ - case RFAL_MODE_LISTEN_NFCB: - case RFAL_MODE_NONE: - return ERR_WRONG_STATE; - - /*******************************************************************************/ - default: - return ERR_NOT_IMPLEMENTED; - } - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalGetBitRate(rfalBitRate* txBR, rfalBitRate* rxBR) { - if((gRFAL.state == RFAL_STATE_IDLE) || (gRFAL.mode == RFAL_MODE_NONE)) { - return ERR_WRONG_STATE; - } - - if(txBR != NULL) { - *txBR = gRFAL.txBR; - } - - if(rxBR != NULL) { - *rxBR = gRFAL.rxBR; - } - - return ERR_NONE; -} - -/*******************************************************************************/ -void rfalSetErrorHandling(rfalEHandling eHandling) { - switch(eHandling) { - case RFAL_ERRORHANDLING_NFC: - case RFAL_ERRORHANDLING_NONE: - st25r3916ClrRegisterBits(ST25R3916_REG_EMD_SUP_CONF, ST25R3916_REG_EMD_SUP_CONF_emd_emv); - break; - - case RFAL_ERRORHANDLING_EMVCO: - /* MISRA 16.4: no empty default statement (in case RFAL_SW_EMD is defined) */ -#ifndef RFAL_SW_EMD - st25r3916ModifyRegister( - ST25R3916_REG_EMD_SUP_CONF, - (ST25R3916_REG_EMD_SUP_CONF_emd_emv | ST25R3916_REG_EMD_SUP_CONF_emd_thld_mask), - (ST25R3916_REG_EMD_SUP_CONF_emd_emv_on | RFAL_EMVCO_RX_MAXLEN)); -#endif /* RFAL_SW_EMD */ - break; - default: - /* MISRA 16.4: no empty default statement (a comment being enough) */ - break; - } - - gRFAL.conf.eHandling = eHandling; -} - -/*******************************************************************************/ -rfalEHandling rfalGetErrorHandling(void) { - return gRFAL.conf.eHandling; -} - -/*******************************************************************************/ -void rfalSetFDTPoll(uint32_t FDTPoll) { - gRFAL.timings.FDTPoll = MIN(FDTPoll, RFAL_ST25R3916_GPT_MAX_1FC); -} - -/*******************************************************************************/ -uint32_t rfalGetFDTPoll(void) { - return gRFAL.timings.FDTPoll; -} - -/*******************************************************************************/ -void rfalSetFDTListen(uint32_t FDTListen) { - gRFAL.timings.FDTListen = MIN(FDTListen, RFAL_ST25R3916_MRT_MAX_1FC); -} - -/*******************************************************************************/ -uint32_t rfalGetFDTListen(void) { - return gRFAL.timings.FDTListen; -} - -/*******************************************************************************/ -void rfalSetGT(uint32_t GT) { - gRFAL.timings.GT = MIN(GT, RFAL_ST25R3916_GT_MAX_1FC); -} - -/*******************************************************************************/ -uint32_t rfalGetGT(void) { - return gRFAL.timings.GT; -} - -/*******************************************************************************/ -bool rfalIsGTExpired(void) { - if(gRFAL.tmr.GT != RFAL_TIMING_NONE) { - if(!rfalTimerisExpired(gRFAL.tmr.GT)) { - return false; - } - } - return true; -} - -/*******************************************************************************/ -ReturnCode rfalFieldOnAndStartGT(void) { - ReturnCode ret; - - /* Check if RFAL has been initialized (Oscillator should be running) and also - * if a direct register access has been performed and left the Oscillator Off */ - if(!st25r3916IsOscOn() || (gRFAL.state < RFAL_STATE_INIT)) { - return ERR_WRONG_STATE; - } - - ret = ERR_NONE; - - /* Set Analog configurations for Field On event */ - rfalSetAnalogConfig((RFAL_ANALOG_CONFIG_TECH_CHIP | RFAL_ANALOG_CONFIG_CHIP_FIELD_ON)); - - /*******************************************************************************/ - /* Perform collision avoidance and turn field On if not already On */ - if(!st25r3916IsTxEnabled() || !gRFAL.field) { - /* Set TARFG: 0 (75us+0ms=75us), GT is fulfilled using a SW timer */ - st25r3916WriteRegister(ST25R3916_REG_FIELD_ON_GT, 0U); - - /* Use Thresholds set by AnalogConfig */ - ret = st25r3916PerformCollisionAvoidance( - ST25R3916_CMD_INITIAL_RF_COLLISION, - ST25R3916_THRESHOLD_DO_NOT_SET, - ST25R3916_THRESHOLD_DO_NOT_SET, - gRFAL.timings.nTRFW); - - /* n * TRFW timing shall vary Activity 2.1 3.3.1.1 */ - gRFAL.timings.nTRFW = rfalGennTRFW(gRFAL.timings.nTRFW); - - gRFAL.field = st25r3916IsTxEnabled(); //(ret == ERR_NONE); - - /* Only turn on Receiver and Transmitter if field was successfully turned On */ - if(gRFAL.field) { - st25r3916TxRxOn(); /* Enable Tx and Rx (Tx is already On)*/ - } - } - - /*******************************************************************************/ - /* Start GT timer in case the GT value is set */ - if((gRFAL.timings.GT != RFAL_TIMING_NONE)) { - /* Ensure that a SW timer doesn't have a lower value then the minimum */ - rfalTimerStart( - gRFAL.tmr.GT, rfalConv1fcToMs(MAX((gRFAL.timings.GT), RFAL_ST25R3916_GT_MIN_1FC))); - } - - return ret; -} - -/*******************************************************************************/ -ReturnCode rfalFieldOff(void) { - /* Check whether a TxRx is not yet finished */ - if(gRFAL.TxRx.state != RFAL_TXRX_STATE_IDLE) { - rfalCleanupTransceive(); - } - - /* Disable Tx and Rx */ - st25r3916TxRxOff(); - - /* Set Analog configurations for Field Off event */ - rfalSetAnalogConfig((RFAL_ANALOG_CONFIG_TECH_CHIP | RFAL_ANALOG_CONFIG_CHIP_FIELD_OFF)); - gRFAL.field = false; - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalStartTransceive(const rfalTransceiveContext* ctx) { - uint32_t FxTAdj; /* FWT or FDT adjustment calculation */ - - /* Check for valid parameters */ - if(ctx == NULL) { - return ERR_PARAM; - } - - /* Ensure that RFAL is already Initialized and the mode has been set */ - if((gRFAL.state >= RFAL_STATE_MODE_SET) /*&& (gRFAL.TxRx.state == RFAL_TXRX_STATE_INIT )*/) { - /*******************************************************************************/ - /* Check whether the field is already On, otherwise no TXE will be received */ - if(!st25r3916IsTxEnabled() && - (!rfalIsModePassiveListen(gRFAL.mode) && (ctx->txBuf != NULL))) { - return ERR_WRONG_STATE; - } - - gRFAL.TxRx.ctx = *ctx; - - /*******************************************************************************/ - if(gRFAL.timings.FDTListen != RFAL_TIMING_NONE) { - /* Calculate MRT adjustment accordingly to the current mode */ - FxTAdj = RFAL_FDT_LISTEN_MRT_ADJUSTMENT; - if(gRFAL.mode == RFAL_MODE_POLL_NFCA) { - FxTAdj += (uint32_t)RFAL_FDT_LISTEN_A_ADJUSTMENT; - } - if(gRFAL.mode == RFAL_MODE_POLL_NFCA_T1T) { - FxTAdj += (uint32_t)RFAL_FDT_LISTEN_A_ADJUSTMENT; - } - if(gRFAL.mode == RFAL_MODE_POLL_NFCB) { - FxTAdj += (uint32_t)RFAL_FDT_LISTEN_B_ADJUSTMENT; - } - if(gRFAL.mode == RFAL_MODE_POLL_NFCV) { - FxTAdj += (uint32_t)RFAL_FDT_LISTEN_V_ADJUSTMENT; - } - - /* Ensure that MRT is using 64/fc steps */ - st25r3916ClrRegisterBits( - ST25R3916_REG_TIMER_EMV_CONTROL, ST25R3916_REG_TIMER_EMV_CONTROL_mrt_step); - - /* If Correlator is being used further adjustment is required for NFCB */ - if((st25r3916CheckReg(ST25R3916_REG_AUX, ST25R3916_REG_AUX_dis_corr, 0x00U)) && - (gRFAL.mode == RFAL_MODE_POLL_NFCB)) { - FxTAdj += (uint32_t) - RFAL_FDT_LISTEN_B_ADJT_CORR; /* Reduce FDT(Listen) */ - st25r3916SetRegisterBits( - ST25R3916_REG_CORR_CONF1, - ST25R3916_REG_CORR_CONF1_corr_s3); /* Ensure BPSK start to 33 pilot pulses */ - st25r3916ChangeRegisterBits( - ST25R3916_REG_SUBC_START_TIME, - ST25R3916_REG_SUBC_START_TIME_sst_mask, - RFAL_FDT_LISTEN_B_ADJT_CORR_SST); /* Set sst */ - } - - /* Set Minimum FDT(Listen) in which PICC is not allowed to send a response */ - st25r3916WriteRegister( - ST25R3916_REG_MASK_RX_TIMER, - (uint8_t)rfalConv1fcTo64fc( - (FxTAdj > gRFAL.timings.FDTListen) ? RFAL_ST25R3916_MRT_MIN_1FC : - (gRFAL.timings.FDTListen - FxTAdj))); - } - - /*******************************************************************************/ - /* FDT Poll will be loaded in rfalPrepareTransceive() once the previous was expired */ - - /*******************************************************************************/ - if((gRFAL.TxRx.ctx.fwt != RFAL_FWT_NONE) && (gRFAL.TxRx.ctx.fwt != 0U)) { - /* Ensure proper timing configuration */ - if(gRFAL.timings.FDTListen >= gRFAL.TxRx.ctx.fwt) { - return ERR_PARAM; - } - - FxTAdj = RFAL_FWT_ADJUSTMENT; - if(gRFAL.mode == RFAL_MODE_POLL_NFCA) { - FxTAdj += (uint32_t)RFAL_FWT_A_ADJUSTMENT; - } - if(gRFAL.mode == RFAL_MODE_POLL_NFCA_T1T) { - FxTAdj += (uint32_t)RFAL_FWT_A_ADJUSTMENT; - } - if(gRFAL.mode == RFAL_MODE_POLL_NFCB) { - FxTAdj += (uint32_t)RFAL_FWT_B_ADJUSTMENT; - } - if((gRFAL.mode == RFAL_MODE_POLL_NFCF) || (gRFAL.mode == RFAL_MODE_POLL_ACTIVE_P2P)) { - FxTAdj += - (uint32_t)((gRFAL.txBR == RFAL_BR_212) ? RFAL_FWT_F_212_ADJUSTMENT : RFAL_FWT_F_424_ADJUSTMENT); - } - - /* Ensure that the given FWT doesn't exceed NRT maximum */ - gRFAL.TxRx.ctx.fwt = MIN((gRFAL.TxRx.ctx.fwt + FxTAdj), RFAL_ST25R3916_NRT_MAX_1FC); - - /* Set FWT in the NRT */ - st25r3916SetNoResponseTime(rfalConv1fcTo64fc(gRFAL.TxRx.ctx.fwt)); - } else { - /* Disable NRT, no NRE will be triggered, therefore wait endlessly for Rx */ - st25r3916SetNoResponseTime(RFAL_ST25R3916_NRT_DISABLED); - } - - gRFAL.state = RFAL_STATE_TXRX; - gRFAL.TxRx.state = RFAL_TXRX_STATE_TX_IDLE; - gRFAL.TxRx.status = ERR_BUSY; - -#if RFAL_FEATURE_NFCV - /*******************************************************************************/ - if((RFAL_MODE_POLL_NFCV == gRFAL.mode) || - (RFAL_MODE_POLL_PICOPASS == - gRFAL.mode)) { /* Exchange receive buffer with internal buffer */ - gRFAL.nfcvData.origCtx = gRFAL.TxRx.ctx; - - gRFAL.TxRx.ctx.rxBuf = - ((gRFAL.nfcvData.origCtx.rxBuf != NULL) ? gRFAL.nfcvData.codingBuffer : NULL); - gRFAL.TxRx.ctx.rxBufLen = - (uint16_t)rfalConvBytesToBits(sizeof(gRFAL.nfcvData.codingBuffer)); - gRFAL.TxRx.ctx.flags = - (uint32_t)RFAL_TXRX_FLAGS_CRC_TX_MANUAL | (uint32_t)RFAL_TXRX_FLAGS_CRC_RX_KEEP | - (uint32_t)RFAL_TXRX_FLAGS_NFCIP1_OFF | - (uint32_t)(gRFAL.nfcvData.origCtx.flags & (uint32_t)RFAL_TXRX_FLAGS_AGC_OFF) | - (uint32_t)RFAL_TXRX_FLAGS_PAR_RX_KEEP | (uint32_t)RFAL_TXRX_FLAGS_PAR_TX_NONE; - - /* In NFCV a TxRx with a valid txBuf and txBufSize==0 indicates to send an EOF */ - /* Skip logic below that would go directly into receive */ - if(gRFAL.TxRx.ctx.txBuf != NULL) { - return ERR_NONE; - } - } -#endif /* RFAL_FEATURE_NFCV */ - - /*******************************************************************************/ - /* Check if the Transceive start performing Tx or goes directly to Rx */ - if((gRFAL.TxRx.ctx.txBuf == NULL) || (gRFAL.TxRx.ctx.txBufLen == 0U)) { - /* Clear FIFO, Clear and Enable the Interrupts */ - rfalPrepareTransceive(); - - /* In AP2P check the field status */ - if(rfalIsModeActiveComm(gRFAL.mode)) { - /* Disable our field upon a Rx reEnable, and start PPON2 manually */ - st25r3916TxOff(); - st25r3916ExecuteCommand(ST25R3916_CMD_START_PPON2_TIMER); - } - - /* No Tx done, enable the Receiver */ - st25r3916ExecuteCommand(ST25R3916_CMD_UNMASK_RECEIVE_DATA); - - /* Start NRT manually, if FWT = 0 (wait endlessly for Rx) chip will ignore anyhow */ - st25r3916ExecuteCommand(ST25R3916_CMD_START_NO_RESPONSE_TIMER); - - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_IDLE; - } - - return ERR_NONE; - } - - return ERR_WRONG_STATE; -} - -/*******************************************************************************/ -bool rfalIsTransceiveInTx(void) { - return ( - (gRFAL.TxRx.state >= RFAL_TXRX_STATE_TX_IDLE) && - (gRFAL.TxRx.state < RFAL_TXRX_STATE_RX_IDLE)); -} - -/*******************************************************************************/ -bool rfalIsTransceiveInRx(void) { - return (gRFAL.TxRx.state >= RFAL_TXRX_STATE_RX_IDLE); -} - -/*******************************************************************************/ -ReturnCode rfalTransceiveBlockingTx( - uint8_t* txBuf, - uint16_t txBufLen, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* actLen, - uint32_t flags, - uint32_t fwt) { - ReturnCode ret; - rfalTransceiveContext ctx; - - rfalCreateByteFlagsTxRxContext(ctx, txBuf, txBufLen, rxBuf, rxBufLen, actLen, flags, fwt); - EXIT_ON_ERR(ret, rfalStartTransceive(&ctx)); - - return rfalTransceiveRunBlockingTx(); -} - -ReturnCode rfalTransceiveBitsBlockingTx( - uint8_t* txBuf, - uint16_t txBufLen, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* actLen, - uint32_t flags, - uint32_t fwt) { - ReturnCode ret; - rfalTransceiveContext ctx = { - .rxBuf = rxBuf, - .rxBufLen = rxBufLen, - .rxRcvdLen = actLen, - .txBuf = txBuf, - .txBufLen = txBufLen, - .flags = flags, - .fwt = fwt, - }; - - EXIT_ON_ERR(ret, rfalStartTransceive(&ctx)); - - return rfalTransceiveRunBlockingTx(); -} - -/*******************************************************************************/ -static ReturnCode rfalTransceiveRunBlockingTx(void) { - ReturnCode ret; - - do { - rfalWorker(); - ret = rfalGetTransceiveStatus(); - } while(rfalIsTransceiveInTx() && (ret == ERR_BUSY)); - - if(rfalIsTransceiveInRx()) { - return ERR_NONE; - } - - return ret; -} - -/*******************************************************************************/ -ReturnCode rfalTransceiveBlockingRx(void) { - ReturnCode ret; - - do { - rfalWorker(); - ret = rfalGetTransceiveStatus(); - } while(rfalIsTransceiveInRx() && (ret == ERR_BUSY)); - - return ret; -} - -/*******************************************************************************/ -ReturnCode rfalTransceiveBlockingTxRx( - uint8_t* txBuf, - uint16_t txBufLen, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* actLen, - uint32_t flags, - uint32_t fwt) { - ReturnCode ret; - - EXIT_ON_ERR( - ret, rfalTransceiveBlockingTx(txBuf, txBufLen, rxBuf, rxBufLen, actLen, flags, fwt)); - ret = rfalTransceiveBlockingRx(); - - /* Convert received bits to bytes */ - if(actLen != NULL) { - *actLen = rfalConvBitsToBytes(*actLen); - } - - return ret; -} - -ReturnCode rfalTransceiveBitsBlockingTxRx( - uint8_t* txBuf, - uint16_t txBufLen, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* actLen, - uint32_t flags, - uint32_t fwt) { - ReturnCode ret; - - EXIT_ON_ERR( - ret, rfalTransceiveBitsBlockingTx(txBuf, txBufLen, rxBuf, rxBufLen, actLen, flags, fwt)); - ret = rfalTransceiveBlockingRx(); - - return ret; -} - -/*******************************************************************************/ -static ReturnCode rfalRunTransceiveWorker(void) { - if(gRFAL.state == RFAL_STATE_TXRX) { - /*******************************************************************************/ - /* Check Transceive Sanity Timer has expired */ - if(gRFAL.tmr.txRx != RFAL_TIMING_NONE) { - if(rfalTimerisExpired(gRFAL.tmr.txRx)) { - /* If sanity timer has expired abort ongoing transceive and signal error */ - gRFAL.TxRx.status = ERR_IO; - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_FAIL; - } - } - - /*******************************************************************************/ - /* Run Tx or Rx state machines */ - if(rfalIsTransceiveInTx()) { - rfalTransceiveTx(); - return rfalGetTransceiveStatus(); - } - if(rfalIsTransceiveInRx()) { - rfalTransceiveRx(); - return rfalGetTransceiveStatus(); - } - } - return ERR_WRONG_STATE; -} - -/*******************************************************************************/ -rfalTransceiveState rfalGetTransceiveState(void) { - return gRFAL.TxRx.state; -} - -/*******************************************************************************/ -ReturnCode rfalGetTransceiveStatus(void) { - return ((gRFAL.TxRx.state == RFAL_TXRX_STATE_IDLE) ? gRFAL.TxRx.status : ERR_BUSY); -} - -/*******************************************************************************/ -ReturnCode rfalGetTransceiveRSSI(uint16_t* rssi) { - uint16_t amRSSI; - uint16_t pmRSSI; - bool isSumMode; - - if(rssi == NULL) { - return ERR_PARAM; - } - - st25r3916GetRSSI(&amRSSI, &pmRSSI); - - /* Check if Correlator Summation mode is being used */ - isSumMode = - (st25r3916CheckReg( - ST25R3916_REG_CORR_CONF1, - ST25R3916_REG_CORR_CONF1_corr_s4, - ST25R3916_REG_CORR_CONF1_corr_s4) ? - st25r3916CheckReg(ST25R3916_REG_AUX, ST25R3916_REG_AUX_dis_corr, 0x00) : - false); - if(isSumMode) { - /*******************************************************************************/ - /* Using SQRT from math.h and float. If due to compiler, resources or performance - * issue this cannot be used, other approaches can be foreseen with less accuracy: - * Use a simpler sqrt algorithm - * *rssi = MAX( amRSSI, pmRSSI ); - * *rssi = ( (amRSSI + pmRSSI) / 2); - */ - *rssi = (uint16_t)sqrt( - ((double)amRSSI * (double)amRSSI) + - ((double)pmRSSI * - (double) - pmRSSI)); /* PRQA S 5209 # MISRA 4.9 - External function (sqrt()) requires double */ - } else { - /* Check which channel was used */ - *rssi = - (st25r3916CheckReg( - ST25R3916_REG_AUX_DISPLAY, - ST25R3916_REG_AUX_DISPLAY_a_cha, - ST25R3916_REG_AUX_DISPLAY_a_cha) ? - pmRSSI : - amRSSI); - } - return ERR_NONE; -} - -/*******************************************************************************/ -void rfalWorker(void) { - platformProtectWorker(); /* Protect RFAL Worker/Task/Process */ - - switch(gRFAL.state) { - case RFAL_STATE_TXRX: - rfalRunTransceiveWorker(); - break; - -#if RFAL_FEATURE_LISTEN_MODE - case RFAL_STATE_LM: - rfalRunListenModeWorker(); - break; -#endif /* RFAL_FEATURE_LISTEN_MODE */ - -#if RFAL_FEATURE_WAKEUP_MODE - case RFAL_STATE_WUM: - rfalRunWakeUpModeWorker(); - break; -#endif /* RFAL_FEATURE_WAKEUP_MODE */ - - /* Nothing to be done */ - default: - /* MISRA 16.4: no empty default statement (a comment being enough) */ - break; - } - - platformUnprotectWorker(); /* Unprotect RFAL Worker/Task/Process */ -} - -/*******************************************************************************/ -static void rfalErrorHandling(void) { - uint16_t fifoBytesToRead; - - fifoBytesToRead = rfalFIFOStatusGetNumBytes(); - -#ifdef RFAL_SW_EMD - /*******************************************************************************/ - /* EMVCo */ - /*******************************************************************************/ - if(gRFAL.conf.eHandling == RFAL_ERRORHANDLING_EMVCO) { - bool rxHasIncParError; - - /*******************************************************************************/ - /* EMD Handling - NFC Forum Digital 1.1 4.1.1.1 ; EMVCo v2.5 4.9.2 */ - /* ReEnable the receiver on frames with a length < 4 bytes, upon: */ - /* - Collision or Framing error detected */ - /* - Residual bits are detected (hard framing error) */ - /* - Parity error */ - /* - CRC error */ - /*******************************************************************************/ - - /* Check if reception has incomplete bytes or parity error */ - rxHasIncParError = - (rfalFIFOStatusIsIncompleteByte() ? true : - rfalFIFOStatusIsMissingPar()); /* MISRA 13.5 */ - - /* In case there are residual bits decrement FIFO bytes */ - /* Ensure FIFO contains some byte as the FIFO might be empty upon Framing errors */ - if((fifoBytesToRead > 0U) && rxHasIncParError) { - fifoBytesToRead--; - } - - if(((gRFAL.fifo.bytesTotal + fifoBytesToRead) < RFAL_EMVCO_RX_MAXLEN) && - ((gRFAL.TxRx.status == ERR_RF_COLLISION) || (gRFAL.TxRx.status == ERR_FRAMING) || - (gRFAL.TxRx.status == ERR_PAR) || (gRFAL.TxRx.status == ERR_CRC) || - rxHasIncParError)) { - /* Ignore this reception, ReEnable receiver which also clears the FIFO */ - st25r3916ExecuteCommand(ST25R3916_CMD_UNMASK_RECEIVE_DATA); - - /* Ensure that the NRT has not expired meanwhile */ - if(st25r3916CheckReg( - ST25R3916_REG_NFCIP1_BIT_RATE, ST25R3916_REG_NFCIP1_BIT_RATE_nrt_on, 0x00)) { - if(st25r3916CheckReg( - ST25R3916_REG_AUX_DISPLAY, ST25R3916_REG_AUX_DISPLAY_rx_act, 0x00)) { - /* Abort reception */ - st25r3916ExecuteCommand(ST25R3916_CMD_MASK_RECEIVE_DATA); - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_FAIL; - return; - } - } - - rfalFIFOStatusClear(); - gRFAL.fifo.bytesTotal = 0; - gRFAL.TxRx.status = ERR_BUSY; - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_WAIT_RXS; - } - return; - } -#endif - - /*******************************************************************************/ - /* ISO14443A Mode */ - /*******************************************************************************/ - if(gRFAL.mode == RFAL_MODE_POLL_NFCA) { - /*******************************************************************************/ - /* If we received a frame with a incomplete byte we`ll raise a specific error * - * ( support for T2T 4 bit ACK / NAK, MIFARE and Kovio ) */ - /*******************************************************************************/ - if((gRFAL.TxRx.status == ERR_PAR) || (gRFAL.TxRx.status == ERR_CRC)) { - if(rfalFIFOStatusIsIncompleteByte()) { - st25r3916ReadFifo((uint8_t*)(gRFAL.TxRx.ctx.rxBuf), fifoBytesToRead); - if((gRFAL.TxRx.ctx.rxRcvdLen) != NULL) { - *gRFAL.TxRx.ctx.rxRcvdLen = rfalFIFOGetNumIncompleteBits(); - } - - gRFAL.TxRx.status = ERR_INCOMPLETE_BYTE; - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_FAIL; - } - } - } -} - -/*******************************************************************************/ -static void rfalCleanupTransceive(void) { - /*******************************************************************************/ - /* Transceive flags */ - /*******************************************************************************/ - - /* Restore default settings on NFCIP1 mode, Receiving parity + CRC bits and manual Tx Parity*/ - st25r3916ClrRegisterBits( - ST25R3916_REG_ISO14443A_NFC, - (ST25R3916_REG_ISO14443A_NFC_no_tx_par | ST25R3916_REG_ISO14443A_NFC_no_rx_par | - ST25R3916_REG_ISO14443A_NFC_nfc_f0)); - - /* Restore AGC enabled */ - st25r3916SetRegisterBits(ST25R3916_REG_RX_CONF2, ST25R3916_REG_RX_CONF2_agc_en); - - /*******************************************************************************/ - - /*******************************************************************************/ - /* Transceive timers */ - /*******************************************************************************/ - rfalTimerDestroy(gRFAL.tmr.txRx); - rfalTimerDestroy(gRFAL.tmr.RXE); - gRFAL.tmr.txRx = RFAL_TIMING_NONE; - gRFAL.tmr.RXE = RFAL_TIMING_NONE; - /*******************************************************************************/ - - /*******************************************************************************/ - /* Execute Post Transceive Callback */ - /*******************************************************************************/ - if(gRFAL.callbacks.postTxRx != NULL) { - gRFAL.callbacks.postTxRx(gRFAL.callbacks.ctx); - } - /*******************************************************************************/ -} - -/*******************************************************************************/ -static void rfalPrepareTransceive(void) { - uint32_t maskInterrupts; - uint8_t reg; - - /* If we are in RW or AP2P mode */ - if(!rfalIsModePassiveListen(gRFAL.mode)) { - /* Reset receive logic with STOP command */ - st25r3916ExecuteCommand(ST25R3916_CMD_STOP); - - /* Reset Rx Gain */ - st25r3916ExecuteCommand(ST25R3916_CMD_RESET_RXGAIN); - } else { - /* In Passive Listen Mode do not use STOP as it stops FDT timer */ - st25r3916ExecuteCommand(ST25R3916_CMD_CLEAR_FIFO); - } - - /*******************************************************************************/ - /* FDT Poll */ - /*******************************************************************************/ - if(rfalIsModePassiveComm(gRFAL.mode)) /* Passive Comms */ - { - /* In Passive communications General Purpose Timer is used to measure FDT Poll */ - if(gRFAL.timings.FDTPoll != RFAL_TIMING_NONE) { - /* Configure GPT to start at RX end */ - st25r3916SetStartGPTimer( - (uint16_t)rfalConv1fcTo8fc(MIN( - gRFAL.timings.FDTPoll, (gRFAL.timings.FDTPoll - RFAL_FDT_POLL_ADJUSTMENT))), - ST25R3916_REG_TIMER_EMV_CONTROL_gptc_erx); - } - } - - /*******************************************************************************/ - /* Execute Pre Transceive Callback */ - /*******************************************************************************/ - if(gRFAL.callbacks.preTxRx != NULL) { - gRFAL.callbacks.preTxRx(gRFAL.callbacks.ctx); - } - /*******************************************************************************/ - - maskInterrupts = - (ST25R3916_IRQ_MASK_FWL | ST25R3916_IRQ_MASK_TXE | ST25R3916_IRQ_MASK_RXS | - ST25R3916_IRQ_MASK_RXE | ST25R3916_IRQ_MASK_PAR | ST25R3916_IRQ_MASK_CRC | - ST25R3916_IRQ_MASK_ERR1 | ST25R3916_IRQ_MASK_ERR2 | ST25R3916_IRQ_MASK_NRE); - - /*******************************************************************************/ - /* Transceive flags */ - /*******************************************************************************/ - - reg = - (ST25R3916_REG_ISO14443A_NFC_no_tx_par_off | ST25R3916_REG_ISO14443A_NFC_no_rx_par_off | - ST25R3916_REG_ISO14443A_NFC_nfc_f0_off); - - /* Check if NFCIP1 mode is to be enabled */ - if((gRFAL.TxRx.ctx.flags & (uint8_t)RFAL_TXRX_FLAGS_NFCIP1_ON) != 0U) { - reg |= ST25R3916_REG_ISO14443A_NFC_nfc_f0; - } - - /* Check if Parity check is to be skipped and to keep the parity + CRC bits in FIFO */ - if((gRFAL.TxRx.ctx.flags & (uint8_t)RFAL_TXRX_FLAGS_PAR_RX_KEEP) != 0U) { - reg |= ST25R3916_REG_ISO14443A_NFC_no_rx_par; - } - - /* Check if automatic Parity bits is to be disabled */ - if((gRFAL.TxRx.ctx.flags & (uint8_t)RFAL_TXRX_FLAGS_PAR_TX_NONE) != 0U) { - reg |= ST25R3916_REG_ISO14443A_NFC_no_tx_par; - } - - /* Apply current TxRx flags on ISO14443A and NFC 106kb/s Settings Register */ - st25r3916ChangeRegisterBits( - ST25R3916_REG_ISO14443A_NFC, - (ST25R3916_REG_ISO14443A_NFC_no_tx_par | ST25R3916_REG_ISO14443A_NFC_no_rx_par | - ST25R3916_REG_ISO14443A_NFC_nfc_f0), - reg); - - /* Check if AGC is to be disabled */ - if((gRFAL.TxRx.ctx.flags & (uint8_t)RFAL_TXRX_FLAGS_AGC_OFF) != 0U) { - st25r3916ClrRegisterBits(ST25R3916_REG_RX_CONF2, ST25R3916_REG_RX_CONF2_agc_en); - } else { - st25r3916SetRegisterBits(ST25R3916_REG_RX_CONF2, ST25R3916_REG_RX_CONF2_agc_en); - } - /*******************************************************************************/ - - /*******************************************************************************/ - /* EMVCo NRT mode */ - /*******************************************************************************/ - if(gRFAL.conf.eHandling == RFAL_ERRORHANDLING_EMVCO) { - st25r3916SetRegisterBits( - ST25R3916_REG_TIMER_EMV_CONTROL, ST25R3916_REG_TIMER_EMV_CONTROL_nrt_emv); - maskInterrupts |= ST25R3916_IRQ_MASK_RX_REST; - } else { - st25r3916ClrRegisterBits( - ST25R3916_REG_TIMER_EMV_CONTROL, ST25R3916_REG_TIMER_EMV_CONTROL_nrt_emv); - } - /*******************************************************************************/ - - /* In Passive Listen mode additionally enable External Field interrupts */ - if(rfalIsModePassiveListen(gRFAL.mode)) { - maskInterrupts |= - (ST25R3916_IRQ_MASK_EOF | - ST25R3916_IRQ_MASK_WU_F); /* Enable external Field interrupts to detect Link Loss and SENF_REQ auto responses */ - } - - /* In Active comms enable also External Field interrupts and set RF Collision Avoindance */ - if(rfalIsModeActiveComm(gRFAL.mode)) { - maskInterrupts |= - (ST25R3916_IRQ_MASK_EOF | ST25R3916_IRQ_MASK_EON | ST25R3916_IRQ_MASK_PPON2 | - ST25R3916_IRQ_MASK_CAT | ST25R3916_IRQ_MASK_CAC); - - /* Set n=0 for subsequent RF Collision Avoidance */ - st25r3916ChangeRegisterBits(ST25R3916_REG_AUX, ST25R3916_REG_AUX_nfc_n_mask, 0); - } - - /*******************************************************************************/ - /* Start transceive Sanity Timer if a FWT is used */ - if((gRFAL.TxRx.ctx.fwt != RFAL_FWT_NONE) && (gRFAL.TxRx.ctx.fwt != 0U)) { - rfalTimerStart(gRFAL.tmr.txRx, rfalCalcSanityTmr(gRFAL.TxRx.ctx.fwt)); - } - /*******************************************************************************/ - - /*******************************************************************************/ - /* Clear and enable these interrupts */ - st25r3916GetInterrupt(maskInterrupts); - st25r3916EnableInterrupts(maskInterrupts); - - /* Clear FIFO status local copy */ - rfalFIFOStatusClear(); -} - -/*******************************************************************************/ -static void rfalTransceiveTx(void) { - volatile uint32_t irqs; - uint16_t tmp; - ReturnCode ret; - - /* Suppress warning in case NFC-V feature is disabled */ - ret = ERR_NONE; - NO_WARNING(ret); - - irqs = ST25R3916_IRQ_MASK_NONE; - - if(gRFAL.TxRx.state != gRFAL.TxRx.lastState) { - /* rfalLogD( "RFAL: lastSt: %d curSt: %d \r\n", gRFAL.TxRx.lastState, gRFAL.TxRx.state ); */ - gRFAL.TxRx.lastState = gRFAL.TxRx.state; - } - - switch(gRFAL.TxRx.state) { - /*******************************************************************************/ - case RFAL_TXRX_STATE_TX_IDLE: - - /* Nothing to do */ - - gRFAL.TxRx.state = RFAL_TXRX_STATE_TX_WAIT_GT; - /* fall through */ - - /*******************************************************************************/ - case RFAL_TXRX_STATE_TX_WAIT_GT: /* PRQA S 2003 # MISRA 16.3 - Intentional fall through */ - - if(!rfalIsGTExpired()) { - break; - } - - rfalTimerDestroy(gRFAL.tmr.GT); - gRFAL.tmr.GT = RFAL_TIMING_NONE; - - gRFAL.TxRx.state = RFAL_TXRX_STATE_TX_WAIT_FDT; - /* fall through */ - - /*******************************************************************************/ - case RFAL_TXRX_STATE_TX_WAIT_FDT: /* PRQA S 2003 # MISRA 16.3 - Intentional fall through */ - - /* Only in Passive communications GPT is used to measure FDT Poll */ - if(rfalIsModePassiveComm(gRFAL.mode)) { - if(st25r3916IsGPTRunning()) { - break; - } - } - - gRFAL.TxRx.state = RFAL_TXRX_STATE_TX_TRANSMIT; - /* fall through */ - - /*******************************************************************************/ - case RFAL_TXRX_STATE_TX_TRANSMIT: /* PRQA S 2003 # MISRA 16.3 - Intentional fall through */ - - /* Clear FIFO, Clear and Enable the Interrupts */ - rfalPrepareTransceive(); - - /* ST25R3916 has a fixed FIFO water level */ - gRFAL.fifo.expWL = RFAL_FIFO_OUT_WL; - -#if RFAL_FEATURE_NFCV - /*******************************************************************************/ - /* In NFC-V streaming mode, the FIFO needs to be loaded with the coded bits */ - if((RFAL_MODE_POLL_NFCV == gRFAL.mode) || (RFAL_MODE_POLL_PICOPASS == gRFAL.mode)) { -#if 0 - /* Debugging code: output the payload bits by writing into the FIFO and subsequent clearing */ - st25r3916WriteFifo(gRFAL.TxRx.ctx.txBuf, rfalConvBitsToBytes(gRFAL.TxRx.ctx.txBufLen)); - st25r3916ExecuteCommand( ST25R3916_CMD_CLEAR_FIFO ); -#endif - /* Calculate the bytes needed to be Written into FIFO (a incomplete byte will be added as 1byte) */ - gRFAL.nfcvData.nfcvOffset = 0; - ret = iso15693VCDCode( - gRFAL.TxRx.ctx.txBuf, - rfalConvBitsToBytes(gRFAL.TxRx.ctx.txBufLen), - (((gRFAL.nfcvData.origCtx.flags & (uint32_t)RFAL_TXRX_FLAGS_CRC_TX_MANUAL) != 0U) ? - false : - true), - (((gRFAL.nfcvData.origCtx.flags & (uint32_t)RFAL_TXRX_FLAGS_NFCV_FLAG_MANUAL) != - 0U) ? - false : - true), - (RFAL_MODE_POLL_PICOPASS == gRFAL.mode), - &gRFAL.fifo.bytesTotal, - &gRFAL.nfcvData.nfcvOffset, - gRFAL.nfcvData.codingBuffer, - MIN((uint16_t)ST25R3916_FIFO_DEPTH, (uint16_t)sizeof(gRFAL.nfcvData.codingBuffer)), - &gRFAL.fifo.bytesWritten); - - if((ret != ERR_NONE) && (ret != ERR_AGAIN)) { - gRFAL.TxRx.status = ret; - gRFAL.TxRx.state = RFAL_TXRX_STATE_TX_FAIL; - break; - } - /* Set the number of full bytes and bits to be transmitted */ - st25r3916SetNumTxBits((uint16_t)rfalConvBytesToBits(gRFAL.fifo.bytesTotal)); - - /* Load FIFO with coded bytes */ - st25r3916WriteFifo(gRFAL.nfcvData.codingBuffer, gRFAL.fifo.bytesWritten); - - } - /*******************************************************************************/ - else -#endif /* RFAL_FEATURE_NFCV */ - { - /* Calculate the bytes needed to be Written into FIFO (a incomplete byte will be added as 1byte) */ - gRFAL.fifo.bytesTotal = (uint16_t)rfalCalcNumBytes(gRFAL.TxRx.ctx.txBufLen); - - /* Set the number of full bytes and bits to be transmitted */ - st25r3916SetNumTxBits(gRFAL.TxRx.ctx.txBufLen); - - /* Load FIFO with total length or FIFO's maximum */ - gRFAL.fifo.bytesWritten = MIN(gRFAL.fifo.bytesTotal, ST25R3916_FIFO_DEPTH); - st25r3916WriteFifo(gRFAL.TxRx.ctx.txBuf, gRFAL.fifo.bytesWritten); - } - - /*Check if Observation Mode is enabled and set it on ST25R391x */ - rfalCheckEnableObsModeTx(); - - /*******************************************************************************/ - /* If we're in Passive Listen mode ensure that the external field is still On */ - if(rfalIsModePassiveListen(gRFAL.mode)) { - if(!rfalIsExtFieldOn()) { - gRFAL.TxRx.status = ERR_LINK_LOSS; - gRFAL.TxRx.state = RFAL_TXRX_STATE_TX_FAIL; - break; - } - } - - /*******************************************************************************/ - /* Trigger/Start transmission */ - if((gRFAL.TxRx.ctx.flags & (uint32_t)RFAL_TXRX_FLAGS_CRC_TX_MANUAL) != 0U) { - st25r3916ExecuteCommand(ST25R3916_CMD_TRANSMIT_WITHOUT_CRC); - } else { - st25r3916ExecuteCommand(ST25R3916_CMD_TRANSMIT_WITH_CRC); - } - - /* Check if a WL level is expected or TXE should come */ - gRFAL.TxRx.state = - ((gRFAL.fifo.bytesWritten < gRFAL.fifo.bytesTotal) ? RFAL_TXRX_STATE_TX_WAIT_WL : - RFAL_TXRX_STATE_TX_WAIT_TXE); - break; - - /*******************************************************************************/ - case RFAL_TXRX_STATE_TX_WAIT_WL: - - irqs = st25r3916GetInterrupt((ST25R3916_IRQ_MASK_FWL | ST25R3916_IRQ_MASK_TXE)); - if(irqs == ST25R3916_IRQ_MASK_NONE) { - break; /* No interrupt to process */ - } - - if(((irqs & ST25R3916_IRQ_MASK_FWL) != 0U) && ((irqs & ST25R3916_IRQ_MASK_TXE) == 0U)) { - gRFAL.TxRx.state = RFAL_TXRX_STATE_TX_RELOAD_FIFO; - } else { - gRFAL.TxRx.status = ERR_IO; - gRFAL.TxRx.state = RFAL_TXRX_STATE_TX_FAIL; - break; - } - - /* fall through */ - - /*******************************************************************************/ - case RFAL_TXRX_STATE_TX_RELOAD_FIFO: /* PRQA S 2003 # MISRA 16.3 - Intentional fall through */ - -#if RFAL_FEATURE_NFCV - /*******************************************************************************/ - /* In NFC-V streaming mode, the FIFO needs to be loaded with the coded bits */ - if((RFAL_MODE_POLL_NFCV == gRFAL.mode) || (RFAL_MODE_POLL_PICOPASS == gRFAL.mode)) { - uint16_t maxLen; - - /* Load FIFO with the remaining length or maximum available (which fit on the coding buffer) */ - maxLen = - (uint16_t)MIN((gRFAL.fifo.bytesTotal - gRFAL.fifo.bytesWritten), gRFAL.fifo.expWL); - maxLen = (uint16_t)MIN(maxLen, sizeof(gRFAL.nfcvData.codingBuffer)); - tmp = 0; - - /* Calculate the bytes needed to be Written into FIFO (a incomplete byte will be added as 1byte) */ - ret = iso15693VCDCode( - gRFAL.TxRx.ctx.txBuf, - rfalConvBitsToBytes(gRFAL.TxRx.ctx.txBufLen), - (((gRFAL.nfcvData.origCtx.flags & (uint32_t)RFAL_TXRX_FLAGS_CRC_TX_MANUAL) != 0U) ? - false : - true), - (((gRFAL.nfcvData.origCtx.flags & (uint32_t)RFAL_TXRX_FLAGS_NFCV_FLAG_MANUAL) != - 0U) ? - false : - true), - (RFAL_MODE_POLL_PICOPASS == gRFAL.mode), - &gRFAL.fifo.bytesTotal, - &gRFAL.nfcvData.nfcvOffset, - gRFAL.nfcvData.codingBuffer, - maxLen, - &tmp); - - if((ret != ERR_NONE) && (ret != ERR_AGAIN)) { - gRFAL.TxRx.status = ret; - gRFAL.TxRx.state = RFAL_TXRX_STATE_TX_FAIL; - break; - } - - /* Load FIFO with coded bytes */ - st25r3916WriteFifo(gRFAL.nfcvData.codingBuffer, tmp); - } - /*******************************************************************************/ - else -#endif /* RFAL_FEATURE_NFCV */ - { - /* Load FIFO with the remaining length or maximum available */ - tmp = MIN( - (gRFAL.fifo.bytesTotal - gRFAL.fifo.bytesWritten), - gRFAL.fifo.expWL); /* tmp holds the number of bytes written on this iteration */ - st25r3916WriteFifo(&gRFAL.TxRx.ctx.txBuf[gRFAL.fifo.bytesWritten], tmp); - } - - /* Update total written bytes to FIFO */ - gRFAL.fifo.bytesWritten += tmp; - - /* Check if a WL level is expected or TXE should come */ - gRFAL.TxRx.state = - ((gRFAL.fifo.bytesWritten < gRFAL.fifo.bytesTotal) ? RFAL_TXRX_STATE_TX_WAIT_WL : - RFAL_TXRX_STATE_TX_WAIT_TXE); - break; - - /*******************************************************************************/ - case RFAL_TXRX_STATE_TX_WAIT_TXE: - - irqs = st25r3916GetInterrupt((ST25R3916_IRQ_MASK_FWL | ST25R3916_IRQ_MASK_TXE)); - if(irqs == ST25R3916_IRQ_MASK_NONE) { - break; /* No interrupt to process */ - } - - if((irqs & ST25R3916_IRQ_MASK_TXE) != 0U) { - gRFAL.TxRx.state = RFAL_TXRX_STATE_TX_DONE; - } else if((irqs & ST25R3916_IRQ_MASK_FWL) != 0U) { - break; /* Ignore ST25R3916 FIFO WL if total TxLen is already on the FIFO */ - } else { - gRFAL.TxRx.status = ERR_IO; - gRFAL.TxRx.state = RFAL_TXRX_STATE_TX_FAIL; - break; - } - - /* fall through */ - - /*******************************************************************************/ - case RFAL_TXRX_STATE_TX_DONE: /* PRQA S 2003 # MISRA 16.3 - Intentional fall through */ - - /* If no rxBuf is provided do not wait/expect Rx */ - if(gRFAL.TxRx.ctx.rxBuf == NULL) { - /*Check if Observation Mode was enabled and disable it on ST25R391x */ - rfalCheckDisableObsMode(); - - /* Clean up Transceive */ - rfalCleanupTransceive(); - - gRFAL.TxRx.status = ERR_NONE; - gRFAL.TxRx.state = RFAL_TXRX_STATE_IDLE; - break; - } - - rfalCheckEnableObsModeRx(); - - /* Goto Rx */ - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_IDLE; - break; - - /*******************************************************************************/ - case RFAL_TXRX_STATE_TX_FAIL: - - /* Error should be assigned by previous state */ - if(gRFAL.TxRx.status == ERR_BUSY) { - gRFAL.TxRx.status = ERR_SYSTEM; - } - - /*Check if Observation Mode was enabled and disable it on ST25R391x */ - rfalCheckDisableObsMode(); - - /* Clean up Transceive */ - rfalCleanupTransceive(); - - gRFAL.TxRx.state = RFAL_TXRX_STATE_IDLE; - break; - - /*******************************************************************************/ - default: - gRFAL.TxRx.status = ERR_SYSTEM; - gRFAL.TxRx.state = RFAL_TXRX_STATE_TX_FAIL; - break; - } -} - -/*******************************************************************************/ -static void rfalTransceiveRx(void) { - volatile uint32_t irqs; - uint16_t tmp; - uint16_t aux; - - irqs = ST25R3916_IRQ_MASK_NONE; - - if(gRFAL.TxRx.state != gRFAL.TxRx.lastState) { - /* rfalLogD( "RFAL: lastSt: %d curSt: %d \r\n", gRFAL.TxRx.lastState, gRFAL.TxRx.state ); */ - gRFAL.TxRx.lastState = gRFAL.TxRx.state; - } - - switch(gRFAL.TxRx.state) { - /*******************************************************************************/ - case RFAL_TXRX_STATE_RX_IDLE: - - /* Clear rx counters */ - gRFAL.fifo.bytesWritten = 0; /* Total bytes written on RxBuffer */ - gRFAL.fifo.bytesTotal = 0; /* Total bytes in FIFO will now be from Rx */ - if(gRFAL.TxRx.ctx.rxRcvdLen != NULL) { - *gRFAL.TxRx.ctx.rxRcvdLen = 0; - } - - gRFAL.TxRx.state = - (rfalIsModeActiveComm(gRFAL.mode) ? RFAL_TXRX_STATE_RX_WAIT_EON : - RFAL_TXRX_STATE_RX_WAIT_RXS); - break; - - /*******************************************************************************/ - case RFAL_TXRX_STATE_RX_WAIT_RXS: - - /*******************************************************************************/ - irqs = st25r3916GetInterrupt( - (ST25R3916_IRQ_MASK_RXS | ST25R3916_IRQ_MASK_NRE | ST25R3916_IRQ_MASK_EOF)); - if(irqs == ST25R3916_IRQ_MASK_NONE) { - break; /* No interrupt to process */ - } - - /* Only raise Timeout if NRE is detected with no Rx Start (NRT EMV mode) */ - if(((irqs & ST25R3916_IRQ_MASK_NRE) != 0U) && ((irqs & ST25R3916_IRQ_MASK_RXS) == 0U)) { - gRFAL.TxRx.status = ERR_TIMEOUT; - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_FAIL; - break; - } - - /* Only raise Link Loss if EOF is detected with no Rx Start */ - if(((irqs & ST25R3916_IRQ_MASK_EOF) != 0U) && ((irqs & ST25R3916_IRQ_MASK_RXS) == 0U)) { - /* In AP2P a Field On has already occurred - treat this as timeout | mute */ - gRFAL.TxRx.status = (rfalIsModeActiveComm(gRFAL.mode) ? ERR_TIMEOUT : ERR_LINK_LOSS); - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_FAIL; - break; - } - - if((irqs & ST25R3916_IRQ_MASK_RXS) != 0U) { - /*******************************************************************************/ - /* REMARK: Silicon workaround ST25R3916 Errata #TBD */ - /* Rarely on corrupted frames I_rxs gets signaled but I_rxe is not signaled */ - /* Use a SW timer to handle an eventual missing RXE */ - rfalTimerStart(gRFAL.tmr.RXE, RFAL_NORXE_TOUT); - /*******************************************************************************/ - - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_WAIT_RXE; - } else { - gRFAL.TxRx.status = ERR_IO; - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_FAIL; - break; - } - - /* remove NRE that might appear together (NRT EMV mode), and remove RXS, but keep EOF if present for next state */ - irqs &= ~(ST25R3916_IRQ_MASK_RXS | ST25R3916_IRQ_MASK_NRE); - - /* fall through */ - - /*******************************************************************************/ - case RFAL_TXRX_STATE_RX_WAIT_RXE: /* PRQA S 2003 # MISRA 16.3 - Intentional fall through */ - - /*******************************************************************************/ - /* REMARK: Silicon workaround ST25R3916 Errata #TBD */ - /* ST25R396 may indicate RXS without RXE afterwards, this happens rarely on */ - /* corrupted frames. */ - /* SW timer is used to timeout upon a missing RXE */ - if(rfalTimerisExpired(gRFAL.tmr.RXE)) { - gRFAL.TxRx.status = ERR_FRAMING; - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_FAIL; - } - /*******************************************************************************/ - - irqs |= st25r3916GetInterrupt( - (ST25R3916_IRQ_MASK_RXE | ST25R3916_IRQ_MASK_FWL | ST25R3916_IRQ_MASK_EOF | - ST25R3916_IRQ_MASK_RX_REST | ST25R3916_IRQ_MASK_WU_F)); - if(irqs == ST25R3916_IRQ_MASK_NONE) { - break; /* No interrupt to process */ - } - - if((irqs & ST25R3916_IRQ_MASK_RX_REST) != 0U) { - /* RX_REST indicates that Receiver has been reset due to EMD, therefore a RXS + RXE should * - * follow if a good reception is followed within the valid initial timeout */ - - /* Check whether NRT has expired already, if so signal a timeout */ - if(st25r3916GetInterrupt(ST25R3916_IRQ_MASK_NRE) != 0U) { - gRFAL.TxRx.status = ERR_TIMEOUT; - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_FAIL; - break; - } - if(st25r3916CheckReg( - ST25R3916_REG_NFCIP1_BIT_RATE, - ST25R3916_REG_NFCIP1_BIT_RATE_nrt_on, - 0)) /* MISRA 13.5 */ - { - gRFAL.TxRx.status = ERR_TIMEOUT; - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_FAIL; - break; - } - - /* Discard any previous RXS */ - st25r3916GetInterrupt(ST25R3916_IRQ_MASK_RXS); - - /* Check whether a following reception has already started */ - if(st25r3916CheckReg( - ST25R3916_REG_AUX_DISPLAY, - ST25R3916_REG_AUX_DISPLAY_rx_act, - ST25R3916_REG_AUX_DISPLAY_rx_act)) { - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_WAIT_RXE; - break; - } - - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_WAIT_RXS; - break; - } - - if(((irqs & ST25R3916_IRQ_MASK_FWL) != 0U) && ((irqs & ST25R3916_IRQ_MASK_RXE) == 0U)) { - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_READ_FIFO; - break; - } - - /* Automatic responses allowed during TxRx only for the SENSF_REQ */ - if((irqs & ST25R3916_IRQ_MASK_WU_F) != 0U) { - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_WAIT_RXS; - break; - } - - /* After RXE retrieve and check for any error irqs */ - irqs |= st25r3916GetInterrupt( - (ST25R3916_IRQ_MASK_CRC | ST25R3916_IRQ_MASK_PAR | ST25R3916_IRQ_MASK_ERR1 | - ST25R3916_IRQ_MASK_ERR2 | ST25R3916_IRQ_MASK_COL)); - - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_ERR_CHECK; - /* fall through */ - - /*******************************************************************************/ - case RFAL_TXRX_STATE_RX_ERR_CHECK: /* PRQA S 2003 # MISRA 16.3 - Intentional fall through */ - - if((irqs & ST25R3916_IRQ_MASK_ERR1) != 0U) { - gRFAL.TxRx.status = ERR_FRAMING; - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_READ_DATA; - - /* Check if there's a specific error handling for this */ - rfalErrorHandling(); - break; - } - /* Discard Soft Framing errors in AP2P and CE */ - else if(rfalIsModePassivePoll(gRFAL.mode) && ((irqs & ST25R3916_IRQ_MASK_ERR2) != 0U)) { - gRFAL.TxRx.status = ERR_FRAMING; - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_READ_DATA; - - /* Check if there's a specific error handling for this */ - rfalErrorHandling(); - break; - } else if((irqs & ST25R3916_IRQ_MASK_PAR) != 0U) { - gRFAL.TxRx.status = ERR_PAR; - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_READ_DATA; - - /* Check if there's a specific error handling for this */ - rfalErrorHandling(); - break; - } else if((irqs & ST25R3916_IRQ_MASK_CRC) != 0U) { - gRFAL.TxRx.status = ERR_CRC; - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_READ_DATA; - - /* Check if there's a specific error handling for this */ - rfalErrorHandling(); - break; - } else if((irqs & ST25R3916_IRQ_MASK_COL) != 0U) { - gRFAL.TxRx.status = ERR_RF_COLLISION; - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_READ_DATA; - - /* Check if there's a specific error handling for this */ - rfalErrorHandling(); - break; - } else if(rfalIsModePassiveListen(gRFAL.mode) && ((irqs & ST25R3916_IRQ_MASK_EOF) != 0U)) { - gRFAL.TxRx.status = ERR_LINK_LOSS; - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_FAIL; - break; - } else if((irqs & ST25R3916_IRQ_MASK_RXE) != 0U) { - /* Reception ended without any error indication, * - * check FIFO status for malformed or incomplete frames */ - - /* Check if the reception ends with an incomplete byte (residual bits) */ - if(rfalFIFOStatusIsIncompleteByte()) { - gRFAL.TxRx.status = ERR_INCOMPLETE_BYTE; - } - /* Check if the reception ends missing parity bit */ - else if(rfalFIFOStatusIsMissingPar()) { - gRFAL.TxRx.status = ERR_FRAMING; - } else { - /* MISRA 15.7 - Empty else */ - } - - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_READ_DATA; - } else { - gRFAL.TxRx.status = ERR_IO; - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_FAIL; - break; - } - - /* fall through */ - - /*******************************************************************************/ - case RFAL_TXRX_STATE_RX_READ_DATA: /* PRQA S 2003 # MISRA 16.3 - Intentional fall through */ - - tmp = rfalFIFOStatusGetNumBytes(); - - /*******************************************************************************/ - /* Check if CRC should not be placed in rxBuf */ - if(((gRFAL.TxRx.ctx.flags & (uint32_t)RFAL_TXRX_FLAGS_CRC_RX_KEEP) == 0U)) { - /* if received frame was bigger than CRC */ - if((uint16_t)(gRFAL.fifo.bytesTotal + tmp) > 0U) { - /* By default CRC will not be placed into the rxBuffer */ - if((tmp > RFAL_CRC_LEN)) { - tmp -= RFAL_CRC_LEN; - } - /* If the CRC was already placed into rxBuffer (due to WL interrupt where CRC was already in FIFO Read) - * cannot remove it from rxBuf. Can only remove it from rxBufLen not indicate the presence of CRC */ - else if(gRFAL.fifo.bytesTotal > RFAL_CRC_LEN) { - gRFAL.fifo.bytesTotal -= RFAL_CRC_LEN; - } else { - /* MISRA 15.7 - Empty else */ - } - } - } - - gRFAL.fifo.bytesTotal += tmp; /* add to total bytes counter */ - - /*******************************************************************************/ - /* Check if remaining bytes fit on the rxBuf available */ - if(gRFAL.fifo.bytesTotal > rfalConvBitsToBytes(gRFAL.TxRx.ctx.rxBufLen)) { - tmp = - (uint16_t)(rfalConvBitsToBytes(gRFAL.TxRx.ctx.rxBufLen) - gRFAL.fifo.bytesWritten); - - /* Transmission errors have precedence over buffer error */ - if(gRFAL.TxRx.status == ERR_BUSY) { - gRFAL.TxRx.status = ERR_NOMEM; - } - } - - /*******************************************************************************/ - /* Retrieve remaining bytes from FIFO to rxBuf, and assign total length rcvd */ - st25r3916ReadFifo(&gRFAL.TxRx.ctx.rxBuf[gRFAL.fifo.bytesWritten], tmp); - if(gRFAL.TxRx.ctx.rxRcvdLen != NULL) { - (*gRFAL.TxRx.ctx.rxRcvdLen) = (uint16_t)rfalConvBytesToBits(gRFAL.fifo.bytesTotal); - if(rfalFIFOStatusIsIncompleteByte()) { - (*gRFAL.TxRx.ctx.rxRcvdLen) -= - (RFAL_BITS_IN_BYTE - rfalFIFOGetNumIncompleteBits()); - } - } - -#if RFAL_FEATURE_NFCV - /*******************************************************************************/ - /* Decode sub bit stream into payload bits for NFCV, if no error found so far */ - if(((RFAL_MODE_POLL_NFCV == gRFAL.mode) || (RFAL_MODE_POLL_PICOPASS == gRFAL.mode)) && - (gRFAL.TxRx.status == ERR_BUSY)) { - ReturnCode ret; - uint16_t offset = 0; /* REMARK offset not currently used */ - - ret = iso15693VICCDecode( - gRFAL.TxRx.ctx.rxBuf, - gRFAL.fifo.bytesTotal, - gRFAL.nfcvData.origCtx.rxBuf, - rfalConvBitsToBytes(gRFAL.nfcvData.origCtx.rxBufLen), - &offset, - gRFAL.nfcvData.origCtx.rxRcvdLen, - gRFAL.nfcvData.ignoreBits, - (RFAL_MODE_POLL_PICOPASS == gRFAL.mode)); - - if(((ERR_NONE == ret) || (ERR_CRC == ret)) && - (((uint32_t)RFAL_TXRX_FLAGS_CRC_RX_KEEP & gRFAL.nfcvData.origCtx.flags) == 0U) && - ((*gRFAL.nfcvData.origCtx.rxRcvdLen % RFAL_BITS_IN_BYTE) == 0U) && - (*gRFAL.nfcvData.origCtx.rxRcvdLen >= rfalConvBytesToBits(RFAL_CRC_LEN))) { - *gRFAL.nfcvData.origCtx.rxRcvdLen -= - (uint16_t)rfalConvBytesToBits(RFAL_CRC_LEN); /* Remove CRC */ - } -#if 0 - /* Debugging code: output the payload bits by writing into the FIFO and subsequent clearing */ - st25r3916WriteFifo(gRFAL.nfcvData.origCtx.rxBuf, rfalConvBitsToBytes( *gRFAL.nfcvData.origCtx.rxRcvdLen)); - st25r3916ExecuteCommand( ST25R3916_CMD_CLEAR_FIFO ); -#endif - - /* Restore original ctx */ - gRFAL.TxRx.ctx = gRFAL.nfcvData.origCtx; - gRFAL.TxRx.status = ((ret != ERR_NONE) ? ret : ERR_BUSY); - } -#endif /* RFAL_FEATURE_NFCV */ - - /*******************************************************************************/ - /* If an error as been marked/detected don't fall into to RX_DONE */ - if(gRFAL.TxRx.status != ERR_BUSY) { - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_FAIL; - break; - } - - if(rfalIsModeActiveComm(gRFAL.mode)) { - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_WAIT_EOF; - break; - } - - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_DONE; - /* fall through */ - - /*******************************************************************************/ - case RFAL_TXRX_STATE_RX_DONE: /* PRQA S 2003 # MISRA 16.3 - Intentional fall through */ - - /*Check if Observation Mode was enabled and disable it on ST25R391x */ - rfalCheckDisableObsMode(); - - /* Clean up Transceive */ - rfalCleanupTransceive(); - - gRFAL.TxRx.status = ERR_NONE; - gRFAL.TxRx.state = RFAL_TXRX_STATE_IDLE; - break; - - /*******************************************************************************/ - case RFAL_TXRX_STATE_RX_READ_FIFO: - - /*******************************************************************************/ - /* REMARK: Silicon workaround ST25R3916 Errata #TBD */ - /* Rarely on corrupted frames I_rxs gets signaled but I_rxe is not signaled */ - /* Use a SW timer to handle an eventual missing RXE */ - rfalTimerStart(gRFAL.tmr.RXE, RFAL_NORXE_TOUT); - /*******************************************************************************/ - - tmp = rfalFIFOStatusGetNumBytes(); - gRFAL.fifo.bytesTotal += tmp; - - /*******************************************************************************/ - /* Calculate the amount of bytes that still fits in rxBuf */ - aux = - ((gRFAL.fifo.bytesTotal > rfalConvBitsToBytes(gRFAL.TxRx.ctx.rxBufLen)) ? - (rfalConvBitsToBytes(gRFAL.TxRx.ctx.rxBufLen) - gRFAL.fifo.bytesWritten) : - tmp); - - /*******************************************************************************/ - /* Retrieve incoming bytes from FIFO to rxBuf, and store already read amount */ - st25r3916ReadFifo(&gRFAL.TxRx.ctx.rxBuf[gRFAL.fifo.bytesWritten], aux); - gRFAL.fifo.bytesWritten += aux; - - /*******************************************************************************/ - /* If the bytes already read were not the full FIFO WL, dump the remaining * - * FIFO so that ST25R391x can continue with reception */ - if(aux < tmp) { - st25r3916ReadFifo(NULL, (tmp - aux)); - } - - rfalFIFOStatusClear(); - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_WAIT_RXE; - break; - - /*******************************************************************************/ - case RFAL_TXRX_STATE_RX_FAIL: - - /*Check if Observation Mode was enabled and disable it on ST25R391x */ - rfalCheckDisableObsMode(); - - /* Clean up Transceive */ - rfalCleanupTransceive(); - - /* Error should be assigned by previous state */ - if(gRFAL.TxRx.status == ERR_BUSY) { - gRFAL.TxRx.status = ERR_SYSTEM; - } - - /*rfalLogD( "RFAL: curSt: %d Error: %d \r\n", gRFAL.TxRx.state, gRFAL.TxRx.status );*/ - gRFAL.TxRx.state = RFAL_TXRX_STATE_IDLE; - break; - - /*******************************************************************************/ - case RFAL_TXRX_STATE_RX_WAIT_EON: - - irqs = st25r3916GetInterrupt( - (ST25R3916_IRQ_MASK_EON | ST25R3916_IRQ_MASK_NRE | ST25R3916_IRQ_MASK_PPON2)); - if(irqs == ST25R3916_IRQ_MASK_NONE) { - break; /* No interrupt to process */ - } - - if((irqs & ST25R3916_IRQ_MASK_EON) != 0U) { - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_WAIT_RXS; - } - - if((irqs & ST25R3916_IRQ_MASK_NRE) != 0U) { - gRFAL.TxRx.status = ERR_TIMEOUT; - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_FAIL; - } - if((irqs & ST25R3916_IRQ_MASK_PPON2) != 0U) { - gRFAL.TxRx.status = ERR_LINK_LOSS; - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_FAIL; - } - break; - - /*******************************************************************************/ - case RFAL_TXRX_STATE_RX_WAIT_EOF: - - irqs = st25r3916GetInterrupt((ST25R3916_IRQ_MASK_CAT | ST25R3916_IRQ_MASK_CAC)); - if(irqs == ST25R3916_IRQ_MASK_NONE) { - break; /* No interrupt to process */ - } - - if((irqs & ST25R3916_IRQ_MASK_CAT) != 0U) { - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_DONE; - } else if((irqs & ST25R3916_IRQ_MASK_CAC) != 0U) { - gRFAL.TxRx.status = ERR_RF_COLLISION; - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_FAIL; - } else { - gRFAL.TxRx.status = ERR_IO; - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_FAIL; - } - break; - - /*******************************************************************************/ - default: - gRFAL.TxRx.status = ERR_SYSTEM; - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_FAIL; - break; - } -} - -/*******************************************************************************/ -static void rfalFIFOStatusUpdate(void) { - if(gRFAL.fifo.status[RFAL_FIFO_STATUS_REG2] == RFAL_FIFO_STATUS_INVALID) { - st25r3916ReadMultipleRegisters( - ST25R3916_REG_FIFO_STATUS1, gRFAL.fifo.status, ST25R3916_FIFO_STATUS_LEN); - } -} - -/*******************************************************************************/ -static void rfalFIFOStatusClear(void) { - gRFAL.fifo.status[RFAL_FIFO_STATUS_REG2] = RFAL_FIFO_STATUS_INVALID; -} - -/*******************************************************************************/ -static uint16_t rfalFIFOStatusGetNumBytes(void) { - uint16_t result; - - rfalFIFOStatusUpdate(); - - result = - ((((uint16_t)gRFAL.fifo.status[RFAL_FIFO_STATUS_REG2] & - ST25R3916_REG_FIFO_STATUS2_fifo_b_mask) >> - ST25R3916_REG_FIFO_STATUS2_fifo_b_shift) - << RFAL_BITS_IN_BYTE); - result |= (((uint16_t)gRFAL.fifo.status[RFAL_FIFO_STATUS_REG1]) & 0x00FFU); - return result; -} - -/*******************************************************************************/ -static bool rfalFIFOStatusIsIncompleteByte(void) { - rfalFIFOStatusUpdate(); - return ( - (gRFAL.fifo.status[RFAL_FIFO_STATUS_REG2] & ST25R3916_REG_FIFO_STATUS2_fifo_lb_mask) != - 0U); -} - -/*******************************************************************************/ -static bool rfalFIFOStatusIsMissingPar(void) { - rfalFIFOStatusUpdate(); - return ((gRFAL.fifo.status[RFAL_FIFO_STATUS_REG2] & ST25R3916_REG_FIFO_STATUS2_np_lb) != 0U); -} - -/*******************************************************************************/ -static uint8_t rfalFIFOGetNumIncompleteBits(void) { - rfalFIFOStatusUpdate(); - return ( - (gRFAL.fifo.status[RFAL_FIFO_STATUS_REG2] & ST25R3916_REG_FIFO_STATUS2_fifo_lb_mask) >> - ST25R3916_REG_FIFO_STATUS2_fifo_lb_shift); -} - -#if RFAL_FEATURE_NFCA - -/*******************************************************************************/ -ReturnCode rfalISO14443ATransceiveShortFrame( - rfal14443AShortFrameCmd txCmd, - uint8_t* rxBuf, - uint8_t rxBufLen, - uint16_t* rxRcvdLen, - uint32_t fwt) { - ReturnCode ret; - uint8_t directCmd; - - /* Check if RFAL is properly initialized */ - if(!st25r3916IsTxEnabled() || (gRFAL.state < RFAL_STATE_MODE_SET) || - ((gRFAL.mode != RFAL_MODE_POLL_NFCA) && (gRFAL.mode != RFAL_MODE_POLL_NFCA_T1T))) { - return ERR_WRONG_STATE; - } - - /* Check for valid parameters */ - if((rxBuf == NULL) || (rxRcvdLen == NULL) || (fwt == RFAL_FWT_NONE)) { - return ERR_PARAM; - } - - /*******************************************************************************/ - /* Select the Direct Command to be performed */ - switch(txCmd) { - case RFAL_14443A_SHORTFRAME_CMD_WUPA: - directCmd = ST25R3916_CMD_TRANSMIT_WUPA; - break; - - case RFAL_14443A_SHORTFRAME_CMD_REQA: - directCmd = ST25R3916_CMD_TRANSMIT_REQA; - break; - - default: - return ERR_PARAM; - } - - /* Disable CRC while receiving since ATQA has no CRC included */ - st25r3916SetRegisterBits(ST25R3916_REG_AUX, ST25R3916_REG_AUX_no_crc_rx); - - /*******************************************************************************/ - /* Wait for GT and FDT */ - while(!rfalIsGTExpired()) { /* MISRA 15.6: mandatory brackets */ - }; - while(st25r3916IsGPTRunning()) { /* MISRA 15.6: mandatory brackets */ - }; - - rfalTimerDestroy(gRFAL.tmr.GT); - gRFAL.tmr.GT = RFAL_TIMING_NONE; - - /*******************************************************************************/ - /* Prepare for Transceive, Receive only (bypass Tx states) */ - gRFAL.TxRx.ctx.flags = - ((uint32_t)RFAL_TXRX_FLAGS_CRC_TX_MANUAL | (uint32_t)RFAL_TXRX_FLAGS_CRC_RX_KEEP); - gRFAL.TxRx.ctx.rxBuf = rxBuf; - gRFAL.TxRx.ctx.rxBufLen = rxBufLen; - gRFAL.TxRx.ctx.rxRcvdLen = rxRcvdLen; - gRFAL.TxRx.ctx.fwt = fwt; - - /*******************************************************************************/ - /* Load NRT with FWT */ - st25r3916SetNoResponseTime(rfalConv1fcTo64fc( - MIN((fwt + RFAL_FWT_ADJUSTMENT + RFAL_FWT_A_ADJUSTMENT), RFAL_ST25R3916_NRT_MAX_1FC))); - - if(gRFAL.timings.FDTListen != RFAL_TIMING_NONE) { - /* Ensure that MRT is using 64/fc steps */ - st25r3916ClrRegisterBits( - ST25R3916_REG_TIMER_EMV_CONTROL, ST25R3916_REG_TIMER_EMV_CONTROL_mrt_step); - - /* Set Minimum FDT(Listen) in which PICC is not allowed to send a response */ - st25r3916WriteRegister( - ST25R3916_REG_MASK_RX_TIMER, - (uint8_t)rfalConv1fcTo64fc( - ((RFAL_FDT_LISTEN_MRT_ADJUSTMENT + RFAL_FDT_LISTEN_A_ADJUSTMENT) > - gRFAL.timings.FDTListen) ? - RFAL_ST25R3916_MRT_MIN_1FC : - (gRFAL.timings.FDTListen - - (RFAL_FDT_LISTEN_MRT_ADJUSTMENT + RFAL_FDT_LISTEN_A_ADJUSTMENT)))); - } - - /* In Passive communications General Purpose Timer is used to measure FDT Poll */ - if(gRFAL.timings.FDTPoll != RFAL_TIMING_NONE) { - /* Configure GPT to start at RX end */ - st25r3916SetStartGPTimer( - (uint16_t)rfalConv1fcTo8fc( - MIN(gRFAL.timings.FDTPoll, (gRFAL.timings.FDTPoll - RFAL_FDT_POLL_ADJUSTMENT))), - ST25R3916_REG_TIMER_EMV_CONTROL_gptc_erx); - } - - /*******************************************************************************/ - rfalPrepareTransceive(); - - /* Also enable bit collision interrupt */ - st25r3916GetInterrupt(ST25R3916_IRQ_MASK_COL); - st25r3916EnableInterrupts(ST25R3916_IRQ_MASK_COL); - - /*Check if Observation Mode is enabled and set it on ST25R391x */ - rfalCheckEnableObsModeTx(); - - /*******************************************************************************/ - /* Clear nbtx bits before sending WUPA/REQA - otherwise ST25R3916 will report parity error, Note2 of the register */ - st25r3916WriteRegister(ST25R3916_REG_NUM_TX_BYTES2, 0); - - /* Send either WUPA or REQA. All affected tags will backscatter ATQA and change to READY state */ - st25r3916ExecuteCommand(directCmd); - - /* Wait for TXE */ - if(st25r3916WaitForInterruptsTimed( - ST25R3916_IRQ_MASK_TXE, - (uint16_t)MAX(rfalConv1fcToMs(fwt), RFAL_ST25R3916_SW_TMR_MIN_1MS)) == 0U) { - ret = ERR_IO; - } else { - /*Check if Observation Mode is enabled and set it on ST25R391x */ - rfalCheckEnableObsModeRx(); - - /* Jump into a transceive Rx state for reception (bypass Tx states) */ - gRFAL.state = RFAL_STATE_TXRX; - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_IDLE; - gRFAL.TxRx.status = ERR_BUSY; - - /* Execute Transceive Rx blocking */ - ret = rfalTransceiveBlockingRx(); - } - - /* Disable Collision interrupt */ - st25r3916DisableInterrupts((ST25R3916_IRQ_MASK_COL)); - - /* ReEnable CRC on Rx */ - st25r3916ClrRegisterBits(ST25R3916_REG_AUX, ST25R3916_REG_AUX_no_crc_rx); - - return ret; -} - -/*******************************************************************************/ -ReturnCode rfalISO14443ATransceiveAnticollisionFrame( - uint8_t* buf, - uint8_t* bytesToSend, - uint8_t* bitsToSend, - uint16_t* rxLength, - uint32_t fwt) { - ReturnCode ret; - rfalTransceiveContext ctx; - uint8_t collByte; - uint8_t collData; - - /* Check if RFAL is properly initialized */ - if((gRFAL.state < RFAL_STATE_MODE_SET) || (gRFAL.mode != RFAL_MODE_POLL_NFCA)) { - return ERR_WRONG_STATE; - } - - /* Check for valid parameters */ - if((buf == NULL) || (bytesToSend == NULL) || (bitsToSend == NULL) || (rxLength == NULL)) { - return ERR_PARAM; - } - - /*******************************************************************************/ - /* Set specific Analog Config for Anticolission if needed */ - rfalSetAnalogConfig( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCA | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_ANTICOL)); - - /*******************************************************************************/ - /* Enable anti collision to recognise collision in first byte of SENS_REQ */ - st25r3916SetRegisterBits(ST25R3916_REG_ISO14443A_NFC, ST25R3916_REG_ISO14443A_NFC_antcl); - - /* Disable CRC while receiving */ - st25r3916SetRegisterBits(ST25R3916_REG_AUX, ST25R3916_REG_AUX_no_crc_rx); - - /*******************************************************************************/ - /* Prepare for Transceive */ - ctx.flags = ((uint32_t)RFAL_TXRX_FLAGS_CRC_TX_MANUAL | (uint32_t)RFAL_TXRX_FLAGS_CRC_RX_KEEP); - ctx.txBuf = buf; - ctx.txBufLen = (uint16_t)(rfalConvBytesToBits(*bytesToSend) + *bitsToSend); - ctx.rxBuf = &buf[*bytesToSend]; - ctx.rxBufLen = (uint16_t)rfalConvBytesToBits(RFAL_ISO14443A_SDD_RES_LEN); - ctx.rxRcvdLen = rxLength; - ctx.fwt = fwt; - - /* Disable Automatic Gain Control (AGC) for better detection of collisions if using Coherent Receiver */ - ctx.flags |= - (st25r3916CheckReg( - ST25R3916_REG_AUX, ST25R3916_REG_AUX_dis_corr, ST25R3916_REG_AUX_dis_corr) ? - (uint32_t)RFAL_TXRX_FLAGS_AGC_OFF : - 0x00U); - - rfalStartTransceive(&ctx); - - /* Additionally enable bit collision interrupt */ - st25r3916GetInterrupt(ST25R3916_IRQ_MASK_COL); - st25r3916EnableInterrupts(ST25R3916_IRQ_MASK_COL); - - /*******************************************************************************/ - collByte = 0; - - /* save the collision byte */ - if((*bitsToSend) > 0U) { - buf[(*bytesToSend)] <<= (RFAL_BITS_IN_BYTE - (*bitsToSend)); - buf[(*bytesToSend)] >>= (RFAL_BITS_IN_BYTE - (*bitsToSend)); - collByte = buf[(*bytesToSend)]; - } - - /*******************************************************************************/ - /* Run Transceive blocking */ - ret = rfalTransceiveRunBlockingTx(); - if(ret == ERR_NONE) { - ret = rfalTransceiveBlockingRx(); - - /*******************************************************************************/ - if((*bitsToSend) > 0U) { - buf[(*bytesToSend)] >>= (*bitsToSend); - buf[(*bytesToSend)] <<= (*bitsToSend); - buf[(*bytesToSend)] |= collByte; - } - - if((ERR_RF_COLLISION == ret)) { - /* read out collision register */ - st25r3916ReadRegister(ST25R3916_REG_COLLISION_STATUS, &collData); - - (*bytesToSend) = - ((collData >> ST25R3916_REG_COLLISION_STATUS_c_byte_shift) & - 0x0FU); // 4-bits Byte information - (*bitsToSend) = - ((collData >> ST25R3916_REG_COLLISION_STATUS_c_bit_shift) & - 0x07U); // 3-bits bit information - } - } - - /*******************************************************************************/ - /* Disable Collision interrupt */ - st25r3916DisableInterrupts((ST25R3916_IRQ_MASK_COL)); - - /* Disable anti collision again */ - st25r3916ClrRegisterBits(ST25R3916_REG_ISO14443A_NFC, ST25R3916_REG_ISO14443A_NFC_antcl); - - /* ReEnable CRC on Rx */ - st25r3916ClrRegisterBits(ST25R3916_REG_AUX, ST25R3916_REG_AUX_no_crc_rx); - /*******************************************************************************/ - - /* Restore common Analog configurations for this mode */ - rfalSetAnalogConfig( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCA | rfalConvBR2ACBR(gRFAL.txBR) | - RFAL_ANALOG_CONFIG_TX)); - rfalSetAnalogConfig( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCA | rfalConvBR2ACBR(gRFAL.rxBR) | - RFAL_ANALOG_CONFIG_RX)); - - return ret; -} - -#endif /* RFAL_FEATURE_NFCA */ - -#if RFAL_FEATURE_NFCV - -/*******************************************************************************/ -ReturnCode rfalISO15693TransceiveAnticollisionFrame( - uint8_t* txBuf, - uint8_t txBufLen, - uint8_t* rxBuf, - uint8_t rxBufLen, - uint16_t* actLen) { - ReturnCode ret; - rfalTransceiveContext ctx; - - /* Check if RFAL is properly initialized */ - if((gRFAL.state < RFAL_STATE_MODE_SET) || (gRFAL.mode != RFAL_MODE_POLL_NFCV)) { - return ERR_WRONG_STATE; - } - - /*******************************************************************************/ - /* Set specific Analog Config for Anticolission if needed */ - rfalSetAnalogConfig( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCV | - RFAL_ANALOG_CONFIG_BITRATE_COMMON | RFAL_ANALOG_CONFIG_ANTICOL)); - - /* Ignoring collisions before the UID (RES_FLAG + DSFID) */ - gRFAL.nfcvData.ignoreBits = (uint16_t)RFAL_ISO15693_IGNORE_BITS; - - /*******************************************************************************/ - /* Prepare for Transceive */ - ctx.flags = - ((txBufLen == 0U) ? (uint32_t)RFAL_TXRX_FLAGS_CRC_TX_MANUAL : - (uint32_t)RFAL_TXRX_FLAGS_CRC_TX_AUTO) | - (uint32_t)RFAL_TXRX_FLAGS_CRC_RX_KEEP | (uint32_t)RFAL_TXRX_FLAGS_AGC_OFF | - ((txBufLen == 0U) ? - (uint32_t)RFAL_TXRX_FLAGS_NFCV_FLAG_MANUAL : - (uint32_t) - RFAL_TXRX_FLAGS_NFCV_FLAG_AUTO); /* Disable Automatic Gain Control (AGC) for better detection of collision */ - ctx.txBuf = txBuf; - ctx.txBufLen = (uint16_t)rfalConvBytesToBits(txBufLen); - ctx.rxBuf = rxBuf; - ctx.rxBufLen = (uint16_t)rfalConvBytesToBits(rxBufLen); - ctx.rxRcvdLen = actLen; - ctx.fwt = rfalConv64fcTo1fc(ISO15693_FWT); - - rfalStartTransceive(&ctx); - - /*******************************************************************************/ - /* Run Transceive blocking */ - ret = rfalTransceiveRunBlockingTx(); - if(ret == ERR_NONE) { - ret = rfalTransceiveBlockingRx(); - } - - /* Check if a Transmission error and received data is less then expected */ - if(((ret == ERR_RF_COLLISION) || (ret == ERR_CRC) || (ret == ERR_FRAMING)) && - (rfalConvBitsToBytes(*ctx.rxRcvdLen) < RFAL_ISO15693_INV_RES_LEN)) { - /* If INVENTORY_RES is shorter than expected, tag is still modulating * - * Ensure that response is complete before next frame */ - platformDelay(( - uint8_t)((RFAL_ISO15693_INV_RES_LEN - rfalConvBitsToBytes(*ctx.rxRcvdLen)) / ((RFAL_ISO15693_INV_RES_LEN / RFAL_ISO15693_INV_RES_DUR) + 1U))); - } - - /* Restore common Analog configurations for this mode */ - rfalSetAnalogConfig( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCV | rfalConvBR2ACBR(gRFAL.txBR) | - RFAL_ANALOG_CONFIG_TX)); - rfalSetAnalogConfig( - (RFAL_ANALOG_CONFIG_POLL | RFAL_ANALOG_CONFIG_TECH_NFCV | rfalConvBR2ACBR(gRFAL.rxBR) | - RFAL_ANALOG_CONFIG_RX)); - - gRFAL.nfcvData.ignoreBits = 0; - return ret; -} - -/*******************************************************************************/ -ReturnCode - rfalISO15693TransceiveEOFAnticollision(uint8_t* rxBuf, uint8_t rxBufLen, uint16_t* actLen) { - uint8_t dummy; - - return rfalISO15693TransceiveAnticollisionFrame(&dummy, 0, rxBuf, rxBufLen, actLen); -} - -/*******************************************************************************/ -ReturnCode rfalISO15693TransceiveEOF(uint8_t* rxBuf, uint8_t rxBufLen, uint16_t* actLen) { - ReturnCode ret; - uint8_t dummy; - - /* Check if RFAL is properly initialized */ - if((gRFAL.state < RFAL_STATE_MODE_SET) || (gRFAL.mode != RFAL_MODE_POLL_NFCV)) { - return ERR_WRONG_STATE; - } - - /*******************************************************************************/ - /* Run Transceive blocking */ - ret = rfalTransceiveBlockingTxRx( - &dummy, - 0, - rxBuf, - rxBufLen, - actLen, - ((uint32_t)RFAL_TXRX_FLAGS_CRC_TX_MANUAL | (uint32_t)RFAL_TXRX_FLAGS_CRC_RX_KEEP | - (uint32_t)RFAL_TXRX_FLAGS_AGC_ON), - rfalConv64fcTo1fc(ISO15693_FWT)); - return ret; -} - -#endif /* RFAL_FEATURE_NFCV */ - -#if RFAL_FEATURE_NFCF - -/*******************************************************************************/ -ReturnCode rfalFeliCaPoll( - rfalFeliCaPollSlots slots, - uint16_t sysCode, - uint8_t reqCode, - rfalFeliCaPollRes* pollResList, - uint8_t pollResListSize, - uint8_t* devicesDetected, - uint8_t* collisionsDetected) { - ReturnCode ret; - uint8_t frame - [RFAL_FELICA_POLL_REQ_LEN - RFAL_FELICA_LEN_LEN]; // LEN is added by ST25R391x automatically - uint16_t actLen; - uint8_t frameIdx; - uint8_t devDetected; - uint8_t colDetected; - rfalEHandling curHandling; - uint8_t nbSlots; - - /* Check if RFAL is properly initialized */ - if((gRFAL.state < RFAL_STATE_MODE_SET) || (gRFAL.mode != RFAL_MODE_POLL_NFCF)) { - return ERR_WRONG_STATE; - } - - frameIdx = 0; - colDetected = 0; - devDetected = 0; - nbSlots = (uint8_t)slots; - - /*******************************************************************************/ - /* Compute SENSF_REQ frame */ - frame[frameIdx++] = (uint8_t)FELICA_CMD_POLLING; /* CMD: SENF_REQ */ - frame[frameIdx++] = (uint8_t)(sysCode >> 8); /* System Code (SC) */ - frame[frameIdx++] = (uint8_t)(sysCode & 0xFFU); /* System Code (SC) */ - frame[frameIdx++] = reqCode; /* Communication Parameter Request (RC)*/ - frame[frameIdx++] = nbSlots; /* TimeSlot (TSN) */ - - /*******************************************************************************/ - /* NRT should not stop on reception - Use EMVCo mode to run NRT in nrt_emv * - * ERRORHANDLING_EMVCO has no special handling for NFC-F mode */ - curHandling = gRFAL.conf.eHandling; - rfalSetErrorHandling(RFAL_ERRORHANDLING_EMVCO); - - /*******************************************************************************/ - /* Run transceive blocking, - * Calculate Total Response Time in(64/fc): - * 512 PICC process time + (n * 256 Time Slot duration) */ - ret = rfalTransceiveBlockingTx( - frame, - (uint16_t)frameIdx, - (uint8_t*)gRFAL.nfcfData.pollResponses, - RFAL_FELICA_POLL_RES_LEN, - &actLen, - (RFAL_TXRX_FLAGS_DEFAULT), - rfalConv64fcTo1fc( - RFAL_FELICA_POLL_DELAY_TIME + - (RFAL_FELICA_POLL_SLOT_TIME * ((uint32_t)nbSlots + 1U)))); - - /*******************************************************************************/ - /* If Tx OK, Wait for all responses, store them as soon as they appear */ - if(ret == ERR_NONE) { - bool timeout; - - do { - ret = rfalTransceiveBlockingRx(); - if(ret == ERR_TIMEOUT) { - /* Upon timeout the full Poll Delay + (Slot time)*(nbSlots) has expired */ - timeout = true; - } else { - /* Reception done, reEnabled Rx for following Slot */ - st25r3916ExecuteCommand(ST25R3916_CMD_UNMASK_RECEIVE_DATA); - st25r3916ExecuteCommand(ST25R3916_CMD_RESET_RXGAIN); - - /* If the reception was OK, new device found */ - if(ret == ERR_NONE) { - devDetected++; - - /* Overwrite the Transceive context for the next reception */ - gRFAL.TxRx.ctx.rxBuf = (uint8_t*)gRFAL.nfcfData.pollResponses[devDetected]; - } - /* If the reception was not OK, mark as collision */ - else { - colDetected++; - } - - /* Check whether NRT has expired meanwhile */ - timeout = st25r3916CheckReg( - ST25R3916_REG_NFCIP1_BIT_RATE, ST25R3916_REG_NFCIP1_BIT_RATE_nrt_on, 0x00); - if(!timeout) { - /* Jump again into transceive Rx state for the following reception */ - gRFAL.TxRx.status = ERR_BUSY; - gRFAL.state = RFAL_STATE_TXRX; - gRFAL.TxRx.state = RFAL_TXRX_STATE_RX_IDLE; - } - } - } while(((nbSlots--) != 0U) && !timeout); - } - - /*******************************************************************************/ - /* Restore NRT to normal mode - back to previous error handling */ - rfalSetErrorHandling(curHandling); - - /*******************************************************************************/ - /* Assign output parameters if requested */ - - if((pollResList != NULL) && (pollResListSize > 0U) && (devDetected > 0U)) { - ST_MEMCPY( - pollResList, - gRFAL.nfcfData.pollResponses, - (RFAL_FELICA_POLL_RES_LEN * (uint32_t)MIN(pollResListSize, devDetected))); - } - - if(devicesDetected != NULL) { - *devicesDetected = devDetected; - } - - if(collisionsDetected != NULL) { - *collisionsDetected = colDetected; - } - - return (((colDetected != 0U) || (devDetected != 0U)) ? ERR_NONE : ret); -} - -#endif /* RFAL_FEATURE_NFCF */ - -/***************************************************************************** - * Listen Mode * - *****************************************************************************/ - -/*******************************************************************************/ -bool rfalIsExtFieldOn(void) { - return st25r3916IsExtFieldOn(); -} - -#if RFAL_FEATURE_LISTEN_MODE - -/*******************************************************************************/ -ReturnCode rfalListenStart( - uint32_t lmMask, - const rfalLmConfPA* confA, - const rfalLmConfPB* confB, - const rfalLmConfPF* confF, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rxLen) { - t_rfalPTMem - PTMem; /* PRQA S 0759 # MISRA 19.2 - Allocating Union where members are of the same type, just different names. Thus no problem can occur. */ - uint8_t* pPTMem; - uint8_t autoResp; - - /* Check if RFAL is initialized */ - if(gRFAL.state < RFAL_STATE_INIT) { - return ERR_WRONG_STATE; - } - - gRFAL.Lm.state = RFAL_LM_STATE_NOT_INIT; - gRFAL.Lm.mdIrqs = ST25R3916_IRQ_MASK_NONE; - gRFAL.Lm.mdReg = - (ST25R3916_REG_MODE_targ_init | ST25R3916_REG_MODE_om_nfc | ST25R3916_REG_MODE_nfc_ar_off); - - /* By default disable all automatic responses */ - autoResp = - (ST25R3916_REG_PASSIVE_TARGET_d_106_ac_a | ST25R3916_REG_PASSIVE_TARGET_rfu | - ST25R3916_REG_PASSIVE_TARGET_d_212_424_1r | ST25R3916_REG_PASSIVE_TARGET_d_ac_ap2p); - - /*******************************************************************************/ - if((lmMask & RFAL_LM_MASK_NFCA) != 0U) { - /* Check if the conf has been provided */ - if(confA == NULL) { - return ERR_PARAM; - } - - pPTMem = (uint8_t*)PTMem.PTMem_A; - - /*******************************************************************************/ - /* Check and set supported NFCID Length */ - switch(confA->nfcidLen) { - case RFAL_LM_NFCID_LEN_04: - st25r3916ChangeRegisterBits( - ST25R3916_REG_AUX, ST25R3916_REG_AUX_nfc_id_mask, ST25R3916_REG_AUX_nfc_id_4bytes); - break; - - case RFAL_LM_NFCID_LEN_07: - st25r3916ChangeRegisterBits( - ST25R3916_REG_AUX, ST25R3916_REG_AUX_nfc_id_mask, ST25R3916_REG_AUX_nfc_id_7bytes); - break; - - default: - return ERR_PARAM; - } - - /*******************************************************************************/ - /* Set NFCID */ - ST_MEMCPY(pPTMem, confA->nfcid, RFAL_NFCID1_TRIPLE_LEN); - pPTMem = &pPTMem[RFAL_NFCID1_TRIPLE_LEN]; /* MISRA 18.4 */ - - /* Set SENS_RES */ - ST_MEMCPY(pPTMem, confA->SENS_RES, RFAL_LM_SENS_RES_LEN); - pPTMem = &pPTMem[RFAL_LM_SENS_RES_LEN]; /* MISRA 18.4 */ - - /* Set SEL_RES */ - *pPTMem++ = - ((confA->nfcidLen == RFAL_LM_NFCID_LEN_04) ? - (confA->SEL_RES & ~RFAL_LM_NFCID_INCOMPLETE) : - (confA->SEL_RES | RFAL_LM_NFCID_INCOMPLETE)); - *pPTMem++ = (confA->SEL_RES & ~RFAL_LM_NFCID_INCOMPLETE); - *pPTMem++ = (confA->SEL_RES & ~RFAL_LM_NFCID_INCOMPLETE); - - /* Write into PTMem-A */ - st25r3916WritePTMem(PTMem.PTMem_A, ST25R3916_PTM_A_LEN); - - /*******************************************************************************/ - /* Enable automatic responses for A */ - autoResp &= ~ST25R3916_REG_PASSIVE_TARGET_d_106_ac_a; - - /* Set Target mode, Bit Rate detection and Listen Mode for NFC-F */ - gRFAL.Lm.mdReg |= - (ST25R3916_REG_MODE_targ_targ | ST25R3916_REG_MODE_om3 | ST25R3916_REG_MODE_om0 | - ST25R3916_REG_MODE_nfc_ar_off); - - gRFAL.Lm.mdIrqs |= - (ST25R3916_IRQ_MASK_WU_A | ST25R3916_IRQ_MASK_WU_A_X | ST25R3916_IRQ_MASK_RXE_PTA); - } - - /*******************************************************************************/ - if((lmMask & RFAL_LM_MASK_NFCB) != 0U) { - /* Check if the conf has been provided */ - if(confB == NULL) { - return ERR_PARAM; - } - - return ERR_NOTSUPP; - } - - /*******************************************************************************/ - if((lmMask & RFAL_LM_MASK_NFCF) != 0U) { - pPTMem = (uint8_t*)PTMem.PTMem_F; - - /* Check if the conf has been provided */ - if(confF == NULL) { - return ERR_PARAM; - } - - /*******************************************************************************/ - /* Set System Code */ - ST_MEMCPY(pPTMem, confF->SC, RFAL_LM_SENSF_SC_LEN); - pPTMem = &pPTMem[RFAL_LM_SENSF_SC_LEN]; /* MISRA 18.4 */ - - /* Set SENSF_RES */ - ST_MEMCPY(pPTMem, confF->SENSF_RES, RFAL_LM_SENSF_RES_LEN); - - /* Set RD bytes to 0x00 as ST25R3916 cannot support advances features */ - pPTMem[RFAL_LM_SENSF_RD0_POS] = - 0x00; /* NFC Forum Digital 1.1 Table 46: 0x00 */ - pPTMem[RFAL_LM_SENSF_RD1_POS] = - 0x00; /* NFC Forum Digital 1.1 Table 47: No automatic bit rates */ - - pPTMem = &pPTMem[RFAL_LM_SENS_RES_LEN]; /* MISRA 18.4 */ - - /* Write into PTMem-F */ - st25r3916WritePTMemF(PTMem.PTMem_F, ST25R3916_PTM_F_LEN); - - /*******************************************************************************/ - /* Write 24 TSN "Random" Numbers at first initialization and let it rollover */ - if(!gRFAL.Lm.iniFlag) { - pPTMem = (uint8_t*)PTMem.TSN; - - *pPTMem++ = 0x12; - *pPTMem++ = 0x34; - *pPTMem++ = 0x56; - *pPTMem++ = 0x78; - *pPTMem++ = 0x9A; - *pPTMem++ = 0xBC; - *pPTMem++ = 0xDF; - *pPTMem++ = 0x21; - *pPTMem++ = 0x43; - *pPTMem++ = 0x65; - *pPTMem++ = 0x87; - *pPTMem++ = 0xA9; - - /* Write into PTMem-TSN */ - st25r3916WritePTMemTSN(PTMem.TSN, ST25R3916_PTM_TSN_LEN); - } - - /*******************************************************************************/ - /* Enable automatic responses for F */ - autoResp &= ~(ST25R3916_REG_PASSIVE_TARGET_d_212_424_1r); - - /* Set Target mode, Bit Rate detection and Listen Mode for NFC-F */ - gRFAL.Lm.mdReg |= - (ST25R3916_REG_MODE_targ_targ | ST25R3916_REG_MODE_om3 | ST25R3916_REG_MODE_om2 | - ST25R3916_REG_MODE_nfc_ar_off); - - /* In CE NFC-F any data without error will be passed to FIFO, to support CUP */ - gRFAL.Lm.mdIrqs |= - (ST25R3916_IRQ_MASK_WU_F | ST25R3916_IRQ_MASK_RXE_PTA | ST25R3916_IRQ_MASK_RXE); - } - - /*******************************************************************************/ - if((lmMask & RFAL_LM_MASK_ACTIVE_P2P) != 0U) { - /* Enable Reception of P2P frames */ - autoResp &= ~(ST25R3916_REG_PASSIVE_TARGET_d_ac_ap2p); - - /* Set Target mode, Bit Rate detection and Automatic Response RF Collision Avoidance */ - gRFAL.Lm.mdReg |= - (ST25R3916_REG_MODE_targ_targ | ST25R3916_REG_MODE_om3 | ST25R3916_REG_MODE_om2 | - ST25R3916_REG_MODE_om0 | ST25R3916_REG_MODE_nfc_ar_auto_rx); - - /* n * TRFW timing shall vary Activity 2.1 3.4.1.1 */ - st25r3916ChangeRegisterBits( - ST25R3916_REG_AUX, ST25R3916_REG_AUX_nfc_n_mask, gRFAL.timings.nTRFW); - gRFAL.timings.nTRFW = rfalGennTRFW(gRFAL.timings.nTRFW); - - gRFAL.Lm.mdIrqs |= (ST25R3916_IRQ_MASK_RXE); - } - - /* Check if one of the modes were selected */ - if((gRFAL.Lm.mdReg & ST25R3916_REG_MODE_targ) == ST25R3916_REG_MODE_targ_targ) { - gRFAL.state = RFAL_STATE_LM; - gRFAL.Lm.mdMask = lmMask; - - gRFAL.Lm.rxBuf = rxBuf; - gRFAL.Lm.rxBufLen = rxBufLen; - gRFAL.Lm.rxLen = rxLen; - *gRFAL.Lm.rxLen = 0; - gRFAL.Lm.dataFlag = false; - gRFAL.Lm.iniFlag = true; - - /* Apply the Automatic Responses configuration */ - st25r3916ChangeRegisterBits( - ST25R3916_REG_PASSIVE_TARGET, - (ST25R3916_REG_PASSIVE_TARGET_d_106_ac_a | ST25R3916_REG_PASSIVE_TARGET_rfu | - ST25R3916_REG_PASSIVE_TARGET_d_212_424_1r | ST25R3916_REG_PASSIVE_TARGET_d_ac_ap2p), - autoResp); - - /* Disable GPT trigger source */ - st25r3916ChangeRegisterBits( - ST25R3916_REG_TIMER_EMV_CONTROL, - ST25R3916_REG_TIMER_EMV_CONTROL_gptc_mask, - ST25R3916_REG_TIMER_EMV_CONTROL_gptc_no_trigger); - - /* On Bit Rate Detection Mode ST25R391x will filter incoming frames during MRT time starting on External Field On event, use 512/fc steps */ - st25r3916SetRegisterBits( - ST25R3916_REG_TIMER_EMV_CONTROL, ST25R3916_REG_TIMER_EMV_CONTROL_mrt_step_512); - st25r3916WriteRegister( - ST25R3916_REG_MASK_RX_TIMER, (uint8_t)rfalConv1fcTo512fc(RFAL_LM_GT)); - - /* Restore default settings on NFCIP1 mode, Receiving parity + CRC bits and manual Tx Parity*/ - st25r3916ClrRegisterBits( - ST25R3916_REG_ISO14443A_NFC, - (ST25R3916_REG_ISO14443A_NFC_no_tx_par | ST25R3916_REG_ISO14443A_NFC_no_rx_par | - ST25R3916_REG_ISO14443A_NFC_nfc_f0)); - - /* External Field Detector enabled as Automatics on rfalInitialize() */ - - /* Set Analog configurations for generic Listen mode */ - /* Not on SetState(POWER OFF) as otherwise would be applied on every Field Event */ - rfalSetAnalogConfig((RFAL_ANALOG_CONFIG_TECH_CHIP | RFAL_ANALOG_CONFIG_CHIP_LISTEN_ON)); - - /* Initialize as POWER_OFF and set proper mode in RF Chip */ - rfalListenSetState(RFAL_LM_STATE_POWER_OFF); - } else { - return ERR_REQUEST; /* Listen Start called but no mode was enabled */ - } - - return ERR_NONE; -} - -/*******************************************************************************/ -static ReturnCode rfalRunListenModeWorker(void) { - volatile uint32_t irqs; - uint8_t tmp; - - if(gRFAL.state != RFAL_STATE_LM) { - return ERR_WRONG_STATE; - } - - switch(gRFAL.Lm.state) { - /*******************************************************************************/ - case RFAL_LM_STATE_POWER_OFF: - - irqs = st25r3916GetInterrupt((ST25R3916_IRQ_MASK_EON)); - if(irqs == ST25R3916_IRQ_MASK_NONE) { - break; /* No interrupt to process */ - } - - if((irqs & ST25R3916_IRQ_MASK_EON) != 0U) { - rfalListenSetState(RFAL_LM_STATE_IDLE); - } else { - break; - } - /* fall through */ - - /*******************************************************************************/ - case RFAL_LM_STATE_IDLE: /* PRQA S 2003 # MISRA 16.3 - Intentional fall through */ - - irqs = st25r3916GetInterrupt( - (ST25R3916_IRQ_MASK_NFCT | ST25R3916_IRQ_MASK_WU_F | ST25R3916_IRQ_MASK_RXE | - ST25R3916_IRQ_MASK_EOF | ST25R3916_IRQ_MASK_RXE_PTA)); - if(irqs == ST25R3916_IRQ_MASK_NONE) { - break; /* No interrupt to process */ - } - - if((irqs & ST25R3916_IRQ_MASK_NFCT) != 0U) { - /* Retrieve detected bitrate */ - uint8_t newBr; - st25r3916ReadRegister(ST25R3916_REG_NFCIP1_BIT_RATE, &newBr); - newBr >>= ST25R3916_REG_NFCIP1_BIT_RATE_nfc_rate_shift; - - if(newBr > ST25R3916_REG_BIT_RATE_rxrate_424) { - newBr = ST25R3916_REG_BIT_RATE_rxrate_424; - } - - gRFAL.Lm.brDetected = - (rfalBitRate)(newBr); /* PRQA S 4342 # MISRA 10.5 - Guaranteed that no invalid enum values may be created. See also equalityGuard_RFAL_BR_106 ff.*/ - } - - if(((irqs & ST25R3916_IRQ_MASK_WU_F) != 0U) && (gRFAL.Lm.brDetected != RFAL_BR_KEEP)) { - rfalListenSetState(RFAL_LM_STATE_READY_F); - } else if(((irqs & ST25R3916_IRQ_MASK_RXE) != 0U) && (gRFAL.Lm.brDetected != RFAL_BR_KEEP)) { - irqs = st25r3916GetInterrupt( - (ST25R3916_IRQ_MASK_WU_F | ST25R3916_IRQ_MASK_RXE | ST25R3916_IRQ_MASK_EOF | - ST25R3916_IRQ_MASK_CRC | ST25R3916_IRQ_MASK_PAR | ST25R3916_IRQ_MASK_ERR2 | - ST25R3916_IRQ_MASK_ERR1)); - - if(((irqs & ST25R3916_IRQ_MASK_CRC) != 0U) || - ((irqs & ST25R3916_IRQ_MASK_PAR) != 0U) || - ((irqs & ST25R3916_IRQ_MASK_ERR1) != 0U)) { - st25r3916ExecuteCommand(ST25R3916_CMD_CLEAR_FIFO); - st25r3916ExecuteCommand(ST25R3916_CMD_UNMASK_RECEIVE_DATA); - st25r3916TxOff(); - break; /* A bad reception occurred, remain in same state */ - } - - /* Retrieve received data */ - *gRFAL.Lm.rxLen = st25r3916GetNumFIFOBytes(); - st25r3916ReadFifo( - gRFAL.Lm.rxBuf, MIN(*gRFAL.Lm.rxLen, rfalConvBitsToBytes(gRFAL.Lm.rxBufLen))); - - /*******************************************************************************/ - /* REMARK: Silicon workaround ST25R3916 Errata #TBD */ - /* In bitrate detection mode CRC is now checked for NFC-A frames */ - if((*gRFAL.Lm.rxLen > RFAL_CRC_LEN) && (gRFAL.Lm.brDetected == RFAL_BR_106)) { - if(rfalCrcCalculateCcitt( - RFAL_ISO14443A_CRC_INTVAL, gRFAL.Lm.rxBuf, *gRFAL.Lm.rxLen) != 0U) { - st25r3916ExecuteCommand(ST25R3916_CMD_CLEAR_FIFO); - st25r3916ExecuteCommand(ST25R3916_CMD_UNMASK_RECEIVE_DATA); - st25r3916TxOff(); - break; /* A bad reception occurred, remain in same state */ - } - } - /*******************************************************************************/ - - /* Check if the data we got has at least the CRC and remove it, otherwise leave at 0 */ - *gRFAL.Lm.rxLen -= ((*gRFAL.Lm.rxLen > RFAL_CRC_LEN) ? RFAL_CRC_LEN : *gRFAL.Lm.rxLen); - *gRFAL.Lm.rxLen = (uint16_t)rfalConvBytesToBits(*gRFAL.Lm.rxLen); - gRFAL.Lm.dataFlag = true; - - /*Check if Observation Mode was enabled and disable it on ST25R391x */ - rfalCheckDisableObsMode(); - } else if( - ((irqs & ST25R3916_IRQ_MASK_RXE_PTA) != 0U) && (gRFAL.Lm.brDetected != RFAL_BR_KEEP)) { - if(((gRFAL.Lm.mdMask & RFAL_LM_MASK_NFCA) != 0U) && - (gRFAL.Lm.brDetected == RFAL_BR_106)) { - st25r3916ReadRegister(ST25R3916_REG_PASSIVE_TARGET_STATUS, &tmp); - if(tmp > ST25R3916_REG_PASSIVE_TARGET_STATUS_pta_st_idle) { - rfalListenSetState(RFAL_LM_STATE_READY_A); - } - } - } else if(((irqs & ST25R3916_IRQ_MASK_EOF) != 0U) && (!gRFAL.Lm.dataFlag)) { - rfalListenSetState(RFAL_LM_STATE_POWER_OFF); - } else { - /* MISRA 15.7 - Empty else */ - } - break; - - /*******************************************************************************/ - case RFAL_LM_STATE_READY_F: - - irqs = st25r3916GetInterrupt( - (ST25R3916_IRQ_MASK_WU_F | ST25R3916_IRQ_MASK_RXE | ST25R3916_IRQ_MASK_EOF)); - if(irqs == ST25R3916_IRQ_MASK_NONE) { - break; /* No interrupt to process */ - } - - if((irqs & ST25R3916_IRQ_MASK_WU_F) != 0U) { - break; - } else if((irqs & ST25R3916_IRQ_MASK_RXE) != 0U) { - /* Retrieve the error flags/irqs */ - irqs |= st25r3916GetInterrupt( - (ST25R3916_IRQ_MASK_CRC | ST25R3916_IRQ_MASK_ERR2 | ST25R3916_IRQ_MASK_ERR1)); - - if(((irqs & ST25R3916_IRQ_MASK_CRC) != 0U) || - ((irqs & ST25R3916_IRQ_MASK_ERR1) != 0U)) { - st25r3916ExecuteCommand(ST25R3916_CMD_CLEAR_FIFO); - st25r3916ExecuteCommand(ST25R3916_CMD_UNMASK_RECEIVE_DATA); - break; /* A bad reception occurred, remain in same state */ - } - - /* Retrieve received data */ - *gRFAL.Lm.rxLen = st25r3916GetNumFIFOBytes(); - st25r3916ReadFifo( - gRFAL.Lm.rxBuf, MIN(*gRFAL.Lm.rxLen, rfalConvBitsToBytes(gRFAL.Lm.rxBufLen))); - - /* Check if the data we got has at least the CRC and remove it, otherwise leave at 0 */ - *gRFAL.Lm.rxLen -= ((*gRFAL.Lm.rxLen > RFAL_CRC_LEN) ? RFAL_CRC_LEN : *gRFAL.Lm.rxLen); - *gRFAL.Lm.rxLen = (uint16_t)rfalConvBytesToBits(*gRFAL.Lm.rxLen); - gRFAL.Lm.dataFlag = true; - } else if((irqs & ST25R3916_IRQ_MASK_EOF) != 0U) { - rfalListenSetState(RFAL_LM_STATE_POWER_OFF); - } else { - /* MISRA 15.7 - Empty else */ - } - break; - - /*******************************************************************************/ - case RFAL_LM_STATE_READY_A: - - irqs = st25r3916GetInterrupt((ST25R3916_IRQ_MASK_EOF | ST25R3916_IRQ_MASK_WU_A)); - if(irqs == ST25R3916_IRQ_MASK_NONE) { - break; /* No interrupt to process */ - } - - if((irqs & ST25R3916_IRQ_MASK_WU_A) != 0U) { - rfalListenSetState(RFAL_LM_STATE_ACTIVE_A); - } else if((irqs & ST25R3916_IRQ_MASK_EOF) != 0U) { - rfalListenSetState(RFAL_LM_STATE_POWER_OFF); - } else { - /* MISRA 15.7 - Empty else */ - } - break; - - /*******************************************************************************/ - case RFAL_LM_STATE_ACTIVE_A: - case RFAL_LM_STATE_ACTIVE_Ax: - - irqs = st25r3916GetInterrupt((ST25R3916_IRQ_MASK_RXE | ST25R3916_IRQ_MASK_EOF)); - if(irqs == ST25R3916_IRQ_MASK_NONE) { - break; /* No interrupt to process */ - } - - if((irqs & ST25R3916_IRQ_MASK_RXE) != 0U) { - /* Retrieve the error flags/irqs */ - irqs |= st25r3916GetInterrupt( - (ST25R3916_IRQ_MASK_PAR | ST25R3916_IRQ_MASK_CRC | ST25R3916_IRQ_MASK_ERR2 | - ST25R3916_IRQ_MASK_ERR1)); - *gRFAL.Lm.rxLen = st25r3916GetNumFIFOBytes(); - - if(((irqs & ST25R3916_IRQ_MASK_CRC) != 0U) || - ((irqs & ST25R3916_IRQ_MASK_ERR1) != 0U) || - ((irqs & ST25R3916_IRQ_MASK_PAR) != 0U) || (*gRFAL.Lm.rxLen <= RFAL_CRC_LEN)) { - /* Clear rx context and FIFO */ - *gRFAL.Lm.rxLen = 0; - st25r3916ExecuteCommand(ST25R3916_CMD_CLEAR_FIFO); - st25r3916ExecuteCommand(ST25R3916_CMD_UNMASK_RECEIVE_DATA); - - /* Check if we should go to IDLE or Sleep */ - if(gRFAL.Lm.state == RFAL_LM_STATE_ACTIVE_Ax) { - rfalListenSleepStart( - RFAL_LM_STATE_SLEEP_A, gRFAL.Lm.rxBuf, gRFAL.Lm.rxBufLen, gRFAL.Lm.rxLen); - } else { - rfalListenSetState(RFAL_LM_STATE_IDLE); - } - - st25r3916DisableInterrupts(ST25R3916_IRQ_MASK_RXE); - break; - } - - /* Remove CRC from length */ - *gRFAL.Lm.rxLen -= RFAL_CRC_LEN; - - /* Retrieve received data */ - st25r3916ReadFifo( - gRFAL.Lm.rxBuf, MIN(*gRFAL.Lm.rxLen, rfalConvBitsToBytes(gRFAL.Lm.rxBufLen))); - *gRFAL.Lm.rxLen = (uint16_t)rfalConvBytesToBits(*gRFAL.Lm.rxLen); - gRFAL.Lm.dataFlag = true; - } else if((irqs & ST25R3916_IRQ_MASK_EOF) != 0U) { - rfalListenSetState(RFAL_LM_STATE_POWER_OFF); - } else { - /* MISRA 15.7 - Empty else */ - } - break; - - /*******************************************************************************/ - case RFAL_LM_STATE_SLEEP_A: - case RFAL_LM_STATE_SLEEP_B: - case RFAL_LM_STATE_SLEEP_AF: - - irqs = st25r3916GetInterrupt( - (ST25R3916_IRQ_MASK_NFCT | ST25R3916_IRQ_MASK_WU_F | ST25R3916_IRQ_MASK_RXE | - ST25R3916_IRQ_MASK_EOF | ST25R3916_IRQ_MASK_RXE_PTA)); - if(irqs == ST25R3916_IRQ_MASK_NONE) { - break; /* No interrupt to process */ - } - - if((irqs & ST25R3916_IRQ_MASK_NFCT) != 0U) { - uint8_t newBr; - /* Retrieve detected bitrate */ - st25r3916ReadRegister(ST25R3916_REG_NFCIP1_BIT_RATE, &newBr); - newBr >>= ST25R3916_REG_NFCIP1_BIT_RATE_nfc_rate_shift; - - if(newBr > ST25R3916_REG_BIT_RATE_rxrate_424) { - newBr = ST25R3916_REG_BIT_RATE_rxrate_424; - } - - gRFAL.Lm.brDetected = - (rfalBitRate)(newBr); /* PRQA S 4342 # MISRA 10.5 - Guaranteed that no invalid enum values may be created. See also equalityGuard_RFAL_BR_106 ff.*/ - } - - if(((irqs & ST25R3916_IRQ_MASK_WU_F) != 0U) && (gRFAL.Lm.brDetected != RFAL_BR_KEEP)) { - rfalListenSetState(RFAL_LM_STATE_READY_F); - } else if(((irqs & ST25R3916_IRQ_MASK_RXE) != 0U) && (gRFAL.Lm.brDetected != RFAL_BR_KEEP)) { - /* Clear rx context and FIFO */ - *gRFAL.Lm.rxLen = 0; - st25r3916ExecuteCommand(ST25R3916_CMD_CLEAR_FIFO); - st25r3916ExecuteCommand(ST25R3916_CMD_UNMASK_RECEIVE_DATA); - - /* REMARK: In order to support CUP or proprietary frames, handling could be added here */ - } else if( - ((irqs & ST25R3916_IRQ_MASK_RXE_PTA) != 0U) && (gRFAL.Lm.brDetected != RFAL_BR_KEEP)) { - if(((gRFAL.Lm.mdMask & RFAL_LM_MASK_NFCA) != 0U) && - (gRFAL.Lm.brDetected == RFAL_BR_106)) { - st25r3916ReadRegister(ST25R3916_REG_PASSIVE_TARGET_STATUS, &tmp); - if(tmp > ST25R3916_REG_PASSIVE_TARGET_STATUS_pta_st_halt) { - rfalListenSetState(RFAL_LM_STATE_READY_Ax); - } - } - } else if((irqs & ST25R3916_IRQ_MASK_EOF) != 0U) { - rfalListenSetState(RFAL_LM_STATE_POWER_OFF); - } else { - /* MISRA 15.7 - Empty else */ - } - break; - - /*******************************************************************************/ - case RFAL_LM_STATE_READY_Ax: - - irqs = st25r3916GetInterrupt((ST25R3916_IRQ_MASK_EOF | ST25R3916_IRQ_MASK_WU_A_X)); - if(irqs == ST25R3916_IRQ_MASK_NONE) { - break; /* No interrupt to process */ - } - - if((irqs & ST25R3916_IRQ_MASK_WU_A_X) != 0U) { - rfalListenSetState(RFAL_LM_STATE_ACTIVE_Ax); - } else if((irqs & ST25R3916_IRQ_MASK_EOF) != 0U) { - rfalListenSetState(RFAL_LM_STATE_POWER_OFF); - } else { - /* MISRA 15.7 - Empty else */ - } - break; - - /*******************************************************************************/ - case RFAL_LM_STATE_CARDEMU_4A: - case RFAL_LM_STATE_CARDEMU_4B: - case RFAL_LM_STATE_CARDEMU_3: - case RFAL_LM_STATE_TARGET_F: - case RFAL_LM_STATE_TARGET_A: - break; - - /*******************************************************************************/ - default: - return ERR_WRONG_STATE; - } - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalListenStop(void) { - /* Check if RFAL is initialized */ - if(gRFAL.state < RFAL_STATE_INIT) { - return ERR_WRONG_STATE; - } - - gRFAL.Lm.state = RFAL_LM_STATE_NOT_INIT; - - /*Check if Observation Mode was enabled and disable it on ST25R391x */ - rfalCheckDisableObsMode(); - - /* Re-Enable the Oscillator if not running */ - st25r3916OscOn(); - - /* Disable Receiver and Transmitter */ - rfalFieldOff(); - - /* Disable all automatic responses */ - st25r3916SetRegisterBits( - ST25R3916_REG_PASSIVE_TARGET, - (ST25R3916_REG_PASSIVE_TARGET_d_212_424_1r | ST25R3916_REG_PASSIVE_TARGET_rfu | - ST25R3916_REG_PASSIVE_TARGET_d_106_ac_a | ST25R3916_REG_PASSIVE_TARGET_d_ac_ap2p)); - - /* As there's no Off mode, set default value: ISO14443A with automatic RF Collision Avoidance Off */ - st25r3916WriteRegister( - ST25R3916_REG_MODE, - (ST25R3916_REG_MODE_om_iso14443a | ST25R3916_REG_MODE_tr_am_ook | - ST25R3916_REG_MODE_nfc_ar_off)); - - st25r3916DisableInterrupts( - (ST25R3916_IRQ_MASK_RXE_PTA | ST25R3916_IRQ_MASK_WU_F | ST25R3916_IRQ_MASK_WU_A | - ST25R3916_IRQ_MASK_WU_A_X | ST25R3916_IRQ_MASK_RFU2 | ST25R3916_IRQ_MASK_OSC)); - st25r3916GetInterrupt( - (ST25R3916_IRQ_MASK_RXE_PTA | ST25R3916_IRQ_MASK_WU_F | ST25R3916_IRQ_MASK_WU_A | - ST25R3916_IRQ_MASK_WU_A_X | ST25R3916_IRQ_MASK_RFU2)); - - /* Set Analog configurations for Listen Off event */ - rfalSetAnalogConfig((RFAL_ANALOG_CONFIG_TECH_CHIP | RFAL_ANALOG_CONFIG_CHIP_LISTEN_OFF)); - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode - rfalListenSleepStart(rfalLmState sleepSt, uint8_t* rxBuf, uint16_t rxBufLen, uint16_t* rxLen) { - /* Check if RFAL is not initialized */ - if(gRFAL.state < RFAL_STATE_INIT) { - return ERR_WRONG_STATE; - } - - switch(sleepSt) { - /*******************************************************************************/ - case RFAL_LM_STATE_SLEEP_A: - - /* Enable automatic responses for A */ - st25r3916ClrRegisterBits( - ST25R3916_REG_PASSIVE_TARGET, (ST25R3916_REG_PASSIVE_TARGET_d_106_ac_a)); - - /* Reset NFCA target */ - st25r3916ExecuteCommand(ST25R3916_CMD_GOTO_SLEEP); - - /* Set Target mode, Bit Rate detection and Listen Mode for NFC-A */ - st25r3916ChangeRegisterBits( - ST25R3916_REG_MODE, - (ST25R3916_REG_MODE_targ | ST25R3916_REG_MODE_om_mask | - ST25R3916_REG_MODE_nfc_ar_mask), - (ST25R3916_REG_MODE_targ_targ | ST25R3916_REG_MODE_om3 | ST25R3916_REG_MODE_om0 | - ST25R3916_REG_MODE_nfc_ar_off)); - break; - - /*******************************************************************************/ - case RFAL_LM_STATE_SLEEP_AF: - - /* Enable automatic responses for A + F */ - st25r3916ClrRegisterBits( - ST25R3916_REG_PASSIVE_TARGET, - (ST25R3916_REG_PASSIVE_TARGET_d_212_424_1r | ST25R3916_REG_PASSIVE_TARGET_d_106_ac_a)); - - /* Reset NFCA target state */ - st25r3916ExecuteCommand(ST25R3916_CMD_GOTO_SLEEP); - - /* Set Target mode, Bit Rate detection, Listen Mode for NFC-A and NFC-F */ - st25r3916ChangeRegisterBits( - ST25R3916_REG_MODE, - (ST25R3916_REG_MODE_targ | ST25R3916_REG_MODE_om_mask | - ST25R3916_REG_MODE_nfc_ar_mask), - (ST25R3916_REG_MODE_targ_targ | ST25R3916_REG_MODE_om3 | ST25R3916_REG_MODE_om2 | - ST25R3916_REG_MODE_om0 | ST25R3916_REG_MODE_nfc_ar_off)); - break; - - /*******************************************************************************/ - case RFAL_LM_STATE_SLEEP_B: - /* REMARK: Support for CE-B would be added here */ - return ERR_NOT_IMPLEMENTED; - - /*******************************************************************************/ - default: - return ERR_PARAM; - } - - /* Ensure that the NFCIP1 mode is disabled */ - st25r3916ClrRegisterBits(ST25R3916_REG_ISO14443A_NFC, ST25R3916_REG_ISO14443A_NFC_nfc_f0); - - st25r3916ExecuteCommand(ST25R3916_CMD_UNMASK_RECEIVE_DATA); - - /* Clear and enable required IRQs */ - st25r3916ClearAndEnableInterrupts( - (ST25R3916_IRQ_MASK_NFCT | ST25R3916_IRQ_MASK_RXS | ST25R3916_IRQ_MASK_CRC | - ST25R3916_IRQ_MASK_ERR1 | ST25R3916_IRQ_MASK_ERR2 | ST25R3916_IRQ_MASK_PAR | - ST25R3916_IRQ_MASK_EON | ST25R3916_IRQ_MASK_EOF | gRFAL.Lm.mdIrqs)); - - /* Check whether the field was turn off right after the Sleep request */ - if(!rfalIsExtFieldOn()) { - /*rfalLogD( "RFAL: curState: %02X newState: %02X \r\n", gRFAL.Lm.state, RFAL_LM_STATE_NOT_INIT );*/ - - rfalListenStop(); - return ERR_LINK_LOSS; - } - - /*rfalLogD( "RFAL: curState: %02X newState: %02X \r\n", gRFAL.Lm.state, sleepSt );*/ - - /* Set the new Sleep State*/ - gRFAL.Lm.state = sleepSt; - gRFAL.state = RFAL_STATE_LM; - - gRFAL.Lm.rxBuf = rxBuf; - gRFAL.Lm.rxBufLen = rxBufLen; - gRFAL.Lm.rxLen = rxLen; - *gRFAL.Lm.rxLen = 0; - gRFAL.Lm.dataFlag = false; - - return ERR_NONE; -} - -/*******************************************************************************/ -rfalLmState rfalListenGetState(bool* dataFlag, rfalBitRate* lastBR) { - /* Allow state retrieval even if gRFAL.state != RFAL_STATE_LM so * - * that this Lm state can be used by caller after activation */ - - if(lastBR != NULL) { - *lastBR = gRFAL.Lm.brDetected; - } - - if(dataFlag != NULL) { - *dataFlag = gRFAL.Lm.dataFlag; - } - - return gRFAL.Lm.state; -} - -/*******************************************************************************/ -ReturnCode rfalListenSetState(rfalLmState newSt) { - ReturnCode ret; - rfalLmState newState; - bool reSetState; - - /* Check if RFAL is initialized */ - if(gRFAL.state < RFAL_STATE_INIT) { - return ERR_WRONG_STATE; - } - - /* SetState clears the Data flag */ - gRFAL.Lm.dataFlag = false; - newState = newSt; - ret = ERR_NONE; - - do { - reSetState = false; - - /*******************************************************************************/ - switch(newState) { - /*******************************************************************************/ - case RFAL_LM_STATE_POWER_OFF: - - /* Enable the receiver and reset logic */ - st25r3916SetRegisterBits(ST25R3916_REG_OP_CONTROL, ST25R3916_REG_OP_CONTROL_rx_en); - st25r3916ExecuteCommand(ST25R3916_CMD_STOP); - - if((gRFAL.Lm.mdMask & RFAL_LM_MASK_NFCA) != 0U) { - /* Enable automatic responses for A */ - st25r3916ClrRegisterBits( - ST25R3916_REG_PASSIVE_TARGET, ST25R3916_REG_PASSIVE_TARGET_d_106_ac_a); - - /* Prepares the NFCIP-1 Passive target logic to wait in the Sense/Idle state */ - st25r3916ExecuteCommand(ST25R3916_CMD_GOTO_SENSE); - } - - if((gRFAL.Lm.mdMask & RFAL_LM_MASK_NFCF) != 0U) { - /* Enable automatic responses for F */ - st25r3916ClrRegisterBits( - ST25R3916_REG_PASSIVE_TARGET, (ST25R3916_REG_PASSIVE_TARGET_d_212_424_1r)); - } - - if((gRFAL.Lm.mdMask & RFAL_LM_MASK_ACTIVE_P2P) != 0U) { - /* Ensure automatic response RF Collision Avoidance is back to only after Rx */ - st25r3916ChangeRegisterBits( - ST25R3916_REG_MODE, - ST25R3916_REG_MODE_nfc_ar_mask, - ST25R3916_REG_MODE_nfc_ar_auto_rx); - - /* Ensure that our field is Off, as automatic response RF Collision Avoidance may have been triggered */ - st25r3916TxOff(); - } - - /*******************************************************************************/ - /* Ensure that the NFCIP1 mode is disabled */ - st25r3916ClrRegisterBits( - ST25R3916_REG_ISO14443A_NFC, ST25R3916_REG_ISO14443A_NFC_nfc_f0); - - /*******************************************************************************/ - /* Clear and enable required IRQs */ - st25r3916DisableInterrupts(ST25R3916_IRQ_MASK_ALL); - - st25r3916ClearAndEnableInterrupts( - (ST25R3916_IRQ_MASK_NFCT | ST25R3916_IRQ_MASK_RXS | ST25R3916_IRQ_MASK_CRC | - ST25R3916_IRQ_MASK_ERR1 | ST25R3916_IRQ_MASK_OSC | ST25R3916_IRQ_MASK_ERR2 | - ST25R3916_IRQ_MASK_PAR | ST25R3916_IRQ_MASK_EON | ST25R3916_IRQ_MASK_EOF | - gRFAL.Lm.mdIrqs)); - - /*******************************************************************************/ - /* Clear the bitRate previously detected */ - gRFAL.Lm.brDetected = RFAL_BR_KEEP; - - /*******************************************************************************/ - /* Apply the initial mode */ - st25r3916ChangeRegisterBits( - ST25R3916_REG_MODE, - (ST25R3916_REG_MODE_targ | ST25R3916_REG_MODE_om_mask | - ST25R3916_REG_MODE_nfc_ar_mask), - (uint8_t)gRFAL.Lm.mdReg); - - /*******************************************************************************/ - /* Check if external Field is already On */ - if(rfalIsExtFieldOn()) { - reSetState = true; - newState = RFAL_LM_STATE_IDLE; /* Set IDLE state */ - } -#if 1 /* Perform bit rate detection in Low power mode */ - else { - st25r3916ClrRegisterBits( - ST25R3916_REG_OP_CONTROL, - (ST25R3916_REG_OP_CONTROL_tx_en | ST25R3916_REG_OP_CONTROL_rx_en | - ST25R3916_REG_OP_CONTROL_en)); - } -#endif - break; - - /*******************************************************************************/ - case RFAL_LM_STATE_IDLE: - - /*******************************************************************************/ - /* Check if device is coming from Low Power bit rate detection */ - if(!st25r3916CheckReg( - ST25R3916_REG_OP_CONTROL, - ST25R3916_REG_OP_CONTROL_en, - ST25R3916_REG_OP_CONTROL_en)) { - /* Exit Low Power mode and confirm the temporarily enable */ - st25r3916SetRegisterBits( - ST25R3916_REG_OP_CONTROL, - (ST25R3916_REG_OP_CONTROL_en | ST25R3916_REG_OP_CONTROL_rx_en)); - - if(!st25r3916CheckReg( - ST25R3916_REG_AUX_DISPLAY, - ST25R3916_REG_AUX_DISPLAY_osc_ok, - ST25R3916_REG_AUX_DISPLAY_osc_ok)) { - /* Wait for Oscillator ready */ - if(st25r3916WaitForInterruptsTimed( - ST25R3916_IRQ_MASK_OSC, ST25R3916_TOUT_OSC_STABLE) == 0U) { - ret = ERR_IO; - break; - } - } - } else { - st25r3916GetInterrupt(ST25R3916_IRQ_MASK_OSC); - } - - /*******************************************************************************/ - /* In Active P2P the Initiator may: Turn its field On; LM goes into IDLE state; - * Initiator sends an unexpected frame raising a Protocol error; Initiator - * turns its field Off and ST25R3916 performs the automatic RF Collision - * Avoidance keeping our field On; upon a Protocol error upper layer sets - * again the state to IDLE to clear dataFlag and wait for next data. - * - * Ensure that when upper layer calls SetState(IDLE), it restores initial - * configuration and that check whether an external Field is still present */ - if((gRFAL.Lm.mdMask & RFAL_LM_MASK_ACTIVE_P2P) != 0U) { - /* Ensure nfc_ar is reset and back to only after Rx */ - st25r3916ExecuteCommand(ST25R3916_CMD_STOP); - st25r3916ChangeRegisterBits( - ST25R3916_REG_MODE, - ST25R3916_REG_MODE_nfc_ar_mask, - ST25R3916_REG_MODE_nfc_ar_auto_rx); - - /* Ensure that our field is Off, as automatic response RF Collision Avoidance may have been triggered */ - st25r3916TxOff(); - - /* If external Field is no longer detected go back to POWER_OFF */ - if(!st25r3916IsExtFieldOn()) { - reSetState = true; - newState = RFAL_LM_STATE_POWER_OFF; /* Set POWER_OFF state */ - } - } - /*******************************************************************************/ - - /* If we are in ACTIVE_A, reEnable Listen for A before going to IDLE, otherwise do nothing */ - if(gRFAL.Lm.state == RFAL_LM_STATE_ACTIVE_A) { - /* Enable automatic responses for A and Reset NFCA target state */ - st25r3916ClrRegisterBits( - ST25R3916_REG_PASSIVE_TARGET, (ST25R3916_REG_PASSIVE_TARGET_d_106_ac_a)); - st25r3916ExecuteCommand(ST25R3916_CMD_GOTO_SENSE); - } - - /* ReEnable the receiver */ - st25r3916ExecuteCommand(ST25R3916_CMD_CLEAR_FIFO); - st25r3916ExecuteCommand(ST25R3916_CMD_UNMASK_RECEIVE_DATA); - - /*******************************************************************************/ - /*Check if Observation Mode is enabled and set it on ST25R391x */ - rfalCheckEnableObsModeRx(); - break; - - /*******************************************************************************/ - case RFAL_LM_STATE_READY_F: - - /*******************************************************************************/ - /* If we're coming from BitRate detection mode, the Bit Rate Definition reg - * still has the last bit rate used. - * If a frame is received between setting the mode to Listen NFCA and - * setting Bit Rate Definition reg, it will raise a framing error. - * Set the bitrate immediately, and then the normal SetMode procedure */ - st25r3916SetBitrate((uint8_t)gRFAL.Lm.brDetected, (uint8_t)gRFAL.Lm.brDetected); - /*******************************************************************************/ - - /* Disable automatic responses for NFC-A */ - st25r3916SetRegisterBits( - ST25R3916_REG_PASSIVE_TARGET, (ST25R3916_REG_PASSIVE_TARGET_d_106_ac_a)); - - /* Set Mode NFC-F only */ - ret = rfalSetMode(RFAL_MODE_LISTEN_NFCF, gRFAL.Lm.brDetected, gRFAL.Lm.brDetected); - gRFAL.state = RFAL_STATE_LM; /* Keep in Listen Mode */ - - /* ReEnable the receiver */ - st25r3916ExecuteCommand(ST25R3916_CMD_CLEAR_FIFO); - st25r3916ExecuteCommand(ST25R3916_CMD_UNMASK_RECEIVE_DATA); - - /* Clear any previous transmission errors (if Reader polled for other/unsupported technologies) */ - st25r3916GetInterrupt( - (ST25R3916_IRQ_MASK_PAR | ST25R3916_IRQ_MASK_CRC | ST25R3916_IRQ_MASK_ERR2 | - ST25R3916_IRQ_MASK_ERR1)); - - st25r3916EnableInterrupts( - ST25R3916_IRQ_MASK_RXE); /* Start looking for any incoming data */ - break; - - /*******************************************************************************/ - case RFAL_LM_STATE_CARDEMU_3: - - /* Set Listen NFCF mode */ - ret = rfalSetMode(RFAL_MODE_LISTEN_NFCF, gRFAL.Lm.brDetected, gRFAL.Lm.brDetected); - break; - - /*******************************************************************************/ - case RFAL_LM_STATE_READY_Ax: - case RFAL_LM_STATE_READY_A: - - /*******************************************************************************/ - /* If we're coming from BitRate detection mode, the Bit Rate Definition reg - * still has the last bit rate used. - * If a frame is received between setting the mode to Listen NFCA and - * setting Bit Rate Definition reg, it will raise a framing error. - * Set the bitrate immediately, and then the normal SetMode procedure */ - st25r3916SetBitrate((uint8_t)gRFAL.Lm.brDetected, (uint8_t)gRFAL.Lm.brDetected); - /*******************************************************************************/ - - /* Disable automatic responses for NFC-F */ - st25r3916SetRegisterBits( - ST25R3916_REG_PASSIVE_TARGET, (ST25R3916_REG_PASSIVE_TARGET_d_212_424_1r)); - - /* Set Mode NFC-A only */ - ret = rfalSetMode(RFAL_MODE_LISTEN_NFCA, gRFAL.Lm.brDetected, gRFAL.Lm.brDetected); - - gRFAL.state = RFAL_STATE_LM; /* Keep in Listen Mode */ - break; - - /*******************************************************************************/ - case RFAL_LM_STATE_ACTIVE_Ax: - case RFAL_LM_STATE_ACTIVE_A: - - /* Disable automatic responses for A */ - st25r3916SetRegisterBits( - ST25R3916_REG_PASSIVE_TARGET, (ST25R3916_REG_PASSIVE_TARGET_d_106_ac_a)); - - /* Clear any previous transmission errors (if Reader polled for other/unsupported technologies) */ - st25r3916GetInterrupt( - (ST25R3916_IRQ_MASK_PAR | ST25R3916_IRQ_MASK_CRC | ST25R3916_IRQ_MASK_ERR2 | - ST25R3916_IRQ_MASK_ERR1)); - - st25r3916EnableInterrupts( - ST25R3916_IRQ_MASK_RXE); /* Start looking for any incoming data */ - break; - - case RFAL_LM_STATE_TARGET_F: - /* Disable Automatic response SENSF_REQ */ - st25r3916SetRegisterBits( - ST25R3916_REG_PASSIVE_TARGET, (ST25R3916_REG_PASSIVE_TARGET_d_212_424_1r)); - break; - - /*******************************************************************************/ - case RFAL_LM_STATE_SLEEP_A: - case RFAL_LM_STATE_SLEEP_B: - case RFAL_LM_STATE_SLEEP_AF: - /* These sleep states have to be set by the rfalListenSleepStart() method */ - return ERR_REQUEST; - - /*******************************************************************************/ - case RFAL_LM_STATE_CARDEMU_4A: - case RFAL_LM_STATE_CARDEMU_4B: - case RFAL_LM_STATE_TARGET_A: - /* States not handled by the LM, just keep state context */ - break; - - /*******************************************************************************/ - default: - return ERR_WRONG_STATE; - } - } while(reSetState); - - gRFAL.Lm.state = newState; - - // Call callback on state change - if(gRFAL.callbacks.state_changed_cb) { - gRFAL.callbacks.state_changed_cb(gRFAL.callbacks.ctx); - } - - return ret; -} - -#endif /* RFAL_FEATURE_LISTEN_MODE */ - -/******************************************************************************* - * Wake-Up Mode * - *******************************************************************************/ - -#if RFAL_FEATURE_WAKEUP_MODE - -/*******************************************************************************/ -ReturnCode rfalWakeUpModeStart(const rfalWakeUpConfig* config) { - uint8_t aux; - uint8_t reg; - uint32_t irqs; - - /* Check if RFAL is not initialized */ - if(gRFAL.state < RFAL_STATE_INIT) { - return ERR_WRONG_STATE; - } - - /* The Wake-Up procedure is explained in detail in Application Note: AN4985 */ - - if(config == NULL) { - gRFAL.wum.cfg.period = RFAL_WUM_PERIOD_200MS; - gRFAL.wum.cfg.irqTout = false; - gRFAL.wum.cfg.indAmp.enabled = true; - gRFAL.wum.cfg.indPha.enabled = false; - gRFAL.wum.cfg.cap.enabled = false; - gRFAL.wum.cfg.indAmp.delta = 2U; - gRFAL.wum.cfg.indAmp.reference = RFAL_WUM_REFERENCE_AUTO; - gRFAL.wum.cfg.indAmp.autoAvg = false; - - /*******************************************************************************/ - /* Check if AAT is enabled and if so make use of the SW Tag Detection */ - if(st25r3916CheckReg( - ST25R3916_REG_IO_CONF2, - ST25R3916_REG_IO_CONF2_aat_en, - ST25R3916_REG_IO_CONF2_aat_en)) { - gRFAL.wum.cfg.swTagDetect = true; - gRFAL.wum.cfg.indAmp.autoAvg = true; - gRFAL.wum.cfg.indAmp.aaWeight = RFAL_WUM_AA_WEIGHT_16; - } - } else { - gRFAL.wum.cfg = *config; - } - - /* Check for valid configuration */ - if((!gRFAL.wum.cfg.cap.enabled && !gRFAL.wum.cfg.indAmp.enabled && - !gRFAL.wum.cfg.indPha.enabled) || - (gRFAL.wum.cfg.cap.enabled && - (gRFAL.wum.cfg.indAmp.enabled || gRFAL.wum.cfg.indPha.enabled)) || - (gRFAL.wum.cfg.cap.enabled && gRFAL.wum.cfg.swTagDetect) || - ((gRFAL.wum.cfg.indAmp.reference > RFAL_WUM_REFERENCE_AUTO) || - (gRFAL.wum.cfg.indPha.reference > RFAL_WUM_REFERENCE_AUTO) || - (gRFAL.wum.cfg.cap.reference > RFAL_WUM_REFERENCE_AUTO))) { - return ERR_PARAM; - } - - irqs = ST25R3916_IRQ_MASK_NONE; - - /* Disable Tx, Rx, External Field Detector and set default ISO14443A mode */ - st25r3916TxRxOff(); - st25r3916ClrRegisterBits(ST25R3916_REG_OP_CONTROL, ST25R3916_REG_OP_CONTROL_en_fd_mask); - st25r3916ChangeRegisterBits( - ST25R3916_REG_MODE, - (ST25R3916_REG_MODE_targ | ST25R3916_REG_MODE_om_mask), - (ST25R3916_REG_MODE_targ_init | ST25R3916_REG_MODE_om_iso14443a)); - - /* Set Analog configurations for Wake-up On event */ - rfalSetAnalogConfig((RFAL_ANALOG_CONFIG_TECH_CHIP | RFAL_ANALOG_CONFIG_CHIP_WAKEUP_ON)); - - /*******************************************************************************/ - /* Prepare Wake-Up Timer Control Register */ - reg = - (uint8_t)(((uint8_t)gRFAL.wum.cfg.period & 0x0FU) << ST25R3916_REG_WUP_TIMER_CONTROL_wut_shift); - reg |= - (uint8_t)(((uint8_t)gRFAL.wum.cfg.period < (uint8_t)RFAL_WUM_PERIOD_100MS) ? ST25R3916_REG_WUP_TIMER_CONTROL_wur : 0x00U); - - if(gRFAL.wum.cfg.irqTout || gRFAL.wum.cfg.swTagDetect) { - reg |= ST25R3916_REG_WUP_TIMER_CONTROL_wto; - irqs |= ST25R3916_IRQ_MASK_WT; - } - - /* Check if HW Wake-up is to be used or SW Tag detection */ - if(gRFAL.wum.cfg.swTagDetect) { - gRFAL.wum.cfg.indAmp.reference = 0U; - gRFAL.wum.cfg.indPha.reference = 0U; - gRFAL.wum.cfg.cap.reference = 0U; - } else { - /*******************************************************************************/ - /* Check if Inductive Amplitude is to be performed */ - if(gRFAL.wum.cfg.indAmp.enabled) { - aux = - (uint8_t)((gRFAL.wum.cfg.indAmp.delta) << ST25R3916_REG_AMPLITUDE_MEASURE_CONF_am_d_shift); - aux |= - (uint8_t)(gRFAL.wum.cfg.indAmp.aaInclMeas ? ST25R3916_REG_AMPLITUDE_MEASURE_CONF_am_aam : 0x00U); - aux |= - (uint8_t)(((uint8_t)gRFAL.wum.cfg.indAmp.aaWeight << ST25R3916_REG_AMPLITUDE_MEASURE_CONF_am_aew_shift) & ST25R3916_REG_AMPLITUDE_MEASURE_CONF_am_aew_mask); - aux |= - (uint8_t)(gRFAL.wum.cfg.indAmp.autoAvg ? ST25R3916_REG_AMPLITUDE_MEASURE_CONF_am_ae : 0x00U); - - st25r3916WriteRegister(ST25R3916_REG_AMPLITUDE_MEASURE_CONF, aux); - - /* Only need to set the reference if not using Auto Average */ - if(!gRFAL.wum.cfg.indAmp.autoAvg) { - if(gRFAL.wum.cfg.indAmp.reference == RFAL_WUM_REFERENCE_AUTO) { - st25r3916MeasureAmplitude(&aux); - gRFAL.wum.cfg.indAmp.reference = aux; - } - st25r3916WriteRegister( - ST25R3916_REG_AMPLITUDE_MEASURE_REF, (uint8_t)gRFAL.wum.cfg.indAmp.reference); - } - - reg |= ST25R3916_REG_WUP_TIMER_CONTROL_wam; - irqs |= ST25R3916_IRQ_MASK_WAM; - } - - /*******************************************************************************/ - /* Check if Inductive Phase is to be performed */ - if(gRFAL.wum.cfg.indPha.enabled) { - aux = - (uint8_t)((gRFAL.wum.cfg.indPha.delta) << ST25R3916_REG_PHASE_MEASURE_CONF_pm_d_shift); - aux |= - (uint8_t)(gRFAL.wum.cfg.indPha.aaInclMeas ? ST25R3916_REG_PHASE_MEASURE_CONF_pm_aam : 0x00U); - aux |= - (uint8_t)(((uint8_t)gRFAL.wum.cfg.indPha.aaWeight << ST25R3916_REG_PHASE_MEASURE_CONF_pm_aew_shift) & ST25R3916_REG_PHASE_MEASURE_CONF_pm_aew_mask); - aux |= - (uint8_t)(gRFAL.wum.cfg.indPha.autoAvg ? ST25R3916_REG_PHASE_MEASURE_CONF_pm_ae : 0x00U); - - st25r3916WriteRegister(ST25R3916_REG_PHASE_MEASURE_CONF, aux); - - /* Only need to set the reference if not using Auto Average */ - if(!gRFAL.wum.cfg.indPha.autoAvg) { - if(gRFAL.wum.cfg.indPha.reference == RFAL_WUM_REFERENCE_AUTO) { - st25r3916MeasurePhase(&aux); - gRFAL.wum.cfg.indPha.reference = aux; - } - st25r3916WriteRegister( - ST25R3916_REG_PHASE_MEASURE_REF, (uint8_t)gRFAL.wum.cfg.indPha.reference); - } - - reg |= ST25R3916_REG_WUP_TIMER_CONTROL_wph; - irqs |= ST25R3916_IRQ_MASK_WPH; - } - - /*******************************************************************************/ - /* Check if Capacitive is to be performed */ - if(gRFAL.wum.cfg.cap.enabled) { - /*******************************************************************************/ - /* Perform Capacitive sensor calibration */ - - /* Disable Oscillator and Field */ - st25r3916ClrRegisterBits( - ST25R3916_REG_OP_CONTROL, - (ST25R3916_REG_OP_CONTROL_en | ST25R3916_REG_OP_CONTROL_tx_en)); - - /* Sensor gain should be configured on Analog Config: RFAL_ANALOG_CONFIG_CHIP_WAKEUP_ON */ - - /* Perform calibration procedure */ - st25r3916CalibrateCapacitiveSensor(NULL); - - /*******************************************************************************/ - aux = - (uint8_t)((gRFAL.wum.cfg.cap.delta) << ST25R3916_REG_CAPACITANCE_MEASURE_CONF_cm_d_shift); - aux |= - (uint8_t)(gRFAL.wum.cfg.cap.aaInclMeas ? ST25R3916_REG_CAPACITANCE_MEASURE_CONF_cm_aam : 0x00U); - aux |= - (uint8_t)(((uint8_t)gRFAL.wum.cfg.cap.aaWeight << ST25R3916_REG_CAPACITANCE_MEASURE_CONF_cm_aew_shift) & ST25R3916_REG_CAPACITANCE_MEASURE_CONF_cm_aew_mask); - aux |= - (uint8_t)(gRFAL.wum.cfg.cap.autoAvg ? ST25R3916_REG_CAPACITANCE_MEASURE_CONF_cm_ae : 0x00U); - - st25r3916WriteRegister(ST25R3916_REG_CAPACITANCE_MEASURE_CONF, aux); - - /* Only need to set the reference if not using Auto Average */ - if(!gRFAL.wum.cfg.cap.autoAvg || gRFAL.wum.cfg.swTagDetect) { - if(gRFAL.wum.cfg.indPha.reference == RFAL_WUM_REFERENCE_AUTO) { - st25r3916MeasureCapacitance(&aux); - gRFAL.wum.cfg.cap.reference = aux; - } - st25r3916WriteRegister( - ST25R3916_REG_CAPACITANCE_MEASURE_REF, (uint8_t)gRFAL.wum.cfg.cap.reference); - } - - reg |= ST25R3916_REG_WUP_TIMER_CONTROL_wcap; - irqs |= ST25R3916_IRQ_MASK_WCAP; - } - } - - /* Disable and clear all interrupts except Wake-Up IRQs */ - st25r3916DisableInterrupts(ST25R3916_IRQ_MASK_ALL); - st25r3916GetInterrupt(irqs); - st25r3916EnableInterrupts(irqs); - - /* Enable Low Power Wake-Up Mode (Disable: Oscilattor, Tx, Rx and External Field Detector) */ - st25r3916WriteRegister(ST25R3916_REG_WUP_TIMER_CONTROL, reg); - st25r3916ChangeRegisterBits( - ST25R3916_REG_OP_CONTROL, - (ST25R3916_REG_OP_CONTROL_en | ST25R3916_REG_OP_CONTROL_rx_en | - ST25R3916_REG_OP_CONTROL_tx_en | ST25R3916_REG_OP_CONTROL_en_fd_mask | - ST25R3916_REG_OP_CONTROL_wu), - ST25R3916_REG_OP_CONTROL_wu); - - gRFAL.wum.state = RFAL_WUM_STATE_ENABLED; - gRFAL.state = RFAL_STATE_WUM; - - return ERR_NONE; -} - -/*******************************************************************************/ -bool rfalWakeUpModeHasWoke(void) { - return (gRFAL.wum.state >= RFAL_WUM_STATE_ENABLED_WOKE); -} - -/*******************************************************************************/ -static uint16_t rfalWakeUpModeFilter(uint16_t curRef, uint16_t curVal, uint8_t weight) { - uint16_t newRef; - - /* Perform the averaging|filter as describded in ST25R3916 DS */ - - /* Avoid signed arithmetics by splitting in two cases */ - if(curVal > curRef) { - newRef = curRef + ((curVal - curRef) / weight); - - /* In order for the reference to converge to final value * - * increment once the diff is smaller that the weight */ - if((curVal != curRef) && (curRef == newRef)) { - newRef &= 0xFF00U; - newRef += 0x0100U; - } - } else { - newRef = curRef - ((curRef - curVal) / weight); - - /* In order for the reference to converge to final value * - * decrement once the diff is smaller that the weight */ - if((curVal != curRef) && (curRef == newRef)) { - newRef &= 0xFF00U; - } - } - - return newRef; -} - -/*******************************************************************************/ -static void rfalRunWakeUpModeWorker(void) { - uint32_t irqs; - uint8_t reg; - uint16_t value; - uint16_t delta; - - if(gRFAL.state != RFAL_STATE_WUM) { - return; - } - - switch(gRFAL.wum.state) { - case RFAL_WUM_STATE_ENABLED: - case RFAL_WUM_STATE_ENABLED_WOKE: - - irqs = st25r3916GetInterrupt( - (ST25R3916_IRQ_MASK_WT | ST25R3916_IRQ_MASK_WAM | ST25R3916_IRQ_MASK_WPH | - ST25R3916_IRQ_MASK_WCAP)); - if(irqs == ST25R3916_IRQ_MASK_NONE) { - break; /* No interrupt to process */ - } - - /*******************************************************************************/ - /* Check and mark which measurement(s) cause interrupt */ - if((irqs & ST25R3916_IRQ_MASK_WAM) != 0U) { - st25r3916ReadRegister(ST25R3916_REG_AMPLITUDE_MEASURE_RESULT, ®); - gRFAL.wum.state = RFAL_WUM_STATE_ENABLED_WOKE; - } - - if((irqs & ST25R3916_IRQ_MASK_WPH) != 0U) { - st25r3916ReadRegister(ST25R3916_REG_PHASE_MEASURE_RESULT, ®); - gRFAL.wum.state = RFAL_WUM_STATE_ENABLED_WOKE; - } - - if((irqs & ST25R3916_IRQ_MASK_WCAP) != 0U) { - st25r3916ReadRegister(ST25R3916_REG_CAPACITANCE_MEASURE_RESULT, ®); - gRFAL.wum.state = RFAL_WUM_STATE_ENABLED_WOKE; - } - - if((irqs & ST25R3916_IRQ_MASK_WT) != 0U) { - /*******************************************************************************/ - if(gRFAL.wum.cfg.swTagDetect) { - /* Enable Ready mode and wait the settle time */ - st25r3916ChangeRegisterBits( - ST25R3916_REG_OP_CONTROL, - (ST25R3916_REG_OP_CONTROL_en | ST25R3916_REG_OP_CONTROL_wu), - ST25R3916_REG_OP_CONTROL_en); - platformDelay(RFAL_ST25R3916_AAT_SETTLE); - - /*******************************************************************************/ - if(gRFAL.wum.cfg.indAmp.enabled) { - /* Perform amplitude measurement */ - st25r3916MeasureAmplitude(®); - - /* Convert inputs to TD format */ - value = rfalConvTDFormat(reg); - delta = rfalConvTDFormat(gRFAL.wum.cfg.indAmp.delta); - - /* Set first measurement as reference */ - if(gRFAL.wum.cfg.indAmp.reference == 0U) { - gRFAL.wum.cfg.indAmp.reference = value; - } - - /* Check if device should be woken */ - if((value >= (gRFAL.wum.cfg.indAmp.reference + delta)) || - (value <= (gRFAL.wum.cfg.indAmp.reference - delta))) { - gRFAL.wum.state = RFAL_WUM_STATE_ENABLED_WOKE; - break; - } - - /* Update moving reference if enabled */ - if(gRFAL.wum.cfg.indAmp.autoAvg) { - gRFAL.wum.cfg.indAmp.reference = rfalWakeUpModeFilter( - gRFAL.wum.cfg.indAmp.reference, - value, - (RFAL_WU_MIN_WEIGHT_VAL << (uint8_t)gRFAL.wum.cfg.indAmp.aaWeight)); - } - } - - /*******************************************************************************/ - if(gRFAL.wum.cfg.indPha.enabled) { - /* Perform Phase measurement */ - st25r3916MeasurePhase(®); - - /* Convert inputs to TD format */ - value = rfalConvTDFormat(reg); - delta = rfalConvTDFormat(gRFAL.wum.cfg.indPha.delta); - - /* Set first measurement as reference */ - if(gRFAL.wum.cfg.indPha.reference == 0U) { - gRFAL.wum.cfg.indPha.reference = value; - } - - /* Check if device should be woken */ - if((value >= (gRFAL.wum.cfg.indPha.reference + delta)) || - (value <= (gRFAL.wum.cfg.indPha.reference - delta))) { - gRFAL.wum.state = RFAL_WUM_STATE_ENABLED_WOKE; - break; - } - - /* Update moving reference if enabled */ - if(gRFAL.wum.cfg.indPha.autoAvg) { - gRFAL.wum.cfg.indPha.reference = rfalWakeUpModeFilter( - gRFAL.wum.cfg.indPha.reference, - value, - (RFAL_WU_MIN_WEIGHT_VAL << (uint8_t)gRFAL.wum.cfg.indPha.aaWeight)); - } - } - - /* Re-Enable low power Wake-Up mode for wto to trigger another measurement(s) */ - st25r3916ChangeRegisterBits( - ST25R3916_REG_OP_CONTROL, - (ST25R3916_REG_OP_CONTROL_en | ST25R3916_REG_OP_CONTROL_wu), - (ST25R3916_REG_OP_CONTROL_wu)); - } - } - break; - - default: - /* MISRA 16.4: no empty default statement (a comment being enough) */ - break; - } -} - -/*******************************************************************************/ -ReturnCode rfalWakeUpModeStop(void) { - /* Check if RFAL is in Wake-up mode */ - if(gRFAL.state != RFAL_STATE_WUM) { - return ERR_WRONG_STATE; - } - - gRFAL.wum.state = RFAL_WUM_STATE_NOT_INIT; - - /* Disable Wake-Up Mode */ - st25r3916ClrRegisterBits(ST25R3916_REG_OP_CONTROL, ST25R3916_REG_OP_CONTROL_wu); - st25r3916DisableInterrupts( - (ST25R3916_IRQ_MASK_WT | ST25R3916_IRQ_MASK_WAM | ST25R3916_IRQ_MASK_WPH | - ST25R3916_IRQ_MASK_WCAP)); - - /* Re-Enable External Field Detector as: Automatics */ - st25r3916ChangeRegisterBits( - ST25R3916_REG_OP_CONTROL, - ST25R3916_REG_OP_CONTROL_en_fd_mask, - ST25R3916_REG_OP_CONTROL_en_fd_auto_efd); - - /* Re-Enable the Oscillator */ - st25r3916OscOn(); - - /* Set Analog configurations for Wake-up Off event */ - rfalSetAnalogConfig((RFAL_ANALOG_CONFIG_TECH_CHIP | RFAL_ANALOG_CONFIG_CHIP_WAKEUP_OFF)); - - return ERR_NONE; -} - -#endif /* RFAL_FEATURE_WAKEUP_MODE */ - -/******************************************************************************* - * Low-Power Mode * - *******************************************************************************/ - -#if RFAL_FEATURE_LOWPOWER_MODE - -/*******************************************************************************/ -ReturnCode rfalLowPowerModeStart(void) { - /* Check if RFAL is not initialized */ - if(gRFAL.state < RFAL_STATE_INIT) { - return ERR_WRONG_STATE; - } - - /* Stop any ongoing activity and set the device in low power by disabling oscillator, transmitter, receiver and external field detector */ - st25r3916ExecuteCommand(ST25R3916_CMD_STOP); - st25r3916ClrRegisterBits( - ST25R3916_REG_OP_CONTROL, - (ST25R3916_REG_OP_CONTROL_en | ST25R3916_REG_OP_CONTROL_rx_en | - ST25R3916_REG_OP_CONTROL_wu | ST25R3916_REG_OP_CONTROL_tx_en | - ST25R3916_REG_OP_CONTROL_en_fd_mask)); - - rfalSetAnalogConfig((RFAL_ANALOG_CONFIG_TECH_CHIP | RFAL_ANALOG_CONFIG_CHIP_LOWPOWER_ON)); - - gRFAL.state = RFAL_STATE_IDLE; - gRFAL.lpm.isRunning = true; - - platformDisableIrqCallback(); - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalLowPowerModeStop(void) { - ReturnCode ret; - - platformEnableIrqCallback(); - - /* Check if RFAL is on right state */ - if(!gRFAL.lpm.isRunning) { - return ERR_WRONG_STATE; - } - - /* Re-enable device */ - EXIT_ON_ERR(ret, st25r3916OscOn()); - st25r3916ChangeRegisterBits( - ST25R3916_REG_OP_CONTROL, - ST25R3916_REG_OP_CONTROL_en_fd_mask, - ST25R3916_REG_OP_CONTROL_en_fd_auto_efd); - - rfalSetAnalogConfig((RFAL_ANALOG_CONFIG_TECH_CHIP | RFAL_ANALOG_CONFIG_CHIP_LOWPOWER_OFF)); - - gRFAL.state = RFAL_STATE_INIT; - return ERR_NONE; -} - -#endif /* RFAL_FEATURE_LOWPOWER_MODE */ - -/******************************************************************************* - * RF Chip * - *******************************************************************************/ - -/*******************************************************************************/ -ReturnCode rfalChipWriteReg(uint16_t reg, const uint8_t* values, uint8_t len) { - if(!st25r3916IsRegValid((uint8_t)reg)) { - return ERR_PARAM; - } - - return st25r3916WriteMultipleRegisters((uint8_t)reg, values, len); -} - -/*******************************************************************************/ -ReturnCode rfalChipReadReg(uint16_t reg, uint8_t* values, uint8_t len) { - if(!st25r3916IsRegValid((uint8_t)reg)) { - return ERR_PARAM; - } - - return st25r3916ReadMultipleRegisters((uint8_t)reg, values, len); -} - -/*******************************************************************************/ -ReturnCode rfalChipExecCmd(uint16_t cmd) { - if(!st25r3916IsCmdValid((uint8_t)cmd)) { - return ERR_PARAM; - } - - return st25r3916ExecuteCommand((uint8_t)cmd); -} - -/*******************************************************************************/ -ReturnCode rfalChipWriteTestReg(uint16_t reg, uint8_t value) { - return st25r3916WriteTestRegister((uint8_t)reg, value); -} - -/*******************************************************************************/ -ReturnCode rfalChipReadTestReg(uint16_t reg, uint8_t* value) { - return st25r3916ReadTestRegister((uint8_t)reg, value); -} - -/*******************************************************************************/ -ReturnCode rfalChipChangeRegBits(uint16_t reg, uint8_t valueMask, uint8_t value) { - if(!st25r3916IsRegValid((uint8_t)reg)) { - return ERR_PARAM; - } - - return st25r3916ChangeRegisterBits((uint8_t)reg, valueMask, value); -} - -/*******************************************************************************/ -ReturnCode rfalChipChangeTestRegBits(uint16_t reg, uint8_t valueMask, uint8_t value) { - st25r3916ChangeTestRegisterBits((uint8_t)reg, valueMask, value); - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalChipSetRFO(uint8_t rfo) { - return st25r3916ChangeRegisterBits( - ST25R3916_REG_TX_DRIVER, ST25R3916_REG_TX_DRIVER_d_res_mask, rfo); -} - -/*******************************************************************************/ -ReturnCode rfalChipGetRFO(uint8_t* result) { - ReturnCode ret; - - ret = st25r3916ReadRegister(ST25R3916_REG_TX_DRIVER, result); - - (*result) = ((*result) & ST25R3916_REG_TX_DRIVER_d_res_mask); - - return ret; -} - -/*******************************************************************************/ -ReturnCode rfalChipMeasureAmplitude(uint8_t* result) { - ReturnCode err; - uint8_t reg_opc, reg_mode, reg_conf1, reg_conf2; - - /* Save registers which will be adjusted below */ - st25r3916ReadRegister(ST25R3916_REG_OP_CONTROL, ®_opc); - st25r3916ReadRegister(ST25R3916_REG_MODE, ®_mode); - st25r3916ReadRegister(ST25R3916_REG_RX_CONF1, ®_conf1); - st25r3916ReadRegister(ST25R3916_REG_RX_CONF2, ®_conf2); - - /* Set values as per defaults of DS. These regs/bits influence receiver chain and change amplitude */ - /* Doing so achieves an amplitude comparable over a complete polling cylce */ - st25r3916WriteRegister(ST25R3916_REG_OP_CONTROL, (reg_opc & ~ST25R3916_REG_OP_CONTROL_rx_chn)); - st25r3916WriteRegister( - ST25R3916_REG_MODE, - ST25R3916_REG_MODE_om_iso14443a | ST25R3916_REG_MODE_targ_init | - ST25R3916_REG_MODE_tr_am_ook | ST25R3916_REG_MODE_nfc_ar_off); - st25r3916WriteRegister( - ST25R3916_REG_RX_CONF1, (reg_conf1 & ~ST25R3916_REG_RX_CONF1_ch_sel_AM)); - st25r3916WriteRegister( - ST25R3916_REG_RX_CONF2, - ((reg_conf2 & ~(ST25R3916_REG_RX_CONF2_demod_mode | ST25R3916_REG_RX_CONF2_amd_sel)) | - ST25R3916_REG_RX_CONF2_amd_sel_peak)); - - /* Perform the actual measurement */ - err = st25r3916MeasureAmplitude(result); - - /* Restore values */ - st25r3916WriteRegister(ST25R3916_REG_OP_CONTROL, reg_opc); - st25r3916WriteRegister(ST25R3916_REG_MODE, reg_mode); - st25r3916WriteRegister(ST25R3916_REG_RX_CONF1, reg_conf1); - st25r3916WriteRegister(ST25R3916_REG_RX_CONF2, reg_conf2); - - return err; -} - -/*******************************************************************************/ -ReturnCode rfalChipMeasurePhase(uint8_t* result) { - st25r3916MeasurePhase(result); - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalChipMeasureCapacitance(uint8_t* result) { - st25r3916MeasureCapacitance(result); - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode rfalChipMeasurePowerSupply(uint8_t param, uint8_t* result) { - *result = st25r3916MeasurePowerSupply(param); - - return ERR_NONE; -} - -/*******************************************************************************/ -extern uint8_t invalid_size_of_stream_configs - [(sizeof(struct st25r3916StreamConfig) == sizeof(struct iso15693StreamConfig)) ? 1 : (-1)]; diff --git a/lib/ST25RFAL002/source/st25r3916/st25r3916.c b/lib/ST25RFAL002/source/st25r3916/st25r3916.c deleted file mode 100644 index 9b6c22bff64..00000000000 --- a/lib/ST25RFAL002/source/st25r3916/st25r3916.c +++ /dev/null @@ -1,792 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

    © COPYRIGHT 2020 STMicroelectronics

    - * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R3916 firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file - * - * \author Gustavo Patricio - * - * \brief ST25R3916 high level interface - * - */ - -/* -****************************************************************************** -* INCLUDES -****************************************************************************** -*/ - -#include "st25r3916.h" -#include "st25r3916_com.h" -#include "st25r3916_led.h" -#include "st25r3916_irq.h" -#include "utils.h" - -/* -****************************************************************************** -* LOCAL DEFINES -****************************************************************************** -*/ - -#define ST25R3916_SUPPLY_THRESHOLD \ - 3600U /*!< Power supply measure threshold between 3.3V or 5V */ -#define ST25R3916_NRT_MAX \ - 0xFFFFU /*!< Max Register value of NRT */ - -#define ST25R3916_TOUT_MEASURE_VDD \ - 100U /*!< Max duration time of Measure Power Supply command Datasheet: 25us */ -#define ST25R3916_TOUT_MEASURE_AMPLITUDE \ - 10U /*!< Max duration time of Measure Amplitude command Datasheet: 25us */ -#define ST25R3916_TOUT_MEASURE_PHASE \ - 10U /*!< Max duration time of Measure Phase command Datasheet: 25us */ -#define ST25R3916_TOUT_MEASURE_CAPACITANCE \ - 10U /*!< Max duration time of Measure Capacitance command Datasheet: 25us */ -#define ST25R3916_TOUT_CALIBRATE_CAP_SENSOR \ - 4U /*!< Max duration Calibrate Capacitive Sensor command Datasheet: 3ms */ -#define ST25R3916_TOUT_ADJUST_REGULATORS \ - 6U /*!< Max duration time of Adjust Regulators command Datasheet: 5ms */ -#define ST25R3916_TOUT_CA \ - 10U /*!< Max duration time of Collision Avoidance command */ - -#define ST25R3916_TEST_REG_PATTERN \ - 0x33U /*!< Register Read Write test pattern used during selftest */ -#define ST25R3916_TEST_WU_TOUT \ - 12U /*!< Timeout used on WU timer during self test */ -#define ST25R3916_TEST_TMR_TOUT \ - 20U /*!< Timeout used during self test */ -#define ST25R3916_TEST_TMR_TOUT_DELTA \ - 2U /*!< Timeout used during self test */ -#define ST25R3916_TEST_TMR_TOUT_8FC \ - (ST25R3916_TEST_TMR_TOUT * 16950U) /*!< Timeout in 8/fc */ - -/* -****************************************************************************** -* LOCAL CONSTANTS -****************************************************************************** -*/ - -/* -****************************************************************************** -* LOCAL VARIABLES -****************************************************************************** -*/ - -static uint32_t gST25R3916NRT_64fcs; - -/* -****************************************************************************** -* LOCAL FUNCTION PROTOTYPES -****************************************************************************** -*/ - -/* - ****************************************************************************** - * LOCAL FUNCTION - ****************************************************************************** - */ - -ReturnCode st25r3916ExecuteCommandAndGetResult( - uint8_t cmd, - uint8_t resReg, - uint8_t tout, - uint8_t* result) { - /* Clear and enable Direct Command interrupt */ - st25r3916GetInterrupt(ST25R3916_IRQ_MASK_DCT); - st25r3916EnableInterrupts(ST25R3916_IRQ_MASK_DCT); - - st25r3916ExecuteCommand(cmd); - - st25r3916WaitForInterruptsTimed(ST25R3916_IRQ_MASK_DCT, tout); - st25r3916DisableInterrupts(ST25R3916_IRQ_MASK_DCT); - - /* After execution read out the result if the pointer is not NULL */ - if(result != NULL) { - st25r3916ReadRegister(resReg, result); - } - - return ERR_NONE; -} - -/* -****************************************************************************** -* GLOBAL FUNCTIONS -****************************************************************************** -*/ - -ReturnCode st25r3916Initialize(void) { - uint16_t vdd_mV; - ReturnCode ret; - - /* Set default state on the ST25R3916 */ - st25r3916ExecuteCommand(ST25R3916_CMD_SET_DEFAULT); - -#ifndef RFAL_USE_I2C - /* Increase MISO driving level as SPI can go up to 10MHz */ - st25r3916WriteRegister(ST25R3916_REG_IO_CONF2, ST25R3916_REG_IO_CONF2_io_drv_lvl); -#endif /* RFAL_USE_I2C */ - - if(!st25r3916CheckChipID(NULL)) { - platformErrorHandle(); - return ERR_HW_MISMATCH; - } - - st25r3916InitInterrupts(); - st25r3916ledInit(); - - gST25R3916NRT_64fcs = 0; - -#ifndef RFAL_USE_I2C - /* Enable pull downs on MISO line */ - st25r3916SetRegisterBits( - ST25R3916_REG_IO_CONF2, - (ST25R3916_REG_IO_CONF2_miso_pd1 | ST25R3916_REG_IO_CONF2_miso_pd2)); -#endif /* RFAL_USE_I2C */ - - /* Disable internal overheat protection */ - st25r3916ChangeTestRegisterBits(0x04, 0x10, 0x10); - -#ifdef ST25R_SELFTEST - /****************************************************************************** - * Check communication interface: - * - write a pattern in a register - * - reads back the register value - * - return ERR_IO in case the read value is different - */ - st25r3916WriteRegister(ST25R3916_REG_BIT_RATE, ST25R3916_TEST_REG_PATTERN); - if(!st25r3916CheckReg( - ST25R3916_REG_BIT_RATE, - (ST25R3916_REG_BIT_RATE_rxrate_mask | ST25R3916_REG_BIT_RATE_txrate_mask), - ST25R3916_TEST_REG_PATTERN)) { - platformErrorHandle(); - return ERR_IO; - } - - /* Restore default value */ - st25r3916WriteRegister(ST25R3916_REG_BIT_RATE, 0x00); - - /* - * Check IRQ Handling: - * - use the Wake-up timer to trigger an IRQ - * - wait the Wake-up timer interrupt - * - return ERR_TIMEOUT when the Wake-up timer interrupt is not received - */ - st25r3916WriteRegister( - ST25R3916_REG_WUP_TIMER_CONTROL, - ST25R3916_REG_WUP_TIMER_CONTROL_wur | ST25R3916_REG_WUP_TIMER_CONTROL_wto); - st25r3916EnableInterrupts(ST25R3916_IRQ_MASK_WT); - st25r3916ExecuteCommand(ST25R3916_CMD_START_WUP_TIMER); - if(st25r3916WaitForInterruptsTimed(ST25R3916_IRQ_MASK_WT, ST25R3916_TEST_WU_TOUT) == 0U) { - platformErrorHandle(); - return ERR_TIMEOUT; - } - st25r3916DisableInterrupts(ST25R3916_IRQ_MASK_WT); - st25r3916WriteRegister(ST25R3916_REG_WUP_TIMER_CONTROL, 0U); - /*******************************************************************************/ -#endif /* ST25R_SELFTEST */ - - /* Enable Oscillator and wait until it gets stable */ - ret = st25r3916OscOn(); - if(ret != ERR_NONE) { - platformErrorHandle(); - return ret; - } - - /* Measure VDD and set sup3V bit according to Power supplied */ - vdd_mV = st25r3916MeasureVoltage(ST25R3916_REG_REGULATOR_CONTROL_mpsv_vdd); - st25r3916ChangeRegisterBits( - ST25R3916_REG_IO_CONF2, - ST25R3916_REG_IO_CONF2_sup3V, - ((vdd_mV < ST25R3916_SUPPLY_THRESHOLD) ? ST25R3916_REG_IO_CONF2_sup3V_3V : - ST25R3916_REG_IO_CONF2_sup3V_5V)); - - /* Make sure Transmitter and Receiver are disabled */ - st25r3916TxRxOff(); - -#ifdef ST25R_SELFTEST_TIMER - /****************************************************************************** - * Check SW timer operation : - * - use the General Purpose timer to measure an amount of time - * - test whether an interrupt is seen when less time was given - * - test whether an interrupt is seen when sufficient time was given - */ - - st25r3916EnableInterrupts(ST25R3916_IRQ_MASK_GPE); - st25r3916SetStartGPTimer( - (uint16_t)ST25R3916_TEST_TMR_TOUT_8FC, ST25R3916_REG_TIMER_EMV_CONTROL_gptc_no_trigger); - if(st25r3916WaitForInterruptsTimed( - ST25R3916_IRQ_MASK_GPE, (ST25R3916_TEST_TMR_TOUT - ST25R3916_TEST_TMR_TOUT_DELTA)) != - 0U) { - platformErrorHandle(); - return ERR_SYSTEM; - } - - /* Stop all activities to stop the GP timer */ - st25r3916ExecuteCommand(ST25R3916_CMD_STOP); - st25r3916ClearAndEnableInterrupts(ST25R3916_IRQ_MASK_GPE); - st25r3916SetStartGPTimer( - (uint16_t)ST25R3916_TEST_TMR_TOUT_8FC, ST25R3916_REG_TIMER_EMV_CONTROL_gptc_no_trigger); - if(st25r3916WaitForInterruptsTimed( - ST25R3916_IRQ_MASK_GPE, (ST25R3916_TEST_TMR_TOUT + ST25R3916_TEST_TMR_TOUT_DELTA)) == - 0U) { - platformErrorHandle(); - return ERR_SYSTEM; - } - - /* Stop all activities to stop the GP timer */ - st25r3916ExecuteCommand(ST25R3916_CMD_STOP); - /*******************************************************************************/ -#endif /* ST25R_SELFTEST_TIMER */ - - /* After reset all interrupts are enabled, so disable them at first */ - st25r3916DisableInterrupts(ST25R3916_IRQ_MASK_ALL); - - /* And clear them, just to be sure */ - st25r3916ClearInterrupts(); - - return ERR_NONE; -} - -/*******************************************************************************/ -void st25r3916Deinitialize(void) { - st25r3916DisableInterrupts(ST25R3916_IRQ_MASK_ALL); - - /* Disable Tx and Rx, Keep OSC On */ - st25r3916TxRxOff(); - - return; -} - -/*******************************************************************************/ -ReturnCode st25r3916OscOn(void) { - /* Check if oscillator is already turned on and stable */ - /* Use ST25R3916_REG_OP_CONTROL_en instead of ST25R3916_REG_AUX_DISPLAY_osc_ok to be on the safe side */ - if(!st25r3916CheckReg( - ST25R3916_REG_OP_CONTROL, ST25R3916_REG_OP_CONTROL_en, ST25R3916_REG_OP_CONTROL_en)) { - /* Clear any eventual previous oscillator IRQ */ - st25r3916GetInterrupt(ST25R3916_IRQ_MASK_OSC); - - /* Enable oscillator frequency stable interrupt */ - st25r3916EnableInterrupts(ST25R3916_IRQ_MASK_OSC); - - /* Enable oscillator and regulator output */ - st25r3916SetRegisterBits(ST25R3916_REG_OP_CONTROL, ST25R3916_REG_OP_CONTROL_en); - - /* Wait for the oscillator interrupt */ - st25r3916WaitForInterruptsTimed(ST25R3916_IRQ_MASK_OSC, ST25R3916_TOUT_OSC_STABLE); - st25r3916DisableInterrupts(ST25R3916_IRQ_MASK_OSC); - } - - if(!st25r3916CheckReg( - ST25R3916_REG_AUX_DISPLAY, - ST25R3916_REG_AUX_DISPLAY_osc_ok, - ST25R3916_REG_AUX_DISPLAY_osc_ok)) { - return ERR_SYSTEM; - } - - return ERR_NONE; -} - -/*******************************************************************************/ -uint8_t st25r3916MeasurePowerSupply(uint8_t mpsv) { - uint8_t result; - - /* Set the source of direct command: Measure Power Supply Voltage */ - st25r3916ChangeRegisterBits( - ST25R3916_REG_REGULATOR_CONTROL, ST25R3916_REG_REGULATOR_CONTROL_mpsv_mask, mpsv); - - /* Execute command: Measure Power Supply Voltage */ - st25r3916ExecuteCommandAndGetResult( - ST25R3916_CMD_MEASURE_VDD, ST25R3916_REG_AD_RESULT, ST25R3916_TOUT_MEASURE_VDD, &result); - - return result; -} - -/*******************************************************************************/ -uint16_t st25r3916MeasureVoltage(uint8_t mpsv) { - uint8_t result; - uint16_t mV; - - result = st25r3916MeasurePowerSupply(mpsv); - - /* Convert cmd output into mV (each step represents 23.4 mV )*/ - mV = ((uint16_t)result) * 23U; - mV += (((((uint16_t)result) * 4U) + 5U) / 10U); - - return mV; -} - -/*******************************************************************************/ -ReturnCode st25r3916AdjustRegulators(uint16_t* result_mV) { - uint8_t result; - - /* Reset logic and set regulated voltages to be defined by result of Adjust Regulators command */ - st25r3916SetRegisterBits( - ST25R3916_REG_REGULATOR_CONTROL, ST25R3916_REG_REGULATOR_CONTROL_reg_s); - st25r3916ClrRegisterBits( - ST25R3916_REG_REGULATOR_CONTROL, ST25R3916_REG_REGULATOR_CONTROL_reg_s); - - /* Execute Adjust regulators cmd and retrieve result */ - st25r3916ExecuteCommandAndGetResult( - ST25R3916_CMD_ADJUST_REGULATORS, - ST25R3916_REG_REGULATOR_RESULT, - ST25R3916_TOUT_ADJUST_REGULATORS, - &result); - - /* Calculate result in mV */ - result >>= ST25R3916_REG_REGULATOR_RESULT_reg_shift; - - if(result_mV != NULL) { - if(st25r3916CheckReg( - ST25R3916_REG_IO_CONF2, - ST25R3916_REG_IO_CONF2_sup3V, - ST25R3916_REG_IO_CONF2_sup3V)) { - result = MIN( - result, - (uint8_t)(result - 5U)); /* In 3.3V mode [0,4] are not used */ - *result_mV = 2400U; /* Minimum regulated voltage 2.4V in case of 3.3V supply */ - } else { - *result_mV = 3600U; /* Minimum regulated voltage 3.6V in case of 5V supply */ - } - - *result_mV += - (uint16_t)result * 100U; /* 100mV steps in both 3.3V and 5V supply */ - } - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode st25r3916MeasureAmplitude(uint8_t* result) { - return st25r3916ExecuteCommandAndGetResult( - ST25R3916_CMD_MEASURE_AMPLITUDE, - ST25R3916_REG_AD_RESULT, - ST25R3916_TOUT_MEASURE_AMPLITUDE, - result); -} - -/*******************************************************************************/ -ReturnCode st25r3916MeasurePhase(uint8_t* result) { - return st25r3916ExecuteCommandAndGetResult( - ST25R3916_CMD_MEASURE_PHASE, ST25R3916_REG_AD_RESULT, ST25R3916_TOUT_MEASURE_PHASE, result); -} - -/*******************************************************************************/ -ReturnCode st25r3916MeasureCapacitance(uint8_t* result) { - return st25r3916ExecuteCommandAndGetResult( - ST25R3916_CMD_MEASURE_CAPACITANCE, - ST25R3916_REG_AD_RESULT, - ST25R3916_TOUT_MEASURE_CAPACITANCE, - result); -} - -/*******************************************************************************/ -ReturnCode st25r3916CalibrateCapacitiveSensor(uint8_t* result) { - ReturnCode ret; - uint8_t res; - - /* Clear Manual calibration values to enable automatic calibration mode */ - st25r3916ClrRegisterBits( - ST25R3916_REG_CAP_SENSOR_CONTROL, ST25R3916_REG_CAP_SENSOR_CONTROL_cs_mcal_mask); - - /* Execute automatic calibration */ - ret = st25r3916ExecuteCommandAndGetResult( - ST25R3916_CMD_CALIBRATE_C_SENSOR, - ST25R3916_REG_CAP_SENSOR_RESULT, - ST25R3916_TOUT_CALIBRATE_CAP_SENSOR, - &res); - - /* Check whether the calibration was successull */ - if(((res & ST25R3916_REG_CAP_SENSOR_RESULT_cs_cal_end) != - ST25R3916_REG_CAP_SENSOR_RESULT_cs_cal_end) || - ((res & ST25R3916_REG_CAP_SENSOR_RESULT_cs_cal_err) == - ST25R3916_REG_CAP_SENSOR_RESULT_cs_cal_err) || - (ret != ERR_NONE)) { - return ERR_IO; - } - - if(result != NULL) { - (*result) = (uint8_t)(res >> ST25R3916_REG_CAP_SENSOR_RESULT_cs_cal_shift); - } - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode st25r3916SetBitrate(uint8_t txrate, uint8_t rxrate) { - uint8_t reg; - - st25r3916ReadRegister(ST25R3916_REG_BIT_RATE, ®); - if(rxrate != ST25R3916_BR_DO_NOT_SET) { - if(rxrate > ST25R3916_BR_848) { - return ERR_PARAM; - } - - reg = (uint8_t)(reg & ~ST25R3916_REG_BIT_RATE_rxrate_mask); /* MISRA 10.3 */ - reg |= rxrate << ST25R3916_REG_BIT_RATE_rxrate_shift; - } - if(txrate != ST25R3916_BR_DO_NOT_SET) { - if(txrate > ST25R3916_BR_6780) { - return ERR_PARAM; - } - - reg = (uint8_t)(reg & ~ST25R3916_REG_BIT_RATE_txrate_mask); /* MISRA 10.3 */ - reg |= txrate << ST25R3916_REG_BIT_RATE_txrate_shift; - } - return st25r3916WriteRegister(ST25R3916_REG_BIT_RATE, reg); -} - -/*******************************************************************************/ -ReturnCode st25r3916PerformCollisionAvoidance( - uint8_t FieldONCmd, - uint8_t pdThreshold, - uint8_t caThreshold, - uint8_t nTRFW) { - uint8_t treMask; - uint32_t irqs; - ReturnCode err; - - if((FieldONCmd != ST25R3916_CMD_INITIAL_RF_COLLISION) && - (FieldONCmd != ST25R3916_CMD_RESPONSE_RF_COLLISION_N)) { - return ERR_PARAM; - } - - err = ERR_INTERNAL; - - /* Check if new thresholds are to be applied */ - if((pdThreshold != ST25R3916_THRESHOLD_DO_NOT_SET) || - (caThreshold != ST25R3916_THRESHOLD_DO_NOT_SET)) { - treMask = 0; - - if(pdThreshold != ST25R3916_THRESHOLD_DO_NOT_SET) { - treMask |= ST25R3916_REG_FIELD_THRESHOLD_ACTV_trg_mask; - } - - if(caThreshold != ST25R3916_THRESHOLD_DO_NOT_SET) { - treMask |= ST25R3916_REG_FIELD_THRESHOLD_ACTV_rfe_mask; - } - - /* Set Detection Threshold and|or Collision Avoidance Threshold */ - st25r3916ChangeRegisterBits( - ST25R3916_REG_FIELD_THRESHOLD_ACTV, - treMask, - (pdThreshold & ST25R3916_REG_FIELD_THRESHOLD_ACTV_trg_mask) | - (caThreshold & ST25R3916_REG_FIELD_THRESHOLD_ACTV_rfe_mask)); - } - - /* Set n x TRFW */ - st25r3916ChangeRegisterBits(ST25R3916_REG_AUX, ST25R3916_REG_AUX_nfc_n_mask, nTRFW); - - /*******************************************************************************/ - /* Enable and clear CA specific interrupts and execute command */ - st25r3916GetInterrupt( - (ST25R3916_IRQ_MASK_CAC | ST25R3916_IRQ_MASK_CAT | ST25R3916_IRQ_MASK_APON)); - st25r3916EnableInterrupts( - (ST25R3916_IRQ_MASK_CAC | ST25R3916_IRQ_MASK_CAT | ST25R3916_IRQ_MASK_APON)); - - st25r3916ExecuteCommand(FieldONCmd); - - /*******************************************************************************/ - /* Wait for initial APON interrupt, indicating anticollision avoidance done and ST25R3916's - * field is now on, or a CAC indicating a collision */ - irqs = st25r3916WaitForInterruptsTimed( - (ST25R3916_IRQ_MASK_CAC | ST25R3916_IRQ_MASK_APON), ST25R3916_TOUT_CA); - - if((ST25R3916_IRQ_MASK_CAC & irqs) != 0U) /* Collision occurred */ - { - err = ERR_RF_COLLISION; - } else if((ST25R3916_IRQ_MASK_APON & irqs) != 0U) { - /* After APON wait for CAT interrupt, indication field was switched on minimum guard time has been fulfilled */ - irqs = st25r3916WaitForInterruptsTimed((ST25R3916_IRQ_MASK_CAT), ST25R3916_TOUT_CA); - - if((ST25R3916_IRQ_MASK_CAT & irqs) != 0U) /* No Collision detected, Field On */ - { - err = ERR_NONE; - } - } else { - /* MISRA 15.7 - Empty else */ - } - - /* Clear any previous External Field events and disable CA specific interrupts */ - st25r3916GetInterrupt((ST25R3916_IRQ_MASK_EOF | ST25R3916_IRQ_MASK_EON)); - st25r3916DisableInterrupts( - (ST25R3916_IRQ_MASK_CAC | ST25R3916_IRQ_MASK_CAT | ST25R3916_IRQ_MASK_APON)); - - return err; -} - -/*******************************************************************************/ -void st25r3916SetNumTxBits(uint16_t nBits) { - st25r3916WriteRegister(ST25R3916_REG_NUM_TX_BYTES2, (uint8_t)((nBits >> 0) & 0xFFU)); - st25r3916WriteRegister(ST25R3916_REG_NUM_TX_BYTES1, (uint8_t)((nBits >> 8) & 0xFFU)); -} - -/*******************************************************************************/ -uint16_t st25r3916GetNumFIFOBytes(void) { - uint8_t reg; - uint16_t result; - - st25r3916ReadRegister(ST25R3916_REG_FIFO_STATUS2, ®); - reg = - ((reg & ST25R3916_REG_FIFO_STATUS2_fifo_b_mask) >> - ST25R3916_REG_FIFO_STATUS2_fifo_b_shift); - result = ((uint16_t)reg << 8); - - st25r3916ReadRegister(ST25R3916_REG_FIFO_STATUS1, ®); - result |= (((uint16_t)reg) & 0x00FFU); - - return result; -} - -/*******************************************************************************/ -uint8_t st25r3916GetNumFIFOLastBits(void) { - uint8_t reg; - - st25r3916ReadRegister(ST25R3916_REG_FIFO_STATUS2, ®); - - return ( - (reg & ST25R3916_REG_FIFO_STATUS2_fifo_lb_mask) >> - ST25R3916_REG_FIFO_STATUS2_fifo_lb_shift); -} - -/*******************************************************************************/ -uint32_t st25r3916GetNoResponseTime(void) { - return gST25R3916NRT_64fcs; -} - -/*******************************************************************************/ -ReturnCode st25r3916SetNoResponseTime(uint32_t nrt_64fcs) { - ReturnCode err; - uint8_t nrt_step; - uint32_t tmpNRT; - - tmpNRT = nrt_64fcs; /* MISRA 17.8 */ - err = ERR_NONE; - - gST25R3916NRT_64fcs = tmpNRT; /* Store given NRT value in 64/fc into local var */ - nrt_step = - ST25R3916_REG_TIMER_EMV_CONTROL_nrt_step_64fc; /* Set default NRT in steps of 64/fc */ - - if(tmpNRT > ST25R3916_NRT_MAX) /* Check if the given NRT value fits using 64/fc steps */ - { - nrt_step = - ST25R3916_REG_TIMER_EMV_CONTROL_nrt_step_4096_fc; /* If not, change NRT set to 4096/fc */ - tmpNRT = ((tmpNRT + 63U) / 64U); /* Calculate number of steps in 4096/fc */ - - if(tmpNRT > ST25R3916_NRT_MAX) /* Check if the NRT value fits using 64/fc steps */ - { - tmpNRT = ST25R3916_NRT_MAX; /* Assign the maximum possible */ - err = ERR_PARAM; /* Signal parameter error */ - } - gST25R3916NRT_64fcs = (64U * tmpNRT); - } - - /* Set the ST25R3916 NRT step units and the value */ - st25r3916ChangeRegisterBits( - ST25R3916_REG_TIMER_EMV_CONTROL, ST25R3916_REG_TIMER_EMV_CONTROL_nrt_step, nrt_step); - st25r3916WriteRegister(ST25R3916_REG_NO_RESPONSE_TIMER1, (uint8_t)(tmpNRT >> 8U)); - st25r3916WriteRegister(ST25R3916_REG_NO_RESPONSE_TIMER2, (uint8_t)(tmpNRT & 0xFFU)); - - return err; -} - -/*******************************************************************************/ -ReturnCode st25r3916SetStartNoResponseTimer(uint32_t nrt_64fcs) { - ReturnCode err; - - err = st25r3916SetNoResponseTime(nrt_64fcs); - if(err == ERR_NONE) { - st25r3916ExecuteCommand(ST25R3916_CMD_START_NO_RESPONSE_TIMER); - } - - return err; -} - -/*******************************************************************************/ -void st25r3916SetGPTime(uint16_t gpt_8fcs) { - st25r3916WriteRegister(ST25R3916_REG_GPT1, (uint8_t)(gpt_8fcs >> 8)); - st25r3916WriteRegister(ST25R3916_REG_GPT2, (uint8_t)(gpt_8fcs & 0xFFU)); -} - -/*******************************************************************************/ -ReturnCode st25r3916SetStartGPTimer(uint16_t gpt_8fcs, uint8_t trigger_source) { - st25r3916SetGPTime(gpt_8fcs); - st25r3916ChangeRegisterBits( - ST25R3916_REG_TIMER_EMV_CONTROL, - ST25R3916_REG_TIMER_EMV_CONTROL_gptc_mask, - trigger_source); - - /* If there's no trigger source, start GPT immediately */ - if(trigger_source == ST25R3916_REG_TIMER_EMV_CONTROL_gptc_no_trigger) { - st25r3916ExecuteCommand(ST25R3916_CMD_START_GP_TIMER); - } - - return ERR_NONE; -} - -/*******************************************************************************/ -bool st25r3916CheckChipID(uint8_t* rev) { - uint8_t ID; - - ID = 0; - st25r3916ReadRegister(ST25R3916_REG_IC_IDENTITY, &ID); - - /* Check if IC Identity Register contains ST25R3916's IC type code */ - if((ID & ST25R3916_REG_IC_IDENTITY_ic_type_mask) != - ST25R3916_REG_IC_IDENTITY_ic_type_st25r3916) { - return false; - } - - if(rev != NULL) { - *rev = (ID & ST25R3916_REG_IC_IDENTITY_ic_rev_mask); - } - - return true; -} - -/*******************************************************************************/ -ReturnCode st25r3916GetRegsDump(t_st25r3916Regs* regDump) { - uint8_t regIt; - - if(regDump == NULL) { - return ERR_PARAM; - } - - /* Dump Registers on space A */ - for(regIt = ST25R3916_REG_IO_CONF1; regIt <= ST25R3916_REG_IC_IDENTITY; regIt++) { - st25r3916ReadRegister(regIt, ®Dump->RsA[regIt]); - } - - regIt = 0; - - /* Read non-consecutive Registers on space B */ - st25r3916ReadRegister(ST25R3916_REG_EMD_SUP_CONF, ®Dump->RsB[regIt++]); - st25r3916ReadRegister(ST25R3916_REG_SUBC_START_TIME, ®Dump->RsB[regIt++]); - st25r3916ReadRegister(ST25R3916_REG_P2P_RX_CONF, ®Dump->RsB[regIt++]); - st25r3916ReadRegister(ST25R3916_REG_CORR_CONF1, ®Dump->RsB[regIt++]); - st25r3916ReadRegister(ST25R3916_REG_CORR_CONF2, ®Dump->RsB[regIt++]); - st25r3916ReadRegister(ST25R3916_REG_SQUELCH_TIMER, ®Dump->RsB[regIt++]); - st25r3916ReadRegister(ST25R3916_REG_FIELD_ON_GT, ®Dump->RsB[regIt++]); - st25r3916ReadRegister(ST25R3916_REG_AUX_MOD, ®Dump->RsB[regIt++]); - st25r3916ReadRegister(ST25R3916_REG_TX_DRIVER_TIMING, ®Dump->RsB[regIt++]); - st25r3916ReadRegister(ST25R3916_REG_RES_AM_MOD, ®Dump->RsB[regIt++]); - st25r3916ReadRegister(ST25R3916_REG_TX_DRIVER_STATUS, ®Dump->RsB[regIt++]); - st25r3916ReadRegister(ST25R3916_REG_REGULATOR_RESULT, ®Dump->RsB[regIt++]); - st25r3916ReadRegister(ST25R3916_REG_OVERSHOOT_CONF1, ®Dump->RsB[regIt++]); - st25r3916ReadRegister(ST25R3916_REG_OVERSHOOT_CONF2, ®Dump->RsB[regIt++]); - st25r3916ReadRegister(ST25R3916_REG_UNDERSHOOT_CONF1, ®Dump->RsB[regIt++]); - st25r3916ReadRegister(ST25R3916_REG_UNDERSHOOT_CONF2, ®Dump->RsB[regIt++]); - - return ERR_NONE; -} - -/*******************************************************************************/ -bool st25r3916IsCmdValid(uint8_t cmd) { - if(!((cmd >= ST25R3916_CMD_SET_DEFAULT) && (cmd <= ST25R3916_CMD_RESPONSE_RF_COLLISION_N)) && - !((cmd >= ST25R3916_CMD_GOTO_SENSE) && (cmd <= ST25R3916_CMD_GOTO_SLEEP)) && - !((cmd >= ST25R3916_CMD_MASK_RECEIVE_DATA) && (cmd <= ST25R3916_CMD_MEASURE_AMPLITUDE)) && - !((cmd >= ST25R3916_CMD_RESET_RXGAIN) && (cmd <= ST25R3916_CMD_ADJUST_REGULATORS)) && - !((cmd >= ST25R3916_CMD_CALIBRATE_DRIVER_TIMING) && - (cmd <= ST25R3916_CMD_START_PPON2_TIMER)) && - (cmd != ST25R3916_CMD_SPACE_B_ACCESS) && (cmd != ST25R3916_CMD_STOP_NRT)) { - return false; - } - return true; -} - -/*******************************************************************************/ -ReturnCode st25r3916StreamConfigure(const struct st25r3916StreamConfig* config) { - uint8_t smd; - uint8_t mode; - - smd = 0; - - if(config->useBPSK != 0U) { - mode = ST25R3916_REG_MODE_om_bpsk_stream; - if((config->din < 2U) || (config->din > 4U)) /* not in fc/4 .. fc/16 */ - { - return ERR_PARAM; - } - smd |= ((4U - config->din) << ST25R3916_REG_STREAM_MODE_scf_shift); - } else { - mode = ST25R3916_REG_MODE_om_subcarrier_stream; - if((config->din < 3U) || (config->din > 6U)) /* not in fc/8 .. fc/64 */ - { - return ERR_PARAM; - } - smd |= ((6U - config->din) << ST25R3916_REG_STREAM_MODE_scf_shift); - if(config->report_period_length == 0U) { - return ERR_PARAM; - } - } - - if((config->dout < 1U) || (config->dout > 7U)) /* not in fc/2 .. fc/128 */ - { - return ERR_PARAM; - } - smd |= (7U - config->dout) << ST25R3916_REG_STREAM_MODE_stx_shift; - - if(config->report_period_length > 3U) { - return ERR_PARAM; - } - smd |= (config->report_period_length << ST25R3916_REG_STREAM_MODE_scp_shift); - - st25r3916WriteRegister(ST25R3916_REG_STREAM_MODE, smd); - st25r3916ChangeRegisterBits(ST25R3916_REG_MODE, ST25R3916_REG_MODE_om_mask, mode); - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode st25r3916GetRSSI(uint16_t* amRssi, uint16_t* pmRssi) { - /*******************************************************************************/ - /* MISRA 8.9 An object should be defined at block scope if its identifier only appears in a single function */ - /*< ST25R3916 RSSI Display Reg values: 0 1 2 3 4 5 6 7 8 9 a b c d e f */ - static const uint16_t st25r3916Rssi2mV[] = { - 0, 20, 27, 37, 52, 72, 99, 136, 190, 262, 357, 500, 686, 950, 1150, 1150}; - - /* ST25R3916 2/3 stage gain reduction [dB] 0 0 0 0 0 3 6 9 12 15 18 na na na na na */ - static const uint16_t st25r3916Gain2Percent[] = { - 100, 100, 100, 100, 100, 141, 200, 281, 398, 562, 794, 1, 1, 1, 1, 1}; - /*******************************************************************************/ - - uint8_t rssi; - uint8_t gainRed; - - st25r3916ReadRegister(ST25R3916_REG_RSSI_RESULT, &rssi); - st25r3916ReadRegister(ST25R3916_REG_GAIN_RED_STATE, &gainRed); - - if(amRssi != NULL) { - *amRssi = - (uint16_t)(((uint32_t)st25r3916Rssi2mV[(rssi >> ST25R3916_REG_RSSI_RESULT_rssi_am_shift)] * (uint32_t)st25r3916Gain2Percent[(gainRed >> ST25R3916_REG_GAIN_RED_STATE_gs_am_shift)]) / 100U); - } - - if(pmRssi != NULL) { - *pmRssi = - (uint16_t)(((uint32_t)st25r3916Rssi2mV[(rssi & ST25R3916_REG_RSSI_RESULT_rssi_pm_mask)] * (uint32_t)st25r3916Gain2Percent[(gainRed & ST25R3916_REG_GAIN_RED_STATE_gs_pm_mask)]) / 100U); - } - - return ERR_NONE; -} diff --git a/lib/ST25RFAL002/source/st25r3916/st25r3916.h b/lib/ST25RFAL002/source/st25r3916/st25r3916.h deleted file mode 100644 index 2ba202d86d5..00000000000 --- a/lib/ST25RFAL002/source/st25r3916/st25r3916.h +++ /dev/null @@ -1,669 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

    © COPYRIGHT 2020 STMicroelectronics

    - * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R3916 firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file - * - * \author Gustavo Patricio - * - * \brief ST25R3916 high level interface - * - * - * \addtogroup RFAL - * @{ - * - * \addtogroup RFAL-HAL - * \brief RFAL Hardware Abstraction Layer - * @{ - * - * \addtogroup ST25R3916 - * \brief RFAL ST25R3916 Driver - * @{ - * - * \addtogroup ST25R3916_Driver - * \brief RFAL ST25R3916 Driver - * @{ - * - */ - -#ifndef ST25R3916_H -#define ST25R3916_H - -/* -****************************************************************************** -* INCLUDES -****************************************************************************** -*/ -#include "platform.h" -#include "st_errno.h" -#include "st25r3916_com.h" - -/* -****************************************************************************** -* GLOBAL DATATYPES -****************************************************************************** -*/ - -/*! Struct to represent all regs on ST25R3916 */ -typedef struct { - uint8_t RsA[( - ST25R3916_REG_IC_IDENTITY + 1U)]; /*!< Registers contained on ST25R3916 space A (Rs-A) */ - uint8_t - RsB[ST25R3916_SPACE_B_REG_LEN]; /*!< Registers contained on ST25R3916 space B (Rs-B) */ -} t_st25r3916Regs; - -/*! Parameters how the stream mode should work */ -struct st25r3916StreamConfig { - uint8_t useBPSK; /*!< 0: subcarrier, 1:BPSK */ - uint8_t din; /*!< Divider for the in subcarrier frequency: fc/2^din */ - uint8_t dout; /*!< Divider for the in subcarrier frequency fc/2^dout */ - uint8_t report_period_length; /*!< Length of the reporting period 2^report_period_length*/ -}; - -/* -****************************************************************************** -* GLOBAL DEFINES -****************************************************************************** -*/ - -/* ST25R3916 direct commands */ -#define ST25R3916_CMD_SET_DEFAULT \ - 0xC1U /*!< Puts the chip in default state (same as after power-up) */ -#define ST25R3916_CMD_STOP 0xC2U /*!< Stops all activities and clears FIFO */ -#define ST25R3916_CMD_TRANSMIT_WITH_CRC \ - 0xC4U /*!< Transmit with CRC */ -#define ST25R3916_CMD_TRANSMIT_WITHOUT_CRC \ - 0xC5U /*!< Transmit without CRC */ -#define ST25R3916_CMD_TRANSMIT_REQA \ - 0xC6U /*!< Transmit REQA */ -#define ST25R3916_CMD_TRANSMIT_WUPA \ - 0xC7U /*!< Transmit WUPA */ -#define ST25R3916_CMD_INITIAL_RF_COLLISION \ - 0xC8U /*!< NFC transmit with Initial RF Collision Avoidance */ -#define ST25R3916_CMD_RESPONSE_RF_COLLISION_N \ - 0xC9U /*!< NFC transmit with Response RF Collision Avoidance */ -#define ST25R3916_CMD_GOTO_SENSE \ - 0xCDU /*!< Passive target logic to Sense/Idle state */ -#define ST25R3916_CMD_GOTO_SLEEP \ - 0xCEU /*!< Passive target logic to Sleep/Halt state */ -#define ST25R3916_CMD_MASK_RECEIVE_DATA \ - 0xD0U /*!< Mask receive data */ -#define ST25R3916_CMD_UNMASK_RECEIVE_DATA \ - 0xD1U /*!< Unmask receive data */ -#define ST25R3916_CMD_AM_MOD_STATE_CHANGE \ - 0xD2U /*!< AM Modulation state change */ -#define ST25R3916_CMD_MEASURE_AMPLITUDE \ - 0xD3U /*!< Measure signal amplitude on RFI inputs */ -#define ST25R3916_CMD_RESET_RXGAIN \ - 0xD5U /*!< Reset RX Gain */ -#define ST25R3916_CMD_ADJUST_REGULATORS \ - 0xD6U /*!< Adjust regulators */ -#define ST25R3916_CMD_CALIBRATE_DRIVER_TIMING \ - 0xD8U /*!< Starts the sequence to adjust the driver timing */ -#define ST25R3916_CMD_MEASURE_PHASE \ - 0xD9U /*!< Measure phase between RFO and RFI signal */ -#define ST25R3916_CMD_CLEAR_RSSI \ - 0xDAU /*!< Clear RSSI bits and restart the measurement */ -#define ST25R3916_CMD_CLEAR_FIFO \ - 0xDBU /*!< Clears FIFO, Collision and IRQ status */ -#define ST25R3916_CMD_TRANSPARENT_MODE \ - 0xDCU /*!< Transparent mode */ -#define ST25R3916_CMD_CALIBRATE_C_SENSOR \ - 0xDDU /*!< Calibrate the capacitive sensor */ -#define ST25R3916_CMD_MEASURE_CAPACITANCE \ - 0xDEU /*!< Measure capacitance */ -#define ST25R3916_CMD_MEASURE_VDD \ - 0xDFU /*!< Measure power supply voltage */ -#define ST25R3916_CMD_START_GP_TIMER \ - 0xE0U /*!< Start the general purpose timer */ -#define ST25R3916_CMD_START_WUP_TIMER \ - 0xE1U /*!< Start the wake-up timer */ -#define ST25R3916_CMD_START_MASK_RECEIVE_TIMER \ - 0xE2U /*!< Start the mask-receive timer */ -#define ST25R3916_CMD_START_NO_RESPONSE_TIMER \ - 0xE3U /*!< Start the no-response timer */ -#define ST25R3916_CMD_START_PPON2_TIMER \ - 0xE4U /*!< Start PPon2 timer */ -#define ST25R3916_CMD_STOP_NRT \ - 0xE8U /*!< Stop No Response Timer */ -#define ST25R3916_CMD_SPACE_B_ACCESS \ - 0xFBU /*!< Enable R/W access to the test registers */ -#define ST25R3916_CMD_TEST_ACCESS \ - 0xFCU /*!< Enable R/W access to the test registers */ - -#define ST25R3916_THRESHOLD_DO_NOT_SET \ - 0xFFU /*!< Indicates not to change this Threshold */ - -#define ST25R3916_BR_DO_NOT_SET \ - 0xFFU /*!< Indicates not to change this Bit Rate */ -#define ST25R3916_BR_106 0x00U /*!< ST25R3916 Bit Rate 106 kbit/s (fc/128) */ -#define ST25R3916_BR_212 0x01U /*!< ST25R3916 Bit Rate 212 kbit/s (fc/64) */ -#define ST25R3916_BR_424 0x02U /*!< ST25R3916 Bit Rate 424 kbit/s (fc/32) */ -#define ST25R3916_BR_848 0x03U /*!< ST25R3916 Bit Rate 848 kbit/s (fc/16) */ -#define ST25R3916_BR_1695 0x04U /*!< ST25R3916 Bit Rate 1696 kbit/s (fc/8) */ -#define ST25R3916_BR_3390 0x05U /*!< ST25R3916 Bit Rate 3390 kbit/s (fc/4) */ -#define ST25R3916_BR_6780 0x07U /*!< ST25R3916 Bit Rate 6780 kbit/s (fc/2) */ - -#define ST25R3916_FIFO_DEPTH 512U /*!< Depth of FIFO */ -#define ST25R3916_TOUT_OSC_STABLE \ - 10U /*!< Max timeout for Oscillator to get stable DS: 700us */ - -/* -****************************************************************************** -* GLOBAL MACROS -****************************************************************************** -*/ - -/*! Enables the Transmitter (Field On) and Receiver */ -#define st25r3916TxRxOn() \ - st25r3916SetRegisterBits( \ - ST25R3916_REG_OP_CONTROL, \ - (ST25R3916_REG_OP_CONTROL_rx_en | ST25R3916_REG_OP_CONTROL_tx_en)) - -/*! Disables the Transmitter (Field Off) and Receiver */ -#define st25r3916TxRxOff() \ - st25r3916ClrRegisterBits( \ - ST25R3916_REG_OP_CONTROL, \ - (ST25R3916_REG_OP_CONTROL_rx_en | ST25R3916_REG_OP_CONTROL_tx_en)) - -/*! Disables the Transmitter (Field Off) */ -#define st25r3916TxOff() \ - st25r3916ClrRegisterBits(ST25R3916_REG_OP_CONTROL, ST25R3916_REG_OP_CONTROL_tx_en) - -/*! Checks if General Purpose Timer is still running by reading gpt_on flag */ -#define st25r3916IsGPTRunning() \ - st25r3916CheckReg( \ - ST25R3916_REG_NFCIP1_BIT_RATE, \ - ST25R3916_REG_NFCIP1_BIT_RATE_gpt_on, \ - ST25R3916_REG_NFCIP1_BIT_RATE_gpt_on) - -/*! Checks if External Filed is detected by reading ST25R3916 External Field Detector output */ -#define st25r3916IsExtFieldOn() \ - st25r3916CheckReg( \ - ST25R3916_REG_AUX_DISPLAY, \ - ST25R3916_REG_AUX_DISPLAY_efd_o, \ - ST25R3916_REG_AUX_DISPLAY_efd_o) - -/*! Checks if Transmitter is enabled (Field On) */ -#define st25r3916IsTxEnabled() \ - st25r3916CheckReg( \ - ST25R3916_REG_OP_CONTROL, ST25R3916_REG_OP_CONTROL_tx_en, ST25R3916_REG_OP_CONTROL_tx_en) - -/*! Checks if NRT is in EMV mode */ -#define st25r3916IsNRTinEMV() \ - st25r3916CheckReg( \ - ST25R3916_REG_TIMER_EMV_CONTROL, \ - ST25R3916_REG_TIMER_EMV_CONTROL_nrt_emv, \ - ST25R3916_REG_TIMER_EMV_CONTROL_nrt_emv_on) - -/*! Checks if last FIFO byte is complete */ -#define st25r3916IsLastFIFOComplete() \ - st25r3916CheckReg(ST25R3916_REG_FIFO_STATUS2, ST25R3916_REG_FIFO_STATUS2_fifo_lb_mask, 0) - -/*! Checks if the Oscillator is enabled */ -#define st25r3916IsOscOn() \ - st25r3916CheckReg( \ - ST25R3916_REG_OP_CONTROL, ST25R3916_REG_OP_CONTROL_en, ST25R3916_REG_OP_CONTROL_en) - -/* -****************************************************************************** -* GLOBAL FUNCTION PROTOTYPES -****************************************************************************** -*/ - -/*! - ***************************************************************************** - * \brief Initialise ST25R3916 driver - * - * This function initialises the ST25R3916 driver. - * - * \return ERR_NONE : Operation successful - * \return ERR_HW_MISMATCH : Expected HW do not match or communication error - * \return ERR_IO : Error during communication selftest. Check communication interface - * \return ERR_TIMEOUT : Timeout during IRQ selftest. Check IRQ handling - * \return ERR_SYSTEM : Failure during oscillator activation or timer error - * - ***************************************************************************** - */ -ReturnCode st25r3916Initialize(void); - -/*! - ***************************************************************************** - * \brief Deinitialize ST25R3916 driver - * - * Calling this function deinitializes the ST25R3916 driver. - * - ***************************************************************************** - */ -void st25r3916Deinitialize(void); - -/*! - ***************************************************************************** - * \brief Turn on Oscillator and Regulator - * - * This function turn on oscillator and regulator and waits for the - * oscillator to become stable - * - * \return ERR_SYSTEM : Failure dusring Oscillator activation - * \return ERR_NONE : No error, Oscillator is active and stable, Regulator is on - * - ***************************************************************************** - */ -ReturnCode st25r3916OscOn(void); - -/*! - ***************************************************************************** - * \brief Sets the bitrate - * - * This function sets the bitrates for rx and tx - * - * \param txrate : speed is 2^txrate * 106 kb/s - * 0xff : don't set txrate (ST25R3916_BR_DO_NOT_SET) - * \param rxrate : speed is 2^rxrate * 106 kb/s - * 0xff : don't set rxrate (ST25R3916_BR_DO_NOT_SET) - * - * \return ERR_PARAM: At least one bit rate was invalid - * \return ERR_NONE : No error, both bit rates were set - * - ***************************************************************************** - */ -ReturnCode st25r3916SetBitrate(uint8_t txrate, uint8_t rxrate); - -/*! - ***************************************************************************** - * \brief Adjusts supply regulators according to the current supply voltage - * - * This function the power level is measured in maximum load conditions and - * the regulated voltage reference is set to 250mV below this level. - * Execution of this function lasts around 5ms. - * - * The regulated voltages will be set to the result of Adjust Regulators - * - * \param [out] result_mV : Result of calibration in milliVolts - * - * \return ERR_IO : Error during communication with ST25R3916 - * \return ERR_NONE : No error - * - ***************************************************************************** - */ -ReturnCode st25r3916AdjustRegulators(uint16_t* result_mV); - -/*! - ***************************************************************************** - * \brief Measure Amplitude - * - * This function measured the amplitude on the RFI inputs and stores the - * result in parameter \a result. - * - * \param[out] result: result of RF measurement. - * - * \return ERR_PARAM : Invalid parameter - * \return ERR_NONE : No error - * - ***************************************************************************** - */ -ReturnCode st25r3916MeasureAmplitude(uint8_t* result); - -/*! - ***************************************************************************** - * \brief Measure Power Supply - * - * This function executes Measure Power Supply and returns the raw value - * - * \param[in] mpsv : one of ST25R3916_REG_REGULATOR_CONTROL_mpsv_vdd - * ST25R3916_REG_REGULATOR_CONTROL_mpsv_vdd_rf - * ST25R3916_REG_REGULATOR_CONTROL_mpsv_vdd_a - * ST25R3916_REG_REGULATOR_CONTROL_mpsv_vdd_d - * ST25R3916_REG_REGULATOR_CONTROL_mpsv_vdd_am - * - * \return the measured voltage in raw format. - * - ***************************************************************************** - */ -uint8_t st25r3916MeasurePowerSupply(uint8_t mpsv); - -/*! - ***************************************************************************** - * \brief Measure Voltage - * - * This function measures the voltage on one of VDD and VDD_* and returns - * the result in mV - * - * \param[in] mpsv : one of ST25R3916_REG_REGULATOR_CONTROL_mpsv_vdd - * ST25R3916_REG_REGULATOR_CONTROL_mpsv_vdd_rf - * ST25R3916_REG_REGULATOR_CONTROL_mpsv_vdd_a - * ST25R3916_REG_REGULATOR_CONTROL_mpsv_vdd_d - * or ST25R3916_REG_REGULATOR_CONTROL_mpsv_vdd_am - * - * \return the measured voltage in mV - * - ***************************************************************************** - */ -uint16_t st25r3916MeasureVoltage(uint8_t mpsv); - -/*! - ***************************************************************************** - * \brief Measure Phase - * - * This function performs a Phase measurement. - * The result is stored in the \a result parameter. - * - * \param[out] result: 8 bit long result of the measurement. - * - * \return ERR_PARAM : Invalid parameter - * \return ERR_NONE : No error - * - ***************************************************************************** - */ -ReturnCode st25r3916MeasurePhase(uint8_t* result); - -/*! - ***************************************************************************** - * \brief Measure Capacitance - * - * This function performs the capacitance measurement and stores the - * result in parameter \a result. - * - * \param[out] result: 8 bit long result of RF measurement. - * - * \return ERR_PARAM : Invalid parameter - * \return ERR_NONE : No error - * - ***************************************************************************** - */ -ReturnCode st25r3916MeasureCapacitance(uint8_t* result); - -/*! - ***************************************************************************** - * \brief Calibrates Capacitive Sensor - * - * This function performs automatic calibration of the capacitive sensor - * and stores the result in parameter \a result. - * - * \warning To avoid interference with Xtal oscillator and reader magnetic - * field, it is strongly recommended to perform calibration - * in Power-down mode only. - * This method does not modify the Oscillator nor transmitter state, - * these should be configured before by user. - * - * \param[out] result: 5 bit long result of the calibration. - * Binary weighted, step 0.1 pF, max 3.1 pF - * - * \return ERR_PARAM : Invalid parameter - * \return ERR_IO : The calibration was not successful - * \return ERR_NONE : No error - * - ***************************************************************************** - */ -ReturnCode st25r3916CalibrateCapacitiveSensor(uint8_t* result); - -/*! - ***************************************************************************** - * \brief Get NRT time - * - * This returns the last value set on the NRT - * - * \warning it does not read chip register, just the sw var that contains the - * last value set before - * - * \return the value of the NRT in 64/fc - */ -uint32_t st25r3916GetNoResponseTime(void); - -/*! - ***************************************************************************** - * \brief Set NRT time - * - * This function sets the No Response Time with the given value - * - * \param [in] nrt_64fcs : no response time in steps of 64/fc (4.72us) - * - * \return ERR_PARAM : Invalid parameter (time is too large) - * \return ERR_NONE : No error - * - ***************************************************************************** - */ -ReturnCode st25r3916SetNoResponseTime(uint32_t nrt_64fcs); - -/*! - ***************************************************************************** - * \brief Set and Start NRT - * - * This function sets the No Response Time with the given value and - * immediately starts it - * Used when needs to add more time before timeout without performing Tx - * - * \param [in] nrt_64fcs : no response time in steps of 64/fc (4.72us) - * - * \return ERR_PARAM : Invalid parameter - * \return ERR_NONE : No error - * - ***************************************************************************** - */ -ReturnCode st25r3916SetStartNoResponseTimer(uint32_t nrt_64fcs); - -/*! - ***************************************************************************** - * \brief Set GPT time - * - * This function sets the General Purpose Timer time registers - * - * \param [in] gpt_8fcs : general purpose timer timeout in steps of 8/fc (590ns) - * - ***************************************************************************** - */ -void st25r3916SetGPTime(uint16_t gpt_8fcs); - -/*! - ***************************************************************************** - * \brief Set and Start GPT - * - * This function sets the General Purpose Timer with the given timeout and - * immediately starts it ONLY if the trigger source is not set to none. - * - * \param [in] gpt_8fcs : general purpose timer timeout in steps of8/fc (590ns) - * \param [in] trigger_source : no trigger, start of Rx, end of Rx, end of Tx in NFC mode - * - * \return ERR_PARAM : Invalid parameter - * \return ERR_NONE : No error - * - ***************************************************************************** - */ -ReturnCode st25r3916SetStartGPTimer(uint16_t gpt_8fcs, uint8_t trigger_source); - -/*! - ***************************************************************************** - * \brief Sets the number Tx Bits - * - * Sets ST25R3916 internal registers with correct number of complete bytes and - * bits to be sent - * - * \param [in] nBits : number of bits to be set/transmitted - * - ***************************************************************************** - */ -void st25r3916SetNumTxBits(uint16_t nBits); - -/*! - ***************************************************************************** - * \brief Get amount of bytes in FIFO - * - * Gets the number of bytes currently in the FIFO - * - * \return the number of bytes currently in the FIFO - * - ***************************************************************************** - */ -uint16_t st25r3916GetNumFIFOBytes(void); - -/*! - ***************************************************************************** - * \brief Get amount of bits of the last FIFO byte if incomplete - * - * Gets the number of bits of the last FIFO byte if incomplete - * - * \return the number of bits of the last FIFO byte if incomplete, 0 if - * the last byte is complete - * - ***************************************************************************** - */ -uint8_t st25r3916GetNumFIFOLastBits(void); - -/*! - ***************************************************************************** - * \brief Perform Collision Avoidance - * - * Performs Collision Avoidance with the given threshold and with the - * n number of TRFW - * - * \param[in] FieldONCmd : Field ON command to be executed ST25R3916_CMD_INITIAL_RF_COLLISION - * or ST25R3916_CMD_RESPONSE_RF_COLLISION_N - * \param[in] pdThreshold : Peer Detection Threshold (ST25R3916_REG_FIELD_THRESHOLD_trg_xx) - * 0xff : don't set Threshold (ST25R3916_THRESHOLD_DO_NOT_SET) - * \param[in] caThreshold : Collision Avoidance Threshold (ST25R3916_REG_FIELD_THRESHOLD_rfe_xx) - * 0xff : don't set Threshold (ST25R3916_THRESHOLD_DO_NOT_SET) - * \param[in] nTRFW : Number of TRFW - * - * \return ERR_PARAM : Invalid parameter - * \return ERR_RF_COLLISION : Collision detected - * \return ERR_NONE : No collision detected - * - ***************************************************************************** - */ -ReturnCode st25r3916PerformCollisionAvoidance( - uint8_t FieldONCmd, - uint8_t pdThreshold, - uint8_t caThreshold, - uint8_t nTRFW); - -/*! - ***************************************************************************** - * \brief Check Identity - * - * Checks if the chip ID is as expected. - * - * 5 bit IC type code for ST25R3916: 00101 - * The 3 lsb contain the IC revision code - * - * \param[out] rev : the IC revision code - * - * \return true when IC type is as expected - * \return false otherwise - */ -bool st25r3916CheckChipID(uint8_t* rev); - -/*! - ***************************************************************************** - * \brief Retrieves all internal registers from ST25R3916 - * - * \param[out] regDump : pointer to the struct/buffer where the reg dump - * will be written - * - * \return ERR_PARAM : Invalid parameter - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode st25r3916GetRegsDump(t_st25r3916Regs* regDump); - -/*! - ***************************************************************************** - * \brief Check if command is valid - * - * Checks if the given command is a valid ST25R3916 command - * - * \param[in] cmd: Command to check - * - * \return true if is a valid command - * \return false otherwise - * - ***************************************************************************** - */ -bool st25r3916IsCmdValid(uint8_t cmd); - -/*! - ***************************************************************************** - * \brief Configure the stream mode of ST25R3916 - * - * This function initializes the stream with the given parameters - * - * \param[in] config : all settings for bitrates, type, etc. - * - * \return ERR_PARAM : Invalid parameter - * \return ERR_NONE : No error, stream mode driver initialized - * - ***************************************************************************** - */ -ReturnCode st25r3916StreamConfigure(const struct st25r3916StreamConfig* config); - -/*! - ***************************************************************************** - * \brief Executes a direct command and returns the result - * - * This function executes the direct command given by \a cmd waits for - * \a sleeptime for I_dct and returns the result read from register \a resreg. - * The value of cmd is not checked. - * - * \param[in] cmd : direct command to execute - * \param[in] resReg: address of the register containing the result - * \param[in] tout : time in milliseconds to wait before reading the result - * \param[out] result: result - * - * \return ERR_NONE : No error - * - ***************************************************************************** - */ -ReturnCode - st25r3916ExecuteCommandAndGetResult(uint8_t cmd, uint8_t resReg, uint8_t tout, uint8_t* result); - -/*! - ***************************************************************************** - * \brief Gets the RSSI values - * - * This function gets the RSSI value of the previous reception taking into - * account the gain reductions that were used. - * RSSI value for both AM and PM channel can be retrieved. - * - * \param[out] amRssi: the RSSI on the AM channel expressed in mV - * \param[out] pmRssi: the RSSI on the PM channel expressed in mV - * - * \return ERR_PARAM : Invalid parameter - * \return ERR_NONE : No error - * - ***************************************************************************** - */ -ReturnCode st25r3916GetRSSI(uint16_t* amRssi, uint16_t* pmRssi); -#endif /* ST25R3916_H */ - -/** - * @} - * - * @} - * - * @} - * - * @} - */ diff --git a/lib/ST25RFAL002/source/st25r3916/st25r3916_aat.c b/lib/ST25RFAL002/source/st25r3916/st25r3916_aat.c deleted file mode 100644 index 234bb2e9954..00000000000 --- a/lib/ST25RFAL002/source/st25r3916/st25r3916_aat.c +++ /dev/null @@ -1,366 +0,0 @@ -/****************************************************************************** - * \attention - * - *

    © COPYRIGHT 2020 STMicroelectronics

    - * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R3916 firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file st25r3916_aat.c - * - * \author - * - * \brief ST25R3916 Antenna Tuning - * - * The antenna tuning algorithm tries to find the optimal settings for - * the AAT_A and AAT_B registers, which are connected to variable capacitors - * to tune the antenna matching. - * - */ - -/* -****************************************************************************** -* INCLUDES -****************************************************************************** -*/ -#include "st25r3916_aat.h" -#include "utils.h" -#include "st_errno.h" -#include "st25r3916.h" -#include "st25r3916_com.h" -#include "platform.h" -#include "rfal_chip.h" - -/* -****************************************************************************** -* GLOBAL DEFINES -****************************************************************************** -*/ -#define ST25R3916_AAT_CAP_DELAY_MAX 10 /*!< Max Variable Capacitor settle delay */ - -/* -****************************************************************************** -* GLOBAL MACROS -****************************************************************************** -*/ -#define st25r3916AatLog(...) /* platformLog(__VA_ARGS__) */ /*!< Logging macro */ - -/* -****************************************************************************** -* LOCAL FUNCTION PROTOTYPES -****************************************************************************** -*/ -static ReturnCode aatHillClimb( - const struct st25r3916AatTuneParams* tuningParams, - struct st25r3916AatTuneResult* tuningStatus); -static int32_t aatGreedyDescent( - uint32_t* f_min, - const struct st25r3916AatTuneParams* tuningParams, - struct st25r3916AatTuneResult* tuningStatus, - int32_t previousDir); -static int32_t aatSteepestDescent( - uint32_t* f_min, - const struct st25r3916AatTuneParams* tuningParams, - struct st25r3916AatTuneResult* tuningStatus, - int32_t previousDir, - int32_t previousDir2); - -static ReturnCode aatMeasure( - uint8_t serCap, - uint8_t parCap, - uint8_t* amplitude, - uint8_t* phase, - uint16_t* measureCnt); -static uint32_t - aatCalcF(const struct st25r3916AatTuneParams* tuningParams, uint8_t amplitude, uint8_t phase); -static ReturnCode aatStepDacVals( - const struct st25r3916AatTuneParams* tuningParams, - uint8_t* a, - uint8_t* b, - int32_t dir); - -/*******************************************************************************/ -ReturnCode st25r3916AatTune( - const struct st25r3916AatTuneParams* tuningParams, - struct st25r3916AatTuneResult* tuningStatus) { - ReturnCode err; - const struct st25r3916AatTuneParams* tp = tuningParams; - struct st25r3916AatTuneResult* ts = tuningStatus; - struct st25r3916AatTuneParams defaultTuningParams = { - .aat_a_min = 0, - .aat_a_max = 255, - .aat_a_start = 127, - .aat_a_stepWidth = 32, - .aat_b_min = 0, - .aat_b_max = 255, - .aat_b_start = 127, - .aat_b_stepWidth = 32, - - .phaTarget = 128, - .phaWeight = 2, - .ampTarget = 196, - .ampWeight = 1, - - .doDynamicSteps = true, - .measureLimit = 50, - }; - struct st25r3916AatTuneResult defaultTuneResult; - - if((NULL != tp) && ((tp->aat_a_min > tp->aat_a_max) || (tp->aat_a_start < tp->aat_a_min) || - (tp->aat_a_start > tp->aat_a_max) || (tp->aat_b_min > tp->aat_b_max) || - (tp->aat_b_start < tp->aat_b_min) || (tp->aat_b_start > tp->aat_b_max))) { - return ERR_PARAM; - } - - if(NULL == tp) { /* Start from current caps with default params */ - st25r3916ReadRegister(ST25R3916_REG_ANT_TUNE_A, &defaultTuningParams.aat_a_start); - st25r3916ReadRegister(ST25R3916_REG_ANT_TUNE_B, &defaultTuningParams.aat_b_start); - tp = &defaultTuningParams; - } - - if(NULL == ts) { - ts = &defaultTuneResult; - } - - ts->measureCnt = 0; /* Clear current measure count */ - - err = aatHillClimb(tp, ts); - - return err; -} - -/*******************************************************************************/ -static ReturnCode aatHillClimb( - const struct st25r3916AatTuneParams* tuningParams, - struct st25r3916AatTuneResult* tuningStatus) { - ReturnCode err = ERR_NONE; - uint32_t f_min; - int32_t direction, gdirection; - uint8_t amp, phs; - struct st25r3916AatTuneParams tp = *tuningParams; // local copy to obey const - - tuningStatus->aat_a = tuningParams->aat_a_start; - tuningStatus->aat_b = tuningParams->aat_b_start; - - /* Get a proper start value */ - aatMeasure(tuningStatus->aat_a, tuningStatus->aat_b, &, &phs, &tuningStatus->measureCnt); - f_min = aatCalcF(&tp, amp, phs); - direction = 0; - - st25r3916AatLog("%d %d: %d***\n", tuningStatus->aat_a, tuningStatus->aat_b, f_min); - - do { - direction = - 0; /* Initially and after reducing step sizes we don't have a previous direction */ - do { - /* With the greedy step below always executed aftwards the -direction does never need to be investigated */ - direction = aatSteepestDescent(&f_min, &tp, tuningStatus, direction, -direction); - if(tuningStatus->measureCnt > tp.measureLimit) { - err = ERR_OVERRUN; - break; - } - do { - gdirection = aatGreedyDescent(&f_min, &tp, tuningStatus, direction); - if(tuningStatus->measureCnt > tp.measureLimit) { - err = ERR_OVERRUN; - break; - } - } while(0 != gdirection); - } while(0 != direction); - tp.aat_a_stepWidth /= 2U; /* Reduce step sizes */ - tp.aat_b_stepWidth /= 2U; - } while(tp.doDynamicSteps && ((tp.aat_a_stepWidth > 0U) || (tp.aat_b_stepWidth > 0U))); - - return err; -} - -/*******************************************************************************/ -static int32_t aatSteepestDescent( - uint32_t* f_min, - const struct st25r3916AatTuneParams* tuningParams, - struct st25r3916AatTuneResult* tuningStatus, - int32_t previousDir, - int32_t previousDir2) { - int32_t i; - uint8_t amp, phs; - uint32_t f; - int32_t bestdir = - 0; /* Negative direction: decrease, Positive: increase. (-)1: aat_a, (-)2: aat_b */ - - for(i = -2; i <= 2; i++) { - uint8_t a = tuningStatus->aat_a, b = tuningStatus->aat_b; - - if((0 == i) || (i == -previousDir) || - (i == -previousDir2)) { /* Skip no direction and avoid going backwards */ - continue; - } - if(0U != aatStepDacVals( - tuningParams, - &a, - &b, - i)) { /* If stepping did not change the value, omit this direction */ - continue; - } - - aatMeasure(a, b, &, &phs, &tuningStatus->measureCnt); - f = aatCalcF(tuningParams, amp, phs); - st25r3916AatLog("%d : %d %d: %d", i, a, b, f); - if(f < *f_min) { /* Value is better than all previous ones */ - st25r3916AatLog("*"); - *f_min = f; - bestdir = i; - } - st25r3916AatLog("\n"); - } - if(0 != bestdir) { /* Walk into the best direction */ - aatStepDacVals(tuningParams, &tuningStatus->aat_a, &tuningStatus->aat_b, bestdir); - } - return bestdir; -} - -/*******************************************************************************/ -static int32_t aatGreedyDescent( - uint32_t* f_min, - const struct st25r3916AatTuneParams* tuningParams, - struct st25r3916AatTuneResult* tuningStatus, - int32_t previousDir) { - uint8_t amp, phs; - uint32_t f; - uint8_t a = tuningStatus->aat_a, b = tuningStatus->aat_b; - - if(0U != aatStepDacVals( - tuningParams, - &a, - &b, - previousDir)) { /* If stepping did not change the value, omit this direction */ - return 0; - } - - aatMeasure(a, b, &, &phs, &tuningStatus->measureCnt); - f = aatCalcF(tuningParams, amp, phs); - st25r3916AatLog("g : %d %d: %d", a, b, f); - if(f < *f_min) { /* Value is better than previous one */ - st25r3916AatLog("*\n"); - tuningStatus->aat_a = a; - tuningStatus->aat_b = b; - *f_min = f; - return previousDir; - } - - st25r3916AatLog("\n"); - return 0; -} - -/*******************************************************************************/ -static uint32_t - aatCalcF(const struct st25r3916AatTuneParams* tuningParams, uint8_t amplitude, uint8_t phase) { - /* f(amp, pha) = (ampWeight * |amp - ampTarget|) + (phaWeight * |pha - phaTarget|) */ - uint8_t ampTarget = tuningParams->ampTarget; - uint8_t phaTarget = tuningParams->phaTarget; - - uint32_t ampWeight = tuningParams->ampWeight; - uint32_t phaWeight = tuningParams->phaWeight; - - /* Temp variables to avoid MISRA R10.8 (cast on composite expression) */ - uint8_t ad = ((amplitude > ampTarget) ? (amplitude - ampTarget) : (ampTarget - amplitude)); - uint8_t pd = ((phase > phaTarget) ? (phase - phaTarget) : (phaTarget - phase)); - - uint32_t ampDelta = (uint32_t)ad; - uint32_t phaDelta = (uint32_t)pd; - - return ((ampWeight * ampDelta) + (phaWeight * phaDelta)); -} - -/*******************************************************************************/ -static ReturnCode aatStepDacVals( - const struct st25r3916AatTuneParams* tuningParams, - uint8_t* a, - uint8_t* b, - int32_t dir) { - int16_t aat_a = (int16_t)*a, aat_b = (int16_t)*b; - - switch(abs(dir)) { /* Advance by steps size in requested direction */ - case 1: - aat_a = (dir < 0) ? (aat_a - (int16_t)tuningParams->aat_a_stepWidth) : - (aat_a + (int16_t)tuningParams->aat_a_stepWidth); - if(aat_a < (int16_t)tuningParams->aat_a_min) { - aat_a = (int16_t)tuningParams->aat_a_min; - } - if(aat_a > (int16_t)tuningParams->aat_a_max) { - aat_a = (int16_t)tuningParams->aat_a_max; - } - if((int16_t)*a == aat_a) { - return ERR_PARAM; - } - break; - case 2: - aat_b = (dir < 0) ? (aat_b - (int16_t)tuningParams->aat_b_stepWidth) : - (aat_b + (int16_t)tuningParams->aat_b_stepWidth); - if(aat_b < (int16_t)tuningParams->aat_b_min) { - aat_b = (int16_t)tuningParams->aat_b_min; - } - if(aat_b > (int16_t)tuningParams->aat_b_max) { - aat_b = (int16_t)tuningParams->aat_b_max; - } - if((int16_t)*b == aat_b) { - return ERR_PARAM; - } - break; - default: - return ERR_REQUEST; - } - /* We only get here if actual values have changed. In all other cases an error is returned */ - *a = (uint8_t)aat_a; - *b = (uint8_t)aat_b; - - return ERR_NONE; -} - -/*******************************************************************************/ -static ReturnCode aatMeasure( - uint8_t serCap, - uint8_t parCap, - uint8_t* amplitude, - uint8_t* phase, - uint16_t* measureCnt) { - ReturnCode err; - - *amplitude = 0; - *phase = 0; - - st25r3916WriteRegister(ST25R3916_REG_ANT_TUNE_A, serCap); - st25r3916WriteRegister(ST25R3916_REG_ANT_TUNE_B, parCap); - - /* Wait till caps have settled.. */ - platformDelay(ST25R3916_AAT_CAP_DELAY_MAX); - - /* Get amplitude and phase .. */ - err = rfalChipMeasureAmplitude(amplitude); - if(ERR_NONE == err) { - err = rfalChipMeasurePhase(phase); - } - - if(measureCnt != NULL) { - (*measureCnt)++; - } - return err; -} diff --git a/lib/ST25RFAL002/source/st25r3916/st25r3916_aat.h b/lib/ST25RFAL002/source/st25r3916/st25r3916_aat.h deleted file mode 100644 index 4c97ad35571..00000000000 --- a/lib/ST25RFAL002/source/st25r3916/st25r3916_aat.h +++ /dev/null @@ -1,109 +0,0 @@ -/****************************************************************************** - * \attention - * - *

    © COPYRIGHT 2020 STMicroelectronics

    - * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R3916 firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file st25r3916_aat.h - * - * \author - * - * \brief ST25R3916 Antenna Tuning - * - * The antenna tuning algorithm tries to find the optimal settings for - * the AAT_A and AAT_B registers, which are connected to variable capacitors - * to tune the antenna matching. - * - */ - -#ifndef ST25R3916_AAT_H -#define ST25R3916_AAT_H - -#include "platform.h" -#include "st_errno.h" - -/* -****************************************************************************** -* GLOBAL DATATYPES -****************************************************************************** -*/ - -/*! - * struct representing input parameters for the antenna tuning - */ -struct st25r3916AatTuneParams { - uint8_t aat_a_min; /*!< min value of A cap */ - uint8_t aat_a_max; /*!< max value of A cap */ - uint8_t aat_a_start; /*!< start value of A cap */ - uint8_t aat_a_stepWidth; /*!< increment stepWidth for A cap */ - uint8_t aat_b_min; /*!< min value of B cap */ - uint8_t aat_b_max; /*!< max value of B cap */ - uint8_t aat_b_start; /*!< start value of B cap */ - uint8_t aat_b_stepWidth; /*!< increment stepWidth for B cap */ - - uint8_t phaTarget; /*!< target phase */ - uint8_t phaWeight; /*!< weight of target phase */ - uint8_t ampTarget; /*!< target amplitude */ - uint8_t ampWeight; /*!< weight of target amplitude */ - - bool doDynamicSteps; /*!< dynamically reduce step size in algo */ - uint8_t measureLimit; /*!< max number of allowed steps/measurements */ -}; - -/*! - * struct representing out parameters for the antenna tuning - */ -struct st25r3916AatTuneResult { - uint8_t aat_a; /*!< serial cap after tuning */ - uint8_t aat_b; /*!< parallel cap after tuning */ - uint8_t pha; /*!< phase after tuning */ - uint8_t amp; /*!< amplitude after tuning */ - uint16_t measureCnt; /*!< number of measures performed */ -}; - -/*! - ***************************************************************************** - * \brief Perform antenna tuning - * - * This function starts an antenna tuning procedure by modifying the serial - * and parallel capacitors of the antenna matching circuit via the AAT_A - * and AAT_B registers. - * - * \param[in] tuningParams : Input parameters for the tuning algorithm. If NULL - * default values will be used. - * \param[out] tuningStatus : Result information of performed tuning. If NULL - * no further information is returned, only registers - * ST25R3916 (AAT_A,B) will be adapted. - * - * \return ERR_IO : Error during communication. - * \return ERR_PARAM : Invalid input parameters - * \return ERR_NONE : No error. - * - ***************************************************************************** - */ -extern ReturnCode st25r3916AatTune( - const struct st25r3916AatTuneParams* tuningParams, - struct st25r3916AatTuneResult* tuningStatus); - -#endif /* ST25R3916_AAT_H */ diff --git a/lib/ST25RFAL002/source/st25r3916/st25r3916_com.c b/lib/ST25RFAL002/source/st25r3916/st25r3916_com.c deleted file mode 100644 index 40d65807d17..00000000000 --- a/lib/ST25RFAL002/source/st25r3916/st25r3916_com.c +++ /dev/null @@ -1,618 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

    © COPYRIGHT 2020 STMicroelectronics

    - * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R3916 firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file - * - * \author Gustavo Patricio - * - * \brief Implementation of ST25R3916 communication - * - */ - -/* -****************************************************************************** -* INCLUDES -****************************************************************************** -*/ - -#include "st25r3916.h" -#include "st25r3916_com.h" -#include "st25r3916_led.h" -#include "st_errno.h" -#include "platform.h" -#include "utils.h" - -/* -****************************************************************************** -* LOCAL DEFINES -****************************************************************************** -*/ - -#define ST25R3916_OPTIMIZE \ - true /*!< Optimization switch: false always write value to register */ -#define ST25R3916_I2C_ADDR \ - (0xA0U >> 1) /*!< ST25R3916's default I2C address */ -#define ST25R3916_REG_LEN 1U /*!< Byte length of a ST25R3916 register */ - -#define ST25R3916_WRITE_MODE \ - (0U << 6) /*!< ST25R3916 Operation Mode: Write */ -#define ST25R3916_READ_MODE \ - (1U << 6) /*!< ST25R3916 Operation Mode: Read */ -#define ST25R3916_CMD_MODE \ - (3U << 6) /*!< ST25R3916 Operation Mode: Direct Command */ -#define ST25R3916_FIFO_LOAD \ - (0x80U) /*!< ST25R3916 Operation Mode: FIFO Load */ -#define ST25R3916_FIFO_READ \ - (0x9FU) /*!< ST25R3916 Operation Mode: FIFO Read */ -#define ST25R3916_PT_A_CONFIG_LOAD \ - (0xA0U) /*!< ST25R3916 Operation Mode: Passive Target Memory A-Config Load */ -#define ST25R3916_PT_F_CONFIG_LOAD \ - (0xA8U) /*!< ST25R3916 Operation Mode: Passive Target Memory F-Config Load */ -#define ST25R3916_PT_TSN_DATA_LOAD \ - (0xACU) /*!< ST25R3916 Operation Mode: Passive Target Memory TSN Load */ -#define ST25R3916_PT_MEM_READ \ - (0xBFU) /*!< ST25R3916 Operation Mode: Passive Target Memory Read */ - -#define ST25R3916_CMD_LEN \ - (1U) /*!< ST25R3916 CMD length */ -#define ST25R3916_BUF_LEN \ - (ST25R3916_CMD_LEN + \ - ST25R3916_FIFO_DEPTH) /*!< ST25R3916 communication buffer: CMD + FIFO length */ - -/* -****************************************************************************** -* MACROS -****************************************************************************** -*/ -#ifdef RFAL_USE_I2C -#define st25r3916I2CStart() \ - platformI2CStart() /*!< ST25R3916 HAL I2C driver macro to start a I2C transfer */ -#define st25r3916I2CStop() \ - platformI2CStop() /*!< ST25R3916 HAL I2C driver macro to stop a I2C transfer */ -#define st25r3916I2CRepeatStart() \ - platformI2CRepeatStart() /*!< ST25R3916 HAL I2C driver macro to repeat Start */ -#define st25r3916I2CSlaveAddrWR(sA) \ - platformI2CSlaveAddrWR( \ - sA) /*!< ST25R3916 HAL I2C driver macro to repeat Start */ -#define st25r3916I2CSlaveAddrRD(sA) \ - platformI2CSlaveAddrRD( \ - sA) /*!< ST25R3916 HAL I2C driver macro to repeat Start */ -#endif /* RFAL_USE_I2C */ - -#if defined(ST25R_COM_SINGLETXRX) && !defined(RFAL_USE_I2C) -static uint8_t - comBuf[ST25R3916_BUF_LEN]; /*!< ST25R3916 communication buffer */ -static uint16_t comBufIt; /*!< ST25R3916 communication buffer iterator */ -#endif /* ST25R_COM_SINGLETXRX */ - -/* - ****************************************************************************** - * LOCAL FUNCTION PROTOTYPES - ****************************************************************************** - */ - -/*! - ****************************************************************************** - * \brief ST25R3916 communication Start - * - * This method performs the required actions to start communications with - * ST25R3916, either by SPI or I2C - ****************************************************************************** - */ -static void st25r3916comStart(void); - -/*! - ****************************************************************************** - * \brief ST25R3916 communication Stop - * - * This method performs the required actions to terminate communications with - * ST25R3916, either by SPI or I2C - ****************************************************************************** - */ -static void st25r3916comStop(void); - -/*! - ****************************************************************************** - * \brief ST25R3916 communication Repeat Start - * - * This method performs the required actions to repeat start a transmission - * with ST25R3916, either by SPI or I2C - ****************************************************************************** - */ -#ifdef RFAL_USE_I2C -static void st25r3916comRepeatStart(void); -#else -#define st25r3916comRepeatStart() -#endif /* RFAL_USE_I2C */ - -/*! - ****************************************************************************** - * \brief ST25R3916 communication Tx - * - * This method performs the required actions to transmit the given buffer - * to ST25R3916, either by SPI or I2C - * - * \param[in] txBuf : the buffer to transmit - * \param[in] txLen : the length of the buffer to transmit - * \param[in] last : true if last data to be transmitted - * \param[in] txOnly : true no reception is to be performed - * - ****************************************************************************** - */ -static void st25r3916comTx(const uint8_t* txBuf, uint16_t txLen, bool last, bool txOnly); - -/*! - ****************************************************************************** - * \brief ST25R3916 communication Rx - * - * This method performs the required actions to receive from ST25R3916 the given - * amount of bytes, either by SPI or I2C - * - * \param[out] rxBuf : the buffer place the received bytes - * \param[in] rxLen : the length to receive - * - ****************************************************************************** - */ -static void st25r3916comRx(uint8_t* rxBuf, uint16_t rxLen); - -/*! - ****************************************************************************** - * \brief ST25R3916 communication Tx Byte - * - * This helper method transmits a byte passed by value and not by reference - * - * \param[in] txByte : the value of the byte to be transmitted - * \param[in] last : true if last byte to be transmitted - * \param[in] txOnly : true no reception is to be performed - * - ****************************************************************************** - */ -static void st25r3916comTxByte(uint8_t txByte, bool last, bool txOnly); - -/* - ****************************************************************************** - * LOCAL FUNCTION - ****************************************************************************** - */ -static void st25r3916comStart(void) { - /* Make this operation atomic, disabling ST25R3916 interrupt during communications*/ - platformProtectST25RComm(); - -#ifdef RFAL_USE_I2C - /* I2C Start and send Slave Address */ - st25r3916I2CStart(); - st25r3916I2CSlaveAddrWR(ST25R3916_I2C_ADDR); -#else - /* Perform the chip select */ - platformSpiSelect(); - -#if defined(ST25R_COM_SINGLETXRX) - comBufIt = 0; /* reset local buffer position */ -#endif /* ST25R_COM_SINGLETXRX */ - -#endif /* RFAL_USE_I2C */ -} - -/*******************************************************************************/ -static void st25r3916comStop(void) { -#ifdef RFAL_USE_I2C - /* Generate Stop signal */ - st25r3916I2CStop(); -#else - /* Release the chip select */ - platformSpiDeselect(); -#endif /* RFAL_USE_I2C */ - - /* reEnable the ST25R3916 interrupt */ - platformUnprotectST25RComm(); -} - -/*******************************************************************************/ -#ifdef RFAL_USE_I2C -static void st25r3916comRepeatStart(void) { - st25r3916I2CRepeatStart(); - st25r3916I2CSlaveAddrRD(ST25R3916_I2C_ADDR); -} -#endif /* RFAL_USE_I2C */ - -/*******************************************************************************/ -static void st25r3916comTx(const uint8_t* txBuf, uint16_t txLen, bool last, bool txOnly) { - NO_WARNING(last); - NO_WARNING(txOnly); - - if(txLen > 0U) { -#ifdef RFAL_USE_I2C - platformI2CTx(txBuf, txLen, last, txOnly); -#else /* RFAL_USE_I2C */ - -#ifdef ST25R_COM_SINGLETXRX - - ST_MEMCPY( - &comBuf[comBufIt], - txBuf, - MIN(txLen, - (ST25R3916_BUF_LEN - - comBufIt))); /* copy tx data to local buffer */ - comBufIt += - MIN(txLen, - (ST25R3916_BUF_LEN - - comBufIt)); /* store position on local buffer */ - - if(last && txOnly) /* only perform SPI transaction if no Rx will follow */ - { - platformSpiTxRx(comBuf, NULL, comBufIt); - } - -#else - platformSpiTxRx(txBuf, NULL, txLen); -#endif /* ST25R_COM_SINGLETXRX */ - -#endif /* RFAL_USE_I2C */ - } -} - -/*******************************************************************************/ -static void st25r3916comRx(uint8_t* rxBuf, uint16_t rxLen) { - if(rxLen > 0U) { -#ifdef RFAL_USE_I2C - platformI2CRx(rxBuf, rxLen); -#else /* RFAL_USE_I2C */ - -#ifdef ST25R_COM_SINGLETXRX - ST_MEMSET( - &comBuf[comBufIt], - 0x00, - MIN(rxLen, - (ST25R3916_BUF_LEN - - comBufIt))); /* clear outgoing buffer */ - platformSpiTxRx( - comBuf, - comBuf, - MIN((comBufIt + rxLen), - ST25R3916_BUF_LEN)); /* transceive as a single SPI call */ - ST_MEMCPY( - rxBuf, - &comBuf[comBufIt], - MIN(rxLen, - (ST25R3916_BUF_LEN - - comBufIt))); /* copy from local buf to output buffer and skip cmd byte */ -#else - if(rxBuf != NULL) { - ST_MEMSET( - rxBuf, 0x00, rxLen); /* clear outgoing buffer */ - } - platformSpiTxRx(NULL, rxBuf, rxLen); -#endif /* ST25R_COM_SINGLETXRX */ -#endif /* RFAL_USE_I2C */ - } -} - -/*******************************************************************************/ -static void st25r3916comTxByte(uint8_t txByte, bool last, bool txOnly) { - uint8_t val = txByte; /* MISRA 17.8: use intermediate variable */ - st25r3916comTx(&val, ST25R3916_REG_LEN, last, txOnly); -} - -/* -****************************************************************************** -* GLOBAL FUNCTIONS -****************************************************************************** -*/ - -/*******************************************************************************/ -ReturnCode st25r3916ReadRegister(uint8_t reg, uint8_t* val) { - return st25r3916ReadMultipleRegisters(reg, val, ST25R3916_REG_LEN); -} - -/*******************************************************************************/ -ReturnCode st25r3916ReadMultipleRegisters(uint8_t reg, uint8_t* values, uint8_t length) { - if(length > 0U) { - st25r3916comStart(); - - /* If is a space-B register send a direct command first */ - if((reg & ST25R3916_SPACE_B) != 0U) { - st25r3916comTxByte(ST25R3916_CMD_SPACE_B_ACCESS, false, false); - } - - st25r3916comTxByte(((reg & ~ST25R3916_SPACE_B) | ST25R3916_READ_MODE), true, false); - st25r3916comRepeatStart(); - st25r3916comRx(values, length); - st25r3916comStop(); - } - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode st25r3916WriteRegister(uint8_t reg, uint8_t val) { - uint8_t value = val; /* MISRA 17.8: use intermediate variable */ - return st25r3916WriteMultipleRegisters(reg, &value, ST25R3916_REG_LEN); -} - -/*******************************************************************************/ -ReturnCode st25r3916WriteMultipleRegisters(uint8_t reg, const uint8_t* values, uint8_t length) { - if(length > 0U) { - st25r3916comStart(); - - if((reg & ST25R3916_SPACE_B) != 0U) { - st25r3916comTxByte(ST25R3916_CMD_SPACE_B_ACCESS, false, true); - } - - st25r3916comTxByte(((reg & ~ST25R3916_SPACE_B) | ST25R3916_WRITE_MODE), false, true); - st25r3916comTx(values, length, true, true); - st25r3916comStop(); - - /* Send a WriteMultiReg event to LED handling */ - st25r3916ledEvtWrMultiReg(reg, values, length); - } - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode st25r3916WriteFifo(const uint8_t* values, uint16_t length) { - if(length > ST25R3916_FIFO_DEPTH) { - return ERR_PARAM; - } - - if(length > 0U) { - st25r3916comStart(); - st25r3916comTxByte(ST25R3916_FIFO_LOAD, false, true); - st25r3916comTx(values, length, true, true); - st25r3916comStop(); - } - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode st25r3916ReadFifo(uint8_t* buf, uint16_t length) { - if(length > 0U) { - st25r3916comStart(); - st25r3916comTxByte(ST25R3916_FIFO_READ, true, false); - - st25r3916comRepeatStart(); - st25r3916comRx(buf, length); - st25r3916comStop(); - } - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode st25r3916WritePTMem(const uint8_t* values, uint16_t length) { - if(length > ST25R3916_PTM_LEN) { - return ERR_PARAM; - } - - if(length > 0U) { - st25r3916comStart(); - st25r3916comTxByte(ST25R3916_PT_A_CONFIG_LOAD, false, true); - st25r3916comTx(values, length, true, true); - st25r3916comStop(); - } - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode st25r3916ReadPTMem(uint8_t* values, uint16_t length) { - uint8_t - tmp[ST25R3916_REG_LEN + - ST25R3916_PTM_LEN]; /* local buffer to handle prepended byte on I2C and SPI */ - - if(length > 0U) { - if(length > ST25R3916_PTM_LEN) { - return ERR_PARAM; - } - - st25r3916comStart(); - st25r3916comTxByte(ST25R3916_PT_MEM_READ, true, false); - - st25r3916comRepeatStart(); - st25r3916comRx(tmp, (ST25R3916_REG_LEN + length)); /* skip prepended byte */ - st25r3916comStop(); - - /* Copy PTMem content without prepended byte */ - ST_MEMCPY(values, (tmp + ST25R3916_REG_LEN), length); - } - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode st25r3916WritePTMemF(const uint8_t* values, uint16_t length) { - if(length > (ST25R3916_PTM_F_LEN + ST25R3916_PTM_TSN_LEN)) { - return ERR_PARAM; - } - - if(length > 0U) { - st25r3916comStart(); - st25r3916comTxByte(ST25R3916_PT_F_CONFIG_LOAD, false, true); - st25r3916comTx(values, length, true, true); - st25r3916comStop(); - } - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode st25r3916WritePTMemTSN(const uint8_t* values, uint16_t length) { - if(length > ST25R3916_PTM_TSN_LEN) { - return ERR_PARAM; - } - - if(length > 0U) { - st25r3916comStart(); - st25r3916comTxByte(ST25R3916_PT_TSN_DATA_LOAD, false, true); - st25r3916comTx(values, length, true, true); - st25r3916comStop(); - } - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode st25r3916ExecuteCommand(uint8_t cmd) { - st25r3916comStart(); - st25r3916comTxByte((cmd | ST25R3916_CMD_MODE), true, true); - st25r3916comStop(); - - /* Send a cmd event to LED handling */ - st25r3916ledEvtCmd(cmd); - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode st25r3916ReadTestRegister(uint8_t reg, uint8_t* val) { - st25r3916comStart(); - st25r3916comTxByte(ST25R3916_CMD_TEST_ACCESS, false, false); - st25r3916comTxByte((reg | ST25R3916_READ_MODE), true, false); - st25r3916comRepeatStart(); - st25r3916comRx(val, ST25R3916_REG_LEN); - st25r3916comStop(); - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode st25r3916WriteTestRegister(uint8_t reg, uint8_t val) { - uint8_t value = val; /* MISRA 17.8: use intermediate variable */ - - st25r3916comStart(); - st25r3916comTxByte(ST25R3916_CMD_TEST_ACCESS, false, true); - st25r3916comTxByte((reg | ST25R3916_WRITE_MODE), false, true); - st25r3916comTx(&value, ST25R3916_REG_LEN, true, true); - st25r3916comStop(); - - return ERR_NONE; -} - -/*******************************************************************************/ -ReturnCode st25r3916ClrRegisterBits(uint8_t reg, uint8_t clr_mask) { - ReturnCode ret; - uint8_t rdVal; - - /* Read current reg value */ - EXIT_ON_ERR(ret, st25r3916ReadRegister(reg, &rdVal)); - - /* Only perform a Write if value to be written is different */ - if(ST25R3916_OPTIMIZE && (rdVal == (uint8_t)(rdVal & ~clr_mask))) { - return ERR_NONE; - } - - /* Write new reg value */ - return st25r3916WriteRegister(reg, (uint8_t)(rdVal & ~clr_mask)); -} - -/*******************************************************************************/ -ReturnCode st25r3916SetRegisterBits(uint8_t reg, uint8_t set_mask) { - ReturnCode ret; - uint8_t rdVal; - - /* Read current reg value */ - EXIT_ON_ERR(ret, st25r3916ReadRegister(reg, &rdVal)); - - /* Only perform a Write if the value to be written is different */ - if(ST25R3916_OPTIMIZE && (rdVal == (rdVal | set_mask))) { - return ERR_NONE; - } - - /* Write new reg value */ - return st25r3916WriteRegister(reg, (rdVal | set_mask)); -} - -/*******************************************************************************/ -ReturnCode st25r3916ChangeRegisterBits(uint8_t reg, uint8_t valueMask, uint8_t value) { - return st25r3916ModifyRegister(reg, valueMask, (valueMask & value)); -} - -/*******************************************************************************/ -ReturnCode st25r3916ModifyRegister(uint8_t reg, uint8_t clr_mask, uint8_t set_mask) { - ReturnCode ret; - uint8_t rdVal; - uint8_t wrVal; - - /* Read current reg value */ - EXIT_ON_ERR(ret, st25r3916ReadRegister(reg, &rdVal)); - - /* Compute new value */ - wrVal = (uint8_t)(rdVal & ~clr_mask); - wrVal |= set_mask; - - /* Only perform a Write if the value to be written is different */ - if(ST25R3916_OPTIMIZE && (rdVal == wrVal)) { - return ERR_NONE; - } - - /* Write new reg value */ - return st25r3916WriteRegister(reg, wrVal); -} - -/*******************************************************************************/ -ReturnCode st25r3916ChangeTestRegisterBits(uint8_t reg, uint8_t valueMask, uint8_t value) { - ReturnCode ret; - uint8_t rdVal; - uint8_t wrVal; - - /* Read current reg value */ - EXIT_ON_ERR(ret, st25r3916ReadTestRegister(reg, &rdVal)); - - /* Compute new value */ - wrVal = (uint8_t)(rdVal & ~valueMask); - wrVal |= (uint8_t)(value & valueMask); - - /* Only perform a Write if the value to be written is different */ - if(ST25R3916_OPTIMIZE && (rdVal == wrVal)) { - return ERR_NONE; - } - - /* Write new reg value */ - return st25r3916WriteTestRegister(reg, wrVal); -} - -/*******************************************************************************/ -bool st25r3916CheckReg(uint8_t reg, uint8_t mask, uint8_t val) { - uint8_t regVal; - - regVal = 0; - st25r3916ReadRegister(reg, ®Val); - - return ((regVal & mask) == val); -} - -/*******************************************************************************/ -bool st25r3916IsRegValid(uint8_t reg) { -#pragma GCC diagnostic ignored "-Wtype-limits" - if(!(((int16_t)reg >= (int32_t)ST25R3916_REG_IO_CONF1) && - (reg <= (ST25R3916_SPACE_B | ST25R3916_REG_IC_IDENTITY)))) { - return false; - } - return true; -} diff --git a/lib/ST25RFAL002/source/st25r3916/st25r3916_irq.c b/lib/ST25RFAL002/source/st25r3916/st25r3916_irq.c deleted file mode 100644 index 74c2797ce4d..00000000000 --- a/lib/ST25RFAL002/source/st25r3916/st25r3916_irq.c +++ /dev/null @@ -1,231 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

    © COPYRIGHT 2020 STMicroelectronics

    - * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R3916 firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file - * - * \author Gustavo Patricio - * - * \brief ST25R3916 Interrupt handling - * - */ - -/* -****************************************************************************** -* INCLUDES -****************************************************************************** -*/ - -#include "st25r3916_irq.h" -#include "st25r3916_com.h" -#include "st25r3916_led.h" -#include "st25r3916.h" -#include "utils.h" - -/* - ****************************************************************************** - * LOCAL DATA TYPES - ****************************************************************************** - */ - -/*! Holds current and previous interrupt callback pointer as well as current Interrupt status and mask */ -typedef struct { - void (*prevCallback)(void); /*!< call back function for ST25R3916 interrupt */ - void (*callback)(void); /*!< call back function for ST25R3916 interrupt */ - uint32_t status; /*!< latest interrupt status */ - uint32_t mask; /*!< Interrupt mask. Negative mask = ST25R3916 mask regs */ -} st25r3916Interrupt; - -/* -****************************************************************************** -* GLOBAL DEFINES -****************************************************************************** -*/ - -/*! Length of the interrupt registers */ -#define ST25R3916_INT_REGS_LEN ((ST25R3916_REG_IRQ_TARGET - ST25R3916_REG_IRQ_MAIN) + 1U) - -/* -****************************************************************************** -* GLOBAL VARIABLES -****************************************************************************** -*/ - -static volatile st25r3916Interrupt st25r3916interrupt; /*!< Instance of ST25R3916 interrupt */ - -/* -****************************************************************************** -* GLOBAL FUNCTIONS -****************************************************************************** -*/ -void st25r3916InitInterrupts(void) { - platformIrqST25RPinInitialize(); - platformIrqST25RSetCallback(st25r3916Isr); - - st25r3916interrupt.callback = NULL; - st25r3916interrupt.prevCallback = NULL; - st25r3916interrupt.status = ST25R3916_IRQ_MASK_NONE; - st25r3916interrupt.mask = ST25R3916_IRQ_MASK_NONE; -} - -/*******************************************************************************/ -void st25r3916Isr(void) { - st25r3916CheckForReceivedInterrupts(); - - // Check if callback is set and run it - if(NULL != st25r3916interrupt.callback) { - st25r3916interrupt.callback(); - } -} - -/*******************************************************************************/ -void st25r3916CheckForReceivedInterrupts(void) { - uint8_t iregs[ST25R3916_INT_REGS_LEN]; - uint32_t irqStatus; - - /* Initialize iregs */ - irqStatus = ST25R3916_IRQ_MASK_NONE; - ST_MEMSET(iregs, (int32_t)(ST25R3916_IRQ_MASK_ALL & 0xFFU), ST25R3916_INT_REGS_LEN); - - /* In case the IRQ is Edge (not Level) triggered read IRQs until done */ - while(platformGpioIsHigh(ST25R_INT_PORT, ST25R_INT_PIN)) { - st25r3916ReadMultipleRegisters(ST25R3916_REG_IRQ_MAIN, iregs, ST25R3916_INT_REGS_LEN); - - irqStatus |= (uint32_t)iregs[0]; - irqStatus |= (uint32_t)iregs[1] << 8; - irqStatus |= (uint32_t)iregs[2] << 16; - irqStatus |= (uint32_t)iregs[3] << 24; - } - - /* Forward all interrupts, even masked ones to application */ - platformProtectST25RIrqStatus(); - st25r3916interrupt.status |= irqStatus; - platformUnprotectST25RIrqStatus(); - - /* Send an IRQ event to LED handling */ - st25r3916ledEvtIrq(st25r3916interrupt.status); -} - -/*******************************************************************************/ -void st25r3916ModifyInterrupts(uint32_t clr_mask, uint32_t set_mask) { - uint8_t i; - uint32_t old_mask; - uint32_t new_mask; - - old_mask = st25r3916interrupt.mask; - new_mask = ((~old_mask & set_mask) | (old_mask & clr_mask)); - st25r3916interrupt.mask &= ~clr_mask; - st25r3916interrupt.mask |= set_mask; - - for(i = 0; i < ST25R3916_INT_REGS_LEN; i++) { - if(((new_mask >> (8U * i)) & 0xFFU) == 0U) { - continue; - } - - st25r3916WriteRegister( - ST25R3916_REG_IRQ_MASK_MAIN + i, - (uint8_t)((st25r3916interrupt.mask >> (8U * i)) & 0xFFU)); - } - return; -} - -/*******************************************************************************/ -uint32_t st25r3916WaitForInterruptsTimed(uint32_t mask, uint16_t tmo) { - uint32_t tmrDelay; - uint32_t status; - - tmrDelay = platformTimerCreate(tmo); - - /* Run until specific interrupt has happen or the timer has expired */ - do { - status = (st25r3916interrupt.status & mask); - } while((!platformTimerIsExpired(tmrDelay) || (tmo == 0U)) && (status == 0U)); - - platformTimerDestroy(tmrDelay); - - status = st25r3916interrupt.status & mask; - - platformProtectST25RIrqStatus(); - st25r3916interrupt.status &= ~status; - platformUnprotectST25RIrqStatus(); - - return status; -} - -/*******************************************************************************/ -uint32_t st25r3916GetInterrupt(uint32_t mask) { - uint32_t irqs; - - irqs = (st25r3916interrupt.status & mask); - if(irqs != ST25R3916_IRQ_MASK_NONE) { - platformProtectST25RIrqStatus(); - st25r3916interrupt.status &= ~irqs; - platformUnprotectST25RIrqStatus(); - } - - return irqs; -} - -/*******************************************************************************/ -void st25r3916ClearAndEnableInterrupts(uint32_t mask) { - st25r3916GetInterrupt(mask); - st25r3916EnableInterrupts(mask); -} - -/*******************************************************************************/ -void st25r3916EnableInterrupts(uint32_t mask) { - st25r3916ModifyInterrupts(mask, 0); -} - -/*******************************************************************************/ -void st25r3916DisableInterrupts(uint32_t mask) { - st25r3916ModifyInterrupts(0, mask); -} - -/*******************************************************************************/ -void st25r3916ClearInterrupts(void) { - uint8_t iregs[ST25R3916_INT_REGS_LEN]; - - st25r3916ReadMultipleRegisters(ST25R3916_REG_IRQ_MAIN, iregs, ST25R3916_INT_REGS_LEN); - - platformProtectST25RIrqStatus(); - st25r3916interrupt.status = ST25R3916_IRQ_MASK_NONE; - platformUnprotectST25RIrqStatus(); - return; -} - -/*******************************************************************************/ -void st25r3916IRQCallbackSet(void (*cb)(void)) { - st25r3916interrupt.prevCallback = st25r3916interrupt.callback; - st25r3916interrupt.callback = cb; -} - -/*******************************************************************************/ -void st25r3916IRQCallbackRestore(void) { - st25r3916interrupt.callback = st25r3916interrupt.prevCallback; - st25r3916interrupt.prevCallback = NULL; -} diff --git a/lib/ST25RFAL002/source/st25r3916/st25r3916_irq.h b/lib/ST25RFAL002/source/st25r3916/st25r3916_irq.h deleted file mode 100644 index e8ce2d07a50..00000000000 --- a/lib/ST25RFAL002/source/st25r3916/st25r3916_irq.h +++ /dev/null @@ -1,296 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

    © COPYRIGHT 2020 STMicroelectronics

    - * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R3916 firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file - * - * \author Gustavo Patricio - * - * \brief ST25R3916 Interrupt handling - * - * \addtogroup RFAL - * @{ - * - * \addtogroup RFAL-HAL - * \brief RFAL Hardware Abstraction Layer - * @{ - * - * \addtogroup ST25R3916 - * \brief RFAL ST25R3916 Driver - * @{ - * - * \addtogroup ST25R3916_IRQ - * \brief RFAL ST25R3916 IRQ - * @{ - * - */ - -#ifndef ST25R3916_IRQ_H -#define ST25R3916_IRQ_H - -/* -****************************************************************************** -* INCLUDES -****************************************************************************** -*/ - -#include "platform.h" - -/* -****************************************************************************** -* GLOBAL DEFINES -****************************************************************************** -*/ - -#define ST25R3916_IRQ_MASK_ALL \ - (uint32_t)(0xFFFFFFFFUL) /*!< All ST25R3916 interrupt sources */ -#define ST25R3916_IRQ_MASK_NONE \ - (uint32_t)(0x00000000UL) /*!< No ST25R3916 interrupt source */ - -/* Main interrupt register */ -#define ST25R3916_IRQ_MASK_OSC \ - (uint32_t)(0x00000080U) /*!< ST25R3916 oscillator stable interrupt */ -#define ST25R3916_IRQ_MASK_FWL \ - (uint32_t)(0x00000040U) /*!< ST25R3916 FIFO water level interrupt */ -#define ST25R3916_IRQ_MASK_RXS \ - (uint32_t)(0x00000020U) /*!< ST25R3916 start of receive interrupt */ -#define ST25R3916_IRQ_MASK_RXE \ - (uint32_t)(0x00000010U) /*!< ST25R3916 end of receive interrupt */ -#define ST25R3916_IRQ_MASK_TXE \ - (uint32_t)(0x00000008U) /*!< ST25R3916 end of transmission interrupt */ -#define ST25R3916_IRQ_MASK_COL \ - (uint32_t)(0x00000004U) /*!< ST25R3916 bit collision interrupt */ -#define ST25R3916_IRQ_MASK_RX_REST \ - (uint32_t)(0x00000002U) /*!< ST25R3916 automatic reception restart interrupt */ -#define ST25R3916_IRQ_MASK_RFU \ - (uint32_t)(0x00000001U) /*!< ST25R3916 RFU interrupt */ - -/* Timer and NFC interrupt register */ -#define ST25R3916_IRQ_MASK_DCT \ - (uint32_t)(0x00008000U) /*!< ST25R3916 termination of direct command interrupt. */ -#define ST25R3916_IRQ_MASK_NRE \ - (uint32_t)(0x00004000U) /*!< ST25R3916 no-response timer expired interrupt */ -#define ST25R3916_IRQ_MASK_GPE \ - (uint32_t)(0x00002000U) /*!< ST25R3916 general purpose timer expired interrupt */ -#define ST25R3916_IRQ_MASK_EON \ - (uint32_t)(0x00001000U) /*!< ST25R3916 external field on interrupt */ -#define ST25R3916_IRQ_MASK_EOF \ - (uint32_t)(0x00000800U) /*!< ST25R3916 external field off interrupt */ -#define ST25R3916_IRQ_MASK_CAC \ - (uint32_t)(0x00000400U) /*!< ST25R3916 collision during RF collision avoidance interrupt */ -#define ST25R3916_IRQ_MASK_CAT \ - (uint32_t)(0x00000200U) /*!< ST25R3916 minimum guard time expired interrupt */ -#define ST25R3916_IRQ_MASK_NFCT \ - (uint32_t)(0x00000100U) /*!< ST25R3916 initiator bit rate recognised interrupt */ - -/* Error and wake-up interrupt register */ -#define ST25R3916_IRQ_MASK_CRC \ - (uint32_t)(0x00800000U) /*!< ST25R3916 CRC error interrupt */ -#define ST25R3916_IRQ_MASK_PAR \ - (uint32_t)(0x00400000U) /*!< ST25R3916 parity error interrupt */ -#define ST25R3916_IRQ_MASK_ERR2 \ - (uint32_t)(0x00200000U) /*!< ST25R3916 soft framing error interrupt */ -#define ST25R3916_IRQ_MASK_ERR1 \ - (uint32_t)(0x00100000U) /*!< ST25R3916 hard framing error interrupt */ -#define ST25R3916_IRQ_MASK_WT \ - (uint32_t)(0x00080000U) /*!< ST25R3916 wake-up interrupt */ -#define ST25R3916_IRQ_MASK_WAM \ - (uint32_t)(0x00040000U) /*!< ST25R3916 wake-up due to amplitude interrupt */ -#define ST25R3916_IRQ_MASK_WPH \ - (uint32_t)(0x00020000U) /*!< ST25R3916 wake-up due to phase interrupt */ -#define ST25R3916_IRQ_MASK_WCAP \ - (uint32_t)(0x00010000U) /*!< ST25R3916 wake-up due to capacitance measurement */ - -/* Passive Target Interrupt Register */ -#define ST25R3916_IRQ_MASK_PPON2 \ - (uint32_t)(0x80000000U) /*!< ST25R3916 PPON2 Field on waiting Timer interrupt */ -#define ST25R3916_IRQ_MASK_SL_WL \ - (uint32_t)(0x40000000U) /*!< ST25R3916 Passive target slot number water level interrupt */ -#define ST25R3916_IRQ_MASK_APON \ - (uint32_t)(0x20000000U) /*!< ST25R3916 Anticollision done and Field On interrupt */ -#define ST25R3916_IRQ_MASK_RXE_PTA \ - (uint32_t)(0x10000000U) /*!< ST25R3916 RXE with an automatic response interrupt */ -#define ST25R3916_IRQ_MASK_WU_F \ - (uint32_t)(0x08000000U) /*!< ST25R3916 212/424b/s Passive target interrupt: Active */ -#define ST25R3916_IRQ_MASK_RFU2 \ - (uint32_t)(0x04000000U) /*!< ST25R3916 RFU2 interrupt */ -#define ST25R3916_IRQ_MASK_WU_A_X \ - (uint32_t)(0x02000000U) /*!< ST25R3916 106kb/s Passive target state interrupt: Active* */ -#define ST25R3916_IRQ_MASK_WU_A \ - (uint32_t)(0x01000000U) /*!< ST25R3916 106kb/s Passive target state interrupt: Active */ - -/* -****************************************************************************** -* GLOBAL FUNCTION PROTOTYPES -****************************************************************************** -*/ - -/*! - ***************************************************************************** - * \brief Wait until an ST25R3916 interrupt occurs - * - * This function is used to access the ST25R3916 interrupt flags. Use this - * to wait for max. \a tmo milliseconds for the \b first interrupt indicated - * with mask \a mask to occur. - * - * \param[in] mask : mask indicating the interrupts to wait for. - * \param[in] tmo : time in milliseconds until timeout occurs. If set to 0 - * the functions waits forever. - * - * \return : 0 if timeout occurred otherwise a mask indicating the cleared - * interrupts. - * - ***************************************************************************** - */ -uint32_t st25r3916WaitForInterruptsTimed(uint32_t mask, uint16_t tmo); - -/*! - ***************************************************************************** - * \brief Get status for the given interrupt - * - * This function is used to check whether the interrupt given by \a mask - * has occurred. If yes the interrupt gets cleared. This function returns - * only status bits which are inside \a mask. - * - * \param[in] mask : mask indicating the interrupt to check for. - * - * \return the mask of the interrupts occurred - * - ***************************************************************************** - */ -uint32_t st25r3916GetInterrupt(uint32_t mask); - -/*! - ***************************************************************************** - * \brief Init the 3916 interrupt - * - * This function is used to check whether the interrupt given by \a mask - * has occurred. - * - ***************************************************************************** - */ -void st25r3916InitInterrupts(void); - -/*! - ***************************************************************************** - * \brief Modifies the Interrupt - * - * This function modifies the interrupt - * - * \param[in] clr_mask : bit mask to be cleared on the interrupt mask - * \param[in] set_mask : bit mask to be set on the interrupt mask - ***************************************************************************** - */ -void st25r3916ModifyInterrupts(uint32_t clr_mask, uint32_t set_mask); - -/*! - ***************************************************************************** - * \brief Checks received interrupts - * - * Checks received interrupts and saves the result into global params - ***************************************************************************** - */ -void st25r3916CheckForReceivedInterrupts(void); - -/*! - ***************************************************************************** - * \brief ISR Service routine - * - * This function modiefies the interrupt - ***************************************************************************** - */ -void st25r3916Isr(void); - -/*! - ***************************************************************************** - * \brief Enable a given ST25R3916 Interrupt source - * - * This function enables all interrupts given by \a mask, - * ST25R3916_IRQ_MASK_ALL enables all interrupts. - * - * \param[in] mask: mask indicating the interrupts to be enabled - * - ***************************************************************************** - */ -void st25r3916EnableInterrupts(uint32_t mask); - -/*! - ***************************************************************************** - * \brief Disable one or more a given ST25R3916 Interrupt sources - * - * This function disables all interrupts given by \a mask. 0xff disables all. - * - * \param[in] mask: mask indicating the interrupts to be disabled. - * - ***************************************************************************** - */ -void st25r3916DisableInterrupts(uint32_t mask); - -/*! - ***************************************************************************** - * \brief Clear all ST25R3916 irq flags - * - ***************************************************************************** - */ -void st25r3916ClearInterrupts(void); - -/*! - ***************************************************************************** - * \brief Clears and then enables the given ST25R3916 Interrupt sources - * - * \param[in] mask: mask indicating the interrupts to be cleared and enabled - ***************************************************************************** - */ -void st25r3916ClearAndEnableInterrupts(uint32_t mask); - -/*! - ***************************************************************************** - * \brief Sets IRQ callback for the ST25R3916 interrupt - * - ***************************************************************************** - */ -void st25r3916IRQCallbackSet(void (*cb)(void)); - -/*! - ***************************************************************************** - * \brief Sets IRQ callback for the ST25R3916 interrupt - * - ***************************************************************************** - */ -void st25r3916IRQCallbackRestore(void); - -#endif /* ST25R3916_IRQ_H */ - -/** - * @} - * - * @} - * - * @} - * - * @} - */ diff --git a/lib/ST25RFAL002/source/st25r3916/st25r3916_led.c b/lib/ST25RFAL002/source/st25r3916/st25r3916_led.c deleted file mode 100644 index 9737337ca34..00000000000 --- a/lib/ST25RFAL002/source/st25r3916/st25r3916_led.c +++ /dev/null @@ -1,148 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

    © COPYRIGHT 2020 STMicroelectronics

    - * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R3916 firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file - * - * \author Gustavo Patricio - * - * \brief ST25R3916 LEDs handling - * - */ - -/* -****************************************************************************** -* INCLUDES -****************************************************************************** -*/ - -#include "st25r3916_led.h" -#include "st25r3916_irq.h" -#include "st25r3916_com.h" -#include "st25r3916.h" - -/* -****************************************************************************** -* MACROS -****************************************************************************** -*/ - -#ifdef PLATFORM_LED_RX_PIN -#define st25r3916ledRxOn() \ - platformLedOn( \ - PLATFORM_LED_RX_PORT, \ - PLATFORM_LED_RX_PIN); /*!< LED Rx Pin On from system HAL */ -#define st25r3916ledRxOff() \ - platformLedOff( \ - PLATFORM_LED_RX_PORT, \ - PLATFORM_LED_RX_PIN); /*!< LED Rx Pin Off from system HAL */ -#else /* PLATFORM_LED_RX_PIN */ -#define st25r3916ledRxOn() -#define st25r3916ledRxOff() -#endif /* PLATFORM_LED_RX_PIN */ - -#ifdef PLATFORM_LED_FIELD_PIN -#define st25r3916ledFieldOn() \ - platformLedOn( \ - PLATFORM_LED_FIELD_PORT, \ - PLATFORM_LED_FIELD_PIN); /*!< LED Field Pin On from system HAL */ -#define st25r3916ledFieldOff() \ - platformLedOff( \ - PLATFORM_LED_FIELD_PORT, \ - PLATFORM_LED_FIELD_PIN); /*!< LED Field Pin Off from system HAL */ -#else /* PLATFORM_LED_FIELD_PIN */ -#define st25r3916ledFieldOn() -#define st25r3916ledFieldOff() -#endif /* PLATFORM_LED_FIELD_PIN */ - -/* -****************************************************************************** -* GLOBAL FUNCTIONS -****************************************************************************** -*/ - -void st25r3916ledInit(void) { - /* Initialize LEDs if existing and defined */ - platformLedsInitialize(); - - st25r3916ledRxOff(); - st25r3916ledFieldOff(); -} - -/*******************************************************************************/ -void st25r3916ledEvtIrq(uint32_t irqs) { - if((irqs & (ST25R3916_IRQ_MASK_TXE | ST25R3916_IRQ_MASK_CAT)) != 0U) { - st25r3916ledFieldOn(); - } - - if((irqs & (ST25R3916_IRQ_MASK_RXS | ST25R3916_IRQ_MASK_NFCT)) != 0U) { - st25r3916ledRxOn(); - } - - if((irqs & (ST25R3916_IRQ_MASK_RXE | ST25R3916_IRQ_MASK_NRE | ST25R3916_IRQ_MASK_RX_REST | - ST25R3916_IRQ_MASK_RXE_PTA | ST25R3916_IRQ_MASK_WU_A | ST25R3916_IRQ_MASK_WU_A_X | - ST25R3916_IRQ_MASK_WU_F | ST25R3916_IRQ_MASK_RFU2)) != 0U) { - st25r3916ledRxOff(); - } -} - -/*******************************************************************************/ -void st25r3916ledEvtWrReg(uint8_t reg, uint8_t val) { - if(reg == ST25R3916_REG_OP_CONTROL) { - if((ST25R3916_REG_OP_CONTROL_tx_en & val) != 0U) { - st25r3916ledFieldOn(); - } else { - st25r3916ledFieldOff(); - } - } -} - -/*******************************************************************************/ -void st25r3916ledEvtWrMultiReg(uint8_t reg, const uint8_t* vals, uint8_t len) { - uint8_t i; - - for(i = 0; i < (len); i++) { - st25r3916ledEvtWrReg((reg + i), vals[i]); - } -} - -/*******************************************************************************/ -void st25r3916ledEvtCmd(uint8_t cmd) { - if((cmd >= ST25R3916_CMD_TRANSMIT_WITH_CRC) && - (cmd <= ST25R3916_CMD_RESPONSE_RF_COLLISION_N)) { - st25r3916ledFieldOff(); - } - - if(cmd == ST25R3916_CMD_UNMASK_RECEIVE_DATA) { - st25r3916ledRxOff(); - } - - if(cmd == ST25R3916_CMD_SET_DEFAULT) { - st25r3916ledFieldOff(); - st25r3916ledRxOff(); - } -} diff --git a/lib/ST25RFAL002/source/st25r3916/st25r3916_led.h b/lib/ST25RFAL002/source/st25r3916/st25r3916_led.h deleted file mode 100644 index 376a8d7df7f..00000000000 --- a/lib/ST25RFAL002/source/st25r3916/st25r3916_led.h +++ /dev/null @@ -1,151 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

    © COPYRIGHT 2020 STMicroelectronics

    - * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R3916 firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file - * - * \author Gustavo Patricio - * - * \brief ST25R3916 LEDs handling - * - * - * \addtogroup RFAL - * @{ - * - * \addtogroup RFAL-HAL - * \brief RFAL Hardware Abstraction Layer - * @{ - * - * \addtogroup ST25R3916 - * \brief RFAL ST25R3916 Driver - * @{ - * - * \addtogroup ST25R3916_LED - * \brief RFAL ST25R3916 LED - * @{ - * - */ - -#ifndef ST25R3916_LED_H -#define ST25R3916_LED_H - -/* -****************************************************************************** -* INCLUDES -****************************************************************************** -*/ - -#include "platform.h" - -/* -****************************************************************************** -* GLOBAL DEFINES -****************************************************************************** -*/ - -/* -****************************************************************************** -* GLOBAL FUNCTION PROTOTYPES -****************************************************************************** -*/ - -/*! - ***************************************************************************** - * \brief ST25R3916 LED Initialize - * - * This function initializes the LEDs that represent ST25R3916 activity - * - ***************************************************************************** - */ -void st25r3916ledInit(void); - -/*! - ***************************************************************************** - * \brief ST25R3916 LED Event Interrupt - * - * This function should be called upon a ST25R3916 Interrupt, providing - * the interrupt event with ST25R3916 irq flags to update LEDs - * - * \param[in] irqs: ST25R3916 irqs mask - * - ***************************************************************************** - */ -void st25r3916ledEvtIrq(uint32_t irqs); - -/*! - ***************************************************************************** - * \brief ST25R3916 LED Event Write Register - * - * This function should be called on a ST25R3916 Write Register operation - * providing the event with the register and value to update LEDs - * - * \param[in] reg: ST25R3916 register to be written - * \param[in] val: value to be written on the register - * - ***************************************************************************** - */ -void st25r3916ledEvtWrReg(uint8_t reg, uint8_t val); - -/*! - ***************************************************************************** - * \brief ST25R3916 LED Event Write Multiple Register - * - * This function should be called upon a ST25R3916 Write Multiple Registers, - * providing the event with the registers and values to update LEDs - * - * \param[in] reg : ST25R3916 first register written - * \param[in] vals: pointer to the values written - * \param[in] len : number of registers written - * - ***************************************************************************** - */ -void st25r3916ledEvtWrMultiReg(uint8_t reg, const uint8_t* vals, uint8_t len); - -/*! - ***************************************************************************** - * \brief ST25R3916 LED Event Direct Command - * - * This function should be called upon a ST25R3916 direct command, providing - * the event with the command executed - * - * \param[in] cmd: ST25R3916 cmd executed - * - ***************************************************************************** - */ -void st25r3916ledEvtCmd(uint8_t cmd); - -#endif /* ST25R3916_LED_H */ - -/** - * @} - * - * @} - * - * @} - * - * @} - */ diff --git a/lib/ST25RFAL002/st_errno.h b/lib/ST25RFAL002/st_errno.h deleted file mode 100644 index cd706b3d4c5..00000000000 --- a/lib/ST25RFAL002/st_errno.h +++ /dev/null @@ -1,158 +0,0 @@ - -/****************************************************************************** - * @attention - * - *

    © COPYRIGHT 2018 STMicroelectronics

    - * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: STxxxx firmware - * LANGUAGE: ISO C99 - */ - -/*! \file st_errno.h - * - * \author - * - * \brief Main error codes - * - */ - -#ifndef ST_ERRNO_H -#define ST_ERRNO_H - -/* -****************************************************************************** -* INCLUDES -****************************************************************************** -*/ - -#include - -/* -****************************************************************************** -* GLOBAL DATA TYPES -****************************************************************************** -*/ - -typedef uint16_t ReturnCode; /*!< Standard Return Code type from function. */ - -/* -****************************************************************************** -* DEFINES -****************************************************************************** -*/ - -/* - * Error codes to be used within the application. - * They are represented by an uint8_t - */ -enum { - ERR_NONE = 0, /*!< no error occurred */ - ERR_NOMEM = 1, /*!< not enough memory to perform the requested operation */ - ERR_BUSY = 2, /*!< device or resource busy */ - ERR_IO = 3, /*!< generic IO error */ - ERR_TIMEOUT = 4, /*!< error due to timeout */ - ERR_REQUEST = 5, /*!< invalid request or requested function can't be executed at the moment */ - ERR_NOMSG = 6, /*!< No message of desired type */ - ERR_PARAM = 7, /*!< Parameter error */ - ERR_SYSTEM = 8, /*!< System error */ - ERR_FRAMING = 9, /*!< Framing error */ - ERR_OVERRUN = 10, /*!< lost one or more received bytes */ - ERR_PROTO = 11, /*!< protocol error */ - ERR_INTERNAL = 12, /*!< Internal Error */ - ERR_AGAIN = 13, /*!< Call again */ - ERR_MEM_CORRUPT = 14, /*!< memory corruption */ - ERR_NOT_IMPLEMENTED = 15, /*!< not implemented */ - ERR_PC_CORRUPT = - 16, /*!< Program Counter has been manipulated or spike/noise trigger illegal operation */ - ERR_SEND = 17, /*!< error sending*/ - ERR_IGNORE = 18, /*!< indicates error detected but to be ignored */ - ERR_SEMANTIC = 19, /*!< indicates error in state machine (unexpected cmd) */ - ERR_SYNTAX = 20, /*!< indicates error in state machine (unknown cmd) */ - ERR_CRC = 21, /*!< crc error */ - ERR_NOTFOUND = 22, /*!< transponder not found */ - ERR_NOTUNIQUE = 23, /*!< transponder not unique - more than one transponder in field */ - ERR_NOTSUPP = 24, /*!< requested operation not supported */ - ERR_WRITE = 25, /*!< write error */ - ERR_FIFO = 26, /*!< fifo over or underflow error */ - ERR_PAR = 27, /*!< parity error */ - ERR_DONE = 28, /*!< transfer has already finished */ - ERR_RF_COLLISION = - 29, /*!< collision error (Bit Collision or during RF Collision avoidance ) */ - ERR_HW_OVERRUN = 30, /*!< lost one or more received bytes */ - ERR_RELEASE_REQ = 31, /*!< device requested release */ - ERR_SLEEP_REQ = 32, /*!< device requested sleep */ - ERR_WRONG_STATE = 33, /*!< incorrent state for requested operation */ - ERR_MAX_RERUNS = 34, /*!< blocking procedure reached maximum runs */ - ERR_DISABLED = 35, /*!< operation aborted due to disabled configuration */ - ERR_HW_MISMATCH = 36, /*!< expected hw do not match */ - ERR_LINK_LOSS = - 37, /*!< Other device's field didn't behave as expected: turned off by Initiator in Passive mode, or AP2P did not turn on field */ - ERR_INVALID_HANDLE = 38, /*!< invalid or not initalized device handle */ - - ERR_INCOMPLETE_BYTE = 40, /*!< Incomplete byte rcvd */ - ERR_INCOMPLETE_BYTE_01 = 41, /*!< Incomplete byte rcvd - 1 bit */ - ERR_INCOMPLETE_BYTE_02 = 42, /*!< Incomplete byte rcvd - 2 bit */ - ERR_INCOMPLETE_BYTE_03 = 43, /*!< Incomplete byte rcvd - 3 bit */ - ERR_INCOMPLETE_BYTE_04 = 44, /*!< Incomplete byte rcvd - 4 bit */ - ERR_INCOMPLETE_BYTE_05 = 45, /*!< Incomplete byte rcvd - 5 bit */ - ERR_INCOMPLETE_BYTE_06 = 46, /*!< Incomplete byte rcvd - 6 bit */ - ERR_INCOMPLETE_BYTE_07 = 47, /*!< Incomplete byte rcvd - 7 bit */ -}; - -/* General Sub-category number */ -#define ERR_GENERIC_GRP (0x0000) /*!< Reserved value for generic error no */ -#define ERR_WARN_GRP (0x0100) /*!< Errors which are not expected in normal operation */ -#define ERR_PROCESS_GRP (0x0200) /*!< Processes management errors */ -#define ERR_SIO_GRP (0x0800) /*!< SIO errors due to logging */ -#define ERR_RINGBUF_GRP (0x0900) /*!< Ring Buffer errors */ -#define ERR_MQ_GRP (0x0A00) /*!< MQ errors */ -#define ERR_TIMER_GRP (0x0B00) /*!< Timer errors */ -#define ERR_RFAL_GRP (0x0C00) /*!< RFAL errors */ -#define ERR_UART_GRP (0x0D00) /*!< UART errors */ -#define ERR_SPI_GRP (0x0E00) /*!< SPI errors */ -#define ERR_I2C_GRP (0x0F00) /*!< I2c errors */ - -#define ERR_INSERT_SIO_GRP(x) (ERR_SIO_GRP | x) /*!< Insert the SIO grp */ -#define ERR_INSERT_RINGBUF_GRP(x) (ERR_RINGBUF_GRP | x) /*!< Insert the Ring Buffer grp */ -#define ERR_INSERT_RFAL_GRP(x) (ERR_RFAL_GRP | x) /*!< Insert the RFAL grp */ -#define ERR_INSERT_SPI_GRP(x) (ERR_SPI_GRP | x) /*!< Insert the spi grp */ -#define ERR_INSERT_I2C_GRP(x) (ERR_I2C_GRP | x) /*!< Insert the i2c grp */ -#define ERR_INSERT_UART_GRP(x) (ERR_UART_GRP | x) /*!< Insert the uart grp */ -#define ERR_INSERT_TIMER_GRP(x) (ERR_TIMER_GRP | x) /*!< Insert the timer grp */ -#define ERR_INSERT_MQ_GRP(x) (ERR_MQ_GRP | x) /*!< Insert the mq grp */ -#define ERR_INSERT_PROCESS_GRP(x) (ERR_PROCESS_GRP | x) /*!< Insert the process grp */ -#define ERR_INSERT_WARN_GRP(x) (ERR_WARN_GRP | x) /*!< Insert the i2c grp */ -#define ERR_INSERT_GENERIC_GRP(x) (ERR_GENERIC_GRP | x) /*!< Insert the generic grp */ - -/* -****************************************************************************** -* GLOBAL MACROS -****************************************************************************** -*/ - -#define ERR_NO_MASK(x) (x & 0x00FF) /*!< Mask the error number */ - -/*! Common code to exit a function with the error if function f return error */ -#define EXIT_ON_ERR(r, f) \ - if(ERR_NONE != (r = f)) { \ - return r; \ - } - -#endif /* ST_ERRNO_H */ diff --git a/lib/ST25RFAL002/timer.c b/lib/ST25RFAL002/timer.c deleted file mode 100644 index ea0678a600d..00000000000 --- a/lib/ST25RFAL002/timer.c +++ /dev/null @@ -1,105 +0,0 @@ -/****************************************************************************** - * @attention - * - *

    © COPYRIGHT 2016 STMicroelectronics

    - * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ -/* - * PROJECT: ST25R391x firmware - * $Revision: $ - * LANGUAGE: ANSI C - */ - -/*! \file timer.c - * - * \brief SW Timer implementation - * - * \author Gustavo Patricio - * - * This module makes use of a System Tick in millisconds and provides - * an abstraction for SW timers - * - */ - -/* -****************************************************************************** -* INCLUDES -****************************************************************************** -*/ -#include "timer.h" -#include - -/* -****************************************************************************** -* LOCAL DEFINES -****************************************************************************** -*/ - -/* -****************************************************************************** -* LOCAL VARIABLES -****************************************************************************** -*/ - -static uint32_t timerStopwatchTick; - -/* -****************************************************************************** -* GLOBAL FUNCTIONS -****************************************************************************** -*/ - -/*******************************************************************************/ -uint32_t timerCalculateTimer(uint16_t time) { - return (furi_get_tick() + time); -} - -/*******************************************************************************/ -bool timerIsExpired(uint32_t timer) { - uint32_t uDiff; - int32_t sDiff; - - uDiff = (timer - furi_get_tick()); /* Calculate the diff between the timers */ - sDiff = uDiff; /* Convert the diff to a signed var */ - - /* Check if the given timer has expired already */ - if(sDiff < 0) { - return true; - } - - return false; -} - -/*******************************************************************************/ -void timerDelay(uint16_t tOut) { - uint32_t t; - - /* Calculate the timer and wait blocking until is running */ - t = timerCalculateTimer(tOut); - while(timerIsRunning(t)) - ; -} - -/*******************************************************************************/ -void timerStopwatchStart(void) { - timerStopwatchTick = furi_get_tick(); -} - -/*******************************************************************************/ -uint32_t timerStopwatchMeasure(void) { - return (uint32_t)(furi_get_tick() - timerStopwatchTick); -} diff --git a/lib/ST25RFAL002/timer.h b/lib/ST25RFAL002/timer.h deleted file mode 100644 index b5f5eb329d5..00000000000 --- a/lib/ST25RFAL002/timer.h +++ /dev/null @@ -1,125 +0,0 @@ -#pragma once -/****************************************************************************** - * @attention - * - *

    © COPYRIGHT 2016 STMicroelectronics

    - * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ -/* - * PROJECT: ST25R391x firmware - * $Revision: $ - * LANGUAGE: ANSI C - */ - -/*! \file timer.h - * - * \brief SW Timer implementation header file - * - * This module makes use of a System Tick in millisconds and provides - * an abstraction for SW timers - * - */ - -/* -****************************************************************************** -* INCLUDES -****************************************************************************** -*/ -#include -#include - -/* -****************************************************************************** -* GLOBAL MACROS -****************************************************************************** -*/ -#define timerIsRunning(t) (!timerIsExpired(t)) - -/* -****************************************************************************** -* GLOBAL DEFINES -****************************************************************************** -*/ - -/*! - ***************************************************************************** - * \brief Calculate Timer - * - * This method calculates when the timer will be expired given the amount - * time in milliseconds /a tOut. - * Once the timer has been calculated it will then be used to check when - * it expires. - * - * \see timersIsExpired - * - * \param[in] time : time/duration in Milliseconds for the timer - * - * \return u32 : The new timer calculated based on the given time - ***************************************************************************** - */ -uint32_t timerCalculateTimer(uint16_t time); - -/*! - ***************************************************************************** - * \brief Checks if a Timer is Expired - * - * This method checks if a timer has already expired. - * Based on the given timer previously calculated it checks if this timer - * has already elapsed - * - * \see timersCalculateTimer - * - * \param[in] timer : the timer to check - * - * \return true : timer has already expired - * \return false : timer is still running - ***************************************************************************** - */ -bool timerIsExpired(uint32_t timer); - -/*! - ***************************************************************************** - * \brief Performs a Delay - * - * This method performs a delay for the given amount of time in Milliseconds - * - * \param[in] time : time/duration in Milliseconds of the delay - * - ***************************************************************************** - */ -void timerDelay(uint16_t time); - -/*! - ***************************************************************************** - * \brief Stopwatch start - * - * This method initiates the stopwatch to later measure the time in ms - * - ***************************************************************************** - */ -void timerStopwatchStart(void); - -/*! - ***************************************************************************** - * \brief Stopwatch Measure - * - * This method returns the elapsed time in ms since the stopwatch was initiated - * - * \return The time in ms since the stopwatch was started - ***************************************************************************** - */ -uint32_t timerStopwatchMeasure(void); diff --git a/lib/ST25RFAL002/utils.h b/lib/ST25RFAL002/utils.h deleted file mode 100644 index 44a141986c5..00000000000 --- a/lib/ST25RFAL002/utils.h +++ /dev/null @@ -1,100 +0,0 @@ - -/****************************************************************************** - * @attention - * - *

    © COPYRIGHT 2018 STMicroelectronics

    - * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: NFCC firmware - * $Revision: $ - * LANGUAGE: ISO C99 - */ - -/*! \file - * - * \author Ulrich Herrmann - * - * \brief Common and helpful macros - * - */ - -#ifndef UTILS_H -#define UTILS_H - -/* -****************************************************************************** -* INCLUDES -****************************************************************************** -*/ -#include -#include - -/* -****************************************************************************** -* GLOBAL MACROS -****************************************************************************** -*/ -/*! - * this macro evaluates an error variable \a ERR against an error code \a EC. - * in case it is not equal it jumps to the given label \a LABEL. - */ -#define EVAL_ERR_NE_GOTO(EC, ERR, LABEL) \ - if(EC != ERR) goto LABEL; - -/*! - * this macro evaluates an error variable \a ERR against an error code \a EC. - * in case it is equal it jumps to the given label \a LABEL. - */ -#define EVAL_ERR_EQ_GOTO(EC, ERR, LABEL) \ - if(EC == ERR) goto LABEL; -#define BITMASK_1 (0x01) /*!< Bit mask for lsb bit */ -#define BITMASK_2 (0x03) /*!< Bit mask for two lsb bits */ -#define BITMASK_3 (0x07) /*!< Bit mask for three lsb bits */ -#define BITMASK_4 (0x0F) /*!< Bit mask for four lsb bits */ -#define U16TOU8(a) ((a)&0x00FF) /*!< Cast 16-bit unsigned to 8-bit unsigned */ -#define GETU16(a) \ - (uint16_t)( \ - (a[0] << 8) | a[1]) /*!< Cast two Big Endian 8-bits byte array to 16-bits unsigned */ - -#define REVERSE_BYTES(pData, nDataSize) \ - unsigned char swap, *lo = pData, *hi = pData + nDataSize - 1; \ - while(lo < hi) { \ - swap = *lo; \ - *lo++ = *hi; \ - *hi-- = swap; \ - } - -#define ST_MEMMOVE memmove /*!< map memmove to string library code */ -#define ST_MEMCPY memcpy /*!< map memcpy to string library code */ -#define ST_MEMSET memset /*!< map memset to string library code */ -#define ST_BYTECMP memcmp /*!< map bytecmp to string library code */ - -#define NO_WARNING(v) ((void)(v)) /*!< Macro to suppress compiler warning */ - -#ifndef NULL -#define NULL (void*)0 /*!< represents a NULL pointer */ -#endif /* !NULL */ - -/* -****************************************************************************** -* GLOBAL FUNCTION PROTOTYPES -****************************************************************************** -*/ - -#endif /* UTILS_H */ diff --git a/lib/digital_signal/SConscript b/lib/digital_signal/SConscript index 2ddf7a58b09..b94c26f780a 100644 --- a/lib/digital_signal/SConscript +++ b/lib/digital_signal/SConscript @@ -6,6 +6,7 @@ env.Append( ], SDK_HEADERS=[ File("digital_signal.h"), + File("digital_sequence.h"), ], ) diff --git a/lib/digital_signal/digital_sequence.c b/lib/digital_signal/digital_sequence.c new file mode 100644 index 00000000000..c85aae8c062 --- /dev/null +++ b/lib/digital_signal/digital_sequence.c @@ -0,0 +1,381 @@ +#include "digital_sequence.h" +#include "digital_signal_i.h" + +#include +#include + +#include +#include + +/** + * To enable debug output on an additional pin, set DIGITAL_SIGNAL_DEBUG_OUTPUT_PIN to the required + * GpioPin variable. It can be passed at compile time via the --extra-define fbt switch. + * NOTE: This pin must be on the same GPIO port as the main pin. + * + * Example: + * ./fbt --extra-define=DIGITAL_SIGNAL_DEBUG_OUTPUT_PIN=gpio_ext_pb3 + */ + +#define TAG "DigitalSequence" + +/* Special value used to indicate the end of DMA ring buffer. */ +#define DIGITAL_SEQUENCE_TIMER_MAX 0xFFFFFFFFUL + +/* Time to wait in loops before returning */ +#define DIGITAL_SEQUENCE_LOCK_WAIT_MS 10UL +#define DIGITAL_SEQUENCE_LOCK_WAIT_TICKS (DIGITAL_SEQUENCE_LOCK_WAIT_MS * 1000 * 64) + +#define DIGITAL_SEQUENCE_GPIO_BUFFER_SIZE 2 + +/* Maximum capacity of the DMA ring buffer. */ +#define DIGITAL_SEQUENCE_RING_BUFFER_SIZE 128 + +#define DIGITAL_SEQUENCE_RING_BUFFER_MIN_FREE_SIZE 2 + +/* Maximum amount of registered signals. */ +#define DIGITAL_SEQUENCE_BANK_SIZE 32 + +typedef enum { + DigitalSequenceStateIdle, + DigitalSequenceStateActive, +} DigitalSequenceState; + +typedef struct { + uint32_t data[DIGITAL_SEQUENCE_RING_BUFFER_SIZE]; + uint32_t write_pos; + uint32_t read_pos; +} DigitalSequenceRingBuffer; + +typedef uint32_t DigitalSequenceGpioBuffer[DIGITAL_SEQUENCE_GPIO_BUFFER_SIZE]; + +typedef const DigitalSignal* DigitalSequenceSignalBank[DIGITAL_SEQUENCE_BANK_SIZE]; + +struct DigitalSequence { + const GpioPin* gpio; + + uint32_t size; + uint32_t max_size; + uint8_t* data; + + LL_DMA_InitTypeDef dma_config_gpio; + LL_DMA_InitTypeDef dma_config_timer; + + DigitalSequenceGpioBuffer gpio_buf; + DigitalSequenceRingBuffer timer_buf; + DigitalSequenceSignalBank signals; + DigitalSequenceState state; +}; + +DigitalSequence* digital_sequence_alloc(uint32_t size, const GpioPin* gpio) { + furi_assert(size); + furi_assert(gpio); + + DigitalSequence* sequence = malloc(sizeof(DigitalSequence)); + + sequence->gpio = gpio; + sequence->max_size = size; + + sequence->data = malloc(sequence->max_size); + + sequence->dma_config_gpio.PeriphOrM2MSrcAddress = (uint32_t)&gpio->port->BSRR; + sequence->dma_config_gpio.MemoryOrM2MDstAddress = (uint32_t)sequence->gpio_buf; + sequence->dma_config_gpio.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH; + sequence->dma_config_gpio.Mode = LL_DMA_MODE_CIRCULAR; + sequence->dma_config_gpio.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT; + sequence->dma_config_gpio.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT; + sequence->dma_config_gpio.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_WORD; + sequence->dma_config_gpio.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_WORD; + sequence->dma_config_gpio.NbData = DIGITAL_SEQUENCE_GPIO_BUFFER_SIZE; + sequence->dma_config_gpio.PeriphRequest = LL_DMAMUX_REQ_TIM2_UP; + sequence->dma_config_gpio.Priority = LL_DMA_PRIORITY_VERYHIGH; + + sequence->dma_config_timer.PeriphOrM2MSrcAddress = (uint32_t)&TIM2->ARR; + sequence->dma_config_timer.MemoryOrM2MDstAddress = (uint32_t)sequence->timer_buf.data; + sequence->dma_config_timer.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH; + sequence->dma_config_timer.Mode = LL_DMA_MODE_CIRCULAR; + sequence->dma_config_timer.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT; + sequence->dma_config_timer.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT; + sequence->dma_config_timer.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_WORD; + sequence->dma_config_timer.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_WORD; + sequence->dma_config_timer.NbData = DIGITAL_SEQUENCE_RING_BUFFER_SIZE; + sequence->dma_config_timer.PeriphRequest = LL_DMAMUX_REQ_TIM2_UP; + sequence->dma_config_timer.Priority = LL_DMA_PRIORITY_HIGH; + + return sequence; +} + +void digital_sequence_free(DigitalSequence* sequence) { + furi_assert(sequence); + + free(sequence->data); + free(sequence); +} + +void digital_sequence_register_signal( + DigitalSequence* sequence, + uint8_t signal_index, + const DigitalSignal* signal) { + furi_assert(sequence); + furi_assert(signal); + furi_assert(signal_index < DIGITAL_SEQUENCE_BANK_SIZE); + + sequence->signals[signal_index] = signal; +} + +void digital_sequence_add_signal(DigitalSequence* sequence, uint8_t signal_index) { + furi_assert(sequence); + furi_assert(signal_index < DIGITAL_SEQUENCE_BANK_SIZE); + furi_assert(sequence->size < sequence->max_size); + + sequence->data[sequence->size++] = signal_index; +} + +static inline void digital_sequence_start_dma(DigitalSequence* sequence) { + furi_assert(sequence); + + LL_DMA_Init(DMA1, LL_DMA_CHANNEL_1, &sequence->dma_config_gpio); + LL_DMA_Init(DMA1, LL_DMA_CHANNEL_2, &sequence->dma_config_timer); + + LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_1); + LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_2); +} + +static inline void digital_sequence_stop_dma() { + LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_1); + LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_2); + LL_DMA_ClearFlag_TC1(DMA1); + LL_DMA_ClearFlag_TC2(DMA1); +} + +static inline void digital_sequence_start_timer() { + furi_hal_bus_enable(FuriHalBusTIM2); + + 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, DIGITAL_SEQUENCE_TIMER_MAX); + LL_TIM_SetCounter(TIM2, 0); + + LL_TIM_EnableCounter(TIM2); + LL_TIM_EnableUpdateEvent(TIM2); + LL_TIM_EnableDMAReq_UPDATE(TIM2); + LL_TIM_GenerateEvent_UPDATE(TIM2); +} + +static void digital_sequence_stop_timer() { + LL_TIM_DisableCounter(TIM2); + LL_TIM_DisableUpdateEvent(TIM2); + LL_TIM_DisableDMAReq_UPDATE(TIM2); + + furi_hal_bus_disable(FuriHalBusTIM2); +} + +static inline void digital_sequence_init_gpio_buffer( + DigitalSequence* sequence, + const DigitalSignal* first_signal) { + const uint32_t bit_set = sequence->gpio->pin << GPIO_BSRR_BS0_Pos +#ifdef DIGITAL_SIGNAL_DEBUG_OUTPUT_PIN + | DIGITAL_SIGNAL_DEBUG_OUTPUT_PIN.pin << GPIO_BSRR_BS0_Pos +#endif + ; + + const uint32_t bit_reset = sequence->gpio->pin << GPIO_BSRR_BR0_Pos +#ifdef DIGITAL_SIGNAL_DEBUG_OUTPUT_PIN + | DIGITAL_SIGNAL_DEBUG_OUTPUT_PIN.pin << GPIO_BSRR_BR0_Pos +#endif + ; + + if(first_signal->start_level) { + sequence->gpio_buf[0] = bit_set; + sequence->gpio_buf[1] = bit_reset; + } else { + sequence->gpio_buf[0] = bit_reset; + sequence->gpio_buf[1] = bit_set; + } +} + +static inline void digital_sequence_finish(DigitalSequence* sequence) { + if(sequence->state == DigitalSequenceStateActive) { + const uint32_t prev_timer = DWT->CYCCNT; + + do { + /* Special value has been loaded into the timer, signaling the end of transmission. */ + if(TIM2->ARR == DIGITAL_SEQUENCE_TIMER_MAX) { + break; + } + + if(DWT->CYCCNT - prev_timer > DIGITAL_SEQUENCE_LOCK_WAIT_TICKS) { + DigitalSequenceRingBuffer* dma_buffer = &sequence->timer_buf; + dma_buffer->read_pos = DIGITAL_SEQUENCE_RING_BUFFER_SIZE - + LL_DMA_GetDataLength(DMA1, LL_DMA_CHANNEL_2); + FURI_LOG_D( + TAG, + "[SEQ] hung %lu ms in finish (ARR 0x%08lx, read %lu, write %lu)", + DIGITAL_SEQUENCE_LOCK_WAIT_MS, + TIM2->ARR, + dma_buffer->read_pos, + dma_buffer->write_pos); + break; + } + } while(true); + } + + digital_sequence_stop_timer(); + digital_sequence_stop_dma(); +} + +static inline void digital_sequence_enqueue_period(DigitalSequence* sequence, uint32_t length) { + DigitalSequenceRingBuffer* dma_buffer = &sequence->timer_buf; + + if(sequence->state == DigitalSequenceStateActive) { + const uint32_t prev_timer = DWT->CYCCNT; + + do { + dma_buffer->read_pos = + DIGITAL_SEQUENCE_RING_BUFFER_SIZE - LL_DMA_GetDataLength(DMA1, LL_DMA_CHANNEL_2); + + const uint32_t size_free = (DIGITAL_SEQUENCE_RING_BUFFER_SIZE + dma_buffer->read_pos - + dma_buffer->write_pos) % + DIGITAL_SEQUENCE_RING_BUFFER_SIZE; + + if(size_free > DIGITAL_SEQUENCE_RING_BUFFER_MIN_FREE_SIZE) { + break; + } + + if(DWT->CYCCNT - prev_timer > DIGITAL_SEQUENCE_LOCK_WAIT_TICKS) { + FURI_LOG_D( + TAG, + "[SEQ] hung %lu ms in queue (ARR 0x%08lx, read %lu, write %lu)", + DIGITAL_SEQUENCE_LOCK_WAIT_MS, + TIM2->ARR, + dma_buffer->read_pos, + dma_buffer->write_pos); + break; + } + + if(TIM2->ARR == DIGITAL_SEQUENCE_TIMER_MAX) { + FURI_LOG_D( + TAG, + "[SEQ] buffer underrun in queue (ARR 0x%08lx, read %lu, write %lu)", + TIM2->ARR, + dma_buffer->read_pos, + dma_buffer->write_pos); + break; + } + } while(true); + } + + dma_buffer->data[dma_buffer->write_pos] = length; + + dma_buffer->write_pos += 1; + dma_buffer->write_pos %= DIGITAL_SEQUENCE_RING_BUFFER_SIZE; + + dma_buffer->data[dma_buffer->write_pos] = DIGITAL_SEQUENCE_TIMER_MAX; +} + +static inline void digital_sequence_timer_buffer_reset(DigitalSequence* sequence) { + sequence->timer_buf.data[0] = DIGITAL_SEQUENCE_TIMER_MAX; + sequence->timer_buf.read_pos = 0; + sequence->timer_buf.write_pos = 0; +} + +void digital_sequence_transmit(DigitalSequence* sequence) { + furi_assert(sequence); + furi_assert(sequence->size); + furi_assert(sequence->state == DigitalSequenceStateIdle); + + FURI_CRITICAL_ENTER(); + + furi_hal_gpio_init(sequence->gpio, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); +#ifdef DIGITAL_SIGNAL_DEBUG_OUTPUT_PIN + furi_hal_gpio_init( + &DIGITAL_SIGNAL_DEBUG_OUTPUT_PIN, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); +#endif + + const DigitalSignal* signal_current = sequence->signals[sequence->data[0]]; + + digital_sequence_init_gpio_buffer(sequence, signal_current); + + int32_t remainder_ticks = 0; + uint32_t reload_value_carry = 0; + uint32_t next_signal_index = 1; + + for(;;) { + const DigitalSignal* signal_next = + (next_signal_index < sequence->size) ? + sequence->signals[sequence->data[next_signal_index++]] : + NULL; + + for(uint32_t i = 0; i < signal_current->size; i++) { + const bool is_last_value = (i == signal_current->size - 1); + const uint32_t reload_value = signal_current->data[i] + reload_value_carry; + + reload_value_carry = 0; + + if(is_last_value) { + if(signal_next != NULL) { + /* Special case: signal boundary. Depending on whether the adjacent levels are equal or not, + * they will be combined to a single one or handled separately. */ + const bool end_level = signal_current->start_level ^ + ((signal_current->size % 2) == 0); + + /* If the adjacent levels are equal, carry the current period duration over to the next signal. */ + if(end_level == signal_next->start_level) { + reload_value_carry = reload_value; + } + } else { + /** Special case: during the last period of the last signal, hold the output level indefinitely. + * @see digital_signal.h + * + * Setting reload_value_carry to a non-zero value will prevent the respective period from being + * added to the DMA ring buffer. */ + reload_value_carry = 1; + } + } + + /* A non-zero reload_value_carry means that the level was the same on the both sides of the signal boundary + * and the two respective periods were combined to one. */ + if(reload_value_carry == 0) { + digital_sequence_enqueue_period(sequence, reload_value); + } + + if(sequence->state == DigitalSequenceStateIdle) { + const bool is_buffer_filled = sequence->timer_buf.write_pos >= + (DIGITAL_SEQUENCE_RING_BUFFER_SIZE - + DIGITAL_SEQUENCE_RING_BUFFER_MIN_FREE_SIZE); + const bool is_end_of_data = (signal_next == NULL) && is_last_value; + + if(is_buffer_filled || is_end_of_data) { + digital_sequence_start_dma(sequence); + digital_sequence_start_timer(); + sequence->state = DigitalSequenceStateActive; + } + } + } + + /* Exit the loop here when no further signals are available */ + if(signal_next == NULL) break; + + /* Prevent the rounding error from accumulating by distributing it across multiple periods. */ + remainder_ticks += signal_current->remainder; + if(remainder_ticks >= DIGITAL_SIGNAL_T_TIM_DIV2) { + remainder_ticks -= DIGITAL_SIGNAL_T_TIM; + reload_value_carry += 1; + } + + signal_current = signal_next; + }; + + digital_sequence_finish(sequence); + digital_sequence_timer_buffer_reset(sequence); + + FURI_CRITICAL_EXIT(); + + sequence->state = DigitalSequenceStateIdle; +} + +void digital_sequence_clear(DigitalSequence* sequence) { + furi_assert(sequence); + + sequence->size = 0; +} diff --git a/lib/digital_signal/digital_sequence.h b/lib/digital_signal/digital_sequence.h new file mode 100644 index 00000000000..4f04ecc464b --- /dev/null +++ b/lib/digital_signal/digital_sequence.h @@ -0,0 +1,112 @@ +/** + * @file digital_sequence.h + * @brief Fast and precise digital signal generation library. + * + * Each sequence is represented by one or more (up to 32) registered signals, which are addressed + * by their indices, and a list of signal indices to be transmitted. + * + * The registered signals must be set up prior to actually defining the sequence. + * + * Example: A sequence containing 4 registered signals and n indices to transmit. + * + * |Signal | Index | + * |:-----:|:-----:| + * | SOF | 0 | + * | EOF | 1 | + * | Zero | 2 | + * | One | 3 | + * + * ``` + * Signal index | 0 | 3 | 2 | 2 | ... | 3 | 1 | + * 0 1 2 3 ... n - 2 n - 1 + * ``` + * + * The above sequence starts by transmitting the signal with index 0, which is SOF in this case, + * then it proceeds with indices 3, 2, 2, which are One, Zero, Zero and after n - 2 signals, + * it will conclude with indices 3 and 1 which are One and EOF respectively. + * + * This way, only the order in which the signals are sent is stored, while the signals themselves + * are not duplicated. + */ +#pragma once + +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct DigitalSequence DigitalSequence; + +/** + * @brief Allocate a DigitalSequence instance of a given size which will operate on a set GPIO pin. + * + * @param[in] size maximum number of signal indices contained in the instance. + * @param[in] gpio the GPIO pin used to generate the signal. + * @returns pointer to the allocated DigitalSequence instance. + */ +DigitalSequence* digital_sequence_alloc(uint32_t size, const GpioPin* gpio); + +/** + * @brief Delete a previously allocated DigitalSequence instance. + * + * @param[in,out] sequence pointer to the instance to be deleted. + */ +void digital_sequence_free(DigitalSequence* sequence); + +/** + * @brief Register a signal within a DigitalSequence instance by its index. + * + * This function must be called for each signal to be used in the sequence. The DigitalSequence + * instance does not own the signals, therefore, their lifetime must be no less than the instance's. + * + * The user is responsible for creation and deletion of DigitalSignal instances and + * also for keeping track of their respective indices. + * + * @param[in,out] sequence pointer to the instance to be modified. + * @param[in] signal_index index to register the signal under (must be less than 32). + * @param[in] signal pointer to the DigitalSignal instance to be registered. + */ +void digital_sequence_register_signal( + DigitalSequence* sequence, + uint8_t signal_index, + const DigitalSignal* signal); + +/** + * @brief Append a signal index to a DigitalSequence instance. + * + * The signal under the index must be registered beforehand by calling digital_sequence_set_signal(). + * + * @param[in,out] sequence pointer to the instance to be modified. + * @param[in] signal_index signal index to be appended to the sequence (must be less than 32). + */ +void digital_sequence_add_signal(DigitalSequence* sequence, uint8_t signal_index); + +/** + * @brief Transmit the sequence contained in the DigitalSequence instance. + * + * Must contain at least one registered signal and one signal index. + * + * NOTE: The current implementation will properly initialise the GPIO provided during construction, + * but it is the caller's responsibility to reconfigure it back before reusing for other purposes. + * This is due to performance reasons. + * + * @param[in] sequence pointer to the sequence to be transmitted. + */ +void digital_sequence_transmit(DigitalSequence* sequence); + +/** + * @brief Clear the signal sequence in a DigitalSequence instance. + * + * Calling this function does not un-register the registered signals, so it is + * safe to call digital_sequence_add_signal() right afterwards. + * + * @param[in,out] sequence pointer to the instance to be cleared. + */ +void digital_sequence_clear(DigitalSequence* sequence); + +#ifdef __cplusplus +} +#endif diff --git a/lib/digital_signal/digital_signal.c b/lib/digital_signal/digital_signal.c index 25adb878b78..a0408900de8 100644 --- a/lib/digital_signal/digital_signal.c +++ b/lib/digital_signal/digital_signal.c @@ -1,106 +1,15 @@ #include "digital_signal.h" +#include "digital_signal_i.h" #include -#include -#include -#include - -#include -#include - -/* must be on bank B */ -// For debugging purposes use `--extra-define=DIGITAL_SIGNAL_DEBUG_OUTPUT_PIN=gpio_ext_pb3` fbt option - -struct ReloadBuffer { - uint32_t* buffer; /* DMA ringbuffer */ - uint32_t size; /* maximum entry count of the ring buffer */ - uint32_t write_pos; /* current buffer write index */ - uint32_t read_pos; /* current buffer read index */ - bool dma_active; -}; - -struct DigitalSequence { - uint8_t signals_size; - bool bake; - uint32_t sequence_used; - uint32_t sequence_size; - DigitalSignal** signals; - uint8_t* sequence; - const GpioPin* gpio; - uint32_t send_time; - bool send_time_active; - LL_DMA_InitTypeDef dma_config_gpio; - LL_DMA_InitTypeDef dma_config_timer; - uint32_t* gpio_buff; - struct ReloadBuffer* dma_buffer; -}; - -struct DigitalSignalInternals { - uint64_t factor; - uint32_t reload_reg_entries; - uint32_t reload_reg_remainder; - uint32_t gpio_buff[2]; - const GpioPin* gpio; - LL_DMA_InitTypeDef dma_config_gpio; - LL_DMA_InitTypeDef dma_config_timer; -}; #define TAG "DigitalSignal" -#define F_TIM (64000000.0) -#define T_TIM 1562 /* 15.625 ns *100 */ -#define T_TIM_DIV2 781 /* 15.625 ns / 2 *100 */ - -/* end marker in DMA ringbuffer, will get written into timer register at the end */ -#define SEQ_TIMER_MAX 0xFFFFFFFF - -/* time to wait in loops before returning */ -#define SEQ_LOCK_WAIT_MS 10UL -#define SEQ_LOCK_WAIT_TICKS (SEQ_LOCK_WAIT_MS * 1000 * 64) - -/* maximum entry count of the sequence dma ring buffer */ -#define RINGBUFFER_SIZE 128 - -/* maximum number of DigitalSignals in a sequence */ -#define SEQUENCE_SIGNALS_SIZE 32 -/* - * if sequence size runs out from the initial value passed to digital_sequence_alloc - * the size will be increased by this amount and reallocated - */ -#define SEQUENCE_SIZE_REALLOCATE_INCREMENT 256 - -DigitalSignal* digital_signal_alloc(uint32_t max_edges_cnt) { +DigitalSignal* digital_signal_alloc(uint32_t max_size) { DigitalSignal* signal = malloc(sizeof(DigitalSignal)); - signal->start_level = true; - signal->edges_max_cnt = max_edges_cnt; - signal->edge_timings = malloc(signal->edges_max_cnt * sizeof(uint32_t)); - signal->edge_cnt = 0; - signal->reload_reg_buff = malloc(signal->edges_max_cnt * sizeof(uint32_t)); - - signal->internals = malloc(sizeof(DigitalSignalInternals)); - DigitalSignalInternals* internals = signal->internals; - internals->factor = 1024 * 1024; - - internals->dma_config_gpio.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH; - internals->dma_config_gpio.Mode = LL_DMA_MODE_CIRCULAR; - internals->dma_config_gpio.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT; - internals->dma_config_gpio.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT; - internals->dma_config_gpio.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_WORD; - internals->dma_config_gpio.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_WORD; - internals->dma_config_gpio.NbData = 2; - internals->dma_config_gpio.PeriphRequest = LL_DMAMUX_REQ_TIM2_UP; - internals->dma_config_gpio.Priority = LL_DMA_PRIORITY_VERYHIGH; - - internals->dma_config_timer.PeriphOrM2MSrcAddress = (uint32_t) & (TIM2->ARR); - internals->dma_config_timer.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH; - internals->dma_config_timer.Mode = LL_DMA_MODE_NORMAL; - internals->dma_config_timer.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT; - internals->dma_config_timer.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT; - internals->dma_config_timer.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_WORD; - internals->dma_config_timer.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_WORD; - internals->dma_config_timer.PeriphRequest = LL_DMAMUX_REQ_TIM2_UP; - internals->dma_config_timer.Priority = LL_DMA_PRIORITY_HIGH; + signal->max_size = max_size; + signal->data = malloc(max_size * sizeof(uint32_t)); return signal; } @@ -108,559 +17,81 @@ DigitalSignal* digital_signal_alloc(uint32_t max_edges_cnt) { void digital_signal_free(DigitalSignal* signal) { furi_assert(signal); - free(signal->edge_timings); - free(signal->reload_reg_buff); - free(signal->internals); + free(signal->data); 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; - } - /* in case there are no edges in our target signal, the signal to append makes the rules */ - if(!signal_a->edge_cnt) { - signal_a->start_level = signal_b->start_level; - } - 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]; - } - } - - for(size_t i = 0; i < signal_b->edge_cnt - start_copy; i++) { - signal_a->edge_timings[signal_a->edge_cnt + i] = signal_b->edge_timings[start_copy + i]; - } - signal_a->edge_cnt += signal_b->edge_cnt - start_copy; - - return true; -} - -bool digital_signal_get_start_level(DigitalSignal* signal) { +bool digital_signal_get_start_level(const DigitalSignal* signal) { furi_assert(signal); return signal->start_level; } -uint32_t digital_signal_get_edges_cnt(DigitalSignal* signal) { +void digital_signal_set_start_level(DigitalSignal* signal, bool level) { furi_assert(signal); - return signal->edge_cnt; + signal->start_level = level; } -void digital_signal_add(DigitalSignal* signal, uint32_t ticks) { +uint32_t digital_signal_get_size(const DigitalSignal* signal) { furi_assert(signal); - furi_assert(signal->edge_cnt < signal->edges_max_cnt); - signal->edge_timings[signal->edge_cnt++] = ticks; + return signal->size; } -void digital_signal_add_pulse(DigitalSignal* signal, uint32_t ticks, bool level) { +void digital_signal_add_period(DigitalSignal* signal, uint32_t ticks) { furi_assert(signal); - furi_assert(signal->edge_cnt < signal->edges_max_cnt); + furi_assert(signal->size < signal->max_size); - /* virgin signal? add it as the only level */ - if(signal->edge_cnt == 0) { - signal->start_level = level; - signal->edge_timings[signal->edge_cnt++] = ticks; - } else { - bool end_level = signal->start_level ^ !(signal->edge_cnt % 2); + const uint32_t duration = ticks + signal->remainder; - if(level != end_level) { - signal->edge_timings[signal->edge_cnt++] = ticks; - } else { - signal->edge_timings[signal->edge_cnt - 1] += ticks; - } - } -} + uint32_t reload_value = duration / DIGITAL_SIGNAL_T_TIM; + int32_t remainder = duration - reload_value * DIGITAL_SIGNAL_T_TIM; -uint32_t 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]; -} - -void digital_signal_prepare_arr(DigitalSignal* signal) { - furi_assert(signal); - - DigitalSignalInternals* internals = signal->internals; - - /* set up signal polarities */ - if(internals->gpio) { - uint32_t bit_set = internals->gpio->pin; - uint32_t bit_reset = internals->gpio->pin << 16; - -#ifdef DIGITAL_SIGNAL_DEBUG_OUTPUT_PIN - bit_set |= DIGITAL_SIGNAL_DEBUG_OUTPUT_PIN.pin; - bit_reset |= DIGITAL_SIGNAL_DEBUG_OUTPUT_PIN.pin << 16; -#endif - - if(signal->start_level) { - internals->gpio_buff[0] = bit_set; - internals->gpio_buff[1] = bit_reset; - } else { - internals->gpio_buff[0] = bit_reset; - internals->gpio_buff[1] = bit_set; - } + if(remainder >= DIGITAL_SIGNAL_T_TIM_DIV2) { + reload_value += 1; + remainder -= DIGITAL_SIGNAL_T_TIM; } - /* set up edge timings */ - internals->reload_reg_entries = 0; - - for(size_t pos = 0; pos < signal->edge_cnt; pos++) { - uint32_t pulse_duration = signal->edge_timings[pos] + internals->reload_reg_remainder; - if(pulse_duration < 10 || pulse_duration > 10000000) { - FURI_LOG_D( - TAG, - "[prepare] pulse_duration out of range: %lu = %lu * %llu", - pulse_duration, - signal->edge_timings[pos], - internals->factor); - pulse_duration = 100; - } - uint32_t pulse_ticks = (pulse_duration + T_TIM_DIV2) / T_TIM; - internals->reload_reg_remainder = pulse_duration - (pulse_ticks * T_TIM); + furi_check(reload_value > 1); - if(pulse_ticks > 1) { - signal->reload_reg_buff[internals->reload_reg_entries++] = pulse_ticks - 1; - } - } + signal->data[signal->size++] = reload_value - 1; + signal->remainder = remainder; } -static void digital_signal_stop_dma() { - LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_1); - LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_2); - LL_DMA_ClearFlag_TC1(DMA1); - LL_DMA_ClearFlag_TC2(DMA1); -} +static void digital_signal_extend_last_period(DigitalSignal* signal, uint32_t ticks) { + furi_assert(signal->size <= signal->max_size); -static void digital_signal_stop_timer() { - LL_TIM_DisableCounter(TIM2); - LL_TIM_DisableUpdateEvent(TIM2); - LL_TIM_DisableDMAReq_UPDATE(TIM2); + const uint32_t reload_value_old = signal->data[signal->size - 1] + 1; + const uint32_t duration = ticks + signal->remainder + reload_value_old * DIGITAL_SIGNAL_T_TIM; - furi_hal_bus_disable(FuriHalBusTIM2); -} - -static void digital_signal_setup_timer() { - furi_hal_bus_enable(FuriHalBusTIM2); + uint32_t reload_value = duration / DIGITAL_SIGNAL_T_TIM; + int32_t remainder = duration - reload_value * DIGITAL_SIGNAL_T_TIM; - 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, SEQ_TIMER_MAX); - LL_TIM_SetCounter(TIM2, 0); -} - -static void digital_signal_start_timer() { - LL_TIM_EnableCounter(TIM2); - LL_TIM_EnableUpdateEvent(TIM2); - LL_TIM_EnableDMAReq_UPDATE(TIM2); - LL_TIM_GenerateEvent_UPDATE(TIM2); -} - -static bool digital_signal_setup_dma(DigitalSignal* signal) { - furi_assert(signal); - DigitalSignalInternals* internals = signal->internals; - - if(!signal->internals->reload_reg_entries) { - return false; + if(remainder >= DIGITAL_SIGNAL_T_TIM_DIV2) { + reload_value += 1; + remainder -= DIGITAL_SIGNAL_T_TIM; } - digital_signal_stop_dma(); - - internals->dma_config_gpio.MemoryOrM2MDstAddress = (uint32_t)internals->gpio_buff; - internals->dma_config_gpio.PeriphOrM2MSrcAddress = (uint32_t) & (internals->gpio->port->BSRR); - internals->dma_config_timer.MemoryOrM2MDstAddress = (uint32_t)signal->reload_reg_buff; - internals->dma_config_timer.NbData = signal->internals->reload_reg_entries; - - /* set up DMA channel 1 and 2 for GPIO and timer copy operations */ - LL_DMA_Init(DMA1, LL_DMA_CHANNEL_1, &internals->dma_config_gpio); - LL_DMA_Init(DMA1, LL_DMA_CHANNEL_2, &internals->dma_config_timer); - /* enable both DMA channels */ - LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_1); - LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_2); + furi_check(reload_value > 1); - return true; + signal->data[signal->size - 1] = reload_value - 1; + signal->remainder = remainder; } -void digital_signal_send(DigitalSignal* signal, const GpioPin* gpio) { +void digital_signal_add_period_with_level(DigitalSignal* signal, uint32_t ticks, bool level) { furi_assert(signal); - if(!signal->edge_cnt) { - return; - } - - /* Configure gpio as output */ - signal->internals->gpio = gpio; - furi_hal_gpio_init( - signal->internals->gpio, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); - - digital_signal_prepare_arr(signal); - - digital_signal_setup_dma(signal); - digital_signal_setup_timer(); - digital_signal_start_timer(); - - while(!LL_DMA_IsActiveFlag_TC2(DMA1)) { - } - - digital_signal_stop_timer(); - digital_signal_stop_dma(); -} - -static void digital_sequence_alloc_signals(DigitalSequence* sequence, uint32_t size) { - sequence->signals_size = size; - sequence->signals = malloc(sequence->signals_size * sizeof(DigitalSignal*)); -} - -static void digital_sequence_alloc_sequence(DigitalSequence* sequence, uint32_t size) { - sequence->sequence_used = 0; - sequence->sequence_size = size; - sequence->sequence = malloc(sequence->sequence_size); - sequence->send_time = 0; - sequence->send_time_active = false; -} - -DigitalSequence* digital_sequence_alloc(uint32_t size, const GpioPin* gpio) { - furi_assert(gpio); - - DigitalSequence* sequence = malloc(sizeof(DigitalSequence)); - - sequence->gpio = gpio; - sequence->bake = false; - - sequence->dma_buffer = malloc(sizeof(struct ReloadBuffer)); - sequence->dma_buffer->size = RINGBUFFER_SIZE; - sequence->dma_buffer->buffer = malloc(sequence->dma_buffer->size * sizeof(uint32_t)); - - sequence->dma_config_gpio.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH; - sequence->dma_config_gpio.Mode = LL_DMA_MODE_CIRCULAR; - sequence->dma_config_gpio.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT; - sequence->dma_config_gpio.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT; - sequence->dma_config_gpio.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_WORD; - sequence->dma_config_gpio.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_WORD; - sequence->dma_config_gpio.NbData = 2; - sequence->dma_config_gpio.PeriphRequest = LL_DMAMUX_REQ_TIM2_UP; - sequence->dma_config_gpio.Priority = LL_DMA_PRIORITY_VERYHIGH; - - sequence->dma_config_timer.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH; - sequence->dma_config_timer.Mode = LL_DMA_MODE_CIRCULAR; - sequence->dma_config_timer.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT; - sequence->dma_config_timer.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT; - sequence->dma_config_timer.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_WORD; - sequence->dma_config_timer.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_WORD; - sequence->dma_config_timer.PeriphOrM2MSrcAddress = (uint32_t) & (TIM2->ARR); - sequence->dma_config_timer.MemoryOrM2MDstAddress = (uint32_t)sequence->dma_buffer->buffer; - sequence->dma_config_timer.NbData = sequence->dma_buffer->size; - sequence->dma_config_timer.PeriphRequest = LL_DMAMUX_REQ_TIM2_UP; - sequence->dma_config_timer.Priority = LL_DMA_PRIORITY_HIGH; - - digital_sequence_alloc_signals(sequence, SEQUENCE_SIGNALS_SIZE); - digital_sequence_alloc_sequence(sequence, size); - - return sequence; -} - -void digital_sequence_free(DigitalSequence* sequence) { - furi_assert(sequence); - - free(sequence->signals); - free(sequence->sequence); - free(sequence->dma_buffer->buffer); - free(sequence->dma_buffer); - free(sequence); -} - -void digital_sequence_set_signal( - DigitalSequence* sequence, - uint8_t signal_index, - DigitalSignal* signal) { - furi_assert(sequence); - furi_assert(signal); - furi_assert(signal_index < sequence->signals_size); - - sequence->signals[signal_index] = signal; - signal->internals->gpio = sequence->gpio; - signal->internals->reload_reg_remainder = 0; - - digital_signal_prepare_arr(signal); -} - -void digital_sequence_set_sendtime(DigitalSequence* sequence, uint32_t send_time) { - furi_assert(sequence); - - sequence->send_time = send_time; - sequence->send_time_active = true; -} - -void digital_sequence_add(DigitalSequence* sequence, uint8_t signal_index) { - furi_assert(sequence); - furi_assert(signal_index < sequence->signals_size); - - if(sequence->sequence_used >= sequence->sequence_size) { - sequence->sequence_size += SEQUENCE_SIZE_REALLOCATE_INCREMENT; - sequence->sequence = realloc(sequence->sequence, sequence->sequence_size); //-V701 - furi_assert(sequence->sequence); - } - - sequence->sequence[sequence->sequence_used++] = signal_index; -} - -static bool digital_sequence_setup_dma(DigitalSequence* sequence) { - furi_assert(sequence); - - digital_signal_stop_dma(); - - sequence->dma_config_gpio.MemoryOrM2MDstAddress = (uint32_t)sequence->gpio_buff; - sequence->dma_config_gpio.PeriphOrM2MSrcAddress = (uint32_t) & (sequence->gpio->port->BSRR); - - /* set up DMA channel 1 and 2 for GPIO and timer copy operations */ - LL_DMA_Init(DMA1, LL_DMA_CHANNEL_1, &sequence->dma_config_gpio); - LL_DMA_Init(DMA1, LL_DMA_CHANNEL_2, &sequence->dma_config_timer); - - /* enable both DMA channels */ - LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_1); - LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_2); - - return true; -} - -static DigitalSignal* digital_sequence_bake(DigitalSequence* sequence) { - furi_assert(sequence); - - uint32_t edges = 0; - - for(uint32_t pos = 0; pos < sequence->sequence_used; pos++) { - uint8_t signal_index = sequence->sequence[pos]; - DigitalSignal* sig = sequence->signals[signal_index]; - - edges += sig->edge_cnt; - } - - DigitalSignal* ret = digital_signal_alloc(edges); - - for(uint32_t pos = 0; pos < sequence->sequence_used; pos++) { - uint8_t signal_index = sequence->sequence[pos]; - DigitalSignal* sig = sequence->signals[signal_index]; - - digital_signal_append(ret, sig); - } - - return ret; -} - -static void digital_sequence_finish(DigitalSequence* sequence) { - struct ReloadBuffer* dma_buffer = sequence->dma_buffer; - - if(dma_buffer->dma_active) { - uint32_t prev_timer = DWT->CYCCNT; - do { - /* we are finished, when the DMA transferred the SEQ_TIMER_MAX marker */ - if(TIM2->ARR == SEQ_TIMER_MAX) { - break; - } - if(DWT->CYCCNT - prev_timer > SEQ_LOCK_WAIT_TICKS) { - dma_buffer->read_pos = - RINGBUFFER_SIZE - LL_DMA_GetDataLength(DMA1, LL_DMA_CHANNEL_2); - FURI_LOG_D( - TAG, - "[SEQ] hung %lu ms in finish (ARR 0x%08lx, read %lu, write %lu)", - SEQ_LOCK_WAIT_MS, - TIM2->ARR, - dma_buffer->read_pos, - dma_buffer->write_pos); - break; - } - } while(1); - } - - digital_signal_stop_timer(); - digital_signal_stop_dma(); -} - -static void digital_sequence_queue_pulse(DigitalSequence* sequence, uint32_t length) { - struct ReloadBuffer* dma_buffer = sequence->dma_buffer; - - if(dma_buffer->dma_active) { - uint32_t prev_timer = DWT->CYCCNT; - do { - dma_buffer->read_pos = RINGBUFFER_SIZE - LL_DMA_GetDataLength(DMA1, LL_DMA_CHANNEL_2); - - uint32_t free = - (RINGBUFFER_SIZE + dma_buffer->read_pos - dma_buffer->write_pos) % RINGBUFFER_SIZE; - - if(free > 2) { - break; - } - - if(DWT->CYCCNT - prev_timer > SEQ_LOCK_WAIT_TICKS) { - FURI_LOG_D( - TAG, - "[SEQ] hung %lu ms in queue (ARR 0x%08lx, read %lu, write %lu)", - SEQ_LOCK_WAIT_MS, - TIM2->ARR, - dma_buffer->read_pos, - dma_buffer->write_pos); - break; - } - if(TIM2->ARR == SEQ_TIMER_MAX) { - FURI_LOG_D( - TAG, - "[SEQ] buffer underrun in queue (ARR 0x%08lx, read %lu, write %lu)", - TIM2->ARR, - dma_buffer->read_pos, - dma_buffer->write_pos); - break; - } - } while(1); - } - - dma_buffer->buffer[dma_buffer->write_pos] = length; - dma_buffer->write_pos++; - dma_buffer->write_pos %= RINGBUFFER_SIZE; - dma_buffer->buffer[dma_buffer->write_pos] = SEQ_TIMER_MAX; -} - -bool digital_sequence_send(DigitalSequence* sequence) { - furi_assert(sequence); - - struct ReloadBuffer* dma_buffer = sequence->dma_buffer; - - furi_hal_gpio_init(sequence->gpio, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); -#ifdef DIGITAL_SIGNAL_DEBUG_OUTPUT_PIN - furi_hal_gpio_init( - &DIGITAL_SIGNAL_DEBUG_OUTPUT_PIN, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); -#endif - - if(sequence->bake) { - DigitalSignal* sig = digital_sequence_bake(sequence); - - digital_signal_send(sig, sequence->gpio); - digital_signal_free(sig); - return true; - } - - if(!sequence->sequence_used) { - return false; - } - - int32_t remainder = 0; - uint32_t trade_for_next = 0; - uint32_t seq_pos_next = 1; - - dma_buffer->dma_active = false; - dma_buffer->buffer[0] = SEQ_TIMER_MAX; - dma_buffer->read_pos = 0; - dma_buffer->write_pos = 0; - - /* already prepare the current signal pointer */ - DigitalSignal* sig = sequence->signals[sequence->sequence[0]]; - DigitalSignal* sig_next = NULL; - /* re-use the GPIO buffer from the first signal */ - sequence->gpio_buff = sig->internals->gpio_buff; - - FURI_CRITICAL_ENTER(); - - while(sig) { - bool last_signal = (seq_pos_next >= sequence->sequence_used); - - if(!last_signal) { - sig_next = sequence->signals[sequence->sequence[seq_pos_next++]]; - } - - for(uint32_t pulse_pos = 0; pulse_pos < sig->internals->reload_reg_entries; pulse_pos++) { - bool last_pulse = ((pulse_pos + 1) >= sig->internals->reload_reg_entries); - uint32_t pulse_length = sig->reload_reg_buff[pulse_pos] + trade_for_next; - - trade_for_next = 0; - - /* when we are too late more than half a tick, make the first edge temporarily longer */ - if(remainder >= T_TIM_DIV2) { - remainder -= T_TIM; - pulse_length += 1; - } - - /* last pulse in current signal and have a next signal? */ - if(last_pulse && sig_next) { - /* when a signal ends with the same level as the next signal begins, let the next signal generate the whole pulse. - beware, we do not want the level after the last edge, but the last level before that edge */ - bool end_level = sig->start_level ^ ((sig->edge_cnt % 2) == 0); - - /* if they have the same level, pass the duration to the next pulse(s) */ - if(end_level == sig_next->start_level) { - trade_for_next = pulse_length; - } - } - - /* if it was decided, that the next signal's first pulse shall also handle our "length", then do not queue here */ - if(!trade_for_next) { - digital_sequence_queue_pulse(sequence, pulse_length); - - if(!dma_buffer->dma_active) { - /* start transmission when buffer was filled enough */ - bool start_send = sequence->dma_buffer->write_pos >= (RINGBUFFER_SIZE - 2); - - /* or it was the last pulse */ - if(last_pulse && last_signal) { - start_send = true; - } - - /* start transmission */ - if(start_send) { - digital_sequence_setup_dma(sequence); - digital_signal_setup_timer(); - - /* if the send time is specified, wait till the core timer passed beyond that time */ - if(sequence->send_time_active) { - sequence->send_time_active = false; - while(sequence->send_time - DWT->CYCCNT < 0x80000000) { - } - } - digital_signal_start_timer(); - dma_buffer->dma_active = true; - } - } - } - } - - remainder += sig->internals->reload_reg_remainder; - sig = sig_next; - sig_next = NULL; - } - - /* wait until last dma transaction was finished */ - FURI_CRITICAL_EXIT(); - digital_sequence_finish(sequence); - - return true; -} - -void digital_sequence_clear(DigitalSequence* sequence) { - furi_assert(sequence); - - sequence->sequence_used = 0; -} - -void digital_sequence_timebase_correction(DigitalSequence* sequence, float factor) { - for(uint32_t sig_pos = 0; sig_pos < sequence->signals_size; sig_pos++) { - DigitalSignal* signal = sequence->signals[sig_pos]; + if(signal->size == 0) { + signal->start_level = level; + digital_signal_add_period(signal, ticks); + } else { + const bool end_level = signal->start_level ^ !(signal->size % 2); - if(signal) { - signal->internals->factor = (uint32_t)(1024 * 1024 * factor); - digital_signal_prepare_arr(signal); + if(level != end_level) { + digital_signal_add_period(signal, ticks); + } else { + digital_signal_extend_last_period(signal, ticks); } } } diff --git a/lib/digital_signal/digital_signal.h b/lib/digital_signal/digital_signal.h index 404d02605e7..f29facfd83d 100644 --- a/lib/digital_signal/digital_signal.h +++ b/lib/digital_signal/digital_signal.h @@ -1,74 +1,121 @@ +/** + * @file digital_signal.h + * @brief Simple digital signal container for the DigitalSequence library. + * + * Each signal is represented by its start level (high or low) and one or more periods. + * The output will transition to its inverse value on each period boundary. + * + * Example: A signal with n periods and HIGH start level. + * + * ``` + * ----+ +------+ +- ... -+ + * t0 | t1 | t2 | t3 | | tn - 1 + * +--------+ +----+ +-------- + * ``` + * + */ #pragma once #include -#include #include -#include - #ifdef __cplusplus extern "C" { #endif -/* helper for easier signal generation */ +// DigitalSignal uses 10 picosecond time units (1 tick = 10 ps). +// Use the macros below to convert the time from other units. + #define DIGITAL_SIGNAL_MS(x) ((x)*100000000UL) #define DIGITAL_SIGNAL_US(x) ((x)*100000UL) #define DIGITAL_SIGNAL_NS(x) ((x)*100UL) #define DIGITAL_SIGNAL_PS(x) ((x) / 10UL) -/* using an anonymous type for the internals */ -typedef struct DigitalSignalInternals DigitalSignalInternals; - -/* and a public one for accessing user-side fields */ -typedef struct DigitalSignal { - bool start_level; - uint32_t edge_cnt; - uint32_t edges_max_cnt; - uint32_t* edge_timings; - uint32_t* reload_reg_buff; /* internal, but used by unit tests */ - DigitalSignalInternals* internals; -} DigitalSignal; - -typedef struct DigitalSequence DigitalSequence; - -DigitalSignal* digital_signal_alloc(uint32_t max_edges_cnt); - +typedef struct DigitalSignal DigitalSignal; + +/** + * @brief Allocate a DigitalSignal instance with a defined maximum size. + * + * @param[in] max_size the maximum number of periods the instance will be able to contain. + * @returns pointer to the allocated instance. + */ +DigitalSignal* digital_signal_alloc(uint32_t max_size); + +/** + * @brief Delete a previously allocated DigitalSignal instance. + * + * @param[in,out] signal pointer to the instance to be deleted. + */ void digital_signal_free(DigitalSignal* signal); -void digital_signal_add(DigitalSignal* signal, uint32_t ticks); - -void digital_signal_add_pulse(DigitalSignal* signal, uint32_t ticks, bool level); - -bool digital_signal_append(DigitalSignal* signal_a, DigitalSignal* signal_b); - -void digital_signal_prepare_arr(DigitalSignal* signal); - -bool digital_signal_get_start_level(DigitalSignal* signal); - -uint32_t digital_signal_get_edges_cnt(DigitalSignal* signal); - -uint32_t digital_signal_get_edge(DigitalSignal* signal, uint32_t edge_num); - -void digital_signal_send(DigitalSignal* signal, const GpioPin* gpio); - -DigitalSequence* digital_sequence_alloc(uint32_t size, const GpioPin* gpio); - -void digital_sequence_free(DigitalSequence* sequence); - -void digital_sequence_set_signal( - DigitalSequence* sequence, - uint8_t signal_index, - DigitalSignal* signal); - -void digital_sequence_set_sendtime(DigitalSequence* sequence, uint32_t send_time); - -void digital_sequence_add(DigitalSequence* sequence, uint8_t signal_index); - -bool digital_sequence_send(DigitalSequence* sequence); - -void digital_sequence_clear(DigitalSequence* sequence); - -void digital_sequence_timebase_correction(DigitalSequence* sequence, float factor); +/** + * @brief Append one period to the end of the DigitalSignal instance. + * + * @param[in,out] signal pointer to a the instance to append to. + * @param[in] ticks the period length, in 10 picosecond units. + */ +void digital_signal_add_period(DigitalSignal* signal, uint32_t ticks); + +/** + * @brief Append one period to the end of the DigitalSignal instance, with the level specified. + * + * If the level is the same as the last level contained in the instance, then it is extened + * by the given ticks value. Otherwise, the behaviour is identical to digital_signal_add_period(). + * + * Example 1: add tc with HIGH level + * ``` + * before: + * ... ------+ + * ta | tb + * +------- + * after: + * ... ------+ +------- + * ta | tb | tc + * +------+ + * ``` + * Example 2: add tc with LOW level + * ``` + * before: + * ... ------+ + * ta | tb + * +------- + * after: + * ... ------+ + * ta | tb + tc + * +-------------- + * ``` + * + * @param[in,out] signal pointer to the instance to append to. + * @param[in] ticks the period length, in 10 picosecond units. + * @param[in] level the level to be set during the period. + */ +void digital_signal_add_period_with_level(DigitalSignal* signal, uint32_t ticks, bool level); + +/** + * @brief Get the current start level contained in the DigitalSignal instance. + * + * If not explicitly set with digital_signal_set_start_level(), it defaults to false. + * + * @param[in] signal pointer to the instance to be queried. + * @returns the start level value. + */ +bool digital_signal_get_start_level(const DigitalSignal* signal); + +/** + * @brief Set the start level contained in the DigitalSignal instance. + * + * @param[in,out] signal pointer to the instance to be modified. + * @param[in] level signal level to be set as the start level. + */ +void digital_signal_set_start_level(DigitalSignal* signal, bool level); + +/** + * @brief Get the number of periods currently stored in a DigitalSignal instance. + * + * @param[in] signal pointer to the instance to be queried. + * @return the number of periods stored in the instance. + */ +uint32_t digital_signal_get_size(const DigitalSignal* signal); #ifdef __cplusplus } diff --git a/lib/digital_signal/digital_signal_i.h b/lib/digital_signal/digital_signal_i.h new file mode 100644 index 00000000000..e473c80c091 --- /dev/null +++ b/lib/digital_signal/digital_signal_i.h @@ -0,0 +1,23 @@ +/** + * @file digital_signal_i.h + * @brief DigitalSignal private definitions. + * + * This file is an implementation detail. It must not be included in + * any public API-related headers. + */ +#include +#include + +#define DIGITAL_SIGNAL_T_TIM 1562 /**< 15.625 ns *100 */ +#define DIGITAL_SIGNAL_T_TIM_DIV2 (DIGITAL_SIGNAL_T_TIM / 2) /**< 15.625 ns / 2 *100 */ + +/** + * @brief DigitalSignal structure type. + */ +struct DigitalSignal { + bool start_level; /**< The level to begin the signal with. */ + uint32_t size; /**< Current period count contained in the instance. */ + uint32_t max_size; /**< Maximum period count this instance can hold. */ + uint32_t* data; /**< Pointer to the array of time periods. */ + int32_t remainder; /**< Remainder left after converting all periods into timer ticks. */ +}; diff --git a/lib/digital_signal/presets/nfc/iso14443_3a_signal.c b/lib/digital_signal/presets/nfc/iso14443_3a_signal.c new file mode 100644 index 00000000000..1f3824e2dcd --- /dev/null +++ b/lib/digital_signal/presets/nfc/iso14443_3a_signal.c @@ -0,0 +1,139 @@ +#include "iso14443_3a_signal.h" + +#include + +#define BITS_IN_BYTE (8) + +#define ISO14443_3A_SIGNAL_BIT_MAX_EDGES (10) +#define ISO14443_3A_SIGNAL_MAX_EDGES (1350) + +#define ISO14443_3A_SIGNAL_SEQUENCE_SIZE \ + (ISO14443_3A_SIGNAL_MAX_EDGES / (ISO14443_3A_SIGNAL_BIT_MAX_EDGES - 2)) + +#define ISO14443_3A_SIGNAL_F_SIG (13560000.0) +#define ISO14443_3A_SIGNAL_T_SIG 7374 //73.746ns*100 +#define ISO14443_3A_SIGNAL_T_SIG_X8 58992 //T_SIG*8 +#define ISO14443_3A_SIGNAL_T_SIG_X8_X8 471936 //T_SIG*8*8 +#define ISO14443_3A_SIGNAL_T_SIG_X8_X9 530928 //T_SIG*8*9 + +typedef enum { + Iso14443_3aSignalIndexZero, + Iso14443_3aSignalIndexOne, + Iso14443_3aSignalIndexCount, +} Iso14443_3aSignalIndex; + +typedef DigitalSignal* Iso14443_3aSignalBank[Iso14443_3aSignalIndexCount]; + +struct Iso14443_3aSignal { + DigitalSequence* tx_sequence; + Iso14443_3aSignalBank signals; +}; + +static void iso14443_3a_signal_add_byte(Iso14443_3aSignal* instance, uint8_t byte, bool parity) { + for(size_t i = 0; i < BITS_IN_BYTE; i++) { + digital_sequence_add_signal( + instance->tx_sequence, + FURI_BIT(byte, i) ? Iso14443_3aSignalIndexOne : Iso14443_3aSignalIndexZero); + } + digital_sequence_add_signal( + instance->tx_sequence, parity ? Iso14443_3aSignalIndexOne : Iso14443_3aSignalIndexZero); +} + +static void iso14443_3a_signal_encode( + Iso14443_3aSignal* instance, + const uint8_t* tx_data, + const uint8_t* tx_parity, + size_t tx_bits) { + furi_assert(instance); + furi_assert(tx_data); + furi_assert(tx_parity); + + // Start of frame + digital_sequence_add_signal(instance->tx_sequence, Iso14443_3aSignalIndexOne); + + if(tx_bits < BITS_IN_BYTE) { + for(size_t i = 0; i < tx_bits; i++) { + digital_sequence_add_signal( + instance->tx_sequence, + FURI_BIT(tx_data[0], i) ? Iso14443_3aSignalIndexOne : Iso14443_3aSignalIndexZero); + } + } else { + for(size_t i = 0; i < tx_bits / BITS_IN_BYTE; i++) { + bool parity = FURI_BIT(tx_parity[i / BITS_IN_BYTE], i % BITS_IN_BYTE); + iso14443_3a_signal_add_byte(instance, tx_data[i], parity); + } + } +} + +static inline void iso14443_3a_signal_set_bit(DigitalSignal* signal, bool bit) { + digital_signal_set_start_level(signal, bit); + + if(bit) { + for(uint32_t i = 0; i < 7; ++i) { + digital_signal_add_period(signal, ISO14443_3A_SIGNAL_T_SIG_X8); + } + digital_signal_add_period(signal, ISO14443_3A_SIGNAL_T_SIG_X8_X9); + } else { + digital_signal_add_period(signal, ISO14443_3A_SIGNAL_T_SIG_X8_X8); + for(uint32_t i = 0; i < 8; ++i) { + digital_signal_add_period(signal, ISO14443_3A_SIGNAL_T_SIG_X8); + } + } +} + +static inline void iso14443_3a_signal_bank_fill(Iso14443_3aSignalBank bank) { + for(uint32_t i = 0; i < Iso14443_3aSignalIndexCount; ++i) { + bank[i] = digital_signal_alloc(ISO14443_3A_SIGNAL_BIT_MAX_EDGES); + iso14443_3a_signal_set_bit(bank[i], i % Iso14443_3aSignalIndexCount != 0); + } +} + +static inline void iso14443_3a_signal_bank_clear(Iso14443_3aSignalBank bank) { + for(uint32_t i = 0; i < Iso14443_3aSignalIndexCount; ++i) { + digital_signal_free(bank[i]); + } +} + +static inline void + iso14443_3a_signal_bank_register(Iso14443_3aSignalBank bank, DigitalSequence* sequence) { + for(uint32_t i = 0; i < Iso14443_3aSignalIndexCount; ++i) { + digital_sequence_register_signal(sequence, i, bank[i]); + } +} + +Iso14443_3aSignal* iso14443_3a_signal_alloc(const GpioPin* pin) { + furi_assert(pin); + + Iso14443_3aSignal* instance = malloc(sizeof(Iso14443_3aSignal)); + instance->tx_sequence = digital_sequence_alloc(ISO14443_3A_SIGNAL_SEQUENCE_SIZE, pin); + + iso14443_3a_signal_bank_fill(instance->signals); + iso14443_3a_signal_bank_register(instance->signals, instance->tx_sequence); + + return instance; +} + +void iso14443_3a_signal_free(Iso14443_3aSignal* instance) { + furi_assert(instance); + furi_assert(instance->tx_sequence); + + iso14443_3a_signal_bank_clear(instance->signals); + digital_sequence_free(instance->tx_sequence); + free(instance); +} + +void iso14443_3a_signal_tx( + Iso14443_3aSignal* instance, + const uint8_t* tx_data, + const uint8_t* tx_parity, + size_t tx_bits) { + furi_assert(instance); + furi_assert(tx_data); + furi_assert(tx_parity); + + FURI_CRITICAL_ENTER(); + digital_sequence_clear(instance->tx_sequence); + iso14443_3a_signal_encode(instance, tx_data, tx_parity, tx_bits); + digital_sequence_transmit(instance->tx_sequence); + FURI_CRITICAL_EXIT(); +} diff --git a/lib/digital_signal/presets/nfc/iso14443_3a_signal.h b/lib/digital_signal/presets/nfc/iso14443_3a_signal.h new file mode 100644 index 00000000000..73269e66c8d --- /dev/null +++ b/lib/digital_signal/presets/nfc/iso14443_3a_signal.h @@ -0,0 +1,51 @@ +/** + * @file iso14443_3a_signal.h + * @brief DigitalSequence preset for generating ISO14443-3A compliant signals. + */ +#pragma once + +#include + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct Iso14443_3aSignal Iso14443_3aSignal; + +/** + * @brief Allocate an Iso14443_3aSignal instance with a set GPIO pin. + * + * @param[in] pin GPIO pin to use during transmission. + * @returns pointer to the allocated instance. + */ +Iso14443_3aSignal* iso14443_3a_signal_alloc(const GpioPin* pin); + +/** + * @brief Delete an Iso14443_3aSignal instance. + * + * @param[in,out] instance pointer to the instance to be deleted. + */ +void iso14443_3a_signal_free(Iso14443_3aSignal* instance); + +/** + * @brief Transmit arbitrary bytes using an Iso14443_3aSignal instance. + * + * This function will block until the transmisson has been completed. + * + * @param[in] instance pointer to the instance used in transmission. + * @param[in] tx_data pointer to the data to be transmitted. + * @param[in] tx_parity pointer to the bit-packed parity array. + * @param[in] tx_bits size of the data to be transmitted in bits. + */ +void iso14443_3a_signal_tx( + Iso14443_3aSignal* instance, + const uint8_t* tx_data, + const uint8_t* tx_parity, + size_t tx_bits); + +#ifdef __cplusplus +} +#endif diff --git a/lib/digital_signal/presets/nfc/iso15693_signal.c b/lib/digital_signal/presets/nfc/iso15693_signal.c new file mode 100644 index 00000000000..735a88f57aa --- /dev/null +++ b/lib/digital_signal/presets/nfc/iso15693_signal.c @@ -0,0 +1,204 @@ +#include "iso15693_signal.h" + +#include + +#define BITS_IN_BYTE (8U) + +#define ISO15693_SIGNAL_COEFF_HI (1U) +#define ISO15693_SIGNAL_COEFF_LO (4U) + +#define ISO15693_SIGNAL_ZERO_EDGES (16U) +#define ISO15693_SIGNAL_ONE_EDGES (ISO15693_SIGNAL_ZERO_EDGES + 1U) +#define ISO15693_SIGNAL_EOF_EDGES (64U) +#define ISO15693_SIGNAL_SOF_EDGES (ISO15693_SIGNAL_EOF_EDGES + 1U) +#define ISO15693_SIGNAL_EDGES (1350U) + +#define ISO15693_SIGNAL_FC (13.56e6) +#define ISO15693_SIGNAL_FC_16 (16.0e11 / ISO15693_SIGNAL_FC) +#define ISO15693_SIGNAL_FC_256 (256.0e11 / ISO15693_SIGNAL_FC) +#define ISO15693_SIGNAL_FC_768 (768.0e11 / ISO15693_SIGNAL_FC) + +typedef enum { + Iso15693SignalIndexSof, + Iso15693SignalIndexEof, + Iso15693SignalIndexOne, + Iso15693SignalIndexZero, + Iso15693SignalIndexNum, +} Iso15693SignalIndex; + +typedef DigitalSignal* Iso15693SignalBank[Iso15693SignalIndexNum]; + +struct Iso15693Signal { + DigitalSequence* tx_sequence; + Iso15693SignalBank banks[Iso15693SignalDataRateNum]; +}; + +// Add an unmodulated signal for the length of Fc / 256 * k (where k = 1 or 4) +static void iso15693_add_silence(DigitalSignal* signal, Iso15693SignalDataRate data_rate) { + const uint32_t k = data_rate == Iso15693SignalDataRateHi ? ISO15693_SIGNAL_COEFF_HI : + ISO15693_SIGNAL_COEFF_LO; + digital_signal_add_period_with_level(signal, ISO15693_SIGNAL_FC_256 * k, false); +} + +// Add 8 * k subcarrier pulses of Fc / 16 (where k = 1 or 4) +static void iso15693_add_subcarrier(DigitalSignal* signal, Iso15693SignalDataRate data_rate) { + const uint32_t k = data_rate == Iso15693SignalDataRateHi ? ISO15693_SIGNAL_COEFF_HI : + ISO15693_SIGNAL_COEFF_LO; + for(uint32_t i = 0; i < ISO15693_SIGNAL_ZERO_EDGES * k; ++i) { + digital_signal_add_period_with_level(signal, ISO15693_SIGNAL_FC_16, !(i % 2)); + } +} + +static void iso15693_add_bit(DigitalSignal* signal, Iso15693SignalDataRate data_rate, bool bit) { + if(bit) { + iso15693_add_silence(signal, data_rate); + iso15693_add_subcarrier(signal, data_rate); + } else { + iso15693_add_subcarrier(signal, data_rate); + iso15693_add_silence(signal, data_rate); + } +} + +static inline void iso15693_add_sof(DigitalSignal* signal, Iso15693SignalDataRate data_rate) { + // Not adding silence since it only increases response time + + for(uint32_t i = 0; i < ISO15693_SIGNAL_FC_768 / ISO15693_SIGNAL_FC_256; ++i) { + iso15693_add_subcarrier(signal, data_rate); + } + + iso15693_add_bit(signal, data_rate, true); +} + +static inline void iso15693_add_eof(DigitalSignal* signal, Iso15693SignalDataRate data_rate) { + iso15693_add_bit(signal, data_rate, false); + + for(uint32_t i = 0; i < ISO15693_SIGNAL_FC_768 / ISO15693_SIGNAL_FC_256; ++i) { + iso15693_add_subcarrier(signal, data_rate); + } + + // Not adding silence since it does nothing here +} + +static inline uint32_t + iso15693_get_sequence_index(Iso15693SignalIndex index, Iso15693SignalDataRate data_rate) { + return index + data_rate * Iso15693SignalIndexNum; +} + +static inline void + iso15693_add_byte(Iso15693Signal* instance, Iso15693SignalDataRate data_rate, uint8_t byte) { + for(size_t i = 0; i < BITS_IN_BYTE; i++) { + const uint8_t bit = byte & (1U << i); + digital_sequence_add_signal( + instance->tx_sequence, + iso15693_get_sequence_index( + bit ? Iso15693SignalIndexOne : Iso15693SignalIndexZero, data_rate)); + } +} + +static inline void iso15693_signal_encode( + Iso15693Signal* instance, + Iso15693SignalDataRate data_rate, + const uint8_t* tx_data, + size_t tx_data_size) { + digital_sequence_add_signal( + instance->tx_sequence, iso15693_get_sequence_index(Iso15693SignalIndexSof, data_rate)); + + for(size_t i = 0; i < tx_data_size; i++) { + iso15693_add_byte(instance, data_rate, tx_data[i]); + } + + digital_sequence_add_signal( + instance->tx_sequence, iso15693_get_sequence_index(Iso15693SignalIndexEof, data_rate)); +} + +static void iso15693_signal_bank_fill(Iso15693Signal* instance, Iso15693SignalDataRate data_rate) { + const uint32_t k = data_rate == Iso15693SignalDataRateHi ? ISO15693_SIGNAL_COEFF_HI : + ISO15693_SIGNAL_COEFF_LO; + DigitalSignal** bank = instance->banks[data_rate]; + + bank[Iso15693SignalIndexSof] = digital_signal_alloc(ISO15693_SIGNAL_SOF_EDGES * k); + bank[Iso15693SignalIndexEof] = digital_signal_alloc(ISO15693_SIGNAL_EOF_EDGES * k); + bank[Iso15693SignalIndexOne] = digital_signal_alloc(ISO15693_SIGNAL_ONE_EDGES * k); + bank[Iso15693SignalIndexZero] = digital_signal_alloc(ISO15693_SIGNAL_ZERO_EDGES * k); + + iso15693_add_sof(bank[Iso15693SignalIndexSof], data_rate); + iso15693_add_eof(bank[Iso15693SignalIndexEof], data_rate); + iso15693_add_bit(bank[Iso15693SignalIndexOne], data_rate, true); + iso15693_add_bit(bank[Iso15693SignalIndexZero], data_rate, false); +} + +static void + iso15693_signal_bank_clear(Iso15693Signal* instance, Iso15693SignalDataRate data_rate) { + DigitalSignal** bank = instance->banks[data_rate]; + + for(uint32_t i = 0; i < Iso15693SignalIndexNum; ++i) { + digital_signal_free(bank[i]); + } +} + +static void + iso15693_signal_bank_register(Iso15693Signal* instance, Iso15693SignalDataRate data_rate) { + for(uint32_t i = 0; i < Iso15693SignalIndexNum; ++i) { + digital_sequence_register_signal( + instance->tx_sequence, + iso15693_get_sequence_index(i, data_rate), + instance->banks[data_rate][i]); + } +} + +Iso15693Signal* iso15693_signal_alloc(const GpioPin* pin) { + furi_assert(pin); + + Iso15693Signal* instance = malloc(sizeof(Iso15693Signal)); + + instance->tx_sequence = digital_sequence_alloc(BITS_IN_BYTE * 255 + 2, pin); + + for(uint32_t i = 0; i < Iso15693SignalDataRateNum; ++i) { + iso15693_signal_bank_fill(instance, i); + iso15693_signal_bank_register(instance, i); + } + + return instance; +} + +void iso15693_signal_free(Iso15693Signal* instance) { + furi_assert(instance); + + digital_sequence_free(instance->tx_sequence); + + for(uint32_t i = 0; i < Iso15693SignalDataRateNum; ++i) { + iso15693_signal_bank_clear(instance, i); + } + + free(instance); +} + +void iso15693_signal_tx( + Iso15693Signal* instance, + Iso15693SignalDataRate data_rate, + const uint8_t* tx_data, + size_t tx_data_size) { + furi_assert(instance); + furi_assert(data_rate < Iso15693SignalDataRateNum); + furi_assert(tx_data); + + FURI_CRITICAL_ENTER(); + digital_sequence_clear(instance->tx_sequence); + iso15693_signal_encode(instance, data_rate, tx_data, tx_data_size); + digital_sequence_transmit(instance->tx_sequence); + + FURI_CRITICAL_EXIT(); +} + +void iso15693_signal_tx_sof(Iso15693Signal* instance, Iso15693SignalDataRate data_rate) { + furi_assert(instance); + furi_assert(data_rate < Iso15693SignalDataRateNum); + + FURI_CRITICAL_ENTER(); + digital_sequence_clear(instance->tx_sequence); + digital_sequence_add_signal( + instance->tx_sequence, iso15693_get_sequence_index(Iso15693SignalIndexSof, data_rate)); + digital_sequence_transmit(instance->tx_sequence); + + FURI_CRITICAL_EXIT(); +} diff --git a/lib/digital_signal/presets/nfc/iso15693_signal.h b/lib/digital_signal/presets/nfc/iso15693_signal.h new file mode 100644 index 00000000000..8219cff157e --- /dev/null +++ b/lib/digital_signal/presets/nfc/iso15693_signal.h @@ -0,0 +1,73 @@ +/** + * @file iso15693_signal.h + * @brief DigitalSequence preset for generating ISO15693-compliant signals. + * + */ +#pragma once + +#include + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct Iso15693Signal Iso15693Signal; + +/** + * @brief Supported data rates. + */ +typedef enum { + Iso15693SignalDataRateHi, /**< High data rate. */ + Iso15693SignalDataRateLo, /**< Low data rate. */ + Iso15693SignalDataRateNum, /**< Data rate mode count. Internal use. */ +} Iso15693SignalDataRate; + +/** + * @brief Allocate an Iso15693Signal instance with a set GPIO pin. + * + * @param[in] pin GPIO pin to use during transmission. + * @returns pointer to the allocated instance. + */ +Iso15693Signal* iso15693_signal_alloc(const GpioPin* pin); + +/** + * @brief Delete an Iso15693Signal instance. + * + * @param[in,out] instance pointer to the instance to be deleted. + */ +void iso15693_signal_free(Iso15693Signal* instance); + +/** + * @brief Transmit arbitrary bytes using an Iso15693Signal instance. + * @see Iso15693SignalDataRate + * + * This function will block until the transmisson has been completed. + * + * @param[in] instance pointer to the instance used in transmission. + * @param[in] data_rate data rate to transmit at. + * @param[in] tx_data pointer to the data to be transmitted. + * @param[in] tx_data_size size of the data to be transmitted in bytes. + */ +void iso15693_signal_tx( + Iso15693Signal* instance, + Iso15693SignalDataRate data_rate, + const uint8_t* tx_data, + size_t tx_data_size); + +/** + * @brief Transmit Start of Frame using an Iso15693Signal instance. + * @see Iso15693SignalDataRate + * + * This function will block until the transmisson has been completed. + * + * @param[in] instance pointer to the instance used in transmission. + * @param[in] data_rate data rate to transmit at. + */ +void iso15693_signal_tx_sof(Iso15693Signal* instance, Iso15693SignalDataRate data_rate); + +#ifdef __cplusplus +} +#endif diff --git a/lib/drivers/st25r3916.c b/lib/drivers/st25r3916.c new file mode 100644 index 00000000000..47726121358 --- /dev/null +++ b/lib/drivers/st25r3916.c @@ -0,0 +1,81 @@ +#include "st25r3916.h" + +#include + +void st25r3916_mask_irq(FuriHalSpiBusHandle* handle, uint32_t mask) { + furi_assert(handle); + + uint8_t irq_mask_regs[4] = { + mask & 0xff, + (mask >> 8) & 0xff, + (mask >> 16) & 0xff, + (mask >> 24) & 0xff, + }; + st25r3916_write_burst_regs(handle, ST25R3916_REG_IRQ_MASK_MAIN, irq_mask_regs, 4); +} + +uint32_t st25r3916_get_irq(FuriHalSpiBusHandle* handle) { + furi_assert(handle); + + uint8_t irq_regs[4] = {}; + uint32_t irq = 0; + st25r3916_read_burst_regs(handle, ST25R3916_REG_IRQ_MASK_MAIN, irq_regs, 4); + // FURI_LOG_I( + // "Mask Irq", "%02X %02X %02X %02X", irq_regs[0], irq_regs[1], irq_regs[2], irq_regs[3]); + st25r3916_read_burst_regs(handle, ST25R3916_REG_IRQ_MAIN, irq_regs, 4); + irq = (uint32_t)irq_regs[0]; + irq |= (uint32_t)irq_regs[1] << 8; + irq |= (uint32_t)irq_regs[2] << 16; + irq |= (uint32_t)irq_regs[3] << 24; + // FURI_LOG_I("iRQ", "%02X %02X %02X %02X", irq_regs[0], irq_regs[1], irq_regs[2], irq_regs[3]); + + return irq; +} + +void st25r3916_write_fifo(FuriHalSpiBusHandle* handle, const uint8_t* buff, size_t bits) { + furi_assert(handle); + furi_assert(buff); + + size_t bytes = (bits + 7) / 8; + + st25r3916_write_reg(handle, ST25R3916_REG_NUM_TX_BYTES2, (uint8_t)(bits & 0xFFU)); + st25r3916_write_reg(handle, ST25R3916_REG_NUM_TX_BYTES1, (uint8_t)((bits >> 8) & 0xFFU)); + + st25r3916_reg_write_fifo(handle, buff, bytes); +} + +bool st25r3916_read_fifo( + FuriHalSpiBusHandle* handle, + uint8_t* buff, + size_t buff_size, + size_t* buff_bits) { + furi_assert(handle); + furi_assert(buff); + + bool read_success = false; + + do { + uint8_t fifo_status[2] = {}; + st25r3916_read_burst_regs(handle, ST25R3916_REG_FIFO_STATUS1, fifo_status, 2); + size_t bytes = ((fifo_status[1] & ST25R3916_REG_FIFO_STATUS2_fifo_b_mask) >> + ST25R3916_REG_FIFO_STATUS2_fifo_b_shift) | + fifo_status[0]; + uint8_t bits = + ((fifo_status[1] & ST25R3916_REG_FIFO_STATUS2_fifo_lb_mask) >> + ST25R3916_REG_FIFO_STATUS2_fifo_lb_shift); + + if(bytes == 0) break; + if(bytes > buff_size) break; + + st25r3916_reg_read_fifo(handle, buff, bytes); + + if(bits) { + *buff_bits = (bytes - 1) * 8 + bits; + } else { + *buff_bits = bytes * 8; + } + read_success = true; + } while(false); + + return read_success; +} diff --git a/lib/drivers/st25r3916.h b/lib/drivers/st25r3916.h new file mode 100644 index 00000000000..532239f4728 --- /dev/null +++ b/lib/drivers/st25r3916.h @@ -0,0 +1,113 @@ +#pragma once + +#include "st25r3916_reg.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define ST25R3916_IRQ_MASK_ALL (uint32_t)(0xFFFFFFFFUL) /** All ST25R3916 interrupt sources */ +#define ST25R3916_IRQ_MASK_NONE (uint32_t)(0x00000000UL) /**No ST25R3916 interrupt source */ + +/** Main interrupt register */ +#define ST25R3916_IRQ_MASK_OSC (uint32_t)(0x00000080U) /** ST25R3916 oscillator stable interrupt */ +#define ST25R3916_IRQ_MASK_FWL (uint32_t)(0x00000040U) /** ST25R3916 FIFO water level interrupt */ +#define ST25R3916_IRQ_MASK_RXS (uint32_t)(0x00000020U) /** ST25R3916 start of receive interrupt */ +#define ST25R3916_IRQ_MASK_RXE (uint32_t)(0x00000010U) /** ST25R3916 end of receive interrupt */ +#define ST25R3916_IRQ_MASK_TXE \ + (uint32_t)(0x00000008U) /** ST25R3916 end of transmission interrupt */ +#define ST25R3916_IRQ_MASK_COL (uint32_t)(0x00000004U) /** ST25R3916 bit collision interrupt */ +#define ST25R3916_IRQ_MASK_RX_REST \ + (uint32_t)(0x00000002U) /** ST25R3916 automatic reception restart interrupt */ +#define ST25R3916_IRQ_MASK_RFU (uint32_t)(0x00000001U) /** ST25R3916 RFU interrupt */ + +/** Timer and NFC interrupt register */ +#define ST25R3916_IRQ_MASK_DCT \ + (uint32_t)(0x00008000U) /** ST25R3916 termination of direct command interrupt. */ +#define ST25R3916_IRQ_MASK_NRE \ + (uint32_t)(0x00004000U) /** ST25R3916 no-response timer expired interrupt */ +#define ST25R3916_IRQ_MASK_GPE \ + (uint32_t)(0x00002000U) /** ST25R3916 general purpose timer expired interrupt */ +#define ST25R3916_IRQ_MASK_EON (uint32_t)(0x00001000U) /** ST25R3916 external field on interrupt */ +#define ST25R3916_IRQ_MASK_EOF \ + (uint32_t)(0x00000800U) /** ST25R3916 external field off interrupt */ +#define ST25R3916_IRQ_MASK_CAC \ + (uint32_t)(0x00000400U) /** ST25R3916 collision during RF collision avoidance */ +#define ST25R3916_IRQ_MASK_CAT \ + (uint32_t)(0x00000200U) /** ST25R3916 minimum guard time expired interrupt */ +#define ST25R3916_IRQ_MASK_NFCT \ + (uint32_t)(0x00000100U) /** ST25R3916 initiator bit rate recognised interrupt */ + +/** Error and wake-up interrupt register */ +#define ST25R3916_IRQ_MASK_CRC (uint32_t)(0x00800000U) /** ST25R3916 CRC error interrupt */ +#define ST25R3916_IRQ_MASK_PAR (uint32_t)(0x00400000U) /** ST25R3916 parity error interrupt */ +#define ST25R3916_IRQ_MASK_ERR2 \ + (uint32_t)(0x00200000U) /** ST25R3916 soft framing error interrupt */ +#define ST25R3916_IRQ_MASK_ERR1 \ + (uint32_t)(0x00100000U) /** ST25R3916 hard framing error interrupt */ +#define ST25R3916_IRQ_MASK_WT (uint32_t)(0x00080000U) /** ST25R3916 wake-up interrupt */ +#define ST25R3916_IRQ_MASK_WAM \ + (uint32_t)(0x00040000U) /** ST25R3916 wake-up due to amplitude interrupt */ +#define ST25R3916_IRQ_MASK_WPH \ + (uint32_t)(0x00020000U) /** ST25R3916 wake-up due to phase interrupt */ +#define ST25R3916_IRQ_MASK_WCAP \ + (uint32_t)(0x00010000U) /** ST25R3916 wake-up due to capacitance measurement */ + +/** Passive Target Interrupt Register */ +#define ST25R3916_IRQ_MASK_PPON2 \ + (uint32_t)(0x80000000U) /** ST25R3916 PPON2 Field on waiting Timer interrupt */ +#define ST25R3916_IRQ_MASK_SL_WL \ + (uint32_t)(0x40000000U) /** ST25R3916 Passive target slot number water level interrupt */ +#define ST25R3916_IRQ_MASK_APON \ + (uint32_t)(0x20000000U) /** ST25R3916 Anticollision done and Field On interrupt */ +#define ST25R3916_IRQ_MASK_RXE_PTA \ + (uint32_t)(0x10000000U) /** ST25R3916 RXE with an automatic response interrupt */ +#define ST25R3916_IRQ_MASK_WU_F \ + (uint32_t)(0x08000000U) /** ST25R3916 212/424b/s Passive target interrupt: Active */ +#define ST25R3916_IRQ_MASK_RFU2 (uint32_t)(0x04000000U) /** ST25R3916 RFU2 interrupt */ +#define ST25R3916_IRQ_MASK_WU_A_X \ + (uint32_t)(0x02000000U) /** ST25R3916 106kb/s Passive target state interrupt: Active* */ +#define ST25R3916_IRQ_MASK_WU_A \ + (uint32_t)(0x01000000U) /** ST25R3916 106kb/s Passive target state interrupt: Active */ + +/** Mask st25r3916 interrupts + * + * @param handle - pointer to FuriHalSpiBusHandle instance + * @param mask - mask of interrupts to be disabled + */ +void st25r3916_mask_irq(FuriHalSpiBusHandle* handle, uint32_t mask); + +/** Get st25r3916 interrupts + * + * @param handle - pointer to FuriHalSpiBusHandle instance + * + * @return received interrupts + */ +uint32_t st25r3916_get_irq(FuriHalSpiBusHandle* handle); + +/** Write FIFO + * + * @param handle - pointer to FuriHalSpiBusHandle instance + * @param buff - buffer to write to FIFO + * @param bits - number of bits to write + */ +void st25r3916_write_fifo(FuriHalSpiBusHandle* handle, const uint8_t* buff, size_t bits); + +/** Read FIFO + * + * @param handle - pointer to FuriHalSpiBusHandle instance + * @param buff - buffer to read from FIFO + * @param buff_size - buffer size n bytes + * @param buff_bits - pointer to number of bits read + * + * @return true if read success, false otherwise +*/ +bool st25r3916_read_fifo( + FuriHalSpiBusHandle* handle, + uint8_t* buff, + size_t buff_size, + size_t* buff_bits); + +#ifdef __cplusplus +} +#endif diff --git a/lib/drivers/st25r3916_reg.c b/lib/drivers/st25r3916_reg.c new file mode 100644 index 00000000000..8ac4672383c --- /dev/null +++ b/lib/drivers/st25r3916_reg.c @@ -0,0 +1,257 @@ +#include "st25r3916_reg.h" + +#include + +#define ST25R3916_WRITE_MODE \ + (0U << 6) /*!< ST25R3916 Operation Mode: Write */ +#define ST25R3916_READ_MODE \ + (1U << 6) /*!< ST25R3916 Operation Mode: Read */ +#define ST25R3916_CMD_MODE \ + (3U << 6) /*!< ST25R3916 Operation Mode: Direct Command */ +#define ST25R3916_FIFO_LOAD \ + (0x80U) /*!< ST25R3916 Operation Mode: FIFO Load */ +#define ST25R3916_FIFO_READ \ + (0x9FU) /*!< ST25R3916 Operation Mode: FIFO Read */ +#define ST25R3916_PT_A_CONFIG_LOAD \ + (0xA0U) /*!< ST25R3916 Operation Mode: Passive Target Memory A-Config Load */ +#define ST25R3916_PT_F_CONFIG_LOAD \ + (0xA8U) /*!< ST25R3916 Operation Mode: Passive Target Memory F-Config Load */ +#define ST25R3916_PT_TSN_DATA_LOAD \ + (0xACU) /*!< ST25R3916 Operation Mode: Passive Target Memory TSN Load */ +#define ST25R3916_PT_MEM_READ \ + (0xBFU) /*!< ST25R3916 Operation Mode: Passive Target Memory Read */ + +#define ST25R3916_CMD_LEN \ + (1U) /*!< ST25R3916 CMD length */ +#define ST25R3916_FIFO_DEPTH (512U) +#define ST25R3916_BUF_LEN \ + (ST25R3916_CMD_LEN + \ + ST25R3916_FIFO_DEPTH) /*!< ST25R3916 communication buffer: CMD + FIFO length */ + +static void st25r3916_reg_tx_byte(FuriHalSpiBusHandle* handle, uint8_t byte) { + uint8_t val = byte; + furi_hal_spi_bus_tx(handle, &val, 1, 5); +} + +void st25r3916_read_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* val) { + furi_assert(handle); + st25r3916_read_burst_regs(handle, reg, val, 1); +} + +void st25r3916_read_burst_regs( + FuriHalSpiBusHandle* handle, + uint8_t reg_start, + uint8_t* values, + uint8_t length) { + furi_assert(handle); + furi_assert(values); + furi_assert(length); + + furi_hal_gpio_write(handle->cs, false); + + if(reg_start & ST25R3916_SPACE_B) { + // Send direct command first + st25r3916_reg_tx_byte(handle, ST25R3916_CMD_SPACE_B_ACCESS); + } + st25r3916_reg_tx_byte(handle, (reg_start & ~ST25R3916_SPACE_B) | ST25R3916_READ_MODE); + furi_hal_spi_bus_rx(handle, values, length, 5); + + furi_hal_gpio_write(handle->cs, true); +} + +void st25r3916_write_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t val) { + furi_assert(handle); + uint8_t reg_val = val; + st25r3916_write_burst_regs(handle, reg, ®_val, 1); +} + +void st25r3916_write_burst_regs( + FuriHalSpiBusHandle* handle, + uint8_t reg_start, + const uint8_t* values, + uint8_t length) { + furi_assert(handle); + furi_assert(values); + furi_assert(length); + + furi_hal_gpio_write(handle->cs, false); + + if(reg_start & ST25R3916_SPACE_B) { + // Send direct command first + st25r3916_reg_tx_byte(handle, ST25R3916_CMD_SPACE_B_ACCESS); + } + st25r3916_reg_tx_byte(handle, (reg_start & ~ST25R3916_SPACE_B) | ST25R3916_WRITE_MODE); + furi_hal_spi_bus_tx(handle, values, length, 5); + + furi_hal_gpio_write(handle->cs, true); +} + +void st25r3916_reg_write_fifo(FuriHalSpiBusHandle* handle, const uint8_t* buff, size_t length) { + furi_assert(handle); + furi_assert(buff); + furi_assert(length); + furi_assert(length <= ST25R3916_FIFO_DEPTH); + + furi_hal_gpio_write(handle->cs, false); + st25r3916_reg_tx_byte(handle, ST25R3916_FIFO_LOAD); + furi_hal_spi_bus_tx(handle, buff, length, 200); + furi_hal_gpio_write(handle->cs, true); +} + +void st25r3916_reg_read_fifo(FuriHalSpiBusHandle* handle, uint8_t* buff, size_t length) { + furi_assert(handle); + furi_assert(buff); + furi_assert(length); + furi_assert(length <= ST25R3916_FIFO_DEPTH); + + furi_hal_gpio_write(handle->cs, false); + st25r3916_reg_tx_byte(handle, ST25R3916_FIFO_READ); + furi_hal_spi_bus_rx(handle, buff, length, 200); + furi_hal_gpio_write(handle->cs, true); +} + +void st25r3916_write_pta_mem(FuriHalSpiBusHandle* handle, const uint8_t* values, size_t length) { + furi_assert(handle); + furi_assert(values); + furi_assert(length); + furi_assert(length <= ST25R3916_PTM_LEN); + + furi_hal_gpio_write(handle->cs, false); + st25r3916_reg_tx_byte(handle, ST25R3916_PT_A_CONFIG_LOAD); + furi_hal_spi_bus_tx(handle, values, length, 200); + furi_hal_gpio_write(handle->cs, true); +} + +void st25r3916_read_pta_mem(FuriHalSpiBusHandle* handle, uint8_t* buff, size_t length) { + furi_assert(handle); + furi_assert(buff); + furi_assert(length); + furi_assert(length <= ST25R3916_PTM_LEN); + + uint8_t tmp_buff[ST25R3916_PTM_LEN + 1]; + furi_hal_gpio_write(handle->cs, false); + st25r3916_reg_tx_byte(handle, ST25R3916_PT_MEM_READ); + furi_hal_spi_bus_rx(handle, tmp_buff, length + 1, 200); + furi_hal_gpio_write(handle->cs, true); + memcpy(buff, tmp_buff + 1, length); +} + +void st25r3916_write_ptf_mem(FuriHalSpiBusHandle* handle, const uint8_t* values, size_t length) { + furi_assert(handle); + furi_assert(values); + + furi_hal_gpio_write(handle->cs, false); + st25r3916_reg_tx_byte(handle, ST25R3916_PT_F_CONFIG_LOAD); + furi_hal_spi_bus_tx(handle, values, length, 200); + furi_hal_gpio_write(handle->cs, true); +} + +void st25r3916_write_pttsn_mem(FuriHalSpiBusHandle* handle, uint8_t* buff, size_t length) { + furi_assert(handle); + furi_assert(buff); + + furi_hal_gpio_write(handle->cs, false); + st25r3916_reg_tx_byte(handle, ST25R3916_PT_TSN_DATA_LOAD); + furi_hal_spi_bus_tx(handle, buff, length, 200); + furi_hal_gpio_write(handle->cs, true); +} + +void st25r3916_direct_cmd(FuriHalSpiBusHandle* handle, uint8_t cmd) { + furi_assert(handle); + + furi_hal_gpio_write(handle->cs, false); + st25r3916_reg_tx_byte(handle, cmd | ST25R3916_CMD_MODE); + furi_hal_gpio_write(handle->cs, true); +} + +void st25r3916_read_test_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* val) { + furi_assert(handle); + + furi_hal_gpio_write(handle->cs, false); + st25r3916_reg_tx_byte(handle, ST25R3916_CMD_TEST_ACCESS); + st25r3916_reg_tx_byte(handle, reg | ST25R3916_READ_MODE); + furi_hal_spi_bus_rx(handle, val, 1, 5); + furi_hal_gpio_write(handle->cs, true); +} + +void st25r3916_write_test_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t val) { + furi_assert(handle); + + furi_hal_gpio_write(handle->cs, false); + st25r3916_reg_tx_byte(handle, ST25R3916_CMD_TEST_ACCESS); + st25r3916_reg_tx_byte(handle, reg | ST25R3916_WRITE_MODE); + furi_hal_spi_bus_tx(handle, &val, 1, 5); + furi_hal_gpio_write(handle->cs, true); +} + +void st25r3916_clear_reg_bits(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t clr_mask) { + furi_assert(handle); + + uint8_t reg_val = 0; + st25r3916_read_reg(handle, reg, ®_val); + if((reg_val & ~clr_mask) != reg_val) { + reg_val &= ~clr_mask; + st25r3916_write_reg(handle, reg, reg_val); + } +} + +void st25r3916_set_reg_bits(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t set_mask) { + furi_assert(handle); + + uint8_t reg_val = 0; + st25r3916_read_reg(handle, reg, ®_val); + if((reg_val | set_mask) != reg_val) { + reg_val |= set_mask; + st25r3916_write_reg(handle, reg, reg_val); + } +} + +void st25r3916_change_reg_bits( + FuriHalSpiBusHandle* handle, + uint8_t reg, + uint8_t mask, + uint8_t value) { + furi_assert(handle); + + st25r3916_modify_reg(handle, reg, mask, (mask & value)); +} + +void st25r3916_modify_reg( + FuriHalSpiBusHandle* handle, + uint8_t reg, + uint8_t clr_mask, + uint8_t set_mask) { + furi_assert(handle); + + uint8_t reg_val = 0; + uint8_t new_val = 0; + st25r3916_read_reg(handle, reg, ®_val); + new_val = (reg_val & ~clr_mask) | set_mask; + if(new_val != reg_val) { + st25r3916_write_reg(handle, reg, new_val); + } +} + +void st25r3916_change_test_reg_bits( + FuriHalSpiBusHandle* handle, + uint8_t reg, + uint8_t mask, + uint8_t value) { + furi_assert(handle); + + uint8_t reg_val = 0; + uint8_t new_val = 0; + st25r3916_read_test_reg(handle, reg, ®_val); + new_val = (reg_val & ~mask) | (mask & value); + if(new_val != reg_val) { + st25r3916_write_test_reg(handle, reg, new_val); + } +} + +bool st25r3916_check_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t mask, uint8_t val) { + furi_assert(handle); + + uint8_t reg_val = 0; + st25r3916_read_reg(handle, reg, ®_val); + return ((reg_val & mask) == val); +} diff --git a/lib/ST25RFAL002/source/st25r3916/st25r3916_com.h b/lib/drivers/st25r3916_reg.h similarity index 62% rename from lib/ST25RFAL002/source/st25r3916/st25r3916_com.h rename to lib/drivers/st25r3916_reg.h index 6a47317e373..8a924fc3929 100644 --- a/lib/ST25RFAL002/source/st25r3916/st25r3916_com.h +++ b/lib/drivers/st25r3916_reg.h @@ -1,262 +1,202 @@ - -/****************************************************************************** - * \attention - * - *

    © COPYRIGHT 2020 STMicroelectronics

    - * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R3916 firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file - * - * \author Gustavo Patricio - * - * \brief ST25R3916 communication declaration file - * - * This driver provides basic abstraction for communication with the ST25R3916 - * - * - * \addtogroup RFAL - * @{ - * - * \addtogroup RFAL-HAL - * \brief RFAL Hardware Abstraction Layer - * @{ - * - * \addtogroup ST25R3916 - * \brief RFAL ST25R3916 Driver - * @{ - * - * \addtogroup ST25R3916_COM - * \brief RFAL ST25R3916 Communications - * @{ - * - */ - -#ifndef ST25R3916_COM_H -#define ST25R3916_COM_H - -/* -****************************************************************************** -* INCLUDES -****************************************************************************** -*/ -#include "platform.h" -#include "st_errno.h" - -/* -****************************************************************************** -* GLOBAL DEFINES -****************************************************************************** -*/ -#define ST25R3916_SPACE_B 0x40U /*!< ST25R3916 Space-B indicator */ -#define ST25R3916_SPACE_B_REG_LEN 16U /*!< Number of register in the space B */ - -#define ST25R3916_FIFO_STATUS_LEN 2 /*!< Number of FIFO Status Register */ - -#define ST25R3916_PTM_A_LEN 15U /*!< Passive target memory A config length */ -#define ST25R3916_PTM_B_LEN 0U /*!< Passive target memory B config length */ -#define ST25R3916_PTM_F_LEN 21U /*!< Passive target memory F config length */ -#define ST25R3916_PTM_TSN_LEN 12U /*!< Passive target memory TSN data length */ - -/*! Full Passive target memory length */ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** ST25R3916 direct commands */ +#define ST25R3916_CMD_SET_DEFAULT \ + 0xC1U /** Puts the chip in default state (same as after power-up */ +#define ST25R3916_CMD_STOP 0xC2U /*!< Stops all activities and clears FIFO */ +#define ST25R3916_CMD_TRANSMIT_WITH_CRC 0xC4U /** Transmit with CRC */ +#define ST25R3916_CMD_TRANSMIT_WITHOUT_CRC 0xC5U /** Transmit without CRC */ +#define ST25R3916_CMD_TRANSMIT_REQA 0xC6U /** Transmit REQA */ +#define ST25R3916_CMD_TRANSMIT_WUPA 0xC7U /** Transmit WUPA */ +#define ST25R3916_CMD_INITIAL_RF_COLLISION \ + 0xC8U /** NFC transmit with Initial RF Collision Avoidance */ +#define ST25R3916_CMD_RESPONSE_RF_COLLISION_N \ + 0xC9U /** NFC transmit with Response RF Collision Avoidance */ +#define ST25R3916_CMD_GOTO_SENSE 0xCDU /** Passive target logic to Sense/Idle state */ +#define ST25R3916_CMD_GOTO_SLEEP 0xCEU /** Passive target logic to Sleep/Halt state */ +#define ST25R3916_CMD_MASK_RECEIVE_DATA 0xD0U /** Mask receive data */ +#define ST25R3916_CMD_UNMASK_RECEIVE_DATA 0xD1U /** Unmask receive data */ +#define ST25R3916_CMD_AM_MOD_STATE_CHANGE 0xD2U /** AM Modulation state change */ +#define ST25R3916_CMD_MEASURE_AMPLITUDE 0xD3U /** Measure singal amplitude on RFI inputs */ +#define ST25R3916_CMD_RESET_RXGAIN 0xD5U /** Reset RX Gain */ +#define ST25R3916_CMD_ADJUST_REGULATORS 0xD6U /** Adjust regulators */ +#define ST25R3916_CMD_CALIBRATE_DRIVER_TIMING \ + 0xD8U /** Starts the sequence to adjust the driver timing */ +#define ST25R3916_CMD_MEASURE_PHASE 0xD9U /** Measure phase between RFO and RFI signal */ +#define ST25R3916_CMD_CLEAR_RSSI 0xDAU /** Clear RSSI bits and restart the measurement */ +#define ST25R3916_CMD_CLEAR_FIFO 0xDBU /** Clears FIFO, Collision and IRQ status */ +#define ST25R3916_CMD_TRANSPARENT_MODE 0xDCU /** Transparent mode */ +#define ST25R3916_CMD_CALIBRATE_C_SENSOR 0xDDU /** Calibrate the capacitive sensor */ +#define ST25R3916_CMD_MEASURE_CAPACITANCE 0xDEU /** Measure capacitance */ +#define ST25R3916_CMD_MEASURE_VDD 0xDFU /** Measure power supply voltage */ +#define ST25R3916_CMD_START_GP_TIMER 0xE0U /** Start the general purpose timer */ +#define ST25R3916_CMD_START_WUP_TIMER 0xE1U /** Start the wake-up timer */ +#define ST25R3916_CMD_START_MASK_RECEIVE_TIMER 0xE2U /** Start the mask-receive timer */ +#define ST25R3916_CMD_START_NO_RESPONSE_TIMER 0xE3U /** Start the no-response timer */ +#define ST25R3916_CMD_START_PPON2_TIMER 0xE4U /** Start PPon2 timer */ +#define ST25R3916_CMD_STOP_NRT 0xE8U /** Stop No Response Timer */ +#define ST25R3916_CMD_SPACE_B_ACCESS 0xFBU /** Enable R/W access to the test registers */ +#define ST25R3916_CMD_TEST_ACCESS 0xFCU /** Enable R/W access to the test registers */ + +#define ST25R3916_SPACE_B 0x40U /** ST25R3916 Space-B indicator */ +#define ST25R3916_SPACE_B_REG_LEN 16U /** Number of register in the space B */ + +#define ST25R3916_FIFO_STATUS_LEN 2 /** Number of FIFO Status Register */ + +#define ST25R3916_PTM_A_LEN 15U /** Passive target memory A config length */ +#define ST25R3916_PTM_B_LEN 0U /** Passive target memory B config length */ +#define ST25R3916_PTM_F_LEN 21U /** Passive target memory F config length */ +#define ST25R3916_PTM_TSN_LEN 12U /** Passive target memory TSN data length */ + +/** Full Passive target memory length */ #define ST25R3916_PTM_LEN \ (ST25R3916_PTM_A_LEN + ST25R3916_PTM_B_LEN + ST25R3916_PTM_F_LEN + ST25R3916_PTM_TSN_LEN) -/* IO configuration registers */ -#define ST25R3916_REG_IO_CONF1 0x00U /*!< RW IO Configuration Register 1 */ -#define ST25R3916_REG_IO_CONF2 0x01U /*!< RW IO Configuration Register 2 */ +/** IO configuration registers */ +#define ST25R3916_REG_IO_CONF1 0x00U /** RW IO Configuration Register 1 */ +#define ST25R3916_REG_IO_CONF2 0x01U /** RW IO Configuration Register 2 */ -/* Operation control and mode definition registers */ -#define ST25R3916_REG_OP_CONTROL 0x02U /*!< RW Operation Control Register */ -#define ST25R3916_REG_MODE 0x03U /*!< RW Mode Definition Register */ -#define ST25R3916_REG_BIT_RATE 0x04U /*!< RW Bit Rate Definition Register */ +/** Operation control and mode definition */ +#define ST25R3916_REG_OP_CONTROL 0x02U /** RW Operation Control Register */ +#define ST25R3916_REG_MODE 0x03U /** RW Mode Definition Register */ +#define ST25R3916_REG_BIT_RATE 0x04U /** RW Bit Rate Definition Register */ -/* Protocol Configuration registers */ -#define ST25R3916_REG_ISO14443A_NFC \ - 0x05U /*!< RW ISO14443A and NFC 106 kBit/s Settings Register */ +/** Protocol Configuration registers */ +#define ST25R3916_REG_ISO14443A_NFC 0x05U /** RW ISO14443A and NFC 106 kBit/s Settings Register */ #define ST25R3916_REG_EMD_SUP_CONF \ - (ST25R3916_SPACE_B | 0x05U) /*!< RW EMD Suppression Configuration Register */ -#define ST25R3916_REG_ISO14443B_1 \ - 0x06U /*!< RW ISO14443B Settings Register 1 */ + (ST25R3916_SPACE_B | 0x05U) /*!< RW EMD Suppression Configuration Register */ +#define ST25R3916_REG_ISO14443B_1 0x06U /** RW ISO14443B Settings Register 1 */ #define ST25R3916_REG_SUBC_START_TIME \ - (ST25R3916_SPACE_B | 0x06U) /*!< RW Subcarrier Start Time Register */ -#define ST25R3916_REG_ISO14443B_2 \ - 0x07U /*!< RW ISO14443B Settings Register 2 */ -#define ST25R3916_REG_PASSIVE_TARGET \ - 0x08U /*!< RW Passive Target Definition Register */ -#define ST25R3916_REG_STREAM_MODE \ - 0x09U /*!< RW Stream Mode Definition Register */ -#define ST25R3916_REG_AUX 0x0AU /*!< RW Auxiliary Definition Register */ - -/* Receiver Configuration registers */ -#define ST25R3916_REG_RX_CONF1 0x0BU /*!< RW Receiver Configuration Register 1 */ -#define ST25R3916_REG_RX_CONF2 0x0CU /*!< RW Receiver Configuration Register 2 */ -#define ST25R3916_REG_RX_CONF3 0x0DU /*!< RW Receiver Configuration Register 3 */ -#define ST25R3916_REG_RX_CONF4 0x0EU /*!< RW Receiver Configuration Register 4 */ + (ST25R3916_SPACE_B | 0x06U) /*!< RW Subcarrier Start Time Register */ +#define ST25R3916_REG_ISO14443B_2 0x07U /** RW ISO14443B Settings Register 2 */ +#define ST25R3916_REG_PASSIVE_TARGET 0x08U /** RW Passive Target Definition Register */ +#define ST25R3916_REG_STREAM_MODE 0x09U /** RW Stream Mode Definition Register */ +#define ST25R3916_REG_AUX 0x0AU /** RW Auxiliary Definition Register */ + +/** Receiver Configuration registers */ +#define ST25R3916_REG_RX_CONF1 0x0BU /** RW Receiver Configuration Register 1 */ +#define ST25R3916_REG_RX_CONF2 0x0CU /** RW Receiver Configuration Register 2 */ +#define ST25R3916_REG_RX_CONF3 0x0DU /** RW Receiver Configuration Register 3 */ +#define ST25R3916_REG_RX_CONF4 0x0EU /** RW Receiver Configuration Register 4 */ #define ST25R3916_REG_P2P_RX_CONF \ - (ST25R3916_SPACE_B | 0x0BU) /*!< RW P2P Receiver Configuration Register 1 */ + (ST25R3916_SPACE_B | 0x0BU) /** RW P2P Receiver Configuration Register 1 */ #define ST25R3916_REG_CORR_CONF1 \ - (ST25R3916_SPACE_B | 0x0CU) /*!< RW Correlator configuration register 1 */ + (ST25R3916_SPACE_B | 0x0CU) /** RW Correlator configuration register 1 */ #define ST25R3916_REG_CORR_CONF2 \ - (ST25R3916_SPACE_B | 0x0DU) /*!< RW Correlator configuration register 2 */ - -/* Timer definition registers */ -#define ST25R3916_REG_MASK_RX_TIMER \ - 0x0FU /*!< RW Mask Receive Timer Register */ -#define ST25R3916_REG_NO_RESPONSE_TIMER1 \ - 0x10U /*!< RW No-response Timer Register 1 */ -#define ST25R3916_REG_NO_RESPONSE_TIMER2 \ - 0x11U /*!< RW No-response Timer Register 2 */ -#define ST25R3916_REG_TIMER_EMV_CONTROL \ - 0x12U /*!< RW Timer and EMV Control */ -#define ST25R3916_REG_GPT1 0x13U /*!< RW General Purpose Timer Register 1 */ -#define ST25R3916_REG_GPT2 0x14U /*!< RW General Purpose Timer Register 2 */ -#define ST25R3916_REG_PPON2 0x15U /*!< RW PPON2 Field waiting Timer Register */ -#define ST25R3916_REG_SQUELCH_TIMER \ - (ST25R3916_SPACE_B | 0x0FU) /*!< RW Squelch timeout Register */ -#define ST25R3916_REG_FIELD_ON_GT \ - (ST25R3916_SPACE_B | 0x15U) /*!< RW NFC Field on guard time */ - -/* Interrupt and associated reporting registers */ -#define ST25R3916_REG_IRQ_MASK_MAIN \ - 0x16U /*!< RW Mask Main Interrupt Register */ -#define ST25R3916_REG_IRQ_MASK_TIMER_NFC \ - 0x17U /*!< RW Mask Timer and NFC Interrupt Register */ -#define ST25R3916_REG_IRQ_MASK_ERROR_WUP \ - 0x18U /*!< RW Mask Error and Wake-up Interrupt Register */ -#define ST25R3916_REG_IRQ_MASK_TARGET \ - 0x19U /*!< RW Mask 3916 Target Interrupt Register */ -#define ST25R3916_REG_IRQ_MAIN 0x1AU /*!< R Main Interrupt Register */ -#define ST25R3916_REG_IRQ_TIMER_NFC \ - 0x1BU /*!< R Timer and NFC Interrupt Register */ -#define ST25R3916_REG_IRQ_ERROR_WUP \ - 0x1CU /*!< R Error and Wake-up Interrupt Register */ -#define ST25R3916_REG_IRQ_TARGET 0x1DU /*!< R ST25R3916 Target Interrupt Register */ -#define ST25R3916_REG_FIFO_STATUS1 \ - 0x1EU /*!< R FIFO Status Register 1 */ -#define ST25R3916_REG_FIFO_STATUS2 \ - 0x1FU /*!< R FIFO Status Register 2 */ -#define ST25R3916_REG_COLLISION_STATUS \ - 0x20U /*!< R Collision Display Register */ -#define ST25R3916_REG_PASSIVE_TARGET_STATUS \ - 0x21U /*!< R Passive target state status */ - -/* Definition of number of transmitted bytes */ -#define ST25R3916_REG_NUM_TX_BYTES1 \ - 0x22U /*!< RW Number of Transmitted Bytes Register 1 */ -#define ST25R3916_REG_NUM_TX_BYTES2 \ - 0x23U /*!< RW Number of Transmitted Bytes Register 2 */ - -/* NFCIP Bit Rate Display Register */ -#define ST25R3916_REG_NFCIP1_BIT_RATE \ - 0x24U /*!< R NFCIP Bit Rate Detection Display Register */ - -/* A/D Converter Output Register */ -#define ST25R3916_REG_AD_RESULT 0x25U /*!< R A/D Converter Output Register */ - -/* Antenna tuning registers */ -#define ST25R3916_REG_ANT_TUNE_A 0x26U /*!< RW Antenna Tuning Control (AAT-A) Register 1 */ -#define ST25R3916_REG_ANT_TUNE_B 0x27U /*!< RW Antenna Tuning Control (AAT-B) Register 2 */ - -/* Antenna Driver and Modulation registers */ -#define ST25R3916_REG_TX_DRIVER 0x28U /*!< RW TX driver register */ -#define ST25R3916_REG_PT_MOD 0x29U /*!< RW PT modulation Register */ -#define ST25R3916_REG_AUX_MOD \ - (ST25R3916_SPACE_B | 0x28U) /*!< RW Aux Modulation setting Register */ + (ST25R3916_SPACE_B | 0x0DU) /** RW Correlator configuration register 2 */ + +/** Timer definition registers */ +#define ST25R3916_REG_MASK_RX_TIMER 0x0FU /** RW Mask Receive Timer Register */ +#define ST25R3916_REG_NO_RESPONSE_TIMER1 0x10U /** RW No-response Timer Register 1 */ +#define ST25R3916_REG_NO_RESPONSE_TIMER2 0x11U /** RW No-response Timer Register 2 */ +#define ST25R3916_REG_TIMER_EMV_CONTROL 0x12U /** RW Timer and EMV Control */ +#define ST25R3916_REG_GPT1 0x13U /** RW General Purpose Timer Register 1 */ +#define ST25R3916_REG_GPT2 0x14U /** RW General Purpose Timer Register 2 */ +#define ST25R3916_REG_PPON2 0x15U /** RW PPON2 Field waiting Timer Register */ +#define ST25R3916_REG_SQUELCH_TIMER (ST25R3916_SPACE_B | 0x0FU) /** RW Squelch timeout Register */ +#define ST25R3916_REG_FIELD_ON_GT (ST25R3916_SPACE_B | 0x15U) /** RW NFC Field on guard time */ + +/** Interrupt and associated reporting registers */ +#define ST25R3916_REG_IRQ_MASK_MAIN 0x16U /** RW Mask Main Interrupt Register */ +#define ST25R3916_REG_IRQ_MASK_TIMER_NFC 0x17U /** RW Mask Timer and NFC Interrupt Register */ +#define ST25R3916_REG_IRQ_MASK_ERROR_WUP 0x18U /** RW Mask Error and Wake-up Interrupt Register */ +#define ST25R3916_REG_IRQ_MASK_TARGET 0x19U /** RW Mask 3916 Target Interrupt Register */ +#define ST25R3916_REG_IRQ_MAIN 0x1AU /** R Main Interrupt Register */ +#define ST25R3916_REG_IRQ_TIMER_NFC 0x1BU /** R Timer and NFC Interrupt Register */ +#define ST25R3916_REG_IRQ_ERROR_WUP 0x1CU /** R Error and Wake-up Interrupt Register */ +#define ST25R3916_REG_IRQ_TARGET 0x1DU /*!< R ST25R3916 Target Interrupt Register */ +#define ST25R3916_REG_FIFO_STATUS1 0x1EU /** R FIFO Status Register 1 */ +#define ST25R3916_REG_FIFO_STATUS2 0x1FU /** R FIFO Status Register 2 */ +#define ST25R3916_REG_COLLISION_STATUS 0x20U /** R Collision Display Register */ +#define ST25R3916_REG_PASSIVE_TARGET_STATUS 0x21U /** R Passive target state status */ + +/** Definition of number of transmitted bytes */ +#define ST25R3916_REG_NUM_TX_BYTES1 0x22U /** RW Number of Transmitted Bytes Register 1 */ +#define ST25R3916_REG_NUM_TX_BYTES2 0x23U /** RW Number of Transmitted Bytes Register 2 */ + +/** NFCIP Bit Rate Display Register */ +#define ST25R3916_REG_NFCIP1_BIT_RATE 0x24U /** R NFCIP Bit Rate Detection Display Register */ + +/** A/D Converter Output Register */ +#define ST25R3916_REG_AD_RESULT 0x25U /** R A/D Converter Output Register */ + +/** Antenna tuning registers */ +#define ST25R3916_REG_ANT_TUNE_A 0x26U /** RW Antenna Tuning Control (AAT-A) Register 1 */ +#define ST25R3916_REG_ANT_TUNE_B 0x27U /** RW Antenna Tuning Control (AAT-B) Register 2 */ + +/** Antenna Driver and Modulation registers */ +#define ST25R3916_REG_TX_DRIVER 0x28U /** RW TX driver register */ +#define ST25R3916_REG_PT_MOD 0x29U /** RW PT modulation Register */ +#define ST25R3916_REG_AUX_MOD (ST25R3916_SPACE_B | 0x28U) /** RW Aux Modulation setting Register */ #define ST25R3916_REG_TX_DRIVER_TIMING \ - (ST25R3916_SPACE_B | 0x29U) /*!< RW TX driver timing Register */ + (ST25R3916_SPACE_B | 0x29U) /** RW TX driver timing Register */ #define ST25R3916_REG_RES_AM_MOD \ - (ST25R3916_SPACE_B | 0x2AU) /*!< RW Resistive AM modulation register */ + (ST25R3916_SPACE_B | 0x2AU) /** RW Resistive AM modulation register */ #define ST25R3916_REG_TX_DRIVER_STATUS \ - (ST25R3916_SPACE_B | 0x2BU) /*!< R TX driver timing readout Register */ + (ST25R3916_SPACE_B | 0x2BU) /** R TX driver timing readout Register */ -/* External Field Detector Threshold Registers */ +/** External Field Detector Threshold Registers */ #define ST25R3916_REG_FIELD_THRESHOLD_ACTV \ - 0x2AU /*!< RW External Field Detector Activation Threshold Reg */ + 0x2AU /** RW External Field Detector Activation Threshold Reg */ #define ST25R3916_REG_FIELD_THRESHOLD_DEACTV \ - 0x2BU /*!< RW External Field Detector Deactivation Threshold Reg*/ + 0x2BU /** RW External Field Detector Deactivation Threshold Reg */ -/* Regulator registers */ -#define ST25R3916_REG_REGULATOR_CONTROL \ - 0x2CU /*!< RW Regulated Voltage Control Register */ +/** Regulator registers */ +#define ST25R3916_REG_REGULATOR_CONTROL 0x2CU /** RW Regulated Voltage Control Register */ #define ST25R3916_REG_REGULATOR_RESULT \ - (ST25R3916_SPACE_B | 0x2CU) /*!< R Regulator Display Register */ - -/* Receiver State Display Register */ -#define ST25R3916_REG_RSSI_RESULT \ - 0x2DU /*!< R RSSI Display Register */ -#define ST25R3916_REG_GAIN_RED_STATE \ - 0x2EU /*!< R Gain Reduction State Register */ -#define ST25R3916_REG_CAP_SENSOR_CONTROL \ - 0x2FU /*!< RW Capacitive Sensor Control Register */ -#define ST25R3916_REG_CAP_SENSOR_RESULT \ - 0x30U /*!< R Capacitive Sensor Display Register */ -#define ST25R3916_REG_AUX_DISPLAY \ - 0x31U /*!< R Auxiliary Display Register */ - -/* Over/Undershoot Protection Configuration Registers */ + (ST25R3916_SPACE_B | 0x2CU) /** R Regulator Display Register */ + +/** Receiver State Display Register */ +#define ST25R3916_REG_RSSI_RESULT 0x2DU /** R RSSI Display Register */ +#define ST25R3916_REG_GAIN_RED_STATE 0x2EU /** R Gain Reduction State Register */ +#define ST25R3916_REG_CAP_SENSOR_CONTROL 0x2FU /** RW Capacitive Sensor Control Register */ +#define ST25R3916_REG_CAP_SENSOR_RESULT 0x30U /** R Capacitive Sensor Display Register */ +#define ST25R3916_REG_AUX_DISPLAY 0x31U /** R Auxiliary Display Register */ + +/** Over/Undershoot Protection Configuration Registers */ #define ST25R3916_REG_OVERSHOOT_CONF1 \ - (ST25R3916_SPACE_B | 0x30U) /*!< RW Overshoot Protection Configuration Register 1 */ + (ST25R3916_SPACE_B | 0x30U) /** RW Overshoot Protection Configuration Register 1 */ #define ST25R3916_REG_OVERSHOOT_CONF2 \ - (ST25R3916_SPACE_B | 0x31U) /*!< RW Overshoot Protection Configuration Register 2 */ + (ST25R3916_SPACE_B | 0x31U) /** RW Overshoot Protection Configuration Register 2 */ #define ST25R3916_REG_UNDERSHOOT_CONF1 \ - (ST25R3916_SPACE_B | 0x32U) /*!< RW Undershoot Protection Configuration Register 1 */ + (ST25R3916_SPACE_B | 0x32U) /** RW Undershoot Protection Configuration Register 1 */ #define ST25R3916_REG_UNDERSHOOT_CONF2 \ - (ST25R3916_SPACE_B | 0x33U) /*!< RW Undershoot Protection Configuration Register 2 */ + (ST25R3916_SPACE_B | 0x33U) /** RW Undershoot Protection Configuration Register 2 */ -/* Detection of card presence */ -#define ST25R3916_REG_WUP_TIMER_CONTROL \ - 0x32U /*!< RW Wake-up Timer Control Register */ +/** Detection of card presence */ +#define ST25R3916_REG_WUP_TIMER_CONTROL 0x32U /** RW Wake-up Timer Control Register */ #define ST25R3916_REG_AMPLITUDE_MEASURE_CONF \ - 0x33U /*!< RW Amplitude Measurement Configuration Register */ + 0x33U /** RW Amplitude Measurement Configuration Register */ #define ST25R3916_REG_AMPLITUDE_MEASURE_REF \ - 0x34U /*!< RW Amplitude Measurement Reference Register */ + 0x34U /** RW Amplitude Measurement Reference Register */ #define ST25R3916_REG_AMPLITUDE_MEASURE_AA_RESULT \ - 0x35U /*!< R Amplitude Measurement Auto Averaging Display Reg */ + 0x35U /** R Amplitude Measurement Auto Averaging Display Reg */ #define ST25R3916_REG_AMPLITUDE_MEASURE_RESULT \ - 0x36U /*!< R Amplitude Measurement Display Register */ -#define ST25R3916_REG_PHASE_MEASURE_CONF \ - 0x37U /*!< RW Phase Measurement Configuration Register */ -#define ST25R3916_REG_PHASE_MEASURE_REF \ - 0x38U /*!< RW Phase Measurement Reference Register */ + 0x36U /** R Amplitude Measurement Display Register */ +#define ST25R3916_REG_PHASE_MEASURE_CONF 0x37U /** RW Phase Measurement Configuration Register */ +#define ST25R3916_REG_PHASE_MEASURE_REF 0x38U /** RW Phase Measurement Reference Register */ #define ST25R3916_REG_PHASE_MEASURE_AA_RESULT \ - 0x39U /*!< R Phase Measurement Auto Averaging Display Register */ -#define ST25R3916_REG_PHASE_MEASURE_RESULT \ - 0x3AU /*!< R Phase Measurement Display Register */ + 0x39U /** R Phase Measurement Auto Averaging Display */ +#define ST25R3916_REG_PHASE_MEASURE_RESULT 0x3AU /** R Phase Measurement Display Register */ #define ST25R3916_REG_CAPACITANCE_MEASURE_CONF \ - 0x3BU /*!< RW Capacitance Measurement Configuration Register */ + 0x3BU /** RW Capacitance Measurement Configuration Register */ #define ST25R3916_REG_CAPACITANCE_MEASURE_REF \ - 0x3CU /*!< RW Capacitance Measurement Reference Register */ + 0x3CU /** RW Capacitance Measurement Reference Register */ #define ST25R3916_REG_CAPACITANCE_MEASURE_AA_RESULT \ - 0x3DU /*!< R Capacitance Measurement Auto Averaging Display Reg*/ + 0x3DU /** R Capacitance Measurement Auto Averaging Display Reg */ #define ST25R3916_REG_CAPACITANCE_MEASURE_RESULT \ - 0x3EU /*!< R Capacitance Measurement Display Register */ + 0x3EU /** R Capacitance Measurement Display Register */ -/* IC identity */ -#define ST25R3916_REG_IC_IDENTITY \ - 0x3FU /*!< R Chip Id: 0 for old silicon, v2 silicon: 0x09 */ +/** IC identity */ +#define ST25R3916_REG_IC_IDENTITY 0x3FU /** R Chip Id: 0 for old silicon, v2 silicon: 0x09 */ -/*! Register bit definitions \cond DOXYGEN_SUPRESS */ +/** Register bit definitions */ #define ST25R3916_REG_IO_CONF1_single (1U << 7) #define ST25R3916_REG_IO_CONF1_rfo2 (1U << 6) @@ -1021,364 +961,184 @@ #define ST25R3916_REG_IC_IDENTITY_ic_rev_mask (7U << 0) #define ST25R3916_REG_IC_IDENTITY_ic_rev_shift (0U) -/*! \endcond DOXYGEN_SUPRESS */ - -/* -****************************************************************************** -* GLOBAL FUNCTION PROTOTYPES -****************************************************************************** -*/ - -/*! - ***************************************************************************** - * \brief Returns the content of a register within the ST25R3916 - * - * This function is used to read out the content of ST25R3916 registers. +/** Read register * - * \param[in] reg: Address of register to read. - * \param[out] val: Returned value. - * - * \return ERR_NONE : Operation successful - * \return ERR_PARAM : Invalid parameter - * \return ERR_SEND : Transmission error or acknowledge not received - ***************************************************************************** + * @param handle - pointer t FuriHalSpiBusHandle instance + * @param reg - register address + * @param val - pointer to the variable to store the read value */ -ReturnCode st25r3916ReadRegister(uint8_t reg, uint8_t* val); +void st25r3916_read_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* val); -/*! - ***************************************************************************** - * \brief Reads from multiple ST25R3916 registers - * - * This function is used to read from multiple registers using the - * auto-increment feature. That is, after each read the address pointer - * inside the ST25R3916 gets incremented automatically. +/** Read multiple registers * - * \param[in] reg: Address of the first register to read from. - * \param[in] values: pointer to a buffer where the result shall be written to. - * \param[in] length: Number of registers to be read out. - * - * \return ERR_NONE : Operation successful - * \return ERR_PARAM : Invalid parameter - * \return ERR_SEND : Transmission error or acknowledge not received - ***************************************************************************** + * @param handle - pointer to FuriHalSpiBusHandle instance + * @param reg_start - start register address + * @param values - pointer to the buffer to store the read values + * @param length - number of registers to read */ -ReturnCode st25r3916ReadMultipleRegisters(uint8_t reg, uint8_t* values, uint8_t length); +void st25r3916_read_burst_regs( + FuriHalSpiBusHandle* handle, + uint8_t reg_start, + uint8_t* values, + uint8_t length); -/*! - ***************************************************************************** - * \brief Writes a given value to a register within the ST25R3916 - * - * This function is used to write \a val to address \a reg within the ST25R3916. +/** Write register * - * \param[in] reg: Address of the register to write. - * \param[in] val: Value to be written. - * - * \return ERR_NONE : Operation successful - * \return ERR_PARAM : Invalid parameter - * \return ERR_SEND : Transmission error or acknowledge not received - ***************************************************************************** + * @param handle - pointer to FuriHalSpiBusHandle instance + * @param reg - register address + * @param val - value to write */ -ReturnCode st25r3916WriteRegister(uint8_t reg, uint8_t val); +void st25r3916_write_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t val); -/*! - ***************************************************************************** - * \brief Writes multiple values to ST25R3916 registers - * - * This function is used to write multiple values to the ST25R3916 using the - * auto-increment feature. That is, after each write the address pointer - * inside the ST25R3916 gets incremented automatically. +/** Write multiple registers * - * \param[in] reg: Address of the first register to write. - * \param[in] values: pointer to a buffer containing the values to be written. - * \param[in] length: Number of values to be written. - * - * \return ERR_NONE : Operation successful - * \return ERR_PARAM : Invalid parameter - * \return ERR_SEND : Transmission error or acknowledge not received - ***************************************************************************** + * @param handle - pointer to FuriHalSpiBusHandle instance + * @param reg_start - start register address + * @param values - pointer to buffer to write + * @param length - number of registers to write */ -ReturnCode st25r3916WriteMultipleRegisters(uint8_t reg, const uint8_t* values, uint8_t length); +void st25r3916_write_burst_regs( + FuriHalSpiBusHandle* handle, + uint8_t reg_start, + const uint8_t* values, + uint8_t length); -/*! - ***************************************************************************** - * \brief Writes values to ST25R3916 FIFO - * - * This function needs to be called in order to write to the ST25R3916 FIFO. +/** Write fifo register * - * \param[in] values: pointer to a buffer containing the values to be written - * to the FIFO. - * \param[in] length: Number of values to be written. - * - * \return ERR_NONE : Operation successful - * \return ERR_PARAM : Invalid parameter - * \return ERR_SEND : Transmission error or acknowledge not received - ***************************************************************************** + * @param handle - pointer to FuriHalSpiBusHandle instance + * @param buff - buffer to write to FIFO + * @param length - number of bytes to write */ -ReturnCode st25r3916WriteFifo(const uint8_t* values, uint16_t length); +void st25r3916_reg_write_fifo(FuriHalSpiBusHandle* handle, const uint8_t* buff, size_t length); -/*! - ***************************************************************************** - * \brief Read values from ST25R3916 FIFO - * - * This function needs to be called in order to read from ST25R3916 FIFO. +/** Read fifo register * - * \param[out] buf: pointer to a buffer where the FIFO content shall be - * written to. - * \param[in] length: Number of bytes to read. - * - * \note: This function doesn't check whether \a length is really the - * number of available bytes in FIFO - * - * \return ERR_NONE : Operation successful - * \return ERR_PARAM : Invalid parameter - * \return ERR_SEND : Transmission error or acknowledge not received - ***************************************************************************** + * @param handle - pointer to FuriHalSpiBusHandle instance + * @param buff - buffer to store the read values + * @param length - number of bytes to read */ -ReturnCode st25r3916ReadFifo(uint8_t* buf, uint16_t length); +void st25r3916_reg_read_fifo(FuriHalSpiBusHandle* handle, uint8_t* buff, size_t length); -/*! - ***************************************************************************** - * \brief Writes values to ST25R3916 PTM - * - * Accesses to the begging of ST25R3916 Passive Target Memory (PTM A Config) - * and writes the given values +/** Write PTA memory register * - * \param[in] values: pointer to a buffer containing the values to be written - * to the Passive Target Memory. - * \param[in] length: Number of values to be written. - * - * \return ERR_NONE : Operation successful - * \return ERR_PARAM : Invalid parameter - * \return ERR_SEND : Transmission error or acknowledge not received - ***************************************************************************** + * @param handle - pointer to FuriHalSpiBusHandle instance + * @param values - pointer to buffer to write + * @param length - number of bytes to write */ -ReturnCode st25r3916WritePTMem(const uint8_t* values, uint16_t length); +void st25r3916_write_pta_mem(FuriHalSpiBusHandle* handle, const uint8_t* values, size_t length); -/*! - ***************************************************************************** - * \brief Reads the ST25R3916 PTM - * - * Accesses to the begging of ST25R3916 Passive Target Memory (PTM A Config) - * and reads the memory for the given length - * - * \param[out] values: pointer to a buffer where the PTM content shall be - * written to. - * \param[in] length: Number of bytes to read. +/** Read PTA memory register * - * \return ERR_NONE : Operation successful - * \return ERR_PARAM : Invalid parameter - * \return ERR_SEND : Transmission error or acknowledge not received - ***************************************************************************** + * @param handle - pointer to FuriHalSpiBusHandle instance + * @param values - buffer to store the read values + * @param length - number of bytes to read */ -ReturnCode st25r3916ReadPTMem(uint8_t* values, uint16_t length); +void st25r3916_read_pta_mem(FuriHalSpiBusHandle* handle, uint8_t* values, size_t length); -/*! - ***************************************************************************** - * \brief Writes values to ST25R3916 PTM F config +/** Write PTF memory register * - * Accesses ST25R3916 Passive Target Memory F config and writes the given values - * - * \param[in] values: pointer to a buffer containing the values to be written - * to the Passive Target Memory - * \param[in] length: Number of values to be written. - * - * \return ERR_NONE : Operation successful - * \return ERR_PARAM : Invalid parameter - * \return ERR_SEND : Transmission error or acknowledge not received - ***************************************************************************** + * @param handle - pointer to FuriHalSpiBusHandle instance + * @param values - pointer to buffer to write + * @param length - number of bytes to write */ -ReturnCode st25r3916WritePTMemF(const uint8_t* values, uint16_t length); +void st25r3916_write_ptf_mem(FuriHalSpiBusHandle* handle, const uint8_t* values, size_t length); -/*! - ***************************************************************************** - * \brief Writes values to ST25R3916 PTM TSN Data - * - * Accesses ST25R3916 Passive Target Memory TSN data and writes the given values - * - * \param[in] values: pointer to a buffer containing the values to be written - * to the Passive Target Memory. - * \param[in] length: Number of values to be written. +/** Read PTTSN memory register * - * \return ERR_NONE : Operation successful - * \return ERR_PARAM : Invalid parameter - * \return ERR_SEND : Transmission error or acknowledge not received - ***************************************************************************** + * @param handle - pointer to FuriHalSpiBusHandle instance + * @param values - pointer to buffer to write + * @param length - number of bytes to write */ -ReturnCode st25r3916WritePTMemTSN(const uint8_t* values, uint16_t length); +void st25r3916_write_pttsn_mem(FuriHalSpiBusHandle* handle, uint8_t* values, size_t length); -/*! - ***************************************************************************** - * \brief Execute a direct command +/** Send Direct command * - * This function is used to start so-called direct command. These commands - * are implemented inside the chip and each command has unique code (see - * datasheet). - * - * \param[in] cmd : code of the direct command to be executed. - * - * \return ERR_NONE : Operation successful - * \return ERR_PARAM : Invalid parameter - * \return ERR_SEND : Transmission error or acknowledge not received - ***************************************************************************** + * @param handle - pointer to FuriHalSpiBusHandle instance + * @param cmd - direct command */ -ReturnCode st25r3916ExecuteCommand(uint8_t cmd); +void st25r3916_direct_cmd(FuriHalSpiBusHandle* handle, uint8_t cmd); -/*! - ***************************************************************************** - * \brief Read a test register within the ST25R3916 - * - * This function is used to read the content of test address \a reg within the ST25R3916 - * - * \param[in] reg: Address of the register to read - * \param[out] val: Returned read value - * - * \return ERR_NONE : Operation successful - * \return ERR_PARAM : Invalid parameter - * \return ERR_SEND : Transmission error or acknowledge not received - ***************************************************************************** +/** Read test register + * @param handle - pointer to FuriHalSpiBusHandle instance + * @param reg - register address + * @param val - pointer to the variable to store the read value */ -ReturnCode st25r3916ReadTestRegister(uint8_t reg, uint8_t* val); +void st25r3916_read_test_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* val); -/*! - ***************************************************************************** - * \brief Writes a given value to a test register within the ST25R3916 - * - * This function is used to write \a val to test address \a reg within the ST25R3916 +/** Write test register * - * \param[in] reg: Address of the register to write - * \param[in] val: Value to be written - * - * \return ERR_NONE : Operation successful - * \return ERR_PARAM : Invalid parameter - * \return ERR_SEND : Transmission error or acknowledge not received - ***************************************************************************** + * @param handle - pointer to FuriHalSpiBusHandle instance + * @param reg - register address + * @param val - value to write */ -ReturnCode st25r3916WriteTestRegister(uint8_t reg, uint8_t val); +void st25r3916_write_test_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t val); -/*! - ***************************************************************************** - * \brief Cleart bits on Register - * - * This function clears the given bitmask on the register +/** Clear register bits * - * \param[in] reg: Address of the register clear - * \param[in] clr_mask: Bitmask of bit to be cleared - * - * \return ERR_NONE : Operation successful - * \return ERR_PARAM : Invalid parameter - * \return ERR_SEND : Transmission error or acknowledge not received - ***************************************************************************** + * @param handle - pointer to FuriHalSpiBusHandle instance + * @param reg - register address + * @param clr_mask - bit mask to clear */ -ReturnCode st25r3916ClrRegisterBits(uint8_t reg, uint8_t clr_mask); +void st25r3916_clear_reg_bits(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t clr_mask); -/*! - ***************************************************************************** - * \brief Set bits on Register - * - * This function sets the given bitmask on the register - * - * \param[in] reg: Address of the register clear - * \param[in] set_mask: Bitmask of bit to be cleared +/** Set register bits * - * \return ERR_NONE : Operation successful - * \return ERR_PARAM : Invalid parameter - * \return ERR_SEND : Transmission error or acknowledge not received - ***************************************************************************** + * @param handle - pointer to FuriHalSpiBusHandle instance + * @param reg - register address + * @param set_mask - bit mask to set */ -ReturnCode st25r3916SetRegisterBits(uint8_t reg, uint8_t set_mask); +void st25r3916_set_reg_bits(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t set_mask); -/*! - ***************************************************************************** - * \brief Changes the given bits on a ST25R3916 register +/** Change register bits * - * This function is used if only a particular bits should be changed within - * an ST25R3916 register. - * - * \param[in] reg: Address of the register to change. - * \param[in] valueMask: bitmask of bits to be changed - * \param[in] value: the bits to be written on the enabled valueMask bits - * - * \return ERR_NONE : Operation successful - * \return ERR_PARAM : Invalid parameter - * \return ERR_SEND : Transmission error or acknowledge not received - ***************************************************************************** + * @param handle - pointer to FuriHalSpiBusHandle instance + * @param reg - register address + * @param mask - bit mask to change + * @param value - new register value to write */ -ReturnCode st25r3916ChangeRegisterBits(uint8_t reg, uint8_t valueMask, uint8_t value); - -/*! - ***************************************************************************** - * \brief Modifies a value within a ST25R3916 register - * - * This function is used if only a particular bits should be changed within - * an ST25R3916 register. - * - * \param[in] reg: Address of the register to write. - * \param[in] clr_mask: bitmask of bits to be cleared to 0. - * \param[in] set_mask: bitmask of bits to be set to 1. - * - * \return ERR_NONE : Operation successful - * \return ERR_PARAM : Invalid parameter - * \return ERR_SEND : Transmission error or acknowledge not received - ***************************************************************************** +void st25r3916_change_reg_bits( + FuriHalSpiBusHandle* handle, + uint8_t reg, + uint8_t mask, + uint8_t value); + +/** Modify register + * + * @param handle - pointer to FuriHalSpiBusHandle instance + * @param reg - register address + * @param clr_mask - bit mask to clear + * @param set_mask - bit mask to set */ -ReturnCode st25r3916ModifyRegister(uint8_t reg, uint8_t clr_mask, uint8_t set_mask); - -/*! - ***************************************************************************** - * \brief Changes the given bits on a ST25R3916 Test register - * - * This function is used if only a particular bits should be changed within - * an ST25R3916 register. - * - * \param[in] reg: Address of the Test register to change. - * \param[in] valueMask: bitmask of bits to be changed - * \param[in] value: the bits to be written on the enabled valueMask bits - * - * \return ERR_NONE : Operation successful - * \return ERR_PARAM : Invalid parameter - * \return ERR_SEND : Transmission error or acknowledge not received - ***************************************************************************** +void st25r3916_modify_reg( + FuriHalSpiBusHandle* handle, + uint8_t reg, + uint8_t clr_mask, + uint8_t set_mask); + +/** Change test register bits + * + * @param handle - pointer to FuriHalSpiBusHandle instance + * @param reg - register address + * @param mask - bit mask to change + * @param value - new register value to write */ -ReturnCode st25r3916ChangeTestRegisterBits(uint8_t reg, uint8_t valueMask, uint8_t value); +void st25r3916_change_test_reg_bits( + FuriHalSpiBusHandle* handle, + uint8_t reg, + uint8_t mask, + uint8_t value); -/*! - ***************************************************************************** - * \brief Checks if register contains a expected value +/** Check register * - * This function checks if the given reg contains a value that once masked - * equals the expected value + * @param handle - pointer to FuriHalSpiBusHandle instance + * @param reg - register address + * @param mask - bit mask to check + * @param val - expected register value * - * \param reg : the register to check the value - * \param mask : the mask apply on register value - * \param val : expected value to be compared to - * - * \return true when reg contains the expected value | false otherwise + * @return true if register value matches the expected value, false otherwise */ -bool st25r3916CheckReg(uint8_t reg, uint8_t mask, uint8_t val); +bool st25r3916_check_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t mask, uint8_t val); -/*! - ***************************************************************************** - * \brief Check if register ID is valid - * - * Checks if the given register ID a valid ST25R3916 register - * - * \param[in] reg: Address of register to check - * - * \return true if is a valid register ID - * \return false otherwise - * - ***************************************************************************** - */ -bool st25r3916IsRegValid(uint8_t reg); - -#endif /* ST25R3916_COM_H */ - -/** - * @} - * - * @} - * - * @} - * - * @} - */ +#ifdef __cplusplus +} +#endif diff --git a/lib/misc.scons b/lib/misc.scons index 10aa3f9d5a9..92fa5a106ff 100644 --- a/lib/misc.scons +++ b/lib/misc.scons @@ -15,6 +15,9 @@ env.Append( ], SDK_HEADERS=[ File("micro-ecc/uECC.h"), + File("nanopb/pb.h"), + File("nanopb/pb_decode.h"), + File("nanopb/pb_encode.h"), ], ) diff --git a/lib/nfc/SConscript b/lib/nfc/SConscript index 7a0859ee462..0e09b693923 100644 --- a/lib/nfc/SConscript +++ b/lib/nfc/SConscript @@ -5,20 +5,53 @@ env.Append( "#/lib/nfc", ], SDK_HEADERS=[ + # Main + File("nfc.h"), File("nfc_device.h"), - File("nfc_worker.h"), - File("nfc_types.h"), - File("helpers/mfkey32.h"), - File("parsers/nfc_supported_card.h"), - File("helpers/nfc_generators.h"), - File("protocols/nfc_util.h"), + File("nfc_listener.h"), + File("nfc_poller.h"), + File("nfc_scanner.h"), + # Protocols + File("protocols/iso14443_3a/iso14443_3a.h"), + File("protocols/iso14443_3b/iso14443_3b.h"), + File("protocols/iso14443_4a/iso14443_4a.h"), + File("protocols/iso14443_4b/iso14443_4b.h"), + File("protocols/mf_ultralight/mf_ultralight.h"), + File("protocols/mf_classic/mf_classic.h"), + File("protocols/mf_desfire/mf_desfire.h"), + File("protocols/slix/slix.h"), + File("protocols/st25tb/st25tb.h"), + # Pollers + File("protocols/iso14443_3a/iso14443_3a_poller.h"), + File("protocols/iso14443_3b/iso14443_3b_poller.h"), + File("protocols/iso14443_4a/iso14443_4a_poller.h"), + File("protocols/iso14443_4b/iso14443_4b_poller.h"), + File("protocols/mf_ultralight/mf_ultralight_poller.h"), + File("protocols/mf_classic/mf_classic_poller.h"), + File("protocols/mf_desfire/mf_desfire_poller.h"), + File("protocols/st25tb/st25tb_poller.h"), + # Listeners + File("protocols/iso14443_3a/iso14443_3a_listener.h"), + File("protocols/iso14443_4a/iso14443_4a_listener.h"), + File("protocols/mf_ultralight/mf_ultralight_listener.h"), + File("protocols/mf_classic/mf_classic_listener.h"), + # Sync API + File("protocols/iso14443_3a/iso14443_3a_poller_sync_api.h"), + File("protocols/mf_ultralight/mf_ultralight_poller_sync_api.h"), + File("protocols/mf_classic/mf_classic_poller_sync_api.h"), + # Misc + File("helpers/nfc_util.h"), + File("helpers/iso14443_crc.h"), + File("helpers/iso13239_crc.h"), + File("helpers/nfc_data_generator.h"), + File("helpers/nfc_dict.h"), ], ) libenv = env.Clone(FW_LIB_NAME="nfc") libenv.ApplyLibFlags() -sources = libenv.GlobRecursive("*.c*") +sources = libenv.GlobRecursive("*.c*", exclude="deprecated/*c") lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources) libenv.Install("${LIB_DIST_DIR}", lib) diff --git a/lib/nfc/helpers/felica_crc.c b/lib/nfc/helpers/felica_crc.c new file mode 100644 index 00000000000..d5e0853a109 --- /dev/null +++ b/lib/nfc/helpers/felica_crc.c @@ -0,0 +1,52 @@ +#include "felica_crc.h" + +#include + +#define FELICA_CRC_POLY (0x1021U) // Polynomial: x^16 + x^12 + x^5 + 1 +#define FELICA_CRC_INIT (0x0000U) + +uint16_t felica_crc_calculate(const uint8_t* data, size_t length) { + uint16_t crc = FELICA_CRC_INIT; + + for(size_t i = 0; i < length; i++) { + crc ^= ((uint16_t)data[i] << 8); + for(size_t j = 0; j < 8; j++) { + if(crc & 0x8000) { + crc <<= 1; + crc ^= FELICA_CRC_POLY; + } else { + crc <<= 1; + } + } + } + + return (crc << 8) | (crc >> 8); +} + +void felica_crc_append(BitBuffer* buf) { + const uint8_t* data = bit_buffer_get_data(buf); + const size_t data_size = bit_buffer_get_size_bytes(buf); + + const uint16_t crc = felica_crc_calculate(data, data_size); + bit_buffer_append_bytes(buf, (const uint8_t*)&crc, FELICA_CRC_SIZE); +} + +bool felica_crc_check(const BitBuffer* buf) { + const size_t data_size = bit_buffer_get_size_bytes(buf); + if(data_size <= FELICA_CRC_SIZE) return false; + + uint16_t crc_received; + bit_buffer_write_bytes_mid(buf, &crc_received, data_size - FELICA_CRC_SIZE, FELICA_CRC_SIZE); + + const uint8_t* data = bit_buffer_get_data(buf); + const uint16_t crc_calc = felica_crc_calculate(data, data_size - FELICA_CRC_SIZE); + + return (crc_calc == crc_received); +} + +void felica_crc_trim(BitBuffer* buf) { + const size_t data_size = bit_buffer_get_size_bytes(buf); + furi_assert(data_size > FELICA_CRC_SIZE); + + bit_buffer_set_size_bytes(buf, data_size - FELICA_CRC_SIZE); +} diff --git a/lib/nfc/helpers/felica_crc.h b/lib/nfc/helpers/felica_crc.h new file mode 100644 index 00000000000..d1dc29e74db --- /dev/null +++ b/lib/nfc/helpers/felica_crc.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include + +#include "bit_buffer.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define FELICA_CRC_SIZE sizeof(uint16_t) + +void felica_crc_append(BitBuffer* buf); + +bool felica_crc_check(const BitBuffer* buf); + +void felica_crc_trim(BitBuffer* buf); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/helpers/iso13239_crc.c b/lib/nfc/helpers/iso13239_crc.c new file mode 100644 index 00000000000..c54fbcfb21c --- /dev/null +++ b/lib/nfc/helpers/iso13239_crc.c @@ -0,0 +1,62 @@ +#include "iso13239_crc.h" + +#include + +#define ISO13239_CRC_INIT_DEFAULT (0xFFFFU) +#define ISO13239_CRC_INIT_PICOPASS (0xE012U) +#define ISO13239_CRC_POLY (0x8408U) + +static uint16_t + iso13239_crc_calculate(Iso13239CrcType type, const uint8_t* data, size_t data_size) { + uint16_t crc; + + if(type == Iso13239CrcTypeDefault) { + crc = ISO13239_CRC_INIT_DEFAULT; + } else if(type == Iso13239CrcTypePicopass) { + crc = ISO13239_CRC_INIT_PICOPASS; + } else { + furi_crash("Wrong ISO13239 CRC type"); + } + + for(size_t i = 0; i < data_size; ++i) { + crc ^= (uint16_t)data[i]; + for(size_t j = 0; j < 8; ++j) { + if(crc & 1U) { + crc = (crc >> 1) ^ ISO13239_CRC_POLY; + } else { + crc >>= 1; + } + } + } + + return type == Iso13239CrcTypePicopass ? crc : ~crc; +} + +void iso13239_crc_append(Iso13239CrcType type, BitBuffer* buf) { + const uint8_t* data = bit_buffer_get_data(buf); + const size_t data_size = bit_buffer_get_size_bytes(buf); + + const uint16_t crc = iso13239_crc_calculate(type, data, data_size); + bit_buffer_append_bytes(buf, (const uint8_t*)&crc, ISO13239_CRC_SIZE); +} + +bool iso13239_crc_check(Iso13239CrcType type, const BitBuffer* buf) { + const size_t data_size = bit_buffer_get_size_bytes(buf); + if(data_size <= ISO13239_CRC_SIZE) return false; + + uint16_t crc_received; + bit_buffer_write_bytes_mid( + buf, &crc_received, data_size - ISO13239_CRC_SIZE, ISO13239_CRC_SIZE); + + const uint8_t* data = bit_buffer_get_data(buf); + const uint16_t crc_calc = iso13239_crc_calculate(type, data, data_size - ISO13239_CRC_SIZE); + + return (crc_calc == crc_received); +} + +void iso13239_crc_trim(BitBuffer* buf) { + const size_t data_size = bit_buffer_get_size_bytes(buf); + furi_assert(data_size > ISO13239_CRC_SIZE); + + bit_buffer_set_size_bytes(buf, data_size - ISO13239_CRC_SIZE); +} diff --git a/lib/nfc/helpers/iso13239_crc.h b/lib/nfc/helpers/iso13239_crc.h new file mode 100644 index 00000000000..c71ec6befe4 --- /dev/null +++ b/lib/nfc/helpers/iso13239_crc.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define ISO13239_CRC_SIZE sizeof(uint16_t) + +typedef enum { + Iso13239CrcTypeDefault, + Iso13239CrcTypePicopass, +} Iso13239CrcType; + +void iso13239_crc_append(Iso13239CrcType type, BitBuffer* buf); + +bool iso13239_crc_check(Iso13239CrcType type, const BitBuffer* buf); + +void iso13239_crc_trim(BitBuffer* buf); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/helpers/iso14443_4_layer.c b/lib/nfc/helpers/iso14443_4_layer.c new file mode 100644 index 00000000000..26f4dc3b7b4 --- /dev/null +++ b/lib/nfc/helpers/iso14443_4_layer.c @@ -0,0 +1,64 @@ +#include "iso14443_4_layer.h" + +#include + +#define ISO14443_4_BLOCK_PCB (1U << 1) +#define ISO14443_4_BLOCK_PCB_I (0U) +#define ISO14443_4_BLOCK_PCB_R (5U << 5) +#define ISO14443_4_BLOCK_PCB_S (3U << 6) + +struct Iso14443_4Layer { + uint8_t pcb; + uint8_t pcb_prev; +}; + +static inline void iso14443_4_layer_update_pcb(Iso14443_4Layer* instance) { + instance->pcb_prev = instance->pcb; + instance->pcb ^= (uint8_t)0x01; +} + +Iso14443_4Layer* iso14443_4_layer_alloc() { + Iso14443_4Layer* instance = malloc(sizeof(Iso14443_4Layer)); + + iso14443_4_layer_reset(instance); + return instance; +} + +void iso14443_4_layer_free(Iso14443_4Layer* instance) { + furi_assert(instance); + free(instance); +} + +void iso14443_4_layer_reset(Iso14443_4Layer* instance) { + furi_assert(instance); + instance->pcb = ISO14443_4_BLOCK_PCB_I | ISO14443_4_BLOCK_PCB; +} + +void iso14443_4_layer_encode_block( + Iso14443_4Layer* instance, + const BitBuffer* input_data, + BitBuffer* block_data) { + furi_assert(instance); + + bit_buffer_append_byte(block_data, instance->pcb); + bit_buffer_append(block_data, input_data); + + iso14443_4_layer_update_pcb(instance); +} + +bool iso14443_4_layer_decode_block( + Iso14443_4Layer* instance, + BitBuffer* output_data, + const BitBuffer* block_data) { + furi_assert(instance); + + bool ret = false; + + do { + if(!bit_buffer_starts_with_byte(block_data, instance->pcb_prev)) break; + bit_buffer_copy_right(output_data, block_data, 1); + ret = true; + } while(false); + + return ret; +} diff --git a/lib/nfc/helpers/iso14443_4_layer.h b/lib/nfc/helpers/iso14443_4_layer.h new file mode 100644 index 00000000000..712173ce1b1 --- /dev/null +++ b/lib/nfc/helpers/iso14443_4_layer.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct Iso14443_4Layer Iso14443_4Layer; + +Iso14443_4Layer* iso14443_4_layer_alloc(); + +void iso14443_4_layer_free(Iso14443_4Layer* instance); + +void iso14443_4_layer_reset(Iso14443_4Layer* instance); + +void iso14443_4_layer_encode_block( + Iso14443_4Layer* instance, + const BitBuffer* input_data, + BitBuffer* block_data); + +bool iso14443_4_layer_decode_block( + Iso14443_4Layer* instance, + BitBuffer* output_data, + const BitBuffer* block_data); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/helpers/iso14443_crc.c b/lib/nfc/helpers/iso14443_crc.c new file mode 100644 index 00000000000..fda9871aa99 --- /dev/null +++ b/lib/nfc/helpers/iso14443_crc.c @@ -0,0 +1,57 @@ +#include "iso14443_crc.h" + +#include + +#define ISO14443_3A_CRC_INIT (0x6363U) +#define ISO14443_3B_CRC_INIT (0xFFFFU) + +static uint16_t + iso14443_crc_calculate(Iso14443CrcType type, const uint8_t* data, size_t data_size) { + uint16_t crc; + + if(type == Iso14443CrcTypeA) { + crc = ISO14443_3A_CRC_INIT; + } else if(type == Iso14443CrcTypeB) { + crc = ISO14443_3B_CRC_INIT; + } else { + furi_crash("Wrong ISO14443 CRC type"); + } + + for(size_t i = 0; i < data_size; i++) { + uint8_t byte = data[i]; + byte ^= (uint8_t)(crc & 0xff); + byte ^= byte << 4; + crc = (crc >> 8) ^ (((uint16_t)byte) << 8) ^ (((uint16_t)byte) << 3) ^ (byte >> 4); + } + + return type == Iso14443CrcTypeA ? crc : ~crc; +} + +void iso14443_crc_append(Iso14443CrcType type, BitBuffer* buf) { + const uint8_t* data = bit_buffer_get_data(buf); + const size_t data_size = bit_buffer_get_size_bytes(buf); + + const uint16_t crc = iso14443_crc_calculate(type, data, data_size); + bit_buffer_append_bytes(buf, (const uint8_t*)&crc, ISO14443_CRC_SIZE); +} + +bool iso14443_crc_check(Iso14443CrcType type, const BitBuffer* buf) { + const size_t data_size = bit_buffer_get_size_bytes(buf); + if(data_size <= ISO14443_CRC_SIZE) return false; + + uint16_t crc_received; + bit_buffer_write_bytes_mid( + buf, &crc_received, data_size - ISO14443_CRC_SIZE, ISO14443_CRC_SIZE); + + const uint8_t* data = bit_buffer_get_data(buf); + const uint16_t crc_calc = iso14443_crc_calculate(type, data, data_size - ISO14443_CRC_SIZE); + + return (crc_calc == crc_received); +} + +void iso14443_crc_trim(BitBuffer* buf) { + const size_t data_size = bit_buffer_get_size_bytes(buf); + furi_assert(data_size > ISO14443_CRC_SIZE); + + bit_buffer_set_size_bytes(buf, data_size - ISO14443_CRC_SIZE); +} diff --git a/lib/nfc/helpers/iso14443_crc.h b/lib/nfc/helpers/iso14443_crc.h new file mode 100644 index 00000000000..14a63841e71 --- /dev/null +++ b/lib/nfc/helpers/iso14443_crc.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define ISO14443_CRC_SIZE sizeof(uint16_t) + +typedef enum { + Iso14443CrcTypeA, + Iso14443CrcTypeB, +} Iso14443CrcType; + +void iso14443_crc_append(Iso14443CrcType type, BitBuffer* buf); + +bool iso14443_crc_check(Iso14443CrcType type, const BitBuffer* buf); + +void iso14443_crc_trim(BitBuffer* buf); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/helpers/mf_classic_dict.c b/lib/nfc/helpers/mf_classic_dict.c deleted file mode 100644 index 93098d409bc..00000000000 --- a/lib/nfc/helpers/mf_classic_dict.c +++ /dev/null @@ -1,346 +0,0 @@ -#include "mf_classic_dict.h" - -#include -#include - -#define MF_CLASSIC_DICT_FLIPPER_PATH EXT_PATH("nfc/assets/mf_classic_dict.nfc") -#define MF_CLASSIC_DICT_USER_PATH EXT_PATH("nfc/assets/mf_classic_dict_user.nfc") -#define MF_CLASSIC_DICT_UNIT_TEST_PATH EXT_PATH("unit_tests/mf_classic_dict.nfc") - -#define TAG "MfClassicDict" - -#define NFC_MF_CLASSIC_KEY_LEN (13) - -struct MfClassicDict { - Stream* stream; - uint32_t total_keys; -}; - -bool mf_classic_dict_check_presence(MfClassicDictType dict_type) { - Storage* storage = furi_record_open(RECORD_STORAGE); - - bool dict_present = false; - if(dict_type == MfClassicDictTypeSystem) { - dict_present = storage_common_stat(storage, MF_CLASSIC_DICT_FLIPPER_PATH, NULL) == FSE_OK; - } else if(dict_type == MfClassicDictTypeUser) { - dict_present = storage_common_stat(storage, MF_CLASSIC_DICT_USER_PATH, NULL) == FSE_OK; - } else if(dict_type == MfClassicDictTypeUnitTest) { - dict_present = storage_common_stat(storage, MF_CLASSIC_DICT_UNIT_TEST_PATH, NULL) == - FSE_OK; - } - - furi_record_close(RECORD_STORAGE); - - return dict_present; -} - -MfClassicDict* mf_classic_dict_alloc(MfClassicDictType dict_type) { - MfClassicDict* dict = malloc(sizeof(MfClassicDict)); - Storage* storage = furi_record_open(RECORD_STORAGE); - dict->stream = buffered_file_stream_alloc(storage); - furi_record_close(RECORD_STORAGE); - - bool dict_loaded = false; - do { - if(dict_type == MfClassicDictTypeSystem) { - if(!buffered_file_stream_open( - dict->stream, - MF_CLASSIC_DICT_FLIPPER_PATH, - FSAM_READ_WRITE, - FSOM_OPEN_EXISTING)) { - buffered_file_stream_close(dict->stream); - break; - } - } else if(dict_type == MfClassicDictTypeUser) { - if(!buffered_file_stream_open( - dict->stream, MF_CLASSIC_DICT_USER_PATH, FSAM_READ_WRITE, FSOM_OPEN_ALWAYS)) { - buffered_file_stream_close(dict->stream); - break; - } - } else if(dict_type == MfClassicDictTypeUnitTest) { - if(!buffered_file_stream_open( - dict->stream, - MF_CLASSIC_DICT_UNIT_TEST_PATH, - FSAM_READ_WRITE, - FSOM_OPEN_ALWAYS)) { - buffered_file_stream_close(dict->stream); - break; - } - } - - // Check for new line ending - if(!stream_eof(dict->stream)) { - if(!stream_seek(dict->stream, -1, StreamOffsetFromEnd)) break; - uint8_t last_char = 0; - if(stream_read(dict->stream, &last_char, 1) != 1) break; - if(last_char != '\n') { - FURI_LOG_D(TAG, "Adding new line ending"); - if(stream_write_char(dict->stream, '\n') != 1) break; - } - if(!stream_rewind(dict->stream)) break; - } - - // Read total amount of keys - FuriString* next_line; - next_line = furi_string_alloc(); - while(true) { - if(!stream_read_line(dict->stream, next_line)) { - FURI_LOG_T(TAG, "No keys left in dict"); - break; - } - FURI_LOG_T( - TAG, - "Read line: %s, len: %zu", - furi_string_get_cstr(next_line), - furi_string_size(next_line)); - if(furi_string_get_char(next_line, 0) == '#') continue; - if(furi_string_size(next_line) != NFC_MF_CLASSIC_KEY_LEN) continue; - dict->total_keys++; - } - furi_string_free(next_line); - stream_rewind(dict->stream); - - dict_loaded = true; - FURI_LOG_I(TAG, "Loaded dictionary with %lu keys", dict->total_keys); - } while(false); - - if(!dict_loaded) { - buffered_file_stream_close(dict->stream); - free(dict); - dict = NULL; - } - - return dict; -} - -void mf_classic_dict_free(MfClassicDict* dict) { - furi_assert(dict); - furi_assert(dict->stream); - - buffered_file_stream_close(dict->stream); - stream_free(dict->stream); - free(dict); -} - -static void mf_classic_dict_int_to_str(uint8_t* key_int, FuriString* key_str) { - furi_string_reset(key_str); - for(size_t i = 0; i < 6; i++) { - furi_string_cat_printf(key_str, "%02X", key_int[i]); - } -} - -static void mf_classic_dict_str_to_int(FuriString* key_str, uint64_t* key_int) { - uint8_t key_byte_tmp; - - *key_int = 0ULL; - for(uint8_t i = 0; i < 12; i += 2) { - args_char_to_hex( - furi_string_get_char(key_str, i), furi_string_get_char(key_str, i + 1), &key_byte_tmp); - *key_int |= (uint64_t)key_byte_tmp << (8 * (5 - i / 2)); - } -} - -uint32_t mf_classic_dict_get_total_keys(MfClassicDict* dict) { - furi_assert(dict); - - return dict->total_keys; -} - -bool mf_classic_dict_rewind(MfClassicDict* dict) { - furi_assert(dict); - furi_assert(dict->stream); - - return stream_rewind(dict->stream); -} - -bool mf_classic_dict_get_next_key_str(MfClassicDict* dict, FuriString* key) { - furi_assert(dict); - furi_assert(dict->stream); - - bool key_read = false; - furi_string_reset(key); - while(!key_read) { - if(!stream_read_line(dict->stream, key)) break; - if(furi_string_get_char(key, 0) == '#') continue; - if(furi_string_size(key) != NFC_MF_CLASSIC_KEY_LEN) continue; - furi_string_left(key, 12); - key_read = true; - } - - return key_read; -} - -bool mf_classic_dict_get_next_key(MfClassicDict* dict, uint64_t* key) { - furi_assert(dict); - furi_assert(dict->stream); - - FuriString* temp_key; - temp_key = furi_string_alloc(); - bool key_read = mf_classic_dict_get_next_key_str(dict, temp_key); - if(key_read) { - mf_classic_dict_str_to_int(temp_key, key); - } - furi_string_free(temp_key); - return key_read; -} - -bool mf_classic_dict_is_key_present_str(MfClassicDict* dict, FuriString* key) { - furi_assert(dict); - furi_assert(dict->stream); - - FuriString* next_line; - next_line = furi_string_alloc(); - - bool key_found = false; - stream_rewind(dict->stream); - while(!key_found) { //-V654 - if(!stream_read_line(dict->stream, next_line)) break; - if(furi_string_get_char(next_line, 0) == '#') continue; - if(furi_string_size(next_line) != NFC_MF_CLASSIC_KEY_LEN) continue; - furi_string_left(next_line, 12); - if(!furi_string_equal(key, next_line)) continue; - key_found = true; - } - - furi_string_free(next_line); - return key_found; -} - -bool mf_classic_dict_is_key_present(MfClassicDict* dict, uint8_t* key) { - FuriString* temp_key; - - temp_key = furi_string_alloc(); - mf_classic_dict_int_to_str(key, temp_key); - bool key_found = mf_classic_dict_is_key_present_str(dict, temp_key); - furi_string_free(temp_key); - return key_found; -} - -bool mf_classic_dict_add_key_str(MfClassicDict* dict, FuriString* key) { - furi_assert(dict); - furi_assert(dict->stream); - - furi_string_cat_printf(key, "\n"); - - bool key_added = false; - do { - if(!stream_seek(dict->stream, 0, StreamOffsetFromEnd)) break; - if(!stream_insert_string(dict->stream, key)) break; - dict->total_keys++; - key_added = true; - } while(false); - - furi_string_left(key, 12); - return key_added; -} - -bool mf_classic_dict_add_key(MfClassicDict* dict, uint8_t* key) { - furi_assert(dict); - furi_assert(dict->stream); - - FuriString* temp_key; - temp_key = furi_string_alloc(); - mf_classic_dict_int_to_str(key, temp_key); - bool key_added = mf_classic_dict_add_key_str(dict, temp_key); - - furi_string_free(temp_key); - return key_added; -} - -bool mf_classic_dict_get_key_at_index_str(MfClassicDict* dict, FuriString* key, uint32_t target) { - furi_assert(dict); - furi_assert(dict->stream); - - FuriString* next_line; - uint32_t index = 0; - next_line = furi_string_alloc(); - furi_string_reset(key); - - bool key_found = false; - while(!key_found) { - if(!stream_read_line(dict->stream, next_line)) break; - if(furi_string_get_char(next_line, 0) == '#') continue; - if(furi_string_size(next_line) != NFC_MF_CLASSIC_KEY_LEN) continue; - if(index++ != target) continue; - furi_string_set_n(key, next_line, 0, 12); - key_found = true; - } - - furi_string_free(next_line); - return key_found; -} - -bool mf_classic_dict_get_key_at_index(MfClassicDict* dict, uint64_t* key, uint32_t target) { - furi_assert(dict); - furi_assert(dict->stream); - - FuriString* temp_key; - temp_key = furi_string_alloc(); - bool key_found = mf_classic_dict_get_key_at_index_str(dict, temp_key, target); - if(key_found) { - mf_classic_dict_str_to_int(temp_key, key); - } - furi_string_free(temp_key); - return key_found; -} - -bool mf_classic_dict_find_index_str(MfClassicDict* dict, FuriString* key, uint32_t* target) { - furi_assert(dict); - furi_assert(dict->stream); - - FuriString* next_line; - next_line = furi_string_alloc(); - - bool key_found = false; - uint32_t index = 0; - stream_rewind(dict->stream); - while(!key_found) { //-V654 - if(!stream_read_line(dict->stream, next_line)) break; - if(furi_string_get_char(next_line, 0) == '#') continue; - if(furi_string_size(next_line) != NFC_MF_CLASSIC_KEY_LEN) continue; - furi_string_left(next_line, 12); - if(!furi_string_equal(key, next_line)) continue; - key_found = true; - *target = index; - } - - furi_string_free(next_line); - return key_found; -} - -bool mf_classic_dict_find_index(MfClassicDict* dict, uint8_t* key, uint32_t* target) { - furi_assert(dict); - furi_assert(dict->stream); - - FuriString* temp_key; - temp_key = furi_string_alloc(); - mf_classic_dict_int_to_str(key, temp_key); - bool key_found = mf_classic_dict_find_index_str(dict, temp_key, target); - - furi_string_free(temp_key); - return key_found; -} - -bool mf_classic_dict_delete_index(MfClassicDict* dict, uint32_t target) { - furi_assert(dict); - furi_assert(dict->stream); - - FuriString* next_line; - next_line = furi_string_alloc(); - uint32_t index = 0; - - bool key_removed = false; - while(!key_removed) { - if(!stream_read_line(dict->stream, next_line)) break; - if(furi_string_get_char(next_line, 0) == '#') continue; - if(furi_string_size(next_line) != NFC_MF_CLASSIC_KEY_LEN) continue; - if(index++ != target) continue; - stream_seek(dict->stream, -NFC_MF_CLASSIC_KEY_LEN, StreamOffsetFromCurrent); - if(!stream_delete(dict->stream, NFC_MF_CLASSIC_KEY_LEN)) break; - dict->total_keys--; - key_removed = true; - } - - furi_string_free(next_line); - return key_removed; -} diff --git a/lib/nfc/helpers/mf_classic_dict.h b/lib/nfc/helpers/mf_classic_dict.h deleted file mode 100644 index b798b1c92e9..00000000000 --- a/lib/nfc/helpers/mf_classic_dict.h +++ /dev/null @@ -1,107 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -typedef enum { - MfClassicDictTypeUser, - MfClassicDictTypeSystem, - MfClassicDictTypeUnitTest, -} MfClassicDictType; - -typedef struct MfClassicDict MfClassicDict; - -bool mf_classic_dict_check_presence(MfClassicDictType dict_type); - -/** Allocate MfClassicDict instance - * - * @param[in] dict_type The dictionary type - * - * @return MfClassicDict instance - */ -MfClassicDict* mf_classic_dict_alloc(MfClassicDictType dict_type); - -/** Free MfClassicDict instance - * - * @param dict MfClassicDict instance - */ -void mf_classic_dict_free(MfClassicDict* dict); - -/** Get total keys count - * - * @param dict MfClassicDict instance - * - * @return total keys count - */ -uint32_t mf_classic_dict_get_total_keys(MfClassicDict* dict); - -/** Rewind to the beginning - * - * @param dict MfClassicDict instance - * - * @return true on success - */ -bool mf_classic_dict_rewind(MfClassicDict* dict); - -bool mf_classic_dict_is_key_present(MfClassicDict* dict, uint8_t* key); - -bool mf_classic_dict_is_key_present_str(MfClassicDict* dict, FuriString* key); - -bool mf_classic_dict_get_next_key(MfClassicDict* dict, uint64_t* key); - -bool mf_classic_dict_get_next_key_str(MfClassicDict* dict, FuriString* key); - -/** Get key at target offset as uint64_t - * - * @param dict MfClassicDict instance - * @param[out] key Pointer to the uint64_t key - * @param[in] target Target offset from current position - * - * @return true on success - */ -bool mf_classic_dict_get_key_at_index(MfClassicDict* dict, uint64_t* key, uint32_t target); - -/** Get key at target offset as string_t - * - * @param dict MfClassicDict instance - * @param[out] key Found key destination buffer - * @param[in] target Target offset from current position - * - * @return true on success - */ -bool mf_classic_dict_get_key_at_index_str(MfClassicDict* dict, FuriString* key, uint32_t target); - -bool mf_classic_dict_add_key(MfClassicDict* dict, uint8_t* key); - -/** Add string representation of the key - * - * @param dict MfClassicDict instance - * @param[in] key String representation of the key - * - * @return true on success - */ -bool mf_classic_dict_add_key_str(MfClassicDict* dict, FuriString* key); - -bool mf_classic_dict_find_index(MfClassicDict* dict, uint8_t* key, uint32_t* target); - -bool mf_classic_dict_find_index_str(MfClassicDict* dict, FuriString* key, uint32_t* target); - -/** Delete key at target offset - * - * @param dict MfClassicDict instance - * @param[in] target Target offset from current position - * - * @return true on success - */ -bool mf_classic_dict_delete_index(MfClassicDict* dict, uint32_t target); - -#ifdef __cplusplus -} -#endif \ No newline at end of file diff --git a/lib/nfc/helpers/mfkey32.c b/lib/nfc/helpers/mfkey32.c deleted file mode 100644 index 47e7e9f6b39..00000000000 --- a/lib/nfc/helpers/mfkey32.c +++ /dev/null @@ -1,228 +0,0 @@ -#include "mfkey32.h" - -#include -#include -#include -#include -#include - -#include -#include - -#define TAG "Mfkey32" - -#define MFKEY32_LOGS_PATH EXT_PATH("nfc/.mfkey32.log") - -typedef enum { - Mfkey32StateIdle, - Mfkey32StateAuthReceived, - Mfkey32StateAuthNtSent, - Mfkey32StateAuthArNrReceived, -} Mfkey32State; - -typedef struct { - uint32_t cuid; - uint8_t sector; - MfClassicKey key; - uint32_t nt0; - uint32_t nr0; - uint32_t ar0; - uint32_t nt1; - uint32_t nr1; - uint32_t ar1; -} Mfkey32Params; - -ARRAY_DEF(Mfkey32Params, Mfkey32Params, M_POD_OPLIST); - -typedef struct { - uint8_t sector; - MfClassicKey key; - uint32_t nt; - uint32_t nr; - uint32_t ar; -} Mfkey32Nonce; - -struct Mfkey32 { - Mfkey32State state; - Stream* file_stream; - Mfkey32Params_t params_arr; - Mfkey32Nonce nonce; - uint32_t cuid; - Mfkey32ParseDataCallback callback; - void* context; -}; - -Mfkey32* mfkey32_alloc(uint32_t cuid) { - Mfkey32* instance = malloc(sizeof(Mfkey32)); - instance->cuid = cuid; - instance->state = Mfkey32StateIdle; - Storage* storage = furi_record_open(RECORD_STORAGE); - instance->file_stream = buffered_file_stream_alloc(storage); - if(!buffered_file_stream_open( - instance->file_stream, MFKEY32_LOGS_PATH, FSAM_WRITE, FSOM_OPEN_APPEND)) { - buffered_file_stream_close(instance->file_stream); - stream_free(instance->file_stream); - free(instance); - instance = NULL; - } else { - Mfkey32Params_init(instance->params_arr); - } - - furi_record_close(RECORD_STORAGE); - - return instance; -} - -void mfkey32_free(Mfkey32* instance) { - furi_assert(instance != NULL); - - Mfkey32Params_clear(instance->params_arr); - buffered_file_stream_close(instance->file_stream); - stream_free(instance->file_stream); - free(instance); -} - -void mfkey32_set_callback(Mfkey32* instance, Mfkey32ParseDataCallback callback, void* context) { - furi_assert(instance); - furi_assert(callback); - - instance->callback = callback; - instance->context = context; -} - -static bool mfkey32_write_params(Mfkey32* instance, Mfkey32Params* params) { - FuriString* str = furi_string_alloc_printf( - "Sec %d key %c cuid %08lx nt0 %08lx nr0 %08lx ar0 %08lx nt1 %08lx nr1 %08lx ar1 %08lx\n", - params->sector, - params->key == MfClassicKeyA ? 'A' : 'B', - params->cuid, - params->nt0, - params->nr0, - params->ar0, - params->nt1, - params->nr1, - params->ar1); - bool write_success = stream_write_string(instance->file_stream, str); - furi_string_free(str); - return write_success; -} - -static void mfkey32_add_params(Mfkey32* instance) { - Mfkey32Nonce* nonce = &instance->nonce; - bool nonce_added = false; - // Search if we partially collected params - if(Mfkey32Params_size(instance->params_arr)) { - Mfkey32Params_it_t it; - for(Mfkey32Params_it(it, instance->params_arr); !Mfkey32Params_end_p(it); - Mfkey32Params_next(it)) { - Mfkey32Params* params = Mfkey32Params_ref(it); - if((params->sector == nonce->sector) && (params->key == nonce->key)) { - params->nt1 = nonce->nt; - params->nr1 = nonce->nr; - params->ar1 = nonce->ar; - nonce_added = true; - FURI_LOG_I( - TAG, - "Params for sector %d key %c collected", - params->sector, - params->key == MfClassicKeyA ? 'A' : 'B'); - // Write on sd card - if(mfkey32_write_params(instance, params)) { - Mfkey32Params_remove(instance->params_arr, it); - if(instance->callback) { - instance->callback(Mfkey32EventParamCollected, instance->context); - } - } - } - } - } - if(!nonce_added) { - Mfkey32Params params = { - .sector = nonce->sector, - .key = nonce->key, - .cuid = instance->cuid, - .nt0 = nonce->nt, - .nr0 = nonce->nr, - .ar0 = nonce->ar, - }; - Mfkey32Params_push_back(instance->params_arr, params); - } -} - -void mfkey32_process_data( - Mfkey32* instance, - uint8_t* data, - uint16_t len, - bool reader_to_tag, - bool crc_dropped) { - furi_assert(instance); - furi_assert(data); - - Mfkey32Nonce* nonce = &instance->nonce; - uint16_t data_len = len; - if((data_len > 3) && !crc_dropped) { - data_len -= 2; - } - - bool data_processed = false; - if(instance->state == Mfkey32StateIdle) { - if(reader_to_tag) { - if((data[0] == 0x60) || (data[0] == 0x61)) { - nonce->key = data[0] == 0x60 ? MfClassicKeyA : MfClassicKeyB; - nonce->sector = mf_classic_get_sector_by_block(data[1]); - instance->state = Mfkey32StateAuthReceived; - data_processed = true; - } - } - } else if(instance->state == Mfkey32StateAuthReceived) { - if(!reader_to_tag) { - if(len == 4) { - nonce->nt = nfc_util_bytes2num(data, 4); - instance->state = Mfkey32StateAuthNtSent; - data_processed = true; - } - } - } else if(instance->state == Mfkey32StateAuthNtSent) { - if(reader_to_tag) { - if(len == 8) { - nonce->nr = nfc_util_bytes2num(data, 4); - nonce->ar = nfc_util_bytes2num(&data[4], 4); - mfkey32_add_params(instance); - instance->state = Mfkey32StateIdle; - } - } - } - if(!data_processed) { - instance->state = Mfkey32StateIdle; - } -} - -uint16_t mfkey32_get_auth_sectors(FuriString* data_str) { - furi_assert(data_str); - - uint16_t nonces_num = 0; - Storage* storage = furi_record_open(RECORD_STORAGE); - Stream* file_stream = buffered_file_stream_alloc(storage); - FuriString* temp_str; - temp_str = furi_string_alloc(); - - do { - if(!buffered_file_stream_open( - file_stream, MFKEY32_LOGS_PATH, FSAM_READ, FSOM_OPEN_EXISTING)) - break; - while(true) { - if(!stream_read_line(file_stream, temp_str)) break; - size_t uid_pos = furi_string_search(temp_str, "cuid"); - furi_string_left(temp_str, uid_pos); - furi_string_push_back(temp_str, '\n'); - furi_string_cat(data_str, temp_str); - nonces_num++; - } - } while(false); - - buffered_file_stream_close(file_stream); - stream_free(file_stream); - furi_string_free(temp_str); - - return nonces_num; -} diff --git a/lib/nfc/helpers/mfkey32.h b/lib/nfc/helpers/mfkey32.h deleted file mode 100644 index e2904322407..00000000000 --- a/lib/nfc/helpers/mfkey32.h +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct Mfkey32 Mfkey32; - -typedef enum { - Mfkey32EventParamCollected, -} Mfkey32Event; - -typedef void (*Mfkey32ParseDataCallback)(Mfkey32Event event, void* context); - -Mfkey32* mfkey32_alloc(uint32_t cuid); - -void mfkey32_free(Mfkey32* instance); - -void mfkey32_process_data( - Mfkey32* instance, - uint8_t* data, - uint16_t len, - bool reader_to_tag, - bool crc_dropped); - -void mfkey32_set_callback(Mfkey32* instance, Mfkey32ParseDataCallback callback, void* context); - -uint16_t mfkey32_get_auth_sectors(FuriString* string); - -#ifdef __cplusplus -} -#endif \ No newline at end of file diff --git a/lib/nfc/helpers/nfc_data_generator.c b/lib/nfc/helpers/nfc_data_generator.c new file mode 100644 index 00000000000..27830516220 --- /dev/null +++ b/lib/nfc/helpers/nfc_data_generator.c @@ -0,0 +1,566 @@ +#include "nfc_data_generator.h" + +#include +#include +#include +#include +#include + +#define NXP_MANUFACTURER_ID (0x04) + +typedef void (*NfcDataGeneratorHandler)(NfcDevice* nfc_device); + +typedef struct { + const char* name; + NfcDataGeneratorHandler handler; +} NfcDataGenerator; + +static const uint8_t version_bytes_mf0ulx1[] = {0x00, 0x04, 0x03, 0x00, 0x01, 0x00, 0x00, 0x03}; +static const uint8_t version_bytes_ntag21x[] = {0x00, 0x04, 0x04, 0x02, 0x01, 0x00, 0x00, 0x03}; +static const uint8_t version_bytes_ntag_i2c[] = {0x00, 0x04, 0x04, 0x05, 0x02, 0x00, 0x00, 0x03}; +static const uint8_t default_data_ntag203[] = + {0xE1, 0x10, 0x12, 0x00, 0x01, 0x03, 0xA0, 0x10, 0x44, 0x03, 0x00, 0xFE}; +static const uint8_t default_data_ntag213[] = {0x01, 0x03, 0xA0, 0x0C, 0x34, 0x03, 0x00, 0xFE}; +static const uint8_t default_data_ntag215_216[] = {0x03, 0x00, 0xFE}; +static const uint8_t default_data_ntag_i2c[] = {0xE1, 0x10, 0x00, 0x00, 0x03, 0x00, 0xFE}; +static const uint8_t default_config_ntag_i2c[] = {0x01, 0x00, 0xF8, 0x48, 0x08, 0x01, 0x00, 0x00}; + +static void nfc_generate_mf_ul_uid(uint8_t* uid) { + uid[0] = NXP_MANUFACTURER_ID; + furi_hal_random_fill_buf(&uid[1], 6); + // I'm not sure how this is generated, but the upper nybble always seems to be 8 + uid[6] &= 0x0F; + uid[6] |= 0x80; +} + +static void nfc_generate_mf_ul_common(MfUltralightData* mfu_data) { + mfu_data->iso14443_3a_data->uid_len = 7; + nfc_generate_mf_ul_uid(mfu_data->iso14443_3a_data->uid); + mfu_data->iso14443_3a_data->atqa[0] = 0x44; + mfu_data->iso14443_3a_data->atqa[1] = 0x00; + mfu_data->iso14443_3a_data->sak = 0x00; +} + +static void nfc_generate_calc_bcc(uint8_t* uid, uint8_t* bcc0, uint8_t* bcc1) { + *bcc0 = 0x88 ^ uid[0] ^ uid[1] ^ uid[2]; + *bcc1 = uid[3] ^ uid[4] ^ uid[5] ^ uid[6]; +} + +static void nfc_generate_mf_ul_copy_uid_with_bcc(MfUltralightData* mfu_data) { + memcpy(mfu_data->page[0].data, mfu_data->iso14443_3a_data->uid, 3); + memcpy(mfu_data->page[1].data, &mfu_data->iso14443_3a_data->uid[3], 4); + + nfc_generate_calc_bcc( + mfu_data->iso14443_3a_data->uid, &mfu_data->page[0].data[3], &mfu_data->page[2].data[0]); +} + +static void nfc_generate_mf_ul_orig(NfcDevice* nfc_device) { + MfUltralightData* mfu_data = mf_ultralight_alloc(); + nfc_generate_mf_ul_common(mfu_data); + + mfu_data->type = MfUltralightTypeUnknown; + mfu_data->pages_total = 16; + mfu_data->pages_read = 16; + nfc_generate_mf_ul_copy_uid_with_bcc(mfu_data); + memset(&mfu_data->page[4], 0xff, sizeof(MfUltralightPage)); + + nfc_device_set_data(nfc_device, NfcProtocolMfUltralight, mfu_data); + mf_ultralight_free(mfu_data); +} + +static void nfc_generate_mf_ul_with_config_common(MfUltralightData* mfu_data, uint8_t num_pages) { + nfc_generate_mf_ul_common(mfu_data); + + mfu_data->pages_total = num_pages; + mfu_data->pages_read = num_pages; + nfc_generate_mf_ul_copy_uid_with_bcc(mfu_data); + uint16_t config_index = (num_pages - 4); + mfu_data->page[config_index].data[0] = 0x04; // STRG_MOD_EN + mfu_data->page[config_index].data[3] = 0xff; // AUTH0 + mfu_data->page[config_index + 1].data[1] = 0x05; // VCTID + memset(&mfu_data->page[config_index + 2], 0xff, sizeof(MfUltralightPage)); // Default PWD + if(num_pages > 20) { + mfu_data->page[config_index - 1].data[3] = MF_ULTRALIGHT_TEARING_FLAG_DEFAULT; + } +} + +static void nfc_generate_mf_ul_ev1_common(MfUltralightData* mfu_data, uint8_t num_pages) { + nfc_generate_mf_ul_with_config_common(mfu_data, num_pages); + memcpy(&mfu_data->version, version_bytes_mf0ulx1, sizeof(MfUltralightVersion)); + for(size_t i = 0; i < 3; ++i) { + mfu_data->tearing_flag[i].data = MF_ULTRALIGHT_TEARING_FLAG_DEFAULT; + } +} + +static void nfc_generate_mf_ul_11(NfcDevice* nfc_device) { + MfUltralightData* mfu_data = mf_ultralight_alloc(); + + nfc_generate_mf_ul_ev1_common(mfu_data, 20); + mfu_data->type = MfUltralightTypeUL11; + mfu_data->version.prod_subtype = 0x01; + mfu_data->version.storage_size = 0x0B; + mfu_data->page[16].data[0] = 0x00; // Low capacitance version does not have STRG_MOD_EN + + nfc_device_set_data(nfc_device, NfcProtocolMfUltralight, mfu_data); + mf_ultralight_free(mfu_data); +} + +static void nfc_generate_mf_ul_h11(NfcDevice* nfc_device) { + MfUltralightData* mfu_data = mf_ultralight_alloc(); + + nfc_generate_mf_ul_ev1_common(mfu_data, 20); + mfu_data->type = MfUltralightTypeUL11; + mfu_data->version.prod_subtype = 0x02; + mfu_data->version.storage_size = 0x0B; + + nfc_device_set_data(nfc_device, NfcProtocolMfUltralight, mfu_data); + mf_ultralight_free(mfu_data); +} + +static void nfc_generate_mf_ul_21(NfcDevice* nfc_device) { + MfUltralightData* mfu_data = mf_ultralight_alloc(); + + nfc_generate_mf_ul_ev1_common(mfu_data, 41); + mfu_data->type = MfUltralightTypeUL21; + mfu_data->version.prod_subtype = 0x01; + mfu_data->version.storage_size = 0x0E; + mfu_data->page[37].data[0] = 0x00; // Low capacitance version does not have STRG_MOD_EN + + nfc_device_set_data(nfc_device, NfcProtocolMfUltralight, mfu_data); + mf_ultralight_free(mfu_data); +} + +static void nfc_generate_mf_ul_h21(NfcDevice* nfc_device) { + MfUltralightData* mfu_data = mf_ultralight_alloc(); + + nfc_generate_mf_ul_ev1_common(mfu_data, 41); + mfu_data->type = MfUltralightTypeUL21; + mfu_data->version.prod_subtype = 0x02; + mfu_data->version.storage_size = 0x0E; + + nfc_device_set_data(nfc_device, NfcProtocolMfUltralight, mfu_data); + mf_ultralight_free(mfu_data); +} + +static void nfc_generate_ntag203(NfcDevice* nfc_device) { + MfUltralightData* mfu_data = mf_ultralight_alloc(); + + nfc_generate_mf_ul_common(mfu_data); + mfu_data->type = MfUltralightTypeNTAG203; + mfu_data->pages_total = 42; + mfu_data->pages_read = 42; + nfc_generate_mf_ul_copy_uid_with_bcc(mfu_data); + mfu_data->page[2].data[1] = 0x48; // Internal byte + memcpy(&mfu_data->page[3], default_data_ntag203, sizeof(MfUltralightPage)); //-V1086 + + nfc_device_set_data(nfc_device, NfcProtocolMfUltralight, mfu_data); + mf_ultralight_free(mfu_data); +} + +static void nfc_generate_ntag21x_common(MfUltralightData* mfu_data, uint8_t num_pages) { + nfc_generate_mf_ul_with_config_common(mfu_data, num_pages); + memcpy(&mfu_data->version, version_bytes_ntag21x, sizeof(MfUltralightVersion)); + mfu_data->page[2].data[1] = 0x48; // Internal byte + // Capability container + mfu_data->page[3].data[0] = 0xE1; + mfu_data->page[3].data[1] = 0x10; +} + +static void nfc_generate_ntag213(NfcDevice* nfc_device) { + MfUltralightData* mfu_data = mf_ultralight_alloc(); + + nfc_generate_ntag21x_common(mfu_data, 45); + mfu_data->type = MfUltralightTypeNTAG213; + mfu_data->version.storage_size = 0x0F; + mfu_data->page[3].data[2] = 0x12; + // Default contents + memcpy(&mfu_data->page[4], default_data_ntag213, sizeof(default_data_ntag213)); + + nfc_device_set_data(nfc_device, NfcProtocolMfUltralight, mfu_data); + mf_ultralight_free(mfu_data); +} + +static void nfc_generate_ntag215(NfcDevice* nfc_device) { + MfUltralightData* mfu_data = mf_ultralight_alloc(); + + nfc_generate_ntag21x_common(mfu_data, 135); + mfu_data->type = MfUltralightTypeNTAG215; + mfu_data->version.storage_size = 0x11; + mfu_data->page[3].data[2] = 0x3E; + // Default contents + memcpy(&mfu_data->page[4], default_data_ntag215_216, sizeof(default_data_ntag215_216)); + + nfc_device_set_data(nfc_device, NfcProtocolMfUltralight, mfu_data); + mf_ultralight_free(mfu_data); +} + +static void nfc_generate_ntag216(NfcDevice* nfc_device) { + MfUltralightData* mfu_data = mf_ultralight_alloc(); + + nfc_generate_ntag21x_common(mfu_data, 231); + mfu_data->type = MfUltralightTypeNTAG216; + mfu_data->version.storage_size = 0x13; + mfu_data->page[3].data[2] = 0x6D; + // Default contents + memcpy(&mfu_data->page[4], default_data_ntag215_216, sizeof(default_data_ntag215_216)); + + nfc_device_set_data(nfc_device, NfcProtocolMfUltralight, mfu_data); + mf_ultralight_free(mfu_data); +} + +static void nfc_generate_ntag_i2c_common( + MfUltralightData* mfu_data, + MfUltralightType type, + uint16_t num_pages) { + nfc_generate_mf_ul_common(mfu_data); + + mfu_data->type = type; + memcpy(&mfu_data->version, version_bytes_ntag_i2c, sizeof(version_bytes_ntag_i2c)); + mfu_data->pages_total = num_pages; + mfu_data->pages_read = num_pages; + memcpy( + mfu_data->page[0].data, + mfu_data->iso14443_3a_data->uid, + mfu_data->iso14443_3a_data->uid_len); + mfu_data->page[1].data[3] = mfu_data->iso14443_3a_data->sak; + mfu_data->page[2].data[0] = mfu_data->iso14443_3a_data->atqa[0]; + mfu_data->page[2].data[1] = mfu_data->iso14443_3a_data->atqa[1]; + + uint16_t config_register_page = 0; + uint16_t session_register_page = 0; + + // Sync with mifare_ultralight.c + switch(type) { + case MfUltralightTypeNTAGI2C1K: + config_register_page = 227; + session_register_page = 229; + break; + case MfUltralightTypeNTAGI2C2K: + config_register_page = 481; + session_register_page = 483; + break; + case MfUltralightTypeNTAGI2CPlus1K: + case MfUltralightTypeNTAGI2CPlus2K: + config_register_page = 232; + session_register_page = 234; + break; + default: + furi_crash("Unknown MFUL"); + break; + } + + memcpy( + &mfu_data->page[config_register_page], + default_config_ntag_i2c, + sizeof(default_config_ntag_i2c)); + memcpy( + &mfu_data->page[session_register_page], + default_config_ntag_i2c, + sizeof(default_config_ntag_i2c)); +} + +static void nfc_generate_ntag_i2c_1k(NfcDevice* nfc_device) { + MfUltralightData* mfu_data = mf_ultralight_alloc(); + + nfc_generate_ntag_i2c_common(mfu_data, MfUltralightTypeNTAGI2C1K, 231); + mfu_data->version.prod_ver_minor = 0x01; + mfu_data->version.storage_size = 0x13; + memcpy(&mfu_data->page[3], default_data_ntag_i2c, sizeof(default_data_ntag_i2c)); + mfu_data->page[3].data[2] = 0x6D; // Size of tag in CC + + nfc_device_set_data(nfc_device, NfcProtocolMfUltralight, mfu_data); + mf_ultralight_free(mfu_data); +} + +static void nfc_generate_ntag_i2c_2k(NfcDevice* nfc_device) { + MfUltralightData* mfu_data = mf_ultralight_alloc(); + + nfc_generate_ntag_i2c_common(mfu_data, MfUltralightTypeNTAGI2C2K, 485); + mfu_data->version.prod_ver_minor = 0x01; + mfu_data->version.storage_size = 0x15; + memcpy(&mfu_data->page[3], default_data_ntag_i2c, sizeof(default_data_ntag_i2c)); + mfu_data->page[3].data[2] = 0xEA; // Size of tag in CC + + nfc_device_set_data(nfc_device, NfcProtocolMfUltralight, mfu_data); + mf_ultralight_free(mfu_data); +} + +static void nfc_generate_ntag_i2c_plus_common( + MfUltralightData* mfu_data, + MfUltralightType type, + uint16_t num_pages) { + nfc_generate_ntag_i2c_common(mfu_data, type, num_pages); + + uint16_t config_index = 227; + mfu_data->page[config_index].data[3] = 0xff; // AUTH0 + + memset(&mfu_data->page[config_index + 2], 0xFF, sizeof(MfUltralightPage)); // Default PWD +} + +static void nfc_generate_ntag_i2c_plus_1k(NfcDevice* nfc_device) { + MfUltralightData* mfu_data = mf_ultralight_alloc(); + + nfc_generate_ntag_i2c_plus_common(mfu_data, MfUltralightTypeNTAGI2CPlus1K, 236); + mfu_data->version.prod_ver_minor = 0x02; + mfu_data->version.storage_size = 0x13; + + nfc_device_set_data(nfc_device, NfcProtocolMfUltralight, mfu_data); + mf_ultralight_free(mfu_data); +} + +static void nfc_generate_ntag_i2c_plus_2k(NfcDevice* nfc_device) { + MfUltralightData* mfu_data = mf_ultralight_alloc(); + + nfc_generate_ntag_i2c_plus_common(mfu_data, MfUltralightTypeNTAGI2CPlus2K, 492); + mfu_data->version.prod_ver_minor = 0x02; + mfu_data->version.storage_size = 0x15; + + nfc_device_set_data(nfc_device, NfcProtocolMfUltralight, mfu_data); + mf_ultralight_free(mfu_data); +} + +static void nfc_generate_mf_classic_uid(uint8_t* uid, uint8_t length) { + uid[0] = NXP_MANUFACTURER_ID; + furi_hal_random_fill_buf(&uid[1], length - 1); +} + +static void + nfc_generate_mf_classic_common(MfClassicData* data, uint8_t uid_len, MfClassicType type) { + data->iso14443_3a_data->uid_len = uid_len; + data->iso14443_3a_data->atqa[0] = 0x44; + data->iso14443_3a_data->atqa[1] = 0x00; + data->iso14443_3a_data->sak = 0x08; + data->type = type; +} + +static void nfc_generate_mf_classic_sector_trailer(MfClassicData* data, uint8_t block) { + // All keys are set to FFFF FFFF FFFFh at chip delivery and the bytes 6, 7 and 8 are set to FF0780h. + MfClassicSectorTrailer* sec_tr = (MfClassicSectorTrailer*)data->block[block].data; + sec_tr->access_bits.data[0] = 0xFF; + sec_tr->access_bits.data[1] = 0x07; + sec_tr->access_bits.data[2] = 0x80; + sec_tr->access_bits.data[3] = 0x69; // Nice + + mf_classic_set_block_read(data, block, &data->block[block]); + mf_classic_set_key_found( + data, mf_classic_get_sector_by_block(block), MfClassicKeyTypeA, 0xFFFFFFFFFFFF); + mf_classic_set_key_found( + data, mf_classic_get_sector_by_block(block), MfClassicKeyTypeB, 0xFFFFFFFFFFFF); +} + +static void nfc_generate_mf_classic_block_0( + uint8_t* block, + uint8_t uid_len, + uint8_t sak, + uint8_t atqa0, + uint8_t atqa1) { + // Block length is always 16 bytes, and the UID can be either 4 or 7 bytes + furi_assert(uid_len == 4 || uid_len == 7); + furi_assert(block); + + if(uid_len == 4) { + // Calculate BCC + block[uid_len] = 0; + + for(int i = 0; i < uid_len; i++) { + block[uid_len] ^= block[i]; + } + } else { + uid_len -= 1; + } + + block[uid_len + 1] = sak; + block[uid_len + 2] = atqa0; + block[uid_len + 3] = atqa1; + + for(int i = uid_len + 4; i < 16; i++) { + block[i] = 0xFF; + } +} + +static void nfc_generate_mf_classic(NfcDevice* nfc_device, uint8_t uid_len, MfClassicType type) { + MfClassicData* mfc_data = mf_classic_alloc(); + + nfc_generate_mf_classic_uid(mfc_data->block[0].data, uid_len); + nfc_generate_mf_classic_common(mfc_data, uid_len, type); + + // Set the UID + mfc_data->iso14443_3a_data->uid[0] = NXP_MANUFACTURER_ID; + for(int i = 1; i < uid_len; i++) { + mfc_data->iso14443_3a_data->uid[i] = mfc_data->block[0].data[i]; + } + + mf_classic_set_block_read(mfc_data, 0, &mfc_data->block[0]); + + uint16_t block_num = mf_classic_get_total_block_num(type); + if(type == MfClassicType4k) { + // Set every block to 0xFF + for(uint16_t i = 1; i < block_num; i++) { + if(mf_classic_is_sector_trailer(i)) { + nfc_generate_mf_classic_sector_trailer(mfc_data, i); + } else { + memset(&mfc_data->block[i].data, 0xFF, 16); + } + mf_classic_set_block_read(mfc_data, i, &mfc_data->block[i]); + } + // Set SAK to 18 + mfc_data->iso14443_3a_data->sak = 0x18; + } else if(type == MfClassicType1k) { + // Set every block to 0xFF + for(uint16_t i = 1; i < block_num; i++) { + if(mf_classic_is_sector_trailer(i)) { + nfc_generate_mf_classic_sector_trailer(mfc_data, i); + } else { + memset(&mfc_data->block[i].data, 0xFF, 16); + } + mf_classic_set_block_read(mfc_data, i, &mfc_data->block[i]); + } + // Set SAK to 08 + mfc_data->iso14443_3a_data->sak = 0x08; + } else if(type == MfClassicTypeMini) { + // Set every block to 0xFF + for(uint16_t i = 1; i < block_num; i++) { + if(mf_classic_is_sector_trailer(i)) { + nfc_generate_mf_classic_sector_trailer(mfc_data, i); + } else { + memset(&mfc_data->block[i].data, 0xFF, 16); + } + mf_classic_set_block_read(mfc_data, i, &mfc_data->block[i]); + } + // Set SAK to 09 + mfc_data->iso14443_3a_data->sak = 0x09; + } + + nfc_generate_mf_classic_block_0( + mfc_data->block[0].data, + uid_len, + mfc_data->iso14443_3a_data->sak, + mfc_data->iso14443_3a_data->atqa[0], + mfc_data->iso14443_3a_data->atqa[1]); + + mfc_data->type = type; + + nfc_device_set_data(nfc_device, NfcProtocolMfClassic, mfc_data); + mf_classic_free(mfc_data); +} + +static void nfc_generate_mf_classic_mini(NfcDevice* nfc_device) { + nfc_generate_mf_classic(nfc_device, 4, MfClassicTypeMini); +} + +static void nfc_generate_mf_classic_1k_4b_uid(NfcDevice* nfc_device) { + nfc_generate_mf_classic(nfc_device, 4, MfClassicType1k); +} + +static void nfc_generate_mf_classic_1k_7b_uid(NfcDevice* nfc_device) { + nfc_generate_mf_classic(nfc_device, 7, MfClassicType1k); +} + +static void nfc_generate_mf_classic_4k_4b_uid(NfcDevice* nfc_device) { + nfc_generate_mf_classic(nfc_device, 4, MfClassicType4k); +} + +static void nfc_generate_mf_classic_4k_7b_uid(NfcDevice* nfc_device) { + nfc_generate_mf_classic(nfc_device, 7, MfClassicType4k); +} + +static const NfcDataGenerator nfc_data_generator[NfcDataGeneratorTypeNum] = { + [NfcDataGeneratorTypeMfUltralight] = + { + .name = "Mifare Ultralight", + .handler = nfc_generate_mf_ul_orig, + }, + [NfcDataGeneratorTypeMfUltralightEV1_11] = + { + .name = "Mifare Ultralight EV1 11", + .handler = nfc_generate_mf_ul_11, + }, + [NfcDataGeneratorTypeMfUltralightEV1_H11] = + { + .name = "Mifare Ultralight EV1 H11", + .handler = nfc_generate_mf_ul_h11, + }, + [NfcDataGeneratorTypeMfUltralightEV1_21] = + { + .name = "Mifare Ultralight EV1 21", + .handler = nfc_generate_mf_ul_21, + }, + [NfcDataGeneratorTypeMfUltralightEV1_H21] = + { + .name = "Mifare Ultralight EV1 H21", + .handler = nfc_generate_mf_ul_h21, + }, + [NfcDataGeneratorTypeNTAG203] = + { + .name = "NTAG203", + .handler = nfc_generate_ntag203, + }, + [NfcDataGeneratorTypeNTAG213] = + { + .name = "NTAG213", + .handler = nfc_generate_ntag213, + }, + [NfcDataGeneratorTypeNTAG215] = + { + .name = "NTAG215", + .handler = nfc_generate_ntag215, + }, + [NfcDataGeneratorTypeNTAG216] = + { + .name = "NTAG216", + .handler = nfc_generate_ntag216, + }, + [NfcDataGeneratorTypeNTAGI2C1k] = + { + .name = "NTAG I2C 1k", + .handler = nfc_generate_ntag_i2c_1k, + }, + [NfcDataGeneratorTypeNTAGI2C2k] = + { + .name = "NTAG I2C 2k", + .handler = nfc_generate_ntag_i2c_2k, + }, + [NfcDataGeneratorTypeNTAGI2CPlus1k] = + { + .name = "NTAG I2C Plus 1k", + .handler = nfc_generate_ntag_i2c_plus_1k, + }, + [NfcDataGeneratorTypeNTAGI2CPlus2k] = + { + .name = "NTAG I2C Plus 2k", + .handler = nfc_generate_ntag_i2c_plus_2k, + }, + [NfcDataGeneratorTypeMfClassicMini] = + { + .name = "Mifare Mini", + .handler = nfc_generate_mf_classic_mini, + }, + [NfcDataGeneratorTypeMfClassic1k_4b] = + { + .name = "Mifare Classic 1k 4byte UID", + .handler = nfc_generate_mf_classic_1k_4b_uid, + }, + [NfcDataGeneratorTypeMfClassic1k_7b] = + { + .name = "Mifare Classic 1k 7byte UID", + .handler = nfc_generate_mf_classic_1k_7b_uid, + }, + [NfcDataGeneratorTypeMfClassic4k_4b] = + { + .name = "Mifare Classic 4k 4byte UID", + .handler = nfc_generate_mf_classic_4k_4b_uid, + }, + [NfcDataGeneratorTypeMfClassic4k_7b] = + { + .name = "Mifare Classic 4k 7byte UID", + .handler = nfc_generate_mf_classic_4k_7b_uid, + }, +}; + +const char* nfc_data_generator_get_name(NfcDataGeneratorType type) { + return nfc_data_generator[type].name; +} + +void nfc_data_generator_fill_data(NfcDataGeneratorType type, NfcDevice* nfc_device) { + nfc_data_generator[type].handler(nfc_device); +} diff --git a/lib/nfc/helpers/nfc_data_generator.h b/lib/nfc/helpers/nfc_data_generator.h new file mode 100644 index 00000000000..01f5dac16e5 --- /dev/null +++ b/lib/nfc/helpers/nfc_data_generator.h @@ -0,0 +1,40 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + NfcDataGeneratorTypeMfUltralight, + NfcDataGeneratorTypeMfUltralightEV1_11, + NfcDataGeneratorTypeMfUltralightEV1_H11, + NfcDataGeneratorTypeMfUltralightEV1_21, + NfcDataGeneratorTypeMfUltralightEV1_H21, + NfcDataGeneratorTypeNTAG203, + NfcDataGeneratorTypeNTAG213, + NfcDataGeneratorTypeNTAG215, + NfcDataGeneratorTypeNTAG216, + NfcDataGeneratorTypeNTAGI2C1k, + NfcDataGeneratorTypeNTAGI2C2k, + NfcDataGeneratorTypeNTAGI2CPlus1k, + NfcDataGeneratorTypeNTAGI2CPlus2k, + + NfcDataGeneratorTypeMfClassicMini, + NfcDataGeneratorTypeMfClassic1k_4b, + NfcDataGeneratorTypeMfClassic1k_7b, + NfcDataGeneratorTypeMfClassic4k_4b, + NfcDataGeneratorTypeMfClassic4k_7b, + + NfcDataGeneratorTypeNum, + +} NfcDataGeneratorType; + +const char* nfc_data_generator_get_name(NfcDataGeneratorType type); + +void nfc_data_generator_fill_data(NfcDataGeneratorType type, NfcDevice* nfc_device); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/helpers/nfc_debug_log.c b/lib/nfc/helpers/nfc_debug_log.c deleted file mode 100644 index 0bfbc2c62f6..00000000000 --- a/lib/nfc/helpers/nfc_debug_log.c +++ /dev/null @@ -1,71 +0,0 @@ -#include "nfc_debug_log.h" - -#include -#include - -#define TAG "NfcDebugLog" - -#define NFC_DEBUG_PCAP_FILENAME EXT_PATH("nfc/debug.txt") - -struct NfcDebugLog { - Stream* file_stream; - FuriString* data_str; -}; - -NfcDebugLog* nfc_debug_log_alloc() { - NfcDebugLog* instance = malloc(sizeof(NfcDebugLog)); - - Storage* storage = furi_record_open(RECORD_STORAGE); - instance->file_stream = buffered_file_stream_alloc(storage); - - if(!buffered_file_stream_open( - instance->file_stream, NFC_DEBUG_PCAP_FILENAME, FSAM_WRITE, FSOM_OPEN_APPEND)) { - buffered_file_stream_close(instance->file_stream); - stream_free(instance->file_stream); - instance->file_stream = NULL; - } - - if(!instance->file_stream) { - free(instance); - instance = NULL; - } else { - instance->data_str = furi_string_alloc(); - } - furi_record_close(RECORD_STORAGE); - - return instance; -} - -void nfc_debug_log_free(NfcDebugLog* instance) { - furi_assert(instance); - furi_assert(instance->file_stream); - furi_assert(instance->data_str); - - buffered_file_stream_close(instance->file_stream); - stream_free(instance->file_stream); - furi_string_free(instance->data_str); - - free(instance); -} - -void nfc_debug_log_process_data( - NfcDebugLog* instance, - uint8_t* data, - uint16_t len, - bool reader_to_tag, - bool crc_dropped) { - furi_assert(instance); - furi_assert(instance->file_stream); - furi_assert(instance->data_str); - furi_assert(data); - UNUSED(crc_dropped); - - furi_string_printf(instance->data_str, "%lu %c:", furi_get_tick(), reader_to_tag ? 'R' : 'T'); - uint16_t data_len = len; - for(size_t i = 0; i < data_len; i++) { - furi_string_cat_printf(instance->data_str, " %02x", data[i]); - } - furi_string_push_back(instance->data_str, '\n'); - - stream_write_string(instance->file_stream, instance->data_str); -} diff --git a/lib/nfc/helpers/nfc_debug_log.h b/lib/nfc/helpers/nfc_debug_log.h deleted file mode 100644 index 261b8008b8e..00000000000 --- a/lib/nfc/helpers/nfc_debug_log.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include -#include - -typedef struct NfcDebugLog NfcDebugLog; - -NfcDebugLog* nfc_debug_log_alloc(); - -void nfc_debug_log_free(NfcDebugLog* instance); - -void nfc_debug_log_process_data( - NfcDebugLog* instance, - uint8_t* data, - uint16_t len, - bool reader_to_tag, - bool crc_dropped); diff --git a/lib/nfc/helpers/nfc_debug_pcap.c b/lib/nfc/helpers/nfc_debug_pcap.c deleted file mode 100644 index 6c7654ad57a..00000000000 --- a/lib/nfc/helpers/nfc_debug_pcap.c +++ /dev/null @@ -1,130 +0,0 @@ -#include "nfc_debug_pcap.h" - -#include -#include -#include -#include - -#define TAG "NfcDebugPcap" - -#define PCAP_MAGIC 0xa1b2c3d4 -#define PCAP_MAJOR 2 -#define PCAP_MINOR 4 -#define DLT_ISO_14443 264 - -#define DATA_PICC_TO_PCD 0xFF -#define DATA_PCD_TO_PICC 0xFE -#define DATA_PICC_TO_PCD_CRC_DROPPED 0xFB -#define DATA_PCD_TO_PICC_CRC_DROPPED 0xFA - -#define NFC_DEBUG_PCAP_FILENAME EXT_PATH("nfc/debug.pcap") - -struct NfcDebugPcap { - Stream* file_stream; -}; - -static Stream* nfc_debug_pcap_open(Storage* storage) { - Stream* stream = NULL; - stream = buffered_file_stream_alloc(storage); - if(!buffered_file_stream_open(stream, NFC_DEBUG_PCAP_FILENAME, FSAM_WRITE, FSOM_OPEN_APPEND)) { - buffered_file_stream_close(stream); - stream_free(stream); - stream = NULL; - } else { - if(!stream_tell(stream)) { - struct { - uint32_t magic; - uint16_t major, minor; - uint32_t reserved[2]; - uint32_t snaplen; - uint32_t link_type; - } __attribute__((__packed__)) pcap_hdr = { - .magic = PCAP_MAGIC, - .major = PCAP_MAJOR, - .minor = PCAP_MINOR, - .snaplen = FURI_HAL_NFC_DATA_BUFF_SIZE, - .link_type = DLT_ISO_14443, - }; - if(stream_write(stream, (uint8_t*)&pcap_hdr, sizeof(pcap_hdr)) != sizeof(pcap_hdr)) { - FURI_LOG_E(TAG, "Failed to write pcap header"); - buffered_file_stream_close(stream); - stream_free(stream); - stream = NULL; - } - } - } - return stream; -} - -NfcDebugPcap* nfc_debug_pcap_alloc() { - NfcDebugPcap* instance = malloc(sizeof(NfcDebugPcap)); - - Storage* storage = furi_record_open(RECORD_STORAGE); - instance->file_stream = nfc_debug_pcap_open(storage); - if(!instance->file_stream) { - free(instance); - instance = NULL; - } - furi_record_close(RECORD_STORAGE); - - return instance; -} - -void nfc_debug_pcap_free(NfcDebugPcap* instance) { - furi_assert(instance); - furi_assert(instance->file_stream); - - buffered_file_stream_close(instance->file_stream); - stream_free(instance->file_stream); - - free(instance); -} - -void nfc_debug_pcap_process_data( - NfcDebugPcap* instance, - uint8_t* data, - uint16_t len, - bool reader_to_tag, - bool crc_dropped) { - furi_assert(instance); - furi_assert(data); - FuriHalRtcDateTime datetime; - furi_hal_rtc_get_datetime(&datetime); - - uint8_t event = 0; - if(reader_to_tag) { - if(crc_dropped) { - event = DATA_PCD_TO_PICC_CRC_DROPPED; - } else { - event = DATA_PCD_TO_PICC; - } - } else { - if(crc_dropped) { - event = DATA_PICC_TO_PCD_CRC_DROPPED; - } else { - event = DATA_PICC_TO_PCD; - } - } - - struct { - // https://wiki.wireshark.org/Development/LibpcapFileFormat#record-packet-header - uint32_t ts_sec; - uint32_t ts_usec; - uint32_t incl_len; - uint32_t orig_len; - // https://www.kaiser.cx/posts/pcap-iso14443/#_packet_data - uint8_t version; - uint8_t event; - uint16_t len; - } __attribute__((__packed__)) pkt_hdr = { - .ts_sec = furi_hal_rtc_datetime_to_timestamp(&datetime), - .ts_usec = 0, - .incl_len = len + 4, - .orig_len = len + 4, - .version = 0, - .event = event, - .len = len << 8 | len >> 8, - }; - stream_write(instance->file_stream, (uint8_t*)&pkt_hdr, sizeof(pkt_hdr)); - stream_write(instance->file_stream, data, len); -} diff --git a/lib/nfc/helpers/nfc_debug_pcap.h b/lib/nfc/helpers/nfc_debug_pcap.h deleted file mode 100644 index eeddc56112c..00000000000 --- a/lib/nfc/helpers/nfc_debug_pcap.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include -#include - -typedef struct NfcDebugPcap NfcDebugPcap; - -NfcDebugPcap* nfc_debug_pcap_alloc(); - -void nfc_debug_pcap_free(NfcDebugPcap* instance); - -void nfc_debug_pcap_process_data( - NfcDebugPcap* instance, - uint8_t* data, - uint16_t len, - bool reader_to_tag, - bool crc_dropped); diff --git a/lib/nfc/helpers/nfc_dict.c b/lib/nfc/helpers/nfc_dict.c new file mode 100644 index 00000000000..d4572a3d623 --- /dev/null +++ b/lib/nfc/helpers/nfc_dict.c @@ -0,0 +1,270 @@ +#include "nfc_dict.h" + +#include +#include +#include +#include +#include + +#include + +#define TAG "NfcDict" + +struct NfcDict { + Stream* stream; + size_t key_size; + size_t key_size_symbols; + uint32_t total_keys; +}; + +typedef struct { + const char* path; + FS_OpenMode open_mode; +} NfcDictFile; + +bool nfc_dict_check_presence(const char* path) { + furi_assert(path); + + Storage* storage = furi_record_open(RECORD_STORAGE); + + bool dict_present = storage_common_stat(storage, path, NULL) == FSE_OK; + + furi_record_close(RECORD_STORAGE); + + return dict_present; +} + +NfcDict* nfc_dict_alloc(const char* path, NfcDictMode mode, size_t key_size) { + furi_assert(path); + + NfcDict* instance = malloc(sizeof(NfcDict)); + Storage* storage = furi_record_open(RECORD_STORAGE); + instance->stream = buffered_file_stream_alloc(storage); + furi_record_close(RECORD_STORAGE); + + FS_OpenMode open_mode = FSOM_OPEN_EXISTING; + if(mode == NfcDictModeOpenAlways) { + open_mode = FSOM_OPEN_ALWAYS; + } + instance->key_size = key_size; + // Byte = 2 symbols + 1 end of line + instance->key_size_symbols = key_size * 2 + 1; + + bool dict_loaded = false; + do { + if(!buffered_file_stream_open(instance->stream, path, FSAM_READ_WRITE, open_mode)) { + buffered_file_stream_close(instance->stream); + break; + } + + // Check for new line ending + if(!stream_eof(instance->stream)) { + if(!stream_seek(instance->stream, -1, StreamOffsetFromEnd)) break; + uint8_t last_char = 0; + if(stream_read(instance->stream, &last_char, 1) != 1) break; + if(last_char != '\n') { + FURI_LOG_D(TAG, "Adding new line ending"); + if(stream_write_char(instance->stream, '\n') != 1) break; + } + if(!stream_rewind(instance->stream)) break; + } + + // Read total amount of keys + FuriString* next_line; + next_line = furi_string_alloc(); + while(true) { + if(!stream_read_line(instance->stream, next_line)) { + FURI_LOG_T(TAG, "No keys left in dict"); + break; + } + FURI_LOG_T( + TAG, + "Read line: %s, len: %zu", + furi_string_get_cstr(next_line), + furi_string_size(next_line)); + if(furi_string_get_char(next_line, 0) == '#') continue; + if(furi_string_size(next_line) != instance->key_size_symbols) continue; + instance->total_keys++; + } + furi_string_free(next_line); + stream_rewind(instance->stream); + + dict_loaded = true; + FURI_LOG_I(TAG, "Loaded dictionary with %lu keys", instance->total_keys); + } while(false); + + if(!dict_loaded) { + buffered_file_stream_close(instance->stream); + free(instance); + instance = NULL; + } + + return instance; +} + +void nfc_dict_free(NfcDict* instance) { + furi_assert(instance); + furi_assert(instance->stream); + + buffered_file_stream_close(instance->stream); + stream_free(instance->stream); + free(instance); +} + +static void nfc_dict_int_to_str(NfcDict* instance, const uint8_t* key_int, FuriString* key_str) { + furi_string_reset(key_str); + for(size_t i = 0; i < instance->key_size; i++) { + furi_string_cat_printf(key_str, "%02X", key_int[i]); + } +} + +static void nfc_dict_str_to_int(NfcDict* instance, FuriString* key_str, uint64_t* key_int) { + uint8_t key_byte_tmp; + + *key_int = 0ULL; + for(uint8_t i = 0; i < instance->key_size * 2; i += 2) { + args_char_to_hex( + furi_string_get_char(key_str, i), furi_string_get_char(key_str, i + 1), &key_byte_tmp); + *key_int |= (uint64_t)key_byte_tmp << (8 * (instance->key_size - 1 - i / 2)); + } +} + +uint32_t nfc_dict_get_total_keys(NfcDict* instance) { + furi_assert(instance); + + return instance->total_keys; +} + +bool nfc_dict_rewind(NfcDict* instance) { + furi_assert(instance); + furi_assert(instance->stream); + + return stream_rewind(instance->stream); +} + +static bool nfc_dict_get_next_key_str(NfcDict* instance, FuriString* key) { + furi_assert(instance); + furi_assert(instance->stream); + + bool key_read = false; + furi_string_reset(key); + while(!key_read) { + if(!stream_read_line(instance->stream, key)) break; + if(furi_string_get_char(key, 0) == '#') continue; + if(furi_string_size(key) != instance->key_size_symbols) continue; + furi_string_left(key, instance->key_size_symbols - 1); + key_read = true; + } + + return key_read; +} + +bool nfc_dict_get_next_key(NfcDict* instance, uint8_t* key, size_t key_size) { + furi_assert(instance); + furi_assert(instance->stream); + furi_assert(instance->key_size == key_size); + + FuriString* temp_key = furi_string_alloc(); + uint64_t key_int = 0; + bool key_read = nfc_dict_get_next_key_str(instance, temp_key); + if(key_read) { + nfc_dict_str_to_int(instance, temp_key, &key_int); + nfc_util_num2bytes(key_int, key_size, key); + } + furi_string_free(temp_key); + return key_read; +} + +static bool nfc_dict_is_key_present_str(NfcDict* instance, FuriString* key) { + furi_assert(instance); + furi_assert(instance->stream); + + FuriString* next_line; + next_line = furi_string_alloc(); + + bool key_found = false; + stream_rewind(instance->stream); + while(!key_found) { //-V654 + if(!stream_read_line(instance->stream, next_line)) break; + if(furi_string_get_char(next_line, 0) == '#') continue; + if(furi_string_size(next_line) != instance->key_size_symbols) continue; + furi_string_left(next_line, instance->key_size_symbols - 1); + if(!furi_string_equal(key, next_line)) continue; + key_found = true; + } + + furi_string_free(next_line); + return key_found; +} + +bool nfc_dict_is_key_present(NfcDict* instance, const uint8_t* key, size_t key_size) { + furi_assert(instance); + furi_assert(key); + furi_assert(instance->stream); + furi_assert(instance->key_size == key_size); + + FuriString* temp_key = furi_string_alloc(); + nfc_dict_int_to_str(instance, key, temp_key); + bool key_found = nfc_dict_is_key_present_str(instance, temp_key); + furi_string_free(temp_key); + + return key_found; +} + +static bool nfc_dict_add_key_str(NfcDict* instance, FuriString* key) { + furi_assert(instance); + furi_assert(instance->stream); + + furi_string_cat_printf(key, "\n"); + + bool key_added = false; + do { + if(!stream_seek(instance->stream, 0, StreamOffsetFromEnd)) break; + if(!stream_insert_string(instance->stream, key)) break; + instance->total_keys++; + key_added = true; + } while(false); + + furi_string_left(key, instance->key_size_symbols - 1); + return key_added; +} + +bool nfc_dict_add_key(NfcDict* instance, const uint8_t* key, size_t key_size) { + furi_assert(instance); + furi_assert(key); + furi_assert(instance->stream); + furi_assert(instance->key_size == key_size); + + FuriString* temp_key = furi_string_alloc(); + nfc_dict_int_to_str(instance, key, temp_key); + bool key_added = nfc_dict_add_key_str(instance, temp_key); + furi_string_free(temp_key); + + return key_added; +} + +bool nfc_dict_delete_key(NfcDict* instance, const uint8_t* key, size_t key_size) { + furi_assert(instance); + furi_assert(instance->stream); + furi_assert(key); + furi_assert(instance->key_size == key_size); + + bool key_removed = false; + uint8_t* temp_key = malloc(key_size); + + nfc_dict_rewind(instance); + while(!key_removed) { + if(!nfc_dict_get_next_key(instance, temp_key, key_size)) break; + if(memcmp(temp_key, key, key_size) == 0) { + int32_t offset = (-1) * (instance->key_size_symbols); + stream_seek(instance->stream, offset, StreamOffsetFromCurrent); + if(!stream_delete(instance->stream, instance->key_size_symbols)) break; + instance->total_keys--; + key_removed = true; + } + } + nfc_dict_rewind(instance); + free(temp_key); + + return key_removed; +} diff --git a/lib/nfc/helpers/nfc_dict.h b/lib/nfc/helpers/nfc_dict.h new file mode 100644 index 00000000000..80f3ff68084 --- /dev/null +++ b/lib/nfc/helpers/nfc_dict.h @@ -0,0 +1,103 @@ +#pragma once + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + NfcDictModeOpenExisting, + NfcDictModeOpenAlways, +} NfcDictMode; + +typedef struct NfcDict NfcDict; + +/** Check dictionary presence + * + * @param path - dictionary path + * + * @return true if dictionary exists, false otherwise +*/ +bool nfc_dict_check_presence(const char* path); + +/** Open or create dictionary + * Depending on mode, dictionary will be opened or created. + * + * @param path - dictionary path + * @param mode - NfcDictMode value + * @param key_size - size of dictionary keys in bytes + * + * @return NfcDict dictionary instance +*/ +NfcDict* nfc_dict_alloc(const char* path, NfcDictMode mode, size_t key_size); + +/** Close dictionary + * + * @param instance - NfcDict dictionary instance +*/ +void nfc_dict_free(NfcDict* instance); + +/** Get total number of keys in dictionary + * + * @param instance - NfcDict dictionary instance + * + * @return total number of keys in dictionary +*/ +uint32_t nfc_dict_get_total_keys(NfcDict* instance); + +/** Rewind dictionary + * + * @param instance - NfcDict dictionary instance + * + * @return true if rewind was successful, false otherwise +*/ +bool nfc_dict_rewind(NfcDict* instance); + +/** Check if key is present in dictionary + * + * @param instance - NfcDict dictionary instance + * @param key - key to check + * @param key_size - size of key in bytes + * + * @return true if key is present, false otherwise +*/ +bool nfc_dict_is_key_present(NfcDict* instance, const uint8_t* key, size_t key_size); + +/** Get next key from dictionary + * This function will return next key from dictionary. If there are no more + * keys, it will return false, and nfc_dict_rewind() should be called. + * + * @param instance - NfcDict dictionary instance + * @param key - buffer to store key + * @param key_size - size of key in bytes + * + * @return true if key was successfully retrieved, false otherwise +*/ +bool nfc_dict_get_next_key(NfcDict* instance, uint8_t* key, size_t key_size); + +/** Add key to dictionary + * + * @param instance - NfcDict dictionary instance + * @param key - key to add + * @param key_size - size of key in bytes + * + * @return true if key was successfully added, false otherwise +*/ +bool nfc_dict_add_key(NfcDict* instance, const uint8_t* key, size_t key_size); + +/** Delete key from dictionary + * + * @param instance - NfcDict dictionary instance + * @param key - key to delete + * @param key_size - size of key in bytes + * + * @return true if key was successfully deleted, false otherwise +*/ +bool nfc_dict_delete_key(NfcDict* instance, const uint8_t* key, size_t key_size); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/helpers/nfc_generators.c b/lib/nfc/helpers/nfc_generators.c deleted file mode 100644 index 50c89aba85c..00000000000 --- a/lib/nfc/helpers/nfc_generators.c +++ /dev/null @@ -1,528 +0,0 @@ -#include -#include "nfc_generators.h" - -#define NXP_MANUFACTURER_ID (0x04) - -static const uint8_t version_bytes_mf0ulx1[] = {0x00, 0x04, 0x03, 0x00, 0x01, 0x00, 0x00, 0x03}; -static const uint8_t version_bytes_ntag21x[] = {0x00, 0x04, 0x04, 0x02, 0x01, 0x00, 0x00, 0x03}; -static const uint8_t version_bytes_ntag_i2c[] = {0x00, 0x04, 0x04, 0x05, 0x02, 0x00, 0x00, 0x03}; -static const uint8_t default_data_ntag203[] = - {0xE1, 0x10, 0x12, 0x00, 0x01, 0x03, 0xA0, 0x10, 0x44, 0x03, 0x00, 0xFE}; -static const uint8_t default_data_ntag213[] = {0x01, 0x03, 0xA0, 0x0C, 0x34, 0x03, 0x00, 0xFE}; -static const uint8_t default_data_ntag215_216[] = {0x03, 0x00, 0xFE}; -static const uint8_t default_data_ntag_i2c[] = {0xE1, 0x10, 0x00, 0x00, 0x03, 0x00, 0xFE}; -static const uint8_t default_config_ntag_i2c[] = {0x01, 0x00, 0xF8, 0x48, 0x08, 0x01, 0x00, 0x00}; - -static void nfc_generate_common_start(NfcDeviceData* data) { - nfc_device_data_clear(data); -} - -static void nfc_generate_mf_ul_uid(uint8_t* uid) { - uid[0] = NXP_MANUFACTURER_ID; - furi_hal_random_fill_buf(&uid[1], 6); - // I'm not sure how this is generated, but the upper nybble always seems to be 8 - uid[6] &= 0x0F; - uid[6] |= 0x80; -} - -static void nfc_generate_mf_classic_uid(uint8_t* uid, uint8_t length) { - uid[0] = NXP_MANUFACTURER_ID; - furi_hal_random_fill_buf(&uid[1], length - 1); -} - -static void nfc_generate_mf_classic_block_0( - uint8_t* block, - uint8_t uid_len, - uint8_t sak, - uint8_t atqa0, - uint8_t atqa1) { - // Block length is always 16 bytes, and the UID can be either 4 or 7 bytes - furi_assert(uid_len == 4 || uid_len == 7); - furi_assert(block); - - if(uid_len == 4) { - // Calculate BCC - block[uid_len] = 0; - - for(int i = 0; i < uid_len; i++) { - block[uid_len] ^= block[i]; - } - } else { - uid_len -= 1; - } - - block[uid_len + 1] = sak; - block[uid_len + 2] = atqa0; - block[uid_len + 3] = atqa1; - - for(int i = uid_len + 4; i < 16; i++) { - block[i] = 0xFF; - } -} - -static void nfc_generate_mf_classic_sector_trailer(MfClassicData* data, uint8_t block) { - // All keys are set to FFFF FFFF FFFFh at chip delivery and the bytes 6, 7 and 8 are set to FF0780h. - MfClassicSectorTrailer* sec_tr = (MfClassicSectorTrailer*)data->block[block].value; - sec_tr->access_bits[0] = 0xFF; - sec_tr->access_bits[1] = 0x07; - sec_tr->access_bits[2] = 0x80; - sec_tr->access_bits[3] = 0x69; // Nice - - memset(sec_tr->key_a, 0xff, sizeof(sec_tr->key_a)); - memset(sec_tr->key_b, 0xff, sizeof(sec_tr->key_b)); - - mf_classic_set_block_read(data, block, &data->block[block]); - mf_classic_set_key_found( - data, mf_classic_get_sector_by_block(block), MfClassicKeyA, 0xFFFFFFFFFFFF); - mf_classic_set_key_found( - data, mf_classic_get_sector_by_block(block), MfClassicKeyB, 0xFFFFFFFFFFFF); -} - -static void nfc_generate_mf_ul_common(NfcDeviceData* data) { - data->nfc_data.type = FuriHalNfcTypeA; - data->nfc_data.interface = FuriHalNfcInterfaceRf; - data->nfc_data.uid_len = 7; - nfc_generate_mf_ul_uid(data->nfc_data.uid); - data->nfc_data.atqa[0] = 0x44; - data->nfc_data.atqa[1] = 0x00; - data->nfc_data.sak = 0x00; - data->protocol = NfcDeviceProtocolMifareUl; -} - -static void - nfc_generate_mf_classic_common(NfcDeviceData* data, uint8_t uid_len, MfClassicType type) { - data->nfc_data.type = FuriHalNfcTypeA; - data->nfc_data.interface = FuriHalNfcInterfaceRf; - data->nfc_data.uid_len = uid_len; - data->nfc_data.atqa[0] = 0x44; - data->nfc_data.atqa[1] = 0x00; - data->nfc_data.sak = 0x08; - data->protocol = NfcDeviceProtocolMifareClassic; - data->mf_classic_data.type = type; -} - -static void nfc_generate_calc_bcc(uint8_t* uid, uint8_t* bcc0, uint8_t* bcc1) { - *bcc0 = 0x88 ^ uid[0] ^ uid[1] ^ uid[2]; - *bcc1 = uid[3] ^ uid[4] ^ uid[5] ^ uid[6]; -} - -static void nfc_generate_mf_ul_copy_uid_with_bcc(NfcDeviceData* data) { - MfUltralightData* mful = &data->mf_ul_data; - memcpy(mful->data, data->nfc_data.uid, 3); - memcpy(&mful->data[4], &data->nfc_data.uid[3], 4); - nfc_generate_calc_bcc(data->nfc_data.uid, &mful->data[3], &mful->data[8]); -} - -static void nfc_generate_mf_ul_orig(NfcDeviceData* data) { - nfc_generate_common_start(data); - nfc_generate_mf_ul_common(data); - - MfUltralightData* mful = &data->mf_ul_data; - mful->type = MfUltralightTypeUnknown; - mful->data_size = 16 * 4; - mful->data_read = mful->data_size; - nfc_generate_mf_ul_copy_uid_with_bcc(data); - // TODO: what's internal byte on page 2? - memset(&mful->data[4 * 4], 0xFF, 4); -} - -static void nfc_generate_mf_ul_ntag203(NfcDeviceData* data) { - nfc_generate_common_start(data); - nfc_generate_mf_ul_common(data); - - MfUltralightData* mful = &data->mf_ul_data; - mful->type = MfUltralightTypeNTAG203; - mful->data_size = 42 * 4; - mful->data_read = mful->data_size; - nfc_generate_mf_ul_copy_uid_with_bcc(data); - mful->data[9] = 0x48; // Internal byte - memcpy(&mful->data[3 * 4], default_data_ntag203, sizeof(default_data_ntag203)); -} - -static void nfc_generate_mf_ul_with_config_common(NfcDeviceData* data, uint8_t num_pages) { - nfc_generate_common_start(data); - nfc_generate_mf_ul_common(data); - - MfUltralightData* mful = &data->mf_ul_data; - mful->data_size = num_pages * 4; - mful->data_read = mful->data_size; - nfc_generate_mf_ul_copy_uid_with_bcc(data); - uint16_t config_index = (num_pages - 4) * 4; - mful->data[config_index] = 0x04; // STRG_MOD_EN - mful->data[config_index + 3] = 0xFF; // AUTH0 - mful->data[config_index + 5] = 0x05; // VCTID - memset(&mful->data[config_index + 8], 0xFF, 4); // Default PWD - if(num_pages > 20) mful->data[config_index - 1] = MF_UL_TEARING_FLAG_DEFAULT; -} - -static void nfc_generate_mf_ul_ev1_common(NfcDeviceData* data, uint8_t num_pages) { - nfc_generate_mf_ul_with_config_common(data, num_pages); - MfUltralightData* mful = &data->mf_ul_data; - memcpy(&mful->version, version_bytes_mf0ulx1, sizeof(version_bytes_mf0ulx1)); - for(size_t i = 0; i < 3; ++i) { - mful->tearing[i] = MF_UL_TEARING_FLAG_DEFAULT; - } - // TODO: what's internal byte on page 2? -} - -static void nfc_generate_mf_ul_11(NfcDeviceData* data) { - nfc_generate_mf_ul_ev1_common(data, 20); - MfUltralightData* mful = &data->mf_ul_data; - mful->type = MfUltralightTypeUL11; - mful->version.prod_subtype = 0x01; - mful->version.storage_size = 0x0B; - mful->data[16 * 4] = 0x00; // Low capacitance version does not have STRG_MOD_EN -} - -static void nfc_generate_mf_ul_h11(NfcDeviceData* data) { - nfc_generate_mf_ul_ev1_common(data, 20); - MfUltralightData* mful = &data->mf_ul_data; - mful->type = MfUltralightTypeUL11; - mful->version.prod_subtype = 0x02; - mful->version.storage_size = 0x0B; -} - -static void nfc_generate_mf_ul_21(NfcDeviceData* data) { - nfc_generate_mf_ul_ev1_common(data, 41); - MfUltralightData* mful = &data->mf_ul_data; - mful->type = MfUltralightTypeUL21; - mful->version.prod_subtype = 0x01; - mful->version.storage_size = 0x0E; - mful->data[37 * 4] = 0x00; // Low capacitance version does not have STRG_MOD_EN -} - -static void nfc_generate_mf_ul_h21(NfcDeviceData* data) { - nfc_generate_mf_ul_ev1_common(data, 41); - MfUltralightData* mful = &data->mf_ul_data; - mful->type = MfUltralightTypeUL21; - mful->version.prod_subtype = 0x02; - mful->version.storage_size = 0x0E; -} - -static void nfc_generate_ntag21x_common(NfcDeviceData* data, uint8_t num_pages) { - nfc_generate_mf_ul_with_config_common(data, num_pages); - MfUltralightData* mful = &data->mf_ul_data; - memcpy(&mful->version, version_bytes_ntag21x, sizeof(version_bytes_mf0ulx1)); - mful->data[9] = 0x48; // Internal byte - // Capability container - mful->data[12] = 0xE1; - mful->data[13] = 0x10; -} - -static void nfc_generate_ntag213(NfcDeviceData* data) { - nfc_generate_ntag21x_common(data, 45); - MfUltralightData* mful = &data->mf_ul_data; - mful->type = MfUltralightTypeNTAG213; - mful->version.storage_size = 0x0F; - mful->data[14] = 0x12; - // Default contents - memcpy(&mful->data[16], default_data_ntag213, sizeof(default_data_ntag213)); -} - -static void nfc_generate_ntag215(NfcDeviceData* data) { - nfc_generate_ntag21x_common(data, 135); - MfUltralightData* mful = &data->mf_ul_data; - mful->type = MfUltralightTypeNTAG215; - mful->version.storage_size = 0x11; - mful->data[14] = 0x3E; - // Default contents - memcpy(&mful->data[16], default_data_ntag215_216, sizeof(default_data_ntag215_216)); -} - -static void nfc_generate_ntag216(NfcDeviceData* data) { - nfc_generate_ntag21x_common(data, 231); - MfUltralightData* mful = &data->mf_ul_data; - mful->type = MfUltralightTypeNTAG216; - mful->version.storage_size = 0x13; - mful->data[14] = 0x6D; - // Default contents - memcpy(&mful->data[16], default_data_ntag215_216, sizeof(default_data_ntag215_216)); -} - -static void - nfc_generate_ntag_i2c_common(NfcDeviceData* data, MfUltralightType type, uint16_t num_pages) { - nfc_generate_common_start(data); - nfc_generate_mf_ul_common(data); - - MfUltralightData* mful = &data->mf_ul_data; - mful->type = type; - memcpy(&mful->version, version_bytes_ntag_i2c, sizeof(version_bytes_ntag_i2c)); - mful->data_size = num_pages * 4; - mful->data_read = mful->data_size; - memcpy(mful->data, data->nfc_data.uid, data->nfc_data.uid_len); - mful->data[7] = data->nfc_data.sak; - mful->data[8] = data->nfc_data.atqa[0]; - mful->data[9] = data->nfc_data.atqa[1]; - - uint16_t config_register_page; - uint16_t session_register_page; - - // Sync with mifare_ultralight.c - switch(type) { - case MfUltralightTypeNTAGI2C1K: - config_register_page = 227; - session_register_page = 229; - break; - case MfUltralightTypeNTAGI2C2K: - config_register_page = 481; - session_register_page = 483; - break; - case MfUltralightTypeNTAGI2CPlus1K: - case MfUltralightTypeNTAGI2CPlus2K: - config_register_page = 232; - session_register_page = 234; - break; - default: - furi_crash("Unknown MFUL"); - break; - } - - memcpy( - &mful->data[config_register_page * 4], - default_config_ntag_i2c, - sizeof(default_config_ntag_i2c)); - memcpy( - &mful->data[session_register_page * 4], - default_config_ntag_i2c, - sizeof(default_config_ntag_i2c)); -} - -static void nfc_generate_ntag_i2c_1k(NfcDeviceData* data) { - nfc_generate_ntag_i2c_common(data, MfUltralightTypeNTAGI2C1K, 231); - MfUltralightData* mful = &data->mf_ul_data; - mful->version.prod_ver_minor = 0x01; - mful->version.storage_size = 0x13; - - memcpy(&mful->data[12], default_data_ntag_i2c, sizeof(default_data_ntag_i2c)); - mful->data[14] = 0x6D; // Size of tag in CC -} - -static void nfc_generate_ntag_i2c_2k(NfcDeviceData* data) { - nfc_generate_ntag_i2c_common(data, MfUltralightTypeNTAGI2C2K, 485); - MfUltralightData* mful = &data->mf_ul_data; - mful->version.prod_ver_minor = 0x01; - mful->version.storage_size = 0x15; - - memcpy(&mful->data[12], default_data_ntag_i2c, sizeof(default_data_ntag_i2c)); - mful->data[14] = 0xEA; // Size of tag in CC -} - -static void nfc_generate_ntag_i2c_plus_common( - NfcDeviceData* data, - MfUltralightType type, - uint16_t num_pages) { - nfc_generate_ntag_i2c_common(data, type, num_pages); - - MfUltralightData* mful = &data->mf_ul_data; - uint16_t config_index = 227 * 4; - mful->data[config_index + 3] = 0xFF; // AUTH0 - memset(&mful->data[config_index + 8], 0xFF, 4); // Default PWD -} - -static void nfc_generate_ntag_i2c_plus_1k(NfcDeviceData* data) { - nfc_generate_ntag_i2c_plus_common(data, MfUltralightTypeNTAGI2CPlus1K, 236); - MfUltralightData* mful = &data->mf_ul_data; - mful->version.prod_ver_minor = 0x02; - mful->version.storage_size = 0x13; -} - -static void nfc_generate_ntag_i2c_plus_2k(NfcDeviceData* data) { - nfc_generate_ntag_i2c_plus_common(data, MfUltralightTypeNTAGI2CPlus2K, 492); - MfUltralightData* mful = &data->mf_ul_data; - mful->version.prod_ver_minor = 0x02; - mful->version.storage_size = 0x15; -} - -void nfc_generate_mf_classic(NfcDeviceData* data, uint8_t uid_len, MfClassicType type) { - nfc_generate_common_start(data); - nfc_generate_mf_classic_uid(data->mf_classic_data.block[0].value, uid_len); - nfc_generate_mf_classic_common(data, uid_len, type); - - // Set the UID - data->nfc_data.uid[0] = NXP_MANUFACTURER_ID; - for(int i = 1; i < uid_len; i++) { - data->nfc_data.uid[i] = data->mf_classic_data.block[0].value[i]; - } - - MfClassicData* mfc = &data->mf_classic_data; - mf_classic_set_block_read(mfc, 0, &mfc->block[0]); - - if(type == MfClassicType4k) { - // Set every block to 0xFF - for(uint16_t i = 1; i < 256; i += 1) { - if(mf_classic_is_sector_trailer(i)) { - nfc_generate_mf_classic_sector_trailer(mfc, i); - } else { - memset(&mfc->block[i].value, 0xFF, 16); - } - mf_classic_set_block_read(mfc, i, &mfc->block[i]); - } - // Set SAK to 18 - data->nfc_data.sak = 0x18; - } else if(type == MfClassicType1k) { - // Set every block to 0xFF - for(uint16_t i = 1; i < MF_CLASSIC_1K_TOTAL_SECTORS_NUM * 4; i += 1) { - if(mf_classic_is_sector_trailer(i)) { - nfc_generate_mf_classic_sector_trailer(mfc, i); - } else { - memset(&mfc->block[i].value, 0xFF, 16); - } - mf_classic_set_block_read(mfc, i, &mfc->block[i]); - } - // Set SAK to 08 - data->nfc_data.sak = 0x08; - } else if(type == MfClassicTypeMini) { - // Set every block to 0xFF - for(uint16_t i = 1; i < MF_MINI_TOTAL_SECTORS_NUM * 4; i += 1) { - if(mf_classic_is_sector_trailer(i)) { - nfc_generate_mf_classic_sector_trailer(mfc, i); - } else { - memset(&mfc->block[i].value, 0xFF, 16); - } - mf_classic_set_block_read(mfc, i, &mfc->block[i]); - } - // Set SAK to 09 - data->nfc_data.sak = 0x09; - } - - nfc_generate_mf_classic_block_0( - data->mf_classic_data.block[0].value, - uid_len, - data->nfc_data.sak, - data->nfc_data.atqa[0], - data->nfc_data.atqa[1]); - - mfc->type = type; -} - -static void nfc_generate_mf_mini(NfcDeviceData* data) { - nfc_generate_mf_classic(data, 4, MfClassicTypeMini); -} - -static void nfc_generate_mf_classic_1k_4b_uid(NfcDeviceData* data) { - nfc_generate_mf_classic(data, 4, MfClassicType1k); -} - -static void nfc_generate_mf_classic_1k_7b_uid(NfcDeviceData* data) { - nfc_generate_mf_classic(data, 7, MfClassicType1k); -} - -static void nfc_generate_mf_classic_4k_4b_uid(NfcDeviceData* data) { - nfc_generate_mf_classic(data, 4, MfClassicType4k); -} - -static void nfc_generate_mf_classic_4k_7b_uid(NfcDeviceData* data) { - nfc_generate_mf_classic(data, 7, MfClassicType4k); -} - -static const NfcGenerator mf_ul_generator = { - .name = "Mifare Ultralight", - .generator_func = nfc_generate_mf_ul_orig, -}; - -static const NfcGenerator mf_ul_11_generator = { - .name = "Mifare Ultralight EV1 11", - .generator_func = nfc_generate_mf_ul_11, -}; - -static const NfcGenerator mf_ul_h11_generator = { - .name = "Mifare Ultralight EV1 H11", - .generator_func = nfc_generate_mf_ul_h11, -}; - -static const NfcGenerator mf_ul_21_generator = { - .name = "Mifare Ultralight EV1 21", - .generator_func = nfc_generate_mf_ul_21, -}; - -static const NfcGenerator mf_ul_h21_generator = { - .name = "Mifare Ultralight EV1 H21", - .generator_func = nfc_generate_mf_ul_h21, -}; - -static const NfcGenerator ntag203_generator = { - .name = "NTAG203", - .generator_func = nfc_generate_mf_ul_ntag203, -}; - -static const NfcGenerator ntag213_generator = { - .name = "NTAG213", - .generator_func = nfc_generate_ntag213, -}; - -static const NfcGenerator ntag215_generator = { - .name = "NTAG215", - .generator_func = nfc_generate_ntag215, -}; - -static const NfcGenerator ntag216_generator = { - .name = "NTAG216", - .generator_func = nfc_generate_ntag216, -}; - -static const NfcGenerator ntag_i2c_1k_generator = { - .name = "NTAG I2C 1k", - .generator_func = nfc_generate_ntag_i2c_1k, -}; - -static const NfcGenerator ntag_i2c_2k_generator = { - .name = "NTAG I2C 2k", - .generator_func = nfc_generate_ntag_i2c_2k, -}; - -static const NfcGenerator ntag_i2c_plus_1k_generator = { - .name = "NTAG I2C Plus 1k", - .generator_func = nfc_generate_ntag_i2c_plus_1k, -}; - -static const NfcGenerator ntag_i2c_plus_2k_generator = { - .name = "NTAG I2C Plus 2k", - .generator_func = nfc_generate_ntag_i2c_plus_2k, -}; - -static const NfcGenerator mifare_mini_generator = { - .name = "Mifare Mini", - .generator_func = nfc_generate_mf_mini, -}; - -static const NfcGenerator mifare_classic_1k_4b_uid_generator = { - .name = "Mifare Classic 1k 4byte UID", - .generator_func = nfc_generate_mf_classic_1k_4b_uid, -}; - -static const NfcGenerator mifare_classic_1k_7b_uid_generator = { - .name = "Mifare Classic 1k 7byte UID", - .generator_func = nfc_generate_mf_classic_1k_7b_uid, -}; - -static const NfcGenerator mifare_classic_4k_4b_uid_generator = { - .name = "Mifare Classic 4k 4byte UID", - .generator_func = nfc_generate_mf_classic_4k_4b_uid, -}; - -static const NfcGenerator mifare_classic_4k_7b_uid_generator = { - .name = "Mifare Classic 4k 7byte UID", - .generator_func = nfc_generate_mf_classic_4k_7b_uid, -}; - -const NfcGenerator* const nfc_generators[] = { - &mf_ul_generator, - &mf_ul_11_generator, - &mf_ul_h11_generator, - &mf_ul_21_generator, - &mf_ul_h21_generator, - &ntag203_generator, - &ntag213_generator, - &ntag215_generator, - &ntag216_generator, - &ntag_i2c_1k_generator, - &ntag_i2c_2k_generator, - &ntag_i2c_plus_1k_generator, - &ntag_i2c_plus_2k_generator, - &mifare_mini_generator, - &mifare_classic_1k_4b_uid_generator, - &mifare_classic_1k_7b_uid_generator, - &mifare_classic_4k_4b_uid_generator, - &mifare_classic_4k_7b_uid_generator, - NULL, -}; diff --git a/lib/nfc/helpers/nfc_generators.h b/lib/nfc/helpers/nfc_generators.h deleted file mode 100644 index 8cee67067ca..00000000000 --- a/lib/nfc/helpers/nfc_generators.h +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - -#include "../nfc_device.h" - -typedef void (*NfcGeneratorFunc)(NfcDeviceData* data); - -typedef struct { - const char* name; - NfcGeneratorFunc generator_func; -} NfcGenerator; - -extern const NfcGenerator* const nfc_generators[]; - -void nfc_generate_mf_classic(NfcDeviceData* data, uint8_t uid_len, MfClassicType type); diff --git a/lib/nfc/protocols/nfc_util.c b/lib/nfc/helpers/nfc_util.c similarity index 100% rename from lib/nfc/protocols/nfc_util.c rename to lib/nfc/helpers/nfc_util.c diff --git a/lib/nfc/protocols/nfc_util.h b/lib/nfc/helpers/nfc_util.h similarity index 100% rename from lib/nfc/protocols/nfc_util.h rename to lib/nfc/helpers/nfc_util.h diff --git a/lib/nfc/helpers/reader_analyzer.c b/lib/nfc/helpers/reader_analyzer.c deleted file mode 100644 index 9bf37a60d29..00000000000 --- a/lib/nfc/helpers/reader_analyzer.c +++ /dev/null @@ -1,265 +0,0 @@ -#include "reader_analyzer.h" -#include -#include -#include - -#include "mfkey32.h" -#include "nfc_debug_pcap.h" -#include "nfc_debug_log.h" - -#define TAG "ReaderAnalyzer" - -#define READER_ANALYZER_MAX_BUFF_SIZE (1024) - -typedef struct { - bool reader_to_tag; - bool crc_dropped; - uint16_t len; -} ReaderAnalyzerHeader; - -typedef enum { - ReaderAnalyzerNfcDataMfClassic, -} ReaderAnalyzerNfcData; - -struct ReaderAnalyzer { - FuriHalNfcDevData nfc_data; - - bool alive; - FuriStreamBuffer* stream; - FuriThread* thread; - - ReaderAnalyzerParseDataCallback callback; - void* context; - - ReaderAnalyzerMode mode; - Mfkey32* mfkey32; - NfcDebugLog* debug_log; - NfcDebugPcap* pcap; -}; - -const FuriHalNfcDevData reader_analyzer_nfc_data[] = { - [ReaderAnalyzerNfcDataMfClassic] = - {.sak = 0x08, - .atqa = {0x44, 0x00}, - .interface = FuriHalNfcInterfaceRf, - .type = FuriHalNfcTypeA, - .uid_len = 7, - .uid = {0x04, 0x77, 0x70, 0x2A, 0x23, 0x4F, 0x80}, - .cuid = 0x2A234F80}, -}; - -void reader_analyzer_parse(ReaderAnalyzer* instance, uint8_t* buffer, size_t size) { - if(size < sizeof(ReaderAnalyzerHeader)) return; - - size_t bytes_i = 0; - while(bytes_i < size) { - ReaderAnalyzerHeader* header = (ReaderAnalyzerHeader*)&buffer[bytes_i]; - uint16_t len = header->len; - if(bytes_i + len > size) break; - bytes_i += sizeof(ReaderAnalyzerHeader); - if(instance->mfkey32) { - mfkey32_process_data( - instance->mfkey32, - &buffer[bytes_i], - len, - header->reader_to_tag, - header->crc_dropped); - } - if(instance->pcap) { - nfc_debug_pcap_process_data( - instance->pcap, &buffer[bytes_i], len, header->reader_to_tag, header->crc_dropped); - } - if(instance->debug_log) { - nfc_debug_log_process_data( - instance->debug_log, - &buffer[bytes_i], - len, - header->reader_to_tag, - header->crc_dropped); - } - bytes_i += len; - } -} - -int32_t reader_analyzer_thread(void* context) { - ReaderAnalyzer* reader_analyzer = context; - uint8_t buffer[READER_ANALYZER_MAX_BUFF_SIZE] = {}; - - while(reader_analyzer->alive || !furi_stream_buffer_is_empty(reader_analyzer->stream)) { - size_t ret = furi_stream_buffer_receive( - reader_analyzer->stream, buffer, READER_ANALYZER_MAX_BUFF_SIZE, 50); - if(ret) { - reader_analyzer_parse(reader_analyzer, buffer, ret); - } - } - - return 0; -} - -ReaderAnalyzer* reader_analyzer_alloc() { - ReaderAnalyzer* instance = malloc(sizeof(ReaderAnalyzer)); - - instance->nfc_data = reader_analyzer_nfc_data[ReaderAnalyzerNfcDataMfClassic]; - instance->alive = false; - instance->stream = - furi_stream_buffer_alloc(READER_ANALYZER_MAX_BUFF_SIZE, sizeof(ReaderAnalyzerHeader)); - - instance->thread = - furi_thread_alloc_ex("ReaderAnalyzerWorker", 2048, reader_analyzer_thread, instance); - furi_thread_set_priority(instance->thread, FuriThreadPriorityLow); - - return instance; -} - -static void reader_analyzer_mfkey_callback(Mfkey32Event event, void* context) { - furi_assert(context); - ReaderAnalyzer* instance = context; - - if(event == Mfkey32EventParamCollected) { - if(instance->callback) { - instance->callback(ReaderAnalyzerEventMfkeyCollected, instance->context); - } - } -} - -void reader_analyzer_start(ReaderAnalyzer* instance, ReaderAnalyzerMode mode) { - furi_assert(instance); - - furi_stream_buffer_reset(instance->stream); - if(mode & ReaderAnalyzerModeDebugLog) { - instance->debug_log = nfc_debug_log_alloc(); - } - if(mode & ReaderAnalyzerModeMfkey) { - instance->mfkey32 = mfkey32_alloc(instance->nfc_data.cuid); - if(instance->mfkey32) { - mfkey32_set_callback(instance->mfkey32, reader_analyzer_mfkey_callback, instance); - } - } - if(mode & ReaderAnalyzerModeDebugPcap) { - instance->pcap = nfc_debug_pcap_alloc(); - } - - instance->alive = true; - furi_thread_start(instance->thread); -} - -void reader_analyzer_stop(ReaderAnalyzer* instance) { - furi_assert(instance); - - instance->alive = false; - furi_thread_join(instance->thread); - - if(instance->debug_log) { - nfc_debug_log_free(instance->debug_log); - instance->debug_log = NULL; - } - if(instance->mfkey32) { - mfkey32_free(instance->mfkey32); - instance->mfkey32 = NULL; - } - if(instance->pcap) { - nfc_debug_pcap_free(instance->pcap); - instance->pcap = NULL; - } -} - -void reader_analyzer_free(ReaderAnalyzer* instance) { - furi_assert(instance); - - reader_analyzer_stop(instance); - furi_thread_free(instance->thread); - furi_stream_buffer_free(instance->stream); - free(instance); -} - -void reader_analyzer_set_callback( - ReaderAnalyzer* instance, - ReaderAnalyzerParseDataCallback callback, - void* context) { - furi_assert(instance); - furi_assert(callback); - - instance->callback = callback; - instance->context = context; -} - -NfcProtocol - reader_analyzer_guess_protocol(ReaderAnalyzer* instance, uint8_t* buff_rx, uint16_t len) { - furi_assert(instance); - furi_assert(buff_rx); - UNUSED(len); - NfcProtocol protocol = NfcDeviceProtocolUnknown; - - if((buff_rx[0] == 0x60) || (buff_rx[0] == 0x61)) { - protocol = NfcDeviceProtocolMifareClassic; - } - - return protocol; -} - -FuriHalNfcDevData* reader_analyzer_get_nfc_data(ReaderAnalyzer* instance) { - furi_assert(instance); - instance->nfc_data = reader_analyzer_nfc_data[ReaderAnalyzerNfcDataMfClassic]; - return &instance->nfc_data; -} - -void reader_analyzer_set_nfc_data(ReaderAnalyzer* instance, FuriHalNfcDevData* nfc_data) { - furi_assert(instance); - furi_assert(nfc_data); - - memcpy(&instance->nfc_data, nfc_data, sizeof(FuriHalNfcDevData)); -} - -static void reader_analyzer_write( - ReaderAnalyzer* instance, - uint8_t* data, - uint16_t len, - bool reader_to_tag, - bool crc_dropped) { - ReaderAnalyzerHeader header = { - .reader_to_tag = reader_to_tag, .crc_dropped = crc_dropped, .len = len}; - size_t data_sent = 0; - data_sent = furi_stream_buffer_send( - instance->stream, &header, sizeof(ReaderAnalyzerHeader), FuriWaitForever); - if(data_sent != sizeof(ReaderAnalyzerHeader)) { - FURI_LOG_W(TAG, "Sent %zu out of %zu bytes", data_sent, sizeof(ReaderAnalyzerHeader)); - } - data_sent = furi_stream_buffer_send(instance->stream, data, len, FuriWaitForever); - if(data_sent != len) { - FURI_LOG_W(TAG, "Sent %zu out of %u bytes", data_sent, len); - } -} - -static void - reader_analyzer_write_rx(uint8_t* data, uint16_t bits, bool crc_dropped, void* context) { - UNUSED(crc_dropped); - ReaderAnalyzer* reader_analyzer = context; - uint16_t bytes = bits < 8 ? 1 : bits / 8; - reader_analyzer_write(reader_analyzer, data, bytes, false, crc_dropped); -} - -static void - reader_analyzer_write_tx(uint8_t* data, uint16_t bits, bool crc_dropped, void* context) { - UNUSED(crc_dropped); - ReaderAnalyzer* reader_analyzer = context; - uint16_t bytes = bits < 8 ? 1 : bits / 8; - reader_analyzer_write(reader_analyzer, data, bytes, true, crc_dropped); -} - -void reader_analyzer_prepare_tx_rx( - ReaderAnalyzer* instance, - FuriHalNfcTxRxContext* tx_rx, - bool is_picc) { - furi_assert(instance); - furi_assert(tx_rx); - - if(is_picc) { - tx_rx->sniff_tx = reader_analyzer_write_rx; - tx_rx->sniff_rx = reader_analyzer_write_tx; - } else { - tx_rx->sniff_rx = reader_analyzer_write_rx; - tx_rx->sniff_tx = reader_analyzer_write_tx; - } - - tx_rx->sniff_context = instance; -} diff --git a/lib/nfc/helpers/reader_analyzer.h b/lib/nfc/helpers/reader_analyzer.h deleted file mode 100644 index 13bf4d77ccf..00000000000 --- a/lib/nfc/helpers/reader_analyzer.h +++ /dev/null @@ -1,43 +0,0 @@ -#pragma once - -#include -#include - -typedef enum { - ReaderAnalyzerModeDebugLog = 0x01, - ReaderAnalyzerModeMfkey = 0x02, - ReaderAnalyzerModeDebugPcap = 0x04, -} ReaderAnalyzerMode; - -typedef enum { - ReaderAnalyzerEventMfkeyCollected, -} ReaderAnalyzerEvent; - -typedef struct ReaderAnalyzer ReaderAnalyzer; - -typedef void (*ReaderAnalyzerParseDataCallback)(ReaderAnalyzerEvent event, void* context); - -ReaderAnalyzer* reader_analyzer_alloc(); - -void reader_analyzer_free(ReaderAnalyzer* instance); - -void reader_analyzer_set_callback( - ReaderAnalyzer* instance, - ReaderAnalyzerParseDataCallback callback, - void* context); - -void reader_analyzer_start(ReaderAnalyzer* instance, ReaderAnalyzerMode mode); - -void reader_analyzer_stop(ReaderAnalyzer* instance); - -NfcProtocol - reader_analyzer_guess_protocol(ReaderAnalyzer* instance, uint8_t* buff_rx, uint16_t len); - -FuriHalNfcDevData* reader_analyzer_get_nfc_data(ReaderAnalyzer* instance); - -void reader_analyzer_set_nfc_data(ReaderAnalyzer* instance, FuriHalNfcDevData* nfc_data); - -void reader_analyzer_prepare_tx_rx( - ReaderAnalyzer* instance, - FuriHalNfcTxRxContext* tx_rx, - bool is_picc); diff --git a/lib/nfc/nfc.c b/lib/nfc/nfc.c new file mode 100644 index 00000000000..a7c4fe1a6d4 --- /dev/null +++ b/lib/nfc/nfc.c @@ -0,0 +1,649 @@ +#ifndef FW_CFG_unit_tests + +#include "nfc.h" + +#include +#include + +#define TAG "Nfc" + +#define NFC_MAX_BUFFER_SIZE (256) + +typedef enum { + NfcStateIdle, + NfcStateRunning, +} NfcState; + +typedef enum { + NfcPollerStateStart, + NfcPollerStateReady, + NfcPollerStateReset, + NfcPollerStateStop, + + NfcPollerStateNum, +} NfcPollerState; + +typedef enum { + NfcCommStateIdle, + NfcCommStateWaitBlockTxTimer, + NfcCommStateReadyTx, + NfcCommStateWaitTxEnd, + NfcCommStateWaitRxStart, + NfcCommStateWaitRxEnd, + NfcCommStateFailed, +} NfcCommState; + +typedef enum { + NfcConfigurationStateIdle, + NfcConfigurationStateDone, +} NfcConfigurationState; + +struct Nfc { + NfcState state; + NfcPollerState poller_state; + NfcCommState comm_state; + NfcConfigurationState config_state; + NfcMode mode; + + uint32_t fdt_listen_fc; + uint32_t mask_rx_time_fc; + uint32_t fdt_poll_fc; + uint32_t fdt_poll_poll_us; + uint32_t guard_time_us; + NfcEventCallback callback; + void* context; + + uint8_t tx_buffer[NFC_MAX_BUFFER_SIZE]; + size_t tx_bits; + uint8_t rx_buffer[NFC_MAX_BUFFER_SIZE]; + size_t rx_bits; + + FuriThread* worker_thread; +}; + +typedef bool (*NfcWorkerPollerStateHandler)(Nfc* instance); + +static const FuriHalNfcTech nfc_tech_table[NfcModeNum][NfcTechNum] = { + [NfcModePoller] = + { + [NfcTechIso14443a] = FuriHalNfcTechIso14443a, + [NfcTechIso14443b] = FuriHalNfcTechIso14443b, + [NfcTechIso15693] = FuriHalNfcTechIso15693, + [NfcTechFelica] = FuriHalNfcTechFelica, + }, + [NfcModeListener] = + { + [NfcTechIso14443a] = FuriHalNfcTechIso14443a, + [NfcTechIso14443b] = FuriHalNfcTechInvalid, + [NfcTechIso15693] = FuriHalNfcTechIso15693, + [NfcTechFelica] = FuriHalNfcTechInvalid, + }, +}; + +static NfcError nfc_process_hal_error(FuriHalNfcError error) { + NfcError ret = NfcErrorNone; + + switch(error) { + case FuriHalNfcErrorNone: + ret = NfcErrorNone; + break; + case FuriHalNfcErrorIncompleteFrame: + ret = NfcErrorIncompleteFrame; + break; + case FuriHalNfcErrorDataFormat: + ret = NfcErrorDataFormat; + break; + + default: + ret = NfcErrorInternal; + } + + return ret; +} + +static int32_t nfc_worker_listener(void* context) { + furi_assert(context); + + Nfc* instance = context; + furi_assert(instance->callback); + furi_assert(instance->config_state == NfcConfigurationStateDone); + + instance->state = NfcStateRunning; + + furi_hal_nfc_event_start(); + + NfcEventData event_data = {}; + event_data.buffer = bit_buffer_alloc(NFC_MAX_BUFFER_SIZE); + NfcEvent nfc_event = {.data = event_data}; + NfcCommand command = NfcCommandContinue; + + while(true) { + FuriHalNfcEvent event = furi_hal_nfc_listener_wait_event(FURI_HAL_NFC_EVENT_WAIT_FOREVER); + if(event & FuriHalNfcEventAbortRequest) { + nfc_event.type = NfcEventTypeUserAbort; + instance->callback(nfc_event, instance->context); + break; + } + if(event & FuriHalNfcEventFieldOn) { + nfc_event.type = NfcEventTypeFieldOn; + instance->callback(nfc_event, instance->context); + } + if(event & FuriHalNfcEventFieldOff) { + nfc_event.type = NfcEventTypeFieldOff; + instance->callback(nfc_event, instance->context); + furi_hal_nfc_listener_idle(); + } + if(event & FuriHalNfcEventListenerActive) { + nfc_event.type = NfcEventTypeListenerActivated; + instance->callback(nfc_event, instance->context); + } + if(event & FuriHalNfcEventRxEnd) { + furi_hal_nfc_timer_block_tx_start(instance->fdt_listen_fc); + + nfc_event.type = NfcEventTypeRxEnd; + furi_hal_nfc_listener_rx( + instance->rx_buffer, sizeof(instance->rx_buffer), &instance->rx_bits); + bit_buffer_copy_bits(event_data.buffer, instance->rx_buffer, instance->rx_bits); + command = instance->callback(nfc_event, instance->context); + if(command == NfcCommandStop) { + break; + } else if(command == NfcCommandReset) { + furi_hal_nfc_listener_enable_rx(); + } else if(command == NfcCommandSleep) { + furi_hal_nfc_listener_sleep(); + } + } + } + + furi_hal_nfc_reset_mode(); + instance->config_state = NfcConfigurationStateIdle; + + bit_buffer_free(event_data.buffer); + furi_hal_nfc_low_power_mode_start(); + return 0; +} + +bool nfc_worker_poller_start_handler(Nfc* instance) { + furi_hal_nfc_poller_field_on(); + if(instance->guard_time_us) { + furi_hal_nfc_timer_block_tx_start_us(instance->guard_time_us); + FuriHalNfcEvent event = furi_hal_nfc_poller_wait_event(FURI_HAL_NFC_EVENT_WAIT_FOREVER); + furi_assert(event & FuriHalNfcEventTimerBlockTxExpired); + } + instance->poller_state = NfcPollerStateReady; + + return false; +} + +bool nfc_worker_poller_ready_handler(Nfc* instance) { + NfcCommand command = NfcCommandContinue; + + NfcEvent event = {.type = NfcEventTypePollerReady}; + command = instance->callback(event, instance->context); + if(command == NfcCommandReset) { + instance->poller_state = NfcPollerStateReset; + } else if(command == NfcCommandStop) { + instance->poller_state = NfcPollerStateStop; + } + + return false; +} + +bool nfc_worker_poller_reset_handler(Nfc* instance) { + furi_hal_nfc_low_power_mode_start(); + furi_delay_ms(100); + furi_hal_nfc_low_power_mode_stop(); + instance->poller_state = NfcPollerStateStart; + + return false; +} + +bool nfc_worker_poller_stop_handler(Nfc* instance) { + furi_hal_nfc_reset_mode(); + instance->config_state = NfcConfigurationStateIdle; + + furi_hal_nfc_low_power_mode_start(); + // Wait after field is off some time to reset tag power + furi_delay_ms(10); + instance->poller_state = NfcPollerStateStart; + + return true; +} + +static const NfcWorkerPollerStateHandler nfc_worker_poller_state_handlers[NfcPollerStateNum] = { + [NfcPollerStateStart] = nfc_worker_poller_start_handler, + [NfcPollerStateReady] = nfc_worker_poller_ready_handler, + [NfcPollerStateReset] = nfc_worker_poller_reset_handler, + [NfcPollerStateStop] = nfc_worker_poller_stop_handler, +}; + +static int32_t nfc_worker_poller(void* context) { + furi_assert(context); + + Nfc* instance = context; + furi_assert(instance->callback); + instance->state = NfcStateRunning; + instance->poller_state = NfcPollerStateStart; + + furi_hal_nfc_event_start(); + + bool exit = false; + while(!exit) { + exit = nfc_worker_poller_state_handlers[instance->poller_state](instance); + } + + return 0; +} + +Nfc* nfc_alloc() { + furi_check(furi_hal_nfc_acquire() == FuriHalNfcErrorNone); + + Nfc* instance = malloc(sizeof(Nfc)); + instance->state = NfcStateIdle; + instance->comm_state = NfcCommStateIdle; + instance->config_state = NfcConfigurationStateIdle; + + instance->worker_thread = furi_thread_alloc(); + furi_thread_set_name(instance->worker_thread, "NfcWorker"); + furi_thread_set_context(instance->worker_thread, instance); + furi_thread_set_priority(instance->worker_thread, FuriThreadPriorityHighest); + furi_thread_set_stack_size(instance->worker_thread, 8 * 1024); + + return instance; +} + +void nfc_free(Nfc* instance) { + furi_assert(instance); + furi_assert(instance->state == NfcStateIdle); + + furi_thread_free(instance->worker_thread); + free(instance); + + furi_hal_nfc_release(); +} + +void nfc_config(Nfc* instance, NfcMode mode, NfcTech tech) { + furi_assert(instance); + furi_assert(mode < NfcModeNum); + furi_assert(tech < NfcTechNum); + furi_assert(instance->config_state == NfcConfigurationStateIdle); + + FuriHalNfcTech hal_tech = nfc_tech_table[mode][tech]; + if(hal_tech == FuriHalNfcTechInvalid) { + furi_crash("Unsupported mode for given tech"); + } + FuriHalNfcMode hal_mode = (mode == NfcModePoller) ? FuriHalNfcModePoller : + FuriHalNfcModeListener; + furi_hal_nfc_low_power_mode_stop(); + furi_hal_nfc_set_mode(hal_mode, hal_tech); + + instance->mode = mode; + instance->config_state = NfcConfigurationStateDone; +} + +void nfc_set_fdt_poll_fc(Nfc* instance, uint32_t fdt_poll_fc) { + furi_assert(instance); + instance->fdt_poll_fc = fdt_poll_fc; +} + +void nfc_set_fdt_listen_fc(Nfc* instance, uint32_t fdt_listen_fc) { + furi_assert(instance); + instance->fdt_listen_fc = fdt_listen_fc; +} + +void nfc_set_fdt_poll_poll_us(Nfc* instance, uint32_t fdt_poll_poll_us) { + furi_assert(instance); + instance->fdt_poll_poll_us = fdt_poll_poll_us; +} + +void nfc_set_guard_time_us(Nfc* instance, uint32_t guard_time_us) { + furi_assert(instance); + instance->guard_time_us = guard_time_us; +} + +void nfc_set_mask_receive_time_fc(Nfc* instance, uint32_t mask_rx_time_fc) { + furi_assert(instance); + instance->mask_rx_time_fc = mask_rx_time_fc; +} + +void nfc_start(Nfc* instance, NfcEventCallback callback, void* context) { + furi_assert(instance); + furi_assert(instance->worker_thread); + furi_assert(callback); + furi_assert(instance->config_state == NfcConfigurationStateDone); + + instance->callback = callback; + instance->context = context; + if(instance->mode == NfcModePoller) { + furi_thread_set_callback(instance->worker_thread, nfc_worker_poller); + } else { + furi_thread_set_callback(instance->worker_thread, nfc_worker_listener); + } + instance->comm_state = NfcCommStateIdle; + furi_thread_start(instance->worker_thread); +} + +void nfc_stop(Nfc* instance) { + furi_assert(instance); + furi_assert(instance->state == NfcStateRunning); + + if(instance->mode == NfcModeListener) { + furi_hal_nfc_abort(); + } + furi_thread_join(instance->worker_thread); + + instance->state = NfcStateIdle; +} + +NfcError nfc_listener_tx(Nfc* instance, const BitBuffer* tx_buffer) { + furi_assert(instance); + furi_assert(tx_buffer); + + NfcError ret = NfcErrorNone; + + while(furi_hal_nfc_timer_block_tx_is_running()) { + } + + FuriHalNfcError error = + furi_hal_nfc_listener_tx(bit_buffer_get_data(tx_buffer), bit_buffer_get_size(tx_buffer)); + if(error != FuriHalNfcErrorNone) { + FURI_LOG_D(TAG, "Failed in listener TX"); + ret = nfc_process_hal_error(error); + } + + return ret; +} + +static NfcError nfc_poller_trx_state_machine(Nfc* instance, uint32_t fwt_fc) { + FuriHalNfcEvent event = 0; + NfcError error = NfcErrorNone; + + while(true) { + event = furi_hal_nfc_poller_wait_event(FURI_HAL_NFC_EVENT_WAIT_FOREVER); + if(event & FuriHalNfcEventTimerBlockTxExpired) { + if(instance->comm_state == NfcCommStateWaitBlockTxTimer) { + instance->comm_state = NfcCommStateReadyTx; + } + } + if(event & FuriHalNfcEventTxEnd) { + if(instance->comm_state == NfcCommStateWaitTxEnd) { + if(fwt_fc) { + furi_hal_nfc_timer_fwt_start(fwt_fc); + } + furi_hal_nfc_timer_block_tx_start_us(instance->fdt_poll_poll_us); + instance->comm_state = NfcCommStateWaitRxStart; + } + } + if(event & FuriHalNfcEventRxStart) { + if(instance->comm_state == NfcCommStateWaitRxStart) { + furi_hal_nfc_timer_block_tx_stop(); + furi_hal_nfc_timer_fwt_stop(); + instance->comm_state = NfcCommStateWaitRxEnd; + } + } + if(event & FuriHalNfcEventRxEnd) { + furi_hal_nfc_timer_block_tx_start(instance->fdt_poll_fc); + furi_hal_nfc_timer_fwt_stop(); + instance->comm_state = NfcCommStateWaitBlockTxTimer; + break; + } + if(event & FuriHalNfcEventTimerFwtExpired) { + if(instance->comm_state == NfcCommStateWaitRxStart) { + error = NfcErrorTimeout; + FURI_LOG_D(TAG, "FWT Timeout"); + if(furi_hal_nfc_timer_block_tx_is_running()) { + instance->comm_state = NfcCommStateWaitBlockTxTimer; + } else { + instance->comm_state = NfcCommStateReadyTx; + } + break; + } + } + } + + return error; +} + +NfcError nfc_iso14443a_poller_trx_custom_parity( + Nfc* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt) { + furi_assert(instance); + furi_assert(tx_buffer); + furi_assert(rx_buffer); + + furi_assert(instance->poller_state == NfcPollerStateReady); + + NfcError ret = NfcErrorNone; + FuriHalNfcError error = FuriHalNfcErrorNone; + do { + furi_hal_nfc_trx_reset(); + while(furi_hal_nfc_timer_block_tx_is_running()) { + FuriHalNfcEvent event = + furi_hal_nfc_poller_wait_event(FURI_HAL_NFC_EVENT_WAIT_FOREVER); + if(event & FuriHalNfcEventTimerBlockTxExpired) break; + } + bit_buffer_write_bytes_with_parity( + tx_buffer, instance->tx_buffer, sizeof(instance->tx_buffer), &instance->tx_bits); + error = + furi_hal_nfc_iso14443a_poller_tx_custom_parity(instance->tx_buffer, instance->tx_bits); + if(error != FuriHalNfcErrorNone) { + FURI_LOG_D(TAG, "Failed in poller TX"); + ret = nfc_process_hal_error(error); + break; + } + instance->comm_state = NfcCommStateWaitTxEnd; + ret = nfc_poller_trx_state_machine(instance, fwt); + if(ret != NfcErrorNone) { + FURI_LOG_T(TAG, "Failed TRX state machine"); + break; + } + + error = furi_hal_nfc_poller_rx( + instance->rx_buffer, sizeof(instance->rx_buffer), &instance->rx_bits); + if(error != FuriHalNfcErrorNone) { + FURI_LOG_D(TAG, "Failed in poller RX"); + ret = nfc_process_hal_error(error); + break; + } + if(instance->rx_bits >= 9) { + if((instance->rx_bits % 9) != 0) { + ret = NfcErrorDataFormat; + break; + } + } + + bit_buffer_copy_bytes_with_parity(rx_buffer, instance->rx_buffer, instance->rx_bits); + } while(false); + + return ret; +} + +NfcError + nfc_poller_trx(Nfc* instance, const BitBuffer* tx_buffer, BitBuffer* rx_buffer, uint32_t fwt) { + furi_assert(instance); + furi_assert(tx_buffer); + furi_assert(rx_buffer); + + furi_assert(instance->poller_state == NfcPollerStateReady); + + NfcError ret = NfcErrorNone; + FuriHalNfcError error = FuriHalNfcErrorNone; + do { + furi_hal_nfc_trx_reset(); + while(furi_hal_nfc_timer_block_tx_is_running()) { + FuriHalNfcEvent event = + furi_hal_nfc_poller_wait_event(FURI_HAL_NFC_EVENT_WAIT_FOREVER); + if(event & FuriHalNfcEventTimerBlockTxExpired) break; + } + error = + furi_hal_nfc_poller_tx(bit_buffer_get_data(tx_buffer), bit_buffer_get_size(tx_buffer)); + if(error != FuriHalNfcErrorNone) { + FURI_LOG_D(TAG, "Failed in poller TX"); + ret = nfc_process_hal_error(error); + break; + } + instance->comm_state = NfcCommStateWaitTxEnd; + ret = nfc_poller_trx_state_machine(instance, fwt); + if(ret != NfcErrorNone) { + FURI_LOG_T(TAG, "Failed TRX state machine"); + break; + } + + error = furi_hal_nfc_poller_rx( + instance->rx_buffer, sizeof(instance->rx_buffer), &instance->rx_bits); + if(error != FuriHalNfcErrorNone) { + FURI_LOG_D(TAG, "Failed in poller RX"); + ret = nfc_process_hal_error(error); + break; + } + + bit_buffer_copy_bits(rx_buffer, instance->rx_buffer, instance->rx_bits); + } while(false); + + return ret; +} + +NfcError nfc_iso14443a_listener_set_col_res_data( + Nfc* instance, + uint8_t* uid, + uint8_t uid_len, + uint8_t* atqa, + uint8_t sak) { + furi_assert(instance); + + FuriHalNfcError error = + furi_hal_nfc_iso14443a_listener_set_col_res_data(uid, uid_len, atqa, sak); + instance->comm_state = NfcCommStateIdle; + return nfc_process_hal_error(error); +} + +NfcError nfc_iso14443a_poller_trx_short_frame( + Nfc* instance, + NfcIso14443aShortFrame frame, + BitBuffer* rx_buffer, + uint32_t fwt) { + furi_assert(instance); + furi_assert(rx_buffer); + + FuriHalNfcaShortFrame short_frame = (frame == NfcIso14443aShortFrameAllReqa) ? + FuriHalNfcaShortFrameAllReq : + FuriHalNfcaShortFrameSensReq; + + furi_assert(instance->poller_state == NfcPollerStateReady); + + NfcError ret = NfcErrorNone; + FuriHalNfcError error = FuriHalNfcErrorNone; + do { + furi_hal_nfc_trx_reset(); + while(furi_hal_nfc_timer_block_tx_is_running()) { + FuriHalNfcEvent event = + furi_hal_nfc_poller_wait_event(FURI_HAL_NFC_EVENT_WAIT_FOREVER); + if(event & FuriHalNfcEventTimerBlockTxExpired) break; + } + error = furi_hal_nfc_iso14443a_poller_trx_short_frame(short_frame); + if(error != FuriHalNfcErrorNone) { + FURI_LOG_D(TAG, "Failed in poller TX"); + ret = nfc_process_hal_error(error); + break; + } + instance->comm_state = NfcCommStateWaitTxEnd; + ret = nfc_poller_trx_state_machine(instance, fwt); + if(ret != NfcErrorNone) { + FURI_LOG_T(TAG, "Failed TRX state machine"); + break; + } + + error = furi_hal_nfc_poller_rx( + instance->rx_buffer, sizeof(instance->rx_buffer), &instance->rx_bits); + if(error != FuriHalNfcErrorNone) { + FURI_LOG_D(TAG, "Failed in poller RX"); + ret = nfc_process_hal_error(error); + break; + } + + bit_buffer_copy_bits(rx_buffer, instance->rx_buffer, instance->rx_bits); + } while(false); + + return ret; +} + +NfcError nfc_iso14443a_poller_trx_sdd_frame( + Nfc* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt) { + furi_assert(instance); + furi_assert(tx_buffer); + furi_assert(rx_buffer); + + furi_assert(instance->poller_state == NfcPollerStateReady); + + NfcError ret = NfcErrorNone; + FuriHalNfcError error = FuriHalNfcErrorNone; + do { + furi_hal_nfc_trx_reset(); + while(furi_hal_nfc_timer_block_tx_is_running()) { + FuriHalNfcEvent event = + furi_hal_nfc_poller_wait_event(FURI_HAL_NFC_EVENT_WAIT_FOREVER); + if(event & FuriHalNfcEventTimerBlockTxExpired) break; + } + error = furi_hal_nfc_iso14443a_tx_sdd_frame( + bit_buffer_get_data(tx_buffer), bit_buffer_get_size(tx_buffer)); + if(error != FuriHalNfcErrorNone) { + FURI_LOG_D(TAG, "Failed in poller TX"); + ret = nfc_process_hal_error(error); + break; + } + instance->comm_state = NfcCommStateWaitTxEnd; + ret = nfc_poller_trx_state_machine(instance, fwt); + if(ret != NfcErrorNone) { + FURI_LOG_T(TAG, "Failed TRX state machine"); + break; + } + + error = furi_hal_nfc_poller_rx( + instance->rx_buffer, sizeof(instance->rx_buffer), &instance->rx_bits); + if(error != FuriHalNfcErrorNone) { + FURI_LOG_D(TAG, "Failed in poller RX"); + ret = nfc_process_hal_error(error); + break; + } + + bit_buffer_copy_bits(rx_buffer, instance->rx_buffer, instance->rx_bits); + } while(false); + + return ret; +} + +NfcError nfc_iso14443a_listener_tx_custom_parity(Nfc* instance, const BitBuffer* tx_buffer) { + furi_assert(instance); + furi_assert(tx_buffer); + + NfcError ret = NfcErrorNone; + FuriHalNfcError error = FuriHalNfcErrorNone; + + const uint8_t* tx_data = bit_buffer_get_data(tx_buffer); + const uint8_t* tx_parity = bit_buffer_get_parity(tx_buffer); + size_t tx_bits = bit_buffer_get_size(tx_buffer); + + error = furi_hal_nfc_iso14443a_listener_tx_custom_parity(tx_data, tx_parity, tx_bits); + ret = nfc_process_hal_error(error); + + return ret; +} + +NfcError nfc_iso15693_listener_tx_sof(Nfc* instance) { + furi_assert(instance); + + while(furi_hal_nfc_timer_block_tx_is_running()) { + } + + FuriHalNfcError error = furi_hal_nfc_iso15693_listener_tx_sof(); + NfcError ret = nfc_process_hal_error(error); + + return ret; +} + +#endif // APP_UNIT_TESTS diff --git a/lib/nfc/nfc.h b/lib/nfc/nfc.h new file mode 100644 index 00000000000..1e8f315a50e --- /dev/null +++ b/lib/nfc/nfc.h @@ -0,0 +1,364 @@ +/** + * @file nfc.h + * @brief Transport layer Nfc library. + * + * The Nfc layer is responsible for setting the operating mode (poller or listener) + * and technology (ISO14443-3A/B, ISO15693, ...), data exchange between higher + * protocol-specific levels and underlying NFC hardware, as well as timings handling. + * + * In applications using the NFC protocol system there is no need to neiter explicitly + * create an Nfc instance nor call any of its functions, as it is all handled + * automatically under the hood. + * + * If the NFC protocol system is not suitable for the application's intended purpose + * or there is need of having direct access to the NFC transport layer, then an Nfc + * instance must be allocated and the below functions shall be used to implement + * the required logic. + */ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Nfc opaque type definition. + */ +typedef struct Nfc Nfc; + +/** + * @brief Enumeration of possible Nfc event types. + * + * Not all technologies implement all events (this is due to hardware limitations). + */ +typedef enum { + NfcEventTypeUserAbort, /**< User code explicitly aborted the current operation. */ + NfcEventTypeFieldOn, /**< Reader's field was detected by the NFC hardware. */ + NfcEventTypeFieldOff, /**< Reader's field was lost. */ + NfcEventTypeTxStart, /**< Data transmission has started. */ + NfcEventTypeTxEnd, /**< Data transmission has ended. */ + NfcEventTypeRxStart, /**< Data reception has started. */ + NfcEventTypeRxEnd, /**< Data reception has ended. */ + + NfcEventTypeListenerActivated, /**< The listener has been activated by the reader. */ + NfcEventTypePollerReady, /**< The card has been activated by the poller. */ +} NfcEventType; + +/** + * @brief Nfc event data structure. + */ +typedef struct { + BitBuffer* buffer; /**< Pointer to the received data buffer. */ +} NfcEventData; + +/** + * @brief Nfc event structure. + * + * Upon emission of an event, an instance of this struct will be passed to the callback. + */ +typedef struct { + NfcEventType type; /**< Type of the emitted event. */ + NfcEventData data; /**< Event-specific data. */ +} NfcEvent; + +/** + * @brief Enumeration of possible Nfc commands. + * + * The event callback must return one of these to determine the next action. + */ +typedef enum { + NfcCommandContinue, /**< Continue operation normally. */ + NfcCommandReset, /**< Reset the current state. */ + NfcCommandStop, /**< Stop the current operation. */ + NfcCommandSleep, /**< Switch Nfc hardware to low-power mode. */ +} NfcCommand; + +/** + * @brief Nfc event callback type. + * + * A function of this type must be passed as the callback parameter upon start of a an Nfc instance. + * + * @param [in] event Nfc event, passed by value, complete with protocol type and data. + * @param [in,out] context pointer to the user-specific context (set when starting an Nfc instance). + * @returns command which the event producer must execute. + */ +typedef NfcCommand (*NfcEventCallback)(NfcEvent event, void* context); + +/** + * @brief Enumeration of possible operating modes. + * + * Not all technologies implement the listener operating mode. + */ +typedef enum { + NfcModePoller, /**< Configure the Nfc instance as a poller. */ + NfcModeListener, /**< Configure the Nfc instance as a listener. */ + + NfcModeNum, /**< Operating mode count. Internal use. */ +} NfcMode; + +/** + * @brief Enumeration of available technologies. + */ +typedef enum { + NfcTechIso14443a, /**< Configure the Nfc instance to use the ISO14443-3A technology. */ + NfcTechIso14443b, /**< Configure the Nfc instance to use the ISO14443-3B technology. */ + NfcTechIso15693, /**< Configure the Nfc instance to use the ISO15693 technology. */ + NfcTechFelica, /**< Configure the Nfc instance to use the FeliCa technology. */ + + NfcTechNum, /**< Technologies count. Internal use. */ +} NfcTech; + +/** + * @brief Enumeration of possible Nfc error codes. + */ +typedef enum { + NfcErrorNone, /**< No error has occurred. */ + NfcErrorInternal, /**< An unknown error has occured on the lower level. */ + NfcErrorTimeout, /**< Operation is taking too long (e.g. card does not respond). */ + NfcErrorIncompleteFrame, /**< An incomplete data frame has been received. */ + NfcErrorDataFormat, /**< Data has not been parsed due to wrong/unknown format. */ +} NfcError; + +/** + * @brief Allocate an Nfc instance. + * + * Will exclusively take over the NFC HAL until deleted. + * + * @returns pointer to the allocated Nfc instance. + */ +Nfc* nfc_alloc(); + +/** + * @brief Delete an Nfc instance. + * + * Will release the NFC HAL lock, making it available for use by others. + * + * @param[in,out] instance pointer to the instance to be deleted. + */ +void nfc_free(Nfc* instance); + +/** + * @brief Configure the Nfc instance to work in a particular mode. + * + * Not all technologies implement the listener operating mode. + * + * @param[in,out] instance pointer to the instance to be configured. + * @param[in] mode required operating mode. + * @param[in] tech required technology configuration. + */ +void nfc_config(Nfc* instance, NfcMode mode, NfcTech tech); + +/** + * @brief Set poller frame delay time. + * + * @param[in,out] instance pointer to the instance to be modified. + * @param[in] fdt_poll_fc frame delay time, in carrier cycles. +*/ +void nfc_set_fdt_poll_fc(Nfc* instance, uint32_t fdt_poll_fc); + +/** + * @brief Set listener frame delay time. + * + * @param[in,out] instance pointer to the instance to be modified. + * @param[in] fdt_listen_fc frame delay time, in carrier cycles. + */ +void nfc_set_fdt_listen_fc(Nfc* instance, uint32_t fdt_listen_fc); + +/** + * @brief Set mask receive time. + * + * @param[in,out] instance pointer to the instance to be modified. + * @param[in] mask_rx_time mask receive time, in carrier cycles. + */ +void nfc_set_mask_receive_time_fc(Nfc* instance, uint32_t mask_rx_time_fc); + +/** + * @brief Set frame delay time. + * + * Frame delay time is the minimum time between two consecutive poll frames. + * + * @param[in,out] instance pointer to the instance to be modified. + * @param[in] fdt_poll_poll_us frame delay time, in microseconds. + */ +void nfc_set_fdt_poll_poll_us(Nfc* instance, uint32_t fdt_poll_poll_us); + +/** + * @brief Set guard time. + * + * @param[in,out] instance pointer to the instance to be modified. + * @param[in] guard_time_us guard time, in microseconds. + */ +void nfc_set_guard_time_us(Nfc* instance, uint32_t guard_time_us); + +/** + * @brief Start the Nfc instance. + * + * The instance must be configured to work with a specific technology + * in a specific operating mode with a nfc_config() call before starting. + * + * Once started, the user code will be receiving events through the provided + * callback which must handle them according to the logic required. + * + * @param[in,out] instance pointer to the instance to be started. + * @param[in] callback pointer to a user-defined callback function which will receive events. + * @param[in] context pointer to a user-specific context (will be passed to the callback). + */ +void nfc_start(Nfc* instance, NfcEventCallback callback, void* context); + +/** + * @brief Stop Nfc instance. + * + * The instance can only be stopped if it is running. + * + * @param[in,out] instance pointer to the instance to be stopped. + */ +void nfc_stop(Nfc* instance); + +/** + * @brief Transmit and receive a data frame in poller mode. + * + * The rx_buffer will be filled with any data received as a response to data + * sent from tx_buffer, with a timeout defined by the fwt parameter. + * + * The data being transmitted and received may be either bit- or byte-oriented. + * It shall not contain any technology-specific sequences as start or stop bits + * and/or other special symbols, as this is handled on the underlying HAL level. + * + * Must ONLY be used inside the callback function. + * + * @param[in,out] instance pointer to the instance to be used in the transaction. + * @param[in] tx_buffer pointer to the buffer containing the data to be transmitted. + * @param[out] rx_buffer pointer to the buffer to be filled with received data. + * @param[in] fwt frame wait time (response timeout), in carrier cycles. + * @returns NfcErrorNone on success, any other error code on failure. + */ +NfcError + nfc_poller_trx(Nfc* instance, const BitBuffer* tx_buffer, BitBuffer* rx_buffer, uint32_t fwt); + +/** + * @brief Transmit a data frame in listener mode. + * + * Used to transmit a response to the reader request in listener mode. + * + * The data being transmitted may be either bit- or byte-oriented. + * It shall not contain any technology-specific sequences as start or stop bits + * and/or other special symbols, as this is handled on the underlying HAL level. + * + * Must ONLY be used inside the callback function. + * + * @param[in,out] instance pointer to the instance to be used in the transaction. + * @param[in] tx_buffer pointer to the buffer containing the data to be transmitted. + * @returns NfcErrorNone on success, any other error code on failure. + */ +NfcError nfc_listener_tx(Nfc* instance, const BitBuffer* tx_buffer); + +/* + * Technology-specific functions. + * + * In a perfect world, this would not be necessary. + * However, the current implementation employs NFC hardware that partially implements + * certain protocols (e.g. ISO14443-3A), thus requiring methods to access such features. + */ + +/******************* ISO14443-3A specific API *******************/ + +/** + * @brief Enumeration of possible ISO14443-3A short frame types. + */ +typedef enum { + NfcIso14443aShortFrameSensReq, + NfcIso14443aShortFrameAllReqa, +} NfcIso14443aShortFrame; + +/** + * @brief Transmit an ISO14443-3A short frame and receive the response in poller mode. + * + * @param[in,out] instance pointer to the instance to be used in the transaction. + * @param[in] frame type of short frame to be sent. + * @param[out] rx_buffer pointer to the buffer to be filled with received data. + * @param[in] fwt frame wait time (response timeout), in carrier cycles. + * @returns NfcErrorNone on success, any other error code on failure. + */ +NfcError nfc_iso14443a_poller_trx_short_frame( + Nfc* instance, + NfcIso14443aShortFrame frame, + BitBuffer* rx_buffer, + uint32_t fwt); + +/** + * @brief Transmit an ISO14443-3A SDD frame and receive the response in poller mode. + * + * @param[in,out] instance pointer to the instance to be used in the transaction. + * @param[in] tx_buffer pointer to the buffer containing the data to be transmitted. + * @param[out] rx_buffer pointer to the buffer to be filled with received data. + * @param[in] fwt frame wait time (response timeout), in carrier cycles. + * @returns NfcErrorNone on success, any other error code on failure. + */ +NfcError nfc_iso14443a_poller_trx_sdd_frame( + Nfc* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt); + +/** + * @brief Transmit an ISO14443-3A data frame with custom parity bits and receive the response in poller mode. + * + * Same as nfc_poller_trx(), but uses the parity bits provided by the user code + * instead of calculating them automatically. + * + * @param[in,out] instance pointer to the instance to be used in the transaction. + * @param[in] tx_buffer pointer to the buffer containing the data to be transmitted. + * @param[out] rx_buffer pointer to the buffer to be filled with received data. + * @param[in] fwt frame wait time (response timeout), in carrier cycles. + * @returns NfcErrorNone on success, any other error code on failure. + */ +NfcError nfc_iso14443a_poller_trx_custom_parity( + Nfc* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt); + +/** + * @brief Transmit an ISO14443-3A frame with custom parity bits in listener mode. + * + * Same as nfc_listener_tx(), but uses the parity bits provided by the user code + * instead of calculating them automatically. + * + * @param[in,out] instance pointer to the instance to be used in the transaction. + * @param[in] tx_buffer pointer to the buffer containing the data to be transmitted. + * @returns NfcErrorNone on success, any other error code on failure. + */ +NfcError nfc_iso14443a_listener_tx_custom_parity(Nfc* instance, const BitBuffer* tx_buffer); + +/** + * @brief Set ISO14443-3A collision resolution parameters in listener mode. + * + * Configures the NFC hardware for automatic collision resolution. + * + * @param[in,out] instance pointer to the instance to be configured. + * @param[in] uid pointer to a byte array containing the UID. + * @param[in] uid_len UID length in bytes (must be supported by the protocol). + * @param[in] atqa ATQA byte value. + * @param[in] sak SAK byte value. + * @returns NfcErrorNone on success, any other error code on failure. + */ +NfcError nfc_iso14443a_listener_set_col_res_data( + Nfc* instance, + uint8_t* uid, + uint8_t uid_len, + uint8_t* atqa, + uint8_t sak); + +/** + * @brief Send ISO15693 Start of Frame pattern in listener mode + * + * @param[in,out] instance pointer to the instance to be configured. + * @returns NfcErrorNone on success, any other error code on failure. + */ +NfcError nfc_iso15693_listener_tx_sof(Nfc* instance); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/nfc_common.h b/lib/nfc/nfc_common.h new file mode 100644 index 00000000000..c3cccf697d3 --- /dev/null +++ b/lib/nfc/nfc_common.h @@ -0,0 +1,22 @@ +/** + * @file nfc_common.h + * @brief Various common NFC-related macros. + */ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +/* NFC file format version which changed ATQA format. Deprecated. */ +#define NFC_LSB_ATQA_FORMAT_VERSION (2) +/* NFC file format version which is still supported as backwards compatible. */ +#define NFC_MINIMUM_SUPPORTED_FORMAT_VERSION NFC_LSB_ATQA_FORMAT_VERSION +/* NFC file format version which implemented the unified loading process. */ +#define NFC_UNIFIED_FORMAT_VERSION (4) +/* Current NFC file format version. */ +#define NFC_CURRENT_FORMAT_VERSION NFC_UNIFIED_FORMAT_VERSION + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/nfc_device.c b/lib/nfc/nfc_device.c index c5c8b433845..267824d16ec 100644 --- a/lib/nfc/nfc_device.c +++ b/lib/nfc/nfc_device.c @@ -1,1735 +1,356 @@ -#include "nfc_device.h" -#include "assets_icons.h" -#include "nfc_types.h" +#include "nfc_device_i.h" -#include -#include -#include +#include #include -#define TAG "NfcDevice" -#define NFC_DEVICE_KEYS_FOLDER EXT_PATH("nfc/.cache") -#define NFC_DEVICE_KEYS_EXTENSION ".keys" +#include "nfc_common.h" +#include "protocols/nfc_device_defs.h" -static const char* nfc_file_header = "Flipper NFC device"; -static const uint32_t nfc_file_version = 3; +#define NFC_FILE_HEADER "Flipper NFC device" +#define NFC_DEV_TYPE_ERROR "Protocol type mismatch" -static const char* nfc_keys_file_header = "Flipper NFC keys"; -static const uint32_t nfc_keys_file_version = 1; +#define NFC_DEVICE_UID_KEY "UID" +#define NFC_DEVICE_TYPE_KEY "Device type" -// Protocols format versions -static const uint32_t nfc_mifare_classic_data_format_version = 2; -static const uint32_t nfc_mifare_ultralight_data_format_version = 1; +#define NFC_DEVICE_UID_MAX_LEN (10U) NfcDevice* nfc_device_alloc() { - NfcDevice* nfc_dev = malloc(sizeof(NfcDevice)); - nfc_dev->storage = furi_record_open(RECORD_STORAGE); - nfc_dev->dialogs = furi_record_open(RECORD_DIALOGS); - nfc_dev->load_path = furi_string_alloc(); - nfc_dev->dev_data.parsed_data = furi_string_alloc(); - nfc_dev->folder = furi_string_alloc(); - - // Rename cache folder name for backward compatibility - if(storage_common_stat(nfc_dev->storage, "/ext/nfc/cache", NULL) == FSE_OK) { - storage_common_rename(nfc_dev->storage, "/ext/nfc/cache", NFC_DEVICE_KEYS_FOLDER); - } - return nfc_dev; -} + NfcDevice* instance = malloc(sizeof(NfcDevice)); + instance->protocol = NfcProtocolInvalid; -void nfc_device_free(NfcDevice* nfc_dev) { - furi_assert(nfc_dev); - nfc_device_clear(nfc_dev); - furi_record_close(RECORD_STORAGE); - furi_record_close(RECORD_DIALOGS); - furi_string_free(nfc_dev->load_path); - furi_string_free(nfc_dev->dev_data.parsed_data); - furi_string_free(nfc_dev->folder); - free(nfc_dev); + return instance; } -static void nfc_device_prepare_format_string(NfcDevice* dev, FuriString* format_string) { - if(dev->format == NfcDeviceSaveFormatUid) { - furi_string_set(format_string, "UID"); - } else if(dev->format == NfcDeviceSaveFormatBankCard) { - furi_string_set(format_string, "Bank card"); - } else if(dev->format == NfcDeviceSaveFormatMifareUl) { - furi_string_set(format_string, nfc_mf_ul_type(dev->dev_data.mf_ul_data.type, true)); - } else if(dev->format == NfcDeviceSaveFormatMifareClassic) { - furi_string_set(format_string, "Mifare Classic"); - } else if(dev->format == NfcDeviceSaveFormatMifareDesfire) { - furi_string_set(format_string, "Mifare DESFire"); - } else if(dev->format == NfcDeviceSaveFormatNfcV) { - furi_string_set(format_string, "ISO15693"); - } else { - furi_string_set(format_string, "Unknown"); - } -} +void nfc_device_free(NfcDevice* instance) { + furi_assert(instance); -static bool nfc_device_parse_format_string(NfcDevice* dev, FuriString* format_string) { - if(furi_string_start_with_str(format_string, "UID")) { - dev->format = NfcDeviceSaveFormatUid; - dev->dev_data.protocol = NfcDeviceProtocolUnknown; - return true; - } - if(furi_string_start_with_str(format_string, "Bank card")) { - dev->format = NfcDeviceSaveFormatBankCard; - dev->dev_data.protocol = NfcDeviceProtocolEMV; - return true; - } - // Check Mifare Ultralight types - for(MfUltralightType type = MfUltralightTypeUnknown; type < MfUltralightTypeNum; type++) { - if(furi_string_equal(format_string, nfc_mf_ul_type(type, true))) { - dev->format = NfcDeviceSaveFormatMifareUl; - dev->dev_data.protocol = NfcDeviceProtocolMifareUl; - dev->dev_data.mf_ul_data.type = type; - return true; - } - } - if(furi_string_start_with_str(format_string, "Mifare Classic")) { - dev->format = NfcDeviceSaveFormatMifareClassic; - dev->dev_data.protocol = NfcDeviceProtocolMifareClassic; - return true; - } - if(furi_string_start_with_str(format_string, "Mifare DESFire")) { - dev->format = NfcDeviceSaveFormatMifareDesfire; - dev->dev_data.protocol = NfcDeviceProtocolMifareDesfire; - return true; - } - if(furi_string_start_with_str(format_string, "ISO15693")) { - dev->format = NfcDeviceSaveFormatNfcV; - dev->dev_data.protocol = NfcDeviceProtocolNfcV; - return true; - } - return false; + nfc_device_clear(instance); + free(instance); } -static bool nfc_device_save_mifare_ul_data(FlipperFormat* file, NfcDevice* dev) { - bool saved = false; - MfUltralightData* data = &dev->dev_data.mf_ul_data; - FuriString* temp_str; - temp_str = furi_string_alloc(); +void nfc_device_clear(NfcDevice* instance) { + furi_assert(instance); - // Save Mifare Ultralight specific data - do { - if(!flipper_format_write_comment_cstr(file, "Mifare Ultralight specific data")) break; - if(!flipper_format_write_uint32( - file, "Data format version", &nfc_mifare_ultralight_data_format_version, 1)) - break; - if(!flipper_format_write_hex(file, "Signature", data->signature, sizeof(data->signature))) - break; - if(!flipper_format_write_hex( - file, "Mifare version", (uint8_t*)&data->version, sizeof(data->version))) - break; - // Write conters and tearing flags data - bool counters_saved = true; - for(uint8_t i = 0; i < 3; i++) { - furi_string_printf(temp_str, "Counter %d", i); - if(!flipper_format_write_uint32( - file, furi_string_get_cstr(temp_str), &data->counter[i], 1)) { - counters_saved = false; - break; - } - furi_string_printf(temp_str, "Tearing %d", i); - if(!flipper_format_write_hex( - file, furi_string_get_cstr(temp_str), &data->tearing[i], 1)) { - counters_saved = false; - break; - } - } - if(!counters_saved) break; - // Write pages data - uint32_t pages_total = data->data_size / 4; - if(!flipper_format_write_uint32(file, "Pages total", &pages_total, 1)) break; - uint32_t pages_read = data->data_read / 4; - if(!flipper_format_write_uint32(file, "Pages read", &pages_read, 1)) break; - bool pages_saved = true; - for(uint16_t i = 0; i < data->data_size; i += 4) { - furi_string_printf(temp_str, "Page %d", i / 4); - if(!flipper_format_write_hex(file, furi_string_get_cstr(temp_str), &data->data[i], 4)) { - pages_saved = false; - break; - } + if(instance->protocol == NfcProtocolInvalid) { + furi_assert(instance->protocol_data == NULL); + } else if(instance->protocol < NfcProtocolNum) { + if(instance->protocol_data) { + nfc_devices[instance->protocol]->free(instance->protocol_data); + instance->protocol_data = NULL; } - if(!pages_saved) break; - - // Write authentication counter - uint32_t auth_counter = data->curr_authlim; - if(!flipper_format_write_uint32(file, "Failed authentication attempts", &auth_counter, 1)) - break; - - saved = true; - } while(false); - - furi_string_free(temp_str); - return saved; + instance->protocol = NfcProtocolInvalid; + } } -bool nfc_device_load_mifare_ul_data(FlipperFormat* file, NfcDevice* dev) { - bool parsed = false; - MfUltralightData* data = &dev->dev_data.mf_ul_data; - FuriString* temp_str; - temp_str = furi_string_alloc(); - uint32_t data_format_version = 0; - - do { - // Read Mifare Ultralight format version - if(!flipper_format_read_uint32(file, "Data format version", &data_format_version, 1)) { - if(!flipper_format_rewind(file)) break; - } - - // Read signature - if(!flipper_format_read_hex(file, "Signature", data->signature, sizeof(data->signature))) - break; - // Read Mifare version - if(!flipper_format_read_hex( - file, "Mifare version", (uint8_t*)&data->version, sizeof(data->version))) - break; - // Read counters and tearing flags - bool counters_parsed = true; - for(uint8_t i = 0; i < 3; i++) { - furi_string_printf(temp_str, "Counter %d", i); - if(!flipper_format_read_uint32( - file, furi_string_get_cstr(temp_str), &data->counter[i], 1)) { - counters_parsed = false; - break; - } - furi_string_printf(temp_str, "Tearing %d", i); - if(!flipper_format_read_hex( - file, furi_string_get_cstr(temp_str), &data->tearing[i], 1)) { - counters_parsed = false; - break; - } - } - if(!counters_parsed) break; - // Read pages - uint32_t pages_total = 0; - if(!flipper_format_read_uint32(file, "Pages total", &pages_total, 1)) break; - uint32_t pages_read = 0; - if(data_format_version < nfc_mifare_ultralight_data_format_version) { - pages_read = pages_total; - } else { - if(!flipper_format_read_uint32(file, "Pages read", &pages_read, 1)) break; - } - data->data_size = pages_total * 4; - data->data_read = pages_read * 4; - if(data->data_size > MF_UL_MAX_DUMP_SIZE || data->data_read > MF_UL_MAX_DUMP_SIZE) break; - bool pages_parsed = true; - for(uint16_t i = 0; i < pages_total; i++) { - furi_string_printf(temp_str, "Page %d", i); - if(!flipper_format_read_hex( - file, furi_string_get_cstr(temp_str), &data->data[i * 4], 4)) { - pages_parsed = false; - break; - } - } - if(!pages_parsed) break; +void nfc_device_reset(NfcDevice* instance) { + furi_assert(instance); + furi_assert(instance->protocol < NfcProtocolNum); - // Read authentication counter - uint32_t auth_counter; - if(!flipper_format_read_uint32(file, "Failed authentication attempts", &auth_counter, 1)) - auth_counter = 0; - data->curr_authlim = auth_counter; - - data->auth_success = mf_ul_is_full_capture(data); - - parsed = true; - } while(false); - - furi_string_free(temp_str); - return parsed; + if(instance->protocol_data) { + nfc_devices[instance->protocol]->reset(instance->protocol_data); + } } -static bool nfc_device_save_mifare_df_key_settings( - FlipperFormat* file, - MifareDesfireKeySettings* ks, - const char* prefix) { - bool saved = false; - FuriString* key; - key = furi_string_alloc(); - - do { - furi_string_printf(key, "%s Change Key ID", prefix); - if(!flipper_format_write_hex(file, furi_string_get_cstr(key), &ks->change_key_id, 1)) - break; - furi_string_printf(key, "%s Config Changeable", prefix); - if(!flipper_format_write_bool(file, furi_string_get_cstr(key), &ks->config_changeable, 1)) - break; - furi_string_printf(key, "%s Free Create Delete", prefix); - if(!flipper_format_write_bool(file, furi_string_get_cstr(key), &ks->free_create_delete, 1)) - break; - furi_string_printf(key, "%s Free Directory List", prefix); - if(!flipper_format_write_bool(file, furi_string_get_cstr(key), &ks->free_directory_list, 1)) - break; - furi_string_printf(key, "%s Key Changeable", prefix); - if(!flipper_format_write_bool( - file, furi_string_get_cstr(key), &ks->master_key_changeable, 1)) - break; - if(ks->flags) { - furi_string_printf(key, "%s Flags", prefix); - if(!flipper_format_write_hex(file, furi_string_get_cstr(key), &ks->flags, 1)) break; - } - furi_string_printf(key, "%s Max Keys", prefix); - if(!flipper_format_write_hex(file, furi_string_get_cstr(key), &ks->max_keys, 1)) break; - for(MifareDesfireKeyVersion* kv = ks->key_version_head; kv; kv = kv->next) { - furi_string_printf(key, "%s Key %d Version", prefix, kv->id); - if(!flipper_format_write_hex(file, furi_string_get_cstr(key), &kv->version, 1)) break; - } - saved = true; - } while(false); +NfcProtocol nfc_device_get_protocol(const NfcDevice* instance) { + furi_assert(instance); + return instance->protocol; +} - furi_string_free(key); - return saved; +const NfcDeviceData* nfc_device_get_data(const NfcDevice* instance, NfcProtocol protocol) { + return nfc_device_get_data_ptr(instance, protocol); } -bool nfc_device_load_mifare_df_key_settings( - FlipperFormat* file, - MifareDesfireKeySettings* ks, - const char* prefix) { - bool parsed = false; - FuriString* key; - key = furi_string_alloc(); +const char* nfc_device_get_protocol_name(NfcProtocol protocol) { + furi_assert(protocol < NfcProtocolNum); - do { - furi_string_printf(key, "%s Change Key ID", prefix); - if(!flipper_format_read_hex(file, furi_string_get_cstr(key), &ks->change_key_id, 1)) break; - furi_string_printf(key, "%s Config Changeable", prefix); - if(!flipper_format_read_bool(file, furi_string_get_cstr(key), &ks->config_changeable, 1)) - break; - furi_string_printf(key, "%s Free Create Delete", prefix); - if(!flipper_format_read_bool(file, furi_string_get_cstr(key), &ks->free_create_delete, 1)) - break; - furi_string_printf(key, "%s Free Directory List", prefix); - if(!flipper_format_read_bool(file, furi_string_get_cstr(key), &ks->free_directory_list, 1)) - break; - furi_string_printf(key, "%s Key Changeable", prefix); - if(!flipper_format_read_bool( - file, furi_string_get_cstr(key), &ks->master_key_changeable, 1)) - break; - furi_string_printf(key, "%s Flags", prefix); - if(flipper_format_key_exist(file, furi_string_get_cstr(key))) { - if(!flipper_format_read_hex(file, furi_string_get_cstr(key), &ks->flags, 1)) break; - } - furi_string_printf(key, "%s Max Keys", prefix); - if(!flipper_format_read_hex(file, furi_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++) { - furi_string_printf(key, "%s Key %d Version", prefix, key_id); - uint8_t version; - if(flipper_format_read_hex(file, furi_string_get_cstr(key), &version, 1)) { - MifareDesfireKeyVersion* kv = malloc(sizeof(MifareDesfireKeyVersion)); - memset(kv, 0, sizeof(MifareDesfireKeyVersion)); - kv->id = key_id; - kv->version = version; - *kv_head = kv; - kv_head = &kv->next; - } - } - parsed = true; - } while(false); - - furi_string_free(key); - return parsed; + return nfc_devices[protocol]->protocol_name; } -static bool nfc_device_save_mifare_df_app(FlipperFormat* file, MifareDesfireApplication* app) { - bool saved = false; - FuriString *prefix, *key; - prefix = - furi_string_alloc_printf("Application %02x%02x%02x", app->id[0], app->id[1], app->id[2]); - key = furi_string_alloc(); - uint8_t* tmp = NULL; +const char* nfc_device_get_name(const NfcDevice* instance, NfcDeviceNameType name_type) { + furi_assert(instance); + furi_assert(instance->protocol < NfcProtocolNum); - do { - if(app->key_settings) { - if(!nfc_device_save_mifare_df_key_settings( - file, app->key_settings, furi_string_get_cstr(prefix))) - break; - } - if(!app->file_head) break; - uint32_t n_files = 0; - for(MifareDesfireFile* f = app->file_head; f; f = f->next) { - n_files++; - } - tmp = malloc(n_files); - int i = 0; - for(MifareDesfireFile* f = app->file_head; f; f = f->next) { - tmp[i++] = f->id; - } - furi_string_printf(key, "%s File IDs", furi_string_get_cstr(prefix)); - if(!flipper_format_write_hex(file, furi_string_get_cstr(key), tmp, n_files)) break; - bool saved_files = true; - for(MifareDesfireFile* f = app->file_head; f; f = f->next) { - saved_files = false; - furi_string_printf(key, "%s File %d Type", furi_string_get_cstr(prefix), f->id); - if(!flipper_format_write_hex(file, furi_string_get_cstr(key), &f->type, 1)) break; - furi_string_printf( - key, "%s File %d Communication Settings", furi_string_get_cstr(prefix), f->id); - if(!flipper_format_write_hex(file, furi_string_get_cstr(key), &f->comm, 1)) break; - furi_string_printf( - key, "%s File %d Access Rights", furi_string_get_cstr(prefix), f->id); - if(!flipper_format_write_hex( - file, furi_string_get_cstr(key), (uint8_t*)&f->access_rights, 2)) - break; - uint16_t size = 0; - if(f->type == MifareDesfireFileTypeStandard || - f->type == MifareDesfireFileTypeBackup) { - size = f->settings.data.size; - furi_string_printf(key, "%s File %d Size", furi_string_get_cstr(prefix), f->id); - if(!flipper_format_write_uint32( - file, furi_string_get_cstr(key), &f->settings.data.size, 1)) - break; - } else if(f->type == MifareDesfireFileTypeValue) { - furi_string_printf( - key, "%s File %d Hi Limit", furi_string_get_cstr(prefix), f->id); - if(!flipper_format_write_uint32( - file, furi_string_get_cstr(key), &f->settings.value.hi_limit, 1)) - break; - furi_string_printf( - key, "%s File %d Lo Limit", furi_string_get_cstr(prefix), f->id); - if(!flipper_format_write_uint32( - file, furi_string_get_cstr(key), &f->settings.value.lo_limit, 1)) - break; - furi_string_printf( - key, "%s File %d Limited Credit Value", furi_string_get_cstr(prefix), f->id); - if(!flipper_format_write_uint32( - file, furi_string_get_cstr(key), &f->settings.value.limited_credit_value, 1)) - break; - furi_string_printf( - key, "%s File %d Limited Credit Enabled", furi_string_get_cstr(prefix), f->id); - if(!flipper_format_write_bool( - file, - furi_string_get_cstr(key), - &f->settings.value.limited_credit_enabled, - 1)) - break; - size = 4; - } else if( - f->type == MifareDesfireFileTypeLinearRecord || - f->type == MifareDesfireFileTypeCyclicRecord) { - furi_string_printf(key, "%s File %d Size", furi_string_get_cstr(prefix), f->id); - if(!flipper_format_write_uint32( - file, furi_string_get_cstr(key), &f->settings.record.size, 1)) - break; - furi_string_printf(key, "%s File %d Max", furi_string_get_cstr(prefix), f->id); - if(!flipper_format_write_uint32( - file, furi_string_get_cstr(key), &f->settings.record.max, 1)) - break; - furi_string_printf(key, "%s File %d Cur", furi_string_get_cstr(prefix), f->id); - if(!flipper_format_write_uint32( - file, furi_string_get_cstr(key), &f->settings.record.cur, 1)) - break; - size = f->settings.record.size * f->settings.record.cur; - } - if(f->contents) { - furi_string_printf(key, "%s File %d", furi_string_get_cstr(prefix), f->id); - if(!flipper_format_write_hex(file, furi_string_get_cstr(key), f->contents, size)) - break; - } - saved_files = true; - } - if(!saved_files) { - break; - } - saved = true; - } while(false); - - free(tmp); - furi_string_free(prefix); - furi_string_free(key); - return saved; + return nfc_devices[instance->protocol]->get_name(instance->protocol_data, name_type); } -bool nfc_device_load_mifare_df_app(FlipperFormat* file, MifareDesfireApplication* app) { - bool parsed = false; - FuriString *prefix, *key; - prefix = - furi_string_alloc_printf("Application %02x%02x%02x", app->id[0], app->id[1], app->id[2]); - key = furi_string_alloc(); - uint8_t* tmp = NULL; - MifareDesfireFile* f = NULL; +const uint8_t* nfc_device_get_uid(const NfcDevice* instance, size_t* uid_len) { + furi_assert(instance); + furi_assert(instance->protocol < NfcProtocolNum); - do { - app->key_settings = malloc(sizeof(MifareDesfireKeySettings)); - memset(app->key_settings, 0, sizeof(MifareDesfireKeySettings)); - if(!nfc_device_load_mifare_df_key_settings( - file, app->key_settings, furi_string_get_cstr(prefix))) { - free(app->key_settings); - app->key_settings = NULL; - break; - } - furi_string_printf(key, "%s File IDs", furi_string_get_cstr(prefix)); - uint32_t n_files; - if(!flipper_format_get_value_count(file, furi_string_get_cstr(key), &n_files)) break; - tmp = malloc(n_files); - if(!flipper_format_read_hex(file, furi_string_get_cstr(key), tmp, n_files)) break; - MifareDesfireFile** file_head = &app->file_head; - bool parsed_files = true; - for(uint32_t i = 0; i < n_files; i++) { - parsed_files = false; - f = malloc(sizeof(MifareDesfireFile)); - memset(f, 0, sizeof(MifareDesfireFile)); - f->id = tmp[i]; - furi_string_printf(key, "%s File %d Type", furi_string_get_cstr(prefix), f->id); - if(!flipper_format_read_hex(file, furi_string_get_cstr(key), &f->type, 1)) break; - furi_string_printf( - key, "%s File %d Communication Settings", furi_string_get_cstr(prefix), f->id); - if(!flipper_format_read_hex(file, furi_string_get_cstr(key), &f->comm, 1)) break; - furi_string_printf( - key, "%s File %d Access Rights", furi_string_get_cstr(prefix), f->id); - if(!flipper_format_read_hex( - file, furi_string_get_cstr(key), (uint8_t*)&f->access_rights, 2)) - break; - if(f->type == MifareDesfireFileTypeStandard || - f->type == MifareDesfireFileTypeBackup) { - furi_string_printf(key, "%s File %d Size", furi_string_get_cstr(prefix), f->id); - if(!flipper_format_read_uint32( - file, furi_string_get_cstr(key), &f->settings.data.size, 1)) - break; - } else if(f->type == MifareDesfireFileTypeValue) { - furi_string_printf( - key, "%s File %d Hi Limit", furi_string_get_cstr(prefix), f->id); - if(!flipper_format_read_uint32( - file, furi_string_get_cstr(key), &f->settings.value.hi_limit, 1)) - break; - furi_string_printf( - key, "%s File %d Lo Limit", furi_string_get_cstr(prefix), f->id); - if(!flipper_format_read_uint32( - file, furi_string_get_cstr(key), &f->settings.value.lo_limit, 1)) - break; - furi_string_printf( - key, "%s File %d Limited Credit Value", furi_string_get_cstr(prefix), f->id); - if(!flipper_format_read_uint32( - file, furi_string_get_cstr(key), &f->settings.value.limited_credit_value, 1)) - break; - furi_string_printf( - key, "%s File %d Limited Credit Enabled", furi_string_get_cstr(prefix), f->id); - if(!flipper_format_read_bool( - file, - furi_string_get_cstr(key), - &f->settings.value.limited_credit_enabled, - 1)) - break; - } else if( - f->type == MifareDesfireFileTypeLinearRecord || - f->type == MifareDesfireFileTypeCyclicRecord) { - furi_string_printf(key, "%s File %d Size", furi_string_get_cstr(prefix), f->id); - if(!flipper_format_read_uint32( - file, furi_string_get_cstr(key), &f->settings.record.size, 1)) - break; - furi_string_printf(key, "%s File %d Max", furi_string_get_cstr(prefix), f->id); - if(!flipper_format_read_uint32( - file, furi_string_get_cstr(key), &f->settings.record.max, 1)) - break; - furi_string_printf(key, "%s File %d Cur", furi_string_get_cstr(prefix), f->id); - if(!flipper_format_read_uint32( - file, furi_string_get_cstr(key), &f->settings.record.cur, 1)) - break; - } - furi_string_printf(key, "%s File %d", furi_string_get_cstr(prefix), f->id); - if(flipper_format_key_exist(file, furi_string_get_cstr(key))) { - uint32_t size; - if(!flipper_format_get_value_count(file, furi_string_get_cstr(key), &size)) break; - f->contents = malloc(size); - if(!flipper_format_read_hex(file, furi_string_get_cstr(key), f->contents, size)) - break; - } - *file_head = f; - file_head = &f->next; - f = NULL; - parsed_files = true; - } - if(!parsed_files) { - break; - } - parsed = true; - } while(false); - - if(f) { - free(f->contents); - free(f); - } - free(tmp); - furi_string_free(prefix); - furi_string_free(key); - return parsed; + return nfc_devices[instance->protocol]->get_uid(instance->protocol_data, uid_len); } -static bool nfc_device_save_mifare_df_data(FlipperFormat* file, NfcDevice* dev) { - bool saved = false; - MifareDesfireData* data = &dev->dev_data.mf_df_data; - uint8_t* tmp = NULL; - - do { - if(!flipper_format_write_comment_cstr(file, "Mifare DESFire specific data")) break; - if(!flipper_format_write_hex( - file, "PICC Version", (uint8_t*)&data->version, sizeof(data->version))) - break; - if(data->free_memory) { - if(!flipper_format_write_uint32(file, "PICC Free Memory", &data->free_memory->bytes, 1)) - break; - } - if(data->master_key_settings) { - if(!nfc_device_save_mifare_df_key_settings(file, data->master_key_settings, "PICC")) - break; - } - uint32_t n_apps = 0; - for(MifareDesfireApplication* app = data->app_head; app; app = app->next) { - n_apps++; - } - if(!flipper_format_write_uint32(file, "Application Count", &n_apps, 1)) break; - if(n_apps) { - tmp = malloc(n_apps * 3); - int i = 0; - for(MifareDesfireApplication* app = data->app_head; app; app = app->next) { - memcpy(tmp + i, app->id, 3); //-V769 - i += 3; - } - if(!flipper_format_write_hex(file, "Application IDs", tmp, n_apps * 3)) break; - for(MifareDesfireApplication* app = data->app_head; app; app = app->next) { - if(!nfc_device_save_mifare_df_app(file, app)) break; - } - } - saved = true; - } while(false); +bool nfc_device_set_uid(NfcDevice* instance, const uint8_t* uid, size_t uid_len) { + furi_assert(instance); + furi_assert(instance->protocol < NfcProtocolNum); - free(tmp); - return saved; + return nfc_devices[instance->protocol]->set_uid(instance->protocol_data, uid, uid_len); } -bool nfc_device_load_mifare_df_data(FlipperFormat* file, NfcDevice* dev) { - bool parsed = false; - MifareDesfireData* data = &dev->dev_data.mf_df_data; - memset(data, 0, sizeof(MifareDesfireData)); - uint8_t* tmp = NULL; +void nfc_device_set_data( + NfcDevice* instance, + NfcProtocol protocol, + const NfcDeviceData* protocol_data) { + furi_assert(instance); + furi_assert(protocol < NfcProtocolNum); - do { - if(!flipper_format_read_hex( - file, "PICC Version", (uint8_t*)&data->version, sizeof(data->version))) - break; - if(flipper_format_key_exist(file, "PICC Free Memory")) { - data->free_memory = malloc(sizeof(MifareDesfireFreeMemory)); - memset(data->free_memory, 0, sizeof(MifareDesfireFreeMemory)); - if(!flipper_format_read_uint32( - file, "PICC Free Memory", &data->free_memory->bytes, 1)) { - free(data->free_memory); - break; - } - } - if(flipper_format_key_exist(file, "PICC Change Key ID")) { - data->master_key_settings = malloc(sizeof(MifareDesfireKeySettings)); - memset(data->master_key_settings, 0, sizeof(MifareDesfireKeySettings)); - if(!nfc_device_load_mifare_df_key_settings(file, data->master_key_settings, "PICC")) { - free(data->master_key_settings); - data->master_key_settings = NULL; - break; - } - } - uint32_t n_apps; - if(!flipper_format_read_uint32(file, "Application Count", &n_apps, 1)) break; - if(n_apps) { - tmp = malloc(n_apps * 3); - if(!flipper_format_read_hex(file, "Application IDs", tmp, n_apps * 3)) break; - bool parsed_apps = true; - MifareDesfireApplication** app_head = &data->app_head; - for(uint32_t i = 0; i < n_apps; i++) { - MifareDesfireApplication* app = malloc(sizeof(MifareDesfireApplication)); - memset(app, 0, sizeof(MifareDesfireApplication)); - memcpy(app->id, &tmp[i * 3], 3); - if(!nfc_device_load_mifare_df_app(file, app)) { - free(app); - parsed_apps = false; - break; - } - *app_head = app; - app_head = &app->next; - } - if(!parsed_apps) { - // accept non-parsed apps, just log a warning: - FURI_LOG_W(TAG, "Non-parsed apps found!"); - } - } - parsed = true; - } while(false); + nfc_device_clear(instance); - free(tmp); - return parsed; -} + instance->protocol = protocol; + instance->protocol_data = nfc_devices[protocol]->alloc(); -static bool nfc_device_save_slix_data( - FlipperFormat* file, - NfcDevice* dev, - SlixTypeFeatures features, - const char* type) { - bool saved = false; - NfcVSlixData* data = &dev->dev_data.nfcv_data.sub_data.slix; + nfc_devices[protocol]->copy(instance->protocol_data, protocol_data); +} - do { - char msg[64]; - snprintf(msg, sizeof(msg), "%s specific data", type); - if(!flipper_format_write_comment_cstr(file, msg)) break; - if(!flipper_format_write_comment_cstr( - file, "Passwords are optional. If password is omitted, any password is accepted")) - break; +void nfc_device_copy_data( + const NfcDevice* instance, + NfcProtocol protocol, + NfcDeviceData* protocol_data) { + furi_assert(instance); + furi_assert(protocol < NfcProtocolNum); + furi_assert(protocol_data); - if(features & SlixFeatureRead) { - if(data->flags & NfcVSlixDataFlagsHasKeyRead) { - if(!flipper_format_write_hex( - file, "Password Read", data->key_read, sizeof(data->key_read))) - break; - } - } - if(features & SlixFeatureWrite) { - if(data->flags & NfcVSlixDataFlagsHasKeyWrite) { - if(!flipper_format_write_hex( - file, "Password Write", data->key_write, sizeof(data->key_write))) - break; - } - } - if(features & SlixFeaturePrivacy) { - if(data->flags & NfcVSlixDataFlagsHasKeyPrivacy) { - if(!flipper_format_write_hex( - file, "Password Privacy", data->key_privacy, sizeof(data->key_privacy))) - break; - } - } - if(features & SlixFeatureDestroy) { - if(data->flags & NfcVSlixDataFlagsHasKeyDestroy) { - if(!flipper_format_write_hex( - file, "Password Destroy", data->key_destroy, sizeof(data->key_destroy))) - break; - } - } - if(features & SlixFeatureEas) { - if(data->flags & NfcVSlixDataFlagsHasKeyEas) { - if(!flipper_format_write_hex( - file, "Password EAS", data->key_eas, sizeof(data->key_eas))) - break; - } - } - if(features & SlixFeatureSignature) { - if(!flipper_format_write_comment_cstr( - file, - "This is the card's secp128r1 elliptic curve signature. It can not be calculated without knowing NXP's private key.")) - break; - if(!flipper_format_write_hex( - file, "Signature", data->signature, sizeof(data->signature))) - break; - } - if(features & SlixFeaturePrivacy) { - bool privacy = (data->flags & NfcVSlixDataFlagsPrivacy) ? true : false; - if(!flipper_format_write_bool(file, "Privacy Mode", &privacy, 1)) break; - } - if(features & SlixFeatureProtection) { - if(!flipper_format_write_comment_cstr(file, "Protection pointer configuration")) break; - if(!flipper_format_write_hex(file, "Protection pointer", &data->pp_pointer, 1)) break; - if(!flipper_format_write_hex(file, "Protection condition", &data->pp_condition, 1)) - break; - } - saved = true; - } while(false); + if(instance->protocol != protocol) { + furi_crash(NFC_DEV_TYPE_ERROR); + } - return saved; + nfc_devices[protocol]->copy(protocol_data, instance->protocol_data); } -bool nfc_device_load_slix_data(FlipperFormat* file, NfcDevice* dev, SlixTypeFeatures features) { - bool parsed = false; - NfcVSlixData* data = &dev->dev_data.nfcv_data.sub_data.slix; - memset(data, 0, sizeof(NfcVSlixData)); +bool nfc_device_is_equal_data( + const NfcDevice* instance, + NfcProtocol protocol, + const NfcDeviceData* protocol_data) { + furi_assert(instance); + furi_assert(protocol < NfcProtocolNum); + furi_assert(protocol_data); - do { - data->flags = 0; - - if(features & SlixFeatureRead) { - if(flipper_format_key_exist(file, "Password Read")) { - if(!flipper_format_read_hex( - file, "Password Read", data->key_read, sizeof(data->key_read))) { - FURI_LOG_D(TAG, "Failed reading Password Read"); - break; - } - data->flags |= NfcVSlixDataFlagsHasKeyRead; - } - } - if(features & SlixFeatureWrite) { - if(flipper_format_key_exist(file, "Password Write")) { - if(!flipper_format_read_hex( - file, "Password Write", data->key_write, sizeof(data->key_write))) { - FURI_LOG_D(TAG, "Failed reading Password Write"); - break; - } - data->flags |= NfcVSlixDataFlagsHasKeyWrite; - } - } - if(features & SlixFeaturePrivacy) { - if(flipper_format_key_exist(file, "Password Privacy")) { - if(!flipper_format_read_hex( - file, "Password Privacy", data->key_privacy, sizeof(data->key_privacy))) { - FURI_LOG_D(TAG, "Failed reading Password Privacy"); - break; - } - data->flags |= NfcVSlixDataFlagsHasKeyPrivacy; - } - } - if(features & SlixFeatureDestroy) { - if(flipper_format_key_exist(file, "Password Destroy")) { - if(!flipper_format_read_hex( - file, "Password Destroy", data->key_destroy, sizeof(data->key_destroy))) { - FURI_LOG_D(TAG, "Failed reading Password Destroy"); - break; - } - data->flags |= NfcVSlixDataFlagsHasKeyDestroy; - } - } - if(features & SlixFeatureEas) { - if(flipper_format_key_exist(file, "Password EAS")) { - if(!flipper_format_read_hex( - file, "Password EAS", data->key_eas, sizeof(data->key_eas))) { - FURI_LOG_D(TAG, "Failed reading Password EAS"); - break; - } - data->flags |= NfcVSlixDataFlagsHasKeyEas; - } - } - if(features & SlixFeatureSignature) { - if(!flipper_format_read_hex( - file, "Signature", data->signature, sizeof(data->signature))) { - FURI_LOG_D(TAG, "Failed reading Signature"); - break; - } - } - if(features & SlixFeaturePrivacy) { - bool privacy; - if(!flipper_format_read_bool(file, "Privacy Mode", &privacy, 1)) { - FURI_LOG_D(TAG, "Failed reading Privacy Mode"); - break; - } - if(privacy) { - data->flags |= NfcVSlixDataFlagsPrivacy; - } - } - if(features & SlixFeatureProtection) { - if(!flipper_format_read_hex(file, "Protection pointer", &(data->pp_pointer), 1)) { - FURI_LOG_D(TAG, "Failed reading Protection pointer"); - break; - } - if(!flipper_format_read_hex(file, "Protection condition", &(data->pp_condition), 1)) { - FURI_LOG_D(TAG, "Failed reading Protection condition"); - break; - } - } - parsed = true; - } while(false); - - return parsed; + return instance->protocol == protocol && + nfc_devices[protocol]->is_equal(instance->protocol_data, protocol_data); } -static bool nfc_device_save_nfcv_data(FlipperFormat* file, NfcDevice* dev) { - bool saved = false; - NfcVData* data = &dev->dev_data.nfcv_data; +bool nfc_device_is_equal(const NfcDevice* instance, const NfcDevice* other) { + furi_assert(instance); + furi_assert(other); - do { - uint32_t temp_uint32 = 0; - uint8_t temp_uint8 = 0; - - if(!flipper_format_write_comment_cstr(file, "Data Storage Format Identifier")) break; - if(!flipper_format_write_hex(file, "DSFID", &(data->dsfid), 1)) break; - if(!flipper_format_write_comment_cstr(file, "Application Family Identifier")) break; - if(!flipper_format_write_hex(file, "AFI", &(data->afi), 1)) break; - if(!flipper_format_write_hex(file, "IC Reference", &(data->ic_ref), 1)) break; - temp_uint32 = data->block_num; - if(!flipper_format_write_comment_cstr(file, "Number of memory blocks, usually 0 to 256")) - break; - if(!flipper_format_write_uint32(file, "Block Count", &temp_uint32, 1)) break; - if(!flipper_format_write_comment_cstr(file, "Size of a single memory block, usually 4")) - break; - if(!flipper_format_write_hex(file, "Block Size", &(data->block_size), 1)) break; - if(!flipper_format_write_hex( - file, "Data Content", data->data, data->block_num * data->block_size)) - break; - if(!flipper_format_write_comment_cstr( - file, - "First byte: DSFID (0x01) / AFI (0x02) / EAS (0x04) / PPL (0x08) lock info, others: block lock info")) - break; - if(!flipper_format_write_hex( - file, "Security Status", data->security_status, 1 + data->block_num)) - break; - if(!flipper_format_write_comment_cstr( - file, - "Subtype of this card (0 = ISO15693, 1 = SLIX, 2 = SLIX-S, 3 = SLIX-L, 4 = SLIX2)")) - break; - temp_uint8 = (uint8_t)data->sub_type; - if(!flipper_format_write_hex(file, "Subtype", &temp_uint8, 1)) break; + return nfc_device_is_equal_data(instance, other->protocol, other->protocol_data); +} - switch(data->sub_type) { - case NfcVTypePlain: - if(!flipper_format_write_comment_cstr(file, "End of ISO15693 parameters")) break; - saved = true; - break; - case NfcVTypeSlix: - saved = nfc_device_save_slix_data(file, dev, SlixFeatureSlix, "SLIX"); - break; - case NfcVTypeSlixS: - saved = nfc_device_save_slix_data(file, dev, SlixFeatureSlixS, "SLIX-S"); - break; - case NfcVTypeSlixL: - saved = nfc_device_save_slix_data(file, dev, SlixFeatureSlixL, "SLIX-L"); - break; - case NfcVTypeSlix2: - saved = nfc_device_save_slix_data(file, dev, SlixFeatureSlix2, "SLIX2"); - break; - default: - break; - } - } while(false); +void nfc_device_set_loading_callback( + NfcDevice* instance, + NfcLoadingCallback callback, + void* context) { + furi_assert(instance); + furi_assert(callback); - return saved; + instance->loading_callback = callback; + instance->loading_callback_context = context; } -bool nfc_device_load_nfcv_data(FlipperFormat* file, NfcDevice* dev) { - bool parsed = false; - NfcVData* data = &dev->dev_data.nfcv_data; +bool nfc_device_save(NfcDevice* instance, const char* path) { + furi_assert(instance); + furi_assert(instance->protocol < NfcProtocolNum); + furi_assert(path); - memset(data, 0x00, sizeof(NfcVData)); + bool saved = false; + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* ff = flipper_format_buffered_file_alloc(storage); + FuriString* temp_str = furi_string_alloc(); + + if(instance->loading_callback) { + instance->loading_callback(instance->loading_callback_context, true); + } do { - uint32_t temp_uint32 = 0; - uint8_t temp_value = 0; + // Open file + if(!flipper_format_buffered_file_open_always(ff, path)) break; - if(!flipper_format_read_hex(file, "DSFID", &(data->dsfid), 1)) { - FURI_LOG_D(TAG, "Failed reading DSFID"); - break; - } - if(!flipper_format_read_hex(file, "AFI", &(data->afi), 1)) { - FURI_LOG_D(TAG, "Failed reading AFI"); - break; - } - if(!flipper_format_read_hex(file, "IC Reference", &(data->ic_ref), 1)) { - FURI_LOG_D(TAG, "Failed reading IC Reference"); - break; - } - if(!flipper_format_read_uint32(file, "Block Count", &temp_uint32, 1)) { - FURI_LOG_D(TAG, "Failed reading Block Count"); - break; - } - data->block_num = temp_uint32; - if(!flipper_format_read_hex(file, "Block Size", &(data->block_size), 1)) { - FURI_LOG_D(TAG, "Failed reading Block Size"); - break; - } - if(!flipper_format_read_hex( - file, "Data Content", data->data, data->block_num * data->block_size)) { - FURI_LOG_D(TAG, "Failed reading Data Content"); + // Write header + if(!flipper_format_write_header_cstr(ff, NFC_FILE_HEADER, NFC_CURRENT_FORMAT_VERSION)) break; - } - /* optional, as added later */ - if(flipper_format_key_exist(file, "Security Status")) { - if(!flipper_format_read_hex( - file, "Security Status", data->security_status, 1 + data->block_num)) { - FURI_LOG_D(TAG, "Failed reading Security Status"); - break; + // Write allowed device types + furi_string_printf(temp_str, "%s can be ", NFC_DEVICE_TYPE_KEY); + for(NfcProtocol protocol = 0; protocol < NfcProtocolNum; ++protocol) { + furi_string_cat(temp_str, nfc_devices[protocol]->protocol_name); + if(protocol < NfcProtocolNum - 1) { + furi_string_cat(temp_str, ", "); } } - if(!flipper_format_read_hex(file, "Subtype", &temp_value, 1)) { - FURI_LOG_D(TAG, "Failed reading Subtype"); - break; - } - data->sub_type = temp_value; - switch(data->sub_type) { - case NfcVTypePlain: - parsed = true; - break; - case NfcVTypeSlix: - parsed = nfc_device_load_slix_data(file, dev, SlixFeatureSlix); - break; - case NfcVTypeSlixS: - parsed = nfc_device_load_slix_data(file, dev, SlixFeatureSlixS); - break; - case NfcVTypeSlixL: - parsed = nfc_device_load_slix_data(file, dev, SlixFeatureSlixL); - break; - case NfcVTypeSlix2: - parsed = nfc_device_load_slix_data(file, dev, SlixFeatureSlix2); - break; - default: + if(!flipper_format_write_comment(ff, temp_str)) break; + + // Write device type + if(!flipper_format_write_string_cstr( + ff, NFC_DEVICE_TYPE_KEY, nfc_devices[instance->protocol]->protocol_name)) break; - } - } while(false); - return parsed; -} + // Write UID + furi_string_printf(temp_str, "%s is common for all formats", NFC_DEVICE_UID_KEY); + if(!flipper_format_write_comment(ff, temp_str)) break; -static bool nfc_device_save_bank_card_data(FlipperFormat* file, NfcDevice* dev) { - bool saved = false; - EmvData* data = &dev->dev_data.emv_data; - uint32_t data_temp = 0; + size_t uid_len; + const uint8_t* uid = nfc_device_get_uid(instance, &uid_len); + if(!flipper_format_write_hex(ff, NFC_DEVICE_UID_KEY, uid, uid_len)) break; + + // Write protocol-dependent data + if(!nfc_devices[instance->protocol]->save(instance->protocol_data, ff)) break; - do { - // Write Bank card specific data - if(!flipper_format_write_comment_cstr(file, "Bank card specific data")) break; - if(!flipper_format_write_hex(file, "AID", data->aid, data->aid_len)) break; - if(!flipper_format_write_string_cstr(file, "Name", data->name)) break; - if(!flipper_format_write_hex(file, "Number", data->number, data->number_len)) break; - if(data->exp_mon) { - uint8_t exp_data[2] = {data->exp_mon, data->exp_year}; - if(!flipper_format_write_hex(file, "Exp data", exp_data, sizeof(exp_data))) break; - } - if(data->country_code) { - data_temp = data->country_code; - if(!flipper_format_write_uint32(file, "Country code", &data_temp, 1)) break; - } - if(data->currency_code) { - data_temp = data->currency_code; - if(!flipper_format_write_uint32(file, "Currency code", &data_temp, 1)) break; - } saved = true; } while(false); - return saved; -} - -bool nfc_device_load_bank_card_data(FlipperFormat* file, NfcDevice* dev) { - bool parsed = false; - EmvData* data = &dev->dev_data.emv_data; - memset(data, 0, sizeof(EmvData)); - uint32_t data_cnt = 0; - FuriString* temp_str; - temp_str = furi_string_alloc(); - - do { - // Load essential data - if(!flipper_format_get_value_count(file, "AID", &data_cnt)) break; - data->aid_len = data_cnt; - if(!flipper_format_read_hex(file, "AID", data->aid, data->aid_len)) break; - if(!flipper_format_read_string(file, "Name", temp_str)) break; - strlcpy(data->name, furi_string_get_cstr(temp_str), sizeof(data->name)); - if(!flipper_format_get_value_count(file, "Number", &data_cnt)) break; - data->number_len = data_cnt; - if(!flipper_format_read_hex(file, "Number", data->number, data->number_len)) break; - parsed = true; - // Load optional data - uint8_t exp_data[2] = {}; - if(flipper_format_read_hex(file, "Exp data", exp_data, 2)) { - data->exp_mon = exp_data[0]; - data->exp_year = exp_data[1]; - } - if(flipper_format_read_uint32(file, "Country code", &data_cnt, 1)) { - data->country_code = data_cnt; - } - if(flipper_format_read_uint32(file, "Currency code", &data_cnt, 1)) { - data->currency_code = data_cnt; - } - } while(false); + if(instance->loading_callback) { + instance->loading_callback(instance->loading_callback_context, false); + } furi_string_free(temp_str); - return parsed; -} + flipper_format_free(ff); + furi_record_close(RECORD_STORAGE); -static void nfc_device_write_mifare_classic_block( - FuriString* block_str, - MfClassicData* data, - uint8_t block_num) { - furi_string_reset(block_str); - bool is_sec_trailer = mf_classic_is_sector_trailer(block_num); - if(is_sec_trailer) { - uint8_t sector_num = mf_classic_get_sector_by_block(block_num); - MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, sector_num); - // Write key A - for(size_t i = 0; i < sizeof(sec_tr->key_a); i++) { - if(mf_classic_is_key_found(data, sector_num, MfClassicKeyA)) { - furi_string_cat_printf(block_str, "%02X ", sec_tr->key_a[i]); - } else { - furi_string_cat_printf(block_str, "?? "); - } - } - // Write Access bytes - for(size_t i = 0; i < MF_CLASSIC_ACCESS_BYTES_SIZE; i++) { - if(mf_classic_is_block_read(data, block_num)) { - furi_string_cat_printf(block_str, "%02X ", sec_tr->access_bits[i]); - } else { - furi_string_cat_printf(block_str, "?? "); - } - } - // Write key B - for(size_t i = 0; i < sizeof(sec_tr->key_b); i++) { - if(mf_classic_is_key_found(data, sector_num, MfClassicKeyB)) { - furi_string_cat_printf(block_str, "%02X ", sec_tr->key_b[i]); - } else { - furi_string_cat_printf(block_str, "?? "); - } - } - } else { - // Write data block - for(size_t i = 0; i < MF_CLASSIC_BLOCK_SIZE; i++) { - if(mf_classic_is_block_read(data, block_num)) { - furi_string_cat_printf(block_str, "%02X ", data->block[block_num].value[i]); - } else { - furi_string_cat_printf(block_str, "?? "); - } - } - } - furi_string_trim(block_str); + return saved; } -static bool nfc_device_save_mifare_classic_data(FlipperFormat* file, NfcDevice* dev) { - bool saved = false; - MfClassicData* data = &dev->dev_data.mf_classic_data; - FuriString* temp_str; - temp_str = furi_string_alloc(); - uint16_t blocks = 0; +static bool nfc_device_load_uid( + FlipperFormat* ff, + uint8_t* uid, + uint32_t* uid_len, + const uint32_t uid_maxlen) { + bool loaded = false; - // Save Mifare Classic specific data do { - if(!flipper_format_write_comment_cstr(file, "Mifare Classic specific data")) break; - - if(data->type == MfClassicTypeMini) { - if(!flipper_format_write_string_cstr(file, "Mifare Classic type", "MINI")) break; - blocks = 20; - } else if(data->type == MfClassicType1k) { - if(!flipper_format_write_string_cstr(file, "Mifare Classic type", "1K")) break; - blocks = 64; - } else if(data->type == MfClassicType4k) { - if(!flipper_format_write_string_cstr(file, "Mifare Classic type", "4K")) break; - blocks = 256; - } - if(!flipper_format_write_uint32( - file, "Data format version", &nfc_mifare_classic_data_format_version, 1)) - break; - if(!flipper_format_write_comment_cstr( - file, "Mifare Classic blocks, \'??\' means unknown data")) - break; - bool block_saved = true; - FuriString* block_str; - block_str = furi_string_alloc(); - for(size_t i = 0; i < blocks; i++) { - furi_string_printf(temp_str, "Block %d", i); - nfc_device_write_mifare_classic_block(block_str, data, i); - if(!flipper_format_write_string(file, furi_string_get_cstr(temp_str), block_str)) { - block_saved = false; - break; - } - } - furi_string_free(block_str); - if(!block_saved) break; - saved = true; + uint32_t uid_len_current; + if(!flipper_format_get_value_count(ff, NFC_DEVICE_UID_KEY, &uid_len_current)) break; + if(uid_len_current > uid_maxlen) break; + if(!flipper_format_read_hex(ff, NFC_DEVICE_UID_KEY, uid, uid_len_current)) break; + + *uid_len = uid_len_current; + loaded = true; } while(false); - furi_string_free(temp_str); - return saved; + return loaded; } -static void nfc_device_load_mifare_classic_block( - FuriString* block_str, - MfClassicData* data, - uint8_t block_num) { - furi_string_trim(block_str); - MfClassicBlock block_tmp = {}; - bool is_sector_trailer = mf_classic_is_sector_trailer(block_num); - uint8_t sector_num = mf_classic_get_sector_by_block(block_num); - uint16_t block_unknown_bytes_mask = 0; - - furi_string_trim(block_str); - for(size_t i = 0; i < MF_CLASSIC_BLOCK_SIZE; i++) { - char hi = furi_string_get_char(block_str, 3 * i); - char low = furi_string_get_char(block_str, 3 * i + 1); - uint8_t byte = 0; - if(hex_char_to_uint8(hi, low, &byte)) { - block_tmp.value[i] = byte; - } else { - FURI_BIT_SET(block_unknown_bytes_mask, i); - } - } +static bool nfc_device_load_unified(NfcDevice* instance, FlipperFormat* ff, uint32_t version) { + bool loaded = false; - if(block_unknown_bytes_mask == 0xffff) { - // All data is unknown, exit - return; - } - - if(is_sector_trailer) { - MfClassicSectorTrailer* sec_tr_tmp = (MfClassicSectorTrailer*)&block_tmp; - // Load Key A - // Key A mask 0b0000000000111111 = 0x003f - if((block_unknown_bytes_mask & 0x003f) == 0) { - uint64_t key = nfc_util_bytes2num(sec_tr_tmp->key_a, sizeof(sec_tr_tmp->key_a)); - mf_classic_set_key_found(data, sector_num, MfClassicKeyA, key); - } - // Load Access Bits - // Access bits mask 0b0000001111000000 = 0x03c0 - if((block_unknown_bytes_mask & 0x03c0) == 0) { - mf_classic_set_block_read(data, block_num, &block_tmp); - } - // Load Key B - // Key B mask 0b1111110000000000 = 0xfc00 - if((block_unknown_bytes_mask & 0xfc00) == 0) { - uint64_t key = nfc_util_bytes2num(sec_tr_tmp->key_b, sizeof(sec_tr_tmp->key_b)); - mf_classic_set_key_found(data, sector_num, MfClassicKeyB, key); - } - } else { - if(block_unknown_bytes_mask == 0) { - mf_classic_set_block_read(data, block_num, &block_tmp); - } - } -} - -static bool nfc_device_load_mifare_classic_data(FlipperFormat* file, NfcDevice* dev) { - bool parsed = false; - MfClassicData* data = &dev->dev_data.mf_classic_data; - FuriString* temp_str; - uint32_t data_format_version = 0; - temp_str = furi_string_alloc(); - uint16_t data_blocks = 0; - memset(data, 0, sizeof(MfClassicData)); + FuriString* temp_str = furi_string_alloc(); do { - // Read Mifare Classic type - if(!flipper_format_read_string(file, "Mifare Classic type", temp_str)) break; - if(!furi_string_cmp(temp_str, "MINI")) { - data->type = MfClassicTypeMini; - data_blocks = 20; - } else if(!furi_string_cmp(temp_str, "1K")) { - data->type = MfClassicType1k; - data_blocks = 64; - } else if(!furi_string_cmp(temp_str, "4K")) { - data->type = MfClassicType4k; - data_blocks = 256; - } else { - break; - } - - bool old_format = false; - // 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 compatibility - if(!flipper_format_rewind(file)) break; - old_format = true; - } else { - if(data_format_version < nfc_mifare_classic_data_format_version) { - old_format = true; - } - } + // Read Nfc device type + if(!flipper_format_read_string(ff, NFC_DEVICE_TYPE_KEY, temp_str)) break; - // Read Mifare Classic blocks - bool block_read = true; - FuriString* block_str; - block_str = furi_string_alloc(); - for(size_t i = 0; i < data_blocks; i++) { - furi_string_printf(temp_str, "Block %d", i); - if(!flipper_format_read_string(file, furi_string_get_cstr(temp_str), block_str)) { - block_read = false; + // Detect protocol + NfcProtocol protocol; + for(protocol = 0; protocol < NfcProtocolNum; ++protocol) { + if(furi_string_equal(temp_str, nfc_devices[protocol]->protocol_name)) { break; } - nfc_device_load_mifare_classic_block(block_str, data, i); - } - furi_string_free(block_str); - if(!block_read) break; - - // Set keys and blocks as unknown for backward compatibility - if(old_format) { - data->key_a_mask = 0ULL; - data->key_b_mask = 0ULL; - memset(data->block_read_mask, 0, sizeof(data->block_read_mask)); } - parsed = true; - } while(false); - - furi_string_free(temp_str); - return parsed; -} - -static void nfc_device_get_key_cache_file_path(NfcDevice* dev, FuriString* file_path) { - uint8_t* uid = dev->dev_data.nfc_data.uid; - uint8_t uid_len = dev->dev_data.nfc_data.uid_len; - furi_string_set(file_path, NFC_DEVICE_KEYS_FOLDER "/"); - for(size_t i = 0; i < uid_len; i++) { - furi_string_cat_printf(file_path, "%02X", uid[i]); - } - furi_string_cat_printf(file_path, NFC_DEVICE_KEYS_EXTENSION); -} + if(protocol == NfcProtocolNum) break; -static bool nfc_device_save_mifare_classic_keys(NfcDevice* dev) { - FlipperFormat* file = flipper_format_file_alloc(dev->storage); - MfClassicData* data = &dev->dev_data.mf_classic_data; - FuriString* temp_str; - temp_str = furi_string_alloc(); + nfc_device_clear(instance); - nfc_device_get_key_cache_file_path(dev, temp_str); - bool save_success = false; - do { - if(!storage_simply_mkdir(dev->storage, NFC_DEVICE_KEYS_FOLDER)) break; - if(!storage_simply_remove(dev->storage, furi_string_get_cstr(temp_str))) break; - if(!flipper_format_file_open_always(file, furi_string_get_cstr(temp_str))) break; - if(!flipper_format_write_header_cstr(file, nfc_keys_file_header, nfc_keys_file_version)) - break; - if(data->type == MfClassicTypeMini) { - if(!flipper_format_write_string_cstr(file, "Mifare Classic type", "MINI")) break; - } else if(data->type == MfClassicType1k) { - if(!flipper_format_write_string_cstr(file, "Mifare Classic type", "1K")) break; - } else if(data->type == MfClassicType4k) { - if(!flipper_format_write_string_cstr(file, "Mifare Classic type", "4K")) 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; - uint8_t sector_num = mf_classic_get_total_sectors_num(data->type); - bool key_save_success = true; - for(size_t i = 0; (i < sector_num) && (key_save_success); i++) { - MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, i); - if(FURI_BIT(data->key_a_mask, i)) { - furi_string_printf(temp_str, "Key A sector %d", i); - key_save_success = flipper_format_write_hex( - file, furi_string_get_cstr(temp_str), sec_tr->key_a, 6); - } - if(!key_save_success) break; - if(FURI_BIT(data->key_b_mask, i)) { - furi_string_printf(temp_str, "Key B sector %d", i); - key_save_success = flipper_format_write_hex( - file, furi_string_get_cstr(temp_str), sec_tr->key_b, 6); - } - } - save_success = key_save_success; - } while(false); + instance->protocol = protocol; + instance->protocol_data = nfc_devices[protocol]->alloc(); - flipper_format_free(file); - furi_string_free(temp_str); - return save_success; -} + // Load UID + uint8_t uid[NFC_DEVICE_UID_MAX_LEN]; + uint32_t uid_len; -bool nfc_device_load_key_cache(NfcDevice* dev) { - furi_assert(dev); - FuriString* temp_str; - temp_str = furi_string_alloc(); + if(!nfc_device_load_uid(ff, uid, &uid_len, NFC_DEVICE_UID_MAX_LEN)) break; + if(!nfc_device_set_uid(instance, uid, uid_len)) break; - MfClassicData* data = &dev->dev_data.mf_classic_data; - nfc_device_get_key_cache_file_path(dev, temp_str); - FlipperFormat* file = flipper_format_file_alloc(dev->storage); + // Load data + if(!nfc_devices[protocol]->load(instance->protocol_data, ff, version)) break; - bool load_success = false; - do { - if(storage_common_stat(dev->storage, furi_string_get_cstr(temp_str), NULL) != FSE_OK) - break; - if(!flipper_format_file_open_existing(file, furi_string_get_cstr(temp_str))) break; - uint32_t version = 0; - if(!flipper_format_read_header(file, temp_str, &version)) break; - if(furi_string_cmp_str(temp_str, nfc_keys_file_header)) break; - if(version != nfc_keys_file_version) break; - if(!flipper_format_read_string(file, "Mifare Classic type", temp_str)) break; - if(!furi_string_cmp(temp_str, "MINI")) { - data->type = MfClassicTypeMini; - } else if(!furi_string_cmp(temp_str, "1K")) { - data->type = MfClassicType1k; - } else if(!furi_string_cmp(temp_str, "4K")) { - data->type = MfClassicType4k; - } else { - 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; - uint8_t sectors = mf_classic_get_total_sectors_num(data->type); - bool key_read_success = true; - for(size_t i = 0; (i < sectors) && (key_read_success); i++) { - MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, i); - if(FURI_BIT(data->key_a_mask, i)) { - furi_string_printf(temp_str, "Key A sector %d", i); - key_read_success = flipper_format_read_hex( - file, furi_string_get_cstr(temp_str), sec_tr->key_a, 6); - } - if(!key_read_success) break; - if(FURI_BIT(data->key_b_mask, i)) { - furi_string_printf(temp_str, "Key B sector %d", i); - key_read_success = flipper_format_read_hex( - file, furi_string_get_cstr(temp_str), sec_tr->key_b, 6); - } - } - load_success = key_read_success; + loaded = true; } while(false); - furi_string_free(temp_str); - flipper_format_free(file); + if(!loaded) { + nfc_device_clear(instance); + } - return load_success; + furi_string_free(temp_str); + return loaded; } -void nfc_device_set_name(NfcDevice* dev, const char* name) { - furi_assert(dev); +static bool nfc_device_load_legacy(NfcDevice* instance, FlipperFormat* ff, uint32_t version) { + bool loaded = false; - strlcpy(dev->dev_name, name, NFC_DEV_NAME_MAX_LEN); -} + FuriString* temp_str = furi_string_alloc(); -static void nfc_device_get_path_without_ext(FuriString* orig_path, FuriString* shadow_path) { - // TODO: this won't work if there is ".nfc" anywhere in the path other than - // at the end - size_t ext_start = furi_string_search(orig_path, NFC_APP_FILENAME_EXTENSION); - furi_string_set_n(shadow_path, orig_path, 0, ext_start); -} + do { + // Read Nfc device type + if(!flipper_format_read_string(ff, NFC_DEVICE_TYPE_KEY, temp_str)) break; -static void nfc_device_get_shadow_path(FuriString* orig_path, FuriString* shadow_path) { - nfc_device_get_path_without_ext(orig_path, shadow_path); - furi_string_cat_printf(shadow_path, "%s", NFC_APP_SHADOW_EXTENSION); -} + nfc_device_clear(instance); -static void nfc_device_get_folder_from_path(FuriString* path, FuriString* folder) { - size_t last_slash = furi_string_search_rchar(path, '/'); - if(last_slash == FURI_STRING_FAILURE) { - // No slashes in the path, treat the whole path as a folder - furi_string_set(folder, path); - } else { - furi_string_set_n(folder, path, 0, last_slash); - } -} + // Detect protocol + for(NfcProtocol protocol = 0; protocol < NfcProtocolNum; protocol++) { + instance->protocol = protocol; + instance->protocol_data = nfc_devices[protocol]->alloc(); -bool nfc_device_save(NfcDevice* dev, const char* dev_name) { - furi_assert(dev); + // Verify protocol + if(nfc_devices[protocol]->verify(instance->protocol_data, temp_str)) { + uint8_t uid[NFC_DEVICE_UID_MAX_LEN]; + uint32_t uid_len; - bool saved = false; - FlipperFormat* file = flipper_format_file_alloc(dev->storage); - FuriHalNfcDevData* data = &dev->dev_data.nfc_data; - FuriString* temp_str; - temp_str = furi_string_alloc(); + // Load data + loaded = nfc_device_load_uid(ff, uid, &uid_len, NFC_DEVICE_UID_MAX_LEN) && + nfc_device_set_uid(instance, uid, uid_len) && + nfc_devices[protocol]->load(instance->protocol_data, ff, version); + break; + } - do { - // Create directory if necessary - FuriString* folder = furi_string_alloc(); - // Get folder from filename (filename is in the form of "folder/filename.nfc", so the folder is "folder/") - furi_string_set(temp_str, dev_name); - // Get folder from filename - nfc_device_get_folder_from_path(temp_str, folder); - FURI_LOG_I("Nfc", "Saving to folder %s", furi_string_get_cstr(folder)); - if(!storage_simply_mkdir(dev->storage, furi_string_get_cstr(folder))) { - FURI_LOG_E("Nfc", "Failed to create folder %s", furi_string_get_cstr(folder)); - break; - } - furi_string_free(folder); - // First remove nfc device file if it was saved - // Open file - if(!flipper_format_file_open_always(file, furi_string_get_cstr(temp_str))) break; - // Write header - if(!flipper_format_write_header_cstr(file, nfc_file_header, nfc_file_version)) break; - // Write nfc device type - if(!flipper_format_write_comment_cstr( - file, "Nfc device type can be UID, Mifare Ultralight, Mifare Classic or ISO15693")) - break; - nfc_device_prepare_format_string(dev, temp_str); - if(!flipper_format_write_string(file, "Device type", temp_str)) break; - // Write UID - if(!flipper_format_write_comment_cstr(file, "UID is common for all formats")) break; - if(!flipper_format_write_hex(file, "UID", data->uid, data->uid_len)) break; - - if(dev->format != NfcDeviceSaveFormatNfcV) { - // Write ATQA, SAK - if(!flipper_format_write_comment_cstr(file, "ISO14443 specific fields")) break; - // Save ATQA in MSB order for correct companion apps display - uint8_t atqa[2] = {data->atqa[1], data->atqa[0]}; - if(!flipper_format_write_hex(file, "ATQA", atqa, 2)) break; - if(!flipper_format_write_hex(file, "SAK", &data->sak, 1)) break; + nfc_device_clear(instance); } - // Save more data if necessary - if(dev->format == NfcDeviceSaveFormatMifareUl) { - if(!nfc_device_save_mifare_ul_data(file, dev)) break; - } else if(dev->format == NfcDeviceSaveFormatMifareDesfire) { - if(!nfc_device_save_mifare_df_data(file, dev)) break; - } else if(dev->format == NfcDeviceSaveFormatNfcV) { - if(!nfc_device_save_nfcv_data(file, dev)) break; - } else if(dev->format == NfcDeviceSaveFormatBankCard) { - if(!nfc_device_save_bank_card_data(file, dev)) break; - } else if(dev->format == NfcDeviceSaveFormatMifareClassic) { - // Save data - if(!nfc_device_save_mifare_classic_data(file, dev)) break; - // Save keys cache - if(!nfc_device_save_mifare_classic_keys(dev)) break; - } - saved = true; - } while(0); + } while(false); - if(!saved) { //-V547 - dialog_message_show_storage_error(dev->dialogs, "Can not save\nkey file"); - } furi_string_free(temp_str); - flipper_format_free(file); - return saved; + return loaded; } -bool nfc_device_save_shadow(NfcDevice* dev, const char* path) { - dev->shadow_file_exist = true; - // Replace extension from .nfc to .shd if necessary - FuriString* orig_path = furi_string_alloc(); - furi_string_set_str(orig_path, path); - FuriString* shadow_path = furi_string_alloc(); - nfc_device_get_shadow_path(orig_path, shadow_path); +bool nfc_device_load(NfcDevice* instance, const char* path) { + furi_assert(instance); + furi_assert(path); - bool file_saved = nfc_device_save(dev, furi_string_get_cstr(shadow_path)); - furi_string_free(orig_path); - furi_string_free(shadow_path); + bool loaded = false; + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* ff = flipper_format_buffered_file_alloc(storage); - return file_saved; -} - -static bool nfc_device_load_data(NfcDevice* dev, FuriString* path, bool show_dialog) { - bool parsed = false; - FlipperFormat* file = flipper_format_file_alloc(dev->storage); - FuriHalNfcDevData* data = &dev->dev_data.nfc_data; - uint32_t data_cnt = 0; FuriString* temp_str; temp_str = furi_string_alloc(); - bool deprecated_version = false; - - // Version 2 of file format had ATQA bytes swapped - uint32_t version_with_lsb_atqa = 2; - if(dev->loading_cb) { - dev->loading_cb(dev->loading_cb_ctx, true); + if(instance->loading_callback) { + instance->loading_callback(instance->loading_callback_context, true); } do { - // Check existence of shadow file - nfc_device_get_shadow_path(path, temp_str); - dev->shadow_file_exist = - storage_common_stat(dev->storage, furi_string_get_cstr(temp_str), NULL) == FSE_OK; - // Open shadow file if it exists. If not - open original - if(dev->shadow_file_exist) { - if(!flipper_format_file_open_existing(file, furi_string_get_cstr(temp_str))) break; - } else { - if(!flipper_format_file_open_existing(file, furi_string_get_cstr(path))) break; - } + if(!flipper_format_buffered_file_open_existing(ff, path)) break; + // Read and verify file header uint32_t version = 0; - if(!flipper_format_read_header(file, temp_str, &version)) break; - if(furi_string_cmp_str(temp_str, nfc_file_header)) break; - if(version != nfc_file_version) { - if(version < version_with_lsb_atqa) { - deprecated_version = true; - break; - } - } - // Read Nfc device type - if(!flipper_format_read_string(file, "Device type", temp_str)) break; - if(!nfc_device_parse_format_string(dev, temp_str)) break; - // Read and parse UID, ATQA and SAK - if(!flipper_format_get_value_count(file, "UID", &data_cnt)) break; - if(!(data_cnt == 4 || data_cnt == 7 || data_cnt == 8)) break; - data->uid_len = data_cnt; - if(!flipper_format_read_hex(file, "UID", data->uid, data->uid_len)) break; - if(dev->format != NfcDeviceSaveFormatNfcV) { - if(version == version_with_lsb_atqa) { - if(!flipper_format_read_hex(file, "ATQA", data->atqa, 2)) break; - } else { - uint8_t atqa[2] = {}; - if(!flipper_format_read_hex(file, "ATQA", atqa, 2)) break; - data->atqa[0] = atqa[1]; - data->atqa[1] = atqa[0]; - } - if(!flipper_format_read_hex(file, "SAK", &data->sak, 1)) break; - } - // Load CUID - uint8_t* cuid_start = data->uid; - if(data->uid_len == 7) { - cuid_start = &data->uid[3]; - } - data->cuid = (cuid_start[0] << 24) | (cuid_start[1] << 16) | (cuid_start[2] << 8) | - (cuid_start[3]); - // Parse other data - if(dev->format == NfcDeviceSaveFormatMifareUl) { - if(!nfc_device_load_mifare_ul_data(file, dev)) break; - } else if(dev->format == NfcDeviceSaveFormatMifareClassic) { - if(!nfc_device_load_mifare_classic_data(file, dev)) break; - } else if(dev->format == NfcDeviceSaveFormatMifareDesfire) { - if(!nfc_device_load_mifare_df_data(file, dev)) break; - } else if(dev->format == NfcDeviceSaveFormatNfcV) { - if(!nfc_device_load_nfcv_data(file, dev)) break; - } else if(dev->format == NfcDeviceSaveFormatBankCard) { - if(!nfc_device_load_bank_card_data(file, dev)) break; - } - parsed = true; - } while(false); - - if(dev->loading_cb) { - dev->loading_cb(dev->loading_cb_ctx, false); - } - - if((!parsed) && (show_dialog)) { - if(deprecated_version) { - dialog_message_show_storage_error(dev->dialogs, "File format deprecated"); - } else { - dialog_message_show_storage_error(dev->dialogs, "Can not parse\nfile"); - } - } + if(!flipper_format_read_header(ff, temp_str, &version)) break; - furi_string_free(temp_str); - flipper_format_free(file); - return parsed; -} - -bool nfc_device_load(NfcDevice* dev, const char* file_path, bool show_dialog) { - furi_assert(dev); - furi_assert(file_path); - - // Load device data - furi_string_set(dev->load_path, file_path); - bool dev_load = nfc_device_load_data(dev, dev->load_path, show_dialog); - if(dev_load) { - // Set device name - FuriString* filename; - filename = furi_string_alloc(); - path_extract_filename_no_ext(file_path, filename); - nfc_device_set_name(dev, furi_string_get_cstr(filename)); - furi_string_free(filename); - } - - return dev_load; -} - -bool nfc_file_select(NfcDevice* dev) { - furi_assert(dev); - const char* folder = furi_string_get_cstr(dev->folder); - - // Input events and views are managed by file_browser - - const DialogsFileBrowserOptions browser_options = { - .extension = NFC_APP_FILENAME_EXTENSION, - .skip_assets = true, - .hide_dot_files = true, - .icon = &I_Nfc_10px, - .hide_ext = true, - .item_loader_callback = NULL, - .item_loader_context = NULL, - .base_path = folder, - }; - - bool res = - dialog_file_browser_show(dev->dialogs, dev->load_path, dev->load_path, &browser_options); - - if(res) { - FuriString* filename; - filename = furi_string_alloc(); - path_extract_filename(dev->load_path, filename, true); - strncpy(dev->dev_name, furi_string_get_cstr(filename), NFC_DEV_NAME_MAX_LEN); - res = nfc_device_load_data(dev, dev->load_path, true); - if(res) { - nfc_device_set_name(dev, dev->dev_name); - } - furi_string_free(filename); - } - - return res; -} - -void nfc_device_data_clear(NfcDeviceData* dev_data) { - if(dev_data->protocol == NfcDeviceProtocolMifareDesfire) { - mf_df_clear(&dev_data->mf_df_data); - } else if(dev_data->protocol == NfcDeviceProtocolMifareClassic) { - memset(&dev_data->mf_classic_data, 0, sizeof(MfClassicData)); - } else if(dev_data->protocol == NfcDeviceProtocolMifareUl) { - mf_ul_reset(&dev_data->mf_ul_data); - } else if(dev_data->protocol == NfcDeviceProtocolEMV) { - memset(&dev_data->emv_data, 0, sizeof(EmvData)); - } + if(furi_string_cmp_str(temp_str, NFC_FILE_HEADER)) break; + if(version < NFC_MINIMUM_SUPPORTED_FORMAT_VERSION) break; - furi_string_reset(dev_data->parsed_data); + // Select loading method + loaded = (version < NFC_UNIFIED_FORMAT_VERSION) ? + nfc_device_load_legacy(instance, ff, version) : + nfc_device_load_unified(instance, ff, version); - memset(&dev_data->nfc_data, 0, sizeof(FuriHalNfcDevData)); - dev_data->protocol = NfcDeviceProtocolUnknown; - furi_string_reset(dev_data->parsed_data); -} - -void nfc_device_clear(NfcDevice* dev) { - furi_assert(dev); - - nfc_device_set_name(dev, ""); - nfc_device_data_clear(&dev->dev_data); - dev->format = NfcDeviceSaveFormatUid; - furi_string_reset(dev->load_path); -} - -bool nfc_device_delete(NfcDevice* dev, bool use_load_path) { - furi_assert(dev); - - bool deleted = false; - FuriString* file_path; - file_path = furi_string_alloc(); - - do { - // Delete original file - if(use_load_path && !furi_string_empty(dev->load_path)) { - furi_string_set(file_path, dev->load_path); - } else { - furi_string_printf( - file_path, - "%s/%s%s", - furi_string_get_cstr(dev->folder), - dev->dev_name, - NFC_APP_FILENAME_EXTENSION); - } - if(!storage_simply_remove(dev->storage, furi_string_get_cstr(file_path))) break; - // Delete shadow file if it exists - if(dev->shadow_file_exist) { - if(use_load_path && !furi_string_empty(dev->load_path)) { - nfc_device_get_shadow_path(dev->load_path, file_path); - } else { - furi_string_printf( - file_path, - "%s/%s%s", - furi_string_get_cstr(dev->folder), - dev->dev_name, - NFC_APP_SHADOW_EXTENSION); - } - if(!storage_simply_remove(dev->storage, furi_string_get_cstr(file_path))) break; - } - deleted = true; - } while(0); + } while(false); - if(!deleted) { - dialog_message_show_storage_error(dev->dialogs, "Can not remove file"); + if(instance->loading_callback) { + instance->loading_callback(instance->loading_callback_context, false); } - furi_string_free(file_path); - return deleted; -} - -bool nfc_device_restore(NfcDevice* dev, bool use_load_path) { - furi_assert(dev); - furi_assert(dev->shadow_file_exist); - - bool restored = false; - FuriString* path; - - path = furi_string_alloc(); - - do { - if(use_load_path && !furi_string_empty(dev->load_path)) { - nfc_device_get_shadow_path(dev->load_path, path); - } else { - furi_string_printf( - path, - "%s/%s%s", - furi_string_get_cstr(dev->folder), - dev->dev_name, - NFC_APP_SHADOW_EXTENSION); - } - if(!storage_simply_remove(dev->storage, furi_string_get_cstr(path))) break; - dev->shadow_file_exist = false; - if(use_load_path && !furi_string_empty(dev->load_path)) { - furi_string_set(path, dev->load_path); - } else { - furi_string_printf( - path, - "%s/%s%s", - furi_string_get_cstr(dev->folder), - dev->dev_name, - NFC_APP_FILENAME_EXTENSION); - } - if(!nfc_device_load_data(dev, path, true)) break; - restored = true; - } while(0); - - furi_string_free(path); - return restored; -} - -void nfc_device_set_loading_callback(NfcDevice* dev, NfcLoadingCallback callback, void* context) { - furi_assert(dev); + furi_string_free(temp_str); + flipper_format_free(ff); + furi_record_close(RECORD_STORAGE); - dev->loading_cb = callback; - dev->loading_cb_ctx = context; + return loaded; } diff --git a/lib/nfc/nfc_device.h b/lib/nfc/nfc_device.h index 76de0b61c64..6636e7c7683 100644 --- a/lib/nfc/nfc_device.h +++ b/lib/nfc/nfc_device.h @@ -1,126 +1,265 @@ +/** + * @file nfc_device.h + * @brief Abstract interface for managing NFC device data. + * + * Under the hood, it makes use of the protocol-specific functions that each one of them provides + * and abstracts it with a protocol-independent API. + * + * It does not perform any signal processing, but merely serves as a container with some handy + * operations such as loading and saving from and to a file. + */ #pragma once +#include #include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "protocols/nfc_device_base.h" +#include "protocols/nfc_protocol.h" #ifdef __cplusplus extern "C" { #endif -#define NFC_DEV_NAME_MAX_LEN 22 -#define NFC_READER_DATA_MAX_SIZE 64 -#define NFC_DICT_KEY_BATCH_SIZE 10 - -#define NFC_APP_FILENAME_PREFIX "NFC" -#define NFC_APP_FILENAME_EXTENSION ".nfc" -#define NFC_APP_SHADOW_EXTENSION ".shd" +/** + * @brief NfcDevice opaque type definition. + */ +typedef struct NfcDevice NfcDevice; +/** + * @brief Loading callback function signature. + * + * A function with such signature can be set as a callback to indicate + * the completion (or a failure) of nfc_device_load() and nfc_device_save() functions. + * + * This facility is commonly used to control GUI elements, such as progress dialogs. + * + * @param[in] context user-defined context that was passed in nfc_device_set_loading_callback(). + * @param[in] state true if the data was loaded successfully, false otherwise. + */ typedef void (*NfcLoadingCallback)(void* context, bool state); -typedef enum { - NfcDeviceProtocolUnknown, - NfcDeviceProtocolEMV, - NfcDeviceProtocolMifareUl, - NfcDeviceProtocolMifareClassic, - NfcDeviceProtocolMifareDesfire, - NfcDeviceProtocolNfcV -} NfcProtocol; - -typedef enum { - NfcDeviceSaveFormatUid, - NfcDeviceSaveFormatBankCard, - NfcDeviceSaveFormatMifareUl, - NfcDeviceSaveFormatMifareClassic, - NfcDeviceSaveFormatMifareDesfire, - NfcDeviceSaveFormatNfcV, -} NfcDeviceSaveFormat; - -typedef struct { - uint8_t data[NFC_READER_DATA_MAX_SIZE]; - uint16_t size; -} NfcReaderRequestData; - -typedef struct { - MfClassicDict* dict; - uint8_t current_sector; -} NfcMfClassicDictAttackData; - -typedef enum { - NfcReadModeAuto, - NfcReadModeMfClassic, - NfcReadModeMfUltralight, - NfcReadModeMfDesfire, - NfcReadModeNFCA, -} NfcReadMode; - -typedef struct { - FuriHalNfcDevData nfc_data; - NfcProtocol protocol; - NfcReadMode read_mode; - union { - NfcReaderRequestData reader_data; - NfcMfClassicDictAttackData mf_classic_dict_attack_data; - MfUltralightAuth mf_ul_auth; - }; - union { - EmvData emv_data; - MfUltralightData mf_ul_data; - MfClassicData mf_classic_data; - MifareDesfireData mf_df_data; - NfcVData nfcv_data; - }; - FuriString* parsed_data; -} NfcDeviceData; - -typedef struct { - Storage* storage; - DialogsApp* dialogs; - NfcDeviceData dev_data; - char dev_name[NFC_DEV_NAME_MAX_LEN + 1]; - FuriString* load_path; - FuriString* folder; - NfcDeviceSaveFormat format; - bool shadow_file_exist; - - NfcLoadingCallback loading_cb; - void* loading_cb_ctx; -} NfcDevice; - +/** + * @brief Allocate an NfcDevice instance. + * + * A newly created instance does not hold any data and thus is considered invalid. The most common + * use case would be to set its data by calling nfc_device_set_data() right afterwards. + * + * @returns pointer to the allocated instance. + */ NfcDevice* nfc_device_alloc(); -void nfc_device_free(NfcDevice* nfc_dev); +/** + * @brief Delete an NfcDevice instance. + * + * @param[in,out] instance pointer to the instance to be deleted. + */ +void nfc_device_free(NfcDevice* instance); + +/** + * @brief Clear an NfcDevice instance. + * + * All data contained in the instance will be deleted and the instance itself will become invalid + * as if it was just allocated. + * + * @param[in,out] instance pointer to the instance to be cleared. + */ +void nfc_device_clear(NfcDevice* instance); + +/** + * @brief Reset an NfcDevice instance. + * + * The data contained in the instance will be reset according to the protocol-defined procedure. + * Unlike the nfc_device_clear() function, the instance will remain valid. + * + * @param[in,out] instance pointer to the instance to be reset. + */ +void nfc_device_reset(NfcDevice* instance); + +/** + * @brief Get the protocol identifier from an NfcDevice instance. + * + * If the instance is invalid, the return value will be NfcProtocolInvalid. + * + * @param[in] instance pointer to the instance to be queried. + * @returns protocol identifier contained in the instance. + */ +NfcProtocol nfc_device_get_protocol(const NfcDevice* instance); + +/** + * @brief Get the protocol-specific data from an NfcDevice instance. + * + * The protocol parameter's behaviour is a bit tricky. The function will check + * whether there is such a protocol somewhere in the protocol hierarchy and return + * the data exactly from that level. + * + * Example: Call nfc_device_get_data() on an instance with Mf DESFire protocol. + * The protocol hierarchy will look like the following: + * + * `Mf DESFire --> ISO14443-4A --> ISO14443-3A` + * + * Thus, the following values of the protocol parameter are valid: + * + * * NfcProtocolIso14443_3a + * * NfcProtocolIso14443_4a + * * NfcProtocolMfDesfire + * + * and passing them to the call would result in the respective data being returned. + * + * However, supplying a protocol identifier which is not in the hierarchy will + * result in a crash. This is to improve type safety. + * + * @param instance pointer to the instance to be queried + * @param protocol protocol identifier of the data to be retrieved. + * @returns pointer to the instance's data. + */ +const NfcDeviceData* nfc_device_get_data(const NfcDevice* instance, NfcProtocol protocol); + +/** + * @brief Get the protocol name by its identifier. + * + * This function does not require an instance as its return result depends only + * the protocol identifier. + * + * @param[in] protocol numeric identifier of the protocol in question. + * @returns pointer to a statically allocated string containing the protocol name. + */ +const char* nfc_device_get_protocol_name(NfcProtocol protocol); -void nfc_device_set_name(NfcDevice* dev, const char* name); +/** + * @brief Get the name of an NfcDevice instance. + * + * The return value may change depending on the instance's internal state and the name_type parameter. + * + * @param[in] instance pointer to the instance to be queried. + * @param[in] name_type type of the name to be displayed. + * @returns pointer to a statically allocated string containing the device name. + */ +const char* nfc_device_get_name(const NfcDevice* instance, NfcDeviceNameType name_type); -bool nfc_device_save(NfcDevice* dev, const char* dev_name); +/** + * @brief Get the unique identifier (UID) of an NfcDevice instance. + * + * The UID length is protocol-dependent. Additionally, a particular protocol might support + * several UID lengths. + * + * @param[in] instance pointer to the instance to be queried. + * @param[out] uid_len pointer to the variable to contain the UID length. + * @returns pointer to the byte array containing the instance's UID. + */ +const uint8_t* nfc_device_get_uid(const NfcDevice* instance, size_t* uid_len); -bool nfc_device_save_shadow(NfcDevice* dev, const char* dev_name); +/** + * @brief Set the unique identifier (UID) of an NfcDevice instance. + * + * The UID length must be supported by the instance's protocol. + * + * @param[in,out] instance pointer to the instance to be modified. + * @param[in] uid pointer to the byte array containing the new UID. + * @param[in] uid_len length of the UID. + * @return true if the UID was valid and set, false otherwise. + */ +bool nfc_device_set_uid(NfcDevice* instance, const uint8_t* uid, size_t uid_len); -bool nfc_device_load(NfcDevice* dev, const char* file_path, bool show_dialog); +/** + * @brief Set the data and protocol of an NfcDevice instance. + * + * Any data previously contained in the instance will be deleted. + * + * @param[in,out] instance pointer to the instance to be modified. + * @param[in] protocol numeric identifier of the data's protocol. + * @param[in] protocol_data pointer to the protocol-specific data. + */ +void nfc_device_set_data( + NfcDevice* instance, + NfcProtocol protocol, + const NfcDeviceData* protocol_data); -bool nfc_device_load_key_cache(NfcDevice* dev); +/** + * @brief Copy (export) the data contained in an NfcDevice instance to an outside NfcDeviceData instance. + * + * This function does the inverse of nfc_device_set_data(). -bool nfc_file_select(NfcDevice* dev); + * The protocol identifier passed as the protocol parameter MUST match the one + * stored in the instance, otherwise a crash will occur. + * This is to improve type safety. + * + * @param[in] instance pointer to the instance to be copied from. + * @param[in] protocol numeric identifier of the instance's protocol. + * @param[out] protocol_data pointer to the destination data. + */ +void nfc_device_copy_data( + const NfcDevice* instance, + NfcProtocol protocol, + NfcDeviceData* protocol_data); -void nfc_device_data_clear(NfcDeviceData* dev); +/** + * @brief Check whether an NfcDevice instance holds certain data. + * + * This function's behaviour is similar to nfc_device_is_equal(), with the difference + * that it takes NfcProtocol and NfcDeviceData* instead of the second NfcDevice*. + * + * The following code snippets [1] and [2] are equivalent: + * + * [1] + * ```c + * bool is_equal = nfc_device_is_equal(device1, device2); + * ``` + * [2] + * ```c + * NfcProtocol protocol = nfc_device_get_protocol(device2); + * const NfcDeviceData* data = nfc_device_get_data(device2, protocol); + * bool is_equal = nfc_device_is_equal_data(device1, protocol, data); + * ``` + * + * @param[in] instance pointer to the instance to be compared. + * @param[in] protocol protocol identifier of the data to be compared. + * @param[in] protocol_data pointer to the NFC device data to be compared. + * @returns true if the instance is of the right type and the data matches, false otherwise. + */ +bool nfc_device_is_equal_data( + const NfcDevice* instance, + NfcProtocol protocol, + const NfcDeviceData* protocol_data); -void nfc_device_clear(NfcDevice* dev); +/** + * @brief Compare two NfcDevice instances to determine whether they are equal. + * + * @param[in] instance pointer to the first instance to be compared. + * @param[in] other pointer to the second instance to be compared. + * @returns true if both instances are considered equal, false otherwise. + */ +bool nfc_device_is_equal(const NfcDevice* instance, const NfcDevice* other); -bool nfc_device_delete(NfcDevice* dev, bool use_load_path); +/** + * @brief Set the loading callback function. + * + * @param[in,out] instance pointer to the instance to be modified. + * @param[in] callback pointer to a function to be called when the load operation completes. + * @param[in] context pointer to a user-specific context (will be passed to the callback). + */ +void nfc_device_set_loading_callback( + NfcDevice* instance, + NfcLoadingCallback callback, + void* context); -bool nfc_device_restore(NfcDevice* dev, bool use_load_path); +/** + * @brief Save NFC device data form an NfcDevice instance to a file. + * + * @param[in] instance pointer to the instance to be saved. + * @param[in] path pointer to a character string with a full file path. + * @returns true if the data was successfully saved, false otherwise. + */ +bool nfc_device_save(NfcDevice* instance, const char* path); -void nfc_device_set_loading_callback(NfcDevice* dev, NfcLoadingCallback callback, void* context); +/** + * @brief Load NFC device data to an NfcDevice instance from a file. + * + * @param[in,out] instance pointer to the instance to be loaded into. + * @param[in] path pointer to a character string with a full file path. + * @returns true if the data was successfully loaded, false otherwise. + */ +bool nfc_device_load(NfcDevice* instance, const char* path); #ifdef __cplusplus } diff --git a/lib/nfc/nfc_device_i.c b/lib/nfc/nfc_device_i.c new file mode 100644 index 00000000000..e98b04251e3 --- /dev/null +++ b/lib/nfc/nfc_device_i.c @@ -0,0 +1,37 @@ +#include "nfc_device_i.h" +#include "protocols/nfc_device_defs.h" + +#include + +static NfcDeviceData* + nfc_device_search_base_protocol_data(const NfcDevice* instance, NfcProtocol protocol) { + NfcProtocol protocol_tmp = instance->protocol; + NfcDeviceData* dev_data_tmp = instance->protocol_data; + + while(true) { + dev_data_tmp = nfc_devices[protocol_tmp]->get_base_data(dev_data_tmp); + protocol_tmp = nfc_protocol_get_parent(protocol_tmp); + if(protocol_tmp == protocol) { + break; + } + } + + return dev_data_tmp; +} + +NfcDeviceData* nfc_device_get_data_ptr(const NfcDevice* instance, NfcProtocol protocol) { + furi_assert(instance); + furi_assert(protocol < NfcProtocolNum); + + NfcDeviceData* dev_data = NULL; + + if(instance->protocol == protocol) { + dev_data = instance->protocol_data; + } else if(nfc_protocol_has_parent(instance->protocol, protocol)) { + dev_data = nfc_device_search_base_protocol_data(instance, protocol); + } else { + furi_crash("Incorrect protocol"); + } + + return dev_data; +} diff --git a/lib/nfc/nfc_device_i.h b/lib/nfc/nfc_device_i.h new file mode 100644 index 00000000000..1a9017d03f6 --- /dev/null +++ b/lib/nfc/nfc_device_i.h @@ -0,0 +1,46 @@ +/** + * @file nfc_device_i.h + * @brief NfcDevice private types and definitions. + * + * This file is an implementation detail. It must not be included in + * any public API-related headers. + */ +#pragma once + +#include "nfc_device.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief NfcDevice structure definition. + */ +struct NfcDevice { + NfcProtocol protocol; /**< Numeric identifier of the data's protocol*/ + NfcDeviceData* protocol_data; /**< Pointer to the NFC device data. */ + + NfcLoadingCallback + loading_callback; /**< Pointer to the function to be called upon loading completion. */ + void* loading_callback_context; /**< Pointer to the context to be passed to the loading callback. */ +}; + +/** + * @brief Get the mutable (non-const) data from an NfcDevice instance. + * + * The behaviour is the same as with nfc_device_get_data(), but the + * return pointer is non-const, allowing for changing data it is pointing to. + * + * @see nfc_device.h + * + * Under the hood, nfc_device_get_data() calls this and then adds const-ness to the return value. + * + * @param instance pointer to the instance to be queried + * @param protocol protocol identifier of the data to be retrieved. + * @returns pointer to the instance's (mutable) data. + */ +NfcDeviceData* nfc_device_get_data_ptr(const NfcDevice* instance, NfcProtocol protocol); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/nfc_listener.c b/lib/nfc/nfc_listener.c new file mode 100644 index 00000000000..57b00a8755a --- /dev/null +++ b/lib/nfc/nfc_listener.c @@ -0,0 +1,144 @@ +#include "nfc_listener.h" + +#include +#include + +#include + +typedef struct NfcListenerListElement { + NfcProtocol protocol; + NfcGenericInstance* listener; + const NfcListenerBase* listener_api; + struct NfcListenerListElement* child; +} NfcListenerListElement; + +typedef struct { + NfcListenerListElement* head; + NfcListenerListElement* tail; +} NfcListenerList; + +struct NfcListener { + NfcProtocol protocol; + Nfc* nfc; + NfcListenerList list; + NfcDevice* nfc_dev; +}; + +static void nfc_listener_list_alloc(NfcListener* instance) { + instance->list.head = malloc(sizeof(NfcListenerListElement)); + instance->list.head->protocol = instance->protocol; + + instance->list.head->listener_api = nfc_listeners_api[instance->protocol]; + instance->list.head->child = NULL; + instance->list.tail = instance->list.head; + + // Build linked list + do { + NfcProtocol parent_protocol = nfc_protocol_get_parent(instance->list.head->protocol); + if(parent_protocol == NfcProtocolInvalid) break; + + NfcListenerListElement* parent = malloc(sizeof(NfcListenerListElement)); + parent->protocol = parent_protocol; + parent->listener_api = nfc_listeners_api[parent_protocol]; + parent->child = instance->list.head; + + instance->list.head = parent; + } while(true); + + // Allocate listener instances + NfcListenerListElement* iter = instance->list.head; + NfcDeviceData* data_tmp = nfc_device_get_data_ptr(instance->nfc_dev, iter->protocol); + iter->listener = iter->listener_api->alloc(instance->nfc, data_tmp); + + do { + if(iter->child == NULL) break; + data_tmp = nfc_device_get_data_ptr(instance->nfc_dev, iter->child->protocol); + iter->child->listener = iter->child->listener_api->alloc(iter->listener, data_tmp); + iter->listener_api->set_callback( + iter->listener, iter->child->listener_api->run, iter->child->listener); + + iter = iter->child; + } while(true); +} + +static void nfc_listener_list_free(NfcListener* instance) { + // Free listener instances + do { + instance->list.head->listener_api->free(instance->list.head->listener); + NfcListenerListElement* child = instance->list.head->child; + free(instance->list.head); + if(child == NULL) break; + instance->list.head = child; + } while(true); +} + +NfcListener* nfc_listener_alloc(Nfc* nfc, NfcProtocol protocol, const NfcDeviceData* data) { + furi_assert(nfc); + furi_assert(protocol < NfcProtocolNum); + furi_assert(data); + furi_assert(nfc_listeners_api[protocol]); + + NfcListener* instance = malloc(sizeof(NfcListener)); + instance->nfc = nfc; + instance->protocol = protocol; + instance->nfc_dev = nfc_device_alloc(); + nfc_device_set_data(instance->nfc_dev, protocol, data); + nfc_listener_list_alloc(instance); + + return instance; +} + +void nfc_listener_free(NfcListener* instance) { + furi_assert(instance); + + nfc_listener_list_free(instance); + nfc_device_free(instance->nfc_dev); + free(instance); +} + +NfcCommand nfc_listener_start_callback(NfcEvent event, void* context) { + furi_assert(context); + + NfcListener* instance = context; + furi_assert(instance->list.head); + + NfcCommand command = NfcCommandContinue; + NfcGenericEvent generic_event = { + .protocol = NfcProtocolInvalid, + .instance = instance->nfc, + .event_data = &event, + }; + + NfcListenerListElement* head_listener = instance->list.head; + command = head_listener->listener_api->run(generic_event, head_listener->listener); + + return command; +} + +void nfc_listener_start(NfcListener* instance, NfcGenericCallback callback, void* context) { + furi_assert(instance); + + NfcListenerListElement* tail_element = instance->list.tail; + tail_element->listener_api->set_callback(tail_element->listener, callback, context); + nfc_start(instance->nfc, nfc_listener_start_callback, instance); +} + +void nfc_listener_stop(NfcListener* instance) { + furi_assert(instance); + + nfc_stop(instance->nfc); +} + +NfcProtocol nfc_listener_get_protocol(const NfcListener* instance) { + furi_assert(instance); + + return instance->protocol; +} + +const NfcDeviceData* nfc_listener_get_data(const NfcListener* instance, NfcProtocol protocol) { + furi_assert(instance); + furi_assert(instance->protocol == protocol); + + NfcListenerListElement* tail_element = instance->list.tail; + return tail_element->listener_api->get_data(tail_element->listener); +} diff --git a/lib/nfc/nfc_listener.h b/lib/nfc/nfc_listener.h new file mode 100644 index 00000000000..dbfeb23bff9 --- /dev/null +++ b/lib/nfc/nfc_listener.h @@ -0,0 +1,94 @@ +/** + * @file nfc_listener.h + * @brief NFC card emulation library. + * + * Once started, it will respond to supported commands from an NFC reader, thus imitating + * (or emulating) an NFC card. The responses will depend on the data that was supplied to + * the listener, so various card types and different cards of the same type can be emulated. + * + * It will also make any changes necessary to the emulated data in response to the + * reader commands if the protocol supports it. + * + * When running, NfcListener will generate events that the calling code must handle + * by providing a callback function. The events passed to the callback are protocol-specific + * and may include errors, state changes, data reception, special function requests and more. + */ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief NfcListener opaque type definition. + */ +typedef struct NfcListener NfcListener; + +/** + * @brief Allocate an NfcListener instance. + * + * @param[in] nfc pointer to an Nfc instance. + * @param[in] protocol identifier of the protocol to be used. + * @param[in] data pointer to the data to use during emulation. + * @returns pointer to an allocated instance. + * + * @see nfc.h + */ +NfcListener* nfc_listener_alloc(Nfc* nfc, NfcProtocol protocol, const NfcDeviceData* data); + +/** + * @brief Delete an NfcListener instance. + * + * @param[in,out] instance pointer to the instance to be deleted. + */ +void nfc_listener_free(NfcListener* instance); + +/** + * @brief Start an NfcListener instance. + * + * The callback logic is protocol-specific, so it cannot be described here in detail. + * However, the callback return value ALWAYS determines what the listener should do next: + * to continue whatever it was doing prior to the callback run or to stop. + * + * @param[in,out] instance pointer to the instance to be started. + * @param[in] callback pointer to a user-defined callback function which will receive events. + * @param[in] context pointer to a user-specific context (will be passed to the callback). + */ +void nfc_listener_start(NfcListener* instance, NfcGenericCallback callback, void* context); + +/** + * @brief Stop an NfcListener instance. + * + * The emulation process can be stopped explicitly (the other way is via the callback return value). + * + * @param[in,out] instance pointer to the instance to be stopped. + */ +void nfc_listener_stop(NfcListener* instance); + +/** + * @brief Get the protocol identifier an NfcListener instance was created with. + * + * @param[in] instance pointer to the instance to be queried. + * @returns identifier of the protocol used by the instance. + */ +NfcProtocol nfc_listener_get_protocol(const NfcListener* instance); + +/** + * @brief Get the data that was that was provided for emulation. + * + * The protocol identifier passed as the protocol parameter MUST match the one + * stored in the instance, otherwise a crash will occur. + * This is to improve type safety. + * + * @param[in] instance pointer to the instance to be queried. + * @param[in] protocol assumed protocol identifier of the data to be retrieved. + * @returns pointer to the NFC device data. + */ +const NfcDeviceData* nfc_listener_get_data(const NfcListener* instance, NfcProtocol protocol); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/nfc_poller.c b/lib/nfc/nfc_poller.c new file mode 100644 index 00000000000..ffe0318a0d3 --- /dev/null +++ b/lib/nfc/nfc_poller.c @@ -0,0 +1,211 @@ +#include "nfc_poller.h" + +#include + +#include + +typedef enum { + NfcPollerSessionStateIdle, + NfcPollerSessionStateActive, + NfcPollerSessionStateStopRequest, +} NfcPollerSessionState; + +typedef struct NfcPollerListElement { + NfcProtocol protocol; + NfcGenericInstance* poller; + const NfcPollerBase* poller_api; + struct NfcPollerListElement* child; +} NfcPollerListElement; + +typedef struct { + NfcPollerListElement* head; + NfcPollerListElement* tail; +} NfcPollerList; + +struct NfcPoller { + NfcProtocol protocol; + Nfc* nfc; + NfcPollerList list; + NfcPollerSessionState session_state; + bool protocol_detected; +}; + +static void nfc_poller_list_alloc(NfcPoller* instance) { + instance->list.head = malloc(sizeof(NfcPollerListElement)); + instance->list.head->protocol = instance->protocol; + instance->list.head->poller_api = nfc_pollers_api[instance->protocol]; + instance->list.head->child = NULL; + instance->list.tail = instance->list.head; + + do { + NfcProtocol parent_protocol = nfc_protocol_get_parent(instance->list.head->protocol); + if(parent_protocol == NfcProtocolInvalid) break; + + NfcPollerListElement* parent = malloc(sizeof(NfcPollerListElement)); + parent->protocol = parent_protocol; + parent->poller_api = nfc_pollers_api[parent_protocol]; + parent->child = instance->list.head; + instance->list.head = parent; + } while(true); + + NfcPollerListElement* iter = instance->list.head; + iter->poller = iter->poller_api->alloc(instance->nfc); + + do { + if(iter->child == NULL) break; + iter->child->poller = iter->child->poller_api->alloc(iter->poller); + iter->poller_api->set_callback( + iter->poller, iter->child->poller_api->run, iter->child->poller); + + iter = iter->child; + } while(true); +} + +static void nfc_poller_list_free(NfcPoller* instance) { + do { + instance->list.head->poller_api->free(instance->list.head->poller); + NfcPollerListElement* child = instance->list.head->child; + free(instance->list.head); + if(child == NULL) break; + instance->list.head = child; + } while(true); +} + +NfcPoller* nfc_poller_alloc(Nfc* nfc, NfcProtocol protocol) { + furi_assert(nfc); + furi_assert(protocol < NfcProtocolNum); + + NfcPoller* instance = malloc(sizeof(NfcPoller)); + instance->session_state = NfcPollerSessionStateIdle; + instance->nfc = nfc; + instance->protocol = protocol; + nfc_poller_list_alloc(instance); + + return instance; +} + +void nfc_poller_free(NfcPoller* instance) { + furi_assert(instance); + + nfc_poller_list_free(instance); + free(instance); +} + +static NfcCommand nfc_poller_start_callback(NfcEvent event, void* context) { + furi_assert(context); + + NfcPoller* instance = context; + + NfcCommand command = NfcCommandContinue; + NfcGenericEvent poller_event = { + .protocol = NfcProtocolInvalid, + .instance = instance->nfc, + .event_data = &event, + }; + + if(event.type == NfcEventTypePollerReady) { + NfcPollerListElement* head_poller = instance->list.head; + command = head_poller->poller_api->run(poller_event, head_poller->poller); + } + + if(instance->session_state == NfcPollerSessionStateStopRequest) { + command = NfcCommandStop; + } + + return command; +} + +void nfc_poller_start(NfcPoller* instance, NfcGenericCallback callback, void* context) { + furi_assert(instance); + furi_assert(callback); + furi_assert(instance->session_state == NfcPollerSessionStateIdle); + + NfcPollerListElement* tail_poller = instance->list.tail; + tail_poller->poller_api->set_callback(tail_poller->poller, callback, context); + + instance->session_state = NfcPollerSessionStateActive; + nfc_start(instance->nfc, nfc_poller_start_callback, instance); +} + +void nfc_poller_stop(NfcPoller* instance) { + furi_assert(instance); + furi_assert(instance->nfc); + + instance->session_state = NfcPollerSessionStateStopRequest; + nfc_stop(instance->nfc); + instance->session_state = NfcPollerSessionStateIdle; +} + +static NfcCommand nfc_poller_detect_tail_callback(NfcGenericEvent event, void* context) { + furi_assert(context); + + NfcPoller* instance = context; + NfcPollerListElement* tail_poller = instance->list.tail; + instance->protocol_detected = tail_poller->poller_api->detect(event, tail_poller->poller); + + return NfcCommandStop; +} + +static NfcCommand nfc_poller_detect_head_callback(NfcEvent event, void* context) { + furi_assert(context); + + NfcPoller* instance = context; + NfcPollerListElement* tail_poller = instance->list.tail; + NfcPollerListElement* head_poller = instance->list.head; + + NfcCommand command = NfcCommandContinue; + NfcGenericEvent poller_event = { + .protocol = NfcProtocolInvalid, + .instance = instance->nfc, + .event_data = &event, + }; + + if(event.type == NfcEventTypePollerReady) { + if(tail_poller == head_poller) { + instance->protocol_detected = + tail_poller->poller_api->detect(poller_event, tail_poller->poller); + command = NfcCommandStop; + } else { + command = head_poller->poller_api->run(poller_event, head_poller->poller); + } + } + + return command; +} + +bool nfc_poller_detect(NfcPoller* instance) { + furi_assert(instance); + furi_assert(instance->session_state == NfcPollerSessionStateIdle); + + instance->session_state = NfcPollerSessionStateActive; + NfcPollerListElement* tail_poller = instance->list.tail; + NfcPollerListElement* iter = instance->list.head; + + if(tail_poller != instance->list.head) { + while(iter->child != tail_poller) iter = iter->child; + iter->poller_api->set_callback(iter->poller, nfc_poller_detect_tail_callback, instance); + } + + nfc_start(instance->nfc, nfc_poller_detect_head_callback, instance); + nfc_stop(instance->nfc); + + if(tail_poller != instance->list.head) { + iter->poller_api->set_callback( + iter->poller, tail_poller->poller_api->run, tail_poller->poller); + } + + return instance->protocol_detected; +} + +NfcProtocol nfc_poller_get_protocol(const NfcPoller* instance) { + furi_assert(instance); + + return instance->protocol; +} + +const NfcDeviceData* nfc_poller_get_data(const NfcPoller* instance) { + furi_assert(instance); + + NfcPollerListElement* tail_poller = instance->list.tail; + return tail_poller->poller_api->get_data(tail_poller->poller); +} diff --git a/lib/nfc/nfc_poller.h b/lib/nfc/nfc_poller.h new file mode 100644 index 00000000000..8ae01a8e43c --- /dev/null +++ b/lib/nfc/nfc_poller.h @@ -0,0 +1,104 @@ +/** + * @file nfc_poller.h + * @brief NFC card reading library. + * + * Once started, it will try to activate and read a card using the designated protocol, + * which is usually obtained by creating and starting an NfcScanner first. + * + * @see nfc_scanner.h + * + * When running, NfcPoller will generate events that the calling code must handle + * by providing a callback function. The events passed to the callback are protocol-specific + * and may include errors, state changes, data reception, special function requests and more. + * + */ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief NfcPoller opaque type definition. + */ +typedef struct NfcPoller NfcPoller; + +/** + * @brief Allocate an NfcPoller instance. + * + * @param[in] nfc pointer to an Nfc instance. + * @param[in] protocol identifier of the protocol to be used. + * @returns pointer to an allocated instance. + * + * @see nfc.h + */ +NfcPoller* nfc_poller_alloc(Nfc* nfc, NfcProtocol protocol); + +/** + * @brief Delete an NfcPoller instance. + * + * @param[in,out] instance pointer to the instance to be deleted. + */ +void nfc_poller_free(NfcPoller* instance); + +/** + * @brief Start an NfcPoller instance. + * + * The callback logic is protocol-specific, so it cannot be described here in detail. + * However, the callback return value ALWAYS determines what the poller should do next: + * to continue whatever it was doing prior to the callback run or to stop. + * + * @param[in,out] instance pointer to the instance to be started. + * @param[in] callback pointer to a user-defined callback function which will receive events. + * @param[in] context pointer to a user-specific context (will be passed to the callback). + */ +void nfc_poller_start(NfcPoller* instance, NfcGenericCallback callback, void* context); + +/** + * @brief Stop an NfcPoller instance. + * + * The reading process can be stopped explicitly (the other way is via the callback return value). + * + * @param[in,out] instance pointer to the instance to be stopped. + */ +void nfc_poller_stop(NfcPoller* instance); + +/** + * @brief Detect whether there is a card supporting a particular protocol in the vicinity. + * + * The behaviour of this function is protocol-defined, in general, it will do whatever is + * necessary to determine whether a card supporting the current protocol is in the vicinity + * and whether it is functioning normally. + * + * It is used automatically inside NfcScanner, so there is usually no need + * to call it explicitly. + * + * @see nfc_scanner.h + * + * @param[in,out] instance pointer to the instance to perform the detection with. + * @returns true if a supported card was detected, false otherwise. + */ +bool nfc_poller_detect(NfcPoller* instance); + +/** + * @brief Get the protocol identifier an NfcPoller instance was created with. + * + * @param[in] instance pointer to the instance to be queried. + * @returns identifier of the protocol used by the instance. + */ +NfcProtocol nfc_poller_get_protocol(const NfcPoller* instance); + +/** + * @brief Get the data that was that was gathered during the reading process. + * + * @param[in] instance pointer to the instance to be queried. + * @returns pointer to the NFC device data. + */ +const NfcDeviceData* nfc_poller_get_data(const NfcPoller* instance); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/nfc_scanner.c b/lib/nfc/nfc_scanner.c new file mode 100644 index 00000000000..0056dfb1ccc --- /dev/null +++ b/lib/nfc/nfc_scanner.c @@ -0,0 +1,267 @@ +#include "nfc_scanner.h" +#include "nfc_poller.h" + +#include + +#include + +#define TAG "NfcScanner" + +typedef enum { + NfcScannerStateIdle, + NfcScannerStateTryBasePollers, + NfcScannerStateFindChildrenProtocols, + NfcScannerStateDetectChildrenProtocols, + NfcScannerStateComplete, + + NfcScannerStateNum, +} NfcScannerState; + +typedef enum { + NfcScannerSessionStateIdle, + NfcScannerSessionStateActive, + NfcScannerSessionStateStopRequest, +} NfcScannerSessionState; + +struct NfcScanner { + Nfc* nfc; + NfcScannerState state; + NfcScannerSessionState session_state; + + NfcScannerCallback callback; + void* context; + + NfcEvent nfc_event; + + NfcProtocol first_detected_protocol; + + size_t base_protocols_num; + size_t base_protocols_idx; + NfcProtocol base_protocols[NfcProtocolNum]; + + size_t detected_base_protocols_num; + NfcProtocol detected_base_protocols[NfcProtocolNum]; + + size_t children_protocols_num; + size_t children_protocols_idx; + NfcProtocol children_protocols[NfcProtocolNum]; + + size_t detected_protocols_num; + NfcProtocol detected_protocols[NfcProtocolNum]; + + NfcProtocol current_protocol; + + FuriThread* scan_worker; +}; + +static void nfc_scanner_reset(NfcScanner* instance) { + instance->base_protocols_idx = 0; + instance->base_protocols_num = 0; + + instance->children_protocols_idx = 0; + instance->children_protocols_num = 0; + + instance->detected_protocols_num = 0; + instance->detected_base_protocols_num = 0; + + instance->current_protocol = 0; +} + +typedef void (*NfcScannerStateHandler)(NfcScanner* instance); + +void nfc_scanner_state_handler_idle(NfcScanner* instance) { + for(size_t i = 0; i < NfcProtocolNum; i++) { + NfcProtocol parent_protocol = nfc_protocol_get_parent(i); + if(parent_protocol == NfcProtocolInvalid) { + instance->base_protocols[instance->base_protocols_num] = i; + instance->base_protocols_num++; + } + } + FURI_LOG_D(TAG, "Found %zu base protocols", instance->base_protocols_num); + + instance->first_detected_protocol = NfcProtocolInvalid; + instance->state = NfcScannerStateTryBasePollers; +} + +void nfc_scanner_state_handler_try_base_pollers(NfcScanner* instance) { + do { + instance->current_protocol = instance->base_protocols[instance->base_protocols_idx]; + + if(instance->first_detected_protocol == instance->current_protocol) { + instance->state = NfcScannerStateFindChildrenProtocols; + break; + } + + NfcPoller* poller = nfc_poller_alloc(instance->nfc, instance->current_protocol); + bool protocol_detected = nfc_poller_detect(poller); + nfc_poller_free(poller); + + if(protocol_detected) { + instance->detected_protocols[instance->detected_protocols_num] = + instance->current_protocol; + instance->detected_protocols_num++; + + instance->detected_base_protocols[instance->detected_base_protocols_num] = + instance->current_protocol; + instance->detected_base_protocols_num++; + + if(instance->first_detected_protocol == NfcProtocolInvalid) { + instance->first_detected_protocol = instance->current_protocol; + instance->current_protocol = NfcProtocolInvalid; + } + } + + instance->base_protocols_idx = + (instance->base_protocols_idx + 1) % instance->base_protocols_num; + } while(false); +} + +void nfc_scanner_state_handler_find_children_protocols(NfcScanner* instance) { + for(size_t i = 0; i < NfcProtocolNum; i++) { + for(size_t j = 0; j < instance->detected_base_protocols_num; j++) { + if(nfc_protocol_has_parent(i, instance->detected_base_protocols[j])) { + instance->children_protocols[instance->children_protocols_num] = i; + instance->children_protocols_num++; + } + } + } + + if(instance->children_protocols_num > 0) { + instance->state = NfcScannerStateDetectChildrenProtocols; + } else { + instance->state = NfcScannerStateComplete; + } + FURI_LOG_D(TAG, "Found %zu children", instance->children_protocols_num); +} + +void nfc_scanner_state_handler_detect_children_protocols(NfcScanner* instance) { + furi_assert(instance->children_protocols_num); + + instance->current_protocol = instance->children_protocols[instance->children_protocols_idx]; + + NfcPoller* poller = nfc_poller_alloc(instance->nfc, instance->current_protocol); + bool protocol_detected = nfc_poller_detect(poller); + nfc_poller_free(poller); + + if(protocol_detected) { + instance->detected_protocols[instance->detected_protocols_num] = + instance->current_protocol; + instance->detected_protocols_num++; + } + + instance->children_protocols_idx++; + if(instance->children_protocols_idx == instance->children_protocols_num) { + instance->state = NfcScannerStateComplete; + } +} + +static void nfc_scanner_filter_detected_protocols(NfcScanner* instance) { + size_t filtered_protocols_num = 0; + NfcProtocol filtered_protocols[NfcProtocolNum] = {}; + + for(size_t i = 0; i < instance->detected_protocols_num; i++) { + bool is_parent = false; + for(size_t j = i; j < instance->detected_protocols_num; j++) { + is_parent = nfc_protocol_has_parent( + instance->detected_protocols[j], instance->detected_protocols[i]); + if(is_parent) break; + } + if(!is_parent) { + filtered_protocols[filtered_protocols_num] = instance->detected_protocols[i]; + filtered_protocols_num++; + } + } + + instance->detected_protocols_num = filtered_protocols_num; + memcpy(instance->detected_protocols, filtered_protocols, filtered_protocols_num); +} + +void nfc_scanner_state_handler_complete(NfcScanner* instance) { + if(instance->detected_protocols_num > 1) { + nfc_scanner_filter_detected_protocols(instance); + } + FURI_LOG_I(TAG, "Detected %zu protocols", instance->detected_protocols_num); + + NfcScannerEvent event = { + .type = NfcScannerEventTypeDetected, + .data = + { + .protocol_num = instance->detected_protocols_num, + .protocols = instance->detected_protocols, + }, + }; + + instance->callback(event, instance->context); + furi_delay_ms(100); +} + +static NfcScannerStateHandler nfc_scanner_state_handlers[NfcScannerStateNum] = { + [NfcScannerStateIdle] = nfc_scanner_state_handler_idle, + [NfcScannerStateTryBasePollers] = nfc_scanner_state_handler_try_base_pollers, + [NfcScannerStateFindChildrenProtocols] = nfc_scanner_state_handler_find_children_protocols, + [NfcScannerStateDetectChildrenProtocols] = nfc_scanner_state_handler_detect_children_protocols, + [NfcScannerStateComplete] = nfc_scanner_state_handler_complete, +}; + +static int32_t nfc_scanner_worker(void* context) { + furi_assert(context); + + NfcScanner* instance = context; + + while(instance->session_state == NfcScannerSessionStateActive) { + nfc_scanner_state_handlers[instance->state](instance); + } + + nfc_scanner_reset(instance); + + return 0; +} + +NfcScanner* nfc_scanner_alloc(Nfc* nfc) { + furi_assert(nfc); + + NfcScanner* instance = malloc(sizeof(NfcScanner)); + instance->nfc = nfc; + + return instance; +} + +void nfc_scanner_free(NfcScanner* instance) { + furi_assert(instance); + + free(instance); +} + +void nfc_scanner_start(NfcScanner* instance, NfcScannerCallback callback, void* context) { + furi_assert(instance); + furi_assert(callback); + furi_assert(instance->session_state == NfcScannerSessionStateIdle); + furi_assert(instance->scan_worker == NULL); + + instance->callback = callback; + instance->context = context; + instance->session_state = NfcScannerSessionStateActive; + + instance->scan_worker = furi_thread_alloc(); + furi_thread_set_name(instance->scan_worker, "NfcScanWorker"); + furi_thread_set_context(instance->scan_worker, instance); + furi_thread_set_stack_size(instance->scan_worker, 4 * 1024); + furi_thread_set_callback(instance->scan_worker, nfc_scanner_worker); + + furi_thread_start(instance->scan_worker); +} + +void nfc_scanner_stop(NfcScanner* instance) { + furi_assert(instance); + furi_assert(instance->scan_worker); + + instance->session_state = NfcScannerSessionStateStopRequest; + furi_thread_join(instance->scan_worker); + instance->session_state = NfcScannerSessionStateIdle; + + furi_thread_free(instance->scan_worker); + instance->scan_worker = NULL; + instance->callback = NULL; + instance->context = NULL; + instance->state = NfcScannerStateIdle; +} diff --git a/lib/nfc/nfc_scanner.h b/lib/nfc/nfc_scanner.h new file mode 100644 index 00000000000..a1b4aabcda3 --- /dev/null +++ b/lib/nfc/nfc_scanner.h @@ -0,0 +1,97 @@ +/** + * @file nfc_scanner.h + * @brief NFC card detection library. + * + * Once started, a NfcScanner instance will iterate over all available protocols + * and return a list of one or more detected protocol identifiers via a user-provided callback. + * + * The NfcScanner behaviour is greedy, i.e. it will not stop scanning upon detection of + * a just one protocol and will try others as well until all possibilities are exhausted. + * This is to allow for multi-protocol card support. + * + * If no supported cards are in the vicinity, the scanning process will continue + * until stopped explicitly. + */ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief NfcScanner opaque type definition. + */ +typedef struct NfcScanner NfcScanner; + +/** + * @brief Event type passed to the user callback. + */ +typedef enum { + NfcScannerEventTypeDetected, /**< One or more protocols have been detected. */ +} NfcScannerEventType; + +/** + * @brief Event data passed to the user callback. + */ +typedef struct { + size_t protocol_num; /**< Number of detected protocols (one or more). */ + NfcProtocol* protocols; /**< Pointer to the array of detected protocol identifiers. */ +} NfcScannerEventData; + +/** + * @brief Event passed to the user callback. + */ +typedef struct { + NfcScannerEventType type; /**< Type of event. Determines how the data must be handled. */ + NfcScannerEventData data; /**< Event-specific data. Handled accordingly to the even type. */ +} NfcScannerEvent; + +/** + * @brief User callback function signature. + * + * A function with such signature must be provided by the user upon calling nfc_scanner_start(). + * + * @param[in] event occurred event, complete with type and data. + * @param[in] context pointer to the context data provided in nfc_scanner_start() call. + */ +typedef void (*NfcScannerCallback)(NfcScannerEvent event, void* context); + +/** + * @brief Allocate an NfcScanner instance. + * + * @param[in] nfc pointer to an Nfc instance. + * @returns pointer to the allocated NfcScanner instance. + * + * @see nfc.h + */ +NfcScanner* nfc_scanner_alloc(Nfc* nfc); + +/** + * @brief Delete an NfcScanner instance. + * + * @param[in,out] pointer to the instance to be deleted. + */ +void nfc_scanner_free(NfcScanner* instance); + +/** + * @brief Start an NfcScanner. + * + * @param[in,out] pointer to the instance to be started. + * @param[in] callback pointer to the callback function (will be called upon a detection event). + * @param[in] context pointer to the caller-specific context (will be passed to the callback). + */ +void nfc_scanner_start(NfcScanner* instance, NfcScannerCallback callback, void* context); + +/** + * @brief Stop an NfcScanner. + * + * @param[in,out] pointer to the instance to be stopped. + */ +void nfc_scanner_stop(NfcScanner* instance); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/nfc_types.c b/lib/nfc/nfc_types.c deleted file mode 100644 index 96b92640f0e..00000000000 --- a/lib/nfc/nfc_types.c +++ /dev/null @@ -1,69 +0,0 @@ -#include "nfc_types.h" - -const char* nfc_get_dev_type(FuriHalNfcType type) { - if(type == FuriHalNfcTypeA) { - return "NFC-A"; - } else if(type == FuriHalNfcTypeB) { - return "NFC-B"; - } else if(type == FuriHalNfcTypeF) { - return "NFC-F"; - } else if(type == FuriHalNfcTypeV) { - return "NFC-V"; - } else { - return "Unknown"; - } -} - -const char* nfc_guess_protocol(NfcProtocol protocol) { - if(protocol == NfcDeviceProtocolEMV) { - return "EMV bank card"; - } else if(protocol == NfcDeviceProtocolMifareUl) { - return "Mifare Ultral/NTAG"; - } else if(protocol == NfcDeviceProtocolMifareClassic) { - return "Mifare Classic"; - } else if(protocol == NfcDeviceProtocolMifareDesfire) { - return "Mifare DESFire"; - } else { - return "Unrecognized"; - } -} - -const char* nfc_mf_ul_type(MfUltralightType type, bool full_name) { - if(type == MfUltralightTypeNTAG213) { - return "NTAG213"; - } else if(type == MfUltralightTypeNTAG215) { - 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 == MfUltralightTypeNTAG203) { - return "NTAG203"; - } else if(type == MfUltralightTypeULC) { - return "Mifare Ultralight C"; - } else if(type == MfUltralightTypeUL11 && full_name) { - return "Mifare Ultralight 11"; - } else if(type == MfUltralightTypeUL21 && full_name) { - return "Mifare Ultralight 21"; - } else { - return "Mifare Ultralight"; - } -} - -const char* nfc_mf_classic_type(MfClassicType type) { - if(type == MfClassicTypeMini) { - return "Mifare Mini 0.3K"; - } else if(type == MfClassicType1k) { - return "Mifare Classic 1K"; - } else if(type == MfClassicType4k) { - return "Mifare Classic 4K"; - } else { - return "Mifare Classic"; - } -} diff --git a/lib/nfc/nfc_types.h b/lib/nfc/nfc_types.h deleted file mode 100644 index 5ebb2d27bc4..00000000000 --- a/lib/nfc/nfc_types.h +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once - -#include "nfc_device.h" - -#ifdef __cplusplus -extern "C" { -#endif - -const char* nfc_get_dev_type(FuriHalNfcType type); - -const char* nfc_guess_protocol(NfcProtocol protocol); - -const char* nfc_mf_ul_type(MfUltralightType type, bool full_name); - -const char* nfc_mf_classic_type(MfClassicType type); - -#ifdef __cplusplus -} -#endif \ No newline at end of file diff --git a/lib/nfc/nfc_worker.c b/lib/nfc/nfc_worker.c deleted file mode 100644 index 36776d80470..00000000000 --- a/lib/nfc/nfc_worker.c +++ /dev/null @@ -1,1325 +0,0 @@ -#include "nfc_worker_i.h" -#include - -#include -#include "parsers/nfc_supported_card.h" - -#define TAG "NfcWorker" - -/***************************** NFC Worker API *******************************/ - -NfcWorker* nfc_worker_alloc() { - NfcWorker* nfc_worker = malloc(sizeof(NfcWorker)); - - // Worker thread attributes - nfc_worker->thread = furi_thread_alloc_ex("NfcWorker", 8192, nfc_worker_task, nfc_worker); - - nfc_worker->callback = NULL; - nfc_worker->context = NULL; - nfc_worker->storage = furi_record_open(RECORD_STORAGE); - - // Initialize rfal - while(furi_hal_nfc_is_busy()) { - furi_delay_ms(10); - } - nfc_worker_change_state(nfc_worker, NfcWorkerStateReady); - - nfc_worker->reader_analyzer = reader_analyzer_alloc(nfc_worker->storage); - - return nfc_worker; -} - -void nfc_worker_free(NfcWorker* nfc_worker) { - furi_assert(nfc_worker); - - furi_thread_free(nfc_worker->thread); - - furi_record_close(RECORD_STORAGE); - - reader_analyzer_free(nfc_worker->reader_analyzer); - - free(nfc_worker); -} - -NfcWorkerState nfc_worker_get_state(NfcWorker* nfc_worker) { - return nfc_worker->state; -} - -void nfc_worker_start( - NfcWorker* nfc_worker, - NfcWorkerState state, - NfcDeviceData* dev_data, - NfcWorkerCallback callback, - void* context) { - furi_assert(nfc_worker); - furi_assert(dev_data); - while(furi_hal_nfc_is_busy()) { - furi_delay_ms(10); - } - furi_hal_nfc_deinit(); - furi_hal_nfc_init(); - - nfc_worker->callback = callback; - nfc_worker->context = context; - nfc_worker->dev_data = dev_data; - nfc_worker_change_state(nfc_worker, state); - furi_thread_start(nfc_worker->thread); -} - -void nfc_worker_stop(NfcWorker* nfc_worker) { - furi_assert(nfc_worker); - furi_assert(nfc_worker->thread); - if(furi_thread_get_state(nfc_worker->thread) != FuriThreadStateStopped) { - furi_hal_nfc_stop(); - nfc_worker_change_state(nfc_worker, NfcWorkerStateStop); - furi_thread_join(nfc_worker->thread); - } -} - -void nfc_worker_change_state(NfcWorker* nfc_worker, NfcWorkerState state) { - nfc_worker->state = state; -} - -/***************************** NFC Worker Thread *******************************/ - -int32_t nfc_worker_task(void* context) { - NfcWorker* nfc_worker = context; - - furi_hal_nfc_exit_sleep(); - - if(nfc_worker->state == NfcWorkerStateRead) { - if(nfc_worker->dev_data->read_mode == NfcReadModeAuto) { - nfc_worker_read(nfc_worker); - } else { - nfc_worker_read_type(nfc_worker); - } - } else if(nfc_worker->state == NfcWorkerStateUidEmulate) { - nfc_worker_emulate_uid(nfc_worker); - } else if(nfc_worker->state == NfcWorkerStateEmulateApdu) { - nfc_worker_emulate_apdu(nfc_worker); - } else if(nfc_worker->state == NfcWorkerStateMfUltralightEmulate) { - nfc_worker_emulate_mf_ultralight(nfc_worker); - } else if(nfc_worker->state == NfcWorkerStateMfClassicEmulate) { - nfc_worker_emulate_mf_classic(nfc_worker); - } else if(nfc_worker->state == NfcWorkerStateMfClassicWrite) { - nfc_worker_write_mf_classic(nfc_worker); - } else if(nfc_worker->state == NfcWorkerStateMfClassicUpdate) { - nfc_worker_update_mf_classic(nfc_worker); - } else if(nfc_worker->state == NfcWorkerStateReadMfUltralightReadAuth) { - nfc_worker_mf_ultralight_read_auth(nfc_worker); - } else if(nfc_worker->state == NfcWorkerStateMfClassicDictAttack) { - nfc_worker_mf_classic_dict_attack(nfc_worker); - } else if(nfc_worker->state == NfcWorkerStateAnalyzeReader) { - nfc_worker_analyze_reader(nfc_worker); - } else if(nfc_worker->state == NfcWorkerStateNfcVEmulate) { - nfc_worker_nfcv_emulate(nfc_worker); - } else if(nfc_worker->state == NfcWorkerStateNfcVSniff) { - nfc_worker_nfcv_sniff(nfc_worker); - } else if(nfc_worker->state == NfcWorkerStateNfcVUnlock) { - nfc_worker_nfcv_unlock(nfc_worker); - } else if(nfc_worker->state == NfcWorkerStateNfcVUnlockAndSave) { - nfc_worker_nfcv_unlock(nfc_worker); - } - furi_hal_nfc_sleep(); - nfc_worker_change_state(nfc_worker, NfcWorkerStateReady); - - return 0; -} - -static bool nfc_worker_read_nfcv(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { - bool read_success = false; - NfcVReader reader = {}; - - FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data; - NfcVData* nfcv_data = &nfc_worker->dev_data->nfcv_data; - - furi_hal_nfc_sleep(); - - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - reader_analyzer_prepare_tx_rx(nfc_worker->reader_analyzer, tx_rx, false); - reader_analyzer_start(nfc_worker->reader_analyzer, ReaderAnalyzerModeDebugLog); - } - - do { - if(!furi_hal_nfc_detect(&nfc_worker->dev_data->nfc_data, 200)) break; - if(!nfcv_read_card(&reader, nfc_data, nfcv_data)) break; - - read_success = true; - } while(false); - - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - reader_analyzer_stop(nfc_worker->reader_analyzer); - } - - return read_success; -} - -void nfc_worker_nfcv_emulate(NfcWorker* nfc_worker) { - FuriHalNfcTxRxContext tx_rx = {}; - FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data; - NfcVData* nfcv_data = &nfc_worker->dev_data->nfcv_data; - - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - reader_analyzer_prepare_tx_rx(nfc_worker->reader_analyzer, &tx_rx, true); - reader_analyzer_start(nfc_worker->reader_analyzer, ReaderAnalyzerModeDebugLog); - } - - nfcv_emu_init(nfc_data, nfcv_data); - while(nfc_worker->state == NfcWorkerStateNfcVEmulate) { - if(nfcv_emu_loop(&tx_rx, nfc_data, nfcv_data, 100)) { - if(nfc_worker->callback) { - nfc_worker->callback(NfcWorkerEventNfcVCommandExecuted, nfc_worker->context); - if(nfcv_data->modified) { - nfc_worker->callback(NfcWorkerEventNfcVContentChanged, nfc_worker->context); - nfcv_data->modified = false; - } - } - } - } - nfcv_emu_deinit(nfcv_data); - - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - reader_analyzer_stop(nfc_worker->reader_analyzer); - } -} - -void nfc_worker_nfcv_sniff(NfcWorker* nfc_worker) { - FuriHalNfcTxRxContext tx_rx = {}; - FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data; - NfcVData* nfcv_data = &nfc_worker->dev_data->nfcv_data; - - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - reader_analyzer_prepare_tx_rx(nfc_worker->reader_analyzer, &tx_rx, true); - reader_analyzer_start(nfc_worker->reader_analyzer, ReaderAnalyzerModeDebugLog); - } - - nfcv_data->sub_type = NfcVTypeSniff; - nfcv_emu_init(nfc_data, nfcv_data); - - while(nfc_worker->state == NfcWorkerStateNfcVSniff) { - if(nfcv_emu_loop(&tx_rx, nfc_data, nfcv_data, 100)) { - if(nfc_worker->callback) { - nfc_worker->callback(NfcWorkerEventNfcVCommandExecuted, nfc_worker->context); - } - } - } - nfcv_emu_deinit(nfcv_data); - - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - reader_analyzer_stop(nfc_worker->reader_analyzer); - } -} - -void nfc_worker_nfcv_unlock(NfcWorker* nfc_worker) { - furi_assert(nfc_worker); - furi_assert(nfc_worker->callback); - - NfcVData* nfcv_data = &nfc_worker->dev_data->nfcv_data; - FuriHalNfcTxRxContext tx_rx = {}; - uint8_t* key_data = nfcv_data->sub_data.slix.key_privacy; - uint32_t key = 0; - - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - reader_analyzer_prepare_tx_rx(nfc_worker->reader_analyzer, &tx_rx, true); - reader_analyzer_start(nfc_worker->reader_analyzer, ReaderAnalyzerModeDebugLog); - } - - furi_hal_nfc_sleep(); - - while((nfc_worker->state == NfcWorkerStateNfcVUnlock) || - (nfc_worker->state == NfcWorkerStateNfcVUnlockAndSave)) { - furi_hal_nfc_exit_sleep(); - furi_hal_nfc_ll_txrx_on(); - furi_hal_nfc_ll_poll(); - if(furi_hal_nfc_ll_set_mode( - FuriHalNfcModePollNfcv, FuriHalNfcBitrate26p48, FuriHalNfcBitrate26p48) != - FuriHalNfcReturnOk) { - break; - } - - furi_hal_nfc_ll_set_fdt_listen(FURI_HAL_NFC_LL_FDT_LISTEN_NFCV_POLLER); - furi_hal_nfc_ll_set_fdt_poll(FURI_HAL_NFC_LL_FDT_POLL_NFCV_POLLER); - furi_hal_nfc_ll_set_error_handling(FuriHalNfcErrorHandlingNfc); - furi_hal_nfc_ll_set_guard_time(FURI_HAL_NFC_LL_GT_NFCV); - - FURI_LOG_D(TAG, "Detect presence"); - ReturnCode ret = slix_get_random(nfcv_data); - - if(ret == ERR_NONE) { - /* there is some chip, responding with a RAND */ - nfc_worker->dev_data->protocol = NfcDeviceProtocolNfcV; - FURI_LOG_D(TAG, " Chip detected. In privacy?"); - ret = nfcv_inventory(NULL); - - if(ret == ERR_NONE) { - /* chip is also visible, so no action required, just save */ - if(nfc_worker->state == NfcWorkerStateNfcVUnlockAndSave) { - NfcVReader reader = {}; - - if(!nfcv_read_card(&reader, &nfc_worker->dev_data->nfc_data, nfcv_data)) { - FURI_LOG_D(TAG, " => failed, wait for chip to disappear."); - snprintf(nfcv_data->error, sizeof(nfcv_data->error), "Read card\nfailed"); - nfc_worker->callback(NfcWorkerEventWrongCardDetected, nfc_worker->context); - } else { - FURI_LOG_D(TAG, " => success, wait for chip to disappear."); - nfc_worker->callback(NfcWorkerEventCardDetected, nfc_worker->context); - } - } else { - FURI_LOG_D(TAG, " => success, wait for chip to disappear."); - nfc_worker->callback(NfcWorkerEventCardDetected, nfc_worker->context); - } - - while(slix_get_random(NULL) == ERR_NONE) { - furi_delay_ms(100); - } - - FURI_LOG_D(TAG, " => chip is already visible, wait for chip to disappear.\r\n"); - nfc_worker->callback(NfcWorkerEventAborted, nfc_worker->context); - while(slix_get_random(NULL) == ERR_NONE) { - furi_delay_ms(100); - } - - key_data[0] = 0; - key_data[1] = 0; - key_data[2] = 0; - key_data[3] = 0; - - } else { - /* chip is invisible, try to unlock */ - FURI_LOG_D(TAG, " chip is invisible, unlocking"); - - if(nfcv_data->auth_method == NfcVAuthMethodManual) { - key |= key_data[0] << 24; - key |= key_data[1] << 16; - key |= key_data[2] << 8; - key |= key_data[3] << 0; - - ret = slix_unlock(nfcv_data, 4); - } else { - key = 0x7FFD6E5B; - key_data[0] = (key >> 24) & 0xFF; - key_data[1] = (key >> 16) & 0xFF; - key_data[2] = (key >> 8) & 0xFF; - key_data[3] = (key >> 0) & 0xFF; - ret = slix_unlock(nfcv_data, 4); - - if(ret != ERR_NONE) { - /* main key failed, trying second one */ - FURI_LOG_D(TAG, " trying second key after resetting"); - - /* reset chip */ - furi_hal_nfc_ll_txrx_off(); - furi_delay_ms(20); - furi_hal_nfc_ll_txrx_on(); - - if(slix_get_random(nfcv_data) != ERR_NONE) { - FURI_LOG_D(TAG, " reset failed"); - } - - key = 0x0F0F0F0F; - key_data[0] = (key >> 24) & 0xFF; - key_data[1] = (key >> 16) & 0xFF; - key_data[2] = (key >> 8) & 0xFF; - key_data[3] = (key >> 0) & 0xFF; - ret = slix_unlock(nfcv_data, 4); - } - } - if(ret != ERR_NONE) { - /* unlock failed */ - FURI_LOG_D(TAG, " => failed, wait for chip to disappear."); - snprintf( - nfcv_data->error, sizeof(nfcv_data->error), "Passwords not\naccepted"); - nfc_worker->callback(NfcWorkerEventWrongCardDetected, nfc_worker->context); - - /* reset chip */ - furi_hal_nfc_ll_txrx_off(); - furi_delay_ms(20); - furi_hal_nfc_ll_txrx_on(); - - /* wait for disappearing */ - while(slix_get_random(NULL) == ERR_NONE) { - furi_delay_ms(100); - } - } - } - } else { - nfc_worker->callback(NfcWorkerEventNoCardDetected, nfc_worker->context); - } - - furi_hal_nfc_ll_txrx_off(); - furi_hal_nfc_sleep(); - furi_delay_ms(100); - } - - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - reader_analyzer_stop(nfc_worker->reader_analyzer); - } -} - -static bool nfc_worker_read_mf_ultralight(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { - bool read_success = false; - MfUltralightReader reader = {}; - MfUltralightData data = {}; - - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - reader_analyzer_prepare_tx_rx(nfc_worker->reader_analyzer, tx_rx, false); - reader_analyzer_start(nfc_worker->reader_analyzer, ReaderAnalyzerModeDebugLog); - } - - do { - // Try to read supported card - FURI_LOG_I(TAG, "Trying to read a supported card ..."); - for(size_t i = 0; i < NfcSupportedCardTypeEnd; i++) { - if(nfc_supported_card[i].protocol == NfcDeviceProtocolMifareUl) { - if(nfc_supported_card[i].verify(nfc_worker, tx_rx)) { - if(nfc_supported_card[i].read(nfc_worker, tx_rx)) { - read_success = true; - nfc_supported_card[i].parse(nfc_worker->dev_data); - break; - } - } else { - furi_hal_nfc_sleep(); - } - } - } - if(read_success) break; - furi_hal_nfc_sleep(); - - // Otherwise, try to read as usual - if(!furi_hal_nfc_detect(&nfc_worker->dev_data->nfc_data, 200)) break; - if(!mf_ul_read_card(tx_rx, &reader, &data)) break; - // Copy data - nfc_worker->dev_data->mf_ul_data = data; - read_success = true; - } while(false); - - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - reader_analyzer_stop(nfc_worker->reader_analyzer); - } - - return read_success; -} - -static bool nfc_worker_read_mf_classic(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { - furi_assert(nfc_worker->callback); - bool read_success = false; - - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - reader_analyzer_prepare_tx_rx(nfc_worker->reader_analyzer, tx_rx, false); - reader_analyzer_start(nfc_worker->reader_analyzer, ReaderAnalyzerModeDebugLog); - } - - do { - // Try to read supported card - FURI_LOG_I(TAG, "Trying to read a supported card ..."); - for(size_t i = 0; i < NfcSupportedCardTypeEnd; i++) { - if(nfc_supported_card[i].protocol == NfcDeviceProtocolMifareClassic) { - if(nfc_supported_card[i].verify(nfc_worker, tx_rx)) { - if(nfc_supported_card[i].read(nfc_worker, tx_rx)) { - read_success = true; - nfc_supported_card[i].parse(nfc_worker->dev_data); - break; - } - } else { - furi_hal_nfc_sleep(); - } - } - } - if(read_success) break; - // Try to read card with key cache - FURI_LOG_I(TAG, "Search for key cache ..."); - if(nfc_worker->callback(NfcWorkerEventReadMfClassicLoadKeyCache, nfc_worker->context)) { - FURI_LOG_I(TAG, "Load keys cache success. Start reading"); - uint8_t sectors_read = - mf_classic_update_card(tx_rx, &nfc_worker->dev_data->mf_classic_data); - uint8_t sectors_total = - mf_classic_get_total_sectors_num(nfc_worker->dev_data->mf_classic_data.type); - FURI_LOG_I(TAG, "Read %d sectors out of %d total", sectors_read, sectors_total); - read_success = mf_classic_is_card_read(&nfc_worker->dev_data->mf_classic_data); - } - } while(false); - - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - reader_analyzer_stop(nfc_worker->reader_analyzer); - } - return read_success; -} - -static bool nfc_worker_read_mf_desfire(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { - bool read_success = false; - MifareDesfireData* data = &nfc_worker->dev_data->mf_df_data; - - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - reader_analyzer_prepare_tx_rx(nfc_worker->reader_analyzer, tx_rx, false); - reader_analyzer_start(nfc_worker->reader_analyzer, ReaderAnalyzerModeDebugLog); - } - - do { - if(!furi_hal_nfc_detect(&nfc_worker->dev_data->nfc_data, 300)) break; - if(!mf_df_read_card(tx_rx, data)) break; - FURI_LOG_I(TAG, "Trying to parse a supported card ..."); - - // The model for parsing DESFire is a little different to other cards; - // we don't have parsers to provide encryption keys, so we can read the - // data normally, and then pass the read data to a parser. - // - // There are fully-protected DESFire cards, but providing keys for them - // is difficult (and unnessesary for many transit cards). - for(size_t i = 0; i < NfcSupportedCardTypeEnd; i++) { - if(nfc_supported_card[i].protocol == NfcDeviceProtocolMifareDesfire) { - if(nfc_supported_card[i].parse(nfc_worker->dev_data)) break; - } - } - read_success = true; - } while(false); - - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - reader_analyzer_stop(nfc_worker->reader_analyzer); - } - - return read_success; -} - -static bool nfc_worker_read_nfca(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { - FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data; - - bool card_read = false; - furi_hal_nfc_sleep(); - if(mf_ul_check_card_type(nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak)) { - FURI_LOG_I(TAG, "Mifare Ultralight / NTAG detected"); - nfc_worker->dev_data->protocol = NfcDeviceProtocolMifareUl; - card_read = nfc_worker_read_mf_ultralight(nfc_worker, tx_rx); - } else if(mf_classic_check_card_type(nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak)) { - FURI_LOG_I(TAG, "Mifare Classic detected"); - nfc_worker->dev_data->protocol = NfcDeviceProtocolMifareClassic; - nfc_worker->dev_data->mf_classic_data.type = - mf_classic_get_classic_type(nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak); - card_read = nfc_worker_read_mf_classic(nfc_worker, tx_rx); - } else if(mf_df_check_card_type(nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak)) { - FURI_LOG_I(TAG, "Mifare DESFire detected"); - nfc_worker->dev_data->protocol = NfcDeviceProtocolMifareDesfire; - if(!nfc_worker_read_mf_desfire(nfc_worker, tx_rx)) { - FURI_LOG_I(TAG, "Unknown card. Save UID"); - nfc_worker->dev_data->protocol = NfcDeviceProtocolUnknown; - } - card_read = true; - } else { - nfc_worker->dev_data->protocol = NfcDeviceProtocolUnknown; - card_read = true; - } - - return card_read; -} - -void nfc_worker_read(NfcWorker* nfc_worker) { - furi_assert(nfc_worker); - furi_assert(nfc_worker->callback); - - nfc_device_data_clear(nfc_worker->dev_data); - NfcDeviceData* dev_data = nfc_worker->dev_data; - FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data; - FuriHalNfcTxRxContext tx_rx = {}; - NfcWorkerEvent event = 0; - bool card_not_detected_notified = false; - - while(nfc_worker->state == NfcWorkerStateRead) { - if(furi_hal_nfc_detect(nfc_data, 300)) { - // Process first found device - nfc_worker->callback(NfcWorkerEventCardDetected, nfc_worker->context); - card_not_detected_notified = false; - if(nfc_data->type == FuriHalNfcTypeA) { - if(nfc_worker_read_nfca(nfc_worker, &tx_rx)) { - if(dev_data->protocol == NfcDeviceProtocolMifareUl) { - event = NfcWorkerEventReadMfUltralight; - break; - } else if(dev_data->protocol == NfcDeviceProtocolMifareClassic) { - event = NfcWorkerEventReadMfClassicDone; - break; - } else if(dev_data->protocol == NfcDeviceProtocolMifareDesfire) { - event = NfcWorkerEventReadMfDesfire; - break; - } else if(dev_data->protocol == NfcDeviceProtocolUnknown) { - event = NfcWorkerEventReadUidNfcA; - break; - } - } else { - if(dev_data->protocol == NfcDeviceProtocolMifareClassic) { - event = NfcWorkerEventReadMfClassicDictAttackRequired; - break; - } - } - } else if(nfc_data->type == FuriHalNfcTypeB) { - event = NfcWorkerEventReadUidNfcB; - break; - } else if(nfc_data->type == FuriHalNfcTypeF) { - event = NfcWorkerEventReadUidNfcF; - break; - } else if(nfc_data->type == FuriHalNfcTypeV) { - FURI_LOG_I(TAG, "NfcV detected"); - nfc_worker->dev_data->protocol = NfcDeviceProtocolNfcV; - if(nfc_worker_read_nfcv(nfc_worker, &tx_rx)) { - FURI_LOG_I(TAG, "nfc_worker_read_nfcv success"); - } - event = NfcWorkerEventReadNfcV; - break; - } - } else { - if(!card_not_detected_notified) { - nfc_worker->callback(NfcWorkerEventNoCardDetected, nfc_worker->context); - card_not_detected_notified = true; - } - } - furi_hal_nfc_sleep(); - furi_delay_ms(100); - } - // Notify caller and exit - if(event > NfcWorkerEventReserved) { - nfc_worker->callback(event, nfc_worker->context); - } -} - -void nfc_worker_read_type(NfcWorker* nfc_worker) { - furi_assert(nfc_worker); - furi_assert(nfc_worker->callback); - - NfcReadMode read_mode = nfc_worker->dev_data->read_mode; - nfc_device_data_clear(nfc_worker->dev_data); - NfcDeviceData* dev_data = nfc_worker->dev_data; - FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data; - FuriHalNfcTxRxContext tx_rx = {}; - NfcWorkerEvent event = 0; - bool card_not_detected_notified = false; - - while(nfc_worker->state == NfcWorkerStateRead) { - if(furi_hal_nfc_detect(nfc_data, 300)) { - FURI_LOG_D(TAG, "Card detected"); - furi_hal_nfc_sleep(); - // Process first found device - nfc_worker->callback(NfcWorkerEventCardDetected, nfc_worker->context); - card_not_detected_notified = false; - if(nfc_data->type == FuriHalNfcTypeA) { - if(read_mode == NfcReadModeMfClassic) { - nfc_worker->dev_data->protocol = NfcDeviceProtocolMifareClassic; - nfc_worker->dev_data->mf_classic_data.type = mf_classic_get_classic_type( - nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak); - if(nfc_worker_read_mf_classic(nfc_worker, &tx_rx)) { - FURI_LOG_D(TAG, "Card read"); - dev_data->protocol = NfcDeviceProtocolMifareClassic; - event = NfcWorkerEventReadMfClassicDone; - break; - } else { - FURI_LOG_D(TAG, "Card read failed"); - dev_data->protocol = NfcDeviceProtocolMifareClassic; - event = NfcWorkerEventReadMfClassicDictAttackRequired; - break; - } - } else if(read_mode == NfcReadModeMfUltralight) { - FURI_LOG_I(TAG, "Mifare Ultralight / NTAG"); - nfc_worker->dev_data->protocol = NfcDeviceProtocolMifareUl; - if(nfc_worker_read_mf_ultralight(nfc_worker, &tx_rx)) { - event = NfcWorkerEventReadMfUltralight; - break; - } - } else if(read_mode == NfcReadModeMfDesfire) { - nfc_worker->dev_data->protocol = NfcDeviceProtocolMifareDesfire; - if(nfc_worker_read_mf_desfire(nfc_worker, &tx_rx)) { - event = NfcWorkerEventReadMfDesfire; - break; - } - } else if(read_mode == NfcReadModeNFCA) { - nfc_worker->dev_data->protocol = NfcDeviceProtocolUnknown; - event = NfcWorkerEventReadUidNfcA; - break; - } - } - } else { - if(!card_not_detected_notified) { - nfc_worker->callback(NfcWorkerEventNoCardDetected, nfc_worker->context); - card_not_detected_notified = true; - } - } - furi_hal_nfc_sleep(); - furi_delay_ms(100); - } - // Notify caller and exit - if(event > NfcWorkerEventReserved) { - nfc_worker->callback(event, nfc_worker->context); - } -} - -void nfc_worker_emulate_uid(NfcWorker* nfc_worker) { - FuriHalNfcTxRxContext tx_rx = {}; - FuriHalNfcDevData* data = &nfc_worker->dev_data->nfc_data; - NfcReaderRequestData* reader_data = &nfc_worker->dev_data->reader_data; - - // TODO add support for RATS - // Need to save ATS to support ISO-14443A-4 emulation - - while(nfc_worker->state == NfcWorkerStateUidEmulate) { - if(furi_hal_nfc_listen(data->uid, data->uid_len, data->atqa, data->sak, false, 100)) { - if(furi_hal_nfc_tx_rx(&tx_rx, 100)) { - reader_data->size = tx_rx.rx_bits / 8; - if(reader_data->size > 0) { - memcpy(reader_data->data, tx_rx.rx_data, reader_data->size); - if(nfc_worker->callback) { - nfc_worker->callback(NfcWorkerEventSuccess, nfc_worker->context); - } - } - } else { - FURI_LOG_E(TAG, "Failed to get reader commands"); - } - } - } -} - -void nfc_worker_emulate_apdu(NfcWorker* nfc_worker) { - FuriHalNfcTxRxContext tx_rx = {}; - FuriHalNfcDevData params = { - .uid = {0xCF, 0x72, 0xd4, 0x40}, - .uid_len = 4, - .atqa = {0x00, 0x04}, - .sak = 0x20, - .type = FuriHalNfcTypeA, - }; - - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - reader_analyzer_prepare_tx_rx(nfc_worker->reader_analyzer, &tx_rx, true); - reader_analyzer_start(nfc_worker->reader_analyzer, ReaderAnalyzerModeDebugLog); - } - - while(nfc_worker->state == NfcWorkerStateEmulateApdu) { //-V1044 - if(furi_hal_nfc_listen(params.uid, params.uid_len, params.atqa, params.sak, false, 300)) { - FURI_LOG_D(TAG, "POS terminal detected"); - if(emv_card_emulation(&tx_rx)) { - FURI_LOG_D(TAG, "EMV card emulated"); - } - } else { - FURI_LOG_D(TAG, "Can't find reader"); - } - furi_hal_nfc_sleep(); - furi_delay_ms(20); - } - - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - reader_analyzer_stop(nfc_worker->reader_analyzer); - } -} - -void nfc_worker_mf_ultralight_auth_received_callback(MfUltralightAuth auth, void* context) { - furi_assert(context); - - NfcWorker* nfc_worker = context; - nfc_worker->dev_data->mf_ul_auth = auth; - if(nfc_worker->callback) { - nfc_worker->callback(NfcWorkerEventMfUltralightPwdAuth, nfc_worker->context); - } -} - -void nfc_worker_emulate_mf_ultralight(NfcWorker* nfc_worker) { - FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data; - MfUltralightEmulator emulator = {}; - mf_ul_prepare_emulation(&emulator, &nfc_worker->dev_data->mf_ul_data); - - // TODO rework with reader analyzer - emulator.auth_received_callback = nfc_worker_mf_ultralight_auth_received_callback; - emulator.context = nfc_worker; - - rfal_platform_spi_acquire(); - - while(nfc_worker->state == NfcWorkerStateMfUltralightEmulate) { - mf_ul_reset_emulation(&emulator, true); - furi_hal_nfc_emulate_nfca( - nfc_data->uid, - nfc_data->uid_len, - nfc_data->atqa, - nfc_data->sak, - mf_ul_prepare_emulation_response, - &emulator, - 5000); - // Check if data was modified - if(emulator.data_changed) { - nfc_worker->dev_data->mf_ul_data = emulator.data; - if(nfc_worker->callback) { - nfc_worker->callback(NfcWorkerEventSuccess, nfc_worker->context); - } - emulator.data_changed = false; - } - } - - rfal_platform_spi_release(); -} - -static bool nfc_worker_mf_get_b_key_from_sector_trailer( - FuriHalNfcTxRxContext* tx_rx, - uint16_t sector, - uint64_t key, - uint64_t* found_key) { - // Some access conditions allow reading B key via A key - - uint8_t block = mf_classic_get_sector_trailer_block_num_by_sector(sector); - - Crypto1 crypto = {}; - MfClassicBlock block_tmp = {}; - MfClassicAuthContext auth_context = {.sector = sector, .key_a = MF_CLASSIC_NO_KEY, .key_b = 0}; - - furi_hal_nfc_sleep(); - - if(mf_classic_auth_attempt(tx_rx, &crypto, &auth_context, key)) { - if(mf_classic_read_block(tx_rx, &crypto, block, &block_tmp)) { - *found_key = nfc_util_bytes2num(&block_tmp.value[10], sizeof(uint8_t) * 6); - - return *found_key; - } - } - - return false; -} - -static void nfc_worker_mf_classic_key_attack( - NfcWorker* nfc_worker, - uint64_t key, - FuriHalNfcTxRxContext* tx_rx, - uint16_t start_sector) { - furi_assert(nfc_worker); - furi_assert(nfc_worker->callback); - - bool card_found_notified = true; - bool card_removed_notified = false; - - MfClassicData* data = &nfc_worker->dev_data->mf_classic_data; - NfcMfClassicDictAttackData* dict_attack_data = - &nfc_worker->dev_data->mf_classic_dict_attack_data; - uint32_t total_sectors = mf_classic_get_total_sectors_num(data->type); - - furi_assert(start_sector < total_sectors); - - nfc_worker->callback(NfcWorkerEventKeyAttackStart, nfc_worker->context); - - // Check every sector's A and B keys with the given key - for(size_t i = start_sector; i < total_sectors; i++) { - nfc_worker->callback(NfcWorkerEventKeyAttackNextSector, nfc_worker->context); - dict_attack_data->current_sector = i; - furi_hal_nfc_sleep(); - if(furi_hal_nfc_activate_nfca(200, NULL)) { - furi_hal_nfc_sleep(); - if(!card_found_notified) { - nfc_worker->callback(NfcWorkerEventCardDetected, nfc_worker->context); - card_found_notified = true; - card_removed_notified = false; - } - uint8_t block_num = mf_classic_get_sector_trailer_block_num_by_sector(i); - if(mf_classic_is_sector_read(data, i)) continue; - if(!mf_classic_is_key_found(data, i, MfClassicKeyA)) { - FURI_LOG_D(TAG, "Trying A key for sector %d, key: %012llX", i, key); - if(mf_classic_authenticate(tx_rx, block_num, key, MfClassicKeyA)) { - mf_classic_set_key_found(data, i, MfClassicKeyA, key); - FURI_LOG_D(TAG, "Key A found: %012llX", key); - nfc_worker->callback(NfcWorkerEventFoundKeyA, nfc_worker->context); - - uint64_t found_key; - if(nfc_worker_mf_get_b_key_from_sector_trailer(tx_rx, i, key, &found_key)) { - FURI_LOG_D(TAG, "Found B key via reading sector %d", i); - mf_classic_set_key_found(data, i, MfClassicKeyB, found_key); - - if(nfc_worker->state == NfcWorkerStateMfClassicDictAttack) { - nfc_worker->callback(NfcWorkerEventFoundKeyB, nfc_worker->context); - } - } - } - furi_hal_nfc_sleep(); - } - if(!mf_classic_is_key_found(data, i, MfClassicKeyB)) { - FURI_LOG_D(TAG, "Trying B key for sector %d, key: %012llX", i, key); - if(mf_classic_authenticate(tx_rx, block_num, key, MfClassicKeyB)) { - mf_classic_set_key_found(data, i, MfClassicKeyB, key); - FURI_LOG_D(TAG, "Key B found: %012llX", key); - nfc_worker->callback(NfcWorkerEventFoundKeyB, nfc_worker->context); - } - } - - if(mf_classic_is_sector_read(data, i)) continue; - mf_classic_read_sector(tx_rx, data, i); - } else { - if(!card_removed_notified) { - nfc_worker->callback(NfcWorkerEventNoCardDetected, nfc_worker->context); - card_removed_notified = true; - card_found_notified = false; - } - } - if(nfc_worker->state != NfcWorkerStateMfClassicDictAttack) break; - } - nfc_worker->callback(NfcWorkerEventKeyAttackStop, nfc_worker->context); -} - -void nfc_worker_mf_classic_dict_attack(NfcWorker* nfc_worker) { - furi_assert(nfc_worker); - furi_assert(nfc_worker->callback); - - MfClassicData* data = &nfc_worker->dev_data->mf_classic_data; - NfcMfClassicDictAttackData* dict_attack_data = - &nfc_worker->dev_data->mf_classic_dict_attack_data; - uint32_t total_sectors = mf_classic_get_total_sectors_num(data->type); - uint64_t key = 0; - uint64_t prev_key = 0; - FuriHalNfcTxRxContext tx_rx = {}; - bool card_found_notified = true; - bool card_removed_notified = false; - - // Load dictionary - MfClassicDict* dict = dict_attack_data->dict; - if(!dict) { - FURI_LOG_E(TAG, "Dictionary not found"); - nfc_worker->callback(NfcWorkerEventNoDictFound, nfc_worker->context); - return; - } - - FURI_LOG_D( - TAG, "Start Dictionary attack, Key Count %lu", mf_classic_dict_get_total_keys(dict)); - for(size_t i = 0; i < total_sectors; i++) { - FURI_LOG_I(TAG, "Sector %d", i); - nfc_worker->callback(NfcWorkerEventNewSector, nfc_worker->context); - uint8_t block_num = mf_classic_get_sector_trailer_block_num_by_sector(i); - if(mf_classic_is_sector_read(data, i)) continue; - if(mf_classic_is_key_found(data, i, MfClassicKeyA) && - mf_classic_is_key_found(data, i, MfClassicKeyB)) - continue; - uint16_t key_index = 0; - while(mf_classic_dict_get_next_key(dict, &key)) { - FURI_LOG_T(TAG, "Key %d", key_index); - if(++key_index % NFC_DICT_KEY_BATCH_SIZE == 0) { - nfc_worker->callback(NfcWorkerEventNewDictKeyBatch, nfc_worker->context); - } - furi_hal_nfc_sleep(); - uint32_t cuid; - if(furi_hal_nfc_activate_nfca(200, &cuid)) { - bool deactivated = false; - if(!card_found_notified) { - nfc_worker->callback(NfcWorkerEventCardDetected, nfc_worker->context); - card_found_notified = true; - card_removed_notified = false; - nfc_worker_mf_classic_key_attack(nfc_worker, prev_key, &tx_rx, i); - deactivated = true; - } - FURI_LOG_D(TAG, "Try to auth to sector %d with key %012llX", i, key); - if(!mf_classic_is_key_found(data, i, MfClassicKeyA)) { - if(mf_classic_authenticate_skip_activate( - &tx_rx, block_num, key, MfClassicKeyA, !deactivated, cuid)) { - mf_classic_set_key_found(data, i, MfClassicKeyA, key); - FURI_LOG_D(TAG, "Key A found: %012llX", key); - nfc_worker->callback(NfcWorkerEventFoundKeyA, nfc_worker->context); - - uint64_t found_key; - if(nfc_worker_mf_get_b_key_from_sector_trailer( - &tx_rx, i, key, &found_key)) { - FURI_LOG_D(TAG, "Found B key via reading sector %d", i); - mf_classic_set_key_found(data, i, MfClassicKeyB, found_key); - - if(nfc_worker->state == NfcWorkerStateMfClassicDictAttack) { - nfc_worker->callback(NfcWorkerEventFoundKeyB, nfc_worker->context); - } - - nfc_worker_mf_classic_key_attack(nfc_worker, found_key, &tx_rx, i + 1); - break; - } - nfc_worker_mf_classic_key_attack(nfc_worker, key, &tx_rx, i + 1); - } - furi_hal_nfc_sleep(); - deactivated = true; - } else { - // If the key A is marked as found and matches the searching key, invalidate it - MfClassicSectorTrailer* sec_trailer = - mf_classic_get_sector_trailer_by_sector(data, i); - - uint8_t current_key[6]; - nfc_util_num2bytes(key, 6, current_key); - - if(mf_classic_is_key_found(data, i, MfClassicKeyA) && - memcmp(sec_trailer->key_a, current_key, 6) == 0) { - if(!mf_classic_authenticate_skip_activate( - &tx_rx, block_num, key, MfClassicKeyA, !deactivated, cuid)) { - mf_classic_set_key_not_found(data, i, MfClassicKeyA); - FURI_LOG_D(TAG, "Key %dA not found in attack", i); - } - } - furi_hal_nfc_sleep(); - deactivated = true; - } - if(!mf_classic_is_key_found(data, i, MfClassicKeyB)) { - if(mf_classic_authenticate_skip_activate( - &tx_rx, block_num, key, MfClassicKeyB, !deactivated, cuid)) { //-V547 - FURI_LOG_D(TAG, "Key B found: %012llX", key); - mf_classic_set_key_found(data, i, MfClassicKeyB, key); - nfc_worker->callback(NfcWorkerEventFoundKeyB, nfc_worker->context); - nfc_worker_mf_classic_key_attack(nfc_worker, key, &tx_rx, i + 1); - } - deactivated = true; //-V1048 - } else { - // If the key B is marked as found and matches the searching key, invalidate it - MfClassicSectorTrailer* sec_trailer = - mf_classic_get_sector_trailer_by_sector(data, i); - - uint8_t current_key[6]; - nfc_util_num2bytes(key, 6, current_key); - - if(mf_classic_is_key_found(data, i, MfClassicKeyB) && - memcmp(sec_trailer->key_b, current_key, 6) == 0) { - if(!mf_classic_authenticate_skip_activate( - &tx_rx, block_num, key, MfClassicKeyB, !deactivated, cuid)) { //-V547 - mf_classic_set_key_not_found(data, i, MfClassicKeyB); - FURI_LOG_D(TAG, "Key %dB not found in attack", i); - } - furi_hal_nfc_sleep(); - deactivated = true; //-V1048 - } - } - if(mf_classic_is_key_found(data, i, MfClassicKeyA) && - mf_classic_is_key_found(data, i, MfClassicKeyB)) - break; - if(nfc_worker->state != NfcWorkerStateMfClassicDictAttack) break; - } else { - if(!card_removed_notified) { - nfc_worker->callback(NfcWorkerEventNoCardDetected, nfc_worker->context); - card_removed_notified = true; - card_found_notified = false; - } - if(nfc_worker->state != NfcWorkerStateMfClassicDictAttack) break; - } - prev_key = key; - } - if(nfc_worker->state != NfcWorkerStateMfClassicDictAttack) break; - mf_classic_read_sector(&tx_rx, data, i); - mf_classic_dict_rewind(dict); - } - if(nfc_worker->state == NfcWorkerStateMfClassicDictAttack) { - nfc_worker->callback(NfcWorkerEventSuccess, nfc_worker->context); - } else { - nfc_worker->callback(NfcWorkerEventAborted, nfc_worker->context); - } -} - -void nfc_worker_emulate_mf_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; - - rfal_platform_spi_acquire(); - - furi_hal_nfc_listen_start(nfc_data); - while(nfc_worker->state == NfcWorkerStateMfClassicEmulate) { //-V1044 - if(furi_hal_nfc_listen_rx(&tx_rx, 300)) { - if(!mf_classic_emulator(&emulator, &tx_rx, false)) { - furi_hal_nfc_listen_start(nfc_data); - } - } - } - 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); - - rfal_platform_spi_release(); -} - -void nfc_worker_write_mf_classic(NfcWorker* nfc_worker) { - FuriHalNfcTxRxContext tx_rx = {}; - bool card_found_notified = false; - FuriHalNfcDevData nfc_data = {}; - MfClassicData* src_data = &nfc_worker->dev_data->mf_classic_data; - MfClassicData dest_data = *src_data; - - while(nfc_worker->state == NfcWorkerStateMfClassicWrite) { - if(furi_hal_nfc_detect(&nfc_data, 200)) { - if(!card_found_notified) { - nfc_worker->callback(NfcWorkerEventCardDetected, nfc_worker->context); - card_found_notified = true; - } - furi_hal_nfc_sleep(); - - FURI_LOG_I(TAG, "Check low level nfc data"); - if(memcmp(&nfc_data, &nfc_worker->dev_data->nfc_data, sizeof(FuriHalNfcDevData)) != - 0) { - FURI_LOG_E(TAG, "Wrong card"); - nfc_worker->callback(NfcWorkerEventWrongCard, nfc_worker->context); - break; - } - - FURI_LOG_I(TAG, "Check mf classic type"); - MfClassicType type = - mf_classic_get_classic_type(nfc_data.atqa[0], nfc_data.atqa[1], nfc_data.sak); - if(type != nfc_worker->dev_data->mf_classic_data.type) { - FURI_LOG_E(TAG, "Wrong mf classic type"); - nfc_worker->callback(NfcWorkerEventWrongCard, nfc_worker->context); - break; - } - - // Set blocks not read - mf_classic_set_sector_data_not_read(&dest_data); - FURI_LOG_I(TAG, "Updating card sectors"); - uint8_t total_sectors = mf_classic_get_total_sectors_num(type); - bool write_success = true; - for(uint8_t i = 0; i < total_sectors; i++) { - FURI_LOG_I(TAG, "Reading sector %d", i); - mf_classic_read_sector(&tx_rx, &dest_data, i); - bool old_data_read = mf_classic_is_sector_data_read(src_data, i); - bool new_data_read = mf_classic_is_sector_data_read(&dest_data, i); - if(old_data_read != new_data_read) { - FURI_LOG_E(TAG, "Failed to update sector %d", i); - write_success = false; - break; - } - if(nfc_worker->state != NfcWorkerStateMfClassicWrite) break; - if(!mf_classic_write_sector(&tx_rx, &dest_data, src_data, i)) { - FURI_LOG_E(TAG, "Failed to write %d sector", i); - write_success = false; - break; - } - } - if(nfc_worker->state != NfcWorkerStateMfClassicWrite) break; - if(write_success) { - nfc_worker->callback(NfcWorkerEventSuccess, nfc_worker->context); - break; - } else { - nfc_worker->callback(NfcWorkerEventFail, nfc_worker->context); - break; - } - - } else { - if(card_found_notified) { - nfc_worker->callback(NfcWorkerEventNoCardDetected, nfc_worker->context); - card_found_notified = false; - } - } - furi_delay_ms(300); - } -} - -void nfc_worker_update_mf_classic(NfcWorker* nfc_worker) { - FuriHalNfcTxRxContext tx_rx = {}; - bool card_found_notified = false; - FuriHalNfcDevData nfc_data = {}; - MfClassicData* old_data = &nfc_worker->dev_data->mf_classic_data; - MfClassicData new_data = *old_data; - - while(nfc_worker->state == NfcWorkerStateMfClassicUpdate) { - if(furi_hal_nfc_detect(&nfc_data, 200)) { - if(!card_found_notified) { - nfc_worker->callback(NfcWorkerEventCardDetected, nfc_worker->context); - card_found_notified = true; - } - furi_hal_nfc_sleep(); - - FURI_LOG_I(TAG, "Check low level nfc data"); - if(memcmp(&nfc_data, &nfc_worker->dev_data->nfc_data, sizeof(FuriHalNfcDevData)) != - 0) { - FURI_LOG_E(TAG, "Low level nfc data mismatch"); - nfc_worker->callback(NfcWorkerEventWrongCard, nfc_worker->context); - break; - } - - FURI_LOG_I(TAG, "Check MF classic type"); - MfClassicType type = - mf_classic_get_classic_type(nfc_data.atqa[0], nfc_data.atqa[1], nfc_data.sak); - if(type != nfc_worker->dev_data->mf_classic_data.type) { - FURI_LOG_E(TAG, "MF classic type mismatch"); - nfc_worker->callback(NfcWorkerEventWrongCard, nfc_worker->context); - break; - } - - // Set blocks not read - mf_classic_set_sector_data_not_read(&new_data); - FURI_LOG_I(TAG, "Updating card sectors"); - uint8_t total_sectors = mf_classic_get_total_sectors_num(type); - bool update_success = true; - for(uint8_t i = 0; i < total_sectors; i++) { - FURI_LOG_I(TAG, "Reading sector %d", i); - mf_classic_read_sector(&tx_rx, &new_data, i); - bool old_data_read = mf_classic_is_sector_data_read(old_data, i); - bool new_data_read = mf_classic_is_sector_data_read(&new_data, i); - if(old_data_read != new_data_read) { - FURI_LOG_E(TAG, "Failed to update sector %d", i); - update_success = false; - break; - } - if(nfc_worker->state != NfcWorkerStateMfClassicUpdate) break; - } - if(nfc_worker->state != NfcWorkerStateMfClassicUpdate) break; - - // Check updated data - if(update_success) { - *old_data = new_data; - nfc_worker->callback(NfcWorkerEventSuccess, nfc_worker->context); - break; - } - } else { - if(card_found_notified) { - nfc_worker->callback(NfcWorkerEventNoCardDetected, nfc_worker->context); - card_found_notified = false; - } - } - furi_delay_ms(300); - } -} - -void nfc_worker_mf_ultralight_read_auth(NfcWorker* nfc_worker) { - furi_assert(nfc_worker); - furi_assert(nfc_worker->callback); - - MfUltralightData* data = &nfc_worker->dev_data->mf_ul_data; - FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data; - FuriHalNfcTxRxContext tx_rx = {}; - MfUltralightReader reader = {}; - mf_ul_reset(data); - - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - reader_analyzer_prepare_tx_rx(nfc_worker->reader_analyzer, &tx_rx, true); - reader_analyzer_start(nfc_worker->reader_analyzer, ReaderAnalyzerModeDebugLog); - } - - uint32_t key = 0; - uint16_t pack = 0; - while(nfc_worker->state == NfcWorkerStateReadMfUltralightReadAuth) { - furi_hal_nfc_sleep(); - if(furi_hal_nfc_detect(nfc_data, 300) && nfc_data->type == FuriHalNfcTypeA) { - if(mf_ul_check_card_type(nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak)) { - nfc_worker->callback(NfcWorkerEventCardDetected, nfc_worker->context); - if(data->auth_method == MfUltralightAuthMethodManual || - data->auth_method == MfUltralightAuthMethodAuto) { - nfc_worker->callback(NfcWorkerEventMfUltralightPassKey, nfc_worker->context); - key = nfc_util_bytes2num(data->auth_key, 4); - } else if(data->auth_method == MfUltralightAuthMethodAmeebo) { - key = mf_ul_pwdgen_amiibo(nfc_data); - } else if(data->auth_method == MfUltralightAuthMethodXiaomi) { - key = mf_ul_pwdgen_xiaomi(nfc_data); - } else { - FURI_LOG_E(TAG, "Incorrect auth method"); - break; - } - - data->auth_success = mf_ultralight_authenticate(&tx_rx, key, &pack); - - if(!data->auth_success) { - // Reset card - furi_hal_nfc_sleep(); - if(!furi_hal_nfc_activate_nfca(300, NULL)) { - nfc_worker->callback(NfcWorkerEventFail, nfc_worker->context); - break; - } - } - - mf_ul_read_card(&tx_rx, &reader, data); - if(data->auth_success) { - MfUltralightConfigPages* config_pages = mf_ultralight_get_config_pages(data); - if(config_pages != NULL) { - config_pages->auth_data.pwd.value = REVERSE_BYTES_U32(key); - config_pages->auth_data.pack.value = pack; - } - nfc_worker->callback(NfcWorkerEventSuccess, nfc_worker->context); - break; - } else { - nfc_worker->callback(NfcWorkerEventFail, nfc_worker->context); - break; - } - } else { - nfc_worker->callback(NfcWorkerEventWrongCardDetected, nfc_worker->context); - furi_delay_ms(10); - } - } else { - nfc_worker->callback(NfcWorkerEventNoCardDetected, nfc_worker->context); - furi_delay_ms(10); - } - } - - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - reader_analyzer_stop(nfc_worker->reader_analyzer); - } -} - -static void nfc_worker_reader_analyzer_callback(ReaderAnalyzerEvent event, void* context) { - furi_assert(context); - NfcWorker* nfc_worker = context; - - if((nfc_worker->state == NfcWorkerStateAnalyzeReader) && - (event == ReaderAnalyzerEventMfkeyCollected)) { - if(nfc_worker->callback) { - nfc_worker->callback(NfcWorkerEventDetectReaderMfkeyCollected, nfc_worker->context); - } - } -} - -void nfc_worker_analyze_reader(NfcWorker* nfc_worker) { - furi_assert(nfc_worker); - furi_assert(nfc_worker->callback); - - FuriHalNfcTxRxContext tx_rx = {}; - - ReaderAnalyzer* reader_analyzer = nfc_worker->reader_analyzer; - FuriHalNfcDevData* nfc_data = NULL; - if(nfc_worker->dev_data->protocol == NfcDeviceProtocolMifareClassic) { - nfc_data = &nfc_worker->dev_data->nfc_data; - reader_analyzer_set_nfc_data(reader_analyzer, nfc_data); - } else { - nfc_data = reader_analyzer_get_nfc_data(reader_analyzer); - } - 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; - reader_analyzer_prepare_tx_rx(reader_analyzer, &tx_rx, true); - reader_analyzer_start(nfc_worker->reader_analyzer, ReaderAnalyzerModeMfkey); - reader_analyzer_set_callback(reader_analyzer, nfc_worker_reader_analyzer_callback, nfc_worker); - - rfal_platform_spi_acquire(); - - FURI_LOG_D(TAG, "Start reader analyzer"); - - uint8_t reader_no_data_received_cnt = 0; - bool reader_no_data_notified = true; - - while(nfc_worker->state == NfcWorkerStateAnalyzeReader) { - furi_hal_nfc_listen_start(nfc_data); - if(furi_hal_nfc_listen_rx(&tx_rx, 300)) { - if(reader_no_data_notified) { - nfc_worker->callback(NfcWorkerEventDetectReaderDetected, nfc_worker->context); - } - reader_no_data_received_cnt = 0; - reader_no_data_notified = false; - NfcProtocol protocol = - reader_analyzer_guess_protocol(reader_analyzer, tx_rx.rx_data, tx_rx.rx_bits / 8); - if(protocol == NfcDeviceProtocolMifareClassic) { - if(!mf_classic_emulator(&emulator, &tx_rx, true)) { - furi_hal_nfc_listen_start(nfc_data); - } - } - } else { - reader_no_data_received_cnt++; - if(!reader_no_data_notified && (reader_no_data_received_cnt > 5)) { - nfc_worker->callback(NfcWorkerEventDetectReaderLost, nfc_worker->context); - reader_no_data_received_cnt = 0; - reader_no_data_notified = true; - } - FURI_LOG_D(TAG, "No data from reader"); - continue; - } - furi_delay_ms(1); - } - - rfal_platform_spi_release(); - - reader_analyzer_stop(nfc_worker->reader_analyzer); - - nfca_signal_free(nfca_signal); -} diff --git a/lib/nfc/nfc_worker.h b/lib/nfc/nfc_worker.h deleted file mode 100644 index f9f5900bbde..00000000000 --- a/lib/nfc/nfc_worker.h +++ /dev/null @@ -1,108 +0,0 @@ -#pragma once - -#include "nfc_device.h" - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct NfcWorker NfcWorker; - -typedef enum { - // Init states - NfcWorkerStateNone, - NfcWorkerStateReady, - // Main worker states - NfcWorkerStateRead, - NfcWorkerStateUidEmulate, - NfcWorkerStateMfUltralightEmulate, - NfcWorkerStateMfClassicEmulate, - NfcWorkerStateMfClassicWrite, - NfcWorkerStateMfClassicUpdate, - NfcWorkerStateReadMfUltralightReadAuth, - NfcWorkerStateMfClassicDictAttack, - NfcWorkerStateAnalyzeReader, - NfcWorkerStateNfcVEmulate, - NfcWorkerStateNfcVUnlock, - NfcWorkerStateNfcVUnlockAndSave, - NfcWorkerStateNfcVSniff, - // Debug - NfcWorkerStateEmulateApdu, - NfcWorkerStateField, - // Transition - NfcWorkerStateStop, -} NfcWorkerState; - -typedef enum { - // Reserve first 50 events for application events - NfcWorkerEventReserved = 50, - - // Nfc read events - NfcWorkerEventReadUidNfcB, - NfcWorkerEventReadUidNfcV, - NfcWorkerEventReadUidNfcF, - NfcWorkerEventReadUidNfcA, - NfcWorkerEventReadMfUltralight, - NfcWorkerEventReadMfDesfire, - NfcWorkerEventReadMfClassicDone, - NfcWorkerEventReadMfClassicLoadKeyCache, - NfcWorkerEventReadMfClassicDictAttackRequired, - NfcWorkerEventReadNfcV, - - // Nfc worker common events - NfcWorkerEventSuccess, - NfcWorkerEventFail, - NfcWorkerEventAborted, - NfcWorkerEventCardDetected, - NfcWorkerEventNoCardDetected, - NfcWorkerEventWrongCardDetected, - - // Read Mifare Classic events - NfcWorkerEventNoDictFound, - NfcWorkerEventNewSector, - NfcWorkerEventNewDictKeyBatch, - NfcWorkerEventFoundKeyA, - NfcWorkerEventFoundKeyB, - NfcWorkerEventKeyAttackStart, - NfcWorkerEventKeyAttackStop, - NfcWorkerEventKeyAttackNextSector, - - // Write Mifare Classic events - NfcWorkerEventWrongCard, - - // Detect Reader events - NfcWorkerEventDetectReaderDetected, - NfcWorkerEventDetectReaderLost, - NfcWorkerEventDetectReaderMfkeyCollected, - - // Mifare Ultralight events - NfcWorkerEventMfUltralightPassKey, // NFC worker requesting manual key - NfcWorkerEventMfUltralightPwdAuth, // Reader sent auth command - NfcWorkerEventNfcVPassKey, // NFC worker requesting manual key - NfcWorkerEventNfcVCommandExecuted, - NfcWorkerEventNfcVContentChanged, -} NfcWorkerEvent; - -typedef bool (*NfcWorkerCallback)(NfcWorkerEvent event, void* context); - -NfcWorker* nfc_worker_alloc(); - -NfcWorkerState nfc_worker_get_state(NfcWorker* nfc_worker); - -void nfc_worker_free(NfcWorker* nfc_worker); - -void nfc_worker_start( - NfcWorker* nfc_worker, - NfcWorkerState state, - NfcDeviceData* dev_data, - NfcWorkerCallback callback, - void* context); - -void nfc_worker_stop(NfcWorker* nfc_worker); -void nfc_worker_nfcv_unlock(NfcWorker* nfc_worker); -void nfc_worker_nfcv_emulate(NfcWorker* nfc_worker); -void nfc_worker_nfcv_sniff(NfcWorker* nfc_worker); - -#ifdef __cplusplus -} -#endif \ No newline at end of file diff --git a/lib/nfc/nfc_worker_i.h b/lib/nfc/nfc_worker_i.h deleted file mode 100644 index b678573ec0d..00000000000 --- a/lib/nfc/nfc_worker_i.h +++ /dev/null @@ -1,59 +0,0 @@ -#pragma once - -#include "nfc_worker.h" - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -struct NfcWorker { - FuriThread* thread; - Storage* storage; - Stream* dict_stream; - - NfcDeviceData* dev_data; - - NfcWorkerCallback callback; - void* context; - - NfcWorkerState state; - - ReaderAnalyzer* reader_analyzer; -}; - -void nfc_worker_change_state(NfcWorker* nfc_worker, NfcWorkerState state); - -int32_t nfc_worker_task(void* context); - -void nfc_worker_read(NfcWorker* nfc_worker); - -void nfc_worker_read_type(NfcWorker* nfc_worker); - -void nfc_worker_emulate_uid(NfcWorker* nfc_worker); - -void nfc_worker_emulate_mf_ultralight(NfcWorker* nfc_worker); - -void nfc_worker_emulate_mf_classic(NfcWorker* nfc_worker); - -void nfc_worker_write_mf_classic(NfcWorker* nfc_worker); - -void nfc_worker_update_mf_classic(NfcWorker* nfc_worker); - -void nfc_worker_mf_classic_dict_attack(NfcWorker* nfc_worker); - -void nfc_worker_mf_ultralight_read_auth(NfcWorker* nfc_worker); - -void nfc_worker_mf_ul_auth_attack(NfcWorker* nfc_worker); - -void nfc_worker_emulate_apdu(NfcWorker* nfc_worker); - -void nfc_worker_analyze_reader(NfcWorker* nfc_worker); diff --git a/lib/nfc/parsers/all_in_one.c b/lib/nfc/parsers/all_in_one.c deleted file mode 100644 index edcc0d0c7ca..00000000000 --- a/lib/nfc/parsers/all_in_one.c +++ /dev/null @@ -1,103 +0,0 @@ -#include "nfc_supported_card.h" -#include "all_in_one.h" - -#include -#include - -#include - -#define ALL_IN_ONE_LAYOUT_UNKNOWN 0 -#define ALL_IN_ONE_LAYOUT_A 1 -#define ALL_IN_ONE_LAYOUT_D 2 -#define ALL_IN_ONE_LAYOUT_E2 3 -#define ALL_IN_ONE_LAYOUT_E3 4 -#define ALL_IN_ONE_LAYOUT_E5 5 -#define ALL_IN_ONE_LAYOUT_2 6 - -uint8_t all_in_one_get_layout(NfcDeviceData* dev_data) { - // I absolutely hate what's about to happen here. - - // Switch on the second half of the third byte of page 5 - FURI_LOG_I("all_in_one", "Layout byte: %02x", dev_data->mf_ul_data.data[(4 * 5) + 2]); - FURI_LOG_I( - "all_in_one", "Layout half-byte: %02x", dev_data->mf_ul_data.data[(4 * 5) + 3] & 0x0F); - switch(dev_data->mf_ul_data.data[(4 * 5) + 2] & 0x0F) { - // If it is A, the layout type is a type A layout - case 0x0A: - return ALL_IN_ONE_LAYOUT_A; - case 0x0D: - return ALL_IN_ONE_LAYOUT_D; - case 0x02: - return ALL_IN_ONE_LAYOUT_2; - default: - FURI_LOG_I( - "all_in_one", - "Unknown layout type: %d", - dev_data->mf_ul_data.data[(4 * 5) + 2] & 0x0F); - return ALL_IN_ONE_LAYOUT_UNKNOWN; - } -} - -bool all_in_one_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { - UNUSED(nfc_worker); - // If this is a all_in_one pass, first 2 bytes of page 4 are 0x45 0xD9 - MfUltralightReader reader = {}; - MfUltralightData data = {}; - - if(!mf_ul_read_card(tx_rx, &reader, &data)) { - return false; - } else { - if(data.data[4 * 4] == 0x45 && data.data[4 * 4 + 1] == 0xD9) { - FURI_LOG_I("all_in_one", "Pass verified"); - return true; - } - } - return false; -} - -bool all_in_one_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { - MfUltralightReader reader = {}; - MfUltralightData data = {}; - if(!mf_ul_read_card(tx_rx, &reader, &data)) { - return false; - } else { - memcpy(&nfc_worker->dev_data->mf_ul_data, &data, sizeof(data)); - FURI_LOG_I("all_in_one", "Card read"); - return true; - } -} - -bool all_in_one_parser_parse(NfcDeviceData* dev_data) { - if(dev_data->mf_ul_data.data[4 * 4] != 0x45 || dev_data->mf_ul_data.data[4 * 4 + 1] != 0xD9) { - FURI_LOG_I("all_in_one", "Pass not verified"); - return false; - } - - uint8_t ride_count = 0; - uint32_t serial = 0; - if(all_in_one_get_layout(dev_data) == ALL_IN_ONE_LAYOUT_A) { - // If the layout is A then the ride count is stored in the first byte of page 8 - ride_count = dev_data->mf_ul_data.data[4 * 8]; - } else if(all_in_one_get_layout(dev_data) == ALL_IN_ONE_LAYOUT_D) { - // If the layout is D, the ride count is stored in the second byte of page 9 - ride_count = dev_data->mf_ul_data.data[4 * 9 + 1]; - } else { - FURI_LOG_I("all_in_one", "Unknown layout: %d", all_in_one_get_layout(dev_data)); - ride_count = 137; - } - - // I hate this with a burning passion. - - // The number starts at the second half of the third byte on page 4, and is 32 bits long - // So we get the second half of the third byte, then bytes 4-6, and then the first half of the 7th byte - // B8 17 A2 A4 BD becomes 81 7A 2A 4B - serial = - (dev_data->mf_ul_data.data[4 * 4 + 2] & 0x0F) << 28 | - dev_data->mf_ul_data.data[4 * 4 + 3] << 20 | dev_data->mf_ul_data.data[4 * 4 + 4] << 12 | - dev_data->mf_ul_data.data[4 * 4 + 5] << 4 | (dev_data->mf_ul_data.data[4 * 4 + 6] >> 4); - - // Format string for rides count - furi_string_printf( - dev_data->parsed_data, "\e#All-In-One\nNumber: %lu\nRides left: %u", serial, ride_count); - return true; -} diff --git a/lib/nfc/parsers/all_in_one.h b/lib/nfc/parsers/all_in_one.h deleted file mode 100644 index 9b646d4dc30..00000000000 --- a/lib/nfc/parsers/all_in_one.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once - -#include "nfc_supported_card.h" - -bool all_in_one_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx); - -bool all_in_one_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx); - -bool all_in_one_parser_parse(NfcDeviceData* dev_data); \ No newline at end of file diff --git a/lib/nfc/parsers/nfc_supported_card.c b/lib/nfc/parsers/nfc_supported_card.c deleted file mode 100644 index 153d4d3c511..00000000000 --- a/lib/nfc/parsers/nfc_supported_card.c +++ /dev/null @@ -1,82 +0,0 @@ -#include "nfc_supported_card.h" - -#include "plantain_parser.h" -#include "troika_parser.h" -#include "plantain_4k_parser.h" -#include "troika_4k_parser.h" -#include "two_cities.h" -#include "all_in_one.h" -#include "opal.h" - -NfcSupportedCard nfc_supported_card[NfcSupportedCardTypeEnd] = { - [NfcSupportedCardTypePlantain] = - { - .protocol = NfcDeviceProtocolMifareClassic, - .verify = plantain_parser_verify, - .read = plantain_parser_read, - .parse = plantain_parser_parse, - }, - [NfcSupportedCardTypeTroika] = - { - .protocol = NfcDeviceProtocolMifareClassic, - .verify = troika_parser_verify, - .read = troika_parser_read, - .parse = troika_parser_parse, - }, - [NfcSupportedCardTypePlantain4K] = - { - .protocol = NfcDeviceProtocolMifareClassic, - .verify = plantain_4k_parser_verify, - .read = plantain_4k_parser_read, - .parse = plantain_4k_parser_parse, - }, - [NfcSupportedCardTypeTroika4K] = - { - .protocol = NfcDeviceProtocolMifareClassic, - .verify = troika_4k_parser_verify, - .read = troika_4k_parser_read, - .parse = troika_4k_parser_parse, - }, - [NfcSupportedCardTypeTwoCities] = - { - .protocol = NfcDeviceProtocolMifareClassic, - .verify = two_cities_parser_verify, - .read = two_cities_parser_read, - .parse = two_cities_parser_parse, - }, - [NfcSupportedCardTypeAllInOne] = - { - .protocol = NfcDeviceProtocolMifareUl, - .verify = all_in_one_parser_verify, - .read = all_in_one_parser_read, - .parse = all_in_one_parser_parse, - }, - [NfcSupportedCardTypeOpal] = - { - .protocol = NfcDeviceProtocolMifareDesfire, - .verify = stub_parser_verify_read, - .read = stub_parser_verify_read, - .parse = opal_parser_parse, - }, - -}; - -bool nfc_supported_card_verify_and_parse(NfcDeviceData* dev_data) { - furi_assert(dev_data); - - bool card_parsed = false; - for(size_t i = 0; i < COUNT_OF(nfc_supported_card); i++) { - if(nfc_supported_card[i].parse(dev_data)) { - card_parsed = true; - break; - } - } - - return card_parsed; -} - -bool stub_parser_verify_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { - UNUSED(nfc_worker); - UNUSED(tx_rx); - return false; -} diff --git a/lib/nfc/parsers/nfc_supported_card.h b/lib/nfc/parsers/nfc_supported_card.h deleted file mode 100644 index 2e8c48a87a7..00000000000 --- a/lib/nfc/parsers/nfc_supported_card.h +++ /dev/null @@ -1,47 +0,0 @@ -#pragma once - -#include -#include "../nfc_worker.h" -#include "../nfc_device.h" - -#ifdef __cplusplus -extern "C" { -#endif - -typedef enum { - NfcSupportedCardTypePlantain, - NfcSupportedCardTypeTroika, - NfcSupportedCardTypePlantain4K, - NfcSupportedCardTypeTroika4K, - NfcSupportedCardTypeTwoCities, - NfcSupportedCardTypeAllInOne, - NfcSupportedCardTypeOpal, - - NfcSupportedCardTypeEnd, -} NfcSupportedCardType; - -typedef bool (*NfcSupportedCardVerify)(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx); - -typedef bool (*NfcSupportedCardRead)(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx); - -typedef bool (*NfcSupportedCardParse)(NfcDeviceData* dev_data); - -typedef struct { - NfcProtocol protocol; - NfcSupportedCardVerify verify; - NfcSupportedCardRead read; - NfcSupportedCardParse parse; -} NfcSupportedCard; - -extern NfcSupportedCard nfc_supported_card[NfcSupportedCardTypeEnd]; - -bool nfc_supported_card_verify_and_parse(NfcDeviceData* dev_data); - -// stub_parser_verify_read does nothing, and always reports that it does not -// support the card. This is needed for DESFire card parsers which can't -// provide keys, and only use NfcSupportedCard->parse. -bool stub_parser_verify_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx); - -#ifdef __cplusplus -} -#endif \ No newline at end of file diff --git a/lib/nfc/parsers/opal.c b/lib/nfc/parsers/opal.c deleted file mode 100644 index 2a6b5d1221a..00000000000 --- a/lib/nfc/parsers/opal.c +++ /dev/null @@ -1,204 +0,0 @@ -/* - * opal.c - Parser for Opal card (Sydney, Australia). - * - * Copyright 2023 Michael Farrell - * - * This will only read "standard" MIFARE DESFire-based Opal cards. Free travel - * cards (including School Opal cards, veteran, vision-impaired persons and - * TfNSW employees' cards) and single-trip tickets are MIFARE Ultralight C - * cards and not supported. - * - * Reference: https://github.com/metrodroid/metrodroid/wiki/Opal - * - * Note: The card values are all little-endian (like Flipper), but the above - * reference was originally written based on Java APIs, which are big-endian. - * This implementation presumes a little-endian system. - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#include "nfc_supported_card.h" -#include "opal.h" - -#include -#include -#include - -#include - -static const uint8_t opal_aid[3] = {0x31, 0x45, 0x53}; -static const char* opal_modes[5] = - {"Rail / Metro", "Ferry / Light Rail", "Bus", "Unknown mode", "Manly Ferry"}; -static const char* opal_usages[14] = { - "New / Unused", - "Tap on: new journey", - "Tap on: transfer from same mode", - "Tap on: transfer from other mode", - "", // Manly Ferry: new journey - "", // Manly Ferry: transfer from ferry - "", // Manly Ferry: transfer from other - "Tap off: distance fare", - "Tap off: flat fare", - "Automated tap off: failed to tap off", - "Tap off: end of trip without start", - "Tap off: reversal", - "Tap on: rejected", - "Unknown usage", -}; - -// Opal file 0x7 structure. Assumes a little-endian CPU. -typedef struct __attribute__((__packed__)) { - uint32_t serial : 32; - uint8_t check_digit : 4; - bool blocked : 1; - uint16_t txn_number : 16; - int32_t balance : 21; - uint16_t days : 15; - uint16_t minutes : 11; - uint8_t mode : 3; - uint16_t usage : 4; - bool auto_topup : 1; - uint8_t weekly_journeys : 4; - uint16_t checksum : 16; -} OpalFile; - -static_assert(sizeof(OpalFile) == 16); - -// Converts an Opal timestamp to FuriHalRtcDateTime. -// -// Opal measures days since 1980-01-01 and minutes since midnight, and presumes -// all days are 1440 minutes. -void opal_date_time_to_furi(uint16_t days, uint16_t minutes, FuriHalRtcDateTime* out) { - if(!out) return; - uint16_t diy; - out->year = 1980; - out->month = 1; - // 1980-01-01 is a Tuesday - out->weekday = ((days + 1) % 7) + 1; - out->hour = minutes / 60; - out->minute = minutes % 60; - out->second = 0; - - // What year is it? - for(;;) { - diy = furi_hal_rtc_get_days_per_year(out->year); - if(days < diy) break; - days -= diy; - out->year++; - } - - // 1-index the day of the year - days++; - // What month is it? - bool is_leap = furi_hal_rtc_is_leap_year(out->year); - - for(;;) { - uint8_t dim = furi_hal_rtc_get_days_per_month(is_leap, out->month); - if(days <= dim) break; - days -= dim; - out->month++; - } - - out->day = days; -} - -bool opal_parser_parse(NfcDeviceData* dev_data) { - if(dev_data->protocol != NfcDeviceProtocolMifareDesfire) { - return false; - } - - MifareDesfireApplication* app = mf_df_get_application(&dev_data->mf_df_data, &opal_aid); - if(app == NULL) { - return false; - } - MifareDesfireFile* f = mf_df_get_file(app, 0x07); - if(f == NULL || f->type != MifareDesfireFileTypeStandard || f->settings.data.size != 16 || - !f->contents) { - return false; - } - - OpalFile* o = (OpalFile*)f->contents; - - uint8_t serial2 = o->serial / 10000000; - uint16_t serial3 = (o->serial / 1000) % 10000; - uint16_t serial4 = (o->serial % 1000); - - if(o->check_digit > 9) { - return false; - } - - char* sign = ""; - if(o->balance < 0) { - // Negative balance. Make this a positive value again and record the - // sign separately, because then we can handle balances of -99..-1 - // cents, as the "dollars" division below would result in a positive - // zero value. - o->balance = abs(o->balance); //-V1081 - sign = "-"; - } - uint8_t cents = o->balance % 100; - int32_t dollars = o->balance / 100; - - FuriHalRtcDateTime timestamp; - opal_date_time_to_furi(o->days, o->minutes, ×tamp); - - if(o->mode >= 3) { - // 3..7 are "reserved", but we use 4 to indicate the Manly Ferry. - o->mode = 3; - } - - if(o->usage >= 4 && o->usage <= 6) { - // Usages 4..6 associated with the Manly Ferry, which correspond to - // usages 1..3 for other modes. - o->usage -= 3; - o->mode = 4; - } - - const char* mode_str = (o->mode <= 4 ? opal_modes[o->mode] : opal_modes[3]); //-V547 - const char* usage_str = (o->usage <= 12 ? opal_usages[o->usage] : opal_usages[13]); - - furi_string_printf( - dev_data->parsed_data, - "\e#Opal: $%s%ld.%02hu\n3085 22%02hhu %04hu %03hu%01hhu\n%s, %s\n", - sign, - dollars, - cents, - serial2, - serial3, - serial4, - o->check_digit, - mode_str, - usage_str); - FuriString* timestamp_str = furi_string_alloc(); - locale_format_date(timestamp_str, ×tamp, locale_get_date_format(), "-"); - furi_string_cat(dev_data->parsed_data, timestamp_str); - furi_string_cat_str(dev_data->parsed_data, " at "); - - locale_format_time(timestamp_str, ×tamp, locale_get_time_format(), false); - furi_string_cat(dev_data->parsed_data, timestamp_str); - - furi_string_free(timestamp_str); - furi_string_cat_printf( - dev_data->parsed_data, - "\nWeekly journeys: %hhu, Txn #%hu\n", - o->weekly_journeys, - o->txn_number); - - if(o->auto_topup) { - furi_string_cat_str(dev_data->parsed_data, "Auto-topup enabled\n"); - } - if(o->blocked) { - furi_string_cat_str(dev_data->parsed_data, "Card blocked\n"); - } - return true; -} diff --git a/lib/nfc/parsers/opal.h b/lib/nfc/parsers/opal.h deleted file mode 100644 index 42caf9a1790..00000000000 --- a/lib/nfc/parsers/opal.h +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once - -#include "nfc_supported_card.h" - -bool opal_parser_parse(NfcDeviceData* dev_data); diff --git a/lib/nfc/parsers/plantain_4k_parser.c b/lib/nfc/parsers/plantain_4k_parser.c deleted file mode 100644 index 19da0b5ebb3..00000000000 --- a/lib/nfc/parsers/plantain_4k_parser.c +++ /dev/null @@ -1,124 +0,0 @@ -#include "nfc_supported_card.h" - -#include -#include - -#include - -static const MfClassicAuthContext plantain_keys_4k[] = { - {.sector = 0, .key_a = 0xFFFFFFFFFFFF, .key_b = 0xFFFFFFFFFFFF}, - {.sector = 1, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, - {.sector = 2, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, - {.sector = 3, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, - {.sector = 4, .key_a = 0xe56ac127dd45, .key_b = 0x19fc84a3784b}, - {.sector = 5, .key_a = 0x77dabc9825e1, .key_b = 0x9764fec3154a}, - {.sector = 6, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, - {.sector = 7, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, - {.sector = 8, .key_a = 0x26973ea74321, .key_b = 0xd27058c6e2c7}, - {.sector = 9, .key_a = 0xeb0a8ff88ade, .key_b = 0x578a9ada41e3}, - {.sector = 10, .key_a = 0xea0fd73cb149, .key_b = 0x29c35fa068fb}, - {.sector = 11, .key_a = 0xc76bf71a2509, .key_b = 0x9ba241db3f56}, - {.sector = 12, .key_a = 0xacffffffffff, .key_b = 0x71f3a315ad26}, - {.sector = 13, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, - {.sector = 14, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, - {.sector = 15, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, - {.sector = 16, .key_a = 0x72f96bdd3714, .key_b = 0x462225cd34cf}, - {.sector = 17, .key_a = 0x044ce1872bc3, .key_b = 0x8c90c70cff4a}, - {.sector = 18, .key_a = 0xbc2d1791dec1, .key_b = 0xca96a487de0b}, - {.sector = 19, .key_a = 0x8791b2ccb5c4, .key_b = 0xc956c3b80da3}, - {.sector = 20, .key_a = 0x8e26e45e7d65, .key_b = 0x8e65b3af7d22}, - {.sector = 21, .key_a = 0x0f318130ed18, .key_b = 0x0c420a20e056}, - {.sector = 22, .key_a = 0x045ceca15535, .key_b = 0x31bec3d9e510}, - {.sector = 23, .key_a = 0x9d993c5d4ef4, .key_b = 0x86120e488abf}, - {.sector = 24, .key_a = 0xc65d4eaa645b, .key_b = 0xb69d40d1a439}, - {.sector = 25, .key_a = 0x3a8a139c20b4, .key_b = 0x8818a9c5d406}, - {.sector = 26, .key_a = 0xbaff3053b496, .key_b = 0x4b7cb25354d3}, - {.sector = 27, .key_a = 0x7413b599c4ea, .key_b = 0xb0a2AAF3A1BA}, - {.sector = 28, .key_a = 0x0ce7cd2cc72b, .key_b = 0xfa1fbb3f0f1f}, - {.sector = 29, .key_a = 0x0be5fac8b06a, .key_b = 0x6f95887a4fd3}, - {.sector = 30, .key_a = 0x0eb23cc8110b, .key_b = 0x04dc35277635}, - {.sector = 31, .key_a = 0xbc4580b7f20b, .key_b = 0xd0a4131fb290}, - {.sector = 32, .key_a = 0x7a396f0d633d, .key_b = 0xad2bdc097023}, - {.sector = 33, .key_a = 0xa3faa6daff67, .key_b = 0x7600e889adf9}, - {.sector = 34, .key_a = 0xfd8705e721b0, .key_b = 0x296fc317a513}, - {.sector = 35, .key_a = 0x22052b480d11, .key_b = 0xe19504c39461}, - {.sector = 36, .key_a = 0xa7141147d430, .key_b = 0xff16014fefc7}, - {.sector = 37, .key_a = 0x8a8d88151a00, .key_b = 0x038b5f9b5a2a}, - {.sector = 38, .key_a = 0xb27addfb64b0, .key_b = 0x152fd0c420a7}, - {.sector = 39, .key_a = 0x7259fa0197c6, .key_b = 0x5583698df085}, -}; - -bool plantain_4k_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { - furi_assert(nfc_worker); - UNUSED(nfc_worker); - - if(nfc_worker->dev_data->mf_classic_data.type != MfClassicType4k) { - return false; - } - - uint8_t sector = 8; - uint8_t block = mf_classic_get_sector_trailer_block_num_by_sector(sector); - FURI_LOG_D("Plant4K", "Verifying sector %d", sector); - if(mf_classic_authenticate(tx_rx, block, 0x26973ea74321, MfClassicKeyA)) { - FURI_LOG_D("Plant4K", "Sector %d verified", sector); - return true; - } - return false; -} - -bool plantain_4k_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { - furi_assert(nfc_worker); - - MfClassicReader reader = {}; - FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data; - reader.type = mf_classic_get_classic_type(nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak); - for(size_t i = 0; i < COUNT_OF(plantain_keys_4k); i++) { - mf_classic_reader_add_sector( - &reader, - plantain_keys_4k[i].sector, - plantain_keys_4k[i].key_a, - plantain_keys_4k[i].key_b); - FURI_LOG_T("plant4k", "Added sector %d", plantain_keys_4k[i].sector); - } - for(int i = 0; i < 5; i++) { - if(mf_classic_read_card(tx_rx, &reader, &nfc_worker->dev_data->mf_classic_data) == 40) { - return true; - } - } - return false; -} - -bool plantain_4k_parser_parse(NfcDeviceData* dev_data) { - MfClassicData* data = &dev_data->mf_classic_data; - - // Verify key - MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, 8); - uint64_t key = nfc_util_bytes2num(sec_tr->key_a, 6); - if(key != plantain_keys_4k[8].key_a) return false; - - // Point to block 0 of sector 4, value 0 - uint8_t* temp_ptr = &data->block[4 * 4].value[0]; - // Read first 4 bytes of block 0 of sector 4 from last to first and convert them to uint32_t - // 38 18 00 00 becomes 00 00 18 38, and equals to 6200 decimal - uint32_t balance = - ((temp_ptr[3] << 24) | (temp_ptr[2] << 16) | (temp_ptr[1] << 8) | temp_ptr[0]) / 100; - // Read card number - // Point to block 0 of sector 0, value 0 - temp_ptr = &data->block[0 * 4].value[0]; - // Read first 7 bytes of block 0 of sector 0 from last to first and convert them to uint64_t - // 04 31 16 8A 23 5C 80 becomes 80 5C 23 8A 16 31 04, and equals to 36130104729284868 decimal - uint8_t card_number_arr[7]; - for(size_t i = 0; i < 7; i++) { - card_number_arr[i] = temp_ptr[6 - i]; - } - // Copy card number to uint64_t - uint64_t card_number = 0; - for(size_t i = 0; i < 7; i++) { - card_number = (card_number << 8) | card_number_arr[i]; - } - - furi_string_printf( - dev_data->parsed_data, "\e#Plantain\nN:%llu-\nBalance:%lu\n", card_number, balance); - - return true; -} diff --git a/lib/nfc/parsers/plantain_4k_parser.h b/lib/nfc/parsers/plantain_4k_parser.h deleted file mode 100644 index 29998af15ea..00000000000 --- a/lib/nfc/parsers/plantain_4k_parser.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once - -#include "nfc_supported_card.h" - -bool plantain_4k_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx); - -bool plantain_4k_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx); - -bool plantain_4k_parser_parse(NfcDeviceData* dev_data); diff --git a/lib/nfc/parsers/plantain_parser.c b/lib/nfc/parsers/plantain_parser.c deleted file mode 100644 index 2e4091dda10..00000000000 --- a/lib/nfc/parsers/plantain_parser.c +++ /dev/null @@ -1,97 +0,0 @@ -#include "nfc_supported_card.h" - -#include -#include - -#include - -static const MfClassicAuthContext plantain_keys[] = { - {.sector = 0, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, - {.sector = 1, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, - {.sector = 2, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, - {.sector = 3, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, - {.sector = 4, .key_a = 0xe56ac127dd45, .key_b = 0x19fc84a3784b}, - {.sector = 5, .key_a = 0x77dabc9825e1, .key_b = 0x9764fec3154a}, - {.sector = 6, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, - {.sector = 7, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, - {.sector = 8, .key_a = 0x26973ea74321, .key_b = 0xd27058c6e2c7}, - {.sector = 9, .key_a = 0xeb0a8ff88ade, .key_b = 0x578a9ada41e3}, - {.sector = 10, .key_a = 0xea0fd73cb149, .key_b = 0x29c35fa068fb}, - {.sector = 11, .key_a = 0xc76bf71a2509, .key_b = 0x9ba241db3f56}, - {.sector = 12, .key_a = 0xacffffffffff, .key_b = 0x71f3a315ad26}, - {.sector = 13, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, - {.sector = 14, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, - {.sector = 15, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, -}; - -bool plantain_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { - furi_assert(nfc_worker); - UNUSED(nfc_worker); - if(nfc_worker->dev_data->mf_classic_data.type != MfClassicType1k) { - return false; - } - - uint8_t sector = 8; - uint8_t block = mf_classic_get_sector_trailer_block_num_by_sector(sector); - FURI_LOG_D("Plant", "Verifying sector %d", sector); - if(mf_classic_authenticate(tx_rx, block, 0x26973ea74321, MfClassicKeyA)) { - FURI_LOG_D("Plant", "Sector %d verified", sector); - return true; - } - return false; -} - -bool plantain_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { - furi_assert(nfc_worker); - - MfClassicReader reader = {}; - FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data; - reader.type = mf_classic_get_classic_type(nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak); - for(size_t i = 0; i < COUNT_OF(plantain_keys); i++) { - mf_classic_reader_add_sector( - &reader, plantain_keys[i].sector, plantain_keys[i].key_a, plantain_keys[i].key_b); - } - - return mf_classic_read_card(tx_rx, &reader, &nfc_worker->dev_data->mf_classic_data) == 16; -} - -uint8_t plantain_calculate_luhn(uint64_t number) { - // No. - UNUSED(number); - return 0; -} - -bool plantain_parser_parse(NfcDeviceData* dev_data) { - MfClassicData* data = &dev_data->mf_classic_data; - - // Verify key - MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, 8); - uint64_t key = nfc_util_bytes2num(sec_tr->key_a, 6); - if(key != plantain_keys[8].key_a) return false; - - // Point to block 0 of sector 4, value 0 - uint8_t* temp_ptr = &data->block[4 * 4].value[0]; - // Read first 4 bytes of block 0 of sector 4 from last to first and convert them to uint32_t - // 38 18 00 00 becomes 00 00 18 38, and equals to 6200 decimal - uint32_t balance = - ((temp_ptr[3] << 24) | (temp_ptr[2] << 16) | (temp_ptr[1] << 8) | temp_ptr[0]) / 100; - // Read card number - // Point to block 0 of sector 0, value 0 - temp_ptr = &data->block[0 * 4].value[0]; - // Read first 7 bytes of block 0 of sector 0 from last to first and convert them to uint64_t - // 04 31 16 8A 23 5C 80 becomes 80 5C 23 8A 16 31 04, and equals to 36130104729284868 decimal - uint8_t card_number_arr[7]; - for(size_t i = 0; i < 7; i++) { - card_number_arr[i] = temp_ptr[6 - i]; - } - // Copy card number to uint64_t - uint64_t card_number = 0; - for(size_t i = 0; i < 7; i++) { - card_number = (card_number << 8) | card_number_arr[i]; - } - - furi_string_printf( - dev_data->parsed_data, "\e#Plantain\nN:%llu-\nBalance:%lu\n", card_number, balance); - - return true; -} diff --git a/lib/nfc/parsers/plantain_parser.h b/lib/nfc/parsers/plantain_parser.h deleted file mode 100644 index 1af8c50657a..00000000000 --- a/lib/nfc/parsers/plantain_parser.h +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -#include "nfc_supported_card.h" - -bool plantain_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx); - -bool plantain_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx); - -bool plantain_parser_parse(NfcDeviceData* dev_data); - -uint8_t plantain_calculate_luhn(uint64_t number); diff --git a/lib/nfc/parsers/troika_4k_parser.c b/lib/nfc/parsers/troika_4k_parser.c deleted file mode 100644 index d248feb17c3..00000000000 --- a/lib/nfc/parsers/troika_4k_parser.c +++ /dev/null @@ -1,106 +0,0 @@ -#include "nfc_supported_card.h" - -#include -#include - -static const MfClassicAuthContext troika_4k_keys[] = { - {.sector = 0, .key_a = 0xa0a1a2a3a4a5, .key_b = 0xfbf225dc5d58}, - {.sector = 1, .key_a = 0xa82607b01c0d, .key_b = 0x2910989b6880}, - {.sector = 2, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99}, - {.sector = 3, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99}, - {.sector = 4, .key_a = 0x73068f118c13, .key_b = 0x2b7f3253fac5}, - {.sector = 5, .key_a = 0xFBC2793D540B, .key_b = 0xd3a297dc2698}, - {.sector = 6, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99}, - {.sector = 7, .key_a = 0xae3d65a3dad4, .key_b = 0x0f1c63013dbb}, - {.sector = 8, .key_a = 0xa73f5dc1d333, .key_b = 0xe35173494a81}, - {.sector = 9, .key_a = 0x69a32f1c2f19, .key_b = 0x6b8bd9860763}, - {.sector = 10, .key_a = 0x9becdf3d9273, .key_b = 0xf8493407799d}, - {.sector = 11, .key_a = 0x08b386463229, .key_b = 0x5efbaecef46b}, - {.sector = 12, .key_a = 0xcd4c61c26e3d, .key_b = 0x31c7610de3b0}, - {.sector = 13, .key_a = 0xa82607b01c0d, .key_b = 0x2910989b6880}, - {.sector = 14, .key_a = 0x0e8f64340ba4, .key_b = 0x4acec1205d75}, - {.sector = 15, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99}, - {.sector = 16, .key_a = 0x6b02733bb6ec, .key_b = 0x7038cd25c408}, - {.sector = 17, .key_a = 0x403d706ba880, .key_b = 0xb39d19a280df}, - {.sector = 18, .key_a = 0xc11f4597efb5, .key_b = 0x70d901648cb9}, - {.sector = 19, .key_a = 0x0db520c78c1c, .key_b = 0x73e5b9d9d3a4}, - {.sector = 20, .key_a = 0x3ebce0925b2f, .key_b = 0x372cc880f216}, - {.sector = 21, .key_a = 0x16a27af45407, .key_b = 0x9868925175ba}, - {.sector = 22, .key_a = 0xaba208516740, .key_b = 0xce26ecb95252}, - {.sector = 23, .key_a = 0xCD64E567ABCD, .key_b = 0x8f79c4fd8a01}, - {.sector = 24, .key_a = 0x764cd061f1e6, .key_b = 0xa74332f74994}, - {.sector = 25, .key_a = 0x1cc219e9fec1, .key_b = 0xb90de525ceb6}, - {.sector = 26, .key_a = 0x2fe3cb83ea43, .key_b = 0xfba88f109b32}, - {.sector = 27, .key_a = 0x07894ffec1d6, .key_b = 0xefcb0e689db3}, - {.sector = 28, .key_a = 0x04c297b91308, .key_b = 0xc8454c154cb5}, - {.sector = 29, .key_a = 0x7a38e3511a38, .key_b = 0xab16584c972a}, - {.sector = 30, .key_a = 0x7545df809202, .key_b = 0xecf751084a80}, - {.sector = 31, .key_a = 0x5125974cd391, .key_b = 0xd3eafb5df46d}, - {.sector = 32, .key_a = 0x7a86aa203788, .key_b = 0xe41242278ca2}, - {.sector = 33, .key_a = 0xafcef64c9913, .key_b = 0x9db96dca4324}, - {.sector = 34, .key_a = 0x04eaa462f70b, .key_b = 0xac17b93e2fae}, - {.sector = 35, .key_a = 0xe734c210f27e, .key_b = 0x29ba8c3e9fda}, - {.sector = 36, .key_a = 0xd5524f591eed, .key_b = 0x5daf42861b4d}, - {.sector = 37, .key_a = 0xe4821a377b75, .key_b = 0xe8709e486465}, - {.sector = 38, .key_a = 0x518dc6eea089, .key_b = 0x97c64ac98ca4}, - {.sector = 39, .key_a = 0xbb52f8cce07f, .key_b = 0x6b6119752c70}, -}; - -bool troika_4k_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { - furi_assert(nfc_worker); - - if(nfc_worker->dev_data->mf_classic_data.type != MfClassicType4k) { - return false; - } - - uint8_t sector = 11; - uint8_t block = mf_classic_get_sector_trailer_block_num_by_sector(sector); - FURI_LOG_D("Troika", "Verifying sector %d", sector); - if(mf_classic_authenticate(tx_rx, block, 0x08b386463229, MfClassicKeyA)) { - FURI_LOG_D("Troika", "Sector %d verified", sector); - return true; - } - return false; -} - -bool troika_4k_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { - furi_assert(nfc_worker); - - MfClassicReader reader = {}; - FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data; - reader.type = mf_classic_get_classic_type(nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak); - for(size_t i = 0; i < COUNT_OF(troika_4k_keys); i++) { - mf_classic_reader_add_sector( - &reader, troika_4k_keys[i].sector, troika_4k_keys[i].key_a, troika_4k_keys[i].key_b); - } - - return mf_classic_read_card(tx_rx, &reader, &nfc_worker->dev_data->mf_classic_data) == 40; -} - -bool troika_4k_parser_parse(NfcDeviceData* dev_data) { - MfClassicData* data = &dev_data->mf_classic_data; - - // Verify key - MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, 4); - uint64_t key = nfc_util_bytes2num(sec_tr->key_a, 6); - if(key != troika_4k_keys[4].key_a) return false; - - // Verify card type - if(data->type != MfClassicType4k) return false; - - uint8_t* temp_ptr = &data->block[8 * 4 + 1].value[5]; - uint16_t balance = ((temp_ptr[0] << 8) | temp_ptr[1]) / 25; - temp_ptr = &data->block[8 * 4].value[2]; - uint32_t number = 0; - for(size_t i = 1; i < 5; i++) { - number <<= 8; - number |= temp_ptr[i]; - } - number >>= 4; - number |= (temp_ptr[0] & 0xf) << 28; - - furi_string_printf( - dev_data->parsed_data, "\e#Troika\nNum: %lu\nBalance: %u rur.", number, balance); - - return true; -} diff --git a/lib/nfc/parsers/troika_4k_parser.h b/lib/nfc/parsers/troika_4k_parser.h deleted file mode 100644 index c1d6f01d3aa..00000000000 --- a/lib/nfc/parsers/troika_4k_parser.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once - -#include "nfc_supported_card.h" - -bool troika_4k_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx); - -bool troika_4k_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx); - -bool troika_4k_parser_parse(NfcDeviceData* dev_data); diff --git a/lib/nfc/parsers/troika_parser.c b/lib/nfc/parsers/troika_parser.c deleted file mode 100644 index 6d8ae98f3cb..00000000000 --- a/lib/nfc/parsers/troika_parser.c +++ /dev/null @@ -1,88 +0,0 @@ -#include "nfc_supported_card.h" - -#include -#include - -static const MfClassicAuthContext troika_keys[] = { - {.sector = 0, .key_a = 0xa0a1a2a3a4a5, .key_b = 0xfbf225dc5d58}, - {.sector = 1, .key_a = 0xa82607b01c0d, .key_b = 0x2910989b6880}, - {.sector = 2, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99}, - {.sector = 3, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99}, - {.sector = 4, .key_a = 0x73068f118c13, .key_b = 0x2b7f3253fac5}, - {.sector = 5, .key_a = 0xfbc2793d540b, .key_b = 0xd3a297dc2698}, - {.sector = 6, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99}, - {.sector = 7, .key_a = 0xae3d65a3dad4, .key_b = 0x0f1c63013dba}, - {.sector = 8, .key_a = 0xa73f5dc1d333, .key_b = 0xe35173494a81}, - {.sector = 9, .key_a = 0x69a32f1c2f19, .key_b = 0x6b8bd9860763}, - {.sector = 10, .key_a = 0x9becdf3d9273, .key_b = 0xf8493407799d}, - {.sector = 11, .key_a = 0x08b386463229, .key_b = 0x5efbaecef46b}, - {.sector = 12, .key_a = 0xcd4c61c26e3d, .key_b = 0x31c7610de3b0}, - {.sector = 13, .key_a = 0xa82607b01c0d, .key_b = 0x2910989b6880}, - {.sector = 14, .key_a = 0x0e8f64340ba4, .key_b = 0x4acec1205d75}, - {.sector = 15, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99}, -}; - -bool troika_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { - furi_assert(nfc_worker); - UNUSED(nfc_worker); - if(nfc_worker->dev_data->mf_classic_data.type != MfClassicType1k) { - return false; - } - - uint8_t sector = 11; - uint8_t block = mf_classic_get_sector_trailer_block_num_by_sector(sector); - FURI_LOG_D("Troika", "Verifying sector %d", sector); - if(mf_classic_authenticate(tx_rx, block, 0x08b386463229, MfClassicKeyA)) { - FURI_LOG_D("Troika", "Sector %d verified", sector); - return true; - } - return false; -} - -bool troika_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { - furi_assert(nfc_worker); - - MfClassicReader reader = {}; - FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data; - reader.type = mf_classic_get_classic_type(nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak); - - for(size_t i = 0; i < COUNT_OF(troika_keys); i++) { - mf_classic_reader_add_sector( - &reader, troika_keys[i].sector, troika_keys[i].key_a, troika_keys[i].key_b); - } - - return mf_classic_read_card(tx_rx, &reader, &nfc_worker->dev_data->mf_classic_data) == 16; -} - -bool troika_parser_parse(NfcDeviceData* dev_data) { - MfClassicData* data = &dev_data->mf_classic_data; - bool troika_parsed = false; - - do { - // Verify key - MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, 8); - uint64_t key = nfc_util_bytes2num(sec_tr->key_a, 6); - if(key != troika_keys[8].key_a) break; - - // Verify card type - if(data->type != MfClassicType1k) break; - - // Parse data - uint8_t* temp_ptr = &data->block[8 * 4 + 1].value[5]; - uint16_t balance = ((temp_ptr[0] << 8) | temp_ptr[1]) / 25; - temp_ptr = &data->block[8 * 4].value[2]; - uint32_t number = 0; - for(size_t i = 1; i < 5; i++) { - number <<= 8; - number |= temp_ptr[i]; - } - number >>= 4; - number |= (temp_ptr[0] & 0xf) << 28; - - furi_string_printf( - dev_data->parsed_data, "\e#Troika\nNum: %lu\nBalance: %u rur.", number, balance); - troika_parsed = true; - } while(false); - - return troika_parsed; -} diff --git a/lib/nfc/parsers/troika_parser.h b/lib/nfc/parsers/troika_parser.h deleted file mode 100644 index 2aae48d29a5..00000000000 --- a/lib/nfc/parsers/troika_parser.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once - -#include "nfc_supported_card.h" - -bool troika_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx); - -bool troika_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx); - -bool troika_parser_parse(NfcDeviceData* dev_data); diff --git a/lib/nfc/parsers/two_cities.c b/lib/nfc/parsers/two_cities.c deleted file mode 100644 index d6d4279ddd0..00000000000 --- a/lib/nfc/parsers/two_cities.c +++ /dev/null @@ -1,146 +0,0 @@ -#include "nfc_supported_card.h" -#include "plantain_parser.h" // For plantain-specific stuff - -#include -#include - -#include - -static const MfClassicAuthContext two_cities_keys_4k[] = { - {.sector = 0, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, - {.sector = 1, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, - {.sector = 2, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99}, - {.sector = 3, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99}, - {.sector = 4, .key_a = 0xe56ac127dd45, .key_b = 0x19fc84a3784b}, - {.sector = 5, .key_a = 0x77dabc9825e1, .key_b = 0x9764fec3154a}, - {.sector = 6, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99}, - {.sector = 7, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, - {.sector = 8, .key_a = 0xa73f5dc1d333, .key_b = 0xe35173494a81}, - {.sector = 9, .key_a = 0x69a32f1c2f19, .key_b = 0x6b8bd9860763}, - {.sector = 10, .key_a = 0xea0fd73cb149, .key_b = 0x29c35fa068fb}, - {.sector = 11, .key_a = 0xc76bf71a2509, .key_b = 0x9ba241db3f56}, - {.sector = 12, .key_a = 0xacffffffffff, .key_b = 0x71f3a315ad26}, - {.sector = 13, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, - {.sector = 14, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, - {.sector = 15, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99}, - {.sector = 16, .key_a = 0x72f96bdd3714, .key_b = 0x462225cd34cf}, - {.sector = 17, .key_a = 0x044ce1872bc3, .key_b = 0x8c90c70cff4a}, - {.sector = 18, .key_a = 0xbc2d1791dec1, .key_b = 0xca96a487de0b}, - {.sector = 19, .key_a = 0x8791b2ccb5c4, .key_b = 0xc956c3b80da3}, - {.sector = 20, .key_a = 0x8e26e45e7d65, .key_b = 0x8e65b3af7d22}, - {.sector = 21, .key_a = 0x0f318130ed18, .key_b = 0x0c420a20e056}, - {.sector = 22, .key_a = 0x045ceca15535, .key_b = 0x31bec3d9e510}, - {.sector = 23, .key_a = 0x9d993c5d4ef4, .key_b = 0x86120e488abf}, - {.sector = 24, .key_a = 0xc65d4eaa645b, .key_b = 0xb69d40d1a439}, - {.sector = 25, .key_a = 0x3a8a139c20b4, .key_b = 0x8818a9c5d406}, - {.sector = 26, .key_a = 0xbaff3053b496, .key_b = 0x4b7cb25354d3}, - {.sector = 27, .key_a = 0x7413b599c4ea, .key_b = 0xb0a2AAF3A1BA}, - {.sector = 28, .key_a = 0x0ce7cd2cc72b, .key_b = 0xfa1fbb3f0f1f}, - {.sector = 29, .key_a = 0x0be5fac8b06a, .key_b = 0x6f95887a4fd3}, - {.sector = 30, .key_a = 0x26973ea74321, .key_b = 0xd27058c6e2c7}, - {.sector = 31, .key_a = 0xeb0a8ff88ade, .key_b = 0x578a9ada41e3}, - {.sector = 32, .key_a = 0x7a396f0d633d, .key_b = 0xad2bdc097023}, - {.sector = 33, .key_a = 0xa3faa6daff67, .key_b = 0x7600e889adf9}, - {.sector = 34, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99}, - {.sector = 35, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99}, - {.sector = 36, .key_a = 0xa7141147d430, .key_b = 0xff16014fefc7}, - {.sector = 37, .key_a = 0x8a8d88151a00, .key_b = 0x038b5f9b5a2a}, - {.sector = 38, .key_a = 0xb27addfb64b0, .key_b = 0x152fd0c420a7}, - {.sector = 39, .key_a = 0x7259fa0197c6, .key_b = 0x5583698df085}, -}; - -bool two_cities_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { - furi_assert(nfc_worker); - UNUSED(nfc_worker); - - if(nfc_worker->dev_data->mf_classic_data.type != MfClassicType4k) { - return false; - } - - uint8_t sector = 4; - uint8_t block = mf_classic_get_sector_trailer_block_num_by_sector(sector); - FURI_LOG_D("2cities", "Verifying sector %d", sector); - if(mf_classic_authenticate(tx_rx, block, 0xe56ac127dd45, MfClassicKeyA)) { - FURI_LOG_D("2cities", "Sector %d verified", sector); - return true; - } - return false; -} - -bool two_cities_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { - furi_assert(nfc_worker); - - MfClassicReader reader = {}; - FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data; - reader.type = mf_classic_get_classic_type(nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak); - for(size_t i = 0; i < COUNT_OF(two_cities_keys_4k); i++) { - mf_classic_reader_add_sector( - &reader, - two_cities_keys_4k[i].sector, - two_cities_keys_4k[i].key_a, - two_cities_keys_4k[i].key_b); - FURI_LOG_T("2cities", "Added sector %d", two_cities_keys_4k[i].sector); - } - - return mf_classic_read_card(tx_rx, &reader, &nfc_worker->dev_data->mf_classic_data) == 40; -} - -bool two_cities_parser_parse(NfcDeviceData* dev_data) { - MfClassicData* data = &dev_data->mf_classic_data; - - // Verify key - MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, 4); - uint64_t key = nfc_util_bytes2num(sec_tr->key_a, 6); - if(key != two_cities_keys_4k[4].key_a) return false; - - // ===== - // PLANTAIN - // ===== - - // Point to block 0 of sector 4, value 0 - uint8_t* temp_ptr = &data->block[4 * 4].value[0]; - // Read first 4 bytes of block 0 of sector 4 from last to first and convert them to uint32_t - // 38 18 00 00 becomes 00 00 18 38, and equals to 6200 decimal - uint32_t balance = - ((temp_ptr[3] << 24) | (temp_ptr[2] << 16) | (temp_ptr[1] << 8) | temp_ptr[0]) / 100; - // Read card number - // Point to block 0 of sector 0, value 0 - temp_ptr = &data->block[0 * 4].value[0]; - // Read first 7 bytes of block 0 of sector 0 from last to first and convert them to uint64_t - // 04 31 16 8A 23 5C 80 becomes 80 5C 23 8A 16 31 04, and equals to 36130104729284868 decimal - uint8_t card_number_arr[7]; - for(size_t i = 0; i < 7; i++) { - card_number_arr[i] = temp_ptr[6 - i]; - } - // Copy card number to uint64_t - uint64_t card_number = 0; - for(size_t i = 0; i < 7; i++) { - card_number = (card_number << 8) | card_number_arr[i]; - } - - // ===== - // --PLANTAIN-- - // ===== - // TROIKA - // ===== - - uint8_t* troika_temp_ptr = &data->block[8 * 4 + 1].value[5]; - uint16_t troika_balance = ((troika_temp_ptr[0] << 8) | troika_temp_ptr[1]) / 25; - troika_temp_ptr = &data->block[8 * 4].value[3]; - uint32_t troika_number = 0; - for(size_t i = 0; i < 4; i++) { - troika_number <<= 8; - troika_number |= troika_temp_ptr[i]; - } - troika_number >>= 4; - - furi_string_printf( - dev_data->parsed_data, - "\e#Troika+Plantain\nPN: %llu-\nPB: %lu rur.\nTN: %lu\nTB: %u rur.\n", - card_number, - balance, - troika_number, - troika_balance); - - return true; -} diff --git a/lib/nfc/parsers/two_cities.h b/lib/nfc/parsers/two_cities.h deleted file mode 100644 index e735bea8e1a..00000000000 --- a/lib/nfc/parsers/two_cities.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once - -#include "nfc_supported_card.h" - -bool two_cities_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx); - -bool two_cities_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx); - -bool two_cities_parser_parse(NfcDeviceData* dev_data); diff --git a/lib/nfc/protocols/crypto1.c b/lib/nfc/protocols/crypto1.c deleted file mode 100644 index f59651cf45f..00000000000 --- a/lib/nfc/protocols/crypto1.c +++ /dev/null @@ -1,128 +0,0 @@ -#include "crypto1.h" -#include "nfc_util.h" -#include - -// Algorithm from https://github.com/RfidResearchGroup/proxmark3.git - -#define SWAPENDIAN(x) \ - ((x) = ((x) >> 8 & 0xff00ff) | ((x)&0xff00ff) << 8, (x) = (x) >> 16 | (x) << 16) -#define LF_POLY_ODD (0x29CE5C) -#define LF_POLY_EVEN (0x870804) - -#define BEBIT(x, n) FURI_BIT(x, (n) ^ 24) - -void crypto1_reset(Crypto1* crypto1) { - furi_assert(crypto1); - crypto1->even = 0; - crypto1->odd = 0; -} - -void crypto1_init(Crypto1* crypto1, uint64_t key) { - furi_assert(crypto1); - crypto1->even = 0; - crypto1->odd = 0; - for(int8_t i = 47; i > 0; i -= 2) { - crypto1->odd = crypto1->odd << 1 | FURI_BIT(key, (i - 1) ^ 7); - crypto1->even = crypto1->even << 1 | FURI_BIT(key, i ^ 7); - } -} - -uint32_t crypto1_filter(uint32_t in) { - uint32_t out = 0; - out = 0xf22c0 >> (in & 0xf) & 16; - out |= 0x6c9c0 >> (in >> 4 & 0xf) & 8; - out |= 0x3c8b0 >> (in >> 8 & 0xf) & 4; - out |= 0x1e458 >> (in >> 12 & 0xf) & 2; - out |= 0x0d938 >> (in >> 16 & 0xf) & 1; - return FURI_BIT(0xEC57E80A, out); -} - -uint8_t crypto1_bit(Crypto1* crypto1, uint8_t in, int is_encrypted) { - furi_assert(crypto1); - uint8_t out = crypto1_filter(crypto1->odd); - uint32_t feed = out & (!!is_encrypted); - feed ^= !!in; - feed ^= LF_POLY_ODD & crypto1->odd; - feed ^= LF_POLY_EVEN & crypto1->even; - crypto1->even = crypto1->even << 1 | (nfc_util_even_parity32(feed)); - - FURI_SWAP(crypto1->odd, crypto1->even); - return out; -} - -uint8_t crypto1_byte(Crypto1* crypto1, uint8_t in, int is_encrypted) { - furi_assert(crypto1); - uint8_t out = 0; - for(uint8_t i = 0; i < 8; i++) { - out |= crypto1_bit(crypto1, FURI_BIT(in, i), is_encrypted) << i; - } - return out; -} - -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++) { - out |= crypto1_bit(crypto1, BEBIT(in, i), is_encrypted) << (24 ^ i); - } - return out; -} - -uint32_t prng_successor(uint32_t x, uint32_t n) { - SWAPENDIAN(x); - while(n--) x = x >> 1 | (x >> 16 ^ x >> 18 ^ x >> 19 ^ x >> 21) << 31; - - return SWAPENDIAN(x); -} - -void crypto1_decrypt( - Crypto1* crypto, - uint8_t* encrypted_data, - uint16_t encrypted_data_bits, - uint8_t* decrypted_data) { - furi_assert(crypto); - furi_assert(encrypted_data); - furi_assert(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 crypto1_encrypt( - Crypto1* crypto, - uint8_t* keystream, - uint8_t* plain_data, - uint16_t plain_data_bits, - uint8_t* encrypted_data, - uint8_t* encrypted_parity) { - furi_assert(crypto); - furi_assert(plain_data); - furi_assert(encrypted_data); - furi_assert(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))); - } - } -} diff --git a/lib/nfc/protocols/emv.c b/lib/nfc/protocols/emv.c deleted file mode 100644 index 4c4ac856b1c..00000000000 --- a/lib/nfc/protocols/emv.c +++ /dev/null @@ -1,444 +0,0 @@ -#include "emv.h" - -#include - -#define TAG "Emv" - -const PDOLValue pdol_term_info = {0x9F59, {0xC8, 0x80, 0x00}}; // Terminal transaction information -const PDOLValue pdol_term_type = {0x9F5A, {0x00}}; // Terminal transaction type -const PDOLValue pdol_merchant_type = {0x9F58, {0x01}}; // Merchant type indicator -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 -const PDOLValue pdol_amount = {0x9F03, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // Amount -const PDOLValue pdol_country_code = {0x9F1A, {0x01, 0x24}}; // Terminal country code -const PDOLValue pdol_currency_code = {0x5F2A, {0x01, 0x24}}; // Transaction currency code -const PDOLValue pdol_term_verification = { - 0x95, - {0x00, 0x00, 0x00, 0x00, 0x00}}; // Terminal verification results -const PDOLValue pdol_transaction_date = {0x9A, {0x19, 0x01, 0x01}}; // Transaction date -const PDOLValue pdol_transaction_type = {0x9C, {0x00}}; // Transaction type -const PDOLValue pdol_transaction_cert = {0x98, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}; // Transaction cert -const PDOLValue pdol_unpredict_number = {0x9F37, {0x82, 0x3D, 0xDE, 0x7A}}; // Unpredictable number - -const PDOLValue* const pdol_values[] = { - &pdol_term_info, - &pdol_term_type, - &pdol_merchant_type, - &pdol_term_trans_qualifies, - &pdol_addtnl_term_qualifies, - &pdol_amount_authorise, - &pdol_amount, - &pdol_country_code, - &pdol_currency_code, - &pdol_term_verification, - &pdol_transaction_date, - &pdol_transaction_type, - &pdol_transaction_cert, - &pdol_unpredict_number, -}; - -static const uint8_t select_ppse_ans[] = {0x6F, 0x29, 0x84, 0x0E, 0x32, 0x50, 0x41, 0x59, 0x2E, - 0x53, 0x59, 0x53, 0x2E, 0x44, 0x44, 0x46, 0x30, 0x31, - 0xA5, 0x17, 0xBF, 0x0C, 0x14, 0x61, 0x12, 0x4F, 0x07, - 0xA0, 0x00, 0x00, 0x00, 0x03, 0x10, 0x10, 0x50, 0x04, - 0x56, 0x49, 0x53, 0x41, 0x87, 0x01, 0x01, 0x90, 0x00}; -static const uint8_t select_app_ans[] = {0x6F, 0x20, 0x84, 0x07, 0xA0, 0x00, 0x00, 0x00, 0x03, - 0x10, 0x10, 0xA5, 0x15, 0x50, 0x04, 0x56, 0x49, 0x53, - 0x41, 0x9F, 0x38, 0x0C, 0x9F, 0x66, 0x04, 0x9F, 0x02, - 0x06, 0x9F, 0x37, 0x04, 0x5F, 0x2A, 0x02, 0x90, 0x00}; -static const uint8_t pdol_ans[] = {0x77, 0x40, 0x82, 0x02, 0x20, 0x00, 0x57, 0x13, 0x55, 0x70, - 0x73, 0x83, 0x85, 0x87, 0x73, 0x31, 0xD1, 0x80, 0x22, 0x01, - 0x38, 0x84, 0x77, 0x94, 0x00, 0x00, 0x1F, 0x5F, 0x34, 0x01, - 0x00, 0x9F, 0x10, 0x07, 0x06, 0x01, 0x11, 0x03, 0x80, 0x00, - 0x00, 0x9F, 0x26, 0x08, 0x7A, 0x65, 0x7F, 0xD3, 0x52, 0x96, - 0xC9, 0x85, 0x9F, 0x27, 0x01, 0x00, 0x9F, 0x36, 0x02, 0x06, - 0x0C, 0x9F, 0x6C, 0x02, 0x10, 0x00, 0x90, 0x00}; - -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]); - } - printf("\r\n"); - } -} - -static bool emv_decode_response(uint8_t* buff, uint16_t len, EmvApplication* app) { - uint16_t i = 0; - uint16_t tag = 0, first_byte = 0; - uint16_t tlen = 0; - bool success = false; - - while(i < 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_TRACK_1_EQUIV: { - char track_1_equiv[80]; - memcpy(track_1_equiv, &buff[i], tlen); - track_1_equiv[tlen] = '\0'; - success = true; - FURI_LOG_T(TAG, "found EMV_TAG_TRACK_1_EQUIV %x : %s", tag, track_1_equiv); - break; - } - case EMV_TAG_TRACK_2_EQUIV: { - // 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; - app->exp_year = (buff[i + x + 1] << 4) | (buff[i + x + 2] >> 4); - app->exp_month = (buff[i + x + 2] << 4) | (buff[i + x + 3] >> 4); - break; - } - } - - // Convert 4-bit to ASCII representation - char track_2_equiv[41]; - uint8_t track_2_equiv_len = 0; - for(int x = 0; x < tlen; x++) { - char top = (buff[i + x] >> 4) + '0'; - char bottom = (buff[i + x] & 0x0F) + '0'; - track_2_equiv[x * 2] = top; - track_2_equiv_len++; - if(top == '?') break; - track_2_equiv[x * 2 + 1] = bottom; - track_2_equiv_len++; - if(bottom == '?') break; - } - track_2_equiv[track_2_equiv_len] = '\0'; - success = true; - FURI_LOG_T(TAG, "found EMV_TAG_TRACK_2_EQUIV %x : %s", tag, track_2_equiv); - 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 success; -} - -static bool emv_select_ppse(FuriHalNfcTxRxContext* tx_rx, EmvApplication* app) { - bool app_aid_found = false; - const uint8_t emv_select_ppse_cmd[] = { - 0x00, 0xA4, // SELECT ppse - 0x04, 0x00, // P1:By name, P2: empty - 0x0e, // Lc: Data length - 0x32, 0x50, 0x41, 0x59, 0x2e, 0x53, 0x59, // Data string: - 0x53, 0x2e, 0x44, 0x44, 0x46, 0x30, 0x31, // 2PAY.SYS.DDF01 (PPSE) - 0x00 // Le - }; - - memcpy(tx_rx->tx_data, emv_select_ppse_cmd, sizeof(emv_select_ppse_cmd)); - tx_rx->tx_bits = sizeof(emv_select_ppse_cmd) * 8; - tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; - - 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_response(tx_rx->rx_data, tx_rx->rx_bits / 8, app)) { - app_aid_found = true; - } else { - FURI_LOG_E(TAG, "Failed to parse application"); - } - } else { - FURI_LOG_E(TAG, "Failed select PPSE"); - } - - return app_aid_found; -} - -static bool emv_select_app(FuriHalNfcTxRxContext* tx_rx, EmvApplication* app) { - app->app_started = false; - const uint8_t emv_select_header[] = { - 0x00, - 0xA4, // SELECT application - 0x04, - 0x00 // P1:By name, P2:First or only occurence - }; - uint16_t size = sizeof(emv_select_header); - - // Copy header - memcpy(tx_rx->tx_data, emv_select_header, size); - // Copy AID - tx_rx->tx_data[size++] = app->aid_len; - memcpy(&tx_rx->tx_data[size], app->aid, app->aid_len); - size += app->aid_len; - tx_rx->tx_data[size++] = 0x00; - tx_rx->tx_bits = size * 8; - tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; - - 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_response(tx_rx->rx_data, tx_rx->rx_bits / 8, app)) { - app->app_started = true; - } else { - FURI_LOG_E(TAG, "Failed to read PAN or PDOL"); - } - } else { - FURI_LOG_E(TAG, "Failed to start application"); - } - - return app->app_started; -} - -static uint16_t emv_prepare_pdol(APDU* dest, APDU* src) { - bool tag_found; - for(uint16_t i = 0; i < src->size; i++) { - tag_found = false; - for(uint8_t j = 0; j < sizeof(pdol_values) / sizeof(PDOLValue*); j++) { - if(src->data[i] == pdol_values[j]->tag) { - // Found tag with 1 byte length - uint8_t len = src->data[++i]; - memcpy(dest->data + dest->size, pdol_values[j]->data, len); - dest->size += len; - tag_found = true; - break; - } else if(((src->data[i] << 8) | src->data[i + 1]) == pdol_values[j]->tag) { - // Found tag with 2 byte length - i += 2; - uint8_t len = src->data[i]; - memcpy(dest->data + dest->size, pdol_values[j]->data, len); - dest->size += len; - tag_found = true; - break; - } - } - if(!tag_found) { - // Unknown tag, fill zeros - i += 2; - uint8_t len = src->data[i]; - memset(dest->data + dest->size, 0, len); - dest->size += len; - } - } - return dest->size; -} - -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}; - uint16_t size = sizeof(emv_gpo_header); - - // Copy header - memcpy(tx_rx->tx_data, emv_gpo_header, size); - APDU pdol_data = {0, {0}}; - // Prepare and copy pdol parameters - emv_prepare_pdol(&pdol_data, &app->pdol); - tx_rx->tx_data[size++] = 0x02 + pdol_data.size; - tx_rx->tx_data[size++] = 0x83; - tx_rx->tx_data[size++] = pdol_data.size; - memcpy(tx_rx->tx_data + size, pdol_data.data, pdol_data.size); - size += pdol_data.size; - tx_rx->tx_data[size++] = 0; - tx_rx->tx_bits = size * 8; - tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; - - 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_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"); - } - - return card_num_read; -} - -static bool emv_read_sfi_record( - FuriHalNfcTxRxContext* tx_rx, - EmvApplication* app, - uint8_t sfi, - uint8_t record_num) { - bool card_num_read = false; - uint8_t sfi_param = (sfi << 3) | (1 << 2); - uint8_t emv_sfi_header[] = { - 0x00, - 0xB2, // READ RECORD - record_num, // P1:record_number - sfi_param, // P2:SFI - 0x00 // Le - }; - - memcpy(tx_rx->tx_data, emv_sfi_header, sizeof(emv_sfi_header)); - tx_rx->tx_bits = sizeof(emv_sfi_header) * 8; - tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; - - if(furi_hal_nfc_tx_rx(tx_rx, 300)) { - emv_trace(tx_rx, "SFI record:"); - if(emv_decode_response(tx_rx->rx_data, tx_rx->rx_bits / 8, app)) { - card_num_read = true; - } - } else { - FURI_LOG_E(TAG, "Failed to read SFI record %d", record_num); - } - - return card_num_read; -} - -static bool emv_read_files(FuriHalNfcTxRxContext* tx_rx, EmvApplication* app) { - bool card_num_read = false; - - if(app->afl.size == 0) { - return false; - } - - FURI_LOG_D(TAG, "Search PAN in SFI"); - // Iterate through all files - for(size_t i = 0; i < app->afl.size; i += 4) { - uint8_t sfi = app->afl.data[i] >> 3; - uint8_t record_start = app->afl.data[i + 1]; - uint8_t record_end = app->afl.data[i + 2]; - // Iterate through all records in file - for(uint8_t record = record_start; record <= record_end; ++record) { - card_num_read |= emv_read_sfi_record(tx_rx, app, sfi, record); - } - } - - return card_num_read; -} - -bool emv_read_bank_card(FuriHalNfcTxRxContext* tx_rx, EmvApplication* emv_app) { - furi_assert(tx_rx); - furi_assert(emv_app); - bool card_num_read = false; - memset(emv_app, 0, sizeof(EmvApplication)); - - do { - if(!emv_select_ppse(tx_rx, emv_app)) break; - if(!emv_select_app(tx_rx, emv_app)) break; - if(emv_get_processing_options(tx_rx, emv_app)) { - card_num_read = true; - } else { - card_num_read = emv_read_files(tx_rx, emv_app); - } - } while(false); - - return card_num_read; -} - -bool emv_card_emulation(FuriHalNfcTxRxContext* tx_rx) { - furi_assert(tx_rx); - bool emulation_complete = false; - tx_rx->tx_bits = 0; - tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; - - do { - FURI_LOG_D(TAG, "Read select PPSE command"); - if(!furi_hal_nfc_tx_rx(tx_rx, 300)) break; - - memcpy(tx_rx->tx_data, select_ppse_ans, sizeof(select_ppse_ans)); - tx_rx->tx_bits = sizeof(select_ppse_ans) * 8; - tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; - FURI_LOG_D(TAG, "Send select PPSE answer and read select App command"); - if(!furi_hal_nfc_tx_rx(tx_rx, 300)) break; - - memcpy(tx_rx->tx_data, select_app_ans, sizeof(select_app_ans)); - tx_rx->tx_bits = sizeof(select_app_ans) * 8; - tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; - FURI_LOG_D(TAG, "Send select App answer and read get PDOL command"); - if(!furi_hal_nfc_tx_rx(tx_rx, 300)) break; - - memcpy(tx_rx->tx_data, pdol_ans, sizeof(pdol_ans)); - tx_rx->tx_bits = sizeof(pdol_ans) * 8; - tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; - FURI_LOG_D(TAG, "Send get PDOL answer"); - if(!furi_hal_nfc_tx_rx(tx_rx, 300)) break; - - emulation_complete = true; - } while(false); - - return emulation_complete; -} diff --git a/lib/nfc/protocols/emv.h b/lib/nfc/protocols/emv.h deleted file mode 100644 index c5b089fdffc..00000000000 --- a/lib/nfc/protocols/emv.h +++ /dev/null @@ -1,80 +0,0 @@ -#pragma once - -#include - -#define MAX_APDU_LEN 255 - -#define EMV_TAG_APP_TEMPLATE 0x61 -#define EMV_TAG_AID 0x4F -#define EMV_TAG_PRIORITY 0x87 -#define EMV_TAG_PDOL 0x9F38 -#define EMV_TAG_CARD_NAME 0x50 -#define EMV_TAG_FCI 0xBF0C -#define EMV_TAG_LOG_CTRL 0x9F4D -#define EMV_TAG_TRACK_1_EQUIV 0x56 -#define EMV_TAG_TRACK_2_EQUIV 0x57 -#define EMV_TAG_PAN 0x5A -#define EMV_TAG_AFL 0x94 -#define EMV_TAG_EXP_DATE 0x5F24 -#define EMV_TAG_COUNTRY_CODE 0x5F28 -#define EMV_TAG_CURRENCY_CODE 0x9F42 -#define EMV_TAG_CARDHOLDER_NAME 0x5F20 - -typedef struct { - char name[32]; - uint8_t aid[16]; - uint16_t aid_len; - uint8_t number[10]; - uint8_t number_len; - uint8_t exp_mon; - uint8_t exp_year; - uint16_t country_code; - uint16_t currency_code; -} EmvData; - -typedef struct { - uint16_t tag; - uint8_t data[]; -} PDOLValue; - -typedef struct { - uint8_t size; - uint8_t data[MAX_APDU_LEN]; -} APDU; - -typedef struct { - uint8_t priority; - uint8_t aid[16]; - uint8_t aid_len; - bool app_started; - char name[32]; - bool name_found; - uint8_t card_number[10]; - uint8_t card_number_len; - uint8_t exp_month; - uint8_t exp_year; - uint16_t country_code; - uint16_t currency_code; - APDU pdol; - APDU afl; -} EmvApplication; - -/** Read bank card data - * @note Search EMV Application, start it, try to read AID, PAN, card name, - * expiration date, currency and country codes - * - * @param tx_rx FuriHalNfcTxRxContext instance - * @param emv_app EmvApplication instance - * - * @return true on success - */ -bool emv_read_bank_card(FuriHalNfcTxRxContext* tx_rx, EmvApplication* emv_app); - -/** Emulate bank card - * @note Answer to application selection and PDOL - * - * @param tx_rx FuriHalNfcTxRxContext instance - * - * @return true on success - */ -bool emv_card_emulation(FuriHalNfcTxRxContext* tx_rx); diff --git a/lib/nfc/protocols/felica/felica.c b/lib/nfc/protocols/felica/felica.c new file mode 100644 index 00000000000..4b7c91fb64e --- /dev/null +++ b/lib/nfc/protocols/felica/felica.c @@ -0,0 +1,147 @@ +#include "felica.h" + +#include + +#include + +#define FELICA_PROTOCOL_NAME "FeliCa" +#define FELICA_DEVICE_NAME "FeliCa" + +#define FELICA_DATA_FORMAT_VERSION "Data format version" +#define FELICA_MANUFACTURE_ID "Manufacture id" +#define FELICA_MANUFACTURE_PARAMETER "Manufacture parameter" + +static const uint32_t felica_data_format_version = 1; + +const NfcDeviceBase nfc_device_felica = { + .protocol_name = FELICA_PROTOCOL_NAME, + .alloc = (NfcDeviceAlloc)felica_alloc, + .free = (NfcDeviceFree)felica_free, + .reset = (NfcDeviceReset)felica_reset, + .copy = (NfcDeviceCopy)felica_copy, + .verify = (NfcDeviceVerify)felica_verify, + .load = (NfcDeviceLoad)felica_load, + .save = (NfcDeviceSave)felica_save, + .is_equal = (NfcDeviceEqual)felica_is_equal, + .get_name = (NfcDeviceGetName)felica_get_device_name, + .get_uid = (NfcDeviceGetUid)felica_get_uid, + .set_uid = (NfcDeviceSetUid)felica_set_uid, + .get_base_data = (NfcDeviceGetBaseData)felica_get_base_data, +}; + +FelicaData* felica_alloc() { + FelicaData* data = malloc(sizeof(FelicaData)); + return data; +} + +void felica_free(FelicaData* data) { + furi_assert(data); + + free(data); +} + +void felica_reset(FelicaData* data) { + memset(data, 0, sizeof(FelicaData)); +} + +void felica_copy(FelicaData* data, const FelicaData* other) { + furi_assert(data); + furi_assert(other); + + *data = *other; +} + +bool felica_verify(FelicaData* data, const FuriString* device_type) { + UNUSED(data); + UNUSED(device_type); + + return false; +} + +bool felica_load(FelicaData* data, FlipperFormat* ff, uint32_t version) { + furi_assert(data); + + bool parsed = false; + + do { + if(version < NFC_UNIFIED_FORMAT_VERSION) break; + + uint32_t data_format_version = 0; + if(!flipper_format_read_uint32(ff, FELICA_DATA_FORMAT_VERSION, &data_format_version, 1)) + break; + if(data_format_version != felica_data_format_version) break; + if(!flipper_format_read_hex(ff, FELICA_MANUFACTURE_ID, data->idm.data, FELICA_IDM_SIZE)) + break; + if(!flipper_format_read_hex( + ff, FELICA_MANUFACTURE_PARAMETER, data->pmm.data, FELICA_PMM_SIZE)) + break; + + parsed = true; + } while(false); + + return parsed; +} + +bool felica_save(const FelicaData* data, FlipperFormat* ff) { + furi_assert(data); + + bool saved = false; + + do { + if(!flipper_format_write_comment_cstr(ff, FELICA_PROTOCOL_NAME " specific data")) break; + if(!flipper_format_write_uint32( + ff, FELICA_DATA_FORMAT_VERSION, &felica_data_format_version, 1)) + break; + if(!flipper_format_write_hex(ff, FELICA_MANUFACTURE_ID, data->idm.data, FELICA_IDM_SIZE)) + break; + if(!flipper_format_write_hex( + ff, FELICA_MANUFACTURE_PARAMETER, data->pmm.data, FELICA_PMM_SIZE)) + break; + + saved = true; + } while(false); + + return saved; +} + +bool felica_is_equal(const FelicaData* data, const FelicaData* other) { + furi_assert(data); + furi_assert(other); + + return memcmp(data, other, sizeof(FelicaData)) == 0; +} + +const char* felica_get_device_name(const FelicaData* data, NfcDeviceNameType name_type) { + UNUSED(data); + UNUSED(name_type); + + return FELICA_DEVICE_NAME; +} + +const uint8_t* felica_get_uid(const FelicaData* data, size_t* uid_len) { + furi_assert(data); + + // Consider Manufacturer ID as UID + if(uid_len) { + *uid_len = FELICA_IDM_SIZE; + } + + return data->idm.data; +} + +bool felica_set_uid(FelicaData* data, const uint8_t* uid, size_t uid_len) { + furi_assert(data); + + // Consider Manufacturer ID as UID + const bool uid_valid = uid_len == FELICA_IDM_SIZE; + if(uid_valid) { + memcpy(data->idm.data, uid, uid_len); + } + + return uid_valid; +} + +FelicaData* felica_get_base_data(const FelicaData* data) { + UNUSED(data); + furi_crash("No base data"); +} diff --git a/lib/nfc/protocols/felica/felica.h b/lib/nfc/protocols/felica/felica.h new file mode 100644 index 00000000000..da9d2294ee8 --- /dev/null +++ b/lib/nfc/protocols/felica/felica.h @@ -0,0 +1,77 @@ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define FELICA_IDM_SIZE (8U) +#define FELICA_PMM_SIZE (8U) + +#define FELICA_GUARD_TIME_US (20000U) +#define FELICA_FDT_POLL_FC (10000U) +#define FELICA_POLL_POLL_MIN_US (1280U) + +#define FELICA_SYSTEM_CODE_CODE (0xFFFFU) +#define FELICA_TIME_SLOT_1 (0x00U) +#define FELICA_TIME_SLOT_2 (0x01U) +#define FELICA_TIME_SLOT_4 (0x03U) +#define FELICA_TIME_SLOT_8 (0x07U) +#define FELICA_TIME_SLOT_16 (0x0FU) + +typedef enum { + FelicaErrorNone, + FelicaErrorNotPresent, + FelicaErrorColResFailed, + FelicaErrorBufferOverflow, + FelicaErrorCommunication, + FelicaErrorFieldOff, + FelicaErrorWrongCrc, + FelicaErrorProtocol, + FelicaErrorTimeout, +} FelicaError; + +typedef struct { + uint8_t data[FELICA_IDM_SIZE]; +} FelicaIDm; + +typedef struct { + uint8_t data[FELICA_PMM_SIZE]; +} FelicaPMm; + +typedef struct { + FelicaIDm idm; + FelicaPMm pmm; +} FelicaData; + +extern const NfcDeviceBase nfc_device_felica; + +FelicaData* felica_alloc(); + +void felica_free(FelicaData* data); + +void felica_reset(FelicaData* data); + +void felica_copy(FelicaData* data, const FelicaData* other); + +bool felica_verify(FelicaData* data, const FuriString* device_type); + +bool felica_load(FelicaData* data, FlipperFormat* ff, uint32_t version); + +bool felica_save(const FelicaData* data, FlipperFormat* ff); + +bool felica_is_equal(const FelicaData* data, const FelicaData* other); + +const char* felica_get_device_name(const FelicaData* data, NfcDeviceNameType name_type); + +const uint8_t* felica_get_uid(const FelicaData* data, size_t* uid_len); + +bool felica_set_uid(FelicaData* data, const uint8_t* uid, size_t uid_len); + +FelicaData* felica_get_base_data(const FelicaData* data); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/felica/felica_poller.c b/lib/nfc/protocols/felica/felica_poller.c new file mode 100644 index 00000000000..3fc2affedc3 --- /dev/null +++ b/lib/nfc/protocols/felica/felica_poller.c @@ -0,0 +1,117 @@ +#include "felica_poller_i.h" + +#include + +#include + +const FelicaData* felica_poller_get_data(FelicaPoller* instance) { + furi_assert(instance); + furi_assert(instance->data); + + return instance->data; +} + +static FelicaPoller* felica_poller_alloc(Nfc* nfc) { + furi_assert(nfc); + + FelicaPoller* instance = malloc(sizeof(FelicaPoller)); + instance->nfc = nfc; + instance->tx_buffer = bit_buffer_alloc(FELICA_POLLER_MAX_BUFFER_SIZE); + instance->rx_buffer = bit_buffer_alloc(FELICA_POLLER_MAX_BUFFER_SIZE); + + nfc_config(instance->nfc, NfcModePoller, NfcTechFelica); + nfc_set_guard_time_us(instance->nfc, FELICA_GUARD_TIME_US); + nfc_set_fdt_poll_fc(instance->nfc, FELICA_FDT_POLL_FC); + nfc_set_fdt_poll_poll_us(instance->nfc, FELICA_POLL_POLL_MIN_US); + instance->data = felica_alloc(); + + instance->felica_event.data = &instance->felica_event_data; + instance->general_event.protocol = NfcProtocolFelica; + instance->general_event.event_data = &instance->felica_event; + instance->general_event.instance = instance; + + return instance; +} + +static void felica_poller_free(FelicaPoller* instance) { + furi_assert(instance); + + furi_assert(instance->tx_buffer); + furi_assert(instance->rx_buffer); + furi_assert(instance->data); + + bit_buffer_free(instance->tx_buffer); + bit_buffer_free(instance->rx_buffer); + felica_free(instance->data); + free(instance); +} + +static void + felica_poller_set_callback(FelicaPoller* instance, NfcGenericCallback callback, void* context) { + furi_assert(instance); + furi_assert(callback); + + instance->callback = callback; + instance->context = context; +} + +static NfcCommand felica_poller_run(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.protocol == NfcProtocolInvalid); + furi_assert(event.event_data); + + FelicaPoller* instance = context; + NfcEvent* nfc_event = event.event_data; + NfcCommand command = NfcCommandContinue; + + if(nfc_event->type == NfcEventTypePollerReady) { + if(instance->state != FelicaPollerStateActivated) { + FelicaError error = felica_poller_async_activate(instance, instance->data); + if(error == FelicaErrorNone) { + instance->felica_event.type = FelicaPollerEventTypeReady; + instance->felica_event_data.error = error; + command = instance->callback(instance->general_event, instance->context); + } else { + instance->felica_event.type = FelicaPollerEventTypeError; + instance->felica_event_data.error = error; + command = instance->callback(instance->general_event, instance->context); + // Add delay to switch context + furi_delay_ms(100); + } + } else { + instance->felica_event.type = FelicaPollerEventTypeReady; + instance->felica_event_data.error = FelicaErrorNone; + command = instance->callback(instance->general_event, instance->context); + } + } + + return command; +} + +static bool felica_poller_detect(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.event_data); + furi_assert(event.instance); + furi_assert(event.protocol == NfcProtocolInvalid); + + bool protocol_detected = false; + FelicaPoller* instance = context; + NfcEvent* nfc_event = event.event_data; + furi_assert(instance->state == FelicaPollerStateIdle); + + if(nfc_event->type == NfcEventTypePollerReady) { + FelicaError error = felica_poller_async_activate(instance, instance->data); + protocol_detected = (error == FelicaErrorNone); + } + + return protocol_detected; +} + +const NfcPollerBase nfc_poller_felica = { + .alloc = (NfcPollerAlloc)felica_poller_alloc, + .free = (NfcPollerFree)felica_poller_free, + .set_callback = (NfcPollerSetCallback)felica_poller_set_callback, + .run = (NfcPollerRun)felica_poller_run, + .detect = (NfcPollerDetect)felica_poller_detect, + .get_data = (NfcPollerGetData)felica_poller_get_data, +}; diff --git a/lib/nfc/protocols/felica/felica_poller.h b/lib/nfc/protocols/felica/felica_poller.h new file mode 100644 index 00000000000..7d0c9525e77 --- /dev/null +++ b/lib/nfc/protocols/felica/felica_poller.h @@ -0,0 +1,30 @@ +#pragma once + +#include "felica.h" +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct FelicaPoller FelicaPoller; + +typedef enum { + FelicaPollerEventTypeError, + FelicaPollerEventTypeReady, +} FelicaPollerEventType; + +typedef struct { + FelicaError error; +} FelicaPollerEventData; + +typedef struct { + FelicaPollerEventType type; + FelicaPollerEventData* data; +} FelicaPollerEvent; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/felica/felica_poller_defs.h b/lib/nfc/protocols/felica/felica_poller_defs.h new file mode 100644 index 00000000000..fc99dc75219 --- /dev/null +++ b/lib/nfc/protocols/felica/felica_poller_defs.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern const NfcPollerBase nfc_poller_felica; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/felica/felica_poller_i.c b/lib/nfc/protocols/felica/felica_poller_i.c new file mode 100644 index 00000000000..d8015fdfade --- /dev/null +++ b/lib/nfc/protocols/felica/felica_poller_i.c @@ -0,0 +1,128 @@ +#include "felica_poller_i.h" + +#include + +#define TAG "FelicaPoller" + +static FelicaError felica_poller_process_error(NfcError error) { + switch(error) { + case NfcErrorNone: + return FelicaErrorNone; + case NfcErrorTimeout: + return FelicaErrorTimeout; + default: + return FelicaErrorNotPresent; + } +} + +static FelicaError felica_poller_frame_exchange( + FelicaPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt) { + furi_assert(instance); + + const size_t tx_bytes = bit_buffer_get_size_bytes(tx_buffer); + furi_assert(tx_bytes <= bit_buffer_get_capacity_bytes(instance->tx_buffer) - FELICA_CRC_SIZE); + + felica_crc_append(instance->tx_buffer); + + FelicaError ret = FelicaErrorNone; + + do { + NfcError error = + nfc_poller_trx(instance->nfc, instance->tx_buffer, instance->rx_buffer, fwt); + if(error != NfcErrorNone) { + ret = felica_poller_process_error(error); + break; + } + + bit_buffer_copy(rx_buffer, instance->rx_buffer); + if(!felica_crc_check(instance->rx_buffer)) { + ret = FelicaErrorWrongCrc; + break; + } + + felica_crc_trim(rx_buffer); + } while(false); + + return ret; +} + +FelicaError felica_poller_async_polling( + FelicaPoller* instance, + const FelicaPollerPollingCommand* cmd, + FelicaPollerPollingResponse* resp) { + furi_assert(instance); + furi_assert(cmd); + furi_assert(resp); + + FelicaError error = FelicaErrorNone; + + do { + bit_buffer_set_size_bytes(instance->tx_buffer, 2); + // Set frame len + bit_buffer_set_byte( + instance->tx_buffer, 0, sizeof(FelicaPollerPollingCommand) + FELICA_CRC_SIZE); + // Set command code + bit_buffer_set_byte(instance->tx_buffer, 1, FELICA_POLLER_CMD_POLLING_REQ_CODE); + // Set other data + bit_buffer_append_bytes( + instance->tx_buffer, (uint8_t*)cmd, sizeof(FelicaPollerPollingCommand)); + + error = felica_poller_frame_exchange( + instance, instance->tx_buffer, instance->rx_buffer, FELICA_POLLER_POLLING_FWT); + + if(error != FelicaErrorNone) break; + if(bit_buffer_get_byte(instance->rx_buffer, 1) != FELICA_POLLER_CMD_POLLING_RESP_CODE) { + error = FelicaErrorProtocol; + break; + } + if(bit_buffer_get_size_bytes(instance->rx_buffer) < + sizeof(FelicaIDm) + sizeof(FelicaPMm) + 1) { + error = FelicaErrorProtocol; + break; + } + + bit_buffer_write_bytes_mid(instance->rx_buffer, resp->idm.data, 2, sizeof(FelicaIDm)); + bit_buffer_write_bytes_mid( + instance->rx_buffer, resp->pmm.data, sizeof(FelicaIDm) + 2, sizeof(FelicaPMm)); + + } while(false); + + return error; +} + +FelicaError felica_poller_async_activate(FelicaPoller* instance, FelicaData* data) { + furi_assert(instance); + + felica_reset(data); + + FelicaError ret; + + do { + bit_buffer_reset(instance->tx_buffer); + bit_buffer_reset(instance->rx_buffer); + + // Send Polling command + const FelicaPollerPollingCommand polling_cmd = { + .system_code = FELICA_SYSTEM_CODE_CODE, + .request_code = 0, + .time_slot = FELICA_TIME_SLOT_1, + }; + FelicaPollerPollingResponse polling_resp = {}; + + ret = felica_poller_async_polling(instance, &polling_cmd, &polling_resp); + + if(ret != FelicaErrorNone) { + FURI_LOG_T(TAG, "Activation failed error: %d", ret); + break; + } + + data->idm = polling_resp.idm; + data->pmm = polling_resp.pmm; + instance->state = FelicaPollerStateActivated; + } while(false); + + return ret; +} diff --git a/lib/nfc/protocols/felica/felica_poller_i.h b/lib/nfc/protocols/felica/felica_poller_i.h new file mode 100644 index 00000000000..e12f0147269 --- /dev/null +++ b/lib/nfc/protocols/felica/felica_poller_i.h @@ -0,0 +1,60 @@ +#pragma once + +#include "felica_poller.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define FELICA_POLLER_MAX_BUFFER_SIZE (256U) + +#define FELICA_POLLER_POLLING_FWT (200000U) + +#define FELICA_POLLER_CMD_POLLING_REQ_CODE (0x00U) +#define FELICA_POLLER_CMD_POLLING_RESP_CODE (0x01U) + +typedef enum { + FelicaPollerStateIdle, + FelicaPollerStateActivated, +} FelicaPollerState; + +struct FelicaPoller { + Nfc* nfc; + FelicaPollerState state; + FelicaData* data; + BitBuffer* tx_buffer; + BitBuffer* rx_buffer; + + NfcGenericEvent general_event; + FelicaPollerEvent felica_event; + FelicaPollerEventData felica_event_data; + NfcGenericCallback callback; + void* context; +}; + +typedef struct { + uint16_t system_code; + uint8_t request_code; + uint8_t time_slot; +} FelicaPollerPollingCommand; + +typedef struct { + FelicaIDm idm; + FelicaPMm pmm; + uint8_t request_data[2]; +} FelicaPollerPollingResponse; + +const FelicaData* felica_poller_get_data(FelicaPoller* instance); + +FelicaError felica_poller_async_polling( + FelicaPoller* instance, + const FelicaPollerPollingCommand* cmd, + FelicaPollerPollingResponse* resp); + +FelicaError felica_poller_async_activate(FelicaPoller* instance, FelicaData* data); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/iso14443_3a/iso14443_3a.c b/lib/nfc/protocols/iso14443_3a/iso14443_3a.c new file mode 100644 index 00000000000..96d0b39537e --- /dev/null +++ b/lib/nfc/protocols/iso14443_3a/iso14443_3a.c @@ -0,0 +1,186 @@ +#include "iso14443_3a.h" + +#include +#include + +#define ISO14443A_ATS_BIT (1U << 5) + +#define ISO14443_3A_PROTOCOL_NAME_LEGACY "UID" +#define ISO14443_3A_PROTOCOL_NAME "ISO14443-3A" +#define ISO14443_3A_DEVICE_NAME "ISO14443-3A (Unknown)" + +#define ISO14443_3A_ATQA_KEY "ATQA" +#define ISO14443_3A_SAK_KEY "SAK" + +const NfcDeviceBase nfc_device_iso14443_3a = { + .protocol_name = ISO14443_3A_PROTOCOL_NAME, + .alloc = (NfcDeviceAlloc)iso14443_3a_alloc, + .free = (NfcDeviceFree)iso14443_3a_free, + .reset = (NfcDeviceReset)iso14443_3a_reset, + .copy = (NfcDeviceCopy)iso14443_3a_copy, + .verify = (NfcDeviceVerify)iso14443_3a_verify, + .load = (NfcDeviceLoad)iso14443_3a_load, + .save = (NfcDeviceSave)iso14443_3a_save, + .is_equal = (NfcDeviceEqual)iso14443_3a_is_equal, + .get_name = (NfcDeviceGetName)iso14443_3a_get_device_name, + .get_uid = (NfcDeviceGetUid)iso14443_3a_get_uid, + .set_uid = (NfcDeviceSetUid)iso14443_3a_set_uid, + .get_base_data = (NfcDeviceGetBaseData)iso14443_3a_get_base_data, +}; + +Iso14443_3aData* iso14443_3a_alloc() { + Iso14443_3aData* data = malloc(sizeof(Iso14443_3aData)); + return data; +} + +void iso14443_3a_free(Iso14443_3aData* data) { + furi_assert(data); + + free(data); +} + +void iso14443_3a_reset(Iso14443_3aData* data) { + furi_assert(data); + memset(data, 0, sizeof(Iso14443_3aData)); +} + +void iso14443_3a_copy(Iso14443_3aData* data, const Iso14443_3aData* other) { + furi_assert(data); + furi_assert(other); + + *data = *other; +} + +bool iso14443_3a_verify(Iso14443_3aData* data, const FuriString* device_type) { + UNUSED(data); + return furi_string_equal(device_type, ISO14443_3A_PROTOCOL_NAME_LEGACY); +} + +bool iso14443_3a_load(Iso14443_3aData* data, FlipperFormat* ff, uint32_t version) { + furi_assert(data); + + bool parsed = false; + + do { + // Common to all format versions + if(!flipper_format_read_hex(ff, ISO14443_3A_ATQA_KEY, data->atqa, 2)) break; + if(!flipper_format_read_hex(ff, ISO14443_3A_SAK_KEY, &data->sak, 1)) break; + + if(version > NFC_LSB_ATQA_FORMAT_VERSION) { + // Swap ATQA bytes for newer versions + FURI_SWAP(data->atqa[0], data->atqa[1]); + } + + parsed = true; + } while(false); + + return parsed; +} + +bool iso14443_3a_save(const Iso14443_3aData* data, FlipperFormat* ff) { + furi_assert(data); + + bool saved = false; + + do { + // Save ATQA in MSB order for correct companion apps display + const uint8_t atqa[2] = {data->atqa[1], data->atqa[0]}; + if(!flipper_format_write_comment_cstr(ff, ISO14443_3A_PROTOCOL_NAME " specific data")) + break; + + // Write ATQA and SAK + if(!flipper_format_write_hex(ff, ISO14443_3A_ATQA_KEY, atqa, 2)) break; + if(!flipper_format_write_hex(ff, ISO14443_3A_SAK_KEY, &data->sak, 1)) break; + saved = true; + } while(false); + + return saved; +} + +bool iso14443_3a_is_equal(const Iso14443_3aData* data, const Iso14443_3aData* other) { + furi_assert(data); + furi_assert(other); + + return memcmp(data, other, sizeof(Iso14443_3aData)) == 0; +} + +const char* iso14443_3a_get_device_name(const Iso14443_3aData* data, NfcDeviceNameType name_type) { + UNUSED(data); + UNUSED(name_type); + return ISO14443_3A_DEVICE_NAME; +} + +const uint8_t* iso14443_3a_get_uid(const Iso14443_3aData* data, size_t* uid_len) { + furi_assert(data); + + if(uid_len) { + *uid_len = data->uid_len; + } + + return data->uid; +} + +bool iso14443_3a_set_uid(Iso14443_3aData* data, const uint8_t* uid, size_t uid_len) { + furi_assert(data); + + const bool uid_valid = uid_len == ISO14443_3A_UID_4_BYTES || + uid_len == ISO14443_3A_UID_7_BYTES || + uid_len == ISO14443_3A_UID_10_BYTES; + + if(uid_valid) { + memcpy(data->uid, uid, uid_len); + data->uid_len = uid_len; + } + + return uid_valid; +} + +Iso14443_3aData* iso14443_3a_get_base_data(const Iso14443_3aData* data) { + UNUSED(data); + furi_crash("No base data"); +} + +uint32_t iso14443_3a_get_cuid(const Iso14443_3aData* data) { + furi_assert(data); + + uint32_t cuid = 0; + const uint8_t* cuid_start = data->uid; + if(data->uid_len == ISO14443_3A_UID_7_BYTES) { + cuid_start = &data->uid[3]; + } + cuid = (cuid_start[0] << 24) | (cuid_start[1] << 16) | (cuid_start[2] << 8) | (cuid_start[3]); + + return cuid; +} + +bool iso14443_3a_supports_iso14443_4(const Iso14443_3aData* data) { + furi_assert(data); + + return data->sak & ISO14443A_ATS_BIT; +} + +uint8_t iso14443_3a_get_sak(const Iso14443_3aData* data) { + furi_assert(data); + + return data->sak; +} + +void iso14443_3a_get_atqa(const Iso14443_3aData* data, uint8_t atqa[2]) { + furi_assert(data); + furi_assert(atqa); + + memcpy(atqa, data->atqa, sizeof(data->atqa)); +} + +void iso14443_3a_set_sak(Iso14443_3aData* data, uint8_t sak) { + furi_assert(data); + + data->sak = sak; +} + +void iso14443_3a_set_atqa(Iso14443_3aData* data, const uint8_t atqa[2]) { + furi_assert(data); + furi_assert(atqa); + + memcpy(data->atqa, atqa, sizeof(data->atqa)); +} diff --git a/lib/nfc/protocols/iso14443_3a/iso14443_3a.h b/lib/nfc/protocols/iso14443_3a/iso14443_3a.h new file mode 100644 index 00000000000..005626e6285 --- /dev/null +++ b/lib/nfc/protocols/iso14443_3a/iso14443_3a.h @@ -0,0 +1,107 @@ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define ISO14443_3A_UID_4_BYTES (4U) +#define ISO14443_3A_UID_7_BYTES (7U) +#define ISO14443_3A_UID_10_BYTES (10U) +#define ISO14443_3A_MAX_UID_SIZE ISO14443_3A_UID_10_BYTES + +#define ISO14443_3A_GUARD_TIME_US (5000) +#define ISO14443_3A_FDT_POLL_FC (1620) +#define ISO14443_3A_FDT_LISTEN_FC (1172) +#define ISO14443_3A_POLLER_MASK_RX_FS ((ISO14443_3A_FDT_LISTEN_FC) / 2) +#define ISO14443_3A_POLL_POLL_MIN_US (1100) + +typedef enum { + Iso14443_3aErrorNone, + Iso14443_3aErrorNotPresent, + Iso14443_3aErrorColResFailed, + Iso14443_3aErrorBufferOverflow, + Iso14443_3aErrorCommunication, + Iso14443_3aErrorFieldOff, + Iso14443_3aErrorWrongCrc, + Iso14443_3aErrorTimeout, +} Iso14443_3aError; + +typedef struct { + uint8_t sens_resp[2]; +} Iso14443_3aSensResp; + +typedef struct { + uint8_t sel_cmd; + uint8_t sel_par; + uint8_t data[4]; // max data bit is 32 +} Iso14443_3aSddReq; + +typedef struct { + uint8_t nfcid[4]; + uint8_t bss; +} Iso14443_3aSddResp; + +typedef struct { + uint8_t sel_cmd; + uint8_t sel_par; + uint8_t nfcid[4]; + uint8_t bcc; +} Iso14443_3aSelReq; + +typedef struct { + uint8_t sak; +} Iso14443_3aSelResp; + +typedef struct { + uint8_t uid[ISO14443_3A_MAX_UID_SIZE]; + uint8_t uid_len; + uint8_t atqa[2]; + uint8_t sak; +} Iso14443_3aData; + +Iso14443_3aData* iso14443_3a_alloc(); + +void iso14443_3a_free(Iso14443_3aData* data); + +void iso14443_3a_reset(Iso14443_3aData* data); + +void iso14443_3a_copy(Iso14443_3aData* data, const Iso14443_3aData* other); + +bool iso14443_3a_verify(Iso14443_3aData* data, const FuriString* device_type); + +bool iso14443_3a_load(Iso14443_3aData* data, FlipperFormat* ff, uint32_t version); + +bool iso14443_3a_save(const Iso14443_3aData* data, FlipperFormat* ff); + +bool iso14443_3a_is_equal(const Iso14443_3aData* data, const Iso14443_3aData* other); + +const char* iso14443_3a_get_device_name(const Iso14443_3aData* data, NfcDeviceNameType name_type); + +const uint8_t* iso14443_3a_get_uid(const Iso14443_3aData* data, size_t* uid_len); + +bool iso14443_3a_set_uid(Iso14443_3aData* data, const uint8_t* uid, size_t uid_len); + +Iso14443_3aData* iso14443_3a_get_base_data(const Iso14443_3aData* data); + +uint32_t iso14443_3a_get_cuid(const Iso14443_3aData* data); + +// Getters and tests + +bool iso14443_3a_supports_iso14443_4(const Iso14443_3aData* data); + +uint8_t iso14443_3a_get_sak(const Iso14443_3aData* data); + +void iso14443_3a_get_atqa(const Iso14443_3aData* data, uint8_t atqa[2]); + +// Setters + +void iso14443_3a_set_sak(Iso14443_3aData* data, uint8_t sak); + +void iso14443_3a_set_atqa(Iso14443_3aData* data, const uint8_t atqa[2]); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/iso14443_3a/iso14443_3a_device_defs.h b/lib/nfc/protocols/iso14443_3a/iso14443_3a_device_defs.h new file mode 100644 index 00000000000..8a736dfa7ff --- /dev/null +++ b/lib/nfc/protocols/iso14443_3a/iso14443_3a_device_defs.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +extern const NfcDeviceBase nfc_device_iso14443_3a; diff --git a/lib/nfc/protocols/iso14443_3a/iso14443_3a_listener.c b/lib/nfc/protocols/iso14443_3a/iso14443_3a_listener.c new file mode 100644 index 00000000000..89d96f6fc53 --- /dev/null +++ b/lib/nfc/protocols/iso14443_3a/iso14443_3a_listener.c @@ -0,0 +1,127 @@ +#include "iso14443_3a_listener_i.h" + +#include "nfc/protocols/nfc_listener_base.h" +#include "nfc/helpers/iso14443_crc.h" + +#include +#include + +#define TAG "Iso14443_3aListener" + +#define ISO14443_3A_LISTENER_MAX_BUFFER_SIZE (256) + +static bool iso14443_3a_listener_halt_received(BitBuffer* buf) { + bool halt_cmd_received = false; + + do { + if(bit_buffer_get_size_bytes(buf) != 4) break; + if(!iso14443_crc_check(Iso14443CrcTypeA, buf)) break; + if(bit_buffer_get_byte(buf, 0) != 0x50) break; + if(bit_buffer_get_byte(buf, 1) != 0x00) break; + halt_cmd_received = true; + } while(false); + + return halt_cmd_received; +} + +Iso14443_3aListener* iso14443_3a_listener_alloc(Nfc* nfc, Iso14443_3aData* data) { + furi_assert(nfc); + + Iso14443_3aListener* instance = malloc(sizeof(Iso14443_3aListener)); + instance->nfc = nfc; + instance->data = data; + instance->tx_buffer = bit_buffer_alloc(ISO14443_3A_LISTENER_MAX_BUFFER_SIZE); + + instance->iso14443_3a_event.data = &instance->iso14443_3a_event_data; + instance->generic_event.protocol = NfcProtocolIso14443_3a; + instance->generic_event.instance = instance; + instance->generic_event.event_data = &instance->iso14443_3a_event; + + nfc_set_fdt_listen_fc(instance->nfc, ISO14443_3A_FDT_LISTEN_FC); + nfc_config(instance->nfc, NfcModeListener, NfcTechIso14443a); + nfc_iso14443a_listener_set_col_res_data( + instance->nfc, + instance->data->uid, + instance->data->uid_len, + instance->data->atqa, + instance->data->sak); + + return instance; +} + +void iso14443_3a_listener_free(Iso14443_3aListener* instance) { + furi_assert(instance); + furi_assert(instance->data); + furi_assert(instance->tx_buffer); + + bit_buffer_free(instance->tx_buffer); + free(instance); +} + +void iso14443_3a_listener_set_callback( + Iso14443_3aListener* instance, + NfcGenericCallback callback, + void* context) { + furi_assert(instance); + + instance->callback = callback; + instance->context = context; +} + +const Iso14443_3aData* iso14443_3a_listener_get_data(Iso14443_3aListener* instance) { + furi_assert(instance); + furi_assert(instance->data); + + return instance->data; +} + +NfcCommand iso14443_3a_listener_run(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.protocol == NfcProtocolInvalid); + furi_assert(event.event_data); + + Iso14443_3aListener* instance = context; + NfcEvent* nfc_event = event.event_data; + NfcCommand command = NfcCommandContinue; + + if(nfc_event->type == NfcEventTypeListenerActivated) { + instance->state = Iso14443_3aListenerStateActive; + } else if(nfc_event->type == NfcEventTypeFieldOff) { + instance->state = Iso14443_3aListenerStateIdle; + if(instance->callback) { + instance->iso14443_3a_event.type = Iso14443_3aListenerEventTypeFieldOff; + instance->callback(instance->generic_event, instance->context); + } + command = NfcCommandSleep; + } else if(nfc_event->type == NfcEventTypeRxEnd) { + if(iso14443_3a_listener_halt_received(nfc_event->data.buffer)) { + if(instance->callback) { + instance->iso14443_3a_event.type = Iso14443_3aListenerEventTypeHalted; + instance->callback(instance->generic_event, instance->context); + } + command = NfcCommandSleep; + } else { + if(iso14443_crc_check(Iso14443CrcTypeA, nfc_event->data.buffer)) { + instance->iso14443_3a_event.type = + Iso14443_3aListenerEventTypeReceivedStandardFrame; + iso14443_crc_trim(nfc_event->data.buffer); + } else { + instance->iso14443_3a_event.type = Iso14443_3aListenerEventTypeReceivedData; + } + instance->iso14443_3a_event_data.buffer = nfc_event->data.buffer; + if(instance->callback) { + command = instance->callback(instance->generic_event, instance->context); + } + } + } + + return command; +} + +const NfcListenerBase nfc_listener_iso14443_3a = { + .alloc = (NfcListenerAlloc)iso14443_3a_listener_alloc, + .free = (NfcListenerFree)iso14443_3a_listener_free, + .set_callback = (NfcListenerSetCallback)iso14443_3a_listener_set_callback, + .get_data = (NfcListenerGetData)iso14443_3a_listener_get_data, + .run = (NfcListenerRun)iso14443_3a_listener_run, +}; diff --git a/lib/nfc/protocols/iso14443_3a/iso14443_3a_listener.h b/lib/nfc/protocols/iso14443_3a/iso14443_3a_listener.h new file mode 100644 index 00000000000..8a550ca0a89 --- /dev/null +++ b/lib/nfc/protocols/iso14443_3a/iso14443_3a_listener.h @@ -0,0 +1,31 @@ +#pragma once + +#include "iso14443_3a.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct Iso14443_3aListener Iso14443_3aListener; + +typedef enum { + Iso14443_3aListenerEventTypeFieldOff, + Iso14443_3aListenerEventTypeHalted, + + Iso14443_3aListenerEventTypeReceivedStandardFrame, + Iso14443_3aListenerEventTypeReceivedData, +} Iso14443_3aListenerEventType; + +typedef struct { + BitBuffer* buffer; +} Iso14443_3aListenerEventData; + +typedef struct { + Iso14443_3aListenerEventType type; + Iso14443_3aListenerEventData* data; +} Iso14443_3aListenerEvent; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/iso14443_3a/iso14443_3a_listener_defs.h b/lib/nfc/protocols/iso14443_3a/iso14443_3a_listener_defs.h new file mode 100644 index 00000000000..b4fccec74c2 --- /dev/null +++ b/lib/nfc/protocols/iso14443_3a/iso14443_3a_listener_defs.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +extern const NfcListenerBase nfc_listener_iso14443_3a; diff --git a/lib/nfc/protocols/iso14443_3a/iso14443_3a_listener_i.c b/lib/nfc/protocols/iso14443_3a/iso14443_3a_listener_i.c new file mode 100644 index 00000000000..46501503cee --- /dev/null +++ b/lib/nfc/protocols/iso14443_3a/iso14443_3a_listener_i.c @@ -0,0 +1,73 @@ +#include "iso14443_3a_listener_i.h" + +#include "nfc/helpers/iso14443_crc.h" + +#define TAG "Iso14443_3aListener" + +static Iso14443_3aError iso14443_3a_listener_process_nfc_error(NfcError error) { + Iso14443_3aError ret = Iso14443_3aErrorNone; + + if(error == NfcErrorNone) { + ret = Iso14443_3aErrorNone; + } else if(error == NfcErrorTimeout) { + ret = Iso14443_3aErrorTimeout; + } else { + ret = Iso14443_3aErrorFieldOff; + } + + return ret; +} + +Iso14443_3aError + iso14443_3a_listener_tx(Iso14443_3aListener* instance, const BitBuffer* tx_buffer) { + furi_assert(instance); + furi_assert(tx_buffer); + + Iso14443_3aError ret = Iso14443_3aErrorNone; + NfcError error = nfc_listener_tx(instance->nfc, tx_buffer); + if(error != NfcErrorNone) { + FURI_LOG_W(TAG, "Tx error: %d", error); + ret = iso14443_3a_listener_process_nfc_error(error); + } + + return ret; +} + +Iso14443_3aError iso14443_3a_listener_tx_with_custom_parity( + Iso14443_3aListener* instance, + const BitBuffer* tx_buffer) { + furi_assert(instance); + furi_assert(tx_buffer); + + Iso14443_3aError ret = Iso14443_3aErrorNone; + NfcError error = nfc_iso14443a_listener_tx_custom_parity(instance->nfc, tx_buffer); + if(error != NfcErrorNone) { + FURI_LOG_W(TAG, "Tx error: %d", error); + ret = iso14443_3a_listener_process_nfc_error(error); + } + + return ret; +}; + +Iso14443_3aError iso14443_3a_listener_send_standard_frame( + Iso14443_3aListener* instance, + const BitBuffer* tx_buffer) { + furi_assert(instance); + furi_assert(tx_buffer); + furi_assert(instance->tx_buffer); + + Iso14443_3aError ret = Iso14443_3aErrorNone; + do { + bit_buffer_copy(instance->tx_buffer, tx_buffer); + iso14443_crc_append(Iso14443CrcTypeA, instance->tx_buffer); + + NfcError error = nfc_listener_tx(instance->nfc, instance->tx_buffer); + if(error != NfcErrorNone) { + FURI_LOG_W(TAG, "Tx error: %d", error); + ret = iso14443_3a_listener_process_nfc_error(error); + break; + } + } while(false); + + return ret; +} diff --git a/lib/nfc/protocols/iso14443_3a/iso14443_3a_listener_i.h b/lib/nfc/protocols/iso14443_3a/iso14443_3a_listener_i.h new file mode 100644 index 00000000000..0113a1cb892 --- /dev/null +++ b/lib/nfc/protocols/iso14443_3a/iso14443_3a_listener_i.h @@ -0,0 +1,42 @@ +#pragma once + +#include "iso14443_3a_listener.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + Iso14443_3aListenerStateIdle, + Iso14443_3aListenerStateActive, +} Iso14443_3aListenerState; + +struct Iso14443_3aListener { + Nfc* nfc; + Iso14443_3aData* data; + Iso14443_3aListenerState state; + + BitBuffer* tx_buffer; + + NfcGenericEvent generic_event; + Iso14443_3aListenerEvent iso14443_3a_event; + Iso14443_3aListenerEventData iso14443_3a_event_data; + NfcGenericCallback callback; + void* context; +}; + +Iso14443_3aError + iso14443_3a_listener_tx(Iso14443_3aListener* instance, const BitBuffer* tx_buffer); + +Iso14443_3aError iso14443_3a_listener_tx_with_custom_parity( + Iso14443_3aListener* instance, + const BitBuffer* tx_buffer); + +Iso14443_3aError iso14443_3a_listener_send_standard_frame( + Iso14443_3aListener* instance, + const BitBuffer* tx_buffer); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller.c b/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller.c new file mode 100644 index 00000000000..880092c333a --- /dev/null +++ b/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller.c @@ -0,0 +1,128 @@ +#include "iso14443_3a_poller_i.h" + +#include + +#include + +#define TAG "ISO14443_3A" + +const Iso14443_3aData* iso14443_3a_poller_get_data(Iso14443_3aPoller* instance) { + furi_assert(instance); + furi_assert(instance->data); + + return instance->data; +} + +static Iso14443_3aPoller* iso14443_3a_poller_alloc(Nfc* nfc) { + furi_assert(nfc); + + Iso14443_3aPoller* instance = malloc(sizeof(Iso14443_3aPoller)); + instance->nfc = nfc; + instance->tx_buffer = bit_buffer_alloc(ISO14443_3A_POLLER_MAX_BUFFER_SIZE); + instance->rx_buffer = bit_buffer_alloc(ISO14443_3A_POLLER_MAX_BUFFER_SIZE); + + nfc_config(instance->nfc, NfcModePoller, NfcTechIso14443a); + nfc_set_guard_time_us(instance->nfc, ISO14443_3A_GUARD_TIME_US); + nfc_set_fdt_poll_fc(instance->nfc, ISO14443_3A_FDT_POLL_FC); + nfc_set_fdt_poll_poll_us(instance->nfc, ISO14443_3A_POLL_POLL_MIN_US); + instance->data = iso14443_3a_alloc(); + + instance->iso14443_3a_event.data = &instance->iso14443_3a_event_data; + instance->general_event.protocol = NfcProtocolIso14443_3a; + instance->general_event.event_data = &instance->iso14443_3a_event; + instance->general_event.instance = instance; + + return instance; +} + +static void iso14443_3a_poller_free_new(Iso14443_3aPoller* iso14443_3a_poller) { + furi_assert(iso14443_3a_poller); + + Iso14443_3aPoller* instance = iso14443_3a_poller; + furi_assert(instance->tx_buffer); + furi_assert(instance->rx_buffer); + furi_assert(instance->data); + + bit_buffer_free(instance->tx_buffer); + bit_buffer_free(instance->rx_buffer); + iso14443_3a_free(instance->data); + free(instance); +} + +static void iso14443_3a_poller_set_callback( + Iso14443_3aPoller* instance, + NfcGenericCallback callback, + void* context) { + furi_assert(instance); + furi_assert(callback); + + instance->callback = callback; + instance->context = context; +} + +static NfcCommand iso14443_3a_poller_run(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.protocol == NfcProtocolInvalid); + furi_assert(event.event_data); + + Iso14443_3aPoller* instance = context; + NfcEvent* nfc_event = event.event_data; + NfcCommand command = NfcCommandContinue; + + if(nfc_event->type == NfcEventTypePollerReady) { + if(instance->state != Iso14443_3aPollerStateActivated) { + Iso14443_3aData data = {}; + Iso14443_3aError error = iso14443_3a_poller_async_activate(instance, &data); + if(error == Iso14443_3aErrorNone) { + instance->state = Iso14443_3aPollerStateActivated; + instance->iso14443_3a_event.type = Iso14443_3aPollerEventTypeReady; + instance->iso14443_3a_event_data.error = error; + command = instance->callback(instance->general_event, instance->context); + } else { + instance->iso14443_3a_event.type = Iso14443_3aPollerEventTypeError; + instance->iso14443_3a_event_data.error = error; + command = instance->callback(instance->general_event, instance->context); + // Add delay to switch context + furi_delay_ms(100); + } + } else { + instance->iso14443_3a_event.type = Iso14443_3aPollerEventTypeReady; + instance->iso14443_3a_event_data.error = Iso14443_3aErrorNone; + command = instance->callback(instance->general_event, instance->context); + } + } + + if(command == NfcCommandReset) { + instance->state = Iso14443_3aPollerStateIdle; + } + + return command; +} + +static bool iso14443_3a_poller_detect(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.event_data); + furi_assert(event.instance); + furi_assert(event.protocol == NfcProtocolInvalid); + + bool protocol_detected = false; + Iso14443_3aPoller* instance = context; + NfcEvent* nfc_event = event.event_data; + furi_assert(instance->state == Iso14443_3aPollerStateIdle); + + if(nfc_event->type == NfcEventTypePollerReady) { + Iso14443_3aError error = iso14443_3a_poller_async_activate(instance, NULL); + protocol_detected = (error == Iso14443_3aErrorNone); + } + + return protocol_detected; +} + +const NfcPollerBase nfc_poller_iso14443_3a = { + .alloc = (NfcPollerAlloc)iso14443_3a_poller_alloc, + .free = (NfcPollerFree)iso14443_3a_poller_free_new, + .set_callback = (NfcPollerSetCallback)iso14443_3a_poller_set_callback, + .run = (NfcPollerRun)iso14443_3a_poller_run, + .detect = (NfcPollerDetect)iso14443_3a_poller_detect, + .get_data = (NfcPollerGetData)iso14443_3a_poller_get_data, +}; diff --git a/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller.h b/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller.h new file mode 100644 index 00000000000..385cd522567 --- /dev/null +++ b/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller.h @@ -0,0 +1,42 @@ +#pragma once + +#include "iso14443_3a.h" +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct Iso14443_3aPoller Iso14443_3aPoller; + +typedef enum { + Iso14443_3aPollerEventTypeError, + Iso14443_3aPollerEventTypeReady, +} Iso14443_3aPollerEventType; + +typedef struct { + Iso14443_3aError error; +} Iso14443_3aPollerEventData; + +typedef struct { + Iso14443_3aPollerEventType type; + Iso14443_3aPollerEventData* data; +} Iso14443_3aPollerEvent; + +Iso14443_3aError iso14443_3a_poller_txrx( + Iso14443_3aPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt); + +Iso14443_3aError iso14443_3a_poller_send_standard_frame( + Iso14443_3aPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_defs.h b/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_defs.h new file mode 100644 index 00000000000..1bcc684b405 --- /dev/null +++ b/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_defs.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +extern const NfcPollerBase nfc_poller_iso14443_3a; diff --git a/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_i.c b/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_i.c new file mode 100644 index 00000000000..3434dc8e354 --- /dev/null +++ b/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_i.c @@ -0,0 +1,293 @@ +#include "iso14443_3a_poller_i.h" + +#include + +#include "nfc/helpers/iso14443_crc.h" + +#define TAG "ISO14443_3A" + +static Iso14443_3aError iso14443_3a_poller_process_error(NfcError error) { + Iso14443_3aError ret = Iso14443_3aErrorNone; + if(error == NfcErrorNone) { + ret = Iso14443_3aErrorNone; + } else if(error == NfcErrorTimeout) { + ret = Iso14443_3aErrorTimeout; + } else { + ret = Iso14443_3aErrorNotPresent; + } + return ret; +} + +static Iso14443_3aError iso14443_3a_poller_standard_frame_exchange( + Iso14443_3aPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt) { + furi_assert(instance); + furi_assert(tx_buffer); + furi_assert(rx_buffer); + + uint16_t tx_bytes = bit_buffer_get_size_bytes(tx_buffer); + furi_assert(tx_bytes <= bit_buffer_get_capacity_bytes(instance->tx_buffer) - 2); + + bit_buffer_copy(instance->tx_buffer, tx_buffer); + iso14443_crc_append(Iso14443CrcTypeA, instance->tx_buffer); + Iso14443_3aError ret = Iso14443_3aErrorNone; + + do { + NfcError error = + nfc_poller_trx(instance->nfc, instance->tx_buffer, instance->rx_buffer, fwt); + if(error != NfcErrorNone) { + ret = iso14443_3a_poller_process_error(error); + break; + } + + bit_buffer_copy(rx_buffer, instance->rx_buffer); + if(!iso14443_crc_check(Iso14443CrcTypeA, instance->rx_buffer)) { + ret = Iso14443_3aErrorWrongCrc; + break; + } + + iso14443_crc_trim(rx_buffer); + } while(false); + + return ret; +} + +Iso14443_3aError iso14443_3a_poller_check_presence(Iso14443_3aPoller* instance) { + furi_assert(instance); + furi_assert(instance->nfc); + + NfcError error = NfcErrorNone; + Iso14443_3aError ret = Iso14443_3aErrorNone; + do { + error = nfc_iso14443a_poller_trx_short_frame( + instance->nfc, + NfcIso14443aShortFrameSensReq, + instance->rx_buffer, + ISO14443_3A_FDT_LISTEN_FC); + if(error != NfcErrorNone) { + ret = iso14443_3a_poller_process_error(error); + break; + } + if(bit_buffer_get_size_bytes(instance->rx_buffer) != sizeof(instance->col_res.sens_resp)) { + ret = Iso14443_3aErrorCommunication; + break; + } + } while(false); + + return ret; +} + +Iso14443_3aError iso14443_3a_poller_halt(Iso14443_3aPoller* instance) { + furi_assert(instance); + furi_assert(instance->nfc); + furi_assert(instance->tx_buffer); + + uint8_t halt_cmd[2] = {0x50, 0x00}; + bit_buffer_copy_bytes(instance->tx_buffer, halt_cmd, sizeof(halt_cmd)); + + iso14443_3a_poller_standard_frame_exchange( + instance, instance->tx_buffer, instance->rx_buffer, ISO14443_3A_FDT_LISTEN_FC); + + instance->state = Iso14443_3aPollerStateIdle; + return Iso14443_3aErrorNone; +} + +Iso14443_3aError iso14443_3a_poller_async_activate( + Iso14443_3aPoller* instance, + Iso14443_3aData* iso14443_3a_data) { + furi_assert(instance); + furi_assert(instance->nfc); + furi_assert(instance->tx_buffer); + furi_assert(instance->rx_buffer); + + // Reset Iso14443_3a poller state + memset(&instance->col_res, 0, sizeof(instance->col_res)); + memset(instance->data, 0, sizeof(Iso14443_3aData)); + bit_buffer_reset(instance->tx_buffer); + bit_buffer_reset(instance->rx_buffer); + + // Halt if necessary + if(instance->state != Iso14443_3aPollerStateIdle) { + iso14443_3a_poller_halt(instance); + instance->state = Iso14443_3aPollerStateIdle; + } + + NfcError error = NfcErrorNone; + Iso14443_3aError ret = Iso14443_3aErrorNone; + + bool activated = false; + do { + error = nfc_iso14443a_poller_trx_short_frame( + instance->nfc, + NfcIso14443aShortFrameSensReq, + instance->rx_buffer, + ISO14443_3A_FDT_LISTEN_FC); + if(error != NfcErrorNone) { + ret = Iso14443_3aErrorNotPresent; + break; + } + if(bit_buffer_get_size_bytes(instance->rx_buffer) != sizeof(instance->col_res.sens_resp)) { + FURI_LOG_W(TAG, "Wrong sens response size"); + ret = Iso14443_3aErrorCommunication; + break; + } + bit_buffer_write_bytes( + instance->rx_buffer, + &instance->col_res.sens_resp, + sizeof(instance->col_res.sens_resp)); + memcpy( + instance->data->atqa, + &instance->col_res.sens_resp, + sizeof(instance->col_res.sens_resp)); + + instance->state = Iso14443_3aPollerStateColResInProgress; + instance->col_res.cascade_level = 0; + instance->col_res.state = Iso14443_3aPollerColResStateStateNewCascade; + + while(instance->state == Iso14443_3aPollerStateColResInProgress) { + if(instance->col_res.state == Iso14443_3aPollerColResStateStateNewCascade) { + bit_buffer_set_size_bytes(instance->tx_buffer, 2); + bit_buffer_set_byte( + instance->tx_buffer, + 0, + ISO14443_3A_POLLER_SEL_CMD(instance->col_res.cascade_level)); + bit_buffer_set_byte(instance->tx_buffer, 1, ISO14443_3A_POLLER_SEL_PAR(2, 0)); + error = nfc_iso14443a_poller_trx_sdd_frame( + instance->nfc, + instance->tx_buffer, + instance->rx_buffer, + ISO14443_3A_FDT_LISTEN_FC); + if(error != NfcErrorNone) { + FURI_LOG_E(TAG, "Sdd request failed: %d", error); + instance->state = Iso14443_3aPollerStateColResFailed; + ret = Iso14443_3aErrorColResFailed; + break; + } + if(bit_buffer_get_size_bytes(instance->rx_buffer) != 5) { + FURI_LOG_E(TAG, "Sdd response wrong length"); + instance->state = Iso14443_3aPollerStateColResFailed; + ret = Iso14443_3aErrorColResFailed; + break; + } + bit_buffer_write_bytes( + instance->rx_buffer, &instance->col_res.sdd_resp, sizeof(Iso14443_3aSddResp)); + instance->col_res.state = Iso14443_3aPollerColResStateStateSelectCascade; + } else if(instance->col_res.state == Iso14443_3aPollerColResStateStateSelectCascade) { + instance->col_res.sel_req.sel_cmd = + ISO14443_3A_POLLER_SEL_CMD(instance->col_res.cascade_level); + instance->col_res.sel_req.sel_par = ISO14443_3A_POLLER_SEL_PAR(7, 0); + memcpy( + instance->col_res.sel_req.nfcid, + instance->col_res.sdd_resp.nfcid, + sizeof(instance->col_res.sdd_resp.nfcid)); + instance->col_res.sel_req.bcc = instance->col_res.sdd_resp.bss; + bit_buffer_copy_bytes( + instance->tx_buffer, + (uint8_t*)&instance->col_res.sel_req, + sizeof(instance->col_res.sel_req)); + ret = iso14443_3a_poller_send_standard_frame( + instance, instance->tx_buffer, instance->rx_buffer, ISO14443_3A_FDT_LISTEN_FC); + if(ret != Iso14443_3aErrorNone) { + FURI_LOG_E(TAG, "Sel request failed: %d", ret); + instance->state = Iso14443_3aPollerStateColResFailed; + ret = Iso14443_3aErrorColResFailed; + break; + } + if(bit_buffer_get_size_bytes(instance->rx_buffer) != + sizeof(instance->col_res.sel_resp)) { + FURI_LOG_E(TAG, "Sel response wrong length"); + instance->state = Iso14443_3aPollerStateColResFailed; + ret = Iso14443_3aErrorColResFailed; + break; + } + bit_buffer_write_bytes( + instance->rx_buffer, + &instance->col_res.sel_resp, + sizeof(instance->col_res.sel_resp)); + FURI_LOG_T(TAG, "Sel resp: %02X", instance->col_res.sel_resp.sak); + if(instance->col_res.sel_req.nfcid[0] == ISO14443_3A_POLLER_SDD_CL) { + // Copy part of UID + memcpy( + &instance->data->uid[instance->data->uid_len], + &instance->col_res.sel_req.nfcid[1], + 3); + instance->data->uid_len += 3; + instance->col_res.cascade_level++; + instance->col_res.state = Iso14443_3aPollerColResStateStateNewCascade; + } else { + FURI_LOG_T(TAG, "Col resolution complete"); + instance->data->sak = instance->col_res.sel_resp.sak; + memcpy( + &instance->data->uid[instance->data->uid_len], + &instance->col_res.sel_req.nfcid[0], + 4); + instance->data->uid_len += 4; + instance->col_res.state = Iso14443_3aPollerColResStateStateSuccess; + instance->state = Iso14443_3aPollerStateActivated; + } + } + } + + activated = (instance->state == Iso14443_3aPollerStateActivated); + } while(false); + + if(activated && iso14443_3a_data) { + *iso14443_3a_data = *instance->data; + } + + return ret; +} + +Iso14443_3aError iso14443_3a_poller_txrx_custom_parity( + Iso14443_3aPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt) { + furi_assert(instance); + furi_assert(tx_buffer); + furi_assert(rx_buffer); + + Iso14443_3aError ret = Iso14443_3aErrorNone; + NfcError error = + nfc_iso14443a_poller_trx_custom_parity(instance->nfc, tx_buffer, rx_buffer, fwt); + if(error != NfcErrorNone) { + ret = iso14443_3a_poller_process_error(error); + } + + return ret; +} + +Iso14443_3aError iso14443_3a_poller_txrx( + Iso14443_3aPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt) { + furi_assert(instance); + furi_assert(tx_buffer); + furi_assert(rx_buffer); + + Iso14443_3aError ret = Iso14443_3aErrorNone; + NfcError error = nfc_poller_trx(instance->nfc, tx_buffer, rx_buffer, fwt); + if(error != NfcErrorNone) { + ret = iso14443_3a_poller_process_error(error); + } + + return ret; +} + +Iso14443_3aError iso14443_3a_poller_send_standard_frame( + Iso14443_3aPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt) { + furi_assert(instance); + furi_assert(tx_buffer); + furi_assert(rx_buffer); + + Iso14443_3aError ret = + iso14443_3a_poller_standard_frame_exchange(instance, tx_buffer, rx_buffer, fwt); + + return ret; +} diff --git a/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_i.h b/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_i.h new file mode 100644 index 00000000000..063ef155665 --- /dev/null +++ b/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_i.h @@ -0,0 +1,81 @@ +#pragma once + +#include "iso14443_3a_poller.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define ISO14443_3A_POLLER_MAX_BUFFER_SIZE (512U) + +#define ISO14443_3A_POLLER_SEL_CMD(cascade_lvl) (0x93 + 2 * (cascade_lvl)) +#define ISO14443_3A_POLLER_SEL_PAR(bytes, bits) (((bytes) << 4 & 0xf0U) | ((bits)&0x0fU)) +#define ISO14443_3A_POLLER_SDD_CL (0x88U) + +typedef enum { + Iso14443_3aPollerColResStateStateIdle, + Iso14443_3aPollerColResStateStateNewCascade, + Iso14443_3aPollerColResStateStateSelectCascade, + Iso14443_3aPollerColResStateStateSuccess, + Iso14443_3aPollerColResStateStateFail, +} Iso14443_3aPollerColResState; + +typedef struct { + Iso14443_3aPollerColResState state; + Iso14443_3aSensResp sens_resp; + Iso14443_3aSddReq sdd_req; + Iso14443_3aSddResp sdd_resp; + Iso14443_3aSelReq sel_req; + Iso14443_3aSelResp sel_resp; + uint8_t cascade_level; +} Iso14443_3aPollerColRes; + +typedef enum { + Iso14443_3aPollerStateIdle, + Iso14443_3aPollerStateColResInProgress, + Iso14443_3aPollerStateColResFailed, + Iso14443_3aPollerStateActivated, +} Iso14443_3aPollerState; + +typedef enum { + Iso14443_3aPollerConfigStateIdle, + Iso14443_3aPollerConfigStateDone, +} Iso14443_3aPollerConfigState; + +struct Iso14443_3aPoller { + Nfc* nfc; + Iso14443_3aPollerState state; + Iso14443_3aPollerConfigState config_state; + Iso14443_3aPollerColRes col_res; + Iso14443_3aData* data; + BitBuffer* tx_buffer; + BitBuffer* rx_buffer; + + NfcGenericEvent general_event; + Iso14443_3aPollerEvent iso14443_3a_event; + Iso14443_3aPollerEventData iso14443_3a_event_data; + NfcGenericCallback callback; + void* context; +}; + +const Iso14443_3aData* iso14443_3a_poller_get_data(Iso14443_3aPoller* instance); + +Iso14443_3aError iso14443_3a_poller_check_presence(Iso14443_3aPoller* instance); + +Iso14443_3aError iso14443_3a_poller_async_activate( + Iso14443_3aPoller* instance, + Iso14443_3aData* iso14443_3a_data); + +Iso14443_3aError iso14443_3a_poller_halt(Iso14443_3aPoller* instance); + +Iso14443_3aError iso14443_3a_poller_txrx_custom_parity( + Iso14443_3aPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_sync_api.c b/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_sync_api.c new file mode 100644 index 00000000000..2bab1a881a2 --- /dev/null +++ b/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_sync_api.c @@ -0,0 +1,58 @@ +#include "iso14443_3a_poller_sync_api.h" + +#include "iso14443_3a_poller_i.h" +#include + +#include + +#define ISO14443_3A_POLLER_FLAG_COMMAND_COMPLETE (1UL << 0) + +typedef struct { + Iso14443_3aPoller* instance; + FuriThreadId thread_id; + Iso14443_3aError error; + Iso14443_3aData data; +} Iso14443_3aPollerContext; + +NfcCommand iso14443_3a_poller_read_callback(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.event_data); + furi_assert(event.instance); + furi_assert(event.protocol == NfcProtocolIso14443_3a); + + Iso14443_3aPollerContext* poller_context = context; + Iso14443_3aPoller* iso14443_3a_poller = event.instance; + Iso14443_3aPollerEvent* iso14443_3a_event = event.event_data; + + if(iso14443_3a_event->type == Iso14443_3aPollerEventTypeReady) { + iso14443_3a_copy(&poller_context->data, iso14443_3a_poller->data); + } + poller_context->error = iso14443_3a_event->data->error; + + furi_thread_flags_set(poller_context->thread_id, ISO14443_3A_POLLER_FLAG_COMMAND_COMPLETE); + + return NfcCommandStop; +} + +Iso14443_3aError iso14443_3a_poller_read(Nfc* nfc, Iso14443_3aData* iso14443_3a_data) { + furi_assert(nfc); + furi_assert(iso14443_3a_data); + + Iso14443_3aPollerContext poller_context = {}; + poller_context.thread_id = furi_thread_get_current_id(); + + NfcPoller* poller = nfc_poller_alloc(nfc, NfcProtocolIso14443_3a); + nfc_poller_start(poller, iso14443_3a_poller_read_callback, &poller_context); + furi_thread_flags_wait( + ISO14443_3A_POLLER_FLAG_COMMAND_COMPLETE, FuriFlagWaitAny, FuriWaitForever); + furi_thread_flags_clear(ISO14443_3A_POLLER_FLAG_COMMAND_COMPLETE); + + nfc_poller_stop(poller); + nfc_poller_free(poller); + + if(poller_context.error == Iso14443_3aErrorNone) { + *iso14443_3a_data = poller_context.data; + } + + return poller_context.error; +} \ No newline at end of file diff --git a/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_sync_api.h b/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_sync_api.h new file mode 100644 index 00000000000..ed17ff43244 --- /dev/null +++ b/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_sync_api.h @@ -0,0 +1,14 @@ +#pragma once + +#include "iso14443_3a.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +Iso14443_3aError iso14443_3a_poller_read(Nfc* nfc, Iso14443_3aData* iso14443_3a_data); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/iso14443_3b/iso14443_3b.c b/lib/nfc/protocols/iso14443_3b/iso14443_3b.c new file mode 100644 index 00000000000..fd81e390da7 --- /dev/null +++ b/lib/nfc/protocols/iso14443_3b/iso14443_3b.c @@ -0,0 +1,223 @@ +#include "iso14443_3b_i.h" + +#include +#include + +#include +#include + +#define ISO14443_3B_PROTOCOL_NAME "ISO14443-3B" +#define ISO14443_3B_DEVICE_NAME "ISO14443-3B (Unknown)" + +#define ISO14443_3B_APP_DATA_KEY "Application data" +#define ISO14443_3B_PROTOCOL_INFO_KEY "Protocol info" + +#define ISO14443_3B_FDT_POLL_DEFAULT_FC (ISO14443_3B_FDT_POLL_FC) + +const NfcDeviceBase nfc_device_iso14443_3b = { + .protocol_name = ISO14443_3B_PROTOCOL_NAME, + .alloc = (NfcDeviceAlloc)iso14443_3b_alloc, + .free = (NfcDeviceFree)iso14443_3b_free, + .reset = (NfcDeviceReset)iso14443_3b_reset, + .copy = (NfcDeviceCopy)iso14443_3b_copy, + .verify = (NfcDeviceVerify)iso14443_3b_verify, + .load = (NfcDeviceLoad)iso14443_3b_load, + .save = (NfcDeviceSave)iso14443_3b_save, + .is_equal = (NfcDeviceEqual)iso14443_3b_is_equal, + .get_name = (NfcDeviceGetName)iso14443_3b_get_device_name, + .get_uid = (NfcDeviceGetUid)iso14443_3b_get_uid, + .set_uid = (NfcDeviceSetUid)iso14443_3b_set_uid, + .get_base_data = (NfcDeviceGetBaseData)iso14443_3b_get_base_data, +}; + +Iso14443_3bData* iso14443_3b_alloc() { + Iso14443_3bData* data = malloc(sizeof(Iso14443_3bData)); + return data; +} + +void iso14443_3b_free(Iso14443_3bData* data) { + furi_assert(data); + + free(data); +} + +void iso14443_3b_reset(Iso14443_3bData* data) { + memset(data, 0, sizeof(Iso14443_3bData)); +} + +void iso14443_3b_copy(Iso14443_3bData* data, const Iso14443_3bData* other) { + furi_assert(data); + furi_assert(other); + + *data = *other; +} + +bool iso14443_3b_verify(Iso14443_3bData* data, const FuriString* device_type) { + UNUSED(data); + UNUSED(device_type); + // No support for old ISO14443-3B + return false; +} + +bool iso14443_3b_load(Iso14443_3bData* data, FlipperFormat* ff, uint32_t version) { + furi_assert(data); + + bool parsed = false; + + do { + if(version < NFC_UNIFIED_FORMAT_VERSION) break; + + if(!flipper_format_read_hex( + ff, ISO14443_3B_APP_DATA_KEY, data->app_data, ISO14443_3B_APP_DATA_SIZE)) + break; + if(!flipper_format_read_hex( + ff, + ISO14443_3B_PROTOCOL_INFO_KEY, + (uint8_t*)&data->protocol_info, + sizeof(Iso14443_3bProtocolInfo))) + break; + + parsed = true; + } while(false); + + return parsed; +} + +bool iso14443_3b_save(const Iso14443_3bData* data, FlipperFormat* ff) { + furi_assert(data); + + bool saved = false; + + do { + if(!flipper_format_write_comment_cstr(ff, ISO14443_3B_PROTOCOL_NAME " specific data")) + break; + if(!flipper_format_write_hex( + ff, ISO14443_3B_APP_DATA_KEY, data->app_data, ISO14443_3B_APP_DATA_SIZE)) + break; + if(!flipper_format_write_hex( + ff, + ISO14443_3B_PROTOCOL_INFO_KEY, + (uint8_t*)&data->protocol_info, + sizeof(Iso14443_3bProtocolInfo))) + break; + saved = true; + } while(false); + + return saved; +} + +bool iso14443_3b_is_equal(const Iso14443_3bData* data, const Iso14443_3bData* other) { + furi_assert(data); + furi_assert(other); + + return memcmp(data, other, sizeof(Iso14443_3bData)) == 0; +} + +const char* iso14443_3b_get_device_name(const Iso14443_3bData* data, NfcDeviceNameType name_type) { + UNUSED(data); + UNUSED(name_type); + + return ISO14443_3B_DEVICE_NAME; +} + +const uint8_t* iso14443_3b_get_uid(const Iso14443_3bData* data, size_t* uid_len) { + furi_assert(data); + furi_assert(uid_len); + + *uid_len = ISO14443_3B_UID_SIZE; + return data->uid; +} + +bool iso14443_3b_set_uid(Iso14443_3bData* data, const uint8_t* uid, size_t uid_len) { + furi_assert(data); + + const bool uid_valid = uid_len == ISO14443_3B_UID_SIZE; + + if(uid_valid) { + memcpy(data->uid, uid, uid_len); + } + + return uid_valid; +} + +Iso14443_3bData* iso14443_3b_get_base_data(const Iso14443_3bData* data) { + UNUSED(data); + furi_crash("No base data"); +} + +bool iso14443_3b_supports_iso14443_4(const Iso14443_3bData* data) { + furi_assert(data); + + return data->protocol_info.protocol_type == 0x01; +} + +bool iso14443_3b_supports_bit_rate(const Iso14443_3bData* data, Iso14443_3bBitRate bit_rate) { + furi_assert(data); + + const uint8_t capability = data->protocol_info.bit_rate_capability; + + switch(bit_rate) { + case Iso14443_3bBitRateBoth106Kbit: + return capability == ISO14443_3B_BIT_RATE_BOTH_106KBIT; + case Iso14443_3bBitRatePiccToPcd212Kbit: + return capability & ISO14443_3B_BIT_RATE_PICC_TO_PCD_212KBIT; + case Iso14443_3bBitRatePiccToPcd424Kbit: + return capability & ISO14443_3B_BIT_RATE_PICC_TO_PCD_424KBIT; + case Iso14443_3bBitRatePiccToPcd848Kbit: + return capability & ISO14443_3B_BIT_RATE_PICC_TO_PCD_848KBIT; + case Iso14443_3bBitRatePcdToPicc212Kbit: + return capability & ISO14443_3B_BIT_RATE_PCD_TO_PICC_212KBIT; + case Iso14443_3bBitRatePcdToPicc424Kbit: + return capability & ISO14443_3B_BIT_RATE_PCD_TO_PICC_424KBIT; + case Iso14443_3bBitRatePcdToPicc848Kbit: + return capability & ISO14443_3B_BIT_RATE_PCD_TO_PICC_848KBIT; + default: + return false; + } +} + +bool iso14443_3b_supports_frame_option(const Iso14443_3bData* data, Iso14443_3bFrameOption option) { + furi_assert(data); + + switch(option) { + case Iso14443_3bFrameOptionNad: + return data->protocol_info.fo & ISO14443_3B_FRAME_OPTION_NAD; + case Iso14443_3bFrameOptionCid: + return data->protocol_info.fo & ISO14443_3B_FRAME_OPTION_CID; + default: + return false; + } +} + +const uint8_t* iso14443_3b_get_application_data(const Iso14443_3bData* data, size_t* data_size) { + furi_assert(data); + furi_assert(data_size); + + *data_size = ISO14443_3B_APP_DATA_SIZE; + return data->app_data; +} + +uint16_t iso14443_3b_get_frame_size_max(const Iso14443_3bData* data) { + furi_assert(data); + + const uint8_t fs_bits = data->protocol_info.max_frame_size; + + if(fs_bits < 5) { + return fs_bits * 8 + 16; + } else if(fs_bits == 5) { + return 64; + } else if(fs_bits == 6) { + return 96; + } else if(fs_bits < 13) { + return 128U << (fs_bits - 7); + } else { + return 0; + } +} + +uint32_t iso14443_3b_get_fwt_fc_max(const Iso14443_3bData* data) { + furi_assert(data); + + const uint8_t fwi = data->protocol_info.fwi; + return fwi < 0x0F ? 4096UL << fwi : ISO14443_3B_FDT_POLL_DEFAULT_FC; +} diff --git a/lib/nfc/protocols/iso14443_3b/iso14443_3b.h b/lib/nfc/protocols/iso14443_3b/iso14443_3b.h new file mode 100644 index 00000000000..848e610c3e8 --- /dev/null +++ b/lib/nfc/protocols/iso14443_3b/iso14443_3b.h @@ -0,0 +1,83 @@ +#pragma once + +#include + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + Iso14443_3bErrorNone, + Iso14443_3bErrorNotPresent, + Iso14443_3bErrorColResFailed, + Iso14443_3bErrorBufferOverflow, + Iso14443_3bErrorCommunication, + Iso14443_3bErrorFieldOff, + Iso14443_3bErrorWrongCrc, + Iso14443_3bErrorTimeout, +} Iso14443_3bError; + +typedef enum { + Iso14443_3bBitRateBoth106Kbit, + Iso14443_3bBitRatePiccToPcd212Kbit, + Iso14443_3bBitRatePiccToPcd424Kbit, + Iso14443_3bBitRatePiccToPcd848Kbit, + Iso14443_3bBitRatePcdToPicc212Kbit, + Iso14443_3bBitRatePcdToPicc424Kbit, + Iso14443_3bBitRatePcdToPicc848Kbit, +} Iso14443_3bBitRate; + +typedef enum { + Iso14443_3bFrameOptionNad, + Iso14443_3bFrameOptionCid, +} Iso14443_3bFrameOption; + +typedef struct Iso14443_3bData Iso14443_3bData; + +// Virtual methods + +Iso14443_3bData* iso14443_3b_alloc(); + +void iso14443_3b_free(Iso14443_3bData* data); + +void iso14443_3b_reset(Iso14443_3bData* data); + +void iso14443_3b_copy(Iso14443_3bData* data, const Iso14443_3bData* other); + +bool iso14443_3b_verify(Iso14443_3bData* data, const FuriString* device_type); + +bool iso14443_3b_load(Iso14443_3bData* data, FlipperFormat* ff, uint32_t version); + +bool iso14443_3b_save(const Iso14443_3bData* data, FlipperFormat* ff); + +bool iso14443_3b_is_equal(const Iso14443_3bData* data, const Iso14443_3bData* other); + +const char* iso14443_3b_get_device_name(const Iso14443_3bData* data, NfcDeviceNameType name_type); + +const uint8_t* iso14443_3b_get_uid(const Iso14443_3bData* data, size_t* uid_len); + +bool iso14443_3b_set_uid(Iso14443_3bData* data, const uint8_t* uid, size_t uid_len); + +Iso14443_3bData* iso14443_3b_get_base_data(const Iso14443_3bData* data); + +// Getters and tests + +bool iso14443_3b_supports_iso14443_4(const Iso14443_3bData* data); + +bool iso14443_3b_supports_bit_rate(const Iso14443_3bData* data, Iso14443_3bBitRate bit_rate); + +bool iso14443_3b_supports_frame_option(const Iso14443_3bData* data, Iso14443_3bFrameOption option); + +const uint8_t* iso14443_3b_get_application_data(const Iso14443_3bData* data, size_t* data_size); + +uint16_t iso14443_3b_get_frame_size_max(const Iso14443_3bData* data); + +uint32_t iso14443_3b_get_fwt_fc_max(const Iso14443_3bData* data); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/iso14443_3b/iso14443_3b_device_defs.h b/lib/nfc/protocols/iso14443_3b/iso14443_3b_device_defs.h new file mode 100644 index 00000000000..6c33900da23 --- /dev/null +++ b/lib/nfc/protocols/iso14443_3b/iso14443_3b_device_defs.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +extern const NfcDeviceBase nfc_device_iso14443_3b; diff --git a/lib/nfc/protocols/iso14443_3b/iso14443_3b_i.c b/lib/nfc/protocols/iso14443_3b/iso14443_3b_i.c new file mode 100644 index 00000000000..cac7ed82665 --- /dev/null +++ b/lib/nfc/protocols/iso14443_3b/iso14443_3b_i.c @@ -0,0 +1 @@ +#include "iso14443_3b_i.h" diff --git a/lib/nfc/protocols/iso14443_3b/iso14443_3b_i.h b/lib/nfc/protocols/iso14443_3b/iso14443_3b_i.h new file mode 100644 index 00000000000..f4ff36bd7e3 --- /dev/null +++ b/lib/nfc/protocols/iso14443_3b/iso14443_3b_i.h @@ -0,0 +1,37 @@ +#pragma once + +#include "iso14443_3b.h" + +#define ISO14443_3B_UID_SIZE (4U) +#define ISO14443_3B_APP_DATA_SIZE (4U) + +#define ISO14443_3B_GUARD_TIME_US (5000U) +#define ISO14443_3B_FDT_POLL_FC (9000U) +#define ISO14443_3B_POLL_POLL_MIN_US (1280U) + +#define ISO14443_3B_BIT_RATE_BOTH_106KBIT (0U << 0) +#define ISO14443_3B_BIT_RATE_PCD_TO_PICC_212KBIT (1U << 0) +#define ISO14443_3B_BIT_RATE_PCD_TO_PICC_424KBIT (1U << 1) +#define ISO14443_3B_BIT_RATE_PCD_TO_PICC_848KBIT (1U << 2) +#define ISO14443_3B_BIT_RATE_PICC_TO_PCD_212KBIT (1U << 4) +#define ISO14443_3B_BIT_RATE_PICC_TO_PCD_424KBIT (1U << 5) +#define ISO14443_3B_BIT_RATE_PICC_TO_PCD_848KBIT (1U << 6) +#define ISO14443_3B_BIT_RATE_BOTH_SAME_COMPULSORY (1U << 7) + +#define ISO14443_3B_FRAME_OPTION_NAD (1U << 1) +#define ISO14443_3B_FRAME_OPTION_CID (1U << 0) + +typedef struct { + uint8_t bit_rate_capability; + uint8_t protocol_type : 4; + uint8_t max_frame_size : 4; + uint8_t fo : 2; + uint8_t adc : 2; + uint8_t fwi : 4; +} Iso14443_3bProtocolInfo; + +struct Iso14443_3bData { + uint8_t uid[ISO14443_3B_UID_SIZE]; + uint8_t app_data[ISO14443_3B_APP_DATA_SIZE]; + Iso14443_3bProtocolInfo protocol_info; +}; diff --git a/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller.c b/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller.c new file mode 100644 index 00000000000..9507f28c410 --- /dev/null +++ b/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller.c @@ -0,0 +1,121 @@ +#include "iso14443_3b_poller_i.h" + +#include + +#include + +#define TAG "ISO14443_3bPoller" + +const Iso14443_3bData* iso14443_3b_poller_get_data(Iso14443_3bPoller* instance) { + furi_assert(instance); + furi_assert(instance->data); + + return instance->data; +} + +static Iso14443_3bPoller* iso14443_3b_poller_alloc(Nfc* nfc) { + furi_assert(nfc); + + Iso14443_3bPoller* instance = malloc(sizeof(Iso14443_3bPoller)); + instance->nfc = nfc; + instance->tx_buffer = bit_buffer_alloc(ISO14443_3B_POLLER_MAX_BUFFER_SIZE); + instance->rx_buffer = bit_buffer_alloc(ISO14443_3B_POLLER_MAX_BUFFER_SIZE); + + nfc_config(instance->nfc, NfcModePoller, NfcTechIso14443b); + nfc_set_guard_time_us(instance->nfc, ISO14443_3B_GUARD_TIME_US); + nfc_set_fdt_poll_fc(instance->nfc, ISO14443_3B_FDT_POLL_FC); + nfc_set_fdt_poll_poll_us(instance->nfc, ISO14443_3B_POLL_POLL_MIN_US); + instance->data = iso14443_3b_alloc(); + + instance->iso14443_3b_event.data = &instance->iso14443_3b_event_data; + instance->general_event.protocol = NfcProtocolIso14443_3b; + instance->general_event.event_data = &instance->iso14443_3b_event; + instance->general_event.instance = instance; + + return instance; +} + +static void iso14443_3b_poller_free(Iso14443_3bPoller* instance) { + furi_assert(instance); + + furi_assert(instance->tx_buffer); + furi_assert(instance->rx_buffer); + furi_assert(instance->data); + + bit_buffer_free(instance->tx_buffer); + bit_buffer_free(instance->rx_buffer); + iso14443_3b_free(instance->data); + free(instance); +} + +static void iso14443_3b_poller_set_callback( + Iso14443_3bPoller* instance, + NfcGenericCallback callback, + void* context) { + furi_assert(instance); + furi_assert(callback); + + instance->callback = callback; + instance->context = context; +} + +static NfcCommand iso14443_3b_poller_run(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.protocol == NfcProtocolInvalid); + furi_assert(event.event_data); + + Iso14443_3bPoller* instance = context; + NfcEvent* nfc_event = event.event_data; + NfcCommand command = NfcCommandContinue; + + if(nfc_event->type == NfcEventTypePollerReady) { + if(instance->state != Iso14443_3bPollerStateActivated) { + Iso14443_3bError error = iso14443_3b_poller_async_activate(instance, instance->data); + if(error == Iso14443_3bErrorNone) { + instance->iso14443_3b_event.type = Iso14443_3bPollerEventTypeReady; + instance->iso14443_3b_event_data.error = error; + command = instance->callback(instance->general_event, instance->context); + } else { + instance->iso14443_3b_event.type = Iso14443_3bPollerEventTypeError; + instance->iso14443_3b_event_data.error = error; + command = instance->callback(instance->general_event, instance->context); + // Add delay to switch context + furi_delay_ms(100); + } + } else { + instance->iso14443_3b_event.type = Iso14443_3bPollerEventTypeReady; + instance->iso14443_3b_event_data.error = Iso14443_3bErrorNone; + command = instance->callback(instance->general_event, instance->context); + } + } + + return command; +} + +static bool iso14443_3b_poller_detect(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.event_data); + furi_assert(event.instance); + furi_assert(event.protocol == NfcProtocolInvalid); + + bool protocol_detected = false; + Iso14443_3bPoller* instance = context; + NfcEvent* nfc_event = event.event_data; + furi_assert(instance->state == Iso14443_3bPollerStateIdle); + + if(nfc_event->type == NfcEventTypePollerReady) { + Iso14443_3bError error = iso14443_3b_poller_async_activate(instance, instance->data); + protocol_detected = (error == Iso14443_3bErrorNone); + } + + return protocol_detected; +} + +const NfcPollerBase nfc_poller_iso14443_3b = { + .alloc = (NfcPollerAlloc)iso14443_3b_poller_alloc, + .free = (NfcPollerFree)iso14443_3b_poller_free, + .set_callback = (NfcPollerSetCallback)iso14443_3b_poller_set_callback, + .run = (NfcPollerRun)iso14443_3b_poller_run, + .detect = (NfcPollerDetect)iso14443_3b_poller_detect, + .get_data = (NfcPollerGetData)iso14443_3b_poller_get_data, +}; diff --git a/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller.h b/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller.h new file mode 100644 index 00000000000..d25d9dbe9fe --- /dev/null +++ b/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller.h @@ -0,0 +1,30 @@ +#pragma once + +#include "iso14443_3b.h" +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct Iso14443_3bPoller Iso14443_3bPoller; + +typedef enum { + Iso14443_3bPollerEventTypeError, + Iso14443_3bPollerEventTypeReady, +} Iso14443_3bPollerEventType; + +typedef struct { + Iso14443_3bError error; +} Iso14443_3bPollerEventData; + +typedef struct { + Iso14443_3bPollerEventType type; + Iso14443_3bPollerEventData* data; +} Iso14443_3bPollerEvent; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller_defs.h b/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller_defs.h new file mode 100644 index 00000000000..60225310abe --- /dev/null +++ b/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller_defs.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +extern const NfcPollerBase nfc_poller_iso14443_3b; diff --git a/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller_i.c b/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller_i.c new file mode 100644 index 00000000000..95668ccf224 --- /dev/null +++ b/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller_i.c @@ -0,0 +1,194 @@ +#include "iso14443_3b_poller_i.h" + +#include + +#define TAG "Iso14443_3bPoller" + +#define ISO14443_3B_ATTRIB_FRAME_SIZE_256 (0x08) + +static Iso14443_3bError iso14443_3b_poller_process_error(NfcError error) { + switch(error) { + case NfcErrorNone: + return Iso14443_3bErrorNone; + case NfcErrorTimeout: + return Iso14443_3bErrorTimeout; + default: + return Iso14443_3bErrorNotPresent; + } +} + +static Iso14443_3bError iso14443_3b_poller_prepare_trx(Iso14443_3bPoller* instance) { + furi_assert(instance); + + if(instance->state == Iso14443_3bPollerStateIdle) { + return iso14443_3b_poller_async_activate(instance, NULL); + } + + return Iso14443_3bErrorNone; +} + +static Iso14443_3bError iso14443_3b_poller_frame_exchange( + Iso14443_3bPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt) { + furi_assert(instance); + + const size_t tx_bytes = bit_buffer_get_size_bytes(tx_buffer); + furi_assert( + tx_bytes <= bit_buffer_get_capacity_bytes(instance->tx_buffer) - ISO14443_CRC_SIZE); + + bit_buffer_copy(instance->tx_buffer, tx_buffer); + iso14443_crc_append(Iso14443CrcTypeB, instance->tx_buffer); + + Iso14443_3bError ret = Iso14443_3bErrorNone; + + do { + NfcError error = + nfc_poller_trx(instance->nfc, instance->tx_buffer, instance->rx_buffer, fwt); + if(error != NfcErrorNone) { + ret = iso14443_3b_poller_process_error(error); + break; + } + + bit_buffer_copy(rx_buffer, instance->rx_buffer); + if(!iso14443_crc_check(Iso14443CrcTypeB, instance->rx_buffer)) { + ret = Iso14443_3bErrorWrongCrc; + break; + } + + iso14443_crc_trim(rx_buffer); + } while(false); + + return ret; +} + +Iso14443_3bError + iso14443_3b_poller_async_activate(Iso14443_3bPoller* instance, Iso14443_3bData* data) { + furi_assert(instance); + furi_assert(instance->nfc); + + iso14443_3b_reset(data); + + Iso14443_3bError ret; + + do { + instance->state = Iso14443_3bPollerStateColResInProgress; + + bit_buffer_reset(instance->tx_buffer); + bit_buffer_reset(instance->rx_buffer); + + // Send REQB + bit_buffer_append_byte(instance->tx_buffer, 0x05); + bit_buffer_append_byte(instance->tx_buffer, 0x00); + bit_buffer_append_byte(instance->tx_buffer, 0x08); + + ret = iso14443_3b_poller_frame_exchange( + instance, instance->tx_buffer, instance->rx_buffer, ISO14443_3B_FDT_POLL_FC); + if(ret != Iso14443_3bErrorNone) { + instance->state = Iso14443_3bPollerStateColResFailed; + break; + } + + typedef struct { + uint8_t flag; + uint8_t uid[ISO14443_3B_UID_SIZE]; + uint8_t app_data[ISO14443_3B_APP_DATA_SIZE]; + Iso14443_3bProtocolInfo protocol_info; + } Iso14443_3bAtqBLayout; + + if(bit_buffer_get_size_bytes(instance->rx_buffer) != sizeof(Iso14443_3bAtqBLayout)) { + FURI_LOG_D(TAG, "Unexpected REQB response"); + instance->state = Iso14443_3bPollerStateColResFailed; + ret = Iso14443_3bErrorCommunication; + break; + } + + instance->state = Iso14443_3bPollerStateActivationInProgress; + + const Iso14443_3bAtqBLayout* atqb = + (const Iso14443_3bAtqBLayout*)bit_buffer_get_data(instance->rx_buffer); + + memcpy(data->uid, atqb->uid, ISO14443_3B_UID_SIZE); + memcpy(data->app_data, atqb->app_data, ISO14443_3B_APP_DATA_SIZE); + + data->protocol_info = atqb->protocol_info; + + bit_buffer_reset(instance->tx_buffer); + bit_buffer_reset(instance->rx_buffer); + + // Send ATTRIB + bit_buffer_append_byte(instance->tx_buffer, 0x1d); + bit_buffer_append_bytes(instance->tx_buffer, data->uid, ISO14443_3B_UID_SIZE); + bit_buffer_append_byte(instance->tx_buffer, 0x00); + bit_buffer_append_byte(instance->tx_buffer, ISO14443_3B_ATTRIB_FRAME_SIZE_256); + bit_buffer_append_byte(instance->tx_buffer, 0x01); + bit_buffer_append_byte(instance->tx_buffer, 0x00); + + ret = iso14443_3b_poller_frame_exchange( + instance, instance->tx_buffer, instance->rx_buffer, iso14443_3b_get_fwt_fc_max(data)); + if(ret != Iso14443_3bErrorNone) { + instance->state = Iso14443_3bPollerStateActivationFailed; + break; + } + + if(bit_buffer_get_size_bytes(instance->rx_buffer) != 1 || + bit_buffer_get_byte(instance->rx_buffer, 0) != 0) { + FURI_LOG_D(TAG, "Unexpected ATTRIB response"); + instance->state = Iso14443_3bPollerStateActivationFailed; + ret = Iso14443_3bErrorCommunication; + break; + } + + instance->state = Iso14443_3bPollerStateActivated; + } while(false); + + return ret; +} + +Iso14443_3bError iso14443_3b_poller_halt(Iso14443_3bPoller* instance) { + furi_assert(instance); + + bit_buffer_reset(instance->tx_buffer); + bit_buffer_reset(instance->rx_buffer); + + bit_buffer_append_byte(instance->tx_buffer, 0x50); + bit_buffer_append_bytes(instance->tx_buffer, instance->data->uid, ISO14443_3B_UID_SIZE); + + Iso14443_3bError ret; + + do { + ret = iso14443_3b_poller_frame_exchange( + instance, instance->tx_buffer, instance->rx_buffer, ISO14443_3B_FDT_POLL_FC); + if(ret != Iso14443_3bErrorNone) { + break; + } + + if(bit_buffer_get_size_bytes(instance->rx_buffer) != sizeof(uint8_t) || + bit_buffer_get_byte(instance->rx_buffer, 0) != 0) { + ret = Iso14443_3bErrorCommunication; + break; + } + + instance->state = Iso14443_3bPollerStateIdle; + } while(false); + + return ret; +} + +Iso14443_3bError iso14443_3b_poller_send_frame( + Iso14443_3bPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer) { + Iso14443_3bError ret; + + do { + ret = iso14443_3b_poller_prepare_trx(instance); + if(ret != Iso14443_3bErrorNone) break; + + ret = iso14443_3b_poller_frame_exchange( + instance, tx_buffer, rx_buffer, iso14443_3b_get_fwt_fc_max(instance->data)); + } while(false); + + return ret; +} diff --git a/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller_i.h b/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller_i.h new file mode 100644 index 00000000000..5821d6373f5 --- /dev/null +++ b/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller_i.h @@ -0,0 +1,49 @@ +#pragma once + +#include "iso14443_3b_poller.h" +#include "iso14443_3b_i.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define ISO14443_3B_POLLER_MAX_BUFFER_SIZE (256U) + +typedef enum { + Iso14443_3bPollerStateIdle, + Iso14443_3bPollerStateColResInProgress, + Iso14443_3bPollerStateColResFailed, + Iso14443_3bPollerStateActivationInProgress, + Iso14443_3bPollerStateActivationFailed, + Iso14443_3bPollerStateActivated, +} Iso14443_3bPollerState; + +struct Iso14443_3bPoller { + Nfc* nfc; + Iso14443_3bPollerState state; + Iso14443_3bData* data; + BitBuffer* tx_buffer; + BitBuffer* rx_buffer; + + NfcGenericEvent general_event; + Iso14443_3bPollerEvent iso14443_3b_event; + Iso14443_3bPollerEventData iso14443_3b_event_data; + NfcGenericCallback callback; + void* context; +}; + +const Iso14443_3bData* iso14443_3b_poller_get_data(Iso14443_3bPoller* instance); + +Iso14443_3bError + iso14443_3b_poller_async_activate(Iso14443_3bPoller* instance, Iso14443_3bData* data); + +Iso14443_3bError iso14443_3b_poller_halt(Iso14443_3bPoller* instance); + +Iso14443_3bError iso14443_3b_poller_send_frame( + Iso14443_3bPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a.c b/lib/nfc/protocols/iso14443_4a/iso14443_4a.c new file mode 100644 index 00000000000..9c2a530d53c --- /dev/null +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a.c @@ -0,0 +1,300 @@ +#include "iso14443_4a_i.h" + +#include + +#define ISO14443_4A_PROTOCOL_NAME "ISO14443-4A" +#define ISO14443_4A_DEVICE_NAME "ISO14443-4A (Unknown)" + +#define ISO14443_4A_T0_KEY "T0" +#define ISO14443_4A_TA1_KEY "TA(1)" +#define ISO14443_4A_TB1_KEY "TB(1)" +#define ISO14443_4A_TC1_KEY "TC(1)" +#define ISO14443_4A_T1_TK_KEY "T1...Tk" + +#define ISO14443_4A_FDT_DEFAULT_FC ISO14443_3A_FDT_POLL_FC + +typedef enum { + Iso14443_4aInterfaceByteTA1, + Iso14443_4aInterfaceByteTB1, + Iso14443_4aInterfaceByteTC1, +} Iso14443_4aInterfaceByte; + +const NfcDeviceBase nfc_device_iso14443_4a = { + .protocol_name = ISO14443_4A_PROTOCOL_NAME, + .alloc = (NfcDeviceAlloc)iso14443_4a_alloc, + .free = (NfcDeviceFree)iso14443_4a_free, + .reset = (NfcDeviceReset)iso14443_4a_reset, + .copy = (NfcDeviceCopy)iso14443_4a_copy, + .verify = (NfcDeviceVerify)iso14443_4a_verify, + .load = (NfcDeviceLoad)iso14443_4a_load, + .save = (NfcDeviceSave)iso14443_4a_save, + .is_equal = (NfcDeviceEqual)iso14443_4a_is_equal, + .get_name = (NfcDeviceGetName)iso14443_4a_get_device_name, + .get_uid = (NfcDeviceGetUid)iso14443_4a_get_uid, + .set_uid = (NfcDeviceSetUid)iso14443_4a_set_uid, + .get_base_data = (NfcDeviceGetBaseData)iso14443_4a_get_base_data, +}; + +Iso14443_4aData* iso14443_4a_alloc() { + Iso14443_4aData* data = malloc(sizeof(Iso14443_4aData)); + + data->iso14443_3a_data = iso14443_3a_alloc(); + data->ats_data.t1_tk = simple_array_alloc(&simple_array_config_uint8_t); + + return data; +} + +void iso14443_4a_free(Iso14443_4aData* data) { + furi_assert(data); + + simple_array_free(data->ats_data.t1_tk); + iso14443_3a_free(data->iso14443_3a_data); + + free(data); +} + +void iso14443_4a_reset(Iso14443_4aData* data) { + furi_assert(data); + + iso14443_3a_reset(data->iso14443_3a_data); + + data->ats_data.tl = 1; + data->ats_data.t0 = 0; + data->ats_data.ta_1 = 0; + data->ats_data.tb_1 = 0; + data->ats_data.tc_1 = 0; + + simple_array_reset(data->ats_data.t1_tk); +} + +void iso14443_4a_copy(Iso14443_4aData* data, const Iso14443_4aData* other) { + furi_assert(data); + furi_assert(other); + + iso14443_3a_copy(data->iso14443_3a_data, other->iso14443_3a_data); + + data->ats_data.tl = other->ats_data.tl; + data->ats_data.t0 = other->ats_data.t0; + data->ats_data.ta_1 = other->ats_data.ta_1; + data->ats_data.tb_1 = other->ats_data.tb_1; + data->ats_data.tc_1 = other->ats_data.tc_1; + + simple_array_copy(data->ats_data.t1_tk, other->ats_data.t1_tk); +} + +bool iso14443_4a_verify(Iso14443_4aData* data, const FuriString* device_type) { + UNUSED(data); + UNUSED(device_type); + + // Empty, unified file format only + return false; +} + +bool iso14443_4a_load(Iso14443_4aData* data, FlipperFormat* ff, uint32_t version) { + furi_assert(data); + + bool parsed = false; + + do { + if(!iso14443_3a_load(data->iso14443_3a_data, ff, version)) break; + + Iso14443_4aAtsData* ats_data = &data->ats_data; + + ats_data->tl = 1; + + if(flipper_format_key_exist(ff, ISO14443_4A_T0_KEY)) { + if(!flipper_format_read_hex(ff, ISO14443_4A_T0_KEY, &ats_data->t0, 1)) break; + ++ats_data->tl; + } + + if(ats_data->t0 & ISO14443_4A_ATS_T0_TA1) { + if(!flipper_format_key_exist(ff, ISO14443_4A_TA1_KEY)) break; + if(!flipper_format_read_hex(ff, ISO14443_4A_TA1_KEY, &ats_data->ta_1, 1)) break; + ++ats_data->tl; + } + if(ats_data->t0 & ISO14443_4A_ATS_T0_TB1) { + if(!flipper_format_key_exist(ff, ISO14443_4A_TB1_KEY)) break; + if(!flipper_format_read_hex(ff, ISO14443_4A_TB1_KEY, &ats_data->tb_1, 1)) break; + ++ats_data->tl; + } + if(ats_data->t0 & ISO14443_4A_ATS_T0_TC1) { + if(!flipper_format_key_exist(ff, ISO14443_4A_TC1_KEY)) break; + if(!flipper_format_read_hex(ff, ISO14443_4A_TC1_KEY, &ats_data->tc_1, 1)) break; + ++ats_data->tl; + } + + if(flipper_format_key_exist(ff, ISO14443_4A_T1_TK_KEY)) { + uint32_t t1_tk_size; + if(!flipper_format_get_value_count(ff, ISO14443_4A_T1_TK_KEY, &t1_tk_size)) break; + + if(t1_tk_size > 0) { + simple_array_init(ats_data->t1_tk, t1_tk_size); + if(!flipper_format_read_hex( + ff, + ISO14443_4A_T1_TK_KEY, + simple_array_get_data(ats_data->t1_tk), + t1_tk_size)) + break; + ats_data->tl += t1_tk_size; + } + } + parsed = true; + } while(false); + + return parsed; +} + +bool iso14443_4a_save(const Iso14443_4aData* data, FlipperFormat* ff) { + furi_assert(data); + + bool saved = false; + + do { + if(!iso14443_3a_save(data->iso14443_3a_data, ff)) break; + if(!flipper_format_write_comment_cstr(ff, ISO14443_4A_PROTOCOL_NAME " specific data")) + break; + + const Iso14443_4aAtsData* ats_data = &data->ats_data; + + if(ats_data->tl > 1) { + if(!flipper_format_write_hex(ff, ISO14443_4A_T0_KEY, &ats_data->t0, 1)) break; + + if(ats_data->t0 & ISO14443_4A_ATS_T0_TA1) { + if(!flipper_format_write_hex(ff, ISO14443_4A_TA1_KEY, &ats_data->ta_1, 1)) break; + } + if(ats_data->t0 & ISO14443_4A_ATS_T0_TB1) { + if(!flipper_format_write_hex(ff, ISO14443_4A_TB1_KEY, &ats_data->tb_1, 1)) break; + } + if(ats_data->t0 & ISO14443_4A_ATS_T0_TC1) { + if(!flipper_format_write_hex(ff, ISO14443_4A_TC1_KEY, &ats_data->tc_1, 1)) break; + } + + const uint32_t t1_tk_size = simple_array_get_count(ats_data->t1_tk); + if(t1_tk_size > 0) { + if(!flipper_format_write_hex( + ff, + ISO14443_4A_T1_TK_KEY, + simple_array_cget_data(ats_data->t1_tk), + t1_tk_size)) + break; + } + } + saved = true; + } while(false); + + return saved; +} + +bool iso14443_4a_is_equal(const Iso14443_4aData* data, const Iso14443_4aData* other) { + return iso14443_3a_is_equal(data->iso14443_3a_data, other->iso14443_3a_data); +} + +const char* iso14443_4a_get_device_name(const Iso14443_4aData* data, NfcDeviceNameType name_type) { + UNUSED(data); + UNUSED(name_type); + return ISO14443_4A_DEVICE_NAME; +} + +const uint8_t* iso14443_4a_get_uid(const Iso14443_4aData* data, size_t* uid_len) { + return iso14443_3a_get_uid(data->iso14443_3a_data, uid_len); +} + +bool iso14443_4a_set_uid(Iso14443_4aData* data, const uint8_t* uid, size_t uid_len) { + furi_assert(data); + + return iso14443_3a_set_uid(data->iso14443_3a_data, uid, uid_len); +} + +Iso14443_3aData* iso14443_4a_get_base_data(const Iso14443_4aData* data) { + furi_assert(data); + + return data->iso14443_3a_data; +} + +uint16_t iso14443_4a_get_frame_size_max(const Iso14443_4aData* data) { + furi_assert(data); + + const uint8_t fsci = data->ats_data.t0 & 0x0F; + + if(fsci < 5) { + return fsci * 8 + 16; + } else if(fsci == 5) { + return 64; + } else if(fsci == 6) { + return 96; + } else if(fsci < 13) { + return 128U << (fsci - 7); + } else { + return 0; + } +} + +uint32_t iso14443_4a_get_fwt_fc_max(const Iso14443_4aData* data) { + furi_assert(data); + + uint32_t fwt_fc_max = ISO14443_4A_FDT_DEFAULT_FC; + + do { + if(!(data->ats_data.tl > 1)) break; + if(!(data->ats_data.t0 & ISO14443_4A_ATS_T0_TB1)) break; + + const uint8_t fwi = data->ats_data.tb_1 >> 4; + if(fwi == 0x0F) break; + + fwt_fc_max = 4096UL << fwi; + } while(false); + + return fwt_fc_max; +} + +const uint8_t* iso14443_4a_get_historical_bytes(const Iso14443_4aData* data, uint32_t* count) { + furi_assert(data); + furi_assert(count); + + *count = simple_array_get_count(data->ats_data.t1_tk); + return simple_array_cget_data(data->ats_data.t1_tk); +} + +bool iso14443_4a_supports_bit_rate(const Iso14443_4aData* data, Iso14443_4aBitRate bit_rate) { + furi_assert(data); + + if(!(data->ats_data.t0 & ISO14443_4A_ATS_T0_TA1)) + return bit_rate == Iso14443_4aBitRateBoth106Kbit; + + const uint8_t ta_1 = data->ats_data.ta_1; + + switch(bit_rate) { + case Iso14443_4aBitRateBoth106Kbit: + return ta_1 == ISO14443_4A_ATS_TA1_BOTH_SAME_COMPULSORY; + case Iso14443_4aBitRatePiccToPcd212Kbit: + return ta_1 & ISO14443_4A_ATS_TA1_PCD_TO_PICC_212KBIT; + case Iso14443_4aBitRatePiccToPcd424Kbit: + return ta_1 & ISO14443_4A_ATS_TA1_PCD_TO_PICC_424KBIT; + case Iso14443_4aBitRatePiccToPcd848Kbit: + return ta_1 & ISO14443_4A_ATS_TA1_PCD_TO_PICC_848KBIT; + case Iso14443_4aBitRatePcdToPicc212Kbit: + return ta_1 & ISO14443_4A_ATS_TA1_PICC_TO_PCD_212KBIT; + case Iso14443_4aBitRatePcdToPicc424Kbit: + return ta_1 & ISO14443_4A_ATS_TA1_PICC_TO_PCD_424KBIT; + case Iso14443_4aBitRatePcdToPicc848Kbit: + return ta_1 & ISO14443_4A_ATS_TA1_PICC_TO_PCD_848KBIT; + default: + return false; + } +} + +bool iso14443_4a_supports_frame_option(const Iso14443_4aData* data, Iso14443_4aFrameOption option) { + furi_assert(data); + + const Iso14443_4aAtsData* ats_data = &data->ats_data; + if(!(ats_data->t0 & ISO14443_4A_ATS_T0_TC1)) return false; + + switch(option) { + case Iso14443_4aFrameOptionNad: + return ats_data->tc_1 & ISO14443_4A_ATS_TC1_NAD; + case Iso14443_4aFrameOptionCid: + return ats_data->tc_1 & ISO14443_4A_ATS_TC1_CID; + default: + return false; + } +} diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a.h b/lib/nfc/protocols/iso14443_4a/iso14443_4a.h new file mode 100644 index 00000000000..9580c140409 --- /dev/null +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a.h @@ -0,0 +1,73 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + Iso14443_4aErrorNone, + Iso14443_4aErrorNotPresent, + Iso14443_4aErrorProtocol, + Iso14443_4aErrorTimeout, +} Iso14443_4aError; + +typedef enum { + Iso14443_4aBitRateBoth106Kbit, + Iso14443_4aBitRatePiccToPcd212Kbit, + Iso14443_4aBitRatePiccToPcd424Kbit, + Iso14443_4aBitRatePiccToPcd848Kbit, + Iso14443_4aBitRatePcdToPicc212Kbit, + Iso14443_4aBitRatePcdToPicc424Kbit, + Iso14443_4aBitRatePcdToPicc848Kbit, +} Iso14443_4aBitRate; + +typedef enum { + Iso14443_4aFrameOptionNad, + Iso14443_4aFrameOptionCid, +} Iso14443_4aFrameOption; + +typedef struct Iso14443_4aData Iso14443_4aData; + +// Virtual methods + +Iso14443_4aData* iso14443_4a_alloc(); + +void iso14443_4a_free(Iso14443_4aData* data); + +void iso14443_4a_reset(Iso14443_4aData* data); + +void iso14443_4a_copy(Iso14443_4aData* data, const Iso14443_4aData* other); + +bool iso14443_4a_verify(Iso14443_4aData* data, const FuriString* device_type); + +bool iso14443_4a_load(Iso14443_4aData* data, FlipperFormat* ff, uint32_t version); + +bool iso14443_4a_save(const Iso14443_4aData* data, FlipperFormat* ff); + +bool iso14443_4a_is_equal(const Iso14443_4aData* data, const Iso14443_4aData* other); + +const char* iso14443_4a_get_device_name(const Iso14443_4aData* data, NfcDeviceNameType name_type); + +const uint8_t* iso14443_4a_get_uid(const Iso14443_4aData* data, size_t* uid_len); + +bool iso14443_4a_set_uid(Iso14443_4aData* data, const uint8_t* uid, size_t uid_len); + +Iso14443_3aData* iso14443_4a_get_base_data(const Iso14443_4aData* data); + +// Getters & Tests + +uint16_t iso14443_4a_get_frame_size_max(const Iso14443_4aData* data); + +uint32_t iso14443_4a_get_fwt_fc_max(const Iso14443_4aData* data); + +const uint8_t* iso14443_4a_get_historical_bytes(const Iso14443_4aData* data, uint32_t* count); + +bool iso14443_4a_supports_bit_rate(const Iso14443_4aData* data, Iso14443_4aBitRate bit_rate); + +bool iso14443_4a_supports_frame_option(const Iso14443_4aData* data, Iso14443_4aFrameOption option); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_device_defs.h b/lib/nfc/protocols/iso14443_4a/iso14443_4a_device_defs.h new file mode 100644 index 00000000000..db372f81030 --- /dev/null +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_device_defs.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +extern const NfcDeviceBase nfc_device_iso14443_4a; diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_i.c b/lib/nfc/protocols/iso14443_4a/iso14443_4a_i.c new file mode 100644 index 00000000000..f6e3acc5c1f --- /dev/null +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_i.c @@ -0,0 +1,71 @@ +#include "iso14443_4a_i.h" + +bool iso14443_4a_ats_parse(Iso14443_4aAtsData* data, const BitBuffer* buf) { + bool can_parse = false; + + do { + const size_t buf_size = bit_buffer_get_size_bytes(buf); + if(buf_size == 0) break; + + size_t current_index = 0; + + const uint8_t tl = bit_buffer_get_byte(buf, current_index++); + if(tl != buf_size) break; + + data->tl = tl; + + if(tl > 1) { + const uint8_t t0 = bit_buffer_get_byte(buf, current_index++); + + const bool has_ta_1 = t0 & ISO14443_4A_ATS_T0_TA1; + const bool has_tb_1 = t0 & ISO14443_4A_ATS_T0_TB1; + const bool has_tc_1 = t0 & ISO14443_4A_ATS_T0_TC1; + + const uint8_t buf_size_min = + 2 + (has_ta_1 ? 1 : 0) + (has_tb_1 ? 1 : 0) + (has_tc_1 ? 1 : 0); + + if(buf_size < buf_size_min) break; + + data->t0 = t0; + + if(has_ta_1) { + data->ta_1 = bit_buffer_get_byte(buf, current_index++); + } + if(has_tb_1) { + data->tb_1 = bit_buffer_get_byte(buf, current_index++); + } + if(has_tc_1) { + data->tc_1 = bit_buffer_get_byte(buf, current_index++); + } + + const uint8_t t1_tk_size = buf_size - buf_size_min; + + if(t1_tk_size > 0) { + simple_array_init(data->t1_tk, t1_tk_size); + bit_buffer_write_bytes_mid( + buf, simple_array_get_data(data->t1_tk), current_index, t1_tk_size); + } + } + + can_parse = true; + } while(false); + + return can_parse; +} + +Iso14443_4aError iso14443_4a_process_error(Iso14443_3aError error) { + switch(error) { + case Iso14443_3aErrorNone: + return Iso14443_4aErrorNone; + case Iso14443_3aErrorNotPresent: + return Iso14443_4aErrorNotPresent; + case Iso14443_3aErrorColResFailed: + case Iso14443_3aErrorCommunication: + case Iso14443_3aErrorWrongCrc: + return Iso14443_4aErrorProtocol; + case Iso14443_3aErrorTimeout: + return Iso14443_4aErrorTimeout; + default: + return Iso14443_4aErrorProtocol; + } +} diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_i.h b/lib/nfc/protocols/iso14443_4a/iso14443_4a_i.h new file mode 100644 index 00000000000..e45fb90cca0 --- /dev/null +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_i.h @@ -0,0 +1,42 @@ +#pragma once + +#include "iso14443_4a.h" + +#include + +#define ISO14443_4A_CMD_READ_ATS (0xE0) + +// ATS bit definitions +#define ISO14443_4A_ATS_T0_TA1 (1U << 4) +#define ISO14443_4A_ATS_T0_TB1 (1U << 5) +#define ISO14443_4A_ATS_T0_TC1 (1U << 6) + +#define ISO14443_4A_ATS_TA1_BOTH_106KBIT (0U << 0) +#define ISO14443_4A_ATS_TA1_PCD_TO_PICC_212KBIT (1U << 0) +#define ISO14443_4A_ATS_TA1_PCD_TO_PICC_424KBIT (1U << 1) +#define ISO14443_4A_ATS_TA1_PCD_TO_PICC_848KBIT (1U << 2) +#define ISO14443_4A_ATS_TA1_PICC_TO_PCD_212KBIT (1U << 4) +#define ISO14443_4A_ATS_TA1_PICC_TO_PCD_424KBIT (1U << 5) +#define ISO14443_4A_ATS_TA1_PICC_TO_PCD_848KBIT (1U << 6) +#define ISO14443_4A_ATS_TA1_BOTH_SAME_COMPULSORY (1U << 7) + +#define ISO14443_4A_ATS_TC1_NAD (1U << 0) +#define ISO14443_4A_ATS_TC1_CID (1U << 1) + +typedef struct { + uint8_t tl; + uint8_t t0; + uint8_t ta_1; + uint8_t tb_1; + uint8_t tc_1; + SimpleArray* t1_tk; +} Iso14443_4aAtsData; + +struct Iso14443_4aData { + Iso14443_3aData* iso14443_3a_data; + Iso14443_4aAtsData ats_data; +}; + +bool iso14443_4a_ats_parse(Iso14443_4aAtsData* data, const BitBuffer* buf); + +Iso14443_4aError iso14443_4a_process_error(Iso14443_3aError error); diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.c b/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.c new file mode 100644 index 00000000000..95612bf54d0 --- /dev/null +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.c @@ -0,0 +1,99 @@ +#include "iso14443_4a_listener_i.h" + +#include +#include + +#define TAG "Iso14443_4aListener" + +#define ISO14443_4A_LISTENER_BUF_SIZE (256U) + +static Iso14443_4aListener* + iso14443_4a_listener_alloc(Iso14443_3aListener* iso14443_3a_listener, Iso14443_4aData* data) { + furi_assert(iso14443_3a_listener); + + Iso14443_4aListener* instance = malloc(sizeof(Iso14443_4aListener)); + instance->iso14443_3a_listener = iso14443_3a_listener; + instance->data = data; + + instance->tx_buffer = bit_buffer_alloc(ISO14443_4A_LISTENER_BUF_SIZE); + + instance->iso14443_4a_event.data = &instance->iso14443_4a_event_data; + instance->generic_event.protocol = NfcProtocolIso14443_4a; + instance->generic_event.instance = instance; + instance->generic_event.event_data = &instance->iso14443_4a_event; + + return instance; +} + +static void iso14443_4a_listener_free(Iso14443_4aListener* instance) { + furi_assert(instance); + furi_assert(instance->data); + furi_assert(instance->tx_buffer); + + bit_buffer_free(instance->tx_buffer); + free(instance); +} + +static void iso14443_4a_listener_set_callback( + Iso14443_4aListener* instance, + NfcGenericCallback callback, + void* context) { + furi_assert(instance); + + instance->callback = callback; + instance->context = context; +} + +static const Iso14443_4aData* iso14443_4a_listener_get_data(Iso14443_4aListener* instance) { + furi_assert(instance); + furi_assert(instance->data); + + return instance->data; +} + +static NfcCommand iso14443_4a_listener_run(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.protocol == NfcProtocolIso14443_3a); + furi_assert(event.event_data); + + Iso14443_4aListener* instance = context; + Iso14443_3aListenerEvent* iso14443_3a_event = event.event_data; + BitBuffer* rx_buffer = iso14443_3a_event->data->buffer; + NfcCommand command = NfcCommandContinue; + + if(iso14443_3a_event->type == Iso14443_3aListenerEventTypeReceivedStandardFrame) { + if(instance->state == Iso14443_4aListenerStateIdle) { + if(bit_buffer_get_size_bytes(rx_buffer) == 2 && + bit_buffer_get_byte(rx_buffer, 0) == ISO14443_4A_CMD_READ_ATS) { + if(iso14443_4a_listener_send_ats(instance, &instance->data->ats_data) != + Iso14443_4aErrorNone) { + command = NfcCommandContinue; + } else { + instance->state = Iso14443_4aListenerStateActive; + } + } + } else { + instance->iso14443_4a_event.type = Iso14443_4aListenerEventTypeReceivedData; + instance->iso14443_4a_event.data->buffer = rx_buffer; + + if(instance->callback) { + command = instance->callback(instance->generic_event, instance->context); + } + } + } else if( + iso14443_3a_event->type == Iso14443_3aListenerEventTypeHalted || + iso14443_3a_event->type == Iso14443_3aListenerEventTypeFieldOff) { + instance->state = Iso14443_4aListenerStateIdle; + command = NfcCommandContinue; + } + + return command; +} + +const NfcListenerBase nfc_listener_iso14443_4a = { + .alloc = (NfcListenerAlloc)iso14443_4a_listener_alloc, + .free = (NfcListenerFree)iso14443_4a_listener_free, + .set_callback = (NfcListenerSetCallback)iso14443_4a_listener_set_callback, + .get_data = (NfcListenerGetData)iso14443_4a_listener_get_data, + .run = (NfcListenerRun)iso14443_4a_listener_run, +}; diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.h b/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.h new file mode 100644 index 00000000000..ba649847b27 --- /dev/null +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +#include "iso14443_4a.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct Iso14443_4aListener Iso14443_4aListener; + +typedef enum { + Iso14443_4aListenerEventTypeHalted, + Iso14443_4aListenerEventTypeReceivedData, +} Iso14443_4aListenerEventType; + +typedef struct { + BitBuffer* buffer; +} Iso14443_4aListenerEventData; + +typedef struct { + Iso14443_4aListenerEventType type; + Iso14443_4aListenerEventData* data; +} Iso14443_4aListenerEvent; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener_defs.h b/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener_defs.h new file mode 100644 index 00000000000..ef70a5e1415 --- /dev/null +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener_defs.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +extern const NfcListenerBase nfc_listener_iso14443_4a; diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener_i.c b/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener_i.c new file mode 100644 index 00000000000..8590c22ade5 --- /dev/null +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener_i.c @@ -0,0 +1,32 @@ +#include "iso14443_4a_listener_i.h" + +#include + +Iso14443_4aError + iso14443_4a_listener_send_ats(Iso14443_4aListener* instance, const Iso14443_4aAtsData* data) { + bit_buffer_reset(instance->tx_buffer); + bit_buffer_append_byte(instance->tx_buffer, data->tl); + + if(data->tl > 1) { + bit_buffer_append_byte(instance->tx_buffer, data->t0); + if(data->t0 & ISO14443_4A_ATS_T0_TA1) { + bit_buffer_append_byte(instance->tx_buffer, data->ta_1); + } + if(data->t0 & ISO14443_4A_ATS_T0_TB1) { + bit_buffer_append_byte(instance->tx_buffer, data->tb_1); + } + if(data->t0 & ISO14443_4A_ATS_T0_TC1) { + bit_buffer_append_byte(instance->tx_buffer, data->tc_1); + } + + const uint32_t t1_tk_size = simple_array_get_count(data->t1_tk); + if(t1_tk_size != 0) { + bit_buffer_append_bytes( + instance->tx_buffer, simple_array_cget_data(data->t1_tk), t1_tk_size); + } + } + + const Iso14443_3aError error = iso14443_3a_listener_send_standard_frame( + instance->iso14443_3a_listener, instance->tx_buffer); + return iso14443_4a_process_error(error); +} diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener_i.h b/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener_i.h new file mode 100644 index 00000000000..d4e884f6f05 --- /dev/null +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener_i.h @@ -0,0 +1,36 @@ +#pragma once + +#include + +#include "iso14443_4a_listener.h" +#include "iso14443_4a_i.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + Iso14443_4aListenerStateIdle, + Iso14443_4aListenerStateActive, +} Iso14443_4aListenerState; + +struct Iso14443_4aListener { + Iso14443_3aListener* iso14443_3a_listener; + Iso14443_4aData* data; + Iso14443_4aListenerState state; + + BitBuffer* tx_buffer; + + NfcGenericEvent generic_event; + Iso14443_4aListenerEvent iso14443_4a_event; + Iso14443_4aListenerEventData iso14443_4a_event_data; + NfcGenericCallback callback; + void* context; +}; + +Iso14443_4aError + iso14443_4a_listener_send_ats(Iso14443_4aListener* instance, const Iso14443_4aAtsData* data); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.c b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.c new file mode 100644 index 00000000000..c07cc6b7f0e --- /dev/null +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.c @@ -0,0 +1,154 @@ +#include "iso14443_4a_poller_i.h" + +#include + +#include + +#define TAG "Iso14443_4aPoller" + +#define ISO14443_4A_POLLER_BUF_SIZE (256U) + +typedef NfcCommand (*Iso14443_4aPollerStateHandler)(Iso14443_4aPoller* instance); + +const Iso14443_4aData* iso14443_4a_poller_get_data(Iso14443_4aPoller* instance) { + furi_assert(instance); + + return instance->data; +} + +static Iso14443_4aPoller* iso14443_4a_poller_alloc(Iso14443_3aPoller* iso14443_3a_poller) { + Iso14443_4aPoller* instance = malloc(sizeof(Iso14443_4aPoller)); + instance->iso14443_3a_poller = iso14443_3a_poller; + instance->data = iso14443_4a_alloc(); + instance->iso14443_4_layer = iso14443_4_layer_alloc(); + instance->tx_buffer = bit_buffer_alloc(ISO14443_4A_POLLER_BUF_SIZE); + instance->rx_buffer = bit_buffer_alloc(ISO14443_4A_POLLER_BUF_SIZE); + + instance->iso14443_4a_event.data = &instance->iso14443_4a_event_data; + + instance->general_event.protocol = NfcProtocolIso14443_4a; + instance->general_event.event_data = &instance->iso14443_4a_event; + instance->general_event.instance = instance; + + return instance; +} + +static void iso14443_4a_poller_free(Iso14443_4aPoller* instance) { + furi_assert(instance); + + iso14443_4a_free(instance->data); + iso14443_4_layer_free(instance->iso14443_4_layer); + bit_buffer_free(instance->tx_buffer); + bit_buffer_free(instance->rx_buffer); + free(instance); +} + +static NfcCommand iso14443_4a_poller_handler_idle(Iso14443_4aPoller* instance) { + iso14443_3a_copy( + instance->data->iso14443_3a_data, + iso14443_3a_poller_get_data(instance->iso14443_3a_poller)); + + iso14443_4_layer_reset(instance->iso14443_4_layer); + + instance->poller_state = Iso14443_4aPollerStateReadAts; + return NfcCommandContinue; +} + +static NfcCommand iso14443_4a_poller_handler_read_ats(Iso14443_4aPoller* instance) { + Iso14443_4aError error = + iso14443_4a_poller_async_read_ats(instance, &instance->data->ats_data); + if(error == Iso14443_4aErrorNone) { + FURI_LOG_D(TAG, "Read ATS success"); + instance->poller_state = Iso14443_4aPollerStateReady; + } else { + FURI_LOG_D(TAG, "Failed to read ATS"); + instance->poller_state = Iso14443_4aPollerStateError; + } + + return NfcCommandContinue; +} + +static NfcCommand iso14443_4a_poller_handler_error(Iso14443_4aPoller* instance) { + iso14443_3a_poller_halt(instance->iso14443_3a_poller); + instance->iso14443_4a_event_data.error = instance->error; + NfcCommand command = instance->callback(instance->general_event, instance->context); + instance->poller_state = Iso14443_4aPollerStateIdle; + return command; +} + +static NfcCommand iso14443_4a_poller_handler_ready(Iso14443_4aPoller* instance) { + instance->iso14443_4a_event.type = Iso14443_4aPollerEventTypeReady; + NfcCommand command = instance->callback(instance->general_event, instance->context); + return command; +} + +static const Iso14443_4aPollerStateHandler + iso14443_4a_poller_state_handler[Iso14443_4aPollerStateNum] = { + [Iso14443_4aPollerStateIdle] = iso14443_4a_poller_handler_idle, + [Iso14443_4aPollerStateReadAts] = iso14443_4a_poller_handler_read_ats, + [Iso14443_4aPollerStateError] = iso14443_4a_poller_handler_error, + [Iso14443_4aPollerStateReady] = iso14443_4a_poller_handler_ready, +}; + +static void iso14443_4a_poller_set_callback( + Iso14443_4aPoller* instance, + NfcGenericCallback callback, + void* context) { + furi_assert(instance); + furi_assert(callback); + + instance->callback = callback; + instance->context = context; +} + +static NfcCommand iso14443_4a_poller_run(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolIso14443_3a); + + Iso14443_4aPoller* instance = context; + furi_assert(instance); + furi_assert(instance->callback); + + Iso14443_3aPollerEvent* iso14443_3a_event = event.event_data; + furi_assert(iso14443_3a_event); + + NfcCommand command = NfcCommandContinue; + + if(iso14443_3a_event->type == Iso14443_3aPollerEventTypeReady) { + command = iso14443_4a_poller_state_handler[instance->poller_state](instance); + } else if(iso14443_3a_event->type == Iso14443_3aPollerEventTypeError) { + instance->iso14443_4a_event.type = Iso14443_4aPollerEventTypeError; + command = instance->callback(instance->general_event, instance->context); + } + + return command; +} + +static bool iso14443_4a_poller_detect(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolIso14443_3a); + + const Iso14443_4aPoller* instance = context; + furi_assert(instance); + + const Iso14443_3aPollerEvent* iso14443_3a_event = event.event_data; + furi_assert(iso14443_3a_event); + iso14443_3a_copy( + instance->data->iso14443_3a_data, + iso14443_3a_poller_get_data(instance->iso14443_3a_poller)); + + bool protocol_detected = false; + + if(iso14443_3a_event->type == Iso14443_3aPollerEventTypeReady) { + protocol_detected = iso14443_3a_supports_iso14443_4(instance->data->iso14443_3a_data); + } + + return protocol_detected; +} + +const NfcPollerBase nfc_poller_iso14443_4a = { + .alloc = (NfcPollerAlloc)iso14443_4a_poller_alloc, + .free = (NfcPollerFree)iso14443_4a_poller_free, + .set_callback = (NfcPollerSetCallback)iso14443_4a_poller_set_callback, + .run = (NfcPollerRun)iso14443_4a_poller_run, + .detect = (NfcPollerDetect)iso14443_4a_poller_detect, + .get_data = (NfcPollerGetData)iso14443_4a_poller_get_data, +}; diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.h b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.h new file mode 100644 index 00000000000..b224299e0a7 --- /dev/null +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +#include "iso14443_4a.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct Iso14443_4aPoller Iso14443_4aPoller; + +typedef enum { + Iso14443_4aPollerEventTypeError, + Iso14443_4aPollerEventTypeReady, +} Iso14443_4aPollerEventType; + +typedef struct { + Iso14443_4aError error; +} Iso14443_4aPollerEventData; + +typedef struct { + Iso14443_4aPollerEventType type; + Iso14443_4aPollerEventData* data; +} Iso14443_4aPollerEvent; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_defs.h b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_defs.h new file mode 100644 index 00000000000..aa62166742b --- /dev/null +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_defs.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +extern const NfcPollerBase nfc_poller_iso14443_4a; diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.c b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.c new file mode 100644 index 00000000000..7221e2aa94d --- /dev/null +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.c @@ -0,0 +1,83 @@ +#include "iso14443_4a_poller_i.h" + +#include + +#include "iso14443_4a_i.h" + +#define TAG "Iso14443_4aPoller" + +#define ISO14443_4A_FSDI_256 (0x8U) + +Iso14443_4aError iso14443_4a_poller_halt(Iso14443_4aPoller* instance) { + furi_assert(instance); + + iso14443_3a_poller_halt(instance->iso14443_3a_poller); + instance->poller_state = Iso14443_4aPollerStateIdle; + + return Iso14443_4aErrorNone; +} + +Iso14443_4aError + iso14443_4a_poller_async_read_ats(Iso14443_4aPoller* instance, Iso14443_4aAtsData* data) { + furi_assert(instance); + + bit_buffer_reset(instance->tx_buffer); + bit_buffer_append_byte(instance->tx_buffer, ISO14443_4A_CMD_READ_ATS); + bit_buffer_append_byte(instance->tx_buffer, ISO14443_4A_FSDI_256 << 4); + + Iso14443_4aError error = Iso14443_4aErrorNone; + + do { + const Iso14443_3aError iso14443_3a_error = iso14443_3a_poller_send_standard_frame( + instance->iso14443_3a_poller, + instance->tx_buffer, + instance->rx_buffer, + ISO14443_4A_POLLER_ATS_FWT_FC); + + if(iso14443_3a_error != Iso14443_3aErrorNone) { + FURI_LOG_E(TAG, "ATS request failed"); + error = iso14443_4a_process_error(iso14443_3a_error); + break; + + } else if(!iso14443_4a_ats_parse(data, instance->rx_buffer)) { + FURI_LOG_E(TAG, "Failed to parse ATS response"); + error = Iso14443_4aErrorProtocol; + break; + } + + } while(false); + + return error; +} + +Iso14443_4aError iso14443_4a_poller_send_block( + Iso14443_4aPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer) { + furi_assert(instance); + + bit_buffer_reset(instance->tx_buffer); + iso14443_4_layer_encode_block(instance->iso14443_4_layer, tx_buffer, instance->tx_buffer); + + Iso14443_4aError error = Iso14443_4aErrorNone; + + do { + Iso14443_3aError iso14443_3a_error = iso14443_3a_poller_send_standard_frame( + instance->iso14443_3a_poller, + instance->tx_buffer, + instance->rx_buffer, + iso14443_4a_get_fwt_fc_max(instance->data)); + + if(iso14443_3a_error != Iso14443_3aErrorNone) { + error = iso14443_4a_process_error(iso14443_3a_error); + break; + + } else if(!iso14443_4_layer_decode_block( + instance->iso14443_4_layer, rx_buffer, instance->rx_buffer)) { + error = Iso14443_4aErrorProtocol; + break; + } + } while(false); + + return error; +} diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.h b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.h new file mode 100644 index 00000000000..1113d381c2b --- /dev/null +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.h @@ -0,0 +1,62 @@ +#pragma once + +#include +#include + +#include "iso14443_4a_poller.h" +#include "iso14443_4a_i.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define ISO14443_4A_POLLER_ATS_FWT_FC (40000) + +typedef enum { + Iso14443_4aPollerStateIdle, + Iso14443_4aPollerStateReadAts, + Iso14443_4aPollerStateError, + Iso14443_4aPollerStateReady, + + Iso14443_4aPollerStateNum, +} Iso14443_4aPollerState; + +typedef enum { + Iso14443_4aPollerSessionStateIdle, + Iso14443_4aPollerSessionStateActive, + Iso14443_4aPollerSessionStateStopRequest, +} Iso14443_4aPollerSessionState; + +struct Iso14443_4aPoller { + Iso14443_3aPoller* iso14443_3a_poller; + Iso14443_4aPollerState poller_state; + Iso14443_4aPollerSessionState session_state; + Iso14443_4aError error; + Iso14443_4aData* data; + Iso14443_4Layer* iso14443_4_layer; + BitBuffer* tx_buffer; + BitBuffer* rx_buffer; + Iso14443_4aPollerEventData iso14443_4a_event_data; + Iso14443_4aPollerEvent iso14443_4a_event; + NfcGenericEvent general_event; + NfcGenericCallback callback; + void* context; +}; + +Iso14443_4aError iso14443_4a_process_error(Iso14443_3aError error); + +const Iso14443_4aData* iso14443_4a_poller_get_data(Iso14443_4aPoller* instance); + +Iso14443_4aError iso14443_4a_poller_halt(Iso14443_4aPoller* instance); + +Iso14443_4aError + iso14443_4a_poller_async_read_ats(Iso14443_4aPoller* instance, Iso14443_4aAtsData* data); + +Iso14443_4aError iso14443_4a_poller_send_block( + Iso14443_4aPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/iso14443_4b/iso14443_4b.c b/lib/nfc/protocols/iso14443_4b/iso14443_4b.c new file mode 100644 index 00000000000..19f2939da06 --- /dev/null +++ b/lib/nfc/protocols/iso14443_4b/iso14443_4b.c @@ -0,0 +1,94 @@ +#include "iso14443_4b_i.h" + +#include +#include + +#define ISO14443_4B_PROTOCOL_NAME "ISO14443-4B" +#define ISO14443_4B_DEVICE_NAME "ISO14443-4B (Unknown)" + +const NfcDeviceBase nfc_device_iso14443_4b = { + .protocol_name = ISO14443_4B_PROTOCOL_NAME, + .alloc = (NfcDeviceAlloc)iso14443_4b_alloc, + .free = (NfcDeviceFree)iso14443_4b_free, + .reset = (NfcDeviceReset)iso14443_4b_reset, + .copy = (NfcDeviceCopy)iso14443_4b_copy, + .verify = (NfcDeviceVerify)iso14443_4b_verify, + .load = (NfcDeviceLoad)iso14443_4b_load, + .save = (NfcDeviceSave)iso14443_4b_save, + .is_equal = (NfcDeviceEqual)iso14443_4b_is_equal, + .get_name = (NfcDeviceGetName)iso14443_4b_get_device_name, + .get_uid = (NfcDeviceGetUid)iso14443_4b_get_uid, + .set_uid = (NfcDeviceSetUid)iso14443_4b_set_uid, + .get_base_data = (NfcDeviceGetBaseData)iso14443_4b_get_base_data, +}; + +Iso14443_4bData* iso14443_4b_alloc() { + Iso14443_4bData* data = malloc(sizeof(Iso14443_4bData)); + + data->iso14443_3b_data = iso14443_3b_alloc(); + return data; +} + +void iso14443_4b_free(Iso14443_4bData* data) { + furi_assert(data); + + iso14443_3b_free(data->iso14443_3b_data); + free(data); +} + +void iso14443_4b_reset(Iso14443_4bData* data) { + furi_assert(data); + + iso14443_3b_reset(data->iso14443_3b_data); +} + +void iso14443_4b_copy(Iso14443_4bData* data, const Iso14443_4bData* other) { + furi_assert(data); + furi_assert(other); + + iso14443_3b_copy(data->iso14443_3b_data, other->iso14443_3b_data); +} + +bool iso14443_4b_verify(Iso14443_4bData* data, const FuriString* device_type) { + UNUSED(data); + UNUSED(device_type); + + // Empty, unified file format only + return false; +} + +bool iso14443_4b_load(Iso14443_4bData* data, FlipperFormat* ff, uint32_t version) { + furi_assert(data); + return iso14443_3b_load(data->iso14443_3b_data, ff, version); +} + +bool iso14443_4b_save(const Iso14443_4bData* data, FlipperFormat* ff) { + furi_assert(data); + return iso14443_3b_save(data->iso14443_3b_data, ff); +} + +bool iso14443_4b_is_equal(const Iso14443_4bData* data, const Iso14443_4bData* other) { + return iso14443_3b_is_equal(data->iso14443_3b_data, other->iso14443_3b_data); +} + +const char* iso14443_4b_get_device_name(const Iso14443_4bData* data, NfcDeviceNameType name_type) { + UNUSED(data); + UNUSED(name_type); + return ISO14443_4B_DEVICE_NAME; +} + +const uint8_t* iso14443_4b_get_uid(const Iso14443_4bData* data, size_t* uid_len) { + return iso14443_3b_get_uid(data->iso14443_3b_data, uid_len); +} + +bool iso14443_4b_set_uid(Iso14443_4bData* data, const uint8_t* uid, size_t uid_len) { + furi_assert(data); + + return iso14443_3b_set_uid(data->iso14443_3b_data, uid, uid_len); +} + +Iso14443_3bData* iso14443_4b_get_base_data(const Iso14443_4bData* data) { + furi_assert(data); + + return data->iso14443_3b_data; +} diff --git a/lib/nfc/protocols/iso14443_4b/iso14443_4b.h b/lib/nfc/protocols/iso14443_4b/iso14443_4b.h new file mode 100644 index 00000000000..1f269ed9144 --- /dev/null +++ b/lib/nfc/protocols/iso14443_4b/iso14443_4b.h @@ -0,0 +1,46 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + Iso14443_4bErrorNone, + Iso14443_4bErrorNotPresent, + Iso14443_4bErrorProtocol, + Iso14443_4bErrorTimeout, +} Iso14443_4bError; + +typedef struct Iso14443_4bData Iso14443_4bData; + +// Virtual methods + +Iso14443_4bData* iso14443_4b_alloc(); + +void iso14443_4b_free(Iso14443_4bData* data); + +void iso14443_4b_reset(Iso14443_4bData* data); + +void iso14443_4b_copy(Iso14443_4bData* data, const Iso14443_4bData* other); + +bool iso14443_4b_verify(Iso14443_4bData* data, const FuriString* device_type); + +bool iso14443_4b_load(Iso14443_4bData* data, FlipperFormat* ff, uint32_t version); + +bool iso14443_4b_save(const Iso14443_4bData* data, FlipperFormat* ff); + +bool iso14443_4b_is_equal(const Iso14443_4bData* data, const Iso14443_4bData* other); + +const char* iso14443_4b_get_device_name(const Iso14443_4bData* data, NfcDeviceNameType name_type); + +const uint8_t* iso14443_4b_get_uid(const Iso14443_4bData* data, size_t* uid_len); + +bool iso14443_4b_set_uid(Iso14443_4bData* data, const uint8_t* uid, size_t uid_len); + +Iso14443_3bData* iso14443_4b_get_base_data(const Iso14443_4bData* data); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/iso14443_4b/iso14443_4b_device_defs.h b/lib/nfc/protocols/iso14443_4b/iso14443_4b_device_defs.h new file mode 100644 index 00000000000..8185c9aeebc --- /dev/null +++ b/lib/nfc/protocols/iso14443_4b/iso14443_4b_device_defs.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +extern const NfcDeviceBase nfc_device_iso14443_4b; diff --git a/lib/nfc/protocols/iso14443_4b/iso14443_4b_i.c b/lib/nfc/protocols/iso14443_4b/iso14443_4b_i.c new file mode 100644 index 00000000000..1df8806408d --- /dev/null +++ b/lib/nfc/protocols/iso14443_4b/iso14443_4b_i.c @@ -0,0 +1,18 @@ +#include "iso14443_4b_i.h" + +Iso14443_4bError iso14443_4b_process_error(Iso14443_3bError error) { + switch(error) { + case Iso14443_3bErrorNone: + return Iso14443_4bErrorNone; + case Iso14443_3bErrorNotPresent: + return Iso14443_4bErrorNotPresent; + case Iso14443_3bErrorColResFailed: + case Iso14443_3bErrorCommunication: + case Iso14443_3bErrorWrongCrc: + return Iso14443_4bErrorProtocol; + case Iso14443_3bErrorTimeout: + return Iso14443_4bErrorTimeout; + default: + return Iso14443_4bErrorProtocol; + } +} diff --git a/lib/nfc/protocols/iso14443_4b/iso14443_4b_i.h b/lib/nfc/protocols/iso14443_4b/iso14443_4b_i.h new file mode 100644 index 00000000000..6090c03936e --- /dev/null +++ b/lib/nfc/protocols/iso14443_4b/iso14443_4b_i.h @@ -0,0 +1,9 @@ +#pragma once + +#include "iso14443_4b.h" + +struct Iso14443_4bData { + Iso14443_3bData* iso14443_3b_data; +}; + +Iso14443_4bError iso14443_4b_process_error(Iso14443_3bError error); diff --git a/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller.c b/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller.c new file mode 100644 index 00000000000..1030ebfb6f9 --- /dev/null +++ b/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller.c @@ -0,0 +1,138 @@ +#include "iso14443_4b_poller_i.h" + +#include + +#include + +#define TAG "Iso14443_4bPoller" + +#define ISO14443_4A_POLLER_BUF_SIZE (256U) + +typedef NfcCommand (*Iso14443_4bPollerStateHandler)(Iso14443_4bPoller* instance); + +const Iso14443_4bData* iso14443_4b_poller_get_data(Iso14443_4bPoller* instance) { + furi_assert(instance); + + return instance->data; +} + +static Iso14443_4bPoller* iso14443_4b_poller_alloc(Iso14443_3bPoller* iso14443_3b_poller) { + Iso14443_4bPoller* instance = malloc(sizeof(Iso14443_4bPoller)); + instance->iso14443_3b_poller = iso14443_3b_poller; + instance->data = iso14443_4b_alloc(); + instance->iso14443_4_layer = iso14443_4_layer_alloc(); + instance->tx_buffer = bit_buffer_alloc(ISO14443_4A_POLLER_BUF_SIZE); + instance->rx_buffer = bit_buffer_alloc(ISO14443_4A_POLLER_BUF_SIZE); + + instance->iso14443_4b_event.data = &instance->iso14443_4b_event_data; + + instance->general_event.protocol = NfcProtocolIso14443_4b; + instance->general_event.event_data = &instance->iso14443_4b_event; + instance->general_event.instance = instance; + + return instance; +} + +static void iso14443_4b_poller_free(Iso14443_4bPoller* instance) { + furi_assert(instance); + + iso14443_4b_free(instance->data); + iso14443_4_layer_free(instance->iso14443_4_layer); + bit_buffer_free(instance->tx_buffer); + bit_buffer_free(instance->rx_buffer); + free(instance); +} + +static NfcCommand iso14443_4b_poller_handler_idle(Iso14443_4bPoller* instance) { + iso14443_3b_copy( + instance->data->iso14443_3b_data, + iso14443_3b_poller_get_data(instance->iso14443_3b_poller)); + + iso14443_4_layer_reset(instance->iso14443_4_layer); + instance->poller_state = Iso14443_4bPollerStateReady; + return NfcCommandContinue; +} + +static NfcCommand iso14443_4b_poller_handler_error(Iso14443_4bPoller* instance) { + iso14443_3b_poller_halt(instance->iso14443_3b_poller); + instance->iso14443_4b_event_data.error = instance->error; + NfcCommand command = instance->callback(instance->general_event, instance->context); + instance->poller_state = Iso14443_4bPollerStateIdle; + return command; +} + +static NfcCommand iso14443_4b_poller_handler_ready(Iso14443_4bPoller* instance) { + instance->iso14443_4b_event.type = Iso14443_4bPollerEventTypeReady; + NfcCommand command = instance->callback(instance->general_event, instance->context); + return command; +} + +static const Iso14443_4bPollerStateHandler + iso14443_4b_poller_state_handler[Iso14443_4bPollerStateNum] = { + [Iso14443_4bPollerStateIdle] = iso14443_4b_poller_handler_idle, + [Iso14443_4bPollerStateError] = iso14443_4b_poller_handler_error, + [Iso14443_4bPollerStateReady] = iso14443_4b_poller_handler_ready, +}; + +static void iso14443_4b_poller_set_callback( + Iso14443_4bPoller* instance, + NfcGenericCallback callback, + void* context) { + furi_assert(instance); + furi_assert(callback); + + instance->callback = callback; + instance->context = context; +} + +static NfcCommand iso14443_4b_poller_run(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolIso14443_3b); + + Iso14443_4bPoller* instance = context; + furi_assert(instance); + furi_assert(instance->callback); + + Iso14443_3bPollerEvent* iso14443_3b_event = event.event_data; + furi_assert(iso14443_3b_event); + + NfcCommand command = NfcCommandContinue; + + if(iso14443_3b_event->type == Iso14443_3bPollerEventTypeReady) { + command = iso14443_4b_poller_state_handler[instance->poller_state](instance); + } else if(iso14443_3b_event->type == Iso14443_3bPollerEventTypeError) { + instance->iso14443_4b_event.type = Iso14443_4bPollerEventTypeError; + command = instance->callback(instance->general_event, instance->context); + } + + return command; +} + +static bool iso14443_4b_poller_detect(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolIso14443_3b); + + const Iso14443_4bPoller* instance = context; + furi_assert(instance); + + const Iso14443_3bPollerEvent* iso14443_3b_event = event.event_data; + furi_assert(iso14443_3b_event); + iso14443_3b_copy( + instance->data->iso14443_3b_data, + iso14443_3b_poller_get_data(instance->iso14443_3b_poller)); + + bool protocol_detected = false; + + if(iso14443_3b_event->type == Iso14443_3bPollerEventTypeReady) { + protocol_detected = iso14443_3b_supports_iso14443_4(instance->data->iso14443_3b_data); + } + + return protocol_detected; +} + +const NfcPollerBase nfc_poller_iso14443_4b = { + .alloc = (NfcPollerAlloc)iso14443_4b_poller_alloc, + .free = (NfcPollerFree)iso14443_4b_poller_free, + .set_callback = (NfcPollerSetCallback)iso14443_4b_poller_set_callback, + .run = (NfcPollerRun)iso14443_4b_poller_run, + .detect = (NfcPollerDetect)iso14443_4b_poller_detect, + .get_data = (NfcPollerGetData)iso14443_4b_poller_get_data, +}; diff --git a/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller.h b/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller.h new file mode 100644 index 00000000000..e60090c04e2 --- /dev/null +++ b/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +#include "iso14443_4b.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct Iso14443_4bPoller Iso14443_4bPoller; + +typedef enum { + Iso14443_4bPollerEventTypeError, + Iso14443_4bPollerEventTypeReady, +} Iso14443_4bPollerEventType; + +typedef struct { + Iso14443_4bError error; +} Iso14443_4bPollerEventData; + +typedef struct { + Iso14443_4bPollerEventType type; + Iso14443_4bPollerEventData* data; +} Iso14443_4bPollerEvent; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller_defs.h b/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller_defs.h new file mode 100644 index 00000000000..4388f2cf8f3 --- /dev/null +++ b/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller_defs.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +extern const NfcPollerBase nfc_poller_iso14443_4b; diff --git a/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller_i.c b/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller_i.c new file mode 100644 index 00000000000..82de1d538a3 --- /dev/null +++ b/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller_i.c @@ -0,0 +1,45 @@ +#include "iso14443_4b_poller_i.h" + +#include + +#include "iso14443_4b_i.h" + +#define TAG "Iso14443_4bPoller" + +Iso14443_4bError iso14443_4b_poller_halt(Iso14443_4bPoller* instance) { + furi_assert(instance); + + iso14443_3b_poller_halt(instance->iso14443_3b_poller); + instance->poller_state = Iso14443_4bPollerStateIdle; + + return Iso14443_4bErrorNone; +} + +Iso14443_4bError iso14443_4b_poller_send_block( + Iso14443_4bPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer) { + furi_assert(instance); + + bit_buffer_reset(instance->tx_buffer); + iso14443_4_layer_encode_block(instance->iso14443_4_layer, tx_buffer, instance->tx_buffer); + + Iso14443_4bError error = Iso14443_4bErrorNone; + + do { + Iso14443_3bError iso14443_3b_error = iso14443_3b_poller_send_frame( + instance->iso14443_3b_poller, instance->tx_buffer, instance->rx_buffer); + + if(iso14443_3b_error != Iso14443_3bErrorNone) { + error = iso14443_4b_process_error(iso14443_3b_error); + break; + + } else if(!iso14443_4_layer_decode_block( + instance->iso14443_4_layer, rx_buffer, instance->rx_buffer)) { + error = Iso14443_4bErrorProtocol; + break; + } + } while(false); + + return error; +} diff --git a/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller_i.h b/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller_i.h new file mode 100644 index 00000000000..4df00adcf10 --- /dev/null +++ b/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller_i.h @@ -0,0 +1,56 @@ +#pragma once + +#include +#include + +#include "iso14443_4b_poller.h" +#include "iso14443_4b_i.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + Iso14443_4bPollerStateIdle, + Iso14443_4bPollerStateError, + Iso14443_4bPollerStateReady, + + Iso14443_4bPollerStateNum, +} Iso14443_4bPollerState; + +typedef enum { + Iso14443_4bPollerSessionStateIdle, + Iso14443_4bPollerSessionStateActive, + Iso14443_4bPollerSessionStateStopRequest, +} Iso14443_4bPollerSessionState; + +struct Iso14443_4bPoller { + Iso14443_3bPoller* iso14443_3b_poller; + Iso14443_4bPollerState poller_state; + Iso14443_4bPollerSessionState session_state; + Iso14443_4bError error; + Iso14443_4bData* data; + Iso14443_4Layer* iso14443_4_layer; + BitBuffer* tx_buffer; + BitBuffer* rx_buffer; + Iso14443_4bPollerEventData iso14443_4b_event_data; + Iso14443_4bPollerEvent iso14443_4b_event; + NfcGenericEvent general_event; + NfcGenericCallback callback; + void* context; +}; + +Iso14443_4bError iso14443_4b_process_error(Iso14443_3bError error); + +const Iso14443_4bData* iso14443_4b_poller_get_data(Iso14443_4bPoller* instance); + +Iso14443_4bError iso14443_4b_poller_halt(Iso14443_4bPoller* instance); + +Iso14443_4bError iso14443_4b_poller_send_block( + Iso14443_4bPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3.c b/lib/nfc/protocols/iso15693_3/iso15693_3.c new file mode 100644 index 00000000000..3203cbad000 --- /dev/null +++ b/lib/nfc/protocols/iso15693_3/iso15693_3.c @@ -0,0 +1,358 @@ +#include "iso15693_3.h" +#include "iso15693_3_device_defs.h" + +#include + +#define ISO15693_3_PROTOCOL_NAME "ISO15693-3" +#define ISO15693_3_PROTOCOL_NAME_LEGACY "ISO15693" +#define ISO15693_3_DEVICE_NAME "ISO15693-3 (Unknown)" + +#define ISO15693_3_LOCK_DSFID_LEGACY (1U << 0) +#define ISO15693_3_LOCK_AFI_LEGACY (1U << 1) + +#define ISO15693_3_DSFID_KEY "DSFID" +#define ISO15693_3_AFI_KEY "AFI" +#define ISO15693_3_IC_REF_KEY "IC Reference" +#define ISO15693_3_BLOCK_COUNT_KEY "Block Count" +#define ISO15693_3_BLOCK_SIZE_KEY "Block Size" +#define ISO15693_3_DATA_CONTENT_KEY "Data Content" +#define ISO15693_3_LOCK_DSFID_KEY "Lock DSFID" +#define ISO15693_3_LOCK_AFI_KEY "Lock AFI" +#define ISO15693_3_SECURITY_STATUS_KEY "Security Status" + +const NfcDeviceBase nfc_device_iso15693_3 = { + .protocol_name = ISO15693_3_PROTOCOL_NAME, + .alloc = (NfcDeviceAlloc)iso15693_3_alloc, + .free = (NfcDeviceFree)iso15693_3_free, + .reset = (NfcDeviceReset)iso15693_3_reset, + .copy = (NfcDeviceCopy)iso15693_3_copy, + .verify = (NfcDeviceVerify)iso15693_3_verify, + .load = (NfcDeviceLoad)iso15693_3_load, + .save = (NfcDeviceSave)iso15693_3_save, + .is_equal = (NfcDeviceEqual)iso15693_3_is_equal, + .get_name = (NfcDeviceGetName)iso15693_3_get_device_name, + .get_uid = (NfcDeviceGetUid)iso15693_3_get_uid, + .set_uid = (NfcDeviceSetUid)iso15693_3_set_uid, + .get_base_data = (NfcDeviceGetBaseData)iso15693_3_get_base_data, +}; + +Iso15693_3Data* iso15693_3_alloc() { + Iso15693_3Data* data = malloc(sizeof(Iso15693_3Data)); + + data->block_data = simple_array_alloc(&simple_array_config_uint8_t); + data->block_security = simple_array_alloc(&simple_array_config_uint8_t); + + return data; +} + +void iso15693_3_free(Iso15693_3Data* data) { + furi_assert(data); + + simple_array_free(data->block_data); + simple_array_free(data->block_security); + free(data); +} + +void iso15693_3_reset(Iso15693_3Data* data) { + furi_assert(data); + + memset(data->uid, 0, ISO15693_3_UID_SIZE); + memset(&data->system_info, 0, sizeof(Iso15693_3SystemInfo)); + memset(&data->settings, 0, sizeof(Iso15693_3Settings)); + + simple_array_reset(data->block_data); + simple_array_reset(data->block_security); +} + +void iso15693_3_copy(Iso15693_3Data* data, const Iso15693_3Data* other) { + furi_assert(data); + furi_assert(other); + + memcpy(data->uid, other->uid, ISO15693_3_UID_SIZE); + + data->system_info = other->system_info; + data->settings = other->settings; + + simple_array_copy(data->block_data, other->block_data); + simple_array_copy(data->block_security, other->block_security); +} + +bool iso15693_3_verify(Iso15693_3Data* data, const FuriString* device_type) { + UNUSED(data); + return furi_string_equal(device_type, ISO15693_3_PROTOCOL_NAME_LEGACY); +} + +static inline bool iso15693_3_load_security_legacy(Iso15693_3Data* data, FlipperFormat* ff) { + bool loaded = false; + uint8_t* legacy_data = NULL; + + do { + uint32_t value_count; + if(!flipper_format_get_value_count(ff, ISO15693_3_SECURITY_STATUS_KEY, &value_count)) + break; + if(simple_array_get_count(data->block_security) + 1 != value_count) break; + + legacy_data = malloc(value_count); + if(!flipper_format_read_hex(ff, ISO15693_3_SECURITY_STATUS_KEY, legacy_data, value_count)) + break; + + // First legacy data byte is lock bits + data->settings.lock_bits.dsfid = legacy_data[0] & ISO15693_3_LOCK_DSFID_LEGACY; + data->settings.lock_bits.afi = legacy_data[0] & ISO15693_3_LOCK_AFI_LEGACY; + + // The rest are block security + memcpy( + &legacy_data[1], + simple_array_get_data(data->block_security), + simple_array_get_count(data->block_security)); + + loaded = true; + } while(false); + + if(legacy_data) free(legacy_data); + + return loaded; +} + +static inline bool iso15693_3_load_security(Iso15693_3Data* data, FlipperFormat* ff) { + bool loaded = false; + + do { + uint32_t value_count; + if(!flipper_format_get_value_count(ff, ISO15693_3_SECURITY_STATUS_KEY, &value_count)) + break; + if(simple_array_get_count(data->block_security) != value_count) break; + if(!flipper_format_read_hex( + ff, + ISO15693_3_SECURITY_STATUS_KEY, + simple_array_get_data(data->block_security), + simple_array_get_count(data->block_security))) + break; + + loaded = true; + } while(false); + + return loaded; +} + +bool iso15693_3_load(Iso15693_3Data* data, FlipperFormat* ff, uint32_t version) { + furi_assert(data); + UNUSED(version); + + bool loaded = false; + + do { + if(flipper_format_key_exist(ff, ISO15693_3_DSFID_KEY)) { + if(!flipper_format_read_hex(ff, ISO15693_3_DSFID_KEY, &data->system_info.dsfid, 1)) + break; + data->system_info.flags |= ISO15693_3_SYSINFO_FLAG_DSFID; + } + + if(flipper_format_key_exist(ff, ISO15693_3_AFI_KEY)) { + if(!flipper_format_read_hex(ff, ISO15693_3_AFI_KEY, &data->system_info.afi, 1)) break; + data->system_info.flags |= ISO15693_3_SYSINFO_FLAG_AFI; + } + + if(flipper_format_key_exist(ff, ISO15693_3_IC_REF_KEY)) { + if(!flipper_format_read_hex(ff, ISO15693_3_IC_REF_KEY, &data->system_info.ic_ref, 1)) + break; + data->system_info.flags |= ISO15693_3_SYSINFO_FLAG_IC_REF; + } + + const bool has_lock_bits = flipper_format_key_exist(ff, ISO15693_3_LOCK_DSFID_KEY) && + flipper_format_key_exist(ff, ISO15693_3_LOCK_AFI_KEY); + if(has_lock_bits) { + Iso15693_3LockBits* lock_bits = &data->settings.lock_bits; + if(!flipper_format_read_bool(ff, ISO15693_3_LOCK_DSFID_KEY, &lock_bits->dsfid, 1)) + break; + if(!flipper_format_read_bool(ff, ISO15693_3_LOCK_AFI_KEY, &lock_bits->afi, 1)) break; + } + + if(flipper_format_key_exist(ff, ISO15693_3_BLOCK_COUNT_KEY) && + flipper_format_key_exist(ff, ISO15693_3_BLOCK_SIZE_KEY)) { + uint32_t block_count; + if(!flipper_format_read_uint32(ff, ISO15693_3_BLOCK_COUNT_KEY, &block_count, 1)) break; + + data->system_info.block_count = block_count; + data->system_info.flags |= ISO15693_3_SYSINFO_FLAG_MEMORY; + + if(!flipper_format_read_hex( + ff, ISO15693_3_BLOCK_SIZE_KEY, &(data->system_info.block_size), 1)) + break; + + simple_array_init( + data->block_data, data->system_info.block_size * data->system_info.block_count); + + if(!flipper_format_read_hex( + ff, + ISO15693_3_DATA_CONTENT_KEY, + simple_array_get_data(data->block_data), + simple_array_get_count(data->block_data))) + break; + + if(flipper_format_key_exist(ff, ISO15693_3_SECURITY_STATUS_KEY)) { + simple_array_init(data->block_security, data->system_info.block_count); + + const bool security_loaded = has_lock_bits ? + iso15693_3_load_security(data, ff) : + iso15693_3_load_security_legacy(data, ff); + if(!security_loaded) break; + } + } + + loaded = true; + } while(false); + + return loaded; +} + +bool iso15693_3_save(const Iso15693_3Data* data, FlipperFormat* ff) { + furi_assert(data); + + bool saved = false; + + do { + if(!flipper_format_write_comment_cstr(ff, ISO15693_3_PROTOCOL_NAME " specific data")) + break; + + if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_DSFID) { + if(!flipper_format_write_comment_cstr(ff, "Data Storage Format Identifier")) break; + if(!flipper_format_write_hex(ff, ISO15693_3_DSFID_KEY, &data->system_info.dsfid, 1)) + break; + } + + if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_AFI) { + if(!flipper_format_write_comment_cstr(ff, "Application Family Identifier")) break; + if(!flipper_format_write_hex(ff, ISO15693_3_AFI_KEY, &data->system_info.afi, 1)) break; + } + + if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_IC_REF) { + if(!flipper_format_write_comment_cstr(ff, "IC Reference - Vendor specific meaning")) + break; + if(!flipper_format_write_hex(ff, ISO15693_3_IC_REF_KEY, &data->system_info.ic_ref, 1)) + break; + } + + if(!flipper_format_write_comment_cstr(ff, "Lock Bits")) break; + if(!flipper_format_write_bool( + ff, ISO15693_3_LOCK_DSFID_KEY, &data->settings.lock_bits.dsfid, 1)) + break; + if(!flipper_format_write_bool( + ff, ISO15693_3_LOCK_AFI_KEY, &data->settings.lock_bits.afi, 1)) + break; + + if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_MEMORY) { + const uint32_t block_count = data->system_info.block_count; + if(!flipper_format_write_comment_cstr( + ff, "Number of memory blocks, valid range = 1..256")) + break; + if(!flipper_format_write_uint32(ff, ISO15693_3_BLOCK_COUNT_KEY, &block_count, 1)) + break; + + if(!flipper_format_write_comment_cstr( + ff, "Size of a single memory block, valid range = 01...20 (hex)")) + break; + if(!flipper_format_write_hex( + ff, ISO15693_3_BLOCK_SIZE_KEY, &data->system_info.block_size, 1)) + break; + + if(!flipper_format_write_hex( + ff, + ISO15693_3_DATA_CONTENT_KEY, + simple_array_cget_data(data->block_data), + simple_array_get_count(data->block_data))) + break; + + if(!flipper_format_write_comment_cstr( + ff, "Block Security Status: 01 = locked, 00 = not locked")) + break; + if(!flipper_format_write_hex( + ff, + ISO15693_3_SECURITY_STATUS_KEY, + simple_array_cget_data(data->block_security), + simple_array_get_count(data->block_security))) + break; + } + saved = true; + } while(false); + + return saved; +} + +bool iso15693_3_is_equal(const Iso15693_3Data* data, const Iso15693_3Data* other) { + furi_assert(data); + furi_assert(other); + + return memcmp(data->uid, other->uid, ISO15693_3_UID_SIZE) == 0 && + memcmp(&data->settings, &other->settings, sizeof(Iso15693_3Settings)) == 0 && + memcmp(&data->system_info, &other->system_info, sizeof(Iso15693_3SystemInfo)) == 0 && + simple_array_is_equal(data->block_data, other->block_data) && + simple_array_is_equal(data->block_security, other->block_security); +} + +const char* iso15693_3_get_device_name(const Iso15693_3Data* data, NfcDeviceNameType name_type) { + UNUSED(data); + UNUSED(name_type); + + return ISO15693_3_DEVICE_NAME; +} + +const uint8_t* iso15693_3_get_uid(const Iso15693_3Data* data, size_t* uid_len) { + furi_assert(data); + + if(uid_len) *uid_len = ISO15693_3_UID_SIZE; + return data->uid; +} + +bool iso15693_3_set_uid(Iso15693_3Data* data, const uint8_t* uid, size_t uid_len) { + furi_assert(data); + furi_assert(uid); + + bool uid_valid = uid_len == ISO15693_3_UID_SIZE; + + if(uid_valid) { + memcpy(data->uid, uid, uid_len); + // All ISO15693-3 cards must have this as first UID byte + data->uid[0] = 0xe0; + } + + return uid_valid; +} + +Iso15693_3Data* iso15693_3_get_base_data(const Iso15693_3Data* data) { + UNUSED(data); + furi_crash("No base data"); +} + +bool iso15693_3_is_block_locked(const Iso15693_3Data* data, uint8_t block_index) { + furi_assert(data); + furi_assert(block_index < data->system_info.block_count); + + return *(const uint8_t*)simple_array_cget(data->block_security, block_index); +} + +uint8_t iso15693_3_get_manufacturer_id(const Iso15693_3Data* data) { + furi_assert(data); + + return data->uid[1]; +} + +uint16_t iso15693_3_get_block_count(const Iso15693_3Data* data) { + furi_assert(data); + + return data->system_info.block_count; +} + +uint8_t iso15693_3_get_block_size(const Iso15693_3Data* data) { + furi_assert(data); + + return data->system_info.block_size; +} + +const uint8_t* iso15693_3_get_block_data(const Iso15693_3Data* data, uint8_t block_index) { + furi_assert(data); + furi_assert(data->system_info.block_count > block_index); + + return (const uint8_t*)simple_array_cget( + data->block_data, block_index * data->system_info.block_size); +} diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3.h b/lib/nfc/protocols/iso15693_3/iso15693_3.h new file mode 100644 index 00000000000..5d4158ab9e3 --- /dev/null +++ b/lib/nfc/protocols/iso15693_3/iso15693_3.h @@ -0,0 +1,163 @@ +#pragma once + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define ISO15693_3_UID_SIZE (8U) + +#define ISO15693_3_GUARD_TIME_US (5000U) +#define ISO15693_3_FDT_POLL_FC (4202U) +#define ISO15693_3_FDT_LISTEN_FC (4320U) +#define ISO15693_3_POLL_POLL_MIN_US (1500U) + +#define ISO15693_3_REQ_FLAG_SUBCARRIER_1 (0U << 0) +#define ISO15693_3_REQ_FLAG_SUBCARRIER_2 (1U << 0) +#define ISO15693_3_REQ_FLAG_DATA_RATE_LO (0U << 1) +#define ISO15693_3_REQ_FLAG_DATA_RATE_HI (1U << 1) +#define ISO15693_3_REQ_FLAG_INVENTORY_T4 (0U << 2) +#define ISO15693_3_REQ_FLAG_INVENTORY_T5 (1U << 2) +#define ISO15693_3_REQ_FLAG_EXTENSION (1U << 3) + +#define ISO15693_3_REQ_FLAG_T4_SELECTED (1U << 4) +#define ISO15693_3_REQ_FLAG_T4_ADDRESSED (1U << 5) +#define ISO15693_3_REQ_FLAG_T4_OPTION (1U << 6) + +#define ISO15693_3_REQ_FLAG_T5_AFI_PRESENT (1U << 4) +#define ISO15693_3_REQ_FLAG_T5_N_SLOTS_16 (0U << 5) +#define ISO15693_3_REQ_FLAG_T5_N_SLOTS_1 (1U << 5) +#define ISO15693_3_REQ_FLAG_T5_OPTION (1U << 6) + +#define ISO15693_3_RESP_FLAG_NONE (0U) +#define ISO15693_3_RESP_FLAG_ERROR (1U << 0) +#define ISO15693_3_RESP_FLAG_EXTENSION (1U << 3) + +#define ISO15693_3_RESP_ERROR_NOT_SUPPORTED (0x01U) +#define ISO15693_3_RESP_ERROR_FORMAT (0x02U) +#define ISO15693_3_RESP_ERROR_OPTION (0x03U) +#define ISO15693_3_RESP_ERROR_UNKNOWN (0x0FU) +#define ISO15693_3_RESP_ERROR_BLOCK_UNAVAILABLE (0x10U) +#define ISO15693_3_RESP_ERROR_BLOCK_ALREADY_LOCKED (0x11U) +#define ISO15693_3_RESP_ERROR_BLOCK_LOCKED (0x12U) +#define ISO15693_3_RESP_ERROR_BLOCK_WRITE (0x13U) +#define ISO15693_3_RESP_ERROR_BLOCK_LOCK (0x14U) +#define ISO15693_3_RESP_ERROR_CUSTOM_START (0xA0U) +#define ISO15693_3_RESP_ERROR_CUSTOM_END (0xDFU) + +#define ISO15693_3_CMD_MANDATORY_START (0x01U) +#define ISO15693_3_CMD_INVENTORY (0x01U) +#define ISO15693_3_CMD_STAY_QUIET (0x02U) +#define ISO15693_3_CMD_MANDATORY_RFU (0x03U) +#define ISO15693_3_CMD_OPTIONAL_START (0x20U) +#define ISO15693_3_CMD_READ_BLOCK (0x20U) +#define ISO15693_3_CMD_WRITE_BLOCK (0x21U) +#define ISO15693_3_CMD_LOCK_BLOCK (0x22U) +#define ISO15693_3_CMD_READ_MULTI_BLOCKS (0x23U) +#define ISO15693_3_CMD_WRITE_MULTI_BLOCKS (0x24U) +#define ISO15693_3_CMD_SELECT (0x25U) +#define ISO15693_3_CMD_RESET_TO_READY (0x26U) +#define ISO15693_3_CMD_WRITE_AFI (0x27U) +#define ISO15693_3_CMD_LOCK_AFI (0x28U) +#define ISO15693_3_CMD_WRITE_DSFID (0x29U) +#define ISO15693_3_CMD_LOCK_DSFID (0x2AU) +#define ISO15693_3_CMD_GET_SYS_INFO (0x2BU) +#define ISO15693_3_CMD_GET_BLOCKS_SECURITY (0x2CU) +#define ISO15693_3_CMD_OPTIONAL_RFU (0x2DU) +#define ISO15693_3_CMD_CUSTOM_START (0xA0U) + +#define ISO15693_3_MANDATORY_COUNT (ISO15693_3_CMD_MANDATORY_RFU - ISO15693_3_CMD_MANDATORY_START) +#define ISO15693_3_OPTIONAL_COUNT (ISO15693_3_CMD_OPTIONAL_RFU - ISO15693_3_CMD_OPTIONAL_START) + +#define ISO15693_3_SYSINFO_FLAG_DSFID (1U << 0) +#define ISO15693_3_SYSINFO_FLAG_AFI (1U << 1) +#define ISO15693_3_SYSINFO_FLAG_MEMORY (1U << 2) +#define ISO15693_3_SYSINFO_FLAG_IC_REF (1U << 3) + +typedef enum { + Iso15693_3ErrorNone, + Iso15693_3ErrorNotPresent, + Iso15693_3ErrorBufferEmpty, + Iso15693_3ErrorBufferOverflow, + Iso15693_3ErrorFieldOff, + Iso15693_3ErrorWrongCrc, + Iso15693_3ErrorTimeout, + Iso15693_3ErrorFormat, + Iso15693_3ErrorIgnore, + Iso15693_3ErrorNotSupported, + Iso15693_3ErrorUidMismatch, + Iso15693_3ErrorFullyHandled, + Iso15693_3ErrorUnexpectedResponse, + Iso15693_3ErrorInternal, + Iso15693_3ErrorCustom, + Iso15693_3ErrorUnknown, +} Iso15693_3Error; + +typedef struct { + uint8_t flags; + uint8_t dsfid; + uint8_t afi; + uint8_t ic_ref; + uint16_t block_count; + uint8_t block_size; +} Iso15693_3SystemInfo; + +typedef struct { + bool dsfid; + bool afi; +} Iso15693_3LockBits; + +typedef struct { + Iso15693_3LockBits lock_bits; +} Iso15693_3Settings; + +typedef struct { + uint8_t uid[ISO15693_3_UID_SIZE]; + Iso15693_3SystemInfo system_info; + Iso15693_3Settings settings; + SimpleArray* block_data; + SimpleArray* block_security; +} Iso15693_3Data; + +Iso15693_3Data* iso15693_3_alloc(); + +void iso15693_3_free(Iso15693_3Data* data); + +void iso15693_3_reset(Iso15693_3Data* data); + +void iso15693_3_copy(Iso15693_3Data* data, const Iso15693_3Data* other); + +bool iso15693_3_verify(Iso15693_3Data* data, const FuriString* device_type); + +bool iso15693_3_load(Iso15693_3Data* data, FlipperFormat* ff, uint32_t version); + +bool iso15693_3_save(const Iso15693_3Data* data, FlipperFormat* ff); + +bool iso15693_3_is_equal(const Iso15693_3Data* data, const Iso15693_3Data* other); + +const char* iso15693_3_get_device_name(const Iso15693_3Data* data, NfcDeviceNameType name_type); + +const uint8_t* iso15693_3_get_uid(const Iso15693_3Data* data, size_t* uid_len); + +bool iso15693_3_set_uid(Iso15693_3Data* data, const uint8_t* uid, size_t uid_len); + +Iso15693_3Data* iso15693_3_get_base_data(const Iso15693_3Data* data); + +// Getters and tests + +bool iso15693_3_is_block_locked(const Iso15693_3Data* data, uint8_t block_index); + +uint8_t iso15693_3_get_manufacturer_id(const Iso15693_3Data* data); + +uint16_t iso15693_3_get_block_count(const Iso15693_3Data* data); + +uint8_t iso15693_3_get_block_size(const Iso15693_3Data* data); + +const uint8_t* iso15693_3_get_block_data(const Iso15693_3Data* data, uint8_t block_index); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3_device_defs.h b/lib/nfc/protocols/iso15693_3/iso15693_3_device_defs.h new file mode 100644 index 00000000000..69a73476e61 --- /dev/null +++ b/lib/nfc/protocols/iso15693_3/iso15693_3_device_defs.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +extern const NfcDeviceBase nfc_device_iso15693_3; diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3_i.c b/lib/nfc/protocols/iso15693_3/iso15693_3_i.c new file mode 100644 index 00000000000..3d8d95c3a1d --- /dev/null +++ b/lib/nfc/protocols/iso15693_3/iso15693_3_i.c @@ -0,0 +1,263 @@ +#include "iso15693_3_i.h" + +bool iso15693_3_error_response_parse(Iso15693_3Error* error, const BitBuffer* buf) { + furi_assert(error); + + if(bit_buffer_get_size_bytes(buf) == 0) { + // YEET! + *error = Iso15693_3ErrorBufferEmpty; + return true; + } + + typedef struct { + uint8_t flags; + uint8_t error; + } ErrorResponseLayout; + + const ErrorResponseLayout* resp = (const ErrorResponseLayout*)bit_buffer_get_data(buf); + + if((resp->flags & ISO15693_3_RESP_FLAG_ERROR) == 0) { + // No error flag is set, the data does not contain an error frame + return false; + } else if(bit_buffer_get_size_bytes(buf) < sizeof(ErrorResponseLayout)) { + // Error bit is set, but not enough data to determine the error + *error = Iso15693_3ErrorUnexpectedResponse; + return true; + } else if( + resp->error >= ISO15693_3_RESP_ERROR_CUSTOM_START && + resp->error <= ISO15693_3_RESP_ERROR_CUSTOM_END) { + // Custom vendor-specific error, must be checked in the respective protocol implementation + *error = Iso15693_3ErrorCustom; + return true; + } + + switch(resp->error) { + case ISO15693_3_RESP_ERROR_NOT_SUPPORTED: + case ISO15693_3_RESP_ERROR_OPTION: + *error = Iso15693_3ErrorNotSupported; + break; + case ISO15693_3_RESP_ERROR_FORMAT: + *error = Iso15693_3ErrorFormat; + break; + case ISO15693_3_RESP_ERROR_BLOCK_UNAVAILABLE: + case ISO15693_3_RESP_ERROR_BLOCK_ALREADY_LOCKED: + case ISO15693_3_RESP_ERROR_BLOCK_LOCKED: + case ISO15693_3_RESP_ERROR_BLOCK_WRITE: + case ISO15693_3_RESP_ERROR_BLOCK_LOCK: + *error = Iso15693_3ErrorInternal; + break; + case ISO15693_3_RESP_ERROR_UNKNOWN: + default: + *error = Iso15693_3ErrorUnknown; + } + + return true; +} + +Iso15693_3Error iso15693_3_inventory_response_parse(uint8_t* data, const BitBuffer* buf) { + furi_assert(data); + + Iso15693_3Error ret = Iso15693_3ErrorNone; + + do { + if(iso15693_3_error_response_parse(&ret, buf)) break; + + typedef struct { + uint8_t flags; + uint8_t dsfid; + uint8_t uid[ISO15693_3_UID_SIZE]; + } InventoryResponseLayout; + + if(bit_buffer_get_size_bytes(buf) != sizeof(InventoryResponseLayout)) { + ret = Iso15693_3ErrorUnexpectedResponse; + break; + } + + const InventoryResponseLayout* resp = + (const InventoryResponseLayout*)bit_buffer_get_data(buf); + // Reverse UID for backward compatibility + for(uint32_t i = 0; i < ISO15693_3_UID_SIZE; ++i) { + data[i] = resp->uid[ISO15693_3_UID_SIZE - i - 1]; + } + + } while(false); + + return ret; +} + +Iso15693_3Error + iso15693_3_system_info_response_parse(Iso15693_3SystemInfo* data, const BitBuffer* buf) { + furi_assert(data); + + Iso15693_3Error ret = Iso15693_3ErrorNone; + + do { + if(iso15693_3_error_response_parse(&ret, buf)) break; + + typedef struct { + uint8_t flags; + uint8_t info_flags; + uint8_t uid[ISO15693_3_UID_SIZE]; + uint8_t extra[]; + } SystemInfoResponseLayout; + + if(bit_buffer_get_size_bytes(buf) < sizeof(SystemInfoResponseLayout)) { + ret = Iso15693_3ErrorUnexpectedResponse; + break; + } + + const SystemInfoResponseLayout* resp = + (const SystemInfoResponseLayout*)bit_buffer_get_data(buf); + + const uint8_t* extra = resp->extra; + const size_t extra_size = (resp->info_flags & ISO15693_3_SYSINFO_FLAG_DSFID ? 1 : 0) + + (resp->info_flags & ISO15693_3_SYSINFO_FLAG_AFI ? 1 : 0) + + (resp->info_flags & ISO15693_3_SYSINFO_FLAG_MEMORY ? 2 : 0) + + (resp->info_flags & ISO15693_3_SYSINFO_FLAG_IC_REF ? 1 : 0); + + if(extra_size != bit_buffer_get_size_bytes(buf) - sizeof(SystemInfoResponseLayout)) { + ret = Iso15693_3ErrorUnexpectedResponse; + break; + } + + data->flags = resp->info_flags; + + if(data->flags & ISO15693_3_SYSINFO_FLAG_DSFID) { + data->dsfid = *extra++; + } + + if(data->flags & ISO15693_3_SYSINFO_FLAG_AFI) { + data->afi = *extra++; + } + + if(data->flags & ISO15693_3_SYSINFO_FLAG_MEMORY) { + // Add 1 to get actual values + data->block_count = *extra++ + 1; + data->block_size = (*extra++ & 0x1F) + 1; + } + + if(data->flags & ISO15693_3_SYSINFO_FLAG_IC_REF) { + data->ic_ref = *extra; + } + + } while(false); + + return ret; +} + +Iso15693_3Error + iso15693_3_read_block_response_parse(uint8_t* data, uint8_t block_size, const BitBuffer* buf) { + furi_assert(data); + + Iso15693_3Error ret = Iso15693_3ErrorNone; + + do { + if(iso15693_3_error_response_parse(&ret, buf)) break; + + typedef struct { + uint8_t flags; + uint8_t block_data[]; + } ReadBlockResponseLayout; + + const size_t buf_size = bit_buffer_get_size_bytes(buf); + const size_t received_block_size = buf_size - sizeof(ReadBlockResponseLayout); + + if(buf_size <= sizeof(ReadBlockResponseLayout) || received_block_size != block_size) { + ret = Iso15693_3ErrorUnexpectedResponse; + break; + } + + const ReadBlockResponseLayout* resp = + (const ReadBlockResponseLayout*)bit_buffer_get_data(buf); + memcpy(data, resp->block_data, received_block_size); + + } while(false); + + return ret; +} + +Iso15693_3Error iso15693_3_get_block_security_response_parse( + uint8_t* data, + uint16_t block_count, + const BitBuffer* buf) { + furi_assert(data); + furi_assert(block_count); + Iso15693_3Error ret = Iso15693_3ErrorNone; + + do { + if(iso15693_3_error_response_parse(&ret, buf)) break; + + typedef struct { + uint8_t flags; + uint8_t block_security[]; + } GetBlockSecurityResponseLayout; + + const size_t buf_size = bit_buffer_get_size_bytes(buf); + const size_t received_block_count = buf_size - sizeof(GetBlockSecurityResponseLayout); + + if(buf_size <= sizeof(GetBlockSecurityResponseLayout) || + received_block_count != block_count) { + ret = Iso15693_3ErrorUnexpectedResponse; + break; + } + + const GetBlockSecurityResponseLayout* resp = + (const GetBlockSecurityResponseLayout*)bit_buffer_get_data(buf); + + memcpy(data, resp->block_security, received_block_count); + + } while(false); + + return ret; +} + +void iso15693_3_append_uid(const Iso15693_3Data* data, BitBuffer* buf) { + for(size_t i = 0; i < ISO15693_3_UID_SIZE; ++i) { + // Reverse the UID + bit_buffer_append_byte(buf, data->uid[ISO15693_3_UID_SIZE - i - 1]); + } +} + +void iso15693_3_append_block(const Iso15693_3Data* data, uint8_t block_num, BitBuffer* buf) { + furi_assert(block_num < data->system_info.block_count); + + const uint32_t block_offset = block_num * data->system_info.block_size; + const uint8_t* block_data = simple_array_cget(data->block_data, block_offset); + + bit_buffer_append_bytes(buf, block_data, data->system_info.block_size); +} + +void iso15693_3_set_block_locked(Iso15693_3Data* data, uint8_t block_index, bool locked) { + furi_assert(data); + furi_assert(block_index < data->system_info.block_count); + + *(uint8_t*)simple_array_get(data->block_security, block_index) = locked ? 1 : 0; +} + +void iso15693_3_set_block_data( + Iso15693_3Data* data, + uint8_t block_num, + const uint8_t* block_data, + size_t block_data_size) { + furi_assert(block_num < data->system_info.block_count); + furi_assert(block_data_size == data->system_info.block_size); + + const uint32_t block_offset = block_num * data->system_info.block_size; + uint8_t* block = simple_array_get(data->block_data, block_offset); + + memcpy(block, block_data, block_data_size); +} + +void iso15693_3_append_block_security( + const Iso15693_3Data* data, + uint8_t block_num, + BitBuffer* buf) { + bit_buffer_append_byte(buf, *(uint8_t*)simple_array_cget(data->block_security, block_num)); +} + +bool iso15693_3_is_equal_uid(const Iso15693_3Data* data, const uint8_t* uid) { + for(size_t i = 0; i < ISO15693_3_UID_SIZE; ++i) { + if(data->uid[i] != uid[ISO15693_3_UID_SIZE - i - 1]) return false; + } + return true; +} diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3_i.h b/lib/nfc/protocols/iso15693_3/iso15693_3_i.h new file mode 100644 index 00000000000..253bda7f598 --- /dev/null +++ b/lib/nfc/protocols/iso15693_3/iso15693_3_i.h @@ -0,0 +1,58 @@ +#pragma once + +#include "iso15693_3.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Check if the buffer contains an error frame and if it does, determine + * the error type. + * NOTE: No changes are done to the result if no error is present. + * + * @param [out] data Pointer to the resulting error value. + * @param [in] buf Data buffer to be checked + * + * @return True if data contains an error frame or is empty, false otherwise + */ +bool iso15693_3_error_response_parse(Iso15693_3Error* error, const BitBuffer* buf); + +Iso15693_3Error iso15693_3_inventory_response_parse(uint8_t* data, const BitBuffer* buf); + +Iso15693_3Error + iso15693_3_system_info_response_parse(Iso15693_3SystemInfo* data, const BitBuffer* buf); + +Iso15693_3Error + iso15693_3_read_block_response_parse(uint8_t* data, uint8_t block_size, const BitBuffer* buf); + +Iso15693_3Error iso15693_3_get_block_security_response_parse( + uint8_t* data, + uint16_t block_count, + const BitBuffer* buf); + +void iso15693_3_append_uid(const Iso15693_3Data* data, BitBuffer* buf); + +void iso15693_3_append_block(const Iso15693_3Data* data, uint8_t block_num, BitBuffer* buf); + +void iso15693_3_set_block_locked(Iso15693_3Data* data, uint8_t block_index, bool locked); + +void iso15693_3_set_block_data( + Iso15693_3Data* data, + uint8_t block_num, + const uint8_t* block_data, + size_t block_data_size); + +void iso15693_3_append_block_security( + const Iso15693_3Data* data, + uint8_t block_num, + BitBuffer* buf); + +// NOTE: the uid parameter has reversed byte order with respect to data +bool iso15693_3_is_equal_uid(const Iso15693_3Data* data, const uint8_t* uid); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3_listener.c b/lib/nfc/protocols/iso15693_3/iso15693_3_listener.c new file mode 100644 index 00000000000..84e75085855 --- /dev/null +++ b/lib/nfc/protocols/iso15693_3/iso15693_3_listener.c @@ -0,0 +1,110 @@ +#include "iso15693_3_listener_i.h" + +#include + +#include +#include +#include + +#define TAG "Iso15693_3Listener" + +#define ISO15693_3_LISTENER_BUFFER_SIZE (64U) + +Iso15693_3Listener* iso15693_3_listener_alloc(Nfc* nfc, Iso15693_3Data* data) { + furi_assert(nfc); + + Iso15693_3Listener* instance = malloc(sizeof(Iso15693_3Listener)); + instance->nfc = nfc; + instance->data = data; + + instance->tx_buffer = bit_buffer_alloc(ISO15693_3_LISTENER_BUFFER_SIZE); + + instance->iso15693_3_event.data = &instance->iso15693_3_event_data; + instance->generic_event.protocol = NfcProtocolIso15693_3; + instance->generic_event.instance = instance; + instance->generic_event.event_data = &instance->iso15693_3_event; + + nfc_set_fdt_listen_fc(instance->nfc, ISO15693_3_FDT_LISTEN_FC); + nfc_config(instance->nfc, NfcModeListener, NfcTechIso15693); + + return instance; +} + +void iso15693_3_listener_free(Iso15693_3Listener* instance) { + furi_assert(instance); + + bit_buffer_free(instance->tx_buffer); + + free(instance); +} + +void iso15693_3_listener_set_callback( + Iso15693_3Listener* instance, + NfcGenericCallback callback, + void* context) { + furi_assert(instance); + + instance->callback = callback; + instance->context = context; +} + +const Iso15693_3Data* iso15693_3_listener_get_data(Iso15693_3Listener* instance) { + furi_assert(instance); + furi_assert(instance->data); + + return instance->data; +} + +NfcCommand iso15693_3_listener_run(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.protocol == NfcProtocolInvalid); + furi_assert(event.event_data); + + Iso15693_3Listener* instance = context; + NfcEvent* nfc_event = event.event_data; + NfcCommand command = NfcCommandContinue; + + if(nfc_event->type == NfcEventTypeRxEnd) { + BitBuffer* rx_buffer = nfc_event->data.buffer; + + if(iso13239_crc_check(Iso13239CrcTypeDefault, rx_buffer)) { + iso13239_crc_trim(rx_buffer); + + const Iso15693_3Error error = iso15693_3_listener_process_request(instance, rx_buffer); + + if(error == Iso15693_3ErrorNotSupported) { + if(instance->callback) { + instance->iso15693_3_event.type = Iso15693_3ListenerEventTypeCustomCommand; + instance->iso15693_3_event.data->buffer = rx_buffer; + command = instance->callback(instance->generic_event, instance->context); + } + + } else if(error == Iso15693_3ErrorUidMismatch) { + iso15693_3_listener_process_uid_mismatch(instance, rx_buffer); + } + + } else if(bit_buffer_get_size(rx_buffer) == 0) { + // Special case: Single EOF + const Iso15693_3Error error = iso15693_3_listener_process_single_eof(instance); + if(error == Iso15693_3ErrorUnexpectedResponse) { + if(instance->callback) { + instance->iso15693_3_event.type = Iso15693_3ListenerEventTypeSingleEof; + command = instance->callback(instance->generic_event, instance->context); + } + } + } else { + FURI_LOG_D( + TAG, "Wrong CRC, buffer size: %zu", bit_buffer_get_size(nfc_event->data.buffer)); + } + } + + return command; +} + +const NfcListenerBase nfc_listener_iso15693_3 = { + .alloc = (NfcListenerAlloc)iso15693_3_listener_alloc, + .free = (NfcListenerFree)iso15693_3_listener_free, + .set_callback = (NfcListenerSetCallback)iso15693_3_listener_set_callback, + .get_data = (NfcListenerGetData)iso15693_3_listener_get_data, + .run = (NfcListenerRun)iso15693_3_listener_run, +}; diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3_listener.h b/lib/nfc/protocols/iso15693_3/iso15693_3_listener.h new file mode 100644 index 00000000000..c69d18db4e7 --- /dev/null +++ b/lib/nfc/protocols/iso15693_3/iso15693_3_listener.h @@ -0,0 +1,30 @@ +#pragma once + +#include + +#include "iso15693_3.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct Iso15693_3Listener Iso15693_3Listener; + +typedef enum { + Iso15693_3ListenerEventTypeFieldOff, + Iso15693_3ListenerEventTypeCustomCommand, + Iso15693_3ListenerEventTypeSingleEof, +} Iso15693_3ListenerEventType; + +typedef struct { + BitBuffer* buffer; +} Iso15693_3ListenerEventData; + +typedef struct { + Iso15693_3ListenerEventType type; + Iso15693_3ListenerEventData* data; +} Iso15693_3ListenerEvent; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3_listener_defs.h b/lib/nfc/protocols/iso15693_3/iso15693_3_listener_defs.h new file mode 100644 index 00000000000..93497512eb4 --- /dev/null +++ b/lib/nfc/protocols/iso15693_3/iso15693_3_listener_defs.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +extern const NfcListenerBase nfc_listener_iso15693_3; diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3_listener_i.c b/lib/nfc/protocols/iso15693_3/iso15693_3_listener_i.c new file mode 100644 index 00000000000..a8dec7ae33f --- /dev/null +++ b/lib/nfc/protocols/iso15693_3/iso15693_3_listener_i.c @@ -0,0 +1,883 @@ +#include "iso15693_3_listener_i.h" + +#include + +#define TAG "Iso15693_3Listener" + +typedef Iso15693_3Error (*Iso15693_3RequestHandler)( + Iso15693_3Listener* instance, + const uint8_t* data, + size_t data_size, + uint8_t flags); + +typedef struct { + Iso15693_3RequestHandler mandatory[ISO15693_3_MANDATORY_COUNT]; + Iso15693_3RequestHandler optional[ISO15693_3_OPTIONAL_COUNT]; +} Iso15693_3ListenerHandlerTable; + +static Iso15693_3Error + iso15693_3_listener_extension_handler(Iso15693_3Listener* instance, uint32_t command, ...) { + Iso15693_3Error error = Iso15693_3ErrorNone; + + do { + if(instance->extension_table == NULL) break; + + Iso15693_3ExtensionHandler handler = NULL; + + if(command < ISO15693_3_CMD_MANDATORY_RFU) { + const Iso15693_3ExtensionHandler* mandatory = instance->extension_table->mandatory; + handler = mandatory[command - ISO15693_3_CMD_MANDATORY_START]; + } else if(command >= ISO15693_3_CMD_OPTIONAL_START && command < ISO15693_3_CMD_OPTIONAL_RFU) { + const Iso15693_3ExtensionHandler* optional = instance->extension_table->optional; + handler = optional[command - ISO15693_3_CMD_OPTIONAL_START]; + } + + if(handler == NULL) break; + + va_list args; + va_start(args, command); + + error = handler(instance->extension_context, args); + + va_end(args); + + } while(false); + return error; +} + +static Iso15693_3Error iso15693_3_listener_inventory_handler( + Iso15693_3Listener* instance, + const uint8_t* data, + size_t data_size, + uint8_t flags) { + Iso15693_3Error error = Iso15693_3ErrorNone; + + do { + const bool afi_flag = flags & ISO15693_3_REQ_FLAG_T5_AFI_PRESENT; + const size_t data_size_min = sizeof(uint8_t) * (afi_flag ? 2 : 1); + + if(data_size < data_size_min) { + error = Iso15693_3ErrorFormat; + break; + } + + if(afi_flag) { + const uint8_t afi = *data++; + // When AFI flag is set, ignore non-matching requests + if(afi != instance->data->system_info.afi) break; + } + + const uint8_t mask_len = *data++; + const size_t data_size_required = data_size_min + mask_len; + + if(data_size != data_size_required) { + error = Iso15693_3ErrorFormat; + break; + } + + if(mask_len != 0) { + // TODO FL-3633: Take mask_len and mask_value into account (if present) + } + + error = iso15693_3_listener_extension_handler(instance, ISO15693_3_CMD_INVENTORY); + if(error != Iso15693_3ErrorNone) break; + + bit_buffer_append_byte(instance->tx_buffer, instance->data->system_info.dsfid); // DSFID + iso15693_3_append_uid(instance->data, instance->tx_buffer); // UID + } while(false); + + return error; +} + +static Iso15693_3Error iso15693_3_listener_stay_quiet_handler( + Iso15693_3Listener* instance, + const uint8_t* data, + size_t data_size, + uint8_t flags) { + UNUSED(data); + UNUSED(data_size); + UNUSED(flags); + + instance->state = Iso15693_3ListenerStateQuiet; + return Iso15693_3ErrorIgnore; +} + +static Iso15693_3Error iso15693_3_listener_read_block_handler( + Iso15693_3Listener* instance, + const uint8_t* data, + size_t data_size, + uint8_t flags) { + Iso15693_3Error error = Iso15693_3ErrorNone; + + do { + typedef struct { + uint8_t block_num; + } Iso15693_3ReadBlockRequestLayout; + + const Iso15693_3ReadBlockRequestLayout* request = + (const Iso15693_3ReadBlockRequestLayout*)data; + + if(data_size != sizeof(Iso15693_3ReadBlockRequestLayout)) { + error = Iso15693_3ErrorFormat; + break; + } + + const uint32_t block_index = request->block_num; + const uint32_t block_count_max = instance->data->system_info.block_count; + + if(block_index >= block_count_max) { + error = Iso15693_3ErrorInternal; + break; + } + + error = iso15693_3_listener_extension_handler( + instance, ISO15693_3_CMD_READ_BLOCK, block_index); + if(error != Iso15693_3ErrorNone) break; + + if(flags & ISO15693_3_REQ_FLAG_T4_OPTION) { + iso15693_3_append_block_security( + instance->data, block_index, instance->tx_buffer); // Block security (optional) + } + + iso15693_3_append_block(instance->data, block_index, instance->tx_buffer); // Block data + } while(false); + + return error; +} + +static Iso15693_3Error iso15693_3_listener_write_block_handler( + Iso15693_3Listener* instance, + const uint8_t* data, + size_t data_size, + uint8_t flags) { + Iso15693_3Error error = Iso15693_3ErrorNone; + + do { + typedef struct { + uint8_t block_num; + uint8_t block_data[]; + } Iso15693_3WriteBlockRequestLayout; + + const Iso15693_3WriteBlockRequestLayout* request = + (const Iso15693_3WriteBlockRequestLayout*)data; + + instance->session_state.wait_for_eof = flags & ISO15693_3_REQ_FLAG_T4_OPTION; + + if(data_size <= sizeof(Iso15693_3WriteBlockRequestLayout)) { + error = Iso15693_3ErrorFormat; + break; + } + + const uint32_t block_index = request->block_num; + const uint32_t block_count_max = instance->data->system_info.block_count; + const uint32_t block_size_max = instance->data->system_info.block_size; + const size_t block_size_received = data_size - sizeof(Iso15693_3WriteBlockRequestLayout); + + if(block_index >= block_count_max) { + error = Iso15693_3ErrorInternal; + break; + } else if(block_size_received != block_size_max) { + error = Iso15693_3ErrorInternal; + break; + } else if(iso15693_3_is_block_locked(instance->data, block_index)) { + error = Iso15693_3ErrorInternal; + break; + } + + error = iso15693_3_listener_extension_handler( + instance, ISO15693_3_CMD_WRITE_BLOCK, block_index, request->block_data); + if(error != Iso15693_3ErrorNone) break; + + iso15693_3_set_block_data( + instance->data, block_index, request->block_data, block_size_received); + } while(false); + + return error; +} + +static Iso15693_3Error iso15693_3_listener_lock_block_handler( + Iso15693_3Listener* instance, + const uint8_t* data, + size_t data_size, + uint8_t flags) { + Iso15693_3Error error = Iso15693_3ErrorNone; + + do { + typedef struct { + uint8_t block_num; + } Iso15693_3LockBlockRequestLayout; + + const Iso15693_3LockBlockRequestLayout* request = + (const Iso15693_3LockBlockRequestLayout*)data; + + instance->session_state.wait_for_eof = flags & ISO15693_3_REQ_FLAG_T4_OPTION; + + if(data_size != sizeof(Iso15693_3LockBlockRequestLayout)) { + error = Iso15693_3ErrorFormat; + break; + } + + const uint32_t block_index = request->block_num; + const uint32_t block_count_max = instance->data->system_info.block_count; + + if(block_index >= block_count_max) { + error = Iso15693_3ErrorInternal; + break; + } else if(iso15693_3_is_block_locked(instance->data, block_index)) { + error = Iso15693_3ErrorInternal; + break; + } + + error = iso15693_3_listener_extension_handler( + instance, ISO15693_3_CMD_LOCK_BLOCK, block_index); + if(error != Iso15693_3ErrorNone) break; + + iso15693_3_set_block_locked(instance->data, block_index, true); + } while(false); + + return error; +} + +static Iso15693_3Error iso15693_3_listener_read_multi_blocks_handler( + Iso15693_3Listener* instance, + const uint8_t* data, + size_t data_size, + uint8_t flags) { + Iso15693_3Error error = Iso15693_3ErrorNone; + + do { + typedef struct { + uint8_t first_block_num; + uint8_t block_count; + } Iso15693_3ReadMultiBlocksRequestLayout; + + const Iso15693_3ReadMultiBlocksRequestLayout* request = + (const Iso15693_3ReadMultiBlocksRequestLayout*)data; + + if(data_size != sizeof(Iso15693_3ReadMultiBlocksRequestLayout)) { + error = Iso15693_3ErrorFormat; + break; + } + + const uint32_t block_index_start = request->first_block_num; + const uint32_t block_index_end = block_index_start + request->block_count; + + const uint32_t block_count = request->block_count + 1; + const uint32_t block_count_max = instance->data->system_info.block_count; + const uint32_t block_count_available = block_count_max - block_index_start; + + if(block_count > block_count_available) { + error = Iso15693_3ErrorInternal; + break; + } + + error = iso15693_3_listener_extension_handler( + instance, + ISO15693_3_CMD_READ_MULTI_BLOCKS, + (uint32_t)block_index_start, + (uint32_t)block_index_end); + if(error != Iso15693_3ErrorNone) break; + + for(uint32_t i = block_index_start; i <= block_index_end; ++i) { + if(flags & ISO15693_3_REQ_FLAG_T4_OPTION) { + iso15693_3_append_block_security( + instance->data, i, instance->tx_buffer); // Block security (optional) + } + iso15693_3_append_block(instance->data, i, instance->tx_buffer); // Block data + } + } while(false); + + return error; +} + +static Iso15693_3Error iso15693_3_listener_write_multi_blocks_handler( + Iso15693_3Listener* instance, + const uint8_t* data, + size_t data_size, + uint8_t flags) { + Iso15693_3Error error = Iso15693_3ErrorNone; + + do { + typedef struct { + uint8_t first_block_num; + uint8_t block_count; + uint8_t block_data[]; + } Iso15693_3WriteMultiBlocksRequestLayout; + + const Iso15693_3WriteMultiBlocksRequestLayout* request = + (const Iso15693_3WriteMultiBlocksRequestLayout*)data; + + instance->session_state.wait_for_eof = flags & ISO15693_3_REQ_FLAG_T4_OPTION; + + if(data_size <= sizeof(Iso15693_3WriteMultiBlocksRequestLayout)) { + error = Iso15693_3ErrorFormat; + break; + } + + const uint32_t block_index_start = request->first_block_num; + const uint32_t block_index_end = block_index_start + request->block_count; + + const uint32_t block_count = request->block_count + 1; + const uint32_t block_count_max = instance->data->system_info.block_count; + const uint32_t block_count_available = block_count_max - block_index_start; + + const size_t block_data_size = data_size - sizeof(Iso15693_3WriteMultiBlocksRequestLayout); + const size_t block_size = block_data_size / block_count; + const size_t block_size_max = instance->data->system_info.block_size; + + if(block_count > block_count_available) { + error = Iso15693_3ErrorInternal; + break; + } else if(block_size != block_size_max) { + error = Iso15693_3ErrorInternal; + break; + } + + error = iso15693_3_listener_extension_handler( + instance, ISO15693_3_CMD_WRITE_MULTI_BLOCKS, block_index_start, block_index_end); + if(error != Iso15693_3ErrorNone) break; + + for(uint32_t i = block_index_start; i <= block_index_end; ++i) { + if(iso15693_3_is_block_locked(instance->data, i)) { + error = Iso15693_3ErrorInternal; + break; + } + } + + if(error != Iso15693_3ErrorNone) break; + + for(uint32_t i = block_index_start; i < block_count + request->first_block_num; ++i) { + const uint8_t* block_data = &request->block_data[block_size * i]; + iso15693_3_set_block_data(instance->data, i, block_data, block_size); + } + } while(false); + + return error; +} + +static Iso15693_3Error iso15693_3_listener_select_handler( + Iso15693_3Listener* instance, + const uint8_t* data, + size_t data_size, + uint8_t flags) { + UNUSED(data); + UNUSED(data_size); + + Iso15693_3Error error = Iso15693_3ErrorNone; + + do { + if(!(flags & ISO15693_3_REQ_FLAG_T4_ADDRESSED)) { + error = Iso15693_3ErrorFormat; + break; + } + + instance->state = Iso15693_3ListenerStateSelected; + } while(false); + + return error; +} + +static Iso15693_3Error iso15693_3_listener_reset_to_ready_handler( + Iso15693_3Listener* instance, + const uint8_t* data, + size_t data_size, + uint8_t flags) { + UNUSED(data); + UNUSED(data_size); + UNUSED(flags); + + instance->state = Iso15693_3ListenerStateReady; + return Iso15693_3ErrorNone; +} + +static Iso15693_3Error iso15693_3_listener_write_afi_handler( + Iso15693_3Listener* instance, + const uint8_t* data, + size_t data_size, + uint8_t flags) { + Iso15693_3Error error = Iso15693_3ErrorNone; + + do { + typedef struct { + uint8_t afi; + } Iso15693_3WriteAfiRequestLayout; + + const Iso15693_3WriteAfiRequestLayout* request = + (const Iso15693_3WriteAfiRequestLayout*)data; + + instance->session_state.wait_for_eof = flags & ISO15693_3_REQ_FLAG_T4_OPTION; + + if(data_size <= sizeof(Iso15693_3WriteAfiRequestLayout)) { + error = Iso15693_3ErrorFormat; + break; + } else if(instance->data->settings.lock_bits.afi) { + error = Iso15693_3ErrorInternal; + break; + } + + error = iso15693_3_listener_extension_handler(instance, ISO15693_3_CMD_WRITE_AFI); + if(error != Iso15693_3ErrorNone) break; + + instance->data->system_info.afi = request->afi; + } while(false); + + return error; +} + +static Iso15693_3Error iso15693_3_listener_lock_afi_handler( + Iso15693_3Listener* instance, + const uint8_t* data, + size_t data_size, + uint8_t flags) { + UNUSED(data); + UNUSED(data_size); + + Iso15693_3Error error = Iso15693_3ErrorNone; + + do { + instance->session_state.wait_for_eof = flags & ISO15693_3_REQ_FLAG_T4_OPTION; + + Iso15693_3LockBits* lock_bits = &instance->data->settings.lock_bits; + + if(lock_bits->afi) { + error = Iso15693_3ErrorInternal; + break; + } + + error = iso15693_3_listener_extension_handler(instance, ISO15693_3_CMD_LOCK_AFI); + if(error != Iso15693_3ErrorNone) break; + + lock_bits->afi = true; + } while(false); + + return error; +} + +static Iso15693_3Error iso15693_3_listener_write_dsfid_handler( + Iso15693_3Listener* instance, + const uint8_t* data, + size_t data_size, + uint8_t flags) { + Iso15693_3Error error = Iso15693_3ErrorNone; + + do { + typedef struct { + uint8_t dsfid; + } Iso15693_3WriteDsfidRequestLayout; + + const Iso15693_3WriteDsfidRequestLayout* request = + (const Iso15693_3WriteDsfidRequestLayout*)data; + + instance->session_state.wait_for_eof = flags & ISO15693_3_REQ_FLAG_T4_OPTION; + + if(data_size <= sizeof(Iso15693_3WriteDsfidRequestLayout)) { + error = Iso15693_3ErrorFormat; + break; + } else if(instance->data->settings.lock_bits.dsfid) { + error = Iso15693_3ErrorInternal; + break; + } + + error = iso15693_3_listener_extension_handler(instance, ISO15693_3_CMD_WRITE_DSFID); + if(error != Iso15693_3ErrorNone) break; + + instance->data->system_info.dsfid = request->dsfid; + } while(false); + + return error; +} + +static Iso15693_3Error iso15693_3_listener_lock_dsfid_handler( + Iso15693_3Listener* instance, + const uint8_t* data, + size_t data_size, + uint8_t flags) { + UNUSED(data); + UNUSED(data_size); + + Iso15693_3Error error = Iso15693_3ErrorNone; + + do { + instance->session_state.wait_for_eof = flags & ISO15693_3_REQ_FLAG_T4_OPTION; + + Iso15693_3LockBits* lock_bits = &instance->data->settings.lock_bits; + + if(lock_bits->dsfid) { + error = Iso15693_3ErrorInternal; + break; + } + + error = iso15693_3_listener_extension_handler(instance, ISO15693_3_CMD_LOCK_DSFID); + if(error != Iso15693_3ErrorNone) break; + + lock_bits->dsfid = true; + } while(false); + + return error; +} + +static Iso15693_3Error iso15693_3_listener_get_system_info_handler( + Iso15693_3Listener* instance, + const uint8_t* data, + size_t data_size, + uint8_t flags) { + UNUSED(data); + UNUSED(data_size); + UNUSED(flags); + + Iso15693_3Error error = Iso15693_3ErrorNone; + + do { + const uint8_t system_flags = instance->data->system_info.flags; + bit_buffer_append_byte(instance->tx_buffer, system_flags); // System info flags + + iso15693_3_append_uid(instance->data, instance->tx_buffer); // UID + + if(system_flags & ISO15693_3_SYSINFO_FLAG_DSFID) { + bit_buffer_append_byte(instance->tx_buffer, instance->data->system_info.dsfid); + } + if(system_flags & ISO15693_3_SYSINFO_FLAG_AFI) { + bit_buffer_append_byte(instance->tx_buffer, instance->data->system_info.afi); + } + if(system_flags & ISO15693_3_SYSINFO_FLAG_MEMORY) { + const uint8_t memory_info[2] = { + instance->data->system_info.block_count - 1, + instance->data->system_info.block_size - 1, + }; + bit_buffer_append_bytes(instance->tx_buffer, memory_info, COUNT_OF(memory_info)); + } + if(system_flags & ISO15693_3_SYSINFO_FLAG_IC_REF) { + bit_buffer_append_byte(instance->tx_buffer, instance->data->system_info.ic_ref); + } + + } while(false); + + return error; +} + +static Iso15693_3Error iso15693_3_listener_get_multi_blocks_security_handler( + Iso15693_3Listener* instance, + const uint8_t* data, + size_t data_size, + uint8_t flags) { + UNUSED(flags); + + Iso15693_3Error error = Iso15693_3ErrorNone; + + do { + typedef struct { + uint8_t first_block_num; + uint8_t block_count; + } Iso15693_3GetMultiBlocksSecurityRequestLayout; + + const Iso15693_3GetMultiBlocksSecurityRequestLayout* request = + (const Iso15693_3GetMultiBlocksSecurityRequestLayout*)data; + + if(data_size < sizeof(Iso15693_3GetMultiBlocksSecurityRequestLayout)) { + error = Iso15693_3ErrorFormat; + break; + } + + const uint32_t block_index_start = request->first_block_num; + const uint32_t block_index_end = block_index_start + request->block_count; + + const uint32_t block_count_max = instance->data->system_info.block_count; + + if(block_index_end >= block_count_max) { + error = Iso15693_3ErrorInternal; + break; + } + + for(uint32_t i = block_index_start; i <= block_index_end; ++i) { + bit_buffer_append_byte( + instance->tx_buffer, iso15693_3_is_block_locked(instance->data, i) ? 1 : 0); + } + } while(false); + + return error; +} + +const Iso15693_3ListenerHandlerTable iso15693_3_handler_table = { + .mandatory = + { + iso15693_3_listener_inventory_handler, + iso15693_3_listener_stay_quiet_handler, + }, + .optional = + { + iso15693_3_listener_read_block_handler, + iso15693_3_listener_write_block_handler, + iso15693_3_listener_lock_block_handler, + iso15693_3_listener_read_multi_blocks_handler, + iso15693_3_listener_write_multi_blocks_handler, + iso15693_3_listener_select_handler, + iso15693_3_listener_reset_to_ready_handler, + iso15693_3_listener_write_afi_handler, + iso15693_3_listener_lock_afi_handler, + iso15693_3_listener_write_dsfid_handler, + iso15693_3_listener_lock_dsfid_handler, + iso15693_3_listener_get_system_info_handler, + iso15693_3_listener_get_multi_blocks_security_handler, + }, +}; + +static Iso15693_3Error iso15693_3_listener_handle_standard_request( + Iso15693_3Listener* instance, + const uint8_t* data, + size_t data_size, + uint8_t command, + uint8_t flags) { + Iso15693_3Error error = Iso15693_3ErrorNone; + + do { + Iso15693_3RequestHandler handler = NULL; + + if(command < ISO15693_3_CMD_MANDATORY_RFU) { + handler = iso15693_3_handler_table.mandatory[command - ISO15693_3_CMD_MANDATORY_START]; + } else if(command >= ISO15693_3_CMD_OPTIONAL_START && command < ISO15693_3_CMD_OPTIONAL_RFU) { + handler = iso15693_3_handler_table.optional[command - ISO15693_3_CMD_OPTIONAL_START]; + } + + if(handler == NULL) { + error = Iso15693_3ErrorNotSupported; + break; + } + + bit_buffer_reset(instance->tx_buffer); + bit_buffer_append_byte(instance->tx_buffer, ISO15693_3_RESP_FLAG_NONE); + + error = handler(instance, data, data_size, flags); + + // The request was fully handled in the protocol extension, no further action necessary + if(error == Iso15693_3ErrorFullyHandled) { + error = Iso15693_3ErrorNone; + } + + // Several commands may not require an answer + if(error == Iso15693_3ErrorFormat || error == Iso15693_3ErrorIgnore) break; + + if(error != Iso15693_3ErrorNone) { + bit_buffer_reset(instance->tx_buffer); + bit_buffer_append_byte(instance->tx_buffer, ISO15693_3_RESP_FLAG_ERROR); + bit_buffer_append_byte(instance->tx_buffer, ISO15693_3_RESP_ERROR_UNKNOWN); + } + + Iso15693_3ListenerSessionState* session_state = &instance->session_state; + + if(!session_state->wait_for_eof) { + error = iso15693_3_listener_send_frame(instance, instance->tx_buffer); + } + + } while(false); + + return error; +} + +static inline Iso15693_3Error iso15693_3_listener_handle_custom_request( + Iso15693_3Listener* instance, + const uint8_t* data, + size_t data_size) { + Iso15693_3Error error; + + do { + typedef struct { + uint8_t manufacturer; + uint8_t extra[]; + } Iso15693_3CustomRequestLayout; + + if(data_size < sizeof(Iso15693_3CustomRequestLayout)) { + error = Iso15693_3ErrorFormat; + break; + } + + const Iso15693_3CustomRequestLayout* request = (const Iso15693_3CustomRequestLayout*)data; + + if(request->manufacturer != iso15693_3_get_manufacturer_id(instance->data)) { + error = Iso15693_3ErrorIgnore; + break; + } + + // This error code will trigger the CustomCommand listener event + error = Iso15693_3ErrorNotSupported; + } while(false); + + return error; +} + +Iso15693_3Error iso15693_3_listener_set_extension_handler_table( + Iso15693_3Listener* instance, + const Iso15693_3ExtensionHandlerTable* table, + void* context) { + furi_assert(instance); + furi_assert(context); + + instance->extension_table = table; + instance->extension_context = context; + return Iso15693_3ErrorNone; +} + +Iso15693_3Error iso15693_3_listener_ready(Iso15693_3Listener* instance) { + furi_assert(instance); + instance->state = Iso15693_3ListenerStateReady; + return Iso15693_3ErrorNone; +} + +static Iso15693_3Error iso15693_3_listener_process_nfc_error(NfcError error) { + Iso15693_3Error ret = Iso15693_3ErrorNone; + + if(error == NfcErrorNone) { + ret = Iso15693_3ErrorNone; + } else if(error == NfcErrorTimeout) { + ret = Iso15693_3ErrorTimeout; + } else { + ret = Iso15693_3ErrorFieldOff; + } + + return ret; +} + +Iso15693_3Error + iso15693_3_listener_send_frame(Iso15693_3Listener* instance, const BitBuffer* tx_buffer) { + furi_assert(instance); + furi_assert(tx_buffer); + + bit_buffer_copy(instance->tx_buffer, tx_buffer); + iso13239_crc_append(Iso13239CrcTypeDefault, instance->tx_buffer); + + NfcError error = nfc_listener_tx(instance->nfc, instance->tx_buffer); + return iso15693_3_listener_process_nfc_error(error); +} + +Iso15693_3Error + iso15693_3_listener_process_request(Iso15693_3Listener* instance, const BitBuffer* rx_buffer) { + Iso15693_3Error error = Iso15693_3ErrorNone; + + do { + typedef struct { + uint8_t flags; + uint8_t command; + uint8_t data[]; + } Iso15693_3RequestLayout; + + const size_t buf_size = bit_buffer_get_size_bytes(rx_buffer); + const size_t buf_size_min = sizeof(Iso15693_3RequestLayout); + + if(buf_size < buf_size_min) { + error = Iso15693_3ErrorFormat; + break; + } + + const Iso15693_3RequestLayout* request = + (const Iso15693_3RequestLayout*)bit_buffer_get_data(rx_buffer); + + Iso15693_3ListenerSessionState* session_state = &instance->session_state; + + if((request->flags & ISO15693_3_REQ_FLAG_INVENTORY_T5) == 0) { + session_state->selected = request->flags & ISO15693_3_REQ_FLAG_T4_SELECTED; + session_state->addressed = request->flags & ISO15693_3_REQ_FLAG_T4_ADDRESSED; + + if(session_state->selected && session_state->addressed) { + // A request mode can be either addressed or selected, but not both + error = Iso15693_3ErrorUnknown; + break; + } else if(instance->state == Iso15693_3ListenerStateQuiet) { + // If the card is quiet, ignore non-addressed commands + if(session_state->addressed) { + error = Iso15693_3ErrorIgnore; + break; + } + } else if(instance->state != Iso15693_3ListenerStateSelected) { + // If the card is not selected, ignore selected commands + if(session_state->selected) { + error = Iso15693_3ErrorIgnore; + break; + } + } + } else { + // If the card is quiet, ignore inventory commands + if(instance->state == Iso15693_3ListenerStateQuiet) { + error = Iso15693_3ErrorIgnore; + break; + } + + session_state->selected = false; + session_state->addressed = false; + } + + if(request->command >= ISO15693_3_CMD_CUSTOM_START) { + // Custom commands are properly handled in the protocol-specific top-level poller + error = iso15693_3_listener_handle_custom_request( + instance, request->data, buf_size - buf_size_min); + break; + } + + const uint8_t* data; + size_t data_size; + + if(session_state->addressed) { + // In addressed mode, UID must be included in each command + const size_t buf_size_min_addr = buf_size_min + ISO15693_3_UID_SIZE; + + if(buf_size < buf_size_min_addr) { + error = Iso15693_3ErrorFormat; + break; + } else if(!iso15693_3_is_equal_uid(instance->data, request->data)) { + error = Iso15693_3ErrorUidMismatch; + break; + } + + data = &request->data[ISO15693_3_UID_SIZE]; + data_size = buf_size - buf_size_min_addr; + + } else { + data = request->data; + data_size = buf_size - buf_size_min; + } + + error = iso15693_3_listener_handle_standard_request( + instance, data, data_size, request->command, request->flags); + + } while(false); + + return error; +} + +Iso15693_3Error iso15693_3_listener_process_single_eof(Iso15693_3Listener* instance) { + Iso15693_3Error error = Iso15693_3ErrorNone; + + do { + if(!instance->session_state.wait_for_eof) { + error = Iso15693_3ErrorUnexpectedResponse; + break; + } + + instance->session_state.wait_for_eof = false; + + error = iso15693_3_listener_send_frame(instance, instance->tx_buffer); + } while(false); + + return error; +} + +Iso15693_3Error iso15693_3_listener_process_uid_mismatch( + Iso15693_3Listener* instance, + const BitBuffer* rx_buffer) { + Iso15693_3Error error = Iso15693_3ErrorNone; + + // No checks, assuming they have been made beforehand + typedef struct { + uint8_t flags; + uint8_t command; + } Iso15693_3RequestLayout; + + const Iso15693_3RequestLayout* request = + (const Iso15693_3RequestLayout*)bit_buffer_get_data(rx_buffer); + + if(request->command == ISO15693_3_CMD_SELECT) { + if(instance->state == Iso15693_3ListenerStateSelected) { + error = iso15693_3_listener_ready(instance); + } + } + + return error; +} diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3_listener_i.h b/lib/nfc/protocols/iso15693_3/iso15693_3_listener_i.h new file mode 100644 index 00000000000..a9e0822bff9 --- /dev/null +++ b/lib/nfc/protocols/iso15693_3/iso15693_3_listener_i.h @@ -0,0 +1,70 @@ +#pragma once + +#include + +#include "iso15693_3_listener.h" + +#include "iso15693_3_i.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + Iso15693_3ListenerStateReady, + Iso15693_3ListenerStateSelected, + Iso15693_3ListenerStateQuiet, +} Iso15693_3ListenerState; + +typedef struct { + bool selected; + bool addressed; + bool wait_for_eof; +} Iso15693_3ListenerSessionState; + +typedef Iso15693_3Error (*Iso15693_3ExtensionHandler)(void* context, va_list args); + +typedef struct { + Iso15693_3ExtensionHandler mandatory[ISO15693_3_MANDATORY_COUNT]; + Iso15693_3ExtensionHandler optional[ISO15693_3_OPTIONAL_COUNT]; +} Iso15693_3ExtensionHandlerTable; + +struct Iso15693_3Listener { + Nfc* nfc; + Iso15693_3Data* data; + Iso15693_3ListenerState state; + Iso15693_3ListenerSessionState session_state; + BitBuffer* tx_buffer; + + NfcGenericEvent generic_event; + Iso15693_3ListenerEvent iso15693_3_event; + Iso15693_3ListenerEventData iso15693_3_event_data; + NfcGenericCallback callback; + void* context; + + const Iso15693_3ExtensionHandlerTable* extension_table; + void* extension_context; +}; + +Iso15693_3Error iso15693_3_listener_set_extension_handler_table( + Iso15693_3Listener* instance, + const Iso15693_3ExtensionHandlerTable* table, + void* context); + +Iso15693_3Error iso15693_3_listener_ready(Iso15693_3Listener* instance); + +Iso15693_3Error + iso15693_3_listener_send_frame(Iso15693_3Listener* instance, const BitBuffer* tx_buffer); + +Iso15693_3Error + iso15693_3_listener_process_request(Iso15693_3Listener* instance, const BitBuffer* rx_buffer); + +Iso15693_3Error iso15693_3_listener_process_single_eof(Iso15693_3Listener* instance); + +Iso15693_3Error iso15693_3_listener_process_uid_mismatch( + Iso15693_3Listener* instance, + const BitBuffer* rx_buffer); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3_poller.c b/lib/nfc/protocols/iso15693_3/iso15693_3_poller.c new file mode 100644 index 00000000000..9500e165393 --- /dev/null +++ b/lib/nfc/protocols/iso15693_3/iso15693_3_poller.c @@ -0,0 +1,122 @@ +#include "iso15693_3_poller_i.h" + +#include + +#include + +#define TAG "ISO15693_3Poller" + +const Iso15693_3Data* iso15693_3_poller_get_data(Iso15693_3Poller* instance) { + furi_assert(instance); + furi_assert(instance->data); + + return instance->data; +} + +static Iso15693_3Poller* iso15693_3_poller_alloc(Nfc* nfc) { + furi_assert(nfc); + + Iso15693_3Poller* instance = malloc(sizeof(Iso15693_3Poller)); + instance->nfc = nfc; + instance->tx_buffer = bit_buffer_alloc(ISO15693_3_POLLER_MAX_BUFFER_SIZE); + instance->rx_buffer = bit_buffer_alloc(ISO15693_3_POLLER_MAX_BUFFER_SIZE); + + nfc_config(instance->nfc, NfcModePoller, NfcTechIso15693); + nfc_set_guard_time_us(instance->nfc, ISO15693_3_GUARD_TIME_US); + nfc_set_fdt_poll_fc(instance->nfc, ISO15693_3_FDT_POLL_FC); + nfc_set_fdt_poll_poll_us(instance->nfc, ISO15693_3_POLL_POLL_MIN_US); + instance->data = iso15693_3_alloc(); + + instance->iso15693_3_event.data = &instance->iso15693_3_event_data; + instance->general_event.protocol = NfcProtocolIso15693_3; + instance->general_event.event_data = &instance->iso15693_3_event; + instance->general_event.instance = instance; + + return instance; +} + +static void iso15693_3_poller_free(Iso15693_3Poller* instance) { + furi_assert(instance); + + furi_assert(instance->tx_buffer); + furi_assert(instance->rx_buffer); + furi_assert(instance->data); + + bit_buffer_free(instance->tx_buffer); + bit_buffer_free(instance->rx_buffer); + iso15693_3_free(instance->data); + free(instance); +} + +static void iso15693_3_poller_set_callback( + Iso15693_3Poller* instance, + NfcGenericCallback callback, + void* context) { + furi_assert(instance); + furi_assert(callback); + + instance->callback = callback; + instance->context = context; +} + +static NfcCommand iso15693_3_poller_run(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.protocol == NfcProtocolInvalid); + furi_assert(event.event_data); + + Iso15693_3Poller* instance = context; + NfcEvent* nfc_event = event.event_data; + NfcCommand command = NfcCommandContinue; + + if(nfc_event->type == NfcEventTypePollerReady) { + if(instance->state != Iso15693_3PollerStateActivated) { + Iso15693_3Error error = iso15693_3_poller_async_activate(instance, instance->data); + if(error == Iso15693_3ErrorNone) { + instance->iso15693_3_event.type = Iso15693_3PollerEventTypeReady; + instance->iso15693_3_event_data.error = error; + command = instance->callback(instance->general_event, instance->context); + } else { + instance->iso15693_3_event.type = Iso15693_3PollerEventTypeError; + instance->iso15693_3_event_data.error = error; + command = instance->callback(instance->general_event, instance->context); + // Add delay to switch context + furi_delay_ms(100); + } + } else { + instance->iso15693_3_event.type = Iso15693_3PollerEventTypeReady; + instance->iso15693_3_event_data.error = Iso15693_3ErrorNone; + command = instance->callback(instance->general_event, instance->context); + } + } + + return command; +} + +static bool iso15693_3_poller_detect(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.event_data); + furi_assert(event.instance); + furi_assert(event.protocol == NfcProtocolInvalid); + + bool protocol_detected = false; + Iso15693_3Poller* instance = context; + NfcEvent* nfc_event = event.event_data; + furi_assert(instance->state == Iso15693_3PollerStateIdle); + + if(nfc_event->type == NfcEventTypePollerReady) { + uint8_t uid[ISO15693_3_UID_SIZE]; + Iso15693_3Error error = iso15693_3_poller_async_inventory(instance, uid); + protocol_detected = (error == Iso15693_3ErrorNone); + } + + return protocol_detected; +} + +const NfcPollerBase nfc_poller_iso15693_3 = { + .alloc = (NfcPollerAlloc)iso15693_3_poller_alloc, + .free = (NfcPollerFree)iso15693_3_poller_free, + .set_callback = (NfcPollerSetCallback)iso15693_3_poller_set_callback, + .run = (NfcPollerRun)iso15693_3_poller_run, + .detect = (NfcPollerDetect)iso15693_3_poller_detect, + .get_data = (NfcPollerGetData)iso15693_3_poller_get_data, +}; diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3_poller.h b/lib/nfc/protocols/iso15693_3/iso15693_3_poller.h new file mode 100644 index 00000000000..9d73242f1ad --- /dev/null +++ b/lib/nfc/protocols/iso15693_3/iso15693_3_poller.h @@ -0,0 +1,29 @@ +#pragma once + +#include "iso15693_3.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct Iso15693_3Poller Iso15693_3Poller; + +typedef enum { + Iso15693_3PollerEventTypeError, + Iso15693_3PollerEventTypeReady, +} Iso15693_3PollerEventType; + +typedef struct { + Iso15693_3Error error; +} Iso15693_3PollerEventData; + +typedef struct { + Iso15693_3PollerEventType type; + Iso15693_3PollerEventData* data; +} Iso15693_3PollerEvent; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3_poller_defs.h b/lib/nfc/protocols/iso15693_3/iso15693_3_poller_defs.h new file mode 100644 index 00000000000..f0447c28ba3 --- /dev/null +++ b/lib/nfc/protocols/iso15693_3/iso15693_3_poller_defs.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +extern const NfcPollerBase nfc_poller_iso15693_3; diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.c b/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.c new file mode 100644 index 00000000000..8b8d8cee04b --- /dev/null +++ b/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.c @@ -0,0 +1,303 @@ +#include "iso15693_3_poller_i.h" + +#include + +#define TAG "Iso15693_3Poller" + +#define BITS_IN_BYTE (8) + +#define ISO15693_3_POLLER_NUM_BLOCKS_PER_QUERY (32U) + +static Iso15693_3Error iso15693_3_poller_process_nfc_error(NfcError error) { + switch(error) { + case NfcErrorNone: + return Iso15693_3ErrorNone; + case NfcErrorTimeout: + return Iso15693_3ErrorTimeout; + default: + return Iso15693_3ErrorNotPresent; + } +} + +static Iso15693_3Error iso15693_3_poller_filter_error(Iso15693_3Error error) { + switch(error) { + /* If a particular optional command is not supported, the card might + * respond with a "Not supported" error or not respond at all. + * Therefore, treat these errors as non-critical ones. */ + case Iso15693_3ErrorNotSupported: + case Iso15693_3ErrorTimeout: + return Iso15693_3ErrorNone; + default: + return error; + } +} + +static Iso15693_3Error iso15693_3_poller_prepare_trx(Iso15693_3Poller* instance) { + furi_assert(instance); + + if(instance->state == Iso15693_3PollerStateIdle) { + return iso15693_3_poller_async_activate(instance, NULL); + } + + return Iso15693_3ErrorNone; +} + +static Iso15693_3Error iso15693_3_poller_frame_exchange( + Iso15693_3Poller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt) { + furi_assert(instance); + + Iso15693_3Error ret = Iso15693_3ErrorNone; + + do { + if(bit_buffer_get_size_bytes(tx_buffer) > + bit_buffer_get_capacity_bytes(instance->tx_buffer) - ISO13239_CRC_SIZE) { + ret = Iso15693_3ErrorBufferOverflow; + break; + } + + bit_buffer_copy(instance->tx_buffer, tx_buffer); + iso13239_crc_append(Iso13239CrcTypeDefault, instance->tx_buffer); + + NfcError error = + nfc_poller_trx(instance->nfc, instance->tx_buffer, instance->rx_buffer, fwt); + if(error != NfcErrorNone) { + ret = iso15693_3_poller_process_nfc_error(error); + break; + } + + if(!iso13239_crc_check(Iso13239CrcTypeDefault, instance->rx_buffer)) { + ret = Iso15693_3ErrorWrongCrc; + break; + } + + iso13239_crc_trim(instance->rx_buffer); + bit_buffer_copy(rx_buffer, instance->rx_buffer); + } while(false); + + return ret; +} + +Iso15693_3Error + iso15693_3_poller_async_activate(Iso15693_3Poller* instance, Iso15693_3Data* data) { + furi_assert(instance); + furi_assert(instance->nfc); + + iso15693_3_reset(data); + + Iso15693_3Error ret; + + do { + instance->state = Iso15693_3PollerStateColResInProgress; + + // Inventory: Mandatory command + ret = iso15693_3_poller_async_inventory(instance, data->uid); + if(ret != Iso15693_3ErrorNone) { + instance->state = Iso15693_3PollerStateColResFailed; + break; + } + + instance->state = Iso15693_3PollerStateActivated; + + // Get system info: Optional command + Iso15693_3SystemInfo* system_info = &data->system_info; + ret = iso15693_3_poller_async_get_system_info(instance, system_info); + if(ret != Iso15693_3ErrorNone) { + ret = iso15693_3_poller_filter_error(ret); + break; + } + + // Read blocks: Optional command + simple_array_init(data->block_data, system_info->block_count * system_info->block_size); + ret = iso15693_3_poller_async_read_blocks( + instance, + simple_array_get_data(data->block_data), + system_info->block_count, + system_info->block_size); + if(ret != Iso15693_3ErrorNone) { + ret = iso15693_3_poller_filter_error(ret); + break; + } + + // Get block security status: Optional command + simple_array_init(data->block_security, system_info->block_count); + + ret = iso15693_3_poller_async_get_blocks_security( + instance, simple_array_get_data(data->block_security), system_info->block_count); + if(ret != Iso15693_3ErrorNone) { + ret = iso15693_3_poller_filter_error(ret); + break; + } + + } while(false); + + return ret; +} + +Iso15693_3Error iso15693_3_poller_async_inventory(Iso15693_3Poller* instance, uint8_t* uid) { + furi_assert(instance); + furi_assert(instance->nfc); + furi_assert(uid); + + bit_buffer_reset(instance->tx_buffer); + bit_buffer_reset(instance->rx_buffer); + + // Send INVENTORY + bit_buffer_append_byte( + instance->tx_buffer, + ISO15693_3_REQ_FLAG_SUBCARRIER_1 | ISO15693_3_REQ_FLAG_DATA_RATE_HI | + ISO15693_3_REQ_FLAG_INVENTORY_T5 | ISO15693_3_REQ_FLAG_T5_N_SLOTS_1); + bit_buffer_append_byte(instance->tx_buffer, ISO15693_3_CMD_INVENTORY); + bit_buffer_append_byte(instance->tx_buffer, 0x00); + + Iso15693_3Error ret; + + do { + ret = iso15693_3_poller_frame_exchange( + instance, instance->tx_buffer, instance->rx_buffer, ISO15693_3_FDT_POLL_FC); + if(ret != Iso15693_3ErrorNone) break; + + ret = iso15693_3_inventory_response_parse(uid, instance->rx_buffer); + } while(false); + + return ret; +} + +Iso15693_3Error iso15693_3_poller_async_get_system_info( + Iso15693_3Poller* instance, + Iso15693_3SystemInfo* data) { + furi_assert(instance); + furi_assert(data); + + bit_buffer_reset(instance->tx_buffer); + bit_buffer_reset(instance->rx_buffer); + + // Send GET SYSTEM INFO + bit_buffer_append_byte( + instance->tx_buffer, ISO15693_3_REQ_FLAG_SUBCARRIER_1 | ISO15693_3_REQ_FLAG_DATA_RATE_HI); + + bit_buffer_append_byte(instance->tx_buffer, ISO15693_3_CMD_GET_SYS_INFO); + + Iso15693_3Error ret; + + do { + ret = iso15693_3_poller_frame_exchange( + instance, instance->tx_buffer, instance->rx_buffer, ISO15693_3_FDT_POLL_FC); + if(ret != Iso15693_3ErrorNone) break; + + ret = iso15693_3_system_info_response_parse(data, instance->rx_buffer); + } while(false); + + return ret; +} + +Iso15693_3Error iso15693_3_poller_async_read_block( + Iso15693_3Poller* instance, + uint8_t* data, + uint8_t block_number, + uint8_t block_size) { + furi_assert(instance); + furi_assert(data); + + bit_buffer_reset(instance->tx_buffer); + bit_buffer_reset(instance->rx_buffer); + + bit_buffer_append_byte( + instance->tx_buffer, ISO15693_3_REQ_FLAG_SUBCARRIER_1 | ISO15693_3_REQ_FLAG_DATA_RATE_HI); + bit_buffer_append_byte(instance->tx_buffer, ISO15693_3_CMD_READ_BLOCK); + bit_buffer_append_byte(instance->tx_buffer, block_number); + + Iso15693_3Error ret; + + do { + ret = iso15693_3_poller_send_frame( + instance, instance->tx_buffer, instance->rx_buffer, ISO15693_3_FDT_POLL_FC); + if(ret != Iso15693_3ErrorNone) break; + + ret = iso15693_3_read_block_response_parse(data, block_size, instance->rx_buffer); + } while(false); + + return ret; +} + +Iso15693_3Error iso15693_3_poller_async_read_blocks( + Iso15693_3Poller* instance, + uint8_t* data, + uint16_t block_count, + uint8_t block_size) { + furi_assert(instance); + furi_assert(data); + furi_assert(block_count); + furi_assert(block_size); + + Iso15693_3Error ret = Iso15693_3ErrorNone; + + for(uint32_t i = 0; i < block_count; ++i) { + ret = iso15693_3_poller_async_read_block(instance, &data[block_size * i], i, block_size); + if(ret != Iso15693_3ErrorNone) break; + } + + return ret; +} + +Iso15693_3Error iso15693_3_poller_async_get_blocks_security( + Iso15693_3Poller* instance, + uint8_t* data, + uint16_t block_count) { + furi_assert(instance); + furi_assert(data); + + // Limit the number of blocks to 32 in a single query + const uint32_t num_queries = block_count / ISO15693_3_POLLER_NUM_BLOCKS_PER_QUERY + + (block_count % ISO15693_3_POLLER_NUM_BLOCKS_PER_QUERY ? 1 : 0); + + Iso15693_3Error ret = Iso15693_3ErrorNone; + + for(uint32_t i = 0; i < num_queries; ++i) { + bit_buffer_reset(instance->tx_buffer); + bit_buffer_reset(instance->rx_buffer); + + bit_buffer_append_byte( + instance->tx_buffer, + ISO15693_3_REQ_FLAG_SUBCARRIER_1 | ISO15693_3_REQ_FLAG_DATA_RATE_HI); + + bit_buffer_append_byte(instance->tx_buffer, ISO15693_3_CMD_GET_BLOCKS_SECURITY); + + const uint8_t start_block_num = i * ISO15693_3_POLLER_NUM_BLOCKS_PER_QUERY; + bit_buffer_append_byte(instance->tx_buffer, start_block_num); + + const uint8_t block_count_per_query = + MIN(block_count - start_block_num, (uint16_t)ISO15693_3_POLLER_NUM_BLOCKS_PER_QUERY); + // Block count byte must be 1 less than the desired count + bit_buffer_append_byte(instance->tx_buffer, block_count_per_query - 1); + + ret = iso15693_3_poller_send_frame( + instance, instance->tx_buffer, instance->rx_buffer, ISO15693_3_FDT_POLL_FC); + if(ret != Iso15693_3ErrorNone) break; + + ret = iso15693_3_get_block_security_response_parse( + &data[start_block_num], block_count_per_query, instance->rx_buffer); + if(ret != Iso15693_3ErrorNone) break; + } + + return ret; +} + +Iso15693_3Error iso15693_3_poller_send_frame( + Iso15693_3Poller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt) { + Iso15693_3Error ret; + + do { + ret = iso15693_3_poller_prepare_trx(instance); + if(ret != Iso15693_3ErrorNone) break; + + ret = iso15693_3_poller_frame_exchange(instance, tx_buffer, rx_buffer, fwt); + } while(false); + + return ret; +} diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.h b/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.h new file mode 100644 index 00000000000..154ee684c97 --- /dev/null +++ b/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.h @@ -0,0 +1,70 @@ +#pragma once + +#include "iso15693_3_poller.h" + +#include "iso15693_3_i.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define ISO15693_3_POLLER_MAX_BUFFER_SIZE (64U) + +typedef enum { + Iso15693_3PollerStateIdle, + Iso15693_3PollerStateColResInProgress, + Iso15693_3PollerStateColResFailed, + Iso15693_3PollerStateActivated, +} Iso15693_3PollerState; + +struct Iso15693_3Poller { + Nfc* nfc; + Iso15693_3PollerState state; + Iso15693_3Data* data; + BitBuffer* tx_buffer; + BitBuffer* rx_buffer; + + NfcGenericEvent general_event; + Iso15693_3PollerEvent iso15693_3_event; + Iso15693_3PollerEventData iso15693_3_event_data; + NfcGenericCallback callback; + void* context; +}; + +const Iso15693_3Data* iso15693_3_poller_get_data(Iso15693_3Poller* instance); + +Iso15693_3Error iso15693_3_poller_async_activate(Iso15693_3Poller* instance, Iso15693_3Data* data); + +Iso15693_3Error iso15693_3_poller_async_inventory(Iso15693_3Poller* instance, uint8_t* uid); + +Iso15693_3Error + iso15693_3_poller_async_get_system_info(Iso15693_3Poller* instance, Iso15693_3SystemInfo* data); + +Iso15693_3Error iso15693_3_poller_async_read_block( + Iso15693_3Poller* instance, + uint8_t* data, + uint8_t block_number, + uint8_t block_size); + +Iso15693_3Error iso15693_3_poller_async_read_blocks( + Iso15693_3Poller* instance, + uint8_t* data, + uint16_t block_count, + uint8_t block_size); + +Iso15693_3Error iso15693_3_poller_async_get_blocks_security( + Iso15693_3Poller* instance, + uint8_t* data, + uint16_t block_count); + +Iso15693_3Error iso15693_3_poller_send_frame( + Iso15693_3Poller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/mf_classic/crypto1.c b/lib/nfc/protocols/mf_classic/crypto1.c new file mode 100644 index 00000000000..df01a348c85 --- /dev/null +++ b/lib/nfc/protocols/mf_classic/crypto1.c @@ -0,0 +1,172 @@ +#include "crypto1.h" + +#include +#include + +// Algorithm from https://github.com/RfidResearchGroup/proxmark3.git + +#define SWAPENDIAN(x) \ + ((x) = ((x) >> 8 & 0xff00ff) | ((x)&0xff00ff) << 8, (x) = (x) >> 16 | (x) << 16) +#define LF_POLY_ODD (0x29CE5C) +#define LF_POLY_EVEN (0x870804) + +#define BEBIT(x, n) FURI_BIT(x, (n) ^ 24) + +Crypto1* crypto1_alloc() { + Crypto1* instance = malloc(sizeof(Crypto1)); + + return instance; +} + +void crypto1_free(Crypto1* instance) { + furi_assert(instance); + + free(instance); +} + +void crypto1_reset(Crypto1* crypto1) { + furi_assert(crypto1); + crypto1->even = 0; + crypto1->odd = 0; +} + +void crypto1_init(Crypto1* crypto1, uint64_t key) { + furi_assert(crypto1); + crypto1->even = 0; + crypto1->odd = 0; + for(int8_t i = 47; i > 0; i -= 2) { + crypto1->odd = crypto1->odd << 1 | FURI_BIT(key, (i - 1) ^ 7); + crypto1->even = crypto1->even << 1 | FURI_BIT(key, i ^ 7); + } +} + +static uint32_t crypto1_filter(uint32_t in) { + uint32_t out = 0; + out = 0xf22c0 >> (in & 0xf) & 16; + out |= 0x6c9c0 >> (in >> 4 & 0xf) & 8; + out |= 0x3c8b0 >> (in >> 8 & 0xf) & 4; + out |= 0x1e458 >> (in >> 12 & 0xf) & 2; + out |= 0x0d938 >> (in >> 16 & 0xf) & 1; + return FURI_BIT(0xEC57E80A, out); +} + +uint8_t crypto1_bit(Crypto1* crypto1, uint8_t in, int is_encrypted) { + furi_assert(crypto1); + uint8_t out = crypto1_filter(crypto1->odd); + uint32_t feed = out & (!!is_encrypted); + feed ^= !!in; + feed ^= LF_POLY_ODD & crypto1->odd; + feed ^= LF_POLY_EVEN & crypto1->even; + crypto1->even = crypto1->even << 1 | (nfc_util_even_parity32(feed)); + + FURI_SWAP(crypto1->odd, crypto1->even); + return out; +} + +uint8_t crypto1_byte(Crypto1* crypto1, uint8_t in, int is_encrypted) { + furi_assert(crypto1); + uint8_t out = 0; + for(uint8_t i = 0; i < 8; i++) { + out |= crypto1_bit(crypto1, FURI_BIT(in, i), is_encrypted) << i; + } + return out; +} + +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++) { + out |= (uint32_t)crypto1_bit(crypto1, BEBIT(in, i), is_encrypted) << (24 ^ i); + } + return out; +} + +uint32_t prng_successor(uint32_t x, uint32_t n) { + SWAPENDIAN(x); + while(n--) x = x >> 1 | (x >> 16 ^ x >> 18 ^ x >> 19 ^ x >> 21) << 31; + + return SWAPENDIAN(x); +} + +void crypto1_decrypt(Crypto1* crypto, const BitBuffer* buff, BitBuffer* out) { + furi_assert(crypto); + furi_assert(buff); + furi_assert(out); + + size_t bits = bit_buffer_get_size(buff); + bit_buffer_set_size(out, bits); + const uint8_t* encrypted_data = bit_buffer_get_data(buff); + if(bits < 8) { + uint8_t decrypted_byte = 0; + uint8_t encrypted_byte = encrypted_data[0]; + decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_byte, 0)) << 0; + decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_byte, 1)) << 1; + decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_byte, 2)) << 2; + decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_byte, 3)) << 3; + bit_buffer_set_byte(out, 0, decrypted_byte); + } else { + for(size_t i = 0; i < bits / 8; i++) { + uint8_t decrypted_byte = crypto1_byte(crypto, 0, 0) ^ encrypted_data[i]; + bit_buffer_set_byte(out, i, decrypted_byte); + } + } +} + +void crypto1_encrypt(Crypto1* crypto, uint8_t* keystream, const BitBuffer* buff, BitBuffer* out) { + furi_assert(crypto); + furi_assert(buff); + furi_assert(out); + + size_t bits = bit_buffer_get_size(buff); + bit_buffer_set_size(out, bits); + const uint8_t* plain_data = bit_buffer_get_data(buff); + if(bits < 8) { + uint8_t encrypted_byte = 0; + for(size_t i = 0; i < bits; i++) { + encrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(plain_data[0], i)) << i; + } + bit_buffer_set_byte(out, 0, encrypted_byte); + } else { + for(size_t i = 0; i < bits / 8; i++) { + uint8_t encrypted_byte = crypto1_byte(crypto, keystream ? keystream[i] : 0, 0) ^ + plain_data[i]; + bool parity_bit = + ((crypto1_filter(crypto->odd) ^ nfc_util_odd_parity8(plain_data[i])) & 0x01); + bit_buffer_set_byte_with_parity(out, i, encrypted_byte, parity_bit); + } + } +} + +void crypto1_encrypt_reader_nonce( + Crypto1* crypto, + uint64_t key, + uint32_t cuid, + uint8_t* nt, + uint8_t* nr, + BitBuffer* out) { + furi_assert(crypto); + furi_assert(nt); + furi_assert(nr); + furi_assert(out); + + bit_buffer_set_size_bytes(out, 8); + uint32_t nt_num = nfc_util_bytes2num(nt, sizeof(uint32_t)); + + crypto1_init(crypto, key); + crypto1_word(crypto, nt_num ^ cuid, 0); + + for(size_t i = 0; i < 4; i++) { + uint8_t byte = crypto1_byte(crypto, nr[i], 0) ^ nr[i]; + bool parity_bit = ((crypto1_filter(crypto->odd) ^ nfc_util_odd_parity8(nr[i])) & 0x01); + bit_buffer_set_byte_with_parity(out, i, byte, parity_bit); + nr[i] = byte; + } + + nt_num = prng_successor(nt_num, 32); + for(size_t i = 4; i < 8; i++) { + nt_num = prng_successor(nt_num, 8); + uint8_t byte = crypto1_byte(crypto, 0, 0) ^ (uint8_t)(nt_num); + bool parity_bit = ((crypto1_filter(crypto->odd) ^ nfc_util_odd_parity8(nt_num)) & 0x01); + bit_buffer_set_byte_with_parity(out, i, byte, parity_bit); + } +} diff --git a/lib/nfc/protocols/crypto1.h b/lib/nfc/protocols/mf_classic/crypto1.h similarity index 56% rename from lib/nfc/protocols/crypto1.h rename to lib/nfc/protocols/mf_classic/crypto1.h index bbf6dc239ce..f2bdb272b0e 100644 --- a/lib/nfc/protocols/crypto1.h +++ b/lib/nfc/protocols/mf_classic/crypto1.h @@ -1,7 +1,6 @@ #pragma once -#include -#include +#include #ifdef __cplusplus extern "C" { @@ -12,6 +11,10 @@ typedef struct { uint32_t even; } Crypto1; +Crypto1* crypto1_alloc(); + +void crypto1_free(Crypto1* instance); + void crypto1_reset(Crypto1* crypto1); void crypto1_init(Crypto1* crypto1, uint64_t key); @@ -22,24 +25,20 @@ uint8_t crypto1_byte(Crypto1* crypto1, uint8_t in, int is_encrypted); uint32_t crypto1_word(Crypto1* crypto1, uint32_t in, int is_encrypted); -uint32_t crypto1_filter(uint32_t in); +void crypto1_decrypt(Crypto1* crypto, const BitBuffer* buff, BitBuffer* out); -uint32_t prng_successor(uint32_t x, uint32_t n); +void crypto1_encrypt(Crypto1* crypto, uint8_t* keystream, const BitBuffer* buff, BitBuffer* out); -void crypto1_decrypt( +void crypto1_encrypt_reader_nonce( Crypto1* crypto, - uint8_t* encrypted_data, - uint16_t encrypted_data_bits, - uint8_t* decrypted_data); + uint64_t key, + uint32_t cuid, + uint8_t* nt, + uint8_t* nr, + BitBuffer* out); -void crypto1_encrypt( - Crypto1* crypto, - uint8_t* keystream, - uint8_t* plain_data, - uint16_t plain_data_bits, - uint8_t* encrypted_data, - uint8_t* encrypted_parity); +uint32_t prng_successor(uint32_t x, uint32_t n); #ifdef __cplusplus } -#endif \ No newline at end of file +#endif diff --git a/lib/nfc/protocols/mf_classic/mf_classic.c b/lib/nfc/protocols/mf_classic/mf_classic.c new file mode 100644 index 00000000000..400cf0d7fb3 --- /dev/null +++ b/lib/nfc/protocols/mf_classic/mf_classic.c @@ -0,0 +1,740 @@ +#include "mf_classic.h" + +#include +#include + +#include + +#define MF_CLASSIC_PROTOCOL_NAME "Mifare Classic" + +typedef struct { + uint8_t sectors_total; + uint16_t blocks_total; + const char* full_name; + const char* type_name; +} MfClassicFeatures; + +static const uint32_t mf_classic_data_format_version = 2; + +static const MfClassicFeatures mf_classic_features[MfClassicTypeNum] = { + [MfClassicTypeMini] = + { + .sectors_total = 5, + .blocks_total = 20, + .full_name = "Mifare Classic Mini 0.3K", + .type_name = "MINI", + }, + [MfClassicType1k] = + { + .sectors_total = 16, + .blocks_total = 64, + .full_name = "Mifare Classic 1K", + .type_name = "1K", + }, + [MfClassicType4k] = + { + .sectors_total = 40, + .blocks_total = 256, + .full_name = "Mifare Classic 4K", + .type_name = "4K", + }, +}; + +const NfcDeviceBase nfc_device_mf_classic = { + .protocol_name = MF_CLASSIC_PROTOCOL_NAME, + .alloc = (NfcDeviceAlloc)mf_classic_alloc, + .free = (NfcDeviceFree)mf_classic_free, + .reset = (NfcDeviceReset)mf_classic_reset, + .copy = (NfcDeviceCopy)mf_classic_copy, + .verify = (NfcDeviceVerify)mf_classic_verify, + .load = (NfcDeviceLoad)mf_classic_load, + .save = (NfcDeviceSave)mf_classic_save, + .is_equal = (NfcDeviceEqual)mf_classic_is_equal, + .get_name = (NfcDeviceGetName)mf_classic_get_device_name, + .get_uid = (NfcDeviceGetUid)mf_classic_get_uid, + .set_uid = (NfcDeviceSetUid)mf_classic_set_uid, + .get_base_data = (NfcDeviceGetBaseData)mf_classic_get_base_data, +}; + +MfClassicData* mf_classic_alloc() { + MfClassicData* data = malloc(sizeof(MfClassicData)); + data->iso14443_3a_data = iso14443_3a_alloc(); + return data; +} + +void mf_classic_free(MfClassicData* data) { + furi_assert(data); + + iso14443_3a_free(data->iso14443_3a_data); + free(data); +} + +void mf_classic_reset(MfClassicData* data) { + furi_assert(data); + + iso14443_3a_reset(data->iso14443_3a_data); +} + +void mf_classic_copy(MfClassicData* data, const MfClassicData* other) { + furi_assert(data); + furi_assert(other); + + iso14443_3a_copy(data->iso14443_3a_data, other->iso14443_3a_data); + for(size_t i = 0; i < COUNT_OF(data->block); i++) { + data->block[i] = other->block[i]; + } + for(size_t i = 0; i < COUNT_OF(data->block_read_mask); i++) { + data->block_read_mask[i] = other->block_read_mask[i]; + } + data->type = other->type; + data->key_a_mask = other->key_a_mask; + data->key_b_mask = other->key_b_mask; +} + +bool mf_classic_verify(MfClassicData* data, const FuriString* device_type) { + UNUSED(data); + return furi_string_equal_str(device_type, "Mifare Classic"); +} + +static void mf_classic_parse_block(FuriString* block_str, MfClassicData* data, uint8_t block_num) { + furi_string_trim(block_str); + MfClassicBlock block_tmp = {}; + bool is_sector_trailer = mf_classic_is_sector_trailer(block_num); + uint8_t sector_num = mf_classic_get_sector_by_block(block_num); + uint16_t block_unknown_bytes_mask = 0; + + furi_string_trim(block_str); + for(size_t i = 0; i < MF_CLASSIC_BLOCK_SIZE; i++) { + char hi = furi_string_get_char(block_str, 3 * i); + char low = furi_string_get_char(block_str, 3 * i + 1); + uint8_t byte = 0; + if(hex_char_to_uint8(hi, low, &byte)) { + block_tmp.data[i] = byte; + } else { + FURI_BIT_SET(block_unknown_bytes_mask, i); + } + } + + if(block_unknown_bytes_mask != 0xffff) { + if(is_sector_trailer) { + MfClassicSectorTrailer* sec_tr_tmp = (MfClassicSectorTrailer*)&block_tmp; + // Load Key A + // Key A mask 0b0000000000111111 = 0x003f + if((block_unknown_bytes_mask & 0x003f) == 0) { + uint64_t key = nfc_util_bytes2num(sec_tr_tmp->key_a.data, sizeof(MfClassicKey)); + mf_classic_set_key_found(data, sector_num, MfClassicKeyTypeA, key); + } + // Load Access Bits + // Access bits mask 0b0000001111000000 = 0x03c0 + if((block_unknown_bytes_mask & 0x03c0) == 0) { + mf_classic_set_block_read(data, block_num, &block_tmp); + } + // Load Key B + // Key B mask 0b1111110000000000 = 0xfc00 + if((block_unknown_bytes_mask & 0xfc00) == 0) { + uint64_t key = nfc_util_bytes2num(sec_tr_tmp->key_b.data, sizeof(MfClassicKey)); + mf_classic_set_key_found(data, sector_num, MfClassicKeyTypeB, key); + } + } else { + if(block_unknown_bytes_mask == 0) { + mf_classic_set_block_read(data, block_num, &block_tmp); + } + } + } +} + +bool mf_classic_load(MfClassicData* data, FlipperFormat* ff, uint32_t version) { + furi_assert(data); + + FuriString* temp_str = furi_string_alloc(); + bool parsed = false; + + do { + // Read ISO14443_3A data + if(!iso14443_3a_load(data->iso14443_3a_data, ff, version)) break; + + // Read Mifare Classic type + if(!flipper_format_read_string(ff, "Mifare Classic type", temp_str)) break; + bool type_parsed = false; + for(size_t i = 0; i < MfClassicTypeNum; i++) { + if(furi_string_equal_str(temp_str, mf_classic_features[i].type_name)) { + data->type = i; + type_parsed = true; + } + } + if(!type_parsed) break; + + // Read format version + uint32_t data_format_version = 0; + bool old_format = false; + // Read Mifare Classic format version + if(!flipper_format_read_uint32(ff, "Data format version", &data_format_version, 1)) { + // Load unread sectors with zero keys access for backward compatibility + if(!flipper_format_rewind(ff)) break; + old_format = true; + } else { + if(data_format_version < mf_classic_data_format_version) { + old_format = true; + } + } + + // Read Mifare Classic blocks + bool block_read = true; + FuriString* block_str = furi_string_alloc(); + uint16_t blocks_total = mf_classic_get_total_block_num(data->type); + for(size_t i = 0; i < blocks_total; i++) { + furi_string_printf(temp_str, "Block %d", i); + if(!flipper_format_read_string(ff, furi_string_get_cstr(temp_str), block_str)) { + block_read = false; + break; + } + mf_classic_parse_block(block_str, data, i); + } + furi_string_free(block_str); + if(!block_read) break; + + // Set keys and blocks as unknown for backward compatibility + if(old_format) { + data->key_a_mask = 0ULL; + data->key_b_mask = 0ULL; + memset(data->block_read_mask, 0, sizeof(data->block_read_mask)); + } + + parsed = true; + } while(false); + + furi_string_free(temp_str); + + return parsed; +} + +static void + mf_classic_set_block_str(FuriString* block_str, const MfClassicData* data, uint8_t block_num) { + furi_string_reset(block_str); + bool is_sec_trailer = mf_classic_is_sector_trailer(block_num); + if(is_sec_trailer) { + uint8_t sector_num = mf_classic_get_sector_by_block(block_num); + MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, sector_num); + // Write key A + for(size_t i = 0; i < sizeof(sec_tr->key_a); i++) { + if(mf_classic_is_key_found(data, sector_num, MfClassicKeyTypeA)) { + furi_string_cat_printf(block_str, "%02X ", sec_tr->key_a.data[i]); + } else { + furi_string_cat_printf(block_str, "?? "); + } + } + // Write Access bytes + for(size_t i = 0; i < MF_CLASSIC_ACCESS_BYTES_SIZE; i++) { + if(mf_classic_is_block_read(data, block_num)) { + furi_string_cat_printf(block_str, "%02X ", sec_tr->access_bits.data[i]); + } else { + furi_string_cat_printf(block_str, "?? "); + } + } + // Write key B + for(size_t i = 0; i < sizeof(sec_tr->key_b); i++) { + if(mf_classic_is_key_found(data, sector_num, MfClassicKeyTypeB)) { + furi_string_cat_printf(block_str, "%02X ", sec_tr->key_b.data[i]); + } else { + furi_string_cat_printf(block_str, "?? "); + } + } + } else { + // Write data block + for(size_t i = 0; i < MF_CLASSIC_BLOCK_SIZE; i++) { + if(mf_classic_is_block_read(data, block_num)) { + furi_string_cat_printf(block_str, "%02X ", data->block[block_num].data[i]); + } else { + furi_string_cat_printf(block_str, "?? "); + } + } + } + furi_string_trim(block_str); +} + +bool mf_classic_save(const MfClassicData* data, FlipperFormat* ff) { + furi_assert(data); + + FuriString* temp_str = furi_string_alloc(); + bool saved = false; + + do { + if(!iso14443_3a_save(data->iso14443_3a_data, ff)) break; + + if(!flipper_format_write_comment_cstr(ff, "Mifare Classic specific data")) break; + if(!flipper_format_write_string_cstr( + ff, "Mifare Classic type", mf_classic_features[data->type].type_name)) + break; + if(!flipper_format_write_uint32( + ff, "Data format version", &mf_classic_data_format_version, 1)) + break; + if(!flipper_format_write_comment_cstr( + ff, "Mifare Classic blocks, \'??\' means unknown data")) + break; + + uint16_t blocks_total = mf_classic_get_total_block_num(data->type); + FuriString* block_str = furi_string_alloc(); + bool block_saved = true; + for(size_t i = 0; i < blocks_total; i++) { + furi_string_printf(temp_str, "Block %d", i); + mf_classic_set_block_str(block_str, data, i); + if(!flipper_format_write_string(ff, furi_string_get_cstr(temp_str), block_str)) { + block_saved = false; + break; + } + } + furi_string_free(block_str); + if(!block_saved) break; + + saved = true; + } while(false); + + furi_string_free(temp_str); + + return saved; +} + +bool mf_classic_is_equal(const MfClassicData* data, const MfClassicData* other) { + bool is_equal = false; + bool data_array_is_equal = true; + + do { + if(!iso14443_3a_is_equal(data->iso14443_3a_data, other->iso14443_3a_data)) break; + if(data->type != other->type) break; + if(data->key_a_mask != other->key_a_mask) break; + if(data->key_b_mask != other->key_b_mask) break; + + for(size_t i = 0; i < COUNT_OF(data->block_read_mask); i++) { + if(data->block_read_mask[i] != other->block_read_mask[i]) { + data_array_is_equal = false; + break; + } + } + if(!data_array_is_equal) break; + + for(size_t i = 0; i < COUNT_OF(data->block); i++) { + if(memcmp(&data->block[i], &other->block[i], sizeof(data->block[i])) != 0) { + data_array_is_equal = false; + break; + } + } + if(!data_array_is_equal) break; + + is_equal = true; + } while(false); + + return is_equal; +} + +const char* mf_classic_get_device_name(const MfClassicData* data, NfcDeviceNameType name_type) { + furi_assert(data); + furi_assert(data->type < MfClassicTypeNum); + + if(name_type == NfcDeviceNameTypeFull) { + return mf_classic_features[data->type].full_name; + } else { + return mf_classic_features[data->type].type_name; + } +} + +const uint8_t* mf_classic_get_uid(const MfClassicData* data, size_t* uid_len) { + furi_assert(data); + + return iso14443_3a_get_uid(data->iso14443_3a_data, uid_len); +} + +bool mf_classic_set_uid(MfClassicData* data, const uint8_t* uid, size_t uid_len) { + furi_assert(data); + + return iso14443_3a_set_uid(data->iso14443_3a_data, uid, uid_len); +} + +Iso14443_3aData* mf_classic_get_base_data(const MfClassicData* data) { + furi_assert(data); + + return data->iso14443_3a_data; +} + +uint8_t mf_classic_get_total_sectors_num(MfClassicType type) { + return mf_classic_features[type].sectors_total; +} + +uint16_t mf_classic_get_total_block_num(MfClassicType type) { + return mf_classic_features[type].blocks_total; +} + +uint8_t mf_classic_get_sector_trailer_num_by_sector(uint8_t sector) { + uint8_t block_num = 0; + + if(sector < 32) { + block_num = sector * 4 + 3; + } else if(sector < 40) { + block_num = 32 * 4 + (sector - 32) * 16 + 15; + } else { + furi_crash("Wrong sector num"); + } + + return block_num; +} + +uint8_t mf_classic_get_sector_trailer_num_by_block(uint8_t block) { + uint8_t sec_tr_block_num = 0; + + if(block < 128) { + sec_tr_block_num = block | 0x03; + } else { + sec_tr_block_num = block | 0x0f; + } + + return sec_tr_block_num; +} + +MfClassicSectorTrailer* + mf_classic_get_sector_trailer_by_sector(const MfClassicData* data, uint8_t sector_num) { + furi_assert(data); + + uint8_t sec_tr_block = mf_classic_get_sector_trailer_num_by_sector(sector_num); + MfClassicSectorTrailer* sec_trailer = (MfClassicSectorTrailer*)&data->block[sec_tr_block]; + + return sec_trailer; +} + +bool mf_classic_is_sector_trailer(uint8_t block) { + return block == mf_classic_get_sector_trailer_num_by_block(block); +} + +uint8_t mf_classic_get_sector_by_block(uint8_t block) { + uint8_t sector = 0; + + if(block < 128) { + sector = (block | 0x03) / 4; + } else { + sector = 32 + ((block | 0x0f) - 32 * 4) / 16; + } + + return sector; +} + +bool mf_classic_block_to_value(const MfClassicBlock* block, int32_t* value, uint8_t* addr) { + furi_assert(block); + + uint32_t v = *(uint32_t*)&block->data[0]; + uint32_t v_inv = *(uint32_t*)&block->data[sizeof(uint32_t)]; + uint32_t v1 = *(uint32_t*)&block->data[sizeof(uint32_t) * 2]; + + bool val_checks = + ((v == v1) && (v == ~v_inv) && (block->data[12] == (~block->data[13] & 0xFF)) && + (block->data[14] == (~block->data[15] & 0xFF)) && (block->data[12] == block->data[14])); + if(value) { + *value = (int32_t)v; + } + if(addr) { + *addr = block->data[12]; + } + return val_checks; +} + +void mf_classic_value_to_block(int32_t value, uint8_t addr, MfClassicBlock* block) { + furi_assert(block); + + uint32_t v_inv = ~((uint32_t)value); + + memcpy(&block->data[0], &value, 4); //-V1086 + memcpy(&block->data[4], &v_inv, 4); //-V1086 + memcpy(&block->data[8], &value, 4); //-V1086 + + block->data[12] = addr; + block->data[13] = ~addr & 0xFF; + block->data[14] = addr; + block->data[15] = ~addr & 0xFF; +} + +bool mf_classic_is_key_found( + const MfClassicData* data, + uint8_t sector_num, + MfClassicKeyType key_type) { + furi_assert(data); + + bool key_found = false; + if(key_type == MfClassicKeyTypeA) { + key_found = (FURI_BIT(data->key_a_mask, sector_num) == 1); + } else if(key_type == MfClassicKeyTypeB) { + key_found = (FURI_BIT(data->key_b_mask, sector_num) == 1); + } + + return key_found; +} + +void mf_classic_set_key_found( + MfClassicData* data, + uint8_t sector_num, + MfClassicKeyType key_type, + uint64_t key) { + furi_assert(data); + + uint8_t key_arr[6] = {}; + MfClassicSectorTrailer* sec_trailer = + mf_classic_get_sector_trailer_by_sector(data, sector_num); + nfc_util_num2bytes(key, 6, key_arr); + if(key_type == MfClassicKeyTypeA) { + memcpy(sec_trailer->key_a.data, key_arr, sizeof(MfClassicKey)); + FURI_BIT_SET(data->key_a_mask, sector_num); + } else if(key_type == MfClassicKeyTypeB) { + memcpy(sec_trailer->key_b.data, key_arr, sizeof(MfClassicKey)); + FURI_BIT_SET(data->key_b_mask, sector_num); + } +} + +void mf_classic_set_key_not_found( + MfClassicData* data, + uint8_t sector_num, + MfClassicKeyType key_type) { + furi_assert(data); + + if(key_type == MfClassicKeyTypeA) { + FURI_BIT_CLEAR(data->key_a_mask, sector_num); + } else if(key_type == MfClassicKeyTypeB) { + FURI_BIT_CLEAR(data->key_b_mask, sector_num); + } +} + +bool mf_classic_is_block_read(const MfClassicData* data, uint8_t block_num) { + furi_assert(data); + + return (FURI_BIT(data->block_read_mask[block_num / 32], block_num % 32) == 1); +} + +void mf_classic_set_block_read(MfClassicData* data, uint8_t block_num, MfClassicBlock* block_data) { + furi_assert(data); + + if(mf_classic_is_sector_trailer(block_num)) { + memcpy(&data->block[block_num].data[6], &block_data->data[6], 4); + } else { + memcpy(data->block[block_num].data, block_data->data, MF_CLASSIC_BLOCK_SIZE); + } + FURI_BIT_SET(data->block_read_mask[block_num / 32], block_num % 32); +} + +uint8_t mf_classic_get_first_block_num_of_sector(uint8_t sector) { + furi_assert(sector < 40); + + uint8_t block = 0; + if(sector < 32) { + block = sector * 4; + } else { + block = 32 * 4 + (sector - 32) * 16; + } + + return block; +} + +uint8_t mf_classic_get_blocks_num_in_sector(uint8_t sector) { + furi_assert(sector < 40); + return sector < 32 ? 4 : 16; +} + +void mf_classic_get_read_sectors_and_keys( + const MfClassicData* data, + uint8_t* sectors_read, + uint8_t* keys_found) { + furi_assert(data); + furi_assert(sectors_read); + furi_assert(keys_found); + + *sectors_read = 0; + *keys_found = 0; + uint8_t sectors_total = mf_classic_get_total_sectors_num(data->type); + for(size_t i = 0; i < sectors_total; i++) { + if(mf_classic_is_key_found(data, i, MfClassicKeyTypeA)) { + *keys_found += 1; + } + if(mf_classic_is_key_found(data, i, MfClassicKeyTypeB)) { + *keys_found += 1; + } + uint8_t first_block = mf_classic_get_first_block_num_of_sector(i); + uint8_t total_blocks_in_sec = mf_classic_get_blocks_num_in_sector(i); + bool blocks_read = true; + for(size_t j = first_block; j < first_block + total_blocks_in_sec; j++) { + blocks_read = mf_classic_is_block_read(data, j); + if(!blocks_read) break; + } + if(blocks_read) { + *sectors_read += 1; + } + } +} + +bool mf_classic_is_card_read(const MfClassicData* data) { + furi_assert(data); + + uint8_t sectors_total = mf_classic_get_total_sectors_num(data->type); + uint8_t sectors_read = 0; + uint8_t keys_found = 0; + mf_classic_get_read_sectors_and_keys(data, §ors_read, &keys_found); + bool card_read = (sectors_read == sectors_total) && (keys_found == sectors_total * 2); + + return card_read; +} + +bool mf_classic_is_sector_read(const MfClassicData* data, uint8_t sector_num) { + furi_assert(data); + + bool sector_read = false; + do { + if(!mf_classic_is_key_found(data, sector_num, MfClassicKeyTypeA)) break; + if(!mf_classic_is_key_found(data, sector_num, MfClassicKeyTypeB)) break; + uint8_t start_block = mf_classic_get_first_block_num_of_sector(sector_num); + uint8_t total_blocks = mf_classic_get_blocks_num_in_sector(sector_num); + uint8_t block_read = true; + for(size_t i = start_block; i < start_block + total_blocks; i++) { + block_read = mf_classic_is_block_read(data, i); + if(!block_read) break; + } + sector_read = block_read; + } while(false); + + return sector_read; +} + +static bool mf_classic_is_allowed_access_sector_trailer( + MfClassicData* data, + uint8_t block_num, + MfClassicKeyType key_type, + MfClassicAction action) { + uint8_t sector_num = mf_classic_get_sector_by_block(block_num); + MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, sector_num); + uint8_t* access_bits_arr = sec_tr->access_bits.data; + uint8_t AC = ((access_bits_arr[1] >> 5) & 0x04) | ((access_bits_arr[2] >> 2) & 0x02) | + ((access_bits_arr[2] >> 7) & 0x01); + + switch(action) { + case MfClassicActionKeyARead: { + return false; + } + case MfClassicActionKeyAWrite: + case MfClassicActionKeyBWrite: { + return ( + (key_type == MfClassicKeyTypeA && (AC == 0x00 || AC == 0x01)) || + (key_type == MfClassicKeyTypeB && (AC == 0x04 || AC == 0x03))); + } + case MfClassicActionKeyBRead: { + return (key_type == MfClassicKeyTypeA && (AC == 0x00 || AC == 0x02 || AC == 0x01)); + } + case MfClassicActionACRead: { + return ( + (key_type == MfClassicKeyTypeA) || + (key_type == MfClassicKeyTypeB && !(AC == 0x00 || AC == 0x02 || AC == 0x01))); + } + case MfClassicActionACWrite: { + return ( + (key_type == MfClassicKeyTypeA && (AC == 0x01)) || + (key_type == MfClassicKeyTypeB && (AC == 0x03 || AC == 0x05))); + } + default: + return false; + } + return true; +} + +bool mf_classic_is_allowed_access_data_block( + MfClassicSectorTrailer* sec_tr, + uint8_t block_num, + MfClassicKeyType key_type, + MfClassicAction action) { + furi_assert(sec_tr); + + uint8_t* access_bits_arr = sec_tr->access_bits.data; + + if(block_num == 0 && action == MfClassicActionDataWrite) { + return false; + } + + uint8_t sector_block = 0; + 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 = ((access_bits_arr[1] >> 2) & 0x04) | ((access_bits_arr[2] << 1) & 0x02) | + ((access_bits_arr[2] >> 4) & 0x01); + break; + } + case 0x01: { + AC = ((access_bits_arr[1] >> 3) & 0x04) | ((access_bits_arr[2] >> 0) & 0x02) | + ((access_bits_arr[2] >> 5) & 0x01); + break; + } + case 0x02: { + AC = ((access_bits_arr[1] >> 4) & 0x04) | ((access_bits_arr[2] >> 1) & 0x02) | + ((access_bits_arr[2] >> 6) & 0x01); + break; + } + default: + return false; + } + + switch(action) { + case MfClassicActionDataRead: { + return ( + (key_type == MfClassicKeyTypeA && !(AC == 0x03 || AC == 0x05 || AC == 0x07)) || + (key_type == MfClassicKeyTypeB && !(AC == 0x07))); + } + case MfClassicActionDataWrite: { + return ( + (key_type == MfClassicKeyTypeA && (AC == 0x00)) || + (key_type == MfClassicKeyTypeB && + (AC == 0x00 || AC == 0x04 || AC == 0x06 || AC == 0x03))); + } + case MfClassicActionDataInc: { + return ( + (key_type == MfClassicKeyTypeA && (AC == 0x00)) || + (key_type == MfClassicKeyTypeB && (AC == 0x00 || AC == 0x06))); + } + case MfClassicActionDataDec: { + return ( + (key_type == MfClassicKeyTypeA && (AC == 0x00 || AC == 0x06 || AC == 0x01)) || + (key_type == MfClassicKeyTypeB && (AC == 0x00 || AC == 0x06 || AC == 0x01))); + } + default: + return false; + } + + return false; +} + +bool mf_classic_is_allowed_access( + MfClassicData* data, + uint8_t block_num, + MfClassicKeyType key_type, + MfClassicAction action) { + furi_assert(data); + + bool access_allowed = false; + if(mf_classic_is_sector_trailer(block_num)) { + access_allowed = + mf_classic_is_allowed_access_sector_trailer(data, block_num, key_type, action); + } else { + uint8_t sector_num = mf_classic_get_sector_by_block(block_num); + MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, sector_num); + access_allowed = + mf_classic_is_allowed_access_data_block(sec_tr, block_num, key_type, action); + } + + return access_allowed; +} + +bool mf_classic_is_value_block(MfClassicSectorTrailer* sec_tr, uint8_t block_num) { + furi_assert(sec_tr); + + // Check if key A can write, if it can, it's transport configuration, not data block + return !mf_classic_is_allowed_access_data_block( + sec_tr, block_num, MfClassicKeyTypeA, MfClassicActionDataWrite) && + (mf_classic_is_allowed_access_data_block( + sec_tr, block_num, MfClassicKeyTypeB, MfClassicActionDataInc) || + mf_classic_is_allowed_access_data_block( + sec_tr, block_num, MfClassicKeyTypeB, MfClassicActionDataDec)); +} diff --git a/lib/nfc/protocols/mf_classic/mf_classic.h b/lib/nfc/protocols/mf_classic/mf_classic.h new file mode 100644 index 00000000000..f180411df52 --- /dev/null +++ b/lib/nfc/protocols/mf_classic/mf_classic.h @@ -0,0 +1,235 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define MF_CLASSIC_CMD_AUTH_KEY_A (0x60U) +#define MF_CLASSIC_CMD_AUTH_KEY_B (0x61U) +#define MF_CLASSIC_CMD_READ_BLOCK (0x30U) +#define MF_CLASSIC_CMD_WRITE_BLOCK (0xA0U) +#define MF_CLASSIC_CMD_VALUE_DEC (0xC0U) +#define MF_CLASSIC_CMD_VALUE_INC (0xC1U) +#define MF_CLASSIC_CMD_VALUE_RESTORE (0xC2U) +#define MF_CLASSIC_CMD_VALUE_TRANSFER (0xB0U) + +#define MF_CLASSIC_CMD_HALT_MSB (0x50) +#define MF_CLASSIC_CMD_HALT_LSB (0x00) +#define MF_CLASSIC_CMD_ACK (0x0A) +#define MF_CLASSIC_CMD_NACK (0x00) + +#define MF_CLASSIC_TOTAL_SECTORS_MAX (40) +#define MF_CLASSIC_TOTAL_BLOCKS_MAX (256) +#define MF_CLASSIC_READ_MASK_SIZE (MF_CLASSIC_TOTAL_BLOCKS_MAX / 32) +#define MF_CLASSIC_BLOCK_SIZE (16) +#define MF_CLASSIC_KEY_SIZE (6) +#define MF_CLASSIC_ACCESS_BYTES_SIZE (4) + +#define MF_CLASSIC_NT_SIZE (4) +#define MF_CLASSIC_NR_SIZE (4) +#define MF_CLASSIC_AR_SIZE (4) +#define MF_CLASSIC_AT_SIZE (4) + +typedef enum { + MfClassicErrorNone, + MfClassicErrorNotPresent, + MfClassicErrorProtocol, + MfClassicErrorAuth, + MfClassicErrorTimeout, +} MfClassicError; + +typedef enum { + MfClassicTypeMini, + MfClassicType1k, + MfClassicType4k, + + MfClassicTypeNum, +} MfClassicType; + +typedef enum { + MfClassicActionDataRead, + MfClassicActionDataWrite, + MfClassicActionDataInc, + MfClassicActionDataDec, + + MfClassicActionKeyARead, + MfClassicActionKeyAWrite, + MfClassicActionKeyBRead, + MfClassicActionKeyBWrite, + MfClassicActionACRead, + MfClassicActionACWrite, +} MfClassicAction; + +typedef enum { + MfClassicValueCommandIncrement, + MfClassicValueCommandDecrement, + MfClassicValueCommandRestore, + + MfClassicValueCommandInvalid, +} MfClassicValueCommand; + +typedef struct { + uint8_t data[MF_CLASSIC_BLOCK_SIZE]; +} MfClassicBlock; + +typedef enum { + MfClassicKeyTypeA, + MfClassicKeyTypeB, +} MfClassicKeyType; + +typedef struct { + uint8_t data[MF_CLASSIC_KEY_SIZE]; +} MfClassicKey; + +typedef struct { + uint8_t data[MF_CLASSIC_ACCESS_BYTES_SIZE]; +} MfClassicAccessBits; + +typedef struct { + uint8_t data[MF_CLASSIC_NT_SIZE]; +} MfClassicNt; + +typedef struct { + uint8_t data[MF_CLASSIC_AT_SIZE]; +} MfClassicAt; + +typedef struct { + uint8_t data[MF_CLASSIC_NR_SIZE]; +} MfClassicNr; + +typedef struct { + uint8_t data[MF_CLASSIC_AR_SIZE]; +} MfClassicAr; + +typedef struct { + uint8_t block_num; + MfClassicKey key; + MfClassicKeyType key_type; + MfClassicNt nt; + MfClassicNr nr; + MfClassicAr ar; + MfClassicAt at; +} MfClassicAuthContext; + +typedef union { + MfClassicBlock block; + struct { + MfClassicKey key_a; + MfClassicAccessBits access_bits; + MfClassicKey key_b; + }; +} MfClassicSectorTrailer; + +typedef struct { + uint64_t key_a_mask; + MfClassicKey key_a[MF_CLASSIC_TOTAL_SECTORS_MAX]; + uint64_t key_b_mask; + MfClassicKey key_b[MF_CLASSIC_TOTAL_SECTORS_MAX]; +} MfClassicDeviceKeys; + +typedef struct { + Iso14443_3aData* iso14443_3a_data; + MfClassicType type; + uint32_t block_read_mask[MF_CLASSIC_READ_MASK_SIZE]; + uint64_t key_a_mask; + uint64_t key_b_mask; + MfClassicBlock block[MF_CLASSIC_TOTAL_BLOCKS_MAX]; +} MfClassicData; + +extern const NfcDeviceBase nfc_device_mf_classic; + +MfClassicData* mf_classic_alloc(); + +void mf_classic_free(MfClassicData* data); + +void mf_classic_reset(MfClassicData* data); + +void mf_classic_copy(MfClassicData* data, const MfClassicData* other); + +bool mf_classic_verify(MfClassicData* data, const FuriString* device_type); + +bool mf_classic_load(MfClassicData* data, FlipperFormat* ff, uint32_t version); + +bool mf_classic_save(const MfClassicData* data, FlipperFormat* ff); + +bool mf_classic_is_equal(const MfClassicData* data, const MfClassicData* other); + +const char* mf_classic_get_device_name(const MfClassicData* data, NfcDeviceNameType name_type); + +const uint8_t* mf_classic_get_uid(const MfClassicData* data, size_t* uid_len); + +bool mf_classic_set_uid(MfClassicData* data, const uint8_t* uid, size_t uid_len); + +Iso14443_3aData* mf_classic_get_base_data(const MfClassicData* data); + +uint8_t mf_classic_get_total_sectors_num(MfClassicType type); + +uint16_t mf_classic_get_total_block_num(MfClassicType type); + +uint8_t mf_classic_get_first_block_num_of_sector(uint8_t sector); + +uint8_t mf_classic_get_blocks_num_in_sector(uint8_t sector); + +uint8_t mf_classic_get_sector_trailer_num_by_sector(uint8_t sector); + +uint8_t mf_classic_get_sector_trailer_num_by_block(uint8_t block); + +MfClassicSectorTrailer* + mf_classic_get_sector_trailer_by_sector(const MfClassicData* data, uint8_t sector_num); + +bool mf_classic_is_sector_trailer(uint8_t block); + +uint8_t mf_classic_get_sector_by_block(uint8_t block); + +bool mf_classic_block_to_value(const MfClassicBlock* block, int32_t* value, uint8_t* addr); + +void mf_classic_value_to_block(int32_t value, uint8_t addr, MfClassicBlock* block); + +bool mf_classic_is_key_found( + const MfClassicData* data, + uint8_t sector_num, + MfClassicKeyType key_type); + +void mf_classic_set_key_found( + MfClassicData* data, + uint8_t sector_num, + MfClassicKeyType key_type, + uint64_t key); + +void mf_classic_set_key_not_found( + MfClassicData* data, + uint8_t sector_num, + MfClassicKeyType key_type); + +bool mf_classic_is_block_read(const MfClassicData* data, uint8_t block_num); + +void mf_classic_set_block_read(MfClassicData* data, uint8_t block_num, MfClassicBlock* block_data); + +bool mf_classic_is_sector_read(const MfClassicData* data, uint8_t sector_num); + +void mf_classic_get_read_sectors_and_keys( + const MfClassicData* data, + uint8_t* sectors_read, + uint8_t* keys_found); + +bool mf_classic_is_card_read(const MfClassicData* data); + +bool mf_classic_is_value_block(MfClassicSectorTrailer* sec_tr, uint8_t block_num); + +bool mf_classic_is_allowed_access_data_block( + MfClassicSectorTrailer* sec_tr, + uint8_t block_num, + MfClassicKeyType key_type, + MfClassicAction action); + +bool mf_classic_is_allowed_access( + MfClassicData* data, + uint8_t block_num, + MfClassicKeyType key_type, + MfClassicAction action); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/mf_classic/mf_classic_listener.c b/lib/nfc/protocols/mf_classic/mf_classic_listener.c new file mode 100644 index 00000000000..f7bd5b3f45f --- /dev/null +++ b/lib/nfc/protocols/mf_classic/mf_classic_listener.c @@ -0,0 +1,656 @@ +#include "mf_classic_listener_i.h" + +#include + +#include +#include + +#include +#include + +#define TAG "MfClassicListener" + +#define MF_CLASSIC_MAX_BUFF_SIZE (64) + +typedef MfClassicListenerCommand ( + *MfClassicListenerCommandHandler)(MfClassicListener* instance, BitBuffer* buf); + +typedef struct { + uint8_t cmd_start_byte; + size_t cmd_len_bits; + size_t command_num; + MfClassicListenerCommandHandler* handler; +} MfClassicListenerCmd; + +static void mf_classic_listener_prepare_emulation(MfClassicListener* instance) { + instance->total_block_num = mf_classic_get_total_block_num(instance->data->type); +} + +static void mf_classic_listener_reset_state(MfClassicListener* instance) { + crypto1_reset(instance->crypto); + memset(&instance->auth_context, 0, sizeof(MfClassicAuthContext)); + instance->comm_state = MfClassicListenerCommStatePlain; + instance->state = MfClassicListenerStateIdle; + instance->cmd_in_progress = false; + instance->current_cmd_handler_idx = 0; + instance->transfer_value = 0; + instance->value_cmd = MfClassicValueCommandInvalid; +} + +static MfClassicListenerCommand + mf_classic_listener_halt_handler(MfClassicListener* instance, BitBuffer* buff) { + MfClassicListenerCommand command = MfClassicListenerCommandNack; + + if(bit_buffer_get_byte(buff, 1) == MF_CLASSIC_CMD_HALT_LSB) { + mf_classic_listener_reset_state(instance); + command = MfClassicListenerCommandSleep; + } + + return command; +} + +static MfClassicListenerCommand mf_classic_listener_auth_first_part_handler( + MfClassicListener* instance, + MfClassicKeyType key_type, + uint8_t block_num) { + MfClassicListenerCommand command = MfClassicListenerCommandNack; + + do { + instance->state = MfClassicListenerStateIdle; + + if(block_num >= instance->total_block_num) { + mf_classic_listener_reset_state(instance); + break; + } + + uint8_t sector_num = mf_classic_get_sector_by_block(block_num); + + MfClassicSectorTrailer* sec_tr = + mf_classic_get_sector_trailer_by_sector(instance->data, sector_num); + MfClassicKey* key = (key_type == MfClassicKeyTypeA) ? &sec_tr->key_a : &sec_tr->key_b; + uint64_t key_num = nfc_util_bytes2num(key->data, sizeof(MfClassicKey)); + uint32_t cuid = iso14443_3a_get_cuid(instance->data->iso14443_3a_data); + + instance->auth_context.key_type = key_type; + instance->auth_context.block_num = block_num; + + furi_hal_random_fill_buf(instance->auth_context.nt.data, sizeof(MfClassicNt)); + uint32_t nt_num = nfc_util_bytes2num(instance->auth_context.nt.data, sizeof(MfClassicNt)); + + crypto1_init(instance->crypto, key_num); + if(instance->comm_state == MfClassicListenerCommStatePlain) { + crypto1_word(instance->crypto, nt_num ^ cuid, 0); + bit_buffer_copy_bytes( + instance->tx_encrypted_buffer, + instance->auth_context.nt.data, + sizeof(MfClassicNt)); + iso14443_3a_listener_tx(instance->iso14443_3a_listener, instance->tx_encrypted_buffer); + command = MfClassicListenerCommandProcessed; + } else { + uint8_t key_stream[4] = {}; + nfc_util_num2bytes(nt_num ^ cuid, sizeof(uint32_t), key_stream); + bit_buffer_copy_bytes( + instance->tx_plain_buffer, instance->auth_context.nt.data, sizeof(MfClassicNt)); + crypto1_encrypt( + instance->crypto, + key_stream, + instance->tx_plain_buffer, + instance->tx_encrypted_buffer); + + iso14443_3a_listener_tx_with_custom_parity( + instance->iso14443_3a_listener, instance->tx_encrypted_buffer); + + command = MfClassicListenerCommandProcessed; + } + + instance->cmd_in_progress = true; + instance->current_cmd_handler_idx++; + } while(false); + + return command; +} + +static MfClassicListenerCommand + mf_classic_listener_auth_key_a_handler(MfClassicListener* instance, BitBuffer* buff) { + MfClassicListenerCommand command = mf_classic_listener_auth_first_part_handler( + instance, MfClassicKeyTypeA, bit_buffer_get_byte(buff, 1)); + + return command; +} + +static MfClassicListenerCommand + mf_classic_listener_auth_key_b_handler(MfClassicListener* instance, BitBuffer* buff) { + MfClassicListenerCommand command = mf_classic_listener_auth_first_part_handler( + instance, MfClassicKeyTypeB, bit_buffer_get_byte(buff, 1)); + + return command; +} + +static MfClassicListenerCommand + mf_classic_listener_auth_second_part_handler(MfClassicListener* instance, BitBuffer* buff) { + MfClassicListenerCommand command = MfClassicListenerCommandSilent; + + do { + instance->cmd_in_progress = false; + + if(bit_buffer_get_size_bytes(buff) != (sizeof(MfClassicNr) + sizeof(MfClassicAr))) { + mf_classic_listener_reset_state(instance); + break; + } + bit_buffer_write_bytes_mid(buff, instance->auth_context.nr.data, 0, sizeof(MfClassicNr)); + bit_buffer_write_bytes_mid( + buff, instance->auth_context.ar.data, sizeof(MfClassicNr), sizeof(MfClassicAr)); + + if(instance->callback) { + instance->mfc_event.type = MfClassicListenerEventTypeAuthContextPartCollected, + instance->mfc_event_data.auth_context = instance->auth_context; + instance->callback(instance->generic_event, instance->context); + } + + uint32_t nr_num = nfc_util_bytes2num(instance->auth_context.nr.data, sizeof(MfClassicNr)); + uint32_t ar_num = nfc_util_bytes2num(instance->auth_context.ar.data, sizeof(MfClassicAr)); + + crypto1_word(instance->crypto, nr_num, 1); + uint32_t nt_num = nfc_util_bytes2num(instance->auth_context.nt.data, sizeof(MfClassicNt)); + uint32_t secret_poller = ar_num ^ crypto1_word(instance->crypto, 0, 0); + if(secret_poller != prng_successor(nt_num, 64)) { + FURI_LOG_D( + TAG, "Wrong reader key: %08lX != %08lX", secret_poller, prng_successor(nt_num, 64)); + mf_classic_listener_reset_state(instance); + break; + } + + uint32_t at_num = prng_successor(nt_num, 96); + nfc_util_num2bytes(at_num, sizeof(uint32_t), instance->auth_context.at.data); + bit_buffer_copy_bytes( + instance->tx_plain_buffer, instance->auth_context.at.data, sizeof(MfClassicAr)); + crypto1_encrypt( + instance->crypto, NULL, instance->tx_plain_buffer, instance->tx_encrypted_buffer); + iso14443_3a_listener_tx_with_custom_parity( + instance->iso14443_3a_listener, instance->tx_encrypted_buffer); + + instance->state = MfClassicListenerStateAuthComplete; + instance->comm_state = MfClassicListenerCommStateEncrypted; + command = MfClassicListenerCommandProcessed; + + if(instance->callback) { + instance->mfc_event.type = MfClassicListenerEventTypeAuthContextFullCollected, + instance->mfc_event_data.auth_context = instance->auth_context; + instance->callback(instance->generic_event, instance->context); + } + } while(false); + + return command; +} + +static MfClassicListenerCommand + mf_classic_listener_read_block_handler(MfClassicListener* instance, BitBuffer* buff) { + MfClassicListenerCommand command = MfClassicListenerCommandNack; + MfClassicAuthContext* auth_ctx = &instance->auth_context; + + do { + if(instance->state != MfClassicListenerStateAuthComplete) break; + + uint8_t block_num = bit_buffer_get_byte(buff, 1); + uint8_t sector_num = mf_classic_get_sector_by_block(block_num); + uint8_t auth_sector_num = mf_classic_get_sector_by_block(auth_ctx->block_num); + if(sector_num != auth_sector_num) break; + + MfClassicBlock access_block = instance->data->block[block_num]; + + if(mf_classic_is_sector_trailer(block_num)) { + MfClassicSectorTrailer* access_sec_tr = (MfClassicSectorTrailer*)&access_block; + if(!mf_classic_is_allowed_access( + instance->data, block_num, auth_ctx->key_type, MfClassicActionKeyARead)) { + memset(access_sec_tr->key_a.data, 0, sizeof(MfClassicKey)); + } + if(!mf_classic_is_allowed_access( + instance->data, block_num, auth_ctx->key_type, MfClassicActionKeyBRead)) { + memset(access_sec_tr->key_b.data, 0, sizeof(MfClassicKey)); + } + if(!mf_classic_is_allowed_access( + instance->data, block_num, auth_ctx->key_type, MfClassicActionACRead)) { + memset(access_sec_tr->access_bits.data, 0, sizeof(MfClassicAccessBits)); + } + } else if(!mf_classic_is_allowed_access( + instance->data, block_num, auth_ctx->key_type, MfClassicActionDataRead)) { + break; + } + + bit_buffer_copy_bytes( + instance->tx_plain_buffer, access_block.data, sizeof(MfClassicBlock)); + iso14443_crc_append(Iso14443CrcTypeA, instance->tx_plain_buffer); + crypto1_encrypt( + instance->crypto, NULL, instance->tx_plain_buffer, instance->tx_encrypted_buffer); + iso14443_3a_listener_tx_with_custom_parity( + instance->iso14443_3a_listener, instance->tx_encrypted_buffer); + command = MfClassicListenerCommandProcessed; + } while(false); + + return command; +} + +static MfClassicListenerCommand mf_classic_listener_write_block_first_part_handler( + MfClassicListener* instance, + BitBuffer* buff) { + MfClassicListenerCommand command = MfClassicListenerCommandNack; + MfClassicAuthContext* auth_ctx = &instance->auth_context; + + do { + if(instance->state != MfClassicListenerStateAuthComplete) break; + + uint8_t block_num = bit_buffer_get_byte(buff, 1); + if(block_num >= instance->total_block_num) break; + + uint8_t sector_num = mf_classic_get_sector_by_block(block_num); + uint8_t auth_sector_num = mf_classic_get_sector_by_block(auth_ctx->block_num); + if(sector_num != auth_sector_num) break; + + instance->cmd_in_progress = true; + instance->current_cmd_handler_idx++; + command = MfClassicListenerCommandAck; + } while(false); + + return command; +} + +static MfClassicListenerCommand mf_classic_listener_write_block_second_part_handler( + MfClassicListener* instance, + BitBuffer* buff) { + MfClassicListenerCommand command = MfClassicListenerCommandNack; + MfClassicAuthContext* auth_ctx = &instance->auth_context; + + do { + instance->cmd_in_progress = false; + + size_t buff_size = bit_buffer_get_size_bytes(buff); + if(buff_size != sizeof(MfClassicBlock)) break; + + uint8_t block_num = auth_ctx->block_num; + MfClassicKeyType key_type = auth_ctx->key_type; + MfClassicBlock block = instance->data->block[block_num]; + + if(mf_classic_is_sector_trailer(block_num)) { + MfClassicSectorTrailer* sec_tr = (MfClassicSectorTrailer*)█ + if(mf_classic_is_allowed_access( + instance->data, block_num, key_type, MfClassicActionKeyAWrite)) { + bit_buffer_write_bytes_mid(buff, sec_tr->key_a.data, 0, sizeof(MfClassicKey)); + } + if(mf_classic_is_allowed_access( + instance->data, block_num, key_type, MfClassicActionKeyBWrite)) { + bit_buffer_write_bytes_mid(buff, sec_tr->key_b.data, 10, sizeof(MfClassicKey)); + } + if(mf_classic_is_allowed_access( + instance->data, block_num, key_type, MfClassicActionACWrite)) { + bit_buffer_write_bytes_mid( + buff, sec_tr->access_bits.data, 6, sizeof(MfClassicAccessBits)); + } + } else { + if(mf_classic_is_allowed_access( + instance->data, block_num, key_type, MfClassicActionDataWrite)) { + bit_buffer_write_bytes_mid(buff, block.data, 0, sizeof(MfClassicBlock)); + } else { + break; + } + } + + instance->data->block[block_num] = block; + command = MfClassicListenerCommandAck; + } while(false); + + return command; +} + +static MfClassicListenerCommand + mf_classic_listener_value_cmd_handler(MfClassicListener* instance, BitBuffer* buff) { + MfClassicListenerCommand command = MfClassicListenerCommandNack; + MfClassicAuthContext* auth_ctx = &instance->auth_context; + + do { + if(instance->state != MfClassicListenerStateAuthComplete) break; + + uint8_t block_num = bit_buffer_get_byte(buff, 1); + if(block_num >= instance->total_block_num) break; + + uint8_t sector_num = mf_classic_get_sector_by_block(block_num); + uint8_t auth_sector_num = mf_classic_get_sector_by_block(auth_ctx->block_num); + if(sector_num != auth_sector_num) break; + + uint8_t cmd = bit_buffer_get_byte(buff, 0); + MfClassicAction action = MfClassicActionDataDec; + if(cmd == MF_CLASSIC_CMD_VALUE_DEC) { + instance->value_cmd = MfClassicValueCommandDecrement; + } else if(cmd == MF_CLASSIC_CMD_VALUE_INC) { + instance->value_cmd = MfClassicValueCommandIncrement; + action = MfClassicActionDataInc; + } else if(cmd == MF_CLASSIC_CMD_VALUE_RESTORE) { + instance->value_cmd = MfClassicValueCommandRestore; + } else { + break; + } + + if(!mf_classic_is_allowed_access(instance->data, block_num, auth_ctx->key_type, action)) { + break; + } + + if(!mf_classic_block_to_value( + &instance->data->block[block_num], &instance->transfer_value, NULL)) { + break; + } + + instance->cmd_in_progress = true; + instance->current_cmd_handler_idx++; + command = MfClassicListenerCommandAck; + } while(false); + + return command; +} + +static MfClassicListenerCommand + mf_classic_listener_value_dec_handler(MfClassicListener* instance, BitBuffer* buff) { + return mf_classic_listener_value_cmd_handler(instance, buff); +} + +static MfClassicListenerCommand + mf_classic_listener_value_inc_handler(MfClassicListener* instance, BitBuffer* buff) { + return mf_classic_listener_value_cmd_handler(instance, buff); +} + +static MfClassicListenerCommand + mf_classic_listener_value_restore_handler(MfClassicListener* instance, BitBuffer* buff) { + return mf_classic_listener_value_cmd_handler(instance, buff); +} + +static MfClassicListenerCommand + mf_classic_listener_value_data_receive_handler(MfClassicListener* instance, BitBuffer* buff) { + MfClassicListenerCommand command = MfClassicListenerCommandNack; + + do { + if(bit_buffer_get_size_bytes(buff) != 4) break; + + int32_t data; + bit_buffer_write_bytes_mid(buff, &data, 0, sizeof(data)); + + if(data < 0) { + data = -data; + } + + if(instance->value_cmd == MfClassicValueCommandDecrement) { + data = -data; + } else if(instance->value_cmd == MfClassicValueCommandRestore) { + data = 0; + } + + instance->transfer_value += data; + + instance->cmd_in_progress = true; + instance->current_cmd_handler_idx++; + command = MfClassicListenerCommandSilent; + } while(false); + + return command; +} + +static MfClassicListenerCommand + mf_classic_listener_value_transfer_handler(MfClassicListener* instance, BitBuffer* buff) { + MfClassicListenerCommand command = MfClassicListenerCommandNack; + MfClassicAuthContext* auth_ctx = &instance->auth_context; + + do { + instance->cmd_in_progress = false; + + if(bit_buffer_get_size_bytes(buff) != 2) break; + if(bit_buffer_get_byte(buff, 0) != MF_CLASSIC_CMD_VALUE_TRANSFER) break; + + uint8_t block_num = bit_buffer_get_byte(buff, 1); + if(!mf_classic_is_allowed_access( + instance->data, block_num, auth_ctx->key_type, MfClassicActionDataDec)) { + break; + } + + mf_classic_value_to_block( + instance->transfer_value, block_num, &instance->data->block[block_num]); + instance->transfer_value = 0; + + command = MfClassicListenerCommandAck; + } while(false); + + return command; +} + +static MfClassicListenerCommandHandler mf_classic_listener_halt_handlers[] = { + mf_classic_listener_halt_handler, +}; + +static MfClassicListenerCommandHandler mf_classic_listener_auth_key_a_handlers[] = { + mf_classic_listener_auth_key_a_handler, + mf_classic_listener_auth_second_part_handler, +}; + +static MfClassicListenerCommandHandler mf_classic_listener_auth_key_b_handlers[] = { + mf_classic_listener_auth_key_b_handler, + mf_classic_listener_auth_second_part_handler, +}; + +static MfClassicListenerCommandHandler mf_classic_listener_read_block_handlers[] = { + mf_classic_listener_read_block_handler, +}; + +static MfClassicListenerCommandHandler mf_classic_listener_write_block_handlers[] = { + mf_classic_listener_write_block_first_part_handler, + mf_classic_listener_write_block_second_part_handler, +}; + +static MfClassicListenerCommandHandler mf_classic_listener_value_dec_handlers[] = { + mf_classic_listener_value_dec_handler, + mf_classic_listener_value_data_receive_handler, + mf_classic_listener_value_transfer_handler, +}; + +static MfClassicListenerCommandHandler mf_classic_listener_value_inc_handlers[] = { + mf_classic_listener_value_inc_handler, + mf_classic_listener_value_data_receive_handler, + mf_classic_listener_value_transfer_handler, +}; + +static MfClassicListenerCommandHandler mf_classic_listener_value_restore_handlers[] = { + mf_classic_listener_value_restore_handler, + mf_classic_listener_value_data_receive_handler, + mf_classic_listener_value_transfer_handler, +}; + +static const MfClassicListenerCmd mf_classic_listener_cmd_handlers[] = { + { + .cmd_start_byte = MF_CLASSIC_CMD_HALT_MSB, + .cmd_len_bits = 2 * 8, + .command_num = COUNT_OF(mf_classic_listener_halt_handlers), + .handler = mf_classic_listener_halt_handlers, + }, + { + .cmd_start_byte = MF_CLASSIC_CMD_AUTH_KEY_A, + .cmd_len_bits = 2 * 8, + .command_num = COUNT_OF(mf_classic_listener_auth_key_a_handlers), + .handler = mf_classic_listener_auth_key_a_handlers, + }, + { + .cmd_start_byte = MF_CLASSIC_CMD_AUTH_KEY_B, + .cmd_len_bits = 2 * 8, + .command_num = COUNT_OF(mf_classic_listener_auth_key_b_handlers), + .handler = mf_classic_listener_auth_key_b_handlers, + }, + { + .cmd_start_byte = MF_CLASSIC_CMD_READ_BLOCK, + .cmd_len_bits = 2 * 8, + .command_num = COUNT_OF(mf_classic_listener_read_block_handlers), + .handler = mf_classic_listener_read_block_handlers, + }, + { + .cmd_start_byte = MF_CLASSIC_CMD_WRITE_BLOCK, + .cmd_len_bits = 2 * 8, + .command_num = COUNT_OF(mf_classic_listener_write_block_handlers), + .handler = mf_classic_listener_write_block_handlers, + }, + { + .cmd_start_byte = MF_CLASSIC_CMD_VALUE_DEC, + .cmd_len_bits = 2 * 8, + .command_num = COUNT_OF(mf_classic_listener_value_dec_handlers), + .handler = mf_classic_listener_value_dec_handlers, + }, + { + .cmd_start_byte = MF_CLASSIC_CMD_VALUE_INC, + .cmd_len_bits = 2 * 8, + .command_num = COUNT_OF(mf_classic_listener_value_inc_handlers), + .handler = mf_classic_listener_value_inc_handlers, + }, + { + .cmd_start_byte = MF_CLASSIC_CMD_VALUE_RESTORE, + .cmd_len_bits = 2 * 8, + .command_num = COUNT_OF(mf_classic_listener_value_restore_handlers), + .handler = mf_classic_listener_value_restore_handlers, + }, +}; + +static void mf_classic_listener_send_short_frame(MfClassicListener* instance, uint8_t data) { + BitBuffer* tx_buffer = instance->tx_plain_buffer; + + bit_buffer_set_size(instance->tx_plain_buffer, 4); + bit_buffer_set_byte(instance->tx_plain_buffer, 0, data); + if(instance->comm_state == MfClassicListenerCommStateEncrypted) { + crypto1_encrypt( + instance->crypto, NULL, instance->tx_plain_buffer, instance->tx_encrypted_buffer); + tx_buffer = instance->tx_encrypted_buffer; + } + + iso14443_3a_listener_tx_with_custom_parity(instance->iso14443_3a_listener, tx_buffer); +} + +NfcCommand mf_classic_listener_run(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.event_data); + furi_assert(event.protocol == NfcProtocolIso14443_3a); + + NfcCommand command = NfcCommandContinue; + MfClassicListener* instance = context; + Iso14443_3aListenerEvent* iso3_event = event.event_data; + BitBuffer* rx_buffer_plain; + + if(iso3_event->type == Iso14443_3aListenerEventTypeFieldOff) { + mf_classic_listener_reset_state(instance); + command = NfcCommandSleep; + } else if( + (iso3_event->type == Iso14443_3aListenerEventTypeReceivedData) || + (iso3_event->type == Iso14443_3aListenerEventTypeReceivedStandardFrame)) { + if(instance->comm_state == MfClassicListenerCommStateEncrypted) { + if(instance->state == MfClassicListenerStateAuthComplete) { + crypto1_decrypt( + instance->crypto, iso3_event->data->buffer, instance->rx_plain_buffer); + rx_buffer_plain = instance->rx_plain_buffer; + if(iso14443_crc_check(Iso14443CrcTypeA, rx_buffer_plain)) { + iso14443_crc_trim(rx_buffer_plain); + } + } else { + rx_buffer_plain = iso3_event->data->buffer; + } + } else { + rx_buffer_plain = iso3_event->data->buffer; + } + + MfClassicListenerCommand mfc_command = MfClassicListenerCommandNack; + if(instance->cmd_in_progress) { + mfc_command = + mf_classic_listener_cmd_handlers[instance->current_cmd_idx] + .handler[instance->current_cmd_handler_idx](instance, rx_buffer_plain); + } else { + for(size_t i = 0; i < COUNT_OF(mf_classic_listener_cmd_handlers); i++) { + if(bit_buffer_get_size(rx_buffer_plain) != + mf_classic_listener_cmd_handlers[i].cmd_len_bits) { + continue; + } + if(bit_buffer_get_byte(rx_buffer_plain, 0) != + mf_classic_listener_cmd_handlers[i].cmd_start_byte) { + continue; + } + instance->current_cmd_idx = i; + instance->current_cmd_handler_idx = 0; + mfc_command = + mf_classic_listener_cmd_handlers[i].handler[0](instance, rx_buffer_plain); + break; + } + } + + if(mfc_command == MfClassicListenerCommandAck) { + mf_classic_listener_send_short_frame(instance, MF_CLASSIC_CMD_ACK); + } else if(mfc_command == MfClassicListenerCommandNack) { + mf_classic_listener_send_short_frame(instance, MF_CLASSIC_CMD_NACK); + } else if(mfc_command == MfClassicListenerCommandSilent) { + command = NfcCommandReset; + } else if(mfc_command == MfClassicListenerCommandSleep) { + command = NfcCommandSleep; + } + } else if(iso3_event->type == Iso14443_3aListenerEventTypeHalted) { + mf_classic_listener_reset_state(instance); + } + + return command; +} + +MfClassicListener* + mf_classic_listener_alloc(Iso14443_3aListener* iso14443_3a_listener, MfClassicData* data) { + MfClassicListener* instance = malloc(sizeof(MfClassicListener)); + instance->iso14443_3a_listener = iso14443_3a_listener; + instance->data = data; + mf_classic_listener_prepare_emulation(instance); + + instance->crypto = crypto1_alloc(); + instance->tx_plain_buffer = bit_buffer_alloc(MF_CLASSIC_MAX_BUFF_SIZE); + instance->tx_encrypted_buffer = bit_buffer_alloc(MF_CLASSIC_MAX_BUFF_SIZE); + instance->rx_plain_buffer = bit_buffer_alloc(MF_CLASSIC_MAX_BUFF_SIZE); + + instance->mfc_event.data = &instance->mfc_event_data; + instance->generic_event.protocol = NfcProtocolMfClassic; + instance->generic_event.event_data = &instance->mfc_event; + instance->generic_event.instance = instance; + + return instance; +} + +void mf_classic_listener_free(MfClassicListener* instance) { + furi_assert(instance); + furi_assert(instance->data); + furi_assert(instance->crypto); + furi_assert(instance->rx_plain_buffer); + furi_assert(instance->tx_encrypted_buffer); + furi_assert(instance->tx_plain_buffer); + + crypto1_free(instance->crypto); + bit_buffer_free(instance->rx_plain_buffer); + bit_buffer_free(instance->tx_encrypted_buffer); + bit_buffer_free(instance->tx_plain_buffer); + + free(instance); +} + +void mf_classic_listener_set_callback( + MfClassicListener* instance, + NfcGenericCallback callback, + void* context) { + furi_assert(instance); + + instance->callback = callback; + instance->context = context; +} + +const MfClassicData* mf_classic_listener_get_data(const MfClassicListener* instance) { + furi_assert(instance); + furi_assert(instance->data); + + return instance->data; +} + +const NfcListenerBase mf_classic_listener = { + .alloc = (NfcListenerAlloc)mf_classic_listener_alloc, + .free = (NfcListenerFree)mf_classic_listener_free, + .set_callback = (NfcListenerSetCallback)mf_classic_listener_set_callback, + .get_data = (NfcListenerGetData)mf_classic_listener_get_data, + .run = (NfcListenerRun)mf_classic_listener_run, +}; diff --git a/lib/nfc/protocols/mf_classic/mf_classic_listener.h b/lib/nfc/protocols/mf_classic/mf_classic_listener.h new file mode 100644 index 00000000000..5169de62d81 --- /dev/null +++ b/lib/nfc/protocols/mf_classic/mf_classic_listener.h @@ -0,0 +1,27 @@ +#pragma once + +#include "mf_classic.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct MfClassicListener MfClassicListener; + +typedef enum { + MfClassicListenerEventTypeAuthContextPartCollected, + MfClassicListenerEventTypeAuthContextFullCollected, +} MfClassicListenerEventType; + +typedef union { + MfClassicAuthContext auth_context; +} MfClassicListenerEventData; + +typedef struct { + MfClassicListenerEventType type; + MfClassicListenerEventData* data; +} MfClassicListenerEvent; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/mf_classic/mf_classic_listener_defs.h b/lib/nfc/protocols/mf_classic/mf_classic_listener_defs.h new file mode 100644 index 00000000000..6c7a1dd6a74 --- /dev/null +++ b/lib/nfc/protocols/mf_classic/mf_classic_listener_defs.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern const NfcListenerBase mf_classic_listener; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/mf_classic/mf_classic_listener_i.h b/lib/nfc/protocols/mf_classic/mf_classic_listener_i.h new file mode 100644 index 00000000000..4b040bec12c --- /dev/null +++ b/lib/nfc/protocols/mf_classic/mf_classic_listener_i.h @@ -0,0 +1,62 @@ +#pragma once + +#include "mf_classic_listener.h" +#include +#include +#include "crypto1.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + MfClassicListenerCommandProcessed, + MfClassicListenerCommandAck, + MfClassicListenerCommandNack, + MfClassicListenerCommandSilent, + MfClassicListenerCommandSleep, +} MfClassicListenerCommand; + +typedef enum { + MfClassicListenerStateIdle, + MfClassicListenerStateAuthComplete, +} MfClassicListenerState; + +typedef enum { + MfClassicListenerCommStatePlain, + MfClassicListenerCommStateEncrypted, +} MfClassicListenerCommState; + +struct MfClassicListener { + Iso14443_3aListener* iso14443_3a_listener; + MfClassicListenerState state; + MfClassicListenerCommState comm_state; + + MfClassicData* data; + BitBuffer* tx_plain_buffer; + BitBuffer* tx_encrypted_buffer; + BitBuffer* rx_plain_buffer; + + Crypto1* crypto; + MfClassicAuthContext auth_context; + + // Value operation data + int32_t transfer_value; + MfClassicValueCommand value_cmd; + + NfcGenericEvent generic_event; + MfClassicListenerEvent mfc_event; + MfClassicListenerEventData mfc_event_data; + NfcGenericCallback callback; + void* context; + + bool cmd_in_progress; + size_t current_cmd_idx; + size_t current_cmd_handler_idx; + + size_t total_block_num; +}; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c new file mode 100644 index 00000000000..3eba6ee5694 --- /dev/null +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -0,0 +1,951 @@ +#include "mf_classic_poller_i.h" + +#include + +#include + +#define TAG "MfClassicPoller" + +#define MF_CLASSIC_MAX_BUFF_SIZE (64) + +typedef NfcCommand (*MfClassicPollerReadHandler)(MfClassicPoller* instance); + +MfClassicPoller* mf_classic_poller_alloc(Iso14443_3aPoller* iso14443_3a_poller) { + furi_assert(iso14443_3a_poller); + + MfClassicPoller* instance = malloc(sizeof(MfClassicPoller)); + instance->iso14443_3a_poller = iso14443_3a_poller; + instance->data = mf_classic_alloc(); + instance->crypto = crypto1_alloc(); + instance->tx_plain_buffer = bit_buffer_alloc(MF_CLASSIC_MAX_BUFF_SIZE); + instance->tx_encrypted_buffer = bit_buffer_alloc(MF_CLASSIC_MAX_BUFF_SIZE); + instance->rx_plain_buffer = bit_buffer_alloc(MF_CLASSIC_MAX_BUFF_SIZE); + instance->rx_encrypted_buffer = bit_buffer_alloc(MF_CLASSIC_MAX_BUFF_SIZE); + instance->current_type_check = MfClassicType4k; + + instance->mfc_event.data = &instance->mfc_event_data; + + instance->general_event.protocol = NfcProtocolMfClassic; + instance->general_event.event_data = &instance->mfc_event; + instance->general_event.instance = instance; + + return instance; +} + +void mf_classic_poller_free(MfClassicPoller* instance) { + furi_assert(instance); + furi_assert(instance->data); + furi_assert(instance->crypto); + furi_assert(instance->tx_plain_buffer); + furi_assert(instance->rx_plain_buffer); + furi_assert(instance->tx_encrypted_buffer); + furi_assert(instance->rx_encrypted_buffer); + + mf_classic_free(instance->data); + crypto1_free(instance->crypto); + bit_buffer_free(instance->tx_plain_buffer); + bit_buffer_free(instance->rx_plain_buffer); + bit_buffer_free(instance->tx_encrypted_buffer); + bit_buffer_free(instance->rx_encrypted_buffer); + + free(instance); +} + +static NfcCommand mf_classic_poller_handle_data_update(MfClassicPoller* instance) { + MfClassicPollerEventDataUpdate* data_update = &instance->mfc_event_data.data_update; + + mf_classic_get_read_sectors_and_keys( + instance->data, &data_update->sectors_read, &data_update->keys_found); + data_update->current_sector = instance->mode_ctx.dict_attack_ctx.current_sector; + instance->mfc_event.type = MfClassicPollerEventTypeDataUpdate; + return instance->callback(instance->general_event, instance->context); +} + +static void mf_classic_poller_check_key_b_is_readable( + MfClassicPoller* instance, + uint8_t block_num, + MfClassicBlock* data) { + do { + if(!mf_classic_is_sector_trailer(block_num)) break; + if(!mf_classic_is_allowed_access( + instance->data, block_num, MfClassicKeyTypeA, MfClassicActionKeyBRead)) + break; + + MfClassicSectorTrailer* sec_tr = (MfClassicSectorTrailer*)data; + uint64_t key_b = nfc_util_bytes2num(sec_tr->key_b.data, sizeof(MfClassicKey)); + uint8_t sector_num = mf_classic_get_sector_by_block(block_num); + mf_classic_set_key_found(instance->data, sector_num, MfClassicKeyTypeB, key_b); + } while(false); +} + +NfcCommand mf_classic_poller_handler_detect_type(MfClassicPoller* instance) { + NfcCommand command = NfcCommandReset; + + if(instance->current_type_check == MfClassicType4k) { + iso14443_3a_copy( + instance->data->iso14443_3a_data, + iso14443_3a_poller_get_data(instance->iso14443_3a_poller)); + MfClassicError error = mf_classic_async_get_nt(instance, 254, MfClassicKeyTypeA, NULL); + if(error == MfClassicErrorNone) { + instance->data->type = MfClassicType4k; + instance->state = MfClassicPollerStateStart; + instance->current_type_check = MfClassicType4k; + FURI_LOG_D(TAG, "4K detected"); + } else { + instance->current_type_check = MfClassicType1k; + } + } else if(instance->current_type_check == MfClassicType1k) { + MfClassicError error = mf_classic_async_get_nt(instance, 62, MfClassicKeyTypeA, NULL); + if(error == MfClassicErrorNone) { + instance->data->type = MfClassicType1k; + FURI_LOG_D(TAG, "1K detected"); + } else { + instance->data->type = MfClassicTypeMini; + FURI_LOG_D(TAG, "Mini detected"); + } + instance->current_type_check = MfClassicType4k; + instance->state = MfClassicPollerStateStart; + } + + return command; +} + +NfcCommand mf_classic_poller_handler_start(MfClassicPoller* instance) { + NfcCommand command = NfcCommandContinue; + + instance->sectors_total = mf_classic_get_total_sectors_num(instance->data->type); + memset(&instance->mode_ctx, 0, sizeof(MfClassicPollerModeContext)); + + instance->mfc_event.type = MfClassicPollerEventTypeRequestMode; + command = instance->callback(instance->general_event, instance->context); + + if(instance->mfc_event_data.poller_mode.mode == MfClassicPollerModeDictAttack) { + mf_classic_copy(instance->data, instance->mfc_event_data.poller_mode.data); + instance->state = MfClassicPollerStateRequestKey; + } else if(instance->mfc_event_data.poller_mode.mode == MfClassicPollerModeRead) { + instance->state = MfClassicPollerStateRequestReadSector; + } else if(instance->mfc_event_data.poller_mode.mode == MfClassicPollerModeWrite) { + instance->state = MfClassicPollerStateRequestSectorTrailer; + } else { + furi_crash("Invalid mode selected"); + } + + return command; +} + +NfcCommand mf_classic_poller_handler_request_sector_trailer(MfClassicPoller* instance) { + NfcCommand command = NfcCommandContinue; + MfClassicPollerWriteContext* write_ctx = &instance->mode_ctx.write_ctx; + + if(write_ctx->current_sector == instance->sectors_total) { + instance->state = MfClassicPollerStateSuccess; + } else { + instance->mfc_event.type = MfClassicPollerEventTypeRequestSectorTrailer; + instance->mfc_event_data.sec_tr_data.sector_num = write_ctx->current_sector; + command = instance->callback(instance->general_event, instance->context); + if(instance->mfc_event_data.sec_tr_data.sector_trailer_provided) { + instance->state = MfClassicPollerStateCheckWriteConditions; + memcpy( + &write_ctx->sec_tr, + &instance->mfc_event_data.sec_tr_data.sector_trailer, + sizeof(MfClassicSectorTrailer)); + write_ctx->current_block = + MAX(1, mf_classic_get_first_block_num_of_sector(write_ctx->current_sector)); + + } else { + write_ctx->current_sector++; + } + } + + return command; +} + +NfcCommand mf_classic_handler_check_write_conditions(MfClassicPoller* instance) { + NfcCommand command = NfcCommandContinue; + + MfClassicPollerWriteContext* write_ctx = &instance->mode_ctx.write_ctx; + MfClassicSectorTrailer* sec_tr = &write_ctx->sec_tr; + + do { + // Check last block in sector to write + uint8_t sec_tr_block_num = + mf_classic_get_sector_trailer_num_by_sector(write_ctx->current_sector); + if(write_ctx->current_block == sec_tr_block_num) { + write_ctx->current_sector++; + instance->state = MfClassicPollerStateRequestSectorTrailer; + break; + } + + // Check write and read access + if(mf_classic_is_allowed_access_data_block( + sec_tr, write_ctx->current_block, MfClassicKeyTypeA, MfClassicActionDataWrite)) { + write_ctx->key_type_write = MfClassicKeyTypeA; + } else if(mf_classic_is_allowed_access_data_block( + sec_tr, + write_ctx->current_block, + MfClassicKeyTypeB, + MfClassicActionDataWrite)) { + write_ctx->key_type_write = MfClassicKeyTypeB; + } else if(mf_classic_is_value_block(sec_tr, write_ctx->current_block)) { + write_ctx->is_value_block = true; + } else { + FURI_LOG_D(TAG, "Not allowed to write block %d", write_ctx->current_block); + write_ctx->current_block++; + break; + } + + if(mf_classic_is_allowed_access_data_block( + sec_tr, + write_ctx->current_block, + write_ctx->key_type_write, + MfClassicActionDataRead)) { + write_ctx->key_type_read = write_ctx->key_type_write; + } else { + write_ctx->key_type_read = write_ctx->key_type_write == MfClassicKeyTypeA ? + MfClassicKeyTypeB : + MfClassicKeyTypeA; + if(!mf_classic_is_allowed_access_data_block( + sec_tr, + write_ctx->current_block, + write_ctx->key_type_read, + MfClassicActionDataRead)) { + FURI_LOG_D(TAG, "Not allowed to read block %d", write_ctx->current_block); + write_ctx->current_block++; + break; + } + } + + write_ctx->need_halt_before_write = + (write_ctx->key_type_read != write_ctx->key_type_write); + instance->state = MfClassicPollerStateReadBlock; + } while(false); + + return command; +} + +NfcCommand mf_classic_poller_handler_read_block(MfClassicPoller* instance) { + NfcCommand command = NfcCommandContinue; + MfClassicPollerWriteContext* write_ctx = &instance->mode_ctx.write_ctx; + + MfClassicKey* auth_key = write_ctx->key_type_read == MfClassicKeyTypeA ? + &write_ctx->sec_tr.key_a : + &write_ctx->sec_tr.key_b; + MfClassicError error = MfClassicErrorNone; + + do { + // Authenticate to sector + error = mf_classic_async_auth( + instance, write_ctx->current_block, auth_key, write_ctx->key_type_read, NULL); + if(error != MfClassicErrorNone) { + FURI_LOG_D(TAG, "Failed to auth to block %d", write_ctx->current_block); + instance->state = MfClassicPollerStateFail; + break; + } + + // Read block from tag + error = + mf_classic_async_read_block(instance, write_ctx->current_block, &write_ctx->tag_block); + if(error != MfClassicErrorNone) { + FURI_LOG_D(TAG, "Failed to read block %d", write_ctx->current_block); + instance->state = MfClassicPollerStateFail; + break; + } + + if(write_ctx->is_value_block) { + mf_classic_async_halt(instance); + instance->state = MfClassicPollerStateWriteValueBlock; + } else { + if(write_ctx->need_halt_before_write) { + mf_classic_async_halt(instance); + } + instance->state = MfClassicPollerStateWriteBlock; + } + } while(false); + + return command; +} + +NfcCommand mf_classic_poller_handler_write_block(MfClassicPoller* instance) { + NfcCommand command = NfcCommandContinue; + + MfClassicPollerWriteContext* write_ctx = &instance->mode_ctx.write_ctx; + MfClassicKey* auth_key = write_ctx->key_type_write == MfClassicKeyTypeA ? + &write_ctx->sec_tr.key_a : + &write_ctx->sec_tr.key_b; + MfClassicError error = MfClassicErrorNone; + + do { + // Request block to write + instance->mfc_event.type = MfClassicPollerEventTypeRequestWriteBlock; + instance->mfc_event_data.write_block_data.block_num = write_ctx->current_block; + command = instance->callback(instance->general_event, instance->context); + if(!instance->mfc_event_data.write_block_data.write_block_provided) break; + + // Compare tag and saved block + if(memcmp( + write_ctx->tag_block.data, + instance->mfc_event_data.write_block_data.write_block.data, + sizeof(MfClassicBlock)) == 0) { + FURI_LOG_D(TAG, "Block %d is equal. Skip writing", write_ctx->current_block); + break; + } + + // Reauth if necessary + if(write_ctx->need_halt_before_write) { + error = mf_classic_async_auth( + instance, write_ctx->current_block, auth_key, write_ctx->key_type_write, NULL); + if(error != MfClassicErrorNone) { + FURI_LOG_D( + TAG, "Failed to auth to block %d for writing", write_ctx->current_block); + instance->state = MfClassicPollerStateFail; + break; + } + } + + // Write block + error = mf_classic_async_write_block( + instance, + write_ctx->current_block, + &instance->mfc_event_data.write_block_data.write_block); + if(error != MfClassicErrorNone) { + FURI_LOG_D(TAG, "Failed to write block %d", write_ctx->current_block); + instance->state = MfClassicPollerStateFail; + break; + } + + } while(false); + + mf_classic_async_halt(instance); + write_ctx->current_block++; + instance->state = MfClassicPollerStateCheckWriteConditions; + + return command; +} + +NfcCommand mf_classic_poller_handler_write_value_block(MfClassicPoller* instance) { + NfcCommand command = NfcCommandContinue; + MfClassicPollerWriteContext* write_ctx = &instance->mode_ctx.write_ctx; + + do { + // Request block to write + instance->mfc_event.type = MfClassicPollerEventTypeRequestWriteBlock; + instance->mfc_event_data.write_block_data.block_num = write_ctx->current_block; + command = instance->callback(instance->general_event, instance->context); + if(!instance->mfc_event_data.write_block_data.write_block_provided) break; + + // Compare tag and saved block + if(memcmp( + write_ctx->tag_block.data, + instance->mfc_event_data.write_block_data.write_block.data, + sizeof(MfClassicBlock)) == 0) { + FURI_LOG_D(TAG, "Block %d is equal. Skip writing", write_ctx->current_block); + break; + } + + bool key_a_inc_allowed = mf_classic_is_allowed_access_data_block( + &write_ctx->sec_tr, + write_ctx->current_block, + MfClassicKeyTypeA, + MfClassicActionDataInc); + bool key_b_inc_allowed = mf_classic_is_allowed_access_data_block( + &write_ctx->sec_tr, + write_ctx->current_block, + MfClassicKeyTypeB, + MfClassicActionDataInc); + bool key_a_dec_allowed = mf_classic_is_allowed_access_data_block( + &write_ctx->sec_tr, + write_ctx->current_block, + MfClassicKeyTypeA, + MfClassicActionDataDec); + bool key_b_dec_allowed = mf_classic_is_allowed_access_data_block( + &write_ctx->sec_tr, + write_ctx->current_block, + MfClassicKeyTypeB, + MfClassicActionDataDec); + + int32_t source_value = 0; + int32_t target_value = 0; + if(!mf_classic_block_to_value( + &instance->mfc_event_data.write_block_data.write_block, &source_value, NULL)) + break; + if(!mf_classic_block_to_value(&write_ctx->tag_block, &target_value, NULL)) break; + + MfClassicKeyType auth_key_type = MfClassicKeyTypeA; + MfClassicValueCommand value_cmd = MfClassicValueCommandIncrement; + int32_t diff = source_value - target_value; + if(diff > 0) { + if(key_a_inc_allowed) { + auth_key_type = MfClassicKeyTypeA; + value_cmd = MfClassicValueCommandIncrement; + } else if(key_b_inc_allowed) { + auth_key_type = MfClassicKeyTypeB; + value_cmd = MfClassicValueCommandIncrement; + } else { + FURI_LOG_D(TAG, "Unable to increment value block"); + break; + } + } else { + if(key_a_dec_allowed) { + auth_key_type = MfClassicKeyTypeA; + value_cmd = MfClassicValueCommandDecrement; + diff *= -1; + } else if(key_b_dec_allowed) { + auth_key_type = MfClassicKeyTypeB; + value_cmd = MfClassicValueCommandDecrement; + diff *= -1; + } else { + FURI_LOG_D(TAG, "Unable to decrement value block"); + break; + } + } + + MfClassicKey* key = (auth_key_type == MfClassicKeyTypeA) ? &write_ctx->sec_tr.key_a : + &write_ctx->sec_tr.key_b; + + MfClassicError error = + mf_classic_async_auth(instance, write_ctx->current_block, key, auth_key_type, NULL); + if(error != MfClassicErrorNone) break; + + error = mf_classic_async_value_cmd(instance, write_ctx->current_block, value_cmd, diff); + if(error != MfClassicErrorNone) break; + + error = mf_classic_async_value_transfer(instance, write_ctx->current_block); + if(error != MfClassicErrorNone) break; + + } while(false); + + mf_classic_async_halt(instance); + write_ctx->is_value_block = false; + write_ctx->current_block++; + instance->state = MfClassicPollerStateCheckWriteConditions; + + return command; +} + +NfcCommand mf_classic_poller_handler_request_read_sector(MfClassicPoller* instance) { + NfcCommand command = NfcCommandContinue; + + MfClassicPollerReadContext* sec_read_ctx = &instance->mode_ctx.read_ctx; + MfClassicPollerEventDataReadSectorRequest* sec_read = + &instance->mfc_event_data.read_sector_request_data; + instance->mfc_event.type = MfClassicPollerEventTypeRequestReadSector; + command = instance->callback(instance->general_event, instance->context); + + if(!sec_read->key_provided) { + instance->state = MfClassicPollerStateSuccess; + } else { + sec_read_ctx->current_sector = sec_read->sector_num; + sec_read_ctx->key = sec_read->key; + sec_read_ctx->key_type = sec_read->key_type; + sec_read_ctx->current_block = + mf_classic_get_first_block_num_of_sector(sec_read->sector_num); + sec_read_ctx->auth_passed = false; + instance->state = MfClassicPollerStateReadSectorBlocks; + } + + return command; +} + +NfcCommand mf_classic_poller_handler_request_read_sector_blocks(MfClassicPoller* instance) { + NfcCommand command = NfcCommandContinue; + + MfClassicPollerReadContext* sec_read_ctx = &instance->mode_ctx.read_ctx; + + do { + MfClassicError error = MfClassicErrorNone; + + if(!sec_read_ctx->auth_passed) { + uint64_t key = nfc_util_bytes2num(sec_read_ctx->key.data, sizeof(MfClassicKey)); + FURI_LOG_D( + TAG, + "Auth to block %d with key %c: %06llx", + sec_read_ctx->current_block, + sec_read_ctx->key_type == MfClassicKeyTypeA ? 'A' : 'B', + key); + error = mf_classic_async_auth( + instance, + sec_read_ctx->current_block, + &sec_read_ctx->key, + sec_read_ctx->key_type, + NULL); + if(error != MfClassicErrorNone) break; + + sec_read_ctx->auth_passed = true; + if(!mf_classic_is_key_found( + instance->data, sec_read_ctx->current_sector, sec_read_ctx->key_type)) { + mf_classic_set_key_found( + instance->data, sec_read_ctx->current_sector, sec_read_ctx->key_type, key); + } + } + if(mf_classic_is_block_read(instance->data, sec_read_ctx->current_block)) break; + + FURI_LOG_D(TAG, "Reading block %d", sec_read_ctx->current_block); + MfClassicBlock read_block = {}; + error = mf_classic_async_read_block(instance, sec_read_ctx->current_block, &read_block); + if(error == MfClassicErrorNone) { + mf_classic_set_block_read(instance->data, sec_read_ctx->current_block, &read_block); + if(sec_read_ctx->key_type == MfClassicKeyTypeA) { + mf_classic_poller_check_key_b_is_readable( + instance, sec_read_ctx->current_block, &read_block); + } + } else { + mf_classic_async_halt(instance); + sec_read_ctx->auth_passed = false; + } + } while(false); + + uint8_t sec_tr_num = mf_classic_get_sector_trailer_num_by_sector(sec_read_ctx->current_sector); + sec_read_ctx->current_block++; + if(sec_read_ctx->current_block > sec_tr_num) { + mf_classic_async_halt(instance); + instance->state = MfClassicPollerStateRequestReadSector; + } + + return command; +} + +NfcCommand mf_classic_poller_handler_request_key(MfClassicPoller* instance) { + NfcCommand command = NfcCommandContinue; + MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; + + instance->mfc_event.type = MfClassicPollerEventTypeRequestKey; + command = instance->callback(instance->general_event, instance->context); + if(instance->mfc_event_data.key_request_data.key_provided) { + dict_attack_ctx->current_key = instance->mfc_event_data.key_request_data.key; + instance->state = MfClassicPollerStateAuthKeyA; + } else { + instance->state = MfClassicPollerStateNextSector; + } + + return command; +} + +NfcCommand mf_classic_poller_handler_auth_a(MfClassicPoller* instance) { + NfcCommand command = NfcCommandContinue; + MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; + + if(mf_classic_is_key_found( + instance->data, dict_attack_ctx->current_sector, MfClassicKeyTypeA)) { + instance->state = MfClassicPollerStateAuthKeyB; + } else { + uint8_t block = mf_classic_get_first_block_num_of_sector(dict_attack_ctx->current_sector); + uint64_t key = nfc_util_bytes2num(dict_attack_ctx->current_key.data, sizeof(MfClassicKey)); + FURI_LOG_D(TAG, "Auth to block %d with key A: %06llx", block, key); + + MfClassicError error = mf_classic_async_auth( + instance, block, &dict_attack_ctx->current_key, MfClassicKeyTypeA, NULL); + if(error == MfClassicErrorNone) { + FURI_LOG_I(TAG, "Key A found"); + mf_classic_set_key_found( + instance->data, dict_attack_ctx->current_sector, MfClassicKeyTypeA, key); + + command = mf_classic_poller_handle_data_update(instance); + dict_attack_ctx->current_key_type = MfClassicKeyTypeA; + dict_attack_ctx->current_block = block; + dict_attack_ctx->auth_passed = true; + instance->state = MfClassicPollerStateReadSector; + } else { + mf_classic_async_halt(instance); + instance->state = MfClassicPollerStateAuthKeyB; + } + } + + return command; +} + +NfcCommand mf_classic_poller_handler_auth_b(MfClassicPoller* instance) { + NfcCommand command = NfcCommandContinue; + MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; + + if(mf_classic_is_key_found( + instance->data, dict_attack_ctx->current_sector, MfClassicKeyTypeB)) { + if(mf_classic_is_key_found( + instance->data, dict_attack_ctx->current_sector, MfClassicKeyTypeA)) { + instance->state = MfClassicPollerStateNextSector; + } else { + instance->state = MfClassicPollerStateRequestKey; + } + } else { + uint8_t block = mf_classic_get_first_block_num_of_sector(dict_attack_ctx->current_sector); + uint64_t key = nfc_util_bytes2num(dict_attack_ctx->current_key.data, sizeof(MfClassicKey)); + FURI_LOG_D(TAG, "Auth to block %d with key B: %06llx", block, key); + + MfClassicError error = mf_classic_async_auth( + instance, block, &dict_attack_ctx->current_key, MfClassicKeyTypeB, NULL); + if(error == MfClassicErrorNone) { + FURI_LOG_I(TAG, "Key B found"); + mf_classic_set_key_found( + instance->data, dict_attack_ctx->current_sector, MfClassicKeyTypeB, key); + + command = mf_classic_poller_handle_data_update(instance); + dict_attack_ctx->current_key_type = MfClassicKeyTypeB; + dict_attack_ctx->current_block = block; + + dict_attack_ctx->auth_passed = true; + instance->state = MfClassicPollerStateReadSector; + } else { + mf_classic_async_halt(instance); + instance->state = MfClassicPollerStateRequestKey; + } + } + + return command; +} + +NfcCommand mf_classic_poller_handler_next_sector(MfClassicPoller* instance) { + NfcCommand command = NfcCommandContinue; + MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; + + dict_attack_ctx->current_sector++; + if(dict_attack_ctx->current_sector == instance->sectors_total) { + instance->state = MfClassicPollerStateSuccess; + } else { + instance->mfc_event.type = MfClassicPollerEventTypeNextSector; + instance->mfc_event_data.next_sector_data.current_sector = dict_attack_ctx->current_sector; + command = instance->callback(instance->general_event, instance->context); + instance->state = MfClassicPollerStateRequestKey; + } + + return command; +} + +NfcCommand mf_classic_poller_handler_read_sector(MfClassicPoller* instance) { + NfcCommand command = NfcCommandContinue; + MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; + + MfClassicError error = MfClassicErrorNone; + uint8_t block_num = dict_attack_ctx->current_block; + MfClassicBlock block = {}; + + do { + if(mf_classic_is_block_read(instance->data, block_num)) break; + + if(!dict_attack_ctx->auth_passed) { + error = mf_classic_async_auth( + instance, + block_num, + &dict_attack_ctx->current_key, + dict_attack_ctx->current_key_type, + NULL); + if(error != MfClassicErrorNone) { + instance->state = MfClassicPollerStateNextSector; + FURI_LOG_W(TAG, "Failed to re-auth. Go to next sector"); + break; + } + } + + FURI_LOG_D(TAG, "Reading block %d", block_num); + error = mf_classic_async_read_block(instance, block_num, &block); + + if(error != MfClassicErrorNone) { + mf_classic_async_halt(instance); + dict_attack_ctx->auth_passed = false; + FURI_LOG_D(TAG, "Failed to read block %d", block_num); + } else { + mf_classic_set_block_read(instance->data, block_num, &block); + if(dict_attack_ctx->current_key_type == MfClassicKeyTypeA) { + mf_classic_poller_check_key_b_is_readable(instance, block_num, &block); + } + } + } while(false); + + uint8_t sec_tr_block_num = + mf_classic_get_sector_trailer_num_by_sector(dict_attack_ctx->current_sector); + dict_attack_ctx->current_block++; + if(dict_attack_ctx->current_block > sec_tr_block_num) { + mf_classic_poller_handle_data_update(instance); + + mf_classic_async_halt(instance); + dict_attack_ctx->auth_passed = false; + + if(dict_attack_ctx->current_sector == instance->sectors_total) { + instance->state = MfClassicPollerStateNextSector; + } else { + dict_attack_ctx->reuse_key_sector = dict_attack_ctx->current_sector; + instance->mfc_event.type = MfClassicPollerEventTypeKeyAttackStart; + instance->mfc_event_data.key_attack_data.current_sector = + dict_attack_ctx->reuse_key_sector; + command = instance->callback(instance->general_event, instance->context); + instance->state = MfClassicPollerStateKeyReuseStart; + } + } + + return command; +} + +NfcCommand mf_classic_poller_handler_key_reuse_start(MfClassicPoller* instance) { + NfcCommand command = NfcCommandContinue; + MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; + + if(dict_attack_ctx->current_key_type == MfClassicKeyTypeA) { + dict_attack_ctx->current_key_type = MfClassicKeyTypeB; + instance->state = MfClassicPollerStateKeyReuseAuthKeyB; + } else { + dict_attack_ctx->reuse_key_sector++; + if(dict_attack_ctx->reuse_key_sector == instance->sectors_total) { + instance->mfc_event.type = MfClassicPollerEventTypeKeyAttackStop; + command = instance->callback(instance->general_event, instance->context); + instance->state = MfClassicPollerStateRequestKey; + } else { + instance->mfc_event.type = MfClassicPollerEventTypeKeyAttackStart; + instance->mfc_event_data.key_attack_data.current_sector = + dict_attack_ctx->reuse_key_sector; + command = instance->callback(instance->general_event, instance->context); + + dict_attack_ctx->current_key_type = MfClassicKeyTypeA; + instance->state = MfClassicPollerStateKeyReuseAuthKeyA; + } + } + + return command; +} + +NfcCommand mf_classic_poller_handler_key_reuse_auth_key_a(MfClassicPoller* instance) { + NfcCommand command = NfcCommandContinue; + MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; + + if(mf_classic_is_key_found( + instance->data, dict_attack_ctx->reuse_key_sector, MfClassicKeyTypeA)) { + instance->state = MfClassicPollerStateKeyReuseStart; + } else { + uint8_t block = + mf_classic_get_first_block_num_of_sector(dict_attack_ctx->reuse_key_sector); + uint64_t key = nfc_util_bytes2num(dict_attack_ctx->current_key.data, sizeof(MfClassicKey)); + FURI_LOG_D(TAG, "Key attack auth to block %d with key A: %06llx", block, key); + + MfClassicError error = mf_classic_async_auth( + instance, block, &dict_attack_ctx->current_key, MfClassicKeyTypeA, NULL); + if(error == MfClassicErrorNone) { + FURI_LOG_I(TAG, "Key A found"); + mf_classic_set_key_found( + instance->data, dict_attack_ctx->reuse_key_sector, MfClassicKeyTypeA, key); + + command = mf_classic_poller_handle_data_update(instance); + dict_attack_ctx->current_key_type = MfClassicKeyTypeA; + dict_attack_ctx->current_block = block; + dict_attack_ctx->auth_passed = true; + instance->state = MfClassicPollerStateKeyReuseReadSector; + } else { + mf_classic_async_halt(instance); + dict_attack_ctx->auth_passed = false; + instance->state = MfClassicPollerStateKeyReuseStart; + } + } + + return command; +} + +NfcCommand mf_classic_poller_handler_key_reuse_auth_key_b(MfClassicPoller* instance) { + NfcCommand command = NfcCommandContinue; + MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; + + if(mf_classic_is_key_found( + instance->data, dict_attack_ctx->reuse_key_sector, MfClassicKeyTypeB)) { + instance->state = MfClassicPollerStateKeyReuseStart; + } else { + uint8_t block = + mf_classic_get_first_block_num_of_sector(dict_attack_ctx->reuse_key_sector); + uint64_t key = nfc_util_bytes2num(dict_attack_ctx->current_key.data, sizeof(MfClassicKey)); + FURI_LOG_D(TAG, "Key attack auth to block %d with key B: %06llx", block, key); + + MfClassicError error = mf_classic_async_auth( + instance, block, &dict_attack_ctx->current_key, MfClassicKeyTypeB, NULL); + if(error == MfClassicErrorNone) { + FURI_LOG_I(TAG, "Key B found"); + mf_classic_set_key_found( + instance->data, dict_attack_ctx->reuse_key_sector, MfClassicKeyTypeB, key); + + command = mf_classic_poller_handle_data_update(instance); + dict_attack_ctx->current_key_type = MfClassicKeyTypeB; + dict_attack_ctx->current_block = block; + + dict_attack_ctx->auth_passed = true; + instance->state = MfClassicPollerStateKeyReuseReadSector; + } else { + mf_classic_async_halt(instance); + dict_attack_ctx->auth_passed = false; + instance->state = MfClassicPollerStateKeyReuseStart; + } + } + + return command; +} + +NfcCommand mf_classic_poller_handler_key_reuse_read_sector(MfClassicPoller* instance) { + NfcCommand command = NfcCommandContinue; + MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; + + MfClassicError error = MfClassicErrorNone; + uint8_t block_num = dict_attack_ctx->current_block; + MfClassicBlock block = {}; + + do { + if(mf_classic_is_block_read(instance->data, block_num)) break; + + if(!dict_attack_ctx->auth_passed) { + error = mf_classic_async_auth( + instance, + block_num, + &dict_attack_ctx->current_key, + dict_attack_ctx->current_key_type, + NULL); + if(error != MfClassicErrorNone) { + instance->state = MfClassicPollerStateKeyReuseStart; + break; + } + } + + FURI_LOG_D(TAG, "Reading block %d", block_num); + error = mf_classic_async_read_block(instance, block_num, &block); + + if(error != MfClassicErrorNone) { + mf_classic_async_halt(instance); + dict_attack_ctx->auth_passed = false; + FURI_LOG_D(TAG, "Failed to read block %d", block_num); + } else { + mf_classic_set_block_read(instance->data, block_num, &block); + if(dict_attack_ctx->current_key_type == MfClassicKeyTypeA) { + mf_classic_poller_check_key_b_is_readable(instance, block_num, &block); + } + } + } while(false); + + uint16_t sec_tr_block_num = + mf_classic_get_sector_trailer_num_by_sector(dict_attack_ctx->reuse_key_sector); + dict_attack_ctx->current_block++; + if(dict_attack_ctx->current_block > sec_tr_block_num) { + mf_classic_async_halt(instance); + dict_attack_ctx->auth_passed = false; + + mf_classic_poller_handle_data_update(instance); + instance->state = MfClassicPollerStateKeyReuseStart; + } + + return command; +} + +NfcCommand mf_classic_poller_handler_success(MfClassicPoller* instance) { + NfcCommand command = NfcCommandContinue; + instance->mfc_event.type = MfClassicPollerEventTypeSuccess; + command = instance->callback(instance->general_event, instance->context); + + return command; +} + +NfcCommand mf_classic_poller_handler_fail(MfClassicPoller* instance) { + NfcCommand command = NfcCommandContinue; + instance->mfc_event.type = MfClassicPollerEventTypeFail; + command = instance->callback(instance->general_event, instance->context); + instance->state = MfClassicPollerStateDetectType; + + return command; +} + +static const MfClassicPollerReadHandler + mf_classic_poller_dict_attack_handler[MfClassicPollerStateNum] = { + [MfClassicPollerStateDetectType] = mf_classic_poller_handler_detect_type, + [MfClassicPollerStateStart] = mf_classic_poller_handler_start, + [MfClassicPollerStateRequestSectorTrailer] = + mf_classic_poller_handler_request_sector_trailer, + [MfClassicPollerStateCheckWriteConditions] = mf_classic_handler_check_write_conditions, + [MfClassicPollerStateReadBlock] = mf_classic_poller_handler_read_block, + [MfClassicPollerStateWriteBlock] = mf_classic_poller_handler_write_block, + [MfClassicPollerStateWriteValueBlock] = mf_classic_poller_handler_write_value_block, + [MfClassicPollerStateNextSector] = mf_classic_poller_handler_next_sector, + [MfClassicPollerStateRequestKey] = mf_classic_poller_handler_request_key, + [MfClassicPollerStateRequestReadSector] = mf_classic_poller_handler_request_read_sector, + [MfClassicPollerStateReadSectorBlocks] = + mf_classic_poller_handler_request_read_sector_blocks, + [MfClassicPollerStateAuthKeyA] = mf_classic_poller_handler_auth_a, + [MfClassicPollerStateAuthKeyB] = mf_classic_poller_handler_auth_b, + [MfClassicPollerStateReadSector] = mf_classic_poller_handler_read_sector, + [MfClassicPollerStateKeyReuseStart] = mf_classic_poller_handler_key_reuse_start, + [MfClassicPollerStateKeyReuseAuthKeyA] = mf_classic_poller_handler_key_reuse_auth_key_a, + [MfClassicPollerStateKeyReuseAuthKeyB] = mf_classic_poller_handler_key_reuse_auth_key_b, + [MfClassicPollerStateKeyReuseReadSector] = mf_classic_poller_handler_key_reuse_read_sector, + [MfClassicPollerStateSuccess] = mf_classic_poller_handler_success, + [MfClassicPollerStateFail] = mf_classic_poller_handler_fail, +}; + +NfcCommand mf_classic_poller_run(NfcGenericEvent event, void* context) { + furi_assert(event.event_data); + furi_assert(event.protocol == NfcProtocolIso14443_3a); + furi_assert(context); + + MfClassicPoller* instance = context; + Iso14443_3aPollerEvent* iso14443_3a_event = event.event_data; + NfcCommand command = NfcCommandContinue; + + if(iso14443_3a_event->type == Iso14443_3aPollerEventTypeReady) { + if(instance->card_state == MfClassicCardStateLost) { + instance->card_state = MfClassicCardStateDetected; + instance->mfc_event.type = MfClassicPollerEventTypeCardDetected; + instance->callback(instance->general_event, instance->context); + } + command = mf_classic_poller_dict_attack_handler[instance->state](instance); + } else if(iso14443_3a_event->type == Iso14443_3aPollerEventTypeError) { + if(instance->card_state == MfClassicCardStateDetected) { + instance->card_state = MfClassicCardStateLost; + instance->mfc_event.type = MfClassicPollerEventTypeCardLost; + command = instance->callback(instance->general_event, instance->context); + } + } + + return command; +} + +bool mf_classic_poller_detect(NfcGenericEvent event, void* context) { + furi_assert(event.event_data); + furi_assert(event.protocol == NfcProtocolIso14443_3a); + furi_assert(context); + + Iso14443_3aPoller* iso3_poller = event.instance; + Iso14443_3aPollerEvent* iso14443_3a_event = event.event_data; + bool detected = false; + const uint8_t auth_cmd[] = {MF_CLASSIC_CMD_AUTH_KEY_A, 0}; + BitBuffer* tx_buffer = bit_buffer_alloc(COUNT_OF(auth_cmd)); + bit_buffer_copy_bytes(tx_buffer, auth_cmd, COUNT_OF(auth_cmd)); + BitBuffer* rx_buffer = bit_buffer_alloc(sizeof(MfClassicNt)); + + if(iso14443_3a_event->type == Iso14443_3aPollerEventTypeReady) { + Iso14443_3aError error = iso14443_3a_poller_send_standard_frame( + iso3_poller, tx_buffer, rx_buffer, MF_CLASSIC_FWT_FC); + if(error == Iso14443_3aErrorWrongCrc) { + if(bit_buffer_get_size_bytes(rx_buffer) == sizeof(MfClassicNt)) { + detected = true; + } + } + } + + bit_buffer_free(tx_buffer); + bit_buffer_free(rx_buffer); + + return detected; +} + +void mf_classic_poller_set_callback( + MfClassicPoller* instance, + NfcGenericCallback callback, + void* context) { + furi_assert(instance); + furi_assert(callback); + + instance->callback = callback; + instance->context = context; +} + +const MfClassicData* mf_classic_poller_get_data(const MfClassicPoller* instance) { + furi_assert(instance); + furi_assert(instance->data); + + return instance->data; +} + +const NfcPollerBase mf_classic_poller = { + .alloc = (NfcPollerAlloc)mf_classic_poller_alloc, + .free = (NfcPollerFree)mf_classic_poller_free, + .set_callback = (NfcPollerSetCallback)mf_classic_poller_set_callback, + .run = (NfcPollerRun)mf_classic_poller_run, + .detect = (NfcPollerDetect)mf_classic_poller_detect, + .get_data = (NfcPollerGetData)mf_classic_poller_get_data, +}; diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.h b/lib/nfc/protocols/mf_classic/mf_classic_poller.h new file mode 100644 index 00000000000..da1f3c3dce5 --- /dev/null +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.h @@ -0,0 +1,109 @@ +#pragma once + +#include "mf_classic.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct MfClassicPoller MfClassicPoller; + +typedef enum { + // Start event + MfClassicPollerEventTypeRequestMode, + + // Read with key cache events + MfClassicPollerEventTypeRequestReadSector, + + // Write events + MfClassicPollerEventTypeRequestSectorTrailer, + MfClassicPollerEventTypeRequestWriteBlock, + + // Dictionary attack events + MfClassicPollerEventTypeRequestKey, + MfClassicPollerEventTypeNextSector, + MfClassicPollerEventTypeDataUpdate, + MfClassicPollerEventTypeFoundKeyA, + MfClassicPollerEventTypeFoundKeyB, + MfClassicPollerEventTypeCardNotDetected, + MfClassicPollerEventTypeKeyAttackStart, + MfClassicPollerEventTypeKeyAttackStop, + MfClassicPollerEventTypeKeyAttackNextSector, + + // Common events + MfClassicPollerEventTypeCardDetected, + MfClassicPollerEventTypeCardLost, + MfClassicPollerEventTypeSuccess, + MfClassicPollerEventTypeFail, +} MfClassicPollerEventType; + +typedef enum { + MfClassicPollerModeRead, + MfClassicPollerModeWrite, + MfClassicPollerModeDictAttack, +} MfClassicPollerMode; + +typedef struct { + MfClassicPollerMode mode; + const MfClassicData* data; +} MfClassicPollerEventDataRequestMode; + +typedef struct { + uint8_t current_sector; +} MfClassicPollerEventDataDictAttackNextSector; + +typedef struct { + uint8_t sectors_read; + uint8_t keys_found; + uint8_t current_sector; +} MfClassicPollerEventDataUpdate; + +typedef struct { + MfClassicKey key; + bool key_provided; +} MfClassicPollerEventDataKeyRequest; + +typedef struct { + uint8_t sector_num; + MfClassicKey key; + MfClassicKeyType key_type; + bool key_provided; +} MfClassicPollerEventDataReadSectorRequest; + +typedef struct { + uint8_t sector_num; + MfClassicBlock sector_trailer; + bool sector_trailer_provided; +} MfClassicPollerEventDataSectorTrailerRequest; + +typedef struct { + uint8_t block_num; + MfClassicBlock write_block; + bool write_block_provided; +} MfClassicPollerEventDataWriteBlockRequest; + +typedef struct { + uint8_t current_sector; +} MfClassicPollerEventKeyAttackData; + +typedef union { + MfClassicError error; + MfClassicPollerEventDataRequestMode poller_mode; + MfClassicPollerEventDataDictAttackNextSector next_sector_data; + MfClassicPollerEventDataKeyRequest key_request_data; + MfClassicPollerEventDataUpdate data_update; + MfClassicPollerEventDataReadSectorRequest read_sector_request_data; + MfClassicPollerEventKeyAttackData key_attack_data; + MfClassicPollerEventDataSectorTrailerRequest sec_tr_data; + MfClassicPollerEventDataWriteBlockRequest write_block_data; +} MfClassicPollerEventData; + +typedef struct { + MfClassicPollerEventType type; + MfClassicPollerEventData* data; +} MfClassicPollerEvent; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller_defs.h b/lib/nfc/protocols/mf_classic/mf_classic_poller_defs.h new file mode 100644 index 00000000000..d0c0b18fcfc --- /dev/null +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_defs.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern const NfcPollerBase mf_classic_poller; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c new file mode 100644 index 00000000000..7eab4fe3b5c --- /dev/null +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c @@ -0,0 +1,386 @@ +#include "mf_classic_poller_i.h" + +#include +#include + +#include + +#define TAG "MfCLassicPoller" + +MfClassicError mf_classic_process_error(Iso14443_3aError error) { + MfClassicError ret = MfClassicErrorNone; + + switch(error) { + case Iso14443_3aErrorNone: + ret = MfClassicErrorNone; + break; + case Iso14443_3aErrorNotPresent: + ret = MfClassicErrorNotPresent; + break; + case Iso14443_3aErrorColResFailed: + case Iso14443_3aErrorCommunication: + case Iso14443_3aErrorWrongCrc: + ret = MfClassicErrorProtocol; + break; + case Iso14443_3aErrorTimeout: + ret = MfClassicErrorTimeout; + break; + default: + ret = MfClassicErrorProtocol; + break; + } + + return ret; +} + +MfClassicError mf_classic_async_get_nt( + MfClassicPoller* instance, + uint8_t block_num, + MfClassicKeyType key_type, + MfClassicNt* nt) { + MfClassicError ret = MfClassicErrorNone; + Iso14443_3aError error = Iso14443_3aErrorNone; + + do { + uint8_t auth_type = (key_type == MfClassicKeyTypeB) ? MF_CLASSIC_CMD_AUTH_KEY_B : + MF_CLASSIC_CMD_AUTH_KEY_A; + uint8_t auth_cmd[2] = {auth_type, block_num}; + bit_buffer_copy_bytes(instance->tx_plain_buffer, auth_cmd, sizeof(auth_cmd)); + + error = iso14443_3a_poller_send_standard_frame( + instance->iso14443_3a_poller, + instance->tx_plain_buffer, + instance->rx_plain_buffer, + MF_CLASSIC_FWT_FC); + if(error != Iso14443_3aErrorWrongCrc) { + ret = mf_classic_process_error(error); + break; + } + if(bit_buffer_get_size_bytes(instance->rx_plain_buffer) != sizeof(MfClassicNt)) { + ret = MfClassicErrorProtocol; + break; + } + + if(nt) { + bit_buffer_write_bytes(instance->rx_plain_buffer, nt->data, sizeof(MfClassicNt)); + } + } while(false); + + return ret; +} + +MfClassicError mf_classic_async_auth( + MfClassicPoller* instance, + uint8_t block_num, + MfClassicKey* key, + MfClassicKeyType key_type, + MfClassicAuthContext* data) { + MfClassicError ret = MfClassicErrorNone; + Iso14443_3aError error = Iso14443_3aErrorNone; + + do { + iso14443_3a_copy( + instance->data->iso14443_3a_data, + iso14443_3a_poller_get_data(instance->iso14443_3a_poller)); + + MfClassicNt nt = {}; + ret = mf_classic_async_get_nt(instance, block_num, key_type, &nt); + if(ret != MfClassicErrorNone) break; + if(data) { + data->nt = nt; + } + + uint32_t cuid = iso14443_3a_get_cuid(instance->data->iso14443_3a_data); + uint64_t key_num = nfc_util_bytes2num(key->data, sizeof(MfClassicKey)); + MfClassicNr nr = {}; + furi_hal_random_fill_buf(nr.data, sizeof(MfClassicNr)); + + crypto1_encrypt_reader_nonce( + instance->crypto, key_num, cuid, nt.data, nr.data, instance->tx_encrypted_buffer); + error = iso14443_3a_poller_txrx_custom_parity( + instance->iso14443_3a_poller, + instance->tx_encrypted_buffer, + instance->rx_encrypted_buffer, + MF_CLASSIC_FWT_FC); + + if(error != Iso14443_3aErrorNone) { + ret = mf_classic_process_error(error); + break; + } + if(bit_buffer_get_size_bytes(instance->rx_encrypted_buffer) != 4) { + ret = MfClassicErrorAuth; + } + + crypto1_word(instance->crypto, 0, 0); + instance->auth_state = MfClassicAuthStatePassed; + + if(data) { + data->nr = nr; + const uint8_t* nr_ar = bit_buffer_get_data(instance->tx_encrypted_buffer); + memcpy(data->ar.data, &nr_ar[4], sizeof(MfClassicAr)); + bit_buffer_write_bytes( + instance->rx_encrypted_buffer, data->at.data, sizeof(MfClassicAt)); + } + } while(false); + + if(ret != MfClassicErrorNone) { + iso14443_3a_poller_halt(instance->iso14443_3a_poller); + } + + return ret; +} + +MfClassicError mf_classic_async_halt(MfClassicPoller* instance) { + MfClassicError ret = MfClassicErrorNone; + Iso14443_3aError error = Iso14443_3aErrorNone; + + do { + uint8_t halt_cmd[2] = {MF_CLASSIC_CMD_HALT_MSB, MF_CLASSIC_CMD_HALT_LSB}; + bit_buffer_copy_bytes(instance->tx_plain_buffer, halt_cmd, sizeof(halt_cmd)); + iso14443_crc_append(Iso14443CrcTypeA, instance->tx_plain_buffer); + + crypto1_encrypt( + instance->crypto, NULL, instance->tx_plain_buffer, instance->tx_encrypted_buffer); + + error = iso14443_3a_poller_txrx_custom_parity( + instance->iso14443_3a_poller, + instance->tx_encrypted_buffer, + instance->rx_encrypted_buffer, + MF_CLASSIC_FWT_FC); + if(error != Iso14443_3aErrorTimeout) { + ret = mf_classic_process_error(error); + break; + } + instance->auth_state = MfClassicAuthStateIdle; + instance->iso14443_3a_poller->state = Iso14443_3aPollerStateIdle; + } while(false); + + return ret; +} + +MfClassicError mf_classic_async_read_block( + MfClassicPoller* instance, + uint8_t block_num, + MfClassicBlock* data) { + MfClassicError ret = MfClassicErrorNone; + Iso14443_3aError error = Iso14443_3aErrorNone; + + do { + uint8_t read_block_cmd[2] = {MF_CLASSIC_CMD_READ_BLOCK, block_num}; + bit_buffer_copy_bytes(instance->tx_plain_buffer, read_block_cmd, sizeof(read_block_cmd)); + iso14443_crc_append(Iso14443CrcTypeA, instance->tx_plain_buffer); + + crypto1_encrypt( + instance->crypto, NULL, instance->tx_plain_buffer, instance->tx_encrypted_buffer); + + error = iso14443_3a_poller_txrx_custom_parity( + instance->iso14443_3a_poller, + instance->tx_encrypted_buffer, + instance->rx_encrypted_buffer, + MF_CLASSIC_FWT_FC); + if(error != Iso14443_3aErrorNone) { + ret = mf_classic_process_error(error); + break; + } + if(bit_buffer_get_size_bytes(instance->rx_encrypted_buffer) != + (sizeof(MfClassicBlock) + 2)) { + ret = MfClassicErrorProtocol; + break; + } + + crypto1_decrypt( + instance->crypto, instance->rx_encrypted_buffer, instance->rx_plain_buffer); + + if(!iso14443_crc_check(Iso14443CrcTypeA, instance->rx_plain_buffer)) { + FURI_LOG_D(TAG, "CRC error"); + ret = MfClassicErrorProtocol; + break; + } + + iso14443_crc_trim(instance->rx_plain_buffer); + bit_buffer_write_bytes(instance->rx_plain_buffer, data->data, sizeof(MfClassicBlock)); + } while(false); + + return ret; +} + +MfClassicError mf_classic_async_write_block( + MfClassicPoller* instance, + uint8_t block_num, + MfClassicBlock* data) { + MfClassicError ret = MfClassicErrorNone; + Iso14443_3aError error = Iso14443_3aErrorNone; + + do { + uint8_t write_block_cmd[2] = {MF_CLASSIC_CMD_WRITE_BLOCK, block_num}; + bit_buffer_copy_bytes(instance->tx_plain_buffer, write_block_cmd, sizeof(write_block_cmd)); + iso14443_crc_append(Iso14443CrcTypeA, instance->tx_plain_buffer); + + crypto1_encrypt( + instance->crypto, NULL, instance->tx_plain_buffer, instance->tx_encrypted_buffer); + + error = iso14443_3a_poller_txrx_custom_parity( + instance->iso14443_3a_poller, + instance->tx_encrypted_buffer, + instance->rx_encrypted_buffer, + MF_CLASSIC_FWT_FC); + if(error != Iso14443_3aErrorNone) { + ret = mf_classic_process_error(error); + break; + } + if(bit_buffer_get_size(instance->rx_encrypted_buffer) != 4) { + ret = MfClassicErrorProtocol; + break; + } + + crypto1_decrypt( + instance->crypto, instance->rx_encrypted_buffer, instance->rx_plain_buffer); + + if(bit_buffer_get_byte(instance->rx_plain_buffer, 0) != MF_CLASSIC_CMD_ACK) { + FURI_LOG_D(TAG, "Not ACK received"); + ret = MfClassicErrorProtocol; + break; + } + + bit_buffer_copy_bytes(instance->tx_plain_buffer, data->data, sizeof(MfClassicBlock)); + iso14443_crc_append(Iso14443CrcTypeA, instance->tx_plain_buffer); + + crypto1_encrypt( + instance->crypto, NULL, instance->tx_plain_buffer, instance->tx_encrypted_buffer); + + error = iso14443_3a_poller_txrx_custom_parity( + instance->iso14443_3a_poller, + instance->tx_encrypted_buffer, + instance->rx_encrypted_buffer, + MF_CLASSIC_FWT_FC); + if(error != Iso14443_3aErrorNone) { + ret = mf_classic_process_error(error); + break; + } + if(bit_buffer_get_size(instance->rx_encrypted_buffer) != 4) { + ret = MfClassicErrorProtocol; + break; + } + + crypto1_decrypt( + instance->crypto, instance->rx_encrypted_buffer, instance->rx_plain_buffer); + + if(bit_buffer_get_byte(instance->rx_plain_buffer, 0) != MF_CLASSIC_CMD_ACK) { + FURI_LOG_D(TAG, "Not ACK received"); + ret = MfClassicErrorProtocol; + break; + } + } while(false); + + return ret; +} + +MfClassicError mf_classic_async_value_cmd( + MfClassicPoller* instance, + uint8_t block_num, + MfClassicValueCommand cmd, + int32_t data) { + MfClassicError ret = MfClassicErrorNone; + Iso14443_3aError error = Iso14443_3aErrorNone; + + do { + uint8_t cmd_value = 0; + if(cmd == MfClassicValueCommandDecrement) { + cmd_value = MF_CLASSIC_CMD_VALUE_DEC; + } else if(cmd == MfClassicValueCommandIncrement) { + cmd_value = MF_CLASSIC_CMD_VALUE_INC; + } else { + cmd_value = MF_CLASSIC_CMD_VALUE_RESTORE; + } + uint8_t value_cmd[2] = {cmd_value, block_num}; + bit_buffer_copy_bytes(instance->tx_plain_buffer, value_cmd, sizeof(value_cmd)); + iso14443_crc_append(Iso14443CrcTypeA, instance->tx_plain_buffer); + + crypto1_encrypt( + instance->crypto, NULL, instance->tx_plain_buffer, instance->tx_encrypted_buffer); + + error = iso14443_3a_poller_txrx_custom_parity( + instance->iso14443_3a_poller, + instance->tx_encrypted_buffer, + instance->rx_encrypted_buffer, + MF_CLASSIC_FWT_FC); + if(error != Iso14443_3aErrorNone) { + ret = mf_classic_process_error(error); + break; + } + if(bit_buffer_get_size(instance->rx_encrypted_buffer) != 4) { + ret = MfClassicErrorProtocol; + break; + } + + crypto1_decrypt( + instance->crypto, instance->rx_encrypted_buffer, instance->rx_plain_buffer); + + if(bit_buffer_get_byte(instance->rx_plain_buffer, 0) != MF_CLASSIC_CMD_ACK) { + FURI_LOG_D(TAG, "Not ACK received"); + ret = MfClassicErrorProtocol; + break; + } + + bit_buffer_copy_bytes(instance->tx_plain_buffer, (uint8_t*)&data, sizeof(data)); + iso14443_crc_append(Iso14443CrcTypeA, instance->tx_plain_buffer); + + crypto1_encrypt( + instance->crypto, NULL, instance->tx_plain_buffer, instance->tx_encrypted_buffer); + + error = iso14443_3a_poller_txrx_custom_parity( + instance->iso14443_3a_poller, + instance->tx_encrypted_buffer, + instance->rx_encrypted_buffer, + MF_CLASSIC_FWT_FC); + + // Command processed if tag doesn't respond + if(error != Iso14443_3aErrorTimeout) { + ret = MfClassicErrorProtocol; + break; + } + ret = MfClassicErrorNone; + } while(false); + + return ret; +} + +MfClassicError mf_classic_async_value_transfer(MfClassicPoller* instance, uint8_t block_num) { + MfClassicError ret = MfClassicErrorNone; + Iso14443_3aError error = Iso14443_3aErrorNone; + + do { + uint8_t transfer_cmd[2] = {MF_CLASSIC_CMD_VALUE_TRANSFER, block_num}; + bit_buffer_copy_bytes(instance->tx_plain_buffer, transfer_cmd, sizeof(transfer_cmd)); + iso14443_crc_append(Iso14443CrcTypeA, instance->tx_plain_buffer); + + crypto1_encrypt( + instance->crypto, NULL, instance->tx_plain_buffer, instance->tx_encrypted_buffer); + + error = iso14443_3a_poller_txrx_custom_parity( + instance->iso14443_3a_poller, + instance->tx_encrypted_buffer, + instance->rx_encrypted_buffer, + MF_CLASSIC_FWT_FC); + if(error != Iso14443_3aErrorNone) { + ret = mf_classic_process_error(error); + break; + } + if(bit_buffer_get_size(instance->rx_encrypted_buffer) != 4) { + ret = MfClassicErrorProtocol; + break; + } + + crypto1_decrypt( + instance->crypto, instance->rx_encrypted_buffer, instance->rx_plain_buffer); + + if(bit_buffer_get_byte(instance->rx_plain_buffer, 0) != MF_CLASSIC_CMD_ACK) { + FURI_LOG_D(TAG, "Not ACK received"); + ret = MfClassicErrorProtocol; + break; + } + + } while(false); + + return ret; +} diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h new file mode 100644 index 00000000000..c6f4ccf7f0e --- /dev/null +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h @@ -0,0 +1,205 @@ +#pragma once + +#include "mf_classic_poller.h" +#include +#include +#include "crypto1.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define MF_CLASSIC_FWT_FC (60000) + +typedef enum { + MfClassicAuthStateIdle, + MfClassicAuthStatePassed, +} MfClassicAuthState; + +typedef enum { + MfClassicCardStateDetected, + MfClassicCardStateLost, +} MfClassicCardState; + +typedef enum { + MfClassicPollerStateDetectType, + MfClassicPollerStateStart, + + // Write states + MfClassicPollerStateRequestSectorTrailer, + MfClassicPollerStateCheckWriteConditions, + MfClassicPollerStateReadBlock, + MfClassicPollerStateWriteBlock, + MfClassicPollerStateWriteValueBlock, + + // Read states + MfClassicPollerStateRequestReadSector, + MfClassicPollerStateReadSectorBlocks, + + // Dict attack states + MfClassicPollerStateNextSector, + MfClassicPollerStateRequestKey, + MfClassicPollerStateReadSector, + MfClassicPollerStateAuthKeyA, + MfClassicPollerStateAuthKeyB, + MfClassicPollerStateKeyReuseStart, + MfClassicPollerStateKeyReuseAuthKeyA, + MfClassicPollerStateKeyReuseAuthKeyB, + MfClassicPollerStateKeyReuseReadSector, + MfClassicPollerStateSuccess, + MfClassicPollerStateFail, + + MfClassicPollerStateNum, +} MfClassicPollerState; + +typedef struct { + uint8_t current_sector; + MfClassicSectorTrailer sec_tr; + uint16_t current_block; + bool is_value_block; + MfClassicKeyType key_type_read; + MfClassicKeyType key_type_write; + bool need_halt_before_write; + MfClassicBlock tag_block; +} MfClassicPollerWriteContext; + +typedef struct { + uint8_t current_sector; + MfClassicKey current_key; + MfClassicKeyType current_key_type; + bool auth_passed; + uint16_t current_block; + uint8_t reuse_key_sector; +} MfClassicPollerDictAttackContext; + +typedef struct { + uint8_t current_sector; + uint16_t current_block; + MfClassicKeyType key_type; + MfClassicKey key; + bool auth_passed; +} MfClassicPollerReadContext; + +typedef union { + MfClassicPollerWriteContext write_ctx; + MfClassicPollerDictAttackContext dict_attack_ctx; + MfClassicPollerReadContext read_ctx; + +} MfClassicPollerModeContext; + +struct MfClassicPoller { + Iso14443_3aPoller* iso14443_3a_poller; + + MfClassicPollerState state; + MfClassicAuthState auth_state; + MfClassicCardState card_state; + + MfClassicType current_type_check; + uint8_t sectors_total; + MfClassicPollerModeContext mode_ctx; + + Crypto1* crypto; + BitBuffer* tx_plain_buffer; + BitBuffer* tx_encrypted_buffer; + BitBuffer* rx_plain_buffer; + BitBuffer* rx_encrypted_buffer; + MfClassicData* data; + + NfcGenericEvent general_event; + MfClassicPollerEvent mfc_event; + MfClassicPollerEventData mfc_event_data; + NfcGenericCallback callback; + void* context; +}; + +typedef struct { + uint8_t block; + MfClassicKeyType key_type; + MfClassicNt nt; +} MfClassicCollectNtContext; + +typedef struct { + uint8_t block_num; + MfClassicKey key; + MfClassicKeyType key_type; + MfClassicBlock block; +} MfClassicReadBlockContext; + +typedef struct { + uint8_t block_num; + MfClassicKey key; + MfClassicKeyType key_type; + MfClassicBlock block; +} MfClassicWriteBlockContext; + +typedef struct { + uint8_t block_num; + MfClassicKey key; + MfClassicKeyType key_type; + int32_t value; +} MfClassicReadValueContext; + +typedef struct { + uint8_t block_num; + MfClassicKey key; + MfClassicKeyType key_type; + MfClassicValueCommand value_cmd; + int32_t data; + int32_t new_value; +} MfClassicChangeValueContext; + +typedef struct { + MfClassicDeviceKeys keys; + uint8_t current_sector; +} MfClassicReadContext; + +typedef union { + MfClassicCollectNtContext collect_nt_context; + MfClassicAuthContext auth_context; + MfClassicReadBlockContext read_block_context; + MfClassicWriteBlockContext write_block_context; + MfClassicReadValueContext read_value_context; + MfClassicChangeValueContext change_value_context; + MfClassicReadContext read_context; +} MfClassicPollerContextData; + +MfClassicError mf_classic_process_error(Iso14443_3aError error); + +MfClassicPoller* mf_classic_poller_alloc(Iso14443_3aPoller* iso14443_3a_poller); + +void mf_classic_poller_free(MfClassicPoller* instance); + +MfClassicError mf_classic_async_get_nt( + MfClassicPoller* instance, + uint8_t block_num, + MfClassicKeyType key_type, + MfClassicNt* nt); + +MfClassicError mf_classic_async_auth( + MfClassicPoller* instance, + uint8_t block_num, + MfClassicKey* key, + MfClassicKeyType key_type, + MfClassicAuthContext* data); + +MfClassicError mf_classic_async_halt(MfClassicPoller* instance); + +MfClassicError + mf_classic_async_read_block(MfClassicPoller* instance, uint8_t block_num, MfClassicBlock* data); + +MfClassicError mf_classic_async_write_block( + MfClassicPoller* instance, + uint8_t block_num, + MfClassicBlock* data); + +MfClassicError mf_classic_async_value_cmd( + MfClassicPoller* instance, + uint8_t block_num, + MfClassicValueCommand cmd, + int32_t data); + +MfClassicError mf_classic_async_value_transfer(MfClassicPoller* instance, uint8_t block_num); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller_sync_api.c b/lib/nfc/protocols/mf_classic/mf_classic_poller_sync_api.c new file mode 100644 index 00000000000..8b9fb69f166 --- /dev/null +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_sync_api.c @@ -0,0 +1,524 @@ +#include "mf_classic_poller_i.h" + +#include + +#include + +#define TAG "MfClassicPoller" + +#define MF_CLASSIC_POLLER_COMPLETE_EVENT (1UL << 0) + +typedef enum { + MfClassicPollerCmdTypeCollectNt, + MfClassicPollerCmdTypeAuth, + MfClassicPollerCmdTypeReadBlock, + MfClassicPollerCmdTypeWriteBlock, + MfClassicPollerCmdTypeReadValue, + MfClassicPollerCmdTypeChangeValue, + + MfClassicPollerCmdTypeNum, +} MfClassicPollerCmdType; + +typedef struct { + MfClassicPollerCmdType cmd_type; + FuriThreadId thread_id; + MfClassicError error; + MfClassicPollerContextData data; +} MfClassicPollerContext; + +typedef MfClassicError ( + *MfClassicPollerCmdHandler)(MfClassicPoller* poller, MfClassicPollerContextData* data); + +static MfClassicError mf_classic_poller_collect_nt_handler( + MfClassicPoller* poller, + MfClassicPollerContextData* data) { + return mf_classic_async_get_nt( + poller, + data->collect_nt_context.block, + data->collect_nt_context.key_type, + &data->collect_nt_context.nt); +} + +static MfClassicError + mf_classic_poller_auth_handler(MfClassicPoller* poller, MfClassicPollerContextData* data) { + return mf_classic_async_auth( + poller, + data->auth_context.block_num, + &data->auth_context.key, + data->auth_context.key_type, + &data->auth_context); +} + +static MfClassicError mf_classic_poller_read_block_handler( + MfClassicPoller* poller, + MfClassicPollerContextData* data) { + MfClassicError error = MfClassicErrorNone; + + do { + error = mf_classic_async_auth( + poller, + data->read_block_context.block_num, + &data->read_block_context.key, + data->read_block_context.key_type, + NULL); + if(error != MfClassicErrorNone) break; + + error = mf_classic_async_read_block( + poller, data->read_block_context.block_num, &data->read_block_context.block); + if(error != MfClassicErrorNone) break; + + error = mf_classic_async_halt(poller); + if(error != MfClassicErrorNone) break; + + } while(false); + + return error; +} + +static MfClassicError mf_classic_poller_write_block_handler( + MfClassicPoller* poller, + MfClassicPollerContextData* data) { + MfClassicError error = MfClassicErrorNone; + + do { + error = mf_classic_async_auth( + poller, + data->read_block_context.block_num, + &data->read_block_context.key, + data->read_block_context.key_type, + NULL); + if(error != MfClassicErrorNone) break; + + error = mf_classic_async_write_block( + poller, data->write_block_context.block_num, &data->write_block_context.block); + if(error != MfClassicErrorNone) break; + + error = mf_classic_async_halt(poller); + if(error != MfClassicErrorNone) break; + + } while(false); + + return error; +} + +static MfClassicError mf_classic_poller_read_value_handler( + MfClassicPoller* poller, + MfClassicPollerContextData* data) { + MfClassicError error = MfClassicErrorNone; + + do { + error = mf_classic_async_auth( + poller, + data->read_value_context.block_num, + &data->read_value_context.key, + data->read_value_context.key_type, + NULL); + if(error != MfClassicErrorNone) break; + + MfClassicBlock block = {}; + error = mf_classic_async_read_block(poller, data->read_value_context.block_num, &block); + if(error != MfClassicErrorNone) break; + + if(!mf_classic_block_to_value(&block, &data->read_value_context.value, NULL)) { + error = MfClassicErrorProtocol; + break; + } + + error = mf_classic_async_halt(poller); + if(error != MfClassicErrorNone) break; + + } while(false); + + return error; +} + +static MfClassicError mf_classic_poller_change_value_handler( + MfClassicPoller* poller, + MfClassicPollerContextData* data) { + MfClassicError error = MfClassicErrorNone; + + do { + error = mf_classic_async_auth( + poller, + data->change_value_context.block_num, + &data->change_value_context.key, + data->change_value_context.key_type, + NULL); + if(error != MfClassicErrorNone) break; + + error = mf_classic_async_value_cmd( + poller, + data->change_value_context.block_num, + data->change_value_context.value_cmd, + data->change_value_context.data); + if(error != MfClassicErrorNone) break; + + error = mf_classic_async_value_transfer(poller, data->change_value_context.block_num); + if(error != MfClassicErrorNone) break; + + MfClassicBlock block = {}; + error = mf_classic_async_read_block(poller, data->change_value_context.block_num, &block); + if(error != MfClassicErrorNone) break; + + error = mf_classic_async_halt(poller); + if(error != MfClassicErrorNone) break; + + if(!mf_classic_block_to_value(&block, &data->change_value_context.new_value, NULL)) { + error = MfClassicErrorProtocol; + break; + } + + } while(false); + + return error; +} + +static const MfClassicPollerCmdHandler mf_classic_poller_cmd_handlers[MfClassicPollerCmdTypeNum] = { + [MfClassicPollerCmdTypeCollectNt] = mf_classic_poller_collect_nt_handler, + [MfClassicPollerCmdTypeAuth] = mf_classic_poller_auth_handler, + [MfClassicPollerCmdTypeReadBlock] = mf_classic_poller_read_block_handler, + [MfClassicPollerCmdTypeWriteBlock] = mf_classic_poller_write_block_handler, + [MfClassicPollerCmdTypeReadValue] = mf_classic_poller_read_value_handler, + [MfClassicPollerCmdTypeChangeValue] = mf_classic_poller_change_value_handler, +}; + +static NfcCommand mf_classic_poller_cmd_callback(NfcGenericEvent event, void* context) { + furi_assert(event.instance); + furi_assert(event.protocol == NfcProtocolIso14443_3a); + furi_assert(event.event_data); + furi_assert(context); + + MfClassicPollerContext* poller_context = context; + Iso14443_3aPollerEvent* iso14443_3a_event = event.event_data; + Iso14443_3aPoller* iso14443_3a_poller = event.instance; + MfClassicPoller* mfc_poller = mf_classic_poller_alloc(iso14443_3a_poller); + + if(iso14443_3a_event->type == Iso14443_3aPollerEventTypeReady) { + poller_context->error = mf_classic_poller_cmd_handlers[poller_context->cmd_type]( + mfc_poller, &poller_context->data); + } else if(iso14443_3a_event->type == Iso14443_3aPollerEventTypeError) { + poller_context->error = mf_classic_process_error(iso14443_3a_event->data->error); + } + + furi_thread_flags_set(poller_context->thread_id, MF_CLASSIC_POLLER_COMPLETE_EVENT); + + mf_classic_poller_free(mfc_poller); + + return NfcCommandStop; +} + +static MfClassicError mf_classic_poller_cmd_execute(Nfc* nfc, MfClassicPollerContext* poller_ctx) { + furi_assert(poller_ctx->cmd_type < MfClassicPollerCmdTypeNum); + + poller_ctx->thread_id = furi_thread_get_current_id(); + + NfcPoller* poller = nfc_poller_alloc(nfc, NfcProtocolIso14443_3a); + nfc_poller_start(poller, mf_classic_poller_cmd_callback, poller_ctx); + furi_thread_flags_wait(MF_CLASSIC_POLLER_COMPLETE_EVENT, FuriFlagWaitAny, FuriWaitForever); + furi_thread_flags_clear(MF_CLASSIC_POLLER_COMPLETE_EVENT); + + nfc_poller_stop(poller); + nfc_poller_free(poller); + + return poller_ctx->error; +} + +MfClassicError mf_classic_poller_collect_nt( + Nfc* nfc, + uint8_t block_num, + MfClassicKeyType key_type, + MfClassicNt* nt) { + furi_assert(nfc); + + MfClassicPollerContext poller_context = { + .cmd_type = MfClassicPollerCmdTypeCollectNt, + .data.collect_nt_context.block = block_num, + .data.collect_nt_context.key_type = key_type, + }; + + MfClassicError error = mf_classic_poller_cmd_execute(nfc, &poller_context); + + if(error == MfClassicErrorNone) { + if(nt) { + *nt = poller_context.data.collect_nt_context.nt; + } + } + + return error; +} + +MfClassicError mf_classic_poller_auth( + Nfc* nfc, + uint8_t block_num, + MfClassicKey* key, + MfClassicKeyType key_type, + MfClassicAuthContext* data) { + furi_assert(nfc); + furi_assert(key); + + MfClassicPollerContext poller_context = { + .cmd_type = MfClassicPollerCmdTypeAuth, + .data.auth_context.block_num = block_num, + .data.auth_context.key = *key, + .data.auth_context.key_type = key_type, + }; + + MfClassicError error = mf_classic_poller_cmd_execute(nfc, &poller_context); + + if(error == MfClassicErrorNone) { + if(data) { + *data = poller_context.data.auth_context; + } + } + + return error; +} + +MfClassicError mf_classic_poller_read_block( + Nfc* nfc, + uint8_t block_num, + MfClassicKey* key, + MfClassicKeyType key_type, + MfClassicBlock* data) { + furi_assert(nfc); + furi_assert(key); + furi_assert(data); + + MfClassicPollerContext poller_context = { + .cmd_type = MfClassicPollerCmdTypeReadBlock, + .data.read_block_context.block_num = block_num, + .data.read_block_context.key = *key, + .data.read_block_context.key_type = key_type, + }; + + MfClassicError error = mf_classic_poller_cmd_execute(nfc, &poller_context); + + if(error == MfClassicErrorNone) { + *data = poller_context.data.read_block_context.block; + } + + return error; +} + +MfClassicError mf_classic_poller_write_block( + Nfc* nfc, + uint8_t block_num, + MfClassicKey* key, + MfClassicKeyType key_type, + MfClassicBlock* data) { + furi_assert(nfc); + furi_assert(key); + furi_assert(data); + + MfClassicPollerContext poller_context = { + .cmd_type = MfClassicPollerCmdTypeWriteBlock, + .data.write_block_context.block_num = block_num, + .data.write_block_context.key = *key, + .data.write_block_context.key_type = key_type, + .data.write_block_context.block = *data, + }; + + MfClassicError error = mf_classic_poller_cmd_execute(nfc, &poller_context); + + return error; +} + +MfClassicError mf_classic_poller_read_value( + Nfc* nfc, + uint8_t block_num, + MfClassicKey* key, + MfClassicKeyType key_type, + int32_t* value) { + furi_assert(nfc); + furi_assert(key); + furi_assert(value); + + MfClassicPollerContext poller_context = { + .cmd_type = MfClassicPollerCmdTypeReadValue, + .data.write_block_context.block_num = block_num, + .data.write_block_context.key = *key, + .data.write_block_context.key_type = key_type, + }; + + MfClassicError error = mf_classic_poller_cmd_execute(nfc, &poller_context); + + if(error == MfClassicErrorNone) { + *value = poller_context.data.read_value_context.value; + } + + return error; +} + +MfClassicError mf_classic_poller_change_value( + Nfc* nfc, + uint8_t block_num, + MfClassicKey* key, + MfClassicKeyType key_type, + int32_t data, + int32_t* new_value) { + furi_assert(nfc); + furi_assert(key); + furi_assert(new_value); + + MfClassicValueCommand command = MfClassicValueCommandRestore; + int32_t command_data = 0; + if(data > 0) { + command = MfClassicValueCommandIncrement; + command_data = data; + } else if(data < 0) { + command = MfClassicValueCommandDecrement; + command_data = -data; + } + + MfClassicPollerContext poller_context = { + .cmd_type = MfClassicPollerCmdTypeChangeValue, + .data.change_value_context.block_num = block_num, + .data.change_value_context.key = *key, + .data.change_value_context.key_type = key_type, + .data.change_value_context.value_cmd = command, + .data.change_value_context.data = command_data, + }; + + MfClassicError error = mf_classic_poller_cmd_execute(nfc, &poller_context); + + if(error == MfClassicErrorNone) { + *new_value = poller_context.data.change_value_context.new_value; + } + + return error; +} + +static bool mf_classic_poller_read_get_next_key( + MfClassicReadContext* read_ctx, + uint8_t* sector_num, + MfClassicKey* key, + MfClassicKeyType* key_type) { + bool next_key_found = false; + + for(uint8_t i = read_ctx->current_sector; i < MF_CLASSIC_TOTAL_SECTORS_MAX; i++) { + if(FURI_BIT(read_ctx->keys.key_a_mask, i)) { + FURI_BIT_CLEAR(read_ctx->keys.key_a_mask, i); + *key = read_ctx->keys.key_a[i]; + *key_type = MfClassicKeyTypeA; + *sector_num = i; + + next_key_found = true; + break; + } + if(FURI_BIT(read_ctx->keys.key_b_mask, i)) { + FURI_BIT_CLEAR(read_ctx->keys.key_b_mask, i); + *key = read_ctx->keys.key_b[i]; + *key_type = MfClassicKeyTypeB; + *sector_num = i; + + next_key_found = true; + read_ctx->current_sector = i; + break; + } + } + + return next_key_found; +} + +NfcCommand mf_classic_poller_read_callback(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.event_data); + furi_assert(event.protocol == NfcProtocolMfClassic); + + NfcCommand command = NfcCommandContinue; + MfClassicPollerContext* poller_context = context; + MfClassicPollerEvent* mfc_event = event.event_data; + + if(mfc_event->type == MfClassicPollerEventTypeCardLost) { + poller_context->error = MfClassicErrorNotPresent; + command = NfcCommandStop; + } else if(mfc_event->type == MfClassicPollerEventTypeRequestMode) { + mfc_event->data->poller_mode.mode = MfClassicPollerModeRead; + } else if(mfc_event->type == MfClassicPollerEventTypeRequestReadSector) { + MfClassicPollerEventDataReadSectorRequest* req_data = + &mfc_event->data->read_sector_request_data; + MfClassicKey key = {}; + MfClassicKeyType key_type = MfClassicKeyTypeA; + uint8_t sector_num = 0; + if(mf_classic_poller_read_get_next_key( + &poller_context->data.read_context, §or_num, &key, &key_type)) { + req_data->sector_num = sector_num; + req_data->key = key; + req_data->key_type = key_type; + req_data->key_provided = true; + } else { + req_data->key_provided = false; + } + } else if(mfc_event->type == MfClassicPollerEventTypeSuccess) { + command = NfcCommandStop; + } + + if(command == NfcCommandStop) { + furi_thread_flags_set(poller_context->thread_id, MF_CLASSIC_POLLER_COMPLETE_EVENT); + } + + return command; +} + +MfClassicError + mf_classic_poller_read(Nfc* nfc, const MfClassicDeviceKeys* keys, MfClassicData* data) { + furi_assert(nfc); + furi_assert(keys); + furi_assert(data); + + MfClassicError error = MfClassicErrorNone; + MfClassicPollerContext poller_context = {}; + poller_context.thread_id = furi_thread_get_current_id(); + poller_context.data.read_context.keys = *keys; + + NfcPoller* poller = nfc_poller_alloc(nfc, NfcProtocolMfClassic); + nfc_poller_start(poller, mf_classic_poller_read_callback, &poller_context); + furi_thread_flags_wait(MF_CLASSIC_POLLER_COMPLETE_EVENT, FuriFlagWaitAny, FuriWaitForever); + furi_thread_flags_clear(MF_CLASSIC_POLLER_COMPLETE_EVENT); + + nfc_poller_stop(poller); + + if(poller_context.error != MfClassicErrorNone) { + error = poller_context.error; + } else { + const MfClassicData* mfc_data = nfc_poller_get_data(poller); + uint8_t sectors_read = 0; + uint8_t keys_found = 0; + + mf_classic_get_read_sectors_and_keys(mfc_data, §ors_read, &keys_found); + if((sectors_read > 0) || (keys_found > 0)) { + mf_classic_copy(data, mfc_data); + } else { + error = MfClassicErrorNotPresent; + } + } + + nfc_poller_free(poller); + + return error; +} + +MfClassicError mf_classic_poller_detect_type(Nfc* nfc, MfClassicType* type) { + furi_assert(nfc); + furi_assert(type); + + MfClassicError error = MfClassicErrorNone; + + const uint8_t mf_classic_verify_block[MfClassicTypeNum] = { + [MfClassicTypeMini] = 0, + [MfClassicType1k] = 62, + [MfClassicType4k] = 254, + }; + + size_t i = 0; + for(i = 0; i < COUNT_OF(mf_classic_verify_block); i++) { + error = mf_classic_poller_collect_nt( + nfc, mf_classic_verify_block[MfClassicTypeNum - i - 1], MfClassicKeyTypeA, NULL); + if(error == MfClassicErrorNone) { + *type = MfClassicTypeNum - i - 1; + break; + } + } + + return error; +} diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller_sync_api.h b/lib/nfc/protocols/mf_classic/mf_classic_poller_sync_api.h new file mode 100644 index 00000000000..11db291b801 --- /dev/null +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_sync_api.h @@ -0,0 +1,59 @@ +#pragma once + +#include "mf_classic.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +MfClassicError mf_classic_poller_collect_nt( + Nfc* nfc, + uint8_t block_num, + MfClassicKeyType key_type, + MfClassicNt* nt); + +MfClassicError mf_classic_poller_auth( + Nfc* nfc, + uint8_t block_num, + MfClassicKey* key, + MfClassicKeyType key_type, + MfClassicAuthContext* data); + +MfClassicError mf_classic_poller_read_block( + Nfc* nfc, + uint8_t block_num, + MfClassicKey* key, + MfClassicKeyType key_type, + MfClassicBlock* data); + +MfClassicError mf_classic_poller_write_block( + Nfc* nfc, + uint8_t block_num, + MfClassicKey* key, + MfClassicKeyType key_type, + MfClassicBlock* data); + +MfClassicError mf_classic_poller_read_value( + Nfc* nfc, + uint8_t block_num, + MfClassicKey* key, + MfClassicKeyType key_type, + int32_t* value); + +MfClassicError mf_classic_poller_change_value( + Nfc* nfc, + uint8_t block_num, + MfClassicKey* key, + MfClassicKeyType key_type, + int32_t data, + int32_t* new_value); + +MfClassicError mf_classic_poller_detect_type(Nfc* nfc, MfClassicType* type); + +MfClassicError + mf_classic_poller_read(Nfc* nfc, const MfClassicDeviceKeys* keys, MfClassicData* data); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire.c b/lib/nfc/protocols/mf_desfire/mf_desfire.c new file mode 100644 index 00000000000..b83198a25e6 --- /dev/null +++ b/lib/nfc/protocols/mf_desfire/mf_desfire.c @@ -0,0 +1,290 @@ +#include "mf_desfire_i.h" + +#include + +#define MF_DESFIRE_PROTOCOL_NAME "Mifare DESFire" + +const NfcDeviceBase nfc_device_mf_desfire = { + .protocol_name = MF_DESFIRE_PROTOCOL_NAME, + .alloc = (NfcDeviceAlloc)mf_desfire_alloc, + .free = (NfcDeviceFree)mf_desfire_free, + .reset = (NfcDeviceReset)mf_desfire_reset, + .copy = (NfcDeviceCopy)mf_desfire_copy, + .verify = (NfcDeviceVerify)mf_desfire_verify, + .load = (NfcDeviceLoad)mf_desfire_load, + .save = (NfcDeviceSave)mf_desfire_save, + .is_equal = (NfcDeviceEqual)mf_desfire_is_equal, + .get_name = (NfcDeviceGetName)mf_desfire_get_device_name, + .get_uid = (NfcDeviceGetUid)mf_desfire_get_uid, + .set_uid = (NfcDeviceSetUid)mf_desfire_set_uid, + .get_base_data = (NfcDeviceGetBaseData)mf_desfire_get_base_data, +}; + +MfDesfireData* mf_desfire_alloc() { + MfDesfireData* data = malloc(sizeof(MfDesfireData)); + data->iso14443_4a_data = iso14443_4a_alloc(); + data->master_key_versions = simple_array_alloc(&mf_desfire_key_version_array_config); + data->application_ids = simple_array_alloc(&mf_desfire_app_id_array_config); + data->applications = simple_array_alloc(&mf_desfire_application_array_config); + + return data; +} + +void mf_desfire_free(MfDesfireData* data) { + furi_assert(data); + + mf_desfire_reset(data); + simple_array_free(data->applications); + simple_array_free(data->application_ids); + simple_array_free(data->master_key_versions); + iso14443_4a_free(data->iso14443_4a_data); + free(data); +} + +void mf_desfire_reset(MfDesfireData* data) { + furi_assert(data); + + iso14443_4a_reset(data->iso14443_4a_data); + + memset(&data->version, 0, sizeof(MfDesfireVersion)); + memset(&data->free_memory, 0, sizeof(MfDesfireFreeMemory)); + + simple_array_reset(data->master_key_versions); + simple_array_reset(data->application_ids); + simple_array_reset(data->applications); +} + +void mf_desfire_copy(MfDesfireData* data, const MfDesfireData* other) { + furi_assert(data); + furi_assert(other); + + mf_desfire_reset(data); + + iso14443_4a_copy(data->iso14443_4a_data, other->iso14443_4a_data); + + data->version = other->version; + data->free_memory = other->free_memory; + data->master_key_settings = other->master_key_settings; + + simple_array_copy(data->master_key_versions, other->master_key_versions); + simple_array_copy(data->application_ids, other->application_ids); + simple_array_copy(data->applications, other->applications); +} + +bool mf_desfire_verify(MfDesfireData* data, const FuriString* device_type) { + UNUSED(data); + return furi_string_equal_str(device_type, MF_DESFIRE_PROTOCOL_NAME); +} + +bool mf_desfire_load(MfDesfireData* data, FlipperFormat* ff, uint32_t version) { + furi_assert(data); + + FuriString* prefix = furi_string_alloc(); + + bool success = false; + + do { + if(!iso14443_4a_load(data->iso14443_4a_data, ff, version)) break; + + if(!mf_desfire_version_load(&data->version, ff)) break; + if(!mf_desfire_free_memory_load(&data->free_memory, ff)) break; + if(!mf_desfire_key_settings_load( + &data->master_key_settings, MF_DESFIRE_FFF_PICC_PREFIX, ff)) + break; + + const uint32_t master_key_version_count = data->master_key_settings.max_keys; + simple_array_init(data->master_key_versions, master_key_version_count); + + uint32_t i; + for(i = 0; i < master_key_version_count; ++i) { + if(!mf_desfire_key_version_load( + simple_array_get(data->master_key_versions, i), + MF_DESFIRE_FFF_PICC_PREFIX, + i, + ff)) + break; + } + + if(i != master_key_version_count) break; + + uint32_t application_count; + if(!mf_desfire_application_count_load(&application_count, ff)) break; + + if(application_count > 0) { + simple_array_init(data->application_ids, application_count); + if(!mf_desfire_application_ids_load( + simple_array_get_data(data->application_ids), application_count, ff)) + break; + + simple_array_init(data->applications, application_count); + for(i = 0; i < application_count; ++i) { + const MfDesfireApplicationId* app_id = simple_array_cget(data->application_ids, i); + furi_string_printf( + prefix, + "%s %02x%02x%02x", + MF_DESFIRE_FFF_APP_PREFIX, + app_id->data[0], + app_id->data[1], + app_id->data[2]); + + if(!mf_desfire_application_load( + simple_array_get(data->applications, i), furi_string_get_cstr(prefix), ff)) + break; + } + + if(i != application_count) break; + } + + success = true; + } while(false); + + furi_string_free(prefix); + return success; +} + +bool mf_desfire_save(const MfDesfireData* data, FlipperFormat* ff) { + furi_assert(data); + + FuriString* prefix = furi_string_alloc(); + + bool success = false; + + do { + if(!iso14443_4a_save(data->iso14443_4a_data, ff)) break; + + if(!flipper_format_write_comment_cstr(ff, MF_DESFIRE_PROTOCOL_NAME " specific data")) + break; + if(!mf_desfire_version_save(&data->version, ff)) break; + if(!mf_desfire_free_memory_save(&data->free_memory, ff)) break; + if(!mf_desfire_key_settings_save( + &data->master_key_settings, MF_DESFIRE_FFF_PICC_PREFIX, ff)) + break; + + const uint32_t master_key_version_count = + simple_array_get_count(data->master_key_versions); + + uint32_t i; + for(i = 0; i < master_key_version_count; ++i) { + if(!mf_desfire_key_version_save( + simple_array_cget(data->master_key_versions, i), + MF_DESFIRE_FFF_PICC_PREFIX, + i, + ff)) + break; + } + + if(i != master_key_version_count) break; + + const uint32_t application_count = simple_array_get_count(data->application_ids); + if(!mf_desfire_application_count_save(&application_count, ff)) break; + + if(application_count > 0) { + if(!mf_desfire_application_ids_save( + simple_array_cget_data(data->application_ids), application_count, ff)) + break; + + for(i = 0; i < application_count; ++i) { + const MfDesfireApplicationId* app_id = simple_array_cget(data->application_ids, i); + furi_string_printf( + prefix, + "%s %02x%02x%02x", + MF_DESFIRE_FFF_APP_PREFIX, + app_id->data[0], + app_id->data[1], + app_id->data[2]); + + const MfDesfireApplication* app = simple_array_cget(data->applications, i); + if(!mf_desfire_application_save(app, furi_string_get_cstr(prefix), ff)) break; + } + + if(i != application_count) break; + } + + success = true; + } while(false); + + furi_string_free(prefix); + return success; +} + +bool mf_desfire_is_equal(const MfDesfireData* data, const MfDesfireData* other) { + furi_assert(data); + furi_assert(other); + + return iso14443_4a_is_equal(data->iso14443_4a_data, other->iso14443_4a_data) && + memcmp(&data->version, &other->version, sizeof(MfDesfireVersion)) == 0 && + memcmp(&data->free_memory, &other->free_memory, sizeof(MfDesfireFreeMemory)) == 0 && + memcmp( + &data->master_key_settings, + &other->master_key_settings, + sizeof(MfDesfireKeySettings)) == 0 && + simple_array_is_equal(data->master_key_versions, other->master_key_versions) && + simple_array_is_equal(data->application_ids, other->application_ids) && + simple_array_is_equal(data->applications, other->applications); +} + +const char* mf_desfire_get_device_name(const MfDesfireData* data, NfcDeviceNameType name_type) { + UNUSED(data); + UNUSED(name_type); + return MF_DESFIRE_PROTOCOL_NAME; +} + +const uint8_t* mf_desfire_get_uid(const MfDesfireData* data, size_t* uid_len) { + furi_assert(data); + + return iso14443_4a_get_uid(data->iso14443_4a_data, uid_len); +} + +bool mf_desfire_set_uid(MfDesfireData* data, const uint8_t* uid, size_t uid_len) { + furi_assert(data); + + return iso14443_4a_set_uid(data->iso14443_4a_data, uid, uid_len); +} + +Iso14443_4aData* mf_desfire_get_base_data(const MfDesfireData* data) { + furi_assert(data); + + return data->iso14443_4a_data; +} + +const MfDesfireApplication* + mf_desfire_get_application(const MfDesfireData* data, const MfDesfireApplicationId* app_id) { + MfDesfireApplication* app = NULL; + + for(uint32_t i = 0; i < simple_array_get_count(data->application_ids); ++i) { + const MfDesfireApplicationId* current_app_id = simple_array_cget(data->application_ids, i); + if(memcmp(app_id, current_app_id, sizeof(MfDesfireApplicationId)) == 0) { + app = simple_array_get(data->applications, i); + } + } + + return app; +} + +const MfDesfireFileSettings* + mf_desfire_get_file_settings(const MfDesfireApplication* data, const MfDesfireFileId* file_id) { + MfDesfireFileSettings* file_settings = NULL; + + for(uint32_t i = 0; i < simple_array_get_count(data->file_ids); ++i) { + const MfDesfireFileId* current_file_id = simple_array_cget(data->file_ids, i); + if(*file_id == *current_file_id) { + file_settings = simple_array_get(data->file_settings, i); + } + } + + return file_settings; +} + +const MfDesfireFileData* + mf_desfire_get_file_data(const MfDesfireApplication* data, const MfDesfireFileId* file_id) { + MfDesfireFileData* file_data = NULL; + + for(uint32_t i = 0; i < simple_array_get_count(data->file_ids); ++i) { + const MfDesfireFileId* current_file_id = simple_array_cget(data->file_ids, i); + if(*file_id == *current_file_id) { + file_data = simple_array_get(data->file_data, i); + } + } + + return file_data; +} diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire.h b/lib/nfc/protocols/mf_desfire/mf_desfire.h new file mode 100644 index 00000000000..4d09dc85103 --- /dev/null +++ b/lib/nfc/protocols/mf_desfire/mf_desfire.h @@ -0,0 +1,191 @@ +#pragma once + +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define MF_DESFIRE_CMD_GET_VERSION (0x60) +#define MF_DESFIRE_CMD_GET_FREE_MEMORY (0x6E) +#define MF_DESFIRE_CMD_GET_KEY_SETTINGS (0x45) +#define MF_DESFIRE_CMD_GET_KEY_VERSION (0x64) +#define MF_DESFIRE_CMD_GET_APPLICATION_IDS (0x6A) +#define MF_DESFIRE_CMD_SELECT_APPLICATION (0x5A) +#define MF_DESFIRE_CMD_GET_FILE_IDS (0x6F) +#define MF_DESFIRE_CMD_GET_FILE_SETTINGS (0xF5) + +#define MF_DESFIRE_CMD_READ_DATA (0xBD) +#define MF_DESFIRE_CMD_GET_VALUE (0x6C) +#define MF_DESFIRE_CMD_READ_RECORDS (0xBB) + +#define MF_DESFIRE_FLAG_HAS_NEXT (0xAF) + +#define MF_DESFIRE_MAX_KEYS (14) +#define MF_DESFIRE_MAX_FILES (32) + +#define MF_DESFIRE_UID_SIZE (7) +#define MF_DESFIRE_BATCH_SIZE (5) +#define MF_DESFIRE_APP_ID_SIZE (3) + +typedef struct { + uint8_t hw_vendor; + uint8_t hw_type; + uint8_t hw_subtype; + uint8_t hw_major; + uint8_t hw_minor; + uint8_t hw_storage; + uint8_t hw_proto; + + uint8_t sw_vendor; + uint8_t sw_type; + uint8_t sw_subtype; + uint8_t sw_major; + uint8_t sw_minor; + uint8_t sw_storage; + uint8_t sw_proto; + + uint8_t uid[MF_DESFIRE_UID_SIZE]; + uint8_t batch[MF_DESFIRE_BATCH_SIZE]; + uint8_t prod_week; + uint8_t prod_year; +} MfDesfireVersion; + +typedef struct { + uint32_t bytes_free; + bool is_present; +} MfDesfireFreeMemory; // EV1+ only + +typedef struct { + bool is_master_key_changeable; + bool is_free_directory_list; + bool is_free_create_delete; + bool is_config_changeable; + uint8_t change_key_id; + uint8_t max_keys; + uint8_t flags; +} MfDesfireKeySettings; + +typedef uint8_t MfDesfireKeyVersion; + +typedef struct { + MfDesfireKeySettings key_settings; + SimpleArray* key_versions; +} MfDesfireKeyConfiguration; + +typedef enum { + MfDesfireFileTypeStandard = 0, + MfDesfireFileTypeBackup = 1, + MfDesfireFileTypeValue = 2, + MfDesfireFileTypeLinearRecord = 3, + MfDesfireFileTypeCyclicRecord = 4, +} MfDesfireFileType; + +typedef enum { + MfDesfireFileCommunicationSettingsPlaintext = 0, + MfDesfireFileCommunicationSettingsAuthenticated = 1, + MfDesfireFileCommunicationSettingsEnciphered = 3, +} MfDesfireFileCommunicationSettings; + +typedef uint8_t MfDesfireFileId; +typedef uint16_t MfDesfireFileAccessRights; + +typedef struct { + MfDesfireFileType type; + MfDesfireFileCommunicationSettings comm; + MfDesfireFileAccessRights access_rights; + union { + struct { + uint32_t size; + } data; + struct { + uint32_t lo_limit; + uint32_t hi_limit; + uint32_t limited_credit_value; + bool limited_credit_enabled; + } value; + struct { + uint32_t size; + uint32_t max; + uint32_t cur; + } record; + }; +} MfDesfireFileSettings; + +typedef struct { + SimpleArray* data; +} MfDesfireFileData; + +typedef struct { + uint8_t data[MF_DESFIRE_APP_ID_SIZE]; +} MfDesfireApplicationId; + +typedef struct MfDesfireApplication { + MfDesfireKeySettings key_settings; + SimpleArray* key_versions; + SimpleArray* file_ids; + SimpleArray* file_settings; + SimpleArray* file_data; +} MfDesfireApplication; + +typedef enum { + MfDesfireErrorNone, + MfDesfireErrorNotPresent, + MfDesfireErrorProtocol, + MfDesfireErrorTimeout, +} MfDesfireError; + +typedef struct { + Iso14443_4aData* iso14443_4a_data; + MfDesfireVersion version; + MfDesfireFreeMemory free_memory; + MfDesfireKeySettings master_key_settings; + SimpleArray* master_key_versions; + SimpleArray* application_ids; + SimpleArray* applications; +} MfDesfireData; + +extern const NfcDeviceBase nfc_device_mf_desfire; + +// Virtual methods + +MfDesfireData* mf_desfire_alloc(); + +void mf_desfire_free(MfDesfireData* data); + +void mf_desfire_reset(MfDesfireData* data); + +void mf_desfire_copy(MfDesfireData* data, const MfDesfireData* other); + +bool mf_desfire_verify(MfDesfireData* data, const FuriString* device_type); + +bool mf_desfire_load(MfDesfireData* data, FlipperFormat* ff, uint32_t version); + +bool mf_desfire_save(const MfDesfireData* data, FlipperFormat* ff); + +bool mf_desfire_is_equal(const MfDesfireData* data, const MfDesfireData* other); + +const char* mf_desfire_get_device_name(const MfDesfireData* data, NfcDeviceNameType name_type); + +const uint8_t* mf_desfire_get_uid(const MfDesfireData* data, size_t* uid_len); + +bool mf_desfire_set_uid(MfDesfireData* data, const uint8_t* uid, size_t uid_len); + +Iso14443_4aData* mf_desfire_get_base_data(const MfDesfireData* data); + +// Getters and tests + +const MfDesfireApplication* + mf_desfire_get_application(const MfDesfireData* data, const MfDesfireApplicationId* app_id); + +const MfDesfireFileSettings* + mf_desfire_get_file_settings(const MfDesfireApplication* data, const MfDesfireFileId* file_id); + +const MfDesfireFileData* + mf_desfire_get_file_data(const MfDesfireApplication* data, const MfDesfireFileId* file_id); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_i.c b/lib/nfc/protocols/mf_desfire/mf_desfire_i.c new file mode 100644 index 00000000000..30313ae2bcf --- /dev/null +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_i.c @@ -0,0 +1,790 @@ +#include "mf_desfire_i.h" + +#define BITS_IN_BYTE (8U) + +#define MF_DESFIRE_FFF_VERSION_KEY \ + MF_DESFIRE_FFF_PICC_PREFIX " " \ + "Version" +#define MF_DESFIRE_FFF_FREE_MEM_KEY \ + MF_DESFIRE_FFF_PICC_PREFIX " " \ + "Free Memory" + +#define MF_DESFIRE_FFF_CHANGE_KEY_ID_KEY "Change Key ID" +#define MF_DESFIRE_FFF_CONFIG_CHANGEABLE_KEY "Config Changeable" +#define MF_DESFIRE_FFF_FREE_CREATE_DELETE_KEY "Free Create Delete" +#define MF_DESFIRE_FFF_FREE_DIR_LIST_KEY "Free Directory List" +#define MF_DESFIRE_FFF_KEY_CHANGEABLE_KEY "Key Changeable" +#define MF_DESFIRE_FFF_FLAGS_KEY "Flags" +#define MF_DESFIRE_FFF_MAX_KEYS_KEY "Max Keys" + +#define MF_DESFIRE_FFF_KEY_SUB_PREFIX "Key" +#define MF_DESFIRE_FFF_KEY_VERSION_KEY "Version" + +#define MF_DESFIRE_FFF_APPLICATION_COUNT_KEY \ + MF_DESFIRE_FFF_APP_PREFIX " " \ + "Count" +#define MF_DESFIRE_FFF_APPLICATION_IDS_KEY \ + MF_DESFIRE_FFF_APP_PREFIX " " \ + "IDs" + +#define MF_DESFIRE_FFF_FILE_SUB_PREFIX "File" +#define MF_DESFIRE_FFF_FILE_IDS_KEY \ + MF_DESFIRE_FFF_FILE_SUB_PREFIX " " \ + "IDs" +#define MF_DESFIRE_FFF_FILE_TYPE_KEY "Type" +#define MF_DESFIRE_FFF_FILE_COMM_SETTINGS_KEY "Communication Settings" +#define MF_DESFIRE_FFF_FILE_ACCESS_RIGHTS_KEY "Access Rights" + +#define MF_DESFIRE_FFF_FILE_SIZE_KEY "Size" + +#define MF_DESFIRE_FFF_FILE_HI_LIMIT_KEY "Hi Limit" +#define MF_DESFIRE_FFF_FILE_LO_LIMIT_KEY "Lo Limit" +#define MF_DESFIRE_FFF_FILE_LIMIT_CREDIT_VALUE_KEY "Limited Credit Value" +#define MF_DESFIRE_FFF_FILE_LIMIT_CREDIT_ENABLED_KEY "Limited Credit Enabled" + +#define MF_DESFIRE_FFF_FILE_MAX_KEY "Max" +#define MF_DESFIRE_FFF_FILE_CUR_KEY "Cur" + +bool mf_desfire_version_parse(MfDesfireVersion* data, const BitBuffer* buf) { + const bool can_parse = bit_buffer_get_size_bytes(buf) == sizeof(MfDesfireVersion); + + if(can_parse) { + bit_buffer_write_bytes(buf, data, sizeof(MfDesfireVersion)); + } + + return can_parse; +} + +bool mf_desfire_free_memory_parse(MfDesfireFreeMemory* data, const BitBuffer* buf) { + typedef struct __attribute__((packed)) { + uint32_t bytes_free : 3 * BITS_IN_BYTE; + } MfDesfireFreeMemoryLayout; + + const bool can_parse = bit_buffer_get_size_bytes(buf) == sizeof(MfDesfireFreeMemoryLayout); + + if(can_parse) { + MfDesfireFreeMemoryLayout layout; + bit_buffer_write_bytes(buf, &layout, sizeof(MfDesfireFreeMemoryLayout)); + data->bytes_free = layout.bytes_free; + } + + data->is_present = can_parse; + + return can_parse; +} + +bool mf_desfire_key_settings_parse(MfDesfireKeySettings* data, const BitBuffer* buf) { + typedef struct __attribute__((packed)) { + bool is_master_key_changeable : 1; + bool is_free_directory_list : 1; + bool is_free_create_delete : 1; + bool is_config_changeable : 1; + uint8_t change_key_id : 4; + uint8_t max_keys : 4; + uint8_t flags : 4; + } MfDesfireKeySettingsLayout; + + const bool can_parse = bit_buffer_get_size_bytes(buf) == sizeof(MfDesfireKeySettingsLayout); + + if(can_parse) { + MfDesfireKeySettingsLayout layout; + bit_buffer_write_bytes(buf, &layout, sizeof(MfDesfireKeySettingsLayout)); + + data->is_master_key_changeable = layout.is_master_key_changeable; + data->is_free_directory_list = layout.is_free_directory_list; + data->is_free_create_delete = layout.is_free_create_delete; + data->is_config_changeable = layout.is_config_changeable; + + data->change_key_id = layout.change_key_id; + data->max_keys = layout.max_keys; + data->flags = layout.flags; + } + + return can_parse; +} + +bool mf_desfire_key_version_parse(MfDesfireKeyVersion* data, const BitBuffer* buf) { + const bool can_parse = bit_buffer_get_size_bytes(buf) == sizeof(MfDesfireKeyVersion); + + if(can_parse) { + bit_buffer_write_bytes(buf, data, sizeof(MfDesfireKeyVersion)); + } + + return can_parse; +} + +bool mf_desfire_application_id_parse( + MfDesfireApplicationId* data, + uint32_t index, + const BitBuffer* buf) { + const bool can_parse = + bit_buffer_get_size_bytes(buf) >= + (index * sizeof(MfDesfireApplicationId) + sizeof(MfDesfireApplicationId)); + + if(can_parse) { + bit_buffer_write_bytes_mid( + buf, data, index * sizeof(MfDesfireApplicationId), sizeof(MfDesfireApplicationId)); + } + + return can_parse; +} + +bool mf_desfire_file_id_parse(MfDesfireFileId* data, uint32_t index, const BitBuffer* buf) { + const bool can_parse = bit_buffer_get_size_bytes(buf) >= + (index * sizeof(MfDesfireFileId) + sizeof(MfDesfireFileId)); + if(can_parse) { + bit_buffer_write_bytes_mid( + buf, data, index * sizeof(MfDesfireFileId), sizeof(MfDesfireFileId)); + } + + return can_parse; +} + +bool mf_desfire_file_settings_parse(MfDesfireFileSettings* data, const BitBuffer* buf) { + bool parsed = false; + + typedef struct __attribute__((packed)) { + uint8_t type; + uint8_t comm; + uint16_t access_rights; + } MfDesfireFileSettingsHeader; + + typedef struct __attribute__((packed)) { + uint32_t size : 3 * BITS_IN_BYTE; + } MfDesfireFileSettingsData; + + typedef struct __attribute__((packed)) { + uint32_t lo_limit; + uint32_t hi_limit; + uint32_t limited_credit_value; + uint8_t limited_credit_enabled; + } MfDesfireFileSettingsValue; + + typedef struct __attribute__((packed)) { + uint32_t size : 3 * BITS_IN_BYTE; + uint32_t max : 3 * BITS_IN_BYTE; + uint32_t cur : 3 * BITS_IN_BYTE; + } MfDesfireFileSettingsRecord; + + typedef struct __attribute__((packed)) { + MfDesfireFileSettingsHeader header; + union { + MfDesfireFileSettingsData data; + MfDesfireFileSettingsValue value; + MfDesfireFileSettingsRecord record; + }; + } MfDesfireFileSettingsLayout; + + do { + const size_t data_size = bit_buffer_get_size_bytes(buf); + const size_t min_data_size = + sizeof(MfDesfireFileSettingsHeader) + sizeof(MfDesfireFileSettingsData); + + if(data_size < min_data_size) break; + + MfDesfireFileSettingsLayout layout; + bit_buffer_write_bytes(buf, &layout, sizeof(MfDesfireFileSettingsLayout)); + + data->type = layout.header.type; + data->comm = layout.header.comm; + data->access_rights = layout.header.access_rights; + + if(data->type == MfDesfireFileTypeStandard || data->type == MfDesfireFileTypeBackup) { + if(data_size != min_data_size) break; + + data->data.size = layout.data.size; + + } else if(data->type == MfDesfireFileTypeValue) { + if(data_size != + sizeof(MfDesfireFileSettingsHeader) + sizeof(MfDesfireFileSettingsValue)) + break; + + data->value.lo_limit = layout.value.lo_limit; + data->value.hi_limit = layout.value.hi_limit; + data->value.limited_credit_value = layout.value.hi_limit; + data->value.limited_credit_enabled = layout.value.limited_credit_enabled; + + } else if( + data->type == MfDesfireFileTypeLinearRecord || + data->type == MfDesfireFileTypeCyclicRecord) { + if(data_size != + sizeof(MfDesfireFileSettingsHeader) + sizeof(MfDesfireFileSettingsRecord)) + break; + + data->record.size = layout.record.size; + data->record.max = layout.record.max; + data->record.cur = layout.record.cur; + + } else { + break; + } + + parsed = true; + } while(false); + + return parsed; +} + +bool mf_desfire_file_data_parse(MfDesfireFileData* data, const BitBuffer* buf) { + const size_t data_size = bit_buffer_get_size_bytes(buf); + + if(data_size > 0) { + simple_array_init(data->data, data_size); + bit_buffer_write_bytes(buf, simple_array_get_data(data->data), data_size); + } + + // Success no matter whether there is data or not + return true; +} + +void mf_desfire_file_data_init(MfDesfireFileData* data) { + data->data = simple_array_alloc(&simple_array_config_uint8_t); +} + +void mf_desfire_application_init(MfDesfireApplication* data) { + data->key_versions = simple_array_alloc(&mf_desfire_key_version_array_config); + data->file_ids = simple_array_alloc(&mf_desfire_file_id_array_config); + data->file_settings = simple_array_alloc(&mf_desfire_file_settings_array_config); + data->file_data = simple_array_alloc(&mf_desfire_file_data_array_config); +} + +void mf_desfire_file_data_reset(MfDesfireFileData* data) { + simple_array_free(data->data); + memset(data, 0, sizeof(MfDesfireFileData)); +} + +void mf_desfire_application_reset(MfDesfireApplication* data) { + simple_array_free(data->key_versions); + simple_array_free(data->file_ids); + simple_array_free(data->file_settings); + simple_array_free(data->file_data); + memset(data, 0, sizeof(MfDesfireApplication)); +} + +void mf_desfire_file_data_copy(MfDesfireFileData* data, const MfDesfireFileData* other) { + simple_array_copy(data->data, other->data); +} + +void mf_desfire_application_copy(MfDesfireApplication* data, const MfDesfireApplication* other) { + data->key_settings = other->key_settings; + simple_array_copy(data->key_versions, other->key_versions); + simple_array_copy(data->file_ids, other->file_ids); + simple_array_copy(data->file_settings, other->file_settings); + simple_array_copy(data->file_data, other->file_data); +} + +bool mf_desfire_version_load(MfDesfireVersion* data, FlipperFormat* ff) { + return flipper_format_read_hex( + ff, MF_DESFIRE_FFF_VERSION_KEY, (uint8_t*)data, sizeof(MfDesfireVersion)); +} + +bool mf_desfire_free_memory_load(MfDesfireFreeMemory* data, FlipperFormat* ff) { + data->is_present = flipper_format_key_exist(ff, MF_DESFIRE_FFF_FREE_MEM_KEY); + return data->is_present ? + flipper_format_read_uint32(ff, MF_DESFIRE_FFF_FREE_MEM_KEY, &data->bytes_free, 1) : + true; +} + +bool mf_desfire_key_settings_load( + MfDesfireKeySettings* data, + const char* prefix, + FlipperFormat* ff) { + bool success = false; + + FuriString* key = furi_string_alloc(); + + do { + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_CHANGE_KEY_ID_KEY); + if(!flipper_format_read_hex(ff, furi_string_get_cstr(key), &data->change_key_id, 1)) break; + + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_CONFIG_CHANGEABLE_KEY); + if(!flipper_format_read_bool(ff, furi_string_get_cstr(key), &data->is_config_changeable, 1)) + break; + + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FREE_CREATE_DELETE_KEY); + if(!flipper_format_read_bool( + ff, furi_string_get_cstr(key), &data->is_free_create_delete, 1)) + break; + + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FREE_DIR_LIST_KEY); + if(!flipper_format_read_bool( + ff, furi_string_get_cstr(key), &data->is_free_directory_list, 1)) + break; + + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_KEY_CHANGEABLE_KEY); + if(!flipper_format_read_bool( + ff, furi_string_get_cstr(key), &data->is_master_key_changeable, 1)) + break; + + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FLAGS_KEY); + if(flipper_format_key_exist(ff, furi_string_get_cstr(key))) { + if(!flipper_format_read_hex(ff, furi_string_get_cstr(key), &data->flags, 1)) break; + } + + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_MAX_KEYS_KEY); + if(!flipper_format_read_hex(ff, furi_string_get_cstr(key), &data->max_keys, 1)) break; + + success = true; + } while(false); + + furi_string_free(key); + return success; +} + +bool mf_desfire_key_version_load( + MfDesfireKeyVersion* data, + const char* prefix, + uint32_t index, + FlipperFormat* ff) { + FuriString* key = furi_string_alloc_printf( + "%s %s %lu %s", + prefix, + MF_DESFIRE_FFF_KEY_SUB_PREFIX, + index, + MF_DESFIRE_FFF_KEY_VERSION_KEY); + const bool success = flipper_format_read_hex(ff, furi_string_get_cstr(key), data, 1); + furi_string_free(key); + return success; +} + +bool mf_desfire_file_count_load(uint32_t* data, const char* prefix, FlipperFormat* ff) { + FuriString* key = furi_string_alloc_printf("%s %s", prefix, MF_DESFIRE_FFF_FILE_IDS_KEY); + const bool success = flipper_format_get_value_count(ff, furi_string_get_cstr(key), data); + furi_string_free(key); + return success; +} + +bool mf_desfire_file_ids_load( + MfDesfireFileId* data, + uint32_t count, + const char* prefix, + FlipperFormat* ff) { + FuriString* key = furi_string_alloc_printf("%s %s", prefix, MF_DESFIRE_FFF_FILE_IDS_KEY); + const bool success = flipper_format_read_hex(ff, furi_string_get_cstr(key), data, count); + furi_string_free(key); + return success; +} + +bool mf_desfire_file_settings_load( + MfDesfireFileSettings* data, + const char* prefix, + FlipperFormat* ff) { + bool success = false; + + FuriString* key = furi_string_alloc(); + + do { + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_TYPE_KEY); + if(!flipper_format_read_hex(ff, furi_string_get_cstr(key), (uint8_t*)&data->type, 1)) + break; + + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_COMM_SETTINGS_KEY); + if(!flipper_format_read_hex(ff, furi_string_get_cstr(key), (uint8_t*)&data->comm, 1)) + break; + + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_ACCESS_RIGHTS_KEY); + if(!flipper_format_read_hex( + ff, + furi_string_get_cstr(key), + (uint8_t*)&data->access_rights, + sizeof(MfDesfireFileAccessRights))) + break; + + if(data->type == MfDesfireFileTypeStandard || data->type == MfDesfireFileTypeBackup) { + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_SIZE_KEY); + if(!flipper_format_read_uint32(ff, furi_string_get_cstr(key), &data->data.size, 1)) + break; + + } else if(data->type == MfDesfireFileTypeValue) { + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_HI_LIMIT_KEY); + if(!flipper_format_read_uint32(ff, furi_string_get_cstr(key), &data->value.hi_limit, 1)) + break; + + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_LO_LIMIT_KEY); + if(!flipper_format_read_uint32(ff, furi_string_get_cstr(key), &data->value.lo_limit, 1)) + break; + + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_LIMIT_CREDIT_VALUE_KEY); + if(!flipper_format_read_uint32( + ff, furi_string_get_cstr(key), &data->value.limited_credit_value, 1)) + break; + + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_LIMIT_CREDIT_ENABLED_KEY); + if(!flipper_format_read_bool( + ff, furi_string_get_cstr(key), &data->value.limited_credit_enabled, 1)) + break; + } else if( + data->type == MfDesfireFileTypeLinearRecord || + data->type == MfDesfireFileTypeCyclicRecord) { + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_SIZE_KEY); + if(!flipper_format_read_uint32(ff, furi_string_get_cstr(key), &data->record.size, 1)) + break; + + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_MAX_KEY); + if(!flipper_format_read_uint32(ff, furi_string_get_cstr(key), &data->record.max, 1)) + break; + + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_CUR_KEY); + if(!flipper_format_read_uint32(ff, furi_string_get_cstr(key), &data->record.cur, 1)) + break; + } + + success = true; + } while(false); + + furi_string_free(key); + + return success; +} + +bool mf_desfire_file_data_load(MfDesfireFileData* data, const char* prefix, FlipperFormat* ff) { + bool success = false; + do { + if(!flipper_format_key_exist(ff, prefix)) { + success = true; + break; + } + + uint32_t data_size; + if(!flipper_format_get_value_count(ff, prefix, &data_size)) break; + + simple_array_init(data->data, data_size); + + if(!flipper_format_read_hex(ff, prefix, simple_array_get_data(data->data), data_size)) + break; + + success = true; + } while(false); + + return success; +} + +bool mf_desfire_application_count_load(uint32_t* data, FlipperFormat* ff) { + return flipper_format_read_uint32(ff, MF_DESFIRE_FFF_APPLICATION_COUNT_KEY, data, 1); +} + +bool mf_desfire_application_ids_load( + MfDesfireApplicationId* data, + uint32_t count, + FlipperFormat* ff) { + return flipper_format_read_hex( + ff, MF_DESFIRE_FFF_APPLICATION_IDS_KEY, data->data, count * sizeof(MfDesfireApplicationId)); +} + +bool mf_desfire_application_load(MfDesfireApplication* data, const char* prefix, FlipperFormat* ff) { + FuriString* sub_prefix = furi_string_alloc(); + bool success = false; + + do { + if(!mf_desfire_key_settings_load(&data->key_settings, prefix, ff)) break; + + const uint32_t key_version_count = data->key_settings.max_keys; + simple_array_init(data->key_versions, key_version_count); + + uint32_t i; + for(i = 0; i < key_version_count; ++i) { + if(!mf_desfire_key_version_load(simple_array_get(data->key_versions, i), prefix, i, ff)) + break; + } + + if(i != key_version_count) break; + + uint32_t file_count; + if(!mf_desfire_file_count_load(&file_count, prefix, ff)) break; + + simple_array_init(data->file_ids, file_count); + if(!mf_desfire_file_ids_load(simple_array_get_data(data->file_ids), file_count, prefix, ff)) + break; + + simple_array_init(data->file_settings, file_count); + simple_array_init(data->file_data, file_count); + + for(i = 0; i < file_count; ++i) { + const MfDesfireFileId* file_id = simple_array_cget(data->file_ids, i); + furi_string_printf( + sub_prefix, "%s %s %u", prefix, MF_DESFIRE_FFF_FILE_SUB_PREFIX, *file_id); + + MfDesfireFileSettings* file_settings = simple_array_get(data->file_settings, i); + if(!mf_desfire_file_settings_load(file_settings, furi_string_get_cstr(sub_prefix), ff)) + break; + + MfDesfireFileData* file_data = simple_array_get(data->file_data, i); + if(!mf_desfire_file_data_load(file_data, furi_string_get_cstr(sub_prefix), ff)) break; + } + + if(i != file_count) break; + + success = true; + } while(false); + + furi_string_free(sub_prefix); + return success; +} + +bool mf_desfire_version_save(const MfDesfireVersion* data, FlipperFormat* ff) { + return flipper_format_write_hex( + ff, MF_DESFIRE_FFF_VERSION_KEY, (const uint8_t*)data, sizeof(MfDesfireVersion)); +} + +bool mf_desfire_free_memory_save(const MfDesfireFreeMemory* data, FlipperFormat* ff) { + return data->is_present ? + flipper_format_write_uint32(ff, MF_DESFIRE_FFF_FREE_MEM_KEY, &data->bytes_free, 1) : + true; +} + +bool mf_desfire_key_settings_save( + const MfDesfireKeySettings* data, + const char* prefix, + FlipperFormat* ff) { + bool success = false; + + FuriString* key = furi_string_alloc(); + + do { + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_CHANGE_KEY_ID_KEY); + if(!flipper_format_write_hex(ff, furi_string_get_cstr(key), &data->change_key_id, 1)) + break; + + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_CONFIG_CHANGEABLE_KEY); + if(!flipper_format_write_bool( + ff, furi_string_get_cstr(key), &data->is_config_changeable, 1)) + break; + + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FREE_CREATE_DELETE_KEY); + if(!flipper_format_write_bool( + ff, furi_string_get_cstr(key), &data->is_free_create_delete, 1)) + break; + + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FREE_DIR_LIST_KEY); + if(!flipper_format_write_bool( + ff, furi_string_get_cstr(key), &data->is_free_directory_list, 1)) + break; + + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_KEY_CHANGEABLE_KEY); + if(!flipper_format_write_bool( + ff, furi_string_get_cstr(key), &data->is_master_key_changeable, 1)) + break; + + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FLAGS_KEY); + if(!flipper_format_write_hex(ff, furi_string_get_cstr(key), &data->flags, 1)) break; + + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_MAX_KEYS_KEY); + if(!flipper_format_write_hex(ff, furi_string_get_cstr(key), &data->max_keys, 1)) break; + + success = true; + } while(false); + + furi_string_free(key); + return success; +} + +bool mf_desfire_key_version_save( + const MfDesfireKeyVersion* data, + const char* prefix, + uint32_t index, + FlipperFormat* ff) { + FuriString* key = furi_string_alloc_printf( + "%s %s %lu %s", + prefix, + MF_DESFIRE_FFF_KEY_SUB_PREFIX, + index, + MF_DESFIRE_FFF_KEY_VERSION_KEY); + const bool success = flipper_format_write_hex(ff, furi_string_get_cstr(key), data, 1); + furi_string_free(key); + return success; +} + +bool mf_desfire_file_ids_save( + const MfDesfireFileId* data, + uint32_t count, + const char* prefix, + FlipperFormat* ff) { + FuriString* key = furi_string_alloc_printf("%s %s", prefix, MF_DESFIRE_FFF_FILE_IDS_KEY); + const bool success = flipper_format_write_hex(ff, furi_string_get_cstr(key), data, count); + furi_string_free(key); + return success; +} + +bool mf_desfire_file_settings_save( + const MfDesfireFileSettings* data, + const char* prefix, + FlipperFormat* ff) { + bool success = false; + + FuriString* key = furi_string_alloc(); + + do { + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_TYPE_KEY); + if(!flipper_format_write_hex(ff, furi_string_get_cstr(key), (const uint8_t*)&data->type, 1)) + break; + + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_COMM_SETTINGS_KEY); + if(!flipper_format_write_hex(ff, furi_string_get_cstr(key), (const uint8_t*)&data->comm, 1)) + break; + + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_ACCESS_RIGHTS_KEY); + if(!flipper_format_write_hex( + ff, + furi_string_get_cstr(key), + (const uint8_t*)&data->access_rights, + sizeof(MfDesfireFileAccessRights))) + break; + + if(data->type == MfDesfireFileTypeStandard || data->type == MfDesfireFileTypeBackup) { + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_SIZE_KEY); + if(!flipper_format_write_uint32(ff, furi_string_get_cstr(key), &data->data.size, 1)) + break; + + } else if(data->type == MfDesfireFileTypeValue) { + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_HI_LIMIT_KEY); + if(!flipper_format_write_uint32( + ff, furi_string_get_cstr(key), &data->value.hi_limit, 1)) + break; + + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_LO_LIMIT_KEY); + if(!flipper_format_write_uint32( + ff, furi_string_get_cstr(key), &data->value.lo_limit, 1)) + break; + + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_LIMIT_CREDIT_VALUE_KEY); + if(!flipper_format_write_uint32( + ff, furi_string_get_cstr(key), &data->value.limited_credit_value, 1)) + break; + + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_LIMIT_CREDIT_ENABLED_KEY); + if(!flipper_format_write_bool( + ff, furi_string_get_cstr(key), &data->value.limited_credit_enabled, 1)) + break; + } else if( + data->type == MfDesfireFileTypeLinearRecord || + data->type == MfDesfireFileTypeCyclicRecord) { + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_SIZE_KEY); + if(!flipper_format_write_uint32(ff, furi_string_get_cstr(key), &data->record.size, 1)) + break; + + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_MAX_KEY); + if(!flipper_format_write_uint32(ff, furi_string_get_cstr(key), &data->record.max, 1)) + break; + + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_CUR_KEY); + if(!flipper_format_write_uint32(ff, furi_string_get_cstr(key), &data->record.cur, 1)) + break; + } + + success = true; + } while(false); + + furi_string_free(key); + return success; +} + +bool mf_desfire_file_data_save( + const MfDesfireFileData* data, + const char* prefix, + FlipperFormat* ff) { + const uint32_t data_size = simple_array_get_count(data->data); + return data_size > 0 ? flipper_format_write_hex( + ff, prefix, simple_array_cget_data(data->data), data_size) : + true; +} + +bool mf_desfire_application_count_save(const uint32_t* data, FlipperFormat* ff) { + return flipper_format_write_uint32(ff, MF_DESFIRE_FFF_APPLICATION_COUNT_KEY, data, 1); +} + +bool mf_desfire_application_ids_save( + const MfDesfireApplicationId* data, + uint32_t count, + FlipperFormat* ff) { + return flipper_format_write_hex( + ff, MF_DESFIRE_FFF_APPLICATION_IDS_KEY, data->data, count * sizeof(MfDesfireApplicationId)); +} + +bool mf_desfire_application_save( + const MfDesfireApplication* data, + const char* prefix, + FlipperFormat* ff) { + FuriString* sub_prefix = furi_string_alloc(); + bool success = false; + + do { + if(!mf_desfire_key_settings_save(&data->key_settings, prefix, ff)) break; + + const uint32_t key_version_count = data->key_settings.max_keys; + + uint32_t i; + for(i = 0; i < key_version_count; ++i) { + if(!mf_desfire_key_version_save( + simple_array_cget(data->key_versions, i), prefix, i, ff)) + break; + } + + if(i != key_version_count) break; + + const uint32_t file_count = simple_array_get_count(data->file_ids); + if(!mf_desfire_file_ids_save(simple_array_get_data(data->file_ids), file_count, prefix, ff)) + break; + + for(i = 0; i < file_count; ++i) { + const MfDesfireFileId* file_id = simple_array_cget(data->file_ids, i); + furi_string_printf( + sub_prefix, "%s %s %u", prefix, MF_DESFIRE_FFF_FILE_SUB_PREFIX, *file_id); + + const MfDesfireFileSettings* file_settings = simple_array_cget(data->file_settings, i); + if(!mf_desfire_file_settings_save(file_settings, furi_string_get_cstr(sub_prefix), ff)) + break; + + const MfDesfireFileData* file_data = simple_array_cget(data->file_data, i); + if(!mf_desfire_file_data_save(file_data, furi_string_get_cstr(sub_prefix), ff)) break; + } + + if(i != file_count) break; + + success = true; + } while(false); + + furi_string_free(sub_prefix); + return success; +} + +const SimpleArrayConfig mf_desfire_key_version_array_config = { + .init = NULL, + .copy = NULL, + .reset = NULL, + .type_size = sizeof(MfDesfireKeyVersion), +}; + +const SimpleArrayConfig mf_desfire_app_id_array_config = { + .init = NULL, + .copy = NULL, + .reset = NULL, + .type_size = sizeof(MfDesfireApplicationId), +}; + +const SimpleArrayConfig mf_desfire_file_id_array_config = { + .init = NULL, + .copy = NULL, + .reset = NULL, + .type_size = sizeof(MfDesfireFileId), +}; + +const SimpleArrayConfig mf_desfire_file_settings_array_config = { + .init = NULL, + .copy = NULL, + .reset = NULL, + .type_size = sizeof(MfDesfireFileSettings), +}; + +const SimpleArrayConfig mf_desfire_file_data_array_config = { + .init = (SimpleArrayInit)mf_desfire_file_data_init, + .copy = (SimpleArrayCopy)mf_desfire_file_data_copy, + .reset = (SimpleArrayReset)mf_desfire_file_data_reset, + .type_size = sizeof(MfDesfireData), +}; + +const SimpleArrayConfig mf_desfire_application_array_config = { + .init = (SimpleArrayInit)mf_desfire_application_init, + .copy = (SimpleArrayCopy)mf_desfire_application_copy, + .reset = (SimpleArrayReset)mf_desfire_application_reset, + .type_size = sizeof(MfDesfireApplication), +}; diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_i.h b/lib/nfc/protocols/mf_desfire/mf_desfire_i.h new file mode 100644 index 00000000000..05381096d16 --- /dev/null +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_i.h @@ -0,0 +1,140 @@ +#pragma once + +#include "mf_desfire.h" + +#define MF_DESFIRE_FFF_PICC_PREFIX "PICC" +#define MF_DESFIRE_FFF_APP_PREFIX "Application" + +// SimpleArray configurations + +extern const SimpleArrayConfig mf_desfire_key_version_array_config; +extern const SimpleArrayConfig mf_desfire_app_id_array_config; +extern const SimpleArrayConfig mf_desfire_file_id_array_config; +extern const SimpleArrayConfig mf_desfire_file_settings_array_config; +extern const SimpleArrayConfig mf_desfire_file_data_array_config; +extern const SimpleArrayConfig mf_desfire_application_array_config; + +// Parse internal MfDesfire structures + +bool mf_desfire_version_parse(MfDesfireVersion* data, const BitBuffer* buf); + +bool mf_desfire_free_memory_parse(MfDesfireFreeMemory* data, const BitBuffer* buf); + +bool mf_desfire_key_settings_parse(MfDesfireKeySettings* data, const BitBuffer* buf); + +bool mf_desfire_key_version_parse(MfDesfireKeyVersion* data, const BitBuffer* buf); + +bool mf_desfire_application_id_parse( + MfDesfireApplicationId* data, + uint32_t index, + const BitBuffer* buf); + +bool mf_desfire_file_id_parse(MfDesfireFileId* data, uint32_t index, const BitBuffer* buf); + +bool mf_desfire_file_settings_parse(MfDesfireFileSettings* data, const BitBuffer* buf); + +bool mf_desfire_file_data_parse(MfDesfireFileData* data, const BitBuffer* buf); + +// Init internal MfDesfire structures + +void mf_desfire_file_data_init(MfDesfireFileData* data); + +void mf_desfire_application_init(MfDesfireApplication* data); + +// Reset internal MfDesfire structures + +void mf_desfire_file_data_reset(MfDesfireFileData* data); + +void mf_desfire_application_reset(MfDesfireApplication* data); + +// Copy internal MfDesfire structures + +void mf_desfire_file_data_copy(MfDesfireFileData* data, const MfDesfireFileData* other); + +void mf_desfire_application_copy(MfDesfireApplication* data, const MfDesfireApplication* other); + +// Load internal MfDesfire structures + +bool mf_desfire_version_load(MfDesfireVersion* data, FlipperFormat* ff); + +bool mf_desfire_free_memory_load(MfDesfireFreeMemory* data, FlipperFormat* ff); + +bool mf_desfire_key_settings_load( + MfDesfireKeySettings* data, + const char* prefix, + FlipperFormat* ff); + +bool mf_desfire_key_version_load( + MfDesfireKeyVersion* data, + const char* prefix, + uint32_t index, + FlipperFormat* ff); + +bool mf_desfire_file_count_load(uint32_t* data, const char* prefix, FlipperFormat* ff); + +bool mf_desfire_file_ids_load( + MfDesfireFileId* data, + uint32_t count, + const char* prefix, + FlipperFormat* ff); + +bool mf_desfire_file_settings_load( + MfDesfireFileSettings* data, + const char* prefix, + FlipperFormat* ff); + +bool mf_desfire_file_data_load(MfDesfireFileData* data, const char* prefix, FlipperFormat* ff); + +bool mf_desfire_application_count_load(uint32_t* data, FlipperFormat* ff); + +bool mf_desfire_application_ids_load( + MfDesfireApplicationId* data, + uint32_t count, + FlipperFormat* ff); + +bool mf_desfire_application_load(MfDesfireApplication* data, const char* prefix, FlipperFormat* ff); + +// Save internal MFDesfire structures + +bool mf_desfire_version_save(const MfDesfireVersion* data, FlipperFormat* ff); + +bool mf_desfire_free_memory_save(const MfDesfireFreeMemory* data, FlipperFormat* ff); + +bool mf_desfire_key_settings_save( + const MfDesfireKeySettings* data, + const char* prefix, + FlipperFormat* ff); + +bool mf_desfire_key_version_save( + const MfDesfireKeyVersion* data, + const char* prefix, + uint32_t index, + FlipperFormat* ff); + +bool mf_desfire_file_ids_save( + const MfDesfireFileId* data, + uint32_t count, + const char* prefix, + FlipperFormat* ff); + +bool mf_desfire_file_settings_save( + const MfDesfireFileSettings* data, + const char* prefix, + FlipperFormat* ff); + +bool mf_desfire_file_data_save( + const MfDesfireFileData* data, + const char* prefix, + FlipperFormat* ff); + +bool mf_desfire_application_count_save(const uint32_t* data, FlipperFormat* ff); + +bool mf_desfire_application_ids_save( + const MfDesfireApplicationId* data, + uint32_t count, + FlipperFormat* ff); + +bool mf_desfire_application_save( + const MfDesfireApplication* data, + const char* prefix, + FlipperFormat* ff); diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c b/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c new file mode 100644 index 00000000000..c74bbc89da6 --- /dev/null +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c @@ -0,0 +1,243 @@ +#include "mf_desfire_poller_i.h" + +#include + +#include + +#define TAG "MfDesfirePoller" + +#define MF_DESFIRE_BUF_SIZE_MAX (64U) + +typedef NfcCommand (*MfDesfirePollerReadHandler)(MfDesfirePoller* instance); + +const MfDesfireData* mf_desfire_poller_get_data(MfDesfirePoller* instance) { + furi_assert(instance); + + return instance->data; +} + +static MfDesfirePoller* mf_desfire_poller_alloc(Iso14443_4aPoller* iso14443_4a_poller) { + MfDesfirePoller* instance = malloc(sizeof(MfDesfirePoller)); + instance->iso14443_4a_poller = iso14443_4a_poller; + instance->data = mf_desfire_alloc(); + instance->tx_buffer = bit_buffer_alloc(MF_DESFIRE_BUF_SIZE_MAX); + instance->rx_buffer = bit_buffer_alloc(MF_DESFIRE_BUF_SIZE_MAX); + instance->input_buffer = bit_buffer_alloc(MF_DESFIRE_BUF_SIZE_MAX); + instance->result_buffer = bit_buffer_alloc(MF_DESFIRE_BUF_SIZE_MAX); + + instance->mf_desfire_event.data = &instance->mf_desfire_event_data; + + instance->general_event.protocol = NfcProtocolMfDesfire; + instance->general_event.event_data = &instance->mf_desfire_event; + instance->general_event.instance = instance; + + return instance; +} + +static void mf_desfire_poller_free(MfDesfirePoller* instance) { + furi_assert(instance); + + mf_desfire_free(instance->data); + bit_buffer_free(instance->tx_buffer); + bit_buffer_free(instance->rx_buffer); + bit_buffer_free(instance->input_buffer); + bit_buffer_free(instance->result_buffer); + free(instance); +} + +static NfcCommand mf_desfire_poller_handler_idle(MfDesfirePoller* instance) { + bit_buffer_reset(instance->input_buffer); + bit_buffer_reset(instance->result_buffer); + bit_buffer_reset(instance->tx_buffer); + bit_buffer_reset(instance->rx_buffer); + + iso14443_4a_copy( + instance->data->iso14443_4a_data, + iso14443_4a_poller_get_data(instance->iso14443_4a_poller)); + + instance->state = MfDesfirePollerStateReadVersion; + return NfcCommandContinue; +} + +static NfcCommand mf_desfire_poller_handler_read_version(MfDesfirePoller* instance) { + instance->error = mf_desfire_poller_async_read_version(instance, &instance->data->version); + if(instance->error == MfDesfireErrorNone) { + FURI_LOG_D(TAG, "Read version success"); + instance->state = MfDesfirePollerStateReadFreeMemory; + } else { + FURI_LOG_E(TAG, "Failed to read version"); + iso14443_4a_poller_halt(instance->iso14443_4a_poller); + instance->state = MfDesfirePollerStateReadFailed; + } + + return NfcCommandContinue; +} + +static NfcCommand mf_desfire_poller_handler_read_free_memory(MfDesfirePoller* instance) { + instance->error = + mf_desfire_poller_async_read_free_memory(instance, &instance->data->free_memory); + if(instance->error == MfDesfireErrorNone) { + FURI_LOG_D(TAG, "Read free memory success"); + instance->state = MfDesfirePollerStateReadMasterKeySettings; + } else { + FURI_LOG_E(TAG, "Failed to read free memory"); + iso14443_4a_poller_halt(instance->iso14443_4a_poller); + instance->state = MfDesfirePollerStateReadFailed; + } + + return NfcCommandContinue; +} + +static NfcCommand mf_desfire_poller_handler_read_master_key_settings(MfDesfirePoller* instance) { + instance->error = + mf_desfire_poller_async_read_key_settings(instance, &instance->data->master_key_settings); + if(instance->error == MfDesfireErrorNone) { + FURI_LOG_D(TAG, "Read master key settings success"); + instance->state = MfDesfirePollerStateReadMasterKeyVersion; + } else { + FURI_LOG_E(TAG, "Failed to read master key settings"); + iso14443_4a_poller_halt(instance->iso14443_4a_poller); + instance->state = MfDesfirePollerStateReadFailed; + } + + return NfcCommandContinue; +} + +static NfcCommand mf_desfire_poller_handler_read_master_key_version(MfDesfirePoller* instance) { + instance->error = mf_desfire_poller_async_read_key_versions( + instance, + instance->data->master_key_versions, + instance->data->master_key_settings.max_keys); + if(instance->error == MfDesfireErrorNone) { + FURI_LOG_D(TAG, "Read master key version success"); + instance->state = MfDesfirePollerStateReadApplicationIds; + } else { + FURI_LOG_E(TAG, "Failed to read master key version"); + iso14443_4a_poller_halt(instance->iso14443_4a_poller); + instance->state = MfDesfirePollerStateReadFailed; + } + + return NfcCommandContinue; +} + +static NfcCommand mf_desfire_poller_handler_read_application_ids(MfDesfirePoller* instance) { + instance->error = + mf_desfire_poller_async_read_application_ids(instance, instance->data->application_ids); + if(instance->error == MfDesfireErrorNone) { + FURI_LOG_D(TAG, "Read application ids success"); + instance->state = MfDesfirePollerStateReadApplications; + } else { + FURI_LOG_E(TAG, "Failed to read application ids"); + iso14443_4a_poller_halt(instance->iso14443_4a_poller); + instance->state = MfDesfirePollerStateReadFailed; + } + + return NfcCommandContinue; +} + +static NfcCommand mf_desfire_poller_handler_read_applications(MfDesfirePoller* instance) { + instance->error = mf_desfire_poller_async_read_applications( + instance, instance->data->application_ids, instance->data->applications); + if(instance->error == MfDesfireErrorNone) { + FURI_LOG_D(TAG, "Read applications success"); + instance->state = MfDesfirePollerStateReadSuccess; + } else { + FURI_LOG_E(TAG, "Failed to read applications"); + iso14443_4a_poller_halt(instance->iso14443_4a_poller); + instance->state = MfDesfirePollerStateReadFailed; + } + + return NfcCommandContinue; +} + +static NfcCommand mf_desfire_poller_handler_read_fail(MfDesfirePoller* instance) { + FURI_LOG_D(TAG, "Read Failed"); + iso14443_4a_poller_halt(instance->iso14443_4a_poller); + instance->mf_desfire_event.data->error = instance->error; + NfcCommand command = instance->callback(instance->general_event, instance->context); + instance->state = MfDesfirePollerStateIdle; + return command; +} + +static NfcCommand mf_desfire_poller_handler_read_success(MfDesfirePoller* instance) { + FURI_LOG_D(TAG, "Read success."); + iso14443_4a_poller_halt(instance->iso14443_4a_poller); + instance->mf_desfire_event.type = MfDesfirePollerEventTypeReadSuccess; + NfcCommand command = instance->callback(instance->general_event, instance->context); + return command; +} + +static const MfDesfirePollerReadHandler mf_desfire_poller_read_handler[MfDesfirePollerStateNum] = { + [MfDesfirePollerStateIdle] = mf_desfire_poller_handler_idle, + [MfDesfirePollerStateReadVersion] = mf_desfire_poller_handler_read_version, + [MfDesfirePollerStateReadFreeMemory] = mf_desfire_poller_handler_read_free_memory, + [MfDesfirePollerStateReadMasterKeySettings] = + mf_desfire_poller_handler_read_master_key_settings, + [MfDesfirePollerStateReadMasterKeyVersion] = mf_desfire_poller_handler_read_master_key_version, + [MfDesfirePollerStateReadApplicationIds] = mf_desfire_poller_handler_read_application_ids, + [MfDesfirePollerStateReadApplications] = mf_desfire_poller_handler_read_applications, + [MfDesfirePollerStateReadFailed] = mf_desfire_poller_handler_read_fail, + [MfDesfirePollerStateReadSuccess] = mf_desfire_poller_handler_read_success, +}; + +static void mf_desfire_poller_set_callback( + MfDesfirePoller* instance, + NfcGenericCallback callback, + void* context) { + furi_assert(instance); + furi_assert(callback); + + instance->callback = callback; + instance->context = context; +} + +static NfcCommand mf_desfire_poller_run(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolIso14443_4a); + + MfDesfirePoller* instance = context; + furi_assert(instance); + furi_assert(instance->callback); + + const Iso14443_4aPollerEvent* iso14443_4a_event = event.event_data; + furi_assert(iso14443_4a_event); + + NfcCommand command = NfcCommandContinue; + + if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeReady) { + command = mf_desfire_poller_read_handler[instance->state](instance); + } else if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeError) { + instance->mf_desfire_event.type = MfDesfirePollerEventTypeReadFailed; + command = instance->callback(instance->general_event, instance->context); + } + + return command; +} + +static bool mf_desfire_poller_detect(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolIso14443_4a); + + MfDesfirePoller* instance = context; + furi_assert(instance); + + const Iso14443_4aPollerEvent* iso14443_4a_event = event.event_data; + furi_assert(iso14443_4a_event); + + bool protocol_detected = false; + + if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeReady) { + MfDesfireVersion version = {}; + const MfDesfireError error = mf_desfire_poller_async_read_version(instance, &version); + protocol_detected = (error == MfDesfireErrorNone); + } + + return protocol_detected; +} + +const NfcPollerBase mf_desfire_poller = { + .alloc = (NfcPollerAlloc)mf_desfire_poller_alloc, + .free = (NfcPollerFree)mf_desfire_poller_free, + .set_callback = (NfcPollerSetCallback)mf_desfire_poller_set_callback, + .run = (NfcPollerRun)mf_desfire_poller_run, + .detect = (NfcPollerDetect)mf_desfire_poller_detect, + .get_data = (NfcPollerGetData)mf_desfire_poller_get_data, +}; diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_poller.h b/lib/nfc/protocols/mf_desfire/mf_desfire_poller.h new file mode 100644 index 00000000000..360b0508ff0 --- /dev/null +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_poller.h @@ -0,0 +1,31 @@ +#pragma once + +#include "mf_desfire.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct MfDesfirePoller MfDesfirePoller; + +typedef enum { + MfDesfirePollerEventTypeReadSuccess, + MfDesfirePollerEventTypeReadFailed, +} MfDesfirePollerEventType; + +typedef struct { + union { + MfDesfireError error; + }; +} MfDesfirePollerEventData; + +typedef struct { + MfDesfirePollerEventType type; + MfDesfirePollerEventData* data; +} MfDesfirePollerEvent; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_poller_defs.h b/lib/nfc/protocols/mf_desfire/mf_desfire_poller_defs.h new file mode 100644 index 00000000000..0c14ceee4d8 --- /dev/null +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_poller_defs.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +extern const NfcPollerBase mf_desfire_poller; diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c b/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c new file mode 100644 index 00000000000..e86cb4c68c2 --- /dev/null +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c @@ -0,0 +1,474 @@ +#include "mf_desfire_poller_i.h" + +#include + +#include "mf_desfire_i.h" + +#define TAG "MfDesfirePoller" + +MfDesfireError mf_desfire_process_error(Iso14443_4aError error) { + switch(error) { + case Iso14443_4aErrorNone: + return MfDesfireErrorNone; + case Iso14443_4aErrorNotPresent: + return MfDesfireErrorNotPresent; + case Iso14443_4aErrorTimeout: + return MfDesfireErrorTimeout; + default: + return MfDesfireErrorProtocol; + } +} + +MfDesfireError mf_desfire_send_chunks( + MfDesfirePoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer) { + furi_assert(instance); + furi_assert(instance->iso14443_4a_poller); + furi_assert(instance->tx_buffer); + furi_assert(instance->rx_buffer); + furi_assert(tx_buffer); + furi_assert(rx_buffer); + + MfDesfireError error = MfDesfireErrorNone; + + do { + Iso14443_4aError iso14443_4a_error = iso14443_4a_poller_send_block( + instance->iso14443_4a_poller, tx_buffer, instance->rx_buffer); + + if(iso14443_4a_error != Iso14443_4aErrorNone) { + error = mf_desfire_process_error(iso14443_4a_error); + break; + } + + bit_buffer_reset(instance->tx_buffer); + bit_buffer_append_byte(instance->tx_buffer, MF_DESFIRE_FLAG_HAS_NEXT); + + if(bit_buffer_get_size_bytes(instance->rx_buffer) > sizeof(uint8_t)) { + bit_buffer_copy_right(rx_buffer, instance->rx_buffer, sizeof(uint8_t)); + } else { + bit_buffer_reset(rx_buffer); + } + + while(bit_buffer_starts_with_byte(instance->rx_buffer, MF_DESFIRE_FLAG_HAS_NEXT)) { + Iso14443_4aError iso14443_4a_error = iso14443_4a_poller_send_block( + instance->iso14443_4a_poller, instance->tx_buffer, instance->rx_buffer); + + if(iso14443_4a_error != Iso14443_4aErrorNone) { + error = mf_desfire_process_error(iso14443_4a_error); + break; + } + + bit_buffer_append_right(rx_buffer, instance->rx_buffer, sizeof(uint8_t)); + } + } while(false); + + return error; +} + +MfDesfireError + mf_desfire_poller_async_read_version(MfDesfirePoller* instance, MfDesfireVersion* data) { + furi_assert(instance); + + bit_buffer_reset(instance->input_buffer); + bit_buffer_append_byte(instance->input_buffer, MF_DESFIRE_CMD_GET_VERSION); + + MfDesfireError error; + + do { + error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer); + + if(error != MfDesfireErrorNone) break; + + if(!mf_desfire_version_parse(data, instance->result_buffer)) { + error = MfDesfireErrorProtocol; + } + } while(false); + + return error; +} + +MfDesfireError + mf_desfire_poller_async_read_free_memory(MfDesfirePoller* instance, MfDesfireFreeMemory* data) { + furi_assert(instance); + + bit_buffer_reset(instance->input_buffer); + bit_buffer_append_byte(instance->input_buffer, MF_DESFIRE_CMD_GET_FREE_MEMORY); + + MfDesfireError error; + + do { + error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer); + + if(error != MfDesfireErrorNone) break; + + if(!mf_desfire_free_memory_parse(data, instance->result_buffer)) { + error = MfDesfireErrorProtocol; + } + } while(false); + + return error; +} + +MfDesfireError mf_desfire_poller_async_read_key_settings( + MfDesfirePoller* instance, + MfDesfireKeySettings* data) { + furi_assert(instance); + + bit_buffer_reset(instance->input_buffer); + bit_buffer_append_byte(instance->input_buffer, MF_DESFIRE_CMD_GET_KEY_SETTINGS); + + MfDesfireError error; + + do { + error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer); + + if(error != MfDesfireErrorNone) break; + + if(!mf_desfire_key_settings_parse(data, instance->result_buffer)) { + error = MfDesfireErrorProtocol; + } + } while(false); + + return error; +} + +MfDesfireError mf_desfire_poller_async_read_key_versions( + MfDesfirePoller* instance, + SimpleArray* data, + uint32_t count) { + furi_assert(instance); + furi_assert(count > 0); + + simple_array_init(data, count); + + bit_buffer_set_size_bytes(instance->input_buffer, sizeof(uint8_t) * 2); + bit_buffer_set_byte(instance->input_buffer, 0, MF_DESFIRE_CMD_GET_KEY_VERSION); + + MfDesfireError error = MfDesfireErrorNone; + + for(uint32_t i = 0; i < count; ++i) { + bit_buffer_set_byte(instance->input_buffer, 1, i); + + error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer); + + if(error != MfDesfireErrorNone) break; + + if(!mf_desfire_key_version_parse(simple_array_get(data, i), instance->result_buffer)) { + error = MfDesfireErrorProtocol; + break; + } + } + + return error; +} + +MfDesfireError + mf_desfire_poller_async_read_application_ids(MfDesfirePoller* instance, SimpleArray* data) { + furi_assert(instance); + + bit_buffer_reset(instance->input_buffer); + bit_buffer_append_byte(instance->input_buffer, MF_DESFIRE_CMD_GET_APPLICATION_IDS); + + MfDesfireError error; + + do { + error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer); + + if(error != MfDesfireErrorNone) break; + + const uint32_t app_id_count = + bit_buffer_get_size_bytes(instance->result_buffer) / sizeof(MfDesfireApplicationId); + if(app_id_count == 0) break; + + simple_array_init(data, app_id_count); + + for(uint32_t i = 0; i < app_id_count; ++i) { + if(!mf_desfire_application_id_parse( + simple_array_get(data, i), i, instance->result_buffer)) { + error = MfDesfireErrorProtocol; + break; + } + } + } while(false); + + return error; +} + +MfDesfireError mf_desfire_poller_async_select_application( + MfDesfirePoller* instance, + const MfDesfireApplicationId* id) { + furi_assert(instance); + + bit_buffer_reset(instance->input_buffer); + bit_buffer_append_byte(instance->input_buffer, MF_DESFIRE_CMD_SELECT_APPLICATION); + bit_buffer_append_bytes( + instance->input_buffer, (const uint8_t*)id, sizeof(MfDesfireApplicationId)); + + MfDesfireError error = + mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer); + + return error; +} + +MfDesfireError + mf_desfire_poller_async_read_file_ids(MfDesfirePoller* instance, SimpleArray* data) { + furi_assert(instance); + + bit_buffer_reset(instance->input_buffer); + bit_buffer_append_byte(instance->input_buffer, MF_DESFIRE_CMD_GET_FILE_IDS); + + MfDesfireError error; + + do { + error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer); + + if(error != MfDesfireErrorNone) break; + + const uint32_t id_count = + bit_buffer_get_size_bytes(instance->result_buffer) / sizeof(MfDesfireFileId); + + if(id_count == 0) break; + simple_array_init(data, id_count); + + for(uint32_t i = 0; i < id_count; ++i) { + if(!mf_desfire_file_id_parse(simple_array_get(data, i), i, instance->result_buffer)) { + error = MfDesfireErrorProtocol; + break; + } + } + } while(false); + + return error; +} + +MfDesfireError mf_desfire_poller_async_read_file_settings( + MfDesfirePoller* instance, + MfDesfireFileId id, + MfDesfireFileSettings* data) { + furi_assert(instance); + + bit_buffer_reset(instance->input_buffer); + bit_buffer_append_byte(instance->input_buffer, MF_DESFIRE_CMD_GET_FILE_SETTINGS); + bit_buffer_append_byte(instance->input_buffer, id); + + MfDesfireError error; + + do { + error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer); + + if(error != MfDesfireErrorNone) break; + + if(!mf_desfire_file_settings_parse(data, instance->result_buffer)) { + error = MfDesfireErrorProtocol; + } + } while(false); + + return error; +} + +MfDesfireError mf_desfire_poller_async_read_file_settings_multi( + MfDesfirePoller* instance, + const SimpleArray* file_ids, + SimpleArray* data) { + furi_assert(instance); + + MfDesfireError error = MfDesfireErrorNone; + + const uint32_t file_id_count = simple_array_get_count(file_ids); + if(file_id_count > 0) { + simple_array_init(data, file_id_count); + } + + for(uint32_t i = 0; i < file_id_count; ++i) { + const MfDesfireFileId file_id = *(const MfDesfireFileId*)simple_array_cget(file_ids, i); + error = mf_desfire_poller_async_read_file_settings( + instance, file_id, simple_array_get(data, i)); + if(error != MfDesfireErrorNone) break; + } + + return error; +} + +MfDesfireError mf_desfire_poller_async_read_file_data( + MfDesfirePoller* instance, + MfDesfireFileId id, + uint32_t offset, + size_t size, + MfDesfireFileData* data) { + furi_assert(instance); + + bit_buffer_reset(instance->input_buffer); + bit_buffer_append_byte(instance->input_buffer, MF_DESFIRE_CMD_READ_DATA); + bit_buffer_append_byte(instance->input_buffer, id); + bit_buffer_append_bytes(instance->input_buffer, (const uint8_t*)&offset, 3); + bit_buffer_append_bytes(instance->input_buffer, (const uint8_t*)&size, 3); + + MfDesfireError error; + + do { + error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer); + + if(error != MfDesfireErrorNone) break; + + if(!mf_desfire_file_data_parse(data, instance->result_buffer)) { + error = MfDesfireErrorProtocol; + } + } while(false); + + return error; +} + +MfDesfireError mf_desfire_poller_async_read_file_value( + MfDesfirePoller* instance, + MfDesfireFileId id, + MfDesfireFileData* data) { + furi_assert(instance); + + bit_buffer_reset(instance->input_buffer); + bit_buffer_append_byte(instance->input_buffer, MF_DESFIRE_CMD_GET_VALUE); + bit_buffer_append_byte(instance->input_buffer, id); + + MfDesfireError error; + + do { + error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer); + + if(error != MfDesfireErrorNone) break; + + if(!mf_desfire_file_data_parse(data, instance->result_buffer)) { + error = MfDesfireErrorProtocol; + } + } while(false); + + return error; +} + +MfDesfireError mf_desfire_poller_async_read_file_records( + MfDesfirePoller* instance, + MfDesfireFileId id, + uint32_t offset, + size_t size, + MfDesfireFileData* data) { + furi_assert(instance); + + bit_buffer_reset(instance->input_buffer); + bit_buffer_append_byte(instance->input_buffer, MF_DESFIRE_CMD_READ_DATA); + bit_buffer_append_byte(instance->input_buffer, id); + bit_buffer_append_bytes(instance->input_buffer, (const uint8_t*)&offset, 3); + bit_buffer_append_bytes(instance->input_buffer, (const uint8_t*)&size, 3); + + MfDesfireError error; + + do { + error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer); + + if(error != MfDesfireErrorNone) break; + + if(!mf_desfire_file_data_parse(data, instance->result_buffer)) { + error = MfDesfireErrorProtocol; + } + } while(false); + + return error; +} + +MfDesfireError mf_desfire_poller_async_read_file_data_multi( + MfDesfirePoller* instance, + const SimpleArray* file_ids, + const SimpleArray* file_settings, + SimpleArray* data) { + furi_assert(instance); + furi_assert(simple_array_get_count(file_ids) == simple_array_get_count(file_settings)); + + MfDesfireError error = MfDesfireErrorNone; + + const uint32_t file_id_count = simple_array_get_count(file_ids); + if(file_id_count > 0) { + simple_array_init(data, file_id_count); + } + + for(uint32_t i = 0; i < file_id_count; ++i) { + const MfDesfireFileId file_id = *(const MfDesfireFileId*)simple_array_cget(file_ids, i); + const MfDesfireFileSettings* file_settings_cur = simple_array_cget(file_settings, i); + const MfDesfireFileType file_type = file_settings_cur->type; + + MfDesfireFileData* file_data = simple_array_get(data, i); + + if(file_type == MfDesfireFileTypeStandard || file_type == MfDesfireFileTypeBackup) { + error = mf_desfire_poller_async_read_file_data( + instance, file_id, 0, file_settings_cur->data.size, file_data); + } else if(file_type == MfDesfireFileTypeValue) { + error = mf_desfire_poller_async_read_file_value(instance, file_id, file_data); + } else if( + file_type == MfDesfireFileTypeLinearRecord || + file_type == MfDesfireFileTypeCyclicRecord) { + error = mf_desfire_poller_async_read_file_records( + instance, file_id, 0, file_settings_cur->data.size, file_data); + } + + if(error != MfDesfireErrorNone) break; + } + + return error; +} + +MfDesfireError mf_desfire_poller_async_read_application( + MfDesfirePoller* instance, + MfDesfireApplication* data) { + furi_assert(instance); + furi_assert(data); + + MfDesfireError error; + + do { + error = mf_desfire_poller_async_read_key_settings(instance, &data->key_settings); + if(error != MfDesfireErrorNone) break; + + error = mf_desfire_poller_async_read_key_versions( + instance, data->key_versions, data->key_settings.max_keys); + if(error != MfDesfireErrorNone) break; + + error = mf_desfire_poller_async_read_file_ids(instance, data->file_ids); + if(error != MfDesfireErrorNone) break; + + error = mf_desfire_poller_async_read_file_settings_multi( + instance, data->file_ids, data->file_settings); + if(error != MfDesfireErrorNone) break; + + error = mf_desfire_poller_async_read_file_data_multi( + instance, data->file_ids, data->file_settings, data->file_data); + if(error != MfDesfireErrorNone) break; + + } while(false); + + return error; +} + +MfDesfireError mf_desfire_poller_async_read_applications( + MfDesfirePoller* instance, + const SimpleArray* app_ids, + SimpleArray* data) { + furi_assert(instance); + + MfDesfireError error = MfDesfireErrorNone; + + const uint32_t app_id_count = simple_array_get_count(app_ids); + if(app_id_count > 0) { + simple_array_init(data, app_id_count); + } + + for(uint32_t i = 0; i < app_id_count; ++i) { + do { + error = mf_desfire_poller_async_select_application( + instance, simple_array_cget(app_ids, i)); + if(error != MfDesfireErrorNone) break; + + MfDesfireApplication* current_app = simple_array_get(data, i); + error = mf_desfire_poller_async_read_application(instance, current_app); + + } while(false); + } + + return error; +} diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.h b/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.h new file mode 100644 index 00000000000..abc48d0eb59 --- /dev/null +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.h @@ -0,0 +1,126 @@ +#pragma once + +#include "mf_desfire_poller.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + MfDesfirePollerStateIdle, + MfDesfirePollerStateReadVersion, + MfDesfirePollerStateReadFreeMemory, + MfDesfirePollerStateReadMasterKeySettings, + MfDesfirePollerStateReadMasterKeyVersion, + MfDesfirePollerStateReadApplicationIds, + MfDesfirePollerStateReadApplications, + MfDesfirePollerStateReadFailed, + MfDesfirePollerStateReadSuccess, + + MfDesfirePollerStateNum, +} MfDesfirePollerState; + +typedef enum { + MfDesfirePollerSessionStateIdle, + MfDesfirePollerSessionStateActive, + MfDesfirePollerSessionStateStopRequest, +} MfDesfirePollerSessionState; + +struct MfDesfirePoller { + Iso14443_4aPoller* iso14443_4a_poller; + MfDesfirePollerSessionState session_state; + MfDesfirePollerState state; + MfDesfireError error; + MfDesfireData* data; + BitBuffer* tx_buffer; + BitBuffer* rx_buffer; + BitBuffer* input_buffer; + BitBuffer* result_buffer; + MfDesfirePollerEventData mf_desfire_event_data; + MfDesfirePollerEvent mf_desfire_event; + NfcGenericEvent general_event; + NfcGenericCallback callback; + void* context; +}; + +MfDesfireError mf_desfire_process_error(Iso14443_4aError error); + +const MfDesfireData* mf_desfire_poller_get_data(MfDesfirePoller* instance); + +MfDesfireError mf_desfire_send_chunks( + MfDesfirePoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer); + +MfDesfireError + mf_desfire_poller_async_read_version(MfDesfirePoller* instance, MfDesfireVersion* data); + +MfDesfireError + mf_desfire_poller_async_read_free_memory(MfDesfirePoller* instance, MfDesfireFreeMemory* data); + +MfDesfireError mf_desfire_poller_async_read_key_settings( + MfDesfirePoller* instance, + MfDesfireKeySettings* data); + +MfDesfireError mf_desfire_poller_async_read_key_versions( + MfDesfirePoller* instance, + SimpleArray* data, + uint32_t count); + +MfDesfireError + mf_desfire_poller_async_read_application_ids(MfDesfirePoller* instance, SimpleArray* data); + +MfDesfireError mf_desfire_poller_async_select_application( + MfDesfirePoller* instance, + const MfDesfireApplicationId* id); + +MfDesfireError mf_desfire_poller_async_read_file_ids(MfDesfirePoller* instance, SimpleArray* data); + +MfDesfireError mf_desfire_poller_async_read_file_settings( + MfDesfirePoller* instance, + MfDesfireFileId id, + MfDesfireFileSettings* data); + +MfDesfireError mf_desfire_poller_async_read_file_settings_multi( + MfDesfirePoller* instance, + const SimpleArray* file_ids, + SimpleArray* data); + +MfDesfireError mf_desfire_poller_async_read_file_data( + MfDesfirePoller* instance, + MfDesfireFileId id, + uint32_t offset, + size_t size, + MfDesfireFileData* data); + +MfDesfireError mf_desfire_poller_async_read_file_value( + MfDesfirePoller* instance, + MfDesfireFileId id, + MfDesfireFileData* data); + +MfDesfireError mf_desfire_poller_async_read_file_records( + MfDesfirePoller* instance, + MfDesfireFileId id, + uint32_t offset, + size_t size, + MfDesfireFileData* data); + +MfDesfireError mf_desfire_poller_async_read_file_data_multi( + MfDesfirePoller* instance, + const SimpleArray* file_ids, + const SimpleArray* file_settings, + SimpleArray* data); + +MfDesfireError + mf_desfire_poller_async_read_application(MfDesfirePoller* instance, MfDesfireApplication* data); + +MfDesfireError mf_desfire_poller_async_read_applications( + MfDesfirePoller* instance, + const SimpleArray* app_ids, + SimpleArray* data); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight.c new file mode 100644 index 00000000000..40d99768636 --- /dev/null +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight.c @@ -0,0 +1,627 @@ +#include "mf_ultralight.h" + +#include +#include + +#define MF_ULTRALIGHT_PROTOCOL_NAME "NTAG/Ultralight" + +#define MF_ULTRALIGHT_FORMAT_VERSION_KEY "Data format version" +#define MF_ULTRALIGHT_TYPE_KEY MF_ULTRALIGHT_PROTOCOL_NAME " type" +#define MF_ULTRALIGHT_SIGNATURE_KEY "Signature" +#define MF_ULTRALIGHT_MIFARE_VERSION_KEY "Mifare version" +#define MF_ULTRALIGHT_COUNTER_KEY "Counter" +#define MF_ULTRALIGHT_TEARING_KEY "Tearing" +#define MF_ULTRALIGHT_PAGES_TOTAL_KEY "Pages total" +#define MF_ULTRALIGHT_PAGES_READ_KEY "Pages read" +#define MF_ULTRALIGHT_PAGE_KEY "Page" +#define MF_ULTRALIGHT_FAILED_ATTEMPTS_KEY "Failed authentication attempts" + +typedef struct { + const char* device_name; + uint16_t total_pages; + uint16_t config_page; + uint32_t feature_set; +} MfUltralightFeatures; + +static const uint32_t mf_ultralight_data_format_version = 2; + +static const MfUltralightFeatures mf_ultralight_features[MfUltralightTypeNum] = { + [MfUltralightTypeUnknown] = + { + .device_name = "Mifare Ultralight", + .total_pages = 16, + .config_page = 0, + .feature_set = MfUltralightFeatureSupportCompatibleWrite, + }, + [MfUltralightTypeMfulC] = + { + .device_name = "Mifare Ultralight C", + .total_pages = 48, + .config_page = 0, + .feature_set = MfUltralightFeatureSupportCompatibleWrite | + MfUltralightFeatureSupportAuthenticate, + }, + [MfUltralightTypeNTAG203] = + { + .device_name = "NTAG203", + .total_pages = 42, + .config_page = 0, + .feature_set = MfUltralightFeatureSupportCompatibleWrite | + MfUltralightFeatureSupportCounterInMemory, + }, + [MfUltralightTypeUL11] = + { + .device_name = "Mifare Ultralight 11", + .total_pages = 20, + .config_page = 16, + .feature_set = + MfUltralightFeatureSupportReadVersion | MfUltralightFeatureSupportReadSignature | + MfUltralightFeatureSupportReadCounter | + MfUltralightFeatureSupportCheckTearingFlag | MfUltralightFeatureSupportFastRead | + MfUltralightFeatureSupportIncCounter | MfUltralightFeatureSupportCompatibleWrite | + MfUltralightFeatureSupportPasswordAuth | MfUltralightFeatureSupportVcsl, + }, + [MfUltralightTypeUL21] = + { + .device_name = "Mifare Ultralight 21", + .total_pages = 41, + .config_page = 37, + .feature_set = + MfUltralightFeatureSupportReadVersion | MfUltralightFeatureSupportReadSignature | + MfUltralightFeatureSupportReadCounter | + MfUltralightFeatureSupportCheckTearingFlag | MfUltralightFeatureSupportFastRead | + MfUltralightFeatureSupportIncCounter | MfUltralightFeatureSupportCompatibleWrite | + MfUltralightFeatureSupportPasswordAuth | MfUltralightFeatureSupportVcsl | + MfUltralightFeatureSupportDynamicLock, + }, + [MfUltralightTypeNTAG213] = + { + .device_name = "NTAG213", + .total_pages = 45, + .config_page = 41, + .feature_set = + MfUltralightFeatureSupportReadVersion | MfUltralightFeatureSupportReadSignature | + MfUltralightFeatureSupportReadCounter | MfUltralightFeatureSupportFastRead | + MfUltralightFeatureSupportCompatibleWrite | + MfUltralightFeatureSupportPasswordAuth | MfUltralightFeatureSupportSingleCounter | + MfUltralightFeatureSupportAsciiMirror | MfUltralightFeatureSupportDynamicLock, + }, + [MfUltralightTypeNTAG215] = + { + .device_name = "NTAG215", + .total_pages = 135, + .config_page = 131, + .feature_set = + MfUltralightFeatureSupportReadVersion | MfUltralightFeatureSupportReadSignature | + MfUltralightFeatureSupportReadCounter | MfUltralightFeatureSupportFastRead | + MfUltralightFeatureSupportCompatibleWrite | + MfUltralightFeatureSupportPasswordAuth | MfUltralightFeatureSupportSingleCounter | + MfUltralightFeatureSupportAsciiMirror | MfUltralightFeatureSupportDynamicLock, + }, + [MfUltralightTypeNTAG216] = + { + .device_name = "NTAG216", + .total_pages = 231, + .config_page = 227, + .feature_set = + MfUltralightFeatureSupportReadVersion | MfUltralightFeatureSupportReadSignature | + MfUltralightFeatureSupportReadCounter | MfUltralightFeatureSupportFastRead | + MfUltralightFeatureSupportCompatibleWrite | + MfUltralightFeatureSupportPasswordAuth | MfUltralightFeatureSupportSingleCounter | + MfUltralightFeatureSupportAsciiMirror | MfUltralightFeatureSupportDynamicLock, + }, + [MfUltralightTypeNTAGI2C1K] = + { + .device_name = "NTAG I2C 1K", + .total_pages = 231, + .config_page = 0, + .feature_set = + MfUltralightFeatureSupportReadVersion | MfUltralightFeatureSupportFastRead | + MfUltralightFeatureSupportSectorSelect | MfUltralightFeatureSupportDynamicLock, + }, + [MfUltralightTypeNTAGI2C2K] = + { + .device_name = "NTAG I2C 2K", + .total_pages = 485, + .config_page = 0, + .feature_set = + MfUltralightFeatureSupportReadVersion | MfUltralightFeatureSupportFastRead | + MfUltralightFeatureSupportSectorSelect | MfUltralightFeatureSupportDynamicLock, + }, + [MfUltralightTypeNTAGI2CPlus1K] = + { + .device_name = "NTAG I2C Plus 1K", + .total_pages = 236, + .config_page = 227, + .feature_set = + MfUltralightFeatureSupportReadVersion | MfUltralightFeatureSupportReadSignature | + MfUltralightFeatureSupportFastRead | MfUltralightFeatureSupportPasswordAuth | + MfUltralightFeatureSupportSectorSelect | MfUltralightFeatureSupportFastWrite | + MfUltralightFeatureSupportDynamicLock, + }, + [MfUltralightTypeNTAGI2CPlus2K] = + { + .device_name = "NTAG I2C Plus 2K", + .total_pages = 492, + .config_page = 227, + .feature_set = + MfUltralightFeatureSupportReadVersion | MfUltralightFeatureSupportReadSignature | + MfUltralightFeatureSupportFastRead | MfUltralightFeatureSupportPasswordAuth | + MfUltralightFeatureSupportSectorSelect | MfUltralightFeatureSupportFastWrite | + MfUltralightFeatureSupportDynamicLock, + }, +}; + +const NfcDeviceBase nfc_device_mf_ultralight = { + .protocol_name = MF_ULTRALIGHT_PROTOCOL_NAME, + .alloc = (NfcDeviceAlloc)mf_ultralight_alloc, + .free = (NfcDeviceFree)mf_ultralight_free, + .reset = (NfcDeviceReset)mf_ultralight_reset, + .copy = (NfcDeviceCopy)mf_ultralight_copy, + .verify = (NfcDeviceVerify)mf_ultralight_verify, + .load = (NfcDeviceLoad)mf_ultralight_load, + .save = (NfcDeviceSave)mf_ultralight_save, + .is_equal = (NfcDeviceEqual)mf_ultralight_is_equal, + .get_name = (NfcDeviceGetName)mf_ultralight_get_device_name, + .get_uid = (NfcDeviceGetUid)mf_ultralight_get_uid, + .set_uid = (NfcDeviceSetUid)mf_ultralight_set_uid, + .get_base_data = (NfcDeviceGetBaseData)mf_ultralight_get_base_data, +}; + +MfUltralightData* mf_ultralight_alloc() { + MfUltralightData* data = malloc(sizeof(MfUltralightData)); + data->iso14443_3a_data = iso14443_3a_alloc(); + return data; +} + +void mf_ultralight_free(MfUltralightData* data) { + furi_assert(data); + + iso14443_3a_free(data->iso14443_3a_data); + free(data); +} + +void mf_ultralight_reset(MfUltralightData* data) { + furi_assert(data); + + iso14443_3a_reset(data->iso14443_3a_data); +} + +void mf_ultralight_copy(MfUltralightData* data, const MfUltralightData* other) { + furi_assert(data); + furi_assert(other); + + iso14443_3a_copy(data->iso14443_3a_data, other->iso14443_3a_data); + for(size_t i = 0; i < COUNT_OF(data->counter); i++) { + data->counter[i] = other->counter[i]; + } + for(size_t i = 0; i < COUNT_OF(data->tearing_flag); i++) { + data->tearing_flag[i] = other->tearing_flag[i]; + } + for(size_t i = 0; i < COUNT_OF(data->page); i++) { + data->page[i] = other->page[i]; + } + + data->type = other->type; + data->version = other->version; + data->signature = other->signature; + + data->pages_read = other->pages_read; + data->pages_total = other->pages_total; + data->auth_attempts = other->auth_attempts; +} + +static const char* + mf_ultralight_get_device_name_by_type(MfUltralightType type, NfcDeviceNameType name_type) { + if(name_type == NfcDeviceNameTypeShort && + (type == MfUltralightTypeUL11 || type == MfUltralightTypeUL21)) { + type = MfUltralightTypeUnknown; + } + + return mf_ultralight_features[type].device_name; +} + +bool mf_ultralight_verify(MfUltralightData* data, const FuriString* device_type) { + furi_assert(data); + + bool verified = false; + + for(MfUltralightType i = 0; i < MfUltralightTypeNum; i++) { + const char* name = mf_ultralight_get_device_name_by_type(i, NfcDeviceNameTypeFull); + verified = furi_string_equal(device_type, name); + if(verified) { + data->type = i; + break; + } + } + + return verified; +} + +bool mf_ultralight_load(MfUltralightData* data, FlipperFormat* ff, uint32_t version) { + furi_assert(data); + + FuriString* temp_str = furi_string_alloc(); + bool parsed = false; + + do { + // Read ISO14443_3A data + if(!iso14443_3a_load(data->iso14443_3a_data, ff, version)) break; + + // Read Ultralight specific data + // Read Mifare Ultralight format version + uint32_t data_format_version = 0; + if(!flipper_format_read_uint32( + ff, MF_ULTRALIGHT_FORMAT_VERSION_KEY, &data_format_version, 1)) { + if(!flipper_format_rewind(ff)) break; + } + + // Read Mifare Ultralight type + if(data_format_version > 1) { + if(!flipper_format_read_string(ff, MF_ULTRALIGHT_TYPE_KEY, temp_str)) break; + if(!mf_ultralight_verify(data, temp_str)) break; + } + + // Read signature + if(!flipper_format_read_hex( + ff, + MF_ULTRALIGHT_SIGNATURE_KEY, + data->signature.data, + sizeof(MfUltralightSignature))) + break; + // Read Mifare version + if(!flipper_format_read_hex( + ff, + MF_ULTRALIGHT_MIFARE_VERSION_KEY, + (uint8_t*)&data->version, + sizeof(MfUltralightVersion))) + break; + // Read counters and tearing flags + bool counters_parsed = true; + for(size_t i = 0; i < 3; i++) { + furi_string_printf(temp_str, "%s %d", MF_ULTRALIGHT_COUNTER_KEY, i); + if(!flipper_format_read_uint32( + ff, furi_string_get_cstr(temp_str), &data->counter[i].counter, 1)) { + counters_parsed = false; + break; + } + furi_string_printf(temp_str, "%s %d", MF_ULTRALIGHT_TEARING_KEY, i); + if(!flipper_format_read_hex( + ff, furi_string_get_cstr(temp_str), &data->tearing_flag[i].data, 1)) { + counters_parsed = false; + break; + } + } + if(!counters_parsed) break; + // Read pages + uint32_t pages_total = 0; + if(!flipper_format_read_uint32(ff, MF_ULTRALIGHT_PAGES_TOTAL_KEY, &pages_total, 1)) break; + uint32_t pages_read = 0; + if(data_format_version < mf_ultralight_data_format_version) { + pages_read = pages_total; + } else { + if(!flipper_format_read_uint32(ff, MF_ULTRALIGHT_PAGES_READ_KEY, &pages_read, 1)) + break; + } + data->pages_total = pages_total; + data->pages_read = pages_read; + + if((pages_read > MF_ULTRALIGHT_MAX_PAGE_NUM) || (pages_total > MF_ULTRALIGHT_MAX_PAGE_NUM)) + break; + + bool pages_parsed = true; + for(size_t i = 0; i < pages_total; i++) { + furi_string_printf(temp_str, "%s %d", MF_ULTRALIGHT_PAGE_KEY, i); + if(!flipper_format_read_hex( + ff, + furi_string_get_cstr(temp_str), + data->page[i].data, + sizeof(MfUltralightPage))) { + pages_parsed = false; + break; + } + } + if(!pages_parsed) break; + + // Read authentication counter + if(!flipper_format_read_uint32( + ff, MF_ULTRALIGHT_FAILED_ATTEMPTS_KEY, &data->auth_attempts, 1)) { + data->auth_attempts = 0; + } + + parsed = true; + } while(false); + + furi_string_free(temp_str); + + return parsed; +} + +bool mf_ultralight_save(const MfUltralightData* data, FlipperFormat* ff) { + furi_assert(data); + + FuriString* temp_str = furi_string_alloc(); + bool saved = false; + + do { + if(!iso14443_3a_save(data->iso14443_3a_data, ff)) break; + + if(!flipper_format_write_comment_cstr(ff, MF_ULTRALIGHT_PROTOCOL_NAME " specific data")) + break; + if(!flipper_format_write_uint32( + ff, MF_ULTRALIGHT_FORMAT_VERSION_KEY, &mf_ultralight_data_format_version, 1)) + break; + + const char* device_type_name = + mf_ultralight_get_device_name_by_type(data->type, NfcDeviceNameTypeFull); + if(!flipper_format_write_string_cstr(ff, MF_ULTRALIGHT_TYPE_KEY, device_type_name)) break; + if(!flipper_format_write_hex( + ff, + MF_ULTRALIGHT_SIGNATURE_KEY, + data->signature.data, + sizeof(MfUltralightSignature))) + break; + if(!flipper_format_write_hex( + ff, + MF_ULTRALIGHT_MIFARE_VERSION_KEY, + (uint8_t*)&data->version, + sizeof(MfUltralightVersion))) + break; + + // Write conters and tearing flags data + bool counters_saved = true; + for(size_t i = 0; i < 3; i++) { + furi_string_printf(temp_str, "%s %d", MF_ULTRALIGHT_COUNTER_KEY, i); + if(!flipper_format_write_uint32( + ff, furi_string_get_cstr(temp_str), &data->counter[i].counter, 1)) { + counters_saved = false; + break; + } + furi_string_printf(temp_str, "%s %d", MF_ULTRALIGHT_TEARING_KEY, i); + if(!flipper_format_write_hex( + ff, furi_string_get_cstr(temp_str), &data->tearing_flag[i].data, 1)) { + counters_saved = false; + break; + } + } + if(!counters_saved) break; + + // Write pages data + uint32_t pages_total = data->pages_total; + uint32_t pages_read = data->pages_read; + if(!flipper_format_write_uint32(ff, MF_ULTRALIGHT_PAGES_TOTAL_KEY, &pages_total, 1)) break; + if(!flipper_format_write_uint32(ff, MF_ULTRALIGHT_PAGES_READ_KEY, &pages_read, 1)) break; + bool pages_saved = true; + for(size_t i = 0; i < data->pages_total; i++) { + furi_string_printf(temp_str, "%s %d", MF_ULTRALIGHT_PAGE_KEY, i); + if(!flipper_format_write_hex( + ff, + furi_string_get_cstr(temp_str), + data->page[i].data, + sizeof(MfUltralightPage))) { + pages_saved = false; + break; + } + } + if(!pages_saved) break; + + // Write authentication counter + if(!flipper_format_write_uint32( + ff, MF_ULTRALIGHT_FAILED_ATTEMPTS_KEY, &data->auth_attempts, 1)) + break; + + saved = true; + } while(false); + + furi_string_free(temp_str); + + return saved; +} + +bool mf_ultralight_is_equal(const MfUltralightData* data, const MfUltralightData* other) { + bool is_equal = false; + bool data_array_is_equal = true; + + do { + if(!iso14443_3a_is_equal(data->iso14443_3a_data, other->iso14443_3a_data)) break; + if(data->type != other->type) break; + if(data->pages_read != other->pages_read) break; + if(data->pages_total != other->pages_total) break; + if(data->auth_attempts != other->auth_attempts) break; + + if(memcmp(&data->version, &other->version, sizeof(data->version)) != 0) break; + if(memcmp(&data->signature, &other->signature, sizeof(data->signature)) != 0) break; + + for(size_t i = 0; i < COUNT_OF(data->counter); i++) { + if(memcmp(&data->counter[i], &other->counter[i], sizeof(data->counter[i])) != 0) { + data_array_is_equal = false; + break; + } + } + if(!data_array_is_equal) break; + + for(size_t i = 0; i < COUNT_OF(data->tearing_flag); i++) { + if(memcmp( + &data->tearing_flag[i], + &other->tearing_flag[i], + sizeof(data->tearing_flag[i])) != 0) { + data_array_is_equal = false; + break; + } + } + if(!data_array_is_equal) break; + + for(size_t i = 0; i < COUNT_OF(data->page); i++) { + if(memcmp(&data->page[i], &other->page[i], sizeof(data->page[i])) != 0) { + data_array_is_equal = false; + break; + } + } + if(!data_array_is_equal) break; + + is_equal = true; + } while(false); + + return is_equal; +} + +const char* + mf_ultralight_get_device_name(const MfUltralightData* data, NfcDeviceNameType name_type) { + furi_assert(data); + furi_assert(data->type < MfUltralightTypeNum); + + return mf_ultralight_get_device_name_by_type(data->type, name_type); +} + +const uint8_t* mf_ultralight_get_uid(const MfUltralightData* data, size_t* uid_len) { + furi_assert(data); + + return iso14443_3a_get_uid(data->iso14443_3a_data, uid_len); +} + +bool mf_ultralight_set_uid(MfUltralightData* data, const uint8_t* uid, size_t uid_len) { + furi_assert(data); + + return iso14443_3a_set_uid(data->iso14443_3a_data, uid, uid_len); +} + +Iso14443_3aData* mf_ultralight_get_base_data(const MfUltralightData* data) { + furi_assert(data); + + return data->iso14443_3a_data; +} + +MfUltralightType mf_ultralight_get_type_by_version(MfUltralightVersion* version) { + furi_assert(version); + + MfUltralightType type = MfUltralightTypeUnknown; + + if(version->storage_size == 0x0B || version->storage_size == 0x00) { + type = MfUltralightTypeUL11; + } else if(version->storage_size == 0x0E) { + type = MfUltralightTypeUL21; + } else if(version->storage_size == 0x0F) { + type = MfUltralightTypeNTAG213; + } else if(version->storage_size == 0x11) { + type = MfUltralightTypeNTAG215; + } else if(version->prod_subtype == 5 && version->prod_ver_major == 2) { + if(version->prod_ver_minor == 1) { + if(version->storage_size == 0x13) { + type = MfUltralightTypeNTAGI2C1K; + } else if(version->storage_size == 0x15) { + type = MfUltralightTypeNTAGI2C2K; + } + } else if(version->prod_ver_minor == 2) { + if(version->storage_size == 0x13) { + type = MfUltralightTypeNTAGI2CPlus1K; + } else if(version->storage_size == 0x15) { + type = MfUltralightTypeNTAGI2CPlus2K; + } + } + } else if(version->storage_size == 0x13) { + type = MfUltralightTypeNTAG216; + } + + return type; +} + +uint16_t mf_ultralight_get_pages_total(MfUltralightType type) { + return mf_ultralight_features[type].total_pages; +} + +uint32_t mf_ultralight_get_feature_support_set(MfUltralightType type) { + return mf_ultralight_features[type].feature_set; +} + +bool mf_ultralight_detect_protocol(const Iso14443_3aData* iso14443_3a_data) { + furi_assert(iso14443_3a_data); + + bool mfu_detected = (iso14443_3a_data->atqa[0] == 0x44) && + (iso14443_3a_data->atqa[1] == 0x00) && (iso14443_3a_data->sak == 0x00); + + return mfu_detected; +} + +uint16_t mf_ultralight_get_config_page_num(MfUltralightType type) { + return mf_ultralight_features[type].config_page; +} + +uint8_t mf_ultralight_get_pwd_page_num(MfUltralightType type) { + uint8_t config_page = mf_ultralight_features[type].config_page; + return (config_page != 0) ? config_page + 2 : 0; +} + +bool mf_ultralight_is_page_pwd_or_pack(MfUltralightType type, uint16_t page) { + uint8_t pwd_page = mf_ultralight_get_pwd_page_num(type); + uint8_t pack_page = pwd_page + 1; + return ((pwd_page != 0) && (page == pwd_page || page == pack_page)); +} + +bool mf_ultralight_support_feature(const uint32_t feature_set, const uint32_t features_to_check) { + return (feature_set & features_to_check) != 0; +} + +bool mf_ultralight_get_config_page(const MfUltralightData* data, MfUltralightConfigPages** config) { + furi_assert(data); + furi_assert(config); + + bool config_pages_found = false; + + uint16_t config_page = mf_ultralight_features[data->type].config_page; + if(config_page != 0) { + *config = (MfUltralightConfigPages*)&data->page[config_page]; //-V1027 + config_pages_found = true; + } + + return config_pages_found; +} + +bool mf_ultralight_is_all_data_read(const MfUltralightData* data) { + furi_assert(data); + + bool all_read = false; + if(data->pages_read == data->pages_total || + (data->type == MfUltralightTypeMfulC && data->pages_read == data->pages_total - 4)) { + // Having read all the pages doesn't mean that we've got everything. + // By default PWD is 0xFFFFFFFF, but if read back it is always 0x00000000, + // so a default read on an auth-supported NTAG is never complete. + uint32_t feature_set = mf_ultralight_get_feature_support_set(data->type); + if(!mf_ultralight_support_feature(feature_set, MfUltralightFeatureSupportPasswordAuth)) { + all_read = true; + } else { + MfUltralightConfigPages* config = NULL; + if(mf_ultralight_get_config_page(data, &config)) { + uint32_t pass = + nfc_util_bytes2num(config->password.data, sizeof(MfUltralightAuthPassword)); + uint16_t pack = + nfc_util_bytes2num(config->pack.data, sizeof(MfUltralightAuthPack)); + all_read = ((pass != 0) || (pack != 0)); + } + } + } + + return all_read; +} + +bool mf_ultralight_is_counter_configured(const MfUltralightData* data) { + furi_assert(data); + + MfUltralightConfigPages* config = NULL; + bool configured = false; + + switch(data->type) { + case MfUltralightTypeNTAG213: + case MfUltralightTypeNTAG215: + case MfUltralightTypeNTAG216: + if(mf_ultralight_get_config_page(data, &config)) { + configured = config->access.nfc_cnt_en; + } + break; + + default: + configured = true; + break; + } + + return configured; +} diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight.h b/lib/nfc/protocols/mf_ultralight/mf_ultralight.h new file mode 100644 index 00000000000..747f5937ad2 --- /dev/null +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight.h @@ -0,0 +1,229 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define MF_ULTRALIGHT_TEARING_FLAG_DEFAULT (0xBD) + +#define MF_ULTRALIGHT_CMD_GET_VERSION (0x60) +#define MF_ULTRALIGHT_CMD_READ_PAGE (0x30) +#define MF_ULTRALIGHT_CMD_FAST_READ (0x3A) +#define MF_ULTRALIGHT_CMD_SECTOR_SELECT (0xC2) +#define MF_ULTRALIGHT_CMD_COMP_WRITE (0xA0) +#define MF_ULTRALIGHT_CMD_WRITE_PAGE (0xA2) +#define MF_ULTRALIGHT_CMD_FAST_WRITE (0xA6) +#define MF_ULTRALIGHT_CMD_READ_SIG (0x3C) +#define MF_ULTRALIGHT_CMD_READ_CNT (0x39) +#define MF_ULTRALIGHT_CMD_INCR_CNT (0xA5) +#define MF_ULTRALIGHT_CMD_CHECK_TEARING (0x3E) +#define MF_ULTRALIGHT_CMD_PWD_AUTH (0x1B) +#define MF_ULTRALIGHT_CMD_AUTH (0x1A) +#define MF_ULTRALIGHT_CMD_VCSL (0x4B) + +#define MF_ULTRALIGHT_CMD_ACK (0x0A) +#define MF_ULTRALIGHT_CMD_NACK (0x00) +#define MF_ULTRALIGHT_CMD_AUTH_NAK (0x04) + +#define MF_ULTRALIGHT_MAX_CNTR_VAL (0x00FFFFFF) +#define MF_ULTRALIGHT_MAX_PAGE_NUM (510) +#define MF_ULTRALIGHT_PAGE_SIZE (4U) +#define MF_ULTRALIGHT_SIGNATURE_SIZE (32) +#define MF_ULTRALIGHT_COUNTER_SIZE (3) +#define MF_ULTRALIGHT_COUNTER_NUM (3) +#define MF_ULTRALIGHT_TEARING_FLAG_SIZE (1) +#define MF_ULTRALIGHT_TEARING_FLAG_NUM (3) +#define MF_ULTRALIGHT_AUTH_PASSWORD_SIZE (4) +#define MF_ULTRALIGHT_AUTH_PACK_SIZE (2) +#define MF_ULTRALIGHT_AUTH_RESPONSE_SIZE (9) + +typedef enum { + MfUltralightErrorNone, + MfUltralightErrorNotPresent, + MfUltralightErrorProtocol, + MfUltralightErrorAuth, + MfUltralightErrorTimeout, +} MfUltralightError; + +typedef enum { + MfUltralightTypeUnknown, + MfUltralightTypeNTAG203, + MfUltralightTypeMfulC, + MfUltralightTypeUL11, + MfUltralightTypeUL21, + MfUltralightTypeNTAG213, + MfUltralightTypeNTAG215, + MfUltralightTypeNTAG216, + MfUltralightTypeNTAGI2C1K, + MfUltralightTypeNTAGI2C2K, + MfUltralightTypeNTAGI2CPlus1K, + MfUltralightTypeNTAGI2CPlus2K, + + MfUltralightTypeNum, +} MfUltralightType; + +typedef enum { + MfUltralightFeatureSupportReadVersion = (1U << 0), + MfUltralightFeatureSupportReadSignature = (1U << 1), + MfUltralightFeatureSupportReadCounter = (1U << 2), + MfUltralightFeatureSupportCheckTearingFlag = (1U << 3), + MfUltralightFeatureSupportFastRead = (1U << 4), + MfUltralightFeatureSupportIncCounter = (1U << 5), + MfUltralightFeatureSupportFastWrite = (1U << 6), + MfUltralightFeatureSupportCompatibleWrite = (1U << 7), + MfUltralightFeatureSupportPasswordAuth = (1U << 8), + MfUltralightFeatureSupportVcsl = (1U << 9), + MfUltralightFeatureSupportSectorSelect = (1U << 10), + MfUltralightFeatureSupportSingleCounter = (1U << 11), + MfUltralightFeatureSupportAsciiMirror = (1U << 12), + MfUltralightFeatureSupportCounterInMemory = (1U << 13), + MfUltralightFeatureSupportDynamicLock = (1U << 14), + MfUltralightFeatureSupportAuthenticate = (1U << 15), +} MfUltralightFeatureSupport; + +typedef struct { + uint8_t data[MF_ULTRALIGHT_PAGE_SIZE]; +} MfUltralightPage; + +typedef struct { + MfUltralightPage page[4]; +} MfUltralightPageReadCommandData; + +typedef struct { + uint8_t header; + uint8_t vendor_id; + uint8_t prod_type; + uint8_t prod_subtype; + uint8_t prod_ver_major; + uint8_t prod_ver_minor; + uint8_t storage_size; + uint8_t protocol_type; +} MfUltralightVersion; + +typedef struct { + uint8_t data[MF_ULTRALIGHT_SIGNATURE_SIZE]; +} MfUltralightSignature; + +typedef union { + uint32_t counter; + uint8_t data[MF_ULTRALIGHT_COUNTER_SIZE]; +} MfUltralightCounter; + +typedef struct { + uint8_t data; +} MfUltralightTearingFlag; + +typedef struct { + uint8_t data[MF_ULTRALIGHT_AUTH_PASSWORD_SIZE]; +} MfUltralightAuthPassword; + +typedef struct { + uint8_t data[MF_ULTRALIGHT_AUTH_PACK_SIZE]; +} MfUltralightAuthPack; + +typedef enum { + MfUltralightMirrorNone, + MfUltralightMirrorUid, + MfUltralightMirrorCounter, + MfUltralightMirrorUidCounter, +} MfUltralightMirrorConf; + +typedef struct __attribute__((packed)) { + union { + uint8_t value; + struct { + uint8_t rfui1 : 2; + bool strg_mod_en : 1; + bool rfui2 : 1; + uint8_t mirror_byte : 2; + MfUltralightMirrorConf mirror_conf : 2; + }; + } mirror; + uint8_t rfui1; + uint8_t mirror_page; + uint8_t auth0; + union { + uint8_t value; + struct { + uint8_t authlim : 3; + bool nfc_cnt_pwd_prot : 1; + bool nfc_cnt_en : 1; + bool nfc_dis_sec1 : 1; // NTAG I2C Plus only + bool cfglck : 1; + bool prot : 1; + }; + } access; + uint8_t vctid; + uint8_t rfui2[2]; + MfUltralightAuthPassword password; + MfUltralightAuthPack pack; + uint8_t rfui3[2]; +} MfUltralightConfigPages; + +typedef struct { + Iso14443_3aData* iso14443_3a_data; + MfUltralightType type; + MfUltralightVersion version; + MfUltralightSignature signature; + MfUltralightCounter counter[MF_ULTRALIGHT_COUNTER_NUM]; + MfUltralightTearingFlag tearing_flag[MF_ULTRALIGHT_TEARING_FLAG_NUM]; + MfUltralightPage page[MF_ULTRALIGHT_MAX_PAGE_NUM]; + uint16_t pages_read; + uint16_t pages_total; + uint32_t auth_attempts; +} MfUltralightData; + +extern const NfcDeviceBase nfc_device_mf_ultralight; + +MfUltralightData* mf_ultralight_alloc(); + +void mf_ultralight_free(MfUltralightData* data); + +void mf_ultralight_reset(MfUltralightData* data); + +void mf_ultralight_copy(MfUltralightData* data, const MfUltralightData* other); + +bool mf_ultralight_verify(MfUltralightData* data, const FuriString* device_type); + +bool mf_ultralight_load(MfUltralightData* data, FlipperFormat* ff, uint32_t version); + +bool mf_ultralight_save(const MfUltralightData* data, FlipperFormat* ff); + +bool mf_ultralight_is_equal(const MfUltralightData* data, const MfUltralightData* other); + +const char* + mf_ultralight_get_device_name(const MfUltralightData* data, NfcDeviceNameType name_type); + +const uint8_t* mf_ultralight_get_uid(const MfUltralightData* data, size_t* uid_len); + +bool mf_ultralight_set_uid(MfUltralightData* data, const uint8_t* uid, size_t uid_len); + +Iso14443_3aData* mf_ultralight_get_base_data(const MfUltralightData* data); + +MfUltralightType mf_ultralight_get_type_by_version(MfUltralightVersion* version); + +uint16_t mf_ultralight_get_pages_total(MfUltralightType type); + +uint32_t mf_ultralight_get_feature_support_set(MfUltralightType type); + +uint16_t mf_ultralight_get_config_page_num(MfUltralightType type); + +uint8_t mf_ultralight_get_pwd_page_num(MfUltralightType type); + +bool mf_ultralight_is_page_pwd_or_pack(MfUltralightType type, uint16_t page_num); + +bool mf_ultralight_support_feature(const uint32_t feature_set, const uint32_t features_to_check); + +bool mf_ultralight_get_config_page(const MfUltralightData* data, MfUltralightConfigPages** config); + +bool mf_ultralight_is_all_data_read(const MfUltralightData* data); + +bool mf_ultralight_detect_protocol(const Iso14443_3aData* iso14443_3a_data); + +bool mf_ultralight_is_counter_configured(const MfUltralightData* data); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_listener.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight_listener.c new file mode 100644 index 00000000000..70c6f6de2a8 --- /dev/null +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_listener.c @@ -0,0 +1,772 @@ +#include "mf_ultralight_listener_i.h" +#include "mf_ultralight_listener_defs.h" + +#include + +#include + +#define TAG "MfUltralightListener" + +#define MF_ULTRALIGHT_LISTENER_MAX_TX_BUFF_SIZE (256) + +typedef enum { + MfUltralightListenerAccessTypeRead, + MfUltralightListenerAccessTypeWrite, +} MfUltralightListenerAccessType; + +typedef struct { + uint8_t cmd; + size_t cmd_len_bits; + MfUltralightListenerCommandCallback callback; +} MfUltralightListenerCmdHandler; + +static bool mf_ultralight_listener_check_access( + MfUltralightListener* instance, + uint16_t start_page, + MfUltralightListenerAccessType access_type) { + bool access_success = false; + bool is_write_op = (access_type == MfUltralightListenerAccessTypeWrite); + + do { + if(!mf_ultralight_support_feature( + instance->features, MfUltralightFeatureSupportPasswordAuth)) { + access_success = true; + break; + } + if(instance->auth_state != MfUltralightListenerAuthStateSuccess) { + if((instance->config->auth0 <= start_page) && + (instance->config->access.prot || is_write_op)) { + break; + } + } + if(instance->config->access.cfglck && is_write_op) { + uint16_t config_page_start = instance->data->pages_total - 4; + if((start_page == config_page_start) || (start_page == config_page_start + 1)) { + break; + } + } + + access_success = true; + } while(false); + + return access_success; +} + +static void mf_ultralight_listener_send_short_resp(MfUltralightListener* instance, uint8_t data) { + furi_assert(instance->tx_buffer); + + bit_buffer_set_size(instance->tx_buffer, 4); + bit_buffer_set_byte(instance->tx_buffer, 0, data); + iso14443_3a_listener_tx(instance->iso14443_3a_listener, instance->tx_buffer); +}; + +static void mf_ultralight_listener_perform_read( + MfUltralightPage* pages, + MfUltralightListener* instance, + uint16_t start_page, + uint8_t page_cnt, + bool do_i2c_page_check) { + uint16_t pages_total = instance->data->pages_total; + mf_ultralight_mirror_read_prepare(start_page, instance); + for(uint8_t i = 0, rollover = 0; i < page_cnt; i++) { + uint16_t page = start_page + i; + + bool page_restricted = !mf_ultralight_listener_check_access( + instance, page, MfUltralightListenerAccessTypeRead); + + if(do_i2c_page_check && !mf_ultralight_i2c_validate_pages(page, page, instance)) + memset(pages[i].data, 0, sizeof(MfUltralightPage)); + else if(mf_ultralight_is_page_pwd_or_pack(instance->data->type, page)) + memset(pages[i].data, 0, sizeof(MfUltralightPage)); + else { + if(do_i2c_page_check) + page = mf_ultralight_i2c_provide_page_by_requested(page, instance); + + page = page_restricted ? rollover++ : page % pages_total; + pages[i] = instance->data->page[page]; + + mf_ultralight_mirror_read_handler(page, pages[i].data, instance); + } + } + mf_ultralight_single_counter_try_increase(instance); +} + +static MfUltralightCommand mf_ultralight_listener_perform_write( + MfUltralightListener* instance, + const uint8_t* const rx_data, + uint16_t start_page, + bool do_i2c_check) { + MfUltralightCommand command = MfUltralightCommandProcessedACK; + + if(start_page < 2 && instance->sector == 0) + command = MfUltralightCommandNotProcessedNAK; + else if(start_page == 2 && instance->sector == 0) + mf_ultralight_static_lock_bytes_write(instance->static_lock, *((uint16_t*)&rx_data[2])); + else if(start_page == 3 && instance->sector == 0) + mf_ultralight_capability_container_write(&instance->data->page[start_page], rx_data); + else if(mf_ultralight_is_page_dynamic_lock(instance, start_page)) + mf_ultralight_dynamic_lock_bytes_write(instance->dynamic_lock, *((uint32_t*)rx_data)); + else { + uint16_t page = start_page; + if(do_i2c_check) page = mf_ultralight_i2c_provide_page_by_requested(start_page, instance); + + memcpy(instance->data->page[page].data, rx_data, sizeof(MfUltralightPage)); + } + + return command; +} + +static MfUltralightCommand + mf_ultralight_listener_read_page_handler(MfUltralightListener* instance, BitBuffer* buffer) { + uint16_t start_page = bit_buffer_get_byte(buffer, 1); + uint16_t pages_total = instance->data->pages_total; + MfUltralightCommand command = MfUltralightCommandNotProcessedNAK; + + FURI_LOG_D(TAG, "CMD_READ: %d", start_page); + + do { + bool do_i2c_check = mf_ultralight_is_i2c_tag(instance->data->type); + + if(do_i2c_check) { + if(!mf_ultralight_i2c_validate_pages(start_page, start_page, instance)) break; + } else if(pages_total < start_page) { + break; + } + + if(!mf_ultralight_listener_check_access( + instance, start_page, MfUltralightListenerAccessTypeRead)) { + break; + } + + MfUltralightPage pages[4] = {}; + mf_ultralight_listener_perform_read(pages, instance, start_page, 4, do_i2c_check); + + bit_buffer_copy_bytes(instance->tx_buffer, (uint8_t*)pages, sizeof(pages)); + iso14443_3a_listener_send_standard_frame( + instance->iso14443_3a_listener, instance->tx_buffer); + command = MfUltralightCommandProcessed; + + } while(false); + + return command; +} + +static MfUltralightCommand + mf_ultralight_listener_fast_read_handler(MfUltralightListener* instance, BitBuffer* buffer) { + MfUltralightCommand command = MfUltralightCommandNotProcessedSilent; + FURI_LOG_D(TAG, "CMD_FAST_READ"); + + do { + if(!mf_ultralight_support_feature(instance->features, MfUltralightFeatureSupportFastRead)) + break; + uint16_t pages_total = instance->data->pages_total; + uint16_t start_page = bit_buffer_get_byte(buffer, 1); + uint16_t end_page = bit_buffer_get_byte(buffer, 2); + bool do_i2c_check = mf_ultralight_is_i2c_tag(instance->data->type); + + if(do_i2c_check) { + if(!mf_ultralight_i2c_validate_pages(start_page, end_page, instance)) { + command = MfUltralightCommandNotProcessedNAK; + break; + } + } else if(end_page > pages_total - 1) { + command = MfUltralightCommandNotProcessedNAK; + break; + } + + if(end_page < start_page) { + command = MfUltralightCommandNotProcessedNAK; + break; + } + + if(!mf_ultralight_listener_check_access( + instance, start_page, MfUltralightListenerAccessTypeRead) || + !mf_ultralight_listener_check_access( + instance, end_page, MfUltralightListenerAccessTypeRead)) { + command = MfUltralightCommandNotProcessedNAK; + break; + } + + MfUltralightPage pages[64] = {}; + uint8_t page_cnt = (end_page - start_page) + 1; + mf_ultralight_listener_perform_read(pages, instance, start_page, page_cnt, do_i2c_check); + + bit_buffer_copy_bytes(instance->tx_buffer, (uint8_t*)pages, page_cnt * 4); + iso14443_3a_listener_send_standard_frame( + instance->iso14443_3a_listener, instance->tx_buffer); + command = MfUltralightCommandProcessed; + } while(false); + + return command; +} + +static MfUltralightCommand + mf_ultralight_listener_write_page_handler(MfUltralightListener* instance, BitBuffer* buffer) { + uint8_t start_page = bit_buffer_get_byte(buffer, 1); + uint16_t pages_total = instance->data->pages_total; + MfUltralightCommand command = MfUltralightCommandNotProcessedNAK; + + FURI_LOG_D(TAG, "CMD_WRITE"); + + do { + bool do_i2c_check = mf_ultralight_is_i2c_tag(instance->data->type); + + if(do_i2c_check) { + if(!mf_ultralight_i2c_validate_pages(start_page, start_page, instance)) break; + } else if(pages_total < start_page) { + break; + } + + if(!mf_ultralight_listener_check_access( + instance, start_page, MfUltralightListenerAccessTypeWrite)) + break; + + if(mf_ultralight_static_lock_check_page(instance->static_lock, start_page)) break; + if(mf_ultralight_dynamic_lock_check_page(instance, start_page)) break; + + const uint8_t* rx_data = bit_buffer_get_data(buffer); + command = + mf_ultralight_listener_perform_write(instance, &rx_data[2], start_page, do_i2c_check); + } while(false); + + return command; +} + +static MfUltralightCommand + mf_ultralight_listener_fast_write_handler(MfUltralightListener* instance, BitBuffer* buffer) { + MfUltralightCommand command = MfUltralightCommandNotProcessedSilent; + FURI_LOG_D(TAG, "CMD_FAST_WRITE"); + + do { + if(!mf_ultralight_support_feature(instance->features, MfUltralightFeatureSupportFastWrite)) + break; + + uint8_t start_page = bit_buffer_get_byte(buffer, 1); + uint8_t end_page = bit_buffer_get_byte(buffer, 2); + if(start_page != 0xF0 || end_page != 0xFF) { + command = MfUltralightCommandNotProcessedNAK; + break; + } + + // No SRAM emulation support + + command = MfUltralightCommandProcessedACK; + } while(false); + + return command; +} + +static MfUltralightCommand + mf_ultralight_listener_read_version_handler(MfUltralightListener* instance, BitBuffer* buffer) { + UNUSED(buffer); + MfUltralightCommand command = MfUltralightCommandNotProcessedSilent; + + FURI_LOG_D(TAG, "CMD_GET_VERSION"); + + if(mf_ultralight_support_feature(instance->features, MfUltralightFeatureSupportReadVersion)) { + bit_buffer_copy_bytes( + instance->tx_buffer, (uint8_t*)&instance->data->version, sizeof(MfUltralightVersion)); + iso14443_3a_listener_send_standard_frame( + instance->iso14443_3a_listener, instance->tx_buffer); + command = MfUltralightCommandProcessed; + } + + return command; +} + +static MfUltralightCommand mf_ultralight_listener_read_signature_handler( + MfUltralightListener* instance, + BitBuffer* buffer) { + UNUSED(buffer); + MfUltralightCommand command = MfUltralightCommandNotProcessedSilent; + + FURI_LOG_D(TAG, "CMD_READ_SIG"); + + if(mf_ultralight_support_feature(instance->features, MfUltralightFeatureSupportReadSignature)) { + bit_buffer_copy_bytes( + instance->tx_buffer, instance->data->signature.data, sizeof(MfUltralightSignature)); + iso14443_3a_listener_send_standard_frame( + instance->iso14443_3a_listener, instance->tx_buffer); + command = MfUltralightCommandProcessed; + } + + return command; +} + +static MfUltralightCommand + mf_ultralight_listener_read_counter_handler(MfUltralightListener* instance, BitBuffer* buffer) { + MfUltralightCommand command = MfUltralightCommandNotProcessedNAK; + + FURI_LOG_D(TAG, "CMD_READ_CNT"); + + do { + uint8_t counter_num = bit_buffer_get_byte(buffer, 1); + if(!mf_ultralight_support_feature( + instance->features, MfUltralightFeatureSupportReadCounter)) + break; + + if(mf_ultralight_support_feature( + instance->features, MfUltralightFeatureSupportSingleCounter)) { + if(instance->config == NULL) break; + + if(!instance->config->access.nfc_cnt_en || counter_num != 2) break; + + if(instance->config->access.nfc_cnt_pwd_prot) { + if(instance->auth_state != MfUltralightListenerAuthStateSuccess) { + break; + } + } + } + + if(counter_num > 2) break; + uint8_t cnt_value[3] = { + (instance->data->counter[counter_num].counter >> 0) & 0xff, + (instance->data->counter[counter_num].counter >> 8) & 0xff, + (instance->data->counter[counter_num].counter >> 16) & 0xff, + }; + bit_buffer_copy_bytes(instance->tx_buffer, cnt_value, sizeof(cnt_value)); + iso14443_3a_listener_send_standard_frame( + instance->iso14443_3a_listener, instance->tx_buffer); + command = MfUltralightCommandProcessed; + } while(false); + + return command; +} + +static MfUltralightCommand mf_ultralight_listener_increase_counter_handler( + MfUltralightListener* instance, + BitBuffer* buffer) { + MfUltralightCommand command = MfUltralightCommandNotProcessedNAK; + + FURI_LOG_D(TAG, "CMD_INCR_CNT"); + + do { + if(!mf_ultralight_support_feature( + instance->features, MfUltralightFeatureSupportIncCounter)) { + command = MfUltralightCommandNotProcessedSilent; + break; + } + + uint8_t counter_num = bit_buffer_get_byte(buffer, 1); + if(counter_num > 2) break; + + if(instance->data->counter[counter_num].counter == MF_ULTRALIGHT_MAX_CNTR_VAL) { + command = MfUltralightCommandProcessed; + break; + } + + MfUltralightCounter buf_counter = {}; + bit_buffer_write_bytes_mid(buffer, buf_counter.data, 2, sizeof(buf_counter.data)); + uint32_t incr_value = buf_counter.counter; + + if(instance->data->counter[counter_num].counter + incr_value > MF_ULTRALIGHT_MAX_CNTR_VAL) + break; + + instance->data->counter[counter_num].counter += incr_value; + command = MfUltralightCommandProcessedACK; + } while(false); + + return command; +} + +static MfUltralightCommand mf_ultralight_listener_check_tearing_handler( + MfUltralightListener* instance, + BitBuffer* buffer) { + MfUltralightCommand command = MfUltralightCommandNotProcessedNAK; + + FURI_LOG_D(TAG, "CMD_CHECK_TEARING"); + + do { + uint8_t tearing_flag_num = bit_buffer_get_byte(buffer, 1); + if(!mf_ultralight_support_feature( + instance->features, + MfUltralightFeatureSupportCheckTearingFlag | + MfUltralightFeatureSupportSingleCounter)) { + break; + } + if(mf_ultralight_support_feature( + instance->features, MfUltralightFeatureSupportSingleCounter) && + (tearing_flag_num != 2)) { + break; + } + if(tearing_flag_num >= MF_ULTRALIGHT_TEARING_FLAG_NUM) { + break; + } + + bit_buffer_set_size_bytes(instance->tx_buffer, 1); + bit_buffer_set_byte( + instance->tx_buffer, 0, instance->data->tearing_flag[tearing_flag_num].data); + iso14443_3a_listener_send_standard_frame( + instance->iso14443_3a_listener, instance->tx_buffer); + command = MfUltralightCommandProcessed; + + } while(false); + + return command; +} + +static MfUltralightCommand + mf_ultralight_listener_vcsl_handler(MfUltralightListener* instance, BitBuffer* buffer) { + MfUltralightCommand command = MfUltralightCommandNotProcessedSilent; + UNUSED(instance); + UNUSED(buffer); + FURI_LOG_D(TAG, "CMD_VCSL"); + do { + if(!mf_ultralight_support_feature(instance->features, MfUltralightFeatureSupportVcsl)) + break; + + MfUltralightConfigPages* config; + if(!mf_ultralight_get_config_page(instance->data, &config)) break; + + bit_buffer_set_size_bytes(instance->tx_buffer, 1); + bit_buffer_set_byte(instance->tx_buffer, 0, config->vctid); + iso14443_3a_listener_send_standard_frame( + instance->iso14443_3a_listener, instance->tx_buffer); + command = MfUltralightCommandProcessed; + } while(false); + + return command; +} + +static MfUltralightCommand + mf_ultralight_listener_auth_handler(MfUltralightListener* instance, BitBuffer* buffer) { + MfUltralightCommand command = MfUltralightCommandNotProcessedNAK; + + FURI_LOG_D(TAG, "CMD_AUTH"); + + do { + if(!mf_ultralight_support_feature( + instance->features, MfUltralightFeatureSupportPasswordAuth)) + break; + + MfUltralightAuthPassword password; + bit_buffer_write_bytes_mid(buffer, password.data, 1, sizeof(password.data)); + + if(instance->callback) { + instance->mfu_event_data.password = password; + instance->mfu_event.type = MfUltralightListenerEventTypeAuth; + instance->callback(instance->generic_event, instance->context); + } + + bool auth_success = + mf_ultralight_auth_check_password(&instance->config->password, &password); + bool card_locked = mf_ultralight_auth_limit_check_and_update(instance, auth_success); + + if(card_locked) { + command = MfUltralightCommandNotProcessedAuthNAK; + break; + } + + if(!auth_success) break; + + bit_buffer_copy_bytes( + instance->tx_buffer, instance->config->pack.data, sizeof(MfUltralightAuthPack)); + instance->auth_state = MfUltralightListenerAuthStateSuccess; + iso14443_3a_listener_send_standard_frame( + instance->iso14443_3a_listener, instance->tx_buffer); + + command = MfUltralightCommandProcessed; + } while(false); + + return command; +} + +static MfUltralightCommand + mf_ultralight_comp_write_handler_p2(MfUltralightListener* instance, BitBuffer* buffer) { + MfUltralightCommand command = MfUltralightCommandNotProcessedNAK; + FURI_LOG_D(TAG, "CMD_CM_WR_2"); + + do { + if(bit_buffer_get_size_bytes(buffer) != 16) break; + + const uint8_t* rx_data = bit_buffer_get_data(buffer); + uint8_t start_page = instance->composite_cmd.data; + + command = mf_ultralight_listener_perform_write(instance, rx_data, start_page, false); + } while(false); + + return command; +} + +static MfUltralightCommand + mf_ultralight_comp_write_handler_p1(MfUltralightListener* instance, BitBuffer* buffer) { + MfUltralightCommand command = MfUltralightCommandNotProcessedSilent; + + FURI_LOG_D(TAG, "CMD_CM_WR_1"); + + do { + if(!mf_ultralight_support_feature( + instance->features, MfUltralightFeatureSupportCompatibleWrite)) + break; + + uint8_t start_page = bit_buffer_get_byte(buffer, 1); + uint16_t last_page = instance->data->pages_total - 1; + + if(start_page < 2 || start_page > last_page) { + command = MfUltralightCommandNotProcessedNAK; + break; + } + + if(!mf_ultralight_listener_check_access( + instance, start_page, MfUltralightListenerAccessTypeWrite)) { + command = MfUltralightCommandNotProcessedNAK; + break; + } + + if(mf_ultralight_static_lock_check_page(instance->static_lock, start_page) || + mf_ultralight_dynamic_lock_check_page(instance, start_page)) { + command = MfUltralightCommandNotProcessedNAK; + break; + } + + instance->composite_cmd.data = start_page; + command = MfUltralightCommandProcessedACK; + mf_ultralight_composite_command_set_next(instance, mf_ultralight_comp_write_handler_p2); + } while(false); + + return command; +} + +static MfUltralightCommand + mf_ultralight_sector_select_handler_p2(MfUltralightListener* instance, BitBuffer* buffer) { + MfUltralightCommand command = MfUltralightCommandNotProcessedNAK; + UNUSED(instance); + UNUSED(buffer); + FURI_LOG_D(TAG, "CMD_SEC_SEL_2"); + + do { + if(bit_buffer_get_size_bytes(buffer) != 4) break; + uint8_t sector = bit_buffer_get_byte(buffer, 0); + if(sector == 0xFF) break; + + instance->sector = sector; + command = MfUltralightCommandProcessedSilent; + } while(false); + + return command; +} + +static MfUltralightCommand + mf_ultralight_sector_select_handler_p1(MfUltralightListener* instance, BitBuffer* buffer) { + MfUltralightCommand command = MfUltralightCommandNotProcessedNAK; + UNUSED(buffer); + FURI_LOG_D(TAG, "CMD_SEC_SEL_1"); + + do { + if(!mf_ultralight_support_feature( + instance->features, MfUltralightFeatureSupportSectorSelect) && + bit_buffer_get_byte(buffer, 1) == 0xFF) + break; + + command = MfUltralightCommandProcessedACK; + mf_ultralight_composite_command_set_next(instance, mf_ultralight_sector_select_handler_p2); + } while(false); + + return command; +} + +static const MfUltralightListenerCmdHandler mf_ultralight_command[] = { + { + .cmd = MF_ULTRALIGHT_CMD_READ_PAGE, + .cmd_len_bits = 2 * 8, + .callback = mf_ultralight_listener_read_page_handler, + }, + { + .cmd = MF_ULTRALIGHT_CMD_FAST_READ, + .cmd_len_bits = 3 * 8, + .callback = mf_ultralight_listener_fast_read_handler, + }, + { + .cmd = MF_ULTRALIGHT_CMD_WRITE_PAGE, + .cmd_len_bits = 6 * 8, + .callback = mf_ultralight_listener_write_page_handler, + }, + { + .cmd = MF_ULTRALIGHT_CMD_FAST_WRITE, + .cmd_len_bits = 67 * 8, + .callback = mf_ultralight_listener_fast_write_handler, + }, + { + .cmd = MF_ULTRALIGHT_CMD_GET_VERSION, + .cmd_len_bits = 8, + .callback = mf_ultralight_listener_read_version_handler, + }, + { + .cmd = MF_ULTRALIGHT_CMD_READ_SIG, + .cmd_len_bits = 2 * 8, + .callback = mf_ultralight_listener_read_signature_handler, + }, + { + .cmd = MF_ULTRALIGHT_CMD_READ_CNT, + .cmd_len_bits = 2 * 8, + .callback = mf_ultralight_listener_read_counter_handler, + }, + { + .cmd = MF_ULTRALIGHT_CMD_CHECK_TEARING, + .cmd_len_bits = 2 * 8, + .callback = mf_ultralight_listener_check_tearing_handler, + }, + { + .cmd = MF_ULTRALIGHT_CMD_PWD_AUTH, + .cmd_len_bits = 5 * 8, + .callback = mf_ultralight_listener_auth_handler, + }, + { + .cmd = MF_ULTRALIGHT_CMD_INCR_CNT, + .cmd_len_bits = 6 * 8, + .callback = mf_ultralight_listener_increase_counter_handler, + }, + { + .cmd = MF_ULTRALIGHT_CMD_SECTOR_SELECT, + .cmd_len_bits = 2 * 8, + .callback = mf_ultralight_sector_select_handler_p1, + }, + { + .cmd = MF_ULTRALIGHT_CMD_COMP_WRITE, + .cmd_len_bits = 2 * 8, + .callback = mf_ultralight_comp_write_handler_p1, + }, + { + .cmd = MF_ULTRALIGHT_CMD_VCSL, + .cmd_len_bits = 21 * 8, + .callback = mf_ultralight_listener_vcsl_handler, + }, +}; + +static void mf_ultralight_listener_prepare_emulation(MfUltralightListener* instance) { + MfUltralightData* data = instance->data; + instance->features = mf_ultralight_get_feature_support_set(data->type); + mf_ultralight_get_config_page(data, &instance->config); + mf_ultralight_mirror_prepare_emulation(instance); + mf_ultralight_static_lock_bytes_prepare(instance); + mf_ultralight_dynamic_lock_bytes_prepare(instance); +} + +static NfcCommand mf_ultralight_command_postprocess( + MfUltralightCommand mfu_command, + MfUltralightListener* instance) { + NfcCommand command = NfcCommandContinue; + + if(mfu_command == MfUltralightCommandProcessedACK) { + mf_ultralight_listener_send_short_resp(instance, MF_ULTRALIGHT_CMD_ACK); + command = NfcCommandContinue; + } else if(mfu_command == MfUltralightCommandProcessedSilent) { + command = NfcCommandReset; + } else if(mfu_command != MfUltralightCommandProcessed) { + instance->auth_state = MfUltralightListenerAuthStateIdle; + command = NfcCommandSleep; + + if(mfu_command == MfUltralightCommandNotProcessedNAK) { + mf_ultralight_listener_send_short_resp(instance, MF_ULTRALIGHT_CMD_NACK); + } else if(mfu_command == MfUltralightCommandNotProcessedAuthNAK) { + mf_ultralight_listener_send_short_resp(instance, MF_ULTRALIGHT_CMD_AUTH_NAK); + } + } + + return command; +} + +static NfcCommand mf_ultralight_reset_listener_state( + MfUltralightListener* instance, + Iso14443_3aListenerEventType event_type) { + mf_ultralight_composite_command_reset(instance); + mf_ultralight_single_counter_try_to_unlock(instance, event_type); + instance->sector = 0; + instance->auth_state = MfUltralightListenerAuthStateIdle; + return NfcCommandSleep; +} + +MfUltralightListener* mf_ultralight_listener_alloc( + Iso14443_3aListener* iso14443_3a_listener, + MfUltralightData* data) { + furi_assert(iso14443_3a_listener); + + MfUltralightListener* instance = malloc(sizeof(MfUltralightListener)); + instance->mirror.ascii_mirror_data = furi_string_alloc(); + instance->iso14443_3a_listener = iso14443_3a_listener; + instance->data = data; + mf_ultralight_static_lock_bytes_prepare(instance); + mf_ultralight_listener_prepare_emulation(instance); + mf_ultralight_composite_command_reset(instance); + instance->sector = 0; + instance->tx_buffer = bit_buffer_alloc(MF_ULTRALIGHT_LISTENER_MAX_TX_BUFF_SIZE); + + instance->mfu_event.data = &instance->mfu_event_data; + instance->generic_event.protocol = NfcProtocolMfUltralight; + instance->generic_event.instance = instance; + instance->generic_event.event_data = &instance->mfu_event; + + return instance; +} + +void mf_ultralight_listener_free(MfUltralightListener* instance) { + furi_assert(instance); + furi_assert(instance->data); + furi_assert(instance->tx_buffer); + + bit_buffer_free(instance->tx_buffer); + furi_string_free(instance->mirror.ascii_mirror_data); + free(instance); +} + +const MfUltralightData* mf_ultralight_listener_get_data(MfUltralightListener* instance) { + furi_assert(instance); + furi_assert(instance->data); + + return instance->data; +} + +void mf_ultralight_listener_set_callback( + MfUltralightListener* instance, + NfcGenericCallback callback, + void* context) { + furi_assert(instance); + + instance->callback = callback; + instance->context = context; +} + +NfcCommand mf_ultralight_listener_run(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.protocol == NfcProtocolIso14443_3a); + furi_assert(event.event_data); + + MfUltralightListener* instance = context; + Iso14443_3aListenerEvent* iso14443_3a_event = event.event_data; + BitBuffer* rx_buffer = iso14443_3a_event->data->buffer; + NfcCommand command = NfcCommandContinue; + + if(iso14443_3a_event->type == Iso14443_3aListenerEventTypeReceivedStandardFrame) { + MfUltralightCommand mfu_command = MfUltralightCommandNotFound; + size_t size = bit_buffer_get_size(rx_buffer); + uint8_t cmd = bit_buffer_get_byte(rx_buffer, 0); + + if(mf_ultralight_composite_command_in_progress(instance)) { + mfu_command = mf_ultralight_composite_command_run(instance, rx_buffer); + } else { + for(size_t i = 0; i < COUNT_OF(mf_ultralight_command); i++) { + if(size != mf_ultralight_command[i].cmd_len_bits) continue; + if(cmd != mf_ultralight_command[i].cmd) continue; + mfu_command = mf_ultralight_command[i].callback(instance, rx_buffer); + + if(mfu_command != MfUltralightCommandNotFound) break; + } + } + command = mf_ultralight_command_postprocess(mfu_command, instance); + } else if( + iso14443_3a_event->type == Iso14443_3aListenerEventTypeReceivedData || + iso14443_3a_event->type == Iso14443_3aListenerEventTypeFieldOff || + iso14443_3a_event->type == Iso14443_3aListenerEventTypeHalted) { + command = mf_ultralight_reset_listener_state(instance, iso14443_3a_event->type); + } + + return command; +} + +const NfcListenerBase mf_ultralight_listener = { + .alloc = (NfcListenerAlloc)mf_ultralight_listener_alloc, + .free = (NfcListenerFree)mf_ultralight_listener_free, + .get_data = (NfcListenerGetData)mf_ultralight_listener_get_data, + .set_callback = (NfcListenerSetCallback)mf_ultralight_listener_set_callback, + .run = (NfcListenerRun)mf_ultralight_listener_run, +}; diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_listener.h b/lib/nfc/protocols/mf_ultralight/mf_ultralight_listener.h new file mode 100644 index 00000000000..fa6a6bd7e70 --- /dev/null +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_listener.h @@ -0,0 +1,28 @@ +#pragma once + +#include "mf_ultralight.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct MfUltralightListener MfUltralightListener; + +typedef enum { + MfUltralightListenerEventTypeAuth, +} MfUltralightListenerEventType; + +typedef struct { + union { + MfUltralightAuthPassword password; + }; +} MfUltralightListenerEventData; + +typedef struct { + MfUltralightListenerEventType type; + MfUltralightListenerEventData* data; +} MfUltralightListenerEvent; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_listener_defs.h b/lib/nfc/protocols/mf_ultralight/mf_ultralight_listener_defs.h new file mode 100644 index 00000000000..aa3e11b8c4d --- /dev/null +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_listener_defs.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern const NfcListenerBase mf_ultralight_listener; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_listener_i.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight_listener_i.c new file mode 100644 index 00000000000..3d6b9a94fdf --- /dev/null +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_listener_i.c @@ -0,0 +1,577 @@ +#include "mf_ultralight_listener_i.h" + +#include + +#define MF_ULTRALIGHT_STATIC_BIT_LOCK_OTP_CC 0 +#define MF_ULTRALIGHT_STATIC_BIT_LOCK_BL_9_4 1 +#define MF_ULTRALIGHT_STATIC_BIT_LOCK_BL_15_10 2 + +#define MF_ULTRALIGHT_BIT_ACTIVE(lock_bits, bit) (((lock_bits) & (1U << (bit))) != 0) +#define MF_ULTRALIGHT_BITS_SET(lock_bits, mask) ((lock_bits) |= (mask)) +#define MF_ULTRALIGHT_BITS_CLR(lock_bits, mask) ((lock_bits) &= ~(mask)) + +#define MF_ULTRALIGHT_PAGE_LOCKED(lock_bits, page) MF_ULTRALIGHT_BIT_ACTIVE(lock_bits, page) + +#define MF_ULTRALIGHT_STATIC_BIT_OTP_CC_LOCKED(lock_bits) \ + MF_ULTRALIGHT_BIT_ACTIVE(lock_bits, MF_ULTRALIGHT_STATIC_BIT_LOCK_OTP_CC) + +#define MF_ULTRALIGHT_STATIC_BITS_9_4_LOCKED(lock_bits) \ + MF_ULTRALIGHT_BIT_ACTIVE(lock_bits, MF_ULTRALIGHT_STATIC_BIT_LOCK_BL_9_4) + +#define MF_ULTRALIGHT_STATIC_BITS_15_10_LOCKED(lock_bits) \ + MF_ULTRALIGHT_BIT_ACTIVE(lock_bits, MF_ULTRALIGHT_STATIC_BIT_LOCK_BL_15_10) + +#define MF_ULTRALIGHT_STATIC_LOCK_L_OTP_CC_MASK (1U << 3) +#define MF_ULTRALIGHT_STATIC_LOCK_L_9_4_MASK \ + ((1U << 9) | (1U << 8) | (1U << 7) | (1U << 6) | (1U << 5) | (1U << 4)) +#define MF_ULTRALIGHT_STATIC_LOCK_L_15_10_MASK \ + ((1U << 15) | (1U << 14) | (1U << 13) | (1U << 12) | (1U << 11) | (1U << 10)) + +#define MF_ULTRALIGHT_PAGE_IN_BOUNDS(page, start, end) (((page) >= (start)) && ((page) <= (end))) + +#define MF_ULTRALIGHT_I2C_PAGE_ON_SESSION_REG(page) \ + MF_ULTRALIGHT_PAGE_IN_BOUNDS(page, 0x00EC, 0x00ED) + +#define MF_ULTRALIGHT_I2C_PAGE_ON_MIRRORED_SESSION_REG(page) \ + MF_ULTRALIGHT_PAGE_IN_BOUNDS(page, 0x00F8, 0x00F9) + +#define MF_ULTRALIGHT_AUTH_RESET_ATTEMPTS(instance) (instance->data->auth_attempts = 0) +#define MF_ULTRALIGHT_AUTH_INCREASE_ATTEMPTS(instance) (instance->data->auth_attempts++) + +static MfUltralightMirrorConf mf_ultralight_mirror_check_mode( + const MfUltralightConfigPages* const config, + const MfUltralightListenerAuthState auth_state) { + MfUltralightMirrorConf mirror_mode = config->mirror.mirror_conf; + + if(mirror_mode == MfUltralightMirrorNone || mirror_mode == MfUltralightMirrorUid) + return mirror_mode; + + if(!config->access.nfc_cnt_en || + (config->access.nfc_cnt_pwd_prot && auth_state != MfUltralightListenerAuthStateSuccess)) { + mirror_mode = mirror_mode == MfUltralightMirrorCounter ? MfUltralightMirrorNone : + MfUltralightMirrorUid; + } + return mirror_mode; +} + +static bool mf_ultralight_mirror_check_boundaries(MfUltralightListener* instance) { + const MfUltralightConfigPages* const conf = instance->config; + + uint8_t last_user_page = mf_ultralight_get_config_page_num(instance->data->type) - 2; + + uint8_t max_page_offset = 0; + uint8_t max_byte_offset = 2; + + MfUltralightMirrorConf mode = mf_ultralight_mirror_check_mode(conf, instance->auth_state); + + bool result = false; + bool again = false; + do { + if(mode == MfUltralightMirrorNone) { + break; + } else if(mode == MfUltralightMirrorUid) { + max_page_offset = 3; + } else if(mode == MfUltralightMirrorCounter) { + max_page_offset = 1; + } else if(mode == MfUltralightMirrorUidCounter) { + max_page_offset = 5; + max_byte_offset = 3; + } + + instance->mirror.actual_mode = mode; + + if(conf->mirror_page <= 3) break; + if(conf->mirror_page < last_user_page - max_page_offset) { + result = true; + break; + } + if(conf->mirror_page == last_user_page - max_page_offset) { + result = (conf->mirror.mirror_byte <= max_byte_offset); + break; + } + + if(conf->mirror_page > last_user_page - max_page_offset && + mode == MfUltralightMirrorUidCounter) { + mode = MfUltralightMirrorUid; + again = true; + } + } while(again); + + return result; +} + +static bool mf_ultralight_mirror_enabled(MfUltralightListener* instance) { + bool mirror_enabled = false; + if(mf_ultralight_support_feature(instance->features, MfUltralightFeatureSupportAsciiMirror) && + (instance->config != NULL) && mf_ultralight_mirror_check_boundaries(instance)) { + mirror_enabled = true; + } + instance->mirror.enabled = mirror_enabled; + return instance->mirror.enabled; +} + +static uint8_t mf_ultralight_get_mirror_data_size(MfUltralightMirrorConf mode) { + switch(mode) { + case MfUltralightMirrorUid: + return 14; + case MfUltralightMirrorCounter: + return 6; + case MfUltralightMirrorUidCounter: + return 21; + default: + return 0; + } +} + +static uint8_t mf_ultralight_get_mirror_last_page(MfUltralightListener* instance) { + uint8_t strSize = mf_ultralight_get_mirror_data_size(instance->mirror.actual_mode); + return (instance->config->mirror_page + 1U + strSize / 4); +} + +static uint8_t mf_ultralight_get_ascii_offset(uint8_t start_page, MfUltralightListener* instance) { + uint8_t start_offset = 0; + if(instance->config->mirror.mirror_conf == MfUltralightMirrorCounter) start_offset = 15; + + uint8_t ascii_offset = start_offset; + + if(start_page > instance->config->mirror_page) + ascii_offset = (start_page - instance->config->mirror_page) * 4 - + instance->config->mirror.mirror_byte + start_offset; + + return ascii_offset; +} + +static uint8_t mf_ultralight_get_ascii_end(MfUltralightMirrorConf mode) { + return (mode == MfUltralightMirrorUid) ? 14 : 21; +} + +static uint8_t mf_ultralight_get_byte_offset( + uint8_t current_page, + const MfUltralightConfigPages* const config) { + return (current_page > config->mirror_page) ? 0 : config->mirror.mirror_byte; +} + +static void mf_ultralight_format_mirror_data( + FuriString* str, + const uint8_t* const data, + const uint8_t data_len) { + for(uint8_t i = 0; i < data_len; i++) furi_string_cat_printf(str, "%02X", data[i]); +} + +void mf_ultralight_mirror_read_prepare(uint8_t start_page, MfUltralightListener* instance) { + if(mf_ultralight_mirror_enabled(instance)) { + instance->mirror.ascii_offset = mf_ultralight_get_ascii_offset(start_page, instance); + instance->mirror.ascii_end = mf_ultralight_get_ascii_end(instance->mirror.actual_mode); + + instance->mirror.mirror_last_page = mf_ultralight_get_mirror_last_page(instance); + } +} + +void mf_ultralight_mirror_read_handler( + uint8_t mirror_page_num, + uint8_t* dest, + MfUltralightListener* instance) { + if(instance->mirror.enabled && mirror_page_num >= instance->config->mirror_page && + mirror_page_num <= instance->mirror.mirror_last_page) { + uint8_t byte_offset = mf_ultralight_get_byte_offset(mirror_page_num, instance->config); + + uint8_t ascii_offset = instance->mirror.ascii_offset; + uint8_t ascii_end = instance->mirror.ascii_end; + uint8_t* source = (uint8_t*)furi_string_get_cstr(instance->mirror.ascii_mirror_data); + for(uint8_t j = byte_offset; (j < 4) && (ascii_offset < ascii_end); j++) { + dest[j] = source[ascii_offset]; + ascii_offset++; + } + instance->mirror.ascii_offset = ascii_offset; + } +} + +void mf_ultralight_mirror_prepare_emulation(MfUltralightListener* instance) { + mf_ultralight_format_mirror_data( + instance->mirror.ascii_mirror_data, + instance->data->iso14443_3a_data->uid, + instance->data->iso14443_3a_data->uid_len); + + furi_string_push_back(instance->mirror.ascii_mirror_data, 'x'); + + mf_ultraligt_mirror_format_counter(instance); +} + +void mf_ultraligt_mirror_format_counter(MfUltralightListener* instance) { + furi_string_left( + instance->mirror.ascii_mirror_data, instance->data->iso14443_3a_data->uid_len * 2 + 1); + + uint8_t* c = instance->data->counter[2].data; + furi_string_cat_printf(instance->mirror.ascii_mirror_data, "%02X%02X%02X", c[2], c[1], c[0]); +} + +bool mf_ultralight_composite_command_in_progress(MfUltralightListener* instance) { + return (instance->composite_cmd.callback != NULL); +} + +MfUltralightCommand + mf_ultralight_composite_command_run(MfUltralightListener* instance, BitBuffer* buffer) { + MfUltralightCommand command = (instance->composite_cmd.callback)(instance, buffer); + mf_ultralight_composite_command_reset(instance); + return command; +} + +void mf_ultralight_composite_command_reset(MfUltralightListener* instance) { + instance->composite_cmd.callback = NULL; + instance->composite_cmd.data = 0; +} + +void mf_ultralight_composite_command_set_next( + MfUltralightListener* instance, + const MfUltralightListenerCommandCallback handler) { + instance->composite_cmd.callback = handler; +} + +void mf_ultralight_single_counter_try_increase(MfUltralightListener* instance) { + if(mf_ultralight_support_feature(instance->features, MfUltralightFeatureSupportSingleCounter) && + instance->config->access.nfc_cnt_en && !instance->single_counter_increased) { + if(instance->data->counter[2].counter < MF_ULTRALIGHT_MAX_CNTR_VAL) { + instance->data->counter[2].counter++; + mf_ultraligt_mirror_format_counter(instance); + } + instance->single_counter_increased = true; + } +} + +void mf_ultralight_single_counter_try_to_unlock( + MfUltralightListener* instance, + Iso14443_3aListenerEventType type) { + if(mf_ultralight_support_feature(instance->features, MfUltralightFeatureSupportSingleCounter) && + type == Iso14443_3aListenerEventTypeFieldOff) { + instance->single_counter_increased = false; + } +} + +static bool mf_ultralight_i2c_page_validator_for_sector0( + uint16_t start_page, + uint16_t end_page, + MfUltralightType type) { + UNUSED(type); + bool valid = false; + if(type == MfUltralightTypeNTAGI2CPlus1K || type == MfUltralightTypeNTAGI2CPlus2K) { + if(start_page <= 0xE9 && end_page <= 0xE9) { + valid = true; + } else if( + MF_ULTRALIGHT_I2C_PAGE_ON_SESSION_REG(start_page) && + MF_ULTRALIGHT_I2C_PAGE_ON_SESSION_REG(end_page)) { + valid = true; + } + } else if(type == MfUltralightTypeNTAGI2C1K) { + if((start_page <= 0xE2) || MF_ULTRALIGHT_PAGE_IN_BOUNDS(start_page, 0x00E8, 0x00E9)) { + valid = true; + } + } else if(type == MfUltralightTypeNTAGI2C2K) { + valid = (start_page <= 0xFF && end_page <= 0xFF); + } + + return valid; +} + +static bool mf_ultralight_i2c_page_validator_for_sector1( + uint16_t start_page, + uint16_t end_page, + MfUltralightType type) { + bool valid = false; + if(type == MfUltralightTypeNTAGI2CPlus2K) { + valid = (start_page <= 0xFF && end_page <= 0xFF); + } else if(type == MfUltralightTypeNTAGI2C2K) { + valid = (MF_ULTRALIGHT_PAGE_IN_BOUNDS(start_page, 0x00E8, 0x00E9) || (start_page <= 0xE0)); + } else if(type == MfUltralightTypeNTAGI2C1K || type == MfUltralightTypeNTAGI2CPlus1K) { + valid = false; + } + + return valid; +} + +static bool mf_ultralight_i2c_page_validator_for_sector2( + uint16_t start_page, + uint16_t end_page, + MfUltralightType type) { + UNUSED(start_page); + UNUSED(end_page); + UNUSED(type); + return false; +} + +static bool mf_ultralight_i2c_page_validator_for_sector3( + uint16_t start_page, + uint16_t end_page, + MfUltralightType type) { + UNUSED(type); + UNUSED(end_page); + return MF_ULTRALIGHT_I2C_PAGE_ON_MIRRORED_SESSION_REG(start_page); +} + +typedef bool ( + *MfUltralightI2CValidator)(uint16_t start_page, uint16_t end_page, MfUltralightType type); + +typedef uint16_t (*MfUltralightI2CPageProvider)(uint16_t page, MfUltralightType type); + +const MfUltralightI2CValidator validation_methods[] = { + mf_ultralight_i2c_page_validator_for_sector0, + mf_ultralight_i2c_page_validator_for_sector1, + mf_ultralight_i2c_page_validator_for_sector2, + mf_ultralight_i2c_page_validator_for_sector3, +}; + +bool mf_ultralight_i2c_validate_pages( + uint16_t start_page, + uint16_t end_page, + MfUltralightListener* instance) { + bool valid = false; + if(instance->sector < COUNT_OF(validation_methods)) { + MfUltralightI2CValidator validate = validation_methods[instance->sector]; + valid = validate(start_page, end_page, instance->data->type); + } + return valid; +} + +bool mf_ultralight_is_i2c_tag(MfUltralightType type) { + return type == MfUltralightTypeNTAGI2C1K || type == MfUltralightTypeNTAGI2C2K || + type == MfUltralightTypeNTAGI2CPlus1K || type == MfUltralightTypeNTAGI2CPlus2K; +} + +static uint16_t mf_ultralight_i2c_page_provider_for_sector0(uint16_t page, MfUltralightType type) { + uint8_t new_page = page; + if(type == MfUltralightTypeNTAGI2CPlus1K || type == MfUltralightTypeNTAGI2CPlus2K) { + if(page == 0x00EC) { + new_page = 234; + } else if(page == 0x00ED) { + new_page = 235; + } + } else if(type == MfUltralightTypeNTAGI2C1K) { + if(page == 0x00E8) { + new_page = 232; + } else if(page == 0x00E9) { + new_page = 233; + } + } else if(type == MfUltralightTypeNTAGI2C2K) { + new_page = page; + } + return new_page; +} + +static uint16_t mf_ultralight_i2c_page_provider_for_sector1(uint16_t page, MfUltralightType type) { + UNUSED(type); + uint16_t new_page = page; + if(type == MfUltralightTypeNTAGI2CPlus2K) new_page = page + 236; + if(type == MfUltralightTypeNTAGI2C2K) new_page = page + 256; + return new_page; +} + +static uint16_t mf_ultralight_i2c_page_provider_for_sector2(uint16_t page, MfUltralightType type) { + UNUSED(type); + return page; +} + +static uint16_t mf_ultralight_i2c_page_provider_for_sector3(uint16_t page, MfUltralightType type) { + uint16_t new_page = page; + if(type == MfUltralightTypeNTAGI2CPlus1K || type == MfUltralightTypeNTAGI2CPlus2K) { + if(page == 0x00F8) + new_page = 234; + else if(page == 0x00F9) + new_page = 235; + } else if(type == MfUltralightTypeNTAGI2C1K || type == MfUltralightTypeNTAGI2C2K) { + if(page == 0x00F8) + new_page = (type == MfUltralightTypeNTAGI2C1K) ? 227 : 481; + else if(page == 0x00F9) + new_page = (type == MfUltralightTypeNTAGI2C1K) ? 228 : 482; + } + return new_page; +} + +const MfUltralightI2CPageProvider provider_methods[] = { + mf_ultralight_i2c_page_provider_for_sector0, + mf_ultralight_i2c_page_provider_for_sector1, + mf_ultralight_i2c_page_provider_for_sector2, + mf_ultralight_i2c_page_provider_for_sector3, +}; + +uint16_t + mf_ultralight_i2c_provide_page_by_requested(uint16_t page, MfUltralightListener* instance) { + uint16_t result = page; + if(instance->sector < COUNT_OF(provider_methods)) { + MfUltralightI2CPageProvider provider = provider_methods[instance->sector]; + result = provider(page, instance->data->type); + } + return result; +} + +void mf_ultralight_static_lock_bytes_prepare(MfUltralightListener* instance) { + instance->static_lock = (uint16_t*)&instance->data->page[2].data[2]; +} + +void mf_ultralight_static_lock_bytes_write( + MfUltralightStaticLockData* const lock_bits, + uint16_t new_bits) { + uint16_t current_locks = *lock_bits; + + if(MF_ULTRALIGHT_STATIC_BIT_OTP_CC_LOCKED(current_locks)) + MF_ULTRALIGHT_BITS_CLR(new_bits, MF_ULTRALIGHT_STATIC_LOCK_L_OTP_CC_MASK); + + if(MF_ULTRALIGHT_STATIC_BITS_9_4_LOCKED(current_locks)) + MF_ULTRALIGHT_BITS_CLR(new_bits, MF_ULTRALIGHT_STATIC_LOCK_L_9_4_MASK); + + if(MF_ULTRALIGHT_STATIC_BITS_15_10_LOCKED(current_locks)) + MF_ULTRALIGHT_BITS_CLR(new_bits, MF_ULTRALIGHT_STATIC_LOCK_L_15_10_MASK); + + MF_ULTRALIGHT_BITS_SET(current_locks, new_bits); + *lock_bits = current_locks; +} + +bool mf_ultralight_static_lock_check_page( + const MfUltralightStaticLockData* const lock_bits, + uint16_t page) { + bool locked = false; + if(MF_ULTRALIGHT_PAGE_IN_BOUNDS(page, 0x0003, 0x000F)) { + uint16_t current_locks = *lock_bits; + locked = MF_ULTRALIGHT_PAGE_LOCKED(current_locks, page); + } + return locked; +} + +void mf_ultralight_capability_container_write( + MfUltralightPage* const current_page, + const uint8_t* const new_data) { + for(uint8_t i = 0; i < MF_ULTRALIGHT_PAGE_SIZE; i++) { + current_page->data[i] |= new_data[i]; + } +} + +static uint16_t mf_ultralight_dynamic_lock_page_num(const MfUltralightData* data) { + uint16_t lock_page; + if(data->type == MfUltralightTypeNTAGI2C1K) + lock_page = 226; + else if(data->type == MfUltralightTypeNTAGI2C2K) + lock_page = 480; + else + lock_page = mf_ultralight_get_config_page_num(data->type) - 1; + return lock_page; +} + +void mf_ultralight_dynamic_lock_bytes_prepare(MfUltralightListener* instance) { + if(mf_ultralight_support_feature(instance->features, MfUltralightFeatureSupportDynamicLock)) { + uint16_t lock_page = mf_ultralight_dynamic_lock_page_num(instance->data); + instance->dynamic_lock = (uint32_t*)instance->data->page[lock_page].data; + } else { + instance->dynamic_lock = NULL; + } +} + +bool mf_ultralight_is_page_dynamic_lock(const MfUltralightListener* instance, uint16_t page) { + bool is_lock = false; + if(mf_ultralight_support_feature(instance->features, MfUltralightFeatureSupportDynamicLock)) { + uint16_t linear_page = page + instance->sector * 256; + + uint16_t lock_page = mf_ultralight_dynamic_lock_page_num(instance->data); + is_lock = linear_page == lock_page; + } + return is_lock; +} + +void mf_ultralight_dynamic_lock_bytes_write( + MfUltralightDynamicLockData* const lock_bits, + uint32_t new_bits) { + furi_assert(lock_bits != NULL); + new_bits &= 0x00FFFFFF; + uint32_t current_lock = *lock_bits; + for(uint8_t i = 0; i < 8; i++) { + uint8_t bl_bit = i + 16; + + if(MF_ULTRALIGHT_BIT_ACTIVE(current_lock, bl_bit)) { + uint8_t lock_bit = i * 2; + uint32_t mask = (1U << lock_bit) | (1U << (lock_bit + 1)); + MF_ULTRALIGHT_BITS_CLR(new_bits, mask); + } + } + MF_ULTRALIGHT_BITS_SET(current_lock, new_bits); + *lock_bits = current_lock; +} + +static uint8_t mf_ultralight_dynamic_lock_granularity(MfUltralightType type) { + switch(type) { + case MfUltralightTypeUL21: + case MfUltralightTypeNTAG213: + return 2; + case MfUltralightTypeNTAG215: + case MfUltralightTypeNTAG216: + case MfUltralightTypeNTAGI2C1K: + case MfUltralightTypeNTAGI2CPlus1K: + return 16; + case MfUltralightTypeNTAGI2C2K: + case MfUltralightTypeNTAGI2CPlus2K: + return 32; + default: + return 1; + } +} + +static uint16_t mf_ultralight_get_upper_page_bound(MfUltralightType type) { + uint16_t upper_page_bound; + + if(type == MfUltralightTypeNTAGI2CPlus2K) + upper_page_bound = 511; + else if(type == MfUltralightTypeNTAGI2C2K) + upper_page_bound = 479; + else { + upper_page_bound = mf_ultralight_get_config_page_num(type) - 2; + } + + return upper_page_bound; +} + +bool mf_ultralight_dynamic_lock_check_page(const MfUltralightListener* instance, uint16_t page) { + UNUSED(page); + bool locked = false; + uint16_t upper_page_bound = mf_ultralight_get_upper_page_bound(instance->data->type); + uint16_t linear_page = page + instance->sector * 256; + + if(mf_ultralight_support_feature(instance->features, MfUltralightFeatureSupportDynamicLock) && + MF_ULTRALIGHT_PAGE_IN_BOUNDS(linear_page, 0x0010, upper_page_bound)) { + uint8_t granularity = mf_ultralight_dynamic_lock_granularity(instance->data->type); + uint8_t bit = (linear_page - 16) / granularity; + uint16_t current_locks = *instance->dynamic_lock; + locked = MF_ULTRALIGHT_PAGE_LOCKED(current_locks, bit); + } + return locked; +} + +static bool mf_ultralight_auth_check_attempts(const MfUltralightListener* instance) { + uint8_t authlim = ((instance->data->type == MfUltralightTypeNTAGI2CPlus1K) || + (instance->data->type == MfUltralightTypeNTAGI2CPlus2K)) ? + (1U << instance->config->access.authlim) : + instance->config->access.authlim; + + return (instance->data->auth_attempts >= authlim); +} + +bool mf_ultralight_auth_limit_check_and_update(MfUltralightListener* instance, bool auth_success) { + bool card_locked = false; + + do { + if(instance->config->access.authlim == 0) break; + card_locked = mf_ultralight_auth_check_attempts(instance); + if(card_locked) break; + + if(auth_success) { + MF_ULTRALIGHT_AUTH_RESET_ATTEMPTS(instance); + } else { + MF_ULTRALIGHT_AUTH_INCREASE_ATTEMPTS(instance); + } + + card_locked = mf_ultralight_auth_check_attempts(instance); + } while(false); + + return card_locked; +} + +bool mf_ultralight_auth_check_password( + const MfUltralightAuthPassword* config_pass, + const MfUltralightAuthPassword* auth_pass) { + return memcmp(config_pass->data, auth_pass->data, sizeof(MfUltralightAuthPassword)) == 0; +} \ No newline at end of file diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_listener_i.h b/lib/nfc/protocols/mf_ultralight/mf_ultralight_listener_i.h new file mode 100644 index 00000000000..ba448d0879c --- /dev/null +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_listener_i.h @@ -0,0 +1,123 @@ +#pragma once + +#include "mf_ultralight_listener.h" +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + MfUltralightListenerAuthStateIdle, + MfUltralightListenerAuthStateSuccess, +} MfUltralightListenerAuthState; + +typedef enum { + MfUltralightCommandNotFound, + MfUltralightCommandProcessed, + MfUltralightCommandProcessedACK, + MfUltralightCommandProcessedSilent, + MfUltralightCommandNotProcessedNAK, + MfUltralightCommandNotProcessedSilent, + MfUltralightCommandNotProcessedAuthNAK, +} MfUltralightCommand; + +typedef MfUltralightCommand ( + *MfUltralightListenerCommandCallback)(MfUltralightListener* instance, BitBuffer* buf); + +typedef uint8_t MfUltralightListenerCompositeCommandData; + +typedef struct { + MfUltralightListenerCompositeCommandData data; + MfUltralightListenerCommandCallback callback; +} MfUltralightListenerCompositeCommandContext; + +typedef struct { + uint8_t enabled; + uint8_t ascii_offset; + uint8_t ascii_end; + uint8_t mirror_last_page; + MfUltralightMirrorConf actual_mode; + FuriString* ascii_mirror_data; +} MfUltralightMirrorMode; + +typedef uint16_t MfUltralightStaticLockData; +typedef uint32_t MfUltralightDynamicLockData; + +struct MfUltralightListener { + Iso14443_3aListener* iso14443_3a_listener; + MfUltralightListenerAuthState auth_state; + MfUltralightData* data; + BitBuffer* tx_buffer; + MfUltralightFeatureSupport features; + MfUltralightConfigPages* config; + MfUltralightStaticLockData* static_lock; + MfUltralightDynamicLockData* dynamic_lock; + + NfcGenericEvent generic_event; + MfUltralightListenerEvent mfu_event; + MfUltralightListenerEventData mfu_event_data; + NfcGenericCallback callback; + uint8_t sector; + bool single_counter_increased; + MfUltralightMirrorMode mirror; + MfUltralightListenerCompositeCommandContext composite_cmd; + void* context; +}; + +void mf_ultralight_single_counter_try_increase(MfUltralightListener* instance); +void mf_ultralight_single_counter_try_to_unlock( + MfUltralightListener* instance, + Iso14443_3aListenerEventType type); + +void mf_ultralight_mirror_prepare_emulation(MfUltralightListener* instance); +void mf_ultraligt_mirror_format_counter(MfUltralightListener* instance); +void mf_ultralight_mirror_read_prepare(uint8_t start_page, MfUltralightListener* instance); +void mf_ultralight_mirror_read_handler( + uint8_t mirror_page_num, + uint8_t* dest, + MfUltralightListener* instance); + +void mf_ultralight_composite_command_set_next( + MfUltralightListener* instance, + const MfUltralightListenerCommandCallback handler); +void mf_ultralight_composite_command_reset(MfUltralightListener* instance); +bool mf_ultralight_composite_command_in_progress(MfUltralightListener* instance); +MfUltralightCommand + mf_ultralight_composite_command_run(MfUltralightListener* instance, BitBuffer* buffer); + +bool mf_ultralight_is_i2c_tag(MfUltralightType type); +bool mf_ultralight_i2c_validate_pages( + uint16_t start_page, + uint16_t end_page, + MfUltralightListener* instance); + +uint16_t + mf_ultralight_i2c_provide_page_by_requested(uint16_t page, MfUltralightListener* instance); + +void mf_ultralight_static_lock_bytes_prepare(MfUltralightListener* instance); +void mf_ultralight_static_lock_bytes_write( + MfUltralightStaticLockData* const lock_bits, + uint16_t new_bits); +bool mf_ultralight_static_lock_check_page( + const MfUltralightStaticLockData* const lock_bits, + uint16_t page); + +void mf_ultralight_capability_container_write( + MfUltralightPage* const current_page, + const uint8_t* const new_data); + +void mf_ultralight_dynamic_lock_bytes_prepare(MfUltralightListener* instance); +bool mf_ultralight_is_page_dynamic_lock(const MfUltralightListener* instance, uint16_t start_page); +void mf_ultralight_dynamic_lock_bytes_write( + MfUltralightDynamicLockData* const lock_bits, + uint32_t new_bits); +bool mf_ultralight_dynamic_lock_check_page(const MfUltralightListener* instance, uint16_t page); +bool mf_ultralight_auth_limit_check_and_update(MfUltralightListener* instance, bool auth_success); +bool mf_ultralight_auth_check_password( + const MfUltralightAuthPassword* config_pass, + const MfUltralightAuthPassword* auth_pass); +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c new file mode 100644 index 00000000000..bf0ced38d0b --- /dev/null +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c @@ -0,0 +1,591 @@ +#include "mf_ultralight_poller_i.h" + +#include + +#include + +#define TAG "MfUltralightPoller" + +typedef NfcCommand (*MfUltralightPollerReadHandler)(MfUltralightPoller* instance); + +static bool mf_ultralight_poller_ntag_i2c_addr_lin_to_tag_ntag_i2c_1k( + uint16_t lin_addr, + uint8_t* sector, + uint8_t* tag, + uint8_t* pages_left) { + bool tag_calculated = false; + // 0 - 226: sector 0 + // 227 - 228: config registers + // 229 - 230: session registers + + if(lin_addr > 230) { + *pages_left = 0; + } else if(lin_addr >= 229) { + *sector = 3; + *pages_left = 2 - (lin_addr - 229); + *tag = lin_addr - 229 + 248; + tag_calculated = true; + } else if(lin_addr >= 227) { + *sector = 0; + *pages_left = 2 - (lin_addr - 227); + *tag = lin_addr - 227 + 232; + tag_calculated = true; + } else { + *sector = 0; + *pages_left = 227 - lin_addr; + *tag = lin_addr; + tag_calculated = true; + } + + return tag_calculated; +} + +static bool mf_ultralight_poller_ntag_i2c_addr_lin_to_tag_ntag_i2c_2k( + uint16_t lin_addr, + uint8_t* sector, + uint8_t* tag, + uint8_t* pages_left) { + bool tag_calculated = false; + // 0 - 255: sector 0 + // 256 - 480: sector 1 + // 481 - 482: config registers + // 483 - 484: session registers + + if(lin_addr > 484) { + *pages_left = 0; + } else if(lin_addr >= 483) { + *sector = 3; + *pages_left = 2 - (lin_addr - 483); + *tag = lin_addr - 483 + 248; + tag_calculated = true; + } else if(lin_addr >= 481) { + *sector = 1; + *pages_left = 2 - (lin_addr - 481); + *tag = lin_addr - 481 + 232; + tag_calculated = true; + } else if(lin_addr >= 256) { + *sector = 1; + *pages_left = 225 - (lin_addr - 256); + *tag = lin_addr - 256; + tag_calculated = true; + } else { + *sector = 0; + *pages_left = 256 - lin_addr; + *tag = lin_addr; + tag_calculated = true; + } + + return tag_calculated; +} + +static bool mf_ultralight_poller_ntag_i2c_addr_lin_to_tag_ntag_i2c_plus_1k( + uint16_t lin_addr, + uint8_t* sector, + uint8_t* tag, + uint8_t* pages_left) { + bool tag_calculated = false; + // 0 - 233: sector 0 + registers + // 234 - 235: session registers + + if(lin_addr > 235) { + *pages_left = 0; + } else if(lin_addr >= 234) { + *sector = 0; + *pages_left = 2 - (lin_addr - 234); + *tag = lin_addr - 234 + 236; + tag_calculated = true; + } else { + *sector = 0; + *pages_left = 234 - lin_addr; + *tag = lin_addr; + tag_calculated = true; + } + + return tag_calculated; +} + +static bool mf_ultralight_poller_ntag_i2c_addr_lin_to_tag_ntag_i2c_plus_2k( + uint16_t lin_addr, + uint8_t* sector, + uint8_t* tag, + uint8_t* pages_left) { + bool tag_calculated = false; + // 0 - 233: sector 0 + registers + // 234 - 235: session registers + // 236 - 491: sector 1 + + if(lin_addr > 491) { + *pages_left = 0; + } else if(lin_addr >= 236) { + *sector = 1; + *pages_left = 256 - (lin_addr - 236); + *tag = lin_addr - 236; + tag_calculated = true; + } else if(lin_addr >= 234) { + *sector = 0; + *pages_left = 2 - (lin_addr - 234); + *tag = lin_addr - 234 + 236; + tag_calculated = true; + } else { + *sector = 0; + *pages_left = 234 - lin_addr; + *tag = lin_addr; + tag_calculated = true; + } + + return tag_calculated; +} + +bool mf_ultralight_poller_ntag_i2c_addr_lin_to_tag( + MfUltralightPoller* instance, + uint16_t lin_addr, + uint8_t* sector, + uint8_t* tag, + uint8_t* pages_left) { + furi_assert(instance); + furi_assert(sector); + furi_assert(tag); + furi_assert(pages_left); + + bool tag_calculated = false; + + if(instance->data->type == MfUltralightTypeNTAGI2C1K) { + tag_calculated = mf_ultralight_poller_ntag_i2c_addr_lin_to_tag_ntag_i2c_1k( + lin_addr, sector, tag, pages_left); + } else if(instance->data->type == MfUltralightTypeNTAGI2C2K) { + tag_calculated = mf_ultralight_poller_ntag_i2c_addr_lin_to_tag_ntag_i2c_2k( + lin_addr, sector, tag, pages_left); + } else if(instance->data->type == MfUltralightTypeNTAGI2CPlus1K) { + tag_calculated = mf_ultralight_poller_ntag_i2c_addr_lin_to_tag_ntag_i2c_plus_1k( + lin_addr, sector, tag, pages_left); + } else if(instance->data->type == MfUltralightTypeNTAGI2CPlus2K) { + tag_calculated = mf_ultralight_poller_ntag_i2c_addr_lin_to_tag_ntag_i2c_plus_2k( + lin_addr, sector, tag, pages_left); + } + + return tag_calculated; +} + +MfUltralightPoller* mf_ultralight_poller_alloc(Iso14443_3aPoller* iso14443_3a_poller) { + furi_assert(iso14443_3a_poller); + + MfUltralightPoller* instance = malloc(sizeof(MfUltralightPoller)); + instance->iso14443_3a_poller = iso14443_3a_poller; + instance->tx_buffer = bit_buffer_alloc(MF_ULTRALIGHT_MAX_BUFF_SIZE); + instance->rx_buffer = bit_buffer_alloc(MF_ULTRALIGHT_MAX_BUFF_SIZE); + instance->data = mf_ultralight_alloc(); + + instance->mfu_event.data = &instance->mfu_event_data; + + instance->general_event.protocol = NfcProtocolMfUltralight; + instance->general_event.event_data = &instance->mfu_event; + instance->general_event.instance = instance; + + return instance; +} + +void mf_ultralight_poller_free(MfUltralightPoller* instance) { + furi_assert(instance); + furi_assert(instance->data); + furi_assert(instance->tx_buffer); + furi_assert(instance->rx_buffer); + + bit_buffer_free(instance->tx_buffer); + bit_buffer_free(instance->rx_buffer); + mf_ultralight_free(instance->data); + free(instance); +} + +static void mf_ultralight_poller_set_callback( + MfUltralightPoller* instance, + NfcGenericCallback callback, + void* context) { + furi_assert(instance); + furi_assert(callback); + + instance->callback = callback; + instance->context = context; +} + +const MfUltralightData* mf_ultralight_poller_get_data(MfUltralightPoller* instance) { + furi_assert(instance); + + return instance->data; +} + +static NfcCommand mf_ultralight_poller_handler_idle(MfUltralightPoller* instance) { + bit_buffer_reset(instance->tx_buffer); + bit_buffer_reset(instance->rx_buffer); + iso14443_3a_copy( + instance->data->iso14443_3a_data, + iso14443_3a_poller_get_data(instance->iso14443_3a_poller)); + instance->counters_read = 0; + instance->counters_total = 3; + instance->tearing_flag_read = 0; + instance->tearing_flag_total = 3; + instance->pages_read = 0; + instance->state = MfUltralightPollerStateReadVersion; + + return NfcCommandContinue; +} + +static NfcCommand mf_ultralight_poller_handler_read_version(MfUltralightPoller* instance) { + instance->error = mf_ultralight_poller_async_read_version(instance, &instance->data->version); + if(instance->error == MfUltralightErrorNone) { + FURI_LOG_D(TAG, "Read version success"); + instance->data->type = mf_ultralight_get_type_by_version(&instance->data->version); + instance->state = MfUltralightPollerStateGetFeatureSet; + } else { + FURI_LOG_D(TAG, "Didn't response. Check Ultralight C"); + iso14443_3a_poller_halt(instance->iso14443_3a_poller); + instance->state = MfUltralightPollerStateDetectMfulC; + } + + return NfcCommandContinue; +} + +static NfcCommand mf_ultralight_poller_handler_check_ultralight_c(MfUltralightPoller* instance) { + instance->error = mf_ultralight_poller_async_authenticate(instance); + if(instance->error == MfUltralightErrorNone) { + FURI_LOG_D(TAG, "Ultralight C detected"); + instance->data->type = MfUltralightTypeMfulC; + instance->state = MfUltralightPollerStateGetFeatureSet; + } else { + FURI_LOG_D(TAG, "Didn't response. Check NTAG 203"); + instance->state = MfUltralightPollerStateDetectNtag203; + } + iso14443_3a_poller_halt(instance->iso14443_3a_poller); + return NfcCommandContinue; +} + +static NfcCommand mf_ultralight_poller_handler_check_ntag_203(MfUltralightPoller* instance) { + MfUltralightPageReadCommandData data = {}; + instance->error = mf_ultralight_poller_async_read_page(instance, 41, &data); + if(instance->error == MfUltralightErrorNone) { + FURI_LOG_D(TAG, "NTAG203 detected"); + instance->data->type = MfUltralightTypeNTAG203; + } else { + FURI_LOG_D(TAG, "Original Ultralight detected"); + iso14443_3a_poller_halt(instance->iso14443_3a_poller); + instance->data->type = MfUltralightTypeUnknown; + } + instance->state = MfUltralightPollerStateGetFeatureSet; + + return NfcCommandContinue; +} + +static NfcCommand mf_ultralight_poller_handler_get_feature_set(MfUltralightPoller* instance) { + instance->feature_set = mf_ultralight_get_feature_support_set(instance->data->type); + instance->pages_total = mf_ultralight_get_pages_total(instance->data->type); + instance->data->pages_total = instance->pages_total; + FURI_LOG_D( + TAG, + "%s detected. Total pages: %d", + mf_ultralight_get_device_name(instance->data, NfcDeviceNameTypeFull), + instance->pages_total); + + instance->state = MfUltralightPollerStateReadSignature; + return NfcCommandContinue; +} + +static NfcCommand mf_ultralight_poller_handler_read_signature(MfUltralightPoller* instance) { + MfUltralightPollerState next_state = MfUltralightPollerStateAuth; + if(mf_ultralight_support_feature( + instance->feature_set, MfUltralightFeatureSupportReadSignature)) { + FURI_LOG_D(TAG, "Reading signature"); + instance->error = + mf_ultralight_poller_async_read_signature(instance, &instance->data->signature); + if(instance->error != MfUltralightErrorNone) { + FURI_LOG_D(TAG, "Read signature failed"); + next_state = MfUltralightPollerStateReadFailed; + } + } else { + FURI_LOG_D(TAG, "Skip reading signature"); + } + instance->state = next_state; + + return NfcCommandContinue; +} + +static NfcCommand mf_ultralight_poller_handler_read_counters(MfUltralightPoller* instance) { + do { + if(!mf_ultralight_support_feature( + instance->feature_set, MfUltralightFeatureSupportReadCounter) || + !mf_ultralight_is_counter_configured(instance->data)) { + FURI_LOG_D(TAG, "Skip reading counters"); + instance->state = MfUltralightPollerStateReadTearingFlags; + break; + } + + MfUltralightConfigPages* config = NULL; + mf_ultralight_get_config_page(instance->data, &config); + + if(config->access.nfc_cnt_pwd_prot && !instance->auth_context.auth_success) { + FURI_LOG_D(TAG, "Counter reading is protected with password"); + instance->state = MfUltralightPollerStateReadTearingFlags; + break; + } + + if(instance->counters_read == instance->counters_total) { + instance->state = MfUltralightPollerStateReadTearingFlags; + break; + } + + if(mf_ultralight_support_feature( + instance->feature_set, MfUltralightFeatureSupportSingleCounter)) { + instance->counters_read = 2; + } + + FURI_LOG_D(TAG, "Reading counter %d", instance->counters_read); + instance->error = mf_ultralight_poller_async_read_counter( + instance, instance->counters_read, &instance->data->counter[instance->counters_read]); + if(instance->error != MfUltralightErrorNone) { + FURI_LOG_D(TAG, "Failed to read %d counter", instance->counters_read); + instance->state = MfUltralightPollerStateReadTearingFlags; + } else { + instance->counters_read++; + } + + } while(false); + + return NfcCommandContinue; +} + +static NfcCommand mf_ultralight_poller_handler_read_tearing_flags(MfUltralightPoller* instance) { + if(mf_ultralight_support_feature( + instance->feature_set, + MfUltralightFeatureSupportCheckTearingFlag | MfUltralightFeatureSupportSingleCounter)) { + if(instance->tearing_flag_read == instance->tearing_flag_total) { + instance->state = MfUltralightPollerStateTryDefaultPass; + } else { + bool single_counter = mf_ultralight_support_feature( + instance->feature_set, MfUltralightFeatureSupportSingleCounter); + if(single_counter) instance->tearing_flag_read = 2; + + FURI_LOG_D(TAG, "Reading tearing flag %d", instance->tearing_flag_read); + instance->error = mf_ultralight_poller_async_read_tearing_flag( + instance, + instance->tearing_flag_read, + &instance->data->tearing_flag[instance->tearing_flag_read]); + if((instance->error == MfUltralightErrorProtocol) && single_counter) { + instance->tearing_flag_read++; + } else if(instance->error != MfUltralightErrorNone) { + FURI_LOG_D(TAG, "Reading tearing flag %d failed", instance->tearing_flag_read); + instance->state = MfUltralightPollerStateTryDefaultPass; + } else { + instance->tearing_flag_read++; + } + } + } else { + FURI_LOG_D(TAG, "Skip reading tearing flags"); + instance->state = MfUltralightPollerStateTryDefaultPass; + } + + return NfcCommandContinue; +} + +static NfcCommand mf_ultralight_poller_handler_auth(MfUltralightPoller* instance) { + NfcCommand command = NfcCommandContinue; + if(mf_ultralight_support_feature( + instance->feature_set, MfUltralightFeatureSupportPasswordAuth)) { + instance->mfu_event.type = MfUltralightPollerEventTypeAuthRequest; + + command = instance->callback(instance->general_event, instance->context); + if(!instance->mfu_event.data->auth_context.skip_auth) { + instance->auth_context.password = instance->mfu_event.data->auth_context.password; + uint32_t pass = nfc_util_bytes2num( + instance->auth_context.password.data, sizeof(MfUltralightAuthPassword)); + FURI_LOG_D(TAG, "Trying to authenticate with password %08lX", pass); + instance->error = + mf_ultralight_poller_async_auth_pwd(instance, &instance->auth_context); + if(instance->error == MfUltralightErrorNone) { + FURI_LOG_D(TAG, "Auth success"); + instance->auth_context.auth_success = true; + instance->mfu_event.data->auth_context = instance->auth_context; + instance->mfu_event.type = MfUltralightPollerEventTypeAuthSuccess; + command = instance->callback(instance->general_event, instance->context); + } else { + FURI_LOG_D(TAG, "Auth failed"); + instance->auth_context.auth_success = false; + instance->mfu_event.type = MfUltralightPollerEventTypeAuthFailed; + command = instance->callback(instance->general_event, instance->context); + iso14443_3a_poller_halt(instance->iso14443_3a_poller); + } + } + } + instance->state = MfUltralightPollerStateReadPages; + + return command; +} + +static NfcCommand mf_ultralight_poller_handler_read_pages(MfUltralightPoller* instance) { + MfUltralightPageReadCommandData data = {}; + uint16_t start_page = instance->pages_read; + if(MF_ULTRALIGHT_IS_NTAG_I2C(instance->data->type)) { + uint8_t tag = 0; + uint8_t sector = 0; + uint8_t pages_left = 0; + if(mf_ultralight_poller_ntag_i2c_addr_lin_to_tag( + instance, start_page, §or, &tag, &pages_left)) { + instance->error = + mf_ultralight_poller_async_read_page_from_sector(instance, sector, tag, &data); + } else { + FURI_LOG_D(TAG, "Failed to calculate sector and tag from %d page", start_page); + instance->error = MfUltralightErrorProtocol; + } + } else { + instance->error = mf_ultralight_poller_async_read_page(instance, start_page, &data); + } + + if(instance->error == MfUltralightErrorNone) { + for(size_t i = 0; i < 4; i++) { + if(start_page + i < instance->pages_total) { + FURI_LOG_D(TAG, "Read page %d success", start_page + i); + instance->data->page[start_page + i] = data.page[i]; + instance->pages_read++; + instance->data->pages_read = instance->pages_read; + } + } + if(instance->pages_read == instance->pages_total) { + instance->state = MfUltralightPollerStateReadCounters; + } + } else { + FURI_LOG_D(TAG, "Read page %d failed", instance->pages_read); + if(instance->pages_read) { + instance->state = MfUltralightPollerStateReadCounters; + } else { + instance->state = MfUltralightPollerStateReadFailed; + } + } + + return NfcCommandContinue; +} + +static NfcCommand mf_ultralight_poller_handler_try_default_pass(MfUltralightPoller* instance) { + do { + if(!mf_ultralight_support_feature( + instance->feature_set, MfUltralightFeatureSupportPasswordAuth)) + break; + + MfUltralightConfigPages* config = NULL; + mf_ultralight_get_config_page(instance->data, &config); + if(instance->auth_context.auth_success) { + config->password = instance->auth_context.password; + config->pack = instance->auth_context.pack; + } else if(config->access.authlim == 0) { + FURI_LOG_D(TAG, "No limits in authentication. Trying default password"); + nfc_util_num2bytes( + MF_ULTRALIGHT_DEFAULT_PASSWORD, + sizeof(MfUltralightAuthPassword), + instance->auth_context.password.data); + instance->error = + mf_ultralight_poller_async_auth_pwd(instance, &instance->auth_context); + if(instance->error == MfUltralightErrorNone) { + FURI_LOG_D(TAG, "Default password detected"); + nfc_util_num2bytes( + MF_ULTRALIGHT_DEFAULT_PASSWORD, + sizeof(MfUltralightAuthPassword), + config->password.data); + config->pack = instance->auth_context.pack; + } + } + + if(instance->pages_read != instance->pages_total) { + // Probably password protected, fix AUTH0 and PROT so before AUTH0 + // can be written and since AUTH0 won't be readable, like on the + // original card + config->auth0 = instance->pages_read; + config->access.prot = true; + } + } while(false); + + instance->state = MfUltralightPollerStateReadSuccess; + return NfcCommandContinue; +} + +static NfcCommand mf_ultralight_poller_handler_read_fail(MfUltralightPoller* instance) { + FURI_LOG_D(TAG, "Read Failed"); + iso14443_3a_poller_halt(instance->iso14443_3a_poller); + instance->mfu_event.data->error = instance->error; + NfcCommand command = instance->callback(instance->general_event, instance->context); + instance->state = MfUltralightPollerStateIdle; + return command; +} + +static NfcCommand mf_ultralight_poller_handler_read_success(MfUltralightPoller* instance) { + FURI_LOG_D(TAG, "Read success"); + iso14443_3a_poller_halt(instance->iso14443_3a_poller); + instance->mfu_event.type = MfUltralightPollerEventTypeReadSuccess; + NfcCommand command = instance->callback(instance->general_event, instance->context); + return command; +} + +static const MfUltralightPollerReadHandler + mf_ultralight_poller_read_handler[MfUltralightPollerStateNum] = { + [MfUltralightPollerStateIdle] = mf_ultralight_poller_handler_idle, + [MfUltralightPollerStateReadVersion] = mf_ultralight_poller_handler_read_version, + [MfUltralightPollerStateDetectMfulC] = mf_ultralight_poller_handler_check_ultralight_c, + [MfUltralightPollerStateDetectNtag203] = mf_ultralight_poller_handler_check_ntag_203, + [MfUltralightPollerStateGetFeatureSet] = mf_ultralight_poller_handler_get_feature_set, + [MfUltralightPollerStateReadSignature] = mf_ultralight_poller_handler_read_signature, + [MfUltralightPollerStateReadCounters] = mf_ultralight_poller_handler_read_counters, + [MfUltralightPollerStateReadTearingFlags] = + mf_ultralight_poller_handler_read_tearing_flags, + [MfUltralightPollerStateAuth] = mf_ultralight_poller_handler_auth, + [MfUltralightPollerStateTryDefaultPass] = mf_ultralight_poller_handler_try_default_pass, + [MfUltralightPollerStateReadPages] = mf_ultralight_poller_handler_read_pages, + [MfUltralightPollerStateReadFailed] = mf_ultralight_poller_handler_read_fail, + [MfUltralightPollerStateReadSuccess] = mf_ultralight_poller_handler_read_success, + +}; + +static NfcCommand mf_ultralight_poller_run(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.event_data); + furi_assert(event.protocol == NfcProtocolIso14443_3a); + + MfUltralightPoller* instance = context; + furi_assert(instance->callback); + + const Iso14443_3aPollerEvent* iso14443_3a_event = event.event_data; + + NfcCommand command = NfcCommandContinue; + + if(iso14443_3a_event->type == Iso14443_3aPollerEventTypeReady) { + command = mf_ultralight_poller_read_handler[instance->state](instance); + } else if(iso14443_3a_event->type == Iso14443_3aPollerEventTypeError) { + instance->mfu_event.type = MfUltralightPollerEventTypeReadFailed; + command = instance->callback(instance->general_event, instance->context); + } + + return command; +} + +static bool mf_ultralight_poller_detect(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.event_data); + furi_assert(event.protocol == NfcProtocolIso14443_3a); + + bool protocol_detected = false; + MfUltralightPoller* instance = context; + const Iso14443_3aPollerEvent* iso14443_3a_event = event.event_data; + + if(iso14443_3a_event->type == Iso14443_3aPollerEventTypeReady) { + MfUltralightPageReadCommandData read_page_cmd_data = {}; + MfUltralightError error = + mf_ultralight_poller_async_read_page(instance, 0, &read_page_cmd_data); + protocol_detected = (error == MfUltralightErrorNone); + iso14443_3a_poller_halt(instance->iso14443_3a_poller); + } + + return protocol_detected; +} + +const NfcPollerBase mf_ultralight_poller = { + .alloc = (NfcPollerAlloc)mf_ultralight_poller_alloc, + .free = (NfcPollerFree)mf_ultralight_poller_free, + .set_callback = (NfcPollerSetCallback)mf_ultralight_poller_set_callback, + .run = (NfcPollerRun)mf_ultralight_poller_run, + .detect = (NfcPollerDetect)mf_ultralight_poller_detect, + .get_data = (NfcPollerGetData)mf_ultralight_poller_get_data, +}; diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h new file mode 100644 index 00000000000..2d4ef33ea94 --- /dev/null +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h @@ -0,0 +1,41 @@ +#pragma once + +#include "mf_ultralight.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct MfUltralightPoller MfUltralightPoller; + +typedef enum { + MfUltralightPollerEventTypeAuthRequest, + MfUltralightPollerEventTypeAuthSuccess, + MfUltralightPollerEventTypeAuthFailed, + MfUltralightPollerEventTypeReadSuccess, + MfUltralightPollerEventTypeReadFailed, +} MfUltralightPollerEventType; + +typedef struct { + MfUltralightAuthPassword password; + MfUltralightAuthPack pack; + bool auth_success; + bool skip_auth; +} MfUltralightPollerAuthContext; + +typedef struct { + union { + MfUltralightPollerAuthContext auth_context; + MfUltralightError error; + }; +} MfUltralightPollerEventData; + +typedef struct { + MfUltralightPollerEventType type; + MfUltralightPollerEventData* data; +} MfUltralightPollerEvent; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_defs.h b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_defs.h new file mode 100644 index 00000000000..80b0d7b6eaa --- /dev/null +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_defs.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +extern const NfcPollerBase mf_ultralight_poller; diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.c new file mode 100644 index 00000000000..795b03e65f9 --- /dev/null +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.c @@ -0,0 +1,308 @@ +#include "mf_ultralight_poller_i.h" + +#include + +#define TAG "MfUltralightPoller" + +MfUltralightError mf_ultralight_process_error(Iso14443_3aError error) { + MfUltralightError ret = MfUltralightErrorNone; + + switch(error) { + case Iso14443_3aErrorNone: + ret = MfUltralightErrorNone; + break; + case Iso14443_3aErrorNotPresent: + ret = MfUltralightErrorNotPresent; + break; + case Iso14443_3aErrorColResFailed: + case Iso14443_3aErrorCommunication: + case Iso14443_3aErrorWrongCrc: + ret = MfUltralightErrorProtocol; + break; + case Iso14443_3aErrorTimeout: + ret = MfUltralightErrorTimeout; + break; + default: + ret = MfUltralightErrorProtocol; + break; + } + + return ret; +} + +MfUltralightError mf_ultralight_poller_async_auth_pwd( + MfUltralightPoller* instance, + MfUltralightPollerAuthContext* data) { + uint8_t auth_cmd[5] = {MF_ULTRALIGHT_CMD_PWD_AUTH}; //-V1009 + memccpy(&auth_cmd[1], data->password.data, 0, MF_ULTRALIGHT_AUTH_PASSWORD_SIZE); + bit_buffer_copy_bytes(instance->tx_buffer, auth_cmd, sizeof(auth_cmd)); + + MfUltralightError ret = MfUltralightErrorNone; + Iso14443_3aError error = Iso14443_3aErrorNone; + do { + error = iso14443_3a_poller_send_standard_frame( + instance->iso14443_3a_poller, + instance->tx_buffer, + instance->rx_buffer, + MF_ULTRALIGHT_POLLER_STANDARD_FWT_FC); + if(error != Iso14443_3aErrorNone) { + ret = mf_ultralight_process_error(error); + break; + } + if(bit_buffer_get_size_bytes(instance->rx_buffer) != MF_ULTRALIGHT_AUTH_PACK_SIZE) { + ret = MfUltralightErrorAuth; + break; + } + bit_buffer_write_bytes(instance->rx_buffer, data->pack.data, MF_ULTRALIGHT_AUTH_PACK_SIZE); + } while(false); + + return ret; +} + +MfUltralightError mf_ultralight_poller_async_authenticate(MfUltralightPoller* instance) { + uint8_t auth_cmd[2] = {MF_ULTRALIGHT_CMD_AUTH, 0x00}; + bit_buffer_copy_bytes(instance->tx_buffer, auth_cmd, sizeof(auth_cmd)); + + MfUltralightError ret = MfUltralightErrorNone; + Iso14443_3aError error = Iso14443_3aErrorNone; + do { + error = iso14443_3a_poller_send_standard_frame( + instance->iso14443_3a_poller, + instance->tx_buffer, + instance->rx_buffer, + MF_ULTRALIGHT_POLLER_STANDARD_FWT_FC); + if(error != Iso14443_3aErrorNone) { + ret = mf_ultralight_process_error(error); + break; + } + if((bit_buffer_get_size_bytes(instance->rx_buffer) != MF_ULTRALIGHT_AUTH_RESPONSE_SIZE) && + (bit_buffer_get_byte(instance->rx_buffer, 0) != 0xAF)) { + ret = MfUltralightErrorAuth; + break; + } + //Save encrypted PICC random number RndB here if needed + } while(false); + + return ret; +} + +MfUltralightError mf_ultralight_poller_async_read_page_from_sector( + MfUltralightPoller* instance, + uint8_t sector, + uint8_t tag, + MfUltralightPageReadCommandData* data) { + MfUltralightError ret = MfUltralightErrorNone; + Iso14443_3aError error = Iso14443_3aErrorNone; + + do { + const uint8_t select_sector_cmd[2] = {MF_ULTRALIGHT_CMD_SECTOR_SELECT, 0xff}; + bit_buffer_copy_bytes(instance->tx_buffer, select_sector_cmd, sizeof(select_sector_cmd)); + error = iso14443_3a_poller_send_standard_frame( + instance->iso14443_3a_poller, + instance->tx_buffer, + instance->rx_buffer, + MF_ULTRALIGHT_POLLER_STANDARD_FWT_FC); + if(error != Iso14443_3aErrorWrongCrc) { + FURI_LOG_D(TAG, "Failed to issue sector select command"); + ret = mf_ultralight_process_error(error); + break; + } + + const uint8_t read_sector_cmd[4] = {sector, 0x00, 0x00, 0x00}; + bit_buffer_copy_bytes(instance->tx_buffer, read_sector_cmd, sizeof(read_sector_cmd)); + error = iso14443_3a_poller_send_standard_frame( + instance->iso14443_3a_poller, + instance->tx_buffer, + instance->rx_buffer, + MF_ULTRALIGHT_POLLER_STANDARD_FWT_FC); + if(error != Iso14443_3aErrorTimeout) { + // This is NOT a typo! The tag ACKs by not sending a response within 1ms. + FURI_LOG_D(TAG, "Sector %u select NAK'd", sector); + ret = MfUltralightErrorProtocol; + break; + } + + ret = mf_ultralight_poller_async_read_page(instance, tag, data); + } while(false); + + return ret; +} + +MfUltralightError mf_ultralight_poller_async_read_page( + MfUltralightPoller* instance, + uint8_t start_page, + MfUltralightPageReadCommandData* data) { + MfUltralightError ret = MfUltralightErrorNone; + Iso14443_3aError error = Iso14443_3aErrorNone; + + do { + uint8_t read_page_cmd[2] = {MF_ULTRALIGHT_CMD_READ_PAGE, start_page}; + bit_buffer_copy_bytes(instance->tx_buffer, read_page_cmd, sizeof(read_page_cmd)); + error = iso14443_3a_poller_send_standard_frame( + instance->iso14443_3a_poller, + instance->tx_buffer, + instance->rx_buffer, + MF_ULTRALIGHT_POLLER_STANDARD_FWT_FC); + if(error != Iso14443_3aErrorNone) { + ret = mf_ultralight_process_error(error); + break; + } + if(bit_buffer_get_size_bytes(instance->rx_buffer) != + sizeof(MfUltralightPageReadCommandData)) { + ret = MfUltralightErrorProtocol; + break; + } + bit_buffer_write_bytes(instance->rx_buffer, data, sizeof(MfUltralightPageReadCommandData)); + } while(false); + + return ret; +} + +MfUltralightError mf_ultralight_poller_async_write_page( + MfUltralightPoller* instance, + uint8_t page, + MfUltralightPage* data) { + MfUltralightError ret = MfUltralightErrorNone; + Iso14443_3aError error = Iso14443_3aErrorNone; + + do { + uint8_t write_page_cmd[MF_ULTRALIGHT_PAGE_SIZE + 2] = {MF_ULTRALIGHT_CMD_WRITE_PAGE, page}; + memcpy(&write_page_cmd[2], data->data, MF_ULTRALIGHT_PAGE_SIZE); + bit_buffer_copy_bytes(instance->tx_buffer, write_page_cmd, sizeof(write_page_cmd)); + error = iso14443_3a_poller_send_standard_frame( + instance->iso14443_3a_poller, + instance->tx_buffer, + instance->rx_buffer, + MF_ULTRALIGHT_POLLER_STANDARD_FWT_FC); + if(error != Iso14443_3aErrorWrongCrc) { + ret = mf_ultralight_process_error(error); + break; + } + if(bit_buffer_get_size(instance->rx_buffer) != 4) { + ret = MfUltralightErrorProtocol; + break; + } + if(!bit_buffer_starts_with_byte(instance->rx_buffer, MF_ULTRALIGHT_CMD_ACK)) { + ret = MfUltralightErrorProtocol; + break; + } + } while(false); + + return ret; +} + +MfUltralightError mf_ultralight_poller_async_read_version( + MfUltralightPoller* instance, + MfUltralightVersion* data) { + MfUltralightError ret = MfUltralightErrorNone; + Iso14443_3aError error = Iso14443_3aErrorNone; + + do { + const uint8_t get_version_cmd = MF_ULTRALIGHT_CMD_GET_VERSION; + bit_buffer_copy_bytes(instance->tx_buffer, &get_version_cmd, sizeof(get_version_cmd)); + error = iso14443_3a_poller_send_standard_frame( + instance->iso14443_3a_poller, + instance->tx_buffer, + instance->rx_buffer, + MF_ULTRALIGHT_POLLER_STANDARD_FWT_FC); + if(error != Iso14443_3aErrorNone) { + ret = mf_ultralight_process_error(error); + break; + } + if(bit_buffer_get_size_bytes(instance->rx_buffer) != sizeof(MfUltralightVersion)) { + FURI_LOG_I( + TAG, "Read Version failed: %zu", bit_buffer_get_size_bytes(instance->rx_buffer)); + ret = MfUltralightErrorProtocol; + break; + } + bit_buffer_write_bytes(instance->rx_buffer, data, sizeof(MfUltralightVersion)); + } while(false); + + return ret; +} + +MfUltralightError mf_ultralight_poller_async_read_signature( + MfUltralightPoller* instance, + MfUltralightSignature* data) { + MfUltralightError ret = MfUltralightErrorNone; + Iso14443_3aError error = Iso14443_3aErrorNone; + + do { + const uint8_t read_signature_cmd[2] = {MF_ULTRALIGHT_CMD_READ_SIG, 0x00}; + bit_buffer_copy_bytes(instance->tx_buffer, read_signature_cmd, sizeof(read_signature_cmd)); + error = iso14443_3a_poller_send_standard_frame( + instance->iso14443_3a_poller, + instance->tx_buffer, + instance->rx_buffer, + MF_ULTRALIGHT_POLLER_STANDARD_FWT_FC); + if(error != Iso14443_3aErrorNone) { + ret = mf_ultralight_process_error(error); + break; + } + if(bit_buffer_get_size_bytes(instance->rx_buffer) != sizeof(MfUltralightSignature)) { + ret = MfUltralightErrorProtocol; + break; + } + bit_buffer_write_bytes(instance->rx_buffer, data, sizeof(MfUltralightSignature)); + } while(false); + + return ret; +} + +MfUltralightError mf_ultralight_poller_async_read_counter( + MfUltralightPoller* instance, + uint8_t counter_num, + MfUltralightCounter* data) { + MfUltralightError ret = MfUltralightErrorNone; + Iso14443_3aError error = Iso14443_3aErrorNone; + + do { + uint8_t read_counter_cmd[2] = {MF_ULTRALIGHT_CMD_READ_CNT, counter_num}; + bit_buffer_copy_bytes(instance->tx_buffer, read_counter_cmd, sizeof(read_counter_cmd)); + error = iso14443_3a_poller_send_standard_frame( + instance->iso14443_3a_poller, + instance->tx_buffer, + instance->rx_buffer, + MF_ULTRALIGHT_POLLER_STANDARD_FWT_FC); + if(error != Iso14443_3aErrorNone) { + ret = mf_ultralight_process_error(error); + break; + } + if(bit_buffer_get_size_bytes(instance->rx_buffer) != MF_ULTRALIGHT_COUNTER_SIZE) { + ret = MfUltralightErrorProtocol; + break; + } + bit_buffer_write_bytes(instance->rx_buffer, data->data, MF_ULTRALIGHT_COUNTER_SIZE); + } while(false); + + return ret; +} + +MfUltralightError mf_ultralight_poller_async_read_tearing_flag( + MfUltralightPoller* instance, + uint8_t tearing_falg_num, + MfUltralightTearingFlag* data) { + MfUltralightError ret = MfUltralightErrorNone; + Iso14443_3aError error = Iso14443_3aErrorNone; + + do { + uint8_t check_tearing_cmd[2] = {MF_ULTRALIGHT_CMD_CHECK_TEARING, tearing_falg_num}; + bit_buffer_copy_bytes(instance->tx_buffer, check_tearing_cmd, sizeof(check_tearing_cmd)); + error = iso14443_3a_poller_send_standard_frame( + instance->iso14443_3a_poller, + instance->tx_buffer, + instance->rx_buffer, + MF_ULTRALIGHT_POLLER_STANDARD_FWT_FC); + if(error != Iso14443_3aErrorNone) { + ret = mf_ultralight_process_error(error); + break; + } + if(bit_buffer_get_size_bytes(instance->rx_buffer) != sizeof(MfUltralightTearingFlag)) { + ret = MfUltralightErrorProtocol; + break; + } + bit_buffer_write_bytes(instance->rx_buffer, data, sizeof(MfUltralightTearingFlag)); + } while(false); + + return ret; +} diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.h b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.h new file mode 100644 index 00000000000..13490cf1a9d --- /dev/null +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.h @@ -0,0 +1,148 @@ +#pragma once + +#include "mf_ultralight_poller.h" +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define MF_ULTRALIGHT_POLLER_STANDARD_FWT_FC (60000) +#define MF_ULTRALIGHT_MAX_BUFF_SIZE (64) + +#define MF_ULTRALIGHT_DEFAULT_PASSWORD (0xffffffffUL) + +#define MF_ULTRALIGHT_IS_NTAG_I2C(type) \ + (((type) == MfUltralightTypeNTAGI2C1K) || ((type) == MfUltralightTypeNTAGI2C2K) || \ + ((type) == MfUltralightTypeNTAGI2CPlus1K) || ((type) == MfUltralightTypeNTAGI2CPlus2K)) + +typedef struct { + MfUltralightPage page; + uint8_t page_to_write; +} MfUltralightPollerWritePageCommand; + +typedef struct { + MfUltralightPageReadCommandData data; + uint8_t start_page; +} MfUltralightPollerReadPageCommand; + +typedef struct { + MfUltralightCounter data; + uint8_t counter_num; +} MfUltralightPollerReadCounterCommand; + +typedef struct { + MfUltralightTearingFlag data; + uint8_t tearing_flag_num; +} MfUltralightPollerReadTearingFlagCommand; + +typedef union { + MfUltralightPollerWritePageCommand write_cmd; + MfUltralightPollerReadPageCommand read_cmd; + MfUltralightVersion version; + MfUltralightSignature signature; + MfUltralightPollerReadCounterCommand counter_cmd; + MfUltralightPollerReadTearingFlagCommand tearing_flag_cmd; + MfUltralightData* data; +} MfUltralightPollerContextData; + +typedef enum { + MfUltralightPollerStateIdle, + MfUltralightPollerStateReadVersion, + MfUltralightPollerStateDetectMfulC, + MfUltralightPollerStateDetectNtag203, + MfUltralightPollerStateGetFeatureSet, + MfUltralightPollerStateReadSignature, + MfUltralightPollerStateReadCounters, + MfUltralightPollerStateReadTearingFlags, + MfUltralightPollerStateAuth, + MfUltralightPollerStateReadPages, + MfUltralightPollerStateTryDefaultPass, + MfUltralightPollerStateReadFailed, + MfUltralightPollerStateReadSuccess, + + MfUltralightPollerStateNum, +} MfUltralightPollerState; + +struct MfUltralightPoller { + Iso14443_3aPoller* iso14443_3a_poller; + MfUltralightPollerState state; + BitBuffer* tx_buffer; + BitBuffer* rx_buffer; + MfUltralightData* data; + MfUltralightPollerAuthContext auth_context; + uint32_t feature_set; + uint16_t pages_read; + uint16_t pages_total; + uint8_t counters_read; + uint8_t counters_total; + uint8_t tearing_flag_read; + uint8_t tearing_flag_total; + MfUltralightError error; + + NfcGenericEvent general_event; + MfUltralightPollerEvent mfu_event; + MfUltralightPollerEventData mfu_event_data; + NfcGenericCallback callback; + void* context; +}; + +MfUltralightError mf_ultralight_process_error(Iso14443_3aError error); + +MfUltralightPoller* mf_ultralight_poller_alloc(Iso14443_3aPoller* iso14443_3a_poller); + +void mf_ultralight_poller_free(MfUltralightPoller* instance); + +const MfUltralightData* mf_ultralight_poller_get_data(MfUltralightPoller* instance); + +bool mf_ultralight_poller_ntag_i2c_addr_lin_to_tag( + MfUltralightPoller* instance, + uint16_t lin_addr, + uint8_t* sector, + uint8_t* tag, + uint8_t* pages_left); + +MfUltralightError mf_ultralight_poller_async_auth_pwd( + MfUltralightPoller* instance, + MfUltralightPollerAuthContext* data); + +MfUltralightError mf_ultralight_poller_async_authenticate(MfUltralightPoller* instance); + +MfUltralightError mf_ultralight_poller_async_read_page( + MfUltralightPoller* instance, + uint8_t start_page, + MfUltralightPageReadCommandData* data); + +MfUltralightError mf_ultralight_poller_async_read_page_from_sector( + MfUltralightPoller* instance, + uint8_t sector, + uint8_t tag, + MfUltralightPageReadCommandData* data); + +MfUltralightError mf_ultralight_poller_async_write_page( + MfUltralightPoller* instance, + uint8_t page, + MfUltralightPage* data); + +MfUltralightError mf_ultralight_poller_async_read_version( + MfUltralightPoller* instance, + MfUltralightVersion* data); + +MfUltralightError mf_ultralight_poller_async_read_signature( + MfUltralightPoller* instance, + MfUltralightSignature* data); + +MfUltralightError mf_ultralight_poller_async_read_counter( + MfUltralightPoller* instance, + uint8_t counter_num, + MfUltralightCounter* data); + +MfUltralightError mf_ultralight_poller_async_read_tearing_flag( + MfUltralightPoller* instance, + uint8_t tearing_falg_num, + MfUltralightTearingFlag* data); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync_api.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync_api.c new file mode 100644 index 00000000000..739df597d2c --- /dev/null +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync_api.c @@ -0,0 +1,287 @@ +#include "mf_ultralight_poller_i.h" + +#include + +#include + +#define MF_ULTRALIGHT_POLLER_COMPLETE_EVENT (1UL << 0) + +typedef enum { + MfUltralightPollerCmdTypeReadPage, + MfUltralightPollerCmdTypeWritePage, + MfUltralightPollerCmdTypeReadVersion, + MfUltralightPollerCmdTypeReadSignature, + MfUltralightPollerCmdTypeReadCounter, + MfUltralightPollerCmdTypeReadTearingFlag, + + MfUltralightPollerCmdTypeNum, +} MfUltralightPollerCmdType; + +typedef struct { + MfUltralightPollerCmdType cmd_type; + FuriThreadId thread_id; + MfUltralightError error; + MfUltralightPollerContextData data; +} MfUltralightPollerContext; + +typedef MfUltralightError (*MfUltralightPollerCmdHandler)( + MfUltralightPoller* poller, + MfUltralightPollerContextData* data); + +MfUltralightError mf_ultralight_poller_read_page_handler( + MfUltralightPoller* poller, + MfUltralightPollerContextData* data) { + return mf_ultralight_poller_async_read_page( + poller, data->read_cmd.start_page, &data->read_cmd.data); +} + +MfUltralightError mf_ultralight_poller_write_page_handler( + MfUltralightPoller* poller, + MfUltralightPollerContextData* data) { + return mf_ultralight_poller_async_write_page( + poller, data->write_cmd.page_to_write, &data->write_cmd.page); +} + +MfUltralightError mf_ultralight_poller_read_version_handler( + MfUltralightPoller* poller, + MfUltralightPollerContextData* data) { + return mf_ultralight_poller_async_read_version(poller, &data->version); +} + +MfUltralightError mf_ultralight_poller_read_signature_handler( + MfUltralightPoller* poller, + MfUltralightPollerContextData* data) { + return mf_ultralight_poller_async_read_signature(poller, &data->signature); +} + +MfUltralightError mf_ultralight_poller_read_counter_handler( + MfUltralightPoller* poller, + MfUltralightPollerContextData* data) { + return mf_ultralight_poller_async_read_counter( + poller, data->counter_cmd.counter_num, &data->counter_cmd.data); +} + +MfUltralightError mf_ultralight_poller_read_tearing_flag_handler( + MfUltralightPoller* poller, + MfUltralightPollerContextData* data) { + return mf_ultralight_poller_async_read_tearing_flag( + poller, data->tearing_flag_cmd.tearing_flag_num, &data->tearing_flag_cmd.data); +} + +static const MfUltralightPollerCmdHandler + mf_ultralight_poller_cmd_handlers[MfUltralightPollerCmdTypeNum] = { + [MfUltralightPollerCmdTypeReadPage] = mf_ultralight_poller_read_page_handler, + [MfUltralightPollerCmdTypeWritePage] = mf_ultralight_poller_write_page_handler, + [MfUltralightPollerCmdTypeReadVersion] = mf_ultralight_poller_read_version_handler, + [MfUltralightPollerCmdTypeReadSignature] = mf_ultralight_poller_read_signature_handler, + [MfUltralightPollerCmdTypeReadCounter] = mf_ultralight_poller_read_counter_handler, + [MfUltralightPollerCmdTypeReadTearingFlag] = + mf_ultralight_poller_read_tearing_flag_handler, +}; + +static NfcCommand mf_ultralight_poller_cmd_callback(NfcGenericEvent event, void* context) { + furi_assert(event.instance); + furi_assert(event.protocol == NfcProtocolIso14443_3a); + furi_assert(event.event_data); + furi_assert(context); + + MfUltralightPollerContext* poller_context = context; + Iso14443_3aPollerEvent* iso14443_3a_event = event.event_data; + Iso14443_3aPoller* iso14443_3a_poller = event.instance; + MfUltralightPoller* mfu_poller = mf_ultralight_poller_alloc(iso14443_3a_poller); + + if(iso14443_3a_event->type == Iso14443_3aPollerEventTypeReady) { + poller_context->error = mf_ultralight_poller_cmd_handlers[poller_context->cmd_type]( + mfu_poller, &poller_context->data); + } else if(iso14443_3a_event->type == Iso14443_3aPollerEventTypeError) { + poller_context->error = mf_ultralight_process_error(iso14443_3a_event->data->error); + } + + furi_thread_flags_set(poller_context->thread_id, MF_ULTRALIGHT_POLLER_COMPLETE_EVENT); + + mf_ultralight_poller_free(mfu_poller); + + return NfcCommandStop; +} + +static MfUltralightError + mf_ultralight_poller_cmd_execute(Nfc* nfc, MfUltralightPollerContext* poller_ctx) { + furi_assert(poller_ctx->cmd_type < MfUltralightPollerCmdTypeNum); + + poller_ctx->thread_id = furi_thread_get_current_id(); + + NfcPoller* poller = nfc_poller_alloc(nfc, NfcProtocolIso14443_3a); + nfc_poller_start(poller, mf_ultralight_poller_cmd_callback, poller_ctx); + furi_thread_flags_wait(MF_ULTRALIGHT_POLLER_COMPLETE_EVENT, FuriFlagWaitAny, FuriWaitForever); + furi_thread_flags_clear(MF_ULTRALIGHT_POLLER_COMPLETE_EVENT); + + nfc_poller_stop(poller); + nfc_poller_free(poller); + + return poller_ctx->error; +} + +MfUltralightError mf_ultralight_poller_read_page(Nfc* nfc, uint16_t page, MfUltralightPage* data) { + furi_assert(nfc); + furi_assert(data); + + MfUltralightPollerContext poller_context = { + .cmd_type = MfUltralightPollerCmdTypeReadPage, + .data.read_cmd.start_page = page, + }; + + MfUltralightError error = mf_ultralight_poller_cmd_execute(nfc, &poller_context); + + if(error == MfUltralightErrorNone) { + *data = poller_context.data.read_cmd.data.page[0]; + } + + return error; +} + +MfUltralightError + mf_ultralight_poller_write_page(Nfc* nfc, uint16_t page, MfUltralightPage* data) { + furi_assert(nfc); + furi_assert(data); + + MfUltralightPollerContext poller_context = { + .cmd_type = MfUltralightPollerCmdTypeWritePage, + .data.write_cmd = + { + .page_to_write = page, + .page = *data, + }, + }; + + MfUltralightError error = mf_ultralight_poller_cmd_execute(nfc, &poller_context); + + return error; +} + +MfUltralightError mf_ultralight_poller_read_version(Nfc* nfc, MfUltralightVersion* data) { + furi_assert(nfc); + furi_assert(data); + + MfUltralightPollerContext poller_context = { + .cmd_type = MfUltralightPollerCmdTypeReadVersion, + }; + + MfUltralightError error = mf_ultralight_poller_cmd_execute(nfc, &poller_context); + + if(error == MfUltralightErrorNone) { + *data = poller_context.data.version; + } + + return error; +} + +MfUltralightError mf_ultralight_poller_read_signature(Nfc* nfc, MfUltralightSignature* data) { + furi_assert(nfc); + furi_assert(data); + + MfUltralightPollerContext poller_context = { + .cmd_type = MfUltralightPollerCmdTypeReadSignature, + }; + + MfUltralightError error = mf_ultralight_poller_cmd_execute(nfc, &poller_context); + + if(error == MfUltralightErrorNone) { + *data = poller_context.data.signature; + } + + return error; +} + +MfUltralightError + mf_ultralight_poller_read_counter(Nfc* nfc, uint8_t counter_num, MfUltralightCounter* data) { + furi_assert(nfc); + furi_assert(data); + + MfUltralightPollerContext poller_context = { + .cmd_type = MfUltralightPollerCmdTypeReadCounter, + .data.counter_cmd.counter_num = counter_num, + }; + + MfUltralightError error = mf_ultralight_poller_cmd_execute(nfc, &poller_context); + + if(error == MfUltralightErrorNone) { + *data = poller_context.data.counter_cmd.data; + } + + return error; +} + +MfUltralightError mf_ultralight_poller_read_tearing_flag( + Nfc* nfc, + uint8_t flag_num, + MfUltralightTearingFlag* data) { + furi_assert(nfc); + furi_assert(data); + + MfUltralightPollerContext poller_context = { + .cmd_type = MfUltralightPollerCmdTypeReadTearingFlag, + .data.tearing_flag_cmd.tearing_flag_num = flag_num, + }; + + MfUltralightError error = mf_ultralight_poller_cmd_execute(nfc, &poller_context); + + if(error == MfUltralightErrorNone) { + *data = poller_context.data.tearing_flag_cmd.data; + } + + return error; +} + +static NfcCommand mf_ultralight_poller_read_callback(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.instance); + furi_assert(event.event_data); + furi_assert(event.protocol == NfcProtocolMfUltralight); + + NfcCommand command = NfcCommandContinue; + MfUltralightPollerContext* poller_context = context; + MfUltralightPoller* mfu_poller = event.instance; + MfUltralightPollerEvent* mfu_event = event.event_data; + + if(mfu_event->type == MfUltralightPollerEventTypeReadSuccess) { + mf_ultralight_copy(poller_context->data.data, mf_ultralight_poller_get_data(mfu_poller)); + poller_context->error = MfUltralightErrorNone; + command = NfcCommandStop; + } else if(mfu_event->type == MfUltralightPollerEventTypeReadFailed) { + poller_context->error = mfu_event->data->error; + command = NfcCommandStop; + } else if(mfu_event->type == MfUltralightPollerEventTypeAuthRequest) { + mfu_event->data->auth_context.skip_auth = true; + } + + if(command == NfcCommandStop) { + furi_thread_flags_set(poller_context->thread_id, MF_ULTRALIGHT_POLLER_COMPLETE_EVENT); + } + + return command; +} + +MfUltralightError mf_ultralight_poller_read_card(Nfc* nfc, MfUltralightData* data) { + furi_assert(nfc); + furi_assert(data); + + MfUltralightPollerContext poller_context = {}; + poller_context.thread_id = furi_thread_get_current_id(); + poller_context.data.data = mf_ultralight_alloc(); + + NfcPoller* poller = nfc_poller_alloc(nfc, NfcProtocolMfUltralight); + nfc_poller_start(poller, mf_ultralight_poller_read_callback, &poller_context); + furi_thread_flags_wait(MF_ULTRALIGHT_POLLER_COMPLETE_EVENT, FuriFlagWaitAny, FuriWaitForever); + furi_thread_flags_clear(MF_ULTRALIGHT_POLLER_COMPLETE_EVENT); + + nfc_poller_stop(poller); + nfc_poller_free(poller); + + if(poller_context.error == MfUltralightErrorNone) { + mf_ultralight_copy(data, poller_context.data.data); + } + + mf_ultralight_free(poller_context.data.data); + + return poller_context.error; +} diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync_api.h b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync_api.h new file mode 100644 index 00000000000..a0124ae0921 --- /dev/null +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync_api.h @@ -0,0 +1,30 @@ +#pragma once + +#include "mf_ultralight.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +MfUltralightError mf_ultralight_poller_read_page(Nfc* nfc, uint16_t page, MfUltralightPage* data); + +MfUltralightError mf_ultralight_poller_write_page(Nfc* nfc, uint16_t page, MfUltralightPage* data); + +MfUltralightError mf_ultralight_poller_read_version(Nfc* nfc, MfUltralightVersion* data); + +MfUltralightError mf_ultralight_poller_read_signature(Nfc* nfc, MfUltralightSignature* data); + +MfUltralightError + mf_ultralight_poller_read_counter(Nfc* nfc, uint8_t counter_num, MfUltralightCounter* data); + +MfUltralightError mf_ultralight_poller_read_tearing_flag( + Nfc* nfc, + uint8_t flag_num, + MfUltralightTearingFlag* data); + +MfUltralightError mf_ultralight_poller_read_card(Nfc* nfc, MfUltralightData* data); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/mifare_classic.c b/lib/nfc/protocols/mifare_classic.c deleted file mode 100644 index a72d4aac0c1..00000000000 --- a/lib/nfc/protocols/mifare_classic.c +++ /dev/null @@ -1,1626 +0,0 @@ -#include "mifare_classic.h" -#include "nfca.h" -#include "nfc_util.h" -#include - -// Algorithm from https://github.com/RfidResearchGroup/proxmark3.git - -#define TAG "MfClassic" - -#define MF_CLASSIC_ACK_CMD 0xAU -#define MF_CLASSIC_NACK_BUF_VALID_CMD 0x0U -#define MF_CLASSIC_NACK_BUF_INVALID_CMD 0x4U -#define MF_CLASSIC_AUTH_KEY_A_CMD 0x60U -#define MF_CLASSIC_AUTH_KEY_B_CMD 0x61U -#define MF_CLASSIC_READ_BLOCK_CMD 0x30U -#define MF_CLASSIC_WRITE_BLOCK_CMD 0xA0U -#define MF_CLASSIC_TRANSFER_CMD 0xB0U -#define MF_CLASSIC_DECREMENT_CMD 0xC0U -#define MF_CLASSIC_INCREMENT_CMD 0xC1U -#define MF_CLASSIC_RESTORE_CMD 0xC2U - -const char* mf_classic_get_type_str(MfClassicType type) { - if(type == MfClassicTypeMini) { - return "MIFARE Mini 0.3K"; - } else if(type == MfClassicType1k) { - return "MIFARE Classic 1K"; - } else if(type == MfClassicType4k) { - return "MIFARE Classic 4K"; - } else { - return "Unknown"; - } -} - -static uint8_t mf_classic_get_first_block_num_of_sector(uint8_t sector) { - furi_assert(sector < 40); - if(sector < 32) { - return sector * 4; - } else { - return 32 * 4 + (sector - 32) * 16; - } -} - -uint8_t mf_classic_get_sector_trailer_block_num_by_sector(uint8_t sector) { - furi_assert(sector < 40); - if(sector < 32) { - return sector * 4 + 3; - } else { - return 32 * 4 + (sector - 32) * 16 + 15; - } -} - -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; -} - -uint8_t mf_classic_get_sector_trailer_num_by_block(uint8_t block) { - if(block < 128) { - return block | 0x03; - } else { - return block | 0x0f; - } -} - -bool mf_classic_is_sector_trailer(uint8_t block) { - return block == mf_classic_get_sector_trailer_num_by_block(block); -} - -MfClassicSectorTrailer* - mf_classic_get_sector_trailer_by_sector(MfClassicData* data, uint8_t sector) { - furi_assert(data); - uint8_t sec_tr_block_num = mf_classic_get_sector_trailer_block_num_by_sector(sector); - return (MfClassicSectorTrailer*)data->block[sec_tr_block_num].value; -} - -uint8_t mf_classic_get_total_sectors_num(MfClassicType type) { - if(type == MfClassicTypeMini) { - return MF_MINI_TOTAL_SECTORS_NUM; - } else if(type == MfClassicType1k) { - return MF_CLASSIC_1K_TOTAL_SECTORS_NUM; - } else if(type == MfClassicType4k) { - return MF_CLASSIC_4K_TOTAL_SECTORS_NUM; - } else { - return 0; - } -} - -uint16_t mf_classic_get_total_block_num(MfClassicType type) { - if(type == MfClassicTypeMini) { - return 20; - } else if(type == MfClassicType1k) { - return 64; - } else if(type == MfClassicType4k) { - return 256; - } else { - return 0; - } -} - -bool mf_classic_is_block_read(MfClassicData* data, uint8_t block_num) { - furi_assert(data); - - return (FURI_BIT(data->block_read_mask[block_num / 32], block_num % 32) == 1); -} - -void mf_classic_set_block_read(MfClassicData* data, uint8_t block_num, MfClassicBlock* block_data) { - furi_assert(data); - - if(mf_classic_is_sector_trailer(block_num)) { - memcpy(&data->block[block_num].value[6], &block_data->value[6], 4); - } else { - memcpy(data->block[block_num].value, block_data->value, MF_CLASSIC_BLOCK_SIZE); - } - FURI_BIT_SET(data->block_read_mask[block_num / 32], block_num % 32); -} - -bool mf_classic_is_sector_data_read(MfClassicData* data, uint8_t sector_num) { - furi_assert(data); - - uint8_t first_block = mf_classic_get_first_block_num_of_sector(sector_num); - uint8_t total_blocks = mf_classic_get_blocks_num_in_sector(sector_num); - bool data_read = true; - for(size_t i = first_block; i < first_block + total_blocks; i++) { - data_read &= mf_classic_is_block_read(data, i); - } - - return data_read; -} - -void mf_classic_set_sector_data_not_read(MfClassicData* data) { - furi_assert(data); - memset(data->block_read_mask, 0, sizeof(data->block_read_mask)); -} - -bool mf_classic_is_key_found(MfClassicData* data, uint8_t sector_num, MfClassicKey key_type) { - furi_assert(data); - - bool key_found = false; - if(key_type == MfClassicKeyA) { - key_found = (FURI_BIT(data->key_a_mask, sector_num) == 1); - } else if(key_type == MfClassicKeyB) { - key_found = (FURI_BIT(data->key_b_mask, sector_num) == 1); - } - - return key_found; -} - -void mf_classic_set_key_found( - MfClassicData* data, - uint8_t sector_num, - MfClassicKey key_type, - uint64_t key) { - furi_assert(data); - - uint8_t key_arr[6] = {}; - MfClassicSectorTrailer* sec_trailer = - mf_classic_get_sector_trailer_by_sector(data, sector_num); - nfc_util_num2bytes(key, 6, key_arr); - if(key_type == MfClassicKeyA) { - memcpy(sec_trailer->key_a, key_arr, sizeof(sec_trailer->key_a)); - FURI_BIT_SET(data->key_a_mask, sector_num); - } else if(key_type == MfClassicKeyB) { - memcpy(sec_trailer->key_b, key_arr, sizeof(sec_trailer->key_b)); - FURI_BIT_SET(data->key_b_mask, sector_num); - } -} - -void mf_classic_set_key_not_found(MfClassicData* data, uint8_t sector_num, MfClassicKey key_type) { - furi_assert(data); - - if(key_type == MfClassicKeyA) { - FURI_BIT_CLEAR(data->key_a_mask, sector_num); - } else if(key_type == MfClassicKeyB) { - FURI_BIT_CLEAR(data->key_b_mask, sector_num); - } -} - -bool mf_classic_is_sector_read(MfClassicData* data, uint8_t sector_num) { - furi_assert(data); - - bool sector_read = false; - do { - if(!mf_classic_is_key_found(data, sector_num, MfClassicKeyA)) break; - if(!mf_classic_is_key_found(data, sector_num, MfClassicKeyB)) break; - uint8_t start_block = mf_classic_get_first_block_num_of_sector(sector_num); - uint8_t total_blocks = mf_classic_get_blocks_num_in_sector(sector_num); - uint8_t block_read = true; - for(size_t i = start_block; i < start_block + total_blocks; i++) { - block_read = mf_classic_is_block_read(data, i); - if(!block_read) break; - } - sector_read = block_read; - } while(false); - - return sector_read; -} - -void mf_classic_get_read_sectors_and_keys( - MfClassicData* data, - uint8_t* sectors_read, - uint8_t* keys_found) { - furi_assert(data); - furi_assert(sectors_read); - furi_assert(keys_found); - - *sectors_read = 0; - *keys_found = 0; - uint8_t sectors_total = mf_classic_get_total_sectors_num(data->type); - for(size_t i = 0; i < sectors_total; i++) { - if(mf_classic_is_key_found(data, i, MfClassicKeyA)) { - *keys_found += 1; - } - if(mf_classic_is_key_found(data, i, MfClassicKeyB)) { - *keys_found += 1; - } - uint8_t first_block = mf_classic_get_first_block_num_of_sector(i); - uint8_t total_blocks_in_sec = mf_classic_get_blocks_num_in_sector(i); - bool blocks_read = true; - for(size_t j = first_block; j < first_block + total_blocks_in_sec; j++) { - blocks_read = mf_classic_is_block_read(data, j); - if(!blocks_read) break; - } - if(blocks_read) { - *sectors_read += 1; - } - } -} - -bool mf_classic_is_card_read(MfClassicData* data) { - furi_assert(data); - - uint8_t sectors_total = mf_classic_get_total_sectors_num(data->type); - uint8_t sectors_read = 0; - uint8_t keys_found = 0; - mf_classic_get_read_sectors_and_keys(data, §ors_read, &keys_found); - bool card_read = (sectors_read == sectors_total) && (keys_found == sectors_total * 2); - - return card_read; -} - -bool mf_classic_is_allowed_access_sector_trailer( - MfClassicData* data, - uint8_t block_num, - MfClassicKey key, - MfClassicAction action) { - uint8_t* sector_trailer = 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: - case MfClassicActionKeyBWrite: { - 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 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; -} - -bool mf_classic_is_allowed_access_data_block( - MfClassicData* data, - uint8_t block_num, - MfClassicKey key, - MfClassicAction action) { - uint8_t* sector_trailer = - data->block[mf_classic_get_sector_trailer_num_by_block(block_num)].value; - - if(block_num == 0 && action == MfClassicActionDataWrite) { - return false; - } - - 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->data, block_num, key, action); - } else { - return mf_classic_is_allowed_access_data_block(&emulator->data, block_num, key, action); - } -} - -bool mf_classic_is_value_block(MfClassicData* data, uint8_t block_num) { - // Check if key A can write, if it can, it's transport configuration, not data block - return !mf_classic_is_allowed_access_data_block( - data, block_num, MfClassicKeyA, MfClassicActionDataWrite) && - (mf_classic_is_allowed_access_data_block( - data, block_num, MfClassicKeyB, MfClassicActionDataInc) || - mf_classic_is_allowed_access_data_block( - data, block_num, MfClassicKeyB, MfClassicActionDataDec)); -} - -bool mf_classic_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK) { - UNUSED(ATQA1); - if((ATQA0 == 0x44 || ATQA0 == 0x04) && (SAK == 0x08 || SAK == 0x88 || SAK == 0x09)) { - return true; - } else if((ATQA0 == 0x01) && (ATQA1 == 0x0F) && (SAK == 0x01)) { - //skylanders support - return true; - } else if( - ((ATQA0 == 0x42 || ATQA0 == 0x02) && (SAK == 0x18)) || - ((ATQA0 == 0x02 || ATQA0 == 0x04 || ATQA0 == 0x08) && (SAK == 0x38))) { - return true; - } else { - return false; - } -} - -MfClassicType mf_classic_get_classic_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK) { - UNUSED(ATQA1); - if((ATQA0 == 0x44 || ATQA0 == 0x04)) { - if((SAK == 0x08 || SAK == 0x88)) { - return MfClassicType1k; - } else if((SAK == 0x38)) { - return MfClassicType4k; - } else if(SAK == 0x09) { - return MfClassicTypeMini; - } - } else if((ATQA0 == 0x01) && (ATQA1 == 0x0F) && (SAK == 0x01)) { - //skylanders support - return MfClassicType1k; - } else if( - ((ATQA0 == 0x42 || ATQA0 == 0x02) && (SAK == 0x18)) || - ((ATQA0 == 0x02 || ATQA0 == 0x08) && (SAK == 0x38))) { - return MfClassicType4k; - } - return MfClassicType1k; -} - -void mf_classic_reader_add_sector( - MfClassicReader* reader, - uint8_t sector, - uint64_t key_a, - uint64_t key_b) { - furi_assert(reader); - furi_assert(sector < MF_CLASSIC_SECTORS_MAX); - furi_assert((key_a != MF_CLASSIC_NO_KEY) || (key_b != MF_CLASSIC_NO_KEY)); - - if(reader->sectors_to_read < MF_CLASSIC_SECTORS_MAX) { - reader->sector_reader[reader->sectors_to_read].key_a = key_a; - reader->sector_reader[reader->sectors_to_read].key_b = key_b; - reader->sector_reader[reader->sectors_to_read].sector_num = sector; - reader->sectors_to_read++; - } -} - -bool mf_classic_block_to_value(const uint8_t* block, int32_t* value, uint8_t* addr) { - uint32_t v = *(uint32_t*)&block[0]; - uint32_t v_inv = *(uint32_t*)&block[4]; - uint32_t v1 = *(uint32_t*)&block[8]; - - bool val_checks = - ((v == v1) && (v == ~v_inv) && (block[12] == (~block[13] & 0xFF)) && - (block[14] == (~block[15] & 0xFF)) && (block[12] == block[14])); - if(value) { - *value = (int32_t)v; - } - if(addr) { - *addr = block[12]; - } - return val_checks; -} - -void mf_classic_value_to_block(int32_t value, uint8_t addr, uint8_t* block) { - uint32_t v_inv = ~((uint32_t)value); - - memcpy(block, &value, 4); //-V1086 - memcpy(block + 4, &v_inv, 4); //-V1086 - memcpy(block + 8, &value, 4); //-V1086 - - block[12] = addr; - block[13] = ~addr & 0xFF; - block[14] = addr; - block[15] = ~addr & 0xFF; -} - -void mf_classic_auth_init_context(MfClassicAuthContext* auth_ctx, uint8_t sector) { - furi_assert(auth_ctx); - auth_ctx->sector = sector; - auth_ctx->key_a = MF_CLASSIC_NO_KEY; - auth_ctx->key_b = MF_CLASSIC_NO_KEY; -} - -static bool mf_classic_auth( - FuriHalNfcTxRxContext* tx_rx, - uint32_t block, - uint64_t key, - MfClassicKey key_type, - Crypto1* crypto, - bool skip_activate, - uint32_t cuid) { - bool auth_success = false; - memset(tx_rx->tx_data, 0, sizeof(tx_rx->tx_data)); - memset(tx_rx->tx_parity, 0, sizeof(tx_rx->tx_parity)); - tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; - - do { - if(!skip_activate && !furi_hal_nfc_activate_nfca(200, &cuid)) break; - if(key_type == MfClassicKeyA) { - tx_rx->tx_data[0] = MF_CLASSIC_AUTH_KEY_A_CMD; - } else { - tx_rx->tx_data[0] = MF_CLASSIC_AUTH_KEY_B_CMD; - } - 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, 6)) break; - - uint32_t nt = (uint32_t)nfc_util_bytes2num(tx_rx->rx_data, 4); - crypto1_init(crypto, key); - crypto1_word(crypto, nt ^ cuid, 0); - uint8_t nr[4] = {}; - nfc_util_num2bytes(prng_successor(DWT->CYCCNT, 32), 4, nr); - for(uint8_t i = 0; i < 4; i++) { - tx_rx->tx_data[i] = crypto1_byte(crypto, nr[i], 0) ^ nr[i]; - tx_rx->tx_parity[0] |= - (((crypto1_filter(crypto->odd) ^ nfc_util_odd_parity8(nr[i])) & 0x01) << (7 - i)); - } - nt = prng_successor(nt, 32); - for(uint8_t i = 4; i < 8; i++) { - nt = prng_successor(nt, 8); - tx_rx->tx_data[i] = crypto1_byte(crypto, 0x00, 0) ^ (nt & 0xff); - tx_rx->tx_parity[0] |= - (((crypto1_filter(crypto->odd) ^ nfc_util_odd_parity8(nt & 0xff)) & 0x01) - << (7 - i)); - } - tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRaw; - tx_rx->tx_bits = 8 * 8; - if(!furi_hal_nfc_tx_rx(tx_rx, 6)) break; - if(tx_rx->rx_bits == 32) { - crypto1_word(crypto, 0, 0); - auth_success = true; - } - } while(false); - - return auth_success; -} - -bool mf_classic_authenticate( - FuriHalNfcTxRxContext* tx_rx, - uint8_t block_num, - uint64_t key, - MfClassicKey key_type) { - furi_assert(tx_rx); - - Crypto1 crypto = {}; - bool key_found = mf_classic_auth(tx_rx, block_num, key, key_type, &crypto, false, 0); - furi_hal_nfc_sleep(); - return key_found; -} - -bool mf_classic_authenticate_skip_activate( - FuriHalNfcTxRxContext* tx_rx, - uint8_t block_num, - uint64_t key, - MfClassicKey key_type, - bool skip_activate, - uint32_t cuid) { - furi_assert(tx_rx); - - Crypto1 crypto = {}; - bool key_found = - mf_classic_auth(tx_rx, block_num, key, key_type, &crypto, skip_activate, cuid); - furi_hal_nfc_sleep(); - return key_found; -} - -bool mf_classic_auth_attempt( - FuriHalNfcTxRxContext* tx_rx, - Crypto1* crypto, - MfClassicAuthContext* auth_ctx, - uint64_t key) { - furi_assert(tx_rx); - furi_assert(auth_ctx); - bool found_key = false; - bool need_halt = (auth_ctx->key_a == MF_CLASSIC_NO_KEY) && - (auth_ctx->key_b == MF_CLASSIC_NO_KEY); - - if(auth_ctx->key_a == MF_CLASSIC_NO_KEY) { - // Try AUTH with key A - if(mf_classic_auth( - tx_rx, - mf_classic_get_sector_trailer_block_num_by_sector(auth_ctx->sector), - key, - MfClassicKeyA, - crypto, - false, - 0)) { - auth_ctx->key_a = key; - found_key = true; - } - } - - if(need_halt) { - furi_hal_nfc_sleep(); - } - - if(auth_ctx->key_b == MF_CLASSIC_NO_KEY) { - // Try AUTH with key B - if(mf_classic_auth( - tx_rx, - mf_classic_get_sector_trailer_block_num_by_sector(auth_ctx->sector), - key, - MfClassicKeyB, - crypto, - false, - 0)) { - auth_ctx->key_b = key; - found_key = true; - } - } - - return found_key; -} - -bool mf_classic_read_block( - FuriHalNfcTxRxContext* tx_rx, - Crypto1* crypto, - uint8_t block_num, - MfClassicBlock* block) { - furi_assert(tx_rx); - furi_assert(crypto); - furi_assert(block); - - bool read_block_success = false; - uint8_t plain_cmd[4] = {MF_CLASSIC_READ_BLOCK_CMD, block_num, 0x00, 0x00}; - nfca_append_crc16(plain_cmd, 2); - - crypto1_encrypt( - crypto, NULL, plain_cmd, sizeof(plain_cmd) * 8, tx_rx->tx_data, tx_rx->tx_parity); - tx_rx->tx_bits = sizeof(plain_cmd) * 8; - tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRaw; - - if(furi_hal_nfc_tx_rx(tx_rx, 50)) { - if(tx_rx->rx_bits == 8 * (MF_CLASSIC_BLOCK_SIZE + 2)) { - uint8_t block_received[MF_CLASSIC_BLOCK_SIZE + 2]; - crypto1_decrypt(crypto, tx_rx->rx_data, tx_rx->rx_bits, block_received); - uint16_t crc_calc = nfca_get_crc16(block_received, MF_CLASSIC_BLOCK_SIZE); - uint16_t crc_received = (block_received[MF_CLASSIC_BLOCK_SIZE + 1] << 8) | - block_received[MF_CLASSIC_BLOCK_SIZE]; - if(crc_received != crc_calc) { - FURI_LOG_E( - TAG, - "Incorrect CRC while reading block %d. Expected %04X, Received %04X", - block_num, - crc_received, - crc_calc); - } else { - memcpy(block->value, block_received, MF_CLASSIC_BLOCK_SIZE); - read_block_success = true; - } - } - } - return read_block_success; -} - -void mf_classic_read_sector(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data, uint8_t sec_num) { - furi_assert(tx_rx); - furi_assert(data); - - furi_hal_nfc_sleep(); - bool key_a_found = mf_classic_is_key_found(data, sec_num, MfClassicKeyA); - bool key_b_found = mf_classic_is_key_found(data, sec_num, MfClassicKeyB); - uint8_t start_block = mf_classic_get_first_block_num_of_sector(sec_num); - uint8_t total_blocks = mf_classic_get_blocks_num_in_sector(sec_num); - MfClassicBlock block_tmp = {}; - uint64_t key = 0; - MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, sec_num); - Crypto1 crypto = {}; - - uint8_t blocks_read = 0; - do { - if(!key_a_found) break; - FURI_LOG_D(TAG, "Try to read blocks with key A"); - key = nfc_util_bytes2num(sec_tr->key_a, sizeof(sec_tr->key_a)); - if(!mf_classic_auth(tx_rx, start_block, key, MfClassicKeyA, &crypto, false, 0)) { - mf_classic_set_key_not_found(data, sec_num, MfClassicKeyA); - FURI_LOG_D(TAG, "Key %dA not found in read", sec_num); - break; - } - - for(size_t i = start_block; i < start_block + total_blocks; i++) { - if(!mf_classic_is_block_read(data, i)) { - if(mf_classic_read_block(tx_rx, &crypto, i, &block_tmp)) { - mf_classic_set_block_read(data, i, &block_tmp); - blocks_read++; - } else if(i > start_block) { - // Try to re-auth to read block in case prevous block was protected from read - furi_hal_nfc_sleep(); - if(!mf_classic_auth(tx_rx, i, key, MfClassicKeyA, &crypto, false, 0)) { - mf_classic_set_key_not_found(data, sec_num, MfClassicKeyA); - FURI_LOG_D(TAG, "Key %dA not found in read", sec_num); - break; - } - if(mf_classic_read_block(tx_rx, &crypto, i, &block_tmp)) { - mf_classic_set_block_read(data, i, &block_tmp); - blocks_read++; - } - } - } else { - blocks_read++; - } - } - FURI_LOG_D(TAG, "Read %d blocks out of %d", blocks_read, total_blocks); - } while(false); - do { - if(blocks_read == total_blocks) break; - if(!key_b_found) break; - if(key_a_found) { - furi_hal_nfc_sleep(); - } - FURI_LOG_D(TAG, "Try to read blocks with key B"); - key = nfc_util_bytes2num(sec_tr->key_b, sizeof(sec_tr->key_b)); - if(!mf_classic_auth(tx_rx, start_block, key, MfClassicKeyB, &crypto, false, 0)) { - mf_classic_set_key_not_found(data, sec_num, MfClassicKeyB); - FURI_LOG_D(TAG, "Key %dB not found in read", sec_num); - break; - } - - for(size_t i = start_block; i < start_block + total_blocks; i++) { - if(!mf_classic_is_block_read(data, i)) { - if(mf_classic_read_block(tx_rx, &crypto, i, &block_tmp)) { - mf_classic_set_block_read(data, i, &block_tmp); - blocks_read++; - } else if(i > start_block) { - // Try to re-auth to read block in case prevous block was protected from read - furi_hal_nfc_sleep(); - if(!mf_classic_auth(tx_rx, i, key, MfClassicKeyB, &crypto, false, 0)) { - mf_classic_set_key_not_found(data, sec_num, MfClassicKeyB); - FURI_LOG_D(TAG, "Key %dB not found in read", sec_num); - break; - } - if(mf_classic_read_block(tx_rx, &crypto, i, &block_tmp)) { - mf_classic_set_block_read(data, i, &block_tmp); - blocks_read++; - } - } - } else { - blocks_read++; - } - } - FURI_LOG_D(TAG, "Read %d blocks out of %d", blocks_read, total_blocks); - } while(false); -} - -static bool mf_classic_read_sector_with_reader( - FuriHalNfcTxRxContext* tx_rx, - Crypto1* crypto, - MfClassicSectorReader* sector_reader, - MfClassicSector* sector) { - furi_assert(tx_rx); - furi_assert(sector_reader); - furi_assert(sector); - - uint64_t key; - MfClassicKey key_type; - uint8_t first_block; - bool sector_read = false; - - furi_hal_nfc_sleep(); - do { - // Activate card - first_block = mf_classic_get_first_block_num_of_sector(sector_reader->sector_num); - if(sector_reader->key_a != MF_CLASSIC_NO_KEY) { - key = sector_reader->key_a; - key_type = MfClassicKeyA; - } else if(sector_reader->key_b != MF_CLASSIC_NO_KEY) { - key = sector_reader->key_b; - key_type = MfClassicKeyB; - } else { - break; - } - - // Auth to first block in sector - if(!mf_classic_auth(tx_rx, first_block, key, key_type, crypto, false, 0)) { - // Set key to MF_CLASSIC_NO_KEY to prevent further attempts - if(key_type == MfClassicKeyA) { - sector_reader->key_a = MF_CLASSIC_NO_KEY; - } else { - sector_reader->key_b = MF_CLASSIC_NO_KEY; - } - break; - } - sector->total_blocks = mf_classic_get_blocks_num_in_sector(sector_reader->sector_num); - - // Read blocks - for(uint8_t i = 0; i < sector->total_blocks; i++) { - if(mf_classic_read_block(tx_rx, crypto, first_block + i, §or->block[i])) continue; - if(i == 0) continue; - // Try to auth to read next block in case previous is locked - furi_hal_nfc_sleep(); - if(!mf_classic_auth(tx_rx, first_block + i, key, key_type, crypto, false, 0)) continue; - mf_classic_read_block(tx_rx, crypto, first_block + i, §or->block[i]); - } - // Save sector keys in last block - if(sector_reader->key_a != MF_CLASSIC_NO_KEY) { - nfc_util_num2bytes( - sector_reader->key_a, 6, §or->block[sector->total_blocks - 1].value[0]); - } - if(sector_reader->key_b != MF_CLASSIC_NO_KEY) { - nfc_util_num2bytes( - sector_reader->key_b, 6, §or->block[sector->total_blocks - 1].value[10]); - } - - sector_read = true; - } while(false); - - return sector_read; -} - -uint8_t mf_classic_read_card( - FuriHalNfcTxRxContext* tx_rx, - MfClassicReader* reader, - MfClassicData* data) { - furi_assert(tx_rx); - furi_assert(reader); - furi_assert(data); - - 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_with_reader( - tx_rx, &reader->crypto, &reader->sector_reader[i], &temp_sector)) { - uint8_t first_block = - mf_classic_get_first_block_num_of_sector(reader->sector_reader[i].sector_num); - for(uint8_t j = 0; j < temp_sector.total_blocks; j++) { - mf_classic_set_block_read(data, first_block + j, &temp_sector.block[j]); - } - if(reader->sector_reader[i].key_a != MF_CLASSIC_NO_KEY) { - mf_classic_set_key_found( - data, - reader->sector_reader[i].sector_num, - MfClassicKeyA, - reader->sector_reader[i].key_a); - } - if(reader->sector_reader[i].key_b != MF_CLASSIC_NO_KEY) { - mf_classic_set_key_found( - data, - reader->sector_reader[i].sector_num, - MfClassicKeyB, - reader->sector_reader[i].key_b); - } - sectors_read++; - } - } - - return sectors_read; -} - -uint8_t mf_classic_update_card(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data) { - furi_assert(tx_rx); - furi_assert(data); - - uint8_t total_sectors = mf_classic_get_total_sectors_num(data->type); - - for(size_t i = 0; i < total_sectors; i++) { - mf_classic_read_sector(tx_rx, data, i); - } - uint8_t sectors_read = 0; - uint8_t keys_found = 0; - mf_classic_get_read_sectors_and_keys(data, §ors_read, &keys_found); - FURI_LOG_D(TAG, "Read %d sectors and %d keys", sectors_read, keys_found); - - return sectors_read; -} - -bool mf_classic_emulator( - MfClassicEmulator* emulator, - FuriHalNfcTxRxContext* tx_rx, - bool is_reader_analyzer) { - furi_assert(emulator); - furi_assert(tx_rx); - uint8_t plain_data[MF_CLASSIC_MAX_DATA_SIZE]; - MfClassicKey access_key = MfClassicKeyA; - bool need_reset = false; - bool need_nack = false; - bool is_encrypted = false; - uint8_t sector = 0; - - // Used for decrement and increment - copy to block on transfer - uint8_t transfer_buf[MF_CLASSIC_BLOCK_SIZE]; - bool transfer_buf_valid = false; - - // Process commands - while(!need_reset && !need_nack) { //-V654 - memset(plain_data, 0, MF_CLASSIC_MAX_DATA_SIZE); - if(!is_encrypted) { - crypto1_reset(&emulator->crypto); - memcpy(plain_data, tx_rx->rx_data, tx_rx->rx_bits / 8); - } else { - 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); - need_reset = true; - break; - } - crypto1_decrypt(&emulator->crypto, tx_rx->rx_data, tx_rx->rx_bits, plain_data); - } - - // After increment, decrement or restore the only allowed command is transfer - uint8_t cmd = plain_data[0]; - if(transfer_buf_valid && cmd != MF_CLASSIC_TRANSFER_CMD) { - need_nack = true; - break; - } - - if(cmd == NFCA_CMD_HALT && plain_data[1] == 0x00) { - FURI_LOG_T(TAG, "Halt received"); - need_reset = true; - break; - } - - if(cmd == NFCA_CMD_RATS) { - // Mifare Classic doesn't support ATS, NACK it and start listening again - FURI_LOG_T(TAG, "RATS received"); - need_nack = true; - break; - } - - if(cmd == MF_CLASSIC_AUTH_KEY_A_CMD || cmd == MF_CLASSIC_AUTH_KEY_B_CMD) { - uint8_t block = plain_data[1]; - uint64_t key = 0; - uint8_t sector_trailer_block = mf_classic_get_sector_trailer_num_by_block(block); - sector = mf_classic_get_sector_by_block(block); - MfClassicSectorTrailer* sector_trailer = - (MfClassicSectorTrailer*)emulator->data.block[sector_trailer_block].value; - if(cmd == MF_CLASSIC_AUTH_KEY_A_CMD) { - if(mf_classic_is_key_found( - &emulator->data, mf_classic_get_sector_by_block(block), MfClassicKeyA) || - is_reader_analyzer) { - key = nfc_util_bytes2num(sector_trailer->key_a, 6); - access_key = MfClassicKeyA; - } else { - FURI_LOG_D(TAG, "Key not known"); - need_nack = true; - break; - } - } else { - if(mf_classic_is_key_found( - &emulator->data, mf_classic_get_sector_by_block(block), MfClassicKeyB) || - is_reader_analyzer) { - key = nfc_util_bytes2num(sector_trailer->key_b, 6); - access_key = MfClassicKeyB; - } else { - FURI_LOG_D(TAG, "Key not known"); - need_nack = true; - break; - } - } - - uint32_t nonce = prng_successor(DWT->CYCCNT, 32) ^ 0xAA; - 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_parity[0] = 0; - nfc_util_odd_parity(tx_rx->tx_data, tx_rx->tx_parity, sizeof(nt)); - tx_rx->tx_bits = sizeof(nt) * 8; - tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; - } else { - 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"); - need_reset = true; - break; - } - - if(tx_rx->rx_bits != 64) { - need_reset = true; - 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 on block %u! %08lX != %08lX", - block, - cardRr, - prng_successor(nonce, 64)); - // Don't send NACK, as the tag doesn't send it - need_reset = true; - break; - } - - uint32_t ans = prng_successor(nonce, 96); - uint8_t response[4] = {}; - nfc_util_num2bytes(ans, 4, response); - crypto1_encrypt( - &emulator->crypto, - NULL, - response, - sizeof(response) * 8, - tx_rx->tx_data, - tx_rx->tx_parity); - tx_rx->tx_bits = sizeof(response) * 8; - tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; - - is_encrypted = true; - continue; - } - - if(!is_encrypted) { - FURI_LOG_T(TAG, "Invalid command before auth session established: %02X", cmd); - need_nack = true; - break; - } - - // Mifare Classic commands always have block number after command - uint8_t block = plain_data[1]; - if(mf_classic_get_sector_by_block(block) != sector) { - // Don't allow access to sectors other than authorized - FURI_LOG_T( - TAG, - "Trying to access block %u from not authorized sector (command: %02X)", - block, - cmd); - need_nack = true; - break; - } - - switch(cmd) { - case MF_CLASSIC_READ_BLOCK_CMD: { - uint8_t block_data[MF_CLASSIC_BLOCK_SIZE + 2] = {}; - 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); //-V1086 - } - 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) || - !mf_classic_is_block_read(&emulator->data, block)) { - need_nack = true; - break; - } - - nfca_append_crc16(block_data, 16); - - crypto1_encrypt( - &emulator->crypto, - NULL, - block_data, - sizeof(block_data) * 8, - tx_rx->tx_data, - tx_rx->tx_parity); - tx_rx->tx_bits = (MF_CLASSIC_BLOCK_SIZE + 2) * 8; - tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; - break; - } - - case MF_CLASSIC_WRITE_BLOCK_CMD: { - // Send ACK - uint8_t ack = MF_CLASSIC_ACK_CMD; - 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)) { - need_reset = true; - break; - } - - if(tx_rx->rx_bits != (MF_CLASSIC_BLOCK_SIZE + 2) * 8) { - need_reset = true; - break; - } - - crypto1_decrypt(&emulator->crypto, tx_rx->rx_data, tx_rx->rx_bits, plain_data); - uint8_t block_data[MF_CLASSIC_BLOCK_SIZE] = {}; - memcpy(block_data, emulator->data.block[block].value, MF_CLASSIC_BLOCK_SIZE); - - if(!mf_classic_is_block_read(&emulator->data, block)) { - // Don't allow writing to the block for which we haven't read data yet - need_nack = true; - break; - } - - if(mf_classic_is_sector_trailer(block)) { - if(mf_classic_is_allowed_access( - emulator, block, access_key, MfClassicActionKeyAWrite)) { - memcpy(block_data, plain_data, 6); //-V1086 - } - 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); - } else { - need_nack = true; - break; - } - } - - if(memcmp(block_data, emulator->data.block[block].value, MF_CLASSIC_BLOCK_SIZE) != 0) { - memcpy(emulator->data.block[block].value, block_data, MF_CLASSIC_BLOCK_SIZE); - emulator->data_changed = true; - } - - // Send ACK - ack = MF_CLASSIC_ACK_CMD; - 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; - break; - } - - case MF_CLASSIC_DECREMENT_CMD: - case MF_CLASSIC_INCREMENT_CMD: - case MF_CLASSIC_RESTORE_CMD: { - MfClassicAction action = (cmd == MF_CLASSIC_INCREMENT_CMD) ? MfClassicActionDataInc : - MfClassicActionDataDec; - - if(!mf_classic_is_allowed_access(emulator, block, access_key, action)) { - need_nack = true; - break; - } - - int32_t prev_value; - uint8_t addr; - if(!mf_classic_block_to_value(emulator->data.block[block].value, &prev_value, &addr)) { - need_nack = true; - break; - } - - // Send ACK - uint8_t ack = MF_CLASSIC_ACK_CMD; - 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)) { - need_reset = true; - break; - } - - if(tx_rx->rx_bits != (sizeof(int32_t) + 2) * 8) { - need_reset = true; - break; - } - - crypto1_decrypt(&emulator->crypto, tx_rx->rx_data, tx_rx->rx_bits, plain_data); - int32_t value = *(int32_t*)&plain_data[0]; - if(value < 0) { - value = -value; - } - if(cmd == MF_CLASSIC_DECREMENT_CMD) { - value = -value; - } else if(cmd == MF_CLASSIC_RESTORE_CMD) { - value = 0; - } - - mf_classic_value_to_block(prev_value + value, addr, transfer_buf); - transfer_buf_valid = true; - // Commands do not ACK - tx_rx->tx_bits = 0; - break; - } - - case MF_CLASSIC_TRANSFER_CMD: { - if(!mf_classic_is_allowed_access(emulator, block, access_key, MfClassicActionDataDec)) { - need_nack = true; - break; - } - if(memcmp(transfer_buf, emulator->data.block[block].value, MF_CLASSIC_BLOCK_SIZE) != - 0) { - memcpy(emulator->data.block[block].value, transfer_buf, MF_CLASSIC_BLOCK_SIZE); - emulator->data_changed = true; - } - transfer_buf_valid = false; - - uint8_t ack = MF_CLASSIC_ACK_CMD; - 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; - break; - } - - default: - FURI_LOG_T(TAG, "Unknown command: %02X", cmd); - need_nack = true; - break; - } - } - - if(need_nack && !need_reset) { - // Send NACK - uint8_t nack = transfer_buf_valid ? MF_CLASSIC_NACK_BUF_VALID_CMD : - MF_CLASSIC_NACK_BUF_INVALID_CMD; - if(is_encrypted) { - 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); - need_reset = true; - } - - return !need_reset; -} - -void mf_classic_halt(FuriHalNfcTxRxContext* tx_rx, Crypto1* crypto) { - furi_assert(tx_rx); - - uint8_t plain_data[4] = {NFCA_CMD_HALT, 0x00, 0x00, 0x00}; - - nfca_append_crc16(plain_data, 2); - if(crypto) { - crypto1_encrypt( - crypto, NULL, plain_data, sizeof(plain_data) * 8, tx_rx->tx_data, tx_rx->tx_parity); - } else { - memcpy(tx_rx->tx_data, plain_data, sizeof(plain_data)); - nfc_util_odd_parity(tx_rx->tx_data, tx_rx->tx_parity, sizeof(plain_data)); - } - - tx_rx->tx_bits = sizeof(plain_data) * 8; - tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRaw; - furi_hal_nfc_tx_rx(tx_rx, 50); -} - -bool mf_classic_write_block( - FuriHalNfcTxRxContext* tx_rx, - Crypto1* crypto, - uint8_t block_num, - MfClassicBlock* src_block) { - furi_assert(tx_rx); - furi_assert(crypto); - furi_assert(src_block); - - bool write_success = false; - uint8_t plain_data[MF_CLASSIC_BLOCK_SIZE + 2] = {}; - uint8_t resp; - - do { - // Send write command - plain_data[0] = MF_CLASSIC_WRITE_BLOCK_CMD; - plain_data[1] = block_num; - nfca_append_crc16(plain_data, 2); - crypto1_encrypt(crypto, NULL, plain_data, 4 * 8, tx_rx->tx_data, tx_rx->tx_parity); - tx_rx->tx_bits = 4 * 8; - tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRaw; - - if(furi_hal_nfc_tx_rx(tx_rx, 50)) { - if(tx_rx->rx_bits == 4) { - crypto1_decrypt(crypto, tx_rx->rx_data, 4, &resp); - if(resp != 0x0A) { - FURI_LOG_D(TAG, "NACK received on write cmd: %02X", resp); - break; - } - } else { - FURI_LOG_D(TAG, "Not ACK received"); - break; - } - } else { - FURI_LOG_D(TAG, "Failed to send write cmd"); - break; - } - - // Send data - memcpy(plain_data, src_block->value, MF_CLASSIC_BLOCK_SIZE); - nfca_append_crc16(plain_data, MF_CLASSIC_BLOCK_SIZE); - crypto1_encrypt( - crypto, - NULL, - plain_data, - (MF_CLASSIC_BLOCK_SIZE + 2) * 8, - tx_rx->tx_data, - tx_rx->tx_parity); - tx_rx->tx_bits = (MF_CLASSIC_BLOCK_SIZE + 2) * 8; - tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRaw; - if(furi_hal_nfc_tx_rx(tx_rx, 50)) { - if(tx_rx->rx_bits == 4) { - crypto1_decrypt(crypto, tx_rx->rx_data, 4, &resp); - if(resp != MF_CLASSIC_ACK_CMD) { - FURI_LOG_D(TAG, "NACK received on sending data"); - break; - } - } else { - FURI_LOG_D(TAG, "Not ACK received"); - break; - } - } else { - FURI_LOG_D(TAG, "Failed to send data"); - break; - } - - write_success = true; - } while(false); - - return write_success; -} - -bool mf_classic_auth_write_block( - FuriHalNfcTxRxContext* tx_rx, - MfClassicBlock* src_block, - uint8_t block_num, - MfClassicKey key_type, - uint64_t key) { - furi_assert(tx_rx); - furi_assert(src_block); - - Crypto1 crypto = {}; - bool write_success = false; - - do { - furi_hal_nfc_sleep(); - if(!mf_classic_auth(tx_rx, block_num, key, key_type, &crypto, false, 0)) { - FURI_LOG_D(TAG, "Auth fail"); - break; - } - - if(!mf_classic_write_block(tx_rx, &crypto, block_num, src_block)) { - FURI_LOG_D(TAG, "Write fail"); - break; - } - write_success = true; - - mf_classic_halt(tx_rx, &crypto); - } while(false); - - return write_success; -} - -bool mf_classic_transfer(FuriHalNfcTxRxContext* tx_rx, Crypto1* crypto, uint8_t block_num) { - furi_assert(tx_rx); - furi_assert(crypto); - - // Send transfer command - uint8_t plain_data[4] = {MF_CLASSIC_TRANSFER_CMD, block_num, 0, 0}; - uint8_t resp = 0; - bool transfer_success = false; - - nfca_append_crc16(plain_data, 2); - crypto1_encrypt( - crypto, NULL, plain_data, sizeof(plain_data) * 8, tx_rx->tx_data, tx_rx->tx_parity); - tx_rx->tx_bits = sizeof(plain_data) * 8; - tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRaw; - - do { - if(furi_hal_nfc_tx_rx(tx_rx, 50)) { - if(tx_rx->rx_bits == 4) { - crypto1_decrypt(crypto, tx_rx->rx_data, 4, &resp); - if(resp != 0x0A) { - FURI_LOG_D(TAG, "NACK received on transfer cmd: %02X", resp); - break; - } - } else { - FURI_LOG_D(TAG, "Not ACK received"); - break; - } - } else { - FURI_LOG_D(TAG, "Failed to send transfer cmd"); - break; - } - - transfer_success = true; - } while(false); - - return transfer_success; -} - -bool mf_classic_value_cmd( - FuriHalNfcTxRxContext* tx_rx, - Crypto1* crypto, - uint8_t block_num, - uint8_t cmd, - int32_t d_value) { - furi_assert(tx_rx); - furi_assert(crypto); - furi_assert( - cmd == MF_CLASSIC_INCREMENT_CMD || cmd == MF_CLASSIC_DECREMENT_CMD || - cmd == MF_CLASSIC_RESTORE_CMD); - furi_assert(d_value >= 0); - - uint8_t plain_data[sizeof(d_value) + 2] = {}; - uint8_t resp = 0; - bool success = false; - - do { - // Send cmd - plain_data[0] = cmd; - plain_data[1] = block_num; - nfca_append_crc16(plain_data, 2); - crypto1_encrypt(crypto, NULL, plain_data, 4 * 8, tx_rx->tx_data, tx_rx->tx_parity); - tx_rx->tx_bits = 4 * 8; - tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRaw; - - if(furi_hal_nfc_tx_rx(tx_rx, 50)) { - if(tx_rx->rx_bits == 4) { - crypto1_decrypt(crypto, tx_rx->rx_data, 4, &resp); - if(resp != 0x0A) { - FURI_LOG_D(TAG, "NACK received on write cmd: %02X", resp); - break; - } - } else { - FURI_LOG_D(TAG, "Not ACK received"); - break; - } - } else { - FURI_LOG_D(TAG, "Failed to send write cmd"); - break; - } - - // Send data - memcpy(plain_data, &d_value, sizeof(d_value)); - nfca_append_crc16(plain_data, sizeof(d_value)); - crypto1_encrypt( - crypto, NULL, plain_data, (sizeof(d_value) + 2) * 8, tx_rx->tx_data, tx_rx->tx_parity); - tx_rx->tx_bits = (sizeof(d_value) + 2) * 8; - tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRaw; - // inc, dec, restore do not ACK, but they do NACK - if(furi_hal_nfc_tx_rx(tx_rx, 50)) { - if(tx_rx->rx_bits == 4) { - crypto1_decrypt(crypto, tx_rx->rx_data, 4, &resp); - if(resp != 0x0A) { - FURI_LOG_D(TAG, "NACK received on transfer cmd: %02X", resp); - break; - } - } else { - FURI_LOG_D(TAG, "Not NACK received"); - break; - } - } - - success = true; - - } while(false); - - return success; -} - -bool mf_classic_value_cmd_full( - FuriHalNfcTxRxContext* tx_rx, - MfClassicBlock* src_block, - uint8_t block_num, - MfClassicKey key_type, - uint64_t key, - int32_t d_value) { - furi_assert(tx_rx); - furi_assert(src_block); - - Crypto1 crypto = {}; - uint8_t cmd; - bool success = false; - - if(d_value > 0) { - cmd = MF_CLASSIC_INCREMENT_CMD; - } else if(d_value < 0) { - cmd = MF_CLASSIC_DECREMENT_CMD; - d_value = -d_value; - } else { - cmd = MF_CLASSIC_RESTORE_CMD; - } - - do { - furi_hal_nfc_sleep(); - if(!mf_classic_auth(tx_rx, block_num, key, key_type, &crypto, false, 0)) { - FURI_LOG_D(TAG, "Value cmd auth fail"); - break; - } - if(!mf_classic_value_cmd(tx_rx, &crypto, block_num, cmd, d_value)) { - FURI_LOG_D(TAG, "Value cmd inc/dec/res fail"); - break; - } - - if(!mf_classic_transfer(tx_rx, &crypto, block_num)) { - FURI_LOG_D(TAG, "Value cmd transfer fail"); - break; - } - - success = true; - - // Send Halt - mf_classic_halt(tx_rx, &crypto); - } while(false); - - return success; -} - -bool mf_classic_write_sector( - FuriHalNfcTxRxContext* tx_rx, - MfClassicData* dest_data, - MfClassicData* src_data, - uint8_t sec_num) { - furi_assert(tx_rx); - furi_assert(dest_data); - furi_assert(src_data); - - uint8_t first_block = mf_classic_get_first_block_num_of_sector(sec_num); - uint8_t total_blocks = mf_classic_get_blocks_num_in_sector(sec_num); - MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(dest_data, sec_num); - bool key_a_found = mf_classic_is_key_found(dest_data, sec_num, MfClassicKeyA); - bool key_b_found = mf_classic_is_key_found(dest_data, sec_num, MfClassicKeyB); - - bool write_success = true; - for(size_t i = first_block; i < first_block + total_blocks; i++) { - // Compare blocks - if(memcmp(dest_data->block[i].value, src_data->block[i].value, MF_CLASSIC_BLOCK_SIZE) != - 0) { - if(mf_classic_is_value_block(dest_data, i)) { - bool key_a_inc_allowed = mf_classic_is_allowed_access_data_block( - dest_data, i, MfClassicKeyA, MfClassicActionDataInc); - bool key_b_inc_allowed = mf_classic_is_allowed_access_data_block( - dest_data, i, MfClassicKeyB, MfClassicActionDataInc); - bool key_a_dec_allowed = mf_classic_is_allowed_access_data_block( - dest_data, i, MfClassicKeyA, MfClassicActionDataDec); - bool key_b_dec_allowed = mf_classic_is_allowed_access_data_block( - dest_data, i, MfClassicKeyB, MfClassicActionDataDec); - - int32_t src_value, dst_value; - - mf_classic_block_to_value(src_data->block[i].value, &src_value, NULL); - mf_classic_block_to_value(dest_data->block[i].value, &dst_value, NULL); - - int32_t diff = src_value - dst_value; - - if(diff > 0) { - if(key_a_found && key_a_inc_allowed) { - FURI_LOG_I(TAG, "Incrementing block %d with key A by %ld", i, diff); - uint64_t key = nfc_util_bytes2num(sec_tr->key_a, 6); - if(!mf_classic_value_cmd_full( - tx_rx, &src_data->block[i], i, MfClassicKeyA, key, diff)) { - FURI_LOG_E(TAG, "Failed to increment block %d", i); - write_success = false; - break; - } - } else if(key_b_found && key_b_inc_allowed) { - FURI_LOG_I(TAG, "Incrementing block %d with key B by %ld", i, diff); - uint64_t key = nfc_util_bytes2num(sec_tr->key_b, 6); - if(!mf_classic_value_cmd_full( - tx_rx, &src_data->block[i], i, MfClassicKeyB, key, diff)) { - FURI_LOG_E(TAG, "Failed to increment block %d", i); - write_success = false; - break; - } - } else { - FURI_LOG_E(TAG, "Failed to increment block %d", i); - } - } else if(diff < 0) { - if(key_a_found && key_a_dec_allowed) { - FURI_LOG_I(TAG, "Decrementing block %d with key A by %ld", i, -diff); - uint64_t key = nfc_util_bytes2num(sec_tr->key_a, 6); - if(!mf_classic_value_cmd_full( - tx_rx, &src_data->block[i], i, MfClassicKeyA, key, diff)) { - FURI_LOG_E(TAG, "Failed to decrement block %d", i); - write_success = false; - break; - } - } else if(key_b_found && key_b_dec_allowed) { - FURI_LOG_I(TAG, "Decrementing block %d with key B by %ld", i, diff); - uint64_t key = nfc_util_bytes2num(sec_tr->key_b, 6); - if(!mf_classic_value_cmd_full( - tx_rx, &src_data->block[i], i, MfClassicKeyB, key, diff)) { - FURI_LOG_E(TAG, "Failed to decrement block %d", i); - write_success = false; - break; - } - } else { - FURI_LOG_E(TAG, "Failed to decrement block %d", i); - } - } else { - FURI_LOG_E(TAG, "Value block %d address changed, cannot write it", i); - } - } else { - bool key_a_write_allowed = mf_classic_is_allowed_access_data_block( - dest_data, i, MfClassicKeyA, MfClassicActionDataWrite); - bool key_b_write_allowed = mf_classic_is_allowed_access_data_block( - dest_data, i, MfClassicKeyB, MfClassicActionDataWrite); - - if(key_a_found && key_a_write_allowed) { - FURI_LOG_I(TAG, "Writing block %d with key A", i); - uint64_t key = nfc_util_bytes2num(sec_tr->key_a, 6); - if(!mf_classic_auth_write_block( - tx_rx, &src_data->block[i], i, MfClassicKeyA, key)) { - FURI_LOG_E(TAG, "Failed to write block %d", i); - write_success = false; - break; - } - } else if(key_b_found && key_b_write_allowed) { - FURI_LOG_I(TAG, "Writing block %d with key A", i); - uint64_t key = nfc_util_bytes2num(sec_tr->key_b, 6); - if(!mf_classic_auth_write_block( - tx_rx, &src_data->block[i], i, MfClassicKeyB, key)) { - FURI_LOG_E(TAG, "Failed to write block %d", i); - write_success = false; - break; - } - } else { - FURI_LOG_E(TAG, "Failed to find key with write access"); - write_success = false; - break; - } - } - } else { - FURI_LOG_D(TAG, "Blocks %d are equal", i); - } - } - - return write_success; -} diff --git a/lib/nfc/protocols/mifare_classic.h b/lib/nfc/protocols/mifare_classic.h deleted file mode 100644 index 7efe81b8c59..00000000000 --- a/lib/nfc/protocols/mifare_classic.h +++ /dev/null @@ -1,251 +0,0 @@ -#pragma once - -#include - -#include "crypto1.h" - -#ifdef __cplusplus -extern "C" { -#endif - -#define MF_CLASSIC_BLOCK_SIZE (16) -#define MF_CLASSIC_TOTAL_BLOCKS_MAX (256) -#define MF_MINI_TOTAL_SECTORS_NUM (5) -#define MF_CLASSIC_1K_TOTAL_SECTORS_NUM (16) -#define MF_CLASSIC_4K_TOTAL_SECTORS_NUM (40) - -#define MF_CLASSIC_SECTORS_MAX (40) -#define MF_CLASSIC_BLOCKS_IN_SECTOR_MAX (16) - -#define MF_CLASSIC_NO_KEY (0xFFFFFFFFFFFFFFFF) -#define MF_CLASSIC_MAX_DATA_SIZE (16) -#define MF_CLASSIC_KEY_SIZE (6) -#define MF_CLASSIC_ACCESS_BYTES_SIZE (4) - -typedef enum { - MfClassicType1k, - MfClassicType4k, - MfClassicTypeMini, -} MfClassicType; - -typedef enum { - MfClassicKeyA, - MfClassicKeyB, -} MfClassicKey; - -typedef enum { - MfClassicActionDataRead, - MfClassicActionDataWrite, - MfClassicActionDataInc, - MfClassicActionDataDec, - - MfClassicActionKeyARead, - MfClassicActionKeyAWrite, - MfClassicActionKeyBRead, - MfClassicActionKeyBWrite, - MfClassicActionACRead, - MfClassicActionACWrite, -} MfClassicAction; - -typedef struct { - uint8_t value[MF_CLASSIC_BLOCK_SIZE]; -} MfClassicBlock; - -typedef struct { - uint8_t key_a[MF_CLASSIC_KEY_SIZE]; - uint8_t access_bits[MF_CLASSIC_ACCESS_BYTES_SIZE]; - uint8_t key_b[MF_CLASSIC_KEY_SIZE]; -} MfClassicSectorTrailer; - -typedef struct { - uint8_t total_blocks; - MfClassicBlock block[MF_CLASSIC_BLOCKS_IN_SECTOR_MAX]; -} MfClassicSector; - -typedef struct { - MfClassicType type; - uint32_t block_read_mask[MF_CLASSIC_TOTAL_BLOCKS_MAX / 32]; - uint64_t key_a_mask; - uint64_t key_b_mask; - MfClassicBlock block[MF_CLASSIC_TOTAL_BLOCKS_MAX]; -} MfClassicData; - -typedef struct { - uint8_t sector; - uint64_t key_a; - uint64_t key_b; -} MfClassicAuthContext; - -typedef struct { - uint8_t sector_num; - uint64_t key_a; - uint64_t key_b; -} MfClassicSectorReader; - -typedef struct { - MfClassicType type; - Crypto1 crypto; - uint8_t sectors_to_read; - MfClassicSectorReader sector_reader[MF_CLASSIC_SECTORS_MAX]; -} MfClassicReader; - -typedef struct { - uint32_t cuid; - Crypto1 crypto; - MfClassicData data; - bool data_changed; -} MfClassicEmulator; - -const char* mf_classic_get_type_str(MfClassicType type); - -bool mf_classic_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK); - -MfClassicType mf_classic_get_classic_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK); - -uint8_t mf_classic_get_total_sectors_num(MfClassicType type); - -uint16_t mf_classic_get_total_block_num(MfClassicType type); - -uint8_t mf_classic_get_sector_trailer_block_num_by_sector(uint8_t sector); - -bool mf_classic_is_sector_trailer(uint8_t block); - -uint8_t mf_classic_get_sector_by_block(uint8_t block); - -bool mf_classic_is_allowed_access_sector_trailer( - MfClassicData* data, - uint8_t block_num, - MfClassicKey key, - MfClassicAction action); - -bool mf_classic_is_allowed_access_data_block( - MfClassicData* data, - uint8_t block_num, - MfClassicKey key, - MfClassicAction action); - -bool mf_classic_is_value_block(MfClassicData* data, uint8_t block_num); - -bool mf_classic_block_to_value(const uint8_t* block, int32_t* value, uint8_t* addr); - -void mf_classic_value_to_block(int32_t value, uint8_t addr, uint8_t* block); - -bool mf_classic_is_key_found(MfClassicData* data, uint8_t sector_num, MfClassicKey key_type); - -void mf_classic_set_key_found( - MfClassicData* data, - uint8_t sector_num, - MfClassicKey key_type, - uint64_t key); - -void mf_classic_set_key_not_found(MfClassicData* data, uint8_t sector_num, MfClassicKey key_type); - -bool mf_classic_is_block_read(MfClassicData* data, uint8_t block_num); - -void mf_classic_set_block_read(MfClassicData* data, uint8_t block_num, MfClassicBlock* block_data); - -bool mf_classic_is_sector_data_read(MfClassicData* data, uint8_t sector_num); - -void mf_classic_set_sector_data_not_read(MfClassicData* data); - -bool mf_classic_is_sector_read(MfClassicData* data, uint8_t sector_num); - -bool mf_classic_is_card_read(MfClassicData* data); - -void mf_classic_get_read_sectors_and_keys( - MfClassicData* data, - uint8_t* sectors_read, - uint8_t* keys_found); - -MfClassicSectorTrailer* - mf_classic_get_sector_trailer_by_sector(MfClassicData* data, uint8_t sector); - -void mf_classic_auth_init_context(MfClassicAuthContext* auth_ctx, uint8_t sector); - -bool mf_classic_authenticate( - FuriHalNfcTxRxContext* tx_rx, - uint8_t block_num, - uint64_t key, - MfClassicKey key_type); - -bool mf_classic_authenticate_skip_activate( - FuriHalNfcTxRxContext* tx_rx, - uint8_t block_num, - uint64_t key, - MfClassicKey key_type, - bool skip_activate, - uint32_t cuid); - -bool mf_classic_auth_attempt( - FuriHalNfcTxRxContext* tx_rx, - Crypto1* crypto, - MfClassicAuthContext* auth_ctx, - uint64_t key); - -void mf_classic_reader_add_sector( - MfClassicReader* reader, - uint8_t sector, - uint64_t key_a, - uint64_t key_b); - -bool mf_classic_read_block( - FuriHalNfcTxRxContext* tx_rx, - Crypto1* crypto, - uint8_t block_num, - MfClassicBlock* block); - -void mf_classic_read_sector(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data, uint8_t sec_num); - -uint8_t mf_classic_read_card( - FuriHalNfcTxRxContext* tx_rx, - MfClassicReader* reader, - MfClassicData* data); - -uint8_t mf_classic_update_card(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data); - -bool mf_classic_emulator( - MfClassicEmulator* emulator, - FuriHalNfcTxRxContext* tx_rx, - bool is_reader_analyzer); - -void mf_classic_halt(FuriHalNfcTxRxContext* tx_rx, Crypto1* crypto); - -bool mf_classic_write_block( - FuriHalNfcTxRxContext* tx_rx, - Crypto1* crypto, - uint8_t block_num, - MfClassicBlock* src_block); - -bool mf_classic_auth_write_block( - FuriHalNfcTxRxContext* tx_rx, - MfClassicBlock* src_block, - uint8_t block_num, - MfClassicKey key_type, - uint64_t key); - -bool mf_classic_transfer(FuriHalNfcTxRxContext* tx_rx, Crypto1* crypto, uint8_t block_num); - -bool mf_classic_value_cmd( - FuriHalNfcTxRxContext* tx_rx, - Crypto1* crypto, - uint8_t block_num, - uint8_t cmd, - int32_t d_value); - -bool mf_classic_value_cmd_full( - FuriHalNfcTxRxContext* tx_rx, - MfClassicBlock* src_block, - uint8_t block_num, - MfClassicKey key_type, - uint64_t key, - int32_t d_value); - -bool mf_classic_write_sector( - FuriHalNfcTxRxContext* tx_rx, - MfClassicData* dest_data, - MfClassicData* src_data, - uint8_t sec_num); - -#ifdef __cplusplus -} -#endif \ No newline at end of file diff --git a/lib/nfc/protocols/mifare_common.c b/lib/nfc/protocols/mifare_common.c deleted file mode 100644 index 90b57e1f031..00000000000 --- a/lib/nfc/protocols/mifare_common.c +++ /dev/null @@ -1,18 +0,0 @@ -#include "mifare_common.h" - -MifareType mifare_common_get_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK) { - MifareType type = MifareTypeUnknown; - - if((ATQA0 == 0x44) && (ATQA1 == 0x00) && (SAK == 0x00)) { - type = MifareTypeUltralight; - } else if( - ((ATQA0 == 0x44 || ATQA0 == 0x04) && (SAK == 0x08 || SAK == 0x88 || SAK == 0x09)) || - ((ATQA0 == 0x42 || ATQA0 == 0x02) && (SAK == 0x18)) || - ((ATQA0 == 0x01) && (ATQA1 == 0x0F) && (SAK == 0x01))) { - type = MifareTypeClassic; - } else if(ATQA0 == 0x44 && ATQA1 == 0x03 && SAK == 0x20) { - type = MifareTypeDesfire; - } - - return type; -} diff --git a/lib/nfc/protocols/mifare_common.h b/lib/nfc/protocols/mifare_common.h deleted file mode 100644 index 2b694d9068a..00000000000 --- a/lib/nfc/protocols/mifare_common.h +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once - -#include - -typedef enum { - MifareTypeUnknown, - MifareTypeUltralight, - MifareTypeClassic, - MifareTypeDesfire, -} MifareType; - -MifareType mifare_common_get_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK); diff --git a/lib/nfc/protocols/mifare_desfire.c b/lib/nfc/protocols/mifare_desfire.c deleted file mode 100644 index e0ead737f87..00000000000 --- a/lib/nfc/protocols/mifare_desfire.c +++ /dev/null @@ -1,665 +0,0 @@ -#include "mifare_desfire.h" -#include -#include - -#define TAG "MifareDESFire" - -void mf_df_clear(MifareDesfireData* data) { - free(data->free_memory); - if(data->master_key_settings) { - MifareDesfireKeyVersion* key_version = data->master_key_settings->key_version_head; - while(key_version) { - MifareDesfireKeyVersion* next_key_version = key_version->next; - free(key_version); - key_version = next_key_version; - } - } - free(data->master_key_settings); - MifareDesfireApplication* app = data->app_head; - while(app) { - MifareDesfireApplication* next_app = app->next; - if(app->key_settings) { - MifareDesfireKeyVersion* key_version = app->key_settings->key_version_head; - while(key_version) { - MifareDesfireKeyVersion* next_key_version = key_version->next; - free(key_version); - key_version = next_key_version; - } - } - free(app->key_settings); - MifareDesfireFile* file = app->file_head; - while(file) { - MifareDesfireFile* next_file = file->next; - free(file->contents); - free(file); - file = next_file; - } - free(app); - app = next_app; - } - data->free_memory = NULL; - data->master_key_settings = NULL; - data->app_head = NULL; -} - -MifareDesfireApplication* mf_df_get_application(MifareDesfireData* data, const uint8_t (*aid)[3]) { - if(!data) { - return NULL; - } - for(MifareDesfireApplication* app = data->app_head; app; app = app->next) { - if(memcmp(aid, app->id, 3) == 0) { - return app; - } - } - return NULL; -} - -MifareDesfireFile* mf_df_get_file(MifareDesfireApplication* app, uint8_t id) { - if(!app) { - return NULL; - } - for(MifareDesfireFile* file = app->file_head; file; file = file->next) { - if(file->id == id) { - return file; - } - } - return NULL; -} - -void mf_df_cat_data(MifareDesfireData* data, FuriString* out) { - mf_df_cat_card_info(data, out); - for(MifareDesfireApplication* app = data->app_head; app; app = app->next) { - mf_df_cat_application(app, out); - } -} - -void mf_df_cat_card_info(MifareDesfireData* data, FuriString* out) { - mf_df_cat_version(&data->version, out); - if(data->free_memory) { - mf_df_cat_free_mem(data->free_memory, out); - } - if(data->master_key_settings) { - mf_df_cat_key_settings(data->master_key_settings, out); - } -} - -void mf_df_cat_version(MifareDesfireVersion* version, FuriString* out) { - furi_string_cat_printf( - out, - "%02x:%02x:%02x:%02x:%02x:%02x:%02x\n", - version->uid[0], - version->uid[1], - version->uid[2], - version->uid[3], - version->uid[4], - version->uid[5], - version->uid[6]); - furi_string_cat_printf( - out, - "hw %02x type %02x sub %02x\n" - " maj %02x min %02x\n" - " size %02x proto %02x\n", - version->hw_vendor, - version->hw_type, - version->hw_subtype, - version->hw_major, - version->hw_minor, - version->hw_storage, - version->hw_proto); - furi_string_cat_printf( - out, - "sw %02x type %02x sub %02x\n" - " maj %02x min %02x\n" - " size %02x proto %02x\n", - version->sw_vendor, - version->sw_type, - version->sw_subtype, - version->sw_major, - version->sw_minor, - version->sw_storage, - version->sw_proto); - furi_string_cat_printf( - out, - "batch %02x:%02x:%02x:%02x:%02x\n" - "week %d year %d\n", - version->batch[0], - version->batch[1], - version->batch[2], - version->batch[3], - version->batch[4], - version->prod_week, - version->prod_year); -} - -void mf_df_cat_free_mem(MifareDesfireFreeMemory* free_mem, FuriString* out) { - furi_string_cat_printf(out, "freeMem %lu\n", free_mem->bytes); -} - -void mf_df_cat_key_settings(MifareDesfireKeySettings* ks, FuriString* out) { - furi_string_cat_printf(out, "changeKeyID %d\n", ks->change_key_id); - furi_string_cat_printf(out, "configChangeable %d\n", ks->config_changeable); - furi_string_cat_printf(out, "freeCreateDelete %d\n", ks->free_create_delete); - furi_string_cat_printf(out, "freeDirectoryList %d\n", ks->free_directory_list); - furi_string_cat_printf(out, "masterChangeable %d\n", ks->master_key_changeable); - if(ks->flags) { - furi_string_cat_printf(out, "flags %d\n", ks->flags); - } - furi_string_cat_printf(out, "maxKeys %d\n", ks->max_keys); - for(MifareDesfireKeyVersion* kv = ks->key_version_head; kv; kv = kv->next) { - furi_string_cat_printf(out, "key %d version %d\n", kv->id, kv->version); - } -} - -void mf_df_cat_application_info(MifareDesfireApplication* app, FuriString* out) { - furi_string_cat_printf(out, "Application %02x%02x%02x\n", app->id[0], app->id[1], app->id[2]); - if(app->key_settings) { - mf_df_cat_key_settings(app->key_settings, out); - } -} - -void mf_df_cat_application(MifareDesfireApplication* app, FuriString* out) { - mf_df_cat_application_info(app, out); - for(MifareDesfireFile* file = app->file_head; file; file = file->next) { - mf_df_cat_file(file, out); - } -} - -void mf_df_cat_file(MifareDesfireFile* file, FuriString* out) { - char* type = "unknown"; - switch(file->type) { - case MifareDesfireFileTypeStandard: - type = "standard"; - break; - case MifareDesfireFileTypeBackup: - type = "backup"; - break; - case MifareDesfireFileTypeValue: - type = "value"; - break; - case MifareDesfireFileTypeLinearRecord: - type = "linear"; - break; - case MifareDesfireFileTypeCyclicRecord: - type = "cyclic"; - break; - } - char* comm = "unknown"; - switch(file->comm) { - case MifareDesfireFileCommunicationSettingsPlaintext: - comm = "plain"; - break; - case MifareDesfireFileCommunicationSettingsAuthenticated: - comm = "auth"; - break; - case MifareDesfireFileCommunicationSettingsEnciphered: - comm = "enciphered"; - break; - } - furi_string_cat_printf(out, "File %d\n", file->id); - furi_string_cat_printf(out, "%s %s\n", type, comm); - furi_string_cat_printf( - out, - "r %d w %d rw %d c %d\n", - file->access_rights >> 12 & 0xF, - file->access_rights >> 8 & 0xF, - file->access_rights >> 4 & 0xF, - file->access_rights & 0xF); - uint16_t size = 0; - uint16_t num = 1; - switch(file->type) { - case MifareDesfireFileTypeStandard: - case MifareDesfireFileTypeBackup: - size = file->settings.data.size; - furi_string_cat_printf(out, "size %d\n", size); - break; - case MifareDesfireFileTypeValue: - size = 4; - furi_string_cat_printf( - out, "lo %lu hi %lu\n", file->settings.value.lo_limit, file->settings.value.hi_limit); - furi_string_cat_printf( - out, - "limit %lu enabled %d\n", - file->settings.value.limited_credit_value, - file->settings.value.limited_credit_enabled); - break; - case MifareDesfireFileTypeLinearRecord: - case MifareDesfireFileTypeCyclicRecord: - size = file->settings.record.size; - num = file->settings.record.cur; - furi_string_cat_printf(out, "size %d\n", size); - furi_string_cat_printf(out, "num %d max %lu\n", num, file->settings.record.max); - break; - } - uint8_t* data = file->contents; - if(data) { - for(int rec = 0; rec < num; rec++) { - furi_string_cat_printf(out, "record %d\n", rec); - for(int ch = 0; ch < size; ch += 4) { - furi_string_cat_printf(out, "%03x|", ch); - for(int i = 0; i < 4; i++) { - if(ch + i < size) { - furi_string_cat_printf(out, "%02x ", data[rec * size + ch + i]); - } else { - furi_string_cat_printf(out, " "); - } - } - for(int i = 0; i < 4 && ch + i < size; i++) { - const size_t data_index = rec * size + ch + i; - if(isprint(data[data_index])) { - furi_string_cat_printf(out, "%c", data[data_index]); - } else { - furi_string_cat_printf(out, "."); - } - } - furi_string_cat_printf(out, "\n"); - } - furi_string_cat_printf(out, " \n"); - } - } -} - -bool mf_df_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK) { - return ATQA0 == 0x44 && ATQA1 == 0x03 && SAK == 0x20; -} - -uint16_t mf_df_prepare_get_version(uint8_t* dest) { - dest[0] = MF_DF_GET_VERSION; - return 1; -} - -bool mf_df_parse_get_version_response(uint8_t* buf, uint16_t len, MifareDesfireVersion* out) { - if(len < 1 || *buf) { - return false; - } - len--; - buf++; - if(len < sizeof(MifareDesfireVersion)) { - return false; - } - memcpy(out, buf, sizeof(MifareDesfireVersion)); - return true; -} - -uint16_t mf_df_prepare_get_free_memory(uint8_t* dest) { - dest[0] = MF_DF_GET_FREE_MEMORY; - return 1; -} - -bool mf_df_parse_get_free_memory_response(uint8_t* buf, uint16_t len, MifareDesfireFreeMemory* out) { - if(len < 1 || *buf) { - return false; - } - len--; - buf++; - if(len != 3) { - return false; - } - out->bytes = buf[0] | (buf[1] << 8) | (buf[2] << 16); - return true; -} - -uint16_t mf_df_prepare_get_key_settings(uint8_t* dest) { - dest[0] = MF_DF_GET_KEY_SETTINGS; - return 1; -} - -bool mf_df_parse_get_key_settings_response( - uint8_t* buf, - uint16_t len, - MifareDesfireKeySettings* out) { - if(len < 1 || *buf) { - return false; - } - len--; - buf++; - if(len < 2) { - return false; - } - out->change_key_id = buf[0] >> 4; - out->config_changeable = (buf[0] & 0x8) != 0; - 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->flags = buf[1] >> 4; - out->max_keys = buf[1] & 0xF; - return true; -} - -uint16_t mf_df_prepare_get_key_version(uint8_t* dest, uint8_t key_id) { - dest[0] = MF_DF_GET_KEY_VERSION; - dest[1] = key_id; - return 2; -} - -bool mf_df_parse_get_key_version_response(uint8_t* buf, uint16_t len, MifareDesfireKeyVersion* out) { - if(len != 2 || *buf) { - return false; - } - out->version = buf[1]; - return true; -} - -uint16_t mf_df_prepare_get_application_ids(uint8_t* dest) { - dest[0] = MF_DF_GET_APPLICATION_IDS; - return 1; -} - -bool mf_df_parse_get_application_ids_response( - uint8_t* buf, - uint16_t len, - MifareDesfireApplication** app_head) { - if(len < 1 || *buf) { - return false; - } - len--; - buf++; - if(len % 3 != 0) { - return false; - } - while(len) { - MifareDesfireApplication* app = malloc(sizeof(MifareDesfireApplication)); - memset(app, 0, sizeof(MifareDesfireApplication)); - memcpy(app->id, buf, 3); - len -= 3; - buf += 3; - *app_head = app; - app_head = &app->next; - } - return true; -} - -uint16_t mf_df_prepare_select_application(uint8_t* dest, uint8_t id[3]) { - dest[0] = MF_DF_SELECT_APPLICATION; - dest[1] = id[0]; - dest[2] = id[1]; - dest[3] = id[2]; - return 4; -} - -bool mf_df_parse_select_application_response(uint8_t* buf, uint16_t len) { - return len == 1 && !*buf; -} - -uint16_t mf_df_prepare_get_file_ids(uint8_t* dest) { - dest[0] = MF_DF_GET_FILE_IDS; - return 1; -} - -bool mf_df_parse_get_file_ids_response(uint8_t* buf, uint16_t len, MifareDesfireFile** file_head) { - if(len < 1 || *buf) { - return false; - } - len--; - buf++; - while(len) { - MifareDesfireFile* file = malloc(sizeof(MifareDesfireFile)); - memset(file, 0, sizeof(MifareDesfireFile)); - file->id = *buf; - len--; - buf++; - *file_head = file; - file_head = &file->next; - } - return true; -} - -uint16_t mf_df_prepare_get_file_settings(uint8_t* dest, uint8_t file_id) { - dest[0] = MF_DF_GET_FILE_SETTINGS; - dest[1] = file_id; - return 2; -} - -bool mf_df_parse_get_file_settings_response(uint8_t* buf, uint16_t len, MifareDesfireFile* out) { - if(len < 5 || *buf) { - return false; - } - len--; - buf++; - out->type = buf[0]; - out->comm = buf[1]; - out->access_rights = buf[2] | (buf[3] << 8); - switch(out->type) { - case MifareDesfireFileTypeStandard: - case MifareDesfireFileTypeBackup: - if(len != 7) { - return false; - } - out->settings.data.size = buf[4] | (buf[5] << 8) | (buf[6] << 16); - break; - case MifareDesfireFileTypeValue: - if(len != 17) { - return false; - } - out->settings.value.lo_limit = buf[4] | (buf[5] << 8) | (buf[6] << 16) | (buf[7] << 24); - out->settings.value.hi_limit = buf[8] | (buf[9] << 8) | (buf[10] << 16) | (buf[11] << 24); - out->settings.value.limited_credit_value = buf[12] | (buf[13] << 8) | (buf[14] << 16) | - (buf[15] << 24); - out->settings.value.limited_credit_enabled = buf[16]; - break; - case MifareDesfireFileTypeLinearRecord: - case MifareDesfireFileTypeCyclicRecord: - if(len != 13) { - return false; - } - out->settings.record.size = buf[4] | (buf[5] << 8) | (buf[6] << 16); - out->settings.record.max = buf[7] | (buf[8] << 8) | (buf[9] << 16); - out->settings.record.cur = buf[10] | (buf[11] << 8) | (buf[12] << 16); - break; - default: - return false; - } - return true; -} - -uint16_t mf_df_prepare_read_data(uint8_t* dest, uint8_t file_id, uint32_t offset, uint32_t len) { - dest[0] = MF_DF_READ_DATA; - dest[1] = file_id; - dest[2] = offset; - dest[3] = offset >> 8; - dest[4] = offset >> 16; - dest[5] = len; - dest[6] = len >> 8; - dest[7] = len >> 16; - return 8; -} - -uint16_t mf_df_prepare_get_value(uint8_t* dest, uint8_t file_id) { - dest[0] = MF_DF_GET_VALUE; - dest[1] = file_id; - return 2; -} - -uint16_t - mf_df_prepare_read_records(uint8_t* dest, uint8_t file_id, uint32_t offset, uint32_t len) { - dest[0] = MF_DF_READ_RECORDS; - dest[1] = file_id; - dest[2] = offset; - dest[3] = offset >> 8; - dest[4] = offset >> 16; - dest[5] = len; - dest[6] = len >> 8; - dest[7] = len >> 16; - return 8; -} - -bool mf_df_parse_read_data_response(uint8_t* buf, uint16_t len, MifareDesfireFile* out) { - if(len < 1 || *buf) { - return false; - } - len--; - buf++; - out->contents = malloc(len); - memcpy(out->contents, buf, len); - return true; -} - -bool mf_df_read_card(FuriHalNfcTxRxContext* tx_rx, MifareDesfireData* data) { - furi_assert(tx_rx); - furi_assert(data); - - bool card_read = false; - do { - // Get version - tx_rx->tx_bits = 8 * mf_df_prepare_get_version(tx_rx->tx_data); - if(!furi_hal_nfc_tx_rx_full(tx_rx)) { - FURI_LOG_W(TAG, "Bad exchange getting version"); - break; - } - if(!mf_df_parse_get_version_response(tx_rx->rx_data, tx_rx->rx_bits / 8, &data->version)) { - FURI_LOG_W(TAG, "Bad DESFire GET_VERSION responce"); - } - - // Get free memory - tx_rx->tx_bits = 8 * mf_df_prepare_get_free_memory(tx_rx->tx_data); - if(furi_hal_nfc_tx_rx_full(tx_rx)) { - data->free_memory = malloc(sizeof(MifareDesfireFreeMemory)); - if(!mf_df_parse_get_free_memory_response( - tx_rx->rx_data, tx_rx->rx_bits / 8, data->free_memory)) { - FURI_LOG_D(TAG, "Bad DESFire GET_FREE_MEMORY response (normal for pre-EV1 cards)"); - free(data->free_memory); - data->free_memory = NULL; - } - } - - // Get key settings - tx_rx->tx_bits = 8 * mf_df_prepare_get_key_settings(tx_rx->tx_data); - if(!furi_hal_nfc_tx_rx_full(tx_rx)) { - FURI_LOG_D(TAG, "Bad exchange getting key settings"); - } else { - data->master_key_settings = malloc(sizeof(MifareDesfireKeySettings)); - if(!mf_df_parse_get_key_settings_response( - tx_rx->rx_data, tx_rx->rx_bits / 8, data->master_key_settings)) { - FURI_LOG_W(TAG, "Bad DESFire GET_KEY_SETTINGS response"); - free(data->master_key_settings); - data->master_key_settings = NULL; - } else { - MifareDesfireKeyVersion** key_version_head = - &data->master_key_settings->key_version_head; - for(uint8_t key_id = 0; key_id < data->master_key_settings->max_keys; key_id++) { - tx_rx->tx_bits = 8 * mf_df_prepare_get_key_version(tx_rx->tx_data, key_id); - if(!furi_hal_nfc_tx_rx_full(tx_rx)) { - FURI_LOG_W(TAG, "Bad exchange getting key version"); - continue; - } - MifareDesfireKeyVersion* key_version = malloc(sizeof(MifareDesfireKeyVersion)); - memset(key_version, 0, sizeof(MifareDesfireKeyVersion)); - key_version->id = key_id; - if(!mf_df_parse_get_key_version_response( - tx_rx->rx_data, tx_rx->rx_bits / 8, key_version)) { - FURI_LOG_W(TAG, "Bad DESFire GET_KEY_VERSION response"); - free(key_version); - continue; - } - *key_version_head = key_version; - key_version_head = &key_version->next; - } - } - } - - // Get application IDs - tx_rx->tx_bits = 8 * mf_df_prepare_get_application_ids(tx_rx->tx_data); - if(!furi_hal_nfc_tx_rx_full(tx_rx)) { - FURI_LOG_W(TAG, "Bad exchange getting application IDs"); - break; - } else { - if(!mf_df_parse_get_application_ids_response( - tx_rx->rx_data, tx_rx->rx_bits / 8, &data->app_head)) { - FURI_LOG_W(TAG, "Bad DESFire GET_APPLICATION_IDS response"); - break; - } - } - - for(MifareDesfireApplication* app = data->app_head; app; app = app->next) { - tx_rx->tx_bits = 8 * mf_df_prepare_select_application(tx_rx->tx_data, app->id); - if(!furi_hal_nfc_tx_rx_full(tx_rx) || - !mf_df_parse_select_application_response( - tx_rx->rx_data, tx_rx->rx_bits / 8)) { //-V1051 - FURI_LOG_W(TAG, "Bad exchange selecting application"); - continue; - } - tx_rx->tx_bits = 8 * mf_df_prepare_get_key_settings(tx_rx->tx_data); - if(!furi_hal_nfc_tx_rx_full(tx_rx)) { - FURI_LOG_W(TAG, "Bad exchange getting key settings"); - } else { - app->key_settings = malloc(sizeof(MifareDesfireKeySettings)); - memset(app->key_settings, 0, sizeof(MifareDesfireKeySettings)); - if(!mf_df_parse_get_key_settings_response( - tx_rx->rx_data, tx_rx->rx_bits / 8, app->key_settings)) { - FURI_LOG_W(TAG, "Bad DESFire GET_KEY_SETTINGS response"); - free(app->key_settings); - app->key_settings = NULL; - continue; - } - - MifareDesfireKeyVersion** key_version_head = &app->key_settings->key_version_head; - for(uint8_t key_id = 0; key_id < app->key_settings->max_keys; key_id++) { - tx_rx->tx_bits = 8 * mf_df_prepare_get_key_version(tx_rx->tx_data, key_id); - if(!furi_hal_nfc_tx_rx_full(tx_rx)) { - FURI_LOG_W(TAG, "Bad exchange getting key version"); - continue; - } - MifareDesfireKeyVersion* key_version = malloc(sizeof(MifareDesfireKeyVersion)); - memset(key_version, 0, sizeof(MifareDesfireKeyVersion)); - key_version->id = key_id; - if(!mf_df_parse_get_key_version_response( - tx_rx->rx_data, tx_rx->rx_bits / 8, key_version)) { - FURI_LOG_W(TAG, "Bad DESFire GET_KEY_VERSION response"); - free(key_version); - continue; - } - *key_version_head = key_version; - key_version_head = &key_version->next; - } - } - - tx_rx->tx_bits = 8 * mf_df_prepare_get_file_ids(tx_rx->tx_data); - if(!furi_hal_nfc_tx_rx_full(tx_rx)) { - FURI_LOG_W(TAG, "Bad exchange getting file IDs"); - } else { - if(!mf_df_parse_get_file_ids_response( - tx_rx->rx_data, tx_rx->rx_bits / 8, &app->file_head)) { - FURI_LOG_W(TAG, "Bad DESFire GET_FILE_IDS response"); - } - } - - for(MifareDesfireFile* file = app->file_head; file; file = file->next) { - tx_rx->tx_bits = 8 * mf_df_prepare_get_file_settings(tx_rx->tx_data, file->id); - if(!furi_hal_nfc_tx_rx_full(tx_rx)) { - FURI_LOG_W(TAG, "Bad exchange getting file settings"); - continue; - } - if(!mf_df_parse_get_file_settings_response( - tx_rx->rx_data, tx_rx->rx_bits / 8, file)) { - FURI_LOG_W(TAG, "Bad DESFire GET_FILE_SETTINGS response"); - continue; - } - switch(file->type) { - case MifareDesfireFileTypeStandard: - case MifareDesfireFileTypeBackup: - tx_rx->tx_bits = 8 * mf_df_prepare_read_data(tx_rx->tx_data, file->id, 0, 0); - break; - case MifareDesfireFileTypeValue: - tx_rx->tx_bits = 8 * mf_df_prepare_get_value(tx_rx->tx_data, file->id); - break; - case MifareDesfireFileTypeLinearRecord: - case MifareDesfireFileTypeCyclicRecord: - tx_rx->tx_bits = - 8 * mf_df_prepare_read_records(tx_rx->tx_data, file->id, 0, 0); - break; - } - if(!furi_hal_nfc_tx_rx_full(tx_rx)) { - FURI_LOG_W(TAG, "Bad exchange reading file %d", file->id); - continue; - } - if(!mf_df_parse_read_data_response(tx_rx->rx_data, tx_rx->rx_bits / 8, file)) { - FURI_LOG_W(TAG, "Bad response reading file %d", file->id); - continue; - } - } - } - - card_read = true; - } while(false); - - return card_read; -} diff --git a/lib/nfc/protocols/mifare_desfire.h b/lib/nfc/protocols/mifare_desfire.h deleted file mode 100644 index 8faa98ec246..00000000000 --- a/lib/nfc/protocols/mifare_desfire.h +++ /dev/null @@ -1,179 +0,0 @@ -#pragma once - -#include -#include - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -#define MF_DF_GET_VERSION (0x60) -#define MF_DF_GET_FREE_MEMORY (0x6E) -#define MF_DF_GET_KEY_SETTINGS (0x45) -#define MF_DF_GET_KEY_VERSION (0x64) -#define MF_DF_GET_APPLICATION_IDS (0x6A) -#define MF_DF_SELECT_APPLICATION (0x5A) -#define MF_DF_GET_FILE_IDS (0x6F) -#define MF_DF_GET_FILE_SETTINGS (0xF5) - -#define MF_DF_READ_DATA (0xBD) -#define MF_DF_GET_VALUE (0x6C) -#define MF_DF_READ_RECORDS (0xBB) - -typedef struct { - uint8_t hw_vendor; - uint8_t hw_type; - uint8_t hw_subtype; - uint8_t hw_major; - uint8_t hw_minor; - uint8_t hw_storage; - uint8_t hw_proto; - - uint8_t sw_vendor; - uint8_t sw_type; - uint8_t sw_subtype; - uint8_t sw_major; - uint8_t sw_minor; - uint8_t sw_storage; - uint8_t sw_proto; - - uint8_t uid[7]; - uint8_t batch[5]; - uint8_t prod_week; - uint8_t prod_year; -} MifareDesfireVersion; - -typedef struct { - uint32_t bytes; -} MifareDesfireFreeMemory; // EV1+ only - -typedef struct MifareDesfireKeyVersion { - uint8_t id; - uint8_t version; - struct MifareDesfireKeyVersion* next; -} MifareDesfireKeyVersion; - -typedef struct { - uint8_t change_key_id; - bool config_changeable; - bool free_create_delete; - bool free_directory_list; - bool master_key_changeable; - uint8_t flags; - uint8_t max_keys; - MifareDesfireKeyVersion* key_version_head; -} MifareDesfireKeySettings; - -typedef enum { - MifareDesfireFileTypeStandard = 0, - MifareDesfireFileTypeBackup = 1, - MifareDesfireFileTypeValue = 2, - MifareDesfireFileTypeLinearRecord = 3, - MifareDesfireFileTypeCyclicRecord = 4, -} MifareDesfireFileType; - -typedef enum { - MifareDesfireFileCommunicationSettingsPlaintext = 0, - MifareDesfireFileCommunicationSettingsAuthenticated = 1, - MifareDesfireFileCommunicationSettingsEnciphered = 3, -} MifareDesfireFileCommunicationSettings; - -typedef struct MifareDesfireFile { - uint8_t id; - MifareDesfireFileType type; - MifareDesfireFileCommunicationSettings comm; - uint16_t access_rights; - union { - struct { - uint32_t size; - } data; - struct { - uint32_t lo_limit; - uint32_t hi_limit; - uint32_t limited_credit_value; - bool limited_credit_enabled; - } value; - struct { - uint32_t size; - uint32_t max; - uint32_t cur; - } record; - } settings; - uint8_t* contents; - - struct MifareDesfireFile* next; -} MifareDesfireFile; - -typedef struct MifareDesfireApplication { - uint8_t id[3]; - MifareDesfireKeySettings* key_settings; - MifareDesfireFile* file_head; - - struct MifareDesfireApplication* next; -} MifareDesfireApplication; - -typedef struct { - MifareDesfireVersion version; - MifareDesfireFreeMemory* free_memory; - MifareDesfireKeySettings* master_key_settings; - MifareDesfireApplication* app_head; -} MifareDesfireData; - -void mf_df_clear(MifareDesfireData* data); - -void mf_df_cat_data(MifareDesfireData* data, FuriString* out); -void mf_df_cat_card_info(MifareDesfireData* data, FuriString* out); -void mf_df_cat_version(MifareDesfireVersion* version, FuriString* out); -void mf_df_cat_free_mem(MifareDesfireFreeMemory* free_mem, FuriString* out); -void mf_df_cat_key_settings(MifareDesfireKeySettings* ks, FuriString* out); -void mf_df_cat_application_info(MifareDesfireApplication* app, FuriString* out); -void mf_df_cat_application(MifareDesfireApplication* app, FuriString* out); -void mf_df_cat_file(MifareDesfireFile* file, FuriString* out); - -bool mf_df_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK); - -MifareDesfireApplication* mf_df_get_application(MifareDesfireData* data, const uint8_t (*aid)[3]); -MifareDesfireFile* mf_df_get_file(MifareDesfireApplication* app, uint8_t id); - -uint16_t mf_df_prepare_get_version(uint8_t* dest); -bool mf_df_parse_get_version_response(uint8_t* buf, uint16_t len, MifareDesfireVersion* out); - -uint16_t mf_df_prepare_get_free_memory(uint8_t* dest); -bool mf_df_parse_get_free_memory_response(uint8_t* buf, uint16_t len, MifareDesfireFreeMemory* out); - -uint16_t mf_df_prepare_get_key_settings(uint8_t* dest); -bool mf_df_parse_get_key_settings_response( - uint8_t* buf, - uint16_t len, - MifareDesfireKeySettings* out); - -uint16_t mf_df_prepare_get_key_version(uint8_t* dest, uint8_t key_id); -bool mf_df_parse_get_key_version_response(uint8_t* buf, uint16_t len, MifareDesfireKeyVersion* out); - -uint16_t mf_df_prepare_get_application_ids(uint8_t* dest); -bool mf_df_parse_get_application_ids_response( - uint8_t* buf, - uint16_t len, - MifareDesfireApplication** app_head); - -uint16_t mf_df_prepare_select_application(uint8_t* dest, uint8_t id[3]); -bool mf_df_parse_select_application_response(uint8_t* buf, uint16_t len); - -uint16_t mf_df_prepare_get_file_ids(uint8_t* dest); -bool mf_df_parse_get_file_ids_response(uint8_t* buf, uint16_t len, MifareDesfireFile** file_head); - -uint16_t mf_df_prepare_get_file_settings(uint8_t* dest, uint8_t file_id); -bool mf_df_parse_get_file_settings_response(uint8_t* buf, uint16_t len, MifareDesfireFile* out); - -uint16_t mf_df_prepare_read_data(uint8_t* dest, uint8_t file_id, uint32_t offset, uint32_t len); -uint16_t mf_df_prepare_get_value(uint8_t* dest, uint8_t file_id); -uint16_t mf_df_prepare_read_records(uint8_t* dest, uint8_t file_id, uint32_t offset, uint32_t len); -bool mf_df_parse_read_data_response(uint8_t* buf, uint16_t len, MifareDesfireFile* out); - -bool mf_df_read_card(FuriHalNfcTxRxContext* tx_rx, MifareDesfireData* data); - -#ifdef __cplusplus -} -#endif \ No newline at end of file diff --git a/lib/nfc/protocols/mifare_ultralight.c b/lib/nfc/protocols/mifare_ultralight.c deleted file mode 100644 index 266b6e2e2e4..00000000000 --- a/lib/nfc/protocols/mifare_ultralight.c +++ /dev/null @@ -1,1946 +0,0 @@ -#include -#include -#include "mifare_ultralight.h" -#include "nfc_util.h" -#include -#include - -#define TAG "MfUltralight" - -// Algorithms from: https://github.com/RfidResearchGroup/proxmark3/blob/0f6061c16f072372b7d4d381911f1542afbc3a69/common/generator.c#L110 -uint32_t mf_ul_pwdgen_xiaomi(FuriHalNfcDevData* data) { - uint8_t hash[20]; - mbedtls_sha1(data->uid, data->uid_len, hash); - - uint32_t pwd = 0; - pwd |= (hash[hash[0] % 20]) << 24; - pwd |= (hash[(hash[0] + 5) % 20]) << 16; - pwd |= (hash[(hash[0] + 13) % 20]) << 8; - pwd |= (hash[(hash[0] + 17) % 20]); - - return pwd; -} - -uint32_t mf_ul_pwdgen_amiibo(FuriHalNfcDevData* data) { - uint8_t* uid = data->uid; - - uint32_t pwd = 0; - pwd |= (uid[1] ^ uid[3] ^ 0xAA) << 24; - pwd |= (uid[2] ^ uid[4] ^ 0x55) << 16; - pwd |= (uid[3] ^ uid[5] ^ 0xAA) << 8; - pwd |= uid[4] ^ uid[6] ^ 0x55; - - return pwd; -} - -bool mf_ul_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK) { - if((ATQA0 == 0x44) && (ATQA1 == 0x00) && (SAK == 0x00)) { - return true; - } - return false; -} - -void mf_ul_reset(MfUltralightData* data) { - furi_assert(data); - data->type = MfUltralightTypeUnknown; - memset(&data->version, 0, sizeof(MfUltralightVersion)); - memset(data->signature, 0, sizeof(data->signature)); - memset(data->counter, 0, sizeof(data->counter)); - memset(data->tearing, 0, sizeof(data->tearing)); - memset(data->data, 0, sizeof(data->data)); - data->data_size = 0; - data->data_read = 0; - data->curr_authlim = 0; - data->auth_success = false; -} - -static MfUltralightFeatures mf_ul_get_features(MfUltralightType type) { - switch(type) { - case MfUltralightTypeUL11: - case MfUltralightTypeUL21: - return MfUltralightSupportFastRead | MfUltralightSupportCompatWrite | - MfUltralightSupportReadCounter | MfUltralightSupportIncrCounter | - MfUltralightSupportAuth | MfUltralightSupportSignature | - MfUltralightSupportTearingFlags | MfUltralightSupportVcsl; - case MfUltralightTypeNTAG213: - case MfUltralightTypeNTAG215: - case MfUltralightTypeNTAG216: - return MfUltralightSupportFastRead | MfUltralightSupportCompatWrite | - MfUltralightSupportReadCounter | MfUltralightSupportAuth | - MfUltralightSupportSignature | MfUltralightSupportSingleCounter | - MfUltralightSupportAsciiMirror; - case MfUltralightTypeNTAGI2C1K: - case MfUltralightTypeNTAGI2C2K: - return MfUltralightSupportFastRead | MfUltralightSupportSectorSelect; - case MfUltralightTypeNTAGI2CPlus1K: - case MfUltralightTypeNTAGI2CPlus2K: - return MfUltralightSupportFastRead | MfUltralightSupportAuth | - MfUltralightSupportFastWrite | MfUltralightSupportSignature | - MfUltralightSupportSectorSelect; - case MfUltralightTypeNTAG203: - return MfUltralightSupportCompatWrite | MfUltralightSupportCounterInMemory; - case MfUltralightTypeULC: - return MfUltralightSupportCompatWrite | MfUltralightSupport3DesAuth; - default: - // Assumed original MFUL 512-bit - return MfUltralightSupportCompatWrite; - } -} - -static void mf_ul_set_default_version(MfUltralightReader* reader, MfUltralightData* data) { - data->type = MfUltralightTypeUnknown; - reader->pages_to_read = 16; -} - -static void mf_ul_set_version_ntag203(MfUltralightReader* reader, MfUltralightData* data) { - data->type = MfUltralightTypeNTAG203; - reader->pages_to_read = 42; -} - -static void mf_ul_set_version_ulc(MfUltralightReader* reader, MfUltralightData* data) { - data->type = MfUltralightTypeULC; - reader->pages_to_read = 48; -} - -bool mf_ultralight_read_version( - FuriHalNfcTxRxContext* tx_rx, - MfUltralightReader* reader, - MfUltralightData* data) { - bool version_read = false; - - do { - FURI_LOG_D(TAG, "Reading version"); - tx_rx->tx_data[0] = MF_UL_GET_VERSION_CMD; - tx_rx->tx_bits = 8; - tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; - if(!furi_hal_nfc_tx_rx(tx_rx, 50) || tx_rx->rx_bits != 64) { - FURI_LOG_D(TAG, "Failed reading version"); - mf_ul_set_default_version(reader, data); - furi_hal_nfc_sleep(); - furi_hal_nfc_activate_nfca(300, NULL); - break; - } - MfUltralightVersion* version = (MfUltralightVersion*)tx_rx->rx_data; - data->version = *version; - if(version->storage_size == 0x0B || version->storage_size == 0x00) { - data->type = MfUltralightTypeUL11; - reader->pages_to_read = 20; - } else if(version->storage_size == 0x0E) { - data->type = MfUltralightTypeUL21; - reader->pages_to_read = 41; - } else if(version->storage_size == 0x0F) { - data->type = MfUltralightTypeNTAG213; - reader->pages_to_read = 45; - } else if(version->storage_size == 0x11) { - data->type = MfUltralightTypeNTAG215; - reader->pages_to_read = 135; - } 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; - known = true; - } else if(version->storage_size == 0x15) { - data->type = MfUltralightTypeNTAGI2C2K; - reader->pages_to_read = 485; - known = true; - } - } else if(version->prod_ver_minor == 2) { - if(version->storage_size == 0x13) { - data->type = MfUltralightTypeNTAGI2CPlus1K; - reader->pages_to_read = 236; - known = true; - } else if(version->storage_size == 0x15) { - data->type = MfUltralightTypeNTAGI2CPlus2K; - reader->pages_to_read = 492; - known = true; - } - } - - if(!known) { - mf_ul_set_default_version(reader, data); - } - } else if(version->storage_size == 0x13) { - data->type = MfUltralightTypeNTAG216; - reader->pages_to_read = 231; - } else { - mf_ul_set_default_version(reader, data); - break; - } - version_read = true; - } while(false); - - reader->supported_features = mf_ul_get_features(data->type); - return version_read; -} - -bool mf_ultralight_authenticate(FuriHalNfcTxRxContext* tx_rx, uint32_t key, uint16_t* pack) { - furi_assert(pack); - bool authenticated = false; - - do { - FURI_LOG_D(TAG, "Authenticating"); - tx_rx->tx_data[0] = MF_UL_PWD_AUTH; - nfc_util_num2bytes(key, 4, &tx_rx->tx_data[1]); - tx_rx->tx_bits = 40; - tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; - if(!furi_hal_nfc_tx_rx(tx_rx, 50)) { - FURI_LOG_D(TAG, "Tag did not respond to authentication"); - break; - } - - // PACK - if(tx_rx->rx_bits < 2 * 8) { - FURI_LOG_D(TAG, "Authentication failed"); - break; - } - - *pack = (tx_rx->rx_data[1] << 8) | tx_rx->rx_data[0]; - - FURI_LOG_I(TAG, "Auth success. Password: %08lX. PACK: %04X", key, *pack); - authenticated = true; - } while(false); - - return authenticated; -} - -static int16_t mf_ultralight_page_addr_to_tag_addr(uint8_t sector, uint8_t page) { - return sector * 256 + page; -} - -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; - } -} - -MfUltralightConfigPages* mf_ultralight_get_config_pages(MfUltralightData* data) { - if(data->type >= MfUltralightTypeUL11 && data->type <= MfUltralightTypeNTAG216) { - return (MfUltralightConfigPages*)&data->data[data->data_size - 4 * 4]; - } else if( - data->type >= MfUltralightTypeNTAGI2CPlus1K && - data->type <= MfUltralightTypeNTAGI2CPlus2K) { - return (MfUltralightConfigPages*)&data->data[0xe3 * 4]; //-V641 - } else { - return NULL; - } -} - -static uint16_t mf_ultralight_calc_auth_count(MfUltralightData* data) { - if(mf_ul_get_features(data->type) & MfUltralightSupportAuth) { - MfUltralightConfigPages* config = mf_ultralight_get_config_pages(data); - uint16_t scaled_authlim = config->access.authlim; - // NTAG I2C Plus uses 2^AUTHLIM attempts rather than the direct number - if(scaled_authlim > 0 && data->type >= MfUltralightTypeNTAGI2CPlus1K && - data->type <= MfUltralightTypeNTAGI2CPlus2K) { - scaled_authlim = 1 << scaled_authlim; - } - return scaled_authlim; - } - - return 0; -} - -// NTAG21x will NAK if NFC_CNT_EN unset, so preempt -static bool mf_ultralight_should_read_counters(MfUltralightData* data) { - if(data->type < MfUltralightTypeNTAG213 || data->type > MfUltralightTypeNTAG216) return true; - - MfUltralightConfigPages* config = mf_ultralight_get_config_pages(data); - return config->access.nfc_cnt_en; -} - -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_direct( - FuriHalNfcTxRxContext* tx_rx, - uint8_t start_index, - uint8_t* data) { - FURI_LOG_D(TAG, "Reading pages %d - %d", start_index, start_index + 3); - tx_rx->tx_data[0] = MF_UL_READ_CMD; - tx_rx->tx_data[1] = start_index; - tx_rx->tx_bits = 16; - tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; - if(!furi_hal_nfc_tx_rx(tx_rx, 50) || tx_rx->rx_bits < 16 * 8) { - FURI_LOG_D(TAG, "Failed to read pages %d - %d", start_index, start_index + 3); - return false; - } - memcpy(data, tx_rx->rx_data, 16); //-V1086 - 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); - - 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 %zu - %zu", i, i + (valid_pages > 4 ? 4 : valid_pages) - 1U); - tx_rx->tx_data[0] = MF_UL_READ_CMD; - 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) || tx_rx->rx_bits < 16 * 8) { - FURI_LOG_D( - TAG, - "Failed to read pages %zu - %zu", - i, - i + (valid_pages > 4 ? 4 : valid_pages) - 1U); - break; - } - - if(valid_pages > 4) { - pages_read_cnt = 4; - } else { - pages_read_cnt = valid_pages; - } - reader->pages_read += pages_read_cnt; - memcpy(&data->data[i * 4], tx_rx->rx_data, pages_read_cnt * 4); - } - data->data_size = reader->pages_to_read * 4; - data->data_read = reader->pages_read * 4; - - return reader->pages_read > 0; -} - -bool mf_ultralight_fast_read_pages( - FuriHalNfcTxRxContext* tx_rx, - MfUltralightReader* reader, - MfUltralightData* data) { - 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; -} - -bool mf_ultralight_read_signature(FuriHalNfcTxRxContext* tx_rx, MfUltralightData* data) { - bool signature_read = false; - - FURI_LOG_D(TAG, "Reading signature"); - tx_rx->tx_data[0] = MF_UL_READ_SIG; - tx_rx->tx_data[1] = 0; - tx_rx->tx_bits = 16; - tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; - if(furi_hal_nfc_tx_rx(tx_rx, 50)) { - memcpy(data->signature, tx_rx->rx_data, sizeof(data->signature)); - signature_read = true; - } else { - FURI_LOG_D(TAG, "Failed redaing signature"); - } - - return signature_read; -} - -bool mf_ultralight_read_counters(FuriHalNfcTxRxContext* tx_rx, MfUltralightData* data) { - uint8_t counter_read = 0; - - FURI_LOG_D(TAG, "Reading counters"); - bool is_single_counter = (mf_ul_get_features(data->type) & MfUltralightSupportSingleCounter) != - 0; - for(size_t i = is_single_counter ? 2 : 0; i < 3; i++) { - tx_rx->tx_data[0] = MF_UL_READ_CNT; - tx_rx->tx_data[1] = i; - 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 %d counter", i); - break; - } - data->counter[i] = (tx_rx->rx_data[2] << 16) | (tx_rx->rx_data[1] << 8) | - tx_rx->rx_data[0]; - counter_read++; - } - - return counter_read == (is_single_counter ? 1 : 3); -} - -bool mf_ultralight_read_tearing_flags(FuriHalNfcTxRxContext* tx_rx, MfUltralightData* data) { - uint8_t flag_read = 0; - - FURI_LOG_D(TAG, "Reading tearing flags"); - for(size_t i = 0; i < 3; i++) { - tx_rx->tx_data[0] = MF_UL_CHECK_TEARING; - tx_rx->tx_data[1] = i; - 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 %d tearing flag", i); - break; - } - data->tearing[i] = tx_rx->rx_data[0]; - flag_read++; - } - - return flag_read == 2; -} - -static bool mf_ul_probe_3des_auth(FuriHalNfcTxRxContext* tx_rx) { - tx_rx->tx_data[0] = MF_UL_AUTHENTICATE_1; - tx_rx->tx_data[1] = 0; - tx_rx->tx_bits = 16; - tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; - bool rc = furi_hal_nfc_tx_rx(tx_rx, 50) && tx_rx->rx_bits == 9 * 8 && - tx_rx->rx_data[0] == 0xAF; - - // Reset just in case, we're not going to finish authenticating and need to if tag doesn't support auth - furi_hal_nfc_sleep(); - furi_hal_nfc_activate_nfca(300, NULL); - - return rc; -} - -bool mf_ul_read_card( - FuriHalNfcTxRxContext* tx_rx, - MfUltralightReader* reader, - MfUltralightData* data) { - furi_assert(tx_rx); - furi_assert(reader); - furi_assert(data); - - bool card_read = false; - - // Read Mifare Ultralight version - if(mf_ultralight_read_version(tx_rx, reader, data)) { - if(reader->supported_features & MfUltralightSupportSignature) { - // Read Signature - mf_ultralight_read_signature(tx_rx, data); - } - } else { - uint8_t dummy[16]; - // No GET_VERSION command, check if AUTHENTICATE command available (detect UL C). - if(mf_ul_probe_3des_auth(tx_rx)) { - mf_ul_set_version_ulc(reader, data); - } else if(mf_ultralight_read_pages_direct(tx_rx, 41, dummy)) { - // No AUTHENTICATE, check for NTAG203 by reading last page (41) - mf_ul_set_version_ntag203(reader, data); - } else { - // We're really an original Mifare Ultralight, reset tag for safety - furi_hal_nfc_sleep(); - furi_hal_nfc_activate_nfca(300, NULL); - } - - reader->supported_features = mf_ul_get_features(data->type); - } - - card_read = mf_ultralight_read_pages(tx_rx, reader, data); - - if(card_read) { - if(reader->supported_features & MfUltralightSupportReadCounter && - mf_ultralight_should_read_counters(data)) { - mf_ultralight_read_counters(tx_rx, data); - } - if(reader->supported_features & MfUltralightSupportTearingFlags) { - mf_ultralight_read_tearing_flags(tx_rx, data); - } - data->curr_authlim = 0; - - if(reader->pages_read == reader->pages_to_read && - reader->supported_features & MfUltralightSupportAuth && !data->auth_success) { - MfUltralightConfigPages* config = mf_ultralight_get_config_pages(data); - if(config->access.authlim == 0) { - // Attempt to auth with default PWD - uint16_t pack; - data->auth_success = mf_ultralight_authenticate(tx_rx, MF_UL_DEFAULT_PWD, &pack); - if(data->auth_success) { - config->auth_data.pwd.value = MF_UL_DEFAULT_PWD; - config->auth_data.pack.value = pack; - } else { - furi_hal_nfc_sleep(); - furi_hal_nfc_activate_nfca(300, NULL); - } - } - } - } - - if(reader->pages_read != reader->pages_to_read) { - if(reader->supported_features & MfUltralightSupportAuth) { - // Probably password protected, fix AUTH0 and PROT so before AUTH0 - // can be written and since AUTH0 won't be readable, like on the - // original card - MfUltralightConfigPages* config = mf_ultralight_get_config_pages(data); - config->auth0 = reader->pages_read; - config->access.prot = true; - } - } - - return card_read; -} - -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) { - if(emulator->config_cache.access.prot) { - uint8_t auth0 = emulator->config_cache.auth0; - 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) { //-V614 - ++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_check_auth(MfUltralightEmulator* emulator, uint8_t start_page, bool is_write) { - if(!emulator->auth_success) { - if(start_page >= emulator->config_cache.auth0 && - (emulator->config_cache.access.prot || is_write)) - return false; - } - - if(is_write && emulator->config_cache.access.cfglck) { - uint16_t config_start_page = emulator->page_num - 4; - if(start_page == config_start_page || start_page == config_start_page + 1) return false; - } - - return true; -} - -static bool mf_ul_ntag_i2c_plus_check_auth( - MfUltralightEmulator* emulator, - uint8_t start_page, - bool is_write) { - if(!emulator->auth_success) { - // Check NFC_PROT - if(emulator->curr_sector == 0 && (emulator->config_cache.access.prot || is_write)) { - if(start_page >= emulator->config_cache.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 - if(emulator->config_cache.access.nfc_dis_sec1) return false; - } - - return true; -} - -static int16_t mf_ul_get_dynamic_lock_page_addr(MfUltralightData* data) { - switch(data->type) { - case MfUltralightTypeNTAG203: - return 0x28; - case MfUltralightTypeUL21: - case MfUltralightTypeNTAG213: - case MfUltralightTypeNTAG215: - case MfUltralightTypeNTAG216: - return data->data_size / 4 - 5; - case MfUltralightTypeNTAGI2C1K: - case MfUltralightTypeNTAGI2CPlus1K: - case MfUltralightTypeNTAGI2CPlus2K: - return 0xe2; - case MfUltralightTypeNTAGI2C2K: - return 0x1e0; - default: - return -1; // No dynamic lock bytes - } -} - -// Returns true if page not locked -// write_page is tag address -static bool mf_ul_check_lock(MfUltralightEmulator* emulator, int16_t write_page) { - if(write_page < 2) return false; // Page 0-1 is always locked - if(write_page == 2) return true; // Page 2 does not have a lock flag - - // Check static lock bytes - if(write_page <= 15) { - uint16_t static_lock_bytes = emulator->data.data[10] | (emulator->data.data[11] << 8); - return (static_lock_bytes & (1 << write_page)) == 0; - } - - // Check dynamic lock bytes - - // Check max page - switch(emulator->data.type) { - case MfUltralightTypeNTAG203: - // Counter page can be locked and is after dynamic locks - if(write_page == 40) return true; - break; - case MfUltralightTypeUL21: - case MfUltralightTypeNTAG213: - case MfUltralightTypeNTAG215: - case MfUltralightTypeNTAG216: - if(write_page >= emulator->page_num - 5) return true; - break; - case MfUltralightTypeNTAGI2C1K: - case MfUltralightTypeNTAGI2CPlus1K: - if(write_page > 225) return true; - break; - case MfUltralightTypeNTAGI2C2K: - if(write_page > 479) return true; - break; - case MfUltralightTypeNTAGI2CPlus2K: - if(write_page >= 226 && write_page <= 255) return true; - if(write_page >= 512) return true; - break; - default: - furi_crash("Unknown MFUL"); - return true; - } - - int16_t dynamic_lock_index = mf_ul_get_dynamic_lock_page_addr(&emulator->data); - if(dynamic_lock_index == -1) return true; - // Run address through converter because NTAG I2C 2K is special - uint16_t valid_pages; // unused - dynamic_lock_index = - mf_ultralight_ntag_i2c_addr_tag_to_lin( - &emulator->data, dynamic_lock_index & 0xff, dynamic_lock_index >> 8, &valid_pages) * - 4; - - uint16_t dynamic_lock_bytes = emulator->data.data[dynamic_lock_index] | - (emulator->data.data[dynamic_lock_index + 1] << 8); - uint8_t shift; - - switch(emulator->data.type) { - // low byte LSB range, MSB range - case MfUltralightTypeNTAG203: - if(write_page >= 16 && write_page <= 27) //-V560 - shift = (write_page - 16) / 4 + 1; - else if(write_page >= 28 && write_page <= 39) //-V560 - shift = (write_page - 28) / 4 + 5; - else if(write_page == 41) - shift = 12; - else { - furi_crash("Unknown MFUL"); - } - - break; - case MfUltralightTypeUL21: - case MfUltralightTypeNTAG213: - // 16-17, 30-31 - shift = (write_page - 16) / 2; - break; - case MfUltralightTypeNTAG215: - case MfUltralightTypeNTAG216: - case MfUltralightTypeNTAGI2C1K: - case MfUltralightTypeNTAGI2CPlus1K: - // 16-31, 128-129 - // 16-31, 128-143 - shift = (write_page - 16) / 16; - break; - case MfUltralightTypeNTAGI2C2K: - // 16-47, 240-271 - shift = (write_page - 16) / 32; - break; - case MfUltralightTypeNTAGI2CPlus2K: - // 16-47, 256-271 - if(write_page >= 208 && write_page <= 225) - shift = 6; - else if(write_page >= 256 && write_page <= 271) - shift = 7; - else - shift = (write_page - 16) / 32; - break; - default: - furi_crash("Unknown MFUL"); - break; - } - - return (dynamic_lock_bytes & (1 << shift)) == 0; -} - -static void mf_ul_make_ascii_mirror(MfUltralightEmulator* emulator, FuriString* str) { - // Locals to improve readability - uint8_t mirror_page = emulator->config->mirror_page; - uint8_t mirror_byte = emulator->config->mirror.mirror_byte; - MfUltralightMirrorConf mirror_conf = emulator->config_cache.mirror.mirror_conf; - uint16_t last_user_page_index = emulator->page_num - 6; - bool uid_printed = false; - - if(mirror_conf == MfUltralightMirrorUid || mirror_conf == MfUltralightMirrorUidCounter) { - // UID range check - if(mirror_page < 4 || mirror_page > last_user_page_index - 3 || - (mirror_page == last_user_page_index - 3 && mirror_byte > 2)) { - if(mirror_conf == MfUltralightMirrorUid) return; - // NTAG21x has the peculiar behavior when UID+counter selected, if UID does not fit but - // counter will fit, it will actually mirror the counter - furi_string_cat(str, " "); - } else { - for(int i = 0; i < 3; ++i) { - furi_string_cat_printf(str, "%02X", emulator->data.data[i]); - } - // Skip BCC0 - for(int i = 4; i < 8; ++i) { - furi_string_cat_printf(str, "%02X", emulator->data.data[i]); - } - uid_printed = true; - } - - uint16_t next_byte_offset = mirror_page * 4 + mirror_byte + 14; - if(mirror_conf == MfUltralightMirrorUidCounter) ++next_byte_offset; - mirror_page = next_byte_offset / 4; - mirror_byte = next_byte_offset % 4; - } - - if(mirror_conf == MfUltralightMirrorCounter || mirror_conf == MfUltralightMirrorUidCounter) { - // Counter is only printed if counter enabled - if(emulator->config_cache.access.nfc_cnt_en) { - // Counter protection check - if(emulator->config_cache.access.nfc_cnt_pwd_prot && !emulator->auth_success) return; - // Counter range check - if(mirror_page < 4) return; - if(mirror_page > last_user_page_index - 1) return; - if(mirror_page == last_user_page_index - 1 && mirror_byte > 2) return; - - if(mirror_conf == MfUltralightMirrorUidCounter) - furi_string_cat(str, uid_printed ? "x" : " "); - - furi_string_cat_printf(str, "%06lX", emulator->data.counter[2]); - } - } -} - -static void mf_ul_increment_single_counter(MfUltralightEmulator* emulator) { - if(!emulator->read_counter_incremented && emulator->config_cache.access.nfc_cnt_en) { - if(emulator->data.counter[2] < 0xFFFFFF) { - ++emulator->data.counter[2]; - emulator->data_changed = true; - } - emulator->read_counter_incremented = true; - } -} - -static bool - mf_ul_emulate_ntag203_counter_write(MfUltralightEmulator* emulator, uint8_t* page_buff) { - // We'll reuse the existing counters for other NTAGs as staging - // Counter 0 stores original value, data is new value - uint32_t counter_value; - if(emulator->data.tearing[0] == MF_UL_TEARING_FLAG_DEFAULT) { - counter_value = emulator->data.data[MF_UL_NTAG203_COUNTER_PAGE * 4] | - (emulator->data.data[MF_UL_NTAG203_COUNTER_PAGE * 4 + 1] << 8); - } else { - // We've had a reset here, so load from original value - counter_value = emulator->data.counter[0]; - } - // Although the datasheet says increment by 0 is always possible, this is not the case on - // an actual tag. If the counter is at 0xFFFF, any writes are locked out. - if(counter_value == 0xFFFF) return false; - uint32_t increment = page_buff[0] | (page_buff[1] << 8); - if(counter_value == 0) { - counter_value = increment; - } else { - // Per datasheet specifying > 0x000F is supposed to NAK, but actual tag doesn't - increment &= 0x000F; - if(counter_value + increment > 0xFFFF) return false; - counter_value += increment; - } - // Commit to new value counter - emulator->data.data[MF_UL_NTAG203_COUNTER_PAGE * 4] = (uint8_t)counter_value; - emulator->data.data[MF_UL_NTAG203_COUNTER_PAGE * 4 + 1] = (uint8_t)(counter_value >> 8); - emulator->data.tearing[0] = MF_UL_TEARING_FLAG_DEFAULT; - if(counter_value == 0xFFFF) { - // Tag will lock out counter if final number is 0xFFFF, even if you try to roll it back - emulator->data.counter[1] = 0xFFFF; - } - emulator->data_changed = true; - return true; -} - -static void mf_ul_emulate_write( - MfUltralightEmulator* emulator, - int16_t tag_addr, - int16_t write_page, - uint8_t* page_buff) { - // Assumption: all access checks have been completed - - if(tag_addr == 2) { - // Handle static locks - uint16_t orig_static_locks = emulator->data.data[write_page * 4 + 2] | - (emulator->data.data[write_page * 4 + 3] << 8); - uint16_t new_static_locks = page_buff[2] | (page_buff[3] << 8); - if(orig_static_locks & 1) new_static_locks &= ~0x08; - if(orig_static_locks & 2) new_static_locks &= ~0xF0; - if(orig_static_locks & 4) new_static_locks &= 0xFF; - new_static_locks |= orig_static_locks; - page_buff[0] = emulator->data.data[write_page * 4]; - page_buff[1] = emulator->data.data[write_page * 4 + 1]; - page_buff[2] = new_static_locks & 0xff; - page_buff[3] = new_static_locks >> 8; - } else if(tag_addr == 3) { - // Handle OTP/capability container - *(uint32_t*)page_buff |= *(uint32_t*)&emulator->data.data[write_page * 4]; - } else if(tag_addr == mf_ul_get_dynamic_lock_page_addr(&emulator->data)) { - // Handle dynamic locks - if(emulator->data.type == MfUltralightTypeNTAG203) { - // NTAG203 lock bytes are a bit different from the others - uint8_t orig_page_lock_byte = emulator->data.data[write_page * 4]; - uint8_t orig_cnt_lock_byte = emulator->data.data[write_page * 4 + 1]; - uint8_t new_page_lock_byte = page_buff[0]; - uint8_t new_cnt_lock_byte = page_buff[1]; - - if(orig_page_lock_byte & 0x01) // Block lock bits 1-3 - new_page_lock_byte &= ~0x0E; - if(orig_page_lock_byte & 0x10) // Block lock bits 5-7 - new_page_lock_byte &= ~0xE0; - for(uint8_t i = 0; i < 4; ++i) { - if(orig_cnt_lock_byte & (1 << i)) // Block lock counter bit - new_cnt_lock_byte &= ~(1 << (4 + i)); - } - - new_page_lock_byte |= orig_page_lock_byte; - new_cnt_lock_byte |= orig_cnt_lock_byte; - page_buff[0] = new_page_lock_byte; - page_buff[1] = new_cnt_lock_byte; - } else { - uint16_t orig_locks = emulator->data.data[write_page * 4] | - (emulator->data.data[write_page * 4 + 1] << 8); - uint8_t orig_block_locks = emulator->data.data[write_page * 4 + 2]; - uint16_t new_locks = page_buff[0] | (page_buff[1] << 8); - uint8_t new_block_locks = page_buff[2]; - - int block_lock_count; - switch(emulator->data.type) { - case MfUltralightTypeUL21: - block_lock_count = 5; - break; - case MfUltralightTypeNTAG213: - block_lock_count = 6; - break; - case MfUltralightTypeNTAG215: - block_lock_count = 4; - break; - case MfUltralightTypeNTAG216: - case MfUltralightTypeNTAGI2C1K: - case MfUltralightTypeNTAGI2CPlus1K: - block_lock_count = 7; - break; - case MfUltralightTypeNTAGI2C2K: - case MfUltralightTypeNTAGI2CPlus2K: - block_lock_count = 8; - break; - default: - furi_crash("Unknown MFUL"); - break; - } - - for(int i = 0; i < block_lock_count; ++i) { - if(orig_block_locks & (1 << i)) new_locks &= ~(3 << (2 * i)); - } - - new_locks |= orig_locks; - new_block_locks |= orig_block_locks; - - page_buff[0] = new_locks & 0xff; - page_buff[1] = new_locks >> 8; - page_buff[2] = new_block_locks; - if(emulator->data.type >= MfUltralightTypeUL21 && //-V1016 - emulator->data.type <= MfUltralightTypeNTAG216) - page_buff[3] = MF_UL_TEARING_FLAG_DEFAULT; - else - page_buff[3] = 0; - } - } - - memcpy(&emulator->data.data[write_page * 4], page_buff, 4); - emulator->data_changed = true; -} - -bool mf_ul_emulation_supported(MfUltralightData* data) { - return data->type != MfUltralightTypeULC; -} - -void mf_ul_reset_emulation(MfUltralightEmulator* emulator, bool is_power_cycle) { - emulator->comp_write_cmd_started = false; - emulator->sector_select_cmd_started = false; - emulator->curr_sector = 0; - emulator->ntag_i2c_plus_sector3_lockout = false; - emulator->auth_success = false; - if(is_power_cycle) { - if(emulator->config != NULL) emulator->config_cache = *emulator->config; - - if(emulator->supported_features & MfUltralightSupportSingleCounter) { - emulator->read_counter_incremented = false; - } - - if(emulator->data.type == MfUltralightTypeNTAG203) { - // Apply lockout if counter ever reached 0xFFFF - if(emulator->data.counter[1] == 0xFFFF) { - emulator->data.data[MF_UL_NTAG203_COUNTER_PAGE * 4] = 0xFF; - emulator->data.data[MF_UL_NTAG203_COUNTER_PAGE * 4 + 1] = 0xFF; - } - // Copy original counter value from data - emulator->data.counter[0] = - emulator->data.data[MF_UL_NTAG203_COUNTER_PAGE * 4] | - (emulator->data.data[MF_UL_NTAG203_COUNTER_PAGE * 4 + 1] << 8); - } - } else { - if(emulator->config != NULL) { - // ACCESS (less CFGLCK) and AUTH0 are updated when reactivated - // MIRROR_CONF is not; don't know about STRG_MOD_EN, but we're not using that anyway - emulator->config_cache.access.value = (emulator->config->access.value & 0xBF) | - (emulator->config_cache.access.value & 0x40); - emulator->config_cache.auth0 = emulator->config->auth0; - } - } - if(emulator->data.type == MfUltralightTypeNTAG203) { - // Mark counter as dirty - emulator->data.tearing[0] = 0; - } -} - -void mf_ul_prepare_emulation(MfUltralightEmulator* emulator, MfUltralightData* data) { - FURI_LOG_D(TAG, "Prepare emulation"); - emulator->data = *data; - emulator->supported_features = mf_ul_get_features(data->type); - emulator->config = mf_ultralight_get_config_pages(&emulator->data); - emulator->page_num = emulator->data.data_size / 4; - emulator->data_changed = false; - memset(&emulator->auth_attempt, 0, sizeof(MfUltralightAuth)); - mf_ul_reset_emulation(emulator, true); -} - -bool mf_ul_prepare_emulation_response( - uint8_t* buff_rx, - uint16_t buff_rx_len, - uint8_t* buff_tx, - uint16_t* buff_tx_len, - uint32_t* data_type, - void* context) { - furi_assert(context); - MfUltralightEmulator* emulator = context; - uint16_t tx_bytes = 0; - uint16_t tx_bits = 0; - bool command_parsed = false; - bool send_ack = false; - bool respond_nothing = false; - bool reset_idle = false; - -#ifdef FURI_DEBUG - FuriString* debug_buf; - debug_buf = furi_string_alloc(); - for(int i = 0; i < (buff_rx_len + 7) / 8; ++i) { - furi_string_cat_printf(debug_buf, "%02x ", buff_rx[i]); - } - furi_string_trim(debug_buf); - FURI_LOG_T(TAG, "Emu RX (%d): %s", buff_rx_len, furi_string_get_cstr(debug_buf)); - furi_string_reset(debug_buf); -#endif - - // Check composite commands - if(emulator->comp_write_cmd_started) { - if(buff_rx_len == 16 * 8) { - if(emulator->data.type == MfUltralightTypeNTAG203 && - emulator->comp_write_page_addr == MF_UL_NTAG203_COUNTER_PAGE) { - send_ack = mf_ul_emulate_ntag203_counter_write(emulator, buff_rx); - command_parsed = send_ack; - } else { - mf_ul_emulate_write( - emulator, - emulator->comp_write_page_addr, - emulator->comp_write_page_addr, - buff_rx); - send_ack = true; - command_parsed = true; - } - } - emulator->comp_write_cmd_started = false; - } else if(emulator->sector_select_cmd_started) { - if(buff_rx_len == 4 * 8) { - 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(buff_rx_len >= 8) { - uint8_t cmd = buff_rx[0]; - if(cmd == MF_UL_GET_VERSION_CMD) { - if(emulator->data.type >= MfUltralightTypeUL11) { - if(buff_rx_len == 1 * 8) { - tx_bytes = sizeof(emulator->data.version); - memcpy(buff_tx, &emulator->data.version, tx_bytes); - *data_type = FURI_HAL_NFC_TXRX_DEFAULT; - command_parsed = true; - } - } - } else if(cmd == MF_UL_READ_CMD) { - if(buff_rx_len == (1 + 1) * 8) { - int16_t start_page = buff_rx[1]; - tx_bytes = 16; - if(emulator->data.type < MfUltralightTypeNTAGI2C1K) { - if(start_page < emulator->page_num) { - do { - uint8_t copied_pages = 0; - uint8_t src_page = start_page; - uint8_t last_page_plus_one = start_page + 4; - uint8_t pwd_page = emulator->page_num - 2; - FuriString* ascii_mirror = NULL; - size_t ascii_mirror_len = 0; - const char* ascii_mirror_cptr = NULL; - uint8_t ascii_mirror_curr_page = 0; - uint8_t ascii_mirror_curr_byte = 0; - if(last_page_plus_one > emulator->page_num) - last_page_plus_one = emulator->page_num; - if(emulator->supported_features & MfUltralightSupportAuth) { - if(!mf_ul_check_auth(emulator, start_page, false)) break; - if(!emulator->auth_success && emulator->config_cache.access.prot && - emulator->config_cache.auth0 < last_page_plus_one) - last_page_plus_one = emulator->config_cache.auth0; - } - if(emulator->supported_features & MfUltralightSupportSingleCounter) - mf_ul_increment_single_counter(emulator); - if(emulator->supported_features & MfUltralightSupportAsciiMirror && - emulator->config_cache.mirror.mirror_conf != - MfUltralightMirrorNone) { - ascii_mirror_curr_byte = emulator->config->mirror.mirror_byte; - ascii_mirror_curr_page = emulator->config->mirror_page; - // Try to avoid wasting time making mirror if we won't copy it - // Conservatively check with UID+counter mirror size - if(last_page_plus_one > ascii_mirror_curr_page && - start_page + 3 >= ascii_mirror_curr_page && - start_page <= ascii_mirror_curr_page + 6) { - ascii_mirror = furi_string_alloc(); - mf_ul_make_ascii_mirror(emulator, ascii_mirror); - ascii_mirror_len = furi_string_utf8_length(ascii_mirror); - ascii_mirror_cptr = furi_string_get_cstr(ascii_mirror); - // Move pointer to where it should be to start copying - if(ascii_mirror_len > 0 && - ascii_mirror_curr_page < start_page && - ascii_mirror_curr_byte != 0) { - uint8_t diff = 4 - ascii_mirror_curr_byte; - ascii_mirror_len -= diff; - ascii_mirror_cptr += diff; - ascii_mirror_curr_byte = 0; - ++ascii_mirror_curr_page; - } - while(ascii_mirror_len > 0 && - ascii_mirror_curr_page < start_page) { - uint8_t diff = ascii_mirror_len > 4 ? 4 : ascii_mirror_len; - ascii_mirror_len -= diff; - ascii_mirror_cptr += diff; - ++ascii_mirror_curr_page; - } - } - } - - uint8_t* dest_ptr = buff_tx; - while(copied_pages < 4) { - // Copy page - memcpy(dest_ptr, &emulator->data.data[src_page * 4], 4); - - // Note: don't have to worry about roll-over with ASCII mirror because - // lowest valid page for it is 4, while roll-over will at best read - // pages 0-2 - if(ascii_mirror_len > 0 && src_page == ascii_mirror_curr_page) { - // Copy ASCII mirror - size_t copy_len = 4 - ascii_mirror_curr_byte; - if(copy_len > ascii_mirror_len) copy_len = ascii_mirror_len; - for(size_t i = 0; i < copy_len; ++i) { - if(*ascii_mirror_cptr != ' ') - dest_ptr[ascii_mirror_curr_byte] = - (uint8_t)*ascii_mirror_cptr; - ++ascii_mirror_curr_byte; - ++ascii_mirror_cptr; - } - ascii_mirror_len -= copy_len; - // Don't care if this is inaccurate after ascii_mirror_len = 0 - ascii_mirror_curr_byte = 0; - ++ascii_mirror_curr_page; - } - - if(emulator->supported_features & MfUltralightSupportAuth) { - if(src_page == pwd_page || src_page == pwd_page + 1) { - // Blank out PWD and PACK pages - memset(dest_ptr, 0, 4); - } - } - - dest_ptr += 4; - ++copied_pages; - ++src_page; - if(src_page >= last_page_plus_one) src_page = 0; - } - if(ascii_mirror != NULL) { - furi_string_free(ascii_mirror); - } - *data_type = FURI_HAL_NFC_TXRX_DEFAULT; - command_parsed = true; - } while(false); - } - } 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; - } - } - } - if(!command_parsed) tx_bytes = 0; - } - } else if(cmd == MF_UL_FAST_READ_CMD) { - if(emulator->supported_features & MfUltralightSupportFastRead) { - if(buff_rx_len == (1 + 2) * 8) { - int16_t start_page = buff_rx[1]; - uint8_t end_page = buff_rx[2]; - if(start_page <= end_page) { - tx_bytes = ((end_page + 1) - start_page) * 4; - if(emulator->data.type < MfUltralightTypeNTAGI2C1K) { - if((start_page < emulator->page_num) && - (end_page < emulator->page_num)) { - do { - if(emulator->supported_features & MfUltralightSupportAuth) { - // NAK if not authenticated and requested pages cross over AUTH0 - if(!emulator->auth_success && - emulator->config_cache.access.prot && - (start_page >= emulator->config_cache.auth0 || - end_page >= emulator->config_cache.auth0)) - break; - } - if(emulator->supported_features & - MfUltralightSupportSingleCounter) - mf_ul_increment_single_counter(emulator); - - // Copy requested pages - memcpy( - buff_tx, &emulator->data.data[start_page * 4], tx_bytes); - - if(emulator->supported_features & - MfUltralightSupportAsciiMirror && - emulator->config_cache.mirror.mirror_conf != - MfUltralightMirrorNone) { - // Copy ASCII mirror - // Less stringent check here, because expecting FAST_READ to - // only be issued once rather than repeatedly - FuriString* ascii_mirror; - ascii_mirror = furi_string_alloc(); - mf_ul_make_ascii_mirror(emulator, ascii_mirror); - size_t ascii_mirror_len = - furi_string_utf8_length(ascii_mirror); - const char* ascii_mirror_cptr = - furi_string_get_cstr(ascii_mirror); - int16_t mirror_start_offset = - (emulator->config->mirror_page - start_page) * 4 + - emulator->config->mirror.mirror_byte; - if(mirror_start_offset < 0) { - if(mirror_start_offset < -(int16_t)ascii_mirror_len) { - // Past ASCII mirror, don't copy - ascii_mirror_len = 0; - } else { - ascii_mirror_cptr += -mirror_start_offset; - ascii_mirror_len -= -mirror_start_offset; - mirror_start_offset = 0; - } - } - if(ascii_mirror_len > 0) { - int16_t mirror_end_offset = - mirror_start_offset + ascii_mirror_len; - if(mirror_end_offset > (end_page + 1) * 4) { - mirror_end_offset = (end_page + 1) * 4; - ascii_mirror_len = - mirror_end_offset - mirror_start_offset; - } - for(size_t i = 0; i < ascii_mirror_len; ++i) { - if(*ascii_mirror_cptr != ' ') - buff_tx[mirror_start_offset] = - (uint8_t)*ascii_mirror_cptr; - ++mirror_start_offset; - ++ascii_mirror_cptr; - } - } - furi_string_free(ascii_mirror); - } - - if(emulator->supported_features & MfUltralightSupportAuth) { - // Clear PWD and PACK pages - uint8_t pwd_page = emulator->page_num - 2; - int16_t pwd_page_offset = pwd_page - start_page; - // PWD page - if(pwd_page_offset >= 0 && pwd_page <= end_page) { - memset(&buff_tx[pwd_page_offset * 4], 0, 4); - // PACK page - if(pwd_page + 1 <= end_page) - memset(&buff_tx[(pwd_page_offset + 1) * 4], 0, 4); - } - } - *data_type = FURI_HAL_NFC_TXRX_DEFAULT; - command_parsed = true; - } while(false); - } - } 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 = tx_bytes; - if(copy_count > valid_pages * 4) copy_count = 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) { - if(buff_rx_len == (1 + 5) * 8) { - do { - uint8_t orig_write_page = buff_rx[1]; - int16_t write_page = orig_write_page; - uint16_t valid_pages; // unused - write_page = mf_ultralight_ntag_i2c_addr_tag_to_lin( - &emulator->data, write_page, emulator->curr_sector, &valid_pages); - if(write_page == -1) // NTAG I2C range check - break; - else if(write_page < 2 || write_page >= emulator->page_num) // Other MFUL/NTAG range check - break; - - if(emulator->supported_features & MfUltralightSupportAuth) { - if(emulator->data.type >= MfUltralightTypeNTAGI2CPlus1K) { - if(!mf_ul_ntag_i2c_plus_check_auth(emulator, orig_write_page, true)) - break; - } else { - if(!mf_ul_check_auth(emulator, orig_write_page, true)) break; - } - } - int16_t tag_addr = mf_ultralight_page_addr_to_tag_addr( - emulator->curr_sector, orig_write_page); - if(!mf_ul_check_lock(emulator, tag_addr)) break; - if(emulator->data.type == MfUltralightTypeNTAG203 && - orig_write_page == MF_UL_NTAG203_COUNTER_PAGE) { - send_ack = mf_ul_emulate_ntag203_counter_write(emulator, &buff_rx[2]); - command_parsed = send_ack; - } else { - mf_ul_emulate_write(emulator, tag_addr, write_page, &buff_rx[2]); - send_ack = true; - command_parsed = true; - } - } while(false); - } - } else if(cmd == MF_UL_FAST_WRITE) { - if(emulator->supported_features & MfUltralightSupportFastWrite) { - if(buff_rx_len == (1 + 66) * 8) { - 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->supported_features & MfUltralightSupportCompatWrite) { - if(buff_rx_len == (1 + 1) * 8) { - uint8_t write_page = buff_rx[1]; - do { - if(write_page < 2 || write_page >= emulator->page_num) break; - if(emulator->supported_features & MfUltralightSupportAuth && - !mf_ul_check_auth(emulator, write_page, true)) - break; - // Note we don't convert to tag addr here because there's only one sector - if(!mf_ul_check_lock(emulator, write_page)) break; - - emulator->comp_write_cmd_started = true; - emulator->comp_write_page_addr = write_page; - send_ack = true; - command_parsed = true; - } while(false); - } - } - } else if(cmd == MF_UL_READ_CNT) { - if(emulator->supported_features & MfUltralightSupportReadCounter) { - if(buff_rx_len == (1 + 1) * 8) { - do { - uint8_t cnt_num = buff_rx[1]; - - // NTAG21x checks - if(emulator->supported_features & MfUltralightSupportSingleCounter) { - if(cnt_num != 2) break; // Only counter 2 is available - if(!emulator->config_cache.access.nfc_cnt_en) - break; // NAK if counter not enabled - if(emulator->config_cache.access.nfc_cnt_pwd_prot && - !emulator->auth_success) - break; - } - - if(cnt_num < 3) { - buff_tx[0] = emulator->data.counter[cnt_num] & 0xFF; - buff_tx[1] = (emulator->data.counter[cnt_num] >> 8) & 0xFF; - buff_tx[2] = (emulator->data.counter[cnt_num] >> 16) & 0xFF; - tx_bytes = 3; - *data_type = FURI_HAL_NFC_TXRX_DEFAULT; - command_parsed = true; - } - } while(false); - } - } - } else if(cmd == MF_UL_INC_CNT) { - if(emulator->supported_features & MfUltralightSupportIncrCounter) { - if(buff_rx_len == (1 + 5) * 8) { - uint8_t cnt_num = buff_rx[1]; - uint32_t inc = (buff_rx[2] | (buff_rx[3] << 8) | (buff_rx[4] << 16)); - // TODO: can you increment by 0 when counter is at 0xffffff? - if((cnt_num < 3) && (emulator->data.counter[cnt_num] != 0x00FFFFFF) && - (emulator->data.counter[cnt_num] + inc <= 0x00FFFFFF)) { - emulator->data.counter[cnt_num] += inc; - // We're RAM-backed, so tearing never happens - emulator->data.tearing[cnt_num] = MF_UL_TEARING_FLAG_DEFAULT; - emulator->data_changed = true; - send_ack = true; - command_parsed = true; - } - } - } - } else if(cmd == MF_UL_PWD_AUTH) { - if(emulator->supported_features & MfUltralightSupportAuth) { - if(buff_rx_len == (1 + 4) * 8) { - // Record password sent by PCD - memcpy( - emulator->auth_attempt.pwd.raw, - &buff_rx[1], - sizeof(emulator->auth_attempt.pwd.raw)); - emulator->auth_attempted = true; - if(emulator->auth_received_callback) { - emulator->auth_received_callback( - emulator->auth_attempt, emulator->context); - } - - uint16_t scaled_authlim = mf_ultralight_calc_auth_count(&emulator->data); - if(scaled_authlim != 0 && emulator->data.curr_authlim >= scaled_authlim) { - if(emulator->data.curr_authlim != UINT16_MAX) { - // Handle case where AUTHLIM has been lowered or changed from 0 - emulator->data.curr_authlim = UINT16_MAX; - emulator->data_changed = true; - } - // AUTHLIM reached, always fail - buff_tx[0] = MF_UL_NAK_AUTHLIM_REACHED; - tx_bits = 4; - *data_type = FURI_HAL_NFC_TX_RAW_RX_DEFAULT; - mf_ul_reset_emulation(emulator, false); - command_parsed = true; - } else { - if(memcmp(&buff_rx[1], emulator->config->auth_data.pwd.raw, 4) == 0) { - // Correct password - buff_tx[0] = emulator->config->auth_data.pack.raw[0]; - buff_tx[1] = emulator->config->auth_data.pack.raw[1]; - tx_bytes = 2; - *data_type = FURI_HAL_NFC_TXRX_DEFAULT; - emulator->auth_success = true; - command_parsed = true; - if(emulator->data.curr_authlim != 0) { - // Reset current AUTHLIM - emulator->data.curr_authlim = 0; - emulator->data_changed = true; - } - } else if(!emulator->config->auth_data.pwd.value) { - // Unknown password, pretend to be an Amiibo - buff_tx[0] = 0x80; - buff_tx[1] = 0x80; - tx_bytes = 2; - *data_type = FURI_HAL_NFC_TXRX_DEFAULT; - emulator->auth_success = true; - command_parsed = true; - } else { - // Wrong password, increase negative verification count - if(emulator->data.curr_authlim < UINT16_MAX) { - ++emulator->data.curr_authlim; - emulator->data_changed = true; - } - if(scaled_authlim != 0 && - emulator->data.curr_authlim >= scaled_authlim) { - emulator->data.curr_authlim = UINT16_MAX; - buff_tx[0] = MF_UL_NAK_AUTHLIM_REACHED; - tx_bits = 4; - *data_type = FURI_HAL_NFC_TX_RAW_RX_DEFAULT; - mf_ul_reset_emulation(emulator, false); - command_parsed = true; - } else { - // Should delay here to slow brute forcing - } - } - } - } - } - } else if(cmd == MF_UL_READ_SIG) { - if(emulator->supported_features & MfUltralightSupportSignature) { - // Check 2nd byte = 0x00 - RFU - if(buff_rx_len == (1 + 1) * 8 && 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) { - if(emulator->supported_features & MfUltralightSupportTearingFlags) { - if(buff_rx_len == (1 + 1) * 8) { - 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) { - reset_idle = true; - FURI_LOG_D(TAG, "Received HLTA"); - } else if(cmd == MF_UL_SECTOR_SELECT) { - if(emulator->supported_features & MfUltralightSupportSectorSelect) { - if(buff_rx_len == (1 + 1) * 8 && buff_rx[1] == 0xFF) { - // Send ACK - emulator->sector_select_cmd_started = true; - send_ack = true; - command_parsed = true; - } - } - } else if(cmd == MF_UL_READ_VCSL) { - if(emulator->supported_features & MfUltralightSupportVcsl) { - if(buff_rx_len == (1 + 20) * 8) { - buff_tx[0] = emulator->config_cache.vctid; - tx_bytes = 1; - *data_type = FURI_HAL_NFC_TXRX_DEFAULT; - command_parsed = true; - } - } - } else { - // NTAG203 appears to NAK instead of just falling off on invalid commands - if(emulator->data.type != MfUltralightTypeNTAG203) reset_idle = true; - FURI_LOG_D(TAG, "Received invalid command"); - } - } else { - reset_idle = true; - FURI_LOG_D(TAG, "Received invalid buffer less than 8 bits in length"); - } - - if(reset_idle) { - mf_ul_reset_emulation(emulator, false); - tx_bits = 0; - command_parsed = true; - } - - if(!command_parsed) { - // Send NACK - buff_tx[0] = MF_UL_NAK_INVALID_ARGUMENT; - tx_bits = 4; - *data_type = FURI_HAL_NFC_TX_RAW_RX_DEFAULT; - // Every NAK should cause reset to IDLE - mf_ul_reset_emulation(emulator, false); - } else if(send_ack) { - buff_tx[0] = MF_UL_ACK; - tx_bits = 4; - *data_type = FURI_HAL_NFC_TX_RAW_RX_DEFAULT; - } - - 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; - } - -#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) { - furi_string_cat_printf(debug_buf, "%02x ", buff_tx[i]); - } - furi_string_trim(debug_buf); - FURI_LOG_T(TAG, "Emu TX (%d): %s", *buff_tx_len, furi_string_get_cstr(debug_buf)); - furi_string_free(debug_buf); - } else { - FURI_LOG_T(TAG, "Emu TX: HALT"); - } -#endif - - return tx_bits > 0; -} - -bool mf_ul_is_full_capture(MfUltralightData* data) { - if(data->data_read != data->data_size) return false; - - // Having read all the pages doesn't mean that we've got everything. - // By default PWD is 0xFFFFFFFF, but if read back it is always 0x00000000, - // so a default read on an auth-supported NTAG is never complete. - if(!(mf_ul_get_features(data->type) & MfUltralightSupportAuth)) return true; - MfUltralightConfigPages* config = mf_ultralight_get_config_pages(data); - return config->auth_data.pwd.value != 0 || config->auth_data.pack.value != 0; -} diff --git a/lib/nfc/protocols/mifare_ultralight.h b/lib/nfc/protocols/mifare_ultralight.h deleted file mode 100644 index 9cb7ca5356c..00000000000 --- a/lib/nfc/protocols/mifare_ultralight.h +++ /dev/null @@ -1,269 +0,0 @@ -#pragma once - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -// 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) - -#define MF_UL_HALT_START (0x50) -#define MF_UL_GET_VERSION_CMD (0x60) -#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) -#define MF_UL_AUTHENTICATE_1 (0x1A) -#define MF_UL_PWD_AUTH (0x1B) -#define MF_UL_READ_SIG (0x3C) -#define MF_UL_CHECK_TEARING (0x3E) -#define MF_UL_READ_VCSL (0x4B) -#define MF_UL_SECTOR_SELECT (0xC2) - -#define MF_UL_ACK (0xa) -#define MF_UL_NAK_INVALID_ARGUMENT (0x0) -#define MF_UL_NAK_AUTHLIM_REACHED (0x4) - -#define MF_UL_NTAG203_COUNTER_PAGE (41) - -#define MF_UL_DEFAULT_PWD (0xFFFFFFFF) - -typedef enum { - MfUltralightAuthMethodManual, - MfUltralightAuthMethodAmeebo, - MfUltralightAuthMethodXiaomi, - MfUltralightAuthMethodAuto, -} MfUltralightAuthMethod; - -// Important: order matters; some features are based on positioning in this enum -typedef enum { - MfUltralightTypeUnknown, - MfUltralightTypeNTAG203, - MfUltralightTypeULC, - // Below have config pages and GET_VERSION support - MfUltralightTypeUL11, - MfUltralightTypeUL21, - MfUltralightTypeNTAG213, - MfUltralightTypeNTAG215, - MfUltralightTypeNTAG216, - // Below also have sector select - // NTAG I2C's *does not* have regular config pages, so it's a bit of an odd duck - MfUltralightTypeNTAGI2C1K, - MfUltralightTypeNTAGI2C2K, - // NTAG I2C Plus has stucture expected from NTAG21x - MfUltralightTypeNTAGI2CPlus1K, - MfUltralightTypeNTAGI2CPlus2K, - - // Keep last for number of types calculation - MfUltralightTypeNum, -} MfUltralightType; - -typedef enum { - MfUltralightSupportNone = 0, - MfUltralightSupportFastRead = 1 << 0, - MfUltralightSupportTearingFlags = 1 << 1, - MfUltralightSupportReadCounter = 1 << 2, - MfUltralightSupportIncrCounter = 1 << 3, - MfUltralightSupportSignature = 1 << 4, - MfUltralightSupportFastWrite = 1 << 5, - MfUltralightSupportCompatWrite = 1 << 6, - MfUltralightSupportAuth = 1 << 7, - MfUltralightSupportVcsl = 1 << 8, - MfUltralightSupportSectorSelect = 1 << 9, - // NTAG21x only has counter 2 - MfUltralightSupportSingleCounter = 1 << 10, - // ASCII mirror is not a command, but handy to have as a flag - MfUltralightSupportAsciiMirror = 1 << 11, - // NTAG203 counter that's in memory rather than through a command - MfUltralightSupportCounterInMemory = 1 << 12, - MfUltralightSupport3DesAuth = 1 << 13, -} MfUltralightFeatures; - -typedef enum { - MfUltralightMirrorNone, - MfUltralightMirrorUid, - MfUltralightMirrorCounter, - MfUltralightMirrorUidCounter, -} MfUltralightMirrorConf; - -typedef struct { - uint8_t header; - uint8_t vendor_id; - uint8_t prod_type; - uint8_t prod_subtype; - uint8_t prod_ver_major; - uint8_t prod_ver_minor; - uint8_t storage_size; - uint8_t protocol_type; -} MfUltralightVersion; - -typedef struct { - uint8_t sn0[3]; - uint8_t btBCC0; - uint8_t sn1[4]; - uint8_t btBCC1; - uint8_t internal; - uint8_t lock[2]; - uint8_t otp[4]; -} MfUltralightManufacturerBlock; - -typedef struct { - MfUltralightType type; - MfUltralightVersion version; - uint8_t signature[32]; - uint32_t counter[3]; - uint8_t tearing[3]; - MfUltralightAuthMethod auth_method; - uint8_t auth_key[4]; - bool auth_success; - uint16_t curr_authlim; - uint16_t data_size; - uint8_t data[MF_UL_MAX_DUMP_SIZE]; - uint16_t data_read; -} MfUltralightData; - -typedef struct __attribute__((packed)) { - union { - uint8_t raw[4]; - uint32_t value; - } pwd; - union { - uint8_t raw[2]; - uint16_t value; - } pack; -} MfUltralightAuth; - -// Common configuration pages for MFUL EV1, NTAG21x, and NTAG I2C Plus -typedef struct __attribute__((packed)) { - union { - uint8_t value; - struct { - uint8_t rfui1 : 2; - bool strg_mod_en : 1; - bool rfui2 : 1; - uint8_t mirror_byte : 2; - MfUltralightMirrorConf mirror_conf : 2; - }; - } mirror; - uint8_t rfui1; - uint8_t mirror_page; - uint8_t auth0; - union { - uint8_t value; - struct { - uint8_t authlim : 3; - bool nfc_cnt_pwd_prot : 1; - bool nfc_cnt_en : 1; - bool nfc_dis_sec1 : 1; // NTAG I2C Plus only - bool cfglck : 1; - bool prot : 1; - }; - } access; - uint8_t vctid; - uint8_t rfui2[2]; - MfUltralightAuth auth_data; - uint8_t rfui3[2]; -} MfUltralightConfigPages; - -typedef struct { - uint16_t pages_to_read; - int16_t pages_read; - MfUltralightFeatures supported_features; -} MfUltralightReader; - -// TODO rework with reader analyzer -typedef void (*MfUltralightAuthReceivedCallback)(MfUltralightAuth auth, void* context); - -typedef struct { - MfUltralightData data; - MfUltralightConfigPages* config; - // Most config values don't apply until power cycle, so cache config pages - // for correct behavior - MfUltralightConfigPages config_cache; - MfUltralightFeatures supported_features; - uint16_t page_num; - bool data_changed; - bool comp_write_cmd_started; - uint8_t comp_write_page_addr; - bool auth_success; - uint8_t curr_sector; - bool sector_select_cmd_started; - bool ntag_i2c_plus_sector3_lockout; - bool read_counter_incremented; - bool auth_attempted; - MfUltralightAuth auth_attempt; - - // TODO rework with reader analyzer - MfUltralightAuthReceivedCallback auth_received_callback; - void* context; -} MfUltralightEmulator; - -void mf_ul_reset(MfUltralightData* data); - -bool mf_ul_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK); - -bool mf_ultralight_read_version( - FuriHalNfcTxRxContext* tx_rx, - MfUltralightReader* reader, - MfUltralightData* data); - -bool mf_ultralight_read_pages_direct( - FuriHalNfcTxRxContext* tx_rx, - uint8_t start_index, - uint8_t* data); - -bool mf_ultralight_read_pages( - FuriHalNfcTxRxContext* tx_rx, - MfUltralightReader* reader, - MfUltralightData* data); - -bool mf_ultralight_fast_read_pages( - FuriHalNfcTxRxContext* tx_rx, - MfUltralightReader* reader, - MfUltralightData* data); - -bool mf_ultralight_read_signature(FuriHalNfcTxRxContext* tx_rx, MfUltralightData* data); - -bool mf_ultralight_read_counters(FuriHalNfcTxRxContext* tx_rx, MfUltralightData* data); - -bool mf_ultralight_read_tearing_flags(FuriHalNfcTxRxContext* tx_rx, MfUltralightData* data); - -bool mf_ultralight_authenticate(FuriHalNfcTxRxContext* tx_rx, uint32_t key, uint16_t* pack); - -MfUltralightConfigPages* mf_ultralight_get_config_pages(MfUltralightData* data); - -bool mf_ul_read_card( - FuriHalNfcTxRxContext* tx_rx, - MfUltralightReader* reader, - MfUltralightData* data); - -bool mf_ul_emulation_supported(MfUltralightData* data); - -void mf_ul_reset_emulation(MfUltralightEmulator* emulator, bool is_power_cycle); - -void mf_ul_prepare_emulation(MfUltralightEmulator* emulator, MfUltralightData* data); - -bool mf_ul_prepare_emulation_response( - uint8_t* buff_rx, - uint16_t buff_rx_len, - uint8_t* buff_tx, - uint16_t* buff_tx_len, - uint32_t* data_type, - void* context); - -uint32_t mf_ul_pwdgen_amiibo(FuriHalNfcDevData* data); - -uint32_t mf_ul_pwdgen_xiaomi(FuriHalNfcDevData* data); - -bool mf_ul_is_full_capture(MfUltralightData* data); - -#ifdef __cplusplus -} -#endif \ No newline at end of file diff --git a/lib/nfc/protocols/nfc_device_base.h b/lib/nfc/protocols/nfc_device_base.h new file mode 100644 index 00000000000..4f3480d455a --- /dev/null +++ b/lib/nfc/protocols/nfc_device_base.h @@ -0,0 +1,26 @@ +/** + * @file nfc_device_base.h + * @brief Common top-level types for the NFC protocol stack. + */ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Verbosity level of the displayed NFC device name. + */ +typedef enum { + NfcDeviceNameTypeFull, /**< Display full(verbose) name. */ + NfcDeviceNameTypeShort, /**< Display shortened name. */ +} NfcDeviceNameType; + +/** + * @brief Generic opaque type for protocol-specific NFC device data. + */ +typedef void NfcDeviceData; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/nfc_device_base_i.h b/lib/nfc/protocols/nfc_device_base_i.h new file mode 100644 index 00000000000..946ae76dcc0 --- /dev/null +++ b/lib/nfc/protocols/nfc_device_base_i.h @@ -0,0 +1,161 @@ +/** + * @file nfc_device_base_i.h + * @brief Abstract interface definitions for the NFC device system. + * + * This file is an implementation detail. It must not be included in + * any public API-related headers. + */ +#pragma once + +#include "nfc_device_base.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Allocate the protocol-specific NFC device data instance. + * + * @returns pointer to the allocated instance. + */ +typedef NfcDeviceData* (*NfcDeviceAlloc)(); + +/** + * @brief Delete the protocol-specific NFC device data instance. + * + * @param[in,out] data pointer to the instance to be deleted. + */ +typedef void (*NfcDeviceFree)(NfcDeviceData* data); + +/** + * @brief Reset the NFC device data instance. + * + * The behaviour is protocol-specific. Usually, required fields are zeroed or + * set to their initial values. + * + * @param[in,out] data pointer to the instance to be reset. + */ +typedef void (*NfcDeviceReset)(NfcDeviceData* data); + +/** + * @brief Copy source instance's data into the destination so that they become equal. + * + * @param[in,out] data pointer to the destination instance. + * @param[in] other pointer to the source instance. + */ +typedef void (*NfcDeviceCopy)(NfcDeviceData* data, const NfcDeviceData* other); + +/** + * @brief Deprecated. Do not use in new protocols. + * @deprecated do not use in new protocols. + * + * @param[in,out] data pointer to the instance to be tested. + * @param[in] device_type pointer to a FuriString containing a device type identifier. + * @returns true if data was verified, false otherwise. + */ +typedef bool (*NfcDeviceVerify)(NfcDeviceData* data, const FuriString* device_type); + +/** + * @brief Load NFC device data from a FlipperFormat file. + * + * The FlipperFormat file structure must be initialised and open by the calling code. + * + * @param[in,out] data pointer to the instance to be loaded into. + * @param[in] ff pointer to the FlipperFormat file instance. + * @param[in] version file format version to use when loading. + * @returns true if loaded successfully, false otherwise. + */ +typedef bool (*NfcDeviceLoad)(NfcDeviceData* data, FlipperFormat* ff, uint32_t version); + +/** + * @brief Save NFC device data to a FlipperFormat file. + * + * The FlipperFormat file structure must be initialised and open by the calling code. + * + * @param[in] data pointer to the instance to be saved. + * @param[in] ff pointer to the FlipperFormat file instance. + * @returns true if saved successfully, false otherwise. + */ +typedef bool (*NfcDeviceSave)(const NfcDeviceData* data, FlipperFormat* ff); + +/** + * @brief Compare two NFC device data instances. + * + * @param[in] data pointer to the first instance to be compared. + * @param[in] other pointer to the second instance to be compared. + * @returns true if instances are equal, false otherwise. + */ +typedef bool (*NfcDeviceEqual)(const NfcDeviceData* data, const NfcDeviceData* other); + +/** + * @brief Get a protocol-specific stateful NFC device name. + * + * The return value may change depending on the instance's internal state and the name_type parameter. + * + * @param[in] data pointer to the instance to be queried. + * @param[in] name_type type of the name to be displayed. + * @returns pointer to a statically allocated character string containing the appropriate name. + */ +typedef const char* (*NfcDeviceGetName)(const NfcDeviceData* data, NfcDeviceNameType name_type); + +/** + * @brief Get the NFC device's unique identifier (UID). + * + * The UID length is protocol-dependent. Additionally, a particular protocol might support + * several UID lengths. + * + * @param[in] data pointer to the instance to be queried. + * @param[out] uid_len pointer to the variable to contain the UID length. + * @returns pointer to the byte array containing the device's UID. + */ +typedef const uint8_t* (*NfcDeviceGetUid)(const NfcDeviceData* data, size_t* uid_len); + +/** + * @brief Set the NFC device's unique identifier (UID). + * + * The UID length must be supported by the protocol in question. + * + * @param[in,out] data pointer to the instance to be modified. + * @param[in] uid pointer to the byte array containing the new UID. + * @param[in] uid_len length of the UID. + * @return true if the UID was valid and set, false otherwise. + */ +typedef bool (*NfcDeviceSetUid)(NfcDeviceData* data, const uint8_t* uid, size_t uid_len); + +/** + * @brief Get the NFC device data associated with the parent protocol. + * + * The protocol the instance's data is associated with must have a parent. + * + * @param[in] data pointer to the instance to be queried. + * @returns pointer to the data instance associated with the parent protocol. + */ +typedef NfcDeviceData* (*NfcDeviceGetBaseData)(const NfcDeviceData* data); + +/** + * @brief Generic NFC device interface. + * + * Each protocol must fill this structure with its own function implementations. + */ +typedef struct { + const char* + protocol_name; /**< Pointer to a statically-allocated string with the protocol name. */ + NfcDeviceAlloc alloc; /**< Pointer to the alloc() function. */ + NfcDeviceFree free; /**< Pointer to the free() function. */ + NfcDeviceReset reset; /**< Pointer to the reset() function. */ + NfcDeviceCopy copy; /**< Pointer to the copy() function. */ + NfcDeviceVerify verify; /**< Deprecated. Set to NULL in new protocols. */ + NfcDeviceLoad load; /**< Pointer to the load() function. */ + NfcDeviceSave save; /**< Pointer to the save() function. */ + NfcDeviceEqual is_equal; /**< Pointer to the is_equal() function. */ + NfcDeviceGetName get_name; /**< Pointer to the get_name() function. */ + NfcDeviceGetUid get_uid; /**< Pointer to the get_uid() function. */ + NfcDeviceSetUid set_uid; /**< Pointer to the set_uid() function. */ + NfcDeviceGetBaseData get_base_data; /**< Pointer to the get_base_data() function. */ +} NfcDeviceBase; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/nfc_device_defs.c b/lib/nfc/protocols/nfc_device_defs.c new file mode 100644 index 00000000000..870bcafd9e0 --- /dev/null +++ b/lib/nfc/protocols/nfc_device_defs.c @@ -0,0 +1,46 @@ +/** + * @file nfc_device_defs.c + * @brief Main NFC device implementation definitions. + * + * All NFC device implementations must be registered here in order to be used + * by the NfcDevice library. + * + * @see nfc_device.h + * + * This file is to be modified upon adding a new protocol (see below). + */ +#include "nfc_device_base_i.h" +#include "nfc_protocol.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * @brief List of registered NFC device implementations. + * + * When implementing a new protocol, add its implementation + * here under its own index defined in nfc_protocol.h. + */ +const NfcDeviceBase* nfc_devices[NfcProtocolNum] = { + [NfcProtocolIso14443_3a] = &nfc_device_iso14443_3a, + [NfcProtocolIso14443_3b] = &nfc_device_iso14443_3b, + [NfcProtocolIso14443_4a] = &nfc_device_iso14443_4a, + [NfcProtocolIso14443_4b] = &nfc_device_iso14443_4b, + [NfcProtocolIso15693_3] = &nfc_device_iso15693_3, + [NfcProtocolFelica] = &nfc_device_felica, + [NfcProtocolMfUltralight] = &nfc_device_mf_ultralight, + [NfcProtocolMfClassic] = &nfc_device_mf_classic, + [NfcProtocolMfDesfire] = &nfc_device_mf_desfire, + [NfcProtocolSlix] = &nfc_device_slix, + [NfcProtocolSt25tb] = &nfc_device_st25tb, + /* Add new protocols here */ +}; diff --git a/lib/nfc/protocols/nfc_device_defs.h b/lib/nfc/protocols/nfc_device_defs.h new file mode 100644 index 00000000000..f5ba2563f46 --- /dev/null +++ b/lib/nfc/protocols/nfc_device_defs.h @@ -0,0 +1,13 @@ +#pragma once + +#include "nfc_device_base_i.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern const NfcDeviceBase* nfc_devices[]; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/nfc_generic_event.h b/lib/nfc/protocols/nfc_generic_event.h new file mode 100644 index 00000000000..ec3bd68dc57 --- /dev/null +++ b/lib/nfc/protocols/nfc_generic_event.h @@ -0,0 +1,79 @@ +/** + * @file nfc_generic_event.h + * @brief Generic Nfc stack event definitions. + * + * Events are the main way of passing information about, well, various events + * that occur across the Nfc protocol stack. + * + * In order to subscribe to events from a certain instance, the user code must call + * its corresponding start() function while providing the appropriate callback. + * During this call, an additional context pointer can be provided, which will be passed + * to the context parameter at the time of the callback execution. + * + * For additional information on how events are passed around and processed, see protocol-specific + * poller and listener implementations found in the respectively named subfolders. + * + */ +#pragma once + +#include "nfc_protocol.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Generic Nfc instance type. + * + * Must be cast to a concrete type before use. + * Depending on the context, a pointer of this type + * may point to an object of the following types: + * - Nfc type, + * - Concrete poller type, + * - Concrete listener type. + */ +typedef void NfcGenericInstance; + +/** + * @brief Generic Nfc event data type. + * + * Must be cast to a concrete type before use. + * Usually, it will be the protocol-specific event type. + */ +typedef void NfcGenericEventData; + +/** + * @brief Generic Nfc event type. + * + * A generic Nfc event contains a protocol identifier, can be used to determine + * the remaing fields' type. + * + * If the value of the protocol field is NfcProtocolInvalid, then it means that + * the event was emitted from an Nfc instance, otherwise it originated from + * a concrete poller or listener instance. + * + * The event_data field is protocol-specific and should be cast to the appropriate type before use. + */ +typedef struct { + NfcProtocol protocol; /**< Protocol identifier of the instance that produced the event. */ + NfcGenericInstance* + instance; /**< Pointer to the protocol-specific instance that produced the event. */ + NfcGenericEventData* event_data; /**< Pointer to the protocol-specific event. */ +} NfcGenericEvent; + +/** + * @brief Generic Nfc event callback type. + * + * A function of this type must be passed as the callback parameter upon start + * of a poller, listener or Nfc instance. + * + * @param [in] event Nfc generic event, passed by value, complete with protocol type and data. + * @param [in,out] context pointer to the user-specific context (set when starting a poller/listener instance). + * @returns the command which the event producer must execute. + */ +typedef NfcCommand (*NfcGenericCallback)(NfcGenericEvent event, void* context); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/nfc_listener_base.h b/lib/nfc/protocols/nfc_listener_base.h new file mode 100644 index 00000000000..6d0cdcd094c --- /dev/null +++ b/lib/nfc/protocols/nfc_listener_base.h @@ -0,0 +1,98 @@ +/** + * @file nfc_listener_base.h + * @brief Abstract interface definitions for the NFC listener system. + * + * This file is an implementation detail. It must not be included in + * any public API-related headers. + * + * @see nfc_listener.h + * + */ +#pragma once + +#include "nfc_generic_event.h" +#include "nfc_device_base.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Allocate a protocol-specific listener instance. + * + * For base listeners pass a pointer to an instance of type Nfc + * as the base_listener parameter, otherwise it must be a pointer to another listener instance + * (compare iso14443_3a/iso14443_3a_listener.c and iso14443_4a/iso14443_4a_listener.c). + * + * @see nfc_protocol.c + * + * The NFC device data passed as the data parameter is copied to the instance and may + * change during the emulation in response to reader commands. + * + * To retrieve the modified data, NfcListenerGetData gets called by the NfcListener + * implementation when the user code calls nfc_listener_get_data(). + * + * @param[in] base_listener pointer to the parent listener instance. + * @param[in] data pointer to the protocol-specific data to use during emulation. + * @returns pointer to the allocated listener instance. + */ +typedef NfcGenericInstance* ( + *NfcListenerAlloc)(NfcGenericInstance* base_listener, NfcDeviceData* data); + +/** + * @brief Delete a protocol-specific listener instance. + * + * @param[in,out] instance pointer to the instance to be deleted. + */ +typedef void (*NfcListenerFree)(NfcGenericInstance* instance); + +/** + * @brief Set the callback function to handle events emitted by the listener instance. + * + * @see nfc_generic_event.h + * + * @param[in,out] listener + * @param[in] callback pointer to the user-defined callback function which will receive events. + * @param[in] context pointer to the user-specific context (will be passed to the callback). + */ +typedef void (*NfcListenerSetCallback)( + NfcGenericInstance* listener, + NfcGenericCallback callback, + void* context); + +/** + * @brief Emulate a supported NFC card with given device data. + * + * @param[in] event protocol-specific event passed by the parent listener instance. + * @param[in,out] context pointer to the protocol-specific listener instance. + * @returns command to be executed by the parent listener instance. + */ +typedef NfcCommand (*NfcListenerRun)(NfcGenericEvent event, void* context); + +/** + * @brief Get the protocol-specific data that was that was provided for emulation. + * + * @param[in] instance pointer to the protocol-specific listener instance. + * @returns pointer to the NFC device data. + */ +typedef const NfcDeviceData* (*NfcListenerGetData)(const NfcGenericInstance* instance); + +/** + * @brief Generic NFC listener interface. + * + * Each protocol must fill this structure with its own function implementations. + * See above for the function signatures and descriptions. + * + * Additionally, see ${PROTOCOL_NAME}/${PROTOCOL_NAME}_listener.c for usage examples. + */ +typedef struct { + NfcListenerAlloc alloc; /**< Pointer to the alloc() function. */ + NfcListenerFree free; /**< Pointer to the free() function. */ + NfcListenerSetCallback set_callback; /**< Pointer to the set_callback() function. */ + NfcListenerRun run; /**< Pointer to the run() function. */ + NfcListenerGetData get_data; /**< Pointer to the get_data() function. */ +} NfcListenerBase; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/nfc_listener_defs.c b/lib/nfc/protocols/nfc_listener_defs.c new file mode 100644 index 00000000000..31f9bc16c63 --- /dev/null +++ b/lib/nfc/protocols/nfc_listener_defs.c @@ -0,0 +1,21 @@ +#include "nfc_listener_defs.h" + +#include +#include +#include +#include +#include +#include + +const NfcListenerBase* nfc_listeners_api[NfcProtocolNum] = { + [NfcProtocolIso14443_3a] = &nfc_listener_iso14443_3a, + [NfcProtocolIso14443_3b] = NULL, + [NfcProtocolIso14443_4a] = &nfc_listener_iso14443_4a, + [NfcProtocolIso14443_4b] = NULL, + [NfcProtocolIso15693_3] = &nfc_listener_iso15693_3, + [NfcProtocolMfUltralight] = &mf_ultralight_listener, + [NfcProtocolMfClassic] = &mf_classic_listener, + [NfcProtocolMfDesfire] = NULL, + [NfcProtocolSlix] = &nfc_listener_slix, + [NfcProtocolSt25tb] = NULL, +}; diff --git a/lib/nfc/protocols/nfc_listener_defs.h b/lib/nfc/protocols/nfc_listener_defs.h new file mode 100644 index 00000000000..4d88cc09830 --- /dev/null +++ b/lib/nfc/protocols/nfc_listener_defs.h @@ -0,0 +1,14 @@ +#pragma once + +#include "nfc_listener_base.h" +#include "nfc_protocol.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern const NfcListenerBase* nfc_listeners_api[NfcProtocolNum]; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/nfc_poller_base.h b/lib/nfc/protocols/nfc_poller_base.h new file mode 100644 index 00000000000..9c4a2b65208 --- /dev/null +++ b/lib/nfc/protocols/nfc_poller_base.h @@ -0,0 +1,132 @@ +/** + * @file nfc_poller_base.h + * @brief Abstract interface definitions for the NFC poller system. + * + * This file is an implementation detail. It must not be included in + * any public API-related headers. + * + * @see nfc_poller.h + * + */ +#pragma once + +#include "nfc_generic_event.h" +#include "nfc_device_base.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Allocate a protocol-specific poller instance. + * + * For base pollers pass a pointer to an instance of type Nfc + * as the base_poller parameter, otherwise it must be a pointer to another poller instance + * (compare iso14443_3a/iso14443_3a_poller.c and iso14443_4a/iso14443_4a_poller.c). + * + * @see nfc_protocol.c + * + * @param[in] base_poller pointer to the parent poller instance. + * @returns pointer to the allocated poller instance. + */ +typedef NfcGenericInstance* (*NfcPollerAlloc)(NfcGenericInstance* base_poller); + +/** + * @brief Delete a protocol-specific poller instance. + * + * @param[in,out] instance pointer to the instance to be deleted. + */ +typedef void (*NfcPollerFree)(NfcGenericInstance* instance); + +/** + * @brief Set the callback function to handle events emitted by the poller instance. + * + * @see nfc_generic_event.h + * + * @param[in,out] poller pointer to the protocol-specific poller instance. + * @param[in] callback pointer to the user-defined callback function which will receive events. + * @param[in] context pointer to the user-specific context (will be passed to the callback). + */ +typedef void ( + *NfcPollerSetCallback)(NfcGenericInstance* poller, NfcGenericCallback callback, void* context); + +/** + * @brief Activate and read a supported NFC card. + * + * Ths function is passed to the parent poller's ${POLLER_NAME}_set_callback function as + * the callback parameter. This is done automatically by the NfcPoller implementation based + * on the protocol hierarchy defined in nfc_protocol.c, so there is no need to call it explicitly. + * + * Thus, it will be called each time the parent poller emits an event. Usually it happens + * only after the parent poller has successfully completed its job. + * + * Example for an application reading a card with a compound (non-base) protocol (simplified): + * + * ``` + * start() <-- set_callback() <-- set_callback() <-- nfc_poller_start() + * | | | + * Nfc | Base Poller | Child Poller | Application + * | | | + * worker() --> run() --> run() ---> handle_event() + * ``` + * + * The base poller receives events directly from an Nfc instance, from which they are + * propagated as needed to however many other pollers there are in the current hierarchy. + * + * This function can be thought of as the poller's "main loop" function. Depending + * on the particular poller implementation, it may perform actions such as reading + * and writing to an NFC card, state changes and control of the parent poller. + * + * @see nfc_generic_event.h + * + * @param[in] event protocol-specific event passed by the parent poller instance. + * @param[in,out] context pointer to the protocol-specific poller instance. + * @returns command to be executed by the parent poller instance. + */ +typedef NfcCommand (*NfcPollerRun)(NfcGenericEvent event, void* context); + +/** + * @brief Determine whether there is a supported card in the vicinity. + * + * The behaviour is mostly the same as of NfcPollerRun, with the difference in the + * procedure and return value. + * The procedure implemented in this function must do whatever it needs to unambigiously + * determine whether a supported and valid NFC card is in the vicinity. + * + * Like the previously described NfcPollerRun, it is called automatically by the NfcPoller + * implementation, so there is no need to call it explicitly. + * + * @param[in] event protocol-specific event passed by the parent poller instance. + * @param[in,out] context pointer to the protocol-specific poller instance. + * @returns true if a supported card was detected, false otherwise. + */ +typedef bool (*NfcPollerDetect)(NfcGenericEvent event, void* context); + +/** + * @brief Get the data that was that was gathered during the reading process. + * + * @param[in] instance pointer to the protocol-specific poller instance. + * @returns pointer to the NFC device data. + */ +typedef const NfcDeviceData* (*NfcPollerGetData)(const NfcGenericInstance* instance); + +/** + * @brief Generic NFC poller interface. + * + * Each protocol must fill this structure with its own function implementations. + * See above for the function signatures and descriptions. + * + * Additionally, see ${PROTOCOL_NAME}/${PROTOCOL_NAME}_poller.c for usage examples. + */ +typedef struct { + NfcPollerAlloc alloc; /**< Pointer to the alloc() function. */ + NfcPollerFree free; /**< Pointer to the free() function. */ + NfcPollerSetCallback set_callback; /**< Pointer to the set_callback() function. */ + NfcPollerRun run; /**< Pointer to the run() function. */ + NfcPollerDetect detect; /**< Pointer to the detect() function. */ + NfcPollerGetData get_data; /**< Pointer to the get_data() function. */ +} NfcPollerBase; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/nfc_poller_defs.c b/lib/nfc/protocols/nfc_poller_defs.c new file mode 100644 index 00000000000..7553c74de1b --- /dev/null +++ b/lib/nfc/protocols/nfc_poller_defs.c @@ -0,0 +1,28 @@ +#include "nfc_poller_defs.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const NfcPollerBase* nfc_pollers_api[NfcProtocolNum] = { + [NfcProtocolIso14443_3a] = &nfc_poller_iso14443_3a, + [NfcProtocolIso14443_3b] = &nfc_poller_iso14443_3b, + [NfcProtocolIso14443_4a] = &nfc_poller_iso14443_4a, + [NfcProtocolIso14443_4b] = &nfc_poller_iso14443_4b, + [NfcProtocolIso15693_3] = &nfc_poller_iso15693_3, + [NfcProtocolFelica] = &nfc_poller_felica, + [NfcProtocolMfUltralight] = &mf_ultralight_poller, + [NfcProtocolMfClassic] = &mf_classic_poller, + [NfcProtocolMfDesfire] = &mf_desfire_poller, + [NfcProtocolSlix] = &nfc_poller_slix, + /* Add new pollers here */ + [NfcProtocolSt25tb] = &nfc_poller_st25tb, +}; diff --git a/lib/nfc/protocols/nfc_poller_defs.h b/lib/nfc/protocols/nfc_poller_defs.h new file mode 100644 index 00000000000..a406a5f08b4 --- /dev/null +++ b/lib/nfc/protocols/nfc_poller_defs.h @@ -0,0 +1,14 @@ +#pragma once + +#include "nfc_poller_base.h" +#include "nfc_protocol.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern const NfcPollerBase* nfc_pollers_api[NfcProtocolNum]; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/nfc_protocol.c b/lib/nfc/protocols/nfc_protocol.c new file mode 100644 index 00000000000..2ea9b39820a --- /dev/null +++ b/lib/nfc/protocols/nfc_protocol.c @@ -0,0 +1,174 @@ +/** + * @file nfc_protocol.c + * @brief Main protocol hierarchy definitions. + * + * To reduce code duplication, all NFC protocols are described as a tree, whose + * structure is shown in the diagram below. The (Start) node is actually + * nonexistent and is there only for clarity. + * + * All its child protocols are considered base protocols, which in turn serve + * as parents to other, usually vendor-specific ones. + * + * ``` + * **************************** Protocol tree structure *************************** + * + * (Start) + * | + * +------------------------+-----------+---------+------------+ + * | | | | | + * ISO14443-3A ISO14443-3B Felica ISO15693-3 ST25TB + * | | | + * +---------------+-------------+ ISO14443-4B SLIX + * | | | + * ISO14443-4A Mf Ultralight Mf Classic + * | + * Mf Desfire + * ``` + * + * When implementing a new protocol, its place in the tree must be determined first. + * If no appropriate base protocols exists, then it must be a base protocol itself. + * + * This file is to be modified upon adding a new protocol (see below). + * + */ +#include "nfc_protocol.h" + +#include + +/** + * @brief Tree node describing a protocol. + * + * All base protocols (see above) have NfcProtocolInvalid + * in the parent_protocol field. + */ +typedef struct { + NfcProtocol parent_protocol; /**< Parent protocol identifier. */ + size_t children_num; /** < Number of the child protocols. */ + const NfcProtocol* children_protocol; /**< Pointer to an array of child protocol identifiers. */ +} NfcProtocolTreeNode; + +/** List of ISO14443-3A child protocols. */ +static const NfcProtocol nfc_protocol_iso14443_3a_children_protocol[] = { + NfcProtocolIso14443_4a, + NfcProtocolMfUltralight, +}; + +/** List of ISO14443-3B child protocols. */ +static const NfcProtocol nfc_protocol_iso14443_3b_children_protocol[] = { + NfcProtocolIso14443_4b, +}; + +/** List of ISO14443-4A child protocols. */ +static const NfcProtocol nfc_protocol_iso14443_4a_children_protocol[] = { + NfcProtocolMfDesfire, +}; + +/** List of ISO115693-3 child protocols. */ +static const NfcProtocol nfc_protocol_iso15693_3_children_protocol[] = { + NfcProtocolSlix, +}; + +/* Add new child protocol lists here (if necessary) */ + +/** + * @brief Flattened description of the NFC protocol tree. + * + * When implementing a new protocol, add the node here under its + * own index defined in nfc_protocol.h. + * + * Additionally, if it has an already implemented protocol as a parent, + * add its identifier to its respective list of child protocols (see above). + */ +static const NfcProtocolTreeNode nfc_protocol_nodes[NfcProtocolNum] = { + [NfcProtocolIso14443_3a] = + { + .parent_protocol = NfcProtocolInvalid, + .children_num = COUNT_OF(nfc_protocol_iso14443_3a_children_protocol), + .children_protocol = nfc_protocol_iso14443_3a_children_protocol, + }, + [NfcProtocolIso14443_3b] = + { + .parent_protocol = NfcProtocolInvalid, + .children_num = COUNT_OF(nfc_protocol_iso14443_3b_children_protocol), + .children_protocol = nfc_protocol_iso14443_3b_children_protocol, + }, + [NfcProtocolIso14443_4a] = + { + .parent_protocol = NfcProtocolIso14443_3a, + .children_num = COUNT_OF(nfc_protocol_iso14443_4a_children_protocol), + .children_protocol = nfc_protocol_iso14443_4a_children_protocol, + }, + [NfcProtocolIso14443_4b] = + { + .parent_protocol = NfcProtocolIso14443_3b, + .children_num = 0, + .children_protocol = NULL, + }, + [NfcProtocolIso15693_3] = + { + .parent_protocol = NfcProtocolInvalid, + .children_num = COUNT_OF(nfc_protocol_iso15693_3_children_protocol), + .children_protocol = nfc_protocol_iso15693_3_children_protocol, + }, + [NfcProtocolFelica] = + { + .parent_protocol = NfcProtocolInvalid, + .children_num = 0, + .children_protocol = NULL, + }, + [NfcProtocolMfUltralight] = + { + .parent_protocol = NfcProtocolIso14443_3a, + .children_num = 0, + .children_protocol = NULL, + }, + [NfcProtocolMfClassic] = + { + .parent_protocol = NfcProtocolIso14443_3a, + .children_num = 0, + .children_protocol = NULL, + }, + [NfcProtocolMfDesfire] = + { + .parent_protocol = NfcProtocolIso14443_4a, + .children_num = 0, + .children_protocol = NULL, + }, + [NfcProtocolSlix] = + { + .parent_protocol = NfcProtocolIso15693_3, + .children_num = 0, + .children_protocol = NULL, + }, + [NfcProtocolSt25tb] = + { + .parent_protocol = NfcProtocolInvalid, + .children_num = 0, + .children_protocol = NULL, + }, + /* Add new protocols here */ +}; + +NfcProtocol nfc_protocol_get_parent(NfcProtocol protocol) { + furi_assert(protocol < NfcProtocolNum); + + return nfc_protocol_nodes[protocol].parent_protocol; +} + +bool nfc_protocol_has_parent(NfcProtocol protocol, NfcProtocol parent_protocol) { + furi_assert(protocol < NfcProtocolNum); + furi_assert(parent_protocol < NfcProtocolNum); + + bool parent_found = false; + const NfcProtocolTreeNode* iter = &nfc_protocol_nodes[protocol]; + + while(iter->parent_protocol != NfcProtocolInvalid) { + if(iter->parent_protocol == parent_protocol) { + parent_found = true; + break; + } + iter = &nfc_protocol_nodes[iter->parent_protocol]; + } + + return parent_found; +} diff --git a/lib/nfc/protocols/nfc_protocol.h b/lib/nfc/protocols/nfc_protocol.h new file mode 100644 index 00000000000..55aa8a5895e --- /dev/null +++ b/lib/nfc/protocols/nfc_protocol.h @@ -0,0 +1,219 @@ +/** + * @file nfc_protocol.h + * @brief Top-level NFC protocol definitions. + * + * This file is to be modified upon adding a new protocol (see below). + * + * # How to add a new NFC protocol + * + * ## 1. Gather information + * + * Having proper protocol documentation would be ideal, although they are often available only for a fee, or given under an NDA. + * As an alternative, reading code from relevant open-source projects or notes gathered by means of reverse engineering will do. + * + * ### 1.1 Technology + * + * Check whether the NFC technology required for the protocol is supported. If no support exists, adding the protocol may + * be problematic, since it is highly hardware-dependent. + * + * @see NfcTech for the enumeration of supported NFC technologies. + * + * ### 1.2 Base protocols + * + * Check whether the protocol to be implemented is built on top of some already supported protocol. + * + * @see NfcProtocol for the enumeration of supported NFC protocols. + * + * If the answer is "yes", then the protocol to be implemented is a child protocol. If no, then it will become a base protocol. + * Sometimes it will be necessary to implement both, e.g. when the target protocol is built on top of a base one, + * but the latter is currently not supported. + * + * ## 2. Create the files + * + * ### 2.1 Recommended file structure + * + * The recommended file structure for a protocol is as follows: + * + * ```text + * protocols + * | + * +- protocol_name + * | + * +- protocol_name.h + * | + * +- protocol_name.c + * | + * +- protocol_name_device_defs.h + * | + * +- protocol_name_poller.h + * | + * +- protocol_name_poller.c + * | + * +- protocol_name_poller_defs.h + * | + * . + * . (files below are optional) + * . + * | + * +- protocol_name_listener.h | + * | | + * +- protocol_name_listener.c |- add for emulation support + * | | + * +- protocol_name_listener_defs.h | + * | + * +- protocol_name_sync_api.h | + * | |- add for synchronous API support + * +- protocol_name_sync_api.c | + * | + * ``` + * + * Additionally, an arbitrary amount of private `protocol_name_*_i.h` header files may be created. Do not put implementation + * details in the regular header files, as they will be exposed in the public firmware API later on. + * + * ### 2.2 File structure explanation + * + * | Filename | Explanation | + * |:------------------------------|:------------| + * | protocol_name.h | Main protocol data structure and associated functions declarations. It is recommended to keep the former as opaque pointer. | + * | protocol_name.c | Implementations of functions declared in `protocol_name.h`. | + * | protocol_name_device_defs.h | Declarations for use by the NfcDevice library. See nfc_device_base_i.h for more info. | + * | protocol_name_poller.h | Protocol-specific poller and associated functions declarations. | + * | protocol_name_poller.c | Implementation of functions declared in `protocol_name_poller.h`. | + * | protocol_name_poller_defs.h | Declarations for use by the NfcPoller library. See nfc_poller_base.h for more info. | + * | protocol_name_listener.h | Protocol-specific listener and associated functions declarations. Optional, needed for emulation support. | + * | protocol_name_listener.c | Implementation of functions declared in `protocol_name_listener.h`. Optional, needed for emulation support. | + * | protocol_name_listener_defs.h | Declarations for use by the NfcListener library. See nfc_listener_base.h for more info. Optional, needed for emulation support. | + * | protocol_name_sync_api.h | Synchronous API declarations. (See below for sync API explanation). Optional.| + * | protocol_name_sync_api.c | Synchronous API implementation. Optional. | + * + * ## 3 Implement the code + * + * ### 3.1 Protocol data structure + * + * A protocol data structure is what holds all data that can be possibly read from a card of a certain type. It may include a unique identifier (UID), + * configuration bytes and flags, built-in memory, and so on. + * Additionally, it must implement the NfcDevice interface so that it could be used by the firmware. + * + * @see nfc_device_base_i.h for the device interface description. + * + * @note It is strongly recommended to implement such a structure as an opaque type and access it via specialised methods only. + * + * If the protocol being implemented is a child protocol, then its data must include a pointer to the parent protocol data structure. + * It is the protocol's responsibility to correctly create and delete the instance the pointer is pointing to. + * + * ### 3.2 Poller (reading functionality) + * + * A poller contains the functions necessary to successfully read a card of supported type. It must also implement a specific interface, + * namely described by the NfcPollerBase type. + * + * Upon creation, a poller instance will receive either a pointer to the Nfc instance (if it's a base protocol), or a pointer to another poller + * instance (if it is a child protocol) as the `alloc()` parameter. + * + * @see nfc_poller_base.h for the poller interface description. + * + * ### 3.3 Listener (emulation functionality) + * + * A listener implementation is optional, needed only when emulation is required/possible. + * + * Same considerations apply to the listener as for the poller. Additionally, upon creation it will receive an additional parameter + * in the form of a pointer to the matching protocol data structure, which will be used during emulation. The listener instance + * does not own this data and does not need to worry about its deletion. + * + * @see nfc_listener_base.h for the listener interface description. + * + * ### 3.4 Synchronous API + * + * Synchronous API does exaclty what it says -- it provides a set of blocking functions for easier use of pollers. + * Instead of creating all necessary instances and setting up callbacks manually, it does it automatically, at the + * expense of some flexibility. + * + * The most notable use of sync API is in the supported card plugins, so it's a good idea to implement it if such a plugin + * is to be implemented afterwards. + * + * ### 3.5 Registering the protocol + * + * After completing the protocol, it must be registered within the NfcProtocol system in order for it to be usable. + * + * 1. In nfc_protocol.h, add a new entry in the NfcProtocol enum in the form of NfcProtocolProtocolName. + * 2. In nfc_protocol.c, add a new entry in the `nfc_protocol_nodes[]` array under the appropriate index. + * 1. If it is a child protocol, register it as a child in the respective `nfc_base_protocol_name_children_protocol[]` array. + * 2. If the protocol has children on its own, create a `nfc_protocol_name_children_protocol[]` array + * with respective identifiers and register it in the protocol entry added in step 2. + * 3. In nfc_device_defs.c, include `protocol_name_device_defs.h` and add a pointer to the + * `protocol_name_device_defs` structure under the appropriate index. + * 4. In nfc_poller_defs.c, include `protocol_name_poller_defs.h` and add a pointer to the + * `protocol_name_poller_defs` structure under the appropriate index. + * 5. (Optional) If emulation support was implemented, do the step 4, but for the listener. + * 6. Add `protocol_name.h`, `protocol_name_poller.h`, and optionally, `protocol_name_listener.h` + * and `protocol_name_sync_api.h` into the `SDK_HEADERS` list in the SConscript file. + * This will export the protocol's types and functions for use by the applications. + * 7. Done! + * + * ## What's next? + * + * It's about time to integrate the newly implemented protocol into the main NFC application. Without that, reading a card + * of this type would crash it. + * + * @see nfc_protocol_support.h for more information on protocol integration. + * + * After having done that, a supported card plugin may be implemented to take further advantage of the new protocol. + * + * @see nfc_supported_card_plugin.h for more information on supported card plugins. + * + */ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Enumeration of all available NFC protocols. + * + * When implementing a new protocol, add its identifier before the + * NfcProtocolNum entry. + */ +typedef enum { + NfcProtocolIso14443_3a, + NfcProtocolIso14443_3b, + NfcProtocolIso14443_4a, + NfcProtocolIso14443_4b, + NfcProtocolIso15693_3, + NfcProtocolFelica, + NfcProtocolMfUltralight, + NfcProtocolMfClassic, + NfcProtocolMfDesfire, + NfcProtocolSlix, + NfcProtocolSt25tb, + /* Add new protocols here */ + + NfcProtocolNum, /**< Special value representing the number of available protocols. */ + + NfcProtocolInvalid, /**< Special value representing an invalid state. */ +} NfcProtocol; + +/** + * @brief Get the immediate parent of a specific protocol. + * + * @param[in] protocol identifier of the protocol in question. + * @returns parent protocol identifier if it has one, or NfcProtocolInvalid otherwise. + */ +NfcProtocol nfc_protocol_get_parent(NfcProtocol protocol); + +/** + * @brief Determine if a specific protocol has a parent on an arbitrary level. + * + * Unlike nfc_protocol_get_parent(), this function will traverse the full protocol hierarchy + * and check each parent node for the matching protocol type. + * + * @param[in] protocol identifier of the protocol in question. + * @param[in] parent_protocol identifier of the parent protocol in question. + * @returns true if the parent of given type exists, false otherwise. + */ +bool nfc_protocol_has_parent(NfcProtocol protocol, NfcProtocol parent_protocol); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/nfca.c b/lib/nfc/protocols/nfca.c deleted file mode 100644 index ab4f3f23c60..00000000000 --- a/lib/nfc/protocols/nfca.c +++ /dev/null @@ -1,140 +0,0 @@ -#include "nfca.h" -#include -#include -#include - -#define NFCA_CRC_INIT (0x6363) - -#define NFCA_F_SIG (13560000.0) -#define T_SIG 7374 //73.746ns*100 -#define T_SIG_x8 58992 //T_SIG*8 -#define T_SIG_x8_x8 471936 //T_SIG*8*8 -#define T_SIG_x8_x9 530928 //T_SIG*8*9 - -#define NFCA_SIGNAL_MAX_EDGES (1350) - -typedef struct { - uint8_t cmd; - uint8_t param; -} nfca_cmd_rats; - -static uint8_t nfca_default_ats[] = {0x05, 0x78, 0x80, 0x80, 0x00}; - -static uint8_t nfca_halt_req[] = {NFCA_CMD_HALT, 0x00}; - -uint16_t nfca_get_crc16(uint8_t* buff, uint16_t len) { - uint16_t crc = NFCA_CRC_INIT; - uint8_t byte = 0; - - for(uint8_t i = 0; i < len; i++) { - byte = buff[i]; - byte ^= (uint8_t)(crc & 0xff); - byte ^= byte << 4; - crc = (crc >> 8) ^ (((uint16_t)byte) << 8) ^ (((uint16_t)byte) << 3) ^ - (((uint16_t)byte) >> 4); - } - - return crc; -} - -void nfca_append_crc16(uint8_t* buff, uint16_t len) { - uint16_t crc = nfca_get_crc16(buff, len); - buff[len] = (uint8_t)crc; - buff[len + 1] = (uint8_t)(crc >> 8); -} - -bool nfca_emulation_handler( - uint8_t* buff_rx, - uint16_t buff_rx_len, - uint8_t* buff_tx, - uint16_t* buff_tx_len) { - bool halt = false; - uint8_t rx_bytes = buff_rx_len / 8; - - if(rx_bytes == sizeof(nfca_halt_req) && !memcmp(buff_rx, nfca_halt_req, rx_bytes)) { - halt = true; - } else if(rx_bytes == sizeof(nfca_cmd_rats) && buff_rx[0] == NFCA_CMD_RATS) { - memcpy(buff_tx, nfca_default_ats, sizeof(nfca_default_ats)); - *buff_tx_len = sizeof(nfca_default_ats) * 8; - } - - return halt; -} - -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] = T_SIG_x8; - } - signal->edge_timings[7] = T_SIG_x8_x9; - signal->edge_cnt = 8; - } else { - signal->start_level = false; - signal->edge_timings[0] = T_SIG_x8_x8; - for(size_t i = 1; i < 9; i++) { - signal->edge_timings[i] = T_SIG_x8; - } - 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 deleted file mode 100644 index e4978a3e0b5..00000000000 --- a/lib/nfc/protocols/nfca.h +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once - -#include -#include - -#include - -#define NFCA_CMD_RATS (0xE0U) -#define NFCA_CMD_HALT (0x50U) - -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); - -bool nfca_emulation_handler( - uint8_t* buff_rx, - 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/nfc/protocols/nfcv.c b/lib/nfc/protocols/nfcv.c deleted file mode 100644 index 28146328115..00000000000 --- a/lib/nfc/protocols/nfcv.c +++ /dev/null @@ -1,1438 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "nfcv.h" -#include "nfc_util.h" -#include "slix.h" - -#define TAG "NfcV" - -/* macros to map "modulate field" flag to GPIO level */ -#define GPIO_LEVEL_MODULATED NFCV_LOAD_MODULATION_POLARITY -#define GPIO_LEVEL_UNMODULATED (!GPIO_LEVEL_MODULATED) - -/* timing macros */ -#define DIGITAL_SIGNAL_UNIT_S (100000000000.0f) -#define DIGITAL_SIGNAL_UNIT_US (100000.0f) - -ReturnCode nfcv_inventory(uint8_t* uid) { - uint16_t received = 0; - rfalNfcvInventoryRes res; - ReturnCode ret = ERR_NONE; - - for(int tries = 0; tries < NFCV_COMMAND_RETRIES; tries++) { - /* TODO: needs proper abstraction via furi_hal(_ll)_* */ - ret = rfalNfcvPollerInventory(RFAL_NFCV_NUM_SLOTS_1, 0, NULL, &res, &received); - - if(ret == ERR_NONE) { - break; - } - } - - if(ret == ERR_NONE) { - if(uid != NULL) { - memcpy(uid, res.UID, NFCV_UID_LENGTH); - } - } - - return ret; -} - -ReturnCode nfcv_read_blocks(NfcVReader* reader, NfcVData* nfcv_data) { - UNUSED(reader); - - uint16_t received = 0; - for(size_t block = 0; block < nfcv_data->block_num; block++) { - uint8_t rxBuf[32]; - FURI_LOG_D(TAG, "Reading block %d/%d", block, (nfcv_data->block_num - 1)); - - ReturnCode ret = ERR_NONE; - for(int tries = 0; tries < NFCV_COMMAND_RETRIES; tries++) { - ret = rfalNfcvPollerReadSingleBlock( - RFAL_NFCV_REQ_FLAG_DEFAULT, NULL, block, rxBuf, sizeof(rxBuf), &received); - - if(ret == ERR_NONE) { - break; - } - } - if(ret != ERR_NONE) { - FURI_LOG_D(TAG, "failed to read: %d", ret); - return ret; - } - memcpy( - &(nfcv_data->data[block * nfcv_data->block_size]), &rxBuf[1], nfcv_data->block_size); - FURI_LOG_D( - TAG, - " %02X %02X %02X %02X", - nfcv_data->data[block * nfcv_data->block_size + 0], - nfcv_data->data[block * nfcv_data->block_size + 1], - nfcv_data->data[block * nfcv_data->block_size + 2], - nfcv_data->data[block * nfcv_data->block_size + 3]); - } - - return ERR_NONE; -} - -ReturnCode nfcv_read_sysinfo(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data) { - uint8_t rxBuf[32]; - uint16_t received = 0; - ReturnCode ret = ERR_NONE; - - FURI_LOG_D(TAG, "Read SYSTEM INFORMATION..."); - - for(int tries = 0; tries < NFCV_COMMAND_RETRIES; tries++) { - /* TODO: needs proper abstraction via furi_hal(_ll)_* */ - ret = rfalNfcvPollerGetSystemInformation( - RFAL_NFCV_REQ_FLAG_DEFAULT, NULL, rxBuf, sizeof(rxBuf), &received); - - if(ret == ERR_NONE) { - break; - } - } - - if(ret == ERR_NONE) { - nfc_data->type = FuriHalNfcTypeV; - nfc_data->uid_len = NFCV_UID_LENGTH; - /* UID is stored reversed in this response */ - for(int pos = 0; pos < nfc_data->uid_len; pos++) { - nfc_data->uid[pos] = rxBuf[2 + (NFCV_UID_LENGTH - 1 - pos)]; - } - nfcv_data->dsfid = rxBuf[NFCV_UID_LENGTH + 2]; - nfcv_data->afi = rxBuf[NFCV_UID_LENGTH + 3]; - nfcv_data->block_num = rxBuf[NFCV_UID_LENGTH + 4] + 1; - nfcv_data->block_size = rxBuf[NFCV_UID_LENGTH + 5] + 1; - nfcv_data->ic_ref = rxBuf[NFCV_UID_LENGTH + 6]; - FURI_LOG_D( - TAG, - " UID: %02X %02X %02X %02X %02X %02X %02X %02X", - nfc_data->uid[0], - nfc_data->uid[1], - nfc_data->uid[2], - nfc_data->uid[3], - nfc_data->uid[4], - nfc_data->uid[5], - nfc_data->uid[6], - nfc_data->uid[7]); - FURI_LOG_D( - TAG, - " DSFID %d, AFI %d, Blocks %d, Size %d, IC Ref %d", - nfcv_data->dsfid, - nfcv_data->afi, - nfcv_data->block_num, - nfcv_data->block_size, - nfcv_data->ic_ref); - return ret; - } - FURI_LOG_D(TAG, "Failed: %d", ret); - - return ret; -} - -bool nfcv_read_card(NfcVReader* reader, FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data) { - furi_assert(reader); - furi_assert(nfc_data); - furi_assert(nfcv_data); - - if(nfcv_read_sysinfo(nfc_data, nfcv_data) != ERR_NONE) { - return false; - } - - if(nfcv_read_blocks(reader, nfcv_data) != ERR_NONE) { - return false; - } - - /* clear all know sub type data before reading them */ - memset(&nfcv_data->sub_data, 0x00, sizeof(nfcv_data->sub_data)); - - if(slix_check_card_type(nfc_data)) { - FURI_LOG_I(TAG, "NXP SLIX detected"); - nfcv_data->sub_type = NfcVTypeSlix; - } else if(slix2_check_card_type(nfc_data)) { - FURI_LOG_I(TAG, "NXP SLIX2 detected"); - nfcv_data->sub_type = NfcVTypeSlix2; - if(slix2_read_custom(nfc_data, nfcv_data) != ERR_NONE) { - return false; - } - } else if(slix_s_check_card_type(nfc_data)) { - FURI_LOG_I(TAG, "NXP SLIX-S detected"); - nfcv_data->sub_type = NfcVTypeSlixS; - } else if(slix_l_check_card_type(nfc_data)) { - FURI_LOG_I(TAG, "NXP SLIX-L detected"); - nfcv_data->sub_type = NfcVTypeSlixL; - } else { - nfcv_data->sub_type = NfcVTypePlain; - } - - return true; -} - -void nfcv_crc(uint8_t* data, uint32_t length) { - uint32_t reg = 0xFFFF; - - for(size_t i = 0; i < length; i++) { - reg = reg ^ ((uint32_t)data[i]); - for(size_t j = 0; j < 8; j++) { - if(reg & 0x0001) { - reg = (reg >> 1) ^ 0x8408; - } else { - reg = (reg >> 1); - } - } - } - - uint16_t crc = ~(uint16_t)(reg & 0xffff); - - data[length + 0] = crc & 0xFF; - data[length + 1] = crc >> 8; -} - -void nfcv_emu_free_signals(NfcVEmuAirSignals* signals) { - furi_assert(signals); - - if(signals->nfcv_resp_one) { - digital_signal_free(signals->nfcv_resp_one); - } - if(signals->nfcv_resp_zero) { - digital_signal_free(signals->nfcv_resp_zero); - } - if(signals->nfcv_resp_sof) { - digital_signal_free(signals->nfcv_resp_sof); - } - if(signals->nfcv_resp_eof) { - digital_signal_free(signals->nfcv_resp_eof); - } - signals->nfcv_resp_one = NULL; - signals->nfcv_resp_zero = NULL; - signals->nfcv_resp_sof = NULL; - signals->nfcv_resp_eof = NULL; -} - -bool nfcv_emu_alloc_signals(NfcVEmuAir* air, NfcVEmuAirSignals* signals, uint32_t slowdown) { - furi_assert(air); - furi_assert(signals); - - bool success = true; - - if(!signals->nfcv_resp_one) { - /* logical one: unmodulated then 8 pulses */ - signals->nfcv_resp_one = digital_signal_alloc( - slowdown * (air->nfcv_resp_unmod->edge_cnt + 8 * air->nfcv_resp_pulse->edge_cnt)); - if(!signals->nfcv_resp_one) { - return false; - } - for(size_t i = 0; i < slowdown; i++) { - success &= digital_signal_append(signals->nfcv_resp_one, air->nfcv_resp_unmod); - } - for(size_t i = 0; i < slowdown * 8; i++) { - success &= digital_signal_append(signals->nfcv_resp_one, air->nfcv_resp_pulse); - } - if(!success) { - return false; - } - } - if(!signals->nfcv_resp_zero) { - /* logical zero: 8 pulses then unmodulated */ - signals->nfcv_resp_zero = digital_signal_alloc( - slowdown * (8 * air->nfcv_resp_pulse->edge_cnt + air->nfcv_resp_unmod->edge_cnt)); - if(!signals->nfcv_resp_zero) { - return false; - } - for(size_t i = 0; i < slowdown * 8; i++) { - success &= digital_signal_append(signals->nfcv_resp_zero, air->nfcv_resp_pulse); - } - for(size_t i = 0; i < slowdown; i++) { - success &= digital_signal_append(signals->nfcv_resp_zero, air->nfcv_resp_unmod); - } - if(!success) { - return false; - } - } - if(!signals->nfcv_resp_sof) { - /* SOF: unmodulated, 24 pulses, logic 1 */ - signals->nfcv_resp_sof = digital_signal_alloc( - slowdown * (3 * air->nfcv_resp_unmod->edge_cnt + 24 * air->nfcv_resp_pulse->edge_cnt) + - signals->nfcv_resp_one->edge_cnt); - if(!signals->nfcv_resp_sof) { - return false; - } - for(size_t i = 0; i < slowdown * 3; i++) { - success &= digital_signal_append(signals->nfcv_resp_sof, air->nfcv_resp_unmod); - } - for(size_t i = 0; i < slowdown * 24; i++) { - success &= digital_signal_append(signals->nfcv_resp_sof, air->nfcv_resp_pulse); - } - success &= digital_signal_append(signals->nfcv_resp_sof, signals->nfcv_resp_one); - if(!success) { - return false; - } - } - if(!signals->nfcv_resp_eof) { - /* EOF: logic 0, 24 pulses, unmodulated */ - signals->nfcv_resp_eof = digital_signal_alloc( - signals->nfcv_resp_zero->edge_cnt + - slowdown * (24 * air->nfcv_resp_pulse->edge_cnt + 3 * air->nfcv_resp_unmod->edge_cnt) + - air->nfcv_resp_unmod->edge_cnt); - if(!signals->nfcv_resp_eof) { - return false; - } - success &= digital_signal_append(signals->nfcv_resp_eof, signals->nfcv_resp_zero); - for(size_t i = 0; i < slowdown * 23; i++) { - success &= digital_signal_append(signals->nfcv_resp_eof, air->nfcv_resp_pulse); - } - /* we don't want to add the last level as we just want a transition to "unmodulated" again */ - for(size_t i = 0; i < slowdown; i++) { - success &= digital_signal_append(signals->nfcv_resp_eof, air->nfcv_resp_half_pulse); - } - } - return success; -} - -bool nfcv_emu_alloc(NfcVData* nfcv_data) { - furi_assert(nfcv_data); - - if(!nfcv_data->frame) { - nfcv_data->frame = malloc(NFCV_FRAMESIZE_MAX); - if(!nfcv_data->frame) { - return false; - } - } - - if(!nfcv_data->emu_air.nfcv_signal) { - /* assuming max frame length is 255 bytes */ - nfcv_data->emu_air.nfcv_signal = digital_sequence_alloc(8 * 255 + 2, &gpio_spi_r_mosi); - if(!nfcv_data->emu_air.nfcv_signal) { - return false; - } - } - if(!nfcv_data->emu_air.nfcv_resp_unmod) { - /* unmodulated 256/fc or 1024/fc signal as building block */ - nfcv_data->emu_air.nfcv_resp_unmod = digital_signal_alloc(4); - if(!nfcv_data->emu_air.nfcv_resp_unmod) { - return false; - } - nfcv_data->emu_air.nfcv_resp_unmod->start_level = GPIO_LEVEL_UNMODULATED; - nfcv_data->emu_air.nfcv_resp_unmod->edge_timings[0] = - (uint32_t)(NFCV_RESP_SUBC1_UNMOD_256 * DIGITAL_SIGNAL_UNIT_S); - nfcv_data->emu_air.nfcv_resp_unmod->edge_cnt = 1; - } - if(!nfcv_data->emu_air.nfcv_resp_pulse) { - /* modulated fc/32 or fc/8 pulse as building block */ - nfcv_data->emu_air.nfcv_resp_pulse = digital_signal_alloc(4); - if(!nfcv_data->emu_air.nfcv_resp_pulse) { - return false; - } - nfcv_data->emu_air.nfcv_resp_pulse->start_level = GPIO_LEVEL_MODULATED; - nfcv_data->emu_air.nfcv_resp_pulse->edge_timings[0] = - (uint32_t)(NFCV_RESP_SUBC1_PULSE_32 * DIGITAL_SIGNAL_UNIT_S); - nfcv_data->emu_air.nfcv_resp_pulse->edge_timings[1] = - (uint32_t)(NFCV_RESP_SUBC1_PULSE_32 * DIGITAL_SIGNAL_UNIT_S); - nfcv_data->emu_air.nfcv_resp_pulse->edge_cnt = 2; - } - - if(!nfcv_data->emu_air.nfcv_resp_half_pulse) { - /* modulated fc/32 or fc/8 pulse as building block */ - nfcv_data->emu_air.nfcv_resp_half_pulse = digital_signal_alloc(4); - if(!nfcv_data->emu_air.nfcv_resp_half_pulse) { - return false; - } - nfcv_data->emu_air.nfcv_resp_half_pulse->start_level = GPIO_LEVEL_MODULATED; - nfcv_data->emu_air.nfcv_resp_half_pulse->edge_timings[0] = - (uint32_t)(NFCV_RESP_SUBC1_PULSE_32 * DIGITAL_SIGNAL_UNIT_S); - nfcv_data->emu_air.nfcv_resp_half_pulse->edge_cnt = 1; - } - - bool success = true; - success &= nfcv_emu_alloc_signals(&nfcv_data->emu_air, &nfcv_data->emu_air.signals_high, 1); - success &= nfcv_emu_alloc_signals(&nfcv_data->emu_air, &nfcv_data->emu_air.signals_low, 4); - - if(!success) { - FURI_LOG_E(TAG, "Failed to allocate signals"); - return false; - } - - digital_sequence_set_signal( - nfcv_data->emu_air.nfcv_signal, - NFCV_SIG_SOF, - nfcv_data->emu_air.signals_high.nfcv_resp_sof); - digital_sequence_set_signal( - nfcv_data->emu_air.nfcv_signal, - NFCV_SIG_BIT0, - nfcv_data->emu_air.signals_high.nfcv_resp_zero); - digital_sequence_set_signal( - nfcv_data->emu_air.nfcv_signal, - NFCV_SIG_BIT1, - nfcv_data->emu_air.signals_high.nfcv_resp_one); - digital_sequence_set_signal( - nfcv_data->emu_air.nfcv_signal, - NFCV_SIG_EOF, - nfcv_data->emu_air.signals_high.nfcv_resp_eof); - digital_sequence_set_signal( - nfcv_data->emu_air.nfcv_signal, - NFCV_SIG_LOW_SOF, - nfcv_data->emu_air.signals_low.nfcv_resp_sof); - digital_sequence_set_signal( - nfcv_data->emu_air.nfcv_signal, - NFCV_SIG_LOW_BIT0, - nfcv_data->emu_air.signals_low.nfcv_resp_zero); - digital_sequence_set_signal( - nfcv_data->emu_air.nfcv_signal, - NFCV_SIG_LOW_BIT1, - nfcv_data->emu_air.signals_low.nfcv_resp_one); - digital_sequence_set_signal( - nfcv_data->emu_air.nfcv_signal, - NFCV_SIG_LOW_EOF, - nfcv_data->emu_air.signals_low.nfcv_resp_eof); - - return true; -} - -void nfcv_emu_free(NfcVData* nfcv_data) { - furi_assert(nfcv_data); - - if(nfcv_data->frame) { - free(nfcv_data->frame); - } - if(nfcv_data->emu_protocol_ctx) { - free(nfcv_data->emu_protocol_ctx); - } - if(nfcv_data->emu_air.nfcv_resp_unmod) { - digital_signal_free(nfcv_data->emu_air.nfcv_resp_unmod); - } - if(nfcv_data->emu_air.nfcv_resp_pulse) { - digital_signal_free(nfcv_data->emu_air.nfcv_resp_pulse); - } - if(nfcv_data->emu_air.nfcv_resp_half_pulse) { - digital_signal_free(nfcv_data->emu_air.nfcv_resp_half_pulse); - } - if(nfcv_data->emu_air.nfcv_signal) { - digital_sequence_free(nfcv_data->emu_air.nfcv_signal); - } - if(nfcv_data->emu_air.reader_signal) { - // Stop pulse reader and disable bus before free - pulse_reader_stop(nfcv_data->emu_air.reader_signal); - // Free pulse reader - pulse_reader_free(nfcv_data->emu_air.reader_signal); - } - - nfcv_data->frame = NULL; - nfcv_data->emu_air.nfcv_resp_unmod = NULL; - nfcv_data->emu_air.nfcv_resp_pulse = NULL; - nfcv_data->emu_air.nfcv_resp_half_pulse = NULL; - nfcv_data->emu_air.nfcv_signal = NULL; - nfcv_data->emu_air.reader_signal = NULL; - - nfcv_emu_free_signals(&nfcv_data->emu_air.signals_high); - nfcv_emu_free_signals(&nfcv_data->emu_air.signals_low); -} - -void nfcv_emu_send( - FuriHalNfcTxRxContext* tx_rx, - NfcVData* nfcv, - uint8_t* data, - uint8_t length, - NfcVSendFlags flags, - uint32_t send_time) { - furi_assert(tx_rx); - furi_assert(nfcv); - - /* picked default value (0) to match the most common format */ - if(flags == NfcVSendFlagsNormal) { - flags = NfcVSendFlagsSof | NfcVSendFlagsCrc | NfcVSendFlagsEof | - NfcVSendFlagsOneSubcarrier | NfcVSendFlagsHighRate; - } - - if(flags & NfcVSendFlagsCrc) { - nfcv_crc(data, length); - length += 2; - } - - /* depending on the request flags, send with high or low rate */ - uint32_t bit0 = (flags & NfcVSendFlagsHighRate) ? NFCV_SIG_BIT0 : NFCV_SIG_LOW_BIT0; - uint32_t bit1 = (flags & NfcVSendFlagsHighRate) ? NFCV_SIG_BIT1 : NFCV_SIG_LOW_BIT1; - uint32_t sof = (flags & NfcVSendFlagsHighRate) ? NFCV_SIG_SOF : NFCV_SIG_LOW_SOF; - uint32_t eof = (flags & NfcVSendFlagsHighRate) ? NFCV_SIG_EOF : NFCV_SIG_LOW_EOF; - - digital_sequence_clear(nfcv->emu_air.nfcv_signal); - - if(flags & NfcVSendFlagsSof) { - digital_sequence_add(nfcv->emu_air.nfcv_signal, sof); - } - - for(int bit_total = 0; bit_total < length * 8; bit_total++) { - uint32_t byte_pos = bit_total / 8; - uint32_t bit_pos = bit_total % 8; - uint8_t bit_val = 0x01 << bit_pos; - - digital_sequence_add(nfcv->emu_air.nfcv_signal, (data[byte_pos] & bit_val) ? bit1 : bit0); - } - - if(flags & NfcVSendFlagsEof) { - digital_sequence_add(nfcv->emu_air.nfcv_signal, eof); - } - - furi_hal_gpio_write(&gpio_spi_r_mosi, GPIO_LEVEL_UNMODULATED); - digital_sequence_set_sendtime(nfcv->emu_air.nfcv_signal, send_time); - digital_sequence_send(nfcv->emu_air.nfcv_signal); - furi_hal_gpio_write(&gpio_spi_r_mosi, GPIO_LEVEL_UNMODULATED); - - if(tx_rx->sniff_tx) { - tx_rx->sniff_tx(data, length * 8, false, tx_rx->sniff_context); - } -} - -static void nfcv_revuidcpy(uint8_t* dst, uint8_t* src) { - for(int pos = 0; pos < NFCV_UID_LENGTH; pos++) { - dst[pos] = src[NFCV_UID_LENGTH - 1 - pos]; - } -} - -static int nfcv_revuidcmp(uint8_t* dst, uint8_t* src) { - for(int pos = 0; pos < NFCV_UID_LENGTH; pos++) { - if(dst[pos] != src[NFCV_UID_LENGTH - 1 - pos]) { - return 1; - } - } - return 0; -} - -void nfcv_emu_handle_packet( - FuriHalNfcTxRxContext* tx_rx, - FuriHalNfcDevData* nfc_data, - void* nfcv_data_in) { - furi_assert(tx_rx); - furi_assert(nfc_data); - furi_assert(nfcv_data_in); - - NfcVData* nfcv_data = (NfcVData*)nfcv_data_in; - NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx; - - if(nfcv_data->frame_length < 2) { - return; - } - - if(nfcv_data->echo_mode) { - nfcv_emu_send( - tx_rx, - nfcv_data, - nfcv_data->frame, - nfcv_data->frame_length, - NfcVSendFlagsSof | NfcVSendFlagsHighRate | NfcVSendFlagsEof, - ctx->send_time); - snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "ECHO data"); - return; - } - - /* parse the frame data for the upcoming part 3 handling */ - ctx->flags = nfcv_data->frame[0]; - ctx->command = nfcv_data->frame[1]; - ctx->selected = !(ctx->flags & NFCV_REQ_FLAG_INVENTORY) && (ctx->flags & NFCV_REQ_FLAG_SELECT); - ctx->addressed = !(ctx->flags & NFCV_REQ_FLAG_INVENTORY) && - (ctx->flags & NFCV_REQ_FLAG_ADDRESS); - ctx->advanced = (ctx->command >= NFCV_CMD_ADVANCED); - ctx->address_offset = 2 + (ctx->advanced ? 1 : 0); - ctx->payload_offset = ctx->address_offset + (ctx->addressed ? NFCV_UID_LENGTH : 0); - ctx->response_flags = NfcVSendFlagsSof | NfcVSendFlagsCrc | NfcVSendFlagsEof; - ctx->send_time = nfcv_data->eof_timestamp + NFCV_FDT_FC(4380); - - if(ctx->flags & NFCV_REQ_FLAG_DATA_RATE) { - ctx->response_flags |= NfcVSendFlagsHighRate; - } - if(ctx->flags & NFCV_REQ_FLAG_SUB_CARRIER) { - ctx->response_flags |= NfcVSendFlagsTwoSubcarrier; - } - - if(ctx->payload_offset + 2 > nfcv_data->frame_length) { -#ifdef NFCV_VERBOSE - FURI_LOG_D(TAG, "command 0x%02X, but packet is too short", ctx->command); -#endif - return; - } - - /* standard behavior is implemented */ - if(ctx->addressed) { - uint8_t* address = &nfcv_data->frame[ctx->address_offset]; - if(nfcv_revuidcmp(address, nfc_data->uid)) { -#ifdef NFCV_VERBOSE - FURI_LOG_D(TAG, "addressed command 0x%02X, but not for us:", ctx->command); - FURI_LOG_D( - TAG, - " dest: %02X%02X%02X%02X%02X%02X%02X%02X", - address[7], - address[6], - address[5], - address[4], - address[3], - address[2], - address[1], - address[0]); - FURI_LOG_D( - TAG, - " our UID: %02X%02X%02X%02X%02X%02X%02X%02X", - nfc_data->uid[0], - nfc_data->uid[1], - nfc_data->uid[2], - nfc_data->uid[3], - nfc_data->uid[4], - nfc_data->uid[5], - nfc_data->uid[6], - nfc_data->uid[7]); -#endif - return; - } - } - - if(ctx->selected && !nfcv_data->selected) { -#ifdef NFCV_VERBOSE - FURI_LOG_D( - TAG, - "selected card shall execute command 0x%02X, but we were not selected", - ctx->command); -#endif - return; - } - - /* then give control to the card subtype specific protocol filter */ - if(ctx->emu_protocol_filter != NULL) { - if(ctx->emu_protocol_filter(tx_rx, nfc_data, nfcv_data)) { - if(strlen(nfcv_data->last_command) > 0) { -#ifdef NFCV_VERBOSE - FURI_LOG_D( - TAG, "Received command %s (handled by filter)", nfcv_data->last_command); -#endif - } - return; - } - } - - switch(ctx->command) { - case NFCV_CMD_INVENTORY: { - bool respond = false; - - if(ctx->flags & NFCV_REQ_FLAG_AFI) { - uint8_t afi = nfcv_data->frame[ctx->payload_offset]; - - uint8_t family = (afi & 0xF0); - uint8_t subfamily = (afi & 0x0F); - - if(family) { - if(subfamily) { - /* selected family and subfamily only */ - if(afi == nfcv_data->afi) { - respond = true; - } - } else { - /* selected family, any subfamily */ - if(family == (nfcv_data->afi & 0xf0)) { - respond = true; - } - } - } else { - if(subfamily) { - /* proprietary subfamily only */ - if(afi == nfcv_data->afi) { - respond = true; - } - } else { - /* all families and subfamilies */ - respond = true; - } - } - - } else { - respond = true; - } - - if(!nfcv_data->quiet && respond) { - int buffer_pos = 0; - ctx->response_buffer[buffer_pos++] = NFCV_NOERROR; - ctx->response_buffer[buffer_pos++] = nfcv_data->dsfid; - nfcv_revuidcpy(&ctx->response_buffer[buffer_pos], nfc_data->uid); - buffer_pos += NFCV_UID_LENGTH; - - nfcv_emu_send( - tx_rx, - nfcv_data, - ctx->response_buffer, - buffer_pos, - ctx->response_flags, - ctx->send_time); - snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "INVENTORY"); - } else { - snprintf( - nfcv_data->last_command, sizeof(nfcv_data->last_command), "INVENTORY (quiet)"); - } - break; - } - - case NFCV_CMD_STAY_QUIET: { - snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "STAYQUIET"); - nfcv_data->quiet = true; - break; - } - - case NFCV_CMD_LOCK_BLOCK: { - uint8_t block = nfcv_data->frame[ctx->payload_offset]; - nfcv_data->security_status[block] |= 0x01; - nfcv_data->modified = true; - - ctx->response_buffer[0] = NFCV_NOERROR; - nfcv_emu_send( - tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); - - snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "LOCK BLOCK %d", block); - break; - } - - case NFCV_CMD_WRITE_DSFID: { - uint8_t id = nfcv_data->frame[ctx->payload_offset]; - - if(!(nfcv_data->security_status[0] & NfcVLockBitDsfid)) { - nfcv_data->dsfid = id; - nfcv_data->modified = true; - ctx->response_buffer[0] = NFCV_NOERROR; - nfcv_emu_send( - tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); - } - - snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "WRITE DSFID %02X", id); - break; - } - - case NFCV_CMD_WRITE_AFI: { - uint8_t id = nfcv_data->frame[ctx->payload_offset]; - - if(!(nfcv_data->security_status[0] & NfcVLockBitAfi)) { - nfcv_data->afi = id; - nfcv_data->modified = true; - ctx->response_buffer[0] = NFCV_NOERROR; - nfcv_emu_send( - tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); - } - - snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "WRITE AFI %02X", id); - break; - } - - case NFCV_CMD_LOCK_DSFID: { - if(!(nfcv_data->security_status[0] & NfcVLockBitDsfid)) { - nfcv_data->security_status[0] |= NfcVLockBitDsfid; - nfcv_data->modified = true; - - ctx->response_buffer[0] = NFCV_NOERROR; - nfcv_emu_send( - tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); - } - - snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "LOCK DSFID"); - break; - } - - case NFCV_CMD_LOCK_AFI: { - if(!(nfcv_data->security_status[0] & NfcVLockBitAfi)) { - nfcv_data->security_status[0] |= NfcVLockBitAfi; - nfcv_data->modified = true; - - ctx->response_buffer[0] = NFCV_NOERROR; - nfcv_emu_send( - tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); - } - - snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "LOCK AFI"); - break; - } - - case NFCV_CMD_SELECT: { - ctx->response_buffer[0] = NFCV_NOERROR; - nfcv_data->selected = true; - nfcv_data->quiet = false; - nfcv_emu_send( - tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); - snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "SELECT"); - break; - } - - case NFCV_CMD_RESET_TO_READY: { - ctx->response_buffer[0] = NFCV_NOERROR; - nfcv_data->quiet = false; - nfcv_emu_send( - tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); - snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "RESET_TO_READY"); - break; - } - - case NFCV_CMD_READ_MULTI_BLOCK: - case NFCV_CMD_READ_BLOCK: { - uint8_t block = nfcv_data->frame[ctx->payload_offset]; - int blocks = 1; - - if(ctx->command == NFCV_CMD_READ_MULTI_BLOCK) { - blocks = nfcv_data->frame[ctx->payload_offset + 1] + 1; - } - - /* limit the maximum block count, underflow accepted */ - if(block + blocks > nfcv_data->block_num) { - blocks = nfcv_data->block_num - block; - } - - /* only respond with the valid blocks, if there are any */ - if(blocks > 0) { - uint8_t buffer_pos = 0; - - ctx->response_buffer[buffer_pos++] = NFCV_NOERROR; - - for(int block_index = 0; block_index < blocks; block_index++) { - int block_current = block + block_index; - /* prepend security status */ - if(ctx->flags & NFCV_REQ_FLAG_OPTION) { - ctx->response_buffer[buffer_pos++] = - nfcv_data->security_status[1 + block_current]; - } - /* then the data block */ - memcpy( - &ctx->response_buffer[buffer_pos], - &nfcv_data->data[nfcv_data->block_size * block_current], - nfcv_data->block_size); - buffer_pos += nfcv_data->block_size; - } - nfcv_emu_send( - tx_rx, - nfcv_data, - ctx->response_buffer, - buffer_pos, - ctx->response_flags, - ctx->send_time); - } else { - /* reply with an error only in addressed or selected mode */ - if(ctx->addressed || ctx->selected) { - ctx->response_buffer[0] = NFCV_RES_FLAG_ERROR; - ctx->response_buffer[1] = NFCV_ERROR_GENERIC; - nfcv_emu_send( - tx_rx, nfcv_data, ctx->response_buffer, 2, ctx->response_flags, ctx->send_time); - } - } - snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "READ BLOCK %d", block); - - break; - } - - case NFCV_CMD_WRITE_MULTI_BLOCK: - case NFCV_CMD_WRITE_BLOCK: { - uint8_t blocks = 1; - uint8_t block = nfcv_data->frame[ctx->payload_offset]; - uint8_t data_pos = ctx->payload_offset + 1; - - if(ctx->command == NFCV_CMD_WRITE_MULTI_BLOCK) { - blocks = nfcv_data->frame[data_pos] + 1; - data_pos++; - } - - uint8_t* data = &nfcv_data->frame[data_pos]; - uint32_t data_len = nfcv_data->block_size * blocks; - - if((block + blocks) <= nfcv_data->block_num && - (data_pos + data_len + 2) == nfcv_data->frame_length) { - ctx->response_buffer[0] = NFCV_NOERROR; - memcpy( - &nfcv_data->data[nfcv_data->block_size * block], - &nfcv_data->frame[data_pos], - data_len); - nfcv_data->modified = true; - - nfcv_emu_send( - tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); - } else { - ctx->response_buffer[0] = NFCV_RES_FLAG_ERROR; - ctx->response_buffer[1] = NFCV_ERROR_GENERIC; - nfcv_emu_send( - tx_rx, nfcv_data, ctx->response_buffer, 2, ctx->response_flags, ctx->send_time); - } - - if(ctx->command == NFCV_CMD_WRITE_MULTI_BLOCK) { - snprintf( - nfcv_data->last_command, - sizeof(nfcv_data->last_command), - "WRITE MULTI BLOCK %d, %d blocks", - block, - blocks); - } else { - snprintf( - nfcv_data->last_command, - sizeof(nfcv_data->last_command), - "WRITE BLOCK %d <- %02X %02X %02X %02X", - block, - data[0], - data[1], - data[2], - data[3]); - } - break; - } - - case NFCV_CMD_GET_SYSTEM_INFO: { - int buffer_pos = 0; - ctx->response_buffer[buffer_pos++] = NFCV_NOERROR; - ctx->response_buffer[buffer_pos++] = NFCV_SYSINFO_FLAG_DSFID | NFCV_SYSINFO_FLAG_AFI | - NFCV_SYSINFO_FLAG_MEMSIZE | NFCV_SYSINFO_FLAG_ICREF; - nfcv_revuidcpy(&ctx->response_buffer[buffer_pos], nfc_data->uid); - buffer_pos += NFCV_UID_LENGTH; - ctx->response_buffer[buffer_pos++] = nfcv_data->dsfid; /* DSFID */ - ctx->response_buffer[buffer_pos++] = nfcv_data->afi; /* AFI */ - ctx->response_buffer[buffer_pos++] = nfcv_data->block_num - 1; /* number of blocks */ - ctx->response_buffer[buffer_pos++] = nfcv_data->block_size - 1; /* block size */ - ctx->response_buffer[buffer_pos++] = nfcv_data->ic_ref; /* IC reference */ - - nfcv_emu_send( - tx_rx, - nfcv_data, - ctx->response_buffer, - buffer_pos, - ctx->response_flags, - ctx->send_time); - snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "SYSTEMINFO"); - break; - } - - case NFCV_CMD_CUST_ECHO_MODE: { - ctx->response_buffer[0] = NFCV_NOERROR; - nfcv_data->echo_mode = true; - nfcv_emu_send( - tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); - snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "ECHO mode"); - break; - } - - case NFCV_CMD_CUST_ECHO_DATA: { - nfcv_emu_send( - tx_rx, - nfcv_data, - &nfcv_data->frame[ctx->payload_offset], - nfcv_data->frame_length - ctx->payload_offset - 2, - NfcVSendFlagsSof | NfcVSendFlagsHighRate | NfcVSendFlagsEof, - ctx->send_time); - snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "ECHO data"); - break; - } - - default: - snprintf( - nfcv_data->last_command, - sizeof(nfcv_data->last_command), - "unsupported: %02X", - ctx->command); - break; - } - - if(strlen(nfcv_data->last_command) > 0) { -#ifdef NFCV_VERBOSE - FURI_LOG_D(TAG, "Received command %s", nfcv_data->last_command); -#endif - } -} - -void nfcv_emu_sniff_packet( - FuriHalNfcTxRxContext* tx_rx, - FuriHalNfcDevData* nfc_data, - void* nfcv_data_in) { - furi_assert(tx_rx); - furi_assert(nfc_data); - furi_assert(nfcv_data_in); - - NfcVData* nfcv_data = (NfcVData*)nfcv_data_in; - NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx; - - if(nfcv_data->frame_length < 2) { - return; - } - - /* parse the frame data for the upcoming part 3 handling */ - ctx->flags = nfcv_data->frame[0]; - ctx->command = nfcv_data->frame[1]; - ctx->selected = (ctx->flags & NFCV_REQ_FLAG_SELECT); - ctx->addressed = !(ctx->flags & NFCV_REQ_FLAG_INVENTORY) && - (ctx->flags & NFCV_REQ_FLAG_ADDRESS); - ctx->advanced = (ctx->command >= NFCV_CMD_ADVANCED); - ctx->address_offset = 2 + (ctx->advanced ? 1 : 0); - ctx->payload_offset = ctx->address_offset + (ctx->addressed ? NFCV_UID_LENGTH : 0); - - char flags_string[5]; - - snprintf( - flags_string, - 5, - "%c%c%c%d", - (ctx->flags & NFCV_REQ_FLAG_INVENTORY) ? - 'I' : - (ctx->addressed ? 'A' : (ctx->selected ? 'S' : '*')), - ctx->advanced ? 'X' : ' ', - (ctx->flags & NFCV_REQ_FLAG_DATA_RATE) ? 'h' : 'l', - (ctx->flags & NFCV_REQ_FLAG_SUB_CARRIER) ? 2 : 1); - - switch(ctx->command) { - case NFCV_CMD_INVENTORY: { - snprintf( - nfcv_data->last_command, sizeof(nfcv_data->last_command), "%s INVENTORY", flags_string); - break; - } - - case NFCV_CMD_STAY_QUIET: { - snprintf( - nfcv_data->last_command, sizeof(nfcv_data->last_command), "%s STAYQUIET", flags_string); - nfcv_data->quiet = true; - break; - } - - case NFCV_CMD_LOCK_BLOCK: { - uint8_t block = nfcv_data->frame[ctx->payload_offset]; - snprintf( - nfcv_data->last_command, - sizeof(nfcv_data->last_command), - "%s LOCK %d", - flags_string, - block); - break; - } - - case NFCV_CMD_WRITE_DSFID: { - uint8_t id = nfcv_data->frame[ctx->payload_offset]; - snprintf( - nfcv_data->last_command, - sizeof(nfcv_data->last_command), - "%s WR DSFID %d", - flags_string, - id); - break; - } - - case NFCV_CMD_WRITE_AFI: { - uint8_t id = nfcv_data->frame[ctx->payload_offset]; - snprintf( - nfcv_data->last_command, - sizeof(nfcv_data->last_command), - "%s WR AFI %d", - flags_string, - id); - break; - } - - case NFCV_CMD_LOCK_DSFID: { - snprintf( - nfcv_data->last_command, - sizeof(nfcv_data->last_command), - "%s LOCK DSFID", - flags_string); - break; - } - - case NFCV_CMD_LOCK_AFI: { - snprintf( - nfcv_data->last_command, sizeof(nfcv_data->last_command), "%s LOCK AFI", flags_string); - break; - } - - case NFCV_CMD_SELECT: { - snprintf( - nfcv_data->last_command, sizeof(nfcv_data->last_command), "%s SELECT", flags_string); - break; - } - - case NFCV_CMD_RESET_TO_READY: { - snprintf( - nfcv_data->last_command, sizeof(nfcv_data->last_command), "%s RESET", flags_string); - break; - } - - case NFCV_CMD_READ_MULTI_BLOCK: - case NFCV_CMD_READ_BLOCK: { - uint8_t block = nfcv_data->frame[ctx->payload_offset]; - uint8_t blocks = 1; - - if(ctx->command == NFCV_CMD_READ_MULTI_BLOCK) { - blocks = nfcv_data->frame[ctx->payload_offset + 1] + 1; - } - - snprintf( - nfcv_data->last_command, - sizeof(nfcv_data->last_command), - "%s READ %d cnt: %d", - flags_string, - block, - blocks); - - break; - } - - case NFCV_CMD_WRITE_MULTI_BLOCK: - case NFCV_CMD_WRITE_BLOCK: { - uint8_t block = nfcv_data->frame[ctx->payload_offset]; - uint8_t blocks = 1; - uint8_t data_pos = 1; - - if(ctx->command == NFCV_CMD_WRITE_MULTI_BLOCK) { - blocks = nfcv_data->frame[ctx->payload_offset + 1] + 1; - data_pos++; - } - - uint8_t* data = &nfcv_data->frame[ctx->payload_offset + data_pos]; - - if(ctx->command == NFCV_CMD_WRITE_MULTI_BLOCK) { - snprintf( - nfcv_data->last_command, - sizeof(nfcv_data->last_command), - "%s WRITE %d, cnd %d", - flags_string, - block, - blocks); - } else { - snprintf( - nfcv_data->last_command, - sizeof(nfcv_data->last_command), - "%s WRITE %d %02X %02X %02X %02X", - flags_string, - block, - data[0], - data[1], - data[2], - data[3]); - } - break; - } - - case NFCV_CMD_GET_SYSTEM_INFO: { - snprintf( - nfcv_data->last_command, - sizeof(nfcv_data->last_command), - "%s SYSTEMINFO", - flags_string); - break; - } - - default: - snprintf( - nfcv_data->last_command, - sizeof(nfcv_data->last_command), - "%s unsupported: %02X", - flags_string, - ctx->command); - break; - } - - if(strlen(nfcv_data->last_command) > 0) { - FURI_LOG_D(TAG, "Received command %s", nfcv_data->last_command); - } -} - -void nfcv_emu_init(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data) { - furi_assert(nfc_data); - furi_assert(nfcv_data); - - if(!nfcv_emu_alloc(nfcv_data)) { - FURI_LOG_E(TAG, "Failed to allocate structures"); - nfcv_data->ready = false; - return; - } - - strcpy(nfcv_data->last_command, ""); - nfcv_data->quiet = false; - nfcv_data->selected = false; - nfcv_data->modified = false; - - /* everything is initialized */ - nfcv_data->ready = true; - - /* ensure the GPIO is already in unmodulated state */ - furi_hal_gpio_init(&gpio_spi_r_mosi, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); - furi_hal_gpio_write(&gpio_spi_r_mosi, GPIO_LEVEL_UNMODULATED); - - rfal_platform_spi_acquire(); - /* stop operation to configure for transparent and passive mode */ - st25r3916ExecuteCommand(ST25R3916_CMD_STOP); - /* set enable, rx_enable and field detector enable */ - st25r3916WriteRegister( - ST25R3916_REG_OP_CONTROL, - ST25R3916_REG_OP_CONTROL_en | ST25R3916_REG_OP_CONTROL_rx_en | - ST25R3916_REG_OP_CONTROL_en_fd_auto_efd); - /* explicitely set the modulation resistor in case system config changes for some reason */ - st25r3916WriteRegister( - ST25R3916_REG_PT_MOD, - (0 << ST25R3916_REG_PT_MOD_ptm_res_shift) | (15 << ST25R3916_REG_PT_MOD_pt_res_shift)); - /* target mode: target, other fields do not have any effect as we use transparent mode */ - st25r3916WriteRegister(ST25R3916_REG_MODE, ST25R3916_REG_MODE_targ); - /* let us modulate the field using MOSI, read ASK modulation using IRQ */ - st25r3916ExecuteCommand(ST25R3916_CMD_TRANSPARENT_MODE); - - furi_hal_spi_bus_handle_deinit(&furi_hal_spi_bus_handle_nfc); - - /* if not set already, initialize the default protocol handler */ - if(!nfcv_data->emu_protocol_ctx) { - nfcv_data->emu_protocol_ctx = malloc(sizeof(NfcVEmuProtocolCtx)); - if(nfcv_data->sub_type == NfcVTypeSniff) { - nfcv_data->emu_protocol_handler = &nfcv_emu_sniff_packet; - } else { - nfcv_data->emu_protocol_handler = &nfcv_emu_handle_packet; - } - } - - FURI_LOG_D(TAG, "Starting NfcV emulation"); - FURI_LOG_D( - TAG, - " UID: %02X %02X %02X %02X %02X %02X %02X %02X", - nfc_data->uid[0], - nfc_data->uid[1], - nfc_data->uid[2], - nfc_data->uid[3], - nfc_data->uid[4], - nfc_data->uid[5], - nfc_data->uid[6], - nfc_data->uid[7]); - - switch(nfcv_data->sub_type) { - case NfcVTypeSlixL: - FURI_LOG_D(TAG, " Card type: SLIX-L"); - slix_l_prepare(nfcv_data); - break; - case NfcVTypeSlixS: - FURI_LOG_D(TAG, " Card type: SLIX-S"); - slix_s_prepare(nfcv_data); - break; - case NfcVTypeSlix2: - FURI_LOG_D(TAG, " Card type: SLIX2"); - slix2_prepare(nfcv_data); - break; - case NfcVTypeSlix: - FURI_LOG_D(TAG, " Card type: SLIX"); - slix_prepare(nfcv_data); - break; - case NfcVTypePlain: - FURI_LOG_D(TAG, " Card type: Plain"); - break; - case NfcVTypeSniff: - FURI_LOG_D(TAG, " Card type: Sniffing"); - break; - } - - /* allocate a 512 edge buffer, more than enough */ - nfcv_data->emu_air.reader_signal = - pulse_reader_alloc(&gpio_nfc_irq_rfid_pull, NFCV_PULSE_BUFFER); - /* timebase shall be 1 ns */ - pulse_reader_set_timebase(nfcv_data->emu_air.reader_signal, PulseReaderUnitNanosecond); - /* and configure to already calculate the number of bits */ - pulse_reader_set_bittime(nfcv_data->emu_air.reader_signal, NFCV_PULSE_DURATION_NS); - /* this IO is fed into the µC via a diode, so we need a pulldown */ - pulse_reader_set_pull(nfcv_data->emu_air.reader_signal, GpioPullDown); - - /* start sampling */ - pulse_reader_start(nfcv_data->emu_air.reader_signal); -} - -void nfcv_emu_deinit(NfcVData* nfcv_data) { - furi_assert(nfcv_data); - - furi_hal_spi_bus_handle_init(&furi_hal_spi_bus_handle_nfc); - nfcv_emu_free(nfcv_data); - - if(nfcv_data->emu_protocol_ctx) { - free(nfcv_data->emu_protocol_ctx); - nfcv_data->emu_protocol_ctx = NULL; - } - - /* set registers back to how we found them */ - st25r3916WriteRegister(ST25R3916_REG_OP_CONTROL, 0x00); - st25r3916WriteRegister(ST25R3916_REG_MODE, 0x08); - rfal_platform_spi_release(); -} - -bool nfcv_emu_loop( - FuriHalNfcTxRxContext* tx_rx, - FuriHalNfcDevData* nfc_data, - NfcVData* nfcv_data, - uint32_t timeout_ms) { - furi_assert(tx_rx); - furi_assert(nfc_data); - furi_assert(nfcv_data); - - bool ret = false; - uint32_t frame_state = NFCV_FRAME_STATE_SOF1; - uint32_t periods_previous = 0; - uint32_t frame_pos = 0; - uint32_t byte_value = 0; - uint32_t bits_received = 0; - uint32_t timeout = timeout_ms * 1000; - bool wait_for_pulse = false; - - if(!nfcv_data->ready) { - return false; - } - -#ifdef NFCV_DIAGNOSTIC_DUMPS - uint8_t period_buffer[NFCV_DIAGNOSTIC_DUMP_SIZE]; - uint32_t period_buffer_pos = 0; -#endif - - while(true) { - uint32_t periods = pulse_reader_receive(nfcv_data->emu_air.reader_signal, timeout); - uint32_t timestamp = DWT->CYCCNT; - - /* when timed out, reset to SOF state */ - if(periods == PULSE_READER_NO_EDGE || periods == PULSE_READER_LOST_EDGE) { - break; - } - -#ifdef NFCV_DIAGNOSTIC_DUMPS - if(period_buffer_pos < sizeof(period_buffer)) { - period_buffer[period_buffer_pos++] = periods; - } -#endif - - /* short helper for detecting a pulse position */ - if(wait_for_pulse) { - wait_for_pulse = false; - if(periods != 1) { - frame_state = NFCV_FRAME_STATE_RESET; - } - continue; - } - - switch(frame_state) { - case NFCV_FRAME_STATE_SOF1: - if(periods == 1) { - frame_state = NFCV_FRAME_STATE_SOF2; - } else { - frame_state = NFCV_FRAME_STATE_SOF1; - break; - } - break; - - case NFCV_FRAME_STATE_SOF2: - /* waiting for the second low period, telling us about coding */ - if(periods == 6) { - frame_state = NFCV_FRAME_STATE_CODING_256; - periods_previous = 0; - wait_for_pulse = true; - } else if(periods == 4) { - frame_state = NFCV_FRAME_STATE_CODING_4; - periods_previous = 2; - wait_for_pulse = true; - } else { - frame_state = NFCV_FRAME_STATE_RESET; - } - break; - - case NFCV_FRAME_STATE_CODING_256: - if(periods_previous > periods) { - frame_state = NFCV_FRAME_STATE_RESET; - break; - } - - /* previous symbol left us with some pulse periods */ - periods -= periods_previous; - - if(periods > 512) { - frame_state = NFCV_FRAME_STATE_RESET; - break; - } else if(periods == 2) { - frame_state = NFCV_FRAME_STATE_EOF; - break; - } - - periods_previous = 512 - (periods + 1); - byte_value = (periods - 1) / 2; - if(frame_pos < NFCV_FRAMESIZE_MAX) { - nfcv_data->frame[frame_pos++] = (uint8_t)byte_value; - } - - wait_for_pulse = true; - - break; - - case NFCV_FRAME_STATE_CODING_4: - if(periods_previous > periods) { - frame_state = NFCV_FRAME_STATE_RESET; - break; - } - - /* previous symbol left us with some pulse periods */ - periods -= periods_previous; - periods_previous = 0; - - byte_value >>= 2; - bits_received += 2; - - if(periods == 1) { - byte_value |= 0x00 << 6; // -V684 - periods_previous = 6; - } else if(periods == 3) { - byte_value |= 0x01 << 6; - periods_previous = 4; - } else if(periods == 5) { - byte_value |= 0x02 << 6; - periods_previous = 2; - } else if(periods == 7) { - byte_value |= 0x03 << 6; - periods_previous = 0; - } else if(periods == 2) { - frame_state = NFCV_FRAME_STATE_EOF; - break; - } else { - frame_state = NFCV_FRAME_STATE_RESET; - break; - } - - if(bits_received >= 8) { - if(frame_pos < NFCV_FRAMESIZE_MAX) { - nfcv_data->frame[frame_pos++] = (uint8_t)byte_value; - } - bits_received = 0; - } - wait_for_pulse = true; - break; - } - - /* post-state-machine cleanup and reset */ - if(frame_state == NFCV_FRAME_STATE_RESET) { - frame_state = NFCV_FRAME_STATE_SOF1; - } else if(frame_state == NFCV_FRAME_STATE_EOF) { - nfcv_data->frame_length = frame_pos; - nfcv_data->eof_timestamp = timestamp; - break; - } - } - - if(frame_state == NFCV_FRAME_STATE_EOF) { - /* we know that this code uses TIM2, so stop pulse reader */ - pulse_reader_stop(nfcv_data->emu_air.reader_signal); - if(tx_rx->sniff_rx) { - tx_rx->sniff_rx(nfcv_data->frame, frame_pos * 8, false, tx_rx->sniff_context); - } - nfcv_data->emu_protocol_handler(tx_rx, nfc_data, nfcv_data); - - pulse_reader_start(nfcv_data->emu_air.reader_signal); - ret = true; - - } -#ifdef NFCV_VERBOSE - else { - if(frame_state != NFCV_FRAME_STATE_SOF1) { - FURI_LOG_T(TAG, "leaving while in state: %lu", frame_state); - } - } -#endif - -#ifdef NFCV_DIAGNOSTIC_DUMPS - if(period_buffer_pos) { - FURI_LOG_T(TAG, "pulses:"); - for(uint32_t pos = 0; pos < period_buffer_pos; pos++) { - FURI_LOG_T(TAG, " #%lu: %u", pos, period_buffer[pos]); - } - } -#endif - - return ret; -} diff --git a/lib/nfc/protocols/nfcv.h b/lib/nfc/protocols/nfcv.h deleted file mode 100644 index e4139de9987..00000000000 --- a/lib/nfc/protocols/nfcv.h +++ /dev/null @@ -1,334 +0,0 @@ -#pragma once - -#include -#include - -#include -#include -#include "nfc_util.h" -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/* true: modulating releases load, false: modulating adds load resistor to field coil */ -#define NFCV_LOAD_MODULATION_POLARITY (false) - -#define NFCV_FC (13560000.0f) /* MHz */ -#define NFCV_RESP_SUBC1_PULSE_32 (1.0f / (NFCV_FC / 32) / 2.0f) /* 1.1799 µs */ -#define NFCV_RESP_SUBC1_UNMOD_256 (256.0f / NFCV_FC) /* 18.8791 µs */ -#define NFCV_PULSE_DURATION_NS (128.0f * 1000000000.0f / NFCV_FC) - -/* ISO/IEC 15693-3:2019(E) 10.4.12: maximum number of blocks is defined as 256 */ -#define NFCV_BLOCKS_MAX 256 -/* ISO/IEC 15693-3:2019(E) 10.4.12: maximum size of blocks is defined as 32 */ -#define NFCV_BLOCKSIZE_MAX 32 -/* the resulting memory size a card can have */ -#define NFCV_MEMSIZE_MAX (NFCV_BLOCKS_MAX * NFCV_BLOCKSIZE_MAX) -/* ISO/IEC 15693-3:2019(E) 7.1b: standard allows up to 8192, the maxium frame length that we are expected to receive/send is less */ -#define NFCV_FRAMESIZE_MAX (1 + NFCV_MEMSIZE_MAX + NFCV_BLOCKS_MAX) - -/* maximum string length for log messages */ -#define NFCV_LOG_STR_LEN 128 -/* maximum of pulses to be buffered by pulse reader */ -#define NFCV_PULSE_BUFFER 512 - -//#define NFCV_DIAGNOSTIC_DUMPS -//#define NFCV_DIAGNOSTIC_DUMP_SIZE 256 -//#define NFCV_VERBOSE - -/* helpers to calculate the send time based on DWT->CYCCNT */ -#define NFCV_FDT_USEC(usec) ((usec)*64) -#define NFCV_FDT_FC(ticks) ((ticks)*6400 / 1356) - -/* state machine when receiving frame bits */ -#define NFCV_FRAME_STATE_SOF1 0 -#define NFCV_FRAME_STATE_SOF2 1 -#define NFCV_FRAME_STATE_CODING_4 2 -#define NFCV_FRAME_STATE_CODING_256 3 -#define NFCV_FRAME_STATE_EOF 4 -#define NFCV_FRAME_STATE_RESET 5 - -/* sequences for every section of a frame */ -#define NFCV_SIG_SOF 0 -#define NFCV_SIG_BIT0 1 -#define NFCV_SIG_BIT1 2 -#define NFCV_SIG_EOF 3 -#define NFCV_SIG_LOW_SOF 4 -#define NFCV_SIG_LOW_BIT0 5 -#define NFCV_SIG_LOW_BIT1 6 -#define NFCV_SIG_LOW_EOF 7 - -/* various constants */ -#define NFCV_COMMAND_RETRIES 5 -#define NFCV_UID_LENGTH 8 - -/* ISO15693 protocol flags */ -typedef enum { - /* ISO15693 protocol flags when INVENTORY is NOT set */ - NFCV_REQ_FLAG_SUB_CARRIER = (1 << 0), - NFCV_REQ_FLAG_DATA_RATE = (1 << 1), - NFCV_REQ_FLAG_INVENTORY = (1 << 2), - NFCV_REQ_FLAG_PROTOCOL_EXT = (1 << 3), - NFCV_REQ_FLAG_SELECT = (1 << 4), - NFCV_REQ_FLAG_ADDRESS = (1 << 5), - NFCV_REQ_FLAG_OPTION = (1 << 6), - /* ISO15693 protocol flags when INVENTORY flag is set */ - NFCV_REQ_FLAG_AFI = (1 << 4), - NFCV_REQ_FLAG_NB_SLOTS = (1 << 5) -} NfcVRequestFlags; - -/* ISO15693 protocol flags */ -typedef enum { - NFCV_RES_FLAG_ERROR = (1 << 0), - NFCV_RES_FLAG_VALIDITY = (1 << 1), - NFCV_RES_FLAG_FINAL = (1 << 2), - NFCV_RES_FLAG_PROTOCOL_EXT = (1 << 3), - NFCV_RES_FLAG_SEC_LEN1 = (1 << 4), - NFCV_RES_FLAG_SEC_LEN2 = (1 << 5), - NFCV_RES_FLAG_WAIT_EXT = (1 << 6), -} NfcVRsponseFlags; - -/* flags for SYSINFO response */ -typedef enum { - NFCV_SYSINFO_FLAG_DSFID = (1 << 0), - NFCV_SYSINFO_FLAG_AFI = (1 << 1), - NFCV_SYSINFO_FLAG_MEMSIZE = (1 << 2), - NFCV_SYSINFO_FLAG_ICREF = (1 << 3) -} NfcVSysinfoFlags; - -/* ISO15693 command codes */ -typedef enum { - /* mandatory command codes */ - NFCV_CMD_INVENTORY = 0x01, - NFCV_CMD_STAY_QUIET = 0x02, - /* optional command codes */ - NFCV_CMD_READ_BLOCK = 0x20, - NFCV_CMD_WRITE_BLOCK = 0x21, - NFCV_CMD_LOCK_BLOCK = 0x22, - NFCV_CMD_READ_MULTI_BLOCK = 0x23, - NFCV_CMD_WRITE_MULTI_BLOCK = 0x24, - NFCV_CMD_SELECT = 0x25, - NFCV_CMD_RESET_TO_READY = 0x26, - NFCV_CMD_WRITE_AFI = 0x27, - NFCV_CMD_LOCK_AFI = 0x28, - NFCV_CMD_WRITE_DSFID = 0x29, - NFCV_CMD_LOCK_DSFID = 0x2A, - NFCV_CMD_GET_SYSTEM_INFO = 0x2B, - NFCV_CMD_READ_MULTI_SECSTATUS = 0x2C, - /* advanced command codes */ - NFCV_CMD_ADVANCED = 0xA0, - /* flipper zero custom command codes */ - NFCV_CMD_CUST_ECHO_MODE = 0xDE, - NFCV_CMD_CUST_ECHO_DATA = 0xDF -} NfcVCommands; - -/* ISO15693 Response error codes */ -typedef enum { - NFCV_NOERROR = 0x00, - NFCV_ERROR_CMD_NOT_SUP = 0x01, // Command not supported - NFCV_ERROR_CMD_NOT_REC = 0x02, // Command not recognized (eg. parameter error) - NFCV_ERROR_CMD_OPTION = 0x03, // Command option not supported - NFCV_ERROR_GENERIC = 0x0F, // No additional Info about this error - NFCV_ERROR_BLOCK_UNAVAILABLE = 0x10, - NFCV_ERROR_BLOCK_LOCKED_ALREADY = 0x11, // cannot lock again - NFCV_ERROR_BLOCK_LOCKED = 0x12, // cannot be changed - NFCV_ERROR_BLOCK_WRITE = 0x13, // Writing was unsuccessful - NFCV_ERROR_BLOCL_WRITELOCK = 0x14 // Locking was unsuccessful -} NfcVErrorcodes; - -typedef enum { - NfcVLockBitDsfid = 1 << 0, - NfcVLockBitAfi = 1 << 1, - NfcVLockBitEas = 1 << 2, - NfcVLockBitPpl = 1 << 3, -} NfcVLockBits; - -typedef enum { - NfcVAuthMethodManual, - NfcVAuthMethodTonieBox, -} NfcVAuthMethod; - -typedef enum { - NfcVTypePlain = 0, - NfcVTypeSlix = 1, - NfcVTypeSlixS = 2, - NfcVTypeSlixL = 3, - NfcVTypeSlix2 = 4, - NfcVTypeSniff = 255, -} NfcVSubtype; - -typedef enum { - NfcVSendFlagsNormal = 0, - NfcVSendFlagsSof = 1 << 0, - NfcVSendFlagsCrc = 1 << 1, - NfcVSendFlagsEof = 1 << 2, - NfcVSendFlagsOneSubcarrier = 0, - NfcVSendFlagsTwoSubcarrier = 1 << 3, - NfcVSendFlagsLowRate = 0, - NfcVSendFlagsHighRate = 1 << 4 -} NfcVSendFlags; - -/* SLIX specific config flags */ -typedef enum { - NfcVSlixDataFlagsNone = 0, - NfcVSlixDataFlagsHasKeyRead = 1 << 0, - NfcVSlixDataFlagsHasKeyWrite = 1 << 1, - NfcVSlixDataFlagsHasKeyPrivacy = 1 << 2, - NfcVSlixDataFlagsHasKeyDestroy = 1 << 3, - NfcVSlixDataFlagsHasKeyEas = 1 << 4, - NfcVSlixDataFlagsValidKeyRead = 1 << 8, - NfcVSlixDataFlagsValidKeyWrite = 1 << 9, - NfcVSlixDataFlagsValidKeyPrivacy = 1 << 10, - NfcVSlixDataFlagsValidKeyDestroy = 1 << 11, - NfcVSlixDataFlagsValidKeyEas = 1 << 12, - NfcVSlixDataFlagsPrivacy = 1 << 16, - NfcVSlixDataFlagsDestroyed = 1 << 17 -} NfcVSlixDataFlags; - -/* abstract the file read/write operations for all SLIX types to reduce duplicated code */ -typedef enum { - SlixFeatureRead = 1 << 0, - SlixFeatureWrite = 1 << 1, - SlixFeaturePrivacy = 1 << 2, - SlixFeatureDestroy = 1 << 3, - SlixFeatureEas = 1 << 4, - SlixFeatureSignature = 1 << 5, - SlixFeatureProtection = 1 << 6, - - SlixFeatureSlix = SlixFeatureEas, - SlixFeatureSlixS = - (SlixFeatureRead | SlixFeatureWrite | SlixFeaturePrivacy | SlixFeatureDestroy | - SlixFeatureEas), - SlixFeatureSlixL = (SlixFeaturePrivacy | SlixFeatureDestroy | SlixFeatureEas), - SlixFeatureSlix2 = - (SlixFeatureRead | SlixFeatureWrite | SlixFeaturePrivacy | SlixFeatureDestroy | - SlixFeatureEas | SlixFeatureSignature | SlixFeatureProtection), -} SlixTypeFeatures; - -typedef struct { - uint32_t flags; - uint8_t key_read[4]; - uint8_t key_write[4]; - uint8_t key_privacy[4]; - uint8_t key_destroy[4]; - uint8_t key_eas[4]; - uint8_t rand[2]; - uint8_t signature[32]; - /* SLIX2 options */ - uint8_t pp_pointer; - uint8_t pp_condition; -} NfcVSlixData; - -typedef union { - NfcVSlixData slix; -} NfcVSubtypeData; - -typedef struct { - DigitalSignal* nfcv_resp_sof; - DigitalSignal* nfcv_resp_one; - DigitalSignal* nfcv_resp_zero; - DigitalSignal* nfcv_resp_eof; -} NfcVEmuAirSignals; - -typedef struct { - PulseReader* reader_signal; - DigitalSignal* nfcv_resp_pulse; /* pulse length, fc/32 */ - DigitalSignal* nfcv_resp_half_pulse; /* half pulse length, fc/32 */ - DigitalSignal* nfcv_resp_unmod; /* unmodulated length 256/fc */ - NfcVEmuAirSignals signals_high; - NfcVEmuAirSignals signals_low; - DigitalSequence* nfcv_signal; -} NfcVEmuAir; - -typedef void (*NfcVEmuProtocolHandler)( - FuriHalNfcTxRxContext* tx_rx, - FuriHalNfcDevData* nfc_data, - void* nfcv_data); -typedef bool (*NfcVEmuProtocolFilter)( - FuriHalNfcTxRxContext* tx_rx, - FuriHalNfcDevData* nfc_data, - void* nfcv_data); - -/* the default ISO15693 handler context */ -typedef struct { - uint8_t flags; /* ISO15693-3 flags of the header as specified */ - uint8_t command; /* ISO15693-3 command at offset 1 as specified */ - bool selected; /* ISO15693-3 flags: selected frame */ - bool addressed; /* ISO15693-3 flags: addressed frame */ - bool advanced; /* ISO15693-3 command: advanced command */ - uint8_t address_offset; /* ISO15693-3 offset of the address in frame, if addressed is set */ - uint8_t payload_offset; /* ISO15693-3 offset of the payload in frame */ - - uint8_t response_buffer[NFCV_FRAMESIZE_MAX]; /* pre-allocated response buffer */ - NfcVSendFlags response_flags; /* flags to use when sending response */ - uint32_t send_time; /* timestamp when to send the response */ - - NfcVEmuProtocolFilter emu_protocol_filter; -} NfcVEmuProtocolCtx; - -typedef struct { - /* common ISO15693 fields, being specified in ISO15693-3 */ - uint8_t dsfid; - uint8_t afi; - uint8_t ic_ref; - uint16_t block_num; - uint8_t block_size; - uint8_t data[NFCV_MEMSIZE_MAX]; - uint8_t security_status[1 + NFCV_BLOCKS_MAX]; - bool selected; - bool quiet; - - bool modified; - bool ready; - bool echo_mode; - - /* specfic variant infos */ - NfcVSubtype sub_type; - NfcVSubtypeData sub_data; - NfcVAuthMethod auth_method; - - /* precalced air level data */ - NfcVEmuAir emu_air; - - uint8_t* frame; /* [NFCV_FRAMESIZE_MAX] ISO15693-2 incoming raw data from air layer */ - uint8_t frame_length; /* ISO15693-2 length of incoming data */ - uint32_t eof_timestamp; /* ISO15693-2 EOF timestamp, read from DWT->CYCCNT */ - - /* handler for the protocol layer as specified in ISO15693-3 */ - NfcVEmuProtocolHandler emu_protocol_handler; - void* emu_protocol_ctx; - /* runtime data */ - char last_command[NFCV_LOG_STR_LEN]; - char error[NFCV_LOG_STR_LEN]; -} NfcVData; - -typedef struct { - uint16_t blocks_to_read; - int16_t blocks_read; -} NfcVReader; - -ReturnCode nfcv_read_blocks(NfcVReader* reader, NfcVData* data); -ReturnCode nfcv_read_sysinfo(FuriHalNfcDevData* nfc_data, NfcVData* data); -ReturnCode nfcv_inventory(uint8_t* uid); -bool nfcv_read_card(NfcVReader* reader, FuriHalNfcDevData* nfc_data, NfcVData* data); - -void nfcv_emu_init(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data); -void nfcv_emu_deinit(NfcVData* nfcv_data); -bool nfcv_emu_loop( - FuriHalNfcTxRxContext* tx_rx, - FuriHalNfcDevData* nfc_data, - NfcVData* nfcv_data, - uint32_t timeout_ms); -void nfcv_emu_send( - FuriHalNfcTxRxContext* tx_rx, - NfcVData* nfcv, - uint8_t* data, - uint8_t length, - NfcVSendFlags flags, - uint32_t send_time); - -#ifdef __cplusplus -} -#endif diff --git a/lib/nfc/protocols/slix.c b/lib/nfc/protocols/slix.c deleted file mode 100644 index 4b15f4b9771..00000000000 --- a/lib/nfc/protocols/slix.c +++ /dev/null @@ -1,784 +0,0 @@ - -#include -#include "nfcv.h" -#include "slix.h" -#include "nfc_util.h" -#include -#include "furi_hal_nfc.h" -#include - -#define TAG "Slix" - -ReturnCode slix2_read_nxp_sysinfo(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data) { - furi_assert(nfc_data); - furi_assert(nfcv_data); - - uint8_t rxBuf[32]; - uint16_t received = 0; - ReturnCode ret = ERR_NONE; - - FURI_LOG_D(TAG, "Read NXP SYSTEM INFORMATION..."); - - for(int tries = 0; tries < NFCV_COMMAND_RETRIES; tries++) { - uint8_t cmd[] = {}; - uint8_t uid[NFCV_UID_LENGTH]; - - /* UID is stored reversed in requests */ - for(int pos = 0; pos < nfc_data->uid_len; pos++) { - uid[pos] = nfc_data->uid[nfc_data->uid_len - 1 - pos]; - } - - ReturnCode ret = rfalNfcvPollerTransceiveReq( - NFCV_CMD_NXP_GET_NXP_SYSTEM_INFORMATION, - RFAL_NFCV_REQ_FLAG_DEFAULT, - NFCV_MANUFACTURER_NXP, - uid, - cmd, - sizeof(cmd), - rxBuf, - sizeof(rxBuf), - &received); - - if(ret == ERR_NONE) { - break; - } - } - - if(ret != ERR_NONE || received != 8) { //-V560 - FURI_LOG_D(TAG, "Failed: %d, %d", ret, received); - return ret; - } - FURI_LOG_D(TAG, "Success..."); - - NfcVSlixData* slix = &nfcv_data->sub_data.slix; - slix->pp_pointer = rxBuf[1]; - slix->pp_condition = rxBuf[2]; - - /* convert NXP's to our internal lock bits format */ - nfcv_data->security_status[0] = 0; - nfcv_data->security_status[0] |= (rxBuf[3] & SlixLockBitDsfid) ? NfcVLockBitDsfid : 0; - nfcv_data->security_status[0] |= (rxBuf[3] & SlixLockBitAfi) ? NfcVLockBitAfi : 0; - nfcv_data->security_status[0] |= (rxBuf[3] & SlixLockBitEas) ? NfcVLockBitEas : 0; - nfcv_data->security_status[0] |= (rxBuf[3] & SlixLockBitPpl) ? NfcVLockBitPpl : 0; - - return ERR_NONE; -} - -ReturnCode slix2_read_signature(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data) { - furi_assert(nfc_data); - furi_assert(nfcv_data); - - uint8_t rxBuf[64]; - uint16_t received = 0; - ReturnCode ret = ERR_NONE; - - FURI_LOG_D(TAG, "Read SIGNATURE..."); - - for(int tries = 0; tries < NFCV_COMMAND_RETRIES; tries++) { - uint8_t cmd[] = {}; - uint8_t uid[NFCV_UID_LENGTH]; - - /* UID is stored reversed in requests */ - for(int pos = 0; pos < nfc_data->uid_len; pos++) { - uid[pos] = nfc_data->uid[nfc_data->uid_len - 1 - pos]; - } - - ReturnCode ret = rfalNfcvPollerTransceiveReq( - NFCV_CMD_NXP_READ_SIGNATURE, - RFAL_NFCV_REQ_FLAG_DEFAULT, - NFCV_MANUFACTURER_NXP, - uid, - cmd, - sizeof(cmd), - rxBuf, - sizeof(rxBuf), - &received); - - if(ret == ERR_NONE) { - break; - } - } - - if(ret != ERR_NONE || received != 33) { //-V560 - FURI_LOG_D(TAG, "Failed: %d, %d", ret, received); - return ret; - } - FURI_LOG_D(TAG, "Success..."); - - NfcVSlixData* slix = &nfcv_data->sub_data.slix; - memcpy(slix->signature, &rxBuf[1], 32); - - return ERR_NONE; -} - -ReturnCode slix2_read_custom(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data) { - ReturnCode ret = ERR_NONE; - - ret = slix2_read_nxp_sysinfo(nfc_data, nfcv_data); - if(ret != ERR_NONE) { - return ret; - } - ret = slix2_read_signature(nfc_data, nfcv_data); - - return ret; -} - -static uint32_t slix_read_be(uint8_t* data, uint32_t length) { - uint32_t value = 0; - - for(uint32_t pos = 0; pos < length; pos++) { - value <<= 8; - value |= data[pos]; - } - - return value; -} - -uint8_t slix_get_ti(FuriHalNfcDevData* nfc_data) { - return (nfc_data->uid[3] >> 3) & 3; -} - -bool slix_check_card_type(FuriHalNfcDevData* nfc_data) { - if((nfc_data->uid[0] == 0xE0) && (nfc_data->uid[1] == 0x04) && (nfc_data->uid[2] == 0x01) && - slix_get_ti(nfc_data) == 2) { - return true; - } - return false; -} - -bool slix2_check_card_type(FuriHalNfcDevData* nfc_data) { - if((nfc_data->uid[0] == 0xE0) && (nfc_data->uid[1] == 0x04) && (nfc_data->uid[2] == 0x01) && - slix_get_ti(nfc_data) == 1) { - return true; - } - return false; -} - -bool slix_s_check_card_type(FuriHalNfcDevData* nfc_data) { - if((nfc_data->uid[0] == 0xE0) && (nfc_data->uid[1] == 0x04) && (nfc_data->uid[2] == 0x02)) { - return true; - } - return false; -} - -bool slix_l_check_card_type(FuriHalNfcDevData* nfc_data) { - if((nfc_data->uid[0] == 0xE0) && (nfc_data->uid[1] == 0x04) && (nfc_data->uid[2] == 0x03)) { - return true; - } - return false; -} - -ReturnCode slix_get_random(NfcVData* data) { - uint16_t received = 0; - uint8_t rxBuf[32]; - - ReturnCode ret = rfalNfcvPollerTransceiveReq( - NFCV_CMD_NXP_GET_RANDOM_NUMBER, - RFAL_NFCV_REQ_FLAG_DEFAULT, - NFCV_MANUFACTURER_NXP, - NULL, - NULL, - 0, - rxBuf, - sizeof(rxBuf), - &received); - - if(ret == ERR_NONE) { - if(received != 3) { - return ERR_PROTO; - } - if(data != NULL) { - data->sub_data.slix.rand[0] = rxBuf[2]; - data->sub_data.slix.rand[1] = rxBuf[1]; - } - } - - return ret; -} - -ReturnCode slix_unlock(NfcVData* data, uint32_t password_id) { - furi_assert(data); - - uint16_t received = 0; - uint8_t rxBuf[32]; - uint8_t cmd_set_pass[] = { - password_id, - data->sub_data.slix.rand[1], - data->sub_data.slix.rand[0], - data->sub_data.slix.rand[1], - data->sub_data.slix.rand[0]}; - uint8_t* password = NULL; - - switch(password_id) { - case SLIX_PASS_READ: - password = data->sub_data.slix.key_read; - break; - case SLIX_PASS_WRITE: - password = data->sub_data.slix.key_write; - break; - case SLIX_PASS_PRIVACY: - password = data->sub_data.slix.key_privacy; - break; - case SLIX_PASS_DESTROY: - password = data->sub_data.slix.key_destroy; - break; - case SLIX_PASS_EASAFI: - password = data->sub_data.slix.key_eas; - break; - default: - break; - } - - if(!password) { - return ERR_NOTSUPP; - } - - for(int pos = 0; pos < 4; pos++) { - cmd_set_pass[1 + pos] ^= password[3 - pos]; - } - - ReturnCode ret = rfalNfcvPollerTransceiveReq( - NFCV_CMD_NXP_SET_PASSWORD, - RFAL_NFCV_REQ_FLAG_DATA_RATE, - NFCV_MANUFACTURER_NXP, - NULL, - cmd_set_pass, - sizeof(cmd_set_pass), - rxBuf, - sizeof(rxBuf), - &received); - - return ret; -} - -static void slix_generic_pass_infos( - uint8_t password_id, - NfcVSlixData* slix, - uint8_t** password, - uint32_t* flag_valid, - uint32_t* flag_set) { - switch(password_id) { - case SLIX_PASS_READ: - *password = slix->key_read; - *flag_valid = NfcVSlixDataFlagsValidKeyRead; - *flag_set = NfcVSlixDataFlagsHasKeyRead; - break; - case SLIX_PASS_WRITE: - *password = slix->key_write; - *flag_valid = NfcVSlixDataFlagsValidKeyWrite; - *flag_set = NfcVSlixDataFlagsHasKeyWrite; - break; - case SLIX_PASS_PRIVACY: - *password = slix->key_privacy; - *flag_valid = NfcVSlixDataFlagsValidKeyPrivacy; - *flag_set = NfcVSlixDataFlagsHasKeyPrivacy; - break; - case SLIX_PASS_DESTROY: - *password = slix->key_destroy; - *flag_valid = NfcVSlixDataFlagsValidKeyDestroy; - *flag_set = NfcVSlixDataFlagsHasKeyDestroy; - break; - case SLIX_PASS_EASAFI: - *password = slix->key_eas; - *flag_valid = NfcVSlixDataFlagsValidKeyEas; - *flag_set = NfcVSlixDataFlagsHasKeyEas; - break; - default: - break; - } -} - -bool slix_generic_protocol_filter( - FuriHalNfcTxRxContext* tx_rx, - FuriHalNfcDevData* nfc_data, - void* nfcv_data_in, - uint32_t password_supported) { - furi_assert(tx_rx); - furi_assert(nfc_data); - furi_assert(nfcv_data_in); - - NfcVData* nfcv_data = (NfcVData*)nfcv_data_in; - NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx; - NfcVSlixData* slix = &nfcv_data->sub_data.slix; - - if((slix->flags & NfcVSlixDataFlagsPrivacy) && - ctx->command != NFCV_CMD_NXP_GET_RANDOM_NUMBER && - ctx->command != NFCV_CMD_NXP_SET_PASSWORD) { - snprintf( - nfcv_data->last_command, - sizeof(nfcv_data->last_command), - "command 0x%02X ignored, privacy mode", - ctx->command); - FURI_LOG_D(TAG, "%s", nfcv_data->last_command); - return true; - } - - bool handled = false; - - switch(ctx->command) { - case NFCV_CMD_NXP_GET_RANDOM_NUMBER: { - slix->rand[0] = furi_hal_random_get(); - slix->rand[1] = furi_hal_random_get(); - - ctx->response_buffer[0] = NFCV_NOERROR; - ctx->response_buffer[1] = slix->rand[1]; - ctx->response_buffer[2] = slix->rand[0]; - - nfcv_emu_send( - tx_rx, nfcv_data, ctx->response_buffer, 3, ctx->response_flags, ctx->send_time); - snprintf( - nfcv_data->last_command, - sizeof(nfcv_data->last_command), - "GET_RANDOM_NUMBER -> 0x%02X%02X", - slix->rand[0], - slix->rand[1]); - - handled = true; - break; - } - - case NFCV_CMD_NXP_SET_PASSWORD: { - /* the password to be set is the first parameter */ - uint8_t password_id = nfcv_data->frame[ctx->payload_offset]; - /* right after that is the XORed password */ - uint8_t* password_xored = &nfcv_data->frame[ctx->payload_offset + 1]; - - /* only handle if the password type is supported */ - if(!(password_id & password_supported)) { - break; - } - - /* fetch the last RAND value */ - uint8_t* rand = slix->rand; - - /* first calc the password that has been sent */ - uint8_t password_rcv[4]; - for(int pos = 0; pos < 4; pos++) { - password_rcv[pos] = password_xored[3 - pos] ^ rand[pos % 2]; - } - uint32_t pass_received = slix_read_be(password_rcv, 4); - - /* then determine the password type (or even update if not set yet) */ - uint8_t* password = NULL; - uint32_t flag_valid = 0; - uint32_t flag_set = 0; - - slix_generic_pass_infos(password_id, slix, &password, &flag_valid, &flag_set); - - /* when the password is not supported, return silently */ - if(!password) { - break; - } - - /* check if the password is known */ - bool pass_valid = false; - uint32_t pass_expect = 0; - - if(slix->flags & flag_set) { - /* if so, fetch the stored password and compare */ - pass_expect = slix_read_be(password, 4); - pass_valid = (pass_expect == pass_received); - } else { - /* if not known, just accept it and store that password */ - memcpy(password, password_rcv, 4); - nfcv_data->modified = true; - slix->flags |= flag_set; - - pass_valid = true; - } - - /* if the pass was valid or accepted for other reasons, continue */ - if(pass_valid) { - slix->flags |= flag_valid; - - /* handle actions when a correct password was given, aside of setting the flag */ - switch(password_id) { - case SLIX_PASS_PRIVACY: - slix->flags &= ~NfcVSlixDataFlagsPrivacy; - nfcv_data->modified = true; - break; - case SLIX_PASS_DESTROY: - slix->flags |= NfcVSlixDataFlagsDestroyed; - FURI_LOG_D(TAG, "Pooof! Got destroyed"); - break; - default: - break; - } - - ctx->response_buffer[0] = NFCV_NOERROR; - nfcv_emu_send( - tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); - snprintf( - nfcv_data->last_command, - sizeof(nfcv_data->last_command), - "SET_PASSWORD #%02X 0x%08lX OK", - password_id, - pass_received); - } else { - snprintf( - nfcv_data->last_command, - sizeof(nfcv_data->last_command), - "SET_PASSWORD #%02X 0x%08lX/%08lX FAIL", - password_id, - pass_received, - pass_expect); - } - handled = true; - break; - } - - case NFCV_CMD_NXP_WRITE_PASSWORD: { - uint8_t password_id = nfcv_data->frame[ctx->payload_offset]; - - if(!(password_id & password_supported)) { - break; - } - - uint8_t* new_password = &nfcv_data->frame[ctx->payload_offset + 1]; - uint8_t* password = NULL; - uint32_t flag_valid = 0; - uint32_t flag_set = 0; - - slix_generic_pass_infos(password_id, slix, &password, &flag_valid, &flag_set); - - /* when the password is not supported, return silently */ - if(!password) { - break; - } - - bool pass_valid = (slix->flags & flag_valid); - if(!(slix->flags & flag_set)) { - pass_valid = true; - } - - if(pass_valid) { - slix->flags |= flag_valid; - slix->flags |= flag_set; - - memcpy(password, new_password, 4); - - ctx->response_buffer[0] = NFCV_NOERROR; - nfcv_emu_send( - tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); - snprintf( - nfcv_data->last_command, sizeof(nfcv_data->last_command), "WRITE_PASSWORD OK"); - } else { - snprintf( - nfcv_data->last_command, sizeof(nfcv_data->last_command), "WRITE_PASSWORD FAIL"); - } - handled = true; - break; - } - - case NFCV_CMD_NXP_ENABLE_PRIVACY: { - ctx->response_buffer[0] = NFCV_NOERROR; - - nfcv_emu_send( - tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); - snprintf( - nfcv_data->last_command, - sizeof(nfcv_data->last_command), - "NFCV_CMD_NXP_ENABLE_PRIVACY"); - - slix->flags |= NfcVSlixDataFlagsPrivacy; - handled = true; - break; - } - } - - return handled; -} - -bool slix_l_protocol_filter( - FuriHalNfcTxRxContext* tx_rx, - FuriHalNfcDevData* nfc_data, - void* nfcv_data_in) { - furi_assert(tx_rx); - furi_assert(nfc_data); - furi_assert(nfcv_data_in); - - bool handled = false; - - /* many SLIX share some of the functions, place that in a generic handler */ - if(slix_generic_protocol_filter( - tx_rx, - nfc_data, - nfcv_data_in, - SLIX_PASS_PRIVACY | SLIX_PASS_DESTROY | SLIX_PASS_EASAFI)) { - return true; - } - - return handled; -} - -void slix_l_prepare(NfcVData* nfcv_data) { - FURI_LOG_D( - TAG, " Privacy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_privacy, 4)); - FURI_LOG_D( - TAG, " Destroy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_destroy, 4)); - FURI_LOG_D(TAG, " EAS pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_eas, 4)); - FURI_LOG_D( - TAG, - " Privacy mode: %s", - (nfcv_data->sub_data.slix.flags & NfcVSlixDataFlagsPrivacy) ? "ON" : "OFF"); - - NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx; - ctx->emu_protocol_filter = &slix_l_protocol_filter; -} - -bool slix_s_protocol_filter( - FuriHalNfcTxRxContext* tx_rx, - FuriHalNfcDevData* nfc_data, - void* nfcv_data_in) { - furi_assert(tx_rx); - furi_assert(nfc_data); - furi_assert(nfcv_data_in); - - bool handled = false; - - /* many SLIX share some of the functions, place that in a generic handler */ - if(slix_generic_protocol_filter(tx_rx, nfc_data, nfcv_data_in, SLIX_PASS_ALL)) { - return true; - } - - return handled; -} - -void slix_s_prepare(NfcVData* nfcv_data) { - FURI_LOG_D( - TAG, " Privacy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_privacy, 4)); - FURI_LOG_D( - TAG, " Destroy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_destroy, 4)); - FURI_LOG_D(TAG, " EAS pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_eas, 4)); - FURI_LOG_D( - TAG, - " Privacy mode: %s", - (nfcv_data->sub_data.slix.flags & NfcVSlixDataFlagsPrivacy) ? "ON" : "OFF"); - - NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx; - ctx->emu_protocol_filter = &slix_s_protocol_filter; -} - -bool slix_protocol_filter( - FuriHalNfcTxRxContext* tx_rx, - FuriHalNfcDevData* nfc_data, - void* nfcv_data_in) { - furi_assert(tx_rx); - furi_assert(nfc_data); - furi_assert(nfcv_data_in); - - bool handled = false; - - /* many SLIX share some of the functions, place that in a generic handler */ - if(slix_generic_protocol_filter(tx_rx, nfc_data, nfcv_data_in, SLIX_PASS_EASAFI)) { - return true; - } - - return handled; -} - -void slix_prepare(NfcVData* nfcv_data) { - FURI_LOG_D( - TAG, " Privacy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_privacy, 4)); - FURI_LOG_D( - TAG, " Destroy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_destroy, 4)); - FURI_LOG_D(TAG, " EAS pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_eas, 4)); - FURI_LOG_D( - TAG, - " Privacy mode: %s", - (nfcv_data->sub_data.slix.flags & NfcVSlixDataFlagsPrivacy) ? "ON" : "OFF"); - - NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx; - ctx->emu_protocol_filter = &slix_protocol_filter; -} - -bool slix2_protocol_filter( // -V524 - FuriHalNfcTxRxContext* tx_rx, - FuriHalNfcDevData* nfc_data, - void* nfcv_data_in) { - furi_assert(tx_rx); - furi_assert(nfc_data); - furi_assert(nfcv_data_in); - - NfcVData* nfcv_data = (NfcVData*)nfcv_data_in; - NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx; - NfcVSlixData* slix = &nfcv_data->sub_data.slix; - - bool handled = false; - - /* many SLIX share some of the functions, place that in a generic handler */ - if(slix_generic_protocol_filter(tx_rx, nfc_data, nfcv_data_in, SLIX_PASS_ALL)) { - return true; - } - - switch(ctx->command) { - /* override WRITE BLOCK for block 79 (16 bit counter) */ - case NFCV_CMD_WRITE_BLOCK: - case NFCV_CMD_WRITE_MULTI_BLOCK: { - uint8_t resp_len = 1; - uint8_t blocks = 1; - uint8_t block = nfcv_data->frame[ctx->payload_offset]; - uint8_t data_pos = ctx->payload_offset + 1; - - if(ctx->command == NFCV_CMD_WRITE_MULTI_BLOCK) { - blocks = nfcv_data->frame[data_pos] + 1; - data_pos++; - } - - uint8_t* data = &nfcv_data->frame[data_pos]; - uint32_t data_len = nfcv_data->block_size * blocks; - - if((block + blocks) <= nfcv_data->block_num && - (data_pos + data_len + 2) == nfcv_data->frame_length) { - ctx->response_buffer[0] = NFCV_NOERROR; - - for(int block_num = block; block_num < block + blocks; block_num++) { - /* special case, 16-bit counter */ - if(block_num == 79) { - uint32_t dest; - uint32_t ctr_old; - - memcpy(&dest, &nfcv_data->frame[data_pos], 4); - memcpy(&ctr_old, &nfcv_data->data[nfcv_data->block_size * block_num], 4); - - uint32_t ctr_new = ctr_old; - bool allowed = true; - - /* increment counter */ - if(dest == 1) { - ctr_new = (ctr_old & 0xFFFF0000) | ((ctr_old + 1) & 0xFFFF); - - /* protection flag set? */ - if(ctr_old & 0x01000000) { //-V1051 - allowed = nfcv_data->sub_data.slix.flags & - NfcVSlixDataFlagsValidKeyRead; - } - } else { - ctr_new = dest; - allowed = nfcv_data->sub_data.slix.flags & NfcVSlixDataFlagsValidKeyWrite; - } - - if(allowed) { - memcpy( //-V1086 - &nfcv_data->data[nfcv_data->block_size * block_num], - &ctr_new, - 4); - } else { - /* incorrect read or write password */ - ctx->response_buffer[0] = NFCV_RES_FLAG_ERROR; - ctx->response_buffer[1] = NFCV_ERROR_GENERIC; - resp_len = 2; - } - } else { - memcpy( - &nfcv_data->data[nfcv_data->block_size * block_num], - &nfcv_data->frame[data_pos], - nfcv_data->block_size); - } - data_pos += nfcv_data->block_size; - } - nfcv_data->modified = true; - - } else { - ctx->response_buffer[0] = NFCV_RES_FLAG_ERROR; - ctx->response_buffer[1] = NFCV_ERROR_GENERIC; - resp_len = 2; - } - - bool respond = (ctx->response_buffer[0] == NFCV_NOERROR) || - (ctx->addressed || ctx->selected); - - if(respond) { - nfcv_emu_send( - tx_rx, - nfcv_data, - ctx->response_buffer, - resp_len, - ctx->response_flags, - ctx->send_time); - } - - if(ctx->command == NFCV_CMD_WRITE_MULTI_BLOCK) { - snprintf( - nfcv_data->last_command, - sizeof(nfcv_data->last_command), - "WRITE MULTI BLOCK %d, %d blocks", - block, - blocks); - } else { - snprintf( - nfcv_data->last_command, - sizeof(nfcv_data->last_command), - "WRITE BLOCK %d <- %02X %02X %02X %02X", - block, - data[0], - data[1], - data[2], - data[3]); - } - handled = true; - break; - } - - case NFCV_CMD_NXP_READ_SIGNATURE: { - uint32_t len = 0; - ctx->response_buffer[len++] = NFCV_NOERROR; - memcpy(&ctx->response_buffer[len], slix->signature, sizeof(slix->signature)); - len += sizeof(slix->signature); - - nfcv_emu_send( - tx_rx, nfcv_data, ctx->response_buffer, len, ctx->response_flags, ctx->send_time); - snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "READ_SIGNATURE"); - - handled = true; - break; - } - - case NFCV_CMD_NXP_GET_NXP_SYSTEM_INFORMATION: { - uint32_t len = 0; - uint8_t lock_bits = 0; - - /* convert our internal lock bits format into NXP's */ - lock_bits |= (nfcv_data->security_status[0] & NfcVLockBitDsfid) ? SlixLockBitDsfid : 0; - lock_bits |= (nfcv_data->security_status[0] & NfcVLockBitAfi) ? SlixLockBitAfi : 0; - lock_bits |= (nfcv_data->security_status[0] & NfcVLockBitEas) ? SlixLockBitEas : 0; - lock_bits |= (nfcv_data->security_status[0] & NfcVLockBitPpl) ? SlixLockBitPpl : 0; - - ctx->response_buffer[len++] = NFCV_NOERROR; - ctx->response_buffer[len++] = nfcv_data->sub_data.slix.pp_pointer; - ctx->response_buffer[len++] = nfcv_data->sub_data.slix.pp_condition; - ctx->response_buffer[len++] = lock_bits; - ctx->response_buffer[len++] = 0x7F; /* features LSB */ - ctx->response_buffer[len++] = 0x35; /* features */ - ctx->response_buffer[len++] = 0; /* features */ - ctx->response_buffer[len++] = 0; /* features MSB */ - - nfcv_emu_send( - tx_rx, nfcv_data, ctx->response_buffer, len, ctx->response_flags, ctx->send_time); - snprintf( - nfcv_data->last_command, - sizeof(nfcv_data->last_command), - "GET_NXP_SYSTEM_INFORMATION"); - - handled = true; - break; - } - } - - return handled; -} - -void slix2_prepare(NfcVData* nfcv_data) { - FURI_LOG_D( - TAG, " Privacy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_privacy, 4)); - FURI_LOG_D( - TAG, " Destroy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_destroy, 4)); - FURI_LOG_D(TAG, " EAS pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_eas, 4)); - FURI_LOG_D( - TAG, - " Privacy mode: %s", - (nfcv_data->sub_data.slix.flags & NfcVSlixDataFlagsPrivacy) ? "ON" : "OFF"); - - NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx; - ctx->emu_protocol_filter = &slix2_protocol_filter; -} diff --git a/lib/nfc/protocols/slix.h b/lib/nfc/protocols/slix.h deleted file mode 100644 index 67f09e46d6f..00000000000 --- a/lib/nfc/protocols/slix.h +++ /dev/null @@ -1,66 +0,0 @@ -#pragma once - -#include -#include -#include "nfc_util.h" -#include - -#define NFCV_MANUFACTURER_NXP 0x04 - -/* ISO15693-3 CUSTOM NXP COMMANDS */ -typedef enum { - NFCV_CMD_NXP_SET_EAS = 0xA2, - NFCV_CMD_NXP_RESET_EAS = 0xA3, - NFCV_CMD_NXP_LOCK_EAS = 0xA4, - NFCV_CMD_NXP_EAS_ALARM = 0xA5, - NFCV_CMD_NXP_PASSWORD_PROTECT_EAS_AFI = 0xA6, - NFCV_CMD_NXP_WRITE_EAS_ID = 0xA7, - NFCV_CMD_NXP_GET_NXP_SYSTEM_INFORMATION = 0xAB, - NFCV_CMD_NXP_INVENTORY_PAGE_READ = 0xB0, - NFCV_CMD_NXP_INVENTORY_PAGE_READ_FAST = 0xB1, - NFCV_CMD_NXP_GET_RANDOM_NUMBER = 0xB2, - NFCV_CMD_NXP_SET_PASSWORD = 0xB3, - NFCV_CMD_NXP_WRITE_PASSWORD = 0xB4, - NFCV_CMD_NXP_64_BIT_PASSWORD_PROTECTION = 0xB5, - NFCV_CMD_NXP_PROTECT_PAGE = 0xB6, - NFCV_CMD_NXP_LOCK_PAGE_PROTECTION_CONDITION = 0xB7, - NFCV_CMD_NXP_DESTROY = 0xB9, - NFCV_CMD_NXP_ENABLE_PRIVACY = 0xBA, - NFCV_CMD_NXP_STAY_QUIET_PERSISTENT = 0xBC, - NFCV_CMD_NXP_READ_SIGNATURE = 0xBD -} SlixCommands; - -/* lock bit bits used in SLIX's NXP SYSTEM INFORMATION response */ -typedef enum { - SlixLockBitAfi = 1 << 0, - SlixLockBitEas = 1 << 1, - SlixLockBitDsfid = 1 << 2, - SlixLockBitPpl = 1 << 3, -} SlixLockBits; - -/* available passwords */ -#define SLIX_PASS_READ 0x01 -#define SLIX_PASS_WRITE 0x02 -#define SLIX_PASS_PRIVACY 0x04 -#define SLIX_PASS_DESTROY 0x08 -#define SLIX_PASS_EASAFI 0x10 - -#define SLIX_PASS_ALL \ - (SLIX_PASS_READ | SLIX_PASS_WRITE | SLIX_PASS_PRIVACY | SLIX_PASS_DESTROY | SLIX_PASS_EASAFI) - -bool slix_check_card_type(FuriHalNfcDevData* nfc_data); -bool slix2_check_card_type(FuriHalNfcDevData* nfc_data); -bool slix_s_check_card_type(FuriHalNfcDevData* nfc_data); -bool slix_l_check_card_type(FuriHalNfcDevData* nfc_data); - -ReturnCode slix2_read_custom(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data); -ReturnCode slix2_read_signature(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data); -ReturnCode slix2_read_nxp_sysinfo(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data); - -ReturnCode slix_get_random(NfcVData* data); -ReturnCode slix_unlock(NfcVData* data, uint32_t password_id); - -void slix_prepare(NfcVData* nfcv_data); -void slix_s_prepare(NfcVData* nfcv_data); -void slix_l_prepare(NfcVData* nfcv_data); -void slix2_prepare(NfcVData* nfcv_data); diff --git a/lib/nfc/protocols/slix/slix.c b/lib/nfc/protocols/slix/slix.c new file mode 100644 index 00000000000..12dc6750dd1 --- /dev/null +++ b/lib/nfc/protocols/slix/slix.c @@ -0,0 +1,433 @@ +#include "slix_i.h" +#include "slix_device_defs.h" + +#include +#include + +#define SLIX_PROTOCOL_NAME "SLIX" +#define SLIX_DEVICE_NAME "SLIX" + +#define SLIX_TYPE_SLIX_SLIX2 (0x01U) +#define SLIX_TYPE_SLIX_S (0x02U) +#define SLIX_TYPE_SLIX_L (0x03U) + +#define SLIX_TYPE_INDICATOR_SLIX (0x02U) +#define SLIX_TYPE_INDICATOR_SLIX2 (0x01U) + +#define SLIX_PASSWORD_READ_KEY "Password Read" +#define SLIX_PASSWORD_WRITE_KEY "Password Write" +#define SLIX_PASSWORD_PRIVACY_KEY "Password Privacy" +#define SLIX_PASSWORD_DESTROY_KEY "Password Destroy" +#define SLIX_PASSWORD_EAS_KEY "Password EAS" +#define SLIX_SIGNATURE_KEY "Signature" +#define SLIX_PRIVACY_MODE_KEY "Privacy Mode" +#define SLIX_PROTECTION_POINTER_KEY "Protection Pointer" +#define SLIX_PROTECTION_CONDITION_KEY "Protection Condition" +#define SLIX_LOCK_EAS_KEY "Lock EAS" +#define SLIX_LOCK_PPL_KEY "Lock PPL" + +typedef struct { + uint8_t iso15693_3[2]; + uint8_t icode_type; + union { + struct { + uint8_t unused_1 : 3; + uint8_t type_indicator : 2; + uint8_t unused_2 : 3; + }; + uint8_t serial_num[5]; + }; +} SlixUidLayout; + +const NfcDeviceBase nfc_device_slix = { + .protocol_name = SLIX_PROTOCOL_NAME, + .alloc = (NfcDeviceAlloc)slix_alloc, + .free = (NfcDeviceFree)slix_free, + .reset = (NfcDeviceReset)slix_reset, + .copy = (NfcDeviceCopy)slix_copy, + .verify = (NfcDeviceVerify)slix_verify, + .load = (NfcDeviceLoad)slix_load, + .save = (NfcDeviceSave)slix_save, + .is_equal = (NfcDeviceEqual)slix_is_equal, + .get_name = (NfcDeviceGetName)slix_get_device_name, + .get_uid = (NfcDeviceGetUid)slix_get_uid, + .set_uid = (NfcDeviceSetUid)slix_set_uid, + .get_base_data = (NfcDeviceGetBaseData)slix_get_base_data, +}; + +static const char* slix_nfc_device_name[] = { + [SlixTypeSlix] = SLIX_DEVICE_NAME, + [SlixTypeSlixS] = SLIX_DEVICE_NAME "-S", + [SlixTypeSlixL] = SLIX_DEVICE_NAME "-L", + [SlixTypeSlix2] = SLIX_DEVICE_NAME "2", +}; + +static const SlixTypeFeatures slix_type_features[] = { + [SlixTypeSlix] = SLIX_TYPE_FEATURES_SLIX, + [SlixTypeSlixS] = SLIX_TYPE_FEATURES_SLIX_S, + [SlixTypeSlixL] = SLIX_TYPE_FEATURES_SLIX_L, + [SlixTypeSlix2] = SLIX_TYPE_FEATURES_SLIX2, +}; + +typedef struct { + const char* key; + SlixTypeFeatures feature_flag; + SlixPassword default_value; +} SlixPasswordConfig; + +static const SlixPasswordConfig slix_password_configs[] = { + [SlixPasswordTypeRead] = {SLIX_PASSWORD_READ_KEY, SLIX_TYPE_FEATURE_READ, 0x00000000U}, + [SlixPasswordTypeWrite] = {SLIX_PASSWORD_WRITE_KEY, SLIX_TYPE_FEATURE_WRITE, 0x00000000U}, + [SlixPasswordTypePrivacy] = {SLIX_PASSWORD_PRIVACY_KEY, SLIX_TYPE_FEATURE_PRIVACY, 0xFFFFFFFFU}, + [SlixPasswordTypeDestroy] = {SLIX_PASSWORD_DESTROY_KEY, SLIX_TYPE_FEATURE_DESTROY, 0xFFFFFFFFU}, + [SlixPasswordTypeEasAfi] = {SLIX_PASSWORD_EAS_KEY, SLIX_TYPE_FEATURE_EAS, 0x00000000U}, +}; + +static void slix_password_set_defaults(SlixPassword* passwords) { + for(uint32_t i = 0; i < COUNT_OF(slix_password_configs); ++i) { + passwords[i] = slix_password_configs[i].default_value; + } +} + +SlixData* slix_alloc() { + SlixData* data = malloc(sizeof(SlixData)); + + data->iso15693_3_data = iso15693_3_alloc(); + slix_password_set_defaults(data->passwords); + + return data; +} + +void slix_free(SlixData* data) { + furi_assert(data); + + iso15693_3_free(data->iso15693_3_data); + + free(data); +} + +void slix_reset(SlixData* data) { + furi_assert(data); + + iso15693_3_reset(data->iso15693_3_data); + slix_password_set_defaults(data->passwords); + + memset(&data->system_info, 0, sizeof(SlixSystemInfo)); + memset(data->signature, 0, sizeof(SlixSignature)); + + data->privacy = false; +} + +void slix_copy(SlixData* data, const SlixData* other) { + furi_assert(data); + furi_assert(other); + + iso15693_3_copy(data->iso15693_3_data, other->iso15693_3_data); + + memcpy(data->passwords, other->passwords, sizeof(SlixPassword) * SlixPasswordTypeCount); + memcpy(data->signature, other->signature, sizeof(SlixSignature)); + + data->system_info = other->system_info; + data->privacy = other->privacy; +} + +bool slix_verify(SlixData* data, const FuriString* device_type) { + UNUSED(data); + UNUSED(device_type); + // No backward compatibility, unified format only + return false; +} + +static bool slix_load_passwords(SlixPassword* passwords, SlixType slix_type, FlipperFormat* ff) { + bool ret = true; + + for(uint32_t i = 0; i < COUNT_OF(slix_password_configs); ++i) { + const SlixPasswordConfig* password_config = &slix_password_configs[i]; + + if(!slix_type_has_features(slix_type, password_config->feature_flag)) continue; + if(!flipper_format_key_exist(ff, password_config->key)) { + passwords[i] = password_config->default_value; + continue; + } + if(!flipper_format_read_hex( + ff, password_config->key, (uint8_t*)&passwords[i], sizeof(SlixPassword))) { + ret = false; + break; + } + } + + return ret; +} + +bool slix_load(SlixData* data, FlipperFormat* ff, uint32_t version) { + furi_assert(data); + + bool loaded = false; + + do { + if(!iso15693_3_load(data->iso15693_3_data, ff, version)) break; + + const SlixType slix_type = slix_get_type(data); + if(slix_type >= SlixTypeCount) break; + + if(!slix_load_passwords(data->passwords, slix_type, ff)) break; + + if(slix_type_has_features(slix_type, SLIX_TYPE_FEATURE_SIGNATURE)) { + if(flipper_format_key_exist(ff, SLIX_SIGNATURE_KEY)) { + if(!flipper_format_read_hex( + ff, SLIX_SIGNATURE_KEY, data->signature, SLIX_SIGNATURE_SIZE)) + break; + } + } + + if(slix_type_has_features(slix_type, SLIX_TYPE_FEATURE_PRIVACY)) { + if(flipper_format_key_exist(ff, SLIX_PRIVACY_MODE_KEY)) { + if(!flipper_format_read_bool(ff, SLIX_PRIVACY_MODE_KEY, &data->privacy, 1)) break; + } + } + + if(slix_type_has_features(slix_type, SLIX_TYPE_FEATURE_PROTECTION)) { + SlixProtection* protection = &data->system_info.protection; + if(flipper_format_key_exist(ff, SLIX_PROTECTION_POINTER_KEY) && + flipper_format_key_exist(ff, SLIX_PROTECTION_CONDITION_KEY)) { + if(!flipper_format_read_hex( + ff, SLIX_PROTECTION_POINTER_KEY, &protection->pointer, 1)) + break; + if(!flipper_format_read_hex( + ff, SLIX_PROTECTION_CONDITION_KEY, &protection->condition, 1)) + break; + } + } + + if(slix_type_has_features(slix_type, SLIX_TYPE_FEATURE_EAS)) { + if(flipper_format_key_exist(ff, SLIX_LOCK_EAS_KEY)) { + SlixLockBits* lock_bits = &data->system_info.lock_bits; + if(!flipper_format_read_bool(ff, SLIX_LOCK_EAS_KEY, &lock_bits->eas, 1)) break; + } + } + + if(slix_type_has_features(slix_type, SLIX_TYPE_FEATURE_PROTECTION)) { + if(flipper_format_key_exist(ff, SLIX_LOCK_PPL_KEY)) { + SlixLockBits* lock_bits = &data->system_info.lock_bits; + if(!flipper_format_read_bool(ff, SLIX_LOCK_PPL_KEY, &lock_bits->ppl, 1)) break; + } + } + + loaded = true; + } while(false); + + return loaded; +} + +static bool + slix_save_passwords(const SlixPassword* passwords, SlixType slix_type, FlipperFormat* ff) { + bool ret = true; + + for(uint32_t i = 0; i < COUNT_OF(slix_password_configs); ++i) { + const SlixPasswordConfig* password_config = &slix_password_configs[i]; + + if(!slix_type_has_features(slix_type, password_config->feature_flag)) continue; + if(!flipper_format_write_hex( + ff, password_config->key, (uint8_t*)&passwords[i], sizeof(SlixPassword))) { + ret = false; + break; + } + } + + return ret; +} + +bool slix_save(const SlixData* data, FlipperFormat* ff) { + furi_assert(data); + + bool saved = false; + + do { + const SlixType slix_type = slix_get_type(data); + if(slix_type >= SlixTypeCount) break; + + if(!iso15693_3_save(data->iso15693_3_data, ff)) break; + if(!flipper_format_write_comment_cstr(ff, SLIX_PROTOCOL_NAME " specific data")) break; + + if(!flipper_format_write_comment_cstr( + ff, + "Passwords are optional. If a password is omitted, a default value will be used")) + break; + + if(!slix_save_passwords(data->passwords, slix_type, ff)) break; + + if(slix_type_has_features(slix_type, SLIX_TYPE_FEATURE_SIGNATURE)) { + if(!flipper_format_write_comment_cstr( + ff, + "This is the card's secp128r1 elliptic curve signature. It can not be calculated without knowing NXP's private key.")) + break; + if(!flipper_format_write_hex( + ff, SLIX_SIGNATURE_KEY, data->signature, SLIX_SIGNATURE_SIZE)) + break; + } + + if(slix_type_has_features(slix_type, SLIX_TYPE_FEATURE_PRIVACY)) { + if(!flipper_format_write_bool(ff, SLIX_PRIVACY_MODE_KEY, &data->privacy, 1)) break; + } + + if(slix_type_has_features(slix_type, SLIX_TYPE_FEATURE_PROTECTION)) { + const SlixProtection* protection = &data->system_info.protection; + if(!flipper_format_write_comment_cstr(ff, "Protection pointer configuration")) break; + if(!flipper_format_write_hex( + ff, SLIX_PROTECTION_POINTER_KEY, &protection->pointer, sizeof(uint8_t))) + break; + if(!flipper_format_write_hex( + ff, SLIX_PROTECTION_CONDITION_KEY, &protection->condition, sizeof(uint8_t))) + break; + } + + if(slix_type_has_features(slix_type, SLIX_TYPE_FEATURE_EAS) || + slix_type_has_features(slix_type, SLIX_TYPE_FEATURE_PROTECTION)) { + if(!flipper_format_write_comment_cstr(ff, "SLIX Lock Bits")) break; + } + + if(slix_type_has_features(slix_type, SLIX_TYPE_FEATURE_EAS)) { + const SlixLockBits* lock_bits = &data->system_info.lock_bits; + if(!flipper_format_write_bool(ff, SLIX_LOCK_EAS_KEY, &lock_bits->eas, 1)) break; + } + + if(slix_type_has_features(slix_type, SLIX_TYPE_FEATURE_PROTECTION)) { + const SlixLockBits* lock_bits = &data->system_info.lock_bits; + if(!flipper_format_write_bool(ff, SLIX_LOCK_PPL_KEY, &lock_bits->ppl, 1)) break; + } + + saved = true; + } while(false); + + return saved; +} + +bool slix_is_equal(const SlixData* data, const SlixData* other) { + return iso15693_3_is_equal(data->iso15693_3_data, other->iso15693_3_data) && + memcmp(&data->system_info, &other->system_info, sizeof(SlixSystemInfo)) == 0 && + memcmp( + data->passwords, other->passwords, sizeof(SlixPassword) * SlixPasswordTypeCount) == + 0 && + memcmp(&data->signature, &other->signature, sizeof(SlixSignature)) == 0 && + data->privacy == other->privacy; +} + +const char* slix_get_device_name(const SlixData* data, NfcDeviceNameType name_type) { + UNUSED(name_type); + + const SlixType slix_type = slix_get_type(data); + furi_assert(slix_type < SlixTypeCount); + + return slix_nfc_device_name[slix_type]; +} + +const uint8_t* slix_get_uid(const SlixData* data, size_t* uid_len) { + return iso15693_3_get_uid(data->iso15693_3_data, uid_len); +} + +bool slix_set_uid(SlixData* data, const uint8_t* uid, size_t uid_len) { + furi_assert(data); + + return iso15693_3_set_uid(data->iso15693_3_data, uid, uid_len); +} + +const Iso15693_3Data* slix_get_base_data(const SlixData* data) { + furi_assert(data); + + return data->iso15693_3_data; +} + +SlixType slix_get_type(const SlixData* data) { + SlixType type = SlixTypeCount; + + do { + if(iso15693_3_get_manufacturer_id(data->iso15693_3_data) != SLIX_NXP_MANUFACTURER_CODE) + break; + + const SlixUidLayout* uid = (const SlixUidLayout*)data->iso15693_3_data->uid; + + if(uid->icode_type == SLIX_TYPE_SLIX_SLIX2) { + if(uid->type_indicator == SLIX_TYPE_INDICATOR_SLIX) { + type = SlixTypeSlix; + } else if(uid->type_indicator == SLIX_TYPE_INDICATOR_SLIX2) { + type = SlixTypeSlix2; + } + } else if(uid->icode_type == SLIX_TYPE_SLIX_S) { + type = SlixTypeSlixS; + } else if(uid->icode_type == SLIX_TYPE_SLIX_L) { + type = SlixTypeSlixL; + } + + } while(false); + + return type; +} + +SlixPassword slix_get_password(const SlixData* data, SlixPasswordType password_type) { + furi_assert(data); + furi_assert(password_type < SlixPasswordTypeCount); + + return data->passwords[password_type]; +} + +uint16_t slix_get_counter(const SlixData* data) { + furi_assert(data); + const SlixCounter* counter = (const SlixCounter*)iso15693_3_get_block_data( + data->iso15693_3_data, SLIX_COUNTER_BLOCK_NUM); + + return counter->value; +} + +bool slix_is_privacy_mode(const SlixData* data) { + furi_assert(data); + + return data->privacy; +} + +bool slix_is_block_protected( + const SlixData* data, + SlixPasswordType password_type, + uint8_t block_num) { + furi_assert(data); + furi_assert(password_type < SlixPasswordTypeCount); + + bool ret = false; + + do { + if(password_type != SlixPasswordTypeRead && password_type != SlixPasswordTypeWrite) break; + if(block_num >= iso15693_3_get_block_count(data->iso15693_3_data)) break; + if(block_num == SLIX_COUNTER_BLOCK_NUM) break; + + const bool high = block_num >= data->system_info.protection.pointer; + const bool read = password_type == SlixPasswordTypeRead; + + const uint8_t condition = high ? (read ? SLIX_PP_CONDITION_RH : SLIX_PP_CONDITION_WH) : + (read ? SLIX_PP_CONDITION_RL : SLIX_PP_CONDITION_WL); + + ret = data->system_info.protection.condition & condition; + } while(false); + + return ret; +} + +bool slix_is_counter_increment_protected(const SlixData* data) { + furi_assert(data); + + const SlixCounter* counter = (const SlixCounter*)iso15693_3_get_block_data( + data->iso15693_3_data, SLIX_COUNTER_BLOCK_NUM); + + return counter->protection != 0; +} + +bool slix_type_has_features(SlixType slix_type, SlixTypeFeatures features) { + furi_assert(slix_type < SlixTypeCount); + + return (slix_type_features[slix_type] & features) == features; +} + +bool slix_type_supports_password(SlixType slix_type, SlixPasswordType password_type) { + furi_assert(slix_type < SlixTypeCount); + furi_assert(password_type < SlixPasswordTypeCount); + + return slix_type_features[slix_type] & slix_password_configs[password_type].feature_flag; +} diff --git a/lib/nfc/protocols/slix/slix.h b/lib/nfc/protocols/slix/slix.h new file mode 100644 index 00000000000..f6c1453c5dd --- /dev/null +++ b/lib/nfc/protocols/slix/slix.h @@ -0,0 +1,146 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define SLIX_BLOCK_SIZE (4U) +#define SLIX_SIGNATURE_SIZE (32U) + +#define SLIX_COUNTER_BLOCK_NUM (79U) + +#define SLIX_PP_CONDITION_RL (1U << 0) +#define SLIX_PP_CONDITION_WL (1U << 1) +#define SLIX_PP_CONDITION_RH (1U << 4) +#define SLIX_PP_CONDITION_WH (1U << 5) + +#define SLIX_FEATURE_FLAG_UM_PP (1UL << 0) +#define SLIX_FEATURE_FLAG_COUNTER (1UL << 1) +#define SLIX_FEATURE_FLAG_EAS_ID (1UL << 2) +#define SLIX_FEATURE_FLAG_EAS_PP (1UL << 3) +#define SLIX_FEATURE_FLAG_AFI_PP (1UL << 4) +#define SLIX_FEATURE_FLAG_INVENTORY_READ_EXT (1UL << 5) +#define SLIX_FEATURE_FLAG_EAS_IR (1UL << 6) +#define SLIX_FEATURE_FLAG_ORIGINALITY_SIG (1UL << 8) +#define SLIX_FEATURE_FLAG_ORIGINALITY_SIG_PP (1UL << 9) +#define SLIX_FEATURE_FLAG_PERSISTENT_QUIET (1UL << 10) +#define SLIX_FEATURE_FLAG_PRIVACY (1UL << 12) +#define SLIX_FEATURE_FLAG_DESTROY (1UL << 13) +#define SLIX_FEATURE_EXT (1UL << 31) + +#define SLIX_TYPE_FEATURE_READ (1U << 0) +#define SLIX_TYPE_FEATURE_WRITE (1U << 1) +#define SLIX_TYPE_FEATURE_PRIVACY (1U << 2) +#define SLIX_TYPE_FEATURE_DESTROY (1U << 3) +#define SLIX_TYPE_FEATURE_EAS (1U << 4) +#define SLIX_TYPE_FEATURE_SIGNATURE (1U << 5) +#define SLIX_TYPE_FEATURE_PROTECTION (1U << 6) + +typedef uint32_t SlixTypeFeatures; + +typedef enum { + SlixErrorNone, + SlixErrorTimeout, + SlixErrorFormat, + SlixErrorNotSupported, + SlixErrorInternal, + SlixErrorWrongPassword, + SlixErrorUidMismatch, + SlixErrorUnknown, +} SlixError; + +typedef enum { + SlixTypeSlix, + SlixTypeSlixS, + SlixTypeSlixL, + SlixTypeSlix2, + SlixTypeCount, +} SlixType; + +typedef enum { + SlixPasswordTypeRead, + SlixPasswordTypeWrite, + SlixPasswordTypePrivacy, + SlixPasswordTypeDestroy, + SlixPasswordTypeEasAfi, + SlixPasswordTypeCount, +} SlixPasswordType; + +typedef uint32_t SlixPassword; +typedef uint8_t SlixSignature[SLIX_SIGNATURE_SIZE]; +typedef bool SlixPrivacy; + +typedef struct { + uint8_t pointer; + uint8_t condition; +} SlixProtection; + +typedef struct { + bool eas; + bool ppl; +} SlixLockBits; + +typedef struct { + SlixProtection protection; + SlixLockBits lock_bits; +} SlixSystemInfo; + +typedef struct { + Iso15693_3Data* iso15693_3_data; + SlixSystemInfo system_info; + SlixSignature signature; + SlixPassword passwords[SlixPasswordTypeCount]; + SlixPrivacy privacy; +} SlixData; + +SlixData* slix_alloc(); + +void slix_free(SlixData* data); + +void slix_reset(SlixData* data); + +void slix_copy(SlixData* data, const SlixData* other); + +bool slix_verify(SlixData* data, const FuriString* device_type); + +bool slix_load(SlixData* data, FlipperFormat* ff, uint32_t version); + +bool slix_save(const SlixData* data, FlipperFormat* ff); + +bool slix_is_equal(const SlixData* data, const SlixData* other); + +const char* slix_get_device_name(const SlixData* data, NfcDeviceNameType name_type); + +const uint8_t* slix_get_uid(const SlixData* data, size_t* uid_len); + +bool slix_set_uid(SlixData* data, const uint8_t* uid, size_t uid_len); + +const Iso15693_3Data* slix_get_base_data(const SlixData* data); + +// Getters and tests + +SlixType slix_get_type(const SlixData* data); + +SlixPassword slix_get_password(const SlixData* data, SlixPasswordType password_type); + +uint16_t slix_get_counter(const SlixData* data); + +bool slix_is_privacy_mode(const SlixData* data); + +bool slix_is_block_protected( + const SlixData* data, + SlixPasswordType password_type, + uint8_t block_num); + +bool slix_is_counter_increment_protected(const SlixData* data); + +// Static methods +bool slix_type_has_features(SlixType slix_type, SlixTypeFeatures features); + +bool slix_type_supports_password(SlixType slix_type, SlixPasswordType password_type); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/slix/slix_device_defs.h b/lib/nfc/protocols/slix/slix_device_defs.h new file mode 100644 index 00000000000..cadd40eecd7 --- /dev/null +++ b/lib/nfc/protocols/slix/slix_device_defs.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +extern const NfcDeviceBase nfc_device_slix; diff --git a/lib/nfc/protocols/slix/slix_i.c b/lib/nfc/protocols/slix/slix_i.c new file mode 100644 index 00000000000..97d66484c01 --- /dev/null +++ b/lib/nfc/protocols/slix/slix_i.c @@ -0,0 +1,127 @@ +#include "slix_i.h" + +#include + +bool slix_error_response_parse(SlixError* error, const BitBuffer* buf) { + Iso15693_3Error iso15693_3_error; + const bool ret = iso15693_3_error_response_parse(&iso15693_3_error, buf); + + if(ret) { + *error = slix_process_iso15693_3_error(iso15693_3_error); + } + + return ret; +} + +SlixError slix_process_iso15693_3_error(Iso15693_3Error iso15693_3_error) { + switch(iso15693_3_error) { + case Iso15693_3ErrorNone: + return SlixErrorNone; + case Iso15693_3ErrorTimeout: + return SlixErrorTimeout; + case Iso15693_3ErrorFormat: + return SlixErrorFormat; + case Iso15693_3ErrorInternal: + return SlixErrorInternal; + default: + return SlixErrorUnknown; + } +} + +SlixError slix_get_nxp_system_info_response_parse(SlixData* data, const BitBuffer* buf) { + furi_assert(data); + SlixError error = SlixErrorNone; + + do { + if(slix_error_response_parse(&error, buf)) break; + + typedef struct { + uint8_t flags; + uint8_t pp_pointer; + uint8_t pp_condition; + uint8_t lock_bits; + uint32_t feature_flags; + } SlixGetNxpSystemInfoResponseLayout; + + const size_t size_received = bit_buffer_get_size_bytes(buf); + const size_t size_required = sizeof(SlixGetNxpSystemInfoResponseLayout); + + if(size_received != size_required) { + error = SlixErrorFormat; + break; + } + + const SlixGetNxpSystemInfoResponseLayout* response = + (const SlixGetNxpSystemInfoResponseLayout*)bit_buffer_get_data(buf); + + SlixProtection* protection = &data->system_info.protection; + protection->pointer = response->pp_pointer; + protection->condition = response->pp_condition; + + Iso15693_3LockBits* iso15693_3_lock_bits = &data->iso15693_3_data->settings.lock_bits; + iso15693_3_lock_bits->dsfid = response->lock_bits & SLIX_LOCK_BITS_DSFID; + iso15693_3_lock_bits->afi = response->lock_bits & SLIX_LOCK_BITS_AFI; + + SlixLockBits* slix_lock_bits = &data->system_info.lock_bits; + slix_lock_bits->eas = response->lock_bits & SLIX_LOCK_BITS_EAS; + slix_lock_bits->ppl = response->lock_bits & SLIX_LOCK_BITS_PPL; + + } while(false); + + return error; +} + +SlixError slix_read_signature_response_parse(SlixSignature data, const BitBuffer* buf) { + SlixError error = SlixErrorNone; + + do { + if(slix_error_response_parse(&error, buf)) break; + + typedef struct { + uint8_t flags; + uint8_t signature[SLIX_SIGNATURE_SIZE]; + } SlixReadSignatureResponseLayout; + + const size_t size_received = bit_buffer_get_size_bytes(buf); + const size_t size_required = sizeof(SlixReadSignatureResponseLayout); + + if(size_received != size_required) { + error = SlixErrorFormat; + break; + } + + const SlixReadSignatureResponseLayout* response = + (const SlixReadSignatureResponseLayout*)bit_buffer_get_data(buf); + + memcpy(data, response->signature, sizeof(SlixSignature)); + } while(false); + + return error; +} + +void slix_set_password(SlixData* data, SlixPasswordType password_type, SlixPassword password) { + furi_assert(data); + furi_assert(password_type < SlixPasswordTypeCount); + + data->passwords[password_type] = password; +} + +void slix_set_privacy_mode(SlixData* data, bool set) { + furi_assert(data); + + data->privacy = set; +} + +void slix_increment_counter(SlixData* data) { + furi_assert(data); + + const uint8_t* block_data = + iso15693_3_get_block_data(data->iso15693_3_data, SLIX_COUNTER_BLOCK_NUM); + + SlixCounter counter; + memcpy(counter.bytes, block_data, SLIX_BLOCK_SIZE); + counter.value += 1; + + iso15693_3_set_block_data( + data->iso15693_3_data, SLIX_COUNTER_BLOCK_NUM, counter.bytes, sizeof(SlixCounter)); +} diff --git a/lib/nfc/protocols/slix/slix_i.h b/lib/nfc/protocols/slix/slix_i.h new file mode 100644 index 00000000000..b5e445f31d0 --- /dev/null +++ b/lib/nfc/protocols/slix/slix_i.h @@ -0,0 +1,86 @@ +#pragma once + +#include "slix.h" + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define SLIX_NXP_MANUFACTURER_CODE (0x04U) + +#define SLIX_LOCK_BITS_AFI (1U << 0) +#define SLIX_LOCK_BITS_EAS (1U << 1) +#define SLIX_LOCK_BITS_DSFID (1U << 2) +#define SLIX_LOCK_BITS_PPL (1U << 3) + +#define SLIX_CMD_CUSTOM_START (0xA2U) +#define SLIX_CMD_SET_EAS (0xA2U) +#define SLIX_CMD_RESET_EAS (0xA3U) +#define SLIX_CMD_LOCK_EAS (0xA4U) +#define SLIX_CMD_EAS_ALARM (0xA5U) +#define SLIX_CMD_PASSWORD_PROTECT_EAS_AFI (0xA6U) +#define SLIX_CMD_WRITE_EAS_ID (0xA7U) +#define SLIX_CMD_GET_NXP_SYSTEM_INFORMATION (0xABU) +#define SLIX_CMD_INVENTORY_PAGE_READ (0xB0U) +#define SLIX_CMD_INVENTORY_PAGE_READ_FAST (0xB1U) +#define SLIX_CMD_GET_RANDOM_NUMBER (0xB2U) +#define SLIX_CMD_SET_PASSWORD (0xB3U) +#define SLIX_CMD_WRITE_PASSWORD (0xB4U) +#define SLIX_CMD_64_BIT_PASSWORD_PROTECTION (0xB5U) +#define SLIX_CMD_PROTECT_PAGE (0xB6U) +#define SLIX_CMD_LOCK_PAGE_PROTECTION_CONDITION (0xB7U) +#define SLIX_CMD_DESTROY (0xB9U) +#define SLIX_CMD_ENABLE_PRIVACY (0xBAU) +#define SLIX_CMD_STAY_QUIET_PERSISTENT (0xBCU) +#define SLIX_CMD_READ_SIGNATURE (0xBDU) +#define SLIX_CMD_CUSTOM_END (0xBEU) +#define SLIX_CMD_CUSTOM_COUNT (SLIX_CMD_CUSTOM_END - SLIX_CMD_CUSTOM_START) + +#define SLIX_TYPE_FEATURES_SLIX (SLIX_TYPE_FEATURE_EAS) +#define SLIX_TYPE_FEATURES_SLIX_S \ + (SLIX_TYPE_FEATURE_READ | SLIX_TYPE_FEATURE_WRITE | SLIX_TYPE_FEATURE_PRIVACY | \ + SLIX_TYPE_FEATURE_DESTROY | SLIX_TYPE_FEATURE_EAS) +#define SLIX_TYPE_FEATURES_SLIX_L \ + (SLIX_TYPE_FEATURE_PRIVACY | SLIX_TYPE_FEATURE_DESTROY | SLIX_TYPE_FEATURE_EAS) +#define SLIX_TYPE_FEATURES_SLIX2 \ + (SLIX_TYPE_FEATURE_READ | SLIX_TYPE_FEATURE_WRITE | SLIX_TYPE_FEATURE_PRIVACY | \ + SLIX_TYPE_FEATURE_DESTROY | SLIX_TYPE_FEATURE_EAS | SLIX_TYPE_FEATURE_SIGNATURE | \ + SLIX_TYPE_FEATURE_PROTECTION) + +#define SLIX2_FEATURE_FLAGS \ + (SLIX_FEATURE_FLAG_UM_PP | SLIX_FEATURE_FLAG_COUNTER | SLIX_FEATURE_FLAG_EAS_ID | \ + SLIX_FEATURE_FLAG_EAS_PP | SLIX_FEATURE_FLAG_AFI_PP | SLIX_FEATURE_FLAG_INVENTORY_READ_EXT | \ + SLIX_FEATURE_FLAG_EAS_IR | SLIX_FEATURE_FLAG_ORIGINALITY_SIG | \ + SLIX_FEATURE_FLAG_PERSISTENT_QUIET | SLIX_FEATURE_FLAG_PRIVACY | SLIX_FEATURE_FLAG_DESTROY) + +typedef union { + struct { + uint16_t value; + uint8_t reserved; + uint8_t protection; + }; + uint8_t bytes[SLIX_BLOCK_SIZE]; +} SlixCounter; + +// Same behaviour as iso15693_3_error_response_parse +bool slix_error_response_parse(SlixError* error, const BitBuffer* buf); + +SlixError slix_process_iso15693_3_error(Iso15693_3Error iso15693_3_error); + +SlixError slix_get_nxp_system_info_response_parse(SlixData* data, const BitBuffer* buf); + +SlixError slix_read_signature_response_parse(SlixSignature data, const BitBuffer* buf); + +// Setters +void slix_set_password(SlixData* data, SlixPasswordType password_type, SlixPassword password); + +void slix_set_privacy_mode(SlixData* data, bool set); + +void slix_increment_counter(SlixData* data); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/slix/slix_listener.c b/lib/nfc/protocols/slix/slix_listener.c new file mode 100644 index 00000000000..204be5ab91a --- /dev/null +++ b/lib/nfc/protocols/slix/slix_listener.c @@ -0,0 +1,79 @@ +#include "slix_listener_i.h" + +#include +#include + +#define TAG "SlixListener" + +#define SLIX_LISTENER_BUF_SIZE (64U) + +static SlixListener* slix_listener_alloc(Iso15693_3Listener* iso15693_3_listener, SlixData* data) { + furi_assert(iso15693_3_listener); + + SlixListener* instance = malloc(sizeof(SlixListener)); + instance->iso15693_3_listener = iso15693_3_listener; + instance->data = data; + + instance->tx_buffer = bit_buffer_alloc(SLIX_LISTENER_BUF_SIZE); + + instance->slix_event.data = &instance->slix_event_data; + instance->generic_event.protocol = NfcProtocolSlix; + instance->generic_event.instance = instance; + instance->generic_event.event_data = &instance->slix_event; + + slix_listener_init_iso15693_3_extensions(instance); + + return instance; +} + +static void slix_listener_free(SlixListener* instance) { + furi_assert(instance); + furi_assert(instance->data); + furi_assert(instance->tx_buffer); + + bit_buffer_free(instance->tx_buffer); + free(instance); +} + +static void + slix_listener_set_callback(SlixListener* instance, NfcGenericCallback callback, void* context) { + furi_assert(instance); + + instance->callback = callback; + instance->context = context; +} + +static const SlixData* slix_listener_get_data(SlixListener* instance) { + furi_assert(instance); + furi_assert(instance->data); + + return instance->data; +} + +static NfcCommand slix_listener_run(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.protocol == NfcProtocolIso15693_3); + furi_assert(event.event_data); + + SlixListener* instance = context; + Iso15693_3ListenerEvent* iso15693_3_event = event.event_data; + BitBuffer* rx_buffer = iso15693_3_event->data->buffer; + NfcCommand command = NfcCommandContinue; + + if(iso15693_3_event->type == Iso15693_3ListenerEventTypeCustomCommand) { + const SlixError error = slix_listener_process_request(instance, rx_buffer); + if(error == SlixErrorWrongPassword) { + command = NfcCommandStop; + } + } + + return command; +} + +const NfcListenerBase nfc_listener_slix = { + .alloc = (NfcListenerAlloc)slix_listener_alloc, + .free = (NfcListenerFree)slix_listener_free, + .set_callback = (NfcListenerSetCallback)slix_listener_set_callback, + .get_data = (NfcListenerGetData)slix_listener_get_data, + .run = (NfcListenerRun)slix_listener_run, +}; diff --git a/lib/nfc/protocols/slix/slix_listener.h b/lib/nfc/protocols/slix/slix_listener.h new file mode 100644 index 00000000000..7b8518a5bb7 --- /dev/null +++ b/lib/nfc/protocols/slix/slix_listener.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +#include "slix.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct SlixListener SlixListener; + +typedef enum { + SlixListenerEventTypeFieldOff, + SlixListenerEventTypeCustomCommand, +} SlixListenerEventType; + +typedef struct { + BitBuffer* buffer; +} SlixListenerEventData; + +typedef struct { + SlixListenerEventType type; + SlixListenerEventData* data; +} SlixListenerEvent; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/slix/slix_listener_defs.h b/lib/nfc/protocols/slix/slix_listener_defs.h new file mode 100644 index 00000000000..0598090c473 --- /dev/null +++ b/lib/nfc/protocols/slix/slix_listener_defs.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +extern const NfcListenerBase nfc_listener_slix; diff --git a/lib/nfc/protocols/slix/slix_listener_i.c b/lib/nfc/protocols/slix/slix_listener_i.c new file mode 100644 index 00000000000..dfcb6c88017 --- /dev/null +++ b/lib/nfc/protocols/slix/slix_listener_i.c @@ -0,0 +1,617 @@ +#include "slix_listener_i.h" + +#include + +#include + +#define TAG "SlixListener" + +typedef SlixError (*SlixRequestHandler)( + SlixListener* instance, + const uint8_t* data, + size_t data_size, + uint8_t flags); + +// Helper functions + +static bool + slix_listener_is_password_lock_enabled(SlixListener* instance, SlixPasswordType password_type) { + return !instance->session_state.password_match[password_type]; +} + +static SlixPasswordType slix_listener_get_password_type_by_id(uint8_t id) { + uint32_t type; + + for(type = 0; type < SlixPasswordTypeCount; ++type) { + if(id >> type == 0x01U) break; + } + + return type; +} + +static SlixPassword + slix_listener_unxor_password(const SlixPassword password_xored, uint16_t random) { + return password_xored ^ ((SlixPassword)random << 16 | random); +} + +static SlixError slix_listener_set_password( + SlixListener* instance, + SlixPasswordType password_type, + SlixPassword password) { + SlixError error = SlixErrorNone; + + do { + if(password_type >= SlixPasswordTypeCount) { + error = SlixErrorInternal; + break; + } + + SlixData* slix_data = instance->data; + + if(!slix_type_supports_password(slix_get_type(slix_data), password_type)) { + error = SlixErrorNotSupported; + break; + } + + SlixListenerSessionState* session_state = &instance->session_state; + session_state->password_match[password_type] = + (password == slix_get_password(slix_data, password_type)); + + if(!session_state->password_match[password_type]) { + error = SlixErrorWrongPassword; + break; + } + } while(false); + + return error; +} + +static SlixError slix_listener_write_password( + SlixListener* instance, + SlixPasswordType password_type, + SlixPassword password) { + SlixError error = SlixErrorNone; + + do { + if(password_type >= SlixPasswordTypeCount) { + error = SlixErrorInternal; + break; + } + + SlixData* slix_data = instance->data; + + if(!slix_type_supports_password(slix_get_type(slix_data), password_type)) { + error = SlixErrorNotSupported; + break; + } + + SlixListenerSessionState* session_state = &instance->session_state; + + if(session_state->password_match[password_type]) { + // TODO FL-3634: check for password lock + slix_set_password(slix_data, password_type, password); + // Require another SET_PASSWORD command with the new password + session_state->password_match[password_type] = false; + } else { + error = SlixErrorWrongPassword; + break; + } + } while(false); + + return error; +} + +// Custom SLIX request handlers +static SlixError slix_listener_default_handler( + SlixListener* instance, + const uint8_t* data, + size_t data_size, + uint8_t flags) { + UNUSED(instance); + UNUSED(data); + UNUSED(data_size); + UNUSED(flags); + + // Empty placeholder handler + return SlixErrorNotSupported; +} + +static SlixError slix_listener_get_nxp_system_info_handler( + SlixListener* instance, + const uint8_t* data, + size_t data_size, + uint8_t flags) { + UNUSED(data); + UNUSED(data_size); + UNUSED(flags); + + const SlixData* slix_data = instance->data; + const Iso15693_3Data* iso15693_data = instance->data->iso15693_3_data; + + const SlixProtection* protection = &slix_data->system_info.protection; + bit_buffer_append_byte(instance->tx_buffer, protection->pointer); + bit_buffer_append_byte(instance->tx_buffer, protection->condition); + + uint8_t lock_bits = 0; + if(iso15693_data->settings.lock_bits.dsfid) { + lock_bits |= SLIX_LOCK_BITS_DSFID; + } + if(iso15693_data->settings.lock_bits.afi) { + lock_bits |= SLIX_LOCK_BITS_AFI; + } + if(slix_data->system_info.lock_bits.eas) { + lock_bits |= SLIX_LOCK_BITS_EAS; + } + if(slix_data->system_info.lock_bits.ppl) { + lock_bits |= SLIX_LOCK_BITS_PPL; + } + bit_buffer_append_byte(instance->tx_buffer, lock_bits); + + const uint32_t feature_flags = SLIX2_FEATURE_FLAGS; + bit_buffer_append_bytes(instance->tx_buffer, (uint8_t*)&feature_flags, sizeof(uint32_t)); + + return SlixErrorNone; +} + +static SlixError slix_listener_get_random_number_handler( + SlixListener* instance, + const uint8_t* data, + size_t data_size, + uint8_t flags) { + UNUSED(data); + UNUSED(data_size); + UNUSED(flags); + + SlixListenerSessionState* session_state = &instance->session_state; + session_state->random = furi_hal_random_get(); + bit_buffer_append_bytes( + instance->tx_buffer, (uint8_t*)&session_state->random, sizeof(uint16_t)); + + return SlixErrorNone; +} + +static SlixError slix_listener_set_password_handler( + SlixListener* instance, + const uint8_t* data, + size_t data_size, + uint8_t flags) { + UNUSED(flags); + SlixError error = SlixErrorNone; + + do { +#pragma pack(push, 1) + typedef struct { + uint8_t password_id; + SlixPassword password_xored; + } SlixSetPasswordRequestLayout; +#pragma pack(pop) + + if(data_size != sizeof(SlixSetPasswordRequestLayout)) { + error = SlixErrorFormat; + break; + } + + const SlixSetPasswordRequestLayout* request = (const SlixSetPasswordRequestLayout*)data; + const SlixPasswordType password_type = + slix_listener_get_password_type_by_id(request->password_id); + const SlixPassword password_received = + slix_listener_unxor_password(request->password_xored, instance->session_state.random); + + error = slix_listener_set_password(instance, password_type, password_received); + if(error != SlixErrorNone) break; + + if(password_type == SlixPasswordTypePrivacy) { + slix_set_privacy_mode(instance->data, false); + } + } while(false); + + return error; +} + +static SlixError slix_listener_write_password_handler( + SlixListener* instance, + const uint8_t* data, + size_t data_size, + uint8_t flags) { + UNUSED(flags); + SlixError error = SlixErrorNone; + + do { +#pragma pack(push, 1) + typedef struct { + uint8_t password_id; + SlixPassword password; + } SlixWritePasswordRequestLayout; +#pragma pack(pop) + + if(data_size != sizeof(SlixWritePasswordRequestLayout)) { + error = SlixErrorFormat; + break; + } + + const SlixWritePasswordRequestLayout* request = + (const SlixWritePasswordRequestLayout*)data; + const SlixPasswordType password_type = + slix_listener_get_password_type_by_id(request->password_id); + + error = slix_listener_write_password(instance, password_type, request->password); + if(error != SlixErrorNone) break; + + } while(false); + + return error; +} + +static SlixError slix_listener_protect_page_handler( + SlixListener* instance, + const uint8_t* data, + size_t data_size, + uint8_t flags) { + UNUSED(flags); + SlixError error = SlixErrorNone; + + do { + typedef struct { + uint8_t pointer; + uint8_t condition; + } SlixProtectPageRequestLayout; + + if(data_size != sizeof(SlixProtectPageRequestLayout)) { + error = SlixErrorFormat; + break; + } + + SlixData* slix_data = instance->data; + + if(slix_data->system_info.lock_bits.ppl) { + error = SlixErrorInternal; + break; + } + + const SlixListenerSessionState* session_state = &instance->session_state; + if(!session_state->password_match[SlixPasswordTypeRead] || + !session_state->password_match[SlixPasswordTypeWrite]) { + error = SlixErrorInternal; + break; + } + + const SlixProtectPageRequestLayout* request = (const SlixProtectPageRequestLayout*)data; + + if(request->pointer >= SLIX_COUNTER_BLOCK_NUM) { + error = SlixErrorInternal; + break; + } + + SlixProtection* protection = &slix_data->system_info.protection; + + protection->pointer = request->pointer; + protection->condition = request->condition; + } while(false); + + return error; +} + +static SlixError slix_listener_enable_privacy_handler( + SlixListener* instance, + const uint8_t* data, + size_t data_size, + uint8_t flags) { + UNUSED(flags); + SlixError error = SlixErrorNone; + + do { + typedef struct { + SlixPassword password_xored; + } SlixEnablePrivacyRequestLayout; + + if(data_size != sizeof(SlixEnablePrivacyRequestLayout)) { + error = SlixErrorFormat; + break; + } + + const SlixEnablePrivacyRequestLayout* request = + (const SlixEnablePrivacyRequestLayout*)data; + + const SlixPassword password_received = + slix_listener_unxor_password(request->password_xored, instance->session_state.random); + + error = slix_listener_set_password(instance, SlixPasswordTypePrivacy, password_received); + if(error != SlixErrorNone) break; + + slix_set_privacy_mode(instance->data, true); + } while(false); + + return error; +} + +static SlixError slix_listener_read_signature_handler( + SlixListener* instance, + const uint8_t* data, + size_t data_size, + uint8_t flags) { + UNUSED(data); + UNUSED(data_size); + UNUSED(flags); + + bit_buffer_append_bytes(instance->tx_buffer, instance->data->signature, sizeof(SlixSignature)); + return SlixErrorNone; +} + +// Custom SLIX commands handler table +static const SlixRequestHandler slix_request_handler_table[SLIX_CMD_CUSTOM_COUNT] = { + slix_listener_default_handler, // SLIX_CMD_SET_EAS (0xA2U) + slix_listener_default_handler, // SLIX_CMD_RESET_EAS (0xA3U) + slix_listener_default_handler, // SLIX_CMD_LOCK_EAS (0xA4U) + slix_listener_default_handler, // SLIX_CMD_EAS_ALARM (0xA5U) + slix_listener_default_handler, // SLIX_CMD_PASSWORD_PROTECT_EAS_AFI (0xA6U) + slix_listener_default_handler, // SLIX_CMD_WRITE_EAS_ID (0xA7U) + slix_listener_default_handler, // UNUSED (0xA8U) + slix_listener_default_handler, // UNUSED (0xA9U) + slix_listener_default_handler, // UNUSED (0xAAU) + slix_listener_get_nxp_system_info_handler, + slix_listener_default_handler, // UNUSED (0xACU) + slix_listener_default_handler, // UNUSED (0xADU) + slix_listener_default_handler, // UNUSED (0xAEU) + slix_listener_default_handler, // UNUSED (0xAFU) + slix_listener_default_handler, // SLIX_CMD_INVENTORY_PAGE_READ (0xB0U) + slix_listener_default_handler, // SLIX_CMD_INVENTORY_PAGE_READ_FAST (0xB1U) + slix_listener_get_random_number_handler, + slix_listener_set_password_handler, + slix_listener_write_password_handler, + slix_listener_default_handler, // SLIX_CMD_64_BIT_PASSWORD_PROTECTION (0xB5U) + slix_listener_protect_page_handler, + slix_listener_default_handler, // SLIX_CMD_LOCK_PAGE_PROTECTION_CONDITION (0xB7U) + slix_listener_default_handler, // UNUSED (0xB8U) + slix_listener_default_handler, // SLIX_CMD_DESTROY (0xB9U) + slix_listener_enable_privacy_handler, + slix_listener_default_handler, // UNUSED (0xBBU) + slix_listener_default_handler, // SLIX_CMD_STAY_QUIET_PERSISTENT (0xBCU) + slix_listener_read_signature_handler, +}; + +// ISO15693-3 Protocol extension handlers + +static Iso15693_3Error + slix_listener_iso15693_3_inventory_extension_handler(SlixListener* instance, va_list args) { + UNUSED(args); + + return instance->data->privacy ? Iso15693_3ErrorIgnore : Iso15693_3ErrorNone; +} + +static Iso15693_3Error + slix_iso15693_3_read_block_extension_handler(SlixListener* instance, va_list args) { + Iso15693_3Error error = Iso15693_3ErrorNone; + + do { + const uint32_t block_num = va_arg(args, uint32_t); + // SLIX Counter has no read protection + if(block_num == SLIX_COUNTER_BLOCK_NUM) break; + + if(slix_is_block_protected(instance->data, SlixPasswordTypeRead, block_num)) { + if(slix_listener_is_password_lock_enabled(instance, SlixPasswordTypeRead)) { + error = Iso15693_3ErrorInternal; + break; + } + } + } while(false); + + return error; +} + +static Iso15693_3Error + slix_listener_iso15693_3_write_block_extension_handler(SlixListener* instance, va_list args) { + Iso15693_3Error error = Iso15693_3ErrorNone; + + do { + const uint32_t block_num = va_arg(args, uint32_t); + + if(block_num == SLIX_COUNTER_BLOCK_NUM) { + const uint32_t counter = *(va_arg(args, uint32_t*)); + if(counter == 0x00000001U) { + if(slix_is_counter_increment_protected(instance->data) && + slix_listener_is_password_lock_enabled(instance, SlixPasswordTypeRead)) { + error = Iso15693_3ErrorInternal; + break; + } + slix_increment_counter(instance->data); + error = Iso15693_3ErrorFullyHandled; + break; + } + } else if(slix_is_block_protected(instance->data, SlixPasswordTypeRead, block_num)) { + if(slix_listener_is_password_lock_enabled(instance, SlixPasswordTypeRead)) { + error = Iso15693_3ErrorInternal; + break; + } + } + + if(slix_is_block_protected(instance->data, SlixPasswordTypeWrite, block_num)) { + if(slix_listener_is_password_lock_enabled(instance, SlixPasswordTypeWrite)) { + error = Iso15693_3ErrorInternal; + break; + } + } + + } while(false); + + return error; +} + +static Iso15693_3Error + slix_listener_iso15693_3_lock_block_extension_handler(SlixListener* instance, va_list args) { + Iso15693_3Error error = Iso15693_3ErrorNone; + + do { + const uint32_t block_num = va_arg(args, uint32_t); + + // SLIX counter cannot be locked + if(block_num == SLIX_COUNTER_BLOCK_NUM) { + error = Iso15693_3ErrorInternal; + break; + } + + if(slix_is_block_protected(instance->data, SlixPasswordTypeRead, block_num)) { + if(slix_listener_is_password_lock_enabled(instance, SlixPasswordTypeRead)) { + error = Iso15693_3ErrorInternal; + break; + } + } + + if(slix_is_block_protected(instance->data, SlixPasswordTypeWrite, block_num)) { + if(slix_listener_is_password_lock_enabled(instance, SlixPasswordTypeWrite)) { + error = Iso15693_3ErrorInternal; + break; + } + } + + } while(false); + + return error; +} + +static Iso15693_3Error slix_listener_iso15693_3_read_multi_block_extension_handler( + SlixListener* instance, + va_list args) { + Iso15693_3Error error = Iso15693_3ErrorNone; + + const uint32_t block_index_start = va_arg(args, uint32_t); + const uint32_t block_index_end = va_arg(args, uint32_t); + + for(uint32_t i = block_index_start; i <= block_index_end; ++i) { + // SLIX Counter has no read protection + if(i == SLIX_COUNTER_BLOCK_NUM) continue; + + if(slix_is_block_protected(instance->data, SlixPasswordTypeRead, i)) { + if(slix_listener_is_password_lock_enabled(instance, SlixPasswordTypeRead)) { + error = Iso15693_3ErrorInternal; + break; + } + } + } + + return error; +} + +static Iso15693_3Error slix_listener_iso15693_3_write_multi_block_extension_handler( + SlixListener* instance, + va_list args) { + UNUSED(instance); + UNUSED(args); + // No mention of this command in SLIX docs, assuming not supported + return Iso15693_3ErrorNotSupported; +} + +static Iso15693_3Error slix_listener_iso15693_3_write_lock_afi_extension_handler( + SlixListener* instance, + va_list args) { + UNUSED(args); + + return slix_listener_is_password_lock_enabled(instance, SlixPasswordTypeEasAfi) ? + Iso15693_3ErrorInternal : + Iso15693_3ErrorNone; +} + +// Extended ISO15693-3 standard commands handler table (NULL = no extension) +static const Iso15693_3ExtensionHandlerTable slix_iso15693_extension_handler_table = { + .mandatory = + { + (Iso15693_3ExtensionHandler)slix_listener_iso15693_3_inventory_extension_handler, + (Iso15693_3ExtensionHandler)NULL // ISO15693_3_CMD_STAY_QUIET (0x02U) + }, + .optional = + { + (Iso15693_3ExtensionHandler)slix_iso15693_3_read_block_extension_handler, + (Iso15693_3ExtensionHandler)slix_listener_iso15693_3_write_block_extension_handler, + (Iso15693_3ExtensionHandler)slix_listener_iso15693_3_lock_block_extension_handler, + (Iso15693_3ExtensionHandler)slix_listener_iso15693_3_read_multi_block_extension_handler, + (Iso15693_3ExtensionHandler) + slix_listener_iso15693_3_write_multi_block_extension_handler, + (Iso15693_3ExtensionHandler)NULL, // ISO15693_3_CMD_SELECT (0x25U) + (Iso15693_3ExtensionHandler)NULL, // ISO15693_3_CMD_RESET_TO_READY (0x26U) + (Iso15693_3ExtensionHandler)slix_listener_iso15693_3_write_lock_afi_extension_handler, + (Iso15693_3ExtensionHandler)slix_listener_iso15693_3_write_lock_afi_extension_handler, + (Iso15693_3ExtensionHandler)NULL, // ISO15693_3_CMD_WRITE_DSFID (0x29U) + (Iso15693_3ExtensionHandler)NULL, // ISO15693_3_CMD_LOCK_DSFID (0x2AU) + (Iso15693_3ExtensionHandler)NULL, // ISO15693_3_CMD_GET_SYS_INFO (0x2BU) + (Iso15693_3ExtensionHandler)NULL, // ISO15693_3_CMD_GET_BLOCKS_SECURITY (0x2CU) + }, +}; + +SlixError slix_listener_init_iso15693_3_extensions(SlixListener* instance) { + iso15693_3_listener_set_extension_handler_table( + instance->iso15693_3_listener, &slix_iso15693_extension_handler_table, instance); + return SlixErrorNone; +} + +SlixError slix_listener_process_request(SlixListener* instance, const BitBuffer* rx_buffer) { + SlixError error = SlixErrorNone; + + do { + typedef struct { + uint8_t flags; + uint8_t command; + uint8_t manufacturer; + uint8_t data[]; + } SlixRequestLayout; + + const size_t buf_size = bit_buffer_get_size_bytes(rx_buffer); + + if(buf_size < sizeof(SlixRequestLayout)) { + error = SlixErrorFormat; + break; + } + + const SlixRequestLayout* request = + (const SlixRequestLayout*)bit_buffer_get_data(rx_buffer); + + const bool addressed_mode = instance->iso15693_3_listener->session_state.addressed; + + const size_t uid_field_size = addressed_mode ? ISO15693_3_UID_SIZE : 0; + const size_t buf_size_min = sizeof(SlixRequestLayout) + uid_field_size; + + if(buf_size < buf_size_min) { + error = SlixErrorFormat; + break; + } + + if(addressed_mode) { + if(!iso15693_3_is_equal_uid(instance->data->iso15693_3_data, request->data)) { + error = SlixErrorUidMismatch; + break; + } + } + + const uint8_t command = request->command; + const bool is_valid_slix_command = command >= SLIX_CMD_CUSTOM_START && + command < SLIX_CMD_CUSTOM_END; + if(!is_valid_slix_command) { + error = SlixErrorNotSupported; + break; + } + + bit_buffer_reset(instance->tx_buffer); + bit_buffer_append_byte(instance->tx_buffer, ISO15693_3_RESP_FLAG_NONE); + + const uint8_t* request_data = &request->data[uid_field_size]; + const size_t request_data_size = buf_size - buf_size_min; + + SlixRequestHandler handler = slix_request_handler_table[command - SLIX_CMD_CUSTOM_START]; + error = handler(instance, request_data, request_data_size, request->flags); + + // It's a trick! Send no reply. + if(error == SlixErrorFormat || error == SlixErrorWrongPassword || + error == SlixErrorNotSupported) + break; + + if(error != SlixErrorNone) { + bit_buffer_reset(instance->tx_buffer); + bit_buffer_append_byte(instance->tx_buffer, ISO15693_3_RESP_FLAG_ERROR); + bit_buffer_append_byte(instance->tx_buffer, ISO15693_3_RESP_ERROR_UNKNOWN); + } + + const Iso15693_3Error iso15693_error = + iso15693_3_listener_send_frame(instance->iso15693_3_listener, instance->tx_buffer); + error = slix_process_iso15693_3_error(iso15693_error); + } while(false); + + return error; +} diff --git a/lib/nfc/protocols/slix/slix_listener_i.h b/lib/nfc/protocols/slix/slix_listener_i.h new file mode 100644 index 00000000000..ce059c5a675 --- /dev/null +++ b/lib/nfc/protocols/slix/slix_listener_i.h @@ -0,0 +1,37 @@ +#pragma once + +#include + +#include "slix_listener.h" +#include "slix_i.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + uint16_t random; + bool password_match[SlixPasswordTypeCount]; +} SlixListenerSessionState; + +struct SlixListener { + Iso15693_3Listener* iso15693_3_listener; + SlixData* data; + SlixListenerSessionState session_state; + + BitBuffer* tx_buffer; + + NfcGenericEvent generic_event; + SlixListenerEvent slix_event; + SlixListenerEventData slix_event_data; + NfcGenericCallback callback; + void* context; +}; + +SlixError slix_listener_init_iso15693_3_extensions(SlixListener* instance); + +SlixError slix_listener_process_request(SlixListener* instance, const BitBuffer* rx_buffer); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/slix/slix_poller.c b/lib/nfc/protocols/slix/slix_poller.c new file mode 100644 index 00000000000..9731bfc6b83 --- /dev/null +++ b/lib/nfc/protocols/slix/slix_poller.c @@ -0,0 +1,159 @@ +#include "slix_poller_i.h" + +#include + +#include + +#define TAG "SlixPoller" + +#define SLIX_POLLER_BUF_SIZE (64U) + +typedef NfcCommand (*SlixPollerStateHandler)(SlixPoller* instance); + +const SlixData* slix_poller_get_data(SlixPoller* instance) { + furi_assert(instance); + + return instance->data; +} + +static SlixPoller* slix_poller_alloc(Iso15693_3Poller* iso15693_3_poller) { + SlixPoller* instance = malloc(sizeof(SlixPoller)); + instance->iso15693_3_poller = iso15693_3_poller; + instance->data = slix_alloc(); + instance->tx_buffer = bit_buffer_alloc(SLIX_POLLER_BUF_SIZE); + instance->rx_buffer = bit_buffer_alloc(SLIX_POLLER_BUF_SIZE); + + instance->slix_event.data = &instance->slix_event_data; + + instance->general_event.protocol = NfcProtocolSlix; + instance->general_event.event_data = &instance->slix_event; + instance->general_event.instance = instance; + + return instance; +} + +static void slix_poller_free(SlixPoller* instance) { + furi_assert(instance); + + slix_free(instance->data); + bit_buffer_free(instance->tx_buffer); + bit_buffer_free(instance->rx_buffer); + free(instance); +} + +static NfcCommand slix_poller_handler_idle(SlixPoller* instance) { + iso15693_3_copy( + instance->data->iso15693_3_data, iso15693_3_poller_get_data(instance->iso15693_3_poller)); + + instance->poller_state = SlixPollerStateGetNxpSysInfo; + return NfcCommandContinue; +} + +static NfcCommand slix_poller_handler_get_nfc_system_info(SlixPoller* instance) { + instance->error = + slix_poller_async_get_nxp_system_info(instance, &instance->data->system_info); + if(instance->error == SlixErrorNone) { + instance->poller_state = SlixPollerStateReadSignature; + } else { + instance->poller_state = SlixPollerStateError; + } + + return NfcCommandContinue; +} + +static NfcCommand slix_poller_handler_read_signature(SlixPoller* instance) { + instance->error = slix_poller_async_read_signature(instance, &instance->data->signature); + if(instance->error == SlixErrorNone) { + instance->poller_state = SlixPollerStateReady; + } else { + instance->poller_state = SlixPollerStateError; + } + + return NfcCommandContinue; +} + +static NfcCommand slix_poller_handler_error(SlixPoller* instance) { + instance->slix_event_data.error = instance->error; + instance->slix_event.type = SlixPollerEventTypeError; + NfcCommand command = instance->callback(instance->general_event, instance->context); + instance->poller_state = SlixPollerStateIdle; + return command; +} + +static NfcCommand slix_poller_handler_ready(SlixPoller* instance) { + instance->slix_event.type = SlixPollerEventTypeReady; + NfcCommand command = instance->callback(instance->general_event, instance->context); + return command; +} + +static const SlixPollerStateHandler slix_poller_state_handler[SlixPollerStateNum] = { + [SlixPollerStateIdle] = slix_poller_handler_idle, + [SlixPollerStateError] = slix_poller_handler_error, + [SlixPollerStateGetNxpSysInfo] = slix_poller_handler_get_nfc_system_info, + [SlixPollerStateReadSignature] = slix_poller_handler_read_signature, + [SlixPollerStateReady] = slix_poller_handler_ready, +}; + +static void + slix_poller_set_callback(SlixPoller* instance, NfcGenericCallback callback, void* context) { + furi_assert(instance); + furi_assert(callback); + + instance->callback = callback; + instance->context = context; +} + +static NfcCommand slix_poller_run(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolIso15693_3); + + SlixPoller* instance = context; + furi_assert(instance); + furi_assert(instance->callback); + + Iso15693_3PollerEvent* iso15693_3_event = event.event_data; + furi_assert(iso15693_3_event); + + NfcCommand command = NfcCommandContinue; + + if(iso15693_3_event->type == Iso15693_3PollerEventTypeReady) { + command = slix_poller_state_handler[instance->poller_state](instance); + } else if(iso15693_3_event->type == Iso15693_3PollerEventTypeError) { + instance->slix_event.type = SlixPollerEventTypeError; + command = instance->callback(instance->general_event, instance->context); + } + + return command; +} + +static bool slix_poller_detect(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolIso15693_3); + + SlixPoller* instance = context; + furi_assert(instance); + + const Iso15693_3PollerEvent* iso15693_3_event = event.event_data; + furi_assert(iso15693_3_event); + iso15693_3_copy( + instance->data->iso15693_3_data, iso15693_3_poller_get_data(instance->iso15693_3_poller)); + + bool protocol_detected = false; + + if(iso15693_3_event->type == Iso15693_3PollerEventTypeReady) { + if(slix_get_type(instance->data) < SlixTypeCount) { + SlixSystemInfo system_info = {}; + SlixError error = slix_poller_async_get_nxp_system_info(instance, &system_info); + protocol_detected = (error == SlixErrorNone); + } + } + + return protocol_detected; +} + +const NfcPollerBase nfc_poller_slix = { + .alloc = (NfcPollerAlloc)slix_poller_alloc, + .free = (NfcPollerFree)slix_poller_free, + .set_callback = (NfcPollerSetCallback)slix_poller_set_callback, + .run = (NfcPollerRun)slix_poller_run, + .detect = (NfcPollerDetect)slix_poller_detect, + .get_data = (NfcPollerGetData)slix_poller_get_data, +}; diff --git a/lib/nfc/protocols/slix/slix_poller.h b/lib/nfc/protocols/slix/slix_poller.h new file mode 100644 index 00000000000..f4c7214de4f --- /dev/null +++ b/lib/nfc/protocols/slix/slix_poller.h @@ -0,0 +1,29 @@ +#pragma once + +#include "slix.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct SlixPoller SlixPoller; + +typedef enum { + SlixPollerEventTypeError, + SlixPollerEventTypeReady, +} SlixPollerEventType; + +typedef struct { + SlixError error; +} SlixPollerEventData; + +typedef struct { + SlixPollerEventType type; + SlixPollerEventData* data; +} SlixPollerEvent; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/slix/slix_poller_defs.h b/lib/nfc/protocols/slix/slix_poller_defs.h new file mode 100644 index 00000000000..df8f298f0a3 --- /dev/null +++ b/lib/nfc/protocols/slix/slix_poller_defs.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +extern const NfcPollerBase nfc_poller_slix; diff --git a/lib/nfc/protocols/slix/slix_poller_i.c b/lib/nfc/protocols/slix/slix_poller_i.c new file mode 100644 index 00000000000..a36e7694a4f --- /dev/null +++ b/lib/nfc/protocols/slix/slix_poller_i.c @@ -0,0 +1,69 @@ +#include "slix_poller_i.h" + +#include + +#include "slix_i.h" + +#define TAG "SlixPoller" + +static void slix_poller_prepare_request(SlixPoller* instance, uint8_t command) { + bit_buffer_reset(instance->tx_buffer); + bit_buffer_reset(instance->rx_buffer); + + bit_buffer_append_byte( + instance->tx_buffer, + ISO15693_3_REQ_FLAG_SUBCARRIER_1 | ISO15693_3_REQ_FLAG_DATA_RATE_HI | + ISO15693_3_REQ_FLAG_T4_ADDRESSED); + bit_buffer_append_byte(instance->tx_buffer, command); + bit_buffer_append_byte(instance->tx_buffer, SLIX_NXP_MANUFACTURER_CODE); + + iso15693_3_append_uid(instance->data->iso15693_3_data, instance->tx_buffer); +} + +SlixError slix_poller_send_frame( + SlixPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt) { + furi_assert(instance); + + const Iso15693_3Error iso15693_3_error = + iso15693_3_poller_send_frame(instance->iso15693_3_poller, tx_buffer, rx_buffer, fwt); + return slix_process_iso15693_3_error(iso15693_3_error); +} + +SlixError slix_poller_async_get_nxp_system_info(SlixPoller* instance, SlixSystemInfo* data) { + furi_assert(instance); + furi_assert(data); + + slix_poller_prepare_request(instance, SLIX_CMD_GET_NXP_SYSTEM_INFORMATION); + + SlixError error = SlixErrorNone; + + do { + error = slix_poller_send_frame( + instance, instance->tx_buffer, instance->rx_buffer, ISO15693_3_FDT_POLL_FC); + if(error != SlixErrorNone) break; + error = slix_get_nxp_system_info_response_parse(instance->data, instance->rx_buffer); + } while(false); + + return error; +} + +SlixError slix_poller_async_read_signature(SlixPoller* instance, SlixSignature* data) { + furi_assert(instance); + furi_assert(data); + + slix_poller_prepare_request(instance, SLIX_CMD_READ_SIGNATURE); + + SlixError error = SlixErrorNone; + + do { + error = slix_poller_send_frame( + instance, instance->tx_buffer, instance->rx_buffer, ISO15693_3_FDT_POLL_FC * 2); + if(error != SlixErrorNone) break; + error = slix_read_signature_response_parse(instance->data->signature, instance->rx_buffer); + } while(false); + + return error; +} diff --git a/lib/nfc/protocols/slix/slix_poller_i.h b/lib/nfc/protocols/slix/slix_poller_i.h new file mode 100644 index 00000000000..c6a8a3c33ef --- /dev/null +++ b/lib/nfc/protocols/slix/slix_poller_i.h @@ -0,0 +1,48 @@ +#pragma once + +#include + +#include "slix_poller.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + SlixPollerStateIdle, + SlixPollerStateGetNxpSysInfo, + SlixPollerStateReadSignature, + SlixPollerStateReady, + SlixPollerStateError, + SlixPollerStateNum, +} SlixPollerState; + +struct SlixPoller { + Iso15693_3Poller* iso15693_3_poller; + SlixData* data; + SlixPollerState poller_state; + SlixError error; + + BitBuffer* tx_buffer; + BitBuffer* rx_buffer; + + SlixPollerEventData slix_event_data; + SlixPollerEvent slix_event; + NfcGenericEvent general_event; + NfcGenericCallback callback; + void* context; +}; + +SlixError slix_poller_send_frame( + SlixPoller* instance, + const BitBuffer* tx_data, + BitBuffer* rx_data, + uint32_t fwt); + +SlixError slix_poller_async_get_nxp_system_info(SlixPoller* instance, SlixSystemInfo* data); + +SlixError slix_poller_async_read_signature(SlixPoller* instance, SlixSignature* data); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/st25tb/st25tb.c b/lib/nfc/protocols/st25tb/st25tb.c new file mode 100644 index 00000000000..d3fac7eeac8 --- /dev/null +++ b/lib/nfc/protocols/st25tb/st25tb.c @@ -0,0 +1,234 @@ +#include "st25tb.h" + +#include "core/string.h" +#include "flipper_format.h" +#include + +#include +#include + +#define ST25TB_PROTOCOL_NAME "ST25TB" +#define ST25TB_TYPE_KEY "ST25TB Type" +#define ST25TB_BLOCK_KEY "Block %d" +#define ST25TB_SYSTEM_BLOCK_KEY "System OTP Block" + +typedef struct { + uint8_t blocks_total; + bool has_otp; + const char* full_name; + const char* type_name; +} St25tbFeatures; + +static const St25tbFeatures st25tb_features[St25tbTypeNum] = { + [St25tbType512At] = + { + .blocks_total = 16, + .has_otp = false, + .full_name = "ST25TB512-AT/SRI512", + .type_name = "512AT", + }, + [St25tbType512Ac] = + { + .blocks_total = 16, + .has_otp = true, + .full_name = "ST25TB512-AC/SRT512", + .type_name = "512AC", + }, + [St25tbTypeX512] = + { + .blocks_total = 16, + .has_otp = true, + .full_name = "SRIX512", + .type_name = "X512", + }, + [St25tbType02k] = + { + .blocks_total = 64, + .has_otp = true, + .full_name = "ST25TB02K/SRI2K", + .type_name = "2K", + }, + [St25tbType04k] = + { + .blocks_total = 128, + .has_otp = true, + .full_name = "ST25TB04K/SRI4K", + .type_name = "4K", + }, + [St25tbTypeX4k] = + { + .blocks_total = 128, + .has_otp = true, + .full_name = "SRIX4K", + .type_name = "X4K", + }, +}; + +const NfcDeviceBase nfc_device_st25tb = { + .protocol_name = ST25TB_PROTOCOL_NAME, + .alloc = (NfcDeviceAlloc)st25tb_alloc, + .free = (NfcDeviceFree)st25tb_free, + .reset = (NfcDeviceReset)st25tb_reset, + .copy = (NfcDeviceCopy)st25tb_copy, + .verify = (NfcDeviceVerify)st25tb_verify, + .load = (NfcDeviceLoad)st25tb_load, + .save = (NfcDeviceSave)st25tb_save, + .is_equal = (NfcDeviceEqual)st25tb_is_equal, + .get_name = (NfcDeviceGetName)st25tb_get_device_name, + .get_uid = (NfcDeviceGetUid)st25tb_get_uid, + .set_uid = (NfcDeviceSetUid)st25tb_set_uid, + .get_base_data = (NfcDeviceGetBaseData)st25tb_get_base_data, +}; + +St25tbData* st25tb_alloc() { + St25tbData* data = malloc(sizeof(St25tbData)); + return data; +} + +void st25tb_free(St25tbData* data) { + furi_assert(data); + + free(data); +} + +void st25tb_reset(St25tbData* data) { + memset(data, 0, sizeof(St25tbData)); +} + +void st25tb_copy(St25tbData* data, const St25tbData* other) { + furi_assert(data); + furi_assert(other); + + *data = *other; +} + +bool st25tb_verify(St25tbData* data, const FuriString* device_type) { + UNUSED(data); + return furi_string_equal_str(device_type, ST25TB_PROTOCOL_NAME); +} + +bool st25tb_load(St25tbData* data, FlipperFormat* ff, uint32_t version) { + furi_assert(data); + + bool parsed = false; + + FuriString* temp_str = furi_string_alloc(); + + do { + if(version < NFC_UNIFIED_FORMAT_VERSION) break; + if(!flipper_format_read_string(ff, ST25TB_TYPE_KEY, temp_str)) break; + + bool type_parsed = false; + for(size_t i = 0; i < St25tbTypeNum; i++) { + if(furi_string_equal_str(temp_str, st25tb_features[i].type_name)) { + data->type = i; + type_parsed = true; + } + } + if(!type_parsed) break; + + bool blocks_parsed = true; + for(uint8_t block = 0; block < st25tb_features[data->type].blocks_total; block++) { + furi_string_printf(temp_str, ST25TB_BLOCK_KEY, block); + if(!flipper_format_read_hex( + ff, furi_string_get_cstr(temp_str), (uint8_t*)&data->blocks[block], 4)) { + blocks_parsed = false; + break; + } + } + if(!blocks_parsed) break; + + if(!flipper_format_read_hex( + ff, ST25TB_SYSTEM_BLOCK_KEY, (uint8_t*)&data->system_otp_block, 4)) + break; + + parsed = true; + } while(false); + + furi_string_free(temp_str); + + return parsed; +} + +bool st25tb_save(const St25tbData* data, FlipperFormat* ff) { + furi_assert(data); + + FuriString* temp_str = furi_string_alloc(); + bool saved = false; + + do { + if(!flipper_format_write_comment_cstr(ff, ST25TB_PROTOCOL_NAME " specific data")) break; + if(!flipper_format_write_string_cstr( + ff, ST25TB_TYPE_KEY, st25tb_features[data->type].type_name)) + break; + + bool blocks_saved = true; + for(uint8_t block = 0; block < st25tb_features[data->type].blocks_total; block++) { + furi_string_printf(temp_str, ST25TB_BLOCK_KEY, block); + if(!flipper_format_write_hex( + ff, furi_string_get_cstr(temp_str), (uint8_t*)&data->blocks[block], 4)) { + blocks_saved = false; + break; + } + } + if(!blocks_saved) break; + + if(!flipper_format_write_hex( + ff, ST25TB_SYSTEM_BLOCK_KEY, (uint8_t*)&data->system_otp_block, 4)) + break; + + saved = true; + } while(false); + + furi_string_free(temp_str); + return saved; +} + +bool st25tb_is_equal(const St25tbData* data, const St25tbData* other) { + furi_assert(data); + furi_assert(other); + + return memcmp(data, other, sizeof(St25tbData)) == 0; +} + +uint8_t st25tb_get_block_count(St25tbType type) { + return st25tb_features[type].blocks_total; +} + +const char* st25tb_get_device_name(const St25tbData* data, NfcDeviceNameType name_type) { + furi_assert(data); + furi_assert(data->type < St25tbTypeNum); + + if(name_type == NfcDeviceNameTypeFull) { + return st25tb_features[data->type].full_name; + } else { + return st25tb_features[data->type].type_name; + } +} + +const uint8_t* st25tb_get_uid(const St25tbData* data, size_t* uid_len) { + furi_assert(data); + + if(uid_len) { + *uid_len = ST25TB_UID_SIZE; + } + + return data->uid; +} + +bool st25tb_set_uid(St25tbData* data, const uint8_t* uid, size_t uid_len) { + furi_assert(data); + + const bool uid_valid = uid_len == ST25TB_UID_SIZE; + + if(uid_valid) { + memcpy(data->uid, uid, uid_len); + } + + return uid_valid; +} + +St25tbData* st25tb_get_base_data(const St25tbData* data) { + UNUSED(data); + furi_crash("No base data"); +} diff --git a/lib/nfc/protocols/st25tb/st25tb.h b/lib/nfc/protocols/st25tb/st25tb.h new file mode 100644 index 00000000000..1edb296ca12 --- /dev/null +++ b/lib/nfc/protocols/st25tb/st25tb.h @@ -0,0 +1,80 @@ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define ST25TB_UID_SIZE (8U) + +//#define ST25TB_FDT_FC (4205U) +#define ST25TB_FDT_FC (8494U) +#define ST25TB_GUARD_TIME_US (5000U) +#define ST25TB_POLL_POLL_MIN_US (1280U) + +#define ST25TB_MAX_BLOCKS (128U) +#define ST25TB_SYSTEM_OTP_BLOCK (0xFFU) +#define ST25TB_BLOCK_SIZE (4U) + +typedef enum { + St25tbErrorNone, + St25tbErrorNotPresent, + St25tbErrorColResFailed, + St25tbErrorBufferOverflow, + St25tbErrorCommunication, + St25tbErrorFieldOff, + St25tbErrorWrongCrc, + St25tbErrorTimeout, +} St25tbError; + +typedef enum { + St25tbType512At, + St25tbType512Ac, + St25tbTypeX512, + St25tbType02k, + St25tbType04k, + St25tbTypeX4k, + St25tbTypeNum, +} St25tbType; + +typedef struct { + uint8_t uid[ST25TB_UID_SIZE]; + St25tbType type; + uint32_t blocks[ST25TB_MAX_BLOCKS]; + uint32_t system_otp_block; + uint8_t chip_id; +} St25tbData; + +extern const NfcDeviceBase nfc_device_st25tb; + +St25tbData* st25tb_alloc(); + +void st25tb_free(St25tbData* data); + +void st25tb_reset(St25tbData* data); + +void st25tb_copy(St25tbData* data, const St25tbData* other); + +bool st25tb_verify(St25tbData* data, const FuriString* device_type); + +bool st25tb_load(St25tbData* data, FlipperFormat* ff, uint32_t version); + +bool st25tb_save(const St25tbData* data, FlipperFormat* ff); + +bool st25tb_is_equal(const St25tbData* data, const St25tbData* other); + +uint8_t st25tb_get_block_count(St25tbType type); + +const char* st25tb_get_device_name(const St25tbData* data, NfcDeviceNameType name_type); + +const uint8_t* st25tb_get_uid(const St25tbData* data, size_t* uid_len); + +bool st25tb_set_uid(St25tbData* data, const uint8_t* uid, size_t uid_len); + +St25tbData* st25tb_get_base_data(const St25tbData* data); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/st25tb/st25tb_poller.c b/lib/nfc/protocols/st25tb/st25tb_poller.c new file mode 100644 index 00000000000..df659a2051d --- /dev/null +++ b/lib/nfc/protocols/st25tb/st25tb_poller.c @@ -0,0 +1,123 @@ +#include "protocols/nfc_protocol.h" +#include "protocols/st25tb/st25tb.h" +#include "st25tb_poller_i.h" + +#include + +#include + +#define TAG "ST25TBPoller" + +const St25tbData* st25tb_poller_get_data(St25tbPoller* instance) { + furi_assert(instance); + furi_assert(instance->data); + + return instance->data; +} + +static St25tbPoller* st25tb_poller_alloc(Nfc* nfc) { + furi_assert(nfc); + + St25tbPoller* instance = malloc(sizeof(St25tbPoller)); + instance->nfc = nfc; + instance->tx_buffer = bit_buffer_alloc(ST25TB_POLLER_MAX_BUFFER_SIZE); + instance->rx_buffer = bit_buffer_alloc(ST25TB_POLLER_MAX_BUFFER_SIZE); + + // RF configuration is the same as 14b + nfc_config(instance->nfc, NfcModePoller, NfcTechIso14443b); + nfc_set_guard_time_us(instance->nfc, ST25TB_GUARD_TIME_US); + nfc_set_fdt_poll_fc(instance->nfc, ST25TB_FDT_FC); + nfc_set_fdt_poll_poll_us(instance->nfc, ST25TB_POLL_POLL_MIN_US); + instance->data = st25tb_alloc(); + + instance->st25tb_event.data = &instance->st25tb_event_data; + instance->general_event.protocol = NfcProtocolSt25tb; + instance->general_event.event_data = &instance->st25tb_event; + instance->general_event.instance = instance; + + return instance; +} + +static void st25tb_poller_free(St25tbPoller* instance) { + furi_assert(instance); + + furi_assert(instance->tx_buffer); + furi_assert(instance->rx_buffer); + furi_assert(instance->data); + + bit_buffer_free(instance->tx_buffer); + bit_buffer_free(instance->rx_buffer); + st25tb_free(instance->data); + free(instance); +} + +static void + st25tb_poller_set_callback(St25tbPoller* instance, NfcGenericCallback callback, void* context) { + furi_assert(instance); + furi_assert(callback); + + instance->callback = callback; + instance->context = context; +} + +static NfcCommand st25tb_poller_run(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.protocol == NfcProtocolInvalid); + furi_assert(event.event_data); + + St25tbPoller* instance = context; + NfcEvent* nfc_event = event.event_data; + NfcCommand command = NfcCommandContinue; + + if(nfc_event->type == NfcEventTypePollerReady) { + if(instance->state != St25tbPollerStateActivated) { + St25tbError error = st25tb_poller_async_activate(instance, instance->data); + + if(error == St25tbErrorNone) { + instance->st25tb_event.type = St25tbPollerEventTypeReady; + instance->st25tb_event_data.error = error; + command = instance->callback(instance->general_event, instance->context); + } else { + instance->st25tb_event.type = St25tbPollerEventTypeError; + instance->st25tb_event_data.error = error; + command = instance->callback(instance->general_event, instance->context); + // Add delay to switch context + furi_delay_ms(100); + } + } else { + instance->st25tb_event.type = St25tbPollerEventTypeReady; + instance->st25tb_event_data.error = St25tbErrorNone; + command = instance->callback(instance->general_event, instance->context); + } + } + + return command; +} + +static bool st25tb_poller_detect(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.event_data); + furi_assert(event.instance); + furi_assert(event.protocol == NfcProtocolInvalid); + + bool protocol_detected = false; + St25tbPoller* instance = context; + NfcEvent* nfc_event = event.event_data; + furi_assert(instance->state == St25tbPollerStateIdle); + + if(nfc_event->type == NfcEventTypePollerReady) { + St25tbError error = st25tb_poller_async_initiate(instance, NULL); + protocol_detected = (error == St25tbErrorNone); + } + + return protocol_detected; +} + +const NfcPollerBase nfc_poller_st25tb = { + .alloc = (NfcPollerAlloc)st25tb_poller_alloc, + .free = (NfcPollerFree)st25tb_poller_free, + .set_callback = (NfcPollerSetCallback)st25tb_poller_set_callback, + .run = (NfcPollerRun)st25tb_poller_run, + .detect = (NfcPollerDetect)st25tb_poller_detect, + .get_data = (NfcPollerGetData)st25tb_poller_get_data, +}; diff --git a/lib/nfc/protocols/st25tb/st25tb_poller.h b/lib/nfc/protocols/st25tb/st25tb_poller.h new file mode 100644 index 00000000000..a521b6d5b47 --- /dev/null +++ b/lib/nfc/protocols/st25tb/st25tb_poller.h @@ -0,0 +1,30 @@ +#pragma once + +#include "st25tb.h" +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct St25tbPoller St25tbPoller; + +typedef enum { + St25tbPollerEventTypeError, + St25tbPollerEventTypeReady, +} St25tbPollerEventType; + +typedef struct { + St25tbError error; +} St25tbPollerEventData; + +typedef struct { + St25tbPollerEventType type; + St25tbPollerEventData* data; +} St25tbPollerEvent; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/st25tb/st25tb_poller_defs.h b/lib/nfc/protocols/st25tb/st25tb_poller_defs.h new file mode 100644 index 00000000000..86b68024f3a --- /dev/null +++ b/lib/nfc/protocols/st25tb/st25tb_poller_defs.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern const NfcPollerBase nfc_poller_st25tb; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/st25tb/st25tb_poller_i.c b/lib/nfc/protocols/st25tb/st25tb_poller_i.c new file mode 100644 index 00000000000..bcbc69382a9 --- /dev/null +++ b/lib/nfc/protocols/st25tb/st25tb_poller_i.c @@ -0,0 +1,297 @@ +#include "st25tb_poller_i.h" + +#include "bit_buffer.h" +#include "core/core_defines.h" +#include "protocols/st25tb/st25tb.h" +#include + +#define TAG "ST25TBPoller" + +static St25tbError st25tb_poller_process_error(NfcError error) { + switch(error) { + case NfcErrorNone: + return St25tbErrorNone; + case NfcErrorTimeout: + return St25tbErrorTimeout; + default: + return St25tbErrorNotPresent; + } +} + +static St25tbError st25tb_poller_prepare_trx(St25tbPoller* instance) { + furi_assert(instance); + + if(instance->state == St25tbPollerStateIdle) { + return st25tb_poller_async_activate(instance, NULL); + } + + return St25tbErrorNone; +} + +static St25tbError st25tb_poller_frame_exchange( + St25tbPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt) { + furi_assert(instance); + + const size_t tx_bytes = bit_buffer_get_size_bytes(tx_buffer); + furi_assert( + tx_bytes <= bit_buffer_get_capacity_bytes(instance->tx_buffer) - ISO14443_CRC_SIZE); + + bit_buffer_copy(instance->tx_buffer, tx_buffer); + iso14443_crc_append(Iso14443CrcTypeB, instance->tx_buffer); + + St25tbError ret = St25tbErrorNone; + + do { + NfcError error = + nfc_poller_trx(instance->nfc, instance->tx_buffer, instance->rx_buffer, fwt); + if(error != NfcErrorNone) { + FURI_LOG_D(TAG, "error during trx: %d", error); + ret = st25tb_poller_process_error(error); + break; + } + + bit_buffer_copy(rx_buffer, instance->rx_buffer); + if(!iso14443_crc_check(Iso14443CrcTypeB, instance->rx_buffer)) { + ret = St25tbErrorWrongCrc; + break; + } + + iso14443_crc_trim(rx_buffer); + } while(false); + + return ret; +} + +St25tbType st25tb_get_type_from_uid(const uint8_t uid[ST25TB_UID_SIZE]) { + switch(uid[2] >> 2) { + case 0x0: + case 0x3: + return St25tbTypeX4k; + case 0x4: + return St25tbTypeX512; + case 0x6: + return St25tbType512Ac; + case 0x7: + return St25tbType04k; + case 0xc: + return St25tbType512At; + case 0xf: + return St25tbType02k; + default: + furi_crash("unsupported st25tb type"); + } +} + +St25tbError st25tb_poller_async_initiate(St25tbPoller* instance, uint8_t* chip_id) { + // Send Initiate() + furi_assert(instance); + furi_assert(instance->nfc); + + instance->state = St25tbPollerStateInitiateInProgress; + bit_buffer_reset(instance->tx_buffer); + bit_buffer_reset(instance->rx_buffer); + bit_buffer_append_byte(instance->tx_buffer, 0x06); + bit_buffer_append_byte(instance->tx_buffer, 0x00); + + St25tbError ret; + do { + ret = st25tb_poller_frame_exchange( + instance, instance->tx_buffer, instance->rx_buffer, ST25TB_FDT_FC); + if(ret != St25tbErrorNone) { + break; + } + + if(bit_buffer_get_size_bytes(instance->rx_buffer) != 1) { + FURI_LOG_D(TAG, "Unexpected Initiate response size"); + ret = St25tbErrorCommunication; + break; + } + if(chip_id) { + *chip_id = bit_buffer_get_byte(instance->rx_buffer, 0); + } + } while(false); + + return ret; +} + +St25tbError st25tb_poller_async_activate(St25tbPoller* instance, St25tbData* data) { + furi_assert(instance); + furi_assert(instance->nfc); + + st25tb_reset(data); + + St25tbError ret; + + do { + ret = st25tb_poller_async_initiate(instance, &data->chip_id); + if(ret != St25tbErrorNone) { + break; + } + + instance->state = St25tbPollerStateActivationInProgress; + + bit_buffer_reset(instance->tx_buffer); + bit_buffer_reset(instance->rx_buffer); + + // Send Select(Chip_ID), let's just assume that collisions won't ever happen :D + bit_buffer_append_byte(instance->tx_buffer, 0x0E); + bit_buffer_append_byte(instance->tx_buffer, data->chip_id); + + ret = st25tb_poller_frame_exchange( + instance, instance->tx_buffer, instance->rx_buffer, ST25TB_FDT_FC); + if(ret != St25tbErrorNone) { + instance->state = St25tbPollerStateActivationFailed; + break; + } + + if(bit_buffer_get_size_bytes(instance->rx_buffer) != 1) { + FURI_LOG_D(TAG, "Unexpected Select response size"); + instance->state = St25tbPollerStateActivationFailed; + ret = St25tbErrorCommunication; + break; + } + + if(bit_buffer_get_byte(instance->rx_buffer, 0) != data->chip_id) { + FURI_LOG_D(TAG, "ChipID mismatch"); + instance->state = St25tbPollerStateActivationFailed; + ret = St25tbErrorColResFailed; + break; + } + instance->state = St25tbPollerStateActivated; + + ret = st25tb_poller_async_get_uid(instance, data->uid); + if(ret != St25tbErrorNone) { + instance->state = St25tbPollerStateActivationFailed; + break; + } + data->type = st25tb_get_type_from_uid(data->uid); + + bool read_blocks = true; + for(uint8_t i = 0; i < st25tb_get_block_count(data->type); i++) { + ret = st25tb_poller_async_read_block(instance, &data->blocks[i], i); + if(ret != St25tbErrorNone) { + read_blocks = false; + break; + } + } + if(!read_blocks) { + break; + } + ret = st25tb_poller_async_read_block( + instance, &data->system_otp_block, ST25TB_SYSTEM_OTP_BLOCK); + } while(false); + + return ret; +} + +St25tbError st25tb_poller_async_get_uid(St25tbPoller* instance, uint8_t uid[ST25TB_UID_SIZE]) { + furi_assert(instance); + furi_assert(instance->nfc); + + St25tbError ret; + + do { + bit_buffer_reset(instance->tx_buffer); + bit_buffer_reset(instance->rx_buffer); + + bit_buffer_append_byte(instance->tx_buffer, 0x0B); + + ret = st25tb_poller_frame_exchange( + instance, instance->tx_buffer, instance->rx_buffer, ST25TB_FDT_FC); + if(ret != St25tbErrorNone) { + break; + } + + if(bit_buffer_get_size_bytes(instance->rx_buffer) != ST25TB_UID_SIZE) { + FURI_LOG_D(TAG, "Unexpected Get_UID() response size"); + instance->state = St25tbPollerStateActivationFailed; + ret = St25tbErrorCommunication; + break; + } + bit_buffer_write_bytes(instance->rx_buffer, uid, ST25TB_UID_SIZE); + FURI_SWAP(uid[0], uid[7]); + FURI_SWAP(uid[1], uid[6]); + FURI_SWAP(uid[2], uid[5]); + FURI_SWAP(uid[3], uid[4]); + } while(false); + return ret; +} + +St25tbError + st25tb_poller_async_read_block(St25tbPoller* instance, uint32_t* block, uint8_t block_number) { + furi_assert(instance); + furi_assert(instance->nfc); + furi_assert(block); + furi_assert( + (block_number <= st25tb_get_block_count(instance->data->type)) || + block_number == ST25TB_SYSTEM_OTP_BLOCK); + FURI_LOG_D(TAG, "reading block %d", block_number); + bit_buffer_reset(instance->tx_buffer); + bit_buffer_reset(instance->rx_buffer); + + // Send Read_block(Addr) + bit_buffer_append_byte(instance->tx_buffer, 0x08); + bit_buffer_append_byte(instance->tx_buffer, block_number); + St25tbError ret; + do { + ret = st25tb_poller_frame_exchange( + instance, instance->tx_buffer, instance->rx_buffer, ST25TB_FDT_FC); + if(ret != St25tbErrorNone) { + break; + } + + if(bit_buffer_get_size_bytes(instance->rx_buffer) != ST25TB_BLOCK_SIZE) { + FURI_LOG_D(TAG, "Unexpected Read_block(Addr) response size"); + ret = St25tbErrorCommunication; + break; + } + bit_buffer_write_bytes(instance->rx_buffer, block, ST25TB_BLOCK_SIZE); + FURI_LOG_D(TAG, "read result: %08lX", *block); + } while(false); + + return ret; +} + +St25tbError st25tb_poller_halt(St25tbPoller* instance) { + furi_assert(instance); + + bit_buffer_reset(instance->tx_buffer); + bit_buffer_reset(instance->rx_buffer); + + // Send Completion() + bit_buffer_append_byte(instance->tx_buffer, 0x0F); + + St25tbError ret; + + do { + ret = st25tb_poller_frame_exchange( + instance, instance->tx_buffer, instance->rx_buffer, ST25TB_FDT_FC); + if(ret != St25tbErrorTimeout) { + break; + } + + instance->state = St25tbPollerStateIdle; + } while(false); + + return ret; +} + +St25tbError st25tb_poller_send_frame( + St25tbPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt) { + St25tbError ret; + + do { + ret = st25tb_poller_prepare_trx(instance); + if(ret != St25tbErrorNone) break; + + ret = st25tb_poller_frame_exchange(instance, tx_buffer, rx_buffer, fwt); + } while(false); + + return ret; +} diff --git a/lib/nfc/protocols/st25tb/st25tb_poller_i.h b/lib/nfc/protocols/st25tb/st25tb_poller_i.h new file mode 100644 index 00000000000..7f38f2d45b5 --- /dev/null +++ b/lib/nfc/protocols/st25tb/st25tb_poller_i.h @@ -0,0 +1,56 @@ +#pragma once + +#include "protocols/st25tb/st25tb.h" +#include "st25tb_poller.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define ST25TB_POLLER_MAX_BUFFER_SIZE (16U) + +typedef enum { + St25tbPollerStateIdle, + St25tbPollerStateInitiateInProgress, + St25tbPollerStateInitiateFailed, + St25tbPollerStateActivationInProgress, + St25tbPollerStateActivationFailed, + St25tbPollerStateActivated, +} St25tbPollerState; + +struct St25tbPoller { + Nfc* nfc; + St25tbPollerState state; + St25tbData* data; + BitBuffer* tx_buffer; + BitBuffer* rx_buffer; + + NfcGenericEvent general_event; + St25tbPollerEvent st25tb_event; + St25tbPollerEventData st25tb_event_data; + NfcGenericCallback callback; + void* context; +}; + +const St25tbData* st25tb_poller_get_data(St25tbPoller* instance); + +St25tbError st25tb_poller_async_initiate(St25tbPoller* instance, uint8_t* chip_id); + +St25tbError st25tb_poller_async_activate(St25tbPoller* instance, St25tbData* data); + +St25tbError st25tb_poller_async_get_uid(St25tbPoller* instance, uint8_t uid[ST25TB_UID_SIZE]); + +St25tbError + st25tb_poller_async_read_block(St25tbPoller* instance, uint32_t* block, uint8_t block_number); + +St25tbError st25tb_poller_halt(St25tbPoller* instance); + +St25tbError st25tb_poller_send_frame( + St25tbPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt); + +#ifdef __cplusplus +} +#endif diff --git a/lib/signal_reader/SConscript b/lib/signal_reader/SConscript new file mode 100644 index 00000000000..ea731442018 --- /dev/null +++ b/lib/signal_reader/SConscript @@ -0,0 +1,20 @@ +Import("env") + +env.Append( + CPPPATH=[ + "#/lib/signal_reader", + ], + SDK_HEADERS=[ + File("signal_reader.h"), + ], +) + +libenv = env.Clone(FW_LIB_NAME="signal_reader") +libenv.ApplyLibFlags() +libenv.Append(CCFLAGS=["-O3", "-funroll-loops", "-Ofast"]) + +sources = libenv.GlobRecursive("*.c*") + +lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources) +libenv.Install("${LIB_DIST_DIR}", lib) +Return("lib") diff --git a/lib/signal_reader/parsers/iso15693/iso15693_parser.c b/lib/signal_reader/parsers/iso15693/iso15693_parser.c new file mode 100644 index 00000000000..a398516c925 --- /dev/null +++ b/lib/signal_reader/parsers/iso15693/iso15693_parser.c @@ -0,0 +1,300 @@ +#include "iso15693_parser.h" + +#include + +#include + +#define ISO15693_PARSER_SIGNAL_READER_BUFF_SIZE (2) +#define ISO15693_PARSER_BITSTREAM_BUFF_SIZE (32) +#define ISO15693_PARSER_BITRATE_F64MHZ (603U) + +#define TAG "Iso15693Parser" + +typedef enum { + Iso15693ParserStateParseSoF, + Iso15693ParserStateParseFrame, + Iso15693ParserStateFail, +} Iso15693ParserState; + +typedef enum { + Iso15693ParserMode1OutOf4, + Iso15693ParserMode1OutOf256, + + Iso15693ParserModeNum, +} Iso15693ParserMode; + +struct Iso15693Parser { + Iso15693ParserState state; + Iso15693ParserMode mode; + + SignalReader* signal_reader; + + uint8_t bitstream_buff[ISO15693_PARSER_BITSTREAM_BUFF_SIZE]; + size_t bitstream_idx; + uint8_t last_byte; + + bool signal_detected; + bool bit_offset_calculated; + uint8_t bit_offset; + size_t byte_idx; + size_t bytes_to_process; + + uint8_t next_byte; + uint16_t next_byte_part; + bool zero_found; + + BitBuffer* parsed_frame; + bool eof_received; + bool frame_parsed; + + Iso15693ParserCallback callback; + void* context; +}; + +typedef enum { + Iso15693ParserCommandProcessed, + Iso15693ParserCommandWaitData, + Iso15693ParserCommandFail, + Iso15693ParserCommandSuccess, +} Iso15693ParserCommand; + +typedef Iso15693ParserCommand (*Iso15693ParserStateHandler)(Iso15693Parser* instance); + +Iso15693Parser* iso15693_parser_alloc(const GpioPin* pin, size_t max_frame_size) { + Iso15693Parser* instance = malloc(sizeof(Iso15693Parser)); + instance->parsed_frame = bit_buffer_alloc(max_frame_size); + + instance->signal_reader = signal_reader_alloc(pin, ISO15693_PARSER_SIGNAL_READER_BUFF_SIZE); + signal_reader_set_sample_rate( + instance->signal_reader, SignalReaderTimeUnit64Mhz, ISO15693_PARSER_BITRATE_F64MHZ); + signal_reader_set_pull(instance->signal_reader, GpioPullDown); + signal_reader_set_polarity(instance->signal_reader, SignalReaderPolarityInverted); + signal_reader_set_trigger(instance->signal_reader, SignalReaderTriggerRisingFallingEdge); + + return instance; +} + +void iso15693_parser_free(Iso15693Parser* instance) { + furi_assert(instance); + + bit_buffer_free(instance->parsed_frame); + signal_reader_free(instance->signal_reader); + free(instance); +} + +void iso15693_parser_reset(Iso15693Parser* instance) { + furi_assert(instance); + + instance->state = Iso15693ParserStateParseSoF; + instance->mode = Iso15693ParserMode1OutOf4; + memset(instance->bitstream_buff, 0x00, sizeof(instance->bitstream_buff)); + instance->bitstream_idx = 0; + + instance->next_byte = 0; + instance->next_byte_part = 0; + + instance->bit_offset = 0; + instance->byte_idx = 0; + instance->bytes_to_process = 0; + instance->signal_detected = false; + instance->bit_offset_calculated = false; + + instance->last_byte = 0x00; + instance->zero_found = false; + instance->eof_received = false; + + bit_buffer_reset(instance->parsed_frame); + instance->frame_parsed = false; +} + +static void signal_reader_callback(SignalReaderEvent event, void* context) { + furi_assert(context); + furi_assert(event.data->data); + furi_assert(event.data->len == ISO15693_PARSER_SIGNAL_READER_BUFF_SIZE / 2); + + Iso15693Parser* instance = context; + furi_assert(instance->callback); + + const uint8_t sof_1_out_of_4 = 0x21; + const uint8_t sof_1_out_of_256 = 0x81; + const uint8_t eof_single = 0x01; + const uint8_t eof = 0x04; + + if(instance->state == Iso15693ParserStateParseSoF) { + if(event.data->data[0] == sof_1_out_of_4) { + instance->mode = Iso15693ParserMode1OutOf4; + instance->state = Iso15693ParserStateParseFrame; + } else if(event.data->data[0] == sof_1_out_of_256) { + instance->mode = Iso15693ParserMode1OutOf256; + instance->state = Iso15693ParserStateParseFrame; + } else if(event.data->data[0] == eof_single) { + instance->eof_received = true; + instance->callback(Iso15693ParserEventDataReceived, instance->context); + } else { + instance->state = Iso15693ParserStateFail; + instance->callback(Iso15693ParserEventDataReceived, instance->context); + } + } else { + if(instance->mode == Iso15693ParserMode1OutOf4) { + if(event.data->data[0] == eof) { + instance->eof_received = true; + instance->callback(Iso15693ParserEventDataReceived, instance->context); + } else { + instance->bitstream_buff[instance->bytes_to_process] = event.data->data[0]; + instance->bytes_to_process++; + if(instance->bytes_to_process == ISO15693_PARSER_BITSTREAM_BUFF_SIZE) { + instance->callback(Iso15693ParserEventDataReceived, instance->context); + } + } + } else { + instance->bitstream_buff[instance->bytes_to_process] = event.data->data[0]; + instance->bytes_to_process++; + if(instance->bytes_to_process == ISO15693_PARSER_BITSTREAM_BUFF_SIZE) { + instance->callback(Iso15693ParserEventDataReceived, instance->context); + } + } + } +} + +static void iso15693_parser_start_signal_reader(Iso15693Parser* instance) { + iso15693_parser_reset(instance); + signal_reader_start(instance->signal_reader, signal_reader_callback, instance); +} + +void iso15693_parser_start( + Iso15693Parser* instance, + Iso15693ParserCallback callback, + void* context) { + furi_assert(instance); + furi_assert(callback); + + instance->callback = callback; + instance->context = context; + iso15693_parser_start_signal_reader(instance); +} + +void iso15693_parser_stop(Iso15693Parser* instance) { + furi_assert(instance); + + signal_reader_stop(instance->signal_reader); +} + +static Iso15693ParserCommand iso15693_parser_parse_1_out_of_4(Iso15693Parser* instance) { + Iso15693ParserCommand command = Iso15693ParserCommandWaitData; + const uint8_t bit_patterns_1_out_of_4[] = {0x02, 0x08, 0x20, 0x80}; + + for(size_t i = 0; i < instance->bytes_to_process; i++) { + // Check next pattern + size_t j = 0; + for(j = 0; j < COUNT_OF(bit_patterns_1_out_of_4); j++) { + if(instance->bitstream_buff[i] == bit_patterns_1_out_of_4[j]) { + instance->next_byte |= j << (instance->next_byte_part * 2); + instance->next_byte_part++; + if(instance->next_byte_part == 4) { + instance->next_byte_part = 0; + bit_buffer_append_byte(instance->parsed_frame, instance->next_byte); + instance->next_byte = 0; + } + break; + } + } + if(j == COUNT_OF(bit_patterns_1_out_of_4)) { + command = Iso15693ParserCommandFail; + break; + } + } + + if(command != Iso15693ParserCommandFail) { + if(instance->eof_received) { + command = Iso15693ParserCommandSuccess; + instance->frame_parsed = true; + } + } + + instance->bytes_to_process = 0; + + return command; +} + +static Iso15693ParserCommand iso15693_parser_parse_1_out_of_256(Iso15693Parser* instance) { + Iso15693ParserCommand command = Iso15693ParserCommandWaitData; + const uint8_t eof = 0x04; + + for(size_t i = instance->byte_idx; i < instance->bytes_to_process; i++) { + // Check EoF + if(instance->next_byte_part == 0) { + if(instance->bitstream_buff[i] == eof) { + instance->frame_parsed = true; + command = Iso15693ParserCommandSuccess; + break; + } + } + + if(instance->zero_found) { + if(instance->bitstream_buff[i] != 0x00) { + command = Iso15693ParserCommandFail; + break; + } + } else { + if(instance->bitstream_buff[i] != 0x00) { + for(size_t j = 0; j < 8; j++) { + if(FURI_BIT(instance->bitstream_buff[i], j) == 1) { + bit_buffer_append_byte( + instance->parsed_frame, instance->next_byte_part * 4 + j / 2); + } + } + } + } + instance->next_byte_part = (instance->next_byte_part + 1) % 64; + } + instance->bytes_to_process = 0; + instance->byte_idx = 0; + + return command; +} + +static const Iso15693ParserStateHandler iso15693_parser_state_handlers[Iso15693ParserModeNum] = { + [Iso15693ParserMode1OutOf4] = iso15693_parser_parse_1_out_of_4, + [Iso15693ParserMode1OutOf256] = iso15693_parser_parse_1_out_of_256, +}; + +bool iso15693_parser_run(Iso15693Parser* instance) { + if(instance->state == Iso15693ParserStateFail) { + iso15693_parser_stop(instance); + iso15693_parser_start_signal_reader(instance); + } else if((instance->state == Iso15693ParserStateParseSoF) && (instance->eof_received)) { + instance->frame_parsed = true; + } else if(instance->bytes_to_process) { + Iso15693ParserCommand command = Iso15693ParserCommandProcessed; + while(command == Iso15693ParserCommandProcessed) { + command = iso15693_parser_state_handlers[instance->mode](instance); + } + + if(command == Iso15693ParserCommandFail) { + iso15693_parser_stop(instance); + iso15693_parser_start_signal_reader(instance); + FURI_LOG_D(TAG, "Frame parse failed"); + } + } + + return instance->frame_parsed; +} + +size_t iso15693_parser_get_data_size_bytes(Iso15693Parser* instance) { + furi_assert(instance); + + return bit_buffer_get_size_bytes(instance->parsed_frame); +} + +void iso15693_parser_get_data( + Iso15693Parser* instance, + uint8_t* buff, + size_t buff_size, + size_t* data_bits) { + furi_assert(instance); + furi_assert(buff); + furi_assert(data_bits); + + bit_buffer_write_bytes(instance->parsed_frame, buff, buff_size); + *data_bits = bit_buffer_get_size(instance->parsed_frame); +} diff --git a/lib/signal_reader/parsers/iso15693/iso15693_parser.h b/lib/signal_reader/parsers/iso15693/iso15693_parser.h new file mode 100644 index 00000000000..3017a96d79a --- /dev/null +++ b/lib/signal_reader/parsers/iso15693/iso15693_parser.h @@ -0,0 +1,42 @@ +#pragma once + +#include "../../signal_reader.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct Iso15693Parser Iso15693Parser; + +typedef enum { + Iso15693ParserEventDataReceived, +} Iso15693ParserEvent; + +typedef void (*Iso15693ParserCallback)(Iso15693ParserEvent event, void* context); + +Iso15693Parser* iso15693_parser_alloc(const GpioPin* pin, size_t max_frame_size); + +void iso15693_parser_free(Iso15693Parser* instance); + +void iso15693_parser_reset(Iso15693Parser* instance); + +void iso15693_parser_start( + Iso15693Parser* instance, + Iso15693ParserCallback callback, + void* context); + +void iso15693_parser_stop(Iso15693Parser* instance); + +bool iso15693_parser_run(Iso15693Parser* instance); + +size_t iso15693_parser_get_data_size_bytes(Iso15693Parser* instance); + +void iso15693_parser_get_data( + Iso15693Parser* instance, + uint8_t* buff, + size_t buff_size, + size_t* data_bits); + +#ifdef __cplusplus +} +#endif diff --git a/lib/signal_reader/signal_reader.c b/lib/signal_reader/signal_reader.c new file mode 100644 index 00000000000..7c4d0bae7ed --- /dev/null +++ b/lib/signal_reader/signal_reader.c @@ -0,0 +1,317 @@ +#include "signal_reader.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#define SIGNAL_READER_DMA DMA2 + +#define SIGNAL_READER_CAPTURE_TIM (TIM16) +#define SIGNAL_READER_CAPTURE_TIM_CHANNEL LL_TIM_CHANNEL_CH1 + +#define SIGNAL_READER_DMA_GPIO LL_DMA_CHANNEL_2 +#define SIGNAL_READER_DMA_GPIO_IRQ FuriHalInterruptIdDma2Ch2 +#define SIGNAL_READER_DMA_GPIO_DEF SIGNAL_READER_DMA, SIGNAL_READER_DMA_GPIO + +#define SIGNAL_READER_DMA_TRIGGER LL_DMA_CHANNEL_3 +#define SIGNAL_READER_DMA_TRIGGER_IRQ FuriHalInterruptIdDma2Ch3 +#define SIGNAL_READER_DMA_TRIGGER_DEF SIGNAL_READER_DMA, SIGNAL_READER_DMA_TRIGGER + +#define SIGNAL_READER_DMA_CNT_SYNC LL_DMA_CHANNEL_5 +#define SIGNAL_READER_DMA_CNT_SYNC_IRQ FuriHalInterruptIdDma2Ch5 +#define SIGNAL_READER_DMA_CNT_SYNC_DEF SIGNAL_READER_DMA, SIGNAL_READER_DMA_CNT_SYNC + +struct SignalReader { + size_t buffer_size; + const GpioPin* pin; + GpioPull pull; + SignalReaderPolarity polarity; + SignalReaderTrigger trigger; + + uint16_t* gpio_buffer; + uint8_t* bitstream_buffer; + uint32_t cnt_en; + + uint32_t tim_cnt_compensation; + uint32_t tim_arr; + + SignalReaderEvent event; + SignalReaderEventData event_data; + + SignalReaderCallback callback; + void* context; +}; + +#define GPIO_PIN_MAP(pin, prefix) \ + (((pin) == (LL_GPIO_PIN_0)) ? prefix##0 : \ + ((pin) == (LL_GPIO_PIN_1)) ? prefix##1 : \ + ((pin) == (LL_GPIO_PIN_2)) ? prefix##2 : \ + ((pin) == (LL_GPIO_PIN_3)) ? prefix##3 : \ + ((pin) == (LL_GPIO_PIN_4)) ? prefix##4 : \ + ((pin) == (LL_GPIO_PIN_5)) ? prefix##5 : \ + ((pin) == (LL_GPIO_PIN_6)) ? prefix##6 : \ + ((pin) == (LL_GPIO_PIN_7)) ? prefix##7 : \ + ((pin) == (LL_GPIO_PIN_8)) ? prefix##8 : \ + ((pin) == (LL_GPIO_PIN_9)) ? prefix##9 : \ + ((pin) == (LL_GPIO_PIN_10)) ? prefix##10 : \ + ((pin) == (LL_GPIO_PIN_11)) ? prefix##11 : \ + ((pin) == (LL_GPIO_PIN_12)) ? prefix##12 : \ + ((pin) == (LL_GPIO_PIN_13)) ? prefix##13 : \ + ((pin) == (LL_GPIO_PIN_14)) ? prefix##14 : \ + prefix##15) + +#define GET_DMAMUX_EXTI_LINE(pin) GPIO_PIN_MAP(pin, LL_DMAMUX_REQ_GEN_EXTI_LINE) + +SignalReader* signal_reader_alloc(const GpioPin* gpio_pin, uint32_t size) { + SignalReader* instance = malloc(sizeof(SignalReader)); + + instance->pin = gpio_pin; + instance->pull = GpioPullNo; + + instance->buffer_size = size; + instance->gpio_buffer = malloc(sizeof(uint16_t) * size * 8); + instance->bitstream_buffer = malloc(size); + + instance->event.data = &instance->event_data; + + return instance; +} + +void signal_reader_free(SignalReader* instance) { + furi_assert(instance); + furi_assert(instance->gpio_buffer); + furi_assert(instance->bitstream_buffer); + + free(instance->gpio_buffer); + free(instance->bitstream_buffer); + free(instance); +} + +void signal_reader_set_pull(SignalReader* instance, GpioPull pull) { + furi_assert(instance); + + instance->pull = pull; +} + +void signal_reader_set_polarity(SignalReader* instance, SignalReaderPolarity polarity) { + furi_assert(instance); + + instance->polarity = polarity; +} + +void signal_reader_set_sample_rate( + SignalReader* instance, + SignalReaderTimeUnit time_unit, + uint32_t time) { + furi_assert(instance); + UNUSED(time_unit); + + instance->tim_arr = time; +} + +void signal_reader_set_trigger(SignalReader* instance, SignalReaderTrigger trigger) { + furi_assert(instance); + + instance->trigger = trigger; +} + +static void furi_hal_sw_digital_pin_dma_rx_isr(void* context) { + SignalReader* instance = context; + + uint16_t* gpio_buff_start = NULL; + uint8_t* bitstream_buff_start = NULL; + + if(LL_DMA_IsActiveFlag_HT2(SIGNAL_READER_DMA)) { + LL_DMA_ClearFlag_HT2(SIGNAL_READER_DMA); + instance->event.type = SignalReaderEventTypeHalfBufferFilled; + gpio_buff_start = instance->gpio_buffer; + bitstream_buff_start = instance->bitstream_buffer; + + if(instance->callback) { + furi_assert(gpio_buff_start); + furi_assert(bitstream_buff_start); + + for(size_t i = 0; i < instance->buffer_size * 4; i++) { + if((i % 8) == 0) { + bitstream_buff_start[i / 8] = 0; + } + uint8_t bit = 0; + if(instance->polarity == SignalReaderPolarityNormal) { + bit = (gpio_buff_start[i] & instance->pin->pin) == instance->pin->pin; + } else { + bit = (gpio_buff_start[i] & instance->pin->pin) == 0; + } + bitstream_buff_start[i / 8] |= bit << (i % 8); + } + instance->event_data.data = bitstream_buff_start; + instance->event_data.len = instance->buffer_size / 2; + instance->callback(instance->event, instance->context); + } + } + if(LL_DMA_IsActiveFlag_TC2(SIGNAL_READER_DMA)) { + LL_DMA_ClearFlag_TC2(SIGNAL_READER_DMA); + instance->event.type = SignalReaderEventTypeFullBufferFilled; + gpio_buff_start = &instance->gpio_buffer[instance->buffer_size * 4]; + bitstream_buff_start = &instance->bitstream_buffer[instance->buffer_size / 2]; + + if(instance->callback) { + furi_assert(gpio_buff_start); + furi_assert(bitstream_buff_start); + + for(size_t i = 0; i < instance->buffer_size * 4; i++) { + if((i % 8) == 0) { + bitstream_buff_start[i / 8] = 0; + } + uint8_t bit = 0; + if(instance->polarity == SignalReaderPolarityNormal) { + bit = (gpio_buff_start[i] & instance->pin->pin) == instance->pin->pin; + } else { + bit = (gpio_buff_start[i] & instance->pin->pin) == 0; + } + bitstream_buff_start[i / 8] |= bit << (i % 8); + } + instance->event_data.data = bitstream_buff_start; + instance->event_data.len = instance->buffer_size / 2; + instance->callback(instance->event, instance->context); + } + } +} + +void signal_reader_start(SignalReader* instance, SignalReaderCallback callback, void* context) { + furi_assert(instance); + furi_assert(callback); + + instance->callback = callback; + instance->context = context; + + // EXTI delay compensation + instance->tim_cnt_compensation = 9; + instance->cnt_en = SIGNAL_READER_CAPTURE_TIM->CR1; + instance->cnt_en |= TIM_CR1_CEN; + + furi_hal_bus_enable(FuriHalBusTIM16); + + // Capture timer config + LL_TIM_SetPrescaler(SIGNAL_READER_CAPTURE_TIM, 0); + LL_TIM_SetCounterMode(SIGNAL_READER_CAPTURE_TIM, LL_TIM_COUNTERMODE_UP); + LL_TIM_SetAutoReload(SIGNAL_READER_CAPTURE_TIM, instance->tim_arr); + LL_TIM_SetClockDivision(SIGNAL_READER_CAPTURE_TIM, LL_TIM_CLOCKDIVISION_DIV1); + + LL_TIM_DisableARRPreload(SIGNAL_READER_CAPTURE_TIM); + LL_TIM_SetClockSource(SIGNAL_READER_CAPTURE_TIM, LL_TIM_CLOCKSOURCE_INTERNAL); + + // Configure TIM channel CC1 + LL_TIM_OC_InitTypeDef TIM_OC_InitStruct = {}; + TIM_OC_InitStruct.OCMode = LL_TIM_OCMODE_FROZEN; + TIM_OC_InitStruct.OCState = LL_TIM_OCSTATE_DISABLE; + TIM_OC_InitStruct.OCNState = LL_TIM_OCSTATE_DISABLE; + TIM_OC_InitStruct.CompareValue = (instance->tim_arr / 2); + TIM_OC_InitStruct.OCPolarity = LL_TIM_OCPOLARITY_HIGH; + LL_TIM_OC_Init( + SIGNAL_READER_CAPTURE_TIM, SIGNAL_READER_CAPTURE_TIM_CHANNEL, &TIM_OC_InitStruct); + LL_TIM_OC_DisableFast(SIGNAL_READER_CAPTURE_TIM, SIGNAL_READER_CAPTURE_TIM_CHANNEL); + + LL_TIM_SetTriggerOutput(SIGNAL_READER_CAPTURE_TIM, LL_TIM_TRGO_RESET); + LL_TIM_DisableMasterSlaveMode(SIGNAL_READER_CAPTURE_TIM); + + // Start + LL_TIM_GenerateEvent_UPDATE(SIGNAL_READER_CAPTURE_TIM); + + /* We need the EXTI to be configured as interrupt generating line, but no ISR registered */ + furi_hal_gpio_init( + instance->pin, GpioModeInterruptRiseFall, instance->pull, GpioSpeedVeryHigh); + + /* Set DMAMUX request generation signal ID on specified DMAMUX channel */ + LL_DMAMUX_SetRequestSignalID( + DMAMUX1, LL_DMAMUX_REQ_GEN_0, GET_DMAMUX_EXTI_LINE(instance->pin->pin)); + /* Set the polarity of the signal on which the DMA request is generated */ + LL_DMAMUX_SetRequestGenPolarity(DMAMUX1, LL_DMAMUX_REQ_GEN_0, LL_DMAMUX_REQ_GEN_POL_RISING); + /* Set the number of DMA requests that will be authorized after a generation event */ + LL_DMAMUX_SetGenRequestNb(DMAMUX1, LL_DMAMUX_REQ_GEN_0, 1); + + // Configure DMA Sync + LL_DMA_SetMemoryAddress( + SIGNAL_READER_DMA_CNT_SYNC_DEF, (uint32_t)&instance->tim_cnt_compensation); + LL_DMA_SetPeriphAddress( + SIGNAL_READER_DMA_CNT_SYNC_DEF, (uint32_t) & (SIGNAL_READER_CAPTURE_TIM->CNT)); + LL_DMA_ConfigTransfer( + SIGNAL_READER_DMA_CNT_SYNC_DEF, + LL_DMA_DIRECTION_MEMORY_TO_PERIPH | LL_DMA_MODE_CIRCULAR | LL_DMA_PERIPH_NOINCREMENT | + LL_DMA_MEMORY_NOINCREMENT | LL_DMA_PDATAALIGN_HALFWORD | LL_DMA_MDATAALIGN_HALFWORD | + LL_DMA_PRIORITY_VERYHIGH); + LL_DMA_SetDataLength(SIGNAL_READER_DMA_CNT_SYNC_DEF, 1); + LL_DMA_SetPeriphRequest(SIGNAL_READER_DMA_CNT_SYNC_DEF, LL_DMAMUX_REQ_GENERATOR0); + + // Configure DMA Sync + LL_DMA_SetMemoryAddress(SIGNAL_READER_DMA_TRIGGER_DEF, (uint32_t)&instance->cnt_en); + LL_DMA_SetPeriphAddress( + SIGNAL_READER_DMA_TRIGGER_DEF, (uint32_t) & (SIGNAL_READER_CAPTURE_TIM->CR1)); + LL_DMA_ConfigTransfer( + SIGNAL_READER_DMA_TRIGGER_DEF, + LL_DMA_DIRECTION_MEMORY_TO_PERIPH | LL_DMA_PERIPH_NOINCREMENT | LL_DMA_MEMORY_NOINCREMENT | + LL_DMA_PDATAALIGN_HALFWORD | LL_DMA_MDATAALIGN_HALFWORD | LL_DMA_PRIORITY_VERYHIGH); + LL_DMA_SetDataLength(SIGNAL_READER_DMA_TRIGGER_DEF, 1); + LL_DMA_SetPeriphRequest(SIGNAL_READER_DMA_TRIGGER_DEF, LL_DMAMUX_REQ_GENERATOR0); + + // Configure DMA Rx pin + LL_DMA_SetMemoryAddress(SIGNAL_READER_DMA_GPIO_DEF, (uint32_t)instance->gpio_buffer); + LL_DMA_SetPeriphAddress(SIGNAL_READER_DMA_GPIO_DEF, (uint32_t) & (instance->pin->port->IDR)); + LL_DMA_ConfigTransfer( + SIGNAL_READER_DMA_GPIO_DEF, + LL_DMA_DIRECTION_PERIPH_TO_MEMORY | LL_DMA_MODE_CIRCULAR | LL_DMA_PERIPH_NOINCREMENT | + LL_DMA_MEMORY_INCREMENT | LL_DMA_PDATAALIGN_HALFWORD | LL_DMA_MDATAALIGN_HALFWORD | + LL_DMA_PRIORITY_HIGH); + LL_DMA_SetDataLength(SIGNAL_READER_DMA_GPIO_DEF, instance->buffer_size * 8); + LL_DMA_SetPeriphRequest(SIGNAL_READER_DMA_GPIO_DEF, LL_DMAMUX_REQ_TIM16_CH1); + + // Configure DMA Channel CC1 + LL_TIM_EnableDMAReq_CC1(SIGNAL_READER_CAPTURE_TIM); + LL_TIM_CC_EnableChannel(SIGNAL_READER_CAPTURE_TIM, SIGNAL_READER_CAPTURE_TIM_CHANNEL); + + // Start DMA irq, higher priority than normal + furi_hal_interrupt_set_isr_ex( + SIGNAL_READER_DMA_GPIO_IRQ, 14, furi_hal_sw_digital_pin_dma_rx_isr, instance); + + // Start DMA Sync timer + LL_DMA_EnableChannel(SIGNAL_READER_DMA_CNT_SYNC_DEF); + + // Start DMA Rx pin + LL_DMA_EnableChannel(SIGNAL_READER_DMA_GPIO_DEF); + // Strat timer + LL_TIM_SetCounter(SIGNAL_READER_CAPTURE_TIM, 0); + if(instance->trigger == SignalReaderTriggerNone) { + LL_TIM_EnableCounter(SIGNAL_READER_CAPTURE_TIM); + } else { + LL_DMA_EnableChannel(SIGNAL_READER_DMA_TRIGGER_DEF); + } + + LL_DMAMUX_EnableRequestGen(DMAMUX1, LL_DMAMUX_REQ_GEN_0); + // Need to clear flags before enabling DMA !!!! + if(LL_DMA_IsActiveFlag_TC2(SIGNAL_READER_DMA)) LL_DMA_ClearFlag_TC1(SIGNAL_READER_DMA); + if(LL_DMA_IsActiveFlag_TE2(SIGNAL_READER_DMA)) LL_DMA_ClearFlag_TE1(SIGNAL_READER_DMA); + LL_DMA_EnableIT_TC(SIGNAL_READER_DMA_GPIO_DEF); + LL_DMA_EnableIT_HT(SIGNAL_READER_DMA_GPIO_DEF); +} + +void signal_reader_stop(SignalReader* instance) { + furi_assert(instance); + + furi_hal_interrupt_set_isr(SIGNAL_READER_DMA_GPIO_IRQ, NULL, NULL); + + // Deinit DMA Rx pin + LL_DMA_DeInit(SIGNAL_READER_DMA_GPIO_DEF); + // Deinit DMA Sync timer + LL_DMA_DeInit(SIGNAL_READER_DMA_CNT_SYNC_DEF); + // Deinit DMA Trigger timer + LL_DMA_DeInit(SIGNAL_READER_DMA_TRIGGER_DEF); + + furi_hal_bus_disable(FuriHalBusTIM16); +} diff --git a/lib/signal_reader/signal_reader.h b/lib/signal_reader/signal_reader.h new file mode 100644 index 00000000000..be18f295a44 --- /dev/null +++ b/lib/signal_reader/signal_reader.h @@ -0,0 +1,67 @@ +#pragma once + +#include +#include +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + SignalReaderEventTypeHalfBufferFilled, + SignalReaderEventTypeFullBufferFilled, +} SignalReaderEventType; + +typedef struct { + uint8_t* data; + size_t len; +} SignalReaderEventData; + +typedef struct { + SignalReaderEventType type; + SignalReaderEventData* data; +} SignalReaderEvent; + +typedef enum { + SignalReaderTimeUnit64Mhz, +} SignalReaderTimeUnit; + +typedef enum { + SignalReaderPolarityNormal, + SignalReaderPolarityInverted, +} SignalReaderPolarity; + +typedef enum { + SignalReaderTriggerNone, + SignalReaderTriggerRisingFallingEdge, +} SignalReaderTrigger; + +typedef void (*SignalReaderCallback)(SignalReaderEvent event, void* context); + +typedef struct SignalReader SignalReader; + +SignalReader* signal_reader_alloc(const GpioPin* gpio_pin, uint32_t size); + +void signal_reader_free(SignalReader* instance); + +void signal_reader_set_pull(SignalReader* instance, GpioPull pull); + +void signal_reader_set_polarity(SignalReader* instance, SignalReaderPolarity polarity); + +void signal_reader_set_sample_rate( + SignalReader* instance, + SignalReaderTimeUnit time_unit, + uint32_t time); + +void signal_reader_set_trigger(SignalReader* instance, SignalReaderTrigger trigger); + +void signal_reader_start(SignalReader* instance, SignalReaderCallback callback, void* context); + +void signal_reader_stop(SignalReader* instance); + +#ifdef __cplusplus +} +#endif diff --git a/lib/subghz/protocols/bin_raw.c b/lib/subghz/protocols/bin_raw.c index 03f4c5bd4b8..80a34c55f72 100644 --- a/lib/subghz/protocols/bin_raw.c +++ b/lib/subghz/protocols/bin_raw.c @@ -9,6 +9,8 @@ #include #include +#include + #define TAG "SubGhzProtocolBinRaw" //change very carefully, RAM ends at the most inopportune moment diff --git a/lib/toolbox/SConscript b/lib/toolbox/SConscript index 04fd8567fcb..de77f0ccf27 100644 --- a/lib/toolbox/SConscript +++ b/lib/toolbox/SConscript @@ -31,6 +31,8 @@ env.Append( File("protocols/protocol_dict.h"), File("pretty_format.h"), File("hex.h"), + File("simple_array.h"), + File("bit_buffer.h"), ], ) diff --git a/lib/toolbox/bit_buffer.c b/lib/toolbox/bit_buffer.c new file mode 100644 index 00000000000..1943ee833dc --- /dev/null +++ b/lib/toolbox/bit_buffer.c @@ -0,0 +1,336 @@ +#include "bit_buffer.h" + +#include + +#define BITS_IN_BYTE (8) + +struct BitBuffer { + uint8_t* data; + uint8_t* parity; + size_t capacity_bytes; + size_t size_bits; +}; + +BitBuffer* bit_buffer_alloc(size_t capacity_bytes) { + furi_assert(capacity_bytes); + + BitBuffer* buf = malloc(sizeof(BitBuffer)); + + buf->data = malloc(capacity_bytes); + size_t parity_buf_size = (capacity_bytes + BITS_IN_BYTE - 1) / BITS_IN_BYTE; + buf->parity = malloc(parity_buf_size); + buf->capacity_bytes = capacity_bytes; + buf->size_bits = 0; + + return buf; +} + +void bit_buffer_free(BitBuffer* buf) { + furi_assert(buf); + + free(buf->data); + free(buf->parity); + free(buf); +} + +void bit_buffer_reset(BitBuffer* buf) { + furi_assert(buf); + + memset(buf->data, 0, buf->capacity_bytes); + size_t parity_buf_size = (buf->capacity_bytes + BITS_IN_BYTE - 1) / BITS_IN_BYTE; + memset(buf->parity, 0, parity_buf_size); + buf->size_bits = 0; +} + +void bit_buffer_copy(BitBuffer* buf, const BitBuffer* other) { + furi_assert(buf); + furi_assert(other); + + if(buf == other) return; + + furi_assert(buf->capacity_bytes * BITS_IN_BYTE >= other->size_bits); + + memcpy(buf->data, other->data, bit_buffer_get_size_bytes(other)); + buf->size_bits = other->size_bits; +} + +void bit_buffer_copy_right(BitBuffer* buf, const BitBuffer* other, size_t start_index) { + furi_assert(buf); + furi_assert(other); + furi_assert(bit_buffer_get_size_bytes(other) > start_index); + furi_assert(buf->capacity_bytes >= bit_buffer_get_size_bytes(other) - start_index); + + memcpy(buf->data, other->data + start_index, bit_buffer_get_size_bytes(other) - start_index); + buf->size_bits = other->size_bits - start_index * BITS_IN_BYTE; +} + +void bit_buffer_copy_left(BitBuffer* buf, const BitBuffer* other, size_t end_index) { + furi_assert(buf); + furi_assert(other); + furi_assert(bit_buffer_get_capacity_bytes(buf) >= end_index); + furi_assert(bit_buffer_get_size_bytes(other) >= end_index); + + memcpy(buf->data, other->data, end_index); + buf->size_bits = end_index * BITS_IN_BYTE; +} + +void bit_buffer_copy_bytes(BitBuffer* buf, const uint8_t* data, size_t size_bytes) { + furi_assert(buf); + furi_assert(data); + furi_assert(buf->capacity_bytes >= size_bytes); + + memcpy(buf->data, data, size_bytes); + buf->size_bits = size_bytes * BITS_IN_BYTE; +} + +void bit_buffer_copy_bits(BitBuffer* buf, const uint8_t* data, size_t size_bits) { + furi_assert(buf); + furi_assert(data); + furi_assert(buf->capacity_bytes * BITS_IN_BYTE >= size_bits); + + size_t size_bytes = (size_bits + BITS_IN_BYTE - 1) / BITS_IN_BYTE; + memcpy(buf->data, data, size_bytes); + buf->size_bits = size_bits; +} + +void bit_buffer_copy_bytes_with_parity(BitBuffer* buf, const uint8_t* data, size_t size_bits) { + furi_assert(buf); + furi_assert(data); + + size_t bits_processed = 0; + size_t curr_byte = 0; + + if(size_bits < BITS_IN_BYTE + 1) { + buf->size_bits = size_bits; + buf->data[0] = data[0]; + } else { + furi_assert(size_bits % (BITS_IN_BYTE + 1) == 0); + while(bits_processed < size_bits) { + buf->data[curr_byte] = data[bits_processed / BITS_IN_BYTE] >> + (bits_processed % BITS_IN_BYTE); + buf->data[curr_byte] |= data[bits_processed / BITS_IN_BYTE + 1] + << (BITS_IN_BYTE - bits_processed % BITS_IN_BYTE); + uint8_t bit = + FURI_BIT(data[bits_processed / BITS_IN_BYTE + 1], bits_processed % BITS_IN_BYTE); + + if(bits_processed % BITS_IN_BYTE) { + buf->parity[curr_byte / BITS_IN_BYTE] = bit; + } else { + buf->parity[curr_byte / BITS_IN_BYTE] |= bit << (bits_processed % BITS_IN_BYTE); + } + bits_processed += BITS_IN_BYTE + 1; + curr_byte++; + } + buf->size_bits = curr_byte * BITS_IN_BYTE; + } +} + +void bit_buffer_write_bytes(const BitBuffer* buf, void* dest, size_t size_bytes) { + furi_assert(buf); + furi_assert(dest); + furi_assert(bit_buffer_get_size_bytes(buf) <= size_bytes); + + memcpy(dest, buf->data, bit_buffer_get_size_bytes(buf)); +} + +void bit_buffer_write_bytes_with_parity( + const BitBuffer* buf, + void* dest, + size_t size_bytes, + size_t* bits_written) { + furi_assert(buf); + furi_assert(dest); + furi_assert(bits_written); + + size_t buf_size_bytes = bit_buffer_get_size_bytes(buf); + size_t buf_size_with_parity_bytes = + (buf_size_bytes * (BITS_IN_BYTE + 1) + BITS_IN_BYTE) / BITS_IN_BYTE; + furi_assert(buf_size_with_parity_bytes <= size_bytes); + + uint8_t next_par_bit = 0; + uint16_t curr_bit_pos = 0; + uint8_t* bitstream = dest; + + for(size_t i = 0; i < buf_size_bytes; i++) { + next_par_bit = FURI_BIT(buf->parity[i / BITS_IN_BYTE], i % BITS_IN_BYTE); + if(curr_bit_pos % BITS_IN_BYTE == 0) { + bitstream[curr_bit_pos / BITS_IN_BYTE] = buf->data[i]; + curr_bit_pos += BITS_IN_BYTE; + bitstream[curr_bit_pos / BITS_IN_BYTE] = next_par_bit; + curr_bit_pos++; + } else { + bitstream[curr_bit_pos / BITS_IN_BYTE] |= buf->data[i] + << (curr_bit_pos % BITS_IN_BYTE); + bitstream[curr_bit_pos / BITS_IN_BYTE + 1] = + buf->data[i] >> (BITS_IN_BYTE - curr_bit_pos % BITS_IN_BYTE); + bitstream[curr_bit_pos / BITS_IN_BYTE + 1] |= next_par_bit + << (curr_bit_pos % BITS_IN_BYTE); + curr_bit_pos += BITS_IN_BYTE + 1; + } + } + + *bits_written = curr_bit_pos; +} + +void bit_buffer_write_bytes_mid( + const BitBuffer* buf, + void* dest, + size_t start_index, + size_t size_bytes) { + furi_assert(buf); + furi_assert(dest); + furi_assert(start_index + size_bytes <= bit_buffer_get_size_bytes(buf)); + + memcpy(dest, buf->data + start_index, size_bytes); +} + +bool bit_buffer_has_partial_byte(const BitBuffer* buf) { + furi_assert(buf); + + return (buf->size_bits % BITS_IN_BYTE) != 0; +} + +bool bit_buffer_starts_with_byte(const BitBuffer* buf, uint8_t byte) { + furi_assert(buf); + + return bit_buffer_get_size_bytes(buf) && (buf->data[0] == byte); +} + +size_t bit_buffer_get_capacity_bytes(const BitBuffer* buf) { + furi_assert(buf); + + return buf->capacity_bytes; +} + +size_t bit_buffer_get_size(const BitBuffer* buf) { + furi_assert(buf); + + return buf->size_bits; +} + +size_t bit_buffer_get_size_bytes(const BitBuffer* buf) { + furi_assert(buf); + + return (buf->size_bits / BITS_IN_BYTE) + (buf->size_bits % BITS_IN_BYTE ? 1 : 0); +} + +uint8_t bit_buffer_get_byte(const BitBuffer* buf, size_t index) { + furi_assert(buf); + furi_assert(buf->capacity_bytes > index); + + return buf->data[index]; +} + +uint8_t bit_buffer_get_byte_from_bit(const BitBuffer* buf, size_t index_bits) { + furi_assert(buf); + furi_assert(buf->capacity_bytes * BITS_IN_BYTE > index_bits); + + const size_t byte_index = index_bits / BITS_IN_BYTE; + const size_t bit_offset = index_bits % BITS_IN_BYTE; + + const uint8_t lo = buf->data[byte_index] >> bit_offset; + const uint8_t hi = buf->data[byte_index + 1] << (BITS_IN_BYTE - bit_offset); + + return lo | hi; +} + +const uint8_t* bit_buffer_get_data(const BitBuffer* buf) { + furi_assert(buf); + + return buf->data; +} + +const uint8_t* bit_buffer_get_parity(const BitBuffer* buf) { + furi_assert(buf); + + return buf->parity; +} + +void bit_buffer_set_byte(BitBuffer* buf, size_t index, uint8_t byte) { + furi_assert(buf); + + const size_t size_bytes = bit_buffer_get_size_bytes(buf); + furi_assert(size_bytes > index); + + buf->data[index] = byte; +} + +void bit_buffer_set_byte_with_parity(BitBuffer* buff, size_t index, uint8_t byte, bool parity) { + furi_assert(buff); + furi_assert(buff->size_bits / BITS_IN_BYTE > index); + + buff->data[index] = byte; + if((index % BITS_IN_BYTE) == 0) { + buff->parity[index / BITS_IN_BYTE] = parity; + } else { + buff->parity[index / BITS_IN_BYTE] |= parity << (index % BITS_IN_BYTE); + } +} + +void bit_buffer_set_size(BitBuffer* buf, size_t new_size) { + furi_assert(buf); + furi_assert(buf->capacity_bytes * BITS_IN_BYTE >= new_size); + + buf->size_bits = new_size; +} + +void bit_buffer_set_size_bytes(BitBuffer* buf, size_t new_size_bytes) { + furi_assert(buf); + furi_assert(buf->capacity_bytes >= new_size_bytes); + + buf->size_bits = new_size_bytes * BITS_IN_BYTE; +} + +void bit_buffer_append(BitBuffer* buf, const BitBuffer* other) { + bit_buffer_append_right(buf, other, 0); +} + +void bit_buffer_append_right(BitBuffer* buf, const BitBuffer* other, size_t start_index) { + furi_assert(buf); + furi_assert(other); + + const size_t size_bytes = bit_buffer_get_size_bytes(buf); + const size_t other_size_bytes = bit_buffer_get_size_bytes(other) - start_index; + + furi_assert(buf->capacity_bytes >= size_bytes + other_size_bytes); + + memcpy(buf->data + size_bytes, other->data + start_index, other_size_bytes); + buf->size_bits += other->size_bits - start_index * BITS_IN_BYTE; +} + +void bit_buffer_append_byte(BitBuffer* buf, uint8_t byte) { + furi_assert(buf); + + const size_t data_size_bytes = bit_buffer_get_size_bytes(buf); + const size_t new_data_size_bytes = data_size_bytes + 1; + furi_assert(new_data_size_bytes <= buf->capacity_bytes); + + buf->data[data_size_bytes] = byte; + buf->size_bits = new_data_size_bytes * BITS_IN_BYTE; +} + +void bit_buffer_append_bytes(BitBuffer* buf, const uint8_t* data, size_t size_bytes) { + furi_assert(buf); + furi_assert(data); + + const size_t buf_size_bytes = bit_buffer_get_size_bytes(buf); + furi_assert(buf->capacity_bytes >= buf_size_bytes + size_bytes); + + memcpy(&buf->data[buf_size_bytes], data, size_bytes); + buf->size_bits += size_bytes * BITS_IN_BYTE; +} + +void bit_buffer_append_bit(BitBuffer* buf, bool bit) { + furi_assert(buf); + furi_assert( + bit_buffer_get_size_bytes(buf) <= + (buf->capacity_bytes - (bit_buffer_has_partial_byte(buf) ? 0 : 1))); + + if(bit) { + const size_t byte_index = buf->size_bits / BITS_IN_BYTE; + const size_t bit_offset = (buf->size_bits % BITS_IN_BYTE); + buf->data[byte_index] |= 1U << bit_offset; + } + + buf->size_bits++; +} diff --git a/lib/toolbox/bit_buffer.h b/lib/toolbox/bit_buffer.h new file mode 100644 index 00000000000..5c50e729c60 --- /dev/null +++ b/lib/toolbox/bit_buffer.h @@ -0,0 +1,325 @@ +#pragma once + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct BitBuffer BitBuffer; + +// Construction, deletion, reset + +/** + * Allocate a BitBuffer instance. + * + * @param [in] capacity_bytes maximum buffer capacity, in bytes + * @return pointer to the allocated BitBuffer instance + */ +BitBuffer* bit_buffer_alloc(size_t capacity_bytes); + +/** + * Delete a BitBuffer instance. + * + * @param [in,out] buf pointer to a BitBuffer instance + */ +void bit_buffer_free(BitBuffer* buf); + +/** + * Clear all data from a BitBuffer instance. + * + * @param [in,out] buf pointer to a BitBuffer instance + */ +void bit_buffer_reset(BitBuffer* buf); + +// Copy and write + +/** + * Copy another BitBuffer instance's contents to this one, replacing + * all of the original data. + * The destination capacity must be no less than the source data size. + * + * @param [in,out] buf pointer to a BitBuffer instance to copy into + * @param [in] other pointer to a BitBuffer instance to copy from + * @note + */ +void bit_buffer_copy(BitBuffer* buf, const BitBuffer* other); + +/** + * Copy all BitBuffer instance's contents to this one, starting from start_index, + * replacing all of the original data. + * The destination capacity must be no less than the source data size + * counting from start_index. + * + * @param [in,out] buf pointer to a BitBuffer instance to copy into + * @param [in] other pointer to a BitBuffer instance to copy from + * @param [in] start_index index to begin copying source data from + */ +void bit_buffer_copy_right(BitBuffer* buf, const BitBuffer* other, size_t start_index); + +/** + * Copy all BitBuffer instance's contents to this one, ending with end_index, + * replacing all of the original data. + * The destination capacity must be no less than the source data size + * counting to end_index. + * + * @param [in,out] buf pointer to a BitBuffer instance to copy into + * @param [in] other pointer to a BitBuffer instance to copy from + * @param [in] end_index index to end copying source data at + */ +void bit_buffer_copy_left(BitBuffer* buf, const BitBuffer* other, size_t end_index); + +/** + * Copy a byte array to a BitBuffer instance, replacing all of the original data. + * The destination capacity must be no less than the source data size. + * + * @param [in,out] buf pointer to a BitBuffer instance to copy into + * @param [in] data pointer to the byte array to be copied + * @param [in] size_bytes size of the data to be copied, in bytes + */ +void bit_buffer_copy_bytes(BitBuffer* buf, const uint8_t* data, size_t size_bytes); + +/** + * Copy a byte array to a BitBuffer instance, replacing all of the original data. + * The destination capacity must be no less than the source data size. + * + * @param [in,out] buf pointer to a BitBuffer instance to copy into + * @param [in] data pointer to the byte array to be copied + * @param [in] size_bits size of the data to be copied, in bits + */ +void bit_buffer_copy_bits(BitBuffer* buf, const uint8_t* data, size_t size_bits); + +/** + * Copy a byte with parity array to a BitBuffer instance, replacing all of the original data. + * The destination capacity must be no less than the source data size. + * + * @param [in,out] buf pointer to a BitBuffer instance to copy into + * @param [in] data pointer to the byte array to be copied + * @param [in] size_bitss size of the data to be copied, in bits + */ +void bit_buffer_copy_bytes_with_parity(BitBuffer* buf, const uint8_t* data, size_t size_bits); + +/** + * Write a BitBuffer instance's entire contents to an arbitrary memory location. + * The destination memory must be allocated. Additionally, the destination + * capacity must be no less than the source data size. + * + * @param [in] buf pointer to a BitBuffer instance to write from + * @param [out] dest pointer to the destination memory location + * @param [in] size_bytes maximum destination data size, in bytes + */ +void bit_buffer_write_bytes(const BitBuffer* buf, void* dest, size_t size_bytes); + +/** + * Write a BitBuffer instance's entire contents to an arbitrary memory location. + * Additionally, place a parity bit after each byte. + * The destination memory must be allocated. Additionally, the destination + * capacity must be no less than the source data size plus parity. + * + * @param [in] buf pointer to a BitBuffer instance to write from + * @param [out] dest pointer to the destination memory location + * @param [in] size_bytes maximum destination data size, in bytes + * @param [out] bits_written actual number of bits writen, in bits + */ +void bit_buffer_write_bytes_with_parity( + const BitBuffer* buf, + void* dest, + size_t size_bytes, + size_t* bits_written); + +/** + * Write a slice of BitBuffer instance's contents to an arbitrary memory location. + * The destination memory must be allocated. Additionally, the destination + * capacity must be no less than the requested slice size. + * + * @param [in] buf pointer to a BitBuffer instance to write from + * @param [out] dest pointer to the destination memory location + * @param [in] start_index index to begin copying source data from + * @param [in] size_bytes data slice size, in bytes + */ +void bit_buffer_write_bytes_mid( + const BitBuffer* buf, + void* dest, + size_t start_index, + size_t size_bytes); + +// Checks + +/** + * Check whether a BitBuffer instance contains a partial byte (i.e. the bit count + * is not divisible by 8). + * + * @param [in] buf pointer to a BitBuffer instance to be checked + * @return true if the instance contains a partial byte, false otherwise + */ +bool bit_buffer_has_partial_byte(const BitBuffer* buf); + +/** + * Check whether a BitBuffer instance's contents start with the designated byte. + * + * @param [in] buf pointer to a BitBuffer instance to be checked + * @param [in] byte byte value to be checked against + * @return true if data starts with designated byte, false otherwise + */ +bool bit_buffer_starts_with_byte(const BitBuffer* buf, uint8_t byte); + +// Getters + +/** + * Get a BitBuffer instance's capacity (i.e. the maximum possible amount of data), in bytes. + * + * @param [in] buf pointer to a BitBuffer instance to be queried + * @return capacity, in bytes + */ +size_t bit_buffer_get_capacity_bytes(const BitBuffer* buf); + +/** + * Get a BitBuffer instance's data size (i.e. the amount of stored data), in bits. + * Might be not divisible by 8 (see bit_buffer_is_partial_byte). + * + * @param [in] buf pointer to a BitBuffer instance to be queried + * @return data size, in bits. + */ +size_t bit_buffer_get_size(const BitBuffer* buf); + +/** + * Get a BitBuffer instance's data size (i.e. the amount of stored data), in bytes. + * If a partial byte is present, it is also counted. + * + * @param [in] buf pointer to a BitBuffer instance to be queried + * @return data size, in bytes. + */ +size_t bit_buffer_get_size_bytes(const BitBuffer* buf); + +/** + * Get a byte value at a specified index in a BitBuffer instance. + * The index must be valid (i.e. less than the instance's data size in bytes). + * + * @param [in] buf pointer to a BitBuffer instance to be queried + * @param [in] index index of the byte in question + */ +uint8_t bit_buffer_get_byte(const BitBuffer* buf, size_t index); + +/** + * Get a byte value starting from the specified bit index in a BitBuffer instance. + * The resulting byte might correspond to a single byte (if the index is a multiple + * of 8), or two overlapping bytes combined. + * The index must be valid (i.e. less than the instance's data size in bits). + * + * @param [in] buf pointer to a BitBuffer instance to be queried + * @param [in] index bit index of the byte in question + */ +uint8_t bit_buffer_get_byte_from_bit(const BitBuffer* buf, size_t index_bits); + +/** + * Get the pointer to a BitBuffer instance's underlying data. + * + * @param [in] buf pointer to a BitBuffer instance to be queried + * @return pointer to the underlying data + */ +const uint8_t* bit_buffer_get_data(const BitBuffer* buf); + +/** + * Get the pointer to a BitBuffer instance's underlying data. + * + * @param [in] buf pointer to a BitBuffer instance to be queried + * @return pointer to the underlying data + */ +const uint8_t* bit_buffer_get_parity(const BitBuffer* buf); + +// Setters + +/** + * Set byte value at a specified index in a BitBuffer instance. + * The index must be valid (i.e. less than the instance's data size in bytes). + * + * @param [in,out] buf pointer to a BitBuffer instance to be modified + * @param [in] index index of the byte in question + * @param [in] byte byte value to be set at index + */ +void bit_buffer_set_byte(BitBuffer* buf, size_t index, uint8_t byte); + +/** + * Set byte and parity bit value at a specified index in a BitBuffer instance. + * The index must be valid (i.e. less than the instance's data size in bytes). + * + * @param [in,out] buf pointer to a BitBuffer instance to be modified + * @param [in] index index of the byte in question + * @param [in] byte byte value to be set at index + * @param [in] parity parity bit value to be set at index + */ +void bit_buffer_set_byte_with_parity(BitBuffer* buff, size_t index, uint8_t byte, bool parity); + +/** + * Resize a BitBuffer instance to a new size, in bits. + * @warning May cause bugs. Use only if absolutely necessary. + * + * @param [in,out] buf pointer to a BitBuffer instance to be resized + * @param [in] new_size the new size of the buffer, in bits + */ +void bit_buffer_set_size(BitBuffer* buf, size_t new_size); + +/** + * Resize a BitBuffer instance to a new size, in bytes. + * @warning May cause bugs. Use only if absolutely necessary. + * + * @param [in,out] buf pointer to a BitBuffer instance to be resized + * @param [in] new_size_bytes the new size of the buffer, in bytes + */ +void bit_buffer_set_size_bytes(BitBuffer* buf, size_t new_size_bytes); + +// Modification + +/** + * Append all BitBuffer's instance contents to this one. The destination capacity + * must be no less than its original data size plus source data size. + * + * @param [in,out] buf pointer to a BitBuffer instance to be appended to + * @param [in] other pointer to a BitBuffer instance to be appended + */ +void bit_buffer_append(BitBuffer* buf, const BitBuffer* other); + +/** + * Append a BitBuffer's instance contents to this one, starting from start_index. + * The destination capacity must be no less than the source data size + * counting from start_index. + * + * @param [in,out] buf pointer to a BitBuffer instance to be appended to + * @param [in] other pointer to a BitBuffer instance to be appended + * @param [in] start_index index to begin copying source data from + */ +void bit_buffer_append_right(BitBuffer* buf, const BitBuffer* other, size_t start_index); + +/** + * Append a byte to a BitBuffer instance. + * The destination capacity must be no less its original data size plus one. + * + * @param [in,out] buf pointer to a BitBuffer instance to be appended to + * @param [in] byte byte value to be appended + */ +void bit_buffer_append_byte(BitBuffer* buf, uint8_t byte); + +/** + * Append a byte array to a BitBuffer instance. + * The destination capacity must be no less its original data size plus source data size. + * + * @param [in,out] buf pointer to a BitBuffer instance to be appended to + * @param [in] data pointer to the byte array to be appended + * @param [in] size_bytes size of the data to be appended, in bytes + */ +void bit_buffer_append_bytes(BitBuffer* buf, const uint8_t* data, size_t size_bytes); + +/** + * Append a bit to a BitBuffer instance. + * The destination capacity must be sufficient to accomodate the additional bit. + * + * @param [in,out] buf pointer to a BitBuffer instance to be appended to + * @param [in] bit bit value to be appended + */ +void bit_buffer_append_bit(BitBuffer* buf, bool bit); + +#ifdef __cplusplus +} +#endif diff --git a/lib/toolbox/simple_array.c b/lib/toolbox/simple_array.c new file mode 100644 index 00000000000..7aed8e34be3 --- /dev/null +++ b/lib/toolbox/simple_array.c @@ -0,0 +1,127 @@ +#include "simple_array.h" + +#include + +struct SimpleArray { + const SimpleArrayConfig* config; + SimpleArrayElement* data; + uint32_t count; +}; + +SimpleArray* simple_array_alloc(const SimpleArrayConfig* config) { + SimpleArray* instance = malloc(sizeof(SimpleArray)); + instance->config = config; + return instance; +} + +void simple_array_free(SimpleArray* instance) { + furi_assert(instance); + + simple_array_reset(instance); + free(instance); +} + +void simple_array_init(SimpleArray* instance, uint32_t count) { + furi_assert(instance); + furi_assert(count > 0); + + simple_array_reset(instance); + + instance->data = malloc(count * instance->config->type_size); + instance->count = count; + + SimpleArrayInit init = instance->config->init; + if(init) { + for(uint32_t i = 0; i < instance->count; ++i) { + init(simple_array_get(instance, i)); + } + } +} + +void simple_array_reset(SimpleArray* instance) { + furi_assert(instance); + + if(instance->data) { + SimpleArrayReset reset = instance->config->reset; + + if(reset) { + for(uint32_t i = 0; i < instance->count; ++i) { + reset(simple_array_get(instance, i)); + } + } + + free(instance->data); + + instance->count = 0; + instance->data = NULL; + } +} + +void simple_array_copy(SimpleArray* instance, const SimpleArray* other) { + furi_assert(instance); + furi_assert(other); + furi_assert(instance->config == other->config); + + simple_array_reset(instance); + + if(other->count == 0) { + return; + } + + simple_array_init(instance, other->count); + + SimpleArrayCopy copy = instance->config->copy; + if(copy) { + for(uint32_t i = 0; i < other->count; ++i) { + copy(simple_array_get(instance, i), simple_array_cget(other, i)); + } + } else { + memcpy(instance->data, other->data, other->count * instance->config->type_size); + } +} + +bool simple_array_is_equal(const SimpleArray* instance, const SimpleArray* other) { + furi_assert(instance); + furi_assert(other); + + // Equal if the same object + if(instance == other) return true; + + return (instance->config == other->config) && (instance->count == other->count) && + ((instance->data == other->data) || (instance->data == NULL) || (other->data == NULL) || + (memcmp(instance->data, other->data, other->count) == 0)); +} + +uint32_t simple_array_get_count(const SimpleArray* instance) { + furi_assert(instance); + return instance->count; +} + +SimpleArrayElement* simple_array_get(SimpleArray* instance, uint32_t index) { + furi_assert(instance); + furi_assert(index < instance->count); + + return instance->data + index * instance->config->type_size; +} + +const SimpleArrayElement* simple_array_cget(const SimpleArray* instance, uint32_t index) { + return simple_array_get((SimpleArrayElement*)instance, index); +} + +SimpleArrayData* simple_array_get_data(SimpleArray* instance) { + furi_assert(instance); + furi_assert(instance->data); + + return instance->data; +} + +const SimpleArrayData* simple_array_cget_data(const SimpleArray* instance) { + return simple_array_get_data((SimpleArray*)instance); +} + +const SimpleArrayConfig simple_array_config_uint8_t = { + .init = NULL, + .copy = NULL, + .reset = NULL, + .type_size = sizeof(uint8_t), +}; diff --git a/lib/toolbox/simple_array.h b/lib/toolbox/simple_array.h new file mode 100644 index 00000000000..a608db08d87 --- /dev/null +++ b/lib/toolbox/simple_array.h @@ -0,0 +1,148 @@ +/** + * @file simple_array.h + * + * @brief This file provides a simple (non-type safe) array for elements with + * (optional) in-place construction support. + * + * No append, remove or take operations are supported. + * Instead, the user must call simple_array_init() in order to reserve the memory for n elements. + * In case if init() is specified in the configuration, then the elements are constructed in-place. + * + * To clear all elements from the array, call simple_array_reset(), which will also call reset() + * for each element in case if it was specified in the configuration. This is useful if a custom + * destructor is required for a particular type. Calling simple_array_free() will also result in a + * simple_array_reset() call automatically. + * + */ +#pragma once + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct SimpleArray SimpleArray; + +typedef void SimpleArrayData; +typedef void SimpleArrayElement; + +typedef void (*SimpleArrayInit)(SimpleArrayElement* elem); +typedef void (*SimpleArrayReset)(SimpleArrayElement* elem); +typedef void (*SimpleArrayCopy)(SimpleArrayElement* elem, const SimpleArrayElement* other); + +/** Simple Array configuration structure. Defined per type. */ +typedef struct { + SimpleArrayInit init; /**< Initialisation (in-place constructor) method. */ + SimpleArrayReset reset; /**< Reset (custom destructor) method. */ + SimpleArrayCopy copy; /**< Copy (custom copy-constructor) method. */ + const size_t type_size; /** Type size, in bytes. */ +} SimpleArrayConfig; + +/** + * Allocate a SimpleArray instance with the given configuration. + * + * @param [in] config Pointer to the type-specific configuration + * @return Pointer to the allocated SimpleArray instance + */ +SimpleArray* simple_array_alloc(const SimpleArrayConfig* config); + +/** + * Free a SimpleArray instance and release its contents. + * + * @param [in] instance Pointer to the SimpleArray instance to be freed + */ +void simple_array_free(SimpleArray* instance); + +/** + * Initialise a SimpleArray instance by allocating additional space to contain + * the requested number of elements. + * If init() is specified in the config, then it is called for each element, + * otherwise the data is filled with zeroes. + * + * @param [in] instance Pointer to the SimpleArray instance to be init'd + * @param [in] count Number of elements to be allocated and init'd + */ +void simple_array_init(SimpleArray* instance, uint32_t count); + +/** + * Reset a SimpleArray instance and delete all of its elements. + * If reset() is specified in the config, then it is called for each element, + * otherwise the data is simply free()'d. + * + * @param [in] instance Pointer to the SimpleArray instance to be reset + */ +void simple_array_reset(SimpleArray* instance); + +/** + * Copy (duplicate) another SimpleArray instance to this one. + * If copy() is specified in the config, then it is called for each element, + * otherwise the data is simply memcpy()'d. + * + * @param [in] instance Pointer to the SimpleArray instance to copy to + * @param [in] other Pointer to the SimpleArray instance to copy from + */ +void simple_array_copy(SimpleArray* instance, const SimpleArray* other); + +/** + * Check if another SimpleArray instance is equal (the same object or holds the + * same data) to this one. + * + * @param [in] instance Pointer to the SimpleArray instance to be compared + * @param [in] other Pointer to the SimpleArray instance to be compared + * @return True if instances are considered equal, false otherwise + */ +bool simple_array_is_equal(const SimpleArray* instance, const SimpleArray* other); + +/** + * Get the count of elements currently contained in a SimpleArray instance. + * + * @param [in] instance Pointer to the SimpleArray instance to query the count from + * @return Count of elements contained in the instance + */ +uint32_t simple_array_get_count(const SimpleArray* instance); + +/** + * Get a pointer to an element contained in a SimpleArray instance. + * + * @param [in] instance Pointer to the SimpleArray instance to get an element from + * @param [in] index Index of the element in question. MUST be less than total element count + * @return Pointer to the element specified by index + */ +SimpleArrayElement* simple_array_get(SimpleArray* instance, uint32_t index); + +/** + * Get a const pointer to an element contained in a SimpleArray instance. + * + * @param [in] instance Pointer to the SimpleArray instance to get an element from + * @param [in] index Index of the element in question. MUST be less than total element count + * @return Const pointer to the element specified by index + */ +const SimpleArrayElement* simple_array_cget(const SimpleArray* instance, uint32_t index); + +/** + * Get a pointer to the internal data of a SimpleArray instance. + * + * @param [in] instance Pointer to the SimpleArray instance to get the data of + * @return Pointer to the instance's internal data + */ +SimpleArrayData* simple_array_get_data(SimpleArray* instance); + +/** + * Get a constant pointer to the internal data of a SimpleArray instance. + * + * @param [in] instance Pointer to the SimpleArray instance to get the data of + * @return Constant pointer to the instance's internal data + */ +const SimpleArrayData* simple_array_cget_data(const SimpleArray* instance); + +// Standard preset configurations + +// Preset configuration for a byte(uint8_t) array. +extern const SimpleArrayConfig simple_array_config_uint8_t; + +#ifdef __cplusplus +} +#endif From 844e0f10e59ef53a6be2b71106b9d143fafeab01 Mon Sep 17 00:00:00 2001 From: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> Date: Sat, 28 Oct 2023 16:56:49 +0300 Subject: [PATCH 786/824] [FL-3639] Fix MF DESFire record file handling (#3167) --- .../nfc/helpers/protocol_support/mf_desfire/mf_desfire_render.c | 1 + lib/nfc/protocols/mf_desfire/mf_desfire.h | 1 + lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire_render.c b/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire_render.c index 883ea720b05..b5ca0d1383b 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire_render.c +++ b/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire_render.c @@ -197,6 +197,7 @@ void nfc_render_mf_desfire_file_settings_data( furi_string_cat_printf(str, "size %lu\n", record_size); break; case MfDesfireFileTypeValue: + record_size = MF_DESFIRE_VALUE_SIZE; furi_string_cat_printf( str, "lo %lu hi %lu\n", settings->value.lo_limit, settings->value.hi_limit); furi_string_cat_printf( diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire.h b/lib/nfc/protocols/mf_desfire/mf_desfire.h index 4d09dc85103..8505b792aa5 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire.h +++ b/lib/nfc/protocols/mf_desfire/mf_desfire.h @@ -29,6 +29,7 @@ extern "C" { #define MF_DESFIRE_UID_SIZE (7) #define MF_DESFIRE_BATCH_SIZE (5) #define MF_DESFIRE_APP_ID_SIZE (3) +#define MF_DESFIRE_VALUE_SIZE (4) typedef struct { uint8_t hw_vendor; diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c b/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c index e86cb4c68c2..17377d66e3c 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c @@ -353,7 +353,7 @@ MfDesfireError mf_desfire_poller_async_read_file_records( furi_assert(instance); bit_buffer_reset(instance->input_buffer); - bit_buffer_append_byte(instance->input_buffer, MF_DESFIRE_CMD_READ_DATA); + bit_buffer_append_byte(instance->input_buffer, MF_DESFIRE_CMD_READ_RECORDS); bit_buffer_append_byte(instance->input_buffer, id); bit_buffer_append_bytes(instance->input_buffer, (const uint8_t*)&offset, 3); bit_buffer_append_bytes(instance->input_buffer, (const uint8_t*)&size, 3); From 3d872cf37a2c4ec616021e1a6da4115610a6637a Mon Sep 17 00:00:00 2001 From: gornekich Date: Sat, 28 Oct 2023 18:22:07 +0400 Subject: [PATCH 787/824] [FL-3637] NFC RC fixes (#3165) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * firmware: remove nfc lib build settings section * furi hal nfc: fix nfc irq gpio deinit * lib nfc: remove deprecated exception from sources * nfc: use ASK demodulator in transparent mode * mf ultralight: add upper page bound for NTAGI2C1K * furi hal nfc: set event if nfc event was started * nfc: fix PVS warnings * lib signal reader: remove gpio pull setting in alloc * furi: added math.h include for compatibility with existing apps * nfc: remove resolved TODO in mf desfire poller * bump api symbol version Co-authored-by: hedger Co-authored-by: あく --- firmware.scons | 9 --------- firmware/targets/f18/api_symbols.csv | 2 +- firmware/targets/f7/api_symbols.csv | 2 +- firmware/targets/f7/furi_hal/furi_hal_nfc_event.c | 5 +++-- firmware/targets/f7/furi_hal/furi_hal_nfc_irq.c | 2 +- firmware/targets/f7/furi_hal/furi_hal_nfc_iso15693.c | 2 +- furi/furi.h | 3 +++ lib/nfc/SConscript | 2 +- lib/nfc/protocols/mf_ultralight/mf_ultralight.c | 2 +- .../protocols/mf_ultralight/mf_ultralight_listener_i.c | 2 ++ lib/signal_reader/parsers/iso15693/iso15693_parser.c | 2 +- 11 files changed, 15 insertions(+), 18 deletions(-) diff --git a/firmware.scons b/firmware.scons index d8e96ad7d03..82f775d719c 100644 --- a/firmware.scons +++ b/firmware.scons @@ -71,15 +71,6 @@ env = ENV.Clone( "FURI_DEBUG" if ENV["DEBUG"] else "FURI_NDEBUG", ], }, - "nfc": { - "CCFLAGS": [ - "-Og", - ], - "CPPDEFINES": [ - "NDEBUG", - "FURI_DEBUG", - ], - }, }, FW_API_TABLE=None, _APP_ICONS=None, diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index 8e15030a94a..4789d316d62 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,40.0,, +Version,+,40.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 3ae099b5f14..038a22eae43 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,40.0,, +Version,+,40.1,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc_event.c b/firmware/targets/f7/furi_hal/furi_hal_nfc_event.c index cce16c5dc9b..266b606fd1b 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_nfc_event.c +++ b/firmware/targets/f7/furi_hal/furi_hal_nfc_event.c @@ -25,9 +25,10 @@ FuriHalNfcError furi_hal_nfc_event_stop() { void furi_hal_nfc_event_set(FuriHalNfcEventInternalType event) { furi_assert(furi_hal_nfc_event); - furi_assert(furi_hal_nfc_event->thread); - furi_thread_flags_set(furi_hal_nfc_event->thread, event); + if(furi_hal_nfc_event->thread) { + furi_thread_flags_set(furi_hal_nfc_event->thread, event); + } } FuriHalNfcError furi_hal_nfc_abort() { diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc_irq.c b/firmware/targets/f7/furi_hal/furi_hal_nfc_irq.c index 63dc3541550..170d8dee619 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_nfc_irq.c +++ b/firmware/targets/f7/furi_hal/furi_hal_nfc_irq.c @@ -23,6 +23,6 @@ void furi_hal_nfc_init_gpio_isr() { } void furi_hal_nfc_deinit_gpio_isr() { - furi_hal_gpio_init(&gpio_nfc_irq_rfid_pull, GpioModeOutputOpenDrain, GpioPullNo, GpioSpeedLow); furi_hal_gpio_remove_int_callback(&gpio_nfc_irq_rfid_pull); + furi_hal_gpio_init(&gpio_nfc_irq_rfid_pull, GpioModeAnalog, GpioPullNo, GpioSpeedLow); } diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc_iso15693.c b/firmware/targets/f7/furi_hal/furi_hal_nfc_iso15693.c index 19ac6dc034f..cc936644b4e 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_nfc_iso15693.c +++ b/firmware/targets/f7/furi_hal/furi_hal_nfc_iso15693.c @@ -48,7 +48,7 @@ static FuriHalNfcIso15693Listener* furi_hal_nfc_iso15693_listener_alloc() { instance->signal = iso15693_signal_alloc(&gpio_spi_r_mosi); instance->parser = - iso15693_parser_alloc(&gpio_spi_r_miso, FURI_HAL_NFC_ISO15693_MAX_FRAME_SIZE); + iso15693_parser_alloc(&gpio_nfc_irq_rfid_pull, FURI_HAL_NFC_ISO15693_MAX_FRAME_SIZE); return instance; } diff --git a/furi/furi.h b/furi/furi.h index cfdeb2c0f40..b1299c9a952 100644 --- a/furi/furi.h +++ b/furi/furi.h @@ -24,6 +24,9 @@ // FreeRTOS timer, REMOVE AFTER REFACTORING #include +// Workaround for math.h leaking through HAL in older versions +#include + #ifdef __cplusplus extern "C" { #endif diff --git a/lib/nfc/SConscript b/lib/nfc/SConscript index 0e09b693923..6c55cf5d2bf 100644 --- a/lib/nfc/SConscript +++ b/lib/nfc/SConscript @@ -51,7 +51,7 @@ env.Append( libenv = env.Clone(FW_LIB_NAME="nfc") libenv.ApplyLibFlags() -sources = libenv.GlobRecursive("*.c*", exclude="deprecated/*c") +sources = libenv.GlobRecursive("*.c*") lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources) libenv.Install("${LIB_DIST_DIR}", lib) diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight.c index 40d99768636..fd845f3c085 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight.c +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight.c @@ -297,7 +297,7 @@ bool mf_ultralight_load(MfUltralightData* data, FlipperFormat* ff, uint32_t vers uint32_t pages_total = 0; if(!flipper_format_read_uint32(ff, MF_ULTRALIGHT_PAGES_TOTAL_KEY, &pages_total, 1)) break; uint32_t pages_read = 0; - if(data_format_version < mf_ultralight_data_format_version) { + if(data_format_version < mf_ultralight_data_format_version) { //-V547 pages_read = pages_total; } else { if(!flipper_format_read_uint32(ff, MF_ULTRALIGHT_PAGES_READ_KEY, &pages_read, 1)) diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_listener_i.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight_listener_i.c index 3d6b9a94fdf..64647492de1 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_listener_i.c +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_listener_i.c @@ -518,6 +518,8 @@ static uint16_t mf_ultralight_get_upper_page_bound(MfUltralightType type) { upper_page_bound = 511; else if(type == MfUltralightTypeNTAGI2C2K) upper_page_bound = 479; + else if(type == MfUltralightTypeNTAGI2C1K) + upper_page_bound = 225; else { upper_page_bound = mf_ultralight_get_config_page_num(type) - 2; } diff --git a/lib/signal_reader/parsers/iso15693/iso15693_parser.c b/lib/signal_reader/parsers/iso15693/iso15693_parser.c index a398516c925..c5326a354d7 100644 --- a/lib/signal_reader/parsers/iso15693/iso15693_parser.c +++ b/lib/signal_reader/parsers/iso15693/iso15693_parser.c @@ -68,7 +68,7 @@ Iso15693Parser* iso15693_parser_alloc(const GpioPin* pin, size_t max_frame_size) signal_reader_set_sample_rate( instance->signal_reader, SignalReaderTimeUnit64Mhz, ISO15693_PARSER_BITRATE_F64MHZ); signal_reader_set_pull(instance->signal_reader, GpioPullDown); - signal_reader_set_polarity(instance->signal_reader, SignalReaderPolarityInverted); + signal_reader_set_polarity(instance->signal_reader, SignalReaderPolarityNormal); signal_reader_set_trigger(instance->signal_reader, SignalReaderTriggerRisingFallingEdge); return instance; From cfaf7455232db493f81ce5c036100029fd18bad2 Mon Sep 17 00:00:00 2001 From: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> Date: Sat, 28 Oct 2023 17:29:14 +0300 Subject: [PATCH 788/824] [FL-3643] Fix crash when reading files > 64B (#3166) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Increase MF DESFire result buffer * Ignore chunks that do not fit into the result buffer and show warning * Display information about partial files Co-authored-by: あく --- .../mf_desfire/mf_desfire_render.c | 18 +++++++++++++++++- .../protocols/mf_desfire/mf_desfire_poller.c | 11 ++++++----- .../protocols/mf_desfire/mf_desfire_poller_i.c | 10 +++++++++- 3 files changed, 32 insertions(+), 7 deletions(-) diff --git a/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire_render.c b/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire_render.c index b5ca0d1383b..94b333f5523 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire_render.c +++ b/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire_render.c @@ -190,6 +190,8 @@ void nfc_render_mf_desfire_file_settings_data( uint32_t record_count = 1; uint32_t record_size = 0; + const uint32_t total_size = simple_array_get_count(data->data); + switch(settings->type) { case MfDesfireFileTypeStandard: case MfDesfireFileTypeBackup: @@ -220,9 +222,20 @@ void nfc_render_mf_desfire_file_settings_data( } for(uint32_t rec = 0; rec < record_count; rec++) { - furi_string_cat_printf(str, "record %lu\n", rec); + const uint32_t size_offset = rec * record_size; + const uint32_t size_remaining = total_size > size_offset ? total_size - size_offset : 0; + + if(size_remaining < record_size) { + furi_string_cat_printf( + str, "record %lu (partial %lu of %lu)\n", rec, size_remaining, record_size); + record_size = size_remaining; + } else { + furi_string_cat_printf(str, "record %lu\n", rec); + } + for(uint32_t ch = 0; ch < record_size; ch += 4) { furi_string_cat_printf(str, "%03lx|", ch); + for(uint32_t i = 0; i < 4; i++) { if(ch + i < record_size) { const uint32_t data_index = rec * record_size + ch + i; @@ -233,6 +246,7 @@ void nfc_render_mf_desfire_file_settings_data( furi_string_cat_printf(str, " "); } } + for(uint32_t i = 0; i < 4 && ch + i < record_size; i++) { const uint32_t data_index = rec * record_size + ch + i; const uint8_t data_byte = @@ -243,8 +257,10 @@ void nfc_render_mf_desfire_file_settings_data( furi_string_cat_printf(str, "."); } } + furi_string_push_back(str, '\n'); } + furi_string_push_back(str, '\n'); } } diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c b/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c index c74bbc89da6..11db021d57c 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c @@ -6,7 +6,8 @@ #define TAG "MfDesfirePoller" -#define MF_DESFIRE_BUF_SIZE_MAX (64U) +#define MF_DESFIRE_BUF_SIZE (64U) +#define MF_DESFIRE_RESULT_BUF_SIZE (512U) typedef NfcCommand (*MfDesfirePollerReadHandler)(MfDesfirePoller* instance); @@ -20,10 +21,10 @@ static MfDesfirePoller* mf_desfire_poller_alloc(Iso14443_4aPoller* iso14443_4a_p MfDesfirePoller* instance = malloc(sizeof(MfDesfirePoller)); instance->iso14443_4a_poller = iso14443_4a_poller; instance->data = mf_desfire_alloc(); - instance->tx_buffer = bit_buffer_alloc(MF_DESFIRE_BUF_SIZE_MAX); - instance->rx_buffer = bit_buffer_alloc(MF_DESFIRE_BUF_SIZE_MAX); - instance->input_buffer = bit_buffer_alloc(MF_DESFIRE_BUF_SIZE_MAX); - instance->result_buffer = bit_buffer_alloc(MF_DESFIRE_BUF_SIZE_MAX); + instance->tx_buffer = bit_buffer_alloc(MF_DESFIRE_BUF_SIZE); + instance->rx_buffer = bit_buffer_alloc(MF_DESFIRE_BUF_SIZE); + instance->input_buffer = bit_buffer_alloc(MF_DESFIRE_BUF_SIZE); + instance->result_buffer = bit_buffer_alloc(MF_DESFIRE_RESULT_BUF_SIZE); instance->mf_desfire_event.data = &instance->mf_desfire_event_data; diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c b/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c index 17377d66e3c..38ae2f466d0 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c @@ -59,7 +59,15 @@ MfDesfireError mf_desfire_send_chunks( break; } - bit_buffer_append_right(rx_buffer, instance->rx_buffer, sizeof(uint8_t)); + const size_t rx_size = bit_buffer_get_size_bytes(instance->rx_buffer); + const size_t rx_capacity_remaining = + bit_buffer_get_capacity_bytes(rx_buffer) - bit_buffer_get_size_bytes(rx_buffer); + + if(rx_size <= rx_capacity_remaining) { + bit_buffer_append_right(rx_buffer, instance->rx_buffer, sizeof(uint8_t)); + } else { + FURI_LOG_W(TAG, "RX buffer overflow: ignoring %zu bytes", rx_size); + } } } while(false); From 0fe93fcfa48ea08479b72c6650fbf4ccf95d445d Mon Sep 17 00:00:00 2001 From: Augusto Zanellato Date: Sat, 28 Oct 2023 16:45:08 +0200 Subject: [PATCH 789/824] fix crash after st25tb save (#3170) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- .../main/nfc/helpers/protocol_support/st25tb/st25tb.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/applications/main/nfc/helpers/protocol_support/st25tb/st25tb.c b/applications/main/nfc/helpers/protocol_support/st25tb/st25tb.c index eef723fed39..32b2f477570 100644 --- a/applications/main/nfc/helpers/protocol_support/st25tb/st25tb.c +++ b/applications/main/nfc/helpers/protocol_support/st25tb/st25tb.c @@ -95,6 +95,11 @@ const NfcProtocolSupportBase nfc_protocol_support_st25tb = { .on_enter = nfc_protocol_support_common_on_enter_empty, .on_event = nfc_scene_saved_menu_on_event_st25tb, }, + .scene_save_name = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, .scene_emulate = { .on_enter = nfc_protocol_support_common_on_enter_empty, From 176fb21f5fee9cb41dee6e1d54dba42c526256f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Mon, 30 Oct 2023 23:51:51 +0900 Subject: [PATCH 790/824] Storage: speedup write_chunk cli command (#3173) * Storage: speedup write_chunk cli command * Storage: handle disconnect on write_chunk correctly --- applications/services/storage/storage_cli.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/applications/services/storage/storage_cli.c b/applications/services/storage/storage_cli.c index 74bcf2d92af..59489d459c2 100644 --- a/applications/services/storage/storage_cli.c +++ b/applications/services/storage/storage_cli.c @@ -333,11 +333,9 @@ static void storage_cli_write_chunk(Cli* cli, FuriString* path, FuriString* args if(buffer_size) { uint8_t* buffer = malloc(buffer_size); - for(uint32_t i = 0; i < buffer_size; i++) { - buffer[i] = cli_getc(cli); - } + size_t read_bytes = cli_read(cli, buffer, buffer_size); - uint16_t written_size = storage_file_write(file, buffer, buffer_size); + uint16_t written_size = storage_file_write(file, buffer, read_bytes); if(written_size != buffer_size) { storage_cli_print_error(storage_file_get_error(file)); From 917410a0a84fbfc0064cb51757972bce743b3980 Mon Sep 17 00:00:00 2001 From: hedger Date: Mon, 30 Oct 2023 19:17:30 +0400 Subject: [PATCH 791/824] [FL-3629] fbt: reworked assets & resources handling (#3160) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fbt: reworking targets & assets handling WIP * fbt: dist fixes * fbt: moved SD card resources to owning apps * unit_tests: moved resources to app folder * github: updated unit_tests paths * github: packaging fixes * unit_tests: fixes * fbt: assets: internal cleanup * fbt: reworked assets handling * github: unit_tests: reintroducing fixes * minor cleanup * fbt: naming changes to reflect private nature of scons tools * fbt: resources: fixed dist archive paths * docs: updated paths * docs: updated more paths * docs: included "resources" parameter in app manifest docs; updated assets readme * updated gitignore for assets * github: updated action versions * unit_tests: restored timeout; scripts: assets: logging changes * gh: don't upload desktop animations for unit test run Co-authored-by: あく --- .github/CODEOWNERS | 4 +- .github/workflows/build.yml | 8 +- .github/workflows/build_compact.yml | 4 +- .../workflows/lint_and_submodule_check.yml | 2 +- .github/workflows/merge_report.yml | 2 +- .github/workflows/pvs_studio.yml | 2 +- .github/workflows/unit_tests.yml | 8 +- .github/workflows/updater_test.yml | 4 +- SConstruct | 34 +++--- applications/debug/unit_tests/application.fam | 1 + .../debug/unit_tests/manifest/manifest.c | 2 +- .../resources/unit_tests/Manifest_test | 0 .../unit_tests/infrared/test_kaseikyo.irtest | 0 .../unit_tests/infrared/test_nec.irtest | 0 .../unit_tests/infrared/test_nec42.irtest | 0 .../unit_tests/infrared/test_nec42ext.irtest | 0 .../unit_tests/infrared/test_necext.irtest | 0 .../unit_tests/infrared/test_rc5.irtest | 0 .../unit_tests/infrared/test_rc5x.irtest | 0 .../unit_tests/infrared/test_rc6.irtest | 0 .../unit_tests/infrared/test_rca.irtest | 0 .../unit_tests/infrared/test_samsung32.irtest | 0 .../unit_tests/infrared/test_sirc.irtest | 0 .../unit_tests/nfc/Ntag213_locked.nfc | 0 .../resources}/unit_tests/nfc/Ntag215.nfc | 0 .../resources}/unit_tests/nfc/Ntag216.nfc | 0 .../unit_tests/nfc/Ultralight_11.nfc | 0 .../unit_tests/nfc/Ultralight_21.nfc | 0 .../unit_tests/nfc/nfc_nfca_signal_long.nfc | 0 .../unit_tests/nfc/nfc_nfca_signal_short.nfc | 0 .../resources}/unit_tests/storage/md5.txt | 0 .../unit_tests/subghz/alutech_at_4n_raw.sub | 0 .../resources}/unit_tests/subghz/ansonic.sub | 0 .../unit_tests/subghz/ansonic_raw.sub | 0 .../resources}/unit_tests/subghz/bett.sub | 0 .../resources}/unit_tests/subghz/bett_raw.sub | 0 .../resources}/unit_tests/subghz/came.sub | 0 .../unit_tests/subghz/came_atomo_raw.sub | 0 .../resources}/unit_tests/subghz/came_raw.sub | 0 .../unit_tests/subghz/came_twee.sub | 0 .../unit_tests/subghz/came_twee_raw.sub | 0 .../unit_tests/subghz/cenmax_raw.sub | 0 .../resources}/unit_tests/subghz/clemsa.sub | 0 .../unit_tests/subghz/clemsa_raw.sub | 0 .../resources}/unit_tests/subghz/doitrand.sub | 0 .../unit_tests/subghz/doitrand_raw.sub | 0 .../resources}/unit_tests/subghz/doorhan.sub | 0 .../unit_tests/subghz/doorhan_raw.sub | 0 .../resources}/unit_tests/subghz/dooya.sub | 0 .../unit_tests/subghz/dooya_raw.sub | 0 .../unit_tests/subghz/faac_slh_raw.sub | 0 .../resources}/unit_tests/subghz/gate_tx.sub | 0 .../unit_tests/subghz/gate_tx_raw.sub | 0 .../resources}/unit_tests/subghz/holtek.sub | 0 .../unit_tests/subghz/holtek_ht12x.sub | 0 .../unit_tests/subghz/holtek_ht12x_raw.sub | 0 .../unit_tests/subghz/holtek_raw.sub | 0 .../unit_tests/subghz/honeywell_wdb.sub | 0 .../unit_tests/subghz/honeywell_wdb_raw.sub | 0 .../unit_tests/subghz/hormann_hsm_raw.sub | 0 .../unit_tests/subghz/ido_117_111_raw.sub | 0 .../unit_tests/subghz/intertechno_v3.sub | 0 .../unit_tests/subghz/intertechno_v3_raw.sub | 0 .../unit_tests/subghz/kia_seed_raw.sub | 0 .../subghz/kinggates_stylo4k_raw.sub | 0 .../resources}/unit_tests/subghz/linear.sub | 0 .../unit_tests/subghz/linear_delta3.sub | 0 .../unit_tests/subghz/linear_delta3_raw.sub | 0 .../unit_tests/subghz/linear_raw.sub | 0 .../resources}/unit_tests/subghz/magellan.sub | 0 .../unit_tests/subghz/magellan_raw.sub | 0 .../resources}/unit_tests/subghz/marantec.sub | 0 .../unit_tests/subghz/marantec_raw.sub | 0 .../resources}/unit_tests/subghz/megacode.sub | 0 .../unit_tests/subghz/megacode_raw.sub | 0 .../unit_tests/subghz/nero_radio_raw.sub | 0 .../unit_tests/subghz/nero_sketch_raw.sub | 0 .../resources}/unit_tests/subghz/nice_flo.sub | 0 .../unit_tests/subghz/nice_flo_raw.sub | 0 .../unit_tests/subghz/nice_flor_s_raw.sub | 0 .../unit_tests/subghz/nice_one_raw.sub | 0 .../unit_tests/subghz/phoenix_v2.sub | 0 .../unit_tests/subghz/phoenix_v2_raw.sub | 0 .../unit_tests/subghz/power_smart.sub | 0 .../unit_tests/subghz/power_smart_raw.sub | 0 .../unit_tests/subghz/princeton.sub | 0 .../unit_tests/subghz/princeton_raw.sub | 0 .../subghz/scher_khan_magic_code.sub | 0 .../unit_tests/subghz/security_pls_1_0.sub | 0 .../subghz/security_pls_1_0_raw.sub | 0 .../unit_tests/subghz/security_pls_2_0.sub | 0 .../subghz/security_pls_2_0_raw.sub | 0 .../resources}/unit_tests/subghz/smc5326.sub | 0 .../unit_tests/subghz/smc5326_raw.sub | 0 .../unit_tests/subghz/somfy_keytis_raw.sub | 0 .../unit_tests/subghz/somfy_telis_raw.sub | 0 .../unit_tests/subghz/test_random_raw.sub | 0 applications/main/bad_usb/application.fam | 1 + .../badusb/Install_qFlipper_macOS.txt | 0 .../badusb/Install_qFlipper_windows.txt | 0 .../resources/badusb/assets/layouts/ba-BA.kl | Bin .../resources/badusb/assets/layouts/cz_CS.kl | Bin .../resources/badusb/assets/layouts/da-DA.kl | Bin .../resources/badusb/assets/layouts/de-CH.kl | Bin .../resources/badusb/assets/layouts/de-DE.kl | Bin .../resources/badusb/assets/layouts/dvorak.kl | Bin .../resources/badusb/assets/layouts/en-UK.kl | Bin .../resources/badusb/assets/layouts/en-US.kl | Bin .../resources/badusb/assets/layouts/es-ES.kl | Bin .../resources/badusb/assets/layouts/fr-BE.kl | Bin .../resources/badusb/assets/layouts/fr-CA.kl | Bin .../resources/badusb/assets/layouts/fr-CH.kl | Bin .../badusb/assets/layouts/fr-FR-mac.kl | Bin .../resources/badusb/assets/layouts/fr-FR.kl | Bin .../resources/badusb/assets/layouts/hr-HR.kl | Bin .../resources/badusb/assets/layouts/hu-HU.kl | Bin .../resources/badusb/assets/layouts/it-IT.kl | Bin .../resources/badusb/assets/layouts/nb-NO.kl | Bin .../resources/badusb/assets/layouts/nl-NL.kl | Bin .../resources/badusb/assets/layouts/pt-BR.kl | Bin .../resources/badusb/assets/layouts/pt-PT.kl | Bin .../resources/badusb/assets/layouts/si-SI.kl | Bin .../resources/badusb/assets/layouts/sk-SK.kl | Bin .../resources/badusb/assets/layouts/sv-SE.kl | Bin .../resources/badusb/assets/layouts/tr-TR.kl | Bin .../bad_usb}/resources/badusb/demo_macos.txt | 0 .../resources/badusb/demo_windows.txt | 0 applications/main/infrared/application.fam | 1 + .../infrared}/resources/infrared/assets/ac.ir | 0 .../resources/infrared/assets/audio.ir | 0 .../resources/infrared/assets/projector.ir | 0 .../infrared}/resources/infrared/assets/tv.ir | 0 applications/main/nfc/application.fam | 1 + .../main/nfc}/resources/nfc/assets/aid.nfc | 0 .../resources/nfc/assets/country_code.nfc | 0 .../resources/nfc/assets/currency_code.nfc | 0 .../resources/nfc/assets/mf_classic_dict.nfc | 0 applications/main/subghz/application.fam | 1 + .../resources/subghz/assets/alutech_at_4n | 0 .../resources/subghz/assets/came_atomo | 0 .../resources/subghz/assets/keeloq_mfcodes | 0 .../subghz/assets/keeloq_mfcodes_user.example | 0 .../resources/subghz/assets/nice_flor_s | 0 .../subghz/assets/setting_user.example | 0 applications/main/u2f/application.fam | 1 + .../main/u2f}/resources/u2f/assets/cert.der | Bin .../u2f}/resources/u2f/assets/cert_key.u2f | 0 applications/services/loader/loader.c | 2 +- applications/services/loader/loader.h | 4 +- .../system/snake_game/application.fam | 1 - assets/.gitignore | 4 - assets/ReadMe.md | 3 - assets/SConscript | 54 +++------- documentation/AppManifests.md | 1 + documentation/AppsOnSDCard.md | 2 +- documentation/HardwareTargets.md | 2 +- documentation/UnitTests.md | 13 ++- documentation/UniversalRemotes.md | 6 +- .../file_formats/InfraredFileFormats.md | 8 +- firmware.scons | 38 ++++--- scripts/ReadMe.md | 4 +- scripts/assets.py | 10 +- scripts/fbt/appmanifest.py | 15 ++- scripts/fbt_tools/fbt_assets.py | 90 +++++++++++----- scripts/fbt_tools/fbt_dist.py | 21 +++- scripts/fbt_tools/fbt_extapps.py | 93 +++++++--------- scripts/fbt_tools/fbt_hwtarget.py | 23 ++-- scripts/fbt_tools/fbt_resources.py | 98 +++++++++++++++++ scripts/fbt_tools/fbt_sdk.py | 24 ++--- scripts/fbt_tools/fbt_version.py | 4 +- scripts/fbt_tools/pvsstudio.py | 4 +- scripts/flipper/assets/dolphin.py | 2 +- scripts/meta.py | 60 ----------- .../app_template/.github/workflows/build.yml | 4 +- site_scons/extapps.scons | 4 - {firmware => targets}/ReadMe.md | 0 {firmware => targets}/SConscript | 0 .../targets => targets}/f18/api_symbols.csv | 90 ++++++++-------- .../f18/furi_hal/furi_hal.c | 0 .../f18/furi_hal/furi_hal_power_config.c | 0 .../f18/furi_hal/furi_hal_resources.c | 0 .../f18/furi_hal/furi_hal_resources.h | 0 .../f18/furi_hal/furi_hal_spi_config.c | 0 .../f18/furi_hal/furi_hal_spi_config.h | 0 .../f18/furi_hal/furi_hal_target_hw.h | 0 .../f18/furi_hal/furi_hal_version_device.c | 0 {firmware/targets => targets}/f18/target.json | 0 .../targets => targets}/f7/api_symbols.csv | 100 +++++++++--------- .../targets => targets}/f7/application_ext.ld | 0 .../f7/ble_glue/app_common.h | 0 .../f7/ble_glue/app_conf.h | 0 .../f7/ble_glue/app_debug.c | 0 .../f7/ble_glue/app_debug.h | 0 .../targets => targets}/f7/ble_glue/ble_app.c | 0 .../targets => targets}/f7/ble_glue/ble_app.h | 0 .../f7/ble_glue/ble_conf.h | 0 .../f7/ble_glue/ble_const.h | 0 .../f7/ble_glue/ble_dbg_conf.h | 0 .../f7/ble_glue/ble_glue.c | 0 .../f7/ble_glue/ble_glue.h | 0 .../f7/ble_glue/compiler.h | 0 .../targets => targets}/f7/ble_glue/gap.c | 0 .../targets => targets}/f7/ble_glue/gap.h | 0 .../f7/ble_glue/hsem_map.h | 0 .../targets => targets}/f7/ble_glue/hw_ipcc.c | 0 .../targets => targets}/f7/ble_glue/osal.h | 0 .../f7/ble_glue/services/battery_service.c | 0 .../f7/ble_glue/services/battery_service.h | 0 .../f7/ble_glue/services/dev_info_service.c | 0 .../f7/ble_glue/services/dev_info_service.h | 0 .../services/dev_info_service_uuid.inc | 0 .../f7/ble_glue/services/gatt_char.c | 0 .../f7/ble_glue/services/gatt_char.h | 0 .../f7/ble_glue/services/hid_service.c | 0 .../f7/ble_glue/services/hid_service.h | 0 .../f7/ble_glue/services/serial_service.c | 0 .../f7/ble_glue/services/serial_service.h | 0 .../ble_glue/services/serial_service_uuid.inc | 0 .../f7/ble_glue/tl_dbg_conf.h | 0 .../targets => targets}/f7/fatfs/fatfs.c | 0 .../targets => targets}/f7/fatfs/fatfs.h | 0 .../targets => targets}/f7/fatfs/ffconf.h | 0 .../f7/fatfs/sector_cache.c | 0 .../f7/fatfs/sector_cache.h | 0 .../f7/fatfs/user_diskio.c | 0 .../f7/fatfs/user_diskio.h | 0 .../f7/furi_hal/furi_hal.c | 0 .../f7/furi_hal/furi_hal_bt.c | 0 .../f7/furi_hal/furi_hal_bt_hid.c | 0 .../f7/furi_hal/furi_hal_bt_serial.c | 0 .../f7/furi_hal/furi_hal_bus.c | 0 .../f7/furi_hal/furi_hal_bus.h | 0 .../f7/furi_hal/furi_hal_clock.c | 0 .../f7/furi_hal/furi_hal_clock.h | 0 .../f7/furi_hal/furi_hal_console.c | 0 .../f7/furi_hal/furi_hal_console.h | 0 .../f7/furi_hal/furi_hal_cortex.c | 0 .../f7/furi_hal/furi_hal_crypto.c | 0 .../f7/furi_hal/furi_hal_debug.c | 0 .../f7/furi_hal/furi_hal_dma.c | 0 .../f7/furi_hal/furi_hal_dma.h | 0 .../f7/furi_hal/furi_hal_flash.c | 0 .../f7/furi_hal/furi_hal_flash.h | 0 .../f7/furi_hal/furi_hal_gpio.c | 0 .../f7/furi_hal/furi_hal_gpio.h | 0 .../f7/furi_hal/furi_hal_i2c.c | 0 .../f7/furi_hal/furi_hal_i2c_config.c | 0 .../f7/furi_hal/furi_hal_i2c_config.h | 0 .../f7/furi_hal/furi_hal_i2c_types.h | 0 .../f7/furi_hal/furi_hal_ibutton.c | 0 .../f7/furi_hal/furi_hal_ibutton.h | 0 .../f7/furi_hal/furi_hal_idle_timer.h | 0 .../f7/furi_hal/furi_hal_info.c | 0 .../f7/furi_hal/furi_hal_infrared.c | 0 .../f7/furi_hal/furi_hal_interrupt.c | 0 .../f7/furi_hal/furi_hal_interrupt.h | 0 .../f7/furi_hal/furi_hal_light.c | 0 .../f7/furi_hal/furi_hal_memory.c | 0 .../f7/furi_hal/furi_hal_mpu.c | 0 .../f7/furi_hal/furi_hal_nfc.c | 0 .../f7/furi_hal/furi_hal_nfc_event.c | 0 .../f7/furi_hal/furi_hal_nfc_felica.c | 0 .../f7/furi_hal/furi_hal_nfc_i.h | 0 .../f7/furi_hal/furi_hal_nfc_irq.c | 0 .../f7/furi_hal/furi_hal_nfc_iso14443a.c | 0 .../f7/furi_hal/furi_hal_nfc_iso14443b.c | 0 .../f7/furi_hal/furi_hal_nfc_iso15693.c | 0 .../f7/furi_hal/furi_hal_nfc_tech_i.h | 0 .../f7/furi_hal/furi_hal_nfc_timer.c | 0 .../f7/furi_hal/furi_hal_os.c | 0 .../f7/furi_hal/furi_hal_os.h | 0 .../f7/furi_hal/furi_hal_power.c | 0 .../f7/furi_hal/furi_hal_power_config.c | 0 .../f7/furi_hal/furi_hal_pwm.c | 0 .../f7/furi_hal/furi_hal_pwm.h | 0 .../f7/furi_hal/furi_hal_random.c | 0 .../f7/furi_hal/furi_hal_region.c | 0 .../f7/furi_hal/furi_hal_resources.c | 0 .../f7/furi_hal/furi_hal_resources.h | 0 .../f7/furi_hal/furi_hal_rfid.c | 0 .../f7/furi_hal/furi_hal_rfid.h | 0 .../f7/furi_hal/furi_hal_rtc.c | 0 .../f7/furi_hal/furi_hal_sd.c | 0 .../f7/furi_hal/furi_hal_speaker.c | 0 .../f7/furi_hal/furi_hal_spi.c | 0 .../f7/furi_hal/furi_hal_spi_config.c | 0 .../f7/furi_hal/furi_hal_spi_config.h | 0 .../f7/furi_hal/furi_hal_spi_types.h | 0 .../f7/furi_hal/furi_hal_subghz.c | 0 .../f7/furi_hal/furi_hal_subghz.h | 0 .../f7/furi_hal/furi_hal_target_hw.h | 0 .../f7/furi_hal/furi_hal_uart.c | 0 .../f7/furi_hal/furi_hal_uart.h | 0 .../f7/furi_hal/furi_hal_usb.c | 0 .../f7/furi_hal/furi_hal_usb_ccid.c | 0 .../f7/furi_hal/furi_hal_usb_cdc.c | 0 .../f7/furi_hal/furi_hal_usb_cdc.h | 0 .../f7/furi_hal/furi_hal_usb_hid.c | 0 .../f7/furi_hal/furi_hal_usb_i.h | 0 .../f7/furi_hal/furi_hal_usb_u2f.c | 0 .../f7/furi_hal/furi_hal_version.c | 0 .../f7/furi_hal/furi_hal_version_device.c | 0 .../f7/furi_hal/furi_hal_vibro.c | 0 .../f7/inc/FreeRTOSConfig.h | 0 .../targets => targets}/f7/inc/alt_boot.h | 0 {firmware/targets => targets}/f7/inc/stm32.h | 0 .../targets => targets}/f7/inc/stm32_assert.h | 0 .../f7/platform_specific/intrinsic_export.h | 0 .../f7/platform_specific/math_wrapper.h | 0 {firmware/targets => targets}/f7/src/dfu.c | 0 {firmware/targets => targets}/f7/src/main.c | 0 .../targets => targets}/f7/src/recovery.c | 0 .../f7/src/system_stm32wbxx.c | 0 {firmware/targets => targets}/f7/src/update.c | 0 .../f7/startup_stm32wb55xx_cm4.s | 0 .../f7/stm32wb55xx_flash.ld | 0 .../f7/stm32wb55xx_ram_fw.ld | 0 {firmware/targets => targets}/f7/target.json | 0 .../furi_hal_include/furi_hal.h | 0 .../furi_hal_include/furi_hal_bt.h | 0 .../furi_hal_include/furi_hal_bt_hid.h | 0 .../furi_hal_include/furi_hal_bt_serial.h | 0 .../furi_hal_include/furi_hal_cortex.h | 0 .../furi_hal_include/furi_hal_crypto.h | 0 .../furi_hal_include/furi_hal_debug.h | 0 .../furi_hal_include/furi_hal_i2c.h | 0 .../furi_hal_include/furi_hal_info.h | 0 .../furi_hal_include/furi_hal_infrared.h | 0 .../furi_hal_include/furi_hal_light.h | 0 .../furi_hal_include/furi_hal_memory.h | 0 .../furi_hal_include/furi_hal_mpu.h | 0 .../furi_hal_include/furi_hal_nfc.h | 0 .../furi_hal_include/furi_hal_power.h | 0 .../furi_hal_include/furi_hal_random.h | 0 .../furi_hal_include/furi_hal_region.h | 0 .../furi_hal_include/furi_hal_rtc.h | 0 .../furi_hal_include/furi_hal_sd.h | 0 .../furi_hal_include/furi_hal_speaker.h | 0 .../furi_hal_include/furi_hal_spi.h | 0 .../furi_hal_include/furi_hal_usb.h | 0 .../furi_hal_include/furi_hal_usb_ccid.h | 0 .../furi_hal_include/furi_hal_usb_hid.h | 0 .../furi_hal_include/furi_hal_usb_hid_u2f.h | 0 .../furi_hal_include/furi_hal_version.h | 0 .../furi_hal_include/furi_hal_vibro.h | 0 345 files changed, 466 insertions(+), 394 deletions(-) rename assets/unit_tests/Manifest => applications/debug/unit_tests/resources/unit_tests/Manifest_test (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/infrared/test_kaseikyo.irtest (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/infrared/test_nec.irtest (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/infrared/test_nec42.irtest (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/infrared/test_nec42ext.irtest (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/infrared/test_necext.irtest (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/infrared/test_rc5.irtest (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/infrared/test_rc5x.irtest (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/infrared/test_rc6.irtest (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/infrared/test_rca.irtest (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/infrared/test_samsung32.irtest (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/infrared/test_sirc.irtest (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/nfc/Ntag213_locked.nfc (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/nfc/Ntag215.nfc (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/nfc/Ntag216.nfc (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/nfc/Ultralight_11.nfc (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/nfc/Ultralight_21.nfc (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/nfc/nfc_nfca_signal_long.nfc (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/nfc/nfc_nfca_signal_short.nfc (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/storage/md5.txt (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/alutech_at_4n_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/ansonic.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/ansonic_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/bett.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/bett_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/came.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/came_atomo_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/came_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/came_twee.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/came_twee_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/cenmax_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/clemsa.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/clemsa_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/doitrand.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/doitrand_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/doorhan.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/doorhan_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/dooya.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/dooya_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/faac_slh_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/gate_tx.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/gate_tx_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/holtek.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/holtek_ht12x.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/holtek_ht12x_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/holtek_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/honeywell_wdb.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/honeywell_wdb_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/hormann_hsm_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/ido_117_111_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/intertechno_v3.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/intertechno_v3_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/kia_seed_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/kinggates_stylo4k_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/linear.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/linear_delta3.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/linear_delta3_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/linear_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/magellan.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/magellan_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/marantec.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/marantec_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/megacode.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/megacode_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/nero_radio_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/nero_sketch_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/nice_flo.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/nice_flo_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/nice_flor_s_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/nice_one_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/phoenix_v2.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/phoenix_v2_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/power_smart.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/power_smart_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/princeton.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/princeton_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/scher_khan_magic_code.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/security_pls_1_0.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/security_pls_1_0_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/security_pls_2_0.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/security_pls_2_0_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/smc5326.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/smc5326_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/somfy_keytis_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/somfy_telis_raw.sub (100%) rename {assets => applications/debug/unit_tests/resources}/unit_tests/subghz/test_random_raw.sub (100%) rename {assets => applications/main/bad_usb}/resources/badusb/Install_qFlipper_macOS.txt (100%) rename {assets => applications/main/bad_usb}/resources/badusb/Install_qFlipper_windows.txt (100%) rename {assets => applications/main/bad_usb}/resources/badusb/assets/layouts/ba-BA.kl (100%) rename {assets => applications/main/bad_usb}/resources/badusb/assets/layouts/cz_CS.kl (100%) rename {assets => applications/main/bad_usb}/resources/badusb/assets/layouts/da-DA.kl (100%) rename {assets => applications/main/bad_usb}/resources/badusb/assets/layouts/de-CH.kl (100%) rename {assets => applications/main/bad_usb}/resources/badusb/assets/layouts/de-DE.kl (100%) rename {assets => applications/main/bad_usb}/resources/badusb/assets/layouts/dvorak.kl (100%) rename {assets => applications/main/bad_usb}/resources/badusb/assets/layouts/en-UK.kl (100%) rename {assets => applications/main/bad_usb}/resources/badusb/assets/layouts/en-US.kl (100%) rename {assets => applications/main/bad_usb}/resources/badusb/assets/layouts/es-ES.kl (100%) rename {assets => applications/main/bad_usb}/resources/badusb/assets/layouts/fr-BE.kl (100%) rename {assets => applications/main/bad_usb}/resources/badusb/assets/layouts/fr-CA.kl (100%) rename {assets => applications/main/bad_usb}/resources/badusb/assets/layouts/fr-CH.kl (100%) rename {assets => applications/main/bad_usb}/resources/badusb/assets/layouts/fr-FR-mac.kl (100%) rename {assets => applications/main/bad_usb}/resources/badusb/assets/layouts/fr-FR.kl (100%) rename {assets => applications/main/bad_usb}/resources/badusb/assets/layouts/hr-HR.kl (100%) rename {assets => applications/main/bad_usb}/resources/badusb/assets/layouts/hu-HU.kl (100%) rename {assets => applications/main/bad_usb}/resources/badusb/assets/layouts/it-IT.kl (100%) rename {assets => applications/main/bad_usb}/resources/badusb/assets/layouts/nb-NO.kl (100%) rename {assets => applications/main/bad_usb}/resources/badusb/assets/layouts/nl-NL.kl (100%) rename {assets => applications/main/bad_usb}/resources/badusb/assets/layouts/pt-BR.kl (100%) rename {assets => applications/main/bad_usb}/resources/badusb/assets/layouts/pt-PT.kl (100%) rename {assets => applications/main/bad_usb}/resources/badusb/assets/layouts/si-SI.kl (100%) rename {assets => applications/main/bad_usb}/resources/badusb/assets/layouts/sk-SK.kl (100%) rename {assets => applications/main/bad_usb}/resources/badusb/assets/layouts/sv-SE.kl (100%) rename {assets => applications/main/bad_usb}/resources/badusb/assets/layouts/tr-TR.kl (100%) rename {assets => applications/main/bad_usb}/resources/badusb/demo_macos.txt (100%) rename {assets => applications/main/bad_usb}/resources/badusb/demo_windows.txt (100%) rename {assets => applications/main/infrared}/resources/infrared/assets/ac.ir (100%) rename {assets => applications/main/infrared}/resources/infrared/assets/audio.ir (100%) rename {assets => applications/main/infrared}/resources/infrared/assets/projector.ir (100%) rename {assets => applications/main/infrared}/resources/infrared/assets/tv.ir (100%) rename {assets => applications/main/nfc}/resources/nfc/assets/aid.nfc (100%) rename {assets => applications/main/nfc}/resources/nfc/assets/country_code.nfc (100%) rename {assets => applications/main/nfc}/resources/nfc/assets/currency_code.nfc (100%) rename {assets => applications/main/nfc}/resources/nfc/assets/mf_classic_dict.nfc (100%) rename {assets => applications/main/subghz}/resources/subghz/assets/alutech_at_4n (100%) rename {assets => applications/main/subghz}/resources/subghz/assets/came_atomo (100%) rename {assets => applications/main/subghz}/resources/subghz/assets/keeloq_mfcodes (100%) rename {assets => applications/main/subghz}/resources/subghz/assets/keeloq_mfcodes_user.example (100%) rename {assets => applications/main/subghz}/resources/subghz/assets/nice_flor_s (100%) rename {assets => applications/main/subghz}/resources/subghz/assets/setting_user.example (100%) rename {assets => applications/main/u2f}/resources/u2f/assets/cert.der (100%) rename {assets => applications/main/u2f}/resources/u2f/assets/cert_key.u2f (100%) create mode 100644 scripts/fbt_tools/fbt_resources.py delete mode 100755 scripts/meta.py rename {firmware => targets}/ReadMe.md (100%) rename {firmware => targets}/SConscript (100%) rename {firmware/targets => targets}/f18/api_symbols.csv (98%) rename {firmware/targets => targets}/f18/furi_hal/furi_hal.c (100%) rename {firmware/targets => targets}/f18/furi_hal/furi_hal_power_config.c (100%) rename {firmware/targets => targets}/f18/furi_hal/furi_hal_resources.c (100%) rename {firmware/targets => targets}/f18/furi_hal/furi_hal_resources.h (100%) rename {firmware/targets => targets}/f18/furi_hal/furi_hal_spi_config.c (100%) rename {firmware/targets => targets}/f18/furi_hal/furi_hal_spi_config.h (100%) rename {firmware/targets => targets}/f18/furi_hal/furi_hal_target_hw.h (100%) rename {firmware/targets => targets}/f18/furi_hal/furi_hal_version_device.c (100%) rename {firmware/targets => targets}/f18/target.json (100%) rename {firmware/targets => targets}/f7/api_symbols.csv (98%) rename {firmware/targets => targets}/f7/application_ext.ld (100%) rename {firmware/targets => targets}/f7/ble_glue/app_common.h (100%) rename {firmware/targets => targets}/f7/ble_glue/app_conf.h (100%) rename {firmware/targets => targets}/f7/ble_glue/app_debug.c (100%) rename {firmware/targets => targets}/f7/ble_glue/app_debug.h (100%) rename {firmware/targets => targets}/f7/ble_glue/ble_app.c (100%) rename {firmware/targets => targets}/f7/ble_glue/ble_app.h (100%) rename {firmware/targets => targets}/f7/ble_glue/ble_conf.h (100%) rename {firmware/targets => targets}/f7/ble_glue/ble_const.h (100%) rename {firmware/targets => targets}/f7/ble_glue/ble_dbg_conf.h (100%) rename {firmware/targets => targets}/f7/ble_glue/ble_glue.c (100%) rename {firmware/targets => targets}/f7/ble_glue/ble_glue.h (100%) rename {firmware/targets => targets}/f7/ble_glue/compiler.h (100%) rename {firmware/targets => targets}/f7/ble_glue/gap.c (100%) rename {firmware/targets => targets}/f7/ble_glue/gap.h (100%) rename {firmware/targets => targets}/f7/ble_glue/hsem_map.h (100%) rename {firmware/targets => targets}/f7/ble_glue/hw_ipcc.c (100%) rename {firmware/targets => targets}/f7/ble_glue/osal.h (100%) rename {firmware/targets => targets}/f7/ble_glue/services/battery_service.c (100%) rename {firmware/targets => targets}/f7/ble_glue/services/battery_service.h (100%) rename {firmware/targets => targets}/f7/ble_glue/services/dev_info_service.c (100%) rename {firmware/targets => targets}/f7/ble_glue/services/dev_info_service.h (100%) rename {firmware/targets => targets}/f7/ble_glue/services/dev_info_service_uuid.inc (100%) rename {firmware/targets => targets}/f7/ble_glue/services/gatt_char.c (100%) rename {firmware/targets => targets}/f7/ble_glue/services/gatt_char.h (100%) rename {firmware/targets => targets}/f7/ble_glue/services/hid_service.c (100%) rename {firmware/targets => targets}/f7/ble_glue/services/hid_service.h (100%) rename {firmware/targets => targets}/f7/ble_glue/services/serial_service.c (100%) rename {firmware/targets => targets}/f7/ble_glue/services/serial_service.h (100%) rename {firmware/targets => targets}/f7/ble_glue/services/serial_service_uuid.inc (100%) rename {firmware/targets => targets}/f7/ble_glue/tl_dbg_conf.h (100%) rename {firmware/targets => targets}/f7/fatfs/fatfs.c (100%) rename {firmware/targets => targets}/f7/fatfs/fatfs.h (100%) rename {firmware/targets => targets}/f7/fatfs/ffconf.h (100%) rename {firmware/targets => targets}/f7/fatfs/sector_cache.c (100%) rename {firmware/targets => targets}/f7/fatfs/sector_cache.h (100%) rename {firmware/targets => targets}/f7/fatfs/user_diskio.c (100%) rename {firmware/targets => targets}/f7/fatfs/user_diskio.h (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_bt.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_bt_hid.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_bt_serial.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_bus.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_bus.h (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_clock.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_clock.h (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_console.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_console.h (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_cortex.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_crypto.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_debug.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_dma.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_dma.h (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_flash.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_flash.h (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_gpio.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_gpio.h (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_i2c.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_i2c_config.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_i2c_config.h (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_i2c_types.h (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_ibutton.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_ibutton.h (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_idle_timer.h (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_info.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_infrared.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_interrupt.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_interrupt.h (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_light.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_memory.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_mpu.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_nfc.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_nfc_event.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_nfc_felica.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_nfc_i.h (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_nfc_irq.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_nfc_iso14443a.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_nfc_iso14443b.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_nfc_iso15693.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_nfc_tech_i.h (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_nfc_timer.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_os.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_os.h (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_power.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_power_config.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_pwm.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_pwm.h (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_random.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_region.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_resources.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_resources.h (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_rfid.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_rfid.h (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_rtc.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_sd.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_speaker.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_spi.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_spi_config.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_spi_config.h (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_spi_types.h (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_subghz.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_subghz.h (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_target_hw.h (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_uart.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_uart.h (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_usb.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_usb_ccid.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_usb_cdc.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_usb_cdc.h (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_usb_hid.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_usb_i.h (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_usb_u2f.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_version.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_version_device.c (100%) rename {firmware/targets => targets}/f7/furi_hal/furi_hal_vibro.c (100%) rename {firmware/targets => targets}/f7/inc/FreeRTOSConfig.h (100%) rename {firmware/targets => targets}/f7/inc/alt_boot.h (100%) rename {firmware/targets => targets}/f7/inc/stm32.h (100%) rename {firmware/targets => targets}/f7/inc/stm32_assert.h (100%) rename {firmware/targets => targets}/f7/platform_specific/intrinsic_export.h (100%) rename {firmware/targets => targets}/f7/platform_specific/math_wrapper.h (100%) rename {firmware/targets => targets}/f7/src/dfu.c (100%) rename {firmware/targets => targets}/f7/src/main.c (100%) rename {firmware/targets => targets}/f7/src/recovery.c (100%) rename {firmware/targets => targets}/f7/src/system_stm32wbxx.c (100%) rename {firmware/targets => targets}/f7/src/update.c (100%) rename {firmware/targets => targets}/f7/startup_stm32wb55xx_cm4.s (100%) rename {firmware/targets => targets}/f7/stm32wb55xx_flash.ld (100%) rename {firmware/targets => targets}/f7/stm32wb55xx_ram_fw.ld (100%) rename {firmware/targets => targets}/f7/target.json (100%) rename {firmware/targets => targets}/furi_hal_include/furi_hal.h (100%) rename {firmware/targets => targets}/furi_hal_include/furi_hal_bt.h (100%) rename {firmware/targets => targets}/furi_hal_include/furi_hal_bt_hid.h (100%) rename {firmware/targets => targets}/furi_hal_include/furi_hal_bt_serial.h (100%) rename {firmware/targets => targets}/furi_hal_include/furi_hal_cortex.h (100%) rename {firmware/targets => targets}/furi_hal_include/furi_hal_crypto.h (100%) rename {firmware/targets => targets}/furi_hal_include/furi_hal_debug.h (100%) rename {firmware/targets => targets}/furi_hal_include/furi_hal_i2c.h (100%) rename {firmware/targets => targets}/furi_hal_include/furi_hal_info.h (100%) rename {firmware/targets => targets}/furi_hal_include/furi_hal_infrared.h (100%) rename {firmware/targets => targets}/furi_hal_include/furi_hal_light.h (100%) rename {firmware/targets => targets}/furi_hal_include/furi_hal_memory.h (100%) rename {firmware/targets => targets}/furi_hal_include/furi_hal_mpu.h (100%) rename {firmware/targets => targets}/furi_hal_include/furi_hal_nfc.h (100%) rename {firmware/targets => targets}/furi_hal_include/furi_hal_power.h (100%) rename {firmware/targets => targets}/furi_hal_include/furi_hal_random.h (100%) rename {firmware/targets => targets}/furi_hal_include/furi_hal_region.h (100%) rename {firmware/targets => targets}/furi_hal_include/furi_hal_rtc.h (100%) rename {firmware/targets => targets}/furi_hal_include/furi_hal_sd.h (100%) rename {firmware/targets => targets}/furi_hal_include/furi_hal_speaker.h (100%) rename {firmware/targets => targets}/furi_hal_include/furi_hal_spi.h (100%) rename {firmware/targets => targets}/furi_hal_include/furi_hal_usb.h (100%) rename {firmware/targets => targets}/furi_hal_include/furi_hal_usb_ccid.h (100%) rename {firmware/targets => targets}/furi_hal_include/furi_hal_usb_hid.h (100%) rename {firmware/targets => targets}/furi_hal_include/furi_hal_usb_hid_u2f.h (100%) rename {firmware/targets => targets}/furi_hal_include/furi_hal_version.h (100%) rename {firmware/targets => targets}/furi_hal_include/furi_hal_vibro.h (100%) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index e02c931af98..b72d9ea6136 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -42,10 +42,10 @@ /applications/examples/example_thermo/ @skotopes @DrZlo13 @hedger @gsurkov # Firmware targets -/firmware/ @skotopes @DrZlo13 @hedger @nminaylov +/targets/ @skotopes @DrZlo13 @hedger @nminaylov # Assets -/assets/resources/infrared/ @skotopes @DrZlo13 @hedger @gsurkov +/applications/main/infrared/resources/ @skotopes @DrZlo13 @hedger @gsurkov # Documentation /documentation/ @skotopes @DrZlo13 @hedger @drunkbatya diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4c553506650..38b1d7b6886 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -25,7 +25,7 @@ jobs: run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; - name: 'Checkout code' - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 1 ref: ${{ github.event.pull_request.head.sha }} @@ -51,11 +51,11 @@ jobs: - name: 'Check API versions for consistency between targets' run: | set -e - N_API_HEADER_SIGNATURES=`ls -1 firmware/targets/f*/api_symbols.csv | xargs -I {} sh -c "head -n2 {} | md5sum" | sort -u | wc -l` + N_API_HEADER_SIGNATURES=`ls -1 targets/f*/api_symbols.csv | xargs -I {} sh -c "head -n2 {} | md5sum" | sort -u | wc -l` if [ $N_API_HEADER_SIGNATURES != 1 ] ; then echo API versions aren\'t matching for available targets. Please update! echo API versions are: - head -n2 firmware/targets/f*/api_symbols.csv + head -n2 targets/f*/api_symbols.csv exit 1 fi @@ -76,7 +76,7 @@ jobs: mkdir artifacts map_analyser_files cp dist/${TARGET}-*/* artifacts/ || true tar czpf "artifacts/flipper-z-${TARGET}-resources-${SUFFIX}.tgz" \ - -C assets resources + -C build/latest resources tar czpf "artifacts/flipper-z-${TARGET}-debugapps-${SUFFIX}.tgz" \ -C dist/${TARGET}-*/apps/Debug . tar czpf "artifacts/flipper-z-${TARGET}-appsymbols-${SUFFIX}.tgz" \ diff --git a/.github/workflows/build_compact.yml b/.github/workflows/build_compact.yml index c63be24a47d..f45275204a3 100644 --- a/.github/workflows/build_compact.yml +++ b/.github/workflows/build_compact.yml @@ -19,7 +19,7 @@ jobs: run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; - name: 'Checkout code' - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 1 submodules: false @@ -46,7 +46,7 @@ jobs: echo "hw-target-code=$TARGET" >> $GITHUB_OUTPUT - name: Deploy uFBT with SDK - uses: flipperdevices/flipperzero-ufbt-action@v0.1.0 + uses: flipperdevices/flipperzero-ufbt-action@v0.1 with: task: setup sdk-file: ${{ steps.build-fw.outputs.sdk-file }} diff --git a/.github/workflows/lint_and_submodule_check.yml b/.github/workflows/lint_and_submodule_check.yml index d24422b7cbd..51e2593eccb 100644 --- a/.github/workflows/lint_and_submodule_check.yml +++ b/.github/workflows/lint_and_submodule_check.yml @@ -16,7 +16,7 @@ jobs: run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; - name: 'Checkout code' - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 2 ref: ${{ github.sha }} diff --git a/.github/workflows/merge_report.yml b/.github/workflows/merge_report.yml index fedc4b87fb3..90302ce1aa0 100644 --- a/.github/workflows/merge_report.yml +++ b/.github/workflows/merge_report.yml @@ -16,7 +16,7 @@ jobs: run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; - name: 'Checkout code' - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 1 ref: ${{ github.event.pull_request.head.sha }} diff --git a/.github/workflows/pvs_studio.yml b/.github/workflows/pvs_studio.yml index 4f650f1b7c3..4527e292078 100644 --- a/.github/workflows/pvs_studio.yml +++ b/.github/workflows/pvs_studio.yml @@ -21,7 +21,7 @@ jobs: run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; - name: 'Checkout code' - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 1 ref: ${{ github.event.pull_request.head.sha }} diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 2680f520c1c..40f72bd0ba5 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -17,7 +17,7 @@ jobs: run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 1 ref: ${{ github.event.pull_request.head.sha }} @@ -32,7 +32,7 @@ jobs: if: success() timeout-minutes: 10 run: | - ./fbt flash SWD_TRANSPORT_SERIAL=2A0906016415303030303032 LIB_DEBUG=1 FIRMWARE_APP_SET=unit_tests FORCE=1 + ./fbt resources firmware_latest flash SWD_TRANSPORT_SERIAL=2A0906016415303030303032 LIB_DEBUG=1 FIRMWARE_APP_SET=unit_tests FORCE=1 - name: 'Wait for flipper and format ext' id: format_ext @@ -50,8 +50,8 @@ jobs: run: | source scripts/toolchain/fbtenv.sh python3 scripts/testing/await_flipper.py ${{steps.device.outputs.flipper}} - python3 scripts/storage.py -p ${{steps.device.outputs.flipper}} -f send assets/resources /ext - python3 scripts/storage.py -p ${{steps.device.outputs.flipper}} -f send assets/unit_tests /ext/unit_tests + rm -rf build/latest/resources/dolphin + python3 scripts/storage.py -p ${{steps.device.outputs.flipper}} -f send build/latest/resources /ext python3 scripts/power.py -p ${{steps.device.outputs.flipper}} reboot python3 scripts/testing/await_flipper.py ${{steps.device.outputs.flipper}} diff --git a/.github/workflows/updater_test.yml b/.github/workflows/updater_test.yml index 9987fdd3244..b14b618a6eb 100644 --- a/.github/workflows/updater_test.yml +++ b/.github/workflows/updater_test.yml @@ -17,7 +17,7 @@ jobs: run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 1 submodules: false @@ -56,7 +56,7 @@ jobs: run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; - name: 'Checkout latest release' - uses: actions/checkout@v3 + uses: actions/checkout@v4 if: failure() with: fetch-depth: 1 diff --git a/SConstruct b/SConstruct index 36e0600235a..faccdfa5b3e 100644 --- a/SConstruct +++ b/SConstruct @@ -67,22 +67,22 @@ if GetOption("fullenv") or any( # Target for self-update package dist_basic_arguments = [ "--bundlever", - '"${UPDATE_VERSION_STRING}"', + "${UPDATE_VERSION_STRING}", ] dist_radio_arguments = [ "--radio", - '"${ROOT_DIR.abspath}/${COPRO_STACK_BIN_DIR}/${COPRO_STACK_BIN}"', + "${ROOT_DIR.abspath}/${COPRO_STACK_BIN_DIR}/${COPRO_STACK_BIN}", "--radiotype", "${COPRO_STACK_TYPE}", "${COPRO_DISCLAIMER}", "--obdata", - '"${ROOT_DIR.abspath}/${COPRO_OB_DATA}"', + "${ROOT_DIR.abspath}/${COPRO_OB_DATA}", "--stackversion", "${COPRO_CUBE_VERSION}", ] dist_resource_arguments = [ "-r", - '"${ROOT_DIR.abspath}/assets/resources"', + firmware_env.subst("${RESOURCES_ROOT}"), ] dist_splash_arguments = ( [ @@ -95,7 +95,7 @@ if GetOption("fullenv") or any( selfupdate_dist = distenv.DistCommand( "updater_package", - (distenv["DIST_DEPENDS"], firmware_env["FW_RESOURCES"]), + (distenv["DIST_DEPENDS"], firmware_env["FW_RESOURCES_MANIFEST"]), DIST_EXTRA=[ *dist_basic_arguments, *dist_radio_arguments, @@ -128,7 +128,8 @@ if GetOption("fullenv") or any( # Installation over USB & CLI usb_update_package = distenv.AddUsbFlashTarget( - "#build/usbinstall.flag", (firmware_env["FW_RESOURCES"], selfupdate_dist) + "#build/usbinstall.flag", + (firmware_env["FW_RESOURCES_MANIFEST"], selfupdate_dist), ) distenv.Alias("flash_usb_full", usb_update_package) @@ -166,16 +167,23 @@ Depends( list(app_artifact.validator for app_artifact in external_app_list), ) Alias("fap_dist", fap_dist) -# distenv.Default(fap_dist) - -distenv.Depends(firmware_env["FW_RESOURCES"], external_apps_artifacts.resources_dist) # Copy all faps to device fap_deploy = distenv.PhonyTarget( "fap_deploy", - "${PYTHON3} ${FBT_SCRIPT_DIR}/storage.py -p ${FLIP_PORT} send ${SOURCE} /ext/apps", - source=Dir("#/assets/resources/apps"), + [ + [ + "${PYTHON3}", + "${FBT_SCRIPT_DIR}/storage.py", + "-p", + "${FLIP_PORT}", + "send", + "${SOURCE}", + "/ext/apps", + ] + ], + source=firmware_env.Dir(("${RESOURCES_ROOT}/apps")), ) @@ -314,9 +322,7 @@ distenv.PhonyTarget( ) # Start Flipper CLI via PySerial's miniterm -distenv.PhonyTarget( - "cli", "${PYTHON3} ${FBT_SCRIPT_DIR}/serial_cli.py -p ${FLIP_PORT}" -) +distenv.PhonyTarget("cli", "${PYTHON3} ${FBT_SCRIPT_DIR}/serial_cli.py -p ${FLIP_PORT}") # Update WiFi devboard firmware distenv.PhonyTarget("devboard_flash", "${PYTHON3} ${FBT_SCRIPT_DIR}/wifi_board.py") diff --git a/applications/debug/unit_tests/application.fam b/applications/debug/unit_tests/application.fam index ad9a278f387..aa25dab279b 100644 --- a/applications/debug/unit_tests/application.fam +++ b/applications/debug/unit_tests/application.fam @@ -5,6 +5,7 @@ App( cdefines=["APP_UNIT_TESTS"], requires=["system_settings"], provides=["delay_test"], + resources="resources", order=100, ) diff --git a/applications/debug/unit_tests/manifest/manifest.c b/applications/debug/unit_tests/manifest/manifest.c index 0b24ad1ed65..19370b0e13f 100644 --- a/applications/debug/unit_tests/manifest/manifest.c +++ b/applications/debug/unit_tests/manifest/manifest.c @@ -22,7 +22,7 @@ MU_TEST(manifest_iteration_test) { ResourceManifestReader* manifest_reader = resource_manifest_reader_alloc(storage); do { // Open manifest file - if(!resource_manifest_reader_open(manifest_reader, EXT_PATH("unit_tests/Manifest"))) { + if(!resource_manifest_reader_open(manifest_reader, EXT_PATH("unit_tests/Manifest_test"))) { result = false; break; } diff --git a/assets/unit_tests/Manifest b/applications/debug/unit_tests/resources/unit_tests/Manifest_test similarity index 100% rename from assets/unit_tests/Manifest rename to applications/debug/unit_tests/resources/unit_tests/Manifest_test diff --git a/assets/unit_tests/infrared/test_kaseikyo.irtest b/applications/debug/unit_tests/resources/unit_tests/infrared/test_kaseikyo.irtest similarity index 100% rename from assets/unit_tests/infrared/test_kaseikyo.irtest rename to applications/debug/unit_tests/resources/unit_tests/infrared/test_kaseikyo.irtest diff --git a/assets/unit_tests/infrared/test_nec.irtest b/applications/debug/unit_tests/resources/unit_tests/infrared/test_nec.irtest similarity index 100% rename from assets/unit_tests/infrared/test_nec.irtest rename to applications/debug/unit_tests/resources/unit_tests/infrared/test_nec.irtest diff --git a/assets/unit_tests/infrared/test_nec42.irtest b/applications/debug/unit_tests/resources/unit_tests/infrared/test_nec42.irtest similarity index 100% rename from assets/unit_tests/infrared/test_nec42.irtest rename to applications/debug/unit_tests/resources/unit_tests/infrared/test_nec42.irtest diff --git a/assets/unit_tests/infrared/test_nec42ext.irtest b/applications/debug/unit_tests/resources/unit_tests/infrared/test_nec42ext.irtest similarity index 100% rename from assets/unit_tests/infrared/test_nec42ext.irtest rename to applications/debug/unit_tests/resources/unit_tests/infrared/test_nec42ext.irtest diff --git a/assets/unit_tests/infrared/test_necext.irtest b/applications/debug/unit_tests/resources/unit_tests/infrared/test_necext.irtest similarity index 100% rename from assets/unit_tests/infrared/test_necext.irtest rename to applications/debug/unit_tests/resources/unit_tests/infrared/test_necext.irtest diff --git a/assets/unit_tests/infrared/test_rc5.irtest b/applications/debug/unit_tests/resources/unit_tests/infrared/test_rc5.irtest similarity index 100% rename from assets/unit_tests/infrared/test_rc5.irtest rename to applications/debug/unit_tests/resources/unit_tests/infrared/test_rc5.irtest diff --git a/assets/unit_tests/infrared/test_rc5x.irtest b/applications/debug/unit_tests/resources/unit_tests/infrared/test_rc5x.irtest similarity index 100% rename from assets/unit_tests/infrared/test_rc5x.irtest rename to applications/debug/unit_tests/resources/unit_tests/infrared/test_rc5x.irtest diff --git a/assets/unit_tests/infrared/test_rc6.irtest b/applications/debug/unit_tests/resources/unit_tests/infrared/test_rc6.irtest similarity index 100% rename from assets/unit_tests/infrared/test_rc6.irtest rename to applications/debug/unit_tests/resources/unit_tests/infrared/test_rc6.irtest diff --git a/assets/unit_tests/infrared/test_rca.irtest b/applications/debug/unit_tests/resources/unit_tests/infrared/test_rca.irtest similarity index 100% rename from assets/unit_tests/infrared/test_rca.irtest rename to applications/debug/unit_tests/resources/unit_tests/infrared/test_rca.irtest diff --git a/assets/unit_tests/infrared/test_samsung32.irtest b/applications/debug/unit_tests/resources/unit_tests/infrared/test_samsung32.irtest similarity index 100% rename from assets/unit_tests/infrared/test_samsung32.irtest rename to applications/debug/unit_tests/resources/unit_tests/infrared/test_samsung32.irtest diff --git a/assets/unit_tests/infrared/test_sirc.irtest b/applications/debug/unit_tests/resources/unit_tests/infrared/test_sirc.irtest similarity index 100% rename from assets/unit_tests/infrared/test_sirc.irtest rename to applications/debug/unit_tests/resources/unit_tests/infrared/test_sirc.irtest diff --git a/assets/unit_tests/nfc/Ntag213_locked.nfc b/applications/debug/unit_tests/resources/unit_tests/nfc/Ntag213_locked.nfc similarity index 100% rename from assets/unit_tests/nfc/Ntag213_locked.nfc rename to applications/debug/unit_tests/resources/unit_tests/nfc/Ntag213_locked.nfc diff --git a/assets/unit_tests/nfc/Ntag215.nfc b/applications/debug/unit_tests/resources/unit_tests/nfc/Ntag215.nfc similarity index 100% rename from assets/unit_tests/nfc/Ntag215.nfc rename to applications/debug/unit_tests/resources/unit_tests/nfc/Ntag215.nfc diff --git a/assets/unit_tests/nfc/Ntag216.nfc b/applications/debug/unit_tests/resources/unit_tests/nfc/Ntag216.nfc similarity index 100% rename from assets/unit_tests/nfc/Ntag216.nfc rename to applications/debug/unit_tests/resources/unit_tests/nfc/Ntag216.nfc diff --git a/assets/unit_tests/nfc/Ultralight_11.nfc b/applications/debug/unit_tests/resources/unit_tests/nfc/Ultralight_11.nfc similarity index 100% rename from assets/unit_tests/nfc/Ultralight_11.nfc rename to applications/debug/unit_tests/resources/unit_tests/nfc/Ultralight_11.nfc diff --git a/assets/unit_tests/nfc/Ultralight_21.nfc b/applications/debug/unit_tests/resources/unit_tests/nfc/Ultralight_21.nfc similarity index 100% rename from assets/unit_tests/nfc/Ultralight_21.nfc rename to applications/debug/unit_tests/resources/unit_tests/nfc/Ultralight_21.nfc diff --git a/assets/unit_tests/nfc/nfc_nfca_signal_long.nfc b/applications/debug/unit_tests/resources/unit_tests/nfc/nfc_nfca_signal_long.nfc similarity index 100% rename from assets/unit_tests/nfc/nfc_nfca_signal_long.nfc rename to applications/debug/unit_tests/resources/unit_tests/nfc/nfc_nfca_signal_long.nfc diff --git a/assets/unit_tests/nfc/nfc_nfca_signal_short.nfc b/applications/debug/unit_tests/resources/unit_tests/nfc/nfc_nfca_signal_short.nfc similarity index 100% rename from assets/unit_tests/nfc/nfc_nfca_signal_short.nfc rename to applications/debug/unit_tests/resources/unit_tests/nfc/nfc_nfca_signal_short.nfc diff --git a/assets/unit_tests/storage/md5.txt b/applications/debug/unit_tests/resources/unit_tests/storage/md5.txt similarity index 100% rename from assets/unit_tests/storage/md5.txt rename to applications/debug/unit_tests/resources/unit_tests/storage/md5.txt diff --git a/assets/unit_tests/subghz/alutech_at_4n_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/alutech_at_4n_raw.sub similarity index 100% rename from assets/unit_tests/subghz/alutech_at_4n_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/alutech_at_4n_raw.sub diff --git a/assets/unit_tests/subghz/ansonic.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/ansonic.sub similarity index 100% rename from assets/unit_tests/subghz/ansonic.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/ansonic.sub diff --git a/assets/unit_tests/subghz/ansonic_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/ansonic_raw.sub similarity index 100% rename from assets/unit_tests/subghz/ansonic_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/ansonic_raw.sub diff --git a/assets/unit_tests/subghz/bett.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/bett.sub similarity index 100% rename from assets/unit_tests/subghz/bett.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/bett.sub diff --git a/assets/unit_tests/subghz/bett_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/bett_raw.sub similarity index 100% rename from assets/unit_tests/subghz/bett_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/bett_raw.sub diff --git a/assets/unit_tests/subghz/came.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/came.sub similarity index 100% rename from assets/unit_tests/subghz/came.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/came.sub diff --git a/assets/unit_tests/subghz/came_atomo_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/came_atomo_raw.sub similarity index 100% rename from assets/unit_tests/subghz/came_atomo_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/came_atomo_raw.sub diff --git a/assets/unit_tests/subghz/came_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/came_raw.sub similarity index 100% rename from assets/unit_tests/subghz/came_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/came_raw.sub diff --git a/assets/unit_tests/subghz/came_twee.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/came_twee.sub similarity index 100% rename from assets/unit_tests/subghz/came_twee.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/came_twee.sub diff --git a/assets/unit_tests/subghz/came_twee_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/came_twee_raw.sub similarity index 100% rename from assets/unit_tests/subghz/came_twee_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/came_twee_raw.sub diff --git a/assets/unit_tests/subghz/cenmax_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/cenmax_raw.sub similarity index 100% rename from assets/unit_tests/subghz/cenmax_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/cenmax_raw.sub diff --git a/assets/unit_tests/subghz/clemsa.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/clemsa.sub similarity index 100% rename from assets/unit_tests/subghz/clemsa.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/clemsa.sub diff --git a/assets/unit_tests/subghz/clemsa_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/clemsa_raw.sub similarity index 100% rename from assets/unit_tests/subghz/clemsa_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/clemsa_raw.sub diff --git a/assets/unit_tests/subghz/doitrand.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/doitrand.sub similarity index 100% rename from assets/unit_tests/subghz/doitrand.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/doitrand.sub diff --git a/assets/unit_tests/subghz/doitrand_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/doitrand_raw.sub similarity index 100% rename from assets/unit_tests/subghz/doitrand_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/doitrand_raw.sub diff --git a/assets/unit_tests/subghz/doorhan.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/doorhan.sub similarity index 100% rename from assets/unit_tests/subghz/doorhan.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/doorhan.sub diff --git a/assets/unit_tests/subghz/doorhan_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/doorhan_raw.sub similarity index 100% rename from assets/unit_tests/subghz/doorhan_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/doorhan_raw.sub diff --git a/assets/unit_tests/subghz/dooya.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/dooya.sub similarity index 100% rename from assets/unit_tests/subghz/dooya.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/dooya.sub diff --git a/assets/unit_tests/subghz/dooya_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/dooya_raw.sub similarity index 100% rename from assets/unit_tests/subghz/dooya_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/dooya_raw.sub diff --git a/assets/unit_tests/subghz/faac_slh_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/faac_slh_raw.sub similarity index 100% rename from assets/unit_tests/subghz/faac_slh_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/faac_slh_raw.sub diff --git a/assets/unit_tests/subghz/gate_tx.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/gate_tx.sub similarity index 100% rename from assets/unit_tests/subghz/gate_tx.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/gate_tx.sub diff --git a/assets/unit_tests/subghz/gate_tx_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/gate_tx_raw.sub similarity index 100% rename from assets/unit_tests/subghz/gate_tx_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/gate_tx_raw.sub diff --git a/assets/unit_tests/subghz/holtek.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/holtek.sub similarity index 100% rename from assets/unit_tests/subghz/holtek.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/holtek.sub diff --git a/assets/unit_tests/subghz/holtek_ht12x.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/holtek_ht12x.sub similarity index 100% rename from assets/unit_tests/subghz/holtek_ht12x.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/holtek_ht12x.sub diff --git a/assets/unit_tests/subghz/holtek_ht12x_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/holtek_ht12x_raw.sub similarity index 100% rename from assets/unit_tests/subghz/holtek_ht12x_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/holtek_ht12x_raw.sub diff --git a/assets/unit_tests/subghz/holtek_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/holtek_raw.sub similarity index 100% rename from assets/unit_tests/subghz/holtek_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/holtek_raw.sub diff --git a/assets/unit_tests/subghz/honeywell_wdb.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/honeywell_wdb.sub similarity index 100% rename from assets/unit_tests/subghz/honeywell_wdb.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/honeywell_wdb.sub diff --git a/assets/unit_tests/subghz/honeywell_wdb_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/honeywell_wdb_raw.sub similarity index 100% rename from assets/unit_tests/subghz/honeywell_wdb_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/honeywell_wdb_raw.sub diff --git a/assets/unit_tests/subghz/hormann_hsm_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/hormann_hsm_raw.sub similarity index 100% rename from assets/unit_tests/subghz/hormann_hsm_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/hormann_hsm_raw.sub diff --git a/assets/unit_tests/subghz/ido_117_111_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/ido_117_111_raw.sub similarity index 100% rename from assets/unit_tests/subghz/ido_117_111_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/ido_117_111_raw.sub diff --git a/assets/unit_tests/subghz/intertechno_v3.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/intertechno_v3.sub similarity index 100% rename from assets/unit_tests/subghz/intertechno_v3.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/intertechno_v3.sub diff --git a/assets/unit_tests/subghz/intertechno_v3_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/intertechno_v3_raw.sub similarity index 100% rename from assets/unit_tests/subghz/intertechno_v3_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/intertechno_v3_raw.sub diff --git a/assets/unit_tests/subghz/kia_seed_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/kia_seed_raw.sub similarity index 100% rename from assets/unit_tests/subghz/kia_seed_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/kia_seed_raw.sub diff --git a/assets/unit_tests/subghz/kinggates_stylo4k_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/kinggates_stylo4k_raw.sub similarity index 100% rename from assets/unit_tests/subghz/kinggates_stylo4k_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/kinggates_stylo4k_raw.sub diff --git a/assets/unit_tests/subghz/linear.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/linear.sub similarity index 100% rename from assets/unit_tests/subghz/linear.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/linear.sub diff --git a/assets/unit_tests/subghz/linear_delta3.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/linear_delta3.sub similarity index 100% rename from assets/unit_tests/subghz/linear_delta3.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/linear_delta3.sub diff --git a/assets/unit_tests/subghz/linear_delta3_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/linear_delta3_raw.sub similarity index 100% rename from assets/unit_tests/subghz/linear_delta3_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/linear_delta3_raw.sub diff --git a/assets/unit_tests/subghz/linear_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/linear_raw.sub similarity index 100% rename from assets/unit_tests/subghz/linear_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/linear_raw.sub diff --git a/assets/unit_tests/subghz/magellan.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/magellan.sub similarity index 100% rename from assets/unit_tests/subghz/magellan.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/magellan.sub diff --git a/assets/unit_tests/subghz/magellan_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/magellan_raw.sub similarity index 100% rename from assets/unit_tests/subghz/magellan_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/magellan_raw.sub diff --git a/assets/unit_tests/subghz/marantec.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/marantec.sub similarity index 100% rename from assets/unit_tests/subghz/marantec.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/marantec.sub diff --git a/assets/unit_tests/subghz/marantec_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/marantec_raw.sub similarity index 100% rename from assets/unit_tests/subghz/marantec_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/marantec_raw.sub diff --git a/assets/unit_tests/subghz/megacode.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/megacode.sub similarity index 100% rename from assets/unit_tests/subghz/megacode.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/megacode.sub diff --git a/assets/unit_tests/subghz/megacode_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/megacode_raw.sub similarity index 100% rename from assets/unit_tests/subghz/megacode_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/megacode_raw.sub diff --git a/assets/unit_tests/subghz/nero_radio_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/nero_radio_raw.sub similarity index 100% rename from assets/unit_tests/subghz/nero_radio_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/nero_radio_raw.sub diff --git a/assets/unit_tests/subghz/nero_sketch_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/nero_sketch_raw.sub similarity index 100% rename from assets/unit_tests/subghz/nero_sketch_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/nero_sketch_raw.sub diff --git a/assets/unit_tests/subghz/nice_flo.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/nice_flo.sub similarity index 100% rename from assets/unit_tests/subghz/nice_flo.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/nice_flo.sub diff --git a/assets/unit_tests/subghz/nice_flo_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/nice_flo_raw.sub similarity index 100% rename from assets/unit_tests/subghz/nice_flo_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/nice_flo_raw.sub diff --git a/assets/unit_tests/subghz/nice_flor_s_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/nice_flor_s_raw.sub similarity index 100% rename from assets/unit_tests/subghz/nice_flor_s_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/nice_flor_s_raw.sub diff --git a/assets/unit_tests/subghz/nice_one_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/nice_one_raw.sub similarity index 100% rename from assets/unit_tests/subghz/nice_one_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/nice_one_raw.sub diff --git a/assets/unit_tests/subghz/phoenix_v2.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/phoenix_v2.sub similarity index 100% rename from assets/unit_tests/subghz/phoenix_v2.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/phoenix_v2.sub diff --git a/assets/unit_tests/subghz/phoenix_v2_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/phoenix_v2_raw.sub similarity index 100% rename from assets/unit_tests/subghz/phoenix_v2_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/phoenix_v2_raw.sub diff --git a/assets/unit_tests/subghz/power_smart.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/power_smart.sub similarity index 100% rename from assets/unit_tests/subghz/power_smart.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/power_smart.sub diff --git a/assets/unit_tests/subghz/power_smart_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/power_smart_raw.sub similarity index 100% rename from assets/unit_tests/subghz/power_smart_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/power_smart_raw.sub diff --git a/assets/unit_tests/subghz/princeton.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/princeton.sub similarity index 100% rename from assets/unit_tests/subghz/princeton.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/princeton.sub diff --git a/assets/unit_tests/subghz/princeton_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/princeton_raw.sub similarity index 100% rename from assets/unit_tests/subghz/princeton_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/princeton_raw.sub diff --git a/assets/unit_tests/subghz/scher_khan_magic_code.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/scher_khan_magic_code.sub similarity index 100% rename from assets/unit_tests/subghz/scher_khan_magic_code.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/scher_khan_magic_code.sub diff --git a/assets/unit_tests/subghz/security_pls_1_0.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/security_pls_1_0.sub similarity index 100% rename from assets/unit_tests/subghz/security_pls_1_0.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/security_pls_1_0.sub diff --git a/assets/unit_tests/subghz/security_pls_1_0_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/security_pls_1_0_raw.sub similarity index 100% rename from assets/unit_tests/subghz/security_pls_1_0_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/security_pls_1_0_raw.sub diff --git a/assets/unit_tests/subghz/security_pls_2_0.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/security_pls_2_0.sub similarity index 100% rename from assets/unit_tests/subghz/security_pls_2_0.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/security_pls_2_0.sub diff --git a/assets/unit_tests/subghz/security_pls_2_0_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/security_pls_2_0_raw.sub similarity index 100% rename from assets/unit_tests/subghz/security_pls_2_0_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/security_pls_2_0_raw.sub diff --git a/assets/unit_tests/subghz/smc5326.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/smc5326.sub similarity index 100% rename from assets/unit_tests/subghz/smc5326.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/smc5326.sub diff --git a/assets/unit_tests/subghz/smc5326_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/smc5326_raw.sub similarity index 100% rename from assets/unit_tests/subghz/smc5326_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/smc5326_raw.sub diff --git a/assets/unit_tests/subghz/somfy_keytis_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/somfy_keytis_raw.sub similarity index 100% rename from assets/unit_tests/subghz/somfy_keytis_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/somfy_keytis_raw.sub diff --git a/assets/unit_tests/subghz/somfy_telis_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/somfy_telis_raw.sub similarity index 100% rename from assets/unit_tests/subghz/somfy_telis_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/somfy_telis_raw.sub diff --git a/assets/unit_tests/subghz/test_random_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/test_random_raw.sub similarity index 100% rename from assets/unit_tests/subghz/test_random_raw.sub rename to applications/debug/unit_tests/resources/unit_tests/subghz/test_random_raw.sub diff --git a/applications/main/bad_usb/application.fam b/applications/main/bad_usb/application.fam index 5c42c9fa3f1..9844e248df9 100644 --- a/applications/main/bad_usb/application.fam +++ b/applications/main/bad_usb/application.fam @@ -6,6 +6,7 @@ App( stack_size=2 * 1024, icon="A_BadUsb_14", order=70, + resources="resources", fap_libs=["assets"], fap_icon="icon.png", fap_category="USB", diff --git a/assets/resources/badusb/Install_qFlipper_macOS.txt b/applications/main/bad_usb/resources/badusb/Install_qFlipper_macOS.txt similarity index 100% rename from assets/resources/badusb/Install_qFlipper_macOS.txt rename to applications/main/bad_usb/resources/badusb/Install_qFlipper_macOS.txt diff --git a/assets/resources/badusb/Install_qFlipper_windows.txt b/applications/main/bad_usb/resources/badusb/Install_qFlipper_windows.txt similarity index 100% rename from assets/resources/badusb/Install_qFlipper_windows.txt rename to applications/main/bad_usb/resources/badusb/Install_qFlipper_windows.txt diff --git a/assets/resources/badusb/assets/layouts/ba-BA.kl b/applications/main/bad_usb/resources/badusb/assets/layouts/ba-BA.kl similarity index 100% rename from assets/resources/badusb/assets/layouts/ba-BA.kl rename to applications/main/bad_usb/resources/badusb/assets/layouts/ba-BA.kl diff --git a/assets/resources/badusb/assets/layouts/cz_CS.kl b/applications/main/bad_usb/resources/badusb/assets/layouts/cz_CS.kl similarity index 100% rename from assets/resources/badusb/assets/layouts/cz_CS.kl rename to applications/main/bad_usb/resources/badusb/assets/layouts/cz_CS.kl diff --git a/assets/resources/badusb/assets/layouts/da-DA.kl b/applications/main/bad_usb/resources/badusb/assets/layouts/da-DA.kl similarity index 100% rename from assets/resources/badusb/assets/layouts/da-DA.kl rename to applications/main/bad_usb/resources/badusb/assets/layouts/da-DA.kl diff --git a/assets/resources/badusb/assets/layouts/de-CH.kl b/applications/main/bad_usb/resources/badusb/assets/layouts/de-CH.kl similarity index 100% rename from assets/resources/badusb/assets/layouts/de-CH.kl rename to applications/main/bad_usb/resources/badusb/assets/layouts/de-CH.kl diff --git a/assets/resources/badusb/assets/layouts/de-DE.kl b/applications/main/bad_usb/resources/badusb/assets/layouts/de-DE.kl similarity index 100% rename from assets/resources/badusb/assets/layouts/de-DE.kl rename to applications/main/bad_usb/resources/badusb/assets/layouts/de-DE.kl diff --git a/assets/resources/badusb/assets/layouts/dvorak.kl b/applications/main/bad_usb/resources/badusb/assets/layouts/dvorak.kl similarity index 100% rename from assets/resources/badusb/assets/layouts/dvorak.kl rename to applications/main/bad_usb/resources/badusb/assets/layouts/dvorak.kl diff --git a/assets/resources/badusb/assets/layouts/en-UK.kl b/applications/main/bad_usb/resources/badusb/assets/layouts/en-UK.kl similarity index 100% rename from assets/resources/badusb/assets/layouts/en-UK.kl rename to applications/main/bad_usb/resources/badusb/assets/layouts/en-UK.kl diff --git a/assets/resources/badusb/assets/layouts/en-US.kl b/applications/main/bad_usb/resources/badusb/assets/layouts/en-US.kl similarity index 100% rename from assets/resources/badusb/assets/layouts/en-US.kl rename to applications/main/bad_usb/resources/badusb/assets/layouts/en-US.kl diff --git a/assets/resources/badusb/assets/layouts/es-ES.kl b/applications/main/bad_usb/resources/badusb/assets/layouts/es-ES.kl similarity index 100% rename from assets/resources/badusb/assets/layouts/es-ES.kl rename to applications/main/bad_usb/resources/badusb/assets/layouts/es-ES.kl diff --git a/assets/resources/badusb/assets/layouts/fr-BE.kl b/applications/main/bad_usb/resources/badusb/assets/layouts/fr-BE.kl similarity index 100% rename from assets/resources/badusb/assets/layouts/fr-BE.kl rename to applications/main/bad_usb/resources/badusb/assets/layouts/fr-BE.kl diff --git a/assets/resources/badusb/assets/layouts/fr-CA.kl b/applications/main/bad_usb/resources/badusb/assets/layouts/fr-CA.kl similarity index 100% rename from assets/resources/badusb/assets/layouts/fr-CA.kl rename to applications/main/bad_usb/resources/badusb/assets/layouts/fr-CA.kl diff --git a/assets/resources/badusb/assets/layouts/fr-CH.kl b/applications/main/bad_usb/resources/badusb/assets/layouts/fr-CH.kl similarity index 100% rename from assets/resources/badusb/assets/layouts/fr-CH.kl rename to applications/main/bad_usb/resources/badusb/assets/layouts/fr-CH.kl diff --git a/assets/resources/badusb/assets/layouts/fr-FR-mac.kl b/applications/main/bad_usb/resources/badusb/assets/layouts/fr-FR-mac.kl similarity index 100% rename from assets/resources/badusb/assets/layouts/fr-FR-mac.kl rename to applications/main/bad_usb/resources/badusb/assets/layouts/fr-FR-mac.kl diff --git a/assets/resources/badusb/assets/layouts/fr-FR.kl b/applications/main/bad_usb/resources/badusb/assets/layouts/fr-FR.kl similarity index 100% rename from assets/resources/badusb/assets/layouts/fr-FR.kl rename to applications/main/bad_usb/resources/badusb/assets/layouts/fr-FR.kl diff --git a/assets/resources/badusb/assets/layouts/hr-HR.kl b/applications/main/bad_usb/resources/badusb/assets/layouts/hr-HR.kl similarity index 100% rename from assets/resources/badusb/assets/layouts/hr-HR.kl rename to applications/main/bad_usb/resources/badusb/assets/layouts/hr-HR.kl diff --git a/assets/resources/badusb/assets/layouts/hu-HU.kl b/applications/main/bad_usb/resources/badusb/assets/layouts/hu-HU.kl similarity index 100% rename from assets/resources/badusb/assets/layouts/hu-HU.kl rename to applications/main/bad_usb/resources/badusb/assets/layouts/hu-HU.kl diff --git a/assets/resources/badusb/assets/layouts/it-IT.kl b/applications/main/bad_usb/resources/badusb/assets/layouts/it-IT.kl similarity index 100% rename from assets/resources/badusb/assets/layouts/it-IT.kl rename to applications/main/bad_usb/resources/badusb/assets/layouts/it-IT.kl diff --git a/assets/resources/badusb/assets/layouts/nb-NO.kl b/applications/main/bad_usb/resources/badusb/assets/layouts/nb-NO.kl similarity index 100% rename from assets/resources/badusb/assets/layouts/nb-NO.kl rename to applications/main/bad_usb/resources/badusb/assets/layouts/nb-NO.kl diff --git a/assets/resources/badusb/assets/layouts/nl-NL.kl b/applications/main/bad_usb/resources/badusb/assets/layouts/nl-NL.kl similarity index 100% rename from assets/resources/badusb/assets/layouts/nl-NL.kl rename to applications/main/bad_usb/resources/badusb/assets/layouts/nl-NL.kl diff --git a/assets/resources/badusb/assets/layouts/pt-BR.kl b/applications/main/bad_usb/resources/badusb/assets/layouts/pt-BR.kl similarity index 100% rename from assets/resources/badusb/assets/layouts/pt-BR.kl rename to applications/main/bad_usb/resources/badusb/assets/layouts/pt-BR.kl diff --git a/assets/resources/badusb/assets/layouts/pt-PT.kl b/applications/main/bad_usb/resources/badusb/assets/layouts/pt-PT.kl similarity index 100% rename from assets/resources/badusb/assets/layouts/pt-PT.kl rename to applications/main/bad_usb/resources/badusb/assets/layouts/pt-PT.kl diff --git a/assets/resources/badusb/assets/layouts/si-SI.kl b/applications/main/bad_usb/resources/badusb/assets/layouts/si-SI.kl similarity index 100% rename from assets/resources/badusb/assets/layouts/si-SI.kl rename to applications/main/bad_usb/resources/badusb/assets/layouts/si-SI.kl diff --git a/assets/resources/badusb/assets/layouts/sk-SK.kl b/applications/main/bad_usb/resources/badusb/assets/layouts/sk-SK.kl similarity index 100% rename from assets/resources/badusb/assets/layouts/sk-SK.kl rename to applications/main/bad_usb/resources/badusb/assets/layouts/sk-SK.kl diff --git a/assets/resources/badusb/assets/layouts/sv-SE.kl b/applications/main/bad_usb/resources/badusb/assets/layouts/sv-SE.kl similarity index 100% rename from assets/resources/badusb/assets/layouts/sv-SE.kl rename to applications/main/bad_usb/resources/badusb/assets/layouts/sv-SE.kl diff --git a/assets/resources/badusb/assets/layouts/tr-TR.kl b/applications/main/bad_usb/resources/badusb/assets/layouts/tr-TR.kl similarity index 100% rename from assets/resources/badusb/assets/layouts/tr-TR.kl rename to applications/main/bad_usb/resources/badusb/assets/layouts/tr-TR.kl diff --git a/assets/resources/badusb/demo_macos.txt b/applications/main/bad_usb/resources/badusb/demo_macos.txt similarity index 100% rename from assets/resources/badusb/demo_macos.txt rename to applications/main/bad_usb/resources/badusb/demo_macos.txt diff --git a/assets/resources/badusb/demo_windows.txt b/applications/main/bad_usb/resources/badusb/demo_windows.txt similarity index 100% rename from assets/resources/badusb/demo_windows.txt rename to applications/main/bad_usb/resources/badusb/demo_windows.txt diff --git a/applications/main/infrared/application.fam b/applications/main/infrared/application.fam index b78b088a727..055d6c3e294 100644 --- a/applications/main/infrared/application.fam +++ b/applications/main/infrared/application.fam @@ -7,6 +7,7 @@ App( icon="A_Infrared_14", stack_size=3 * 1024, order=40, + resources="resources", fap_libs=["assets"], fap_icon="icon.png", fap_category="Infrared", diff --git a/assets/resources/infrared/assets/ac.ir b/applications/main/infrared/resources/infrared/assets/ac.ir similarity index 100% rename from assets/resources/infrared/assets/ac.ir rename to applications/main/infrared/resources/infrared/assets/ac.ir diff --git a/assets/resources/infrared/assets/audio.ir b/applications/main/infrared/resources/infrared/assets/audio.ir similarity index 100% rename from assets/resources/infrared/assets/audio.ir rename to applications/main/infrared/resources/infrared/assets/audio.ir diff --git a/assets/resources/infrared/assets/projector.ir b/applications/main/infrared/resources/infrared/assets/projector.ir similarity index 100% rename from assets/resources/infrared/assets/projector.ir rename to applications/main/infrared/resources/infrared/assets/projector.ir diff --git a/assets/resources/infrared/assets/tv.ir b/applications/main/infrared/resources/infrared/assets/tv.ir similarity index 100% rename from assets/resources/infrared/assets/tv.ir rename to applications/main/infrared/resources/infrared/assets/tv.ir diff --git a/applications/main/nfc/application.fam b/applications/main/nfc/application.fam index 20ebd4ca081..3c8dab2bf1f 100644 --- a/applications/main/nfc/application.fam +++ b/applications/main/nfc/application.fam @@ -7,6 +7,7 @@ App( icon="A_NFC_14", stack_size=5 * 1024, order=30, + resources="resources", fap_libs=["assets"], fap_icon="icon.png", fap_category="NFC", diff --git a/assets/resources/nfc/assets/aid.nfc b/applications/main/nfc/resources/nfc/assets/aid.nfc similarity index 100% rename from assets/resources/nfc/assets/aid.nfc rename to applications/main/nfc/resources/nfc/assets/aid.nfc diff --git a/assets/resources/nfc/assets/country_code.nfc b/applications/main/nfc/resources/nfc/assets/country_code.nfc similarity index 100% rename from assets/resources/nfc/assets/country_code.nfc rename to applications/main/nfc/resources/nfc/assets/country_code.nfc diff --git a/assets/resources/nfc/assets/currency_code.nfc b/applications/main/nfc/resources/nfc/assets/currency_code.nfc similarity index 100% rename from assets/resources/nfc/assets/currency_code.nfc rename to applications/main/nfc/resources/nfc/assets/currency_code.nfc diff --git a/assets/resources/nfc/assets/mf_classic_dict.nfc b/applications/main/nfc/resources/nfc/assets/mf_classic_dict.nfc similarity index 100% rename from assets/resources/nfc/assets/mf_classic_dict.nfc rename to applications/main/nfc/resources/nfc/assets/mf_classic_dict.nfc diff --git a/applications/main/subghz/application.fam b/applications/main/subghz/application.fam index 4f21cb6c4e9..ba9b16abfd7 100644 --- a/applications/main/subghz/application.fam +++ b/applications/main/subghz/application.fam @@ -7,6 +7,7 @@ App( icon="A_Sub1ghz_14", stack_size=3 * 1024, order=10, + resources="resources", fap_libs=["assets", "hwdrivers"], fap_icon="icon.png", fap_category="Sub-GHz", diff --git a/assets/resources/subghz/assets/alutech_at_4n b/applications/main/subghz/resources/subghz/assets/alutech_at_4n similarity index 100% rename from assets/resources/subghz/assets/alutech_at_4n rename to applications/main/subghz/resources/subghz/assets/alutech_at_4n diff --git a/assets/resources/subghz/assets/came_atomo b/applications/main/subghz/resources/subghz/assets/came_atomo similarity index 100% rename from assets/resources/subghz/assets/came_atomo rename to applications/main/subghz/resources/subghz/assets/came_atomo diff --git a/assets/resources/subghz/assets/keeloq_mfcodes b/applications/main/subghz/resources/subghz/assets/keeloq_mfcodes similarity index 100% rename from assets/resources/subghz/assets/keeloq_mfcodes rename to applications/main/subghz/resources/subghz/assets/keeloq_mfcodes diff --git a/assets/resources/subghz/assets/keeloq_mfcodes_user.example b/applications/main/subghz/resources/subghz/assets/keeloq_mfcodes_user.example similarity index 100% rename from assets/resources/subghz/assets/keeloq_mfcodes_user.example rename to applications/main/subghz/resources/subghz/assets/keeloq_mfcodes_user.example diff --git a/assets/resources/subghz/assets/nice_flor_s b/applications/main/subghz/resources/subghz/assets/nice_flor_s similarity index 100% rename from assets/resources/subghz/assets/nice_flor_s rename to applications/main/subghz/resources/subghz/assets/nice_flor_s diff --git a/assets/resources/subghz/assets/setting_user.example b/applications/main/subghz/resources/subghz/assets/setting_user.example similarity index 100% rename from assets/resources/subghz/assets/setting_user.example rename to applications/main/subghz/resources/subghz/assets/setting_user.example diff --git a/applications/main/u2f/application.fam b/applications/main/u2f/application.fam index 8167e6277b3..bf41eb0f7a5 100644 --- a/applications/main/u2f/application.fam +++ b/applications/main/u2f/application.fam @@ -6,6 +6,7 @@ App( stack_size=2 * 1024, icon="A_U2F_14", order=80, + resources="resources", fap_libs=["assets"], fap_category="USB", fap_icon="icon.png", diff --git a/assets/resources/u2f/assets/cert.der b/applications/main/u2f/resources/u2f/assets/cert.der similarity index 100% rename from assets/resources/u2f/assets/cert.der rename to applications/main/u2f/resources/u2f/assets/cert.der diff --git a/assets/resources/u2f/assets/cert_key.u2f b/applications/main/u2f/resources/u2f/assets/cert_key.u2f similarity index 100% rename from assets/resources/u2f/assets/cert_key.u2f rename to applications/main/u2f/resources/u2f/assets/cert_key.u2f diff --git a/applications/services/loader/loader.c b/applications/services/loader/loader.c index 53f70a1ec6b..29ec86ac669 100644 --- a/applications/services/loader/loader.c +++ b/applications/services/loader/loader.c @@ -179,7 +179,7 @@ static FlipperInternalApplication const* loader_find_application_by_name_in_list const FlipperInternalApplication* list, const uint32_t n_apps) { for(size_t i = 0; i < n_apps; i++) { - if(strcmp(name, list[i].name) == 0) { + if((strcmp(name, list[i].name) == 0) || (strcmp(name, list[i].appid) == 0)) { return &list[i]; } } diff --git a/applications/services/loader/loader.h b/applications/services/loader/loader.h index 3da676e651a..cca65628f88 100644 --- a/applications/services/loader/loader.h +++ b/applications/services/loader/loader.h @@ -29,7 +29,7 @@ typedef struct { /** * @brief Start application * @param[in] instance loader instance - * @param[in] name application name + * @param[in] name application name or id * @param[in] args application arguments * @param[out] error_message detailed error message, can be NULL * @return LoaderStatus @@ -40,7 +40,7 @@ LoaderStatus /** * @brief Start application with GUI error message * @param[in] instance loader instance - * @param[in] name application name + * @param[in] name application name or id * @param[in] args application arguments * @return LoaderStatus */ diff --git a/applications/system/snake_game/application.fam b/applications/system/snake_game/application.fam index 9a99ebac840..9e803f65db5 100644 --- a/applications/system/snake_game/application.fam +++ b/applications/system/snake_game/application.fam @@ -5,7 +5,6 @@ App( entry_point="snake_game_app", requires=["gui"], stack_size=1 * 1024, - targets=["f7"], fap_version="1.0", fap_description="Classic Snake Game", fap_icon="snake_10px.png", diff --git a/assets/.gitignore b/assets/.gitignore index a66a6eed4cf..ca338d63336 100644 --- a/assets/.gitignore +++ b/assets/.gitignore @@ -1,5 +1 @@ /core2_firmware -/resources/Manifest -/resources/apps/* -/resources/dolphin/* -/resources/apps_data/**/*.fal diff --git a/assets/ReadMe.md b/assets/ReadMe.md index 2d493b4fec4..84310e731f6 100644 --- a/assets/ReadMe.md +++ b/assets/ReadMe.md @@ -32,10 +32,7 @@ Good starting point: https://docs.unrealengine.com/4.27/en-US/ProductionPipeline Don't include assets that you are not using, compiler is not going to strip unused assets. # Structure -- `compiled` - Output folder made for compiled assets, after building project, in `build` directory. - `dolphin` - Dolphin game assets sources. Goes to `compiled` and `resources` folders in `build` directory. - `icons` - Icons sources. Goes to `compiled` folder in `build` directory. - `protobuf` - Protobuf sources. Goes to `compiled` folder in `build` directory. -- `resources` - Assets that is going to be provisioned to SD card. - `slideshow` - One-time slideshows for desktop -- `unit_tests` - Some pre-defined signals for testing purposes. diff --git a/assets/SConscript b/assets/SConscript index 9bd273626d1..c10de78af72 100644 --- a/assets/SConscript +++ b/assets/SConscript @@ -1,16 +1,16 @@ -from fbt.version import get_git_commit_unix_timestamp - Import("env") assetsenv = env.Clone( tools=["fbt_assets"], FW_LIB_NAME="assets", - GIT_UNIX_TIMESTAMP=get_git_commit_unix_timestamp(), + ASSETS_WORK_DIR=env.Dir("compiled"), + ASSETS_SRC_DIR=env.Dir("#/assets"), ) assetsenv.ApplyLibFlags() icons = assetsenv.CompileIcons( - assetsenv.Dir("compiled"), assetsenv.Dir("#/assets/icons") + assetsenv["ASSETS_WORK_DIR"], + assetsenv["ASSETS_SRC_DIR"].Dir("icons"), ) assetsenv.Alias("icons", icons) @@ -18,7 +18,7 @@ assetsenv.Alias("icons", icons) # Protobuf .proto -> .c + .h proto_src = assetsenv.Glob("protobuf/*.proto", source=True) proto_options = assetsenv.Glob("protobuf/*.options", source=True) -proto = assetsenv.ProtoBuilder(assetsenv.Dir("compiled"), proto_src) +proto = assetsenv.ProtoBuilder(assetsenv["ASSETS_WORK_DIR"], proto_src) assetsenv.Depends(proto, proto_options) # Precious(proto) assetsenv.Alias("proto", proto) @@ -27,8 +27,8 @@ assetsenv.Alias("proto", proto) # Internal animations dolphin_internal = assetsenv.DolphinSymBuilder( - assetsenv.Dir("compiled"), - assetsenv.Dir("#/assets/dolphin"), + assetsenv["ASSETS_WORK_DIR"], + assetsenv["ASSETS_SRC_DIR"].Dir("dolphin"), DOLPHIN_RES_TYPE="internal", ) assetsenv.Alias("dolphin_internal", dolphin_internal) @@ -37,8 +37,8 @@ assetsenv.Alias("dolphin_internal", dolphin_internal) # Blocking animations dolphin_blocking = assetsenv.DolphinSymBuilder( - assetsenv.Dir("compiled"), - assetsenv.Dir("#/assets/dolphin"), + assetsenv["ASSETS_WORK_DIR"], + assetsenv["ASSETS_SRC_DIR"].Dir("dolphin"), DOLPHIN_RES_TYPE="blocking", ) assetsenv.Alias("dolphin_blocking", dolphin_blocking) @@ -46,8 +46,8 @@ assetsenv.Alias("dolphin_blocking", dolphin_blocking) # Protobuf version meta proto_ver = assetsenv.ProtoVerBuilder( - "compiled/protobuf_version.h", - "#/assets/protobuf/Changelog", + "${ASSETS_WORK_DIR}/protobuf_version.h", + assetsenv["ASSETS_SRC_DIR"].File("protobuf/Changelog"), ) assetsenv.Depends(proto_ver, proto) assetsenv.Alias("proto_ver", proto_ver) @@ -61,41 +61,19 @@ assetsenv.Install("${LIB_DIST_DIR}", assetslib) # Resources for SD card -env.SetDefault(FW_RESOURCES=None) if assetsenv["IS_BASE_FIRMWARE"]: + dolphin_external_out_dir = assetsenv["ASSETS_WORK_DIR"].Dir("dolphin") # External dolphin animations dolphin_external = assetsenv.DolphinExtBuilder( - assetsenv.Dir("#/assets/resources/dolphin"), - assetsenv.Dir("#/assets/dolphin"), + dolphin_external_out_dir, + assetsenv["ASSETS_SRC_DIR"].Dir("dolphin"), DOLPHIN_RES_TYPE="external", ) if assetsenv["FORCE"]: assetsenv.AlwaysBuild(dolphin_external) assetsenv.Alias("dolphin_ext", dolphin_external) - assetsenv.Clean(dolphin_external, assetsenv.Dir("#/assets/resources/dolphin")) - - # Resources manifest - resources = assetsenv.Command( - "#/assets/resources/Manifest", - assetsenv.GlobRecursive( - "*", - assetsenv.Dir("resources").srcnode(), - exclude=["Manifest"], - ), - action=Action( - '${PYTHON3} "${ASSETS_COMPILER}" manifest "${TARGET.dir.posix}" --timestamp=${GIT_UNIX_TIMESTAMP}', - "${RESMANIFESTCOMSTR}", - ), - ) - assetsenv.Precious(resources) - assetsenv.AlwaysBuild(resources) - assetsenv.Clean( - resources, - assetsenv.Dir("#/assets/resources/apps"), - ) + assetsenv.Clean(dolphin_external, dolphin_external_out_dir) - # Exporting resources node to external environment - env.Replace(FW_RESOURCES=resources) - assetsenv.Alias("resources", resources) + env.Replace(DOLPHIN_EXTERNAL_OUT_DIR=dolphin_external_out_dir) Return("assetslib") diff --git a/documentation/AppManifests.md b/documentation/AppManifests.md index 72c15ad48fe..0b3217c58cc 100644 --- a/documentation/AppManifests.md +++ b/documentation/AppManifests.md @@ -41,6 +41,7 @@ Only two parameters are mandatory: **_appid_** and **_apptype_**. Others are opt - **order**: order of an application within its group when sorting entries in it. The lower the order is, the closer to the start of the list the item is placed. _Used for ordering startup hooks and menu entries._ - **sdk_headers**: list of C header files from this app's code to include in API definitions for external applications. - **targets**: list of strings and target names with which this application is compatible. If not specified, the application is built for all targets. The default value is `["all"]`. +- **resources**: name of a folder within the application's source folder to be used for packacking SD card resources for this application. They will only be used if application is included in build configuration. The default value is `""`, meaning no resources are packaged. #### Parameters for external applications diff --git a/documentation/AppsOnSDCard.md b/documentation/AppsOnSDCard.md index f04793b4f4c..3f6d51acf06 100644 --- a/documentation/AppsOnSDCard.md +++ b/documentation/AppsOnSDCard.md @@ -61,7 +61,7 @@ The App Loader allocates memory for the application and copies it to RAM, proces ## API versioning -Not all parts of firmware are available for external applications. A subset of available functions and variables is defined in the "api_symbols.csv" file, which is a part of the firmware target definition in the `firmware/targets/` directory. +Not all parts of firmware are available for external applications. A subset of available functions and variables is defined in the "api_symbols.csv" file, which is a part of the firmware target definition in the `targets/` directory. **`fbt`** uses semantic versioning for the API. The major version is incremented when there are breaking changes in the API. The minor version is incremented when new features are added. diff --git a/documentation/HardwareTargets.md b/documentation/HardwareTargets.md index 0c3474839b7..b3213d4f506 100644 --- a/documentation/HardwareTargets.md +++ b/documentation/HardwareTargets.md @@ -2,7 +2,7 @@ Flipper's firmware is modular and supports different hardware configurations in a common code base. It encapsulates hardware-specific differences in `furi_hal`, board initialization code, linker files, SDK data and other information in a _target definition_. -Target-specific files are placed in a single sub-folder in `firmware/targets`. It must contain a target definition file, `target.json`, and may contain other files if they are referenced by current target's definition. By default, `fbt` gathers all source files in target folder, unless they are explicitly excluded. +Target-specific files are placed in a single sub-folder in `targets`. It must contain a target definition file, `target.json`, and may contain other files if they are referenced by current target's definition. By default, `fbt` gathers all source files in target folder, unless they are explicitly excluded. Targets can inherit most code parts from other targets, to reduce common code duplication. diff --git a/documentation/UnitTests.md b/documentation/UnitTests.md index 4717daa8ca0..9352917cdc2 100644 --- a/documentation/UnitTests.md +++ b/documentation/UnitTests.md @@ -15,10 +15,9 @@ Running existing unit tests is useful to ensure that the new code doesn't introd To run the unit tests, follow these steps: -1. Compile the firmware with the tests enabled: `./fbt FIRMWARE_APP_SET=unit_tests`. -2. Flash the firmware using your preferred method. -3. Copy the [assets/unit_tests](/assets/unit_tests) folder to the root of your Flipper Zero's SD card. -4. Launch the CLI session and run the `unit_tests` command. +1. Compile the firmware with the tests enabled: `./fbt FIRMWARE_APP_SET=unit_tests updater_package`. +2. Flash the firmware using your preferred method, including SD card resources (`build/latest/resources`). +3. Launch the CLI session and run the `unit_tests` command. **NOTE:** To run a particular test (and skip all others), specify its name as the command argument. See [test_index.c](/applications/debug/unit_tests/test_index.c) for the complete list of test names. @@ -33,7 +32,7 @@ The common entry point for all tests is the [unit_tests](/applications/debug/uni #### Test assets -Some unit tests require external data in order to function. These files (commonly called assets) reside in the [assets/unit_tests](/assets/unit_tests) directory in their respective subdirectories. Asset files can be of any type (plain text, FlipperFormat (FFF), binary, etc.). +Some unit tests require external data in order to function. These files (commonly called assets) reside in the [unit_tests](/applications/debug/unit_tests/resources/unit_tests) directory in their respective subdirectories. Asset files can be of any type (plain text, FlipperFormat (FFF), binary, etc.). ### Application-specific @@ -42,10 +41,10 @@ Some unit tests require external data in order to function. These files (commonl Each infrared protocol has a corresponding set of unit tests, so it makes sense to implement one when adding support for a new protocol. To add unit tests for your protocol, follow these steps: -1. Create a file named `test_.irtest` in the [assets](/assets/unit_tests/infrared) directory. +1. Create a file named `test_.irtest` in the [assets](/applications/debug/unit_tests/resources/unit_tests/infrared) directory. 2. Fill it with the test data (more on it below). 3. Add the test code to [infrared_test.c](/applications/debug/unit_tests/infrared/infrared_test.c). -4. Update the [assets](/assets/unit_tests/infrared) on your Flipper Zero and run the tests to see if they pass. +4. Build and install firmware with resources, install it on your Flipper and run the tests to see if they pass. ##### Test data format diff --git a/documentation/UniversalRemotes.md b/documentation/UniversalRemotes.md index 325f640d7e0..213709afbfe 100644 --- a/documentation/UniversalRemotes.md +++ b/documentation/UniversalRemotes.md @@ -13,7 +13,7 @@ Each signal is recorded using the following algorithm: The signal names are self-explanatory. Remember to make sure that every recorded signal does what it's supposed to. -If everything checks out, append these signals **to the end** of the [TV universal remote file](/assets/resources/infrared/assets/tv.ir). +If everything checks out, append these signals **to the end** of the [TV universal remote file](/applications/main/infrared/resources/infrared/assets/tv.ir). ## Audio players @@ -23,7 +23,7 @@ The signal names are self-explanatory. On many remotes, the `Play` button doubles as `Pause`. In this case, record it as `Play` omitting the `Pause`. Make sure that every signal does what it's supposed to. -If everything checks out, append these signals **to the end** of the [audio player universal remote file](/assets/resources/infrared/assets/audio.ir). +If everything checks out, append these signals **to the end** of the [audio player universal remote file](/applications/main/infrared/resources/infrared/assets/audio.ir). ## Projectors @@ -67,7 +67,7 @@ Finally, record the `Off` signal: The resulting remote file should now contain 6 signals. You can omit any of them, but you then won't be able to use their functionality. Test the file against the actual device. Make sure that every signal does what it's supposed to. -If everything checks out, append these signals **to the end** of the [A/C universal remote file](/assets/resources/infrared/assets/ac.ir). +If everything checks out, append these signals **to the end** of the [A/C universal remote file](/applications/main/infrared/resources/infrared/assets/ac.ir). ## Final steps diff --git a/documentation/file_formats/InfraredFileFormats.md b/documentation/file_formats/InfraredFileFormats.md index c9b6a953655..4d43bd5b8e0 100644 --- a/documentation/file_formats/InfraredFileFormats.md +++ b/documentation/file_formats/InfraredFileFormats.md @@ -72,9 +72,9 @@ Known protocols are represented in the `parsed` form, whereas non-recognized sig ### Examples -- [TV Universal Library](/assets/resources/infrared/assets/tv.ir) -- [A/C Universal Library](/assets/resources/infrared/assets/ac.ir) -- [Audio Universal Library](/assets/resources/infrared/assets/audio.ir) +- [TV Universal Library](/applications/main/infrared/resources/infrared/assets/tv.ir) +- [A/C Universal Library](/applications/main/infrared/resources/infrared/assets/ac.ir) +- [Audio Universal Library](/applications/main/infrared/resources/infrared/assets/audio.ir) ### Description @@ -92,7 +92,7 @@ See [Universal Remotes](/documentation/UniversalRemotes.md) for more information ### Examples -See [Infrared Unit Tests](/assets/unit_tests/infrared/) for various examples. +See [Infrared Unit Tests](/applications/debug/unit_tests/resources/unit_tests/infrared/) for various examples. ### Description diff --git a/firmware.scons b/firmware.scons index 82f775d719c..e8e50022c70 100644 --- a/firmware.scons +++ b/firmware.scons @@ -1,15 +1,10 @@ -from SCons.Errors import UserError -from SCons.Node import FS - import itertools -from fbt_extra.util import ( - should_gen_cdb_and_link_dir, - link_elf_dir_as_latest, -) - from fbt.sdk.cache import LazySdkVersionLoader - +from fbt.version import get_git_commit_unix_timestamp +from fbt_extra.util import link_elf_dir_as_latest, should_gen_cdb_and_link_dir +from SCons.Errors import UserError +from SCons.Node import FS Import("ENV", "fw_build_meta") @@ -22,12 +17,15 @@ env = ENV.Clone( "pvsstudio", "fbt_hwtarget", "fbt_envhooks", + "fbt_resources", ], COMPILATIONDB_USE_ABSPATH=False, BUILD_DIR=fw_build_meta["build_dir"], IS_BASE_FIRMWARE=fw_build_meta["type"] == "firmware", FW_FLAVOR=fw_build_meta["flavor"], LIB_DIST_DIR=fw_build_meta["build_dir"].Dir("lib"), + RESOURCES_ROOT=fw_build_meta["build_dir"].Dir("resources"), + TARGETS_ROOT=Dir("#/targets"), LINT_SOURCES=[ Dir("applications"), ], @@ -37,7 +35,7 @@ env = ENV.Clone( CPPPATH=[ "#/furi", *(f"#/{app_dir[0]}" for app_dir in ENV["APPDIRS"] if app_dir[1]), - "#/firmware/targets/furi_hal_include", + "#/targets/furi_hal_include", ], # Specific flags for building libraries - always do optimized builds FW_LIB_OPTS={ @@ -104,7 +102,7 @@ lib_targets = env.BuildModules( [ "lib", "assets", - "firmware", + "targets", "furi", ], ) @@ -144,12 +142,26 @@ fwenv.PrepareApplicationsBuild() # Build external apps + configure SDK if env["IS_BASE_FIRMWARE"]: fwenv.SetDefault(FBT_FAP_DEBUG_ELF_ROOT=fwenv["BUILD_DIR"].Dir(".extapps")) - fwenv["FW_EXTAPPS"] = SConscript( + fw_extapps = fwenv["FW_EXTAPPS"] = SConscript( "site_scons/extapps.scons", exports={"ENV": fwenv}, ) - fw_artifacts.append(fwenv["FW_EXTAPPS"].sdk_tree) + fw_artifacts.append(fw_extapps.sdk_tree) + + # Resources for SD card + resources = fwenv.ResourcesDist( + _EXTRA_DIST=[fwenv["DOLPHIN_EXTERNAL_OUT_DIR"]], + ) + + manifest = fwenv.ManifestBuilder( + "${RESOURCES_ROOT}/Manifest", + source=resources, + GIT_UNIX_TIMESTAMP=get_git_commit_unix_timestamp(), + ) + fwenv.Replace(FW_RESOURCES_MANIFEST=manifest) + fwenv.Alias("resources", manifest) + # API getter fwenv.Append(FBT_API_VERSION=LazySdkVersionLoader(fwenv.subst("$SDK_DEFINITION"))) fwenv.PhonyTarget( "get_apiversion", diff --git a/scripts/ReadMe.md b/scripts/ReadMe.md index a9feba11b37..359ce472ae6 100644 --- a/scripts/ReadMe.md +++ b/scripts/ReadMe.md @@ -52,10 +52,10 @@ ob.py set # Assets delivery -Run in the root folder of the repo: +Build the firmware and run in the root folder of the repo: ```bash -python scripts/storage.py -p send assets/resources /ext +python scripts/storage.py -p send build/latest/resources /ext ``` diff --git a/scripts/assets.py b/scripts/assets.py index bd8b38ae6d4..1099f0c330d 100755 --- a/scripts/assets.py +++ b/scripts/assets.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import os +import shutil from flipper.app import App from flipper.assets.icon import file2image @@ -220,6 +221,7 @@ def manifest(self): if not os.path.isdir(directory_path): self.logger.error(f'"{directory_path}" is not a directory') exit(255) + manifest_file = os.path.join(directory_path, "Manifest") old_manifest = Manifest() if os.path.exists(manifest_file): @@ -234,13 +236,15 @@ def manifest(self): self.logger.info("Comparing new manifest with existing") only_in_old, changed, only_in_new = Manifest.compare(old_manifest, new_manifest) for record in only_in_old: - self.logger.info(f"Only in old: {record}") + self.logger.debug(f"Only in old: {record}") for record in changed: self.logger.info(f"Changed: {record}") for record in only_in_new: - self.logger.info(f"Only in new: {record}") + self.logger.debug(f"Only in new: {record}") if any((only_in_old, changed, only_in_new)): - self.logger.warning("Manifests are different, updating") + self.logger.info( + f"Manifest updated ({len(only_in_new)} new, {len(only_in_old)} removed, {len(changed)} changed)" + ) new_manifest.save(manifest_file) else: self.logger.info("Manifest is up-to-date!") diff --git a/scripts/fbt/appmanifest.py b/scripts/fbt/appmanifest.py index 1a6cae9b17c..3a3640d4259 100644 --- a/scripts/fbt/appmanifest.py +++ b/scripts/fbt/appmanifest.py @@ -64,6 +64,7 @@ class Library: order: int = 0 sdk_headers: List[str] = field(default_factory=list) targets: List[str] = field(default_factory=lambda: ["all"]) + resources: Optional[str] = None # .fap-specific sources: List[str] = field(default_factory=lambda: ["*.c*"]) @@ -272,11 +273,15 @@ def __init__( self._check_unsatisfied() # unneeded? self._check_target_match() self._group_plugins() - self.apps = sorted( + self._apps = sorted( list(map(self.appmgr.get, self.appnames)), key=lambda app: app.appid, ) + @property + def apps(self): + return list(self._apps) + def _is_missing_dep(self, dep_name: str): return dep_name not in self.appnames @@ -385,13 +390,13 @@ def _group_plugins(self): def get_apps_cdefs(self): cdefs = set() - for app in self.apps: + for app in self._apps: cdefs.update(app.cdefines) return sorted(list(cdefs)) def get_sdk_headers(self): sdk_headers = [] - for app in self.apps: + for app in self._apps: sdk_headers.extend( [ src._appdir.File(header) @@ -405,14 +410,14 @@ def get_apps_of_type(self, apptype: FlipperAppType, all_known: bool = False): return sorted( filter( lambda app: app.apptype == apptype, - self.appmgr.known_apps.values() if all_known else self.apps, + self.appmgr.known_apps.values() if all_known else self._apps, ), key=lambda app: app.order, ) def get_builtin_apps(self): return list( - filter(lambda app: app.apptype in self.BUILTIN_APP_TYPES, self.apps) + filter(lambda app: app.apptype in self.BUILTIN_APP_TYPES, self._apps) ) def get_builtin_app_folders(self): diff --git a/scripts/fbt_tools/fbt_assets.py b/scripts/fbt_tools/fbt_assets.py index d923c328f21..dcf391f2d2a 100644 --- a/scripts/fbt_tools/fbt_assets.py +++ b/scripts/fbt_tools/fbt_assets.py @@ -5,9 +5,10 @@ from SCons.Action import Action from SCons.Builder import Builder from SCons.Errors import StopError +from SCons.Node.FS import File -def icons_emitter(target, source, env): +def _icons_emitter(target, source, env): icons_src = env.GlobRecursive("*.png", env["ICON_SRC_DIR"]) icons_src += env.GlobRecursive("**/frame_rate", env["ICON_SRC_DIR"]) @@ -18,7 +19,7 @@ def icons_emitter(target, source, env): return target, icons_src -def proto_emitter(target, source, env): +def _proto_emitter(target, source, env): target = [] for src in source: basename = os.path.splitext(src.name)[0] @@ -27,7 +28,7 @@ def proto_emitter(target, source, env): return target, source -def dolphin_emitter(target, source, env): +def _dolphin_emitter(target, source, env): res_root_dir = source[0].Dir(env["DOLPHIN_RES_TYPE"]) source = [res_root_dir] source.extend(env.GlobRecursive("*.*", res_root_dir.srcnode())) @@ -38,16 +39,15 @@ def dolphin_emitter(target, source, env): if env["DOLPHIN_RES_TYPE"] == "external": target = [target_base_dir.File("manifest.txt")] ## A detailed list of files to be generated - ## works better if we just leave target the folder - # target = [] - # target.extend( - # map( - # lambda node: target_base_dir.File( - # res_root_dir.rel_path(node).replace(".png", ".bm") - # ), - # filter(lambda node: isinstance(node, SCons.Node.FS.File), source), - # ) - # ) + # Preserve original paths, do .png -> .bm conversion + target.extend( + map( + lambda node: target_base_dir.File( + res_root_dir.rel_path(node).replace(".png", ".bm") + ), + filter(lambda node: isinstance(node, File), source), + ) + ) else: asset_basename = f"assets_dolphin_{env['DOLPHIN_RES_TYPE']}" target = [ @@ -65,7 +65,7 @@ def dolphin_emitter(target, source, env): return target, source -def _invoke_git(args, source_dir): +def __invoke_git(args, source_dir): cmd = ["git"] cmd.extend(args) return ( @@ -75,11 +75,11 @@ def _invoke_git(args, source_dir): ) -def proto_ver_generator(target, source, env): +def _proto_ver_generator(target, source, env): target_file = target[0] src_dir = source[0].dir.abspath try: - _invoke_git( + __invoke_git( ["fetch", "--tags"], source_dir=src_dir, ) @@ -88,7 +88,7 @@ def proto_ver_generator(target, source, env): print(fg.boldred("Git: fetch failed")) try: - git_describe = _invoke_git( + git_describe = __invoke_git( ["describe", "--tags", "--abbrev=0"], source_dir=src_dir, ) @@ -127,7 +127,6 @@ def generate(env): ICONSCOMSTR="\tICONS\t${TARGET}", PROTOCOMSTR="\tPROTO\t${SOURCE}", DOLPHINCOMSTR="\tDOLPHIN\t${DOLPHIN_RES_TYPE}", - RESMANIFESTCOMSTR="\tMANIFEST\t${TARGET}", PBVERCOMSTR="\tPBVER\t${TARGET}", ) @@ -135,37 +134,74 @@ def generate(env): BUILDERS={ "IconBuilder": Builder( action=Action( - '${PYTHON3} ${ASSETS_COMPILER} icons ${ICON_SRC_DIR} ${TARGET.dir} --filename "${ICON_FILE_NAME}"', + [ + [ + "${PYTHON3}", + "${ASSETS_COMPILER}", + "icons", + "${ICON_SRC_DIR}", + "${TARGET.dir}", + "--filename", + "${ICON_FILE_NAME}", + ], + ], "${ICONSCOMSTR}", ), - emitter=icons_emitter, + emitter=_icons_emitter, ), "ProtoBuilder": Builder( action=Action( - "${PYTHON3} ${NANOPB_COMPILER} -q -I${SOURCE.dir.posix} -D${TARGET.dir.posix} ${SOURCES.posix}", + [ + [ + "${PYTHON3}", + "${NANOPB_COMPILER}", + "-q", + "-I${SOURCE.dir.posix}", + "-D${TARGET.dir.posix}", + "${SOURCES.posix}", + ], + ], "${PROTOCOMSTR}", ), - emitter=proto_emitter, + emitter=_proto_emitter, suffix=".pb.c", src_suffix=".proto", ), "DolphinSymBuilder": Builder( action=Action( - "${PYTHON3} ${ASSETS_COMPILER} dolphin -s dolphin_${DOLPHIN_RES_TYPE} ${SOURCE} ${_DOLPHIN_OUT_DIR}", + [ + [ + "${PYTHON3}", + "${ASSETS_COMPILER}", + "dolphin", + "-s", + "dolphin_${DOLPHIN_RES_TYPE}", + "${SOURCE}", + "${_DOLPHIN_OUT_DIR}", + ], + ], "${DOLPHINCOMSTR}", ), - emitter=dolphin_emitter, + emitter=_dolphin_emitter, ), "DolphinExtBuilder": Builder( action=Action( - "${PYTHON3} ${ASSETS_COMPILER} dolphin ${SOURCE} ${_DOLPHIN_OUT_DIR}", + [ + [ + "${PYTHON3}", + "${ASSETS_COMPILER}", + "dolphin", + "${SOURCE}", + "${_DOLPHIN_OUT_DIR}", + ], + ], "${DOLPHINCOMSTR}", ), - emitter=dolphin_emitter, + emitter=_dolphin_emitter, ), "ProtoVerBuilder": Builder( action=Action( - proto_ver_generator, + _proto_ver_generator, "${PBVERCOMSTR}", ), ), diff --git a/scripts/fbt_tools/fbt_dist.py b/scripts/fbt_tools/fbt_dist.py index fdf66c0a719..bf586b8fbba 100644 --- a/scripts/fbt_tools/fbt_dist.py +++ b/scripts/fbt_tools/fbt_dist.py @@ -96,7 +96,21 @@ def DistCommand(env, name, source, **kw): command = env.Command( target, source, - '@${PYTHON3} "${DIST_SCRIPT}" copy -p ${DIST_PROJECTS} -s "${DIST_SUFFIX}" ${DIST_EXTRA}', + action=Action( + [ + [ + "${PYTHON3}", + "${DIST_SCRIPT}", + "copy", + "-p", + "${DIST_PROJECTS}", + "-s", + "${DIST_SUFFIX}", + "${DIST_EXTRA}", + ] + ], + "${DISTCOMSTR}", + ), **kw, ) env.Pseudo(target) @@ -106,7 +120,10 @@ def DistCommand(env, name, source, **kw): def generate(env): if not env["VERBOSE"]: - env.SetDefault(COPROCOMSTR="\tCOPRO\t${TARGET}") + env.SetDefault( + COPROCOMSTR="\tCOPRO\t${TARGET}", + DISTCOMSTR="\tDIST\t${TARGET}", + ) env.AddMethod(AddFwProject) env.AddMethod(DistCommand) env.AddMethod(AddFwFlashTarget) diff --git a/scripts/fbt_tools/fbt_extapps.py b/scripts/fbt_tools/fbt_extapps.py index 963429f2451..94307539a71 100644 --- a/scripts/fbt_tools/fbt_extapps.py +++ b/scripts/fbt_tools/fbt_extapps.py @@ -1,7 +1,5 @@ import itertools -import os import pathlib -import shutil from dataclasses import dataclass, field from typing import Dict, List, Optional @@ -290,7 +288,7 @@ def prepare_app_metadata(target, source, env): ) -def validate_app_imports(target, source, env): +def _validate_app_imports(target, source, env): sdk_cache = SdkCache(env["SDK_DEFINITION"].path, load_version_only=False) app_syms = set() with open(target[0].path, "rt") as f: @@ -342,35 +340,7 @@ def GetExtAppByIdOrPath(env, app_dir): return app_artifacts -def resources_fap_dist_emitter(target, source, env): - # Initially we have a single target - target dir - # Here we inject pairs of (target, source) for each file - resources_root = target[0] - - target = [] - for app_artifacts in env["EXT_APPS"].values(): - for _, dist_path in filter( - lambda dist_entry: dist_entry[0], app_artifacts.dist_entries - ): - source.append(app_artifacts.compact) - target.append(resources_root.File(dist_path)) - - assert len(target) == len(source) - return (target, source) - - -def resources_fap_dist_action(target, source, env): - # FIXME: find a proper way to remove stale files - target_dir = env.Dir("${RESOURCES_ROOT}/apps") - shutil.rmtree(target_dir.path, ignore_errors=True) - - # Iterate over pairs generated in emitter - for src, target in zip(source, target): - os.makedirs(os.path.dirname(target.path), exist_ok=True) - shutil.copy(src.path, target.path) - - -def embed_app_metadata_emitter(target, source, env): +def _embed_app_metadata_emitter(target, source, env): app = env["APP"] # Hack: change extension for fap libs @@ -407,33 +377,52 @@ def generate_embed_app_metadata_actions(source, target, env, for_signature): Action(prepare_app_metadata, "$APPMETA_COMSTR"), ] - objcopy_str = ( - "${OBJCOPY} " - "--remove-section .ARM.attributes " - "--add-section ${_FAP_META_SECTION}=${APP._section_fapmeta} " - ) + objcopy_args = [ + "${OBJCOPY}", + "--remove-section", + ".ARM.attributes", + "--add-section", + "${_FAP_META_SECTION}=${APP._section_fapmeta}", + "--set-section-flags", + "${_FAP_META_SECTION}=contents,noload,readonly,data", + ] if app._section_fapfileassets: actions.append(Action(prepare_app_file_assets, "$APPFILE_COMSTR")) - objcopy_str += ( - "--add-section ${_FAP_FILEASSETS_SECTION}=${APP._section_fapfileassets} " + objcopy_args.extend( + ( + "--add-section", + "${_FAP_FILEASSETS_SECTION}=${APP._section_fapfileassets}", + "--set-section-flags", + "${_FAP_FILEASSETS_SECTION}=contents,noload,readonly,data", + ) ) - objcopy_str += ( - "--set-section-flags ${_FAP_META_SECTION}=contents,noload,readonly,data " - "--strip-debug --strip-unneeded " - "--add-gnu-debuglink=${SOURCE} " - "${SOURCES} ${TARGET}" + objcopy_args.extend( + ( + "--strip-debug", + "--strip-unneeded", + "--add-gnu-debuglink=${SOURCE}", + "${SOURCES}", + "${TARGET}", + ) ) actions.extend( ( Action( - objcopy_str, + [objcopy_args], "$APPMETAEMBED_COMSTR", ), Action( - "${PYTHON3} ${FBT_SCRIPT_DIR}/fastfap.py ${TARGET} ${OBJCOPY}", + [ + [ + "${PYTHON3}", + "${FBT_SCRIPT_DIR}/fastfap.py", + "${TARGET}", + "${OBJCOPY}", + ] + ], "$FASTFAP_COMSTR", ), ) @@ -511,7 +500,6 @@ def generate(env, **kw): ) if not env["VERBOSE"]: env.SetDefault( - FAPDISTCOMSTR="\tFAPDIST\t${TARGET}", APPMETA_COMSTR="\tAPPMETA\t${TARGET}", APPFILE_COMSTR="\tAPPFILE\t${TARGET}", APPMETAEMBED_COMSTR="\tFAP\t${TARGET}", @@ -534,18 +522,11 @@ def generate(env, **kw): env.Append( BUILDERS={ - "FapDist": Builder( - action=Action( - resources_fap_dist_action, - "$FAPDISTCOMSTR", - ), - emitter=resources_fap_dist_emitter, - ), "EmbedAppMetadata": Builder( generator=generate_embed_app_metadata_actions, suffix=".fap", src_suffix=".elf", - emitter=embed_app_metadata_emitter, + emitter=_embed_app_metadata_emitter, ), "ValidateAppImports": Builder( action=[ @@ -554,7 +535,7 @@ def generate(env, **kw): None, # "$APPDUMP_COMSTR", ), Action( - validate_app_imports, + _validate_app_imports, "$APPCHECK_COMSTR", ), ], diff --git a/scripts/fbt_tools/fbt_hwtarget.py b/scripts/fbt_tools/fbt_hwtarget.py index 1831a6984da..67975ed0f8f 100644 --- a/scripts/fbt_tools/fbt_hwtarget.py +++ b/scripts/fbt_tools/fbt_hwtarget.py @@ -2,9 +2,9 @@ class HardwareTargetLoader: - def __init__(self, env, target_scons_dir, target_id): + def __init__(self, env, root_target_scons_dir, target_id): self.env = env - self.target_scons_dir = target_scons_dir + self.all_targets_root_dir = root_target_scons_dir self.target_dir = self._getTargetDir(target_id) # self.target_id = target_id self.layered_target_dirs = [] @@ -23,7 +23,7 @@ def __init__(self, env, target_scons_dir, target_id): self._processTargetDefinitions(target_id) def _getTargetDir(self, target_id): - return self.target_scons_dir.Dir(f"f{target_id}") + return self.all_targets_root_dir.Dir(f"f{target_id}") def _loadDescription(self, target_id): target_json_file = self._getTargetDir(target_id).File("target.json") @@ -34,14 +34,14 @@ def _loadDescription(self, target_id): return vals def _processTargetDefinitions(self, target_id): - self.layered_target_dirs.append(f"targets/f{target_id}") + target_dir = self._getTargetDir(target_id) + self.layered_target_dirs.append(target_dir) config = self._loadDescription(target_id) for path_list in ("include_paths", "sdk_header_paths"): getattr(self, path_list).extend( - f"#/firmware/targets/f{target_id}/{p}" - for p in config.get(path_list, []) + target_dir.Dir(p) for p in config.get(path_list, []) ) self.excluded_sources.extend(config.get("excluded_sources", [])) @@ -50,7 +50,7 @@ def _processTargetDefinitions(self, target_id): file_attrs = ( # (name, use_src_node) - ("startup_script", False), + ("startup_script", True), ("linker_script_flash", True), ("linker_script_ram", True), ("linker_script_app", True), @@ -59,9 +59,10 @@ def _processTargetDefinitions(self, target_id): for attr_name, use_src_node in file_attrs: if (val := config.get(attr_name)) and not getattr(self, attr_name): - node = self.env.File(f"firmware/targets/f{target_id}/{val}") + node = target_dir.File(val) if use_src_node: node = node.srcnode() + # print(f"Got node {node}, {node.path} for {attr_name}") setattr(self, attr_name, node) for attr_name in ("linker_dependencies",): @@ -84,8 +85,8 @@ def gatherSources(self): ) seen_filenames.update(f.name for f in accepted_sources) sources.extend(accepted_sources) - # print(f"Found {len(sources)} sources: {list(f.name for f in sources)}") - return sources + # print(f"Found {len(sources)} sources: {list(f.path for f in sources)}") + return list(f.get_path(self.all_targets_root_dir) for f in sources) def gatherSdkHeaders(self): sdk_headers = [] @@ -101,7 +102,7 @@ def gatherSdkHeaders(self): def ConfigureForTarget(env, target_id): - target_loader = HardwareTargetLoader(env, env.Dir("#/firmware/targets"), target_id) + target_loader = HardwareTargetLoader(env, env["TARGETS_ROOT"], target_id) env.Replace( TARGET_CFG=target_loader, SDK_DEFINITION=target_loader.sdk_symbols, diff --git a/scripts/fbt_tools/fbt_resources.py b/scripts/fbt_tools/fbt_resources.py new file mode 100644 index 00000000000..47c624081f8 --- /dev/null +++ b/scripts/fbt_tools/fbt_resources.py @@ -0,0 +1,98 @@ +import os +import shutil + +from SCons.Action import Action +from SCons.Builder import Builder +from SCons.Errors import StopError +from SCons.Node.FS import Dir, File + + +def _resources_dist_emitter(target, source, env): + resources_root = env.Dir(env["RESOURCES_ROOT"]) + + target = [] + for app_artifacts in env["FW_EXTAPPS"].application_map.values(): + for _, dist_path in filter( + lambda dist_entry: dist_entry[0], app_artifacts.dist_entries + ): + source.append(app_artifacts.compact) + target.append(resources_root.File(dist_path)) + + # Deploy apps' resources too + for app in env["APPBUILD"].apps: + if not app.resources: + continue + apps_resource_dir = app._appdir.Dir(app.resources) + for res_file in env.GlobRecursive("*", apps_resource_dir): + if not isinstance(res_file, File): + continue + source.append(res_file) + target.append(resources_root.File(res_file.get_path(apps_resource_dir))) + + # Deploy other stuff from _EXTRA_DIST + for extra_dist in env["_EXTRA_DIST"]: + if isinstance(extra_dist, Dir): + for extra_file in env.GlobRecursive("*", extra_dist): + if not isinstance(extra_file, File): + continue + source.append(extra_file) + target.append( + # Preserve dir name from original node + resources_root.Dir(extra_dist.name).File( + extra_file.get_path(extra_dist) + ) + ) + else: + raise StopError(f"Unsupported extra dist type: {type(extra_dist)}") + + assert len(target) == len(source) + return (target, source) + + +def _resources_dist_action(target, source, env): + shutil.rmtree(env.Dir(env["RESOURCES_ROOT"]).abspath, ignore_errors=True) + for src, target in zip(source, target): + os.makedirs(os.path.dirname(target.path), exist_ok=True) + shutil.copy(src.path, target.path) + + +def generate(env, **kw): + env.SetDefault( + ASSETS_COMPILER="${FBT_SCRIPT_DIR}/assets.py", + ) + + if not env["VERBOSE"]: + env.SetDefault( + RESOURCEDISTCOMSTR="\tRESDIST\t${RESOURCES_ROOT}", + RESMANIFESTCOMSTR="\tMANIFST\t${TARGET}", + ) + + env.Append( + BUILDERS={ + "ResourcesDist": Builder( + action=Action( + _resources_dist_action, + "${RESOURCEDISTCOMSTR}", + ), + emitter=_resources_dist_emitter, + ), + "ManifestBuilder": Builder( + action=Action( + [ + [ + "${PYTHON3}", + "${ASSETS_COMPILER}", + "manifest", + "${TARGET.dir.posix}", + "--timestamp=${GIT_UNIX_TIMESTAMP}", + ] + ], + "${RESMANIFESTCOMSTR}", + ) + ), + } + ) + + +def exists(env): + return True diff --git a/scripts/fbt_tools/fbt_sdk.py b/scripts/fbt_tools/fbt_sdk.py index 2f7d62388df..6350f14b8b1 100644 --- a/scripts/fbt_tools/fbt_sdk.py +++ b/scripts/fbt_tools/fbt_sdk.py @@ -37,13 +37,13 @@ def ProcessSdkDepends(env, filename): return depends -def api_amalgam_emitter(target, source, env): +def _api_amalgam_emitter(target, source, env): target.append(env.ChangeFileExtension(target[0], ".d")) target.append(env.ChangeFileExtension(target[0], ".i.c")) return target, source -def api_amalgam_gen_origin_header(target, source, env): +def _api_amalgam_gen_origin_header(target, source, env): mega_file = env.subst("${TARGET}.c", target=target[0]) with open(mega_file, "wt") as sdk_c: sdk_c.write( @@ -183,12 +183,12 @@ def deploy_action(self): self._generate_sdk_meta() -def deploy_sdk_header_tree_action(target, source, env): +def _deploy_sdk_header_tree_action(target, source, env): sdk_tree = SdkTreeBuilder(env, target, source) return sdk_tree.deploy_action() -def deploy_sdk_header_tree_emitter(target, source, env): +def _deploy_sdk_header_tree_emitter(target, source, env): sdk_tree = SdkTreeBuilder(env, target, source) return sdk_tree.emitter(target, source, env) @@ -227,7 +227,7 @@ def _check_sdk_is_up2date(sdk_cache: SdkCache): ) -def validate_api_cache(source, target, env): +def _validate_api_cache(source, target, env): # print(f"Generating SDK for {source[0]} to {target[0]}") current_sdk = SdkCollector() current_sdk.process_source_file_for_sdk(source[0].path) @@ -240,7 +240,7 @@ def validate_api_cache(source, target, env): _check_sdk_is_up2date(sdk_cache) -def generate_api_table(source, target, env): +def _generate_api_table(source, target, env): sdk_cache = SdkCache(source[0].path) _check_sdk_is_up2date(sdk_cache) @@ -278,10 +278,10 @@ def generate(env, **kw): env.Append( BUILDERS={ "ApiAmalgamator": Builder( - emitter=api_amalgam_emitter, + emitter=_api_amalgam_emitter, action=[ Action( - api_amalgam_gen_origin_header, + _api_amalgam_gen_origin_header, "$SDK_AMALGAMATE_HEADER_COMSTR", ), Action( @@ -293,15 +293,15 @@ def generate(env, **kw): ), "SDKHeaderTreeExtractor": Builder( action=Action( - deploy_sdk_header_tree_action, + _deploy_sdk_header_tree_action, "$SDKTREE_COMSTR", ), - emitter=deploy_sdk_header_tree_emitter, + emitter=_deploy_sdk_header_tree_emitter, src_suffix=".d", ), "ApiTableValidator": Builder( action=Action( - validate_api_cache, + _validate_api_cache, "$SDKSYM_UPDATER_COMSTR", ), suffix=".csv", @@ -309,7 +309,7 @@ def generate(env, **kw): ), "ApiSymbolTable": Builder( action=Action( - generate_api_table, + _generate_api_table, "$APITABLE_GENERATOR_COMSTR", ), suffix=".h", diff --git a/scripts/fbt_tools/fbt_version.py b/scripts/fbt_tools/fbt_version.py index aead13b29fa..f1a782523ff 100644 --- a/scripts/fbt_tools/fbt_version.py +++ b/scripts/fbt_tools/fbt_version.py @@ -2,7 +2,7 @@ from SCons.Builder import Builder -def version_emitter(target, source, env): +def _version_emitter(target, source, env): target_dir = target[0] target = [ target_dir.File("version.inc.h"), @@ -24,7 +24,7 @@ def generate(env): '-o ${TARGET.dir.posix} --dir "${ROOT_DIR}"', "${VERSIONCOMSTR}", ), - emitter=version_emitter, + emitter=_version_emitter, ), } ) diff --git a/scripts/fbt_tools/pvsstudio.py b/scripts/fbt_tools/pvsstudio.py index 211f46aee8e..f43db126e31 100644 --- a/scripts/fbt_tools/pvsstudio.py +++ b/scripts/fbt_tools/pvsstudio.py @@ -17,7 +17,7 @@ def _set_browser_action(target, source, env): __no_browser = True -def emit_pvsreport(target, source, env): +def _emit_pvsreport(target, source, env): target_dir = env["REPORT_DIR"] if env["PLATFORM"] == "win32": # Report generator on Windows emits to a subfolder of given output folder @@ -96,7 +96,7 @@ def generate(env): ], "${PVSCONVCOMSTR}", ), - emitter=emit_pvsreport, + emitter=_emit_pvsreport, src_suffix=".log", ), } diff --git a/scripts/flipper/assets/dolphin.py b/scripts/flipper/assets/dolphin.py index e9089a1b991..cf98c8253c8 100644 --- a/scripts/flipper/assets/dolphin.py +++ b/scripts/flipper/assets/dolphin.py @@ -55,7 +55,7 @@ def load(self, animation_directory: str): if not os.path.isfile(meta_filename): raise Exception(f"Animation meta file doesn't exist: { meta_filename }") - self.logger.info(f"Loading meta from {meta_filename}") + self.logger.debug(f"Loading meta from {meta_filename}") file = FlipperFormatFile() file.load(meta_filename) diff --git a/scripts/meta.py b/scripts/meta.py deleted file mode 100755 index f47ef65fb0e..00000000000 --- a/scripts/meta.py +++ /dev/null @@ -1,60 +0,0 @@ -#!/usr/bin/env python3 - -import json - -from flipper.app import App - - -class Main(App): - def init(self): - self.subparsers = self.parser.add_subparsers(help="sub-command help") - - # generate - self.parser_generate = self.subparsers.add_parser( - "generate", help="Generate JSON meta file" - ) - self.parser_generate.add_argument("-p", dest="project", required=True) - self.parser_generate.add_argument( - "-DBUILD_DATE", dest="build_date", required=True - ) - self.parser_generate.add_argument("-DGIT_COMMIT", dest="commit", required=True) - self.parser_generate.add_argument("-DGIT_BRANCH", dest="branch", required=True) - self.parser_generate.add_argument( - "-DTARGET", dest="target", type=int, required=True - ) - self.parser_generate.set_defaults(func=self.generate) - - # merge - self.parser_merge = self.subparsers.add_parser( - "merge", help="Merge JSON meta files" - ) - self.parser_merge.add_argument( - "-i", dest="input", action="append", nargs="+", required=True - ) - self.parser_merge.set_defaults(func=self.merge) - - def generate(self): - meta = {} - for k, v in vars(self.args).items(): - if k in ["project", "func", "debug"]: - continue - if isinstance(v, str): - v = v.strip('"') - meta[self.args.project + "_" + k] = v - - print(json.dumps(meta, indent=4)) - return 0 - - def merge(self): - full = {} - for path in self.args.input[0]: - with open(path, mode="r") as file: - dict = json.loads(file.read()) - full.update(dict) - - print(json.dumps(full, indent=4)) - return 0 - - -if __name__ == "__main__": - Main()() diff --git a/scripts/ufbt/project_template/app_template/.github/workflows/build.yml b/scripts/ufbt/project_template/app_template/.github/workflows/build.yml index c11ffc180fd..143847c4a2e 100644 --- a/scripts/ufbt/project_template/app_template/.github/workflows/build.yml +++ b/scripts/ufbt/project_template/app_template/.github/workflows/build.yml @@ -27,9 +27,9 @@ jobs: name: 'ufbt: Build for ${{ matrix.name }}' steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Build with ufbt - uses: flipperdevices/flipperzero-ufbt-action@v0.1.1 + uses: flipperdevices/flipperzero-ufbt-action@v0.1 id: build-app with: sdk-channel: ${{ matrix.sdk-channel }} diff --git a/site_scons/extapps.scons b/site_scons/extapps.scons index f9227ed37b2..769b3eb1540 100644 --- a/site_scons/extapps.scons +++ b/site_scons/extapps.scons @@ -14,7 +14,6 @@ appenv = ENV["APPENV"] = ENV.Clone( "fbt_assets", "fbt_sdk", ], - RESOURCES_ROOT=ENV.Dir("#/assets/resources"), ) appenv.Replace( @@ -56,7 +55,6 @@ appenv.AppendUnique( @dataclass class FlipperExtAppBuildArtifacts: application_map: dict = field(default_factory=dict) - resources_dist: NodeList = field(default_factory=NodeList) sdk_tree: NodeList = field(default_factory=NodeList) @@ -78,8 +76,6 @@ Alias( list(app_artifact.validator for app_artifact in extapps.application_map.values()), ) -extapps.resources_dist = appenv.FapDist(appenv["RESOURCES_ROOT"], []) - if appsrc := appenv.subst("$APPSRC"): launch_target = appenv.AddAppLaunchTarget(appsrc, "launch") diff --git a/firmware/ReadMe.md b/targets/ReadMe.md similarity index 100% rename from firmware/ReadMe.md rename to targets/ReadMe.md diff --git a/firmware/SConscript b/targets/SConscript similarity index 100% rename from firmware/SConscript rename to targets/SConscript diff --git a/firmware/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv similarity index 98% rename from firmware/targets/f18/api_symbols.csv rename to targets/f18/api_symbols.csv index 4789d316d62..b4efd7911b5 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -36,51 +36,6 @@ Header,+,applications/services/notification/notification_messages.h,, Header,+,applications/services/power/power_service/power.h,, Header,+,applications/services/rpc/rpc_app.h,, Header,+,applications/services/storage/storage.h,, -Header,+,firmware/targets/f18/furi_hal/furi_hal_resources.h,, -Header,+,firmware/targets/f18/furi_hal/furi_hal_spi_config.h,, -Header,+,firmware/targets/f18/furi_hal/furi_hal_target_hw.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_bus.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_clock.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_console.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_dma.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_flash.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_gpio.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_i2c_config.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_i2c_types.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_idle_timer.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_interrupt.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_os.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_pwm.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_spi_types.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_uart.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_usb_cdc.h,, -Header,+,firmware/targets/f7/platform_specific/intrinsic_export.h,, -Header,+,firmware/targets/f7/platform_specific/math_wrapper.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_bt.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_bt_hid.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_bt_serial.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_cortex.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_crypto.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_debug.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_i2c.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_info.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_light.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_memory.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_mpu.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_power.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_random.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_region.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_rtc.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_sd.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_speaker.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_spi.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_usb.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_usb_ccid.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_usb_hid.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_usb_hid_u2f.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_version.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_vibro.h,, Header,+,lib/digital_signal/digital_sequence.h,, Header,+,lib/digital_signal/digital_signal.h,, Header,+,lib/drivers/cc1101_regs.h,, @@ -194,6 +149,51 @@ Header,+,lib/toolbox/stream/string_stream.h,, Header,+,lib/toolbox/tar/tar_archive.h,, Header,+,lib/toolbox/value_index.h,, Header,+,lib/toolbox/version.h,, +Header,+,targets/f18/furi_hal/furi_hal_resources.h,, +Header,+,targets/f18/furi_hal/furi_hal_spi_config.h,, +Header,+,targets/f18/furi_hal/furi_hal_target_hw.h,, +Header,+,targets/f7/furi_hal/furi_hal_bus.h,, +Header,+,targets/f7/furi_hal/furi_hal_clock.h,, +Header,+,targets/f7/furi_hal/furi_hal_console.h,, +Header,+,targets/f7/furi_hal/furi_hal_dma.h,, +Header,+,targets/f7/furi_hal/furi_hal_flash.h,, +Header,+,targets/f7/furi_hal/furi_hal_gpio.h,, +Header,+,targets/f7/furi_hal/furi_hal_i2c_config.h,, +Header,+,targets/f7/furi_hal/furi_hal_i2c_types.h,, +Header,+,targets/f7/furi_hal/furi_hal_idle_timer.h,, +Header,+,targets/f7/furi_hal/furi_hal_interrupt.h,, +Header,+,targets/f7/furi_hal/furi_hal_os.h,, +Header,+,targets/f7/furi_hal/furi_hal_pwm.h,, +Header,+,targets/f7/furi_hal/furi_hal_spi_types.h,, +Header,+,targets/f7/furi_hal/furi_hal_uart.h,, +Header,+,targets/f7/furi_hal/furi_hal_usb_cdc.h,, +Header,+,targets/f7/platform_specific/intrinsic_export.h,, +Header,+,targets/f7/platform_specific/math_wrapper.h,, +Header,+,targets/furi_hal_include/furi_hal.h,, +Header,+,targets/furi_hal_include/furi_hal_bt.h,, +Header,+,targets/furi_hal_include/furi_hal_bt_hid.h,, +Header,+,targets/furi_hal_include/furi_hal_bt_serial.h,, +Header,+,targets/furi_hal_include/furi_hal_cortex.h,, +Header,+,targets/furi_hal_include/furi_hal_crypto.h,, +Header,+,targets/furi_hal_include/furi_hal_debug.h,, +Header,+,targets/furi_hal_include/furi_hal_i2c.h,, +Header,+,targets/furi_hal_include/furi_hal_info.h,, +Header,+,targets/furi_hal_include/furi_hal_light.h,, +Header,+,targets/furi_hal_include/furi_hal_memory.h,, +Header,+,targets/furi_hal_include/furi_hal_mpu.h,, +Header,+,targets/furi_hal_include/furi_hal_power.h,, +Header,+,targets/furi_hal_include/furi_hal_random.h,, +Header,+,targets/furi_hal_include/furi_hal_region.h,, +Header,+,targets/furi_hal_include/furi_hal_rtc.h,, +Header,+,targets/furi_hal_include/furi_hal_sd.h,, +Header,+,targets/furi_hal_include/furi_hal_speaker.h,, +Header,+,targets/furi_hal_include/furi_hal_spi.h,, +Header,+,targets/furi_hal_include/furi_hal_usb.h,, +Header,+,targets/furi_hal_include/furi_hal_usb_ccid.h,, +Header,+,targets/furi_hal_include/furi_hal_usb_hid.h,, +Header,+,targets/furi_hal_include/furi_hal_usb_hid_u2f.h,, +Header,+,targets/furi_hal_include/furi_hal_version.h,, +Header,+,targets/furi_hal_include/furi_hal_vibro.h,, Function,-,LL_ADC_CommonDeInit,ErrorStatus,ADC_Common_TypeDef* Function,-,LL_ADC_CommonInit,ErrorStatus,"ADC_Common_TypeDef*, const LL_ADC_CommonInitTypeDef*" Function,-,LL_ADC_CommonStructInit,void,LL_ADC_CommonInitTypeDef* diff --git a/firmware/targets/f18/furi_hal/furi_hal.c b/targets/f18/furi_hal/furi_hal.c similarity index 100% rename from firmware/targets/f18/furi_hal/furi_hal.c rename to targets/f18/furi_hal/furi_hal.c diff --git a/firmware/targets/f18/furi_hal/furi_hal_power_config.c b/targets/f18/furi_hal/furi_hal_power_config.c similarity index 100% rename from firmware/targets/f18/furi_hal/furi_hal_power_config.c rename to targets/f18/furi_hal/furi_hal_power_config.c diff --git a/firmware/targets/f18/furi_hal/furi_hal_resources.c b/targets/f18/furi_hal/furi_hal_resources.c similarity index 100% rename from firmware/targets/f18/furi_hal/furi_hal_resources.c rename to targets/f18/furi_hal/furi_hal_resources.c diff --git a/firmware/targets/f18/furi_hal/furi_hal_resources.h b/targets/f18/furi_hal/furi_hal_resources.h similarity index 100% rename from firmware/targets/f18/furi_hal/furi_hal_resources.h rename to targets/f18/furi_hal/furi_hal_resources.h diff --git a/firmware/targets/f18/furi_hal/furi_hal_spi_config.c b/targets/f18/furi_hal/furi_hal_spi_config.c similarity index 100% rename from firmware/targets/f18/furi_hal/furi_hal_spi_config.c rename to targets/f18/furi_hal/furi_hal_spi_config.c diff --git a/firmware/targets/f18/furi_hal/furi_hal_spi_config.h b/targets/f18/furi_hal/furi_hal_spi_config.h similarity index 100% rename from firmware/targets/f18/furi_hal/furi_hal_spi_config.h rename to targets/f18/furi_hal/furi_hal_spi_config.h diff --git a/firmware/targets/f18/furi_hal/furi_hal_target_hw.h b/targets/f18/furi_hal/furi_hal_target_hw.h similarity index 100% rename from firmware/targets/f18/furi_hal/furi_hal_target_hw.h rename to targets/f18/furi_hal/furi_hal_target_hw.h diff --git a/firmware/targets/f18/furi_hal/furi_hal_version_device.c b/targets/f18/furi_hal/furi_hal_version_device.c similarity index 100% rename from firmware/targets/f18/furi_hal/furi_hal_version_device.c rename to targets/f18/furi_hal/furi_hal_version_device.c diff --git a/firmware/targets/f18/target.json b/targets/f18/target.json similarity index 100% rename from firmware/targets/f18/target.json rename to targets/f18/target.json diff --git a/firmware/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv similarity index 98% rename from firmware/targets/f7/api_symbols.csv rename to targets/f7/api_symbols.csv index 038a22eae43..7599230d722 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -37,56 +37,6 @@ Header,+,applications/services/notification/notification_messages.h,, Header,+,applications/services/power/power_service/power.h,, Header,+,applications/services/rpc/rpc_app.h,, Header,+,applications/services/storage/storage.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_bus.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_clock.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_console.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_dma.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_flash.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_gpio.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_i2c_config.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_i2c_types.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_ibutton.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_idle_timer.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_interrupt.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_os.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_pwm.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_resources.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_rfid.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_spi_config.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_spi_types.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_subghz.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_target_hw.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_uart.h,, -Header,+,firmware/targets/f7/furi_hal/furi_hal_usb_cdc.h,, -Header,+,firmware/targets/f7/platform_specific/intrinsic_export.h,, -Header,+,firmware/targets/f7/platform_specific/math_wrapper.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_bt.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_bt_hid.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_bt_serial.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_cortex.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_crypto.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_debug.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_i2c.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_info.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_infrared.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_light.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_memory.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_mpu.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_nfc.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_power.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_random.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_region.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_rtc.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_sd.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_speaker.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_spi.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_usb.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_usb_ccid.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_usb_hid.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_usb_hid_u2f.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_version.h,, -Header,+,firmware/targets/furi_hal_include/furi_hal_vibro.h,, Header,+,lib/digital_signal/digital_sequence.h,, Header,+,lib/digital_signal/digital_signal.h,, Header,+,lib/drivers/cc1101_regs.h,, @@ -262,6 +212,56 @@ Header,+,lib/toolbox/stream/string_stream.h,, Header,+,lib/toolbox/tar/tar_archive.h,, Header,+,lib/toolbox/value_index.h,, Header,+,lib/toolbox/version.h,, +Header,+,targets/f7/furi_hal/furi_hal_bus.h,, +Header,+,targets/f7/furi_hal/furi_hal_clock.h,, +Header,+,targets/f7/furi_hal/furi_hal_console.h,, +Header,+,targets/f7/furi_hal/furi_hal_dma.h,, +Header,+,targets/f7/furi_hal/furi_hal_flash.h,, +Header,+,targets/f7/furi_hal/furi_hal_gpio.h,, +Header,+,targets/f7/furi_hal/furi_hal_i2c_config.h,, +Header,+,targets/f7/furi_hal/furi_hal_i2c_types.h,, +Header,+,targets/f7/furi_hal/furi_hal_ibutton.h,, +Header,+,targets/f7/furi_hal/furi_hal_idle_timer.h,, +Header,+,targets/f7/furi_hal/furi_hal_interrupt.h,, +Header,+,targets/f7/furi_hal/furi_hal_os.h,, +Header,+,targets/f7/furi_hal/furi_hal_pwm.h,, +Header,+,targets/f7/furi_hal/furi_hal_resources.h,, +Header,+,targets/f7/furi_hal/furi_hal_rfid.h,, +Header,+,targets/f7/furi_hal/furi_hal_spi_config.h,, +Header,+,targets/f7/furi_hal/furi_hal_spi_types.h,, +Header,+,targets/f7/furi_hal/furi_hal_subghz.h,, +Header,+,targets/f7/furi_hal/furi_hal_target_hw.h,, +Header,+,targets/f7/furi_hal/furi_hal_uart.h,, +Header,+,targets/f7/furi_hal/furi_hal_usb_cdc.h,, +Header,+,targets/f7/platform_specific/intrinsic_export.h,, +Header,+,targets/f7/platform_specific/math_wrapper.h,, +Header,+,targets/furi_hal_include/furi_hal.h,, +Header,+,targets/furi_hal_include/furi_hal_bt.h,, +Header,+,targets/furi_hal_include/furi_hal_bt_hid.h,, +Header,+,targets/furi_hal_include/furi_hal_bt_serial.h,, +Header,+,targets/furi_hal_include/furi_hal_cortex.h,, +Header,+,targets/furi_hal_include/furi_hal_crypto.h,, +Header,+,targets/furi_hal_include/furi_hal_debug.h,, +Header,+,targets/furi_hal_include/furi_hal_i2c.h,, +Header,+,targets/furi_hal_include/furi_hal_info.h,, +Header,+,targets/furi_hal_include/furi_hal_infrared.h,, +Header,+,targets/furi_hal_include/furi_hal_light.h,, +Header,+,targets/furi_hal_include/furi_hal_memory.h,, +Header,+,targets/furi_hal_include/furi_hal_mpu.h,, +Header,+,targets/furi_hal_include/furi_hal_nfc.h,, +Header,+,targets/furi_hal_include/furi_hal_power.h,, +Header,+,targets/furi_hal_include/furi_hal_random.h,, +Header,+,targets/furi_hal_include/furi_hal_region.h,, +Header,+,targets/furi_hal_include/furi_hal_rtc.h,, +Header,+,targets/furi_hal_include/furi_hal_sd.h,, +Header,+,targets/furi_hal_include/furi_hal_speaker.h,, +Header,+,targets/furi_hal_include/furi_hal_spi.h,, +Header,+,targets/furi_hal_include/furi_hal_usb.h,, +Header,+,targets/furi_hal_include/furi_hal_usb_ccid.h,, +Header,+,targets/furi_hal_include/furi_hal_usb_hid.h,, +Header,+,targets/furi_hal_include/furi_hal_usb_hid_u2f.h,, +Header,+,targets/furi_hal_include/furi_hal_version.h,, +Header,+,targets/furi_hal_include/furi_hal_vibro.h,, Function,-,LL_ADC_CommonDeInit,ErrorStatus,ADC_Common_TypeDef* Function,-,LL_ADC_CommonInit,ErrorStatus,"ADC_Common_TypeDef*, const LL_ADC_CommonInitTypeDef*" Function,-,LL_ADC_CommonStructInit,void,LL_ADC_CommonInitTypeDef* diff --git a/firmware/targets/f7/application_ext.ld b/targets/f7/application_ext.ld similarity index 100% rename from firmware/targets/f7/application_ext.ld rename to targets/f7/application_ext.ld diff --git a/firmware/targets/f7/ble_glue/app_common.h b/targets/f7/ble_glue/app_common.h similarity index 100% rename from firmware/targets/f7/ble_glue/app_common.h rename to targets/f7/ble_glue/app_common.h diff --git a/firmware/targets/f7/ble_glue/app_conf.h b/targets/f7/ble_glue/app_conf.h similarity index 100% rename from firmware/targets/f7/ble_glue/app_conf.h rename to targets/f7/ble_glue/app_conf.h diff --git a/firmware/targets/f7/ble_glue/app_debug.c b/targets/f7/ble_glue/app_debug.c similarity index 100% rename from firmware/targets/f7/ble_glue/app_debug.c rename to targets/f7/ble_glue/app_debug.c diff --git a/firmware/targets/f7/ble_glue/app_debug.h b/targets/f7/ble_glue/app_debug.h similarity index 100% rename from firmware/targets/f7/ble_glue/app_debug.h rename to targets/f7/ble_glue/app_debug.h diff --git a/firmware/targets/f7/ble_glue/ble_app.c b/targets/f7/ble_glue/ble_app.c similarity index 100% rename from firmware/targets/f7/ble_glue/ble_app.c rename to targets/f7/ble_glue/ble_app.c diff --git a/firmware/targets/f7/ble_glue/ble_app.h b/targets/f7/ble_glue/ble_app.h similarity index 100% rename from firmware/targets/f7/ble_glue/ble_app.h rename to targets/f7/ble_glue/ble_app.h diff --git a/firmware/targets/f7/ble_glue/ble_conf.h b/targets/f7/ble_glue/ble_conf.h similarity index 100% rename from firmware/targets/f7/ble_glue/ble_conf.h rename to targets/f7/ble_glue/ble_conf.h diff --git a/firmware/targets/f7/ble_glue/ble_const.h b/targets/f7/ble_glue/ble_const.h similarity index 100% rename from firmware/targets/f7/ble_glue/ble_const.h rename to targets/f7/ble_glue/ble_const.h diff --git a/firmware/targets/f7/ble_glue/ble_dbg_conf.h b/targets/f7/ble_glue/ble_dbg_conf.h similarity index 100% rename from firmware/targets/f7/ble_glue/ble_dbg_conf.h rename to targets/f7/ble_glue/ble_dbg_conf.h diff --git a/firmware/targets/f7/ble_glue/ble_glue.c b/targets/f7/ble_glue/ble_glue.c similarity index 100% rename from firmware/targets/f7/ble_glue/ble_glue.c rename to targets/f7/ble_glue/ble_glue.c diff --git a/firmware/targets/f7/ble_glue/ble_glue.h b/targets/f7/ble_glue/ble_glue.h similarity index 100% rename from firmware/targets/f7/ble_glue/ble_glue.h rename to targets/f7/ble_glue/ble_glue.h diff --git a/firmware/targets/f7/ble_glue/compiler.h b/targets/f7/ble_glue/compiler.h similarity index 100% rename from firmware/targets/f7/ble_glue/compiler.h rename to targets/f7/ble_glue/compiler.h diff --git a/firmware/targets/f7/ble_glue/gap.c b/targets/f7/ble_glue/gap.c similarity index 100% rename from firmware/targets/f7/ble_glue/gap.c rename to targets/f7/ble_glue/gap.c diff --git a/firmware/targets/f7/ble_glue/gap.h b/targets/f7/ble_glue/gap.h similarity index 100% rename from firmware/targets/f7/ble_glue/gap.h rename to targets/f7/ble_glue/gap.h diff --git a/firmware/targets/f7/ble_glue/hsem_map.h b/targets/f7/ble_glue/hsem_map.h similarity index 100% rename from firmware/targets/f7/ble_glue/hsem_map.h rename to targets/f7/ble_glue/hsem_map.h diff --git a/firmware/targets/f7/ble_glue/hw_ipcc.c b/targets/f7/ble_glue/hw_ipcc.c similarity index 100% rename from firmware/targets/f7/ble_glue/hw_ipcc.c rename to targets/f7/ble_glue/hw_ipcc.c diff --git a/firmware/targets/f7/ble_glue/osal.h b/targets/f7/ble_glue/osal.h similarity index 100% rename from firmware/targets/f7/ble_glue/osal.h rename to targets/f7/ble_glue/osal.h diff --git a/firmware/targets/f7/ble_glue/services/battery_service.c b/targets/f7/ble_glue/services/battery_service.c similarity index 100% rename from firmware/targets/f7/ble_glue/services/battery_service.c rename to targets/f7/ble_glue/services/battery_service.c diff --git a/firmware/targets/f7/ble_glue/services/battery_service.h b/targets/f7/ble_glue/services/battery_service.h similarity index 100% rename from firmware/targets/f7/ble_glue/services/battery_service.h rename to targets/f7/ble_glue/services/battery_service.h diff --git a/firmware/targets/f7/ble_glue/services/dev_info_service.c b/targets/f7/ble_glue/services/dev_info_service.c similarity index 100% rename from firmware/targets/f7/ble_glue/services/dev_info_service.c rename to targets/f7/ble_glue/services/dev_info_service.c diff --git a/firmware/targets/f7/ble_glue/services/dev_info_service.h b/targets/f7/ble_glue/services/dev_info_service.h similarity index 100% rename from firmware/targets/f7/ble_glue/services/dev_info_service.h rename to targets/f7/ble_glue/services/dev_info_service.h diff --git a/firmware/targets/f7/ble_glue/services/dev_info_service_uuid.inc b/targets/f7/ble_glue/services/dev_info_service_uuid.inc similarity index 100% rename from firmware/targets/f7/ble_glue/services/dev_info_service_uuid.inc rename to targets/f7/ble_glue/services/dev_info_service_uuid.inc diff --git a/firmware/targets/f7/ble_glue/services/gatt_char.c b/targets/f7/ble_glue/services/gatt_char.c similarity index 100% rename from firmware/targets/f7/ble_glue/services/gatt_char.c rename to targets/f7/ble_glue/services/gatt_char.c diff --git a/firmware/targets/f7/ble_glue/services/gatt_char.h b/targets/f7/ble_glue/services/gatt_char.h similarity index 100% rename from firmware/targets/f7/ble_glue/services/gatt_char.h rename to targets/f7/ble_glue/services/gatt_char.h diff --git a/firmware/targets/f7/ble_glue/services/hid_service.c b/targets/f7/ble_glue/services/hid_service.c similarity index 100% rename from firmware/targets/f7/ble_glue/services/hid_service.c rename to targets/f7/ble_glue/services/hid_service.c diff --git a/firmware/targets/f7/ble_glue/services/hid_service.h b/targets/f7/ble_glue/services/hid_service.h similarity index 100% rename from firmware/targets/f7/ble_glue/services/hid_service.h rename to targets/f7/ble_glue/services/hid_service.h diff --git a/firmware/targets/f7/ble_glue/services/serial_service.c b/targets/f7/ble_glue/services/serial_service.c similarity index 100% rename from firmware/targets/f7/ble_glue/services/serial_service.c rename to targets/f7/ble_glue/services/serial_service.c diff --git a/firmware/targets/f7/ble_glue/services/serial_service.h b/targets/f7/ble_glue/services/serial_service.h similarity index 100% rename from firmware/targets/f7/ble_glue/services/serial_service.h rename to targets/f7/ble_glue/services/serial_service.h diff --git a/firmware/targets/f7/ble_glue/services/serial_service_uuid.inc b/targets/f7/ble_glue/services/serial_service_uuid.inc similarity index 100% rename from firmware/targets/f7/ble_glue/services/serial_service_uuid.inc rename to targets/f7/ble_glue/services/serial_service_uuid.inc diff --git a/firmware/targets/f7/ble_glue/tl_dbg_conf.h b/targets/f7/ble_glue/tl_dbg_conf.h similarity index 100% rename from firmware/targets/f7/ble_glue/tl_dbg_conf.h rename to targets/f7/ble_glue/tl_dbg_conf.h diff --git a/firmware/targets/f7/fatfs/fatfs.c b/targets/f7/fatfs/fatfs.c similarity index 100% rename from firmware/targets/f7/fatfs/fatfs.c rename to targets/f7/fatfs/fatfs.c diff --git a/firmware/targets/f7/fatfs/fatfs.h b/targets/f7/fatfs/fatfs.h similarity index 100% rename from firmware/targets/f7/fatfs/fatfs.h rename to targets/f7/fatfs/fatfs.h diff --git a/firmware/targets/f7/fatfs/ffconf.h b/targets/f7/fatfs/ffconf.h similarity index 100% rename from firmware/targets/f7/fatfs/ffconf.h rename to targets/f7/fatfs/ffconf.h diff --git a/firmware/targets/f7/fatfs/sector_cache.c b/targets/f7/fatfs/sector_cache.c similarity index 100% rename from firmware/targets/f7/fatfs/sector_cache.c rename to targets/f7/fatfs/sector_cache.c diff --git a/firmware/targets/f7/fatfs/sector_cache.h b/targets/f7/fatfs/sector_cache.h similarity index 100% rename from firmware/targets/f7/fatfs/sector_cache.h rename to targets/f7/fatfs/sector_cache.h diff --git a/firmware/targets/f7/fatfs/user_diskio.c b/targets/f7/fatfs/user_diskio.c similarity index 100% rename from firmware/targets/f7/fatfs/user_diskio.c rename to targets/f7/fatfs/user_diskio.c diff --git a/firmware/targets/f7/fatfs/user_diskio.h b/targets/f7/fatfs/user_diskio.h similarity index 100% rename from firmware/targets/f7/fatfs/user_diskio.h rename to targets/f7/fatfs/user_diskio.h diff --git a/firmware/targets/f7/furi_hal/furi_hal.c b/targets/f7/furi_hal/furi_hal.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal.c rename to targets/f7/furi_hal/furi_hal.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_bt.c b/targets/f7/furi_hal/furi_hal_bt.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_bt.c rename to targets/f7/furi_hal/furi_hal_bt.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_bt_hid.c b/targets/f7/furi_hal/furi_hal_bt_hid.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_bt_hid.c rename to targets/f7/furi_hal/furi_hal_bt_hid.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_bt_serial.c b/targets/f7/furi_hal/furi_hal_bt_serial.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_bt_serial.c rename to targets/f7/furi_hal/furi_hal_bt_serial.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_bus.c b/targets/f7/furi_hal/furi_hal_bus.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_bus.c rename to targets/f7/furi_hal/furi_hal_bus.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_bus.h b/targets/f7/furi_hal/furi_hal_bus.h similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_bus.h rename to targets/f7/furi_hal/furi_hal_bus.h diff --git a/firmware/targets/f7/furi_hal/furi_hal_clock.c b/targets/f7/furi_hal/furi_hal_clock.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_clock.c rename to targets/f7/furi_hal/furi_hal_clock.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_clock.h b/targets/f7/furi_hal/furi_hal_clock.h similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_clock.h rename to targets/f7/furi_hal/furi_hal_clock.h diff --git a/firmware/targets/f7/furi_hal/furi_hal_console.c b/targets/f7/furi_hal/furi_hal_console.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_console.c rename to targets/f7/furi_hal/furi_hal_console.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_console.h b/targets/f7/furi_hal/furi_hal_console.h similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_console.h rename to targets/f7/furi_hal/furi_hal_console.h diff --git a/firmware/targets/f7/furi_hal/furi_hal_cortex.c b/targets/f7/furi_hal/furi_hal_cortex.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_cortex.c rename to targets/f7/furi_hal/furi_hal_cortex.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_crypto.c b/targets/f7/furi_hal/furi_hal_crypto.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_crypto.c rename to targets/f7/furi_hal/furi_hal_crypto.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_debug.c b/targets/f7/furi_hal/furi_hal_debug.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_debug.c rename to targets/f7/furi_hal/furi_hal_debug.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_dma.c b/targets/f7/furi_hal/furi_hal_dma.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_dma.c rename to targets/f7/furi_hal/furi_hal_dma.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_dma.h b/targets/f7/furi_hal/furi_hal_dma.h similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_dma.h rename to targets/f7/furi_hal/furi_hal_dma.h diff --git a/firmware/targets/f7/furi_hal/furi_hal_flash.c b/targets/f7/furi_hal/furi_hal_flash.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_flash.c rename to targets/f7/furi_hal/furi_hal_flash.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_flash.h b/targets/f7/furi_hal/furi_hal_flash.h similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_flash.h rename to targets/f7/furi_hal/furi_hal_flash.h diff --git a/firmware/targets/f7/furi_hal/furi_hal_gpio.c b/targets/f7/furi_hal/furi_hal_gpio.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_gpio.c rename to targets/f7/furi_hal/furi_hal_gpio.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_gpio.h b/targets/f7/furi_hal/furi_hal_gpio.h similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_gpio.h rename to targets/f7/furi_hal/furi_hal_gpio.h diff --git a/firmware/targets/f7/furi_hal/furi_hal_i2c.c b/targets/f7/furi_hal/furi_hal_i2c.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_i2c.c rename to targets/f7/furi_hal/furi_hal_i2c.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_i2c_config.c b/targets/f7/furi_hal/furi_hal_i2c_config.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_i2c_config.c rename to targets/f7/furi_hal/furi_hal_i2c_config.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_i2c_config.h b/targets/f7/furi_hal/furi_hal_i2c_config.h similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_i2c_config.h rename to targets/f7/furi_hal/furi_hal_i2c_config.h diff --git a/firmware/targets/f7/furi_hal/furi_hal_i2c_types.h b/targets/f7/furi_hal/furi_hal_i2c_types.h similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_i2c_types.h rename to targets/f7/furi_hal/furi_hal_i2c_types.h diff --git a/firmware/targets/f7/furi_hal/furi_hal_ibutton.c b/targets/f7/furi_hal/furi_hal_ibutton.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_ibutton.c rename to targets/f7/furi_hal/furi_hal_ibutton.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_ibutton.h b/targets/f7/furi_hal/furi_hal_ibutton.h similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_ibutton.h rename to targets/f7/furi_hal/furi_hal_ibutton.h diff --git a/firmware/targets/f7/furi_hal/furi_hal_idle_timer.h b/targets/f7/furi_hal/furi_hal_idle_timer.h similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_idle_timer.h rename to targets/f7/furi_hal/furi_hal_idle_timer.h diff --git a/firmware/targets/f7/furi_hal/furi_hal_info.c b/targets/f7/furi_hal/furi_hal_info.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_info.c rename to targets/f7/furi_hal/furi_hal_info.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_infrared.c b/targets/f7/furi_hal/furi_hal_infrared.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_infrared.c rename to targets/f7/furi_hal/furi_hal_infrared.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_interrupt.c b/targets/f7/furi_hal/furi_hal_interrupt.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_interrupt.c rename to targets/f7/furi_hal/furi_hal_interrupt.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_interrupt.h b/targets/f7/furi_hal/furi_hal_interrupt.h similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_interrupt.h rename to targets/f7/furi_hal/furi_hal_interrupt.h diff --git a/firmware/targets/f7/furi_hal/furi_hal_light.c b/targets/f7/furi_hal/furi_hal_light.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_light.c rename to targets/f7/furi_hal/furi_hal_light.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_memory.c b/targets/f7/furi_hal/furi_hal_memory.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_memory.c rename to targets/f7/furi_hal/furi_hal_memory.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_mpu.c b/targets/f7/furi_hal/furi_hal_mpu.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_mpu.c rename to targets/f7/furi_hal/furi_hal_mpu.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc.c b/targets/f7/furi_hal/furi_hal_nfc.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_nfc.c rename to targets/f7/furi_hal/furi_hal_nfc.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc_event.c b/targets/f7/furi_hal/furi_hal_nfc_event.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_nfc_event.c rename to targets/f7/furi_hal/furi_hal_nfc_event.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc_felica.c b/targets/f7/furi_hal/furi_hal_nfc_felica.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_nfc_felica.c rename to targets/f7/furi_hal/furi_hal_nfc_felica.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc_i.h b/targets/f7/furi_hal/furi_hal_nfc_i.h similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_nfc_i.h rename to targets/f7/furi_hal/furi_hal_nfc_i.h diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc_irq.c b/targets/f7/furi_hal/furi_hal_nfc_irq.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_nfc_irq.c rename to targets/f7/furi_hal/furi_hal_nfc_irq.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc_iso14443a.c b/targets/f7/furi_hal/furi_hal_nfc_iso14443a.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_nfc_iso14443a.c rename to targets/f7/furi_hal/furi_hal_nfc_iso14443a.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc_iso14443b.c b/targets/f7/furi_hal/furi_hal_nfc_iso14443b.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_nfc_iso14443b.c rename to targets/f7/furi_hal/furi_hal_nfc_iso14443b.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc_iso15693.c b/targets/f7/furi_hal/furi_hal_nfc_iso15693.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_nfc_iso15693.c rename to targets/f7/furi_hal/furi_hal_nfc_iso15693.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc_tech_i.h b/targets/f7/furi_hal/furi_hal_nfc_tech_i.h similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_nfc_tech_i.h rename to targets/f7/furi_hal/furi_hal_nfc_tech_i.h diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc_timer.c b/targets/f7/furi_hal/furi_hal_nfc_timer.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_nfc_timer.c rename to targets/f7/furi_hal/furi_hal_nfc_timer.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_os.c b/targets/f7/furi_hal/furi_hal_os.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_os.c rename to targets/f7/furi_hal/furi_hal_os.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_os.h b/targets/f7/furi_hal/furi_hal_os.h similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_os.h rename to targets/f7/furi_hal/furi_hal_os.h diff --git a/firmware/targets/f7/furi_hal/furi_hal_power.c b/targets/f7/furi_hal/furi_hal_power.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_power.c rename to targets/f7/furi_hal/furi_hal_power.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_power_config.c b/targets/f7/furi_hal/furi_hal_power_config.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_power_config.c rename to targets/f7/furi_hal/furi_hal_power_config.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_pwm.c b/targets/f7/furi_hal/furi_hal_pwm.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_pwm.c rename to targets/f7/furi_hal/furi_hal_pwm.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_pwm.h b/targets/f7/furi_hal/furi_hal_pwm.h similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_pwm.h rename to targets/f7/furi_hal/furi_hal_pwm.h diff --git a/firmware/targets/f7/furi_hal/furi_hal_random.c b/targets/f7/furi_hal/furi_hal_random.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_random.c rename to targets/f7/furi_hal/furi_hal_random.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_region.c b/targets/f7/furi_hal/furi_hal_region.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_region.c rename to targets/f7/furi_hal/furi_hal_region.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_resources.c b/targets/f7/furi_hal/furi_hal_resources.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_resources.c rename to targets/f7/furi_hal/furi_hal_resources.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_resources.h b/targets/f7/furi_hal/furi_hal_resources.h similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_resources.h rename to targets/f7/furi_hal/furi_hal_resources.h diff --git a/firmware/targets/f7/furi_hal/furi_hal_rfid.c b/targets/f7/furi_hal/furi_hal_rfid.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_rfid.c rename to targets/f7/furi_hal/furi_hal_rfid.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_rfid.h b/targets/f7/furi_hal/furi_hal_rfid.h similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_rfid.h rename to targets/f7/furi_hal/furi_hal_rfid.h diff --git a/firmware/targets/f7/furi_hal/furi_hal_rtc.c b/targets/f7/furi_hal/furi_hal_rtc.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_rtc.c rename to targets/f7/furi_hal/furi_hal_rtc.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_sd.c b/targets/f7/furi_hal/furi_hal_sd.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_sd.c rename to targets/f7/furi_hal/furi_hal_sd.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_speaker.c b/targets/f7/furi_hal/furi_hal_speaker.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_speaker.c rename to targets/f7/furi_hal/furi_hal_speaker.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_spi.c b/targets/f7/furi_hal/furi_hal_spi.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_spi.c rename to targets/f7/furi_hal/furi_hal_spi.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_spi_config.c b/targets/f7/furi_hal/furi_hal_spi_config.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_spi_config.c rename to targets/f7/furi_hal/furi_hal_spi_config.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_spi_config.h b/targets/f7/furi_hal/furi_hal_spi_config.h similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_spi_config.h rename to targets/f7/furi_hal/furi_hal_spi_config.h diff --git a/firmware/targets/f7/furi_hal/furi_hal_spi_types.h b/targets/f7/furi_hal/furi_hal_spi_types.h similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_spi_types.h rename to targets/f7/furi_hal/furi_hal_spi_types.h diff --git a/firmware/targets/f7/furi_hal/furi_hal_subghz.c b/targets/f7/furi_hal/furi_hal_subghz.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_subghz.c rename to targets/f7/furi_hal/furi_hal_subghz.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_subghz.h b/targets/f7/furi_hal/furi_hal_subghz.h similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_subghz.h rename to targets/f7/furi_hal/furi_hal_subghz.h diff --git a/firmware/targets/f7/furi_hal/furi_hal_target_hw.h b/targets/f7/furi_hal/furi_hal_target_hw.h similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_target_hw.h rename to targets/f7/furi_hal/furi_hal_target_hw.h diff --git a/firmware/targets/f7/furi_hal/furi_hal_uart.c b/targets/f7/furi_hal/furi_hal_uart.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_uart.c rename to targets/f7/furi_hal/furi_hal_uart.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_uart.h b/targets/f7/furi_hal/furi_hal_uart.h similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_uart.h rename to targets/f7/furi_hal/furi_hal_uart.h diff --git a/firmware/targets/f7/furi_hal/furi_hal_usb.c b/targets/f7/furi_hal/furi_hal_usb.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_usb.c rename to targets/f7/furi_hal/furi_hal_usb.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_usb_ccid.c b/targets/f7/furi_hal/furi_hal_usb_ccid.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_usb_ccid.c rename to targets/f7/furi_hal/furi_hal_usb_ccid.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_usb_cdc.c b/targets/f7/furi_hal/furi_hal_usb_cdc.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_usb_cdc.c rename to targets/f7/furi_hal/furi_hal_usb_cdc.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_usb_cdc.h b/targets/f7/furi_hal/furi_hal_usb_cdc.h similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_usb_cdc.h rename to targets/f7/furi_hal/furi_hal_usb_cdc.h diff --git a/firmware/targets/f7/furi_hal/furi_hal_usb_hid.c b/targets/f7/furi_hal/furi_hal_usb_hid.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_usb_hid.c rename to targets/f7/furi_hal/furi_hal_usb_hid.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_usb_i.h b/targets/f7/furi_hal/furi_hal_usb_i.h similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_usb_i.h rename to targets/f7/furi_hal/furi_hal_usb_i.h diff --git a/firmware/targets/f7/furi_hal/furi_hal_usb_u2f.c b/targets/f7/furi_hal/furi_hal_usb_u2f.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_usb_u2f.c rename to targets/f7/furi_hal/furi_hal_usb_u2f.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_version.c b/targets/f7/furi_hal/furi_hal_version.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_version.c rename to targets/f7/furi_hal/furi_hal_version.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_version_device.c b/targets/f7/furi_hal/furi_hal_version_device.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_version_device.c rename to targets/f7/furi_hal/furi_hal_version_device.c diff --git a/firmware/targets/f7/furi_hal/furi_hal_vibro.c b/targets/f7/furi_hal/furi_hal_vibro.c similarity index 100% rename from firmware/targets/f7/furi_hal/furi_hal_vibro.c rename to targets/f7/furi_hal/furi_hal_vibro.c diff --git a/firmware/targets/f7/inc/FreeRTOSConfig.h b/targets/f7/inc/FreeRTOSConfig.h similarity index 100% rename from firmware/targets/f7/inc/FreeRTOSConfig.h rename to targets/f7/inc/FreeRTOSConfig.h diff --git a/firmware/targets/f7/inc/alt_boot.h b/targets/f7/inc/alt_boot.h similarity index 100% rename from firmware/targets/f7/inc/alt_boot.h rename to targets/f7/inc/alt_boot.h diff --git a/firmware/targets/f7/inc/stm32.h b/targets/f7/inc/stm32.h similarity index 100% rename from firmware/targets/f7/inc/stm32.h rename to targets/f7/inc/stm32.h diff --git a/firmware/targets/f7/inc/stm32_assert.h b/targets/f7/inc/stm32_assert.h similarity index 100% rename from firmware/targets/f7/inc/stm32_assert.h rename to targets/f7/inc/stm32_assert.h diff --git a/firmware/targets/f7/platform_specific/intrinsic_export.h b/targets/f7/platform_specific/intrinsic_export.h similarity index 100% rename from firmware/targets/f7/platform_specific/intrinsic_export.h rename to targets/f7/platform_specific/intrinsic_export.h diff --git a/firmware/targets/f7/platform_specific/math_wrapper.h b/targets/f7/platform_specific/math_wrapper.h similarity index 100% rename from firmware/targets/f7/platform_specific/math_wrapper.h rename to targets/f7/platform_specific/math_wrapper.h diff --git a/firmware/targets/f7/src/dfu.c b/targets/f7/src/dfu.c similarity index 100% rename from firmware/targets/f7/src/dfu.c rename to targets/f7/src/dfu.c diff --git a/firmware/targets/f7/src/main.c b/targets/f7/src/main.c similarity index 100% rename from firmware/targets/f7/src/main.c rename to targets/f7/src/main.c diff --git a/firmware/targets/f7/src/recovery.c b/targets/f7/src/recovery.c similarity index 100% rename from firmware/targets/f7/src/recovery.c rename to targets/f7/src/recovery.c diff --git a/firmware/targets/f7/src/system_stm32wbxx.c b/targets/f7/src/system_stm32wbxx.c similarity index 100% rename from firmware/targets/f7/src/system_stm32wbxx.c rename to targets/f7/src/system_stm32wbxx.c diff --git a/firmware/targets/f7/src/update.c b/targets/f7/src/update.c similarity index 100% rename from firmware/targets/f7/src/update.c rename to targets/f7/src/update.c diff --git a/firmware/targets/f7/startup_stm32wb55xx_cm4.s b/targets/f7/startup_stm32wb55xx_cm4.s similarity index 100% rename from firmware/targets/f7/startup_stm32wb55xx_cm4.s rename to targets/f7/startup_stm32wb55xx_cm4.s diff --git a/firmware/targets/f7/stm32wb55xx_flash.ld b/targets/f7/stm32wb55xx_flash.ld similarity index 100% rename from firmware/targets/f7/stm32wb55xx_flash.ld rename to targets/f7/stm32wb55xx_flash.ld diff --git a/firmware/targets/f7/stm32wb55xx_ram_fw.ld b/targets/f7/stm32wb55xx_ram_fw.ld similarity index 100% rename from firmware/targets/f7/stm32wb55xx_ram_fw.ld rename to targets/f7/stm32wb55xx_ram_fw.ld diff --git a/firmware/targets/f7/target.json b/targets/f7/target.json similarity index 100% rename from firmware/targets/f7/target.json rename to targets/f7/target.json diff --git a/firmware/targets/furi_hal_include/furi_hal.h b/targets/furi_hal_include/furi_hal.h similarity index 100% rename from firmware/targets/furi_hal_include/furi_hal.h rename to targets/furi_hal_include/furi_hal.h diff --git a/firmware/targets/furi_hal_include/furi_hal_bt.h b/targets/furi_hal_include/furi_hal_bt.h similarity index 100% rename from firmware/targets/furi_hal_include/furi_hal_bt.h rename to targets/furi_hal_include/furi_hal_bt.h diff --git a/firmware/targets/furi_hal_include/furi_hal_bt_hid.h b/targets/furi_hal_include/furi_hal_bt_hid.h similarity index 100% rename from firmware/targets/furi_hal_include/furi_hal_bt_hid.h rename to targets/furi_hal_include/furi_hal_bt_hid.h diff --git a/firmware/targets/furi_hal_include/furi_hal_bt_serial.h b/targets/furi_hal_include/furi_hal_bt_serial.h similarity index 100% rename from firmware/targets/furi_hal_include/furi_hal_bt_serial.h rename to targets/furi_hal_include/furi_hal_bt_serial.h diff --git a/firmware/targets/furi_hal_include/furi_hal_cortex.h b/targets/furi_hal_include/furi_hal_cortex.h similarity index 100% rename from firmware/targets/furi_hal_include/furi_hal_cortex.h rename to targets/furi_hal_include/furi_hal_cortex.h diff --git a/firmware/targets/furi_hal_include/furi_hal_crypto.h b/targets/furi_hal_include/furi_hal_crypto.h similarity index 100% rename from firmware/targets/furi_hal_include/furi_hal_crypto.h rename to targets/furi_hal_include/furi_hal_crypto.h diff --git a/firmware/targets/furi_hal_include/furi_hal_debug.h b/targets/furi_hal_include/furi_hal_debug.h similarity index 100% rename from firmware/targets/furi_hal_include/furi_hal_debug.h rename to targets/furi_hal_include/furi_hal_debug.h diff --git a/firmware/targets/furi_hal_include/furi_hal_i2c.h b/targets/furi_hal_include/furi_hal_i2c.h similarity index 100% rename from firmware/targets/furi_hal_include/furi_hal_i2c.h rename to targets/furi_hal_include/furi_hal_i2c.h diff --git a/firmware/targets/furi_hal_include/furi_hal_info.h b/targets/furi_hal_include/furi_hal_info.h similarity index 100% rename from firmware/targets/furi_hal_include/furi_hal_info.h rename to targets/furi_hal_include/furi_hal_info.h diff --git a/firmware/targets/furi_hal_include/furi_hal_infrared.h b/targets/furi_hal_include/furi_hal_infrared.h similarity index 100% rename from firmware/targets/furi_hal_include/furi_hal_infrared.h rename to targets/furi_hal_include/furi_hal_infrared.h diff --git a/firmware/targets/furi_hal_include/furi_hal_light.h b/targets/furi_hal_include/furi_hal_light.h similarity index 100% rename from firmware/targets/furi_hal_include/furi_hal_light.h rename to targets/furi_hal_include/furi_hal_light.h diff --git a/firmware/targets/furi_hal_include/furi_hal_memory.h b/targets/furi_hal_include/furi_hal_memory.h similarity index 100% rename from firmware/targets/furi_hal_include/furi_hal_memory.h rename to targets/furi_hal_include/furi_hal_memory.h diff --git a/firmware/targets/furi_hal_include/furi_hal_mpu.h b/targets/furi_hal_include/furi_hal_mpu.h similarity index 100% rename from firmware/targets/furi_hal_include/furi_hal_mpu.h rename to targets/furi_hal_include/furi_hal_mpu.h diff --git a/firmware/targets/furi_hal_include/furi_hal_nfc.h b/targets/furi_hal_include/furi_hal_nfc.h similarity index 100% rename from firmware/targets/furi_hal_include/furi_hal_nfc.h rename to targets/furi_hal_include/furi_hal_nfc.h diff --git a/firmware/targets/furi_hal_include/furi_hal_power.h b/targets/furi_hal_include/furi_hal_power.h similarity index 100% rename from firmware/targets/furi_hal_include/furi_hal_power.h rename to targets/furi_hal_include/furi_hal_power.h diff --git a/firmware/targets/furi_hal_include/furi_hal_random.h b/targets/furi_hal_include/furi_hal_random.h similarity index 100% rename from firmware/targets/furi_hal_include/furi_hal_random.h rename to targets/furi_hal_include/furi_hal_random.h diff --git a/firmware/targets/furi_hal_include/furi_hal_region.h b/targets/furi_hal_include/furi_hal_region.h similarity index 100% rename from firmware/targets/furi_hal_include/furi_hal_region.h rename to targets/furi_hal_include/furi_hal_region.h diff --git a/firmware/targets/furi_hal_include/furi_hal_rtc.h b/targets/furi_hal_include/furi_hal_rtc.h similarity index 100% rename from firmware/targets/furi_hal_include/furi_hal_rtc.h rename to targets/furi_hal_include/furi_hal_rtc.h diff --git a/firmware/targets/furi_hal_include/furi_hal_sd.h b/targets/furi_hal_include/furi_hal_sd.h similarity index 100% rename from firmware/targets/furi_hal_include/furi_hal_sd.h rename to targets/furi_hal_include/furi_hal_sd.h diff --git a/firmware/targets/furi_hal_include/furi_hal_speaker.h b/targets/furi_hal_include/furi_hal_speaker.h similarity index 100% rename from firmware/targets/furi_hal_include/furi_hal_speaker.h rename to targets/furi_hal_include/furi_hal_speaker.h diff --git a/firmware/targets/furi_hal_include/furi_hal_spi.h b/targets/furi_hal_include/furi_hal_spi.h similarity index 100% rename from firmware/targets/furi_hal_include/furi_hal_spi.h rename to targets/furi_hal_include/furi_hal_spi.h diff --git a/firmware/targets/furi_hal_include/furi_hal_usb.h b/targets/furi_hal_include/furi_hal_usb.h similarity index 100% rename from firmware/targets/furi_hal_include/furi_hal_usb.h rename to targets/furi_hal_include/furi_hal_usb.h diff --git a/firmware/targets/furi_hal_include/furi_hal_usb_ccid.h b/targets/furi_hal_include/furi_hal_usb_ccid.h similarity index 100% rename from firmware/targets/furi_hal_include/furi_hal_usb_ccid.h rename to targets/furi_hal_include/furi_hal_usb_ccid.h diff --git a/firmware/targets/furi_hal_include/furi_hal_usb_hid.h b/targets/furi_hal_include/furi_hal_usb_hid.h similarity index 100% rename from firmware/targets/furi_hal_include/furi_hal_usb_hid.h rename to targets/furi_hal_include/furi_hal_usb_hid.h diff --git a/firmware/targets/furi_hal_include/furi_hal_usb_hid_u2f.h b/targets/furi_hal_include/furi_hal_usb_hid_u2f.h similarity index 100% rename from firmware/targets/furi_hal_include/furi_hal_usb_hid_u2f.h rename to targets/furi_hal_include/furi_hal_usb_hid_u2f.h diff --git a/firmware/targets/furi_hal_include/furi_hal_version.h b/targets/furi_hal_include/furi_hal_version.h similarity index 100% rename from firmware/targets/furi_hal_include/furi_hal_version.h rename to targets/furi_hal_include/furi_hal_version.h diff --git a/firmware/targets/furi_hal_include/furi_hal_vibro.h b/targets/furi_hal_include/furi_hal_vibro.h similarity index 100% rename from firmware/targets/furi_hal_include/furi_hal_vibro.h rename to targets/furi_hal_include/furi_hal_vibro.h From c8180747dbfb7b6976bca9226ae42227737e0d0d Mon Sep 17 00:00:00 2001 From: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> Date: Mon, 30 Oct 2023 19:20:35 +0300 Subject: [PATCH 792/824] [FL-3456] Allow for larger Infrared remotes (#3164) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Do not load all signals at once (Draft) * Minor cleanup * Refactor remote renaming * Improve function signatures * Rename infrared_remote functions * Optimise signal loading * Implement adding signals to remote * Add read_name() method * Deprecate a function * Partially implement deleting signals (draft) * Use m-array instead of m-list for signal name directory * Use plain C strings instead of furi_string * Implement deleting signals * Implement deleting signals via generalised callback * Implement renaming signals * Rename some types * Some more renaming * Remove unused type * Implement inserting signals (internal use) * Improve InfraredMoveView * Send an event to move a signal * Remove unused type * Implement moving signals * Implement creating new remotes with one signal * Un-deprecate and rename a function * Add InfraredRemote API docs * Add InfraredSignal API docs * Better error messages * Show progress pop-up when moving buttons in a remote * Copy labels to the InfraredMoveView to avoid pointer invalidation * Improve file selection scene * Show progress pop-up when renaming buttons in a remote * Refactor a scene * Show progress when deleting a button from remote * Use a random name for temp files * Add docs to infrared_brute_force.h * Rename Infrared type to InfraredApp * Add docs to infrared_app_i.h Co-authored-by: あく --- applications/main/infrared/infrared.h | 3 - .../infrared/{infrared.c => infrared_app.c} | 183 ++++---- applications/main/infrared/infrared_app.h | 15 + applications/main/infrared/infrared_app_i.h | 288 ++++++++++++ .../main/infrared/infrared_brute_force.c | 6 +- .../main/infrared/infrared_brute_force.h | 89 +++- applications/main/infrared/infrared_cli.c | 4 +- applications/main/infrared/infrared_i.h | 146 ------ applications/main/infrared/infrared_remote.c | 439 +++++++++++++----- applications/main/infrared/infrared_remote.h | 231 ++++++++- .../main/infrared/infrared_remote_button.c | 37 -- .../main/infrared/infrared_remote_button.h | 14 - applications/main/infrared/infrared_signal.c | 54 +-- applications/main/infrared/infrared_signal.h | 179 ++++++- .../common/infrared_scene_universal_common.c | 17 +- .../infrared/scenes/infrared_scene_ask_back.c | 10 +- .../scenes/infrared_scene_ask_retry.c | 10 +- .../infrared/scenes/infrared_scene_debug.c | 14 +- .../infrared/scenes/infrared_scene_edit.c | 10 +- .../infrared_scene_edit_button_select.c | 16 +- .../scenes/infrared_scene_edit_delete.c | 62 ++- .../scenes/infrared_scene_edit_delete_done.c | 8 +- .../scenes/infrared_scene_edit_move.c | 73 ++- .../scenes/infrared_scene_edit_rename.c | 35 +- .../scenes/infrared_scene_edit_rename_done.c | 8 +- .../scenes/infrared_scene_error_databases.c | 8 +- .../infrared/scenes/infrared_scene_learn.c | 8 +- .../scenes/infrared_scene_learn_done.c | 8 +- .../scenes/infrared_scene_learn_enter_name.c | 37 +- .../scenes/infrared_scene_learn_success.c | 20 +- .../infrared/scenes/infrared_scene_remote.c | 16 +- .../scenes/infrared_scene_remote_list.c | 31 +- .../main/infrared/scenes/infrared_scene_rpc.c | 13 +- .../infrared/scenes/infrared_scene_start.c | 10 +- .../scenes/infrared_scene_universal.c | 10 +- .../scenes/infrared_scene_universal_ac.c | 4 +- .../scenes/infrared_scene_universal_audio.c | 4 +- .../infrared_scene_universal_projector.c | 4 +- .../scenes/infrared_scene_universal_tv.c | 4 +- .../main/infrared/views/infrared_move_view.c | 200 ++++---- .../main/infrared/views/infrared_move_view.h | 13 +- 41 files changed, 1594 insertions(+), 747 deletions(-) delete mode 100644 applications/main/infrared/infrared.h rename applications/main/infrared/{infrared.c => infrared_app.c} (74%) create mode 100644 applications/main/infrared/infrared_app.h create mode 100644 applications/main/infrared/infrared_app_i.h delete mode 100644 applications/main/infrared/infrared_i.h delete mode 100644 applications/main/infrared/infrared_remote_button.c delete mode 100644 applications/main/infrared/infrared_remote_button.h diff --git a/applications/main/infrared/infrared.h b/applications/main/infrared/infrared.h deleted file mode 100644 index e5eeb11772f..00000000000 --- a/applications/main/infrared/infrared.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -typedef struct Infrared Infrared; diff --git a/applications/main/infrared/infrared.c b/applications/main/infrared/infrared_app.c similarity index 74% rename from applications/main/infrared/infrared.c rename to applications/main/infrared/infrared_app.c index fcf45c25c28..e29eda30fb4 100644 --- a/applications/main/infrared/infrared.c +++ b/applications/main/infrared/infrared_app.c @@ -1,48 +1,52 @@ -#include "infrared_i.h" +#include "infrared_app_i.h" #include +#include #include +#define TAG "InfraredApp" + #define INFRARED_TX_MIN_INTERVAL_MS 50U -static const NotificationSequence* infrared_notification_sequences[] = { - &sequence_success, - &sequence_set_only_green_255, - &sequence_reset_green, - &sequence_solid_yellow, - &sequence_reset_rgb, - &sequence_blink_start_cyan, - &sequence_blink_start_magenta, - &sequence_blink_stop, +static const NotificationSequence* + infrared_notification_sequences[InfraredNotificationMessageCount] = { + &sequence_success, + &sequence_set_only_green_255, + &sequence_reset_green, + &sequence_solid_yellow, + &sequence_reset_rgb, + &sequence_blink_start_cyan, + &sequence_blink_start_magenta, + &sequence_blink_stop, }; -static void infrared_make_app_folder(Infrared* infrared) { +static void infrared_make_app_folder(InfraredApp* infrared) { if(!storage_simply_mkdir(infrared->storage, INFRARED_APP_FOLDER)) { - dialog_message_show_storage_error(infrared->dialogs, "Cannot create\napp folder"); + infrared_show_error_message(infrared, "Cannot create\napp folder"); } } static bool infrared_custom_event_callback(void* context, uint32_t event) { furi_assert(context); - Infrared* infrared = context; + InfraredApp* infrared = context; return scene_manager_handle_custom_event(infrared->scene_manager, event); } static bool infrared_back_event_callback(void* context) { furi_assert(context); - Infrared* infrared = context; + InfraredApp* infrared = context; return scene_manager_handle_back_event(infrared->scene_manager); } static void infrared_tick_event_callback(void* context) { furi_assert(context); - Infrared* infrared = context; + InfraredApp* infrared = context; scene_manager_handle_tick_event(infrared->scene_manager); } static void infrared_rpc_command_callback(RpcAppSystemEvent event, void* context) { furi_assert(context); - Infrared* infrared = context; + InfraredApp* infrared = context; furi_assert(infrared->rpc_ctx); if(event == RpcAppEventSessionClose) { @@ -109,8 +113,8 @@ static void infrared_find_vacant_remote_name(FuriString* name, const char* path) furi_record_close(RECORD_STORAGE); } -static Infrared* infrared_alloc() { - Infrared* infrared = malloc(sizeof(Infrared)); +static InfraredApp* infrared_alloc() { + InfraredApp* infrared = malloc(sizeof(InfraredApp)); infrared->file_path = furi_string_alloc(); @@ -139,7 +143,7 @@ static Infrared* infrared_alloc() { infrared->worker = infrared_worker_alloc(); infrared->remote = infrared_remote_alloc(); - infrared->received_signal = infrared_signal_alloc(); + infrared->current_signal = infrared_signal_alloc(); infrared->brute_force = infrared_brute_force_alloc(); infrared->submenu = submenu_alloc(); @@ -184,7 +188,7 @@ static Infrared* infrared_alloc() { return infrared; } -static void infrared_free(Infrared* infrared) { +static void infrared_free(InfraredApp* infrared) { furi_assert(infrared); ViewDispatcher* view_dispatcher = infrared->view_dispatcher; InfraredAppState* app_state = &infrared->app_state; @@ -229,7 +233,7 @@ static void infrared_free(Infrared* infrared) { scene_manager_free(infrared->scene_manager); infrared_brute_force_free(infrared->brute_force); - infrared_signal_free(infrared->received_signal); + infrared_signal_free(infrared->current_signal); infrared_remote_free(infrared->remote); infrared_worker_free(infrared->worker); @@ -248,65 +252,61 @@ static void infrared_free(Infrared* infrared) { } bool infrared_add_remote_with_button( - Infrared* infrared, + const InfraredApp* infrared, const char* button_name, - InfraredSignal* signal) { + const InfraredSignal* signal) { InfraredRemote* remote = infrared->remote; - FuriString *new_name, *new_path; - new_name = furi_string_alloc_set(INFRARED_DEFAULT_REMOTE_NAME); - new_path = furi_string_alloc_set(INFRARED_APP_FOLDER); + FuriString* new_name = furi_string_alloc_set(INFRARED_DEFAULT_REMOTE_NAME); + FuriString* new_path = furi_string_alloc_set(INFRARED_APP_FOLDER); infrared_find_vacant_remote_name(new_name, furi_string_get_cstr(new_path)); furi_string_cat_printf( new_path, "/%s%s", furi_string_get_cstr(new_name), INFRARED_APP_EXTENSION); - infrared_remote_reset(remote); - infrared_remote_set_name(remote, furi_string_get_cstr(new_name)); - infrared_remote_set_path(remote, furi_string_get_cstr(new_path)); + bool success = false; + + do { + if(!infrared_remote_create(remote, furi_string_get_cstr(new_path))) break; + if(!infrared_remote_append_signal(remote, signal, button_name)) break; + success = true; + } while(false); furi_string_free(new_name); furi_string_free(new_path); - return infrared_remote_add_button(remote, button_name, signal); + + return success; } -bool infrared_rename_current_remote(Infrared* infrared, const char* name) { +bool infrared_rename_current_remote(const InfraredApp* infrared, const char* new_name) { InfraredRemote* remote = infrared->remote; - const char* remote_path = infrared_remote_get_path(remote); + const char* old_path = infrared_remote_get_path(remote); - if(!strcmp(infrared_remote_get_name(remote), name)) { + if(!strcmp(infrared_remote_get_name(remote), new_name)) { return true; } - FuriString* new_name; - new_name = furi_string_alloc_set(name); + FuriString* new_name_fstr = furi_string_alloc_set(new_name); + FuriString* new_path_fstr = furi_string_alloc_set(old_path); - infrared_find_vacant_remote_name(new_name, remote_path); + infrared_find_vacant_remote_name(new_name_fstr, old_path); - FuriString* new_path; - new_path = furi_string_alloc_set(infrared_remote_get_path(remote)); - if(furi_string_end_with(new_path, INFRARED_APP_EXTENSION)) { - size_t filename_start = furi_string_search_rchar(new_path, '/'); - furi_string_left(new_path, filename_start); + if(furi_string_end_with(new_path_fstr, INFRARED_APP_EXTENSION)) { + path_extract_dirname(old_path, new_path_fstr); } - furi_string_cat_printf( - new_path, "/%s%s", furi_string_get_cstr(new_name), INFRARED_APP_EXTENSION); - Storage* storage = furi_record_open(RECORD_STORAGE); + path_append(new_path_fstr, furi_string_get_cstr(new_name_fstr)); + furi_string_cat(new_path_fstr, INFRARED_APP_EXTENSION); - FS_Error status = storage_common_rename( - storage, infrared_remote_get_path(remote), furi_string_get_cstr(new_path)); - infrared_remote_set_name(remote, furi_string_get_cstr(new_name)); - infrared_remote_set_path(remote, furi_string_get_cstr(new_path)); + const bool success = infrared_remote_rename(remote, furi_string_get_cstr(new_path_fstr)); - furi_string_free(new_name); - furi_string_free(new_path); + furi_string_free(new_name_fstr); + furi_string_free(new_path_fstr); - furi_record_close(RECORD_STORAGE); - return (status == FSE_OK || status == FSE_EXIST); + return success; } -void infrared_tx_start_signal(Infrared* infrared, InfraredSignal* signal) { +void infrared_tx_start(InfraredApp* infrared) { if(infrared->app_state.is_transmitting) { return; } @@ -317,12 +317,12 @@ void infrared_tx_start_signal(Infrared* infrared, InfraredSignal* signal) { return; } - if(infrared_signal_is_raw(signal)) { - InfraredRawSignal* raw = infrared_signal_get_raw_signal(signal); + if(infrared_signal_is_raw(infrared->current_signal)) { + const InfraredRawSignal* raw = infrared_signal_get_raw_signal(infrared->current_signal); infrared_worker_set_raw_signal( infrared->worker, raw->timings, raw->timings_size, raw->frequency, raw->duty_cycle); } else { - InfraredMessage* message = infrared_signal_get_message(signal); + const InfraredMessage* message = infrared_signal_get_message(infrared->current_signal); infrared_worker_set_decoded_signal(infrared->worker, message); } @@ -336,20 +336,20 @@ void infrared_tx_start_signal(Infrared* infrared, InfraredSignal* signal) { infrared->app_state.is_transmitting = true; } -void infrared_tx_start_button_index(Infrared* infrared, size_t button_index) { - furi_assert(button_index < infrared_remote_get_button_count(infrared->remote)); - - InfraredRemoteButton* button = infrared_remote_get_button(infrared->remote, button_index); - InfraredSignal* signal = infrared_remote_button_get_signal(button); - - infrared_tx_start_signal(infrared, signal); -} +void infrared_tx_start_button_index(InfraredApp* infrared, size_t button_index) { + furi_assert(button_index < infrared_remote_get_signal_count(infrared->remote)); -void infrared_tx_start_received(Infrared* infrared) { - infrared_tx_start_signal(infrared, infrared->received_signal); + if(infrared_remote_load_signal(infrared->remote, infrared->current_signal, button_index)) { + infrared_tx_start(infrared); + } else { + infrared_show_error_message( + infrared, + "Failed to load\n\"%s\"", + infrared_remote_get_signal_name(infrared->remote, button_index)); + } } -void infrared_tx_stop(Infrared* infrared) { +void infrared_tx_stop(InfraredApp* infrared) { if(!infrared->app_state.is_transmitting) { return; } @@ -363,25 +363,27 @@ void infrared_tx_stop(Infrared* infrared) { infrared->app_state.last_transmit_time = furi_get_tick(); } -void infrared_text_store_set(Infrared* infrared, uint32_t bank, const char* text, ...) { +void infrared_text_store_set(InfraredApp* infrared, uint32_t bank, const char* fmt, ...) { va_list args; - va_start(args, text); + va_start(args, fmt); - vsnprintf(infrared->text_store[bank], INFRARED_TEXT_STORE_SIZE, text, args); + vsnprintf(infrared->text_store[bank], INFRARED_TEXT_STORE_SIZE, fmt, args); va_end(args); } -void infrared_text_store_clear(Infrared* infrared, uint32_t bank) { +void infrared_text_store_clear(InfraredApp* infrared, uint32_t bank) { memset(infrared->text_store[bank], 0, INFRARED_TEXT_STORE_SIZE + 1); } -void infrared_play_notification_message(Infrared* infrared, uint32_t message) { - furi_assert(message < sizeof(infrared_notification_sequences) / sizeof(NotificationSequence*)); +void infrared_play_notification_message( + const InfraredApp* infrared, + InfraredNotificationMessage message) { + furi_assert(message < InfraredNotificationMessageCount); notification_message(infrared->notifications, infrared_notification_sequences[message]); } -void infrared_show_loading_popup(Infrared* infrared, bool show) { +void infrared_show_loading_popup(const InfraredApp* infrared, bool show) { TaskHandle_t timer_task = xTaskGetHandle(configTIMER_SERVICE_TASK_NAME); ViewStack* view_stack = infrared->view_stack; Loading* loading = infrared->loading; @@ -397,19 +399,30 @@ void infrared_show_loading_popup(Infrared* infrared, bool show) { } } +void infrared_show_error_message(const InfraredApp* infrared, const char* fmt, ...) { + va_list args; + va_start(args, fmt); + + FuriString* message = furi_string_alloc_vprintf(fmt, args); + dialog_message_show_storage_error(infrared->dialogs, furi_string_get_cstr(message)); + + furi_string_free(message); + va_end(args); +} + void infrared_signal_received_callback(void* context, InfraredWorkerSignal* received_signal) { furi_assert(context); - Infrared* infrared = context; + InfraredApp* infrared = context; if(infrared_worker_signal_is_decoded(received_signal)) { infrared_signal_set_message( - infrared->received_signal, infrared_worker_get_decoded_signal(received_signal)); + infrared->current_signal, infrared_worker_get_decoded_signal(received_signal)); } else { const uint32_t* timings; size_t timings_size; infrared_worker_get_raw_signal(received_signal, &timings, &timings_size); infrared_signal_set_raw_signal( - infrared->received_signal, + infrared->current_signal, timings, timings_size, INFRARED_COMMON_CARRIER_FREQUENCY, @@ -422,20 +435,20 @@ void infrared_signal_received_callback(void* context, InfraredWorkerSignal* rece void infrared_text_input_callback(void* context) { furi_assert(context); - Infrared* infrared = context; + InfraredApp* infrared = context; view_dispatcher_send_custom_event( infrared->view_dispatcher, InfraredCustomEventTypeTextEditDone); } void infrared_popup_closed_callback(void* context) { furi_assert(context); - Infrared* infrared = context; + InfraredApp* infrared = context; view_dispatcher_send_custom_event( infrared->view_dispatcher, InfraredCustomEventTypePopupClosed); } int32_t infrared_app(void* p) { - Infrared* infrared = infrared_alloc(); + InfraredApp* infrared = infrared_alloc(); infrared_make_app_folder(infrared); @@ -451,13 +464,15 @@ int32_t infrared_app(void* p) { rpc_system_app_send_started(infrared->rpc_ctx); is_rpc_mode = true; } else { - furi_string_set(infrared->file_path, (const char*)p); - is_remote_loaded = infrared_remote_load(infrared->remote, infrared->file_path); + const char* file_path = (const char*)p; + is_remote_loaded = infrared_remote_load(infrared->remote, file_path); + if(!is_remote_loaded) { - dialog_message_show_storage_error( - infrared->dialogs, "Failed to load\nselected remote"); + infrared_show_error_message(infrared, "Failed to load\n\"%s\"", file_path); return -1; } + + furi_string_set(infrared->file_path, file_path); } } diff --git a/applications/main/infrared/infrared_app.h b/applications/main/infrared/infrared_app.h new file mode 100644 index 00000000000..a6f87402a94 --- /dev/null +++ b/applications/main/infrared/infrared_app.h @@ -0,0 +1,15 @@ +/** + * @file infrared_app.h + * @brief Infrared application - start here. + * + * @see infrared_app_i.h for the main application data structure and functions. + * @see infrared_signal.h for the infrared signal library - loading, storing and transmitting signals. + * @see infrared_remote.hl for the infrared remote library - loading, storing and manipulating remotes. + * @see infrared_brute_force.h for the infrared brute force - loading and transmitting multiple signals. + */ +#pragma once + +/** + * @brief InfraredApp opaque type declaration. + */ +typedef struct InfraredApp InfraredApp; diff --git a/applications/main/infrared/infrared_app_i.h b/applications/main/infrared/infrared_app_i.h new file mode 100644 index 00000000000..c9dfe3ab865 --- /dev/null +++ b/applications/main/infrared/infrared_app_i.h @@ -0,0 +1,288 @@ +/** + * @file infrared_app_i.h + * @brief Main Infrared application types and functions. + */ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include + +#include "infrared_app.h" +#include "infrared_remote.h" +#include "infrared_brute_force.h" +#include "infrared_custom_event.h" + +#include "scenes/infrared_scene.h" +#include "views/infrared_progress_view.h" +#include "views/infrared_debug_view.h" +#include "views/infrared_move_view.h" + +#include "rpc/rpc_app.h" + +#define INFRARED_FILE_NAME_SIZE 100 +#define INFRARED_TEXT_STORE_NUM 2 +#define INFRARED_TEXT_STORE_SIZE 128 + +#define INFRARED_MAX_BUTTON_NAME_LENGTH 22 +#define INFRARED_MAX_REMOTE_NAME_LENGTH 22 + +#define INFRARED_APP_FOLDER ANY_PATH("infrared") +#define INFRARED_APP_EXTENSION ".ir" + +#define INFRARED_DEFAULT_REMOTE_NAME "Remote" +#define INFRARED_LOG_TAG "InfraredApp" + +/** + * @brief Enumeration of invalid remote button indices. + */ +typedef enum { + InfraredButtonIndexNone = -1, /**< No button is currently selected. */ +} InfraredButtonIndex; + +/** + * @brief Enumeration of editing targets. + */ +typedef enum { + InfraredEditTargetNone, /**< No editing target is selected. */ + InfraredEditTargetRemote, /**< Whole remote is selected as editing target. */ + InfraredEditTargetButton, /**< Single button is selected as editing target. */ +} InfraredEditTarget; + +/** + * @brief Enumeration of editing modes. + */ +typedef enum { + InfraredEditModeNone, /**< No editing mode is selected. */ + InfraredEditModeRename, /**< Rename mode is selected. */ + InfraredEditModeDelete, /**< Delete mode is selected. */ +} InfraredEditMode; + +/** + * @brief Infrared application state type. + */ +typedef struct { + bool is_learning_new_remote; /**< Learning new remote or adding to an existing one. */ + bool is_debug_enabled; /**< Whether to enable or disable debugging features. */ + bool is_transmitting; /**< Whether a signal is currently being transmitted. */ + InfraredEditTarget edit_target : 8; /**< Selected editing target (a remote or a button). */ + InfraredEditMode edit_mode : 8; /**< Selected editing operation (rename or delete). */ + int32_t current_button_index; /**< Selected button index (move destination). */ + int32_t prev_button_index; /**< Previous button index (move source). */ + uint32_t last_transmit_time; /**< Lat time a signal was transmitted. */ +} InfraredAppState; + +/** + * @brief Infrared application type. + */ +struct InfraredApp { + SceneManager* scene_manager; /**< Pointer to a SceneManager instance. */ + ViewDispatcher* view_dispatcher; /**< Pointer to a ViewDispatcher instance. */ + + Gui* gui; /**< Pointer to a Gui instance. */ + Storage* storage; /**< Pointer to a Storage instance. */ + DialogsApp* dialogs; /**< Pointer to a DialogsApp instance. */ + NotificationApp* notifications; /**< Pointer to a NotificationApp instance. */ + InfraredWorker* worker; /**< Used to send or receive signals. */ + InfraredRemote* remote; /**< Holds the currently loaded remote. */ + InfraredSignal* current_signal; /**< Holds the currently loaded signal. */ + InfraredBruteForce* brute_force; /**< Used for the Universal Remote feature. */ + + Submenu* submenu; /**< Standard view for displaying application menus. */ + TextInput* text_input; /**< Standard view for receiving user text input. */ + DialogEx* dialog_ex; /**< Standard view for displaying dialogs. */ + ButtonMenu* button_menu; /**< Custom view for interacting with IR remotes. */ + Popup* popup; /**< Standard view for displaying messages. */ + + ViewStack* view_stack; /**< Standard view for displaying stacked interfaces. */ + InfraredDebugView* debug_view; /**< Custom view for displaying debug information. */ + InfraredMoveView* move_view; /**< Custom view for rearranging buttons in a remote. */ + + ButtonPanel* button_panel; /**< Standard view for displaying control panels. */ + Loading* loading; /**< Standard view for informing about long operations. */ + InfraredProgressView* progress; /**< Custom view for showing brute force progress. */ + + FuriString* file_path; /**< Full path to the currently loaded file. */ + /** Arbitrary text storage for various inputs. */ + char text_store[INFRARED_TEXT_STORE_NUM][INFRARED_TEXT_STORE_SIZE + 1]; + InfraredAppState app_state; /**< Application state. */ + + void* rpc_ctx; /**< Pointer to the RPC context object. */ +}; + +/** + * @brief Enumeration of all used view types. + */ +typedef enum { + InfraredViewSubmenu, + InfraredViewTextInput, + InfraredViewDialogEx, + InfraredViewButtonMenu, + InfraredViewPopup, + InfraredViewStack, + InfraredViewDebugView, + InfraredViewMove, +} InfraredView; + +/** + * @brief Enumeration of all notification message types. + */ +typedef enum { + InfraredNotificationMessageSuccess, /**< Play a short happy tune. */ + InfraredNotificationMessageGreenOn, /**< Turn green LED on. */ + InfraredNotificationMessageGreenOff, /**< Turn green LED off. */ + InfraredNotificationMessageYellowOn, /**< Turn yellow LED on. */ + InfraredNotificationMessageYellowOff, /**< Turn yellow LED off. */ + InfraredNotificationMessageBlinkStartRead, /**< Blink the LED to indicate receiver mode. */ + InfraredNotificationMessageBlinkStartSend, /**< Blink the LED to indicate transmitter mode. */ + InfraredNotificationMessageBlinkStop, /**< Stop blinking the LED. */ + InfraredNotificationMessageCount, /**< Special value equal to the message type count. */ +} InfraredNotificationMessage; + +/** + * @brief Add a new remote with a single signal. + * + * The filename will be automatically generated depending on + * the names and number of other files in the infrared data directory. + * + * @param[in] infrared pointer to the application instance. + * @param[in] name pointer to a zero-terminated string containing the signal name. + * @param[in] signal pointer to the signal to be added. + * @return true if the remote was successfully created, false otherwise. + */ +bool infrared_add_remote_with_button( + const InfraredApp* infrared, + const char* name, + const InfraredSignal* signal); + +/** + * @brief Rename the currently loaded remote. + * + * @param[in] infrared pointer to the application instance. + * @param[in] new_name pointer to a zero-terminated string containing the new remote name. + * @return true if the remote was successfully renamed, false otherwise. + */ +bool infrared_rename_current_remote(const InfraredApp* infrared, const char* new_name); + +/** + * @brief Begin transmission of the currently loaded signal. + * + * The signal will be repeated indefinitely until stopped. + * + * @param[in,out] infrared pointer to the application instance. + */ +void infrared_tx_start(InfraredApp* infrared); + +/** + * @brief Load a signal under the given index and begin transmission. + * + * The signal will be repeated indefinitely until stopped. + * + * @param[in,out] infrared pointer to the application instance. + * @param[in] button_index index of the signal to be loaded. + * @returns true if the signal could be loaded, false otherwise. + */ +void infrared_tx_start_button_index(InfraredApp* infrared, size_t button_index); + +/** + * @brief Stop transmission of the currently loaded signal. + * + * @param[in,out] infrared pointer to the application instance. + */ +void infrared_tx_stop(InfraredApp* infrared); + +/** + * @brief Set the internal text store with formatted text. + * + * @param[in,out] infrared pointer to the application instance. + * @param[in] bank index of text store bank (0 or 1). + * @param[in] fmt pointer to a zero-terminated string containing the format text. + * @param[in] ... additional arguments. + */ +void infrared_text_store_set(InfraredApp* infrared, uint32_t bank, const char* fmt, ...) + _ATTRIBUTE((__format__(__printf__, 3, 4))); + +/** + * @brief Clear the internal text store. + * + * @param[in,out] infrared pointer to the application instance. + * @param[in] bank index of text store bank (0 or 1). + */ +void infrared_text_store_clear(InfraredApp* infrared, uint32_t bank); + +/** + * @brief Play a sound and/or blink the LED. + * + * @param[in] infrared pointer to the application instance. + * @param[in] message type of the message to play. + */ +void infrared_play_notification_message( + const InfraredApp* infrared, + InfraredNotificationMessage message); + +/** + * @brief Show a loading pop-up screen. + * + * In order for this to work, a Stack view must be currently active and + * the main view must be added to it. + * + * @param[in] infrared pointer to the application instance. + * @param[in] show whether to show or hide the pop-up. + */ +void infrared_show_loading_popup(const InfraredApp* infrared, bool show); + +/** + * @brief Show a formatted error messsage. + * + * @param[in] infrared pointer to the application instance. + * @param[in] fmt pointer to a zero-terminated string containing the format text. + * @param[in] ... additional arguments. + */ +void infrared_show_error_message(const InfraredApp* infrared, const char* fmt, ...) + _ATTRIBUTE((__format__(__printf__, 2, 3))); + +/** + * @brief Common received signal callback. + * + * Called when the worker has received a complete infrared signal. + * + * @param[in,out] context pointer to the user-specified context object. + * @param[in] received_signal pointer to the received signal. + */ +void infrared_signal_received_callback(void* context, InfraredWorkerSignal* received_signal); + +/** + * @brief Common text input callback. + * + * Called when the input has been accepted by the user. + * + * @param[in,out] context pointer to the user-specified context object. + */ +void infrared_text_input_callback(void* context); + +/** + * @brief Common popup close callback. + * + * Called when the popup has been closed either by the user or after a timeout. + * + * @param[in,out] context pointer to the user-specified context object. + */ +void infrared_popup_closed_callback(void* context); diff --git a/applications/main/infrared/infrared_brute_force.c b/applications/main/infrared/infrared_brute_force.c index 3ca5c409f79..bb7992ae61c 100644 --- a/applications/main/infrared/infrared_brute_force.c +++ b/applications/main/infrared/infrared_brute_force.c @@ -111,7 +111,7 @@ bool infrared_brute_force_start( return success; } -bool infrared_brute_force_is_started(InfraredBruteForce* brute_force) { +bool infrared_brute_force_is_started(const InfraredBruteForce* brute_force) { return brute_force->is_started; } @@ -129,7 +129,9 @@ void infrared_brute_force_stop(InfraredBruteForce* brute_force) { bool infrared_brute_force_send_next(InfraredBruteForce* brute_force) { furi_assert(brute_force->is_started); const bool success = infrared_signal_search_and_read( - brute_force->current_signal, brute_force->ff, brute_force->current_record_name); + brute_force->current_signal, + brute_force->ff, + furi_string_get_cstr(brute_force->current_record_name)); if(success) { infrared_signal_transmit(brute_force->current_signal); } diff --git a/applications/main/infrared/infrared_brute_force.h b/applications/main/infrared/infrared_brute_force.h index 042d1556b74..33677d2ec18 100644 --- a/applications/main/infrared/infrared_brute_force.h +++ b/applications/main/infrared/infrared_brute_force.h @@ -1,23 +1,110 @@ +/** + * @file infrared_brute_force.h + * @brief Infrared signal brute-forcing library. + * + * The BruteForce library is used to send large quantities of signals, + * sorted by a category. It is used to implement the Universal Remote + * feature. + */ #pragma once #include #include +/** + * @brief InfraredBruteForce opaque type declaration. + */ typedef struct InfraredBruteForce InfraredBruteForce; +/** + * @brief Create a new InfraredBruteForce instance. + * + * @returns pointer to the created instance. + */ InfraredBruteForce* infrared_brute_force_alloc(); + +/** + * @brief Delete an InfraredBruteForce instance. + * + * @param[in,out] brute_force pointer to the instance to be deleted. + */ void infrared_brute_force_free(InfraredBruteForce* brute_force); + +/** + * @brief Set an InfraredBruteForce instance to use a signal database contained in a file. + * + * @param[in,out] brute_force pointer to the instance to be configured. + * @param[in] db_filename pointer to a zero-terminated string containing a full path to the database file. + */ void infrared_brute_force_set_db_filename(InfraredBruteForce* brute_force, const char* db_filename); + +/** + * @brief Build a signal dictionary from a previously set database file. + * + * This function must be called each time after setting the database via + * a infrared_brute_force_set_db_filename() call. + * + * @param[in,out] brute_force pointer to the instance to be updated. + * @returns true on success, false otherwise. + */ bool infrared_brute_force_calculate_messages(InfraredBruteForce* brute_force); + +/** + * @brief Start transmitting signals from a category stored in an InfraredBruteForce's instance dictionary. + * + * @param[in,out] brute_force pointer to the instance to be started. + * @param[in] index index of the signal category in the dictionary. + * @returns true on success, false otherwise. + */ bool infrared_brute_force_start( InfraredBruteForce* brute_force, uint32_t index, uint32_t* record_count); -bool infrared_brute_force_is_started(InfraredBruteForce* brute_force); + +/** + * @brief Determine whether the transmission was started. + * + * @param[in] brute_force pointer to the instance to be tested. + * @returns true if transmission was started, false otherwise. + */ +bool infrared_brute_force_is_started(const InfraredBruteForce* brute_force); + +/** + * @brief Stop transmitting the signals. + * + * @param[in] brute_force pointer to the instance to be stopped. + */ void infrared_brute_force_stop(InfraredBruteForce* brute_force); + +/** + * @brief Send the next signal from the chosen category. + * + * This function is called repeatedly until no more signals are left + * in the chosen signal category. + * + * @warning Transmission must be started first by calling infrared_brute_force_start() + * before calling this function. + * + * @param[in,out] brute_force pointer to the instance to be used. + * @returns true if the next signal existed and could be transmitted, false otherwise. + */ bool infrared_brute_force_send_next(InfraredBruteForce* brute_force); + +/** + * @brief Add a signal category to an InfraredBruteForce instance's dictionary. + * + * @param[in,out] brute_force pointer to the instance to be updated. + * @param[in] index index of the category to be added. + * @param[in] name name of the category to be added. + */ void infrared_brute_force_add_record( InfraredBruteForce* brute_force, uint32_t index, const char* name); + +/** + * @brief Reset an InfraredBruteForce instance. + * + * @param[in,out] brute_force pointer to the instance to be reset. + */ void infrared_brute_force_reset(InfraredBruteForce* brute_force); diff --git a/applications/main/infrared/infrared_cli.c b/applications/main/infrared/infrared_cli.c index 54b5cabab76..c960ffa2856 100644 --- a/applications/main/infrared/infrared_cli.c +++ b/applications/main/infrared/infrared_cli.c @@ -202,7 +202,7 @@ static bool } static bool infrared_cli_decode_raw_signal( - InfraredRawSignal* raw_signal, + const InfraredRawSignal* raw_signal, InfraredDecoderHandler* decoder, FlipperFormat* output_file, const char* signal_name) { @@ -274,7 +274,7 @@ static bool infrared_cli_decode_file(FlipperFormat* input_file, FlipperFormat* o continue; } } - InfraredRawSignal* raw_signal = infrared_signal_get_raw_signal(signal); + const InfraredRawSignal* raw_signal = infrared_signal_get_raw_signal(signal); printf( "Raw signal: %s, %zu samples\r\n", furi_string_get_cstr(tmp), diff --git a/applications/main/infrared/infrared_i.h b/applications/main/infrared/infrared_i.h deleted file mode 100644 index 6fb6a65c7b9..00000000000 --- a/applications/main/infrared/infrared_i.h +++ /dev/null @@ -1,146 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include - -#include - -#include "infrared.h" -#include "infrared_remote.h" -#include "infrared_brute_force.h" -#include "infrared_custom_event.h" - -#include "scenes/infrared_scene.h" -#include "views/infrared_progress_view.h" -#include "views/infrared_debug_view.h" -#include "views/infrared_move_view.h" - -#include "rpc/rpc_app.h" - -#define INFRARED_FILE_NAME_SIZE 100 -#define INFRARED_TEXT_STORE_NUM 2 -#define INFRARED_TEXT_STORE_SIZE 128 - -#define INFRARED_MAX_BUTTON_NAME_LENGTH 22 -#define INFRARED_MAX_REMOTE_NAME_LENGTH 22 - -#define INFRARED_APP_FOLDER ANY_PATH("infrared") -#define INFRARED_APP_EXTENSION ".ir" - -#define INFRARED_DEFAULT_REMOTE_NAME "Remote" -#define INFRARED_LOG_TAG "InfraredApp" - -typedef enum { - InfraredButtonIndexNone = -1, -} InfraredButtonIndex; - -typedef enum { - InfraredEditTargetNone, - InfraredEditTargetRemote, - InfraredEditTargetButton, -} InfraredEditTarget; - -typedef enum { - InfraredEditModeNone, - InfraredEditModeRename, - InfraredEditModeDelete, -} InfraredEditMode; - -typedef struct { - bool is_learning_new_remote; - bool is_debug_enabled; - bool is_transmitting; - InfraredEditTarget edit_target : 8; - InfraredEditMode edit_mode : 8; - int32_t current_button_index; - int32_t current_button_index_move_orig; - uint32_t last_transmit_time; -} InfraredAppState; - -struct Infrared { - SceneManager* scene_manager; - ViewDispatcher* view_dispatcher; - - Gui* gui; - Storage* storage; - DialogsApp* dialogs; - NotificationApp* notifications; - InfraredWorker* worker; - InfraredRemote* remote; - InfraredSignal* received_signal; - InfraredBruteForce* brute_force; - - Submenu* submenu; - TextInput* text_input; - DialogEx* dialog_ex; - ButtonMenu* button_menu; - Popup* popup; - - ViewStack* view_stack; - InfraredDebugView* debug_view; - InfraredMoveView* move_view; - - ButtonPanel* button_panel; - Loading* loading; - InfraredProgressView* progress; - - FuriString* file_path; - char text_store[INFRARED_TEXT_STORE_NUM][INFRARED_TEXT_STORE_SIZE + 1]; - InfraredAppState app_state; - - void* rpc_ctx; -}; - -typedef enum { - InfraredViewSubmenu, - InfraredViewTextInput, - InfraredViewDialogEx, - InfraredViewButtonMenu, - InfraredViewPopup, - InfraredViewStack, - InfraredViewDebugView, - InfraredViewMove, -} InfraredView; - -typedef enum { - InfraredNotificationMessageSuccess, - InfraredNotificationMessageGreenOn, - InfraredNotificationMessageGreenOff, - InfraredNotificationMessageYellowOn, - InfraredNotificationMessageYellowOff, - InfraredNotificationMessageBlinkStartRead, - InfraredNotificationMessageBlinkStartSend, - InfraredNotificationMessageBlinkStop, -} InfraredNotificationMessage; - -bool infrared_add_remote_with_button(Infrared* infrared, const char* name, InfraredSignal* signal); -bool infrared_rename_current_remote(Infrared* infrared, const char* name); -void infrared_tx_start_signal(Infrared* infrared, InfraredSignal* signal); -void infrared_tx_start_button_index(Infrared* infrared, size_t button_index); -void infrared_tx_start_received(Infrared* infrared); -void infrared_tx_stop(Infrared* infrared); -void infrared_text_store_set(Infrared* infrared, uint32_t bank, const char* text, ...); -void infrared_text_store_clear(Infrared* infrared, uint32_t bank); -void infrared_play_notification_message(Infrared* infrared, uint32_t message); -void infrared_show_loading_popup(Infrared* infrared, bool show); - -void infrared_signal_received_callback(void* context, InfraredWorkerSignal* received_signal); -void infrared_text_input_callback(void* context); -void infrared_popup_closed_callback(void* context); diff --git a/applications/main/infrared/infrared_remote.c b/applications/main/infrared/infrared_remote.c index 70d1b59ef38..5b241fe9f60 100644 --- a/applications/main/infrared/infrared_remote.c +++ b/applications/main/infrared/infrared_remote.c @@ -1,197 +1,428 @@ #include "infrared_remote.h" -#include -#include -#include #include + +#include #include #include -#include #define TAG "InfraredRemote" -ARRAY_DEF(InfraredButtonArray, InfraredRemoteButton*, M_PTR_OPLIST); +#define INFRARED_FILE_HEADER "IR signals file" +#define INFRARED_FILE_VERSION (1) + +ARRAY_DEF(StringArray, const char*, M_CSTR_DUP_OPLIST); //-V575 struct InfraredRemote { - InfraredButtonArray_t buttons; + StringArray_t signal_names; FuriString* name; FuriString* path; }; -static void infrared_remote_clear_buttons(InfraredRemote* remote) { - InfraredButtonArray_it_t it; - for(InfraredButtonArray_it(it, remote->buttons); !InfraredButtonArray_end_p(it); - InfraredButtonArray_next(it)) { - infrared_remote_button_free(*InfraredButtonArray_cref(it)); - } - InfraredButtonArray_reset(remote->buttons); -} +typedef struct { + InfraredRemote* remote; + FlipperFormat* ff_in; + FlipperFormat* ff_out; + FuriString* signal_name; + InfraredSignal* signal; + size_t signal_index; +} InfraredBatch; + +typedef struct { + size_t signal_index; + const char* signal_name; + const InfraredSignal* signal; +} InfraredBatchTarget; + +typedef bool ( + *InfraredBatchCallback)(const InfraredBatch* batch, const InfraredBatchTarget* target); InfraredRemote* infrared_remote_alloc() { InfraredRemote* remote = malloc(sizeof(InfraredRemote)); - InfraredButtonArray_init(remote->buttons); + StringArray_init(remote->signal_names); remote->name = furi_string_alloc(); remote->path = furi_string_alloc(); return remote; } void infrared_remote_free(InfraredRemote* remote) { - infrared_remote_clear_buttons(remote); - InfraredButtonArray_clear(remote->buttons); + StringArray_clear(remote->signal_names); furi_string_free(remote->path); furi_string_free(remote->name); free(remote); } void infrared_remote_reset(InfraredRemote* remote) { - infrared_remote_clear_buttons(remote); + StringArray_reset(remote->signal_names); furi_string_reset(remote->name); furi_string_reset(remote->path); } -void infrared_remote_set_name(InfraredRemote* remote, const char* name) { - furi_string_set(remote->name, name); -} - -const char* infrared_remote_get_name(InfraredRemote* remote) { +const char* infrared_remote_get_name(const InfraredRemote* remote) { return furi_string_get_cstr(remote->name); } -void infrared_remote_set_path(InfraredRemote* remote, const char* path) { +static void infrared_remote_set_path(InfraredRemote* remote, const char* path) { furi_string_set(remote->path, path); + path_extract_filename(remote->path, remote->name, true); } -const char* infrared_remote_get_path(InfraredRemote* remote) { +const char* infrared_remote_get_path(const InfraredRemote* remote) { return furi_string_get_cstr(remote->path); } -size_t infrared_remote_get_button_count(InfraredRemote* remote) { - return InfraredButtonArray_size(remote->buttons); +size_t infrared_remote_get_signal_count(const InfraredRemote* remote) { + return StringArray_size(remote->signal_names); +} + +const char* infrared_remote_get_signal_name(const InfraredRemote* remote, size_t index) { + furi_assert(index < infrared_remote_get_signal_count(remote)); + return *StringArray_cget(remote->signal_names, index); } -InfraredRemoteButton* infrared_remote_get_button(InfraredRemote* remote, size_t index) { - furi_assert(index < InfraredButtonArray_size(remote->buttons)); - return *InfraredButtonArray_get(remote->buttons, index); +bool infrared_remote_load_signal( + const InfraredRemote* remote, + InfraredSignal* signal, + size_t index) { + furi_assert(index < infrared_remote_get_signal_count(remote)); + + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* ff = flipper_format_buffered_file_alloc(storage); + + bool success = false; + + do { + const char* path = furi_string_get_cstr(remote->path); + if(!flipper_format_buffered_file_open_existing(ff, path)) break; + + const char* name = infrared_remote_get_signal_name(remote, index); + + if(!infrared_signal_search_and_read(signal, ff, name)) { + FURI_LOG_E(TAG, "Failed to load signal '%s' from file '%s'", name, path); + break; + } + + success = true; + } while(false); + + flipper_format_free(ff); + furi_record_close(RECORD_STORAGE); + + return success; } -bool infrared_remote_find_button_by_name(InfraredRemote* remote, const char* name, size_t* index) { - for(size_t i = 0; i < InfraredButtonArray_size(remote->buttons); i++) { - InfraredRemoteButton* button = *InfraredButtonArray_get(remote->buttons, i); - if(!strcmp(infrared_remote_button_get_name(button), name)) { +bool infrared_remote_get_signal_index( + const InfraredRemote* remote, + const char* name, + size_t* index) { + uint32_t i = 0; + StringArray_it_t it; + + for(StringArray_it(it, remote->signal_names); !StringArray_end_p(it); + StringArray_next(it), ++i) { + if(strcmp(*StringArray_cref(it), name) == 0) { *index = i; return true; } } + return false; } -bool infrared_remote_add_button(InfraredRemote* remote, const char* name, InfraredSignal* signal) { - InfraredRemoteButton* button = infrared_remote_button_alloc(); - infrared_remote_button_set_name(button, name); - infrared_remote_button_set_signal(button, signal); - InfraredButtonArray_push_back(remote->buttons, button); - return infrared_remote_store(remote); +bool infrared_remote_append_signal( + InfraredRemote* remote, + const InfraredSignal* signal, + const char* name) { + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* ff = flipper_format_file_alloc(storage); + + bool success = false; + const char* path = furi_string_get_cstr(remote->path); + + do { + if(!flipper_format_file_open_append(ff, path)) break; + if(!infrared_signal_save(signal, ff, name)) break; + + StringArray_push_back(remote->signal_names, name); + success = true; + } while(false); + + flipper_format_free(ff); + furi_record_close(RECORD_STORAGE); + + return success; +} + +static bool infrared_remote_batch_start( + InfraredRemote* remote, + InfraredBatchCallback batch_callback, + const InfraredBatchTarget* target) { + FuriString* tmp = furi_string_alloc(); + Storage* storage = furi_record_open(RECORD_STORAGE); + + InfraredBatch batch_context = { + .remote = remote, + .ff_in = flipper_format_buffered_file_alloc(storage), + .ff_out = flipper_format_buffered_file_alloc(storage), + .signal_name = furi_string_alloc(), + .signal = infrared_signal_alloc(), + .signal_index = 0, + }; + + const char* path_in = furi_string_get_cstr(remote->path); + const char* path_out; + + FS_Error status; + + do { + furi_string_printf(tmp, "%s.temp%08x.swp", path_in, rand()); + path_out = furi_string_get_cstr(tmp); + status = storage_common_stat(storage, path_out, NULL); + } while(status == FSE_OK || status == FSE_EXIST); + + bool success = false; + + do { + if(!flipper_format_buffered_file_open_existing(batch_context.ff_in, path_in)) break; + if(!flipper_format_buffered_file_open_always(batch_context.ff_out, path_out)) break; + if(!flipper_format_write_header_cstr( + batch_context.ff_out, INFRARED_FILE_HEADER, INFRARED_FILE_VERSION)) + break; + + const size_t signal_count = infrared_remote_get_signal_count(remote); + + for(; batch_context.signal_index < signal_count; ++batch_context.signal_index) { + if(!infrared_signal_read( + batch_context.signal, batch_context.ff_in, batch_context.signal_name)) + break; + if(!batch_callback(&batch_context, target)) break; + } + + if(batch_context.signal_index != signal_count) break; + + if(!flipper_format_buffered_file_close(batch_context.ff_out)) break; + if(!flipper_format_buffered_file_close(batch_context.ff_in)) break; + + const FS_Error status = storage_common_rename(storage, path_out, path_in); + success = (status == FSE_OK || status == FSE_EXIST); + } while(false); + + infrared_signal_free(batch_context.signal); + furi_string_free(batch_context.signal_name); + flipper_format_free(batch_context.ff_out); + flipper_format_free(batch_context.ff_in); + furi_string_free(tmp); + + furi_record_close(RECORD_STORAGE); + + return success; +} + +static bool infrared_remote_insert_signal_callback( + const InfraredBatch* batch, + const InfraredBatchTarget* target) { + // Insert a signal under the specified index + if(batch->signal_index == target->signal_index) { + if(!infrared_signal_save(target->signal, batch->ff_out, target->signal_name)) return false; + StringArray_push_at( + batch->remote->signal_names, target->signal_index, target->signal_name); + } + + // Write the rest normally + return infrared_signal_save( + batch->signal, batch->ff_out, furi_string_get_cstr(batch->signal_name)); +} + +bool infrared_remote_insert_signal( + InfraredRemote* remote, + const InfraredSignal* signal, + const char* name, + size_t index) { + if(index >= infrared_remote_get_signal_count(remote)) { + return infrared_remote_append_signal(remote, signal, name); + } + + const InfraredBatchTarget insert_target = { + .signal_index = index, + .signal_name = name, + .signal = signal, + }; + + return infrared_remote_batch_start( + remote, infrared_remote_insert_signal_callback, &insert_target); +} + +static bool infrared_remote_rename_signal_callback( + const InfraredBatch* batch, + const InfraredBatchTarget* target) { + const char* signal_name; + + if(batch->signal_index == target->signal_index) { + // Rename the signal at requested index + signal_name = target->signal_name; + StringArray_set_at(batch->remote->signal_names, batch->signal_index, signal_name); + } else { + // Use the original name otherwise + signal_name = furi_string_get_cstr(batch->signal_name); + } + + return infrared_signal_save(batch->signal, batch->ff_out, signal_name); } -bool infrared_remote_rename_button(InfraredRemote* remote, const char* new_name, size_t index) { - furi_assert(index < InfraredButtonArray_size(remote->buttons)); - InfraredRemoteButton* button = *InfraredButtonArray_get(remote->buttons, index); - infrared_remote_button_set_name(button, new_name); - return infrared_remote_store(remote); +bool infrared_remote_rename_signal(InfraredRemote* remote, size_t index, const char* new_name) { + furi_assert(index < infrared_remote_get_signal_count(remote)); + + const InfraredBatchTarget rename_target = { + .signal_index = index, + .signal_name = new_name, + .signal = NULL, + }; + + return infrared_remote_batch_start( + remote, infrared_remote_rename_signal_callback, &rename_target); +} + +static bool infrared_remote_delete_signal_callback( + const InfraredBatch* batch, + const InfraredBatchTarget* target) { + if(batch->signal_index == target->signal_index) { + // Do not save the signal to be deleted, remove it from the signal name list instead + StringArray_remove_v( + batch->remote->signal_names, batch->signal_index, batch->signal_index + 1); + } else { + // Pass other signals through + return infrared_signal_save( + batch->signal, batch->ff_out, furi_string_get_cstr(batch->signal_name)); + } + + return true; } -bool infrared_remote_delete_button(InfraredRemote* remote, size_t index) { - furi_assert(index < InfraredButtonArray_size(remote->buttons)); - InfraredRemoteButton* button; - InfraredButtonArray_pop_at(&button, remote->buttons, index); - infrared_remote_button_free(button); - return infrared_remote_store(remote); +bool infrared_remote_delete_signal(InfraredRemote* remote, size_t index) { + furi_assert(index < infrared_remote_get_signal_count(remote)); + + const InfraredBatchTarget delete_target = { + .signal_index = index, + .signal_name = NULL, + .signal = NULL, + }; + + return infrared_remote_batch_start( + remote, infrared_remote_delete_signal_callback, &delete_target); } -void infrared_remote_move_button(InfraredRemote* remote, size_t index_orig, size_t index_dest) { - furi_assert(index_orig < InfraredButtonArray_size(remote->buttons)); - furi_assert(index_dest < InfraredButtonArray_size(remote->buttons)); +bool infrared_remote_move_signal(InfraredRemote* remote, size_t index, size_t new_index) { + const size_t signal_count = infrared_remote_get_signal_count(remote); + furi_assert(index < signal_count); + furi_assert(new_index < signal_count); + + if(index == new_index) return true; + + InfraredSignal* signal = infrared_signal_alloc(); + char* signal_name = strdup(infrared_remote_get_signal_name(remote, index)); + + bool success = false; + + do { + if(!infrared_remote_load_signal(remote, signal, index)) break; + if(!infrared_remote_delete_signal(remote, index)) break; + if(!infrared_remote_insert_signal(remote, signal, signal_name, new_index)) break; + + success = true; + } while(false); + + free(signal_name); + infrared_signal_free(signal); - InfraredRemoteButton* button; - InfraredButtonArray_pop_at(&button, remote->buttons, index_orig); - InfraredButtonArray_push_at(remote->buttons, index_dest, button); + return success; } -bool infrared_remote_store(InfraredRemote* remote) { +bool infrared_remote_create(InfraredRemote* remote, const char* path) { + FURI_LOG_I(TAG, "Creating new file: '%s'", path); + + infrared_remote_reset(remote); + infrared_remote_set_path(remote, path); + Storage* storage = furi_record_open(RECORD_STORAGE); FlipperFormat* ff = flipper_format_file_alloc(storage); - const char* path = furi_string_get_cstr(remote->path); - FURI_LOG_I(TAG, "store file: \'%s\'", path); + bool success = false; - bool success = flipper_format_file_open_always(ff, path) && - flipper_format_write_header_cstr(ff, "IR signals file", 1); - if(success) { - InfraredButtonArray_it_t it; - for(InfraredButtonArray_it(it, remote->buttons); !InfraredButtonArray_end_p(it); - InfraredButtonArray_next(it)) { - InfraredRemoteButton* button = *InfraredButtonArray_cref(it); - success = infrared_signal_save( - infrared_remote_button_get_signal(button), - ff, - infrared_remote_button_get_name(button)); - if(!success) { - break; - } - } - } + do { + if(!flipper_format_file_open_always(ff, path)) break; + if(!flipper_format_write_header_cstr(ff, INFRARED_FILE_HEADER, INFRARED_FILE_VERSION)) + break; + + success = true; + } while(false); flipper_format_free(ff); furi_record_close(RECORD_STORAGE); + return success; } -bool infrared_remote_load(InfraredRemote* remote, FuriString* path) { +bool infrared_remote_load(InfraredRemote* remote, const char* path) { + FURI_LOG_I(TAG, "Loading file: '%s'", path); + Storage* storage = furi_record_open(RECORD_STORAGE); FlipperFormat* ff = flipper_format_buffered_file_alloc(storage); - FuriString* buf; - buf = furi_string_alloc(); - - FURI_LOG_I(TAG, "load file: \'%s\'", furi_string_get_cstr(path)); + FuriString* tmp = furi_string_alloc(); bool success = false; do { - if(!flipper_format_buffered_file_open_existing(ff, furi_string_get_cstr(path))) break; + if(!flipper_format_buffered_file_open_existing(ff, path)) break; + uint32_t version; - if(!flipper_format_read_header(ff, buf, &version)) break; - if(!furi_string_equal(buf, "IR signals file") || (version != 1)) break; - - path_extract_filename(path, buf, true); - infrared_remote_clear_buttons(remote); - infrared_remote_set_name(remote, furi_string_get_cstr(buf)); - infrared_remote_set_path(remote, furi_string_get_cstr(path)); - - for(bool can_read = true; can_read;) { - InfraredRemoteButton* button = infrared_remote_button_alloc(); - can_read = infrared_signal_read(infrared_remote_button_get_signal(button), ff, buf); - if(can_read) { - infrared_remote_button_set_name(button, furi_string_get_cstr(buf)); - InfraredButtonArray_push_back(remote->buttons, button); - } else { - infrared_remote_button_free(button); - } + if(!flipper_format_read_header(ff, tmp, &version)) break; + + if(!furi_string_equal(tmp, INFRARED_FILE_HEADER) || (version != INFRARED_FILE_VERSION)) + break; + + infrared_remote_set_path(remote, path); + StringArray_reset(remote->signal_names); + + while(infrared_signal_read_name(ff, tmp)) { + StringArray_push_back(remote->signal_names, furi_string_get_cstr(tmp)); } + success = true; } while(false); - furi_string_free(buf); + furi_string_free(tmp); flipper_format_free(ff); furi_record_close(RECORD_STORAGE); + return success; } -bool infrared_remote_remove(InfraredRemote* remote) { +bool infrared_remote_rename(InfraredRemote* remote, const char* new_path) { + const char* old_path = infrared_remote_get_path(remote); + Storage* storage = furi_record_open(RECORD_STORAGE); + const FS_Error status = storage_common_rename(storage, old_path, new_path); + furi_record_close(RECORD_STORAGE); - FS_Error status = storage_common_remove(storage, furi_string_get_cstr(remote->path)); - infrared_remote_reset(remote); + const bool success = (status == FSE_OK || status == FSE_EXIST); + + if(success) { + infrared_remote_set_path(remote, new_path); + } + + return success; +} +bool infrared_remote_remove(InfraredRemote* remote) { + Storage* storage = furi_record_open(RECORD_STORAGE); + const FS_Error status = storage_common_remove(storage, infrared_remote_get_path(remote)); furi_record_close(RECORD_STORAGE); - return (status == FSE_OK || status == FSE_NOT_EXIST); + + const bool success = (status == FSE_OK || status == FSE_NOT_EXIST); + + if(success) { + infrared_remote_reset(remote); + } + + return success; } diff --git a/applications/main/infrared/infrared_remote.h b/applications/main/infrared/infrared_remote.h index 47aa77e2ef8..7477cd3b5af 100644 --- a/applications/main/infrared/infrared_remote.h +++ b/applications/main/infrared/infrared_remote.h @@ -1,30 +1,229 @@ +/** + * @file infrared_remote.h + * @brief Infrared remote library. + * + * An infrared remote contains zero or more infrared signals which + * have a (possibly non-unique) name each. + * + * The current implementation does load only the names into the memory, + * while the signals themselves are loaded on-demand one by one. In theory, + * this should allow for quite large remotes with relatively bulky signals. + */ #pragma once -#include - -#include "infrared_remote_button.h" +#include "infrared_signal.h" +/** + * @brief InfraredRemote opaque type declaration. + */ typedef struct InfraredRemote InfraredRemote; +/** + * @brief Create a new InfraredRemote instance. + * + * @returns pointer to the created instance. + */ InfraredRemote* infrared_remote_alloc(); + +/** + * @brief Delete an InfraredRemote instance. + * + * @param[in,out] remote pointer to the instance to be deleted. + */ void infrared_remote_free(InfraredRemote* remote); + +/** + * @brief Reset an InfraredRemote instance. + * + * Resetting a remote clears its signal name list and + * the associated file path. + * + * @param[in,out] remote pointer to the instance to be deleted. + */ void infrared_remote_reset(InfraredRemote* remote); -void infrared_remote_set_name(InfraredRemote* remote, const char* name); -const char* infrared_remote_get_name(InfraredRemote* remote); +/** + * @brief Get an InfraredRemote instance's name. + * + * The name is deduced from the file path. + * + * The return value remains valid unless one of the following functions is called: + * - infrared_remote_reset() + * - infrared_remote_load() + * - infrared_remote_create() + * + * @param[in] remote pointer to the instance to be queried. + * @returns pointer to a zero-terminated string containing the name. + */ +const char* infrared_remote_get_name(const InfraredRemote* remote); + +/** + * @brief Get an InfraredRemote instance's file path. + * + * Same return value validity considerations as infrared_remote_get_name(). + * + * @param[in] remote pointer to the instance to be queried. + * @returns pointer to a zero-terminated string containing the path. + */ +const char* infrared_remote_get_path(const InfraredRemote* remote); + +/** + * @brief Get the number of signals listed in an InfraredRemote instance. + * + * @param[in] remote pointer to the instance to be queried. + * @returns number of signals, zero or more + */ +size_t infrared_remote_get_signal_count(const InfraredRemote* remote); + +/** + * @brief Get the name of a signal listed in an InfraredRemote instance. + * + * @param[in] remote pointer to the instance to be queried. + * @param[in] index index of the signal in question. Must be less than the total signal count. + */ +const char* infrared_remote_get_signal_name(const InfraredRemote* remote, size_t index); + +/** + * @brief Get the index of a signal listed in an InfraredRemote instance by its name. + * + * @param[in] remote pointer to the instance to be queried. + * @param[in] name pointer to a zero-terminated string containig the name of the signal in question. + * @param[out] index pointer to the variable to hold the signal index. + * @returns true if a signal with the given name was found, false otherwise. + */ +bool infrared_remote_get_signal_index( + const InfraredRemote* remote, + const char* name, + size_t* index); + +/** + * @brief Load a signal listed in an InfraredRemote instance. + * + * As mentioned above, the signals are loaded on-demand. The user code must call this function + * each time it wants to interact with a new signal. + * + * @param[in] remote pointer to the instance to load from. + * @param[out] signal pointer to the signal to load into. Must be allocated. + * @param[in] index index of the signal to be loaded. Must be less than the total signal count. + * @return true if the signal was successfully loaded, false otherwise. + */ +bool infrared_remote_load_signal( + const InfraredRemote* remote, + InfraredSignal* signal, + size_t index); + +/** + * @brief Append a signal to the file associated with an InfraredRemote instance. + * + * The file path must be somehow initialised first by calling either infrared_remote_load() or + * infrared_remote_create(). As the name suggests, the signal will be put in the end of the file. + * + * @param[in,out] remote pointer to the instance to append to. + * @param[in] signal pointer to the signal to be appended. + * @param[in] name pointer to a zero-terminated string containing the name of the signal. + * @returns true if the signal was successfully appended, false otherwise. + */ +bool infrared_remote_append_signal( + InfraredRemote* remote, + const InfraredSignal* signal, + const char* name); + +/** + * @brief Insert a signal to the file associated with an InfraredRemote instance. + * + * Same behaviour as infrared_remote_append_signal(), but the user code can decide where to + * put the signal in the file. + * + * Index values equal to or greater than the total signal count will result in behaviour + * identical to infrared_remote_append_signal(). + * + * @param[in,out] remote pointer to the instance to insert to. + * @param[in] signal pointer to the signal to be inserted. + * @param[in] name pointer to a zero-terminated string containing the name of the signal. + * @param[in] index the index under which the signal shall be inserted. + * @returns true if the signal was successfully inserted, false otherwise. + */ +bool infrared_remote_insert_signal( + InfraredRemote* remote, + const InfraredSignal* signal, + const char* name, + size_t index); + +/** + * @brief Rename a signal in the file associated with an InfraredRemote instance. + * + * Only changes the signal's name, but neither its position nor contents. + * + * @param[in,out] remote pointer to the instance to be modified. + * @param[in] index index of the signal to be renamed. Must be less than the total signal count. + * @param[in] new_name pointer to a zero-terminated string containig the signal's new name. + * @returns true if the signal was successfully renamed, false otherwise. + */ +bool infrared_remote_rename_signal(InfraredRemote* remote, size_t index, const char* new_name); + +/** + * @brief Change a signal's position in the file associated with an InfraredRemote instance. + * + * Only changes the signal's position (index), but neither its name nor contents. + * + * @param[in,out] remote pointer to the instance to be modified. + * @param[in] index index of the signal to be moved. Must be less than the total signal count. + * @param[in] new_index index of the signal to be moved. Must be less than the total signal count. + */ +bool infrared_remote_move_signal(InfraredRemote* remote, size_t index, size_t new_index); + +/** + * @brief Delete a signal in the file associated with an InfraredRemote instance. + * + * @param[in,out] remote pointer to the instance to be modified. + * @param[in] index index of the signal to be deleted. Must be less than the total signal count. + * @returns true if the signal was successfully deleted, false otherwise. + */ +bool infrared_remote_delete_signal(InfraredRemote* remote, size_t index); -void infrared_remote_set_path(InfraredRemote* remote, const char* path); -const char* infrared_remote_get_path(InfraredRemote* remote); +/** + * @brief Create a new file and associate it with an InfraredRemote instance. + * + * The instance will be reset and given a new empty file with just the header. + * + * @param[in,out] remote pointer to the instance to be assigned with a new file. + * @param[in] path pointer to a zero-terminated string containing the full file path. + * @returns true if the file was successfully created, false otherwise. + */ +bool infrared_remote_create(InfraredRemote* remote, const char* path); -size_t infrared_remote_get_button_count(InfraredRemote* remote); -InfraredRemoteButton* infrared_remote_get_button(InfraredRemote* remote, size_t index); -bool infrared_remote_find_button_by_name(InfraredRemote* remote, const char* name, size_t* index); +/** + * @brief Associate an InfraredRemote instance with a file and load the signal names from it. + * + * The instance will be reset and fill its signal name list from the given file. + * The file must already exist and be valid. + * + * @param[in,out] remote pointer to the instance to be assigned with an existing file. + * @param[in] path pointer to a zero-terminated string containing the full file path. + * @returns true if the file was successfully loaded, false otherwise. + */ +bool infrared_remote_load(InfraredRemote* remote, const char* path); -bool infrared_remote_add_button(InfraredRemote* remote, const char* name, InfraredSignal* signal); -bool infrared_remote_rename_button(InfraredRemote* remote, const char* new_name, size_t index); -bool infrared_remote_delete_button(InfraredRemote* remote, size_t index); -void infrared_remote_move_button(InfraredRemote* remote, size_t index_orig, size_t index_dest); +/** + * @brief Rename the file associated with an InfraredRemote instance. + * + * Only renames the file, no signals are added, moved or deleted. + * + * @param[in,out] remote pointer to the instance to be modified. + * @param[in] new_path pointer to a zero-terminated string containing the new full file path. + * @returns true if the file was successfully renamed, false otherwise. + */ +bool infrared_remote_rename(InfraredRemote* remote, const char* new_path); -bool infrared_remote_store(InfraredRemote* remote); -bool infrared_remote_load(InfraredRemote* remote, FuriString* path); +/** + * @brief Remove the file associated with an InfraredRemote instance. + * + * This operation is irreversible and fully deletes the remote file + * from the underlying filesystem. + * After calling this function, the instance becomes invalid until + * infrared_remote_create() or infrared_remote_load() are successfully executed. + * + * @param[in,out] remote pointer to the instance to be modified. + * @returns true if the file was successfully removed, false otherwise. + */ bool infrared_remote_remove(InfraredRemote* remote); diff --git a/applications/main/infrared/infrared_remote_button.c b/applications/main/infrared/infrared_remote_button.c deleted file mode 100644 index 1f6315ec528..00000000000 --- a/applications/main/infrared/infrared_remote_button.c +++ /dev/null @@ -1,37 +0,0 @@ -#include "infrared_remote_button.h" - -#include - -struct InfraredRemoteButton { - FuriString* name; - InfraredSignal* signal; -}; - -InfraredRemoteButton* infrared_remote_button_alloc() { - InfraredRemoteButton* button = malloc(sizeof(InfraredRemoteButton)); - button->name = furi_string_alloc(); - button->signal = infrared_signal_alloc(); - return button; -} - -void infrared_remote_button_free(InfraredRemoteButton* button) { - furi_string_free(button->name); - infrared_signal_free(button->signal); - free(button); -} - -void infrared_remote_button_set_name(InfraredRemoteButton* button, const char* name) { - furi_string_set(button->name, name); -} - -const char* infrared_remote_button_get_name(InfraredRemoteButton* button) { - return furi_string_get_cstr(button->name); -} - -void infrared_remote_button_set_signal(InfraredRemoteButton* button, InfraredSignal* signal) { - infrared_signal_set_signal(button->signal, signal); -} - -InfraredSignal* infrared_remote_button_get_signal(InfraredRemoteButton* button) { - return button->signal; -} diff --git a/applications/main/infrared/infrared_remote_button.h b/applications/main/infrared/infrared_remote_button.h deleted file mode 100644 index f25b759b566..00000000000 --- a/applications/main/infrared/infrared_remote_button.h +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - -#include "infrared_signal.h" - -typedef struct InfraredRemoteButton InfraredRemoteButton; - -InfraredRemoteButton* infrared_remote_button_alloc(); -void infrared_remote_button_free(InfraredRemoteButton* button); - -void infrared_remote_button_set_name(InfraredRemoteButton* button, const char* name); -const char* infrared_remote_button_get_name(InfraredRemoteButton* button); - -void infrared_remote_button_set_signal(InfraredRemoteButton* button, InfraredSignal* signal); -InfraredSignal* infrared_remote_button_get_signal(InfraredRemoteButton* button); diff --git a/applications/main/infrared/infrared_signal.c b/applications/main/infrared/infrared_signal.c index 9154dfbf68e..c73e4db98da 100644 --- a/applications/main/infrared/infrared_signal.c +++ b/applications/main/infrared/infrared_signal.c @@ -8,6 +8,8 @@ #define TAG "InfraredSignal" +#define INFRARED_SIGNAL_NAME_KEY "name" + struct InfraredSignal { bool is_raw; union { @@ -24,7 +26,7 @@ static void infrared_signal_clear_timings(InfraredSignal* signal) { } } -static bool infrared_signal_is_message_valid(InfraredMessage* message) { +static bool infrared_signal_is_message_valid(const InfraredMessage* message) { if(!infrared_is_protocol_valid(message->protocol)) { FURI_LOG_E(TAG, "Unknown protocol"); return false; @@ -57,7 +59,7 @@ static bool infrared_signal_is_message_valid(InfraredMessage* message) { return true; } -static bool infrared_signal_is_raw_valid(InfraredRawSignal* raw) { +static bool infrared_signal_is_raw_valid(const InfraredRawSignal* raw) { if((raw->frequency > INFRARED_MAX_FREQUENCY) || (raw->frequency < INFRARED_MIN_FREQUENCY)) { FURI_LOG_E( TAG, @@ -83,7 +85,8 @@ static bool infrared_signal_is_raw_valid(InfraredRawSignal* raw) { return true; } -static inline bool infrared_signal_save_message(InfraredMessage* message, FlipperFormat* ff) { +static inline bool + infrared_signal_save_message(const InfraredMessage* message, FlipperFormat* ff) { const char* protocol_name = infrared_get_protocol_name(message->protocol); return flipper_format_write_string_cstr(ff, "type", "parsed") && flipper_format_write_string_cstr(ff, "protocol", protocol_name) && @@ -91,7 +94,7 @@ static inline bool infrared_signal_save_message(InfraredMessage* message, Flippe flipper_format_write_hex(ff, "command", (uint8_t*)&message->command, 4); } -static inline bool infrared_signal_save_raw(InfraredRawSignal* raw, FlipperFormat* ff) { +static inline bool infrared_signal_save_raw(const InfraredRawSignal* raw, FlipperFormat* ff) { furi_assert(raw->timings_size <= MAX_TIMINGS_AMOUNT); return flipper_format_write_string_cstr(ff, "type", "raw") && flipper_format_write_uint32(ff, "frequency", &raw->frequency, 1) && @@ -180,11 +183,11 @@ void infrared_signal_free(InfraredSignal* signal) { free(signal); } -bool infrared_signal_is_raw(InfraredSignal* signal) { +bool infrared_signal_is_raw(const InfraredSignal* signal) { return signal->is_raw; } -bool infrared_signal_is_valid(InfraredSignal* signal) { +bool infrared_signal_is_valid(const InfraredSignal* signal) { return signal->is_raw ? infrared_signal_is_raw_valid(&signal->payload.raw) : infrared_signal_is_message_valid(&signal->payload.message); } @@ -218,7 +221,7 @@ void infrared_signal_set_raw_signal( memcpy(signal->payload.raw.timings, timings, timings_size * sizeof(uint32_t)); } -InfraredRawSignal* infrared_signal_get_raw_signal(InfraredSignal* signal) { +const InfraredRawSignal* infrared_signal_get_raw_signal(const InfraredSignal* signal) { furi_assert(signal->is_raw); return &signal->payload.raw; } @@ -230,14 +233,14 @@ void infrared_signal_set_message(InfraredSignal* signal, const InfraredMessage* signal->payload.message = *message; } -InfraredMessage* infrared_signal_get_message(InfraredSignal* signal) { +const InfraredMessage* infrared_signal_get_message(const InfraredSignal* signal) { furi_assert(!signal->is_raw); return &signal->payload.message; } -bool infrared_signal_save(InfraredSignal* signal, FlipperFormat* ff, const char* name) { +bool infrared_signal_save(const InfraredSignal* signal, FlipperFormat* ff, const char* name) { if(!flipper_format_write_comment_cstr(ff, "") || - !flipper_format_write_string_cstr(ff, "name", name)) { + !flipper_format_write_string_cstr(ff, INFRARED_SIGNAL_NAME_KEY, name)) { return false; } else if(signal->is_raw) { return infrared_signal_save_raw(&signal->payload.raw, ff); @@ -247,33 +250,30 @@ bool infrared_signal_save(InfraredSignal* signal, FlipperFormat* ff, const char* } bool infrared_signal_read(InfraredSignal* signal, FlipperFormat* ff, FuriString* name) { - FuriString* tmp = furi_string_alloc(); - bool success = false; do { - if(!flipper_format_read_string(ff, "name", tmp)) break; - furi_string_set(name, tmp); + if(!infrared_signal_read_name(ff, name)) break; if(!infrared_signal_read_body(signal, ff)) break; - success = true; - } while(0); - furi_string_free(tmp); + success = true; //-V779 + } while(false); + return success; } -bool infrared_signal_search_and_read( - InfraredSignal* signal, - FlipperFormat* ff, - const FuriString* name) { +bool infrared_signal_read_name(FlipperFormat* ff, FuriString* name) { + return flipper_format_read_string(ff, INFRARED_SIGNAL_NAME_KEY, name); +} + +bool infrared_signal_search_and_read(InfraredSignal* signal, FlipperFormat* ff, const char* name) { bool success = false; FuriString* tmp = furi_string_alloc(); do { bool is_name_found = false; - while(flipper_format_read_string(ff, "name", tmp)) { - is_name_found = furi_string_equal(name, tmp); - if(is_name_found) break; + while(!is_name_found && infrared_signal_read_name(ff, tmp)) { //-V560 + is_name_found = furi_string_equal(tmp, name); } if(!is_name_found) break; //-V547 if(!infrared_signal_read_body(signal, ff)) break; //-V779 @@ -284,9 +284,9 @@ bool infrared_signal_search_and_read( return success; } -void infrared_signal_transmit(InfraredSignal* signal) { +void infrared_signal_transmit(const InfraredSignal* signal) { if(signal->is_raw) { - InfraredRawSignal* raw_signal = &signal->payload.raw; + const InfraredRawSignal* raw_signal = &signal->payload.raw; infrared_send_raw_ext( raw_signal->timings, raw_signal->timings_size, @@ -294,7 +294,7 @@ void infrared_signal_transmit(InfraredSignal* signal) { raw_signal->frequency, raw_signal->duty_cycle); } else { - InfraredMessage* message = &signal->payload.message; + const InfraredMessage* message = &signal->payload.message; infrared_send(message, 1); } } diff --git a/applications/main/infrared/infrared_signal.h b/applications/main/infrared/infrared_signal.h index 29c66193834..fcbb3c87396 100644 --- a/applications/main/infrared/infrared_signal.h +++ b/applications/main/infrared/infrared_signal.h @@ -1,45 +1,186 @@ +/** + * @file infrared_signal.h + * @brief Infrared signal library. + * + * Infrared signals may be of two types: + * - known to the infrared signal decoder, or *parsed* signals + * - the rest, or *raw* signals, which are treated merely as a set of timings. + */ #pragma once -#include -#include -#include - -#include #include +#include +/** + * @brief InfraredSignal opaque type declaration. + */ typedef struct InfraredSignal InfraredSignal; +/** + * @brief Raw signal type definition. + * + * Measurement units used: + * - time: microseconds (uS) + * - frequency: Hertz (Hz) + * - duty_cycle: no units, fraction between 0 and 1. + */ typedef struct { - size_t timings_size; - uint32_t* timings; - uint32_t frequency; - float duty_cycle; + size_t timings_size; /**< Number of elements in the timings array. */ + uint32_t* timings; /**< Pointer to an array of timings describing the signal. */ + uint32_t frequency; /**< Carrier frequency of the signal. */ + float duty_cycle; /**< Duty cycle of the signal. */ } InfraredRawSignal; +/** + * @brief Create a new InfraredSignal instance. + * + * @returns pointer to the instance created. + */ InfraredSignal* infrared_signal_alloc(); + +/** + * @brief Delete an InfraredSignal instance. + * + * @param[in,out] signal pointer to the instance to be deleted. + */ void infrared_signal_free(InfraredSignal* signal); -bool infrared_signal_is_raw(InfraredSignal* signal); -bool infrared_signal_is_valid(InfraredSignal* signal); +/** + * @brief Test whether an InfraredSignal instance holds a raw signal. + * + * @param[in] signal pointer to the instance to be tested. + * @returns true if the instance holds a raw signal, false otherwise. + */ +bool infrared_signal_is_raw(const InfraredSignal* signal); + +/** + * @brief Test whether an InfraredSignal instance holds any signal. + * + * @param[in] signal pointer to the instance to be tested. + * @returns true if the instance holds raw signal, false otherwise. + */ +bool infrared_signal_is_valid(const InfraredSignal* signal); +/** + * @brief Set an InfraredInstance to hold the signal from another one. + * + * Any instance's previous contents will be automatically deleted before + * copying the source instance's contents. + * + * @param[in,out] signal pointer to the destination instance. + * @param[in] other pointer to the source instance. + */ void infrared_signal_set_signal(InfraredSignal* signal, const InfraredSignal* other); +/** + * @brief Set an InfraredInstance to hold a raw signal. + * + * Any instance's previous contents will be automatically deleted before + * copying the raw signal. + * + * After this call, infrared_signal_is_raw() will return true. + * + * @param[in,out] signal pointer to the destination instance. + * @param[in] timings pointer to an array containing the raw signal timings. + * @param[in] timings_size number of elements in the timings array. + * @param[in] frequency signal carrier frequency, in Hertz. + * @param[in] duty_cycle signal duty cycle, fraction between 0 and 1. + */ void infrared_signal_set_raw_signal( InfraredSignal* signal, const uint32_t* timings, size_t timings_size, uint32_t frequency, float duty_cycle); -InfraredRawSignal* infrared_signal_get_raw_signal(InfraredSignal* signal); +/** + * @brief Get the raw signal held by an InfraredSignal instance. + * + * @warning the instance MUST hold a *raw* signal, otherwise undefined behaviour will occur. + * + * @param[in] signal pointer to the instance to be queried. + * @returns pointer to the raw signal structure held by the instance. + */ +const InfraredRawSignal* infrared_signal_get_raw_signal(const InfraredSignal* signal); + +/** + * @brief Set an InfraredInstance to hold a parsed signal. + * + * Any instance's previous contents will be automatically deleted before + * copying the raw signal. + * + * After this call, infrared_signal_is_raw() will return false. + * + * @param[in,out] signal pointer to the destination instance. + * @param[in] message pointer to the message containing the parsed signal. + */ void infrared_signal_set_message(InfraredSignal* signal, const InfraredMessage* message); -InfraredMessage* infrared_signal_get_message(InfraredSignal* signal); -bool infrared_signal_save(InfraredSignal* signal, FlipperFormat* ff, const char* name); +/** + * @brief Get the parsed signal held by an InfraredSignal instance. + * + * @warning the instance MUST hold a *parsed* signal, otherwise undefined behaviour will occur. + * + * @param[in] signal pointer to the instance to be queried. + * @returns pointer to the parsed signal structure held by the instance. + */ +const InfraredMessage* infrared_signal_get_message(const InfraredSignal* signal); + +/** + * @brief Read a signal from a FlipperFormat file into an InfraredSignal instance. + * + * The file must be allocated and open prior to this call. The seek position determines + * which signal will be read (if there is more than one in the file). Calling this function + * repeatedly will result in all signals in the file to be read until no more are left. + * + * @param[in,out] signal pointer to the instance to be read into. + * @param[in,out] ff pointer to the FlipperFormat file instance to read from. + * @param[out] name pointer to the string to hold the signal name. Must be properly allocated. + * @returns true if a signal was successfully read, false otherwise (e.g. no more signals to read). + */ bool infrared_signal_read(InfraredSignal* signal, FlipperFormat* ff, FuriString* name); -bool infrared_signal_search_and_read( - InfraredSignal* signal, - FlipperFormat* ff, - const FuriString* name); -void infrared_signal_transmit(InfraredSignal* signal); +/** + * @brief Read a signal name from a FlipperFormat file. + * + * Same behaviour as infrared_signal_read(), but only the name is read. + * + * @param[in,out] ff pointer to the FlipperFormat file instance to read from. + * @param[out] name pointer to the string to hold the signal name. Must be properly allocated. + * @returns true if a signal name was successfully read, false otherwise (e.g. no more signals to read). + */ +bool infrared_signal_read_name(FlipperFormat* ff, FuriString* name); + +/** + * @brief Read a signal with a particular name from a FlipperFormat file into an InfraredSignal instance. + * + * This function will look for a signal with the given name and if found, attempt to read it. + * Same considerations apply as to infrared_signal_read(). + * + * @param[in,out] signal pointer to the instance to be read into. + * @param[in,out] ff pointer to the FlipperFormat file instance to read from. + * @param[in] name pointer to a zero-terminated string containing the requested signal name. + * @returns true if a signal was found and successfully read, false otherwise (e.g. the signal was not found). + */ +bool infrared_signal_search_and_read(InfraredSignal* signal, FlipperFormat* ff, const char* name); + +/** + * @brief Save a signal contained in an InfraredSignal instance to a FlipperFormat file. + * + * The file must be allocated and open prior to this call. Additionally, an appropriate header + * must be already written into the file. + * + * @param[in] signal pointer to the instance holding the signal to be saved. + * @param[in,out] ff pointer to the FlipperFormat file instance to write to. + * @param[in] name pointer to a zero-terminated string contating the name of the signal. + */ +bool infrared_signal_save(const InfraredSignal* signal, FlipperFormat* ff, const char* name); + +/** + * @brief Transmit a signal contained in an InfraredSignal instance. + * + * The transmission happens once per call using the built-in hardware (via HAL calls). + * + * @param[in] signal pointer to the instance holding the signal to be transmitted. + */ +void infrared_signal_transmit(const InfraredSignal* signal); diff --git a/applications/main/infrared/scenes/common/infrared_scene_universal_common.c b/applications/main/infrared/scenes/common/infrared_scene_universal_common.c index 96f28cc4891..4967d195664 100644 --- a/applications/main/infrared/scenes/common/infrared_scene_universal_common.c +++ b/applications/main/infrared/scenes/common/infrared_scene_universal_common.c @@ -1,20 +1,21 @@ -#include "../../infrared_i.h" +#include "../../infrared_app_i.h" #include void infrared_scene_universal_common_item_callback(void* context, uint32_t index) { - Infrared* infrared = context; + InfraredApp* infrared = context; uint32_t event = infrared_custom_event_pack(InfraredCustomEventTypeButtonSelected, index); view_dispatcher_send_custom_event(infrared->view_dispatcher, event); } static void infrared_scene_universal_common_progress_back_callback(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; uint32_t event = infrared_custom_event_pack(InfraredCustomEventTypeBackPressed, -1); view_dispatcher_send_custom_event(infrared->view_dispatcher, event); } -static void infrared_scene_universal_common_show_popup(Infrared* infrared, uint32_t record_count) { +static void + infrared_scene_universal_common_show_popup(InfraredApp* infrared, uint32_t record_count) { ViewStack* view_stack = infrared->view_stack; InfraredProgressView* progress = infrared->progress; infrared_progress_view_set_progress_total(progress, record_count); @@ -24,7 +25,7 @@ static void infrared_scene_universal_common_show_popup(Infrared* infrared, uint3 infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkStartSend); } -static void infrared_scene_universal_common_hide_popup(Infrared* infrared) { +static void infrared_scene_universal_common_hide_popup(InfraredApp* infrared) { ViewStack* view_stack = infrared->view_stack; InfraredProgressView* progress = infrared->progress; view_stack_remove_view(view_stack, infrared_progress_view_get_view(progress)); @@ -32,12 +33,12 @@ static void infrared_scene_universal_common_hide_popup(Infrared* infrared) { } void infrared_scene_universal_common_on_enter(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; view_stack_add_view(infrared->view_stack, button_panel_get_view(infrared->button_panel)); } bool infrared_scene_universal_common_on_event(void* context, SceneManagerEvent event) { - Infrared* infrared = context; + InfraredApp* infrared = context; SceneManager* scene_manager = infrared->scene_manager; InfraredBruteForce* brute_force = infrared->brute_force; bool consumed = false; @@ -84,7 +85,7 @@ bool infrared_scene_universal_common_on_event(void* context, SceneManagerEvent e } void infrared_scene_universal_common_on_exit(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; ButtonPanel* button_panel = infrared->button_panel; view_stack_remove_view(infrared->view_stack, button_panel_get_view(button_panel)); infrared_brute_force_reset(infrared->brute_force); diff --git a/applications/main/infrared/scenes/infrared_scene_ask_back.c b/applications/main/infrared/scenes/infrared_scene_ask_back.c index 77fc97f9871..f97a38bb091 100644 --- a/applications/main/infrared/scenes/infrared_scene_ask_back.c +++ b/applications/main/infrared/scenes/infrared_scene_ask_back.c @@ -1,12 +1,12 @@ -#include "../infrared_i.h" +#include "../infrared_app_i.h" static void infrared_scene_dialog_result_callback(DialogExResult result, void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; view_dispatcher_send_custom_event(infrared->view_dispatcher, result); } void infrared_scene_ask_back_on_enter(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; DialogEx* dialog_ex = infrared->dialog_ex; if(infrared->app_state.is_learning_new_remote) { @@ -28,7 +28,7 @@ void infrared_scene_ask_back_on_enter(void* context) { } bool infrared_scene_ask_back_on_event(void* context, SceneManagerEvent event) { - Infrared* infrared = context; + InfraredApp* infrared = context; SceneManager* scene_manager = infrared->scene_manager; bool consumed = false; @@ -54,6 +54,6 @@ bool infrared_scene_ask_back_on_event(void* context, SceneManagerEvent event) { } void infrared_scene_ask_back_on_exit(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; dialog_ex_reset(infrared->dialog_ex); } diff --git a/applications/main/infrared/scenes/infrared_scene_ask_retry.c b/applications/main/infrared/scenes/infrared_scene_ask_retry.c index 602e470c7c3..365ed5c812f 100644 --- a/applications/main/infrared/scenes/infrared_scene_ask_retry.c +++ b/applications/main/infrared/scenes/infrared_scene_ask_retry.c @@ -1,12 +1,12 @@ -#include "../infrared_i.h" +#include "../infrared_app_i.h" static void infrared_scene_dialog_result_callback(DialogExResult result, void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; view_dispatcher_send_custom_event(infrared->view_dispatcher, result); } void infrared_scene_ask_retry_on_enter(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; DialogEx* dialog_ex = infrared->dialog_ex; dialog_ex_set_header(dialog_ex, "Retry Reading?", 64, 11, AlignCenter, AlignTop); @@ -23,7 +23,7 @@ void infrared_scene_ask_retry_on_enter(void* context) { } bool infrared_scene_ask_retry_on_event(void* context, SceneManagerEvent event) { - Infrared* infrared = context; + InfraredApp* infrared = context; SceneManager* scene_manager = infrared->scene_manager; bool consumed = false; @@ -43,6 +43,6 @@ bool infrared_scene_ask_retry_on_event(void* context, SceneManagerEvent event) { } void infrared_scene_ask_retry_on_exit(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; dialog_ex_reset(infrared->dialog_ex); } diff --git a/applications/main/infrared/scenes/infrared_scene_debug.c b/applications/main/infrared/scenes/infrared_scene_debug.c index 204978697b8..adffbc83ace 100644 --- a/applications/main/infrared/scenes/infrared_scene_debug.c +++ b/applications/main/infrared/scenes/infrared_scene_debug.c @@ -1,7 +1,7 @@ -#include "../infrared_i.h" +#include "../infrared_app_i.h" void infrared_scene_debug_on_enter(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; InfraredWorker* worker = infrared->worker; infrared_worker_rx_set_received_signal_callback( @@ -14,16 +14,16 @@ void infrared_scene_debug_on_enter(void* context) { } bool infrared_scene_debug_on_event(void* context, SceneManagerEvent event) { - Infrared* infrared = context; + InfraredApp* infrared = context; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { if(event.event == InfraredCustomEventTypeSignalReceived) { InfraredDebugView* debug_view = infrared->debug_view; - InfraredSignal* signal = infrared->received_signal; + InfraredSignal* signal = infrared->current_signal; if(infrared_signal_is_raw(signal)) { - InfraredRawSignal* raw = infrared_signal_get_raw_signal(signal); + const InfraredRawSignal* raw = infrared_signal_get_raw_signal(signal); infrared_debug_view_set_text(debug_view, "RAW\n%d samples\n", raw->timings_size); printf("RAW, %zu samples:\r\n", raw->timings_size); @@ -33,7 +33,7 @@ bool infrared_scene_debug_on_event(void* context, SceneManagerEvent event) { printf("\r\n"); } else { - InfraredMessage* message = infrared_signal_get_message(signal); + const InfraredMessage* message = infrared_signal_get_message(signal); infrared_debug_view_set_text( debug_view, "%s\nA:0x%0*lX\nC:0x%0*lX\n%s\n", @@ -61,7 +61,7 @@ bool infrared_scene_debug_on_event(void* context, SceneManagerEvent event) { } void infrared_scene_debug_on_exit(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; InfraredWorker* worker = infrared->worker; infrared_worker_rx_stop(worker); infrared_worker_rx_enable_blink_on_receiving(worker, false); diff --git a/applications/main/infrared/scenes/infrared_scene_edit.c b/applications/main/infrared/scenes/infrared_scene_edit.c index 02bba7a3f46..c22e9539631 100644 --- a/applications/main/infrared/scenes/infrared_scene_edit.c +++ b/applications/main/infrared/scenes/infrared_scene_edit.c @@ -1,4 +1,4 @@ -#include "../infrared_i.h" +#include "../infrared_app_i.h" typedef enum { SubmenuIndexAddButton, @@ -10,12 +10,12 @@ typedef enum { } SubmenuIndex; static void infrared_scene_edit_submenu_callback(void* context, uint32_t index) { - Infrared* infrared = context; + InfraredApp* infrared = context; view_dispatcher_send_custom_event(infrared->view_dispatcher, index); } void infrared_scene_edit_on_enter(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; Submenu* submenu = infrared->submenu; SceneManager* scene_manager = infrared->scene_manager; @@ -64,7 +64,7 @@ void infrared_scene_edit_on_enter(void* context) { } bool infrared_scene_edit_on_event(void* context, SceneManagerEvent event) { - Infrared* infrared = context; + InfraredApp* infrared = context; SceneManager* scene_manager = infrared->scene_manager; bool consumed = false; @@ -106,6 +106,6 @@ bool infrared_scene_edit_on_event(void* context, SceneManagerEvent event) { } void infrared_scene_edit_on_exit(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; submenu_reset(infrared->submenu); } diff --git a/applications/main/infrared/scenes/infrared_scene_edit_button_select.c b/applications/main/infrared/scenes/infrared_scene_edit_button_select.c index 5f5a1d8face..a76b4e836b2 100644 --- a/applications/main/infrared/scenes/infrared_scene_edit_button_select.c +++ b/applications/main/infrared/scenes/infrared_scene_edit_button_select.c @@ -1,12 +1,12 @@ -#include "../infrared_i.h" +#include "../infrared_app_i.h" static void infrared_scene_edit_button_select_submenu_callback(void* context, uint32_t index) { - Infrared* infrared = context; + InfraredApp* infrared = context; view_dispatcher_send_custom_event(infrared->view_dispatcher, index); } void infrared_scene_edit_button_select_on_enter(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; Submenu* submenu = infrared->submenu; InfraredRemote* remote = infrared->remote; InfraredAppState* app_state = &infrared->app_state; @@ -16,16 +16,16 @@ void infrared_scene_edit_button_select_on_enter(void* context) { "Delete Button:"; submenu_set_header(submenu, header); - const size_t button_count = infrared_remote_get_button_count(remote); + const size_t button_count = infrared_remote_get_signal_count(remote); for(size_t i = 0; i < button_count; ++i) { - InfraredRemoteButton* button = infrared_remote_get_button(remote, i); submenu_add_item( submenu, - infrared_remote_button_get_name(button), + infrared_remote_get_signal_name(remote, i), i, infrared_scene_edit_button_select_submenu_callback, context); } + if(button_count && app_state->current_button_index != InfraredButtonIndexNone) { submenu_set_selected_item(submenu, app_state->current_button_index); app_state->current_button_index = InfraredButtonIndexNone; @@ -35,7 +35,7 @@ void infrared_scene_edit_button_select_on_enter(void* context) { } bool infrared_scene_edit_button_select_on_event(void* context, SceneManagerEvent event) { - Infrared* infrared = context; + InfraredApp* infrared = context; InfraredAppState* app_state = &infrared->app_state; SceneManager* scene_manager = infrared->scene_manager; bool consumed = false; @@ -57,6 +57,6 @@ bool infrared_scene_edit_button_select_on_event(void* context, SceneManagerEvent } void infrared_scene_edit_button_select_on_exit(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; submenu_reset(infrared->submenu); } diff --git a/applications/main/infrared/scenes/infrared_scene_edit_delete.c b/applications/main/infrared/scenes/infrared_scene_edit_delete.c index 4dfc054fbd2..c1735da0829 100644 --- a/applications/main/infrared/scenes/infrared_scene_edit_delete.c +++ b/applications/main/infrared/scenes/infrared_scene_edit_delete.c @@ -1,42 +1,49 @@ -#include "../infrared_i.h" +#include "../infrared_app_i.h" static void infrared_scene_edit_delete_dialog_result_callback(DialogExResult result, void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; view_dispatcher_send_custom_event(infrared->view_dispatcher, result); } void infrared_scene_edit_delete_on_enter(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; DialogEx* dialog_ex = infrared->dialog_ex; InfraredRemote* remote = infrared->remote; const InfraredEditTarget edit_target = infrared->app_state.edit_target; if(edit_target == InfraredEditTargetButton) { - int32_t current_button_index = infrared->app_state.current_button_index; - furi_assert(current_button_index != InfraredButtonIndexNone); - dialog_ex_set_header(dialog_ex, "Delete Button?", 64, 0, AlignCenter, AlignTop); - InfraredRemoteButton* current_button = - infrared_remote_get_button(remote, current_button_index); - InfraredSignal* signal = infrared_remote_button_get_signal(current_button); - if(infrared_signal_is_raw(signal)) { - const InfraredRawSignal* raw = infrared_signal_get_raw_signal(signal); + const int32_t current_button_index = infrared->app_state.current_button_index; + furi_check(current_button_index != InfraredButtonIndexNone); + + if(!infrared_remote_load_signal(remote, infrared->current_signal, current_button_index)) { + infrared_show_error_message( + infrared, + "Failed to load\n\"%s\"", + infrared_remote_get_signal_name(remote, current_button_index)); + scene_manager_previous_scene(infrared->scene_manager); + return; + } + + if(infrared_signal_is_raw(infrared->current_signal)) { + const InfraredRawSignal* raw = + infrared_signal_get_raw_signal(infrared->current_signal); infrared_text_store_set( infrared, 0, - "%s\nRAW\n%ld samples", - infrared_remote_button_get_name(current_button), + "%s\nRAW\n%zu samples", + infrared_remote_get_signal_name(remote, current_button_index), raw->timings_size); } else { - const InfraredMessage* message = infrared_signal_get_message(signal); + const InfraredMessage* message = infrared_signal_get_message(infrared->current_signal); infrared_text_store_set( infrared, 0, "%s\n%s\nA=0x%0*lX C=0x%0*lX", - infrared_remote_button_get_name(current_button), + infrared_remote_get_signal_name(remote, current_button_index), infrared_get_protocol_name(message->protocol), ROUND_UP_TO(infrared_get_protocol_address_length(message->protocol), 4), message->address, @@ -49,9 +56,9 @@ void infrared_scene_edit_delete_on_enter(void* context) { infrared_text_store_set( infrared, 0, - "%s\n with %lu buttons", + "%s\n with %zu buttons", infrared_remote_get_name(remote), - infrared_remote_get_button_count(remote)); + infrared_remote_get_signal_count(remote)); } else { furi_assert(0); } @@ -63,11 +70,14 @@ void infrared_scene_edit_delete_on_enter(void* context) { dialog_ex_set_result_callback(dialog_ex, infrared_scene_edit_delete_dialog_result_callback); dialog_ex_set_context(dialog_ex, context); - view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewDialogEx); + view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationHorizontal); + view_stack_add_view(infrared->view_stack, dialog_ex_get_view(infrared->dialog_ex)); + + view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack); } bool infrared_scene_edit_delete_on_event(void* context, SceneManagerEvent event) { - Infrared* infrared = context; + InfraredApp* infrared = context; SceneManager* scene_manager = infrared->scene_manager; bool consumed = false; @@ -83,18 +93,24 @@ bool infrared_scene_edit_delete_on_event(void* context, SceneManagerEvent event) if(edit_target == InfraredEditTargetButton) { furi_assert(app_state->current_button_index != InfraredButtonIndexNone); - success = infrared_remote_delete_button(remote, app_state->current_button_index); + infrared_show_loading_popup(infrared, true); + success = infrared_remote_delete_signal(remote, app_state->current_button_index); + infrared_show_loading_popup(infrared, false); app_state->current_button_index = InfraredButtonIndexNone; } else if(edit_target == InfraredEditTargetRemote) { success = infrared_remote_remove(remote); app_state->current_button_index = InfraredButtonIndexNone; } else { - furi_assert(0); + furi_crash(NULL); } if(success) { scene_manager_next_scene(scene_manager, InfraredSceneEditDeleteDone); } else { + infrared_show_error_message( + infrared, + "Failed to\ndelete %s", + edit_target == InfraredEditTargetButton ? "button" : "file"); const uint32_t possible_scenes[] = {InfraredSceneRemoteList, InfraredSceneStart}; scene_manager_search_and_switch_to_previous_scene_one_of( scene_manager, possible_scenes, COUNT_OF(possible_scenes)); @@ -107,6 +123,6 @@ bool infrared_scene_edit_delete_on_event(void* context, SceneManagerEvent event) } void infrared_scene_edit_delete_on_exit(void* context) { - Infrared* infrared = context; - UNUSED(infrared); + InfraredApp* infrared = context; + view_stack_remove_view(infrared->view_stack, dialog_ex_get_view(infrared->dialog_ex)); } diff --git a/applications/main/infrared/scenes/infrared_scene_edit_delete_done.c b/applications/main/infrared/scenes/infrared_scene_edit_delete_done.c index 49a299d2aa6..0ee6399142b 100644 --- a/applications/main/infrared/scenes/infrared_scene_edit_delete_done.c +++ b/applications/main/infrared/scenes/infrared_scene_edit_delete_done.c @@ -1,7 +1,7 @@ -#include "../infrared_i.h" +#include "../infrared_app_i.h" void infrared_scene_edit_delete_done_on_enter(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; Popup* popup = infrared->popup; popup_set_icon(popup, 0, 2, &I_DolphinMafia_115x62); @@ -16,7 +16,7 @@ void infrared_scene_edit_delete_done_on_enter(void* context) { } bool infrared_scene_edit_delete_done_on_event(void* context, SceneManagerEvent event) { - Infrared* infrared = context; + InfraredApp* infrared = context; SceneManager* scene_manager = infrared->scene_manager; bool consumed = false; @@ -43,6 +43,6 @@ bool infrared_scene_edit_delete_done_on_event(void* context, SceneManagerEvent e } void infrared_scene_edit_delete_done_on_exit(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; UNUSED(infrared); } diff --git a/applications/main/infrared/scenes/infrared_scene_edit_move.c b/applications/main/infrared/scenes/infrared_scene_edit_move.c index 370c352dbd0..4959a831095 100644 --- a/applications/main/infrared/scenes/infrared_scene_edit_move.c +++ b/applications/main/infrared/scenes/infrared_scene_edit_move.c @@ -1,44 +1,69 @@ -#include "../infrared_i.h" +#include "../infrared_app_i.h" -static void infrared_scene_move_button(uint32_t index_old, uint32_t index_new, void* context) { - InfraredRemote* remote = context; - furi_assert(remote); - infrared_remote_move_button(remote, index_old, index_new); -} +static void infrared_scene_edit_move_button_callback( + uint32_t index_old, + uint32_t index_new, + void* context) { + InfraredApp* infrared = context; + furi_assert(infrared); + + infrared->app_state.prev_button_index = index_old; + infrared->app_state.current_button_index = index_new; -static const char* infrared_scene_get_btn_name(uint32_t index, void* context) { - InfraredRemote* remote = context; - furi_assert(remote); - InfraredRemoteButton* button = infrared_remote_get_button(remote, index); - return (infrared_remote_button_get_name(button)); + view_dispatcher_send_custom_event( + infrared->view_dispatcher, InfraredCustomEventTypeButtonSelected); } void infrared_scene_edit_move_on_enter(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; InfraredRemote* remote = infrared->remote; - infrared_move_view_set_callback(infrared->move_view, infrared_scene_move_button); + for(size_t i = 0; i < infrared_remote_get_signal_count(remote); ++i) { + infrared_move_view_add_item( + infrared->move_view, infrared_remote_get_signal_name(remote, i)); + } + + infrared_move_view_set_callback( + infrared->move_view, infrared_scene_edit_move_button_callback, infrared); - uint32_t btn_count = infrared_remote_get_button_count(remote); - infrared_move_view_list_init( - infrared->move_view, btn_count, infrared_scene_get_btn_name, remote); - infrared_move_view_list_update(infrared->move_view); + view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationHorizontal); + view_stack_add_view(infrared->view_stack, infrared_move_view_get_view(infrared->move_view)); - view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewMove); + view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack); } bool infrared_scene_edit_move_on_event(void* context, SceneManagerEvent event) { - Infrared* infrared = context; + InfraredApp* infrared = context; bool consumed = false; - UNUSED(event); - UNUSED(infrared); + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == InfraredCustomEventTypeButtonSelected) { + infrared_show_loading_popup(infrared, true); + const bool button_moved = infrared_remote_move_signal( + infrared->remote, + infrared->app_state.prev_button_index, + infrared->app_state.current_button_index); + infrared_show_loading_popup(infrared, false); + + if(!button_moved) { + infrared_show_error_message( + infrared, + "Failed to move\n\"%s\"", + infrared_remote_get_signal_name( + infrared->remote, infrared->app_state.current_button_index)); + scene_manager_search_and_switch_to_previous_scene( + infrared->scene_manager, InfraredSceneRemoteList); + } + + consumed = true; + } + } return consumed; } void infrared_scene_edit_move_on_exit(void* context) { - Infrared* infrared = context; - InfraredRemote* remote = infrared->remote; - infrared_remote_store(remote); + InfraredApp* infrared = context; + view_stack_remove_view(infrared->view_stack, infrared_move_view_get_view(infrared->move_view)); + infrared_move_view_reset(infrared->move_view); } diff --git a/applications/main/infrared/scenes/infrared_scene_edit_rename.c b/applications/main/infrared/scenes/infrared_scene_edit_rename.c index a2199215d74..e29f108651d 100644 --- a/applications/main/infrared/scenes/infrared_scene_edit_rename.c +++ b/applications/main/infrared/scenes/infrared_scene_edit_rename.c @@ -1,10 +1,10 @@ -#include "../infrared_i.h" +#include "../infrared_app_i.h" #include #include void infrared_scene_edit_rename_on_enter(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; InfraredRemote* remote = infrared->remote; TextInput* text_input = infrared->text_input; size_t enter_name_length = 0; @@ -14,14 +14,12 @@ void infrared_scene_edit_rename_on_enter(void* context) { text_input_set_header_text(text_input, "Name the button"); const int32_t current_button_index = infrared->app_state.current_button_index; - furi_assert(current_button_index != InfraredButtonIndexNone); + furi_check(current_button_index != InfraredButtonIndexNone); - InfraredRemoteButton* current_button = - infrared_remote_get_button(remote, current_button_index); enter_name_length = INFRARED_MAX_BUTTON_NAME_LENGTH; strncpy( infrared->text_store[0], - infrared_remote_button_get_name(current_button), + infrared_remote_get_signal_name(remote, current_button_index), enter_name_length); } else if(edit_target == InfraredEditTargetRemote) { @@ -44,7 +42,7 @@ void infrared_scene_edit_rename_on_enter(void* context) { furi_string_free(folder_path); } else { - furi_assert(0); + furi_crash(NULL); } text_input_set_result_callback( @@ -55,11 +53,14 @@ void infrared_scene_edit_rename_on_enter(void* context) { enter_name_length, false); - view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewTextInput); + view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationHorizontal); + view_stack_add_view(infrared->view_stack, text_input_get_view(infrared->text_input)); + + view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack); } bool infrared_scene_edit_rename_on_event(void* context, SceneManagerEvent event) { - Infrared* infrared = context; + InfraredApp* infrared = context; InfraredRemote* remote = infrared->remote; SceneManager* scene_manager = infrared->scene_manager; InfraredAppState* app_state = &infrared->app_state; @@ -72,18 +73,24 @@ bool infrared_scene_edit_rename_on_event(void* context, SceneManagerEvent event) if(edit_target == InfraredEditTargetButton) { const int32_t current_button_index = app_state->current_button_index; furi_assert(current_button_index != InfraredButtonIndexNone); - success = infrared_remote_rename_button( - remote, infrared->text_store[0], current_button_index); + infrared_show_loading_popup(infrared, true); + success = infrared_remote_rename_signal( + remote, current_button_index, infrared->text_store[0]); + infrared_show_loading_popup(infrared, false); app_state->current_button_index = InfraredButtonIndexNone; } else if(edit_target == InfraredEditTargetRemote) { success = infrared_rename_current_remote(infrared, infrared->text_store[0]); } else { - furi_assert(0); + furi_crash(NULL); } if(success) { scene_manager_next_scene(scene_manager, InfraredSceneEditRenameDone); } else { + infrared_show_error_message( + infrared, + "Failed to\nrename %s", + edit_target == InfraredEditTargetButton ? "button" : "file"); scene_manager_search_and_switch_to_previous_scene( scene_manager, InfraredSceneRemoteList); } @@ -95,9 +102,11 @@ bool infrared_scene_edit_rename_on_event(void* context, SceneManagerEvent event) } void infrared_scene_edit_rename_on_exit(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; TextInput* text_input = infrared->text_input; + view_stack_remove_view(infrared->view_stack, text_input_get_view(text_input)); + void* validator_context = text_input_get_validator_callback_context(text_input); text_input_set_validator(text_input, NULL, NULL); diff --git a/applications/main/infrared/scenes/infrared_scene_edit_rename_done.c b/applications/main/infrared/scenes/infrared_scene_edit_rename_done.c index 6c7096e17d0..35f51598941 100644 --- a/applications/main/infrared/scenes/infrared_scene_edit_rename_done.c +++ b/applications/main/infrared/scenes/infrared_scene_edit_rename_done.c @@ -1,7 +1,7 @@ -#include "../infrared_i.h" +#include "../infrared_app_i.h" void infrared_scene_edit_rename_done_on_enter(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; Popup* popup = infrared->popup; popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); @@ -16,7 +16,7 @@ void infrared_scene_edit_rename_done_on_enter(void* context) { } bool infrared_scene_edit_rename_done_on_event(void* context, SceneManagerEvent event) { - Infrared* infrared = context; + InfraredApp* infrared = context; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { @@ -33,6 +33,6 @@ bool infrared_scene_edit_rename_done_on_event(void* context, SceneManagerEvent e } void infrared_scene_edit_rename_done_on_exit(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; UNUSED(infrared); } diff --git a/applications/main/infrared/scenes/infrared_scene_error_databases.c b/applications/main/infrared/scenes/infrared_scene_error_databases.c index 4ed4dee583b..f51f83f25f3 100644 --- a/applications/main/infrared/scenes/infrared_scene_error_databases.c +++ b/applications/main/infrared/scenes/infrared_scene_error_databases.c @@ -1,7 +1,7 @@ -#include "../infrared_i.h" +#include "../infrared_app_i.h" void infrared_scene_error_databases_on_enter(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; Popup* popup = infrared->popup; popup_set_icon(popup, 5, 11, &I_SDQuestion_35x43); @@ -16,7 +16,7 @@ void infrared_scene_error_databases_on_enter(void* context) { } bool infrared_scene_error_databases_on_event(void* context, SceneManagerEvent event) { - Infrared* infrared = context; + InfraredApp* infrared = context; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { @@ -31,7 +31,7 @@ bool infrared_scene_error_databases_on_event(void* context, SceneManagerEvent ev } void infrared_scene_error_databases_on_exit(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; popup_reset(infrared->popup); infrared_play_notification_message(infrared, InfraredNotificationMessageYellowOff); } diff --git a/applications/main/infrared/scenes/infrared_scene_learn.c b/applications/main/infrared/scenes/infrared_scene_learn.c index 46646c6d69a..bcd0a2cd0f8 100644 --- a/applications/main/infrared/scenes/infrared_scene_learn.c +++ b/applications/main/infrared/scenes/infrared_scene_learn.c @@ -1,8 +1,8 @@ -#include "../infrared_i.h" +#include "../infrared_app_i.h" #include void infrared_scene_learn_on_enter(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; Popup* popup = infrared->popup; InfraredWorker* worker = infrared->worker; @@ -21,7 +21,7 @@ void infrared_scene_learn_on_enter(void* context) { } bool infrared_scene_learn_on_event(void* context, SceneManagerEvent event) { - Infrared* infrared = context; + InfraredApp* infrared = context; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { @@ -37,7 +37,7 @@ bool infrared_scene_learn_on_event(void* context, SceneManagerEvent event) { } void infrared_scene_learn_on_exit(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; Popup* popup = infrared->popup; infrared_worker_rx_set_received_signal_callback(infrared->worker, NULL, NULL); infrared_worker_rx_stop(infrared->worker); diff --git a/applications/main/infrared/scenes/infrared_scene_learn_done.c b/applications/main/infrared/scenes/infrared_scene_learn_done.c index 54b7da7247b..b4eb38331d0 100644 --- a/applications/main/infrared/scenes/infrared_scene_learn_done.c +++ b/applications/main/infrared/scenes/infrared_scene_learn_done.c @@ -1,7 +1,7 @@ -#include "../infrared_i.h" +#include "../infrared_app_i.h" void infrared_scene_learn_done_on_enter(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; Popup* popup = infrared->popup; popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); @@ -21,7 +21,7 @@ void infrared_scene_learn_done_on_enter(void* context) { } bool infrared_scene_learn_done_on_event(void* context, SceneManagerEvent event) { - Infrared* infrared = context; + InfraredApp* infrared = context; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { @@ -38,7 +38,7 @@ bool infrared_scene_learn_done_on_event(void* context, SceneManagerEvent event) } void infrared_scene_learn_done_on_exit(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; infrared->app_state.is_learning_new_remote = false; popup_set_header(infrared->popup, NULL, 0, 0, AlignLeft, AlignTop); } diff --git a/applications/main/infrared/scenes/infrared_scene_learn_enter_name.c b/applications/main/infrared/scenes/infrared_scene_learn_enter_name.c index 104a4cb7b65..be46a869168 100644 --- a/applications/main/infrared/scenes/infrared_scene_learn_enter_name.c +++ b/applications/main/infrared/scenes/infrared_scene_learn_enter_name.c @@ -1,16 +1,16 @@ -#include "../infrared_i.h" +#include "../infrared_app_i.h" #include void infrared_scene_learn_enter_name_on_enter(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; TextInput* text_input = infrared->text_input; - InfraredSignal* signal = infrared->received_signal; + InfraredSignal* signal = infrared->current_signal; if(infrared_signal_is_raw(signal)) { - InfraredRawSignal* raw = infrared_signal_get_raw_signal(signal); - infrared_text_store_set(infrared, 0, "RAW_%d", raw->timings_size); + const InfraredRawSignal* raw = infrared_signal_get_raw_signal(signal); + infrared_text_store_set(infrared, 0, "RAW_%zu", raw->timings_size); } else { - InfraredMessage* message = infrared_signal_get_message(signal); + const InfraredMessage* message = infrared_signal_get_message(signal); infrared_text_store_set( infrared, 0, @@ -28,31 +28,32 @@ void infrared_scene_learn_enter_name_on_enter(void* context) { infrared->text_store[0], INFRARED_MAX_BUTTON_NAME_LENGTH, true); + view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewTextInput); } bool infrared_scene_learn_enter_name_on_event(void* context, SceneManagerEvent event) { - Infrared* infrared = context; - InfraredSignal* signal = infrared->received_signal; + InfraredApp* infrared = context; + InfraredSignal* signal = infrared->current_signal; SceneManager* scene_manager = infrared->scene_manager; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { if(event.event == InfraredCustomEventTypeTextEditDone) { - bool success = false; - if(infrared->app_state.is_learning_new_remote) { - success = - infrared_add_remote_with_button(infrared, infrared->text_store[0], signal); - } else { - success = - infrared_remote_add_button(infrared->remote, infrared->text_store[0], signal); - } + const char* signal_name = infrared->text_store[0]; + const bool success = + infrared->app_state.is_learning_new_remote ? + infrared_add_remote_with_button(infrared, signal_name, signal) : + infrared_remote_append_signal(infrared->remote, signal, signal_name); if(success) { scene_manager_next_scene(scene_manager, InfraredSceneLearnDone); dolphin_deed(DolphinDeedIrSave); } else { - dialog_message_show_storage_error(infrared->dialogs, "Failed to save file"); + infrared_show_error_message( + infrared, + "Failed to\n%s", + infrared->app_state.is_learning_new_remote ? "create file" : "add signal"); const uint32_t possible_scenes[] = {InfraredSceneRemoteList, InfraredSceneStart}; scene_manager_search_and_switch_to_previous_scene_one_of( scene_manager, possible_scenes, COUNT_OF(possible_scenes)); @@ -65,6 +66,6 @@ bool infrared_scene_learn_enter_name_on_event(void* context, SceneManagerEvent e } void infrared_scene_learn_enter_name_on_exit(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; UNUSED(infrared); } diff --git a/applications/main/infrared/scenes/infrared_scene_learn_success.c b/applications/main/infrared/scenes/infrared_scene_learn_success.c index 469d4de9e46..deb54bb5ef2 100644 --- a/applications/main/infrared/scenes/infrared_scene_learn_success.c +++ b/applications/main/infrared/scenes/infrared_scene_learn_success.c @@ -1,26 +1,26 @@ -#include "../infrared_i.h" +#include "../infrared_app_i.h" static void infrared_scene_learn_success_dialog_result_callback(DialogExResult result, void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; view_dispatcher_send_custom_event(infrared->view_dispatcher, result); } void infrared_scene_learn_success_on_enter(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; DialogEx* dialog_ex = infrared->dialog_ex; - InfraredSignal* signal = infrared->received_signal; + InfraredSignal* signal = infrared->current_signal; infrared_play_notification_message(infrared, InfraredNotificationMessageGreenOn); if(infrared_signal_is_raw(signal)) { - InfraredRawSignal* raw = infrared_signal_get_raw_signal(signal); + const InfraredRawSignal* raw = infrared_signal_get_raw_signal(signal); dialog_ex_set_header(dialog_ex, "Unknown", 95, 10, AlignCenter, AlignCenter); - infrared_text_store_set(infrared, 0, "%d samples", raw->timings_size); + infrared_text_store_set(infrared, 0, "%zu samples", raw->timings_size); dialog_ex_set_text(dialog_ex, infrared->text_store[0], 75, 23, AlignLeft, AlignTop); } else { - InfraredMessage* message = infrared_signal_get_message(signal); + const InfraredMessage* message = infrared_signal_get_message(signal); uint8_t addr_digits = ROUND_UP_TO(infrared_get_protocol_address_length(message->protocol), 4); uint8_t cmd_digits = @@ -56,7 +56,7 @@ void infrared_scene_learn_success_on_enter(void* context) { } bool infrared_scene_learn_success_on_event(void* context, SceneManagerEvent event) { - Infrared* infrared = context; + InfraredApp* infrared = context; SceneManager* scene_manager = infrared->scene_manager; const bool is_transmitter_idle = !infrared->app_state.is_transmitting; bool consumed = false; @@ -84,7 +84,7 @@ bool infrared_scene_learn_success_on_event(void* context, SceneManagerEvent even consumed = true; } else if(event.event == DialogExPressCenter) { infrared_play_notification_message(infrared, InfraredNotificationMessageGreenOff); - infrared_tx_start_received(infrared); + infrared_tx_start(infrared); consumed = true; } else if(event.event == DialogExReleaseCenter) { infrared_tx_stop(infrared); @@ -96,7 +96,7 @@ bool infrared_scene_learn_success_on_event(void* context, SceneManagerEvent even } void infrared_scene_learn_success_on_exit(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; dialog_ex_reset(infrared->dialog_ex); infrared_play_notification_message(infrared, InfraredNotificationMessageGreenOff); } diff --git a/applications/main/infrared/scenes/infrared_scene_remote.c b/applications/main/infrared/scenes/infrared_scene_remote.c index c1f5b6627ce..5974acbfecb 100644 --- a/applications/main/infrared/scenes/infrared_scene_remote.c +++ b/applications/main/infrared/scenes/infrared_scene_remote.c @@ -1,4 +1,4 @@ -#include "../infrared_i.h" +#include "../infrared_app_i.h" typedef enum { ButtonIndexPlus = -2, @@ -8,7 +8,7 @@ typedef enum { static void infrared_scene_remote_button_menu_callback(void* context, int32_t index, InputType type) { - Infrared* infrared = context; + InfraredApp* infrared = context; uint16_t custom_type; if(type == InputTypePress) { @@ -26,17 +26,15 @@ static void } void infrared_scene_remote_on_enter(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; InfraredRemote* remote = infrared->remote; ButtonMenu* button_menu = infrared->button_menu; SceneManager* scene_manager = infrared->scene_manager; - size_t button_count = infrared_remote_get_button_count(remote); - for(size_t i = 0; i < button_count; ++i) { - InfraredRemoteButton* button = infrared_remote_get_button(remote, i); + for(size_t i = 0; i < infrared_remote_get_signal_count(remote); ++i) { button_menu_add_item( button_menu, - infrared_remote_button_get_name(button), + infrared_remote_get_signal_name(remote, i), i, infrared_scene_remote_button_menu_callback, ButtonMenuItemTypeCommon, @@ -68,7 +66,7 @@ void infrared_scene_remote_on_enter(void* context) { } bool infrared_scene_remote_on_event(void* context, SceneManagerEvent event) { - Infrared* infrared = context; + InfraredApp* infrared = context; SceneManager* scene_manager = infrared->scene_manager; const bool is_transmitter_idle = !infrared->app_state.is_transmitting; bool consumed = false; @@ -116,6 +114,6 @@ bool infrared_scene_remote_on_event(void* context, SceneManagerEvent event) { } void infrared_scene_remote_on_exit(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; button_menu_reset(infrared->button_menu); } diff --git a/applications/main/infrared/scenes/infrared_scene_remote_list.c b/applications/main/infrared/scenes/infrared_scene_remote_list.c index 55f14416bf2..2276e270a0c 100644 --- a/applications/main/infrared/scenes/infrared_scene_remote_list.c +++ b/applications/main/infrared/scenes/infrared_scene_remote_list.c @@ -1,31 +1,34 @@ -#include "../infrared_i.h" +#include "../infrared_app_i.h" void infrared_scene_remote_list_on_enter(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; SceneManager* scene_manager = infrared->scene_manager; ViewDispatcher* view_dispatcher = infrared->view_dispatcher; + view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical); + view_dispatcher_switch_to_view(view_dispatcher, InfraredViewStack); + DialogsFileBrowserOptions browser_options; dialog_file_browser_set_basic_options(&browser_options, INFRARED_APP_EXTENSION, &I_ir_10px); browser_options.base_path = INFRARED_APP_FOLDER; - bool success = dialog_file_browser_show( - infrared->dialogs, infrared->file_path, infrared->file_path, &browser_options); - - if(success) { - view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical); - view_dispatcher_switch_to_view(view_dispatcher, InfraredViewStack); + while(dialog_file_browser_show( + infrared->dialogs, infrared->file_path, infrared->file_path, &browser_options)) { + const char* file_path = furi_string_get_cstr(infrared->file_path); infrared_show_loading_popup(infrared, true); - success = infrared_remote_load(infrared->remote, infrared->file_path); + const bool remote_loaded = infrared_remote_load(infrared->remote, file_path); infrared_show_loading_popup(infrared, false); - } - if(success) { - scene_manager_next_scene(scene_manager, InfraredSceneRemote); - } else { - scene_manager_previous_scene(scene_manager); + if(remote_loaded) { + scene_manager_next_scene(scene_manager, InfraredSceneRemote); + return; + } else { + infrared_show_error_message(infrared, "Failed to load\n\"%s\"", file_path); + } } + + scene_manager_previous_scene(scene_manager); } bool infrared_scene_remote_list_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/main/infrared/scenes/infrared_scene_rpc.c b/applications/main/infrared/scenes/infrared_scene_rpc.c index 04f17416d90..fa5a599afa7 100644 --- a/applications/main/infrared/scenes/infrared_scene_rpc.c +++ b/applications/main/infrared/scenes/infrared_scene_rpc.c @@ -1,4 +1,4 @@ -#include "../infrared_i.h" +#include "../infrared_app_i.h" #include typedef enum { @@ -8,7 +8,7 @@ typedef enum { } InfraredRpcState; void infrared_scene_rpc_on_enter(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; Popup* popup = infrared->popup; popup_set_header(popup, "Infrared", 89, 42, AlignCenter, AlignBottom); @@ -27,7 +27,7 @@ void infrared_scene_rpc_on_enter(void* context) { } bool infrared_scene_rpc_on_event(void* context, SceneManagerEvent event) { - Infrared* infrared = context; + InfraredApp* infrared = context; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { @@ -43,7 +43,8 @@ bool infrared_scene_rpc_on_event(void* context, SceneManagerEvent event) { const char* arg = rpc_system_app_get_data(infrared->rpc_ctx); if(arg && (state == InfraredRpcStateIdle)) { furi_string_set(infrared->file_path, arg); - result = infrared_remote_load(infrared->remote, infrared->file_path); + result = infrared_remote_load( + infrared->remote, furi_string_get_cstr(infrared->file_path)); if(result) { scene_manager_set_scene_state( infrared->scene_manager, InfraredSceneRpc, InfraredRpcStateLoaded); @@ -61,7 +62,7 @@ bool infrared_scene_rpc_on_event(void* context, SceneManagerEvent event) { const char* arg = rpc_system_app_get_data(infrared->rpc_ctx); if(arg && (state == InfraredRpcStateLoaded)) { size_t button_index = 0; - if(infrared_remote_find_button_by_name(infrared->remote, arg, &button_index)) { + if(infrared_remote_get_signal_index(infrared->remote, arg, &button_index)) { infrared_tx_start_button_index(infrared, button_index); result = true; scene_manager_set_scene_state( @@ -91,7 +92,7 @@ bool infrared_scene_rpc_on_event(void* context, SceneManagerEvent event) { } void infrared_scene_rpc_on_exit(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; if(scene_manager_get_scene_state(infrared->scene_manager, InfraredSceneRpc) == InfraredRpcStateSending) { infrared_tx_stop(infrared); diff --git a/applications/main/infrared/scenes/infrared_scene_start.c b/applications/main/infrared/scenes/infrared_scene_start.c index c7df0f45ba6..0e23bb7b8c9 100644 --- a/applications/main/infrared/scenes/infrared_scene_start.c +++ b/applications/main/infrared/scenes/infrared_scene_start.c @@ -1,4 +1,4 @@ -#include "../infrared_i.h" +#include "../infrared_app_i.h" enum SubmenuIndex { SubmenuIndexUniversalRemotes, @@ -8,12 +8,12 @@ enum SubmenuIndex { }; static void infrared_scene_start_submenu_callback(void* context, uint32_t index) { - Infrared* infrared = context; + InfraredApp* infrared = context; view_dispatcher_send_custom_event(infrared->view_dispatcher, index); } void infrared_scene_start_on_enter(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; Submenu* submenu = infrared->submenu; SceneManager* scene_manager = infrared->scene_manager; @@ -50,7 +50,7 @@ void infrared_scene_start_on_enter(void* context) { } bool infrared_scene_start_on_event(void* context, SceneManagerEvent event) { - Infrared* infrared = context; + InfraredApp* infrared = context; SceneManager* scene_manager = infrared->scene_manager; bool consumed = false; @@ -79,6 +79,6 @@ bool infrared_scene_start_on_event(void* context, SceneManagerEvent event) { } void infrared_scene_start_on_exit(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; submenu_reset(infrared->submenu); } diff --git a/applications/main/infrared/scenes/infrared_scene_universal.c b/applications/main/infrared/scenes/infrared_scene_universal.c index e09abde70e9..197478e3345 100644 --- a/applications/main/infrared/scenes/infrared_scene_universal.c +++ b/applications/main/infrared/scenes/infrared_scene_universal.c @@ -1,4 +1,4 @@ -#include "../infrared_i.h" +#include "../infrared_app_i.h" typedef enum { SubmenuIndexUniversalTV, @@ -8,12 +8,12 @@ typedef enum { } SubmenuIndex; static void infrared_scene_universal_submenu_callback(void* context, uint32_t index) { - Infrared* infrared = context; + InfraredApp* infrared = context; view_dispatcher_send_custom_event(infrared->view_dispatcher, index); } void infrared_scene_universal_on_enter(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; Submenu* submenu = infrared->submenu; submenu_add_item( @@ -47,7 +47,7 @@ void infrared_scene_universal_on_enter(void* context) { } bool infrared_scene_universal_on_event(void* context, SceneManagerEvent event) { - Infrared* infrared = context; + InfraredApp* infrared = context; SceneManager* scene_manager = infrared->scene_manager; bool consumed = false; @@ -72,6 +72,6 @@ bool infrared_scene_universal_on_event(void* context, SceneManagerEvent event) { } void infrared_scene_universal_on_exit(void* context) { - Infrared* infrared = context; + InfraredApp* infrared = context; submenu_reset(infrared->submenu); } diff --git a/applications/main/infrared/scenes/infrared_scene_universal_ac.c b/applications/main/infrared/scenes/infrared_scene_universal_ac.c index 5f762d122fd..764a9518909 100644 --- a/applications/main/infrared/scenes/infrared_scene_universal_ac.c +++ b/applications/main/infrared/scenes/infrared_scene_universal_ac.c @@ -1,11 +1,11 @@ -#include "../infrared_i.h" +#include "../infrared_app_i.h" #include "common/infrared_scene_universal_common.h" void infrared_scene_universal_ac_on_enter(void* context) { infrared_scene_universal_common_on_enter(context); - Infrared* infrared = context; + InfraredApp* infrared = context; ButtonPanel* button_panel = infrared->button_panel; InfraredBruteForce* brute_force = infrared->brute_force; diff --git a/applications/main/infrared/scenes/infrared_scene_universal_audio.c b/applications/main/infrared/scenes/infrared_scene_universal_audio.c index 3938b6080d3..241a22bcbb7 100644 --- a/applications/main/infrared/scenes/infrared_scene_universal_audio.c +++ b/applications/main/infrared/scenes/infrared_scene_universal_audio.c @@ -1,11 +1,11 @@ -#include "../infrared_i.h" +#include "../infrared_app_i.h" #include "common/infrared_scene_universal_common.h" void infrared_scene_universal_audio_on_enter(void* context) { infrared_scene_universal_common_on_enter(context); - Infrared* infrared = context; + InfraredApp* infrared = context; ButtonPanel* button_panel = infrared->button_panel; InfraredBruteForce* brute_force = infrared->brute_force; diff --git a/applications/main/infrared/scenes/infrared_scene_universal_projector.c b/applications/main/infrared/scenes/infrared_scene_universal_projector.c index 27ca46ea9de..d8520deb39e 100644 --- a/applications/main/infrared/scenes/infrared_scene_universal_projector.c +++ b/applications/main/infrared/scenes/infrared_scene_universal_projector.c @@ -1,11 +1,11 @@ -#include "../infrared_i.h" +#include "../infrared_app_i.h" #include "common/infrared_scene_universal_common.h" void infrared_scene_universal_projector_on_enter(void* context) { infrared_scene_universal_common_on_enter(context); - Infrared* infrared = context; + InfraredApp* infrared = context; ButtonPanel* button_panel = infrared->button_panel; InfraredBruteForce* brute_force = infrared->brute_force; diff --git a/applications/main/infrared/scenes/infrared_scene_universal_tv.c b/applications/main/infrared/scenes/infrared_scene_universal_tv.c index f2958d887d5..6031205f551 100644 --- a/applications/main/infrared/scenes/infrared_scene_universal_tv.c +++ b/applications/main/infrared/scenes/infrared_scene_universal_tv.c @@ -1,11 +1,11 @@ -#include "../infrared_i.h" +#include "../infrared_app_i.h" #include "common/infrared_scene_universal_common.h" void infrared_scene_universal_tv_on_enter(void* context) { infrared_scene_universal_common_on_enter(context); - Infrared* infrared = context; + InfraredApp* infrared = context; ButtonPanel* button_panel = infrared->button_panel; InfraredBruteForce* brute_force = infrared->brute_force; diff --git a/applications/main/infrared/views/infrared_move_view.c b/applications/main/infrared/views/infrared_move_view.c index d838a5f828f..374a65a44e8 100644 --- a/applications/main/infrared/views/infrared_move_view.c +++ b/applications/main/infrared/views/infrared_move_view.c @@ -1,10 +1,11 @@ #include "infrared_move_view.h" +#include + #include #include -#include -#include +#include #define LIST_ITEMS 4U #define LIST_LINE_H 13U @@ -13,42 +14,41 @@ struct InfraredMoveView { View* view; - InfraredMoveCallback move_cb; - void* cb_context; + InfraredMoveCallback callback; + void* callback_context; }; +ARRAY_DEF(InfraredMoveViewItemArray, const char*, M_CSTR_DUP_OPLIST); //-V575 + typedef struct { - const char** btn_names; - uint32_t btn_number; + InfraredMoveViewItemArray_t labels; int32_t list_offset; - int32_t item_idx; + int32_t current_idx; + int32_t start_idx; bool is_moving; - - InfraredMoveGetItemCallback get_item_cb; } InfraredMoveViewModel; static void infrared_move_view_draw_callback(Canvas* canvas, void* _model) { InfraredMoveViewModel* model = _model; - UNUSED(model); - canvas_set_color(canvas, ColorBlack); canvas_set_font(canvas, FontPrimary); elements_multiline_text_aligned( canvas, canvas_width(canvas) / 2, 0, AlignCenter, AlignTop, "Select a button to move"); - bool show_scrollbar = model->btn_number > LIST_ITEMS; + const size_t btn_number = InfraredMoveViewItemArray_size(model->labels); + const bool show_scrollbar = btn_number > LIST_ITEMS; canvas_set_font(canvas, FontSecondary); - for(uint32_t i = 0; i < MIN(model->btn_number, LIST_ITEMS); i++) { - int32_t idx = CLAMP((uint32_t)(i + model->list_offset), model->btn_number, 0u); - uint8_t x_offset = (model->is_moving && model->item_idx == idx) ? MOVE_X_OFFSET : 0; + for(uint32_t i = 0; i < MIN(btn_number, LIST_ITEMS); i++) { + int32_t idx = CLAMP((uint32_t)(i + model->list_offset), btn_number, 0U); + uint8_t x_offset = (model->is_moving && model->current_idx == idx) ? MOVE_X_OFFSET : 0; uint8_t y_offset = HEADER_H + i * LIST_LINE_H; uint8_t box_end_x = canvas_width(canvas) - (show_scrollbar ? 6 : 1); canvas_set_color(canvas, ColorBlack); - if(model->item_idx == idx) { + if(model->current_idx == idx) { canvas_draw_box(canvas, x_offset, y_offset, box_end_x - x_offset, LIST_LINE_H); canvas_set_color(canvas, ColorWhite); @@ -60,7 +60,12 @@ static void infrared_move_view_draw_callback(Canvas* canvas, void* _model) { canvas_draw_dot(canvas, box_end_x - 1, y_offset + LIST_LINE_H - 1); } canvas_draw_str_aligned( - canvas, x_offset + 3, y_offset + 3, AlignLeft, AlignTop, model->btn_names[idx]); + canvas, + x_offset + 3, + y_offset + 3, + AlignLeft, + AlignTop, + *InfraredMoveViewItemArray_cget(model->labels, idx)); } if(show_scrollbar) { @@ -69,22 +74,22 @@ static void infrared_move_view_draw_callback(Canvas* canvas, void* _model) { canvas_width(canvas), HEADER_H, canvas_height(canvas) - HEADER_H, - model->item_idx, - model->btn_number); + model->current_idx, + btn_number); } } static void update_list_offset(InfraredMoveViewModel* model) { - int32_t bounds = model->btn_number > (LIST_ITEMS - 1) ? 2 : model->btn_number; - - if((model->btn_number > (LIST_ITEMS - 1)) && - (model->item_idx >= ((int32_t)model->btn_number - 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->btn_number - bounds, 0); - } else if(model->list_offset > model->item_idx - bounds) { - model->list_offset = CLAMP(model->item_idx - 1, (int32_t)model->btn_number - bounds, 0); + const size_t btn_number = InfraredMoveViewItemArray_size(model->labels); + const int32_t bounds = btn_number > (LIST_ITEMS - 1) ? 2 : btn_number; + + if((btn_number > (LIST_ITEMS - 1)) && (model->current_idx >= ((int32_t)btn_number - 1))) { + model->list_offset = model->current_idx - (LIST_ITEMS - 1); + } else if(model->list_offset < model->current_idx - bounds) { + model->list_offset = + CLAMP(model->current_idx - (int32_t)(LIST_ITEMS - 2), (int32_t)btn_number - bounds, 0); + } else if(model->list_offset > model->current_idx - bounds) { + model->list_offset = CLAMP(model->current_idx - 1, (int32_t)btn_number - bounds, 0); } } @@ -95,117 +100,130 @@ static bool infrared_move_view_input_callback(InputEvent* event, void* context) if(((event->type == InputTypeShort || event->type == InputTypeRepeat)) && ((event->key == InputKeyUp) || (event->key == InputKeyDown))) { - bool is_moving = false; - uint32_t index_old = 0; - uint32_t index_new = 0; with_view_model( move_view->view, InfraredMoveViewModel * model, { - is_moving = model->is_moving; - index_old = model->item_idx; + const size_t btn_number = InfraredMoveViewItemArray_size(model->labels); + const int32_t item_idx_prev = model->current_idx; + if(event->key == InputKeyUp) { - if(model->item_idx <= 0) { - model->item_idx = model->btn_number; + if(model->current_idx <= 0) { + model->current_idx = btn_number; } - model->item_idx--; + model->current_idx--; + } else if(event->key == InputKeyDown) { - model->item_idx++; - if(model->item_idx >= (int32_t)(model->btn_number)) { - model->item_idx = 0; + model->current_idx++; + if(model->current_idx >= (int32_t)(btn_number)) { + model->current_idx = 0; } } - index_new = model->item_idx; + + if(model->is_moving) { + InfraredMoveViewItemArray_swap_at( + model->labels, item_idx_prev, model->current_idx); + } + update_list_offset(model); }, - !is_moving); - if((is_moving) && (move_view->move_cb)) { - move_view->move_cb(index_old, index_new, move_view->cb_context); - infrared_move_view_list_update(move_view); - } + true); + consumed = true; - } - if((event->key == InputKeyOk) && (event->type == InputTypeShort)) { + } else if((event->key == InputKeyOk) && (event->type == InputTypeShort)) { with_view_model( move_view->view, InfraredMoveViewModel * model, - { model->is_moving = !(model->is_moving); }, + { + if(!model->is_moving) { + model->start_idx = model->current_idx; + } else if(move_view->callback) { + move_view->callback( + model->start_idx, model->current_idx, move_view->callback_context); + } + model->is_moving = !(model->is_moving); + }, true); + consumed = true; - } - return consumed; -} -static void infrared_move_view_on_exit(void* context) { - furi_assert(context); - InfraredMoveView* move_view = context; + } else if(event->key == InputKeyBack) { + with_view_model( + move_view->view, + InfraredMoveViewModel * model, + { + if(model->is_moving && move_view->callback) { + move_view->callback( + model->start_idx, model->current_idx, move_view->callback_context); + } + model->is_moving = false; + }, + false); - with_view_model( - move_view->view, - InfraredMoveViewModel * model, - { - if(model->btn_names) { - free(model->btn_names); - model->btn_names = NULL; - } - model->btn_number = 0; - model->get_item_cb = NULL; - }, - false); - move_view->cb_context = NULL; -} + // Not consuming, Back event is passed thru + } -void infrared_move_view_set_callback(InfraredMoveView* move_view, InfraredMoveCallback callback) { - furi_assert(move_view); - move_view->move_cb = callback; + return consumed; } -void infrared_move_view_list_init( +void infrared_move_view_set_callback( InfraredMoveView* move_view, - uint32_t item_count, - InfraredMoveGetItemCallback load_cb, + InfraredMoveCallback callback, void* context) { furi_assert(move_view); - move_view->cb_context = context; + move_view->callback = callback; + move_view->callback_context = context; +} + +void infrared_move_view_add_item(InfraredMoveView* move_view, const char* label) { with_view_model( move_view->view, InfraredMoveViewModel * model, - { - furi_assert(model->btn_names == NULL); - model->btn_names = malloc(sizeof(char*) * item_count); - model->btn_number = item_count; - model->get_item_cb = load_cb; - }, - false); + { InfraredMoveViewItemArray_push_back(model->labels, label); }, + true); } -void infrared_move_view_list_update(InfraredMoveView* move_view) { - furi_assert(move_view); +void infrared_move_view_reset(InfraredMoveView* move_view) { with_view_model( move_view->view, InfraredMoveViewModel * model, { - for(uint32_t i = 0; i < model->btn_number; i++) { - if(!model->get_item_cb) break; - model->btn_names[i] = model->get_item_cb(i, move_view->cb_context); - } + InfraredMoveViewItemArray_reset(model->labels); + model->list_offset = 0; + model->start_idx = 0; + model->current_idx = 0; + model->is_moving = false; }, - true); + false); + move_view->callback_context = NULL; } InfraredMoveView* infrared_move_view_alloc(void) { InfraredMoveView* move_view = malloc(sizeof(InfraredMoveView)); + move_view->view = view_alloc(); view_allocate_model(move_view->view, ViewModelTypeLocking, sizeof(InfraredMoveViewModel)); view_set_draw_callback(move_view->view, infrared_move_view_draw_callback); view_set_input_callback(move_view->view, infrared_move_view_input_callback); - view_set_exit_callback(move_view->view, infrared_move_view_on_exit); view_set_context(move_view->view, move_view); + + with_view_model( + move_view->view, + InfraredMoveViewModel * model, + { InfraredMoveViewItemArray_init(model->labels); }, + true); + return move_view; } void infrared_move_view_free(InfraredMoveView* move_view) { + with_view_model( + move_view->view, + InfraredMoveViewModel * model, + { InfraredMoveViewItemArray_clear(model->labels); }, + true); + view_free(move_view->view); free(move_view); } diff --git a/applications/main/infrared/views/infrared_move_view.h b/applications/main/infrared/views/infrared_move_view.h index b9b0cd864a5..0ab15ce0d1e 100644 --- a/applications/main/infrared/views/infrared_move_view.h +++ b/applications/main/infrared/views/infrared_move_view.h @@ -6,20 +6,17 @@ typedef struct InfraredMoveView InfraredMoveView; typedef void (*InfraredMoveCallback)(uint32_t index_old, uint32_t index_new, void* context); -typedef const char* (*InfraredMoveGetItemCallback)(uint32_t index, void* context); - InfraredMoveView* infrared_move_view_alloc(void); void infrared_move_view_free(InfraredMoveView* debug_view); View* infrared_move_view_get_view(InfraredMoveView* debug_view); -void infrared_move_view_set_callback(InfraredMoveView* move_view, InfraredMoveCallback callback); - -void infrared_move_view_list_init( +void infrared_move_view_set_callback( InfraredMoveView* move_view, - uint32_t item_count, - InfraredMoveGetItemCallback load_cb, + InfraredMoveCallback callback, void* context); -void infrared_move_view_list_update(InfraredMoveView* move_view); \ No newline at end of file +void infrared_move_view_add_item(InfraredMoveView* move_view, const char* label); + +void infrared_move_view_reset(InfraredMoveView* move_view); From 9af81ce8d03320ec73bafcda132199e7d82d8674 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Tue, 31 Oct 2023 19:40:32 +0900 Subject: [PATCH 793/824] Furi: cleanup crash use (#3175) * Furi: optional message in furi_crash and furi_halt * Consistent furi_crash use * UnitTests: crash instead of assert * furi: check: fixed macro for default arg * unit_tests: fixed crashes everywhere * lib: infrared: fixed PVS warnings * furi: eliminated __FURI_ASSERT_MESSAGE_FLAG * Furi: update check.h docs * Furi: add check.h usage note * Docs: grammar --------- Co-authored-by: hedger --- applications/debug/unit_tests/bt/bt_test.c | 4 +- .../debug/unit_tests/infrared/infrared_test.c | 2 +- applications/debug/unit_tests/nfc/nfc_test.c | 2 +- .../debug/unit_tests/nfc/nfc_transport.c | 50 +++++++++---------- .../infrared_scene_edit_button_select.c | 2 +- .../scenes/infrared_scene_edit_delete.c | 4 +- .../scenes/infrared_scene_edit_delete_done.c | 2 +- .../scenes/infrared_scene_edit_rename.c | 4 +- .../desktop/animations/animation_manager.c | 6 +-- .../desktop/views/desktop_view_pin_input.c | 4 +- applications/services/gui/canvas.c | 8 +-- applications/services/gui/elements.c | 2 +- .../services/gui/modules/byte_input.c | 4 +- applications/services/gui/modules/popup.c | 2 +- .../services/gui/modules/text_input.c | 4 +- applications/services/gui/view.c | 4 +- applications/services/gui/view_dispatcher.c | 2 +- applications/services/rpc/rpc_app.c | 2 +- .../scenes/desktop_settings_scene_pin_auth.c | 2 +- .../scenes/desktop_settings_scene_pin_error.c | 2 +- .../desktop_settings_scene_pin_setup_howto.c | 2 +- .../desktop_settings_scene_pin_setup_howto2.c | 2 +- applications/system/hid_app/hid.c | 22 ++++---- documentation/FuriCheck.md | 40 +++++++++++++++ furi/core/check.c | 4 +- furi/core/check.h | 32 ++++++++---- lib/ibutton/ibutton_protocols.c | 2 +- .../nec/infrared_encoder_nec.c | 2 +- .../sirc/infrared_encoder_sirc.c | 2 +- lib/infrared/worker/infrared_transmit.c | 2 +- lib/infrared/worker/infrared_worker.c | 12 ++--- targets/f18/api_symbols.csv | 6 +-- targets/f7/api_symbols.csv | 6 +-- targets/f7/furi_hal/furi_hal_infrared.c | 14 +++--- targets/f7/furi_hal/furi_hal_spi.c | 2 +- targets/f7/furi_hal/furi_hal_subghz.c | 2 +- targets/f7/furi_hal/furi_hal_version.c | 2 +- 37 files changed, 159 insertions(+), 107 deletions(-) create mode 100644 documentation/FuriCheck.md diff --git a/applications/debug/unit_tests/bt/bt_test.c b/applications/debug/unit_tests/bt/bt_test.c index 2cbfd684a29..32cf6533f88 100644 --- a/applications/debug/unit_tests/bt/bt_test.c +++ b/applications/debug/unit_tests/bt/bt_test.c @@ -28,7 +28,7 @@ void bt_test_alloc() { } void bt_test_free() { - furi_assert(bt_test); + furi_check(bt_test); free(bt_test->nvm_ram_buff_ref); free(bt_test->nvm_ram_buff_dut); bt_keys_storage_free(bt_test->bt_keys_storage); @@ -89,7 +89,7 @@ static void bt_test_keys_remove_test_file() { } MU_TEST(bt_test_keys_storage_serial_profile) { - furi_assert(bt_test); + furi_check(bt_test); bt_test_keys_remove_test_file(); bt_test_keys_storage_profile(); diff --git a/applications/debug/unit_tests/infrared/infrared_test.c b/applications/debug/unit_tests/infrared/infrared_test.c index b2acad470e6..294c2da9a55 100644 --- a/applications/debug/unit_tests/infrared/infrared_test.c +++ b/applications/debug/unit_tests/infrared/infrared_test.c @@ -27,7 +27,7 @@ static void infrared_test_alloc() { } static void infrared_test_free() { - furi_assert(test); + furi_check(test); infrared_free_decoder(test->decoder_handler); infrared_free_encoder(test->encoder_handler); flipper_format_free(test->ff); diff --git a/applications/debug/unit_tests/nfc/nfc_test.c b/applications/debug/unit_tests/nfc/nfc_test.c index e7406fab8af..2d647f8ef56 100644 --- a/applications/debug/unit_tests/nfc/nfc_test.c +++ b/applications/debug/unit_tests/nfc/nfc_test.c @@ -34,7 +34,7 @@ static void nfc_test_alloc() { } static void nfc_test_free() { - furi_assert(nfc_test); + furi_check(nfc_test); furi_record_close(RECORD_STORAGE); free(nfc_test); diff --git a/applications/debug/unit_tests/nfc/nfc_transport.c b/applications/debug/unit_tests/nfc/nfc_transport.c index ee2657bea17..e2e313fdef3 100644 --- a/applications/debug/unit_tests/nfc/nfc_transport.c +++ b/applications/debug/unit_tests/nfc/nfc_transport.c @@ -122,7 +122,7 @@ Nfc* nfc_alloc() { } void nfc_free(Nfc* instance) { - furi_assert(instance); + furi_check(instance); free(instance); } @@ -165,9 +165,9 @@ NfcError nfc_iso14443a_listener_set_col_res_data( uint8_t uid_len, uint8_t* atqa, uint8_t sak) { - furi_assert(instance); - furi_assert(uid); - furi_assert(atqa); + furi_check(instance); + furi_check(uid); + furi_check(atqa); nfc_prepare_col_res_data(instance, uid, uid_len, atqa, sak); @@ -176,7 +176,7 @@ NfcError nfc_iso14443a_listener_set_col_res_data( static int32_t nfc_worker_poller(void* context) { Nfc* instance = context; - furi_assert(instance->callback); + furi_check(instance->callback); instance->state = NfcStateReady; NfcCommand command = NfcCommandContinue; @@ -196,7 +196,7 @@ static int32_t nfc_worker_poller(void* context) { } static void nfc_worker_listener_pass_col_res(Nfc* instance, uint8_t* rx_data, uint16_t rx_bits) { - furi_assert(instance->col_res_status != Iso14443_3aColResStatusDone); + furi_check(instance->col_res_status != Iso14443_3aColResStatusDone); BitBuffer* tx_buffer = bit_buffer_alloc(NFC_MAX_BUFFER_SIZE); bool processed = false; @@ -255,7 +255,7 @@ static void nfc_worker_listener_pass_col_res(Nfc* instance, uint8_t* rx_data, ui static int32_t nfc_worker_listener(void* context) { Nfc* instance = context; - furi_assert(instance->callback); + furi_check(instance->callback); NfcMessage message = {}; @@ -295,17 +295,17 @@ static int32_t nfc_worker_listener(void* context) { } void nfc_start(Nfc* instance, NfcEventCallback callback, void* context) { - furi_assert(instance); - furi_assert(instance->worker_thread == NULL); + furi_check(instance); + furi_check(instance->worker_thread == NULL); if(instance->mode == NfcModeListener) { - furi_assert(listener_queue == NULL); + furi_check(listener_queue == NULL); // Check that poller didn't start - furi_assert(poller_queue == NULL); + furi_check(poller_queue == NULL); } else { - furi_assert(poller_queue == NULL); + furi_check(poller_queue == NULL); // Check that poller is started after listener - furi_assert(listener_queue); + furi_check(listener_queue); } instance->callback = callback; @@ -334,8 +334,8 @@ void nfc_start(Nfc* instance, NfcEventCallback callback, void* context) { } void nfc_stop(Nfc* instance) { - furi_assert(instance); - furi_assert(instance->worker_thread); + furi_check(instance); + furi_check(instance->worker_thread); if(instance->mode == NfcModeListener) { NfcMessage message = {.type = NfcMessageTypeAbort}; @@ -361,10 +361,10 @@ void nfc_stop(Nfc* instance) { // Called from worker thread NfcError nfc_listener_tx(Nfc* instance, const BitBuffer* tx_buffer) { - furi_assert(instance); - furi_assert(poller_queue); - furi_assert(listener_queue); - furi_assert(tx_buffer); + furi_check(instance); + furi_check(poller_queue); + furi_check(listener_queue); + furi_check(tx_buffer); NfcMessage message = {}; message.type = NfcMessageTypeTx; @@ -382,11 +382,11 @@ NfcError nfc_iso14443a_listener_tx_custom_parity(Nfc* instance, const BitBuffer* NfcError nfc_poller_trx(Nfc* instance, const BitBuffer* tx_buffer, BitBuffer* rx_buffer, uint32_t fwt) { - furi_assert(instance); - furi_assert(tx_buffer); - furi_assert(rx_buffer); - furi_assert(poller_queue); - furi_assert(listener_queue); + furi_check(instance); + furi_check(tx_buffer); + furi_check(rx_buffer); + furi_check(poller_queue); + furi_check(listener_queue); UNUSED(fwt); NfcError error = NfcErrorNone; @@ -396,7 +396,7 @@ NfcError message.data.data_bits = bit_buffer_get_size(tx_buffer); bit_buffer_write_bytes(tx_buffer, message.data.data, bit_buffer_get_size_bytes(tx_buffer)); // Tx - furi_assert(furi_message_queue_put(listener_queue, &message, FuriWaitForever) == FuriStatusOk); + furi_check(furi_message_queue_put(listener_queue, &message, FuriWaitForever) == FuriStatusOk); // Rx FuriStatus status = furi_message_queue_get(poller_queue, &message, 50); diff --git a/applications/main/infrared/scenes/infrared_scene_edit_button_select.c b/applications/main/infrared/scenes/infrared_scene_edit_button_select.c index a76b4e836b2..3fd59b5792b 100644 --- a/applications/main/infrared/scenes/infrared_scene_edit_button_select.c +++ b/applications/main/infrared/scenes/infrared_scene_edit_button_select.c @@ -48,7 +48,7 @@ bool infrared_scene_edit_button_select_on_event(void* context, SceneManagerEvent } else if(edit_mode == InfraredEditModeDelete) { scene_manager_next_scene(scene_manager, InfraredSceneEditDelete); } else { - furi_assert(0); + furi_crash(); } consumed = true; } diff --git a/applications/main/infrared/scenes/infrared_scene_edit_delete.c b/applications/main/infrared/scenes/infrared_scene_edit_delete.c index c1735da0829..0cb88efdb66 100644 --- a/applications/main/infrared/scenes/infrared_scene_edit_delete.c +++ b/applications/main/infrared/scenes/infrared_scene_edit_delete.c @@ -60,7 +60,7 @@ void infrared_scene_edit_delete_on_enter(void* context) { infrared_remote_get_name(remote), infrared_remote_get_signal_count(remote)); } else { - furi_assert(0); + furi_crash(); } dialog_ex_set_text(dialog_ex, infrared->text_store[0], 64, 31, AlignCenter, AlignCenter); @@ -101,7 +101,7 @@ bool infrared_scene_edit_delete_on_event(void* context, SceneManagerEvent event) success = infrared_remote_remove(remote); app_state->current_button_index = InfraredButtonIndexNone; } else { - furi_crash(NULL); + furi_crash(); } if(success) { diff --git a/applications/main/infrared/scenes/infrared_scene_edit_delete_done.c b/applications/main/infrared/scenes/infrared_scene_edit_delete_done.c index 0ee6399142b..9205db4c4e7 100644 --- a/applications/main/infrared/scenes/infrared_scene_edit_delete_done.c +++ b/applications/main/infrared/scenes/infrared_scene_edit_delete_done.c @@ -33,7 +33,7 @@ bool infrared_scene_edit_delete_done_on_event(void* context, SceneManagerEvent e view_dispatcher_stop(infrared->view_dispatcher); } } else { - furi_assert(0); + furi_crash(); } consumed = true; } diff --git a/applications/main/infrared/scenes/infrared_scene_edit_rename.c b/applications/main/infrared/scenes/infrared_scene_edit_rename.c index e29f108651d..178690926d4 100644 --- a/applications/main/infrared/scenes/infrared_scene_edit_rename.c +++ b/applications/main/infrared/scenes/infrared_scene_edit_rename.c @@ -42,7 +42,7 @@ void infrared_scene_edit_rename_on_enter(void* context) { furi_string_free(folder_path); } else { - furi_crash(NULL); + furi_crash(); } text_input_set_result_callback( @@ -81,7 +81,7 @@ bool infrared_scene_edit_rename_on_event(void* context, SceneManagerEvent event) } else if(edit_target == InfraredEditTargetRemote) { success = infrared_rename_current_remote(infrared, infrared->text_store[0]); } else { - furi_crash(NULL); + furi_crash(); } if(success) { diff --git a/applications/services/desktop/animations/animation_manager.c b/applications/services/desktop/animations/animation_manager.c index f4c8f17a3ae..873fb6aa2c4 100644 --- a/applications/services/desktop/animations/animation_manager.c +++ b/applications/services/desktop/animations/animation_manager.c @@ -456,7 +456,7 @@ void animation_manager_unload_and_stall_animation(AnimationManager* animation_ma } furi_timer_stop(animation_manager->idle_animation_timer); } else { - furi_assert(0); + furi_crash(); } FURI_LOG_I( @@ -528,7 +528,7 @@ void animation_manager_load_and_continue_animation(AnimationManager* animation_m } } else { /* Unknown state is an error. But not in release version.*/ - furi_assert(0); + furi_crash(); } /* if can't restore previous animation - select new */ @@ -564,7 +564,7 @@ static void animation_manager_switch_to_one_shot_view(AnimationManager* animatio } else if(stats.level == 2) { one_shot_view_start_animation(animation_manager->one_shot_view, &A_Levelup2_128x64); } else { - furi_assert(0); + furi_crash(); } } diff --git a/applications/services/desktop/views/desktop_view_pin_input.c b/applications/services/desktop/views/desktop_view_pin_input.c index d3dadd7d713..93bbffedc6f 100644 --- a/applications/services/desktop/views/desktop_view_pin_input.c +++ b/applications/services/desktop/views/desktop_view_pin_input.c @@ -78,7 +78,7 @@ static bool desktop_view_pin_input_input(InputEvent* event, void* context) { } break; default: - furi_assert(0); + furi_crash(); break; } } @@ -129,7 +129,7 @@ static void desktop_view_pin_input_draw_cells(Canvas* canvas, DesktopViewPinInpu canvas_draw_icon_ex(canvas, x + 2, y + 3, &I_Pin_arrow_up_7x9, IconRotation90); break; default: - furi_assert(0); + furi_crash(); break; } } diff --git a/applications/services/gui/canvas.c b/applications/services/gui/canvas.c index 37edc5d33d8..85c0528530d 100644 --- a/applications/services/gui/canvas.c +++ b/applications/services/gui/canvas.c @@ -135,7 +135,7 @@ void canvas_set_font(Canvas* canvas, Font font) { } else if(font == FontBigNumbers) { u8g2_SetFont(&canvas->fb, u8g2_font_profont22_tn); } else { - furi_crash(NULL); + furi_crash(); } } @@ -175,7 +175,7 @@ void canvas_draw_str_aligned( x -= (u8g2_GetStrWidth(&canvas->fb, str) / 2); break; default: - furi_crash(NULL); + furi_crash(); break; } @@ -189,7 +189,7 @@ void canvas_draw_str_aligned( y += (u8g2_GetAscent(&canvas->fb) / 2); break; default: - furi_crash(NULL); + furi_crash(); break; } @@ -530,7 +530,7 @@ void canvas_set_orientation(Canvas* canvas, CanvasOrientation orientation) { rotate_cb = U8G2_R1; break; default: - furi_assert(0); + furi_crash(); } if(need_swap) FURI_SWAP(canvas->width, canvas->height); diff --git a/applications/services/gui/elements.c b/applications/services/gui/elements.c index a6ab84fb8d8..e92c2433cda 100644 --- a/applications/services/gui/elements.c +++ b/applications/services/gui/elements.c @@ -232,7 +232,7 @@ static size_t } else if(horizontal == AlignRight) { px_left = x; } else { - furi_assert(0); + furi_crash(); } if(len_px > px_left) { diff --git a/applications/services/gui/modules/byte_input.c b/applications/services/gui/modules/byte_input.c index 4846bbd8cc9..e9cd78da02b 100644 --- a/applications/services/gui/modules/byte_input.c +++ b/applications/services/gui/modules/byte_input.c @@ -79,7 +79,7 @@ static uint8_t byte_input_get_row_size(uint8_t row_index) { row_size = COUNT_OF(keyboard_keys_row_2); break; default: - furi_crash(NULL); + furi_crash(); } return row_size; @@ -102,7 +102,7 @@ static const ByteInputKey* byte_input_get_row(uint8_t row_index) { row = keyboard_keys_row_2; break; default: - furi_crash(NULL); + furi_crash(); } return row; diff --git a/applications/services/gui/modules/popup.c b/applications/services/gui/modules/popup.c index d75abb95fa6..520efceef96 100644 --- a/applications/services/gui/modules/popup.c +++ b/applications/services/gui/modules/popup.c @@ -98,7 +98,7 @@ void popup_start_timer(void* context) { if(timer_period == 0) timer_period = 1; if(furi_timer_start(popup->timer, timer_period) != FuriStatusOk) { - furi_assert(0); + furi_crash(); }; } } diff --git a/applications/services/gui/modules/text_input.c b/applications/services/gui/modules/text_input.c index 86b7bca1e0f..50453cf22c4 100644 --- a/applications/services/gui/modules/text_input.c +++ b/applications/services/gui/modules/text_input.c @@ -101,7 +101,7 @@ static uint8_t get_row_size(uint8_t row_index) { row_size = COUNT_OF(keyboard_keys_row_3); break; default: - furi_crash(NULL); + furi_crash(); } return row_size; @@ -121,7 +121,7 @@ static const TextInputKey* get_row(uint8_t row_index) { row = keyboard_keys_row_3; break; default: - furi_crash(NULL); + furi_crash(); } return row; diff --git a/applications/services/gui/view.c b/applications/services/gui/view.c index 07ae072a171..316f5c612a1 100644 --- a/applications/services/gui/view.c +++ b/applications/services/gui/view.c @@ -82,7 +82,7 @@ void view_allocate_model(View* view, ViewModelType type, size_t size) { model->data = malloc(size); view->model = model; } else { - furi_crash(NULL); + furi_crash(); } } @@ -99,7 +99,7 @@ void view_free_model(View* view) { free(model); view->model = NULL; } else { - furi_crash(NULL); + furi_crash(); } } diff --git a/applications/services/gui/view_dispatcher.c b/applications/services/gui/view_dispatcher.c index 0119abc2002..87b07a87c47 100644 --- a/applications/services/gui/view_dispatcher.c +++ b/applications/services/gui/view_dispatcher.c @@ -207,7 +207,7 @@ void view_dispatcher_attach_to_gui( } else if(type == ViewDispatcherTypeFullscreen) { gui_add_view_port(gui, view_dispatcher->view_port, GuiLayerFullscreen); } else { - furi_check(NULL); + furi_crash(); } view_dispatcher->gui = gui; } diff --git a/applications/services/rpc/rpc_app.c b/applications/services/rpc/rpc_app.c index bf44ed2deed..e86eaa493d7 100644 --- a/applications/services/rpc/rpc_app.c +++ b/applications/services/rpc/rpc_app.c @@ -62,7 +62,7 @@ static void rpc_system_app_start_process(const PB_Main* request, void* context) } else if(status == LoaderStatusOk) { result = PB_CommandStatus_OK; } else { - furi_crash(NULL); + furi_crash(); } } else { result = PB_CommandStatus_ERROR_INVALID_PARAMETERS; diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_auth.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_auth.c index be2ee48259b..b73fe347b2d 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_auth.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_auth.c @@ -68,7 +68,7 @@ bool desktop_settings_scene_pin_auth_on_event(void* context, SceneManagerEvent e } else if(state == SCENE_STATE_PIN_AUTH_DISABLE) { scene_manager_next_scene(app->scene_manager, DesktopSettingsAppScenePinDisable); } else { - furi_assert(0); + furi_crash(); } consumed = true; break; diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_error.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_error.c index 508992cee78..1ba3c1b2daf 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_error.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_error.c @@ -39,7 +39,7 @@ void desktop_settings_scene_pin_error_on_enter(void* context) { } else if(state == SCENE_STATE_PIN_ERROR_WRONG) { desktop_view_pin_input_set_label_primary(app->pin_input_view, 35, 8, "Wrong PIN!"); } else { - furi_assert(0); + furi_crash(); } desktop_view_pin_input_set_label_secondary(app->pin_input_view, 0, 8, NULL); desktop_view_pin_input_set_label_button(app->pin_input_view, "Retry"); diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup_howto.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup_howto.c index ec128246fa3..31eec3871ad 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup_howto.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup_howto.c @@ -32,7 +32,7 @@ bool desktop_settings_scene_pin_setup_howto_on_event(void* context, SceneManager consumed = true; break; default: - furi_crash(NULL); + furi_crash(); } } return consumed; diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup_howto2.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup_howto2.c index 44b8e1bf795..0ebf85c64b7 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup_howto2.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup_howto2.c @@ -52,7 +52,7 @@ bool desktop_settings_scene_pin_setup_howto2_on_event(void* context, SceneManage break; } default: - furi_crash(NULL); + furi_crash(); } } return consumed; diff --git a/applications/system/hid_app/hid.c b/applications/system/hid_app/hid.c index 6c4b928dea7..a42fc609178 100644 --- a/applications/system/hid_app/hid.c +++ b/applications/system/hid_app/hid.c @@ -264,7 +264,7 @@ void hid_hal_keyboard_press(Hid* instance, uint16_t event) { } else if(instance->transport == HidTransportUsb) { furi_hal_hid_kb_press(event); } else { - furi_crash(NULL); + furi_crash(); } } @@ -275,7 +275,7 @@ void hid_hal_keyboard_release(Hid* instance, uint16_t event) { } else if(instance->transport == HidTransportUsb) { furi_hal_hid_kb_release(event); } else { - furi_crash(NULL); + furi_crash(); } } @@ -286,7 +286,7 @@ void hid_hal_keyboard_release_all(Hid* instance) { } else if(instance->transport == HidTransportUsb) { furi_hal_hid_kb_release_all(); } else { - furi_crash(NULL); + furi_crash(); } } @@ -297,7 +297,7 @@ void hid_hal_consumer_key_press(Hid* instance, uint16_t event) { } else if(instance->transport == HidTransportUsb) { furi_hal_hid_consumer_key_press(event); } else { - furi_crash(NULL); + furi_crash(); } } @@ -308,7 +308,7 @@ void hid_hal_consumer_key_release(Hid* instance, uint16_t event) { } else if(instance->transport == HidTransportUsb) { furi_hal_hid_consumer_key_release(event); } else { - furi_crash(NULL); + furi_crash(); } } @@ -319,7 +319,7 @@ void hid_hal_consumer_key_release_all(Hid* instance) { } else if(instance->transport == HidTransportUsb) { furi_hal_hid_kb_release_all(); } else { - furi_crash(NULL); + furi_crash(); } } @@ -330,7 +330,7 @@ void hid_hal_mouse_move(Hid* instance, int8_t dx, int8_t dy) { } else if(instance->transport == HidTransportUsb) { furi_hal_hid_mouse_move(dx, dy); } else { - furi_crash(NULL); + furi_crash(); } } @@ -341,7 +341,7 @@ void hid_hal_mouse_scroll(Hid* instance, int8_t delta) { } else if(instance->transport == HidTransportUsb) { furi_hal_hid_mouse_scroll(delta); } else { - furi_crash(NULL); + furi_crash(); } } @@ -352,7 +352,7 @@ void hid_hal_mouse_press(Hid* instance, uint16_t event) { } else if(instance->transport == HidTransportUsb) { furi_hal_hid_mouse_press(event); } else { - furi_crash(NULL); + furi_crash(); } } @@ -363,7 +363,7 @@ void hid_hal_mouse_release(Hid* instance, uint16_t event) { } else if(instance->transport == HidTransportUsb) { furi_hal_hid_mouse_release(event); } else { - furi_crash(NULL); + furi_crash(); } } @@ -375,7 +375,7 @@ void hid_hal_mouse_release_all(Hid* instance) { furi_hal_hid_mouse_release(HID_MOUSE_BTN_LEFT); furi_hal_hid_mouse_release(HID_MOUSE_BTN_RIGHT); } else { - furi_crash(NULL); + furi_crash(); } } diff --git a/documentation/FuriCheck.md b/documentation/FuriCheck.md new file mode 100644 index 00000000000..02f3fc9173b --- /dev/null +++ b/documentation/FuriCheck.md @@ -0,0 +1,40 @@ +# Run time checks and forced system crash + +The best way to protect system integrity is to reduce amount cases that we must handle and crash the system as early as possible. +For that purpose we have bunch of helpers located in Furi Core check.h. + +## Couple notes before start + +- Definition of Crash - log event, save crash information in RTC and reboot the system. +- Definition of Halt - log event, stall the system. +- Debug and production builds behaves differently: debug build will never reset system in order to preserve state for debugging. +- If you have debugger connected we will stop before reboot automatically. +- All helpers accept optional MESSAGE_CSTR: it can be in RAM or Flash memory, but only messages from Flash will be shown after system reboot. +- MESSAGE_CSTR can be NULL, but macros magic already doing it for you, so just don't. + +## `furi_assert(CONDITION)` or `furi_assert(CONDITION, MESSAGE_CSTR)` + +Assert condition in development environment and crash the system if CONDITION is false. + +- Should be used at development stage in apps and services +- Keep in mind that release never contains this check +- Keep in mind that libraries never contains this check by default, use `LIB_DEBUG=1` if you need it +- Avoid putting function calls into CONDITION, since it may be omitted in some builds + +## `furi_check(CONDITION)` or `furi_check(CONDITION, MESSAGE_CSTR)` + +Always assert condition and crash the system if CONDITION is false. + +- Use it if you always need to check conditions + +## `furi_crash()` or `furi_crash(MESSAGE_CSTR)` + +Crash the system. + +- Use it to crash the system. For example: if abnormal condition detected. + +## `furi_halt()` or `furi_halt(MESSAGE_CSTR)` + +Halt the system. + +- We use it internally to shutdown flipper if poweroff is not possible. diff --git a/furi/core/check.c b/furi/core/check.c index 8888eddfb35..ea1de71425d 100644 --- a/furi/core/check.c +++ b/furi/core/check.c @@ -128,7 +128,7 @@ static void __furi_print_name(bool isr) { } } -FURI_NORETURN void __furi_crash() { +FURI_NORETURN void __furi_crash_implementation() { __disable_irq(); GET_MESSAGE_AND_STORE_REGISTERS(); @@ -179,7 +179,7 @@ FURI_NORETURN void __furi_crash() { __builtin_unreachable(); } -FURI_NORETURN void __furi_halt() { +FURI_NORETURN void __furi_halt_implementation() { __disable_irq(); GET_MESSAGE_AND_STORE_REGISTERS(); diff --git a/furi/core/check.h b/furi/core/check.h index 004422e807e..2d5df4cf6c4 100644 --- a/furi/core/check.h +++ b/furi/core/check.h @@ -28,39 +28,51 @@ extern "C" { #define __FURI_CHECK_MESSAGE_FLAG (0x02) /** Crash system */ -FURI_NORETURN void __furi_crash(); +FURI_NORETURN void __furi_crash_implementation(); /** Halt system */ -FURI_NORETURN void __furi_halt(); +FURI_NORETURN void __furi_halt_implementation(); /** Crash system with message. Show message after reboot. */ -#define furi_crash(message) \ +#define __furi_crash(message) \ do { \ register const void* r12 asm("r12") = (void*)message; \ asm volatile("sukima%=:" : : "r"(r12)); \ - __furi_crash(); \ + __furi_crash_implementation(); \ } while(0) +/** Crash system + * + * @param optional message (const char*) + */ +#define furi_crash(...) M_APPLY(__furi_crash, M_IF_EMPTY(__VA_ARGS__)((NULL), (__VA_ARGS__))) + /** Halt system with message. */ -#define furi_halt(message) \ +#define __furi_halt(message) \ do { \ register const void* r12 asm("r12") = (void*)message; \ asm volatile("sukima%=:" : : "r"(r12)); \ - __furi_halt(); \ + __furi_halt_implementation(); \ } while(0) +/** Halt system + * + * @param optional message (const char*) + */ +#define furi_halt(...) M_APPLY(__furi_halt, M_IF_EMPTY(__VA_ARGS__)((NULL), (__VA_ARGS__))) + /** Check condition and crash if check failed */ #define __furi_check(__e, __m) \ do { \ if(!(__e)) { \ - furi_crash(__m); \ + __furi_crash(__m); \ } \ } while(0) /** Check condition and crash if failed * * @param condition to check - * @param optional message + * @param optional message (const char*) */ #define furi_check(...) \ M_APPLY(__furi_check, M_DEFAULT_ARGS(2, (__FURI_CHECK_MESSAGE_FLAG), __VA_ARGS__)) @@ -70,7 +82,7 @@ FURI_NORETURN void __furi_halt(); #define __furi_assert(__e, __m) \ do { \ if(!(__e)) { \ - furi_crash(__m); \ + __furi_crash(__m); \ } \ } while(0) #else @@ -86,7 +98,7 @@ FURI_NORETURN void __furi_halt(); * @warning only will do check if firmware compiled in debug mode * * @param condition to check - * @param optional message + * @param optional message (const char*) */ #define furi_assert(...) \ M_APPLY(__furi_assert, M_DEFAULT_ARGS(2, (__FURI_ASSERT_MESSAGE_FLAG), __VA_ARGS__)) diff --git a/lib/ibutton/ibutton_protocols.c b/lib/ibutton/ibutton_protocols.c index 75aa225efe4..df74126708c 100644 --- a/lib/ibutton/ibutton_protocols.c +++ b/lib/ibutton/ibutton_protocols.c @@ -48,7 +48,7 @@ static void ibutton_protocols_get_group_by_id( local_id -= ibutton_protocol_groups[i]->protocol_count; } } - furi_crash(NULL); + furi_crash(); } iButtonProtocols* ibutton_protocols_alloc() { diff --git a/lib/infrared/encoder_decoder/nec/infrared_encoder_nec.c b/lib/infrared/encoder_decoder/nec/infrared_encoder_nec.c index 87f815142ab..47d8c3c6496 100644 --- a/lib/infrared/encoder_decoder/nec/infrared_encoder_nec.c +++ b/lib/infrared/encoder_decoder/nec/infrared_encoder_nec.c @@ -48,7 +48,7 @@ void infrared_encoder_nec_reset(void* encoder_ptr, const InfraredMessage* messag *data2 = (message->command & 0xFFC0) >> 6; encoder->bits_to_encode = 42; } else { - furi_assert(0); + furi_crash(); } } diff --git a/lib/infrared/encoder_decoder/sirc/infrared_encoder_sirc.c b/lib/infrared/encoder_decoder/sirc/infrared_encoder_sirc.c index 6adf2235ce4..39c2eb16678 100644 --- a/lib/infrared/encoder_decoder/sirc/infrared_encoder_sirc.c +++ b/lib/infrared/encoder_decoder/sirc/infrared_encoder_sirc.c @@ -23,7 +23,7 @@ void infrared_encoder_sirc_reset(void* encoder_ptr, const InfraredMessage* messa *data |= (message->address & 0x1FFF) << 7; encoder->bits_to_encode = 20; } else { - furi_assert(0); + furi_crash(); } } diff --git a/lib/infrared/worker/infrared_transmit.c b/lib/infrared/worker/infrared_transmit.c index 113fb632448..8f99c006621 100644 --- a/lib/infrared/worker/infrared_transmit.c +++ b/lib/infrared/worker/infrared_transmit.c @@ -88,7 +88,7 @@ FuriHalInfraredTxGetDataState state = FuriHalInfraredTxGetDataStateDone; } } else { - furi_crash(NULL); + furi_crash(); } return state; diff --git a/lib/infrared/worker/infrared_worker.c b/lib/infrared/worker/infrared_worker.c index 46effd420dc..5e3257e2604 100644 --- a/lib/infrared/worker/infrared_worker.c +++ b/lib/infrared/worker/infrared_worker.c @@ -367,10 +367,11 @@ static FuriHalInfraredTxGetDataState *duration = timing.duration; state = timing.state; } else { - furi_assert(0); + // Why bother if we crash anyway?.. *level = 0; *duration = 100; state = FuriHalInfraredTxGetDataStateDone; + furi_crash(); } uint32_t flags_set = furi_thread_flags_set( @@ -414,7 +415,7 @@ static bool infrared_get_new_signal(InfraredWorker* instance) { } else if(response == InfraredWorkerGetSignalResponseStop) { new_signal_obtained = false; } else { - furi_assert(0); + furi_crash(); } return new_signal_obtained; @@ -443,9 +444,8 @@ static bool infrared_worker_tx_fill_buffer(InfraredWorker* instance) { } if(status == InfraredStatusError) { - furi_assert(0); new_data_available = false; - break; + furi_crash(); } else if(status == InfraredStatusOk) { timing.state = FuriHalInfraredTxGetDataStateOk; } else if(status == InfraredStatusDone) { @@ -456,7 +456,7 @@ static bool infrared_worker_tx_fill_buffer(InfraredWorker* instance) { timing.state = FuriHalInfraredTxGetDataStateLastDone; } } else { - furi_assert(0); + furi_crash(); } uint32_t written_size = furi_stream_buffer_send(instance->stream, &timing, sizeof(InfraredWorkerTiming), 0); @@ -548,7 +548,7 @@ static int32_t infrared_worker_tx_thread(void* thread_context) { break; default: - furi_assert(0); + furi_crash(); break; } } diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index b4efd7911b5..cb62b40c40b 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,40.1,, +Version,+,41.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -302,10 +302,10 @@ Function,-,__eprintf,void,"const char*, const char*, unsigned int, const char*" Function,+,__errno,int*, Function,-,__fpclassifyd,int,double Function,-,__fpclassifyf,int,float -Function,+,__furi_crash,void, +Function,+,__furi_crash_implementation,void, Function,+,__furi_critical_enter,__FuriCriticalInfo, Function,+,__furi_critical_exit,void,__FuriCriticalInfo -Function,+,__furi_halt,void, +Function,+,__furi_halt_implementation,void, Function,-,__getdelim,ssize_t,"char**, size_t*, int, FILE*" Function,-,__getline,ssize_t,"char**, size_t*, FILE*" Function,-,__isinfd,int,double diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 7599230d722..9d9c1a01b37 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,40.1,, +Version,+,41.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -370,10 +370,10 @@ Function,-,__eprintf,void,"const char*, const char*, unsigned int, const char*" Function,+,__errno,int*, Function,-,__fpclassifyd,int,double Function,-,__fpclassifyf,int,float -Function,+,__furi_crash,void, +Function,+,__furi_crash_implementation,void, Function,+,__furi_critical_enter,__FuriCriticalInfo, Function,+,__furi_critical_exit,void,__FuriCriticalInfo -Function,+,__furi_halt,void, +Function,+,__furi_halt_implementation,void, Function,-,__getdelim,ssize_t,"char**, size_t*, int, FILE*" Function,-,__getline,ssize_t,"char**, size_t*, FILE*" Function,-,__isinfd,int,double diff --git a/targets/f7/furi_hal/furi_hal_infrared.c b/targets/f7/furi_hal/furi_hal_infrared.c index d3e36c2b5d4..3b20b6bc3a6 100644 --- a/targets/f7/furi_hal/furi_hal_infrared.c +++ b/targets/f7/furi_hal/furi_hal_infrared.c @@ -125,7 +125,7 @@ static void furi_hal_infrared_tim_rx_isr() { if(infrared_tim_rx.capture_callback) infrared_tim_rx.capture_callback(infrared_tim_rx.capture_context, 1, duration); } else { - furi_assert(0); + furi_crash(); } } @@ -141,7 +141,7 @@ static void furi_hal_infrared_tim_rx_isr() { if(infrared_tim_rx.capture_callback) infrared_tim_rx.capture_callback(infrared_tim_rx.capture_context, 0, duration); } else { - furi_assert(0); + furi_crash(); } } } @@ -254,7 +254,7 @@ static uint8_t furi_hal_infrared_get_current_dma_tx_buffer(void) { } else if(buffer_adr == (uint32_t)infrared_tim_tx.buffer[1].data) { buf_num = 1; } else { - furi_assert(0); + furi_crash(); } return buf_num; } @@ -263,7 +263,7 @@ static void furi_hal_infrared_tx_dma_polarity_isr() { #if INFRARED_DMA_CH1_CHANNEL == LL_DMA_CHANNEL_1 if(LL_DMA_IsActiveFlag_TE1(INFRARED_DMA)) { LL_DMA_ClearFlag_TE1(INFRARED_DMA); - furi_crash(NULL); + furi_crash(); } if(LL_DMA_IsActiveFlag_TC1(INFRARED_DMA) && LL_DMA_IsEnabledIT_TC(INFRARED_DMA_CH1_DEF)) { LL_DMA_ClearFlag_TC1(INFRARED_DMA); @@ -285,7 +285,7 @@ static void furi_hal_infrared_tx_dma_isr() { #if INFRARED_DMA_CH2_CHANNEL == LL_DMA_CHANNEL_2 if(LL_DMA_IsActiveFlag_TE2(INFRARED_DMA)) { LL_DMA_ClearFlag_TE2(INFRARED_DMA); - furi_crash(NULL); + furi_crash(); } if(LL_DMA_IsActiveFlag_HT2(INFRARED_DMA) && LL_DMA_IsEnabledIT_HT(INFRARED_DMA_CH2_DEF)) { LL_DMA_ClearFlag_HT2(INFRARED_DMA); @@ -303,7 +303,7 @@ static void furi_hal_infrared_tx_dma_isr() { } else if(furi_hal_infrared_state == InfraredStateAsyncTxStopReq) { /* fallthrough */ } else { - furi_crash(NULL); + furi_crash(); } } if(LL_DMA_IsActiveFlag_TC2(INFRARED_DMA) && LL_DMA_IsEnabledIT_TC(INFRARED_DMA_CH2_DEF)) { @@ -596,7 +596,7 @@ static void furi_hal_infrared_async_tx_free_resources(void) { void furi_hal_infrared_async_tx_start(uint32_t freq, float duty_cycle) { if((duty_cycle > 1) || (duty_cycle <= 0) || (freq > INFRARED_MAX_FREQUENCY) || (freq < INFRARED_MIN_FREQUENCY) || (infrared_tim_tx.data_callback == NULL)) { - furi_crash(NULL); + furi_crash(); } furi_assert(furi_hal_infrared_state == InfraredStateIdle); diff --git a/targets/f7/furi_hal/furi_hal_spi.c b/targets/f7/furi_hal/furi_hal_spi.c index a8884105aae..5c33760ea1c 100644 --- a/targets/f7/furi_hal/furi_hal_spi.c +++ b/targets/f7/furi_hal/furi_hal_spi.c @@ -221,7 +221,7 @@ bool furi_hal_spi_bus_trx_dma( dma_rx_req = LL_DMAMUX_REQ_SPI2_RX; dma_tx_req = LL_DMAMUX_REQ_SPI2_TX; } else { - furi_crash(NULL); + furi_crash(); } if(rx_buffer == NULL) { diff --git a/targets/f7/furi_hal/furi_hal_subghz.c b/targets/f7/furi_hal/furi_hal_subghz.c index ac71b5f6c73..f751463532e 100644 --- a/targets/f7/furi_hal/furi_hal_subghz.c +++ b/targets/f7/furi_hal/furi_hal_subghz.c @@ -604,7 +604,7 @@ static void furi_hal_subghz_async_tx_timer_isr() { furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullDown, GpioSpeedLow); LL_TIM_DisableCounter(TIM2); } else { - furi_crash(NULL); + furi_crash(); } } } diff --git a/targets/f7/furi_hal/furi_hal_version.c b/targets/f7/furi_hal/furi_hal_version.c index 859a8c39fc6..e4364a51892 100644 --- a/targets/f7/furi_hal/furi_hal_version.c +++ b/targets/f7/furi_hal/furi_hal_version.c @@ -187,7 +187,7 @@ void furi_hal_version_init() { furi_hal_version_load_otp_v2(); break; default: - furi_crash(NULL); + furi_crash(); } furi_hal_rtc_set_register(FuriHalRtcRegisterVersion, (uint32_t)version_get()); From bbe68d6ffc10998a364118abace7ca44ecf6ace7 Mon Sep 17 00:00:00 2001 From: hedger Date: Tue, 31 Oct 2023 15:27:58 +0400 Subject: [PATCH 794/824] [FL-3629] fbt: SD card resource handling speedup (#3178) * fbt: reduced size of resources dependency graphs, resulting in faster build task evaluation * lib: flipper_app: fixed error message & error handling for plugins --- firmware.scons | 8 +- .../plugins/plugin_manager.c | 3 +- scripts/fbt_tools/fbt_assets.py | 26 +++--- scripts/fbt_tools/fbt_resources.py | 93 +++++++++++-------- 4 files changed, 74 insertions(+), 56 deletions(-) diff --git a/firmware.scons b/firmware.scons index e8e50022c70..537774254cd 100644 --- a/firmware.scons +++ b/firmware.scons @@ -148,15 +148,11 @@ if env["IS_BASE_FIRMWARE"]: ) fw_artifacts.append(fw_extapps.sdk_tree) - # Resources for SD card - resources = fwenv.ResourcesDist( - _EXTRA_DIST=[fwenv["DOLPHIN_EXTERNAL_OUT_DIR"]], - ) - + # Resources & manifest for SD card manifest = fwenv.ManifestBuilder( "${RESOURCES_ROOT}/Manifest", - source=resources, GIT_UNIX_TIMESTAMP=get_git_commit_unix_timestamp(), + _EXTRA_DIST=[fwenv["DOLPHIN_EXTERNAL_OUT_DIR"]], ) fwenv.Replace(FW_RESOURCES_MANIFEST=manifest) fwenv.Alias("resources", manifest) diff --git a/lib/flipper_application/plugins/plugin_manager.c b/lib/flipper_application/plugins/plugin_manager.c index e2a7b83f422..8f30ed13ec5 100644 --- a/lib/flipper_application/plugins/plugin_manager.c +++ b/lib/flipper_application/plugins/plugin_manager.c @@ -66,7 +66,8 @@ PluginManagerError plugin_manager_load_single(PluginManager* manager, const char FlipperApplicationLoadStatus load_status = flipper_application_map_to_memory(lib); if(load_status != FlipperApplicationLoadStatusSuccess) { - FURI_LOG_E(TAG, "Failed to load module_demo_plugin1.fal"); + FURI_LOG_E(TAG, "Failed to load %s", path); + error = PluginManagerErrorLoaderError; break; } diff --git a/scripts/fbt_tools/fbt_assets.py b/scripts/fbt_tools/fbt_assets.py index dcf391f2d2a..5c32ae1a983 100644 --- a/scripts/fbt_tools/fbt_assets.py +++ b/scripts/fbt_tools/fbt_assets.py @@ -30,24 +30,26 @@ def _proto_emitter(target, source, env): def _dolphin_emitter(target, source, env): res_root_dir = source[0].Dir(env["DOLPHIN_RES_TYPE"]) - source = [res_root_dir] + source = list() source.extend(env.GlobRecursive("*.*", res_root_dir.srcnode())) target_base_dir = target[0] env.Replace(_DOLPHIN_OUT_DIR=target[0]) + env.Replace(_DOLPHIN_SRC_DIR=res_root_dir) if env["DOLPHIN_RES_TYPE"] == "external": target = [target_base_dir.File("manifest.txt")] ## A detailed list of files to be generated + # Not used ATM, becasuse it inflates the internal dependency graph too much # Preserve original paths, do .png -> .bm conversion - target.extend( - map( - lambda node: target_base_dir.File( - res_root_dir.rel_path(node).replace(".png", ".bm") - ), - filter(lambda node: isinstance(node, File), source), - ) - ) + # target.extend( + # map( + # lambda node: target_base_dir.File( + # res_root_dir.rel_path(node).replace(".png", ".bm") + # ), + # filter(lambda node: isinstance(node, File), source), + # ) + # ) else: asset_basename = f"assets_dolphin_{env['DOLPHIN_RES_TYPE']}" target = [ @@ -55,7 +57,7 @@ def _dolphin_emitter(target, source, env): target_base_dir.File(asset_basename + ".h"), ] - # Debug output + ## Debug output # print( # f"Dolphin res type: {env['DOLPHIN_RES_TYPE']},\ntarget files:", # list(f.path for f in target), @@ -176,7 +178,7 @@ def generate(env): "dolphin", "-s", "dolphin_${DOLPHIN_RES_TYPE}", - "${SOURCE}", + "${_DOLPHIN_SRC_DIR}", "${_DOLPHIN_OUT_DIR}", ], ], @@ -191,7 +193,7 @@ def generate(env): "${PYTHON3}", "${ASSETS_COMPILER}", "dolphin", - "${SOURCE}", + "${_DOLPHIN_SRC_DIR}", "${_DOLPHIN_OUT_DIR}", ], ], diff --git a/scripts/fbt_tools/fbt_resources.py b/scripts/fbt_tools/fbt_resources.py index 47c624081f8..4c3146ba45c 100644 --- a/scripts/fbt_tools/fbt_resources.py +++ b/scripts/fbt_tools/fbt_resources.py @@ -7,16 +7,21 @@ from SCons.Node.FS import Dir, File -def _resources_dist_emitter(target, source, env): +def __generate_resources_dist_entries(env): + src_target_entries = [] + resources_root = env.Dir(env["RESOURCES_ROOT"]) - target = [] for app_artifacts in env["FW_EXTAPPS"].application_map.values(): for _, dist_path in filter( lambda dist_entry: dist_entry[0], app_artifacts.dist_entries ): - source.append(app_artifacts.compact) - target.append(resources_root.File(dist_path)) + src_target_entries.append( + ( + app_artifacts.compact, + resources_root.File(dist_path), + ) + ) # Deploy apps' resources too for app in env["APPBUILD"].apps: @@ -26,34 +31,48 @@ def _resources_dist_emitter(target, source, env): for res_file in env.GlobRecursive("*", apps_resource_dir): if not isinstance(res_file, File): continue - source.append(res_file) - target.append(resources_root.File(res_file.get_path(apps_resource_dir))) + src_target_entries.append( + ( + res_file, + resources_root.File( + res_file.get_path(apps_resource_dir), + ), + ) + ) # Deploy other stuff from _EXTRA_DIST for extra_dist in env["_EXTRA_DIST"]: if isinstance(extra_dist, Dir): - for extra_file in env.GlobRecursive("*", extra_dist): - if not isinstance(extra_file, File): - continue - source.append(extra_file) - target.append( - # Preserve dir name from original node - resources_root.Dir(extra_dist.name).File( - extra_file.get_path(extra_dist) - ) + src_target_entries.append( + ( + extra_dist, + resources_root.Dir(extra_dist.name), ) + ) else: raise StopError(f"Unsupported extra dist type: {type(extra_dist)}") - assert len(target) == len(source) + return src_target_entries + + +def _resources_dist_emitter(target, source, env): + src_target_entries = __generate_resources_dist_entries(env) + source = list(map(lambda entry: entry[0], src_target_entries)) return (target, source) def _resources_dist_action(target, source, env): + dist_entries = __generate_resources_dist_entries(env) + assert len(dist_entries) == len(source) shutil.rmtree(env.Dir(env["RESOURCES_ROOT"]).abspath, ignore_errors=True) - for src, target in zip(source, target): - os.makedirs(os.path.dirname(target.path), exist_ok=True) - shutil.copy(src.path, target.path) + for src, target in dist_entries: + if isinstance(src, File): + os.makedirs(os.path.dirname(target.path), exist_ok=True) + shutil.copy(src.path, target.path) + elif isinstance(src, Dir): + shutil.copytree(src.path, target.path) + else: + raise StopError(f"Unsupported dist entry type: {type(src)}") def generate(env, **kw): @@ -69,26 +88,26 @@ def generate(env, **kw): env.Append( BUILDERS={ - "ResourcesDist": Builder( - action=Action( - _resources_dist_action, - "${RESOURCEDISTCOMSTR}", - ), - emitter=_resources_dist_emitter, - ), "ManifestBuilder": Builder( - action=Action( - [ + action=[ + Action( + _resources_dist_action, + "${RESOURCEDISTCOMSTR}", + ), + Action( [ - "${PYTHON3}", - "${ASSETS_COMPILER}", - "manifest", - "${TARGET.dir.posix}", - "--timestamp=${GIT_UNIX_TIMESTAMP}", - ] - ], - "${RESMANIFESTCOMSTR}", - ) + [ + "${PYTHON3}", + "${ASSETS_COMPILER}", + "manifest", + "${TARGET.dir.posix}", + "--timestamp=${GIT_UNIX_TIMESTAMP}", + ] + ], + "${RESMANIFESTCOMSTR}", + ), + ], + emitter=_resources_dist_emitter, ), } ) From bf8984a225099152b23dd1590862b635d81a7329 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Tue, 31 Oct 2023 15:34:21 +0400 Subject: [PATCH 795/824] [FL-3647] Rename menu items related to dummy mode and sound (#3177) Co-authored-by: hedger --- .../services/desktop/views/desktop_view_lock_menu.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/applications/services/desktop/views/desktop_view_lock_menu.c b/applications/services/desktop/views/desktop_view_lock_menu.c index f4790ebb8cf..1ba8542b4a6 100644 --- a/applications/services/desktop/views/desktop_view_lock_menu.c +++ b/applications/services/desktop/views/desktop_view_lock_menu.c @@ -60,13 +60,13 @@ void desktop_lock_menu_draw_callback(Canvas* canvas, void* model) { str = "Lock"; } else if(i == DesktopLockMenuIndexStealth) { if(m->stealth_mode) { - str = "Sound Mode"; + str = "Unmute"; } else { - str = "Stealth Mode"; + str = "Mute"; } } else if(i == DesktopLockMenuIndexDummy) { //-V547 if(m->dummy_mode) { - str = "Brainiac Mode"; + str = "Default Mode"; } else { str = "Dummy Mode"; } From 7bd3bd7ea4a0d600d7e5171dcfe7574da2019526 Mon Sep 17 00:00:00 2001 From: hedger Date: Wed, 1 Nov 2023 08:21:31 +0400 Subject: [PATCH 796/824] fbt: source collection improvements (#3181) * fbt: reduced amount of redundant compilation units * fbt: added GatherSources() method which can reject source paths starting with "!" in sources list; optimized apps' source lists * docs: updated on path exclusion for `sources` * apps: examples: fixed example_advanced_plugins source list * docs: more details on `sources`; apps: narrower sources lists --- .../examples/example_plugins/application.fam | 3 +++ .../example_plugins_advanced/application.fam | 1 + applications/main/ibutton/application.fam | 1 + applications/main/infrared/application.fam | 6 +++++ applications/main/lfrfid/application.fam | 1 + applications/main/nfc/application.fam | 12 +++++++++ applications/main/subghz/application.fam | 6 +++++ documentation/AppManifests.md | 2 +- firmware.scons | 2 +- scripts/fbt_tools/fbt_extapps.py | 12 +++------ scripts/fbt_tools/sconsrecursiveglob.py | 27 ++++++++++++++++++- 11 files changed, 61 insertions(+), 12 deletions(-) diff --git a/applications/examples/example_plugins/application.fam b/applications/examples/example_plugins/application.fam index a6e3c20781e..d9a36da564b 100644 --- a/applications/examples/example_plugins/application.fam +++ b/applications/examples/example_plugins/application.fam @@ -5,6 +5,7 @@ App( entry_point="example_plugins_app", stack_size=2 * 1024, fap_category="Examples", + sources=["*.c", "!plugin*.c"], ) App( @@ -21,6 +22,7 @@ App( apptype=FlipperAppType.PLUGIN, entry_point="example_plugin1_ep", requires=["example_plugins", "example_plugins_multi"], + sources=["plugin1.c"], ) App( @@ -28,4 +30,5 @@ App( apptype=FlipperAppType.PLUGIN, entry_point="example_plugin2_ep", requires=["example_plugins_multi"], + sources=["plugin2.c"], ) diff --git a/applications/examples/example_plugins_advanced/application.fam b/applications/examples/example_plugins_advanced/application.fam index d40c0dde295..0c7e3e3b94d 100644 --- a/applications/examples/example_plugins_advanced/application.fam +++ b/applications/examples/example_plugins_advanced/application.fam @@ -5,6 +5,7 @@ App( entry_point="example_advanced_plugins_app", stack_size=2 * 1024, fap_category="Examples", + sources=["*.c*", "!plugin*.c"], ) App( diff --git a/applications/main/ibutton/application.fam b/applications/main/ibutton/application.fam index a8faa629cea..01c02ec23d6 100644 --- a/applications/main/ibutton/application.fam +++ b/applications/main/ibutton/application.fam @@ -17,5 +17,6 @@ App( apptype=FlipperAppType.STARTUP, targets=["f7"], entry_point="ibutton_on_system_start", + sources=["ibutton_cli.c"], order=60, ) diff --git a/applications/main/infrared/application.fam b/applications/main/infrared/application.fam index 055d6c3e294..575bebbe481 100644 --- a/applications/main/infrared/application.fam +++ b/applications/main/infrared/application.fam @@ -7,6 +7,7 @@ App( icon="A_Infrared_14", stack_size=3 * 1024, order=40, + sources=["*.c", "!infrared_cli.c"], resources="resources", fap_libs=["assets"], fap_icon="icon.png", @@ -18,5 +19,10 @@ App( apptype=FlipperAppType.STARTUP, targets=["f7"], entry_point="infrared_on_system_start", + sources=[ + "infrared_cli.c", + "infrared_brute_force.c", + "infrared_signal.c", + ], order=20, ) diff --git a/applications/main/lfrfid/application.fam b/applications/main/lfrfid/application.fam index cad07fbf777..c067d786fc4 100644 --- a/applications/main/lfrfid/application.fam +++ b/applications/main/lfrfid/application.fam @@ -17,5 +17,6 @@ App( targets=["f7"], apptype=FlipperAppType.STARTUP, entry_point="lfrfid_on_system_start", + sources=["lfrfid_cli.c"], order=50, ) diff --git a/applications/main/nfc/application.fam b/applications/main/nfc/application.fam index 3c8dab2bf1f..33a2011a70e 100644 --- a/applications/main/nfc/application.fam +++ b/applications/main/nfc/application.fam @@ -8,6 +8,11 @@ App( stack_size=5 * 1024, order=30, resources="resources", + sources=[ + "*.c", + "!plugins", + "!nfc_cli.c", + ], fap_libs=["assets"], fap_icon="icon.png", fap_category="NFC", @@ -21,6 +26,7 @@ App( entry_point="all_in_one_plugin_ep", targets=["f7"], requires=["nfc"], + sources=["plugins/supported_cards/all_in_one.c"], ) App( @@ -29,6 +35,7 @@ App( entry_point="opal_plugin_ep", targets=["f7"], requires=["nfc"], + sources=["plugins/supported_cards/opal.c"], ) App( @@ -37,6 +44,7 @@ App( entry_point="myki_plugin_ep", targets=["f7"], requires=["nfc"], + sources=["plugins/supported_cards/myki.c"], ) App( @@ -45,6 +53,7 @@ App( entry_point="troika_plugin_ep", targets=["f7"], requires=["nfc"], + sources=["plugins/supported_cards/troika.c"], ) App( @@ -53,6 +62,7 @@ App( entry_point="plantain_plugin_ep", targets=["f7"], requires=["nfc"], + sources=["plugins/supported_cards/plantain.c"], ) App( @@ -61,6 +71,7 @@ App( entry_point="two_cities_plugin_ep", targets=["f7"], requires=["nfc"], + sources=["plugins/supported_cards/two_cities.c"], ) App( @@ -68,5 +79,6 @@ App( targets=["f7"], apptype=FlipperAppType.STARTUP, entry_point="nfc_on_system_start", + sources=["nfc_cli.c"], order=30, ) diff --git a/applications/main/subghz/application.fam b/applications/main/subghz/application.fam index ba9b16abfd7..5f9f24dcd31 100644 --- a/applications/main/subghz/application.fam +++ b/applications/main/subghz/application.fam @@ -7,6 +7,11 @@ App( icon="A_Sub1ghz_14", stack_size=3 * 1024, order=10, + sources=[ + "*.c", + "!subghz_cli.c", + "!helpers/subghz_chat.c", + ], resources="resources", fap_libs=["assets", "hwdrivers"], fap_icon="icon.png", @@ -18,5 +23,6 @@ App( targets=["f7"], apptype=FlipperAppType.STARTUP, entry_point="subghz_on_system_start", + sources=["subghz_cli.c", "helpers/subghz_chat.c"], order=40, ) diff --git a/documentation/AppManifests.md b/documentation/AppManifests.md index 0b3217c58cc..d190a798ba8 100644 --- a/documentation/AppManifests.md +++ b/documentation/AppManifests.md @@ -47,7 +47,7 @@ Only two parameters are mandatory: **_appid_** and **_apptype_**. Others are opt The following parameters are used only for [FAPs](./AppsOnSDCard.md): -- **sources**: list of strings, file name masks used for gathering sources within the app folder. The default value of `["*.c*"]` includes C and C++ source files. Applications cannot use the `"lib"` folder for their own source code, as it is reserved for **fap_private_libs**. +- **sources**: list of strings, file name masks used for gathering sources within the app folder. The default value of `["*.c*"]` includes C and C++ source files. Applications cannot use the `"lib"` folder for their own source code, as it is reserved for **fap_private_libs**. Paths starting with `"!"` are excluded from the list of sources. They can also include wildcard characters and directory names. For example, a value of `["*.c*", "!plugins"]` will include all C and C++ source files in the app folder except those in the `plugins` (and `lib`) folders. Paths with no wildcards (`*, ?`) are treated as full literal paths for both inclusion and exclusion. - **fap_version**: string, application version. The default value is "0.1". You can also use a tuple of 2 numbers in the form of (x,y) to specify the version. It is also possible to add more dot-separated parts to the version, like patch number, but only major and minor version numbers are stored in the built .fap. - **fap_icon**: name of a `.png` file, 1-bit color depth, 10x10px, to be embedded within `.fap` file. - **fap_libs**: list of extra libraries to link the application against. Provides access to extra functions that are not exported as a part of main firmware at the expense of increased `.fap` file size and RAM consumption. diff --git a/firmware.scons b/firmware.scons index 537774254cd..eca6afc4c70 100644 --- a/firmware.scons +++ b/firmware.scons @@ -197,7 +197,7 @@ sources = [apps_c] # Gather sources only from app folders in current configuration sources.extend( itertools.chain.from_iterable( - fwenv.GlobRecursive(source_type, appdir.relpath, exclude=["lib"]) + fwenv.GatherSources([source_type, "!lib"], appdir.relpath) for appdir, source_type in fwenv["APPBUILD"].get_builtin_app_folders() ) ) diff --git a/scripts/fbt_tools/fbt_extapps.py b/scripts/fbt_tools/fbt_extapps.py index 94307539a71..b88fa792911 100644 --- a/scripts/fbt_tools/fbt_extapps.py +++ b/scripts/fbt_tools/fbt_extapps.py @@ -147,16 +147,10 @@ def _build_app(self): CPPPATH=[self.app_work_dir, self.app._appdir], ) - app_sources = list( - itertools.chain.from_iterable( - self.app_env.GlobRecursive( - source_type, - self.app_work_dir, - exclude="lib", - ) - for source_type in self.app.sources - ) + app_sources = self.app_env.GatherSources( + [self.app.sources, "!lib"], self.app_work_dir ) + if not app_sources: raise UserError(f"No source files found for {self.app.appid}") diff --git a/scripts/fbt_tools/sconsrecursiveglob.py b/scripts/fbt_tools/sconsrecursiveglob.py index e7eb8fb7297..de64c76b7b3 100644 --- a/scripts/fbt_tools/sconsrecursiveglob.py +++ b/scripts/fbt_tools/sconsrecursiveglob.py @@ -1,7 +1,9 @@ +import itertools + import SCons from fbt.util import GLOB_FILE_EXCLUSION -from SCons.Script import Flatten from SCons.Node.FS import has_glob_magic +from SCons.Script import Flatten def GlobRecursive(env, pattern, node=".", exclude=[]): @@ -23,12 +25,35 @@ def GlobRecursive(env, pattern, node=".", exclude=[]): # Otherwise, just assume that file at path exists else: results.append(node.File(pattern)) + ## Debug # print(f"Glob result for {pattern} from {node}: {results}") return results +def GatherSources(env, sources_list, node="."): + sources_list = list(set(Flatten(sources_list))) + include_sources = list(filter(lambda x: not x.startswith("!"), sources_list)) + exclude_sources = list(x[1:] for x in sources_list if x.startswith("!")) + gathered_sources = list( + itertools.chain.from_iterable( + env.GlobRecursive( + source_type, + node, + exclude=exclude_sources, + ) + for source_type in include_sources + ) + ) + ## Debug + # print( + # f"Gathered sources for {sources_list} from {node}: {list(f.path for f in gathered_sources)}" + # ) + return gathered_sources + + def generate(env): env.AddMethod(GlobRecursive) + env.AddMethod(GatherSources) def exists(env): From aa063285165abf2bef9582f3a01b422cedf651e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Wed, 1 Nov 2023 16:24:11 +0900 Subject: [PATCH 797/824] Furi, FuriHal: remove FreeRTOS headers leaks (#3179) * Furi: remove direct FreeRTOS timers use * Furi: eliminate FreeRTOS headers leak. What did it cost? Everything... * SubGhz: proper public api for protocols. Format Sources. * Furi: slightly less redundant declarations * Desktop: proper types in printf * Sync API Symbols * Furi: add timer reset and fix dolphin service, fix unit tests * Furi: proper timer restart method naming and correct behavior in timer stopped state. --------- Co-authored-by: hedger --- applications/debug/direct_draw/direct_draw.c | 2 +- applications/debug/unit_tests/rpc/rpc_test.c | 10 +- applications/debug/unit_tests/test_index.c | 4 +- applications/main/infrared/infrared_app.c | 5 +- applications/main/nfc/nfc_app.c | 5 +- applications/main/subghz/views/receiver.c | 6 +- .../desktop/animations/animation_manager.c | 3 +- .../desktop/animations/animation_storage.c | 4 +- .../animations/views/bubble_animation_view.c | 8 +- .../views/one_shot_animation_view.c | 13 +- .../desktop/scenes/desktop_scene_locked.c | 1 - .../desktop/scenes/desktop_scene_pin_input.c | 23 ++-- .../scenes/desktop_scene_pin_timeout.c | 1 - .../desktop/views/desktop_view_locked.c | 19 ++- .../desktop/views/desktop_view_main.c | 21 ++-- .../desktop/views/desktop_view_pin_input.c | 27 ++-- .../desktop/views/desktop_view_pin_timeout.c | 17 ++- applications/services/dolphin/dolphin.c | 43 ++++--- applications/services/dolphin/dolphin_i.h | 6 +- applications/services/gui/icon_animation.c | 8 +- applications/services/input/input.c | 20 +-- applications/services/rpc/rpc.c | 3 +- applications/services/rpc/rpc.h | 2 +- applications/services/rpc/rpc_cli.c | 1 - applications/system/updater/updater.c | 1 - furi/core/base.h | 1 + furi/core/check.c | 2 - furi/core/common_defines.h | 2 - furi/core/critical.c | 3 + furi/core/event_flag.c | 1 + furi/core/kernel.c | 7 ++ furi/core/kernel.h | 6 + furi/core/memmgr_heap.c | 4 +- furi/core/message_queue.c | 3 +- furi/core/mutex.c | 1 + furi/core/semaphore.c | 1 + furi/core/stream_buffer.c | 1 + furi/core/thread.c | 4 +- furi/core/thread.h | 6 +- furi/core/timer.c | 39 ++++++ furi/core/timer.h | 27 ++++ furi/flipper.c | 2 + furi/furi.c | 4 +- furi/furi.h | 3 - lib/infrared/worker/infrared_worker.c | 6 +- lib/lfrfid/lfrfid_worker.c | 4 +- .../iso14443_4a/iso14443_4a_poller_i.h | 2 - .../iso14443_4b/iso14443_4b_poller_i.h | 2 - lib/print/wrappers.h | 3 +- lib/subghz/SConscript | 1 + lib/subghz/protocols/bin_raw.h | 5 +- lib/subghz/protocols/keeloq.h | 21 +--- lib/subghz/protocols/public_api.h | 63 ++++++++++ lib/subghz/protocols/secplus_v1.h | 9 +- lib/subghz/protocols/secplus_v2.h | 21 +--- lib/subghz/subghz_protocol_registry.h | 23 ---- lib/toolbox/buffer_stream.c | 2 +- lib/toolbox/buffer_stream.h | 2 +- targets/f18/api_symbols.csv | 114 +---------------- targets/f7/api_symbols.csv | 115 ++---------------- targets/f7/ble_glue/gap.c | 2 - targets/f7/furi_hal/furi_hal_flash.c | 3 + targets/f7/furi_hal/furi_hal_os.c | 3 + targets/f7/furi_hal/furi_hal_power.c | 8 +- targets/f7/furi_hal/furi_hal_spi.c | 2 +- targets/f7/inc/FreeRTOSConfig.h | 3 +- targets/f7/inc/furi_config.h | 3 + targets/f7/src/main.c | 1 - 68 files changed, 316 insertions(+), 472 deletions(-) create mode 100644 lib/subghz/protocols/public_api.h create mode 100644 targets/f7/inc/furi_config.h diff --git a/applications/debug/direct_draw/direct_draw.c b/applications/debug/direct_draw/direct_draw.c index 71c65eab135..63e03530a7a 100644 --- a/applications/debug/direct_draw/direct_draw.c +++ b/applications/debug/direct_draw/direct_draw.c @@ -71,7 +71,7 @@ static void direct_draw_run(DirectDraw* instance) { size_t counter = 0; float fps = 0; - vTaskPrioritySet(furi_thread_get_current_id(), FuriThreadPriorityIdle); + furi_thread_set_current_priority(FuriThreadPriorityIdle); do { size_t elapsed = DWT->CYCCNT - start; diff --git a/applications/debug/unit_tests/rpc/rpc_test.c b/applications/debug/unit_tests/rpc/rpc_test.c index 645e75e8448..5659ba877df 100644 --- a/applications/debug/unit_tests/rpc/rpc_test.c +++ b/applications/debug/unit_tests/rpc/rpc_test.c @@ -18,6 +18,8 @@ #include #include #include + +#include #include LIST_DEF(MsgList, PB_Main, M_POD_OPLIST) @@ -36,7 +38,7 @@ typedef struct { FuriStreamBuffer* output_stream; SemaphoreHandle_t close_session_semaphore; SemaphoreHandle_t terminate_semaphore; - TickType_t timeout; + uint32_t timeout; } RpcSessionContext; static RpcSessionContext rpc_session[TEST_RPC_SESSIONS]; @@ -544,7 +546,7 @@ static bool test_rpc_pb_stream_read(pb_istream_t* istream, pb_byte_t* buf, size_ RpcSessionContext* session_context = istream->state; size_t bytes_received = 0; - TickType_t now = xTaskGetTickCount(); + uint32_t now = furi_get_tick(); int32_t time_left = session_context->timeout - now; time_left = MAX(time_left, 0); bytes_received = @@ -688,7 +690,7 @@ static void test_rpc_decode_and_compare(MsgList_t expected_msg_list, uint8_t ses furi_check(!MsgList_empty_p(expected_msg_list)); furi_check(session < TEST_RPC_SESSIONS); - rpc_session[session].timeout = xTaskGetTickCount() + MAX_RECEIVE_OUTPUT_TIMEOUT; + rpc_session[session].timeout = furi_get_tick() + MAX_RECEIVE_OUTPUT_TIMEOUT; pb_istream_t istream = { .callback = test_rpc_pb_stream_read, .state = &rpc_session[session], @@ -712,7 +714,7 @@ static void test_rpc_decode_and_compare(MsgList_t expected_msg_list, uint8_t ses pb_release(&PB_Main_msg, &result); } - rpc_session[session].timeout = xTaskGetTickCount() + 50; + rpc_session[session].timeout = furi_get_tick() + 50; if(pb_decode_ex(&istream, &PB_Main_msg, &result, PB_DECODE_DELIMITED)) { mu_fail("decoded more than expected"); } diff --git a/applications/debug/unit_tests/test_index.c b/applications/debug/unit_tests/test_index.c index edaf950c54b..7c1b6b44477 100644 --- a/applications/debug/unit_tests/test_index.c +++ b/applications/debug/unit_tests/test_index.c @@ -65,8 +65,8 @@ const UnitTest unit_tests[] = { void minunit_print_progress() { static const char progress[] = {'\\', '|', '/', '-'}; static uint8_t progress_counter = 0; - static TickType_t last_tick = 0; - TickType_t current_tick = xTaskGetTickCount(); + static uint32_t last_tick = 0; + uint32_t current_tick = furi_get_tick(); if(current_tick - last_tick > 20) { last_tick = current_tick; printf("[%c]\033[3D", progress[++progress_counter % COUNT_OF(progress)]); diff --git a/applications/main/infrared/infrared_app.c b/applications/main/infrared/infrared_app.c index e29eda30fb4..7abb4e4eb61 100644 --- a/applications/main/infrared/infrared_app.c +++ b/applications/main/infrared/infrared_app.c @@ -384,18 +384,17 @@ void infrared_play_notification_message( } void infrared_show_loading_popup(const InfraredApp* infrared, bool show) { - TaskHandle_t timer_task = xTaskGetHandle(configTIMER_SERVICE_TASK_NAME); ViewStack* view_stack = infrared->view_stack; Loading* loading = infrared->loading; if(show) { // Raise timer priority so that animations can play - vTaskPrioritySet(timer_task, configMAX_PRIORITIES - 1); + furi_timer_set_thread_priority(FuriTimerThreadPriorityElevated); view_stack_add_view(view_stack, loading_get_view(loading)); } else { view_stack_remove_view(view_stack, loading_get_view(loading)); // Restore default timer priority - vTaskPrioritySet(timer_task, configTIMER_TASK_PRIORITY); + furi_timer_set_thread_priority(FuriTimerThreadPriorityNormal); } } diff --git a/applications/main/nfc/nfc_app.c b/applications/main/nfc/nfc_app.c index fe680aa32df..9e0de8891bb 100644 --- a/applications/main/nfc/nfc_app.c +++ b/applications/main/nfc/nfc_app.c @@ -411,15 +411,14 @@ bool nfc_load_from_file_select(NfcApp* instance) { void nfc_show_loading_popup(void* context, bool show) { NfcApp* nfc = context; - TaskHandle_t timer_task = xTaskGetHandle(configTIMER_SERVICE_TASK_NAME); if(show) { // Raise timer priority so that animations can play - vTaskPrioritySet(timer_task, configMAX_PRIORITIES - 1); + furi_timer_set_thread_priority(FuriTimerThreadPriorityElevated); view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewLoading); } else { // Restore default timer priority - vTaskPrioritySet(timer_task, configTIMER_TASK_PRIORITY); + furi_timer_set_thread_priority(FuriTimerThreadPriorityNormal); } } diff --git a/applications/main/subghz/views/receiver.c b/applications/main/subghz/views/receiver.c index e1014b81109..23fa26c772c 100644 --- a/applications/main/subghz/views/receiver.c +++ b/applications/main/subghz/views/receiver.c @@ -91,7 +91,7 @@ void subghz_view_receiver_set_lock(SubGhzViewReceiver* subghz_receiver, bool loc SubGhzViewReceiverModel * model, { model->bar_show = SubGhzViewReceiverBarShowLock; }, true); - furi_timer_start(subghz_receiver->timer, pdMS_TO_TICKS(1000)); + furi_timer_start(subghz_receiver->timer, 1000); } else { with_view_model( subghz_receiver->view, @@ -316,7 +316,7 @@ bool subghz_view_receiver_input(InputEvent* event, void* context) { { model->bar_show = SubGhzViewReceiverBarShowToUnlockPress; }, true); if(subghz_receiver->lock_count == 0) { - furi_timer_start(subghz_receiver->timer, pdMS_TO_TICKS(1000)); + furi_timer_start(subghz_receiver->timer, 1000); } if(event->key == InputKeyBack && event->type == InputTypeShort) { subghz_receiver->lock_count++; @@ -330,7 +330,7 @@ bool subghz_view_receiver_input(InputEvent* event, void* context) { { model->bar_show = SubGhzViewReceiverBarShowUnlock; }, true); //subghz_receiver->lock = false; - furi_timer_start(subghz_receiver->timer, pdMS_TO_TICKS(650)); + furi_timer_start(subghz_receiver->timer, 650); } return true; diff --git a/applications/services/desktop/animations/animation_manager.c b/applications/services/desktop/animations/animation_manager.c index 873fb6aa2c4..44c0c228c4e 100644 --- a/applications/services/desktop/animations/animation_manager.c +++ b/applications/services/desktop/animations/animation_manager.c @@ -2,7 +2,6 @@ #include #include #include -#include #include #include #include @@ -450,7 +449,7 @@ void animation_manager_unload_and_stall_animation(AnimationManager* animation_ma animation_manager->state = AnimationManagerStateFreezedIdle; animation_manager->freezed_animation_time_left = - xTimerGetExpiryTime(animation_manager->idle_animation_timer) - xTaskGetTickCount(); + furi_timer_get_expire_time(animation_manager->idle_animation_timer) - furi_get_tick(); if(animation_manager->freezed_animation_time_left < 0) { animation_manager->freezed_animation_time_left = 0; } diff --git a/applications/services/desktop/animations/animation_storage.c b/applications/services/desktop/animations/animation_storage.c index 2c16cf726da..c99cc7b5d09 100644 --- a/applications/services/desktop/animations/animation_storage.c +++ b/applications/services/desktop/animations/animation_storage.c @@ -304,7 +304,7 @@ static bool animation_storage_load_frames( if(file_info.size > max_filesize) { FURI_LOG_E( TAG, - "Filesize %lld, max: %d (width %d, height %d)", + "Filesize %llu, max: %zu (width %u, height %u)", file_info.size, max_filesize, width, @@ -329,7 +329,7 @@ static bool animation_storage_load_frames( if(!frames_ok) { FURI_LOG_E( TAG, - "Load \'%s\' failed, %dx%d, size: %lld", + "Load \'%s\' failed, %ux%u, size: %llu", furi_string_get_cstr(filename), width, height, diff --git a/applications/services/desktop/animations/views/bubble_animation_view.c b/applications/services/desktop/animations/views/bubble_animation_view.c index 30a165087bb..9585b2771e0 100644 --- a/applications/services/desktop/animations/views/bubble_animation_view.c +++ b/applications/services/desktop/animations/views/bubble_animation_view.c @@ -23,7 +23,7 @@ typedef struct { uint8_t active_bubbles; uint8_t passive_bubbles; uint8_t active_shift; - TickType_t active_ended_at; + uint32_t active_ended_at; Icon* freeze_frame; } BubbleAnimationViewModel; @@ -154,7 +154,7 @@ static void bubble_animation_activate(BubbleAnimationView* view, bool force) { if(model->current != NULL) { if(!force) { if((model->active_ended_at + model->current->active_cooldown * 1000) > - xTaskGetTickCount()) { + furi_get_tick()) { activate = false; } else if(model->active_shift) { activate = false; @@ -215,7 +215,7 @@ static void bubble_animation_next_frame(BubbleAnimationViewModel* model) { model->active_cycle = 0; model->current_frame = 0; model->current_bubble = bubble_animation_pick_bubble(model, false); - model->active_ended_at = xTaskGetTickCount(); + model->active_ended_at = furi_get_tick(); } if(model->current_bubble) { @@ -355,7 +355,7 @@ void bubble_animation_view_set_animation( furi_assert(model); model->current = new_animation; - model->active_ended_at = xTaskGetTickCount() - (model->current->active_cooldown * 1000); + model->active_ended_at = furi_get_tick() - (model->current->active_cooldown * 1000); model->active_bubbles = 0; model->passive_bubbles = 0; for(int i = 0; i < new_animation->frame_bubble_sequences_count; ++i) { diff --git a/applications/services/desktop/animations/views/one_shot_animation_view.c b/applications/services/desktop/animations/views/one_shot_animation_view.c index 077f82d092e..004fcde7b56 100644 --- a/applications/services/desktop/animations/views/one_shot_animation_view.c +++ b/applications/services/desktop/animations/views/one_shot_animation_view.c @@ -1,7 +1,6 @@ #include "one_shot_animation_view.h" #include -#include #include #include #include @@ -11,7 +10,7 @@ typedef void (*OneShotInteractCallback)(void*); struct OneShotView { View* view; - TimerHandle_t update_timer; + FuriTimer* update_timer; OneShotInteractCallback interact_callback; void* interact_callback_context; }; @@ -22,8 +21,8 @@ typedef struct { bool block_input; } OneShotViewModel; -static void one_shot_view_update_timer_callback(TimerHandle_t xTimer) { - OneShotView* view = (void*)pvTimerGetTimerID(xTimer); +static void one_shot_view_update_timer_callback(void* context) { + OneShotView* view = context; OneShotViewModel* model = view_get_model(view->view); if((model->index + 1) < model->icon->frame_count) { @@ -81,7 +80,7 @@ OneShotView* one_shot_view_alloc(void) { OneShotView* view = malloc(sizeof(OneShotView)); view->view = view_alloc(); view->update_timer = - xTimerCreate(NULL, 1000, pdTRUE, view, one_shot_view_update_timer_callback); + furi_timer_alloc(one_shot_view_update_timer_callback, FuriTimerTypePeriodic, view); view_allocate_model(view->view, ViewModelTypeLocking, sizeof(OneShotViewModel)); view_set_context(view->view, view); @@ -94,7 +93,7 @@ OneShotView* one_shot_view_alloc(void) { void one_shot_view_free(OneShotView* view) { furi_assert(view); - xTimerDelete(view->update_timer, portMAX_DELAY); + furi_timer_free(view->update_timer); view_free(view->view); view->view = NULL; free(view); @@ -120,7 +119,7 @@ void one_shot_view_start_animation(OneShotView* view, const Icon* icon) { model->icon = icon; model->block_input = true; view_commit_model(view->view, true); - xTimerChangePeriod(view->update_timer, 1000 / model->icon->frame_rate, portMAX_DELAY); + furi_timer_start(view->update_timer, 1000 / model->icon->frame_rate); } View* one_shot_view_get_view(OneShotView* view) { diff --git a/applications/services/desktop/scenes/desktop_scene_locked.c b/applications/services/desktop/scenes/desktop_scene_locked.c index bbed5600151..034eedb8ace 100644 --- a/applications/services/desktop/scenes/desktop_scene_locked.c +++ b/applications/services/desktop/scenes/desktop_scene_locked.c @@ -3,7 +3,6 @@ #include #include #include -#include #include "../desktop.h" #include "../desktop_i.h" diff --git a/applications/services/desktop/scenes/desktop_scene_pin_input.c b/applications/services/desktop/scenes/desktop_scene_pin_input.c index e062c1b97d2..0e248def604 100644 --- a/applications/services/desktop/scenes/desktop_scene_pin_input.c +++ b/applications/services/desktop/scenes/desktop_scene_pin_input.c @@ -3,7 +3,6 @@ #include #include #include -#include #include #include @@ -20,7 +19,7 @@ #define INPUT_PIN_VIEW_TIMEOUT 15000 typedef struct { - TimerHandle_t timer; + FuriTimer* timer; } DesktopScenePinInputState; static void desktop_scene_locked_light_red(bool value) { @@ -33,17 +32,16 @@ static void desktop_scene_locked_light_red(bool value) { furi_record_close(RECORD_NOTIFICATION); } -static void - desktop_scene_pin_input_set_timer(Desktop* desktop, bool enable, TickType_t new_period) { +static void desktop_scene_pin_input_set_timer(Desktop* desktop, bool enable, uint32_t new_period) { furi_assert(desktop); DesktopScenePinInputState* state = (DesktopScenePinInputState*)scene_manager_get_scene_state( desktop->scene_manager, DesktopScenePinInput); furi_assert(state); if(enable) { - xTimerChangePeriod(state->timer, new_period, portMAX_DELAY); + furi_timer_start(state->timer, new_period); } else { - xTimerStop(state->timer, portMAX_DELAY); + furi_timer_stop(state->timer); } } @@ -64,8 +62,8 @@ static void desktop_scene_pin_input_done_callback(const PinCode* pin_code, void* } } -static void desktop_scene_pin_input_timer_callback(TimerHandle_t timer) { - Desktop* desktop = pvTimerGetTimerID(timer); +static void desktop_scene_pin_input_timer_callback(void* context) { + Desktop* desktop = context; view_dispatcher_send_custom_event( desktop->view_dispatcher, DesktopPinInputEventResetWrongPinLabel); @@ -84,7 +82,7 @@ void desktop_scene_pin_input_on_enter(void* context) { DesktopScenePinInputState* state = malloc(sizeof(DesktopScenePinInputState)); state->timer = - xTimerCreate(NULL, 10000, pdFALSE, desktop, desktop_scene_pin_input_timer_callback); + furi_timer_alloc(desktop_scene_pin_input_timer_callback, FuriTimerTypeOnce, desktop); scene_manager_set_scene_state(desktop->scene_manager, DesktopScenePinInput, (uint32_t)state); desktop_view_pin_input_hide_pin(desktop->pin_input_view, true); @@ -149,10 +147,7 @@ void desktop_scene_pin_input_on_exit(void* context) { DesktopScenePinInputState* state = (DesktopScenePinInputState*)scene_manager_get_scene_state( desktop->scene_manager, DesktopScenePinInput); - xTimerStop(state->timer, portMAX_DELAY); - while(xTimerIsTimerActive(state->timer)) { - furi_delay_tick(1); - } - xTimerDelete(state->timer, portMAX_DELAY); + + furi_timer_free(state->timer); free(state); } diff --git a/applications/services/desktop/scenes/desktop_scene_pin_timeout.c b/applications/services/desktop/scenes/desktop_scene_pin_timeout.c index 2f009e7d2aa..e3336ad76d6 100644 --- a/applications/services/desktop/scenes/desktop_scene_pin_timeout.c +++ b/applications/services/desktop/scenes/desktop_scene_pin_timeout.c @@ -1,6 +1,5 @@ #include #include -#include #include #include "../desktop_i.h" diff --git a/applications/services/desktop/views/desktop_view_locked.c b/applications/services/desktop/views/desktop_view_locked.c index 3cee25425ef..8df889dddd3 100644 --- a/applications/services/desktop/views/desktop_view_locked.c +++ b/applications/services/desktop/views/desktop_view_locked.c @@ -5,7 +5,6 @@ #include #include #include -#include #include #include "../desktop_i.h" @@ -29,7 +28,7 @@ struct DesktopViewLocked { DesktopViewLockedCallback callback; void* context; - TimerHandle_t timer; + FuriTimer* timer; uint8_t lock_count; uint32_t lock_lastpress; }; @@ -58,8 +57,8 @@ void desktop_view_locked_set_callback( locked_view->context = context; } -static void locked_view_timer_callback(TimerHandle_t timer) { - DesktopViewLocked* locked_view = pvTimerGetTimerID(timer); +static void locked_view_timer_callback(void* context) { + DesktopViewLocked* locked_view = context; locked_view->callback(DesktopLockedEventUpdate, locked_view->context); } @@ -90,7 +89,7 @@ static void desktop_view_locked_update_hint_icon_timeout(DesktopViewLocked* lock model->view_state = DesktopViewLockedStateLockedHintShown; } view_commit_model(locked_view->view, change_state); - xTimerChangePeriod(locked_view->timer, pdMS_TO_TICKS(LOCKED_HINT_TIMEOUT_MS), portMAX_DELAY); + furi_timer_start(locked_view->timer, LOCKED_HINT_TIMEOUT_MS); } void desktop_view_locked_update(DesktopViewLocked* locked_view) { @@ -110,7 +109,7 @@ void desktop_view_locked_update(DesktopViewLocked* locked_view) { view_commit_model(locked_view->view, true); if(view_state != DesktopViewLockedStateDoorsClosing) { - xTimerStop(locked_view->timer, portMAX_DELAY); + furi_timer_stop(locked_view->timer); } } @@ -148,7 +147,7 @@ static bool desktop_view_locked_input(InputEvent* event, void* context) { furi_assert(context); bool is_changed = false; - const uint32_t press_time = xTaskGetTickCount(); + const uint32_t press_time = furi_get_tick(); DesktopViewLocked* locked_view = context; DesktopViewLockedModel* model = view_get_model(locked_view->view); if(model->view_state == DesktopViewLockedStateUnlockedHintShown && @@ -196,7 +195,7 @@ DesktopViewLocked* desktop_view_locked_alloc() { DesktopViewLocked* locked_view = malloc(sizeof(DesktopViewLocked)); locked_view->view = view_alloc(); locked_view->timer = - xTimerCreate(NULL, 1000 / 16, pdTRUE, locked_view, locked_view_timer_callback); + furi_timer_alloc(locked_view_timer_callback, FuriTimerTypePeriodic, locked_view); view_allocate_model(locked_view->view, ViewModelTypeLocking, sizeof(DesktopViewLockedModel)); view_set_context(locked_view->view, locked_view); @@ -219,7 +218,7 @@ void desktop_view_locked_close_doors(DesktopViewLocked* locked_view) { model->view_state = DesktopViewLockedStateDoorsClosing; model->door_offset = DOOR_OFFSET_START; view_commit_model(locked_view->view, true); - xTimerChangePeriod(locked_view->timer, pdMS_TO_TICKS(DOOR_MOVING_INTERVAL_MS), portMAX_DELAY); + furi_timer_start(locked_view->timer, DOOR_MOVING_INTERVAL_MS); } void desktop_view_locked_lock(DesktopViewLocked* locked_view, bool pin_locked) { @@ -236,7 +235,7 @@ void desktop_view_locked_unlock(DesktopViewLocked* locked_view) { model->view_state = DesktopViewLockedStateUnlockedHintShown; model->pin_locked = false; view_commit_model(locked_view->view, true); - xTimerChangePeriod(locked_view->timer, pdMS_TO_TICKS(UNLOCKED_HINT_TIMEOUT_MS), portMAX_DELAY); + furi_timer_start(locked_view->timer, UNLOCKED_HINT_TIMEOUT_MS); } bool desktop_view_locked_is_locked_hint_visible(DesktopViewLocked* locked_view) { diff --git a/applications/services/desktop/views/desktop_view_main.c b/applications/services/desktop/views/desktop_view_main.c index d323567e792..5e16f60862a 100644 --- a/applications/services/desktop/views/desktop_view_main.c +++ b/applications/services/desktop/views/desktop_view_main.c @@ -13,14 +13,14 @@ struct DesktopMainView { View* view; DesktopMainViewCallback callback; void* context; - TimerHandle_t poweroff_timer; + FuriTimer* poweroff_timer; bool dummy_mode; }; #define DESKTOP_MAIN_VIEW_POWEROFF_TIMEOUT 5000 -static void desktop_main_poweroff_timer_callback(TimerHandle_t timer) { - DesktopMainView* main_view = pvTimerGetTimerID(timer); +static void desktop_main_poweroff_timer_callback(void* context) { + DesktopMainView* main_view = context; main_view->callback(DesktopMainEventOpenPowerOff, main_view->context); } @@ -90,12 +90,9 @@ bool desktop_main_input_callback(InputEvent* event, void* context) { if(event->key == InputKeyBack) { if(event->type == InputTypePress) { - xTimerChangePeriod( - main_view->poweroff_timer, - pdMS_TO_TICKS(DESKTOP_MAIN_VIEW_POWEROFF_TIMEOUT), - portMAX_DELAY); + furi_timer_start(main_view->poweroff_timer, DESKTOP_MAIN_VIEW_POWEROFF_TIMEOUT); } else if(event->type == InputTypeRelease) { - xTimerStop(main_view->poweroff_timer, portMAX_DELAY); + furi_timer_stop(main_view->poweroff_timer); } } @@ -109,12 +106,8 @@ DesktopMainView* desktop_main_alloc() { view_set_context(main_view->view, main_view); view_set_input_callback(main_view->view, desktop_main_input_callback); - main_view->poweroff_timer = xTimerCreate( - NULL, - pdMS_TO_TICKS(DESKTOP_MAIN_VIEW_POWEROFF_TIMEOUT), - pdFALSE, - main_view, - desktop_main_poweroff_timer_callback); + main_view->poweroff_timer = + furi_timer_alloc(desktop_main_poweroff_timer_callback, FuriTimerTypeOnce, main_view); return main_view; } diff --git a/applications/services/desktop/views/desktop_view_pin_input.c b/applications/services/desktop/views/desktop_view_pin_input.c index 93bbffedc6f..0894bb776f4 100644 --- a/applications/services/desktop/views/desktop_view_pin_input.c +++ b/applications/services/desktop/views/desktop_view_pin_input.c @@ -4,7 +4,6 @@ #include #include #include -#include #include "desktop_view_pin_input.h" #include @@ -21,7 +20,7 @@ struct DesktopViewPinInput { DesktopViewPinInputCallback timeout_callback; DesktopViewPinInputDoneCallback done_callback; void* context; - TimerHandle_t timer; + FuriTimer* timer; }; typedef struct { @@ -90,7 +89,7 @@ static bool desktop_view_pin_input_input(InputEvent* event, void* context) { pin_input->back_callback(pin_input->context); } - xTimerStart(pin_input->timer, 0); + furi_timer_start(pin_input->timer, NO_ACTIVITY_TIMEOUT); return true; } @@ -170,8 +169,8 @@ static void desktop_view_pin_input_draw(Canvas* canvas, void* context) { } } -void desktop_view_pin_input_timer_callback(TimerHandle_t timer) { - DesktopViewPinInput* pin_input = pvTimerGetTimerID(timer); +void desktop_view_pin_input_timer_callback(void* context) { + DesktopViewPinInput* pin_input = context; if(pin_input->timeout_callback) { pin_input->timeout_callback(pin_input->context); @@ -180,12 +179,12 @@ void desktop_view_pin_input_timer_callback(TimerHandle_t timer) { static void desktop_view_pin_input_enter(void* context) { DesktopViewPinInput* pin_input = context; - xTimerStart(pin_input->timer, portMAX_DELAY); + furi_timer_start(pin_input->timer, NO_ACTIVITY_TIMEOUT); } static void desktop_view_pin_input_exit(void* context) { DesktopViewPinInput* pin_input = context; - xTimerStop(pin_input->timer, portMAX_DELAY); + furi_timer_stop(pin_input->timer); } DesktopViewPinInput* desktop_view_pin_input_alloc(void) { @@ -195,12 +194,8 @@ DesktopViewPinInput* desktop_view_pin_input_alloc(void) { view_set_context(pin_input->view, pin_input); view_set_draw_callback(pin_input->view, desktop_view_pin_input_draw); view_set_input_callback(pin_input->view, desktop_view_pin_input_input); - pin_input->timer = xTimerCreate( - NULL, - pdMS_TO_TICKS(NO_ACTIVITY_TIMEOUT), - pdFALSE, - pin_input, - desktop_view_pin_input_timer_callback); + pin_input->timer = + furi_timer_alloc(desktop_view_pin_input_timer_callback, FuriTimerTypeOnce, pin_input); view_set_enter_callback(pin_input->view, desktop_view_pin_input_enter); view_set_exit_callback(pin_input->view, desktop_view_pin_input_exit); @@ -216,11 +211,7 @@ DesktopViewPinInput* desktop_view_pin_input_alloc(void) { void desktop_view_pin_input_free(DesktopViewPinInput* pin_input) { furi_assert(pin_input); - xTimerStop(pin_input->timer, portMAX_DELAY); - while(xTimerIsTimerActive(pin_input->timer)) { - furi_delay_tick(1); - } - xTimerDelete(pin_input->timer, portMAX_DELAY); + furi_timer_free(pin_input->timer); view_free(pin_input->view); free(pin_input); diff --git a/applications/services/desktop/views/desktop_view_pin_timeout.c b/applications/services/desktop/views/desktop_view_pin_timeout.c index e64c264ffde..f24ecc8ead3 100644 --- a/applications/services/desktop/views/desktop_view_pin_timeout.c +++ b/applications/services/desktop/views/desktop_view_pin_timeout.c @@ -3,7 +3,6 @@ #include #include #include -#include #include #include #include @@ -13,7 +12,7 @@ struct DesktopViewPinTimeout { View* view; - TimerHandle_t timer; + FuriTimer* timer; DesktopViewPinTimeoutDoneCallback callback; void* context; }; @@ -32,8 +31,8 @@ void desktop_view_pin_timeout_set_callback( instance->context = context; } -static void desktop_view_pin_timeout_timer_callback(TimerHandle_t timer) { - DesktopViewPinTimeout* instance = pvTimerGetTimerID(timer); +static void desktop_view_pin_timeout_timer_callback(void* context) { + DesktopViewPinTimeout* instance = context; bool stop = false; DesktopViewPinTimeoutModel* model = view_get_model(instance->view); @@ -45,7 +44,7 @@ static void desktop_view_pin_timeout_timer_callback(TimerHandle_t timer) { view_commit_model(instance->view, true); if(stop) { - xTimerStop(instance->timer, portMAX_DELAY); + furi_timer_stop(instance->timer); instance->callback(instance->context); } } @@ -73,15 +72,15 @@ static void desktop_view_pin_timeout_draw(Canvas* canvas, void* _model) { void desktop_view_pin_timeout_free(DesktopViewPinTimeout* instance) { view_free(instance->view); - xTimerDelete(instance->timer, portMAX_DELAY); + furi_timer_free(instance->timer); free(instance); } DesktopViewPinTimeout* desktop_view_pin_timeout_alloc(void) { DesktopViewPinTimeout* instance = malloc(sizeof(DesktopViewPinTimeout)); - instance->timer = xTimerCreate( - NULL, pdMS_TO_TICKS(1000), pdTRUE, instance, desktop_view_pin_timeout_timer_callback); + instance->timer = + furi_timer_alloc(desktop_view_pin_timeout_timer_callback, FuriTimerTypePeriodic, instance); instance->view = view_alloc(); view_allocate_model(instance->view, ViewModelTypeLockFree, sizeof(DesktopViewPinTimeoutModel)); @@ -101,7 +100,7 @@ void desktop_view_pin_timeout_start(DesktopViewPinTimeout* instance, uint32_t ti model->time_left = time_left; view_commit_model(instance->view, true); - xTimerStart(instance->timer, portMAX_DELAY); + furi_timer_start(instance->timer, 1000); } View* desktop_view_pin_timeout_get_view(DesktopViewPinTimeout* instance) { diff --git a/applications/services/dolphin/dolphin.c b/applications/services/dolphin/dolphin.c index 579b400ad01..5b526ed3a69 100644 --- a/applications/services/dolphin/dolphin.c +++ b/applications/services/dolphin/dolphin.c @@ -1,7 +1,6 @@ #include "dolphin/dolphin.h" #include "dolphin/helpers/dolphin_state.h" #include "dolphin_i.h" -#include "portmacro.h" #include "projdefs.h" #include #include @@ -45,8 +44,8 @@ void dolphin_flush(Dolphin* dolphin) { dolphin_event_send_wait(dolphin, &event); } -void dolphin_butthurt_timer_callback(TimerHandle_t xTimer) { - Dolphin* dolphin = pvTimerGetTimerID(xTimer); +void dolphin_butthurt_timer_callback(void* context) { + Dolphin* dolphin = context; furi_assert(dolphin); DolphinEvent event; @@ -54,8 +53,8 @@ void dolphin_butthurt_timer_callback(TimerHandle_t xTimer) { dolphin_event_send_async(dolphin, &event); } -void dolphin_flush_timer_callback(TimerHandle_t xTimer) { - Dolphin* dolphin = pvTimerGetTimerID(xTimer); +void dolphin_flush_timer_callback(void* context) { + Dolphin* dolphin = context; furi_assert(dolphin); DolphinEvent event; @@ -63,11 +62,11 @@ void dolphin_flush_timer_callback(TimerHandle_t xTimer) { dolphin_event_send_async(dolphin, &event); } -void dolphin_clear_limits_timer_callback(TimerHandle_t xTimer) { - Dolphin* dolphin = pvTimerGetTimerID(xTimer); +void dolphin_clear_limits_timer_callback(void* context) { + Dolphin* dolphin = context; furi_assert(dolphin); - xTimerChangePeriod(dolphin->clear_limits_timer, HOURS_IN_TICKS(24), portMAX_DELAY); + furi_timer_start(dolphin->clear_limits_timer, HOURS_IN_TICKS(24)); DolphinEvent event; event.type = DolphinEventTypeClearLimits; @@ -80,12 +79,12 @@ Dolphin* dolphin_alloc() { dolphin->state = dolphin_state_alloc(); dolphin->event_queue = furi_message_queue_alloc(8, sizeof(DolphinEvent)); dolphin->pubsub = furi_pubsub_alloc(); - dolphin->butthurt_timer = xTimerCreate( - NULL, HOURS_IN_TICKS(2 * 24), pdTRUE, dolphin, dolphin_butthurt_timer_callback); + dolphin->butthurt_timer = + furi_timer_alloc(dolphin_butthurt_timer_callback, FuriTimerTypePeriodic, dolphin); dolphin->flush_timer = - xTimerCreate(NULL, 30 * 1000, pdFALSE, dolphin, dolphin_flush_timer_callback); - dolphin->clear_limits_timer = xTimerCreate( - NULL, HOURS_IN_TICKS(24), pdTRUE, dolphin, dolphin_clear_limits_timer_callback); + furi_timer_alloc(dolphin_flush_timer_callback, FuriTimerTypeOnce, dolphin); + dolphin->clear_limits_timer = + furi_timer_alloc(dolphin_clear_limits_timer_callback, FuriTimerTypePeriodic, dolphin); return dolphin; } @@ -125,14 +124,14 @@ FuriPubSub* dolphin_get_pubsub(Dolphin* dolphin) { static void dolphin_update_clear_limits_timer_period(Dolphin* dolphin) { furi_assert(dolphin); - TickType_t now_ticks = xTaskGetTickCount(); - TickType_t timer_expires_at = xTimerGetExpiryTime(dolphin->clear_limits_timer); + uint32_t now_ticks = furi_get_tick(); + uint32_t timer_expires_at = furi_timer_get_expire_time(dolphin->clear_limits_timer); if((timer_expires_at - now_ticks) > HOURS_IN_TICKS(0.1)) { FuriHalRtcDateTime date; furi_hal_rtc_get_datetime(&date); - TickType_t now_time_in_ms = ((date.hour * 60 + date.minute) * 60 + date.second) * 1000; - TickType_t time_to_clear_limits = 0; + uint32_t now_time_in_ms = ((date.hour * 60 + date.minute) * 60 + date.second) * 1000; + uint32_t time_to_clear_limits = 0; if(date.hour < 5) { time_to_clear_limits = HOURS_IN_TICKS(5) - now_time_in_ms; @@ -140,7 +139,7 @@ static void dolphin_update_clear_limits_timer_period(Dolphin* dolphin) { time_to_clear_limits = HOURS_IN_TICKS(24 + 5) - now_time_in_ms; } - xTimerChangePeriod(dolphin->clear_limits_timer, time_to_clear_limits, portMAX_DELAY); + furi_timer_start(dolphin->clear_limits_timer, time_to_clear_limits); } } @@ -156,9 +155,9 @@ int32_t dolphin_srv(void* p) { furi_record_create(RECORD_DOLPHIN, dolphin); dolphin_state_load(dolphin->state); - xTimerReset(dolphin->butthurt_timer, portMAX_DELAY); + furi_timer_stop(dolphin->butthurt_timer); dolphin_update_clear_limits_timer_period(dolphin); - xTimerReset(dolphin->clear_limits_timer, portMAX_DELAY); + furi_timer_stop(dolphin->clear_limits_timer); DolphinEvent event; while(1) { @@ -168,8 +167,8 @@ int32_t dolphin_srv(void* p) { dolphin_state_on_deed(dolphin->state, event.deed); DolphinPubsubEvent event = DolphinPubsubEventUpdate; furi_pubsub_publish(dolphin->pubsub, &event); - xTimerReset(dolphin->butthurt_timer, portMAX_DELAY); - xTimerReset(dolphin->flush_timer, portMAX_DELAY); + furi_timer_restart(dolphin->butthurt_timer); + furi_timer_restart(dolphin->flush_timer); } else if(event.type == DolphinEventTypeStats) { event.stats->icounter = dolphin->state->data.icounter; event.stats->butthurt = dolphin->state->data.butthurt; diff --git a/applications/services/dolphin/dolphin_i.h b/applications/services/dolphin/dolphin_i.h index ceeff1e1a9a..2d716c18138 100644 --- a/applications/services/dolphin/dolphin_i.h +++ b/applications/services/dolphin/dolphin_i.h @@ -30,9 +30,9 @@ struct Dolphin { // Queue FuriMessageQueue* event_queue; FuriPubSub* pubsub; - TimerHandle_t butthurt_timer; - TimerHandle_t flush_timer; - TimerHandle_t clear_limits_timer; + FuriTimer* butthurt_timer; + FuriTimer* flush_timer; + FuriTimer* clear_limits_timer; }; Dolphin* dolphin_alloc(); diff --git a/applications/services/gui/icon_animation.c b/applications/services/gui/icon_animation.c index b63d233f3d5..a39ef2e254e 100644 --- a/applications/services/gui/icon_animation.c +++ b/applications/services/gui/icon_animation.c @@ -15,7 +15,6 @@ IconAnimation* icon_animation_alloc(const Icon* icon) { void icon_animation_free(IconAnimation* instance) { furi_assert(instance); icon_animation_stop(instance); - while(xTimerIsTimerActive(instance->timer) == pdTRUE) furi_delay_tick(1); furi_timer_free(instance->timer); free(instance); } @@ -67,10 +66,9 @@ void icon_animation_start(IconAnimation* instance) { instance->animating = true; furi_assert(instance->icon->frame_rate); furi_check( - xTimerChangePeriod( + furi_timer_start( instance->timer, - (furi_kernel_get_tick_frequency() / instance->icon->frame_rate), - portMAX_DELAY) == pdPASS); + (furi_kernel_get_tick_frequency() / instance->icon->frame_rate)) == FuriStatusOk); } } @@ -78,7 +76,7 @@ void icon_animation_stop(IconAnimation* instance) { furi_assert(instance); if(instance->animating) { instance->animating = false; - furi_check(xTimerStop(instance->timer, portMAX_DELAY) == pdPASS); + furi_timer_stop(instance->timer); instance->frame = 0; } } diff --git a/applications/services/input/input.c b/applications/services/input/input.c index 8da0a340033..216aa39b2e6 100644 --- a/applications/services/input/input.c +++ b/applications/services/input/input.c @@ -6,20 +6,6 @@ static Input* input = NULL; -inline static void input_timer_start(FuriTimer* timer_id, uint32_t ticks) { - TimerHandle_t hTimer = (TimerHandle_t)timer_id; - furi_check(xTimerChangePeriod(hTimer, ticks, portMAX_DELAY) == pdPASS); -} - -inline static void input_timer_stop(FuriTimer* timer_id) { - TimerHandle_t hTimer = (TimerHandle_t)timer_id; - furi_check(xTimerStop(hTimer, portMAX_DELAY) == pdPASS); - // xTimerStop is not actually stopping timer, - // Instead it places stop event into timer queue - // This code ensures that timer is stopped - while(xTimerIsTimerActive(hTimer) == pdTRUE) furi_delay_tick(1); -} - void input_press_timer_callback(void* arg) { InputPinState* input_pin = arg; InputEvent event; @@ -123,10 +109,12 @@ int32_t input_srv(void* p) { input->counter++; input->pin_states[i].counter = input->counter; event.sequence_counter = input->pin_states[i].counter; - input_timer_start(input->pin_states[i].press_timer, INPUT_PRESS_TICKS); + furi_timer_start(input->pin_states[i].press_timer, INPUT_PRESS_TICKS); } else { event.sequence_counter = input->pin_states[i].counter; - input_timer_stop(input->pin_states[i].press_timer); + furi_timer_stop(input->pin_states[i].press_timer); + while(furi_timer_is_running(input->pin_states[i].press_timer)) + furi_delay_tick(1); if(input->pin_states[i].press_counter < INPUT_LONG_PRESS_COUNTS) { event.type = InputTypeShort; furi_pubsub_publish(input->event_pubsub, &event); diff --git a/applications/services/rpc/rpc.c b/applications/services/rpc/rpc.c index 3e9665ad8da..826f2225376 100644 --- a/applications/services/rpc/rpc.c +++ b/applications/services/rpc/rpc.c @@ -6,7 +6,6 @@ #include #include -#include #include @@ -162,7 +161,7 @@ void rpc_session_set_terminated_callback( * odd: client sends close request and sends command after. */ size_t - rpc_session_feed(RpcSession* session, uint8_t* encoded_bytes, size_t size, TickType_t timeout) { + rpc_session_feed(RpcSession* session, uint8_t* encoded_bytes, size_t size, uint32_t timeout) { furi_assert(session); furi_assert(encoded_bytes); diff --git a/applications/services/rpc/rpc.h b/applications/services/rpc/rpc.h index d11fdc16244..863bca355b4 100644 --- a/applications/services/rpc/rpc.h +++ b/applications/services/rpc/rpc.h @@ -124,7 +124,7 @@ void rpc_session_set_terminated_callback( * * @return actually consumed bytes */ -size_t rpc_session_feed(RpcSession* session, uint8_t* buffer, size_t size, TickType_t timeout); +size_t rpc_session_feed(RpcSession* session, uint8_t* buffer, size_t size, uint32_t timeout); /** Get available size of RPC buffer * diff --git a/applications/services/rpc/rpc_cli.c b/applications/services/rpc/rpc_cli.c index f1c139b5f93..4612752a83c 100644 --- a/applications/services/rpc/rpc_cli.c +++ b/applications/services/rpc/rpc_cli.c @@ -2,7 +2,6 @@ #include #include #include -#include #define TAG "RpcCli" diff --git a/applications/system/updater/updater.c b/applications/system/updater/updater.c index e749f3ce6e6..4c7fd29e9cf 100644 --- a/applications/system/updater/updater.c +++ b/applications/system/updater/updater.c @@ -5,7 +5,6 @@ #include #include #include -#include #include static bool updater_custom_event_callback(void* context, uint32_t event) { diff --git a/furi/core/base.h b/furi/core/base.h index 29e87419200..642ff2b6cda 100644 --- a/furi/core/base.h +++ b/furi/core/base.h @@ -2,6 +2,7 @@ #include #include +#include #ifdef __cplusplus extern "C" { diff --git a/furi/core/check.c b/furi/core/check.c index ea1de71425d..b56db656379 100644 --- a/furi/core/check.c +++ b/furi/core/check.c @@ -55,8 +55,6 @@ PLACE_IN_SECTION("MB_MEM2") uint32_t __furi_check_registers[13] = {0}; : "memory"); extern size_t xPortGetTotalHeapSize(void); -extern size_t xPortGetFreeHeapSize(void); -extern size_t xPortGetMinimumEverFreeHeapSize(void); static void __furi_put_uint32_as_text(uint32_t data) { char tmp_str[] = "-2147483648"; diff --git a/furi/core/common_defines.h b/furi/core/common_defines.h index 5bd218d3576..2b30c3b06da 100644 --- a/furi/core/common_defines.h +++ b/furi/core/common_defines.h @@ -2,8 +2,6 @@ #include "core_defines.h" #include -#include -#include #ifdef __cplusplus extern "C" { diff --git a/furi/core/critical.c b/furi/core/critical.c index 57fe2403be7..3bef2be38e0 100644 --- a/furi/core/critical.c +++ b/furi/core/critical.c @@ -1,5 +1,8 @@ #include "common_defines.h" +#include +#include + __FuriCriticalInfo __furi_critical_enter(void) { __FuriCriticalInfo info; diff --git a/furi/core/event_flag.c b/furi/core/event_flag.c index 9406a581f7b..96b9591877e 100644 --- a/furi/core/event_flag.c +++ b/furi/core/event_flag.c @@ -2,6 +2,7 @@ #include "common_defines.h" #include "check.h" +#include #include #define FURI_EVENT_FLAG_MAX_BITS_EVENT_GROUPS 24U diff --git a/furi/core/kernel.c b/furi/core/kernel.c index 7928ad11c38..89a50a9b52f 100644 --- a/furi/core/kernel.c +++ b/furi/core/kernel.c @@ -5,6 +5,9 @@ #include +#include +#include + #include CMSIS_device_header bool furi_kernel_is_irq_or_masked() { @@ -31,6 +34,10 @@ bool furi_kernel_is_irq_or_masked() { return (irq); } +bool furi_kernel_is_running() { + return xTaskGetSchedulerState() != taskSCHEDULER_RUNNING; +} + int32_t furi_kernel_lock() { furi_assert(!furi_kernel_is_irq_or_masked()); diff --git a/furi/core/kernel.h b/furi/core/kernel.h index 371f76c1f78..c962402efd6 100644 --- a/furi/core/kernel.h +++ b/furi/core/kernel.h @@ -27,6 +27,12 @@ extern "C" { */ bool furi_kernel_is_irq_or_masked(); +/** Check if kernel is running + * + * @return true if running, false otherwise + */ +bool furi_kernel_is_running(); + /** Lock kernel, pause process scheduling * * @warning This should never be called in interrupt request context. diff --git a/furi/core/memmgr_heap.c b/furi/core/memmgr_heap.c index b8baf9c7c62..a3e127c3c1b 100644 --- a/furi/core/memmgr_heap.c +++ b/furi/core/memmgr_heap.c @@ -47,8 +47,8 @@ all the API functions to use the MPU wrappers. That should only be done when task.h is included from an application file. */ #define MPU_WRAPPERS_INCLUDED_FROM_API_FILE -#include "FreeRTOS.h" -#include "task.h" +#include +#include #undef MPU_WRAPPERS_INCLUDED_FROM_API_FILE diff --git a/furi/core/message_queue.c b/furi/core/message_queue.c index ddf56f00640..e20fa420a08 100644 --- a/furi/core/message_queue.c +++ b/furi/core/message_queue.c @@ -1,8 +1,9 @@ #include "kernel.h" #include "message_queue.h" +#include "check.h" + #include #include -#include "check.h" FuriMessageQueue* furi_message_queue_alloc(uint32_t msg_count, uint32_t msg_size) { furi_assert((furi_kernel_is_irq_or_masked() == 0U) && (msg_count > 0U) && (msg_size > 0U)); diff --git a/furi/core/mutex.c b/furi/core/mutex.c index 9fb964a1e53..8794e10dc39 100644 --- a/furi/core/mutex.c +++ b/furi/core/mutex.c @@ -2,6 +2,7 @@ #include "check.h" #include "common_defines.h" +#include #include FuriMutex* furi_mutex_alloc(FuriMutexType type) { diff --git a/furi/core/semaphore.c b/furi/core/semaphore.c index 8c99bfc541f..1f1a07780cd 100644 --- a/furi/core/semaphore.c +++ b/furi/core/semaphore.c @@ -2,6 +2,7 @@ #include "check.h" #include "common_defines.h" +#include #include FuriSemaphore* furi_semaphore_alloc(uint32_t max_count, uint32_t initial_count) { diff --git a/furi/core/stream_buffer.c b/furi/core/stream_buffer.c index bf483948be6..a13d256b110 100644 --- a/furi/core/stream_buffer.c +++ b/furi/core/stream_buffer.c @@ -2,6 +2,7 @@ #include "check.h" #include "stream_buffer.h" #include "common_defines.h" + #include #include diff --git a/furi/core/thread.c b/furi/core/thread.c index de50bde7a47..db4feeb4e16 100644 --- a/furi/core/thread.c +++ b/furi/core/thread.c @@ -7,11 +7,13 @@ #include "mutex.h" #include "string.h" -#include #include "log.h" #include #include +#include +#include + #define TAG "FuriThread" #define THREAD_NOTIFY_INDEX 1 // Index 0 is used for stream buffers diff --git a/furi/core/thread.h b/furi/core/thread.h index 692f2a10083..44d66fb21a2 100644 --- a/furi/core/thread.h +++ b/furi/core/thread.h @@ -8,6 +8,9 @@ #include "base.h" #include "common_defines.h" +#include +#include + #ifdef __cplusplus extern "C" { #endif @@ -28,7 +31,8 @@ typedef enum { FuriThreadPriorityNormal = 16, /**< Normal */ FuriThreadPriorityHigh = 17, /**< High */ FuriThreadPriorityHighest = 18, /**< Highest */ - FuriThreadPriorityIsr = (configMAX_PRIORITIES - 1), /**< Deferred ISR (highest possible) */ + FuriThreadPriorityIsr = + (FURI_CONFIG_THREAD_MAX_PRIORITIES - 1), /**< Deferred ISR (highest possible) */ } FuriThreadPriority; /** FuriThread anonymous structure */ diff --git a/furi/core/timer.c b/furi/core/timer.c index 7743ffe7018..0a89b892017 100644 --- a/furi/core/timer.c +++ b/furi/core/timer.c @@ -97,6 +97,23 @@ FuriStatus furi_timer_start(FuriTimer* instance, uint32_t ticks) { return (stat); } +FuriStatus furi_timer_restart(FuriTimer* instance) { + furi_assert(!furi_kernel_is_irq_or_masked()); + furi_assert(instance); + + TimerHandle_t hTimer = (TimerHandle_t)instance; + FuriStatus stat; + + if(xTimerReset(hTimer, portMAX_DELAY) == pdPASS) { + stat = FuriStatusOk; + } else { + stat = FuriStatusErrorResource; + } + + /* Return execution status */ + return (stat); +} + FuriStatus furi_timer_stop(FuriTimer* instance) { furi_assert(!furi_kernel_is_irq_or_masked()); furi_assert(instance); @@ -125,6 +142,15 @@ uint32_t furi_timer_is_running(FuriTimer* instance) { return (uint32_t)xTimerIsTimerActive(hTimer); } +uint32_t furi_timer_get_expire_time(FuriTimer* instance) { + furi_assert(!furi_kernel_is_irq_or_masked()); + furi_assert(instance); + + TimerHandle_t hTimer = (TimerHandle_t)instance; + + return (uint32_t)xTimerGetExpiryTime(hTimer); +} + void furi_timer_pending_callback(FuriTimerPendigCallback callback, void* context, uint32_t arg) { BaseType_t ret = pdFAIL; if(furi_kernel_is_irq_or_masked()) { @@ -133,4 +159,17 @@ void furi_timer_pending_callback(FuriTimerPendigCallback callback, void* context ret = xTimerPendFunctionCall(callback, context, arg, FuriWaitForever); } furi_check(ret == pdPASS); +} + +void furi_timer_set_thread_priority(FuriTimerThreadPriority priority) { + furi_assert(!furi_kernel_is_irq_or_masked()); + TaskHandle_t task_handle = xTaskGetHandle(configTIMER_SERVICE_TASK_NAME); + + if(priority == FuriTimerThreadPriorityNormal) { + vTaskPrioritySet(task_handle, configTIMER_TASK_PRIORITY); + } else if(priority == FuriTimerThreadPriorityElevated) { + vTaskPrioritySet(task_handle, configMAX_PRIORITIES - 1); + } else { + furi_crash(); + } } \ No newline at end of file diff --git a/furi/core/timer.h b/furi/core/timer.h index 3f43de5fd9f..47b44c71a64 100644 --- a/furi/core/timer.h +++ b/furi/core/timer.h @@ -40,6 +40,14 @@ void furi_timer_free(FuriTimer* instance); */ FuriStatus furi_timer_start(FuriTimer* instance, uint32_t ticks); +/** Restart timer with previous timeout value + * + * @param instance The pointer to FuriTimer instance + * + * @return The furi status. + */ +FuriStatus furi_timer_restart(FuriTimer* instance); + /** Stop timer * * @param instance The pointer to FuriTimer instance @@ -56,10 +64,29 @@ FuriStatus furi_timer_stop(FuriTimer* instance); */ uint32_t furi_timer_is_running(FuriTimer* instance); +/** Get timer expire time + * + * @param instance The Timer instance + * + * @return expire tick + */ +uint32_t furi_timer_get_expire_time(FuriTimer* instance); + typedef void (*FuriTimerPendigCallback)(void* context, uint32_t arg); void furi_timer_pending_callback(FuriTimerPendigCallback callback, void* context, uint32_t arg); +typedef enum { + FuriTimerThreadPriorityNormal, /**< Lower then other threads */ + FuriTimerThreadPriorityElevated, /**< Same as other threads */ +} FuriTimerThreadPriority; + +/** Set Timer thread priority + * + * @param[in] priority The priority + */ +void furi_timer_set_thread_priority(FuriTimerThreadPriority priority); + #ifdef __cplusplus } #endif diff --git a/furi/flipper.c b/furi/flipper.c index 8806ce27fb3..b29424a9f4f 100644 --- a/furi/flipper.c +++ b/furi/flipper.c @@ -5,6 +5,8 @@ #include #include +#include + #define TAG "Flipper" static void flipper_print_version(const char* target, const Version* version) { diff --git a/furi/furi.c b/furi/furi.c index cc0e3f4f12f..6247e259fb9 100644 --- a/furi/furi.c +++ b/furi/furi.c @@ -1,6 +1,8 @@ #include "furi.h" #include -#include "queue.h" + +#include +#include void furi_init() { furi_assert(!furi_kernel_is_irq_or_masked()); diff --git a/furi/furi.h b/furi/furi.h index b1299c9a952..422509055f7 100644 --- a/furi/furi.h +++ b/furi/furi.h @@ -21,9 +21,6 @@ #include -// FreeRTOS timer, REMOVE AFTER REFACTORING -#include - // Workaround for math.h leaking through HAL in older versions #include diff --git a/lib/infrared/worker/infrared_worker.c b/lib/infrared/worker/infrared_worker.c index 5e3257e2604..38392fc06ed 100644 --- a/lib/infrared/worker/infrared_worker.c +++ b/lib/infrared/worker/infrared_worker.c @@ -165,7 +165,7 @@ static int32_t infrared_worker_rx_thread(void* thread_context) { InfraredWorker* instance = thread_context; uint32_t events = 0; LevelDuration level_duration; - TickType_t last_blink_time = 0; + uint32_t last_blink_time = 0; while(1) { events = furi_thread_flags_wait(INFRARED_WORKER_ALL_RX_EVENTS, 0, FuriWaitForever); @@ -173,8 +173,8 @@ static int32_t infrared_worker_rx_thread(void* thread_context) { if(events & INFRARED_WORKER_RX_RECEIVED) { if(!instance->rx.overrun && instance->blink_enable && - ((xTaskGetTickCount() - last_blink_time) > 80)) { - last_blink_time = xTaskGetTickCount(); + ((furi_get_tick() - last_blink_time) > 80)) { + last_blink_time = furi_get_tick(); notification_message(instance->notification, &sequence_blink_blue_10); } if(instance->signal.timings_cnt == 0) diff --git a/lib/lfrfid/lfrfid_worker.c b/lib/lfrfid/lfrfid_worker.c index cbc7b02e37e..ffaa8ee925f 100644 --- a/lib/lfrfid/lfrfid_worker.c +++ b/lib/lfrfid/lfrfid_worker.c @@ -1,7 +1,7 @@ +#include "lfrfid_worker_i.h" + #include #include -#include -#include "lfrfid_worker_i.h" typedef enum { LFRFIDEventStopThread = (1 << 0), diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.h b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.h index 1113d381c2b..ce878cb40ae 100644 --- a/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.h +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.h @@ -43,8 +43,6 @@ struct Iso14443_4aPoller { void* context; }; -Iso14443_4aError iso14443_4a_process_error(Iso14443_3aError error); - const Iso14443_4aData* iso14443_4a_poller_get_data(Iso14443_4aPoller* instance); Iso14443_4aError iso14443_4a_poller_halt(Iso14443_4aPoller* instance); diff --git a/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller_i.h b/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller_i.h index 4df00adcf10..bd55c618820 100644 --- a/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller_i.h +++ b/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller_i.h @@ -40,8 +40,6 @@ struct Iso14443_4bPoller { void* context; }; -Iso14443_4bError iso14443_4b_process_error(Iso14443_3bError error); - const Iso14443_4bData* iso14443_4b_poller_get_data(Iso14443_4bPoller* instance); Iso14443_4bError iso14443_4b_poller_halt(Iso14443_4bPoller* instance); diff --git a/lib/print/wrappers.h b/lib/print/wrappers.h index b6f0f004b65..7c0d1f92ebe 100644 --- a/lib/print/wrappers.h +++ b/lib/print/wrappers.h @@ -1,3 +1,5 @@ +#pragma once + #include #include #include @@ -6,7 +8,6 @@ extern "C" { #endif -void _putchar(char character); int __wrap_printf(const char* format, ...); int __wrap_vsnprintf(char* str, size_t size, const char* format, va_list args); int __wrap_puts(const char* str); diff --git a/lib/subghz/SConscript b/lib/subghz/SConscript index 82c925c1a31..35850aa83e8 100644 --- a/lib/subghz/SConscript +++ b/lib/subghz/SConscript @@ -12,6 +12,7 @@ env.Append( File("subghz_tx_rx_worker.h"), File("transmitter.h"), File("protocols/raw.h"), + File("protocols/public_api.h"), File("blocks/const.h"), File("blocks/decoder.h"), File("blocks/encoder.h"), diff --git a/lib/subghz/protocols/bin_raw.h b/lib/subghz/protocols/bin_raw.h index 82775e57593..26cc6ec3a20 100644 --- a/lib/subghz/protocols/bin_raw.h +++ b/lib/subghz/protocols/bin_raw.h @@ -1,6 +1,7 @@ #pragma once #include "base.h" +#include "public_api.h" #define SUBGHZ_PROTOCOL_BIN_RAW_NAME "BinRAW" @@ -80,10 +81,6 @@ void subghz_protocol_decoder_bin_raw_feed(void* context, bool level, uint32_t du */ uint8_t subghz_protocol_decoder_bin_raw_get_hash_data(void* context); -void subghz_protocol_decoder_bin_raw_data_input_rssi( - SubGhzProtocolDecoderBinRAW* instance, - float rssi); - /** * Serialize data SubGhzProtocolDecoderBinRAW. * @param context Pointer to a SubGhzProtocolDecoderBinRAW instance diff --git a/lib/subghz/protocols/keeloq.h b/lib/subghz/protocols/keeloq.h index 59cd9cf986f..4abd14413be 100644 --- a/lib/subghz/protocols/keeloq.h +++ b/lib/subghz/protocols/keeloq.h @@ -1,6 +1,7 @@ #pragma once #include "base.h" +#include "public_api.h" #define SUBGHZ_PROTOCOL_KEELOQ_NAME "KeeLoq" @@ -24,26 +25,6 @@ void* subghz_protocol_encoder_keeloq_alloc(SubGhzEnvironment* environment); */ void subghz_protocol_encoder_keeloq_free(void* context); -/** - * Key generation from simple data. - * @param context Pointer to a SubGhzProtocolEncoderKeeloq instance - * @param flipper_format Pointer to a FlipperFormat instance - * @param serial Serial number, 28 bit - * @param btn Button number, 4 bit - * @param cnt Container value, 16 bit - * @param manufacture_name Name of manufacturer's key - * @param preset Modulation, SubGhzRadioPreset - * @return true On success - */ -bool subghz_protocol_keeloq_create_data( - void* context, - FlipperFormat* flipper_format, - uint32_t serial, - uint8_t btn, - uint16_t cnt, - const char* manufacture_name, - SubGhzRadioPreset* preset); - /** * Deserialize and generating an upload to send. * @param context Pointer to a SubGhzProtocolEncoderKeeloq instance diff --git a/lib/subghz/protocols/public_api.h b/lib/subghz/protocols/public_api.h new file mode 100644 index 00000000000..174f175cdfa --- /dev/null +++ b/lib/subghz/protocols/public_api.h @@ -0,0 +1,63 @@ +#pragma once + +#include "../types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * 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 preset Modulation, SubGhzRadioPreset + * @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, + SubGhzRadioPreset* preset); + +/** + * Key generation from simple data. + * @param context Pointer to a SubGhzProtocolEncoderKeeloq instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param serial Serial number, 28 bit + * @param btn Button number, 4 bit + * @param cnt Container value, 16 bit + * @param manufacture_name Name of manufacturer's key + * @param preset Modulation, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_keeloq_create_data( + void* context, + FlipperFormat* flipper_format, + uint32_t serial, + uint8_t btn, + uint16_t cnt, + const char* manufacture_name, + SubGhzRadioPreset* preset); + +typedef struct SubGhzProtocolDecoderBinRAW SubGhzProtocolDecoderBinRAW; + +void subghz_protocol_decoder_bin_raw_data_input_rssi( + SubGhzProtocolDecoderBinRAW* instance, + float rssi); + +/** + * Validation of fixed parts SubGhzProtocolDecoderSecPlus_v1. + * @param fixed fixed parts + * @return true On success + */ +bool subghz_protocol_secplus_v1_check_fixed(uint32_t fixed); + +#ifdef __cplusplus +} +#endif diff --git a/lib/subghz/protocols/secplus_v1.h b/lib/subghz/protocols/secplus_v1.h index 3490f2ca540..e01f8bcdaa4 100644 --- a/lib/subghz/protocols/secplus_v1.h +++ b/lib/subghz/protocols/secplus_v1.h @@ -1,5 +1,7 @@ #pragma once + #include "base.h" +#include "public_api.h" #define SUBGHZ_PROTOCOL_SECPLUS_V1_NAME "Security+ 1.0" @@ -100,13 +102,6 @@ SubGhzProtocolStatus subghz_protocol_decoder_secplus_v1_serialize( SubGhzProtocolStatus 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.h b/lib/subghz/protocols/secplus_v2.h index 0eea732af15..9eb912a27e1 100644 --- a/lib/subghz/protocols/secplus_v2.h +++ b/lib/subghz/protocols/secplus_v2.h @@ -1,5 +1,7 @@ #pragma once + #include "base.h" +#include "public_api.h" #define SUBGHZ_PROTOCOL_SECPLUS_V2_NAME "Security+ 2.0" @@ -45,25 +47,6 @@ void subghz_protocol_encoder_secplus_v2_stop(void* context); */ 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 preset Modulation, SubGhzRadioPreset - * @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, - SubGhzRadioPreset* preset); - /** * Allocate SubGhzProtocolDecoderSecPlus_v2. * @param environment Pointer to a SubGhzEnvironment instance diff --git a/lib/subghz/subghz_protocol_registry.h b/lib/subghz/subghz_protocol_registry.h index 8e80071b518..2dfa9ce8b6c 100644 --- a/lib/subghz/subghz_protocol_registry.h +++ b/lib/subghz/subghz_protocol_registry.h @@ -10,29 +10,6 @@ extern const SubGhzProtocolRegistry subghz_protocol_registry; typedef struct SubGhzProtocolDecoderBinRAW SubGhzProtocolDecoderBinRAW; -bool subghz_protocol_secplus_v2_create_data( - void* context, - FlipperFormat* flipper_format, - uint32_t serial, - uint8_t btn, - uint32_t cnt, - SubGhzRadioPreset* preset); - -bool subghz_protocol_keeloq_create_data( - void* context, - FlipperFormat* flipper_format, - uint32_t serial, - uint8_t btn, - uint16_t cnt, - const char* manufacture_name, - SubGhzRadioPreset* preset); - -void subghz_protocol_decoder_bin_raw_data_input_rssi( - SubGhzProtocolDecoderBinRAW* instance, - float rssi); - -bool subghz_protocol_secplus_v1_check_fixed(uint32_t fixed); - #ifdef __cplusplus } #endif diff --git a/lib/toolbox/buffer_stream.c b/lib/toolbox/buffer_stream.c index 37b2514ef3d..e8cb334d51c 100644 --- a/lib/toolbox/buffer_stream.c +++ b/lib/toolbox/buffer_stream.c @@ -112,7 +112,7 @@ bool buffer_stream_send_from_isr(BufferStream* buffer_stream, const uint8_t* dat return result; } -Buffer* buffer_stream_receive(BufferStream* buffer_stream, TickType_t timeout) { +Buffer* buffer_stream_receive(BufferStream* buffer_stream, uint32_t timeout) { Buffer* buffer; size_t size = furi_stream_buffer_receive(buffer_stream->stream, &buffer, sizeof(Buffer*), timeout); diff --git a/lib/toolbox/buffer_stream.h b/lib/toolbox/buffer_stream.h index 9db54775322..5c3dc0a3403 100644 --- a/lib/toolbox/buffer_stream.h +++ b/lib/toolbox/buffer_stream.h @@ -69,7 +69,7 @@ bool buffer_stream_send_from_isr(BufferStream* buffer_stream, const uint8_t* dat * @param timeout * @return Buffer* */ -Buffer* buffer_stream_receive(BufferStream* buffer_stream, TickType_t timeout); +Buffer* buffer_stream_receive(BufferStream* buffer_stream, uint32_t timeout); /** * @brief Get stream overrun count diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index cb62b40c40b..3a2abc9b654 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,41.0,, +Version,+,43.2,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -417,7 +417,6 @@ Function,-,_perror_r,void,"_reent*, const char*" Function,-,_printf_r,int,"_reent*, const char*, ..." Function,-,_putc_r,int,"_reent*, int, FILE*" Function,-,_putc_unlocked_r,int,"_reent*, int, FILE*" -Function,-,_putchar,void,char Function,-,_putchar_r,int,"_reent*, int" Function,-,_putchar_unlocked_r,int,"_reent*, int" Function,-,_putenv_r,int,"_reent*, char*" @@ -751,8 +750,6 @@ Function,-,dprintf,int,"int, const char*, ..." Function,-,drand48,double, Function,-,drem,double,"double, double" Function,-,dremf,float,"float, float" -Function,-,eTaskConfirmSleepModeStatus,eSleepModeStatus, -Function,-,eTaskGetState,eTaskState,TaskHandle_t Function,+,elements_bold_rounded_frame,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t" Function,+,elements_bubble,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t" Function,+,elements_bubble_str,void,"Canvas*, uint8_t, uint8_t, const char*, Align, Align" @@ -1342,6 +1339,7 @@ Function,+,furi_hal_vibro_on,void,_Bool Function,-,furi_init,void, Function,+,furi_kernel_get_tick_frequency,uint32_t, Function,+,furi_kernel_is_irq_or_masked,_Bool, +Function,+,furi_kernel_is_running,_Bool, Function,+,furi_kernel_lock,int32_t, Function,+,furi_kernel_restore_lock,int32_t,int32_t Function,+,furi_kernel_unlock,int32_t, @@ -1452,7 +1450,6 @@ Function,+,furi_string_utf8_push,void,"FuriString*, FuriStringUnicodeValue" Function,+,furi_string_vprintf,int,"FuriString*, const char[], va_list" Function,+,furi_thread_alloc,FuriThread*, Function,+,furi_thread_alloc_ex,FuriThread*,"const char*, uint32_t, FuriThreadCallback, void*" -Function,+,furi_thread_catch,void, Function,-,furi_thread_disable_heap_trace,void,FuriThread* Function,+,furi_thread_enable_heap_trace,void,FuriThread* Function,+,furi_thread_enumerate,uint32_t,"FuriThreadId*, uint32_t" @@ -1493,8 +1490,11 @@ Function,+,furi_thread_suspend,void,FuriThreadId Function,+,furi_thread_yield,void, Function,+,furi_timer_alloc,FuriTimer*,"FuriTimerCallback, FuriTimerType, void*" Function,+,furi_timer_free,void,FuriTimer* +Function,+,furi_timer_get_expire_time,uint32_t,FuriTimer* Function,+,furi_timer_is_running,uint32_t,FuriTimer* Function,+,furi_timer_pending_callback,void,"FuriTimerPendigCallback, void*, uint32_t" +Function,+,furi_timer_restart,FuriStatus,FuriTimer* +Function,+,furi_timer_set_thread_priority,void,FuriTimerThreadPriority Function,+,furi_timer_start,FuriStatus,"FuriTimer*, uint32_t" Function,+,furi_timer_stop,FuriStatus,FuriTimer* Function,-,fwrite,size_t,"const void*, size_t, size_t, FILE*" @@ -1838,8 +1838,6 @@ Function,+,pb_read,_Bool,"pb_istream_t*, pb_byte_t*, size_t" Function,+,pb_release,void,"const pb_msgdesc_t*, void*" Function,+,pb_skip_field,_Bool,"pb_istream_t*, pb_wire_type_t" Function,+,pb_write,_Bool,"pb_ostream_t*, const pb_byte_t*, size_t" -Function,-,pcTaskGetName,char*,TaskHandle_t -Function,-,pcTimerGetName,const char*,TimerHandle_t Function,-,pclose,int,FILE* Function,-,perror,void,const char* Function,+,plugin_manager_alloc,PluginManager*,"const char*, uint32_t, const ElfApiInterface*" @@ -1914,12 +1912,6 @@ Function,-,putchar_unlocked,int,int Function,-,putenv,int,char* Function,-,puts,int,const char* Function,-,putw,int,"int, FILE*" -Function,-,pvPortCalloc,void*,"size_t, size_t" -Function,-,pvPortMalloc,void*,size_t -Function,-,pvTaskGetThreadLocalStoragePointer,void*,"TaskHandle_t, BaseType_t" -Function,-,pvTaskIncrementMutexHeldCount,TaskHandle_t, -Function,-,pvTimerGetTimerID,void*,const TimerHandle_t -Function,-,pxPortInitialiseStack,StackType_t*,"StackType_t*, TaskFunction_t, void*" Function,-,qsort,void,"void*, size_t, size_t, __compar_fn_t" Function,-,qsort_r,void,"void*, size_t, size_t, int (*)(const void*, const void*, void*), void*" Function,-,quick_exit,void,int @@ -1949,7 +1941,7 @@ Function,-,round,double,double Function,+,roundf,float,float Function,-,roundl,long double,long double Function,+,rpc_session_close,void,RpcSession* -Function,+,rpc_session_feed,size_t,"RpcSession*, uint8_t*, size_t, TickType_t" +Function,+,rpc_session_feed,size_t,"RpcSession*, uint8_t*, size_t, uint32_t" Function,+,rpc_session_get_available_size,size_t,RpcSession* Function,+,rpc_session_get_owner,RpcOwner,RpcSession* Function,+,rpc_session_open,RpcSession*,"Rpc*, RpcOwner" @@ -2281,67 +2273,10 @@ Function,-,uECC_sign_deterministic,int,"const uint8_t*, const uint8_t*, unsigned Function,-,uECC_valid_public_key,int,"const uint8_t*, uECC_Curve" Function,-,uECC_verify,int,"const uint8_t*, const uint8_t*, unsigned, const uint8_t*, uECC_Curve" Function,+,uint8_to_hex_chars,void,"const uint8_t*, uint8_t*, int" -Function,-,ulTaskGenericNotifyTake,uint32_t,"UBaseType_t, BaseType_t, TickType_t" -Function,-,ulTaskGenericNotifyValueClear,uint32_t,"TaskHandle_t, UBaseType_t, uint32_t" -Function,-,ulTaskGetIdleRunTimeCounter,uint32_t, -Function,-,ulTaskGetIdleRunTimePercent,uint32_t, Function,-,ungetc,int,"int, FILE*" Function,-,unsetenv,int,const char* Function,-,usbd_poll,void,usbd_device* Function,-,utoa,char*,"unsigned, char*, int" -Function,-,uxListRemove,UBaseType_t,ListItem_t* -Function,-,uxTaskGetNumberOfTasks,UBaseType_t, -Function,-,uxTaskGetStackHighWaterMark,UBaseType_t,TaskHandle_t -Function,-,uxTaskGetStackHighWaterMark2,uint16_t,TaskHandle_t -Function,-,uxTaskGetSystemState,UBaseType_t,"TaskStatus_t*, const UBaseType_t, uint32_t*" -Function,-,uxTaskGetTaskNumber,UBaseType_t,TaskHandle_t -Function,-,uxTaskPriorityGet,UBaseType_t,const TaskHandle_t -Function,-,uxTaskPriorityGetFromISR,UBaseType_t,const TaskHandle_t -Function,-,uxTaskResetEventItemValue,TickType_t, -Function,-,uxTimerGetReloadMode,UBaseType_t,TimerHandle_t -Function,-,uxTimerGetTimerNumber,UBaseType_t,TimerHandle_t -Function,-,vApplicationGetIdleTaskMemory,void,"StaticTask_t**, StackType_t**, uint32_t*" -Function,-,vApplicationGetTimerTaskMemory,void,"StaticTask_t**, StackType_t**, uint32_t*" -Function,-,vListInitialise,void,List_t* -Function,-,vListInitialiseItem,void,ListItem_t* -Function,-,vListInsert,void,"List_t*, ListItem_t*" -Function,-,vListInsertEnd,void,"List_t*, ListItem_t*" -Function,-,vPortDefineHeapRegions,void,const HeapRegion_t* -Function,-,vPortEndScheduler,void, -Function,+,vPortEnterCritical,void, -Function,+,vPortExitCritical,void, -Function,-,vPortFree,void,void* -Function,-,vPortGetHeapStats,void,HeapStats_t* -Function,-,vPortInitialiseBlocks,void, -Function,-,vPortSuppressTicksAndSleep,void,TickType_t -Function,-,vTaskAllocateMPURegions,void,"TaskHandle_t, const MemoryRegion_t*" -Function,-,vTaskDelay,void,const TickType_t -Function,-,vTaskDelete,void,TaskHandle_t -Function,-,vTaskEndScheduler,void, -Function,-,vTaskGenericNotifyGiveFromISR,void,"TaskHandle_t, UBaseType_t, BaseType_t*" -Function,-,vTaskGetInfo,void,"TaskHandle_t, TaskStatus_t*, BaseType_t, eTaskState" -Function,-,vTaskGetRunTimeStats,void,char* -Function,-,vTaskInternalSetTimeOutState,void,TimeOut_t* -Function,-,vTaskList,void,char* -Function,-,vTaskMissedYield,void, -Function,-,vTaskPlaceOnEventList,void,"List_t*, const TickType_t" -Function,-,vTaskPlaceOnEventListRestricted,void,"List_t*, TickType_t, const BaseType_t" -Function,-,vTaskPlaceOnUnorderedEventList,void,"List_t*, const TickType_t, const TickType_t" -Function,-,vTaskPriorityDisinheritAfterTimeout,void,"const TaskHandle_t, UBaseType_t" -Function,+,vTaskPrioritySet,void,"TaskHandle_t, UBaseType_t" -Function,-,vTaskRemoveFromUnorderedEventList,void,"ListItem_t*, const TickType_t" -Function,-,vTaskResume,void,TaskHandle_t -Function,-,vTaskSetTaskNumber,void,"TaskHandle_t, const UBaseType_t" -Function,-,vTaskSetThreadLocalStoragePointer,void,"TaskHandle_t, BaseType_t, void*" -Function,-,vTaskSetTimeOutState,void,TimeOut_t* -Function,-,vTaskStartScheduler,void, -Function,-,vTaskStepTick,void,TickType_t -Function,-,vTaskSuspend,void,TaskHandle_t -Function,-,vTaskSuspendAll,void, -Function,-,vTaskSwitchContext,void, -Function,-,vTimerSetReloadMode,void,"TimerHandle_t, const BaseType_t" -Function,-,vTimerSetTimerID,void,"TimerHandle_t, void*" -Function,-,vTimerSetTimerNumber,void,"TimerHandle_t, UBaseType_t" Function,+,validator_is_file_alloc_init,ValidatorIsFile*,"const char*, const char*, const char*" Function,+,validator_is_file_callback,_Bool,"const char*, FuriString*, void*" Function,+,validator_is_file_free,void,ValidatorIsFile* @@ -2456,43 +2391,6 @@ Function,+,widget_alloc,Widget*, Function,+,widget_free,void,Widget* Function,+,widget_get_view,View*,Widget* Function,+,widget_reset,void,Widget* -Function,-,xPortGetFreeHeapSize,size_t, -Function,-,xPortGetMinimumEverFreeHeapSize,size_t, -Function,-,xPortStartScheduler,BaseType_t, -Function,-,xTaskAbortDelay,BaseType_t,TaskHandle_t -Function,-,xTaskCallApplicationTaskHook,BaseType_t,"TaskHandle_t, void*" -Function,-,xTaskCatchUpTicks,BaseType_t,TickType_t -Function,-,xTaskCheckForTimeOut,BaseType_t,"TimeOut_t*, TickType_t*" -Function,-,xTaskCreate,BaseType_t,"TaskFunction_t, const char*, const uint16_t, void*, UBaseType_t, TaskHandle_t*" -Function,-,xTaskCreateStatic,TaskHandle_t,"TaskFunction_t, const char*, const uint32_t, void*, UBaseType_t, StackType_t*, StaticTask_t*" -Function,-,xTaskDelayUntil,BaseType_t,"TickType_t*, const TickType_t" -Function,-,xTaskGenericNotify,BaseType_t,"TaskHandle_t, UBaseType_t, uint32_t, eNotifyAction, uint32_t*" -Function,-,xTaskGenericNotifyFromISR,BaseType_t,"TaskHandle_t, UBaseType_t, uint32_t, eNotifyAction, uint32_t*, BaseType_t*" -Function,-,xTaskGenericNotifyStateClear,BaseType_t,"TaskHandle_t, UBaseType_t" -Function,-,xTaskGenericNotifyWait,BaseType_t,"UBaseType_t, uint32_t, uint32_t, uint32_t*, TickType_t" -Function,-,xTaskGetCurrentTaskHandle,TaskHandle_t, -Function,+,xTaskGetHandle,TaskHandle_t,const char* -Function,-,xTaskGetIdleTaskHandle,TaskHandle_t, -Function,+,xTaskGetSchedulerState,BaseType_t, -Function,+,xTaskGetTickCount,TickType_t, -Function,-,xTaskGetTickCountFromISR,TickType_t, -Function,-,xTaskIncrementTick,BaseType_t, -Function,-,xTaskPriorityDisinherit,BaseType_t,const TaskHandle_t -Function,-,xTaskPriorityInherit,BaseType_t,const TaskHandle_t -Function,-,xTaskRemoveFromEventList,BaseType_t,const List_t* -Function,-,xTaskResumeAll,BaseType_t, -Function,-,xTaskResumeFromISR,BaseType_t,TaskHandle_t -Function,-,xTimerCreate,TimerHandle_t,"const char*, const TickType_t, const BaseType_t, void*, TimerCallbackFunction_t" -Function,-,xTimerCreateStatic,TimerHandle_t,"const char*, const TickType_t, const BaseType_t, void*, TimerCallbackFunction_t, StaticTimer_t*" -Function,-,xTimerCreateTimerTask,BaseType_t, -Function,-,xTimerGenericCommand,BaseType_t,"TimerHandle_t, const BaseType_t, const TickType_t, BaseType_t*, const TickType_t" -Function,-,xTimerGetExpiryTime,TickType_t,TimerHandle_t -Function,-,xTimerGetPeriod,TickType_t,TimerHandle_t -Function,-,xTimerGetReloadMode,BaseType_t,TimerHandle_t -Function,-,xTimerGetTimerDaemonTaskHandle,TaskHandle_t, -Function,-,xTimerIsTimerActive,BaseType_t,TimerHandle_t -Function,-,xTimerPendFunctionCall,BaseType_t,"PendedFunction_t, void*, uint32_t, TickType_t" -Function,-,xTimerPendFunctionCallFromISR,BaseType_t,"PendedFunction_t, void*, uint32_t, BaseType_t*" Function,-,y0,double,double Function,-,y0f,float,float Function,-,y1,double,double diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 9d9c1a01b37..0f81f119114 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,41.0,, +Version,+,43.2,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -179,6 +179,7 @@ Header,+,lib/subghz/blocks/math.h,, Header,+,lib/subghz/devices/cc1101_configs.h,, Header,+,lib/subghz/devices/cc1101_int/cc1101_int_interconnect.h,, Header,+,lib/subghz/environment.h,, +Header,+,lib/subghz/protocols/public_api.h,, Header,+,lib/subghz/protocols/raw.h,, Header,+,lib/subghz/receiver.h,, Header,+,lib/subghz/registry.h,, @@ -485,7 +486,6 @@ Function,-,_perror_r,void,"_reent*, const char*" Function,-,_printf_r,int,"_reent*, const char*, ..." Function,-,_putc_r,int,"_reent*, int, FILE*" Function,-,_putc_unlocked_r,int,"_reent*, int, FILE*" -Function,-,_putchar,void,char Function,-,_putchar_r,int,"_reent*, int" Function,-,_putchar_unlocked_r,int,"_reent*, int" Function,-,_putenv_r,int,"_reent*, char*" @@ -839,8 +839,6 @@ Function,-,dprintf,int,"int, const char*, ..." Function,-,drand48,double, Function,-,drem,double,"double, double" Function,-,dremf,float,"float, float" -Function,-,eTaskConfirmSleepModeStatus,eSleepModeStatus, -Function,-,eTaskGetState,eTaskState,TaskHandle_t Function,+,elements_bold_rounded_frame,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t" Function,+,elements_bubble,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t" Function,+,elements_bubble_str,void,"Canvas*, uint8_t, uint8_t, const char*, Align, Align" @@ -1536,6 +1534,7 @@ Function,+,furi_hal_vibro_on,void,_Bool Function,-,furi_init,void, Function,+,furi_kernel_get_tick_frequency,uint32_t, Function,+,furi_kernel_is_irq_or_masked,_Bool, +Function,+,furi_kernel_is_running,_Bool, Function,+,furi_kernel_lock,int32_t, Function,+,furi_kernel_restore_lock,int32_t,int32_t Function,+,furi_kernel_unlock,int32_t, @@ -1646,7 +1645,6 @@ Function,+,furi_string_utf8_push,void,"FuriString*, FuriStringUnicodeValue" Function,+,furi_string_vprintf,int,"FuriString*, const char[], va_list" Function,+,furi_thread_alloc,FuriThread*, Function,+,furi_thread_alloc_ex,FuriThread*,"const char*, uint32_t, FuriThreadCallback, void*" -Function,+,furi_thread_catch,void, Function,-,furi_thread_disable_heap_trace,void,FuriThread* Function,+,furi_thread_enable_heap_trace,void,FuriThread* Function,+,furi_thread_enumerate,uint32_t,"FuriThreadId*, uint32_t" @@ -1687,8 +1685,11 @@ Function,+,furi_thread_suspend,void,FuriThreadId Function,+,furi_thread_yield,void, Function,+,furi_timer_alloc,FuriTimer*,"FuriTimerCallback, FuriTimerType, void*" Function,+,furi_timer_free,void,FuriTimer* +Function,+,furi_timer_get_expire_time,uint32_t,FuriTimer* Function,+,furi_timer_is_running,uint32_t,FuriTimer* Function,+,furi_timer_pending_callback,void,"FuriTimerPendigCallback, void*, uint32_t" +Function,+,furi_timer_restart,FuriStatus,FuriTimer* +Function,+,furi_timer_set_thread_priority,void,FuriTimerThreadPriority Function,+,furi_timer_start,FuriStatus,"FuriTimer*, uint32_t" Function,+,furi_timer_stop,FuriStatus,FuriTimer* Function,-,fwrite,size_t,"const void*, size_t, size_t, FILE*" @@ -2380,8 +2381,6 @@ Function,+,pb_read,_Bool,"pb_istream_t*, pb_byte_t*, size_t" Function,+,pb_release,void,"const pb_msgdesc_t*, void*" Function,+,pb_skip_field,_Bool,"pb_istream_t*, pb_wire_type_t" Function,+,pb_write,_Bool,"pb_ostream_t*, const pb_byte_t*, size_t" -Function,-,pcTaskGetName,char*,TaskHandle_t -Function,-,pcTimerGetName,const char*,TimerHandle_t Function,-,pclose,int,FILE* Function,-,perror,void,const char* Function,+,plugin_manager_alloc,PluginManager*,"const char*, uint32_t, const ElfApiInterface*" @@ -2456,12 +2455,6 @@ Function,-,putchar_unlocked,int,int Function,-,putenv,int,char* Function,-,puts,int,const char* Function,-,putw,int,"int, FILE*" -Function,-,pvPortCalloc,void*,"size_t, size_t" -Function,-,pvPortMalloc,void*,size_t -Function,-,pvTaskGetThreadLocalStoragePointer,void*,"TaskHandle_t, BaseType_t" -Function,-,pvTaskIncrementMutexHeldCount,TaskHandle_t, -Function,-,pvTimerGetTimerID,void*,const TimerHandle_t -Function,-,pxPortInitialiseStack,StackType_t*,"StackType_t*, TaskFunction_t, void*" Function,-,qsort,void,"void*, size_t, size_t, __compar_fn_t" Function,-,qsort_r,void,"void*, size_t, size_t, int (*)(const void*, const void*, void*), void*" Function,-,quick_exit,void,int @@ -2491,7 +2484,7 @@ Function,-,round,double,double Function,+,roundf,float,float Function,-,roundl,long double,long double Function,+,rpc_session_close,void,RpcSession* -Function,+,rpc_session_feed,size_t,"RpcSession*, uint8_t*, size_t, TickType_t" +Function,+,rpc_session_feed,size_t,"RpcSession*, uint8_t*, size_t, uint32_t" Function,+,rpc_session_get_available_size,size_t,RpcSession* Function,+,rpc_session_get_owner,RpcOwner,RpcSession* Function,+,rpc_session_open,RpcSession*,"Rpc*, RpcOwner" @@ -3011,67 +3004,10 @@ Function,-,uECC_sign_deterministic,int,"const uint8_t*, const uint8_t*, unsigned Function,-,uECC_valid_public_key,int,"const uint8_t*, uECC_Curve" Function,-,uECC_verify,int,"const uint8_t*, const uint8_t*, unsigned, const uint8_t*, uECC_Curve" Function,+,uint8_to_hex_chars,void,"const uint8_t*, uint8_t*, int" -Function,-,ulTaskGenericNotifyTake,uint32_t,"UBaseType_t, BaseType_t, TickType_t" -Function,-,ulTaskGenericNotifyValueClear,uint32_t,"TaskHandle_t, UBaseType_t, uint32_t" -Function,-,ulTaskGetIdleRunTimeCounter,uint32_t, -Function,-,ulTaskGetIdleRunTimePercent,uint32_t, Function,-,ungetc,int,"int, FILE*" Function,-,unsetenv,int,const char* Function,-,usbd_poll,void,usbd_device* Function,-,utoa,char*,"unsigned, char*, int" -Function,-,uxListRemove,UBaseType_t,ListItem_t* -Function,-,uxTaskGetNumberOfTasks,UBaseType_t, -Function,-,uxTaskGetStackHighWaterMark,UBaseType_t,TaskHandle_t -Function,-,uxTaskGetStackHighWaterMark2,uint16_t,TaskHandle_t -Function,-,uxTaskGetSystemState,UBaseType_t,"TaskStatus_t*, const UBaseType_t, uint32_t*" -Function,-,uxTaskGetTaskNumber,UBaseType_t,TaskHandle_t -Function,-,uxTaskPriorityGet,UBaseType_t,const TaskHandle_t -Function,-,uxTaskPriorityGetFromISR,UBaseType_t,const TaskHandle_t -Function,-,uxTaskResetEventItemValue,TickType_t, -Function,-,uxTimerGetReloadMode,UBaseType_t,TimerHandle_t -Function,-,uxTimerGetTimerNumber,UBaseType_t,TimerHandle_t -Function,-,vApplicationGetIdleTaskMemory,void,"StaticTask_t**, StackType_t**, uint32_t*" -Function,-,vApplicationGetTimerTaskMemory,void,"StaticTask_t**, StackType_t**, uint32_t*" -Function,-,vListInitialise,void,List_t* -Function,-,vListInitialiseItem,void,ListItem_t* -Function,-,vListInsert,void,"List_t*, ListItem_t*" -Function,-,vListInsertEnd,void,"List_t*, ListItem_t*" -Function,-,vPortDefineHeapRegions,void,const HeapRegion_t* -Function,-,vPortEndScheduler,void, -Function,+,vPortEnterCritical,void, -Function,+,vPortExitCritical,void, -Function,-,vPortFree,void,void* -Function,-,vPortGetHeapStats,void,HeapStats_t* -Function,-,vPortInitialiseBlocks,void, -Function,-,vPortSuppressTicksAndSleep,void,TickType_t -Function,-,vTaskAllocateMPURegions,void,"TaskHandle_t, const MemoryRegion_t*" -Function,-,vTaskDelay,void,const TickType_t -Function,-,vTaskDelete,void,TaskHandle_t -Function,-,vTaskEndScheduler,void, -Function,-,vTaskGenericNotifyGiveFromISR,void,"TaskHandle_t, UBaseType_t, BaseType_t*" -Function,-,vTaskGetInfo,void,"TaskHandle_t, TaskStatus_t*, BaseType_t, eTaskState" -Function,-,vTaskGetRunTimeStats,void,char* -Function,-,vTaskInternalSetTimeOutState,void,TimeOut_t* -Function,-,vTaskList,void,char* -Function,-,vTaskMissedYield,void, -Function,-,vTaskPlaceOnEventList,void,"List_t*, const TickType_t" -Function,-,vTaskPlaceOnEventListRestricted,void,"List_t*, TickType_t, const BaseType_t" -Function,-,vTaskPlaceOnUnorderedEventList,void,"List_t*, const TickType_t, const TickType_t" -Function,-,vTaskPriorityDisinheritAfterTimeout,void,"const TaskHandle_t, UBaseType_t" -Function,+,vTaskPrioritySet,void,"TaskHandle_t, UBaseType_t" -Function,-,vTaskRemoveFromUnorderedEventList,void,"ListItem_t*, const TickType_t" -Function,-,vTaskResume,void,TaskHandle_t -Function,-,vTaskSetTaskNumber,void,"TaskHandle_t, const UBaseType_t" -Function,-,vTaskSetThreadLocalStoragePointer,void,"TaskHandle_t, BaseType_t, void*" -Function,-,vTaskSetTimeOutState,void,TimeOut_t* -Function,-,vTaskStartScheduler,void, -Function,-,vTaskStepTick,void,TickType_t -Function,-,vTaskSuspend,void,TaskHandle_t -Function,-,vTaskSuspendAll,void, -Function,-,vTaskSwitchContext,void, -Function,-,vTimerSetReloadMode,void,"TimerHandle_t, const BaseType_t" -Function,-,vTimerSetTimerID,void,"TimerHandle_t, void*" -Function,-,vTimerSetTimerNumber,void,"TimerHandle_t, UBaseType_t" Function,+,validator_is_file_alloc_init,ValidatorIsFile*,"const char*, const char*, const char*" Function,+,validator_is_file_callback,_Bool,"const char*, FuriString*, void*" Function,+,validator_is_file_free,void,ValidatorIsFile* @@ -3186,43 +3122,6 @@ Function,+,widget_alloc,Widget*, Function,+,widget_free,void,Widget* Function,+,widget_get_view,View*,Widget* Function,+,widget_reset,void,Widget* -Function,-,xPortGetFreeHeapSize,size_t, -Function,-,xPortGetMinimumEverFreeHeapSize,size_t, -Function,-,xPortStartScheduler,BaseType_t, -Function,-,xTaskAbortDelay,BaseType_t,TaskHandle_t -Function,-,xTaskCallApplicationTaskHook,BaseType_t,"TaskHandle_t, void*" -Function,-,xTaskCatchUpTicks,BaseType_t,TickType_t -Function,-,xTaskCheckForTimeOut,BaseType_t,"TimeOut_t*, TickType_t*" -Function,-,xTaskCreate,BaseType_t,"TaskFunction_t, const char*, const uint16_t, void*, UBaseType_t, TaskHandle_t*" -Function,-,xTaskCreateStatic,TaskHandle_t,"TaskFunction_t, const char*, const uint32_t, void*, UBaseType_t, StackType_t*, StaticTask_t*" -Function,-,xTaskDelayUntil,BaseType_t,"TickType_t*, const TickType_t" -Function,-,xTaskGenericNotify,BaseType_t,"TaskHandle_t, UBaseType_t, uint32_t, eNotifyAction, uint32_t*" -Function,-,xTaskGenericNotifyFromISR,BaseType_t,"TaskHandle_t, UBaseType_t, uint32_t, eNotifyAction, uint32_t*, BaseType_t*" -Function,-,xTaskGenericNotifyStateClear,BaseType_t,"TaskHandle_t, UBaseType_t" -Function,-,xTaskGenericNotifyWait,BaseType_t,"UBaseType_t, uint32_t, uint32_t, uint32_t*, TickType_t" -Function,-,xTaskGetCurrentTaskHandle,TaskHandle_t, -Function,+,xTaskGetHandle,TaskHandle_t,const char* -Function,-,xTaskGetIdleTaskHandle,TaskHandle_t, -Function,+,xTaskGetSchedulerState,BaseType_t, -Function,+,xTaskGetTickCount,TickType_t, -Function,-,xTaskGetTickCountFromISR,TickType_t, -Function,-,xTaskIncrementTick,BaseType_t, -Function,-,xTaskPriorityDisinherit,BaseType_t,const TaskHandle_t -Function,-,xTaskPriorityInherit,BaseType_t,const TaskHandle_t -Function,-,xTaskRemoveFromEventList,BaseType_t,const List_t* -Function,-,xTaskResumeAll,BaseType_t, -Function,-,xTaskResumeFromISR,BaseType_t,TaskHandle_t -Function,-,xTimerCreate,TimerHandle_t,"const char*, const TickType_t, const BaseType_t, void*, TimerCallbackFunction_t" -Function,-,xTimerCreateStatic,TimerHandle_t,"const char*, const TickType_t, const BaseType_t, void*, TimerCallbackFunction_t, StaticTimer_t*" -Function,-,xTimerCreateTimerTask,BaseType_t, -Function,-,xTimerGenericCommand,BaseType_t,"TimerHandle_t, const BaseType_t, const TickType_t, BaseType_t*, const TickType_t" -Function,-,xTimerGetExpiryTime,TickType_t,TimerHandle_t -Function,-,xTimerGetPeriod,TickType_t,TimerHandle_t -Function,-,xTimerGetReloadMode,BaseType_t,TimerHandle_t -Function,-,xTimerGetTimerDaemonTaskHandle,TaskHandle_t, -Function,-,xTimerIsTimerActive,BaseType_t,TimerHandle_t -Function,-,xTimerPendFunctionCall,BaseType_t,"PendedFunction_t, void*, uint32_t, TickType_t" -Function,-,xTimerPendFunctionCallFromISR,BaseType_t,"PendedFunction_t, void*, uint32_t, BaseType_t*" Function,-,y0,double,double Function,-,y0f,float,float Function,-,y1,double,double diff --git a/targets/f7/ble_glue/gap.c b/targets/f7/ble_glue/gap.c index 360c1f6b684..f0533567eae 100644 --- a/targets/f7/ble_glue/gap.c +++ b/targets/f7/ble_glue/gap.c @@ -532,8 +532,6 @@ void gap_thread_stop() { // Free resources furi_mutex_free(gap->state_mutex); furi_message_queue_free(gap->command_queue); - furi_timer_stop(gap->advertise_timer); - while(xTimerIsTimerActive(gap->advertise_timer) == pdTRUE) furi_delay_tick(1); furi_timer_free(gap->advertise_timer); free(gap); gap = NULL; diff --git a/targets/f7/furi_hal/furi_hal_flash.c b/targets/f7/furi_hal/furi_hal_flash.c index bc65b29eb61..284d48bf53d 100644 --- a/targets/f7/furi_hal/furi_hal_flash.c +++ b/targets/f7/furi_hal/furi_hal_flash.c @@ -11,6 +11,9 @@ #include +#include +#include + #define TAG "FuriHalFlash" #define FURI_HAL_CRITICAL_MSG "Critical flash operation fail" diff --git a/targets/f7/furi_hal/furi_hal_os.c b/targets/f7/furi_hal/furi_hal_os.c index 046cf79dc1d..ea835b95fbe 100644 --- a/targets/f7/furi_hal/furi_hal_os.c +++ b/targets/f7/furi_hal/furi_hal_os.c @@ -10,6 +10,9 @@ #include +#include +#include + #define TAG "FuriHalOs" #define FURI_HAL_IDLE_TIMER_CLK_HZ 32768 diff --git a/targets/f7/furi_hal/furi_hal_power.c b/targets/f7/furi_hal/furi_hal_power.c index 0eb93e664bd..119dee81f92 100644 --- a/targets/f7/furi_hal/furi_hal_power.c +++ b/targets/f7/furi_hal/furi_hal_power.c @@ -459,10 +459,10 @@ void furi_hal_power_disable_external_3_3v() { } void furi_hal_power_suppress_charge_enter() { - vTaskSuspendAll(); + FURI_CRITICAL_ENTER(); bool disable_charging = furi_hal_power.suppress_charge == 0; furi_hal_power.suppress_charge++; - xTaskResumeAll(); + FURI_CRITICAL_EXIT(); if(disable_charging) { furi_hal_i2c_acquire(&furi_hal_i2c_handle_power); @@ -472,10 +472,10 @@ void furi_hal_power_suppress_charge_enter() { } void furi_hal_power_suppress_charge_exit() { - vTaskSuspendAll(); + FURI_CRITICAL_ENTER(); furi_hal_power.suppress_charge--; bool enable_charging = furi_hal_power.suppress_charge == 0; - xTaskResumeAll(); + FURI_CRITICAL_EXIT(); if(enable_charging) { furi_hal_i2c_acquire(&furi_hal_i2c_handle_power); diff --git a/targets/f7/furi_hal/furi_hal_spi.c b/targets/f7/furi_hal/furi_hal_spi.c index 5c33760ea1c..98ca71af357 100644 --- a/targets/f7/furi_hal/furi_hal_spi.c +++ b/targets/f7/furi_hal/furi_hal_spi.c @@ -200,7 +200,7 @@ bool furi_hal_spi_bus_trx_dma( furi_assert(size > 0); // If scheduler is not running, use blocking mode - if(xTaskGetSchedulerState() != taskSCHEDULER_RUNNING) { + if(furi_kernel_is_running()) { return furi_hal_spi_bus_trx(handle, tx_buffer, rx_buffer, size, timeout_ms); } diff --git a/targets/f7/inc/FreeRTOSConfig.h b/targets/f7/inc/FreeRTOSConfig.h index 024d43a6de8..3bc57f8f3be 100644 --- a/targets/f7/inc/FreeRTOSConfig.h +++ b/targets/f7/inc/FreeRTOSConfig.h @@ -3,13 +3,14 @@ #if defined(__ICCARM__) || defined(__CC_ARM) || defined(__GNUC__) #include #pragma GCC diagnostic ignored "-Wredundant-decls" -extern uint32_t SystemCoreClock; #endif #ifndef CMSIS_device_header #define CMSIS_device_header "stm32wbxx.h" #endif /* CMSIS_device_header */ +#include CMSIS_device_header + #define configENABLE_FPU 1 #define configENABLE_MPU 0 diff --git a/targets/f7/inc/furi_config.h b/targets/f7/inc/furi_config.h new file mode 100644 index 00000000000..c935611ab7b --- /dev/null +++ b/targets/f7/inc/furi_config.h @@ -0,0 +1,3 @@ +#pragma once + +#define FURI_CONFIG_THREAD_MAX_PRIORITIES (32) \ No newline at end of file diff --git a/targets/f7/src/main.c b/targets/f7/src/main.c index 2c353f52b20..ca705fe5e42 100644 --- a/targets/f7/src/main.c +++ b/targets/f7/src/main.c @@ -2,7 +2,6 @@ #include #include #include -#include #include #define TAG "Main" From 47cc05dab4beee6c1a7620c45b15abfb48b9916e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Thu, 2 Nov 2023 00:23:02 +0900 Subject: [PATCH 798/824] [FL-3655] Dolphin: Extreme butthurt loop fix (#3184) * Furi: change timer restart function signature, explicitly require timer time. Dolphin: fix incorrect timer usage caused by refactoring. * Format Sources * Furi: update timer documentation --- applications/services/dolphin/dolphin.c | 8 ++++---- furi/core/timer.c | 13 +++++++++---- furi/core/timer.h | 5 +++-- targets/f18/api_symbols.csv | 4 ++-- targets/f7/api_symbols.csv | 4 ++-- 5 files changed, 20 insertions(+), 14 deletions(-) diff --git a/applications/services/dolphin/dolphin.c b/applications/services/dolphin/dolphin.c index 5b526ed3a69..bef7c4a2853 100644 --- a/applications/services/dolphin/dolphin.c +++ b/applications/services/dolphin/dolphin.c @@ -155,9 +155,9 @@ int32_t dolphin_srv(void* p) { furi_record_create(RECORD_DOLPHIN, dolphin); dolphin_state_load(dolphin->state); - furi_timer_stop(dolphin->butthurt_timer); + furi_timer_restart(dolphin->butthurt_timer, HOURS_IN_TICKS(2 * 24)); dolphin_update_clear_limits_timer_period(dolphin); - furi_timer_stop(dolphin->clear_limits_timer); + furi_timer_restart(dolphin->clear_limits_timer, HOURS_IN_TICKS(24)); DolphinEvent event; while(1) { @@ -167,8 +167,8 @@ int32_t dolphin_srv(void* p) { dolphin_state_on_deed(dolphin->state, event.deed); DolphinPubsubEvent event = DolphinPubsubEventUpdate; furi_pubsub_publish(dolphin->pubsub, &event); - furi_timer_restart(dolphin->butthurt_timer); - furi_timer_restart(dolphin->flush_timer); + furi_timer_restart(dolphin->butthurt_timer, HOURS_IN_TICKS(2 * 24)); + furi_timer_restart(dolphin->flush_timer, 30 * 1000); } else if(event.type == DolphinEventTypeStats) { event.stats->icounter = dolphin->state->data.icounter; event.stats->butthurt = dolphin->state->data.butthurt; diff --git a/furi/core/timer.c b/furi/core/timer.c index 0a89b892017..17347e5c7dd 100644 --- a/furi/core/timer.c +++ b/furi/core/timer.c @@ -51,7 +51,7 @@ FuriTimer* furi_timer_alloc(FuriTimerCallback func, FuriTimerType type, void* co callb = (TimerCallback_t*)((uint32_t)callb | 1U); // TimerCallback function is always provided as a callback and is used to call application // specified function with its context both stored in structure callb. - hTimer = xTimerCreate(NULL, 1, reload, callb, TimerCallback); + hTimer = xTimerCreate(NULL, portMAX_DELAY, reload, callb, TimerCallback); furi_check(hTimer); /* Return timer ID */ @@ -83,6 +83,7 @@ void furi_timer_free(FuriTimer* instance) { FuriStatus furi_timer_start(FuriTimer* instance, uint32_t ticks) { furi_assert(!furi_kernel_is_irq_or_masked()); furi_assert(instance); + furi_assert(ticks < portMAX_DELAY); TimerHandle_t hTimer = (TimerHandle_t)instance; FuriStatus stat; @@ -97,14 +98,16 @@ FuriStatus furi_timer_start(FuriTimer* instance, uint32_t ticks) { return (stat); } -FuriStatus furi_timer_restart(FuriTimer* instance) { +FuriStatus furi_timer_restart(FuriTimer* instance, uint32_t ticks) { furi_assert(!furi_kernel_is_irq_or_masked()); furi_assert(instance); + furi_assert(ticks < portMAX_DELAY); TimerHandle_t hTimer = (TimerHandle_t)instance; FuriStatus stat; - if(xTimerReset(hTimer, portMAX_DELAY) == pdPASS) { + if(xTimerChangePeriod(hTimer, ticks, portMAX_DELAY) == pdPASS && + xTimerReset(hTimer, portMAX_DELAY) == pdPASS) { stat = FuriStatusOk; } else { stat = FuriStatusErrorResource; @@ -163,7 +166,9 @@ void furi_timer_pending_callback(FuriTimerPendigCallback callback, void* context void furi_timer_set_thread_priority(FuriTimerThreadPriority priority) { furi_assert(!furi_kernel_is_irq_or_masked()); - TaskHandle_t task_handle = xTaskGetHandle(configTIMER_SERVICE_TASK_NAME); + + TaskHandle_t task_handle = xTimerGetTimerDaemonTaskHandle(); + furi_check(task_handle); // Don't call this method before timer task start if(priority == FuriTimerThreadPriorityNormal) { vTaskPrioritySet(task_handle, configTIMER_TASK_PRIORITY); diff --git a/furi/core/timer.h b/furi/core/timer.h index 47b44c71a64..d27ef5025e8 100644 --- a/furi/core/timer.h +++ b/furi/core/timer.h @@ -34,7 +34,7 @@ void furi_timer_free(FuriTimer* instance); /** Start timer * * @param instance The pointer to FuriTimer instance - * @param[in] ticks The ticks + * @param[in] ticks The interval in ticks * * @return The furi status. */ @@ -43,10 +43,11 @@ FuriStatus furi_timer_start(FuriTimer* instance, uint32_t ticks); /** Restart timer with previous timeout value * * @param instance The pointer to FuriTimer instance + * @param[in] ticks The interval in ticks * * @return The furi status. */ -FuriStatus furi_timer_restart(FuriTimer* instance); +FuriStatus furi_timer_restart(FuriTimer* instance, uint32_t ticks); /** Stop timer * diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 3a2abc9b654..5f8e836c007 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,43.2,, +Version,+,44.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -1493,7 +1493,7 @@ Function,+,furi_timer_free,void,FuriTimer* Function,+,furi_timer_get_expire_time,uint32_t,FuriTimer* Function,+,furi_timer_is_running,uint32_t,FuriTimer* Function,+,furi_timer_pending_callback,void,"FuriTimerPendigCallback, void*, uint32_t" -Function,+,furi_timer_restart,FuriStatus,FuriTimer* +Function,+,furi_timer_restart,FuriStatus,"FuriTimer*, uint32_t" Function,+,furi_timer_set_thread_priority,void,FuriTimerThreadPriority Function,+,furi_timer_start,FuriStatus,"FuriTimer*, uint32_t" Function,+,furi_timer_stop,FuriStatus,FuriTimer* diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 0f81f119114..57adf96aeca 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,43.2,, +Version,+,44.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -1688,7 +1688,7 @@ Function,+,furi_timer_free,void,FuriTimer* Function,+,furi_timer_get_expire_time,uint32_t,FuriTimer* Function,+,furi_timer_is_running,uint32_t,FuriTimer* Function,+,furi_timer_pending_callback,void,"FuriTimerPendigCallback, void*, uint32_t" -Function,+,furi_timer_restart,FuriStatus,FuriTimer* +Function,+,furi_timer_restart,FuriStatus,"FuriTimer*, uint32_t" Function,+,furi_timer_set_thread_priority,void,FuriTimerThreadPriority Function,+,furi_timer_start,FuriStatus,"FuriTimer*, uint32_t" Function,+,furi_timer_stop,FuriStatus,FuriTimer* From 0131eb3aa29568d593d8a581d6d7d2c0bd10c2df Mon Sep 17 00:00:00 2001 From: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> Date: Thu, 2 Nov 2023 16:10:16 +0300 Subject: [PATCH 799/824] [FL-3656] Fix crash when exiting write mode (#3191) --- .../main/ibutton/ibutton_custom_event.h | 8 +++-- .../main/ibutton/scenes/ibutton_scene_write.c | 29 ++++++++++++++----- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/applications/main/ibutton/ibutton_custom_event.h b/applications/main/ibutton/ibutton_custom_event.h index 28bcb94a0dc..1ac50ee3cf6 100644 --- a/applications/main/ibutton/ibutton_custom_event.h +++ b/applications/main/ibutton/ibutton_custom_event.h @@ -1,6 +1,6 @@ #pragma once -enum iButtonCustomEvent { +typedef enum { // Reserve first 100 events for button types and indexes, starting from 0 iButtonCustomEventReserved = 100, @@ -10,8 +10,12 @@ enum iButtonCustomEvent { iButtonCustomEventByteEditResult, iButtonCustomEventWorkerEmulated, iButtonCustomEventWorkerRead, + iButtonCustomEventWorkerWriteOK, + iButtonCustomEventWorkerWriteSameKey, + iButtonCustomEventWorkerWriteNoDetect, + iButtonCustomEventWorkerWriteCannotWrite, iButtonCustomEventRpcLoad, iButtonCustomEventRpcExit, iButtonCustomEventRpcSessionClose, -}; +} iButtonCustomEvent; diff --git a/applications/main/ibutton/scenes/ibutton_scene_write.c b/applications/main/ibutton/scenes/ibutton_scene_write.c index 541aa1c52b0..63be635069c 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_write.c +++ b/applications/main/ibutton/scenes/ibutton_scene_write.c @@ -5,9 +5,26 @@ typedef enum { iButtonSceneWriteStateBlinkYellow, } iButtonSceneWriteState; +static inline iButtonCustomEvent + ibutton_scene_write_to_custom_event(iButtonWorkerWriteResult result) { + switch(result) { + case iButtonWorkerWriteOK: + return iButtonCustomEventWorkerWriteOK; + case iButtonWorkerWriteSameKey: + return iButtonCustomEventWorkerWriteSameKey; + case iButtonWorkerWriteNoDetect: + return iButtonCustomEventWorkerWriteNoDetect; + case iButtonWorkerWriteCannotWrite: + return iButtonCustomEventWorkerWriteCannotWrite; + default: + furi_crash(); + } +} + static void ibutton_scene_write_callback(void* context, iButtonWorkerWriteResult result) { iButton* ibutton = context; - view_dispatcher_send_custom_event(ibutton->view_dispatcher, result); + view_dispatcher_send_custom_event( + ibutton->view_dispatcher, ibutton_scene_write_to_custom_event(result)); } void ibutton_scene_write_on_enter(void* context) { @@ -61,16 +78,14 @@ bool ibutton_scene_write_on_event(void* context, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { consumed = true; - if((event.event == iButtonWorkerWriteOK) || (event.event == iButtonWorkerWriteSameKey)) { + if((event.event == iButtonCustomEventWorkerWriteOK) || + (event.event == iButtonCustomEventWorkerWriteSameKey)) { scene_manager_next_scene(scene_manager, iButtonSceneWriteSuccess); - } else if(event.event == iButtonWorkerWriteNoDetect) { + } else if(event.event == iButtonCustomEventWorkerWriteNoDetect) { ibutton_notification_message(ibutton, iButtonNotificationMessageEmulateBlink); - } else if(event.event == iButtonWorkerWriteCannotWrite) { + } else if(event.event == iButtonCustomEventWorkerWriteCannotWrite) { ibutton_notification_message(ibutton, iButtonNotificationMessageYellowBlink); } - - } else if(event.type == SceneManagerEventTypeTick) { - consumed = true; } return consumed; From 0d94abf85693c0eb9eee53f8d0a83692e6390ff8 Mon Sep 17 00:00:00 2001 From: hedger Date: Thu, 2 Nov 2023 17:28:39 +0400 Subject: [PATCH 800/824] fbt: dist improvements (#3186) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fbt: MENUEXTERNAL apps now respect build configuration; fixed `fap_deploy` * fbt, ufbt: better message for missing app manifests (shows real paths) Co-authored-by: あく --- SConstruct | 1 + scripts/fbt/appmanifest.py | 22 +++++++++++++--------- scripts/fbt_tools/fbt_apps.py | 7 ++++++- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/SConstruct b/SConstruct index faccdfa5b3e..97d7e5e5e28 100644 --- a/SConstruct +++ b/SConstruct @@ -185,6 +185,7 @@ fap_deploy = distenv.PhonyTarget( ], source=firmware_env.Dir(("${RESOURCES_ROOT}/apps")), ) +Depends(fap_deploy, firmware_env["FW_RESOURCES_MANIFEST"]) # Target for bundling core2 package for qFlipper diff --git a/scripts/fbt/appmanifest.py b/scripts/fbt/appmanifest.py index 3a3640d4259..bef4eb02b1f 100644 --- a/scripts/fbt/appmanifest.py +++ b/scripts/fbt/appmanifest.py @@ -240,12 +240,12 @@ class AppBuildset: FlipperAppType.SETTINGS, FlipperAppType.STARTUP, ) - EXTERNAL_APP_TYPES = ( - FlipperAppType.EXTERNAL, - FlipperAppType.MENUEXTERNAL, - FlipperAppType.PLUGIN, - FlipperAppType.DEBUG, - ) + EXTERNAL_APP_TYPES_MAP = { + FlipperAppType.EXTERNAL: True, + FlipperAppType.PLUGIN: True, + FlipperAppType.DEBUG: True, + FlipperAppType.MENUEXTERNAL: False, + } @staticmethod def print_writer(message): @@ -318,8 +318,8 @@ def _process_deps(self): def _process_ext_apps(self): extapps = [ app - for apptype in self.EXTERNAL_APP_TYPES - for app in self.get_apps_of_type(apptype, True) + for (apptype, global_lookup) in self.EXTERNAL_APP_TYPES_MAP.items() + for app in self.get_apps_of_type(apptype, global_lookup) ] extapps.extend(map(self.appmgr.get, self._extra_ext_appnames)) @@ -407,10 +407,14 @@ def get_sdk_headers(self): return sdk_headers def get_apps_of_type(self, apptype: FlipperAppType, all_known: bool = False): + """Looks up apps of given type in current app set. If all_known is true, + ignores app set and checks all loaded apps' manifests.""" return sorted( filter( lambda app: app.apptype == apptype, - self.appmgr.known_apps.values() if all_known else self._apps, + self.appmgr.known_apps.values() + if all_known + else map(self.appmgr.get, self.appnames), ), key=lambda app: app.order, ) diff --git a/scripts/fbt_tools/fbt_apps.py b/scripts/fbt_tools/fbt_apps.py index 9a071805520..edce194f07d 100644 --- a/scripts/fbt_tools/fbt_apps.py +++ b/scripts/fbt_tools/fbt_apps.py @@ -21,8 +21,13 @@ def LoadAppManifest(env, entry): APP_MANIFEST_NAME = "application.fam" manifest_glob = entry.glob(APP_MANIFEST_NAME) if len(manifest_glob) == 0: + try: + disk_node = next(filter(lambda d: d.exists(), entry.get_all_rdirs())) + except Exception: + disk_node = entry + raise FlipperManifestException( - f"Folder {entry}: manifest {APP_MANIFEST_NAME} is missing" + f"App folder '{disk_node.abspath}': missing manifest ({APP_MANIFEST_NAME})" ) app_manifest_file_path = manifest_glob[0].rfile().abspath From 085c90af40f66350af26038342b3c3f2fe6e64d6 Mon Sep 17 00:00:00 2001 From: DerSkythe <31771569+derskythe@users.noreply.github.com> Date: Sun, 5 Nov 2023 17:59:22 +0400 Subject: [PATCH 801/824] fix: invariant format of log time data #3195 (#3202) --- scripts/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/version.py b/scripts/version.py index e68f7b41d7c..4b1c739bc5b 100755 --- a/scripts/version.py +++ b/scripts/version.py @@ -46,7 +46,7 @@ def get_version_info(self): ) else: commit_date = datetime.strptime( - self._exec_git("log -1 --format=%cd").strip(), + self._exec_git("log -1 --format=%cd --date=default").strip(), "%a %b %d %H:%M:%S %Y %z", ) From 16ffa2bf75e6acbecc0b0d48f792a630525c0db2 Mon Sep 17 00:00:00 2001 From: gornekich Date: Thu, 9 Nov 2023 10:18:36 +0400 Subject: [PATCH 802/824] [FL-3657] Fix NFC unit tests (#3192) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- lib/nfc/helpers/nfc_data_generator.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/nfc/helpers/nfc_data_generator.c b/lib/nfc/helpers/nfc_data_generator.c index 27830516220..011f9f6db7d 100644 --- a/lib/nfc/helpers/nfc_data_generator.c +++ b/lib/nfc/helpers/nfc_data_generator.c @@ -28,6 +28,7 @@ static const uint8_t default_config_ntag_i2c[] = {0x01, 0x00, 0xF8, 0x48, 0x08, static void nfc_generate_mf_ul_uid(uint8_t* uid) { uid[0] = NXP_MANUFACTURER_ID; furi_hal_random_fill_buf(&uid[1], 6); + uid[3] |= 0x01; // To avoid forbidden 0x88 value // I'm not sure how this is generated, but the upper nybble always seems to be 8 uid[6] &= 0x0F; uid[6] |= 0x80; @@ -322,6 +323,7 @@ static void nfc_generate_ntag_i2c_plus_2k(NfcDevice* nfc_device) { static void nfc_generate_mf_classic_uid(uint8_t* uid, uint8_t length) { uid[0] = NXP_MANUFACTURER_ID; furi_hal_random_fill_buf(&uid[1], length - 1); + uid[3] |= 0x01; // To avoid forbidden 0x88 value } static void From 49dcf81743859c17ee332f882e838500206f8039 Mon Sep 17 00:00:00 2001 From: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> Date: Fri, 10 Nov 2023 10:22:34 +0300 Subject: [PATCH 803/824] [FL-3618] Infrared remote button index support (#3180) * Do not load all signals at once (Draft) * Minor cleanup * Refactor remote renaming * Improve function signatures * Rename infrared_remote functions * Optimise signal loading * Implement adding signals to remote * Add read_name() method * Deprecate a function * Partially implement deleting signals (draft) * Use m-array instead of m-list for signal name directory * Use plain C strings instead of furi_string * Implement deleting signals * Implement deleting signals via generalised callback * Implement renaming signals * Rename some types * Some more renaming * Remove unused type * Implement inserting signals (internal use) * Improve InfraredMoveView * Send an event to move a signal * Remove unused type * Implement moving signals * Implement creating new remotes with one signal * Un-deprecate and rename a function * Add InfraredRemote API docs * Add InfraredSignal API docs * Better error messages * Show progress pop-up when moving buttons in a remote * Copy labels to the InfraredMoveView to avoid pointer invalidation * Improve file selection scene * Show progress pop-up when renaming buttons in a remote * Refactor a scene * Show progress when deleting a button from remote * Use a random name for temp files * Add docs to infrared_brute_force.h * Rename Infrared type to InfraredApp * Add docs to infrared_app_i.h * Deliver event data via a callback * Bundle event data together with event type * Change DataExchange behaviour * Adapt RPC debug app to new API * Remove rogue output * Add Doxygen comments to rpc_app.h * Simplify rpc_app.c code * Remove superflous parameter * Do not allocate protobuf messages on the stack * Fix GetError response * Support for button indices * Comment out shallow submodules * Fix F18 api * Fix logical error and add more debug output * fbt: testing unshallow for protobuf * github: lint&checks: unshallow prior to checks * Fix a TODO * github: do not unshallow the unshallowed * fbt: assets: only attempt to unshallow if cannot describe * Do not use the name when loading a signal by index (duh) * Simplify loading infrared signals by name * Sync with protobuf release * Infrared: use compact furi_crash macros Co-authored-by: hedger Co-authored-by: hedger Co-authored-by: Aleksandr Kutuzov --- .../workflows/lint_and_submodule_check.yml | 2 +- .../debug/rpc_debug_app/rpc_debug_app.c | 39 +- ...pc_debug_app_scene_receive_data_exchange.c | 39 +- applications/main/ibutton/ibutton.c | 14 +- .../main/ibutton/ibutton_custom_event.h | 2 +- .../main/ibutton/scenes/ibutton_scene_rpc.c | 23 +- applications/main/infrared/infrared_app.c | 34 +- applications/main/infrared/infrared_app_i.h | 1 + .../main/infrared/infrared_brute_force.c | 2 +- .../main/infrared/infrared_custom_event.h | 5 +- applications/main/infrared/infrared_remote.c | 7 +- applications/main/infrared/infrared_signal.c | 36 +- applications/main/infrared/infrared_signal.h | 21 +- .../infrared/scenes/infrared_scene_remote.c | 6 +- .../main/infrared/scenes/infrared_scene_rpc.c | 45 +- applications/main/lfrfid/lfrfid.c | 12 +- .../main/lfrfid/scenes/lfrfid_scene_rpc.c | 8 +- .../main/nfc/helpers/nfc_custom_event.h | 2 +- .../protocol_support/nfc_protocol_support.c | 16 +- applications/main/nfc/nfc_app.c | 14 +- .../main/subghz/scenes/subghz_scene_rpc.c | 16 +- applications/main/subghz/subghz.c | 16 +- applications/services/rpc/rpc.c | 3 + applications/services/rpc/rpc_app.c | 465 +++++++++--------- applications/services/rpc/rpc_app.h | 202 +++++++- assets/protobuf | 2 +- scripts/fbt_tools/fbt_assets.py | 45 +- targets/f18/api_symbols.csv | 4 +- targets/f7/api_symbols.csv | 4 +- 29 files changed, 670 insertions(+), 415 deletions(-) diff --git a/.github/workflows/lint_and_submodule_check.yml b/.github/workflows/lint_and_submodule_check.yml index 51e2593eccb..3f4d8c79bd0 100644 --- a/.github/workflows/lint_and_submodule_check.yml +++ b/.github/workflows/lint_and_submodule_check.yml @@ -23,7 +23,7 @@ jobs: - name: 'Check protobuf branch' run: | - git submodule update --init + git submodule update --init; SUB_PATH="assets/protobuf"; SUB_BRANCH="dev"; SUB_COMMITS_MIN=40; diff --git a/applications/debug/rpc_debug_app/rpc_debug_app.c b/applications/debug/rpc_debug_app/rpc_debug_app.c index 314be5e7225..2a0756383c2 100644 --- a/applications/debug/rpc_debug_app/rpc_debug_app.c +++ b/applications/debug/rpc_debug_app/rpc_debug_app.c @@ -21,22 +21,51 @@ static void rpc_debug_app_tick_event_callback(void* context) { scene_manager_handle_tick_event(app->scene_manager); } -static void rpc_debug_app_rpc_command_callback(RpcAppSystemEvent event, void* context) { +static void + rpc_debug_app_format_hex(const uint8_t* data, size_t data_size, char* buf, size_t buf_size) { + if(data == NULL || data_size == 0) { + strncpy(buf, "", buf_size); + return; + } + + const size_t byte_width = 3; + const size_t line_width = 7; + + data_size = MIN(data_size, buf_size / (byte_width + 1)); + + for(size_t i = 0; i < data_size; ++i) { + char* p = buf + (i * byte_width); + char sep = !((i + 1) % line_width) ? '\n' : ' '; + snprintf(p, byte_width + 1, "%02X%c", data[i], sep); + } + + buf[buf_size - 1] = '\0'; +} + +static void rpc_debug_app_rpc_command_callback(const RpcAppSystemEvent* event, void* context) { furi_assert(context); RpcDebugApp* app = context; furi_assert(app->rpc); - if(event == RpcAppEventSessionClose) { + if(event->type == RpcAppEventTypeSessionClose) { scene_manager_stop(app->scene_manager); view_dispatcher_stop(app->view_dispatcher); rpc_system_app_set_callback(app->rpc, NULL, NULL); app->rpc = NULL; - } else if(event == RpcAppEventAppExit) { + } else if(event->type == RpcAppEventTypeAppExit) { scene_manager_stop(app->scene_manager); view_dispatcher_stop(app->view_dispatcher); - rpc_system_app_confirm(app->rpc, RpcAppEventAppExit, true); + rpc_system_app_confirm(app->rpc, true); + } else if(event->type == RpcAppEventTypeDataExchange) { + furi_assert(event->data.type == RpcAppSystemEventDataTypeBytes); + + rpc_debug_app_format_hex( + event->data.bytes.ptr, event->data.bytes.size, app->text_store, TEXT_STORE_SIZE); + + view_dispatcher_send_custom_event( + app->view_dispatcher, RpcDebugAppCustomEventRpcDataExchange); } else { - rpc_system_app_confirm(app->rpc, event, false); + rpc_system_app_confirm(app->rpc, false); } } diff --git a/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_receive_data_exchange.c b/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_receive_data_exchange.c index 98bafff8a7b..10d5e36f624 100644 --- a/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_receive_data_exchange.c +++ b/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_receive_data_exchange.c @@ -1,40 +1,5 @@ #include "../rpc_debug_app.h" -static void rpc_debug_app_scene_start_format_hex( - const uint8_t* data, - size_t data_size, - char* buf, - size_t buf_size) { - furi_assert(data); - furi_assert(buf); - - const size_t byte_width = 3; - const size_t line_width = 7; - - data_size = MIN(data_size, buf_size / (byte_width + 1)); - - for(size_t i = 0; i < data_size; ++i) { - char* p = buf + (i * byte_width); - char sep = !((i + 1) % line_width) ? '\n' : ' '; - snprintf(p, byte_width + 1, "%02X%c", data[i], sep); - } - - buf[buf_size - 1] = '\0'; -} - -static void rpc_debug_app_scene_receive_data_exchange_callback( - const uint8_t* data, - size_t data_size, - void* context) { - RpcDebugApp* app = context; - if(data) { - rpc_debug_app_scene_start_format_hex(data, data_size, app->text_store, TEXT_STORE_SIZE); - } else { - strncpy(app->text_store, "", TEXT_STORE_SIZE); - } - view_dispatcher_send_custom_event(app->view_dispatcher, RpcDebugAppCustomEventRpcDataExchange); -} - void rpc_debug_app_scene_receive_data_exchange_on_enter(void* context) { RpcDebugApp* app = context; strncpy(app->text_store, "Received data will appear here...", TEXT_STORE_SIZE); @@ -42,8 +7,6 @@ void rpc_debug_app_scene_receive_data_exchange_on_enter(void* context) { text_box_set_text(app->text_box, app->text_store); text_box_set_font(app->text_box, TextBoxFontHex); - rpc_system_app_set_data_exchange_callback( - app->rpc, rpc_debug_app_scene_receive_data_exchange_callback, app); view_dispatcher_switch_to_view(app->view_dispatcher, RpcDebugAppViewTextBox); } @@ -53,6 +16,7 @@ bool rpc_debug_app_scene_receive_data_exchange_on_event(void* context, SceneMana if(event.type == SceneManagerEventTypeCustom) { if(event.event == RpcDebugAppCustomEventRpcDataExchange) { + rpc_system_app_confirm(app->rpc, true); notification_message(app->notifications, &sequence_blink_cyan_100); notification_message(app->notifications, &sequence_display_backlight_on); text_box_set_text(app->text_box, app->text_store); @@ -66,5 +30,4 @@ bool rpc_debug_app_scene_receive_data_exchange_on_event(void* context, SceneMana void rpc_debug_app_scene_receive_data_exchange_on_exit(void* context) { RpcDebugApp* app = context; text_box_reset(app->text_box); - rpc_system_app_set_data_exchange_callback(app->rpc, NULL, NULL); } diff --git a/applications/main/ibutton/ibutton.c b/applications/main/ibutton/ibutton.c index 760918097f1..fb6d9dcb52a 100644 --- a/applications/main/ibutton/ibutton.c +++ b/applications/main/ibutton/ibutton.c @@ -39,21 +39,23 @@ static void ibutton_make_app_folder(iButton* ibutton) { furi_record_close(RECORD_STORAGE); } -static void ibutton_rpc_command_callback(RpcAppSystemEvent event, void* context) { +static void ibutton_rpc_command_callback(const RpcAppSystemEvent* event, void* context) { furi_assert(context); iButton* ibutton = context; - if(event == RpcAppEventSessionClose) { + if(event->type == RpcAppEventTypeSessionClose) { view_dispatcher_send_custom_event( ibutton->view_dispatcher, iButtonCustomEventRpcSessionClose); rpc_system_app_set_callback(ibutton->rpc, NULL, NULL); ibutton->rpc = NULL; - } else if(event == RpcAppEventAppExit) { + } else if(event->type == RpcAppEventTypeAppExit) { view_dispatcher_send_custom_event(ibutton->view_dispatcher, iButtonCustomEventRpcExit); - } else if(event == RpcAppEventLoadFile) { - view_dispatcher_send_custom_event(ibutton->view_dispatcher, iButtonCustomEventRpcLoad); + } else if(event->type == RpcAppEventTypeLoadFile) { + furi_assert(event->data.type == RpcAppSystemEventDataTypeString); + furi_string_set(ibutton->file_path, event->data.string); + view_dispatcher_send_custom_event(ibutton->view_dispatcher, iButtonCustomEventRpcLoadFile); } else { - rpc_system_app_confirm(ibutton->rpc, event, false); + rpc_system_app_confirm(ibutton->rpc, false); } } diff --git a/applications/main/ibutton/ibutton_custom_event.h b/applications/main/ibutton/ibutton_custom_event.h index 1ac50ee3cf6..b0246d31094 100644 --- a/applications/main/ibutton/ibutton_custom_event.h +++ b/applications/main/ibutton/ibutton_custom_event.h @@ -15,7 +15,7 @@ typedef enum { iButtonCustomEventWorkerWriteNoDetect, iButtonCustomEventWorkerWriteCannotWrite, - iButtonCustomEventRpcLoad, + iButtonCustomEventRpcLoadFile, iButtonCustomEventRpcExit, iButtonCustomEventRpcSessionClose, } iButtonCustomEvent; diff --git a/applications/main/ibutton/scenes/ibutton_scene_rpc.c b/applications/main/ibutton/scenes/ibutton_scene_rpc.c index 9205eb4b46f..7106fefaa25 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_rpc.c +++ b/applications/main/ibutton/scenes/ibutton_scene_rpc.c @@ -23,28 +23,23 @@ bool ibutton_scene_rpc_on_event(void* context, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { consumed = true; - if(event.event == iButtonCustomEventRpcLoad) { + if(event.event == iButtonCustomEventRpcLoadFile) { bool result = false; - const char* file_path = rpc_system_app_get_data(ibutton->rpc); - if(file_path && (furi_string_empty(ibutton->file_path))) { - furi_string_set(ibutton->file_path, file_path); + if(ibutton_load_key(ibutton)) { + popup_set_text(popup, ibutton->key_name, 82, 32, AlignCenter, AlignTop); + view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewPopup); - if(ibutton_load_key(ibutton)) { - popup_set_text(popup, ibutton->key_name, 82, 32, AlignCenter, AlignTop); - view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewPopup); + ibutton_notification_message(ibutton, iButtonNotificationMessageEmulateStart); + ibutton_worker_emulate_start(ibutton->worker, ibutton->key); - ibutton_notification_message(ibutton, iButtonNotificationMessageEmulateStart); - ibutton_worker_emulate_start(ibutton->worker, ibutton->key); - - result = true; - } + result = true; } - rpc_system_app_confirm(ibutton->rpc, RpcAppEventLoadFile, result); + rpc_system_app_confirm(ibutton->rpc, result); } else if(event.event == iButtonCustomEventRpcExit) { - rpc_system_app_confirm(ibutton->rpc, RpcAppEventAppExit, true); + rpc_system_app_confirm(ibutton->rpc, true); scene_manager_stop(ibutton->scene_manager); view_dispatcher_stop(ibutton->view_dispatcher); diff --git a/applications/main/infrared/infrared_app.c b/applications/main/infrared/infrared_app.c index 7abb4e4eb61..645659bbc5d 100644 --- a/applications/main/infrared/infrared_app.c +++ b/applications/main/infrared/infrared_app.c @@ -44,30 +44,42 @@ static void infrared_tick_event_callback(void* context) { scene_manager_handle_tick_event(infrared->scene_manager); } -static void infrared_rpc_command_callback(RpcAppSystemEvent event, void* context) { +static void infrared_rpc_command_callback(const RpcAppSystemEvent* event, void* context) { furi_assert(context); InfraredApp* infrared = context; furi_assert(infrared->rpc_ctx); - if(event == RpcAppEventSessionClose) { + if(event->type == RpcAppEventTypeSessionClose) { view_dispatcher_send_custom_event( infrared->view_dispatcher, InfraredCustomEventTypeRpcSessionClose); rpc_system_app_set_callback(infrared->rpc_ctx, NULL, NULL); infrared->rpc_ctx = NULL; - } else if(event == RpcAppEventAppExit) { + } else if(event->type == RpcAppEventTypeAppExit) { view_dispatcher_send_custom_event( infrared->view_dispatcher, InfraredCustomEventTypeRpcExit); - } else if(event == RpcAppEventLoadFile) { + } else if(event->type == RpcAppEventTypeLoadFile) { + furi_assert(event->data.type == RpcAppSystemEventDataTypeString); + furi_string_set(infrared->file_path, event->data.string); view_dispatcher_send_custom_event( - infrared->view_dispatcher, InfraredCustomEventTypeRpcLoad); - } else if(event == RpcAppEventButtonPress) { - view_dispatcher_send_custom_event( - infrared->view_dispatcher, InfraredCustomEventTypeRpcButtonPress); - } else if(event == RpcAppEventButtonRelease) { + infrared->view_dispatcher, InfraredCustomEventTypeRpcLoadFile); + } else if(event->type == RpcAppEventTypeButtonPress) { + furi_assert( + event->data.type == RpcAppSystemEventDataTypeString || + event->data.type == RpcAppSystemEventDataTypeInt32); + if(event->data.type == RpcAppSystemEventDataTypeString) { + furi_string_set(infrared->button_name, event->data.string); + view_dispatcher_send_custom_event( + infrared->view_dispatcher, InfraredCustomEventTypeRpcButtonPressName); + } else { + infrared->app_state.current_button_index = event->data.i32; + view_dispatcher_send_custom_event( + infrared->view_dispatcher, InfraredCustomEventTypeRpcButtonPressIndex); + } + } else if(event->type == RpcAppEventTypeButtonRelease) { view_dispatcher_send_custom_event( infrared->view_dispatcher, InfraredCustomEventTypeRpcButtonRelease); } else { - rpc_system_app_confirm(infrared->rpc_ctx, event, false); + rpc_system_app_confirm(infrared->rpc_ctx, false); } } @@ -117,6 +129,7 @@ static InfraredApp* infrared_alloc() { InfraredApp* infrared = malloc(sizeof(InfraredApp)); infrared->file_path = furi_string_alloc(); + infrared->button_name = furi_string_alloc(); InfraredAppState* app_state = &infrared->app_state; app_state->is_learning_new_remote = false; @@ -247,6 +260,7 @@ static void infrared_free(InfraredApp* infrared) { infrared->gui = NULL; furi_string_free(infrared->file_path); + furi_string_free(infrared->button_name); free(infrared); } diff --git a/applications/main/infrared/infrared_app_i.h b/applications/main/infrared/infrared_app_i.h index c9dfe3ab865..c35d3fa410a 100644 --- a/applications/main/infrared/infrared_app_i.h +++ b/applications/main/infrared/infrared_app_i.h @@ -121,6 +121,7 @@ struct InfraredApp { InfraredProgressView* progress; /**< Custom view for showing brute force progress. */ FuriString* file_path; /**< Full path to the currently loaded file. */ + FuriString* button_name; /** Name of the button requested in RPC mode. */ /** Arbitrary text storage for various inputs. */ char text_store[INFRARED_TEXT_STORE_NUM][INFRARED_TEXT_STORE_SIZE + 1]; InfraredAppState app_state; /**< Application state. */ diff --git a/applications/main/infrared/infrared_brute_force.c b/applications/main/infrared/infrared_brute_force.c index bb7992ae61c..373c7270f58 100644 --- a/applications/main/infrared/infrared_brute_force.c +++ b/applications/main/infrared/infrared_brute_force.c @@ -128,7 +128,7 @@ void infrared_brute_force_stop(InfraredBruteForce* brute_force) { bool infrared_brute_force_send_next(InfraredBruteForce* brute_force) { furi_assert(brute_force->is_started); - const bool success = infrared_signal_search_and_read( + const bool success = infrared_signal_search_by_name_and_read( brute_force->current_signal, brute_force->ff, furi_string_get_cstr(brute_force->current_record_name)); diff --git a/applications/main/infrared/infrared_custom_event.h b/applications/main/infrared/infrared_custom_event.h index 09440ddec18..30bb0f729cd 100644 --- a/applications/main/infrared/infrared_custom_event.h +++ b/applications/main/infrared/infrared_custom_event.h @@ -15,9 +15,10 @@ enum InfraredCustomEventType { InfraredCustomEventTypeButtonSelected, InfraredCustomEventTypeBackPressed, - InfraredCustomEventTypeRpcLoad, + InfraredCustomEventTypeRpcLoadFile, InfraredCustomEventTypeRpcExit, - InfraredCustomEventTypeRpcButtonPress, + InfraredCustomEventTypeRpcButtonPressName, + InfraredCustomEventTypeRpcButtonPressIndex, InfraredCustomEventTypeRpcButtonRelease, InfraredCustomEventTypeRpcSessionClose, }; diff --git a/applications/main/infrared/infrared_remote.c b/applications/main/infrared/infrared_remote.c index 5b241fe9f60..cab241c1258 100644 --- a/applications/main/infrared/infrared_remote.c +++ b/applications/main/infrared/infrared_remote.c @@ -95,10 +95,9 @@ bool infrared_remote_load_signal( const char* path = furi_string_get_cstr(remote->path); if(!flipper_format_buffered_file_open_existing(ff, path)) break; - const char* name = infrared_remote_get_signal_name(remote, index); - - if(!infrared_signal_search_and_read(signal, ff, name)) { - FURI_LOG_E(TAG, "Failed to load signal '%s' from file '%s'", name, path); + if(!infrared_signal_search_by_index_and_read(signal, ff, index)) { + const char* signal_name = infrared_remote_get_signal_name(remote, index); + FURI_LOG_E(TAG, "Failed to load signal '%s' from file '%s'", signal_name, path); break; } diff --git a/applications/main/infrared/infrared_signal.c b/applications/main/infrared/infrared_signal.c index c73e4db98da..2b0d3479111 100644 --- a/applications/main/infrared/infrared_signal.c +++ b/applications/main/infrared/infrared_signal.c @@ -266,19 +266,37 @@ bool infrared_signal_read_name(FlipperFormat* ff, FuriString* name) { return flipper_format_read_string(ff, INFRARED_SIGNAL_NAME_KEY, name); } -bool infrared_signal_search_and_read(InfraredSignal* signal, FlipperFormat* ff, const char* name) { +bool infrared_signal_search_by_name_and_read( + InfraredSignal* signal, + FlipperFormat* ff, + const char* name) { bool success = false; FuriString* tmp = furi_string_alloc(); - do { - bool is_name_found = false; - while(!is_name_found && infrared_signal_read_name(ff, tmp)) { //-V560 - is_name_found = furi_string_equal(tmp, name); + while(infrared_signal_read_name(ff, tmp)) { + if(furi_string_equal(tmp, name)) { + success = infrared_signal_read_body(signal, ff); + break; } - if(!is_name_found) break; //-V547 - if(!infrared_signal_read_body(signal, ff)) break; //-V779 - success = true; - } while(false); + } + + furi_string_free(tmp); + return success; +} + +bool infrared_signal_search_by_index_and_read( + InfraredSignal* signal, + FlipperFormat* ff, + size_t index) { + bool success = false; + FuriString* tmp = furi_string_alloc(); + + for(uint32_t i = 0; infrared_signal_read_name(ff, tmp); ++i) { + if(i == index) { + success = infrared_signal_read_body(signal, ff); + break; + } + } furi_string_free(tmp); return success; diff --git a/applications/main/infrared/infrared_signal.h b/applications/main/infrared/infrared_signal.h index fcbb3c87396..cfa4cfa94e2 100644 --- a/applications/main/infrared/infrared_signal.h +++ b/applications/main/infrared/infrared_signal.h @@ -162,7 +162,26 @@ bool infrared_signal_read_name(FlipperFormat* ff, FuriString* name); * @param[in] name pointer to a zero-terminated string containing the requested signal name. * @returns true if a signal was found and successfully read, false otherwise (e.g. the signal was not found). */ -bool infrared_signal_search_and_read(InfraredSignal* signal, FlipperFormat* ff, const char* name); +bool infrared_signal_search_by_name_and_read( + InfraredSignal* signal, + FlipperFormat* ff, + const char* name); + +/** + * @brief Read a signal with a particular index from a FlipperFormat file into an InfraredSignal instance. + * + * This function will look for a signal with the given index and if found, attempt to read it. + * Same considerations apply as to infrared_signal_read(). + * + * @param[in,out] signal pointer to the instance to be read into. + * @param[in,out] ff pointer to the FlipperFormat file instance to read from. + * @param[in] index the requested signal index. + * @returns true if a signal was found and successfully read, false otherwise (e.g. the signal was not found). + */ +bool infrared_signal_search_by_index_and_read( + InfraredSignal* signal, + FlipperFormat* ff, + size_t index); /** * @brief Save a signal contained in an InfraredSignal instance to a FlipperFormat file. diff --git a/applications/main/infrared/scenes/infrared_scene_remote.c b/applications/main/infrared/scenes/infrared_scene_remote.c index 5974acbfecb..6c1d1ec4e39 100644 --- a/applications/main/infrared/scenes/infrared_scene_remote.c +++ b/applications/main/infrared/scenes/infrared_scene_remote.c @@ -1,7 +1,7 @@ #include "../infrared_app_i.h" typedef enum { - ButtonIndexPlus = -2, + ButtonIndexLearn = -2, ButtonIndexEdit = -1, ButtonIndexNA = 0, } ButtonIndex; @@ -44,7 +44,7 @@ void infrared_scene_remote_on_enter(void* context) { button_menu_add_item( button_menu, "+", - ButtonIndexPlus, + ButtonIndexLearn, infrared_scene_remote_button_menu_callback, ButtonMenuItemTypeControl, context); @@ -95,7 +95,7 @@ bool infrared_scene_remote_on_event(void* context, SceneManagerEvent event) { if(is_transmitter_idle) { scene_manager_set_scene_state( scene_manager, InfraredSceneRemote, (unsigned)button_index); - if(button_index == ButtonIndexPlus) { + if(button_index == ButtonIndexLearn) { infrared->app_state.is_learning_new_remote = false; scene_manager_next_scene(scene_manager, InfraredSceneLearn); consumed = true; diff --git a/applications/main/infrared/scenes/infrared_scene_rpc.c b/applications/main/infrared/scenes/infrared_scene_rpc.c index fa5a599afa7..f3408fba4dd 100644 --- a/applications/main/infrared/scenes/infrared_scene_rpc.c +++ b/applications/main/infrared/scenes/infrared_scene_rpc.c @@ -1,6 +1,8 @@ #include "../infrared_app_i.h" #include +#define TAG "InfraredApp" + typedef enum { InfraredRpcStateIdle, InfraredRpcStateLoaded, @@ -38,11 +40,9 @@ bool infrared_scene_rpc_on_event(void* context, SceneManagerEvent event) { view_dispatcher_stop(infrared->view_dispatcher); } else if(event.event == InfraredCustomEventTypePopupClosed) { view_dispatcher_stop(infrared->view_dispatcher); - } else if(event.event == InfraredCustomEventTypeRpcLoad) { + } else if(event.event == InfraredCustomEventTypeRpcLoadFile) { bool result = false; - const char* arg = rpc_system_app_get_data(infrared->rpc_ctx); - if(arg && (state == InfraredRpcStateIdle)) { - furi_string_set(infrared->file_path, arg); + if(state == InfraredRpcStateIdle) { result = infrared_remote_load( infrared->remote, furi_string_get_cstr(infrared->file_path)); if(result) { @@ -56,20 +56,35 @@ bool infrared_scene_rpc_on_event(void* context, SceneManagerEvent event) { popup_set_text( infrared->popup, infrared->text_store[0], 89, 44, AlignCenter, AlignTop); - rpc_system_app_confirm(infrared->rpc_ctx, RpcAppEventLoadFile, result); - } else if(event.event == InfraredCustomEventTypeRpcButtonPress) { + rpc_system_app_confirm(infrared->rpc_ctx, result); + } else if( + event.event == InfraredCustomEventTypeRpcButtonPressName || + event.event == InfraredCustomEventTypeRpcButtonPressIndex) { bool result = false; - const char* arg = rpc_system_app_get_data(infrared->rpc_ctx); - if(arg && (state == InfraredRpcStateLoaded)) { - size_t button_index = 0; - if(infrared_remote_get_signal_index(infrared->remote, arg, &button_index)) { - infrared_tx_start_button_index(infrared, button_index); - result = true; + if(state == InfraredRpcStateLoaded) { + if(event.event == InfraredCustomEventTypeRpcButtonPressName) { + const char* button_name = furi_string_get_cstr(infrared->button_name); + size_t index; + const bool index_found = + infrared_remote_get_signal_index(infrared->remote, button_name, &index); + infrared->app_state.current_button_index = + index_found ? (signed)index : InfraredButtonIndexNone; + FURI_LOG_D(TAG, "Sending signal with name \"%s\"", button_name); + } else { + FURI_LOG_D( + TAG, + "Sending signal with index \"%ld\"", + infrared->app_state.current_button_index); + } + if(infrared->app_state.current_button_index != InfraredButtonIndexNone) { + infrared_tx_start_button_index( + infrared, infrared->app_state.current_button_index); scene_manager_set_scene_state( infrared->scene_manager, InfraredSceneRpc, InfraredRpcStateSending); + result = true; } } - rpc_system_app_confirm(infrared->rpc_ctx, RpcAppEventButtonRelease, result); + rpc_system_app_confirm(infrared->rpc_ctx, result); } else if(event.event == InfraredCustomEventTypeRpcButtonRelease) { bool result = false; if(state == InfraredRpcStateSending) { @@ -78,11 +93,11 @@ bool infrared_scene_rpc_on_event(void* context, SceneManagerEvent event) { scene_manager_set_scene_state( infrared->scene_manager, InfraredSceneRpc, InfraredRpcStateLoaded); } - rpc_system_app_confirm(infrared->rpc_ctx, RpcAppEventButtonRelease, result); + rpc_system_app_confirm(infrared->rpc_ctx, result); } else if(event.event == InfraredCustomEventTypeRpcExit) { scene_manager_stop(infrared->scene_manager); view_dispatcher_stop(infrared->view_dispatcher); - rpc_system_app_confirm(infrared->rpc_ctx, RpcAppEventAppExit, true); + rpc_system_app_confirm(infrared->rpc_ctx, true); } else if(event.event == InfraredCustomEventTypeRpcSessionClose) { scene_manager_stop(infrared->scene_manager); view_dispatcher_stop(infrared->view_dispatcher); diff --git a/applications/main/lfrfid/lfrfid.c b/applications/main/lfrfid/lfrfid.c index 2bef08df21f..cd0613861fe 100644 --- a/applications/main/lfrfid/lfrfid.c +++ b/applications/main/lfrfid/lfrfid.c @@ -13,21 +13,23 @@ static bool lfrfid_debug_back_event_callback(void* context) { return scene_manager_handle_back_event(app->scene_manager); } -static void rpc_command_callback(RpcAppSystemEvent rpc_event, void* context) { +static void rpc_command_callback(const RpcAppSystemEvent* event, void* context) { furi_assert(context); LfRfid* app = (LfRfid*)context; - if(rpc_event == RpcAppEventSessionClose) { + if(event->type == RpcAppEventTypeSessionClose) { view_dispatcher_send_custom_event(app->view_dispatcher, LfRfidEventRpcSessionClose); // Detach RPC rpc_system_app_set_callback(app->rpc_ctx, NULL, NULL); app->rpc_ctx = NULL; - } else if(rpc_event == RpcAppEventAppExit) { + } else if(event->type == RpcAppEventTypeAppExit) { view_dispatcher_send_custom_event(app->view_dispatcher, LfRfidEventExit); - } else if(rpc_event == RpcAppEventLoadFile) { + } else if(event->type == RpcAppEventTypeLoadFile) { + furi_assert(event->data.type == RpcAppSystemEventDataTypeString); + furi_string_set(app->file_path, event->data.string); view_dispatcher_send_custom_event(app->view_dispatcher, LfRfidEventRpcLoadFile); } else { - rpc_system_app_confirm(app->rpc_ctx, rpc_event, false); + rpc_system_app_confirm(app->rpc_ctx, false); } } diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_rpc.c b/applications/main/lfrfid/scenes/lfrfid_scene_rpc.c index 156dd97afa1..906218d74f0 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_rpc.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_rpc.c @@ -24,17 +24,15 @@ bool lfrfid_scene_rpc_on_event(void* context, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { consumed = true; if(event.event == LfRfidEventExit) { - rpc_system_app_confirm(app->rpc_ctx, RpcAppEventAppExit, true); + rpc_system_app_confirm(app->rpc_ctx, true); scene_manager_stop(app->scene_manager); view_dispatcher_stop(app->view_dispatcher); } else if(event.event == LfRfidEventRpcSessionClose) { scene_manager_stop(app->scene_manager); view_dispatcher_stop(app->view_dispatcher); } else if(event.event == LfRfidEventRpcLoadFile) { - const char* arg = rpc_system_app_get_data(app->rpc_ctx); bool result = false; - if(arg && (app->rpc_state == LfRfidRpcStateIdle)) { - furi_string_set(app->file_path, arg); + if(app->rpc_state == LfRfidRpcStateIdle) { if(lfrfid_load_key_data(app, app->file_path, false)) { lfrfid_worker_start_thread(app->lfworker); lfrfid_worker_emulate_start(app->lfworker, (LFRFIDProtocol)app->protocol_id); @@ -48,7 +46,7 @@ bool lfrfid_scene_rpc_on_event(void* context, SceneManagerEvent event) { result = true; } } - rpc_system_app_confirm(app->rpc_ctx, RpcAppEventLoadFile, result); + rpc_system_app_confirm(app->rpc_ctx, result); } } return consumed; diff --git a/applications/main/nfc/helpers/nfc_custom_event.h b/applications/main/nfc/helpers/nfc_custom_event.h index 2a3b13e1a41..16fbc47492b 100644 --- a/applications/main/nfc/helpers/nfc_custom_event.h +++ b/applications/main/nfc/helpers/nfc_custom_event.h @@ -21,7 +21,7 @@ typedef enum { NfcCustomEventTextInputDone, NfcCustomEventDictAttackDone, - NfcCustomEventRpcLoad, + NfcCustomEventRpcLoadFile, NfcCustomEventRpcExit, NfcCustomEventRpcSessionClose, diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c index bf6c0842fe5..87fbe9f0828 100644 --- a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c @@ -694,15 +694,17 @@ static bool nfc_protocol_support_scene_rpc_on_event(NfcApp* instance, SceneManag bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { - if(event.event == NfcCustomEventRpcLoad && instance->rpc_state == NfcRpcStateIdle) { - furi_string_set(instance->file_path, rpc_system_app_get_data(instance->rpc_ctx)); - const bool load_success = nfc_load_file(instance, instance->file_path, false); - if(load_success) { - nfc_protocol_support_scene_rpc_setup_ui_and_emulate(instance); + if(event.event == NfcCustomEventRpcLoadFile) { + bool success = false; + if(instance->rpc_state == NfcRpcStateIdle) { + if(nfc_load_file(instance, instance->file_path, false)) { + nfc_protocol_support_scene_rpc_setup_ui_and_emulate(instance); + success = true; + } } - rpc_system_app_confirm(instance->rpc_ctx, RpcAppEventLoadFile, load_success); + rpc_system_app_confirm(instance->rpc_ctx, success); } else if(event.event == NfcCustomEventRpcExit) { - rpc_system_app_confirm(instance->rpc_ctx, RpcAppEventAppExit, true); + rpc_system_app_confirm(instance->rpc_ctx, true); scene_manager_stop(instance->scene_manager); view_dispatcher_stop(instance->view_dispatcher); } else if(event.event == NfcCustomEventRpcSessionClose) { diff --git a/applications/main/nfc/nfc_app.c b/applications/main/nfc/nfc_app.c index 9e0de8891bb..141a67e5cba 100644 --- a/applications/main/nfc/nfc_app.c +++ b/applications/main/nfc/nfc_app.c @@ -14,22 +14,24 @@ bool nfc_back_event_callback(void* context) { return scene_manager_handle_back_event(nfc->scene_manager); } -static void nfc_app_rpc_command_callback(RpcAppSystemEvent rpc_event, void* context) { +static void nfc_app_rpc_command_callback(const RpcAppSystemEvent* event, void* context) { furi_assert(context); NfcApp* nfc = (NfcApp*)context; furi_assert(nfc->rpc_ctx); - if(rpc_event == RpcAppEventSessionClose) { + if(event->type == RpcAppEventTypeSessionClose) { view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventRpcSessionClose); rpc_system_app_set_callback(nfc->rpc_ctx, NULL, NULL); nfc->rpc_ctx = NULL; - } else if(rpc_event == RpcAppEventAppExit) { + } else if(event->type == RpcAppEventTypeAppExit) { view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventRpcExit); - } else if(rpc_event == RpcAppEventLoadFile) { - view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventRpcLoad); + } else if(event->type == RpcAppEventTypeLoadFile) { + furi_assert(event->data.type == RpcAppSystemEventDataTypeString); + furi_string_set(nfc->file_path, event->data.string); + view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventRpcLoadFile); } else { - rpc_system_app_confirm(nfc->rpc_ctx, rpc_event, false); + rpc_system_app_confirm(nfc->rpc_ctx, false); } } diff --git a/applications/main/subghz/scenes/subghz_scene_rpc.c b/applications/main/subghz/scenes/subghz_scene_rpc.c index d4bf3e808eb..f8bf066d549 100644 --- a/applications/main/subghz/scenes/subghz_scene_rpc.c +++ b/applications/main/subghz/scenes/subghz_scene_rpc.c @@ -33,13 +33,13 @@ bool subghz_scene_rpc_on_event(void* context, SceneManagerEvent event) { if(event.event == SubGhzCustomEventSceneExit) { scene_manager_stop(subghz->scene_manager); view_dispatcher_stop(subghz->view_dispatcher); - rpc_system_app_confirm(subghz->rpc_ctx, RpcAppEventAppExit, true); + rpc_system_app_confirm(subghz->rpc_ctx, true); } else if(event.event == SubGhzCustomEventSceneRpcSessionClose) { scene_manager_stop(subghz->scene_manager); view_dispatcher_stop(subghz->view_dispatcher); } else if(event.event == SubGhzCustomEventSceneRpcButtonPress) { bool result = false; - if((state == SubGhzRpcStateLoaded)) { + if(state == SubGhzRpcStateLoaded) { switch( subghz_txrx_tx_start(subghz->txrx, subghz_txrx_get_fff_data(subghz->txrx))) { case SubGhzTxRxStartTxStateErrorOnlyRx: @@ -61,7 +61,7 @@ bool subghz_scene_rpc_on_event(void* context, SceneManagerEvent event) { break; } } - rpc_system_app_confirm(subghz->rpc_ctx, RpcAppEventButtonPress, result); + rpc_system_app_confirm(subghz->rpc_ctx, result); } else if(event.event == SubGhzCustomEventSceneRpcButtonRelease) { bool result = false; if(state == SubGhzRpcStateTx) { @@ -70,15 +70,13 @@ bool subghz_scene_rpc_on_event(void* context, SceneManagerEvent event) { result = true; } state = SubGhzRpcStateIdle; - rpc_system_app_confirm(subghz->rpc_ctx, RpcAppEventButtonRelease, result); + rpc_system_app_confirm(subghz->rpc_ctx, result); } else if(event.event == SubGhzCustomEventSceneRpcLoad) { bool result = false; - const char* arg = rpc_system_app_get_data(subghz->rpc_ctx); - if(arg && (state == SubGhzRpcStateIdle)) { - if(subghz_key_load(subghz, arg, false)) { + if(state == SubGhzRpcStateIdle) { + if(subghz_key_load(subghz, furi_string_get_cstr(subghz->file_path), false)) { scene_manager_set_scene_state( subghz->scene_manager, SubGhzSceneRpc, SubGhzRpcStateLoaded); - furi_string_set(subghz->file_path, arg); result = true; FuriString* file_name; file_name = furi_string_alloc(); @@ -97,7 +95,7 @@ bool subghz_scene_rpc_on_event(void* context, SceneManagerEvent event) { rpc_system_app_set_error_text(subghz->rpc_ctx, "Cannot parse file"); } } - rpc_system_app_confirm(subghz->rpc_ctx, RpcAppEventLoadFile, result); + rpc_system_app_confirm(subghz->rpc_ctx, result); } } return consumed; diff --git a/applications/main/subghz/subghz.c b/applications/main/subghz/subghz.c index e8148798ef4..69a72e95d63 100644 --- a/applications/main/subghz/subghz.c +++ b/applications/main/subghz/subghz.c @@ -20,29 +20,31 @@ void subghz_tick_event_callback(void* context) { scene_manager_handle_tick_event(subghz->scene_manager); } -static void subghz_rpc_command_callback(RpcAppSystemEvent event, void* context) { +static void subghz_rpc_command_callback(const RpcAppSystemEvent* event, void* context) { furi_assert(context); SubGhz* subghz = context; furi_assert(subghz->rpc_ctx); - if(event == RpcAppEventSessionClose) { + if(event->type == RpcAppEventTypeSessionClose) { view_dispatcher_send_custom_event( subghz->view_dispatcher, SubGhzCustomEventSceneRpcSessionClose); rpc_system_app_set_callback(subghz->rpc_ctx, NULL, NULL); subghz->rpc_ctx = NULL; - } else if(event == RpcAppEventAppExit) { + } else if(event->type == RpcAppEventTypeAppExit) { view_dispatcher_send_custom_event(subghz->view_dispatcher, SubGhzCustomEventSceneExit); - } else if(event == RpcAppEventLoadFile) { + } else if(event->type == RpcAppEventTypeLoadFile) { + furi_assert(event->data.type == RpcAppSystemEventDataTypeString); + furi_string_set(subghz->file_path, event->data.string); view_dispatcher_send_custom_event(subghz->view_dispatcher, SubGhzCustomEventSceneRpcLoad); - } else if(event == RpcAppEventButtonPress) { + } else if(event->type == RpcAppEventTypeButtonPress) { view_dispatcher_send_custom_event( subghz->view_dispatcher, SubGhzCustomEventSceneRpcButtonPress); - } else if(event == RpcAppEventButtonRelease) { + } else if(event->type == RpcAppEventTypeButtonRelease) { view_dispatcher_send_custom_event( subghz->view_dispatcher, SubGhzCustomEventSceneRpcButtonRelease); } else { - rpc_system_app_confirm(subghz->rpc_ctx, event, false); + rpc_system_app_confirm(subghz->rpc_ctx, false); } } diff --git a/applications/services/rpc/rpc.c b/applications/services/rpc/rpc.c index 826f2225376..5880e7d9f96 100644 --- a/applications/services/rpc/rpc.c +++ b/applications/services/rpc/rpc.c @@ -477,12 +477,15 @@ void rpc_send_and_release(RpcSession* session, PB_Main* message) { } void rpc_send_and_release_empty(RpcSession* session, uint32_t command_id, PB_CommandStatus status) { + furi_assert(session); + PB_Main message = { .command_id = command_id, .command_status = status, .has_next = false, .which_content = PB_Main_empty_tag, }; + rpc_send_and_release(session, &message); pb_release(&PB_Main_msg, &message); } diff --git a/applications/services/rpc/rpc_app.c b/applications/services/rpc/rpc_app.c index e86eaa493d7..9af652dae17 100644 --- a/applications/services/rpc/rpc_app.c +++ b/applications/services/rpc/rpc_app.c @@ -10,49 +10,85 @@ struct RpcAppSystem { RpcSession* session; - RpcAppSystemCallback app_callback; - void* app_context; + RpcAppSystemCallback callback; + void* callback_context; - RpcAppSystemDataExchangeCallback data_exchange_callback; - void* data_exchange_context; + uint32_t error_code; + char* error_text; - PB_Main* state_msg; - PB_Main* error_msg; - - uint32_t last_id; - char* last_data; + uint32_t last_command_id; + RpcAppSystemEventType last_event_type; }; #define RPC_SYSTEM_APP_TEMP_ARGS_SIZE 16 +static void rpc_system_app_send_state_response( + RpcAppSystem* rpc_app, + PB_App_AppState state, + const char* name) { + PB_Main* response = malloc(sizeof(PB_Main)); + + response->which_content = PB_Main_app_state_response_tag; + response->content.app_state_response.state = state; + + FURI_LOG_D(TAG, "%s", name); + rpc_send(rpc_app->session, response); + + free(response); +} + +static void rpc_system_app_send_error_response( + RpcAppSystem* rpc_app, + uint32_t command_id, + PB_CommandStatus status, + const char* name) { + // Not describing all possible errors as only APP_NOT_RUNNING is used + const char* status_str = status == PB_CommandStatus_ERROR_APP_NOT_RUNNING ? "APP_NOT_RUNNING" : + "UNKNOWN"; + FURI_LOG_E(TAG, "%s: %s, id %lu, status: %d", name, status_str, command_id, status); + rpc_send_and_release_empty(rpc_app->session, command_id, status); +} + +static void rpc_system_app_set_last_command( + RpcAppSystem* rpc_app, + uint32_t command_id, + const RpcAppSystemEvent* event) { + furi_assert(rpc_app->last_command_id == 0); + furi_assert(rpc_app->last_event_type == RpcAppEventTypeInvalid); + + rpc_app->last_command_id = command_id; + rpc_app->last_event_type = event->type; +} + static void rpc_system_app_start_process(const PB_Main* request, void* context) { furi_assert(request); - furi_assert(context); - furi_assert(request->which_content == PB_Main_app_start_request_tag); - RpcAppSystem* rpc_app = context; - RpcSession* session = rpc_app->session; - rpc_system_app_error_reset(rpc_app); - furi_assert(session); - char args_temp[RPC_SYSTEM_APP_TEMP_ARGS_SIZE]; - furi_assert(!rpc_app->last_id); - furi_assert(!rpc_app->last_data); + RpcAppSystem* rpc_app = context; + furi_assert(rpc_app); + furi_assert(rpc_app->last_command_id == 0); + furi_assert(rpc_app->last_event_type == RpcAppEventTypeInvalid); FURI_LOG_D(TAG, "StartProcess: id %lu", request->command_id); - PB_CommandStatus result; - Loader* loader = furi_record_open(RECORD_LOADER); const char* app_name = request->content.app_start_request.name; + + PB_CommandStatus result; + if(app_name) { + rpc_system_app_error_reset(rpc_app); + + char app_args_temp[RPC_SYSTEM_APP_TEMP_ARGS_SIZE]; const char* app_args = request->content.app_start_request.args; + if(app_args && strcmp(app_args, "RPC") == 0) { // If app is being started in RPC mode - pass RPC context via args string - snprintf(args_temp, RPC_SYSTEM_APP_TEMP_ARGS_SIZE, "RPC %08lX", (uint32_t)rpc_app); - app_args = args_temp; + snprintf(app_args_temp, RPC_SYSTEM_APP_TEMP_ARGS_SIZE, "RPC %08lX", (uint32_t)rpc_app); + app_args = app_args_temp; } - LoaderStatus status = loader_start(loader, app_name, app_args, NULL); + + const LoaderStatus status = loader_start(loader, app_name, app_args, NULL); if(status == LoaderStatusErrorAppStarted) { result = PB_CommandStatus_ERROR_APP_SYSTEM_LOCKED; } else if(status == LoaderStatusErrorInternal) { @@ -71,266 +107,271 @@ static void rpc_system_app_start_process(const PB_Main* request, void* context) furi_record_close(RECORD_LOADER); FURI_LOG_D(TAG, "StartProcess: response id %lu, result %d", request->command_id, result); - rpc_send_and_release_empty(session, request->command_id, result); + rpc_send_and_release_empty(rpc_app->session, request->command_id, result); } static void rpc_system_app_lock_status_process(const PB_Main* request, void* context) { furi_assert(request); - furi_assert(context); - furi_assert(request->which_content == PB_Main_app_lock_status_request_tag); + RpcAppSystem* rpc_app = context; + furi_assert(rpc_app); + rpc_system_app_error_reset(rpc_app); - RpcSession* session = rpc_app->session; - furi_assert(session); FURI_LOG_D(TAG, "LockStatus"); - Loader* loader = furi_record_open(RECORD_LOADER); - - PB_Main response = { - .has_next = false, - .command_status = PB_CommandStatus_OK, - .command_id = request->command_id, - .which_content = PB_Main_app_lock_status_response_tag, - }; + PB_Main* response = malloc(sizeof(PB_Main)); - response.content.app_lock_status_response.locked = loader_is_locked(loader); + response->command_id = request->command_id; + response->which_content = PB_Main_app_lock_status_response_tag; + Loader* loader = furi_record_open(RECORD_LOADER); + response->content.app_lock_status_response.locked = loader_is_locked(loader); furi_record_close(RECORD_LOADER); FURI_LOG_D(TAG, "LockStatus: response"); - rpc_send_and_release(session, &response); - pb_release(&PB_Main_msg, &response); + rpc_send_and_release(rpc_app->session, response); + + free(response); } static void rpc_system_app_exit_request(const PB_Main* request, void* context) { furi_assert(request); - furi_assert(context); - furi_assert(request->which_content == PB_Main_app_exit_request_tag); - RpcAppSystem* rpc_app = context; - rpc_system_app_error_reset(rpc_app); - RpcSession* session = rpc_app->session; - furi_assert(session); - PB_CommandStatus status; + RpcAppSystem* rpc_app = context; + furi_assert(rpc_app); - if(rpc_app->app_callback) { + if(rpc_app->callback) { FURI_LOG_D(TAG, "ExitRequest: id %lu", request->command_id); - furi_assert(!rpc_app->last_id); - furi_assert(!rpc_app->last_data); - rpc_app->last_id = request->command_id; - rpc_app->app_callback(RpcAppEventAppExit, rpc_app->app_context); + + const RpcAppSystemEvent event = { + .type = RpcAppEventTypeAppExit, + .data = + { + .type = RpcAppSystemEventDataTypeNone, + {0}, + }, + }; + + rpc_system_app_error_reset(rpc_app); + rpc_system_app_set_last_command(rpc_app, request->command_id, &event); + + rpc_app->callback(&event, rpc_app->callback_context); + } else { - status = PB_CommandStatus_ERROR_APP_NOT_RUNNING; - FURI_LOG_E( - TAG, "ExitRequest: APP_NOT_RUNNING, id %lu, status: %d", request->command_id, status); - rpc_send_and_release_empty(session, request->command_id, status); + rpc_system_app_send_error_response( + rpc_app, request->command_id, PB_CommandStatus_ERROR_APP_NOT_RUNNING, "ExitRequest"); } } static void rpc_system_app_load_file(const PB_Main* request, void* context) { furi_assert(request); - furi_assert(context); - furi_assert(request->which_content == PB_Main_app_load_file_request_tag); + RpcAppSystem* rpc_app = context; - rpc_system_app_error_reset(rpc_app); - RpcSession* session = rpc_app->session; - furi_assert(session); + furi_assert(rpc_app); - PB_CommandStatus status; - if(rpc_app->app_callback) { + if(rpc_app->callback) { FURI_LOG_D(TAG, "LoadFile: id %lu", request->command_id); - furi_assert(!rpc_app->last_id); - furi_assert(!rpc_app->last_data); - rpc_app->last_id = request->command_id; - rpc_app->last_data = strdup(request->content.app_load_file_request.path); - rpc_app->app_callback(RpcAppEventLoadFile, rpc_app->app_context); + + const RpcAppSystemEvent event = { + .type = RpcAppEventTypeLoadFile, + .data = + { + .type = RpcAppSystemEventDataTypeString, + .string = request->content.app_load_file_request.path, + }, + }; + + rpc_system_app_error_reset(rpc_app); + rpc_system_app_set_last_command(rpc_app, request->command_id, &event); + + rpc_app->callback(&event, rpc_app->callback_context); + } else { - status = PB_CommandStatus_ERROR_APP_NOT_RUNNING; - FURI_LOG_E( - TAG, "LoadFile: APP_NOT_RUNNING, id %lu, status: %d", request->command_id, status); - rpc_send_and_release_empty(session, request->command_id, status); + rpc_system_app_send_error_response( + rpc_app, request->command_id, PB_CommandStatus_ERROR_APP_NOT_RUNNING, "LoadFile"); } } static void rpc_system_app_button_press(const PB_Main* request, void* context) { furi_assert(request); - furi_assert(context); - furi_assert(request->which_content == PB_Main_app_button_press_request_tag); + RpcAppSystem* rpc_app = context; - rpc_system_app_error_reset(rpc_app); - RpcSession* session = rpc_app->session; - furi_assert(session); + furi_assert(rpc_app); - PB_CommandStatus status; - if(rpc_app->app_callback) { + if(rpc_app->callback) { FURI_LOG_D(TAG, "ButtonPress"); - furi_assert(!rpc_app->last_id); - furi_assert(!rpc_app->last_data); - rpc_app->last_id = request->command_id; - rpc_app->last_data = strdup(request->content.app_button_press_request.args); - rpc_app->app_callback(RpcAppEventButtonPress, rpc_app->app_context); + + RpcAppSystemEvent event; + event.type = RpcAppEventTypeButtonPress; + + if(strlen(request->content.app_button_press_request.args) != 0) { + event.data.type = RpcAppSystemEventDataTypeString; + event.data.string = request->content.app_button_press_request.args; + } else { + event.data.type = RpcAppSystemEventDataTypeInt32; + event.data.i32 = request->content.app_button_press_request.index; + } + + rpc_system_app_error_reset(rpc_app); + rpc_system_app_set_last_command(rpc_app, request->command_id, &event); + + rpc_app->callback(&event, rpc_app->callback_context); + } else { - status = PB_CommandStatus_ERROR_APP_NOT_RUNNING; - FURI_LOG_E( - TAG, "ButtonPress: APP_NOT_RUNNING, id %lu, status: %d", request->command_id, status); - rpc_send_and_release_empty(session, request->command_id, status); + rpc_system_app_send_error_response( + rpc_app, request->command_id, PB_CommandStatus_ERROR_APP_NOT_RUNNING, "ButtonPress"); } } static void rpc_system_app_button_release(const PB_Main* request, void* context) { furi_assert(request); furi_assert(request->which_content == PB_Main_app_button_release_request_tag); - furi_assert(context); RpcAppSystem* rpc_app = context; - rpc_system_app_error_reset(rpc_app); - RpcSession* session = rpc_app->session; - furi_assert(session); + furi_assert(rpc_app); - PB_CommandStatus status; - if(rpc_app->app_callback) { + if(rpc_app->callback) { FURI_LOG_D(TAG, "ButtonRelease"); - furi_assert(!rpc_app->last_id); - furi_assert(!rpc_app->last_data); - rpc_app->last_id = request->command_id; - rpc_app->app_callback(RpcAppEventButtonRelease, rpc_app->app_context); + + const RpcAppSystemEvent event = { + .type = RpcAppEventTypeButtonRelease, + .data = + { + .type = RpcAppSystemEventDataTypeNone, + {0}, + }, + }; + + rpc_system_app_error_reset(rpc_app); + rpc_system_app_set_last_command(rpc_app, request->command_id, &event); + + rpc_app->callback(&event, rpc_app->callback_context); + } else { - status = PB_CommandStatus_ERROR_APP_NOT_RUNNING; - FURI_LOG_E( - TAG, "ButtonRelease: APP_NOT_RUNNING, id %lu, status: %d", request->command_id, status); - rpc_send_and_release_empty(session, request->command_id, status); + rpc_system_app_send_error_response( + rpc_app, request->command_id, PB_CommandStatus_ERROR_APP_NOT_RUNNING, "ButtonRelease"); } } static void rpc_system_app_get_error_process(const PB_Main* request, void* context) { furi_assert(request); furi_assert(request->which_content == PB_Main_app_get_error_request_tag); - furi_assert(context); RpcAppSystem* rpc_app = context; - RpcSession* session = rpc_app->session; - furi_assert(session); + furi_assert(rpc_app); + + PB_Main* response = malloc(sizeof(PB_Main)); - rpc_app->error_msg->command_id = request->command_id; + response->command_id = request->command_id; + response->which_content = PB_Main_app_get_error_response_tag; + response->content.app_get_error_response.code = rpc_app->error_code; + response->content.app_get_error_response.text = rpc_app->error_text; FURI_LOG_D(TAG, "GetError"); - rpc_send(session, rpc_app->error_msg); + rpc_send(rpc_app->session, response); + + free(response); } static void rpc_system_app_data_exchange_process(const PB_Main* request, void* context) { furi_assert(request); furi_assert(request->which_content == PB_Main_app_data_exchange_request_tag); - furi_assert(context); RpcAppSystem* rpc_app = context; - rpc_system_app_error_reset(rpc_app); - RpcSession* session = rpc_app->session; - furi_assert(session); + furi_assert(rpc_app); - PB_CommandStatus command_status; - pb_bytes_array_t* data = request->content.app_data_exchange_request.data; + if(rpc_app->callback) { + FURI_LOG_D(TAG, "DataExchange"); - if(rpc_app->data_exchange_callback) { - uint8_t* data_bytes = NULL; - size_t data_size = 0; - if(data) { - data_bytes = data->bytes; - data_size = data->size; - } - rpc_app->data_exchange_callback(data_bytes, data_size, rpc_app->data_exchange_context); - command_status = PB_CommandStatus_OK; + const pb_bytes_array_t* data = request->content.app_data_exchange_request.data; + + const RpcAppSystemEvent event = { + .type = RpcAppEventTypeDataExchange, + .data = + { + .type = RpcAppSystemEventDataTypeBytes, + .bytes = + { + .ptr = data ? data->bytes : NULL, + .size = data ? data->size : 0, + }, + }, + }; + + rpc_system_app_error_reset(rpc_app); + rpc_system_app_set_last_command(rpc_app, request->command_id, &event); + + rpc_app->callback(&event, rpc_app->callback_context); } else { - command_status = PB_CommandStatus_ERROR_APP_CMD_ERROR; + rpc_system_app_send_error_response( + rpc_app, request->command_id, PB_CommandStatus_ERROR_APP_NOT_RUNNING, "DataExchange"); } - - FURI_LOG_D(TAG, "DataExchange"); - rpc_send_and_release_empty(session, request->command_id, command_status); } void rpc_system_app_send_started(RpcAppSystem* rpc_app) { furi_assert(rpc_app); - RpcSession* session = rpc_app->session; - furi_assert(session); - - rpc_app->state_msg->content.app_state_response.state = PB_App_AppState_APP_STARTED; - - FURI_LOG_D(TAG, "SendStarted"); - rpc_send(session, rpc_app->state_msg); + rpc_system_app_send_state_response(rpc_app, PB_App_AppState_APP_STARTED, "SendStarted"); } void rpc_system_app_send_exited(RpcAppSystem* rpc_app) { furi_assert(rpc_app); - RpcSession* session = rpc_app->session; - furi_assert(session); - - rpc_app->state_msg->content.app_state_response.state = PB_App_AppState_APP_CLOSED; - - FURI_LOG_D(TAG, "SendExit"); - rpc_send(session, rpc_app->state_msg); -} - -const char* rpc_system_app_get_data(RpcAppSystem* rpc_app) { - furi_assert(rpc_app); - furi_assert(rpc_app->last_data); - return rpc_app->last_data; + rpc_system_app_send_state_response(rpc_app, PB_App_AppState_APP_CLOSED, "SendExit"); } -void rpc_system_app_confirm(RpcAppSystem* rpc_app, RpcAppSystemEvent event, bool result) { +void rpc_system_app_confirm(RpcAppSystem* rpc_app, bool result) { furi_assert(rpc_app); - RpcSession* session = rpc_app->session; - furi_assert(session); - furi_assert(rpc_app->last_id); - - PB_CommandStatus status = result ? PB_CommandStatus_OK : PB_CommandStatus_ERROR_APP_CMD_ERROR; - - uint32_t last_id = 0; - switch(event) { - case RpcAppEventAppExit: - case RpcAppEventLoadFile: - case RpcAppEventButtonPress: - case RpcAppEventButtonRelease: - last_id = rpc_app->last_id; - rpc_app->last_id = 0; - if(rpc_app->last_data) { - free(rpc_app->last_data); - rpc_app->last_data = NULL; - } - FURI_LOG_D(TAG, "AppConfirm: event %d last_id %lu status %d", event, last_id, status); - rpc_send_and_release_empty(session, last_id, status); - break; - default: - furi_crash("RPC App state programming Error"); - break; - } + furi_assert(rpc_app->last_command_id != 0); + /* Ensure that only commands of these types can be confirmed */ + furi_assert( + rpc_app->last_event_type == RpcAppEventTypeAppExit || + rpc_app->last_event_type == RpcAppEventTypeLoadFile || + rpc_app->last_event_type == RpcAppEventTypeButtonPress || + rpc_app->last_event_type == RpcAppEventTypeButtonRelease || + rpc_app->last_event_type == RpcAppEventTypeDataExchange); + + const uint32_t last_command_id = rpc_app->last_command_id; + const RpcAppSystemEventType last_event_type = rpc_app->last_event_type; + + rpc_app->last_command_id = 0; + rpc_app->last_event_type = RpcAppEventTypeInvalid; + + const PB_CommandStatus status = result ? PB_CommandStatus_OK : + PB_CommandStatus_ERROR_APP_CMD_ERROR; + FURI_LOG_D( + TAG, + "AppConfirm: event %d last_id %lu status %d", + last_event_type, + last_command_id, + status); + + rpc_send_and_release_empty(rpc_app->session, last_command_id, status); } void rpc_system_app_set_callback(RpcAppSystem* rpc_app, RpcAppSystemCallback callback, void* ctx) { furi_assert(rpc_app); - rpc_app->app_callback = callback; - rpc_app->app_context = ctx; + rpc_app->callback = callback; + rpc_app->callback_context = ctx; } void rpc_system_app_set_error_code(RpcAppSystem* rpc_app, uint32_t error_code) { furi_assert(rpc_app); - PB_App_GetErrorResponse* content = &rpc_app->error_msg->content.app_get_error_response; - content->code = error_code; + rpc_app->error_code = error_code; } void rpc_system_app_set_error_text(RpcAppSystem* rpc_app, const char* error_text) { furi_assert(rpc_app); - PB_App_GetErrorResponse* content = &rpc_app->error_msg->content.app_get_error_response; - if(content->text) { - free(content->text); + if(rpc_app->error_text) { + free(rpc_app->error_text); } - content->text = error_text ? strdup(error_text) : NULL; + rpc_app->error_text = error_text ? strdup(error_text) : NULL; } void rpc_system_app_error_reset(RpcAppSystem* rpc_app) { @@ -340,29 +381,13 @@ void rpc_system_app_error_reset(RpcAppSystem* rpc_app) { rpc_system_app_set_error_text(rpc_app, NULL); } -void rpc_system_app_set_data_exchange_callback( - RpcAppSystem* rpc_app, - RpcAppSystemDataExchangeCallback callback, - void* ctx) { - furi_assert(rpc_app); - - rpc_app->data_exchange_callback = callback; - rpc_app->data_exchange_context = ctx; -} - void rpc_system_app_exchange_data(RpcAppSystem* rpc_app, const uint8_t* data, size_t data_size) { furi_assert(rpc_app); - RpcSession* session = rpc_app->session; - furi_assert(session); - PB_Main message = { - .command_id = 0, - .command_status = PB_CommandStatus_OK, - .has_next = false, - .which_content = PB_Main_app_data_exchange_request_tag, - }; + PB_Main* request = malloc(sizeof(PB_Main)); - PB_App_DataExchangeRequest* content = &message.content.app_data_exchange_request; + request->which_content = PB_Main_app_data_exchange_request_tag; + PB_App_DataExchangeRequest* content = &request->content.app_data_exchange_request; if(data && data_size) { content->data = malloc(PB_BYTES_ARRAY_T_ALLOCSIZE(data_size)); @@ -372,7 +397,9 @@ void rpc_system_app_exchange_data(RpcAppSystem* rpc_app, const uint8_t* data, si content->data = NULL; } - rpc_send_and_release(session, &message); + rpc_send_and_release(rpc_app->session, request); + + free(request); } void* rpc_system_app_alloc(RpcSession* session) { @@ -381,18 +408,6 @@ void* rpc_system_app_alloc(RpcSession* session) { RpcAppSystem* rpc_app = malloc(sizeof(RpcAppSystem)); rpc_app->session = session; - // App exit message - rpc_app->state_msg = malloc(sizeof(PB_Main)); - rpc_app->state_msg->which_content = PB_Main_app_state_response_tag; - rpc_app->state_msg->command_status = PB_CommandStatus_OK; - - // App error message - rpc_app->error_msg = malloc(sizeof(PB_Main)); - rpc_app->error_msg->which_content = PB_Main_app_get_error_response_tag; - rpc_app->error_msg->command_status = PB_CommandStatus_OK; - rpc_app->error_msg->content.app_get_error_response.code = 0; - rpc_app->error_msg->content.app_get_error_response.text = NULL; - RpcHandler rpc_handler = { .message_handler = NULL, .decode_submessage = NULL, @@ -429,24 +444,24 @@ void* rpc_system_app_alloc(RpcSession* session) { void rpc_system_app_free(void* context) { RpcAppSystem* rpc_app = context; furi_assert(rpc_app); - RpcSession* session = rpc_app->session; - furi_assert(session); - - if(rpc_app->app_callback) { - rpc_app->app_callback(RpcAppEventSessionClose, rpc_app->app_context); + furi_assert(rpc_app->session); + + if(rpc_app->callback) { + const RpcAppSystemEvent event = { + .type = RpcAppEventTypeSessionClose, + .data = + { + .type = RpcAppSystemEventDataTypeNone, + {0}, + }, + }; + + rpc_app->callback(&event, rpc_app->callback_context); } - while(rpc_app->app_callback) { + while(rpc_app->callback) { furi_delay_tick(1); } - furi_assert(!rpc_app->data_exchange_callback); - - if(rpc_app->last_data) free(rpc_app->last_data); - - pb_release(&PB_Main_msg, rpc_app->error_msg); - - free(rpc_app->error_msg); - free(rpc_app->state_msg); free(rpc_app); } diff --git a/applications/services/rpc/rpc_app.h b/applications/services/rpc/rpc_app.h index d5c6fee9629..4ee5a24d379 100644 --- a/applications/services/rpc/rpc_app.h +++ b/applications/services/rpc/rpc_app.h @@ -1,45 +1,213 @@ +/** + * @file rpc_app.h + * @brief Application RPC subsystem interface. + * + * The application RPC subsystem provides facilities for interacting with applications, + * such as starting/stopping, passing parameters, sending commands and exchanging arbitrary data. + * + * All commands are handled asynchronously via a user-settable callback. + * + * For a complete description of message types handled in this subsystem, + * see https://github.com/flipperdevices/flipperzero-protobuf/blob/dev/application.proto + */ #pragma once + #include "rpc.h" #ifdef __cplusplus extern "C" { #endif +/** + * @brief Enumeration of possible event data types. + */ typedef enum { - RpcAppEventSessionClose, - RpcAppEventAppExit, - RpcAppEventLoadFile, - RpcAppEventButtonPress, - RpcAppEventButtonRelease, + RpcAppSystemEventDataTypeNone, /**< No data is provided by the event. */ + RpcAppSystemEventDataTypeString, /**< Event data contains a zero-terminated string. */ + RpcAppSystemEventDataTypeInt32, /**< Event data contains a signed 32-bit integer. */ + RpcAppSystemEventDataTypeBytes, /**< Event data contains zero or more bytes. */ +} RpcAppSystemEventDataType; + +/** + * @brief Event data structure, containing the type and associated data. + * + * All below fields except for type are valid only if the respective type is set. + */ +typedef struct { + RpcAppSystemEventDataType + type; /**< Type of the data. The meaning of other fields depends on this one. */ + union { + const char* string; /**< Pointer to a zero-terminated character string. */ + int32_t i32; /**< Signed 32-bit integer value. */ + struct { + const uint8_t* ptr; /**< Pointer to the byte array data. */ + size_t size; /**< Size of the byte array, in bytes. */ + } bytes; /**< Byte array of arbitrary length. */ + }; +} RpcAppSystemEventData; + +/** + * @brief Enumeration of possible event types. + */ +typedef enum { + /** + * @brief Denotes an invalid state. + * + * An event of this type shall never be passed into the callback. + */ + RpcAppEventTypeInvalid, + /** + * @brief The client side has closed the session. + * + * After receiving this event, the RPC context is no more valid. + */ + RpcAppEventTypeSessionClose, + /** + * @brief The client has requested the application to exit. + * + * The application must exit after receiving this command. + */ + RpcAppEventTypeAppExit, + /** + * @brief The client has requested the application to load a file. + * + * This command's meaning is application-specific, i.e. the application might or + * might not require additional commands after loading a file to do anything useful. + */ + RpcAppEventTypeLoadFile, + /** + * @brief The client has informed the application that a button has been pressed. + * + * This command's meaning is application-specific, e.g. to select a part of the + * previously loaded file or to invoke a particular function within the application. + */ + RpcAppEventTypeButtonPress, + /** + * @brief The client has informed the application that a button has been released. + * + * This command's meaning is application-specific, e.g. to cease + * all activities to be conducted while a button is being pressed. + */ + RpcAppEventTypeButtonRelease, + /** + * @brief The client has sent a byte array of arbitrary size. + * + * This command's purpose is bi-directional exchange of arbitrary raw data. + * Useful for implementing higher-level protocols while using the RPC as a transport layer. + */ + RpcAppEventTypeDataExchange, +} RpcAppSystemEventType; + +/** + * @brief RPC application subsystem event structure. + */ +typedef struct { + RpcAppSystemEventType type; /**< Type of the event. */ + RpcAppSystemEventData data; /**< Data associated with the event. */ } RpcAppSystemEvent; -typedef void (*RpcAppSystemCallback)(RpcAppSystemEvent event, void* context); -typedef void ( - *RpcAppSystemDataExchangeCallback)(const uint8_t* data, size_t data_size, void* context); +/** + * @brief Callback function type. + * + * A function of this type must be passed to rpc_system_app_set_callback() by the user code. + * + * @warning The event pointer is valid ONLY inside the callback function. + * + * @param[in] event pointer to the event object. Valid only inside the callback function. + * @param[in,out] context pointer to the user-defined context object. + */ +typedef void (*RpcAppSystemCallback)(const RpcAppSystemEvent* event, void* context); +/** + * @brief RPC application subsystem opaque type declaration. + */ typedef struct RpcAppSystem RpcAppSystem; -void rpc_system_app_set_callback(RpcAppSystem* rpc_app, RpcAppSystemCallback callback, void* ctx); +/** + * @brief Set the callback function for use by an RpcAppSystem instance. + * + * @param[in,out] rpc_app pointer to the instance to be configured. + * @param[in] callback pointer to the function to be called upon message reception. + * @param[in,out] context pointer to the user-defined context object. Will be passed to the callback. + */ +void rpc_system_app_set_callback( + RpcAppSystem* rpc_app, + RpcAppSystemCallback callback, + void* context); +/** + * @brief Send a notification that an RpcAppSystem instance has been started and is ready. + * + * Call this function once right after acquiring an RPC context and setting the callback. + * + * @param[in,out] rpc_app pointer to the instance to be used. + */ void rpc_system_app_send_started(RpcAppSystem* rpc_app); +/** + * @brief Send a notification that the application using an RpcAppSystem instance is about to exit. + * + * Call this function when the application is about to exit (usually in the *_free() function). + * + * @param[in,out] rpc_app pointer to the instance to be used. + */ void rpc_system_app_send_exited(RpcAppSystem* rpc_app); -const char* rpc_system_app_get_data(RpcAppSystem* rpc_app); - -void rpc_system_app_confirm(RpcAppSystem* rpc_app, RpcAppSystemEvent event, bool result); +/** + * @brief Send a confirmation that the application using an RpcAppSystem instance has handled the event. + * + * An explicit confirmation is required for the following event types: + * - RpcAppEventTypeAppExit + * - RpcAppEventTypeLoadFile + * - RpcAppEventTypeButtonPress + * - RpcAppEventTypeButtonRelease + * - RpcAppEventTypeDataExchange + * + * Not confirming these events will result in a client-side timeout. + * + * @param[in,out] rpc_app pointer to the instance to be used. + * @param[in] result whether the command was successfully handled or not (true for success). + */ +void rpc_system_app_confirm(RpcAppSystem* rpc_app, bool result); +/** + * @brief Set the error code stored in an RpcAppSystem instance. + * + * The error code can be retrieved by the client at any time by using the GetError request. + * The error code value has no meaning within the subsystem, i.e. it is only passed through to the client. + * + * @param[in,out] rpc_app pointer to the instance to be modified. + * @param[in] error_code arbitrary error code to be set. + */ void rpc_system_app_set_error_code(RpcAppSystem* rpc_app, uint32_t error_code); +/** + * @brief Set the error text stored in an RpcAppSystem instance. + * + * The error text can be retrieved by the client at any time by using the GetError request. + * The text has no meaning within the subsystem, i.e. it is only passed through to the client. + * + * @param[in,out] rpc_app pointer to the instance to be modified. + * @param[in] error_text Pointer to a zero-terminated string containing the error text. + */ void rpc_system_app_set_error_text(RpcAppSystem* rpc_app, const char* error_text); +/** + * @brief Reset the error code and text stored in an RpcAppSystem instance. + * + * Resets the error code to 0 and error text to "" (empty string). + * + * @param[in,out] rpc_app pointer to the instance to be reset. + */ void rpc_system_app_error_reset(RpcAppSystem* rpc_app); -void rpc_system_app_set_data_exchange_callback( - RpcAppSystem* rpc_app, - RpcAppSystemDataExchangeCallback callback, - void* ctx); - +/** + * @brief Send a byte array of arbitrary data to the client using an RpcAppSystem instance. + * + * @param[in,out] rpc_app pointer to the instance to be used. + * @param[in] data pointer to the data buffer to be sent. + * @param[in] data_size size of the data buffer, in bytes. + */ void rpc_system_app_exchange_data(RpcAppSystem* rpc_app, const uint8_t* data, size_t data_size); #ifdef __cplusplus diff --git a/assets/protobuf b/assets/protobuf index 327163d5867..23ad19a7566 160000 --- a/assets/protobuf +++ b/assets/protobuf @@ -1 +1 @@ -Subproject commit 327163d5867c7aa3051334c93ced718d15bfe4da +Subproject commit 23ad19a756649ed9f6677b598e5361c5cce6847b diff --git a/scripts/fbt_tools/fbt_assets.py b/scripts/fbt_tools/fbt_assets.py index 5c32ae1a983..492a66b6654 100644 --- a/scripts/fbt_tools/fbt_assets.py +++ b/scripts/fbt_tools/fbt_assets.py @@ -80,22 +80,35 @@ def __invoke_git(args, source_dir): def _proto_ver_generator(target, source, env): target_file = target[0] src_dir = source[0].dir.abspath - try: - __invoke_git( - ["fetch", "--tags"], - source_dir=src_dir, - ) - except (subprocess.CalledProcessError, EnvironmentError): - # Not great, not terrible - print(fg.boldred("Git: fetch failed")) - - try: - git_describe = __invoke_git( - ["describe", "--tags", "--abbrev=0"], - source_dir=src_dir, - ) - except (subprocess.CalledProcessError, EnvironmentError): - raise StopError("Git: describe failed") + + def fetch(unshallow=False): + git_args = ["fetch", "--tags"] + if unshallow: + git_args.append("--unshallow") + + try: + __invoke_git(git_args, source_dir=src_dir) + except (subprocess.CalledProcessError, EnvironmentError): + # Not great, not terrible + print(fg.boldred("Git: fetch failed")) + + def describe(): + try: + return __invoke_git( + ["describe", "--tags", "--abbrev=0"], + source_dir=src_dir, + ) + except (subprocess.CalledProcessError, EnvironmentError): + return None + + fetch() + git_describe = describe() + if not git_describe: + fetch(unshallow=True) + git_describe = describe() + + if not git_describe: + raise StopError("Failed to process git tags for protobuf versioning") git_major, git_minor = git_describe.split(".") version_file_data = ( diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 5f8e836c007..0eadd799d47 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1950,14 +1950,12 @@ Function,+,rpc_session_set_close_callback,void,"RpcSession*, RpcSessionClosedCal Function,+,rpc_session_set_context,void,"RpcSession*, void*" Function,+,rpc_session_set_send_bytes_callback,void,"RpcSession*, RpcSendBytesCallback" Function,+,rpc_session_set_terminated_callback,void,"RpcSession*, RpcSessionTerminatedCallback" -Function,+,rpc_system_app_confirm,void,"RpcAppSystem*, RpcAppSystemEvent, _Bool" +Function,+,rpc_system_app_confirm,void,"RpcAppSystem*, _Bool" Function,+,rpc_system_app_error_reset,void,RpcAppSystem* Function,+,rpc_system_app_exchange_data,void,"RpcAppSystem*, const uint8_t*, size_t" -Function,+,rpc_system_app_get_data,const char*,RpcAppSystem* Function,+,rpc_system_app_send_exited,void,RpcAppSystem* Function,+,rpc_system_app_send_started,void,RpcAppSystem* Function,+,rpc_system_app_set_callback,void,"RpcAppSystem*, RpcAppSystemCallback, void*" -Function,+,rpc_system_app_set_data_exchange_callback,void,"RpcAppSystem*, RpcAppSystemDataExchangeCallback, void*" Function,+,rpc_system_app_set_error_code,void,"RpcAppSystem*, uint32_t" Function,+,rpc_system_app_set_error_text,void,"RpcAppSystem*, const char*" Function,-,rpmatch,int,const char* diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 57adf96aeca..98345319536 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -2493,14 +2493,12 @@ Function,+,rpc_session_set_close_callback,void,"RpcSession*, RpcSessionClosedCal Function,+,rpc_session_set_context,void,"RpcSession*, void*" Function,+,rpc_session_set_send_bytes_callback,void,"RpcSession*, RpcSendBytesCallback" Function,+,rpc_session_set_terminated_callback,void,"RpcSession*, RpcSessionTerminatedCallback" -Function,+,rpc_system_app_confirm,void,"RpcAppSystem*, RpcAppSystemEvent, _Bool" +Function,+,rpc_system_app_confirm,void,"RpcAppSystem*, _Bool" Function,+,rpc_system_app_error_reset,void,RpcAppSystem* Function,+,rpc_system_app_exchange_data,void,"RpcAppSystem*, const uint8_t*, size_t" -Function,+,rpc_system_app_get_data,const char*,RpcAppSystem* Function,+,rpc_system_app_send_exited,void,RpcAppSystem* Function,+,rpc_system_app_send_started,void,RpcAppSystem* Function,+,rpc_system_app_set_callback,void,"RpcAppSystem*, RpcAppSystemCallback, void*" -Function,+,rpc_system_app_set_data_exchange_callback,void,"RpcAppSystem*, RpcAppSystemDataExchangeCallback, void*" Function,+,rpc_system_app_set_error_code,void,"RpcAppSystem*, uint32_t" Function,+,rpc_system_app_set_error_text,void,"RpcAppSystem*, const char*" Function,-,rpmatch,int,const char* From dc246ddb0958b17707b4dad9e8e1609f3517d662 Mon Sep 17 00:00:00 2001 From: Tobias Jost Date: Wed, 15 Nov 2023 08:39:29 +0100 Subject: [PATCH 804/824] Fix limited_credit_value having wrong value in mf_desfire_file_settings_parse (#3204) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: hedger Co-authored-by: あく --- lib/nfc/protocols/mf_desfire/mf_desfire_i.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_i.c b/lib/nfc/protocols/mf_desfire/mf_desfire_i.c index 30313ae2bcf..129dcdf5e1d 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_i.c +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_i.c @@ -201,7 +201,7 @@ bool mf_desfire_file_settings_parse(MfDesfireFileSettings* data, const BitBuffer data->value.lo_limit = layout.value.lo_limit; data->value.hi_limit = layout.value.hi_limit; - data->value.limited_credit_value = layout.value.hi_limit; + data->value.limited_credit_value = layout.value.limited_credit_value; data->value.limited_credit_enabled = layout.value.limited_credit_enabled; } else if( From d0b9a3a4ae7abbb1a0e97616226d2b77d86f08e7 Mon Sep 17 00:00:00 2001 From: RebornedBrain <138568282+RebornedBrain@users.noreply.github.com> Date: Wed, 15 Nov 2023 11:02:35 +0300 Subject: [PATCH 805/824] [NFC] MF Ultralight no pwd polling adjustment (#3207) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Listener log level changed to Trace * Show pages count without pwd pages in case of no auth success * Fixed unit tests Co-authored-by: gornekich Co-authored-by: あく --- applications/debug/unit_tests/nfc/nfc_test.c | 19 +++++++++--- .../mf_ultralight/mf_ultralight_listener.c | 30 +++++++++---------- .../mf_ultralight/mf_ultralight_poller.c | 4 +++ 3 files changed, 34 insertions(+), 19 deletions(-) diff --git a/applications/debug/unit_tests/nfc/nfc_test.c b/applications/debug/unit_tests/nfc/nfc_test.c index 2d647f8ef56..ce5a9cb9cc5 100644 --- a/applications/debug/unit_tests/nfc/nfc_test.c +++ b/applications/debug/unit_tests/nfc/nfc_test.c @@ -203,10 +203,21 @@ static void mf_ultralight_reader_test(const char* path) { NfcDevice* nfc_device = nfc_device_alloc(); mu_assert(nfc_device_load(nfc_device, path), "nfc_device_load() failed\r\n"); - NfcListener* mfu_listener = nfc_listener_alloc( - listener, - NfcProtocolMfUltralight, - nfc_device_get_data(nfc_device, NfcProtocolMfUltralight)); + MfUltralightData* data = + (MfUltralightData*)nfc_device_get_data(nfc_device, NfcProtocolMfUltralight); + + uint32_t features = mf_ultralight_get_feature_support_set(data->type); + bool pwd_supported = + mf_ultralight_support_feature(features, MfUltralightFeatureSupportPasswordAuth); + uint8_t pwd_num = mf_ultralight_get_pwd_page_num(data->type); + const uint8_t zero_pwd[4] = {0, 0, 0, 0}; + + if(pwd_supported && !memcmp(data->page[pwd_num].data, zero_pwd, sizeof(zero_pwd))) { + data->pages_read -= 2; + } + + NfcListener* mfu_listener = nfc_listener_alloc(listener, NfcProtocolMfUltralight, data); + nfc_listener_start(mfu_listener, NULL, NULL); MfUltralightData* mfu_data = mf_ultralight_alloc(); diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_listener.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight_listener.c index 70c6f6de2a8..5bef2a354c9 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_listener.c +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_listener.c @@ -122,7 +122,7 @@ static MfUltralightCommand uint16_t pages_total = instance->data->pages_total; MfUltralightCommand command = MfUltralightCommandNotProcessedNAK; - FURI_LOG_D(TAG, "CMD_READ: %d", start_page); + FURI_LOG_T(TAG, "CMD_READ: %d", start_page); do { bool do_i2c_check = mf_ultralight_is_i2c_tag(instance->data->type); @@ -154,7 +154,7 @@ static MfUltralightCommand static MfUltralightCommand mf_ultralight_listener_fast_read_handler(MfUltralightListener* instance, BitBuffer* buffer) { MfUltralightCommand command = MfUltralightCommandNotProcessedSilent; - FURI_LOG_D(TAG, "CMD_FAST_READ"); + FURI_LOG_T(TAG, "CMD_FAST_READ"); do { if(!mf_ultralight_support_feature(instance->features, MfUltralightFeatureSupportFastRead)) @@ -206,7 +206,7 @@ static MfUltralightCommand uint16_t pages_total = instance->data->pages_total; MfUltralightCommand command = MfUltralightCommandNotProcessedNAK; - FURI_LOG_D(TAG, "CMD_WRITE"); + FURI_LOG_T(TAG, "CMD_WRITE"); do { bool do_i2c_check = mf_ultralight_is_i2c_tag(instance->data->type); @@ -235,7 +235,7 @@ static MfUltralightCommand static MfUltralightCommand mf_ultralight_listener_fast_write_handler(MfUltralightListener* instance, BitBuffer* buffer) { MfUltralightCommand command = MfUltralightCommandNotProcessedSilent; - FURI_LOG_D(TAG, "CMD_FAST_WRITE"); + FURI_LOG_T(TAG, "CMD_FAST_WRITE"); do { if(!mf_ultralight_support_feature(instance->features, MfUltralightFeatureSupportFastWrite)) @@ -261,7 +261,7 @@ static MfUltralightCommand UNUSED(buffer); MfUltralightCommand command = MfUltralightCommandNotProcessedSilent; - FURI_LOG_D(TAG, "CMD_GET_VERSION"); + FURI_LOG_T(TAG, "CMD_GET_VERSION"); if(mf_ultralight_support_feature(instance->features, MfUltralightFeatureSupportReadVersion)) { bit_buffer_copy_bytes( @@ -280,7 +280,7 @@ static MfUltralightCommand mf_ultralight_listener_read_signature_handler( UNUSED(buffer); MfUltralightCommand command = MfUltralightCommandNotProcessedSilent; - FURI_LOG_D(TAG, "CMD_READ_SIG"); + FURI_LOG_T(TAG, "CMD_READ_SIG"); if(mf_ultralight_support_feature(instance->features, MfUltralightFeatureSupportReadSignature)) { bit_buffer_copy_bytes( @@ -297,7 +297,7 @@ static MfUltralightCommand mf_ultralight_listener_read_counter_handler(MfUltralightListener* instance, BitBuffer* buffer) { MfUltralightCommand command = MfUltralightCommandNotProcessedNAK; - FURI_LOG_D(TAG, "CMD_READ_CNT"); + FURI_LOG_T(TAG, "CMD_READ_CNT"); do { uint8_t counter_num = bit_buffer_get_byte(buffer, 1); @@ -338,7 +338,7 @@ static MfUltralightCommand mf_ultralight_listener_increase_counter_handler( BitBuffer* buffer) { MfUltralightCommand command = MfUltralightCommandNotProcessedNAK; - FURI_LOG_D(TAG, "CMD_INCR_CNT"); + FURI_LOG_T(TAG, "CMD_INCR_CNT"); do { if(!mf_ultralight_support_feature( @@ -374,7 +374,7 @@ static MfUltralightCommand mf_ultralight_listener_check_tearing_handler( BitBuffer* buffer) { MfUltralightCommand command = MfUltralightCommandNotProcessedNAK; - FURI_LOG_D(TAG, "CMD_CHECK_TEARING"); + FURI_LOG_T(TAG, "CMD_CHECK_TEARING"); do { uint8_t tearing_flag_num = bit_buffer_get_byte(buffer, 1); @@ -410,7 +410,7 @@ static MfUltralightCommand MfUltralightCommand command = MfUltralightCommandNotProcessedSilent; UNUSED(instance); UNUSED(buffer); - FURI_LOG_D(TAG, "CMD_VCSL"); + FURI_LOG_T(TAG, "CMD_VCSL"); do { if(!mf_ultralight_support_feature(instance->features, MfUltralightFeatureSupportVcsl)) break; @@ -432,7 +432,7 @@ static MfUltralightCommand mf_ultralight_listener_auth_handler(MfUltralightListener* instance, BitBuffer* buffer) { MfUltralightCommand command = MfUltralightCommandNotProcessedNAK; - FURI_LOG_D(TAG, "CMD_AUTH"); + FURI_LOG_T(TAG, "CMD_AUTH"); do { if(!mf_ultralight_support_feature( @@ -474,7 +474,7 @@ static MfUltralightCommand static MfUltralightCommand mf_ultralight_comp_write_handler_p2(MfUltralightListener* instance, BitBuffer* buffer) { MfUltralightCommand command = MfUltralightCommandNotProcessedNAK; - FURI_LOG_D(TAG, "CMD_CM_WR_2"); + FURI_LOG_T(TAG, "CMD_CM_WR_2"); do { if(bit_buffer_get_size_bytes(buffer) != 16) break; @@ -492,7 +492,7 @@ static MfUltralightCommand mf_ultralight_comp_write_handler_p1(MfUltralightListener* instance, BitBuffer* buffer) { MfUltralightCommand command = MfUltralightCommandNotProcessedSilent; - FURI_LOG_D(TAG, "CMD_CM_WR_1"); + FURI_LOG_T(TAG, "CMD_CM_WR_1"); do { if(!mf_ultralight_support_feature( @@ -532,7 +532,7 @@ static MfUltralightCommand MfUltralightCommand command = MfUltralightCommandNotProcessedNAK; UNUSED(instance); UNUSED(buffer); - FURI_LOG_D(TAG, "CMD_SEC_SEL_2"); + FURI_LOG_T(TAG, "CMD_SEC_SEL_2"); do { if(bit_buffer_get_size_bytes(buffer) != 4) break; @@ -550,7 +550,7 @@ static MfUltralightCommand mf_ultralight_sector_select_handler_p1(MfUltralightListener* instance, BitBuffer* buffer) { MfUltralightCommand command = MfUltralightCommandNotProcessedNAK; UNUSED(buffer); - FURI_LOG_D(TAG, "CMD_SEC_SEL_1"); + FURI_LOG_T(TAG, "CMD_SEC_SEL_1"); do { if(!mf_ultralight_support_feature( diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c index bf0ced38d0b..4ad7bc147d0 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c @@ -487,6 +487,7 @@ static NfcCommand mf_ultralight_poller_handler_try_default_pass(MfUltralightPoll sizeof(MfUltralightAuthPassword), config->password.data); config->pack = instance->auth_context.pack; + instance->auth_context.auth_success = true; } } @@ -496,6 +497,9 @@ static NfcCommand mf_ultralight_poller_handler_try_default_pass(MfUltralightPoll // original card config->auth0 = instance->pages_read; config->access.prot = true; + } else if(!instance->auth_context.auth_success) { + instance->pages_read -= 2; + instance->data->pages_read -= 2; } } while(false); From c00776ca2279a86a62c253cfd2c3a910dabd7795 Mon Sep 17 00:00:00 2001 From: gornekich Date: Wed, 15 Nov 2023 12:32:45 +0400 Subject: [PATCH 806/824] [FL-3666] NFC API improvements (#3214) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * drivers: expose st25r3916 driver API * nfc poller: add start with custom callback * mf classic: rework sync API with poller custom start * mf ultralight: rework sync API with poller custom start * iso14443_3a poller: remove unused col res state * nfc: rework nfc poller custom start * mf ultralight: rename sync API * mf classic: rename sync API * iso14443-3a: rename sync API * nfc: remove async prefix in internal functions * nfc: expose internal API * nfc: fix sync api include and docs * targets: fix f18 build * nfc: rework NfcGenericEventEx type * nfc poller: add documentation * iso14443-3a poller: add documentation * felica poller: add documentation * iso14443_3b poller: add documentation * so14443_4a poller: add documentation * iso14443_4b poller: add documentation * iso15693 poller: add documentation * slix poller: add documentation * mf desfire poller: add documentation * mf ultralight poller: fix API and add documentation * mf classic poller: add documentation Co-authored-by: あく --- applications/debug/unit_tests/nfc/nfc_test.c | 44 +-- .../nfc/plugins/supported_cards/plantain.c | 8 +- .../main/nfc/plugins/supported_cards/troika.c | 8 +- .../nfc/plugins/supported_cards/two_cities.c | 8 +- lib/drivers/SConscript | 2 + lib/nfc/SConscript | 6 +- lib/nfc/nfc_poller.c | 72 +++++ lib/nfc/nfc_poller.h | 37 +++ lib/nfc/protocols/felica/felica_poller.c | 4 +- lib/nfc/protocols/felica/felica_poller.h | 40 ++- lib/nfc/protocols/felica/felica_poller_i.c | 6 +- lib/nfc/protocols/felica/felica_poller_i.h | 4 +- .../iso14443_3a/iso14443_3a_poller.c | 4 +- .../iso14443_3a/iso14443_3a_poller.h | 115 ++++++- .../iso14443_3a/iso14443_3a_poller_i.c | 5 +- .../iso14443_3a/iso14443_3a_poller_i.h | 20 -- ...r_sync_api.c => iso14443_3a_poller_sync.c} | 4 +- ...r_sync_api.h => iso14443_3a_poller_sync.h} | 2 +- .../iso14443_3b/iso14443_3b_poller.c | 4 +- .../iso14443_3b/iso14443_3b_poller.h | 71 ++++- .../iso14443_3b/iso14443_3b_poller_i.c | 5 +- .../iso14443_3b/iso14443_3b_poller_i.h | 10 - lib/nfc/protocols/iso14443_4a/iso14443_4a.h | 16 +- lib/nfc/protocols/iso14443_4a/iso14443_4a_i.h | 16 - .../iso14443_4a/iso14443_4a_poller.c | 3 +- .../iso14443_4a/iso14443_4a_poller.h | 70 ++++- .../iso14443_4a/iso14443_4a_poller_i.c | 2 +- .../iso14443_4a/iso14443_4a_poller_i.h | 10 - .../iso14443_4b/iso14443_4b_poller.h | 56 +++- .../iso14443_4b/iso14443_4b_poller_i.h | 7 - .../protocols/iso15693_3/iso15693_3_poller.c | 4 +- .../protocols/iso15693_3/iso15693_3_poller.h | 132 +++++++- .../iso15693_3/iso15693_3_poller_i.c | 28 +- .../iso15693_3/iso15693_3_poller_i.h | 30 -- .../protocols/mf_classic/mf_classic_poller.c | 68 ++--- .../protocols/mf_classic/mf_classic_poller.h | 289 ++++++++++++++---- .../mf_classic/mf_classic_poller_i.c | 16 +- .../mf_classic/mf_classic_poller_i.h | 31 -- ...er_sync_api.c => mf_classic_poller_sync.c} | 69 ++--- ...er_sync_api.h => mf_classic_poller_sync.h} | 16 +- .../protocols/mf_desfire/mf_desfire_poller.c | 15 +- .../protocols/mf_desfire/mf_desfire_poller.h | 259 +++++++++++++++- .../mf_desfire/mf_desfire_poller_i.c | 62 ++-- .../mf_desfire/mf_desfire_poller_i.h | 72 ----- .../mf_ultralight/mf_ultralight_poller.c | 25 +- .../mf_ultralight/mf_ultralight_poller.h | 178 ++++++++++- .../mf_ultralight/mf_ultralight_poller_i.c | 28 +- .../mf_ultralight/mf_ultralight_poller_i.h | 40 --- ...sync_api.c => mf_ultralight_poller_sync.c} | 51 ++-- .../mf_ultralight/mf_ultralight_poller_sync.h | 34 +++ .../mf_ultralight_poller_sync_api.h | 30 -- lib/nfc/protocols/nfc_protocol.h | 10 +- lib/nfc/protocols/slix/slix_poller.c | 7 +- lib/nfc/protocols/slix/slix_poller.h | 68 ++++- lib/nfc/protocols/slix/slix_poller_i.c | 4 +- lib/nfc/protocols/slix/slix_poller_i.h | 10 - lib/nfc/protocols/st25tb/st25tb_poller.c | 4 +- lib/nfc/protocols/st25tb/st25tb_poller.h | 17 ++ lib/nfc/protocols/st25tb/st25tb_poller_i.c | 19 +- lib/nfc/protocols/st25tb/st25tb_poller_i.h | 17 -- targets/f18/api_symbols.csv | 27 +- targets/f7/api_symbols.csv | 116 +++++-- 62 files changed, 1712 insertions(+), 723 deletions(-) rename lib/nfc/protocols/iso14443_3a/{iso14443_3a_poller_sync_api.c => iso14443_3a_poller_sync.c} (93%) rename lib/nfc/protocols/iso14443_3a/{iso14443_3a_poller_sync_api.h => iso14443_3a_poller_sync.h} (58%) rename lib/nfc/protocols/mf_classic/{mf_classic_poller_sync_api.c => mf_classic_poller_sync.c} (88%) rename lib/nfc/protocols/mf_classic/{mf_classic_poller_sync_api.h => mf_classic_poller_sync.h} (64%) rename lib/nfc/protocols/mf_ultralight/{mf_ultralight_poller_sync_api.c => mf_ultralight_poller_sync.c} (83%) create mode 100644 lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.h delete mode 100644 lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync_api.h diff --git a/applications/debug/unit_tests/nfc/nfc_test.c b/applications/debug/unit_tests/nfc/nfc_test.c index ce5a9cb9cc5..0dcd09046df 100644 --- a/applications/debug/unit_tests/nfc/nfc_test.c +++ b/applications/debug/unit_tests/nfc/nfc_test.c @@ -7,10 +7,10 @@ #include #include #include -#include +#include #include -#include -#include +#include +#include #include #include @@ -182,8 +182,8 @@ MU_TEST(iso14443_3a_reader) { Iso14443_3aData iso14443_3a_poller_data = {}; mu_assert( - iso14443_3a_poller_read(poller, &iso14443_3a_poller_data) == Iso14443_3aErrorNone, - "iso14443_3a_poller_read() failed"); + iso14443_3a_poller_sync_read(poller, &iso14443_3a_poller_data) == Iso14443_3aErrorNone, + "iso14443_3a_poller_sync_read() failed"); nfc_listener_stop(iso3_listener); mu_assert( @@ -221,8 +221,8 @@ static void mf_ultralight_reader_test(const char* path) { nfc_listener_start(mfu_listener, NULL, NULL); MfUltralightData* mfu_data = mf_ultralight_alloc(); - MfUltralightError error = mf_ultralight_poller_read_card(poller, mfu_data); - mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_read_card() failed"); + MfUltralightError error = mf_ultralight_poller_sync_read_card(poller, mfu_data); + mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_sync_read_card() failed"); nfc_listener_stop(mfu_listener); nfc_listener_free(mfu_listener); @@ -270,8 +270,8 @@ MU_TEST(ntag_213_locked_reader) { nfc_listener_start(mfu_listener, NULL, NULL); MfUltralightData* mfu_data = mf_ultralight_alloc(); - MfUltralightError error = mf_ultralight_poller_read_card(poller, mfu_data); - mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_read_card() failed"); + MfUltralightError error = mf_ultralight_poller_sync_read_card(poller, mfu_data); + mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_sync_read_card() failed"); nfc_listener_stop(mfu_listener); nfc_listener_free(mfu_listener); @@ -308,8 +308,8 @@ static void mf_ultralight_write() { MfUltralightData* mfu_data = mf_ultralight_alloc(); // Initial read - MfUltralightError error = mf_ultralight_poller_read_card(poller, mfu_data); - mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_read_card() failed"); + MfUltralightError error = mf_ultralight_poller_sync_read_card(poller, mfu_data); + mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_sync_read_card() failed"); mu_assert( mf_ultralight_is_equal(mfu_data, nfc_device_get_data(nfc_device, NfcProtocolMfUltralight)), @@ -321,13 +321,13 @@ static void mf_ultralight_write() { FURI_LOG_D(TAG, "Writing page %d", i); furi_hal_random_fill_buf(page.data, sizeof(MfUltralightPage)); mfu_data->page[i] = page; - error = mf_ultralight_poller_write_page(poller, i, &page); - mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_write_page() failed"); + error = mf_ultralight_poller_sync_write_page(poller, i, &page); + mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_sync_write_page() failed"); } // Verification read - error = mf_ultralight_poller_read_card(poller, mfu_data); - mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_read_card() failed"); + error = mf_ultralight_poller_sync_read_card(poller, mfu_data); + mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_sync_read_card() failed"); nfc_listener_stop(mfu_listener); const MfUltralightData* mfu_listener_data = @@ -355,7 +355,7 @@ static void mf_classic_reader() { MfClassicBlock block = {}; MfClassicKey key = {.data = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}; - mf_classic_poller_read_block(poller, 0, &key, MfClassicKeyTypeA, &block); + mf_classic_poller_sync_read_block(poller, 0, &key, MfClassicKeyTypeA, &block); nfc_listener_stop(mfc_listener); nfc_listener_free(mfc_listener); @@ -383,8 +383,8 @@ static void mf_classic_write() { furi_hal_random_fill_buf(block_write.data, sizeof(MfClassicBlock)); MfClassicKey key = {.data = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}; - mf_classic_poller_write_block(poller, 1, &key, MfClassicKeyTypeA, &block_write); - mf_classic_poller_read_block(poller, 1, &key, MfClassicKeyTypeA, &block_read); + mf_classic_poller_sync_write_block(poller, 1, &key, MfClassicKeyTypeA, &block_write); + mf_classic_poller_sync_read_block(poller, 1, &key, MfClassicKeyTypeA, &block_read); nfc_listener_stop(mfc_listener); nfc_listener_free(mfc_listener); @@ -413,16 +413,18 @@ static void mf_classic_value_block() { mf_classic_value_to_block(value, 1, &block_write); MfClassicError error = MfClassicErrorNone; - error = mf_classic_poller_write_block(poller, 1, &key, MfClassicKeyTypeA, &block_write); + error = mf_classic_poller_sync_write_block(poller, 1, &key, MfClassicKeyTypeA, &block_write); mu_assert(error == MfClassicErrorNone, "Write failed"); int32_t data = 200; int32_t new_value = 0; - error = mf_classic_poller_change_value(poller, 1, &key, MfClassicKeyTypeA, data, &new_value); + error = + mf_classic_poller_sync_change_value(poller, 1, &key, MfClassicKeyTypeA, data, &new_value); mu_assert(error == MfClassicErrorNone, "Value increment failed"); mu_assert(new_value == value + data, "Value not match"); - error = mf_classic_poller_change_value(poller, 1, &key, MfClassicKeyTypeA, -data, &new_value); + error = + mf_classic_poller_sync_change_value(poller, 1, &key, MfClassicKeyTypeA, -data, &new_value); mu_assert(error == MfClassicErrorNone, "Value decrement failed"); mu_assert(new_value == value, "Value not match"); diff --git a/applications/main/nfc/plugins/supported_cards/plantain.c b/applications/main/nfc/plugins/supported_cards/plantain.c index cb8c0093d04..a21e1cd415b 100644 --- a/applications/main/nfc/plugins/supported_cards/plantain.c +++ b/applications/main/nfc/plugins/supported_cards/plantain.c @@ -4,7 +4,7 @@ #include #include -#include +#include #define TAG "Plantain" @@ -91,7 +91,7 @@ static bool plantain_verify_type(Nfc* nfc, MfClassicType type) { MfClassicAuthContext auth_context; MfClassicError error = - mf_classic_poller_auth(nfc, block_num, &key, MfClassicKeyTypeA, &auth_context); + mf_classic_poller_sync_auth(nfc, block_num, &key, MfClassicKeyTypeA, &auth_context); if(error != MfClassicErrorNone) { FURI_LOG_D(TAG, "Failed to read block %u: %d", block_num, error); break; @@ -119,7 +119,7 @@ static bool plantain_read(Nfc* nfc, NfcDevice* device) { do { MfClassicType type = MfClassicTypeMini; - MfClassicError error = mf_classic_poller_detect_type(nfc, &type); + MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type); if(error != MfClassicErrorNone) break; data->type = type; @@ -134,7 +134,7 @@ static bool plantain_read(Nfc* nfc, NfcDevice* device) { FURI_BIT_SET(keys.key_b_mask, i); } - error = mf_classic_poller_read(nfc, &keys, data); + error = mf_classic_poller_sync_read(nfc, &keys, data); if(error != MfClassicErrorNone) { FURI_LOG_W(TAG, "Failed to read data"); break; diff --git a/applications/main/nfc/plugins/supported_cards/troika.c b/applications/main/nfc/plugins/supported_cards/troika.c index d42b977c6cf..7cf1e4dd8c4 100644 --- a/applications/main/nfc/plugins/supported_cards/troika.c +++ b/applications/main/nfc/plugins/supported_cards/troika.c @@ -4,7 +4,7 @@ #include #include -#include +#include #define TAG "Troika" @@ -91,7 +91,7 @@ static bool troika_verify_type(Nfc* nfc, MfClassicType type) { MfClassicAuthContext auth_context; MfClassicError error = - mf_classic_poller_auth(nfc, block_num, &key, MfClassicKeyTypeA, &auth_context); + mf_classic_poller_sync_auth(nfc, block_num, &key, MfClassicKeyTypeA, &auth_context); if(error != MfClassicErrorNone) { FURI_LOG_D(TAG, "Failed to read block %u: %d", block_num, error); break; @@ -118,7 +118,7 @@ static bool troika_read(Nfc* nfc, NfcDevice* device) { do { MfClassicType type = MfClassicTypeMini; - MfClassicError error = mf_classic_poller_detect_type(nfc, &type); + MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type); if(error != MfClassicErrorNone) break; data->type = type; @@ -136,7 +136,7 @@ static bool troika_read(Nfc* nfc, NfcDevice* device) { FURI_BIT_SET(keys.key_b_mask, i); } - error = mf_classic_poller_read(nfc, &keys, data); + error = mf_classic_poller_sync_read(nfc, &keys, data); if(error != MfClassicErrorNone) { FURI_LOG_W(TAG, "Failed to read data"); break; diff --git a/applications/main/nfc/plugins/supported_cards/two_cities.c b/applications/main/nfc/plugins/supported_cards/two_cities.c index fb964103ee8..1748d372d2a 100644 --- a/applications/main/nfc/plugins/supported_cards/two_cities.c +++ b/applications/main/nfc/plugins/supported_cards/two_cities.c @@ -4,7 +4,7 @@ #include #include -#include +#include #define TAG "TwoCities" @@ -49,7 +49,7 @@ bool two_cities_verify(Nfc* nfc) { MfClassicAuthContext auth_ctx = {}; MfClassicError error = - mf_classic_poller_auth(nfc, block_num, &key, MfClassicKeyTypeA, &auth_ctx); + mf_classic_poller_sync_auth(nfc, block_num, &key, MfClassicKeyTypeA, &auth_ctx); if(error != MfClassicErrorNone) { FURI_LOG_D(TAG, "Failed to read block %u: %d", block_num, error); break; @@ -72,7 +72,7 @@ static bool two_cities_read(Nfc* nfc, NfcDevice* device) { do { MfClassicType type = MfClassicTypeMini; - MfClassicError error = mf_classic_poller_detect_type(nfc, &type); + MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type); if(error != MfClassicErrorNone) break; data->type = type; @@ -84,7 +84,7 @@ static bool two_cities_read(Nfc* nfc, NfcDevice* device) { FURI_BIT_SET(keys.key_b_mask, i); } - error = mf_classic_poller_read(nfc, &keys, data); + error = mf_classic_poller_sync_read(nfc, &keys, data); if(error != MfClassicErrorNone) { FURI_LOG_W(TAG, "Failed to read data"); break; diff --git a/lib/drivers/SConscript b/lib/drivers/SConscript index 103472ccb35..cf93d4bce98 100644 --- a/lib/drivers/SConscript +++ b/lib/drivers/SConscript @@ -6,6 +6,8 @@ env.Append( ], SDK_HEADERS=[ File("cc1101_regs.h"), + File("st25r3916_reg.h"), + File("st25r3916.h"), ], ) diff --git a/lib/nfc/SConscript b/lib/nfc/SConscript index 6c55cf5d2bf..605a8639dd8 100644 --- a/lib/nfc/SConscript +++ b/lib/nfc/SConscript @@ -36,9 +36,9 @@ env.Append( File("protocols/mf_ultralight/mf_ultralight_listener.h"), File("protocols/mf_classic/mf_classic_listener.h"), # Sync API - File("protocols/iso14443_3a/iso14443_3a_poller_sync_api.h"), - File("protocols/mf_ultralight/mf_ultralight_poller_sync_api.h"), - File("protocols/mf_classic/mf_classic_poller_sync_api.h"), + File("protocols/iso14443_3a/iso14443_3a_poller_sync.h"), + File("protocols/mf_ultralight/mf_ultralight_poller_sync.h"), + File("protocols/mf_classic/mf_classic_poller_sync.h"), # Misc File("helpers/nfc_util.h"), File("helpers/iso14443_crc.h"), diff --git a/lib/nfc/nfc_poller.c b/lib/nfc/nfc_poller.c index ffe0318a0d3..48be37d9259 100644 --- a/lib/nfc/nfc_poller.c +++ b/lib/nfc/nfc_poller.c @@ -28,6 +28,9 @@ struct NfcPoller { NfcPollerList list; NfcPollerSessionState session_state; bool protocol_detected; + + NfcGenericCallbackEx callback; + void* context; }; static void nfc_poller_list_alloc(NfcPoller* instance) { @@ -127,6 +130,75 @@ void nfc_poller_start(NfcPoller* instance, NfcGenericCallback callback, void* co nfc_start(instance->nfc, nfc_poller_start_callback, instance); } +static NfcCommand nfc_poller_start_ex_tail_callback(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.protocol != NfcProtocolInvalid); + + NfcPoller* instance = context; + NfcCommand command = NfcCommandContinue; + + NfcGenericEventEx poller_event = { + .poller = instance->list.tail->poller, + .parent_event_data = event.event_data, + }; + + command = instance->callback(poller_event, instance->context); + + return command; +} + +static NfcCommand nfc_poller_start_ex_head_callback(NfcEvent event, void* context) { + furi_assert(context); + + NfcCommand command = NfcCommandContinue; + NfcPoller* instance = context; + + NfcProtocol parent_protocol = nfc_protocol_get_parent(instance->protocol); + + if(parent_protocol == NfcProtocolInvalid) { + NfcGenericEventEx poller_event = { + .poller = instance->list.tail->poller, + .parent_event_data = &event, + }; + + command = instance->callback(poller_event, instance->context); + } else { + NfcGenericEvent poller_event = { + .protocol = NfcProtocolInvalid, + .instance = instance->nfc, + .event_data = &event, + }; + NfcPollerListElement* head_poller = instance->list.head; + command = head_poller->poller_api->run(poller_event, head_poller->poller); + } + + if(instance->session_state == NfcPollerSessionStateStopRequest) { + command = NfcCommandStop; + } + + return command; +} + +void nfc_poller_start_ex(NfcPoller* instance, NfcGenericCallbackEx callback, void* context) { + furi_assert(instance); + furi_assert(callback); + furi_assert(instance->session_state == NfcPollerSessionStateIdle); + + instance->callback = callback; + instance->context = context; + + NfcProtocol parent_protocol = nfc_protocol_get_parent(instance->protocol); + if(parent_protocol != NfcProtocolInvalid) { + NfcPollerListElement* iter = instance->list.head; + while(iter->protocol != parent_protocol) iter = iter->child; + + iter->poller_api->set_callback(iter->poller, nfc_poller_start_ex_tail_callback, instance); + } + + instance->session_state = NfcPollerSessionStateActive; + nfc_start(instance->nfc, nfc_poller_start_ex_head_callback, instance); +} + void nfc_poller_stop(NfcPoller* instance) { furi_assert(instance); furi_assert(instance->nfc); diff --git a/lib/nfc/nfc_poller.h b/lib/nfc/nfc_poller.h index 8ae01a8e43c..18fbfb388ad 100644 --- a/lib/nfc/nfc_poller.h +++ b/lib/nfc/nfc_poller.h @@ -26,6 +26,31 @@ extern "C" { */ typedef struct NfcPoller NfcPoller; +/** + * @brief Extended generic Nfc event type. + * + * An extended generic Nfc event contains protocol poller and it's parent protocol event data. + * If protocol has no parent, then events are produced by Nfc instance. + * + * The parent_event_data field is protocol-specific and should be cast to the appropriate type before use. + */ +typedef struct { + NfcGenericInstance* poller; /**< Pointer to the protocol poller. */ + NfcGenericEventData* + parent_event_data /**< Pointer to the protocol's parent poller event data. */; +} NfcGenericEventEx; + +/** + * @brief Extended generic Nfc event callback type. + * + * A function of this type must be passed as the callback parameter upon extended start of a poller. + * + * @param [in] event Nfc extended generic event, passed by value, complete with protocol type and data. + * @param [in,out] context pointer to the user-specific context (set when starting a poller/listener instance). + * @returns the command which the event producer must execute. + */ +typedef NfcCommand (*NfcGenericCallbackEx)(NfcGenericEventEx event, void* context); + /** * @brief Allocate an NfcPoller instance. * @@ -57,6 +82,18 @@ void nfc_poller_free(NfcPoller* instance); */ void nfc_poller_start(NfcPoller* instance, NfcGenericCallback callback, void* context); +/** + * @brief Start an NfcPoller instance in extended mode. + * + * When nfc poller is started in extended mode, callback will be called with parent protocol events + * and protocol instance. This mode enables to make custom poller state machines. + * + * @param[in,out] instance pointer to the instance to be started. + * @param[in] callback pointer to a user-defined callback function which will receive events. + * @param[in] context pointer to a user-specific context (will be passed to the callback). + */ +void nfc_poller_start_ex(NfcPoller* instance, NfcGenericCallbackEx callback, void* context); + /** * @brief Stop an NfcPoller instance. * diff --git a/lib/nfc/protocols/felica/felica_poller.c b/lib/nfc/protocols/felica/felica_poller.c index 3fc2affedc3..23b1604e19b 100644 --- a/lib/nfc/protocols/felica/felica_poller.c +++ b/lib/nfc/protocols/felica/felica_poller.c @@ -66,7 +66,7 @@ static NfcCommand felica_poller_run(NfcGenericEvent event, void* context) { if(nfc_event->type == NfcEventTypePollerReady) { if(instance->state != FelicaPollerStateActivated) { - FelicaError error = felica_poller_async_activate(instance, instance->data); + FelicaError error = felica_poller_activate(instance, instance->data); if(error == FelicaErrorNone) { instance->felica_event.type = FelicaPollerEventTypeReady; instance->felica_event_data.error = error; @@ -100,7 +100,7 @@ static bool felica_poller_detect(NfcGenericEvent event, void* context) { furi_assert(instance->state == FelicaPollerStateIdle); if(nfc_event->type == NfcEventTypePollerReady) { - FelicaError error = felica_poller_async_activate(instance, instance->data); + FelicaError error = felica_poller_activate(instance, instance->data); protocol_detected = (error == FelicaErrorNone); } diff --git a/lib/nfc/protocols/felica/felica_poller.h b/lib/nfc/protocols/felica/felica_poller.h index 7d0c9525e77..45fd9a9a1fc 100644 --- a/lib/nfc/protocols/felica/felica_poller.h +++ b/lib/nfc/protocols/felica/felica_poller.h @@ -9,22 +9,50 @@ extern "C" { #endif +/** + * @brief FelicaPoller opaque type definition. + */ typedef struct FelicaPoller FelicaPoller; +/** + * @brief Enumeration of possible Felica poller event types. + */ typedef enum { - FelicaPollerEventTypeError, - FelicaPollerEventTypeReady, + FelicaPollerEventTypeError, /**< The card was activated by the poller. */ + FelicaPollerEventTypeReady, /**< An error occured during activation procedure. */ } FelicaPollerEventType; -typedef struct { - FelicaError error; +/** + * @brief Felica poller event data. + */ +typedef union { + FelicaError error; /**< Error code indicating card activation fail reason. */ } FelicaPollerEventData; +/** + * @brief FelicaPoller poller event structure. + * + * Upon emission of an event, an instance of this struct will be passed to the callback. + */ typedef struct { - FelicaPollerEventType type; - FelicaPollerEventData* data; + FelicaPollerEventType type; /**< Type of emmitted event. */ + FelicaPollerEventData* data; /**< Pointer to event specific data. */ } FelicaPollerEvent; +/** + * @brief Perform collision resolution procedure. + * + * Must ONLY be used inside the callback function. + * + * Perfoms the collision resolution procedure as defined in FeliCa standars. The data + * field will be filled with Felica data on success. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[out] data pointer to the Felica data structure to be filled. + * @return FelicaErrorNone on success, an error code on failure. + */ +FelicaError felica_poller_activate(FelicaPoller* instance, FelicaData* data); + #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/felica/felica_poller_i.c b/lib/nfc/protocols/felica/felica_poller_i.c index d8015fdfade..bfbf150ef98 100644 --- a/lib/nfc/protocols/felica/felica_poller_i.c +++ b/lib/nfc/protocols/felica/felica_poller_i.c @@ -49,7 +49,7 @@ static FelicaError felica_poller_frame_exchange( return ret; } -FelicaError felica_poller_async_polling( +FelicaError felica_poller_polling( FelicaPoller* instance, const FelicaPollerPollingCommand* cmd, FelicaPollerPollingResponse* resp) { @@ -93,7 +93,7 @@ FelicaError felica_poller_async_polling( return error; } -FelicaError felica_poller_async_activate(FelicaPoller* instance, FelicaData* data) { +FelicaError felica_poller_activate(FelicaPoller* instance, FelicaData* data) { furi_assert(instance); felica_reset(data); @@ -112,7 +112,7 @@ FelicaError felica_poller_async_activate(FelicaPoller* instance, FelicaData* dat }; FelicaPollerPollingResponse polling_resp = {}; - ret = felica_poller_async_polling(instance, &polling_cmd, &polling_resp); + ret = felica_poller_polling(instance, &polling_cmd, &polling_resp); if(ret != FelicaErrorNone) { FURI_LOG_T(TAG, "Activation failed error: %d", ret); diff --git a/lib/nfc/protocols/felica/felica_poller_i.h b/lib/nfc/protocols/felica/felica_poller_i.h index e12f0147269..3bd4d91f9f9 100644 --- a/lib/nfc/protocols/felica/felica_poller_i.h +++ b/lib/nfc/protocols/felica/felica_poller_i.h @@ -48,13 +48,11 @@ typedef struct { const FelicaData* felica_poller_get_data(FelicaPoller* instance); -FelicaError felica_poller_async_polling( +FelicaError felica_poller_polling( FelicaPoller* instance, const FelicaPollerPollingCommand* cmd, FelicaPollerPollingResponse* resp); -FelicaError felica_poller_async_activate(FelicaPoller* instance, FelicaData* data); - #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller.c b/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller.c index 880092c333a..158250acdf7 100644 --- a/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller.c +++ b/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller.c @@ -72,7 +72,7 @@ static NfcCommand iso14443_3a_poller_run(NfcGenericEvent event, void* context) { if(nfc_event->type == NfcEventTypePollerReady) { if(instance->state != Iso14443_3aPollerStateActivated) { Iso14443_3aData data = {}; - Iso14443_3aError error = iso14443_3a_poller_async_activate(instance, &data); + Iso14443_3aError error = iso14443_3a_poller_activate(instance, &data); if(error == Iso14443_3aErrorNone) { instance->state = Iso14443_3aPollerStateActivated; instance->iso14443_3a_event.type = Iso14443_3aPollerEventTypeReady; @@ -111,7 +111,7 @@ static bool iso14443_3a_poller_detect(NfcGenericEvent event, void* context) { furi_assert(instance->state == Iso14443_3aPollerStateIdle); if(nfc_event->type == NfcEventTypePollerReady) { - Iso14443_3aError error = iso14443_3a_poller_async_activate(instance, NULL); + Iso14443_3aError error = iso14443_3a_poller_activate(instance, NULL); protocol_detected = (error == Iso14443_3aErrorNone); } diff --git a/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller.h b/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller.h index 385cd522567..42e4b4bf524 100644 --- a/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller.h +++ b/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller.h @@ -9,34 +9,137 @@ extern "C" { #endif +/** + * @brief Iso14443_3aPoller opaque type definition. + */ typedef struct Iso14443_3aPoller Iso14443_3aPoller; +/** + * @brief Enumeration of possible Iso14443_3a poller event types. + */ typedef enum { - Iso14443_3aPollerEventTypeError, - Iso14443_3aPollerEventTypeReady, + Iso14443_3aPollerEventTypeError, /**< The card was activated by the poller. */ + Iso14443_3aPollerEventTypeReady, /**< An error occured during activation procedure. */ } Iso14443_3aPollerEventType; -typedef struct { - Iso14443_3aError error; +/** + * @brief Iso14443_3a poller event data. + */ +typedef union { + Iso14443_3aError error; /**< Error code indicating card activation fail reason. */ } Iso14443_3aPollerEventData; +/** + * @brief Iso14443_3a poller event structure. + * + * Upon emission of an event, an instance of this struct will be passed to the callback. + */ typedef struct { - Iso14443_3aPollerEventType type; - Iso14443_3aPollerEventData* data; + Iso14443_3aPollerEventType type; /**< Type of emmitted event. */ + Iso14443_3aPollerEventData* data; /**< Pointer to event specific data. */ } Iso14443_3aPollerEvent; +/** + * @brief Transmit and receive Iso14443_3a frames in poller mode. + * + * Must ONLY be used inside the callback function. + * + * The rx_buffer will be filled with any data received as a response to data + * sent from tx_buffer, with a timeout defined by the fwt parameter. + * + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] tx_buffer pointer to the buffer containing the data to be transmitted. + * @param[out] rx_buffer pointer to the buffer to be filled with received data. + * @param[in] fwt frame wait time (response timeout), in carrier cycles. + * @return Iso14443_3aErrorNone on success, an error code on failure. + */ Iso14443_3aError iso14443_3a_poller_txrx( Iso14443_3aPoller* instance, const BitBuffer* tx_buffer, BitBuffer* rx_buffer, uint32_t fwt); +/** + * @brief Transmit and receive Iso14443_3a standard frames in poller mode. + * + * Must ONLY be used inside the callback function. + * + * The rx_buffer will be filled with any data received as a response to data + * sent from tx_buffer, with a timeout defined by the fwt parameter. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] tx_buffer pointer to the buffer containing the data to be transmitted. + * @param[out] rx_buffer pointer to the buffer to be filled with received data. + * @param[in] fwt frame wait time (response timeout), in carrier cycles. + * @return Iso14443_3aErrorNone on success, an error code on failure. + */ Iso14443_3aError iso14443_3a_poller_send_standard_frame( Iso14443_3aPoller* instance, const BitBuffer* tx_buffer, BitBuffer* rx_buffer, uint32_t fwt); +/** + * @brief Transmit and receive Iso14443_3a frames with custom parity bits in poller mode. + * + * Must ONLY be used inside the callback function. + * + * The rx_buffer will be filled with any data received as a response to data + * sent from tx_buffer, with a timeout defined by the fwt parameter. + * + * Custom parity bits must be set in the tx_buffer. The rx_buffer will contain + * the received data with the parity bits. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] tx_buffer pointer to the buffer containing the data to be transmitted. + * @param[out] rx_buffer pointer to the buffer to be filled with received data. + * @param[in] fwt frame wait time (response timeout), in carrier cycles. + * @return Iso14443_3aErrorNone on success, an error code on failure. + */ +Iso14443_3aError iso14443_3a_poller_txrx_custom_parity( + Iso14443_3aPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt); + +/** + * @brief Checks presence of Iso14443_3a complient card. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @return Iso14443_3aErrorNone if card is present, an error code otherwise. + */ +Iso14443_3aError iso14443_3a_poller_check_presence(Iso14443_3aPoller* instance); + +/** + * @brief Perform collision resolution procedure. + * + * Must ONLY be used inside the callback function. + * + * Perfoms the collision resolution procedure as defined in Iso14443-3a. The iso14443_3a_data + * field will be filled with Iso14443-3a data on success. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[out] iso14443_3a_data pointer to the Iso14443_3a data structure to be filled. + * @return Iso14443_3aErrorNone on success, an error code on failure. + */ +Iso14443_3aError + iso14443_3a_poller_activate(Iso14443_3aPoller* instance, Iso14443_3aData* iso14443_3a_data); + +/** + * @brief Send HALT command to the card. + * + * Must ONLY be used inside the callback function. + * + * Halts card and changes internal Iso14443_3aPoller state to Idle. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @return Iso14443_3aErrorNone on success, an error code on failure. + */ +Iso14443_3aError iso14443_3a_poller_halt(Iso14443_3aPoller* instance); + #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_i.c b/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_i.c index 3434dc8e354..2be88bc515d 100644 --- a/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_i.c +++ b/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_i.c @@ -94,9 +94,8 @@ Iso14443_3aError iso14443_3a_poller_halt(Iso14443_3aPoller* instance) { return Iso14443_3aErrorNone; } -Iso14443_3aError iso14443_3a_poller_async_activate( - Iso14443_3aPoller* instance, - Iso14443_3aData* iso14443_3a_data) { +Iso14443_3aError + iso14443_3a_poller_activate(Iso14443_3aPoller* instance, Iso14443_3aData* iso14443_3a_data) { furi_assert(instance); furi_assert(instance->nfc); furi_assert(instance->tx_buffer); diff --git a/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_i.h b/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_i.h index 063ef155665..764f1a6b593 100644 --- a/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_i.h +++ b/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_i.h @@ -39,15 +39,9 @@ typedef enum { Iso14443_3aPollerStateActivated, } Iso14443_3aPollerState; -typedef enum { - Iso14443_3aPollerConfigStateIdle, - Iso14443_3aPollerConfigStateDone, -} Iso14443_3aPollerConfigState; - struct Iso14443_3aPoller { Nfc* nfc; Iso14443_3aPollerState state; - Iso14443_3aPollerConfigState config_state; Iso14443_3aPollerColRes col_res; Iso14443_3aData* data; BitBuffer* tx_buffer; @@ -62,20 +56,6 @@ struct Iso14443_3aPoller { const Iso14443_3aData* iso14443_3a_poller_get_data(Iso14443_3aPoller* instance); -Iso14443_3aError iso14443_3a_poller_check_presence(Iso14443_3aPoller* instance); - -Iso14443_3aError iso14443_3a_poller_async_activate( - Iso14443_3aPoller* instance, - Iso14443_3aData* iso14443_3a_data); - -Iso14443_3aError iso14443_3a_poller_halt(Iso14443_3aPoller* instance); - -Iso14443_3aError iso14443_3a_poller_txrx_custom_parity( - Iso14443_3aPoller* instance, - const BitBuffer* tx_buffer, - BitBuffer* rx_buffer, - uint32_t fwt); - #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_sync_api.c b/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_sync.c similarity index 93% rename from lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_sync_api.c rename to lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_sync.c index 2bab1a881a2..ea7a6ae156a 100644 --- a/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_sync_api.c +++ b/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_sync.c @@ -1,4 +1,4 @@ -#include "iso14443_3a_poller_sync_api.h" +#include "iso14443_3a_poller_sync.h" #include "iso14443_3a_poller_i.h" #include @@ -34,7 +34,7 @@ NfcCommand iso14443_3a_poller_read_callback(NfcGenericEvent event, void* context return NfcCommandStop; } -Iso14443_3aError iso14443_3a_poller_read(Nfc* nfc, Iso14443_3aData* iso14443_3a_data) { +Iso14443_3aError iso14443_3a_poller_sync_read(Nfc* nfc, Iso14443_3aData* iso14443_3a_data) { furi_assert(nfc); furi_assert(iso14443_3a_data); diff --git a/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_sync_api.h b/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_sync.h similarity index 58% rename from lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_sync_api.h rename to lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_sync.h index ed17ff43244..72f084d1b06 100644 --- a/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_sync_api.h +++ b/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_sync.h @@ -7,7 +7,7 @@ extern "C" { #endif -Iso14443_3aError iso14443_3a_poller_read(Nfc* nfc, Iso14443_3aData* iso14443_3a_data); +Iso14443_3aError iso14443_3a_poller_sync_read(Nfc* nfc, Iso14443_3aData* iso14443_3a_data); #ifdef __cplusplus } diff --git a/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller.c b/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller.c index 9507f28c410..f0c9b67ad1c 100644 --- a/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller.c +++ b/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller.c @@ -70,7 +70,7 @@ static NfcCommand iso14443_3b_poller_run(NfcGenericEvent event, void* context) { if(nfc_event->type == NfcEventTypePollerReady) { if(instance->state != Iso14443_3bPollerStateActivated) { - Iso14443_3bError error = iso14443_3b_poller_async_activate(instance, instance->data); + Iso14443_3bError error = iso14443_3b_poller_activate(instance, instance->data); if(error == Iso14443_3bErrorNone) { instance->iso14443_3b_event.type = Iso14443_3bPollerEventTypeReady; instance->iso14443_3b_event_data.error = error; @@ -104,7 +104,7 @@ static bool iso14443_3b_poller_detect(NfcGenericEvent event, void* context) { furi_assert(instance->state == Iso14443_3bPollerStateIdle); if(nfc_event->type == NfcEventTypePollerReady) { - Iso14443_3bError error = iso14443_3b_poller_async_activate(instance, instance->data); + Iso14443_3bError error = iso14443_3b_poller_activate(instance, instance->data); protocol_detected = (error == Iso14443_3bErrorNone); } diff --git a/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller.h b/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller.h index d25d9dbe9fe..940903c1dcc 100644 --- a/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller.h +++ b/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller.h @@ -9,22 +9,81 @@ extern "C" { #endif +/** + * @brief Iso14443_3bPoller opaque type definition. + */ typedef struct Iso14443_3bPoller Iso14443_3bPoller; +/** + * @brief Enumeration of possible Iso14443_3b poller event types. + */ typedef enum { - Iso14443_3bPollerEventTypeError, - Iso14443_3bPollerEventTypeReady, + Iso14443_3bPollerEventTypeError, /**< The card was activated by the poller. */ + Iso14443_3bPollerEventTypeReady, /**< An error occured during activation procedure. */ } Iso14443_3bPollerEventType; -typedef struct { - Iso14443_3bError error; +/** + * @brief Iso14443_3b poller event data. + */ +typedef union { + Iso14443_3bError error; /**< Error code indicating card activation fail reason. */ } Iso14443_3bPollerEventData; +/** + * @brief Iso14443_3b poller event structure. + * + * Upon emission of an event, an instance of this struct will be passed to the callback. + */ typedef struct { - Iso14443_3bPollerEventType type; - Iso14443_3bPollerEventData* data; + Iso14443_3bPollerEventType type; /**< Type of emmitted event. */ + Iso14443_3bPollerEventData* data; /**< Pointer to event specific data. */ } Iso14443_3bPollerEvent; +/** + * @brief Transmit and receive Iso14443_3b frames in poller mode. + * + * Must ONLY be used inside the callback function. + * + * The rx_buffer will be filled with any data received as a response to data + * sent from tx_buffer, with a timeout defined by the fwt parameter. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] tx_buffer pointer to the buffer containing the data to be transmitted. + * @param[out] rx_buffer pointer to the buffer to be filled with received data. + * @param[in] fwt frame wait time (response timeout), in carrier cycles. + * @return Iso14443_3bErrorNone on success, an error code on failure. + */ +Iso14443_3bError iso14443_3b_poller_send_frame( + Iso14443_3bPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer); + +/** + * @brief Perform collision resolution procedure. + * + * Must ONLY be used inside the callback function. + * + * Perfoms the collision resolution procedure as defined in Iso14443-3b. The data + * field will be filled with Iso14443-3b data on success. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[out] data pointer to the Iso14443_3b data structure to be filled. + * @return Iso14443_3bErrorNone on success, an error code on failure. + */ +Iso14443_3bError iso14443_3b_poller_activate(Iso14443_3bPoller* instance, Iso14443_3bData* data); + +/** + * @brief Send HALT command to the card. + * + * Must ONLY be used inside the callback function. + * + * Halts card and changes internal Iso14443_3bPoller state to Idle. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @return Iso14443_3bErrorNone on success, an error code on failure. + */ +Iso14443_3bError iso14443_3b_poller_halt(Iso14443_3bPoller* instance); + #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller_i.c b/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller_i.c index 95668ccf224..1ee5237c641 100644 --- a/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller_i.c +++ b/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller_i.c @@ -21,7 +21,7 @@ static Iso14443_3bError iso14443_3b_poller_prepare_trx(Iso14443_3bPoller* instan furi_assert(instance); if(instance->state == Iso14443_3bPollerStateIdle) { - return iso14443_3b_poller_async_activate(instance, NULL); + return iso14443_3b_poller_activate(instance, NULL); } return Iso14443_3bErrorNone; @@ -63,8 +63,7 @@ static Iso14443_3bError iso14443_3b_poller_frame_exchange( return ret; } -Iso14443_3bError - iso14443_3b_poller_async_activate(Iso14443_3bPoller* instance, Iso14443_3bData* data) { +Iso14443_3bError iso14443_3b_poller_activate(Iso14443_3bPoller* instance, Iso14443_3bData* data) { furi_assert(instance); furi_assert(instance->nfc); diff --git a/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller_i.h b/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller_i.h index 5821d6373f5..2503c2e41e8 100644 --- a/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller_i.h +++ b/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller_i.h @@ -34,16 +34,6 @@ struct Iso14443_3bPoller { const Iso14443_3bData* iso14443_3b_poller_get_data(Iso14443_3bPoller* instance); -Iso14443_3bError - iso14443_3b_poller_async_activate(Iso14443_3bPoller* instance, Iso14443_3bData* data); - -Iso14443_3bError iso14443_3b_poller_halt(Iso14443_3bPoller* instance); - -Iso14443_3bError iso14443_3b_poller_send_frame( - Iso14443_3bPoller* instance, - const BitBuffer* tx_buffer, - BitBuffer* rx_buffer); - #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a.h b/lib/nfc/protocols/iso14443_4a/iso14443_4a.h index 9580c140409..df212152de1 100644 --- a/lib/nfc/protocols/iso14443_4a/iso14443_4a.h +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a.h @@ -2,6 +2,8 @@ #include +#include + #ifdef __cplusplus extern "C" { #endif @@ -28,7 +30,19 @@ typedef enum { Iso14443_4aFrameOptionCid, } Iso14443_4aFrameOption; -typedef struct Iso14443_4aData Iso14443_4aData; +typedef struct { + uint8_t tl; + uint8_t t0; + uint8_t ta_1; + uint8_t tb_1; + uint8_t tc_1; + SimpleArray* t1_tk; +} Iso14443_4aAtsData; + +typedef struct { + Iso14443_3aData* iso14443_3a_data; + Iso14443_4aAtsData ats_data; +} Iso14443_4aData; // Virtual methods diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_i.h b/lib/nfc/protocols/iso14443_4a/iso14443_4a_i.h index e45fb90cca0..e5483a6ba1f 100644 --- a/lib/nfc/protocols/iso14443_4a/iso14443_4a_i.h +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_i.h @@ -2,8 +2,6 @@ #include "iso14443_4a.h" -#include - #define ISO14443_4A_CMD_READ_ATS (0xE0) // ATS bit definitions @@ -23,20 +21,6 @@ #define ISO14443_4A_ATS_TC1_NAD (1U << 0) #define ISO14443_4A_ATS_TC1_CID (1U << 1) -typedef struct { - uint8_t tl; - uint8_t t0; - uint8_t ta_1; - uint8_t tb_1; - uint8_t tc_1; - SimpleArray* t1_tk; -} Iso14443_4aAtsData; - -struct Iso14443_4aData { - Iso14443_3aData* iso14443_3a_data; - Iso14443_4aAtsData ats_data; -}; - bool iso14443_4a_ats_parse(Iso14443_4aAtsData* data, const BitBuffer* buf); Iso14443_4aError iso14443_4a_process_error(Iso14443_3aError error); diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.c b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.c index c07cc6b7f0e..e20048b509d 100644 --- a/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.c +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.c @@ -55,8 +55,7 @@ static NfcCommand iso14443_4a_poller_handler_idle(Iso14443_4aPoller* instance) { } static NfcCommand iso14443_4a_poller_handler_read_ats(Iso14443_4aPoller* instance) { - Iso14443_4aError error = - iso14443_4a_poller_async_read_ats(instance, &instance->data->ats_data); + Iso14443_4aError error = iso14443_4a_poller_read_ats(instance, &instance->data->ats_data); if(error == Iso14443_4aErrorNone) { FURI_LOG_D(TAG, "Read ATS success"); instance->poller_state = Iso14443_4aPollerStateReady; diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.h b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.h index b224299e0a7..73eb6ef74da 100644 --- a/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.h +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.h @@ -8,22 +8,80 @@ extern "C" { #endif +/** + * @brief Iso14443_4aPoller opaque type definition. + */ typedef struct Iso14443_4aPoller Iso14443_4aPoller; +/** + * @brief Enumeration of possible Iso14443_4a poller event types. + */ typedef enum { - Iso14443_4aPollerEventTypeError, - Iso14443_4aPollerEventTypeReady, + Iso14443_4aPollerEventTypeError, /**< The card was activated by the poller. */ + Iso14443_4aPollerEventTypeReady, /**< An error occured during activation procedure. */ } Iso14443_4aPollerEventType; -typedef struct { - Iso14443_4aError error; +/** + * @brief Iso14443_4a poller event data. + */ +typedef union { + Iso14443_4aError error; /**< Error code indicating card activation fail reason. */ } Iso14443_4aPollerEventData; +/** + * @brief Iso14443_4a poller event structure. + * + * Upon emission of an event, an instance of this struct will be passed to the callback. + */ typedef struct { - Iso14443_4aPollerEventType type; - Iso14443_4aPollerEventData* data; + Iso14443_4aPollerEventType type; /**< Type of emmitted event. */ + Iso14443_4aPollerEventData* data; /**< Pointer to event specific data. */ } Iso14443_4aPollerEvent; +/** + * @brief Transmit and receive Iso14443_4a blocks in poller mode. + * + * Must ONLY be used inside the callback function. + * + * The rx_buffer will be filled with any data received as a response to data + * sent from tx_buffer. The fwt parameter is calculated during activation procedure. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] tx_buffer pointer to the buffer containing the data to be transmitted. + * @param[out] rx_buffer pointer to the buffer to be filled with received data. + * @return Iso14443_4aErrorNone on success, an error code on failure. + */ +Iso14443_4aError iso14443_4a_poller_send_block( + Iso14443_4aPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer); + +/** + * @brief Send HALT command to the card. + * + * Must ONLY be used inside the callback function. + * + * Halts card and changes internal Iso14443_4aPoller state to Idle. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @return Iso14443_4aErrorNone on success, an error code on failure. + */ +Iso14443_4aError iso14443_4a_poller_halt(Iso14443_4aPoller* instance); + +/** + * @brief Read Answer To Select (ATS) from the card. + * + * Must ONLY be used inside the callback function. + * + * Send Request Answer To Select (RATS) command to the card and parse the response. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[out] data pointer to the buffer to be filled with ATS data. + * @return Iso14443_4aErrorNone on success, an error code on failure. + */ +Iso14443_4aError + iso14443_4a_poller_read_ats(Iso14443_4aPoller* instance, Iso14443_4aAtsData* data); + #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.c b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.c index 7221e2aa94d..938e4e715fd 100644 --- a/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.c +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.c @@ -18,7 +18,7 @@ Iso14443_4aError iso14443_4a_poller_halt(Iso14443_4aPoller* instance) { } Iso14443_4aError - iso14443_4a_poller_async_read_ats(Iso14443_4aPoller* instance, Iso14443_4aAtsData* data) { + iso14443_4a_poller_read_ats(Iso14443_4aPoller* instance, Iso14443_4aAtsData* data) { furi_assert(instance); bit_buffer_reset(instance->tx_buffer); diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.h b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.h index ce878cb40ae..7aae852e437 100644 --- a/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.h +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.h @@ -45,16 +45,6 @@ struct Iso14443_4aPoller { const Iso14443_4aData* iso14443_4a_poller_get_data(Iso14443_4aPoller* instance); -Iso14443_4aError iso14443_4a_poller_halt(Iso14443_4aPoller* instance); - -Iso14443_4aError - iso14443_4a_poller_async_read_ats(Iso14443_4aPoller* instance, Iso14443_4aAtsData* data); - -Iso14443_4aError iso14443_4a_poller_send_block( - Iso14443_4aPoller* instance, - const BitBuffer* tx_buffer, - BitBuffer* rx_buffer); - #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller.h b/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller.h index e60090c04e2..03b288c079c 100644 --- a/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller.h +++ b/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller.h @@ -8,22 +8,66 @@ extern "C" { #endif +/** + * @brief Iso14443_4bPoller opaque type definition. + */ typedef struct Iso14443_4bPoller Iso14443_4bPoller; +/** + * @brief Enumeration of possible Iso14443_4b poller event types. + */ typedef enum { - Iso14443_4bPollerEventTypeError, - Iso14443_4bPollerEventTypeReady, + Iso14443_4bPollerEventTypeError, /**< The card was activated by the poller. */ + Iso14443_4bPollerEventTypeReady, /**< An error occured during activation procedure. */ } Iso14443_4bPollerEventType; -typedef struct { - Iso14443_4bError error; +/** + * @brief Iso14443_4b poller event data. + */ +typedef union { + Iso14443_4bError error; /**< Error code indicating card activation fail reason. */ } Iso14443_4bPollerEventData; +/** + * @brief Iso14443_4b poller event structure. + * + * Upon emission of an event, an instance of this struct will be passed to the callback. + */ typedef struct { - Iso14443_4bPollerEventType type; - Iso14443_4bPollerEventData* data; + Iso14443_4bPollerEventType type; /**< Type of emmitted event. */ + Iso14443_4bPollerEventData* data; /**< Pointer to event specific data. */ } Iso14443_4bPollerEvent; +/** + * @brief Transmit and receive Iso14443_4b blocks in poller mode. + * + * Must ONLY be used inside the callback function. + * + * The rx_buffer will be filled with any data received as a response to data + * sent from tx_buffer. The fwt parameter is calculated during activation procedure. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] tx_buffer pointer to the buffer containing the data to be transmitted. + * @param[out] rx_buffer pointer to the buffer to be filled with received data. + * @return Iso14443_4bErrorNone on success, an error code on failure. + */ +Iso14443_4bError iso14443_4b_poller_send_block( + Iso14443_4bPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer); + +/** + * @brief Send HALT command to the card. + * + * Must ONLY be used inside the callback function. + * + * Halts card and changes internal Iso14443_4aPoller state to Idle. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @return Iso14443_4bErrorNone on success, an error code on failure. + */ +Iso14443_4bError iso14443_4b_poller_halt(Iso14443_4bPoller* instance); + #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller_i.h b/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller_i.h index bd55c618820..fc9c2632b2b 100644 --- a/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller_i.h +++ b/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller_i.h @@ -42,13 +42,6 @@ struct Iso14443_4bPoller { const Iso14443_4bData* iso14443_4b_poller_get_data(Iso14443_4bPoller* instance); -Iso14443_4bError iso14443_4b_poller_halt(Iso14443_4bPoller* instance); - -Iso14443_4bError iso14443_4b_poller_send_block( - Iso14443_4bPoller* instance, - const BitBuffer* tx_buffer, - BitBuffer* rx_buffer); - #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3_poller.c b/lib/nfc/protocols/iso15693_3/iso15693_3_poller.c index 9500e165393..cf27b1f3fd5 100644 --- a/lib/nfc/protocols/iso15693_3/iso15693_3_poller.c +++ b/lib/nfc/protocols/iso15693_3/iso15693_3_poller.c @@ -70,7 +70,7 @@ static NfcCommand iso15693_3_poller_run(NfcGenericEvent event, void* context) { if(nfc_event->type == NfcEventTypePollerReady) { if(instance->state != Iso15693_3PollerStateActivated) { - Iso15693_3Error error = iso15693_3_poller_async_activate(instance, instance->data); + Iso15693_3Error error = iso15693_3_poller_activate(instance, instance->data); if(error == Iso15693_3ErrorNone) { instance->iso15693_3_event.type = Iso15693_3PollerEventTypeReady; instance->iso15693_3_event_data.error = error; @@ -105,7 +105,7 @@ static bool iso15693_3_poller_detect(NfcGenericEvent event, void* context) { if(nfc_event->type == NfcEventTypePollerReady) { uint8_t uid[ISO15693_3_UID_SIZE]; - Iso15693_3Error error = iso15693_3_poller_async_inventory(instance, uid); + Iso15693_3Error error = iso15693_3_poller_inventory(instance, uid); protocol_detected = (error == Iso15693_3ErrorNone); } diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3_poller.h b/lib/nfc/protocols/iso15693_3/iso15693_3_poller.h index 9d73242f1ad..a187ceace14 100644 --- a/lib/nfc/protocols/iso15693_3/iso15693_3_poller.h +++ b/lib/nfc/protocols/iso15693_3/iso15693_3_poller.h @@ -8,22 +8,142 @@ extern "C" { #endif +/** + * @brief Iso15693_3Poller opaque type definition. + */ typedef struct Iso15693_3Poller Iso15693_3Poller; +/** + * @brief Enumeration of possible Iso15693_3 poller event types. + */ typedef enum { - Iso15693_3PollerEventTypeError, - Iso15693_3PollerEventTypeReady, + Iso15693_3PollerEventTypeError, /**< The card was activated by the poller. */ + Iso15693_3PollerEventTypeReady, /**< An error occured during activation procedure. */ } Iso15693_3PollerEventType; -typedef struct { - Iso15693_3Error error; +/** + * @brief Iso15693_3 poller event data. + */ +typedef union { + Iso15693_3Error error; /**< Error code indicating card activation fail reason. */ } Iso15693_3PollerEventData; +/** + * @brief Iso15693_3 poller event structure. + * + * Upon emission of an event, an instance of this struct will be passed to the callback. + */ typedef struct { - Iso15693_3PollerEventType type; - Iso15693_3PollerEventData* data; + Iso15693_3PollerEventType type; /**< Type of emmitted event. */ + Iso15693_3PollerEventData* data; /**< Pointer to event specific data. */ } Iso15693_3PollerEvent; +/** + * @brief Transmit and receive Iso15693_3 frames in poller mode. + * + * Must ONLY be used inside the callback function. + * + * The rx_buffer will be filled with any data received as a response to data + * sent from tx_buffer, with a timeout defined by the fwt parameter. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] tx_buffer pointer to the buffer containing the data to be transmitted. + * @param[out] rx_buffer pointer to the buffer to be filled with received data. + * @param[in] fwt frame wait time (response timeout), in carrier cycles. + * @return Iso15693_3ErrorNone on success, an error code on failure. + */ +Iso15693_3Error iso15693_3_poller_send_frame( + Iso15693_3Poller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt); + +/** + * @brief Perform activation procedure. + * + * Must ONLY be used inside the callback function. + * + * Perfoms the activation procedure as defined in Iso15693_3. The data + * field will be filled with Iso15693_3Data data on success. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[out] data pointer to the Iso15693_3 data structure to be filled. + * @return Iso15693_3ErrorNone on success, an error code on failure. + */ +Iso15693_3Error iso15693_3_poller_activate(Iso15693_3Poller* instance, Iso15693_3Data* data); + +/** + * @brief Send invertory command and parse response. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[out] uid pointer to the buffer to be filled with the UID. + * @return Iso15693_3ErrorNone on success, an error code on failure. + */ +Iso15693_3Error iso15693_3_poller_inventory(Iso15693_3Poller* instance, uint8_t* uid); + +/** + * @brief Send get system info command and parse response. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[out] data pointer to the Iso15693_3SystemInfo structure to be filled. + * @return Iso15693_3ErrorNone on success, an error code on failure. + */ +Iso15693_3Error + iso15693_3_poller_get_system_info(Iso15693_3Poller* instance, Iso15693_3SystemInfo* data); + +/** + * @brief Read Iso15693_3 block. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[out] data pointer to the buffer to be filled with the block data. + * @param[in] block_number block number to be read. + * @param[in] block_size size of the block to be read. + * @return Iso15693_3ErrorNone on success, an error code on failure. + */ +Iso15693_3Error iso15693_3_poller_read_block( + Iso15693_3Poller* instance, + uint8_t* data, + uint8_t block_number, + uint8_t block_size); + +/** + * @brief Read multiple Iso15693_3 blocks. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[out] data pointer to the buffer to be filled with the block data. + * @param[in] block_count number of blocks to be read. + * @param[in] block_size size of the blocks to be read. + * @return Iso15693_3ErrorNone on success, an error code on failure. + */ +Iso15693_3Error iso15693_3_poller_read_blocks( + Iso15693_3Poller* instance, + uint8_t* data, + uint16_t block_count, + uint8_t block_size); + +/** + * @brief Get Iso15693_3 block security status. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[out] data pointer to the buffer to be filled with the block security status. + * @param[in] block_count block security number to be read. + * @return Iso15693_3ErrorNone on success, an error code on failure. + */ +Iso15693_3Error iso15693_3_poller_get_blocks_security( + Iso15693_3Poller* instance, + uint8_t* data, + uint16_t block_count); + #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.c b/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.c index 8b8d8cee04b..917f7dbb8e0 100644 --- a/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.c +++ b/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.c @@ -36,7 +36,7 @@ static Iso15693_3Error iso15693_3_poller_prepare_trx(Iso15693_3Poller* instance) furi_assert(instance); if(instance->state == Iso15693_3PollerStateIdle) { - return iso15693_3_poller_async_activate(instance, NULL); + return iso15693_3_poller_activate(instance, NULL); } return Iso15693_3ErrorNone; @@ -80,8 +80,7 @@ static Iso15693_3Error iso15693_3_poller_frame_exchange( return ret; } -Iso15693_3Error - iso15693_3_poller_async_activate(Iso15693_3Poller* instance, Iso15693_3Data* data) { +Iso15693_3Error iso15693_3_poller_activate(Iso15693_3Poller* instance, Iso15693_3Data* data) { furi_assert(instance); furi_assert(instance->nfc); @@ -93,7 +92,7 @@ Iso15693_3Error instance->state = Iso15693_3PollerStateColResInProgress; // Inventory: Mandatory command - ret = iso15693_3_poller_async_inventory(instance, data->uid); + ret = iso15693_3_poller_inventory(instance, data->uid); if(ret != Iso15693_3ErrorNone) { instance->state = Iso15693_3PollerStateColResFailed; break; @@ -103,7 +102,7 @@ Iso15693_3Error // Get system info: Optional command Iso15693_3SystemInfo* system_info = &data->system_info; - ret = iso15693_3_poller_async_get_system_info(instance, system_info); + ret = iso15693_3_poller_get_system_info(instance, system_info); if(ret != Iso15693_3ErrorNone) { ret = iso15693_3_poller_filter_error(ret); break; @@ -111,7 +110,7 @@ Iso15693_3Error // Read blocks: Optional command simple_array_init(data->block_data, system_info->block_count * system_info->block_size); - ret = iso15693_3_poller_async_read_blocks( + ret = iso15693_3_poller_read_blocks( instance, simple_array_get_data(data->block_data), system_info->block_count, @@ -124,7 +123,7 @@ Iso15693_3Error // Get block security status: Optional command simple_array_init(data->block_security, system_info->block_count); - ret = iso15693_3_poller_async_get_blocks_security( + ret = iso15693_3_poller_get_blocks_security( instance, simple_array_get_data(data->block_security), system_info->block_count); if(ret != Iso15693_3ErrorNone) { ret = iso15693_3_poller_filter_error(ret); @@ -136,7 +135,7 @@ Iso15693_3Error return ret; } -Iso15693_3Error iso15693_3_poller_async_inventory(Iso15693_3Poller* instance, uint8_t* uid) { +Iso15693_3Error iso15693_3_poller_inventory(Iso15693_3Poller* instance, uint8_t* uid) { furi_assert(instance); furi_assert(instance->nfc); furi_assert(uid); @@ -165,9 +164,8 @@ Iso15693_3Error iso15693_3_poller_async_inventory(Iso15693_3Poller* instance, ui return ret; } -Iso15693_3Error iso15693_3_poller_async_get_system_info( - Iso15693_3Poller* instance, - Iso15693_3SystemInfo* data) { +Iso15693_3Error + iso15693_3_poller_get_system_info(Iso15693_3Poller* instance, Iso15693_3SystemInfo* data) { furi_assert(instance); furi_assert(data); @@ -193,7 +191,7 @@ Iso15693_3Error iso15693_3_poller_async_get_system_info( return ret; } -Iso15693_3Error iso15693_3_poller_async_read_block( +Iso15693_3Error iso15693_3_poller_read_block( Iso15693_3Poller* instance, uint8_t* data, uint8_t block_number, @@ -222,7 +220,7 @@ Iso15693_3Error iso15693_3_poller_async_read_block( return ret; } -Iso15693_3Error iso15693_3_poller_async_read_blocks( +Iso15693_3Error iso15693_3_poller_read_blocks( Iso15693_3Poller* instance, uint8_t* data, uint16_t block_count, @@ -235,14 +233,14 @@ Iso15693_3Error iso15693_3_poller_async_read_blocks( Iso15693_3Error ret = Iso15693_3ErrorNone; for(uint32_t i = 0; i < block_count; ++i) { - ret = iso15693_3_poller_async_read_block(instance, &data[block_size * i], i, block_size); + ret = iso15693_3_poller_read_block(instance, &data[block_size * i], i, block_size); if(ret != Iso15693_3ErrorNone) break; } return ret; } -Iso15693_3Error iso15693_3_poller_async_get_blocks_security( +Iso15693_3Error iso15693_3_poller_get_blocks_security( Iso15693_3Poller* instance, uint8_t* data, uint16_t block_count) { diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.h b/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.h index 154ee684c97..346d0d724c5 100644 --- a/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.h +++ b/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.h @@ -35,36 +35,6 @@ struct Iso15693_3Poller { const Iso15693_3Data* iso15693_3_poller_get_data(Iso15693_3Poller* instance); -Iso15693_3Error iso15693_3_poller_async_activate(Iso15693_3Poller* instance, Iso15693_3Data* data); - -Iso15693_3Error iso15693_3_poller_async_inventory(Iso15693_3Poller* instance, uint8_t* uid); - -Iso15693_3Error - iso15693_3_poller_async_get_system_info(Iso15693_3Poller* instance, Iso15693_3SystemInfo* data); - -Iso15693_3Error iso15693_3_poller_async_read_block( - Iso15693_3Poller* instance, - uint8_t* data, - uint8_t block_number, - uint8_t block_size); - -Iso15693_3Error iso15693_3_poller_async_read_blocks( - Iso15693_3Poller* instance, - uint8_t* data, - uint16_t block_count, - uint8_t block_size); - -Iso15693_3Error iso15693_3_poller_async_get_blocks_security( - Iso15693_3Poller* instance, - uint8_t* data, - uint16_t block_count); - -Iso15693_3Error iso15693_3_poller_send_frame( - Iso15693_3Poller* instance, - const BitBuffer* tx_buffer, - BitBuffer* rx_buffer, - uint32_t fwt); - #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index 3eba6ee5694..dbc32a1b51c 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -85,7 +85,7 @@ NfcCommand mf_classic_poller_handler_detect_type(MfClassicPoller* instance) { iso14443_3a_copy( instance->data->iso14443_3a_data, iso14443_3a_poller_get_data(instance->iso14443_3a_poller)); - MfClassicError error = mf_classic_async_get_nt(instance, 254, MfClassicKeyTypeA, NULL); + MfClassicError error = mf_classic_poller_get_nt(instance, 254, MfClassicKeyTypeA, NULL); if(error == MfClassicErrorNone) { instance->data->type = MfClassicType4k; instance->state = MfClassicPollerStateStart; @@ -95,7 +95,7 @@ NfcCommand mf_classic_poller_handler_detect_type(MfClassicPoller* instance) { instance->current_type_check = MfClassicType1k; } } else if(instance->current_type_check == MfClassicType1k) { - MfClassicError error = mf_classic_async_get_nt(instance, 62, MfClassicKeyTypeA, NULL); + MfClassicError error = mf_classic_poller_get_nt(instance, 62, MfClassicKeyTypeA, NULL); if(error == MfClassicErrorNone) { instance->data->type = MfClassicType1k; FURI_LOG_D(TAG, "1K detected"); @@ -234,7 +234,7 @@ NfcCommand mf_classic_poller_handler_read_block(MfClassicPoller* instance) { do { // Authenticate to sector - error = mf_classic_async_auth( + error = mf_classic_poller_auth( instance, write_ctx->current_block, auth_key, write_ctx->key_type_read, NULL); if(error != MfClassicErrorNone) { FURI_LOG_D(TAG, "Failed to auth to block %d", write_ctx->current_block); @@ -243,8 +243,8 @@ NfcCommand mf_classic_poller_handler_read_block(MfClassicPoller* instance) { } // Read block from tag - error = - mf_classic_async_read_block(instance, write_ctx->current_block, &write_ctx->tag_block); + error = mf_classic_poller_read_block( + instance, write_ctx->current_block, &write_ctx->tag_block); if(error != MfClassicErrorNone) { FURI_LOG_D(TAG, "Failed to read block %d", write_ctx->current_block); instance->state = MfClassicPollerStateFail; @@ -252,11 +252,11 @@ NfcCommand mf_classic_poller_handler_read_block(MfClassicPoller* instance) { } if(write_ctx->is_value_block) { - mf_classic_async_halt(instance); + mf_classic_poller_halt(instance); instance->state = MfClassicPollerStateWriteValueBlock; } else { if(write_ctx->need_halt_before_write) { - mf_classic_async_halt(instance); + mf_classic_poller_halt(instance); } instance->state = MfClassicPollerStateWriteBlock; } @@ -292,7 +292,7 @@ NfcCommand mf_classic_poller_handler_write_block(MfClassicPoller* instance) { // Reauth if necessary if(write_ctx->need_halt_before_write) { - error = mf_classic_async_auth( + error = mf_classic_poller_auth( instance, write_ctx->current_block, auth_key, write_ctx->key_type_write, NULL); if(error != MfClassicErrorNone) { FURI_LOG_D( @@ -303,7 +303,7 @@ NfcCommand mf_classic_poller_handler_write_block(MfClassicPoller* instance) { } // Write block - error = mf_classic_async_write_block( + error = mf_classic_poller_write_block( instance, write_ctx->current_block, &instance->mfc_event_data.write_block_data.write_block); @@ -315,7 +315,7 @@ NfcCommand mf_classic_poller_handler_write_block(MfClassicPoller* instance) { } while(false); - mf_classic_async_halt(instance); + mf_classic_poller_halt(instance); write_ctx->current_block++; instance->state = MfClassicPollerStateCheckWriteConditions; @@ -403,18 +403,18 @@ NfcCommand mf_classic_poller_handler_write_value_block(MfClassicPoller* instance &write_ctx->sec_tr.key_b; MfClassicError error = - mf_classic_async_auth(instance, write_ctx->current_block, key, auth_key_type, NULL); + mf_classic_poller_auth(instance, write_ctx->current_block, key, auth_key_type, NULL); if(error != MfClassicErrorNone) break; - error = mf_classic_async_value_cmd(instance, write_ctx->current_block, value_cmd, diff); + error = mf_classic_poller_value_cmd(instance, write_ctx->current_block, value_cmd, diff); if(error != MfClassicErrorNone) break; - error = mf_classic_async_value_transfer(instance, write_ctx->current_block); + error = mf_classic_poller_value_transfer(instance, write_ctx->current_block); if(error != MfClassicErrorNone) break; } while(false); - mf_classic_async_halt(instance); + mf_classic_poller_halt(instance); write_ctx->is_value_block = false; write_ctx->current_block++; instance->state = MfClassicPollerStateCheckWriteConditions; @@ -462,7 +462,7 @@ NfcCommand mf_classic_poller_handler_request_read_sector_blocks(MfClassicPoller* sec_read_ctx->current_block, sec_read_ctx->key_type == MfClassicKeyTypeA ? 'A' : 'B', key); - error = mf_classic_async_auth( + error = mf_classic_poller_auth( instance, sec_read_ctx->current_block, &sec_read_ctx->key, @@ -481,7 +481,7 @@ NfcCommand mf_classic_poller_handler_request_read_sector_blocks(MfClassicPoller* FURI_LOG_D(TAG, "Reading block %d", sec_read_ctx->current_block); MfClassicBlock read_block = {}; - error = mf_classic_async_read_block(instance, sec_read_ctx->current_block, &read_block); + error = mf_classic_poller_read_block(instance, sec_read_ctx->current_block, &read_block); if(error == MfClassicErrorNone) { mf_classic_set_block_read(instance->data, sec_read_ctx->current_block, &read_block); if(sec_read_ctx->key_type == MfClassicKeyTypeA) { @@ -489,7 +489,7 @@ NfcCommand mf_classic_poller_handler_request_read_sector_blocks(MfClassicPoller* instance, sec_read_ctx->current_block, &read_block); } } else { - mf_classic_async_halt(instance); + mf_classic_poller_halt(instance); sec_read_ctx->auth_passed = false; } } while(false); @@ -497,7 +497,7 @@ NfcCommand mf_classic_poller_handler_request_read_sector_blocks(MfClassicPoller* uint8_t sec_tr_num = mf_classic_get_sector_trailer_num_by_sector(sec_read_ctx->current_sector); sec_read_ctx->current_block++; if(sec_read_ctx->current_block > sec_tr_num) { - mf_classic_async_halt(instance); + mf_classic_poller_halt(instance); instance->state = MfClassicPollerStateRequestReadSector; } @@ -532,7 +532,7 @@ NfcCommand mf_classic_poller_handler_auth_a(MfClassicPoller* instance) { uint64_t key = nfc_util_bytes2num(dict_attack_ctx->current_key.data, sizeof(MfClassicKey)); FURI_LOG_D(TAG, "Auth to block %d with key A: %06llx", block, key); - MfClassicError error = mf_classic_async_auth( + MfClassicError error = mf_classic_poller_auth( instance, block, &dict_attack_ctx->current_key, MfClassicKeyTypeA, NULL); if(error == MfClassicErrorNone) { FURI_LOG_I(TAG, "Key A found"); @@ -545,7 +545,7 @@ NfcCommand mf_classic_poller_handler_auth_a(MfClassicPoller* instance) { dict_attack_ctx->auth_passed = true; instance->state = MfClassicPollerStateReadSector; } else { - mf_classic_async_halt(instance); + mf_classic_poller_halt(instance); instance->state = MfClassicPollerStateAuthKeyB; } } @@ -570,7 +570,7 @@ NfcCommand mf_classic_poller_handler_auth_b(MfClassicPoller* instance) { uint64_t key = nfc_util_bytes2num(dict_attack_ctx->current_key.data, sizeof(MfClassicKey)); FURI_LOG_D(TAG, "Auth to block %d with key B: %06llx", block, key); - MfClassicError error = mf_classic_async_auth( + MfClassicError error = mf_classic_poller_auth( instance, block, &dict_attack_ctx->current_key, MfClassicKeyTypeB, NULL); if(error == MfClassicErrorNone) { FURI_LOG_I(TAG, "Key B found"); @@ -584,7 +584,7 @@ NfcCommand mf_classic_poller_handler_auth_b(MfClassicPoller* instance) { dict_attack_ctx->auth_passed = true; instance->state = MfClassicPollerStateReadSector; } else { - mf_classic_async_halt(instance); + mf_classic_poller_halt(instance); instance->state = MfClassicPollerStateRequestKey; } } @@ -621,7 +621,7 @@ NfcCommand mf_classic_poller_handler_read_sector(MfClassicPoller* instance) { if(mf_classic_is_block_read(instance->data, block_num)) break; if(!dict_attack_ctx->auth_passed) { - error = mf_classic_async_auth( + error = mf_classic_poller_auth( instance, block_num, &dict_attack_ctx->current_key, @@ -635,10 +635,10 @@ NfcCommand mf_classic_poller_handler_read_sector(MfClassicPoller* instance) { } FURI_LOG_D(TAG, "Reading block %d", block_num); - error = mf_classic_async_read_block(instance, block_num, &block); + error = mf_classic_poller_read_block(instance, block_num, &block); if(error != MfClassicErrorNone) { - mf_classic_async_halt(instance); + mf_classic_poller_halt(instance); dict_attack_ctx->auth_passed = false; FURI_LOG_D(TAG, "Failed to read block %d", block_num); } else { @@ -655,7 +655,7 @@ NfcCommand mf_classic_poller_handler_read_sector(MfClassicPoller* instance) { if(dict_attack_ctx->current_block > sec_tr_block_num) { mf_classic_poller_handle_data_update(instance); - mf_classic_async_halt(instance); + mf_classic_poller_halt(instance); dict_attack_ctx->auth_passed = false; if(dict_attack_ctx->current_sector == instance->sectors_total) { @@ -713,7 +713,7 @@ NfcCommand mf_classic_poller_handler_key_reuse_auth_key_a(MfClassicPoller* insta uint64_t key = nfc_util_bytes2num(dict_attack_ctx->current_key.data, sizeof(MfClassicKey)); FURI_LOG_D(TAG, "Key attack auth to block %d with key A: %06llx", block, key); - MfClassicError error = mf_classic_async_auth( + MfClassicError error = mf_classic_poller_auth( instance, block, &dict_attack_ctx->current_key, MfClassicKeyTypeA, NULL); if(error == MfClassicErrorNone) { FURI_LOG_I(TAG, "Key A found"); @@ -726,7 +726,7 @@ NfcCommand mf_classic_poller_handler_key_reuse_auth_key_a(MfClassicPoller* insta dict_attack_ctx->auth_passed = true; instance->state = MfClassicPollerStateKeyReuseReadSector; } else { - mf_classic_async_halt(instance); + mf_classic_poller_halt(instance); dict_attack_ctx->auth_passed = false; instance->state = MfClassicPollerStateKeyReuseStart; } @@ -748,7 +748,7 @@ NfcCommand mf_classic_poller_handler_key_reuse_auth_key_b(MfClassicPoller* insta uint64_t key = nfc_util_bytes2num(dict_attack_ctx->current_key.data, sizeof(MfClassicKey)); FURI_LOG_D(TAG, "Key attack auth to block %d with key B: %06llx", block, key); - MfClassicError error = mf_classic_async_auth( + MfClassicError error = mf_classic_poller_auth( instance, block, &dict_attack_ctx->current_key, MfClassicKeyTypeB, NULL); if(error == MfClassicErrorNone) { FURI_LOG_I(TAG, "Key B found"); @@ -762,7 +762,7 @@ NfcCommand mf_classic_poller_handler_key_reuse_auth_key_b(MfClassicPoller* insta dict_attack_ctx->auth_passed = true; instance->state = MfClassicPollerStateKeyReuseReadSector; } else { - mf_classic_async_halt(instance); + mf_classic_poller_halt(instance); dict_attack_ctx->auth_passed = false; instance->state = MfClassicPollerStateKeyReuseStart; } @@ -783,7 +783,7 @@ NfcCommand mf_classic_poller_handler_key_reuse_read_sector(MfClassicPoller* inst if(mf_classic_is_block_read(instance->data, block_num)) break; if(!dict_attack_ctx->auth_passed) { - error = mf_classic_async_auth( + error = mf_classic_poller_auth( instance, block_num, &dict_attack_ctx->current_key, @@ -796,10 +796,10 @@ NfcCommand mf_classic_poller_handler_key_reuse_read_sector(MfClassicPoller* inst } FURI_LOG_D(TAG, "Reading block %d", block_num); - error = mf_classic_async_read_block(instance, block_num, &block); + error = mf_classic_poller_read_block(instance, block_num, &block); if(error != MfClassicErrorNone) { - mf_classic_async_halt(instance); + mf_classic_poller_halt(instance); dict_attack_ctx->auth_passed = false; FURI_LOG_D(TAG, "Failed to read block %d", block_num); } else { @@ -814,7 +814,7 @@ NfcCommand mf_classic_poller_handler_key_reuse_read_sector(MfClassicPoller* inst mf_classic_get_sector_trailer_num_by_sector(dict_attack_ctx->reuse_key_sector); dict_attack_ctx->current_block++; if(dict_attack_ctx->current_block > sec_tr_block_num) { - mf_classic_async_halt(instance); + mf_classic_poller_halt(instance); dict_attack_ctx->auth_passed = false; mf_classic_poller_handle_data_update(instance); diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.h b/lib/nfc/protocols/mf_classic/mf_classic_poller.h index da1f3c3dce5..f05a6800ad1 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.h @@ -7,103 +7,274 @@ extern "C" { #endif +/** + * @brief MfClassicPoller opaque type definition. + */ typedef struct MfClassicPoller MfClassicPoller; +/** + * @brief Enumeration of possible MfClassic poller event types. + */ typedef enum { - // Start event - MfClassicPollerEventTypeRequestMode, - - // Read with key cache events - MfClassicPollerEventTypeRequestReadSector, - - // Write events - MfClassicPollerEventTypeRequestSectorTrailer, - MfClassicPollerEventTypeRequestWriteBlock, - - // Dictionary attack events - MfClassicPollerEventTypeRequestKey, - MfClassicPollerEventTypeNextSector, - MfClassicPollerEventTypeDataUpdate, - MfClassicPollerEventTypeFoundKeyA, - MfClassicPollerEventTypeFoundKeyB, - MfClassicPollerEventTypeCardNotDetected, - MfClassicPollerEventTypeKeyAttackStart, - MfClassicPollerEventTypeKeyAttackStop, - MfClassicPollerEventTypeKeyAttackNextSector, - - // Common events - MfClassicPollerEventTypeCardDetected, - MfClassicPollerEventTypeCardLost, - MfClassicPollerEventTypeSuccess, - MfClassicPollerEventTypeFail, + MfClassicPollerEventTypeRequestMode, /**< Poller requests to fill the mode. */ + + MfClassicPollerEventTypeRequestReadSector, /**< Poller requests data to read sector. */ + + MfClassicPollerEventTypeRequestSectorTrailer, /**< Poller requests sector trailer for writing block. */ + MfClassicPollerEventTypeRequestWriteBlock, /**< Poller requests data to write block. */ + + MfClassicPollerEventTypeRequestKey, /**< Poller requests key for sector authentication. */ + MfClassicPollerEventTypeNextSector, /**< Poller switches to next sector during dictionary attack. */ + MfClassicPollerEventTypeDataUpdate, /**< Poller updates data. */ + MfClassicPollerEventTypeFoundKeyA, /**< Poller found key A. */ + MfClassicPollerEventTypeFoundKeyB, /**< Poller found key B. */ + MfClassicPollerEventTypeKeyAttackStart, /**< Poller starts key attack. */ + MfClassicPollerEventTypeKeyAttackStop, /**< Poller stops key attack. */ + MfClassicPollerEventTypeKeyAttackNextSector, /**< Poller switches to next sector during key attack. */ + + MfClassicPollerEventTypeCardDetected, /**< Poller detected card. */ + MfClassicPollerEventTypeCardLost, /**< Poller lost card. */ + MfClassicPollerEventTypeSuccess, /**< Poller succeeded. */ + MfClassicPollerEventTypeFail, /**< Poller failed. */ } MfClassicPollerEventType; +/** + * @brief MfClassic poller mode. + */ typedef enum { - MfClassicPollerModeRead, - MfClassicPollerModeWrite, - MfClassicPollerModeDictAttack, + MfClassicPollerModeRead, /**< Poller reading mode. */ + MfClassicPollerModeWrite, /**< Poller writing mode. */ + MfClassicPollerModeDictAttack, /**< Poller dictionary attack mode. */ } MfClassicPollerMode; +/** + * @brief MfClassic poller request mode event data. + * + * This instance of this structure must be filled on MfClassicPollerEventTypeRequestMode event. + */ typedef struct { - MfClassicPollerMode mode; - const MfClassicData* data; + MfClassicPollerMode mode; /**< Mode to be used by poller. */ + const MfClassicData* data; /**< Data to be used by poller. */ } MfClassicPollerEventDataRequestMode; +/** + * @brief MfClassic poller next sector event data. + * + * The instance of this structure is filled by poller and passed with + * MfClassicPollerEventTypeNextSector event. + */ typedef struct { - uint8_t current_sector; + uint8_t current_sector; /**< Current sector number. */ } MfClassicPollerEventDataDictAttackNextSector; +/** + * @brief MfClassic poller update event data. + * + * The instance of this structure is filled by poller and passed with + * MfClassicPollerEventTypeDataUpdate event. + */ typedef struct { - uint8_t sectors_read; - uint8_t keys_found; - uint8_t current_sector; + uint8_t sectors_read; /**< Number of sectors read. */ + uint8_t keys_found; /**< Number of keys found. */ + uint8_t current_sector; /**< Current sector number. */ } MfClassicPollerEventDataUpdate; +/** + * @brief MfClassic poller key request event data. + * + * The instance of this structure must be filled on MfClassicPollerEventTypeRequestKey event. + */ typedef struct { - MfClassicKey key; - bool key_provided; + MfClassicKey key; /**< Key to be used by poller. */ + bool key_provided; /**< Flag indicating if key is provided. */ } MfClassicPollerEventDataKeyRequest; +/** + * @brief MfClassic poller read sector request event data. + * + * The instance of this structure must be filled on MfClassicPollerEventTypeRequestReadSector event. + */ typedef struct { - uint8_t sector_num; - MfClassicKey key; - MfClassicKeyType key_type; - bool key_provided; + uint8_t sector_num; /**< Sector number to be read. */ + MfClassicKey key; /**< Key to be used by poller. */ + MfClassicKeyType key_type; /**< Key type to be used by poller. */ + bool key_provided; /**< Flag indicating if key is provided. */ } MfClassicPollerEventDataReadSectorRequest; +/** + * @brief MfClassic poller sector trailer request event data. + * + * The instance of this structure must be filled on MfClassicPollerEventTypeRequestSectorTrailer event. + */ typedef struct { - uint8_t sector_num; - MfClassicBlock sector_trailer; - bool sector_trailer_provided; + uint8_t sector_num; /**< Sector number to be read. */ + MfClassicBlock sector_trailer; /**< Sector trailer to be used by poller. */ + bool sector_trailer_provided; /**< Flag indicating if sector trailer is provided. */ } MfClassicPollerEventDataSectorTrailerRequest; +/** + * @brief MfClassic poller write block request event data. + * + * The instance of this structure must be filled on MfClassicPollerEventTypeRequestWriteBlock event. + */ typedef struct { - uint8_t block_num; - MfClassicBlock write_block; - bool write_block_provided; + uint8_t block_num; /**< Block number to be written. */ + MfClassicBlock write_block; /**< Block to be written. */ + bool write_block_provided; /**< Flag indicating if block is provided. */ } MfClassicPollerEventDataWriteBlockRequest; +/** + * @brief MfClassic poller key attack event data. + * + * The instance of this structure is filled by poller and passed with + * MfClassicPollerEventTypeKeyAttackNextSector event. + */ typedef struct { - uint8_t current_sector; + uint8_t current_sector; /**< Current sector number. */ } MfClassicPollerEventKeyAttackData; +/** + * @brief MfClassic poller event data. + */ typedef union { - MfClassicError error; - MfClassicPollerEventDataRequestMode poller_mode; - MfClassicPollerEventDataDictAttackNextSector next_sector_data; - MfClassicPollerEventDataKeyRequest key_request_data; - MfClassicPollerEventDataUpdate data_update; - MfClassicPollerEventDataReadSectorRequest read_sector_request_data; - MfClassicPollerEventKeyAttackData key_attack_data; - MfClassicPollerEventDataSectorTrailerRequest sec_tr_data; - MfClassicPollerEventDataWriteBlockRequest write_block_data; + MfClassicError error; /**< Error code on MfClassicPollerEventTypeFail event. */ + MfClassicPollerEventDataRequestMode poller_mode; /**< Poller mode context. */ + MfClassicPollerEventDataDictAttackNextSector next_sector_data; /**< Next sector context. */ + MfClassicPollerEventDataKeyRequest key_request_data; /**< Key request context. */ + MfClassicPollerEventDataUpdate data_update; /**< Data update context. */ + MfClassicPollerEventDataReadSectorRequest + read_sector_request_data; /**< Read sector request context. */ + MfClassicPollerEventKeyAttackData key_attack_data; /**< Key attack context. */ + MfClassicPollerEventDataSectorTrailerRequest sec_tr_data; /**< Sector trailer request context. */ + MfClassicPollerEventDataWriteBlockRequest write_block_data; /**< Write block request context. */ } MfClassicPollerEventData; +/** + * @brief MfClassic poller event. + * + * Upon emission of an event, an instance of this struct will be passed to the callback. + */ typedef struct { - MfClassicPollerEventType type; - MfClassicPollerEventData* data; + MfClassicPollerEventType type; /**< Event type. */ + MfClassicPollerEventData* data; /**< Pointer to event specific data. */ } MfClassicPollerEvent; +/** + * @brief Collect tag nonce during authentication. + * + * Must ONLY be used inside the callback function. + * + * Starts authentication procedure and collects tag nonce. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] block_num block number for authentication. + * @param[in] key_type key type to be used for authentication. + * @param[out] nt pointer to the MfClassicNt structure to be filled with nonce data. + * @return MfClassicErrorNone on success, an error code on failure. + */ +MfClassicError mf_classic_poller_get_nt( + MfClassicPoller* instance, + uint8_t block_num, + MfClassicKeyType key_type, + MfClassicNt* nt); + +/** + * @brief Perform authentication. + * + * Must ONLY be used inside the callback function. + * + * Perform authentication as specified in Mf Classic protocol. Initialize crypto state for futher + * communication with the tag. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] block_num block number for authentication. + * @param[in] key key to be used for authentication. + * @param[in] key_type key type to be used for authentication. + * @param[out] data pointer to MfClassicAuthContext structure to be filled with authentication data. + * @return MfClassicErrorNone on success, an error code on failure. + */ +MfClassicError mf_classic_poller_auth( + MfClassicPoller* instance, + uint8_t block_num, + MfClassicKey* key, + MfClassicKeyType key_type, + MfClassicAuthContext* data); + +/** + * @brief Halt the tag. + * + * Must ONLY be used inside the callback function. + * + * Halt the tag and reset crypto state of the poller. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @return MfClassicErrorNone on success, an error code on failure. + */ +MfClassicError mf_classic_poller_halt(MfClassicPoller* instance); + +/** + * @brief Read block from tag. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] block_num block number to be read. + * @param[out] data pointer to the MfClassicBlock structure to be filled with block data. + * @return MfClassicErrorNone on success, an error code on failure. + */ +MfClassicError mf_classic_poller_read_block( + MfClassicPoller* instance, + uint8_t block_num, + MfClassicBlock* data); + +/** + * @brief Write block to tag. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] block_num block number to be written. + * @param[in] data pointer to the MfClassicBlock structure to be written. + * @return MfClassicErrorNone on success, an error code on failure. + */ +MfClassicError mf_classic_poller_write_block( + MfClassicPoller* instance, + uint8_t block_num, + MfClassicBlock* data); + +/** + * @brief Perform value command on tag. + * + * Must ONLY be used inside the callback function. + * + * Perform Increment, Decrement or Restore command on tag. The result is stored in internal transfer + * block of the tag. Use mf_classic_poller_value_transfer to transfer the result to the tag. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] block_num block number to be used for value command. + * @param[in] cmd value command to be performed. + * @param[in] data value to be used for value command. + * @return MfClassicErrorNone on success, an error code on failure. + */ +MfClassicError mf_classic_poller_value_cmd( + MfClassicPoller* instance, + uint8_t block_num, + MfClassicValueCommand cmd, + int32_t data); + +/** + * @brief Transfer internal transfer block to tag. + * + * Must ONLY be used inside the callback function. + * + * Transfer internal transfer block to tag. The block is filled by mf_classic_poller_value_cmd. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] block_num block number to be used for value command. + * @return MfClassicErrorNone on success, an error code on failure. + */ +MfClassicError mf_classic_poller_value_transfer(MfClassicPoller* instance, uint8_t block_num); + #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c index 7eab4fe3b5c..4b071815ea8 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c @@ -33,7 +33,7 @@ MfClassicError mf_classic_process_error(Iso14443_3aError error) { return ret; } -MfClassicError mf_classic_async_get_nt( +MfClassicError mf_classic_poller_get_nt( MfClassicPoller* instance, uint8_t block_num, MfClassicKeyType key_type, @@ -69,7 +69,7 @@ MfClassicError mf_classic_async_get_nt( return ret; } -MfClassicError mf_classic_async_auth( +MfClassicError mf_classic_poller_auth( MfClassicPoller* instance, uint8_t block_num, MfClassicKey* key, @@ -84,7 +84,7 @@ MfClassicError mf_classic_async_auth( iso14443_3a_poller_get_data(instance->iso14443_3a_poller)); MfClassicNt nt = {}; - ret = mf_classic_async_get_nt(instance, block_num, key_type, &nt); + ret = mf_classic_poller_get_nt(instance, block_num, key_type, &nt); if(ret != MfClassicErrorNone) break; if(data) { data->nt = nt; @@ -130,7 +130,7 @@ MfClassicError mf_classic_async_auth( return ret; } -MfClassicError mf_classic_async_halt(MfClassicPoller* instance) { +MfClassicError mf_classic_poller_halt(MfClassicPoller* instance) { MfClassicError ret = MfClassicErrorNone; Iso14443_3aError error = Iso14443_3aErrorNone; @@ -158,7 +158,7 @@ MfClassicError mf_classic_async_halt(MfClassicPoller* instance) { return ret; } -MfClassicError mf_classic_async_read_block( +MfClassicError mf_classic_poller_read_block( MfClassicPoller* instance, uint8_t block_num, MfClassicBlock* data) { @@ -204,7 +204,7 @@ MfClassicError mf_classic_async_read_block( return ret; } -MfClassicError mf_classic_async_write_block( +MfClassicError mf_classic_poller_write_block( MfClassicPoller* instance, uint8_t block_num, MfClassicBlock* data) { @@ -275,7 +275,7 @@ MfClassicError mf_classic_async_write_block( return ret; } -MfClassicError mf_classic_async_value_cmd( +MfClassicError mf_classic_poller_value_cmd( MfClassicPoller* instance, uint8_t block_num, MfClassicValueCommand cmd, @@ -345,7 +345,7 @@ MfClassicError mf_classic_async_value_cmd( return ret; } -MfClassicError mf_classic_async_value_transfer(MfClassicPoller* instance, uint8_t block_num) { +MfClassicError mf_classic_poller_value_transfer(MfClassicPoller* instance, uint8_t block_num) { MfClassicError ret = MfClassicErrorNone; Iso14443_3aError error = Iso14443_3aErrorNone; diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h index c6f4ccf7f0e..0be42196f1d 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h @@ -169,37 +169,6 @@ MfClassicPoller* mf_classic_poller_alloc(Iso14443_3aPoller* iso14443_3a_poller); void mf_classic_poller_free(MfClassicPoller* instance); -MfClassicError mf_classic_async_get_nt( - MfClassicPoller* instance, - uint8_t block_num, - MfClassicKeyType key_type, - MfClassicNt* nt); - -MfClassicError mf_classic_async_auth( - MfClassicPoller* instance, - uint8_t block_num, - MfClassicKey* key, - MfClassicKeyType key_type, - MfClassicAuthContext* data); - -MfClassicError mf_classic_async_halt(MfClassicPoller* instance); - -MfClassicError - mf_classic_async_read_block(MfClassicPoller* instance, uint8_t block_num, MfClassicBlock* data); - -MfClassicError mf_classic_async_write_block( - MfClassicPoller* instance, - uint8_t block_num, - MfClassicBlock* data); - -MfClassicError mf_classic_async_value_cmd( - MfClassicPoller* instance, - uint8_t block_num, - MfClassicValueCommand cmd, - int32_t data); - -MfClassicError mf_classic_async_value_transfer(MfClassicPoller* instance, uint8_t block_num); - #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller_sync_api.c b/lib/nfc/protocols/mf_classic/mf_classic_poller_sync.c similarity index 88% rename from lib/nfc/protocols/mf_classic/mf_classic_poller_sync_api.c rename to lib/nfc/protocols/mf_classic/mf_classic_poller_sync.c index 8b9fb69f166..69954452aa9 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_sync_api.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_sync.c @@ -1,3 +1,4 @@ +#include "mf_classic_poller_sync.h" #include "mf_classic_poller_i.h" #include @@ -32,7 +33,7 @@ typedef MfClassicError ( static MfClassicError mf_classic_poller_collect_nt_handler( MfClassicPoller* poller, MfClassicPollerContextData* data) { - return mf_classic_async_get_nt( + return mf_classic_poller_get_nt( poller, data->collect_nt_context.block, data->collect_nt_context.key_type, @@ -41,7 +42,7 @@ static MfClassicError mf_classic_poller_collect_nt_handler( static MfClassicError mf_classic_poller_auth_handler(MfClassicPoller* poller, MfClassicPollerContextData* data) { - return mf_classic_async_auth( + return mf_classic_poller_auth( poller, data->auth_context.block_num, &data->auth_context.key, @@ -55,7 +56,7 @@ static MfClassicError mf_classic_poller_read_block_handler( MfClassicError error = MfClassicErrorNone; do { - error = mf_classic_async_auth( + error = mf_classic_poller_auth( poller, data->read_block_context.block_num, &data->read_block_context.key, @@ -63,11 +64,11 @@ static MfClassicError mf_classic_poller_read_block_handler( NULL); if(error != MfClassicErrorNone) break; - error = mf_classic_async_read_block( + error = mf_classic_poller_read_block( poller, data->read_block_context.block_num, &data->read_block_context.block); if(error != MfClassicErrorNone) break; - error = mf_classic_async_halt(poller); + error = mf_classic_poller_halt(poller); if(error != MfClassicErrorNone) break; } while(false); @@ -81,7 +82,7 @@ static MfClassicError mf_classic_poller_write_block_handler( MfClassicError error = MfClassicErrorNone; do { - error = mf_classic_async_auth( + error = mf_classic_poller_auth( poller, data->read_block_context.block_num, &data->read_block_context.key, @@ -89,11 +90,11 @@ static MfClassicError mf_classic_poller_write_block_handler( NULL); if(error != MfClassicErrorNone) break; - error = mf_classic_async_write_block( + error = mf_classic_poller_write_block( poller, data->write_block_context.block_num, &data->write_block_context.block); if(error != MfClassicErrorNone) break; - error = mf_classic_async_halt(poller); + error = mf_classic_poller_halt(poller); if(error != MfClassicErrorNone) break; } while(false); @@ -107,7 +108,7 @@ static MfClassicError mf_classic_poller_read_value_handler( MfClassicError error = MfClassicErrorNone; do { - error = mf_classic_async_auth( + error = mf_classic_poller_auth( poller, data->read_value_context.block_num, &data->read_value_context.key, @@ -116,7 +117,7 @@ static MfClassicError mf_classic_poller_read_value_handler( if(error != MfClassicErrorNone) break; MfClassicBlock block = {}; - error = mf_classic_async_read_block(poller, data->read_value_context.block_num, &block); + error = mf_classic_poller_read_block(poller, data->read_value_context.block_num, &block); if(error != MfClassicErrorNone) break; if(!mf_classic_block_to_value(&block, &data->read_value_context.value, NULL)) { @@ -124,7 +125,7 @@ static MfClassicError mf_classic_poller_read_value_handler( break; } - error = mf_classic_async_halt(poller); + error = mf_classic_poller_halt(poller); if(error != MfClassicErrorNone) break; } while(false); @@ -138,7 +139,7 @@ static MfClassicError mf_classic_poller_change_value_handler( MfClassicError error = MfClassicErrorNone; do { - error = mf_classic_async_auth( + error = mf_classic_poller_auth( poller, data->change_value_context.block_num, &data->change_value_context.key, @@ -146,21 +147,21 @@ static MfClassicError mf_classic_poller_change_value_handler( NULL); if(error != MfClassicErrorNone) break; - error = mf_classic_async_value_cmd( + error = mf_classic_poller_value_cmd( poller, data->change_value_context.block_num, data->change_value_context.value_cmd, data->change_value_context.data); if(error != MfClassicErrorNone) break; - error = mf_classic_async_value_transfer(poller, data->change_value_context.block_num); + error = mf_classic_poller_value_transfer(poller, data->change_value_context.block_num); if(error != MfClassicErrorNone) break; MfClassicBlock block = {}; - error = mf_classic_async_read_block(poller, data->change_value_context.block_num, &block); + error = mf_classic_poller_read_block(poller, data->change_value_context.block_num, &block); if(error != MfClassicErrorNone) break; - error = mf_classic_async_halt(poller); + error = mf_classic_poller_halt(poller); if(error != MfClassicErrorNone) break; if(!mf_classic_block_to_value(&block, &data->change_value_context.new_value, NULL)) { @@ -182,16 +183,14 @@ static const MfClassicPollerCmdHandler mf_classic_poller_cmd_handlers[MfClassicP [MfClassicPollerCmdTypeChangeValue] = mf_classic_poller_change_value_handler, }; -static NfcCommand mf_classic_poller_cmd_callback(NfcGenericEvent event, void* context) { - furi_assert(event.instance); - furi_assert(event.protocol == NfcProtocolIso14443_3a); - furi_assert(event.event_data); +static NfcCommand mf_classic_poller_cmd_callback(NfcGenericEventEx event, void* context) { + furi_assert(event.poller); + furi_assert(event.parent_event_data); furi_assert(context); MfClassicPollerContext* poller_context = context; - Iso14443_3aPollerEvent* iso14443_3a_event = event.event_data; - Iso14443_3aPoller* iso14443_3a_poller = event.instance; - MfClassicPoller* mfc_poller = mf_classic_poller_alloc(iso14443_3a_poller); + Iso14443_3aPollerEvent* iso14443_3a_event = event.parent_event_data; + MfClassicPoller* mfc_poller = event.poller; if(iso14443_3a_event->type == Iso14443_3aPollerEventTypeReady) { poller_context->error = mf_classic_poller_cmd_handlers[poller_context->cmd_type]( @@ -202,8 +201,6 @@ static NfcCommand mf_classic_poller_cmd_callback(NfcGenericEvent event, void* co furi_thread_flags_set(poller_context->thread_id, MF_CLASSIC_POLLER_COMPLETE_EVENT); - mf_classic_poller_free(mfc_poller); - return NfcCommandStop; } @@ -212,8 +209,8 @@ static MfClassicError mf_classic_poller_cmd_execute(Nfc* nfc, MfClassicPollerCon poller_ctx->thread_id = furi_thread_get_current_id(); - NfcPoller* poller = nfc_poller_alloc(nfc, NfcProtocolIso14443_3a); - nfc_poller_start(poller, mf_classic_poller_cmd_callback, poller_ctx); + NfcPoller* poller = nfc_poller_alloc(nfc, NfcProtocolMfClassic); + nfc_poller_start_ex(poller, mf_classic_poller_cmd_callback, poller_ctx); furi_thread_flags_wait(MF_CLASSIC_POLLER_COMPLETE_EVENT, FuriFlagWaitAny, FuriWaitForever); furi_thread_flags_clear(MF_CLASSIC_POLLER_COMPLETE_EVENT); @@ -223,7 +220,7 @@ static MfClassicError mf_classic_poller_cmd_execute(Nfc* nfc, MfClassicPollerCon return poller_ctx->error; } -MfClassicError mf_classic_poller_collect_nt( +MfClassicError mf_classic_poller_sync_collect_nt( Nfc* nfc, uint8_t block_num, MfClassicKeyType key_type, @@ -247,7 +244,7 @@ MfClassicError mf_classic_poller_collect_nt( return error; } -MfClassicError mf_classic_poller_auth( +MfClassicError mf_classic_poller_sync_auth( Nfc* nfc, uint8_t block_num, MfClassicKey* key, @@ -274,7 +271,7 @@ MfClassicError mf_classic_poller_auth( return error; } -MfClassicError mf_classic_poller_read_block( +MfClassicError mf_classic_poller_sync_read_block( Nfc* nfc, uint8_t block_num, MfClassicKey* key, @@ -300,7 +297,7 @@ MfClassicError mf_classic_poller_read_block( return error; } -MfClassicError mf_classic_poller_write_block( +MfClassicError mf_classic_poller_sync_write_block( Nfc* nfc, uint8_t block_num, MfClassicKey* key, @@ -323,7 +320,7 @@ MfClassicError mf_classic_poller_write_block( return error; } -MfClassicError mf_classic_poller_read_value( +MfClassicError mf_classic_poller_sync_read_value( Nfc* nfc, uint8_t block_num, MfClassicKey* key, @@ -349,7 +346,7 @@ MfClassicError mf_classic_poller_read_value( return error; } -MfClassicError mf_classic_poller_change_value( +MfClassicError mf_classic_poller_sync_change_value( Nfc* nfc, uint8_t block_num, MfClassicKey* key, @@ -461,7 +458,7 @@ NfcCommand mf_classic_poller_read_callback(NfcGenericEvent event, void* context) } MfClassicError - mf_classic_poller_read(Nfc* nfc, const MfClassicDeviceKeys* keys, MfClassicData* data) { + mf_classic_poller_sync_read(Nfc* nfc, const MfClassicDeviceKeys* keys, MfClassicData* data) { furi_assert(nfc); furi_assert(keys); furi_assert(data); @@ -498,7 +495,7 @@ MfClassicError return error; } -MfClassicError mf_classic_poller_detect_type(Nfc* nfc, MfClassicType* type) { +MfClassicError mf_classic_poller_sync_detect_type(Nfc* nfc, MfClassicType* type) { furi_assert(nfc); furi_assert(type); @@ -512,7 +509,7 @@ MfClassicError mf_classic_poller_detect_type(Nfc* nfc, MfClassicType* type) { size_t i = 0; for(i = 0; i < COUNT_OF(mf_classic_verify_block); i++) { - error = mf_classic_poller_collect_nt( + error = mf_classic_poller_sync_collect_nt( nfc, mf_classic_verify_block[MfClassicTypeNum - i - 1], MfClassicKeyTypeA, NULL); if(error == MfClassicErrorNone) { *type = MfClassicTypeNum - i - 1; diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller_sync_api.h b/lib/nfc/protocols/mf_classic/mf_classic_poller_sync.h similarity index 64% rename from lib/nfc/protocols/mf_classic/mf_classic_poller_sync_api.h rename to lib/nfc/protocols/mf_classic/mf_classic_poller_sync.h index 11db291b801..d384e46e4e8 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_sync_api.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_sync.h @@ -7,41 +7,41 @@ extern "C" { #endif -MfClassicError mf_classic_poller_collect_nt( +MfClassicError mf_classic_poller_sync_collect_nt( Nfc* nfc, uint8_t block_num, MfClassicKeyType key_type, MfClassicNt* nt); -MfClassicError mf_classic_poller_auth( +MfClassicError mf_classic_poller_sync_auth( Nfc* nfc, uint8_t block_num, MfClassicKey* key, MfClassicKeyType key_type, MfClassicAuthContext* data); -MfClassicError mf_classic_poller_read_block( +MfClassicError mf_classic_poller_sync_read_block( Nfc* nfc, uint8_t block_num, MfClassicKey* key, MfClassicKeyType key_type, MfClassicBlock* data); -MfClassicError mf_classic_poller_write_block( +MfClassicError mf_classic_poller_sync_write_block( Nfc* nfc, uint8_t block_num, MfClassicKey* key, MfClassicKeyType key_type, MfClassicBlock* data); -MfClassicError mf_classic_poller_read_value( +MfClassicError mf_classic_poller_sync_read_value( Nfc* nfc, uint8_t block_num, MfClassicKey* key, MfClassicKeyType key_type, int32_t* value); -MfClassicError mf_classic_poller_change_value( +MfClassicError mf_classic_poller_sync_change_value( Nfc* nfc, uint8_t block_num, MfClassicKey* key, @@ -49,10 +49,10 @@ MfClassicError mf_classic_poller_change_value( int32_t data, int32_t* new_value); -MfClassicError mf_classic_poller_detect_type(Nfc* nfc, MfClassicType* type); +MfClassicError mf_classic_poller_sync_detect_type(Nfc* nfc, MfClassicType* type); MfClassicError - mf_classic_poller_read(Nfc* nfc, const MfClassicDeviceKeys* keys, MfClassicData* data); + mf_classic_poller_sync_read(Nfc* nfc, const MfClassicDeviceKeys* keys, MfClassicData* data); #ifdef __cplusplus } diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c b/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c index 11db021d57c..5af033d4ca6 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c @@ -61,7 +61,7 @@ static NfcCommand mf_desfire_poller_handler_idle(MfDesfirePoller* instance) { } static NfcCommand mf_desfire_poller_handler_read_version(MfDesfirePoller* instance) { - instance->error = mf_desfire_poller_async_read_version(instance, &instance->data->version); + instance->error = mf_desfire_poller_read_version(instance, &instance->data->version); if(instance->error == MfDesfireErrorNone) { FURI_LOG_D(TAG, "Read version success"); instance->state = MfDesfirePollerStateReadFreeMemory; @@ -75,8 +75,7 @@ static NfcCommand mf_desfire_poller_handler_read_version(MfDesfirePoller* instan } static NfcCommand mf_desfire_poller_handler_read_free_memory(MfDesfirePoller* instance) { - instance->error = - mf_desfire_poller_async_read_free_memory(instance, &instance->data->free_memory); + instance->error = mf_desfire_poller_read_free_memory(instance, &instance->data->free_memory); if(instance->error == MfDesfireErrorNone) { FURI_LOG_D(TAG, "Read free memory success"); instance->state = MfDesfirePollerStateReadMasterKeySettings; @@ -91,7 +90,7 @@ static NfcCommand mf_desfire_poller_handler_read_free_memory(MfDesfirePoller* in static NfcCommand mf_desfire_poller_handler_read_master_key_settings(MfDesfirePoller* instance) { instance->error = - mf_desfire_poller_async_read_key_settings(instance, &instance->data->master_key_settings); + mf_desfire_poller_read_key_settings(instance, &instance->data->master_key_settings); if(instance->error == MfDesfireErrorNone) { FURI_LOG_D(TAG, "Read master key settings success"); instance->state = MfDesfirePollerStateReadMasterKeyVersion; @@ -105,7 +104,7 @@ static NfcCommand mf_desfire_poller_handler_read_master_key_settings(MfDesfirePo } static NfcCommand mf_desfire_poller_handler_read_master_key_version(MfDesfirePoller* instance) { - instance->error = mf_desfire_poller_async_read_key_versions( + instance->error = mf_desfire_poller_read_key_versions( instance, instance->data->master_key_versions, instance->data->master_key_settings.max_keys); @@ -123,7 +122,7 @@ static NfcCommand mf_desfire_poller_handler_read_master_key_version(MfDesfirePol static NfcCommand mf_desfire_poller_handler_read_application_ids(MfDesfirePoller* instance) { instance->error = - mf_desfire_poller_async_read_application_ids(instance, instance->data->application_ids); + mf_desfire_poller_read_application_ids(instance, instance->data->application_ids); if(instance->error == MfDesfireErrorNone) { FURI_LOG_D(TAG, "Read application ids success"); instance->state = MfDesfirePollerStateReadApplications; @@ -137,7 +136,7 @@ static NfcCommand mf_desfire_poller_handler_read_application_ids(MfDesfirePoller } static NfcCommand mf_desfire_poller_handler_read_applications(MfDesfirePoller* instance) { - instance->error = mf_desfire_poller_async_read_applications( + instance->error = mf_desfire_poller_read_applications( instance, instance->data->application_ids, instance->data->applications); if(instance->error == MfDesfireErrorNone) { FURI_LOG_D(TAG, "Read applications success"); @@ -227,7 +226,7 @@ static bool mf_desfire_poller_detect(NfcGenericEvent event, void* context) { if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeReady) { MfDesfireVersion version = {}; - const MfDesfireError error = mf_desfire_poller_async_read_version(instance, &version); + const MfDesfireError error = mf_desfire_poller_read_version(instance, &version); protocol_detected = (error == MfDesfireErrorNone); } diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_poller.h b/lib/nfc/protocols/mf_desfire/mf_desfire_poller.h index 360b0508ff0..6ef2f3f68d4 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_poller.h +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_poller.h @@ -8,24 +8,267 @@ extern "C" { #endif +/** + * @brief MfDesfirePoller opaque type definition. + */ typedef struct MfDesfirePoller MfDesfirePoller; +/** + * @brief Enumeration of possible MfDesfire poller event types. + */ typedef enum { - MfDesfirePollerEventTypeReadSuccess, - MfDesfirePollerEventTypeReadFailed, + MfDesfirePollerEventTypeReadSuccess, /**< Card was read successfully. */ + MfDesfirePollerEventTypeReadFailed, /**< Poller failed to read card. */ } MfDesfirePollerEventType; -typedef struct { - union { - MfDesfireError error; - }; +/** + * @brief MfDesfire poller event data. + */ +typedef union { + MfDesfireError error; /**< Error code indicating card reading fail reason. */ } MfDesfirePollerEventData; +/** + * @brief MfDesfire poller event structure. + * + * Upon emission of an event, an instance of this struct will be passed to the callback. + */ typedef struct { - MfDesfirePollerEventType type; - MfDesfirePollerEventData* data; + MfDesfirePollerEventType type; /**< Type of emmitted event. */ + MfDesfirePollerEventData* data; /**< Pointer to event specific data. */ } MfDesfirePollerEvent; +/** + * @brief Transmit and receive MfDesfire chunks in poller mode. + * + * Must ONLY be used inside the callback function. + * + * The rx_buffer will be filled with any data received as a response to data + * sent from tx_buffer, with a timeout defined by the fwt parameter. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] tx_buffer pointer to the buffer containing the data to be transmitted. + * @param[out] rx_buffer pointer to the buffer to be filled with received data. + * @return MfDesfireErrorNone on success, an error code on failure. + */ +MfDesfireError mf_desfire_send_chunks( + MfDesfirePoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer); + +/** + * @brief Read MfDesfire card version. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[out] data pointer to the MfDesfireVersion structure to be filled with version data. + * @return MfDesfireErrorNone on success, an error code on failure. + */ +MfDesfireError mf_desfire_poller_read_version(MfDesfirePoller* instance, MfDesfireVersion* data); + +/** + * @brief Read free memory available on MfDesfire card. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[out] data pointer to the MfDesfireFreeMemory structure to be filled with free memory data. + * @return MfDesfireErrorNone on success, an error code on failure. + */ +MfDesfireError + mf_desfire_poller_read_free_memory(MfDesfirePoller* instance, MfDesfireFreeMemory* data); + +/** + * @brief Read key settings on MfDesfire card. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[out] data pointer to the MfDesfireKeySettings structure to be filled with key settings data. + * @return MfDesfireErrorNone on success, an error code on failure. + */ +MfDesfireError + mf_desfire_poller_read_key_settings(MfDesfirePoller* instance, MfDesfireKeySettings* data); + +/** + * @brief Read key versions on MfDesfire card. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[out] data pointer to the SimpleArray structure to be filled with key versions data. + * @param[in] count number of key versions to read. + * @return MfDesfireErrorNone on success, an error code on failure. + */ +MfDesfireError mf_desfire_poller_read_key_versions( + MfDesfirePoller* instance, + SimpleArray* data, + uint32_t count); + +/** + * @brief Read applications IDs on MfDesfire card. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[out] data pointer to the SimpleArray structure to be filled with application ids data. + * @return MfDesfireErrorNone on success, an error code on failure. + */ +MfDesfireError + mf_desfire_poller_read_application_ids(MfDesfirePoller* instance, SimpleArray* data); + +/** + * @brief Select application on MfDesfire card. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] id pointer to the MfDesfireApplicationId structure with application id to select. + * @return MfDesfireErrorNone on success, an error code on failure. + */ +MfDesfireError mf_desfire_poller_select_application( + MfDesfirePoller* instance, + const MfDesfireApplicationId* id); + +/** + * @brief Read file IDs for selected application on MfDesfire card. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[out] data pointer to the SimpleArray structure to be filled with file ids data. + * @return MfDesfireErrorNone on success, an error code on failure. + */ +MfDesfireError mf_desfire_poller_read_file_ids(MfDesfirePoller* instance, SimpleArray* data); + +/** + * @brief Read file settings on MfDesfire card. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] id file id to read settings for. + * @param[out] data pointer to the MfDesfireFileSettings structure to be filled with file settings data. + * @return MfDesfireErrorNone on success, an error code on failure. + */ +MfDesfireError mf_desfire_poller_read_file_settings( + MfDesfirePoller* instance, + MfDesfireFileId id, + MfDesfireFileSettings* data); + +/** + * @brief Read multiple file settings on MfDesfire card. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] file_ids pointer to the SimpleArray structure array with file ids to read settings for. + * @param[out] data pointer to the SimpleArray structure array to be filled with file settings data. + * @return MfDesfireErrorNone on success, an error code on failure. + */ +MfDesfireError mf_desfire_poller_read_file_settings_multi( + MfDesfirePoller* instance, + const SimpleArray* file_ids, + SimpleArray* data); + +/** + * @brief Read file data on MfDesfire card. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] id file id to read data from. + * @param[in] offset offset in bytes to start reading from. + * @param[in] size number of bytes to read. + * @param[out] data pointer to the MfDesfireFileData structure to be filled with file data. + * @return MfDesfireErrorNone on success, an error code on failure. + */ +MfDesfireError mf_desfire_poller_read_file_data( + MfDesfirePoller* instance, + MfDesfireFileId id, + uint32_t offset, + size_t size, + MfDesfireFileData* data); + +/** + * @brief Read file value on MfDesfire card. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] id file id to read value from. + * @param[out] data pointer to the MfDesfireFileData structure to be filled with file value. + * @return MfDesfireErrorNone on success, an error code on failure. + */ +MfDesfireError mf_desfire_poller_read_file_value( + MfDesfirePoller* instance, + MfDesfireFileId id, + MfDesfireFileData* data); + +/** + * @brief Read file records on MfDesfire card. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] id file id to read data from. + * @param[in] offset offset in bytes to start reading from. + * @param[in] size number of bytes to read. + * @param[out] data pointer to the MfDesfireFileData structure to be filled with file records data. + * @return MfDesfireErrorNone on success, an error code on failure. + */ +MfDesfireError mf_desfire_poller_read_file_records( + MfDesfirePoller* instance, + MfDesfireFileId id, + uint32_t offset, + size_t size, + MfDesfireFileData* data); + +/** + * @brief Read data from multiple files on MfDesfire card. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] file_ids pointer to the SimpleArray structure array with files ids to read data from. + * @param[in] file_settings pointer to the SimpleArray structure array with files settings to read data from. + * @param[out] data pointer to the SimpleArray structure array to be filled with files data. + * @return MfDesfireErrorNone on success, an error code on failure. + */ +MfDesfireError mf_desfire_poller_read_file_data_multi( + MfDesfirePoller* instance, + const SimpleArray* file_ids, + const SimpleArray* file_settings, + SimpleArray* data); + +/** + * @brief Read application data for selected application on MfDesfire card. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[out] data pointer to the MfDesfireApplication structure to be filled with application data. + * @return MfDesfireErrorNone on success, an error code on failure. + */ +MfDesfireError + mf_desfire_poller_read_application(MfDesfirePoller* instance, MfDesfireApplication* data); + +/** + * @brief Read multiple applications data on MfDesfire card. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] app_ids pointer to the SimpleArray structure array with application ids to read data from. + * @param[out] data pointer to the SimpleArray structure array to be filled with applications data. + * @return MfDesfireErrorNone on success, an error code on failure. + */ +MfDesfireError mf_desfire_poller_read_applications( + MfDesfirePoller* instance, + const SimpleArray* app_ids, + SimpleArray* data); + #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c b/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c index 38ae2f466d0..0b2d8413828 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c @@ -74,8 +74,7 @@ MfDesfireError mf_desfire_send_chunks( return error; } -MfDesfireError - mf_desfire_poller_async_read_version(MfDesfirePoller* instance, MfDesfireVersion* data) { +MfDesfireError mf_desfire_poller_read_version(MfDesfirePoller* instance, MfDesfireVersion* data) { furi_assert(instance); bit_buffer_reset(instance->input_buffer); @@ -97,7 +96,7 @@ MfDesfireError } MfDesfireError - mf_desfire_poller_async_read_free_memory(MfDesfirePoller* instance, MfDesfireFreeMemory* data) { + mf_desfire_poller_read_free_memory(MfDesfirePoller* instance, MfDesfireFreeMemory* data) { furi_assert(instance); bit_buffer_reset(instance->input_buffer); @@ -118,9 +117,8 @@ MfDesfireError return error; } -MfDesfireError mf_desfire_poller_async_read_key_settings( - MfDesfirePoller* instance, - MfDesfireKeySettings* data) { +MfDesfireError + mf_desfire_poller_read_key_settings(MfDesfirePoller* instance, MfDesfireKeySettings* data) { furi_assert(instance); bit_buffer_reset(instance->input_buffer); @@ -141,7 +139,7 @@ MfDesfireError mf_desfire_poller_async_read_key_settings( return error; } -MfDesfireError mf_desfire_poller_async_read_key_versions( +MfDesfireError mf_desfire_poller_read_key_versions( MfDesfirePoller* instance, SimpleArray* data, uint32_t count) { @@ -172,7 +170,7 @@ MfDesfireError mf_desfire_poller_async_read_key_versions( } MfDesfireError - mf_desfire_poller_async_read_application_ids(MfDesfirePoller* instance, SimpleArray* data) { + mf_desfire_poller_read_application_ids(MfDesfirePoller* instance, SimpleArray* data) { furi_assert(instance); bit_buffer_reset(instance->input_buffer); @@ -203,7 +201,7 @@ MfDesfireError return error; } -MfDesfireError mf_desfire_poller_async_select_application( +MfDesfireError mf_desfire_poller_select_application( MfDesfirePoller* instance, const MfDesfireApplicationId* id) { furi_assert(instance); @@ -219,8 +217,7 @@ MfDesfireError mf_desfire_poller_async_select_application( return error; } -MfDesfireError - mf_desfire_poller_async_read_file_ids(MfDesfirePoller* instance, SimpleArray* data) { +MfDesfireError mf_desfire_poller_read_file_ids(MfDesfirePoller* instance, SimpleArray* data) { furi_assert(instance); bit_buffer_reset(instance->input_buffer); @@ -250,7 +247,7 @@ MfDesfireError return error; } -MfDesfireError mf_desfire_poller_async_read_file_settings( +MfDesfireError mf_desfire_poller_read_file_settings( MfDesfirePoller* instance, MfDesfireFileId id, MfDesfireFileSettings* data) { @@ -275,7 +272,7 @@ MfDesfireError mf_desfire_poller_async_read_file_settings( return error; } -MfDesfireError mf_desfire_poller_async_read_file_settings_multi( +MfDesfireError mf_desfire_poller_read_file_settings_multi( MfDesfirePoller* instance, const SimpleArray* file_ids, SimpleArray* data) { @@ -290,15 +287,14 @@ MfDesfireError mf_desfire_poller_async_read_file_settings_multi( for(uint32_t i = 0; i < file_id_count; ++i) { const MfDesfireFileId file_id = *(const MfDesfireFileId*)simple_array_cget(file_ids, i); - error = mf_desfire_poller_async_read_file_settings( - instance, file_id, simple_array_get(data, i)); + error = mf_desfire_poller_read_file_settings(instance, file_id, simple_array_get(data, i)); if(error != MfDesfireErrorNone) break; } return error; } -MfDesfireError mf_desfire_poller_async_read_file_data( +MfDesfireError mf_desfire_poller_read_file_data( MfDesfirePoller* instance, MfDesfireFileId id, uint32_t offset, @@ -327,7 +323,7 @@ MfDesfireError mf_desfire_poller_async_read_file_data( return error; } -MfDesfireError mf_desfire_poller_async_read_file_value( +MfDesfireError mf_desfire_poller_read_file_value( MfDesfirePoller* instance, MfDesfireFileId id, MfDesfireFileData* data) { @@ -352,7 +348,7 @@ MfDesfireError mf_desfire_poller_async_read_file_value( return error; } -MfDesfireError mf_desfire_poller_async_read_file_records( +MfDesfireError mf_desfire_poller_read_file_records( MfDesfirePoller* instance, MfDesfireFileId id, uint32_t offset, @@ -381,7 +377,7 @@ MfDesfireError mf_desfire_poller_async_read_file_records( return error; } -MfDesfireError mf_desfire_poller_async_read_file_data_multi( +MfDesfireError mf_desfire_poller_read_file_data_multi( MfDesfirePoller* instance, const SimpleArray* file_ids, const SimpleArray* file_settings, @@ -404,14 +400,14 @@ MfDesfireError mf_desfire_poller_async_read_file_data_multi( MfDesfireFileData* file_data = simple_array_get(data, i); if(file_type == MfDesfireFileTypeStandard || file_type == MfDesfireFileTypeBackup) { - error = mf_desfire_poller_async_read_file_data( + error = mf_desfire_poller_read_file_data( instance, file_id, 0, file_settings_cur->data.size, file_data); } else if(file_type == MfDesfireFileTypeValue) { - error = mf_desfire_poller_async_read_file_value(instance, file_id, file_data); + error = mf_desfire_poller_read_file_value(instance, file_id, file_data); } else if( file_type == MfDesfireFileTypeLinearRecord || file_type == MfDesfireFileTypeCyclicRecord) { - error = mf_desfire_poller_async_read_file_records( + error = mf_desfire_poller_read_file_records( instance, file_id, 0, file_settings_cur->data.size, file_data); } @@ -421,30 +417,29 @@ MfDesfireError mf_desfire_poller_async_read_file_data_multi( return error; } -MfDesfireError mf_desfire_poller_async_read_application( - MfDesfirePoller* instance, - MfDesfireApplication* data) { +MfDesfireError + mf_desfire_poller_read_application(MfDesfirePoller* instance, MfDesfireApplication* data) { furi_assert(instance); furi_assert(data); MfDesfireError error; do { - error = mf_desfire_poller_async_read_key_settings(instance, &data->key_settings); + error = mf_desfire_poller_read_key_settings(instance, &data->key_settings); if(error != MfDesfireErrorNone) break; - error = mf_desfire_poller_async_read_key_versions( + error = mf_desfire_poller_read_key_versions( instance, data->key_versions, data->key_settings.max_keys); if(error != MfDesfireErrorNone) break; - error = mf_desfire_poller_async_read_file_ids(instance, data->file_ids); + error = mf_desfire_poller_read_file_ids(instance, data->file_ids); if(error != MfDesfireErrorNone) break; - error = mf_desfire_poller_async_read_file_settings_multi( + error = mf_desfire_poller_read_file_settings_multi( instance, data->file_ids, data->file_settings); if(error != MfDesfireErrorNone) break; - error = mf_desfire_poller_async_read_file_data_multi( + error = mf_desfire_poller_read_file_data_multi( instance, data->file_ids, data->file_settings, data->file_data); if(error != MfDesfireErrorNone) break; @@ -453,7 +448,7 @@ MfDesfireError mf_desfire_poller_async_read_application( return error; } -MfDesfireError mf_desfire_poller_async_read_applications( +MfDesfireError mf_desfire_poller_read_applications( MfDesfirePoller* instance, const SimpleArray* app_ids, SimpleArray* data) { @@ -468,12 +463,11 @@ MfDesfireError mf_desfire_poller_async_read_applications( for(uint32_t i = 0; i < app_id_count; ++i) { do { - error = mf_desfire_poller_async_select_application( - instance, simple_array_cget(app_ids, i)); + error = mf_desfire_poller_select_application(instance, simple_array_cget(app_ids, i)); if(error != MfDesfireErrorNone) break; MfDesfireApplication* current_app = simple_array_get(data, i); - error = mf_desfire_poller_async_read_application(instance, current_app); + error = mf_desfire_poller_read_application(instance, current_app); } while(false); } diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.h b/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.h index abc48d0eb59..1c80af36fb6 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.h +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.h @@ -49,78 +49,6 @@ MfDesfireError mf_desfire_process_error(Iso14443_4aError error); const MfDesfireData* mf_desfire_poller_get_data(MfDesfirePoller* instance); -MfDesfireError mf_desfire_send_chunks( - MfDesfirePoller* instance, - const BitBuffer* tx_buffer, - BitBuffer* rx_buffer); - -MfDesfireError - mf_desfire_poller_async_read_version(MfDesfirePoller* instance, MfDesfireVersion* data); - -MfDesfireError - mf_desfire_poller_async_read_free_memory(MfDesfirePoller* instance, MfDesfireFreeMemory* data); - -MfDesfireError mf_desfire_poller_async_read_key_settings( - MfDesfirePoller* instance, - MfDesfireKeySettings* data); - -MfDesfireError mf_desfire_poller_async_read_key_versions( - MfDesfirePoller* instance, - SimpleArray* data, - uint32_t count); - -MfDesfireError - mf_desfire_poller_async_read_application_ids(MfDesfirePoller* instance, SimpleArray* data); - -MfDesfireError mf_desfire_poller_async_select_application( - MfDesfirePoller* instance, - const MfDesfireApplicationId* id); - -MfDesfireError mf_desfire_poller_async_read_file_ids(MfDesfirePoller* instance, SimpleArray* data); - -MfDesfireError mf_desfire_poller_async_read_file_settings( - MfDesfirePoller* instance, - MfDesfireFileId id, - MfDesfireFileSettings* data); - -MfDesfireError mf_desfire_poller_async_read_file_settings_multi( - MfDesfirePoller* instance, - const SimpleArray* file_ids, - SimpleArray* data); - -MfDesfireError mf_desfire_poller_async_read_file_data( - MfDesfirePoller* instance, - MfDesfireFileId id, - uint32_t offset, - size_t size, - MfDesfireFileData* data); - -MfDesfireError mf_desfire_poller_async_read_file_value( - MfDesfirePoller* instance, - MfDesfireFileId id, - MfDesfireFileData* data); - -MfDesfireError mf_desfire_poller_async_read_file_records( - MfDesfirePoller* instance, - MfDesfireFileId id, - uint32_t offset, - size_t size, - MfDesfireFileData* data); - -MfDesfireError mf_desfire_poller_async_read_file_data_multi( - MfDesfirePoller* instance, - const SimpleArray* file_ids, - const SimpleArray* file_settings, - SimpleArray* data); - -MfDesfireError - mf_desfire_poller_async_read_application(MfDesfirePoller* instance, MfDesfireApplication* data); - -MfDesfireError mf_desfire_poller_async_read_applications( - MfDesfirePoller* instance, - const SimpleArray* app_ids, - SimpleArray* data); - #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c index 4ad7bc147d0..86ab68c8b14 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c @@ -230,7 +230,7 @@ static NfcCommand mf_ultralight_poller_handler_idle(MfUltralightPoller* instance } static NfcCommand mf_ultralight_poller_handler_read_version(MfUltralightPoller* instance) { - instance->error = mf_ultralight_poller_async_read_version(instance, &instance->data->version); + instance->error = mf_ultralight_poller_read_version(instance, &instance->data->version); if(instance->error == MfUltralightErrorNone) { FURI_LOG_D(TAG, "Read version success"); instance->data->type = mf_ultralight_get_type_by_version(&instance->data->version); @@ -245,7 +245,7 @@ static NfcCommand mf_ultralight_poller_handler_read_version(MfUltralightPoller* } static NfcCommand mf_ultralight_poller_handler_check_ultralight_c(MfUltralightPoller* instance) { - instance->error = mf_ultralight_poller_async_authenticate(instance); + instance->error = mf_ultralight_poller_authenticate(instance); if(instance->error == MfUltralightErrorNone) { FURI_LOG_D(TAG, "Ultralight C detected"); instance->data->type = MfUltralightTypeMfulC; @@ -260,7 +260,7 @@ static NfcCommand mf_ultralight_poller_handler_check_ultralight_c(MfUltralightPo static NfcCommand mf_ultralight_poller_handler_check_ntag_203(MfUltralightPoller* instance) { MfUltralightPageReadCommandData data = {}; - instance->error = mf_ultralight_poller_async_read_page(instance, 41, &data); + instance->error = mf_ultralight_poller_read_page(instance, 41, &data); if(instance->error == MfUltralightErrorNone) { FURI_LOG_D(TAG, "NTAG203 detected"); instance->data->type = MfUltralightTypeNTAG203; @@ -294,7 +294,7 @@ static NfcCommand mf_ultralight_poller_handler_read_signature(MfUltralightPoller instance->feature_set, MfUltralightFeatureSupportReadSignature)) { FURI_LOG_D(TAG, "Reading signature"); instance->error = - mf_ultralight_poller_async_read_signature(instance, &instance->data->signature); + mf_ultralight_poller_read_signature(instance, &instance->data->signature); if(instance->error != MfUltralightErrorNone) { FURI_LOG_D(TAG, "Read signature failed"); next_state = MfUltralightPollerStateReadFailed; @@ -337,7 +337,7 @@ static NfcCommand mf_ultralight_poller_handler_read_counters(MfUltralightPoller* } FURI_LOG_D(TAG, "Reading counter %d", instance->counters_read); - instance->error = mf_ultralight_poller_async_read_counter( + instance->error = mf_ultralight_poller_read_counter( instance, instance->counters_read, &instance->data->counter[instance->counters_read]); if(instance->error != MfUltralightErrorNone) { FURI_LOG_D(TAG, "Failed to read %d counter", instance->counters_read); @@ -363,7 +363,7 @@ static NfcCommand mf_ultralight_poller_handler_read_tearing_flags(MfUltralightPo if(single_counter) instance->tearing_flag_read = 2; FURI_LOG_D(TAG, "Reading tearing flag %d", instance->tearing_flag_read); - instance->error = mf_ultralight_poller_async_read_tearing_flag( + instance->error = mf_ultralight_poller_read_tearing_flag( instance, instance->tearing_flag_read, &instance->data->tearing_flag[instance->tearing_flag_read]); @@ -396,8 +396,7 @@ static NfcCommand mf_ultralight_poller_handler_auth(MfUltralightPoller* instance uint32_t pass = nfc_util_bytes2num( instance->auth_context.password.data, sizeof(MfUltralightAuthPassword)); FURI_LOG_D(TAG, "Trying to authenticate with password %08lX", pass); - instance->error = - mf_ultralight_poller_async_auth_pwd(instance, &instance->auth_context); + instance->error = mf_ultralight_poller_auth_pwd(instance, &instance->auth_context); if(instance->error == MfUltralightErrorNone) { FURI_LOG_D(TAG, "Auth success"); instance->auth_context.auth_success = true; @@ -428,13 +427,13 @@ static NfcCommand mf_ultralight_poller_handler_read_pages(MfUltralightPoller* in if(mf_ultralight_poller_ntag_i2c_addr_lin_to_tag( instance, start_page, §or, &tag, &pages_left)) { instance->error = - mf_ultralight_poller_async_read_page_from_sector(instance, sector, tag, &data); + mf_ultralight_poller_read_page_from_sector(instance, sector, tag, &data); } else { FURI_LOG_D(TAG, "Failed to calculate sector and tag from %d page", start_page); instance->error = MfUltralightErrorProtocol; } } else { - instance->error = mf_ultralight_poller_async_read_page(instance, start_page, &data); + instance->error = mf_ultralight_poller_read_page(instance, start_page, &data); } if(instance->error == MfUltralightErrorNone) { @@ -478,8 +477,7 @@ static NfcCommand mf_ultralight_poller_handler_try_default_pass(MfUltralightPoll MF_ULTRALIGHT_DEFAULT_PASSWORD, sizeof(MfUltralightAuthPassword), instance->auth_context.password.data); - instance->error = - mf_ultralight_poller_async_auth_pwd(instance, &instance->auth_context); + instance->error = mf_ultralight_poller_auth_pwd(instance, &instance->auth_context); if(instance->error == MfUltralightErrorNone) { FURI_LOG_D(TAG, "Default password detected"); nfc_util_num2bytes( @@ -576,8 +574,7 @@ static bool mf_ultralight_poller_detect(NfcGenericEvent event, void* context) { if(iso14443_3a_event->type == Iso14443_3aPollerEventTypeReady) { MfUltralightPageReadCommandData read_page_cmd_data = {}; - MfUltralightError error = - mf_ultralight_poller_async_read_page(instance, 0, &read_page_cmd_data); + MfUltralightError error = mf_ultralight_poller_read_page(instance, 0, &read_page_cmd_data); protocol_detected = (error == MfUltralightErrorNone); iso14443_3a_poller_halt(instance->iso14443_3a_poller); } diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h index 2d4ef33ea94..665d90cb700 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h @@ -7,35 +7,181 @@ extern "C" { #endif +/** + * @brief MfUltralightPoller opaque type definition. + */ typedef struct MfUltralightPoller MfUltralightPoller; +/** + * @brief Enumeration of possible MfUltralight poller event types. + */ typedef enum { - MfUltralightPollerEventTypeAuthRequest, - MfUltralightPollerEventTypeAuthSuccess, - MfUltralightPollerEventTypeAuthFailed, - MfUltralightPollerEventTypeReadSuccess, - MfUltralightPollerEventTypeReadFailed, + MfUltralightPollerEventTypeAuthRequest, /**< Poller requests to fill authentication context. */ + MfUltralightPollerEventTypeAuthSuccess, /**< Authentication succeeded. */ + MfUltralightPollerEventTypeAuthFailed, /**< Authentication failed. */ + MfUltralightPollerEventTypeReadSuccess, /**< Poller read card successfully. */ + MfUltralightPollerEventTypeReadFailed, /**< Poller failed to read card. */ } MfUltralightPollerEventType; +/** + * @brief MfUltralight poller authentication context. + */ typedef struct { - MfUltralightAuthPassword password; - MfUltralightAuthPack pack; - bool auth_success; - bool skip_auth; + MfUltralightAuthPassword password; /**< Password to be used for authentication. */ + MfUltralightAuthPack pack; /**< Pack received on successfull authentication. */ + bool auth_success; /**< Set to true if authentication succeeded, false otherwise. */ + bool skip_auth; /**< Set to true if authentication should be skipped, false otherwise. */ } MfUltralightPollerAuthContext; -typedef struct { - union { - MfUltralightPollerAuthContext auth_context; - MfUltralightError error; - }; +/** + * @brief MfUltralight poller event data. + */ +typedef union { + MfUltralightPollerAuthContext auth_context; /**< Authentication context. */ + MfUltralightError error; /**< Error code indicating reading fail reason. */ } MfUltralightPollerEventData; +/** + * @brief MfUltralight poller event structure. + * + * Upon emission of an event, an instance of this struct will be passed to the callback. + */ typedef struct { - MfUltralightPollerEventType type; - MfUltralightPollerEventData* data; + MfUltralightPollerEventType type; /**< Type of emmitted event. */ + MfUltralightPollerEventData* data; /**< Pointer to event specific data. */ } MfUltralightPollerEvent; +/** + * @brief Perform authentication with password. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in, out] data pointer to the authentication context. + * @return MfUltralightErrorNone on success, an error code on failure. + */ +MfUltralightError mf_ultralight_poller_auth_pwd( + MfUltralightPoller* instance, + MfUltralightPollerAuthContext* data); + +/** + * @brief Start authentication procedure. + * + * Must ONLY be used inside the callback function. + * + * This function now is used only to identify Mf Ultralight C cards. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @return MfUltralightErrorNone if card supports authentication command, an error code on otherwise. + */ +MfUltralightError mf_ultralight_poller_authenticate(MfUltralightPoller* instance); + +/** + * @brief Read page from card. + * + * Must ONLY be used inside the callback function. + * + * Send read command and parse response. The response on this command is data of 4 pages starting + * from the page specified in the command. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] start_page page number to be read. + * @param[out] data pointer to the MfUltralightPageReadCommandData structure to be filled with page data. + * @return MfUltralightErrorNone on success, an error code on failure. + */ +MfUltralightError mf_ultralight_poller_read_page( + MfUltralightPoller* instance, + uint8_t start_page, + MfUltralightPageReadCommandData* data); + +/** + * @brief Read page from sector. + * + * Must ONLY be used inside the callback function. + * + * This command should be used for NTAGI2C tags. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] sector sector number to be read. + * @param[in] tag tag number to be read. + * @param[out] data pointer to the MfUltralightPageReadCommandData structure to be filled with page data. + * @return MfUltralightErrorNone on success, an error code on failure. + */ +MfUltralightError mf_ultralight_poller_read_page_from_sector( + MfUltralightPoller* instance, + uint8_t sector, + uint8_t tag, + MfUltralightPageReadCommandData* data); + +/** + * @brief Write page to card. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] page page number to be written. + * @param[in] data pointer to the MfUltralightPage structure to be written. + * @return MfUltralightErrorNone on success, an error code on failure. + */ +MfUltralightError mf_ultralight_poller_write_page( + MfUltralightPoller* instance, + uint8_t page, + const MfUltralightPage* data); + +/** + * @brief Read version from card. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[out] data pointer to the MfUltralightVersion structure to be filled. + * @return MfUltralightErrorNone on success, an error code on failure. + */ +MfUltralightError + mf_ultralight_poller_read_version(MfUltralightPoller* instance, MfUltralightVersion* data); + +/** + * @brief Read signature from card. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[out] data pointer to the MfUltralightSignature structure to be filled. + * @return MfUltralightErrorNone on success, an error code on failure. + */ +MfUltralightError + mf_ultralight_poller_read_signature(MfUltralightPoller* instance, MfUltralightSignature* data); + +/** + * @brief Read counter from card. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] counter_num counter number to be read. + * @param[out] data pointer to the MfUltralightCounter structure to be filled. + * @return MfUltralightErrorNone on success, an error code on failure. + */ +MfUltralightError mf_ultralight_poller_read_counter( + MfUltralightPoller* instance, + uint8_t counter_num, + MfUltralightCounter* data); + +/** + * @brief Read tearing flag from card. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] tearing_falg_num tearing flag number to be read. + * @param[out] data pointer to the MfUltralightTearingFlag structure to be filled. + * @return MfUltralightErrorNone on success, an error code on failure. + */ +MfUltralightError mf_ultralight_poller_read_tearing_flag( + MfUltralightPoller* instance, + uint8_t tearing_falg_num, + MfUltralightTearingFlag* data); + #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.c index 795b03e65f9..2d88db3e598 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.c +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.c @@ -30,7 +30,7 @@ MfUltralightError mf_ultralight_process_error(Iso14443_3aError error) { return ret; } -MfUltralightError mf_ultralight_poller_async_auth_pwd( +MfUltralightError mf_ultralight_poller_auth_pwd( MfUltralightPoller* instance, MfUltralightPollerAuthContext* data) { uint8_t auth_cmd[5] = {MF_ULTRALIGHT_CMD_PWD_AUTH}; //-V1009 @@ -59,7 +59,7 @@ MfUltralightError mf_ultralight_poller_async_auth_pwd( return ret; } -MfUltralightError mf_ultralight_poller_async_authenticate(MfUltralightPoller* instance) { +MfUltralightError mf_ultralight_poller_authenticate(MfUltralightPoller* instance) { uint8_t auth_cmd[2] = {MF_ULTRALIGHT_CMD_AUTH, 0x00}; bit_buffer_copy_bytes(instance->tx_buffer, auth_cmd, sizeof(auth_cmd)); @@ -86,7 +86,7 @@ MfUltralightError mf_ultralight_poller_async_authenticate(MfUltralightPoller* in return ret; } -MfUltralightError mf_ultralight_poller_async_read_page_from_sector( +MfUltralightError mf_ultralight_poller_read_page_from_sector( MfUltralightPoller* instance, uint8_t sector, uint8_t tag, @@ -122,13 +122,13 @@ MfUltralightError mf_ultralight_poller_async_read_page_from_sector( break; } - ret = mf_ultralight_poller_async_read_page(instance, tag, data); + ret = mf_ultralight_poller_read_page(instance, tag, data); } while(false); return ret; } -MfUltralightError mf_ultralight_poller_async_read_page( +MfUltralightError mf_ultralight_poller_read_page( MfUltralightPoller* instance, uint8_t start_page, MfUltralightPageReadCommandData* data) { @@ -158,10 +158,10 @@ MfUltralightError mf_ultralight_poller_async_read_page( return ret; } -MfUltralightError mf_ultralight_poller_async_write_page( +MfUltralightError mf_ultralight_poller_write_page( MfUltralightPoller* instance, uint8_t page, - MfUltralightPage* data) { + const MfUltralightPage* data) { MfUltralightError ret = MfUltralightErrorNone; Iso14443_3aError error = Iso14443_3aErrorNone; @@ -191,9 +191,8 @@ MfUltralightError mf_ultralight_poller_async_write_page( return ret; } -MfUltralightError mf_ultralight_poller_async_read_version( - MfUltralightPoller* instance, - MfUltralightVersion* data) { +MfUltralightError + mf_ultralight_poller_read_version(MfUltralightPoller* instance, MfUltralightVersion* data) { MfUltralightError ret = MfUltralightErrorNone; Iso14443_3aError error = Iso14443_3aErrorNone; @@ -221,9 +220,8 @@ MfUltralightError mf_ultralight_poller_async_read_version( return ret; } -MfUltralightError mf_ultralight_poller_async_read_signature( - MfUltralightPoller* instance, - MfUltralightSignature* data) { +MfUltralightError + mf_ultralight_poller_read_signature(MfUltralightPoller* instance, MfUltralightSignature* data) { MfUltralightError ret = MfUltralightErrorNone; Iso14443_3aError error = Iso14443_3aErrorNone; @@ -249,7 +247,7 @@ MfUltralightError mf_ultralight_poller_async_read_signature( return ret; } -MfUltralightError mf_ultralight_poller_async_read_counter( +MfUltralightError mf_ultralight_poller_read_counter( MfUltralightPoller* instance, uint8_t counter_num, MfUltralightCounter* data) { @@ -278,7 +276,7 @@ MfUltralightError mf_ultralight_poller_async_read_counter( return ret; } -MfUltralightError mf_ultralight_poller_async_read_tearing_flag( +MfUltralightError mf_ultralight_poller_read_tearing_flag( MfUltralightPoller* instance, uint8_t tearing_falg_num, MfUltralightTearingFlag* data) { diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.h b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.h index 13490cf1a9d..c89402b421c 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.h +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.h @@ -103,46 +103,6 @@ bool mf_ultralight_poller_ntag_i2c_addr_lin_to_tag( uint8_t* tag, uint8_t* pages_left); -MfUltralightError mf_ultralight_poller_async_auth_pwd( - MfUltralightPoller* instance, - MfUltralightPollerAuthContext* data); - -MfUltralightError mf_ultralight_poller_async_authenticate(MfUltralightPoller* instance); - -MfUltralightError mf_ultralight_poller_async_read_page( - MfUltralightPoller* instance, - uint8_t start_page, - MfUltralightPageReadCommandData* data); - -MfUltralightError mf_ultralight_poller_async_read_page_from_sector( - MfUltralightPoller* instance, - uint8_t sector, - uint8_t tag, - MfUltralightPageReadCommandData* data); - -MfUltralightError mf_ultralight_poller_async_write_page( - MfUltralightPoller* instance, - uint8_t page, - MfUltralightPage* data); - -MfUltralightError mf_ultralight_poller_async_read_version( - MfUltralightPoller* instance, - MfUltralightVersion* data); - -MfUltralightError mf_ultralight_poller_async_read_signature( - MfUltralightPoller* instance, - MfUltralightSignature* data); - -MfUltralightError mf_ultralight_poller_async_read_counter( - MfUltralightPoller* instance, - uint8_t counter_num, - MfUltralightCounter* data); - -MfUltralightError mf_ultralight_poller_async_read_tearing_flag( - MfUltralightPoller* instance, - uint8_t tearing_falg_num, - MfUltralightTearingFlag* data); - #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync_api.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.c similarity index 83% rename from lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync_api.c rename to lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.c index 739df597d2c..c4833facf0f 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync_api.c +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.c @@ -1,3 +1,4 @@ +#include "mf_ultralight_poller_sync.h" #include "mf_ultralight_poller_i.h" #include @@ -31,40 +32,39 @@ typedef MfUltralightError (*MfUltralightPollerCmdHandler)( MfUltralightError mf_ultralight_poller_read_page_handler( MfUltralightPoller* poller, MfUltralightPollerContextData* data) { - return mf_ultralight_poller_async_read_page( - poller, data->read_cmd.start_page, &data->read_cmd.data); + return mf_ultralight_poller_read_page(poller, data->read_cmd.start_page, &data->read_cmd.data); } MfUltralightError mf_ultralight_poller_write_page_handler( MfUltralightPoller* poller, MfUltralightPollerContextData* data) { - return mf_ultralight_poller_async_write_page( + return mf_ultralight_poller_write_page( poller, data->write_cmd.page_to_write, &data->write_cmd.page); } MfUltralightError mf_ultralight_poller_read_version_handler( MfUltralightPoller* poller, MfUltralightPollerContextData* data) { - return mf_ultralight_poller_async_read_version(poller, &data->version); + return mf_ultralight_poller_read_version(poller, &data->version); } MfUltralightError mf_ultralight_poller_read_signature_handler( MfUltralightPoller* poller, MfUltralightPollerContextData* data) { - return mf_ultralight_poller_async_read_signature(poller, &data->signature); + return mf_ultralight_poller_read_signature(poller, &data->signature); } MfUltralightError mf_ultralight_poller_read_counter_handler( MfUltralightPoller* poller, MfUltralightPollerContextData* data) { - return mf_ultralight_poller_async_read_counter( + return mf_ultralight_poller_read_counter( poller, data->counter_cmd.counter_num, &data->counter_cmd.data); } MfUltralightError mf_ultralight_poller_read_tearing_flag_handler( MfUltralightPoller* poller, MfUltralightPollerContextData* data) { - return mf_ultralight_poller_async_read_tearing_flag( + return mf_ultralight_poller_read_tearing_flag( poller, data->tearing_flag_cmd.tearing_flag_num, &data->tearing_flag_cmd.data); } @@ -79,16 +79,14 @@ static const MfUltralightPollerCmdHandler mf_ultralight_poller_read_tearing_flag_handler, }; -static NfcCommand mf_ultralight_poller_cmd_callback(NfcGenericEvent event, void* context) { - furi_assert(event.instance); - furi_assert(event.protocol == NfcProtocolIso14443_3a); - furi_assert(event.event_data); +static NfcCommand mf_ultralight_poller_cmd_callback(NfcGenericEventEx event, void* context) { + furi_assert(event.poller); + furi_assert(event.parent_event_data); furi_assert(context); MfUltralightPollerContext* poller_context = context; - Iso14443_3aPollerEvent* iso14443_3a_event = event.event_data; - Iso14443_3aPoller* iso14443_3a_poller = event.instance; - MfUltralightPoller* mfu_poller = mf_ultralight_poller_alloc(iso14443_3a_poller); + Iso14443_3aPollerEvent* iso14443_3a_event = event.parent_event_data; + MfUltralightPoller* mfu_poller = event.poller; if(iso14443_3a_event->type == Iso14443_3aPollerEventTypeReady) { poller_context->error = mf_ultralight_poller_cmd_handlers[poller_context->cmd_type]( @@ -99,8 +97,6 @@ static NfcCommand mf_ultralight_poller_cmd_callback(NfcGenericEvent event, void* furi_thread_flags_set(poller_context->thread_id, MF_ULTRALIGHT_POLLER_COMPLETE_EVENT); - mf_ultralight_poller_free(mfu_poller); - return NfcCommandStop; } @@ -110,8 +106,8 @@ static MfUltralightError poller_ctx->thread_id = furi_thread_get_current_id(); - NfcPoller* poller = nfc_poller_alloc(nfc, NfcProtocolIso14443_3a); - nfc_poller_start(poller, mf_ultralight_poller_cmd_callback, poller_ctx); + NfcPoller* poller = nfc_poller_alloc(nfc, NfcProtocolMfUltralight); + nfc_poller_start_ex(poller, mf_ultralight_poller_cmd_callback, poller_ctx); furi_thread_flags_wait(MF_ULTRALIGHT_POLLER_COMPLETE_EVENT, FuriFlagWaitAny, FuriWaitForever); furi_thread_flags_clear(MF_ULTRALIGHT_POLLER_COMPLETE_EVENT); @@ -121,7 +117,8 @@ static MfUltralightError return poller_ctx->error; } -MfUltralightError mf_ultralight_poller_read_page(Nfc* nfc, uint16_t page, MfUltralightPage* data) { +MfUltralightError + mf_ultralight_poller_sync_read_page(Nfc* nfc, uint16_t page, MfUltralightPage* data) { furi_assert(nfc); furi_assert(data); @@ -140,7 +137,7 @@ MfUltralightError mf_ultralight_poller_read_page(Nfc* nfc, uint16_t page, MfUltr } MfUltralightError - mf_ultralight_poller_write_page(Nfc* nfc, uint16_t page, MfUltralightPage* data) { + mf_ultralight_poller_sync_write_page(Nfc* nfc, uint16_t page, MfUltralightPage* data) { furi_assert(nfc); furi_assert(data); @@ -158,7 +155,7 @@ MfUltralightError return error; } -MfUltralightError mf_ultralight_poller_read_version(Nfc* nfc, MfUltralightVersion* data) { +MfUltralightError mf_ultralight_poller_sync_read_version(Nfc* nfc, MfUltralightVersion* data) { furi_assert(nfc); furi_assert(data); @@ -175,7 +172,7 @@ MfUltralightError mf_ultralight_poller_read_version(Nfc* nfc, MfUltralightVersio return error; } -MfUltralightError mf_ultralight_poller_read_signature(Nfc* nfc, MfUltralightSignature* data) { +MfUltralightError mf_ultralight_poller_sync_read_signature(Nfc* nfc, MfUltralightSignature* data) { furi_assert(nfc); furi_assert(data); @@ -192,8 +189,10 @@ MfUltralightError mf_ultralight_poller_read_signature(Nfc* nfc, MfUltralightSign return error; } -MfUltralightError - mf_ultralight_poller_read_counter(Nfc* nfc, uint8_t counter_num, MfUltralightCounter* data) { +MfUltralightError mf_ultralight_poller_sync_read_counter( + Nfc* nfc, + uint8_t counter_num, + MfUltralightCounter* data) { furi_assert(nfc); furi_assert(data); @@ -211,7 +210,7 @@ MfUltralightError return error; } -MfUltralightError mf_ultralight_poller_read_tearing_flag( +MfUltralightError mf_ultralight_poller_sync_read_tearing_flag( Nfc* nfc, uint8_t flag_num, MfUltralightTearingFlag* data) { @@ -261,7 +260,7 @@ static NfcCommand mf_ultralight_poller_read_callback(NfcGenericEvent event, void return command; } -MfUltralightError mf_ultralight_poller_read_card(Nfc* nfc, MfUltralightData* data) { +MfUltralightError mf_ultralight_poller_sync_read_card(Nfc* nfc, MfUltralightData* data) { furi_assert(nfc); furi_assert(data); diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.h b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.h new file mode 100644 index 00000000000..ac585aad7e9 --- /dev/null +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.h @@ -0,0 +1,34 @@ +#pragma once + +#include "mf_ultralight.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +MfUltralightError + mf_ultralight_poller_sync_read_page(Nfc* nfc, uint16_t page, MfUltralightPage* data); + +MfUltralightError + mf_ultralight_poller_sync_write_page(Nfc* nfc, uint16_t page, MfUltralightPage* data); + +MfUltralightError mf_ultralight_poller_sync_read_version(Nfc* nfc, MfUltralightVersion* data); + +MfUltralightError mf_ultralight_poller_sync_read_signature(Nfc* nfc, MfUltralightSignature* data); + +MfUltralightError mf_ultralight_poller_sync_read_counter( + Nfc* nfc, + uint8_t counter_num, + MfUltralightCounter* data); + +MfUltralightError mf_ultralight_poller_sync_read_tearing_flag( + Nfc* nfc, + uint8_t flag_num, + MfUltralightTearingFlag* data); + +MfUltralightError mf_ultralight_poller_sync_read_card(Nfc* nfc, MfUltralightData* data); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync_api.h b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync_api.h deleted file mode 100644 index a0124ae0921..00000000000 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync_api.h +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -#include "mf_ultralight.h" -#include - -#ifdef __cplusplus -extern "C" { -#endif - -MfUltralightError mf_ultralight_poller_read_page(Nfc* nfc, uint16_t page, MfUltralightPage* data); - -MfUltralightError mf_ultralight_poller_write_page(Nfc* nfc, uint16_t page, MfUltralightPage* data); - -MfUltralightError mf_ultralight_poller_read_version(Nfc* nfc, MfUltralightVersion* data); - -MfUltralightError mf_ultralight_poller_read_signature(Nfc* nfc, MfUltralightSignature* data); - -MfUltralightError - mf_ultralight_poller_read_counter(Nfc* nfc, uint8_t counter_num, MfUltralightCounter* data); - -MfUltralightError mf_ultralight_poller_read_tearing_flag( - Nfc* nfc, - uint8_t flag_num, - MfUltralightTearingFlag* data); - -MfUltralightError mf_ultralight_poller_read_card(Nfc* nfc, MfUltralightData* data); - -#ifdef __cplusplus -} -#endif diff --git a/lib/nfc/protocols/nfc_protocol.h b/lib/nfc/protocols/nfc_protocol.h index 55aa8a5895e..ee634533356 100644 --- a/lib/nfc/protocols/nfc_protocol.h +++ b/lib/nfc/protocols/nfc_protocol.h @@ -61,9 +61,9 @@ * | | * +- protocol_name_listener_defs.h | * | - * +- protocol_name_sync_api.h | + * +- protocol_name_sync.h | * | |- add for synchronous API support - * +- protocol_name_sync_api.c | + * +- protocol_name_sync.c | * | * ``` * @@ -83,8 +83,8 @@ * | protocol_name_listener.h | Protocol-specific listener and associated functions declarations. Optional, needed for emulation support. | * | protocol_name_listener.c | Implementation of functions declared in `protocol_name_listener.h`. Optional, needed for emulation support. | * | protocol_name_listener_defs.h | Declarations for use by the NfcListener library. See nfc_listener_base.h for more info. Optional, needed for emulation support. | - * | protocol_name_sync_api.h | Synchronous API declarations. (See below for sync API explanation). Optional.| - * | protocol_name_sync_api.c | Synchronous API implementation. Optional. | + * | protocol_name_sync.h | Synchronous API declarations. (See below for sync API explanation). Optional.| + * | protocol_name_sync.c | Synchronous API implementation. Optional. | * * ## 3 Implement the code * @@ -145,7 +145,7 @@ * `protocol_name_poller_defs` structure under the appropriate index. * 5. (Optional) If emulation support was implemented, do the step 4, but for the listener. * 6. Add `protocol_name.h`, `protocol_name_poller.h`, and optionally, `protocol_name_listener.h` - * and `protocol_name_sync_api.h` into the `SDK_HEADERS` list in the SConscript file. + * and `protocol_name_sync.h` into the `SDK_HEADERS` list in the SConscript file. * This will export the protocol's types and functions for use by the applications. * 7. Done! * diff --git a/lib/nfc/protocols/slix/slix_poller.c b/lib/nfc/protocols/slix/slix_poller.c index 9731bfc6b83..46a17119436 100644 --- a/lib/nfc/protocols/slix/slix_poller.c +++ b/lib/nfc/protocols/slix/slix_poller.c @@ -50,8 +50,7 @@ static NfcCommand slix_poller_handler_idle(SlixPoller* instance) { } static NfcCommand slix_poller_handler_get_nfc_system_info(SlixPoller* instance) { - instance->error = - slix_poller_async_get_nxp_system_info(instance, &instance->data->system_info); + instance->error = slix_poller_get_nxp_system_info(instance, &instance->data->system_info); if(instance->error == SlixErrorNone) { instance->poller_state = SlixPollerStateReadSignature; } else { @@ -62,7 +61,7 @@ static NfcCommand slix_poller_handler_get_nfc_system_info(SlixPoller* instance) } static NfcCommand slix_poller_handler_read_signature(SlixPoller* instance) { - instance->error = slix_poller_async_read_signature(instance, &instance->data->signature); + instance->error = slix_poller_read_signature(instance, &instance->data->signature); if(instance->error == SlixErrorNone) { instance->poller_state = SlixPollerStateReady; } else { @@ -141,7 +140,7 @@ static bool slix_poller_detect(NfcGenericEvent event, void* context) { if(iso15693_3_event->type == Iso15693_3PollerEventTypeReady) { if(slix_get_type(instance->data) < SlixTypeCount) { SlixSystemInfo system_info = {}; - SlixError error = slix_poller_async_get_nxp_system_info(instance, &system_info); + SlixError error = slix_poller_get_nxp_system_info(instance, &system_info); protocol_detected = (error == SlixErrorNone); } } diff --git a/lib/nfc/protocols/slix/slix_poller.h b/lib/nfc/protocols/slix/slix_poller.h index f4c7214de4f..62d60be5fe2 100644 --- a/lib/nfc/protocols/slix/slix_poller.h +++ b/lib/nfc/protocols/slix/slix_poller.h @@ -8,22 +8,78 @@ extern "C" { #endif +/** + * @brief SlixPoller opaque type definition. + */ typedef struct SlixPoller SlixPoller; +/** + * @brief Enumeration of possible Slix poller event types. + */ typedef enum { - SlixPollerEventTypeError, - SlixPollerEventTypeReady, + SlixPollerEventTypeError, /**< An error occured while reading card. */ + SlixPollerEventTypeReady, /**< The card was successfully read by the poller. */ } SlixPollerEventType; -typedef struct { - SlixError error; +/** + * @brief Slixs poller event data. + */ +typedef union { + SlixError error; /**< Error code indicating card reaing fail reason. */ } SlixPollerEventData; +/** + * @brief Slix poller event structure. + * + * Upon emission of an event, an instance of this struct will be passed to the callback. + */ typedef struct { - SlixPollerEventType type; - SlixPollerEventData* data; + SlixPollerEventType type; /**< Type of emmitted event. */ + SlixPollerEventData* data; /**< Pointer to event specific data. */ } SlixPollerEvent; +/** + * @brief Transmit and receive Slix frames in poller mode. + * + * Must ONLY be used inside the callback function. + * + * The rx_buffer will be filled with any data received as a response to data + * sent from tx_buffer, with a timeout defined by the fwt parameter. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] tx_buffer pointer to the buffer containing the data to be transmitted. + * @param[out] rx_buffer pointer to the buffer to be filled with received data. + * @param[in] fwt frame wait time (response timeout), in carrier cycles. + * @return SlixErrorNone on success, an error code on failure. + */ +SlixError slix_poller_send_frame( + SlixPoller* instance, + const BitBuffer* tx_data, + BitBuffer* rx_data, + uint32_t fwt); + +/** + * @brief Send get nxp system info command and parse response. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[out] data pointer to the SlixSystemInfo structure to be filled. + * @return SlixErrorNone on success, an error code on failure. + */ +SlixError slix_poller_get_nxp_system_info(SlixPoller* instance, SlixSystemInfo* data); + +/** + * @brief Read signature from card. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[out] data pointer to the SlixSignature structure to be filled. + * @return SlixErrorNone on success, an error code on failure. + */ +SlixError slix_poller_read_signature(SlixPoller* instance, SlixSignature* data); + #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/slix/slix_poller_i.c b/lib/nfc/protocols/slix/slix_poller_i.c index a36e7694a4f..6d7bdf37795 100644 --- a/lib/nfc/protocols/slix/slix_poller_i.c +++ b/lib/nfc/protocols/slix/slix_poller_i.c @@ -32,7 +32,7 @@ SlixError slix_poller_send_frame( return slix_process_iso15693_3_error(iso15693_3_error); } -SlixError slix_poller_async_get_nxp_system_info(SlixPoller* instance, SlixSystemInfo* data) { +SlixError slix_poller_get_nxp_system_info(SlixPoller* instance, SlixSystemInfo* data) { furi_assert(instance); furi_assert(data); @@ -50,7 +50,7 @@ SlixError slix_poller_async_get_nxp_system_info(SlixPoller* instance, SlixSystem return error; } -SlixError slix_poller_async_read_signature(SlixPoller* instance, SlixSignature* data) { +SlixError slix_poller_read_signature(SlixPoller* instance, SlixSignature* data) { furi_assert(instance); furi_assert(data); diff --git a/lib/nfc/protocols/slix/slix_poller_i.h b/lib/nfc/protocols/slix/slix_poller_i.h index c6a8a3c33ef..1fda1a7d246 100644 --- a/lib/nfc/protocols/slix/slix_poller_i.h +++ b/lib/nfc/protocols/slix/slix_poller_i.h @@ -33,16 +33,6 @@ struct SlixPoller { void* context; }; -SlixError slix_poller_send_frame( - SlixPoller* instance, - const BitBuffer* tx_data, - BitBuffer* rx_data, - uint32_t fwt); - -SlixError slix_poller_async_get_nxp_system_info(SlixPoller* instance, SlixSystemInfo* data); - -SlixError slix_poller_async_read_signature(SlixPoller* instance, SlixSignature* data); - #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/st25tb/st25tb_poller.c b/lib/nfc/protocols/st25tb/st25tb_poller.c index df659a2051d..2bc5dd94105 100644 --- a/lib/nfc/protocols/st25tb/st25tb_poller.c +++ b/lib/nfc/protocols/st25tb/st25tb_poller.c @@ -71,7 +71,7 @@ static NfcCommand st25tb_poller_run(NfcGenericEvent event, void* context) { if(nfc_event->type == NfcEventTypePollerReady) { if(instance->state != St25tbPollerStateActivated) { - St25tbError error = st25tb_poller_async_activate(instance, instance->data); + St25tbError error = st25tb_poller_activate(instance, instance->data); if(error == St25tbErrorNone) { instance->st25tb_event.type = St25tbPollerEventTypeReady; @@ -106,7 +106,7 @@ static bool st25tb_poller_detect(NfcGenericEvent event, void* context) { furi_assert(instance->state == St25tbPollerStateIdle); if(nfc_event->type == NfcEventTypePollerReady) { - St25tbError error = st25tb_poller_async_initiate(instance, NULL); + St25tbError error = st25tb_poller_initiate(instance, NULL); protocol_detected = (error == St25tbErrorNone); } diff --git a/lib/nfc/protocols/st25tb/st25tb_poller.h b/lib/nfc/protocols/st25tb/st25tb_poller.h index a521b6d5b47..d3b85e30665 100644 --- a/lib/nfc/protocols/st25tb/st25tb_poller.h +++ b/lib/nfc/protocols/st25tb/st25tb_poller.h @@ -25,6 +25,23 @@ typedef struct { St25tbPollerEventData* data; } St25tbPollerEvent; +St25tbError st25tb_poller_send_frame( + St25tbPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt); + +St25tbError st25tb_poller_initiate(St25tbPoller* instance, uint8_t* chip_id); + +St25tbError st25tb_poller_activate(St25tbPoller* instance, St25tbData* data); + +St25tbError st25tb_poller_get_uid(St25tbPoller* instance, uint8_t* uid); + +St25tbError + st25tb_poller_read_block(St25tbPoller* instance, uint32_t* block, uint8_t block_number); + +St25tbError st25tb_poller_halt(St25tbPoller* instance); + #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/st25tb/st25tb_poller_i.c b/lib/nfc/protocols/st25tb/st25tb_poller_i.c index bcbc69382a9..76c9a8b1fa0 100644 --- a/lib/nfc/protocols/st25tb/st25tb_poller_i.c +++ b/lib/nfc/protocols/st25tb/st25tb_poller_i.c @@ -22,7 +22,7 @@ static St25tbError st25tb_poller_prepare_trx(St25tbPoller* instance) { furi_assert(instance); if(instance->state == St25tbPollerStateIdle) { - return st25tb_poller_async_activate(instance, NULL); + return st25tb_poller_activate(instance, NULL); } return St25tbErrorNone; @@ -85,7 +85,7 @@ St25tbType st25tb_get_type_from_uid(const uint8_t uid[ST25TB_UID_SIZE]) { } } -St25tbError st25tb_poller_async_initiate(St25tbPoller* instance, uint8_t* chip_id) { +St25tbError st25tb_poller_initiate(St25tbPoller* instance, uint8_t* chip_id) { // Send Initiate() furi_assert(instance); furi_assert(instance->nfc); @@ -117,7 +117,7 @@ St25tbError st25tb_poller_async_initiate(St25tbPoller* instance, uint8_t* chip_i return ret; } -St25tbError st25tb_poller_async_activate(St25tbPoller* instance, St25tbData* data) { +St25tbError st25tb_poller_activate(St25tbPoller* instance, St25tbData* data) { furi_assert(instance); furi_assert(instance->nfc); @@ -126,7 +126,7 @@ St25tbError st25tb_poller_async_activate(St25tbPoller* instance, St25tbData* dat St25tbError ret; do { - ret = st25tb_poller_async_initiate(instance, &data->chip_id); + ret = st25tb_poller_initiate(instance, &data->chip_id); if(ret != St25tbErrorNone) { break; } @@ -162,7 +162,7 @@ St25tbError st25tb_poller_async_activate(St25tbPoller* instance, St25tbData* dat } instance->state = St25tbPollerStateActivated; - ret = st25tb_poller_async_get_uid(instance, data->uid); + ret = st25tb_poller_get_uid(instance, data->uid); if(ret != St25tbErrorNone) { instance->state = St25tbPollerStateActivationFailed; break; @@ -171,7 +171,7 @@ St25tbError st25tb_poller_async_activate(St25tbPoller* instance, St25tbData* dat bool read_blocks = true; for(uint8_t i = 0; i < st25tb_get_block_count(data->type); i++) { - ret = st25tb_poller_async_read_block(instance, &data->blocks[i], i); + ret = st25tb_poller_read_block(instance, &data->blocks[i], i); if(ret != St25tbErrorNone) { read_blocks = false; break; @@ -180,14 +180,13 @@ St25tbError st25tb_poller_async_activate(St25tbPoller* instance, St25tbData* dat if(!read_blocks) { break; } - ret = st25tb_poller_async_read_block( - instance, &data->system_otp_block, ST25TB_SYSTEM_OTP_BLOCK); + ret = st25tb_poller_read_block(instance, &data->system_otp_block, ST25TB_SYSTEM_OTP_BLOCK); } while(false); return ret; } -St25tbError st25tb_poller_async_get_uid(St25tbPoller* instance, uint8_t uid[ST25TB_UID_SIZE]) { +St25tbError st25tb_poller_get_uid(St25tbPoller* instance, uint8_t* uid) { furi_assert(instance); furi_assert(instance->nfc); @@ -221,7 +220,7 @@ St25tbError st25tb_poller_async_get_uid(St25tbPoller* instance, uint8_t uid[ST25 } St25tbError - st25tb_poller_async_read_block(St25tbPoller* instance, uint32_t* block, uint8_t block_number) { + st25tb_poller_read_block(St25tbPoller* instance, uint32_t* block, uint8_t block_number) { furi_assert(instance); furi_assert(instance->nfc); furi_assert(block); diff --git a/lib/nfc/protocols/st25tb/st25tb_poller_i.h b/lib/nfc/protocols/st25tb/st25tb_poller_i.h index 7f38f2d45b5..27218d7b44a 100644 --- a/lib/nfc/protocols/st25tb/st25tb_poller_i.h +++ b/lib/nfc/protocols/st25tb/st25tb_poller_i.h @@ -34,23 +34,6 @@ struct St25tbPoller { const St25tbData* st25tb_poller_get_data(St25tbPoller* instance); -St25tbError st25tb_poller_async_initiate(St25tbPoller* instance, uint8_t* chip_id); - -St25tbError st25tb_poller_async_activate(St25tbPoller* instance, St25tbData* data); - -St25tbError st25tb_poller_async_get_uid(St25tbPoller* instance, uint8_t uid[ST25TB_UID_SIZE]); - -St25tbError - st25tb_poller_async_read_block(St25tbPoller* instance, uint32_t* block, uint8_t block_number); - -St25tbError st25tb_poller_halt(St25tbPoller* instance); - -St25tbError st25tb_poller_send_frame( - St25tbPoller* instance, - const BitBuffer* tx_buffer, - BitBuffer* rx_buffer, - uint32_t fwt); - #ifdef __cplusplus } #endif diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 0eadd799d47..44829c264da 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,44.0,, +Version,+,45.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -39,6 +39,8 @@ Header,+,applications/services/storage/storage.h,, Header,+,lib/digital_signal/digital_sequence.h,, Header,+,lib/digital_signal/digital_signal.h,, Header,+,lib/drivers/cc1101_regs.h,, +Header,+,lib/drivers/st25r3916.h,, +Header,+,lib/drivers/st25r3916_reg.h,, Header,+,lib/flipper_application/api_hashtable/api_hashtable.h,, Header,+,lib/flipper_application/api_hashtable/compilesort.hpp,, Header,+,lib/flipper_application/flipper_application.h,, @@ -2045,6 +2047,29 @@ Function,+,srand,void,unsigned Function,-,srand48,void,long Function,-,srandom,void,unsigned Function,+,sscanf,int,"const char*, const char*, ..." +Function,+,st25r3916_change_reg_bits,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t, uint8_t" +Function,+,st25r3916_change_test_reg_bits,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t, uint8_t" +Function,+,st25r3916_check_reg,_Bool,"FuriHalSpiBusHandle*, uint8_t, uint8_t, uint8_t" +Function,+,st25r3916_clear_reg_bits,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t" +Function,+,st25r3916_direct_cmd,void,"FuriHalSpiBusHandle*, uint8_t" +Function,+,st25r3916_get_irq,uint32_t,FuriHalSpiBusHandle* +Function,+,st25r3916_mask_irq,void,"FuriHalSpiBusHandle*, uint32_t" +Function,+,st25r3916_modify_reg,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t, uint8_t" +Function,+,st25r3916_read_burst_regs,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t*, uint8_t" +Function,+,st25r3916_read_fifo,_Bool,"FuriHalSpiBusHandle*, uint8_t*, size_t, size_t*" +Function,+,st25r3916_read_pta_mem,void,"FuriHalSpiBusHandle*, uint8_t*, size_t" +Function,+,st25r3916_read_reg,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t*" +Function,+,st25r3916_read_test_reg,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t*" +Function,+,st25r3916_reg_read_fifo,void,"FuriHalSpiBusHandle*, uint8_t*, size_t" +Function,+,st25r3916_reg_write_fifo,void,"FuriHalSpiBusHandle*, const uint8_t*, size_t" +Function,+,st25r3916_set_reg_bits,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t" +Function,+,st25r3916_write_burst_regs,void,"FuriHalSpiBusHandle*, uint8_t, const uint8_t*, uint8_t" +Function,+,st25r3916_write_fifo,void,"FuriHalSpiBusHandle*, const uint8_t*, size_t" +Function,+,st25r3916_write_pta_mem,void,"FuriHalSpiBusHandle*, const uint8_t*, size_t" +Function,+,st25r3916_write_ptf_mem,void,"FuriHalSpiBusHandle*, const uint8_t*, size_t" +Function,+,st25r3916_write_pttsn_mem,void,"FuriHalSpiBusHandle*, uint8_t*, size_t" +Function,+,st25r3916_write_reg,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t" +Function,+,st25r3916_write_test_reg,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t" Function,+,storage_common_copy,FS_Error,"Storage*, const char*, const char*" Function,+,storage_common_exists,_Bool,"Storage*, const char*" Function,+,storage_common_fs_info,FS_Error,"Storage*, const char*, uint64_t*, uint64_t*" diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 98345319536..514f3328682 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,44.0,, +Version,+,45.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -40,6 +40,8 @@ Header,+,applications/services/storage/storage.h,, Header,+,lib/digital_signal/digital_sequence.h,, Header,+,lib/digital_signal/digital_signal.h,, Header,+,lib/drivers/cc1101_regs.h,, +Header,+,lib/drivers/st25r3916.h,, +Header,+,lib/drivers/st25r3916_reg.h,, Header,+,lib/flipper_application/api_hashtable/api_hashtable.h,, Header,+,lib/flipper_application/api_hashtable/compilesort.hpp,, Header,+,lib/flipper_application/flipper_application.h,, @@ -117,7 +119,7 @@ Header,+,lib/nfc/nfc_scanner.h,, Header,+,lib/nfc/protocols/iso14443_3a/iso14443_3a.h,, Header,+,lib/nfc/protocols/iso14443_3a/iso14443_3a_listener.h,, Header,+,lib/nfc/protocols/iso14443_3a/iso14443_3a_poller.h,, -Header,+,lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_sync_api.h,, +Header,+,lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_sync.h,, Header,+,lib/nfc/protocols/iso14443_3b/iso14443_3b.h,, Header,+,lib/nfc/protocols/iso14443_3b/iso14443_3b_poller.h,, Header,+,lib/nfc/protocols/iso14443_4a/iso14443_4a.h,, @@ -128,13 +130,13 @@ Header,+,lib/nfc/protocols/iso14443_4b/iso14443_4b_poller.h,, Header,+,lib/nfc/protocols/mf_classic/mf_classic.h,, Header,+,lib/nfc/protocols/mf_classic/mf_classic_listener.h,, Header,+,lib/nfc/protocols/mf_classic/mf_classic_poller.h,, -Header,+,lib/nfc/protocols/mf_classic/mf_classic_poller_sync_api.h,, +Header,+,lib/nfc/protocols/mf_classic/mf_classic_poller_sync.h,, Header,+,lib/nfc/protocols/mf_desfire/mf_desfire.h,, Header,+,lib/nfc/protocols/mf_desfire/mf_desfire_poller.h,, Header,+,lib/nfc/protocols/mf_ultralight/mf_ultralight.h,, Header,+,lib/nfc/protocols/mf_ultralight/mf_ultralight_listener.h,, Header,+,lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h,, -Header,+,lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync_api.h,, +Header,+,lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.h,, Header,+,lib/nfc/protocols/slix/slix.h,, Header,+,lib/nfc/protocols/st25tb/st25tb.h,, Header,+,lib/nfc/protocols/st25tb/st25tb_poller.h,, @@ -1861,9 +1863,13 @@ Function,+,iso14443_3a_get_sak,uint8_t,const Iso14443_3aData* Function,+,iso14443_3a_get_uid,const uint8_t*,"const Iso14443_3aData*, size_t*" Function,+,iso14443_3a_is_equal,_Bool,"const Iso14443_3aData*, const Iso14443_3aData*" Function,+,iso14443_3a_load,_Bool,"Iso14443_3aData*, FlipperFormat*, uint32_t" -Function,+,iso14443_3a_poller_read,Iso14443_3aError,"Nfc*, Iso14443_3aData*" +Function,+,iso14443_3a_poller_activate,Iso14443_3aError,"Iso14443_3aPoller*, Iso14443_3aData*" +Function,+,iso14443_3a_poller_check_presence,Iso14443_3aError,Iso14443_3aPoller* +Function,+,iso14443_3a_poller_halt,Iso14443_3aError,Iso14443_3aPoller* Function,+,iso14443_3a_poller_send_standard_frame,Iso14443_3aError,"Iso14443_3aPoller*, const BitBuffer*, BitBuffer*, uint32_t" +Function,+,iso14443_3a_poller_sync_read,Iso14443_3aError,"Nfc*, Iso14443_3aData*" Function,+,iso14443_3a_poller_txrx,Iso14443_3aError,"Iso14443_3aPoller*, const BitBuffer*, BitBuffer*, uint32_t" +Function,+,iso14443_3a_poller_txrx_custom_parity,Iso14443_3aError,"Iso14443_3aPoller*, const BitBuffer*, BitBuffer*, uint32_t" Function,+,iso14443_3a_reset,void,Iso14443_3aData* Function,+,iso14443_3a_save,_Bool,"const Iso14443_3aData*, FlipperFormat*" Function,+,iso14443_3a_set_atqa,void,"Iso14443_3aData*, const uint8_t[2]" @@ -1882,6 +1888,9 @@ Function,+,iso14443_3b_get_fwt_fc_max,uint32_t,const Iso14443_3bData* Function,+,iso14443_3b_get_uid,const uint8_t*,"const Iso14443_3bData*, size_t*" Function,+,iso14443_3b_is_equal,_Bool,"const Iso14443_3bData*, const Iso14443_3bData*" Function,+,iso14443_3b_load,_Bool,"Iso14443_3bData*, FlipperFormat*, uint32_t" +Function,+,iso14443_3b_poller_activate,Iso14443_3bError,"Iso14443_3bPoller*, Iso14443_3bData*" +Function,+,iso14443_3b_poller_halt,Iso14443_3bError,Iso14443_3bPoller* +Function,+,iso14443_3b_poller_send_frame,Iso14443_3bError,"Iso14443_3bPoller*, const BitBuffer*, BitBuffer*" Function,+,iso14443_3b_reset,void,Iso14443_3bData* Function,+,iso14443_3b_save,_Bool,"const Iso14443_3bData*, FlipperFormat*" Function,+,iso14443_3b_set_uid,_Bool,"Iso14443_3bData*, const uint8_t*, size_t" @@ -1900,6 +1909,9 @@ Function,+,iso14443_4a_get_historical_bytes,const uint8_t*,"const Iso14443_4aDat Function,+,iso14443_4a_get_uid,const uint8_t*,"const Iso14443_4aData*, size_t*" Function,+,iso14443_4a_is_equal,_Bool,"const Iso14443_4aData*, const Iso14443_4aData*" Function,+,iso14443_4a_load,_Bool,"Iso14443_4aData*, FlipperFormat*, uint32_t" +Function,+,iso14443_4a_poller_halt,Iso14443_4aError,Iso14443_4aPoller* +Function,+,iso14443_4a_poller_read_ats,Iso14443_4aError,"Iso14443_4aPoller*, Iso14443_4aAtsData*" +Function,+,iso14443_4a_poller_send_block,Iso14443_4aError,"Iso14443_4aPoller*, const BitBuffer*, BitBuffer*" Function,+,iso14443_4a_reset,void,Iso14443_4aData* Function,+,iso14443_4a_save,_Bool,"const Iso14443_4aData*, FlipperFormat*" Function,+,iso14443_4a_set_uid,_Bool,"Iso14443_4aData*, const uint8_t*, size_t" @@ -1914,6 +1926,8 @@ Function,+,iso14443_4b_get_device_name,const char*,"const Iso14443_4bData*, NfcD Function,+,iso14443_4b_get_uid,const uint8_t*,"const Iso14443_4bData*, size_t*" Function,+,iso14443_4b_is_equal,_Bool,"const Iso14443_4bData*, const Iso14443_4bData*" Function,+,iso14443_4b_load,_Bool,"Iso14443_4bData*, FlipperFormat*, uint32_t" +Function,+,iso14443_4b_poller_halt,Iso14443_4bError,Iso14443_4bPoller* +Function,+,iso14443_4b_poller_send_block,Iso14443_4bError,"Iso14443_4bPoller*, const BitBuffer*, BitBuffer*" Function,+,iso14443_4b_reset,void,Iso14443_4bData* Function,+,iso14443_4b_save,_Bool,"const Iso14443_4bData*, FlipperFormat*" Function,+,iso14443_4b_set_uid,_Bool,"Iso14443_4bData*, const uint8_t*, size_t" @@ -2141,14 +2155,21 @@ Function,+,mf_classic_is_sector_read,_Bool,"const MfClassicData*, uint8_t" Function,+,mf_classic_is_sector_trailer,_Bool,uint8_t Function,+,mf_classic_is_value_block,_Bool,"MfClassicSectorTrailer*, uint8_t" Function,+,mf_classic_load,_Bool,"MfClassicData*, FlipperFormat*, uint32_t" -Function,+,mf_classic_poller_auth,MfClassicError,"Nfc*, uint8_t, MfClassicKey*, MfClassicKeyType, MfClassicAuthContext*" -Function,+,mf_classic_poller_change_value,MfClassicError,"Nfc*, uint8_t, MfClassicKey*, MfClassicKeyType, int32_t, int32_t*" -Function,+,mf_classic_poller_collect_nt,MfClassicError,"Nfc*, uint8_t, MfClassicKeyType, MfClassicNt*" -Function,+,mf_classic_poller_detect_type,MfClassicError,"Nfc*, MfClassicType*" -Function,+,mf_classic_poller_read,MfClassicError,"Nfc*, const MfClassicDeviceKeys*, MfClassicData*" -Function,+,mf_classic_poller_read_block,MfClassicError,"Nfc*, uint8_t, MfClassicKey*, MfClassicKeyType, MfClassicBlock*" -Function,+,mf_classic_poller_read_value,MfClassicError,"Nfc*, uint8_t, MfClassicKey*, MfClassicKeyType, int32_t*" -Function,+,mf_classic_poller_write_block,MfClassicError,"Nfc*, uint8_t, MfClassicKey*, MfClassicKeyType, MfClassicBlock*" +Function,+,mf_classic_poller_auth,MfClassicError,"MfClassicPoller*, uint8_t, MfClassicKey*, MfClassicKeyType, MfClassicAuthContext*" +Function,+,mf_classic_poller_get_nt,MfClassicError,"MfClassicPoller*, uint8_t, MfClassicKeyType, MfClassicNt*" +Function,+,mf_classic_poller_halt,MfClassicError,MfClassicPoller* +Function,+,mf_classic_poller_read_block,MfClassicError,"MfClassicPoller*, uint8_t, MfClassicBlock*" +Function,+,mf_classic_poller_sync_auth,MfClassicError,"Nfc*, uint8_t, MfClassicKey*, MfClassicKeyType, MfClassicAuthContext*" +Function,+,mf_classic_poller_sync_change_value,MfClassicError,"Nfc*, uint8_t, MfClassicKey*, MfClassicKeyType, int32_t, int32_t*" +Function,+,mf_classic_poller_sync_collect_nt,MfClassicError,"Nfc*, uint8_t, MfClassicKeyType, MfClassicNt*" +Function,+,mf_classic_poller_sync_detect_type,MfClassicError,"Nfc*, MfClassicType*" +Function,+,mf_classic_poller_sync_read,MfClassicError,"Nfc*, const MfClassicDeviceKeys*, MfClassicData*" +Function,+,mf_classic_poller_sync_read_block,MfClassicError,"Nfc*, uint8_t, MfClassicKey*, MfClassicKeyType, MfClassicBlock*" +Function,+,mf_classic_poller_sync_read_value,MfClassicError,"Nfc*, uint8_t, MfClassicKey*, MfClassicKeyType, int32_t*" +Function,+,mf_classic_poller_sync_write_block,MfClassicError,"Nfc*, uint8_t, MfClassicKey*, MfClassicKeyType, MfClassicBlock*" +Function,+,mf_classic_poller_value_cmd,MfClassicError,"MfClassicPoller*, uint8_t, MfClassicValueCommand, int32_t" +Function,+,mf_classic_poller_value_transfer,MfClassicError,"MfClassicPoller*, uint8_t" +Function,+,mf_classic_poller_write_block,MfClassicError,"MfClassicPoller*, uint8_t, MfClassicBlock*" Function,+,mf_classic_reset,void,MfClassicData* Function,+,mf_classic_save,_Bool,"const MfClassicData*, FlipperFormat*" Function,+,mf_classic_set_block_read,void,"MfClassicData*, uint8_t, MfClassicBlock*" @@ -2168,8 +2189,24 @@ Function,+,mf_desfire_get_file_settings,const MfDesfireFileSettings*,"const MfDe Function,+,mf_desfire_get_uid,const uint8_t*,"const MfDesfireData*, size_t*" Function,+,mf_desfire_is_equal,_Bool,"const MfDesfireData*, const MfDesfireData*" Function,+,mf_desfire_load,_Bool,"MfDesfireData*, FlipperFormat*, uint32_t" +Function,+,mf_desfire_poller_read_application,MfDesfireError,"MfDesfirePoller*, MfDesfireApplication*" +Function,+,mf_desfire_poller_read_application_ids,MfDesfireError,"MfDesfirePoller*, SimpleArray*" +Function,+,mf_desfire_poller_read_applications,MfDesfireError,"MfDesfirePoller*, const SimpleArray*, SimpleArray*" +Function,+,mf_desfire_poller_read_file_data,MfDesfireError,"MfDesfirePoller*, MfDesfireFileId, uint32_t, size_t, MfDesfireFileData*" +Function,+,mf_desfire_poller_read_file_data_multi,MfDesfireError,"MfDesfirePoller*, const SimpleArray*, const SimpleArray*, SimpleArray*" +Function,+,mf_desfire_poller_read_file_ids,MfDesfireError,"MfDesfirePoller*, SimpleArray*" +Function,+,mf_desfire_poller_read_file_records,MfDesfireError,"MfDesfirePoller*, MfDesfireFileId, uint32_t, size_t, MfDesfireFileData*" +Function,+,mf_desfire_poller_read_file_settings,MfDesfireError,"MfDesfirePoller*, MfDesfireFileId, MfDesfireFileSettings*" +Function,+,mf_desfire_poller_read_file_settings_multi,MfDesfireError,"MfDesfirePoller*, const SimpleArray*, SimpleArray*" +Function,+,mf_desfire_poller_read_file_value,MfDesfireError,"MfDesfirePoller*, MfDesfireFileId, MfDesfireFileData*" +Function,+,mf_desfire_poller_read_free_memory,MfDesfireError,"MfDesfirePoller*, MfDesfireFreeMemory*" +Function,+,mf_desfire_poller_read_key_settings,MfDesfireError,"MfDesfirePoller*, MfDesfireKeySettings*" +Function,+,mf_desfire_poller_read_key_versions,MfDesfireError,"MfDesfirePoller*, SimpleArray*, uint32_t" +Function,+,mf_desfire_poller_read_version,MfDesfireError,"MfDesfirePoller*, MfDesfireVersion*" +Function,+,mf_desfire_poller_select_application,MfDesfireError,"MfDesfirePoller*, const MfDesfireApplicationId*" Function,+,mf_desfire_reset,void,MfDesfireData* Function,+,mf_desfire_save,_Bool,"const MfDesfireData*, FlipperFormat*" +Function,+,mf_desfire_send_chunks,MfDesfireError,"MfDesfirePoller*, const BitBuffer*, BitBuffer*" Function,+,mf_desfire_set_uid,_Bool,"MfDesfireData*, const uint8_t*, size_t" Function,+,mf_desfire_verify,_Bool,"MfDesfireData*, const FuriString*" Function,+,mf_ultralight_alloc,MfUltralightData*, @@ -2190,13 +2227,22 @@ Function,+,mf_ultralight_is_counter_configured,_Bool,const MfUltralightData* Function,+,mf_ultralight_is_equal,_Bool,"const MfUltralightData*, const MfUltralightData*" Function,+,mf_ultralight_is_page_pwd_or_pack,_Bool,"MfUltralightType, uint16_t" Function,+,mf_ultralight_load,_Bool,"MfUltralightData*, FlipperFormat*, uint32_t" -Function,+,mf_ultralight_poller_read_card,MfUltralightError,"Nfc*, MfUltralightData*" -Function,+,mf_ultralight_poller_read_counter,MfUltralightError,"Nfc*, uint8_t, MfUltralightCounter*" -Function,+,mf_ultralight_poller_read_page,MfUltralightError,"Nfc*, uint16_t, MfUltralightPage*" -Function,+,mf_ultralight_poller_read_signature,MfUltralightError,"Nfc*, MfUltralightSignature*" -Function,+,mf_ultralight_poller_read_tearing_flag,MfUltralightError,"Nfc*, uint8_t, MfUltralightTearingFlag*" -Function,+,mf_ultralight_poller_read_version,MfUltralightError,"Nfc*, MfUltralightVersion*" -Function,+,mf_ultralight_poller_write_page,MfUltralightError,"Nfc*, uint16_t, MfUltralightPage*" +Function,+,mf_ultralight_poller_auth_pwd,MfUltralightError,"MfUltralightPoller*, MfUltralightPollerAuthContext*" +Function,+,mf_ultralight_poller_authenticate,MfUltralightError,MfUltralightPoller* +Function,+,mf_ultralight_poller_read_counter,MfUltralightError,"MfUltralightPoller*, uint8_t, MfUltralightCounter*" +Function,+,mf_ultralight_poller_read_page,MfUltralightError,"MfUltralightPoller*, uint8_t, MfUltralightPageReadCommandData*" +Function,+,mf_ultralight_poller_read_page_from_sector,MfUltralightError,"MfUltralightPoller*, uint8_t, uint8_t, MfUltralightPageReadCommandData*" +Function,+,mf_ultralight_poller_read_signature,MfUltralightError,"MfUltralightPoller*, MfUltralightSignature*" +Function,+,mf_ultralight_poller_read_tearing_flag,MfUltralightError,"MfUltralightPoller*, uint8_t, MfUltralightTearingFlag*" +Function,+,mf_ultralight_poller_read_version,MfUltralightError,"MfUltralightPoller*, MfUltralightVersion*" +Function,+,mf_ultralight_poller_sync_read_card,MfUltralightError,"Nfc*, MfUltralightData*" +Function,+,mf_ultralight_poller_sync_read_counter,MfUltralightError,"Nfc*, uint8_t, MfUltralightCounter*" +Function,+,mf_ultralight_poller_sync_read_page,MfUltralightError,"Nfc*, uint16_t, MfUltralightPage*" +Function,+,mf_ultralight_poller_sync_read_signature,MfUltralightError,"Nfc*, MfUltralightSignature*" +Function,+,mf_ultralight_poller_sync_read_tearing_flag,MfUltralightError,"Nfc*, uint8_t, MfUltralightTearingFlag*" +Function,+,mf_ultralight_poller_sync_read_version,MfUltralightError,"Nfc*, MfUltralightVersion*" +Function,+,mf_ultralight_poller_sync_write_page,MfUltralightError,"Nfc*, uint16_t, MfUltralightPage*" +Function,+,mf_ultralight_poller_write_page,MfUltralightError,"MfUltralightPoller*, uint8_t, const MfUltralightPage*" Function,+,mf_ultralight_reset,void,MfUltralightData* Function,+,mf_ultralight_save,_Bool,"const MfUltralightData*, FlipperFormat*" Function,+,mf_ultralight_set_uid,_Bool,"MfUltralightData*, const uint8_t*, size_t" @@ -2290,6 +2336,7 @@ Function,+,nfc_poller_free,void,NfcPoller* Function,+,nfc_poller_get_data,const NfcDeviceData*,const NfcPoller* Function,+,nfc_poller_get_protocol,NfcProtocol,const NfcPoller* Function,+,nfc_poller_start,void,"NfcPoller*, NfcGenericCallback, void*" +Function,+,nfc_poller_start_ex,void,"NfcPoller*, NfcGenericCallbackEx, void*" Function,+,nfc_poller_stop,void,NfcPoller* Function,+,nfc_poller_trx,NfcError,"Nfc*, const BitBuffer*, BitBuffer*, uint32_t" Function,+,nfc_protocol_get_parent,NfcProtocol,NfcProtocol @@ -2608,6 +2655,29 @@ Function,+,srand,void,unsigned Function,-,srand48,void,long Function,-,srandom,void,unsigned Function,+,sscanf,int,"const char*, const char*, ..." +Function,+,st25r3916_change_reg_bits,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t, uint8_t" +Function,+,st25r3916_change_test_reg_bits,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t, uint8_t" +Function,+,st25r3916_check_reg,_Bool,"FuriHalSpiBusHandle*, uint8_t, uint8_t, uint8_t" +Function,+,st25r3916_clear_reg_bits,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t" +Function,+,st25r3916_direct_cmd,void,"FuriHalSpiBusHandle*, uint8_t" +Function,+,st25r3916_get_irq,uint32_t,FuriHalSpiBusHandle* +Function,+,st25r3916_mask_irq,void,"FuriHalSpiBusHandle*, uint32_t" +Function,+,st25r3916_modify_reg,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t, uint8_t" +Function,+,st25r3916_read_burst_regs,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t*, uint8_t" +Function,+,st25r3916_read_fifo,_Bool,"FuriHalSpiBusHandle*, uint8_t*, size_t, size_t*" +Function,+,st25r3916_read_pta_mem,void,"FuriHalSpiBusHandle*, uint8_t*, size_t" +Function,+,st25r3916_read_reg,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t*" +Function,+,st25r3916_read_test_reg,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t*" +Function,+,st25r3916_reg_read_fifo,void,"FuriHalSpiBusHandle*, uint8_t*, size_t" +Function,+,st25r3916_reg_write_fifo,void,"FuriHalSpiBusHandle*, const uint8_t*, size_t" +Function,+,st25r3916_set_reg_bits,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t" +Function,+,st25r3916_write_burst_regs,void,"FuriHalSpiBusHandle*, uint8_t, const uint8_t*, uint8_t" +Function,+,st25r3916_write_fifo,void,"FuriHalSpiBusHandle*, const uint8_t*, size_t" +Function,+,st25r3916_write_pta_mem,void,"FuriHalSpiBusHandle*, const uint8_t*, size_t" +Function,+,st25r3916_write_ptf_mem,void,"FuriHalSpiBusHandle*, const uint8_t*, size_t" +Function,+,st25r3916_write_pttsn_mem,void,"FuriHalSpiBusHandle*, uint8_t*, size_t" +Function,+,st25r3916_write_reg,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t" +Function,+,st25r3916_write_test_reg,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t" Function,+,st25tb_alloc,St25tbData*, Function,+,st25tb_copy,void,"St25tbData*, const St25tbData*" Function,+,st25tb_free,void,St25tbData* @@ -2617,6 +2687,12 @@ Function,+,st25tb_get_device_name,const char*,"const St25tbData*, NfcDeviceNameT Function,+,st25tb_get_uid,const uint8_t*,"const St25tbData*, size_t*" Function,+,st25tb_is_equal,_Bool,"const St25tbData*, const St25tbData*" Function,+,st25tb_load,_Bool,"St25tbData*, FlipperFormat*, uint32_t" +Function,+,st25tb_poller_activate,St25tbError,"St25tbPoller*, St25tbData*" +Function,+,st25tb_poller_get_uid,St25tbError,"St25tbPoller*, uint8_t*" +Function,+,st25tb_poller_halt,St25tbError,St25tbPoller* +Function,+,st25tb_poller_initiate,St25tbError,"St25tbPoller*, uint8_t*" +Function,+,st25tb_poller_read_block,St25tbError,"St25tbPoller*, uint32_t*, uint8_t" +Function,+,st25tb_poller_send_frame,St25tbError,"St25tbPoller*, const BitBuffer*, BitBuffer*, uint32_t" Function,+,st25tb_reset,void,St25tbData* Function,+,st25tb_save,_Bool,"const St25tbData*, FlipperFormat*" Function,+,st25tb_set_uid,_Bool,"St25tbData*, const uint8_t*, size_t" From 615a1479734ecf2e03626153f60dc267800cc429 Mon Sep 17 00:00:00 2001 From: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> Date: Wed, 15 Nov 2023 11:45:41 +0300 Subject: [PATCH 807/824] [FL-3608] Fix iButton crash on missing file (#3210) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- applications/main/ibutton/ibutton.c | 13 ++++++------- applications/main/ibutton/ibutton_i.h | 2 +- .../main/ibutton/scenes/ibutton_scene_add_value.c | 2 +- .../main/ibutton/scenes/ibutton_scene_rpc.c | 2 +- 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/applications/main/ibutton/ibutton.c b/applications/main/ibutton/ibutton.c index fb6d9dcb52a..afd51f7c9e1 100644 --- a/applications/main/ibutton/ibutton.c +++ b/applications/main/ibutton/ibutton.c @@ -174,22 +174,21 @@ void ibutton_free(iButton* ibutton) { free(ibutton); } -bool ibutton_load_key(iButton* ibutton) { +bool ibutton_load_key(iButton* ibutton, bool show_error) { view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewLoading); const bool success = ibutton_protocols_load( ibutton->protocols, ibutton->key, furi_string_get_cstr(ibutton->file_path)); - if(!success) { - dialog_message_show_storage_error(ibutton->dialogs, "Cannot load\nkey file"); - - } else { + if(success) { FuriString* tmp = furi_string_alloc(); path_extract_filename(ibutton->file_path, tmp, true); strncpy(ibutton->key_name, furi_string_get_cstr(tmp), IBUTTON_KEY_NAME_SIZE); furi_string_free(tmp); + } else if(show_error) { + dialog_message_show_storage_error(ibutton->dialogs, "Cannot load\nkey file"); } return success; @@ -210,7 +209,7 @@ bool ibutton_select_and_load_key(iButton* ibutton) { if(!dialog_file_browser_show( ibutton->dialogs, ibutton->file_path, ibutton->file_path, &browser_options)) break; - success = ibutton_load_key(ibutton); + success = ibutton_load_key(ibutton, true); } while(!success); return success; @@ -283,7 +282,7 @@ int32_t ibutton_app(void* arg) { } else { furi_string_set(ibutton->file_path, (const char*)arg); - key_loaded = ibutton_load_key(ibutton); + key_loaded = ibutton_load_key(ibutton, true); } } diff --git a/applications/main/ibutton/ibutton_i.h b/applications/main/ibutton/ibutton_i.h index 077b148076a..c6a35f888b4 100644 --- a/applications/main/ibutton/ibutton_i.h +++ b/applications/main/ibutton/ibutton_i.h @@ -90,7 +90,7 @@ typedef enum { } iButtonNotificationMessage; bool ibutton_select_and_load_key(iButton* ibutton); -bool ibutton_load_key(iButton* ibutton); +bool ibutton_load_key(iButton* ibutton, bool show_error); bool ibutton_save_key(iButton* ibutton); bool ibutton_delete_key(iButton* ibutton); void ibutton_reset_key(iButton* ibutton); diff --git a/applications/main/ibutton/scenes/ibutton_scene_add_value.c b/applications/main/ibutton/scenes/ibutton_scene_add_value.c index dc340771b2a..71b852115e6 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_add_value.c +++ b/applications/main/ibutton/scenes/ibutton_scene_add_value.c @@ -43,7 +43,7 @@ bool ibutton_scene_add_value_on_event(void* context, SceneManagerEvent event) { } else if(event.type == SceneManagerEventTypeBack) { // User cancelled editing, reload the key from storage if(scene_manager_has_previous_scene(scene_manager, iButtonSceneSavedKeyMenu)) { - if(!ibutton_load_key(ibutton)) { + if(!ibutton_load_key(ibutton, true)) { consumed = scene_manager_search_and_switch_to_previous_scene( scene_manager, iButtonSceneStart); } diff --git a/applications/main/ibutton/scenes/ibutton_scene_rpc.c b/applications/main/ibutton/scenes/ibutton_scene_rpc.c index 7106fefaa25..f4f193a47e8 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_rpc.c +++ b/applications/main/ibutton/scenes/ibutton_scene_rpc.c @@ -26,7 +26,7 @@ bool ibutton_scene_rpc_on_event(void* context, SceneManagerEvent event) { if(event.event == iButtonCustomEventRpcLoadFile) { bool result = false; - if(ibutton_load_key(ibutton)) { + if(ibutton_load_key(ibutton, false)) { popup_set_text(popup, ibutton->key_name, 82, 32, AlignCenter, AlignTop); view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewPopup); From ba074068b0c0c620b6ab13b88c8e294b9d3aaf8d Mon Sep 17 00:00:00 2001 From: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> Date: Wed, 15 Nov 2023 11:56:13 +0300 Subject: [PATCH 808/824] [FL-3662] Do not remove file when renaming to itself (#3193) * Do not allow overwriting a file with dir and support renaming file to itself * Fix operator precedence error * Add support for storage-specific path equivalence checks * Fix typo * Fix updater compilation * Update Doxygen comments in storage.h Co-authored-by: Aleksandr Kutuzov --- .../storage/filesystem_api_internal.h | 8 + applications/services/storage/storage.h | 593 ++++++++++++------ .../services/storage/storage_external_api.c | 40 +- .../services/storage/storage_message.h | 9 + .../services/storage/storage_processing.c | 50 ++ .../services/storage/storages/storage_ext.c | 11 + .../services/storage/storages/storage_int.c | 5 + targets/f18/api_symbols.csv | 3 +- targets/f7/api_symbols.csv | 3 +- 9 files changed, 507 insertions(+), 215 deletions(-) diff --git a/applications/services/storage/filesystem_api_internal.h b/applications/services/storage/filesystem_api_internal.h index 967d3bb41c1..ba98b380e94 100644 --- a/applications/services/storage/filesystem_api_internal.h +++ b/applications/services/storage/filesystem_api_internal.h @@ -165,6 +165,13 @@ typedef struct { * @param total_space pointer to total space value * @param free_space pointer to free space value * @return FS_Error error info + * + * @var FS_Common_Api::equivalent_path + * @brief Test whether two paths are equivalent (e.g differing case on a case-insensitive fs) + * @param path1 first path to be compared + * @param path2 second path to be compared + * @param truncate if set to true, compare only up to the path1's length + * @return true if path1 and path2 are considered equivalent */ typedef struct { FS_Error (*const stat)(void* context, const char* path, FileInfo* fileinfo); @@ -175,6 +182,7 @@ typedef struct { const char* fs_path, uint64_t* total_space, uint64_t* free_space); + bool (*const equivalent_path)(const char* path1, const char* path2); } FS_Common_Api; /** Full filesystem api structure */ diff --git a/applications/services/storage/storage.h b/applications/services/storage/storage.h index 1abc8ed0ebd..3caa155c7dc 100644 --- a/applications/services/storage/storage.h +++ b/applications/services/storage/storage.h @@ -1,4 +1,9 @@ +/** + * @file storage.h + * @brief APIs for working with storages, directories and files. + */ #pragma once + #include #include "filesystem_api_defines.h" #include "storage_sd_api.h" @@ -23,43 +28,62 @@ extern "C" { typedef struct Storage Storage; -/** Allocates and initializes a file descriptor - * @return File* +/** + * @brief Allocate and initialize a file instance. + * + * @param storage pointer to a storage API instance. + * @return pointer to the created instance. */ File* storage_file_alloc(Storage* storage); -/** Frees the file descriptor. Closes the file if it was open. +/** + * @brief Free the file instance. + * + * If the file was open, calling this function will close it automatically. + * @param file pointer to the file instance to be freed. */ void storage_file_free(File* file); +/** + * @brief Enumeration of events emitted by the storage through the PubSub system. + */ typedef enum { - StorageEventTypeCardMount, - StorageEventTypeCardUnmount, - StorageEventTypeCardMountError, - StorageEventTypeFileClose, - StorageEventTypeDirClose, + StorageEventTypeCardMount, /**< SD card was mounted. */ + StorageEventTypeCardUnmount, /**< SD card was unmounted. */ + StorageEventTypeCardMountError, /**< An error occurred during mounting of an SD card. */ + StorageEventTypeFileClose, /**< A file was closed. */ + StorageEventTypeDirClose, /**< A directory was closed. */ } StorageEventType; +/** + * @brief Storage event (passed to the PubSub callback). + */ typedef struct { - StorageEventType type; + StorageEventType type; /**< Type of the event. */ } StorageEvent; /** - * Get storage pubsub. + * @brief Get the storage pubsub instance. + * * Storage will send StorageEvent messages. - * @param storage - * @return FuriPubSub* + * + * @param storage pointer to a storage API instance. + * @return pointer to the pubsub instance. */ FuriPubSub* storage_get_pubsub(Storage* storage); /******************* File Functions *******************/ -/** Opens an existing file or create a new one. - * @param file pointer to file object. - * @param path path to file - * @param access_mode access mode from FS_AccessMode +/** + * @brief Open an existing file or create a new one. + * + * @warning The calling code MUST call storage_file_close() even if the open operation had failed. + * + * @param file pointer to the file instance to be opened. + * @param path pointer to a zero-terminated string containing the path to the file to be opened. + * @param access_mode access mode from FS_AccessMode. * @param open_mode open mode from FS_OpenMode - * @return success flag. You need to close the file even if the open operation failed. + * @return true if the file was successfully opened, false otherwise. */ bool storage_file_open( File* file, @@ -67,202 +91,267 @@ bool storage_file_open( FS_AccessMode access_mode, FS_OpenMode open_mode); -/** Close the file. - * @param file pointer to a file object, the file object will be freed. - * @return success flag +/** + * @brief Close the file. + * + * @param file pointer to the file instance to be closed. + * @return true if the file was successfully closed, false otherwise. */ bool storage_file_close(File* file); -/** Tells if the file is open - * @param file pointer to a file object - * @return bool true if file is open +/** + * @brief Check whether the file is open. + * + * @param file pointer to the file instance in question. + * @return true if the file is open, false otherwise. */ bool storage_file_is_open(File* file); -/** Tells if the file is a directory - * @param file pointer to a file object - * @return bool true if file is a directory +/** + * @brief Check whether a file instance represents a directory. + * + * @param file pointer to the file instance in question. + * @return true if the file instance represents a directory, false otherwise. */ bool storage_file_is_dir(File* file); -/** Reads bytes from a file into a buffer - * @param file pointer to file object. - * @param buff pointer to a buffer, for reading - * @param bytes_to_read how many bytes to read. Must be less than or equal to the size of the buffer. - * @return uint16_t how many bytes were actually read +/** + * @brief Read bytes from a file into a buffer. + * + * @param file pointer to the file instance to read from. + * @param buff pointer to the buffer to be filled with read data. + * @param bytes_to_read number of bytes to read. Must be less than or equal to the size of the buffer. + * @return actual number of bytes read (may be fewer than requested). */ uint16_t storage_file_read(File* file, void* buff, uint16_t bytes_to_read); -/** Writes bytes from a buffer to a file - * @param file pointer to file object. - * @param buff pointer to buffer, for writing - * @param bytes_to_write how many bytes to write. Must be less than or equal to the size of the buffer. - * @return uint16_t how many bytes were actually written +/** + * @brief Write bytes from a buffer to a file. + * + * @param file pointer to the file instance to write into. + * @param buff pointer to the buffer containing the data to be written. + * @param bytes_to_write number of bytes to write. Must be less than or equal to the size of the buffer. + * @return actual number of bytes written (may be fewer than requested). */ uint16_t storage_file_write(File* file, const void* buff, uint16_t bytes_to_write); -/** Moves the r/w pointer - * @param file pointer to file object. - * @param offset offset to move the r/w pointer - * @param from_start set an offset from the start or from the current position +/** + * @brief Change the current access position in a file. + * + * @param file pointer to the file instance in question. + * @param offset access position offset (meaning depends on from_start parameter). + * @param from_start if true, set the access position relative to the file start, otherwise relative to the current position. * @return success flag */ bool storage_file_seek(File* file, uint32_t offset, bool from_start); -/** Gets the position of the r/w pointer - * @param file pointer to file object. - * @return uint64_t position of the r/w pointer +/** + * @brief Get the current access position. + * + * @param file pointer to the file instance in question. + * @return current access position. */ uint64_t storage_file_tell(File* file); -/** Truncates the file size to the current position of the r/w pointer - * @param file pointer to file object. - * @return bool success flag +/** + * @brief Truncate the file size to the current access position. + * + * @param file pointer to the file instance to be truncated. + * @return true if the file was successfully truncated, false otherwise. */ bool storage_file_truncate(File* file); -/** Gets the size of the file - * @param file pointer to file object. - * @return uint64_t size of the file +/** + * @brief Get the file size. + * + * @param file pointer to the file instance in question. + * @return size of the file, in bytes. */ uint64_t storage_file_size(File* file); -/** Writes file cache to storage - * @param file pointer to file object. - * @return bool success flag +/** + * @brief Synchronise the file cache with the actual storage. + * + * @param file pointer to the file instance in question. + * @return true if the file was successfully synchronised, false otherwise. */ bool storage_file_sync(File* file); -/** Checks that the r/w pointer is at the end of the file - * @param file pointer to file object. - * @return bool success flag +/** + * @brief Check whether the current access position is at the end of the file. + * + * @param file pointer to a file instance in question. + * @return bool true if the current access position is at the end of the file, false otherwise. */ bool storage_file_eof(File* file); /** - * @brief Check that file exists + * @brief Check whether a file exists. * - * @param storage - * @param path - * @return true if file exists + * @param storage pointer to a storage API instance. + * @param path pointer to a zero-terminated string containing the path to the file in question. + * @return true if the file exists, false otherwise. */ bool storage_file_exists(Storage* storage, const char* path); /** - * @brief Copy data from one opened file to another opened file - * Size bytes will be copied from current position of source file to current position of destination file + * @brief Copy data from a source file to the destination file. + * + * Both files must be opened prior to calling this function. + * + * The requested amount of bytes will be copied from the current access position + * in the source file to the current access position in the destination file. * - * @param source source file - * @param destination destination file - * @param size size of data to copy - * @return bool success flag + * @param source pointer to a source file instance. + * @param destination pointer to a destination file instance. + * @param size data size to be copied, in bytes. + * @return true if the data was successfully copied, false otherwise. */ bool storage_file_copy_to_file(File* source, File* destination, uint32_t size); -/******************* Dir Functions *******************/ +/******************* Directory Functions *******************/ -/** Opens a directory to get objects from it - * @param app pointer to the api - * @param file pointer to file object. - * @param path path to directory - * @return bool success flag. You need to close the directory even if the open operation failed. +/** + * @brief Open a directory. + * + * Opening a directory is necessary to be able to read its contents with storage_dir_read(). + * + * @warning The calling code MUST call storage_dir_close() even if the open operation had failed. + * + * @param file pointer to a file instance representing the directory in question. + * @param path pointer to a zero-terminated string containing the path of the directory in question. + * @return true if the directory was successfully opened, false otherwise. */ bool storage_dir_open(File* file, const char* path); -/** Close the directory. Also free file handle structure and point it to the NULL. - * @param file pointer to a file object. - * @return bool success flag +/** + * @brief Close the directory. + * + * @param file pointer to a file instance representing the directory in question. + * @return true if the directory was successfully closed, false otherwise. */ bool storage_dir_close(File* file); -/** Reads the next object in the directory - * @param file pointer to file object. - * @param fileinfo pointer to the read FileInfo, may be NULL - * @param name pointer to name buffer, may be NULL - * @param name_length name buffer length - * @return success flag (if the next object does not exist, it also returns false and sets the file error id to FSE_NOT_EXIST) +/** + * @brief Get the next item in the directory. + * + * If the next object does not exist, this function returns false as well + * and sets the file error id to FSE_NOT_EXIST. + * + * @param file pointer to a file instance representing the directory in question. + * @param fileinfo pointer to the FileInfo structure to contain the info (may be NULL). + * @param name pointer to the buffer to contain the name (may be NULL). + * @param name_length maximum capacity of the name buffer, in bytes. + * @return true if the next item was successfully read, false otherwise. */ bool storage_dir_read(File* file, FileInfo* fileinfo, char* name, uint16_t name_length); -/** Rewinds the read pointer to first item in the directory - * @param file pointer to file object. - * @return bool success flag +/** + * @brief Change the access position to first item in the directory. + * + * @param file pointer to a file instance representing the directory in question. + * @return true if the access position was successfully changed, false otherwise. */ bool storage_dir_rewind(File* file); /** - * @brief Check that dir exists + * @brief Check whether a directory exists. * - * @param storage - * @param path - * @return bool + * @param storage pointer to a storage API instance. + * @param path pointer to a zero-terminated string containing the path of the directory in question. + * @return true if the directory exists, false otherwise. */ bool storage_dir_exists(Storage* storage, const char* path); /******************* Common Functions *******************/ -/** Retrieves unix timestamp of last access - * - * @param storage The storage instance - * @param path path to file/directory - * @param timestamp the timestamp pointer +/** + * @brief Get the last access time in UNIX format. * - * @return FS_Error operation result + * @param storage pointer to a storage API instance. + * @param path pointer to a zero-terminated string containing the path of the item in question. + * @param timestamp pointer to a value to contain the timestamp. + * @return FSE_OK if the timestamp has been successfully received, any other error code on failure. */ FS_Error storage_common_timestamp(Storage* storage, const char* path, uint32_t* timestamp); -/** Retrieves information about a file/directory - * @param app pointer to the api - * @param path path to file/directory - * @param fileinfo pointer to the read FileInfo, may be NULL - * @return FS_Error operation result +/** + * @brief Get information about a file or a directory. + * + * @param storage pointer to a storage API instance. + * @param path pointer to a zero-terminated string containing the path of the item in question. + * @param fileinfo pointer to the FileInfo structure to contain the info (may be NULL). + * @return FSE_OK if the info has been successfully received, any other error code on failure. */ FS_Error storage_common_stat(Storage* storage, const char* path, FileInfo* fileinfo); -/** Removes a file/directory from the repository, the directory must be empty and the file/directory must not be open - * @param app pointer to the api - * @param path - * @return FS_Error operation result +/** + * @brief Remove a file or a directory. + * + * The directory must be empty. + * The file or the directory must NOT be open. + * + * @param storage pointer to a storage API instance. + * @param path pointer to a zero-terminated string containing the path of the item to be removed. + * @return FSE_OK if the file or directory has been successfully removed, any other error code on failure. */ FS_Error storage_common_remove(Storage* storage, const char* path); -/** Renames file/directory, file/directory must not be open. Will overwrite existing file. - * @param app pointer to the api - * @param old_path old path - * @param new_path new path - * @return FS_Error operation result +/** + * @brief Rename a file or a directory. + * + * The file or the directory must NOT be open. + * Will overwrite the destination file if it already exists. + * + * Renaming a regular file to itself does nothing and always succeeds. + * Renaming a directory to itself or to a subdirectory of itself always fails. + * + * @param storage pointer to a storage API instance. + * @param old_path pointer to a zero-terminated string containing the source path. + * @param new_path pointer to a zero-terminated string containing the destination path. + * @return FSE_OK if the file or directory has been successfully renamed, any other error code on failure. */ FS_Error storage_common_rename(Storage* storage, const char* old_path, const char* new_path); -/** Copy file, file must not be open - * @param app pointer to the api - * @param old_path old path - * @param new_path new path - * @return FS_Error operation result +/** + * @brief Copy the file to a new location. + * + * The file must NOT be open at the time of calling this function. + * + * @param storage pointer to a storage API instance. + * @param old_path pointer to a zero-terminated string containing the source path. + * @param new_path pointer to a zero-terminated string containing the destination path. + * @return FSE_OK if the file has been successfully copied, any other error code on failure. */ FS_Error storage_common_copy(Storage* storage, const char* old_path, const char* new_path); -/** Copy one folder contents into another with rename of all conflicting files - * @param app pointer to the api - * @param old_path old path - * @param new_path new path - * @return FS_Error operation result +/** + * @brief Copy the contents of one directory into another and rename all conflicting files. + * + * @param storage pointer to a storage API instance. + * @param old_path pointer to a zero-terminated string containing the source path. + * @param new_path pointer to a zero-terminated string containing the destination path. + * @return FSE_OK if the directories have been successfully merged, any other error code on failure. */ FS_Error storage_common_merge(Storage* storage, const char* old_path, const char* new_path); -/** Creates a directory - * @param app pointer to the api - * @param path directory path - * @return FS_Error operation result +/** + * @brief Create a directory. + * + * @param storage pointer to a storage API instance. + * @param fs_path pointer to a zero-terminated string containing the directory path. + * @return FSE_OK if the directory has been successfully created, any other error code on failure. */ FS_Error storage_common_mkdir(Storage* storage, const char* path); -/** Gets general information about the storage - * @param app pointer to the api - * @param fs_path the path to the storage of interest - * @param total_space pointer to total space record, will be filled - * @param free_space pointer to free space record, will be filled - * @return FS_Error operation result +/** + * @brief Get the general information about the storage. + * + * @param storage pointer to a storage API instance. + * @param fs_path pointer to a zero-terminated string containing the path to the storage question. + * @param total_space pointer to the value to contain the total capacity, in bytes. + * @param free_space pointer to the value to contain the available space, in bytes. + * @return FSE_OK if the information has been successfully received, any other error code on failure. */ FS_Error storage_common_fs_info( Storage* storage, @@ -271,150 +360,242 @@ FS_Error storage_common_fs_info( uint64_t* free_space); /** - * @brief Parse aliases in path and replace them with real path - * Also will create special folders if they are not exist + * @brief Parse aliases in a path and replace them with the real path. + * + * Necessary special directories will be created automatically if they did not exist. * - * @param storage - * @param path - * @return bool + * @param storage pointer to a storage API instance. + * @param path pointer to a zero-terminated string containing the path in question. + * @return true if the path was successfully resolved, false otherwise. */ void storage_common_resolve_path_and_ensure_app_directory(Storage* storage, FuriString* path); /** - * @brief Move content of one folder to another, with rename of all conflicting files. - * Source folder will be deleted if the migration is successful. + * @brief Move the contents of source folder to destination one and rename all conflicting files. + * + * Source folder will be deleted if the migration was successful. * - * @param storage - * @param source - * @param dest - * @return FS_Error + * @param storage pointer to a storage API instance. + * @param source pointer to a zero-terminated string containing the source path. + * @param dest pointer to a zero-terminated string containing the destination path. + * @return FSE_OK if the migration was successfull completed, any other error code on failure. */ FS_Error storage_common_migrate(Storage* storage, const char* source, const char* dest); /** - * @brief Check that file or dir exists + * @brief Check whether a file or a directory exists. * - * @param storage - * @param path - * @return bool + * @param storage pointer to a storage API instance. + * @param path pointer to a zero-terminated string containing the path in question. + * @return true if a file or a directory exists, false otherwise. */ bool storage_common_exists(Storage* storage, const char* path); +/** + * @brief Check whether two paths are equivalent. + * + * This function will resolve aliases and apply filesystem-specific + * rules to determine whether the two given paths are equivalent. + * + * Examples: + * - /int/text and /ext/test -> false (Different storages), + * - /int/Test and /int/test -> false (Case-sensitive storage), + * - /ext/Test and /ext/test -> true (Case-insensitive storage). + * + * If the truncate parameter is set to true, the second path will be + * truncated to be no longer than the first one. It is useful to determine + * whether path2 is a subdirectory of path1. + * + * @param storage pointer to a storage API instance. + * @param path1 pointer to a zero-terminated string containing the first path. + * @param path2 pointer to a zero-terminated string containing the second path. + * @param truncate whether to truncate path2 to be no longer than path1. + * @return true if paths are equivalent, false otherwise. + */ +bool storage_common_equivalent_path( + Storage* storage, + const char* path1, + const char* path2, + bool truncate); + /******************* Error Functions *******************/ -/** Retrieves the error text from the error id - * @param error_id error id - * @return const char* error text +/** + * @brief Get the textual description of a numeric error identifer. + * + * @param error_id numeric identifier of the error in question. + * @return pointer to a statically allocated zero-terminated string containing the respective error text. */ const char* storage_error_get_desc(FS_Error error_id); -/** Retrieves the error id from the file object - * @param file pointer to file object. Pointer must not point to NULL. YOU CANNOT RETRIEVE THE ERROR ID IF THE FILE HAS BEEN CLOSED - * @return FS_Error error id +/** + * @brief Get the numeric error identifier from a file instance. + * + * @warning It is not possible to get the error identifier after the file has been closed. + * + * @param file pointer to the file instance in question (must NOT be NULL). + * @return numeric identifier of the last error associated with the file instance. */ FS_Error storage_file_get_error(File* file); -/** Retrieves the internal (storage-specific) error id from the file object - * @param file pointer to file object. Pointer must not point to NULL. YOU CANNOT RETRIEVE THE INTERNAL ERROR ID IF THE FILE HAS BEEN CLOSED - * @return FS_Error error id +/** + * @brief Get the internal (storage-specific) numeric error identifier from a file instance. + * + * @warning It is not possible to get the internal error identifier after the file has been closed. + * + * @param file pointer to the file instance in question (must NOT be NULL). + * @return numeric identifier of the last internal error associated with the file instance. */ int32_t storage_file_get_internal_error(File* file); -/** Retrieves the error text from the file object - * @param file pointer to file object. Pointer must not point to NULL. YOU CANNOT RETRIEVE THE ERROR TEXT IF THE FILE HAS BEEN CLOSED - * @return const char* error text +/** + * @brief Get the textual description of a the last error associated with a file instance. + * + * @warning It is not possible to get the error text after the file has been closed. + * + * @param file pointer to the file instance in question (must NOT be NULL). + * @return pointer to a statically allocated zero-terminated string containing the respective error text. */ const char* storage_file_get_error_desc(File* file); /******************* SD Card Functions *******************/ -/** Formats SD Card - * @param api pointer to the api - * @return FS_Error operation result +/** + * @brief Format the SD Card. + * + * @param storage pointer to a storage API instance. + * @return FSE_OK if the card was successfully formatted, any other error code on failure. */ -FS_Error storage_sd_format(Storage* api); +FS_Error storage_sd_format(Storage* storage); -/** Will unmount the SD card. - * Will return FSE_NOT_READY if the SD card is not mounted. - * Will return FSE_DENIED if there are open files on the SD card. - * @param api pointer to the api - * @return FS_Error operation result +/** + * @brief Unmount the SD card. + * + * These return values have special meaning: + * - FSE_NOT_READY if the SD card is not mounted. + * - FSE_DENIED if there are open files on the SD card. + * + * @param storage pointer to a storage API instance. + * @return FSE_OK if the card was successfully formatted, any other error code on failure. */ -FS_Error storage_sd_unmount(Storage* api); +FS_Error storage_sd_unmount(Storage* storage); -/** Will mount the SD card - * @param api pointer to the api - * @return FS_Error operation result +/** + * @brief Mount the SD card. + * + * @param storage pointer to a storage API instance. + * @return FSE_OK if the card was successfully mounted, any other error code on failure. */ -FS_Error storage_sd_mount(Storage* api); +FS_Error storage_sd_mount(Storage* storage); -/** Retrieves SD card information - * @param api pointer to the api - * @param info pointer to the info - * @return FS_Error operation result +/** + * @brief Get SD card information. + * + * @param storage pointer to a storage API instance. + * @param info pointer to the info object to contain the requested information. + * @return FSE_OK if the info was successfully received, any other error code on failure. */ -FS_Error storage_sd_info(Storage* api, SDInfo* info); +FS_Error storage_sd_info(Storage* storage, SDInfo* info); -/** Retrieves SD card status - * @param api pointer to the api - * @return FS_Error operation result +/** + * @brief Get SD card status. + * + * @param storage pointer to a storage API instance. + * @return storage status in the form of a numeric error identifier. */ -FS_Error storage_sd_status(Storage* api); +FS_Error storage_sd_status(Storage* storage); /******************* Internal LFS Functions *******************/ typedef void (*Storage_name_converter)(FuriString*); -/** Backs up internal storage to a tar archive - * @param api pointer to the api - * @param dstmane destination archive path - * @return FS_Error operation result +/** + * @brief Back up the internal storage contents to a *.tar archive. + * + * @param storage pointer to a storage API instance. + * @param dstname pointer to a zero-terminated string containing the archive file path. + * @return FSE_OK if the storage was successfully backed up, any other error code on failure. */ -FS_Error storage_int_backup(Storage* api, const char* dstname); +FS_Error storage_int_backup(Storage* storage, const char* dstname); -/** Restores internal storage from a tar archive - * @param api pointer to the api - * @param dstmane archive path - * @param converter pointer to filename conversion function, may be NULL - * @return FS_Error operation result +/** + * @brief Restore the internal storage contents from a *.tar archive. + * + * @param storage pointer to a storage API instance. + * @param dstname pointer to a zero-terminated string containing the archive file path. + * @param converter pointer to a filename conversion function (may be NULL). + * @return FSE_OK if the storage was successfully restored, any other error code on failure. */ FS_Error storage_int_restore(Storage* api, const char* dstname, Storage_name_converter converter); /***************** Simplified Functions ******************/ /** - * Removes a file/directory, the directory must be empty and the file/directory must not be open - * @param storage pointer to the api - * @param path - * @return true on success or if file/dir is not exist + * @brief Remove a file or a directory. + * + * The following conditions must be met: + * - the directory must be empty. + * - the file or the directory must NOT be open. + * + * @param storage pointer to a storage API instance. + * @param path pointer to a zero-terminated string containing the item path. + * @return true on success or if the item does not exist, false otherwise. */ bool storage_simply_remove(Storage* storage, const char* path); /** - * Recursively removes a file/directory, the directory can be not empty - * @param storage pointer to the api - * @param path - * @return true on success or if file/dir is not exist + * @brief Recursively remove a file or a directory. + * + * Unlike storage_simply_remove(), the directory does not need to be empty. + * + * @param storage pointer to a storage API instance. + * @param path pointer to a zero-terminated string containing the item path. + * @return true on success or if the item does not exist, false otherwise. */ bool storage_simply_remove_recursive(Storage* storage, const char* path); /** - * Creates a directory - * @param storage - * @param path - * @return true on success or if directory is already exist + * @brief Create a directory. + * + * @param storage pointer to a storage API instance. + * @param path pointer to a zero-terminated string containing the directory path. + * @return true on success or if directory does already exist, false otherwise. */ bool storage_simply_mkdir(Storage* storage, const char* path); /** - * @brief Get next free filename. + * @brief Get the next free filename in a directory. + * + * Usage example: + * ```c + * FuriString* file_name = furi_string_alloc(); + * Storage* storage = furi_record_open(RECORD_STORAGE); + * + * storage_get_next_filename(storage, + * "/ext/test", + * "cookies", + * ".yum", + * 20); + * + * furi_record_close(RECORD_STORAGE); + * + * use_file_name(file_name); + * + * furi_string_free(file_name); + * ``` + * Possible file_name values after calling storage_get_next_filename(): + * "cookies", "cookies1", "cookies2", ... etc depending on whether any of + * these files have already existed in the directory. + * + * @note If the resulting next file name length is greater than set by the max_len + * parameter, the original filename will be returned instead. * - * @param storage - * @param dirname - * @param filename - * @param fileextension - * @param nextfilename return name - * @param max_len max len name + * @param storage pointer to a storage API instance. + * @param dirname pointer to a zero-terminated string containing the directory path. + * @param filename pointer to a zero-terminated string containing the file name. + * @param fileextension pointer to a zero-terminated string containing the file extension. + * @param nextfilename pointer to a dynamic string containing the resulting file name. + * @param max_len maximum length of the new name. */ void storage_get_next_filename( Storage* storage, diff --git a/applications/services/storage/storage_external_api.c b/applications/services/storage/storage_external_api.c index ed69b49a57d..1027d43101a 100644 --- a/applications/services/storage/storage_external_api.c +++ b/applications/services/storage/storage_external_api.c @@ -431,17 +431,22 @@ FS_Error storage_common_rename(Storage* storage, const char* old_path, const cha } if(storage_dir_exists(storage, old_path)) { - FuriString* dir_path = furi_string_alloc_set_str(old_path); - if(!furi_string_end_with_str(dir_path, "/")) { - furi_string_cat_str(dir_path, "/"); + // Cannot overwrite a file with a directory + if(storage_file_exists(storage, new_path)) { + error = FSE_INVALID_NAME; + break; } - const char* dir_path_s = furi_string_get_cstr(dir_path); - if(strncmp(new_path, dir_path_s, strlen(dir_path_s)) == 0) { + + // Cannot rename a directory to itself or to a nested directory + if(storage_common_equivalent_path(storage, old_path, new_path, true)) { error = FSE_INVALID_NAME; - furi_string_free(dir_path); break; } - furi_string_free(dir_path); + + // Renaming a regular file to itself does nothing and always succeeds + } else if(storage_common_equivalent_path(storage, old_path, new_path, false)) { + error = FSE_OK; + break; } if(storage_file_exists(storage, new_path)) { @@ -742,6 +747,27 @@ bool storage_common_exists(Storage* storage, const char* path) { return storage_common_stat(storage, path, &file_info) == FSE_OK; } +bool storage_common_equivalent_path( + Storage* storage, + const char* path1, + const char* path2, + bool truncate) { + S_API_PROLOGUE; + + SAData data = { + .cequivpath = { + .path1 = path1, + .path2 = path2, + .truncate = truncate, + .thread_id = furi_thread_get_current_id(), + }}; + + S_API_MESSAGE(StorageCommandCommonEquivalentPath); + S_API_EPILOGUE; + + return S_RETURN_BOOL; +} + /****************** ERROR ******************/ const char* storage_error_get_desc(FS_Error error_id) { diff --git a/applications/services/storage/storage_message.h b/applications/services/storage/storage_message.h index 01bc2038010..cd45906d4ac 100644 --- a/applications/services/storage/storage_message.h +++ b/applications/services/storage/storage_message.h @@ -69,6 +69,13 @@ typedef struct { FuriThreadId thread_id; } SADataCResolvePath; +typedef struct { + const char* path1; + const char* path2; + bool truncate; + FuriThreadId thread_id; +} SADataCEquivPath; + typedef struct { uint32_t id; } SADataError; @@ -99,6 +106,7 @@ typedef union { SADataCStat cstat; SADataCFSInfo cfsinfo; SADataCResolvePath cresolvepath; + SADataCEquivPath cequivpath; SADataError error; @@ -142,6 +150,7 @@ typedef enum { StorageCommandSDStatus, StorageCommandCommonResolvePath, StorageCommandSDMount, + StorageCommandCommonEquivalentPath, } StorageCommand; typedef struct { diff --git a/applications/services/storage/storage_processing.c b/applications/services/storage/storage_processing.c index 00126af6f37..9e96765b624 100644 --- a/applications/services/storage/storage_processing.c +++ b/applications/services/storage/storage_processing.c @@ -98,6 +98,12 @@ static FS_Error storage_get_data(Storage* app, FuriString* path, StorageData** s } } +static void storage_path_trim_trailing_slashes(FuriString* path) { + while(furi_string_end_with(path, "/")) { + furi_string_left(path, furi_string_size(path) - 1); + } +} + /******************* File Functions *******************/ bool storage_process_file_open( @@ -357,6 +363,8 @@ static FS_Error storage_process_common_remove(Storage* app, FuriString* path) { FS_Error ret = storage_get_data(app, path, &storage); do { + if(ret != FSE_OK) break; + if(storage_path_already_open(path, storage)) { ret = FSE_ALREADY_OPEN; break; @@ -398,6 +406,31 @@ static FS_Error storage_process_common_fs_info( return ret; } +static bool + storage_process_common_equivalent_path(Storage* app, FuriString* path1, FuriString* path2) { + bool ret = false; + + do { + const StorageType storage_type1 = storage_get_type_by_path(path1); + const StorageType storage_type2 = storage_get_type_by_path(path2); + + // Paths on different storages are of course not equal + if(storage_type1 != storage_type2) break; + + StorageData* storage; + const FS_Error status = storage_get_data(app, path1, &storage); + + if(status != FSE_OK) break; + + FS_CALL( + storage, + common.equivalent_path(furi_string_get_cstr(path1), furi_string_get_cstr(path2))); + + } while(false); + + return ret; +} + /****************** Raw SD API ******************/ // TODO FL-3521: think about implementing a custom storage API to split that kind of api linkage #include "storages/storage_ext.h" @@ -649,6 +682,23 @@ void storage_process_message_internal(Storage* app, StorageMessage* message) { app, message->data->cresolvepath.path, message->data->cresolvepath.thread_id, true); break; + case StorageCommandCommonEquivalentPath: { + FuriString* path1 = furi_string_alloc_set(message->data->cequivpath.path1); + FuriString* path2 = furi_string_alloc_set(message->data->cequivpath.path2); + storage_path_trim_trailing_slashes(path1); + storage_path_trim_trailing_slashes(path2); + storage_process_alias(app, path1, message->data->cequivpath.thread_id, false); + storage_process_alias(app, path2, message->data->cequivpath.thread_id, false); + if(message->data->cequivpath.truncate) { + furi_string_left(path2, furi_string_size(path1)); + } + message->return_data->bool_value = + storage_process_common_equivalent_path(app, path1, path2); + furi_string_free(path1); + furi_string_free(path2); + break; + } + // SD operations case StorageCommandSDFormat: message->return_data->error_value = storage_process_sd_format(app); diff --git a/applications/services/storage/storages/storage_ext.c b/applications/services/storage/storages/storage_ext.c index 4630d99ea74..7e617c0ff20 100644 --- a/applications/services/storage/storages/storage_ext.c +++ b/applications/services/storage/storages/storage_ext.c @@ -596,6 +596,16 @@ static FS_Error storage_ext_common_fs_info( #endif } +static bool storage_ext_common_equivalent_path(const char* path1, const char* path2) { +#ifdef FURI_RAM_EXEC + UNUSED(path1); + UNUSED(path2); + return false; +#else + return strcasecmp(path1, path2) == 0; +#endif +} + /******************* Init Storage *******************/ static const FS_Api fs_api = { .file = @@ -624,6 +634,7 @@ static const FS_Api fs_api = { .mkdir = storage_ext_common_mkdir, .remove = storage_ext_common_remove, .fs_info = storage_ext_common_fs_info, + .equivalent_path = storage_ext_common_equivalent_path, }, }; diff --git a/applications/services/storage/storages/storage_int.c b/applications/services/storage/storages/storage_int.c index 2534d47a112..39b092c1d19 100644 --- a/applications/services/storage/storages/storage_int.c +++ b/applications/services/storage/storages/storage_int.c @@ -686,6 +686,10 @@ static FS_Error storage_int_common_fs_info( return storage_int_parse_error(result); } +static bool storage_int_common_equivalent_path(const char* path1, const char* path2) { + return strcmp(path1, path2) == 0; +} + /******************* Init Storage *******************/ static const FS_Api fs_api = { .file = @@ -714,6 +718,7 @@ static const FS_Api fs_api = { .mkdir = storage_int_common_mkdir, .remove = storage_int_common_remove, .fs_info = storage_int_common_fs_info, + .equivalent_path = storage_int_common_equivalent_path, }, }; diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 44829c264da..8ed8f403c70 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,45.0,, +Version,+,45.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -2071,6 +2071,7 @@ Function,+,st25r3916_write_pttsn_mem,void,"FuriHalSpiBusHandle*, uint8_t*, size_ Function,+,st25r3916_write_reg,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t" Function,+,st25r3916_write_test_reg,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t" Function,+,storage_common_copy,FS_Error,"Storage*, const char*, const char*" +Function,+,storage_common_equivalent_path,_Bool,"Storage*, const char*, const char*, _Bool" Function,+,storage_common_exists,_Bool,"Storage*, const char*" Function,+,storage_common_fs_info,FS_Error,"Storage*, const char*, uint64_t*, uint64_t*" Function,+,storage_common_merge,FS_Error,"Storage*, const char*, const char*" diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 514f3328682..64695c2dbc9 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,45.0,, +Version,+,45.1,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -2698,6 +2698,7 @@ Function,+,st25tb_save,_Bool,"const St25tbData*, FlipperFormat*" Function,+,st25tb_set_uid,_Bool,"St25tbData*, const uint8_t*, size_t" Function,+,st25tb_verify,_Bool,"St25tbData*, const FuriString*" Function,+,storage_common_copy,FS_Error,"Storage*, const char*, const char*" +Function,+,storage_common_equivalent_path,_Bool,"Storage*, const char*, const char*, _Bool" Function,+,storage_common_exists,_Bool,"Storage*, const char*" Function,+,storage_common_fs_info,FS_Error,"Storage*, const char*, uint64_t*, uint64_t*" Function,+,storage_common_merge,FS_Error,"Storage*, const char*, const char*" From a61b5d4b4cd7f11f4b836ba085860f58689905b8 Mon Sep 17 00:00:00 2001 From: Flipper Zelebro <149575765+flipperzelebro@users.noreply.github.com> Date: Wed, 15 Nov 2023 10:04:45 +0100 Subject: [PATCH 809/824] Add Mastercode SubGHz Protocol (#3187) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add Mastercode SubGHz Protocol * Add 2 valid raw files and cleanup code * Add tests to the two Raw Files * Remove extra test & delete comments * Fixes pulse length and shows correct Key Co-authored-by: FlipperZelebro Co-authored-by: あく --- .../unit_tests/subghz/mastercode.sub | 7 + .../unit_tests/subghz/mastercode_raw.sub | 6 + .../debug/unit_tests/subghz/subghz_test.c | 15 + lib/subghz/protocols/mastercode.c | 360 ++++++++++++++++++ lib/subghz/protocols/mastercode.h | 109 ++++++ lib/subghz/protocols/protocol_items.c | 1 + lib/subghz/protocols/protocol_items.h | 1 + 7 files changed, 499 insertions(+) create mode 100644 applications/debug/unit_tests/resources/unit_tests/subghz/mastercode.sub create mode 100644 applications/debug/unit_tests/resources/unit_tests/subghz/mastercode_raw.sub create mode 100644 lib/subghz/protocols/mastercode.c create mode 100644 lib/subghz/protocols/mastercode.h diff --git a/applications/debug/unit_tests/resources/unit_tests/subghz/mastercode.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/mastercode.sub new file mode 100644 index 00000000000..f50abbfe8f5 --- /dev/null +++ b/applications/debug/unit_tests/resources/unit_tests/subghz/mastercode.sub @@ -0,0 +1,7 @@ +Filetype: Flipper SubGhz Key File +Version: 1 +Frequency: 433920000 +Preset: FuriHalSubGhzPresetOok270Async +Protocol: Mastercode +Bit: 36 +Key: 00 00 00 0B 7E 00 3C 08 diff --git a/applications/debug/unit_tests/resources/unit_tests/subghz/mastercode_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/mastercode_raw.sub new file mode 100644 index 00000000000..69d3f396cdb --- /dev/null +++ b/applications/debug/unit_tests/resources/unit_tests/subghz/mastercode_raw.sub @@ -0,0 +1,6 @@ +Filetype: Flipper SubGhz RAW File +Version: 1 +Frequency: 433920000 +Preset: FuriHalSubGhzPresetOok270Async +Protocol: RAW +RAW_Data: 10389 -66 405095 -102 207 -106 1165 -130 963739 -1232 899 -2250 2003 -1190 2017 -1202 911 -2256 2021 -1162 2045 -1134 2047 -1164 2047 -1138 2031 -1180 2039 -1182 949 -2190 995 -2214 961 -2228 963 -2198 963 -2214 977 -2212 975 -2210 975 -2208 971 -2200 963 -2210 993 -2184 2075 -1130 2051 -1142 2055 -1136 2047 -1178 965 -2236 933 -2220 975 -2184 999 -2222 967 -2208 969 -2214 979 -2202 2027 -1156 975 -2242 943 -16080 2023 -1162 967 -2220 2057 -1114 2061 -1124 1007 -2242 2025 -1134 2055 -1168 2017 -1138 2075 -1134 2053 -1136 2075 -1130 979 -2214 979 -2174 999 -2182 1001 -2204 977 -2206 1003 -2188 979 -2176 999 -2182 1009 -2176 1009 -2176 1001 -2212 2029 -1116 2091 -1102 2109 -1092 2095 -1126 1001 -2150 1011 -2180 1011 -2180 1009 -2178 1009 -2172 1009 -2166 1001 -2198 2065 -1136 975 -2220 971 -16018 2097 -1166 951 -2240 2009 -1186 2011 -1160 979 -2208 2035 -1134 2053 -1138 2061 -1158 2045 -1152 2029 -1152 2051 -1166 963 -2188 993 -2222 951 -2214 963 -2220 965 -2212 979 -2212 977 -2180 1003 -2202 965 -2218 975 -2216 967 -2188 2061 -1124 2083 -1126 2071 -1130 2059 -1134 993 -2188 979 -2240 947 -2204 979 -2214 971 -2214 973 -2210 971 -2206 2053 -1130 979 -2216 969 -16056 2053 -1134 1001 -2224 2021 -1150 2051 -1154 953 -2240 2045 -1146 2023 -1168 2033 -1144 2065 -1146 2055 -1130 2071 -1160 961 -2192 973 -2190 1005 -2214 975 -2206 967 -2206 975 -2206 967 -2208 975 -2212 967 -2212 979 -2218 977 -2178 2063 -1156 2035 -1160 2061 -1126 2065 -1130 981 -2186 1003 -2210 977 -2208 973 -2202 977 -2200 965 -2248 943 -2206 2039 -1190 941 -48536 65 -7254 263 -68 363 -102 131 -232 263 -264 751 -230 225 -822 397 -634 231 -268 263 -134 267 -64 867 -132 305 -138 67 -100 331 -98 891 -66 455 -66 531 -100 299 -134 897 -98 693 -132 291 -132 333 -98 337 -68 331 diff --git a/applications/debug/unit_tests/subghz/subghz_test.c b/applications/debug/unit_tests/subghz/subghz_test.c index 64e0591dfa6..2f2b9e9811c 100644 --- a/applications/debug/unit_tests/subghz/subghz_test.c +++ b/applications/debug/unit_tests/subghz/subghz_test.c @@ -654,6 +654,13 @@ MU_TEST(subghz_decoder_kinggates_stylo4k_test) { "Test decoder " SUBGHZ_PROTOCOL_KINGGATES_STYLO_4K_NAME " error\r\n"); } +MU_TEST(subghz_decoder_mastercode_test) { + mu_assert( + subghz_decoder_test( + EXT_PATH("unit_tests/subghz/mastercode_raw.sub"), SUBGHZ_PROTOCOL_MASTERCODE_NAME), + "Test decoder " SUBGHZ_PROTOCOL_MASTERCODE_NAME " error\r\n"); +} + //test encoders MU_TEST(subghz_encoder_princeton_test) { mu_assert( @@ -805,6 +812,12 @@ MU_TEST(subghz_encoder_dooya_test) { "Test encoder " SUBGHZ_PROTOCOL_DOOYA_NAME " error\r\n"); } +MU_TEST(subghz_encoder_mastercode_test) { + mu_assert( + subghz_encoder_test(EXT_PATH("unit_tests/subghz/mastercode.sub")), + "Test encoder " SUBGHZ_PROTOCOL_MASTERCODE_NAME " error\r\n"); +} + MU_TEST(subghz_random_test) { mu_assert(subghz_decode_random_test(TEST_RANDOM_DIR_NAME), "Random test error\r\n"); } @@ -855,6 +868,7 @@ MU_TEST_SUITE(subghz) { MU_RUN_TEST(subghz_decoder_alutech_at_4n_test); MU_RUN_TEST(subghz_decoder_nice_one_test); MU_RUN_TEST(subghz_decoder_kinggates_stylo4k_test); + MU_RUN_TEST(subghz_decoder_mastercode_test); MU_RUN_TEST(subghz_encoder_princeton_test); MU_RUN_TEST(subghz_encoder_came_test); @@ -881,6 +895,7 @@ MU_TEST_SUITE(subghz) { MU_RUN_TEST(subghz_encoder_smc5326_test); MU_RUN_TEST(subghz_encoder_holtek_ht12x_test); MU_RUN_TEST(subghz_encoder_dooya_test); + MU_RUN_TEST(subghz_encoder_mastercode_test); MU_RUN_TEST(subghz_random_test); subghz_test_deinit(); diff --git a/lib/subghz/protocols/mastercode.c b/lib/subghz/protocols/mastercode.c new file mode 100644 index 00000000000..54ad5bfaa44 --- /dev/null +++ b/lib/subghz/protocols/mastercode.c @@ -0,0 +1,360 @@ +#include "mastercode.h" + +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +// protocol MASTERCODE Clemsa MV1/MV12 +#define TAG "SubGhzProtocolMastercode" + +#define DIP_P 0b11 //(+) +#define DIP_O 0b10 //(0) +#define DIP_N 0b00 //(-) + +#define DIP_PATTERN "%c%c%c%c%c%c%c%c" + +#define SHOW_DIP_P(dip, check_dip) \ + ((((dip >> 0x0) & 0x3) == check_dip) ? '*' : '_'), \ + ((((dip >> 0x2) & 0x3) == check_dip) ? '*' : '_'), \ + ((((dip >> 0x4) & 0x3) == check_dip) ? '*' : '_'), \ + ((((dip >> 0x6) & 0x3) == check_dip) ? '*' : '_'), \ + ((((dip >> 0x8) & 0x3) == check_dip) ? '*' : '_'), \ + ((((dip >> 0xA) & 0x3) == check_dip) ? '*' : '_'), \ + ((((dip >> 0xC) & 0x3) == check_dip) ? '*' : '_'), \ + ((((dip >> 0xE) & 0x3) == check_dip) ? '*' : '_') + +static const SubGhzBlockConst subghz_protocol_mastercode_const = { + .te_short = 1072, + .te_long = 2145, + .te_delta = 150, + .min_count_bit_for_found = 36, +}; + +struct SubGhzProtocolDecoderMastercode { + SubGhzProtocolDecoderBase base; + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; +}; + +struct SubGhzProtocolEncoderMastercode { + SubGhzProtocolEncoderBase base; + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + MastercodeDecoderStepReset = 0, + MastercodeDecoderStepSaveDuration, + MastercodeDecoderStepCheckDuration, +} MastercodeDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_mastercode_decoder = { + .alloc = subghz_protocol_decoder_mastercode_alloc, + .free = subghz_protocol_decoder_mastercode_free, + + .feed = subghz_protocol_decoder_mastercode_feed, + .reset = subghz_protocol_decoder_mastercode_reset, + + .get_hash_data = subghz_protocol_decoder_mastercode_get_hash_data, + .serialize = subghz_protocol_decoder_mastercode_serialize, + .deserialize = subghz_protocol_decoder_mastercode_deserialize, + .get_string = subghz_protocol_decoder_mastercode_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_mastercode_encoder = { + .alloc = subghz_protocol_encoder_mastercode_alloc, + .free = subghz_protocol_encoder_mastercode_free, + + .deserialize = subghz_protocol_encoder_mastercode_deserialize, + .stop = subghz_protocol_encoder_mastercode_stop, + .yield = subghz_protocol_encoder_mastercode_yield, +}; + +const SubGhzProtocol subghz_protocol_mastercode = { + .name = SUBGHZ_PROTOCOL_MASTERCODE_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_mastercode_decoder, + .encoder = &subghz_protocol_mastercode_encoder, +}; + +void* subghz_protocol_encoder_mastercode_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderMastercode* instance = malloc(sizeof(SubGhzProtocolEncoderMastercode)); + + instance->base.protocol = &subghz_protocol_mastercode; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->encoder.repeat = 10; + instance->encoder.size_upload = 72; + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_running = false; + return instance; +} + +void subghz_protocol_encoder_mastercode_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderMastercode* instance = context; + free(instance->encoder.upload); + free(instance); +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderMastercode instance + * @return true On success + */ +static bool + subghz_protocol_encoder_mastercode_get_upload(SubGhzProtocolEncoderMastercode* 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, "Size upload exceeds allocated encoder buffer."); + return false; + } else { + instance->encoder.size_upload = size_upload; + } + + for(uint8_t i = instance->generic.data_count_bit; i > 1; i--) { + if(bit_read(instance->generic.data, i - 1)) { + //send bit 1 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_mastercode_const.te_long); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_mastercode_const.te_short); + } else { + //send bit 0 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_mastercode_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_mastercode_const.te_long); + } + } + if(bit_read(instance->generic.data, 0)) { + //send bit 1 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_mastercode_const.te_long); + instance->encoder.upload[index++] = level_duration_make( + false, + (uint32_t)subghz_protocol_mastercode_const.te_short + + subghz_protocol_mastercode_const.te_short * 13); + } else { + //send bit 0 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_mastercode_const.te_short); + instance->encoder.upload[index++] = level_duration_make( + false, + (uint32_t)subghz_protocol_mastercode_const.te_long + + subghz_protocol_mastercode_const.te_short * 13); + } + return true; +} + +SubGhzProtocolStatus + subghz_protocol_encoder_mastercode_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderMastercode* instance = context; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; + do { + ret = subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_mastercode_const.min_count_bit_for_found); + if(ret != SubGhzProtocolStatusOk) { + break; + } + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + if(!subghz_protocol_encoder_mastercode_get_upload(instance)) { + ret = SubGhzProtocolStatusErrorEncoderGetUpload; + break; + } + instance->encoder.is_running = true; + + } while(false); + + return ret; +} + +void subghz_protocol_encoder_mastercode_stop(void* context) { + SubGhzProtocolEncoderMastercode* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_mastercode_yield(void* context) { + SubGhzProtocolEncoderMastercode* instance = context; + + if(instance->encoder.repeat == 0 || !instance->encoder.is_running) { + instance->encoder.is_running = 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_mastercode_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderMastercode* instance = malloc(sizeof(SubGhzProtocolDecoderMastercode)); + instance->base.protocol = &subghz_protocol_mastercode; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_mastercode_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderMastercode* instance = context; + free(instance); +} + +void subghz_protocol_decoder_mastercode_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderMastercode* instance = context; + instance->decoder.parser_step = MastercodeDecoderStepReset; +} + +void subghz_protocol_decoder_mastercode_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderMastercode* instance = context; + + switch(instance->decoder.parser_step) { + case MastercodeDecoderStepReset: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_mastercode_const.te_short * 15) < + subghz_protocol_mastercode_const.te_delta * 15)) { + instance->decoder.parser_step = MastercodeDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } + break; + + case MastercodeDecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = MastercodeDecoderStepCheckDuration; + } else { + instance->decoder.parser_step = MastercodeDecoderStepReset; + } + break; + + case MastercodeDecoderStepCheckDuration: + if(!level) { + if((DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_mastercode_const.te_short) < + subghz_protocol_mastercode_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_mastercode_const.te_long) < + subghz_protocol_mastercode_const.te_delta * 8)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = MastercodeDecoderStepSaveDuration; + } else if( + (DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_mastercode_const.te_long) < + subghz_protocol_mastercode_const.te_delta * 8) && + (DURATION_DIFF(duration, subghz_protocol_mastercode_const.te_short) < + subghz_protocol_mastercode_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = MastercodeDecoderStepSaveDuration; + } else if( + DURATION_DIFF(duration, subghz_protocol_mastercode_const.te_short * 15) < + subghz_protocol_mastercode_const.te_delta * 15) { + if((DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_mastercode_const.te_short) < + subghz_protocol_mastercode_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + } else if((DURATION_DIFF( + instance->decoder.te_last, + subghz_protocol_mastercode_const.te_long) < + subghz_protocol_mastercode_const.te_delta * 8)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + } else { + instance->decoder.parser_step = MastercodeDecoderStepReset; + } + + if(instance->decoder.decode_count_bit == + subghz_protocol_mastercode_const.min_count_bit_for_found) { + 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 = MastercodeDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + + } else { + instance->decoder.parser_step = MastercodeDecoderStepReset; + } + } else { + instance->decoder.parser_step = MastercodeDecoderStepReset; + } + break; + } +} + +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + */ +static void subghz_protocol_mastercode_check_remote_controller(SubGhzBlockGeneric* instance) { + instance->serial = (instance->data >> 4) & 0xFFFF; + instance->btn = (instance->data >> 2 & 0x03); +} + +uint8_t subghz_protocol_decoder_mastercode_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderMastercode* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +SubGhzProtocolStatus subghz_protocol_decoder_mastercode_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderMastercode* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +SubGhzProtocolStatus + subghz_protocol_decoder_mastercode_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderMastercode* instance = context; + return subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_mastercode_const.min_count_bit_for_found); +} + +void subghz_protocol_decoder_mastercode_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderMastercode* instance = context; + subghz_protocol_mastercode_check_remote_controller(&instance->generic); + furi_string_cat_printf( + output, + "%s %dbit\r\n" + "Key:%llX Btn %X\r\n" + " +: " DIP_PATTERN "\r\n" + " o: " DIP_PATTERN "\r\n" + " -: " DIP_PATTERN "\r\n", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint64_t)(instance->generic.data), + instance->generic.btn, + SHOW_DIP_P(instance->generic.serial, DIP_P), + SHOW_DIP_P(instance->generic.serial, DIP_O), + SHOW_DIP_P(instance->generic.serial, DIP_N)); +} diff --git a/lib/subghz/protocols/mastercode.h b/lib/subghz/protocols/mastercode.h new file mode 100644 index 00000000000..c5c73db989a --- /dev/null +++ b/lib/subghz/protocols/mastercode.h @@ -0,0 +1,109 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_MASTERCODE_NAME "Mastercode" + +typedef struct SubGhzProtocolDecoderMastercode SubGhzProtocolDecoderMastercode; +typedef struct SubGhzProtocolEncoderMastercode SubGhzProtocolEncoderMastercode; + +extern const SubGhzProtocolDecoder subghz_protocol_mastercode_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_mastercode_encoder; +extern const SubGhzProtocol subghz_protocol_mastercode; + +/** + * Allocate SubGhzProtocolEncoderMastercode. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderMastercode* pointer to a SubGhzProtocolEncoderMastercode instance + */ +void* subghz_protocol_encoder_mastercode_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderMastercode. + * @param context Pointer to a SubGhzProtocolEncoderMastercode instance + */ +void subghz_protocol_encoder_mastercode_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderMastercode instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + subghz_protocol_encoder_mastercode_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderMastercode instance + */ +void subghz_protocol_encoder_mastercode_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderMastercode instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_mastercode_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderMastercode. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderMastercode* pointer to a SubGhzProtocolDecoderMastercode instance + */ +void* subghz_protocol_decoder_mastercode_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderMastercode. + * @param context Pointer to a SubGhzProtocolDecoderMastercode instance + */ +void subghz_protocol_decoder_mastercode_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderMastercode. + * @param context Pointer to a SubGhzProtocolDecoderMastercode instance + */ +void subghz_protocol_decoder_mastercode_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderMastercode instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_mastercode_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderMastercode instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_mastercode_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderMastercode. + * @param context Pointer to a SubGhzProtocolDecoderMastercode instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return status + */ +SubGhzProtocolStatus subghz_protocol_decoder_mastercode_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderMastercode. + * @param context Pointer to a SubGhzProtocolDecoderMastercode instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + subghz_protocol_decoder_mastercode_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderMastercode instance + * @param output Resulting text + */ +void subghz_protocol_decoder_mastercode_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/protocol_items.c b/lib/subghz/protocols/protocol_items.c index 74244c5ff40..472a354e384 100644 --- a/lib/subghz/protocols/protocol_items.c +++ b/lib/subghz/protocols/protocol_items.c @@ -43,6 +43,7 @@ const SubGhzProtocol* subghz_protocol_registry_items[] = { &subghz_protocol_alutech_at_4n, &subghz_protocol_kinggates_stylo_4k, &subghz_protocol_bin_raw, + &subghz_protocol_mastercode, }; const SubGhzProtocolRegistry subghz_protocol_registry = { diff --git a/lib/subghz/protocols/protocol_items.h b/lib/subghz/protocols/protocol_items.h index f1a28ac9b51..c5a090e993d 100644 --- a/lib/subghz/protocols/protocol_items.h +++ b/lib/subghz/protocols/protocol_items.h @@ -44,3 +44,4 @@ #include "alutech_at_4n.h" #include "kinggates_stylo_4k.h" #include "bin_raw.h" +#include "mastercode.h" From 457aa5331fe8cbd72d2a17586ff903390f779fe3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Thu, 16 Nov 2023 01:11:05 +0900 Subject: [PATCH 810/824] Various Fixes for 0.95 (#3215) * FuriHal: retry gauge/charger initialization * FuriHal: lower logging level for flash known errata * FuriHal: graceful fail if subghz chip is not working * Furi: issue stop command even if timer is not active, document timer behavior --- furi/core/timer.c | 11 +--- furi/core/timer.h | 13 +++++ targets/f7/furi_hal/furi_hal_flash.c | 2 +- targets/f7/furi_hal/furi_hal_power.c | 33 ++++++++++-- targets/f7/furi_hal/furi_hal_subghz.c | 78 +++++++++++++++++---------- 5 files changed, 95 insertions(+), 42 deletions(-) diff --git a/furi/core/timer.c b/furi/core/timer.c index 17347e5c7dd..027e4cf40db 100644 --- a/furi/core/timer.c +++ b/furi/core/timer.c @@ -122,17 +122,10 @@ FuriStatus furi_timer_stop(FuriTimer* instance) { furi_assert(instance); TimerHandle_t hTimer = (TimerHandle_t)instance; - FuriStatus stat; - if(xTimerIsTimerActive(hTimer) == pdFALSE) { - stat = FuriStatusErrorResource; - } else { - furi_check(xTimerStop(hTimer, portMAX_DELAY) == pdPASS); - stat = FuriStatusOk; - } + furi_check(xTimerStop(hTimer, portMAX_DELAY) == pdPASS); - /* Return execution status */ - return (stat); + return FuriStatusOk; } uint32_t furi_timer_is_running(FuriTimer* instance) { diff --git a/furi/core/timer.h b/furi/core/timer.h index d27ef5025e8..f8f40c56267 100644 --- a/furi/core/timer.h +++ b/furi/core/timer.h @@ -32,6 +32,9 @@ FuriTimer* furi_timer_alloc(FuriTimerCallback func, FuriTimerType type, void* co void furi_timer_free(FuriTimer* instance); /** Start timer + * + * @warning This is asynchronous call, real operation will happen as soon as + * timer service process this request. * * @param instance The pointer to FuriTimer instance * @param[in] ticks The interval in ticks @@ -41,6 +44,9 @@ void furi_timer_free(FuriTimer* instance); FuriStatus furi_timer_start(FuriTimer* instance, uint32_t ticks); /** Restart timer with previous timeout value + * + * @warning This is asynchronous call, real operation will happen as soon as + * timer service process this request. * * @param instance The pointer to FuriTimer instance * @param[in] ticks The interval in ticks @@ -50,6 +56,9 @@ FuriStatus furi_timer_start(FuriTimer* instance, uint32_t ticks); FuriStatus furi_timer_restart(FuriTimer* instance, uint32_t ticks); /** Stop timer + * + * @warning This is asynchronous call, real operation will happen as soon as + * timer service process this request. * * @param instance The pointer to FuriTimer instance * @@ -58,6 +67,10 @@ FuriStatus furi_timer_restart(FuriTimer* instance, uint32_t ticks); FuriStatus furi_timer_stop(FuriTimer* instance); /** Is timer running + * + * @warning This cal may and will return obsolete timer state if timer + * commands are still in the queue. Please read FreeRTOS timer + * documentation first. * * @param instance The pointer to FuriTimer instance * diff --git a/targets/f7/furi_hal/furi_hal_flash.c b/targets/f7/furi_hal/furi_hal_flash.c index 284d48bf53d..7ac7a8bd12e 100644 --- a/targets/f7/furi_hal/furi_hal_flash.c +++ b/targets/f7/furi_hal/furi_hal_flash.c @@ -101,7 +101,7 @@ void furi_hal_flash_init() { // WRITE_REG(FLASH->SR, FLASH_SR_OPTVERR); /* Actually, reset all error flags on start */ if(READ_BIT(FLASH->SR, FURI_HAL_FLASH_SR_ERRORS)) { - FURI_LOG_E(TAG, "FLASH->SR 0x%08lX", FLASH->SR); + FURI_LOG_W(TAG, "FLASH->SR 0x%08lX(Known ERRATA)", FLASH->SR); WRITE_REG(FLASH->SR, FURI_HAL_FLASH_SR_ERRORS); } } diff --git a/targets/f7/furi_hal/furi_hal_power.c b/targets/f7/furi_hal/furi_hal_power.c index 119dee81f92..9e3a70da731 100644 --- a/targets/f7/furi_hal/furi_hal_power.c +++ b/targets/f7/furi_hal/furi_hal_power.c @@ -71,12 +71,37 @@ void furi_hal_power_init() { furi_hal_i2c_acquire(&furi_hal_i2c_handle_power); // Find and init gauge - if(bq27220_init(&furi_hal_i2c_handle_power)) { - furi_hal_power.gauge_ok = bq27220_apply_data_memory( - &furi_hal_i2c_handle_power, furi_hal_power_gauge_data_memory); + size_t retry = 2; + while(retry > 0) { + furi_hal_power.gauge_ok = bq27220_init(&furi_hal_i2c_handle_power); + if(furi_hal_power.gauge_ok) { + furi_hal_power.gauge_ok = bq27220_apply_data_memory( + &furi_hal_i2c_handle_power, furi_hal_power_gauge_data_memory); + } + if(furi_hal_power.gauge_ok) { + break; + } else { + // Normal startup time is 250ms + // But if we try to access gauge at that stage it will become unresponsive + // 2 seconds timeout needed to restart communication + furi_delay_us(2020202); + } + retry--; } // Find and init charger - furi_hal_power.charger_ok = bq25896_init(&furi_hal_i2c_handle_power); + retry = 2; + while(retry > 0) { + furi_hal_power.charger_ok = bq25896_init(&furi_hal_i2c_handle_power); + if(furi_hal_power.charger_ok) { + break; + } else { + // Most likely I2C communication error + // 2 seconds should be enough for all chips on the line to timeout + // Also timing out here is very abnormal + furi_delay_us(2020202); + } + retry--; + } furi_hal_i2c_release(&furi_hal_i2c_handle_power); FURI_LOG_I(TAG, "Init OK"); diff --git a/targets/f7/furi_hal/furi_hal_subghz.c b/targets/f7/furi_hal/furi_hal_subghz.c index f751463532e..a00ca7bf6d4 100644 --- a/targets/f7/furi_hal/furi_hal_subghz.c +++ b/targets/f7/furi_hal/furi_hal_subghz.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -29,7 +30,7 @@ static uint32_t furi_hal_subghz_debug_gpio_buff[2]; /** SubGhz state */ typedef enum { SubGhzStateInit, /**< Init pending */ - + SubGhzStateBroken, /**< Chip power-on self test failed */ SubGhzStateIdle, /**< Idle, energy save mode */ SubGhzStateAsyncRx, /**< Async RX started */ @@ -69,46 +70,67 @@ const GpioPin* furi_hal_subghz_get_data_gpio() { void furi_hal_subghz_init() { furi_assert(furi_hal_subghz.state == SubGhzStateInit); - furi_hal_subghz.state = SubGhzStateIdle; + furi_hal_subghz.state = SubGhzStateBroken; furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); - + do { #ifdef FURI_HAL_SUBGHZ_TX_GPIO - furi_hal_gpio_init(&FURI_HAL_SUBGHZ_TX_GPIO, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_init( + &FURI_HAL_SUBGHZ_TX_GPIO, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); #endif - // Reset - furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeAnalog, GpioPullNo, GpioSpeedLow); - cc1101_reset(&furi_hal_spi_bus_handle_subghz); - cc1101_write_reg(&furi_hal_spi_bus_handle_subghz, CC1101_IOCFG0, CC1101IocfgHighImpedance); + // Reset + furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + cc1101_reset(&furi_hal_spi_bus_handle_subghz); + cc1101_write_reg(&furi_hal_spi_bus_handle_subghz, CC1101_IOCFG0, CC1101IocfgHighImpedance); - // Prepare GD0 for power on self test - furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow); + // Prepare GD0 for power on self test + furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow); - // GD0 low - cc1101_write_reg(&furi_hal_spi_bus_handle_subghz, CC1101_IOCFG0, CC1101IocfgHW); - while(furi_hal_gpio_read(&gpio_cc1101_g0) != false) - ; + // GD0 low + FuriHalCortexTimer timeout = furi_hal_cortex_timer_get(10000); + cc1101_write_reg(&furi_hal_spi_bus_handle_subghz, CC1101_IOCFG0, CC1101IocfgHW); + while(furi_hal_gpio_read(&gpio_cc1101_g0) != false && + !furi_hal_cortex_timer_is_expired(timeout)) + ; - // GD0 high - cc1101_write_reg( - &furi_hal_spi_bus_handle_subghz, CC1101_IOCFG0, CC1101IocfgHW | CC1101_IOCFG_INV); - while(furi_hal_gpio_read(&gpio_cc1101_g0) != true) - ; + if(furi_hal_gpio_read(&gpio_cc1101_g0) != false) { + break; + } - // Reset GD0 to floating state - cc1101_write_reg(&furi_hal_spi_bus_handle_subghz, CC1101_IOCFG0, CC1101IocfgHighImpedance); - furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + // GD0 high + timeout = furi_hal_cortex_timer_get(10000); + cc1101_write_reg( + &furi_hal_spi_bus_handle_subghz, CC1101_IOCFG0, CC1101IocfgHW | CC1101_IOCFG_INV); + while(furi_hal_gpio_read(&gpio_cc1101_g0) != true && + !furi_hal_cortex_timer_is_expired(timeout)) + ; + + if(furi_hal_gpio_read(&gpio_cc1101_g0) != true) { + break; + } - // RF switches - furi_hal_gpio_init(&gpio_rf_sw_0, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); - cc1101_write_reg(&furi_hal_spi_bus_handle_subghz, CC1101_IOCFG2, CC1101IocfgHW); + // Reset GD0 to floating state + cc1101_write_reg(&furi_hal_spi_bus_handle_subghz, CC1101_IOCFG0, CC1101IocfgHighImpedance); + furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeAnalog, GpioPullNo, GpioSpeedLow); - // Go to sleep - cc1101_shutdown(&furi_hal_spi_bus_handle_subghz); + // RF switches + furi_hal_gpio_init(&gpio_rf_sw_0, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); + cc1101_write_reg(&furi_hal_spi_bus_handle_subghz, CC1101_IOCFG2, CC1101IocfgHW); + + // Go to sleep + cc1101_shutdown(&furi_hal_spi_bus_handle_subghz); + + furi_hal_subghz.state = SubGhzStateIdle; + } while(false); furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); - FURI_LOG_I(TAG, "Init OK"); + + if(furi_hal_subghz.state == SubGhzStateIdle) { + FURI_LOG_I(TAG, "Init OK"); + } else { + FURI_LOG_E(TAG, "Init Fail"); + } } void furi_hal_subghz_sleep() { From 98d5718ec9e39824745a2ca364f4d06a5a05c828 Mon Sep 17 00:00:00 2001 From: hedger Date: Wed, 15 Nov 2023 20:27:35 +0400 Subject: [PATCH 811/824] fbt: improvements (#3217) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fbt: changed cdefines & lib handling for external apps; added extra checks for app manifest fields; moved around AppsC generator * fbt: commandline fixes for spaces in paths * fbt: fixed stringification for FAP_VERSION * fbt: Removed excessive quoting for gdb * docs: update for cdefines; fbt: typo fix * fbt: enforcing at least 2 components in app version= Co-authored-by: あく --- SConstruct | 36 +++-- applications/debug/accessor/application.fam | 1 - .../debug/battery_test_app/application.fam | 1 - applications/debug/blink_test/application.fam | 1 - applications/debug/ccid_test/application.fam | 1 - applications/debug/crash_test/application.fam | 1 - .../debug/display_test/application.fam | 1 - .../debug/file_browser_test/application.fam | 1 - .../debug/keypad_test/application.fam | 1 - .../debug/locale_test/application.fam | 1 - .../debug/text_box_test/application.fam | 1 - applications/debug/uart_echo/application.fam | 1 - applications/debug/usb_mouse/application.fam | 1 - applications/debug/usb_test/application.fam | 1 - applications/debug/vibro_test/application.fam | 1 - documentation/AppManifests.md | 2 +- firmware.scons | 4 +- scripts/fbt/appmanifest.py | 129 ++++-------------- scripts/fbt_tools/fbt_apps.py | 103 +++++++++++++- scripts/fbt_tools/fbt_extapps.py | 21 ++- scripts/fbt_tools/fbt_sdk.py | 15 +- scripts/fbt_tools/fbt_version.py | 20 ++- scripts/fbt_tools/fwbin.py | 19 ++- scripts/fbt_tools/jflash.py | 13 +- scripts/fbt_tools/objdump.py | 3 +- scripts/fbt_tools/openocd.py | 1 + scripts/fbt_tools/pvsstudio.py | 24 +++- scripts/fbt_tools/strip.py | 3 +- scripts/ufbt/SConstruct | 24 +++- scripts/version.py | 2 +- site_scons/environ.scons | 4 +- 31 files changed, 271 insertions(+), 166 deletions(-) diff --git a/SConstruct b/SConstruct index 97d7e5e5e28..a2c5cd9e7af 100644 --- a/SConstruct +++ b/SConstruct @@ -172,17 +172,19 @@ Alias("fap_dist", fap_dist) fap_deploy = distenv.PhonyTarget( "fap_deploy", - [ + Action( [ - "${PYTHON3}", - "${FBT_SCRIPT_DIR}/storage.py", - "-p", - "${FLIP_PORT}", - "send", - "${SOURCE}", - "/ext/apps", + [ + "${PYTHON3}", + "${FBT_SCRIPT_DIR}/storage.py", + "-p", + "${FLIP_PORT}", + "send", + "${SOURCE}", + "/ext/apps", + ] ] - ], + ), source=firmware_env.Dir(("${RESOURCES_ROOT}/apps")), ) Depends(fap_deploy, firmware_env["FW_RESOURCES_MANIFEST"]) @@ -261,7 +263,7 @@ distenv.PhonyTarget( distenv.PhonyTarget( "debug_other_blackmagic", "${GDBPYCOM}", - GDBOPTS="${GDBOPTS_BASE} ${GDBOPTS_BLACKMAGIC}", + GDBOPTS="${GDBOPTS_BASE} ${GDBOPTS_BLACKMAGIC}", GDBREMOTE="${BLACKMAGIC_ADDR}", GDBPYOPTS=debug_other_opts, ) @@ -276,13 +278,13 @@ distenv.PhonyTarget( # Linter distenv.PhonyTarget( "lint", - "${PYTHON3} ${FBT_SCRIPT_DIR}/lint.py check ${LINT_SOURCES}", + [["${PYTHON3}", "${FBT_SCRIPT_DIR}/lint.py", "check", "${LINT_SOURCES}"]], LINT_SOURCES=[n.srcnode() for n in firmware_env["LINT_SOURCES"]], ) distenv.PhonyTarget( "format", - "${PYTHON3} ${FBT_SCRIPT_DIR}/lint.py format ${LINT_SOURCES}", + [["${PYTHON3}", "${FBT_SCRIPT_DIR}/lint.py", "format", "${LINT_SOURCES}"]], LINT_SOURCES=[n.srcnode() for n in firmware_env["LINT_SOURCES"]], ) @@ -323,10 +325,14 @@ distenv.PhonyTarget( ) # Start Flipper CLI via PySerial's miniterm -distenv.PhonyTarget("cli", "${PYTHON3} ${FBT_SCRIPT_DIR}/serial_cli.py -p ${FLIP_PORT}") +distenv.PhonyTarget( + "cli", [["${PYTHON3}", "${FBT_SCRIPT_DIR}/serial_cli.py", "-p", "${FLIP_PORT}"]] +) # Update WiFi devboard firmware -distenv.PhonyTarget("devboard_flash", "${PYTHON3} ${FBT_SCRIPT_DIR}/wifi_board.py") +distenv.PhonyTarget( + "devboard_flash", [["${PYTHON3}", "${FBT_SCRIPT_DIR}/wifi_board.py"]] +) # Find blackmagic probe @@ -361,5 +367,5 @@ distenv.Alias("vscode_dist", vscode_dist) # Configure shell with build tools distenv.PhonyTarget( "env", - "@echo $( ${FBT_SCRIPT_DIR}/toolchain/fbtenv.sh $)", + "@echo $( ${FBT_SCRIPT_DIR.abspath}/toolchain/fbtenv.sh $)", ) diff --git a/applications/debug/accessor/application.fam b/applications/debug/accessor/application.fam index 6b84727112b..65a6c86663a 100644 --- a/applications/debug/accessor/application.fam +++ b/applications/debug/accessor/application.fam @@ -4,7 +4,6 @@ App( apptype=FlipperAppType.DEBUG, targets=["f7"], entry_point="accessor_app", - cdefines=["APP_ACCESSOR"], requires=["gui"], stack_size=4 * 1024, order=40, diff --git a/applications/debug/battery_test_app/application.fam b/applications/debug/battery_test_app/application.fam index f97d1027914..5f4acd83d5f 100644 --- a/applications/debug/battery_test_app/application.fam +++ b/applications/debug/battery_test_app/application.fam @@ -3,7 +3,6 @@ App( name="Battery Test", apptype=FlipperAppType.DEBUG, entry_point="battery_test_app", - cdefines=["APP_BATTERY_TEST"], requires=[ "gui", "power", diff --git a/applications/debug/blink_test/application.fam b/applications/debug/blink_test/application.fam index c6a8a922a41..d7d873fb9a0 100644 --- a/applications/debug/blink_test/application.fam +++ b/applications/debug/blink_test/application.fam @@ -3,7 +3,6 @@ App( name="Blink Test", apptype=FlipperAppType.DEBUG, entry_point="blink_test_app", - cdefines=["APP_BLINK"], requires=["gui"], stack_size=1 * 1024, order=10, diff --git a/applications/debug/ccid_test/application.fam b/applications/debug/ccid_test/application.fam index e0cbc8d85e4..ad907677085 100644 --- a/applications/debug/ccid_test/application.fam +++ b/applications/debug/ccid_test/application.fam @@ -3,7 +3,6 @@ App( name="CCID Debug", apptype=FlipperAppType.DEBUG, entry_point="ccid_test_app", - cdefines=["CCID_TEST"], requires=[ "gui", ], diff --git a/applications/debug/crash_test/application.fam b/applications/debug/crash_test/application.fam index 55f62f86d89..357efe667ac 100644 --- a/applications/debug/crash_test/application.fam +++ b/applications/debug/crash_test/application.fam @@ -3,7 +3,6 @@ App( name="Crash Test", apptype=FlipperAppType.DEBUG, entry_point="crash_test_app", - cdefines=["APP_CRASH_TEST"], requires=["gui"], stack_size=1 * 1024, fap_category="Debug", diff --git a/applications/debug/display_test/application.fam b/applications/debug/display_test/application.fam index e8a00d2ae11..6a2d9c20c1a 100644 --- a/applications/debug/display_test/application.fam +++ b/applications/debug/display_test/application.fam @@ -3,7 +3,6 @@ App( name="Display Test", apptype=FlipperAppType.DEBUG, entry_point="display_test_app", - cdefines=["APP_DISPLAY_TEST"], requires=["gui"], fap_libs=["misc"], stack_size=1 * 1024, diff --git a/applications/debug/file_browser_test/application.fam b/applications/debug/file_browser_test/application.fam index 4a401a649d1..bb08ad2c55d 100644 --- a/applications/debug/file_browser_test/application.fam +++ b/applications/debug/file_browser_test/application.fam @@ -3,7 +3,6 @@ App( name="File Browser Test", apptype=FlipperAppType.DEBUG, entry_point="file_browser_app", - cdefines=["APP_FILE_BROWSER_TEST"], requires=["gui"], stack_size=2 * 1024, order=150, diff --git a/applications/debug/keypad_test/application.fam b/applications/debug/keypad_test/application.fam index 6859af26f69..90851950b22 100644 --- a/applications/debug/keypad_test/application.fam +++ b/applications/debug/keypad_test/application.fam @@ -3,7 +3,6 @@ App( name="Keypad Test", apptype=FlipperAppType.DEBUG, entry_point="keypad_test_app", - cdefines=["APP_KEYPAD_TEST"], requires=["gui"], stack_size=1 * 1024, order=30, diff --git a/applications/debug/locale_test/application.fam b/applications/debug/locale_test/application.fam index e46eeff51c7..d341122f99d 100644 --- a/applications/debug/locale_test/application.fam +++ b/applications/debug/locale_test/application.fam @@ -3,7 +3,6 @@ App( name="Locale Test", apptype=FlipperAppType.DEBUG, entry_point="locale_test_app", - cdefines=["APP_LOCALE"], requires=["gui", "locale"], stack_size=2 * 1024, order=70, diff --git a/applications/debug/text_box_test/application.fam b/applications/debug/text_box_test/application.fam index 3e54df9cc57..823c21d0688 100644 --- a/applications/debug/text_box_test/application.fam +++ b/applications/debug/text_box_test/application.fam @@ -3,7 +3,6 @@ App( name="Text Box Test", apptype=FlipperAppType.DEBUG, entry_point="text_box_test_app", - cdefines=["APP_TEXT_BOX_TEST"], requires=["gui"], stack_size=1 * 1024, order=140, diff --git a/applications/debug/uart_echo/application.fam b/applications/debug/uart_echo/application.fam index 8863a1a9424..7b030bcfa68 100644 --- a/applications/debug/uart_echo/application.fam +++ b/applications/debug/uart_echo/application.fam @@ -3,7 +3,6 @@ App( name="UART Echo", apptype=FlipperAppType.DEBUG, entry_point="uart_echo_app", - cdefines=["APP_UART_ECHO"], requires=["gui"], stack_size=2 * 1024, order=70, diff --git a/applications/debug/usb_mouse/application.fam b/applications/debug/usb_mouse/application.fam index 5c434004510..7747613d58a 100644 --- a/applications/debug/usb_mouse/application.fam +++ b/applications/debug/usb_mouse/application.fam @@ -3,7 +3,6 @@ App( name="USB Mouse Demo", apptype=FlipperAppType.DEBUG, entry_point="usb_mouse_app", - cdefines=["APP_USB_MOUSE"], requires=["gui"], stack_size=1 * 1024, order=60, diff --git a/applications/debug/usb_test/application.fam b/applications/debug/usb_test/application.fam index 27395c34d40..463bb4a26ef 100644 --- a/applications/debug/usb_test/application.fam +++ b/applications/debug/usb_test/application.fam @@ -3,7 +3,6 @@ App( name="USB Test", apptype=FlipperAppType.DEBUG, entry_point="usb_test_app", - cdefines=["APP_USB_TEST"], requires=["gui"], stack_size=1 * 1024, order=50, diff --git a/applications/debug/vibro_test/application.fam b/applications/debug/vibro_test/application.fam index f7115cc9621..c35a7223f8a 100644 --- a/applications/debug/vibro_test/application.fam +++ b/applications/debug/vibro_test/application.fam @@ -3,7 +3,6 @@ App( name="Vibro Test", apptype=FlipperAppType.DEBUG, entry_point="vibro_test_app", - cdefines=["APP_VIBRO_TEST"], requires=["gui"], stack_size=1 * 1024, order=20, diff --git a/documentation/AppManifests.md b/documentation/AppManifests.md index d190a798ba8..9afdccb0e41 100644 --- a/documentation/AppManifests.md +++ b/documentation/AppManifests.md @@ -32,7 +32,7 @@ Only two parameters are mandatory: **_appid_** and **_apptype_**. Others are opt - **name**: name displayed in menus. - **entry_point**: C function to be used as the application's entry point. Note that C++ function names are mangled, so you need to wrap them in `extern "C"` to use them as entry points. - **flags**: internal flags for system apps. Do not use. -- **cdefines**: C preprocessor definitions to declare globally for other apps when the current application is included in the active build configuration. +- **cdefines**: C preprocessor definitions to declare globally for other apps when the current application is included in the active build configuration. **For external applications**: specified definitions are used when building the application itself. - **requires**: list of application IDs to include in the build configuration when the current application is referenced in the list of applications to build. - **conflicts**: list of application IDs with which the current application conflicts. If any of them is found in the constructed application list, **`fbt`** will abort the firmware build process. - **provides**: functionally identical to **_requires_** field. diff --git a/firmware.scons b/firmware.scons index eca6afc4c70..004def9a999 100644 --- a/firmware.scons +++ b/firmware.scons @@ -219,7 +219,7 @@ AddPostAction(fwelf, fwenv["APPBUILD_DUMP"]) AddPostAction( fwelf, Action( - '${PYTHON3} "${BIN_SIZE_SCRIPT}" elf ${TARGET}', + [["${PYTHON3}", "${BIN_SIZE_SCRIPT}", "elf", "${TARGET}"]], "Firmware size", ), ) @@ -229,7 +229,7 @@ fwhex = fwenv["FW_HEX"] = fwenv.HEXBuilder("${FIRMWARE_BUILD_CFG}") fwbin = fwenv["FW_BIN"] = fwenv.BINBuilder("${FIRMWARE_BUILD_CFG}") AddPostAction( fwbin, - Action('@${PYTHON3} "${BIN_SIZE_SCRIPT}" bin ${TARGET}'), + Action([["@${PYTHON3}", "${BIN_SIZE_SCRIPT}", "bin", "${TARGET}"]]), ) fwdfu = fwenv["FW_DFU"] = fwenv.DFUBuilder("${FIRMWARE_BUILD_CFG}") diff --git a/scripts/fbt/appmanifest.py b/scripts/fbt/appmanifest.py index bef4eb02b1f..d32869b106f 100644 --- a/scripts/fbt/appmanifest.py +++ b/scripts/fbt/appmanifest.py @@ -33,6 +33,8 @@ class FlipperAppType(Enum): @dataclass class FlipperApplication: APP_ID_REGEX: ClassVar[re.Pattern] = re.compile(r"^[a-z0-9_]+$") + PRIVATE_FIELD_PREFIX: ClassVar[str] = "_" + APP_MANIFEST_DEFAULT_NAME: ClassVar[str] = "application.fam" @dataclass class ExternallyBuiltFile: @@ -48,8 +50,6 @@ class Library: cdefines: List[str] = field(default_factory=list) cincludes: List[str] = field(default_factory=list) - PRIVATE_FIELD_PREFIX = "_" - appid: str apptype: FlipperAppType name: Optional[str] = "" @@ -117,8 +117,10 @@ def __post_init__(self): self.fap_version = tuple(int(v) for v in self.fap_version.split(".")) except ValueError: raise FlipperManifestException( - f"Invalid version string '{self.fap_version}'. Must be in the form 'major.minor'" + f"Invalid version '{self.fap_version}'. Must be in the form 'major.minor'" ) + if len(self.fap_version) < 2: + raise ValueError("Not enough version components") class AppManager: @@ -155,11 +157,20 @@ def _validate_app_params(self, *args, **kw): raise FlipperManifestException( f"App {kw.get('appid')} cannot have fal_embedded set" ) - # Harmless - cdefines for external apps are meaningless - # if apptype == FlipperAppType.EXTERNAL and kw.get("cdefines"): - # raise FlipperManifestException( - # f"External app {kw.get('appid')} must not have 'cdefines' in manifest" - # ) + + if apptype in AppBuildset.dist_app_types: + # For distributing .fap's resources, there's "fap_file_assets" + for app_property in ("resources",): + if kw.get(app_property): + raise FlipperManifestException( + f"App {kw.get('appid')} of type {apptype} cannot have '{app_property}' in manifest" + ) + else: + for app_property in ("fap_extbuild", "fap_private_libs", "fap_icon_assets"): + if kw.get(app_property): + raise FlipperManifestException( + f"App {kw.get('appid')} of type {apptype} must not have '{app_property}' in manifest" + ) def load_manifest(self, app_manifest_path: str, app_dir_node: object): if not os.path.exists(app_manifest_path): @@ -241,12 +252,21 @@ class AppBuildset: FlipperAppType.STARTUP, ) EXTERNAL_APP_TYPES_MAP = { + # AppType -> bool: true if always deploy, false if obey app set FlipperAppType.EXTERNAL: True, FlipperAppType.PLUGIN: True, FlipperAppType.DEBUG: True, FlipperAppType.MENUEXTERNAL: False, } + @classmethod + @property + def dist_app_types(cls): + """Applications that are installed on SD card""" + return list( + entry[0] for entry in cls.EXTERNAL_APP_TYPES_MAP.items() if entry[1] + ) + @staticmethod def print_writer(message): print(message) @@ -432,96 +452,3 @@ def get_builtin_app_folders(self): for source_type in app.sources ) ) - - -class ApplicationsCGenerator: - APP_TYPE_MAP = { - FlipperAppType.SERVICE: ("FlipperInternalApplication", "FLIPPER_SERVICES"), - FlipperAppType.SYSTEM: ("FlipperInternalApplication", "FLIPPER_SYSTEM_APPS"), - FlipperAppType.APP: ("FlipperInternalApplication", "FLIPPER_APPS"), - FlipperAppType.DEBUG: ("FlipperInternalApplication", "FLIPPER_DEBUG_APPS"), - FlipperAppType.SETTINGS: ( - "FlipperInternalApplication", - "FLIPPER_SETTINGS_APPS", - ), - FlipperAppType.STARTUP: ( - "FlipperInternalOnStartHook", - "FLIPPER_ON_SYSTEM_START", - ), - } - - APP_EXTERNAL_TYPE = ( - "FlipperExternalApplication", - "FLIPPER_EXTERNAL_APPS", - ) - - def __init__(self, buildset: AppBuildset, autorun_app: str = ""): - self.buildset = buildset - self.autorun = autorun_app - - def get_app_ep_forward(self, app: FlipperApplication): - if app.apptype == FlipperAppType.STARTUP: - return f"extern void {app.entry_point}();" - return f"extern int32_t {app.entry_point}(void* p);" - - def get_app_descr(self, app: FlipperApplication): - if app.apptype == FlipperAppType.STARTUP: - return app.entry_point - return f""" - {{.app = {app.entry_point}, - .name = "{app.name}", - .appid = "{app.appid}", - .stack_size = {app.stack_size}, - .icon = {f"&{app.icon}" if app.icon else "NULL"}, - .flags = {'|'.join(f"FlipperInternalApplicationFlag{flag}" for flag in app.flags)} }}""" - - def get_external_app_descr(self, app: FlipperApplication): - app_path = "/ext/apps" - if app.fap_category: - app_path += f"/{app.fap_category}" - app_path += f"/{app.appid}.fap" - return f""" - {{ - .name = "{app.name}", - .icon = {f"&{app.icon}" if app.icon else "NULL"}, - .path = "{app_path}" }}""" - - def generate(self): - contents = [ - '#include "applications.h"', - "#include ", - f'const char* FLIPPER_AUTORUN_APP_NAME = "{self.autorun}";', - ] - for apptype in self.APP_TYPE_MAP: - contents.extend( - map(self.get_app_ep_forward, self.buildset.get_apps_of_type(apptype)) - ) - entry_type, entry_block = self.APP_TYPE_MAP[apptype] - contents.append(f"const {entry_type} {entry_block}[] = {{") - contents.append( - ",\n".join( - map(self.get_app_descr, self.buildset.get_apps_of_type(apptype)) - ) - ) - contents.append("};") - contents.append( - f"const size_t {entry_block}_COUNT = COUNT_OF({entry_block});" - ) - - archive_app = self.buildset.get_apps_of_type(FlipperAppType.ARCHIVE) - if archive_app: - contents.extend( - [ - self.get_app_ep_forward(archive_app[0]), - f"const FlipperInternalApplication FLIPPER_ARCHIVE = {self.get_app_descr(archive_app[0])};", - ] - ) - - entry_type, entry_block = self.APP_EXTERNAL_TYPE - external_apps = self.buildset.get_apps_of_type(FlipperAppType.MENUEXTERNAL) - contents.append(f"const {entry_type} {entry_block}[] = {{") - contents.append(",\n".join(map(self.get_external_app_descr, external_apps))) - contents.append("};") - contents.append(f"const size_t {entry_block}_COUNT = COUNT_OF({entry_block});") - - return "\n".join(contents) diff --git a/scripts/fbt_tools/fbt_apps.py b/scripts/fbt_tools/fbt_apps.py index edce194f07d..dadf6dc0c8c 100644 --- a/scripts/fbt_tools/fbt_apps.py +++ b/scripts/fbt_tools/fbt_apps.py @@ -1,25 +1,118 @@ from ansi.color import fg from fbt.appmanifest import ( - ApplicationsCGenerator, AppManager, + AppBuildset, + FlipperApplication, FlipperAppType, FlipperManifestException, ) from SCons.Action import Action from SCons.Builder import Builder from SCons.Errors import StopError -from SCons.Warnings import WarningOnByDefault, warn from SCons.Script import GetOption +from SCons.Warnings import WarningOnByDefault, warn # Adding objects for application management to env # AppManager env["APPMGR"] - loads all manifests; manages list of known apps # AppBuildset env["APPBUILD"] - contains subset of apps, filtered for current config +class ApplicationsCGenerator: + APP_TYPE_MAP = { + FlipperAppType.SERVICE: ("FlipperInternalApplication", "FLIPPER_SERVICES"), + FlipperAppType.SYSTEM: ("FlipperInternalApplication", "FLIPPER_SYSTEM_APPS"), + FlipperAppType.APP: ("FlipperInternalApplication", "FLIPPER_APPS"), + FlipperAppType.DEBUG: ("FlipperInternalApplication", "FLIPPER_DEBUG_APPS"), + FlipperAppType.SETTINGS: ( + "FlipperInternalApplication", + "FLIPPER_SETTINGS_APPS", + ), + FlipperAppType.STARTUP: ( + "FlipperInternalOnStartHook", + "FLIPPER_ON_SYSTEM_START", + ), + } + + APP_EXTERNAL_TYPE = ( + "FlipperExternalApplication", + "FLIPPER_EXTERNAL_APPS", + ) + + def __init__(self, buildset: AppBuildset, autorun_app: str = ""): + self.buildset = buildset + self.autorun = autorun_app + + def get_app_ep_forward(self, app: FlipperApplication): + if app.apptype == FlipperAppType.STARTUP: + return f"extern void {app.entry_point}();" + return f"extern int32_t {app.entry_point}(void* p);" + + def get_app_descr(self, app: FlipperApplication): + if app.apptype == FlipperAppType.STARTUP: + return app.entry_point + return f""" + {{.app = {app.entry_point}, + .name = "{app.name}", + .appid = "{app.appid}", + .stack_size = {app.stack_size}, + .icon = {f"&{app.icon}" if app.icon else "NULL"}, + .flags = {'|'.join(f"FlipperInternalApplicationFlag{flag}" for flag in app.flags)} }}""" + + def get_external_app_descr(self, app: FlipperApplication): + app_path = "/ext/apps" + if app.fap_category: + app_path += f"/{app.fap_category}" + app_path += f"/{app.appid}.fap" + return f""" + {{ + .name = "{app.name}", + .icon = {f"&{app.icon}" if app.icon else "NULL"}, + .path = "{app_path}" }}""" + + def generate(self): + contents = [ + '#include "applications.h"', + "#include ", + f'const char* FLIPPER_AUTORUN_APP_NAME = "{self.autorun}";', + ] + for apptype in self.APP_TYPE_MAP: + contents.extend( + map(self.get_app_ep_forward, self.buildset.get_apps_of_type(apptype)) + ) + entry_type, entry_block = self.APP_TYPE_MAP[apptype] + contents.append(f"const {entry_type} {entry_block}[] = {{") + contents.append( + ",\n".join( + map(self.get_app_descr, self.buildset.get_apps_of_type(apptype)) + ) + ) + contents.append("};") + contents.append( + f"const size_t {entry_block}_COUNT = COUNT_OF({entry_block});" + ) + + archive_app = self.buildset.get_apps_of_type(FlipperAppType.ARCHIVE) + if archive_app: + contents.extend( + [ + self.get_app_ep_forward(archive_app[0]), + f"const FlipperInternalApplication FLIPPER_ARCHIVE = {self.get_app_descr(archive_app[0])};", + ] + ) + + entry_type, entry_block = self.APP_EXTERNAL_TYPE + external_apps = self.buildset.get_apps_of_type(FlipperAppType.MENUEXTERNAL) + contents.append(f"const {entry_type} {entry_block}[] = {{") + contents.append(",\n".join(map(self.get_external_app_descr, external_apps))) + contents.append("};") + contents.append(f"const size_t {entry_block}_COUNT = COUNT_OF({entry_block});") + + return "\n".join(contents) + + def LoadAppManifest(env, entry): try: - APP_MANIFEST_NAME = "application.fam" - manifest_glob = entry.glob(APP_MANIFEST_NAME) + manifest_glob = entry.glob(FlipperApplication.APP_MANIFEST_DEFAULT_NAME) if len(manifest_glob) == 0: try: disk_node = next(filter(lambda d: d.exists(), entry.get_all_rdirs())) @@ -27,7 +120,7 @@ def LoadAppManifest(env, entry): disk_node = entry raise FlipperManifestException( - f"App folder '{disk_node.abspath}': missing manifest ({APP_MANIFEST_NAME})" + f"App folder '{disk_node.abspath}': missing manifest ({FlipperApplication.APP_MANIFEST_DEFAULT_NAME})" ) app_manifest_file_path = manifest_glob[0].rfile().abspath diff --git a/scripts/fbt_tools/fbt_extapps.py b/scripts/fbt_tools/fbt_extapps.py index b88fa792911..a7914c4f877 100644 --- a/scripts/fbt_tools/fbt_extapps.py +++ b/scripts/fbt_tools/fbt_extapps.py @@ -58,7 +58,8 @@ def _setup_app_env(self): ) self.app_env.Append( CPPDEFINES=[ - ("FAP_VERSION", f'"{".".join(map(str, self.app.fap_version))}"') + ("FAP_VERSION", f'\\"{".".join(map(str, self.app.fap_version))}\\"'), + *self.app.cdefines, ], ) self.app_env.VariantDir(self.app_work_dir, self.app._appdir, duplicate=False) @@ -143,8 +144,8 @@ def _build_app(self): self.app._assets_dirs = [self.app._appdir.Dir(self.app.fap_file_assets)] self.app_env.Append( - LIBS=[*self.app.fap_libs, *self.private_libs], - CPPPATH=[self.app_work_dir, self.app._appdir], + LIBS=[*self.app.fap_libs, *self.private_libs, *self.app.fap_libs], + CPPPATH=[self.app_env.Dir(self.app_work_dir), self.app._appdir], ) app_sources = self.app_env.GatherSources( @@ -472,7 +473,19 @@ def AddAppLaunchTarget(env, appname, launch_target_name): components = _gather_app_components(env, appname) target = env.PhonyTarget( launch_target_name, - '${PYTHON3} "${APP_RUN_SCRIPT}" -p ${FLIP_PORT} ${EXTRA_ARGS} -s ${SOURCES} -t ${FLIPPER_FILE_TARGETS}', + [ + [ + "${PYTHON3}", + "${APP_RUN_SCRIPT}", + "-p", + "${FLIP_PORT}", + "${EXTRA_ARGS}", + "-s", + "${SOURCES}", + "-t", + "${FLIPPER_FILE_TARGETS}", + ] + ], source=components.deploy_sources.values(), FLIPPER_FILE_TARGETS=components.deploy_sources.keys(), EXTRA_ARGS=components.extra_launch_args, diff --git a/scripts/fbt_tools/fbt_sdk.py b/scripts/fbt_tools/fbt_sdk.py index 6350f14b8b1..17acc8cf1ae 100644 --- a/scripts/fbt_tools/fbt_sdk.py +++ b/scripts/fbt_tools/fbt_sdk.py @@ -285,7 +285,20 @@ def generate(env, **kw): "$SDK_AMALGAMATE_HEADER_COMSTR", ), Action( - "$CC -o $TARGET -E -P $CCFLAGS $_CCCOMCOM $SDK_PP_FLAGS -MMD ${TARGET}.c", + [ + [ + "$CC", + "-o", + "$TARGET", + "-E", + "-P", + "$CCFLAGS", + "$_CCCOMCOM", + "$SDK_PP_FLAGS", + "-MMD", + "${TARGET}.c", + ] + ], "$SDK_AMALGAMATE_PP_COMSTR", ), ], diff --git a/scripts/fbt_tools/fbt_version.py b/scripts/fbt_tools/fbt_version.py index f1a782523ff..e64167b3dc5 100644 --- a/scripts/fbt_tools/fbt_version.py +++ b/scripts/fbt_tools/fbt_version.py @@ -19,10 +19,22 @@ def generate(env): BUILDERS={ "VersionBuilder": Builder( action=Action( - '${PYTHON3} "${VERSION_SCRIPT}" generate ' - "-t ${TARGET_HW} -fw-origin ${FIRMWARE_ORIGIN} " - '-o ${TARGET.dir.posix} --dir "${ROOT_DIR}"', - "${VERSIONCOMSTR}", + [ + [ + "${PYTHON3}", + "${VERSION_SCRIPT}", + "generate", + "-t", + "${TARGET_HW}", + "--fw-origin", + "${FIRMWARE_ORIGIN}", + "-o", + "${TARGET.dir.posix}", + "--dir", + "${ROOT_DIR}", + "${VERSIONCOMSTR}", + ] + ] ), emitter=_version_emitter, ), diff --git a/scripts/fbt_tools/fwbin.py b/scripts/fbt_tools/fwbin.py index 06a435b6db1..860f83b1b98 100644 --- a/scripts/fbt_tools/fwbin.py +++ b/scripts/fbt_tools/fwbin.py @@ -25,7 +25,7 @@ def generate(env): BUILDERS={ "HEXBuilder": Builder( action=Action( - '${OBJCOPY} -O ihex "${SOURCE}" "${TARGET}"', + [["${OBJCOPY}", "-O", "ihex", "${SOURCE}", "${TARGET}"]], "${HEXCOMSTR}", ), suffix=".hex", @@ -33,7 +33,7 @@ def generate(env): ), "BINBuilder": Builder( action=Action( - '${OBJCOPY} -O binary -S "${SOURCE}" "${TARGET}"', + [["${OBJCOPY}", "-O", "binary", "-S", "${SOURCE}", "${TARGET}"]], "${BINCOMSTR}", ), suffix=".bin", @@ -41,7 +41,20 @@ def generate(env): ), "DFUBuilder": Builder( action=Action( - '${PYTHON3} "${BIN2DFU}" -i "${SOURCE}" -o "${TARGET}" -a ${IMAGE_BASE_ADDRESS} -l "Flipper Zero F${TARGET_HW}"', + [ + [ + "${PYTHON3}", + "${BIN2DFU}", + "-i", + "${SOURCE}", + "-o", + "${TARGET}", + "-a", + "${IMAGE_BASE_ADDRESS}", + "-l", + "Flipper Zero F${TARGET_HW}", + ] + ], "${DFUCOMSTR}", ), suffix=".dfu", diff --git a/scripts/fbt_tools/jflash.py b/scripts/fbt_tools/jflash.py index aea7279b636..5eb9f2c1932 100644 --- a/scripts/fbt_tools/jflash.py +++ b/scripts/fbt_tools/jflash.py @@ -1,5 +1,6 @@ from SCons.Builder import Builder from SCons.Defaults import Touch +from SCons.Action import Action def generate(env): @@ -9,13 +10,21 @@ def generate(env): "-auto", "-exit", ], - JFLASHCOM="${JFLASH} -openprj${JFLASHPROJECT} -open${SOURCE},${JFLASHADDR} ${JFLASHFLAGS}", ) env.Append( BUILDERS={ "JFlash": Builder( action=[ - "${JFLASHCOM}", + Action( + [ + [ + "${JFLASH}", + "-openprj${JFLASHPROJECT}", + "-open${SOURCE},${JFLASHADDR}", + "${JFLASHFLAGS}", + ] + ] + ), Touch("${TARGET}"), ], ), diff --git a/scripts/fbt_tools/objdump.py b/scripts/fbt_tools/objdump.py index 31f8176484b..e3dbc6d8759 100644 --- a/scripts/fbt_tools/objdump.py +++ b/scripts/fbt_tools/objdump.py @@ -6,13 +6,12 @@ def generate(env): env.SetDefault( OBJDUMP="objdump", OBJDUMPFLAGS=[], - OBJDUMPCOM="$OBJDUMP $OBJDUMPFLAGS -S $SOURCES > $TARGET", ) env.Append( BUILDERS={ "ObjDump": Builder( action=Action( - "${OBJDUMPCOM}", + [["$OBJDUMP", "$OBJDUMPFLAGS", "-S", "$SOURCES", ">", "$TARGET"]], "${OBJDUMPCOMSTR}", ), suffix=".lst", diff --git a/scripts/fbt_tools/openocd.py b/scripts/fbt_tools/openocd.py index 157d798f40d..596f5f8a644 100644 --- a/scripts/fbt_tools/openocd.py +++ b/scripts/fbt_tools/openocd.py @@ -5,6 +5,7 @@ __OPENOCD_BIN = "openocd" +# TODO: FL-3663: rework argument passing to lists _oocd_action = Action( "${OPENOCD} ${OPENOCD_OPTS} ${OPENOCD_COMMAND}", "${OPENOCDCOMSTR}", diff --git a/scripts/fbt_tools/pvsstudio.py b/scripts/fbt_tools/pvsstudio.py index f43db126e31..ecf9d4b0681 100644 --- a/scripts/fbt_tools/pvsstudio.py +++ b/scripts/fbt_tools/pvsstudio.py @@ -79,7 +79,17 @@ def generate(env): BUILDERS={ "PVSCheck": Builder( action=Action( - '${PVSCHECKBIN} analyze ${PVSOPTIONS} -f "${SOURCE}" -o "${TARGET}"', + [ + [ + "${PVSCHECKBIN}", + "analyze", + "${PVSOPTIONS}", + "-f", + "${SOURCE}", + "-o", + "${TARGET}", + ] + ], "${PVSCHECKCOMSTR}", ), suffix=".log", @@ -92,7 +102,17 @@ def generate(env): # PlogConverter.exe and plog-converter have different behavior Mkdir("${TARGET.dir}") if env["PLATFORM"] == "win32" else None, Action(_set_browser_action, None), - '${PVSCONVBIN} ${PVSCONVOPTIONS} "${SOURCE}" -o "${REPORT_DIR}"', + Action( + [ + [ + "${PVSCONVBIN}", + "${PVSCONVOPTIONS}", + "${SOURCE}", + "-o", + "${REPORT_DIR}", + ] + ] + ), ], "${PVSCONVCOMSTR}", ), diff --git a/scripts/fbt_tools/strip.py b/scripts/fbt_tools/strip.py index ee14fc185a3..39f3a620cf1 100644 --- a/scripts/fbt_tools/strip.py +++ b/scripts/fbt_tools/strip.py @@ -6,13 +6,12 @@ def generate(env): env.SetDefault( STRIP="strip", STRIPFLAGS=[], - STRIPCOM="$STRIP $STRIPFLAGS $SOURCES -o $TARGET", ) env.Append( BUILDERS={ "ELFStripper": Builder( action=Action( - "${STRIPCOM}", + [["$STRIP", "$STRIPFLAGS", "$SOURCES", "-o", "$TARGET"]], "${STRIPCOMSTR}", ), suffix=".elf", diff --git a/scripts/ufbt/SConstruct b/scripts/ufbt/SConstruct index 98e6b638f36..46d6635788e 100644 --- a/scripts/ufbt/SConstruct +++ b/scripts/ufbt/SConstruct @@ -325,24 +325,26 @@ else: appenv.PhonyTarget( "cli", - '${PYTHON3} "${FBT_SCRIPT_DIR}/serial_cli.py" -p ${FLIP_PORT}', + [["${PYTHON3}", "${FBT_SCRIPT_DIR}/serial_cli.py", "-p", "${FLIP_PORT}"]], ) # Update WiFi devboard firmware -dist_env.PhonyTarget("devboard_flash", "${PYTHON3} ${FBT_SCRIPT_DIR}/wifi_board.py") +dist_env.PhonyTarget( + "devboard_flash", [["${PYTHON3}", "${FBT_SCRIPT_DIR}/wifi_board.py"]] +) # Linter dist_env.PhonyTarget( "lint", - "${PYTHON3} ${FBT_SCRIPT_DIR}/lint.py check ${LINT_SOURCES}", + [["${PYTHON3}", "${FBT_SCRIPT_DIR}/lint.py", "check", "${LINT_SOURCES}"]], source=original_app_dir.File(".clang-format"), LINT_SOURCES=[original_app_dir], ) dist_env.PhonyTarget( "format", - "${PYTHON3} ${FBT_SCRIPT_DIR}/lint.py format ${LINT_SOURCES}", + [["${PYTHON3}", "${FBT_SCRIPT_DIR}/lint.py", "format", "${LINT_SOURCES}"]], source=original_app_dir.File(".clang-format"), LINT_SOURCES=[original_app_dir], ) @@ -455,7 +457,17 @@ if dolphin_src_dir.exists(): ) dist_env.PhonyTarget( "dolphin_ext", - '${PYTHON3} ${FBT_SCRIPT_DIR}/storage.py -p ${FLIP_PORT} send "${SOURCE}" /ext/dolphin', + [ + [ + "${PYTHON3}", + "${FBT_SCRIPT_DIR}/storage.py", + "-p", + "${FLIP_PORT}", + "send", + "${SOURCE}", + "/ext/dolphin", + ] + ], source=ufbt_build_dir.Dir("dolphin"), ) else: @@ -467,7 +479,7 @@ else: dist_env.PhonyTarget( "env", - "@echo $( ${FBT_SCRIPT_DIR}/toolchain/fbtenv.sh $)", + "@echo $( ${FBT_SCRIPT_DIR.abspath}/toolchain/fbtenv.sh $)", ) dist_env.PostConfigureUfbtEnvionment() diff --git a/scripts/version.py b/scripts/version.py index 4b1c739bc5b..98b1b7e8555 100755 --- a/scripts/version.py +++ b/scripts/version.py @@ -101,7 +101,7 @@ def init(self): required=True, ) self.parser_generate.add_argument( - "-fw-origin", + "--fw-origin", dest="firmware_origin", type=str, help="firmware origin", diff --git a/site_scons/environ.scons b/site_scons/environ.scons index b638b101859..74762cb15bb 100644 --- a/site_scons/environ.scons +++ b/site_scons/environ.scons @@ -27,6 +27,8 @@ variables_to_forward = [ "PYTHONNOUSERSITE", "TMP", "TEMP", + # ccache + "CCACHE_DISABLE", # Colors for tools "TERM", ] @@ -62,7 +64,7 @@ coreenv = VAR_ENV.Clone( # Setting up temp file parameters - to overcome command line length limits TEMPFILEARGESCFUNC=tempfile_arg_esc_func, ROOT_DIR=Dir("#"), - FBT_SCRIPT_DIR="${ROOT_DIR}/scripts", + FBT_SCRIPT_DIR=Dir("#/scripts"), ) # If DIST_SUFFIX is set in environment, is has precedence (set by CI) From 4b3e8aba294bf94be4ac7a73873202e3078afb2c Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Wed, 15 Nov 2023 19:39:27 +0300 Subject: [PATCH 812/824] [FL-3664] 64k does not enough (#3216) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Unit tests: add "exists" to furi_record tests * Unit tests: mu_warn, storage 64k test * Storage: read/write over 64k * Unit tests: moar tests for storage r/w for >64k cases * Apps, libs: replace uint16_t with size_t on storage r/w operations * Unit tests: better data pattern, subghz: warning if transmission is prohibited Co-authored-by: あく --- .../debug/unit_tests/furi/furi_record_test.c | 27 +++++--- applications/debug/unit_tests/minunit.h | 5 ++ .../debug/unit_tests/storage/dirwalk_test.c | 2 +- .../debug/unit_tests/storage/storage_test.c | 65 +++++++++++++++++++ .../debug/unit_tests/subghz/subghz_test.c | 1 + applications/debug/unit_tests/test_index.c | 10 +++ .../main/archive/helpers/archive_favorites.c | 4 +- applications/main/subghz/subghz_cli.c | 2 +- .../services/notification/notification_app.c | 4 +- applications/services/rpc/rpc_storage.c | 2 +- applications/services/storage/storage.h | 6 +- applications/services/storage/storage_cli.c | 22 +++---- .../services/storage/storage_external_api.c | 41 +++++++++++- .../scenes/storage_settings_scene_benchmark.c | 4 +- .../updater/util/update_task_worker_flasher.c | 2 +- lib/flipper_application/elf/elf_file.c | 2 +- lib/music_worker/music_worker.c | 2 +- lib/toolbox/crc32_calc.c | 2 +- lib/toolbox/md5_calc.c | 4 +- lib/toolbox/saved_struct.c | 6 +- lib/toolbox/stream/file_stream.c | 24 +------ lib/update_util/dfu_file.c | 6 +- lib/update_util/update_operation.c | 2 +- targets/f18/api_symbols.csv | 10 +-- targets/f7/api_symbols.csv | 10 +-- 25 files changed, 186 insertions(+), 79 deletions(-) diff --git a/applications/debug/unit_tests/furi/furi_record_test.c b/applications/debug/unit_tests/furi/furi_record_test.c index 512ddfdc4a2..236e1efc56b 100644 --- a/applications/debug/unit_tests/furi/furi_record_test.c +++ b/applications/debug/unit_tests/furi/furi_record_test.c @@ -3,18 +3,29 @@ #include #include "../minunit.h" +#define TEST_RECORD_NAME "test/holding" + void test_furi_create_open() { - // 1. Create record + // Test that record does not exist + mu_check(furi_record_exists(TEST_RECORD_NAME) == false); + + // Create record uint8_t test_data = 0; - furi_record_create("test/holding", (void*)&test_data); + furi_record_create(TEST_RECORD_NAME, (void*)&test_data); - // 2. Open it - void* record = furi_record_open("test/holding"); + // Test that record exists + mu_check(furi_record_exists(TEST_RECORD_NAME) == true); + + // Open it + void* record = furi_record_open(TEST_RECORD_NAME); mu_assert_pointers_eq(record, &test_data); - // 3. Close it - furi_record_close("test/holding"); + // Close it + furi_record_close(TEST_RECORD_NAME); + + // Clean up + furi_record_destroy(TEST_RECORD_NAME); - // 4. Clean up - furi_record_destroy("test/holding"); + // Test that record does not exist + mu_check(furi_record_exists(TEST_RECORD_NAME) == false); } diff --git a/applications/debug/unit_tests/minunit.h b/applications/debug/unit_tests/minunit.h index 69bfba6d92e..083db5a9a9a 100644 --- a/applications/debug/unit_tests/minunit.h +++ b/applications/debug/unit_tests/minunit.h @@ -81,6 +81,7 @@ __attribute__((unused)) static void (*minunit_teardown)(void) = NULL; void minunit_print_progress(void); void minunit_print_fail(const char* error); +void minunit_printf_warning(const char* format, ...); /* Definitions */ #define MU_TEST(method_name) static void method_name(void) @@ -150,6 +151,10 @@ void minunit_print_fail(const char* error); minunit_end_proc_timer - minunit_proc_timer);) #define MU_EXIT_CODE minunit_fail +/* Warnings */ +#define mu_warn(message) \ + MU__SAFE_BLOCK(minunit_printf_warning("%s:%d: %s", __FILE__, __LINE__, message);) + /* Assertions */ #define mu_check(test) \ MU__SAFE_BLOCK( \ diff --git a/applications/debug/unit_tests/storage/dirwalk_test.c b/applications/debug/unit_tests/storage/dirwalk_test.c index e0842a7a438..19ac336fffb 100644 --- a/applications/debug/unit_tests/storage/dirwalk_test.c +++ b/applications/debug/unit_tests/storage/dirwalk_test.c @@ -139,7 +139,7 @@ static bool write_file_13DA(Storage* storage, const char* path) { File* file = storage_file_alloc(storage); bool result = false; if(storage_file_open(file, path, FSAM_WRITE, FSOM_CREATE_ALWAYS)) { - result = storage_file_write(file, "13DA", 4) == 4; + result = (storage_file_write(file, "13DA", 4) == 4); } storage_file_close(file); storage_file_free(file); diff --git a/applications/debug/unit_tests/storage/storage_test.c b/applications/debug/unit_tests/storage/storage_test.c index 13188e5e0fc..5ea36935b13 100644 --- a/applications/debug/unit_tests/storage/storage_test.c +++ b/applications/debug/unit_tests/storage/storage_test.c @@ -115,6 +115,66 @@ MU_TEST(storage_file_open_close) { furi_record_close(RECORD_STORAGE); } +static bool storage_file_read_write_test(File* file, uint8_t* data, size_t test_size) { + const char* filename = UNIT_TESTS_PATH("storage_chunk.test"); + + // fill with pattern + for(size_t i = 0; i < test_size; i++) { + data[i] = (i % 113); + } + + bool result = false; + do { + if(!storage_file_open(file, filename, FSAM_WRITE, FSOM_CREATE_ALWAYS)) break; + if(test_size != storage_file_write(file, data, test_size)) break; + storage_file_close(file); + + // reset data + memset(data, 0, test_size); + + if(!storage_file_open(file, filename, FSAM_READ, FSOM_OPEN_EXISTING)) break; + if(test_size != storage_file_read(file, data, test_size)) break; + storage_file_close(file); + + // check that data is correct + for(size_t i = 0; i < test_size; i++) { + if(data[i] != (i % 113)) { + break; + } + } + + result = true; + } while(false); + + return result; +} + +MU_TEST(storage_file_read_write_64k) { + Storage* storage = furi_record_open(RECORD_STORAGE); + File* file = storage_file_alloc(storage); + + size_t size_1k = 1024; + size_t size_64k = size_1k + size_1k * 63; + size_t size_65k = size_64k + size_1k; + size_t size_max = size_65k + 8; + + size_t max_ram_block = memmgr_heap_get_max_free_block(); + + if(max_ram_block < size_max) { + mu_warn("Not enough RAM for >64k block test"); + } else { + uint8_t* data = malloc(size_max); + mu_check(storage_file_read_write_test(file, data, size_1k)); + mu_check(storage_file_read_write_test(file, data, size_64k)); + mu_check(storage_file_read_write_test(file, data, size_65k)); + mu_check(storage_file_read_write_test(file, data, size_max)); + free(data); + } + + storage_file_free(file); + furi_record_close(RECORD_STORAGE); +} + MU_TEST_SUITE(storage_file) { storage_file_open_lock_setup(); MU_RUN_TEST(storage_file_open_close); @@ -122,6 +182,10 @@ MU_TEST_SUITE(storage_file) { storage_file_open_lock_teardown(); } +MU_TEST_SUITE(storage_file_64k) { + MU_RUN_TEST(storage_file_read_write_64k); +} + MU_TEST(storage_dir_open_close) { Storage* storage = furi_record_open(RECORD_STORAGE); File* file; @@ -640,6 +704,7 @@ MU_TEST_SUITE(test_md5_calc_suite) { int run_minunit_test_storage() { MU_RUN_SUITE(storage_file); + MU_RUN_SUITE(storage_file_64k); MU_RUN_SUITE(storage_dir); MU_RUN_SUITE(storage_rename); MU_RUN_SUITE(test_data_path); diff --git a/applications/debug/unit_tests/subghz/subghz_test.c b/applications/debug/unit_tests/subghz/subghz_test.c index 2f2b9e9811c..60c7abd0323 100644 --- a/applications/debug/unit_tests/subghz/subghz_test.c +++ b/applications/debug/unit_tests/subghz/subghz_test.c @@ -326,6 +326,7 @@ bool subghz_hal_async_tx_test_run(SubGhzHalAsyncTxTestType type) { furi_hal_subghz_set_frequency_and_path(433920000); if(!furi_hal_subghz_start_async_tx(subghz_hal_async_tx_test_yield, &test)) { + mu_warn("SubGHZ transmission is prohibited"); return false; } diff --git a/applications/debug/unit_tests/test_index.c b/applications/debug/unit_tests/test_index.c index 7c1b6b44477..d7afaa3c4f3 100644 --- a/applications/debug/unit_tests/test_index.c +++ b/applications/debug/unit_tests/test_index.c @@ -78,6 +78,16 @@ void minunit_print_fail(const char* str) { printf(_FURI_LOG_CLR_E "%s\r\n" _FURI_LOG_CLR_RESET, str); } +void minunit_printf_warning(const char* format, ...) { + FuriString* str = furi_string_alloc(); + va_list args; + va_start(args, format); + furi_string_vprintf(str, format, args); + va_end(args); + printf(_FURI_LOG_CLR_W "%s\r\n" _FURI_LOG_CLR_RESET, furi_string_get_cstr(str)); + furi_string_free(str); +} + void unit_tests_cli(Cli* cli, FuriString* args, void* context) { UNUSED(cli); UNUSED(args); diff --git a/applications/main/archive/helpers/archive_favorites.c b/applications/main/archive/helpers/archive_favorites.c index f395ee5a116..682aa6b3830 100644 --- a/applications/main/archive/helpers/archive_favorites.c +++ b/applications/main/archive/helpers/archive_favorites.c @@ -12,12 +12,12 @@ static bool archive_favorites_read_line(File* file, FuriString* str_result) { bool result = false; do { - uint16_t read_count = storage_file_read(file, buffer, ARCHIVE_FAV_FILE_BUF_LEN); + size_t read_count = storage_file_read(file, buffer, ARCHIVE_FAV_FILE_BUF_LEN); if(storage_file_get_error(file) != FSE_OK) { return false; } - for(uint16_t i = 0; i < read_count; i++) { + for(size_t i = 0; i < read_count; i++) { if(buffer[i] == '\n') { uint32_t position = storage_file_tell(file); if(storage_file_get_error(file) != FSE_OK) { diff --git a/applications/main/subghz/subghz_cli.c b/applications/main/subghz/subghz_cli.c index 0a7b521273f..e1b5e868414 100644 --- a/applications/main/subghz/subghz_cli.c +++ b/applications/main/subghz/subghz_cli.c @@ -946,7 +946,7 @@ static void subghz_cli_command(Cli* cli, FuriString* args, void* context) { static bool subghz_on_system_start_istream_read(pb_istream_t* istream, pb_byte_t* buf, size_t count) { File* file = istream->state; - uint16_t ret = storage_file_read(file, buf, count); + size_t ret = storage_file_read(file, buf, count); return (count == ret); } diff --git a/applications/services/notification/notification_app.c b/applications/services/notification/notification_app.c index 5769ced9268..9baa738b79c 100644 --- a/applications/services/notification/notification_app.c +++ b/applications/services/notification/notification_app.c @@ -444,7 +444,7 @@ static bool notification_load_settings(NotificationApp* app) { storage_file_open(file, NOTIFICATION_SETTINGS_PATH, FSAM_READ, FSOM_OPEN_EXISTING); if(fs_result) { - uint16_t bytes_count = storage_file_read(file, &settings, settings_size); + size_t bytes_count = storage_file_read(file, &settings, settings_size); if(bytes_count != settings_size) { fs_result = false; @@ -488,7 +488,7 @@ static bool notification_save_settings(NotificationApp* app) { storage_file_open(file, NOTIFICATION_SETTINGS_PATH, FSAM_WRITE, FSOM_CREATE_ALWAYS); if(fs_result) { - uint16_t bytes_count = storage_file_write(file, &settings, settings_size); + size_t bytes_count = storage_file_write(file, &settings, settings_size); if(bytes_count != settings_size) { fs_result = false; diff --git a/applications/services/rpc/rpc_storage.c b/applications/services/rpc/rpc_storage.c index ee024b823a1..913d89f1afb 100644 --- a/applications/services/rpc/rpc_storage.c +++ b/applications/services/rpc/rpc_storage.c @@ -466,7 +466,7 @@ static void rpc_system_storage_write_process(const PB_Main* request, void* conte request->content.storage_write_request.file.data->size) { uint8_t* buffer = request->content.storage_write_request.file.data->bytes; size_t buffer_size = request->content.storage_write_request.file.data->size; - uint16_t written_size = storage_file_write(file, buffer, buffer_size); + size_t written_size = storage_file_write(file, buffer, buffer_size); fs_operation_success = (written_size == buffer_size); } diff --git a/applications/services/storage/storage.h b/applications/services/storage/storage.h index 3caa155c7dc..20a371fc081 100644 --- a/applications/services/storage/storage.h +++ b/applications/services/storage/storage.h @@ -123,7 +123,7 @@ bool storage_file_is_dir(File* file); * @param bytes_to_read number of bytes to read. Must be less than or equal to the size of the buffer. * @return actual number of bytes read (may be fewer than requested). */ -uint16_t storage_file_read(File* file, void* buff, uint16_t bytes_to_read); +size_t storage_file_read(File* file, void* buff, size_t bytes_to_read); /** * @brief Write bytes from a buffer to a file. @@ -133,7 +133,7 @@ uint16_t storage_file_read(File* file, void* buff, uint16_t bytes_to_read); * @param bytes_to_write number of bytes to write. Must be less than or equal to the size of the buffer. * @return actual number of bytes written (may be fewer than requested). */ -uint16_t storage_file_write(File* file, const void* buff, uint16_t bytes_to_write); +size_t storage_file_write(File* file, const void* buff, size_t bytes_to_write); /** * @brief Change the current access position in a file. @@ -207,7 +207,7 @@ bool storage_file_exists(Storage* storage, const char* path); * @param size data size to be copied, in bytes. * @return true if the data was successfully copied, false otherwise. */ -bool storage_file_copy_to_file(File* source, File* destination, uint32_t size); +bool storage_file_copy_to_file(File* source, File* destination, size_t size); /******************* Directory Functions *******************/ diff --git a/applications/services/storage/storage_cli.c b/applications/services/storage/storage_cli.c index 59489d459c2..2927022a3f4 100644 --- a/applications/services/storage/storage_cli.c +++ b/applications/services/storage/storage_cli.c @@ -198,15 +198,15 @@ static void storage_cli_read(Cli* cli, FuriString* path) { File* file = storage_file_alloc(api); if(storage_file_open(file, furi_string_get_cstr(path), FSAM_READ, FSOM_OPEN_EXISTING)) { - const uint16_t buffer_size = 128; - uint16_t read_size = 0; + const size_t buffer_size = 128; + size_t read_size = 0; uint8_t* data = malloc(buffer_size); printf("Size: %lu\r\n", (uint32_t)storage_file_size(file)); do { read_size = storage_file_read(file, data, buffer_size); - for(uint16_t i = 0; i < read_size; i++) { + for(size_t i = 0; i < read_size; i++) { printf("%c", data[i]); } } while(read_size > 0); @@ -227,7 +227,7 @@ static void storage_cli_write(Cli* cli, FuriString* path) { Storage* api = furi_record_open(RECORD_STORAGE); File* file = storage_file_alloc(api); - const uint16_t buffer_size = 512; + const size_t buffer_size = 512; uint8_t* buffer = malloc(buffer_size); if(storage_file_open(file, furi_string_get_cstr(path), FSAM_WRITE, FSOM_OPEN_APPEND)) { @@ -239,10 +239,10 @@ static void storage_cli_write(Cli* cli, FuriString* path) { uint8_t symbol = cli_getc(cli); if(symbol == CliSymbolAsciiETX) { - uint16_t write_size = read_index % buffer_size; + size_t write_size = read_index % buffer_size; if(write_size > 0) { - uint16_t written_size = storage_file_write(file, buffer, write_size); + size_t written_size = storage_file_write(file, buffer, write_size); if(written_size != write_size) { storage_cli_print_error(storage_file_get_error(file)); @@ -257,7 +257,7 @@ static void storage_cli_write(Cli* cli, FuriString* path) { read_index++; if(((read_index % buffer_size) == 0)) { - uint16_t written_size = storage_file_write(file, buffer, buffer_size); + size_t written_size = storage_file_write(file, buffer, buffer_size); if(written_size != buffer_size) { storage_cli_print_error(storage_file_get_error(file)); @@ -289,7 +289,7 @@ static void storage_cli_read_chunks(Cli* cli, FuriString* path, FuriString* args } else if(storage_file_open(file, furi_string_get_cstr(path), FSAM_READ, FSOM_OPEN_EXISTING)) { uint64_t file_size = storage_file_size(file); - printf("Size: %lu\r\n", (uint32_t)file_size); + printf("Size: %llu\r\n", file_size); if(buffer_size) { uint8_t* data = malloc(buffer_size); @@ -297,8 +297,8 @@ static void storage_cli_read_chunks(Cli* cli, FuriString* path, FuriString* args printf("\r\nReady?\r\n"); cli_getc(cli); - uint16_t read_size = storage_file_read(file, data, buffer_size); - for(uint16_t i = 0; i < read_size; i++) { + size_t read_size = storage_file_read(file, data, buffer_size); + for(size_t i = 0; i < read_size; i++) { putchar(data[i]); } file_size -= read_size; @@ -335,7 +335,7 @@ static void storage_cli_write_chunk(Cli* cli, FuriString* path, FuriString* args size_t read_bytes = cli_read(cli, buffer, buffer_size); - uint16_t written_size = storage_file_write(file, buffer, read_bytes); + size_t written_size = storage_file_write(file, buffer, read_bytes); if(written_size != buffer_size) { storage_cli_print_error(storage_file_get_error(file)); diff --git a/applications/services/storage/storage_external_api.c b/applications/services/storage/storage_external_api.c index 1027d43101a..666090346a0 100644 --- a/applications/services/storage/storage_external_api.c +++ b/applications/services/storage/storage_external_api.c @@ -139,7 +139,7 @@ bool storage_file_close(File* file) { return S_RETURN_BOOL; } -uint16_t storage_file_read(File* file, void* buff, uint16_t bytes_to_read) { +static uint16_t storage_file_read_underlying(File* file, void* buff, uint16_t bytes_to_read) { if(bytes_to_read == 0) { return 0; } @@ -159,7 +159,8 @@ uint16_t storage_file_read(File* file, void* buff, uint16_t bytes_to_read) { return S_RETURN_UINT16; } -uint16_t storage_file_write(File* file, const void* buff, uint16_t bytes_to_write) { +static uint16_t + storage_file_write_underlying(File* file, const void* buff, uint16_t bytes_to_write) { if(bytes_to_write == 0) { return 0; } @@ -179,6 +180,40 @@ uint16_t storage_file_write(File* file, const void* buff, uint16_t bytes_to_writ return S_RETURN_UINT16; } +size_t storage_file_read(File* file, void* buff, size_t to_read) { + size_t total = 0; + + const size_t max_chunk = UINT16_MAX; + do { + const size_t chunk = MIN((to_read - total), max_chunk); + size_t read = storage_file_read_underlying(file, buff + total, chunk); + total += read; + + if(storage_file_get_error(file) != FSE_OK || read != chunk) { + break; + } + } while(total != to_read); + + return total; +} + +size_t storage_file_write(File* file, const void* buff, size_t to_write) { + size_t total = 0; + + const size_t max_chunk = UINT16_MAX; + do { + const size_t chunk = MIN((to_write - total), max_chunk); + size_t written = storage_file_write_underlying(file, buff + total, chunk); + total += written; + + if(storage_file_get_error(file) != FSE_OK || written != chunk) { + break; + } + } while(total != to_write); + + return total; +} + bool storage_file_seek(File* file, uint32_t offset, bool from_start) { S_FILE_API_PROLOGUE; S_API_PROLOGUE; @@ -252,7 +287,7 @@ bool storage_file_exists(Storage* storage, const char* path) { return exist; } -bool storage_file_copy_to_file(File* source, File* destination, uint32_t size) { +bool storage_file_copy_to_file(File* source, File* destination, size_t size) { uint8_t* buffer = malloc(FILE_BUFFER_SIZE); while(size) { diff --git a/applications/settings/storage_settings/scenes/storage_settings_scene_benchmark.c b/applications/settings/storage_settings/scenes/storage_settings_scene_benchmark.c index 8359c00be3d..a5bf1b9d376 100644 --- a/applications/settings/storage_settings/scenes/storage_settings_scene_benchmark.c +++ b/applications/settings/storage_settings/scenes/storage_settings_scene_benchmark.c @@ -44,7 +44,7 @@ static bool storage_settings_scene_bench_write( } static bool - storage_settings_scene_bench_read(Storage* api, uint16_t size, uint8_t* data, uint32_t* speed) { + storage_settings_scene_bench_read(Storage* api, size_t size, uint8_t* data, uint32_t* speed) { File* file = storage_file_alloc(api); bool result = true; *speed = -1; @@ -82,7 +82,7 @@ static void storage_settings_scene_benchmark(StorageSettings* app) { bench_data[i] = (uint8_t)i; } - uint16_t bench_size[BENCH_COUNT] = {1, 8, 32, 256, 512, 1024}; + size_t bench_size[BENCH_COUNT] = {1, 8, 32, 256, 512, 1024}; uint32_t bench_w_speed[BENCH_COUNT] = {0, 0, 0, 0, 0, 0}; uint32_t bench_r_speed[BENCH_COUNT] = {0, 0, 0, 0, 0, 0}; diff --git a/applications/system/updater/util/update_task_worker_flasher.c b/applications/system/updater/util/update_task_worker_flasher.c index c560319928b..1b4b0790032 100644 --- a/applications/system/updater/util/update_task_worker_flasher.c +++ b/applications/system/updater/util/update_task_worker_flasher.c @@ -104,7 +104,7 @@ static bool update_task_write_stack_data(UpdateTask* update_task) { update_task_set_progress(update_task, UpdateTaskStageRadioWrite, 0); uint8_t* fw_block = malloc(FLASH_PAGE_SIZE); - uint16_t bytes_read = 0; + size_t bytes_read = 0; uint32_t element_offs = 0; while(element_offs < stack_size) { diff --git a/lib/flipper_application/elf/elf_file.c b/lib/flipper_application/elf/elf_file.c index b2c9445ffe3..8a78cca413d 100644 --- a/lib/flipper_application/elf/elf_file.c +++ b/lib/flipper_application/elf/elf_file.c @@ -101,7 +101,7 @@ static bool elf_read_string_from_offset(ELFFile* elf, off_t offset, FuriString* buffer[ELF_NAME_BUFFER_LEN] = 0; while(true) { - uint16_t read = storage_file_read(elf->fd, buffer, ELF_NAME_BUFFER_LEN); + size_t read = storage_file_read(elf->fd, buffer, ELF_NAME_BUFFER_LEN); furi_string_cat(name, buffer); if(strlen(buffer) < ELF_NAME_BUFFER_LEN) { result = true; diff --git a/lib/music_worker/music_worker.c b/lib/music_worker/music_worker.c index 61fc838f2e3..279d1267376 100644 --- a/lib/music_worker/music_worker.c +++ b/lib/music_worker/music_worker.c @@ -396,7 +396,7 @@ bool music_worker_load_rtttl_from_file(MusicWorker* instance, const char* file_p break; }; - uint16_t ret = 0; + size_t ret = 0; do { uint8_t buffer[65] = {0}; ret = storage_file_read(file, buffer, sizeof(buffer) - 1); diff --git a/lib/toolbox/crc32_calc.c b/lib/toolbox/crc32_calc.c index c0cd169b180..78295167f38 100644 --- a/lib/toolbox/crc32_calc.c +++ b/lib/toolbox/crc32_calc.c @@ -14,7 +14,7 @@ uint32_t crc32_calc_file(File* file, const FileCrcProgressCb progress_cb, void* uint32_t file_crc = 0; uint8_t* data_buffer = malloc(CRC_DATA_BUFFER_MAX_LEN); - uint16_t data_buffer_valid_len; + size_t data_buffer_valid_len; uint32_t file_size = storage_file_size(file); diff --git a/lib/toolbox/md5_calc.c b/lib/toolbox/md5_calc.c index b050295a147..7f335a33f2b 100644 --- a/lib/toolbox/md5_calc.c +++ b/lib/toolbox/md5_calc.c @@ -5,13 +5,13 @@ bool md5_calc_file(File* file, const char* path, unsigned char output[16], FS_Er bool result = storage_file_open(file, path, FSAM_READ, FSOM_OPEN_EXISTING); if(result) { - const uint16_t size_to_read = 512; + const size_t size_to_read = 512; uint8_t* data = malloc(size_to_read); md5_context* md5_ctx = malloc(sizeof(md5_context)); md5_starts(md5_ctx); while(true) { - uint16_t read_size = storage_file_read(file, data, size_to_read); + size_t read_size = storage_file_read(file, data, size_to_read); if(read_size == 0) break; md5_update(md5_ctx, data, read_size); } diff --git a/lib/toolbox/saved_struct.c b/lib/toolbox/saved_struct.c index 02b73f21044..2f1c09c8eb5 100644 --- a/lib/toolbox/saved_struct.c +++ b/lib/toolbox/saved_struct.c @@ -46,7 +46,7 @@ bool saved_struct_save(const char* path, void* data, size_t size, uint8_t magic, header.flags = 0; header.timestamp = 0; - uint16_t bytes_count = storage_file_write(file, &header, sizeof(header)); + size_t bytes_count = storage_file_write(file, &header, sizeof(header)); bytes_count += storage_file_write(file, data, size); if(bytes_count != (size + sizeof(header))) { @@ -79,7 +79,7 @@ bool saved_struct_load(const char* path, void* data, size_t size, uint8_t magic, } if(result) { - uint16_t bytes_count = storage_file_read(file, &header, sizeof(SavedStructHeader)); + size_t bytes_count = storage_file_read(file, &header, sizeof(SavedStructHeader)); bytes_count += storage_file_read(file, data_read, size); if(bytes_count != (sizeof(SavedStructHeader) + size)) { @@ -146,7 +146,7 @@ bool saved_struct_get_payload_size( break; } - uint16_t bytes_count = storage_file_read(file, &header, sizeof(SavedStructHeader)); + size_t bytes_count = storage_file_read(file, &header, sizeof(SavedStructHeader)); if(bytes_count != sizeof(SavedStructHeader)) { FURI_LOG_E(TAG, "Failed to read header"); break; diff --git a/lib/toolbox/stream/file_stream.c b/lib/toolbox/stream/file_stream.c index 095dce472ca..2b5348b3e85 100644 --- a/lib/toolbox/stream/file_stream.c +++ b/lib/toolbox/stream/file_stream.c @@ -134,31 +134,11 @@ static size_t file_stream_size(FileStream* stream) { } static size_t file_stream_write(FileStream* stream, const uint8_t* data, size_t size) { - // TODO FL-3545: cache - size_t need_to_write = size; - while(need_to_write > 0) { - uint16_t was_written = - storage_file_write(stream->file, data + (size - need_to_write), need_to_write); - need_to_write -= was_written; - - if(was_written == 0) break; - } - - return size - need_to_write; + return storage_file_write(stream->file, data, size); } static size_t file_stream_read(FileStream* stream, uint8_t* data, size_t size) { - // TODO FL-3545: cache - size_t need_to_read = size; - while(need_to_read > 0) { - uint16_t was_read = - storage_file_read(stream->file, data + (size - need_to_read), need_to_read); - need_to_read -= was_read; - - if(was_read == 0) break; - } - - return size - need_to_read; + return storage_file_read(stream->file, data, size); } static bool file_stream_delete_and_insert( diff --git a/lib/update_util/dfu_file.c b/lib/update_util/dfu_file.c index eef9f064591..85b661e8e00 100644 --- a/lib/update_util/dfu_file.c +++ b/lib/update_util/dfu_file.c @@ -22,7 +22,7 @@ uint8_t dfu_file_validate_headers(File* dfuf, const DfuValidationParams* referen DfuPrefix dfu_prefix = {0}; DfuSuffix dfu_suffix = {0}; - uint16_t bytes_read = 0; + size_t bytes_read = 0; if(!storage_file_is_open(dfuf) || !storage_file_seek(dfuf, 0, true)) { return 0; @@ -90,7 +90,7 @@ static DfuUpdateBlockResult dfu_file_perform_task_for_update_pages( } uint8_t* fw_block = malloc(FLASH_PAGE_SIZE); - uint16_t bytes_read = 0; + size_t bytes_read = 0; uint32_t element_offs = 0; while(element_offs < header->dwElementSize) { @@ -125,7 +125,7 @@ static DfuUpdateBlockResult dfu_file_perform_task_for_update_pages( bool dfu_file_process_targets(const DfuUpdateTask* task, File* dfuf, const uint8_t n_targets) { TargetPrefix target_prefix = {0}; ImageElementHeader image_element = {0}; - uint16_t bytes_read = 0; + size_t bytes_read = 0; if(!storage_file_seek(dfuf, sizeof(DfuPrefix), true)) { return UpdateBlockResult_Failed; diff --git a/lib/update_util/update_operation.c b/lib/update_util/update_operation.c index 0cecfc016a3..39a7ea07526 100644 --- a/lib/update_util/update_operation.c +++ b/lib/update_util/update_operation.c @@ -85,7 +85,7 @@ bool update_operation_get_current_package_manifest_path(Storage* storage, FuriSt upd_file, UPDATE_FILE_POINTER_FN, FSAM_READ, FSOM_OPEN_EXISTING)) { break; } - uint16_t bytes_read = + size_t bytes_read = storage_file_read(upd_file, manifest_name_buffer, UPDATE_MANIFEST_MAX_PATH_LEN); if((bytes_read == 0) || (bytes_read == UPDATE_MANIFEST_MAX_PATH_LEN)) { break; diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 8ed8f403c70..d861d851173 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,45.1,, +Version,+,46.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -1376,7 +1376,7 @@ Function,+,furi_pubsub_subscribe,FuriPubSubSubscription*,"FuriPubSub*, FuriPubSu Function,+,furi_pubsub_unsubscribe,void,"FuriPubSub*, FuriPubSubSubscription*" Function,+,furi_record_close,void,const char* Function,+,furi_record_create,void,"const char*, void*" -Function,-,furi_record_destroy,_Bool,const char* +Function,+,furi_record_destroy,_Bool,const char* Function,+,furi_record_exists,_Bool,const char* Function,-,furi_record_init,void, Function,+,furi_record_open,void*,const char* @@ -2090,7 +2090,7 @@ Function,-,storage_dir_rewind,_Bool,File* Function,+,storage_error_get_desc,const char*,FS_Error Function,+,storage_file_alloc,File*,Storage* Function,+,storage_file_close,_Bool,File* -Function,+,storage_file_copy_to_file,_Bool,"File*, File*, uint32_t" +Function,+,storage_file_copy_to_file,_Bool,"File*, File*, size_t" Function,+,storage_file_eof,_Bool,File* Function,+,storage_file_exists,_Bool,"Storage*, const char*" Function,+,storage_file_free,void,File* @@ -2100,13 +2100,13 @@ Function,-,storage_file_get_internal_error,int32_t,File* Function,+,storage_file_is_dir,_Bool,File* Function,+,storage_file_is_open,_Bool,File* Function,+,storage_file_open,_Bool,"File*, const char*, FS_AccessMode, FS_OpenMode" -Function,+,storage_file_read,uint16_t,"File*, void*, uint16_t" +Function,+,storage_file_read,size_t,"File*, void*, size_t" Function,+,storage_file_seek,_Bool,"File*, uint32_t, _Bool" Function,+,storage_file_size,uint64_t,File* Function,+,storage_file_sync,_Bool,File* Function,+,storage_file_tell,uint64_t,File* Function,+,storage_file_truncate,_Bool,File* -Function,+,storage_file_write,uint16_t,"File*, const void*, uint16_t" +Function,+,storage_file_write,size_t,"File*, const void*, size_t" Function,+,storage_get_next_filename,void,"Storage*, const char*, const char*, const char*, FuriString*, uint8_t" Function,+,storage_get_pubsub,FuriPubSub*,Storage* Function,+,storage_int_backup,FS_Error,"Storage*, const char*" diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 64695c2dbc9..c50db0c77cf 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,45.1,, +Version,+,46.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -1571,7 +1571,7 @@ Function,+,furi_pubsub_subscribe,FuriPubSubSubscription*,"FuriPubSub*, FuriPubSu Function,+,furi_pubsub_unsubscribe,void,"FuriPubSub*, FuriPubSubSubscription*" Function,+,furi_record_close,void,const char* Function,+,furi_record_create,void,"const char*, void*" -Function,-,furi_record_destroy,_Bool,const char* +Function,+,furi_record_destroy,_Bool,const char* Function,+,furi_record_exists,_Bool,const char* Function,-,furi_record_init,void, Function,+,furi_record_open,void*,const char* @@ -2717,7 +2717,7 @@ Function,-,storage_dir_rewind,_Bool,File* Function,+,storage_error_get_desc,const char*,FS_Error Function,+,storage_file_alloc,File*,Storage* Function,+,storage_file_close,_Bool,File* -Function,+,storage_file_copy_to_file,_Bool,"File*, File*, uint32_t" +Function,+,storage_file_copy_to_file,_Bool,"File*, File*, size_t" Function,+,storage_file_eof,_Bool,File* Function,+,storage_file_exists,_Bool,"Storage*, const char*" Function,+,storage_file_free,void,File* @@ -2727,13 +2727,13 @@ Function,-,storage_file_get_internal_error,int32_t,File* Function,+,storage_file_is_dir,_Bool,File* Function,+,storage_file_is_open,_Bool,File* Function,+,storage_file_open,_Bool,"File*, const char*, FS_AccessMode, FS_OpenMode" -Function,+,storage_file_read,uint16_t,"File*, void*, uint16_t" +Function,+,storage_file_read,size_t,"File*, void*, size_t" Function,+,storage_file_seek,_Bool,"File*, uint32_t, _Bool" Function,+,storage_file_size,uint64_t,File* Function,+,storage_file_sync,_Bool,File* Function,+,storage_file_tell,uint64_t,File* Function,+,storage_file_truncate,_Bool,File* -Function,+,storage_file_write,uint16_t,"File*, const void*, uint16_t" +Function,+,storage_file_write,size_t,"File*, const void*, size_t" Function,+,storage_get_next_filename,void,"Storage*, const char*, const char*, const char*, FuriString*, uint8_t" Function,+,storage_get_pubsub,FuriPubSub*,Storage* Function,+,storage_int_backup,FS_Error,"Storage*, const char*" From 1c3cbec661b735b08f8401f93a50a772aff48a62 Mon Sep 17 00:00:00 2001 From: RebornedBrain <138568282+RebornedBrain@users.noreply.github.com> Date: Sun, 26 Nov 2023 11:10:33 +0300 Subject: [PATCH 813/824] [FL-3640] NFC: Felica UID emulation (#3190) * Added basic template of Felica listener * Raw nfc felica listener functions * Added functions to setup chip for felica listener * Cleanup function templates from unnecessary parts * Removed todo comment * Updated api versions * Adjusted chip config for felica * Set proper chip passive target mode for felica * Added felica function to unit tests * Update furi_hal_nfc_felica.c * Removed duplication Co-authored-by: gornekich --- .../debug/unit_tests/nfc/nfc_transport.c | 15 ++ .../helpers/protocol_support/felica/felica.c | 10 +- lib/nfc/nfc.c | 18 ++- lib/nfc/nfc.h | 19 +++ lib/nfc/protocols/felica/felica.h | 2 + lib/nfc/protocols/felica/felica_listener.c | 79 ++++++++++ lib/nfc/protocols/felica/felica_listener.h | 14 ++ .../protocols/felica/felica_listener_defs.h | 13 ++ lib/nfc/protocols/felica/felica_listener_i.h | 21 +++ lib/nfc/protocols/nfc_listener_defs.c | 2 + targets/f7/api_symbols.csv | 2 + targets/f7/furi_hal/furi_hal_nfc_felica.c | 138 +++++++++++++++++- targets/furi_hal_include/furi_hal_nfc.h | 17 +++ 13 files changed, 346 insertions(+), 4 deletions(-) create mode 100644 lib/nfc/protocols/felica/felica_listener.c create mode 100644 lib/nfc/protocols/felica/felica_listener.h create mode 100644 lib/nfc/protocols/felica/felica_listener_defs.h create mode 100644 lib/nfc/protocols/felica/felica_listener_i.h diff --git a/applications/debug/unit_tests/nfc/nfc_transport.c b/applications/debug/unit_tests/nfc/nfc_transport.c index e2e313fdef3..e9f4e21341a 100644 --- a/applications/debug/unit_tests/nfc/nfc_transport.c +++ b/applications/debug/unit_tests/nfc/nfc_transport.c @@ -455,4 +455,19 @@ NfcError nfc_iso15693_listener_tx_sof(Nfc* instance) { return NfcErrorNone; } +NfcError nfc_felica_listener_set_sensf_res_data( + Nfc* instance, + const uint8_t* idm, + const uint8_t idm_len, + const uint8_t* pmm, + const uint8_t pmm_len) { + furi_assert(instance); + furi_assert(idm); + furi_assert(pmm); + furi_assert(idm_len == 8); + furi_assert(pmm_len == 8); + + return NfcErrorNone; +} + #endif diff --git a/applications/main/nfc/helpers/protocol_support/felica/felica.c b/applications/main/nfc/helpers/protocol_support/felica/felica.c index b3e629f4a52..f9c84912161 100644 --- a/applications/main/nfc/helpers/protocol_support/felica/felica.c +++ b/applications/main/nfc/helpers/protocol_support/felica/felica.c @@ -67,8 +67,14 @@ static bool nfc_scene_saved_menu_on_event_felica(NfcApp* instance, uint32_t even return false; } +static void nfc_scene_emulate_on_enter_felica(NfcApp* instance) { + const FelicaData* data = nfc_device_get_data(instance->nfc_device, NfcProtocolFelica); + instance->listener = nfc_listener_alloc(instance->nfc, NfcProtocolFelica, data); + nfc_listener_start(instance->listener, NULL, NULL); +} + const NfcProtocolSupportBase nfc_protocol_support_felica = { - .features = NfcProtocolFeatureNone, + .features = NfcProtocolFeatureEmulateUid, .scene_info = { @@ -102,7 +108,7 @@ const NfcProtocolSupportBase nfc_protocol_support_felica = { }, .scene_emulate = { - .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_enter = nfc_scene_emulate_on_enter_felica, .on_event = nfc_protocol_support_common_on_event_empty, }, }; diff --git a/lib/nfc/nfc.c b/lib/nfc/nfc.c index a7c4fe1a6d4..6475cce435a 100644 --- a/lib/nfc/nfc.c +++ b/lib/nfc/nfc.c @@ -76,7 +76,7 @@ static const FuriHalNfcTech nfc_tech_table[NfcModeNum][NfcTechNum] = { [NfcTechIso14443a] = FuriHalNfcTechIso14443a, [NfcTechIso14443b] = FuriHalNfcTechInvalid, [NfcTechIso15693] = FuriHalNfcTechIso15693, - [NfcTechFelica] = FuriHalNfcTechInvalid, + [NfcTechFelica] = FuriHalNfcTechFelica, }, }; @@ -646,4 +646,20 @@ NfcError nfc_iso15693_listener_tx_sof(Nfc* instance) { return ret; } +NfcError nfc_felica_listener_set_sensf_res_data( + Nfc* instance, + const uint8_t* idm, + const uint8_t idm_len, + const uint8_t* pmm, + const uint8_t pmm_len) { + furi_assert(instance); + furi_assert(idm); + furi_assert(pmm); + + FuriHalNfcError error = + furi_hal_nfc_felica_listener_set_sensf_res_data(idm, idm_len, pmm, pmm_len); + instance->comm_state = NfcCommStateIdle; + return nfc_process_hal_error(error); +} + #endif // APP_UNIT_TESTS diff --git a/lib/nfc/nfc.h b/lib/nfc/nfc.h index 1e8f315a50e..4f7980b0260 100644 --- a/lib/nfc/nfc.h +++ b/lib/nfc/nfc.h @@ -351,6 +351,25 @@ NfcError nfc_iso14443a_listener_set_col_res_data( uint8_t* atqa, uint8_t sak); +/** + * @brief Set FeliCa collision resolution parameters in listener mode. + * + * Configures the NFC hardware for automatic collision resolution. + * + * @param[in,out] instance pointer to the instance to be configured. + * @param[in] idm pointer to a byte array containing the IDm. + * @param[in] idm_len IDm length in bytes. + * @param[in] pmm pointer to a byte array containing the PMm. + * @param[in] pmm_len PMm length in bytes. + * @returns NfcErrorNone on success, any other error code on failure. +*/ +NfcError nfc_felica_listener_set_sensf_res_data( + Nfc* instance, + const uint8_t* idm, + const uint8_t idm_len, + const uint8_t* pmm, + const uint8_t pmm_len); + /** * @brief Send ISO15693 Start of Frame pattern in listener mode * diff --git a/lib/nfc/protocols/felica/felica.h b/lib/nfc/protocols/felica/felica.h index da9d2294ee8..31e040b8a1e 100644 --- a/lib/nfc/protocols/felica/felica.h +++ b/lib/nfc/protocols/felica/felica.h @@ -14,6 +14,8 @@ extern "C" { #define FELICA_FDT_POLL_FC (10000U) #define FELICA_POLL_POLL_MIN_US (1280U) +#define FELICA_FDT_LISTEN_FC (1172) + #define FELICA_SYSTEM_CODE_CODE (0xFFFFU) #define FELICA_TIME_SLOT_1 (0x00U) #define FELICA_TIME_SLOT_2 (0x01U) diff --git a/lib/nfc/protocols/felica/felica_listener.c b/lib/nfc/protocols/felica/felica_listener.c new file mode 100644 index 00000000000..4e6c0578547 --- /dev/null +++ b/lib/nfc/protocols/felica/felica_listener.c @@ -0,0 +1,79 @@ +#include "felica_listener_i.h" + +#include "nfc/protocols/nfc_listener_base.h" + +#define FELICA_LISTENER_MAX_BUFFER_SIZE (64) +#define TAG "Felica" + +FelicaListener* felica_listener_alloc(Nfc* nfc, FelicaData* data) { + furi_assert(nfc); + furi_assert(data); + + FelicaListener* instance = malloc(sizeof(FelicaListener)); + instance->nfc = nfc; + instance->data = data; + instance->tx_buffer = bit_buffer_alloc(FELICA_LISTENER_MAX_BUFFER_SIZE); + instance->rx_buffer = bit_buffer_alloc(FELICA_LISTENER_MAX_BUFFER_SIZE); + + nfc_set_fdt_listen_fc(instance->nfc, FELICA_FDT_LISTEN_FC); + + nfc_config(instance->nfc, NfcModeListener, NfcTechFelica); + nfc_felica_listener_set_sensf_res_data( + nfc, data->idm.data, sizeof(data->idm), data->pmm.data, sizeof(data->pmm)); + + return instance; +} + +void felica_listener_free(FelicaListener* instance) { + furi_assert(instance); + furi_assert(instance->tx_buffer); + + bit_buffer_free(instance->tx_buffer); + bit_buffer_free(instance->rx_buffer); + free(instance); +} + +void felica_listener_set_callback( + FelicaListener* listener, + NfcGenericCallback callback, + void* context) { + UNUSED(listener); + UNUSED(callback); + UNUSED(context); +} + +const FelicaData* felica_listener_get_data(const FelicaListener* instance) { + furi_assert(instance); + furi_assert(instance->data); + + return instance->data; +} + +NfcCommand felica_listener_run(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.protocol == NfcProtocolInvalid); + furi_assert(event.event_data); + + FelicaListener* instance = context; + NfcEvent* nfc_event = event.event_data; + NfcCommand command = NfcCommandContinue; + + if(nfc_event->type == NfcEventTypeListenerActivated) { + instance->state = Felica_ListenerStateActivated; + FURI_LOG_D(TAG, "Activated"); + } else if(nfc_event->type == NfcEventTypeFieldOff) { + instance->state = Felica_ListenerStateIdle; + FURI_LOG_D(TAG, "Field Off"); + } else if(nfc_event->type == NfcEventTypeRxEnd) { + FURI_LOG_D(TAG, "Rx Done"); + } + return command; +} + +const NfcListenerBase nfc_listener_felica = { + .alloc = (NfcListenerAlloc)felica_listener_alloc, + .free = (NfcListenerFree)felica_listener_free, + .set_callback = (NfcListenerSetCallback)felica_listener_set_callback, + .get_data = (NfcListenerGetData)felica_listener_get_data, + .run = (NfcListenerRun)felica_listener_run, +}; \ No newline at end of file diff --git a/lib/nfc/protocols/felica/felica_listener.h b/lib/nfc/protocols/felica/felica_listener.h new file mode 100644 index 00000000000..d210befa574 --- /dev/null +++ b/lib/nfc/protocols/felica/felica_listener.h @@ -0,0 +1,14 @@ +#pragma once + +#include "felica.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct FelicaListener FelicaListener; + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/nfc/protocols/felica/felica_listener_defs.h b/lib/nfc/protocols/felica/felica_listener_defs.h new file mode 100644 index 00000000000..19b252be59e --- /dev/null +++ b/lib/nfc/protocols/felica/felica_listener_defs.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern const NfcListenerBase nfc_listener_felica; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/felica/felica_listener_i.h b/lib/nfc/protocols/felica/felica_listener_i.h new file mode 100644 index 00000000000..4fa25a16209 --- /dev/null +++ b/lib/nfc/protocols/felica/felica_listener_i.h @@ -0,0 +1,21 @@ +#include "felica_listener.h" + +#include + +typedef enum { + Felica_ListenerStateIdle, + Felica_ListenerStateActivated, +} FelicaListenerState; + +struct FelicaListener { + Nfc* nfc; + FelicaData* data; + FelicaListenerState state; + + BitBuffer* tx_buffer; + BitBuffer* rx_buffer; + + NfcGenericEvent generic_event; + NfcGenericCallback callback; + void* context; +}; \ No newline at end of file diff --git a/lib/nfc/protocols/nfc_listener_defs.c b/lib/nfc/protocols/nfc_listener_defs.c index 31f9bc16c63..2a6167e9cb3 100644 --- a/lib/nfc/protocols/nfc_listener_defs.c +++ b/lib/nfc/protocols/nfc_listener_defs.c @@ -6,6 +6,7 @@ #include #include #include +#include const NfcListenerBase* nfc_listeners_api[NfcProtocolNum] = { [NfcProtocolIso14443_3a] = &nfc_listener_iso14443_3a, @@ -18,4 +19,5 @@ const NfcListenerBase* nfc_listeners_api[NfcProtocolNum] = { [NfcProtocolMfDesfire] = NULL, [NfcProtocolSlix] = &nfc_listener_slix, [NfcProtocolSt25tb] = NULL, + [NfcProtocolFelica] = &nfc_listener_felica, }; diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index c50db0c77cf..9431028abe9 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1287,6 +1287,7 @@ Function,+,furi_hal_nfc_abort,FuriHalNfcError, Function,+,furi_hal_nfc_acquire,FuriHalNfcError, Function,+,furi_hal_nfc_event_start,FuriHalNfcError, Function,+,furi_hal_nfc_event_stop,FuriHalNfcError, +Function,+,furi_hal_nfc_felica_listener_set_sensf_res_data,FuriHalNfcError,"const uint8_t*, const uint8_t, const uint8_t*, const uint8_t" Function,+,furi_hal_nfc_field_detect_start,FuriHalNfcError, Function,+,furi_hal_nfc_field_detect_stop,FuriHalNfcError, Function,+,furi_hal_nfc_field_is_present,_Bool, @@ -2316,6 +2317,7 @@ Function,+,nfc_dict_get_next_key,_Bool,"NfcDict*, uint8_t*, size_t" Function,+,nfc_dict_get_total_keys,uint32_t,NfcDict* Function,+,nfc_dict_is_key_present,_Bool,"NfcDict*, const uint8_t*, size_t" Function,+,nfc_dict_rewind,_Bool,NfcDict* +Function,+,nfc_felica_listener_set_sensf_res_data,NfcError,"Nfc*, const uint8_t*, const uint8_t, const uint8_t*, const uint8_t" Function,+,nfc_free,void,Nfc* Function,+,nfc_iso14443a_listener_set_col_res_data,NfcError,"Nfc*, uint8_t*, uint8_t, uint8_t*, uint8_t" Function,+,nfc_iso14443a_listener_tx_custom_parity,NfcError,"Nfc*, const BitBuffer*" diff --git a/targets/f7/furi_hal/furi_hal_nfc_felica.c b/targets/f7/furi_hal/furi_hal_nfc_felica.c index e4b8ac0ee6b..f762a0ed32b 100644 --- a/targets/f7/furi_hal/furi_hal_nfc_felica.c +++ b/targets/f7/furi_hal/furi_hal_nfc_felica.c @@ -1,6 +1,9 @@ #include "furi_hal_nfc_i.h" #include "furi_hal_nfc_tech_i.h" +// Prevent FDT timer from starting +#define FURI_HAL_NFC_FELICA_LISTENER_FDT_COMP_FC (INT32_MAX) + static FuriHalNfcError furi_hal_nfc_felica_poller_init(FuriHalSpiBusHandle* handle) { // Enable Felica mode, AM modulation st25r3916_change_reg_bits( @@ -50,6 +53,126 @@ static FuriHalNfcError furi_hal_nfc_felica_poller_deinit(FuriHalSpiBusHandle* ha return FuriHalNfcErrorNone; } +static FuriHalNfcError furi_hal_nfc_felica_listener_init(FuriHalSpiBusHandle* handle) { + furi_assert(handle); + st25r3916_write_reg( + handle, + ST25R3916_REG_OP_CONTROL, + ST25R3916_REG_OP_CONTROL_en | ST25R3916_REG_OP_CONTROL_rx_en | + ST25R3916_REG_OP_CONTROL_en_fd_auto_efd); + + // Enable Target Felica mode, AM modulation + st25r3916_write_reg( + handle, + ST25R3916_REG_MODE, + ST25R3916_REG_MODE_targ_targ | ST25R3916_REG_MODE_om2 | ST25R3916_REG_MODE_tr_am); + + st25r3916_change_reg_bits( + handle, + ST25R3916_REG_BIT_RATE, + ST25R3916_REG_BIT_RATE_txrate_mask | ST25R3916_REG_BIT_RATE_rxrate_mask, + ST25R3916_REG_BIT_RATE_txrate_212 | ST25R3916_REG_BIT_RATE_rxrate_212); + + // Receive configuration + st25r3916_write_reg( + handle, + ST25R3916_REG_RX_CONF1, + ST25R3916_REG_RX_CONF1_lp0 | ST25R3916_REG_RX_CONF1_hz_12_80khz); + + // AGC enabled, ratio 3:1, squelch after TX + st25r3916_write_reg( + handle, + ST25R3916_REG_RX_CONF2, + ST25R3916_REG_RX_CONF2_agc6_3 | ST25R3916_REG_RX_CONF2_agc_m | + ST25R3916_REG_RX_CONF2_agc_en | ST25R3916_REG_RX_CONF2_sqm_dyn); + // HF operation, full gain on AM and PM channels + st25r3916_write_reg(handle, ST25R3916_REG_RX_CONF3, 0x00); + // No gain reduction on AM and PM channels + st25r3916_write_reg(handle, ST25R3916_REG_RX_CONF4, 0x00); + // 10% ASK modulation + st25r3916_write_reg(handle, ST25R3916_REG_TX_DRIVER, ST25R3916_REG_TX_DRIVER_am_mod_10percent); + + // Correlator setup + st25r3916_write_reg( + handle, + ST25R3916_REG_CORR_CONF1, + ST25R3916_REG_CORR_CONF1_corr_s6 | ST25R3916_REG_CORR_CONF1_corr_s4 | + ST25R3916_REG_CORR_CONF1_corr_s2); + + // Sleep mode disable, 424kHz mode off + st25r3916_write_reg(handle, ST25R3916_REG_CORR_CONF2, 0x00); + + st25r3916_write_reg(handle, ST25R3916_REG_MASK_RX_TIMER, 0x02); + + st25r3916_direct_cmd(handle, ST25R3916_CMD_STOP); + uint32_t interrupts = + (ST25R3916_IRQ_MASK_FWL | ST25R3916_IRQ_MASK_TXE | ST25R3916_IRQ_MASK_RXS | + ST25R3916_IRQ_MASK_RXE | ST25R3916_IRQ_MASK_PAR | ST25R3916_IRQ_MASK_CRC | + ST25R3916_IRQ_MASK_ERR1 | ST25R3916_IRQ_MASK_ERR2 | ST25R3916_IRQ_MASK_NRE | + ST25R3916_IRQ_MASK_EON | ST25R3916_IRQ_MASK_EOF | ST25R3916_IRQ_MASK_WU_A_X | + ST25R3916_IRQ_MASK_WU_A); + // Clear interrupts + st25r3916_get_irq(handle); + + st25r3916_write_reg( + handle, + ST25R3916_REG_PASSIVE_TARGET, + ST25R3916_REG_PASSIVE_TARGET_d_106_ac_a | ST25R3916_REG_PASSIVE_TARGET_d_ac_ap2p | + ST25R3916_REG_PASSIVE_TARGET_fdel_1); + // Enable interrupts + st25r3916_mask_irq(handle, ~interrupts); + st25r3916_direct_cmd(handle, ST25R3916_CMD_GOTO_SENSE); + + return FuriHalNfcErrorNone; +} + +static FuriHalNfcError furi_hal_nfc_felica_listener_deinit(FuriHalSpiBusHandle* handle) { + UNUSED(handle); + return FuriHalNfcErrorNone; +} + +static FuriHalNfcEvent furi_hal_nfc_felica_listener_wait_event(uint32_t timeout_ms) { + UNUSED(timeout_ms); + FuriHalNfcEvent event = furi_hal_nfc_wait_event_common(timeout_ms); + + return event; +} + +FuriHalNfcError furi_hal_nfc_felica_listener_tx( + FuriHalSpiBusHandle* handle, + const uint8_t* tx_data, + size_t tx_bits) { + UNUSED(handle); + UNUSED(tx_data); + UNUSED(tx_bits); + return FuriHalNfcErrorNone; +} + +FuriHalNfcError furi_hal_nfc_felica_listener_sleep(FuriHalSpiBusHandle* handle) { + UNUSED(handle); + return FuriHalNfcErrorNone; +} + +FuriHalNfcError furi_hal_nfc_felica_listener_idle(FuriHalSpiBusHandle* handle) { + UNUSED(handle); + return FuriHalNfcErrorNone; +} + +FuriHalNfcError furi_hal_nfc_felica_listener_set_sensf_res_data( + const uint8_t* idm, + const uint8_t idm_len, + const uint8_t* pmm, + const uint8_t pmm_len) { + FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + // Write PT Memory + uint8_t pt_memory[19] = {}; + pt_memory[2] = 0x01; + memcpy(pt_memory + 3, idm, idm_len); + memcpy(pt_memory + 3 + idm_len, pmm, pmm_len); + st25r3916_write_ptf_mem(handle, pt_memory, sizeof(pt_memory)); + return FuriHalNfcErrorNone; +} + const FuriHalNfcTechBase furi_hal_nfc_felica = { .poller = { @@ -65,5 +188,18 @@ const FuriHalNfcTechBase furi_hal_nfc_felica = { .rx = furi_hal_nfc_common_fifo_rx, }, - .listener = {}, + .listener = + { + .compensation = + { + .fdt = FURI_HAL_NFC_FELICA_LISTENER_FDT_COMP_FC, + }, + .init = furi_hal_nfc_felica_listener_init, + .deinit = furi_hal_nfc_felica_listener_deinit, + .wait_event = furi_hal_nfc_felica_listener_wait_event, + .tx = furi_hal_nfc_felica_listener_tx, + .rx = furi_hal_nfc_common_fifo_rx, + .sleep = furi_hal_nfc_felica_listener_sleep, + .idle = furi_hal_nfc_felica_listener_idle, + }, }; diff --git a/targets/furi_hal_include/furi_hal_nfc.h b/targets/furi_hal_include/furi_hal_nfc.h index ad4080e2647..3d145d10026 100644 --- a/targets/furi_hal_include/furi_hal_nfc.h +++ b/targets/furi_hal_include/furi_hal_nfc.h @@ -452,6 +452,23 @@ FuriHalNfcError furi_hal_nfc_iso14443a_listener_tx_custom_parity( */ FuriHalNfcError furi_hal_nfc_iso15693_listener_tx_sof(); +/** + * @brief Set FeliCa collision resolution parameters in listener mode. + * + * Configures the NFC hardware for automatic collision resolution. + * + * @param[in] idm pointer to a byte array containing the IDm. + * @param[in] idm_len IDm length in bytes. + * @param[in] pmm pointer to a byte array containing the PMm. + * @param[in] pmm_len PMm length in bytes. + * @returns NfcErrorNone on success, any other error code on failure. +*/ +FuriHalNfcError furi_hal_nfc_felica_listener_set_sensf_res_data( + const uint8_t* idm, + const uint8_t idm_len, + const uint8_t* pmm, + const uint8_t pmm_len); + #ifdef __cplusplus } #endif From f9101d80840b4b7ef7146e41bba99aac881bbfca Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Sun, 26 Nov 2023 12:20:49 +0400 Subject: [PATCH 814/824] [FL-3686] Mifare Classic fixes (#3221) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update Mifare Classic generators to create more accuate data * Check the transfer buffer validity for NACK * Fix the AC issues * CRC errors don't really affect emulation, checking for them isn't worth it * Make ATQA logic a bit easier to understand * mf classic: change log level * mf classic: fix log level Co-authored-by: gornekich Co-authored-by: あく --- lib/nfc/helpers/nfc_data_generator.c | 41 ++++++++++++------- lib/nfc/protocols/mf_classic/mf_classic.c | 13 +++--- lib/nfc/protocols/mf_classic/mf_classic.h | 2 + .../mf_classic/mf_classic_listener.c | 25 ++++++++++- .../mf_classic/mf_classic_listener_i.h | 1 + 5 files changed, 60 insertions(+), 22 deletions(-) diff --git a/lib/nfc/helpers/nfc_data_generator.c b/lib/nfc/helpers/nfc_data_generator.c index 011f9f6db7d..21f062605b6 100644 --- a/lib/nfc/helpers/nfc_data_generator.c +++ b/lib/nfc/helpers/nfc_data_generator.c @@ -329,9 +329,23 @@ static void nfc_generate_mf_classic_uid(uint8_t* uid, uint8_t length) { static void nfc_generate_mf_classic_common(MfClassicData* data, uint8_t uid_len, MfClassicType type) { data->iso14443_3a_data->uid_len = uid_len; - data->iso14443_3a_data->atqa[0] = 0x44; + data->iso14443_3a_data->atqa[0] = 0x00; data->iso14443_3a_data->atqa[1] = 0x00; - data->iso14443_3a_data->sak = 0x08; + data->iso14443_3a_data->sak = 0x00; + // Calculate the proper ATQA and SAK + if(uid_len == 7) { + data->iso14443_3a_data->atqa[0] |= 0x40; + } + if(type == MfClassicType1k) { + data->iso14443_3a_data->atqa[0] |= 0x04; + data->iso14443_3a_data->sak = 0x08; + } else if(type == MfClassicType4k) { + data->iso14443_3a_data->atqa[0] |= 0x02; + data->iso14443_3a_data->sak = 0x18; + } else if(type == MfClassicTypeMini) { + data->iso14443_3a_data->atqa[0] |= 0x08; + data->iso14443_3a_data->sak = 0x09; + } data->type = type; } @@ -343,6 +357,11 @@ static void nfc_generate_mf_classic_sector_trailer(MfClassicData* data, uint8_t sec_tr->access_bits.data[2] = 0x80; sec_tr->access_bits.data[3] = 0x69; // Nice + for(int i = 0; i < 6; i++) { + sec_tr->key_a.data[i] = 0xFF; + sec_tr->key_b.data[i] = 0xFF; + } + mf_classic_set_block_read(data, block, &data->block[block]); mf_classic_set_key_found( data, mf_classic_get_sector_by_block(block), MfClassicKeyTypeA, 0xFFFFFFFFFFFF); @@ -396,41 +415,35 @@ static void nfc_generate_mf_classic(NfcDevice* nfc_device, uint8_t uid_len, MfCl uint16_t block_num = mf_classic_get_total_block_num(type); if(type == MfClassicType4k) { - // Set every block to 0xFF + // Set every block to 0x00 for(uint16_t i = 1; i < block_num; i++) { if(mf_classic_is_sector_trailer(i)) { nfc_generate_mf_classic_sector_trailer(mfc_data, i); } else { - memset(&mfc_data->block[i].data, 0xFF, 16); + memset(&mfc_data->block[i].data, 0x00, 16); } mf_classic_set_block_read(mfc_data, i, &mfc_data->block[i]); } - // Set SAK to 18 - mfc_data->iso14443_3a_data->sak = 0x18; } else if(type == MfClassicType1k) { - // Set every block to 0xFF + // Set every block to 0x00 for(uint16_t i = 1; i < block_num; i++) { if(mf_classic_is_sector_trailer(i)) { nfc_generate_mf_classic_sector_trailer(mfc_data, i); } else { - memset(&mfc_data->block[i].data, 0xFF, 16); + memset(&mfc_data->block[i].data, 0x00, 16); } mf_classic_set_block_read(mfc_data, i, &mfc_data->block[i]); } - // Set SAK to 08 - mfc_data->iso14443_3a_data->sak = 0x08; } else if(type == MfClassicTypeMini) { - // Set every block to 0xFF + // Set every block to 0x00 for(uint16_t i = 1; i < block_num; i++) { if(mf_classic_is_sector_trailer(i)) { nfc_generate_mf_classic_sector_trailer(mfc_data, i); } else { - memset(&mfc_data->block[i].data, 0xFF, 16); + memset(&mfc_data->block[i].data, 0x00, 16); } mf_classic_set_block_read(mfc_data, i, &mfc_data->block[i]); } - // Set SAK to 09 - mfc_data->iso14443_3a_data->sak = 0x09; } nfc_generate_mf_classic_block_0( diff --git a/lib/nfc/protocols/mf_classic/mf_classic.c b/lib/nfc/protocols/mf_classic/mf_classic.c index 400cf0d7fb3..e68e8c71872 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic.c +++ b/lib/nfc/protocols/mf_classic/mf_classic.c @@ -606,6 +606,7 @@ static bool mf_classic_is_allowed_access_sector_trailer( uint8_t* access_bits_arr = sec_tr->access_bits.data; uint8_t AC = ((access_bits_arr[1] >> 5) & 0x04) | ((access_bits_arr[2] >> 2) & 0x02) | ((access_bits_arr[2] >> 7) & 0x01); + FURI_LOG_T("NFC", "AC: %02X", AC); switch(action) { case MfClassicActionKeyARead: { @@ -615,20 +616,20 @@ static bool mf_classic_is_allowed_access_sector_trailer( case MfClassicActionKeyBWrite: { return ( (key_type == MfClassicKeyTypeA && (AC == 0x00 || AC == 0x01)) || - (key_type == MfClassicKeyTypeB && (AC == 0x04 || AC == 0x03))); + (key_type == MfClassicKeyTypeB && + (AC == 0x00 || AC == 0x04 || AC == 0x03 || AC == 0x01))); } case MfClassicActionKeyBRead: { - return (key_type == MfClassicKeyTypeA && (AC == 0x00 || AC == 0x02 || AC == 0x01)); + return (key_type == MfClassicKeyTypeA && (AC == 0x00 || AC == 0x02 || AC == 0x01)) || + (key_type == MfClassicKeyTypeB && (AC == 0x00 || AC == 0x02 || AC == 0x01)); } case MfClassicActionACRead: { - return ( - (key_type == MfClassicKeyTypeA) || - (key_type == MfClassicKeyTypeB && !(AC == 0x00 || AC == 0x02 || AC == 0x01))); + return ((key_type == MfClassicKeyTypeA) || (key_type == MfClassicKeyTypeB)); } case MfClassicActionACWrite: { return ( (key_type == MfClassicKeyTypeA && (AC == 0x01)) || - (key_type == MfClassicKeyTypeB && (AC == 0x03 || AC == 0x05))); + (key_type == MfClassicKeyTypeB && (AC == 0x01 || AC == 0x03 || AC == 0x05))); } default: return false; diff --git a/lib/nfc/protocols/mf_classic/mf_classic.h b/lib/nfc/protocols/mf_classic/mf_classic.h index f180411df52..146e6a6f157 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic.h +++ b/lib/nfc/protocols/mf_classic/mf_classic.h @@ -19,6 +19,8 @@ extern "C" { #define MF_CLASSIC_CMD_HALT_LSB (0x00) #define MF_CLASSIC_CMD_ACK (0x0A) #define MF_CLASSIC_CMD_NACK (0x00) +#define MF_CLASSIC_CMD_NACK_TRANSFER_INVALID (0x04) +#define MF_CLASSIC_CMD_NACK_TRANSFER_CRC_ERROR (0x01) #define MF_CLASSIC_TOTAL_SECTORS_MAX (40) #define MF_CLASSIC_TOTAL_BLOCKS_MAX (256) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_listener.c b/lib/nfc/protocols/mf_classic/mf_classic_listener.c index f7bd5b3f45f..fb12ba8a95c 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_listener.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_listener.c @@ -34,6 +34,7 @@ static void mf_classic_listener_reset_state(MfClassicListener* instance) { instance->cmd_in_progress = false; instance->current_cmd_handler_idx = 0; instance->transfer_value = 0; + instance->transfer_valid = false; instance->value_cmd = MfClassicValueCommandInvalid; } @@ -154,7 +155,7 @@ static MfClassicListenerCommand uint32_t nt_num = nfc_util_bytes2num(instance->auth_context.nt.data, sizeof(MfClassicNt)); uint32_t secret_poller = ar_num ^ crypto1_word(instance->crypto, 0, 0); if(secret_poller != prng_successor(nt_num, 64)) { - FURI_LOG_D( + FURI_LOG_T( TAG, "Wrong reader key: %08lX != %08lX", secret_poller, prng_successor(nt_num, 64)); mf_classic_listener_reset_state(instance); break; @@ -272,6 +273,17 @@ static MfClassicListenerCommand mf_classic_listener_write_block_second_part_hand if(mf_classic_is_sector_trailer(block_num)) { MfClassicSectorTrailer* sec_tr = (MfClassicSectorTrailer*)█ + + // Check if any writing is allowed + if(!mf_classic_is_allowed_access( + instance->data, block_num, key_type, MfClassicActionKeyAWrite) && + !mf_classic_is_allowed_access( + instance->data, block_num, key_type, MfClassicActionKeyBWrite) && + !mf_classic_is_allowed_access( + instance->data, block_num, key_type, MfClassicActionACWrite)) { + break; + } + if(mf_classic_is_allowed_access( instance->data, block_num, key_type, MfClassicActionKeyAWrite)) { bit_buffer_write_bytes_mid(buff, sec_tr->key_a.data, 0, sizeof(MfClassicKey)); @@ -338,6 +350,7 @@ static MfClassicListenerCommand break; } + instance->transfer_valid = true; instance->cmd_in_progress = true; instance->current_cmd_handler_idx++; command = MfClassicListenerCommandAck; @@ -382,6 +395,7 @@ static MfClassicListenerCommand } instance->transfer_value += data; + instance->transfer_valid = true; instance->cmd_in_progress = true; instance->current_cmd_handler_idx++; @@ -411,6 +425,7 @@ static MfClassicListenerCommand mf_classic_value_to_block( instance->transfer_value, block_num, &instance->data->block[block_num]); instance->transfer_value = 0; + instance->transfer_valid = false; command = MfClassicListenerCommandAck; } while(false); @@ -581,7 +596,13 @@ NfcCommand mf_classic_listener_run(NfcGenericEvent event, void* context) { if(mfc_command == MfClassicListenerCommandAck) { mf_classic_listener_send_short_frame(instance, MF_CLASSIC_CMD_ACK); } else if(mfc_command == MfClassicListenerCommandNack) { - mf_classic_listener_send_short_frame(instance, MF_CLASSIC_CMD_NACK); + // Calculate nack based on the transfer buffer validity + uint8_t nack = MF_CLASSIC_CMD_NACK; + if(!instance->transfer_valid) { + nack += MF_CLASSIC_CMD_NACK_TRANSFER_INVALID; + } + + mf_classic_listener_send_short_frame(instance, nack); } else if(mfc_command == MfClassicListenerCommandSilent) { command = NfcCommandReset; } else if(mfc_command == MfClassicListenerCommandSleep) { diff --git a/lib/nfc/protocols/mf_classic/mf_classic_listener_i.h b/lib/nfc/protocols/mf_classic/mf_classic_listener_i.h index 4b040bec12c..52273be9c22 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_listener_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_listener_i.h @@ -42,6 +42,7 @@ struct MfClassicListener { // Value operation data int32_t transfer_value; + bool transfer_valid; MfClassicValueCommand value_cmd; NfcGenericEvent generic_event; From ff129e524a89fddaed7b84a4a5af5928daa97c42 Mon Sep 17 00:00:00 2001 From: Evgeny Stepanischev Date: Thu, 30 Nov 2023 12:38:48 +0300 Subject: [PATCH 815/824] Allows you to use UCS-2 in canvas_glyph_width (#3226) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * allows you to use UCS-2 in canvas_glyph_width * Sync API Symbols Co-authored-by: あく --- applications/services/gui/canvas.c | 2 +- applications/services/gui/canvas.h | 2 +- targets/f18/api_symbols.csv | 4 ++-- targets/f7/api_symbols.csv | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/applications/services/gui/canvas.c b/applications/services/gui/canvas.c index 85c0528530d..44adcd93951 100644 --- a/applications/services/gui/canvas.c +++ b/applications/services/gui/canvas.c @@ -202,7 +202,7 @@ uint16_t canvas_string_width(Canvas* canvas, const char* str) { return u8g2_GetStrWidth(&canvas->fb, str); } -uint8_t canvas_glyph_width(Canvas* canvas, char symbol) { +uint8_t canvas_glyph_width(Canvas* canvas, uint16_t symbol) { furi_assert(canvas); return u8g2_GetGlyphWidth(&canvas->fb, symbol); } diff --git a/applications/services/gui/canvas.h b/applications/services/gui/canvas.h index f34d02bfbac..a369e213bd9 100644 --- a/applications/services/gui/canvas.h +++ b/applications/services/gui/canvas.h @@ -214,7 +214,7 @@ uint16_t canvas_string_width(Canvas* canvas, const char* str); * * @return width in pixels */ -uint8_t canvas_glyph_width(Canvas* canvas, char symbol); +uint8_t canvas_glyph_width(Canvas* canvas, uint16_t symbol); /** Draw bitmap picture at position defined by x,y. * diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index d861d851173..7835718defa 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,46.0,, +Version,+,47.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -633,7 +633,7 @@ Function,+,canvas_draw_str_aligned,void,"Canvas*, uint8_t, uint8_t, Align, Align Function,+,canvas_draw_triangle,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, CanvasDirection" Function,+,canvas_draw_xbm,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, const uint8_t*" Function,+,canvas_get_font_params,const CanvasFontParameters*,"const Canvas*, Font" -Function,+,canvas_glyph_width,uint8_t,"Canvas*, char" +Function,+,canvas_glyph_width,uint8_t,"Canvas*, uint16_t" Function,+,canvas_height,uint8_t,const Canvas* Function,+,canvas_invert_color,void,Canvas* Function,+,canvas_reset,void,Canvas* diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 9431028abe9..f31349f9c9d 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,46.0,, +Version,+,47.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -722,7 +722,7 @@ Function,+,canvas_draw_str_aligned,void,"Canvas*, uint8_t, uint8_t, Align, Align Function,+,canvas_draw_triangle,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, CanvasDirection" Function,+,canvas_draw_xbm,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, const uint8_t*" Function,+,canvas_get_font_params,const CanvasFontParameters*,"const Canvas*, Font" -Function,+,canvas_glyph_width,uint8_t,"Canvas*, char" +Function,+,canvas_glyph_width,uint8_t,"Canvas*, uint16_t" Function,+,canvas_height,uint8_t,const Canvas* Function,+,canvas_invert_color,void,Canvas* Function,+,canvas_reset,void,Canvas* From a849d49c92ff6d015d558717c9bb5c72ad074d86 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Thu, 30 Nov 2023 13:51:38 +0400 Subject: [PATCH 816/824] [FL-3682] Add the secret door animation (#3233) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- .../external/L2_Secret_door_128x64/frame_0.png | Bin 0 -> 1648 bytes .../external/L2_Secret_door_128x64/frame_1.png | Bin 0 -> 1655 bytes .../external/L2_Secret_door_128x64/frame_10.png | Bin 0 -> 1552 bytes .../external/L2_Secret_door_128x64/frame_11.png | Bin 0 -> 1548 bytes .../external/L2_Secret_door_128x64/frame_12.png | Bin 0 -> 1577 bytes .../external/L2_Secret_door_128x64/frame_13.png | Bin 0 -> 1541 bytes .../external/L2_Secret_door_128x64/frame_14.png | Bin 0 -> 1578 bytes .../external/L2_Secret_door_128x64/frame_15.png | Bin 0 -> 1897 bytes .../external/L2_Secret_door_128x64/frame_16.png | Bin 0 -> 1918 bytes .../external/L2_Secret_door_128x64/frame_17.png | Bin 0 -> 1882 bytes .../external/L2_Secret_door_128x64/frame_18.png | Bin 0 -> 1907 bytes .../external/L2_Secret_door_128x64/frame_19.png | Bin 0 -> 1974 bytes .../external/L2_Secret_door_128x64/frame_2.png | Bin 0 -> 1636 bytes .../external/L2_Secret_door_128x64/frame_20.png | Bin 0 -> 1937 bytes .../external/L2_Secret_door_128x64/frame_21.png | Bin 0 -> 1894 bytes .../external/L2_Secret_door_128x64/frame_22.png | Bin 0 -> 1923 bytes .../external/L2_Secret_door_128x64/frame_23.png | Bin 0 -> 1966 bytes .../external/L2_Secret_door_128x64/frame_24.png | Bin 0 -> 1958 bytes .../external/L2_Secret_door_128x64/frame_25.png | Bin 0 -> 1946 bytes .../external/L2_Secret_door_128x64/frame_26.png | Bin 0 -> 1881 bytes .../external/L2_Secret_door_128x64/frame_27.png | Bin 0 -> 1902 bytes .../external/L2_Secret_door_128x64/frame_28.png | Bin 0 -> 1969 bytes .../external/L2_Secret_door_128x64/frame_29.png | Bin 0 -> 2094 bytes .../external/L2_Secret_door_128x64/frame_3.png | Bin 0 -> 1599 bytes .../external/L2_Secret_door_128x64/frame_30.png | Bin 0 -> 2028 bytes .../external/L2_Secret_door_128x64/frame_31.png | Bin 0 -> 1922 bytes .../external/L2_Secret_door_128x64/frame_32.png | Bin 0 -> 1900 bytes .../external/L2_Secret_door_128x64/frame_33.png | Bin 0 -> 1938 bytes .../external/L2_Secret_door_128x64/frame_34.png | Bin 0 -> 1923 bytes .../external/L2_Secret_door_128x64/frame_35.png | Bin 0 -> 1913 bytes .../external/L2_Secret_door_128x64/frame_36.png | Bin 0 -> 1937 bytes .../external/L2_Secret_door_128x64/frame_37.png | Bin 0 -> 1944 bytes .../external/L2_Secret_door_128x64/frame_38.png | Bin 0 -> 1683 bytes .../external/L2_Secret_door_128x64/frame_39.png | Bin 0 -> 1662 bytes .../external/L2_Secret_door_128x64/frame_4.png | Bin 0 -> 1618 bytes .../external/L2_Secret_door_128x64/frame_40.png | Bin 0 -> 1725 bytes .../external/L2_Secret_door_128x64/frame_41.png | Bin 0 -> 1654 bytes .../external/L2_Secret_door_128x64/frame_42.png | Bin 0 -> 1495 bytes .../external/L2_Secret_door_128x64/frame_43.png | Bin 0 -> 1440 bytes .../external/L2_Secret_door_128x64/frame_44.png | Bin 0 -> 1445 bytes .../external/L2_Secret_door_128x64/frame_45.png | Bin 0 -> 1464 bytes .../external/L2_Secret_door_128x64/frame_46.png | Bin 0 -> 1446 bytes .../external/L2_Secret_door_128x64/frame_47.png | Bin 0 -> 1369 bytes .../external/L2_Secret_door_128x64/frame_48.png | Bin 0 -> 1529 bytes .../external/L2_Secret_door_128x64/frame_49.png | Bin 0 -> 1851 bytes .../external/L2_Secret_door_128x64/frame_5.png | Bin 0 -> 1625 bytes .../external/L2_Secret_door_128x64/frame_50.png | Bin 0 -> 2132 bytes .../external/L2_Secret_door_128x64/frame_51.png | Bin 0 -> 2258 bytes .../external/L2_Secret_door_128x64/frame_52.png | Bin 0 -> 1615 bytes .../external/L2_Secret_door_128x64/frame_6.png | Bin 0 -> 1591 bytes .../external/L2_Secret_door_128x64/frame_7.png | Bin 0 -> 1671 bytes .../external/L2_Secret_door_128x64/frame_8.png | Bin 0 -> 1612 bytes .../external/L2_Secret_door_128x64/frame_9.png | Bin 0 -> 1575 bytes .../external/L2_Secret_door_128x64/meta.txt | 14 ++++++++++++++ assets/dolphin/external/manifest.txt | 7 +++++++ 55 files changed, 21 insertions(+) create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_0.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_1.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_10.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_11.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_12.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_13.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_14.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_15.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_16.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_17.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_18.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_19.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_2.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_20.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_21.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_22.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_23.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_24.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_25.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_26.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_27.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_28.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_29.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_3.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_30.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_31.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_32.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_33.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_34.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_35.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_36.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_37.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_38.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_39.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_4.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_40.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_41.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_42.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_43.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_44.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_45.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_46.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_47.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_48.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_49.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_5.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_50.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_51.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_52.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_6.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_7.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_8.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/frame_9.png create mode 100644 assets/dolphin/external/L2_Secret_door_128x64/meta.txt diff --git a/assets/dolphin/external/L2_Secret_door_128x64/frame_0.png b/assets/dolphin/external/L2_Secret_door_128x64/frame_0.png new file mode 100644 index 0000000000000000000000000000000000000000..8f29d5e2c7065c50e1ff361f437e85f24cfd1113 GIT binary patch literal 1648 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 b3!?u3+$H50?G|~m00000NkvXXu0mjf9Le=- literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Secret_door_128x64/frame_13.png b/assets/dolphin/external/L2_Secret_door_128x64/frame_13.png new file mode 100644 index 0000000000000000000000000000000000000000..38171c273ca522acbac21d3154c54aca99cd5db0 GIT binary patch literal 1541 zcmV+g2KxDlP)|T}FV6v(CIEwG9Yq(9LZN z5mox)v3Ttk;ETODYW+HZC!^3e1Uwqw5b$t(3Gi@i%K=I$RBNSD$|dVPE!RQyyCnx` z#<+?Ee7z?iqTfVTgKs=;p32X+PJ3}pD3o^ zZAoC11U;*f`TKEC0;=DwRlo=d(0SYQPc(i5qTlUhAW8y$4GkAhHhzPr-{~p<&EmBr zPBIuzTr^ThYt9<~ZdJmlZY5~?eItN2nWpd=<6F;4BtX_c$&!hP4y`$6jgK6XsWK2T zWMoV~R`UcsDZJzm-Qtx*+1-F^-&6Vkky5Q6(}0kme@?>8_C^VD_uS(T2`HEYNXpNE z6tg1t`Ex6AcjKZs4-y3k;KilY%CTzy(S$F<&94YCk(T(MEFQFn_8d$Aq)yL(<@&FB zhv4A~j~GGZ9ZTqKVAckAmS3}b;<9UAY6afvckeA9sMBLgszq4Q{ToH*{7C7FlBU1D zid=sPAPK{--NnUm?2i8)Jo$F(^svgO{@y3HS9yH4;$pUCG*-~u4R^v@yBqR(W*B0&myX10 zE7L|2)~uFtbl%n84<SCwyi2x6f-66|pHAhOlm9LXCPkvTkKy3tRn?V{y+R674pV4G=77^vogp8Rk zWLBwYdVq|wY&51@0Wsj|cFK-!K~EcC=qt$980#cdB|o>BjaNQFkK1%R2c)@0*c+mOekl_(bu%L~9}U)j}yn zG<@ykNNZ9+GF^b36gkUeRm){T(Nzn9SRTk2r*_9uhji9!$ z_DM8f&qrIqcP1BF^RGJj83d`d{=N=IPQ2H))i!G-OY?fdMSD{&H( zfKK4dCZevezkfFZSj{bo1k(Pa#$0{+xr{F_n{VHvhTs{nH}-BuM!=0Bg|3yIPpz65 zLGR(uzYE}>t2cmc9h5?XP zDHpnb=1yerl0XgK9BJp=Lit!i!5_@$J6 z{~y3Jz7|v4^+;&QOt>_{qaMmD!~1f4#@J#NFtz(2_{m0vMCNJNq@S-qG=?L|pyUZx z8~^kGT-p8iRX{dlUDxJpa(afpI%m+<^GJR5=SxyHS7!7iNrpz5+}1{n-ykK7cTEr_ zJ@D&0J`e5YED0K<4M&AUlxoS35@NOI>t)Y&=OcO*JAqP+?FEl0u2)2a`5utXde8`q zA{utGjf&iFP(1)wo~B4?;}PTWIS>+f>qU%i%@>8WUQe!UbJ8;!i6z6&?C;Z2YqkGdc*LD%jYoibtJNp| r(@~1@8$r=n>Mb(srbaLl!k&nK#X!8+KN{(N00000NkvXXu0mjf#LVgb literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Secret_door_128x64/frame_14.png b/assets/dolphin/external/L2_Secret_door_128x64/frame_14.png new file mode 100644 index 0000000000000000000000000000000000000000..3eb9a1f6327dffcc27476326c75d9b48c1f75cf8 GIT binary patch literal 1578 zcmV+_2G#kAP)74%zv8YW9hk9RX$=SXVtYs|Lb-cW?65(P5P$ zFp5ZPb|vg9E5C}2^1BPKPf;Eu;9%Q$5L8GG}W0bmN%uSYw za6|B-fd;9T@YY*0%tWMUy;YU{TPLvMAuGVTg5hUgG{~HjnU9F3vJ6I_p6)B=B?%UT zk-N7R4JZa%b;To|5kL#oJcrF@Dq9~&>!Wmz zDqr(+}{b*KG&{7T0!}35usWTkJeRPc!>ZRaHU0V z4|JW>GX7*kpvd$}uCD%3KM$#srFpmH!IFmqyx$_UG=O8#qG)9xADO6m6`l<%fY;v$ zvhJ@GQYC9OK&)};%vA){0gtCH!}roJorCWm)M~X37azAg-Xba@1R4!KJtAmPlfDA@ zk;8%%aiiDEZQK4*02Q{CyCVf2B}!YbK}!ptWC&EA+Evk$anv9@J$Q5;$MO0*fmNkv zj3G)KWwP54W?iee0{MAS+3_{ntG9=R@GYRr7&64sbCFoBc+4WJe9LH~w9wBig-WmZ zzFW>XFLe#4lG5v{(4m#<~ktEsW2U?}IW@^{8rk$nJ~dlnI_9IP=n2mQN|K z(}*;ZQY>fE#=GPT0Drdt%hNKOWg>)!OrFH~o-t&=l`W+q$y1|y)Aci6qcFb@FuJ+N zthcUe`Bu)(L?2EELi13{FU2e+cj!rs03$;71gi;26B^FXFfE-EQ9cdx(z%o_>u>&g zma;2O`WQW2Vmb?nU`@ys`aI=R#YV!Z literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Secret_door_128x64/frame_15.png b/assets/dolphin/external/L2_Secret_door_128x64/frame_15.png new file mode 100644 index 0000000000000000000000000000000000000000..6244074889e65942a79e6f658f1ad82e989cb88a GIT binary patch literal 1897 zcmV-v2bTDWP)ewA(h)M%0 z3)Z2kVA_eDJ6@^#tDx9^p&gnTtz-oD%-ggMf0N*|_SAa+$&B~1%-lf)MIYF)uO`8c z9e)LhGju;+{lhLIDEh#s$17|9-FEzlZph|m@wSVONT3d!)#^wejM!U+-8Fv3FK;i) zh9e|-XKeKjWUy_JO++k(47-TX?Oqjpw8N2|Z0$$0=1&Pwu(CWSb?UK?G$CbV^tSySSsl-+^rN_`jL(WOjbm^dvJ-6u#P)_4NxF zrBa_FS1xUrgV4a4bLvEt{!vY3Bp40GY}#K9w0=lDD5={zeuAWinYnQCsxc`;}!K+onR+D}s-NtzutI|8{-|y^r*RkVuWPprG_s0qZkT z7Dji{CwD&6KGiait^cn+r<++;KO!r3|5f`}!pV1+NO z{aK5Y0BS&1Axr-e89Wi9vT)a(@8=TPok8!P{w#cFuvKI!-nZ)u#E4<HQ_*T)+b1d^)s#D$_OlZ}#def2zJ>s;>0ea!9ffv!f8L3J#3Nd8^m&zr zP}`#C%$I)sDgrRrU2XsF$W9WT2(}HgQ`w!UeOA|5Nn&F#+w!a6JAy5+UmTXAds~J` zEUF5*v70N*v52XXRXSBUF!6tUueS(ITp-oDS3!@9jsKLJ^RUCBQ6DeeCzMg{zD) zLjvZrMJ5z8-~LGguz*LqNIf~Ae$nZm5_})*(F|a1K2ijZzzV@C=dG4NgixN_c53iZ zKjat70NF9|v8R9vY|xP){A2>G?)*w-P{!-+ExwWfez#`}H+$5o_+`hAb~bBQ)dtb& z;-3_OUiGu1R>ukr;M=1ykv-*oK&D?Z5oWa*ZTiEP==ibk+wC>blW z`{UEUkOA14+AnUjuruWX^=0?6;9LKh-)h_QEc`6|iHOquh%KfkD!xR%DZ!79m8=c& zq4)Vp0N!&#XTqX%tvy!;jC>{?}@NXot@zeN_l%zS;IDxPB~uQc(gL zwMFyz3Ib%QJv&B4$V6Xelcj4$$n2Cdk^$^J-|n^qP=ck@bemFDyKF~gN52fHBr#iv z2r7xy;J5bB44|B?`!nT5N@PDG&WZyRIvPg4WTz2(Wc73Kejmrfz6-E*5v5j?`q-7h z5=Zq%X%`7lYbPpW=UW4WiISFs{gY@ z%E)tT0o|5DC=Y55 zDiB$tGIAA3wh~~)#Ly1Tc6xS={I!0wqa^a=AAUto`aWvbn-eYdLwcNsTqRosUw*u;w{MUcQm}%#BPm?f1S4av jBvaPT`!kA&bRGWzWq*skP+kMC00000NkvXXu0mjfeO{Yi literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Secret_door_128x64/frame_16.png b/assets/dolphin/external/L2_Secret_door_128x64/frame_16.png new file mode 100644 index 0000000000000000000000000000000000000000..f1306c2eb4a149aad4d800fe64d0ef950c8ec0d5 GIT binary patch literal 1918 zcmV-^2Z8vBP)Wg zUzxEx8*~-S27jG!y4}BXZGL{mpKGo4SpQ}KrYs!@%!-)C^x`IV=IGd{;uR5}%^Tg@ zGec*u*m#ETOkp&s!Ymt$ve9Pl>g(bi={~X}S|6@6OXqAXK2Pmy&)n`KqI(k75OIjUI|5i-KyJB-v*yt~#{cC8g>0}Q@ynTmjVzYM!o9C-Ns7P!&Hiz(jL~EIvt9ojEIe%$6$} zzthOi*3gam2$_!Wok-}^8jmX?U)`Xp;X0jv()m|q1E%-;q{f=k>^&M#=~Kpd`OKNv zWKKo?M8j7_{i`r?3P5zDALY@(OukFp$=ZJs@u$%}J$~dAfas8d$*af1Zl=NK-Oaor z-B&iUjNtWE1YqKbDRVsAG<>f*>uTqB*1wZw*qDzZfJfCOvv*NOVe(mnnDrf3I=|BZW+Z5h`BVZJ zxn!bRRJRu$DBA{X&|e4B3m-N?3Y1TB8qY~ z`dOWi<}rDu>ni7aJC*rx9T^dB+XF<4Vw+=#G6Q!0ooHl@;#HB)`VrPRGNNKnVz?~< z49NQGI9BO8C4zsx>wNXJk-g8z=W%-iAa)u%rel`~Hpai}I$w2ObqcADm}~t)0!#vl z;Gsk$%Lvtc>S=+=qkW3L+Y_KVmic>puL4O>rDgv!lVOpl_72f}oec2fdiP`CE-+22 z^4MUV%-?Mo*}UpHBE7dIK&O^6Iee~e9+Opd;M`2V(lb;9$kzJ5Uf_-d$h!S(%v$kp z(7X%+9g&rmOb^OuM>@64CcX*GsDKhr_&k?`_vw_Es^HH~r^*SRPs$(U)$GMp8 zQREwb#qR?-Cq^d@^<+>rl3w=L5ukE5laD^4RL?r^alWka#KshV zRx^N-#eN+Dx*vF0w0T`s$M1EaT-C)#{YaOvkxbMV{J9cY2t zkI23Spd5R~y7+FNVLwNwoyTL~pNFgI?$wBDo|0Z|%uW_Sy}NerWRnQ~0~oIB*L(-? zO!v_qHF+5M_>~Z^QzXxtOtz+pxw9kU*nbq+HS#2Ls7Jl18%CR1(-b|MfYQiKAtIU0 z@zG6ScY~sIj$`qCmH||~s*TK+tAM8r|J?9a0#u7v&co5Q8mH>naejtM&vdr&dToCH zY>NP=tnDr^bBeb|%>Z~mMFM4ASXy4yKr`FuDL`>#8I`Zt87uk0O z{QTdz`BzWQny=~_>8W%uoH#$ydn%9iy@~{D1^-iMHw)}y#{rS8-C8~6{7PR}?rP`b zoL|=Y`addmYq|eGGMBme*a literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Secret_door_128x64/frame_17.png b/assets/dolphin/external/L2_Secret_door_128x64/frame_17.png new file mode 100644 index 0000000000000000000000000000000000000000..ed8d96bab11cdcdde1efdc162669fa9ac5911ee8 GIT binary patch literal 1882 zcmV-g2c`IlP)>mitID=)!YQzt=};hXoLG?^ zw)bvhjD3tT_I)2?jInLon3&Hd<~w|j?=ALyAKSKlyPgdkFS=SkKjBwrEZcAO9JcX~ z0Eoy~hX}L%StO_g-7efG0*`)LsB5SaIkSsnY=7JK7U61&aP{CbKNWO{E&DQ~e?&^6 z1wEStciQ=tKw(O*&P^f0zVCktfI;Yzp-KdtSQl3fzUmk2gU_blRh!ZMwr%6h9A{)u z7LBpid*>}u6?YH5I`5ABE4E+Cz|(joXGO({$NJe`9p`GnxBhqYwfEdxqw1O=-geMv zf$R=uzk7YW#m9+OcgEF%Z|8JY(WdO^%H|nEPyhs5)M(I1K~N3Y4r2DN7W~f6uabZ% z=TYqYUMIlpXalOGhzb$Lh^yw6xpf7)L9Zx4SK`qIu1SjkWBknXRt+LDtWtz4kzhse z8Nu!SS$N?tt^!Gb0qhb1(Ou-T&{ZUso<5~i*7rGPTLCOsO27dXBCzMo@!t;q^sl2x zw*99A&+uC1gqIR5G9svHVO3S#!I6TG&4=0c?iJsv@qA^)TNO(6?Tx9Xg(wKoxGT^J zy0YW(-Re1bd;LL_}ci7*-UDM}v>;)wB{7a?v;ye=nr~2%Nk}gh)2eP=wAr zGJ8kT$`M>z0W7@T8|`L>ND)~7vkSk-o_)`hDjJ8)gN|_*0!(;8&uRk(Cbi5)Ca0{> zM1Q-d2(O6sQVR1UOdEG2z!>Y)V8RPXb}I13QAohrF+}sq>IXK*AHLm@027%S*k=kn zsZ|Tb?0t3TM>U7y1KMHNOMD0Df=$|crT__*Romzb^R2t{I~BjR&5o1odTRoVQQNhu zxUhYA-Cy!Qu2#f?( zBFuhhuad(R5@f;3`a}qcbLS!u;T_SU${5>5U`yp4$uhs9zES${j(VB@p4~?gV2nBm zCe-|1Jym!

    NgTqtDDYR>z?IOMePpg^C1wCyCjnvR}NKp_9*#AgT!-qX24E?zLt8 zj-(HdG?g8n%~SQkv+iQ7`zY1Xe-3;6`--yGQ>$^@&ae?1U59L9*Oh z0X(R?DGR=}=M|QfaiX!50x>FVncb}afAbw6LKb}6&fERfanR=cT_FSW6YtgFZ=3Uf z3VkY+4>B3lqZclAmKLIqH7N6$pPsVN_xq>M9gC3#B&zsl|EQg+5cWN*?9~8zZO~5U zS7hfAH9&TBvXYAJY~QK^cu@K)wM6zjssqp$c2apn4WLx|o@}BVvw%ba@`zM9#v$>n zEpNQ&B;bt^`S)e_?kS)Oc4SiJQdxNHm)T}@2}74KS$cTHcYq3%Jg$N*EbH$Dz`j>f znrF{zr_=`j9RU#R2-WR-6i5Q^qmN=IQZM`bFaeG%!0N?gKMb}Y@^*--1WG$rjCsh^ zqJzlBEsH<~MLJn~Rui!2_I)QPM+kL9X|i!2p8{rH#-A~G0a#Ul&c`FqwsOSlAhPjN zB=Zb(chah{G5GAb#lXI@RmQy<-s&-AjL-kE!b(I15&PtteUQQS5s){t6URD)enfQSUDAIjWTC}mXFyfcRwx=uxs*-;W31T5%Q zU88YT5@dFx_eT}LJLOEg7%LQXDqyepvJR?OZw$PitpJw5RnmCxal9^~LUz9U10`a# UP{|Ze;s5{u07*qoM6N<$f?@!a9smFU literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Secret_door_128x64/frame_18.png b/assets/dolphin/external/L2_Secret_door_128x64/frame_18.png new file mode 100644 index 0000000000000000000000000000000000000000..e1df3ea495e94ea5d0336d3aba41f02af8dcca5c GIT binary patch literal 1907 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Secret_door_128x64/frame_19.png b/assets/dolphin/external/L2_Secret_door_128x64/frame_19.png new file mode 100644 index 0000000000000000000000000000000000000000..d231a656181d21d77dfc168ace0e42aaffd02137 GIT binary patch literal 1974 zcmV;n2TAyeP) zcK2Vx*@Bo{$ALSO;10T1elUHs?`5*Rn%T>D0(j+UaVq#8olLwk304OGh`(&F5m714 zY%6^x`x8yBjIrQnV`{-yGQrAPztX1+m7ub6Qo&~YA1y4RtH`m6O~aXG;cmK*e2SiX zRG7&7Sx$ffJwP|hj!@E*lHd$fgJkIXtC~tL_v$!eikNAU{=V;0&YpSa(8-Q}2xjjS zg3lT}gzb$z?=V#a-0v3#p<;m1;>tuYd)>vcIX@-%Q8JpZQ7l&YOBr`GN`P`Injmwg zQX1aQPWpFu@GJh<8d28&HkqoOsAQY$iy5zil>}U{vX?OeqQeB$&AR*KcjsH>x8I)^ z4tAkR0!~;-1U8O#D!l=ofIEWUO~R_p6?VK|?;haj!m}i>1w=us^as^N&jf!Zx=CoV zxCXS@DI*a~@0E+z(V=Kn=PPA_(W)Bn1b_AEKf*kfGMF(T)AjbZZ5uzU7mpKUPO-W1 zMA&b3Qf6s>b?{gDQJqIUtLXpocmHe6E3ev&s#Dp#DoE(_-6!AswZb0^cGjcdzDK3g z#@>Gyz$n(E^W0C=&2F^ie=8be3w+wiKdTpsug2O=lef=RwSPsepT&yg^;n(& z29*TpfFE0(2#PJdjSg1OeYQT;OJTo-C03PO=7X1gUjxsADhW8|okh6{lr2X4M|y#k zbYJxs*O>Ja*}nbKJv#vmY$F(DGuhchz^rE*+X>bc!Ds8p)>AEQOy{x$@D8YFW8}5D zOWSI5x{Pq=$&c36(WN?9CcqfAw^<5V@-SIe`yzIkZSYnFzspvKf`2sw7&5A>=qY`* z5m^tCF~Us4r`9lIM>TU@TemX+qDuXlKH8W`1eIW`g3o-P4%|Um`&{-SXclbqJISB| zs%VkTV;jPJsnYz)liw)==&ypbe>Vd#RO(r#Uo8!0Z>B6LyDO8x+sl}y(ieOj129wq z9Lcd08Z}BtpY|P3nM#7p_R*e#CG=62G@GZr2|N=K#e3N1RckXQf+`K2fMAkzPClEb z3Z-l7v3VYu00ti@47NSTWT|W@O9XGz%b-7KBV&+=_Rp@)`!4=Hz*C^IoHcQNlcOLi zJ6)_i@f}&V_6Uz<0Bpn*)kmemGeiX)COPB8M~y1GEmZJG2EgFshTw^>pvnqedz~)7 z>tocIR+-?D31DEsXXB{jfr+FNAxaP&!@wR1z6vU{i!xF286KSg9=H)6SQ40?qT`5d zsy>;`YVb40h~)e_v*3^M;2wY>qHljf*hZl&21)6o4(h78W`d78-Q-|Q@l^@HU;*(B zpQ22rYoFLKs+)#-p3UX$%rbySwE_Lg1i&j_cD1_8zcKpn2@oo|s`eh)14O4{j9RU} zEBJoy&L`>_SQCuDBfF1GfHCrVnw^+V@L8f^V;MciG2bUtgWvVn!R!3Uyn_I+3Q#&qvpXR=z`HSYeuWVM?X3x@1I-Aepey9 z0M#p(m-U#X;K&y?M>anuP;|XSyGrN%>>CrH60~mc*?0CEZJ<9H)2Q*R@$KmN-(PzT zJQ94gHADMd-=fYwD-EizVT~lN=PMIH!FR(3`##gvcbDUFDN!P1tb%fttEjBa;14E21ePc&N&XiMz#1tW_ra)apa-7jYRSMLt7Jhx{W$(H znWzu)V_%&BjPfzKi~(lv;HKN@8>*$InvB)k!B;ndW9T}H!S$aiDZI@pvPRU)RduV0 zrFsR{_HgL`# zKiGZwaW$ga7`-WDa!N1zT(O48&dRx1PvgID+u#2yST#Iq!)G1r=tcEg$y9=c>iaz|5hhKl$4YKm;t;u&pNk-*{jI0 z=c|BwjK9m*z*(mhovUCUX~HV>+nFm1mPb`STT?bq2bBZ;2XF0HlYkc%w*UYD07*qo IM6N<$g8wtl{r~^~ literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Secret_door_128x64/frame_2.png b/assets/dolphin/external/L2_Secret_door_128x64/frame_2.png new file mode 100644 index 0000000000000000000000000000000000000000..e2a864c76259bfec69678932d7d9321cf8279f41 GIT binary patch literal 1636 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 XJs`E4X;N8O00000NkvXXu0mjf8}P7( literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Secret_door_128x64/frame_21.png b/assets/dolphin/external/L2_Secret_door_128x64/frame_21.png new file mode 100644 index 0000000000000000000000000000000000000000..0d407de64bfc2072706a182f61cf9c0aa101cd6a GIT binary patch literal 1894 zcmV-s2buVZP)lgDWXM^mm@PS!#j4`(H9{@y- z00QIS-GXoBC@>%!_E}Xb0QjJd+C@AAo~|7~^3aZuBQw9VpUR%AfcYm{C|hjb_n(b; z^lu%zd+@d2M+YC-?PUCnd@{oIb6x%<@wq70q|f~SYZq?fp=_KOck zX8c`5{De&)YVI@`>_Sn7MvbB#li%98CuY80hY5bR&raWv?N7_sHPAYzjn6Pb)E{`x zGJ3Oe*NjK&Wo+01NeBQD4+zMhZ7Q?1JUzSSQ9zJ&uL?fOaF1!Tbvmp`0l+$DDb6z* z9ZWPA(dp4W!7KyJ2%FWYxwiHh5a>DXi=fP5+aF{GtH?wEJEAwxd$=O_kzd|EZNRnv z^4&JpX#Fw9*!GDXpyrJc54{nfY$mKe{40Z>om-_0i9Ty%3IKZHDhNbSlj8xw13@Tu+DiKL6UX23=m}eY!IxLtn05dqAU=W0U+8yBnaK?+IIa; zaPAa*#C)hkvi>nixbM!3ppGEJ)#lJ^Sfr_mkZlicTVGy|~0Fon%(tD~90 z(jQ`k6=h+CT-kFVU&i?VEKUPJ-9Jbgs3dsHS_JhqIGBF$Ie+Lpej)f&S>5n zW6OePx9Wd2<55C*tPqvHs1$tpc~=0490>&PfV)SsveC@;fqo+}Wq`gt07NQ4$T9|H zzt!#1?5bM^3NqRY(zW{DyfGdG0Fev`Sq+Uk>+H6`__nga%o;+rl-T|Ldj2#3h`1Xf z8>umHqlo5ZL5uWf8%&SZyEh2y!?HgNI$NUVh;8I-+Kozz zwxgd}{l@}ej5Qz_BL;%j=}>N$_A@4{tQ|4NqX94m4+3;RDAV;^IVc+lbl~Xssvd&X zMdO|?^7(iGh)BSqI%sK82idNLbfP}Nu3yPcq@UGwuOxXkytOZV6SzW{0CxLayN-O< zW1e)Hvgngx2djt9;Z+P^>!i(SogpZIsJZg&$lOkY8I3c+pApLxuVa8Q;x}XHxy_J@ zuxyg)v6-)R^*$o8<2Fft+1R`Mc(K?_N7NjF0L_VNJWKs(4)UA*v~%tH5mPV?@__Al9>#(Jp-a$uXN}+9(9XeC-@ndsv>h=2Y@3) zvjQpw%(&KLgOBDRzdX?E`AA;D08#xz6z}~$L#mnIWd?7)=xcc_0Bk3ZvO|CObk77r z#00FK2Ldb1?PA+b{7a#$1$FLrJi50toxdLUZUx?4=7y2^$Gi`SZYLB7o(OIhqnO&zk?*XE@-m@(DXdf&)K~VfP0EpPk zXYSK8N5xWj7JTGu27;(OJpM({j8w8Y>jk2Kc-KSy+m1uJPMSt*-=lr~`%eHMYVN#o zq?=J~=7ZKp!L_zLmdY5Q(nGus09mk+el#B4v8(fSKs?ZS!D6nB+9nD<(t8~MX0lSR zfifz)`c-|4SO@_jdKX~lGQl6?g$%%mcerTW)?_U!`ezHS2ng(zl@0#3{jom_&~-Jd z>r8MXDz|#kEkm#4*+uH`jaR$$WbDnEA;59mU>g?B}*M|14k@t<^(gf|2R54A22VCQ$>LW&bR=G8T3J z=g>%OqcYzr4#AM{-0Ux+z_&U=u6Jpcdz07*qoM6N<$f_-9@Gynhq literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Secret_door_128x64/frame_22.png b/assets/dolphin/external/L2_Secret_door_128x64/frame_22.png new file mode 100644 index 0000000000000000000000000000000000000000..f53ee0588351daa326d75d7f83454511592676a1 GIT binary patch literal 1923 zcmV-}2YmR6P)k3up(7@vD6^NSQZ=ScdHJ`~oBIQ=S_Y|@*Ys1fEg2`Gxg-V0BQ!XvsBLUs`G3PPE zuvuikG2^j8o5DoHXW8CCCV=IhX9mK%IDaia7{ykU)!(!Gso-|IeK&!f1#ljt^ll*R z0Ga7sGry{D%5Hr=cRkPrD+#DD69`kNWXxI4uLfau%_#;D`TG>@S|tHD;03}YI?kU& zH3&r(_ke3g_E%WoIF9wGPK=<9vJRGITE_tqtDrqohnKOdTYmH&SU~7;d}eII^?$qx zoT5Q52(U+B<7l|0!vZ?=8P4BrIICsA+jlfyuk+a5xCItWNOc+A9s4Tu+tp#|+zkTD z!JS6WlGW$)U{T{m(vCwS!_LALGXJbz#6yb#JUYj~|&wSS`4cQHLb{QCgYkW#?1SpL!DYjGCvvIN($=2SC{HEdP7 zkDP}#>9b+`vz}TOZP2o(Gk^zlF#E2@u;D^v9op>$rs*DSkM@UWVX>MQ6u;&kXLK7) zc!9uVJ%$jlL&M**&N|!qm6BM!QCRlBRR9PofgrzI&b8)y4WbutXF9*q0D3`Cne+4> zAOoR(sde~W!#Zf@d_5n_i(ZRS=(C)!%Ye0~Dd)c}10e7~Er!rznehrEbQ(TyFV%Id zX0E!{TlWASET?(3k|7&NT7Js;%+9vsHf81cmL-5emjOM7Wg((Mk;l@ab)fuI=Hs(d zMwre_-(c#w3Ik|l*dUYL0?UDwvVe0fyLT8R(wFHAF3SKK88*-|QB#KW=>^zM=68Au zy#}5^^}LWZj^j;l0a>shup&PL76`g5Sk8=W)N58UKeN8K7psKmbGmc@ti|7&vASLf z3X2hBOk_N|?bdv+ADPBwdjJL&@fM9{`T`5t+SR;?5urUGHo`IReO z^g9cNk1hd%Y!k??k&(Rq9NiXnuZ~R4`aR?PwLT~SwDYiY*T~XsHxN)q$N5?3MYj+y z_`aT>Py(V1kX3%7MQvYvYx=Ry;@SNb#P62eoO#Z$ad7$og2p{ZK|-=jxLYw?z%=yzVL`J zd7vHL-d$Ti_3zalU_xdG*1Hnz?_d9&z^FsMD!i+k^`?zIC+zUnnPwZkuISfe58Pn( zWycQA&i<&u!||ygsJx((&(BIKX4@A7WH-(hsBXZzVpZ0O0}u)^;OpNGOp@;%Q?I9xVL8o!CLE^0mLwdDd&69-enTBD|>iT&X3ls z%AM_e)Y<-@MpX^-m zQjamlImQ_0d5$s0IF4h?@5gccK1XSLeQ&O7Yt=<?*|fVVlYKvq zgEK${7}Z`4%NU`9w>y7#8Srdia04K6mTDyap2@}ysE6`2=XcAIMf5y6qw1LmMaC8I0JGsYO7NMQHCa8onmkK;(oLewZl)b+p0k&zX*ylGBYxwb8lVNAK3NtlhNuG=r~=; zcpTfkhbTCf-UL?JK@Fftu02FsqVB5sp1zfg{|4}!w!8|?0wJU34kOI*x&G=hu+rdF zbs(}n3g^rprPLhf=J1TJFV-NXY^btXz+I7fS2=&m_TcSR(aFyFH?ejHfM=+EUdd3; zG%t{%Tg0yNaCPQ;d&B1zYV*s^@AvbH06?RWs&PC6ZqSn#>Af=utZg0n3U=Lq=KO7c z4)zpSMc%l2Sj_pXQC#f}Dvh7DtqL2Qo{Vfy)uz*~yDR`u5jHYta3vT#hkMUapJzwK z{wtlYI+EGq&BegNb(fU@i|DXeub?6-0g=2)S-9HyOpld|rIWFl!W6eLfHf*=-X?4Y z?_vaeHs>p4;VS2QOjzwz<{bQ~=)SxMK-kT(1Kb%1D`i2+o(?Kyg~{mZ#zeR+0Hy)f zxy;cR2r9e)VnG!hzDl80RGeQag`SQG_FQfafT0FHygdM>V$V7AJHi{Y4$T_5RYs`LbGF}%aaJ43D!vi`WB4z3SO%;@2?RTLe(q}g zDjAP!J-Za0%G_J7fz$vvROOJPHC13kg!1h?MyFn*X~#0>Gek{n#9E#mj3vJw029%s zygAI_ouG&ulGW}=j}iplJP#~5nM@YM7GEm?h^QoZQH`HCK7UtZ+iOar1I@AxW$ws) zw8`4)1tj(pQO_~PgBW0fQEZl3b}r6W89~V=9E-}=Y7dbyUL>3Sj-QWVfQcycS$0m~ zL9uQ>0_#b#fXZ$Yp3RZ|XkC=mOm5`-^E{u&02Wzuh0n7r?=Zq#mu0t=%NU?i_oM7) zO(oik&mPYJ7BlOj4JozQqQeMt-CR@2 zFWU!>J^mJ;Alj&Xt}=mFGk0T`^LOF(0bsMsUh{A~J6LpnwQN+d;{1rAI{L?W;H%&Y zSp7#Z7GRBEB(u|jv$<-68QHJC55b^&EdyYKhA(+0W?hc5EOI_F!JKF7Jj!8xfcG8u z60r~V5UAH{G$T0}EU#n$biUVRSH>dedx6s_4eTc2=~l0ZOrLti{NKNcdlOjXM~=tu zk@F*6-F)%ndMsw2vtEJOtvdfWj&}!uMKwckjk;F7df{PbWx;DgJtt;1Oj({BZ~V1A zfQJe_vjf^`gL<``4B!O|D{GOgDClsn|5N}(VDAbl9eAcA3nJyZ=Urv(b5Bp!TUed{ zR1csaatgcmGiQ1!Cuta4@R>fPIwq`=zi z%^GDAuMqWPX~7%RBa6Eu9+w%qqYPs!`4AB%HrO0^jGa?*JoEc^oIayRuindgf~9M|MR;Io_+n zQ;qXSc}`ft%%YH)3e}1~dcS(}?{t3kG*wAM*1kH&QUdt?l|@wyj0jL2Tj5UGSN0%S z@mwK+?;Fkc-c^+=5@1!!o}-a!e1+7Q$5`>$#dLygOnJM^o*c)A=HO<(``b-hPBg*fX z4pd)dbkshIP^>>joxh5-D@ezRydIJ+RRCq6pU9qh5-6kCb3bO*KEhqjU#&QqI%L4P zUTKd(;9|VIG2nCmoArqntLC{Q=a1GJo`5rCR0P|m03K+(N)s%?@i|izL`6D(fdo6@ zeXvyl{De#myhAE=LAE*S9V300w2K z`t6aW3RO$kyJ|F9n_eQUaz0y6M((J6w4Tcq;KP843fK3?F|wKf<&_b$w_xed9jO!ZvF|WYhNV06T!rQLxQZgi!>+ zjYl=P()p@i=6v7x=M<$W?J70EPo{v@J_|i1^~w=QjBG9(ixp-{U7|5pkSIb{$NVG#(D~-bNY*_t z_xH?;Hbfg7p&I$tR@sr+Y5f&pAC&^o5vq@eN)OQ{Ga}f1MuO4WAycxb7Q){@uK)~@ z({Uq}V%Rv?E}E0gk52LJRuqmnXK$K#L=C`DiCSMPw5I1X z$oghuviB9|d%JxfHg?wbu|=T4p8(YR9{w|@MRM;8(a5&wwBW~M^RYE#Dr93m_87=E zm$jLRe0BP_~)Ps%Z2!MdLi828hse0@&gFY)i5WU#gs=T^qEcG))qRbMDIh~{Er_&@&r zV--L}eXo4_epw->=84*=zLk$S*&6Jg*<|k?rvNJI@mQvSjz&pk?Ufy)@M&R1nn5!7 z^*xFJ>cdKFHECCQxcAto#?IzuxqpU8ejI=AN&Xt0V1;3;z>arJLjCg$KJuditSZTO zAv?yt*!gfLR?Xw3omJ63;+YaeW1_9<2$mfsqp`8=j{RpJm%J^<@!!7-Sn;Ln3S`eKVq>@Et{m+VIgu_w-{Ck$s4naehz#=-U88R6k_Vzmr_Yas2zgj8O}X zK2EGQ1d&zODo06Ja~@5k{9^K|z0+x{$fVk_HLfZSG1&a?@-c9fiRf%E-S(c@ sFcLjT zda;iÎi$8n4ZkH=$7S@qe%l;L?-0ou0u93y|0ESG&}=|jvpkhNfOnGM9qHb(L(OOXC=;Dk=PW}4C?nW#tNyTYmfly9V5jp}?8W)sxp?zD z-PU|1n73z}0&gRkY;a`~oPlbzR_(Q#%3tyWN6cwBK`@cji0nl^J(X3CK*$B2(ke zY!t8J{ER(wk1UJdr%X2kaCsQx=a;~2-S2GJ(j8?4r7N2WvWNBV$gi%q%WgcM(oex% zWQm~OvmTW7bh7-bonIwkR_6{qXxFm`-~*pd^HOyc5fL)|L3Yy}=kG+7gj1IHz&a~s z#KUvmaDE7i3{)GAiUB?>B%tt-HN&qAKZWUH(!2W;<7L|opM?R8tCGw-brkdg{UC$ms4XA~@ z`n?MoBJg{h`>x36XU7iDVQ1kuj`1BeU1QkI2#!a_S2w40D#u~ubeyn?)yPL3O7{+8 zcsjl_&h8kX=2V(P?@&;e)gM$Ft}f0fKWAkFrT4EU&?5wnbsSu&U(wtQ5zdPI9SvU; z^{;|^3m7B+w|{=Wf(*H=$mf)-GQz5@D-pjM-Mi;M|9gO{k_n3pU39X9M^Y>5J7wS% zw~Z*stUd5~csAg>GyyWs-BnYodaXmN98kr+UFkm3%TT0N1k04-=iG<@9f#~jw#--u z;T>$nS+8)uw@+(7*({^x{6&BrHS$bUHtNl(W`uV}zCx$Dm2-~cIL>I~&W2YP=Q!^r zQ(wC^ewXvB*=ou8l^)=Vh@6~l6!9I-J)`04>WP}UtS%n=l2+aT=={gy&!4XOz~(=8 z@G}A&GVFFfj%725)r0NI0JkJS&5FwOvRU~u7Ro0E<&xm-d zH&JJh%^tVS0Nzz;f@}DEH|MjSOyih6*lg|Z=#0t=%AlOWZGQ<|X^?QfPE0rN1(+g66gaSOqtFmQy&YD#!KLVJDp*WekKho^k#d|0n@0r+RfXsPjyc+9`TWj!oS?4QxM79C_cmkl)RYUlW1jyGpv?Y2n zEOKS_-S!sXfzzZV(`oE>pmIfIvZykEdU>5_y^kb7bTveojPq6c#WkXFemAqy{Jit3 zsAMqk=uRqDOkB+dq?Tijym-SVg z&vs;pV%{P?`5G8SedN$?3ifPKYgpro2;Oy2>9yuO$3Bq&mgBA3>K(rK*_)5+M{8y5 zi0=HAIr#mfu7P~z!}ly_jWN1u-_sZM3lVYEou74CWas^S1Oc*+ALF+J_%q66y86mE z+hh@8%0$hg(#iI#%*C>oZ3456%^0=%Y+VDDF7M85>1Ewsw0n)EXJ@D3vq*Bw44@*O z@75MndY$hvN}>Z~oz?3l%Bd`bGHfTl=YLbi3l4mD*$kjyZ3+)4iV$`#N0xVIj?g7R zRDM{Wr<=)Qm9kqkfnLNb=Vz{V98nf2*p4i`8qadsxmiM5=b)~fmnMMK+>N2rrk6@l zCqGqpozWH9-kq22JF@UH)1?Vu)p#7QToJxR|G^iq!^;%A<^Z>k`s{gAE;fl;|@S3L*pn7Nus%!VU z`0On}=~v|e_p!ra(Ru7RGt^~$p5HHh4P@u2=af_O3aXjRtkWTvwLWIBv$H@{W?s?* ztT5WUnsp*oUF~){-?~B~ysms_ufgjnz1s=Ezf7Ig!J>XEGekP8&bRcg%CmjDN&v2h zPsH>86&yJ#Yt$-xtzt7RJo!j&cLw0*mUVx#K%^*x9h>MxHA+({XIcfW-15 zH=Q14cAA-;=V@kU$8p%ecs4M`@HviqoabrBas0TdVDeCQ^d8g8u9d8vsP<2JZ}%Dw z`AWyxP2{JD=I9V20Q# zzA__~2pmdP7Oof^$&2JZ$@$FA3}bKcotRIP7aguU8Un`btK~y7#8DKVK_!^Eu$L%6QG{y||X;Q_LoIhKR z*m{_3wnsC2=JAcny)djpY_Zv~8Mr4Y0R|!>L`pI;>?T5| zBfbhdobP|eu`Aag5nuqDyUJlJL{RZt&SwsrasCylU9o-WY!P5U%?4hfLWC7t?v8xy z$U9CS_jfyjT*|0LKmIOYMX*`yU_W-(=IrhT z?r^@hnVl9ofr7qZwrwMTH-e>iT+|)7Lf6&KXTZQ2&nlf&CO(eie*)~Pr_~1RtZSJb zuWywEccuNvR+dS#Zwh{1xfX+82~!Q7!4;8TIe6F6@DArIP;BVPSuO!8^P;)gd{urG zIkV;G?#PcE$$F>C1||KU=eg$;z!t>jsl@Q}vNYB~HNr%oeaR5NxW`t0yE5J zikZlcHqHO6kl?PPoN?D0&f7Uy7?R``RV2GkV8q4~JT^V6!4VApv zXkW$SGXSEB@TeRNWZ8nfb9LjF$@Ak;d5iS0bx<8|Bfvn_y^o(WuvE+5+mZffPZ6v! z&Poc-W%GZ?e=7mZ_K?8r&+i0Y4$e_XpwxWS)sYq1Ps+-C5 zuS9;9)r`URRv&?eBE_UN$>Uvg_*!FmN2&ucIq+ z#(cb2k8(R|e)OKdswFA4>k~@=cF$3XP?1Ggmjs=<&qz=a4DH2dAC&>H&W}InB$gS} zjNs*DL|AoxC8v5P!^dWTXp^ev__H5ZfU>L8g3_omyQ2)?b*c7$Tn4C8yOJ8QZ|L?3 zUQgC_N5Dq1<9r2`T_14@sG{1>tK>wDnj$kQJ=uGe(~(@W7&4gN*AoEIjgshn)@{$~ zR3jZDUL~RmmJvRV0IOhiRZYKGZe|)qW`m-yS_d2Zhzt-pr)rqk2C*ZazaP&!4#N4n zoWBdNKLyMpJ018r8Hp4dP!Gypjr{CeK=%Ha|5^f6n^tWud(JknLkKk`<2nyu!0?tAEEm$X0pylA+(#x&B!zEAOAanS0ak0S>U$=e4go4 zNub!NMEno>_H|17f^$&!`0k0U_kY&L(#K}abd6$!?i zA#L;T7@g$H1M5-j1qaR1qC9&0hK?kik3FJH3kd zC<9bodKYL*HV^fzo>IIl%qYtQSow51yD)S%I-G4{l~QLjfSQs$vYOnf_X;vyW)E)y zm7^HW_s(TqesyJO5_Pk#TwTwOZw_63NUXTuQXdxCaKmDWg0eT zH?r>yQv_HwbT)FT_jWp8Nz;+O>b>sRRX7gvmqChi)kP}?bdENazMamG^swHcL!NAH z(b#Shc>BNA24xqp6Q51RijFHYKt=-9o=ARnKZB^3F@yg%(ANq7CwjJujw3o<`3~o2 zJwVlKAS&l$K%l-=VBPokU+Cnn;Mp#CXH{ODkfpDfPvd@qE_ zzjf@}gRlMG9egzJ%!yyYOXO1~v=bGEB1N!mdcKv-+KJskuO6~a^8c0pkK6 zZ_d>u*m2^oAaR7@=TDVwNX|SPo`P6XArky1zVXD*)D6-4G+s08L=2B|wO1c7XEXg) zu{+1F_+{-yYB)oZ*Q~_a(~3?xvWrwBLxx>M7~O6y_Ad6YE-c61NDQ9%q##oiWX6^X&1bsHqbuL27d>l!()GE z2a(x%@9C+mbu38Rh!$Yy^Q6>DWcTJ@8T^RQl{7T^XXki-8PvI`3<(B2xkpN($E^lq zRrb4q_6ilx84~ER-h6)%U_AUS5=0elh6tnmw@J{AsOqm2qFEvw$1!&e%%HManN*?Q zd$mDjJ>L|3T}PogDrvxyz}w%CKMxRLW`jK^Dsy-$F~;pCLl-jhcr}t2d}|-!O}MkT zDpqW;4Voo^wQuLIqy@B*-*)m@?Ndzyh&-MR_-&vEwhi71Ug{KG| zX`ySKSJfQI7m+`EaVY@?b`il#3*UP3qjhS#D;VRyf&c^EM97Tabpop=-UhWo2-{vE z$fY$v1dj-|4+H9p@+2Em^|!0g^n9`bX89eh`D31MCcpq%un7KsoFOU-ZQog$?Yd>K zHKQ@eSF|UPXm=ohNc?4q?&HC$tfSjP#>dVAGoxbhRF6s?aY_) zdTRoR_*MJ>I?*=B8W9n6A3LvZ&z?@yKg#PYC9ydksQ?2J31;iu3=t|mdHtfAfwk!$ z-4FTyNCoh~h#?|>ZD8gES$h!)qJEi^pCyq8s}zq^0Fm5@LwT~g!C5tllp=HT8HuXv zL<;eKJ!kdtOTluw_l#Bj8eT=tP`Mps3P6_T82K{=7*sRpelx+3$W+CnY5-Ibv(4|YZ*67Tb-!%z z(KzH6TUT^Ve1rm6Nk=Deri$Nz>dBu~1~z{5*SI|a2J9)cf!439SF05twXfdEX8jlm zEHbx?+ZTb+h9F5@$(Qz7w%tA(s>Bp23L3Mikx4fq4^aS{20G;(DG3X%R|i;I*_>}l zl3kl64O5(3D*!{c#Ow4jV;cq8+KW!Ar#MU%JjEI9k0ihjXcEkhNAFpXSnyd|L$?yo zhcV`VMSzvmz+@dgM<*?l7W8{Bu+jC_QxfantqI^2M_zlC2vP7eu&0vAI8p>XJ_@MU z7p>#A1c>hQ`u=bGu{@06-LZ!)j|fbz*_k9N5!%A4niu@zc*u7F z6=Wqp6I@TqZ9i{bZO2P#=y$JQ6nv3~d=Rt(uYk&KCbMgmrs`LeniyE2sM`mYQ$Qvs z?HG$}At3wSMxrZJ)&A(;v9@UbiVcKY)&K|@=|uf)O@PK(RhS8EwP@8obbNln@E8Ix zcpKma)4PA8`?-fsrJI2_|7+#LjTKzEDc%(e^+3jWon+k;JQ_VqZ9KmTC_`>;c!i>13a4f%wunj-1{r3C*#xbD;qq7YVbW0WastTI~2gCA8V`p-^LAevUyys?A@EaMnv_h o_ihEKk}2w!-Lnk5eHv~50EUc~c&+1cLjV8(07*qoM6N<$f+m2AUjP6A literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Secret_door_128x64/frame_28.png b/assets/dolphin/external/L2_Secret_door_128x64/frame_28.png new file mode 100644 index 0000000000000000000000000000000000000000..b16dda91d5811ca36993ea8783a19934068ea27c GIT binary patch literal 1969 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Secret_door_128x64/frame_30.png b/assets/dolphin/external/L2_Secret_door_128x64/frame_30.png new file mode 100644 index 0000000000000000000000000000000000000000..08074849841151bf7e31c917e604a6f103c05bca GIT binary patch literal 2028 zcmV-P)t2Cq)Mc?8rJO4lgBX zuQ}%&V~p#%#{b)I=(hDfd$s0VZDWkm+!YzR5n03P!=A}@PnS!FV+`2Fo;^&diLjY# z6sJf(P5y6zcI1WU&O{y}MsUjG-w_E;V(medqZX(%#GZyfY&F23WewK&DzfKAi$Q1tKf!z;B2M zULo3rtYIMH7fPQ4WQ@Qvh6({|{ZBg@Mo%Lr!BU8|_G1mSI!T421?0W^i9J;jHN+>{2!hV{6{RBM>BJoo0Flp%<7i!!NtK-uX3qKbWss!+Rtr?CYt+Ac zQ{6{FDQARycFNHwBXm+i`-%3ALY|@?NFknbqMl^6oh-;Q1u8Q_M!NJ~?U`&ZRq~<{ zA`29s7 zk!RkT)^ivEJVYV{6ZDQMUCLQC0=ADG;-yUNy_}oP*_jfadc(T}$T28i-bKDdN4Vc5 zK8sWHO3XRGBEWicVu3MUKR*6+@}tG<37p`;TIZpw*(xlBu&|d2!#k*wFVlx^i>|A& z=36f`*JZ5y3JtFWgs2Mo%66;p>r96l*95H*A(&Ts-n!$>)v@e^6Dj&UL<&VDi(aEc zmZ+R^UYY{f0QuZ2p4hxB}1WkWR7dPP*! zQ!|F+DOLAHd$FQemiP>56*9XcfP|dyu^KdSQUH{vn0IA`h~~RtxmIfNBKr5UIE$8$zcNM!sIBLSlx9lir(wNB$V#yMPF}UF1t4vjv`9 z-wO8@@hl2>1X|BOg?tSrODO`#2`wWTBTjInc0g%L8$s0LQQe~D_4Y3NTYap4rw}#7 z2P2;;a^1_9Tu}E}ZJ857cB6>wOpe#cj6u2DGByicMiM1|&dI+Gtb}*H_&X5I@yy6~ z)|GQQWWx@7W-Aff&^2Y!*fYn(}p2_W9 z$Ql!qBU_(jQXs9s}8D0%PXC@N zu9JL}wyYy7bBf4O`1{YpBSz3kTBpKT7(~^+hj)`7ktcGZiKY+ub+A5Hzq@)xSBXgI z-}!98n(OX@kLgSrLgX`mb)`QGM~iiaey@0ncw*#8XubF3^x8j#{D>p1e%jHW$m35z zp1;vC5Z$wW8F@3t+jX+A_P2|OaUyw8l>hOA5Hx^2uR?}V`I67|=>UyqXL__w#=ub> zvHST!hyJ`{XJlW<2p+nFR@@0xk2SKNme;EID@AP7&={;J7F}Dv_tK^}7t+T|4WK7J zbquVKU0I`Q1XfBqo%bU$M#*PI{f`KM^3^Ht?}DcQS!-m-M`>i;qi5E96+^M=4J$RG zzG&aD2Jkk5gzQEnk7dC&?a3j>=txj0LbEcn(~b6G`C@Z;*N2=~TQw?((0$j&rP8AY zA(f>qUH6Gb0G5O0J-Z_LqR>1b*;!G`jNQ9lUt{buH8TkPTngNU=+TC)?J3Y|)Au{@ z3b`(wWyqg%zW$FODWvoU-KP6@g>a-G_Npqwg6x;^{A^^{0ZTtw@~`Xq`L}^61hK^} zi|*Q>H6N103!QRK6amoM6{BYBq4}bQSs^!710btso6`!E&pN=Oi;grX+{-zT=X=JD za;Y2_xmxpNpo_i}*9TLkhwl)Q93M>=5kr)7N}05bmIosFWIsfLZfDO?e-C$%{t^Kq zAs-=|=OUpU&ClAA0WuOQH2|8o>iLoV-bK2+nm-BCf-!+38>^0u$Y+hs7(rHfQjSO@ z$Qa*U*O&hfpcN~Mx0r{h~Qi zdY6_F^~)GRBz;&O{~L|qi4?ENVR_Bkgq}w$nHfU_(x{>#{d5G7`>v9&WxnJ3(2Gmu zYUOAFkpeZTXdVT{2++0000< KMNUMnLSTZsm)8OS literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Secret_door_128x64/frame_31.png b/assets/dolphin/external/L2_Secret_door_128x64/frame_31.png new file mode 100644 index 0000000000000000000000000000000000000000..d4dfbce33f3b5c7aa34e16d5b3b7c855a3741c71 GIT binary patch literal 1922 zcmV-|2YvX7P)4V~^Od12cB9Hb(8_J~C};t>w-vQVzggS?nVH z1o^Y}&w?QyA?*N}Nf;;C74c6f!A^Uuz044g=1V)k43H5gNPn{uWX3HePrQdq=Y&-l z_3#}|Ajdz2oeI!e3;WhusWo5T!_`iq(KkWj z^8U=YD^iGC1E7hc=Q5|yc&Nl`MPSyl!sZ?GWQq42E$skI6j>FkbcPuMRylzjYX_pW zWz;CUPgYTl+Fgt>oix2+vX3QkdQ7Y0AAxkJH>Ck9O-Y+~LzAvKusFKq0|{h2iXgN= zJ?To!D!|H<0ZK2Yh?jO`a9bXNHij+4x>45H$na6jIKeLRyIo#euZ?bxFhhLD%@|@y zIU}z~ZdXUeY&saRCt9~|EA5=c3gSE57eR>lY>w3iuasVG+iCLq=Wc^;r|)Oxjq0)$ zxLL16i^y!Mj)ZS?f{ff}@9TLyWXAB?-ce<2J)5bnS4LuXd{nDM7?Jxd*nVugUgJb6 zb)${(NMYsQ8e=EJ`X=&A z;S0_yOMZtYjagWRgjr}Mo4NM!tYVg3d#2q<+#>`;vVyR?K(p-ZM2#weUN2Kvd24?g zZoS}H0<0P$lK(dqhN!?BCH@WYY`;$~EJON}-&<+tNl4r8RSSsZALV5EOe+R6 z9)nTxy?ft+$X@CzVVW>cp7n&fEjn`P`63{xj{102zaINE@jK^$d^PYyZ-U-%Ytyo% z&dO-kajf>JRzZC<*m@;ugPzaSLTJ76v#9{G5la$NK?%qqbJeK`8gx;UW!81y>&9t^Lq2W9abu~+T8Mkbq2N&Rqzb?45^tu z(gy2iWXmY=Pn-fWX;rrI`dz(~cMP+tbff@sT*t@n0xTs!N4v;byrb4AvQ8lHbvxue z%ch8U8HL{hWUKkR0A`alv0kPBB#=sZ>7o>0AuNzjydip5!AoP_Dd8lt3OtKGhxvT~%fr}|ASdYecWmqm6|nOA z6Vb2LR#*2cfa z#xN@{dl4!NIM4QuQI+%HDGs`>oZI?`;LU#_IgB<9Mb1n_Nn%ljj%@OrW!A2Y?cR}k zH?qgX*2I4gw;aJ+fJc7jyd4p1r5MJR%yT^-+a_|l-AXWvTGd|)wO;;skuKX>KR;br zkAOK^>UJ4*Iq3w^{{JX|^d#DZs{86-VWJY*Ajd=fBcRhsr~s#AP8x+aPu`QZL{2G&&sQ}v8XfOtu4cEk@qD3zPjS*ZzUKrVUkXJ7 z?SyyYV3lW;6EKCZ9?vTnvwP>gK+4o$DCd7DfaM)eAUd|NW6U~%B;U*NyI$^~n!q9u z*~XIJns^iV7RY1ePP;rO@gh&>^(fD09K=!{k3g$WhWuJ@q@T1T#^>YjmjltcHIDu5(glP=|Lwa54IGmbF2;W`+s;9#!aI9Yq7T|2HHe=FEB zlv%*;yuZ`QBv6s%-AB)@&zbp8T*Fge(!q|24O1%i` z&-F$z{dPdkC+*7Gq}ydTPtMOML1fcu@_%Xo27069qIbiaN7o=;TUooH=b6px^Xuzq z4e*!(czFsfxc4~`{W7A^oLUtuN3bH-d(K9F)@eMPc7mbr1DuL{J-0<(p3IYm+kr(-uR=WGvhGpJFNhJ02l+#Rk^2=ZvX%Q07*qo IM6N<$g7b8%pa1{> literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Secret_door_128x64/frame_32.png b/assets/dolphin/external/L2_Secret_door_128x64/frame_32.png new file mode 100644 index 0000000000000000000000000000000000000000..c9c169f2eb4b33cc403c054bc170d3880343f82c GIT binary patch literal 1900 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Secret_door_128x64/frame_34.png b/assets/dolphin/external/L2_Secret_door_128x64/frame_34.png new file mode 100644 index 0000000000000000000000000000000000000000..d0f3a5383d809824126230e478a9e2ad74da83ff GIT binary patch literal 1923 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Secret_door_128x64/frame_36.png b/assets/dolphin/external/L2_Secret_door_128x64/frame_36.png new file mode 100644 index 0000000000000000000000000000000000000000..4b34fe377c1b7ed112f6b453fbfd5ad4d004e4d5 GIT binary patch literal 1937 zcmV;C2X6R@P)JAWDFmT z{pkGLh3w{J>gaX?DgQ2X7~j(Qx-M!ivuBq9c2lQS5dKE`myl~|24Wd$)nTRcdu!d6 z0$3x+3RTYgElyB196OI4H(5e6pgx21i3njJ9pSe)L8h?Vecp2oYn|y3A9=H@4q)rRPm0Iu2cW^VgK`yfrF! zL)zqnqFCo~^xV5n;d)X1>Nxxr2KYZ1KVrLcjQocZEWKKE+kNm)w1zv_V`plzbag)n zvJ9Wiv6@A89NrVg@7#waYh?}1?#MPLx{09kedSOk_3m;4R==tdcNCI4jIV*%d5kg6 zXhZ1PN_D+rgp8vy&LGD{>;ASb0@)0WIjw{7jKb1Cl2Ks{xo&3T6-My%l=7b_Aer;9 z(zZ44%os#2l4Sry|K8zS8;oEXYb8A(r@9+nFwOVYJPAGiivcnU>1!k-8+k|;V*t%! z+Mz1OM~Bbqb2lR|sW-e*A}|lreK}p7$=yr82LNSEjo|RwWh4IOK3#@ zC@*)d&B(LTf!Fy+r;+z|ppuHKko%#&4%P}q)xI99smgm^1#@V~QJ9n^b=?8I?v6FB zbA}%UTZf5qxt=G-T1B0gW3A^qz_36!TUqcNxWfr7{h!uNBN9lM1ws zT5mMwPHL7qbjsB7%`bzPDE0EyN<>?R+!2XZH{$7Y7eg_va1~R=N8gZF{gR4V?UR=l zv;tXr&E)xL3=6CE(q|&aTGuYjbJi~wQlHPf;d!~f=T)q_a*hf{Z;yy@jF;qDs0N35bOvS*Qq=p95HX()TKnbr%z4f!2rL21d@nI*a8f znfkzUFjT#>k?bKVNYUS{aqn~jrVuy34P+v0UcEwal@6nb)l3srz3lpI>XPa4=?Ir# zj6Vj!0Eo)1>KsL(XMDyO9s}s~!nCT|&BZvc~U(?n>l+PnXOYSP_RD&pIbIYCU8ke?I@} z0vMY1s#>`j)DC8i-$|*ulbAqhOU_xX8?xO*1&r|#8-A+AStdoSMm{ne>lkc=BL=iO z7bFKowv@ZGHeij9^f)WVmjW%CTZw#&HnWGwv~N?cS1VME|3onu86Uy?JwO!U*7F@X zzjC0=cvSRSwE~)po_82Oa=gsgNT0vH4D#ARRxqN^3@kQ@T2F5-vle=mZSA@vKcaTF zt#zOOd(D3u=qX$^{+!Ef!ZJFN$2dVWH%bl68CALfrhCZ*sK(E~hi8p%(R`=zJ$aoi zrB^6wCa4;}iYW4}^JfG0Ck0qWX9VtweCwHh?jcGAndf&K-_mD7x2udk+Q#P3!u4WW zFglc7(byq(1(uV%#rRAcILGNg|LBc*RX-9opRE|Flg<&kD)Wqt862p9T?K0+tq)@P zzkU#cCRoqAB7V2=8B~qS&gnR-sO^k&vFf{z9)OAot9o7C9Lizrylg}>2CG!@jI8YW zpkPG$v7Lw1>5BpM0A1-uMN$<}TjfDU;GH@6F34bvic=3(i}}R>9=+=ctA(F7+MJ)I z)#{v8==Hq)SM-WnZ+6d!?$P~^oPZQT@2eZr4%1cf-|Ykv-RoLWtFK;>X%&2C4PX^w zY=^*ZEHeHIqLkpV)#|?FbwxI^gJ(cACu98MIR5@UfZmi=paY%V11gHLwZZDXv>{g3 z@E9?=&i5JPU)N>-9$;5n)+N_VN!k&lk@TypJ!1^i1B7tPUgT%w-+BG|?_le1OW`hb$y#*;7Kj1n z)nft=cN_nQ0W4)$bg@9ELwSx+&BwCys4@Yw#v=@Xlwl7hJEyu<1=A7aYu>Nu;6;3N zW#zwu0kV+Mv0~6C>j`UZCgSCM>#!NTh5<5|8<(4`Fi3Xp4xm~{?+Y1SqRIeI)Byhg XB{?Fy!d-=500000NkvXXu0mjf&_JrU literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Secret_door_128x64/frame_37.png b/assets/dolphin/external/L2_Secret_door_128x64/frame_37.png new file mode 100644 index 0000000000000000000000000000000000000000..4935b75a6f35f2c11093d8cee1a0e9991e2a1bf7 GIT binary patch literal 1944 zcmV;J2WR++P)NS!%o*lQ%)q(yt(Y)&5mv#78JQKt+k-1iK>sj!dw_o@ftOh#x3BzzW!+uOR)&nV^aa z@!8n7ufJWWF3y&YZYQwvuR;g;%;xhvrMb$Q!wj%Hb+U%=8`Hmq)#Wpgnvqr=);fQr z>#`D{l3+VjJMUYZV3s&_o;q%137-M|9(?YI5Czr|zQqYDjot439y*ww;Sg{8u&WPH z>TV??cu(%=3A*tl@{dF3@|})w$b?$Y8%A_&y7lI#HHJ?kzk;#@D4u2CBWGA4pvwuY ztSj(D@{i-k0Wwl;9rn-ty|3LC))`hd!#dQH%D__*od!O30Gi|{vIxqWjUC>VG;me&^x;Dy`9& zXx?XrkeLjC(!WANwqT}bj9+mE*4Q%pb|MO<$QHJT5zvaD{I_aBeKYk^s;o! zY(dU7(-_`OdREJkZOZ<9u54;L(mtxe(HyIX-LrJgoEhDBAPc-sT|?HVOK6ttcKm0T>a|YE}x*{QpxNJ@}yLi&G z(~h01;7TV@8u6(NUpIm#d+Yf1W4*G*Hb15i;@>vZ5XX*^A)U!ANDcRvT4y4ZETASn&G* zQB$WXvk}T7Mv<41sy3pC)Xa|T9g>li1-4IW)JCX8{&@dS50EuHR(RH&p@xddyaKGQ zBQkBDfieb@kDgtzUWI&Arrxu)vsxl%$-kFIni%uY3_SLmNBOlH=iG@mpM;*F1sUloel0w6 z0)_k;;!*UoF5X8Eo2%$zU{QxJsE?L3Qnw=KMU*za@Z?t_tjEd@9b=d8Eu7gdJz?n5)3-i24ja-*QU77Vedy z&KISpF7j9FpB<-U0rmY8$ybhnW1J84k0(89ikPtRepfVJd6Yt6aj?bv9tEuU{p>p42V0J0T8*&2{OYfi?oLN zXW7Ek)VB-10Y;dyEj#+EeF#eT==s}DK!%|EmFV}RS;J;h@g1G`Z04%SN1eUjcNUDS z;iEkOtHaEY8Tj&o^s#3du66>i+g?4YrOnHV2t;!#*TIlZ?Sm~?Upj@k%CAB33C?0!f^JQ(Y z@KMiDDHDDk>(Hafua=}M$Uo2X_s78&na4gUYcNN=!Z|xLz*VIG$^cfmH>R=ycf+4D zX69$GM55Yn$ISEqSCfC9C;v~xRO{y|x@4_7f&%LZ=<45pMFv2|sOZW9oemW_!b(GC zf49$-YemoP8KAN;=zdm4H-;nFpYeRf1|Q*8#%vE@<=vhEs<5?jh0r_u38vfEenh&( eYzDZa2lx*Z!7Ci0000P)2x$ZqmI z)d0uhtC*zw-2pqw_pJzwGATunD(3!2d_ulf6zGGTJ&2=BawVclMVb5Gu%moWMWD2y zYeD2ZVx4zYertQm_pJ-aGKt{M%0IB1e8n~}!X*98+VrdZAM7Mwu?Fyn^W%x2Kx3xh zoAr!JV~bSpk>YK&e4!#RT3AG@*cH`&m2x8?wnF*zyj2)h%U9y7k1>X4od}Z>(-w1* z@}jveij0RK*ITqAhCd_Uv`m`tFvwC4!ekY5(*72o)hQ;*6Uxs{@`Y*u?FO_Qr7^VH z!!tlaY;;&N%L^*s3gQc(bE`&&xmeDZaX`>!iJph6I{&;(hL2V0YiE-lTVjuGMO>H=57TLeI{%JZdt5F?oJ8m)Mx z5g`-eDW4K%c&>^PQ||<>R=x+XP30eL-GSqNv?5Sio8fIIJX_Plo`$QHj)eS7QRq$D z5CfO?`Ltt1@N5l#5Ja)dO-jm(To+ltV(q74wt+KKEd;AN2V@;RqU&3;dCt^DRkMvO z9n%1;;-x}yk%5a9lD%;44V+NAo?tTytSG_}I@&!f`NNRj`{|QmtP03PPWqj%*{z^M zWGk)b1@SWyIuNhdX5mQ1oNcwdhbY?f3Jbp}k4M0D9G(MMLX3oHYhP(?#879wb(Qj~ z@_i-Bo&`j_%QGT+Z0%etAO@K2!83}Fw4W=Lo_#g~8$gv;ctw>J!Vw->CbsZvHlXVlV>^{C=^Y1_2t1-}6gZ63ZlZ|(xg*wK5} z3>Ce02iZP6-n|s2zF3-Ci1lQuM40Acp9jN z6gKI{GCDpSqMnBss5BVjw6*WMBb!FtE?@3IVwTf3p z1Zwo+JAqd!eP#%PMggg&iF&?wl%Vk@4UDpF911A{_TGQmKxy&HHjwT`Tf$jbU4j(> zH2%DZk&Oh_APS{{rVlI4pWX+c$E!m>HH204tq6cdhG-d0t)fwcOh^VN^lN>)w5EekDF7S{bFkMF1+KtKsE8t4Pf7Yzc$% zGjp^ejdib8_Ypb1M!}vJT>;BWkci>&?Foa@myX`LOwA$jhoD0FV~n3c5W4AXZnl)m z_tCEBf;>K+(R#w5yda+KosZTW5y#6aoy*}=1JILPaFkhX9SZGQOX*pf{9KjiSWckT z0N(Xh?I8U&%kLPpkW3d!04b-uwCg3Pyx!kw literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Secret_door_128x64/frame_39.png b/assets/dolphin/external/L2_Secret_door_128x64/frame_39.png new file mode 100644 index 0000000000000000000000000000000000000000..d276c21233c76a9226e90ffb3065d2f9bfaa23af GIT binary patch literal 1662 zcmV-^27&pBP)|~@=aLw{BfrY{SWbZd^cvNX=m41TiX&KMVVANZ=!|?!pCYlmEm0!~V zFp)|}h$tBgy#qTVzSFs>&Nv-jv`e($F$it&q&CGFE?VmDqMZB~RPo!33%6ZWn)s8^v;dL}q;PUa*$j5u7`}Oe0=x_v@dHy&6 zUAV&$GW4p@74aUqR}9WIdYl~E=P?Fcnt`Rp%_|tUAyt537ve zePUi>2H=FL%nBMUnof801f9m$j9QU7vS51vZjp4YvSHMCNX}3J+J9t?uNhu5dUPL} z&b{fMJq2KRg<5q4YDlef?s5VuOQrFpy=CS0>e)*FShm+dt23!3+9O!cW93?rv+4{L zY2PxwXD{7g4L|oX#u$+IGJb|{6(Vwdm4Gq{1Zd42w?-35 zTBnr-aq|t4ukG2AxdNW3$M^q~0I%{b=F)R3opDsD*#>XL_*OaN`UD9P$KsyzWF|2K zWW7_{c(&$8l}fMeG=5}HniEt6qQv7%Pf*+hY64NAEMy!dS{8O1-)hV_e%_Kt_sP-c z-n)MxB#W;jM9q(vytL01XmtSUaoI?Y^5i0z%HlOXl>>iX z>7Ok=yU^wUE61V=WoX8ksht$)A%)I$c;b{6tWBFHhqWIgj2M@f@`1*Pg-9y7y{X zDYVl_6x_bwTa$Sh&3G1Bil~UG>`X0 z(|_cAUtu(FHmcMuruCHpR+^HPGw~hKkNM&sNgl1~>yF?q;{z}m0B@fA{8U)SLe7A9 zoMOC=lyNw2FPC>R=6QV*xM-IKRuxCS2zK@ZVg|^JO;1n%6Wl<^a~kT9VFz7pPbC@P zL~zHasLa)S^OV#op3-cC;>t$+$paAYxt$s$+Wcj(BXBs|Nc@&Ez=`n6hU-0NWDgz9 zajfSsPwIr#&}2*X0GyyF*MFKVP+)?>RqfJ;Gg^&txsn`Y7EKj1Pc*BS<|Gi{I|69!_Y6;Vm5U6YYG<(ef>R7XaRJEbA43}o zNza^&Zl`>TE-nDxxBzQfzdM?_`2)TF^fC2LeC7b! zIsYW%TiyDKj4O^qpS7lf713Y8*Y!Jrt3p=6!~dkN{w{q*_`1FgtVXvpd1|4D27um} zX)_Zqxb+?froBp*ulu`zyQ4pftP^MhF^sM(Lr-Dax(b`98K9EU9jM3z7HugDMCe4W zTk=Hte*R!YSK7bZfjXT)mX*jcB|8(px7@cCy z2TnyK`X?$JQ2bA#e$^4G(eXXPPX6qljr%_v|BiY5DpvUa1224YscsB!od5s;07*qo IM6N<$g381tfdBvi literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Secret_door_128x64/frame_4.png b/assets/dolphin/external/L2_Secret_door_128x64/frame_4.png new file mode 100644 index 0000000000000000000000000000000000000000..354a96b6c5b40c54cf1fd55e0d7205040cdbf583 GIT binary patch literal 1618 zcmV-Y2CeytP)AXuSu>^=+xNGJB zfB}sdW5977Fh-s1Mdvt<2FbqQ@N>5SU)+ncBT{E$2N`$8*_~cz80%XC=HXic{y4r4 z@W*kCNd>lg6UgpgeRJ5oKRKZJ8VK-TDHS7RXy837oS@^JcVmhJ0PvBaDJr_&qT_V} z%Gb-2^4S&BN&w3YDT^w)YjA{VSx!J@@kaEg7$4xqCUA_=t~}15mXS24w0P`pd;oxR zCULJqkK;P3d zX9TXN(40Uj50>$Fg2x7K2k=Cxr`~2zdBf;&0*g1MQ-P(!S>%lO$@0mVD^4ktcz$K=ieSI;Sp?A6S}n{xt6 zgYxc_;oZQNfXo<`i=HP&N*9jTaCEI_5m@PcMa`FFngvbo%WeT$D)pw==#|pb;Bl!x zIzE;#<3>~g7lcda08$fYWd$xOr5YV+;4}xQswMi~V-_FFnWw%Bs5VR1tpcy*o?8mY zW`H{|%S@*yO=VMJ<=UtH9&rjQ^_jnX%0MBq~rZOWvMTX|xX#1nk zYAwQR={|gQ3!0!+sX?3FzSf@3OxGP`Y7=N}p83LLCh!1BTVsK1GOcTG7FJ_Td3l}gDFZ|XG#s-I zh0^uWYiGnMJ(-|Fr}N6}jsB<%$L}8bb*cwc@fwY6Ht{-FmlIUyBhlMR*C;*8`xs*k z+zDr+A)?4=ACS!+9%LL+J1ci1@!roP)6@(t4rj&a9>(5isWxOC)4u3>$>7(T>$mJp zYqs$L$h!coao~t^it{K_%Mw|dt8vLp^~5!W)G@ey6f2AmKr(=4h-`}WATs^xL^k_z z`{*D^qK&hHgjL1|V4A?EhfG$uBO0U1m?CTQ0^dnZ^ypf!e^Q&>i>4w)W*4}d?Z@hwNNy!jIy z0UMBO@~nN8xd%rl>j*#8)3V0LjSY1w?D18Py&KfJY@&q7+9%FYIq_&#V0s|gowds%?3O;F<}n_OK7Q=p!B(tvAZKg zW+kaRd$aM!7{5J$H{$6K1FzS52~P%?;oYloox_tgxwR_;;L(!-c^w>?40`7Wls+d6 zrpxi$8QPgV4R=>yu=WA){}VA0qg&3v3B?_HXGicF9)Gv8anuyWc4k+3&;vVKRe3?Kwib26Oe2+oieAHS z12c!3nGvuO@H*~y?zslfUQRyldo~kv2NBx?R3b6kJ46nLmC4n?>pATgDiKKQIj-we zekvdLoyu$Rj_7B32I$TZ7rM%XrdY4TVq;t9(juSxKI7a=Ge9ImM3C`(&PZH_@H&WO zd@1u;kssMV^Hb96lnl_FA*#-38UK=rrt7HuQsi6vSL^#J16YThogqA*)r_DS{#uKj z#c~2mPD}2}`8t9P>nSt|+!35%=`-$mHe2n6rT=NvJHkropN@~u0AJ|%2c+VWi=+2( Qk^lez07*qoM6N<$g0=nl8vp`YmHw&-Vt7&7$bBf-3d%a$NpBb;Ud){j`X6^D^$J%iEtb6p?kYnlK#36PvEO#sKA?tE%vMG$1#H(>dX!-Y300zrz-Q=L!kTd~v21gmzo=3{UU z9|1sxX9NP3H?F%g$3kW1XE~qR+1kH#jjoXx095cwAXsJLsl|$*GV`f?M0Bn=R+6uk zUo!xxkjy}cEHX9v2|StcmBEc`#;xIvU7`h#L$JY7@#`ixnvmkXM)~47JYJ~ zfb2k!#&OREbL^`9!-lGim*(Ng)NBkpZ+ws)2q=$^L#CkR<6ASI&K2kD(N9qk2*^Ai z0e}Oa2!t7VS*Xf*kKHQ{=NdiE4()yKz1^CFrN+%`7&jpMJbT%~nY#*H>wJ#S*{?$9tI#qozaJC1e2m zk6Gtyj>l(Q&`aPxtIfsp{!Mpd7$Ca1vy31ugeQx#nV`b?l9MC-kd)CBk~~iUzB+k2 z({-r3#(~TogC&2ZEMz&~ z>SH2J9pZz&jU9;Obo$=?WzZ}ECWpu2UW|Et z90YkfQcY;Y07Uv!jR5a`S3OkT~k%H+M&#nXmXVtE!fqHLG3Ik9@C)5AH z>gnC;{(rX5vSd?uo7*V{@Ce=wZp(`HkETun9wecBkK?4^$q0N&2Im_|*AfGq3NJHx zn!hZj)AY;$?eUI9{2T*BoMSNn9Y-0(Y91ptdTl^^vc{${z{&Mrv)O{DRLttfF%d+! z=MtuO0mF!sm>?QZ-m%ggBfFr?+MkJwP68VwPv)OA84Xoi)`;BGoZlK@fYv_0BK2(f zPGW#v;La2%uyG-+K7c3lwG%SDe;QltE?+bHtL(joaJ*`nubr%M zd*-LIZbr}UAh7JsZXgjiH71hzX!vPaZXue5k-&nDHD=*}_y~c8;{emt8jovA85`$$%$y+s%^W^ z26o|YO>g)HkR^7(a{#r&nW)p+=V$d&t9s3oK^x-h!u4%nYx|ww znr~v+UA^~^hU#AJk39C%&#dTmeHW08>R|FTLmnC0`>OprS9Yaw8EdFAK(?|cF&hNl z;I~drBl;ruEqkJR-+mw9X(p&f1{2Kcok8SOlJm`s-nWy$3a9T5pvVc?jO^Vsb2wTeF{{G6=FT^PNOS0J481>KB}jsG3tI3(VNKs$cvoVZW07Phj^tt?2&`-_M@D TfiX(a00000NkvXXu0mjftQ1Sx literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Secret_door_128x64/frame_41.png b/assets/dolphin/external/L2_Secret_door_128x64/frame_41.png new file mode 100644 index 0000000000000000000000000000000000000000..529f6cdd4554f19c211e1c06ee28370e31309452 GIT binary patch literal 1654 zcmV-+28sEJP)t^d=5qgAbU_ z)f@mY0071q;Ndt9u*TwZiZKQp$MJawKU?>r*KTC(id=DRBz@jJ`V8fGI;61x04EhD zIgnzE@e!aoTblqMzq|9PjU7QyX&=J#pM;a&=m-QYI(KzGwQt4FC=;yO2@qp&34a2B z3a3*1 z0H~14K$ux%YV;krbH{fEH(xVp4KH3LdWblL7kJt;E^{Xa=-%Oj!ONzafup341bL)VVi<`LPc)j&Hh{-8P#xI8+JGX+f_Z{7KHu6(``{S+O6z?tVy z0Pul#1VTn$6}onO#O@V`ONE|fhxP9<23(edwZbiGm^2|Nup+_G6$mKzF6Vpm&a#J> zM(8}LE|mc!V=7s}lEvxtj+&s;`Ib{Fm?I0Z29O46_bLlUONSN=72x$p*7=s>EvL`U z!&>tw{fnmnil|YmfxsPURnA>Oz-_5?zBae4-O+XS=%3nl3^F=HEweR(_dZ=*Z|AHA zLj~=7&X3Hc8=~WJE@OwN3P!)1H# zX>u$E_{h^zK$e=KLEurjhS_nuIp0eW5jiupmByMMSx{UAMh&uz(qNrdBI9>(eq{b3 z#|2o@s{Zk_+X<`yDqN+@=qW+dmvO!}m)Ut(&gZhIExeYguGw!1fJknW5M5x+@zV2I z=SPFio5#$Yc@T8swo?FI{0KB>db&^#l_S|=1pyz=ZOS5AS6M;G=eLSh2Jk9S4J!*R zu>q%7d``!C^j}5&QTy?;wGk=;;q0QAC!EjqOL>($kLOr!zhs~Fe0T7mxgv$nZ1iQF zUky&{Qe_=M1tVoqSqn(>z9I!shH5fKcL$7BU~$IK-nX99G2Xac@WlW;2xc6=>twmJ zwc}SL_j{0zdONNQwdu5~U_RIa&96J|egOaLTWO3nmOq!)uCijU0Jrh9jzU@vjSAld zk>-wef3?z|4T9`ZF#^d(kpUv-WbL}^?nm(59(6E(H-hZ$M>dtxRdpXgWq_IM8^CRq znnrgrSoiUp!t&9dQ$UsxehnRSn@y?G`%!qsEh5DLn&92(AK`V_C{`Lj(mQvsrX*jp ze~AHhhD7b&kuVEuO;%0^RrXrGDq(=#>HxaCt;LQOfe}c0ywd^wk)Cr|S-UKhFu+c1 zFx$APgj8roZSvToYmL*V05ov-<_~~QxgEN#UA-etx;WgKk=(!2M0c%CGKJq=b2%lN zi8%l5eSn?y-x*L^lIS0=)-mLb&VQN(&<23Hvw9~^iT?3w z<@~ofpW|u!`7!V*d&J)di0phyS5lyOJ@%&<=0qmVW^IgDQyDpnze~68Dg85gL*E7>o?cW-e?@Jg&Dk37z3;(m zqepbF=nXT#)6NHoMRv5ax2n6VmCR3J*0>6POff(ex`QcF4M$`|bZ9KFs%O^jtlqER z2l%CCPc{fD0zdoiOjAm-Go$yb6Zk96N6d7`vhurR-v#kc$5!I$0O}5cDr8#H6#?k} z$(Xn9eu|2kz>{4i>u&u^e=+1u=q@^+#ESlZ083e%Y!Y(6GXMYp07*qoM6N<$g6lCG AJOBUy literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Secret_door_128x64/frame_42.png b/assets/dolphin/external/L2_Secret_door_128x64/frame_42.png new file mode 100644 index 0000000000000000000000000000000000000000..15168cd11124217a5092687483149426b63aebba GIT binary patch literal 1495 zcmV;|1t|K7P)xkkIPCPs3`dZ>^RC9n@{P#p%><)z0{AjGhYtt9 z#48*D)9cr(Bgewb$geU!mJ{G_U!!{@Ishg-;t1BUaMor;Ff;NoeTt~Abge{RJAO?E zz(gt?A+pI>=reF;#LskYx@X)T9<)ld;4ug-@T6yy=AAOY?1<-rq2JFqy>~6O$w3KJ zJA$;1d)70@RcC*fVOGRTdUztW8ePtVKB#sCN)NB2nF1dlt42Q7mFnx^k1@j$Xy*BF z0J`uDN665t!mNn*$US0kuF&J;(0*^N!J!#gD%`w+aSCyc<#C2tj?n3zWqeETZ1%8@ z5v(WXC1wCln98i6(W2?}jGADk@in7XWR5J@8h~3Q-K#7ZH6D^PRDjkWS>tPl*Nh(3 zL)&w2`e(ZU46jh5jzA6Rt(+?}0F|ZE_>yi}`MvvWrGG5j*3jxqYKhhematx06;4o* z_ATRkvcPPxhVQzx)|w;?cg9&vI`PrZc)JA8T8mDV@vRI;dCmfteIzH?;Q#jls^D=1 zZcSD36K$m`jW2Dwwr&>OPS7SxBSp5?@X<)m6rBBY6~@>2bH=Yc3M{!ehcLT<3ZcBw zfsr+2#1PeyACb7Iss=7_ZLDcm}!_@9pg=l z7+;ffhVdyc?e_{ya{z$zTAnB(#|PvK4fXQloK%?d}@tChHG^mlLt9tl)^YebKd*;L^V5?5{?r-T!ei;NHIq znAWaU!4bwYdepO^RsJ2-XTMZbYEDoAO6wy7NE0(lkk!&Z#@W%&8Q*dOjz$!mE*~{O z_DU671)dF`Rrzz1K4}a*3t|SyyfBI2F3xgw5mt?8$5xU7o`@N`MeC~({#J3@k;(ub z!mGgA4F9Tef;%O1>nMYLP z7%~2e=OYH!a$S)LuEfrz^+@E8w1QWH(}PRPPsfgp%1r)l z5j&Uu`;|NMxv}rlBIcX2z|N)rewEIAeEnfZ%1ZRBl=eDO_uK= zMRIm5Y&p@Lz-6dA$~hBn|1*e2K<1smN}H#>1j?r6NNZf_Y+1esH6%@cp7}tvGFO4q z$&0Eq9>pl%R}t6+(9ZS_>9|-GQn4D*iFQWTnOqe>qkMPzz}x4qQs7xwty8JS0id7t z8df)cM@5m!%}4O)oLk>9!h4kO(DwnJj0zpE+NTKL)Hi|vo)i5XxH$k9Q5Eejx873> z;?Iom4QBvJ=+ziPj#Yb)ZG#6pN{RyjbUOe5002ovPDHLkV1jaF)=B^X literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Secret_door_128x64/frame_43.png b/assets/dolphin/external/L2_Secret_door_128x64/frame_43.png new file mode 100644 index 0000000000000000000000000000000000000000..16196a7062fb707b4adc7a2199a42d9709024ab5 GIT binary patch literal 1440 zcmV;R1z-A!P)7U9a@zky#-pYv!KVgZvz+5t*YFVhtb-(%y?K7`?KioPyUMwdCiB&ynu6BeyQr z^e>(QD6C3FID#g_S|eD-=D-WlNb3K}j#kAW~YV;#0$yx@WV`u1kUHaGWa`$Rbd>B-N9iks$P5+}( zzei?-Bith@*HwXi3EmgLdlGg}{oH9DOX1@=k9wkiR;KWtP-bQ{i&NIfLOhlNs7Lj= z8u$r8!tT*8;noTu0AIne>!L{MpWmNp8$J6+%LMqU0dh{@ElPHDgk0s1m>K;&>$Hyy zp!Lk760Vk7x8g425<5k=9>3#Z{KkSEFO8CT;*d{ZnX}L3{W>&(o`K5nSJsZ~;KFL_R>iI;QtDr;rNCnUM6A^z_1O zS5$!1%qw;RxlnIkU>D?ct<4DD=2%o9)Xw{H3g830N5h@*1{=Ki^_xLF^`An1WJ}v~ zG>%-i+Ib&ND6fP|1>zgX2dMj9e2S(Hl0M#fzyC_u z+nYTjQM*pLo`f_9=cH!dU!6eh6mSChHTHRvc4uc3G}oYssm^bR;5{;v)4zq2F}Px(Zqf3t-41= zzPISL+G~N7{@H9-N-2_HPb;Psa*{Eeu@qi%y$ha6KGm%iuaRJhf^viHQ@el+Wi2Bl z^hS>5`E2s7a~$2(v~2luf-<27eLvg*Bm;WK_2?cI`4RFn zw}O^!-2KZ_eMeifJIM$5Jy5xWd=A-lfOq{$Dc@ZH6_#PZ-Q-tfSNp4x&)H?cPX@>^ z0ATJBt`~Qc@3E7jhcC`n^ACi0;9ZPaj)IA`lCRl1oBUo~1)Tv@3~>N}d8GEhLVjzD z98Lal9G`zJn<7YefK{Wv0+TFd06g|iTd6aSs2iFugCf`=`YWvIe^%7W?cb%IBr&a|6#NMy(2Cee1xFhff?H-Ctu>KD+Azzq8*)q zzICIYjnq5a=_LG|1bQ>Uu8__4cdg+`(|WyHBmW7l z;G1z`Qn?dAiTo#R2NZ8|YF8If@nrJfhLcPG^~&w}QWx-tJ#k8B``Vt5uYcGZ%`O1o zJHkEh0CwvVR(vlo8Yh69{xPJ?Ae}wqetPvSg4+8g8~{)(kq@9($M~M)6kH*BCpddK zJH7DED=R=EdfAgeD%5)}unSr*fg%|JXA|rAj5Uku{pkYe0B>u!GoCO9FTQ>=h^GFx zknfEi4W8CGbKRnPFG?t_gj)sT6UYbX|4rPiH10*O-U|!iBAvt2P6DMlc;-esqgGbQ z3Z!1WC%s|m^PfOE#pQ)5%r(^5&>7)a!L!u}8D>7_1 z{n4m5I6@Se4s=HrYW~fjH;~`k?cBXaJ^KFxQ_;Jyc>n*600000NkvXXu0mjfVSBd? literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Secret_door_128x64/frame_45.png b/assets/dolphin/external/L2_Secret_door_128x64/frame_45.png new file mode 100644 index 0000000000000000000000000000000000000000..5a2221cb9282fccb2758f6d5f0d0d854d864d06f GIT binary patch literal 1464 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 AOaK4? literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Secret_door_128x64/frame_47.png b/assets/dolphin/external/L2_Secret_door_128x64/frame_47.png new file mode 100644 index 0000000000000000000000000000000000000000..a6d5407ad0f5f04dc351b8d5fb995f2c99094d34 GIT binary patch literal 1369 zcmV-f1*ZCmP)ele$>*&Qi*EQrxQ7jLaRM!T7CwJ#s!_d;s{-G4=Upu7DVT>P&;g z2wGT}&G?l3BaNRK3Ph@B-d`91qn8md8?*v=UhhQq%4b(!^1LklVMkCI0OJWpu#TIF zQHh@?3AE~uyUxNsC$&N)co~5^hx=@W+=cdWF5v+;?;d?nw)=*FQH~%biqzOoJHcq$ zw*V*5Scg%4;DuRAx#j@bj$p|TFwH+1`?FWPSD#oO>T{M}j^lt2chVE9+IAXsS!4QL zv47NxA0>Bo)$p;bUeDkfRZ)&*afVR}t^Lkq{JWS>WB@D(j?>lw>a`eBUzkqs$O%Ro z-y^d({Pv)fB0B&lPNP%Rc<$U&z&0Zt!0WA1N5F>Ess*on7HHK+ zkMT3)%*g(!zA8SH0X)>w6B?+#v^G@RC^Be&r19^-2S7H6W{7rGdmTYCxV1{Kw3Wus zG(JW7yu8GZ`O`;W0kIwQ9+T`J>}}3rV)2N#Lv-B2nXG^dx-T9zr(MkOTu5V8+!?jiot( zMEx1r{tQqb3XNjc)sLYu0B*M%&g@A=V&R>Nr4qdQ*}Cbp%jyS!|Mvm3n$%ibw&!+G zPQ{MYU-7XdK-L6hHKnxjO0j=u8~{)(F;Igy@$zw$gOkPM)`0s!e0D<*%MysOQ}3zh+*+oo2`;w+ZZZ`aii zKvV%yM%WE2=QJLK7yw`&1?`&GSkaXOyg@-b(?38G1FXsht#NlXu!im`V09da{x$cV z@rf|NE<0nmgvH0+fRmziPuAa43O)&H9&fxKa8k5xG5)Tl;HnNCg)0ZB*wy%N#7WVA zeWhYPKHpxm3*N*6Cq@7DRk}O)xAOOZS_J@HYyLN_klk?t@bupza*Oy6^Wy_RXZ-;3 z%D3DTSgoY8+!@>rNle(br#8sidneMh6dP!eHs(CzhMo$hgx(#I{2)r{mg^<`d&YG?6Q~XT z7EpOofhG^e_aE zfU_x|qL(pnJm|eBA_j1B3$>VD*Y#e6(s}nDk9|ic$pErK zWekBc0582-^z!&j-p;NyLg!Dq0p%FTRqqi*1d(`*Oamj7pULl;b$Del`v4rF)`lSE zC_KM+P0&;MBa|+|;$77UYGDYH0Z2Sr8v#u}S>=0*+`GZ1UtkO%Dsa{iBxQTNsC6Xh zq5N4CPw`>&xCqQtp*5+2n;)&3YmA{q*k4Wg1s}&iNpaHAMjDHWN9S+?M&^z*0!;?) z{;UWUfnG!m!OHa-S`=2TmA{JOG0Z+dH3OqL67d>OWa()P5|34t4*>ojgR+n|&#U;T zr(2CC{Qb$EUl4jDD4Lh?ylAmkn@{Qd9tGd($_G$VK&GMx@EMRt`Lmz%cThfnqY+dK z>C7(*)L~<@MnKnq>2dd0g!x8L4Q5b&kpn9&qI^!qLT3kbGk{r?kLB?i0iDOu+67%B zz|474GXgvYBU=~Ej4lQcRr)AH!5)sUp76h0=r1^XEof%%2l#xSo*D&yzB^u?!iU=o z?e~KSMjHwj>MiW*{Qy621zQDBH22N!{RK4N@2qGEFD(Z~6u;`Gap9kPzAH|x3I^~i zmKIgsId>S2*65MsS=GQToZ3-{`@SQd>iqyh1hB3EAYNC)s&co27y&Xp^HqvJj3*xb zFj5hKUNajvyRpnJUn`^{0Kh)^@d--SP*nr$uz9-}au#eKz#9Rgzxd4vs|#AtvnSHZ z2s3>DbONK1U}bn}n9Qc$(F@7)O+^6OB3{`Tc349VP8!zB)$94*5IQ4@_m?%aSV!dd zla`S*%QqYWD3Mjufz(W@aOhyxeorv7AQZ7m-DCI4&x2X@nd1qBk(Xj1gou zSZc~TkMg(($(@Q?$)Xv8CnC{WLQ_5fDFSJA zZ{>E&^H&4s;RW;z?mY!rdD&Z3Y9|kAoTdEZIDY?bptrE&h^?-CT5wt8%4dAdLgGmc zuH5tRlL|#{%t1- zPE{lJQHlTnq!ak`G{8Yi4Vq_3`^wShiZl!KNScxJx=1JRt8^JrC1v+fJG#+vUY%(D z4fel|#gkf8{W}_v7SgqT0d45CIDv5EE&lHmpb_2i$TtGnee(7s${4DX3}4s367~e; fXVw>K^2~~V)y8fTxI6PC00000NkvXXu0mjf>V3UQ literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Secret_door_128x64/frame_49.png b/assets/dolphin/external/L2_Secret_door_128x64/frame_49.png new file mode 100644 index 0000000000000000000000000000000000000000..0e6845ac244980c1d2fcea4abc84ef0b33b215d9 GIT binary patch literal 1851 zcmV-B2gLY^P)Wqssi>-iO&uk9PLGs^@Sv~@?<@>&HLz*6iA zaTp;g3s-YKv;U|vFv79)u=|0136&9|_exhTtin4)#qUy@Q6T zfXKj1f54dItlGaaP%7S48hFVkiBUXOjTlNA8HM)d>& z3xKS=QLM`Np1R$;){2~uawE<$kah1BR1C3n99agAaQ+bip6wyFZHvDHFhR2e!KzVu z{_37!rSq@Q4_G>fJ_OAO1Sv2$dM=uGitu2w;BMmj^URJ01@t7D*#67XA}rk=QCMW zZ&GoFHy-D{In3y1diAFNB4`Gz4EJihCyOnTZIpgC;{4U9FyJg4$^as@e+Bg5Q8bS6 zGR+HuYSUMtbnjGxvhd!T-=p`?9)O{8eCcTtEgfE2V6rl$skf<%0xr{Y^y+m#1sK*(+|+TNqvo8#Ha?5Wm$Pw&&)1g6|FFf-3;WGn+M-5H&1ezw10wq)#> zUBf-bnuQkwcn#33ZvtnWkF#}kr1s;u%6?_aqc^ulr`Eguz3&%<0xeWGaVA*l5y_oO zScAr7SFaV@(XQ+Xh5>DOM8;)}ZAUC-#F@?);b-fn51&O&*7LONmCoPy{lmf0$%U2npl49PsVHhi}8QLbfQMYd-XWq`~g*cA<4?bKc{o4v8tx>h(pJ1?St zE0OVPGC<{ODbC`kKtZa+l4a3s=zFH4x3{wN_mqiY2Cy2TZ$q|MV3f-;9e-Dx&z@I;1@XZEdLb3#MvS-v zbf1t31lA+tWz{Zl*-JayCFoQL}jn^pk0H}1t z;dc!d3H<9%=55s0K1~zV*z?OzqH2tZ^!UE8+(4FIbQSedVd z-JJm<7DFdk*%l!i2woCKO{3NoCG%Sk;B}c%=HHnC+NSH0W{nfcQG7#`7*?~$7(w5A zKGL$h9zxspv@$u5KO&qh$ay}dP#0Og7w+8`()t!6K zEky65+OEe`_B!i)5qbZE5H!T9`QPe%1Y1A5pRs<6X&3{raavB+`6BY(1Y(VkXzE)U zL4>tw>_m{s#>#t3UUc$c?;_{FP66$QS7-j?^Hij-z3`5oUiTi^iDRus@qrV*oz|;} zGT(DxyN(1&blS+u(9nKPeF^-gqhAla%#X^XNZk3a1oM^lZ`WT0vEX1hYtg-E_D0`B z*cD-&=d1S2fOo{7^8W%D#o&4R%0PSZdAqkxMUiYh-+PakwRcu8G0Xt0*!m_Q8@F^-4GnaX14o pfU4j-JHMgo#B2{xWq>n!fIogQ{T#2oX!rmC002ovPDHLkV1iOkj#vNy literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Secret_door_128x64/frame_5.png b/assets/dolphin/external/L2_Secret_door_128x64/frame_5.png new file mode 100644 index 0000000000000000000000000000000000000000..45decf285fb34e41768e7304fa41765308dbe80e GIT binary patch literal 1625 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 KMNUMnLSTZ-kN8Re literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Secret_door_128x64/frame_51.png b/assets/dolphin/external/L2_Secret_door_128x64/frame_51.png new file mode 100644 index 0000000000000000000000000000000000000000..38184aa75e307e689cff898bae3a09765ef43093 GIT binary patch literal 2258 zcmV;@2rc)CP)yrx@}qw{e!5KH$c($X zwqqT@0LpNxBw>WAE&R3QGylKU26h=>r3HL206TykMnE=*4%|^GC*;n`kNQz|V|Dia z=>OL>o?Ycx6-LFl7N2zvxw3m&zDf5EvvpXwtNV$GaSgdZz;(=?1BP$PH!2=!I^M_k zj&$?*w*3MNT?Lhc&V0a_eLDl7yw*-yK+~>uk`KJSUY+?+RaEl2D0hBBOOPSjc>MrZzTU40C#7I>$>Xi z0hprGKu|p@?!S8`xHI{GVIEL6x4sBE0R#mAW#cpmu>Gti-%0#8hgl}H{SE?vuA{3! zP|2~;Zg>SOqy1Hc$O zxjO)ed!kf8=q8`(QnN`X8QFT=`y*juo>{e9{z}+MxuaiR*Y%NL1}r?lmE_+oD+RDC zUuA$X@{df4T04hQRf9Vl;J&%H8j@W~&pwr)F8o>TU$Ohu86X;7*|)m4+ZL3LyN0?7 zaL&xJYB&8V_=G4&8LHKu>VC(5yyqS;`m3b-Sv=F*nL~E$KC?q$#+8HBzPUc5vkVlt zK3liDhv{$*_jC_;3Sn2C&H%IYbgrEkV>}pd_Q{3C#MXZ>03w(nI+LvOPxdZHxSvqMzR z?iK_>^a9de10nV4yc%@mZWSb-5;LodZT7&ofX5T>0iEpH;f+=iILV`(RYpWdKG|Y|U>8JCJ$ZZ4byK6$m<& zAN49W$QYTiA7y|p2$XU)6J+o25;Z$8;AT29e;E+s&n!8svz4;H(kA2#&~XwK#%l~Z zOF-t}j;Q3L?#fQ2U>68;J*#wl-${OCg!pp7g7Rj76(`X#d`$z{HIp4Cag*-~##8}; zwHG((7&qnv{h4-DQDyC6?xnvG)JeWVB|6uoD|Hy^{j|68%=M=Ng1Wa#`>Ox5_e@a# zj3df}Dd;4hF#v)9opHpSDeyiX3|8W4O1Obyp}vr__3dSQN$_Oiw5Iof$+4t8_N^ zWHW+O*(i=qJX`h*&*OoK&Wtf7Sz@LUHn5<>_+m4Jy1L1aS65iVS={QL(pmY@DZuDk z<^wB+2Qz|6aulNCDlsw9?4>|d%>-^d4iMsgOn$~DyG+wfKKje%G3DRK_W)TRTj~9{ zKHjgurI?h)V z27;<8vv#F`&f17W`>O++Vj~5{6&om+t7-Qc-!S9M5p}3hs^q#3GX{u0s9w#k(RP){ zPD{&ZUB!{Lq!S6pSeWhWy1c&|7&lIZE@+|?uC40ulwX;ixsLf#&o2RXNps5Sc=?lISz8bA42^NEP?WpqJ^3eou+MKinjuutTfb zLMN%+%5{^^{8Q{S6N2r9#ZnECDsDE5tO7yp%i{g`eSnT(1%hm_spz(96>49W@XY_X zo=J?O0us}Y`?Gl1ImTCJhs`3&=8qX*HTg_gb;aPEzRK1`LFRJW2h-63rPDSMT#i4g zl_NE#VAf&RHAJDm;qJ?j?Uq*Ltw)XzEDT(b1}#) zxy)atyVI7YzIg4Nk6IV)M*F*hcB4N_|4yJwYiNue!b~6oBJOeRu=mk#1j5b?po(XO g0lK~1l`O9R0h`z=xZ>{7K>z>%07*qoM6N<$f}HGIiU0rr literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Secret_door_128x64/frame_52.png b/assets/dolphin/external/L2_Secret_door_128x64/frame_52.png new file mode 100644 index 0000000000000000000000000000000000000000..1259a591a4b81d5bf1d12cf1d6dc739f94e88a16 GIT binary patch literal 1615 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Secret_door_128x64/frame_8.png b/assets/dolphin/external/L2_Secret_door_128x64/frame_8.png new file mode 100644 index 0000000000000000000000000000000000000000..3621243f71f10b63c819eca3ae8474717db41550 GIT binary patch literal 1612 zcmV-S2DABzP)c?O7i*cfGg~qE&{Vk${ma@O3rf!@U?EU@0qaf5RNJ-cQCr6@96x2Ob~R~ zH&p~`6Gl#w3(CxKH|G=4lf!q|H{AtPgCHyWj?N#b1OX~{j34hyZD1w{_-EcCzRrIJ z8U(ZUHH*MZaiQfbOKD9`)+?f1Ri{~bGl5dw`4Oz{0JX0fjF~ntQc#;TtDHw+-k1m- z0glYU$9C5;Xb|wZ!vd063WywG!Rtpg=Uq|ob<_VaDj(V^ZZtB8}+?l|UXB65PK8NRg zEf8mz@d~DR5WU}*FK5$>SA59>8!2C&pn1+Z~%1XAjEK4IUMLorS=3b>JF^=yx%*qqzJSo z(jF_$FhNJ#wBmSm=Gn(g4N}3jB2eyl>GZ1eqqZwTHccRUAsvG?-@CQYS^Ne7y5l1z zu=1$3<11KoScM!8S@ZE?bjRspCl31mm9VT+GjxsSJ^e&?^+B6mAq(+9mr`x$2j<43!PNN2g3R&@tl2B*uLDo*!`h72|Iqnb)NTolhKiV{2VToLy0mJ~BB33_s7w22FuhmKN zJtB+W6S2$GT8PyE5-1&+!V`xgp91a>X%{UBv;dHsO1oxtTb@sldX3{ePoDwgf>C6O z9y$I~f`RKSZNzPuEy|>f=(@%R85{n7Eu6o0IbTa3T92GbGk+D)TC;To3Y7tdzz*kI za%91&MQ=6r?0`k-{5nw_&{1lzWLT`@&85|JFXsT-Kz@U^NN=H)J~isAuSQe^TF9Ok zSoff^kvU(!6pB>-Xlvs+4e36#Jo*lSpg9`Jzj6&)*I?bF%_aS&=@GF)_{X8hZuBZx z3);Cu6a?B_SD`Y$)h&2MINC8-4v&2D5&%f<=mKg8%^=x8SiOb{vTN7w9q;+)TsQ4= zj;8>Kb}mZ&C^8HXMK(gcD`4e)G$>?XTd&@|)$2s83#dY?9}sz&=&vxsN~ddUk;oK- z7OYxm#%FSF^;rNyL6%Tdv1q4fgCTPK?wY}x4`tb?AnsZZ{Cxng^+6UUf$Z!|5OBFy z7l;-t$7R#tc<N6V% z6}qn&57lF=Ba3X>=?L;6-*U9I_H+%v6_yLI7K0i{8`H%A2&%eDc=KuUx|mLrFa4zH z$L~oq0P1`%7_@vgYlJ9hH3M|l3aisv2gAvAA4L9t8YOd0lzYaQXeU)t1`02D$94JH zCqWDLD%$?F@F$_5LJOvL^;kXME}%>Y$)bP(J{1xDe>$@Dj)i~MKbMr*&bD^|0000< KMNUMnLSTYT=Jyc* literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Secret_door_128x64/frame_9.png b/assets/dolphin/external/L2_Secret_door_128x64/frame_9.png new file mode 100644 index 0000000000000000000000000000000000000000..4d3d537162cb4c59a4c379e0493749eabde8edad GIT binary patch literal 1575 zcmV+?2H5$DP)pcN0+xJWYqe}7!LpR0Oc>?HOSF`VnM@v`EnoQ@=dDY7O7f^?jPksv_j_VH2szLkNIAfTV2boONDcc4LVm3_@l zpp>|1N=wl?LSdqk$@x)O>!DeE)IJsVMQp(3xUFDINpxNoRnE=oGU|Lwj~VujR1km4 zKxv{Esa$D~mZEoC6qf~#4s_YxVb~q9uLBPjJn{*XQMzn^sp(b8KTAyIH z^IHqP4YU@QHn9>$>0dkf)%)}8H_N_fD|n>j&^}+y`4Z3*J>$Cal1yn#Ca0ph@%ejH zD0MJ5&=t;~-AC_`Xcqxu1GE8@wJ(xThQ1Lr+xS;GSAtiIBk1~%=0A}&s>bgbWvVe! z$5w$iXAoG_D#@-uHN%PlaOX+%P+b~w*|SR2OcgW}`^wI)Ld5yVsg9PVD<{R}SHLF* z6g~M-XCnKpv`MvnFUbMWyD}@Aqq<#P(U4jVmjr;+`{tiVz-35|JdOi$@Td+moFBW4B(WP>dvWC`PV~3XD2On#b>Ii4j)B$p9Kv)8|)2b2LAY=A(LMov)pCl=3sz zRuxVLKz~|@BvKKK-qABb_bS;YQc~a9INGyL?~oMF*4Zn9GiEe@L9g7aATofYD4L~P z6#nUf;8iaO18N5=(t3`J_h2$WUnp7=b2*tN(<(p`KkIp=9+h}WCqJqTbued;JNo5U z!8?sJlDx`bI*#M_e+Q4`09wRHa*9u41kV<&`R`1uF{lnz=>FLG$@dnKPCftK$|sHQ808u7D4&&RGLbdEYvMG;AJjVHiK(I<(Z=5wAg~gO8 z1D`p7rWkT9Sjt(n&&NsgJ|Pe&v}-{^_Nue0j)?P*flk5b%7c;!wRtP9CbLmG~j+Awkm7jZX8046{*3(tpx^ zBz@^;QjkP-;m)V)XMU=E0E-E%%|4Mtc&nxAC^&-os`dU|?#OY`Wu z(&v`VQ9o&VQNRqKZJHa5Z$LUh-p^`A&_GYT=6pm?Pw)PmucPThZOvI>WWN$4p3ho~ z{jFfnsH3NT7PQkwpJ~sp&><=#D~H4VYvFH$!G!}NAA@WjH7yTo0S5RY4t^D6kjbkw Z`~cbttH@|hC}98q002ovPDHLkV1k|1=!pOT literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L2_Secret_door_128x64/meta.txt b/assets/dolphin/external/L2_Secret_door_128x64/meta.txt new file mode 100644 index 00000000000..4091b12769a --- /dev/null +++ b/assets/dolphin/external/L2_Secret_door_128x64/meta.txt @@ -0,0 +1,14 @@ +Filetype: Flipper Animation +Version: 1 + +Width: 128 +Height: 64 +Passive frames: 29 +Active frames: 24 +Frames order: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 +Active cycles: 1 +Frame rate: 2 +Duration: 3600 +Active cooldown: 7 + +Bubble slots: 0 diff --git a/assets/dolphin/external/manifest.txt b/assets/dolphin/external/manifest.txt index 1d3f3510689..d7348b25cdd 100644 --- a/assets/dolphin/external/manifest.txt +++ b/assets/dolphin/external/manifest.txt @@ -175,3 +175,10 @@ Max butthurt: 12 Min level: 2 Max level: 3 Weight: 4 + +Name: L2_Secret_door_128x64 +Min butthurt: 0 +Max butthurt: 12 +Min level: 2 +Max level: 3 +Weight: 4 From 890c9e87ceac86dc3d70dd3f09657483b1c4209b Mon Sep 17 00:00:00 2001 From: hedger Date: Fri, 1 Dec 2023 13:16:48 +0400 Subject: [PATCH 817/824] [FL-3690] Libraries cleanup; u2f crypto rework to use mbedtls (#3234) * examples: plugins: utilize fal_embedded * libs: removed fnv1a_hash * furi: added FURI_PACKED; apps, libs: changed to use FURI_PACKED * lib: mbedtls: using custom config * lib: toolbox: removed md5, switched to mbedtls * targets: f18: link fix * lib: added mbedtls_cfg.h * apps: nfc: explicit dependency on libmbedtls * u2f: reworking to mbedtls * u2f: replaced sha256 & hmac with mbedtls * u2f: functional rework using mbedtls * libs: dropped micro-ecc * u2f: dropped old implementation * toolbox: removed sha256 impl * mcheck() for mbedtls * libs: removed libmisc; split into smaller libs * apps: debug: fixed display_test * apps: include cleanups * fbt: fixed VERSIONCOMSTR * furi: added FURI_CHECK_RETURN * lib: removed qrcode * cleanup * fbt: lint_py+format_py: fixed excessive command length * api: Removed bzero from f7 * api: Removed bzero from f18 * Bump API Symbols Co-authored-by: Aleksandr Kutuzov --- .pvsoptions | 2 +- SConstruct | 11 +- .../debug/ccid_test/iso7816_t0_apdu.h | 4 +- .../debug/display_test/application.fam | 2 +- applications/debug/unit_tests/rpc/rpc_test.c | 36 +- .../example_plugins_advanced/application.fam | 2 + .../example_advanced_plugins.c | 5 +- applications/main/nfc/application.fam | 2 +- applications/main/u2f/application.fam | 2 +- applications/main/u2f/hmac_sha256.c | 98 - applications/main/u2f/hmac_sha256.h | 38 - applications/main/u2f/u2f.c | 243 +- applications/main/u2f/u2f_data.c | 2 +- applications/services/rpc/rpc_gui.c | 5 +- applications/services/rpc/rpc_i.h | 2 +- applications/services/rpc/rpc_storage.c | 16 +- .../storage_move_to_sd/storage_move_to_sd.c | 4 +- furi/core/common_defines.h | 8 + lib/ReadMe.md | 5 +- lib/SConscript | 73 +- lib/appframe.scons | 3 + lib/digital_signal/SConscript | 3 + lib/drivers/SConscript | 3 + lib/flipper_application/elf/elf_file.c | 7 +- lib/flipper_format/SConscript | 3 + lib/fnv1a-hash/fnv1a-hash.c | 10 - lib/fnv1a-hash/fnv1a-hash.h | 39 - lib/heatshrink.scons | 23 + lib/infrared/SConscript | 3 + lib/mbedtls | 2 +- lib/mbedtls.scons | 32 +- lib/mbedtls_cfg.h | 92 + lib/micro-ecc/LICENSE.txt | 21 - lib/micro-ecc/README.md | 41 - lib/micro-ecc/asm_arm.inc | 821 ------ lib/micro-ecc/asm_arm_mult_square.inc | 2311 ----------------- lib/micro-ecc/asm_arm_mult_square_umaal.inc | 1202 --------- lib/micro-ecc/curve-specific.inc | 1249 --------- lib/micro-ecc/platform-specific.inc | 94 - lib/micro-ecc/types.h | 108 - lib/micro-ecc/uECC.c | 1669 ------------ lib/micro-ecc/uECC.h | 367 --- lib/micro-ecc/uECC_vli.h | 172 -- lib/microtar.scons | 2 +- lib/misc.scons | 58 - lib/mlib.scons | 27 + lib/music_worker/SConscript | 3 + lib/nanopb.scons | 31 + lib/nfc/SConscript | 3 + lib/nfc/protocols/mf_desfire/mf_desfire_i.c | 14 +- .../protocols/mf_ultralight/mf_ultralight.h | 2 +- lib/print/SConscript | 3 + lib/pulse_reader/SConscript | 3 + lib/qrcode/qrcode.c | 975 ------- lib/qrcode/qrcode.h | 99 - lib/signal_reader/SConscript | 5 +- lib/subghz/SConscript | 3 + lib/toolbox/SConscript | 5 +- lib/toolbox/md5.c | 299 --- lib/toolbox/md5.h | 83 - lib/toolbox/md5_calc.c | 44 +- lib/toolbox/sha256.c | 221 -- lib/toolbox/sha256.h | 24 - lib/u8g2/SConscript | 20 + lib/update_util/SConscript | 16 + scripts/fbt_tools/fbt_apps.py | 1 - scripts/fbt_tools/fbt_version.py | 4 +- targets/f18/api_symbols.csv | 211 +- targets/f18/target.json | 12 +- targets/f7/api_symbols.csv | 213 +- targets/f7/furi_hal/furi_hal_usb_ccid.c | 6 +- targets/f7/furi_hal/furi_hal_usb_cdc.c | 4 +- targets/f7/furi_hal/furi_hal_usb_hid.c | 12 +- targets/f7/furi_hal/furi_hal_usb_u2f.c | 2 +- targets/f7/target.json | 8 +- 75 files changed, 888 insertions(+), 10360 deletions(-) delete mode 100644 applications/main/u2f/hmac_sha256.c delete mode 100644 applications/main/u2f/hmac_sha256.h delete mode 100644 lib/fnv1a-hash/fnv1a-hash.c delete mode 100644 lib/fnv1a-hash/fnv1a-hash.h create mode 100644 lib/heatshrink.scons create mode 100644 lib/mbedtls_cfg.h delete mode 100644 lib/micro-ecc/LICENSE.txt delete mode 100644 lib/micro-ecc/README.md delete mode 100644 lib/micro-ecc/asm_arm.inc delete mode 100644 lib/micro-ecc/asm_arm_mult_square.inc delete mode 100644 lib/micro-ecc/asm_arm_mult_square_umaal.inc delete mode 100644 lib/micro-ecc/curve-specific.inc delete mode 100644 lib/micro-ecc/platform-specific.inc delete mode 100644 lib/micro-ecc/types.h delete mode 100644 lib/micro-ecc/uECC.c delete mode 100644 lib/micro-ecc/uECC.h delete mode 100644 lib/micro-ecc/uECC_vli.h delete mode 100644 lib/misc.scons create mode 100644 lib/mlib.scons create mode 100644 lib/nanopb.scons delete mode 100644 lib/qrcode/qrcode.c delete mode 100644 lib/qrcode/qrcode.h delete mode 100644 lib/toolbox/md5.c delete mode 100644 lib/toolbox/md5.h delete mode 100644 lib/toolbox/sha256.c delete mode 100644 lib/toolbox/sha256.h create mode 100644 lib/u8g2/SConscript create mode 100644 lib/update_util/SConscript diff --git a/.pvsoptions b/.pvsoptions index 0312180924d..3337d7eb5c2 100644 --- a/.pvsoptions +++ b/.pvsoptions @@ -1 +1 @@ ---ignore-ccache -C gccarm --rules-config .pvsconfig -e lib/cmsis_core -e lib/fatfs -e lib/fnv1a-hash -e lib/FreeRTOS-Kernel -e lib/heatshrink -e lib/libusb_stm32 -e lib/littlefs -e lib/mbedtls -e lib/micro-ecc -e lib/microtar -e lib/mlib -e lib/qrcode -e lib/stm32wb_cmsis -e lib/stm32wb_copro -e lib/stm32wb_hal -e lib/u8g2 -e lib/nanopb -e */arm-none-eabi/* +--ignore-ccache -C gccarm --rules-config .pvsconfig -e lib/cmsis_core -e lib/fatfs -e lib/fnv1a-hash -e lib/FreeRTOS-Kernel -e lib/heatshrink -e lib/libusb_stm32 -e lib/littlefs -e lib/mbedtls -e lib/microtar -e lib/mlib -e lib/stm32wb_cmsis -e lib/stm32wb_copro -e lib/stm32wb_hal -e lib/u8g2 -e lib/nanopb -e */arm-none-eabi/* diff --git a/SConstruct b/SConstruct index a2c5cd9e7af..b42218a579c 100644 --- a/SConstruct +++ b/SConstruct @@ -288,13 +288,17 @@ distenv.PhonyTarget( LINT_SOURCES=[n.srcnode() for n in firmware_env["LINT_SOURCES"]], ) -# PY_LINT_SOURCES contains recursively-built modules' SConscript files + application manifests +# PY_LINT_SOURCES contains recursively-built modules' SConscript files # Here we add additional Python files residing in repo root firmware_env.Append( PY_LINT_SOURCES=[ # Py code folders "site_scons", "scripts", + "applications", + "applications_user", + "assets", + "targets", # Extra files "SConstruct", "firmware.scons", @@ -304,7 +308,10 @@ firmware_env.Append( black_commandline = "@${PYTHON3} -m black ${PY_BLACK_ARGS} ${PY_LINT_SOURCES}" -black_base_args = ["--include", '"\\.scons|\\.py|SConscript|SConstruct"'] +black_base_args = [ + "--include", + '"(\\.scons|\\.py|SConscript|SConstruct|\\.fam)$"', +] distenv.PhonyTarget( "lint_py", diff --git a/applications/debug/ccid_test/iso7816_t0_apdu.h b/applications/debug/ccid_test/iso7816_t0_apdu.h index b66d66054da..5ca13eb6041 100644 --- a/applications/debug/ccid_test/iso7816_t0_apdu.h +++ b/applications/debug/ccid_test/iso7816_t0_apdu.h @@ -13,12 +13,12 @@ struct ISO7816_Command_APDU { //body uint8_t Lc; uint8_t Le; -} __attribute__((packed)); +} FURI_PACKED; struct ISO7816_Response_APDU { uint8_t SW1; uint8_t SW2; -} __attribute__((packed)); +} FURI_PACKED; void iso7816_answer_to_reset(uint8_t* atrBuffer, uint32_t* atrlen); void iso7816_read_command_apdu( diff --git a/applications/debug/display_test/application.fam b/applications/debug/display_test/application.fam index 6a2d9c20c1a..7b2357b01d8 100644 --- a/applications/debug/display_test/application.fam +++ b/applications/debug/display_test/application.fam @@ -4,7 +4,7 @@ App( apptype=FlipperAppType.DEBUG, entry_point="display_test_app", requires=["gui"], - fap_libs=["misc"], + fap_libs=["u8g2"], stack_size=1 * 1024, order=120, fap_category="Debug", diff --git a/applications/debug/unit_tests/rpc/rpc_test.c b/applications/debug/unit_tests/rpc/rpc_test.c index 5659ba877df..3faf6157211 100644 --- a/applications/debug/unit_tests/rpc/rpc_test.c +++ b/applications/debug/unit_tests/rpc/rpc_test.c @@ -1,27 +1,31 @@ -#include "flipper.pb.h" #include #include -#include "pb_decode.h" -#include -#include "rpc/rpc_i.h" -#include "storage.pb.h" -#include "storage/filesystem_api_defines.h" -#include "storage/storage.h" #include -#include "../minunit.h" #include -#include -#include -#include -#include -#include -#include -#include -#include #include #include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include "../minunit.h" + +#include +#include +#include +#include +#include +#include + LIST_DEF(MsgList, PB_Main, M_POD_OPLIST) #define M_OPL_MsgList_t() LIST_OPLIST(MsgList) diff --git a/applications/examples/example_plugins_advanced/application.fam b/applications/examples/example_plugins_advanced/application.fam index 0c7e3e3b94d..10fdc042ff2 100644 --- a/applications/examples/example_plugins_advanced/application.fam +++ b/applications/examples/example_plugins_advanced/application.fam @@ -14,6 +14,7 @@ App( entry_point="advanced_plugin1_ep", requires=["example_advanced_plugins"], sources=["plugin1.c"], + fal_embedded=True, ) App( @@ -22,4 +23,5 @@ App( entry_point="advanced_plugin2_ep", requires=["example_advanced_plugins"], sources=["plugin2.c"], + fal_embedded=True, ) diff --git a/applications/examples/example_plugins_advanced/example_advanced_plugins.c b/applications/examples/example_plugins_advanced/example_advanced_plugins.c index 2b137e1d484..77ab8205103 100644 --- a/applications/examples/example_plugins_advanced/example_advanced_plugins.c +++ b/applications/examples/example_plugins_advanced/example_advanced_plugins.c @@ -23,7 +23,10 @@ int32_t example_advanced_plugins_app(void* p) { PLUGIN_APP_ID, PLUGIN_API_VERSION, composite_api_resolver_get(resolver)); do { - if(plugin_manager_load_all(manager, APP_DATA_PATH("plugins")) != PluginManagerErrorNone) { + // For built-in .fals (fal_embedded==True), use APP_ASSETS_PATH + // Otherwise, use APP_DATA_PATH + if(plugin_manager_load_all(manager, APP_ASSETS_PATH("plugins")) != + PluginManagerErrorNone) { FURI_LOG_E(TAG, "Failed to load all libs"); break; } diff --git a/applications/main/nfc/application.fam b/applications/main/nfc/application.fam index 33a2011a70e..9a98b57c8c3 100644 --- a/applications/main/nfc/application.fam +++ b/applications/main/nfc/application.fam @@ -13,7 +13,7 @@ App( "!plugins", "!nfc_cli.c", ], - fap_libs=["assets"], + fap_libs=["assets", "mbedtls"], fap_icon="icon.png", fap_category="NFC", ) diff --git a/applications/main/u2f/application.fam b/applications/main/u2f/application.fam index bf41eb0f7a5..5e0cde736c9 100644 --- a/applications/main/u2f/application.fam +++ b/applications/main/u2f/application.fam @@ -7,7 +7,7 @@ App( icon="A_U2F_14", order=80, resources="resources", - fap_libs=["assets"], + fap_libs=["assets", "mbedtls"], fap_category="USB", fap_icon="icon.png", ) diff --git a/applications/main/u2f/hmac_sha256.c b/applications/main/u2f/hmac_sha256.c deleted file mode 100644 index 611aa2a6f48..00000000000 --- a/applications/main/u2f/hmac_sha256.c +++ /dev/null @@ -1,98 +0,0 @@ -/* - * hmac.c - HMAC - * - * Copyright (C) 2017 Sergei Glushchenko - * Author: Sergei Glushchenko - * - * This file is a part of U2F firmware for STM32 - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * As additional permission under GNU GPL version 3 section 7, you may - * distribute non-source form of the Program without the copy of the - * GNU GPL normally required by section 4, provided you inform the - * recipients of GNU GPL by a written offer. - * - */ -#include - -#include "sha256.h" -#include "hmac_sha256.h" - -static void _hmac_sha256_init(const hmac_context* ctx) { - hmac_sha256_context* context = (hmac_sha256_context*)ctx; - sha256_start(&context->sha_ctx); -} - -static void - _hmac_sha256_update(const hmac_context* ctx, const uint8_t* message, unsigned message_size) { - hmac_sha256_context* context = (hmac_sha256_context*)ctx; - sha256_update(&context->sha_ctx, message, message_size); -} - -static void _hmac_sha256_finish(const hmac_context* ctx, uint8_t* hash_result) { - hmac_sha256_context* context = (hmac_sha256_context*)ctx; - sha256_finish(&context->sha_ctx, hash_result); -} - -/* Compute an HMAC using K as a key (as in RFC 6979). Note that K is always - the same size as the hash result size. */ -static void hmac_init(const hmac_context* ctx, const uint8_t* K) { - uint8_t* pad = ctx->tmp + 2 * ctx->result_size; - unsigned i; - for(i = 0; i < ctx->result_size; ++i) pad[i] = K[i] ^ 0x36; - for(; i < ctx->block_size; ++i) pad[i] = 0x36; - - ctx->init_hash(ctx); - ctx->update_hash(ctx, pad, ctx->block_size); -} - -static void hmac_update(const hmac_context* ctx, const uint8_t* message, unsigned message_size) { - ctx->update_hash(ctx, message, message_size); -} - -static void hmac_finish(const hmac_context* ctx, const uint8_t* K, uint8_t* result) { - uint8_t* pad = ctx->tmp + 2 * ctx->result_size; - unsigned i; - for(i = 0; i < ctx->result_size; ++i) pad[i] = K[i] ^ 0x5c; - for(; i < ctx->block_size; ++i) pad[i] = 0x5c; - - ctx->finish_hash(ctx, result); - - ctx->init_hash(ctx); - ctx->update_hash(ctx, pad, ctx->block_size); - ctx->update_hash(ctx, result, ctx->result_size); - ctx->finish_hash(ctx, result); -} - -void hmac_sha256_init(hmac_sha256_context* ctx, const uint8_t* K) { - ctx->hmac_ctx.init_hash = _hmac_sha256_init; - ctx->hmac_ctx.update_hash = _hmac_sha256_update; - ctx->hmac_ctx.finish_hash = _hmac_sha256_finish; - ctx->hmac_ctx.block_size = 64; - ctx->hmac_ctx.result_size = 32; - ctx->hmac_ctx.tmp = ctx->tmp; - hmac_init(&ctx->hmac_ctx, K); -} - -void hmac_sha256_update( - const hmac_sha256_context* ctx, - const uint8_t* message, - unsigned message_size) { - hmac_update(&ctx->hmac_ctx, message, message_size); -} - -void hmac_sha256_finish(const hmac_sha256_context* ctx, const uint8_t* K, uint8_t* hash_result) { - hmac_finish(&ctx->hmac_ctx, K, hash_result); -} diff --git a/applications/main/u2f/hmac_sha256.h b/applications/main/u2f/hmac_sha256.h deleted file mode 100644 index add123142d4..00000000000 --- a/applications/main/u2f/hmac_sha256.h +++ /dev/null @@ -1,38 +0,0 @@ -#pragma once - -#include "sha256.h" - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct hmac_context { - void (*init_hash)(const struct hmac_context* context); - void (*update_hash)( - const struct hmac_context* context, - const uint8_t* message, - unsigned message_size); - void (*finish_hash)(const struct hmac_context* context, uint8_t* hash_result); - unsigned block_size; /* Hash function block size in bytes, eg 64 for SHA-256. */ - unsigned result_size; /* Hash function result size in bytes, eg 32 for SHA-256. */ - uint8_t* tmp; /* Must point to a buffer of at least (2 * result_size + block_size) bytes. */ -} hmac_context; - -typedef struct hmac_sha256_context { - hmac_context hmac_ctx; - sha256_context sha_ctx; - uint8_t tmp[32 * 2 + 64]; -} hmac_sha256_context; - -void hmac_sha256_init(hmac_sha256_context* ctx, const uint8_t* K); - -void hmac_sha256_update( - const hmac_sha256_context* ctx, - const uint8_t* message, - unsigned message_size); - -void hmac_sha256_finish(const hmac_sha256_context* ctx, const uint8_t* K, uint8_t* hash_result); - -#ifdef __cplusplus -} -#endif diff --git a/applications/main/u2f/u2f.c b/applications/main/u2f/u2f.c index ce70212a9fa..3bdafe9185e 100644 --- a/applications/main/u2f/u2f.c +++ b/applications/main/u2f/u2f.c @@ -1,18 +1,22 @@ -#include #include "u2f.h" #include "u2f_hid.h" #include "u2f_data.h" + +#include #include #include #include // for lfs_tobe32 -#include "toolbox/sha256.h" -#include "hmac_sha256.h" -#include "micro-ecc/uECC.h" +#include +#include +#include +#include #define TAG "U2f" #define WORKER_TAG TAG "Worker" +#define MCHECK(expr) furi_check((expr) == 0) + #define U2F_CMD_REGISTER 0x01 #define U2F_CMD_AUTHENTICATE 0x02 #define U2F_CMD_VERSION 0x03 @@ -25,16 +29,26 @@ typedef enum { 0x08, // "dont-enforce-user-presence-and-sign" - send auth response even if user is missing } U2fAuthMode; +#define U2F_HASH_SIZE 32 +#define U2F_NONCE_SIZE 32 +#define U2F_CHALLENGE_SIZE 32 +#define U2F_APP_ID_SIZE 32 + +#define U2F_EC_KEY_SIZE 32 +#define U2F_EC_BIGNUM_SIZE 32 +#define U2F_EC_POINT_SIZE 65 + typedef struct { uint8_t format; uint8_t xy[64]; -} __attribute__((packed)) U2fPubKey; +} FURI_PACKED U2fPubKey; +_Static_assert(sizeof(U2fPubKey) == U2F_EC_POINT_SIZE, "U2fPubKey size mismatch"); typedef struct { uint8_t len; - uint8_t hash[32]; - uint8_t nonce[32]; -} __attribute__((packed)) U2fKeyHandle; + uint8_t hash[U2F_HASH_SIZE]; + uint8_t nonce[U2F_NONCE_SIZE]; +} FURI_PACKED U2fKeyHandle; typedef struct { uint8_t cla; @@ -42,16 +56,16 @@ typedef struct { uint8_t p1; uint8_t p2; uint8_t len[3]; - uint8_t challenge[32]; - uint8_t app_id[32]; -} __attribute__((packed)) U2fRegisterReq; + uint8_t challenge[U2F_CHALLENGE_SIZE]; + uint8_t app_id[U2F_APP_ID_SIZE]; +} FURI_PACKED U2fRegisterReq; typedef struct { uint8_t reserved; U2fPubKey pub_key; U2fKeyHandle key_handle; uint8_t cert[]; -} __attribute__((packed)) U2fRegisterResp; +} FURI_PACKED U2fRegisterResp; typedef struct { uint8_t cla; @@ -59,16 +73,16 @@ typedef struct { uint8_t p1; uint8_t p2; uint8_t len[3]; - uint8_t challenge[32]; - uint8_t app_id[32]; + uint8_t challenge[U2F_CHALLENGE_SIZE]; + uint8_t app_id[U2F_APP_ID_SIZE]; U2fKeyHandle key_handle; -} __attribute__((packed)) U2fAuthReq; +} FURI_PACKED U2fAuthReq; typedef struct { uint8_t user_present; uint32_t counter; uint8_t signature[]; -} __attribute__((packed)) U2fAuthResp; +} FURI_PACKED U2fAuthResp; static const uint8_t ver_str[] = {"U2F_V2"}; @@ -78,19 +92,20 @@ static const uint8_t state_user_missing[] = {0x69, 0x85}; static const uint8_t state_wrong_data[] = {0x6A, 0x80}; struct U2fData { - uint8_t device_key[32]; - uint8_t cert_key[32]; + uint8_t device_key[U2F_EC_KEY_SIZE]; + uint8_t cert_key[U2F_EC_KEY_SIZE]; uint32_t counter; - const struct uECC_Curve_t* p_curve; bool ready; bool user_present; U2fEvtCallback callback; void* context; + mbedtls_ecp_group group; }; -static int u2f_uecc_random(uint8_t* dest, unsigned size) { +static int u2f_uecc_random_cb(void* context, uint8_t* dest, unsigned size) { + UNUSED(context); furi_hal_random_fill_buf(dest, size); - return 1; + return 0; } U2fData* u2f_alloc() { @@ -99,6 +114,7 @@ U2fData* u2f_alloc() { void u2f_free(U2fData* U2F) { furi_assert(U2F); + mbedtls_ecp_group_free(&U2F->group); free(U2F); } @@ -129,8 +145,8 @@ bool u2f_init(U2fData* U2F) { } } - U2F->p_curve = uECC_secp256r1(); - uECC_set_rng(u2f_uecc_random); + mbedtls_ecp_group_init(&U2F->group); + mbedtls_ecp_group_load(&U2F->group, MBEDTLS_ECP_DP_SECP256R1); U2F->ready = true; return true; @@ -171,21 +187,63 @@ static uint8_t u2f_der_encode_signature(uint8_t* der, uint8_t* sig) { der[0] = 0x30; uint8_t len = 2; - len += u2f_der_encode_int(der + len, sig, 32); - len += u2f_der_encode_int(der + len, sig + 32, 32); + len += u2f_der_encode_int(der + len, sig, U2F_HASH_SIZE); + len += u2f_der_encode_int(der + len, sig + U2F_HASH_SIZE, U2F_HASH_SIZE); der[1] = len - 2; return len; } +static void + u2f_ecc_sign(mbedtls_ecp_group* grp, const uint8_t* key, uint8_t* hash, uint8_t* signature) { + mbedtls_mpi r, s, d; + + mbedtls_mpi_init(&r); + mbedtls_mpi_init(&s); + mbedtls_mpi_init(&d); + + MCHECK(mbedtls_mpi_read_binary(&d, key, U2F_EC_KEY_SIZE)); + MCHECK(mbedtls_ecdsa_sign(grp, &r, &s, &d, hash, U2F_HASH_SIZE, u2f_uecc_random_cb, NULL)); + MCHECK(mbedtls_mpi_write_binary(&r, signature, U2F_EC_BIGNUM_SIZE)); + MCHECK(mbedtls_mpi_write_binary(&s, signature + U2F_EC_BIGNUM_SIZE, U2F_EC_BIGNUM_SIZE)); + + mbedtls_mpi_free(&r); + mbedtls_mpi_free(&s); + mbedtls_mpi_free(&d); +} + +static void u2f_ecc_compute_public_key( + mbedtls_ecp_group* grp, + const uint8_t* private_key, + U2fPubKey* public_key) { + mbedtls_ecp_point Q; + mbedtls_mpi d; + size_t olen; + + mbedtls_ecp_point_init(&Q); + mbedtls_mpi_init(&d); + + MCHECK(mbedtls_mpi_read_binary(&d, private_key, U2F_EC_KEY_SIZE)); + MCHECK(mbedtls_ecp_mul(grp, &Q, &d, &grp->G, u2f_uecc_random_cb, NULL)); + MCHECK(mbedtls_ecp_check_privkey(grp, &d)); + + MCHECK(mbedtls_ecp_point_write_binary( + grp, &Q, MBEDTLS_ECP_PF_UNCOMPRESSED, &olen, (unsigned char*)public_key, sizeof(U2fPubKey))); + + mbedtls_ecp_point_free(&Q); + mbedtls_mpi_free(&d); +} + +/////////////////////////////////////////// + static uint16_t u2f_register(U2fData* U2F, uint8_t* buf) { U2fRegisterReq* req = (U2fRegisterReq*)buf; U2fRegisterResp* resp = (U2fRegisterResp*)buf; U2fKeyHandle handle; - uint8_t private[32]; + uint8_t private[U2F_EC_KEY_SIZE]; U2fPubKey pub_key; - uint8_t hash[32]; - uint8_t signature[64]; + uint8_t hash[U2F_HASH_SIZE]; + uint8_t signature[U2F_EC_BIGNUM_SIZE * 2]; if(u2f_data_check(false) == false) { U2F->ready = false; @@ -201,40 +259,54 @@ static uint16_t u2f_register(U2fData* U2F, uint8_t* buf) { } U2F->user_present = false; - hmac_sha256_context hmac_ctx; - sha256_context sha_ctx; + handle.len = U2F_HASH_SIZE * 2; - handle.len = 32 * 2; // Generate random nonce furi_hal_random_fill_buf(handle.nonce, 32); - // Generate private key - hmac_sha256_init(&hmac_ctx, U2F->device_key); - hmac_sha256_update(&hmac_ctx, req->app_id, 32); - hmac_sha256_update(&hmac_ctx, handle.nonce, 32); - hmac_sha256_finish(&hmac_ctx, U2F->device_key, private); + { + mbedtls_md_context_t hmac_ctx; + mbedtls_md_init(&hmac_ctx); + MCHECK(mbedtls_md_setup(&hmac_ctx, mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), 1)); + MCHECK(mbedtls_md_hmac_starts(&hmac_ctx, U2F->device_key, sizeof(U2F->device_key))); - // Generate private key handle - hmac_sha256_init(&hmac_ctx, U2F->device_key); - hmac_sha256_update(&hmac_ctx, private, 32); - hmac_sha256_update(&hmac_ctx, req->app_id, 32); - hmac_sha256_finish(&hmac_ctx, U2F->device_key, handle.hash); + // Generate private key + MCHECK(mbedtls_md_hmac_update(&hmac_ctx, req->app_id, sizeof(req->app_id))); + MCHECK(mbedtls_md_hmac_update(&hmac_ctx, handle.nonce, sizeof(handle.nonce))); + MCHECK(mbedtls_md_hmac_finish(&hmac_ctx, private)); + + MCHECK(mbedtls_md_hmac_reset(&hmac_ctx)); + + // Generate private key handle + MCHECK(mbedtls_md_hmac_update(&hmac_ctx, private, sizeof(private))); + MCHECK(mbedtls_md_hmac_update(&hmac_ctx, req->app_id, sizeof(req->app_id))); + MCHECK(mbedtls_md_hmac_finish(&hmac_ctx, handle.hash)); + } // Generate public key - pub_key.format = 0x04; // Uncompressed point - uECC_compute_public_key(private, pub_key.xy, U2F->p_curve); + u2f_ecc_compute_public_key(&U2F->group, private, &pub_key); // Generate signature - uint8_t reserved_byte = 0; - sha256_start(&sha_ctx); - sha256_update(&sha_ctx, &reserved_byte, 1); - sha256_update(&sha_ctx, req->app_id, 32); - sha256_update(&sha_ctx, req->challenge, 32); - sha256_update(&sha_ctx, handle.hash, handle.len); - sha256_update(&sha_ctx, (uint8_t*)&pub_key, 65); - sha256_finish(&sha_ctx, hash); + { + uint8_t reserved_byte = 0; + + mbedtls_sha256_context sha_ctx; + + mbedtls_sha256_init(&sha_ctx); + mbedtls_sha256_starts(&sha_ctx, 0); - uECC_sign(U2F->cert_key, hash, 32, signature, U2F->p_curve); + mbedtls_sha256_update(&sha_ctx, &reserved_byte, 1); + mbedtls_sha256_update(&sha_ctx, req->app_id, sizeof(req->app_id)); + mbedtls_sha256_update(&sha_ctx, req->challenge, sizeof(req->challenge)); + mbedtls_sha256_update(&sha_ctx, handle.hash, handle.len); + mbedtls_sha256_update(&sha_ctx, (uint8_t*)&pub_key, sizeof(U2fPubKey)); + + mbedtls_sha256_finish(&sha_ctx, hash); + mbedtls_sha256_free(&sha_ctx); + } + + // Sign hash + u2f_ecc_sign(&U2F->group, U2F->cert_key, hash, signature); // Encode response message resp->reserved = 0x05; @@ -250,13 +322,11 @@ static uint16_t u2f_register(U2fData* U2F, uint8_t* buf) { static uint16_t u2f_authenticate(U2fData* U2F, uint8_t* buf) { U2fAuthReq* req = (U2fAuthReq*)buf; U2fAuthResp* resp = (U2fAuthResp*)buf; - uint8_t priv_key[32]; + uint8_t priv_key[U2F_EC_KEY_SIZE]; uint8_t mac_control[32]; - hmac_sha256_context hmac_ctx; - sha256_context sha_ctx; uint8_t flags = 0; - uint8_t hash[32]; - uint8_t signature[64]; + uint8_t hash[U2F_HASH_SIZE]; + uint8_t signature[U2F_HASH_SIZE * 2]; uint32_t be_u2f_counter; if(u2f_data_check(false) == false) { @@ -281,26 +351,42 @@ static uint16_t u2f_authenticate(U2fData* U2F, uint8_t* buf) { be_u2f_counter = lfs_tobe32(U2F->counter + 1); // Generate hash - sha256_start(&sha_ctx); - sha256_update(&sha_ctx, req->app_id, 32); - sha256_update(&sha_ctx, &flags, 1); - sha256_update(&sha_ctx, (uint8_t*)&(be_u2f_counter), 4); - sha256_update(&sha_ctx, req->challenge, 32); - sha256_finish(&sha_ctx, hash); - - // Recover private key - hmac_sha256_init(&hmac_ctx, U2F->device_key); - hmac_sha256_update(&hmac_ctx, req->app_id, 32); - hmac_sha256_update(&hmac_ctx, req->key_handle.nonce, 32); - hmac_sha256_finish(&hmac_ctx, U2F->device_key, priv_key); - - // Generate and verify private key handle - hmac_sha256_init(&hmac_ctx, U2F->device_key); - hmac_sha256_update(&hmac_ctx, priv_key, 32); - hmac_sha256_update(&hmac_ctx, req->app_id, 32); - hmac_sha256_finish(&hmac_ctx, U2F->device_key, mac_control); - - if(memcmp(req->key_handle.hash, mac_control, 32) != 0) { + { + mbedtls_sha256_context sha_ctx; + + mbedtls_sha256_init(&sha_ctx); + mbedtls_sha256_starts(&sha_ctx, 0); + + mbedtls_sha256_update(&sha_ctx, req->app_id, sizeof(req->app_id)); + mbedtls_sha256_update(&sha_ctx, &flags, 1); + mbedtls_sha256_update(&sha_ctx, (uint8_t*)&(be_u2f_counter), sizeof(be_u2f_counter)); + mbedtls_sha256_update(&sha_ctx, req->challenge, sizeof(req->challenge)); + + mbedtls_sha256_finish(&sha_ctx, hash); + mbedtls_sha256_free(&sha_ctx); + } + + { + mbedtls_md_context_t hmac_ctx; + mbedtls_md_init(&hmac_ctx); + MCHECK(mbedtls_md_setup(&hmac_ctx, mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), 1)); + MCHECK(mbedtls_md_hmac_starts(&hmac_ctx, U2F->device_key, sizeof(U2F->device_key))); + + // Recover private key + MCHECK(mbedtls_md_hmac_update(&hmac_ctx, req->app_id, sizeof(req->app_id))); + MCHECK(mbedtls_md_hmac_update( + &hmac_ctx, req->key_handle.nonce, sizeof(req->key_handle.nonce))); + MCHECK(mbedtls_md_hmac_finish(&hmac_ctx, priv_key)); + + MCHECK(mbedtls_md_hmac_reset(&hmac_ctx)); + + // Generate and verify private key handle + MCHECK(mbedtls_md_hmac_update(&hmac_ctx, priv_key, sizeof(priv_key))); + MCHECK(mbedtls_md_hmac_update(&hmac_ctx, req->app_id, sizeof(req->app_id))); + MCHECK(mbedtls_md_hmac_finish(&hmac_ctx, mac_control)); + } + + if(memcmp(req->key_handle.hash, mac_control, sizeof(mac_control)) != 0) { FURI_LOG_W(TAG, "Wrong handle!"); memcpy(&buf[0], state_wrong_data, 2); return 2; @@ -311,7 +397,8 @@ static uint16_t u2f_authenticate(U2fData* U2F, uint8_t* buf) { return 2; } - uECC_sign(priv_key, hash, 32, signature, U2F->p_curve); + // Sign hash + u2f_ecc_sign(&U2F->group, priv_key, hash, signature); resp->user_present = flags; resp->counter = be_u2f_counter; diff --git a/applications/main/u2f/u2f_data.c b/applications/main/u2f/u2f_data.c index 34360f3d639..cb9c4c2947d 100644 --- a/applications/main/u2f/u2f_data.c +++ b/applications/main/u2f/u2f_data.c @@ -37,7 +37,7 @@ typedef struct { uint32_t counter; uint8_t random_salt[24]; uint32_t control; -} __attribute__((packed)) U2fCounterData; +} FURI_PACKED U2fCounterData; bool u2f_data_check(bool cert_only) { bool state = false; diff --git a/applications/services/rpc/rpc_gui.c b/applications/services/rpc/rpc_gui.c index 9eff4bca6c5..ca8fc61a45f 100644 --- a/applications/services/rpc/rpc_gui.c +++ b/applications/services/rpc/rpc_gui.c @@ -1,9 +1,10 @@ -#include "flipper.pb.h" #include "rpc_i.h" -#include "gui.pb.h" #include #include +#include +#include + #define TAG "RpcGui" typedef enum { diff --git a/applications/services/rpc/rpc_i.h b/applications/services/rpc/rpc_i.h index 16e5e594d16..ffca50231c7 100644 --- a/applications/services/rpc/rpc_i.h +++ b/applications/services/rpc/rpc_i.h @@ -1,6 +1,6 @@ #pragma once #include "rpc.h" -#include "storage/filesystem_api_defines.h" +#include #include #include #include diff --git a/applications/services/rpc/rpc_storage.c b/applications/services/rpc/rpc_storage.c index 913d89f1afb..a934d1c31a1 100644 --- a/applications/services/rpc/rpc_storage.c +++ b/applications/services/rpc/rpc_storage.c @@ -1,18 +1,18 @@ -#include "flipper.pb.h" #include #include #include -#include "pb_decode.h" -#include "rpc/rpc.h" -#include "rpc_i.h" -#include "storage.pb.h" -#include "storage/filesystem_api_defines.h" -#include "storage/storage.h" -#include +#include +#include +#include +#include #include #include #include +#include +#include +#include + #define TAG "RpcStorage" #define MAX_NAME_LENGTH 255 diff --git a/applications/system/storage_move_to_sd/storage_move_to_sd.c b/applications/system/storage_move_to_sd/storage_move_to_sd.c index 25893f01106..5d1e694bca4 100644 --- a/applications/system/storage_move_to_sd/storage_move_to_sd.c +++ b/applications/system/storage_move_to_sd/storage_move_to_sd.c @@ -1,8 +1,8 @@ #include "storage_move_to_sd.h" + #include #include -#include "loader/loader.h" -#include +#include #include #include diff --git a/furi/core/common_defines.h b/furi/core/common_defines.h index 2b30c3b06da..b0062e65916 100644 --- a/furi/core/common_defines.h +++ b/furi/core/common_defines.h @@ -17,6 +17,10 @@ extern "C" { #define FURI_WEAK __attribute__((weak)) #endif +#ifndef FURI_PACKED +#define FURI_PACKED __attribute__((packed)) +#endif + #ifndef FURI_IS_IRQ_MASKED #define FURI_IS_IRQ_MASKED() (__get_PRIMASK() != 0U) #endif @@ -47,6 +51,10 @@ void __furi_critical_exit(__FuriCriticalInfo info); #define FURI_CRITICAL_EXIT() __furi_critical_exit(__furi_critical_info); #endif +#ifndef FURI_CHECK_RETURN +#define FURI_CHECK_RETURN __attribute__((__warn_unused_result__)) +#endif + #ifdef __cplusplus } #endif diff --git a/lib/ReadMe.md b/lib/ReadMe.md index 326d933aa5d..3adb7701877 100644 --- a/lib/ReadMe.md +++ b/lib/ReadMe.md @@ -11,7 +11,6 @@ - `fatfs` - FatFS file system driver - `flipper_application` - Flipper application library, used for FAPs - `flipper_format` - Flipper File Format library -- `fnv1a-hash` - FNV-1a hash library - `heatshrink` - Heatshrink compression library - `ibutton` - ibutton library, used by iButton application - `infrared` - Infrared library, used by Infrared application @@ -19,7 +18,6 @@ - `libusb_stm32` - LibUSB for STM32 series MCU - `littlefs` - LittleFS file system driver, used by internal storage - `mbedtls` - MbedTLS cryptography library -- `micro-ecc` - MicroECC cryptography library - `microtar` - MicroTAR library - `mlib` - M-Lib C containers library - `nanopb` - NanoPB library, protobuf implementation for MCU @@ -28,11 +26,10 @@ - `print` - Tiny printf implementation - `digital_signal` - Digital Signal library used by NFC for software implemented protocols - `pulse_reader` - Pulse Reader library used by NFC for software implemented protocols -- `qrcode` - QR-Code library - `stm32wb_cmsis` - STM32WB series CMSIS headers, extends CMSIS Core - `stm32wb_copro` - STM32WB Copro library: contains WPAN and radio co-processor firmware - `stm32wb_hal` - STM32WB HAL library, extends STM32WB CMSIS and provides HAL - `subghz` - Subghz library, used by SubGhz application -- `toolbox` - Toolbox library, contains various things that is used by flipper firmware +- `toolbox` - Toolbox library, contains various things that is used by Flipper firmware - `u8g2` - u8g2 graphics library, used by GUI subsystem - `update_util` - update utilities library, used by updater \ No newline at end of file diff --git a/lib/SConscript b/lib/SConscript index f2cc9d18a0d..4835724e09b 100644 --- a/lib/SConscript +++ b/lib/SConscript @@ -1,87 +1,24 @@ Import("env") -env.Append( - LINT_SOURCES=[ - Dir("app-scened-template"), - Dir("digital_signal"), - Dir("pulse_reader"), - Dir("signal_reader"), - Dir("drivers"), - Dir("flipper_format"), - Dir("infrared"), - Dir("nfc"), - Dir("subghz"), - Dir("toolbox"), - Dir("u8g2"), - Dir("update_util"), - Dir("print"), - Dir("music_worker"), - ], -) - env.Append( CPPPATH=[ "#/", "#/lib", # TODO FL-3553: remove! - "#/lib/mlib", # Ugly hack Dir("../assets/compiled"), ], - SDK_HEADERS=[ - *( - File(f"#/lib/mlib/m-{name}.h") - for name in ( - "algo", - "array", - "bptree", - "core", - "deque", - "dict", - "list", - "rbtree", - "tuple", - "variant", - ) - ), - ], - CPPDEFINES=[ - '"M_MEMORY_FULL(x)=abort()"', - ], ) -# drivers -# fatfs -# flipper_format -# infrared -# littlefs -# subghz -# toolbox -# one_wire -# micro-ecc -# misc -# digital_signal -# fnv1a_hash -# microtar -# nfc -# qrcode -# u8g2 -# update_util -# heatshrink -# nanopb -# apps -# app-scened-template -# callback-connector -# app-template - - libs = env.BuildModules( [ + "mlib", "stm32wb", "freertos", "print", "microtar", + "mbedtls", "toolbox", "libusb_stm32", "drivers", @@ -91,17 +28,19 @@ libs = env.BuildModules( "ibutton", "infrared", "littlefs", - "mbedtls", "subghz", "nfc", "digital_signal", "pulse_reader", "signal_reader", "appframe", - "misc", + "u8g2", "lfrfid", "flipper_application", "music_worker", + "nanopb", + "update_util", + "heatshrink", ], ) diff --git a/lib/appframe.scons b/lib/appframe.scons index 935986d644f..fb268579d66 100644 --- a/lib/appframe.scons +++ b/lib/appframe.scons @@ -5,6 +5,9 @@ env.Append( "#/lib/app-scened-template", "#/lib/callback-connector", ], + LINT_SOURCES=[ + Dir("app-scened-template"), + ], ) diff --git a/lib/digital_signal/SConscript b/lib/digital_signal/SConscript index b94c26f780a..41d33489047 100644 --- a/lib/digital_signal/SConscript +++ b/lib/digital_signal/SConscript @@ -8,6 +8,9 @@ env.Append( File("digital_signal.h"), File("digital_sequence.h"), ], + LINT_SOURCES=[ + Dir("."), + ], ) libenv = env.Clone(FW_LIB_NAME="digital_signal") diff --git a/lib/drivers/SConscript b/lib/drivers/SConscript index cf93d4bce98..a790d54a7a4 100644 --- a/lib/drivers/SConscript +++ b/lib/drivers/SConscript @@ -9,6 +9,9 @@ env.Append( File("st25r3916_reg.h"), File("st25r3916.h"), ], + LINT_SOURCES=[ + Dir("."), + ], ) diff --git a/lib/flipper_application/elf/elf_file.c b/lib/flipper_application/elf/elf_file.c index 8a78cca413d..65431686625 100644 --- a/lib/flipper_application/elf/elf_file.c +++ b/lib/flipper_application/elf/elf_file.c @@ -1,7 +1,8 @@ -#include "storage/storage.h" -#include #include "elf_file.h" #include "elf_file_i.h" + +#include +#include #include "elf_api_interface.h" #include "../api_hashtable/api_hashtable.h" @@ -34,7 +35,7 @@ const uint8_t trampoline_code_little_endian[TRAMPOLINE_CODE_SIZE] = typedef struct { uint8_t code[TRAMPOLINE_CODE_SIZE]; uint32_t addr; -} __attribute__((packed)) JMPTrampoline; +} FURI_PACKED JMPTrampoline; /**************************************************************************************************/ /********************************************* Caches *********************************************/ diff --git a/lib/flipper_format/SConscript b/lib/flipper_format/SConscript index 9c9e8b6f338..f16cd4f391c 100644 --- a/lib/flipper_format/SConscript +++ b/lib/flipper_format/SConscript @@ -9,6 +9,9 @@ env.Append( File("flipper_format_i.h"), File("flipper_format_stream.h"), ], + LINT_SOURCES=[ + Dir("."), + ], ) diff --git a/lib/fnv1a-hash/fnv1a-hash.c b/lib/fnv1a-hash/fnv1a-hash.c deleted file mode 100644 index 69c675f310a..00000000000 --- a/lib/fnv1a-hash/fnv1a-hash.c +++ /dev/null @@ -1,10 +0,0 @@ -#include "fnv1a-hash.h" - -// FNV-1a hash, 32-bit -uint32_t fnv1a_buffer_hash(const uint8_t* buffer, uint32_t length, uint32_t hash) -{ - for (uint32_t i = 0; i < length; i++) { - hash = (hash ^ buffer[i]) * 16777619ULL; - } - return hash; -} diff --git a/lib/fnv1a-hash/fnv1a-hash.h b/lib/fnv1a-hash/fnv1a-hash.h deleted file mode 100644 index 3218cc27c75..00000000000 --- a/lib/fnv1a-hash/fnv1a-hash.h +++ /dev/null @@ -1,39 +0,0 @@ -#pragma once -#include - -#ifdef __cplusplus -extern "C" { -#endif - -#define FNV_1A_INIT 2166136261UL - -// FNV-1a hash, 32-bit -uint32_t fnv1a_buffer_hash(const uint8_t* buffer, uint32_t length, uint32_t hash); - -#ifdef __cplusplus -} -#endif - -#ifdef __cplusplus -// constexpr FNV-1a hash for strings, 32-bit -inline constexpr uint32_t fnv1a_string_hash(const char* str) { - uint32_t hash = FNV_1A_INIT; - - while(*str) { - hash = (hash ^ *str) * 16777619ULL; - str += 1; - } - return hash; -} -#else -// FNV-1a hash for strings, 32-bit -inline uint32_t fnv1a_string_hash(const char* str) { - uint32_t hash = FNV_1A_INIT; - - while(*str) { - hash = (hash ^ *str) * 16777619ULL; - str += 1; - } - return hash; -} -#endif diff --git a/lib/heatshrink.scons b/lib/heatshrink.scons new file mode 100644 index 00000000000..241b5a34e36 --- /dev/null +++ b/lib/heatshrink.scons @@ -0,0 +1,23 @@ +from fbt.util import GLOB_FILE_EXCLUSION + +Import("env") + +env.Append( + CPPPATH=[ + "#/lib/heatshrink", + ], +) + + +libenv = env.Clone(FW_LIB_NAME="heatshrink") +libenv.ApplyLibFlags() + +sources = Glob( + "heatshrink/heatshrink_*.c*", + exclude=GLOB_FILE_EXCLUSION, + source=True, +) + +lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources) +libenv.Install("${LIB_DIST_DIR}", lib) +Return("lib") diff --git a/lib/infrared/SConscript b/lib/infrared/SConscript index 9a1543f00fa..a32248a645b 100644 --- a/lib/infrared/SConscript +++ b/lib/infrared/SConscript @@ -10,6 +10,9 @@ env.Append( File("worker/infrared_worker.h"), File("worker/infrared_transmit.h"), ], + LINT_SOURCES=[ + Dir("."), + ], ) diff --git a/lib/mbedtls b/lib/mbedtls index d65aeb37349..edb8fec9882 160000 --- a/lib/mbedtls +++ b/lib/mbedtls @@ -1 +1 @@ -Subproject commit d65aeb37349ad1a50e0f6c9b694d4b5290d60e49 +Subproject commit edb8fec9882084344a314368ac7fd957a187519c diff --git a/lib/mbedtls.scons b/lib/mbedtls.scons index 79a4a25203f..77add769668 100644 --- a/lib/mbedtls.scons +++ b/lib/mbedtls.scons @@ -2,13 +2,21 @@ Import("env") env.Append( CPPPATH=[ - "#/lib/mbedtls", + # "#/lib/mbedtls", "#/lib/mbedtls/include", ], SDK_HEADERS=[ File("mbedtls/include/mbedtls/des.h"), File("mbedtls/include/mbedtls/sha1.h"), + File("mbedtls/include/mbedtls/sha256.h"), + File("mbedtls/include/mbedtls/md5.h"), + File("mbedtls/include/mbedtls/md.h"), + File("mbedtls/include/mbedtls/ecdsa.h"), + File("mbedtls/include/mbedtls/ecdh.h"), + File("mbedtls/include/mbedtls/ecp.h"), + # File("mbedtls/include/mbedtls/sha1.h"), ], + CPPDEFINES=[("MBEDTLS_CONFIG_FILE", '\\"mbedtls_cfg.h\\"')], ) @@ -20,14 +28,30 @@ libenv.AppendUnique( # Required for lib to be linkable with .faps "-mword-relocations", "-mlong-calls", + # Crappy code :) + "-Wno-redundant-decls", ], ) +# If we were to build full mbedtls, we would need to use this: +# sources = libenv.GlobRecursive("*.c*", "mbedtls/library") +# Otherwise, we can just use the files we need: sources = [ - "mbedtls/library/des.c", - "mbedtls/library/sha1.c", - "mbedtls/library/platform_util.c", + File("mbedtls/library/bignum.c"), + File("mbedtls/library/bignum_core.c"), + File("mbedtls/library/ecdsa.c"), + File("mbedtls/library/ecp.c"), + File("mbedtls/library/ecp_curves.c"), + File("mbedtls/library/md.c"), + File("mbedtls/library/md5.c"), + File("mbedtls/library/platform_util.c"), + File("mbedtls/library/ripemd160.c"), + File("mbedtls/library/sha1.c"), + File("mbedtls/library/sha256.c"), + File("mbedtls/library/des.c"), ] +Depends(sources, File("mbedtls_cfg.h")) + lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources) libenv.Install("${LIB_DIST_DIR}", lib) diff --git a/lib/mbedtls_cfg.h b/lib/mbedtls_cfg.h new file mode 100644 index 00000000000..5af98e96575 --- /dev/null +++ b/lib/mbedtls_cfg.h @@ -0,0 +1,92 @@ +#pragma once + +/** +* A subset of the mbedTLS configuration options that are relevant to the +* Flipper Zero firmware and apps. They are built to "mbedtls" library you can +* link your apps with. +* +* If you need more features, either bring the full mbedtls library into your +* app using "fap_private_libs" or open an issue on GitHub to add them to the +* default configuration. +**/ + +#define MBEDTLS_HAVE_ASM + +#define MBEDTLS_NO_UDBL_DIVISION +#define MBEDTLS_NO_64BIT_MULTIPLICATION + +#define MBEDTLS_DEPRECATED_WARNING + +#define MBEDTLS_AES_FEWER_TABLES +// #define MBEDTLS_CHECK_RETURN_WARNING + +#define MBEDTLS_CIPHER_MODE_CBC +#define MBEDTLS_CIPHER_MODE_CFB +#define MBEDTLS_CIPHER_MODE_CTR +#define MBEDTLS_CIPHER_MODE_OFB +#define MBEDTLS_CIPHER_MODE_XTS + +#define MBEDTLS_CIPHER_PADDING_PKCS7 +#define MBEDTLS_CIPHER_PADDING_ONE_AND_ZEROS +#define MBEDTLS_CIPHER_PADDING_ZEROS_AND_LEN +#define MBEDTLS_CIPHER_PADDING_ZEROS + +/* Short Weierstrass curves (supporting ECP, ECDH, ECDSA) */ +// #define MBEDTLS_ECP_DP_SECP192R1_ENABLED +// #define MBEDTLS_ECP_DP_SECP224R1_ENABLED +#define MBEDTLS_ECP_DP_SECP256R1_ENABLED +// #define MBEDTLS_ECP_DP_SECP384R1_ENABLED +// #define MBEDTLS_ECP_DP_SECP521R1_ENABLED +// #define MBEDTLS_ECP_DP_SECP192K1_ENABLED +// #define MBEDTLS_ECP_DP_SECP224K1_ENABLED +// #define MBEDTLS_ECP_DP_SECP256K1_ENABLED +// #define MBEDTLS_ECP_DP_BP256R1_ENABLED +// #define MBEDTLS_ECP_DP_BP384R1_ENABLED +// #define MBEDTLS_ECP_DP_BP512R1_ENABLED +/* Montgomery curves (supporting ECP) */ +// #define MBEDTLS_ECP_DP_CURVE25519_ENABLED +// #define MBEDTLS_ECP_DP_CURVE448_ENABLED + +#define MBEDTLS_ECP_NIST_OPTIM + +#define MBEDTLS_GENPRIME +// #define MBEDTLS_PKCS1_V15 +// #define MBEDTLS_PKCS1_V21 + +#define MBEDTLS_MD_C + +#define MBEDTLS_ASN1_PARSE_C +#define MBEDTLS_ASN1_WRITE_C +#define MBEDTLS_BASE64_C +#define MBEDTLS_BIGNUM_C +#define MBEDTLS_OID_C + +// #define MBEDTLS_CHACHA20_C +// #define MBEDTLS_CHACHAPOLY_C +#define MBEDTLS_CIPHER_C +#define MBEDTLS_DES_C +#define MBEDTLS_DHM_C + +#define MBEDTLS_ECDH_C + +#define MBEDTLS_ECDSA_C +#define MBEDTLS_ECP_C + +#define MBEDTLS_GCM_C + +#define MBEDTLS_AES_C +#define MBEDTLS_MD5_C + +// #define MBEDTLS_PEM_PARSE_C +// #define MBEDTLS_PEM_WRITE_C + +// #define MBEDTLS_PLATFORM_MEMORY +// #define MBEDTLS_PLATFORM_C + +// #define MBEDTLS_RIPEMD160_C +// #define MBEDTLS_RSA_C +#define MBEDTLS_SHA224_C +#define MBEDTLS_SHA256_C +#define MBEDTLS_SHA1_C + +#define MBEDTLS_ERROR_C \ No newline at end of file diff --git a/lib/micro-ecc/LICENSE.txt b/lib/micro-ecc/LICENSE.txt deleted file mode 100644 index ab099ae5a51..00000000000 --- a/lib/micro-ecc/LICENSE.txt +++ /dev/null @@ -1,21 +0,0 @@ -Copyright (c) 2014, Kenneth MacKay -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/lib/micro-ecc/README.md b/lib/micro-ecc/README.md deleted file mode 100644 index 111321bf765..00000000000 --- a/lib/micro-ecc/README.md +++ /dev/null @@ -1,41 +0,0 @@ -micro-ecc -========== - -A small and fast ECDH and ECDSA implementation for 8-bit, 32-bit, and 64-bit processors. - -The static version of micro-ecc (ie, where the curve was selected at compile-time) can be found in the "static" branch. - -Features --------- - - * Resistant to known side-channel attacks. - * Written in C, with optional GCC inline assembly for AVR, ARM and Thumb platforms. - * Supports 8, 32, and 64-bit architectures. - * Small code size. - * No dynamic memory allocation. - * Support for 5 standard curves: secp160r1, secp192r1, secp224r1, secp256r1, and secp256k1. - * BSD 2-clause license. - -Usage Notes ------------ -### Point Representation ### -Compressed points are represented in the standard format as defined in http://www.secg.org/sec1-v2.pdf; uncompressed points are represented in standard format, but without the `0x04` prefix. All functions except `uECC_decompress()` only accept uncompressed points; use `uECC_compress()` and `uECC_decompress()` to convert between compressed and uncompressed point representations. - -Private keys are represented in the standard format. - -### Using the Code ### - -I recommend just copying (or symlink) the uECC files into your project. Then just `#include "uECC.h"` to use the micro-ecc functions. - -For use with Arduino, you can use the Library Manager to download micro-ecc (**Sketch**=>**Include Library**=>**Manage Libraries**). You can then use uECC just like any other Arduino library (uECC should show up in the **Sketch**=>**Import Library** submenu). - -See uECC.h for documentation for each function. - -### Compilation Notes ### - - * Should compile with any C/C++ compiler that supports stdint.h (this includes Visual Studio 2013). - * If you want to change the defaults for any of the uECC compile-time options (such as `uECC_OPTIMIZATION_LEVEL`), you must change them in your Makefile or similar so that uECC.c is compiled with the desired values (ie, compile uECC.c with `-DuECC_OPTIMIZATION_LEVEL=3` or whatever). - * When compiling for a Thumb-1 platform, you must use the `-fomit-frame-pointer` GCC option (this is enabled by default when compiling with `-O1` or higher). - * When compiling for an ARM/Thumb-2 platform with `uECC_OPTIMIZATION_LEVEL` >= 3, you must use the `-fomit-frame-pointer` GCC option (this is enabled by default when compiling with `-O1` or higher). - * When compiling for AVR, you must have optimizations enabled (compile with `-O1` or higher). - * When building for Windows, you will need to link in the `advapi32.lib` system library. diff --git a/lib/micro-ecc/asm_arm.inc b/lib/micro-ecc/asm_arm.inc deleted file mode 100644 index 43844da6a56..00000000000 --- a/lib/micro-ecc/asm_arm.inc +++ /dev/null @@ -1,821 +0,0 @@ -/* Copyright 2015, Kenneth MacKay. Licensed under the BSD 2-clause license. */ - -#ifndef _UECC_ASM_ARM_H_ -#define _UECC_ASM_ARM_H_ - -#if (uECC_SUPPORTS_secp256r1 || uECC_SUPPORTS_secp256k1) - #define uECC_MIN_WORDS 8 -#endif -#if uECC_SUPPORTS_secp224r1 - #undef uECC_MIN_WORDS - #define uECC_MIN_WORDS 7 -#endif -#if uECC_SUPPORTS_secp192r1 - #undef uECC_MIN_WORDS - #define uECC_MIN_WORDS 6 -#endif -#if uECC_SUPPORTS_secp160r1 - #undef uECC_MIN_WORDS - #define uECC_MIN_WORDS 5 -#endif - -#if (uECC_PLATFORM == uECC_arm_thumb) - #define REG_RW "+l" - #define REG_WRITE "=l" -#else - #define REG_RW "+r" - #define REG_WRITE "=r" -#endif - -#if (uECC_PLATFORM == uECC_arm_thumb || uECC_PLATFORM == uECC_arm_thumb2) - #define REG_RW_LO "+l" - #define REG_WRITE_LO "=l" -#else - #define REG_RW_LO "+r" - #define REG_WRITE_LO "=r" -#endif - -#if (uECC_PLATFORM == uECC_arm_thumb2) - #define RESUME_SYNTAX -#else - #define RESUME_SYNTAX ".syntax divided \n\t" -#endif - -#if (uECC_OPTIMIZATION_LEVEL >= 2) - -uECC_VLI_API uECC_word_t uECC_vli_add(uECC_word_t *result, - const uECC_word_t *left, - const uECC_word_t *right, - wordcount_t num_words) { -#if (uECC_MAX_WORDS != uECC_MIN_WORDS) - #if (uECC_PLATFORM == uECC_arm_thumb) || (uECC_PLATFORM == uECC_arm_thumb2) - uint32_t jump = (uECC_MAX_WORDS - num_words) * 4 * 2 + 1; - #else /* ARM */ - uint32_t jump = (uECC_MAX_WORDS - num_words) * 4 * 4; - #endif -#endif - uint32_t carry; - uint32_t left_word; - uint32_t right_word; - - __asm__ volatile ( - ".syntax unified \n\t" - "movs %[carry], #0 \n\t" - #if (uECC_MAX_WORDS != uECC_MIN_WORDS) - "adr %[left], 1f \n\t" - ".align 4 \n\t" - "adds %[jump], %[left] \n\t" - #endif - - "ldmia %[lptr]!, {%[left]} \n\t" - "ldmia %[rptr]!, {%[right]} \n\t" - "adds %[left], %[right] \n\t" - "stmia %[dptr]!, {%[left]} \n\t" - - #if (uECC_MAX_WORDS != uECC_MIN_WORDS) - "bx %[jump] \n\t" - #endif - "1: \n\t" - REPEAT(DEC(uECC_MAX_WORDS), - "ldmia %[lptr]!, {%[left]} \n\t" - "ldmia %[rptr]!, {%[right]} \n\t" - "adcs %[left], %[right] \n\t" - "stmia %[dptr]!, {%[left]} \n\t") - - "adcs %[carry], %[carry] \n\t" - RESUME_SYNTAX - : [dptr] REG_RW_LO (result), [lptr] REG_RW_LO (left), [rptr] REG_RW_LO (right), - #if (uECC_MAX_WORDS != uECC_MIN_WORDS) - [jump] REG_RW_LO (jump), - #endif - [carry] REG_WRITE_LO (carry), [left] REG_WRITE_LO (left_word), - [right] REG_WRITE_LO (right_word) - : - : "cc", "memory" - ); - return carry; -} -#define asm_add 1 - -#pragma GCC diagnostic ignored "-Wredundant-decls" -uECC_VLI_API uECC_word_t uECC_vli_sub(uECC_word_t *result, - const uECC_word_t *left, - const uECC_word_t *right, - wordcount_t num_words) { -#if (uECC_MAX_WORDS != uECC_MIN_WORDS) - #if (uECC_PLATFORM == uECC_arm_thumb) || (uECC_PLATFORM == uECC_arm_thumb2) - uint32_t jump = (uECC_MAX_WORDS - num_words) * 4 * 2 + 1; - #else /* ARM */ - uint32_t jump = (uECC_MAX_WORDS - num_words) * 4 * 4; - #endif -#endif - uint32_t carry; - uint32_t left_word; - uint32_t right_word; - - __asm__ volatile ( - ".syntax unified \n\t" - "movs %[carry], #0 \n\t" - #if (uECC_MAX_WORDS != uECC_MIN_WORDS) - "adr %[left], 1f \n\t" - ".align 4 \n\t" - "adds %[jump], %[left] \n\t" - #endif - - "ldmia %[lptr]!, {%[left]} \n\t" - "ldmia %[rptr]!, {%[right]} \n\t" - "subs %[left], %[right] \n\t" - "stmia %[dptr]!, {%[left]} \n\t" - - #if (uECC_MAX_WORDS != uECC_MIN_WORDS) - "bx %[jump] \n\t" - #endif - "1: \n\t" - REPEAT(DEC(uECC_MAX_WORDS), - "ldmia %[lptr]!, {%[left]} \n\t" - "ldmia %[rptr]!, {%[right]} \n\t" - "sbcs %[left], %[right] \n\t" - "stmia %[dptr]!, {%[left]} \n\t") - - "adcs %[carry], %[carry] \n\t" - RESUME_SYNTAX - : [dptr] REG_RW_LO (result), [lptr] REG_RW_LO (left), [rptr] REG_RW_LO (right), - #if (uECC_MAX_WORDS != uECC_MIN_WORDS) - [jump] REG_RW_LO (jump), - #endif - [carry] REG_WRITE_LO (carry), [left] REG_WRITE_LO (left_word), - [right] REG_WRITE_LO (right_word) - : - : "cc", "memory" - ); - return !carry; /* Note that on ARM, carry flag set means "no borrow" when subtracting - (for some reason...) */ -} -#define asm_sub 1 - -#endif /* (uECC_OPTIMIZATION_LEVEL >= 2) */ - -#if (uECC_OPTIMIZATION_LEVEL >= 3) - -#if (uECC_PLATFORM != uECC_arm_thumb) - -#if uECC_ARM_USE_UMAAL - #include "asm_arm_mult_square_umaal.inc" -#else - #include "asm_arm_mult_square.inc" -#endif - -#if (uECC_OPTIMIZATION_LEVEL == 3) - -uECC_VLI_API void uECC_vli_mult(uint32_t *result, - const uint32_t *left, - const uint32_t *right, - wordcount_t num_words) { - register uint32_t *r0 __asm__("r0") = result; - register const uint32_t *r1 __asm__("r1") = left; - register const uint32_t *r2 __asm__("r2") = right; - register uint32_t r3 __asm__("r3") = num_words; - - __asm__ volatile ( - ".syntax unified \n\t" -#if (uECC_MIN_WORDS == 5) - FAST_MULT_ASM_5 - #if (uECC_MAX_WORDS > 5) - FAST_MULT_ASM_5_TO_6 - #endif - #if (uECC_MAX_WORDS > 6) - FAST_MULT_ASM_6_TO_7 - #endif - #if (uECC_MAX_WORDS > 7) - FAST_MULT_ASM_7_TO_8 - #endif -#elif (uECC_MIN_WORDS == 6) - FAST_MULT_ASM_6 - #if (uECC_MAX_WORDS > 6) - FAST_MULT_ASM_6_TO_7 - #endif - #if (uECC_MAX_WORDS > 7) - FAST_MULT_ASM_7_TO_8 - #endif -#elif (uECC_MIN_WORDS == 7) - FAST_MULT_ASM_7 - #if (uECC_MAX_WORDS > 7) - FAST_MULT_ASM_7_TO_8 - #endif -#elif (uECC_MIN_WORDS == 8) - FAST_MULT_ASM_8 -#endif - "1: \n\t" - RESUME_SYNTAX - : "+r" (r0), "+r" (r1), "+r" (r2) - : "r" (r3) - : "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r12", "r14", "cc", "memory" - ); -} -#define asm_mult 1 - -#if uECC_SQUARE_FUNC -uECC_VLI_API void uECC_vli_square(uECC_word_t *result, - const uECC_word_t *left, - wordcount_t num_words) { - register uint32_t *r0 __asm__("r0") = result; - register const uint32_t *r1 __asm__("r1") = left; - register uint32_t r2 __asm__("r2") = num_words; - - __asm__ volatile ( - ".syntax unified \n\t" -#if (uECC_MIN_WORDS == 5) - FAST_SQUARE_ASM_5 - #if (uECC_MAX_WORDS > 5) - FAST_SQUARE_ASM_5_TO_6 - #endif - #if (uECC_MAX_WORDS > 6) - FAST_SQUARE_ASM_6_TO_7 - #endif - #if (uECC_MAX_WORDS > 7) - FAST_SQUARE_ASM_7_TO_8 - #endif -#elif (uECC_MIN_WORDS == 6) - FAST_SQUARE_ASM_6 - #if (uECC_MAX_WORDS > 6) - FAST_SQUARE_ASM_6_TO_7 - #endif - #if (uECC_MAX_WORDS > 7) - FAST_SQUARE_ASM_7_TO_8 - #endif -#elif (uECC_MIN_WORDS == 7) - FAST_SQUARE_ASM_7 - #if (uECC_MAX_WORDS > 7) - FAST_SQUARE_ASM_7_TO_8 - #endif -#elif (uECC_MIN_WORDS == 8) - FAST_SQUARE_ASM_8 -#endif - - "1: \n\t" - RESUME_SYNTAX - : "+r" (r0), "+r" (r1) - : "r" (r2) - : "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r12", "r14", "cc", "memory" - ); -} -#define asm_square 1 -#endif /* uECC_SQUARE_FUNC */ - -#else /* (uECC_OPTIMIZATION_LEVEL > 3) */ - -uECC_VLI_API void uECC_vli_mult(uint32_t *result, - const uint32_t *left, - const uint32_t *right, - wordcount_t num_words) { - register uint32_t *r0 __asm__("r0") = result; - register const uint32_t *r1 __asm__("r1") = left; - register const uint32_t *r2 __asm__("r2") = right; - register uint32_t r3 __asm__("r3") = num_words; - -#if uECC_SUPPORTS_secp160r1 - if (num_words == 5) { - __asm__ volatile ( - ".syntax unified \n\t" - FAST_MULT_ASM_5 - RESUME_SYNTAX - : "+r" (r0), "+r" (r1), "+r" (r2) - : "r" (r3) - : "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r12", "r14", "cc", "memory" - ); - return; - } -#endif -#if uECC_SUPPORTS_secp192r1 - if (num_words == 6) { - __asm__ volatile ( - ".syntax unified \n\t" - FAST_MULT_ASM_6 - RESUME_SYNTAX - : "+r" (r0), "+r" (r1), "+r" (r2) - : "r" (r3) - : "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r12", "r14", "cc", "memory" - ); - return; - } -#endif -#if uECC_SUPPORTS_secp224r1 - if (num_words == 7) { - __asm__ volatile ( - ".syntax unified \n\t" - FAST_MULT_ASM_7 - RESUME_SYNTAX - : "+r" (r0), "+r" (r1), "+r" (r2) - : "r" (r3) - : "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r12", "r14", "cc", "memory" - ); - return; - } -#endif -#if (uECC_SUPPORTS_secp256r1 || uECC_SUPPORTS_secp256k1) - if (num_words == 8) { - __asm__ volatile ( - ".syntax unified \n\t" - FAST_MULT_ASM_8 - RESUME_SYNTAX - : "+r" (r0), "+r" (r1), "+r" (r2) - : "r" (r3) - : "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r12", "r14", "cc", "memory" - ); - return; - } -#endif -} -#define asm_mult 1 - -#if uECC_SQUARE_FUNC -uECC_VLI_API void uECC_vli_square(uECC_word_t *result, - const uECC_word_t *left, - wordcount_t num_words) { - register uint32_t *r0 __asm__("r0") = result; - register const uint32_t *r1 __asm__("r1") = left; - register uint32_t r2 __asm__("r2") = num_words; - -#if uECC_SUPPORTS_secp160r1 - if (num_words == 5) { - __asm__ volatile ( - ".syntax unified \n\t" - FAST_SQUARE_ASM_5 - RESUME_SYNTAX - : "+r" (r0), "+r" (r1) - : "r" (r2) - : "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r12", "r14", "cc", "memory" - ); - return; - } -#endif -#if uECC_SUPPORTS_secp192r1 - if (num_words == 6) { - __asm__ volatile ( - ".syntax unified \n\t" - FAST_SQUARE_ASM_6 - RESUME_SYNTAX - : "+r" (r0), "+r" (r1) - : "r" (r2) - : "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r12", "r14", "cc", "memory" - ); - return; - } -#endif -#if uECC_SUPPORTS_secp224r1 - if (num_words == 7) { - __asm__ volatile ( - ".syntax unified \n\t" - FAST_SQUARE_ASM_7 - RESUME_SYNTAX - : "+r" (r0), "+r" (r1) - : "r" (r2) - : "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r12", "r14", "cc", "memory" - ); - return; - } -#endif -#if (uECC_SUPPORTS_secp256r1 || uECC_SUPPORTS_secp256k1) - if (num_words == 8) { - __asm__ volatile ( - ".syntax unified \n\t" - FAST_SQUARE_ASM_8 - RESUME_SYNTAX - : "+r" (r0), "+r" (r1) - : "r" (r2) - : "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r12", "r14", "cc", "memory" - ); - return; - } -#endif -} -#define asm_square 1 -#endif /* uECC_SQUARE_FUNC */ - -#endif /* (uECC_OPTIMIZATION_LEVEL > 3) */ - -#endif /* uECC_PLATFORM != uECC_arm_thumb */ - -#endif /* (uECC_OPTIMIZATION_LEVEL >= 3) */ - -/* ---- "Small" implementations ---- */ - -#if !asm_add -uECC_VLI_API uECC_word_t uECC_vli_add(uECC_word_t *result, - const uECC_word_t *left, - const uECC_word_t *right, - wordcount_t num_words) { - uint32_t carry = 0; - uint32_t left_word; - uint32_t right_word; - - __asm__ volatile ( - ".syntax unified \n\t" - "1: \n\t" - "ldmia %[lptr]!, {%[left]} \n\t" /* Load left word. */ - "ldmia %[rptr]!, {%[right]} \n\t" /* Load right word. */ - "lsrs %[carry], #1 \n\t" /* Set up carry flag (carry = 0 after this). */ - "adcs %[left], %[left], %[right] \n\t" /* Add with carry. */ - "adcs %[carry], %[carry], %[carry] \n\t" /* Store carry bit. */ - "stmia %[dptr]!, {%[left]} \n\t" /* Store result word. */ - "subs %[ctr], #1 \n\t" /* Decrement counter. */ - "bne 1b \n\t" /* Loop until counter == 0. */ - RESUME_SYNTAX - : [dptr] REG_RW (result), [lptr] REG_RW (left), [rptr] REG_RW (right), - [ctr] REG_RW (num_words), [carry] REG_RW (carry), - [left] REG_WRITE (left_word), [right] REG_WRITE (right_word) - : - : "cc", "memory" - ); - return carry; -} -#define asm_add 1 -#endif - -#if !asm_sub -uECC_VLI_API uECC_word_t uECC_vli_sub(uECC_word_t *result, - const uECC_word_t *left, - const uECC_word_t *right, - wordcount_t num_words) { - uint32_t carry = 1; /* carry = 1 initially (means don't borrow) */ - uint32_t left_word; - uint32_t right_word; - - __asm__ volatile ( - ".syntax unified \n\t" - "1: \n\t" - "ldmia %[lptr]!, {%[left]} \n\t" /* Load left word. */ - "ldmia %[rptr]!, {%[right]} \n\t" /* Load right word. */ - "lsrs %[carry], #1 \n\t" /* Set up carry flag (carry = 0 after this). */ - "sbcs %[left], %[left], %[right] \n\t" /* Subtract with borrow. */ - "adcs %[carry], %[carry], %[carry] \n\t" /* Store carry bit. */ - "stmia %[dptr]!, {%[left]} \n\t" /* Store result word. */ - "subs %[ctr], #1 \n\t" /* Decrement counter. */ - "bne 1b \n\t" /* Loop until counter == 0. */ - RESUME_SYNTAX - : [dptr] REG_RW (result), [lptr] REG_RW (left), [rptr] REG_RW (right), - [ctr] REG_RW (num_words), [carry] REG_RW (carry), - [left] REG_WRITE (left_word), [right] REG_WRITE (right_word) - : - : "cc", "memory" - ); - return !carry; -} -#define asm_sub 1 -#endif - -#if !asm_mult -uECC_VLI_API void uECC_vli_mult(uECC_word_t *result, - const uECC_word_t *left, - const uECC_word_t *right, - wordcount_t num_words) { -#if (uECC_PLATFORM != uECC_arm_thumb) - uint32_t c0 = 0; - uint32_t c1 = 0; - uint32_t c2 = 0; - uint32_t k = 0; - uint32_t i; - uint32_t t0, t1; - - __asm__ volatile ( - ".syntax unified \n\t" - - "1: \n\t" /* outer loop (k < num_words) */ - "movs %[i], #0 \n\t" /* i = 0 */ - "b 3f \n\t" - - "2: \n\t" /* outer loop (k >= num_words) */ - "movs %[i], %[k] \n\t" /* i = k */ - "subs %[i], %[last_word] \n\t" /* i = k - (num_words - 1) (times 4) */ - - "3: \n\t" /* inner loop */ - "subs %[t0], %[k], %[i] \n\t" /* t0 = k-i */ - - "ldr %[t1], [%[right], %[t0]] \n\t" /* t1 = right[k - i] */ - "ldr %[t0], [%[left], %[i]] \n\t" /* t0 = left[i] */ - - "umull %[t0], %[t1], %[t0], %[t1] \n\t" /* (t0, t1) = left[i] * right[k - i] */ - - "adds %[c0], %[c0], %[t0] \n\t" /* add low word to c0 */ - "adcs %[c1], %[c1], %[t1] \n\t" /* add high word to c1, including carry */ - "adcs %[c2], %[c2], #0 \n\t" /* add carry to c2 */ - - "adds %[i], #4 \n\t" /* i += 4 */ - "cmp %[i], %[last_word] \n\t" /* i > (num_words - 1) (times 4)? */ - "bgt 4f \n\t" /* if so, exit the loop */ - "cmp %[i], %[k] \n\t" /* i <= k? */ - "ble 3b \n\t" /* if so, continue looping */ - - "4: \n\t" /* end inner loop */ - - "str %[c0], [%[result], %[k]] \n\t" /* result[k] = c0 */ - "mov %[c0], %[c1] \n\t" /* c0 = c1 */ - "mov %[c1], %[c2] \n\t" /* c1 = c2 */ - "movs %[c2], #0 \n\t" /* c2 = 0 */ - "adds %[k], #4 \n\t" /* k += 4 */ - "cmp %[k], %[last_word] \n\t" /* k <= (num_words - 1) (times 4) ? */ - "ble 1b \n\t" /* if so, loop back, start with i = 0 */ - "cmp %[k], %[last_word], lsl #1 \n\t" /* k <= (num_words * 2 - 2) (times 4) ? */ - "ble 2b \n\t" /* if so, loop back, start with i = (k + 1) - num_words */ - /* end outer loop */ - - "str %[c0], [%[result], %[k]] \n\t" /* result[num_words * 2 - 1] = c0 */ - RESUME_SYNTAX - : [c0] "+r" (c0), [c1] "+r" (c1), [c2] "+r" (c2), - [k] "+r" (k), [i] "=&r" (i), [t0] "=&r" (t0), [t1] "=&r" (t1) - : [result] "r" (result), [left] "r" (left), [right] "r" (right), - [last_word] "r" ((num_words - 1) * 4) - : "cc", "memory" - ); - -#else /* Thumb-1 */ - uint32_t r4, r5, r6, r7; - - __asm__ volatile ( - ".syntax unified \n\t" - "subs %[r3], #1 \n\t" /* r3 = num_words - 1 */ - "lsls %[r3], #2 \n\t" /* r3 = (num_words - 1) * 4 */ - "mov r8, %[r3] \n\t" /* r8 = (num_words - 1) * 4 */ - "lsls %[r3], #1 \n\t" /* r3 = (num_words - 1) * 8 */ - "mov r9, %[r3] \n\t" /* r9 = (num_words - 1) * 8 */ - "movs %[r3], #0 \n\t" /* c0 = 0 */ - "movs %[r4], #0 \n\t" /* c1 = 0 */ - "movs %[r5], #0 \n\t" /* c2 = 0 */ - "movs %[r6], #0 \n\t" /* k = 0 */ - - "push {%[r0]} \n\t" /* keep result on the stack */ - - "1: \n\t" /* outer loop (k < num_words) */ - "movs %[r7], #0 \n\t" /* r7 = i = 0 */ - "b 3f \n\t" - - "2: \n\t" /* outer loop (k >= num_words) */ - "movs %[r7], %[r6] \n\t" /* r7 = k */ - "mov %[r0], r8 \n\t" /* r0 = (num_words - 1) * 4 */ - "subs %[r7], %[r0] \n\t" /* r7 = i = k - (num_words - 1) (times 4) */ - - "3: \n\t" /* inner loop */ - "mov r10, %[r3] \n\t" - "mov r11, %[r4] \n\t" - "mov r12, %[r5] \n\t" - "mov r14, %[r6] \n\t" - "subs %[r0], %[r6], %[r7] \n\t" /* r0 = k - i */ - - "ldr %[r4], [%[r2], %[r0]] \n\t" /* r4 = right[k - i] */ - "ldr %[r0], [%[r1], %[r7]] \n\t" /* r0 = left[i] */ - - "lsrs %[r3], %[r0], #16 \n\t" /* r3 = a1 */ - "uxth %[r0], %[r0] \n\t" /* r0 = a0 */ - - "lsrs %[r5], %[r4], #16 \n\t" /* r5 = b1 */ - "uxth %[r4], %[r4] \n\t" /* r4 = b0 */ - - "movs %[r6], %[r3] \n\t" /* r6 = a1 */ - "muls %[r6], %[r5], %[r6] \n\t" /* r6 = a1 * b1 */ - "muls %[r3], %[r4], %[r3] \n\t" /* r3 = b0 * a1 */ - "muls %[r5], %[r0], %[r5] \n\t" /* r5 = a0 * b1 */ - "muls %[r0], %[r4], %[r0] \n\t" /* r0 = a0 * b0 */ - - /* Add middle terms */ - "lsls %[r4], %[r3], #16 \n\t" - "lsrs %[r3], %[r3], #16 \n\t" - "adds %[r0], %[r4] \n\t" - "adcs %[r6], %[r3] \n\t" - - "lsls %[r4], %[r5], #16 \n\t" - "lsrs %[r5], %[r5], #16 \n\t" - "adds %[r0], %[r4] \n\t" - "adcs %[r6], %[r5] \n\t" - - "mov %[r3], r10\n\t" - "mov %[r4], r11\n\t" - "mov %[r5], r12\n\t" - "adds %[r3], %[r0] \n\t" /* add low word to c0 */ - "adcs %[r4], %[r6] \n\t" /* add high word to c1, including carry */ - "movs %[r0], #0 \n\t" /* r0 = 0 (does not affect carry bit) */ - "adcs %[r5], %[r0] \n\t" /* add carry to c2 */ - - "mov %[r6], r14\n\t" /* r6 = k */ - - "adds %[r7], #4 \n\t" /* i += 4 */ - "cmp %[r7], r8 \n\t" /* i > (num_words - 1) (times 4)? */ - "bgt 4f \n\t" /* if so, exit the loop */ - "cmp %[r7], %[r6] \n\t" /* i <= k? */ - "ble 3b \n\t" /* if so, continue looping */ - - "4: \n\t" /* end inner loop */ - - "ldr %[r0], [sp, #0] \n\t" /* r0 = result */ - - "str %[r3], [%[r0], %[r6]] \n\t" /* result[k] = c0 */ - "mov %[r3], %[r4] \n\t" /* c0 = c1 */ - "mov %[r4], %[r5] \n\t" /* c1 = c2 */ - "movs %[r5], #0 \n\t" /* c2 = 0 */ - "adds %[r6], #4 \n\t" /* k += 4 */ - "cmp %[r6], r8 \n\t" /* k <= (num_words - 1) (times 4) ? */ - "ble 1b \n\t" /* if so, loop back, start with i = 0 */ - "cmp %[r6], r9 \n\t" /* k <= (num_words * 2 - 2) (times 4) ? */ - "ble 2b \n\t" /* if so, loop back, with i = (k + 1) - num_words */ - /* end outer loop */ - - "str %[r3], [%[r0], %[r6]] \n\t" /* result[num_words * 2 - 1] = c0 */ - "pop {%[r0]} \n\t" /* pop result off the stack */ - - ".syntax divided \n\t" - : [r3] "+l" (num_words), [r4] "=&l" (r4), - [r5] "=&l" (r5), [r6] "=&l" (r6), [r7] "=&l" (r7) - : [r0] "l" (result), [r1] "l" (left), [r2] "l" (right) - : "r8", "r9", "r10", "r11", "r12", "r14", "cc", "memory" - ); -#endif -} -#define asm_mult 1 -#endif - -#if uECC_SQUARE_FUNC -#if !asm_square -uECC_VLI_API void uECC_vli_square(uECC_word_t *result, - const uECC_word_t *left, - wordcount_t num_words) { -#if (uECC_PLATFORM != uECC_arm_thumb) - uint32_t c0 = 0; - uint32_t c1 = 0; - uint32_t c2 = 0; - uint32_t k = 0; - uint32_t i, tt; - uint32_t t0, t1; - - __asm__ volatile ( - ".syntax unified \n\t" - - "1: \n\t" /* outer loop (k < num_words) */ - "movs %[i], #0 \n\t" /* i = 0 */ - "b 3f \n\t" - - "2: \n\t" /* outer loop (k >= num_words) */ - "movs %[i], %[k] \n\t" /* i = k */ - "subs %[i], %[last_word] \n\t" /* i = k - (num_words - 1) (times 4) */ - - "3: \n\t" /* inner loop */ - "subs %[tt], %[k], %[i] \n\t" /* tt = k-i */ - - "ldr %[t1], [%[left], %[tt]] \n\t" /* t1 = left[k - i] */ - "ldr %[t0], [%[left], %[i]] \n\t" /* t0 = left[i] */ - - "umull %[t0], %[t1], %[t0], %[t1] \n\t" /* (t0, t1) = left[i] * right[k - i] */ - - "cmp %[i], %[tt] \n\t" /* (i < k - i) ? */ - "bge 4f \n\t" /* if i >= k - i, skip */ - "adds %[c0], %[c0], %[t0] \n\t" /* add low word to c0 */ - "adcs %[c1], %[c1], %[t1] \n\t" /* add high word to c1, including carry */ - "adcs %[c2], %[c2], #0 \n\t" /* add carry to c2 */ - - "4: \n\t" - "adds %[c0], %[c0], %[t0] \n\t" /* add low word to c0 */ - "adcs %[c1], %[c1], %[t1] \n\t" /* add high word to c1, including carry */ - "adcs %[c2], %[c2], #0 \n\t" /* add carry to c2 */ - - "adds %[i], #4 \n\t" /* i += 4 */ - "cmp %[i], %[k] \n\t" /* i >= k? */ - "bge 5f \n\t" /* if so, exit the loop */ - "subs %[tt], %[k], %[i] \n\t" /* tt = k - i */ - "cmp %[i], %[tt] \n\t" /* i <= k - i? */ - "ble 3b \n\t" /* if so, continue looping */ - - "5: \n\t" /* end inner loop */ - - "str %[c0], [%[result], %[k]] \n\t" /* result[k] = c0 */ - "mov %[c0], %[c1] \n\t" /* c0 = c1 */ - "mov %[c1], %[c2] \n\t" /* c1 = c2 */ - "movs %[c2], #0 \n\t" /* c2 = 0 */ - "adds %[k], #4 \n\t" /* k += 4 */ - "cmp %[k], %[last_word] \n\t" /* k <= (num_words - 1) (times 4) ? */ - "ble 1b \n\t" /* if so, loop back, start with i = 0 */ - "cmp %[k], %[last_word], lsl #1 \n\t" /* k <= (num_words * 2 - 2) (times 4) ? */ - "ble 2b \n\t" /* if so, loop back, start with i = (k + 1) - num_words */ - /* end outer loop */ - - "str %[c0], [%[result], %[k]] \n\t" /* result[num_words * 2 - 1] = c0 */ - RESUME_SYNTAX - : [c0] "+r" (c0), [c1] "+r" (c1), [c2] "+r" (c2), - [k] "+r" (k), [i] "=&r" (i), [tt] "=&r" (tt), [t0] "=&r" (t0), [t1] "=&r" (t1) - : [result] "r" (result), [left] "r" (left), [last_word] "r" ((num_words - 1) * 4) - : "cc", "memory" - ); - -#else - uint32_t r3, r4, r5, r6, r7; - - __asm__ volatile ( - ".syntax unified \n\t" - "subs %[r2], #1 \n\t" /* r2 = num_words - 1 */ - "lsls %[r2], #2 \n\t" /* r2 = (num_words - 1) * 4 */ - "mov r8, %[r2] \n\t" /* r8 = (num_words - 1) * 4 */ - "lsls %[r2], #1 \n\t" /* r2 = (num_words - 1) * 8 */ - "mov r9, %[r2] \n\t" /* r9 = (num_words - 1) * 8 */ - "movs %[r2], #0 \n\t" /* c0 = 0 */ - "movs %[r3], #0 \n\t" /* c1 = 0 */ - "movs %[r4], #0 \n\t" /* c2 = 0 */ - "movs %[r5], #0 \n\t" /* k = 0 */ - - "push {%[r0]} \n\t" /* keep result on the stack */ - - "1: \n\t" /* outer loop (k < num_words) */ - "movs %[r6], #0 \n\t" /* r6 = i = 0 */ - "b 3f \n\t" - - "2: \n\t" /* outer loop (k >= num_words) */ - "movs %[r6], %[r5] \n\t" /* r6 = k */ - "mov %[r0], r8 \n\t" /* r0 = (num_words - 1) * 4 */ - "subs %[r6], %[r0] \n\t" /* r6 = i = k - (num_words - 1) (times 4) */ - - "3: \n\t" /* inner loop */ - "mov r10, %[r2] \n\t" - "mov r11, %[r3] \n\t" - "mov r12, %[r4] \n\t" - "mov r14, %[r5] \n\t" - "subs %[r7], %[r5], %[r6] \n\t" /* r7 = k - i */ - - "ldr %[r3], [%[r1], %[r7]] \n\t" /* r3 = left[k - i] */ - "ldr %[r0], [%[r1], %[r6]] \n\t" /* r0 = left[i] */ - - "lsrs %[r2], %[r0], #16 \n\t" /* r2 = a1 */ - "uxth %[r0], %[r0] \n\t" /* r0 = a0 */ - - "lsrs %[r4], %[r3], #16 \n\t" /* r4 = b1 */ - "uxth %[r3], %[r3] \n\t" /* r3 = b0 */ - - "movs %[r5], %[r2] \n\t" /* r5 = a1 */ - "muls %[r5], %[r4], %[r5] \n\t" /* r5 = a1 * b1 */ - "muls %[r2], %[r3], %[r2] \n\t" /* r2 = b0 * a1 */ - "muls %[r4], %[r0], %[r4] \n\t" /* r4 = a0 * b1 */ - "muls %[r0], %[r3], %[r0] \n\t" /* r0 = a0 * b0 */ - - /* Add middle terms */ - "lsls %[r3], %[r2], #16 \n\t" - "lsrs %[r2], %[r2], #16 \n\t" - "adds %[r0], %[r3] \n\t" - "adcs %[r5], %[r2] \n\t" - - "lsls %[r3], %[r4], #16 \n\t" - "lsrs %[r4], %[r4], #16 \n\t" - "adds %[r0], %[r3] \n\t" - "adcs %[r5], %[r4] \n\t" - - /* Add to acc, doubling if necessary */ - "mov %[r2], r10\n\t" - "mov %[r3], r11\n\t" - "mov %[r4], r12\n\t" - - "cmp %[r6], %[r7] \n\t" /* (i < k - i) ? */ - "bge 4f \n\t" /* if i >= k - i, skip */ - "movs %[r7], #0 \n\t" /* r7 = 0 */ - "adds %[r2], %[r0] \n\t" /* add low word to c0 */ - "adcs %[r3], %[r5] \n\t" /* add high word to c1, including carry */ - "adcs %[r4], %[r7] \n\t" /* add carry to c2 */ - "4: \n\t" - "movs %[r7], #0 \n\t" /* r7 = 0 */ - "adds %[r2], %[r0] \n\t" /* add low word to c0 */ - "adcs %[r3], %[r5] \n\t" /* add high word to c1, including carry */ - "adcs %[r4], %[r7] \n\t" /* add carry to c2 */ - - "mov %[r5], r14\n\t" /* r5 = k */ - - "adds %[r6], #4 \n\t" /* i += 4 */ - "cmp %[r6], %[r5] \n\t" /* i >= k? */ - "bge 5f \n\t" /* if so, exit the loop */ - "subs %[r7], %[r5], %[r6] \n\t" /* r7 = k - i */ - "cmp %[r6], %[r7] \n\t" /* i <= k - i? */ - "ble 3b \n\t" /* if so, continue looping */ - - "5: \n\t" /* end inner loop */ - - "ldr %[r0], [sp, #0] \n\t" /* r0 = result */ - - "str %[r2], [%[r0], %[r5]] \n\t" /* result[k] = c0 */ - "mov %[r2], %[r3] \n\t" /* c0 = c1 */ - "mov %[r3], %[r4] \n\t" /* c1 = c2 */ - "movs %[r4], #0 \n\t" /* c2 = 0 */ - "adds %[r5], #4 \n\t" /* k += 4 */ - "cmp %[r5], r8 \n\t" /* k <= (num_words - 1) (times 4) ? */ - "ble 1b \n\t" /* if so, loop back, start with i = 0 */ - "cmp %[r5], r9 \n\t" /* k <= (num_words * 2 - 2) (times 4) ? */ - "ble 2b \n\t" /* if so, loop back, with i = (k + 1) - num_words */ - /* end outer loop */ - - "str %[r2], [%[r0], %[r5]] \n\t" /* result[num_words * 2 - 1] = c0 */ - "pop {%[r0]} \n\t" /* pop result off the stack */ - - ".syntax divided \n\t" - : [r2] "+l" (num_words), [r3] "=&l" (r3), [r4] "=&l" (r4), - [r5] "=&l" (r5), [r6] "=&l" (r6), [r7] "=&l" (r7) - : [r0] "l" (result), [r1] "l" (left) - : "r8", "r9", "r10", "r11", "r12", "r14", "cc", "memory" - ); -#endif -} -#define asm_square 1 -#endif -#endif /* uECC_SQUARE_FUNC */ - -#endif /* _UECC_ASM_ARM_H_ */ diff --git a/lib/micro-ecc/asm_arm_mult_square.inc b/lib/micro-ecc/asm_arm_mult_square.inc deleted file mode 100644 index 8907fc1858d..00000000000 --- a/lib/micro-ecc/asm_arm_mult_square.inc +++ /dev/null @@ -1,2311 +0,0 @@ -/* Copyright 2015, Kenneth MacKay. Licensed under the BSD 2-clause license. */ - -#ifndef _UECC_ASM_ARM_MULT_SQUARE_H_ -#define _UECC_ASM_ARM_MULT_SQUARE_H_ - -#define FAST_MULT_ASM_5 \ - "push {r3} \n\t" \ - "add r0, 12 \n\t" \ - "add r2, 12 \n\t" \ - "ldmia r1!, {r3,r4} \n\t" \ - "ldmia r2!, {r6,r7} \n\t" \ - \ - "umull r11, r12, r3, r6 \n\t" \ - "stmia r0!, {r11} \n\t" \ - \ - "mov r10, #0 \n\t" \ - "umull r11, r9, r3, r7 \n\t" \ - "adds r12, r12, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "umull r11, r14, r4, r6 \n\t" \ - "adds r12, r12, r11 \n\t" \ - "adcs r9, r9, r14 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "stmia r0!, {r12} \n\t" \ - \ - "umull r12, r14, r4, r7 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adc r10, r10, r14 \n\t" \ - "stmia r0!, {r9, r10} \n\t" \ - \ - "sub r0, 28 \n\t" \ - "sub r2, 20 \n\t" \ - "ldmia r2!, {r6,r7,r8} \n\t" \ - "ldmia r1!, {r5} \n\t" \ - \ - "umull r11, r12, r3, r6 \n\t" \ - "stmia r0!, {r11} \n\t" \ - \ - "mov r10, #0 \n\t" \ - "umull r11, r9, r3, r7 \n\t" \ - "adds r12, r12, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "umull r11, r14, r4, r6 \n\t" \ - "adds r12, r12, r11 \n\t" \ - "adcs r9, r9, r14 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "stmia r0!, {r12} \n\t" \ - \ - "mov r11, #0 \n\t" \ - "umull r12, r14, r3, r8 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "umull r12, r14, r4, r7 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "umull r12, r14, r5, r6 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "stmia r0!, {r9} \n\t" \ - \ - "ldmia r1!, {r3} \n\t" \ - "mov r12, #0 \n\t" \ - "umull r14, r9, r4, r8 \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, r9 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "umull r14, r9, r5, r7 \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, r9 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "umull r14, r9, r3, r6 \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, r9 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "ldr r14, [r0] \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, #0 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "stmia r0!, {r10} \n\t" \ - \ - "ldmia r1!, {r4} \n\t" \ - "mov r14, #0 \n\t" \ - "umull r9, r10, r5, r8 \n\t" \ - "adds r11, r11, r9 \n\t" \ - "adcs r12, r12, r10 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "umull r9, r10, r3, r7 \n\t" \ - "adds r11, r11, r9 \n\t" \ - "adcs r12, r12, r10 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "umull r9, r10, r4, r6 \n\t" \ - "adds r11, r11, r9 \n\t" \ - "adcs r12, r12, r10 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "ldr r9, [r0] \n\t" \ - "adds r11, r11, r9 \n\t" \ - "adcs r12, r12, #0 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "stmia r0!, {r11} \n\t" \ - \ - "ldmia r2!, {r6} \n\t" \ - "mov r9, #0 \n\t" \ - "umull r10, r11, r5, r6 \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r14, r14, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "umull r10, r11, r3, r8 \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r14, r14, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "umull r10, r11, r4, r7 \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r14, r14, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "ldr r10, [r0] \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r14, r14, #0 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "stmia r0!, {r12} \n\t" \ - \ - "ldmia r2!, {r7} \n\t" \ - "mov r10, #0 \n\t" \ - "umull r11, r12, r5, r7 \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "umull r11, r12, r3, r6 \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "umull r11, r12, r4, r8 \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "ldr r11, [r0] \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, #0 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "stmia r0!, {r14} \n\t" \ - \ - "mov r11, #0 \n\t" \ - "umull r12, r14, r3, r7 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "umull r12, r14, r4, r6 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "stmia r0!, {r9} \n\t" \ - \ - "umull r14, r9, r4, r7 \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adc r11, r11, r9 \n\t" \ - "stmia r0!, {r10, r11} \n\t" \ - "pop {r3} \n\t" - -#define FAST_MULT_ASM_5_TO_6 \ - "cmp r3, #5 \n\t" \ - "beq 1f \n\t" \ - \ - /* r4 = left high, r5 = right high */ \ - "ldr r4, [r1] \n\t" \ - "ldr r5, [r2] \n\t" \ - \ - "sub r0, #20 \n\t" \ - "sub r1, #20 \n\t" \ - "sub r2, #20 \n\t" \ - \ - "ldr r6, [r0] \n\t" \ - "ldr r7, [r1], #4 \n\t" \ - "ldr r8, [r2], #4 \n\t" \ - "mov r14, #0 \n\t" \ - "umull r9, r10, r4, r8 \n\t" \ - "umull r11, r12, r5, r7 \n\t" \ - "adds r9, r9, r6 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "adds r9, r9, r11 \n\t" \ - "adcs r10, r10, r12 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "str r9, [r0], #4 \n\t" \ - \ - "ldr r6, [r0] \n\t" \ - "adds r10, r10, r6 \n\t" \ - "adcs r14, r14, #0 \n\t" \ - "ldr r7, [r1], #4 \n\t" \ - "ldr r8, [r2], #4 \n\t" \ - "mov r9, #0 \n\t" \ - "umull r11, r12, r4, r8 \n\t" \ - "adds r10, r10, r11 \n\t" \ - "adcs r14, r14, r12 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "umull r11, r12, r5, r7 \n\t" \ - "adds r10, r10, r11 \n\t" \ - "adcs r14, r14, r12 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "str r10, [r0], #4 \n\t" \ - \ - "ldr r6, [r0] \n\t" \ - "adds r14, r14, r6 \n\t" \ - "adcs r9, r9, #0 \n\t" \ - "ldr r7, [r1], #4 \n\t" \ - "ldr r8, [r2], #4 \n\t" \ - "mov r10, #0 \n\t" \ - "umull r11, r12, r4, r8 \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "umull r11, r12, r5, r7 \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "str r14, [r0], #4 \n\t" \ - \ - "ldr r6, [r0] \n\t" \ - "adds r9, r9, r6 \n\t" \ - "adcs r10, r10, #0 \n\t" \ - "ldr r7, [r1], #4 \n\t" \ - "ldr r8, [r2], #4 \n\t" \ - "mov r14, #0 \n\t" \ - "umull r11, r12, r4, r8 \n\t" \ - "adds r9, r9, r11 \n\t" \ - "adcs r10, r10, r12 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "umull r11, r12, r5, r7 \n\t" \ - "adds r9, r9, r11 \n\t" \ - "adcs r10, r10, r12 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "str r9, [r0], #4 \n\t" \ - \ - "ldr r6, [r0] \n\t" \ - "adds r10, r10, r6 \n\t" \ - "adcs r14, r14, #0 \n\t" \ - /* skip past already-loaded (r4, r5) */ \ - "ldr r7, [r1], #8 \n\t" \ - "ldr r8, [r2], #8 \n\t" \ - "mov r9, #0 \n\t" \ - "umull r11, r12, r4, r8 \n\t" \ - "adds r10, r10, r11 \n\t" \ - "adcs r14, r14, r12 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "umull r11, r12, r5, r7 \n\t" \ - "adds r10, r10, r11 \n\t" \ - "adcs r14, r14, r12 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "str r10, [r0], #4 \n\t" \ - \ - "umull r11, r12, r4, r5 \n\t" \ - "adds r11, r11, r14 \n\t" \ - "adc r12, r12, r9 \n\t" \ - "stmia r0!, {r11, r12} \n\t" - -#define FAST_MULT_ASM_6 \ - "push {r3} \n\t" \ - "add r0, 12 \n\t" \ - "add r2, 12 \n\t" \ - "ldmia r1!, {r3,r4,r5} \n\t" \ - "ldmia r2!, {r6,r7,r8} \n\t" \ - \ - "umull r11, r12, r3, r6 \n\t" \ - "stmia r0!, {r11} \n\t" \ - \ - "mov r10, #0 \n\t" \ - "umull r11, r9, r3, r7 \n\t" \ - "adds r12, r12, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "umull r11, r14, r4, r6 \n\t" \ - "adds r12, r12, r11 \n\t" \ - "adcs r9, r9, r14 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "stmia r0!, {r12} \n\t" \ - \ - "mov r11, #0 \n\t" \ - "umull r12, r14, r3, r8 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "umull r12, r14, r4, r7 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "umull r12, r14, r5, r6 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "stmia r0!, {r9} \n\t" \ - \ - "mov r12, #0 \n\t" \ - "umull r14, r9, r4, r8 \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, r9 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "umull r14, r9, r5, r7 \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, r9 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "stmia r0!, {r10} \n\t" \ - \ - "umull r9, r10, r5, r8 \n\t" \ - "adds r11, r11, r9 \n\t" \ - "adc r12, r12, r10 \n\t" \ - "stmia r0!, {r11, r12} \n\t" \ - \ - "sub r0, 36 \n\t" \ - "sub r2, 24 \n\t" \ - "ldmia r2!, {r6,r7,r8} \n\t" \ - \ - "umull r11, r12, r3, r6 \n\t" \ - "stmia r0!, {r11} \n\t" \ - \ - "mov r10, #0 \n\t" \ - "umull r11, r9, r3, r7 \n\t" \ - "adds r12, r12, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "umull r11, r14, r4, r6 \n\t" \ - "adds r12, r12, r11 \n\t" \ - "adcs r9, r9, r14 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "stmia r0!, {r12} \n\t" \ - \ - "mov r11, #0 \n\t" \ - "umull r12, r14, r3, r8 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "umull r12, r14, r4, r7 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "umull r12, r14, r5, r6 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "stmia r0!, {r9} \n\t" \ - \ - "ldmia r1!, {r3} \n\t" \ - "mov r12, #0 \n\t" \ - "umull r14, r9, r4, r8 \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, r9 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "umull r14, r9, r5, r7 \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, r9 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "umull r14, r9, r3, r6 \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, r9 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "ldr r14, [r0] \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, #0 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "stmia r0!, {r10} \n\t" \ - \ - "ldmia r1!, {r4} \n\t" \ - "mov r14, #0 \n\t" \ - "umull r9, r10, r5, r8 \n\t" \ - "adds r11, r11, r9 \n\t" \ - "adcs r12, r12, r10 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "umull r9, r10, r3, r7 \n\t" \ - "adds r11, r11, r9 \n\t" \ - "adcs r12, r12, r10 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "umull r9, r10, r4, r6 \n\t" \ - "adds r11, r11, r9 \n\t" \ - "adcs r12, r12, r10 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "ldr r9, [r0] \n\t" \ - "adds r11, r11, r9 \n\t" \ - "adcs r12, r12, #0 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "stmia r0!, {r11} \n\t" \ - \ - "ldmia r1!, {r5} \n\t" \ - "mov r9, #0 \n\t" \ - "umull r10, r11, r3, r8 \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r14, r14, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "umull r10, r11, r4, r7 \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r14, r14, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "umull r10, r11, r5, r6 \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r14, r14, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "ldr r10, [r0] \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r14, r14, #0 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "stmia r0!, {r12} \n\t" \ - \ - "ldmia r2!, {r6} \n\t" \ - "mov r10, #0 \n\t" \ - "umull r11, r12, r3, r6 \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "umull r11, r12, r4, r8 \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "umull r11, r12, r5, r7 \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "ldr r11, [r0] \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, #0 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "stmia r0!, {r14} \n\t" \ - \ - "ldmia r2!, {r7} \n\t" \ - "mov r11, #0 \n\t" \ - "umull r12, r14, r3, r7 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "umull r12, r14, r4, r6 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "umull r12, r14, r5, r8 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "ldr r12, [r0] \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, #0 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "stmia r0!, {r9} \n\t" \ - \ - "ldmia r2!, {r8} \n\t" \ - "mov r12, #0 \n\t" \ - "umull r14, r9, r3, r8 \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, r9 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "umull r14, r9, r4, r7 \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, r9 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "umull r14, r9, r5, r6 \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, r9 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "ldr r14, [r0] \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, #0 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "stmia r0!, {r10} \n\t" \ - \ - "mov r14, #0 \n\t" \ - "umull r9, r10, r4, r8 \n\t" \ - "adds r11, r11, r9 \n\t" \ - "adcs r12, r12, r10 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "umull r9, r10, r5, r7 \n\t" \ - "adds r11, r11, r9 \n\t" \ - "adcs r12, r12, r10 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "stmia r0!, {r11} \n\t" \ - \ - "umull r10, r11, r5, r8 \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adc r14, r14, r11 \n\t" \ - "stmia r0!, {r12, r14} \n\t" \ - "pop {r3} \n\t" - -#define FAST_MULT_ASM_6_TO_7 \ - "cmp r3, #6 \n\t" \ - "beq 1f \n\t" \ - \ - /* r4 = left high, r5 = right high */ \ - "ldr r4, [r1] \n\t" \ - "ldr r5, [r2] \n\t" \ - \ - "sub r0, #24 \n\t" \ - "sub r1, #24 \n\t" \ - "sub r2, #24 \n\t" \ - \ - "ldr r6, [r0] \n\t" \ - "ldr r7, [r1], #4 \n\t" \ - "ldr r8, [r2], #4 \n\t" \ - "mov r14, #0 \n\t" \ - "umull r9, r10, r4, r8 \n\t" \ - "umull r11, r12, r5, r7 \n\t" \ - "adds r9, r9, r6 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "adds r9, r9, r11 \n\t" \ - "adcs r10, r10, r12 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "str r9, [r0], #4 \n\t" \ - \ - "ldr r6, [r0] \n\t" \ - "adds r10, r10, r6 \n\t" \ - "adcs r14, r14, #0 \n\t" \ - "ldr r7, [r1], #4 \n\t" \ - "ldr r8, [r2], #4 \n\t" \ - "mov r9, #0 \n\t" \ - "umull r11, r12, r4, r8 \n\t" \ - "adds r10, r10, r11 \n\t" \ - "adcs r14, r14, r12 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "umull r11, r12, r5, r7 \n\t" \ - "adds r10, r10, r11 \n\t" \ - "adcs r14, r14, r12 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "str r10, [r0], #4 \n\t" \ - \ - "ldr r6, [r0] \n\t" \ - "adds r14, r14, r6 \n\t" \ - "adcs r9, r9, #0 \n\t" \ - "ldr r7, [r1], #4 \n\t" \ - "ldr r8, [r2], #4 \n\t" \ - "mov r10, #0 \n\t" \ - "umull r11, r12, r4, r8 \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "umull r11, r12, r5, r7 \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "str r14, [r0], #4 \n\t" \ - \ - "ldr r6, [r0] \n\t" \ - "adds r9, r9, r6 \n\t" \ - "adcs r10, r10, #0 \n\t" \ - "ldr r7, [r1], #4 \n\t" \ - "ldr r8, [r2], #4 \n\t" \ - "mov r14, #0 \n\t" \ - "umull r11, r12, r4, r8 \n\t" \ - "adds r9, r9, r11 \n\t" \ - "adcs r10, r10, r12 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "umull r11, r12, r5, r7 \n\t" \ - "adds r9, r9, r11 \n\t" \ - "adcs r10, r10, r12 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "str r9, [r0], #4 \n\t" \ - \ - "ldr r6, [r0] \n\t" \ - "adds r10, r10, r6 \n\t" \ - "adcs r14, r14, #0 \n\t" \ - "ldr r7, [r1], #4 \n\t" \ - "ldr r8, [r2], #4 \n\t" \ - "mov r9, #0 \n\t" \ - "umull r11, r12, r4, r8 \n\t" \ - "adds r10, r10, r11 \n\t" \ - "adcs r14, r14, r12 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "umull r11, r12, r5, r7 \n\t" \ - "adds r10, r10, r11 \n\t" \ - "adcs r14, r14, r12 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "str r10, [r0], #4 \n\t" \ - \ - "ldr r6, [r0] \n\t" \ - "adds r14, r14, r6 \n\t" \ - "adcs r9, r9, #0 \n\t" \ - /* skip past already-loaded (r4, r5) */ \ - "ldr r7, [r1], #8 \n\t" \ - "ldr r8, [r2], #8 \n\t" \ - "mov r10, #0 \n\t" \ - "umull r11, r12, r4, r8 \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "umull r11, r12, r5, r7 \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "str r14, [r0], #4 \n\t" \ - \ - "umull r11, r12, r4, r5 \n\t" \ - "adds r11, r11, r9 \n\t" \ - "adc r12, r12, r10 \n\t" \ - "stmia r0!, {r11, r12} \n\t" - -#define FAST_MULT_ASM_7 \ - "push {r3} \n\t" \ - "add r0, 24 \n\t" \ - "add r2, 24 \n\t" \ - "ldmia r1!, {r3} \n\t" \ - "ldmia r2!, {r6} \n\t" \ - \ - "umull r9, r10, r3, r6 \n\t" \ - "stmia r0!, {r9, r10} \n\t" \ - \ - "sub r0, 20 \n\t" \ - "sub r2, 16 \n\t" \ - "ldmia r2!, {r6, r7, r8} \n\t" \ - "ldmia r1!, {r4, r5} \n\t" \ - \ - "umull r9, r10, r3, r6 \n\t" \ - "stmia r0!, {r9} \n\t" \ - \ - "mov r14, #0 \n\t" \ - "umull r9, r12, r3, r7 \n\t" \ - "adds r10, r10, r9 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "umull r9, r11, r4, r6 \n\t" \ - "adds r10, r10, r9 \n\t" \ - "adcs r12, r12, r11 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "stmia r0!, {r10} \n\t" \ - \ - "mov r9, #0 \n\t" \ - "umull r10, r11, r3, r8 \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r14, r14, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "umull r10, r11, r4, r7 \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r14, r14, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "umull r10, r11, r5, r6 \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r14, r14, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "stmia r0!, {r12} \n\t" \ - \ - "ldmia r1!, {r3} \n\t" \ - "mov r10, #0 \n\t" \ - "umull r11, r12, r4, r8 \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "umull r11, r12, r5, r7 \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "umull r11, r12, r3, r6 \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "ldr r11, [r0] \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, #0 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "stmia r0!, {r14} \n\t" \ - \ - "ldmia r2!, {r6} \n\t" \ - "mov r11, #0 \n\t" \ - "umull r12, r14, r4, r6 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "umull r12, r14, r5, r8 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "umull r12, r14, r3, r7 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "ldr r12, [r0] \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, #0 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "stmia r0!, {r9} \n\t" \ - \ - "mov r12, #0 \n\t" \ - "umull r14, r9, r5, r6 \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, r9 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "umull r14, r9, r3, r8 \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, r9 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "stmia r0!, {r10} \n\t" \ - \ - "umull r9, r10, r3, r6 \n\t" \ - "adds r11, r11, r9 \n\t" \ - "adc r12, r12, r10 \n\t" \ - "stmia r0!, {r11, r12} \n\t" \ - \ - "sub r0, 44 \n\t" \ - "sub r1, 16 \n\t" \ - "sub r2, 28 \n\t" \ - "ldmia r1!, {r3,r4,r5} \n\t" \ - "ldmia r2!, {r6,r7,r8} \n\t" \ - \ - "umull r9, r10, r3, r6 \n\t" \ - "stmia r0!, {r9} \n\t" \ - \ - "mov r14, #0 \n\t" \ - "umull r9, r12, r3, r7 \n\t" \ - "adds r10, r10, r9 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "umull r9, r11, r4, r6 \n\t" \ - "adds r10, r10, r9 \n\t" \ - "adcs r12, r12, r11 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "stmia r0!, {r10} \n\t" \ - \ - "mov r9, #0 \n\t" \ - "umull r10, r11, r3, r8 \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r14, r14, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "umull r10, r11, r4, r7 \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r14, r14, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "umull r10, r11, r5, r6 \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r14, r14, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "stmia r0!, {r12} \n\t" \ - \ - "ldmia r1!, {r3} \n\t" \ - "mov r10, #0 \n\t" \ - "umull r11, r12, r4, r8 \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "umull r11, r12, r5, r7 \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "umull r11, r12, r3, r6 \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "ldr r11, [r0] \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, #0 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "stmia r0!, {r14} \n\t" \ - \ - "ldmia r1!, {r4} \n\t" \ - "mov r11, #0 \n\t" \ - "umull r12, r14, r5, r8 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "umull r12, r14, r3, r7 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "umull r12, r14, r4, r6 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "ldr r12, [r0] \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, #0 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "stmia r0!, {r9} \n\t" \ - \ - "ldmia r1!, {r5} \n\t" \ - "mov r12, #0 \n\t" \ - "umull r14, r9, r3, r8 \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, r9 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "umull r14, r9, r4, r7 \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, r9 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "umull r14, r9, r5, r6 \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, r9 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "ldr r14, [r0] \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, #0 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "stmia r0!, {r10} \n\t" \ - \ - "ldmia r1!, {r3} \n\t" \ - "mov r14, #0 \n\t" \ - "umull r9, r10, r4, r8 \n\t" \ - "adds r11, r11, r9 \n\t" \ - "adcs r12, r12, r10 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "umull r9, r10, r5, r7 \n\t" \ - "adds r11, r11, r9 \n\t" \ - "adcs r12, r12, r10 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "umull r9, r10, r3, r6 \n\t" \ - "adds r11, r11, r9 \n\t" \ - "adcs r12, r12, r10 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "ldr r9, [r0] \n\t" \ - "adds r11, r11, r9 \n\t" \ - "adcs r12, r12, #0 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "stmia r0!, {r11} \n\t" \ - \ - "ldmia r2!, {r6} \n\t" \ - "mov r9, #0 \n\t" \ - "umull r10, r11, r4, r6 \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r14, r14, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "umull r10, r11, r5, r8 \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r14, r14, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "umull r10, r11, r3, r7 \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r14, r14, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "ldr r10, [r0] \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r14, r14, #0 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "stmia r0!, {r12} \n\t" \ - \ - "ldmia r2!, {r7} \n\t" \ - "mov r10, #0 \n\t" \ - "umull r11, r12, r4, r7 \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "umull r11, r12, r5, r6 \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "umull r11, r12, r3, r8 \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "ldr r11, [r0] \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, #0 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "stmia r0!, {r14} \n\t" \ - \ - "ldmia r2!, {r8} \n\t" \ - "mov r11, #0 \n\t" \ - "umull r12, r14, r4, r8 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "umull r12, r14, r5, r7 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "umull r12, r14, r3, r6 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "ldr r12, [r0] \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, #0 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "stmia r0!, {r9} \n\t" \ - \ - "ldmia r2!, {r6} \n\t" \ - "mov r12, #0 \n\t" \ - "umull r14, r9, r4, r6 \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, r9 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "umull r14, r9, r5, r8 \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, r9 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "umull r14, r9, r3, r7 \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, r9 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "ldr r14, [r0] \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, #0 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "stmia r0!, {r10} \n\t" \ - \ - "mov r14, #0 \n\t" \ - "umull r9, r10, r5, r6 \n\t" \ - "adds r11, r11, r9 \n\t" \ - "adcs r12, r12, r10 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "umull r9, r10, r3, r8 \n\t" \ - "adds r11, r11, r9 \n\t" \ - "adcs r12, r12, r10 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "stmia r0!, {r11} \n\t" \ - \ - "umull r10, r11, r3, r6 \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adc r14, r14, r11 \n\t" \ - "stmia r0!, {r12, r14} \n\t" \ - "pop {r3} \n\t" - -#define FAST_MULT_ASM_7_TO_8 \ - "cmp r3, #7 \n\t" \ - "beq 1f \n\t" \ - \ - /* r4 = left high, r5 = right high */ \ - "ldr r4, [r1] \n\t" \ - "ldr r5, [r2] \n\t" \ - \ - "sub r0, #28 \n\t" \ - "sub r1, #28 \n\t" \ - "sub r2, #28 \n\t" \ - \ - "ldr r6, [r0] \n\t" \ - "ldr r7, [r1], #4 \n\t" \ - "ldr r8, [r2], #4 \n\t" \ - "mov r14, #0 \n\t" \ - "umull r9, r10, r4, r8 \n\t" \ - "umull r11, r12, r5, r7 \n\t" \ - "adds r9, r9, r6 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "adds r9, r9, r11 \n\t" \ - "adcs r10, r10, r12 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "str r9, [r0], #4 \n\t" \ - \ - "ldr r6, [r0] \n\t" \ - "adds r10, r10, r6 \n\t" \ - "adcs r14, r14, #0 \n\t" \ - "ldr r7, [r1], #4 \n\t" \ - "ldr r8, [r2], #4 \n\t" \ - "mov r9, #0 \n\t" \ - "umull r11, r12, r4, r8 \n\t" \ - "adds r10, r10, r11 \n\t" \ - "adcs r14, r14, r12 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "umull r11, r12, r5, r7 \n\t" \ - "adds r10, r10, r11 \n\t" \ - "adcs r14, r14, r12 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "str r10, [r0], #4 \n\t" \ - \ - "ldr r6, [r0] \n\t" \ - "adds r14, r14, r6 \n\t" \ - "adcs r9, r9, #0 \n\t" \ - "ldr r7, [r1], #4 \n\t" \ - "ldr r8, [r2], #4 \n\t" \ - "mov r10, #0 \n\t" \ - "umull r11, r12, r4, r8 \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "umull r11, r12, r5, r7 \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "str r14, [r0], #4 \n\t" \ - \ - "ldr r6, [r0] \n\t" \ - "adds r9, r9, r6 \n\t" \ - "adcs r10, r10, #0 \n\t" \ - "ldr r7, [r1], #4 \n\t" \ - "ldr r8, [r2], #4 \n\t" \ - "mov r14, #0 \n\t" \ - "umull r11, r12, r4, r8 \n\t" \ - "adds r9, r9, r11 \n\t" \ - "adcs r10, r10, r12 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "umull r11, r12, r5, r7 \n\t" \ - "adds r9, r9, r11 \n\t" \ - "adcs r10, r10, r12 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "str r9, [r0], #4 \n\t" \ - \ - "ldr r6, [r0] \n\t" \ - "adds r10, r10, r6 \n\t" \ - "adcs r14, r14, #0 \n\t" \ - "ldr r7, [r1], #4 \n\t" \ - "ldr r8, [r2], #4 \n\t" \ - "mov r9, #0 \n\t" \ - "umull r11, r12, r4, r8 \n\t" \ - "adds r10, r10, r11 \n\t" \ - "adcs r14, r14, r12 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "umull r11, r12, r5, r7 \n\t" \ - "adds r10, r10, r11 \n\t" \ - "adcs r14, r14, r12 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "str r10, [r0], #4 \n\t" \ - \ - "ldr r6, [r0] \n\t" \ - "adds r14, r14, r6 \n\t" \ - "adcs r9, r9, #0 \n\t" \ - "ldr r7, [r1], #4 \n\t" \ - "ldr r8, [r2], #4 \n\t" \ - "mov r10, #0 \n\t" \ - "umull r11, r12, r4, r8 \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "umull r11, r12, r5, r7 \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "str r14, [r0], #4 \n\t" \ - \ - "ldr r6, [r0] \n\t" \ - "adds r9, r9, r6 \n\t" \ - "adcs r10, r10, #0 \n\t" \ - /* skip past already-loaded (r4, r5) */ \ - "ldr r7, [r1], #8 \n\t" \ - "ldr r8, [r2], #8 \n\t" \ - "mov r14, #0 \n\t" \ - "umull r11, r12, r4, r8 \n\t" \ - "adds r9, r9, r11 \n\t" \ - "adcs r10, r10, r12 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "umull r11, r12, r5, r7 \n\t" \ - "adds r9, r9, r11 \n\t" \ - "adcs r10, r10, r12 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "str r9, [r0], #4 \n\t" \ - \ - "umull r11, r12, r4, r5 \n\t" \ - "adds r11, r11, r10 \n\t" \ - "adc r12, r12, r14 \n\t" \ - "stmia r0!, {r11, r12} \n\t" - -#define FAST_MULT_ASM_8 \ - "push {r3} \n\t" \ - "add r0, 24 \n\t" \ - "add r2, 24 \n\t" \ - "ldmia r1!, {r3,r4} \n\t" \ - "ldmia r2!, {r6,r7} \n\t" \ - \ - "umull r11, r12, r3, r6 \n\t" \ - "stmia r0!, {r11} \n\t" \ - \ - "mov r10, #0 \n\t" \ - "umull r11, r9, r3, r7 \n\t" \ - "adds r12, r12, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "umull r11, r14, r4, r6 \n\t" \ - "adds r12, r12, r11 \n\t" \ - "adcs r9, r9, r14 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "stmia r0!, {r12} \n\t" \ - \ - "umull r12, r14, r4, r7 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adc r10, r10, r14 \n\t" \ - "stmia r0!, {r9, r10} \n\t" \ - \ - "sub r0, 28 \n\t" \ - "sub r2, 20 \n\t" \ - "ldmia r2!, {r6,r7,r8} \n\t" \ - "ldmia r1!, {r5} \n\t" \ - \ - "umull r11, r12, r3, r6 \n\t" \ - "stmia r0!, {r11} \n\t" \ - \ - "mov r10, #0 \n\t" \ - "umull r11, r9, r3, r7 \n\t" \ - "adds r12, r12, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "umull r11, r14, r4, r6 \n\t" \ - "adds r12, r12, r11 \n\t" \ - "adcs r9, r9, r14 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "stmia r0!, {r12} \n\t" \ - \ - "mov r11, #0 \n\t" \ - "umull r12, r14, r3, r8 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "umull r12, r14, r4, r7 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "umull r12, r14, r5, r6 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "stmia r0!, {r9} \n\t" \ - \ - "ldmia r1!, {r3} \n\t" \ - "mov r12, #0 \n\t" \ - "umull r14, r9, r4, r8 \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, r9 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "umull r14, r9, r5, r7 \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, r9 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "umull r14, r9, r3, r6 \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, r9 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "ldr r14, [r0] \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, #0 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "stmia r0!, {r10} \n\t" \ - \ - "ldmia r1!, {r4} \n\t" \ - "mov r14, #0 \n\t" \ - "umull r9, r10, r5, r8 \n\t" \ - "adds r11, r11, r9 \n\t" \ - "adcs r12, r12, r10 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "umull r9, r10, r3, r7 \n\t" \ - "adds r11, r11, r9 \n\t" \ - "adcs r12, r12, r10 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "umull r9, r10, r4, r6 \n\t" \ - "adds r11, r11, r9 \n\t" \ - "adcs r12, r12, r10 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "ldr r9, [r0] \n\t" \ - "adds r11, r11, r9 \n\t" \ - "adcs r12, r12, #0 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "stmia r0!, {r11} \n\t" \ - \ - "ldmia r2!, {r6} \n\t" \ - "mov r9, #0 \n\t" \ - "umull r10, r11, r5, r6 \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r14, r14, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "umull r10, r11, r3, r8 \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r14, r14, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "umull r10, r11, r4, r7 \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r14, r14, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "ldr r10, [r0] \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r14, r14, #0 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "stmia r0!, {r12} \n\t" \ - \ - "ldmia r2!, {r7} \n\t" \ - "mov r10, #0 \n\t" \ - "umull r11, r12, r5, r7 \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "umull r11, r12, r3, r6 \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "umull r11, r12, r4, r8 \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "ldr r11, [r0] \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, #0 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "stmia r0!, {r14} \n\t" \ - \ - "mov r11, #0 \n\t" \ - "umull r12, r14, r3, r7 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "umull r12, r14, r4, r6 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "stmia r0!, {r9} \n\t" \ - \ - "umull r14, r9, r4, r7 \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adc r11, r11, r9 \n\t" \ - "stmia r0!, {r10, r11} \n\t" \ - \ - "sub r0, 52 \n\t" \ - "sub r1, 20 \n\t" \ - "sub r2, 32 \n\t" \ - "ldmia r1!, {r3,r4,r5} \n\t" \ - "ldmia r2!, {r6,r7,r8} \n\t" \ - \ - "umull r11, r12, r3, r6 \n\t" \ - "stmia r0!, {r11} \n\t" \ - \ - "mov r10, #0 \n\t" \ - "umull r11, r9, r3, r7 \n\t" \ - "adds r12, r12, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "umull r11, r14, r4, r6 \n\t" \ - "adds r12, r12, r11 \n\t" \ - "adcs r9, r9, r14 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "stmia r0!, {r12} \n\t" \ - \ - "mov r11, #0 \n\t" \ - "umull r12, r14, r3, r8 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "umull r12, r14, r4, r7 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "umull r12, r14, r5, r6 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "stmia r0!, {r9} \n\t" \ - \ - "ldmia r1!, {r3} \n\t" \ - "mov r12, #0 \n\t" \ - "umull r14, r9, r4, r8 \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, r9 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "umull r14, r9, r5, r7 \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, r9 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "umull r14, r9, r3, r6 \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, r9 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "ldr r14, [r0] \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, #0 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "stmia r0!, {r10} \n\t" \ - \ - "ldmia r1!, {r4} \n\t" \ - "mov r14, #0 \n\t" \ - "umull r9, r10, r5, r8 \n\t" \ - "adds r11, r11, r9 \n\t" \ - "adcs r12, r12, r10 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "umull r9, r10, r3, r7 \n\t" \ - "adds r11, r11, r9 \n\t" \ - "adcs r12, r12, r10 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "umull r9, r10, r4, r6 \n\t" \ - "adds r11, r11, r9 \n\t" \ - "adcs r12, r12, r10 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "ldr r9, [r0] \n\t" \ - "adds r11, r11, r9 \n\t" \ - "adcs r12, r12, #0 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "stmia r0!, {r11} \n\t" \ - \ - "ldmia r1!, {r5} \n\t" \ - "mov r9, #0 \n\t" \ - "umull r10, r11, r3, r8 \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r14, r14, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "umull r10, r11, r4, r7 \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r14, r14, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "umull r10, r11, r5, r6 \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r14, r14, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "ldr r10, [r0] \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r14, r14, #0 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "stmia r0!, {r12} \n\t" \ - \ - "ldmia r1!, {r3} \n\t" \ - "mov r10, #0 \n\t" \ - "umull r11, r12, r4, r8 \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "umull r11, r12, r5, r7 \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "umull r11, r12, r3, r6 \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "ldr r11, [r0] \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, #0 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "stmia r0!, {r14} \n\t" \ - \ - "ldmia r1!, {r4} \n\t" \ - "mov r11, #0 \n\t" \ - "umull r12, r14, r5, r8 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "umull r12, r14, r3, r7 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "umull r12, r14, r4, r6 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "ldr r12, [r0] \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, #0 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "stmia r0!, {r9} \n\t" \ - \ - "ldmia r2!, {r6} \n\t" \ - "mov r12, #0 \n\t" \ - "umull r14, r9, r5, r6 \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, r9 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "umull r14, r9, r3, r8 \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, r9 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "umull r14, r9, r4, r7 \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, r9 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "ldr r14, [r0] \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, #0 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "stmia r0!, {r10} \n\t" \ - \ - "ldmia r2!, {r7} \n\t" \ - "mov r14, #0 \n\t" \ - "umull r9, r10, r5, r7 \n\t" \ - "adds r11, r11, r9 \n\t" \ - "adcs r12, r12, r10 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "umull r9, r10, r3, r6 \n\t" \ - "adds r11, r11, r9 \n\t" \ - "adcs r12, r12, r10 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "umull r9, r10, r4, r8 \n\t" \ - "adds r11, r11, r9 \n\t" \ - "adcs r12, r12, r10 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "ldr r9, [r0] \n\t" \ - "adds r11, r11, r9 \n\t" \ - "adcs r12, r12, #0 \n\t" \ - "adc r14, r14, #0 \n\t" \ - "stmia r0!, {r11} \n\t" \ - \ - "ldmia r2!, {r8} \n\t" \ - "mov r9, #0 \n\t" \ - "umull r10, r11, r5, r8 \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r14, r14, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "umull r10, r11, r3, r7 \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r14, r14, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "umull r10, r11, r4, r6 \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r14, r14, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "ldr r10, [r0] \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r14, r14, #0 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "stmia r0!, {r12} \n\t" \ - \ - "ldmia r2!, {r6} \n\t" \ - "mov r10, #0 \n\t" \ - "umull r11, r12, r5, r6 \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "umull r11, r12, r3, r8 \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "umull r11, r12, r4, r7 \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "ldr r11, [r0] \n\t" \ - "adds r14, r14, r11 \n\t" \ - "adcs r9, r9, #0 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "stmia r0!, {r14} \n\t" \ - \ - "ldmia r2!, {r7} \n\t" \ - "mov r11, #0 \n\t" \ - "umull r12, r14, r5, r7 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "umull r12, r14, r3, r6 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "umull r12, r14, r4, r8 \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, r14 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "ldr r12, [r0] \n\t" \ - "adds r9, r9, r12 \n\t" \ - "adcs r10, r10, #0 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "stmia r0!, {r9} \n\t" \ - \ - "mov r12, #0 \n\t" \ - "umull r14, r9, r3, r7 \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, r9 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "umull r14, r9, r4, r6 \n\t" \ - "adds r10, r10, r14 \n\t" \ - "adcs r11, r11, r9 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "stmia r0!, {r10} \n\t" \ - \ - "umull r9, r10, r4, r7 \n\t" \ - "adds r11, r11, r9 \n\t" \ - "adc r12, r12, r10 \n\t" \ - "stmia r0!, {r11, r12} \n\t" \ - "pop {r3} \n\t" - -#define FAST_SQUARE_ASM_5 \ - "push {r2} \n\t" \ - "ldmia r1!, {r2,r3,r4,r5,r6} \n\t" \ - "push {r1} \n\t" \ - \ - "umull r11, r12, r2, r2 \n\t" \ - "stmia r0!, {r11} \n\t" \ - \ - "mov r9, #0 \n\t" \ - "umull r10, r11, r2, r3 \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r8, r11, #0 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r8, r8, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "stmia r0!, {r12} \n\t" \ - \ - "mov r10, #0 \n\t" \ - "umull r11, r12, r2, r4 \n\t" \ - "adds r11, r11, r11 \n\t" \ - "adcs r12, r12, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "adds r8, r8, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "umull r11, r12, r3, r3 \n\t" \ - "adds r8, r8, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "stmia r0!, {r8} \n\t" \ - \ - "mov r12, #0 \n\t" \ - "umull r8, r11, r2, r5 \n\t" \ - "umull r1, r14, r3, r4 \n\t" \ - "adds r8, r8, r1 \n\t" \ - "adcs r11, r11, r14 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "adds r8, r8, r8 \n\t" \ - "adcs r11, r11, r11 \n\t" \ - "adc r12, r12, r12 \n\t" \ - "adds r8, r8, r9 \n\t" \ - "adcs r11, r11, r10 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "stmia r0!, {r8} \n\t" \ - \ - "mov r10, #0 \n\t" \ - "umull r8, r9, r2, r6 \n\t" \ - "umull r1, r14, r3, r5 \n\t" \ - "adds r8, r8, r1 \n\t" \ - "adcs r9, r9, r14 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "adds r8, r8, r8 \n\t" \ - "adcs r9, r9, r9 \n\t" \ - "adc r10, r10, r10 \n\t" \ - "umull r1, r14, r4, r4 \n\t" \ - "adds r8, r8, r1 \n\t" \ - "adcs r9, r9, r14 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "adds r8, r8, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "stmia r0!, {r8} \n\t" \ - \ - "mov r12, #0 \n\t" \ - "umull r8, r11, r3, r6 \n\t" \ - "umull r1, r14, r4, r5 \n\t" \ - "adds r8, r8, r1 \n\t" \ - "adcs r11, r11, r14 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "adds r8, r8, r8 \n\t" \ - "adcs r11, r11, r11 \n\t" \ - "adc r12, r12, r12 \n\t" \ - "adds r8, r8, r9 \n\t" \ - "adcs r11, r11, r10 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "stmia r0!, {r8} \n\t" \ - \ - "mov r8, #0 \n\t" \ - "umull r1, r10, r4, r6 \n\t" \ - "adds r1, r1, r1 \n\t" \ - "adcs r10, r10, r10 \n\t" \ - "adc r8, r8, #0 \n\t" \ - "adds r11, r11, r1 \n\t" \ - "adcs r12, r12, r10 \n\t" \ - "adc r8, r8, #0 \n\t" \ - "umull r1, r10, r5, r5 \n\t" \ - "adds r11, r11, r1 \n\t" \ - "adcs r12, r12, r10 \n\t" \ - "adc r8, r8, #0 \n\t" \ - "stmia r0!, {r11} \n\t" \ - \ - "mov r11, #0 \n\t" \ - "umull r1, r10, r5, r6 \n\t" \ - "adds r1, r1, r1 \n\t" \ - "adcs r10, r10, r10 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "adds r12, r12, r1 \n\t" \ - "adcs r8, r8, r10 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "stmia r0!, {r12} \n\t" \ - \ - "umull r1, r10, r6, r6 \n\t" \ - "adds r8, r8, r1 \n\t" \ - "adcs r11, r11, r10 \n\t" \ - "stmia r0!, {r8, r11} \n\t" \ - "pop {r1, r2} \n\t" - -#define FAST_SQUARE_ASM_5_TO_6 \ - "cmp r2, #5 \n\t" \ - "beq 1f \n\t" \ - \ - "sub r0, #20 \n\t" \ - "sub r1, #20 \n\t" \ - \ - /* Do off-center multiplication */ \ - "ldmia r1!, {r6,r7,r8,r9,r10,r11} \n\t" \ - "umull r3, r4, r6, r11 \n\t" \ - "umull r6, r5, r7, r11 \n\t" \ - "adds r4, r4, r6 \n\t" \ - "umull r7, r6, r8, r11 \n\t" \ - "adcs r5, r5, r7 \n\t" \ - "umull r8, r7, r9, r11 \n\t" \ - "adcs r6, r6, r8 \n\t" \ - "umull r9, r8, r10, r11 \n\t" \ - "adcs r7, r7, r9 \n\t" \ - "adcs r8, r8, #0 \n\t" \ - \ - /* Multiply by 2 */ \ - "mov r9, #0 \n\t" \ - "adds r3, r3, r3 \n\t" \ - "adcs r4, r4, r4 \n\t" \ - "adcs r5, r5, r5 \n\t" \ - "adcs r6, r6, r6 \n\t" \ - "adcs r7, r7, r7 \n\t" \ - "adcs r8, r8, r8 \n\t" \ - "adcs r9, r9, #0 \n\t" \ - \ - /* Add into previous */ \ - "ldr r14, [r0], #4 \n\t" \ - "adds r3, r3, r14 \n\t" \ - "ldr r14, [r0], #4 \n\t" \ - "adcs r4, r4, r14 \n\t" \ - "ldr r14, [r0], #4 \n\t" \ - "adcs r5, r5, r14 \n\t" \ - "ldr r14, [r0], #4 \n\t" \ - "adcs r6, r6, r14 \n\t" \ - "ldr r14, [r0], #4 \n\t" \ - "adcs r7, r7, r14 \n\t" \ - "adcs r8, r8, #0 \n\t" \ - "adcs r9, r9, #0 \n\t" \ - "sub r0, #20 \n\t" \ - \ - /* Perform center multiplication */ \ - "umlal r8, r9, r11, r11 \n\t" \ - "stmia r0!, {r3,r4,r5,r6,r7,r8,r9} \n\t" - -#define FAST_SQUARE_ASM_6 \ - "push {r2} \n\t" \ - "ldmia r1!, {r2,r3,r4,r5,r6,r7} \n\t" \ - "push {r1} \n\t" \ - \ - "umull r11, r12, r2, r2 \n\t" \ - "stmia r0!, {r11} \n\t" \ - \ - "mov r9, #0 \n\t" \ - "umull r10, r11, r2, r3 \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r8, r11, #0 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r8, r8, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "stmia r0!, {r12} \n\t" \ - \ - "mov r10, #0 \n\t" \ - "umull r11, r12, r2, r4 \n\t" \ - "adds r11, r11, r11 \n\t" \ - "adcs r12, r12, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "adds r8, r8, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "umull r11, r12, r3, r3 \n\t" \ - "adds r8, r8, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "stmia r0!, {r8} \n\t" \ - \ - "mov r12, #0 \n\t" \ - "umull r8, r11, r2, r5 \n\t" \ - "umull r1, r14, r3, r4 \n\t" \ - "adds r8, r8, r1 \n\t" \ - "adcs r11, r11, r14 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "adds r8, r8, r8 \n\t" \ - "adcs r11, r11, r11 \n\t" \ - "adc r12, r12, r12 \n\t" \ - "adds r8, r8, r9 \n\t" \ - "adcs r11, r11, r10 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "stmia r0!, {r8} \n\t" \ - \ - "mov r10, #0 \n\t" \ - "umull r8, r9, r2, r6 \n\t" \ - "umull r1, r14, r3, r5 \n\t" \ - "adds r8, r8, r1 \n\t" \ - "adcs r9, r9, r14 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "adds r8, r8, r8 \n\t" \ - "adcs r9, r9, r9 \n\t" \ - "adc r10, r10, r10 \n\t" \ - "umull r1, r14, r4, r4 \n\t" \ - "adds r8, r8, r1 \n\t" \ - "adcs r9, r9, r14 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "adds r8, r8, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "stmia r0!, {r8} \n\t" \ - \ - "mov r12, #0 \n\t" \ - "umull r8, r11, r2, r7 \n\t" \ - "umull r1, r14, r3, r6 \n\t" \ - "adds r8, r8, r1 \n\t" \ - "adcs r11, r11, r14 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "umull r1, r14, r4, r5 \n\t" \ - "adds r8, r8, r1 \n\t" \ - "adcs r11, r11, r14 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "adds r8, r8, r8 \n\t" \ - "adcs r11, r11, r11 \n\t" \ - "adc r12, r12, r12 \n\t" \ - "adds r8, r8, r9 \n\t" \ - "adcs r11, r11, r10 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "stmia r0!, {r8} \n\t" \ - \ - "mov r10, #0 \n\t" \ - "umull r8, r9, r3, r7 \n\t" \ - "umull r1, r14, r4, r6 \n\t" \ - "adds r8, r8, r1 \n\t" \ - "adcs r9, r9, r14 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "adds r8, r8, r8 \n\t" \ - "adcs r9, r9, r9 \n\t" \ - "adc r10, r10, r10 \n\t" \ - "umull r1, r14, r5, r5 \n\t" \ - "adds r8, r8, r1 \n\t" \ - "adcs r9, r9, r14 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "adds r8, r8, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "stmia r0!, {r8} \n\t" \ - \ - "mov r12, #0 \n\t" \ - "umull r8, r11, r4, r7 \n\t" \ - "umull r1, r14, r5, r6 \n\t" \ - "adds r8, r8, r1 \n\t" \ - "adcs r11, r11, r14 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "adds r8, r8, r8 \n\t" \ - "adcs r11, r11, r11 \n\t" \ - "adc r12, r12, r12 \n\t" \ - "adds r8, r8, r9 \n\t" \ - "adcs r11, r11, r10 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "stmia r0!, {r8} \n\t" \ - \ - "mov r8, #0 \n\t" \ - "umull r1, r10, r5, r7 \n\t" \ - "adds r1, r1, r1 \n\t" \ - "adcs r10, r10, r10 \n\t" \ - "adc r8, r8, #0 \n\t" \ - "adds r11, r11, r1 \n\t" \ - "adcs r12, r12, r10 \n\t" \ - "adc r8, r8, #0 \n\t" \ - "umull r1, r10, r6, r6 \n\t" \ - "adds r11, r11, r1 \n\t" \ - "adcs r12, r12, r10 \n\t" \ - "adc r8, r8, #0 \n\t" \ - "stmia r0!, {r11} \n\t" \ - \ - "mov r11, #0 \n\t" \ - "umull r1, r10, r6, r7 \n\t" \ - "adds r1, r1, r1 \n\t" \ - "adcs r10, r10, r10 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "adds r12, r12, r1 \n\t" \ - "adcs r8, r8, r10 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "stmia r0!, {r12} \n\t" \ - \ - "umull r1, r10, r7, r7 \n\t" \ - "adds r8, r8, r1 \n\t" \ - "adcs r11, r11, r10 \n\t" \ - "stmia r0!, {r8, r11} \n\t" \ - "pop {r1, r2} \n\t" - -#define FAST_SQUARE_ASM_6_TO_7 \ - "cmp r2, #6 \n\t" \ - "beq 1f \n\t" \ - \ - "sub r0, #24 \n\t" \ - "sub r1, #24 \n\t" \ - \ - /* Do off-center multiplication */ \ - "ldmia r1!, {r6,r7,r8,r9,r10,r11,r12} \n\t" \ - "umull r3, r4, r6, r12 \n\t" \ - "umull r6, r5, r7, r12 \n\t" \ - "adds r4, r4, r6 \n\t" \ - "umull r7, r6, r8, r12 \n\t" \ - "adcs r5, r5, r7 \n\t" \ - "umull r8, r7, r9, r12 \n\t" \ - "adcs r6, r6, r8 \n\t" \ - "umull r9, r8, r10, r12 \n\t" \ - "adcs r7, r7, r9 \n\t" \ - "umull r10, r9, r11, r12 \n\t" \ - "adcs r8, r8, r10 \n\t" \ - "adcs r9, r9, #0 \n\t" \ - \ - /* Multiply by 2 */ \ - "mov r10, #0 \n\t" \ - "adds r3, r3, r3 \n\t" \ - "adcs r4, r4, r4 \n\t" \ - "adcs r5, r5, r5 \n\t" \ - "adcs r6, r6, r6 \n\t" \ - "adcs r7, r7, r7 \n\t" \ - "adcs r8, r8, r8 \n\t" \ - "adcs r9, r9, r9 \n\t" \ - "adcs r10, r10, #0 \n\t" \ - \ - /* Add into previous */ \ - "ldr r14, [r0], #4 \n\t" \ - "adds r3, r3, r14 \n\t" \ - "ldr r14, [r0], #4 \n\t" \ - "adcs r4, r4, r14 \n\t" \ - "ldr r14, [r0], #4 \n\t" \ - "adcs r5, r5, r14 \n\t" \ - "ldr r14, [r0], #4 \n\t" \ - "adcs r6, r6, r14 \n\t" \ - "ldr r14, [r0], #4 \n\t" \ - "adcs r7, r7, r14 \n\t" \ - "ldr r14, [r0], #4 \n\t" \ - "adcs r8, r8, r14 \n\t" \ - "adcs r9, r9, #0 \n\t" \ - "adcs r10, r10, #0 \n\t" \ - "sub r0, #24 \n\t" \ - \ - /* Perform center multiplication */ \ - "umlal r9, r10, r12, r12 \n\t" \ - "stmia r0!, {r3,r4,r5,r6,r7,r8,r9,r10} \n\t" - -#define FAST_SQUARE_ASM_7 \ - "push {r2} \n\t" \ - "ldmia r1!, {r2, r3, r4, r5, r6, r7, r8} \n\t" \ - "push {r1} \n\t" \ - "sub r1, 4 \n\t" \ - \ - "add r0, 24 \n\t" \ - "umull r9, r10, r2, r8 \n\t" \ - "stmia r0!, {r9, r10} \n\t" \ - "sub r0, 32 \n\t" \ - \ - "umull r11, r12, r2, r2 \n\t" \ - "stmia r0!, {r11} \n\t" \ - \ - "mov r9, #0 \n\t" \ - "umull r10, r11, r2, r3 \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r8, r11, #0 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r8, r8, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "stmia r0!, {r12} \n\t" \ - \ - "mov r10, #0 \n\t" \ - "umull r11, r12, r2, r4 \n\t" \ - "adds r11, r11, r11 \n\t" \ - "adcs r12, r12, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "adds r8, r8, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "umull r11, r12, r3, r3 \n\t" \ - "adds r8, r8, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "stmia r0!, {r8} \n\t" \ - \ - "mov r12, #0 \n\t" \ - "umull r8, r11, r2, r5 \n\t" \ - "mov r14, r11 \n\t" \ - "umlal r8, r11, r3, r4 \n\t" \ - "cmp r14, r11 \n\t" \ - "it hi \n\t" \ - "adchi r12, r12, #0 \n\t" \ - "adds r8, r8, r8 \n\t" \ - "adcs r11, r11, r11 \n\t" \ - "adc r12, r12, r12 \n\t" \ - "adds r8, r8, r9 \n\t" \ - "adcs r11, r11, r10 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "stmia r0!, {r8} \n\t" \ - \ - "mov r10, #0 \n\t" \ - "umull r8, r9, r2, r6 \n\t" \ - "mov r14, r9 \n\t" \ - "umlal r8, r9, r3, r5 \n\t" \ - "cmp r14, r9 \n\t" \ - "it hi \n\t" \ - "adchi r10, r10, #0 \n\t" \ - "adds r8, r8, r8 \n\t" \ - "adcs r9, r9, r9 \n\t" \ - "adc r10, r10, r10 \n\t" \ - "mov r14, r9 \n\t" \ - "umlal r8, r9, r4, r4 \n\t" \ - "cmp r14, r9 \n\t" \ - "it hi \n\t" \ - "adchi r10, r10, #0 \n\t" \ - "adds r8, r8, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "stmia r0!, {r8} \n\t" \ - \ - "mov r12, #0 \n\t" \ - "umull r8, r11, r2, r7 \n\t" \ - "mov r14, r11 \n\t" \ - "umlal r8, r11, r3, r6 \n\t" \ - "cmp r14, r11 \n\t" \ - "it hi \n\t" \ - "adchi r12, r12, #0 \n\t" \ - "mov r14, r11 \n\t" \ - "umlal r8, r11, r4, r5 \n\t" \ - "cmp r14, r11 \n\t" \ - "it hi \n\t" \ - "adchi r12, r12, #0 \n\t" \ - "adds r8, r8, r8 \n\t" \ - "adcs r11, r11, r11 \n\t" \ - "adc r12, r12, r12 \n\t" \ - "adds r8, r8, r9 \n\t" \ - "adcs r11, r11, r10 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "stmia r0!, {r8} \n\t" \ - \ - "ldmia r1!, {r2} \n\t" \ - "mov r10, #0 \n\t" \ - "umull r8, r9, r3, r7 \n\t" \ - "mov r14, r9 \n\t" \ - "umlal r8, r9, r4, r6 \n\t" \ - "cmp r14, r9 \n\t" \ - "it hi \n\t" \ - "adchi r10, r10, #0 \n\t" \ - "ldr r14, [r0] \n\t" \ - "adds r8, r8, r14 \n\t" \ - "adcs r9, r9, #0 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "adds r8, r8, r8 \n\t" \ - "adcs r9, r9, r9 \n\t" \ - "adc r10, r10, r10 \n\t" \ - "mov r14, r9 \n\t" \ - "umlal r8, r9, r5, r5 \n\t" \ - "cmp r14, r9 \n\t" \ - "it hi \n\t" \ - "adchi r10, r10, #0 \n\t" \ - "adds r8, r8, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "stmia r0!, {r8} \n\t" \ - \ - "mov r12, #0 \n\t" \ - "umull r8, r11, r3, r2 \n\t" \ - "mov r14, r11 \n\t" \ - "umlal r8, r11, r4, r7 \n\t" \ - "cmp r14, r11 \n\t" \ - "it hi \n\t" \ - "adchi r12, r12, #0 \n\t" \ - "mov r14, r11 \n\t" \ - "umlal r8, r11, r5, r6 \n\t" \ - "cmp r14, r11 \n\t" \ - "it hi \n\t" \ - "adchi r12, r12, #0 \n\t" \ - "ldr r14, [r0] \n\t" \ - "adds r8, r8, r14 \n\t" \ - "adcs r11, r11, #0 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "adds r8, r8, r8 \n\t" \ - "adcs r11, r11, r11 \n\t" \ - "adc r12, r12, r12 \n\t" \ - "adds r8, r8, r9 \n\t" \ - "adcs r11, r11, r10 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "stmia r0!, {r8} \n\t" \ - \ - "mov r10, #0 \n\t" \ - "umull r8, r9, r4, r2 \n\t" \ - "mov r14, r9 \n\t" \ - "umlal r8, r9, r5, r7 \n\t" \ - "cmp r14, r9 \n\t" \ - "it hi \n\t" \ - "adchi r10, r10, #0 \n\t" \ - "adds r8, r8, r8 \n\t" \ - "adcs r9, r9, r9 \n\t" \ - "adc r10, r10, r10 \n\t" \ - "mov r14, r9 \n\t" \ - "umlal r8, r9, r6, r6 \n\t" \ - "cmp r14, r9 \n\t" \ - "it hi \n\t" \ - "adchi r10, r10, #0 \n\t" \ - "adds r8, r8, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "stmia r0!, {r8} \n\t" \ - \ - "mov r12, #0 \n\t" \ - "umull r8, r11, r5, r2 \n\t" \ - "mov r14, r11 \n\t" \ - "umlal r8, r11, r6, r7 \n\t" \ - "cmp r14, r11 \n\t" \ - "it hi \n\t" \ - "adchi r12, r12, #0 \n\t" \ - "adds r8, r8, r8 \n\t" \ - "adcs r11, r11, r11 \n\t" \ - "adc r12, r12, r12 \n\t" \ - "adds r8, r8, r9 \n\t" \ - "adcs r11, r11, r10 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "stmia r0!, {r8} \n\t" \ - \ - "mov r8, #0 \n\t" \ - "umull r1, r10, r6, r2 \n\t" \ - "adds r1, r1, r1 \n\t" \ - "adcs r10, r10, r10 \n\t" \ - "adc r8, r8, #0 \n\t" \ - "adds r11, r11, r1 \n\t" \ - "adcs r12, r12, r10 \n\t" \ - "adc r8, r8, #0 \n\t" \ - "umull r1, r10, r7, r7 \n\t" \ - "adds r11, r11, r1 \n\t" \ - "adcs r12, r12, r10 \n\t" \ - "adc r8, r8, #0 \n\t" \ - "stmia r0!, {r11} \n\t" \ - \ - "mov r11, #0 \n\t" \ - "umull r1, r10, r7, r2 \n\t" \ - "adds r1, r1, r1 \n\t" \ - "adcs r10, r10, r10 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "adds r12, r12, r1 \n\t" \ - "adcs r8, r8, r10 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "stmia r0!, {r12} \n\t" \ - \ - "umull r1, r10, r2, r2 \n\t" \ - "adds r8, r8, r1 \n\t" \ - "adcs r11, r11, r10 \n\t" \ - "stmia r0!, {r8, r11} \n\t" \ - "pop {r1, r2} \n\t" - -#define FAST_SQUARE_ASM_7_TO_8 \ - "cmp r2, #7 \n\t" \ - "beq 1f \n\t" \ - \ - "sub r0, #28 \n\t" \ - "sub r1, #28 \n\t" \ - \ - /* Do off-center multiplication */ \ - "ldmia r1!, {r6,r7,r8,r9,r10,r11,r12,r14} \n\t" \ - "umull r3, r4, r6, r14 \n\t" \ - "umull r6, r5, r7, r14 \n\t" \ - "adds r4, r4, r6 \n\t" \ - "umull r7, r6, r8, r14 \n\t" \ - "adcs r5, r5, r7 \n\t" \ - "umull r8, r7, r9, r14 \n\t" \ - "adcs r6, r6, r8 \n\t" \ - "umull r9, r8, r10, r14 \n\t" \ - "adcs r7, r7, r9 \n\t" \ - "umull r10, r9, r11, r14 \n\t" \ - "adcs r8, r8, r10 \n\t" \ - "umull r11, r10, r12, r14 \n\t" \ - "adcs r9, r9, r11 \n\t" \ - "adcs r10, r10, #0 \n\t" \ - \ - /* Multiply by 2 */ \ - "mov r11, #0 \n\t" \ - "adds r3, r3, r3 \n\t" \ - "adcs r4, r4, r4 \n\t" \ - "adcs r5, r5, r5 \n\t" \ - "adcs r6, r6, r6 \n\t" \ - "adcs r7, r7, r7 \n\t" \ - "adcs r8, r8, r8 \n\t" \ - "adcs r9, r9, r9 \n\t" \ - "adcs r10, r10, r10 \n\t" \ - "adcs r11, r11, #0 \n\t" \ - \ - /* Add into previous */ \ - "ldr r12, [r0], #4 \n\t" \ - "adds r3, r3, r12 \n\t" \ - "ldr r12, [r0], #4 \n\t" \ - "adcs r4, r4, r12 \n\t" \ - "ldr r12, [r0], #4 \n\t" \ - "adcs r5, r5, r12 \n\t" \ - "ldr r12, [r0], #4 \n\t" \ - "adcs r6, r6, r12 \n\t" \ - "ldr r12, [r0], #4 \n\t" \ - "adcs r7, r7, r12 \n\t" \ - "ldr r12, [r0], #4 \n\t" \ - "adcs r8, r8, r12 \n\t" \ - "ldr r12, [r0], #4 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adcs r10, r10, #0 \n\t" \ - "adcs r11, r11, #0 \n\t" \ - "sub r0, #28 \n\t" \ - \ - /* Perform center multiplication */ \ - "umlal r10, r11, r14, r14 \n\t" \ - "stmia r0!, {r3,r4,r5,r6,r7,r8,r9,r10,r11} \n\t" - -#define FAST_SQUARE_ASM_8 \ - "push {r2} \n\t" \ - "ldmia r1!, {r2,r3,r4,r5,r6,r7,r8,r9} \n\t" \ - "push {r1} \n\t" \ - "sub r1, 8 \n\t" \ - \ - "add r0, 24 \n\t" \ - "umull r10, r11, r2, r8 \n\t" \ - "umull r12, r14, r2, r9 \n\t" \ - "umull r8, r9, r3, r9 \n\t" \ - "adds r11, r11, r12 \n\t" \ - "adcs r12, r14, r8 \n\t" \ - "adcs r14, r9, #0 \n\t" \ - "stmia r0!, {r10, r11, r12, r14} \n\t" \ - "sub r0, 40 \n\t" \ - \ - "umull r11, r12, r2, r2 \n\t" \ - "stmia r0!, {r11} \n\t" \ - \ - "mov r9, #0 \n\t" \ - "umull r10, r11, r2, r3 \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r8, r11, #0 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "adds r12, r12, r10 \n\t" \ - "adcs r8, r8, r11 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "stmia r0!, {r12} \n\t" \ - \ - "mov r10, #0 \n\t" \ - "umull r11, r12, r2, r4 \n\t" \ - "adds r11, r11, r11 \n\t" \ - "adcs r12, r12, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "adds r8, r8, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "umull r11, r12, r3, r3 \n\t" \ - "adds r8, r8, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "stmia r0!, {r8} \n\t" \ - \ - "mov r12, #0 \n\t" \ - "umull r8, r11, r2, r5 \n\t" \ - "mov r14, r11 \n\t" \ - "umlal r8, r11, r3, r4 \n\t" \ - "cmp r14, r11 \n\t" \ - "it hi \n\t" \ - "adchi r12, r12, #0 \n\t" \ - "adds r8, r8, r8 \n\t" \ - "adcs r11, r11, r11 \n\t" \ - "adc r12, r12, r12 \n\t" \ - "adds r8, r8, r9 \n\t" \ - "adcs r11, r11, r10 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "stmia r0!, {r8} \n\t" \ - \ - "mov r10, #0 \n\t" \ - "umull r8, r9, r2, r6 \n\t" \ - "mov r14, r9 \n\t" \ - "umlal r8, r9, r3, r5 \n\t" \ - "cmp r14, r9 \n\t" \ - "it hi \n\t" \ - "adchi r10, r10, #0 \n\t" \ - "adds r8, r8, r8 \n\t" \ - "adcs r9, r9, r9 \n\t" \ - "adc r10, r10, r10 \n\t" \ - "mov r14, r9 \n\t" \ - "umlal r8, r9, r4, r4 \n\t" \ - "cmp r14, r9 \n\t" \ - "it hi \n\t" \ - "adchi r10, r10, #0 \n\t" \ - "adds r8, r8, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "stmia r0!, {r8} \n\t" \ - \ - "mov r12, #0 \n\t" \ - "umull r8, r11, r2, r7 \n\t" \ - "mov r14, r11 \n\t" \ - "umlal r8, r11, r3, r6 \n\t" \ - "cmp r14, r11 \n\t" \ - "it hi \n\t" \ - "adchi r12, r12, #0 \n\t" \ - "mov r14, r11 \n\t" \ - "umlal r8, r11, r4, r5 \n\t" \ - "cmp r14, r11 \n\t" \ - "it hi \n\t" \ - "adchi r12, r12, #0 \n\t" \ - "adds r8, r8, r8 \n\t" \ - "adcs r11, r11, r11 \n\t" \ - "adc r12, r12, r12 \n\t" \ - "adds r8, r8, r9 \n\t" \ - "adcs r11, r11, r10 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "stmia r0!, {r8} \n\t" \ - \ - "ldmia r1!, {r2} \n\t" \ - "mov r10, #0 \n\t" \ - "umull r8, r9, r3, r7 \n\t" \ - "mov r14, r9 \n\t" \ - "umlal r8, r9, r4, r6 \n\t" \ - "cmp r14, r9 \n\t" \ - "it hi \n\t" \ - "adchi r10, r10, #0 \n\t" \ - "ldr r14, [r0] \n\t" \ - "adds r8, r8, r14 \n\t" \ - "adcs r9, r9, #0 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "adds r8, r8, r8 \n\t" \ - "adcs r9, r9, r9 \n\t" \ - "adc r10, r10, r10 \n\t" \ - "mov r14, r9 \n\t" \ - "umlal r8, r9, r5, r5 \n\t" \ - "cmp r14, r9 \n\t" \ - "it hi \n\t" \ - "adchi r10, r10, #0 \n\t" \ - "adds r8, r8, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "stmia r0!, {r8} \n\t" \ - \ - "mov r12, #0 \n\t" \ - "umull r8, r11, r3, r2 \n\t" \ - "mov r14, r11 \n\t" \ - "umlal r8, r11, r4, r7 \n\t" \ - "cmp r14, r11 \n\t" \ - "it hi \n\t" \ - "adchi r12, r12, #0 \n\t" \ - "mov r14, r11 \n\t" \ - "umlal r8, r11, r5, r6 \n\t" \ - "cmp r14, r11 \n\t" \ - "it hi \n\t" \ - "adchi r12, r12, #0 \n\t" \ - "ldr r14, [r0] \n\t" \ - "adds r8, r8, r14 \n\t" \ - "adcs r11, r11, #0 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "adds r8, r8, r8 \n\t" \ - "adcs r11, r11, r11 \n\t" \ - "adc r12, r12, r12 \n\t" \ - "adds r8, r8, r9 \n\t" \ - "adcs r11, r11, r10 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "stmia r0!, {r8} \n\t" \ - \ - "ldmia r1!, {r3} \n\t" \ - "mov r10, #0 \n\t" \ - "umull r8, r9, r4, r2 \n\t" \ - "mov r14, r9 \n\t" \ - "umlal r8, r9, r5, r7 \n\t" \ - "cmp r14, r9 \n\t" \ - "it hi \n\t" \ - "adchi r10, r10, #0 \n\t" \ - "ldr r14, [r0] \n\t" \ - "adds r8, r8, r14 \n\t" \ - "adcs r9, r9, #0 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "adds r8, r8, r8 \n\t" \ - "adcs r9, r9, r9 \n\t" \ - "adc r10, r10, r10 \n\t" \ - "mov r14, r9 \n\t" \ - "umlal r8, r9, r6, r6 \n\t" \ - "cmp r14, r9 \n\t" \ - "it hi \n\t" \ - "adchi r10, r10, #0 \n\t" \ - "adds r8, r8, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "stmia r0!, {r8} \n\t" \ - \ - "mov r12, #0 \n\t" \ - "umull r8, r11, r4, r3 \n\t" \ - "mov r14, r11 \n\t" \ - "umlal r8, r11, r5, r2 \n\t" \ - "cmp r14, r11 \n\t" \ - "it hi \n\t" \ - "adchi r12, r12, #0 \n\t" \ - "mov r14, r11 \n\t" \ - "umlal r8, r11, r6, r7 \n\t" \ - "cmp r14, r11 \n\t" \ - "it hi \n\t" \ - "adchi r12, r12, #0 \n\t" \ - "ldr r14, [r0] \n\t" \ - "adds r8, r8, r14 \n\t" \ - "adcs r11, r11, #0 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "adds r8, r8, r8 \n\t" \ - "adcs r11, r11, r11 \n\t" \ - "adc r12, r12, r12 \n\t" \ - "adds r8, r8, r9 \n\t" \ - "adcs r11, r11, r10 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "stmia r0!, {r8} \n\t" \ - \ - "mov r10, #0 \n\t" \ - "umull r8, r9, r5, r3 \n\t" \ - "mov r14, r9 \n\t" \ - "umlal r8, r9, r6, r2 \n\t" \ - "cmp r14, r9 \n\t" \ - "it hi \n\t" \ - "adchi r10, r10, #0 \n\t" \ - "adds r8, r8, r8 \n\t" \ - "adcs r9, r9, r9 \n\t" \ - "adc r10, r10, r10 \n\t" \ - "mov r14, r9 \n\t" \ - "umlal r8, r9, r7, r7 \n\t" \ - "cmp r14, r9 \n\t" \ - "it hi \n\t" \ - "adchi r10, r10, #0 \n\t" \ - "adds r8, r8, r11 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - "stmia r0!, {r8} \n\t" \ - \ - "mov r12, #0 \n\t" \ - "umull r8, r11, r6, r3 \n\t" \ - "mov r14, r11 \n\t" \ - "umlal r8, r11, r7, r2 \n\t" \ - "cmp r14, r11 \n\t" \ - "it hi \n\t" \ - "adchi r12, r12, #0 \n\t" \ - "adds r8, r8, r8 \n\t" \ - "adcs r11, r11, r11 \n\t" \ - "adc r12, r12, r12 \n\t" \ - "adds r8, r8, r9 \n\t" \ - "adcs r11, r11, r10 \n\t" \ - "adc r12, r12, #0 \n\t" \ - "stmia r0!, {r8} \n\t" \ - \ - "mov r8, #0 \n\t" \ - "umull r1, r10, r7, r3 \n\t" \ - "adds r1, r1, r1 \n\t" \ - "adcs r10, r10, r10 \n\t" \ - "adc r8, r8, #0 \n\t" \ - "adds r11, r11, r1 \n\t" \ - "adcs r12, r12, r10 \n\t" \ - "adc r8, r8, #0 \n\t" \ - "umull r1, r10, r2, r2 \n\t" \ - "adds r11, r11, r1 \n\t" \ - "adcs r12, r12, r10 \n\t" \ - "adc r8, r8, #0 \n\t" \ - "stmia r0!, {r11} \n\t" \ - \ - "mov r11, #0 \n\t" \ - "umull r1, r10, r2, r3 \n\t" \ - "adds r1, r1, r1 \n\t" \ - "adcs r10, r10, r10 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "adds r12, r12, r1 \n\t" \ - "adcs r8, r8, r10 \n\t" \ - "adc r11, r11, #0 \n\t" \ - "stmia r0!, {r12} \n\t" \ - \ - "umull r1, r10, r3, r3 \n\t" \ - "adds r8, r8, r1 \n\t" \ - "adcs r11, r11, r10 \n\t" \ - "stmia r0!, {r8, r11} \n\t" \ - "pop {r1, r2} \n\t" - -#endif /* _UECC_ASM_ARM_MULT_SQUARE_H_ */ diff --git a/lib/micro-ecc/asm_arm_mult_square_umaal.inc b/lib/micro-ecc/asm_arm_mult_square_umaal.inc deleted file mode 100644 index c554d20e385..00000000000 --- a/lib/micro-ecc/asm_arm_mult_square_umaal.inc +++ /dev/null @@ -1,1202 +0,0 @@ -/* Copyright 2015, Kenneth MacKay. Licensed under the BSD 2-clause license. */ - -#ifndef _UECC_ASM_ARM_MULT_SQUARE_H_ -#define _UECC_ASM_ARM_MULT_SQUARE_H_ - -#define FAST_MULT_ASM_5 \ - "push {r3} \n\t" \ - "ldmia r2!, {r3, r4, r5, r6, r7} \n\t" \ - "push {r2} \n\t" \ - \ - "ldr r2, [r1], #4 \n\t" \ - "umull r8, r9, r3, r2 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "mov r10, #0 \n\t" \ - "umaal r9, r10, r4, r2 \n\t" \ - "mov r11, #0 \n\t" \ - "umaal r10, r11, r5, r2 \n\t" \ - "mov r12, #0 \n\t" \ - "umaal r11, r12, r6, r2 \n\t" \ - "mov r14, #0 \n\t" \ - "umaal r12, r14, r7, r2 \n\t" \ - \ - "ldr r2, [r1], #4 \n\t" \ - "mov r8, #0 \n\t" \ - "umaal r8, r9, r3, r2 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r4, r2 \n\t" \ - "umaal r10, r11, r5, r2 \n\t" \ - "umaal r11, r12, r6, r2 \n\t" \ - "umaal r12, r14, r7, r2 \n\t" \ - \ - "ldr r2, [r1], #4 \n\t" \ - "mov r8, #0 \n\t" \ - "umaal r8, r9, r3, r2 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r4, r2 \n\t" \ - "umaal r10, r11, r5, r2 \n\t" \ - "umaal r11, r12, r6, r2 \n\t" \ - "umaal r12, r14, r7, r2 \n\t" \ - \ - "ldr r2, [r1], #4 \n\t" \ - "mov r8, #0 \n\t" \ - "umaal r8, r9, r3, r2 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r4, r2 \n\t" \ - "umaal r10, r11, r5, r2 \n\t" \ - "umaal r11, r12, r6, r2 \n\t" \ - "umaal r12, r14, r7, r2 \n\t" \ - \ - "ldr r2, [r1], #4 \n\t" \ - "mov r8, #0 \n\t" \ - "umaal r8, r9, r3, r2 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r4, r2 \n\t" \ - "umaal r10, r11, r5, r2 \n\t" \ - "umaal r11, r12, r6, r2 \n\t" \ - "umaal r12, r14, r7, r2 \n\t" \ - \ - "str r9, [r0], #4 \n\t" \ - "str r10, [r0], #4 \n\t" \ - "str r11, [r0], #4 \n\t" \ - "str r12, [r0], #4 \n\t" \ - "str r14, [r0], #4 \n\t" \ - \ - "pop {r2, r3} \n\t" - -#define FAST_MULT_ASM_5_TO_6 \ - "cmp r3, #5 \n\t" \ - "beq 1f \n\t" \ - \ - /* r4 = left high */ \ - "ldr r4, [r1] \n\t" \ - \ - "sub r0, #20 \n\t" \ - "sub r1, #20 \n\t" \ - "sub r2, #20 \n\t" \ - \ - /* Do right side */ \ - "ldr r14, [r2], #4 \n\t" \ - "mov r5, #0 \n\t" \ - "ldr r6, [r0], #4 \n\t" \ - "umaal r5, r6, r4, r14 \n\t" \ - "ldr r14, [r2], #4 \n\t" \ - "ldr r7, [r0], #4 \n\t" \ - "umaal r6, r7, r4, r14 \n\t" \ - "ldr r14, [r2], #4 \n\t" \ - "ldr r8, [r0], #4 \n\t" \ - "umaal r7, r8, r4, r14 \n\t" \ - "ldr r14, [r2], #4 \n\t" \ - "ldr r9, [r0], #4 \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "ldr r14, [r2], #4 \n\t" \ - "ldr r10, [r0], #4 \n\t" \ - "umaal r9, r10, r4, r14 \n\t" \ - "sub r0, #20 \n\t" \ - \ - /* r4 = right high */ \ - "ldr r4, [r2], #4 \n\t" \ - \ - /* Do left side */ \ - "ldr r14, [r1], #4 \n\t" \ - "mov r12, #0 \n\t" \ - "umaal r12, r5, r4, r14 \n\t" \ - "str r12, [r0], #4 \n\t" \ - "ldr r14, [r1], #4 \n\t" \ - "umaal r5, r6, r4, r14 \n\t" \ - "str r5, [r0], #4 \n\t" \ - "ldr r14, [r1], #4 \n\t" \ - "umaal r6, r7, r4, r14 \n\t" \ - "str r6, [r0], #4 \n\t" \ - "ldr r14, [r1], #4 \n\t" \ - "umaal r7, r8, r4, r14 \n\t" \ - "str r7, [r0], #4 \n\t" \ - "ldr r14, [r1], #4 \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "umaal r9, r10, r4, r14 \n\t" \ - "stmia r0!, {r9, r10} \n\t" - -#define FAST_MULT_ASM_6 \ - "ldmia r2!, {r4, r5, r6} \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "umull r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "mov r10, #0 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "mov r11, #0 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "mov r8, #0 \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "mov r8, #0 \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "mov r8, #0 \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "mov r8, #0 \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "mov r8, #0 \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - \ - "str r9, [r0], #4 \n\t" \ - "str r10, [r0], #4 \n\t" \ - "str r11, [r0], #4 \n\t" \ - \ - "sub r0, #24 \n\t" \ - "sub r1, #24 \n\t" \ - "ldmia r2!, {r4, r5, r6} \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "ldr r8, [r0] \n\t" \ - "mov r9, #0 \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "mov r10, #0 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "mov r11, #0 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "ldr r8, [r0] \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "ldr r8, [r0] \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "ldr r8, [r0] \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "ldr r8, [r0] \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "ldr r8, [r0] \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - \ - "str r9, [r0], #4 \n\t" \ - "str r10, [r0], #4 \n\t" \ - "str r11, [r0], #4 \n\t" - -#define FAST_MULT_ASM_6_TO_7 \ - "cmp r3, #6 \n\t" \ - "beq 1f \n\t" \ - \ - /* r4 = left high */ \ - "ldr r4, [r1] \n\t" \ - \ - "sub r0, #24 \n\t" \ - "sub r1, #24 \n\t" \ - "sub r2, #24 \n\t" \ - \ - /* Do right side */ \ - "ldr r14, [r2], #4 \n\t" \ - "mov r5, #0 \n\t" \ - "ldr r6, [r0], #4 \n\t" \ - "umaal r5, r6, r4, r14 \n\t" \ - "ldr r14, [r2], #4 \n\t" \ - "ldr r7, [r0], #4 \n\t" \ - "umaal r6, r7, r4, r14 \n\t" \ - "ldr r14, [r2], #4 \n\t" \ - "ldr r8, [r0], #4 \n\t" \ - "umaal r7, r8, r4, r14 \n\t" \ - "ldr r14, [r2], #4 \n\t" \ - "ldr r9, [r0], #4 \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "ldr r14, [r2], #4 \n\t" \ - "ldr r10, [r0], #4 \n\t" \ - "umaal r9, r10, r4, r14 \n\t" \ - "ldr r14, [r2], #4 \n\t" \ - "ldr r11, [r0], #4 \n\t" \ - "umaal r10, r11, r4, r14 \n\t" \ - "sub r0, #24 \n\t" \ - \ - /* r4 = right high */ \ - "ldr r4, [r2], #4 \n\t" \ - \ - /* Do left side */ \ - "ldr r14, [r1], #4 \n\t" \ - "mov r12, #0 \n\t" \ - "umaal r12, r5, r4, r14 \n\t" \ - "str r12, [r0], #4 \n\t" \ - "ldr r14, [r1], #4 \n\t" \ - "umaal r5, r6, r4, r14 \n\t" \ - "str r5, [r0], #4 \n\t" \ - "ldr r14, [r1], #4 \n\t" \ - "umaal r6, r7, r4, r14 \n\t" \ - "str r6, [r0], #4 \n\t" \ - "ldr r14, [r1], #4 \n\t" \ - "umaal r7, r8, r4, r14 \n\t" \ - "str r7, [r0], #4 \n\t" \ - "ldr r14, [r1], #4 \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "ldr r14, [r1], #4 \n\t" \ - "umaal r9, r10, r4, r14 \n\t" \ - "str r9, [r0], #4 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "umaal r10, r11, r4, r14 \n\t" \ - "stmia r0!, {r10, r11} \n\t" - -#define FAST_MULT_ASM_7 \ - "ldmia r2!, {r4, r5, r6, r7} \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "umull r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "mov r10, #0 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "mov r11, #0 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - "mov r12, #0 \n\t" \ - "umaal r11, r12, r7, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "mov r8, #0 \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - "umaal r11, r12, r7, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "mov r8, #0 \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - "umaal r11, r12, r7, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "mov r8, #0 \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - "umaal r11, r12, r7, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "mov r8, #0 \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - "umaal r11, r12, r7, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "mov r8, #0 \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - "umaal r11, r12, r7, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "mov r8, #0 \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - "umaal r11, r12, r7, r14 \n\t" \ - \ - "str r9, [r0], #4 \n\t" \ - "str r10, [r0], #4 \n\t" \ - "str r11, [r0], #4 \n\t" \ - "str r12, [r0], #4 \n\t" \ - \ - "sub r0, #28 \n\t" \ - "sub r1, #28 \n\t" \ - "ldmia r2!, {r4, r5, r6} \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "ldr r8, [r0] \n\t" \ - "mov r9, #0 \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "mov r10, #0 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "mov r11, #0 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "ldr r8, [r0] \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "ldr r8, [r0] \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "ldr r8, [r0] \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "ldr r8, [r0] \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "ldr r8, [r0] \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "ldr r8, [r0] \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - \ - "str r9, [r0], #4 \n\t" \ - "str r10, [r0], #4 \n\t" \ - "str r11, [r0], #4 \n\t" - -#define FAST_MULT_ASM_7_TO_8 \ - "cmp r3, #7 \n\t" \ - "beq 1f \n\t" \ - "push {r3} \n\t" \ - \ - /* r4 = left high */ \ - "ldr r4, [r1] \n\t" \ - \ - "sub r0, #28 \n\t" \ - "sub r1, #28 \n\t" \ - "sub r2, #28 \n\t" \ - \ - /* Do right side */ \ - "ldr r14, [r2], #4 \n\t" \ - "mov r5, #0 \n\t" \ - "ldr r6, [r0], #4 \n\t" \ - "umaal r5, r6, r4, r14 \n\t" \ - "ldr r14, [r2], #4 \n\t" \ - "ldr r7, [r0], #4 \n\t" \ - "umaal r6, r7, r4, r14 \n\t" \ - "ldr r14, [r2], #4 \n\t" \ - "ldr r8, [r0], #4 \n\t" \ - "umaal r7, r8, r4, r14 \n\t" \ - "ldr r14, [r2], #4 \n\t" \ - "ldr r9, [r0], #4 \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "ldr r14, [r2], #4 \n\t" \ - "ldr r10, [r0], #4 \n\t" \ - "umaal r9, r10, r4, r14 \n\t" \ - "ldr r14, [r2], #4 \n\t" \ - "ldr r11, [r0], #4 \n\t" \ - "umaal r10, r11, r4, r14 \n\t" \ - "ldr r14, [r2], #4 \n\t" \ - "ldr r12, [r0], #4 \n\t" \ - "umaal r11, r12, r4, r14 \n\t" \ - "sub r0, #28 \n\t" \ - \ - /* r4 = right high */ \ - "ldr r4, [r2], #4 \n\t" \ - \ - /* Do left side */ \ - "ldr r14, [r1], #4 \n\t" \ - "mov r3, #0 \n\t" \ - "umaal r3, r5, r4, r14 \n\t" \ - "str r3, [r0], #4 \n\t" \ - "ldr r14, [r1], #4 \n\t" \ - "umaal r5, r6, r4, r14 \n\t" \ - "str r5, [r0], #4 \n\t" \ - "ldr r14, [r1], #4 \n\t" \ - "umaal r6, r7, r4, r14 \n\t" \ - "str r6, [r0], #4 \n\t" \ - "ldr r14, [r1], #4 \n\t" \ - "umaal r7, r8, r4, r14 \n\t" \ - "str r7, [r0], #4 \n\t" \ - "ldr r14, [r1], #4 \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "ldr r14, [r1], #4 \n\t" \ - "umaal r9, r10, r4, r14 \n\t" \ - "str r9, [r0], #4 \n\t" \ - "ldr r14, [r1], #4 \n\t" \ - "umaal r10, r11, r4, r14 \n\t" \ - "str r10, [r0], #4 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "umaal r11, r12, r4, r14 \n\t" \ - "stmia r0!, {r11, r12} \n\t" \ - "pop {r3} \n\t" - -#define FAST_MULT_ASM_8 \ - "ldmia r2!, {r4, r5, r6, r7} \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "umull r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "mov r10, #0 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "mov r11, #0 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - "mov r12, #0 \n\t" \ - "umaal r11, r12, r7, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "mov r8, #0 \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - "umaal r11, r12, r7, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "mov r8, #0 \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - "umaal r11, r12, r7, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "mov r8, #0 \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - "umaal r11, r12, r7, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "mov r8, #0 \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - "umaal r11, r12, r7, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "mov r8, #0 \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - "umaal r11, r12, r7, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "mov r8, #0 \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - "umaal r11, r12, r7, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "mov r8, #0 \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - "umaal r11, r12, r7, r14 \n\t" \ - \ - "str r9, [r0], #4 \n\t" \ - "str r10, [r0], #4 \n\t" \ - "str r11, [r0], #4 \n\t" \ - "str r12, [r0], #4 \n\t" \ - \ - "sub r0, #32 \n\t" \ - "sub r1, #32 \n\t" \ - "ldmia r2!, {r4, r5, r6, r7} \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "ldr r8, [r0] \n\t" \ - "mov r9, #0 \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "mov r10, #0 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "mov r11, #0 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - "mov r12, #0 \n\t" \ - "umaal r11, r12, r7, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "ldr r8, [r0] \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - "umaal r11, r12, r7, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "ldr r8, [r0] \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - "umaal r11, r12, r7, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "ldr r8, [r0] \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - "umaal r11, r12, r7, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "ldr r8, [r0] \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - "umaal r11, r12, r7, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "ldr r8, [r0] \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - "umaal r11, r12, r7, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "ldr r8, [r0] \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - "umaal r11, r12, r7, r14 \n\t" \ - \ - "ldr r14, [r1], #4 \n\t" \ - "ldr r8, [r0] \n\t" \ - "umaal r8, r9, r4, r14 \n\t" \ - "str r8, [r0], #4 \n\t" \ - "umaal r9, r10, r5, r14 \n\t" \ - "umaal r10, r11, r6, r14 \n\t" \ - "umaal r11, r12, r7, r14 \n\t" \ - \ - "str r9, [r0], #4 \n\t" \ - "str r10, [r0], #4 \n\t" \ - "str r11, [r0], #4 \n\t" \ - "str r12, [r0], #4 \n\t" - -#define FAST_SQUARE_ASM_5 \ - "ldmia r1!, {r9,r10,r11,r12,r14} \n\t" \ - "push {r1, r2} \n\t" \ - \ - "umull r1, r2, r10, r9 \n\t" \ - "mov r3, #0 \n\t" \ - "umaal r2, r3, r11, r9 \n\t" \ - "mov r4, #0 \n\t" \ - "umaal r3, r4, r12, r9 \n\t" \ - "mov r5, #0 \n\t" \ - "umaal r4, r5, r14, r9 \n\t" \ - \ - "mov r6, #0 \n\t" \ - "umaal r6, r3, r11, r10 \n\t" \ - "umaal r3, r4, r12, r10 \n\t" \ - "adds r1, r1, r1 \n\t" \ - "adcs r2, r2, r2 \n\t" \ - "adcs r6, r6, r6 \n\t" \ - "adcs r3, r3, r3 \n\t" \ - \ - "umull r7, r8, r9, r9 \n\t" \ - /* Store carry in r9 */ \ - "mov r9, #0 \n\t" \ - "adc r9, r9, #0 \n\t" \ - "adds r8, r8, r1 \n\t" \ - "stmia r0!, {r7,r8} \n\t" \ - \ - "umull r7, r8, r10, r10 \n\t" \ - "adcs r7, r7, r2 \n\t" \ - "adcs r8, r8, r6 \n\t" \ - "stmia r0!, {r7,r8} \n\t" \ - \ - "umaal r4, r5, r14, r10 \n\t" \ - /* Store carry in r10 */ \ - "mov r10, #0 \n\t" \ - "adc r10, r10, #0 \n\t" \ - \ - "mov r1, #0 \n\t" \ - "umaal r1, r4, r12, r11 \n\t" \ - "umaal r4, r5, r14, r11 \n\t" \ - \ - "mov r2, #0 \n\t" \ - "umaal r2, r5, r14, r12 \n\t" \ - /* Load carry from r9 */ \ - "lsrs r9, #1 \n\t" \ - "adcs r1, r1, r1 \n\t" \ - "adcs r4, r4, r4 \n\t" \ - "adcs r2, r2, r2 \n\t" \ - "adcs r5, r5, r5 \n\t" \ - /* r9 is 0 now */ \ - "adc r9, r9, #0 \n\t" \ - \ - /* Use carry from r10 */ \ - "umaal r3, r10, r11, r11 \n\t" \ - "adds r10, r10, r1 \n\t" \ - "stmia r0!, {r3,r10} \n\t" \ - \ - "umull r6, r10, r12, r12 \n\t" \ - "adcs r6, r6, r4 \n\t" \ - "adcs r10, r10, r2 \n\t" \ - "stmia r0!, {r6,r10} \n\t" \ - \ - "umull r6, r10, r14, r14 \n\t" \ - "adcs r6, r6, r5 \n\t" \ - "adcs r10, r10, r9 \n\t" \ - "stmia r0!, {r6,r10} \n\t" \ - "pop {r1, r2} \n\t" - -#define FAST_SQUARE_ASM_5_TO_6 \ - "cmp r2, #5 \n\t" \ - "beq 1f \n\t" \ - \ - "sub r0, #20 \n\t" \ - "sub r1, #20 \n\t" \ - \ - /* Do off-center multiplication */ \ - "ldmia r1!, {r5,r6,r7,r8,r9,r14} \n\t" \ - "umull r3, r4, r5, r14 \n\t" \ - "mov r5, #0 \n\t" \ - "umaal r4, r5, r6, r14 \n\t" \ - "mov r6, #0 \n\t" \ - "umaal r5, r6, r7, r14 \n\t" \ - "mov r7, #0 \n\t" \ - "umaal r6, r7, r8, r14 \n\t" \ - "mov r8, #0 \n\t" \ - "umaal r7, r8, r9, r14 \n\t" \ - \ - /* Multiply by 2 */ \ - "mov r9, #0 \n\t" \ - "adds r3, r3, r3 \n\t" \ - "adcs r4, r4, r4 \n\t" \ - "adcs r5, r5, r5 \n\t" \ - "adcs r6, r6, r6 \n\t" \ - "adcs r7, r7, r7 \n\t" \ - "adcs r8, r8, r8 \n\t" \ - "adcs r9, r9, #0 \n\t" \ - \ - /* Add into previous */ \ - "ldr r12, [r0], #4 \n\t" \ - "adds r3, r3, r12 \n\t" \ - "ldr r12, [r0], #4 \n\t" \ - "adcs r4, r4, r12 \n\t" \ - "ldr r12, [r0], #4 \n\t" \ - "adcs r5, r5, r12 \n\t" \ - "ldr r12, [r0], #4 \n\t" \ - "adcs r6, r6, r12 \n\t" \ - "ldr r12, [r0], #4 \n\t" \ - "adcs r7, r7, r12 \n\t" \ - "adcs r8, r8, #0 \n\t" \ - "adcs r9, r9, #0 \n\t" \ - "sub r0, #20 \n\t" \ - \ - /* Perform center multiplication */ \ - "umlal r8, r9, r14, r14 \n\t" \ - "stmia r0!, {r3,r4,r5,r6,r7,r8,r9} \n\t" - -#define FAST_SQUARE_ASM_6 \ - "ldmia r1!, {r8,r9,r10,r11,r12,r14} \n\t" \ - "push {r1, r2} \n\t" \ - \ - "umull r1, r2, r9, r8 \n\t" \ - "mov r3, #0 \n\t" \ - "umaal r2, r3, r10, r8 \n\t" \ - "mov r4, #0 \n\t" \ - "umaal r3, r4, r11, r8 \n\t" \ - "mov r5, #0 \n\t" \ - "umaal r4, r5, r12, r8 \n\t" \ - "mov r6, #0 \n\t" \ - "umaal r5, r6, r14, r8 \n\t" \ - \ - "mov r7, #0 \n\t" \ - "umaal r7, r3, r10, r9 \n\t" \ - "umaal r3, r4, r11, r9 \n\t" \ - "umaal r4, r5, r12, r9 \n\t" \ - "push {r4, r5} \n\t" \ - "adds r1, r1, r1 \n\t" \ - "adcs r2, r2, r2 \n\t" \ - "adcs r7, r7, r7 \n\t" \ - "adcs r3, r3, r3 \n\t" \ - \ - "umull r4, r5, r8, r8 \n\t" \ - /* Store carry in r8 */ \ - "mov r8, #0 \n\t" \ - "adc r8, r8, #0 \n\t" \ - "adds r5, r5, r1 \n\t" \ - "stmia r0!, {r4,r5} \n\t" \ - \ - "umull r4, r5, r9, r9 \n\t" \ - "adcs r4, r4, r2 \n\t" \ - "adcs r5, r5, r7 \n\t" \ - "stmia r0!, {r4,r5} \n\t" \ - \ - "pop {r4, r5} \n\t" \ - "umaal r5, r6, r14, r9 \n\t" \ - /* Store carry in r9 */ \ - "mov r9, #0 \n\t" \ - "adc r9, r9, #0 \n\t" \ - \ - "mov r1, #0 \n\t" \ - "umaal r1, r4, r11, r10 \n\t" \ - "umaal r4, r5, r12, r10 \n\t" \ - "umaal r5, r6, r14, r10 \n\t" \ - \ - "mov r2, #0 \n\t" \ - "umaal r2, r5, r12, r11 \n\t" \ - "umaal r5, r6, r14, r11 \n\t" \ - \ - "mov r7, #0 \n\t" \ - "umaal r7, r6, r14, r12 \n\t" \ - \ - /* Load carry from r8 */ \ - "lsrs r8, #1 \n\t" \ - "adcs r1, r1, r1 \n\t" \ - "adcs r4, r4, r4 \n\t" \ - "adcs r2, r2, r2 \n\t" \ - "adcs r5, r5, r5 \n\t" \ - "adcs r7, r7, r7 \n\t" \ - "adcs r6, r6, r6 \n\t" \ - "adc r8, r8, #0 \n\t" \ - \ - /* Use carry from r9 */ \ - "umaal r3, r9, r10, r10 \n\t" \ - "adds r9, r9, r1 \n\t" \ - "stmia r0!, {r3,r9} \n\t" \ - \ - "umull r9, r10, r11, r11 \n\t" \ - "adcs r9, r9, r4 \n\t" \ - "adcs r10, r10, r2 \n\t" \ - "stmia r0!, {r9,r10} \n\t" \ - \ - "umull r9, r10, r12, r12 \n\t" \ - "adcs r9, r9, r5 \n\t" \ - "adcs r10, r10, r7 \n\t" \ - "stmia r0!, {r9,r10} \n\t" \ - \ - "umull r9, r10, r14, r14 \n\t" \ - "adcs r9, r9, r6 \n\t" \ - "adcs r10, r10, r8 \n\t" \ - "stmia r0!, {r9,r10} \n\t" \ - "pop {r1, r2} \n\t" - -#define FAST_SQUARE_ASM_6_TO_7 \ - "cmp r2, #6 \n\t" \ - "beq 1f \n\t" \ - \ - "sub r0, #24 \n\t" \ - "sub r1, #24 \n\t" \ - \ - /* Do off-center multiplication */ \ - "ldmia r1!, {r5,r6,r7,r8,r9,r10,r14} \n\t" \ - "umull r3, r4, r5, r14 \n\t" \ - "mov r5, #0 \n\t" \ - "umaal r4, r5, r6, r14 \n\t" \ - "mov r6, #0 \n\t" \ - "umaal r5, r6, r7, r14 \n\t" \ - "mov r7, #0 \n\t" \ - "umaal r6, r7, r8, r14 \n\t" \ - "mov r8, #0 \n\t" \ - "umaal r7, r8, r9, r14 \n\t" \ - "mov r9, #0 \n\t" \ - "umaal r8, r9, r10, r14 \n\t" \ - \ - /* Multiply by 2 */ \ - "mov r10, #0 \n\t" \ - "adds r3, r3, r3 \n\t" \ - "adcs r4, r4, r4 \n\t" \ - "adcs r5, r5, r5 \n\t" \ - "adcs r6, r6, r6 \n\t" \ - "adcs r7, r7, r7 \n\t" \ - "adcs r8, r8, r8 \n\t" \ - "adcs r9, r9, r9 \n\t" \ - "adcs r10, r10, #0 \n\t" \ - \ - /* Add into previous */ \ - "ldr r12, [r0], #4 \n\t" \ - "adds r3, r3, r12 \n\t" \ - "ldr r12, [r0], #4 \n\t" \ - "adcs r4, r4, r12 \n\t" \ - "ldr r12, [r0], #4 \n\t" \ - "adcs r5, r5, r12 \n\t" \ - "ldr r12, [r0], #4 \n\t" \ - "adcs r6, r6, r12 \n\t" \ - "ldr r12, [r0], #4 \n\t" \ - "adcs r7, r7, r12 \n\t" \ - "ldr r12, [r0], #4 \n\t" \ - "adcs r8, r8, r12 \n\t" \ - "adcs r9, r9, #0 \n\t" \ - "adcs r10, r10, #0 \n\t" \ - "sub r0, #24 \n\t" \ - \ - /* Perform center multiplication */ \ - "umlal r9, r10, r14, r14 \n\t" \ - "stmia r0!, {r3,r4,r5,r6,r7,r8,r9,r10} \n\t" - -#define FAST_SQUARE_ASM_7 \ - "ldmia r1!, {r9,r10,r11,r12} \n\t" \ - "push {r2} \n\t" \ - \ - "umull r14, r2, r10, r9 \n\t" \ - "mov r3, #0 \n\t" \ - "umaal r2, r3, r11, r9 \n\t" \ - "mov r4, #0 \n\t" \ - "umaal r3, r4, r12, r9 \n\t" \ - \ - "mov r5, #0 \n\t" \ - "umaal r5, r3, r11, r10 \n\t" \ - "adds r14, r14, r14 \n\t" \ - "adcs r2, r2, r2 \n\t" \ - "adcs r5, r5, r5 \n\t" \ - /* Store carry in r7 */ \ - "mov r7, #0 \n\t" \ - "adc r7, r7, #0 \n\t" \ - \ - "umull r6, r8, r9, r9 \n\t" \ - "adds r8, r8, r14 \n\t" \ - "stmia r0!, {r6,r8} \n\t" \ - \ - "umull r6, r8, r10, r10 \n\t" \ - "adcs r6, r6, r2 \n\t" \ - "adcs r8, r8, r5 \n\t" \ - "stmia r0!, {r6,r8} \n\t" \ - /* Store carry in r8 */ \ - "mov r8, #0 \n\t" \ - "adc r8, r8, #0 \n\t" \ - \ - "ldmia r1!, {r2, r6, r14} \n\t" \ - "push {r1} \n\t" \ - "umaal r3, r4, r2, r9 \n\t" \ - "mov r5, #0 \n\t" \ - "umaal r4, r5, r6, r9 \n\t" \ - "mov r1, #0 \n\t" \ - "umaal r5, r1, r14, r9 \n\t" \ - \ - "mov r9, #0 \n\t" \ - "umaal r3, r9, r12, r10 \n\t" \ - "umaal r9, r4, r2, r10 \n\t" \ - "umaal r4, r5, r6, r10 \n\t" \ - "umaal r5, r1, r14, r10 \n\t" \ - \ - "mov r10, #0 \n\t" \ - "umaal r10, r9, r12, r11 \n\t" \ - "umaal r9, r4, r2, r11 \n\t" \ - "umaal r4, r5, r6, r11 \n\t" \ - "umaal r5, r1, r14, r11 \n\t" \ - \ - /* Load carry from r7 */ \ - "lsrs r7, #1 \n\t" \ - "adcs r3, r3, r3 \n\t" \ - "adcs r10, r10, r10 \n\t" \ - "adcs r9, r9, r9 \n\t" \ - /* Store carry back in r7 */ \ - "adc r7, r7, #0 \n\t" \ - \ - /* Use carry from r8 */ \ - "umaal r3, r8, r11, r11 \n\t" \ - "adds r8, r8, r10 \n\t" \ - "stmia r0!, {r3,r8} \n\t" \ - /* Store carry back in r8 */ \ - "mov r8, #0 \n\t" \ - "adc r8, r8, #0 \n\t" \ - \ - "mov r3, #0 \n\t" \ - "umaal r3, r4, r2, r12 \n\t" \ - "umaal r4, r5, r6, r12 \n\t" \ - "umaal r5, r1, r14, r12 \n\t" \ - \ - "mov r10, #0 \n\t" \ - "umaal r10, r5, r6, r2 \n\t" \ - "umaal r5, r1, r14, r2 \n\t" \ - \ - "mov r11, #0 \n\t" \ - "umaal r11, r1, r14, r6 \n\t" \ - \ - /* Load carry from r7 */ \ - "lsrs r7, #1 \n\t" \ - "adcs r3, r3, r3 \n\t" \ - "adcs r4, r4, r4 \n\t" \ - "adcs r10, r10, r10 \n\t" \ - "adcs r5, r5, r5 \n\t" \ - "adcs r11, r11, r11 \n\t" \ - "adcs r1, r1, r1 \n\t" \ - "adc r7, r7, #0 \n\t" \ - \ - /* Use carry from r8 */ \ - "umaal r8, r9, r12, r12 \n\t" \ - "adds r9, r9, r3 \n\t" \ - "stmia r0!, {r8,r9} \n\t" \ - \ - "umull r8, r9, r2, r2 \n\t" \ - "adcs r8, r8, r4 \n\t" \ - "adcs r9, r9, r10 \n\t" \ - "stmia r0!, {r8,r9} \n\t" \ - \ - "umull r8, r9, r6, r6 \n\t" \ - "adcs r8, r8, r5 \n\t" \ - "adcs r9, r9, r11 \n\t" \ - "stmia r0!, {r8,r9} \n\t" \ - \ - "umull r8, r9, r14, r14 \n\t" \ - "adcs r8, r8, r1 \n\t" \ - "adcs r9, r9, r7 \n\t" \ - "stmia r0!, {r8,r9} \n\t" \ - "pop {r1, r2} \n\t" - -#define FAST_SQUARE_ASM_7_TO_8 \ - "cmp r2, #7 \n\t" \ - "beq 1f \n\t" \ - \ - "sub r0, #28 \n\t" \ - "sub r1, #28 \n\t" \ - \ - /* Do off-center multiplication */ \ - "ldmia r1!, {r5,r6,r7,r8,r9,r10,r11,r14} \n\t" \ - "umull r3, r4, r5, r14 \n\t" \ - "mov r5, #0 \n\t" \ - "umaal r4, r5, r6, r14 \n\t" \ - "mov r6, #0 \n\t" \ - "umaal r5, r6, r7, r14 \n\t" \ - "mov r7, #0 \n\t" \ - "umaal r6, r7, r8, r14 \n\t" \ - "mov r8, #0 \n\t" \ - "umaal r7, r8, r9, r14 \n\t" \ - "mov r9, #0 \n\t" \ - "umaal r8, r9, r10, r14 \n\t" \ - "mov r10, #0 \n\t" \ - "umaal r9, r10, r11, r14 \n\t" \ - \ - /* Multiply by 2 */ \ - "mov r11, #0 \n\t" \ - "adds r3, r3, r3 \n\t" \ - "adcs r4, r4, r4 \n\t" \ - "adcs r5, r5, r5 \n\t" \ - "adcs r6, r6, r6 \n\t" \ - "adcs r7, r7, r7 \n\t" \ - "adcs r8, r8, r8 \n\t" \ - "adcs r9, r9, r9 \n\t" \ - "adcs r10, r10, r10 \n\t" \ - "adcs r11, r11, #0 \n\t" \ - \ - /* Add into previous */ \ - "ldr r12, [r0], #4 \n\t" \ - "adds r3, r3, r12 \n\t" \ - "ldr r12, [r0], #4 \n\t" \ - "adcs r4, r4, r12 \n\t" \ - "ldr r12, [r0], #4 \n\t" \ - "adcs r5, r5, r12 \n\t" \ - "ldr r12, [r0], #4 \n\t" \ - "adcs r6, r6, r12 \n\t" \ - "ldr r12, [r0], #4 \n\t" \ - "adcs r7, r7, r12 \n\t" \ - "ldr r12, [r0], #4 \n\t" \ - "adcs r8, r8, r12 \n\t" \ - "ldr r12, [r0], #4 \n\t" \ - "adcs r9, r9, r12 \n\t" \ - "adcs r10, r10, #0 \n\t" \ - "adcs r11, r11, #0 \n\t" \ - "sub r0, #28 \n\t" \ - \ - /* Perform center multiplication */ \ - "umlal r10, r11, r14, r14 \n\t" \ - "stmia r0!, {r3,r4,r5,r6,r7,r8,r9,r10,r11} \n\t" - -#define FAST_SQUARE_ASM_8 \ - "ldmia r1!, {r10,r11,r12,r14} \n\t" \ - "push {r2} \n\t" \ - \ - "umull r2, r3, r11, r10 \n\t" \ - "mov r4, #0 \n\t" \ - "umaal r3, r4, r12, r10 \n\t" \ - "mov r5, #0 \n\t" \ - "umaal r4, r5, r14, r10 \n\t" \ - \ - "mov r6, #0 \n\t" \ - "umaal r6, r4, r12, r11 \n\t" \ - "adds r2, r2, r2 \n\t" \ - "adcs r3, r3, r3 \n\t" \ - "adcs r6, r6, r6 \n\t" \ - /* Store carry in r7 */ \ - "mov r7, #0 \n\t" \ - "adc r7, r7, #0 \n\t" \ - \ - "umull r8, r9, r10, r10 \n\t" \ - "adds r9, r9, r2 \n\t" \ - "stmia r0!, {r8,r9} \n\t" \ - \ - "umull r8, r9, r11, r11 \n\t" \ - "adcs r8, r8, r3 \n\t" \ - "adcs r9, r9, r6 \n\t" \ - "stmia r0!, {r8,r9} \n\t" \ - /* Store carry in r8 */ \ - "mov r8, #0 \n\t" \ - "adc r8, r8, #0 \n\t" \ - \ - "ldmia r1!, {r2, r3} \n\t" \ - "push {r1} \n\t" \ - "umaal r4, r5, r2, r10 \n\t" \ - "mov r6, #0 \n\t" \ - "umaal r5, r6, r3, r10 \n\t" \ - \ - "mov r9, #0 \n\t" \ - "umaal r9, r4, r14, r11 \n\t" \ - "umaal r4, r5, r2, r11 \n\t" \ - \ - "mov r1, #0 \n\t" \ - "umaal r1, r4, r14, r12 \n\t" \ - \ - /* Load carry from r7 */ \ - "lsrs r7, #1 \n\t" \ - "adcs r9, r9, r9 \n\t" \ - "adcs r1, r1, r1 \n\t" \ - /* Store carry back in r7 */ \ - "adc r7, r7, #0 \n\t" \ - \ - /* Use carry from r8 */ \ - "umaal r8, r9, r12, r12 \n\t" \ - "adds r9, r9, r1 \n\t" \ - "stmia r0!, {r8,r9} \n\t" \ - /* Store carry back in r8 */ \ - "mov r8, #0 \n\t" \ - "adc r8, r8, #0 \n\t" \ - \ - "pop {r1} \n\t" \ - /* TODO could fix up r1 value on stack here */ \ - /* and leave the value on the stack (rather */ \ - /* than popping) if supporting curves > 256 bits */ \ - "ldr r9, [r1], #4 \n\t" \ - "ldr r1, [r1] \n\t" \ - \ - "push {r7} \n\t" \ - "umaal r5, r6, r9, r10 \n\t" \ - "mov r7, #0 \n\t" \ - "umaal r6, r7, r1, r10 \n\t" \ - /* Carry now stored in r10 */ \ - "pop {r10} \n\t" \ - \ - "umaal r4, r5, r3, r11 \n\t" \ - "umaal r5, r6, r9, r11 \n\t" \ - "umaal r6, r7, r1, r11 \n\t" \ - \ - "mov r11, #0 \n\t" \ - "umaal r11, r4, r2, r12 \n\t" \ - "umaal r4, r5, r3, r12 \n\t" \ - "umaal r5, r6, r9, r12 \n\t" \ - "umaal r6, r7, r1, r12 \n\t" \ - \ - "mov r12, #0 \n\t" \ - "umaal r12, r4, r2, r14 \n\t" \ - "umaal r4, r5, r3, r14 \n\t" \ - "umaal r5, r6, r9, r14 \n\t" \ - "umaal r6, r7, r1, r14 \n\t" \ - \ - /* Load carry from r10 */ \ - "lsrs r10, #1 \n\t" \ - "adcs r11, r11, r11 \n\t" \ - "adcs r12, r12, r12 \n\t" \ - "adc r10, r10, #0 \n\t" \ - \ - /* Use carry from r8 */ \ - "umaal r8, r11, r14, r14 \n\t" \ - "adds r11, r11, r12 \n\t" \ - "stmia r0!, {r8,r11} \n\t" \ - /* Store carry back in r8 */ \ - "mov r8, #0 \n\t" \ - "adc r8, r8, #0 \n\t" \ - \ - "mov r11, #0 \n\t" \ - "umaal r11, r5, r3, r2 \n\t" \ - "umaal r5, r6, r9, r2 \n\t" \ - "umaal r6, r7, r1, r2 \n\t" \ - \ - "mov r12, #0 \n\t" \ - "umaal r12, r6, r9, r3 \n\t" \ - "umaal r6, r7, r1, r3 \n\t" \ - \ - "mov r14, #0 \n\t" \ - "umaal r14, r7, r1, r9 \n\t" \ - \ - /* Load carry from r10 */ \ - "lsrs r10, #1 \n\t" \ - "adcs r4, r4, r4 \n\t" \ - "adcs r11, r11, r11 \n\t" \ - "adcs r5, r5, r5 \n\t" \ - "adcs r12, r12, r12 \n\t" \ - "adcs r6, r6, r6 \n\t" \ - "adcs r14, r14, r14 \n\t" \ - "adcs r7, r7, r7 \n\t" \ - "adc r10, r10, #0 \n\t" \ - \ - /* Use carry from r8 */ \ - "umaal r4, r8, r2, r2 \n\t" \ - "adds r8, r8, r11 \n\t" \ - "stmia r0!, {r4,r8} \n\t" \ - \ - "umull r4, r8, r3, r3 \n\t" \ - "adcs r4, r4, r5 \n\t" \ - "adcs r8, r8, r12 \n\t" \ - "stmia r0!, {r4,r8} \n\t" \ - \ - "umull r4, r8, r9, r9 \n\t" \ - "adcs r4, r4, r6 \n\t" \ - "adcs r8, r8, r14 \n\t" \ - "stmia r0!, {r4,r8} \n\t" \ - \ - "umull r4, r8, r1, r1 \n\t" \ - "adcs r4, r4, r7 \n\t" \ - "adcs r8, r8, r10 \n\t" \ - "stmia r0!, {r4,r8} \n\t" \ - /* TODO pop {r1, r2} if supporting curves > 256 bits */ \ - "pop {r2} \n\t" - -#endif /* _UECC_ASM_ARM_MULT_SQUARE_H_ */ diff --git a/lib/micro-ecc/curve-specific.inc b/lib/micro-ecc/curve-specific.inc deleted file mode 100644 index f5d3da842c8..00000000000 --- a/lib/micro-ecc/curve-specific.inc +++ /dev/null @@ -1,1249 +0,0 @@ -/* Copyright 2015, Kenneth MacKay. Licensed under the BSD 2-clause license. */ - -#ifndef _UECC_CURVE_SPECIFIC_H_ -#define _UECC_CURVE_SPECIFIC_H_ - -#define num_bytes_secp160r1 20 -#define num_bytes_secp192r1 24 -#define num_bytes_secp224r1 28 -#define num_bytes_secp256r1 32 -#define num_bytes_secp256k1 32 - -#if (uECC_WORD_SIZE == 1) - -#define num_words_secp160r1 20 -#define num_words_secp192r1 24 -#define num_words_secp224r1 28 -#define num_words_secp256r1 32 -#define num_words_secp256k1 32 - -#define BYTES_TO_WORDS_8(a, b, c, d, e, f, g, h) \ - 0x##a, 0x##b, 0x##c, 0x##d, 0x##e, 0x##f, 0x##g, 0x##h -#define BYTES_TO_WORDS_4(a, b, c, d) 0x##a, 0x##b, 0x##c, 0x##d - -#elif (uECC_WORD_SIZE == 4) - -#define num_words_secp160r1 5 -#define num_words_secp192r1 6 -#define num_words_secp224r1 7 -#define num_words_secp256r1 8 -#define num_words_secp256k1 8 - -#define BYTES_TO_WORDS_8(a, b, c, d, e, f, g, h) 0x##d##c##b##a, 0x##h##g##f##e -#define BYTES_TO_WORDS_4(a, b, c, d) 0x##d##c##b##a - -#elif (uECC_WORD_SIZE == 8) - -#define num_words_secp160r1 3 -#define num_words_secp192r1 3 -#define num_words_secp224r1 4 -#define num_words_secp256r1 4 -#define num_words_secp256k1 4 - -#define BYTES_TO_WORDS_8(a, b, c, d, e, f, g, h) 0x##h##g##f##e##d##c##b##a##ull -#define BYTES_TO_WORDS_4(a, b, c, d) 0x##d##c##b##a##ull - -#endif /* uECC_WORD_SIZE */ - -#if uECC_SUPPORTS_secp160r1 || uECC_SUPPORTS_secp192r1 || \ - uECC_SUPPORTS_secp224r1 || uECC_SUPPORTS_secp256r1 -static void double_jacobian_default(uECC_word_t * X1, - uECC_word_t * Y1, - uECC_word_t * Z1, - uECC_Curve curve) { - /* t1 = X, t2 = Y, t3 = Z */ - uECC_word_t t4[uECC_MAX_WORDS]; - uECC_word_t t5[uECC_MAX_WORDS]; - wordcount_t num_words = curve->num_words; - - if (uECC_vli_isZero(Z1, num_words)) { - return; - } - - uECC_vli_modSquare_fast(t4, Y1, curve); /* t4 = y1^2 */ - uECC_vli_modMult_fast(t5, X1, t4, curve); /* t5 = x1*y1^2 = A */ - uECC_vli_modSquare_fast(t4, t4, curve); /* t4 = y1^4 */ - uECC_vli_modMult_fast(Y1, Y1, Z1, curve); /* t2 = y1*z1 = z3 */ - uECC_vli_modSquare_fast(Z1, Z1, curve); /* t3 = z1^2 */ - - uECC_vli_modAdd(X1, X1, Z1, curve->p, num_words); /* t1 = x1 + z1^2 */ - uECC_vli_modAdd(Z1, Z1, Z1, curve->p, num_words); /* t3 = 2*z1^2 */ - uECC_vli_modSub(Z1, X1, Z1, curve->p, num_words); /* t3 = x1 - z1^2 */ - uECC_vli_modMult_fast(X1, X1, Z1, curve); /* t1 = x1^2 - z1^4 */ - - uECC_vli_modAdd(Z1, X1, X1, curve->p, num_words); /* t3 = 2*(x1^2 - z1^4) */ - uECC_vli_modAdd(X1, X1, Z1, curve->p, num_words); /* t1 = 3*(x1^2 - z1^4) */ - if (uECC_vli_testBit(X1, 0)) { - uECC_word_t l_carry = uECC_vli_add(X1, X1, curve->p, num_words); - uECC_vli_rshift1(X1, num_words); - X1[num_words - 1] |= l_carry << (uECC_WORD_BITS - 1); - } else { - uECC_vli_rshift1(X1, num_words); - } - /* t1 = 3/2*(x1^2 - z1^4) = B */ - - uECC_vli_modSquare_fast(Z1, X1, curve); /* t3 = B^2 */ - uECC_vli_modSub(Z1, Z1, t5, curve->p, num_words); /* t3 = B^2 - A */ - uECC_vli_modSub(Z1, Z1, t5, curve->p, num_words); /* t3 = B^2 - 2A = x3 */ - uECC_vli_modSub(t5, t5, Z1, curve->p, num_words); /* t5 = A - x3 */ - uECC_vli_modMult_fast(X1, X1, t5, curve); /* t1 = B * (A - x3) */ - uECC_vli_modSub(t4, X1, t4, curve->p, num_words); /* t4 = B * (A - x3) - y1^4 = y3 */ - - uECC_vli_set(X1, Z1, num_words); - uECC_vli_set(Z1, Y1, num_words); - uECC_vli_set(Y1, t4, num_words); -} - -/* Computes result = x^3 + ax + b. result must not overlap x. */ -static void x_side_default(uECC_word_t *result, const uECC_word_t *x, uECC_Curve curve) { - uECC_word_t _3[uECC_MAX_WORDS] = {3}; /* -a = 3 */ - wordcount_t num_words = curve->num_words; - - uECC_vli_modSquare_fast(result, x, curve); /* r = x^2 */ - uECC_vli_modSub(result, result, _3, curve->p, num_words); /* r = x^2 - 3 */ - uECC_vli_modMult_fast(result, result, x, curve); /* r = x^3 - 3x */ - uECC_vli_modAdd(result, result, curve->b, curve->p, num_words); /* r = x^3 - 3x + b */ -} -#endif /* uECC_SUPPORTS_secp... */ - -#if uECC_SUPPORT_COMPRESSED_POINT -#if uECC_SUPPORTS_secp160r1 || uECC_SUPPORTS_secp192r1 || \ - uECC_SUPPORTS_secp256r1 || uECC_SUPPORTS_secp256k1 -/* Compute a = sqrt(a) (mod curve_p). */ -static void mod_sqrt_default(uECC_word_t *a, uECC_Curve curve) { - bitcount_t i; - uECC_word_t p1[uECC_MAX_WORDS] = {1}; - uECC_word_t l_result[uECC_MAX_WORDS] = {1}; - wordcount_t num_words = curve->num_words; - - /* When curve->p == 3 (mod 4), we can compute - sqrt(a) = a^((curve->p + 1) / 4) (mod curve->p). */ - uECC_vli_add(p1, curve->p, p1, num_words); /* p1 = curve_p + 1 */ - for (i = uECC_vli_numBits(p1, num_words) - 1; i > 1; --i) { - uECC_vli_modSquare_fast(l_result, l_result, curve); - if (uECC_vli_testBit(p1, i)) { - uECC_vli_modMult_fast(l_result, l_result, a, curve); - } - } - uECC_vli_set(a, l_result, num_words); -} -#endif /* uECC_SUPPORTS_secp... */ -#endif /* uECC_SUPPORT_COMPRESSED_POINT */ - -#if uECC_SUPPORTS_secp160r1 - -#if (uECC_OPTIMIZATION_LEVEL > 0) -static void vli_mmod_fast_secp160r1(uECC_word_t *result, uECC_word_t *product); -#endif - -static const struct uECC_Curve_t curve_secp160r1 = { - num_words_secp160r1, - num_bytes_secp160r1, - 161, /* num_n_bits */ - { BYTES_TO_WORDS_8(FF, FF, FF, 7F, FF, FF, FF, FF), - BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF), - BYTES_TO_WORDS_4(FF, FF, FF, FF) }, - { BYTES_TO_WORDS_8(57, 22, 75, CA, D3, AE, 27, F9), - BYTES_TO_WORDS_8(C8, F4, 01, 00, 00, 00, 00, 00), - BYTES_TO_WORDS_8(00, 00, 00, 00, 01, 00, 00, 00) }, - { BYTES_TO_WORDS_8(82, FC, CB, 13, B9, 8B, C3, 68), - BYTES_TO_WORDS_8(89, 69, 64, 46, 28, 73, F5, 8E), - BYTES_TO_WORDS_4(68, B5, 96, 4A), - - BYTES_TO_WORDS_8(32, FB, C5, 7A, 37, 51, 23, 04), - BYTES_TO_WORDS_8(12, C9, DC, 59, 7D, 94, 68, 31), - BYTES_TO_WORDS_4(55, 28, A6, 23) }, - { BYTES_TO_WORDS_8(45, FA, 65, C5, AD, D4, D4, 81), - BYTES_TO_WORDS_8(9F, F8, AC, 65, 8B, 7A, BD, 54), - BYTES_TO_WORDS_4(FC, BE, 97, 1C) }, - &double_jacobian_default, -#if uECC_SUPPORT_COMPRESSED_POINT - &mod_sqrt_default, -#endif - &x_side_default, -#if (uECC_OPTIMIZATION_LEVEL > 0) - &vli_mmod_fast_secp160r1 -#endif -}; - -uECC_Curve uECC_secp160r1(void) { return &curve_secp160r1; } - -#if (uECC_OPTIMIZATION_LEVEL > 0 && !asm_mmod_fast_secp160r1) -/* Computes result = product % curve_p - see http://www.isys.uni-klu.ac.at/PDF/2001-0126-MT.pdf page 354 - - Note that this only works if log2(omega) < log2(p) / 2 */ -static void omega_mult_secp160r1(uECC_word_t *result, const uECC_word_t *right); -#if uECC_WORD_SIZE == 8 -static void vli_mmod_fast_secp160r1(uECC_word_t *result, uECC_word_t *product) { - uECC_word_t tmp[2 * num_words_secp160r1]; - uECC_word_t copy; - - uECC_vli_clear(tmp, num_words_secp160r1); - uECC_vli_clear(tmp + num_words_secp160r1, num_words_secp160r1); - - omega_mult_secp160r1(tmp, product + num_words_secp160r1 - 1); /* (Rq, q) = q * c */ - - product[num_words_secp160r1 - 1] &= 0xffffffff; - copy = tmp[num_words_secp160r1 - 1]; - tmp[num_words_secp160r1 - 1] &= 0xffffffff; - uECC_vli_add(result, product, tmp, num_words_secp160r1); /* (C, r) = r + q */ - uECC_vli_clear(product, num_words_secp160r1); - tmp[num_words_secp160r1 - 1] = copy; - omega_mult_secp160r1(product, tmp + num_words_secp160r1 - 1); /* Rq*c */ - uECC_vli_add(result, result, product, num_words_secp160r1); /* (C1, r) = r + Rq*c */ - - while (uECC_vli_cmp_unsafe(result, curve_secp160r1.p, num_words_secp160r1) > 0) { - uECC_vli_sub(result, result, curve_secp160r1.p, num_words_secp160r1); - } -} - -static void omega_mult_secp160r1(uint64_t *result, const uint64_t *right) { - uint32_t carry; - unsigned i; - - /* Multiply by (2^31 + 1). */ - carry = 0; - for (i = 0; i < num_words_secp160r1; ++i) { - uint64_t tmp = (right[i] >> 32) | (right[i + 1] << 32); - result[i] = (tmp << 31) + tmp + carry; - carry = (tmp >> 33) + (result[i] < tmp || (carry && result[i] == tmp)); - } - result[i] = carry; -} -#else -static void vli_mmod_fast_secp160r1(uECC_word_t *result, uECC_word_t *product) { - uECC_word_t tmp[2 * num_words_secp160r1]; - uECC_word_t carry; - - uECC_vli_clear(tmp, num_words_secp160r1); - uECC_vli_clear(tmp + num_words_secp160r1, num_words_secp160r1); - - omega_mult_secp160r1(tmp, product + num_words_secp160r1); /* (Rq, q) = q * c */ - - carry = uECC_vli_add(result, product, tmp, num_words_secp160r1); /* (C, r) = r + q */ - uECC_vli_clear(product, num_words_secp160r1); - omega_mult_secp160r1(product, tmp + num_words_secp160r1); /* Rq*c */ - carry += uECC_vli_add(result, result, product, num_words_secp160r1); /* (C1, r) = r + Rq*c */ - - while (carry > 0) { - --carry; - uECC_vli_sub(result, result, curve_secp160r1.p, num_words_secp160r1); - } - if (uECC_vli_cmp_unsafe(result, curve_secp160r1.p, num_words_secp160r1) > 0) { - uECC_vli_sub(result, result, curve_secp160r1.p, num_words_secp160r1); - } -} -#endif - -#if uECC_WORD_SIZE == 1 -static void omega_mult_secp160r1(uint8_t *result, const uint8_t *right) { - uint8_t carry; - uint8_t i; - - /* Multiply by (2^31 + 1). */ - uECC_vli_set(result + 4, right, num_words_secp160r1); /* 2^32 */ - uECC_vli_rshift1(result + 4, num_words_secp160r1); /* 2^31 */ - result[3] = right[0] << 7; /* get last bit from shift */ - - carry = uECC_vli_add(result, result, right, num_words_secp160r1); /* 2^31 + 1 */ - for (i = num_words_secp160r1; carry; ++i) { - uint16_t sum = (uint16_t)result[i] + carry; - result[i] = (uint8_t)sum; - carry = sum >> 8; - } -} -#elif uECC_WORD_SIZE == 4 -static void omega_mult_secp160r1(uint32_t *result, const uint32_t *right) { - uint32_t carry; - unsigned i; - - /* Multiply by (2^31 + 1). */ - uECC_vli_set(result + 1, right, num_words_secp160r1); /* 2^32 */ - uECC_vli_rshift1(result + 1, num_words_secp160r1); /* 2^31 */ - result[0] = right[0] << 31; /* get last bit from shift */ - - carry = uECC_vli_add(result, result, right, num_words_secp160r1); /* 2^31 + 1 */ - for (i = num_words_secp160r1; carry; ++i) { - uint64_t sum = (uint64_t)result[i] + carry; - result[i] = (uint32_t)sum; - carry = sum >> 32; - } -} -#endif /* uECC_WORD_SIZE */ -#endif /* (uECC_OPTIMIZATION_LEVEL > 0 && !asm_mmod_fast_secp160r1) */ - -#endif /* uECC_SUPPORTS_secp160r1 */ - -#if uECC_SUPPORTS_secp192r1 - -#if (uECC_OPTIMIZATION_LEVEL > 0) -static void vli_mmod_fast_secp192r1(uECC_word_t *result, uECC_word_t *product); -#endif - -static const struct uECC_Curve_t curve_secp192r1 = { - num_words_secp192r1, - num_bytes_secp192r1, - 192, /* num_n_bits */ - { BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF), - BYTES_TO_WORDS_8(FE, FF, FF, FF, FF, FF, FF, FF), - BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF) }, - { BYTES_TO_WORDS_8(31, 28, D2, B4, B1, C9, 6B, 14), - BYTES_TO_WORDS_8(36, F8, DE, 99, FF, FF, FF, FF), - BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF) }, - { BYTES_TO_WORDS_8(12, 10, FF, 82, FD, 0A, FF, F4), - BYTES_TO_WORDS_8(00, 88, A1, 43, EB, 20, BF, 7C), - BYTES_TO_WORDS_8(F6, 90, 30, B0, 0E, A8, 8D, 18), - - BYTES_TO_WORDS_8(11, 48, 79, 1E, A1, 77, F9, 73), - BYTES_TO_WORDS_8(D5, CD, 24, 6B, ED, 11, 10, 63), - BYTES_TO_WORDS_8(78, DA, C8, FF, 95, 2B, 19, 07) }, - { BYTES_TO_WORDS_8(B1, B9, 46, C1, EC, DE, B8, FE), - BYTES_TO_WORDS_8(49, 30, 24, 72, AB, E9, A7, 0F), - BYTES_TO_WORDS_8(E7, 80, 9C, E5, 19, 05, 21, 64) }, - &double_jacobian_default, -#if uECC_SUPPORT_COMPRESSED_POINT - &mod_sqrt_default, -#endif - &x_side_default, -#if (uECC_OPTIMIZATION_LEVEL > 0) - &vli_mmod_fast_secp192r1 -#endif -}; - -uECC_Curve uECC_secp192r1(void) { return &curve_secp192r1; } - -#if (uECC_OPTIMIZATION_LEVEL > 0) -/* Computes result = product % curve_p. - See algorithm 5 and 6 from http://www.isys.uni-klu.ac.at/PDF/2001-0126-MT.pdf */ -#if uECC_WORD_SIZE == 1 -static void vli_mmod_fast_secp192r1(uint8_t *result, uint8_t *product) { - uint8_t tmp[num_words_secp192r1]; - uint8_t carry; - - uECC_vli_set(result, product, num_words_secp192r1); - - uECC_vli_set(tmp, &product[24], num_words_secp192r1); - carry = uECC_vli_add(result, result, tmp, num_words_secp192r1); - - tmp[0] = tmp[1] = tmp[2] = tmp[3] = tmp[4] = tmp[5] = tmp[6] = tmp[7] = 0; - tmp[8] = product[24]; tmp[9] = product[25]; tmp[10] = product[26]; tmp[11] = product[27]; - tmp[12] = product[28]; tmp[13] = product[29]; tmp[14] = product[30]; tmp[15] = product[31]; - tmp[16] = product[32]; tmp[17] = product[33]; tmp[18] = product[34]; tmp[19] = product[35]; - tmp[20] = product[36]; tmp[21] = product[37]; tmp[22] = product[38]; tmp[23] = product[39]; - carry += uECC_vli_add(result, result, tmp, num_words_secp192r1); - - tmp[0] = tmp[8] = product[40]; - tmp[1] = tmp[9] = product[41]; - tmp[2] = tmp[10] = product[42]; - tmp[3] = tmp[11] = product[43]; - tmp[4] = tmp[12] = product[44]; - tmp[5] = tmp[13] = product[45]; - tmp[6] = tmp[14] = product[46]; - tmp[7] = tmp[15] = product[47]; - tmp[16] = tmp[17] = tmp[18] = tmp[19] = tmp[20] = tmp[21] = tmp[22] = tmp[23] = 0; - carry += uECC_vli_add(result, result, tmp, num_words_secp192r1); - - while (carry || uECC_vli_cmp_unsafe(curve_secp192r1.p, result, num_words_secp192r1) != 1) { - carry -= uECC_vli_sub(result, result, curve_secp192r1.p, num_words_secp192r1); - } -} -#elif uECC_WORD_SIZE == 4 -static void vli_mmod_fast_secp192r1(uint32_t *result, uint32_t *product) { - uint32_t tmp[num_words_secp192r1]; - int carry; - - uECC_vli_set(result, product, num_words_secp192r1); - - uECC_vli_set(tmp, &product[6], num_words_secp192r1); - carry = uECC_vli_add(result, result, tmp, num_words_secp192r1); - - tmp[0] = tmp[1] = 0; - tmp[2] = product[6]; - tmp[3] = product[7]; - tmp[4] = product[8]; - tmp[5] = product[9]; - carry += uECC_vli_add(result, result, tmp, num_words_secp192r1); - - tmp[0] = tmp[2] = product[10]; - tmp[1] = tmp[3] = product[11]; - tmp[4] = tmp[5] = 0; - carry += uECC_vli_add(result, result, tmp, num_words_secp192r1); - - while (carry || uECC_vli_cmp_unsafe(curve_secp192r1.p, result, num_words_secp192r1) != 1) { - carry -= uECC_vli_sub(result, result, curve_secp192r1.p, num_words_secp192r1); - } -} -#else -static void vli_mmod_fast_secp192r1(uint64_t *result, uint64_t *product) { - uint64_t tmp[num_words_secp192r1]; - int carry; - - uECC_vli_set(result, product, num_words_secp192r1); - - uECC_vli_set(tmp, &product[3], num_words_secp192r1); - carry = (int)uECC_vli_add(result, result, tmp, num_words_secp192r1); - - tmp[0] = 0; - tmp[1] = product[3]; - tmp[2] = product[4]; - carry += uECC_vli_add(result, result, tmp, num_words_secp192r1); - - tmp[0] = tmp[1] = product[5]; - tmp[2] = 0; - carry += uECC_vli_add(result, result, tmp, num_words_secp192r1); - - while (carry || uECC_vli_cmp_unsafe(curve_secp192r1.p, result, num_words_secp192r1) != 1) { - carry -= uECC_vli_sub(result, result, curve_secp192r1.p, num_words_secp192r1); - } -} -#endif /* uECC_WORD_SIZE */ -#endif /* (uECC_OPTIMIZATION_LEVEL > 0) */ - -#endif /* uECC_SUPPORTS_secp192r1 */ - -#if uECC_SUPPORTS_secp224r1 - -#if uECC_SUPPORT_COMPRESSED_POINT -static void mod_sqrt_secp224r1(uECC_word_t *a, uECC_Curve curve); -#endif -#if (uECC_OPTIMIZATION_LEVEL > 0) -static void vli_mmod_fast_secp224r1(uECC_word_t *result, uECC_word_t *product); -#endif - -static const struct uECC_Curve_t curve_secp224r1 = { - num_words_secp224r1, - num_bytes_secp224r1, - 224, /* num_n_bits */ - { BYTES_TO_WORDS_8(01, 00, 00, 00, 00, 00, 00, 00), - BYTES_TO_WORDS_8(00, 00, 00, 00, FF, FF, FF, FF), - BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF), - BYTES_TO_WORDS_4(FF, FF, FF, FF) }, - { BYTES_TO_WORDS_8(3D, 2A, 5C, 5C, 45, 29, DD, 13), - BYTES_TO_WORDS_8(3E, F0, B8, E0, A2, 16, FF, FF), - BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF), - BYTES_TO_WORDS_4(FF, FF, FF, FF) }, - { BYTES_TO_WORDS_8(21, 1D, 5C, 11, D6, 80, 32, 34), - BYTES_TO_WORDS_8(22, 11, C2, 56, D3, C1, 03, 4A), - BYTES_TO_WORDS_8(B9, 90, 13, 32, 7F, BF, B4, 6B), - BYTES_TO_WORDS_4(BD, 0C, 0E, B7), - - BYTES_TO_WORDS_8(34, 7E, 00, 85, 99, 81, D5, 44), - BYTES_TO_WORDS_8(64, 47, 07, 5A, A0, 75, 43, CD), - BYTES_TO_WORDS_8(E6, DF, 22, 4C, FB, 23, F7, B5), - BYTES_TO_WORDS_4(88, 63, 37, BD) }, - { BYTES_TO_WORDS_8(B4, FF, 55, 23, 43, 39, 0B, 27), - BYTES_TO_WORDS_8(BA, D8, BF, D7, B7, B0, 44, 50), - BYTES_TO_WORDS_8(56, 32, 41, F5, AB, B3, 04, 0C), - BYTES_TO_WORDS_4(85, 0A, 05, B4) }, - &double_jacobian_default, -#if uECC_SUPPORT_COMPRESSED_POINT - &mod_sqrt_secp224r1, -#endif - &x_side_default, -#if (uECC_OPTIMIZATION_LEVEL > 0) - &vli_mmod_fast_secp224r1 -#endif -}; - -uECC_Curve uECC_secp224r1(void) { return &curve_secp224r1; } - - -#if uECC_SUPPORT_COMPRESSED_POINT -/* Routine 3.2.4 RS; from http://www.nsa.gov/ia/_files/nist-routines.pdf */ -static void mod_sqrt_secp224r1_rs(uECC_word_t *d1, - uECC_word_t *e1, - uECC_word_t *f1, - const uECC_word_t *d0, - const uECC_word_t *e0, - const uECC_word_t *f0) { - uECC_word_t t[num_words_secp224r1]; - - uECC_vli_modSquare_fast(t, d0, &curve_secp224r1); /* t <-- d0 ^ 2 */ - uECC_vli_modMult_fast(e1, d0, e0, &curve_secp224r1); /* e1 <-- d0 * e0 */ - uECC_vli_modAdd(d1, t, f0, curve_secp224r1.p, num_words_secp224r1); /* d1 <-- t + f0 */ - uECC_vli_modAdd(e1, e1, e1, curve_secp224r1.p, num_words_secp224r1); /* e1 <-- e1 + e1 */ - uECC_vli_modMult_fast(f1, t, f0, &curve_secp224r1); /* f1 <-- t * f0 */ - uECC_vli_modAdd(f1, f1, f1, curve_secp224r1.p, num_words_secp224r1); /* f1 <-- f1 + f1 */ - uECC_vli_modAdd(f1, f1, f1, curve_secp224r1.p, num_words_secp224r1); /* f1 <-- f1 + f1 */ -} - -/* Routine 3.2.5 RSS; from http://www.nsa.gov/ia/_files/nist-routines.pdf */ -static void mod_sqrt_secp224r1_rss(uECC_word_t *d1, - uECC_word_t *e1, - uECC_word_t *f1, - const uECC_word_t *d0, - const uECC_word_t *e0, - const uECC_word_t *f0, - const bitcount_t j) { - bitcount_t i; - - uECC_vli_set(d1, d0, num_words_secp224r1); /* d1 <-- d0 */ - uECC_vli_set(e1, e0, num_words_secp224r1); /* e1 <-- e0 */ - uECC_vli_set(f1, f0, num_words_secp224r1); /* f1 <-- f0 */ - for (i = 1; i <= j; i++) { - mod_sqrt_secp224r1_rs(d1, e1, f1, d1, e1, f1); /* RS (d1,e1,f1,d1,e1,f1) */ - } -} - -/* Routine 3.2.6 RM; from http://www.nsa.gov/ia/_files/nist-routines.pdf */ -static void mod_sqrt_secp224r1_rm(uECC_word_t *d2, - uECC_word_t *e2, - uECC_word_t *f2, - const uECC_word_t *c, - const uECC_word_t *d0, - const uECC_word_t *e0, - const uECC_word_t *d1, - const uECC_word_t *e1) { - uECC_word_t t1[num_words_secp224r1]; - uECC_word_t t2[num_words_secp224r1]; - - uECC_vli_modMult_fast(t1, e0, e1, &curve_secp224r1); /* t1 <-- e0 * e1 */ - uECC_vli_modMult_fast(t1, t1, c, &curve_secp224r1); /* t1 <-- t1 * c */ - /* t1 <-- p - t1 */ - uECC_vli_modSub(t1, curve_secp224r1.p, t1, curve_secp224r1.p, num_words_secp224r1); - uECC_vli_modMult_fast(t2, d0, d1, &curve_secp224r1); /* t2 <-- d0 * d1 */ - uECC_vli_modAdd(t2, t2, t1, curve_secp224r1.p, num_words_secp224r1); /* t2 <-- t2 + t1 */ - uECC_vli_modMult_fast(t1, d0, e1, &curve_secp224r1); /* t1 <-- d0 * e1 */ - uECC_vli_modMult_fast(e2, d1, e0, &curve_secp224r1); /* e2 <-- d1 * e0 */ - uECC_vli_modAdd(e2, e2, t1, curve_secp224r1.p, num_words_secp224r1); /* e2 <-- e2 + t1 */ - uECC_vli_modSquare_fast(f2, e2, &curve_secp224r1); /* f2 <-- e2^2 */ - uECC_vli_modMult_fast(f2, f2, c, &curve_secp224r1); /* f2 <-- f2 * c */ - /* f2 <-- p - f2 */ - uECC_vli_modSub(f2, curve_secp224r1.p, f2, curve_secp224r1.p, num_words_secp224r1); - uECC_vli_set(d2, t2, num_words_secp224r1); /* d2 <-- t2 */ -} - -/* Routine 3.2.7 RP; from http://www.nsa.gov/ia/_files/nist-routines.pdf */ -static void mod_sqrt_secp224r1_rp(uECC_word_t *d1, - uECC_word_t *e1, - uECC_word_t *f1, - const uECC_word_t *c, - const uECC_word_t *r) { - wordcount_t i; - wordcount_t pow2i = 1; - uECC_word_t d0[num_words_secp224r1]; - uECC_word_t e0[num_words_secp224r1] = {1}; /* e0 <-- 1 */ - uECC_word_t f0[num_words_secp224r1]; - - uECC_vli_set(d0, r, num_words_secp224r1); /* d0 <-- r */ - /* f0 <-- p - c */ - uECC_vli_modSub(f0, curve_secp224r1.p, c, curve_secp224r1.p, num_words_secp224r1); - for (i = 0; i <= 6; i++) { - mod_sqrt_secp224r1_rss(d1, e1, f1, d0, e0, f0, pow2i); /* RSS (d1,e1,f1,d0,e0,f0,2^i) */ - mod_sqrt_secp224r1_rm(d1, e1, f1, c, d1, e1, d0, e0); /* RM (d1,e1,f1,c,d1,e1,d0,e0) */ - uECC_vli_set(d0, d1, num_words_secp224r1); /* d0 <-- d1 */ - uECC_vli_set(e0, e1, num_words_secp224r1); /* e0 <-- e1 */ - uECC_vli_set(f0, f1, num_words_secp224r1); /* f0 <-- f1 */ - pow2i *= 2; - } -} - -/* Compute a = sqrt(a) (mod curve_p). */ -/* Routine 3.2.8 mp_mod_sqrt_224; from http://www.nsa.gov/ia/_files/nist-routines.pdf */ -static void mod_sqrt_secp224r1(uECC_word_t *a, uECC_Curve curve) { - (void)curve; - bitcount_t i; - uECC_word_t e1[num_words_secp224r1]; - uECC_word_t f1[num_words_secp224r1]; - uECC_word_t d0[num_words_secp224r1]; - uECC_word_t e0[num_words_secp224r1]; - uECC_word_t f0[num_words_secp224r1]; - uECC_word_t d1[num_words_secp224r1]; - - /* s = a; using constant instead of random value */ - mod_sqrt_secp224r1_rp(d0, e0, f0, a, a); /* RP (d0, e0, f0, c, s) */ - mod_sqrt_secp224r1_rs(d1, e1, f1, d0, e0, f0); /* RS (d1, e1, f1, d0, e0, f0) */ - for (i = 1; i <= 95; i++) { - uECC_vli_set(d0, d1, num_words_secp224r1); /* d0 <-- d1 */ - uECC_vli_set(e0, e1, num_words_secp224r1); /* e0 <-- e1 */ - uECC_vli_set(f0, f1, num_words_secp224r1); /* f0 <-- f1 */ - mod_sqrt_secp224r1_rs(d1, e1, f1, d0, e0, f0); /* RS (d1, e1, f1, d0, e0, f0) */ - if (uECC_vli_isZero(d1, num_words_secp224r1)) { /* if d1 == 0 */ - break; - } - } - uECC_vli_modInv(f1, e0, curve_secp224r1.p, num_words_secp224r1); /* f1 <-- 1 / e0 */ - uECC_vli_modMult_fast(a, d0, f1, &curve_secp224r1); /* a <-- d0 / e0 */ -} -#endif /* uECC_SUPPORT_COMPRESSED_POINT */ - -#if (uECC_OPTIMIZATION_LEVEL > 0) -/* Computes result = product % curve_p - from http://www.nsa.gov/ia/_files/nist-routines.pdf */ -#if uECC_WORD_SIZE == 1 -static void vli_mmod_fast_secp224r1(uint8_t *result, uint8_t *product) { - uint8_t tmp[num_words_secp224r1]; - int8_t carry; - - /* t */ - uECC_vli_set(result, product, num_words_secp224r1); - - /* s1 */ - tmp[0] = tmp[1] = tmp[2] = tmp[3] = 0; - tmp[4] = tmp[5] = tmp[6] = tmp[7] = 0; - tmp[8] = tmp[9] = tmp[10] = tmp[11] = 0; - tmp[12] = product[28]; tmp[13] = product[29]; tmp[14] = product[30]; tmp[15] = product[31]; - tmp[16] = product[32]; tmp[17] = product[33]; tmp[18] = product[34]; tmp[19] = product[35]; - tmp[20] = product[36]; tmp[21] = product[37]; tmp[22] = product[38]; tmp[23] = product[39]; - tmp[24] = product[40]; tmp[25] = product[41]; tmp[26] = product[42]; tmp[27] = product[43]; - carry = uECC_vli_add(result, result, tmp, num_words_secp224r1); - - /* s2 */ - tmp[12] = product[44]; tmp[13] = product[45]; tmp[14] = product[46]; tmp[15] = product[47]; - tmp[16] = product[48]; tmp[17] = product[49]; tmp[18] = product[50]; tmp[19] = product[51]; - tmp[20] = product[52]; tmp[21] = product[53]; tmp[22] = product[54]; tmp[23] = product[55]; - tmp[24] = tmp[25] = tmp[26] = tmp[27] = 0; - carry += uECC_vli_add(result, result, tmp, num_words_secp224r1); - - /* d1 */ - tmp[0] = product[28]; tmp[1] = product[29]; tmp[2] = product[30]; tmp[3] = product[31]; - tmp[4] = product[32]; tmp[5] = product[33]; tmp[6] = product[34]; tmp[7] = product[35]; - tmp[8] = product[36]; tmp[9] = product[37]; tmp[10] = product[38]; tmp[11] = product[39]; - tmp[12] = product[40]; tmp[13] = product[41]; tmp[14] = product[42]; tmp[15] = product[43]; - tmp[16] = product[44]; tmp[17] = product[45]; tmp[18] = product[46]; tmp[19] = product[47]; - tmp[20] = product[48]; tmp[21] = product[49]; tmp[22] = product[50]; tmp[23] = product[51]; - tmp[24] = product[52]; tmp[25] = product[53]; tmp[26] = product[54]; tmp[27] = product[55]; - carry -= uECC_vli_sub(result, result, tmp, num_words_secp224r1); - - /* d2 */ - tmp[0] = product[44]; tmp[1] = product[45]; tmp[2] = product[46]; tmp[3] = product[47]; - tmp[4] = product[48]; tmp[5] = product[49]; tmp[6] = product[50]; tmp[7] = product[51]; - tmp[8] = product[52]; tmp[9] = product[53]; tmp[10] = product[54]; tmp[11] = product[55]; - tmp[12] = tmp[13] = tmp[14] = tmp[15] = 0; - tmp[16] = tmp[17] = tmp[18] = tmp[19] = 0; - tmp[20] = tmp[21] = tmp[22] = tmp[23] = 0; - tmp[24] = tmp[25] = tmp[26] = tmp[27] = 0; - carry -= uECC_vli_sub(result, result, tmp, num_words_secp224r1); - - if (carry < 0) { - do { - carry += uECC_vli_add(result, result, curve_secp224r1.p, num_words_secp224r1); - } while (carry < 0); - } else { - while (carry || uECC_vli_cmp_unsafe(curve_secp224r1.p, result, num_words_secp224r1) != 1) { - carry -= uECC_vli_sub(result, result, curve_secp224r1.p, num_words_secp224r1); - } - } -} -#elif uECC_WORD_SIZE == 4 -static void vli_mmod_fast_secp224r1(uint32_t *result, uint32_t *product) -{ - uint32_t tmp[num_words_secp224r1]; - int carry; - - /* t */ - uECC_vli_set(result, product, num_words_secp224r1); - - /* s1 */ - tmp[0] = tmp[1] = tmp[2] = 0; - tmp[3] = product[7]; - tmp[4] = product[8]; - tmp[5] = product[9]; - tmp[6] = product[10]; - carry = uECC_vli_add(result, result, tmp, num_words_secp224r1); - - /* s2 */ - tmp[3] = product[11]; - tmp[4] = product[12]; - tmp[5] = product[13]; - tmp[6] = 0; - carry += uECC_vli_add(result, result, tmp, num_words_secp224r1); - - /* d1 */ - tmp[0] = product[7]; - tmp[1] = product[8]; - tmp[2] = product[9]; - tmp[3] = product[10]; - tmp[4] = product[11]; - tmp[5] = product[12]; - tmp[6] = product[13]; - carry -= uECC_vli_sub(result, result, tmp, num_words_secp224r1); - - /* d2 */ - tmp[0] = product[11]; - tmp[1] = product[12]; - tmp[2] = product[13]; - tmp[3] = tmp[4] = tmp[5] = tmp[6] = 0; - carry -= uECC_vli_sub(result, result, tmp, num_words_secp224r1); - - if (carry < 0) { - do { - carry += uECC_vli_add(result, result, curve_secp224r1.p, num_words_secp224r1); - } while (carry < 0); - } else { - while (carry || uECC_vli_cmp_unsafe(curve_secp224r1.p, result, num_words_secp224r1) != 1) { - carry -= uECC_vli_sub(result, result, curve_secp224r1.p, num_words_secp224r1); - } - } -} -#else -static void vli_mmod_fast_secp224r1(uint64_t *result, uint64_t *product) -{ - uint64_t tmp[num_words_secp224r1]; - int carry = 0; - - /* t */ - uECC_vli_set(result, product, num_words_secp224r1); - result[num_words_secp224r1 - 1] &= 0xffffffff; - - /* s1 */ - tmp[0] = 0; - tmp[1] = product[3] & 0xffffffff00000000ull; - tmp[2] = product[4]; - tmp[3] = product[5] & 0xffffffff; - uECC_vli_add(result, result, tmp, num_words_secp224r1); - - /* s2 */ - tmp[1] = product[5] & 0xffffffff00000000ull; - tmp[2] = product[6]; - tmp[3] = 0; - uECC_vli_add(result, result, tmp, num_words_secp224r1); - - /* d1 */ - tmp[0] = (product[3] >> 32) | (product[4] << 32); - tmp[1] = (product[4] >> 32) | (product[5] << 32); - tmp[2] = (product[5] >> 32) | (product[6] << 32); - tmp[3] = product[6] >> 32; - carry -= uECC_vli_sub(result, result, tmp, num_words_secp224r1); - - /* d2 */ - tmp[0] = (product[5] >> 32) | (product[6] << 32); - tmp[1] = product[6] >> 32; - tmp[2] = tmp[3] = 0; - carry -= uECC_vli_sub(result, result, tmp, num_words_secp224r1); - - if (carry < 0) { - do { - carry += uECC_vli_add(result, result, curve_secp224r1.p, num_words_secp224r1); - } while (carry < 0); - } else { - while (uECC_vli_cmp_unsafe(curve_secp224r1.p, result, num_words_secp224r1) != 1) { - uECC_vli_sub(result, result, curve_secp224r1.p, num_words_secp224r1); - } - } -} -#endif /* uECC_WORD_SIZE */ -#endif /* (uECC_OPTIMIZATION_LEVEL > 0) */ - -#endif /* uECC_SUPPORTS_secp224r1 */ - -#if uECC_SUPPORTS_secp256r1 - -#if (uECC_OPTIMIZATION_LEVEL > 0) -static void vli_mmod_fast_secp256r1(uECC_word_t *result, uECC_word_t *product); -#endif - -static const struct uECC_Curve_t curve_secp256r1 = { - num_words_secp256r1, - num_bytes_secp256r1, - 256, /* num_n_bits */ - { BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF), - BYTES_TO_WORDS_8(FF, FF, FF, FF, 00, 00, 00, 00), - BYTES_TO_WORDS_8(00, 00, 00, 00, 00, 00, 00, 00), - BYTES_TO_WORDS_8(01, 00, 00, 00, FF, FF, FF, FF) }, - { BYTES_TO_WORDS_8(51, 25, 63, FC, C2, CA, B9, F3), - BYTES_TO_WORDS_8(84, 9E, 17, A7, AD, FA, E6, BC), - BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF), - BYTES_TO_WORDS_8(00, 00, 00, 00, FF, FF, FF, FF) }, - { BYTES_TO_WORDS_8(96, C2, 98, D8, 45, 39, A1, F4), - BYTES_TO_WORDS_8(A0, 33, EB, 2D, 81, 7D, 03, 77), - BYTES_TO_WORDS_8(F2, 40, A4, 63, E5, E6, BC, F8), - BYTES_TO_WORDS_8(47, 42, 2C, E1, F2, D1, 17, 6B), - - BYTES_TO_WORDS_8(F5, 51, BF, 37, 68, 40, B6, CB), - BYTES_TO_WORDS_8(CE, 5E, 31, 6B, 57, 33, CE, 2B), - BYTES_TO_WORDS_8(16, 9E, 0F, 7C, 4A, EB, E7, 8E), - BYTES_TO_WORDS_8(9B, 7F, 1A, FE, E2, 42, E3, 4F) }, - { BYTES_TO_WORDS_8(4B, 60, D2, 27, 3E, 3C, CE, 3B), - BYTES_TO_WORDS_8(F6, B0, 53, CC, B0, 06, 1D, 65), - BYTES_TO_WORDS_8(BC, 86, 98, 76, 55, BD, EB, B3), - BYTES_TO_WORDS_8(E7, 93, 3A, AA, D8, 35, C6, 5A) }, - &double_jacobian_default, -#if uECC_SUPPORT_COMPRESSED_POINT - &mod_sqrt_default, -#endif - &x_side_default, -#if (uECC_OPTIMIZATION_LEVEL > 0) - &vli_mmod_fast_secp256r1 -#endif -}; - -uECC_Curve uECC_secp256r1(void) { return &curve_secp256r1; } - - -#if (uECC_OPTIMIZATION_LEVEL > 0 && !asm_mmod_fast_secp256r1) -/* Computes result = product % curve_p - from http://www.nsa.gov/ia/_files/nist-routines.pdf */ -#if uECC_WORD_SIZE == 1 -static void vli_mmod_fast_secp256r1(uint8_t *result, uint8_t *product) { - uint8_t tmp[num_words_secp256r1]; - int8_t carry; - - /* t */ - uECC_vli_set(result, product, num_words_secp256r1); - - /* s1 */ - tmp[0] = tmp[1] = tmp[2] = tmp[3] = 0; - tmp[4] = tmp[5] = tmp[6] = tmp[7] = 0; - tmp[8] = tmp[9] = tmp[10] = tmp[11] = 0; - tmp[12] = product[44]; tmp[13] = product[45]; tmp[14] = product[46]; tmp[15] = product[47]; - tmp[16] = product[48]; tmp[17] = product[49]; tmp[18] = product[50]; tmp[19] = product[51]; - tmp[20] = product[52]; tmp[21] = product[53]; tmp[22] = product[54]; tmp[23] = product[55]; - tmp[24] = product[56]; tmp[25] = product[57]; tmp[26] = product[58]; tmp[27] = product[59]; - tmp[28] = product[60]; tmp[29] = product[61]; tmp[30] = product[62]; tmp[31] = product[63]; - carry = uECC_vli_add(tmp, tmp, tmp, num_words_secp256r1); - carry += uECC_vli_add(result, result, tmp, num_words_secp256r1); - - /* s2 */ - tmp[12] = product[48]; tmp[13] = product[49]; tmp[14] = product[50]; tmp[15] = product[51]; - tmp[16] = product[52]; tmp[17] = product[53]; tmp[18] = product[54]; tmp[19] = product[55]; - tmp[20] = product[56]; tmp[21] = product[57]; tmp[22] = product[58]; tmp[23] = product[59]; - tmp[24] = product[60]; tmp[25] = product[61]; tmp[26] = product[62]; tmp[27] = product[63]; - tmp[28] = tmp[29] = tmp[30] = tmp[31] = 0; - carry += uECC_vli_add(tmp, tmp, tmp, num_words_secp256r1); - carry += uECC_vli_add(result, result, tmp, num_words_secp256r1); - - /* s3 */ - tmp[0] = product[32]; tmp[1] = product[33]; tmp[2] = product[34]; tmp[3] = product[35]; - tmp[4] = product[36]; tmp[5] = product[37]; tmp[6] = product[38]; tmp[7] = product[39]; - tmp[8] = product[40]; tmp[9] = product[41]; tmp[10] = product[42]; tmp[11] = product[43]; - tmp[12] = tmp[13] = tmp[14] = tmp[15] = 0; - tmp[16] = tmp[17] = tmp[18] = tmp[19] = 0; - tmp[20] = tmp[21] = tmp[22] = tmp[23] = 0; - tmp[24] = product[56]; tmp[25] = product[57]; tmp[26] = product[58]; tmp[27] = product[59]; - tmp[28] = product[60]; tmp[29] = product[61]; tmp[30] = product[62]; tmp[31] = product[63]; - carry += uECC_vli_add(result, result, tmp, num_words_secp256r1); - - /* s4 */ - tmp[0] = product[36]; tmp[1] = product[37]; tmp[2] = product[38]; tmp[3] = product[39]; - tmp[4] = product[40]; tmp[5] = product[41]; tmp[6] = product[42]; tmp[7] = product[43]; - tmp[8] = product[44]; tmp[9] = product[45]; tmp[10] = product[46]; tmp[11] = product[47]; - tmp[12] = product[52]; tmp[13] = product[53]; tmp[14] = product[54]; tmp[15] = product[55]; - tmp[16] = product[56]; tmp[17] = product[57]; tmp[18] = product[58]; tmp[19] = product[59]; - tmp[20] = product[60]; tmp[21] = product[61]; tmp[22] = product[62]; tmp[23] = product[63]; - tmp[24] = product[52]; tmp[25] = product[53]; tmp[26] = product[54]; tmp[27] = product[55]; - tmp[28] = product[32]; tmp[29] = product[33]; tmp[30] = product[34]; tmp[31] = product[35]; - carry += uECC_vli_add(result, result, tmp, num_words_secp256r1); - - /* d1 */ - tmp[0] = product[44]; tmp[1] = product[45]; tmp[2] = product[46]; tmp[3] = product[47]; - tmp[4] = product[48]; tmp[5] = product[49]; tmp[6] = product[50]; tmp[7] = product[51]; - tmp[8] = product[52]; tmp[9] = product[53]; tmp[10] = product[54]; tmp[11] = product[55]; - tmp[12] = tmp[13] = tmp[14] = tmp[15] = 0; - tmp[16] = tmp[17] = tmp[18] = tmp[19] = 0; - tmp[20] = tmp[21] = tmp[22] = tmp[23] = 0; - tmp[24] = product[32]; tmp[25] = product[33]; tmp[26] = product[34]; tmp[27] = product[35]; - tmp[28] = product[40]; tmp[29] = product[41]; tmp[30] = product[42]; tmp[31] = product[43]; - carry -= uECC_vli_sub(result, result, tmp, num_words_secp256r1); - - /* d2 */ - tmp[0] = product[48]; tmp[1] = product[49]; tmp[2] = product[50]; tmp[3] = product[51]; - tmp[4] = product[52]; tmp[5] = product[53]; tmp[6] = product[54]; tmp[7] = product[55]; - tmp[8] = product[56]; tmp[9] = product[57]; tmp[10] = product[58]; tmp[11] = product[59]; - tmp[12] = product[60]; tmp[13] = product[61]; tmp[14] = product[62]; tmp[15] = product[63]; - tmp[16] = tmp[17] = tmp[18] = tmp[19] = 0; - tmp[20] = tmp[21] = tmp[22] = tmp[23] = 0; - tmp[24] = product[36]; tmp[25] = product[37]; tmp[26] = product[38]; tmp[27] = product[39]; - tmp[28] = product[44]; tmp[29] = product[45]; tmp[30] = product[46]; tmp[31] = product[47]; - carry -= uECC_vli_sub(result, result, tmp, num_words_secp256r1); - - /* d3 */ - tmp[0] = product[52]; tmp[1] = product[53]; tmp[2] = product[54]; tmp[3] = product[55]; - tmp[4] = product[56]; tmp[5] = product[57]; tmp[6] = product[58]; tmp[7] = product[59]; - tmp[8] = product[60]; tmp[9] = product[61]; tmp[10] = product[62]; tmp[11] = product[63]; - tmp[12] = product[32]; tmp[13] = product[33]; tmp[14] = product[34]; tmp[15] = product[35]; - tmp[16] = product[36]; tmp[17] = product[37]; tmp[18] = product[38]; tmp[19] = product[39]; - tmp[20] = product[40]; tmp[21] = product[41]; tmp[22] = product[42]; tmp[23] = product[43]; - tmp[24] = tmp[25] = tmp[26] = tmp[27] = 0; - tmp[28] = product[48]; tmp[29] = product[49]; tmp[30] = product[50]; tmp[31] = product[51]; - carry -= uECC_vli_sub(result, result, tmp, num_words_secp256r1); - - /* d4 */ - tmp[0] = product[56]; tmp[1] = product[57]; tmp[2] = product[58]; tmp[3] = product[59]; - tmp[4] = product[60]; tmp[5] = product[61]; tmp[6] = product[62]; tmp[7] = product[63]; - tmp[8] = tmp[9] = tmp[10] = tmp[11] = 0; - tmp[12] = product[36]; tmp[13] = product[37]; tmp[14] = product[38]; tmp[15] = product[39]; - tmp[16] = product[40]; tmp[17] = product[41]; tmp[18] = product[42]; tmp[19] = product[43]; - tmp[20] = product[44]; tmp[21] = product[45]; tmp[22] = product[46]; tmp[23] = product[47]; - tmp[24] = tmp[25] = tmp[26] = tmp[27] = 0; - tmp[28] = product[52]; tmp[29] = product[53]; tmp[30] = product[54]; tmp[31] = product[55]; - carry -= uECC_vli_sub(result, result, tmp, num_words_secp256r1); - - if (carry < 0) { - do { - carry += uECC_vli_add(result, result, curve_secp256r1.p, num_words_secp256r1); - } while (carry < 0); - } else { - while (carry || uECC_vli_cmp_unsafe(curve_secp256r1.p, result, num_words_secp256r1) != 1) { - carry -= uECC_vli_sub(result, result, curve_secp256r1.p, num_words_secp256r1); - } - } -} -#elif uECC_WORD_SIZE == 4 -static void vli_mmod_fast_secp256r1(uint32_t *result, uint32_t *product) { - uint32_t tmp[num_words_secp256r1]; - int carry; - - /* t */ - uECC_vli_set(result, product, num_words_secp256r1); - - /* s1 */ - tmp[0] = tmp[1] = tmp[2] = 0; - tmp[3] = product[11]; - tmp[4] = product[12]; - tmp[5] = product[13]; - tmp[6] = product[14]; - tmp[7] = product[15]; - carry = uECC_vli_add(tmp, tmp, tmp, num_words_secp256r1); - carry += uECC_vli_add(result, result, tmp, num_words_secp256r1); - - /* s2 */ - tmp[3] = product[12]; - tmp[4] = product[13]; - tmp[5] = product[14]; - tmp[6] = product[15]; - tmp[7] = 0; - carry += uECC_vli_add(tmp, tmp, tmp, num_words_secp256r1); - carry += uECC_vli_add(result, result, tmp, num_words_secp256r1); - - /* s3 */ - tmp[0] = product[8]; - tmp[1] = product[9]; - tmp[2] = product[10]; - tmp[3] = tmp[4] = tmp[5] = 0; - tmp[6] = product[14]; - tmp[7] = product[15]; - carry += uECC_vli_add(result, result, tmp, num_words_secp256r1); - - /* s4 */ - tmp[0] = product[9]; - tmp[1] = product[10]; - tmp[2] = product[11]; - tmp[3] = product[13]; - tmp[4] = product[14]; - tmp[5] = product[15]; - tmp[6] = product[13]; - tmp[7] = product[8]; - carry += uECC_vli_add(result, result, tmp, num_words_secp256r1); - - /* d1 */ - tmp[0] = product[11]; - tmp[1] = product[12]; - tmp[2] = product[13]; - tmp[3] = tmp[4] = tmp[5] = 0; - tmp[6] = product[8]; - tmp[7] = product[10]; - carry -= uECC_vli_sub(result, result, tmp, num_words_secp256r1); - - /* d2 */ - tmp[0] = product[12]; - tmp[1] = product[13]; - tmp[2] = product[14]; - tmp[3] = product[15]; - tmp[4] = tmp[5] = 0; - tmp[6] = product[9]; - tmp[7] = product[11]; - carry -= uECC_vli_sub(result, result, tmp, num_words_secp256r1); - - /* d3 */ - tmp[0] = product[13]; - tmp[1] = product[14]; - tmp[2] = product[15]; - tmp[3] = product[8]; - tmp[4] = product[9]; - tmp[5] = product[10]; - tmp[6] = 0; - tmp[7] = product[12]; - carry -= uECC_vli_sub(result, result, tmp, num_words_secp256r1); - - /* d4 */ - tmp[0] = product[14]; - tmp[1] = product[15]; - tmp[2] = 0; - tmp[3] = product[9]; - tmp[4] = product[10]; - tmp[5] = product[11]; - tmp[6] = 0; - tmp[7] = product[13]; - carry -= uECC_vli_sub(result, result, tmp, num_words_secp256r1); - - if (carry < 0) { - do { - carry += uECC_vli_add(result, result, curve_secp256r1.p, num_words_secp256r1); - } while (carry < 0); - } else { - while (carry || uECC_vli_cmp_unsafe(curve_secp256r1.p, result, num_words_secp256r1) != 1) { - carry -= uECC_vli_sub(result, result, curve_secp256r1.p, num_words_secp256r1); - } - } -} -#else -static void vli_mmod_fast_secp256r1(uint64_t *result, uint64_t *product) { - uint64_t tmp[num_words_secp256r1]; - int carry; - - /* t */ - uECC_vli_set(result, product, num_words_secp256r1); - - /* s1 */ - tmp[0] = 0; - tmp[1] = product[5] & 0xffffffff00000000ull; - tmp[2] = product[6]; - tmp[3] = product[7]; - carry = (int)uECC_vli_add(tmp, tmp, tmp, num_words_secp256r1); - carry += uECC_vli_add(result, result, tmp, num_words_secp256r1); - - /* s2 */ - tmp[1] = product[6] << 32; - tmp[2] = (product[6] >> 32) | (product[7] << 32); - tmp[3] = product[7] >> 32; - carry += uECC_vli_add(tmp, tmp, tmp, num_words_secp256r1); - carry += uECC_vli_add(result, result, tmp, num_words_secp256r1); - - /* s3 */ - tmp[0] = product[4]; - tmp[1] = product[5] & 0xffffffff; - tmp[2] = 0; - tmp[3] = product[7]; - carry += uECC_vli_add(result, result, tmp, num_words_secp256r1); - - /* s4 */ - tmp[0] = (product[4] >> 32) | (product[5] << 32); - tmp[1] = (product[5] >> 32) | (product[6] & 0xffffffff00000000ull); - tmp[2] = product[7]; - tmp[3] = (product[6] >> 32) | (product[4] << 32); - carry += uECC_vli_add(result, result, tmp, num_words_secp256r1); - - /* d1 */ - tmp[0] = (product[5] >> 32) | (product[6] << 32); - tmp[1] = (product[6] >> 32); - tmp[2] = 0; - tmp[3] = (product[4] & 0xffffffff) | (product[5] << 32); - carry -= uECC_vli_sub(result, result, tmp, num_words_secp256r1); - - /* d2 */ - tmp[0] = product[6]; - tmp[1] = product[7]; - tmp[2] = 0; - tmp[3] = (product[4] >> 32) | (product[5] & 0xffffffff00000000ull); - carry -= uECC_vli_sub(result, result, tmp, num_words_secp256r1); - - /* d3 */ - tmp[0] = (product[6] >> 32) | (product[7] << 32); - tmp[1] = (product[7] >> 32) | (product[4] << 32); - tmp[2] = (product[4] >> 32) | (product[5] << 32); - tmp[3] = (product[6] << 32); - carry -= uECC_vli_sub(result, result, tmp, num_words_secp256r1); - - /* d4 */ - tmp[0] = product[7]; - tmp[1] = product[4] & 0xffffffff00000000ull; - tmp[2] = product[5]; - tmp[3] = product[6] & 0xffffffff00000000ull; - carry -= uECC_vli_sub(result, result, tmp, num_words_secp256r1); - - if (carry < 0) { - do { - carry += uECC_vli_add(result, result, curve_secp256r1.p, num_words_secp256r1); - } while (carry < 0); - } else { - while (carry || uECC_vli_cmp_unsafe(curve_secp256r1.p, result, num_words_secp256r1) != 1) { - carry -= uECC_vli_sub(result, result, curve_secp256r1.p, num_words_secp256r1); - } - } -} -#endif /* uECC_WORD_SIZE */ -#endif /* (uECC_OPTIMIZATION_LEVEL > 0 && !asm_mmod_fast_secp256r1) */ - -#endif /* uECC_SUPPORTS_secp256r1 */ - -#if uECC_SUPPORTS_secp256k1 - -static void double_jacobian_secp256k1(uECC_word_t * X1, - uECC_word_t * Y1, - uECC_word_t * Z1, - uECC_Curve curve); -static void x_side_secp256k1(uECC_word_t *result, const uECC_word_t *x, uECC_Curve curve); -#if (uECC_OPTIMIZATION_LEVEL > 0) -static void vli_mmod_fast_secp256k1(uECC_word_t *result, uECC_word_t *product); -#endif - -static const struct uECC_Curve_t curve_secp256k1 = { - num_words_secp256k1, - num_bytes_secp256k1, - 256, /* num_n_bits */ - { BYTES_TO_WORDS_8(2F, FC, FF, FF, FE, FF, FF, FF), - BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF), - BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF), - BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF) }, - { BYTES_TO_WORDS_8(41, 41, 36, D0, 8C, 5E, D2, BF), - BYTES_TO_WORDS_8(3B, A0, 48, AF, E6, DC, AE, BA), - BYTES_TO_WORDS_8(FE, FF, FF, FF, FF, FF, FF, FF), - BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF) }, - { BYTES_TO_WORDS_8(98, 17, F8, 16, 5B, 81, F2, 59), - BYTES_TO_WORDS_8(D9, 28, CE, 2D, DB, FC, 9B, 02), - BYTES_TO_WORDS_8(07, 0B, 87, CE, 95, 62, A0, 55), - BYTES_TO_WORDS_8(AC, BB, DC, F9, 7E, 66, BE, 79), - - BYTES_TO_WORDS_8(B8, D4, 10, FB, 8F, D0, 47, 9C), - BYTES_TO_WORDS_8(19, 54, 85, A6, 48, B4, 17, FD), - BYTES_TO_WORDS_8(A8, 08, 11, 0E, FC, FB, A4, 5D), - BYTES_TO_WORDS_8(65, C4, A3, 26, 77, DA, 3A, 48) }, - { BYTES_TO_WORDS_8(07, 00, 00, 00, 00, 00, 00, 00), - BYTES_TO_WORDS_8(00, 00, 00, 00, 00, 00, 00, 00), - BYTES_TO_WORDS_8(00, 00, 00, 00, 00, 00, 00, 00), - BYTES_TO_WORDS_8(00, 00, 00, 00, 00, 00, 00, 00) }, - &double_jacobian_secp256k1, -#if uECC_SUPPORT_COMPRESSED_POINT - &mod_sqrt_default, -#endif - &x_side_secp256k1, -#if (uECC_OPTIMIZATION_LEVEL > 0) - &vli_mmod_fast_secp256k1 -#endif -}; - -uECC_Curve uECC_secp256k1(void) { return &curve_secp256k1; } - - -/* Double in place */ -static void double_jacobian_secp256k1(uECC_word_t * X1, - uECC_word_t * Y1, - uECC_word_t * Z1, - uECC_Curve curve) { - /* t1 = X, t2 = Y, t3 = Z */ - uECC_word_t t4[num_words_secp256k1]; - uECC_word_t t5[num_words_secp256k1]; - - if (uECC_vli_isZero(Z1, num_words_secp256k1)) { - return; - } - - uECC_vli_modSquare_fast(t5, Y1, curve); /* t5 = y1^2 */ - uECC_vli_modMult_fast(t4, X1, t5, curve); /* t4 = x1*y1^2 = A */ - uECC_vli_modSquare_fast(X1, X1, curve); /* t1 = x1^2 */ - uECC_vli_modSquare_fast(t5, t5, curve); /* t5 = y1^4 */ - uECC_vli_modMult_fast(Z1, Y1, Z1, curve); /* t3 = y1*z1 = z3 */ - - uECC_vli_modAdd(Y1, X1, X1, curve->p, num_words_secp256k1); /* t2 = 2*x1^2 */ - uECC_vli_modAdd(Y1, Y1, X1, curve->p, num_words_secp256k1); /* t2 = 3*x1^2 */ - if (uECC_vli_testBit(Y1, 0)) { - uECC_word_t carry = uECC_vli_add(Y1, Y1, curve->p, num_words_secp256k1); - uECC_vli_rshift1(Y1, num_words_secp256k1); - Y1[num_words_secp256k1 - 1] |= carry << (uECC_WORD_BITS - 1); - } else { - uECC_vli_rshift1(Y1, num_words_secp256k1); - } - /* t2 = 3/2*(x1^2) = B */ - - uECC_vli_modSquare_fast(X1, Y1, curve); /* t1 = B^2 */ - uECC_vli_modSub(X1, X1, t4, curve->p, num_words_secp256k1); /* t1 = B^2 - A */ - uECC_vli_modSub(X1, X1, t4, curve->p, num_words_secp256k1); /* t1 = B^2 - 2A = x3 */ - - uECC_vli_modSub(t4, t4, X1, curve->p, num_words_secp256k1); /* t4 = A - x3 */ - uECC_vli_modMult_fast(Y1, Y1, t4, curve); /* t2 = B * (A - x3) */ - uECC_vli_modSub(Y1, Y1, t5, curve->p, num_words_secp256k1); /* t2 = B * (A - x3) - y1^4 = y3 */ -} - -/* Computes result = x^3 + b. result must not overlap x. */ -static void x_side_secp256k1(uECC_word_t *result, const uECC_word_t *x, uECC_Curve curve) { - uECC_vli_modSquare_fast(result, x, curve); /* r = x^2 */ - uECC_vli_modMult_fast(result, result, x, curve); /* r = x^3 */ - uECC_vli_modAdd(result, result, curve->b, curve->p, num_words_secp256k1); /* r = x^3 + b */ -} - -#if (uECC_OPTIMIZATION_LEVEL > 0 && !asm_mmod_fast_secp256k1) -static void omega_mult_secp256k1(uECC_word_t *result, const uECC_word_t *right); -static void vli_mmod_fast_secp256k1(uECC_word_t *result, uECC_word_t *product) { - uECC_word_t tmp[2 * num_words_secp256k1]; - uECC_word_t carry; - - uECC_vli_clear(tmp, num_words_secp256k1); - uECC_vli_clear(tmp + num_words_secp256k1, num_words_secp256k1); - - omega_mult_secp256k1(tmp, product + num_words_secp256k1); /* (Rq, q) = q * c */ - - carry = uECC_vli_add(result, product, tmp, num_words_secp256k1); /* (C, r) = r + q */ - uECC_vli_clear(product, num_words_secp256k1); - omega_mult_secp256k1(product, tmp + num_words_secp256k1); /* Rq*c */ - carry += uECC_vli_add(result, result, product, num_words_secp256k1); /* (C1, r) = r + Rq*c */ - - while (carry > 0) { - --carry; - uECC_vli_sub(result, result, curve_secp256k1.p, num_words_secp256k1); - } - if (uECC_vli_cmp_unsafe(result, curve_secp256k1.p, num_words_secp256k1) > 0) { - uECC_vli_sub(result, result, curve_secp256k1.p, num_words_secp256k1); - } -} - -#if uECC_WORD_SIZE == 1 -static void omega_mult_secp256k1(uint8_t * result, const uint8_t * right) { - /* Multiply by (2^32 + 2^9 + 2^8 + 2^7 + 2^6 + 2^4 + 1). */ - uECC_word_t r0 = 0; - uECC_word_t r1 = 0; - uECC_word_t r2 = 0; - wordcount_t k; - - /* Multiply by (2^9 + 2^8 + 2^7 + 2^6 + 2^4 + 1). */ - muladd(0xD1, right[0], &r0, &r1, &r2); - result[0] = r0; - r0 = r1; - r1 = r2; - /* r2 is still 0 */ - - for (k = 1; k < num_words_secp256k1; ++k) { - muladd(0x03, right[k - 1], &r0, &r1, &r2); - muladd(0xD1, right[k], &r0, &r1, &r2); - result[k] = r0; - r0 = r1; - r1 = r2; - r2 = 0; - } - muladd(0x03, right[num_words_secp256k1 - 1], &r0, &r1, &r2); - result[num_words_secp256k1] = r0; - result[num_words_secp256k1 + 1] = r1; - /* add the 2^32 multiple */ - result[4 + num_words_secp256k1] = - uECC_vli_add(result + 4, result + 4, right, num_words_secp256k1); -} -#elif uECC_WORD_SIZE == 4 -static void omega_mult_secp256k1(uint32_t * result, const uint32_t * right) { - /* Multiply by (2^9 + 2^8 + 2^7 + 2^6 + 2^4 + 1). */ - uint32_t carry = 0; - wordcount_t k; - - for (k = 0; k < num_words_secp256k1; ++k) { - uint64_t p = (uint64_t)0x3D1 * right[k] + carry; - result[k] = (uint32_t) p; - carry = p >> 32; - } - result[num_words_secp256k1] = carry; - /* add the 2^32 multiple */ - result[1 + num_words_secp256k1] = - uECC_vli_add(result + 1, result + 1, right, num_words_secp256k1); -} -#else -static void omega_mult_secp256k1(uint64_t * result, const uint64_t * right) { - uECC_word_t r0 = 0; - uECC_word_t r1 = 0; - uECC_word_t r2 = 0; - wordcount_t k; - - /* Multiply by (2^32 + 2^9 + 2^8 + 2^7 + 2^6 + 2^4 + 1). */ - for (k = 0; k < num_words_secp256k1; ++k) { - muladd(0x1000003D1ull, right[k], &r0, &r1, &r2); - result[k] = r0; - r0 = r1; - r1 = r2; - r2 = 0; - } - result[num_words_secp256k1] = r0; -} -#endif /* uECC_WORD_SIZE */ -#endif /* (uECC_OPTIMIZATION_LEVEL > 0 && && !asm_mmod_fast_secp256k1) */ - -#endif /* uECC_SUPPORTS_secp256k1 */ - -#endif /* _UECC_CURVE_SPECIFIC_H_ */ diff --git a/lib/micro-ecc/platform-specific.inc b/lib/micro-ecc/platform-specific.inc deleted file mode 100644 index 7e0373f5059..00000000000 --- a/lib/micro-ecc/platform-specific.inc +++ /dev/null @@ -1,94 +0,0 @@ -/* Copyright 2015, Kenneth MacKay. Licensed under the BSD 2-clause license. */ - -#ifndef _UECC_PLATFORM_SPECIFIC_H_ -#define _UECC_PLATFORM_SPECIFIC_H_ - -#include "types.h" - -#if (defined(_WIN32) || defined(_WIN64)) -/* Windows */ - -// use pragma syntax to prevent tweaking the linker script for getting CryptXYZ function -#pragma comment(lib, "crypt32.lib") -#pragma comment(lib, "advapi32.lib") - -#define WIN32_LEAN_AND_MEAN -#include -#include - -static int default_RNG(uint8_t *dest, unsigned size) { - HCRYPTPROV prov; - if (!CryptAcquireContext(&prov, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) { - return 0; - } - - CryptGenRandom(prov, size, (BYTE *)dest); - CryptReleaseContext(prov, 0); - return 1; -} -#define default_RNG_defined 1 - -#elif defined(unix) || defined(__linux__) || defined(__unix__) || defined(__unix) || \ - (defined(__APPLE__) && defined(__MACH__)) || defined(uECC_POSIX) - -/* Some POSIX-like system with /dev/urandom or /dev/random. */ -#include -#include -#include - -#ifndef O_CLOEXEC - #define O_CLOEXEC 0 -#endif - -static int default_RNG(uint8_t *dest, unsigned size) { - int fd = open("/dev/urandom", O_RDONLY | O_CLOEXEC); - if (fd == -1) { - fd = open("/dev/random", O_RDONLY | O_CLOEXEC); - if (fd == -1) { - return 0; - } - } - - char *ptr = (char *)dest; - size_t left = size; - while (left > 0) { - ssize_t bytes_read = read(fd, ptr, left); - if (bytes_read <= 0) { // read failed - close(fd); - return 0; - } - left -= bytes_read; - ptr += bytes_read; - } - - close(fd); - return 1; -} -#define default_RNG_defined 1 - -#elif defined(RIOT_VERSION) - -#include - -static int default_RNG(uint8_t *dest, unsigned size) { - random_bytes(dest, size); - return 1; -} -#define default_RNG_defined 1 - -#elif defined(NRF52_SERIES) - -#include "app_error.h" -#include "nrf_crypto_rng.h" - -static int default_RNG(uint8_t *dest, unsigned size) -{ - // make sure to call nrf_crypto_init and nrf_crypto_rng_init first - ret_code_t ret_code = nrf_crypto_rng_vector_generate(dest, size); - return (ret_code == NRF_SUCCESS) ? 1 : 0; -} -#define default_RNG_defined 1 - -#endif /* platform */ - -#endif /* _UECC_PLATFORM_SPECIFIC_H_ */ diff --git a/lib/micro-ecc/types.h b/lib/micro-ecc/types.h deleted file mode 100644 index 9ee81438fac..00000000000 --- a/lib/micro-ecc/types.h +++ /dev/null @@ -1,108 +0,0 @@ -/* Copyright 2015, Kenneth MacKay. Licensed under the BSD 2-clause license. */ - -#ifndef _UECC_TYPES_H_ -#define _UECC_TYPES_H_ - -#ifndef uECC_PLATFORM - #if __AVR__ - #define uECC_PLATFORM uECC_avr - #elif defined(__thumb2__) || defined(_M_ARMT) /* I think MSVC only supports Thumb-2 targets */ - #define uECC_PLATFORM uECC_arm_thumb2 - #elif defined(__thumb__) - #define uECC_PLATFORM uECC_arm_thumb - #elif defined(__arm__) || defined(_M_ARM) - #define uECC_PLATFORM uECC_arm - #elif defined(__aarch64__) - #define uECC_PLATFORM uECC_arm64 - #elif defined(__i386__) || defined(_M_IX86) || defined(_X86_) || defined(__I86__) - #define uECC_PLATFORM uECC_x86 - #elif defined(__amd64__) || defined(_M_X64) - #define uECC_PLATFORM uECC_x86_64 - #else - #define uECC_PLATFORM uECC_arch_other - #endif -#endif - -#ifndef uECC_ARM_USE_UMAAL - #if (uECC_PLATFORM == uECC_arm) && (__ARM_ARCH >= 6) - #define uECC_ARM_USE_UMAAL 1 - #elif (uECC_PLATFORM == uECC_arm_thumb2) && (__ARM_ARCH >= 6) && !__ARM_ARCH_7M__ - #define uECC_ARM_USE_UMAAL 1 - #else - #define uECC_ARM_USE_UMAAL 0 - #endif -#endif - -#ifndef uECC_WORD_SIZE - #if uECC_PLATFORM == uECC_avr - #define uECC_WORD_SIZE 1 - #elif (uECC_PLATFORM == uECC_x86_64 || uECC_PLATFORM == uECC_arm64) - #define uECC_WORD_SIZE 8 - #else - #define uECC_WORD_SIZE 4 - #endif -#endif - -#if (uECC_WORD_SIZE != 1) && (uECC_WORD_SIZE != 4) && (uECC_WORD_SIZE != 8) - #error "Unsupported value for uECC_WORD_SIZE" -#endif - -#if ((uECC_PLATFORM == uECC_avr) && (uECC_WORD_SIZE != 1)) - #pragma message ("uECC_WORD_SIZE must be 1 for AVR") - #undef uECC_WORD_SIZE - #define uECC_WORD_SIZE 1 -#endif - -#if ((uECC_PLATFORM == uECC_arm || uECC_PLATFORM == uECC_arm_thumb || \ - uECC_PLATFORM == uECC_arm_thumb2) && \ - (uECC_WORD_SIZE != 4)) - #pragma message ("uECC_WORD_SIZE must be 4 for ARM") - #undef uECC_WORD_SIZE - #define uECC_WORD_SIZE 4 -#endif - -#if defined(__SIZEOF_INT128__) || ((__clang_major__ * 100 + __clang_minor__) >= 302) - #define SUPPORTS_INT128 1 -#else - #define SUPPORTS_INT128 0 -#endif - -typedef int8_t wordcount_t; -typedef int16_t bitcount_t; -typedef int8_t cmpresult_t; - -#if (uECC_WORD_SIZE == 1) - -typedef uint8_t uECC_word_t; -typedef uint16_t uECC_dword_t; - -#define HIGH_BIT_SET 0x80 -#define uECC_WORD_BITS 8 -#define uECC_WORD_BITS_SHIFT 3 -#define uECC_WORD_BITS_MASK 0x07 - -#elif (uECC_WORD_SIZE == 4) - -typedef uint32_t uECC_word_t; -typedef uint64_t uECC_dword_t; - -#define HIGH_BIT_SET 0x80000000 -#define uECC_WORD_BITS 32 -#define uECC_WORD_BITS_SHIFT 5 -#define uECC_WORD_BITS_MASK 0x01F - -#elif (uECC_WORD_SIZE == 8) - -typedef uint64_t uECC_word_t; -#if SUPPORTS_INT128 -typedef unsigned __int128 uECC_dword_t; -#endif - -#define HIGH_BIT_SET 0x8000000000000000ull -#define uECC_WORD_BITS 64 -#define uECC_WORD_BITS_SHIFT 6 -#define uECC_WORD_BITS_MASK 0x03F - -#endif /* uECC_WORD_SIZE */ - -#endif /* _UECC_TYPES_H_ */ diff --git a/lib/micro-ecc/uECC.c b/lib/micro-ecc/uECC.c deleted file mode 100644 index a3d502cf21b..00000000000 --- a/lib/micro-ecc/uECC.c +++ /dev/null @@ -1,1669 +0,0 @@ -/* Copyright 2014, Kenneth MacKay. Licensed under the BSD 2-clause license. */ - -#include "uECC.h" -#include "uECC_vli.h" - -#ifndef uECC_RNG_MAX_TRIES - #define uECC_RNG_MAX_TRIES 64 -#endif - -#if uECC_ENABLE_VLI_API - #define uECC_VLI_API -#else - #define uECC_VLI_API static -#endif - -#if (uECC_PLATFORM == uECC_avr) || \ - (uECC_PLATFORM == uECC_arm) || \ - (uECC_PLATFORM == uECC_arm_thumb) || \ - (uECC_PLATFORM == uECC_arm_thumb2) - #define CONCATX(a, ...) a ## __VA_ARGS__ - #define CONCAT(a, ...) CONCATX(a, __VA_ARGS__) - - #define STRX(a) #a - #define STR(a) STRX(a) - - #define EVAL(...) EVAL1(EVAL1(EVAL1(EVAL1(__VA_ARGS__)))) - #define EVAL1(...) EVAL2(EVAL2(EVAL2(EVAL2(__VA_ARGS__)))) - #define EVAL2(...) EVAL3(EVAL3(EVAL3(EVAL3(__VA_ARGS__)))) - #define EVAL3(...) EVAL4(EVAL4(EVAL4(EVAL4(__VA_ARGS__)))) - #define EVAL4(...) __VA_ARGS__ - - #define DEC_1 0 - #define DEC_2 1 - #define DEC_3 2 - #define DEC_4 3 - #define DEC_5 4 - #define DEC_6 5 - #define DEC_7 6 - #define DEC_8 7 - #define DEC_9 8 - #define DEC_10 9 - #define DEC_11 10 - #define DEC_12 11 - #define DEC_13 12 - #define DEC_14 13 - #define DEC_15 14 - #define DEC_16 15 - #define DEC_17 16 - #define DEC_18 17 - #define DEC_19 18 - #define DEC_20 19 - #define DEC_21 20 - #define DEC_22 21 - #define DEC_23 22 - #define DEC_24 23 - #define DEC_25 24 - #define DEC_26 25 - #define DEC_27 26 - #define DEC_28 27 - #define DEC_29 28 - #define DEC_30 29 - #define DEC_31 30 - #define DEC_32 31 - - #define DEC(N) CONCAT(DEC_, N) - - #define SECOND_ARG(_, val, ...) val - #define SOME_CHECK_0 ~, 0 - #define GET_SECOND_ARG(...) SECOND_ARG(__VA_ARGS__, SOME,) - #define SOME_OR_0(N) GET_SECOND_ARG(CONCAT(SOME_CHECK_, N)) - - #define EMPTY(...) - #define DEFER(...) __VA_ARGS__ EMPTY() - - #define REPEAT_NAME_0() REPEAT_0 - #define REPEAT_NAME_SOME() REPEAT_SOME - #define REPEAT_0(...) - #define REPEAT_SOME(N, stuff) DEFER(CONCAT(REPEAT_NAME_, SOME_OR_0(DEC(N))))()(DEC(N), stuff) stuff - #define REPEAT(N, stuff) EVAL(REPEAT_SOME(N, stuff)) - - #define REPEATM_NAME_0() REPEATM_0 - #define REPEATM_NAME_SOME() REPEATM_SOME - #define REPEATM_0(...) - #define REPEATM_SOME(N, macro) macro(N) \ - DEFER(CONCAT(REPEATM_NAME_, SOME_OR_0(DEC(N))))()(DEC(N), macro) - #define REPEATM(N, macro) EVAL(REPEATM_SOME(N, macro)) -#endif - -#include "platform-specific.inc" - -#if (uECC_WORD_SIZE == 1) - #if uECC_SUPPORTS_secp160r1 - #define uECC_MAX_WORDS 21 /* Due to the size of curve_n. */ - #endif - #if uECC_SUPPORTS_secp192r1 - #undef uECC_MAX_WORDS - #define uECC_MAX_WORDS 24 - #endif - #if uECC_SUPPORTS_secp224r1 - #undef uECC_MAX_WORDS - #define uECC_MAX_WORDS 28 - #endif - #if (uECC_SUPPORTS_secp256r1 || uECC_SUPPORTS_secp256k1) - #undef uECC_MAX_WORDS - #define uECC_MAX_WORDS 32 - #endif -#elif (uECC_WORD_SIZE == 4) - #if uECC_SUPPORTS_secp160r1 - #define uECC_MAX_WORDS 6 /* Due to the size of curve_n. */ - #endif - #if uECC_SUPPORTS_secp192r1 - #undef uECC_MAX_WORDS - #define uECC_MAX_WORDS 6 - #endif - #if uECC_SUPPORTS_secp224r1 - #undef uECC_MAX_WORDS - #define uECC_MAX_WORDS 7 - #endif - #if (uECC_SUPPORTS_secp256r1 || uECC_SUPPORTS_secp256k1) - #undef uECC_MAX_WORDS - #define uECC_MAX_WORDS 8 - #endif -#elif (uECC_WORD_SIZE == 8) - #if uECC_SUPPORTS_secp160r1 - #define uECC_MAX_WORDS 3 - #endif - #if uECC_SUPPORTS_secp192r1 - #undef uECC_MAX_WORDS - #define uECC_MAX_WORDS 3 - #endif - #if uECC_SUPPORTS_secp224r1 - #undef uECC_MAX_WORDS - #define uECC_MAX_WORDS 4 - #endif - #if (uECC_SUPPORTS_secp256r1 || uECC_SUPPORTS_secp256k1) - #undef uECC_MAX_WORDS - #define uECC_MAX_WORDS 4 - #endif -#endif /* uECC_WORD_SIZE */ - -#define BITS_TO_WORDS(num_bits) ((num_bits + ((uECC_WORD_SIZE * 8) - 1)) / (uECC_WORD_SIZE * 8)) -#define BITS_TO_BYTES(num_bits) ((num_bits + 7) / 8) - -struct uECC_Curve_t { - wordcount_t num_words; - wordcount_t num_bytes; - bitcount_t num_n_bits; - uECC_word_t p[uECC_MAX_WORDS]; - uECC_word_t n[uECC_MAX_WORDS]; - uECC_word_t G[uECC_MAX_WORDS * 2]; - uECC_word_t b[uECC_MAX_WORDS]; - void (*double_jacobian)(uECC_word_t * X1, - uECC_word_t * Y1, - uECC_word_t * Z1, - uECC_Curve curve); -#if uECC_SUPPORT_COMPRESSED_POINT - void (*mod_sqrt)(uECC_word_t *a, uECC_Curve curve); -#endif - void (*x_side)(uECC_word_t *result, const uECC_word_t *x, uECC_Curve curve); -#if (uECC_OPTIMIZATION_LEVEL > 0) - void (*mmod_fast)(uECC_word_t *result, uECC_word_t *product); -#endif -}; - -#if uECC_VLI_NATIVE_LITTLE_ENDIAN -static void bcopy(uint8_t *dst, - const uint8_t *src, - unsigned num_bytes) { - while (0 != num_bytes) { - num_bytes--; - dst[num_bytes] = src[num_bytes]; - } -} -#endif - -static cmpresult_t uECC_vli_cmp_unsafe(const uECC_word_t *left, - const uECC_word_t *right, - wordcount_t num_words); - -#if (uECC_PLATFORM == uECC_arm || uECC_PLATFORM == uECC_arm_thumb || \ - uECC_PLATFORM == uECC_arm_thumb2) - #include "asm_arm.inc" -#endif - -#if (uECC_PLATFORM == uECC_avr) - #include "asm_avr.inc" -#endif - -#if default_RNG_defined -static uECC_RNG_Function g_rng_function = &default_RNG; -#else -static uECC_RNG_Function g_rng_function = 0; -#endif - -void uECC_set_rng(uECC_RNG_Function rng_function) { - g_rng_function = rng_function; -} - -uECC_RNG_Function uECC_get_rng(void) { - return g_rng_function; -} - -int uECC_curve_private_key_size(uECC_Curve curve) { - return BITS_TO_BYTES(curve->num_n_bits); -} - -int uECC_curve_public_key_size(uECC_Curve curve) { - return 2 * curve->num_bytes; -} - -#if !asm_clear -uECC_VLI_API void uECC_vli_clear(uECC_word_t *vli, wordcount_t num_words) { - wordcount_t i; - for (i = 0; i < num_words; ++i) { - vli[i] = 0; - } -} -#endif /* !asm_clear */ - -/* Constant-time comparison to zero - secure way to compare long integers */ -/* Returns 1 if vli == 0, 0 otherwise. */ -uECC_VLI_API uECC_word_t uECC_vli_isZero(const uECC_word_t *vli, wordcount_t num_words) { - uECC_word_t bits = 0; - wordcount_t i; - for (i = 0; i < num_words; ++i) { - bits |= vli[i]; - } - return (bits == 0); -} - -/* Returns nonzero if bit 'bit' of vli is set. */ -uECC_VLI_API uECC_word_t uECC_vli_testBit(const uECC_word_t *vli, bitcount_t bit) { - return (vli[bit >> uECC_WORD_BITS_SHIFT] & ((uECC_word_t)1 << (bit & uECC_WORD_BITS_MASK))); -} - -/* Counts the number of words in vli. */ -static wordcount_t vli_numDigits(const uECC_word_t *vli, const wordcount_t max_words) { - wordcount_t i; - /* Search from the end until we find a non-zero digit. - We do it in reverse because we expect that most digits will be nonzero. */ - for (i = max_words - 1; i >= 0 && vli[i] == 0; --i) { - } - - return (i + 1); -} - -/* Counts the number of bits required to represent vli. */ -uECC_VLI_API bitcount_t uECC_vli_numBits(const uECC_word_t *vli, const wordcount_t max_words) { - uECC_word_t i; - uECC_word_t digit; - - wordcount_t num_digits = vli_numDigits(vli, max_words); - if (num_digits == 0) { - return 0; - } - - digit = vli[num_digits - 1]; - for (i = 0; digit; ++i) { - digit >>= 1; - } - - return (((bitcount_t)(num_digits - 1) << uECC_WORD_BITS_SHIFT) + i); -} - -/* Sets dest = src. */ -#if !asm_set -uECC_VLI_API void uECC_vli_set(uECC_word_t *dest, const uECC_word_t *src, wordcount_t num_words) { - wordcount_t i; - for (i = 0; i < num_words; ++i) { - dest[i] = src[i]; - } -} -#endif /* !asm_set */ - -/* Returns sign of left - right. */ -static cmpresult_t uECC_vli_cmp_unsafe(const uECC_word_t *left, - const uECC_word_t *right, - wordcount_t num_words) { - wordcount_t i; - for (i = num_words - 1; i >= 0; --i) { - if (left[i] > right[i]) { - return 1; - } else if (left[i] < right[i]) { - return -1; - } - } - return 0; -} - -/* Constant-time comparison function - secure way to compare long integers */ -/* Returns one if left == right, zero otherwise. */ -uECC_VLI_API uECC_word_t uECC_vli_equal(const uECC_word_t *left, - const uECC_word_t *right, - wordcount_t num_words) { - uECC_word_t diff = 0; - wordcount_t i; - for (i = num_words - 1; i >= 0; --i) { - diff |= (left[i] ^ right[i]); - } - return (diff == 0); -} - -uECC_VLI_API uECC_word_t uECC_vli_sub(uECC_word_t *result, - const uECC_word_t *left, - const uECC_word_t *right, - wordcount_t num_words); - -/* Returns sign of left - right, in constant time. */ -uECC_VLI_API cmpresult_t uECC_vli_cmp(const uECC_word_t *left, - const uECC_word_t *right, - wordcount_t num_words) { - uECC_word_t tmp[uECC_MAX_WORDS]; - uECC_word_t neg = !!uECC_vli_sub(tmp, left, right, num_words); - uECC_word_t equal = uECC_vli_isZero(tmp, num_words); - return (!equal - 2 * neg); -} - -/* Computes vli = vli >> 1. */ -#if !asm_rshift1 -uECC_VLI_API void uECC_vli_rshift1(uECC_word_t *vli, wordcount_t num_words) { - uECC_word_t *end = vli; - uECC_word_t carry = 0; - - vli += num_words; - while (vli-- > end) { - uECC_word_t temp = *vli; - *vli = (temp >> 1) | carry; - carry = temp << (uECC_WORD_BITS - 1); - } -} -#endif /* !asm_rshift1 */ - -/* Computes result = left + right, returning carry. Can modify in place. */ -#if !asm_add -uECC_VLI_API uECC_word_t uECC_vli_add(uECC_word_t *result, - const uECC_word_t *left, - const uECC_word_t *right, - wordcount_t num_words) { - uECC_word_t carry = 0; - wordcount_t i; - for (i = 0; i < num_words; ++i) { - uECC_word_t sum = left[i] + right[i] + carry; - if (sum != left[i]) { - carry = (sum < left[i]); - } - result[i] = sum; - } - return carry; -} -#endif /* !asm_add */ - -/* Computes result = left - right, returning borrow. Can modify in place. */ -#if !asm_sub -uECC_VLI_API uECC_word_t uECC_vli_sub(uECC_word_t *result, - const uECC_word_t *left, - const uECC_word_t *right, - wordcount_t num_words) { - uECC_word_t borrow = 0; - wordcount_t i; - for (i = 0; i < num_words; ++i) { - uECC_word_t diff = left[i] - right[i] - borrow; - if (diff != left[i]) { - borrow = (diff > left[i]); - } - result[i] = diff; - } - return borrow; -} -#endif /* !asm_sub */ - -#if !asm_mult || (uECC_SQUARE_FUNC && !asm_square) || \ - (uECC_SUPPORTS_secp256k1 && (uECC_OPTIMIZATION_LEVEL > 0) && \ - ((uECC_WORD_SIZE == 1) || (uECC_WORD_SIZE == 8))) -static void muladd(uECC_word_t a, - uECC_word_t b, - uECC_word_t *r0, - uECC_word_t *r1, - uECC_word_t *r2) { -#if uECC_WORD_SIZE == 8 && !SUPPORTS_INT128 - uint64_t a0 = a & 0xffffffffull; - uint64_t a1 = a >> 32; - uint64_t b0 = b & 0xffffffffull; - uint64_t b1 = b >> 32; - - uint64_t i0 = a0 * b0; - uint64_t i1 = a0 * b1; - uint64_t i2 = a1 * b0; - uint64_t i3 = a1 * b1; - - uint64_t p0, p1; - - i2 += (i0 >> 32); - i2 += i1; - if (i2 < i1) { /* overflow */ - i3 += 0x100000000ull; - } - - p0 = (i0 & 0xffffffffull) | (i2 << 32); - p1 = i3 + (i2 >> 32); - - *r0 += p0; - *r1 += (p1 + (*r0 < p0)); - *r2 += ((*r1 < p1) || (*r1 == p1 && *r0 < p0)); -#else - uECC_dword_t p = (uECC_dword_t)a * b; - uECC_dword_t r01 = ((uECC_dword_t)(*r1) << uECC_WORD_BITS) | *r0; - r01 += p; - *r2 += (r01 < p); - *r1 = r01 >> uECC_WORD_BITS; - *r0 = (uECC_word_t)r01; -#endif -} -#endif /* muladd needed */ - -#if !asm_mult -uECC_VLI_API void uECC_vli_mult(uECC_word_t *result, - const uECC_word_t *left, - const uECC_word_t *right, - wordcount_t num_words) { - uECC_word_t r0 = 0; - uECC_word_t r1 = 0; - uECC_word_t r2 = 0; - wordcount_t i, k; - - /* Compute each digit of result in sequence, maintaining the carries. */ - for (k = 0; k < num_words; ++k) { - for (i = 0; i <= k; ++i) { - muladd(left[i], right[k - i], &r0, &r1, &r2); - } - result[k] = r0; - r0 = r1; - r1 = r2; - r2 = 0; - } - for (k = num_words; k < num_words * 2 - 1; ++k) { - for (i = (k + 1) - num_words; i < num_words; ++i) { - muladd(left[i], right[k - i], &r0, &r1, &r2); - } - result[k] = r0; - r0 = r1; - r1 = r2; - r2 = 0; - } - result[num_words * 2 - 1] = r0; -} -#endif /* !asm_mult */ - -#if uECC_SQUARE_FUNC - -#if !asm_square -static void mul2add(uECC_word_t a, - uECC_word_t b, - uECC_word_t *r0, - uECC_word_t *r1, - uECC_word_t *r2) { -#if uECC_WORD_SIZE == 8 && !SUPPORTS_INT128 - uint64_t a0 = a & 0xffffffffull; - uint64_t a1 = a >> 32; - uint64_t b0 = b & 0xffffffffull; - uint64_t b1 = b >> 32; - - uint64_t i0 = a0 * b0; - uint64_t i1 = a0 * b1; - uint64_t i2 = a1 * b0; - uint64_t i3 = a1 * b1; - - uint64_t p0, p1; - - i2 += (i0 >> 32); - i2 += i1; - if (i2 < i1) - { /* overflow */ - i3 += 0x100000000ull; - } - - p0 = (i0 & 0xffffffffull) | (i2 << 32); - p1 = i3 + (i2 >> 32); - - *r2 += (p1 >> 63); - p1 = (p1 << 1) | (p0 >> 63); - p0 <<= 1; - - *r0 += p0; - *r1 += (p1 + (*r0 < p0)); - *r2 += ((*r1 < p1) || (*r1 == p1 && *r0 < p0)); -#else - uECC_dword_t p = (uECC_dword_t)a * b; - uECC_dword_t r01 = ((uECC_dword_t)(*r1) << uECC_WORD_BITS) | *r0; - *r2 += (p >> (uECC_WORD_BITS * 2 - 1)); - p *= 2; - r01 += p; - *r2 += (r01 < p); - *r1 = r01 >> uECC_WORD_BITS; - *r0 = (uECC_word_t)r01; -#endif -} - -uECC_VLI_API void uECC_vli_square(uECC_word_t *result, - const uECC_word_t *left, - wordcount_t num_words) { - uECC_word_t r0 = 0; - uECC_word_t r1 = 0; - uECC_word_t r2 = 0; - - wordcount_t i, k; - - for (k = 0; k < num_words * 2 - 1; ++k) { - uECC_word_t min = (k < num_words ? 0 : (k + 1) - num_words); - for (i = min; i <= k && i <= k - i; ++i) { - if (i < k-i) { - mul2add(left[i], left[k - i], &r0, &r1, &r2); - } else { - muladd(left[i], left[k - i], &r0, &r1, &r2); - } - } - result[k] = r0; - r0 = r1; - r1 = r2; - r2 = 0; - } - - result[num_words * 2 - 1] = r0; -} -#endif /* !asm_square */ - -#else /* uECC_SQUARE_FUNC */ - -#if uECC_ENABLE_VLI_API -uECC_VLI_API void uECC_vli_square(uECC_word_t *result, - const uECC_word_t *left, - wordcount_t num_words) { - uECC_vli_mult(result, left, left, num_words); -} -#endif /* uECC_ENABLE_VLI_API */ - -#endif /* uECC_SQUARE_FUNC */ - -/* Computes result = (left + right) % mod. - Assumes that left < mod and right < mod, and that result does not overlap mod. */ -uECC_VLI_API void uECC_vli_modAdd(uECC_word_t *result, - const uECC_word_t *left, - const uECC_word_t *right, - const uECC_word_t *mod, - wordcount_t num_words) { - uECC_word_t carry = uECC_vli_add(result, left, right, num_words); - if (carry || uECC_vli_cmp_unsafe(mod, result, num_words) != 1) { - /* result > mod (result = mod + remainder), so subtract mod to get remainder. */ - uECC_vli_sub(result, result, mod, num_words); - } -} - -/* Computes result = (left - right) % mod. - Assumes that left < mod and right < mod, and that result does not overlap mod. */ -uECC_VLI_API void uECC_vli_modSub(uECC_word_t *result, - const uECC_word_t *left, - const uECC_word_t *right, - const uECC_word_t *mod, - wordcount_t num_words) { - uECC_word_t l_borrow = uECC_vli_sub(result, left, right, num_words); - if (l_borrow) { - /* In this case, result == -diff == (max int) - diff. Since -x % d == d - x, - we can get the correct result from result + mod (with overflow). */ - uECC_vli_add(result, result, mod, num_words); - } -} - -/* Computes result = product % mod, where product is 2N words long. */ -/* Currently only designed to work for curve_p or curve_n. */ -uECC_VLI_API void uECC_vli_mmod(uECC_word_t *result, - uECC_word_t *product, - const uECC_word_t *mod, - wordcount_t num_words) { - uECC_word_t mod_multiple[2 * uECC_MAX_WORDS]; - uECC_word_t tmp[2 * uECC_MAX_WORDS]; - uECC_word_t *v[2] = {tmp, product}; - uECC_word_t index; - - /* Shift mod so its highest set bit is at the maximum position. */ - bitcount_t shift = (num_words * 2 * uECC_WORD_BITS) - uECC_vli_numBits(mod, num_words); - wordcount_t word_shift = shift / uECC_WORD_BITS; - wordcount_t bit_shift = shift % uECC_WORD_BITS; - uECC_word_t carry = 0; - uECC_vli_clear(mod_multiple, word_shift); - if (bit_shift > 0) { - for(index = 0; index < (uECC_word_t)num_words; ++index) { - mod_multiple[word_shift + index] = (mod[index] << bit_shift) | carry; - carry = mod[index] >> (uECC_WORD_BITS - bit_shift); - } - } else { - uECC_vli_set(mod_multiple + word_shift, mod, num_words); - } - - for (index = 1; shift >= 0; --shift) { - uECC_word_t borrow = 0; - wordcount_t i; - for (i = 0; i < num_words * 2; ++i) { - uECC_word_t diff = v[index][i] - mod_multiple[i] - borrow; - if (diff != v[index][i]) { - borrow = (diff > v[index][i]); - } - v[1 - index][i] = diff; - } - index = !(index ^ borrow); /* Swap the index if there was no borrow */ - uECC_vli_rshift1(mod_multiple, num_words); - mod_multiple[num_words - 1] |= mod_multiple[num_words] << (uECC_WORD_BITS - 1); - uECC_vli_rshift1(mod_multiple + num_words, num_words); - } - uECC_vli_set(result, v[index], num_words); -} - -/* Computes result = (left * right) % mod. */ -uECC_VLI_API void uECC_vli_modMult(uECC_word_t *result, - const uECC_word_t *left, - const uECC_word_t *right, - const uECC_word_t *mod, - wordcount_t num_words) { - uECC_word_t product[2 * uECC_MAX_WORDS]; - uECC_vli_mult(product, left, right, num_words); - uECC_vli_mmod(result, product, mod, num_words); -} - -uECC_VLI_API void uECC_vli_modMult_fast(uECC_word_t *result, - const uECC_word_t *left, - const uECC_word_t *right, - uECC_Curve curve) { - uECC_word_t product[2 * uECC_MAX_WORDS]; - uECC_vli_mult(product, left, right, curve->num_words); -#if (uECC_OPTIMIZATION_LEVEL > 0) - curve->mmod_fast(result, product); -#else - uECC_vli_mmod(result, product, curve->p, curve->num_words); -#endif -} - -#if uECC_SQUARE_FUNC - -#if uECC_ENABLE_VLI_API -/* Computes result = left^2 % mod. */ -uECC_VLI_API void uECC_vli_modSquare(uECC_word_t *result, - const uECC_word_t *left, - const uECC_word_t *mod, - wordcount_t num_words) { - uECC_word_t product[2 * uECC_MAX_WORDS]; - uECC_vli_square(product, left, num_words); - uECC_vli_mmod(result, product, mod, num_words); -} -#endif /* uECC_ENABLE_VLI_API */ - -uECC_VLI_API void uECC_vli_modSquare_fast(uECC_word_t *result, - const uECC_word_t *left, - uECC_Curve curve) { - uECC_word_t product[2 * uECC_MAX_WORDS]; - uECC_vli_square(product, left, curve->num_words); -#if (uECC_OPTIMIZATION_LEVEL > 0) - curve->mmod_fast(result, product); -#else - uECC_vli_mmod(result, product, curve->p, curve->num_words); -#endif -} - -#else /* uECC_SQUARE_FUNC */ - -#if uECC_ENABLE_VLI_API -uECC_VLI_API void uECC_vli_modSquare(uECC_word_t *result, - const uECC_word_t *left, - const uECC_word_t *mod, - wordcount_t num_words) { - uECC_vli_modMult(result, left, left, mod, num_words); -} -#endif /* uECC_ENABLE_VLI_API */ - -uECC_VLI_API void uECC_vli_modSquare_fast(uECC_word_t *result, - const uECC_word_t *left, - uECC_Curve curve) { - uECC_vli_modMult_fast(result, left, left, curve); -} - -#endif /* uECC_SQUARE_FUNC */ - -#define EVEN(vli) (!(vli[0] & 1)) -static void vli_modInv_update(uECC_word_t *uv, - const uECC_word_t *mod, - wordcount_t num_words) { - uECC_word_t carry = 0; - if (!EVEN(uv)) { - carry = uECC_vli_add(uv, uv, mod, num_words); - } - uECC_vli_rshift1(uv, num_words); - if (carry) { - uv[num_words - 1] |= HIGH_BIT_SET; - } -} - -/* Computes result = (1 / input) % mod. All VLIs are the same size. - See "From Euclid's GCD to Montgomery Multiplication to the Great Divide" */ -uECC_VLI_API void uECC_vli_modInv(uECC_word_t *result, - const uECC_word_t *input, - const uECC_word_t *mod, - wordcount_t num_words) { - uECC_word_t a[uECC_MAX_WORDS], b[uECC_MAX_WORDS], u[uECC_MAX_WORDS], v[uECC_MAX_WORDS]; - cmpresult_t cmpResult; - - if (uECC_vli_isZero(input, num_words)) { - uECC_vli_clear(result, num_words); - return; - } - - uECC_vli_set(a, input, num_words); - uECC_vli_set(b, mod, num_words); - uECC_vli_clear(u, num_words); - u[0] = 1; - uECC_vli_clear(v, num_words); - while ((cmpResult = uECC_vli_cmp_unsafe(a, b, num_words)) != 0) { - if (EVEN(a)) { - uECC_vli_rshift1(a, num_words); - vli_modInv_update(u, mod, num_words); - } else if (EVEN(b)) { - uECC_vli_rshift1(b, num_words); - vli_modInv_update(v, mod, num_words); - } else if (cmpResult > 0) { - uECC_vli_sub(a, a, b, num_words); - uECC_vli_rshift1(a, num_words); - if (uECC_vli_cmp_unsafe(u, v, num_words) < 0) { - uECC_vli_add(u, u, mod, num_words); - } - uECC_vli_sub(u, u, v, num_words); - vli_modInv_update(u, mod, num_words); - } else { - uECC_vli_sub(b, b, a, num_words); - uECC_vli_rshift1(b, num_words); - if (uECC_vli_cmp_unsafe(v, u, num_words) < 0) { - uECC_vli_add(v, v, mod, num_words); - } - uECC_vli_sub(v, v, u, num_words); - vli_modInv_update(v, mod, num_words); - } - } - uECC_vli_set(result, u, num_words); -} - -/* ------ Point operations ------ */ - -#include "curve-specific.inc" - -/* Returns 1 if 'point' is the point at infinity, 0 otherwise. */ -#define EccPoint_isZero(point, curve) uECC_vli_isZero((point), (curve)->num_words * 2) - -/* Point multiplication algorithm using Montgomery's ladder with co-Z coordinates. -From http://eprint.iacr.org/2011/338.pdf -*/ - -/* Modify (x1, y1) => (x1 * z^2, y1 * z^3) */ -static void apply_z(uECC_word_t * X1, - uECC_word_t * Y1, - const uECC_word_t * const Z, - uECC_Curve curve) { - uECC_word_t t1[uECC_MAX_WORDS]; - - uECC_vli_modSquare_fast(t1, Z, curve); /* z^2 */ - uECC_vli_modMult_fast(X1, X1, t1, curve); /* x1 * z^2 */ - uECC_vli_modMult_fast(t1, t1, Z, curve); /* z^3 */ - uECC_vli_modMult_fast(Y1, Y1, t1, curve); /* y1 * z^3 */ -} - -/* P = (x1, y1) => 2P, (x2, y2) => P' */ -static void XYcZ_initial_double(uECC_word_t * X1, - uECC_word_t * Y1, - uECC_word_t * X2, - uECC_word_t * Y2, - const uECC_word_t * const initial_Z, - uECC_Curve curve) { - uECC_word_t z[uECC_MAX_WORDS]; - wordcount_t num_words = curve->num_words; - if (initial_Z) { - uECC_vli_set(z, initial_Z, num_words); - } else { - uECC_vli_clear(z, num_words); - z[0] = 1; - } - - uECC_vli_set(X2, X1, num_words); - uECC_vli_set(Y2, Y1, num_words); - - apply_z(X1, Y1, z, curve); - curve->double_jacobian(X1, Y1, z, curve); - apply_z(X2, Y2, z, curve); -} - -/* Input P = (x1, y1, Z), Q = (x2, y2, Z) - Output P' = (x1', y1', Z3), P + Q = (x3, y3, Z3) - or P => P', Q => P + Q -*/ -static void XYcZ_add(uECC_word_t * X1, - uECC_word_t * Y1, - uECC_word_t * X2, - uECC_word_t * Y2, - uECC_Curve curve) { - /* t1 = X1, t2 = Y1, t3 = X2, t4 = Y2 */ - uECC_word_t t5[uECC_MAX_WORDS]; - wordcount_t num_words = curve->num_words; - - uECC_vli_modSub(t5, X2, X1, curve->p, num_words); /* t5 = x2 - x1 */ - uECC_vli_modSquare_fast(t5, t5, curve); /* t5 = (x2 - x1)^2 = A */ - uECC_vli_modMult_fast(X1, X1, t5, curve); /* t1 = x1*A = B */ - uECC_vli_modMult_fast(X2, X2, t5, curve); /* t3 = x2*A = C */ - uECC_vli_modSub(Y2, Y2, Y1, curve->p, num_words); /* t4 = y2 - y1 */ - uECC_vli_modSquare_fast(t5, Y2, curve); /* t5 = (y2 - y1)^2 = D */ - - uECC_vli_modSub(t5, t5, X1, curve->p, num_words); /* t5 = D - B */ - uECC_vli_modSub(t5, t5, X2, curve->p, num_words); /* t5 = D - B - C = x3 */ - uECC_vli_modSub(X2, X2, X1, curve->p, num_words); /* t3 = C - B */ - uECC_vli_modMult_fast(Y1, Y1, X2, curve); /* t2 = y1*(C - B) */ - uECC_vli_modSub(X2, X1, t5, curve->p, num_words); /* t3 = B - x3 */ - uECC_vli_modMult_fast(Y2, Y2, X2, curve); /* t4 = (y2 - y1)*(B - x3) */ - uECC_vli_modSub(Y2, Y2, Y1, curve->p, num_words); /* t4 = y3 */ - - uECC_vli_set(X2, t5, num_words); -} - -/* Input P = (x1, y1, Z), Q = (x2, y2, Z) - Output P + Q = (x3, y3, Z3), P - Q = (x3', y3', Z3) - or P => P - Q, Q => P + Q -*/ -static void XYcZ_addC(uECC_word_t * X1, - uECC_word_t * Y1, - uECC_word_t * X2, - uECC_word_t * Y2, - uECC_Curve curve) { - /* t1 = X1, t2 = Y1, t3 = X2, t4 = Y2 */ - uECC_word_t t5[uECC_MAX_WORDS]; - uECC_word_t t6[uECC_MAX_WORDS]; - uECC_word_t t7[uECC_MAX_WORDS]; - wordcount_t num_words = curve->num_words; - - uECC_vli_modSub(t5, X2, X1, curve->p, num_words); /* t5 = x2 - x1 */ - uECC_vli_modSquare_fast(t5, t5, curve); /* t5 = (x2 - x1)^2 = A */ - uECC_vli_modMult_fast(X1, X1, t5, curve); /* t1 = x1*A = B */ - uECC_vli_modMult_fast(X2, X2, t5, curve); /* t3 = x2*A = C */ - uECC_vli_modAdd(t5, Y2, Y1, curve->p, num_words); /* t5 = y2 + y1 */ - uECC_vli_modSub(Y2, Y2, Y1, curve->p, num_words); /* t4 = y2 - y1 */ - - uECC_vli_modSub(t6, X2, X1, curve->p, num_words); /* t6 = C - B */ - uECC_vli_modMult_fast(Y1, Y1, t6, curve); /* t2 = y1 * (C - B) = E */ - uECC_vli_modAdd(t6, X1, X2, curve->p, num_words); /* t6 = B + C */ - uECC_vli_modSquare_fast(X2, Y2, curve); /* t3 = (y2 - y1)^2 = D */ - uECC_vli_modSub(X2, X2, t6, curve->p, num_words); /* t3 = D - (B + C) = x3 */ - - uECC_vli_modSub(t7, X1, X2, curve->p, num_words); /* t7 = B - x3 */ - uECC_vli_modMult_fast(Y2, Y2, t7, curve); /* t4 = (y2 - y1)*(B - x3) */ - uECC_vli_modSub(Y2, Y2, Y1, curve->p, num_words); /* t4 = (y2 - y1)*(B - x3) - E = y3 */ - - uECC_vli_modSquare_fast(t7, t5, curve); /* t7 = (y2 + y1)^2 = F */ - uECC_vli_modSub(t7, t7, t6, curve->p, num_words); /* t7 = F - (B + C) = x3' */ - uECC_vli_modSub(t6, t7, X1, curve->p, num_words); /* t6 = x3' - B */ - uECC_vli_modMult_fast(t6, t6, t5, curve); /* t6 = (y2+y1)*(x3' - B) */ - uECC_vli_modSub(Y1, t6, Y1, curve->p, num_words); /* t2 = (y2+y1)*(x3' - B) - E = y3' */ - - uECC_vli_set(X1, t7, num_words); -} - -/* result may overlap point. */ -static void EccPoint_mult(uECC_word_t * result, - const uECC_word_t * point, - const uECC_word_t * scalar, - const uECC_word_t * initial_Z, - bitcount_t num_bits, - uECC_Curve curve) { - /* R0 and R1 */ - uECC_word_t Rx[2][uECC_MAX_WORDS]; - uECC_word_t Ry[2][uECC_MAX_WORDS]; - uECC_word_t z[uECC_MAX_WORDS]; - bitcount_t i; - uECC_word_t nb; - wordcount_t num_words = curve->num_words; - - uECC_vli_set(Rx[1], point, num_words); - uECC_vli_set(Ry[1], point + num_words, num_words); - - XYcZ_initial_double(Rx[1], Ry[1], Rx[0], Ry[0], initial_Z, curve); - - for (i = num_bits - 2; i > 0; --i) { - nb = !uECC_vli_testBit(scalar, i); - XYcZ_addC(Rx[1 - nb], Ry[1 - nb], Rx[nb], Ry[nb], curve); - XYcZ_add(Rx[nb], Ry[nb], Rx[1 - nb], Ry[1 - nb], curve); - } - - nb = !uECC_vli_testBit(scalar, 0); - XYcZ_addC(Rx[1 - nb], Ry[1 - nb], Rx[nb], Ry[nb], curve); - - /* Find final 1/Z value. */ - uECC_vli_modSub(z, Rx[1], Rx[0], curve->p, num_words); /* X1 - X0 */ - uECC_vli_modMult_fast(z, z, Ry[1 - nb], curve); /* Yb * (X1 - X0) */ - uECC_vli_modMult_fast(z, z, point, curve); /* xP * Yb * (X1 - X0) */ - uECC_vli_modInv(z, z, curve->p, num_words); /* 1 / (xP * Yb * (X1 - X0)) */ - /* yP / (xP * Yb * (X1 - X0)) */ - uECC_vli_modMult_fast(z, z, point + num_words, curve); - uECC_vli_modMult_fast(z, z, Rx[1 - nb], curve); /* Xb * yP / (xP * Yb * (X1 - X0)) */ - /* End 1/Z calculation */ - - XYcZ_add(Rx[nb], Ry[nb], Rx[1 - nb], Ry[1 - nb], curve); - apply_z(Rx[0], Ry[0], z, curve); - - uECC_vli_set(result, Rx[0], num_words); - uECC_vli_set(result + num_words, Ry[0], num_words); -} - -static uECC_word_t regularize_k(const uECC_word_t * const k, - uECC_word_t *k0, - uECC_word_t *k1, - uECC_Curve curve) { - wordcount_t num_n_words = BITS_TO_WORDS(curve->num_n_bits); - bitcount_t num_n_bits = curve->num_n_bits; - uECC_word_t carry = uECC_vli_add(k0, k, curve->n, num_n_words) || - (num_n_bits < ((bitcount_t)num_n_words * uECC_WORD_SIZE * 8) && - uECC_vli_testBit(k0, num_n_bits)); - uECC_vli_add(k1, k0, curve->n, num_n_words); - return carry; -} - -/* Generates a random integer in the range 0 < random < top. - Both random and top have num_words words. */ -uECC_VLI_API int uECC_generate_random_int(uECC_word_t *random, - const uECC_word_t *top, - wordcount_t num_words) { - uECC_word_t mask = (uECC_word_t)-1; - uECC_word_t tries; - bitcount_t num_bits = uECC_vli_numBits(top, num_words); - - if (!g_rng_function) { - return 0; - } - - for (tries = 0; tries < uECC_RNG_MAX_TRIES; ++tries) { - if (!g_rng_function((uint8_t *)random, num_words * uECC_WORD_SIZE)) { - return 0; - } - random[num_words - 1] &= mask >> ((bitcount_t)(num_words * uECC_WORD_SIZE * 8 - num_bits)); - if (!uECC_vli_isZero(random, num_words) && - uECC_vli_cmp(top, random, num_words) == 1) { - return 1; - } - } - return 0; -} - -static uECC_word_t EccPoint_compute_public_key(uECC_word_t *result, - uECC_word_t *private_key, - uECC_Curve curve) { - uECC_word_t tmp1[uECC_MAX_WORDS]; - uECC_word_t tmp2[uECC_MAX_WORDS]; - uECC_word_t *p2[2] = {tmp1, tmp2}; - uECC_word_t *initial_Z = 0; - uECC_word_t carry; - - /* Regularize the bitcount for the private key so that attackers cannot use a side channel - attack to learn the number of leading zeros. */ - carry = regularize_k(private_key, tmp1, tmp2, curve); - - /* If an RNG function was specified, try to get a random initial Z value to improve - protection against side-channel attacks. */ - if (g_rng_function) { - if (!uECC_generate_random_int(p2[carry], curve->p, curve->num_words)) { - return 0; - } - initial_Z = p2[carry]; - } - EccPoint_mult(result, curve->G, p2[!carry], initial_Z, curve->num_n_bits + 1, curve); - - if (EccPoint_isZero(result, curve)) { - return 0; - } - return 1; -} - -#if uECC_WORD_SIZE == 1 - -uECC_VLI_API void uECC_vli_nativeToBytes(uint8_t *bytes, - int num_bytes, - const uint8_t *native) { - wordcount_t i; - for (i = 0; i < num_bytes; ++i) { - bytes[i] = native[(num_bytes - 1) - i]; - } -} - -uECC_VLI_API void uECC_vli_bytesToNative(uint8_t *native, - const uint8_t *bytes, - int num_bytes) { - uECC_vli_nativeToBytes(native, num_bytes, bytes); -} - -#else - -uECC_VLI_API void uECC_vli_nativeToBytes(uint8_t *bytes, - int num_bytes, - const uECC_word_t *native) { - int i; - for (i = 0; i < num_bytes; ++i) { - unsigned b = num_bytes - 1 - i; - bytes[i] = native[b / uECC_WORD_SIZE] >> (8 * (b % uECC_WORD_SIZE)); - } -} - -uECC_VLI_API void uECC_vli_bytesToNative(uECC_word_t *native, - const uint8_t *bytes, - int num_bytes) { - int i; - uECC_vli_clear(native, (num_bytes + (uECC_WORD_SIZE - 1)) / uECC_WORD_SIZE); - for (i = 0; i < num_bytes; ++i) { - unsigned b = num_bytes - 1 - i; - native[b / uECC_WORD_SIZE] |= - (uECC_word_t)bytes[i] << (8 * (b % uECC_WORD_SIZE)); - } -} - -#endif /* uECC_WORD_SIZE */ - -int uECC_make_key(uint8_t *public_key, - uint8_t *private_key, - uECC_Curve curve) { -#if uECC_VLI_NATIVE_LITTLE_ENDIAN - uECC_word_t *_private = (uECC_word_t *)private_key; - uECC_word_t *_public = (uECC_word_t *)public_key; -#else - uECC_word_t _private[uECC_MAX_WORDS]; - uECC_word_t _public[uECC_MAX_WORDS * 2]; -#endif - uECC_word_t tries; - - for (tries = 0; tries < uECC_RNG_MAX_TRIES; ++tries) { - if (!uECC_generate_random_int(_private, curve->n, BITS_TO_WORDS(curve->num_n_bits))) { - return 0; - } - - if (EccPoint_compute_public_key(_public, _private, curve)) { -#if uECC_VLI_NATIVE_LITTLE_ENDIAN == 0 - uECC_vli_nativeToBytes(private_key, BITS_TO_BYTES(curve->num_n_bits), _private); - uECC_vli_nativeToBytes(public_key, curve->num_bytes, _public); - uECC_vli_nativeToBytes( - public_key + curve->num_bytes, curve->num_bytes, _public + curve->num_words); -#endif - return 1; - } - } - return 0; -} - -int uECC_shared_secret(const uint8_t *public_key, - const uint8_t *private_key, - uint8_t *secret, - uECC_Curve curve) { - uECC_word_t _public[uECC_MAX_WORDS * 2]; - uECC_word_t _private[uECC_MAX_WORDS]; - - uECC_word_t tmp[uECC_MAX_WORDS]; - uECC_word_t *p2[2] = {_private, tmp}; - uECC_word_t *initial_Z = 0; - uECC_word_t carry; - wordcount_t num_words = curve->num_words; - wordcount_t num_bytes = curve->num_bytes; - -#if uECC_VLI_NATIVE_LITTLE_ENDIAN - bcopy((uint8_t *) _private, private_key, num_bytes); - bcopy((uint8_t *) _public, public_key, num_bytes*2); -#else - uECC_vli_bytesToNative(_private, private_key, BITS_TO_BYTES(curve->num_n_bits)); - uECC_vli_bytesToNative(_public, public_key, num_bytes); - uECC_vli_bytesToNative(_public + num_words, public_key + num_bytes, num_bytes); -#endif - - /* Regularize the bitcount for the private key so that attackers cannot use a side channel - attack to learn the number of leading zeros. */ - carry = regularize_k(_private, _private, tmp, curve); - - /* If an RNG function was specified, try to get a random initial Z value to improve - protection against side-channel attacks. */ - if (g_rng_function) { - if (!uECC_generate_random_int(p2[carry], curve->p, num_words)) { - return 0; - } - initial_Z = p2[carry]; - } - - EccPoint_mult(_public, _public, p2[!carry], initial_Z, curve->num_n_bits + 1, curve); -#if uECC_VLI_NATIVE_LITTLE_ENDIAN - bcopy((uint8_t *) secret, (uint8_t *) _public, num_bytes); -#else - uECC_vli_nativeToBytes(secret, num_bytes, _public); -#endif - return !EccPoint_isZero(_public, curve); -} - -#if uECC_SUPPORT_COMPRESSED_POINT -void uECC_compress(const uint8_t *public_key, uint8_t *compressed, uECC_Curve curve) { - wordcount_t i; - for (i = 0; i < curve->num_bytes; ++i) { - compressed[i+1] = public_key[i]; - } -#if uECC_VLI_NATIVE_LITTLE_ENDIAN - compressed[0] = 2 + (public_key[curve->num_bytes] & 0x01); -#else - compressed[0] = 2 + (public_key[curve->num_bytes * 2 - 1] & 0x01); -#endif -} - -void uECC_decompress(const uint8_t *compressed, uint8_t *public_key, uECC_Curve curve) { -#if uECC_VLI_NATIVE_LITTLE_ENDIAN - uECC_word_t *point = (uECC_word_t *)public_key; -#else - uECC_word_t point[uECC_MAX_WORDS * 2]; -#endif - uECC_word_t *y = point + curve->num_words; -#if uECC_VLI_NATIVE_LITTLE_ENDIAN - bcopy(public_key, compressed+1, curve->num_bytes); -#else - uECC_vli_bytesToNative(point, compressed + 1, curve->num_bytes); -#endif - curve->x_side(y, point, curve); - curve->mod_sqrt(y, curve); - - if ((y[0] & 0x01) != (compressed[0] & 0x01)) { - uECC_vli_sub(y, curve->p, y, curve->num_words); - } - -#if uECC_VLI_NATIVE_LITTLE_ENDIAN == 0 - uECC_vli_nativeToBytes(public_key, curve->num_bytes, point); - uECC_vli_nativeToBytes(public_key + curve->num_bytes, curve->num_bytes, y); -#endif -} -#endif /* uECC_SUPPORT_COMPRESSED_POINT */ - -uECC_VLI_API int uECC_valid_point(const uECC_word_t *point, uECC_Curve curve) { - uECC_word_t tmp1[uECC_MAX_WORDS]; - uECC_word_t tmp2[uECC_MAX_WORDS]; - wordcount_t num_words = curve->num_words; - - /* The point at infinity is invalid. */ - if (EccPoint_isZero(point, curve)) { - return 0; - } - - /* x and y must be smaller than p. */ - if (uECC_vli_cmp_unsafe(curve->p, point, num_words) != 1 || - uECC_vli_cmp_unsafe(curve->p, point + num_words, num_words) != 1) { - return 0; - } - - uECC_vli_modSquare_fast(tmp1, point + num_words, curve); - curve->x_side(tmp2, point, curve); /* tmp2 = x^3 + ax + b */ - - /* Make sure that y^2 == x^3 + ax + b */ - return (int)(uECC_vli_equal(tmp1, tmp2, num_words)); -} - -int uECC_valid_public_key(const uint8_t *public_key, uECC_Curve curve) { -#if uECC_VLI_NATIVE_LITTLE_ENDIAN - uECC_word_t *_public = (uECC_word_t *)public_key; -#else - uECC_word_t _public[uECC_MAX_WORDS * 2]; -#endif - -#if uECC_VLI_NATIVE_LITTLE_ENDIAN == 0 - uECC_vli_bytesToNative(_public, public_key, curve->num_bytes); - uECC_vli_bytesToNative( - _public + curve->num_words, public_key + curve->num_bytes, curve->num_bytes); -#endif - return uECC_valid_point(_public, curve); -} - -int uECC_compute_public_key(const uint8_t *private_key, uint8_t *public_key, uECC_Curve curve) { -#if uECC_VLI_NATIVE_LITTLE_ENDIAN - uECC_word_t *_private = (uECC_word_t *)private_key; - uECC_word_t *_public = (uECC_word_t *)public_key; -#else - uECC_word_t _private[uECC_MAX_WORDS]; - uECC_word_t _public[uECC_MAX_WORDS * 2]; -#endif - -#if uECC_VLI_NATIVE_LITTLE_ENDIAN == 0 - uECC_vli_bytesToNative(_private, private_key, BITS_TO_BYTES(curve->num_n_bits)); -#endif - - /* Make sure the private key is in the range [1, n-1]. */ - if (uECC_vli_isZero(_private, BITS_TO_WORDS(curve->num_n_bits))) { - return 0; - } - - if (uECC_vli_cmp(curve->n, _private, BITS_TO_WORDS(curve->num_n_bits)) != 1) { - return 0; - } - - /* Compute public key. */ - if (!EccPoint_compute_public_key(_public, _private, curve)) { - return 0; - } - -#if uECC_VLI_NATIVE_LITTLE_ENDIAN == 0 - uECC_vli_nativeToBytes(public_key, curve->num_bytes, _public); - uECC_vli_nativeToBytes( - public_key + curve->num_bytes, curve->num_bytes, _public + curve->num_words); -#endif - return 1; -} - - -/* -------- ECDSA code -------- */ - -static void bits2int(uECC_word_t *native, - const uint8_t *bits, - unsigned bits_size, - uECC_Curve curve) { - unsigned num_n_bytes = BITS_TO_BYTES(curve->num_n_bits); - unsigned num_n_words = BITS_TO_WORDS(curve->num_n_bits); - int shift; - uECC_word_t carry; - uECC_word_t *ptr; - - if (bits_size > num_n_bytes) { - bits_size = num_n_bytes; - } - - uECC_vli_clear(native, num_n_words); -#if uECC_VLI_NATIVE_LITTLE_ENDIAN - bcopy((uint8_t *) native, bits, bits_size); -#else - uECC_vli_bytesToNative(native, bits, bits_size); -#endif - if (bits_size * 8 <= (unsigned)curve->num_n_bits) { - return; - } - shift = bits_size * 8 - curve->num_n_bits; - carry = 0; - ptr = native + num_n_words; - while (ptr-- > native) { - uECC_word_t temp = *ptr; - *ptr = (temp >> shift) | carry; - carry = temp << (uECC_WORD_BITS - shift); - } - - /* Reduce mod curve_n */ - if (uECC_vli_cmp_unsafe(curve->n, native, num_n_words) != 1) { - uECC_vli_sub(native, native, curve->n, num_n_words); - } -} - -static int uECC_sign_with_k_internal(const uint8_t *private_key, - const uint8_t *message_hash, - unsigned hash_size, - uECC_word_t *k, - uint8_t *signature, - uECC_Curve curve) { - - uECC_word_t tmp[uECC_MAX_WORDS]; - uECC_word_t s[uECC_MAX_WORDS]; - uECC_word_t *k2[2] = {tmp, s}; - uECC_word_t *initial_Z = 0; -#if uECC_VLI_NATIVE_LITTLE_ENDIAN - uECC_word_t *p = (uECC_word_t *)signature; -#else - uECC_word_t p[uECC_MAX_WORDS * 2]; -#endif - uECC_word_t carry; - wordcount_t num_words = curve->num_words; - wordcount_t num_n_words = BITS_TO_WORDS(curve->num_n_bits); - bitcount_t num_n_bits = curve->num_n_bits; - - /* Make sure 0 < k < curve_n */ - if (uECC_vli_isZero(k, num_words) || uECC_vli_cmp(curve->n, k, num_n_words) != 1) { - return 0; - } - - carry = regularize_k(k, tmp, s, curve); - /* If an RNG function was specified, try to get a random initial Z value to improve - protection against side-channel attacks. */ - if (g_rng_function) { - if (!uECC_generate_random_int(k2[carry], curve->p, num_words)) { - return 0; - } - initial_Z = k2[carry]; - } - EccPoint_mult(p, curve->G, k2[!carry], initial_Z, num_n_bits + 1, curve); - if (uECC_vli_isZero(p, num_words)) { - return 0; - } - - /* If an RNG function was specified, get a random number - to prevent side channel analysis of k. */ - if (!g_rng_function) { - uECC_vli_clear(tmp, num_n_words); - tmp[0] = 1; - } else if (!uECC_generate_random_int(tmp, curve->n, num_n_words)) { - return 0; - } - - /* Prevent side channel analysis of uECC_vli_modInv() to determine - bits of k / the private key by premultiplying by a random number */ - uECC_vli_modMult(k, k, tmp, curve->n, num_n_words); /* k' = rand * k */ - uECC_vli_modInv(k, k, curve->n, num_n_words); /* k = 1 / k' */ - uECC_vli_modMult(k, k, tmp, curve->n, num_n_words); /* k = 1 / k */ - -#if uECC_VLI_NATIVE_LITTLE_ENDIAN == 0 - uECC_vli_nativeToBytes(signature, curve->num_bytes, p); /* store r */ -#endif - -#if uECC_VLI_NATIVE_LITTLE_ENDIAN - bcopy((uint8_t *) tmp, private_key, BITS_TO_BYTES(curve->num_n_bits)); -#else - uECC_vli_bytesToNative(tmp, private_key, BITS_TO_BYTES(curve->num_n_bits)); /* tmp = d */ -#endif - - s[num_n_words - 1] = 0; - uECC_vli_set(s, p, num_words); - uECC_vli_modMult(s, tmp, s, curve->n, num_n_words); /* s = r*d */ - - bits2int(tmp, message_hash, hash_size, curve); - uECC_vli_modAdd(s, tmp, s, curve->n, num_n_words); /* s = e + r*d */ - uECC_vli_modMult(s, s, k, curve->n, num_n_words); /* s = (e + r*d) / k */ - if (uECC_vli_numBits(s, num_n_words) > (bitcount_t)curve->num_bytes * 8) { - return 0; - } -#if uECC_VLI_NATIVE_LITTLE_ENDIAN - bcopy((uint8_t *) signature + curve->num_bytes, (uint8_t *) s, curve->num_bytes); -#else - uECC_vli_nativeToBytes(signature + curve->num_bytes, curve->num_bytes, s); -#endif - return 1; -} - -/* For testing - sign with an explicitly specified k value */ -int uECC_sign_with_k(const uint8_t *private_key, - const uint8_t *message_hash, - unsigned hash_size, - const uint8_t *k, - uint8_t *signature, - uECC_Curve curve) { - uECC_word_t k2[uECC_MAX_WORDS]; - bits2int(k2, k, BITS_TO_BYTES(curve->num_n_bits), curve); - return uECC_sign_with_k_internal(private_key, message_hash, hash_size, k2, signature, curve); -} - -int uECC_sign(const uint8_t *private_key, - const uint8_t *message_hash, - unsigned hash_size, - uint8_t *signature, - uECC_Curve curve) { - uECC_word_t k[uECC_MAX_WORDS]; - uECC_word_t tries; - - for (tries = 0; tries < uECC_RNG_MAX_TRIES; ++tries) { - if (!uECC_generate_random_int(k, curve->n, BITS_TO_WORDS(curve->num_n_bits))) { - return 0; - } - - if (uECC_sign_with_k_internal(private_key, message_hash, hash_size, k, signature, curve)) { - return 1; - } - } - return 0; -} - -/* Compute an HMAC using K as a key (as in RFC 6979). Note that K is always - the same size as the hash result size. */ -static void HMAC_init(const uECC_HashContext *hash_context, const uint8_t *K) { - uint8_t *pad = hash_context->tmp + 2 * hash_context->result_size; - unsigned i; - for (i = 0; i < hash_context->result_size; ++i) - pad[i] = K[i] ^ 0x36; - for (; i < hash_context->block_size; ++i) - pad[i] = 0x36; - - hash_context->init_hash(hash_context); - hash_context->update_hash(hash_context, pad, hash_context->block_size); -} - -static void HMAC_update(const uECC_HashContext *hash_context, - const uint8_t *message, - unsigned message_size) { - hash_context->update_hash(hash_context, message, message_size); -} - -static void HMAC_finish(const uECC_HashContext *hash_context, - const uint8_t *K, - uint8_t *result) { - uint8_t *pad = hash_context->tmp + 2 * hash_context->result_size; - unsigned i; - for (i = 0; i < hash_context->result_size; ++i) - pad[i] = K[i] ^ 0x5c; - for (; i < hash_context->block_size; ++i) - pad[i] = 0x5c; - - hash_context->finish_hash(hash_context, result); - - hash_context->init_hash(hash_context); - hash_context->update_hash(hash_context, pad, hash_context->block_size); - hash_context->update_hash(hash_context, result, hash_context->result_size); - hash_context->finish_hash(hash_context, result); -} - -/* V = HMAC_K(V) */ -static void update_V(const uECC_HashContext *hash_context, uint8_t *K, uint8_t *V) { - HMAC_init(hash_context, K); - HMAC_update(hash_context, V, hash_context->result_size); - HMAC_finish(hash_context, K, V); -} - -/* Deterministic signing, similar to RFC 6979. Differences are: - * We just use H(m) directly rather than bits2octets(H(m)) - (it is not reduced modulo curve_n). - * We generate a value for k (aka T) directly rather than converting endianness. - - Layout of hash_context->tmp: | | (1 byte overlapped 0x00 or 0x01) / */ -int uECC_sign_deterministic(const uint8_t *private_key, - const uint8_t *message_hash, - unsigned hash_size, - const uECC_HashContext *hash_context, - uint8_t *signature, - uECC_Curve curve) { - uint8_t *K = hash_context->tmp; - uint8_t *V = K + hash_context->result_size; - wordcount_t num_bytes = curve->num_bytes; - wordcount_t num_n_words = BITS_TO_WORDS(curve->num_n_bits); - bitcount_t num_n_bits = curve->num_n_bits; - uECC_word_t tries; - unsigned i; - for (i = 0; i < hash_context->result_size; ++i) { - V[i] = 0x01; - K[i] = 0; - } - - /* K = HMAC_K(V || 0x00 || int2octets(x) || h(m)) */ - HMAC_init(hash_context, K); - V[hash_context->result_size] = 0x00; - HMAC_update(hash_context, V, hash_context->result_size + 1); - HMAC_update(hash_context, private_key, num_bytes); - HMAC_update(hash_context, message_hash, hash_size); - HMAC_finish(hash_context, K, K); - - update_V(hash_context, K, V); - - /* K = HMAC_K(V || 0x01 || int2octets(x) || h(m)) */ - HMAC_init(hash_context, K); - V[hash_context->result_size] = 0x01; - HMAC_update(hash_context, V, hash_context->result_size + 1); - HMAC_update(hash_context, private_key, num_bytes); - HMAC_update(hash_context, message_hash, hash_size); - HMAC_finish(hash_context, K, K); - - update_V(hash_context, K, V); - - for (tries = 0; tries < uECC_RNG_MAX_TRIES; ++tries) { - uECC_word_t T[uECC_MAX_WORDS]; - uint8_t *T_ptr = (uint8_t *)T; - wordcount_t T_bytes = 0; - for (;;) { - update_V(hash_context, K, V); - for (i = 0; i < hash_context->result_size; ++i) { - T_ptr[T_bytes++] = V[i]; - if (T_bytes >= num_n_words * uECC_WORD_SIZE) { - goto filled; - } - } - } - filled: - if ((bitcount_t)num_n_words * uECC_WORD_SIZE * 8 > num_n_bits) { - uECC_word_t mask = (uECC_word_t)-1; - T[num_n_words - 1] &= - mask >> ((bitcount_t)(num_n_words * uECC_WORD_SIZE * 8 - num_n_bits)); - } - - if (uECC_sign_with_k_internal(private_key, message_hash, hash_size, T, signature, curve)) { - return 1; - } - - /* K = HMAC_K(V || 0x00) */ - HMAC_init(hash_context, K); - V[hash_context->result_size] = 0x00; - HMAC_update(hash_context, V, hash_context->result_size + 1); - HMAC_finish(hash_context, K, K); - - update_V(hash_context, K, V); - } - return 0; -} - -static bitcount_t smax(bitcount_t a, bitcount_t b) { - return (a > b ? a : b); -} - -int uECC_verify(const uint8_t *public_key, - const uint8_t *message_hash, - unsigned hash_size, - const uint8_t *signature, - uECC_Curve curve) { - uECC_word_t u1[uECC_MAX_WORDS], u2[uECC_MAX_WORDS]; - uECC_word_t z[uECC_MAX_WORDS]; - uECC_word_t sum[uECC_MAX_WORDS * 2]; - uECC_word_t rx[uECC_MAX_WORDS]; - uECC_word_t ry[uECC_MAX_WORDS]; - uECC_word_t tx[uECC_MAX_WORDS]; - uECC_word_t ty[uECC_MAX_WORDS]; - uECC_word_t tz[uECC_MAX_WORDS]; - const uECC_word_t *points[4]; - const uECC_word_t *point; - bitcount_t num_bits; - bitcount_t i; -#if uECC_VLI_NATIVE_LITTLE_ENDIAN - uECC_word_t *_public = (uECC_word_t *)public_key; -#else - uECC_word_t _public[uECC_MAX_WORDS * 2]; -#endif - uECC_word_t r[uECC_MAX_WORDS], s[uECC_MAX_WORDS]; - wordcount_t num_words = curve->num_words; - wordcount_t num_n_words = BITS_TO_WORDS(curve->num_n_bits); - - rx[num_n_words - 1] = 0; - r[num_n_words - 1] = 0; - s[num_n_words - 1] = 0; - -#if uECC_VLI_NATIVE_LITTLE_ENDIAN - bcopy((uint8_t *) r, signature, curve->num_bytes); - bcopy((uint8_t *) s, signature + curve->num_bytes, curve->num_bytes); -#else - uECC_vli_bytesToNative(_public, public_key, curve->num_bytes); - uECC_vli_bytesToNative( - _public + num_words, public_key + curve->num_bytes, curve->num_bytes); - uECC_vli_bytesToNative(r, signature, curve->num_bytes); - uECC_vli_bytesToNative(s, signature + curve->num_bytes, curve->num_bytes); -#endif - - /* r, s must not be 0. */ - if (uECC_vli_isZero(r, num_words) || uECC_vli_isZero(s, num_words)) { - return 0; - } - - /* r, s must be < n. */ - if (uECC_vli_cmp_unsafe(curve->n, r, num_n_words) != 1 || - uECC_vli_cmp_unsafe(curve->n, s, num_n_words) != 1) { - return 0; - } - - /* Calculate u1 and u2. */ - uECC_vli_modInv(z, s, curve->n, num_n_words); /* z = 1/s */ - u1[num_n_words - 1] = 0; - bits2int(u1, message_hash, hash_size, curve); - uECC_vli_modMult(u1, u1, z, curve->n, num_n_words); /* u1 = e/s */ - uECC_vli_modMult(u2, r, z, curve->n, num_n_words); /* u2 = r/s */ - - /* Calculate sum = G + Q. */ - uECC_vli_set(sum, _public, num_words); - uECC_vli_set(sum + num_words, _public + num_words, num_words); - uECC_vli_set(tx, curve->G, num_words); - uECC_vli_set(ty, curve->G + num_words, num_words); - uECC_vli_modSub(z, sum, tx, curve->p, num_words); /* z = x2 - x1 */ - XYcZ_add(tx, ty, sum, sum + num_words, curve); - uECC_vli_modInv(z, z, curve->p, num_words); /* z = 1/z */ - apply_z(sum, sum + num_words, z, curve); - - /* Use Shamir's trick to calculate u1*G + u2*Q */ - points[0] = 0; - points[1] = curve->G; - points[2] = _public; - points[3] = sum; - num_bits = smax(uECC_vli_numBits(u1, num_n_words), - uECC_vli_numBits(u2, num_n_words)); - - point = points[(!!uECC_vli_testBit(u1, num_bits - 1)) | - ((!!uECC_vli_testBit(u2, num_bits - 1)) << 1)]; - uECC_vli_set(rx, point, num_words); - uECC_vli_set(ry, point + num_words, num_words); - uECC_vli_clear(z, num_words); - z[0] = 1; - - for (i = num_bits - 2; i >= 0; --i) { - uECC_word_t index; - curve->double_jacobian(rx, ry, z, curve); - - index = (!!uECC_vli_testBit(u1, i)) | ((!!uECC_vli_testBit(u2, i)) << 1); - point = points[index]; - if (point) { - uECC_vli_set(tx, point, num_words); - uECC_vli_set(ty, point + num_words, num_words); - apply_z(tx, ty, z, curve); - uECC_vli_modSub(tz, rx, tx, curve->p, num_words); /* Z = x2 - x1 */ - XYcZ_add(tx, ty, rx, ry, curve); - uECC_vli_modMult_fast(z, z, tz, curve); - } - } - - uECC_vli_modInv(z, z, curve->p, num_words); /* Z = 1/Z */ - apply_z(rx, ry, z, curve); - - /* v = x1 (mod n) */ - if (uECC_vli_cmp_unsafe(curve->n, rx, num_n_words) != 1) { - uECC_vli_sub(rx, rx, curve->n, num_n_words); - } - - /* Accept only if v == r. */ - return (int)(uECC_vli_equal(rx, r, num_words)); -} - -#if uECC_ENABLE_VLI_API - -unsigned uECC_curve_num_words(uECC_Curve curve) { - return curve->num_words; -} - -unsigned uECC_curve_num_bytes(uECC_Curve curve) { - return curve->num_bytes; -} - -unsigned uECC_curve_num_bits(uECC_Curve curve) { - return curve->num_bytes * 8; -} - -unsigned uECC_curve_num_n_words(uECC_Curve curve) { - return BITS_TO_WORDS(curve->num_n_bits); -} - -unsigned uECC_curve_num_n_bytes(uECC_Curve curve) { - return BITS_TO_BYTES(curve->num_n_bits); -} - -unsigned uECC_curve_num_n_bits(uECC_Curve curve) { - return curve->num_n_bits; -} - -const uECC_word_t *uECC_curve_p(uECC_Curve curve) { - return curve->p; -} - -const uECC_word_t *uECC_curve_n(uECC_Curve curve) { - return curve->n; -} - -const uECC_word_t *uECC_curve_G(uECC_Curve curve) { - return curve->G; -} - -const uECC_word_t *uECC_curve_b(uECC_Curve curve) { - return curve->b; -} - -#if uECC_SUPPORT_COMPRESSED_POINT -void uECC_vli_mod_sqrt(uECC_word_t *a, uECC_Curve curve) { - curve->mod_sqrt(a, curve); -} -#endif - -void uECC_vli_mmod_fast(uECC_word_t *result, uECC_word_t *product, uECC_Curve curve) { -#if (uECC_OPTIMIZATION_LEVEL > 0) - curve->mmod_fast(result, product); -#else - uECC_vli_mmod(result, product, curve->p, curve->num_words); -#endif -} - -void uECC_point_mult(uECC_word_t *result, - const uECC_word_t *point, - const uECC_word_t *scalar, - uECC_Curve curve) { - uECC_word_t tmp1[uECC_MAX_WORDS]; - uECC_word_t tmp2[uECC_MAX_WORDS]; - uECC_word_t *p2[2] = {tmp1, tmp2}; - uECC_word_t carry = regularize_k(scalar, tmp1, tmp2, curve); - - EccPoint_mult(result, point, p2[!carry], 0, curve->num_n_bits + 1, curve); -} - -#endif /* uECC_ENABLE_VLI_API */ diff --git a/lib/micro-ecc/uECC.h b/lib/micro-ecc/uECC.h deleted file mode 100644 index dcbdbfa8b42..00000000000 --- a/lib/micro-ecc/uECC.h +++ /dev/null @@ -1,367 +0,0 @@ -/* Copyright 2014, Kenneth MacKay. Licensed under the BSD 2-clause license. */ - -#ifndef _UECC_H_ -#define _UECC_H_ - -#include - -/* Platform selection options. -If uECC_PLATFORM is not defined, the code will try to guess it based on compiler macros. -Possible values for uECC_PLATFORM are defined below: */ -#define uECC_arch_other 0 -#define uECC_x86 1 -#define uECC_x86_64 2 -#define uECC_arm 3 -#define uECC_arm_thumb 4 -#define uECC_arm_thumb2 5 -#define uECC_arm64 6 -#define uECC_avr 7 - -/* If desired, you can define uECC_WORD_SIZE as appropriate for your platform (1, 4, or 8 bytes). -If uECC_WORD_SIZE is not explicitly defined then it will be automatically set based on your -platform. */ - -/* Optimization level; trade speed for code size. - Larger values produce code that is faster but larger. - Currently supported values are 0 - 4; 0 is unusably slow for most applications. - Optimization level 4 currently only has an effect ARM platforms where more than one - curve is enabled. */ -#ifndef uECC_OPTIMIZATION_LEVEL - #define uECC_OPTIMIZATION_LEVEL 2 -#endif - -/* uECC_SQUARE_FUNC - If enabled (defined as nonzero), this will cause a specific function to be -used for (scalar) squaring instead of the generic multiplication function. This can make things -faster somewhat faster, but increases the code size. */ -#ifndef uECC_SQUARE_FUNC - #define uECC_SQUARE_FUNC 0 -#endif - -/* uECC_VLI_NATIVE_LITTLE_ENDIAN - If enabled (defined as nonzero), this will switch to native -little-endian format for *all* arrays passed in and out of the public API. This includes public -and private keys, shared secrets, signatures and message hashes. -Using this switch reduces the amount of call stack memory used by uECC, since less intermediate -translations are required. -Note that this will *only* work on native little-endian processors and it will treat the uint8_t -arrays passed into the public API as word arrays, therefore requiring the provided byte arrays -to be word aligned on architectures that do not support unaligned accesses. -IMPORTANT: Keys and signatures generated with uECC_VLI_NATIVE_LITTLE_ENDIAN=1 are incompatible -with keys and signatures generated with uECC_VLI_NATIVE_LITTLE_ENDIAN=0; all parties must use -the same endianness. */ -#ifndef uECC_VLI_NATIVE_LITTLE_ENDIAN - #define uECC_VLI_NATIVE_LITTLE_ENDIAN 0 -#endif - -/* Curve support selection. Set to 0 to remove that curve. */ -#ifndef uECC_SUPPORTS_secp160r1 - #define uECC_SUPPORTS_secp160r1 1 -#endif -#ifndef uECC_SUPPORTS_secp192r1 - #define uECC_SUPPORTS_secp192r1 1 -#endif -#ifndef uECC_SUPPORTS_secp224r1 - #define uECC_SUPPORTS_secp224r1 1 -#endif -#ifndef uECC_SUPPORTS_secp256r1 - #define uECC_SUPPORTS_secp256r1 1 -#endif -#ifndef uECC_SUPPORTS_secp256k1 - #define uECC_SUPPORTS_secp256k1 1 -#endif - -/* Specifies whether compressed point format is supported. - Set to 0 to disable point compression/decompression functions. */ -#ifndef uECC_SUPPORT_COMPRESSED_POINT - #define uECC_SUPPORT_COMPRESSED_POINT 1 -#endif - -struct uECC_Curve_t; -typedef const struct uECC_Curve_t * uECC_Curve; - -#ifdef __cplusplus -extern "C" -{ -#endif - -#if uECC_SUPPORTS_secp160r1 -uECC_Curve uECC_secp160r1(void); -#endif -#if uECC_SUPPORTS_secp192r1 -uECC_Curve uECC_secp192r1(void); -#endif -#if uECC_SUPPORTS_secp224r1 -uECC_Curve uECC_secp224r1(void); -#endif -#if uECC_SUPPORTS_secp256r1 -uECC_Curve uECC_secp256r1(void); -#endif -#if uECC_SUPPORTS_secp256k1 -uECC_Curve uECC_secp256k1(void); -#endif - -/* uECC_RNG_Function type -The RNG function should fill 'size' random bytes into 'dest'. It should return 1 if -'dest' was filled with random data, or 0 if the random data could not be generated. -The filled-in values should be either truly random, or from a cryptographically-secure PRNG. - -A correctly functioning RNG function must be set (using uECC_set_rng()) before calling -uECC_make_key() or uECC_sign(). - -Setting a correctly functioning RNG function improves the resistance to side-channel attacks -for uECC_shared_secret() and uECC_sign_deterministic(). - -A correct RNG function is set by default when building for Windows, Linux, or OS X. -If you are building on another POSIX-compliant system that supports /dev/random or /dev/urandom, -you can define uECC_POSIX to use the predefined RNG. For embedded platforms there is no predefined -RNG function; you must provide your own. -*/ -typedef int (*uECC_RNG_Function)(uint8_t *dest, unsigned size); - -/* uECC_set_rng() function. -Set the function that will be used to generate random bytes. The RNG function should -return 1 if the random data was generated, or 0 if the random data could not be generated. - -On platforms where there is no predefined RNG function (eg embedded platforms), this must -be called before uECC_make_key() or uECC_sign() are used. - -Inputs: - rng_function - The function that will be used to generate random bytes. -*/ -void uECC_set_rng(uECC_RNG_Function rng_function); - -/* uECC_get_rng() function. - -Returns the function that will be used to generate random bytes. -*/ -uECC_RNG_Function uECC_get_rng(void); - -/* uECC_curve_private_key_size() function. - -Returns the size of a private key for the curve in bytes. -*/ -int uECC_curve_private_key_size(uECC_Curve curve); - -/* uECC_curve_public_key_size() function. - -Returns the size of a public key for the curve in bytes. -*/ -int uECC_curve_public_key_size(uECC_Curve curve); - -/* uECC_make_key() function. -Create a public/private key pair. - -Outputs: - public_key - Will be filled in with the public key. Must be at least 2 * the curve size - (in bytes) long. For example, if the curve is secp256r1, public_key must be 64 - bytes long. - private_key - Will be filled in with the private key. Must be as long as the curve order; this - is typically the same as the curve size, except for secp160r1. For example, if the - curve is secp256r1, private_key must be 32 bytes long. - - For secp160r1, private_key must be 21 bytes long! Note that the first byte will - almost always be 0 (there is about a 1 in 2^80 chance of it being non-zero). - -Returns 1 if the key pair was generated successfully, 0 if an error occurred. -*/ -int uECC_make_key(uint8_t *public_key, uint8_t *private_key, uECC_Curve curve); - -/* uECC_shared_secret() function. -Compute a shared secret given your secret key and someone else's public key. If the public key -is not from a trusted source and has not been previously verified, you should verify it first -using uECC_valid_public_key(). -Note: It is recommended that you hash the result of uECC_shared_secret() before using it for -symmetric encryption or HMAC. - -Inputs: - public_key - The public key of the remote party. - private_key - Your private key. - -Outputs: - secret - Will be filled in with the shared secret value. Must be the same size as the - curve size; for example, if the curve is secp256r1, secret must be 32 bytes long. - -Returns 1 if the shared secret was generated successfully, 0 if an error occurred. -*/ -int uECC_shared_secret(const uint8_t *public_key, - const uint8_t *private_key, - uint8_t *secret, - uECC_Curve curve); - -#if uECC_SUPPORT_COMPRESSED_POINT -/* uECC_compress() function. -Compress a public key. - -Inputs: - public_key - The public key to compress. - -Outputs: - compressed - Will be filled in with the compressed public key. Must be at least - (curve size + 1) bytes long; for example, if the curve is secp256r1, - compressed must be 33 bytes long. -*/ -void uECC_compress(const uint8_t *public_key, uint8_t *compressed, uECC_Curve curve); - -/* uECC_decompress() function. -Decompress a compressed public key. - -Inputs: - compressed - The compressed public key. - -Outputs: - public_key - Will be filled in with the decompressed public key. -*/ -void uECC_decompress(const uint8_t *compressed, uint8_t *public_key, uECC_Curve curve); -#endif /* uECC_SUPPORT_COMPRESSED_POINT */ - -/* uECC_valid_public_key() function. -Check to see if a public key is valid. - -Note that you are not required to check for a valid public key before using any other uECC -functions. However, you may wish to avoid spending CPU time computing a shared secret or -verifying a signature using an invalid public key. - -Inputs: - public_key - The public key to check. - -Returns 1 if the public key is valid, 0 if it is invalid. -*/ -int uECC_valid_public_key(const uint8_t *public_key, uECC_Curve curve); - -/* uECC_compute_public_key() function. -Compute the corresponding public key for a private key. - -Inputs: - private_key - The private key to compute the public key for - -Outputs: - public_key - Will be filled in with the corresponding public key - -Returns 1 if the key was computed successfully, 0 if an error occurred. -*/ -int uECC_compute_public_key(const uint8_t *private_key, uint8_t *public_key, uECC_Curve curve); - -/* uECC_sign() function. -Generate an ECDSA signature for a given hash value. - -Usage: Compute a hash of the data you wish to sign (SHA-2 is recommended) and pass it in to -this function along with your private key. - -Inputs: - private_key - Your private key. - message_hash - The hash of the message to sign. - hash_size - The size of message_hash in bytes. - -Outputs: - signature - Will be filled in with the signature value. Must be at least 2 * curve size long. - For example, if the curve is secp256r1, signature must be 64 bytes long. - -Returns 1 if the signature generated successfully, 0 if an error occurred. -*/ -int uECC_sign(const uint8_t *private_key, - const uint8_t *message_hash, - unsigned hash_size, - uint8_t *signature, - uECC_Curve curve); - -/* uECC_HashContext structure. -This is used to pass in an arbitrary hash function to uECC_sign_deterministic(). -The structure will be used for multiple hash computations; each time a new hash -is computed, init_hash() will be called, followed by one or more calls to -update_hash(), and finally a call to finish_hash() to produce the resulting hash. - -The intention is that you will create a structure that includes uECC_HashContext -followed by any hash-specific data. For example: - -typedef struct SHA256_HashContext { - uECC_HashContext uECC; - SHA256_CTX ctx; -} SHA256_HashContext; - -void init_SHA256(uECC_HashContext *base) { - SHA256_HashContext *context = (SHA256_HashContext *)base; - SHA256_Init(&context->ctx); -} - -void update_SHA256(uECC_HashContext *base, - const uint8_t *message, - unsigned message_size) { - SHA256_HashContext *context = (SHA256_HashContext *)base; - SHA256_Update(&context->ctx, message, message_size); -} - -void finish_SHA256(uECC_HashContext *base, uint8_t *hash_result) { - SHA256_HashContext *context = (SHA256_HashContext *)base; - SHA256_Final(hash_result, &context->ctx); -} - -... when signing ... -{ - uint8_t tmp[32 + 32 + 64]; - SHA256_HashContext ctx = {{&init_SHA256, &update_SHA256, &finish_SHA256, 64, 32, tmp}}; - uECC_sign_deterministic(key, message_hash, &ctx.uECC, signature); -} -*/ -typedef struct uECC_HashContext { - void (*init_hash)(const struct uECC_HashContext *context); - void (*update_hash)(const struct uECC_HashContext *context, - const uint8_t *message, - unsigned message_size); - void (*finish_hash)(const struct uECC_HashContext *context, uint8_t *hash_result); - unsigned block_size; /* Hash function block size in bytes, eg 64 for SHA-256. */ - unsigned result_size; /* Hash function result size in bytes, eg 32 for SHA-256. */ - uint8_t *tmp; /* Must point to a buffer of at least (2 * result_size + block_size) bytes. */ -} uECC_HashContext; - -/* uECC_sign_deterministic() function. -Generate an ECDSA signature for a given hash value, using a deterministic algorithm -(see RFC 6979). You do not need to set the RNG using uECC_set_rng() before calling -this function; however, if the RNG is defined it will improve resistance to side-channel -attacks. - -Usage: Compute a hash of the data you wish to sign (SHA-2 is recommended) and pass it to -this function along with your private key and a hash context. Note that the message_hash -does not need to be computed with the same hash function used by hash_context. - -Inputs: - private_key - Your private key. - message_hash - The hash of the message to sign. - hash_size - The size of message_hash in bytes. - hash_context - A hash context to use. - -Outputs: - signature - Will be filled in with the signature value. - -Returns 1 if the signature generated successfully, 0 if an error occurred. -*/ -int uECC_sign_deterministic(const uint8_t *private_key, - const uint8_t *message_hash, - unsigned hash_size, - const uECC_HashContext *hash_context, - uint8_t *signature, - uECC_Curve curve); - -/* uECC_verify() function. -Verify an ECDSA signature. - -Usage: Compute the hash of the signed data using the same hash as the signer and -pass it to this function along with the signer's public key and the signature values (r and s). - -Inputs: - public_key - The signer's public key. - message_hash - The hash of the signed data. - hash_size - The size of message_hash in bytes. - signature - The signature value. - -Returns 1 if the signature is valid, 0 if it is invalid. -*/ -int uECC_verify(const uint8_t *public_key, - const uint8_t *message_hash, - unsigned hash_size, - const uint8_t *signature, - uECC_Curve curve); - -#ifdef __cplusplus -} /* end of extern "C" */ -#endif - -#endif /* _UECC_H_ */ diff --git a/lib/micro-ecc/uECC_vli.h b/lib/micro-ecc/uECC_vli.h deleted file mode 100644 index 864cc333569..00000000000 --- a/lib/micro-ecc/uECC_vli.h +++ /dev/null @@ -1,172 +0,0 @@ -/* Copyright 2015, Kenneth MacKay. Licensed under the BSD 2-clause license. */ - -#ifndef _UECC_VLI_H_ -#define _UECC_VLI_H_ - -#include "uECC.h" -#include "types.h" - -/* Functions for raw large-integer manipulation. These are only available - if uECC.c is compiled with uECC_ENABLE_VLI_API defined to 1. */ -#ifndef uECC_ENABLE_VLI_API - #define uECC_ENABLE_VLI_API 0 -#endif - -#ifdef __cplusplus -extern "C" -{ -#endif - -#if uECC_ENABLE_VLI_API - -void uECC_vli_clear(uECC_word_t *vli, wordcount_t num_words); - -/* Constant-time comparison to zero - secure way to compare long integers */ -/* Returns 1 if vli == 0, 0 otherwise. */ -uECC_word_t uECC_vli_isZero(const uECC_word_t *vli, wordcount_t num_words); - -/* Returns nonzero if bit 'bit' of vli is set. */ -uECC_word_t uECC_vli_testBit(const uECC_word_t *vli, bitcount_t bit); - -/* Counts the number of bits required to represent vli. */ -bitcount_t uECC_vli_numBits(const uECC_word_t *vli, const wordcount_t max_words); - -/* Sets dest = src. */ -void uECC_vli_set(uECC_word_t *dest, const uECC_word_t *src, wordcount_t num_words); - -/* Constant-time comparison function - secure way to compare long integers */ -/* Returns one if left == right, zero otherwise */ -uECC_word_t uECC_vli_equal(const uECC_word_t *left, - const uECC_word_t *right, - wordcount_t num_words); - -/* Constant-time comparison function - secure way to compare long integers */ -/* Returns sign of left - right, in constant time. */ -cmpresult_t uECC_vli_cmp(const uECC_word_t *left, const uECC_word_t *right, wordcount_t num_words); - -/* Computes vli = vli >> 1. */ -void uECC_vli_rshift1(uECC_word_t *vli, wordcount_t num_words); - -/* Computes result = left + right, returning carry. Can modify in place. */ -uECC_word_t uECC_vli_add(uECC_word_t *result, - const uECC_word_t *left, - const uECC_word_t *right, - wordcount_t num_words); - -/* Computes result = left - right, returning borrow. Can modify in place. */ -uECC_word_t uECC_vli_sub(uECC_word_t *result, - const uECC_word_t *left, - const uECC_word_t *right, - wordcount_t num_words); - -/* Computes result = left * right. Result must be 2 * num_words long. */ -void uECC_vli_mult(uECC_word_t *result, - const uECC_word_t *left, - const uECC_word_t *right, - wordcount_t num_words); - -/* Computes result = left^2. Result must be 2 * num_words long. */ -void uECC_vli_square(uECC_word_t *result, const uECC_word_t *left, wordcount_t num_words); - -/* Computes result = (left + right) % mod. - Assumes that left < mod and right < mod, and that result does not overlap mod. */ -void uECC_vli_modAdd(uECC_word_t *result, - const uECC_word_t *left, - const uECC_word_t *right, - const uECC_word_t *mod, - wordcount_t num_words); - -/* Computes result = (left - right) % mod. - Assumes that left < mod and right < mod, and that result does not overlap mod. */ -void uECC_vli_modSub(uECC_word_t *result, - const uECC_word_t *left, - const uECC_word_t *right, - const uECC_word_t *mod, - wordcount_t num_words); - -/* Computes result = product % mod, where product is 2N words long. - Currently only designed to work for mod == curve->p or curve_n. */ -void uECC_vli_mmod(uECC_word_t *result, - uECC_word_t *product, - const uECC_word_t *mod, - wordcount_t num_words); - -/* Calculates result = product (mod curve->p), where product is up to - 2 * curve->num_words long. */ -void uECC_vli_mmod_fast(uECC_word_t *result, uECC_word_t *product, uECC_Curve curve); - -/* Computes result = (left * right) % mod. - Currently only designed to work for mod == curve->p or curve_n. */ -void uECC_vli_modMult(uECC_word_t *result, - const uECC_word_t *left, - const uECC_word_t *right, - const uECC_word_t *mod, - wordcount_t num_words); - -/* Computes result = (left * right) % curve->p. */ -void uECC_vli_modMult_fast(uECC_word_t *result, - const uECC_word_t *left, - const uECC_word_t *right, - uECC_Curve curve); - -/* Computes result = left^2 % mod. - Currently only designed to work for mod == curve->p or curve_n. */ -void uECC_vli_modSquare(uECC_word_t *result, - const uECC_word_t *left, - const uECC_word_t *mod, - wordcount_t num_words); - -/* Computes result = left^2 % curve->p. */ -void uECC_vli_modSquare_fast(uECC_word_t *result, const uECC_word_t *left, uECC_Curve curve); - -/* Computes result = (1 / input) % mod.*/ -void uECC_vli_modInv(uECC_word_t *result, - const uECC_word_t *input, - const uECC_word_t *mod, - wordcount_t num_words); - -#if uECC_SUPPORT_COMPRESSED_POINT -/* Calculates a = sqrt(a) (mod curve->p) */ -void uECC_vli_mod_sqrt(uECC_word_t *a, uECC_Curve curve); -#endif - -/* Converts an integer in uECC native format to big-endian bytes. */ -void uECC_vli_nativeToBytes(uint8_t *bytes, int num_bytes, const uECC_word_t *native); -/* Converts big-endian bytes to an integer in uECC native format. */ -void uECC_vli_bytesToNative(uECC_word_t *native, const uint8_t *bytes, int num_bytes); - -unsigned uECC_curve_num_words(uECC_Curve curve); -unsigned uECC_curve_num_bytes(uECC_Curve curve); -unsigned uECC_curve_num_bits(uECC_Curve curve); -unsigned uECC_curve_num_n_words(uECC_Curve curve); -unsigned uECC_curve_num_n_bytes(uECC_Curve curve); -unsigned uECC_curve_num_n_bits(uECC_Curve curve); - -const uECC_word_t *uECC_curve_p(uECC_Curve curve); -const uECC_word_t *uECC_curve_n(uECC_Curve curve); -const uECC_word_t *uECC_curve_G(uECC_Curve curve); -const uECC_word_t *uECC_curve_b(uECC_Curve curve); - -int uECC_valid_point(const uECC_word_t *point, uECC_Curve curve); - -/* Multiplies a point by a scalar. Points are represented by the X coordinate followed by - the Y coordinate in the same array, both coordinates are curve->num_words long. Note - that scalar must be curve->num_n_words long (NOT curve->num_words). */ -void uECC_point_mult(uECC_word_t *result, - const uECC_word_t *point, - const uECC_word_t *scalar, - uECC_Curve curve); - -/* Generates a random integer in the range 0 < random < top. - Both random and top have num_words words. */ -int uECC_generate_random_int(uECC_word_t *random, - const uECC_word_t *top, - wordcount_t num_words); - -#endif /* uECC_ENABLE_VLI_API */ - -#ifdef __cplusplus -} /* end of extern "C" */ -#endif - -#endif /* _UECC_VLI_H_ */ diff --git a/lib/microtar.scons b/lib/microtar.scons index 6ee36d403cd..54949fb42b6 100644 --- a/lib/microtar.scons +++ b/lib/microtar.scons @@ -14,7 +14,7 @@ libenv.Append( CPPDEFINES=["MICROTAR_DISABLE_API_CHECKS"], ) -sources = libenv.GlobRecursive("*.c", "microtar/src") +sources = [File("microtar/src/microtar.c")] lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources) libenv.Install("${LIB_DIST_DIR}", lib) diff --git a/lib/misc.scons b/lib/misc.scons deleted file mode 100644 index 92fa5a106ff..00000000000 --- a/lib/misc.scons +++ /dev/null @@ -1,58 +0,0 @@ -from fbt.util import GLOB_FILE_EXCLUSION - -Import("env") - -env.Append( - CPPPATH=[ - "#/lib/fnv1a_hash", - "#/lib/heatshrink", - "#/lib/micro-ecc", - "#/lib/nanopb", - "#/lib/u8g2", - ], - CPPDEFINES=[ - "PB_ENABLE_MALLOC", - ], - SDK_HEADERS=[ - File("micro-ecc/uECC.h"), - File("nanopb/pb.h"), - File("nanopb/pb_decode.h"), - File("nanopb/pb_encode.h"), - ], -) - - -libenv = env.Clone(FW_LIB_NAME="misc") -libenv.ApplyLibFlags() - -sources = [] - -libs_recurse = [ - "micro-ecc", - "u8g2", - "update_util", -] - -for lib in libs_recurse: - sources += libenv.GlobRecursive("*.c*", lib) - -libs_plain = [ - "nanopb", -] - -for lib in libs_plain: - sources += Glob( - lib + "/*.c*", - exclude=GLOB_FILE_EXCLUSION, - source=True, - ) - -sources += Glob( - "heatshrink/heatshrink_*.c*", - exclude=GLOB_FILE_EXCLUSION, - source=True, -) - -lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources) -libenv.Install("${LIB_DIST_DIR}", lib) -Return("lib") diff --git a/lib/mlib.scons b/lib/mlib.scons new file mode 100644 index 00000000000..2bdd3728955 --- /dev/null +++ b/lib/mlib.scons @@ -0,0 +1,27 @@ +Import("env") + +env.Append( + CPPPATH=[ + "#/lib/mlib", + ], + SDK_HEADERS=[ + *( + File(f"#/lib/mlib/m-{name}.h") + for name in ( + "algo", + "array", + "bptree", + "core", + "deque", + "dict", + "list", + "rbtree", + "tuple", + "variant", + ) + ), + ], + CPPDEFINES=[ + '"M_MEMORY_FULL(x)=abort()"', + ], +) diff --git a/lib/music_worker/SConscript b/lib/music_worker/SConscript index 36d01d8596a..0439286cebf 100644 --- a/lib/music_worker/SConscript +++ b/lib/music_worker/SConscript @@ -7,6 +7,9 @@ env.Append( SDK_HEADERS=[ File("music_worker.h"), ], + LINT_SOURCES=[ + Dir("."), + ], ) libenv = env.Clone(FW_LIB_NAME="music_worker") diff --git a/lib/nanopb.scons b/lib/nanopb.scons new file mode 100644 index 00000000000..43e828b85e6 --- /dev/null +++ b/lib/nanopb.scons @@ -0,0 +1,31 @@ +from fbt.util import GLOB_FILE_EXCLUSION + +Import("env") + +env.Append( + CPPPATH=[ + "#/lib/nanopb", + ], + CPPDEFINES=[ + "PB_ENABLE_MALLOC", + ], + SDK_HEADERS=[ + File("nanopb/pb.h"), + File("nanopb/pb_decode.h"), + File("nanopb/pb_encode.h"), + ], +) + + +libenv = env.Clone(FW_LIB_NAME="nanopb") +libenv.ApplyLibFlags() + +sources = Glob( + "nanopb/*.c*", + exclude=GLOB_FILE_EXCLUSION, + source=True, +) + +lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources) +libenv.Install("${LIB_DIST_DIR}", lib) +Return("lib") diff --git a/lib/nfc/SConscript b/lib/nfc/SConscript index 605a8639dd8..21f2fb49f6a 100644 --- a/lib/nfc/SConscript +++ b/lib/nfc/SConscript @@ -4,6 +4,9 @@ env.Append( CPPPATH=[ "#/lib/nfc", ], + LINT_SOURCES=[ + Dir("."), + ], SDK_HEADERS=[ # Main File("nfc.h"), diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_i.c b/lib/nfc/protocols/mf_desfire/mf_desfire_i.c index 129dcdf5e1d..8e65eca5a54 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_i.c +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_i.c @@ -56,7 +56,7 @@ bool mf_desfire_version_parse(MfDesfireVersion* data, const BitBuffer* buf) { } bool mf_desfire_free_memory_parse(MfDesfireFreeMemory* data, const BitBuffer* buf) { - typedef struct __attribute__((packed)) { + typedef struct FURI_PACKED { uint32_t bytes_free : 3 * BITS_IN_BYTE; } MfDesfireFreeMemoryLayout; @@ -74,7 +74,7 @@ bool mf_desfire_free_memory_parse(MfDesfireFreeMemory* data, const BitBuffer* bu } bool mf_desfire_key_settings_parse(MfDesfireKeySettings* data, const BitBuffer* buf) { - typedef struct __attribute__((packed)) { + typedef struct FURI_PACKED { bool is_master_key_changeable : 1; bool is_free_directory_list : 1; bool is_free_create_delete : 1; @@ -143,30 +143,30 @@ bool mf_desfire_file_id_parse(MfDesfireFileId* data, uint32_t index, const BitBu bool mf_desfire_file_settings_parse(MfDesfireFileSettings* data, const BitBuffer* buf) { bool parsed = false; - typedef struct __attribute__((packed)) { + typedef struct FURI_PACKED { uint8_t type; uint8_t comm; uint16_t access_rights; } MfDesfireFileSettingsHeader; - typedef struct __attribute__((packed)) { + typedef struct FURI_PACKED { uint32_t size : 3 * BITS_IN_BYTE; } MfDesfireFileSettingsData; - typedef struct __attribute__((packed)) { + typedef struct FURI_PACKED { uint32_t lo_limit; uint32_t hi_limit; uint32_t limited_credit_value; uint8_t limited_credit_enabled; } MfDesfireFileSettingsValue; - typedef struct __attribute__((packed)) { + typedef struct FURI_PACKED { uint32_t size : 3 * BITS_IN_BYTE; uint32_t max : 3 * BITS_IN_BYTE; uint32_t cur : 3 * BITS_IN_BYTE; } MfDesfireFileSettingsRecord; - typedef struct __attribute__((packed)) { + typedef struct FURI_PACKED { MfDesfireFileSettingsHeader header; union { MfDesfireFileSettingsData data; diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight.h b/lib/nfc/protocols/mf_ultralight/mf_ultralight.h index 747f5937ad2..4786b18253c 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight.h +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight.h @@ -130,7 +130,7 @@ typedef enum { MfUltralightMirrorUidCounter, } MfUltralightMirrorConf; -typedef struct __attribute__((packed)) { +typedef struct FURI_PACKED { union { uint8_t value; struct { diff --git a/lib/print/SConscript b/lib/print/SConscript index f34c8152fab..819e60bf07f 100644 --- a/lib/print/SConscript +++ b/lib/print/SConscript @@ -100,6 +100,9 @@ env.Append( SDK_HEADERS=[ File("wrappers.h"), ], + LINT_SOURCES=[ + Dir("."), + ], ) libenv = env.Clone(FW_LIB_NAME="print") diff --git a/lib/pulse_reader/SConscript b/lib/pulse_reader/SConscript index f00851a20d5..a134783798c 100644 --- a/lib/pulse_reader/SConscript +++ b/lib/pulse_reader/SConscript @@ -7,6 +7,9 @@ env.Append( SDK_HEADERS=[ File("pulse_reader.h"), ], + LINT_SOURCES=[ + Dir("."), + ], ) libenv = env.Clone(FW_LIB_NAME="pulse_reader") diff --git a/lib/qrcode/qrcode.c b/lib/qrcode/qrcode.c deleted file mode 100644 index fb5bd8a6e78..00000000000 --- a/lib/qrcode/qrcode.c +++ /dev/null @@ -1,975 +0,0 @@ -/** - * The MIT License (MIT) - * - * This library is written and maintained by Richard Moore. - * Major parts were derived from Project Nayuki's library. - * - * Copyright (c) 2017 Richard Moore (https://github.com/ricmoo/QRCode) - * Copyright (c) 2017 Project Nayuki (https://www.nayuki.io/page/qr-code-generator-library) - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -/** - * Special thanks to Nayuki (https://www.nayuki.io/) from which this library was - * heavily inspired and compared against. - * - * See: https://github.com/nayuki/QR-Code-generator/tree/master/cpp - */ - -#include "qrcode.h" - -#include -#include - -#pragma mark - Error Correction Lookup tables - -#if LOCK_VERSION == 0 - -static const uint16_t NUM_ERROR_CORRECTION_CODEWORDS[4][40] = { - // 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level - {10, 16, 26, 36, 48, 64, 72, 88, 110, 130, 150, 176, 198, 216, - 240, 280, 308, 338, 364, 416, 442, 476, 504, 560, 588, 644, 700, 728, - 784, 812, 868, 924, 980, 1036, 1064, 1120, 1204, 1260, 1316, 1372}, // Medium - {7, 10, 15, 20, 26, 36, 40, 48, 60, 72, 80, 96, 104, 120, - 132, 144, 168, 180, 196, 224, 224, 252, 270, 300, 312, 336, 360, 390, - 420, 450, 480, 510, 540, 570, 570, 600, 630, 660, 720, 750}, // Low - {17, 28, 44, 64, 88, 112, 130, 156, 192, 224, 264, 308, 352, 384, - 432, 480, 532, 588, 650, 700, 750, 816, 900, 960, 1050, 1110, 1200, 1260, - 1350, 1440, 1530, 1620, 1710, 1800, 1890, 1980, 2100, 2220, 2310, 2430}, // High - {13, 22, 36, 52, 72, 96, 108, 132, 160, 192, 224, 260, 288, 320, - 360, 408, 448, 504, 546, 600, 644, 690, 750, 810, 870, 952, 1020, 1050, - 1140, 1200, 1290, 1350, 1440, 1530, 1590, 1680, 1770, 1860, 1950, 2040}, // Quartile -}; - -static const uint8_t NUM_ERROR_CORRECTION_BLOCKS[4][40] = { - // Version: (note that index 0 is for padding, and is set to an illegal value) - // 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level - {1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, 9, 10, 10, 11, 13, 14, 16, - 17, 17, 18, 20, 21, 23, 25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49}, // Medium - {1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8, - 8, 9, 9, 10, 12, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25}, // Low - {1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, 16, 18, 16, 19, 21, 25, 25, - 25, 34, 30, 32, 35, 37, 40, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81}, // High - {1, 1, 2, 2, 4, 4, 6, 6, 8, 8, 8, 10, 12, 16, 12, 17, 16, 18, 21, 20, - 23, 23, 25, 27, 29, 34, 34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68}, // Quartile -}; - -static const uint16_t NUM_RAW_DATA_MODULES[40] = { - // 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, - 208, - 359, - 567, - 807, - 1079, - 1383, - 1568, - 1936, - 2336, - 2768, - 3232, - 3728, - 4256, - 4651, - 5243, - 5867, - 6523, - // 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, - 7211, - 7931, - 8683, - 9252, - 10068, - 10916, - 11796, - 12708, - 13652, - 14628, - 15371, - 16411, - 17483, - 18587, - // 32, 33, 34, 35, 36, 37, 38, 39, 40 - 19723, - 20891, - 22091, - 23008, - 24272, - 25568, - 26896, - 28256, - 29648}; - -// @TODO: Put other LOCK_VERSIONS here -#elif LOCK_VERSION == 3 - -static const int16_t NUM_ERROR_CORRECTION_CODEWORDS[4] = {26, 15, 44, 36}; - -static const int8_t NUM_ERROR_CORRECTION_BLOCKS[4] = {1, 1, 2, 2}; - -static const uint16_t NUM_RAW_DATA_MODULES = 567; - -#else - -#error Unsupported LOCK_VERSION (add it...) - -#endif - -static int max(int a, int b) { - if(a > b) { - return a; - } - return b; -} - -/* -static int abs(int value) { - if (value < 0) { return -value; } - return value; -} -*/ - -#pragma mark - Mode testing and conversion - -static int8_t getAlphanumeric(char c) { - if(c >= '0' && c <= '9') { - return (c - '0'); - } - if(c >= 'A' && c <= 'Z') { - return (c - 'A' + 10); - } - - switch(c) { - case ' ': - return 36; - case '$': - return 37; - case '%': - return 38; - case '*': - return 39; - case '+': - return 40; - case '-': - return 41; - case '.': - return 42; - case '/': - return 43; - case ':': - return 44; - } - - return -1; -} - -static bool isAlphanumeric(const char* text, uint16_t length) { - while(length != 0) { - if(getAlphanumeric(text[--length]) == -1) { - return false; - } - } - return true; -} - -static bool isNumeric(const char* text, uint16_t length) { - while(length != 0) { - char c = text[--length]; - if(c < '0' || c > '9') { - return false; - } - } - return true; -} - -#pragma mark - Counting - -// We store the following tightly packed (less 8) in modeInfo -// <=9 <=26 <= 40 -// NUMERIC ( 10, 12, 14); -// ALPHANUMERIC ( 9, 11, 13); -// BYTE ( 8, 16, 16); -static char getModeBits(uint8_t version, uint8_t mode) { - // Note: We use 15 instead of 16; since 15 doesn't exist and we cannot store 16 (8 + 8) in 3 bits - // hex(int("".join(reversed([('00' + bin(x - 8)[2:])[-3:] for x in [10, 9, 8, 12, 11, 15, 14, 13, 15]])), 2)) - unsigned int modeInfo = 0x7bbb80a; - -#if LOCK_VERSION == 0 || LOCK_VERSION > 9 - if(version > 9) { - modeInfo >>= 9; - } -#endif - -#if LOCK_VERSION == 0 || LOCK_VERSION > 26 - if(version > 26) { - modeInfo >>= 9; - } -#endif - - char result = 8 + ((modeInfo >> (3 * mode)) & 0x07); - if(result == 15) { - result = 16; - } - - return result; -} - -#pragma mark - BitBucket - -typedef struct BitBucket { - uint32_t bitOffsetOrWidth; - uint16_t capacityBytes; - uint8_t* data; -} BitBucket; - -/* -void bb_dump(BitBucket *bitBuffer) { - printf("Buffer: "); - for (uint32_t i = 0; i < bitBuffer->capacityBytes; i++) { - printf("%02x", bitBuffer->data[i]); - if ((i % 4) == 3) { printf(" "); } - } - printf("\n"); -} -*/ - -static uint16_t bb_getGridSizeBytes(uint8_t size) { - return (((size * size) + 7) / 8); -} - -static uint16_t bb_getBufferSizeBytes(uint32_t bits) { - return ((bits + 7) / 8); -} - -static void bb_initBuffer(BitBucket* bitBuffer, uint8_t* data, int32_t capacityBytes) { - bitBuffer->bitOffsetOrWidth = 0; - bitBuffer->capacityBytes = capacityBytes; - bitBuffer->data = data; - - memset(data, 0, bitBuffer->capacityBytes); -} - -static void bb_initGrid(BitBucket* bitGrid, uint8_t* data, uint8_t size) { - bitGrid->bitOffsetOrWidth = size; - bitGrid->capacityBytes = bb_getGridSizeBytes(size); - bitGrid->data = data; - - memset(data, 0, bitGrid->capacityBytes); -} - -static void bb_appendBits(BitBucket* bitBuffer, uint32_t val, uint8_t length) { - uint32_t offset = bitBuffer->bitOffsetOrWidth; - for(int8_t i = length - 1; i >= 0; i--, offset++) { - bitBuffer->data[offset >> 3] |= ((val >> i) & 1) << (7 - (offset & 7)); - } - bitBuffer->bitOffsetOrWidth = offset; -} -/* -void bb_setBits(BitBucket *bitBuffer, uint32_t val, int offset, uint8_t length) { - for (int8_t i = length - 1; i >= 0; i--, offset++) { - bitBuffer->data[offset >> 3] |= ((val >> i) & 1) << (7 - (offset & 7)); - } -} -*/ -static void bb_setBit(BitBucket* bitGrid, uint8_t x, uint8_t y, bool on) { - uint32_t offset = y * bitGrid->bitOffsetOrWidth + x; - uint8_t mask = 1 << (7 - (offset & 0x07)); - if(on) { - bitGrid->data[offset >> 3] |= mask; - } else { - bitGrid->data[offset >> 3] &= ~mask; - } -} - -static void bb_invertBit(BitBucket* bitGrid, uint8_t x, uint8_t y, bool invert) { - uint32_t offset = y * bitGrid->bitOffsetOrWidth + x; - uint8_t mask = 1 << (7 - (offset & 0x07)); - bool on = ((bitGrid->data[offset >> 3] & (1 << (7 - (offset & 0x07)))) != 0); - if(on ^ invert) { - bitGrid->data[offset >> 3] |= mask; - } else { - bitGrid->data[offset >> 3] &= ~mask; - } -} - -static bool bb_getBit(BitBucket* bitGrid, uint8_t x, uint8_t y) { - uint32_t offset = y * bitGrid->bitOffsetOrWidth + x; - return (bitGrid->data[offset >> 3] & (1 << (7 - (offset & 0x07)))) != 0; -} - -#pragma mark - Drawing Patterns - -// XORs the data modules in this QR Code with the given mask pattern. Due to XOR's mathematical -// properties, calling applyMask(m) twice with the same value is equivalent to no change at all. -// This means it is possible to apply a mask, undo it, and try another mask. Note that a final -// well-formed QR Code symbol needs exactly one mask applied (not zero, not two, etc.). -static void applyMask(BitBucket* modules, BitBucket* isFunction, uint8_t mask) { - uint8_t size = modules->bitOffsetOrWidth; - - for(uint8_t y = 0; y < size; y++) { - for(uint8_t x = 0; x < size; x++) { - if(bb_getBit(isFunction, x, y)) { - continue; - } - - bool invert = 0; - switch(mask) { - case 0: - invert = (x + y) % 2 == 0; - break; - case 1: - invert = y % 2 == 0; - break; - case 2: - invert = x % 3 == 0; - break; - case 3: - invert = (x + y) % 3 == 0; - break; - case 4: - invert = (x / 3 + y / 2) % 2 == 0; - break; - case 5: - invert = x * y % 2 + x * y % 3 == 0; - break; - case 6: - invert = (x * y % 2 + x * y % 3) % 2 == 0; - break; - case 7: - invert = ((x + y) % 2 + x * y % 3) % 2 == 0; - break; - } - bb_invertBit(modules, x, y, invert); - } - } -} - -static void - setFunctionModule(BitBucket* modules, BitBucket* isFunction, uint8_t x, uint8_t y, bool on) { - bb_setBit(modules, x, y, on); - bb_setBit(isFunction, x, y, true); -} - -// Draws a 9*9 finder pattern including the border separator, with the center module at (x, y). -static void drawFinderPattern(BitBucket* modules, BitBucket* isFunction, uint8_t x, uint8_t y) { - uint8_t size = modules->bitOffsetOrWidth; - - for(int8_t i = -4; i <= 4; i++) { - for(int8_t j = -4; j <= 4; j++) { - uint8_t dist = max(abs(i), abs(j)); // Chebyshev/infinity norm - int16_t xx = x + j, yy = y + i; - if(0 <= xx && xx < size && 0 <= yy && yy < size) { - setFunctionModule(modules, isFunction, xx, yy, dist != 2 && dist != 4); - } - } - } -} - -// Draws a 5*5 alignment pattern, with the center module at (x, y). -static void drawAlignmentPattern(BitBucket* modules, BitBucket* isFunction, uint8_t x, uint8_t y) { - for(int8_t i = -2; i <= 2; i++) { - for(int8_t j = -2; j <= 2; j++) { - setFunctionModule(modules, isFunction, x + j, y + i, max(abs(i), abs(j)) != 1); - } - } -} - -// Draws two copies of the format bits (with its own error correction code) -// based on the given mask and this object's error correction level field. -static void drawFormatBits(BitBucket* modules, BitBucket* isFunction, uint8_t ecc, uint8_t mask) { - uint8_t size = modules->bitOffsetOrWidth; - - // Calculate error correction code and pack bits - uint32_t data = ecc << 3 | mask; // errCorrLvl is uint2, mask is uint3 - uint32_t rem = data; - for(int i = 0; i < 10; i++) { - rem = (rem << 1) ^ ((rem >> 9) * 0x537); - } - - data = data << 10 | rem; - data ^= 0x5412; // uint15 - - // Draw first copy - for(uint8_t i = 0; i <= 5; i++) { - setFunctionModule(modules, isFunction, 8, i, ((data >> i) & 1) != 0); - } - - setFunctionModule(modules, isFunction, 8, 7, ((data >> 6) & 1) != 0); - setFunctionModule(modules, isFunction, 8, 8, ((data >> 7) & 1) != 0); - setFunctionModule(modules, isFunction, 7, 8, ((data >> 8) & 1) != 0); - - for(int8_t i = 9; i < 15; i++) { - setFunctionModule(modules, isFunction, 14 - i, 8, ((data >> i) & 1) != 0); - } - - // Draw second copy - for(int8_t i = 0; i <= 7; i++) { - setFunctionModule(modules, isFunction, size - 1 - i, 8, ((data >> i) & 1) != 0); - } - - for(int8_t i = 8; i < 15; i++) { - setFunctionModule(modules, isFunction, 8, size - 15 + i, ((data >> i) & 1) != 0); - } - - setFunctionModule(modules, isFunction, 8, size - 8, true); -} - -// Draws two copies of the version bits (with its own error correction code), -// based on this object's version field (which only has an effect for 7 <= version <= 40). -static void drawVersion(BitBucket* modules, BitBucket* isFunction, uint8_t version) { - int8_t size = modules->bitOffsetOrWidth; - -#if LOCK_VERSION != 0 && LOCK_VERSION < 7 - return; - -#else - if(version < 7) { - return; - } - - // Calculate error correction code and pack bits - uint32_t rem = version; // version is uint6, in the range [7, 40] - for(uint8_t i = 0; i < 12; i++) { - rem = (rem << 1) ^ ((rem >> 11) * 0x1F25); - } - - uint32_t data = version << 12 | rem; // uint18 - - // Draw two copies - for(uint8_t i = 0; i < 18; i++) { - bool bit = ((data >> i) & 1) != 0; - uint8_t a = size - 11 + i % 3, b = i / 3; - setFunctionModule(modules, isFunction, a, b, bit); - setFunctionModule(modules, isFunction, b, a, bit); - } - -#endif -} - -static void - drawFunctionPatterns(BitBucket* modules, BitBucket* isFunction, uint8_t version, uint8_t ecc) { - uint8_t size = modules->bitOffsetOrWidth; - - // Draw the horizontal and vertical timing patterns - for(uint8_t i = 0; i < size; i++) { - setFunctionModule(modules, isFunction, 6, i, i % 2 == 0); - setFunctionModule(modules, isFunction, i, 6, i % 2 == 0); - } - - // Draw 3 finder patterns (all corners except bottom right; overwrites some timing modules) - drawFinderPattern(modules, isFunction, 3, 3); - drawFinderPattern(modules, isFunction, size - 4, 3); - drawFinderPattern(modules, isFunction, 3, size - 4); - -#if LOCK_VERSION == 0 || LOCK_VERSION > 1 - - if(version > 1) { - // Draw the numerous alignment patterns - - uint8_t alignCount = version / 7 + 2; - uint8_t step; - if(version != 32) { - step = (version * 4 + alignCount * 2 + 1) / (2 * alignCount - 2) * - 2; // ceil((size - 13) / (2*numAlign - 2)) * 2 - } else { // C-C-C-Combo breaker! - step = 26; - } - - uint8_t alignPositionIndex = alignCount - 1; - uint8_t alignPosition[alignCount]; - - alignPosition[0] = 6; - - uint8_t size = version * 4 + 17; - for(uint8_t i = 0, pos = size - 7; i < alignCount - 1; i++, pos -= step) { - alignPosition[alignPositionIndex--] = pos; - } - - for(uint8_t i = 0; i < alignCount; i++) { - for(uint8_t j = 0; j < alignCount; j++) { - if((i == 0 && j == 0) || (i == 0 && j == alignCount - 1) || - (i == alignCount - 1 && j == 0)) { - continue; // Skip the three finder corners - } else { - drawAlignmentPattern(modules, isFunction, alignPosition[i], alignPosition[j]); - } - } - } - } - -#endif - - // Draw configuration data - drawFormatBits( - modules, isFunction, ecc, 0); // Dummy mask value; overwritten later in the constructor - drawVersion(modules, isFunction, version); -} - -// Draws the given sequence of 8-bit codewords (data and error correction) onto the entire -// data area of this QR Code symbol. Function modules need to be marked off before this is called. -static void drawCodewords(BitBucket* modules, BitBucket* isFunction, BitBucket* codewords) { - uint32_t bitLength = codewords->bitOffsetOrWidth; - uint8_t* data = codewords->data; - - uint8_t size = modules->bitOffsetOrWidth; - - // Bit index into the data - uint32_t i = 0; - - // Do the funny zigzag scan - for(int16_t right = size - 1; right >= 1; - right -= 2) { // Index of right column in each column pair - if(right == 6) { - right = 5; - } - - for(uint8_t vert = 0; vert < size; vert++) { // Vertical counter - for(int j = 0; j < 2; j++) { - uint8_t x = right - j; // Actual x coordinate - bool upwards = ((right & 2) == 0) ^ (x < 6); - uint8_t y = upwards ? size - 1 - vert : vert; // Actual y coordinate - if(!bb_getBit(isFunction, x, y) && i < bitLength) { - bb_setBit(modules, x, y, ((data[i >> 3] >> (7 - (i & 7))) & 1) != 0); - i++; - } - // If there are any remainder bits (0 to 7), they are already - // set to 0/false/white when the grid of modules was initialized - } - } - } -} - -#pragma mark - Penalty Calculation - -#define PENALTY_N1 3 -#define PENALTY_N2 3 -#define PENALTY_N3 40 -#define PENALTY_N4 10 - -// Calculates and returns the penalty score based on state of this QR Code's current modules. -// This is used by the automatic mask choice algorithm to find the mask pattern that yields the lowest score. -// @TODO: This can be optimized by working with the bytes instead of bits. -static uint32_t getPenaltyScore(BitBucket* modules) { - uint32_t result = 0; - - uint8_t size = modules->bitOffsetOrWidth; - - // Adjacent modules in row having same color - for(uint8_t y = 0; y < size; y++) { - bool colorX = bb_getBit(modules, 0, y); - for(uint8_t x = 1, runX = 1; x < size; x++) { - bool cx = bb_getBit(modules, x, y); - if(cx != colorX) { - colorX = cx; - runX = 1; - - } else { - runX++; - if(runX == 5) { - result += PENALTY_N1; - } else if(runX > 5) { - result++; - } - } - } - } - - // Adjacent modules in column having same color - for(uint8_t x = 0; x < size; x++) { - bool colorY = bb_getBit(modules, x, 0); - for(uint8_t y = 1, runY = 1; y < size; y++) { - bool cy = bb_getBit(modules, x, y); - if(cy != colorY) { - colorY = cy; - runY = 1; - } else { - runY++; - if(runY == 5) { - result += PENALTY_N1; - } else if(runY > 5) { - result++; - } - } - } - } - - uint16_t black = 0; - for(uint8_t y = 0; y < size; y++) { - uint16_t bitsRow = 0, bitsCol = 0; - for(uint8_t x = 0; x < size; x++) { - bool color = bb_getBit(modules, x, y); - - // 2*2 blocks of modules having same color - if(x > 0 && y > 0) { - bool colorUL = bb_getBit(modules, x - 1, y - 1); - bool colorUR = bb_getBit(modules, x, y - 1); - bool colorL = bb_getBit(modules, x - 1, y); - if(color == colorUL && color == colorUR && color == colorL) { - result += PENALTY_N2; - } - } - - // Finder-like pattern in rows and columns - bitsRow = ((bitsRow << 1) & 0x7FF) | color; - bitsCol = ((bitsCol << 1) & 0x7FF) | bb_getBit(modules, y, x); - - // Needs 11 bits accumulated - if(x >= 10) { - if(bitsRow == 0x05D || bitsRow == 0x5D0) { - result += PENALTY_N3; - } - if(bitsCol == 0x05D || bitsCol == 0x5D0) { - result += PENALTY_N3; - } - } - - // Balance of black and white modules - if(color) { - black++; - } - } - } - - // Find smallest k such that (45-5k)% <= dark/total <= (55+5k)% - uint16_t total = size * size; - for(uint16_t k = 0; black * 20 < (9 - k) * total || black * 20 > (11 + k) * total; k++) { - result += PENALTY_N4; - } - - return result; -} - -#pragma mark - Reed-Solomon Generator - -static uint8_t rs_multiply(uint8_t x, uint8_t y) { - // Russian peasant multiplication - // See: https://en.wikipedia.org/wiki/Ancient_Egyptian_multiplication - uint16_t z = 0; - for(int8_t i = 7; i >= 0; i--) { - z = (z << 1) ^ ((z >> 7) * 0x11D); - z ^= ((y >> i) & 1) * x; - } - return z; -} - -static void rs_init(uint8_t degree, uint8_t* coeff) { - memset(coeff, 0, degree); - coeff[degree - 1] = 1; - - // Compute the product polynomial (x - r^0) * (x - r^1) * (x - r^2) * ... * (x - r^{degree-1}), - // drop the highest term, and store the rest of the coefficients in order of descending powers. - // Note that r = 0x02, which is a generator element of this field GF(2^8/0x11D). - uint16_t root = 1; - for(uint8_t i = 0; i < degree; i++) { - // Multiply the current product by (x - r^i) - for(uint8_t j = 0; j < degree; j++) { - coeff[j] = rs_multiply(coeff[j], root); - if(j + 1 < degree) { - coeff[j] ^= coeff[j + 1]; - } - } - root = (root << 1) ^ ((root >> 7) * 0x11D); // Multiply by 0x02 mod GF(2^8/0x11D) - } -} - -static void rs_getRemainder( - uint8_t degree, - uint8_t* coeff, - uint8_t* data, - uint8_t length, - uint8_t* result, - uint8_t stride) { - // Compute the remainder by performing polynomial division - - //for (uint8_t i = 0; i < degree; i++) { result[] = 0; } - //memset(result, 0, degree); - - for(uint8_t i = 0; i < length; i++) { - uint8_t factor = data[i] ^ result[0]; - for(uint8_t j = 1; j < degree; j++) { - result[(j - 1) * stride] = result[j * stride]; - } - result[(degree - 1) * stride] = 0; - - for(uint8_t j = 0; j < degree; j++) { - result[j * stride] ^= rs_multiply(coeff[j], factor); - } - } -} - -#pragma mark - QrCode - -static int8_t encodeDataCodewords( - BitBucket* dataCodewords, - const uint8_t* text, - uint16_t length, - uint8_t version) { - int8_t mode = MODE_BYTE; - - if(isNumeric((char*)text, length)) { - mode = MODE_NUMERIC; - bb_appendBits(dataCodewords, 1 << MODE_NUMERIC, 4); - bb_appendBits(dataCodewords, length, getModeBits(version, MODE_NUMERIC)); - - uint16_t accumData = 0; - uint8_t accumCount = 0; - for(uint16_t i = 0; i < length; i++) { - accumData = accumData * 10 + ((char)(text[i]) - '0'); - accumCount++; - if(accumCount == 3) { - bb_appendBits(dataCodewords, accumData, 10); - accumData = 0; - accumCount = 0; - } - } - - // 1 or 2 digits remaining - if(accumCount > 0) { - bb_appendBits(dataCodewords, accumData, accumCount * 3 + 1); - } - - } else if(isAlphanumeric((char*)text, length)) { - mode = MODE_ALPHANUMERIC; - bb_appendBits(dataCodewords, 1 << MODE_ALPHANUMERIC, 4); - bb_appendBits(dataCodewords, length, getModeBits(version, MODE_ALPHANUMERIC)); - - uint16_t accumData = 0; - uint8_t accumCount = 0; - for(uint16_t i = 0; i < length; i++) { - accumData = accumData * 45 + getAlphanumeric((char)(text[i])); - accumCount++; - if(accumCount == 2) { - bb_appendBits(dataCodewords, accumData, 11); - accumData = 0; - accumCount = 0; - } - } - - // 1 character remaining - if(accumCount > 0) { - bb_appendBits(dataCodewords, accumData, 6); - } - - } else { - bb_appendBits(dataCodewords, 1 << MODE_BYTE, 4); - bb_appendBits(dataCodewords, length, getModeBits(version, MODE_BYTE)); - for(uint16_t i = 0; i < length; i++) { - bb_appendBits(dataCodewords, (char)(text[i]), 8); - } - } - - //bb_setBits(dataCodewords, length, 4, getModeBits(version, mode)); - - return mode; -} - -static void performErrorCorrection(uint8_t version, uint8_t ecc, BitBucket* data) { - // See: http://www.thonky.com/qr-code-tutorial/structure-final-message - -#if LOCK_VERSION == 0 - uint8_t numBlocks = NUM_ERROR_CORRECTION_BLOCKS[ecc][version - 1]; - uint16_t totalEcc = NUM_ERROR_CORRECTION_CODEWORDS[ecc][version - 1]; - uint16_t moduleCount = NUM_RAW_DATA_MODULES[version - 1]; -#else - uint8_t numBlocks = NUM_ERROR_CORRECTION_BLOCKS[ecc]; - uint16_t totalEcc = NUM_ERROR_CORRECTION_CODEWORDS[ecc]; - uint16_t moduleCount = NUM_RAW_DATA_MODULES; -#endif - - uint8_t blockEccLen = totalEcc / numBlocks; - uint8_t numShortBlocks = numBlocks - moduleCount / 8 % numBlocks; - uint8_t shortBlockLen = moduleCount / 8 / numBlocks; - - uint8_t shortDataBlockLen = shortBlockLen - blockEccLen; - - uint8_t result[data->capacityBytes]; - memset(result, 0, sizeof(result)); - - uint8_t coeff[blockEccLen]; - rs_init(blockEccLen, coeff); - - uint16_t offset = 0; - uint8_t* dataBytes = data->data; - - // Interleave all short blocks - for(uint8_t i = 0; i < shortDataBlockLen; i++) { - uint16_t index = i; - uint8_t stride = shortDataBlockLen; - for(uint8_t blockNum = 0; blockNum < numBlocks; blockNum++) { - result[offset++] = dataBytes[index]; - -#if LOCK_VERSION == 0 || LOCK_VERSION >= 5 - if(blockNum == numShortBlocks) { - stride++; - } -#endif - index += stride; - } - } - - // Version less than 5 only have short blocks -#if LOCK_VERSION == 0 || LOCK_VERSION >= 5 - { - // Interleave long blocks - uint16_t index = shortDataBlockLen * (numShortBlocks + 1); - uint8_t stride = shortDataBlockLen; - for(uint8_t blockNum = 0; blockNum < numBlocks - numShortBlocks; blockNum++) { - result[offset++] = dataBytes[index]; - - if(blockNum == 0) { - stride++; - } - index += stride; - } - } -#endif - - // Add all ecc blocks, interleaved - uint8_t blockSize = shortDataBlockLen; - for(uint8_t blockNum = 0; blockNum < numBlocks; blockNum++) { -#if LOCK_VERSION == 0 || LOCK_VERSION >= 5 - if(blockNum == numShortBlocks) { - blockSize++; - } -#endif - rs_getRemainder( - blockEccLen, coeff, dataBytes, blockSize, &result[offset + blockNum], numBlocks); - dataBytes += blockSize; - } - - memcpy(data->data, result, data->capacityBytes); - data->bitOffsetOrWidth = moduleCount; -} - -// We store the Format bits tightly packed into a single byte (each of the 4 modes is 2 bits) -// The format bits can be determined by ECC_FORMAT_BITS >> (2 * ecc) -static const uint8_t ECC_FORMAT_BITS = (0x02 << 6) | (0x03 << 4) | (0x00 << 2) | (0x01 << 0); - -#pragma mark - Public QRCode functions - -uint16_t qrcode_getBufferSize(uint8_t version) { - return bb_getGridSizeBytes(4 * version + 17); -} - -// @TODO: Return error if data is too big. -int8_t qrcode_initBytes( - QRCode* qrcode, - uint8_t* modules, - uint8_t version, - uint8_t ecc, - uint8_t* data, - uint16_t length) { - uint8_t size = version * 4 + 17; - qrcode->version = version; - qrcode->size = size; - qrcode->ecc = ecc; - qrcode->modules = modules; - - uint8_t eccFormatBits = (ECC_FORMAT_BITS >> (2 * ecc)) & 0x03; - -#if LOCK_VERSION == 0 - uint16_t moduleCount = NUM_RAW_DATA_MODULES[version - 1]; - uint16_t dataCapacity = - moduleCount / 8 - NUM_ERROR_CORRECTION_CODEWORDS[eccFormatBits][version - 1]; -#else - version = LOCK_VERSION; - uint16_t moduleCount = NUM_RAW_DATA_MODULES; - uint16_t dataCapacity = moduleCount / 8 - NUM_ERROR_CORRECTION_CODEWORDS[eccFormatBits]; -#endif - - struct BitBucket codewords; - uint8_t codewordBytes[bb_getBufferSizeBytes(moduleCount)]; - bb_initBuffer(&codewords, codewordBytes, (int32_t)sizeof(codewordBytes)); - - // Place the data code words into the buffer - int8_t mode = encodeDataCodewords(&codewords, data, length, version); - - if(mode < 0) { - return -1; - } - qrcode->mode = mode; - - // Add terminator and pad up to a byte if applicable - uint32_t padding = (dataCapacity * 8) - codewords.bitOffsetOrWidth; - if(padding > 4) { - padding = 4; - } - bb_appendBits(&codewords, 0, padding); - bb_appendBits(&codewords, 0, (8 - codewords.bitOffsetOrWidth % 8) % 8); - - // Pad with alternate bytes until data capacity is reached - for(uint8_t padByte = 0xEC; codewords.bitOffsetOrWidth < (dataCapacity * 8); - padByte ^= 0xEC ^ 0x11) { - bb_appendBits(&codewords, padByte, 8); - } - - BitBucket modulesGrid; - bb_initGrid(&modulesGrid, modules, size); - - BitBucket isFunctionGrid; - uint8_t isFunctionGridBytes[bb_getGridSizeBytes(size)]; - bb_initGrid(&isFunctionGrid, isFunctionGridBytes, size); - - // Draw function patterns, draw all codewords, do masking - drawFunctionPatterns(&modulesGrid, &isFunctionGrid, version, eccFormatBits); - performErrorCorrection(version, eccFormatBits, &codewords); - drawCodewords(&modulesGrid, &isFunctionGrid, &codewords); - - // Find the best (lowest penalty) mask - uint8_t mask = 0; - int32_t minPenalty = INT32_MAX; - for(uint8_t i = 0; i < 8; i++) { - drawFormatBits(&modulesGrid, &isFunctionGrid, eccFormatBits, i); - applyMask(&modulesGrid, &isFunctionGrid, i); - int penalty = getPenaltyScore(&modulesGrid); - if(penalty < minPenalty) { - mask = i; - minPenalty = penalty; - } - applyMask(&modulesGrid, &isFunctionGrid, i); // Undoes the mask due to XOR - } - - qrcode->mask = mask; - - // Overwrite old format bits - drawFormatBits(&modulesGrid, &isFunctionGrid, eccFormatBits, mask); - - // Apply the final choice of mask - applyMask(&modulesGrid, &isFunctionGrid, mask); - - return 0; -} - -int8_t qrcode_initText( - QRCode* qrcode, - uint8_t* modules, - uint8_t version, - uint8_t ecc, - const char* data) { - return qrcode_initBytes(qrcode, modules, version, ecc, (uint8_t*)data, strlen(data)); -} - -bool qrcode_getModule(QRCode* qrcode, uint8_t x, uint8_t y) { - if(x < 0 || x >= qrcode->size || y < 0 || y >= qrcode->size) { - return false; - } - - uint32_t offset = y * qrcode->size + x; - return (qrcode->modules[offset >> 3] & (1 << (7 - (offset & 0x07)))) != 0; -} diff --git a/lib/qrcode/qrcode.h b/lib/qrcode/qrcode.h deleted file mode 100644 index 6d637ba04d6..00000000000 --- a/lib/qrcode/qrcode.h +++ /dev/null @@ -1,99 +0,0 @@ -/** - * The MIT License (MIT) - * - * This library is written and maintained by Richard Moore. - * Major parts were derived from Project Nayuki's library. - * - * Copyright (c) 2017 Richard Moore (https://github.com/ricmoo/QRCode) - * Copyright (c) 2017 Project Nayuki (https://www.nayuki.io/page/qr-code-generator-library) - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -/** - * Special thanks to Nayuki (https://www.nayuki.io/) from which this library was - * heavily inspired and compared against. - * - * See: https://github.com/nayuki/QR-Code-generator/tree/master/cpp - */ - -#ifndef __QRCODE_H_ -#define __QRCODE_H_ - -#ifndef __cplusplus -typedef unsigned char bool; -static const bool false = 0; -static const bool true = 1; -#endif - -#include - -// QR Code Format Encoding -#define MODE_NUMERIC 0 -#define MODE_ALPHANUMERIC 1 -#define MODE_BYTE 2 - -// Error Correction Code Levels -#define ECC_LOW 0 -#define ECC_MEDIUM 1 -#define ECC_QUARTILE 2 -#define ECC_HIGH 3 - -// If set to non-zero, this library can ONLY produce QR codes at that version -// This saves a lot of dynamic memory, as the codeword tables are skipped -#ifndef LOCK_VERSION -#define LOCK_VERSION 0 -#endif - -typedef struct QRCode { - uint8_t version; - uint8_t size; - uint8_t ecc; - uint8_t mode; - uint8_t mask; - uint8_t* modules; -} QRCode; - -#ifdef __cplusplus -extern "C" { -#endif /* __cplusplus */ - -uint16_t qrcode_getBufferSize(uint8_t version); - -int8_t qrcode_initText( - QRCode* qrcode, - uint8_t* modules, - uint8_t version, - uint8_t ecc, - const char* data); -int8_t qrcode_initBytes( - QRCode* qrcode, - uint8_t* modules, - uint8_t version, - uint8_t ecc, - uint8_t* data, - uint16_t length); - -bool qrcode_getModule(QRCode* qrcode, uint8_t x, uint8_t y); - -#ifdef __cplusplus -} -#endif /* __cplusplus */ - -#endif /* __QRCODE_H_ */ diff --git a/lib/signal_reader/SConscript b/lib/signal_reader/SConscript index ea731442018..386e3419d75 100644 --- a/lib/signal_reader/SConscript +++ b/lib/signal_reader/SConscript @@ -7,11 +7,14 @@ env.Append( SDK_HEADERS=[ File("signal_reader.h"), ], + LINT_SOURCES=[ + Dir("."), + ], ) libenv = env.Clone(FW_LIB_NAME="signal_reader") libenv.ApplyLibFlags() -libenv.Append(CCFLAGS=["-O3", "-funroll-loops", "-Ofast"]) +libenv.AppendUnique(CCFLAGS=["-O3", "-funroll-loops", "-Ofast"]) sources = libenv.GlobRecursive("*.c*") diff --git a/lib/subghz/SConscript b/lib/subghz/SConscript index 35850aa83e8..d0bc2a2543c 100644 --- a/lib/subghz/SConscript +++ b/lib/subghz/SConscript @@ -4,6 +4,9 @@ env.Append( CPPPATH=[ "#/lib/subghz", ], + LINT_SOURCES=[ + Dir("."), + ], SDK_HEADERS=[ File("environment.h"), File("receiver.h"), diff --git a/lib/toolbox/SConscript b/lib/toolbox/SConscript index de77f0ccf27..14f8de0646e 100644 --- a/lib/toolbox/SConscript +++ b/lib/toolbox/SConscript @@ -7,6 +7,9 @@ env.Append( CPPPATH=[ "#/lib/toolbox", ], + LINT_SOURCES=[ + Dir("."), + ], SDK_HEADERS=[ File("api_lock.h"), File("compress.h"), @@ -14,10 +17,8 @@ env.Append( File("manchester_encoder.h"), File("path.h"), File("name_generator.h"), - File("sha256.h"), File("crc32_calc.h"), File("dir_walk.h"), - File("md5.h"), File("args.h"), File("saved_struct.h"), File("version.h"), diff --git a/lib/toolbox/md5.c b/lib/toolbox/md5.c deleted file mode 100644 index a907d52e3b4..00000000000 --- a/lib/toolbox/md5.c +++ /dev/null @@ -1,299 +0,0 @@ -/******************************************************************************* -* Portions COPYRIGHT 2015 STMicroelectronics * -* Portions Copyright (C) 2006-2013, Brainspark B.V. * -*******************************************************************************/ - -/* - * RFC 1321 compliant MD5 implementation - * - * Copyright (C) 2006-2013, Brainspark B.V. - * - * This file is part of PolarSSL (http://www.polarssl.org) - * Lead Maintainer: Paul Bakker - * - * All rights reserved. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ -/* - * The MD5 algorithm was designed by Ron Rivest in 1991. - * - * http://www.ietf.org/rfc/rfc1321.txt - */ - -/** - ****************************************************************************** - * @file md5.c - * @author MCD Application Team - * @brief This file has been modified to support the hardware Cryptographic and - * Hash processors embedded in STM32F415xx/417xx/437xx/439xx/756xx devices. - * This support is activated by defining the "USE_STM32F4XX_HW_CRYPTO" - * or "USE_STM32F7XX_HW_CRYPTO" macro in PolarSSL config.h file. - ****************************************************************************** - * @attention - * - * Licensed under MCD-ST Liberty SW License Agreement V2, (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.st.com/software_license_agreement_liberty_v2 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - ****************************************************************************** - */ - -#include "md5.h" - -/* - * 32-bit integer manipulation macros (little endian) - */ -#ifndef GET_UINT32_LE -#define GET_UINT32_LE(n, b, i) \ - { \ - (n) = ((uint32_t)(b)[(i)]) | ((uint32_t)(b)[(i) + 1] << 8) | \ - ((uint32_t)(b)[(i) + 2] << 16) | ((uint32_t)(b)[(i) + 3] << 24); \ - } -#endif - -#ifndef PUT_UINT32_LE -#define PUT_UINT32_LE(n, b, i) \ - { \ - (b)[(i)] = (unsigned char)((n)); \ - (b)[(i) + 1] = (unsigned char)((n) >> 8); \ - (b)[(i) + 2] = (unsigned char)((n) >> 16); \ - (b)[(i) + 3] = (unsigned char)((n) >> 24); \ - } -#endif - -/* - * MD5 context setup - */ -void md5_starts(md5_context* ctx) { - ctx->total[0] = 0; - ctx->total[1] = 0; - - ctx->state[0] = 0x67452301; - ctx->state[1] = 0xEFCDAB89; - ctx->state[2] = 0x98BADCFE; - ctx->state[3] = 0x10325476; -} - -void md5_process(md5_context* ctx, const unsigned char data[64]) { - uint32_t X[16], A, B, C, D; - - GET_UINT32_LE(X[0], data, 0); - GET_UINT32_LE(X[1], data, 4); - GET_UINT32_LE(X[2], data, 8); - GET_UINT32_LE(X[3], data, 12); - GET_UINT32_LE(X[4], data, 16); - GET_UINT32_LE(X[5], data, 20); - GET_UINT32_LE(X[6], data, 24); - GET_UINT32_LE(X[7], data, 28); - GET_UINT32_LE(X[8], data, 32); - GET_UINT32_LE(X[9], data, 36); - GET_UINT32_LE(X[10], data, 40); - GET_UINT32_LE(X[11], data, 44); - GET_UINT32_LE(X[12], data, 48); - GET_UINT32_LE(X[13], data, 52); - GET_UINT32_LE(X[14], data, 56); - GET_UINT32_LE(X[15], data, 60); - -#define S(x, n) (((x) << (n)) | (((x)&0xFFFFFFFF) >> (32 - (n)))) - -#define P(a, b, c, d, k, s, t) \ - { \ - a += F(b, c, d) + X[k] + t; \ - a = S(a, s) + b; \ - } - - A = ctx->state[0]; - B = ctx->state[1]; - C = ctx->state[2]; - D = ctx->state[3]; - -#define F(x, y, z) ((z) ^ ((x) & ((y) ^ (z)))) - - P(A, B, C, D, 0, 7, 0xD76AA478); - P(D, A, B, C, 1, 12, 0xE8C7B756); - P(C, D, A, B, 2, 17, 0x242070DB); - P(B, C, D, A, 3, 22, 0xC1BDCEEE); - P(A, B, C, D, 4, 7, 0xF57C0FAF); - P(D, A, B, C, 5, 12, 0x4787C62A); - P(C, D, A, B, 6, 17, 0xA8304613); - P(B, C, D, A, 7, 22, 0xFD469501); - P(A, B, C, D, 8, 7, 0x698098D8); - P(D, A, B, C, 9, 12, 0x8B44F7AF); - P(C, D, A, B, 10, 17, 0xFFFF5BB1); - P(B, C, D, A, 11, 22, 0x895CD7BE); - P(A, B, C, D, 12, 7, 0x6B901122); - P(D, A, B, C, 13, 12, 0xFD987193); - P(C, D, A, B, 14, 17, 0xA679438E); - P(B, C, D, A, 15, 22, 0x49B40821); - -#undef F - -#define F(x, y, z) ((y) ^ ((z) & ((x) ^ (y)))) - - P(A, B, C, D, 1, 5, 0xF61E2562); - P(D, A, B, C, 6, 9, 0xC040B340); - P(C, D, A, B, 11, 14, 0x265E5A51); - P(B, C, D, A, 0, 20, 0xE9B6C7AA); - P(A, B, C, D, 5, 5, 0xD62F105D); - P(D, A, B, C, 10, 9, 0x02441453); - P(C, D, A, B, 15, 14, 0xD8A1E681); - P(B, C, D, A, 4, 20, 0xE7D3FBC8); - P(A, B, C, D, 9, 5, 0x21E1CDE6); - P(D, A, B, C, 14, 9, 0xC33707D6); - P(C, D, A, B, 3, 14, 0xF4D50D87); - P(B, C, D, A, 8, 20, 0x455A14ED); - P(A, B, C, D, 13, 5, 0xA9E3E905); - P(D, A, B, C, 2, 9, 0xFCEFA3F8); - P(C, D, A, B, 7, 14, 0x676F02D9); - P(B, C, D, A, 12, 20, 0x8D2A4C8A); - -#undef F - -#define F(x, y, z) ((x) ^ (y) ^ (z)) - - P(A, B, C, D, 5, 4, 0xFFFA3942); - P(D, A, B, C, 8, 11, 0x8771F681); - P(C, D, A, B, 11, 16, 0x6D9D6122); - P(B, C, D, A, 14, 23, 0xFDE5380C); - P(A, B, C, D, 1, 4, 0xA4BEEA44); - P(D, A, B, C, 4, 11, 0x4BDECFA9); - P(C, D, A, B, 7, 16, 0xF6BB4B60); - P(B, C, D, A, 10, 23, 0xBEBFBC70); - P(A, B, C, D, 13, 4, 0x289B7EC6); - P(D, A, B, C, 0, 11, 0xEAA127FA); - P(C, D, A, B, 3, 16, 0xD4EF3085); - P(B, C, D, A, 6, 23, 0x04881D05); - P(A, B, C, D, 9, 4, 0xD9D4D039); - P(D, A, B, C, 12, 11, 0xE6DB99E5); - P(C, D, A, B, 15, 16, 0x1FA27CF8); - P(B, C, D, A, 2, 23, 0xC4AC5665); - -#undef F - -#define F(x, y, z) ((y) ^ ((x) | ~(z))) - - P(A, B, C, D, 0, 6, 0xF4292244); - P(D, A, B, C, 7, 10, 0x432AFF97); - P(C, D, A, B, 14, 15, 0xAB9423A7); - P(B, C, D, A, 5, 21, 0xFC93A039); - P(A, B, C, D, 12, 6, 0x655B59C3); - P(D, A, B, C, 3, 10, 0x8F0CCC92); - P(C, D, A, B, 10, 15, 0xFFEFF47D); - P(B, C, D, A, 1, 21, 0x85845DD1); - P(A, B, C, D, 8, 6, 0x6FA87E4F); - P(D, A, B, C, 15, 10, 0xFE2CE6E0); - P(C, D, A, B, 6, 15, 0xA3014314); - P(B, C, D, A, 13, 21, 0x4E0811A1); - P(A, B, C, D, 4, 6, 0xF7537E82); - P(D, A, B, C, 11, 10, 0xBD3AF235); - P(C, D, A, B, 2, 15, 0x2AD7D2BB); - P(B, C, D, A, 9, 21, 0xEB86D391); - -#undef F - - ctx->state[0] += A; - ctx->state[1] += B; - ctx->state[2] += C; - ctx->state[3] += D; -} - -/* - * MD5 process buffer - */ -void md5_update(md5_context* ctx, const unsigned char* input, size_t ilen) { - size_t fill; - uint32_t left; - - if(ilen <= 0) return; - - left = ctx->total[0] & 0x3F; - fill = 64 - left; - - ctx->total[0] += (uint32_t)ilen; - ctx->total[0] &= 0xFFFFFFFF; - - if(ctx->total[0] < (uint32_t)ilen) ctx->total[1]++; - - if(left && ilen >= fill) { - memcpy((void*)(ctx->buffer + left), input, fill); - md5_process(ctx, ctx->buffer); - input += fill; - ilen -= fill; - left = 0; - } - - while(ilen >= 64) { - md5_process(ctx, input); - input += 64; - ilen -= 64; - } - - if(ilen > 0) { - memcpy((void*)(ctx->buffer + left), input, ilen); - } -} - -static const unsigned char md5_padding[64] = {0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - -/* - * MD5 final digest - */ -void md5_finish(md5_context* ctx, unsigned char output[16]) { - uint32_t last, padn; - uint32_t high, low; - unsigned char msglen[8]; - - high = (ctx->total[0] >> 29) | (ctx->total[1] << 3); - low = (ctx->total[0] << 3); - - PUT_UINT32_LE(low, msglen, 0); - PUT_UINT32_LE(high, msglen, 4); - - last = ctx->total[0] & 0x3F; - padn = (last < 56) ? (56 - last) : (120 - last); - - md5_update(ctx, md5_padding, padn); - md5_update(ctx, msglen, 8); - - PUT_UINT32_LE(ctx->state[0], output, 0); - PUT_UINT32_LE(ctx->state[1], output, 4); - PUT_UINT32_LE(ctx->state[2], output, 8); - PUT_UINT32_LE(ctx->state[3], output, 12); -} - -/* - * output = MD5( input buffer ) - */ -void md5(const unsigned char* input, size_t ilen, unsigned char output[16]) { - md5_context ctx; - - md5_starts(&ctx); - md5_update(&ctx, input, ilen); - md5_finish(&ctx, output); - - memset(&ctx, 0, sizeof(md5_context)); //-V597 -} diff --git a/lib/toolbox/md5.h b/lib/toolbox/md5.h deleted file mode 100644 index fe53db8d3e5..00000000000 --- a/lib/toolbox/md5.h +++ /dev/null @@ -1,83 +0,0 @@ -/** - * \file md5.h - * - * \brief MD5 message digest algorithm (hash function) - * - * Copyright (C) 2006-2013, Brainspark B.V. - * - * This file is part of PolarSSL (http://www.polarssl.org) - * Lead Maintainer: Paul Bakker - * - * All rights reserved. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include -#include -#include - -/** - * \brief MD5 context structure - */ -typedef struct { - uint32_t total[2]; /*!< number of bytes processed */ - uint32_t state[4]; /*!< intermediate digest state */ - unsigned char buffer[64]; /*!< data block being processed */ -} md5_context; - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * \brief MD5 context setup - * - * \param ctx context to be initialized - */ -void md5_starts(md5_context* ctx); - -/** - * \brief MD5 process buffer - * - * \param ctx MD5 context - * \param input buffer holding the data - * \param ilen length of the input data - */ -void md5_update(md5_context* ctx, const unsigned char* input, size_t ilen); - -/** - * \brief MD5 final digest - * - * \param ctx MD5 context - * \param output MD5 checksum result - */ -void md5_finish(md5_context* ctx, unsigned char output[16]); - -/* Internal use */ -void md5_process(md5_context* ctx, const unsigned char data[64]); - -/** - * \brief Output = MD5( input buffer ) - * - * \param input buffer holding the data - * \param ilen length of the input data - * \param output MD5 checksum result - */ -void md5(const unsigned char* input, size_t ilen, unsigned char output[16]); - -#ifdef __cplusplus -} -#endif diff --git a/lib/toolbox/md5_calc.c b/lib/toolbox/md5_calc.c index 7f335a33f2b..59c9403e812 100644 --- a/lib/toolbox/md5_calc.c +++ b/lib/toolbox/md5_calc.c @@ -1,24 +1,38 @@ -#include "md5.h" #include "md5_calc.h" +#include +#include +#include + bool md5_calc_file(File* file, const char* path, unsigned char output[16], FS_Error* file_error) { - bool result = storage_file_open(file, path, FSAM_READ, FSOM_OPEN_EXISTING); + if(!storage_file_open(file, path, FSAM_READ, FSOM_OPEN_EXISTING)) { + if(file_error != NULL) { + *file_error = storage_file_get_error(file); + } + return false; + } - if(result) { - const size_t size_to_read = 512; - uint8_t* data = malloc(size_to_read); - md5_context* md5_ctx = malloc(sizeof(md5_context)); - - md5_starts(md5_ctx); - while(true) { - size_t read_size = storage_file_read(file, data, size_to_read); - if(read_size == 0) break; - md5_update(md5_ctx, data, read_size); + const size_t size_to_read = 512; + uint8_t* data = malloc(size_to_read); + bool result = true; + + mbedtls_md5_context* md5_ctx = malloc(sizeof(mbedtls_md5_context)); + mbedtls_md5_init(md5_ctx); + mbedtls_md5_starts(md5_ctx); + while(true) { + size_t read_size = storage_file_read(file, data, size_to_read); + if(storage_file_get_error(file) != FSE_OK) { + result = false; + break; + } + if(read_size == 0) { + break; } - md5_finish(md5_ctx, output); - free(md5_ctx); - free(data); + mbedtls_md5_update(md5_ctx, data, read_size); } + mbedtls_md5_finish(md5_ctx, output); + free(md5_ctx); + free(data); if(file_error != NULL) { *file_error = storage_file_get_error(file); diff --git a/lib/toolbox/sha256.c b/lib/toolbox/sha256.c deleted file mode 100644 index ff4984439db..00000000000 --- a/lib/toolbox/sha256.c +++ /dev/null @@ -1,221 +0,0 @@ -/* - * sha256.c -- Compute SHA-256 hash - * - * Just for little endian architecture. - * - * Code taken from: - * http://gladman.plushost.co.uk/oldsite/cryptography_technology/sha/index.php - * - * File names are sha2.c, sha2.h, brg_types.h, brg_endian.h - * in the archive sha2-07-01-07.zip. - * - * Code is modified in the style of PolarSSL API. - * - * See original copyright notice below. - */ -/* - --------------------------------------------------------------------------- - Copyright (c) 2002, Dr Brian Gladman, Worcester, UK. All rights reserved. - - LICENSE TERMS - - The free distribution and use of this software in both source and binary - form is allowed (with or without changes) provided that: - - 1. distributions of this source code include the above copyright - notice, this list of conditions and the following disclaimer; - - 2. distributions in binary form include the above copyright - notice, this list of conditions and the following disclaimer - in the documentation and/or other associated materials; - - 3. the copyright holder's name is not used to endorse products - built using this software without specific written permission. - - ALTERNATIVELY, provided that this notice is retained in full, this product - may be distributed under the terms of the GNU General Public License (GPL), - in which case the provisions of the GPL apply INSTEAD OF those given above. - - DISCLAIMER - - This software is provided 'as is' with no explicit or implied warranties - in respect of its properties, including, but not limited to, correctness - and/or fitness for purpose. - --------------------------------------------------------------------------- - Issue Date: 01/08/2005 -*/ - -#include -#include -#include -#include "sha256.h" - -#define SHA256_MASK (SHA256_BLOCK_SIZE - 1) - -static void memcpy_output_bswap32(unsigned char* dst, const uint32_t* p) { - int i; - uint32_t q = 0; - - for(i = 0; i < 32; i++) { - if((i & 3) == 0) q = __builtin_bswap32(p[i >> 2]); /* bswap32 is GCC extention */ - dst[i] = q >> ((i & 3) * 8); - } -} - -#define rotr32(x, n) (((x) >> n) | ((x) << (32 - (n)))) - -#define ch(x, y, z) ((z) ^ ((x) & ((y) ^ (z)))) -#define maj(x, y, z) (((x) & (y)) | ((z) & ((x) ^ (y)))) - -/* round transforms for SHA256 compression functions */ -#define vf(n, i) v[((n) - (i)) & 7] - -#define hf(i) (p[(i)&15] += g_1(p[((i) + 14) & 15]) + p[((i) + 9) & 15] + g_0(p[((i) + 1) & 15])) - -#define v_cycle0(i) \ - p[i] = __builtin_bswap32(p[i]); \ - vf(7, i) += p[i] + k_0[i] + s_1(vf(4, i)) + ch(vf(4, i), vf(5, i), vf(6, i)); \ - vf(3, i) += vf(7, i); \ - vf(7, i) += s_0(vf(0, i)) + maj(vf(0, i), vf(1, i), vf(2, i)) - -#define v_cycle(i, j) \ - vf(7, i) += hf(i) + k_0[i + j] + s_1(vf(4, i)) + ch(vf(4, i), vf(5, i), vf(6, i)); \ - vf(3, i) += vf(7, i); \ - vf(7, i) += s_0(vf(0, i)) + maj(vf(0, i), vf(1, i), vf(2, i)) - -#define s_0(x) (rotr32((x), 2) ^ rotr32((x), 13) ^ rotr32((x), 22)) -#define s_1(x) (rotr32((x), 6) ^ rotr32((x), 11) ^ rotr32((x), 25)) -#define g_0(x) (rotr32((x), 7) ^ rotr32((x), 18) ^ ((x) >> 3)) -#define g_1(x) (rotr32((x), 17) ^ rotr32((x), 19) ^ ((x) >> 10)) -#define k_0 k256 - -static const uint32_t k256[64] = { - 0X428A2F98, 0X71374491, 0XB5C0FBCF, 0XE9B5DBA5, 0X3956C25B, 0X59F111F1, 0X923F82A4, 0XAB1C5ED5, - 0XD807AA98, 0X12835B01, 0X243185BE, 0X550C7DC3, 0X72BE5D74, 0X80DEB1FE, 0X9BDC06A7, 0XC19BF174, - 0XE49B69C1, 0XEFBE4786, 0X0FC19DC6, 0X240CA1CC, 0X2DE92C6F, 0X4A7484AA, 0X5CB0A9DC, 0X76F988DA, - 0X983E5152, 0XA831C66D, 0XB00327C8, 0XBF597FC7, 0XC6E00BF3, 0XD5A79147, 0X06CA6351, 0X14292967, - 0X27B70A85, 0X2E1B2138, 0X4D2C6DFC, 0X53380D13, 0X650A7354, 0X766A0ABB, 0X81C2C92E, 0X92722C85, - 0XA2BFE8A1, 0XA81A664B, 0XC24B8B70, 0XC76C51A3, 0XD192E819, 0XD6990624, 0XF40E3585, 0X106AA070, - 0X19A4C116, 0X1E376C08, 0X2748774C, 0X34B0BCB5, 0X391C0CB3, 0X4ED8AA4A, 0X5B9CCA4F, 0X682E6FF3, - 0X748F82EE, 0X78A5636F, 0X84C87814, 0X8CC70208, 0X90BEFFFA, 0XA4506CEB, 0XBEF9A3F7, 0XC67178F2, -}; - -void sha256_process(sha256_context* ctx) { - uint32_t i; - uint32_t* p = ctx->wbuf; - uint32_t v[8]; - - memcpy(v, ctx->state, 8 * sizeof(uint32_t)); - - v_cycle0(0); - v_cycle0(1); - v_cycle0(2); - v_cycle0(3); - v_cycle0(4); - v_cycle0(5); - v_cycle0(6); - v_cycle0(7); - v_cycle0(8); - v_cycle0(9); - v_cycle0(10); - v_cycle0(11); - v_cycle0(12); - v_cycle0(13); - v_cycle0(14); - v_cycle0(15); - - for(i = 16; i < 64; i += 16) { - v_cycle(0, i); - v_cycle(1, i); - v_cycle(2, i); - v_cycle(3, i); - v_cycle(4, i); - v_cycle(5, i); - v_cycle(6, i); - v_cycle(7, i); - v_cycle(8, i); - v_cycle(9, i); - v_cycle(10, i); - v_cycle(11, i); - v_cycle(12, i); - v_cycle(13, i); - v_cycle(14, i); - v_cycle(15, i); - } - - ctx->state[0] += v[0]; - ctx->state[1] += v[1]; - ctx->state[2] += v[2]; - ctx->state[3] += v[3]; - ctx->state[4] += v[4]; - ctx->state[5] += v[5]; - ctx->state[6] += v[6]; - ctx->state[7] += v[7]; -} - -void sha256_update(sha256_context* ctx, const unsigned char* input, unsigned int ilen) { - uint32_t left = (ctx->total[0] & SHA256_MASK); - uint32_t fill = SHA256_BLOCK_SIZE - left; - - ctx->total[0] += ilen; - if(ctx->total[0] < ilen) ctx->total[1]++; - - while(ilen >= fill) { - memcpy(((unsigned char*)ctx->wbuf) + left, input, fill); - sha256_process(ctx); - input += fill; - ilen -= fill; - left = 0; - fill = SHA256_BLOCK_SIZE; - } - - memcpy(((unsigned char*)ctx->wbuf) + left, input, ilen); -} - -void sha256_finish(sha256_context* ctx, unsigned char output[32]) { - uint32_t last = (ctx->total[0] & SHA256_MASK); - - ctx->wbuf[last >> 2] = __builtin_bswap32(ctx->wbuf[last >> 2]); - ctx->wbuf[last >> 2] &= 0xffffff80UL << (8 * (~last & 3)); - ctx->wbuf[last >> 2] |= 0x00000080UL << (8 * (~last & 3)); - ctx->wbuf[last >> 2] = __builtin_bswap32(ctx->wbuf[last >> 2]); - - if(last > SHA256_BLOCK_SIZE - 9) { - if(last < 60) ctx->wbuf[15] = 0; - sha256_process(ctx); - last = 0; - } else - last = (last >> 2) + 1; - - while(last < 14) ctx->wbuf[last++] = 0; - - ctx->wbuf[14] = __builtin_bswap32((ctx->total[0] >> 29) | (ctx->total[1] << 3)); - ctx->wbuf[15] = __builtin_bswap32(ctx->total[0] << 3); - sha256_process(ctx); - - memcpy_output_bswap32(output, ctx->state); - memset(ctx, 0, sizeof(sha256_context)); -} - -static const uint32_t initial_state[8] = { - 0x6a09e667, - 0xbb67ae85, - 0x3c6ef372, - 0xa54ff53a, - 0x510e527f, - 0x9b05688c, - 0x1f83d9ab, - 0x5be0cd19}; - -void sha256_start(sha256_context* ctx) { - ctx->total[0] = ctx->total[1] = 0; - memcpy(ctx->state, initial_state, 8 * sizeof(uint32_t)); -} - -void sha256(const unsigned char* input, unsigned int ilen, unsigned char output[32]) { - sha256_context ctx; - - sha256_start(&ctx); - sha256_update(&ctx, input, ilen); - sha256_finish(&ctx, output); -} diff --git a/lib/toolbox/sha256.h b/lib/toolbox/sha256.h deleted file mode 100644 index c544d3ebb1d..00000000000 --- a/lib/toolbox/sha256.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#define SHA256_DIGEST_SIZE 32 -#define SHA256_BLOCK_SIZE 64 - -typedef struct { - uint32_t total[2]; - uint32_t state[8]; - uint32_t wbuf[16]; -} sha256_context; - -void sha256(const unsigned char* input, unsigned int ilen, unsigned char output[32]); -void sha256_start(sha256_context* ctx); -void sha256_finish(sha256_context* ctx, unsigned char output[32]); -void sha256_update(sha256_context* ctx, const unsigned char* input, unsigned int ilen); -void sha256_process(sha256_context* ctx); - -#ifdef __cplusplus -} -#endif \ No newline at end of file diff --git a/lib/u8g2/SConscript b/lib/u8g2/SConscript new file mode 100644 index 00000000000..dacdfbacb4b --- /dev/null +++ b/lib/u8g2/SConscript @@ -0,0 +1,20 @@ +Import("env") + +env.Append( + CPPPATH=[ + "#/lib/u8g2", + ], + LINT_SOURCES=[ + Dir("."), + ], +) + + +libenv = env.Clone(FW_LIB_NAME="u8g2") +libenv.ApplyLibFlags() + +sources = libenv.GlobRecursive("*.c") + +lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources) +libenv.Install("${LIB_DIST_DIR}", lib) +Return("lib") diff --git a/lib/update_util/SConscript b/lib/update_util/SConscript new file mode 100644 index 00000000000..c818973d978 --- /dev/null +++ b/lib/update_util/SConscript @@ -0,0 +1,16 @@ +Import("env") + +env.Append( + LINT_SOURCES=[ + Dir("."), + ], +) + +libenv = env.Clone(FW_LIB_NAME="update_util") +libenv.ApplyLibFlags() + +sources = libenv.GlobRecursive("*.c") + +lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources) +libenv.Install("${LIB_DIST_DIR}", lib) +Return("lib") diff --git a/scripts/fbt_tools/fbt_apps.py b/scripts/fbt_tools/fbt_apps.py index dadf6dc0c8c..7e0aec5ea60 100644 --- a/scripts/fbt_tools/fbt_apps.py +++ b/scripts/fbt_tools/fbt_apps.py @@ -125,7 +125,6 @@ def LoadAppManifest(env, entry): app_manifest_file_path = manifest_glob[0].rfile().abspath env["APPMGR"].load_manifest(app_manifest_file_path, entry) - env.Append(PY_LINT_SOURCES=[app_manifest_file_path]) except FlipperManifestException as e: if not GetOption("silent"): warn(WarningOnByDefault, str(e)) diff --git a/scripts/fbt_tools/fbt_version.py b/scripts/fbt_tools/fbt_version.py index e64167b3dc5..0dd5d0feb91 100644 --- a/scripts/fbt_tools/fbt_version.py +++ b/scripts/fbt_tools/fbt_version.py @@ -32,9 +32,9 @@ def generate(env): "${TARGET.dir.posix}", "--dir", "${ROOT_DIR}", - "${VERSIONCOMSTR}", ] - ] + ], + "${VERSIONCOMSTR}", ), emitter=_version_emitter, ), diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 7835718defa..cd89b554af8 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,47.0,, +Version,+,48.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -77,8 +77,13 @@ Header,+,lib/libusb_stm32/inc/usb_std.h,, Header,+,lib/libusb_stm32/inc/usb_tmc.h,, Header,+,lib/libusb_stm32/inc/usbd_core.h,, Header,+,lib/mbedtls/include/mbedtls/des.h,, +Header,+,lib/mbedtls/include/mbedtls/ecdh.h,, +Header,+,lib/mbedtls/include/mbedtls/ecdsa.h,, +Header,+,lib/mbedtls/include/mbedtls/ecp.h,, +Header,+,lib/mbedtls/include/mbedtls/md.h,, +Header,+,lib/mbedtls/include/mbedtls/md5.h,, Header,+,lib/mbedtls/include/mbedtls/sha1.h,, -Header,+,lib/micro-ecc/uECC.h,, +Header,+,lib/mbedtls/include/mbedtls/sha256.h,, Header,+,lib/mlib/m-algo.h,, Header,+,lib/mlib/m-array.h,, Header,+,lib/mlib/m-bptree.h,, @@ -136,13 +141,11 @@ Header,+,lib/toolbox/float_tools.h,, Header,+,lib/toolbox/hex.h,, Header,+,lib/toolbox/manchester_decoder.h,, Header,+,lib/toolbox/manchester_encoder.h,, -Header,+,lib/toolbox/md5.h,, Header,+,lib/toolbox/name_generator.h,, Header,+,lib/toolbox/path.h,, Header,+,lib/toolbox/pretty_format.h,, Header,+,lib/toolbox/protocols/protocol_dict.h,, Header,+,lib/toolbox/saved_struct.h,, -Header,+,lib/toolbox/sha256.h,, Header,+,lib/toolbox/simple_array.h,, Header,+,lib/toolbox/stream/buffered_file_stream.h,, Header,+,lib/toolbox/stream/file_stream.h,, @@ -452,7 +455,6 @@ Function,-,_system_r,int,"_reent*, const char*" Function,-,_tempnam_r,char*,"_reent*, const char*, const char*" Function,-,_tmpfile_r,FILE*,_reent* Function,-,_tmpnam_r,char*,"_reent*, char*" -Function,-,_tzset_r,void,_reent* Function,-,_ungetc_r,int,"_reent*, int, FILE*" Function,-,_unsetenv_r,int,"_reent*, const char*" Function,-,_vasiprintf_r,int,"_reent*, char**, const char*, __gnuc_va_list" @@ -499,8 +501,6 @@ Function,+,args_read_hex_bytes,_Bool,"FuriString*, uint8_t*, size_t" Function,+,args_read_int_and_trim,_Bool,"FuriString*, int*" Function,+,args_read_probably_quoted_string_and_trim,_Bool,"FuriString*, FuriString*" Function,+,args_read_string_and_trim,_Bool,"FuriString*, FuriString*" -Function,-,asctime,char*,const tm* -Function,-,asctime_r,char*,"const tm*, char*" Function,-,asin,double,double Function,-,asinf,float,float Function,-,asinh,double,double @@ -611,7 +611,7 @@ Function,+,byte_input_get_view,View*,ByteInput* Function,+,byte_input_set_header_text,void,"ByteInput*, const char*" Function,+,byte_input_set_result_callback,void,"ByteInput*, ByteInputCallback, ByteChangedCallback, void*, uint8_t*, uint8_t" Function,-,bzero,void,"void*, size_t" -Function,-,calloc,void*,"size_t, size_t" +Function,+,calloc,void*,"size_t, size_t" Function,+,canvas_clear,void,Canvas* Function,+,canvas_commit,void,Canvas* Function,+,canvas_current_font_height,uint8_t,const Canvas* @@ -665,7 +665,6 @@ Function,+,cli_read_timeout,size_t,"Cli*, uint8_t*, size_t, uint32_t" Function,+,cli_session_close,void,Cli* Function,+,cli_session_open,void,"Cli*, void*" Function,+,cli_write,void,"Cli*, const uint8_t*, size_t" -Function,-,clock,clock_t, Function,+,composite_api_resolver_add,void,"CompositeApiResolver*, const ElfApiInterface*" Function,+,composite_api_resolver_alloc,CompositeApiResolver*, Function,+,composite_api_resolver_free,void,CompositeApiResolver* @@ -689,8 +688,6 @@ Function,-,cosl,long double,long double Function,+,crc32_calc_buffer,uint32_t,"uint32_t, const void*, size_t" Function,+,crc32_calc_file,uint32_t,"File*, const FileCrcProgressCb, void*" Function,-,ctermid,char*,char* -Function,-,ctime,char*,const time_t* -Function,-,ctime_r,char*,"const time_t*, char*" Function,-,cuserid,char*,char* Function,+,dialog_ex_alloc,DialogEx*, Function,+,dialog_ex_disable_extended_events,void,DialogEx* @@ -716,7 +713,6 @@ Function,+,dialog_message_set_icon,void,"DialogMessage*, const Icon*, uint8_t, u Function,+,dialog_message_set_text,void,"DialogMessage*, const char*, uint8_t, uint8_t, Align, Align" Function,+,dialog_message_show,DialogMessageButton,"DialogsApp*, const DialogMessage*" Function,+,dialog_message_show_storage_error,void,"DialogsApp*, const char*" -Function,-,difftime,double,"time_t, time_t" Function,+,digital_sequence_add_signal,void,"DigitalSequence*, uint8_t" Function,-,digital_sequence_alloc,DigitalSequence*,"uint32_t, const GpioPin*" Function,-,digital_sequence_clear,void,DigitalSequence* @@ -1518,8 +1514,6 @@ Function,-,getenv,char*,const char* Function,-,gets,char*,char* Function,-,getsubopt,int,"char**, char**, char**" Function,-,getw,int,FILE* -Function,-,gmtime,tm*,const time_t* -Function,-,gmtime_r,tm*,"const time_t*, tm*" Function,+,gui_add_framebuffer_callback,void,"Gui*, GuiCanvasCommitCallback, void*" Function,+,gui_add_view_port,void,"Gui*, ViewPort*, GuiLayer" Function,+,gui_direct_draw_acquire,Canvas*,Gui* @@ -1637,8 +1631,6 @@ Function,+,locale_get_time_format,LocaleTimeFormat, Function,+,locale_set_date_format,void,LocaleDateFormat Function,+,locale_set_measurement_unit,void,LocaleMeasurementUnits Function,+,locale_set_time_format,void,LocaleTimeFormat -Function,-,localtime,tm*,const time_t* -Function,-,localtime_r,tm*,"const time_t*, tm*" Function,-,log,double,double Function,-,log10,double,double Function,-,log10f,float,float @@ -1682,29 +1674,167 @@ Function,-,mbedtls_des_init,void,mbedtls_des_context* Function,-,mbedtls_des_key_check_key_parity,int,const unsigned char[8] Function,-,mbedtls_des_key_check_weak,int,const unsigned char[8] Function,-,mbedtls_des_key_set_parity,void,unsigned char[8] -Function,-,mbedtls_des_self_test,int,int Function,-,mbedtls_des_setkey,void,"uint32_t[32], const unsigned char[8]" Function,-,mbedtls_des_setkey_dec,int,"mbedtls_des_context*, const unsigned char[8]" Function,-,mbedtls_des_setkey_enc,int,"mbedtls_des_context*, const unsigned char[8]" +Function,-,mbedtls_ecdh_calc_secret,int,"mbedtls_ecdh_context*, size_t*, unsigned char*, size_t, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_ecdh_can_do,int,mbedtls_ecp_group_id +Function,-,mbedtls_ecdh_compute_shared,int,"mbedtls_ecp_group*, mbedtls_mpi*, const mbedtls_ecp_point*, const mbedtls_mpi*, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_ecdh_free,void,mbedtls_ecdh_context* +Function,-,mbedtls_ecdh_gen_public,int,"mbedtls_ecp_group*, mbedtls_mpi*, mbedtls_ecp_point*, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_ecdh_get_params,int,"mbedtls_ecdh_context*, const mbedtls_ecp_keypair*, mbedtls_ecdh_side" +Function,-,mbedtls_ecdh_init,void,mbedtls_ecdh_context* +Function,-,mbedtls_ecdh_make_params,int,"mbedtls_ecdh_context*, size_t*, unsigned char*, size_t, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_ecdh_make_public,int,"mbedtls_ecdh_context*, size_t*, unsigned char*, size_t, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_ecdh_read_params,int,"mbedtls_ecdh_context*, const unsigned char**, const unsigned char*" +Function,-,mbedtls_ecdh_read_public,int,"mbedtls_ecdh_context*, const unsigned char*, size_t" +Function,-,mbedtls_ecdh_setup,int,"mbedtls_ecdh_context*, mbedtls_ecp_group_id" +Function,-,mbedtls_ecdsa_can_do,int,mbedtls_ecp_group_id +Function,-,mbedtls_ecdsa_free,void,mbedtls_ecdsa_context* +Function,-,mbedtls_ecdsa_from_keypair,int,"mbedtls_ecdsa_context*, const mbedtls_ecp_keypair*" +Function,-,mbedtls_ecdsa_genkey,int,"mbedtls_ecdsa_context*, mbedtls_ecp_group_id, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_ecdsa_init,void,mbedtls_ecdsa_context* +Function,-,mbedtls_ecdsa_read_signature,int,"mbedtls_ecdsa_context*, const unsigned char*, size_t, const unsigned char*, size_t" +Function,-,mbedtls_ecdsa_read_signature_restartable,int,"mbedtls_ecdsa_context*, const unsigned char*, size_t, const unsigned char*, size_t, mbedtls_ecdsa_restart_ctx*" +Function,-,mbedtls_ecdsa_sign,int,"mbedtls_ecp_group*, mbedtls_mpi*, mbedtls_mpi*, const mbedtls_mpi*, const unsigned char*, size_t, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_ecdsa_sign_restartable,int,"mbedtls_ecp_group*, mbedtls_mpi*, mbedtls_mpi*, const mbedtls_mpi*, const unsigned char*, size_t, int (*)(void*, unsigned char*, size_t), void*, int (*)(void*, unsigned char*, size_t), void*, mbedtls_ecdsa_restart_ctx*" +Function,-,mbedtls_ecdsa_verify,int,"mbedtls_ecp_group*, const unsigned char*, size_t, const mbedtls_ecp_point*, const mbedtls_mpi*, const mbedtls_mpi*" +Function,-,mbedtls_ecdsa_verify_restartable,int,"mbedtls_ecp_group*, const unsigned char*, size_t, const mbedtls_ecp_point*, const mbedtls_mpi*, const mbedtls_mpi*, mbedtls_ecdsa_restart_ctx*" +Function,-,mbedtls_ecdsa_write_signature,int,"mbedtls_ecdsa_context*, mbedtls_md_type_t, const unsigned char*, size_t, unsigned char*, size_t, size_t*, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_ecdsa_write_signature_restartable,int,"mbedtls_ecdsa_context*, mbedtls_md_type_t, const unsigned char*, size_t, unsigned char*, size_t, size_t*, int (*)(void*, unsigned char*, size_t), void*, mbedtls_ecdsa_restart_ctx*" +Function,-,mbedtls_ecp_check_privkey,int,"const mbedtls_ecp_group*, const mbedtls_mpi*" +Function,-,mbedtls_ecp_check_pub_priv,int,"const mbedtls_ecp_keypair*, const mbedtls_ecp_keypair*, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_ecp_check_pubkey,int,"const mbedtls_ecp_group*, const mbedtls_ecp_point*" +Function,-,mbedtls_ecp_copy,int,"mbedtls_ecp_point*, const mbedtls_ecp_point*" +Function,-,mbedtls_ecp_curve_info_from_grp_id,const mbedtls_ecp_curve_info*,mbedtls_ecp_group_id +Function,-,mbedtls_ecp_curve_info_from_name,const mbedtls_ecp_curve_info*,const char* +Function,-,mbedtls_ecp_curve_info_from_tls_id,const mbedtls_ecp_curve_info*,uint16_t +Function,-,mbedtls_ecp_curve_list,const mbedtls_ecp_curve_info*, +Function,-,mbedtls_ecp_export,int,"const mbedtls_ecp_keypair*, mbedtls_ecp_group*, mbedtls_mpi*, mbedtls_ecp_point*" +Function,-,mbedtls_ecp_gen_key,int,"mbedtls_ecp_group_id, mbedtls_ecp_keypair*, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_ecp_gen_keypair,int,"mbedtls_ecp_group*, mbedtls_mpi*, mbedtls_ecp_point*, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_ecp_gen_keypair_base,int,"mbedtls_ecp_group*, const mbedtls_ecp_point*, mbedtls_mpi*, mbedtls_ecp_point*, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_ecp_gen_privkey,int,"const mbedtls_ecp_group*, mbedtls_mpi*, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_ecp_get_type,mbedtls_ecp_curve_type,const mbedtls_ecp_group* +Function,-,mbedtls_ecp_group_copy,int,"mbedtls_ecp_group*, const mbedtls_ecp_group*" +Function,-,mbedtls_ecp_group_free,void,mbedtls_ecp_group* +Function,-,mbedtls_ecp_group_init,void,mbedtls_ecp_group* +Function,-,mbedtls_ecp_group_load,int,"mbedtls_ecp_group*, mbedtls_ecp_group_id" +Function,-,mbedtls_ecp_grp_id_list,const mbedtls_ecp_group_id*, +Function,-,mbedtls_ecp_is_zero,int,mbedtls_ecp_point* +Function,-,mbedtls_ecp_keypair_free,void,mbedtls_ecp_keypair* +Function,-,mbedtls_ecp_keypair_init,void,mbedtls_ecp_keypair* +Function,-,mbedtls_ecp_mul,int,"mbedtls_ecp_group*, mbedtls_ecp_point*, const mbedtls_mpi*, const mbedtls_ecp_point*, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_ecp_mul_restartable,int,"mbedtls_ecp_group*, mbedtls_ecp_point*, const mbedtls_mpi*, const mbedtls_ecp_point*, int (*)(void*, unsigned char*, size_t), void*, mbedtls_ecp_restart_ctx*" +Function,-,mbedtls_ecp_muladd,int,"mbedtls_ecp_group*, mbedtls_ecp_point*, const mbedtls_mpi*, const mbedtls_ecp_point*, const mbedtls_mpi*, const mbedtls_ecp_point*" +Function,-,mbedtls_ecp_muladd_restartable,int,"mbedtls_ecp_group*, mbedtls_ecp_point*, const mbedtls_mpi*, const mbedtls_ecp_point*, const mbedtls_mpi*, const mbedtls_ecp_point*, mbedtls_ecp_restart_ctx*" +Function,-,mbedtls_ecp_point_cmp,int,"const mbedtls_ecp_point*, const mbedtls_ecp_point*" +Function,-,mbedtls_ecp_point_free,void,mbedtls_ecp_point* +Function,-,mbedtls_ecp_point_init,void,mbedtls_ecp_point* +Function,-,mbedtls_ecp_point_read_binary,int,"const mbedtls_ecp_group*, mbedtls_ecp_point*, const unsigned char*, size_t" +Function,-,mbedtls_ecp_point_read_string,int,"mbedtls_ecp_point*, int, const char*, const char*" +Function,-,mbedtls_ecp_point_write_binary,int,"const mbedtls_ecp_group*, const mbedtls_ecp_point*, int, size_t*, unsigned char*, size_t" +Function,-,mbedtls_ecp_read_key,int,"mbedtls_ecp_group_id, mbedtls_ecp_keypair*, const unsigned char*, size_t" +Function,-,mbedtls_ecp_set_zero,int,mbedtls_ecp_point* +Function,-,mbedtls_ecp_tls_read_group,int,"mbedtls_ecp_group*, const unsigned char**, size_t" +Function,-,mbedtls_ecp_tls_read_group_id,int,"mbedtls_ecp_group_id*, const unsigned char**, size_t" +Function,-,mbedtls_ecp_tls_read_point,int,"const mbedtls_ecp_group*, mbedtls_ecp_point*, const unsigned char**, size_t" +Function,-,mbedtls_ecp_tls_write_group,int,"const mbedtls_ecp_group*, size_t*, unsigned char*, size_t" +Function,-,mbedtls_ecp_tls_write_point,int,"const mbedtls_ecp_group*, const mbedtls_ecp_point*, int, size_t*, unsigned char*, size_t" +Function,-,mbedtls_ecp_write_key,int,"mbedtls_ecp_keypair*, unsigned char*, size_t" +Function,-,mbedtls_internal_md5_process,int,"mbedtls_md5_context*, const unsigned char[64]" Function,-,mbedtls_internal_sha1_process,int,"mbedtls_sha1_context*, const unsigned char[64]" -Function,-,mbedtls_platform_gmtime_r,tm*,"const mbedtls_time_t*, tm*" +Function,-,mbedtls_internal_sha256_process,int,"mbedtls_sha256_context*, const unsigned char[64]" +Function,-,mbedtls_md,int,"const mbedtls_md_info_t*, const unsigned char*, size_t, unsigned char*" +Function,-,mbedtls_md5,int,"const unsigned char*, size_t, unsigned char[16]" +Function,-,mbedtls_md5_clone,void,"mbedtls_md5_context*, const mbedtls_md5_context*" +Function,-,mbedtls_md5_finish,int,"mbedtls_md5_context*, unsigned char[16]" +Function,-,mbedtls_md5_free,void,mbedtls_md5_context* +Function,-,mbedtls_md5_init,void,mbedtls_md5_context* +Function,-,mbedtls_md5_starts,int,mbedtls_md5_context* +Function,-,mbedtls_md5_update,int,"mbedtls_md5_context*, const unsigned char*, size_t" +Function,-,mbedtls_md_clone,int,"mbedtls_md_context_t*, const mbedtls_md_context_t*" +Function,-,mbedtls_md_finish,int,"mbedtls_md_context_t*, unsigned char*" +Function,-,mbedtls_md_free,void,mbedtls_md_context_t* +Function,-,mbedtls_md_get_name,const char*,const mbedtls_md_info_t* +Function,-,mbedtls_md_get_size,unsigned char,const mbedtls_md_info_t* +Function,-,mbedtls_md_get_type,mbedtls_md_type_t,const mbedtls_md_info_t* +Function,-,mbedtls_md_hmac,int,"const mbedtls_md_info_t*, const unsigned char*, size_t, const unsigned char*, size_t, unsigned char*" +Function,-,mbedtls_md_hmac_finish,int,"mbedtls_md_context_t*, unsigned char*" +Function,-,mbedtls_md_hmac_reset,int,mbedtls_md_context_t* +Function,-,mbedtls_md_hmac_starts,int,"mbedtls_md_context_t*, const unsigned char*, size_t" +Function,-,mbedtls_md_hmac_update,int,"mbedtls_md_context_t*, const unsigned char*, size_t" +Function,-,mbedtls_md_info_from_ctx,const mbedtls_md_info_t*,const mbedtls_md_context_t* +Function,-,mbedtls_md_info_from_string,const mbedtls_md_info_t*,const char* +Function,-,mbedtls_md_info_from_type,const mbedtls_md_info_t*,mbedtls_md_type_t +Function,-,mbedtls_md_init,void,mbedtls_md_context_t* +Function,-,mbedtls_md_list,const int*, +Function,-,mbedtls_md_setup,int,"mbedtls_md_context_t*, const mbedtls_md_info_t*, int" +Function,-,mbedtls_md_starts,int,mbedtls_md_context_t* +Function,-,mbedtls_md_update,int,"mbedtls_md_context_t*, const unsigned char*, size_t" +Function,-,mbedtls_mpi_add_abs,int,"mbedtls_mpi*, const mbedtls_mpi*, const mbedtls_mpi*" +Function,-,mbedtls_mpi_add_int,int,"mbedtls_mpi*, const mbedtls_mpi*, mbedtls_mpi_sint" +Function,-,mbedtls_mpi_add_mpi,int,"mbedtls_mpi*, const mbedtls_mpi*, const mbedtls_mpi*" +Function,-,mbedtls_mpi_bitlen,size_t,const mbedtls_mpi* +Function,-,mbedtls_mpi_cmp_abs,int,"const mbedtls_mpi*, const mbedtls_mpi*" +Function,-,mbedtls_mpi_cmp_int,int,"const mbedtls_mpi*, mbedtls_mpi_sint" +Function,-,mbedtls_mpi_cmp_mpi,int,"const mbedtls_mpi*, const mbedtls_mpi*" +Function,-,mbedtls_mpi_copy,int,"mbedtls_mpi*, const mbedtls_mpi*" +Function,-,mbedtls_mpi_div_int,int,"mbedtls_mpi*, mbedtls_mpi*, const mbedtls_mpi*, mbedtls_mpi_sint" +Function,-,mbedtls_mpi_div_mpi,int,"mbedtls_mpi*, mbedtls_mpi*, const mbedtls_mpi*, const mbedtls_mpi*" +Function,-,mbedtls_mpi_exp_mod,int,"mbedtls_mpi*, const mbedtls_mpi*, const mbedtls_mpi*, const mbedtls_mpi*, mbedtls_mpi*" +Function,-,mbedtls_mpi_fill_random,int,"mbedtls_mpi*, size_t, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_mpi_free,void,mbedtls_mpi* +Function,-,mbedtls_mpi_gcd,int,"mbedtls_mpi*, const mbedtls_mpi*, const mbedtls_mpi*" +Function,-,mbedtls_mpi_gen_prime,int,"mbedtls_mpi*, size_t, int, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_mpi_get_bit,int,"const mbedtls_mpi*, size_t" +Function,-,mbedtls_mpi_grow,int,"mbedtls_mpi*, size_t" +Function,-,mbedtls_mpi_init,void,mbedtls_mpi* +Function,-,mbedtls_mpi_inv_mod,int,"mbedtls_mpi*, const mbedtls_mpi*, const mbedtls_mpi*" +Function,-,mbedtls_mpi_is_prime_ext,int,"const mbedtls_mpi*, int, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_mpi_lsb,size_t,const mbedtls_mpi* +Function,-,mbedtls_mpi_lset,int,"mbedtls_mpi*, mbedtls_mpi_sint" +Function,-,mbedtls_mpi_lt_mpi_ct,int,"const mbedtls_mpi*, const mbedtls_mpi*, unsigned*" +Function,-,mbedtls_mpi_mod_int,int,"mbedtls_mpi_uint*, const mbedtls_mpi*, mbedtls_mpi_sint" +Function,-,mbedtls_mpi_mod_mpi,int,"mbedtls_mpi*, const mbedtls_mpi*, const mbedtls_mpi*" +Function,-,mbedtls_mpi_mul_int,int,"mbedtls_mpi*, const mbedtls_mpi*, mbedtls_mpi_uint" +Function,-,mbedtls_mpi_mul_mpi,int,"mbedtls_mpi*, const mbedtls_mpi*, const mbedtls_mpi*" +Function,-,mbedtls_mpi_random,int,"mbedtls_mpi*, mbedtls_mpi_sint, const mbedtls_mpi*, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_mpi_read_binary,int,"mbedtls_mpi*, const unsigned char*, size_t" +Function,-,mbedtls_mpi_read_binary_le,int,"mbedtls_mpi*, const unsigned char*, size_t" +Function,-,mbedtls_mpi_read_string,int,"mbedtls_mpi*, int, const char*" +Function,-,mbedtls_mpi_safe_cond_assign,int,"mbedtls_mpi*, const mbedtls_mpi*, unsigned char" +Function,-,mbedtls_mpi_safe_cond_swap,int,"mbedtls_mpi*, mbedtls_mpi*, unsigned char" +Function,-,mbedtls_mpi_set_bit,int,"mbedtls_mpi*, size_t, unsigned char" +Function,-,mbedtls_mpi_shift_l,int,"mbedtls_mpi*, size_t" +Function,-,mbedtls_mpi_shift_r,int,"mbedtls_mpi*, size_t" +Function,-,mbedtls_mpi_shrink,int,"mbedtls_mpi*, size_t" +Function,-,mbedtls_mpi_size,size_t,const mbedtls_mpi* +Function,-,mbedtls_mpi_sub_abs,int,"mbedtls_mpi*, const mbedtls_mpi*, const mbedtls_mpi*" +Function,-,mbedtls_mpi_sub_int,int,"mbedtls_mpi*, const mbedtls_mpi*, mbedtls_mpi_sint" +Function,-,mbedtls_mpi_sub_mpi,int,"mbedtls_mpi*, const mbedtls_mpi*, const mbedtls_mpi*" +Function,-,mbedtls_mpi_swap,void,"mbedtls_mpi*, mbedtls_mpi*" +Function,-,mbedtls_mpi_write_binary,int,"const mbedtls_mpi*, unsigned char*, size_t" +Function,-,mbedtls_mpi_write_binary_le,int,"const mbedtls_mpi*, unsigned char*, size_t" +Function,-,mbedtls_mpi_write_string,int,"const mbedtls_mpi*, int, char*, size_t, size_t*" Function,-,mbedtls_platform_zeroize,void,"void*, size_t" Function,-,mbedtls_sha1,int,"const unsigned char*, size_t, unsigned char[20]" Function,-,mbedtls_sha1_clone,void,"mbedtls_sha1_context*, const mbedtls_sha1_context*" Function,-,mbedtls_sha1_finish,int,"mbedtls_sha1_context*, unsigned char[20]" Function,-,mbedtls_sha1_free,void,mbedtls_sha1_context* Function,-,mbedtls_sha1_init,void,mbedtls_sha1_context* -Function,-,mbedtls_sha1_self_test,int,int Function,-,mbedtls_sha1_starts,int,mbedtls_sha1_context* Function,-,mbedtls_sha1_update,int,"mbedtls_sha1_context*, const unsigned char*, size_t" +Function,-,mbedtls_sha256,int,"const unsigned char*, size_t, unsigned char*, int" +Function,-,mbedtls_sha256_clone,void,"mbedtls_sha256_context*, const mbedtls_sha256_context*" +Function,-,mbedtls_sha256_finish,int,"mbedtls_sha256_context*, unsigned char*" +Function,-,mbedtls_sha256_free,void,mbedtls_sha256_context* +Function,-,mbedtls_sha256_init,void,mbedtls_sha256_context* +Function,-,mbedtls_sha256_starts,int,"mbedtls_sha256_context*, int" +Function,-,mbedtls_sha256_update,int,"mbedtls_sha256_context*, const unsigned char*, size_t" Function,-,mblen,int,"const char*, size_t" Function,-,mbstowcs,size_t,"wchar_t*, const char*, size_t" Function,-,mbtowc,int,"wchar_t*, const char*, size_t" -Function,+,md5,void,"const unsigned char*, size_t, unsigned char[16]" -Function,+,md5_finish,void,"md5_context*, unsigned char[16]" -Function,+,md5_process,void,"md5_context*, const unsigned char[64]" -Function,+,md5_starts,void,md5_context* -Function,+,md5_update,void,"md5_context*, const unsigned char*, size_t" Function,-,memccpy,void*,"void*, const void*, int, size_t" Function,+,memchr,void*,"const void*, int, size_t" Function,+,memcmp,int,"const void*, const void*, size_t" @@ -1737,7 +1867,6 @@ Function,-,mkostemps,int,"char*, int, int" Function,-,mkstemp,int,char* Function,-,mkstemps,int,"char*, int" Function,-,mktemp,char*,char* -Function,-,mktime,time_t,tm* Function,-,modf,double,"double, double*" Function,-,modff,float,"float, float*" Function,-,modfl,long double,"long double, long double*" @@ -2003,11 +2132,6 @@ Function,-,setkey,void,const char* Function,-,setlinebuf,int,FILE* Function,-,setstate,char*,char* Function,-,setvbuf,int,"FILE*, char*, int, size_t" -Function,+,sha256,void,"const unsigned char*, unsigned int, unsigned char[32]" -Function,+,sha256_finish,void,"sha256_context*, unsigned char[32]" -Function,+,sha256_process,void,sha256_context* -Function,+,sha256_start,void,sha256_context* -Function,+,sha256_update,void,"sha256_context*, const unsigned char*, unsigned int" Function,+,signal_reader_alloc,SignalReader*,"const GpioPin*, uint32_t" Function,+,signal_reader_free,void,SignalReader* Function,+,signal_reader_set_polarity,void,"SignalReader*, SignalReaderPolarity" @@ -2171,8 +2295,6 @@ Function,+,stream_write_vaformat,size_t,"Stream*, const char*, va_list" Function,-,strerror,char*,int Function,-,strerror_l,char*,"int, locale_t" Function,-,strerror_r,char*,"int, char*, size_t" -Function,-,strftime,size_t,"char*, size_t, const char*, const tm*" -Function,-,strftime_l,size_t,"char*, size_t, const char*, const tm*, locale_t" Function,+,string_stream_alloc,Stream*, Function,-,strlcat,size_t,"char*, const char*, size_t" Function,+,strlcpy,size_t,"char*, const char*, size_t" @@ -2187,8 +2309,6 @@ Function,-,strndup,char*,"const char*, size_t" Function,-,strnlen,size_t,"const char*, size_t" Function,-,strnstr,char*,"const char*, const char*, size_t" Function,-,strpbrk,char*,"const char*, const char*" -Function,-,strptime,char*,"const char*, const char*, tm*" -Function,-,strptime_l,char*,"const char*, const char*, tm*, locale_t" Function,+,strrchr,char*,"const char*, int" Function,-,strsep,char*,"char**, const char*" Function,-,strsignal,char*,int @@ -2263,7 +2383,6 @@ Function,+,text_input_set_validator,void,"TextInput*, TextInputValidatorCallback Function,-,tgamma,double,double Function,-,tgammaf,float,float Function,-,tgammal,long double,long double -Function,-,time,time_t,time_t* Function,-,timingsafe_bcmp,int,"const void*, const void*, size_t" Function,-,timingsafe_memcmp,int,"const void*, const void*, size_t" Function,-,tmpfile,FILE*, @@ -2277,25 +2396,6 @@ Function,-,toupper_l,int,"int, locale_t" Function,-,trunc,double,double Function,-,truncf,float,float Function,-,truncl,long double,long double -Function,-,tzset,void, -Function,-,uECC_compress,void,"const uint8_t*, uint8_t*, uECC_Curve" -Function,+,uECC_compute_public_key,int,"const uint8_t*, uint8_t*, uECC_Curve" -Function,-,uECC_curve_private_key_size,int,uECC_Curve -Function,-,uECC_curve_public_key_size,int,uECC_Curve -Function,-,uECC_decompress,void,"const uint8_t*, uint8_t*, uECC_Curve" -Function,-,uECC_get_rng,uECC_RNG_Function, -Function,-,uECC_make_key,int,"uint8_t*, uint8_t*, uECC_Curve" -Function,-,uECC_secp160r1,uECC_Curve, -Function,-,uECC_secp192r1,uECC_Curve, -Function,-,uECC_secp224r1,uECC_Curve, -Function,-,uECC_secp256k1,uECC_Curve, -Function,+,uECC_secp256r1,uECC_Curve, -Function,+,uECC_set_rng,void,uECC_RNG_Function -Function,-,uECC_shared_secret,int,"const uint8_t*, const uint8_t*, uint8_t*, uECC_Curve" -Function,+,uECC_sign,int,"const uint8_t*, const uint8_t*, unsigned, uint8_t*, uECC_Curve" -Function,-,uECC_sign_deterministic,int,"const uint8_t*, const uint8_t*, unsigned, const uECC_HashContext*, uint8_t*, uECC_Curve" -Function,-,uECC_valid_public_key,int,"const uint8_t*, uECC_Curve" -Function,-,uECC_verify,int,"const uint8_t*, const uint8_t*, unsigned, const uint8_t*, uECC_Curve" Function,+,uint8_to_hex_chars,void,"const uint8_t*, uint8_t*, int" Function,-,ungetc,int,"int, FILE*" Function,-,unsetenv,int,const char* @@ -2428,13 +2528,10 @@ Variable,-,MSIRangeTable,const uint32_t[16], Variable,-,SmpsPrescalerTable,const uint32_t[4][6], Variable,+,SystemCoreClock,uint32_t, Variable,+,_ctype_,const char[], -Variable,-,_daylight,int, Variable,+,_global_impure_ptr,_reent*, Variable,+,_impure_ptr,_reent*, Variable,-,_sys_errlist,const char*[], Variable,-,_sys_nerr,int, -Variable,-,_timezone,long, -Variable,-,_tzname,char*[2], Variable,+,cli_vcp,CliSession, Variable,+,firmware_api_interface,const ElfApiInterface*, Variable,+,furi_hal_i2c_bus_external,FuriHalI2cBus, diff --git a/targets/f18/target.json b/targets/f18/target.json index 19de9dd6156..e021a5b2296 100644 --- a/targets/f18/target.json +++ b/targets/f18/target.json @@ -18,7 +18,6 @@ "hwdrivers", "fatfs", "littlefs", - "flipperformat", "toolbox", "digital_signal", "signal_reader", @@ -28,12 +27,15 @@ "assets", "one_wire", "music_worker", - "misc", + "mbedtls", "flipper_application", - "flipperformat", "toolbox", + "u8g2", + "nanopb", + "update_util", + "heatshrink", + "flipperformat", "flipper18" - ], "excluded_sources": [ "furi_hal_infrared.c", @@ -63,4 +65,4 @@ "ibutton", "infrared" ] -} +} \ No newline at end of file diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index f31349f9c9d..45c98ae1a5f 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,47.0,, +Version,+,48.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -90,8 +90,13 @@ Header,+,lib/libusb_stm32/inc/usb_std.h,, Header,+,lib/libusb_stm32/inc/usb_tmc.h,, Header,+,lib/libusb_stm32/inc/usbd_core.h,, Header,+,lib/mbedtls/include/mbedtls/des.h,, +Header,+,lib/mbedtls/include/mbedtls/ecdh.h,, +Header,+,lib/mbedtls/include/mbedtls/ecdsa.h,, +Header,+,lib/mbedtls/include/mbedtls/ecp.h,, +Header,+,lib/mbedtls/include/mbedtls/md.h,, +Header,+,lib/mbedtls/include/mbedtls/md5.h,, Header,+,lib/mbedtls/include/mbedtls/sha1.h,, -Header,+,lib/micro-ecc/uECC.h,, +Header,+,lib/mbedtls/include/mbedtls/sha256.h,, Header,+,lib/mlib/m-algo.h,, Header,+,lib/mlib/m-array.h,, Header,+,lib/mlib/m-bptree.h,, @@ -200,13 +205,11 @@ Header,+,lib/toolbox/float_tools.h,, Header,+,lib/toolbox/hex.h,, Header,+,lib/toolbox/manchester_decoder.h,, Header,+,lib/toolbox/manchester_encoder.h,, -Header,+,lib/toolbox/md5.h,, Header,+,lib/toolbox/name_generator.h,, Header,+,lib/toolbox/path.h,, Header,+,lib/toolbox/pretty_format.h,, Header,+,lib/toolbox/protocols/protocol_dict.h,, Header,+,lib/toolbox/saved_struct.h,, -Header,+,lib/toolbox/sha256.h,, Header,+,lib/toolbox/simple_array.h,, Header,+,lib/toolbox/stream/buffered_file_stream.h,, Header,+,lib/toolbox/stream/file_stream.h,, @@ -521,7 +524,6 @@ Function,-,_system_r,int,"_reent*, const char*" Function,-,_tempnam_r,char*,"_reent*, const char*, const char*" Function,-,_tmpfile_r,FILE*,_reent* Function,-,_tmpnam_r,char*,"_reent*, char*" -Function,-,_tzset_r,void,_reent* Function,-,_ungetc_r,int,"_reent*, int, FILE*" Function,-,_unsetenv_r,int,"_reent*, const char*" Function,-,_vasiprintf_r,int,"_reent*, char**, const char*, __gnuc_va_list" @@ -568,8 +570,6 @@ Function,+,args_read_hex_bytes,_Bool,"FuriString*, uint8_t*, size_t" Function,+,args_read_int_and_trim,_Bool,"FuriString*, int*" Function,+,args_read_probably_quoted_string_and_trim,_Bool,"FuriString*, FuriString*" Function,+,args_read_string_and_trim,_Bool,"FuriString*, FuriString*" -Function,-,asctime,char*,const tm* -Function,-,asctime_r,char*,"const tm*, char*" Function,-,asin,double,double Function,-,asinf,float,float Function,-,asinh,double,double @@ -700,7 +700,7 @@ Function,+,byte_input_get_view,View*,ByteInput* Function,+,byte_input_set_header_text,void,"ByteInput*, const char*" Function,+,byte_input_set_result_callback,void,"ByteInput*, ByteInputCallback, ByteChangedCallback, void*, uint8_t*, uint8_t" Function,-,bzero,void,"void*, size_t" -Function,-,calloc,void*,"size_t, size_t" +Function,+,calloc,void*,"size_t, size_t" Function,+,canvas_clear,void,Canvas* Function,+,canvas_commit,void,Canvas* Function,+,canvas_current_font_height,uint8_t,const Canvas* @@ -754,7 +754,6 @@ Function,+,cli_read_timeout,size_t,"Cli*, uint8_t*, size_t, uint32_t" Function,+,cli_session_close,void,Cli* Function,+,cli_session_open,void,"Cli*, void*" Function,+,cli_write,void,"Cli*, const uint8_t*, size_t" -Function,-,clock,clock_t, Function,+,composite_api_resolver_add,void,"CompositeApiResolver*, const ElfApiInterface*" Function,+,composite_api_resolver_alloc,CompositeApiResolver*, Function,+,composite_api_resolver_free,void,CompositeApiResolver* @@ -778,8 +777,6 @@ Function,-,cosl,long double,long double Function,+,crc32_calc_buffer,uint32_t,"uint32_t, const void*, size_t" Function,+,crc32_calc_file,uint32_t,"File*, const FileCrcProgressCb, void*" Function,-,ctermid,char*,char* -Function,-,ctime,char*,const time_t* -Function,-,ctime_r,char*,"const time_t*, char*" Function,-,cuserid,char*,char* Function,+,dialog_ex_alloc,DialogEx*, Function,+,dialog_ex_disable_extended_events,void,DialogEx* @@ -805,7 +802,6 @@ Function,+,dialog_message_set_icon,void,"DialogMessage*, const Icon*, uint8_t, u Function,+,dialog_message_set_text,void,"DialogMessage*, const char*, uint8_t, uint8_t, Align, Align" Function,+,dialog_message_show,DialogMessageButton,"DialogsApp*, const DialogMessage*" Function,+,dialog_message_show_storage_error,void,"DialogsApp*, const char*" -Function,-,difftime,double,"time_t, time_t" Function,+,digital_sequence_add_signal,void,"DigitalSequence*, uint8_t" Function,-,digital_sequence_alloc,DigitalSequence*,"uint32_t, const GpioPin*" Function,-,digital_sequence_clear,void,DigitalSequence* @@ -1714,8 +1710,6 @@ Function,-,getenv,char*,const char* Function,-,gets,char*,char* Function,-,getsubopt,int,"char**, char**, char**" Function,-,getw,int,FILE* -Function,-,gmtime,tm*,const time_t* -Function,-,gmtime_r,tm*,"const time_t*, tm*" Function,+,gui_add_framebuffer_callback,void,"Gui*, GuiCanvasCommitCallback, void*" Function,+,gui_add_view_port,void,"Gui*, ViewPort*, GuiLayer" Function,+,gui_direct_draw_acquire,Canvas*,Gui* @@ -2036,8 +2030,6 @@ Function,+,locale_get_time_format,LocaleTimeFormat, Function,+,locale_set_date_format,void,LocaleDateFormat Function,+,locale_set_measurement_unit,void,LocaleMeasurementUnits Function,+,locale_set_time_format,void,LocaleTimeFormat -Function,-,localtime,tm*,const time_t* -Function,-,localtime_r,tm*,"const time_t*, tm*" Function,-,log,double,double Function,-,log10,double,double Function,-,log10f,float,float @@ -2081,29 +2073,167 @@ Function,-,mbedtls_des_init,void,mbedtls_des_context* Function,-,mbedtls_des_key_check_key_parity,int,const unsigned char[8] Function,-,mbedtls_des_key_check_weak,int,const unsigned char[8] Function,-,mbedtls_des_key_set_parity,void,unsigned char[8] -Function,-,mbedtls_des_self_test,int,int Function,-,mbedtls_des_setkey,void,"uint32_t[32], const unsigned char[8]" Function,-,mbedtls_des_setkey_dec,int,"mbedtls_des_context*, const unsigned char[8]" Function,-,mbedtls_des_setkey_enc,int,"mbedtls_des_context*, const unsigned char[8]" +Function,-,mbedtls_ecdh_calc_secret,int,"mbedtls_ecdh_context*, size_t*, unsigned char*, size_t, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_ecdh_can_do,int,mbedtls_ecp_group_id +Function,-,mbedtls_ecdh_compute_shared,int,"mbedtls_ecp_group*, mbedtls_mpi*, const mbedtls_ecp_point*, const mbedtls_mpi*, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_ecdh_free,void,mbedtls_ecdh_context* +Function,-,mbedtls_ecdh_gen_public,int,"mbedtls_ecp_group*, mbedtls_mpi*, mbedtls_ecp_point*, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_ecdh_get_params,int,"mbedtls_ecdh_context*, const mbedtls_ecp_keypair*, mbedtls_ecdh_side" +Function,-,mbedtls_ecdh_init,void,mbedtls_ecdh_context* +Function,-,mbedtls_ecdh_make_params,int,"mbedtls_ecdh_context*, size_t*, unsigned char*, size_t, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_ecdh_make_public,int,"mbedtls_ecdh_context*, size_t*, unsigned char*, size_t, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_ecdh_read_params,int,"mbedtls_ecdh_context*, const unsigned char**, const unsigned char*" +Function,-,mbedtls_ecdh_read_public,int,"mbedtls_ecdh_context*, const unsigned char*, size_t" +Function,-,mbedtls_ecdh_setup,int,"mbedtls_ecdh_context*, mbedtls_ecp_group_id" +Function,-,mbedtls_ecdsa_can_do,int,mbedtls_ecp_group_id +Function,-,mbedtls_ecdsa_free,void,mbedtls_ecdsa_context* +Function,-,mbedtls_ecdsa_from_keypair,int,"mbedtls_ecdsa_context*, const mbedtls_ecp_keypair*" +Function,-,mbedtls_ecdsa_genkey,int,"mbedtls_ecdsa_context*, mbedtls_ecp_group_id, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_ecdsa_init,void,mbedtls_ecdsa_context* +Function,-,mbedtls_ecdsa_read_signature,int,"mbedtls_ecdsa_context*, const unsigned char*, size_t, const unsigned char*, size_t" +Function,-,mbedtls_ecdsa_read_signature_restartable,int,"mbedtls_ecdsa_context*, const unsigned char*, size_t, const unsigned char*, size_t, mbedtls_ecdsa_restart_ctx*" +Function,-,mbedtls_ecdsa_sign,int,"mbedtls_ecp_group*, mbedtls_mpi*, mbedtls_mpi*, const mbedtls_mpi*, const unsigned char*, size_t, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_ecdsa_sign_restartable,int,"mbedtls_ecp_group*, mbedtls_mpi*, mbedtls_mpi*, const mbedtls_mpi*, const unsigned char*, size_t, int (*)(void*, unsigned char*, size_t), void*, int (*)(void*, unsigned char*, size_t), void*, mbedtls_ecdsa_restart_ctx*" +Function,-,mbedtls_ecdsa_verify,int,"mbedtls_ecp_group*, const unsigned char*, size_t, const mbedtls_ecp_point*, const mbedtls_mpi*, const mbedtls_mpi*" +Function,-,mbedtls_ecdsa_verify_restartable,int,"mbedtls_ecp_group*, const unsigned char*, size_t, const mbedtls_ecp_point*, const mbedtls_mpi*, const mbedtls_mpi*, mbedtls_ecdsa_restart_ctx*" +Function,-,mbedtls_ecdsa_write_signature,int,"mbedtls_ecdsa_context*, mbedtls_md_type_t, const unsigned char*, size_t, unsigned char*, size_t, size_t*, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_ecdsa_write_signature_restartable,int,"mbedtls_ecdsa_context*, mbedtls_md_type_t, const unsigned char*, size_t, unsigned char*, size_t, size_t*, int (*)(void*, unsigned char*, size_t), void*, mbedtls_ecdsa_restart_ctx*" +Function,-,mbedtls_ecp_check_privkey,int,"const mbedtls_ecp_group*, const mbedtls_mpi*" +Function,-,mbedtls_ecp_check_pub_priv,int,"const mbedtls_ecp_keypair*, const mbedtls_ecp_keypair*, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_ecp_check_pubkey,int,"const mbedtls_ecp_group*, const mbedtls_ecp_point*" +Function,-,mbedtls_ecp_copy,int,"mbedtls_ecp_point*, const mbedtls_ecp_point*" +Function,-,mbedtls_ecp_curve_info_from_grp_id,const mbedtls_ecp_curve_info*,mbedtls_ecp_group_id +Function,-,mbedtls_ecp_curve_info_from_name,const mbedtls_ecp_curve_info*,const char* +Function,-,mbedtls_ecp_curve_info_from_tls_id,const mbedtls_ecp_curve_info*,uint16_t +Function,-,mbedtls_ecp_curve_list,const mbedtls_ecp_curve_info*, +Function,-,mbedtls_ecp_export,int,"const mbedtls_ecp_keypair*, mbedtls_ecp_group*, mbedtls_mpi*, mbedtls_ecp_point*" +Function,-,mbedtls_ecp_gen_key,int,"mbedtls_ecp_group_id, mbedtls_ecp_keypair*, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_ecp_gen_keypair,int,"mbedtls_ecp_group*, mbedtls_mpi*, mbedtls_ecp_point*, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_ecp_gen_keypair_base,int,"mbedtls_ecp_group*, const mbedtls_ecp_point*, mbedtls_mpi*, mbedtls_ecp_point*, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_ecp_gen_privkey,int,"const mbedtls_ecp_group*, mbedtls_mpi*, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_ecp_get_type,mbedtls_ecp_curve_type,const mbedtls_ecp_group* +Function,-,mbedtls_ecp_group_copy,int,"mbedtls_ecp_group*, const mbedtls_ecp_group*" +Function,-,mbedtls_ecp_group_free,void,mbedtls_ecp_group* +Function,-,mbedtls_ecp_group_init,void,mbedtls_ecp_group* +Function,-,mbedtls_ecp_group_load,int,"mbedtls_ecp_group*, mbedtls_ecp_group_id" +Function,-,mbedtls_ecp_grp_id_list,const mbedtls_ecp_group_id*, +Function,-,mbedtls_ecp_is_zero,int,mbedtls_ecp_point* +Function,-,mbedtls_ecp_keypair_free,void,mbedtls_ecp_keypair* +Function,-,mbedtls_ecp_keypair_init,void,mbedtls_ecp_keypair* +Function,-,mbedtls_ecp_mul,int,"mbedtls_ecp_group*, mbedtls_ecp_point*, const mbedtls_mpi*, const mbedtls_ecp_point*, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_ecp_mul_restartable,int,"mbedtls_ecp_group*, mbedtls_ecp_point*, const mbedtls_mpi*, const mbedtls_ecp_point*, int (*)(void*, unsigned char*, size_t), void*, mbedtls_ecp_restart_ctx*" +Function,-,mbedtls_ecp_muladd,int,"mbedtls_ecp_group*, mbedtls_ecp_point*, const mbedtls_mpi*, const mbedtls_ecp_point*, const mbedtls_mpi*, const mbedtls_ecp_point*" +Function,-,mbedtls_ecp_muladd_restartable,int,"mbedtls_ecp_group*, mbedtls_ecp_point*, const mbedtls_mpi*, const mbedtls_ecp_point*, const mbedtls_mpi*, const mbedtls_ecp_point*, mbedtls_ecp_restart_ctx*" +Function,-,mbedtls_ecp_point_cmp,int,"const mbedtls_ecp_point*, const mbedtls_ecp_point*" +Function,-,mbedtls_ecp_point_free,void,mbedtls_ecp_point* +Function,-,mbedtls_ecp_point_init,void,mbedtls_ecp_point* +Function,-,mbedtls_ecp_point_read_binary,int,"const mbedtls_ecp_group*, mbedtls_ecp_point*, const unsigned char*, size_t" +Function,-,mbedtls_ecp_point_read_string,int,"mbedtls_ecp_point*, int, const char*, const char*" +Function,-,mbedtls_ecp_point_write_binary,int,"const mbedtls_ecp_group*, const mbedtls_ecp_point*, int, size_t*, unsigned char*, size_t" +Function,-,mbedtls_ecp_read_key,int,"mbedtls_ecp_group_id, mbedtls_ecp_keypair*, const unsigned char*, size_t" +Function,-,mbedtls_ecp_set_zero,int,mbedtls_ecp_point* +Function,-,mbedtls_ecp_tls_read_group,int,"mbedtls_ecp_group*, const unsigned char**, size_t" +Function,-,mbedtls_ecp_tls_read_group_id,int,"mbedtls_ecp_group_id*, const unsigned char**, size_t" +Function,-,mbedtls_ecp_tls_read_point,int,"const mbedtls_ecp_group*, mbedtls_ecp_point*, const unsigned char**, size_t" +Function,-,mbedtls_ecp_tls_write_group,int,"const mbedtls_ecp_group*, size_t*, unsigned char*, size_t" +Function,-,mbedtls_ecp_tls_write_point,int,"const mbedtls_ecp_group*, const mbedtls_ecp_point*, int, size_t*, unsigned char*, size_t" +Function,-,mbedtls_ecp_write_key,int,"mbedtls_ecp_keypair*, unsigned char*, size_t" +Function,-,mbedtls_internal_md5_process,int,"mbedtls_md5_context*, const unsigned char[64]" Function,-,mbedtls_internal_sha1_process,int,"mbedtls_sha1_context*, const unsigned char[64]" -Function,-,mbedtls_platform_gmtime_r,tm*,"const mbedtls_time_t*, tm*" +Function,-,mbedtls_internal_sha256_process,int,"mbedtls_sha256_context*, const unsigned char[64]" +Function,-,mbedtls_md,int,"const mbedtls_md_info_t*, const unsigned char*, size_t, unsigned char*" +Function,-,mbedtls_md5,int,"const unsigned char*, size_t, unsigned char[16]" +Function,-,mbedtls_md5_clone,void,"mbedtls_md5_context*, const mbedtls_md5_context*" +Function,-,mbedtls_md5_finish,int,"mbedtls_md5_context*, unsigned char[16]" +Function,-,mbedtls_md5_free,void,mbedtls_md5_context* +Function,-,mbedtls_md5_init,void,mbedtls_md5_context* +Function,-,mbedtls_md5_starts,int,mbedtls_md5_context* +Function,-,mbedtls_md5_update,int,"mbedtls_md5_context*, const unsigned char*, size_t" +Function,-,mbedtls_md_clone,int,"mbedtls_md_context_t*, const mbedtls_md_context_t*" +Function,-,mbedtls_md_finish,int,"mbedtls_md_context_t*, unsigned char*" +Function,-,mbedtls_md_free,void,mbedtls_md_context_t* +Function,-,mbedtls_md_get_name,const char*,const mbedtls_md_info_t* +Function,-,mbedtls_md_get_size,unsigned char,const mbedtls_md_info_t* +Function,-,mbedtls_md_get_type,mbedtls_md_type_t,const mbedtls_md_info_t* +Function,-,mbedtls_md_hmac,int,"const mbedtls_md_info_t*, const unsigned char*, size_t, const unsigned char*, size_t, unsigned char*" +Function,-,mbedtls_md_hmac_finish,int,"mbedtls_md_context_t*, unsigned char*" +Function,-,mbedtls_md_hmac_reset,int,mbedtls_md_context_t* +Function,-,mbedtls_md_hmac_starts,int,"mbedtls_md_context_t*, const unsigned char*, size_t" +Function,-,mbedtls_md_hmac_update,int,"mbedtls_md_context_t*, const unsigned char*, size_t" +Function,-,mbedtls_md_info_from_ctx,const mbedtls_md_info_t*,const mbedtls_md_context_t* +Function,-,mbedtls_md_info_from_string,const mbedtls_md_info_t*,const char* +Function,-,mbedtls_md_info_from_type,const mbedtls_md_info_t*,mbedtls_md_type_t +Function,-,mbedtls_md_init,void,mbedtls_md_context_t* +Function,-,mbedtls_md_list,const int*, +Function,-,mbedtls_md_setup,int,"mbedtls_md_context_t*, const mbedtls_md_info_t*, int" +Function,-,mbedtls_md_starts,int,mbedtls_md_context_t* +Function,-,mbedtls_md_update,int,"mbedtls_md_context_t*, const unsigned char*, size_t" +Function,-,mbedtls_mpi_add_abs,int,"mbedtls_mpi*, const mbedtls_mpi*, const mbedtls_mpi*" +Function,-,mbedtls_mpi_add_int,int,"mbedtls_mpi*, const mbedtls_mpi*, mbedtls_mpi_sint" +Function,-,mbedtls_mpi_add_mpi,int,"mbedtls_mpi*, const mbedtls_mpi*, const mbedtls_mpi*" +Function,-,mbedtls_mpi_bitlen,size_t,const mbedtls_mpi* +Function,-,mbedtls_mpi_cmp_abs,int,"const mbedtls_mpi*, const mbedtls_mpi*" +Function,-,mbedtls_mpi_cmp_int,int,"const mbedtls_mpi*, mbedtls_mpi_sint" +Function,-,mbedtls_mpi_cmp_mpi,int,"const mbedtls_mpi*, const mbedtls_mpi*" +Function,-,mbedtls_mpi_copy,int,"mbedtls_mpi*, const mbedtls_mpi*" +Function,-,mbedtls_mpi_div_int,int,"mbedtls_mpi*, mbedtls_mpi*, const mbedtls_mpi*, mbedtls_mpi_sint" +Function,-,mbedtls_mpi_div_mpi,int,"mbedtls_mpi*, mbedtls_mpi*, const mbedtls_mpi*, const mbedtls_mpi*" +Function,-,mbedtls_mpi_exp_mod,int,"mbedtls_mpi*, const mbedtls_mpi*, const mbedtls_mpi*, const mbedtls_mpi*, mbedtls_mpi*" +Function,-,mbedtls_mpi_fill_random,int,"mbedtls_mpi*, size_t, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_mpi_free,void,mbedtls_mpi* +Function,-,mbedtls_mpi_gcd,int,"mbedtls_mpi*, const mbedtls_mpi*, const mbedtls_mpi*" +Function,-,mbedtls_mpi_gen_prime,int,"mbedtls_mpi*, size_t, int, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_mpi_get_bit,int,"const mbedtls_mpi*, size_t" +Function,-,mbedtls_mpi_grow,int,"mbedtls_mpi*, size_t" +Function,-,mbedtls_mpi_init,void,mbedtls_mpi* +Function,-,mbedtls_mpi_inv_mod,int,"mbedtls_mpi*, const mbedtls_mpi*, const mbedtls_mpi*" +Function,-,mbedtls_mpi_is_prime_ext,int,"const mbedtls_mpi*, int, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_mpi_lsb,size_t,const mbedtls_mpi* +Function,-,mbedtls_mpi_lset,int,"mbedtls_mpi*, mbedtls_mpi_sint" +Function,-,mbedtls_mpi_lt_mpi_ct,int,"const mbedtls_mpi*, const mbedtls_mpi*, unsigned*" +Function,-,mbedtls_mpi_mod_int,int,"mbedtls_mpi_uint*, const mbedtls_mpi*, mbedtls_mpi_sint" +Function,-,mbedtls_mpi_mod_mpi,int,"mbedtls_mpi*, const mbedtls_mpi*, const mbedtls_mpi*" +Function,-,mbedtls_mpi_mul_int,int,"mbedtls_mpi*, const mbedtls_mpi*, mbedtls_mpi_uint" +Function,-,mbedtls_mpi_mul_mpi,int,"mbedtls_mpi*, const mbedtls_mpi*, const mbedtls_mpi*" +Function,-,mbedtls_mpi_random,int,"mbedtls_mpi*, mbedtls_mpi_sint, const mbedtls_mpi*, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_mpi_read_binary,int,"mbedtls_mpi*, const unsigned char*, size_t" +Function,-,mbedtls_mpi_read_binary_le,int,"mbedtls_mpi*, const unsigned char*, size_t" +Function,-,mbedtls_mpi_read_string,int,"mbedtls_mpi*, int, const char*" +Function,-,mbedtls_mpi_safe_cond_assign,int,"mbedtls_mpi*, const mbedtls_mpi*, unsigned char" +Function,-,mbedtls_mpi_safe_cond_swap,int,"mbedtls_mpi*, mbedtls_mpi*, unsigned char" +Function,-,mbedtls_mpi_set_bit,int,"mbedtls_mpi*, size_t, unsigned char" +Function,-,mbedtls_mpi_shift_l,int,"mbedtls_mpi*, size_t" +Function,-,mbedtls_mpi_shift_r,int,"mbedtls_mpi*, size_t" +Function,-,mbedtls_mpi_shrink,int,"mbedtls_mpi*, size_t" +Function,-,mbedtls_mpi_size,size_t,const mbedtls_mpi* +Function,-,mbedtls_mpi_sub_abs,int,"mbedtls_mpi*, const mbedtls_mpi*, const mbedtls_mpi*" +Function,-,mbedtls_mpi_sub_int,int,"mbedtls_mpi*, const mbedtls_mpi*, mbedtls_mpi_sint" +Function,-,mbedtls_mpi_sub_mpi,int,"mbedtls_mpi*, const mbedtls_mpi*, const mbedtls_mpi*" +Function,-,mbedtls_mpi_swap,void,"mbedtls_mpi*, mbedtls_mpi*" +Function,-,mbedtls_mpi_write_binary,int,"const mbedtls_mpi*, unsigned char*, size_t" +Function,-,mbedtls_mpi_write_binary_le,int,"const mbedtls_mpi*, unsigned char*, size_t" +Function,-,mbedtls_mpi_write_string,int,"const mbedtls_mpi*, int, char*, size_t, size_t*" Function,-,mbedtls_platform_zeroize,void,"void*, size_t" -Function,+,mbedtls_sha1,int,"const unsigned char*, size_t, unsigned char[20]" +Function,-,mbedtls_sha1,int,"const unsigned char*, size_t, unsigned char[20]" Function,-,mbedtls_sha1_clone,void,"mbedtls_sha1_context*, const mbedtls_sha1_context*" Function,-,mbedtls_sha1_finish,int,"mbedtls_sha1_context*, unsigned char[20]" Function,-,mbedtls_sha1_free,void,mbedtls_sha1_context* Function,-,mbedtls_sha1_init,void,mbedtls_sha1_context* -Function,-,mbedtls_sha1_self_test,int,int Function,-,mbedtls_sha1_starts,int,mbedtls_sha1_context* Function,-,mbedtls_sha1_update,int,"mbedtls_sha1_context*, const unsigned char*, size_t" +Function,-,mbedtls_sha256,int,"const unsigned char*, size_t, unsigned char*, int" +Function,-,mbedtls_sha256_clone,void,"mbedtls_sha256_context*, const mbedtls_sha256_context*" +Function,-,mbedtls_sha256_finish,int,"mbedtls_sha256_context*, unsigned char*" +Function,-,mbedtls_sha256_free,void,mbedtls_sha256_context* +Function,-,mbedtls_sha256_init,void,mbedtls_sha256_context* +Function,-,mbedtls_sha256_starts,int,"mbedtls_sha256_context*, int" +Function,-,mbedtls_sha256_update,int,"mbedtls_sha256_context*, const unsigned char*, size_t" Function,-,mblen,int,"const char*, size_t" Function,-,mbstowcs,size_t,"wchar_t*, const char*, size_t" Function,-,mbtowc,int,"wchar_t*, const char*, size_t" -Function,+,md5,void,"const unsigned char*, size_t, unsigned char[16]" -Function,+,md5_finish,void,"md5_context*, unsigned char[16]" -Function,+,md5_process,void,"md5_context*, const unsigned char[64]" -Function,+,md5_starts,void,md5_context* -Function,+,md5_update,void,"md5_context*, const unsigned char*, size_t" Function,-,memccpy,void*,"void*, const void*, int, size_t" Function,+,memchr,void*,"const void*, int, size_t" Function,+,memcmp,int,"const void*, const void*, size_t" @@ -2255,7 +2385,6 @@ Function,-,mkostemps,int,"char*, int, int" Function,-,mkstemp,int,char* Function,-,mkstemps,int,"char*, int" Function,-,mktemp,char*,char* -Function,-,mktime,time_t,tm* Function,-,modf,double,"double, double*" Function,-,modff,float,"float, float*" Function,-,modfl,long double,"long double, long double*" @@ -2593,11 +2722,6 @@ Function,-,setkey,void,const char* Function,-,setlinebuf,int,FILE* Function,-,setstate,char*,char* Function,-,setvbuf,int,"FILE*, char*, int, size_t" -Function,+,sha256,void,"const unsigned char*, unsigned int, unsigned char[32]" -Function,+,sha256_finish,void,"sha256_context*, unsigned char[32]" -Function,+,sha256_process,void,sha256_context* -Function,+,sha256_start,void,sha256_context* -Function,+,sha256_update,void,"sha256_context*, const unsigned char*, unsigned int" Function,+,signal_reader_alloc,SignalReader*,"const GpioPin*, uint32_t" Function,+,signal_reader_free,void,SignalReader* Function,+,signal_reader_set_polarity,void,"SignalReader*, SignalReaderPolarity" @@ -2800,8 +2924,6 @@ Function,+,stream_write_vaformat,size_t,"Stream*, const char*, va_list" Function,-,strerror,char*,int Function,-,strerror_l,char*,"int, locale_t" Function,-,strerror_r,char*,"int, char*, size_t" -Function,-,strftime,size_t,"char*, size_t, const char*, const tm*" -Function,-,strftime_l,size_t,"char*, size_t, const char*, const tm*, locale_t" Function,+,string_stream_alloc,Stream*, Function,-,strlcat,size_t,"char*, const char*, size_t" Function,+,strlcpy,size_t,"char*, const char*, size_t" @@ -2816,8 +2938,6 @@ Function,-,strndup,char*,"const char*, size_t" Function,-,strnlen,size_t,"const char*, size_t" Function,-,strnstr,char*,"const char*, const char*, size_t" Function,-,strpbrk,char*,"const char*, const char*" -Function,-,strptime,char*,"const char*, const char*, tm*" -Function,-,strptime_l,char*,"const char*, const char*, tm*, locale_t" Function,+,strrchr,char*,"const char*, int" Function,-,strsep,char*,"char**, const char*" Function,-,strsignal,char*,int @@ -3047,7 +3167,6 @@ Function,+,text_input_set_validator,void,"TextInput*, TextInputValidatorCallback Function,-,tgamma,double,double Function,-,tgammaf,float,float Function,-,tgammal,long double,long double -Function,-,time,time_t,time_t* Function,-,timingsafe_bcmp,int,"const void*, const void*, size_t" Function,-,timingsafe_memcmp,int,"const void*, const void*, size_t" Function,-,tmpfile,FILE*, @@ -3061,25 +3180,6 @@ Function,-,toupper_l,int,"int, locale_t" Function,-,trunc,double,double Function,-,truncf,float,float Function,-,truncl,long double,long double -Function,-,tzset,void, -Function,-,uECC_compress,void,"const uint8_t*, uint8_t*, uECC_Curve" -Function,+,uECC_compute_public_key,int,"const uint8_t*, uint8_t*, uECC_Curve" -Function,-,uECC_curve_private_key_size,int,uECC_Curve -Function,-,uECC_curve_public_key_size,int,uECC_Curve -Function,-,uECC_decompress,void,"const uint8_t*, uint8_t*, uECC_Curve" -Function,-,uECC_get_rng,uECC_RNG_Function, -Function,-,uECC_make_key,int,"uint8_t*, uint8_t*, uECC_Curve" -Function,-,uECC_secp160r1,uECC_Curve, -Function,-,uECC_secp192r1,uECC_Curve, -Function,-,uECC_secp224r1,uECC_Curve, -Function,-,uECC_secp256k1,uECC_Curve, -Function,+,uECC_secp256r1,uECC_Curve, -Function,+,uECC_set_rng,void,uECC_RNG_Function -Function,-,uECC_shared_secret,int,"const uint8_t*, const uint8_t*, uint8_t*, uECC_Curve" -Function,+,uECC_sign,int,"const uint8_t*, const uint8_t*, unsigned, uint8_t*, uECC_Curve" -Function,-,uECC_sign_deterministic,int,"const uint8_t*, const uint8_t*, unsigned, const uECC_HashContext*, uint8_t*, uECC_Curve" -Function,-,uECC_valid_public_key,int,"const uint8_t*, uECC_Curve" -Function,-,uECC_verify,int,"const uint8_t*, const uint8_t*, unsigned, const uint8_t*, uECC_Curve" Function,+,uint8_to_hex_chars,void,"const uint8_t*, uint8_t*, int" Function,-,ungetc,int,"int, FILE*" Function,-,unsetenv,int,const char* @@ -3212,13 +3312,10 @@ Variable,-,MSIRangeTable,const uint32_t[16], Variable,-,SmpsPrescalerTable,const uint32_t[4][6], Variable,+,SystemCoreClock,uint32_t, Variable,+,_ctype_,const char[], -Variable,-,_daylight,int, Variable,+,_global_impure_ptr,_reent*, Variable,+,_impure_ptr,_reent*, Variable,-,_sys_errlist,const char*[], Variable,-,_sys_nerr,int, -Variable,-,_timezone,long, -Variable,-,_tzname,char*[2], Variable,+,cli_vcp,CliSession, Variable,+,firmware_api_interface,const ElfApiInterface*, Variable,+,furi_hal_i2c_bus_external,FuriHalI2cBus, diff --git a/targets/f7/furi_hal/furi_hal_usb_ccid.c b/targets/f7/furi_hal/furi_hal_usb_ccid.c index 5c35c69f884..e24713ced52 100644 --- a/targets/f7/furi_hal/furi_hal_usb_ccid.c +++ b/targets/f7/furi_hal/furi_hal_usb_ccid.c @@ -39,12 +39,12 @@ struct CcidIntfDescriptor { struct usb_ccid_descriptor ccid_desc; struct usb_endpoint_descriptor ccid_bulk_in; struct usb_endpoint_descriptor ccid_bulk_out; -} __attribute__((packed)); +} FURI_PACKED; struct CcidConfigDescriptor { struct usb_config_descriptor config; struct CcidIntfDescriptor intf_0; -} __attribute__((packed)); +} FURI_PACKED; enum CCID_Features_Auto_t { CCID_Features_Auto_None = 0x0, @@ -255,7 +255,7 @@ typedef struct ccid_bulk_message_header { uint32_t dwLength; uint8_t bSlot; uint8_t bSeq; -} __attribute__((packed)) ccid_bulk_message_header_t; +} FURI_PACKED ccid_bulk_message_header_t; uint8_t SendBuffer[sizeof(ccid_bulk_message_header_t) + CCID_DATABLOCK_SIZE]; diff --git a/targets/f7/furi_hal/furi_hal_usb_cdc.c b/targets/f7/furi_hal/furi_hal_usb_cdc.c index 4c4f727badb..e9cb51e203c 100644 --- a/targets/f7/furi_hal/furi_hal_usb_cdc.c +++ b/targets/f7/furi_hal/furi_hal_usb_cdc.c @@ -35,13 +35,13 @@ struct CdcIadDescriptor { struct CdcConfigDescriptorSingle { struct usb_config_descriptor config; struct CdcIadDescriptor iad_0; -} __attribute__((packed)); +} FURI_PACKED; struct CdcConfigDescriptorDual { struct usb_config_descriptor config; struct CdcIadDescriptor iad_0; struct CdcIadDescriptor iad_1; -} __attribute__((packed)); +} FURI_PACKED; static const struct usb_string_descriptor dev_manuf_desc = USB_STRING_DESC("Flipper Devices Inc."); diff --git a/targets/f7/furi_hal/furi_hal_usb_hid.c b/targets/f7/furi_hal/furi_hal_usb_hid.c index 334aa010264..b744e5ef72e 100644 --- a/targets/f7/furi_hal/furi_hal_usb_hid.c +++ b/targets/f7/furi_hal/furi_hal_usb_hid.c @@ -24,7 +24,7 @@ struct HidIntfDescriptor { struct HidConfigDescriptor { struct usb_config_descriptor config; struct HidIntfDescriptor intf_0; -} __attribute__((packed)); +} FURI_PACKED; enum HidReportId { ReportIdKeyboard = 1, @@ -199,7 +199,7 @@ struct HidReportMouse { int8_t x; int8_t y; int8_t wheel; -} __attribute__((packed)); +} FURI_PACKED; struct HidReportKB { uint8_t report_id; @@ -208,23 +208,23 @@ struct HidReportKB { uint8_t reserved; uint8_t btn[HID_KB_MAX_KEYS]; } boot; -} __attribute__((packed)); +} FURI_PACKED; struct HidReportConsumer { uint8_t report_id; uint16_t btn[HID_CONSUMER_MAX_KEYS]; -} __attribute__((packed)); +} FURI_PACKED; struct HidReportLED { uint8_t report_id; uint8_t led_state; -} __attribute__((packed)); +} FURI_PACKED; static struct HidReport { struct HidReportKB keyboard; struct HidReportMouse mouse; struct HidReportConsumer consumer; -} __attribute__((packed)) hid_report; +} FURI_PACKED hid_report; static void hid_init(usbd_device* dev, FuriHalUsbInterface* intf, void* ctx); static void hid_deinit(usbd_device* dev); diff --git a/targets/f7/furi_hal/furi_hal_usb_u2f.c b/targets/f7/furi_hal/furi_hal_usb_u2f.c index fe711512a14..cc7e23b77e3 100644 --- a/targets/f7/furi_hal/furi_hal_usb_u2f.c +++ b/targets/f7/furi_hal/furi_hal_usb_u2f.c @@ -25,7 +25,7 @@ struct HidIadDescriptor { struct HidConfigDescriptor { struct usb_config_descriptor config; struct HidIadDescriptor iad_0; -} __attribute__((packed)); +} FURI_PACKED; /* HID report: FIDO U2F */ static const uint8_t hid_u2f_report_desc[] = { diff --git a/targets/f7/target.json b/targets/f7/target.json index 63b5cdb9276..7a816828c78 100644 --- a/targets/f7/target.json +++ b/targets/f7/target.json @@ -25,7 +25,6 @@ "fatfs", "littlefs", "subghz", - "flipperformat", "toolbox", "nfc", "digital_signal", @@ -39,12 +38,15 @@ "one_wire", "ibutton", "music_worker", - "misc", "mbedtls", "lfrfid", "flipper_application", - "flipperformat", "toolbox", + "u8g2", + "nanopb", + "update_util", + "heatshrink", + "flipperformat", "flipper7" ] } \ No newline at end of file From c1e0d02afc8131e782aa3f2e5c1bbd6f43890790 Mon Sep 17 00:00:00 2001 From: Augusto Zanellato Date: Fri, 1 Dec 2023 10:42:00 +0100 Subject: [PATCH 818/824] ST25TB poller refining + write support (#3239) * nfc: st25tb: rework async poller * nfc: st25tb: introduce sync poller * nfc: st25tb: add write support * nfc: st25tb: rewrite poller to use better states * nfc: st25tb: move to mode request state after success * nfc: st25tb: minor bug fixes * type wasn't properly set on ready event * sending NfcCustomEventPollerFailure on St25tbPollerEventTypeFailure caused poller to being freed and ultimately resulted in a thread crash Co-authored-by: Aleksandr Kutuzov --- .../helpers/protocol_support/st25tb/st25tb.c | 4 +- lib/nfc/SConscript | 1 + lib/nfc/protocols/st25tb/st25tb.c | 20 ++ lib/nfc/protocols/st25tb/st25tb.h | 5 +- lib/nfc/protocols/st25tb/st25tb_poller.c | 154 +++++++++++-- lib/nfc/protocols/st25tb/st25tb_poller.h | 40 +++- lib/nfc/protocols/st25tb/st25tb_poller_i.c | 192 +++++++++------- lib/nfc/protocols/st25tb/st25tb_poller_i.h | 33 ++- lib/nfc/protocols/st25tb/st25tb_poller_sync.c | 211 ++++++++++++++++++ lib/nfc/protocols/st25tb/st25tb_poller_sync.h | 20 ++ targets/f18/api_symbols.csv | 2 +- targets/f7/api_symbols.csv | 11 +- 12 files changed, 565 insertions(+), 128 deletions(-) create mode 100644 lib/nfc/protocols/st25tb/st25tb_poller_sync.c create mode 100644 lib/nfc/protocols/st25tb/st25tb_poller_sync.h diff --git a/applications/main/nfc/helpers/protocol_support/st25tb/st25tb.c b/applications/main/nfc/helpers/protocol_support/st25tb/st25tb.c index 32b2f477570..e22af48b34e 100644 --- a/applications/main/nfc/helpers/protocol_support/st25tb/st25tb.c +++ b/applications/main/nfc/helpers/protocol_support/st25tb/st25tb.c @@ -29,7 +29,9 @@ static NfcCommand nfc_scene_read_poller_callback_st25tb(NfcGenericEvent event, v NfcApp* instance = context; const St25tbPollerEvent* st25tb_event = event.event_data; - if(st25tb_event->type == St25tbPollerEventTypeReady) { + if(st25tb_event->type == St25tbPollerEventTypeRequestMode) { + st25tb_event->data->mode_request.mode = St25tbPollerModeRead; + } else if(st25tb_event->type == St25tbPollerEventTypeSuccess) { nfc_device_set_data( instance->nfc_device, NfcProtocolSt25tb, nfc_poller_get_data(instance->poller)); view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess); diff --git a/lib/nfc/SConscript b/lib/nfc/SConscript index 21f2fb49f6a..d2cfbe2fb61 100644 --- a/lib/nfc/SConscript +++ b/lib/nfc/SConscript @@ -42,6 +42,7 @@ env.Append( File("protocols/iso14443_3a/iso14443_3a_poller_sync.h"), File("protocols/mf_ultralight/mf_ultralight_poller_sync.h"), File("protocols/mf_classic/mf_classic_poller_sync.h"), + File("protocols/st25tb/st25tb_poller_sync.h"), # Misc File("helpers/nfc_util.h"), File("helpers/iso14443_crc.h"), diff --git a/lib/nfc/protocols/st25tb/st25tb.c b/lib/nfc/protocols/st25tb/st25tb.c index d3fac7eeac8..785cf831d93 100644 --- a/lib/nfc/protocols/st25tb/st25tb.c +++ b/lib/nfc/protocols/st25tb/st25tb.c @@ -232,3 +232,23 @@ St25tbData* st25tb_get_base_data(const St25tbData* data) { UNUSED(data); furi_crash("No base data"); } + +St25tbType st25tb_get_type_from_uid(const uint8_t* uid) { + switch(uid[2] >> 2) { + case 0x0: + case 0x3: + return St25tbTypeX4k; + case 0x4: + return St25tbTypeX512; + case 0x6: + return St25tbType512Ac; + case 0x7: + return St25tbType04k; + case 0xc: + return St25tbType512At; + case 0xf: + return St25tbType02k; + default: + furi_crash("unsupported st25tb type"); + } +} diff --git a/lib/nfc/protocols/st25tb/st25tb.h b/lib/nfc/protocols/st25tb/st25tb.h index 1edb296ca12..ed02dc2b207 100644 --- a/lib/nfc/protocols/st25tb/st25tb.h +++ b/lib/nfc/protocols/st25tb/st25tb.h @@ -1,6 +1,5 @@ #pragma once -#include #include #ifdef __cplusplus @@ -27,6 +26,7 @@ typedef enum { St25tbErrorFieldOff, St25tbErrorWrongCrc, St25tbErrorTimeout, + St25tbErrorWriteFailed, } St25tbError; typedef enum { @@ -44,7 +44,6 @@ typedef struct { St25tbType type; uint32_t blocks[ST25TB_MAX_BLOCKS]; uint32_t system_otp_block; - uint8_t chip_id; } St25tbData; extern const NfcDeviceBase nfc_device_st25tb; @@ -75,6 +74,8 @@ bool st25tb_set_uid(St25tbData* data, const uint8_t* uid, size_t uid_len); St25tbData* st25tb_get_base_data(const St25tbData* data); +St25tbType st25tb_get_type_from_uid(const uint8_t* uid); + #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/st25tb/st25tb_poller.c b/lib/nfc/protocols/st25tb/st25tb_poller.c index 2bc5dd94105..fd6dc4f09f0 100644 --- a/lib/nfc/protocols/st25tb/st25tb_poller.c +++ b/lib/nfc/protocols/st25tb/st25tb_poller.c @@ -1,13 +1,12 @@ -#include "protocols/nfc_protocol.h" -#include "protocols/st25tb/st25tb.h" +#include "st25tb_poller.h" #include "st25tb_poller_i.h" #include -#include - #define TAG "ST25TBPoller" +typedef NfcCommand (*St25tbPollerStateHandler)(St25tbPoller* instance); + const St25tbData* st25tb_poller_get_data(St25tbPoller* instance) { furi_assert(instance); furi_assert(instance->data); @@ -20,6 +19,7 @@ static St25tbPoller* st25tb_poller_alloc(Nfc* nfc) { St25tbPoller* instance = malloc(sizeof(St25tbPoller)); instance->nfc = nfc; + instance->state = St25tbPollerStateSelect; instance->tx_buffer = bit_buffer_alloc(ST25TB_POLLER_MAX_BUFFER_SIZE); instance->rx_buffer = bit_buffer_alloc(ST25TB_POLLER_MAX_BUFFER_SIZE); @@ -60,6 +60,128 @@ static void instance->context = context; } +static NfcCommand st25tb_poller_select_handler(St25tbPoller* instance) { + NfcCommand command = NfcCommandContinue; + + do { + St25tbError error = st25tb_poller_select(instance, NULL); + if(error != St25tbErrorNone) { + instance->state = St25tbPollerStateFailure; + instance->st25tb_event_data.error = error; + break; + } + + instance->st25tb_event.type = St25tbPollerEventTypeReady; + instance->st25tb_event.data->ready.type = instance->data->type; + command = instance->callback(instance->general_event, instance->context); + instance->state = St25tbPollerStateRequestMode; + } while(false); + + return command; +} + +static NfcCommand st25tb_poller_request_mode_handler(St25tbPoller* instance) { + NfcCommand command = NfcCommandContinue; + instance->st25tb_event.type = St25tbPollerEventTypeRequestMode; + command = instance->callback(instance->general_event, instance->context); + + St25tbPollerEventDataModeRequest* mode_request_data = + &instance->st25tb_event_data.mode_request; + + furi_assert(mode_request_data->mode < St25tbPollerModeNum); + + if(mode_request_data->mode == St25tbPollerModeRead) { + instance->state = St25tbPollerStateRead; + instance->poller_ctx.read.current_block = 0; + } else { + instance->state = St25tbPollerStateWrite; + instance->poller_ctx.write.block_number = + mode_request_data->params.write_params.block_number; + instance->poller_ctx.write.block_data = mode_request_data->params.write_params.block_data; + } + + return command; +} + +static NfcCommand st25tb_poller_read_handler(St25tbPoller* instance) { + St25tbError error = St25tbErrorNone; + + do { + uint8_t total_blocks = st25tb_get_block_count(instance->data->type); + uint8_t* current_block = &instance->poller_ctx.read.current_block; + if(*current_block == total_blocks) { + error = st25tb_poller_read_block( + instance, &instance->data->system_otp_block, ST25TB_SYSTEM_OTP_BLOCK); + if(error != St25tbErrorNone) { + FURI_LOG_E(TAG, "Failed to read OTP block"); + instance->state = St25tbPollerStateFailure; + instance->st25tb_event_data.error = error; + break; + } else { + instance->state = St25tbPollerStateSuccess; + break; + } + } else { + error = st25tb_poller_read_block( + instance, &instance->data->blocks[*current_block], *current_block); + if(error != St25tbErrorNone) { + FURI_LOG_E(TAG, "Failed to read block %d", *current_block); + instance->state = St25tbPollerStateFailure; + instance->st25tb_event_data.error = error; + break; + } + + *current_block += 1; + } + } while(false); + + return NfcCommandContinue; +} + +static NfcCommand st25tb_poller_write_handler(St25tbPoller* instance) { + St25tbPollerWriteContext* write_ctx = &instance->poller_ctx.write; + St25tbError error = + st25tb_poller_write_block(instance, write_ctx->block_data, write_ctx->block_number); + + if(error == St25tbErrorNone) { + instance->state = St25tbPollerStateSuccess; + } else { + instance->state = St25tbPollerStateFailure; + instance->st25tb_event_data.error = error; + } + + return NfcCommandContinue; +} + +NfcCommand st25tb_poller_success_handler(St25tbPoller* instance) { + NfcCommand command = NfcCommandContinue; + instance->st25tb_event.type = St25tbPollerEventTypeSuccess; + command = instance->callback(instance->general_event, instance->context); + furi_delay_ms(100); + instance->state = St25tbPollerStateRequestMode; + + return command; +} + +NfcCommand st25tb_poller_failure_handler(St25tbPoller* instance) { + NfcCommand command = NfcCommandContinue; + instance->st25tb_event.type = St25tbPollerEventTypeFailure; + command = instance->callback(instance->general_event, instance->context); + furi_delay_ms(100); + instance->state = St25tbPollerStateSelect; + + return command; +} + +static St25tbPollerStateHandler st25tb_poller_state_handlers[St25tbPollerStateNum] = { + [St25tbPollerStateSelect] = st25tb_poller_select_handler, + [St25tbPollerStateRequestMode] = st25tb_poller_request_mode_handler, + [St25tbPollerStateRead] = st25tb_poller_read_handler, + [St25tbPollerStateWrite] = st25tb_poller_write_handler, + [St25tbPollerStateSuccess] = st25tb_poller_success_handler, + [St25tbPollerStateFailure] = st25tb_poller_failure_handler, +}; + static NfcCommand st25tb_poller_run(NfcGenericEvent event, void* context) { furi_assert(context); furi_assert(event.protocol == NfcProtocolInvalid); @@ -69,26 +191,10 @@ static NfcCommand st25tb_poller_run(NfcGenericEvent event, void* context) { NfcEvent* nfc_event = event.event_data; NfcCommand command = NfcCommandContinue; - if(nfc_event->type == NfcEventTypePollerReady) { - if(instance->state != St25tbPollerStateActivated) { - St25tbError error = st25tb_poller_activate(instance, instance->data); + furi_assert(instance->state < St25tbPollerStateNum); - if(error == St25tbErrorNone) { - instance->st25tb_event.type = St25tbPollerEventTypeReady; - instance->st25tb_event_data.error = error; - command = instance->callback(instance->general_event, instance->context); - } else { - instance->st25tb_event.type = St25tbPollerEventTypeError; - instance->st25tb_event_data.error = error; - command = instance->callback(instance->general_event, instance->context); - // Add delay to switch context - furi_delay_ms(100); - } - } else { - instance->st25tb_event.type = St25tbPollerEventTypeReady; - instance->st25tb_event_data.error = St25tbErrorNone; - command = instance->callback(instance->general_event, instance->context); - } + if(nfc_event->type == NfcEventTypePollerReady) { + command = st25tb_poller_state_handlers[instance->state](instance); } return command; @@ -103,7 +209,7 @@ static bool st25tb_poller_detect(NfcGenericEvent event, void* context) { bool protocol_detected = false; St25tbPoller* instance = context; NfcEvent* nfc_event = event.event_data; - furi_assert(instance->state == St25tbPollerStateIdle); + furi_assert(instance->state == St25tbPollerStateSelect); if(nfc_event->type == NfcEventTypePollerReady) { St25tbError error = st25tb_poller_initiate(instance, NULL); diff --git a/lib/nfc/protocols/st25tb/st25tb_poller.h b/lib/nfc/protocols/st25tb/st25tb_poller.h index d3b85e30665..87687b7eba9 100644 --- a/lib/nfc/protocols/st25tb/st25tb_poller.h +++ b/lib/nfc/protocols/st25tb/st25tb_poller.h @@ -3,8 +3,6 @@ #include "st25tb.h" #include -#include - #ifdef __cplusplus extern "C" { #endif @@ -12,11 +10,40 @@ extern "C" { typedef struct St25tbPoller St25tbPoller; typedef enum { - St25tbPollerEventTypeError, St25tbPollerEventTypeReady, + St25tbPollerEventTypeRequestMode, + St25tbPollerEventTypeFailure, + St25tbPollerEventTypeSuccess, } St25tbPollerEventType; typedef struct { + St25tbType type; +} St25tbPollerReadyData; + +typedef enum { + St25tbPollerModeRead, + St25tbPollerModeWrite, + + St25tbPollerModeNum, +} St25tbPollerMode; + +typedef struct { + uint8_t block_number; + uint32_t block_data; +} St25tbPollerEventDataModeRequestWriteParams; + +typedef union { + St25tbPollerEventDataModeRequestWriteParams write_params; +} St25tbPollerEventDataModeRequestParams; + +typedef struct { + St25tbPollerMode mode; + St25tbPollerEventDataModeRequestParams params; +} St25tbPollerEventDataModeRequest; + +typedef union { + St25tbPollerReadyData ready; + St25tbPollerEventDataModeRequest mode_request; St25tbError error; } St25tbPollerEventData; @@ -31,15 +58,18 @@ St25tbError st25tb_poller_send_frame( BitBuffer* rx_buffer, uint32_t fwt); -St25tbError st25tb_poller_initiate(St25tbPoller* instance, uint8_t* chip_id); +St25tbError st25tb_poller_initiate(St25tbPoller* instance, uint8_t* chip_id_ptr); -St25tbError st25tb_poller_activate(St25tbPoller* instance, St25tbData* data); +St25tbError st25tb_poller_select(St25tbPoller* instance, uint8_t* chip_id_ptr); St25tbError st25tb_poller_get_uid(St25tbPoller* instance, uint8_t* uid); St25tbError st25tb_poller_read_block(St25tbPoller* instance, uint32_t* block, uint8_t block_number); +St25tbError + st25tb_poller_write_block(St25tbPoller* instance, uint32_t block, uint8_t block_number); + St25tbError st25tb_poller_halt(St25tbPoller* instance); #ifdef __cplusplus diff --git a/lib/nfc/protocols/st25tb/st25tb_poller_i.c b/lib/nfc/protocols/st25tb/st25tb_poller_i.c index 76c9a8b1fa0..adb8626a30f 100644 --- a/lib/nfc/protocols/st25tb/st25tb_poller_i.c +++ b/lib/nfc/protocols/st25tb/st25tb_poller_i.c @@ -1,8 +1,5 @@ #include "st25tb_poller_i.h" -#include "bit_buffer.h" -#include "core/core_defines.h" -#include "protocols/st25tb/st25tb.h" #include #define TAG "ST25TBPoller" @@ -18,17 +15,7 @@ static St25tbError st25tb_poller_process_error(NfcError error) { } } -static St25tbError st25tb_poller_prepare_trx(St25tbPoller* instance) { - furi_assert(instance); - - if(instance->state == St25tbPollerStateIdle) { - return st25tb_poller_activate(instance, NULL); - } - - return St25tbErrorNone; -} - -static St25tbError st25tb_poller_frame_exchange( +St25tbError st25tb_poller_send_frame( St25tbPoller* instance, const BitBuffer* tx_buffer, BitBuffer* rx_buffer, @@ -48,7 +35,7 @@ static St25tbError st25tb_poller_frame_exchange( NfcError error = nfc_poller_trx(instance->nfc, instance->tx_buffer, instance->rx_buffer, fwt); if(error != NfcErrorNone) { - FURI_LOG_D(TAG, "error during trx: %d", error); + FURI_LOG_T(TAG, "error during trx: %d", error); ret = st25tb_poller_process_error(error); break; } @@ -65,32 +52,11 @@ static St25tbError st25tb_poller_frame_exchange( return ret; } -St25tbType st25tb_get_type_from_uid(const uint8_t uid[ST25TB_UID_SIZE]) { - switch(uid[2] >> 2) { - case 0x0: - case 0x3: - return St25tbTypeX4k; - case 0x4: - return St25tbTypeX512; - case 0x6: - return St25tbType512Ac; - case 0x7: - return St25tbType04k; - case 0xc: - return St25tbType512At; - case 0xf: - return St25tbType02k; - default: - furi_crash("unsupported st25tb type"); - } -} - -St25tbError st25tb_poller_initiate(St25tbPoller* instance, uint8_t* chip_id) { +St25tbError st25tb_poller_initiate(St25tbPoller* instance, uint8_t* chip_id_ptr) { // Send Initiate() furi_assert(instance); furi_assert(instance->nfc); - instance->state = St25tbPollerStateInitiateInProgress; bit_buffer_reset(instance->tx_buffer); bit_buffer_reset(instance->rx_buffer); bit_buffer_append_byte(instance->tx_buffer, 0x06); @@ -98,77 +64,90 @@ St25tbError st25tb_poller_initiate(St25tbPoller* instance, uint8_t* chip_id) { St25tbError ret; do { - ret = st25tb_poller_frame_exchange( + ret = st25tb_poller_send_frame( instance, instance->tx_buffer, instance->rx_buffer, ST25TB_FDT_FC); if(ret != St25tbErrorNone) { break; } if(bit_buffer_get_size_bytes(instance->rx_buffer) != 1) { - FURI_LOG_D(TAG, "Unexpected Initiate response size"); + FURI_LOG_E(TAG, "Unexpected Initiate response size"); ret = St25tbErrorCommunication; break; } - if(chip_id) { - *chip_id = bit_buffer_get_byte(instance->rx_buffer, 0); + uint8_t chip_id = bit_buffer_get_byte(instance->rx_buffer, 0); + FURI_LOG_D(TAG, "Got chip_id=0x%02X", chip_id); + if(chip_id_ptr) { + *chip_id_ptr = bit_buffer_get_byte(instance->rx_buffer, 0); } } while(false); return ret; } -St25tbError st25tb_poller_activate(St25tbPoller* instance, St25tbData* data) { +St25tbError st25tb_poller_select(St25tbPoller* instance, uint8_t* chip_id_ptr) { furi_assert(instance); furi_assert(instance->nfc); - st25tb_reset(data); - St25tbError ret; do { - ret = st25tb_poller_initiate(instance, &data->chip_id); - if(ret != St25tbErrorNone) { - break; - } + uint8_t chip_id; - instance->state = St25tbPollerStateActivationInProgress; + if(chip_id_ptr != NULL) { + chip_id = *chip_id_ptr; + } else { + ret = st25tb_poller_initiate(instance, &chip_id); + if(ret != St25tbErrorNone) { + break; + } + } bit_buffer_reset(instance->tx_buffer); bit_buffer_reset(instance->rx_buffer); // Send Select(Chip_ID), let's just assume that collisions won't ever happen :D bit_buffer_append_byte(instance->tx_buffer, 0x0E); - bit_buffer_append_byte(instance->tx_buffer, data->chip_id); + bit_buffer_append_byte(instance->tx_buffer, chip_id); - ret = st25tb_poller_frame_exchange( + ret = st25tb_poller_send_frame( instance, instance->tx_buffer, instance->rx_buffer, ST25TB_FDT_FC); if(ret != St25tbErrorNone) { - instance->state = St25tbPollerStateActivationFailed; break; } if(bit_buffer_get_size_bytes(instance->rx_buffer) != 1) { - FURI_LOG_D(TAG, "Unexpected Select response size"); - instance->state = St25tbPollerStateActivationFailed; + FURI_LOG_E(TAG, "Unexpected Select response size"); ret = St25tbErrorCommunication; break; } - if(bit_buffer_get_byte(instance->rx_buffer, 0) != data->chip_id) { - FURI_LOG_D(TAG, "ChipID mismatch"); - instance->state = St25tbPollerStateActivationFailed; + if(bit_buffer_get_byte(instance->rx_buffer, 0) != chip_id) { + FURI_LOG_E(TAG, "ChipID mismatch"); ret = St25tbErrorColResFailed; break; } - instance->state = St25tbPollerStateActivated; - ret = st25tb_poller_get_uid(instance, data->uid); + ret = st25tb_poller_get_uid(instance, instance->data->uid); if(ret != St25tbErrorNone) { - instance->state = St25tbPollerStateActivationFailed; break; } - data->type = st25tb_get_type_from_uid(data->uid); + instance->data->type = st25tb_get_type_from_uid(instance->data->uid); + } while(false); + + return ret; +} + +St25tbError st25tb_poller_read(St25tbPoller* instance, St25tbData* data) { + furi_assert(instance); + furi_assert(instance->nfc); + + St25tbError ret; + + memcpy(data, instance->data, sizeof(St25tbData)); + + do { bool read_blocks = true; for(uint8_t i = 0; i < st25tb_get_block_count(data->type); i++) { ret = st25tb_poller_read_block(instance, &data->blocks[i], i); @@ -181,6 +160,9 @@ St25tbError st25tb_poller_activate(St25tbPoller* instance, St25tbData* data) { break; } ret = st25tb_poller_read_block(instance, &data->system_otp_block, ST25TB_SYSTEM_OTP_BLOCK); + if(ret != St25tbErrorNone) { + break; + } } while(false); return ret; @@ -198,15 +180,14 @@ St25tbError st25tb_poller_get_uid(St25tbPoller* instance, uint8_t* uid) { bit_buffer_append_byte(instance->tx_buffer, 0x0B); - ret = st25tb_poller_frame_exchange( + ret = st25tb_poller_send_frame( instance, instance->tx_buffer, instance->rx_buffer, ST25TB_FDT_FC); if(ret != St25tbErrorNone) { break; } if(bit_buffer_get_size_bytes(instance->rx_buffer) != ST25TB_UID_SIZE) { - FURI_LOG_D(TAG, "Unexpected Get_UID() response size"); - instance->state = St25tbPollerStateActivationFailed; + FURI_LOG_E(TAG, "Unexpected Get_UID() response size"); ret = St25tbErrorCommunication; break; } @@ -215,6 +196,17 @@ St25tbError st25tb_poller_get_uid(St25tbPoller* instance, uint8_t* uid) { FURI_SWAP(uid[1], uid[6]); FURI_SWAP(uid[2], uid[5]); FURI_SWAP(uid[3], uid[4]); + FURI_LOG_I( + TAG, + "Got tag with uid: %02X %02X %02X %02X %02X %02X %02X %02X", + uid[0], + uid[1], + uid[2], + uid[3], + uid[4], + uid[5], + uid[6], + uid[7]); } while(false); return ret; } @@ -227,7 +219,7 @@ St25tbError furi_assert( (block_number <= st25tb_get_block_count(instance->data->type)) || block_number == ST25TB_SYSTEM_OTP_BLOCK); - FURI_LOG_D(TAG, "reading block %d", block_number); + FURI_LOG_T(TAG, "reading block %d", block_number); bit_buffer_reset(instance->tx_buffer); bit_buffer_reset(instance->rx_buffer); @@ -236,60 +228,88 @@ St25tbError bit_buffer_append_byte(instance->tx_buffer, block_number); St25tbError ret; do { - ret = st25tb_poller_frame_exchange( + ret = st25tb_poller_send_frame( instance, instance->tx_buffer, instance->rx_buffer, ST25TB_FDT_FC); if(ret != St25tbErrorNone) { break; } if(bit_buffer_get_size_bytes(instance->rx_buffer) != ST25TB_BLOCK_SIZE) { - FURI_LOG_D(TAG, "Unexpected Read_block(Addr) response size"); + FURI_LOG_E(TAG, "Unexpected Read_block(Addr) response size"); ret = St25tbErrorCommunication; break; } bit_buffer_write_bytes(instance->rx_buffer, block, ST25TB_BLOCK_SIZE); - FURI_LOG_D(TAG, "read result: %08lX", *block); + FURI_LOG_D(TAG, "Read_block(%d) result: %08lX", block_number, *block); } while(false); return ret; } -St25tbError st25tb_poller_halt(St25tbPoller* instance) { +St25tbError + st25tb_poller_write_block(St25tbPoller* instance, uint32_t block, uint8_t block_number) { furi_assert(instance); - + furi_assert(instance->nfc); + furi_assert( + (block_number <= st25tb_get_block_count(instance->data->type)) || + block_number == ST25TB_SYSTEM_OTP_BLOCK); + FURI_LOG_T(TAG, "writing block %d", block_number); bit_buffer_reset(instance->tx_buffer); - bit_buffer_reset(instance->rx_buffer); - - // Send Completion() - bit_buffer_append_byte(instance->tx_buffer, 0x0F); + // Send Write_block(Addr, Data) + bit_buffer_append_byte(instance->tx_buffer, 0x09); + bit_buffer_append_byte(instance->tx_buffer, block_number); + bit_buffer_append_bytes(instance->tx_buffer, (uint8_t*)&block, ST25TB_BLOCK_SIZE); St25tbError ret; - do { - ret = st25tb_poller_frame_exchange( + ret = st25tb_poller_send_frame( instance, instance->tx_buffer, instance->rx_buffer, ST25TB_FDT_FC); - if(ret != St25tbErrorTimeout) { + if(ret != St25tbErrorTimeout) { // tag doesn't ack writes so timeout are expected. break; } - instance->state = St25tbPollerStateIdle; + furi_delay_ms(7); // 7ms is the max programming time as per datasheet + + uint32_t block_check; + ret = st25tb_poller_read_block(instance, &block_check, block_number); + if(ret != St25tbErrorNone) { + FURI_LOG_E(TAG, "write verification failed: read error"); + break; + } + if(block_check != block) { + FURI_LOG_E( + TAG, + "write verification failed: wrote %08lX but read back %08lX", + block, + block_check); + ret = St25tbErrorWriteFailed; + break; + } + FURI_LOG_D(TAG, "wrote %08lX to block %d", block, block_number); } while(false); return ret; } -St25tbError st25tb_poller_send_frame( - St25tbPoller* instance, - const BitBuffer* tx_buffer, - BitBuffer* rx_buffer, - uint32_t fwt) { +St25tbError st25tb_poller_halt(St25tbPoller* instance) { + furi_assert(instance); + + bit_buffer_reset(instance->tx_buffer); + bit_buffer_reset(instance->rx_buffer); + + // Send Completion() + bit_buffer_append_byte(instance->tx_buffer, 0x0F); + St25tbError ret; do { - ret = st25tb_poller_prepare_trx(instance); - if(ret != St25tbErrorNone) break; + ret = st25tb_poller_send_frame( + instance, instance->tx_buffer, instance->rx_buffer, ST25TB_FDT_FC); + if(ret != St25tbErrorTimeout) { + break; + } - ret = st25tb_poller_frame_exchange(instance, tx_buffer, rx_buffer, fwt); + instance->state = St25tbPollerStateSelect; } while(false); return ret; diff --git a/lib/nfc/protocols/st25tb/st25tb_poller_i.h b/lib/nfc/protocols/st25tb/st25tb_poller_i.h index 27218d7b44a..e16feb7812c 100644 --- a/lib/nfc/protocols/st25tb/st25tb_poller_i.h +++ b/lib/nfc/protocols/st25tb/st25tb_poller_i.h @@ -1,8 +1,9 @@ #pragma once -#include "protocols/st25tb/st25tb.h" #include "st25tb_poller.h" +#include + #ifdef __cplusplus extern "C" { #endif @@ -10,14 +11,30 @@ extern "C" { #define ST25TB_POLLER_MAX_BUFFER_SIZE (16U) typedef enum { - St25tbPollerStateIdle, - St25tbPollerStateInitiateInProgress, - St25tbPollerStateInitiateFailed, - St25tbPollerStateActivationInProgress, - St25tbPollerStateActivationFailed, - St25tbPollerStateActivated, + St25tbPollerStateSelect, + St25tbPollerStateRequestMode, + St25tbPollerStateRead, + St25tbPollerStateWrite, + St25tbPollerStateSuccess, + St25tbPollerStateFailure, + + St25tbPollerStateNum, } St25tbPollerState; +typedef struct { + uint8_t current_block; +} St25tbPollerReadContext; + +typedef struct { + uint8_t block_number; + uint32_t block_data; +} St25tbPollerWriteContext; + +typedef union { + St25tbPollerReadContext read; + St25tbPollerWriteContext write; +} St25tbPollerContext; + struct St25tbPoller { Nfc* nfc; St25tbPollerState state; @@ -25,6 +42,8 @@ struct St25tbPoller { BitBuffer* tx_buffer; BitBuffer* rx_buffer; + St25tbPollerContext poller_ctx; + NfcGenericEvent general_event; St25tbPollerEvent st25tb_event; St25tbPollerEventData st25tb_event_data; diff --git a/lib/nfc/protocols/st25tb/st25tb_poller_sync.c b/lib/nfc/protocols/st25tb/st25tb_poller_sync.c new file mode 100644 index 00000000000..3cd0b379290 --- /dev/null +++ b/lib/nfc/protocols/st25tb/st25tb_poller_sync.c @@ -0,0 +1,211 @@ +#include "st25tb_poller_sync.h" +#include "st25tb_poller_i.h" + +#define ST25TB_POLLER_FLAG_COMMAND_COMPLETE (1UL << 0) + +typedef enum { + St25tbPollerCmdTypeDetectType, + St25tbPollerCmdTypeReadBlock, + St25tbPollerCmdTypeWriteBlock, + + St25tbPollerCmdTypeNum, +} St25tbPollerCmdType; + +typedef struct { + St25tbType* type; +} St25tbPollerCmdDetectTypeData; + +typedef struct { + St25tbData* data; +} St25tbPollerCmdReadData; + +typedef struct { + uint8_t block_num; + uint32_t* block; +} St25tbPollerCmdReadBlockData; + +typedef struct { + uint8_t block_num; + uint32_t block; +} St25tbPollerCmdWriteBlockData; + +typedef union { + St25tbPollerCmdDetectTypeData detect_type; + St25tbPollerCmdReadData read; + St25tbPollerCmdReadBlockData read_block; + St25tbPollerCmdWriteBlockData write_block; +} St25tbPollerCmdData; + +typedef struct { + FuriThreadId thread_id; + St25tbError error; + St25tbPollerCmdType cmd_type; + St25tbPollerCmdData cmd_data; +} St25tbPollerSyncContext; + +typedef St25tbError (*St25tbPollerCmdHandler)(St25tbPoller* poller, St25tbPollerCmdData* data); + +static St25tbError st25tb_poller_detect_handler(St25tbPoller* poller, St25tbPollerCmdData* data) { + uint8_t uid[ST25TB_UID_SIZE]; + St25tbError error = st25tb_poller_get_uid(poller, uid); + if(error == St25tbErrorNone) { + *data->detect_type.type = st25tb_get_type_from_uid(uid); + } + return error; +} + +static St25tbError + st25tb_poller_read_block_handler(St25tbPoller* poller, St25tbPollerCmdData* data) { + return st25tb_poller_read_block(poller, data->read_block.block, data->read_block.block_num); +} + +static St25tbError + st25tb_poller_write_block_handler(St25tbPoller* poller, St25tbPollerCmdData* data) { + return st25tb_poller_write_block(poller, data->write_block.block, data->write_block.block_num); +} + +static St25tbPollerCmdHandler st25tb_poller_cmd_handlers[St25tbPollerCmdTypeNum] = { + [St25tbPollerCmdTypeDetectType] = st25tb_poller_detect_handler, + [St25tbPollerCmdTypeReadBlock] = st25tb_poller_read_block_handler, + [St25tbPollerCmdTypeWriteBlock] = st25tb_poller_write_block_handler, +}; + +static NfcCommand st25tb_poller_cmd_callback(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.event_data); + furi_assert(event.instance); + furi_assert(event.protocol == NfcProtocolSt25tb); + + St25tbPollerSyncContext* poller_context = context; + St25tbPoller* st25tb_poller = event.instance; + St25tbPollerEvent* st25tb_event = event.event_data; + + if(st25tb_event->type == St25tbPollerEventTypeReady) { + poller_context->error = st25tb_poller_cmd_handlers[poller_context->cmd_type]( + st25tb_poller, &poller_context->cmd_data); + } else { + poller_context->error = st25tb_event->data->error; + } + + furi_thread_flags_set(poller_context->thread_id, ST25TB_POLLER_FLAG_COMMAND_COMPLETE); + + return NfcCommandStop; +} + +static St25tbError st25tb_poller_cmd_execute(Nfc* nfc, St25tbPollerSyncContext* poller_ctx) { + furi_assert(nfc); + furi_assert(poller_ctx->cmd_type < St25tbPollerCmdTypeNum); + poller_ctx->thread_id = furi_thread_get_current_id(); + + NfcPoller* poller = nfc_poller_alloc(nfc, NfcProtocolSt25tb); + nfc_poller_start(poller, st25tb_poller_cmd_callback, poller_ctx); + furi_thread_flags_wait(ST25TB_POLLER_FLAG_COMMAND_COMPLETE, FuriFlagWaitAny, FuriWaitForever); + furi_thread_flags_clear(ST25TB_POLLER_FLAG_COMMAND_COMPLETE); + + nfc_poller_stop(poller); + nfc_poller_free(poller); + + return poller_ctx->error; +} + +St25tbError st25tb_poller_sync_read_block(Nfc* nfc, uint8_t block_num, uint32_t* block) { + furi_assert(block); + St25tbPollerSyncContext poller_context = { + .cmd_type = St25tbPollerCmdTypeReadBlock, + .cmd_data = + { + .read_block = + { + .block = block, + .block_num = block_num, + }, + }, + }; + return st25tb_poller_cmd_execute(nfc, &poller_context); +} + +St25tbError st25tb_poller_sync_write_block(Nfc* nfc, uint8_t block_num, uint32_t block) { + St25tbPollerSyncContext poller_context = { + .cmd_type = St25tbPollerCmdTypeWriteBlock, + .cmd_data = + { + .write_block = + { + .block = block, + .block_num = block_num, + }, + }, + }; + return st25tb_poller_cmd_execute(nfc, &poller_context); +} + +St25tbError st25tb_poller_sync_detect_type(Nfc* nfc, St25tbType* type) { + furi_assert(type); + St25tbPollerSyncContext poller_context = { + .cmd_type = St25tbPollerCmdTypeDetectType, + .cmd_data = + { + .detect_type = + { + .type = type, + }, + }, + }; + return st25tb_poller_cmd_execute(nfc, &poller_context); +} + +static NfcCommand nfc_scene_read_poller_callback_st25tb(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.event_data); + furi_assert(event.instance); + furi_assert(event.protocol == NfcProtocolSt25tb); + + St25tbPollerSyncContext* poller_context = context; + St25tbPollerEvent* st25tb_event = event.event_data; + + NfcCommand command = NfcCommandContinue; + if(st25tb_event->type == St25tbPollerEventTypeRequestMode) { + st25tb_event->data->mode_request.mode = St25tbPollerModeRead; + } else if( + st25tb_event->type == St25tbPollerEventTypeSuccess || + st25tb_event->type == St25tbPollerEventTypeFailure) { + if(st25tb_event->type == St25tbPollerEventTypeSuccess) { + memcpy( + poller_context->cmd_data.read.data, + st25tb_poller_get_data(event.instance), + sizeof(St25tbData)); + } else { + poller_context->error = st25tb_event->data->error; + } + command = NfcCommandStop; + furi_thread_flags_set(poller_context->thread_id, ST25TB_POLLER_FLAG_COMMAND_COMPLETE); + } + + return command; +} + +St25tbError st25tb_poller_sync_read(Nfc* nfc, St25tbData* data) { + furi_assert(nfc); + furi_assert(data); + + St25tbPollerSyncContext poller_context = { + .thread_id = furi_thread_get_current_id(), + .cmd_data = + { + .read = + { + .data = data, + }, + }, + }; + + NfcPoller* poller = nfc_poller_alloc(nfc, NfcProtocolSt25tb); + nfc_poller_start(poller, nfc_scene_read_poller_callback_st25tb, &poller_context); + furi_thread_flags_wait(ST25TB_POLLER_FLAG_COMMAND_COMPLETE, FuriFlagWaitAny, FuriWaitForever); + furi_thread_flags_clear(ST25TB_POLLER_FLAG_COMMAND_COMPLETE); + + nfc_poller_stop(poller); + nfc_poller_free(poller); + + return poller_context.error; +} \ No newline at end of file diff --git a/lib/nfc/protocols/st25tb/st25tb_poller_sync.h b/lib/nfc/protocols/st25tb/st25tb_poller_sync.h new file mode 100644 index 00000000000..ecd994b3981 --- /dev/null +++ b/lib/nfc/protocols/st25tb/st25tb_poller_sync.h @@ -0,0 +1,20 @@ +#pragma once + +#include "st25tb.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +St25tbError st25tb_poller_sync_read_block(Nfc* nfc, uint8_t block_num, uint32_t* block); + +St25tbError st25tb_poller_sync_write_block(Nfc* nfc, uint8_t block_num, uint32_t block); + +St25tbError st25tb_poller_sync_detect_type(Nfc* nfc, St25tbType* type); + +St25tbError st25tb_poller_sync_read(Nfc* nfc, St25tbData* data); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index cd89b554af8..e735e2c6ddd 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,48.0,, +Version,+,49.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 45c98ae1a5f..e25254dddd2 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,48.0,, +Version,+,49.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -145,6 +145,7 @@ Header,+,lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.h,, Header,+,lib/nfc/protocols/slix/slix.h,, Header,+,lib/nfc/protocols/st25tb/st25tb.h,, Header,+,lib/nfc/protocols/st25tb/st25tb_poller.h,, +Header,+,lib/nfc/protocols/st25tb/st25tb_poller_sync.h,, Header,+,lib/one_wire/maxim_crc.h,, Header,+,lib/one_wire/one_wire_host.h,, Header,+,lib/one_wire/one_wire_slave.h,, @@ -2810,15 +2811,21 @@ Function,+,st25tb_free,void,St25tbData* Function,+,st25tb_get_base_data,St25tbData*,const St25tbData* Function,+,st25tb_get_block_count,uint8_t,St25tbType Function,+,st25tb_get_device_name,const char*,"const St25tbData*, NfcDeviceNameType" +Function,+,st25tb_get_type_from_uid,St25tbType,const uint8_t* Function,+,st25tb_get_uid,const uint8_t*,"const St25tbData*, size_t*" Function,+,st25tb_is_equal,_Bool,"const St25tbData*, const St25tbData*" Function,+,st25tb_load,_Bool,"St25tbData*, FlipperFormat*, uint32_t" -Function,+,st25tb_poller_activate,St25tbError,"St25tbPoller*, St25tbData*" Function,+,st25tb_poller_get_uid,St25tbError,"St25tbPoller*, uint8_t*" Function,+,st25tb_poller_halt,St25tbError,St25tbPoller* Function,+,st25tb_poller_initiate,St25tbError,"St25tbPoller*, uint8_t*" Function,+,st25tb_poller_read_block,St25tbError,"St25tbPoller*, uint32_t*, uint8_t" +Function,+,st25tb_poller_select,St25tbError,"St25tbPoller*, uint8_t*" Function,+,st25tb_poller_send_frame,St25tbError,"St25tbPoller*, const BitBuffer*, BitBuffer*, uint32_t" +Function,+,st25tb_poller_sync_detect_type,St25tbError,"Nfc*, St25tbType*" +Function,+,st25tb_poller_sync_read,St25tbError,"Nfc*, St25tbData*" +Function,+,st25tb_poller_sync_read_block,St25tbError,"Nfc*, uint8_t, uint32_t*" +Function,+,st25tb_poller_sync_write_block,St25tbError,"Nfc*, uint8_t, uint32_t" +Function,+,st25tb_poller_write_block,St25tbError,"St25tbPoller*, uint32_t, uint8_t" Function,+,st25tb_reset,void,St25tbData* Function,+,st25tb_save,_Bool,"const St25tbData*, FlipperFormat*" Function,+,st25tb_set_uid,_Bool,"St25tbData*, const uint8_t*, size_t" From b51a754fd95285069c360afa48de380e6311c8e2 Mon Sep 17 00:00:00 2001 From: Augusto Zanellato Date: Fri, 1 Dec 2023 14:25:53 +0100 Subject: [PATCH 819/824] Mifare Classic nested auth support (#3238) Co-authored-by: Aleksandr Kutuzov --- lib/nfc/protocols/mf_classic/crypto1.c | 9 +- lib/nfc/protocols/mf_classic/crypto1.h | 3 +- .../protocols/mf_classic/mf_classic_poller.h | 40 +++++++++ .../mf_classic/mf_classic_poller_i.c | 89 ++++++++++++++++--- targets/f18/api_symbols.csv | 2 +- targets/f7/api_symbols.csv | 4 +- 6 files changed, 128 insertions(+), 19 deletions(-) diff --git a/lib/nfc/protocols/mf_classic/crypto1.c b/lib/nfc/protocols/mf_classic/crypto1.c index df01a348c85..02bc677ba69 100644 --- a/lib/nfc/protocols/mf_classic/crypto1.c +++ b/lib/nfc/protocols/mf_classic/crypto1.c @@ -143,7 +143,8 @@ void crypto1_encrypt_reader_nonce( uint32_t cuid, uint8_t* nt, uint8_t* nr, - BitBuffer* out) { + BitBuffer* out, + bool is_nested) { furi_assert(crypto); furi_assert(nt); furi_assert(nr); @@ -153,7 +154,11 @@ void crypto1_encrypt_reader_nonce( uint32_t nt_num = nfc_util_bytes2num(nt, sizeof(uint32_t)); crypto1_init(crypto, key); - crypto1_word(crypto, nt_num ^ cuid, 0); + if(is_nested) { + nt_num = crypto1_word(crypto, nt_num ^ cuid, 1) ^ nt_num; + } else { + crypto1_word(crypto, nt_num ^ cuid, 0); + } for(size_t i = 0; i < 4; i++) { uint8_t byte = crypto1_byte(crypto, nr[i], 0) ^ nr[i]; diff --git a/lib/nfc/protocols/mf_classic/crypto1.h b/lib/nfc/protocols/mf_classic/crypto1.h index f2bdb272b0e..7cc16fcffde 100644 --- a/lib/nfc/protocols/mf_classic/crypto1.h +++ b/lib/nfc/protocols/mf_classic/crypto1.h @@ -35,7 +35,8 @@ void crypto1_encrypt_reader_nonce( uint32_t cuid, uint8_t* nt, uint8_t* nr, - BitBuffer* out); + BitBuffer* out, + bool is_nested); uint32_t prng_successor(uint32_t x, uint32_t n); diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.h b/lib/nfc/protocols/mf_classic/mf_classic_poller.h index f05a6800ad1..19e52570175 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.h @@ -178,6 +178,25 @@ MfClassicError mf_classic_poller_get_nt( MfClassicKeyType key_type, MfClassicNt* nt); +/** + * @brief Collect tag nonce during nested authentication. + * + * Must ONLY be used inside the callback function. + * + * Starts nested authentication procedure and collects tag nonce. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] block_num block number for authentication. + * @param[in] key_type key type to be used for authentication. + * @param[out] nt pointer to the MfClassicNt structure to be filled with nonce data. + * @return MfClassicErrorNone on success, an error code on failure. + */ +MfClassicError mf_classic_poller_get_nt_nested( + MfClassicPoller* instance, + uint8_t block_num, + MfClassicKeyType key_type, + MfClassicNt* nt); + /** * @brief Perform authentication. * @@ -200,6 +219,27 @@ MfClassicError mf_classic_poller_auth( MfClassicKeyType key_type, MfClassicAuthContext* data); +/** + * @brief Perform nested authentication. + * + * Must ONLY be used inside the callback function. + * + * Perform nested authentication as specified in Mf Classic protocol. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] block_num block number for authentication. + * @param[in] key key to be used for authentication. + * @param[in] key_type key type to be used for authentication. + * @param[out] data pointer to MfClassicAuthContext structure to be filled with authentication data. + * @return MfClassicErrorNone on success, an error code on failure. + */ +MfClassicError mf_classic_poller_auth_nested( + MfClassicPoller* instance, + uint8_t block_num, + MfClassicKey* key, + MfClassicKeyType key_type, + MfClassicAuthContext* data); + /** * @brief Halt the tag. * diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c index 4b071815ea8..16bfb3f7282 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c @@ -33,11 +33,12 @@ MfClassicError mf_classic_process_error(Iso14443_3aError error) { return ret; } -MfClassicError mf_classic_poller_get_nt( +static MfClassicError mf_classic_poller_get_nt_common( MfClassicPoller* instance, uint8_t block_num, MfClassicKeyType key_type, - MfClassicNt* nt) { + MfClassicNt* nt, + bool is_nested) { MfClassicError ret = MfClassicErrorNone; Iso14443_3aError error = Iso14443_3aErrorNone; @@ -47,14 +48,29 @@ MfClassicError mf_classic_poller_get_nt( uint8_t auth_cmd[2] = {auth_type, block_num}; bit_buffer_copy_bytes(instance->tx_plain_buffer, auth_cmd, sizeof(auth_cmd)); - error = iso14443_3a_poller_send_standard_frame( - instance->iso14443_3a_poller, - instance->tx_plain_buffer, - instance->rx_plain_buffer, - MF_CLASSIC_FWT_FC); - if(error != Iso14443_3aErrorWrongCrc) { - ret = mf_classic_process_error(error); - break; + if(is_nested) { + iso14443_crc_append(Iso14443CrcTypeA, instance->tx_plain_buffer); + crypto1_encrypt( + instance->crypto, NULL, instance->tx_plain_buffer, instance->tx_encrypted_buffer); + error = iso14443_3a_poller_txrx_custom_parity( + instance->iso14443_3a_poller, + instance->tx_encrypted_buffer, + instance->rx_plain_buffer, // NT gets decrypted by mf_classic_async_auth + MF_CLASSIC_FWT_FC); + if(error != Iso14443_3aErrorNone) { + ret = mf_classic_process_error(error); + break; + } + } else { + error = iso14443_3a_poller_send_standard_frame( + instance->iso14443_3a_poller, + instance->tx_plain_buffer, + instance->rx_plain_buffer, + MF_CLASSIC_FWT_FC); + if(error != Iso14443_3aErrorWrongCrc) { + ret = mf_classic_process_error(error); + break; + } } if(bit_buffer_get_size_bytes(instance->rx_plain_buffer) != sizeof(MfClassicNt)) { ret = MfClassicErrorProtocol; @@ -69,12 +85,29 @@ MfClassicError mf_classic_poller_get_nt( return ret; } -MfClassicError mf_classic_poller_auth( +MfClassicError mf_classic_poller_get_nt( + MfClassicPoller* instance, + uint8_t block_num, + MfClassicKeyType key_type, + MfClassicNt* nt) { + return mf_classic_poller_get_nt_common(instance, block_num, key_type, nt, false); +} + +MfClassicError mf_classic_poller_get_nt_nested( + MfClassicPoller* instance, + uint8_t block_num, + MfClassicKeyType key_type, + MfClassicNt* nt) { + return mf_classic_poller_get_nt_common(instance, block_num, key_type, nt, true); +} + +static MfClassicError mf_classic_poller_auth_common( MfClassicPoller* instance, uint8_t block_num, MfClassicKey* key, MfClassicKeyType key_type, - MfClassicAuthContext* data) { + MfClassicAuthContext* data, + bool is_nested) { MfClassicError ret = MfClassicErrorNone; Iso14443_3aError error = Iso14443_3aErrorNone; @@ -84,7 +117,11 @@ MfClassicError mf_classic_poller_auth( iso14443_3a_poller_get_data(instance->iso14443_3a_poller)); MfClassicNt nt = {}; - ret = mf_classic_poller_get_nt(instance, block_num, key_type, &nt); + if(is_nested) { + ret = mf_classic_poller_get_nt_nested(instance, block_num, key_type, &nt); + } else { + ret = mf_classic_poller_get_nt(instance, block_num, key_type, &nt); + } if(ret != MfClassicErrorNone) break; if(data) { data->nt = nt; @@ -96,7 +133,13 @@ MfClassicError mf_classic_poller_auth( furi_hal_random_fill_buf(nr.data, sizeof(MfClassicNr)); crypto1_encrypt_reader_nonce( - instance->crypto, key_num, cuid, nt.data, nr.data, instance->tx_encrypted_buffer); + instance->crypto, + key_num, + cuid, + nt.data, + nr.data, + instance->tx_encrypted_buffer, + is_nested); error = iso14443_3a_poller_txrx_custom_parity( instance->iso14443_3a_poller, instance->tx_encrypted_buffer, @@ -130,6 +173,24 @@ MfClassicError mf_classic_poller_auth( return ret; } +MfClassicError mf_classic_poller_auth( + MfClassicPoller* instance, + uint8_t block_num, + MfClassicKey* key, + MfClassicKeyType key_type, + MfClassicAuthContext* data) { + return mf_classic_poller_auth_common(instance, block_num, key, key_type, data, false); +} + +MfClassicError mf_classic_poller_auth_nested( + MfClassicPoller* instance, + uint8_t block_num, + MfClassicKey* key, + MfClassicKeyType key_type, + MfClassicAuthContext* data) { + return mf_classic_poller_auth_common(instance, block_num, key, key_type, data, true); +} + MfClassicError mf_classic_poller_halt(MfClassicPoller* instance) { MfClassicError ret = MfClassicErrorNone; Iso14443_3aError error = Iso14443_3aErrorNone; diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index e735e2c6ddd..cb34f969adc 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,49.0,, +Version,+,49.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index e25254dddd2..439fc7bf5a5 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,49.0,, +Version,+,49.1,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -2288,6 +2288,8 @@ Function,+,mf_classic_is_sector_trailer,_Bool,uint8_t Function,+,mf_classic_is_value_block,_Bool,"MfClassicSectorTrailer*, uint8_t" Function,+,mf_classic_load,_Bool,"MfClassicData*, FlipperFormat*, uint32_t" Function,+,mf_classic_poller_auth,MfClassicError,"MfClassicPoller*, uint8_t, MfClassicKey*, MfClassicKeyType, MfClassicAuthContext*" +Function,+,mf_classic_poller_auth_nested,MfClassicError,"MfClassicPoller*, uint8_t, MfClassicKey*, MfClassicKeyType, MfClassicAuthContext*" +Function,+,mf_classic_poller_get_nt_nested,MfClassicError,"MfClassicPoller*, uint8_t, MfClassicKeyType, MfClassicNt*" Function,+,mf_classic_poller_get_nt,MfClassicError,"MfClassicPoller*, uint8_t, MfClassicKeyType, MfClassicNt*" Function,+,mf_classic_poller_halt,MfClassicError,MfClassicPoller* Function,+,mf_classic_poller_read_block,MfClassicError,"MfClassicPoller*, uint8_t, MfClassicBlock*" From 6a5d63803aeadc9f2b3cb4f150bb32dee841dc03 Mon Sep 17 00:00:00 2001 From: RebornedBrain <138568282+RebornedBrain@users.noreply.github.com> Date: Sat, 2 Dec 2023 07:45:47 +0300 Subject: [PATCH 820/824] [FL-3675] Ntag21x write (#3246) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * New scenes for ultralight poller write mode * Added new button and transition logic for write operation For now write is only possible for NTAG21x cards with default password and no AUTHLIM set * Poller states extended * Enums and datatypes extended for new poller mode * Added mode field to poller instance datatype * New states for poller added in order to implement write mode * Added new event type for locked cards in order to simplify state flow * New logic for poller write commands * Scenes adjustments * Scenes renamed * New field added to poller instance * Now we write in 'page per call' mode * Now function takes callback return value into account * Callback will be called only in write mode * Event type added * Log adjusted and start page to write set * Logs added and check in now false at start, then it moves to true * Now mf_ultralight_poller_handler_request_write_data halts card in case of check failure and stops poller * All fail events now returns NfcCommandStop callback * In case of fail we move back properly * Remove garbage Co-authored-by: gornekich Co-authored-by: あく --- .../mf_ultralight/mf_ultralight.c | 13 ++ .../main/nfc/scenes/nfc_scene_config.h | 4 + .../scenes/nfc_scene_mf_ultralight_write.c | 119 +++++++++++++++ .../nfc_scene_mf_ultralight_write_fail.c | 67 +++++++++ .../nfc_scene_mf_ultralight_write_success.c | 43 ++++++ .../nfc_scene_mf_ultralight_wrong_card.c | 58 ++++++++ .../mf_ultralight/mf_ultralight_poller.c | 139 +++++++++++++++++- .../mf_ultralight/mf_ultralight_poller.h | 16 ++ .../mf_ultralight/mf_ultralight_poller_i.h | 7 + 9 files changed, 462 insertions(+), 4 deletions(-) create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_ultralight_write.c create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_ultralight_write_fail.c create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_ultralight_write_success.c create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_ultralight_wrong_card.c diff --git a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c index c4fd04c7e5a..3e27fc539e6 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c +++ b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c @@ -12,6 +12,7 @@ enum { SubmenuIndexUnlock = SubmenuIndexCommonMax, SubmenuIndexUnlockByReader, SubmenuIndexUnlockByPassword, + SubmenuIndexWrite, }; static void nfc_scene_info_on_enter_mf_ultralight(NfcApp* instance) { @@ -106,6 +107,15 @@ static void nfc_scene_read_and_saved_menu_on_enter_mf_ultralight(NfcApp* instanc SubmenuIndexUnlock, nfc_protocol_support_common_submenu_callback, instance); + } else if( + data->type == MfUltralightTypeNTAG213 || data->type == MfUltralightTypeNTAG215 || + data->type == MfUltralightTypeNTAG216) { + submenu_add_item( + submenu, + "Write", + SubmenuIndexWrite, + nfc_protocol_support_common_submenu_callback, + instance); } } @@ -146,6 +156,9 @@ static bool if(event == SubmenuIndexUnlock) { scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightUnlockMenu); return true; + } else if(event == SubmenuIndexWrite) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightWrite); + return true; } return false; } diff --git a/applications/main/nfc/scenes/nfc_scene_config.h b/applications/main/nfc/scenes/nfc_scene_config.h index f415c66a6fa..a9887996d6d 100644 --- a/applications/main/nfc/scenes/nfc_scene_config.h +++ b/applications/main/nfc/scenes/nfc_scene_config.h @@ -24,6 +24,10 @@ ADD_SCENE(nfc, field, Field) ADD_SCENE(nfc, retry_confirm, RetryConfirm) ADD_SCENE(nfc, exit_confirm, ExitConfirm) +ADD_SCENE(nfc, mf_ultralight_write, MfUltralightWrite) +ADD_SCENE(nfc, mf_ultralight_write_success, MfUltralightWriteSuccess) +ADD_SCENE(nfc, mf_ultralight_write_fail, MfUltralightWriteFail) +ADD_SCENE(nfc, mf_ultralight_wrong_card, MfUltralightWrongCard) ADD_SCENE(nfc, mf_ultralight_unlock_menu, MfUltralightUnlockMenu) ADD_SCENE(nfc, mf_ultralight_unlock_warn, MfUltralightUnlockWarn) ADD_SCENE(nfc, mf_ultralight_key_input, MfUltralightKeyInput) diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_write.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_write.c new file mode 100644 index 00000000000..b3c1beef5ab --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_write.c @@ -0,0 +1,119 @@ +#include "../nfc_app_i.h" + +#include + +enum { + NfcSceneMfUltralightWriteStateCardSearch, + NfcSceneMfUltralightWriteStateCardFound, +}; + +NfcCommand nfc_scene_mf_ultralight_write_worker_callback(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.event_data); + furi_assert(event.protocol == NfcProtocolMfUltralight); + + NfcCommand command = NfcCommandContinue; + NfcApp* instance = context; + MfUltralightPollerEvent* mfu_event = event.event_data; + + if(mfu_event->type == MfUltralightPollerEventTypeRequestMode) { + mfu_event->data->poller_mode = MfUltralightPollerModeWrite; + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventCardDetected); + } else if(mfu_event->type == MfUltralightPollerEventTypeAuthRequest) { + mfu_event->data->auth_context.skip_auth = true; + } else if(mfu_event->type == MfUltralightPollerEventTypeRequestWriteData) { + mfu_event->data->write_data = + nfc_device_get_data(instance->nfc_device, NfcProtocolMfUltralight); + } else if(mfu_event->type == MfUltralightPollerEventTypeCardMismatch) { + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventWrongCard); + command = NfcCommandStop; + } else if(mfu_event->type == MfUltralightPollerEventTypeCardLocked) { + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerFailure); + command = NfcCommandStop; + } else if(mfu_event->type == MfUltralightPollerEventTypeWriteFail) { + command = NfcCommandStop; + } else if(mfu_event->type == MfUltralightPollerEventTypeWriteSuccess) { + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess); + command = NfcCommandStop; + } + return command; +} + +static void nfc_scene_mf_ultralight_write_setup_view(NfcApp* instance) { + Popup* popup = instance->popup; + popup_reset(popup); + uint32_t state = + scene_manager_get_scene_state(instance->scene_manager, NfcSceneMfUltralightWrite); + + if(state == NfcSceneMfUltralightWriteStateCardSearch) { + popup_set_text( + instance->popup, "Apply the initial\ncard only", 128, 32, AlignRight, AlignCenter); + popup_set_icon(instance->popup, 0, 8, &I_NFC_manual_60x50); + } else { + popup_set_header(popup, "Writing\nDon't move...", 52, 32, AlignLeft, AlignCenter); + popup_set_icon(popup, 12, 23, &A_Loading_24); + } + + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup); +} + +void nfc_scene_mf_ultralight_write_on_enter(void* context) { + NfcApp* instance = context; + dolphin_deed(DolphinDeedNfcEmulate); + + scene_manager_set_scene_state( + instance->scene_manager, + NfcSceneMfUltralightWrite, + NfcSceneMfUltralightWriteStateCardSearch); + nfc_scene_mf_ultralight_write_setup_view(instance); + + // Setup and start worker + FURI_LOG_D("WMFU", "Card searching..."); + instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfUltralight); + nfc_poller_start(instance->poller, nfc_scene_mf_ultralight_write_worker_callback, instance); + + nfc_blink_emulate_start(instance); +} + +bool nfc_scene_mf_ultralight_write_on_event(void* context, SceneManagerEvent event) { + NfcApp* instance = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcCustomEventCardDetected) { + scene_manager_set_scene_state( + instance->scene_manager, + NfcSceneMfUltralightWrite, + NfcSceneMfUltralightWriteStateCardFound); + nfc_scene_mf_ultralight_write_setup_view(instance); + consumed = true; + } else if(event.event == NfcCustomEventWrongCard) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightWrongCard); + consumed = true; + } else if(event.event == NfcCustomEventPollerSuccess) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightWriteSuccess); + consumed = true; + } else if(event.event == NfcCustomEventPollerFailure) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightWriteFail); + consumed = true; + } + } + + return consumed; +} + +void nfc_scene_mf_ultralight_write_on_exit(void* context) { + NfcApp* instance = context; + + nfc_poller_stop(instance->poller); + nfc_poller_free(instance->poller); + + scene_manager_set_scene_state( + instance->scene_manager, + NfcSceneMfUltralightWrite, + NfcSceneMfUltralightWriteStateCardSearch); + // Clear view + popup_reset(instance->popup); + + nfc_blink_stop(instance); +} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_write_fail.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_write_fail.c new file mode 100644 index 00000000000..dff5f278153 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_write_fail.c @@ -0,0 +1,67 @@ +#include "../nfc_app_i.h" + +void nfc_scene_mf_ultralight_write_fail_widget_callback( + GuiButtonType result, + InputType type, + void* context) { + NfcApp* instance = context; + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(instance->view_dispatcher, result); + } +} + +void nfc_scene_mf_ultralight_write_fail_on_enter(void* context) { + NfcApp* instance = context; + Widget* widget = instance->widget; + + notification_message(instance->notifications, &sequence_error); + + widget_add_icon_element(widget, 72, 17, &I_DolphinCommon_56x48); + widget_add_string_element( + widget, 7, 4, AlignLeft, AlignTop, FontPrimary, "Writing gone wrong!"); + widget_add_string_multiline_element( + widget, + 7, + 17, + AlignLeft, + AlignTop, + FontSecondary, + "Card protected by\npassword, AUTH0\nor lock bits"); + + widget_add_button_element( + widget, + GuiButtonTypeLeft, + "Finish", + nfc_scene_mf_ultralight_write_fail_widget_callback, + instance); + + // Setup and start worker + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget); +} + +static bool nfc_scene_mf_ultralight_write_fail_move_to_back_scene(const NfcApp* const instance) { + bool was_saved = scene_manager_has_previous_scene(instance->scene_manager, NfcSceneSavedMenu); + uint32_t scene_id = was_saved ? NfcSceneSavedMenu : NfcSceneReadMenu; + + return scene_manager_search_and_switch_to_previous_scene(instance->scene_manager, scene_id); +} + +bool nfc_scene_mf_ultralight_write_fail_on_event(void* context, SceneManagerEvent event) { + NfcApp* instance = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == GuiButtonTypeLeft) { + consumed = nfc_scene_mf_ultralight_write_fail_move_to_back_scene(instance); + } + } else if(event.type == SceneManagerEventTypeBack) { + consumed = nfc_scene_mf_ultralight_write_fail_move_to_back_scene(instance); + } + return consumed; +} + +void nfc_scene_mf_ultralight_write_fail_on_exit(void* context) { + NfcApp* instance = context; + + widget_reset(instance->widget); +} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_write_success.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_write_success.c new file mode 100644 index 00000000000..c1fbc35ee54 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_write_success.c @@ -0,0 +1,43 @@ +#include "../nfc_app_i.h" + +void nfc_scene_mf_ultralight_write_success_popup_callback(void* context) { + NfcApp* instance = context; + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventViewExit); +} + +void nfc_scene_mf_ultralight_write_success_on_enter(void* context) { + NfcApp* instance = context; + dolphin_deed(DolphinDeedNfcSave); + + notification_message(instance->notifications, &sequence_success); + + Popup* popup = instance->popup; + popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); + popup_set_header(popup, "Successfully\nwritten", 13, 22, AlignLeft, AlignBottom); + popup_set_timeout(popup, 1500); + popup_set_context(popup, instance); + popup_set_callback(popup, nfc_scene_mf_ultralight_write_success_popup_callback); + popup_enable_timeout(popup); + + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup); +} + +bool nfc_scene_mf_ultralight_write_success_on_event(void* context, SceneManagerEvent event) { + NfcApp* instance = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcCustomEventViewExit) { + consumed = scene_manager_search_and_switch_to_previous_scene( + instance->scene_manager, NfcSceneSavedMenu); + } + } + return consumed; +} + +void nfc_scene_mf_ultralight_write_success_on_exit(void* context) { + NfcApp* instance = context; + + // Clear view + popup_reset(instance->popup); +} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_wrong_card.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_wrong_card.c new file mode 100644 index 00000000000..a225c474db8 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_wrong_card.c @@ -0,0 +1,58 @@ +#include "../nfc_app_i.h" + +void nfc_scene_mf_ultralight_wrong_card_widget_callback( + GuiButtonType result, + InputType type, + void* context) { + NfcApp* instance = context; + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(instance->view_dispatcher, result); + } +} + +void nfc_scene_mf_ultralight_wrong_card_on_enter(void* context) { + NfcApp* instance = context; + Widget* widget = instance->widget; + + notification_message(instance->notifications, &sequence_error); + + widget_add_icon_element(widget, 73, 17, &I_DolphinCommon_56x48); + widget_add_string_element( + widget, 3, 4, AlignLeft, AlignTop, FontPrimary, "This is wrong card"); + widget_add_string_multiline_element( + widget, + 4, + 17, + AlignLeft, + AlignTop, + FontSecondary, + "Card of the same\ntype should be\n presented"); + //"Data management\nis only possible\nwith card of same type"); + widget_add_button_element( + widget, + GuiButtonTypeLeft, + "Retry", + nfc_scene_mf_ultralight_wrong_card_widget_callback, + instance); + + // Setup and start worker + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget); +} + +bool nfc_scene_mf_ultralight_wrong_card_on_event(void* context, SceneManagerEvent event) { + NfcApp* instance = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == GuiButtonTypeLeft) { + consumed = scene_manager_previous_scene(instance->scene_manager); + } + } + return consumed; +} + +void nfc_scene_mf_ultralight_wrong_card_on_exit(void* context) { + NfcApp* instance = context; + + widget_reset(instance->widget); +} \ No newline at end of file diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c index 86ab68c8b14..619cd8c5fbc 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c @@ -224,11 +224,24 @@ static NfcCommand mf_ultralight_poller_handler_idle(MfUltralightPoller* instance instance->tearing_flag_read = 0; instance->tearing_flag_total = 3; instance->pages_read = 0; - instance->state = MfUltralightPollerStateReadVersion; - + instance->state = MfUltralightPollerStateRequestMode; + instance->current_page = 0; return NfcCommandContinue; } +static NfcCommand mf_ultralight_poller_handler_request_mode(MfUltralightPoller* instance) { + NfcCommand command = NfcCommandContinue; + + instance->mfu_event.type = MfUltralightPollerEventTypeRequestMode; + instance->mfu_event.data->poller_mode = MfUltralightPollerModeRead; + + command = instance->callback(instance->general_event, instance->context); + instance->mode = instance->mfu_event.data->poller_mode; + + instance->state = MfUltralightPollerStateReadVersion; + return command; +} + static NfcCommand mf_ultralight_poller_handler_read_version(MfUltralightPoller* instance) { instance->error = mf_ultralight_poller_read_version(instance, &instance->data->version); if(instance->error == MfUltralightErrorNone) { @@ -259,6 +272,7 @@ static NfcCommand mf_ultralight_poller_handler_check_ultralight_c(MfUltralightPo } static NfcCommand mf_ultralight_poller_handler_check_ntag_203(MfUltralightPoller* instance) { + MfUltralightPollerState next_state = MfUltralightPollerStateGetFeatureSet; MfUltralightPageReadCommandData data = {}; instance->error = mf_ultralight_poller_read_page(instance, 41, &data); if(instance->error == MfUltralightErrorNone) { @@ -268,8 +282,13 @@ static NfcCommand mf_ultralight_poller_handler_check_ntag_203(MfUltralightPoller FURI_LOG_D(TAG, "Original Ultralight detected"); iso14443_3a_poller_halt(instance->iso14443_3a_poller); instance->data->type = MfUltralightTypeUnknown; + if(instance->mode == MfUltralightPollerModeWrite) { + instance->mfu_event.type = MfUltralightPollerEventTypeCardMismatch; + instance->callback(instance->general_event, instance->context); + next_state = MfUltralightPollerStateWriteFail; + } } - instance->state = MfUltralightPollerStateGetFeatureSet; + instance->state = next_state; return NfcCommandContinue; } @@ -508,6 +527,7 @@ static NfcCommand mf_ultralight_poller_handler_try_default_pass(MfUltralightPoll static NfcCommand mf_ultralight_poller_handler_read_fail(MfUltralightPoller* instance) { FURI_LOG_D(TAG, "Read Failed"); iso14443_3a_poller_halt(instance->iso14443_3a_poller); + instance->mfu_event.type = MfUltralightPollerEventTypeReadFailed; instance->mfu_event.data->error = instance->error; NfcCommand command = instance->callback(instance->general_event, instance->context); instance->state = MfUltralightPollerStateIdle; @@ -516,15 +536,121 @@ static NfcCommand mf_ultralight_poller_handler_read_fail(MfUltralightPoller* ins static NfcCommand mf_ultralight_poller_handler_read_success(MfUltralightPoller* instance) { FURI_LOG_D(TAG, "Read success"); - iso14443_3a_poller_halt(instance->iso14443_3a_poller); instance->mfu_event.type = MfUltralightPollerEventTypeReadSuccess; NfcCommand command = instance->callback(instance->general_event, instance->context); + + if(instance->mode == MfUltralightPollerModeRead) { + iso14443_3a_poller_halt(instance->iso14443_3a_poller); + instance->state = MfUltralightPollerStateIdle; + } else { + instance->state = MfUltralightPollerStateRequestWriteData; + } + + return command; +} + +static NfcCommand mf_ultralight_poller_handler_request_write_data(MfUltralightPoller* instance) { + FURI_LOG_D(TAG, "Check writing capability"); + NfcCommand command = NfcCommandContinue; + MfUltralightPollerState next_state = MfUltralightPollerStateWritePages; + instance->current_page = 4; + + instance->mfu_event.type = MfUltralightPollerEventTypeRequestWriteData; + instance->callback(instance->general_event, instance->context); + + const MfUltralightData* write_data = instance->mfu_event.data->write_data; + const MfUltralightData* tag_data = instance->data; + uint32_t features = mf_ultralight_get_feature_support_set(tag_data->type); + + bool check_passed = false; + do { + if(write_data->type != tag_data->type) { + FURI_LOG_D(TAG, "Incorrect tag type"); + instance->mfu_event.type = MfUltralightPollerEventTypeCardMismatch; + break; + } + + if(!instance->auth_context.auth_success) { + FURI_LOG_D(TAG, "Unknown password"); + instance->mfu_event.type = MfUltralightPollerEventTypeCardLocked; + break; + } + + const MfUltralightPage staticlock_page = tag_data->page[2]; + if(staticlock_page.data[2] != 0 || staticlock_page.data[3] != 0) { + FURI_LOG_D(TAG, "Static lock bits are set"); + instance->mfu_event.type = MfUltralightPollerEventTypeCardLocked; + break; + } + + if(mf_ultralight_support_feature(features, MfUltralightFeatureSupportDynamicLock)) { + uint8_t dynlock_num = mf_ultralight_get_config_page_num(tag_data->type) - 1; + const MfUltralightPage dynlock_page = tag_data->page[dynlock_num]; + if(dynlock_page.data[0] != 0 || dynlock_page.data[1] != 0) { + FURI_LOG_D(TAG, "Dynamic lock bits are set"); + instance->mfu_event.type = MfUltralightPollerEventTypeCardLocked; + break; + } + } + + check_passed = true; + } while(false); + + if(!check_passed) { + iso14443_3a_poller_halt(instance->iso14443_3a_poller); + command = instance->callback(instance->general_event, instance->context); + next_state = MfUltralightPollerStateWriteFail; + } + + instance->state = next_state; + return command; +} + +static NfcCommand mf_ultralight_poller_handler_write_pages(MfUltralightPoller* instance) { + NfcCommand command = NfcCommandContinue; + + do { + const MfUltralightData* write_data = instance->mfu_event.data->write_data; + uint8_t end_page = mf_ultralight_get_config_page_num(write_data->type) - 1; + if(instance->current_page == end_page) { + instance->state = MfUltralightPollerStateWriteSuccess; + break; + } + FURI_LOG_D(TAG, "Writing page %d", instance->current_page); + MfUltralightError error = mf_ultralight_poller_write_page( + instance, instance->current_page, &write_data->page[instance->current_page]); + if(error != MfUltralightErrorNone) { + instance->state = MfUltralightPollerStateWriteFail; + instance->error = error; + break; + } + instance->current_page++; + } while(false); + + return command; +} + +static NfcCommand mf_ultralight_poller_handler_write_fail(MfUltralightPoller* instance) { + FURI_LOG_D(TAG, "Write failed"); + iso14443_3a_poller_halt(instance->iso14443_3a_poller); + instance->mfu_event.data->error = instance->error; + instance->mfu_event.type = MfUltralightPollerEventTypeWriteFail; + NfcCommand command = instance->callback(instance->general_event, instance->context); + return command; +} + +static NfcCommand mf_ultralight_poller_handler_write_success(MfUltralightPoller* instance) { + FURI_LOG_D(TAG, "Write success"); + iso14443_3a_poller_halt(instance->iso14443_3a_poller); + instance->mfu_event.type = MfUltralightPollerEventTypeWriteSuccess; + NfcCommand command = instance->callback(instance->general_event, instance->context); return command; } static const MfUltralightPollerReadHandler mf_ultralight_poller_read_handler[MfUltralightPollerStateNum] = { [MfUltralightPollerStateIdle] = mf_ultralight_poller_handler_idle, + [MfUltralightPollerStateRequestMode] = mf_ultralight_poller_handler_request_mode, [MfUltralightPollerStateReadVersion] = mf_ultralight_poller_handler_read_version, [MfUltralightPollerStateDetectMfulC] = mf_ultralight_poller_handler_check_ultralight_c, [MfUltralightPollerStateDetectNtag203] = mf_ultralight_poller_handler_check_ntag_203, @@ -538,6 +664,11 @@ static const MfUltralightPollerReadHandler [MfUltralightPollerStateReadPages] = mf_ultralight_poller_handler_read_pages, [MfUltralightPollerStateReadFailed] = mf_ultralight_poller_handler_read_fail, [MfUltralightPollerStateReadSuccess] = mf_ultralight_poller_handler_read_success, + [MfUltralightPollerStateRequestWriteData] = + mf_ultralight_poller_handler_request_write_data, + [MfUltralightPollerStateWritePages] = mf_ultralight_poller_handler_write_pages, + [MfUltralightPollerStateWriteFail] = mf_ultralight_poller_handler_write_fail, + [MfUltralightPollerStateWriteSuccess] = mf_ultralight_poller_handler_write_success, }; diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h index 665d90cb700..2343be089bf 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h @@ -16,13 +16,27 @@ typedef struct MfUltralightPoller MfUltralightPoller; * @brief Enumeration of possible MfUltralight poller event types. */ typedef enum { + MfUltralightPollerEventTypeRequestMode, /**< Poller requests for operating mode. */ MfUltralightPollerEventTypeAuthRequest, /**< Poller requests to fill authentication context. */ MfUltralightPollerEventTypeAuthSuccess, /**< Authentication succeeded. */ MfUltralightPollerEventTypeAuthFailed, /**< Authentication failed. */ MfUltralightPollerEventTypeReadSuccess, /**< Poller read card successfully. */ MfUltralightPollerEventTypeReadFailed, /**< Poller failed to read card. */ + MfUltralightPollerEventTypeRequestWriteData, /**< Poller request card data for write operation. */ + MfUltralightPollerEventTypeCardMismatch, /**< Type of card for writing differs from presented one. */ + MfUltralightPollerEventTypeCardLocked, /**< Presented card is locked by password, AUTH0 or lock bytes. */ + MfUltralightPollerEventTypeWriteSuccess, /**< Poller wrote card successfully. */ + MfUltralightPollerEventTypeWriteFail, /**< Poller failed to write card. */ } MfUltralightPollerEventType; +/** + * @brief Enumeration of possible MfUltralight poller operating modes. + */ +typedef enum { + MfUltralightPollerModeRead, /**< Poller will only read card. It's a default mode. */ + MfUltralightPollerModeWrite, /**< Poller will write already saved card to another presented card. */ +} MfUltralightPollerMode; + /** * @brief MfUltralight poller authentication context. */ @@ -39,6 +53,8 @@ typedef struct { typedef union { MfUltralightPollerAuthContext auth_context; /**< Authentication context. */ MfUltralightError error; /**< Error code indicating reading fail reason. */ + const MfUltralightData* write_data; + MfUltralightPollerMode poller_mode; } MfUltralightPollerEventData; /** diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.h b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.h index c89402b421c..7c7354b1c6d 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.h +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.h @@ -49,6 +49,7 @@ typedef union { typedef enum { MfUltralightPollerStateIdle, + MfUltralightPollerStateRequestMode, MfUltralightPollerStateReadVersion, MfUltralightPollerStateDetectMfulC, MfUltralightPollerStateDetectNtag203, @@ -61,6 +62,10 @@ typedef enum { MfUltralightPollerStateTryDefaultPass, MfUltralightPollerStateReadFailed, MfUltralightPollerStateReadSuccess, + MfUltralightPollerStateRequestWriteData, + MfUltralightPollerStateWritePages, + MfUltralightPollerStateWriteFail, + MfUltralightPollerStateWriteSuccess, MfUltralightPollerStateNum, } MfUltralightPollerState; @@ -68,6 +73,7 @@ typedef enum { struct MfUltralightPoller { Iso14443_3aPoller* iso14443_3a_poller; MfUltralightPollerState state; + MfUltralightPollerMode mode; BitBuffer* tx_buffer; BitBuffer* rx_buffer; MfUltralightData* data; @@ -79,6 +85,7 @@ struct MfUltralightPoller { uint8_t counters_total; uint8_t tearing_flag_read; uint8_t tearing_flag_total; + uint16_t current_page; MfUltralightError error; NfcGenericEvent general_event; From 93732865acac9053cb0c580a2bf3e58962b3a678 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Sat, 2 Dec 2023 08:52:04 +0400 Subject: [PATCH 821/824] [FL-3132] HID app: Add new function key icons (#3236) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add new function key icons * Fix graphical glitches on the buttons Co-authored-by: あく --- applications/system/hid_app/assets/Alt_11x7.png | Bin 2417 -> 0 bytes applications/system/hid_app/assets/Alt_17x10.png | Bin 0 -> 550 bytes applications/system/hid_app/assets/Cmd_15x7.png | Bin 2426 -> 0 bytes applications/system/hid_app/assets/Cmd_17x10.png | Bin 0 -> 556 bytes applications/system/hid_app/assets/Ctrl_15x7.png | Bin 2433 -> 0 bytes .../system/hid_app/assets/Ctrl_17x10.png | Bin 0 -> 552 bytes applications/system/hid_app/assets/Del_12x7.png | Bin 2417 -> 0 bytes applications/system/hid_app/assets/Del_17x10.png | Bin 0 -> 551 bytes applications/system/hid_app/assets/Esc_14x7.png | Bin 2430 -> 0 bytes applications/system/hid_app/assets/Esc_17x10.png | Bin 0 -> 550 bytes applications/system/hid_app/assets/Tab_15x7.png | Bin 2419 -> 0 bytes applications/system/hid_app/assets/Tab_17x10.png | Bin 0 -> 549 bytes applications/system/hid_app/views/hid_keyboard.c | 14 +++++++------- 13 files changed, 7 insertions(+), 7 deletions(-) delete mode 100644 applications/system/hid_app/assets/Alt_11x7.png create mode 100644 applications/system/hid_app/assets/Alt_17x10.png delete mode 100644 applications/system/hid_app/assets/Cmd_15x7.png create mode 100644 applications/system/hid_app/assets/Cmd_17x10.png delete mode 100644 applications/system/hid_app/assets/Ctrl_15x7.png create mode 100644 applications/system/hid_app/assets/Ctrl_17x10.png delete mode 100644 applications/system/hid_app/assets/Del_12x7.png create mode 100644 applications/system/hid_app/assets/Del_17x10.png delete mode 100644 applications/system/hid_app/assets/Esc_14x7.png create mode 100644 applications/system/hid_app/assets/Esc_17x10.png delete mode 100644 applications/system/hid_app/assets/Tab_15x7.png create mode 100644 applications/system/hid_app/assets/Tab_17x10.png diff --git a/applications/system/hid_app/assets/Alt_11x7.png b/applications/system/hid_app/assets/Alt_11x7.png deleted file mode 100644 index 3e4bf320ee313c725e228356b6bf58989e804441..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2417 zcmbtVdsGu=79SqVBOtIXmWphdCPf~SC-O=|SV#a}0MIvsdA#66UH}hCLeQNGkr)bq zcTa>1R32bv_F?`7bNu@j92v z`l8yzhW7qnMoXqL9u8oW115)RouiKo*y%c3U!Xp?HC)*;s#de{#`Mk&>7CTA2M#+O z-lDTv({%Ojqbaa?pl7CLb}3=}LU4di?!BDea*vzjVF*~Jz*o@)l&bxPA+j_S9Sc1i1mNL1KbF+DpzfjGLEg>amgp`<$n!g7O z4LeaQ_RUh>s}RCYx$OlJQn!|$bLVPD--*{vpQI+h?4C}~Gx1;gI8Wwje*SIvS*|+b z+_%NP&ts`Imd$ovpY19!#akK43s0ZzjW~YHXt>bR%EUU~jvih;?c16eQEmU7MeJof z^WboQtFW)=rjF{W%KAZYk!{hVKXKI?j~2B3zA-y>Vk2Ys+x$bIIh+=~`}A3o^_cYJ z*cs``x!Xe%gH4XVECMy5dlp;A$GRB4rAw5@o#&gTq+ygRrWvWyvgA(Vcm<`KDWD4q zv*v8bMI($&@1F(>?b~)r@+DpDkIYy&_sDm#)8!F)_5R_i`a9T9_y-Brd#HHp-R|RP$5K=5!=8%Rhwf3P zi-s9`nd5!oHLl~^{uxe6{{e|s2R!i#lyJ{b!;(amr%(OSHT;>bZ99-&r>r+hFo<3l znQikfOl+GiwB3@a85rT-{}EH6s!s;@x5f<7&{#C~6I)Cbu%|n9YFpyu#nXQ$jl#tr z_p5xPdZ`=-Nsd?3^(M)Vps|ggWgCm=`}Vq*yKO=A8F(z%e>fX;+0%TeT(5Ip+U~YLLDMh=lygg!Ga*WQb=;t? z$L*}^jS)fC9c8xTPotG`y8)m#tzp;F{PTV3PxQJ6f!Y&GdP{anlN;hY?Zkk{hav^> zLLuNp$VPyk&Rc*UA?Xk&pu+@o37JD&tj}RUe_0Oza^ea2NRT)P43;7|f=Lkt$a1AI zKnE6s<-+h_y3=Gd7R!?XGF>I{Viwa81RaDTF)Y^_I|6|23EfpRlM{NYvY{54=Dj_T zm{@c;G!l;#{(&tB%U3@_g`@*-n__C99OXE^punoT8aw|K@;dqPft%e zgGFbtsDuR-OO@jyB~^}5UVyyB;X{}hg%voA$U!ZxC=N-+y~t#3pw(lAr%WLfu7;9h zD|rza(v>0wok?TRWitBfJTW{3S|j;dPb@T50ntMs3`s`C5MfSv9S~8t|4ra^PBLZr+gf(V6dJ|a$*L2_Kc=MoB<1eUP3G5y?J`2Kz_?wSZMbQI|zk|ts&BO4VTHGzoJ{Q=g_q+wXVfp^zX8k zXkadhi1cz8a7jU`yfR`w>=5vMLf_q#b0C@ofJXY2^MiFZqHE;VgEL)Go44AN4T`EP zs4c-k#>KST&6=a;Qx$1IIfMUXdufGPq$qdvcazVeHJe9^ckO70@?kEP)L%(Hre^b&aHEonE(xCqcjVkM=k!t!!_ zYC5O<+{b=NO*}I*EwN3!1%ohS1o1M#C?f>uWl-h-e){N^6(#a}zVj!^O0FWaMjoAI zlB6){5;Z5);}R31$7Q6++x`FBS0mBi=Txr=LdBzHy|zQ;CKNU->*K((3Wrek0Oy%v zes&)Q-bCLhYQ8|eBA@6YN^=FSz=Hm(eG{(jQj8)Dum3s32i>k78GhN>N&o-=07*qoM6N<$g1ch(GXMYp literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/Cmd_15x7.png b/applications/system/hid_app/assets/Cmd_15x7.png deleted file mode 100644 index a63a4be747a89745cbb32b5c9fb55d754f600dae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2426 zcmbtVdsGu=79So3iM+POA}pt*Ns))-i6kTqu|NWi-5{nsTv^1B3}k?0(##-1d9^^x z3RnxPRtt@fibYsq1q2#h1rb5^6xgQ15d`EVT@a)$LKR^r5rI9}?jJjIX6DYFdw=)d z-}l`+e+~%_vcfsy006KOyw4M1PaEtq-(ZH_yL0Sa0l?H63J3^E2nY&*6JgbdO0fhC z0u`VNl8A!*0l=rAAS_De6lTZ$s-b%8JKtyNN=pl^7rp;dlPK!SzB|4MR?GW5ixO-6 zjNxQQJvW-~>2@K1vAH+b`~mUF9#eDVQSV`n_qTs_C$#h1 z809B}i9cM;dX{mSe=niAH*4qT)zmb*0h9VC$30ZX?tf;h2^;A{b1TVE^-bF)umQ4| zAp%j_D>*9M47XVlcJPYRI9hgr*>!#EdEJRP+kq@~QX}Vf%M@>+mKI%%OG+0NTaMYh z1q=>(kg5*M5#Op3LLRy81rajFt)Fw}>&M=VHBOr%#X;=uPS&~DLw=r9dBZ>dF8n;V zIil?A zGMe%EP+zOCx9GN!=9ADjBX}DqHFDqC^Ek(G z+3E3fveWZF4o(gtoiG)>FGh*vDrQ)J~yofff5qK+n@ z2nw_2od`uEREM|E;tO5dK9uH5yVyIdy>A^OAkxPTUAhz_qDaHcyOKAa?ANjwC;f7; zrsL)g4b!%#qo*U`_R(&|UUr%yB2OC_uF>8>enQ`gbBrgUt#@@Ls*a_m=m*_Z(GQq= zEEmnwO)@5W!|S{!w*4a{(;Te?5}1K+!qAGaVgiJaGBQlnG1aqYG@ zDYF~~^8UVSaz>~6=~BD(P(D;rQ+x7Xhi{8I_1(EWmsvK06(*g}WbKU1xZ$hQKn1r{{Y0d8ZH^f* z_W3dEMq|WI$BuI6iP=cC!lfVRNxiyv>DluGX^%~G13>+$C6gr=^~Rf$JMGwj$Om@| zVueD$1CuuZCPuFVCYWS|oq!PyFvesKcF{kB08@h=02J60@RPzZAU;F}qc9?am3X>0 znTmI#Q|Vq*9~zS^C)4RHDxT&ojg`=8?s&YLP%MEIdSiD0Fg|6xf@X8dWJNaDBS$ge zkFkkGfkJnqyM=#XOJF%!tdB-k%awWqfTLz(q8voUc(puHfw0xS1cL<|ll5u}0dFut z6MPA~g(3I=SOwx4WGb0T;NtLjJVzysWs7)$%kJ2lFF}T)N;ZX(oSaNf_9DZoI0}u$ zVo|7c3Y|{EEJ#R-0u`%C3WWFqy^GhUgNro zyu$8ksfKoVu}k&zvIA707==}#Fr3K!KU&-n>A%3%WTD?P?42qI+Ztkh*KlbZ$~xV$ zJ%^%isWmP3qJIrqtbw^WEYiziz{SOuRSshFAymNg3*GEca5xVK*xH(wEG$tZI^C|J zL$f^%z2nt{Ga--b{~ecAmiV6MR}0tG@eKBfEez4@=ryZ5nJ0OpR!6h}7pY?r@<$Fc huhluV`!L-O&R7cBE}ON;j!OLi67Yk07yb8r@*g!;qu&4k diff --git a/applications/system/hid_app/assets/Cmd_17x10.png b/applications/system/hid_app/assets/Cmd_17x10.png new file mode 100644 index 0000000000000000000000000000000000000000..26ca3395c699cd19f2ae3c12d46954a45310eab7 GIT binary patch literal 556 zcmV+{0@MA8P)L%(Hre^b&aHEonE(xCqcjVkM=k!t!!_ zYC5O<+{b=NO*}I*EwN3!1%ohS1o1M#C?f>uWl-h-e){N^6(#a}zVj!^O0FWaMjoAI zlB6){5;Z5);}R31$7Q6++x`FBS0mBi=Txr=LdBzHy|zQ;CKNU->*K((3Wrek0Oy%v zes&)Q-bCLhYQ8n!+r=$Vtj3glk1^)H`0000501vCmP2ndL{yrdq5DkeKY1oo)gKX&HK%$+;;{_gMI z@4I*Y9vTw7oajgd0ARW3FTybVX^USLmge}qGsnRd0L*NlfPm1%fZzZ)2}V9rNu*#f zs00y68W!vi0KNqU5ixS72z&mwH5F^#`z1?PQc`H6_x?v^Qdmd!tr0y~q3H1}N~#p> zX&$?M5L)duIC~6o=i3YH>c8(V>NiB!yS}@vbUcrGq%!n~S(e*O`;*|q*Bq=yYrHCI zi>ebZwD$ZqTs)a^uNR%@UACv%GkRaIi>asMY3kkU0~J??wOTG#pWePay`8$|%e@|Z zSDXBCu;JpZ@kvr+Z|CFl(}sl6)1kp8xwmt=%6wbM10bMZdF5=(PBX#NCacc487uAk zNyVgr6_=}ksJFkfeQ>tzz*nr?*g#LWQ~7#rcdo@f%Hz#u7U+ZSFL~bI|KLt;4Q%!) z{U(HR{X*8$%yWUa6Sdu0>vvZ$((U^Q)sOdkAp7p@veQHi^@gT602oAi=ILOPyTLYiXPi&Q>vFi;2k@Va3+Nw(kJ_ z10K}!9n+L|%EgdJZfil5+~?}Axii(nZ^fIYPf!yeZf85^c>EUv&xyRjU*Cv4!PiEe z{BFPhvsmhR`$iYP&$kp?V@-_Y*+);dM;$z1IZ()PSmv1TLXWJP@^8wFs&YMM7kduN z+&$3KBw@M#*Jr1Wtz>L{Q@Dp@3#CPGJ$i!ZI3hnh za$J6R=0^WmUxWK^dfz{dOPJ~AqMSks|PHKBxQ*m-q%BPoJLeS+-k{>rv1 zA86QiU2R=$i7k6Om0P&!%BZ~Bph!*aP4qtYUV`H`D%NyMcMxeaG{oNPERVg%-fXS6 z$RK2nc1ND~9$ou$Br*L*I9eP0&@V*BKTeHEl2jZ$vdKI0iK%ldkY1y%wM;ONo*kNQ zURRjdJYDF#I&-jhh=1o7RD)8o%ys8ol4^UAucU8Z7tt^U4trX zRbSpu8^)$|$o;deHUs(4!OE&{{{6+}=Apqj;u)65Th@0k9daA}airF%qr|3azx*yx z{t3&s0T$_>IdrEU2T_f=@2u zM%Lx}sP&F*rOu;|qSZ>5UZ5-O!WP5RXFJj#5_Ek)^&tbn;G$l3MS8Op9}xNAMp3+2 z40zzOB|tD)0T6J>1U~^2CSZ!oJp5wqf&jBQJpd^2Cm@i)aUdyF4r4GXhgBq&H;qAZ zV=-7>3|}Ujrl7G{90rN$EsK}3nC>K!n^+=+ltyEB05Cmdx`1YTh_E1A7?ESx@Td61 zqC?>uv5n%ta;30>CNV~%sTC@t0l-spaZv$c5|Ubxq(r%DKk}Rf7nhA{I+-+Qf+hNq zH;O|^0WboRd}s_BgUlzANF*L2i|2+3gXZ1wH$So*!&F>4JtZZDmf}T&kpw!E!{N{w zEINxt#Vx34suGi^sY;ac9OMO#5JaU2q{1LrNiyO};^1V=k4!cOS~%u;DpX?eLMSCV zpBJ7XT`f`3nKTAnp`gFY6UD;7MUt=dM8i{6AUzC3;bcS#;^vfB0pW%F-vnOeghBDI z32yu{$9thBc*;CHh*-=O!qQ{~sKi7k$aw8WLmI@R>aNOS*Y`9^KedOIqxC|ITUg z2Idp-NH2x~pLi|e*bRI>M2m!i@V8gx>=vxJM6x9084PH0_rF5MbS?FO2pDzR5MjZ* zwk&l7d}deg9ccNd!iI}=F85dO%9S@&l&}v#b~bM?zl!cVm9q)ib$xhxQ?%EK84a&h c-wD*%=rG#yo8xYw#yKPk3=y98-}>2q0D4cRIRF3v diff --git a/applications/system/hid_app/assets/Ctrl_17x10.png b/applications/system/hid_app/assets/Ctrl_17x10.png new file mode 100644 index 0000000000000000000000000000000000000000..0eda72160009907c74a5f44e2dd90cd923f1fdfb GIT binary patch literal 552 zcmV+@0@wYCP)L%(Hre^b&aHEonE(xCqcjVkM=k!t!!_ zYC5O<+{b=NO*}I*EwN3!1%ohS1o1M#C?f>uWl-h-e){N^6(#a}zVj!^O0FWaMjoAI zlB6){5;Z5);}R31$7Q6++x`FBS0mBi=Txr=LdBzHy|zQ;CKNU->*K((3Wrek0Oy%v zes&)Q-bCLhYQ8xs5R007ntcL;*T1E?xm8=0AOSR^Z7w>e1AR?kD%|VBvQy9 zQbH&!4fgi|09IaJXq0?Ys5S5J)n!{Z|CG^LRFrQq@BU?Zd~iqRtAM6VKY)s_wgif`Ns|I)}IS72nUHoGK4GWt8DK(?08e@?SP)W7V!@ zH3e01S6h33A1RznyVs|l=v#NF$|dqhpS_`r?FGu+p9jlsjOcV6tS+^EeQG;p>*t4^ z4{tH}^HBY@TlXiy#=fq{m!}tE$1Vi<8)VMmimkOm=Oe#7-kQ3s5?pEQ|uMbDV& zw#65MgReGJ0O4aI^2t8U!=li*R2+yYS?Uw z3`Et`W}(C>o=zJ2LG7kd?A&?Jjvuz3?K~E1)t{kBsN=RYP6*~I7~(==LRxU4`H1CP zz`&q0rF8!^`K?kB?3~@27cTd>@muyx)yV5HhN%;jSeVn*&OQ_KnYYVC&d_gv3Omcw zg`fN8sLxX|<+637z1PQk3eB-5dcxeJ*~IYU$4m$F**5EJbM0wi6;nP<>ERU)-&%<) zvGjw3y-lK?f))eqHLdO4paQ#s`+wuc81Jul`h6a_9I|h|X>7EE?o+fte$alteo`Jvt~F0fmKP_qo5iTeoeUvO zn4dATiBvF5-}v@vFyEp1Jz1`-gR|4Z{l*cJTK2HIqchon9IW-ssm&Qp@@|}um3=i( z-gbSb*3+uHt-CF*<#3mB4<}U_o>Su&rmeZD{uSFCYrBtvHQnkwjLxr-)PBc-GtN zRT&%mbN>16_*5tQ>!ntU!Cd%wdBxZN{;Z*Scqp29k?H)p`Q0B*IF4Nzt=ZI3WKnTc zeitbHfXS*yg!4~rdXmpTYOOJA&SBg1?n<`hKnbD!iM-Y0Kc1EIfI-KlLG@vmdBBu|Qa`*Q~oPz1fNnh+HT_ z7$Xt^&bVv}5DZ=g2)JZ`pMU`aFw~C_{9=9r14fH_08rvjz*~kyLtu~`!4S0^QGrZ% zDjjrW(wVMw7Q>UOpfZ_kI>>OB#YmY9ClGWLNu;n+Z|npBh9?Y{(acW}mSqz?vc>mq zuiz7l28Tpo5u$fEQba+O=%Z0J3YFdf;A%LysDLmDs8Pf#)f|l%Y0-j%%X&491TLCj zabBbdQ4q*SPzdy((y4S3j|hSw7nQ|uf(3p{?)aM*NseJE4vm(SltfK(r6Oo7jlpKK zX>=xy$)w;G6m_x^lV~VPHTfCHa~uJrmZGoAzP zIOZk6^~q?|AvW>%nwBri_55z zplV28QZLf;ny$`KpXTDCaNxCkJiX~`7Tufmaz1NZSNN$a$P}QuI@^z zi|z1im+H&Q;iHfQL(mWeiRb+vExs7(zra?p(C-;eAPVDKL!$2*9)n9;qg%4)()2C0 zs>Po5@1hoOU>*^V^n4ibh&vkWJn{LkODOOTd1GVN!44t-niAY+7sQnTK`9ZNcc54D zdVd^D3=VgLXD diff --git a/applications/system/hid_app/assets/Del_17x10.png b/applications/system/hid_app/assets/Del_17x10.png new file mode 100644 index 0000000000000000000000000000000000000000..13d736983079f920184d157d83054efe809a6981 GIT binary patch literal 551 zcmV+?0@(eDP)L%(Hre^b&aHEonE(xCqcjVkM=k!t!!_ zYC5O<+{b=NO*}I*EwN3!1%ohS1o1M#C?f>uWl-h-e){N^6(#a}zVj!^O0FWaMjoAI zlB6){5;Z5);}R31$7Q6++x`FBS0mBi=Txr=LdBzHy|zQ;CKNU->*K((3Wrek0Oy%v zes&)Q-bCLhYQ8Askdnas~hZ002ovPDHLkV1h}~`Pcve literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/Esc_14x7.png b/applications/system/hid_app/assets/Esc_14x7.png deleted file mode 100644 index 3be4691d8a3d050bf14f5bd29099705d984eead7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2430 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 literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/views/hid_keyboard.c b/applications/system/hid_app/views/hid_keyboard.c index 17ff754f52c..9060c1d6a64 100644 --- a/applications/system/hid_app/views/hid_keyboard.c +++ b/applications/system/hid_app/views/hid_keyboard.c @@ -49,7 +49,7 @@ typedef struct { #define ROW_COUNT 7 #define COLUMN_COUNT 12 -// 0 width items are not drawn, but there value is used +// 0 width items are not drawn, but their value is used const HidKeyboardKey hid_keyboard_keyset[ROW_COUNT][COLUMN_COUNT] = { { {.width = 1, .icon = &I_ButtonF1_5x8, .value = HID_KEYBOARD_F1}, @@ -140,17 +140,17 @@ const HidKeyboardKey hid_keyboard_keyset[ROW_COUNT][COLUMN_COUNT] = { {.width = 1, .icon = &I_ButtonRight_4x7, .value = HID_KEYBOARD_RIGHT_ARROW}, }, { - {.width = 2, .icon = &I_Ctrl_15x7, .value = HID_KEYBOARD_L_CTRL}, + {.width = 2, .icon = &I_Ctrl_17x10, .value = HID_KEYBOARD_L_CTRL}, {.width = 0, .value = HID_KEYBOARD_L_CTRL}, - {.width = 2, .icon = &I_Alt_11x7, .value = HID_KEYBOARD_L_ALT}, + {.width = 2, .icon = &I_Alt_17x10, .value = HID_KEYBOARD_L_ALT}, {.width = 0, .value = HID_KEYBOARD_L_ALT}, - {.width = 2, .icon = &I_Cmd_15x7, .value = HID_KEYBOARD_L_GUI}, + {.width = 2, .icon = &I_Cmd_17x10, .value = HID_KEYBOARD_L_GUI}, {.width = 0, .value = HID_KEYBOARD_L_GUI}, - {.width = 2, .icon = &I_Tab_15x7, .value = HID_KEYBOARD_TAB}, + {.width = 2, .icon = &I_Tab_17x10, .value = HID_KEYBOARD_TAB}, {.width = 0, .value = HID_KEYBOARD_TAB}, - {.width = 2, .icon = &I_Esc_14x7, .value = HID_KEYBOARD_ESCAPE}, + {.width = 2, .icon = &I_Esc_17x10, .value = HID_KEYBOARD_ESCAPE}, {.width = 0, .value = HID_KEYBOARD_ESCAPE}, - {.width = 2, .icon = &I_Del_12x7, .value = HID_KEYBOARD_DELETE_FORWARD}, + {.width = 2, .icon = &I_Del_17x10, .value = HID_KEYBOARD_DELETE_FORWARD}, {.width = 0, .value = HID_KEYBOARD_DELETE_FORWARD}, }, }; From 04cead1fc56d4093bffee8d3a4a7b47fba93061f Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Sat, 2 Dec 2023 09:03:10 +0400 Subject: [PATCH 822/824] [FL-3620] Add the "remove pairing" button to BLE hid (#3237) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- applications/system/hid_app/hid.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/applications/system/hid_app/hid.c b/applications/system/hid_app/hid.c index a42fc609178..88a68f09d0f 100644 --- a/applications/system/hid_app/hid.c +++ b/applications/system/hid_app/hid.c @@ -14,8 +14,22 @@ enum HidDebugSubmenuIndex { HidSubmenuIndexMouse, HidSubmenuIndexMouseClicker, HidSubmenuIndexMouseJiggler, + HidSubmenuIndexRemovePairing, }; +static void bt_hid_remove_pairing(Bt* bt) { + bt_disconnect(bt); + + // Wait 2nd core to update nvm storage + furi_delay_ms(200); + + furi_hal_bt_stop_advertising(); + + bt_forget_bonded_devices(bt); + + furi_hal_bt_start_advertising(); +} + static void hid_submenu_callback(void* context, uint32_t index) { furi_assert(context); Hid* app = context; @@ -45,6 +59,8 @@ static void hid_submenu_callback(void* context, uint32_t index) { } else if(index == HidSubmenuIndexMouseJiggler) { app->view_id = HidViewMouseJiggler; view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMouseJiggler); + } else if(index == HidSubmenuIndexRemovePairing) { + bt_hid_remove_pairing(app->bt); } } @@ -143,6 +159,14 @@ Hid* hid_alloc(HidTransport transport) { HidSubmenuIndexMouseJiggler, hid_submenu_callback, app); + if(transport == HidTransportBle) { + submenu_add_item( + app->device_type_submenu, + "Remove Pairing", + HidSubmenuIndexRemovePairing, + hid_submenu_callback, + app); + } view_set_previous_callback(submenu_get_view(app->device_type_submenu), hid_exit); view_dispatcher_add_view( app->view_dispatcher, HidViewSubmenu, submenu_get_view(app->device_type_submenu)); From c6a14e1a6779ded87b848b06b2cbc51a147f96b3 Mon Sep 17 00:00:00 2001 From: pborsutzki Date: Sat, 2 Dec 2023 08:27:58 +0100 Subject: [PATCH 823/824] Fixed a zero allocation error when reading an iso15693 nfc tag with no additional blocks. (#3229) Co-authored-by: gornekich --- .../iso15693_3/iso15693_3_poller_i.c | 44 ++++++++++--------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.c b/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.c index 917f7dbb8e0..ca6f5435e3e 100644 --- a/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.c +++ b/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.c @@ -108,28 +108,30 @@ Iso15693_3Error iso15693_3_poller_activate(Iso15693_3Poller* instance, Iso15693_ break; } - // Read blocks: Optional command - simple_array_init(data->block_data, system_info->block_count * system_info->block_size); - ret = iso15693_3_poller_read_blocks( - instance, - simple_array_get_data(data->block_data), - system_info->block_count, - system_info->block_size); - if(ret != Iso15693_3ErrorNone) { - ret = iso15693_3_poller_filter_error(ret); - break; - } - - // Get block security status: Optional command - simple_array_init(data->block_security, system_info->block_count); - - ret = iso15693_3_poller_get_blocks_security( - instance, simple_array_get_data(data->block_security), system_info->block_count); - if(ret != Iso15693_3ErrorNone) { - ret = iso15693_3_poller_filter_error(ret); - break; + if(system_info->block_count > 0) { + // Read blocks: Optional command + simple_array_init( + data->block_data, system_info->block_count * system_info->block_size); + ret = iso15693_3_poller_read_blocks( + instance, + simple_array_get_data(data->block_data), + system_info->block_count, + system_info->block_size); + if(ret != Iso15693_3ErrorNone) { + ret = iso15693_3_poller_filter_error(ret); + break; + } + + // Get block security status: Optional command + simple_array_init(data->block_security, system_info->block_count); + + ret = iso15693_3_poller_get_blocks_security( + instance, simple_array_get_data(data->block_security), system_info->block_count); + if(ret != Iso15693_3ErrorNone) { + ret = iso15693_3_poller_filter_error(ret); + break; + } } - } while(false); return ret; From eb6fe0a4dbe2e6d3776483e6a191de2307b0060e Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Sat, 2 Dec 2023 11:34:02 +0400 Subject: [PATCH 824/824] SubGhz: fix count bit for detect gate_tx protocol (#3253) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- lib/subghz/protocols/gate_tx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/subghz/protocols/gate_tx.c b/lib/subghz/protocols/gate_tx.c index 51a424fed94..2ebd6bb03b9 100644 --- a/lib/subghz/protocols/gate_tx.c +++ b/lib/subghz/protocols/gate_tx.c @@ -227,7 +227,7 @@ void subghz_protocol_decoder_gate_tx_feed(void* context, bool level, uint32_t du if(duration >= ((uint32_t)subghz_protocol_gate_tx_const.te_short * 10 + subghz_protocol_gate_tx_const.te_delta)) { instance->decoder.parser_step = GateTXDecoderStepFoundStartBit; - if(instance->decoder.decode_count_bit >= + if(instance->decoder.decode_count_bit == subghz_protocol_gate_tx_const.min_count_bit_for_found) { instance->generic.data = instance->decoder.decode_data; instance->generic.data_count_bit = instance->decoder.decode_count_bit;

    P0X%tZA`1z%!;olWF5xr*3uMB=637O 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 z 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∋0Y1%8s`BZ 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

    W$9MSFwd|$qU|8 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

    =uj2T^*ZzJ}Yp$<|6yhUSl$mpA^l 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�A{WwDb)MVE`&z}X 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%0Dyzp3#7`kA@dY%y;RTiVOrstx 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 diff --git a/lib/ST25RFAL002/include/rfal_analogConfig.h b/lib/ST25RFAL002/include/rfal_analogConfig.h deleted file mode 100644 index de9db7be9c6..00000000000 --- a/lib/ST25RFAL002/include/rfal_analogConfig.h +++ /dev/null @@ -1,435 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

    © COPYRIGHT 2020 STMicroelectronics

    - * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R391x firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file rfal_AnalogConfig.h - * - * \author bkam - * - * \brief RF Chip Analog Configuration Settings - * - * - * \addtogroup RFAL - * @{ - * - * \addtogroup RFAL-HAL - * \brief RFAL Hardware Abstraction Layer - * @{ - * - * \addtogroup AnalogConfig - * \brief RFAL Analog Config Module - * @{ - * - */ - -#ifndef RFAL_ANALOG_CONFIG_H -#define RFAL_ANALOG_CONFIG_H - -/* - ****************************************************************************** - * INCLUDES - ****************************************************************************** - */ -#include "platform.h" -#include "st_errno.h" -#include "rfal_rf.h" - -/* - ****************************************************************************** - * DEFINES - ****************************************************************************** - */ - -#define RFAL_ANALOG_CONFIG_LUT_SIZE \ - (87U) /*!< Maximum number of Configuration IDs in the Loop Up Table */ -#define RFAL_ANALOG_CONFIG_LUT_NOT_FOUND \ - (0xFFU) /*!< Index value indicating no Configuration IDs found */ - -#define RFAL_ANALOG_CONFIG_TBL_SIZE \ - (1024U) /*!< Maximum number of Register-Mask-Value in the Setting List */ - -#define RFAL_ANALOG_CONFIG_POLL_LISTEN_MODE_MASK \ - (0x8000U) /*!< Mask bit of Poll Mode in Analog Configuration ID */ -#define RFAL_ANALOG_CONFIG_TECH_MASK \ - (0x7F00U) /*!< Mask bits for Technology in Analog Configuration ID */ -#define RFAL_ANALOG_CONFIG_BITRATE_MASK \ - (0x00F0U) /*!< Mask bits for Bit rate in Analog Configuration ID */ -#define RFAL_ANALOG_CONFIG_DIRECTION_MASK \ - (0x000FU) /*!< Mask bits for Direction in Analog Configuration ID */ -#define RFAL_ANALOG_CONFIG_CHIP_SPECIFIC_MASK \ - (0x00FFU) /*!< Mask bits for Chip Specific Technology */ - -#define RFAL_ANALOG_CONFIG_POLL_LISTEN_MODE_SHIFT \ - (15U) /*!< Shift value of Poll Mode in Analog Configuration ID */ -#define RFAL_ANALOG_CONFIG_TECH_SHIFT \ - (8U) /*!< Shift value for Technology in Analog Configuration ID */ -#define RFAL_ANALOG_CONFIG_BITRATE_SHIFT \ - (4U) /*!< Shift value for Technology in Analog Configuration ID */ -#define RFAL_ANALOG_CONFIG_DIRECTION_SHIFT \ - (0U) /*!< Shift value for Direction in Analog Configuration ID */ - -#define RFAL_ANALOG_CONFIG_POLL \ - (0x0000U) /*!< Poll Mode bit setting in Analog Configuration ID */ -#define RFAL_ANALOG_CONFIG_LISTEN \ - (0x8000U) /*!< Listen Mode bit setting in Analog Configuration ID */ - -#define RFAL_ANALOG_CONFIG_TECH_CHIP \ - (0x0000U) /*!< Chip-Specific bit setting in Analog Configuration ID */ -#define RFAL_ANALOG_CONFIG_TECH_NFCA \ - (0x0100U) /*!< NFC-A Technology bits setting in Analog Configuration ID */ -#define RFAL_ANALOG_CONFIG_TECH_NFCB \ - (0x0200U) /*!< NFC-B Technology bits setting in Analog Configuration ID */ -#define RFAL_ANALOG_CONFIG_TECH_NFCF \ - (0x0400U) /*!< NFC-F Technology bits setting in Analog Configuration ID */ -#define RFAL_ANALOG_CONFIG_TECH_AP2P \ - (0x0800U) /*!< AP2P Technology bits setting in Analog Configuration ID */ -#define RFAL_ANALOG_CONFIG_TECH_NFCV \ - (0x1000U) /*!< NFC-V Technology bits setting in Analog Configuration ID */ -#define RFAL_ANALOG_CONFIG_TECH_RFU (0x2000U) /*!< RFU for Technology bits */ - -#define RFAL_ANALOG_CONFIG_BITRATE_COMMON \ - (0x0000U) /*!< Common settings for all bit rates in Analog Configuration ID */ -#define RFAL_ANALOG_CONFIG_BITRATE_106 \ - (0x0010U) /*!< 106kbits/s settings in Analog Configuration ID */ -#define RFAL_ANALOG_CONFIG_BITRATE_212 \ - (0x0020U) /*!< 212kbits/s settings in Analog Configuration ID */ -#define RFAL_ANALOG_CONFIG_BITRATE_424 \ - (0x0030U) /*!< 424kbits/s settings in Analog Configuration ID */ -#define RFAL_ANALOG_CONFIG_BITRATE_848 \ - (0x0040U) /*!< 848kbits/s settings in Analog Configuration ID */ -#define RFAL_ANALOG_CONFIG_BITRATE_1695 \ - (0x0050U) /*!< 1695kbits/s settings in Analog Configuration ID */ -#define RFAL_ANALOG_CONFIG_BITRATE_3390 \ - (0x0060U) /*!< 3390kbits/s settings in Analog Configuration ID */ -#define RFAL_ANALOG_CONFIG_BITRATE_6780 \ - (0x0070U) /*!< 6780kbits/s settings in Analog Configuration ID */ -#define RFAL_ANALOG_CONFIG_BITRATE_1OF4 \ - (0x00C0U) /*!< 1 out of 4 for NFC-V setting in Analog Configuration ID */ -#define RFAL_ANALOG_CONFIG_BITRATE_1OF256 \ - (0x00D0U) /*!< 1 out of 256 for NFC-V setting in Analog Configuration ID */ - -#define RFAL_ANALOG_CONFIG_NO_DIRECTION \ - (0x0000U) /*!< No direction setting in Analog Conf ID (Chip Specific only) */ -#define RFAL_ANALOG_CONFIG_TX \ - (0x0001U) /*!< Transmission bit setting in Analog Configuration ID */ -#define RFAL_ANALOG_CONFIG_RX \ - (0x0002U) /*!< Reception bit setting in Analog Configuration ID */ -#define RFAL_ANALOG_CONFIG_ANTICOL \ - (0x0003U) /*!< Anticollision setting in Analog Configuration ID */ -#define RFAL_ANALOG_CONFIG_DPO \ - (0x0004U) /*!< DPO setting in Analog Configuration ID */ - -#define RFAL_ANALOG_CONFIG_CHIP_INIT \ - (0x0000U) /*!< Chip-Specific event: Startup;Reset;Initialize */ -#define RFAL_ANALOG_CONFIG_CHIP_DEINIT \ - (0x0001U) /*!< Chip-Specific event: Deinitialize */ -#define RFAL_ANALOG_CONFIG_CHIP_FIELD_ON \ - (0x0002U) /*!< Chip-Specific event: Field On */ -#define RFAL_ANALOG_CONFIG_CHIP_FIELD_OFF \ - (0x0003U) /*!< Chip-Specific event: Field Off */ -#define RFAL_ANALOG_CONFIG_CHIP_WAKEUP_ON \ - (0x0004U) /*!< Chip-Specific event: Wake-up On */ -#define RFAL_ANALOG_CONFIG_CHIP_WAKEUP_OFF \ - (0x0005U) /*!< Chip-Specific event: Wake-up Off */ -#define RFAL_ANALOG_CONFIG_CHIP_LISTEN_ON \ - (0x0006U) /*!< Chip-Specific event: Listen On */ -#define RFAL_ANALOG_CONFIG_CHIP_LISTEN_OFF \ - (0x0007U) /*!< Chip-Specific event: Listen Off */ -#define RFAL_ANALOG_CONFIG_CHIP_POLL_COMMON \ - (0x0008U) /*!< Chip-Specific event: Poll common */ -#define RFAL_ANALOG_CONFIG_CHIP_LISTEN_COMMON \ - (0x0009U) /*!< Chip-Specific event: Listen common */ -#define RFAL_ANALOG_CONFIG_CHIP_LOWPOWER_ON \ - (0x000AU) /*!< Chip-Specific event: Low Power On */ -#define RFAL_ANALOG_CONFIG_CHIP_LOWPOWER_OFF \ - (0x000BU) /*!< Chip-Specific event: Low Power Off */ - -#define RFAL_ANALOG_CONFIG_UPDATE_LAST \ - (0x00U) /*!< Value indicating Last configuration set during update */ -#define RFAL_ANALOG_CONFIG_UPDATE_MORE \ - (0x01U) /*!< Value indicating More configuration set coming during update */ - -/* - ****************************************************************************** - * GLOBAL MACROS - ****************************************************************************** - */ - -#define RFAL_ANALOG_CONFIG_ID_GET_POLL_LISTEN(id) \ - (RFAL_ANALOG_CONFIG_POLL_LISTEN_MODE_MASK & (id)) /*!< Check if id indicates Listen mode */ - -#define RFAL_ANALOG_CONFIG_ID_GET_TECH(id) \ - (RFAL_ANALOG_CONFIG_TECH_MASK & (id)) /*!< Get the technology of Configuration ID */ -#define RFAL_ANALOG_CONFIG_ID_IS_CHIP(id) \ - (RFAL_ANALOG_CONFIG_TECH_MASK & (id)) /*!< Check if ID indicates Chip-specific */ -#define RFAL_ANALOG_CONFIG_ID_IS_NFCA(id) \ - (RFAL_ANALOG_CONFIG_TECH_NFCA & (id)) /*!< Check if ID indicates NFC-A */ -#define RFAL_ANALOG_CONFIG_ID_IS_NFCB(id) \ - (RFAL_ANALOG_CONFIG_TECH_NFCB & (id)) /*!< Check if ID indicates NFC-B */ -#define RFAL_ANALOG_CONFIG_ID_IS_NFCF(id) \ - (RFAL_ANALOG_CONFIG_TECH_NFCF & (id)) /*!< Check if ID indicates NFC-F */ -#define RFAL_ANALOG_CONFIG_ID_IS_AP2P(id) \ - (RFAL_ANALOG_CONFIG_TECH_AP2P & (id)) /*!< Check if ID indicates AP2P */ -#define RFAL_ANALOG_CONFIG_ID_IS_NFCV(id) \ - (RFAL_ANALOG_CONFIG_TECH_NFCV & (id)) /*!< Check if ID indicates NFC-V */ - -#define RFAL_ANALOG_CONFIG_ID_GET_BITRATE(id) \ - (RFAL_ANALOG_CONFIG_BITRATE_MASK & (id)) /*!< Get Bitrate of Configuration ID */ -#define RFAL_ANALOG_CONFIG_ID_IS_COMMON(id) \ - (RFAL_ANALOG_CONFIG_BITRATE_MASK & (id)) /*!< Check if ID indicates common bitrate */ -#define RFAL_ANALOG_CONFIG_ID_IS_106(id) \ - (RFAL_ANALOG_CONFIG_BITRATE_106 & (id)) /*!< Check if ID indicates 106kbits/s */ -#define RFAL_ANALOG_CONFIG_ID_IS_212(id) \ - (RFAL_ANALOG_CONFIG_BITRATE_212 & (id)) /*!< Check if ID indicates 212kbits/s */ -#define RFAL_ANALOG_CONFIG_ID_IS_424(id) \ - (RFAL_ANALOG_CONFIG_BITRATE_424 & (id)) /*!< Check if ID indicates 424kbits/s */ -#define RFAL_ANALOG_CONFIG_ID_IS_848(id) \ - (RFAL_ANALOG_CONFIG_BITRATE_848 & (id)) /*!< Check if ID indicates 848kbits/s */ -#define RFAL_ANALOG_CONFIG_ID_IS_1695(id) \ - (RFAL_ANALOG_CONFIG_BITRATE_1695 & (id)) /*!< Check if ID indicates 1695kbits/s */ -#define RFAL_ANALOG_CONFIG_ID_IS_3390(id) \ - (RFAL_ANALOG_CONFIG_BITRATE_3390 & (id)) /*!< Check if ID indicates 3390kbits/s */ -#define RFAL_ANALOG_CONFIG_ID_IS_6780(id) \ - (RFAL_ANALOG_CONFIG_BITRATE_6780 & (id)) /*!< Check if ID indicates 6780kbits/s */ -#define RFAL_ANALOG_CONFIG_ID_IS_1OF4(id) \ - (RFAL_ANALOG_CONFIG_BITRATE_1OF4 & (id)) /*!< Check if ID indicates 1 out of 4 bitrate */ -#define RFAL_ANALOG_CONFIG_ID_IS_1OF256(id) \ - (RFAL_ANALOG_CONFIG_BITRATE_1OF256 & (id)) /*!< Check if ID indicates 1 out of 256 bitrate */ - -#define RFAL_ANALOG_CONFIG_ID_GET_DIRECTION(id) \ - (RFAL_ANALOG_CONFIG_DIRECTION_MASK & (id)) /*!< Get Direction of Configuration ID */ -#define RFAL_ANALOG_CONFIG_ID_IS_TX(id) \ - (RFAL_ANALOG_CONFIG_TX & (id)) /*!< Check if id indicates TX */ -#define RFAL_ANALOG_CONFIG_ID_IS_RX(id) \ - (RFAL_ANALOG_CONFIG_RX & (id)) /*!< Check if id indicates RX */ - -#define RFAL_ANALOG_CONFIG_CONFIG_NUM(x) \ - (sizeof(x) / sizeof((x)[0])) /*!< Get Analog Config number */ - -/*! Set Analog Config ID value by: Mode, Technology, Bitrate and Direction */ -#define RFAL_ANALOG_CONFIG_ID_SET(mode, tech, br, direction) \ - (RFAL_ANALOG_CONFIG_ID_GET_POLL_LISTEN(mode) | RFAL_ANALOG_CONFIG_ID_GET_TECH(tech) | \ - RFAL_ANALOG_CONFIG_ID_GET_BITRATE(br) | RFAL_ANALOG_CONFIG_ID_GET_DIRECTION(direction)) - -/* - ****************************************************************************** - * GLOBAL DATA TYPES - ****************************************************************************** - */ - -typedef uint8_t - rfalAnalogConfigMode; /*!< Polling or Listening Mode of Configuration */ -typedef uint8_t - rfalAnalogConfigTech; /*!< Technology of Configuration */ -typedef uint8_t - rfalAnalogConfigBitrate; /*!< Bitrate of Configuration */ -typedef uint8_t - rfalAnalogConfigDirection; /*!< Transmit/Receive direction of Configuration */ - -typedef uint8_t - rfalAnalogConfigRegAddr[2]; /*!< Register Address to ST Chip */ -typedef uint8_t - rfalAnalogConfigRegMask; /*!< Register Mask Value */ -typedef uint8_t - rfalAnalogConfigRegVal; /*!< Register Value */ - -typedef uint16_t - rfalAnalogConfigId; /*!< Analog Configuration ID */ -typedef uint16_t - rfalAnalogConfigOffset; /*!< Analog Configuration offset address in the table */ -typedef uint8_t - rfalAnalogConfigNum; /*!< Number of Analog settings for the respective Configuration ID */ - -/*! Struct that contain the Register-Mask-Value set. Make sure that the whole structure size is even and unaligned! */ -typedef struct { - rfalAnalogConfigRegAddr addr; /*!< Register Address */ - rfalAnalogConfigRegMask mask; /*!< Register Mask Value */ - rfalAnalogConfigRegVal val; /*!< Register Value */ -} rfalAnalogConfigRegAddrMaskVal; - -/*! Struct that represents the Analog Configs */ -typedef struct { - uint8_t id[sizeof(rfalAnalogConfigId)]; /*!< Configuration ID */ - rfalAnalogConfigNum num; /*!< Number of Config Sets to follow */ - rfalAnalogConfigRegAddrMaskVal regSet[]; - /*!< Register-Mask-Value sets */ /* PRQA S 1060 # MISRA 18.7 - Flexible Array Members are the only meaningful way of denoting a variable length input buffer which follows a fixed header structure. */ -} rfalAnalogConfig; - -/* -****************************************************************************** -* GLOBAL FUNCTION PROTOTYPES -****************************************************************************** -*/ - -/*! - ***************************************************************************** - * \brief Initialize the Analog Configuration - * - * Reset the Analog Configuration LUT pointer to reference to default settings. - * - ***************************************************************************** - */ -void rfalAnalogConfigInitialize(void); - -/*! - ***************************************************************************** - * \brief Indicate if the current Analog Configuration Table is complete and ready to be used. - * - * \return true if current Analog Configuration Table is complete and ready to be used. - * \return false if current Analog Configuration Table is incomplete - * - ***************************************************************************** - */ -bool rfalAnalogConfigIsReady(void); - -/*! - ***************************************************************************** - * \brief Write the whole Analog Configuration table in raw format - * - * Writes the Analog Configuration and Look Up Table with the given raw table - * - * NOTE: Function does not check the validity of the given Table contents - * - * \param[in] configTbl: location of config Table to be loaded - * \param[in] configTblSize: size of the config Table to be loaded - * - * \return ERR_NONE : if setting is updated - * \return ERR_PARAM : if configTbl is invalid - * \return ERR_NOMEM : if the given Table is bigger exceeds the max size - * \return ERR_REQUEST : if the update Configuration Id is disabled - * - ***************************************************************************** - */ -ReturnCode rfalAnalogConfigListWriteRaw(const uint8_t* configTbl, uint16_t configTblSize); - -/*! - ***************************************************************************** - * \brief Write the Analog Configuration table with new analog settings. - * - * Writes the Analog Configuration and Look Up Table with the new list of register-mask-value - * and Configuration ID respectively. - * - * NOTE: Function does not check for the validity of the Register Address. - * - * \param[in] more: 0x00 indicates it is last Configuration ID settings; - * 0x01 indicates more Configuration ID setting(s) are coming. - * \param[in] *config: reference to the configuration list of current Configuration ID. - * - * \return ERR_PARAM : if Configuration ID or parameter is invalid - * \return ERR_NOMEM : if LUT is full - * \return ERR_REQUEST : if the update Configuration Id is disabled - * \return ERR_NONE : if setting is updated - * - ***************************************************************************** - */ -ReturnCode rfalAnalogConfigListWrite(uint8_t more, const rfalAnalogConfig* config); - -/*! - ***************************************************************************** - * \brief Read the whole Analog Configuration table in raw format - * - * Reads the whole Analog Configuration Table in raw format - * - * \param[out] tblBuf: location to the buffer to place the Config Table - * \param[in] tblBufLen: length of the buffer to place the Config Table - * \param[out] configTblSize: Config Table size - * - * \return ERR_PARAM : if configTbl or configTblSize is invalid - * \return ERR_NOMEM : if configTblSize is not enough for the whole table - * \return ERR_NONE : if read is successful - * - ***************************************************************************** - */ -ReturnCode - rfalAnalogConfigListReadRaw(uint8_t* tblBuf, uint16_t tblBufLen, uint16_t* configTblSize); - -/*! - ***************************************************************************** - * \brief Read the Analog Configuration table. - * - * Read the Analog Configuration Table - * - * \param[in] configOffset: offset to the next Configuration ID in the List Table to be read. - * \param[out] more: 0x00 indicates it is last Configuration ID settings; - * 0x01 indicates more Configuration ID setting(s) are coming. - * \param[out] config: configuration id, number of configuration sets and register-mask-value sets - * \param[in] numConfig: the remaining configuration settings space available; - * - * \return ERR_NOMEM : if number of Configuration for respective Configuration ID is greater the the remaining configuration setting space available - * \return ERR_NONE : if read is successful - * - ***************************************************************************** - */ -ReturnCode rfalAnalogConfigListRead( - rfalAnalogConfigOffset* configOffset, - uint8_t* more, - rfalAnalogConfig* config, - rfalAnalogConfigNum numConfig); - -/*! - ***************************************************************************** - * \brief Set the Analog settings of indicated Configuration ID. - * - * Update the chip with indicated analog settings of indicated Configuration ID. - * - * \param[in] configId: configuration ID - * - * \return ERR_PARAM if Configuration ID is invalid - * \return ERR_INTERNAL if error updating setting to chip - * \return ERR_NONE if new settings is applied to chip - * - ***************************************************************************** - */ -ReturnCode rfalSetAnalogConfig(rfalAnalogConfigId configId); - -/*! - ***************************************************************************** - * \brief Generates Analog Config mode ID - * - * Converts RFAL mode and bitrate into Analog Config Mode ID. - * - * Update the chip with indicated analog settings of indicated Configuration ID. - * - * \param[in] md: RFAL mode format - * \param[in] br: RFAL bit rate format - * \param[in] dir: Analog Config communication direction - * - * \return Analog Config Mode ID - * - ***************************************************************************** - */ -uint16_t rfalAnalogConfigGenModeID(rfalMode md, rfalBitRate br, uint16_t dir); - -#endif /* RFAL_ANALOG_CONFIG_H */ - -/** - * @} - * - * @} - * - * @} - */ diff --git a/lib/ST25RFAL002/include/rfal_chip.h b/lib/ST25RFAL002/include/rfal_chip.h deleted file mode 100644 index 296136b4189..00000000000 --- a/lib/ST25RFAL002/include/rfal_chip.h +++ /dev/null @@ -1,287 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

    © COPYRIGHT 2020 STMicroelectronics

    - * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R391x firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file rfal_chip.h - * - * \author Gustavo Patricio - * - * \brief RF Chip specific Layer - * - * \warning This layer, which provides direct access to RF chip, should - * only be used for debug purposes and/or advanced features - * - * - * \addtogroup RFAL - * @{ - * - * \addtogroup RFAL-HAL - * \brief RFAL Hardware Abstraction Layer - * @{ - * - * \addtogroup Chip - * \brief RFAL RF Chip Module - * @{ - * - */ - -#ifndef RFAL_CHIP_H -#define RFAL_CHIP_H - -/* -****************************************************************************** -* INCLUDES -****************************************************************************** -*/ -#include "platform.h" -#include "st_errno.h" -#include "rfal_rf.h" - -/***************************************************************************** - * RF Chip * - *****************************************************************************/ - -/*! - ***************************************************************************** - * \brief Writes a register on the RF Chip - * - * Checks if the given register is valid and if so, writes the value(s) - * on the RF Chip register - * - * \param[in] reg: register address to be written, or the first if len > 1 - * \param[in] values: pointer with content to be written on the register(s) - * \param[in] len: number of consecutive registers to be written - * - * - * \return ERR_PARAM : Invalid register or bad request - * \return ERR_NOTSUPP : Feature not supported - * \return ERR_NONE : Write done with no error - ***************************************************************************** - */ -ReturnCode rfalChipWriteReg(uint16_t reg, const uint8_t* values, uint8_t len); - -/*! - ***************************************************************************** - * \brief Reads a register on the RF Chip - * - * Checks if the given register is valid and if so, reads the value(s) - * of the RF Chip register(s) - * - * \param[in] reg: register address to be read, or the first if len > 1 - * \param[out] values: pointer where the register(s) read content will be placed - * \param[in] len: number of consecutive registers to be read - * - * \return ERR_PARAM : Invalid register or bad request - * \return ERR_NOTSUPP : Feature not supported - * \return ERR_NONE : Read done with no error - ***************************************************************************** - */ -ReturnCode rfalChipReadReg(uint16_t reg, uint8_t* values, uint8_t len); - -/*! - ***************************************************************************** - * \brief Change a register on the RF Chip - * - * Change the value of the register bits on the RF Chip Test set in the valueMask. - * - * \param[in] reg: register address to be modified - * \param[in] valueMask: mask value of the register bits to be changed - * \param[in] value: register value to be set - * - * \return ERR_PARAM : Invalid register or bad request - * \return ERR_NOTSUPP : Feature not supported - * \return ERR_OK : Change done with no error - ***************************************************************************** - */ -ReturnCode rfalChipChangeRegBits(uint16_t reg, uint8_t valueMask, uint8_t value); - -/*! - ***************************************************************************** - * \brief Writes a Test register on the RF Chip - * - * Writes the value on the RF Chip Test register - * - * \param[in] reg: register address to be written - * \param[in] value: value to be written on the register - * - * - * \return ERR_PARAM : Invalid register or bad request - * \return ERR_NOTSUPP : Feature not supported - * \return ERR_NONE : Write done with no error - ***************************************************************************** - */ -ReturnCode rfalChipWriteTestReg(uint16_t reg, uint8_t value); - -/*! - ***************************************************************************** - * \brief Reads a Test register on the RF Chip - * - * Reads the value of the RF Chip Test register - * - * \param[in] reg: register address to be read - * \param[out] value: pointer where the register content will be placed - * - * \return ERR_PARAM :Invalid register or bad request - * \return ERR_NOTSUPP : Feature not supported - * \return ERR_NONE : Read done with no error - ***************************************************************************** - */ -ReturnCode rfalChipReadTestReg(uint16_t reg, uint8_t* value); - -/*! - ***************************************************************************** - * \brief Change a Test register on the RF Chip - * - * Change the value of the register bits on the RF Chip Test set in the valueMask. - * - * \param[in] reg: test register address to be modified - * \param[in] valueMask: mask value of the register bits to be changed - * \param[in] value: register value to be set - * - * \return ERR_PARAM : Invalid register or bad request - * \return ERR_NOTSUPP : Feature not supported - * \return ERR_OK : Change done with no error - ***************************************************************************** - */ -ReturnCode rfalChipChangeTestRegBits(uint16_t reg, uint8_t valueMask, uint8_t value); - -/*! - ***************************************************************************** - * \brief Execute command on the RF Chip - * - * Checks if the given command is valid and if so, executes it on - * the RF Chip - * - * \param[in] cmd: direct command to be executed - * - * \return ERR_PARAM : Invalid command or bad request - * \return ERR_NOTSUPP : Feature not supported - * \return ERR_NONE : Direct command executed with no error - ***************************************************************************** - */ -ReturnCode rfalChipExecCmd(uint16_t cmd); - -/*! - ***************************************************************************** - * \brief Set RFO - * - * Sets the RFO value to be used when the field is on (unmodulated/active) - * - * \param[in] rfo : the RFO value to be used - * - * \return ERR_IO : Internal error - * \return ERR_NOTSUPP : Feature not supported - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalChipSetRFO(uint8_t rfo); - -/*! - ***************************************************************************** - * \brief Get RFO - * - * Gets the RFO value used used when the field is on (unmodulated/active) - * - * \param[out] result : the current RFO value - * - * \return ERR_IO : Internal error - * \return ERR_NOTSUPP : Feature not supported - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalChipGetRFO(uint8_t* result); - -/*! - ***************************************************************************** - * \brief Measure Amplitude - * - * Measures the RF Amplitude - * - * \param[out] result : result of RF measurement - * - * \return ERR_IO : Internal error - * \return ERR_NOTSUPP : Feature not supported - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalChipMeasureAmplitude(uint8_t* result); - -/*! - ***************************************************************************** - * \brief Measure Phase - * - * Measures the Phase - * - * \param[out] result : result of Phase measurement - * - * \return ERR_IO : Internal error - * \return ERR_NOTSUPP : Feature not supported - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalChipMeasurePhase(uint8_t* result); - -/*! - ***************************************************************************** - * \brief Measure Capacitance - * - * Measures the Capacitance - * - * \param[out] result : result of Capacitance measurement - * - * \return ERR_IO : Internal error - * \return ERR_NOTSUPP : Feature not supported - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalChipMeasureCapacitance(uint8_t* result); - -/*! - ***************************************************************************** - * \brief Measure Power Supply - * - * Measures the Power Supply - * - * \param[in] param : measurement parameter (chip specific) - * \param[out] result : result of the measurement - * - * \return ERR_IO : Internal error - * \return ERR_NOTSUPP : Feature not supported - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalChipMeasurePowerSupply(uint8_t param, uint8_t* result); - -#endif /* RFAL_CHIP_H */ - -/** - * @} - * - * @} - * - * @} - */ diff --git a/lib/ST25RFAL002/include/rfal_crc.h b/lib/ST25RFAL002/include/rfal_crc.h deleted file mode 100644 index 134318cd1d2..00000000000 --- a/lib/ST25RFAL002/include/rfal_crc.h +++ /dev/null @@ -1,74 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

    © COPYRIGHT 2020 STMicroelectronics

    - * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R391x firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file rfal_crc.h - * - * \author Ulrich Herrmann - * - * \brief CRC calculation module - * - */ -/*! - * - */ - -#ifndef RFAL_CRC_H_ -#define RFAL_CRC_H_ - -/* -****************************************************************************** -* INCLUDES -****************************************************************************** -*/ -#include "platform.h" - -/* -****************************************************************************** -* GLOBAL FUNCTION PROTOTYPES -****************************************************************************** -*/ -/*! - ***************************************************************************** - * \brief Calculate CRC according to CCITT standard. - * - * This function takes \a length bytes from \a buf and calculates the CRC - * for this data. The result is returned. - * \note This implementation calculates the CRC with LSB first, i.e. all - * bytes are "read" from right to left. - * - * \param[in] preloadValue : Initial value of CRC calculation. - * \param[in] buf : buffer to calculate the CRC for. - * \param[in] length : size of the buffer. - * - * \return 16 bit long crc value. - * - ***************************************************************************** - */ -extern uint16_t rfalCrcCalculateCcitt(uint16_t preloadValue, const uint8_t* buf, uint16_t length); - -#endif /* RFAL_CRC_H_ */ diff --git a/lib/ST25RFAL002/include/rfal_dpo.h b/lib/ST25RFAL002/include/rfal_dpo.h deleted file mode 100644 index 7dd2cde5e24..00000000000 --- a/lib/ST25RFAL002/include/rfal_dpo.h +++ /dev/null @@ -1,207 +0,0 @@ - -/****************************************************************************** - * @attention - * - *

    © COPYRIGHT 2020 STMicroelectronics

    - * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R391x firmware - * $Revision: $ - * LANGUAGE: ISO C99 - */ - -/*! \file rfal_dpo.h - * - * \author Martin Zechleitner - * - * \brief Dynamic Power adjustment - * - * This module provides an interface to perform the power adjustment dynamically - * - * - * \addtogroup RFAL - * @{ - * - * \addtogroup RFAL-HAL - * \brief RFAL Hardware Abstraction Layer - * @{ - * - * \addtogroup DPO - * \brief RFAL Dynamic Power Module - * @{ - * - */ - -#ifndef RFAL_DPO_H -#define RFAL_DPO_H - -/* - ****************************************************************************** - * INCLUDES - ****************************************************************************** - */ -#include "platform.h" -#include "st_errno.h" - -/* - ****************************************************************************** - * GLOBAL DEFINES - ****************************************************************************** - */ - -#define RFAL_DPO_TABLE_SIZE_MAX 15U /*!< Max DPO table size */ -#define RFAL_DPO_TABLE_PARAMETER 3U /*!< DPO table Parameter length */ - -/* -****************************************************************************** -* GLOBAL TYPES -****************************************************************************** -*/ - -/*! DPO table entry struct */ -typedef struct { - uint8_t rfoRes; /*!< Setting for the resistance level of the RFO */ - uint8_t inc; /*!< Threshold for incrementing the output power */ - uint8_t dec; /*!< Threshold for decrementing the output power */ -} rfalDpoEntry; - -/*! Function pointer to method doing the reference measurement */ -typedef ReturnCode (*rfalDpoMeasureFunc)(uint8_t*); - -/* -****************************************************************************** -* GLOBAL FUNCTION PROTOTYPES -****************************************************************************** -*/ - -/*! - ***************************************************************************** - * \brief Initialize dynamic power table - * - * This function sets the internal dynamic power table to the default - * values stored in rfal_DpoTbl.h - * - ***************************************************************************** - */ -void rfalDpoInitialize(void); - -/*! - ***************************************************************************** - * \brief Set the measurement method - * - * This function sets the measurement method used for reference measurement. - * Based on the measurement the power will then be adjusted - * - * \param[in] dpoMeasureFunc: callback of measurement function - * - ***************************************************************************** - */ -void rfalDpoSetMeasureCallback(rfalDpoMeasureFunc dpoMeasureFunc); - -/*! - ***************************************************************************** - * \brief Write dynamic power table - * - * Load the dynamic power table - * - * \param[in] powerTbl: location of power Table to be loaded - * \param[in] powerTblEntries: number of entries of the power Table to be loaded - * - * \return ERR_NONE : No error - * \return ERR_PARAM : if configTbl is invalid - * \return ERR_NOMEM : if the given Table is bigger exceeds the max size - ***************************************************************************** - */ -ReturnCode rfalDpoTableWrite(rfalDpoEntry* powerTbl, uint8_t powerTblEntries); - -/*! - ***************************************************************************** - * \brief Dynamic power table Read - * - * Read the dynamic power table - * - * \param[out] tblBuf: location to the rfalDpoEntry[] to place the Table - * \param[in] tblBufEntries: number of entries available in tblBuf to place the power Table - * \param[out] tableEntries: returned number of entries actually written into tblBuf - * - * \return ERR_NONE : No error - * \return ERR_PARAM : if configTbl is invalid or parameters are invalid - ***************************************************************************** - */ -ReturnCode rfalDpoTableRead(rfalDpoEntry* tblBuf, uint8_t tblBufEntries, uint8_t* tableEntries); - -/*! - ***************************************************************************** - * \brief Dynamic power adjust - * - * It measures the current output and adjusts the power accordingly to - * the dynamic power table - * - * \return ERR_NONE : No error - * \return ERR_PARAM : if configTbl is invalid or parameters are invalid - * \return ERR_WRONG_STATE : if the current state is valid for DPO Adjustment - ***************************************************************************** - */ -ReturnCode rfalDpoAdjust(void); - -/*! - ***************************************************************************** - * \brief Get Current Dynamic power table entry - * - * Return current used DPO power table entry settings - * - * \return ERR_NONE : Current DpoEntry. This includes d_res, inc and dec - * - ***************************************************************************** - */ -rfalDpoEntry* rfalDpoGetCurrentTableEntry(void); - -/*! - ***************************************************************************** - * \brief Dynamic power set enabled state - * - * \param[in] enable: new active state - * - * Set state to enable or disable the Dynamic power adjustment - * - ***************************************************************************** - */ -void rfalDpoSetEnabled(bool enable); - -/*! - ***************************************************************************** - * \brief Get the Dynamic power enabled state - * - * Get state of the Dynamic power adjustment - * - * \return true : enabled - * \return false : disabled - ***************************************************************************** - */ -bool rfalDpoIsEnabled(void); - -#endif /* RFAL_DPO_H */ - -/** - * @} - * - * @} - * - * @} - */ diff --git a/lib/ST25RFAL002/include/rfal_iso15693_2.h b/lib/ST25RFAL002/include/rfal_iso15693_2.h deleted file mode 100644 index 9949812050d..00000000000 --- a/lib/ST25RFAL002/include/rfal_iso15693_2.h +++ /dev/null @@ -1,206 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

    © COPYRIGHT 2020 STMicroelectronics

    - * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R391x firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file rfal_iso15693_2.h - * - * \author Ulrich Herrmann - * - * \brief Implementation of ISO-15693-2 - * - */ -/*! - * - */ - -#ifndef RFAL_ISO_15693_2_H -#define RFAL_ISO_15693_2_H - -/* -****************************************************************************** -* INCLUDES -****************************************************************************** -*/ -#include "platform.h" -#include "st_errno.h" - -/* -****************************************************************************** -* GLOBAL DATATYPES -****************************************************************************** -*/ -/*! Enum holding possible VCD codings */ -typedef enum { ISO15693_VCD_CODING_1_4, ISO15693_VCD_CODING_1_256 } iso15693VcdCoding_t; - -/*! Enum holding possible VICC datarates */ - -/*! Configuration parameter used by #iso15693PhyConfigure */ -typedef struct { - iso15693VcdCoding_t coding; /*!< desired VCD coding */ - uint32_t - speedMode; /*!< 0: normal mode, 1: 2^1 = x2 Fast mode, 2 : 2^2 = x4 mode, 3 : 2^3 = x8 mode - all rx pulse numbers and times are divided by 1,2,4,8 */ -} iso15693PhyConfig_t; - -/*! Parameters how the stream mode should work */ -struct iso15693StreamConfig { - uint8_t useBPSK; /*!< 0: subcarrier, 1:BPSK */ - uint8_t din; /*!< the divider for the in subcarrier frequency: fc/2^din */ - uint8_t dout; /*!< the divider for the in subcarrier frequency fc/2^dout */ - uint8_t report_period_length; /*!< the length of the reporting period 2^report_period_length*/ -}; -/* -****************************************************************************** -* GLOBAL CONSTANTS -****************************************************************************** -*/ - -#define ISO15693_REQ_FLAG_TWO_SUBCARRIERS \ - 0x01U /*!< Flag indication that communication uses two subcarriers */ -#define ISO15693_REQ_FLAG_HIGH_DATARATE \ - 0x02U /*!< Flag indication that communication uses high bitrate */ -#define ISO15693_MASK_FDT_LISTEN \ - (65) /*!< t1min = 308,2us = 4192/fc = 65.5 * 64/fc */ - -/*! t1max = 323,3us = 4384/fc = 68.5 * 64/fc - * 12 = 768/fc unmodulated time of single subcarrior SoF */ -#define ISO15693_FWT (69 + 12) - -/* -****************************************************************************** -* GLOBAL FUNCTION PROTOTYPES -****************************************************************************** -*/ -/*! - ***************************************************************************** - * \brief Initialize the ISO15693 phy - * - * \param[in] config : ISO15693 phy related configuration (See #iso15693PhyConfig_t) - * \param[out] needed_stream_config : return a pointer to the stream config - * needed for this iso15693 config. To be used for configure RF chip. - * - * \return ERR_IO : Error during communication. - * \return ERR_NONE : No error. - * - ***************************************************************************** - */ -extern ReturnCode iso15693PhyConfigure( - const iso15693PhyConfig_t* config, - const struct iso15693StreamConfig** needed_stream_config); - -/*! - ***************************************************************************** - * \brief Return current phy configuration - * - * This function returns current Phy configuration previously - * set by #iso15693PhyConfigure - * - * \param[out] config : ISO15693 phy configuration. - * - * \return ERR_NONE : No error. - * - ***************************************************************************** - */ -extern ReturnCode iso15693PhyGetConfiguration(iso15693PhyConfig_t* config); - -/*! - ***************************************************************************** - * \brief Code an ISO15693 compatible frame - * - * This function takes \a length bytes from \a buffer, perform proper - * encoding and sends out the frame to the ST25R391x. - * - * \param[in] buffer : data to send, modified to adapt flags. - * \param[in] length : number of bytes to send. - * \param[in] sendCrc : If set to true, CRC is appended to the frame - * \param[in] sendFlags: If set to true, flag field is sent according to - * ISO15693. - * \param[in] picopassMode : If set to true, the coding will be according to Picopass - * \param[out] subbit_total_length : Return the complete bytes which need to - * be send for the current coding - * \param[in,out] offset : Set to 0 for first transfer, function will update it to - point to next byte to be coded - * \param[out] outbuf : buffer where the function will store the coded subbit stream - * \param[out] outBufSize : the size of the output buffer - * \param[out] actOutBufSize : the amount of data stored into the buffer at this call - * - * \return ERR_IO : Error during communication. - * \return ERR_AGAIN : Data was not coded all the way. Call function again with a new/emptied buffer - * \return ERR_NO_MEM : In case outBuf is not big enough. Needs to have at - least 5 bytes for 1of4 coding and 65 bytes for 1of256 coding - * \return ERR_NONE : No error. - * - ***************************************************************************** - */ -extern ReturnCode iso15693VCDCode( - uint8_t* buffer, - uint16_t length, - bool sendCrc, - bool sendFlags, - bool picopassMode, - uint16_t* subbit_total_length, - uint16_t* offset, - uint8_t* outbuf, - uint16_t outBufSize, - uint16_t* actOutBufSize); - -/*! - ***************************************************************************** - * \brief Receive an ISO15693 compatible frame - * - * This function receives an ISO15693 frame from the ST25R391x, decodes the frame - * and writes the raw data to \a buffer. - * \note Buffer needs to be big enough to hold CRC also (+2 bytes) - * - * \param[in] inBuf : buffer with the hamming coded stream to be decoded - * \param[in] inBufLen : number of bytes to decode (=length of buffer). - * \param[out] outBuf : buffer where received data shall be written to. - * \param[in] outBufLen : Length of output buffer, should be approx twice the size of inBuf - * \param[out] outBufPos : The number of decoded bytes. Could be used in - * extended implementation to allow multiple calls - * \param[out] bitsBeforeCol : in case of ERR_COLLISION this value holds the - * number of bits in the current byte where the collision happened. - * \param[in] ignoreBits : number of bits in the beginning where collisions will be ignored - * \param[in] picopassMode : if set to true, the decoding will be according to Picopass - * - * \return ERR_COLLISION : collision occurred, data incorrect - * \return ERR_CRC : CRC error, data incorrect - * \return ERR_TIMEOUT : timeout waiting for data. - * \return ERR_NONE : No error. - * - ***************************************************************************** - */ -extern ReturnCode iso15693VICCDecode( - const uint8_t* inBuf, - uint16_t inBufLen, - uint8_t* outBuf, - uint16_t outBufLen, - uint16_t* outBufPos, - uint16_t* bitsBeforeCol, - uint16_t ignoreBits, - bool picopassMode); - -#endif /* RFAL_ISO_15693_2_H */ diff --git a/lib/ST25RFAL002/include/rfal_isoDep.h b/lib/ST25RFAL002/include/rfal_isoDep.h deleted file mode 100644 index 9b2d32c64d8..00000000000 --- a/lib/ST25RFAL002/include/rfal_isoDep.h +++ /dev/null @@ -1,1092 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

    © COPYRIGHT 2020 STMicroelectronics

    - * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R391x firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file rfal_isoDep.h - * - * \author Gustavo Patricio - * - * \brief Implementation of ISO-DEP protocol - * - * This implementation was based on the following specs: - * - ISO/IEC 14443-4 2nd Edition 2008-07-15 - * - NFC Forum Digital Protocol 1.1 2014-01-14 - * - * - * \addtogroup RFAL - * @{ - * - * \addtogroup RFAL-AL - * \brief RFAL Abstraction Layer - * @{ - * - * \addtogroup ISO-DEP - * \brief RFAL ISO-DEP Module - * @{ - * - */ - -#ifndef RFAL_ISODEP_H_ -#define RFAL_ISODEP_H_ -/* - ****************************************************************************** - * INCLUDES - ****************************************************************************** - */ -#include "platform.h" -#include "rfal_nfcb.h" - -/* - ****************************************************************************** - * ENABLE SWITCH - ****************************************************************************** - */ - -#ifndef RFAL_FEATURE_ISO_DEP -#define RFAL_FEATURE_ISO_DEP \ - false /*!< ISO-DEP module configuration missing. Disabled by default */ -#endif - -/* If module is disabled remove the need for the user to set lengths */ -#if !RFAL_FEATURE_ISO_DEP -#undef RFAL_FEATURE_ISO_DEP_IBLOCK_MAX_LEN -#undef RFAL_FEATURE_ISO_DEP_APDU_MAX_LEN - -#define RFAL_FEATURE_ISO_DEP_IBLOCK_MAX_LEN (1U) /*!< ISO-DEP I-Block max length, set to "none" */ -#define RFAL_FEATURE_ISO_DEP_APDU_MAX_LEN (1U) /*!< ISO-DEP APDU max length, set to "none" */ -#endif /* !RFAL_FEATURE_NFC_DEP */ - -/* - ****************************************************************************** - * DEFINES - ****************************************************************************** - */ - -#define RFAL_ISODEP_PROLOGUE_SIZE \ - (3U) /*!< Length of Prologue Field for I-Block Format */ - -#define RFAL_ISODEP_PCB_LEN \ - (1U) /*!< PCB length */ -#define RFAL_ISODEP_DID_LEN \ - (1U) /*!< DID length */ -#define RFAL_ISODEP_NAD_LEN \ - (1U) /*!< NAD length */ -#define RFAL_ISODEP_NO_DID \ - (0x00U) /*!< DID value indicating the ISO-DEP layer not to use DID */ -#define RFAL_ISODEP_NO_NAD \ - (0xFFU) /*!< NAD value indicating the ISO-DEP layer not to use NAD */ - -#define RFAL_ISODEP_FWI_MASK \ - (0xF0U) /*!< Mask bits of FWI */ -#define RFAL_ISODEP_FWI_SHIFT \ - (4U) /*!< Shift val of FWI */ -#define RFAL_ISODEP_FWI_DEFAULT \ - (4U) /*!< Default value for FWI Digital 1.0 11.6.2.17 */ -#define RFAL_ISODEP_ADV_FEATURE \ - (0x0FU) /*!< Indicate 256 Bytes FSD and Advanc Proto Feature support:NAD & DID */ - -#define RFAL_ISODEP_DID_MAX \ - (14U) /*!< Maximum DID value */ - -#define RFAL_ISODEP_BRI_MASK \ - (0x07U) /*!< Mask bits for Poll to Listen Send bitrate */ -#define RFAL_ISODEP_BSI_MASK \ - (0x70U) /*!< Mask bits for Listen to Poll Send bitrate */ -#define RFAL_ISODEP_SAME_BITRATE_MASK \ - (0x80U) /*!< Mask bit indicate only same bit rate D for both direction support */ -#define RFAL_ISODEP_BITRATE_RFU_MASK \ - (0x08U) /*!< Mask bit for RFU */ - -/*! Maximum Frame Waiting Time = ((256 * 16/fc) * 2^FWImax) = ((256*16/fc)*2^14) = (67108864)/fc = 2^26 (1/fc) */ -#define RFAL_ISODEP_MAX_FWT ((uint32_t)1U << 26) - -#define RFAL_ISODEP_FSDI_DEFAULT \ - RFAL_ISODEP_FSXI_256 /*!< Default Frame Size Integer in Poll mode */ -#define RFAL_ISODEP_FSX_KEEP (0xFFU) /*!< Flag to keep FSX from activation */ -#define RFAL_ISODEP_DEFAULT_FSCI \ - RFAL_ISODEP_FSXI_256 /*!< FSCI default value to be used in Listen Mode */ -#define RFAL_ISODEP_DEFAULT_FSC \ - RFAL_ISODEP_FSX_256 /*!< FSC default value (aligned RFAL_ISODEP_DEFAULT_FSCI) */ -#define RFAL_ISODEP_DEFAULT_SFGI (0U) /*!< SFGI Default value to be used in Listen Mode */ -#define RFAL_ISODEP_DEFAULT_FWI (8U) /*!< Default Listener FWI (Max) Digital 2.0 B7 & B3 */ - -#define RFAL_ISODEP_APDU_MAX_LEN \ - RFAL_ISODEP_FSX_1024 /*!< Max APDU length */ - -#define RFAL_ISODEP_ATTRIB_RES_MBLI_NO_INFO \ - (0x00U) /*!< MBLI indicating no information on its internal input buffer size */ -#define RFAL_ISODEP_ATTRIB_REQ_PARAM1_DEFAULT \ - (0x00U) /*!< Default values of Param 1 of ATTRIB_REQ Digital 1.0 12.6.1.3-5 */ -#define RFAL_ISODEP_ATTRIB_HLINFO_LEN \ - (32U) /*!< Maximum Size of Higher Layer Information */ -#define RFAL_ISODEP_ATS_HB_MAX_LEN \ - (15U) /*!< Maximum length of Historical Bytes Digital 1.1 13.6.2.23 */ -#define RFAL_ISODEP_ATTRIB_REQ_MIN_LEN \ - (9U) /*!< Minimum Length of ATTRIB_REQ command */ -#define RFAL_ISODEP_ATTRIB_RES_MIN_LEN \ - (1U) /*!< Minimum Length of ATTRIB_RES response */ - -#define RFAL_ISODEP_SPARAM_VALUES_MAX_LEN \ - (16U) /*!< Maximum Length of the value field on S(PARAMETERS) */ -#define RFAL_ISODEP_SPARAM_TAG_BLOCKINFO \ - (0xA0U) /*!< S(PARAMETERS) tag Block information */ -#define RFAL_ISODEP_SPARAM_TAG_BRREQ \ - (0xA1U) /*!< S(PARAMETERS) tag Bit rates Request */ -#define RFAL_ISODEP_SPARAM_TAG_BRIND \ - (0xA2U) /*!< S(PARAMETERS) tag Bit rates Indication */ -#define RFAL_ISODEP_SPARAM_TAG_BRACT \ - (0xA3U) /*!< S(PARAMETERS) tag Bit rates Activation */ -#define RFAL_ISODEP_SPARAM_TAG_BRACK \ - (0xA4U) /*!< S(PARAMETERS) tag Bit rates Acknowledgement */ - -#define RFAL_ISODEP_SPARAM_TAG_SUP_PCD2PICC \ - (0x80U) /*!< S(PARAMETERS) tag Supported bit rates from PCD to PICC */ -#define RFAL_ISODEP_SPARAM_TAG_SUP_PICC2PCD \ - (0x81U) /*!< S(PARAMETERS) tag Supported bit rates from PICC to PCD */ -#define RFAL_ISODEP_SPARAM_TAG_SUP_FRAME \ - (0x82U) /*!< S(PARAMETERS) tag Supported framing options PICC to PCD */ -#define RFAL_ISODEP_SPARAM_TAG_SEL_PCD2PICC \ - (0x83U) /*!< S(PARAMETERS) tag Selected bit rate from PCD to PICC */ -#define RFAL_ISODEP_SPARAM_TAG_SEL_PICC2PCD \ - (0x84U) /*!< S(PARAMETERS) tag Selected bit rate from PICC to PCD */ -#define RFAL_ISODEP_SPARAM_TAG_SEL_FRAME \ - (0x85U) /*!< S(PARAMETERS) tag Selected framing options PICC to PCD */ - -#define RFAL_ISODEP_SPARAM_TAG_LEN \ - (1) /*!< S(PARAMETERS) Tag Length */ -#define RFAL_ISODEP_SPARAM_TAG_BRREQ_LEN \ - (0U) /*!< S(PARAMETERS) tag Bit rates Request Length */ -#define RFAL_ISODEP_SPARAM_TAG_PICC2PCD_LEN \ - (2U) /*!< S(PARAMETERS) bit rates from PCD to PICC Length */ -#define RFAL_ISODEP_SPARAM_TAG_PCD2PICC_LEN \ - (2U) /*!< S(PARAMETERS) bit rates from PICC to PCD Length */ -#define RFAL_ISODEP_SPARAM_TAG_BRACK_LEN \ - (0U) /*!< S(PARAMETERS) tag Bit rates Acknowledgement Length */ - -#define RFAL_ISODEP_ATS_TA_DPL_212 \ - (0x01U) /*!< ATS TA DSI 212 kbps support bit mask */ -#define RFAL_ISODEP_ATS_TA_DPL_424 \ - (0x02U) /*!< ATS TA DSI 424 kbps support bit mask */ -#define RFAL_ISODEP_ATS_TA_DPL_848 \ - (0x04U) /*!< ATS TA DSI 848 kbps support bit mask */ -#define RFAL_ISODEP_ATS_TA_DLP_212 \ - (0x10U) /*!< ATS TA DSI 212 kbps support bit mask */ -#define RFAL_ISODEP_ATS_TA_DLP_424 \ - (0x20U) /*!< ATS TA DRI 424 kbps support bit mask */ -#define RFAL_ISODEP_ATS_TA_DLP_848 \ - (0x40U) /*!< ATS TA DRI 848 kbps support bit mask */ -#define RFAL_ISODEP_ATS_TA_SAME_D \ - (0x80U) /*!< ATS TA same bit both directions bit mask */ -#define RFAL_ISODEP_ATS_TB_FWI_MASK \ - (0xF0U) /*!< Mask bits for FWI (Frame Waiting Integer) in TB byte */ -#define RFAL_ISODEP_ATS_TB_SFGI_MASK \ - (0x0FU) /*!< Mask bits for SFGI (Start-Up Frame Guard Integer) in TB byte */ - -#define RFAL_ISODEP_ATS_T0_TA_PRESENCE_MASK \ - (0x10U) /*!< Mask bit for TA presence */ -#define RFAL_ISODEP_ATS_T0_TB_PRESENCE_MASK \ - (0x20U) /*!< Mask bit for TB presence */ -#define RFAL_ISODEP_ATS_T0_TC_PRESENCE_MASK \ - (0x40U) /*!< Mask bit for TC presence */ -#define RFAL_ISODEP_ATS_T0_FSCI_MASK \ - (0x0FU) /*!< Mask bit for FSCI presence */ -#define RFAL_ISODEP_ATS_T0_OFFSET \ - (0x01U) /*!< Offset of T0 in ATS Response */ - -#define RFAL_ISODEP_MAX_I_RETRYS \ - (2U) /*!< Number of retries for a I-Block Digital 2.0 16.2.5.4 */ -#define RFAL_ISODEP_MAX_R_RETRYS \ - (3U) /*!< Number of retries for a R-Block Digital 2.0 B9 - nRETRY ACK/NAK: [2,5] */ -#define RFAL_ISODEP_MAX_WTX_NACK_RETRYS \ - (3U) /*!< Number of S(WTX) replied with NACK Digital 2.0 B9 - nRETRY WTX[2,5] */ -#define RFAL_ISODEP_MAX_WTX_RETRYS \ - (20U) /*!< Number of overall S(WTX) retries Digital 2.0 16.2.5.2 */ -#define RFAL_ISODEP_MAX_WTX_RETRYS_ULTD \ - (255U) /*!< Use unlimited number of overall S(WTX) */ -#define RFAL_ISODEP_MAX_DSL_RETRYS \ - (0U) /*!< Number of retries for a S(DESELECT) Digital 2.0 B9 - nRETRY DESELECT: [0,5] */ -#define RFAL_ISODEP_RATS_RETRIES \ - (1U) /*!< RATS retries upon fail Digital 2.0 B7 - nRETRY RATS [0,1] */ - -/*! Frame Size for Proximity Card Integer definitions */ -typedef enum { - RFAL_ISODEP_FSXI_16 = - 0, /*!< Frame Size for Proximity Card Integer with 16 bytes */ - RFAL_ISODEP_FSXI_24 = - 1, /*!< Frame Size for Proximity Card Integer with 24 bytes */ - RFAL_ISODEP_FSXI_32 = - 2, /*!< Frame Size for Proximity Card Integer with 32 bytes */ - RFAL_ISODEP_FSXI_40 = - 3, /*!< Frame Size for Proximity Card Integer with 40 bytes */ - RFAL_ISODEP_FSXI_48 = - 4, /*!< Frame Size for Proximity Card Integer with 48 bytes */ - RFAL_ISODEP_FSXI_64 = - 5, /*!< Frame Size for Proximity Card Integer with 64 bytes */ - RFAL_ISODEP_FSXI_96 = - 6, /*!< Frame Size for Proximity Card Integer with 96 bytes */ - RFAL_ISODEP_FSXI_128 = - 7, /*!< Frame Size for Proximity Card Integer with 128 bytes */ - RFAL_ISODEP_FSXI_256 = - 8, /*!< Frame Size for Proximity Card Integer with 256 bytes */ - RFAL_ISODEP_FSXI_512 = - 9, /*!< Frame Size for Proximity Card Integer with 512 bytes ISO14443-3 Amd2 2012 */ - RFAL_ISODEP_FSXI_1024 = - 10, /*!< Frame Size for Proximity Card Integer with 1024 bytes ISO14443-3 Amd2 2012 */ - RFAL_ISODEP_FSXI_2048 = - 11, /*!< Frame Size for Proximity Card Integer with 2048 bytes ISO14443-3 Amd2 2012 */ - RFAL_ISODEP_FSXI_4096 = - 12 /*!< Frame Size for Proximity Card Integer with 4096 bytes ISO14443-3 Amd2 2012 */ -} rfalIsoDepFSxI; - -/*! Frame Size for Proximity Card definitions */ -typedef enum { - RFAL_ISODEP_FSX_16 = - 16, /*!< Frame Size for Proximity Card with 16 bytes */ - RFAL_ISODEP_FSX_24 = - 24, /*!< Frame Size for Proximity Card with 24 bytes */ - RFAL_ISODEP_FSX_32 = - 32, /*!< Frame Size for Proximity Card with 32 bytes */ - RFAL_ISODEP_FSX_40 = - 40, /*!< Frame Size for Proximity Card with 40 bytes */ - RFAL_ISODEP_FSX_48 = - 48, /*!< Frame Size for Proximity Card with 48 bytes */ - RFAL_ISODEP_FSX_64 = - 64, /*!< Frame Size for Proximity Card with 64 bytes */ - RFAL_ISODEP_FSX_96 = - 96, /*!< Frame Size for Proximity Card with 96 bytes */ - RFAL_ISODEP_FSX_128 = - 128, /*!< Frame Size for Proximity Card with 128 bytes */ - RFAL_ISODEP_FSX_256 = - 256, /*!< Frame Size for Proximity Card with 256 bytes */ - RFAL_ISODEP_FSX_512 = - 512, /*!< Frame Size for Proximity Card with 512 bytes ISO14443-3 Amd2 2012 */ - RFAL_ISODEP_FSX_1024 = - 1024, /*!< Frame Size for Proximity Card with 1024 bytes ISO14443-3 Amd2 2012 */ - RFAL_ISODEP_FSX_2048 = - 2048, /*!< Frame Size for Proximity Card with 2048 bytes ISO14443-3 Amd2 2012 */ - RFAL_ISODEP_FSX_4096 = - 4096, /*!< Frame Size for Proximity Card with 4096 bytes ISO14443-3 Amd2 2012 */ -} rfalIsoDepFSx; - -/* - ****************************************************************************** - * GLOBAL MACROS - ****************************************************************************** - */ - -/* - ****************************************************************************** - * GLOBAL DATA TYPES - ****************************************************************************** - */ - -/*! RATS format Digital 1.1 13.6.1 */ -typedef struct { - uint8_t CMD; /*!< RATS command byte: 0xE0 */ - uint8_t PARAM; /*!< Param indicating FSDI and DID */ -} rfalIsoDepRats; - -/*! ATS response format Digital 1.1 13.6.2 */ -typedef struct { - uint8_t TL; /*!< Length Byte, including TL byte itself */ - uint8_t T0; /*!< Format Byte T0 indicating if TA, TB, TC */ - uint8_t TA; /*!< Interface Byte TA(1) */ - uint8_t TB; /*!< Interface Byte TB(1) */ - uint8_t TC; /*!< Interface Byte TC(1) */ - uint8_t HB[RFAL_ISODEP_ATS_HB_MAX_LEN]; /*!< Historical Bytes */ -} rfalIsoDepAts; - -/*! PPS Request format (Protocol and Parameter Selection) ISO14443-4 5.3 */ -typedef struct { - uint8_t PPSS; /*!< Start Byte: [ 1101b | CID[4b] ] */ - uint8_t PPS0; /*!< Parameter 0:[ 000b | PPS1[1n] | 0001b ] */ - uint8_t PPS1; /*!< Parameter 1:[ 0000b | DSI[2b] | DRI[2b] ]*/ -} rfalIsoDepPpsReq; - -/*! PPS Response format (Protocol and Parameter Selection) ISO14443-4 5.4 */ -typedef struct { - uint8_t PPSS; /*!< Start Byte: [ 1101b | CID[4b] ] */ -} rfalIsoDepPpsRes; - -/*! ATTRIB Command Format Digital 1.1 15.6.1 */ -typedef struct { - uint8_t cmd; /*!< ATTRIB_REQ command byte */ - uint8_t nfcid0[RFAL_NFCB_NFCID0_LEN]; /*!< NFCID0 of the card to be selected */ - struct { - uint8_t PARAM1; /*!< PARAM1 of ATTRIB command */ - uint8_t PARAM2; /*!< PARAM2 of ATTRIB command */ - uint8_t PARAM3; /*!< PARAM3 of ATTRIB command */ - uint8_t PARAM4; /*!< PARAM4 of ATTRIB command */ - } Param; /*!< Parameter of ATTRIB command */ - uint8_t HLInfo[RFAL_ISODEP_ATTRIB_HLINFO_LEN]; /*!< Higher Layer Information */ -} rfalIsoDepAttribCmd; - -/*! ATTRIB Response Format Digital 1.1 15.6.2 */ -typedef struct { - uint8_t mbliDid; /*!< Contains MBLI and DID */ - uint8_t HLInfo[RFAL_ISODEP_ATTRIB_HLINFO_LEN]; /*!< Higher Layer Information */ -} rfalIsoDepAttribRes; - -/*! S(Parameters) Command Format ISO14443-4 (2016) Table 4 */ -typedef struct { - uint8_t tag; /*!< S(PARAMETERS) Tag field */ - uint8_t length; /*!< S(PARAMETERS) Length field */ - uint8_t value[RFAL_ISODEP_SPARAM_VALUES_MAX_LEN]; /*!< S(PARAMETERS) Value field */ -} rfalIsoDepSParameter; - -/*! Activation info as Poller and Listener for NFC-A and NFC-B */ -typedef union { /* PRQA S 0750 # MISRA 19.2 - Both members of the union will not be used concurrently, device is only of type A or B at a time. Thus no problem can occur. */ - - /*! NFC-A information */ - union { /* PRQA S 0750 # MISRA 19.2 - Both members of the union will not be used concurrently, device is only PCD or PICC at a time. Thus no problem can occur. */ - struct { - rfalIsoDepAts ATS; /*!< ATS response (Poller mode) */ - uint8_t ATSLen; /*!< ATS response length (Poller mode) */ - } Listener; - struct { - rfalIsoDepRats RATS; /*!< RATS request (Listener mode) */ - } Poller; - } A; - - /*! NFC-B information */ - union { /* PRQA S 0750 # MISRA 19.2 - Both members of the union will not be used concurrently, device is only PCD or PICC at a time. Thus no problem can occur. */ - struct { - rfalIsoDepAttribRes ATTRIB_RES; /*!< ATTRIB_RES (Poller mode) */ - uint8_t ATTRIB_RESLen; /*!< ATTRIB_RES length (Poller mode) */ - } Listener; - struct { - rfalIsoDepAttribCmd ATTRIB; /*!< ATTRIB request (Listener mode) */ - uint8_t ATTRIBLen; /*!< ATTRIB request length (Listener mode) */ - } Poller; - } B; -} rfalIsoDepActivation; - -/*! ISO-DEP device Info */ -typedef struct { - uint8_t FWI; /*!< Frame Waiting Integer */ - uint32_t FWT; /*!< Frame Waiting Time (1/fc) */ - uint32_t dFWT; /*!< Delta Frame Waiting Time (1/fc) */ - uint32_t SFGI; /*!< Start-up Frame Guard time Integer */ - uint32_t SFGT; /*!< Start-up Frame Guard Time (ms) */ - uint8_t FSxI; /*!< Frame Size Device/Card Integer (FSDI or FSCI) */ - uint16_t FSx; /*!< Frame Size Device/Card (FSD or FSC) */ - uint32_t MBL; /*!< Maximum Buffer Length (optional for NFC-B) */ - rfalBitRate DSI; /*!< Bit Rate coding from Listener (PICC) to Poller (PCD) */ - rfalBitRate DRI; /*!< Bit Rate coding from Poller (PCD) to Listener (PICC) */ - uint8_t DID; /*!< Device ID */ - uint8_t NAD; /*!< Node ADdress */ - bool supDID; /*!< DID supported flag */ - bool supNAD; /*!< NAD supported flag */ - bool supAdFt; /*!< Advanced Features supported flag */ -} rfalIsoDepInfo; - -/*! ISO-DEP Device structure */ -typedef struct { - rfalIsoDepActivation activation; /*!< Activation Info */ - rfalIsoDepInfo info; /*!< ISO-DEP (ISO14443-4) device Info */ -} rfalIsoDepDevice; - -/*! ATTRIB Response parameters */ -typedef struct { - uint8_t mbli; /*!< MBLI */ - uint8_t HLInfo[RFAL_ISODEP_ATTRIB_HLINFO_LEN]; /*!< Hi Layer Information */ - uint8_t HLInfoLen; /*!< Hi Layer Information Length */ -} rfalIsoDepAttribResParam; - -/*! ATS Response parameter */ -typedef struct { - uint8_t fsci; /*!< Frame Size of Proximity Card Integer */ - uint8_t fwi; /*!< Frame Waiting Time Integer */ - uint8_t sfgi; /*!< Start-Up Frame Guard Time Integer */ - bool didSupport; /*!< DID Supported */ - uint8_t ta; /*!< Max supported bitrate both direction */ - uint8_t* hb; /*!< Historical Bytes data */ - uint8_t hbLen; /*!< Historical Bytes Length */ -} rfalIsoDepAtsParam; - -/*! Structure of I-Block Buffer format from caller */ -typedef struct { - uint8_t prologue[RFAL_ISODEP_PROLOGUE_SIZE]; /*!< Prologue/SoD buffer */ - uint8_t - inf[RFAL_FEATURE_ISO_DEP_IBLOCK_MAX_LEN]; /*!< INF/Payload buffer */ -} rfalIsoDepBufFormat; - -/*! Structure of APDU Buffer format from caller */ -typedef struct { - uint8_t prologue[RFAL_ISODEP_PROLOGUE_SIZE]; /*!< Prologue/SoD buffer */ - uint8_t apdu[RFAL_FEATURE_ISO_DEP_APDU_MAX_LEN]; /*!< APDU/Payload buffer */ -} rfalIsoDepApduBufFormat; - -/*! Listen Activation Parameters Structure */ -typedef struct { - rfalIsoDepBufFormat* rxBuf; /*!< Receive Buffer struct reference */ - uint16_t* rxLen; /*!< Received INF data length in Bytes */ - bool* isRxChaining; /*!< Received data is not complete */ - rfalIsoDepDevice* isoDepDev; /*!< ISO-DEP device info */ -} rfalIsoDepListenActvParam; - -/*! Structure of parameters used on ISO DEP Transceive */ -typedef struct { - rfalIsoDepBufFormat* txBuf; /*!< Transmit Buffer struct reference */ - uint16_t txBufLen; /*!< Transmit Buffer INF field length in Bytes*/ - bool isTxChaining; /*!< Transmit data is not complete */ - rfalIsoDepBufFormat* rxBuf; /*!< Receive Buffer struct reference in Bytes */ - uint16_t* rxLen; /*!< Received INF data length in Bytes */ - bool* isRxChaining; /*!< Received data is not complete */ - uint32_t FWT; /*!< FWT to be used (ignored in Listen Mode) */ - uint32_t dFWT; /*!< Delta FWT to be used */ - uint16_t ourFSx; /*!< Our device Frame Size (FSD or FSC) */ - uint16_t FSx; /*!< Other device Frame Size (FSD or FSC) */ - uint8_t DID; /*!< Device ID (RFAL_ISODEP_NO_DID if no DID) */ -} rfalIsoDepTxRxParam; - -/*! Structure of parameters used on ISO DEP APDU Transceive */ -typedef struct { - rfalIsoDepApduBufFormat* txBuf; /*!< Transmit Buffer struct reference */ - uint16_t txBufLen; /*!< Transmit Buffer INF field length in Bytes*/ - rfalIsoDepApduBufFormat* rxBuf; /*!< Receive Buffer struct reference in Bytes */ - uint16_t* rxLen; /*!< Received INF data length in Bytes */ - rfalIsoDepBufFormat* tmpBuf; /*!< Temp buffer for Rx I-Blocks (internal) */ - uint32_t FWT; /*!< FWT to be used (ignored in Listen Mode) */ - uint32_t dFWT; /*!< Delta FWT to be used */ - uint16_t FSx; /*!< Other device Frame Size (FSD or FSC) */ - uint16_t ourFSx; /*!< Our device Frame Size (FSD or FSC) */ - uint8_t DID; /*!< Device ID (RFAL_ISODEP_NO_DID if no DID) */ -} rfalIsoDepApduTxRxParam; - -/* - ****************************************************************************** - * GLOBAL FUNCTION PROTOTYPES - ****************************************************************************** - */ - -/*! - ****************************************************************************** - * \brief Initialize the ISO-DEP protocol - * - * Initialize the ISO-DEP protocol layer with default config - ****************************************************************************** - */ -void rfalIsoDepInitialize(void); - -/*! - ****************************************************************************** - * \brief Initialize the ISO-DEP protocol - * - * Initialize the ISO-DEP protocol layer with additional parameters allowing - * to customise the protocol layer for specific behaviours - * - - * \param[in] compMode : Compliance mode to be performed - * \param[in] maxRetriesR : Number of retries for a R-Block - * Digital 2.0 B9 - nRETRY ACK/NAK: [2,5] - * \param[in] maxRetriesSnWTX : Number of retries for a S(WTX) (only in case - * of NAKs) Digital 2.0 B9 - nRETRY WTX[2,5] - * \param[in] maxRetriesSWTX : Number of overall S(WTX) retries. - * Use RFAL_ISODEP_MAX_WTX_RETRYS_ULTD for disabling - * this limit check Digital 2.0 16.2.5.2 - * \param[in] maxRetriesSDSL : Number of retries for a S(DESELECT) - * Digital 2.0 B9 - nRETRY DESELECT: [0,5] - * \param[in] maxRetriesI : Number of retries for a I-Block - * Digital 2.0 16.2.5.4 - * \param[in] maxRetriesRATS : Number of retries for RATS - * Digital 2.0 B7 - nRETRY RATS [0,1] - * - ****************************************************************************** - */ -void rfalIsoDepInitializeWithParams( - rfalComplianceMode compMode, - uint8_t maxRetriesR, - uint8_t maxRetriesSnWTX, - uint8_t maxRetriesSWTX, - uint8_t maxRetriesSDSL, - uint8_t maxRetriesI, - uint8_t maxRetriesRATS); - -/*! - ***************************************************************************** - * \brief FSxI to FSx - * - * Convert Frame Size for proximity coupling Device Integer (FSxI) to - * Frame Size for proximity coupling Device (FSx) - * - * FSD - maximum frame size for NFC Forum Device in Poll Mode - * FSC - maximum frame size for NFC Forum Device in Listen Mode - * - * FSxI = FSDI or FSCI - * FSx = FSD or FSC - * - * The FSD/FSC value includes the header and CRC - * - * \param[in] FSxI : Frame Size for proximity coupling Device Integer - * - * \return fsx : Frame Size for proximity coupling Device (FSD or FSC) - * - ***************************************************************************** - */ -uint16_t rfalIsoDepFSxI2FSx(uint8_t FSxI); - -/*! - ***************************************************************************** - * \brief FWI to FWT - * - * Convert Frame Waiting time Integer (FWI) to Frame Waiting Time (FWT) in - * 1/fc units - * - * \param[in] fwi : Frame Waiting time Integer - * - * \return fwt : Frame Waiting Time in 1/fc units - * - ***************************************************************************** - */ -uint32_t rfalIsoDepFWI2FWT(uint8_t fwi); - -/*! - ***************************************************************************** - * \brief Check if the buffer data contains a valid RATS command - * - * Check if it is a well formed RATS command with 2 bytes - * This function does not check the validity of FSDI and DID - * - * \param[in] buf : reference to buffer containing the data to be checked - * \param[in] bufLen : length of data in the buffer in bytes - * - * \return true if the data indicates a RATS command; false otherwise - ***************************************************************************** - */ -bool rfalIsoDepIsRats(const uint8_t* buf, uint8_t bufLen); - -/*! - ***************************************************************************** - * \brief Check if the buffer data contains a valid ATTRIB command - * - * Check if it is a well formed ATTRIB command, but does not check the - * validity of the information inside - * - * \param[in] buf : reference to buffer containing the data to be checked - * \param[in] bufLen : length of data in the buffer in bytes - * - * \return true if the data indicates a ATTRIB command; false otherwise - ***************************************************************************** - */ -bool rfalIsoDepIsAttrib(const uint8_t* buf, uint8_t bufLen); - -/*! - ***************************************************************************** - * \brief Start Listen Activation Handling - * - * Start Listen Activation Handling and setup to receive first I-block which may - * contain complete or partial APDU after activation is completed - * - * Pass in RATS for T4AT, or ATTRIB for T4BT, to handle ATS or ATTRIB Response respectively - * The Activation Handling handles ATS and ATTRIB Response; and additionally PPS Response - * if a PPS is received for T4AT. - * The method uses the current RFAL state machine to determine if it is expecting RATS or ATTRIB - * - * Activation is completed if PPS Response is sent or if first PDU is received in T4T-A - * Activation is completed if ATTRIB Response is sent in T4T-B - * - * \ref rfalIsoDepListenGetActivationStatus provide status if activation is completed. - * \ref rfalIsoDepStartTransceive shall be called right after activation is completed - * - * \param[in] atsParam : reference to ATS parameters - * \param[in] attribResParam : reference to ATTRIB_RES parameters - * \param[in] buf : reference to buffer containing RATS or ATTRIB - * \param[in] bufLen : length in bytes of the given buffer - * \param[in] actParam : reference to incoming reception information will be placed - * - * - * \warning Once the Activation has been completed the method - * rfalIsoDepGetTransceiveStatus() must be called. - * If activation has completed due to reception of a data block (not PPS) the - * buffer owned by the caller and passed on actParam must still contain this data. - * The first data will be processed (I-Block or S-DSL) by rfalIsoDepGetTransceiveStatus() - * inform the caller and then for the next transaction use rfalIsoDepStartTransceive() - * - * \return ERR_NONE : RATS/ATTRIB is valid and activation has started - * \return ERR_PARAM : Invalid parameters - * \return ERR_PROTO : Invalid request - * \return ERR_NOTSUPP : Feature not supported - ***************************************************************************** - */ -ReturnCode rfalIsoDepListenStartActivation( - rfalIsoDepAtsParam* atsParam, - const rfalIsoDepAttribResParam* attribResParam, - const uint8_t* buf, - uint16_t bufLen, - rfalIsoDepListenActvParam actParam); - -/*! - ***************************************************************************** - * \brief Get the current Activation Status - * - * \return ERR_NONE if Activation is already completed - * \return ERR_BUSY if Activation is ongoing - * \return ERR_LINK_LOSS if Remote Field is turned off - ***************************************************************************** - */ -ReturnCode rfalIsoDepListenGetActivationStatus(void); - -/*! - ***************************************************************************** - * \brief Get the ISO-DEP Communication Information - * - * Gets the maximum INF length in bytes based on current Frame Size - * for proximity coupling Device (FSD or FSC) excluding the header and CRC - * - * \return maximum INF length in bytes - ***************************************************************************** - */ -uint16_t rfalIsoDepGetMaxInfLen(void); - -/*! - ***************************************************************************** - * \brief ISO-DEP Start Transceive - * - * This method triggers a ISO-DEP Transceive containing a complete or - * partial APDU - * It transmits the given message and handles all protocol retransmitions, - * error handling and control messages - * - * The txBuf contains a complete or partial APDU (INF) to be transmitted - * The Prologue field will be manipulated by the Transceive - * - * If the buffer contains a partial APDU and is not the last block, - * then isTxChaining must be set to true - * - * \param[in] param: reference parameters to be used for the Transceive - * - * \return ERR_PARAM : Bad request - * \return ERR_WRONG_STATE : The module is not in a proper state - * \return ERR_NONE : The Transceive request has been started - ***************************************************************************** - */ -ReturnCode rfalIsoDepStartTransceive(rfalIsoDepTxRxParam param); - -/*! - ***************************************************************************** - * \brief Get the Transceive status - * - * Returns the status of the ISO-DEP Transceive - * - * \warning When the other device is performing chaining once a chained - * block is received the error ERR_AGAIN is sent. At this point - * caller must handle the received data immediately. - * When ERR_AGAIN is returned an ACK has already been sent to - * the other device and the next block might be incoming. - * If rfalWorker() is called frequently it will place the next - * block on the given buffer - * - * - * \return ERR_NONE : Transceive has been completed successfully - * \return ERR_BUSY : Transceive is ongoing - * \return ERR_PROTO : Protocol error occurred - * \return ERR_TIMEOUT : Timeout error occurred - * \return ERR_SLEEP_REQ : Deselect has been received and responded - * \return ERR_NOMEM : The received INF does not fit into the - * receive buffer - * \return ERR_LINK_LOSS : Communication is lost because Reader/Writer - * has turned off its field - * \return ERR_AGAIN : received one chaining block, continue to call - * this method to retrieve the remaining blocks - ***************************************************************************** - */ -ReturnCode rfalIsoDepGetTransceiveStatus(void); - -/*! - ***************************************************************************** - * \brief ISO-DEP Start APDU Transceive - * - * This method triggers a ISO-DEP Transceive containing a complete APDU - * It transmits the given message and handles all protocol retransmitions, - * error handling and control messages - * - * The txBuf contains a complete APDU to be transmitted - * The Prologue field will be manipulated by the Transceive - * - * \warning the txBuf will be modified during the transmission - * \warning the maximum RF frame which can be received is limited by param.tmpBuf - * - * \param[in] param: reference parameters to be used for the Transceive - * - * \return ERR_PARAM : Bad request - * \return ERR_WRONG_STATE : The module is not in a proper state - * \return ERR_NONE : The Transceive request has been started - ***************************************************************************** - */ -ReturnCode rfalIsoDepStartApduTransceive(rfalIsoDepApduTxRxParam param); - -/*! - ***************************************************************************** - * \brief Get the APDU Transceive status - * - * \return ERR_NONE : if Transceive has been completed successfully - * \return ERR_BUSY : if Transceive is ongoing - * \return ERR_PROTO : if a protocol error occurred - * \return ERR_TIMEOUT : if a timeout error occurred - * \return ERR_SLEEP_REQ : if Deselect is received and responded - * \return ERR_NOMEM : if the received INF does not fit into the - * receive buffer - * \return ERR_LINK_LOSS : if communication is lost because Reader/Writer - * has turned off its field - ***************************************************************************** - */ -ReturnCode rfalIsoDepGetApduTransceiveStatus(void); - -/*! - ***************************************************************************** - * \brief ISO-DEP Send RATS - * - * This sends a RATS to make a NFC-A Listen Device to enter - * ISO-DEP layer (ISO14443-4) and checks if the received ATS is valid - * - * \param[in] FSDI : Frame Size Device Integer to be used - * \param[in] DID : Device ID to be used or RFAL_ISODEP_NO_DID for not use DID - * \param[out] ats : pointer to place the ATS Response - * \param[out] atsLen : pointer to place the ATS length - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_TIMEOUT : Timeout error - * \return ERR_PAR : Parity error detected - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_NONE : No error, ATS received - ***************************************************************************** - */ -ReturnCode rfalIsoDepRATS(rfalIsoDepFSxI FSDI, uint8_t DID, rfalIsoDepAts* ats, uint8_t* atsLen); - -/*! - ***************************************************************************** - * \brief ISO-DEP Send PPS - * - * This sends a PPS to make a NFC-A Listen Device change the communications - * bit rate from 106kbps to one of the supported bit rates - * Additionally checks if the received PPS response is valid - * - * \param[in] DID : Device ID - * \param[in] DSI : DSI code the divisor from Listener (PICC) to Poller (PCD) - * \param[in] DRI : DRI code the divisor from Poller (PCD) to Listener (PICC) - * \param[out] ppsRes : pointer to place the PPS Response - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_TIMEOUT : Timeout error - * \return ERR_PAR : Parity error detected - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_NONE : No error, PPS Response received - ***************************************************************************** - */ -ReturnCode rfalIsoDepPPS(uint8_t DID, rfalBitRate DSI, rfalBitRate DRI, rfalIsoDepPpsRes* ppsRes); - -/*! - ***************************************************************************** - * \brief ISO-DEP Send ATTRIB - * - * This sends a ATTRIB to make a NFC-B Listen Device to enter - * ISO-DEP layer (ISO14443-4) and checks if the received ATTRIB Response is valid - * - * \param[in] nfcid0 : NFCID0 to be used for the ATTRIB - * \param[in] PARAM1 : ATTRIB PARAM1 byte (communication parameters) - * \param[in] DSI : DSI code the divisor from Listener (PICC) to Poller (PCD) - * \param[in] DRI : DRI code the divisor from Poller (PCD) to Listener (PICC) - * \param[in] FSDI : PCD's Frame Size to be announced on the ATTRIB - * \param[in] PARAM3 : ATTRIB PARAM1 byte (protocol type) - * \param[in] DID : Device ID to be used or RFAL_ISODEP_NO_DID for not use DID - * \param[in] HLInfo : pointer to Higher layer INF (NULL if none) - * \param[in] HLInfoLen : Length HLInfo - * \param[in] fwt : Frame Waiting Time to be used (from SENSB_RES) - * \param[out] attribRes : pointer to place the ATTRIB Response - * \param[out] attribResLen : pointer to place the ATTRIB Response length - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_TIMEOUT : Timeout error - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_NONE : No error, ATTRIB Response received - ***************************************************************************** - */ -ReturnCode rfalIsoDepATTRIB( - const uint8_t* nfcid0, - uint8_t PARAM1, - rfalBitRate DSI, - rfalBitRate DRI, - rfalIsoDepFSxI FSDI, - uint8_t PARAM3, - uint8_t DID, - const uint8_t* HLInfo, - uint8_t HLInfoLen, - uint32_t fwt, - rfalIsoDepAttribRes* attribRes, - uint8_t* attribResLen); - -/*! - ***************************************************************************** - * \brief Deselects PICC - * - * This function sends a deselect command to PICC and waits for it`s - * response in a blocking way - * - * \return ERR_NONE : Deselect successfully sent and acknowledged by PICC - * \return ERR_TIMEOUT: No response rcvd from PICC - * - ***************************************************************************** - */ -ReturnCode rfalIsoDepDeselect(void); - -/*! - ***************************************************************************** - * \brief ISO-DEP Poller Handle NFC-A Activation - * - * This performs a NFC-A Activation into ISO-DEP layer (ISO14443-4) with the given - * parameters. It sends RATS and if the higher bit rates are supported by - * both devices it additionally sends PPS - * Once Activated all details of the device are provided on isoDepDev - * - * \param[in] FSDI : Frame Size Device Integer to be used - * \param[in] DID : Device ID to be used or RFAL_ISODEP_NO_DID for not use DID - * \param[in] maxBR : Max bit rate supported by the Poller - * \param[out] isoDepDev : ISO-DEP information of the activated Listen device - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_TIMEOUT : Timeout error - * \return ERR_PAR : Parity error detected - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_NONE : No error, activation successful - ***************************************************************************** - */ -ReturnCode rfalIsoDepPollAHandleActivation( - rfalIsoDepFSxI FSDI, - uint8_t DID, - rfalBitRate maxBR, - rfalIsoDepDevice* isoDepDev); - -/*! - ***************************************************************************** - * \brief ISO-DEP Poller Handle NFC-B Activation - * - * This performs a NFC-B Activation into ISO-DEP layer (ISO14443-4) with the given - * parameters. It sends ATTRIB and calculates supported higher bit rates of both - * devices and performs activation. - * Once Activated all details of the device are provided on isoDepDev - * - * \param[in] FSDI : Frame Size Device Integer to be used - * \param[in] DID : Device ID to be used or RFAL_ISODEP_NO_DID for not use DID - * \param[in] maxBR : Max bit rate supported by the Poller - * \param[in] PARAM1 : ATTRIB PARAM1 byte (communication parameters) - * \param[in] nfcbDev : pointer to the NFC-B Device containing the SENSB_RES - * \param[in] HLInfo : pointer to Higher layer INF (NULL if none) - * \param[in] HLInfoLen : Length HLInfo - * \param[out] isoDepDev : ISO-DEP information of the activated Listen device - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_TIMEOUT : Timeout error - * \return ERR_PAR : Parity error detected - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_NONE : No error, activation successful - ***************************************************************************** - */ -ReturnCode rfalIsoDepPollBHandleActivation( - rfalIsoDepFSxI FSDI, - uint8_t DID, - rfalBitRate maxBR, - uint8_t PARAM1, - const rfalNfcbListenDevice* nfcbDev, - const uint8_t* HLInfo, - uint8_t HLInfoLen, - rfalIsoDepDevice* isoDepDev); - -/*! - ***************************************************************************** - * \brief ISO-DEP Poller Handle S(Parameters) - * - * This checks if PICC supports S(PARAMETERS), retrieves PICC's - * capabilities and sets the Bit Rate at the highest supported by both - * devices - * - * \param[out] isoDepDev : ISO-DEP information of the activated Listen device - * \param[in] maxTxBR : Maximum Tx bit rate supported by PCD - * \param[in] maxRxBR : Maximum Rx bit rate supported by PCD - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_TIMEOUT : Timeout error - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_NONE : No error, S(PARAMETERS) selection successful - ***************************************************************************** - */ -ReturnCode rfalIsoDepPollHandleSParameters( - rfalIsoDepDevice* isoDepDev, - rfalBitRate maxTxBR, - rfalBitRate maxRxBR); - -/*! - ***************************************************************************** - * \brief ISO-DEP Poller Start NFC-A Activation - * - * This starts a NFC-A Activation into ISO-DEP layer (ISO14443-4) with the given - * parameters. It sends RATS and if the higher bit rates are supported by - * both devices it additionally sends PPS - * Once Activated all details of the device are provided on isoDepDev - * - * - * \see rfalIsoDepPollAGetActivationStatus - * - * \param[in] FSDI : Frame Size Device Integer to be used - * \param[in] DID : Device ID to be used or RFAL_ISODEP_NO_DID for not use DID - * \param[in] maxBR : Max bit rate supported by the Poller - * \param[out] isoDepDev : ISO-DEP information of the activated Listen device - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_TIMEOUT : Timeout error - * \return ERR_PAR : Parity error detected - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_NONE : No error, start of asynchronous operation successful - ***************************************************************************** - */ -ReturnCode rfalIsoDepPollAStartActivation( - rfalIsoDepFSxI FSDI, - uint8_t DID, - rfalBitRate maxBR, - rfalIsoDepDevice* isoDepDev); - -/*! - ***************************************************************************** - * \brief ISO-DEP Poller Get NFC-A Activation Status - * - * Returns the activation status started by rfalIsoDepPollAStartActivation - * - * \see rfalIsoDepPollAStartActivation - * - * \return ERR_BUSY : Operation is ongoing - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_TIMEOUT : Timeout error - * \return ERR_PAR : Parity error detected - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_NONE : No error, activation successful - ***************************************************************************** - */ -ReturnCode rfalIsoDepPollAGetActivationStatus(void); - -/*! - ***************************************************************************** - * \brief ISO-DEP Poller Start NFC-B Activation - * - * This starts a NFC-B Activation into ISO-DEP layer (ISO14443-4) with the given - * parameters. It will send ATTRIB and calculate supported higher bit rates of both - * devices and perform activation. - * Once Activated all details of the device are provided on isoDepDev - * - * \see rfalIsoDepPollBGetActivationStatus - * - * \param[in] FSDI : Frame Size Device Integer to be used - * \param[in] DID : Device ID to be used or RFAL_ISODEP_NO_DID for not use DID - * \param[in] maxBR : Max bit rate supported by the Poller - * \param[in] PARAM1 : ATTRIB PARAM1 byte (communication parameters) - * \param[in] nfcbDev : pointer to the NFC-B Device containing the SENSB_RES - * \param[in] HLInfo : pointer to Higher layer INF (NULL if none) - * \param[in] HLInfoLen : Length HLInfo - * \param[out] isoDepDev : ISO-DEP information of the activated Listen device - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_TIMEOUT : Timeout error - * \return ERR_PAR : Parity error detected - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_NONE : No error, start of asynchronous operation successful - ***************************************************************************** - */ -ReturnCode rfalIsoDepPollBStartActivation( - rfalIsoDepFSxI FSDI, - uint8_t DID, - rfalBitRate maxBR, - uint8_t PARAM1, - const rfalNfcbListenDevice* nfcbDev, - const uint8_t* HLInfo, - uint8_t HLInfoLen, - rfalIsoDepDevice* isoDepDev); - -/*! - ***************************************************************************** - * \brief ISO-DEP Poller Get NFC-B Activation Status - * - * Returns the activation status started by rfalIsoDepPollBStartActivation - * - * \see rfalIsoDepPollBStartActivation - * - * \return ERR_BUSY : Operation is ongoing - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_TIMEOUT : Timeout error - * \return ERR_PAR : Parity error detected - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_NONE : No error, activation successful - ***************************************************************************** - */ -ReturnCode rfalIsoDepPollBGetActivationStatus(void); - -#endif /* RFAL_ISODEP_H_ */ - -/** - * @} - * - * @} - * - * @} - */ diff --git a/lib/ST25RFAL002/include/rfal_nfc.h b/lib/ST25RFAL002/include/rfal_nfc.h deleted file mode 100644 index 88a740a0a7b..00000000000 --- a/lib/ST25RFAL002/include/rfal_nfc.h +++ /dev/null @@ -1,425 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

    © COPYRIGHT 2020 STMicroelectronics

    - * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R391x firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file rfal_nfc.h - * - * \brief RFAL NFC device - * - * This module provides the required features to behave as an NFC Poller - * or Listener device. It grants an easy to use interface for the following - * activities: Technology Detection, Collision Resolution, Activation, - * Data Exchange, and Deactivation - * - * This layer is influenced by (but not fully aligned with) the NFC Forum - * specifications, in particular: Activity 2.0 and NCI 2.0 - * - * - * - * \addtogroup RFAL - * @{ - * - * \addtogroup RFAL-HL - * \brief RFAL Higher Layer - * @{ - * - * \addtogroup NFC - * \brief RFAL NFC Device - * @{ - * - */ - -#ifndef RFAL_NFC_H -#define RFAL_NFC_H - -/* -****************************************************************************** -* INCLUDES -****************************************************************************** -*/ -#include "platform.h" -#include "st_errno.h" -#include "rfal_rf.h" -#include "rfal_nfca.h" -#include "rfal_nfcb.h" -#include "rfal_nfcf.h" -#include "rfal_nfcv.h" -#include "rfal_st25tb.h" -#include "rfal_nfcDep.h" -#include "rfal_isoDep.h" - -/* -****************************************************************************** -* GLOBAL DEFINES -****************************************************************************** -*/ - -#define RFAL_NFC_TECH_NONE 0x0000U /*!< No technology */ -#define RFAL_NFC_POLL_TECH_A 0x0001U /*!< NFC-A technology Flag */ -#define RFAL_NFC_POLL_TECH_B 0x0002U /*!< NFC-B technology Flag */ -#define RFAL_NFC_POLL_TECH_F 0x0004U /*!< NFC-F technology Flag */ -#define RFAL_NFC_POLL_TECH_V 0x0008U /*!< NFC-V technology Flag */ -#define RFAL_NFC_POLL_TECH_AP2P 0x0010U /*!< AP2P technology Flag */ -#define RFAL_NFC_POLL_TECH_ST25TB 0x0020U /*!< ST25TB technology Flag */ -#define RFAL_NFC_LISTEN_TECH_A 0x1000U /*!< NFC-V technology Flag */ -#define RFAL_NFC_LISTEN_TECH_B 0x2000U /*!< NFC-V technology Flag */ -#define RFAL_NFC_LISTEN_TECH_F 0x4000U /*!< NFC-V technology Flag */ -#define RFAL_NFC_LISTEN_TECH_AP2P 0x8000U /*!< NFC-V technology Flag */ - -/* -****************************************************************************** -* GLOBAL MACROS -****************************************************************************** -*/ - -/*! Checks if a device is currently activated */ -#define rfalNfcIsDevActivated(st) \ - (((st) >= RFAL_NFC_STATE_ACTIVATED) && ((st) < RFAL_NFC_STATE_DEACTIVATION)) - -/*! Checks if a device is in discovery */ -#define rfalNfcIsInDiscovery(st) \ - (((st) >= RFAL_NFC_STATE_START_DISCOVERY) && ((st) < RFAL_NFC_STATE_ACTIVATED)) - -/*! Checks if remote device is in Poll mode */ -#define rfalNfcIsRemDevPoller(tp) \ - (((tp) >= RFAL_NFC_POLL_TYPE_NFCA) && ((tp) <= RFAL_NFC_POLL_TYPE_AP2P)) - -/*! Checks if remote device is in Listen mode */ -#define rfalNfcIsRemDevListener(tp) \ - (((int16_t)(tp) >= (int16_t)RFAL_NFC_LISTEN_TYPE_NFCA) && ((tp) <= RFAL_NFC_LISTEN_TYPE_AP2P)) - -/* -****************************************************************************** -* GLOBAL ENUMS -****************************************************************************** -*/ - -/* -****************************************************************************** -* GLOBAL TYPES -****************************************************************************** -*/ - -/*! Main state */ -typedef enum { - RFAL_NFC_STATE_NOTINIT = 0, /*!< Not Initialized state */ - RFAL_NFC_STATE_IDLE = 1, /*!< Initialize state */ - RFAL_NFC_STATE_START_DISCOVERY = 2, /*!< Start Discovery loop state */ - RFAL_NFC_STATE_WAKEUP_MODE = 3, /*!< Wake-Up state */ - RFAL_NFC_STATE_POLL_TECHDETECT = 10, /*!< Technology Detection state */ - RFAL_NFC_STATE_POLL_COLAVOIDANCE = 11, /*!< Collision Avoidance state */ - RFAL_NFC_STATE_POLL_SELECT = 12, /*!< Wait for Selection state */ - RFAL_NFC_STATE_POLL_ACTIVATION = 13, /*!< Activation state */ - RFAL_NFC_STATE_LISTEN_TECHDETECT = 20, /*!< Listen Tech Detect */ - RFAL_NFC_STATE_LISTEN_COLAVOIDANCE = 21, /*!< Listen Collision Avoidance */ - RFAL_NFC_STATE_LISTEN_ACTIVATION = 22, /*!< Listen Activation state */ - RFAL_NFC_STATE_LISTEN_SLEEP = 23, /*!< Listen Sleep state */ - RFAL_NFC_STATE_ACTIVATED = 30, /*!< Activated state */ - RFAL_NFC_STATE_DATAEXCHANGE = 31, /*!< Data Exchange Start state */ - RFAL_NFC_STATE_DATAEXCHANGE_DONE = 33, /*!< Data Exchange terminated */ - RFAL_NFC_STATE_DEACTIVATION = 34 /*!< Deactivation state */ -} rfalNfcState; - -/*! Device type */ -typedef enum { - RFAL_NFC_LISTEN_TYPE_NFCA = 0, /*!< NFC-A Listener device type */ - RFAL_NFC_LISTEN_TYPE_NFCB = 1, /*!< NFC-B Listener device type */ - RFAL_NFC_LISTEN_TYPE_NFCF = 2, /*!< NFC-F Listener device type */ - RFAL_NFC_LISTEN_TYPE_NFCV = 3, /*!< NFC-V Listener device type */ - RFAL_NFC_LISTEN_TYPE_ST25TB = 4, /*!< ST25TB Listener device type */ - RFAL_NFC_LISTEN_TYPE_AP2P = 5, /*!< AP2P Listener device type */ - RFAL_NFC_POLL_TYPE_NFCA = 10, /*!< NFC-A Poller device type */ - RFAL_NFC_POLL_TYPE_NFCB = 11, /*!< NFC-B Poller device type */ - RFAL_NFC_POLL_TYPE_NFCF = 12, /*!< NFC-F Poller device type */ - RFAL_NFC_POLL_TYPE_NFCV = 13, /*!< NFC-V Poller device type */ - RFAL_NFC_POLL_TYPE_AP2P = 15 /*!< AP2P Poller device type */ -} rfalNfcDevType; - -/*! Device interface */ -typedef enum { - RFAL_NFC_INTERFACE_RF = 0, /*!< RF Frame interface */ - RFAL_NFC_INTERFACE_ISODEP = 1, /*!< ISO-DEP interface */ - RFAL_NFC_INTERFACE_NFCDEP = 2 /*!< NFC-DEP interface */ -} rfalNfcRfInterface; - -/*! Device struct containing all its details */ -typedef struct { - rfalNfcDevType type; /*!< Device's type */ - union { /* PRQA S 0750 # MISRA 19.2 - Members of the union will not be used concurrently, only one technology at a time */ - rfalNfcaListenDevice nfca; /*!< NFC-A Listen Device instance */ - rfalNfcbListenDevice nfcb; /*!< NFC-B Listen Device instance */ - rfalNfcfListenDevice nfcf; /*!< NFC-F Listen Device instance */ - rfalNfcvListenDevice nfcv; /*!< NFC-V Listen Device instance */ - rfalSt25tbListenDevice st25tb; /*!< ST25TB Listen Device instance*/ - } dev; /*!< Device's instance */ - - uint8_t* nfcid; /*!< Device's NFCID */ - uint8_t nfcidLen; /*!< Device's NFCID length */ - rfalNfcRfInterface rfInterface; /*!< Device's interface */ - - union { /* PRQA S 0750 # MISRA 19.2 - Members of the union will not be used concurrently, only one protocol at a time */ - rfalIsoDepDevice isoDep; /*!< ISO-DEP instance */ - rfalNfcDepDevice nfcDep; /*!< NFC-DEP instance */ - } proto; /*!< Device's protocol */ -} rfalNfcDevice; - -/*! Discovery parameters */ -typedef struct { - rfalComplianceMode compMode; /*!< Compliance mode to be used */ - uint16_t techs2Find; /*!< Technologies to search for */ - uint16_t totalDuration; /*!< Duration of a whole Poll + Listen cycle */ - uint8_t devLimit; /*!< Max number of devices */ - rfalBitRate maxBR; /*!< Max Bit rate to be used for communications */ - - rfalBitRate nfcfBR; /*!< Bit rate to poll for NFC-F */ - uint8_t - nfcid3[RFAL_NFCDEP_NFCID3_LEN]; /*!< NFCID3 to be used on the ATR_REQ/ATR_RES */ - uint8_t GB[RFAL_NFCDEP_GB_MAX_LEN]; /*!< General bytes to be used on the ATR-REQ */ - uint8_t GBLen; /*!< Length of the General Bytes */ - rfalBitRate ap2pBR; /*!< Bit rate to poll for AP2P */ - - rfalLmConfPA lmConfigPA; /*!< Configuration for Passive Listen mode NFC-A */ - rfalLmConfPF lmConfigPF; /*!< Configuration for Passive Listen mode NFC-A */ - - void (*notifyCb)(rfalNfcState st); /*!< Callback to Notify upper layer */ - - bool wakeupEnabled; /*!< Enable Wake-Up mode before polling */ - bool wakeupConfigDefault; /*!< Wake-Up mode default configuration */ - rfalWakeUpConfig wakeupConfig; /*!< Wake-Up mode configuration */ - - bool activate_after_sak; // Set device to Active mode after SAK response -} rfalNfcDiscoverParam; - -/*! Buffer union, only one interface is used at a time */ -typedef union { /* PRQA S 0750 # MISRA 19.2 - Members of the union will not be used concurrently, only one interface at a time */ - uint8_t rfBuf[RFAL_FEATURE_NFC_RF_BUF_LEN]; /*!< RF buffer */ - rfalIsoDepApduBufFormat isoDepBuf; /*!< ISO-DEP buffer format (with header/prologue) */ - rfalNfcDepPduBufFormat nfcDepBuf; /*!< NFC-DEP buffer format (with header/prologue) */ -} rfalNfcBuffer; - -/*******************************************************************************/ - -/* -****************************************************************************** -* GLOBAL FUNCTION PROTOTYPES -****************************************************************************** -*/ - -/*! - ***************************************************************************** - * \brief RFAL NFC Worker - * - * It runs the internal state machine and runs the RFAL RF worker. - ***************************************************************************** - */ -void rfalNfcWorker(void); - -/*! - ***************************************************************************** - * \brief RFAL NFC Initialize - * - * It initializes this module and its dependencies - * - * \return ERR_WRONG_STATE : Incorrect state for this operation - * \return ERR_IO : Generic internal error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalNfcInitialize(void); - -/*! - ***************************************************************************** - * \brief RFAL NFC Discovery - * - * It set the device in Discovery state. - * In discovery it will Poll and/or Listen for the technologies configured, - * and perform Wake-up mode if configured to do so. - * - * The device list passed on disParams must not be empty. - * The number of devices on the list is indicated by the devLimit and shall - * be at >= 1. - * - * \param[in] disParams : discovery configuration parameters - * - * \return ERR_WRONG_STATE : Incorrect state for this operation - * \return ERR_PARAM : Invalid parameters - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalNfcDiscover(const rfalNfcDiscoverParam* disParams); - -/*! - ***************************************************************************** - * \brief RFAL NFC Get State - * - * It returns the current state - * - * \return rfalNfcState : the current state - ***************************************************************************** - */ -rfalNfcState rfalNfcGetState(void); - -/*! - ***************************************************************************** - * \brief RFAL NFC Get Devices Found - * - * It returns the location of the device list and the number of - * devices found. - * - * \param[out] devList : device list location - * \param[out] devCnt : number of devices found - * - * \return ERR_WRONG_STATE : Incorrect state for this operation - * Discovery still ongoing - * \return ERR_PARAM : Invalid parameters - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalNfcGetDevicesFound(rfalNfcDevice** devList, uint8_t* devCnt); - -/*! - ***************************************************************************** - * \brief RFAL NFC Get Active Device - * - * It returns the location of the device current Active device - * - * \param[out] dev : device info location - * - * \return ERR_WRONG_STATE : Incorrect state for this operation - * No device activated - * \return ERR_PARAM : Invalid parameters - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalNfcGetActiveDevice(rfalNfcDevice** dev); - -/*! - ***************************************************************************** - * \brief RFAL NFC Select Device - * - * It selects the device to be activated. - * It shall be called when more than one device has been identified to - * indiacte which device shall be active - * - * \param[in] devIdx : device index to be activated - * - * \return ERR_WRONG_STATE : Incorrect state for this operation - * Not in select state - * \return ERR_PARAM : Invalid parameters - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalNfcSelect(uint8_t devIdx); - -/*! - ***************************************************************************** - * \brief RFAL NFC Start Data Exchange - * - * After a device has been activated, it starts a data exchange. - * It handles automatically which interface/protocol to be used and acts accordingly. - * - * In Listen mode the first frame/data shall be sent by the Reader/Initiator - * therefore this method must be called first with txDataLen set to zero - * to retrieve the rxData and rcvLen locations. - * - * - * \param[in] txData : data to be transmitted - * \param[in] txDataLen : size of the data to be transmitted - * \param[out] rxData : location of the received data after operation is completed - * \param[out] rvdLen : location of thelength of the received data - * \param[in] fwt : FWT to be used in case of RF interface. - * If ISO-DEP or NFC-DEP interface is used, this will be ignored - * - * \return ERR_WRONG_STATE : Incorrect state for this operation - * \return ERR_PARAM : Invalid parameters - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalNfcDataExchangeStart( - uint8_t* txData, - uint16_t txDataLen, - uint8_t** rxData, - uint16_t** rvdLen, - uint32_t fwt, - uint32_t tx_flag); - -ReturnCode rfalNfcDataExchangeCustomStart( - uint8_t* txData, - uint16_t txDataLen, - uint8_t** rxData, - uint16_t** rvdLen, - uint32_t fwt, - uint32_t flags); - -/*! - ***************************************************************************** - * \brief RFAL NFC Get Data Exchange Status - * - * Gets current Data Exchange status - * - * \return ERR_NONE : Transceive done with no error - * \return ERR_BUSY : Transceive ongoing - * \return ERR_AGAIN : received one chaining block, copy received data - * and continue to call this method to retrieve the - * remaining blocks - * \return ERR_XXXX : Error occurred - * \return ERR_TIMEOUT : No response - * \return ERR_FRAMING : Framing error detected - * \return ERR_PAR : Parity error detected - * \return ERR_CRC : CRC error detected - * \return ERR_LINK_LOSS : Link Loss - External Field is Off - * \return ERR_RF_COLLISION : Collision detected - * \return ERR_IO : Internal error - ***************************************************************************** - */ -ReturnCode rfalNfcDataExchangeGetStatus(void); - -/*! - ***************************************************************************** - * \brief RFAL NFC Deactivate - * - * It triggers the deactivation procedure to terminate communications with - * remote device. At the end the field will be turned off. - * - * \param[in] discovery : TRUE if after deactivation go back into discovery - * : FALSE if after deactivation remain in idle - * - * \return ERR_WRONG_STATE : Incorrect state for this operation - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalNfcDeactivate(bool discovery); - -#endif /* RFAL_NFC_H */ - -/** - * @} - * - * @} - * - * @} - */ diff --git a/lib/ST25RFAL002/include/rfal_nfcDep.h b/lib/ST25RFAL002/include/rfal_nfcDep.h deleted file mode 100644 index 9cf770e53b7..00000000000 --- a/lib/ST25RFAL002/include/rfal_nfcDep.h +++ /dev/null @@ -1,830 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

    © COPYRIGHT 2020 STMicroelectronics

    - * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R391x firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file rfal_nfcDep.h - * - * \author Gustavo Patricio - * - * \brief Implementation of NFC-DEP protocol - * - * NFC-DEP is also known as NFCIP - Near Field Communication - * Interface and Protocol - * - * This implementation was based on the following specs: - * - NFC Forum Digital 1.1 - * - ECMA 340 3rd Edition 2013 - * - * - * \addtogroup RFAL - * @{ - * - * \addtogroup RFAL-AL - * \brief RFAL Abstraction Layer - * @{ - * - * \addtogroup NFC-DEP - * \brief RFAL NFC-DEP Module - * @{ - */ - -#ifndef RFAL_NFCDEP_H_ -#define RFAL_NFCDEP_H_ - -/* - ****************************************************************************** - * INCLUDES - ****************************************************************************** - */ -#include "platform.h" -#include "st_errno.h" -#include "rfal_rf.h" - -/* - ****************************************************************************** - * ENABLE SWITCH - ****************************************************************************** - */ - -#ifndef RFAL_FEATURE_NFC_DEP -#define RFAL_FEATURE_NFC_DEP \ - false /*!< NFC-DEP module configuration missing. Disabled by default */ -#endif - -/* If module is disabled remove the need for the user to set lengths */ -#if !RFAL_FEATURE_NFC_DEP -#undef RFAL_FEATURE_NFC_DEP_BLOCK_MAX_LEN -#undef RFAL_FEATURE_NFC_DEP_PDU_MAX_LEN - -#define RFAL_FEATURE_NFC_DEP_BLOCK_MAX_LEN 1U /*!< NFC-DEP Block/Payload length, set to "none" */ -#define RFAL_FEATURE_NFC_DEP_PDU_MAX_LEN 1U /*!< NFC-DEP PDU length, set to "none" */ -#endif /* !RFAL_FEATURE_NFC_DEP */ - -/* - ****************************************************************************** - * DEFINES - ****************************************************************************** - */ -#define RFAL_NFCDEP_FRAME_SIZE_MAX_LEN \ - 254U /*!< Maximum Frame Size Digital 2.0 Table 90 */ -#define RFAL_NFCDEP_DEPREQ_HEADER_LEN \ - 5U /*!< DEP_REQ header length: CMD_TYPE + CMD_CMD + PBF + DID + NAD */ - -/*! Length NFCIP DEP REQ or RES header (incl LEN) */ -#define RFAL_NFCDEP_DEP_HEADER \ - (RFAL_NFCDEP_LEN_LEN + RFAL_NFCDEP_CMDTYPE_LEN + RFAL_NFCDEP_CMD_LEN + RFAL_NFCDEP_DEP_PFB_LEN) -#define RFAL_NFCDEP_HEADER \ - (RFAL_NFCDEP_CMDTYPE_LEN + RFAL_NFCDEP_CMD_LEN) /*!< NFCIP header length */ -#define RFAL_NFCDEP_SB_LEN \ - 1U /*!< SB length on NFCIP fram for NFC-A */ -#define RFAL_NFCDEP_LEN_LEN \ - 1U /*!< LEN length on NFCIP frame */ -#define RFAL_NFCDEP_CMDTYPE_LEN \ - 1U /*!< Length of the cmd type (REQ | RES) on NFCIP frame */ -#define RFAL_NFCDEP_CMD_LEN \ - 1U /*!< Length of the cmd on NFCIP frame */ -#define RFAL_NFCDEP_DID_LEN \ - 1U /*!< Length of did on NFCIP frame */ -#define RFAL_NFCDEP_DEP_PFB_LEN \ - 1U /*!< Length of the PFB field on NFCIP frame */ - -#define RFAL_NFCDEP_DSL_RLS_LEN_NO_DID \ - (RFAL_NFCDEP_LEN_LEN + RFAL_NFCDEP_CMDTYPE_LEN + \ - RFAL_NFCDEP_CMD_LEN) /*!< Length of DSL_REQ and RLS_REQ with no DID */ -#define RFAL_NFCDEP_DSL_RLS_LEN_DID \ - (RFAL_NFCDEP_DSL_RLS_LEN_NO_DID + \ - RFAL_NFCDEP_DID_LEN) /*!< Length of DSL_REQ and RLS_REQ with DID */ - -#define RFAL_NFCDEP_FS_VAL_MIN \ - 64U /*!< Minimum LR value */ -#define RFAL_NFCDEP_LR_VAL_MASK \ - 0x03U /*!< Bit mask for a LR value */ -#define RFAL_NFCDEP_PP_LR_MASK \ - 0x30U /*!< Bit mask for LR value in PP byte on a ATR REQ/RES */ -#define RFAL_NFCDEP_PP_LR_SHIFT \ - 4U /*!< Position of LR value in PP byte on a ATR REQ/RES */ - -#define RFAL_NFCDEP_DID_MAX \ - 14U /*!< Max DID value Digital 14.6.2.3 */ -#define RFAL_NFCDEP_DID_KEEP \ - 0xFFU /*!< Keep DID value already configured */ -#define RFAL_NFCDEP_DID_NO \ - 0x00U /*!< No DID shall be used */ -#define RFAL_NFCDEP_NAD_NO \ - 0x00U /*!< No NAD shall be used */ - -#define RFAL_NFCDEP_OPER_RTOX_REQ_DIS \ - 0x01U /*!< Operation config: RTOX REQ disable */ -#define RFAL_NFCDEP_OPER_RTOX_REQ_EN \ - 0x00U /*!< Operation config: RTOX REQ enable */ - -#define RFAL_NFCDEP_OPER_ATN_DIS \ - 0x00U /*!< Operation config: ATN disable */ -#define RFAL_NFCDEP_OPER_ATN_EN \ - 0x02U /*!< Operation config: ATN enable */ - -#define RFAL_NFCDEP_OPER_EMPTY_DEP_DIS \ - 0x04U /*!< Operation config: empty DEPs disable */ -#define RFAL_NFCDEP_OPER_EMPTY_DEP_EN \ - 0x00U /*!< Operation config: empty DEPs enable */ - -#define RFAL_NFCDEP_OPER_FULL_MI_DIS \ - 0x00U /*!< Operation config: full chaining DEPs disable */ -#define RFAL_NFCDEP_OPER_FULL_MI_EN \ - 0x08U /*!< Operation config: full chaining DEPs enable */ - -#define RFAL_NFCDEP_BRS_MAINTAIN \ - 0xC0U /*!< Value signalling that BR is to be maintained (no PSL) */ -#define RFAL_NFCDEP_BRS_Dx_MASK \ - 0x07U /*!< Value signalling that BR is to be maintained (no PSL) */ -#define RFAL_NFCDEP_BRS_DSI_POS \ - 3U /*!< Value signalling that BR is to be maintained (no PSL) */ - -#define RFAL_NFCDEP_WT_DELTA \ - (16U - RFAL_NFCDEP_WT_DELTA_ADJUST) /*!< NFC-DEP dWRT (adjusted) Digital 2.0 B.10 */ -#define RFAL_NFCDEP_WT_DELTA_ADJUST \ - 4U /*!< dWRT value adjustment */ - -#define RFAL_NFCDEP_ATR_REQ_NFCID3_POS \ - 2U /*!< NFCID3 offset in ATR_REQ frame */ -#define RFAL_NFCDEP_NFCID3_LEN \ - 10U /*!< NFCID3 Length */ - -#define RFAL_NFCDEP_LEN_MIN \ - 3U /*!< Minimum length byte LEN value */ -#define RFAL_NFCDEP_LEN_MAX \ - 255U /*!< Maximum length byte LEN value */ - -#define RFAL_NFCDEP_ATRRES_HEADER_LEN \ - 2U /*!< ATR RES Header Len: CmdType: 0xD5 + Cod: 0x01 */ -#define RFAL_NFCDEP_ATRRES_MIN_LEN \ - 17U /*!< Minimum length for an ATR RES */ -#define RFAL_NFCDEP_ATRRES_MAX_LEN \ - 64U /*!< Maximum length for an ATR RES Digital 1.0 14.6.1 */ -#define RFAL_NFCDEP_ATRREQ_MIN_LEN \ - 16U /*!< Minimum length for an ATR REQ */ -#define RFAL_NFCDEP_ATRREQ_MAX_LEN \ - RFAL_NFCDEP_ATRRES_MAX_LEN /*!< Maximum length for an ATR REQ Digital 1.0 14.6.1 */ - -#define RFAL_NFCDEP_GB_MAX_LEN \ - (RFAL_NFCDEP_ATRREQ_MAX_LEN - \ - RFAL_NFCDEP_ATRREQ_MIN_LEN) /*!< Maximum length the General Bytes on ATR Digital 1.1 16.6.3 */ - -#define RFAL_NFCDEP_WT_INI_DEFAULT \ - RFAL_NFCDEP_WT_INI_MAX /*!< WT Initiator default value Digital 1.0 14.6.3.8 */ -#define RFAL_NFCDEP_WT_INI_MIN 0U /*!< WT Initiator minimum value Digital 1.0 14.6.3.8 */ -#define RFAL_NFCDEP_WT_INI_MAX 14U /*!< WT Initiator maximum value Digital 1.0 14.6.3.8 A.10 */ -#define RFAL_NFCDEP_RWT_INI_MAX \ - rfalNfcDepWT2RWT(RFAL_NFCDEP_WT_INI_MAX) /*!< RWT Initiator maximum value */ - -#define RFAL_NFCDEP_WT_TRG_MAX_D10 8U /*!< WT target max Digital 1.0 14.6.3.8 A.10 */ -#define RFAL_NFCDEP_WT_TRG_MAX_D11 14U /*!< WT target max Digital 1.1 16.6.3.9 A.9 */ -#define RFAL_NFCDEP_WT_TRG_MAX_L13 10U /*!< WT target max [LLCP] 1.3 6.2.1 */ -#define RFAL_NFCDEP_WT_TRG_MAX \ - RFAL_NFCDEP_WT_TRG_MAX_D11 /*!< WT target max Digital x.x | LLCP x.x */ -#define RFAL_NFCDEP_RWT_TRG_MAX \ - rfalNfcDepWT2RWT(RFAL_NFCDEP_WT_TRG_MAX) /*!< RWT Initiator maximum value */ - -/*! Maximum Frame Waiting Time = ((256 * 16/fc)*2^FWImax) = ((256*16/fc)*2^14) = (1048576 / 64)/fc = (100000h*64)/fc */ -#define RFAL_NFCDEP_MAX_FWT ((uint32_t)1U << 20) - -#define RFAL_NFCDEP_WT_MASK \ - 0x0FU /*!< Bit mask for the Wait Time value */ - -#define RFAL_NFCDEP_BR_MASK_106 \ - 0x01U /*!< Enable mask bit rate 106 */ -#define RFAL_NFCDEP_BR_MASK_212 \ - 0x02U /*!< Enable mask bit rate 242 */ -#define RFAL_NFCDEP_BR_MASK_424 \ - 0x04U /*!< Enable mask bit rate 424 */ - -/* - ****************************************************************************** - * GLOBAL MACROS - ****************************************************************************** - */ - -#define rfalNfcDepWT2RWT(wt) \ - ((uint32_t)1U \ - << (((uint32_t)(wt)&RFAL_NFCDEP_WT_MASK) + \ - 12U)) /*!< Converts WT value to RWT (1/fc) */ - -/*! Returns the BRS value from the given bit rate */ -#define rfalNfcDepDx2BRS(br) \ - ((((uint8_t)(br)&RFAL_NFCDEP_BRS_Dx_MASK) << RFAL_NFCDEP_BRS_DSI_POS) | \ - ((uint8_t)(br)&RFAL_NFCDEP_BRS_Dx_MASK)) - -#define rfalNfcDepBRS2DRI(brs) \ - (uint8_t)(( \ - uint8_t)(brs)&RFAL_NFCDEP_BRS_Dx_MASK) /*!< Returns the DRI value from the given BRS byte */ -#define rfalNfcDepBRS2DSI(brs) \ - (uint8_t)( \ - ((uint8_t)(brs) >> RFAL_NFCDEP_BRS_DSI_POS) & \ - RFAL_NFCDEP_BRS_Dx_MASK) /*!< Returns the DSI value from the given BRS byte */ - -#define rfalNfcDepPP2LR(PPx) \ - (((uint8_t)(PPx)&RFAL_NFCDEP_PP_LR_MASK) >> \ - RFAL_NFCDEP_PP_LR_SHIFT) /*!< Returns the LR value from the given PPx byte */ -#define rfalNfcDepLR2PP(LRx) \ - (((uint8_t)(LRx) << RFAL_NFCDEP_PP_LR_SHIFT) & \ - RFAL_NFCDEP_PP_LR_MASK) /*!< Returns the PP byte with the given LRx value */ - -/*! Returns the Frame size value from the given LRx value */ -#define rfalNfcDepLR2FS(LRx) \ - (uint16_t)( \ - MIN((RFAL_NFCDEP_FS_VAL_MIN * ((uint16_t)(LRx) + 1U)), RFAL_NFCDEP_FRAME_SIZE_MAX_LEN)) - -/*! - * Despite DIGITAL 1.0 14.6.2.1 stating that the last two bytes may filled with - * any value, some devices (Samsung Google Nexus) only accept when these are 0 */ -#define rfalNfcDepSetNFCID(dst, src, len) \ - ST_MEMSET((dst), 0x00, RFAL_NFCDEP_NFCID3_LEN); \ - if((len) > 0U) { \ - ST_MEMCPY((dst), (src), (len)); \ - } - -/* - ****************************************************************************** - * GLOBAL ENUMERATIONS - ****************************************************************************** - */ - -/*! Enumeration of NFC-DEP bit rate in ATR Digital 1.0 Table 93 and 94 */ -enum { - RFAL_NFCDEP_Bx_NO_HIGH_BR = 0x00, /*!< Peer supports no high bit rates */ - RFAL_NFCDEP_Bx_08_848 = 0x01, /*!< Peer also supports 848 */ - RFAL_NFCDEP_Bx_16_1695 = 0x02, /*!< Peer also supports 1695 */ - RFAL_NFCDEP_Bx_32_3390 = 0x04, /*!< Peer also supports 3390 */ - RFAL_NFCDEP_Bx_64_6780 = 0x08 /*!< Peer also supports 6780 */ -}; - -/*! Enumeration of NFC-DEP bit rate Divider in PSL Digital 1.0 Table 100 */ -enum { - RFAL_NFCDEP_Dx_01_106 = RFAL_BR_106, /*!< Divisor D = 1 : bit rate = 106 */ - RFAL_NFCDEP_Dx_02_212 = RFAL_BR_212, /*!< Divisor D = 2 : bit rate = 212 */ - RFAL_NFCDEP_Dx_04_424 = RFAL_BR_424, /*!< Divisor D = 4 : bit rate = 424 */ - RFAL_NFCDEP_Dx_08_848 = RFAL_BR_848, /*!< Divisor D = 8 : bit rate = 848 */ - RFAL_NFCDEP_Dx_16_1695 = RFAL_BR_1695, /*!< Divisor D = 16 : bit rate = 1695 */ - RFAL_NFCDEP_Dx_32_3390 = RFAL_BR_3390, /*!< Divisor D = 32 : bit rate = 3390 */ - RFAL_NFCDEP_Dx_64_6780 = RFAL_BR_6780 /*!< Divisor D = 64 : bit rate = 6780 */ -}; - -/*! Enumeration of NFC-DEP Length Reduction (LR) Digital 1.0 Table 91 */ -enum { - RFAL_NFCDEP_LR_64 = 0x00, /*!< Maximum payload size is 64 bytes */ - RFAL_NFCDEP_LR_128 = 0x01, /*!< Maximum payload size is 128 bytes */ - RFAL_NFCDEP_LR_192 = 0x02, /*!< Maximum payload size is 192 bytes */ - RFAL_NFCDEP_LR_254 = 0x03 /*!< Maximum payload size is 254 bytes */ -}; - -/* - ****************************************************************************** - * GLOBAL DATA TYPES - ****************************************************************************** - */ - -/*! NFC-DEP callback to check if upper layer has deactivation pending */ -typedef bool (*rfalNfcDepDeactCallback)(void); - -/*! Enumeration of the nfcip communication modes */ -typedef enum { - RFAL_NFCDEP_COMM_PASSIVE, /*!< Passive communication mode */ - RFAL_NFCDEP_COMM_ACTIVE /*!< Active communication mode */ -} rfalNfcDepCommMode; - -/*! Enumeration of the nfcip roles */ -typedef enum { - RFAL_NFCDEP_ROLE_INITIATOR, /*!< Perform as Initiator */ - RFAL_NFCDEP_ROLE_TARGET /*!< Perform as Target */ -} rfalNfcDepRole; - -/*! Struct that holds all NFCIP configs */ -typedef struct { - rfalNfcDepRole role; /*!< Current NFCIP role */ - rfalNfcDepCommMode commMode; /*!< Current NFCIP communication mode */ - uint8_t oper; /*!< Operation config similar to NCI 1.0 Table 81 */ - - uint8_t did; /*!< Current Device ID (DID) */ - uint8_t nad; /*!< Current Node Addressing (NAD) */ - uint8_t bs; /*!< Bit rate in Sending Direction */ - uint8_t br; /*!< Bit rate in Receiving Direction */ - uint8_t nfcid[RFAL_NFCDEP_NFCID3_LEN]; /*!< Pointer to the NFCID to be used */ - uint8_t nfcidLen; /*!< Length of the given NFCID in nfcid */ - uint8_t gb[RFAL_NFCDEP_GB_MAX_LEN]; /*!< Pointer General Bytes (GB) to be used */ - uint8_t gbLen; /*!< Length of the given GB in gb */ - uint8_t lr; /*!< Length Reduction (LR) to be used */ - uint8_t to; /*!< Timeout (TO) to be used */ - uint32_t fwt; /*!< Frame Waiting Time (FWT) to be used */ - uint32_t dFwt; /*!< Delta Frame Waiting Time (dFWT) to be used */ -} rfalNfcDepConfigs; - -/*! ATR_REQ command Digital 1.1 16.6.2 */ -typedef struct { - uint8_t CMD1; /*!< Command format 0xD4 */ - uint8_t CMD2; /*!< Command Value */ - uint8_t NFCID3[RFAL_NFCDEP_NFCID3_LEN]; /*!< NFCID3 value */ - uint8_t DID; /*!< DID */ - uint8_t BSi; /*!< Sending Bitrate for Initiator */ - uint8_t BRi; /*!< Receiving Bitrate for Initiator */ - uint8_t PPi; /*!< Optional Parameters presence indicator */ - uint8_t GBi[RFAL_NFCDEP_GB_MAX_LEN]; /*!< General Bytes */ -} rfalNfcDepAtrReq; - -/*! ATR_RES response Digital 1.1 16.6.3 */ -typedef struct { - uint8_t CMD1; /*!< Response Byte 0xD5 */ - uint8_t CMD2; /*!< Command Value */ - uint8_t NFCID3[RFAL_NFCDEP_NFCID3_LEN]; /*!< NFCID3 value */ - uint8_t DID; /*!< DID */ - uint8_t BSt; /*!< Sending Bitrate for Initiator */ - uint8_t BRt; /*!< Receiving Bitrate for Initiator */ - uint8_t TO; /*!< Timeout */ - uint8_t PPt; /*!< Optional Parameters presence indicator */ - uint8_t GBt[RFAL_NFCDEP_GB_MAX_LEN]; /*!< General Bytes */ -} rfalNfcDepAtrRes; - -/*! Structure of transmit I-PDU Buffer format from caller */ -typedef struct { - uint8_t prologue[RFAL_NFCDEP_DEPREQ_HEADER_LEN]; /*!< Prologue space for NFC-DEP header*/ - uint8_t inf[RFAL_FEATURE_NFC_DEP_BLOCK_MAX_LEN]; /*!< INF | Data area of the buffer */ -} rfalNfcDepBufFormat; - -/*! Structure of APDU Buffer format from caller */ -typedef struct { - uint8_t prologue[RFAL_NFCDEP_DEPREQ_HEADER_LEN]; /*!< Prologue/SoD buffer */ - uint8_t pdu[RFAL_FEATURE_NFC_DEP_PDU_MAX_LEN]; /*!< Complete PDU/Payload buffer */ -} rfalNfcDepPduBufFormat; - -/*! Activation info as Initiator and Target */ -typedef union { /* PRQA S 0750 # MISRA 19.2 - Both members of the union will not be used concurrently , device is only initiatior or target a time. No problem can occur. */ - struct { - rfalNfcDepAtrRes ATR_RES; /*!< ATR RES (Initiator mode) */ - uint8_t ATR_RESLen; /*!< ATR RES length (Initiator mode) */ - } Target; /*!< Target */ - struct { - rfalNfcDepAtrReq ATR_REQ; /*!< ATR REQ (Target mode) */ - uint8_t ATR_REQLen; /*!< ATR REQ length (Target mode) */ - } Initiator; /*!< Initiator */ -} rfalNfcDepActivation; - -/*! NFC-DEP device Info */ -typedef struct { - uint8_t GBLen; /*!< General Bytes length */ - uint8_t WT; /*!< WT to be used (ignored in Listen Mode) */ - uint32_t FWT; /*!< FWT to be used (1/fc)(ignored Listen Mode) */ - uint32_t dFWT; /*!< Delta FWT to be used (1/fc) */ - uint8_t LR; /*!< Length Reduction coding the max payload */ - uint16_t FS; /*!< Frame Size */ - rfalBitRate DSI; /*!< Bit Rate coding from Initiator to Target */ - rfalBitRate DRI; /*!< Bit Rate coding from Target to Initiator */ - uint8_t DID; /*!< Device ID (RFAL_NFCDEP_DID_NO if no DID) */ - uint8_t NAD; /*!< Node ADdress (RFAL_NFCDEP_NAD_NO if no NAD)*/ -} rfalNfcDepInfo; - -/*! NFC-DEP Device structure */ -typedef struct { - rfalNfcDepActivation activation; /*!< Activation Info */ - rfalNfcDepInfo info; /*!< NFC-DEP device Info */ -} rfalNfcDepDevice; - -/*! NFCIP Protocol structure for P2P Target - * - * operParam : derives from NFC-Forum NCI NFC-DEP Operation Parameter - * NCI 1.1 Table 86: NFC-DEP Operation Parameter - * and it's a bit mask composed as: - * [ 0000b - * | Chain SHALL use max. Transport Data Byte[1b] - * | I-PDU with no Transport Data SHALL NOT be sent [1b] - * | NFC-DEP Target SHALL NOT send RTOX request [1b] - * ] - * - */ -typedef struct { - rfalNfcDepCommMode commMode; /*!< Initiator in Active P2P or Passive P2P*/ - uint8_t operParam; /*!< NFC-DEP Operation Parameter */ - uint8_t* nfcid; /*!< Initiator's NFCID2 or NFCID3 */ - uint8_t nfcidLen; /*!< Initiator's NFCID length (NFCID2/3) */ - uint8_t DID; /*!< Initiator's Device ID DID */ - uint8_t NAD; /*!< Initiator's Node ID NAD */ - uint8_t BS; /*!< Initiator's Bit Rates supported in Tx */ - uint8_t BR; /*!< Initiator's Bit Rates supported in Rx */ - uint8_t LR; /*!< Initiator's Length reduction */ - uint8_t* GB; /*!< Initiator's General Bytes (Gi) */ - uint8_t GBLen; /*!< Initiator's General Bytes length */ -} rfalNfcDepAtrParam; - -/*! Structure of parameters to be passed in for nfcDepListenStartActivation */ -typedef struct { - rfalNfcDepBufFormat* rxBuf; /*!< Receive Buffer struct reference */ - uint16_t* rxLen; /*!< Receive INF data length in bytes */ - bool* isRxChaining; /*!< Received data is not complete */ - rfalNfcDepDevice* nfcDepDev; /*!< NFC-DEP device info */ -} rfalNfcDepListenActvParam; - -/*! NFCIP Protocol structure for P2P Target - * - * operParam : derives from NFC-Forum NCI NFC-DEP Operation Parameter - * NCI 1.1 Table 86: NFC-DEP Operation Parameter - * and it's a bit mask composed as: - * [ 0000b - * | Chain SHALL use max. Transport Data Byte[1b] - * | I-PDU with no Transport Data SHALL NOT be sent [1b] - * | NFC-DEP Target SHALL NOT send RTOX request [1b] - * ] - * - */ -typedef struct { - rfalNfcDepCommMode commMode; /*!< Target in Active P2P or Passive P2P */ - uint8_t nfcid3[RFAL_NFCDEP_NFCID3_LEN]; /*!< Target's NFCID3 */ - uint8_t bst; /*!< Target's Bit Rates supported in Tx */ - uint8_t brt; /*!< Target's Bit Rates supported in Rx */ - uint8_t to; /*!< Target's timeout (TO) value */ - uint8_t ppt; /*!< Target's Presence optional Params(PPt)*/ - uint8_t GBt[RFAL_NFCDEP_GB_MAX_LEN]; /*!< Target's General Bytes (Gt) */ - uint8_t GBtLen; /*!< Target's General Bytes length */ - uint8_t operParam; /*!< NFC-DEP Operation Parameter */ -} rfalNfcDepTargetParam; - -/*! Structure of parameters to be passed in for nfcDepStartIpduTransceive */ -typedef struct { - rfalNfcDepBufFormat* txBuf; /*!< Transmit Buffer struct reference */ - uint16_t txBufLen; /*!< Transmit Buffer INF field length in bytes */ - bool isTxChaining; /*!< Transmit data is not complete */ - rfalNfcDepBufFormat* rxBuf; /*!< Receive Buffer struct reference */ - uint16_t* rxLen; /*!< Receive INF data length */ - bool* isRxChaining; /*!< Received data is not complete */ - uint32_t FWT; /*!< FWT to be used (ignored in Listen Mode) */ - uint32_t dFWT; /*!< Delta FWT to be used */ - uint16_t FSx; /*!< Other device Frame Size (FSD or FSC) */ - uint8_t DID; /*!< Device ID (RFAL_ISODEP_NO_DID if no DID) */ -} rfalNfcDepTxRxParam; - -/*! Structure of parameters used on NFC DEP PDU Transceive */ -typedef struct { - rfalNfcDepPduBufFormat* txBuf; /*!< Transmit Buffer struct reference */ - uint16_t txBufLen; /*!< Transmit Buffer INF field length in Bytes*/ - rfalNfcDepPduBufFormat* rxBuf; /*!< Receive Buffer struct reference in Bytes */ - uint16_t* rxLen; /*!< Received INF data length in Bytes */ - rfalNfcDepBufFormat* tmpBuf; /*!< Temp buffer for single PDUs (internal) */ - uint32_t FWT; /*!< FWT to be used (ignored in Listen Mode) */ - uint32_t dFWT; /*!< Delta FWT to be used */ - uint16_t FSx; /*!< Other device Frame Size (FSD or FSC) */ - uint8_t DID; /*!< Device ID (RFAL_ISODEP_NO_DID if no DID) */ -} rfalNfcDepPduTxRxParam; - -/* - * ***************************************************************************** - * GLOBAL VARIABLE DECLARATIONS - ****************************************************************************** - */ - -/* - ****************************************************************************** - * GLOBAL FUNCTION PROTOTYPES - ****************************************************************************** - */ - -/*! - ****************************************************************************** - * \brief NFCIP Initialize - * - * This method resets all NFC-DEP inner states, counters and context and sets - * default values - * - ****************************************************************************** - */ -void rfalNfcDepInitialize(void); - -/*! - ****************************************************************************** - * \brief Set deactivating callback - * - * Sets the deactivating callback so that nfcip layer can check if upper layer - * has a deactivation pending, and not perform error recovery upon specific - * errors - * - * \param[in] pFunc : method pointer to deactivation flag check - ****************************************************************************** - */ -void rfalNfcDepSetDeactivatingCallback(rfalNfcDepDeactCallback pFunc); - -/*! - ****************************************************************************** - * \brief Calculate Response Waiting Time - * - * Calculates the Response Waiting Time (RWT) from the given Waiting Time (WT) - * - * \param[in] wt : the WT value to calculate RWT - * - * \return RWT value in 1/fc - ****************************************************************************** - */ -uint32_t rfalNfcDepCalculateRWT(uint8_t wt); - -/*! - ****************************************************************************** - * \brief NFC-DEP Initiator ATR (Attribute Request) - * - * This method configures the NFC-DEP layer with given parameters and then - * sends an ATR to the Target with and checks for a valid response response - * - * \param[in] param : parameters to initialize and compose the ATR - * \param[out] atrRes : location to store the ATR_RES - * \param[out] atrResLen : length of the ATR_RES received - * - * \return ERR_NONE : No error - * \return ERR_TIMEOUT : Timeout occurred - * \return ERR_PROTO : Protocol error occurred - ****************************************************************************** - */ -ReturnCode - rfalNfcDepATR(const rfalNfcDepAtrParam* param, rfalNfcDepAtrRes* atrRes, uint8_t* atrResLen); - -/*! - ****************************************************************************** - * \brief NFC-DEP Initiator PSL (Parameter Selection) - * - * This method sends a PSL to the Target with the given parameters and checks - * for a valid response response - * - * The parameters must be coded according to Digital 1.1 16.7.1 - * - * \param[in] BRS : the selected Bit Rates for Initiator and Target - * \param[in] FSL : the maximum length of Commands and Responses - * - * \return ERR_NONE : No error - * \return ERR_TIMEOUT : Timeout occurred - * \return ERR_PROTO : Protocol error occurred - ****************************************************************************** - */ -ReturnCode rfalNfcDepPSL(uint8_t BRS, uint8_t FSL); - -/*! - ****************************************************************************** - * \brief NFC-DEP Initiator DSL (Deselect) - * - * This method checks if the NFCIP module is configured as initiator and if - * so sends a DSL REQ, waits the target's response and checks it - * - * In case of performing as target no action is taken - * - * \return ERR_NONE : No error - * \return ERR_TIMEOUT : Timeout occurred - * \return ERR_MAX_RERUNS : Timeout occurred - * \return ERR_PROTO : Protocol error occurred - ****************************************************************************** - */ -ReturnCode rfalNfcDepDSL(void); - -/*! - ****************************************************************************** - * \brief NFC-DEP Initiator RLS (Release) - * - * This method checks if the NFCIP module is configured as initiator and if - * so sends a RLS REQ, waits target's response and checks it - * - * In case of performing as target no action is taken - * - * \return ERR_NONE : No error - * \return ERR_TIMEOUT : Timeout occurred - * \return ERR_MAX_RERUNS : Timeout occurred - * \return ERR_PROTO : Protocol error occurred - ****************************************************************************** - */ -ReturnCode rfalNfcDepRLS(void); - -/*! - ***************************************************************************** - * \brief NFC-DEP Initiator Handle Activation - * - * This performs a Activation into NFC-DEP layer with the given - * parameters. It sends ATR_REQ and if the higher bit rates are supported by - * both devices it additionally sends PSL - * Once Activated all details of the device are provided on nfcDepDev - * - * \param[in] param : required parameters to initialize and send ATR_REQ - * \param[in] desiredBR : Desired bit rate supported by the Poller - * \param[out] nfcDepDev : NFC-DEP information of the activated Listen device - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_TIMEOUT : Timeout error - * \return ERR_PAR : Parity error detected - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_NONE : No error, activation successful - ***************************************************************************** - */ -ReturnCode rfalNfcDepInitiatorHandleActivation( - rfalNfcDepAtrParam* param, - rfalBitRate desiredBR, - rfalNfcDepDevice* nfcDepDev); - -/*! - ****************************************************************************** - * \brief Check if buffer contains valid ATR_REQ - * - * This method checks if the given ATR_REQ is valid - * - * - * \param[in] buf : buffer holding Initiator's received request - * \param[in] bufLen : size of the msg contained on the buf in Bytes - * \param[out] nfcid3 : pointer to where the NFCID3 may be outputted, - * nfcid3 has NFCF_SENSF_NFCID3_LEN as length - * Pass NULL if output parameter not desired - * - * \return true : Valid ATR_REQ received, the ATR_RES has been computed in txBuf - * \return false : Invalid protocol request - * - ****************************************************************************** - */ -bool rfalNfcDepIsAtrReq(const uint8_t* buf, uint16_t bufLen, uint8_t* nfcid3); - -/*! - ****************************************************************************** - * \brief Check is Target has received ATR - * - * This method checks if the NFCIP module is configured as target and if a - * ATR REQ has been received ( whether is in activation or in data exchange) - * - * \return true : a ATR has already been received - * \return false : no ATR has been received - ****************************************************************************** - */ -bool rfalNfcDepTargetRcvdATR(void); - -/*! - ***************************************************************************** - * \brief NFCDEP Start Listen Activation Handling - * - * Start Activation Handling and setup to receive first frame which may - * contain complete or partial DEP-REQ after activation is completed - * - * Pass in ATR_REQ for NFC-DEP to handle ATR_RES. The Activation Handling - * handles ATR_RES and PSL_RES if a PSL_REQ is received - * - * Activation is completed if PSL_RES is sent or if first I-PDU is received - * - * \ref rfalNfcDepListenGetActivationStatus() provide status of the - * ongoing activation - * - * \warning nfcDepGetTransceiveStatus() shall be called right after activation - * is completed (i.e. rfalNfcDepListenGetActivationStatus() return ERR_NONE) - * to check for first received frame. - * - * \param[in] param : Target parameters to be used - * \param[in] atrReq : reference to buffer containing ATR_REQ - * \param[in] atrReqLength: Length of ATR_REQ - * \param[out] rxParam : references to buffer, length and chaining indication - * for first complete LLCP to be received - * - * \return ERR_NONE : ATR_REQ is valid and activation ongoing - * \return ERR_PARAM : ATR_REQ or other params are invalid - * \return ERR_LINK_LOSS : Remote Field is turned off - ***************************************************************************** - */ -ReturnCode rfalNfcDepListenStartActivation( - const rfalNfcDepTargetParam* param, - const uint8_t* atrReq, - uint16_t atrReqLength, - rfalNfcDepListenActvParam rxParam); - -/*! - ***************************************************************************** - * \brief Get the current NFC-DEP Activation Status - * - * \return ERR_NONE : Activation has completed successfully - * \return ERR_BUSY : Activation is ongoing - * \return ERR_LINK_LOSS : Remote Field was turned off - ***************************************************************************** - */ -ReturnCode rfalNfcDepListenGetActivationStatus(void); - -/*! - ***************************************************************************** - * \brief Start Transceive - * - * Transceives a complete or partial DEP block - * - * The txBuf contains complete or partial of DEP to be transmitted. - * The Prologue field of the I-PDU is handled internally - * - * If the buffer contains partial LLCP and is not the last block, then - * isTxChaining must be set to true - * - * \param[in] param: reference parameters to be used for the Transceive - * - * \return ERR_PARAM : Bad request - * \return ERR_WRONG_STATE : The module is not in a proper state - * \return ERR_NONE : The Transceive request has been started - ***************************************************************************** - */ -ReturnCode rfalNfcDepStartTransceive(const rfalNfcDepTxRxParam* param); - -/*! - ***************************************************************************** - * \brief Return the Transceive status - * - * Returns the status of the NFC-DEP Transceive - * - * \warning When the other device is performing chaining once a chained - * block is received the error ERR_AGAIN is sent. At this point - * caller must handle the received data immediately. - * When ERR_AGAIN is returned an ACK has already been sent to - * the other device and the next block might be incoming. - * If rfalWorker() is called frequently it will place the next - * block on the given buffer - * - * \return ERR_NONE : Transceive has been completed successfully - * \return ERR_BUSY : Transceive is ongoing - * \return ERR_PROTO : Protocol error occurred - * \return ERR_TIMEOUT : Timeout error occurred - * \return ERR_SLEEP_REQ : Deselect has been received and responded - * \return ERR_NOMEM : The received I-PDU does not fit into the - * receive buffer - * \return ERR_LINK_LOSS : Communication is lost because Reader/Writer - * has turned off its field - * \return ERR_AGAIN : received one chaining block, continue to call - * this method to retrieve the remaining blocks - ***************************************************************************** - */ -ReturnCode rfalNfcDepGetTransceiveStatus(void); - -/*! - ***************************************************************************** - * \brief Start PDU Transceive - * - * This method triggers a NFC-DEP Transceive containing a complete PDU - * It transmits the given message and handles all protocol retransmitions, - * error handling and control messages - * - * The txBuf contains a complete PDU to be transmitted - * The Prologue field will be manipulated by the Transceive - * - * \warning the txBuf will be modified during the transmission - * \warning the maximum RF frame which can be received is limited by param.tmpBuf - * - * \param[in] param: reference parameters to be used for the Transceive - * - * \return ERR_PARAM : Bad request - * \return ERR_WRONG_STATE : The module is not in a proper state - * \return ERR_NONE : The Transceive request has been started - ***************************************************************************** - */ -ReturnCode rfalNfcDepStartPduTransceive(rfalNfcDepPduTxRxParam param); - -/*! - ***************************************************************************** - * \brief Return the PSU Transceive status - * - * Returns the status of the NFC-DEP PDU Transceive - * - * - * \return ERR_NONE : Transceive has been completed successfully - * \return ERR_BUSY : Transceive is ongoing - * \return ERR_PROTO : Protocol error occurred - * \return ERR_TIMEOUT : Timeout error occurred - * \return ERR_SLEEP_REQ : Deselect has been received and responded - * \return ERR_NOMEM : The received I-PDU does not fit into the - * receive buffer - * \return ERR_LINK_LOSS : Communication is lost because Reader/Writer - * has turned off its field - ***************************************************************************** - */ -ReturnCode rfalNfcDepGetPduTransceiveStatus(void); - -#endif /* RFAL_NFCDEP_H_ */ - -/** - * @} - * - * @} - * - * @} - */ diff --git a/lib/ST25RFAL002/include/rfal_nfca.h b/lib/ST25RFAL002/include/rfal_nfca.h deleted file mode 100644 index 4772586cbbf..00000000000 --- a/lib/ST25RFAL002/include/rfal_nfca.h +++ /dev/null @@ -1,497 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

    © COPYRIGHT 2020 STMicroelectronics

    - * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R391x firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file rfal_nfca.h - * - * \author Gustavo Patricio - * - * \brief Provides several NFC-A convenience methods and definitions - * - * It provides a Poller (ISO14443A PCD) interface and as well as - * some NFC-A Listener (ISO14443A PICC) helpers. - * - * The definitions and helpers methods provided by this module are only - * up to ISO14443-3 layer - * - * - * An usage example is provided here: \ref exampleRfalNfca.c - * \example exampleRfalNfca.c - * - * - * \addtogroup RFAL - * @{ - * - * \addtogroup RFAL-AL - * \brief RFAL Abstraction Layer - * @{ - * - * \addtogroup NFC-A - * \brief RFAL NFC-A Module - * @{ - * - */ - -#ifndef RFAL_NFCA_H -#define RFAL_NFCA_H - -/* - ****************************************************************************** - * INCLUDES - ****************************************************************************** - */ -#include "platform.h" -#include "st_errno.h" -#include "rfal_rf.h" -#include "rfal_t1t.h" - -/* - ****************************************************************************** - * GLOBAL DEFINES - ****************************************************************************** - */ - -#define RFAL_NFCA_CASCADE_1_UID_LEN \ - 4U /*!< UID length of cascade level 1 only tag */ -#define RFAL_NFCA_CASCADE_2_UID_LEN \ - 7U /*!< UID length of cascade level 2 only tag */ -#define RFAL_NFCA_CASCADE_3_UID_LEN \ - 10U /*!< UID length of cascade level 3 only tag */ - -#define RFAL_NFCA_SENS_RES_PLATFORM_MASK \ - 0x0FU /*!< SENS_RES (ATQA) platform configuration mask Digital 1.1 Table 10 */ -#define RFAL_NFCA_SENS_RES_PLATFORM_T1T \ - 0x0CU /*!< SENS_RES (ATQA) T1T platform configuration Digital 1.1 Table 10 */ - -#define RFAL_NFCA_SEL_RES_CONF_MASK \ - 0x60U /*!< SEL_RES (SAK) platform configuration mask Digital 1.1 Table 19 */ -#define RFAL_NFCA_SEL_RES_CONF_T2T \ - 0x00U /*!< SEL_RES (SAK) T2T configuration Digital 1.1 Table 19 */ -#define RFAL_NFCA_SEL_RES_CONF_T4T \ - 0x20U /*!< SEL_RES (SAK) T4T configuration Digital 1.1 Table 19 */ -#define RFAL_NFCA_SEL_RES_CONF_NFCDEP \ - 0x40U /*!< SEL_RES (SAK) NFC-DEP configuration Digital 1.1 Table 19 */ -#define RFAL_NFCA_SEL_RES_CONF_T4T_NFCDEP \ - 0x60U /*!< SEL_RES (SAK) T4T and NFC-DEP configuration Digital 1.1 Table 19 */ - -/*! NFC-A minimum FDT(listen) = ((n * 128 + (84)) / fc) with n_min = 9 Digital 1.1 6.10.1 - * = (1236)/fc - * Relax with 3etu: (3*128)/fc as with multiple NFC-A cards, response may take longer (JCOP cards) - * = (1236 + 384)/fc = 1620 / fc */ -#define RFAL_NFCA_FDTMIN 1620U -/* - ****************************************************************************** - * GLOBAL MACROS - ****************************************************************************** - */ - -/*! Checks if device is a T1T given its SENS_RES */ -#define rfalNfcaIsSensResT1T(sensRes) \ - ((((rfalNfcaSensRes*)(sensRes))->platformInfo & RFAL_NFCA_SENS_RES_PLATFORM_MASK) == \ - RFAL_NFCA_SENS_RES_PLATFORM_T1T) - -/*! Checks if device is a T2T given its SENS_RES */ -#define rfalNfcaIsSelResT2T(selRes) \ - ((((rfalNfcaSelRes*)(selRes))->sak & RFAL_NFCA_SEL_RES_CONF_MASK) == \ - RFAL_NFCA_SEL_RES_CONF_T2T) - -/*! Checks if device is a T4T given its SENS_RES */ -#define rfalNfcaIsSelResT4T(selRes) \ - ((((rfalNfcaSelRes*)(selRes))->sak & RFAL_NFCA_SEL_RES_CONF_MASK) == \ - RFAL_NFCA_SEL_RES_CONF_T4T) - -/*! Checks if device supports NFC-DEP protocol given its SENS_RES */ -#define rfalNfcaIsSelResNFCDEP(selRes) \ - ((((rfalNfcaSelRes*)(selRes))->sak & RFAL_NFCA_SEL_RES_CONF_MASK) == \ - RFAL_NFCA_SEL_RES_CONF_NFCDEP) - -/*! Checks if device supports ISO-DEP and NFC-DEP protocol given its SENS_RES */ -#define rfalNfcaIsSelResT4TNFCDEP(selRes) \ - ((((rfalNfcaSelRes*)(selRes))->sak & RFAL_NFCA_SEL_RES_CONF_MASK) == \ - RFAL_NFCA_SEL_RES_CONF_T4T_NFCDEP) - -/*! Checks if a NFC-A listener device supports multiple protocols (ISO-DEP and NFC-DEP) */ -#define rfalNfcaLisDevIsMultiProto(lisDev) \ - (((rfalNfcaListenDevice*)(lisDev))->type == RFAL_NFCA_T4T_NFCDEP) - -/* -****************************************************************************** -* GLOBAL TYPES -****************************************************************************** -*/ - -/*! NFC-A Listen device types */ -typedef enum { - RFAL_NFCA_T1T = - 0x01, /* Device configured for T1T Digital 1.1 Table 9 */ - RFAL_NFCA_T2T = - 0x00, /* Device configured for T2T Digital 1.1 Table 19 */ - RFAL_NFCA_T4T = - 0x20, /* Device configured for T4T Digital 1.1 Table 19 */ - RFAL_NFCA_NFCDEP = - 0x40, /* Device configured for NFC-DEP Digital 1.1 Table 19 */ - RFAL_NFCA_T4T_NFCDEP = - 0x60 /* Device configured for NFC-DEP and T4T Digital 1.1 Table 19 */ -} rfalNfcaListenDeviceType; - -/*! SENS_RES (ATQA) format Digital 1.1 6.6.3 & Table 7 */ -typedef struct { - uint8_t - anticollisionInfo; /*!< SENS_RES Anticollision Information */ - uint8_t - platformInfo; /*!< SENS_RES Platform Information */ -} rfalNfcaSensRes; - -/*! SDD_REQ (Anticollision) format Digital 1.1 6.7.1 & Table 11 */ -typedef struct { - uint8_t - selCmd; /*!< SDD_REQ SEL_CMD: cascade Level */ - uint8_t - selPar; /*!< SDD_REQ SEL_PAR: Byte Count[4b] | Bit Count[4b] (NVB: Number of Valid Bits)*/ -} rfalNfcaSddReq; - -/*! SDD_RES (UID CLn) format Digital 1.1 6.7.2 & Table 15 */ -typedef struct { - uint8_t nfcid1 - [RFAL_NFCA_CASCADE_1_UID_LEN]; /*!< NFCID1 cascade level NFCID */ - uint8_t bcc; /*!< BCC Exclusive-OR over first 4 bytes of SDD_RES */ -} rfalNfcaSddRes; - -/*! SEL_REQ (Select) format Digital 1.1 6.8.1 & Table 17 */ -typedef struct { - uint8_t - selCmd; /*!< SDD_REQ SEL_CMD: cascade Level */ - uint8_t - selPar; /*!< SDD_REQ SEL_PAR: Byte Count[4b] | Bit Count[4b] (NVB: Number of Valid Bits)*/ - uint8_t nfcid1 - [RFAL_NFCA_CASCADE_1_UID_LEN]; /*!< NFCID1 data */ - uint8_t bcc; /*!< Checksum calculated as exclusive-OR over the 4 bytes of NFCID1 CLn */ -} rfalNfcaSelReq; - -/*! SEL_RES (SAK) format Digital 1.1 6.8.2 & Table 19 */ -typedef struct { - uint8_t sak; /*!< Select Acknowledge */ -} rfalNfcaSelRes; - -/*! NFC-A listener device (PICC) struct */ -typedef struct { - rfalNfcaListenDeviceType - type; /*!< NFC-A Listen device type */ - rfalNfcaSensRes - sensRes; /*!< SENS_RES (ATQA) */ - rfalNfcaSelRes - selRes; /*!< SEL_RES (SAK) */ - uint8_t - nfcId1Len; /*!< NFCID1 Length */ - uint8_t nfcId1 - [RFAL_NFCA_CASCADE_3_UID_LEN]; /*!< NFCID1 (UID) */ -#ifdef RFAL_FEATURE_T1T - rfalT1TRidRes - ridRes; /*!< RID_RES */ -#endif /* RFAL_FEATURE_T1T */ - bool isSleep; /*!< Device sleeping flag */ -} rfalNfcaListenDevice; - -/* -****************************************************************************** -* GLOBAL FUNCTION PROTOTYPES -****************************************************************************** -*/ - -/*! - ***************************************************************************** - * \brief Initialize NFC-A Poller mode - * - * This methods configures RFAL RF layer to perform as a - * NFC-A Poller/RW (ISO14443A PCD) including all default timings and bit rate - * to 106 kbps - - * - * \return ERR_WRONG_STATE : RFAL not initialized or mode not set - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalNfcaPollerInitialize(void); - -/*! - ***************************************************************************** - * \brief NFC-A Poller Check Presence - * - * This method checks if a NFC-A Listen device (PICC) is present on the field - * by sending an ALL_REQ (WUPA) or SENS_REQ (REQA) - * - * \param[in] cmd : Indicate if to send an ALL_REQ or a SENS_REQ - * \param[out] sensRes : If received, the SENS_RES - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_RF_COLLISION : Collision detected one or more device in the field - * \return ERR_PAR : Parity error detected, one or more device in the field - * \return ERR_CRC : CRC error detected, one or more device in the field - * \return ERR_FRAMING : Framing error detected, one or more device in the field - * \return ERR_PROTO : Protocol error detected, one or more device in the field - * \return ERR_TIMEOUT : Timeout error, no listener device detected - * \return ERR_NONE : No error, one or more device in the field - ***************************************************************************** - */ -ReturnCode rfalNfcaPollerCheckPresence(rfal14443AShortFrameCmd cmd, rfalNfcaSensRes* sensRes); - -/*! - ***************************************************************************** - * \brief NFC-A Poller Select - * - * This method selects a NFC-A Listener device (PICC) - * - * \param[in] nfcid1 : Listener device NFCID1 to be selected - * \param[in] nfcidLen : Length of the NFCID1 to be selected - * \param[out] selRes : pointer to place the SEL_RES - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_TIMEOUT : Timeout error - * \return ERR_PAR : Parity error detected - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_NONE : No error, SEL_RES received - ***************************************************************************** - */ -ReturnCode rfalNfcaPollerSelect(const uint8_t* nfcid1, uint8_t nfcidLen, rfalNfcaSelRes* selRes); - -/*! - ***************************************************************************** - * \brief NFC-A Poller Sleep - * - * This method sends a SLP_REQ (HLTA) - * No response is expected afterwards Digital 1.1 6.9.2.1 - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalNfcaPollerSleep(void); - -/*! - ***************************************************************************** - * \brief NFC-A Technology Detection - * - * This method performs NFC-A Technology Detection as defined in the spec - * given in the compliance mode - * - * \param[in] compMode : compliance mode to be performed - * \param[out] sensRes : location to store the SENS_RES, if received - * - * When compMode is set to ISO compliance a SLP_REQ (HLTA) is not sent - * after detection. When set to EMV a ALL_REQ (WUPA) is sent instead of - * a SENS_REQ (REQA) - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_NONE : No error, one or more device in the field - ***************************************************************************** - */ -ReturnCode - rfalNfcaPollerTechnologyDetection(rfalComplianceMode compMode, rfalNfcaSensRes* sensRes); - -/*! - ***************************************************************************** - * \brief NFC-A Poller Collision Resolution - * - * Collision resolution for one NFC-A Listener device/card (PICC) as - * defined in Activity 2.1 9.3.4 - * - * This method executes anti collision loop and select the device with higher NFCID1 - * - * When devLimit = 0 it is configured to perform collision detection only. Once a collision - * is detected the collision resolution is aborted immediately. If only one device is found - * with no collisions, it will properly resolved. - * - * \param[in] devLimit : device limit value (CON_DEVICES_LIMIT) - * \param[out] collPending : pointer to collision pending flag (INT_COLL_PEND) - * \param[out] selRes : location to store the last Select Response from listener device (PICC) - * \param[out] nfcId1 : location to store the NFCID1 (UID), ensure RFAL_NFCA_CASCADE_3_UID_LEN - * \param[out] nfcId1Len : pointer to length of NFCID1 (UID) - * - * \return ERR_WRONG_STATE : RFAL not initialized or mode not set - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_PROTO : Card length invalid - * \return ERR_IGNORE : conDevLimit is 0 and there is a collision - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalNfcaPollerSingleCollisionResolution( - uint8_t devLimit, - bool* collPending, - rfalNfcaSelRes* selRes, - uint8_t* nfcId1, - uint8_t* nfcId1Len); - -/*! - ***************************************************************************** - * \brief NFC-A Poller Full Collision Resolution - * - * Performs a full Collision resolution as defined in Activity 2.1 9.3.4 - * - * \param[in] compMode : compliance mode to be performed - * \param[in] devLimit : device limit value, and size nfcaDevList - * \param[out] nfcaDevList : NFC-A listener device info - * \param[out] devCnt : Devices found counter - * - * When compMode is set to ISO compliance it assumes that the device is - * not sleeping and therefore no ALL_REQ (WUPA) is sent at the beginning. - * When compMode is set to NFC compliance an additional ALL_REQ (WUPA) is sent - * at the beginning. - * - * - * When devLimit = 0 it is configured to perform collision detection only. Once a collision - * is detected the collision resolution is aborted immediately. If only one device is found - * with no collisions, it will properly resolved. - * - * - * \return ERR_WRONG_STATE : RFAL not initialized or mode not set - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalNfcaPollerFullCollisionResolution( - rfalComplianceMode compMode, - uint8_t devLimit, - rfalNfcaListenDevice* nfcaDevList, - uint8_t* devCnt); - -/*! - ***************************************************************************** - * \brief NFC-A Poller Full Collision Resolution with Sleep - * - * Performs a full Collision resolution similar to rfalNfcaPollerFullCollisionResolution - * but an additional SLP_REQ (HLTA) -> SENS_RES (REQA) is sent regardless if there - * was a collision. - * This proprietary behaviour ensures proper activation of certain devices that suffer - * from influence of Type B commands as foreseen in ISO14443-3 5.2.3 or were somehow - * not detected by the first round of collision resolution - * - * \param[in] devLimit : device limit value, and size nfcaDevList - * \param[out] nfcaDevList : NFC-A listener device info - * \param[out] devCnt : Devices found counter - * - * - * \return ERR_WRONG_STATE : RFAL not initialized or mode not set - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalNfcaPollerSleepFullCollisionResolution( - uint8_t devLimit, - rfalNfcaListenDevice* nfcaDevList, - uint8_t* devCnt); - -/*! - ***************************************************************************** - * \brief NFC-A Poller Start Full Collision Resolution - * - * This method starts the full Collision resolution as defined - * in Activity 1.0 or 1.1 9.3.4 - * - * \param[in] compMode : compliance mode to be performed - * \param[in] devLimit : device limit value, and size nfcaDevList - * \param[out] nfcaDevList : NFC-A listener device info - * \param[out] devCnt : Devices found counter - * - * When compMode is set to ISO compliance it assumes that the device is - * not sleeping and therefore no ALL_REQ (WUPA) is sent at the beginning. - * When compMode is set to NFC compliance an additional ALL_REQ (WUPA) is sent at - * the beginning. - * - * - * When devLimit = 0 it is configured to perform collision detection only. Once a collision - * is detected the collision resolution is aborted immediately. If only one device is found - * with no collisions, it will properly resolved. - * - * - * \return ERR_WRONG_STATE : RFAL not initialized or mode not set - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalNfcaPollerStartFullCollisionResolution( - rfalComplianceMode compMode, - uint8_t devLimit, - rfalNfcaListenDevice* nfcaDevList, - uint8_t* devCnt); - -/*! - ***************************************************************************** - * \brief NFC-A Get Full Collision Resolution Status - * - * Returns the Collision Resolution status - * - * \return ERR_BUSY : Operation is ongoing - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_TIMEOUT : Timeout error - * \return ERR_PAR : Parity error detected - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_NONE : No error, activation successful - ***************************************************************************** - */ -ReturnCode rfalNfcaPollerGetFullCollisionResolutionStatus(void); - -/*! - ***************************************************************************** - * \brief NFC-A Listener is SLP_REQ - * - * Checks if the given buffer contains valid NFC-A SLP_REQ (HALT) - * - * \param[in] buf: buffer containing data - * \param[in] bufLen: length of the data in buffer to be checked - * - * \return true if data in buf contains a SLP_REQ ; false otherwise - ***************************************************************************** - */ -bool rfalNfcaListenerIsSleepReq(const uint8_t* buf, uint16_t bufLen); - -#endif /* RFAL_NFCA_H */ - -/** - * @} - * - * @} - * - * @} - */ diff --git a/lib/ST25RFAL002/include/rfal_nfcb.h b/lib/ST25RFAL002/include/rfal_nfcb.h deleted file mode 100644 index 67bcb327135..00000000000 --- a/lib/ST25RFAL002/include/rfal_nfcb.h +++ /dev/null @@ -1,425 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

    © COPYRIGHT 2020 STMicroelectronics

    - * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R391x firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file rfal_nfcb.h - * - * \author Gustavo Patricio - * - * \brief Implementation of NFC-B (ISO14443B) helpers - * - * It provides a NFC-B Poller (ISO14443B PCD) interface and - * also provides some NFC-B Listener (ISO14443B PICC) helpers - * - * The definitions and helpers methods provided by this module are only - * up to ISO14443-3 layer (excluding ATTRIB) - * - * - * \addtogroup RFAL - * @{ - * - * \addtogroup RFAL-AL - * \brief RFAL Abstraction Layer - * @{ - * - * \addtogroup NFC-B - * \brief RFAL NFC-B Module - * @{ - * - */ - -#ifndef RFAL_NFCB_H -#define RFAL_NFCB_H - -/* - ****************************************************************************** - * INCLUDES - ****************************************************************************** - */ -#include "platform.h" -#include "st_errno.h" -#include "rfal_rf.h" - -/* - ****************************************************************************** - * GLOBAL DEFINES - ****************************************************************************** - */ - -#define RFAL_NFCB_FWTSENSB 7680U /*!< NFC-B FWT(SENSB) Digital 2.0 B.3 */ -#define RFAL_NFCB_DFWT 49152U /*!< NFC-B dFWT Delta 2.0 7.9.1.3 & B.3 */ -#define RFAL_NFCB_DTPOLL_10 rfalConvMsTo1fc(20) /*!< NFC-B Delta Tb Poll Digital 1.0 A.2 */ -#define RFAL_NFCB_DTPOLL_20 rfalConvMsTo1fc(17) /*!< NFC-B Delta Tb Poll Digital 2.1 B.3 */ - -#define RFAL_NFCB_AFI 0x00U /*!< NFC-B default Application Family Digital 1.1 7.6.1.1 */ -#define RFAL_NFCB_PARAM 0x00U /*!< NFC-B default SENSB_REQ PARAM */ -#define RFAL_NFCB_CRC_LEN 2U /*!< NFC-B CRC length and CRC_B(AID) Digital 1.1 Table 28 */ -#define RFAL_NFCB_NFCID0_LEN 4U /*!< Length of NFC-B NFCID0 */ -#define RFAL_NFCB_CMD_LEN 1U /*!< Length of NFC-B Command */ - -#define RFAL_NFCB_SENSB_RES_LEN 12U /*!< Standard length of SENSB_RES without SFGI byte */ -#define RFAL_NFCB_SENSB_RES_EXT_LEN \ - 13U /*!< Extended length of SENSB_RES with SFGI byte */ - -#define RFAL_NFCB_SENSB_REQ_ADV_FEATURE \ - 0x20U /*!< Bit mask for Advance Feature in SENSB_REQ */ -#define RFAL_NFCB_SENSB_RES_FSCI_MASK \ - 0x0FU /*!< Bit mask for FSCI value in SENSB_RES */ -#define RFAL_NFCB_SENSB_RES_FSCI_SHIFT \ - 4U /*!< Shift for FSCI value in SENSB_RES */ -#define RFAL_NFCB_SENSB_RES_PROTO_RFU_MASK \ - 0x08U /*!< Bit mask for Protocol Type RFU in SENSB_RES */ -#define RFAL_NFCB_SENSB_RES_PROTO_TR2_MASK \ - 0x03U /*!< Bit mask for Protocol Type TR2 in SENSB_RES */ -#define RFAL_NFCB_SENSB_RES_PROTO_TR2_SHIFT \ - 1U /*!< Shift for Protocol Type TR2 in SENSB_RES */ -#define RFAL_NFCB_SENSB_RES_PROTO_ISO_MASK \ - 0x01U /*!< Bit mask Protocol Type ISO14443 Compliant in SENSB_RES */ -#define RFAL_NFCB_SENSB_RES_FWI_MASK \ - 0x0FU /*!< Bit mask for FWI value in SENSB_RES */ -#define RFAL_NFCB_SENSB_RES_FWI_SHIFT \ - 4U /*!< Bit mask for FWI value in SENSB_RES */ -#define RFAL_NFCB_SENSB_RES_ADC_MASK \ - 0x0CU /*!< Bit mask for ADC value in SENSB_RES */ -#define RFAL_NFCB_SENSB_RES_ADC_ADV_FEATURE_MASK \ - 0x08U /*!< Bit mask for ADC.Advanced Proto Features in SENSB_RES */ -#define RFAL_NFCB_SENSB_RES_ADC_PROPRIETARY_MASK \ - 0x04U /*!< Bit mask for ADC.Proprietary Application in SENSB_RES */ -#define RFAL_NFCB_SENSB_RES_FO_DID_MASK \ - 0x01U /*!< Bit mask for DID in SENSB_RES */ -#define RFAL_NFCB_SENSB_RES_FO_NAD_MASK \ - 0x02U /*!< Bit mask for DID in SENSB_RES */ -#define RFAL_NFCB_SENSB_RES_FO_MASK \ - 0x03U /*!< Bit mask for FO value in SENSB_RES (NAD and DID) */ -#define RFAL_NFCB_SENSB_RES_SFGI_MASK \ - 0x0FU /*!< Bit mask for SFGI in SENSB_RES */ -#define RFAL_NFCB_SENSB_RES_SFGI_SHIFT \ - 4U /*!< Shift for SFGI in SENSB_RES */ - -/* -****************************************************************************** -* GLOBAL MACROS -****************************************************************************** -*/ - -/*! Get device's FSCI given its SENSB_RES Digital 1.1 7.6.2 */ -#define rfalNfcbGetFSCI(sensbRes) \ - ((((rfalNfcbSensbRes*)(sensbRes))->protInfo.FsciProType >> RFAL_NFCB_SENSB_RES_FSCI_SHIFT) & \ - RFAL_NFCB_SENSB_RES_FSCI_MASK) - -/*! Checks if the given NFC-B device indicates ISO-DEP support */ -#define rfalNfcbIsIsoDepSupported(dev) \ - ((((rfalNfcbListenDevice*)(dev))->sensbRes.protInfo.FsciProType & \ - RFAL_NFCB_SENSB_RES_PROTO_ISO_MASK) != 0U) - -/* -****************************************************************************** -* GLOBAL TYPES -****************************************************************************** -*/ - -/*! SENSB_REQ and ALLB_REQ param Digital 1.1 7.6.1 */ -typedef enum { - RFAL_NFCB_SENS_CMD_ALLB_REQ = 0x08, /*!< ALLB_REQ (WUPB) */ - RFAL_NFCB_SENS_CMD_SENSB_REQ = 0x00 /*!< SENSB_REQ (REQB) */ -} rfalNfcbSensCmd; - -/*! Number of Slots (NI) codes used for NFC-B anti collision Digital 1.1 Table 26 */ -typedef enum { - RFAL_NFCB_SLOT_NUM_1 = 0, /*!< N=0 : 1 slot */ - RFAL_NFCB_SLOT_NUM_2 = 1, /*!< N=1 : 2 slots */ - RFAL_NFCB_SLOT_NUM_4 = 2, /*!< N=2 : 4 slots */ - RFAL_NFCB_SLOT_NUM_8 = 3, /*!< N=3 : 8 slots */ - RFAL_NFCB_SLOT_NUM_16 = 4 /*!< N=4 : 16 slots */ -} rfalNfcbSlots; - -/*! SENSB_RES (ATQB) Application Data Format Digital 1.1 Table 28 */ -typedef struct { - uint8_t AFI; /*!< Application Family Identifier */ - uint8_t CRC_B[RFAL_NFCB_CRC_LEN]; /*!< CRC_B of AID */ - uint8_t numApps; /*!< Number of Applications */ -} rfalNfcbSensbResAppData; - -/*! SENSB_RES Protocol Info format Digital 1.1 Table 29 */ -typedef struct { - uint8_t - BRC; /*!< Bit Rate Capability */ - uint8_t - FsciProType; /*!< Frame Size Card Integer [4b] | Protocol Type[4 bits] */ - uint8_t - FwiAdcFo; /*!< Frame Waiting Integer [4b] | Application Data Coding [2b] | Frame Options [2b] */ - uint8_t - SFGI; /*!< Optional: Start-Up Frame Guard Time Integer[4b] | RFU [4b] */ -} rfalNfcbSensbResProtocolInfo; - -/*! SENSB_RES format Digital 1.1 7.6.2 */ -typedef struct { - uint8_t cmd; /*!< SENSB_RES: 50h */ - uint8_t nfcid0[RFAL_NFCB_NFCID0_LEN]; /*!< NFC Identifier (PUPI)*/ - rfalNfcbSensbResAppData appData; /*!< Application Data */ - rfalNfcbSensbResProtocolInfo protInfo; /*!< Protocol Information */ -} rfalNfcbSensbRes; - -/*! NFC-B listener device (PICC) struct */ -typedef struct { - uint8_t sensbResLen; /*!< SENSB_RES length */ - rfalNfcbSensbRes sensbRes; /*!< SENSB_RES */ - bool isSleep; /*!< Device sleeping flag */ -} rfalNfcbListenDevice; - -/* -****************************************************************************** -* GLOBAL FUNCTION PROTOTYPES -****************************************************************************** -*/ - -/*! - ***************************************************************************** - * \brief Initialize NFC-B Poller mode - * - * This methods configures RFAL RF layer to perform as a - * NFC-B Poller/RW (ISO14443B PCD) including all default timings - * - * It sets NFC-B parameters (AFI, PARAM) to default values - * - * \return ERR_WRONG_STATE : RFAL not initialized or mode not set - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalNfcbPollerInitialize(void); - -/*! - ***************************************************************************** - * \brief Set NFC-B Poller parameters - * - * This methods configures RFAL RF layer to perform as a - * NFCA Poller/RW (ISO14443A PCD) including all default timings - * - * Additionally configures NFC-B specific parameters to be used on the - * following communications - * - * \param[in] AFI : Application Family Identifier to be used - * \param[in] PARAM : PARAM to be used, it announces whether Advanced - * Features or Extended SENSB_RES is supported - * - * \return ERR_WRONG_STATE : RFAL not initialized or mode not set - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalNfcbPollerInitializeWithParams(uint8_t AFI, uint8_t PARAM); - -/*! - ***************************************************************************** - * \brief NFC-B Poller Check Presence - * - * This method checks if a NFC-B Listen device (PICC) is present on the field - * by sending an ALLB_REQ (WUPB) or SENSB_REQ (REQB) - * - * \param[in] cmd : Indicate if to send an ALL_REQ or a SENS_REQ - * \param[in] slots : The number of slots to be announced - * \param[out] sensbRes : If received, the SENSB_RES - * \param[out] sensbResLen : If received, the SENSB_RES length - * - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_TIMEOUT : Timeout error, no listener device detected - * \return ERR_RF_COLLISION : Collision detected one or more device in the field - * \return ERR_PAR : Parity error detected, one or more device in the field - * \return ERR_CRC : CRC error detected, one or more device in the field - * \return ERR_FRAMING : Framing error detected, one or more device in the field - * \return ERR_PROTO : Protocol error detected, invalid SENSB_RES received - * \return ERR_NONE : No error, SENSB_RES received - ***************************************************************************** - */ -ReturnCode rfalNfcbPollerCheckPresence( - rfalNfcbSensCmd cmd, - rfalNfcbSlots slots, - rfalNfcbSensbRes* sensbRes, - uint8_t* sensbResLen); - -/*! - ***************************************************************************** - * \brief NFC-B Poller Sleep - * - * This function is used to send the SLPB_REQ (HLTB) command to put the PICC with - * the given NFCID0 to state HALT so that they do not reply to further SENSB_REQ - * commands (only to ALLB_REQ) - * - * \param[in] nfcid0 : NFCID of the device to be put to Sleep - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalNfcbPollerSleep(const uint8_t* nfcid0); - -/*! - ***************************************************************************** - * \brief NFC-B Poller Slot Marker - * - * This method selects a NFC-B Slot marker frame - * - * \param[in] slotCode : Slot Code [1-15] - * \param[out] sensbRes : If received, the SENSB_RES - * \param[out] sensbResLen : If received, the SENSB_RES length - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_TIMEOUT : Timeout error - * \return ERR_PAR : Parity error detected - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_NONE : No error, SEL_RES received - ***************************************************************************** - */ -ReturnCode - rfalNfcbPollerSlotMarker(uint8_t slotCode, rfalNfcbSensbRes* sensbRes, uint8_t* sensbResLen); - -/*! - ***************************************************************************** - * \brief NFC-B Technology Detection - * - * This method performs NFC-B Technology Detection as defined in the spec - * given in the compliance mode - * - * \param[in] compMode : compliance mode to be performed - * \param[out] sensbRes : location to store the SENSB_RES, if received - * \param[out] sensbResLen : length of the SENSB_RES, if received - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_NONE : No error, one or more device in the field - ***************************************************************************** - */ -ReturnCode rfalNfcbPollerTechnologyDetection( - rfalComplianceMode compMode, - rfalNfcbSensbRes* sensbRes, - uint8_t* sensbResLen); - -/*! - ***************************************************************************** - * \brief NFC-B Poller Collision Resolution - * - * NFC-B Collision resolution Listener device/card (PICC) as - * defined in Activity 1.1 9.3.5 - * - * This function is used to perform collision resolution for detection in case - * of multiple NFC Forum Devices with Technology B detected. - * Target with valid SENSB_RES will be stored in devInfo and nfcbDevCount incremented. - * - * \param[in] compMode : compliance mode to be performed - * \param[in] devLimit : device limit value, and size nfcbDevList - * \param[out] nfcbDevList : NFC-B listener device info - * \param[out] devCnt : devices found counter - * - * \return ERR_WRONG_STATE : RFAL not initialized or mode not set - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_PROTO : Protocol error detected - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalNfcbPollerCollisionResolution( - rfalComplianceMode compMode, - uint8_t devLimit, - rfalNfcbListenDevice* nfcbDevList, - uint8_t* devCnt); - -/*! - ***************************************************************************** - * \brief NFC-B Poller Collision Resolution Slotted - * - * NFC-B Collision resolution Listener device/card (PICC). The sequence can - * be configured to be according to NFC Forum Activity 1.1 9.3.5, ISO10373 - * or EMVCo - * - * This function is used to perform collision resolution for detection in case - * of multiple NFC Forum Devices with Technology B are detected. - * Target with valid SENSB_RES will be stored in devInfo and nfcbDevCount incremented. - * - * This method provides the means to perform a collision resolution loop with specific - * initial and end number of slots. This allows to user to start the loop already with - * greater number of slots, and or limit the end number of slots. At the end a flag - * indicating whether there were collisions pending is returned. - * - * If RFAL_COMPLIANCE_MODE_ISO is used \a initSlots must be set to RFAL_NFCB_SLOT_NUM_1 - * - * - * \param[in] compMode : compliance mode to be performed - * \param[in] devLimit : device limit value, and size nfcbDevList - * \param[in] initSlots : number of slots to open initially - * \param[in] endSlots : number of slots when to stop collision resolution - * \param[out] nfcbDevList : NFC-B listener device info - * \param[out] devCnt : devices found counter - * \param[out] colPending : flag indicating whether collision are still pending - * - * \return ERR_WRONG_STATE : RFAL not initialized or mode not set - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_PROTO : Protocol error detected - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalNfcbPollerSlottedCollisionResolution( - rfalComplianceMode compMode, - uint8_t devLimit, - rfalNfcbSlots initSlots, - rfalNfcbSlots endSlots, - rfalNfcbListenDevice* nfcbDevList, - uint8_t* devCnt, - bool* colPending); - -/*! - ***************************************************************************** - * \brief NFC-B TR2 code to FDT - * - * Converts the TR2 code as defined in Digital 1.1 Table 33 Minimum - * TR2 Coding to Frame Delay Time (FDT) in 1/Fc - * - * \param[in] tr2Code : TR2 code as defined in Digital 1.1 Table 33 - * - * \return FDT in 1/Fc - ***************************************************************************** - */ -uint32_t rfalNfcbTR2ToFDT(uint8_t tr2Code); - -#endif /* RFAL_NFCB_H */ - -/** - * @} - * - * @} - * - * @} - */ diff --git a/lib/ST25RFAL002/include/rfal_nfcf.h b/lib/ST25RFAL002/include/rfal_nfcf.h deleted file mode 100644 index 30c34765db8..00000000000 --- a/lib/ST25RFAL002/include/rfal_nfcf.h +++ /dev/null @@ -1,403 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

    © COPYRIGHT 2020 STMicroelectronics

    - * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R391x firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file rfal_nfcf.h - * - * \author Gustavo Patricio - * - * \brief Implementation of NFC-F Poller (FeliCa PCD) device - * - * The definitions and helpers methods provided by this module are - * aligned with NFC-F (FeliCa - JIS X6319-4) - * - * - * \addtogroup RFAL - * @{ - * - * \addtogroup RFAL-AL - * \brief RFAL Abstraction Layer - * @{ - * - * \addtogroup NFC-F - * \brief RFAL NFC-F Module - * @{ - * - */ - -#ifndef RFAL_NFCF_H -#define RFAL_NFCF_H - -/* - ****************************************************************************** - * INCLUDES - ****************************************************************************** - */ -#include "platform.h" -#include "st_errno.h" -#include "rfal_rf.h" - -/* - ****************************************************************************** - * GLOBAL DEFINES - ****************************************************************************** - */ - -#define RFAL_NFCF_NFCID2_LEN 8U /*!< NFCID2 (FeliCa IDm) length */ -#define RFAL_NFCF_SENSF_RES_LEN_MIN 16U /*!< SENSF_RES minimum length */ -#define RFAL_NFCF_SENSF_RES_LEN_MAX 18U /*!< SENSF_RES maximum length */ -#define RFAL_NFCF_SENSF_RES_PAD0_LEN 2U /*!< SENSF_RES PAD0 length */ -#define RFAL_NFCF_SENSF_RES_PAD1_LEN 2U /*!< SENSF_RES PAD1 length */ -#define RFAL_NFCF_SENSF_RES_RD_LEN 2U /*!< SENSF_RES Request Data length */ -#define RFAL_NFCF_SENSF_RES_BYTE1 1U /*!< SENSF_RES first byte value */ -#define RFAL_NFCF_SENSF_SC_LEN 2U /*!< Felica SENSF_REQ System Code length */ -#define RFAL_NFCF_SENSF_PARAMS_SC1_POS 0U /*!< System Code byte1 position in the SENSF_REQ */ -#define RFAL_NFCF_SENSF_PARAMS_SC2_POS 1U /*!< System Code byte2 position in the SENSF_REQ */ -#define RFAL_NFCF_SENSF_PARAMS_RC_POS 2U /*!< Request Code position in the SENSF_REQ */ -#define RFAL_NFCF_SENSF_PARAMS_TSN_POS 3U /*!< Time Slot Number position in the SENSF_REQ */ -#define RFAL_NFCF_POLL_MAXCARDS 16U /*!< Max number slots/cards 16 */ - -#define RFAL_NFCF_CMD_POS 0U /*!< Command/Response code length */ -#define RFAL_NFCF_CMD_LEN 1U /*!< Command/Response code length */ -#define RFAL_NFCF_LENGTH_LEN 1U /*!< LEN field length */ -#define RFAL_NFCF_HEADER_LEN (RFAL_NFCF_LENGTH_LEN + RFAL_NFCF_CMD_LEN) /*!< Header length*/ - -#define RFAL_NFCF_SENSF_NFCID2_BYTE1_POS \ - 0U /*!< NFCID2 byte1 position */ -#define RFAL_NFCF_SENSF_NFCID2_BYTE2_POS \ - 1U /*!< NFCID2 byte2 position */ - -#define RFAL_NFCF_SENSF_NFCID2_PROT_TYPE_LEN \ - 2U /*!< NFCID2 length for byte 1 and byte 2 indicating NFC-DEP or T3T support */ -#define RFAL_NFCF_SENSF_NFCID2_BYTE1_NFCDEP \ - 0x01U /*!< NFCID2 byte1 NFC-DEP support Digital 1.0 Table 44 */ -#define RFAL_NFCF_SENSF_NFCID2_BYTE2_NFCDEP \ - 0xFEU /*!< NFCID2 byte2 NFC-DEP support Digital 1.0 Table 44 */ - -#define RFAL_NFCF_SYSTEMCODE \ - 0xFFFFU /*!< SENSF_RES Default System Code Digital 1.0 6.6.1.1 */ - -#define RFAL_NFCF_BLOCK_LEN \ - 16U /*!< NFCF T3T Block size T3T 1.0 4.1 */ -#define RFAL_NFCF_CHECKUPDATE_RES_ST1_POS \ - 9U /*!< Check|Update Res Status Flag 1 position T3T 1.0 Table 8 */ -#define RFAL_NFCF_CHECKUPDATE_RES_ST2_POS \ - 10U /*!< Check|Update Res Status Flag 2 position T3T 1.0 Table 8 */ -#define RFAL_NFCF_CHECKUPDATE_RES_NOB_POS \ - 11U /*!< Check|Update Res Number of Blocks position T3T 1.0 Table 8 */ - -#define RFAL_NFCF_STATUS_FLAG_SUCCESS \ - 0x00U /*!< Check response Number of Blocks position T3T 1.0 Table 11 */ -#define RFAL_NFCF_STATUS_FLAG_ERROR \ - 0xFFU /*!< Check response Number of Blocks position T3T 1.0 Table 11 */ - -#define RFAL_NFCF_BLOCKLISTELEM_LEN \ - 0x80U /*!< Block List Element Length bit (2|3 bytes) T3T 1.0 5.6.1 */ - -#define RFAL_NFCF_SERVICECODE_RDONLY \ - 0x000BU /*!< NDEF Service Code as Read-Only T3T 1.0 7.2.1 */ -#define RFAL_NFCF_SERVICECODE_RDWR \ - 0x0009U /*!< NDEF Service Code as Read and Write T3T 1.0 7.2.1 */ - -/*! NFC-F Felica command set JIS X6319-4 9.1 */ -enum { - RFAL_NFCF_CMD_POLLING = - 0x00, /*!< SENSF_REQ (Felica Poll/REQC command to identify a card ) */ - RFAL_NFCF_CMD_POLLING_RES = - 0x01, /*!< SENSF_RES (Felica Poll/REQC command response ) */ - RFAL_NFCF_CMD_REQUEST_SERVICE = - 0x02, /*!< verify the existence of Area and Service */ - RFAL_NFCF_CMD_REQUEST_RESPONSE = - 0x04, /*!< verify the existence of a card */ - RFAL_NFCF_CMD_READ_WITHOUT_ENCRYPTION = - 0x06, /*!< read Block Data from a Service that requires no authentication */ - RFAL_NFCF_CMD_READ_WITHOUT_ENCRYPTION_RES = - 0x07, /*!< read Block Data response from a Service with no authentication */ - RFAL_NFCF_CMD_WRITE_WITHOUT_ENCRYPTION = - 0x08, /*!< write Block Data to a Service that requires no authentication */ - RFAL_NFCF_CMD_WRITE_WITHOUT_ENCRYPTION_RES = - 0x09, /*!< write Block Data response to a Service with no authentication */ - RFAL_NFCF_CMD_REQUEST_SYSTEM_CODE = - 0x0c, /*!< acquire the System Code registered to a card */ - RFAL_NFCF_CMD_AUTHENTICATION1 = - 0x10, /*!< authenticate a card */ - RFAL_NFCF_CMD_AUTHENTICATION2 = - 0x12, /*!< allow a card to authenticate a Reader/Writer */ - RFAL_NFCF_CMD_READ = - 0x14, /*!< read Block Data from a Service that requires authentication */ - RFAL_NFCF_CMD_WRITE = - 0x16, /*!< write Block Data to a Service that requires authentication */ -}; - -/* - ****************************************************************************** - * GLOBAL MACROS - ****************************************************************************** - */ - -/*! Checks if the given NFC-F device indicates NFC-DEP support */ -#define rfalNfcfIsNfcDepSupported(dev) \ - ((((rfalNfcfListenDevice*)(dev))->sensfRes.NFCID2[RFAL_NFCF_SENSF_NFCID2_BYTE1_POS] == \ - RFAL_NFCF_SENSF_NFCID2_BYTE1_NFCDEP) && \ - (((rfalNfcfListenDevice*)(dev))->sensfRes.NFCID2[RFAL_NFCF_SENSF_NFCID2_BYTE2_POS] == \ - RFAL_NFCF_SENSF_NFCID2_BYTE2_NFCDEP)) - -/* -****************************************************************************** -* GLOBAL TYPES -****************************************************************************** -*/ - -/*! NFC-F SENSF_RES format Digital 1.1 8.6.2 */ -typedef struct { - uint8_t CMD; /*!< Command Code: 01h */ - uint8_t NFCID2[RFAL_NFCF_NFCID2_LEN]; /*!< NFCID2 */ - uint8_t PAD0[RFAL_NFCF_SENSF_RES_PAD0_LEN]; /*!< PAD0 */ - uint8_t PAD1[RFAL_NFCF_SENSF_RES_PAD1_LEN]; /*!< PAD1 */ - uint8_t MRTIcheck; /*!< MRTIcheck */ - uint8_t MRTIupdate; /*!< MRTIupdate */ - uint8_t PAD2; /*!< PAD2 */ - uint8_t RD[RFAL_NFCF_SENSF_RES_RD_LEN]; /*!< Request Data */ -} rfalNfcfSensfRes; - -/*! NFC-F poller device (PCD) struct */ -typedef struct { - uint8_t NFCID2[RFAL_NFCF_NFCID2_LEN]; /*!< NFCID2 */ -} rfalNfcfPollDevice; - -/*! NFC-F listener device (PICC) struct */ -typedef struct { - uint8_t sensfResLen; /*!< SENF_RES length */ - rfalNfcfSensfRes sensfRes; /*!< SENF_RES */ -} rfalNfcfListenDevice; - -typedef uint16_t rfalNfcfServ; /*!< NFC-F Service Code */ - -/*! NFC-F Block List Element (2 or 3 bytes element) T3T 1.0 5.6.1 */ -typedef struct { - uint8_t conf; /*!< Access Mode | Serv Code List Order */ - uint16_t blockNum; /*!< Block Number */ -} rfalNfcfBlockListElem; - -/*! Check Update Service list and Block list parameter */ -typedef struct { - uint8_t numServ; /*!< Number of Services */ - rfalNfcfServ* servList; /*!< Service Code List */ - uint8_t numBlock; /*!< Number of Blocks */ - rfalNfcfBlockListElem* blockList; /*!< Block Number List */ -} rfalNfcfServBlockListParam; - -/* -****************************************************************************** -* GLOBAL FUNCTION PROTOTYPES -****************************************************************************** -*/ - -/*! - ***************************************************************************** - * \brief Initialize NFC-F Poller mode - * - * This methods configures RFAL RF layer to perform as a - * NFC-F Poller/RW (FeliCa PCD) including all default timings - * - * \param[in] bitRate : NFC-F bitrate to be initialize (212 or 424) - * - * \return ERR_WRONG_STATE : RFAL not initialized or mode not set - * \return ERR_PARAM : Incorrect bitrate - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalNfcfPollerInitialize(rfalBitRate bitRate); - -/*! - ***************************************************************************** - * \brief NFC-F Poller Check Presence - * - * This function sends a Poll/SENSF command according to NFC Activity spec - * It detects if a NCF-F device is within range - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_TIMEOUT : Timeout error, no listener device detected - * \return ERR_NONE : No error and some NFC-F device was detected - * - ***************************************************************************** - */ -ReturnCode rfalNfcfPollerCheckPresence(void); - -/*! - ***************************************************************************** - * \brief NFC-F Poller Poll - * - * This function sends to all PICCs in field the POLL command with the given - * number of slots. - * - * \param[in] slots : the number of slots to be performed - * \param[in] sysCode : as given in FeliCa poll command - * \param[in] reqCode : FeliCa communication parameters - * \param[out] cardList : Parameter of type rfalFeliCaPollRes which will hold the cards found - * \param[out] devCnt : actual number of cards found - * \param[out] collisions : number of collisions encountered - * - * \warning the list cardList has to be as big as the number of slots for the Poll - * - * \return ERR_WRONG_STATE : RFAL not initialized or incorrect mode - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_CRC : CRC error detected - * \return ERR_FRAMING : Framing error detected - * \return ERR_PROTO : Protocol error detected - * \return ERR_TIMEOUT : Timeout error, no listener device detected - * \return ERR_NONE : No error and some NFC-F device was detected - * - ***************************************************************************** - */ -ReturnCode rfalNfcfPollerPoll( - rfalFeliCaPollSlots slots, - uint16_t sysCode, - uint8_t reqCode, - rfalFeliCaPollRes* cardList, - uint8_t* devCnt, - uint8_t* collisions); - -/*! - ***************************************************************************** - * \brief NFC-F Poller Full Collision Resolution - * - * Performs a full Collision resolution as defined in Activity 1.1 9.3.4 - * - * \param[in] compMode : compliance mode to be performed - * \param[in] devLimit : device limit value, and size nfcaDevList - * \param[out] nfcfDevList : NFC-F listener devices list - * \param[out] devCnt : Devices found counter - * - * \return ERR_WRONG_STATE : RFAL not initialized or mode not set - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalNfcfPollerCollisionResolution( - rfalComplianceMode compMode, - uint8_t devLimit, - rfalNfcfListenDevice* nfcfDevList, - uint8_t* devCnt); - -/*! - ***************************************************************************** - * \brief NFC-F Poller Check/Read - * - * It computes a Check / Read command according to T3T 1.0 and JIS X6319-4 and - * sends it to PICC. If successfully, the rxBuf will contain the the number of - * blocks in the first byte followed by the blocks data. - * - * \param[in] nfcid2 : nfcid2 of the device - * \param[in] servBlock : parameter containing the list of Services and - * Blocks to be addressed by this command - * \param[out] rxBuf : buffer to place check/read data - * \param[in] rxBufLen : size of the rxBuf - * \param[out] rcvdLen : length of data placed in rxBuf - * - * \return ERR_WRONG_STATE : RFAL not initialized or mode not set - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_REQUEST : The request was executed with error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalNfcfPollerCheck( - const uint8_t* nfcid2, - const rfalNfcfServBlockListParam* servBlock, - uint8_t* rxBuf, - uint16_t rxBufLen, - uint16_t* rcvdLen); - -/*! - ***************************************************************************** - * \brief NFC-F Poller Update/Write - * - * It computes a Update / Write command according to T3T 1.0 and JIS X6319-4 and - * sends it to PICC. - * - * \param[in] nfcid2 : nfcid2 of the device - * \param[in] servBlock : parameter containing the list of Services and - * Blocks to be addressed by this command - * \param[in] txBuf : buffer where the request will be composed - * \param[in] txBufLen : size of txBuf - * \param[in] blockData : data to written on the given block(s) - * \param[out] rxBuf : buffer to place check/read data - * \param[in] rxBufLen : size of the rxBuf - * - * \return ERR_WRONG_STATE : RFAL not initialized or mode not set - * \return ERR_PARAM : Invalid parameters - * \return ERR_IO : Generic internal error - * \return ERR_REQUEST : The request was executed with error - * \return ERR_NONE : No error - ***************************************************************************** - */ -ReturnCode rfalNfcfPollerUpdate( - const uint8_t* nfcid2, - const rfalNfcfServBlockListParam* servBlock, - uint8_t* txBuf, - uint16_t txBufLen, - const uint8_t* blockData, - uint8_t* rxBuf, - uint16_t rxBufLen); - -/*! - ***************************************************************************** - * \brief NFC-F Listener is T3T Request - * - * This method checks if the given data is a valid T3T command (Read or Write) - * and in case a valid request has been received it may output the request's NFCID2 - * - * \param[in] buf : buffer holding Initiator's received command - * \param[in] bufLen : length of received command in bytes - * \param[out] nfcid2 : pointer to where the NFCID2 may be outputted, - * nfcid2 has NFCF_SENSF_NFCID2_LEN as length - * Pass NULL if output parameter not desired - * - * \return true : Valid T3T command (Read or Write) received - * \return false : Invalid protocol request - * - ***************************************************************************** - */ -bool rfalNfcfListenerIsT3TReq(const uint8_t* buf, uint16_t bufLen, uint8_t* nfcid2); - -#endif /* RFAL_NFCF_H */ - -/** - * @} - * - * @} - * - * @} - */ diff --git a/lib/ST25RFAL002/include/rfal_nfcv.h b/lib/ST25RFAL002/include/rfal_nfcv.h deleted file mode 100644 index b5e1c00aeb3..00000000000 --- a/lib/ST25RFAL002/include/rfal_nfcv.h +++ /dev/null @@ -1,923 +0,0 @@ - -/****************************************************************************** - * \attention - * - *

    © COPYRIGHT 2020 STMicroelectronics

    - * - * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * www.st.com/myliberty - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, - * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. - * See the License for the specific language governing permissions and - * limitations under the License. - * -******************************************************************************/ - -/* - * PROJECT: ST25R391x firmware - * Revision: - * LANGUAGE: ISO C99 - */ - -/*! \file rfal_nfcv.h - * - * \author Gustavo Patricio - * - * \brief Implementation of NFC-V Poller (ISO15693) device - * - * The definitions and helpers methods provided by this module - * are aligned with NFC-V Digital 2.1 - * - * - * \addtogroup RFAL - * @{ - * - * \addtogroup RFAL-AL - * \brief RFAL Abstraction Layer - * @{ - * - * \addtogroup NFC-V - * \brief RFAL NFC-V Module - * @{ - * - */ - -#ifndef RFAL_NFCV_H -#define RFAL_NFCV_H - -/* - ****************************************************************************** - * INCLUDES - ****************************************************************************** - */ -#include "platform.h" -#include "st_errno.h" -#include "rfal_rf.h" - -/* - ****************************************************************************** - * GLOBAL DEFINES - ****************************************************************************** - */ - -#define RFAL_NFCV_UID_LEN 8U /*!< NFC-V UID length */ -#define RFAL_NFCV_MAX_BLOCK_LEN \ - 32U /*!< Max Block size: can be of up to 256 bits ISO 15693 2000 5 */ -#define RFAL_NFCV_BNO_LEN 1U /*!< NFC-V Block Number length */ -#define RFAL_NFCV_CRC_LEN 2U /*!< NFC-V CRC length */ -#define RFAL_NFCV_MAX_GEN_DATA_LEN \ - (RFAL_NFCV_MAX_BLOCK_LEN + RFAL_NFCV_BNO_LEN + RFAL_NFCV_UID_LEN) /*!
    © COPYRIGHT 2020 STMicroelectronics

    s)& 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%0A% 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 z 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

    2. Configuration

    &lZMP0o;ht2SJ@vHc6c;m%^iATiYje!0|<*d)-84jbhS;s(yJ(~H(Ss?}<-V&qsX5^h zkUz)0$8Ohz&5?SiPD?SCo=CIb4_I=q%pGnx+lQ;BABFj@&nxGe^iHfchWw$xeKVAcKuv{`6%Eb}r&i4uEJm;saYJTa+v9JS#xI<1;^eLLk6W(`#$4>%PF_or!tP4S zOzMu6i2ag#$P|J2V8+^-|6YXcBll_KDsJE2?ygi+fJST#eHrfU&*;xjUrtYXMn7M$ z^||Sb(!H_vmiGNmyMhg1Cs&VdE;Mt-IVwwcrlg!_&Vw#!asB>$u;%uvXfs7AC50=% zpRw|N$>>J(&L1Y*lE8Lj__8@f76q^%Gkho@TRM?SaitK+A%R^K6aesR(eMN|!O;Op zV$dPP9Un+AoykK30LmoFV#N-gJ@(l*u%(C-;s%>_|a@aSroTW zCp;H~Ce~ThG9syw) zEDFd30@EYu>+6F|5D*HP2=_7YC6aVOaD6xo3gg`-dN30t+yDtffd0O~ynrmSFVYof z^>;Yl2@UpRvzbUJl*8dbI0g^~iwcDy5D2I~914f)@jUc`LIT*tV7-7KwI2*PN)U-f zW3p+C0MHI2(T8!CjRy0?{yPgg^B>xPpua1{s~I$y$b`Zm`a4!7}Ki)xX zSIU3%{#W84d#E-#d1o<(TAh-bp z2GVjQl4t=t7VWg260t78WKZ zhY%(hgaHm?Wra2PfyFUMXX%sx_75!iKUl<1u{#4nXYwNBC@k7J3fYRqpo9L(8cF-P z7UQ4t{f#C6T#Lz1u~42E=uU6{U$1|dcmuSv{bzW2hkwSO62O~z7H_!8UA2pOjBYOr6$rKCie3z7@InH%mQJ`zjCb_^0Yo%bT>t<8 diff --git a/assets/icons/Infrared/CoolLo_hvr_25x27.png b/assets/icons/Infrared/CoolLo_hvr_25x27.png deleted file mode 100644 index ae5316e4d49992b01a39a4298e36cac1fd365388..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3657 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 P_yK!sCtMlE=iI*m`3F!x diff --git a/assets/icons/Infrared/Dehumidify_25x27.png b/assets/icons/Infrared/Dehumidify_25x27.png deleted file mode 100644 index dca77ae410bcc2196a693023640372da1b1912ea..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3665 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 Y0_*BPx59$cJUb46GqS>#p?%qL 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 Tfe|}@hk%WR1FiydCgT49&LB(v diff --git a/assets/icons/Infrared/HeatHi_25x27.png b/assets/icons/Infrared/HeatHi_25x27.png deleted file mode 100644 index a1724f995562587ec54740789f37729d567e0f88..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3676 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 diff --git a/assets/icons/Infrared/HeatHi_hvr_25x27.png b/assets/icons/Infrared/HeatHi_hvr_25x27.png deleted file mode 100644 index b92108d68de2ab09bed6b451e36396881e5611ea..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3661 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 T-|@M}j{(?QJ7Y`Hry~9hE1FGt diff --git a/assets/icons/Infrared/HeatLo_25x27.png b/assets/icons/Infrared/HeatLo_25x27.png deleted file mode 100644 index af2e59d4940aefa657c280acb8073800408dabba..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3670 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 O0dr$(Y#G`u;{O0;Ay2me diff --git a/assets/icons/Infrared/Mute_25x27.png b/assets/icons/Infrared/Mute_25x27.png deleted file mode 100644 index d8812dd4fbac915cde375ec6be18cb2c7cfff6ec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3670 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 diff --git a/assets/icons/Infrared/Mute_hvr_25x27.png b/assets/icons/Infrared/Mute_hvr_25x27.png deleted file mode 100644 index 155bd900438c969a4703694eb743a321ba4c8aa4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3657 zcmaJ^c{o&U8$Xr`MY4uujA+9cTTI3_mQj{zY@-rm42Id78O$isqLggevL=+&(56hX zRSMan$eNvmhJ@_jc)z#z`~LX)&UKyh+|Tp;e!u&^pZh-7bFeC>sOMz9X0SO;WvU3oaaG_qe1BtBaI z+IQ1i0GJkFF~b;-Ojh_FQUHE{+NQZjqGn+IVkt_^6#&Hp{a}f)t$cB4pjOkVRtl)y z2|RLq>Ma5!0sxV9)*KAn7X>DN+8A#FItmhcWPy&HT?IlwG#`+<&w3BP=}W-V`8-;k zzv?xR%{B#V3RV^Ig}Xi1w&o8n-vk83VB)r$dII68=IRXq$bcU}?3Qd40NDy;YqeJP z7kE%3I7mLAsy?YPSlRvJKElJ`jFfa0N#vM-ZPPXY zkY8+hwPs)vH#RyvFzPc#9wQ^|0@ND5M}d_A z1t)+m7r?J?(OsuoIQzKBhmgf6mcfeY?yTFWS|i0>B3rjkse0Wm*BXj4Ue_6*^hbrw z^LE#|L#Bb#X{M=#Rk;^ePh-hJMd+BZdO>q2d0|XtttR5!YRvwYLEXnE)upe^V6Jjd zV7nUD3v|4Na$VA(#P~Vw8KvY^;e=pOfPLAa%KrJ5XZ6IYuervZrf93ByPTWIDD7L?VZVHa!ysS4#8)S1mv@Kqz#A{zk(Wwj`d5NJU>B| z-R!wD>-EmE%ffhLph+1#hZ9Ut76WW(`d||Kym!C;mXw7x$Fpuf5Brw=i_KyN4&|1hmK( z?}mpz&&h8&`ZbUNgkFomg||S(e6Dw5PhP)b>3J0+uT~?28^P$g#OmP`F^7Vr6{@nZ zT%kSuQQ?x4LZ>c?zb0OYi?hbqVi5OKCsm3iN`+j7FJ6Ma-V08+=TNK~BC%M_^nyK( z!&tG^zANum&Qd+KS4`3)0l7!4?#8gah_Y&&w#`cW zEc7It4N8eGiQ69MaAUdd&2Whn>}}+rt#HmBbY7yw5k0)gh~S-D+UBX*_m4FeWPK94 z9|^m4+=i4Lgn~F;DUj3+OjB)4mqFC$Y2>zjNF(?X7W^RNj8fs=i@PmTKBTk;eBClW zHh%GE@HWxQq8DqBUi;(%mvXLBH?A_KEk&96jC0C-&}f|FmIe#*rrFCefxd8Uh+78jsuk3w;X13J-OK zbX9bDP0^-grY5?$fz(XXO!dq;7zwrzC|s&vdaN{}v^%i*oAF271Z!M)?){wDcyEjK z9O-B7oa{GlJGVvYke05Hew-W;YJi(=h{JsixJlr&OkWzfx1-*#G``v|x5XxRFGubB z72hE}rmV3Ko3hJ`t@{Pv6JUh(B4~H#Rp-4OuGrt07?9>u+EVuqj?pS8LJrboKRg>J6e71g5vR(25`=*Pr3(IBBE&j~ zHe+e?ZNj2%O0)z@mFy3NMVwBLuXEjAZS`BKpyW4lfTy7TX6Az=@U9=r< zcrFy#Z%vHkC=ujRWfw!G_8-u`ulem*u_NO&V?n*0J?ysZTOKnLTX6lw;coxuv>;!$ zKM_sDO{R0gIrll|nOA;U&ZI~%B=-nUT^+9OsD@u1qVtat({wV1)0~nSxT2AsoENgA z<4yHh!A5R+ZgFP~B5q2gJTV!$2r@@Ob~=pYlkF5Ge_TqBaImb%iOxCpxP)`W=*DQK zOVYeS@8M9ylHX+qtH5_U!Vc(zta!N_FH~mjTrAqZu$wzicWM|WW+Z0hqD2YG#RorY zmFz2V@Hu!eL3EfkN_wx5aOM5Nu*xERwp*GQVcU{hj4ncn$=E{f9b}%^d4kD7mLrdU z(&_VA`?jdE^@7aV^0-GR&ZDOt9H!e$Ke*EJrHo3HqinDy)E!;w%_K~>hSvMlKS^Fi z4PwyFKm1ueKVr}qTkqUK9K7JFn^cfkS5GebdHe-V8k^+p%=J%cJK=KffNYWno65dd z^y`roXLQij-lOg89E;S|_2o#(`>6@!MBW6Mz2H{)@JPA8S=)!p)1rk6Gtnao9;72* z7`H?G=VBU*8{any=wbDyPBc7ccmKGppF1CtdCtAwWwr1@NkykxS9@h|@R?b$i;B{R zzi<;L%bp!%x(1Y6hR%^&zhCH$mG@F8tsD*OR|bLY&Kc}wnuKZ8Q-d01Kb zM3{uukKT=hQAg6lDP8GPX~EB^#}A0THXC`hx9{%RGefAax0~MdylL!9Jy$X(G=XFt zY=u<@vNdu^+3?%t+^^SjGZjxMT33{;hjF1pxWF2{Sl6uV%zf~g#o-GHL;2qMS()}G z@K32dtOeVZ)ByKm?&ZFS<}4Fg#&_EikA=F~FJrOp(`D43;03R4)smxy@FKW%74l{(D)8~yXI&zkPHSmoSzlL{qdwccGE)zZ0NLGx3x z!6M&nd)z}}nGaVgx>mn!S=#>XT=Cjtd=;txmuHn{^BcGATruG*S!;>wAFm9wS~pm? zsaUB*$7U)iDTvCK|L|@~7|4H;FWLM)%w?T5L4PpTw>r3dv>CI~F}-y$s=2a=RWM8A zJ}7tyt5K;@Dr;iFXYQ^PHg$(`Io#5|nsTMoeJiGGsM(e!TuuH5hXv!Yvui7mg-f%` z;fAU^Hoh+zmKcU3#fK4$rQrcHZ;BTlWJALG;?a1lH|umA-Ut8$lnG842FA`7hNF-) zu^SjoCW*>p1AvhUlZwR!;29t5bqCSpDTgv zO$ny&{|jzs2eTp58CWt7Z({)m^8_>r1aBC^{E)e}u9+6X(9!@3wKO+?TIrcV4H0?< zmI$boKH@jmf`SVq;mM5OT<`yIp?~CVbOMRW^K5~q5rXjERx}C;^jF3(!XINXwEUyq z-(2rM#$ssoM=pdH3}mCO|5fMTOT6aUF#qXV-sGRY$CG)@PUCg;307b#?~T}uvURfL zjqB^{cRFwN@+@~73o|EXSKI!)VUT`k&*1tyk;o$lsJ!ii>wg5|;vUpezsAyjZD YaFqjKH6{atc#Z%YOM8nVbI%L^0xlYDPyhe` diff --git a/assets/icons/Infrared/Off_25x27.png b/assets/icons/Infrared/Off_25x27.png deleted file mode 100644 index c15100606ac9b2c46fec8d5ed290d72345fb22e2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9530 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 T&A>rCE`gSso@%+Wb;y4K-_9ky diff --git a/assets/icons/Infrared/Off_hvr_25x27.png b/assets/icons/Infrared/Off_hvr_25x27.png deleted file mode 100644 index d5e5e6f45d78e070d06e033bd529d0067de94301..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8460 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 diff --git a/assets/icons/Infrared/Pause_25x27.png b/assets/icons/Infrared/Pause_25x27.png deleted file mode 100644 index a371ba81718e864efe908e61f34bc842079cc20d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3634 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 diff --git a/assets/icons/Infrared/Pause_hvr_25x27.png b/assets/icons/Infrared/Pause_hvr_25x27.png deleted file mode 100644 index 472d583db0ff9adc5ef810675c8b915c6e5a899c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3623 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 HpN{x9W3^Ap diff --git a/assets/icons/Infrared/Play_hvr_25x27.png b/assets/icons/Infrared/Play_hvr_25x27.png deleted file mode 100644 index 6708dcdbf2c7ac0c656bbb1c2441fb0f4d6fc9c5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3643 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 SxG~3f0odCdwl1|i74;vAA8iN# diff --git a/assets/icons/Infrared/TrackNext_hvr_25x27.png b/assets/icons/Infrared/TrackNext_hvr_25x27.png deleted file mode 100644 index a4de4fc3cbe582349b8c79bbb7ec6e931e8bc792..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3639 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 W@<`O{ko&lf02|BW7A0mL7ybo9V{1nM diff --git a/assets/icons/Infrared/TrackPrev_hvr_25x27.png b/assets/icons/Infrared/TrackPrev_hvr_25x27.png deleted file mode 100644 index 838055341e568fed60b3e28a280fe839d2c9db9e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3644 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 diff --git a/assets/icons/Infrared/Up_hvr_25x27.png b/assets/icons/Infrared/Up_hvr_25x27.png deleted file mode 100644 index cf71e59655a8b2a480f3809cdc10853d3bb62b78..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3630 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 literal 0 HcmV?d00001 diff --git a/assets/icons/Infrared/celsius_24x23.png b/assets/icons/Infrared/celsius_24x23.png new file mode 100644 index 0000000000000000000000000000000000000000..64d7a1db15f32f41a18192266d2547b05924bd0a GIT binary patch literal 257 zcmeAS@N?(olHy`uVBq!ia0vp^5ye{*DclnxWk0+KE{?i6sa+}CXNcDkwS{cRTO%}r%pg7Hfd zM0Z{O?6E29YaYAw##{Ca+*w>FUfg}P=T<@9{|l^dcJ=Bpo^ufZI-kMQ)z4*}Q$iB} DZpUS8 literal 0 HcmV?d00001 diff --git a/assets/icons/Infrared/celsius_hover_24x23.png b/assets/icons/Infrared/celsius_hover_24x23.png new file mode 100644 index 0000000000000000000000000000000000000000..0488b40f571b62a87a8dc73d23c6b0163a82e2fb GIT binary patch literal 204 zcmeAS@N?(olHy`uVBq!ia0vp^56zNUAg?5KVx_Kc@S=9{hHS5cOa^8B(;ufP`bqj$ z9NoHtdFxA~ys}*i$@`SQiKM-ox~)Dh_GcX9iJey;75#{@)!4OV;hzPoOijIO=7jKQ zB+uk!6N$bzk+-t(0h`{EFgX(9OUk#;OXjYW@u?>0OyZ7bOsX0q9awY=}j#~Q)lYwtFH`n2+FfYPF`T<1TDF!(&aQLnf1 zpX9cSTbC&8+;&oHYx?i$I;M=09+Tf(Z`7H*_RfTS-Hp@cUHWa?^=WGN|B3uhcId6N WNR!|HO@kfiKn71&KbLh*2~7a>0CT5LZ7#^uqS=xg=h+l^>MX=)LYd;e1AencD=Xu`uo%hM_i!ZMgpVg{& zCsS8a0Dwt-L95gCGJUU}AEV#*{jU!INU4sh)^t^cetdfU>C3kiXg zTVxq`NMr^G%-Oh&pzfNhsE$l~^U)Vn0$}XA(`b>Fu_#;EWz7V`#;#A<0F-89-?Y{c zfo-(vcnb6L-3JCbw!$=X25N*Qs0b1UwHmt2ROR|~SJFpb@WUAmIV#3&6_d+>V zn1QfNO#Q}mjHlMbHtOyGdE97}e%-)6~FBCYwzzLZ&FUWje z&PZ_hFf?6YcjUTO8RnuprN?v$@nw#SqKJ($EDlyVL6Rhn7dcT(Q%E}8^oSX!y>NOY zp`p+U9G^JYgNdZs#v4RoXsHK1xV|!y^}^vr(JSL()8_=1PkI^wdxmj3)^!izA*rLz z>FBVr=_9U=Lc9@JbatKTB$qF%0Wt{=8W^vS?y=Uz1c$vY_93;!&8FkoI0|P5c*Br& zFC?aCAzf1#s=zvqEoUlfUMm-}s#GotLU~@w@v0_dDpH|bDT;;Sh^t|1!$lq$aqS~+ z?ijasL{9AB`ZTqM0_QQZD*<-lpl8`Rxft=7;E~>tYoA<9MwNOebi&bUxMa5f&3bf1 z9h8IvUnU-`B!or!4?);s*)r++}S_v!h*zq7Or#=!kuFq>-bKcoskFITnQ I`IRUC07P^?p#T5? diff --git a/assets/icons/Infrared/Vol_down_25x27.png b/assets/icons/Infrared/ch_down_hover_24x21.png similarity index 70% rename from assets/icons/Infrared/Vol_down_25x27.png rename to assets/icons/Infrared/ch_down_hover_24x21.png index d7ae44558228a7527d2267bbf6aeb8297ae50a8c..9b840f9ce30dae6832c802c37c92cb483fe636a9 100644 GIT binary patch delta 302 zcmZ1`b6w1@Gr-TCmrII^fq{Y7)59eQNJ{{*CFgX(9OUk#;OXjYW@u?!EYmL&M>xsklEe@f1Q_4=-pp1fPxePB`LDU+jTw%lwe z@(BwT;Ru~5$=a~`*7q3;CVctyROXrp!?kJGcRiZ7mR+R9@>Wvo=|TnZ$e&S$H+8d< tUB9fG`Y(I88SB#->()3V@A$ied8bX%HL<2Mn}LpC@O1TaS?83{1OU&~X`BE6 delta 1027 zcmaJ=&ubGw7#*=z8(L^DfByVGV{ z!4z+L67)}~UIfvzpjfl|nnPJKqfNz3=;G-h8|G^6Kp4O8L%2 z>T(JIFkvieRXSgy-<1nz>HmH2>pcKcs;jCMLselA;+DIP0EqYBmUF4@j&yx0ArQSz zmT`-8tPp{sgPRB%p0$dq$a1#se@1fvoW17O>ZEQi$u{;_E5Wd_7f?0;bMtXv*=vZv zCR%lUh57aF0|Q-0VOC~L-V9XKb{Drpw7gxe+1qQjLFV&v zR)T}U&~%~GlB-&2kc+OAF4HC?kU6f?>9CzFi^Ek;kR*xYMNSkm6q1Ryd}75JKS~cJ zG!)sP8xR-!Fp;#Hc!MYmE%l%WFHnZEel$2JdSqN|1)RY0Nl!yy*ECPZdfp*CB30Bn z9UaxS0>o8Ogf~K)-d!u59qe4deCUIab=3;HcZi0i?E=UU7W~ccL48ylKjY z9}&y9k)bIJRbXA$k$EjA=A>*^mGpuj=nKU>uNU>=jH+iBN`?IFkgH*P!$Uq9a-Ab? z?ijawL{9AB1vIsWLU$86r4W0t-?QwVT#Rr`@JMgKbxtluRHg0-op7`oF1g$P?s~XI z9h8K9UnU-IT+vq8tFk>Yp* zyr_UQJN9OJbL{Eg-_Jje=k~-jLxSDO(&*3XWU*BGZcM!zxxI4+>^=Zbvggk|qcXtI K%i8OO#-o4tOFr8G diff --git a/assets/icons/Infrared/Vol_up_25x27.png b/assets/icons/Infrared/ch_text_31x34.png similarity index 70% rename from assets/icons/Infrared/Vol_up_25x27.png rename to assets/icons/Infrared/ch_text_31x34.png index c4d9e87a0657b6c2ed093679528796128a3d0127..30e0f584c2e89c05ad406326470311c1ee2bc35f 100644 GIT binary patch delta 324 zcmZ1@^H$8SGr-TCmrII^fq{Y7)59eQNXrAU5(g8I)X2mFWMJ^<+-PCKRj=!AaUQ5kA=x9ymw};5je((| zg@NH0P^jSr14F3+1H-EX1_rAc3=HB0b9M#V03|pJJR*x37`TN&n2}-D90{OcpQnps zh)3t!sR#KQ3^<%MKmM=ZDR;p$;ijvN&L!z>cAZ8no(xJ(L0V5Tv!<`Qz_>K@!pd3q zzT_+m=WMlBc>UX)`GDB$JJRQ-ADoruW)#8I@Z`qfxSL!v1EMUx+3l(6e}C7g+vxl9 zo1f30f2(uKM#25z{*%S)_&5A?dFOL}@|U3VnX8_%neKgQzeHRvyx)p(ZnJmlTh6dt Qpwk#UUHx3vIVCg!0MsFFv;Y7A delta 1033 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 LNkvXXu0mjf(wcu| literal 0 HcmV?d00001 diff --git a/assets/icons/Infrared/ch_up_hover_24x21.png b/assets/icons/Infrared/ch_up_hover_24x21.png new file mode 100644 index 0000000000000000000000000000000000000000..944a973f4a44c288b7e119dfba09f0f52c88a138 GIT binary patch literal 2898 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 diff --git a/assets/icons/Infrared/dry_19x20.png b/assets/icons/Infrared/dry_19x20.png new file mode 100644 index 0000000000000000000000000000000000000000..c689c067585eee19cd671210de06ed1c4cf400e3 GIT binary patch literal 996 zcmaJ=y>HV%6gMhGRcWR2F#rQPxfLoB@%bxFY}IX(I-!>8C`wZ`8@bq*#%k>|_LaCD z7%GMq7}yz+U}IoqK;lD)nTB$?D9LG&t zYi5nvE9|{GF~+`)C%?kjb+l;#MDA2Kw@EiNY^a&|BUExhdo2LJQzJ05th!Ea@4(GFK}$6Gsy@Ww%rv?>xselcy;EdVaS7C3f(dI)214B6|9m715J=JjwnhXrwJ(U zs=2J9YIlGv$_f+}C@UFB(Z#H;Wov37FUL*>-=Q?rA?$X$LRS$;v<)Rq z)1W9rSZ=h(ltgtxb_W+(?GQ@h z4hey*3OO)uBgYHU$n74)wsk9rDGD5HnFh~D1kZEz1t}|KO)ame1x=DlrJ|OXOIo2^ zEXqn=74rkENt`_&2XugS&#>A^Y$^ypWR*=Ec@MB#j))I>Q|sR7UKU33^|9{gUUDO` zkTnC--Tt-fL5K+`b%)|*hoSs&z%(B*aUUz=OYFzEP+hN=PEJmkyZQd>LuOVivrtdI zwVKVQIX<<*-Fn8&zMNTmG$)nkrnWvG9a@(^v~E70F{d_@&ivpVd6_SgtVL@2qmOF-SnebOS>J-z>l>Wetd5feY;XhGQ7O+xGp2?Jfy_W}d{c>e(c&o$s--j?jJLIz%K9Fc=@qwS1`j^@JM zyI?2LIe||Y28n+h#Cl@DDX-4`G>jmarm&#_&ztJlO;Di`0h%ah9Z8ZwK@)M&%@y)$ zPTK~Gq^L+zk)md0RhRO5E)Ql8|#^-4rAqfwjE2S84es$Hx0;1M9*{el4hwDwW_JPk|xWRRo04%rIo7XvZ5Aq zQgMzosdM6!fX%V)1-7&ln+n1Yd1aGC-UH%RBkF_c)VjC4m%>uM8P;9iOK~X{@n%rE z+rM@_4{-sd?n1o$u#i6qxaK1+?%!v%9{({`oBM5x+sS0|`s=Ij+!O0&shxc59=Yz` zrM(q)YjqoLW~!Sn4qw*fcTdl*eH3etetovyJ~8$lt_Y9{cZQwoUpP{zTMhF~`RMT< Dw7e|g literal 0 HcmV?d00001 diff --git a/assets/icons/Infrared/dry_text_15x5.png b/assets/icons/Infrared/dry_text_15x5.png new file mode 100644 index 0000000000000000000000000000000000000000..7696e1fc8a48c39e06ea76f22917b449ce0cf960 GIT binary patch literal 968 zcmaJ=%Wl&^6tz?pRTUL1DMD!EB2gsb@gr$sD<(=}hgzzukOq+ji%vX~#A@v^_LMkT zAk-bKD)9*{SiuhvzrlryJ{c*9Ae?aCWU0 zx3~Day0*f_&Am@OQ^CIT-J^z44K!W3loC zlv9HX1cV`w2FGDyq^3C4HMpOJp$MiBc4Uf|MRnXJsMDALT~czMEGwX>OQ?kN#e$aC z9|KiZH7ILP)pCkv$OR)`0JBHr31i$dT2^D0j-O0%z*uBJI2;b8p(fF|4;5Y4p{znx z&2dC78HEg`xiHz9D_A7)Vn1R&4MC=ey7YvZA}{u`iy&IahRJMFd}c635mY2O>uL^k z-Ty;_V1Z6pi(J)vt#HyFMFh4;LQi6k-(7DjOBEUQm>@>uHl@e&K{f}J(PTg)pys6_ z*mjZUhgsy&l;XOE6DACW9&s#FGf{16fNbJ8RxDnF;OuIJxYiik13QED!t4sA}T)e!N{8BFD z#lY-t|GDdVh%Zp)E|!-c7VA$!zVk6(?u$>~hWy94(cEj>lgWg;&wu=W!_5Q7saNP;_{w|J&?IKspN_KU;YYGu{ zIQZ-AyDfEZxfvD&}$3 zB9kMQB#7=h?K^u@)>pr~{%e-9--wZ!_vaX&)}kj%+vO%L?^1Ahvd0_feg;ohKbLh* G2~7ZSg>Oj! literal 0 HcmV?d00001 diff --git a/assets/icons/Infrared/fahren_hover_24x23.png b/assets/icons/Infrared/fahren_hover_24x23.png new file mode 100644 index 0000000000000000000000000000000000000000..db922c5576f8748eafd7bc4100935fe07c5358fd GIT binary patch literal 205 zcmeAS@N?(olHy`uVBq!ia0vp^5C67P$`twqeT&>4V^+H|;LTO0rM%T4NxUb|Iq3z+ z1hhR>vsf@=>fh{*3K|Bik&$~pxJxF#z>#oR`>C^Vf7i2 ziL=6+R4oZYP_#h8{N6czuYc*O9q7EZ*SSXi iV*Z9dymI1z$)&lec_p4Mwn{(|y_C!pE2v&eTzV(l^BO2X6`A2w)XyseQ)G@)(P>_% z{2h;ydVwLq7T^=&3Z(!4|G)I+{JTJwqo<2wNX4xylMICz6nG9D`fvZXbZzhR6+I~p zaf+O~PsuterZxO}uMsQG!pfUDW!r_OTe0UKO`iXwjIVlguO1^C!JN!WkYE z7I|@sFx!H2O8XAwT#(w{#udY|MU-cYnEKyi3ZZXn&z}0{_PDuEraM^b)-4ZzuYIRd zZanJ_E()*}njmSIo$uhbaiy@>Jtm1$_kNwsn7Dp@+h67vHxjw?%KlzaGp~OAkN@h* b`xlva|1J_f(ZI0;=tu@nS3j3^P6x~N<3RoEaj5InMqrw zi@MHqVmFC&m_EB!t2=xV?-QQr_L<@@u7@8vBfL=id8x_14W@?=O%Z&1%WJjQo~b7{ zxQZ`(vY<*uK~U*Zo`ciGmCRv#xDN&#dj2E#d$WyQcD3??t?}n>P2}D#Is5U=($&5; izfb%R554~W1@q^~QmJVwBENx7W$<+Mb6Mw<&;$Ujw_9oe literal 0 HcmV?d00001 diff --git a/assets/icons/Infrared/hourglass3_24x24.png b/assets/icons/Infrared/hourglass3_24x24.png new file mode 100644 index 0000000000000000000000000000000000000000..e7be1e99557d8984bb5bb0a3d7ff053d730198e1 GIT binary patch literal 223 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1SIoCSFHz9oCO|{#S9GG!XV7ZFl!D-#Y9gR z#}JF&t5Xkh9WdZMoqTXz2M=wq18=ukM500XWqH5J@F>+pMB`=eT$`gouX`B T6mIqgI*!59)z4*}Q$iB}Hpo;m literal 0 HcmV?d00001 diff --git a/assets/icons/Infrared/hourglass4_24x24.png b/assets/icons/Infrared/hourglass4_24x24.png new file mode 100644 index 0000000000000000000000000000000000000000..49eee2f53a2ef63642ef09e216a1757f6fcedb86 GIT binary patch literal 239 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1SIoCSFHz9oCO|{#S9GG!XV7ZFl!D-#X?UP z#}JF&wNnm?F(|UQzC82)|E9&uB=Wby$D$`4nw!iV zXC3P6S|ffUAj0(;%g!mgj_FTfFY#k+sQh_&x@FmJVYSOEC!M)(@@z?O44X^j_38dm z3%9SEGiipZ(n=krw#)Oo@4obW)nMWpaa&>fCdNzmCrAIg6!BulicVGcUDgkSSMX=q k-42~#v*<<5@*C^&To>$O{Qe{RE6}kFp00i_>zopr03<+P1^@s6 literal 0 HcmV?d00001 diff --git a/assets/icons/Infrared/hourglass5_24x24.png b/assets/icons/Infrared/hourglass5_24x24.png new file mode 100644 index 0000000000000000000000000000000000000000..90e1d4b4e7cd09bace77a8ec0570a4c7dbe9d25a GIT binary patch literal 218 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1SIoCSFHz9oCO|{#S9GG!XV7ZFl!D-MUSV8 zV~9oX+bIY68Web(SAP6oUnys@ndxpzfzdmch;Iv`8PmeTj%;iBR4c&1Q2aHn`tFG{ zY7q=IFRG`0*t)6J>OJG5Bs<)JxOi^`wJ7Y#ETl(=ivU^ls=7@G~4&JZ%;_Z$9=lvct>~csp{r-X180anr MPgg&ebxsLQ09GzglK=n! literal 0 HcmV?d00001 diff --git a/assets/icons/Infrared/hourglass6_24x24.png b/assets/icons/Infrared/hourglass6_24x24.png new file mode 100644 index 0000000000000000000000000000000000000000..e68c744f0c4490fb7f38b3de74e9e780be2d4175 GIT binary patch literal 238 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1SIoCSFHz9oCO|{#S9GG!XV7ZFl!D-#R5+k z#}JF&wU_VmH7Ibf1r+T2uYI}ove%k$bR=DNDrXcsP8 zRw2gn^$p84xgg7LI$yGqXM{05GWXFA|MxLAQawC4?6*xz)UV}PtJcN8e%0H$DvkHw zQnM8%<+_`8?mc!}k$dV}lcqq8ISXXIbbtMNajB{4l1#;!*Dho=atYpiy=`u*lgSpN hjGfN6e{$TWABO1CAT>pSv_xxF$iiVFAnjT6!K0W$o2Q7 zD5tQO5tbrTFL^DPx@o)3mTCXq^p9N!AV U9lNT|16{}9>FVdQ&MBb@0Ee1Vo&W#< literal 0 HcmV?d00001 diff --git a/assets/icons/Infrared/max_hover_24x23.png b/assets/icons/Infrared/max_hover_24x23.png new file mode 100644 index 0000000000000000000000000000000000000000..65f97b0ce3a52af81042eef38574f0ece8cbf1a0 GIT binary patch literal 208 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 Q#{d8T07*qoM6N<$f=M%l5&!@I literal 0 HcmV?d00001 diff --git a/assets/icons/Infrared/mute_hover_19x20.png b/assets/icons/Infrared/mute_hover_19x20.png new file mode 100644 index 0000000000000000000000000000000000000000..e9a5b35102782da59c7ba4993bf8c60f355d4bdb GIT binary patch literal 2877 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 literal 0 HcmV?d00001 diff --git a/assets/icons/Infrared/next_19x20.png b/assets/icons/Infrared/next_19x20.png new file mode 100644 index 0000000000000000000000000000000000000000..512b68745ac452eccaab049c59fe7133db57df1a GIT binary patch literal 2894 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 literal 0 HcmV?d00001 diff --git a/assets/icons/Infrared/pause_text_23x5.png b/assets/icons/Infrared/pause_text_23x5.png new file mode 100644 index 0000000000000000000000000000000000000000..72c7b04036f195a8362e8fd69e1a284a5c2eb9d0 GIT binary patch literal 2856 zcmV+@3)l3CP)StO&>uS)ve<0AYj>5AR{$W90N^4L=L-RlQUJ&DC0@ZjPh;=*jPLSYvv5M~MFBAl0-BNIsH z15C~g000{K(ZT*WKal6<?_01!^k@7iDG<<3=fuAC~28EsPoqkpK{9G%|Vj005J}`Hw&=0RYXHq~ibpyyzHQsFW8>#s~laM4*8xut5h5 z!4#~(4xGUqyucR%VFpA%3?#rj5JCpzfE)^;7?wd9RKPme1hudO8lVxH;SjXJF*pt9 z;1XPc>u?taU>Kgl7`%oF1VP9M6Ja4bh!J9r*dopd7nzO(B4J20l7OTj>4+3jBE`sZ zqynizYLQ(?Bl0bB6giDtK>Co|$RIL`{EECsF_eL_Q3KQhbwIhO9~z3rpmWi5G!I>X zmZEFX8nhlgfVQHi(M#xcbO3#dj$?q)F%D*o*1Pf{>6$SWH+$s3q(pv=X`qR|$iJF~TPzlc-O$C3+J1 z#CT#lv5;6stS0Uu9wDA3UMCI{Uz12A4#|?_P6{CkNG+sOq(0IRX`DyT~9-sA|ffUF>wk++Z!kWZ5P$;0Hg6gtI-;!FvmBvPc55=u2?Kjj3apE5$3psG>L zsh-pbs)#zDT1jo7c2F-(3)vyY4>O^>2$gY-Gd%Qm(Z8e zYv>2*=jns=cMJ`N4THx>VkjAF8G9M07`GWOnM|ey)0dgZR4~^v8<}UA514ONSSt1^ zd=-((5|uiYR+WC0=c-gyb5%dpd8!Lkt5pxHURHgkMpd&=fR^vEcAI*_=wwAG2sV%zY%w@v@XU~7=xdm1xY6*0;iwVIXu6TaXrs|dqbIl~ z?uTdNHFy_3W~^@g_pF#!K2~{F^;XxcN!DEJEbDF7 zS8PxlSDOr*I-AS3sI8l=#CDr)-xT5$k15hA^;2%zG3@;83hbKf2JJcaVfH2VZT8O{ z%p4LO);n}Nd~$Sk%yw*Wyz8XlG{dRHsl(}4XB%gsbDi@w7p6;)%MzD%mlsoQr;4X; zpL)xc%+^yMd)ZNTI#eJ*$O)i@o$z8)e??LqN_gLa_%;TM>o2SC_ zkmoO6c3xRt`@J4dvz#WL)-Y|z+r(Soy~}%GIzByR`p)SCKE^%*pL(B%zNWq+-#xw~ ze%5}Oeh2)X`#bu}{g3#+;d$~F@lFL`0l@*~0lk45fwKc^10MvL1f>Tx1&sx}1}_Xg z6+#RN4Ot&@lW)Km@*DYMGu&q^n$Z=?2%QyL8~QNJCQKgI5srq>2;UHXZ>IT7>CCnW zh~P(Th`1kV8JQRPeH1AwGO8}>QM6NZadh`A)~w`N`)9q5@sFvDxjWlxwsLl7tZHmh zY-8-3xPZ8-xPf?w_(k!T5_A(J3GIpG#Ms0=iQ{tu=WLoYoaCBRmULsT<=mpV7v|~C z%bs^USv6UZd^m-e5|^?+<%1wXP%juy<)>~<9TW0|n}ttBzM_qyQL(qUN<5P0omQ3h zINdvaL;7fjPeygdGYL;pD|wL_lDQ-EO;$wK-mK5raoH_7l$?~Dqf!lNmb5F^Ft;eT zPi8AClMUo~=55LwlZVRpxOiFd;3B_8yA~shQx|tGF!j;$toK>JuS&gYLDkTP@C~gS@r~shUu{a>bfJ1` z^^VQ7&C1OKHDNXFTgC{M|V%fo{xK_dk6MK@9S!GZ*1JJzrV5xZBjOk z9!NTH<(q(S+MDf~ceQX@Dh|Ry<-sT4rhI$jQ0Sq~!`#Eo-%($2E^vo}is5J@NVEf|KK?WT&2;PCq@=ncR8z zO#GQ^T~S@VXG71PKNocFOt)Y6$@AXlk6rM*aP%VgV%sIRORYVwJx6|U{ozQjTW{-S z_si{9Jg#)~P3t?+@6&(!YQWWV*Z9{iU7vZq@5byKw{9lg9JnRA_4s!7?H6|n?o8ZW zdXIRo{Jz@#>IeD{>VLHUv1Pz*;P_y`V9&!@5AO~Mho1hF|I>%z(nrik)gwkDjgOrl z9~%uCz4Bzvli{bbrxVZ0epdf^>vOB;-~HnIOV3#R*zgPai_gEVd8zYq@2jb=I>#f& zAH2?aJ@KaetGWp*@#%3%&q3HdEvyfL~q!0000StO&>uS)ve<0AYj>5AR{$W90N^4L=L-RlQUJ&DC0@ZjPh;=*jPLSYvv5M~MFBAl0-BNIsH z15C~g000{K(ZT*WKal6<?_01!^k@7iDG<<3=fuAC~28EsPoqkpK{9G%|Vj005J}`Hw&=0RYXHq~ibpyyzHQsFW8>#s~laM4*8xut5h5 z!4#~(4xGUqyucR%VFpA%3?#rj5JCpzfE)^;7?wd9RKPme1hudO8lVxH;SjXJF*pt9 z;1XPc>u?taU>Kgl7`%oF1VP9M6Ja4bh!J9r*dopd7nzO(B4J20l7OTj>4+3jBE`sZ zqynizYLQ(?Bl0bB6giDtK>Co|$RIL`{EECsF_eL_Q3KQhbwIhO9~z3rpmWi5G!I>X zmZEFX8nhlgfVQHi(M#xcbO3#dj$?q)F%D*o*1Pf{>6$SWH+$s3q(pv=X`qR|$iJF~TPzlc-O$C3+J1 z#CT#lv5;6stS0Uu9wDA3UMCI{Uz12A4#|?_P6{CkNG+sOq(0IRX`DyT~9-sA|ffUF>wk++Z!kWZ5P$;0Hg6gtI-;!FvmBvPc55=u2?Kjj3apE5$3psG>L zsh-pbs)#zDT1jo7c2F-(3)vyY4>O^>2$gY-Gd%Qm(Z8e zYv>2*=jns=cMJ`N4THx>VkjAF8G9M07`GWOnM|ey)0dgZR4~^v8<}UA514ONSSt1^ zd=-((5|uiYR+WC0=c-gyb5%dpd8!Lkt5pxHURHgkMpd&=fR^vEcAI*_=wwAG2sV%zY%w@v@XU~7=xdm1xY6*0;iwVIXu6TaXrs|dqbIl~ z?uTdNHFy_3W~^@g_pF#!K2~{F^;XxcN!DEJEbDF7 zS8PxlSDOr*I-AS3sI8l=#CDr)-xT5$k15hA^;2%zG3@;83hbKf2JJcaVfH2VZT8O{ z%p4LO);n}Nd~$Sk%yw*Wyz8XlG{dRHsl(}4XB%gsbDi@w7p6;)%MzD%mlsoQr;4X; zpL)xc%+^yMd)ZNTI#eJ*$O)i@o$z8)e??LqN_gLa_%;TM>o2SC_ zkmoO6c3xRt`@J4dvz#WL)-Y|z+r(Soy~}%GIzByR`p)SCKE^%*pL(B%zNWq+-#xw~ ze%5}Oeh2)X`#bu}{g3#+;d$~F@lFL`0l@*~0lk45fwKc^10MvL1f>Tx1&sx}1}_Xg z6+#RN4Ot&@lW)Km@*DYMGu&q^n$Z=?2%QyL8~QNJCQKgI5srq>2;UHXZ>IT7>CCnW zh~P(Th`1kV8JQRPeH1AwGO8}>QM6NZadh`A)~w`N`)9q5@sFvDxjWlxwsLl7tZHmh zY-8-3xPZ8-xPf?w_(k!T5_A(J3GIpG#Ms0=iQ{tu=WLoYoaCBRmULsT<=mpV7v|~C z%bs^USv6UZd^m-e5|^?+<%1wXP%juy<)>~<9TW0|n}ttBzM_qyQL(qUN<5P0omQ3h zINdvaL;7fjPeygdGYL;pD|wL_lDQ-EO;$wK-mK5raoH_7l$?~Dqf!lNmb5F^Ft;eT zPi8AClMUo~=55LwlZVRpxOiFd;3B_8yA~shQx|tGF!j;$toK>JuS&gYLDkTP@C~gS@r~shUu{a>bfJ1` z^^VQ7&C1OKHDNXFTgC{M|V%fo{xK_dk6MK@9S!GZ*1JJzrV5xZBjOk z9!NTH<(q(S+MDf~ceQX@Dh|Ry<-sT4rhI$jQ0Sq~!`#Eo-%($2E^vo}is5J@NVEf|KK?WT&2;PCq@=ncR8z zO#GQ^T~S@VXG71PKNocFOt)Y6$@AXlk6rM*aP%VgV%sIRORYVwJx6|U{ozQjTW{-S z_si{9Jg#)~P3t?+@6&(!YQWWV*Z9{iU7vZq@5byKw{9lg9JnRA_4s!7?H6|n?o8ZW zdXIRo{Jz@#>IeD{>VLHUv1Pz*;P_y`V9&!@5AO~Mho1hF|I>%z(nrik)gwkDjgOrl z9~%uCz4Bzvli{bbrxVZ0epdf^>vOB;-~HnIOV3#R*zgPai_gEVd8zYq@2jb=I>#f& zAH2?aJ@KaetFgX(9OUk#;OXjYW@u?ZAH z;uvDld-k#+AAX?Yr;E zjT2Z*vMUd3RvwAmIbBD!|L&DfVVR02g=XmRzWBanx~-JOKmXr&1D delta 1016 zcmaJ=&ui2`6dtJ*S?s|c1b-aH6rlw-lce43CN|b?vbBL`S+@&aJT;rlc0)Irm`uBA zFI&8b;K93~CvPfv5ClO5@t{ZXVnOgKR6O_xtds3-D}^?Y%$wo+zW3g|FR#x(zFc@v zDc>5)T+9Ff#>_>d%GL|)y>w=Tec$!I-2ot@d%9jRbsdHgX?kr8K(hO)Jd^3}C|A#= z1X`-oWzwV_JHlYzB@GNs-(JI2Y`a@`KjT>dMy`0ZI;~qvszZF|7Gs z&N`;Bf!Dl1jL*tkN?|H78)AO=i)C;;&Du`mKSQPYPVIiLr1*s_KipG#@5NE^30UdJP zL#}d!+dU+wcJM=%+Q5;wiQR>W_^{uz>K$K$)FK z%kcK|aX2-)H-5J8={{ODMvX5I+dJ2P|1`;)uah@-PJzWIVEWf1=`Z5~vs5--7FX~6 E1NL`5761SM diff --git a/assets/icons/Infrared/play_text_19x5.png b/assets/icons/Infrared/play_text_19x5.png new file mode 100644 index 0000000000000000000000000000000000000000..c5f067bcf4fc6fa5518d744b829510728896fbc9 GIT binary patch literal 2850 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 Ct2{>l literal 0 HcmV?d00001 diff --git a/assets/icons/Infrared/power_hover_19x20.png b/assets/icons/Infrared/power_hover_19x20.png new file mode 100644 index 0000000000000000000000000000000000000000..3a41249ff30c4e1fad3738add47af077be33a49e GIT binary patch literal 996 zcmaJ=zi-n(6h72awS_LgQl`UUD5CNCS7>a-v`L-NNOcvZk%+`VFZQLeTKio4N}Rxe z*iac5*^rPbF(ENCuplIs?jZ3uz{G~iIZaXqf+hRCJH79Fzdk>&ZQWa#zdjEDSg3AT zb#AZn_uA|f|2B_)Rk&GXem(&S%dk0@g0f4C`uhC>pd(&{J zFXA*tO#G0e0Vu2{A$Il%gDtY_1*Y)h!$$#nt|{El*^(Wula9C1kH~g^tKszb9NiUG z@4`Z2Z~~t&3=@Aph>gS)2D}FM(`zKaK?vJ3g|niXb`7r6h(KMGvyLRma77n!-qlue zs-`bPMN(8GsYp??vT8^cEzqL(r6dSx~?Nh zL5h;)5!tvGFq~wAI5T3fNbE#j$UGXrlo7Y+Au|PD>{%CnIHnEa;iUM?P=Z4wi&EOv zDA2b5AL{$#=$O^XdA?F~fmFs+K8m64CQqJGmBhA{Oyt zP`cZ{c0Ib{0!rPnc==&0e-d!bM_k;WAO3#Ae~g*hW~0pQ>FMe5*Vo^wA9zO+B XAX5OhdzX(+I2Wjvx2$)ioo9al15LZ7#^*aQm6-^pq_>lv4WeKe5|_(jitLu?ZT!k+o*feW|L_*w8_L|>ZW^8 z@T8!4@}}Y+P=A16!J8*RFCrf5FYw?GSSQ=vtq0dY^1d^?&+|U-M;ou5CRadVzoEw(|oSl3MmSn+9_z*^{TlO}Qf{kxyY5&%<+fzx6w zyRLgQ6x}>V%)*$X0a#kjV%OUt47SO35E;U+_Xh$Dd_%ZZw6PsmNhetAC1j(w?s&Z& zPxFQ4Yj7#kIYCI63$t)HO7+YT2E01=^J^r)K?vJ1gd{Oxmh7f7e)*wNxdY9SwJJ0GrDcM#|(jsJ*py%$Fxy8>=f@A%G?-9 zBF?KC1={xiL&I-YLb-hB_5yM)2X*t6BleJu( zh(%ls%4hr6tVdUTfpT}ey!StO&>uS)ve<0AYj>5AR{$W90N^4L=L-RlQUJ&DC0@ZjPh;=*jPLSYvv5M~MFBAl0-BNIsH z15C~g000{K(ZT*WKal6<?_01!^k@7iDG<<3=fuAC~28EsPoqkpK{9G%|Vj005J}`Hw&=0RYXHq~ibpyyzHQsFW8>#s~laM4*8xut5h5 z!4#~(4xGUqyucR%VFpA%3?#rj5JCpzfE)^;7?wd9RKPme1hudO8lVxH;SjXJF*pt9 z;1XPc>u?taU>Kgl7`%oF1VP9M6Ja4bh!J9r*dopd7nzO(B4J20l7OTj>4+3jBE`sZ zqynizYLQ(?Bl0bB6giDtK>Co|$RIL`{EECsF_eL_Q3KQhbwIhO9~z3rpmWi5G!I>X zmZEFX8nhlgfVQHi(M#xcbO3#dj$?q)F%D*o*1Pf{>6$SWH+$s3q(pv=X`qR|$iJF~TPzlc-O$C3+J1 z#CT#lv5;6stS0Uu9wDA3UMCI{Uz12A4#|?_P6{CkNG+sOq(0IRX`DyT~9-sA|ffUF>wk++Z!kWZ5P$;0Hg6gtI-;!FvmBvPc55=u2?Kjj3apE5$3psG>L zsh-pbs)#zDT1jo7c2F-(3)vyY4>O^>2$gY-Gd%Qm(Z8e zYv>2*=jns=cMJ`N4THx>VkjAF8G9M07`GWOnM|ey)0dgZR4~^v8<}UA514ONSSt1^ zd=-((5|uiYR+WC0=c-gyb5%dpd8!Lkt5pxHURHgkMpd&=fR^vEcAI*_=wwAG2sV%zY%w@v@XU~7=xdm1xY6*0;iwVIXu6TaXrs|dqbIl~ z?uTdNHFy_3W~^@g_pF#!K2~{F^;XxcN!DEJEbDF7 zS8PxlSDOr*I-AS3sI8l=#CDr)-xT5$k15hA^;2%zG3@;83hbKf2JJcaVfH2VZT8O{ z%p4LO);n}Nd~$Sk%yw*Wyz8XlG{dRHsl(}4XB%gsbDi@w7p6;)%MzD%mlsoQr;4X; zpL)xc%+^yMd)ZNTI#eJ*$O)i@o$z8)e??LqN_gLa_%;TM>o2SC_ zkmoO6c3xRt`@J4dvz#WL)-Y|z+r(Soy~}%GIzByR`p)SCKE^%*pL(B%zNWq+-#xw~ ze%5}Oeh2)X`#bu}{g3#+;d$~F@lFL`0l@*~0lk45fwKc^10MvL1f>Tx1&sx}1}_Xg z6+#RN4Ot&@lW)Km@*DYMGu&q^n$Z=?2%QyL8~QNJCQKgI5srq>2;UHXZ>IT7>CCnW zh~P(Th`1kV8JQRPeH1AwGO8}>QM6NZadh`A)~w`N`)9q5@sFvDxjWlxwsLl7tZHmh zY-8-3xPZ8-xPf?w_(k!T5_A(J3GIpG#Ms0=iQ{tu=WLoYoaCBRmULsT<=mpV7v|~C z%bs^USv6UZd^m-e5|^?+<%1wXP%juy<)>~<9TW0|n}ttBzM_qyQL(qUN<5P0omQ3h zINdvaL;7fjPeygdGYL;pD|wL_lDQ-EO;$wK-mK5raoH_7l$?~Dqf!lNmb5F^Ft;eT zPi8AClMUo~=55LwlZVRpxOiFd;3B_8yA~shQx|tGF!j;$toK>JuS&gYLDkTP@C~gS@r~shUu{a>bfJ1` z^^VQ7&C1OKHDNXFTgC{M|V%fo{xK_dk6MK@9S!GZ*1JJzrV5xZBjOk z9!NTH<(q(S+MDf~ceQX@Dh|Ry<-sT4rhI$jQ0Sq~!`#Eo-%($2E^vo}is5J@NVEf|KK?WT&2;PCq@=ncR8z zO#GQ^T~S@VXG71PKNocFOt)Y6$@AXlk6rM*aP%VgV%sIRORYVwJx6|U{ozQjTW{-S z_si{9Jg#)~P3t?+@6&(!YQWWV*Z9{iU7vZq@5byKw{9lg9JnRA_4s!7?H6|n?o8ZW zdXIRo{Jz@#>IeD{>VLHUv1Pz*;P_y`V9&!@5AO~Mho1hF|I>%z(nrik)gwkDjgOrl z9~%uCz4Bzvli{bbrxVZ0epdf^>vOB;-~HnIOV3#R*zgPai_gEVd8zYq@2jb=I>#f& zAH2?aJ@KaetdJfcKx%%f4r2UPB7c6{({t$u{AfoGeC8P7*GsZDHk*hMQCb r+;2h!|FQAVBUKI8Oo-(90DJun+Fvjtm}^Sb00000NkvXXu0mjfB$#-} literal 0 HcmV?d00001 diff --git a/assets/icons/Infrared/prev_hover_19x20.png b/assets/icons/Infrared/prev_hover_19x20.png new file mode 100644 index 0000000000000000000000000000000000000000..be9dce7004e32a96e301563d942082c904172265 GIT binary patch literal 2877 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 N002ovPDHLkV1i0;gn0k} literal 0 HcmV?d00001 diff --git a/assets/icons/Infrared/vol_tv_text_29x34.png b/assets/icons/Infrared/vol_tv_text_29x34.png new file mode 100644 index 0000000000000000000000000000000000000000..caef54c25841658a5fa3c9412deeb69de7f45546 GIT binary patch literal 2920 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 literal 0 HcmV?d00001 diff --git a/assets/icons/Infrared/voldown_hover_24x21.png b/assets/icons/Infrared/voldown_hover_24x21.png new file mode 100644 index 0000000000000000000000000000000000000000..6bc57c70eb593c37d5074810bc3c5f828f21e181 GIT binary patch literal 2884 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 literal 0 HcmV?d00001 diff --git a/assets/icons/Infrared/volup_24x21.png b/assets/icons/Infrared/volup_24x21.png new file mode 100644 index 0000000000000000000000000000000000000000..688552751ce0f8f12172e1ac1b0cee0366f9e11b GIT binary patch literal 2910 zcmV-k3!(IhP)StO&>uS)ve<0AYj>5AR{$W90N^4L=L-RlQUJ&DC0@ZjPh;=*jPLSYvv5M~MFBAl0-BNIsH z15C~g000{K(ZT*WKal6<?_01!^k@7iDG<<3=fuAC~28EsPoqkpK{9G%|Vj005J}`Hw&=0RYXHq~ibpyyzHQsFW8>#s~laM4*8xut5h5 z!4#~(4xGUqyucR%VFpA%3?#rj5JCpzfE)^;7?wd9RKPme1hudO8lVxH;SjXJF*pt9 z;1XPc>u?taU>Kgl7`%oF1VP9M6Ja4bh!J9r*dopd7nzO(B4J20l7OTj>4+3jBE`sZ zqynizYLQ(?Bl0bB6giDtK>Co|$RIL`{EECsF_eL_Q3KQhbwIhO9~z3rpmWi5G!I>X zmZEFX8nhlgfVQHi(M#xcbO3#dj$?q)F%D*o*1Pf{>6$SWH+$s3q(pv=X`qR|$iJF~TPzlc-O$C3+J1 z#CT#lv5;6stS0Uu9wDA3UMCI{Uz12A4#|?_P6{CkNG+sOq(0IRX`DyT~9-sA|ffUF>wk++Z!kWZ5P$;0Hg6gtI-;!FvmBvPc55=u2?Kjj3apE5$3psG>L zsh-pbs)#zDT1jo7c2F-(3)vyY4>O^>2$gY-Gd%Qm(Z8e zYv>2*=jns=cMJ`N4THx>VkjAF8G9M07`GWOnM|ey)0dgZR4~^v8<}UA514ONSSt1^ zd=-((5|uiYR+WC0=c-gyb5%dpd8!Lkt5pxHURHgkMpd&=fR^vEcAI*_=wwAG2sV%zY%w@v@XU~7=xdm1xY6*0;iwVIXu6TaXrs|dqbIl~ z?uTdNHFy_3W~^@g_pF#!K2~{F^;XxcN!DEJEbDF7 zS8PxlSDOr*I-AS3sI8l=#CDr)-xT5$k15hA^;2%zG3@;83hbKf2JJcaVfH2VZT8O{ z%p4LO);n}Nd~$Sk%yw*Wyz8XlG{dRHsl(}4XB%gsbDi@w7p6;)%MzD%mlsoQr;4X; zpL)xc%+^yMd)ZNTI#eJ*$O)i@o$z8)e??LqN_gLa_%;TM>o2SC_ zkmoO6c3xRt`@J4dvz#WL)-Y|z+r(Soy~}%GIzByR`p)SCKE^%*pL(B%zNWq+-#xw~ ze%5}Oeh2)X`#bu}{g3#+;d$~F@lFL`0l@*~0lk45fwKc^10MvL1f>Tx1&sx}1}_Xg z6+#RN4Ot&@lW)Km@*DYMGu&q^n$Z=?2%QyL8~QNJCQKgI5srq>2;UHXZ>IT7>CCnW zh~P(Th`1kV8JQRPeH1AwGO8}>QM6NZadh`A)~w`N`)9q5@sFvDxjWlxwsLl7tZHmh zY-8-3xPZ8-xPf?w_(k!T5_A(J3GIpG#Ms0=iQ{tu=WLoYoaCBRmULsT<=mpV7v|~C z%bs^USv6UZd^m-e5|^?+<%1wXP%juy<)>~<9TW0|n}ttBzM_qyQL(qUN<5P0omQ3h zINdvaL;7fjPeygdGYL;pD|wL_lDQ-EO;$wK-mK5raoH_7l$?~Dqf!lNmb5F^Ft;eT zPi8AClMUo~=55LwlZVRpxOiFd;3B_8yA~shQx|tGF!j;$toK>JuS&gYLDkTP@C~gS@r~shUu{a>bfJ1` z^^VQ7&C1OKHDNXFTgC{M|V%fo{xK_dk6MK@9S!GZ*1JJzrV5xZBjOk z9!NTH<(q(S+MDf~ceQX@Dh|Ry<-sT4rhI$jQ0Sq~!`#Eo-%($2E^vo}is5J@NVEf|KK?WT&2;PCq@=ncR8z zO#GQ^T~S@VXG71PKNocFOt)Y6$@AXlk6rM*aP%VgV%sIRORYVwJx6|U{ozQjTW{-S z_si{9Jg#)~P3t?+@6&(!YQWWV*Z9{iU7vZq@5byKw{9lg9JnRA_4s!7?H6|n?o8ZW zdXIRo{Jz@#>IeD{>VLHUv1Pz*;P_y`V9&!@5AO~Mho1hF|I>%z(nrik)gwkDjgOrl z9~%uCz4Bzvli{bbrxVZ0epdf^>vOB;-~HnIOV3#R*zgPai_gEVd8zYq@2jb=I>#f& zAH2?aJ@Kaet>&>HU)Z^1f# zU-J1&GD-M>r;z3EGSyJIaQ$eD@jocuqdAJ#qOGF7X!)~A9t{CnBJ9MSC;$Ke07*qo IM6N<$f(6Wjg#Z8m literal 0 HcmV?d00001 diff --git a/assets/icons/Infrared/volup_hover_24x21.png b/assets/icons/Infrared/volup_hover_24x21.png new file mode 100644 index 0000000000000000000000000000000000000000..5d790e7966f5b842fd74d692beae068c1b8766ee GIT binary patch literal 2895 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 Date: Wed, 23 Aug 2023 23:51:32 +0400 Subject: [PATCH 725/824] SubGhz: fix todo (#2984) * [FL-3501] SubGhz: fix Handle multiple external cc1101 modules * [FL-3502] SubGhz: fix Protocol not found error message * [FL-3503] SubGhz: fix Handle rx buffer overflow * {FL-3520] SubGhz: Handle RX buffer overflow with external cc1101 * [FL-3548] SubGhz: Security+ 2.0 counter start value * [FL-3552] Sub-GHz: Check saved file * [FL-3555] [FL-3554] Sub-GHz: RX buffer overflow handling and check that buffer has been properly written * [FL-3557] Sub-GHz: No optimization required * [FL-3558] Sub-GHz: Keeloq 0 discriminator * [FL-3559] Sub-GHz: Keeloq unknown learning * [FL-3560] Sub-GHz: callback for updating keeloq data on display * SubGhz: fix RXFIFO_OVERFLOW Co-authored-by: Aleksandr Kutuzov --- .../drivers/subghz/cc1101_ext/cc1101_ext.c | 3 +- .../helpers/subghz_txrx_create_protocol_key.c | 67 +++++++++---------- .../helpers/subghz_txrx_create_protocol_key.h | 20 +++--- .../scenes/subghz_scene_radio_setting.c | 34 +++++++--- .../subghz/scenes/subghz_scene_set_type.c | 8 +-- applications/main/subghz/subghz_i.c | 5 +- .../targets/f7/furi_hal/furi_hal_subghz.c | 3 +- lib/subghz/protocols/bin_raw.c | 2 - lib/subghz/protocols/keeloq.c | 9 ++- lib/subghz/protocols/secplus_v2.c | 1 - lib/subghz/subghz_tx_rx_worker.c | 4 +- lib/subghz/types.h | 1 + 12 files changed, 83 insertions(+), 74 deletions(-) diff --git a/applications/drivers/subghz/cc1101_ext/cc1101_ext.c b/applications/drivers/subghz/cc1101_ext/cc1101_ext.c index eb0ec56a48f..0a9599d03f0 100644 --- a/applications/drivers/subghz/cc1101_ext/cc1101_ext.c +++ b/applications/drivers/subghz/cc1101_ext/cc1101_ext.c @@ -333,8 +333,7 @@ bool subghz_device_cc1101_ext_rx_pipe_not_empty() { (CC1101_STATUS_RXBYTES) | CC1101_BURST, (uint8_t*)status); furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); - // TODO FL-3520: you can add a buffer overflow flag if needed - if(status->NUM_RXBYTES > 0) { + if((status->NUM_RXBYTES > 0) || (status->RXFIFO_OVERFLOW == 0)) { return true; } else { return false; diff --git a/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.c b/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.c index b5d47cea946..0413173e6e8 100644 --- a/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.c +++ b/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.c @@ -13,7 +13,7 @@ #define TAG "SubGhzCreateProtocolKey" -bool subghz_txrx_gen_data_protocol( +SubGhzProtocolStatus subghz_txrx_gen_data_protocol( void* context, const char* preset_name, uint32_t frequency, @@ -23,30 +23,29 @@ bool subghz_txrx_gen_data_protocol( furi_assert(context); SubGhzTxRx* instance = context; - bool res = false; + SubGhzProtocolStatus ret = SubGhzProtocolStatusOk; subghz_txrx_set_preset(instance, preset_name, frequency, NULL, 0); instance->decoder_result = subghz_receiver_search_decoder_base_by_name(instance->receiver, protocol_name); if(instance->decoder_result == NULL) { - //TODO FL-3502: Error - // furi_string_set(error_str, "Protocol not\nfound!"); - // scene_manager_next_scene(scene_manager, SubGhzSceneShowErrorSub); FURI_LOG_E(TAG, "Protocol not found!"); - return false; + ret = SubGhzProtocolStatusErrorProtocolNotFound; + return ret; } do { Stream* fff_data_stream = flipper_format_get_raw_stream(instance->fff_data); stream_clean(fff_data_stream); - if(subghz_protocol_decoder_base_serialize( - instance->decoder_result, instance->fff_data, instance->preset) != - SubGhzProtocolStatusOk) { + ret = subghz_protocol_decoder_base_serialize( + instance->decoder_result, instance->fff_data, instance->preset); + if(ret != SubGhzProtocolStatusOk) { FURI_LOG_E(TAG, "Unable to serialize"); break; } if(!flipper_format_update_uint32(instance->fff_data, "Bit", &bit, 1)) { + ret = SubGhzProtocolStatusErrorParserOthers; FURI_LOG_E(TAG, "Unable to update Bit"); break; } @@ -56,15 +55,15 @@ bool subghz_txrx_gen_data_protocol( key_data[sizeof(uint64_t) - i - 1] = (key >> (i * 8)) & 0xFF; } if(!flipper_format_update_hex(instance->fff_data, "Key", key_data, sizeof(uint64_t))) { + ret = SubGhzProtocolStatusErrorParserOthers; FURI_LOG_E(TAG, "Unable to update Key"); break; } - res = true; } while(false); - return res; + return ret; } -bool subghz_txrx_gen_data_protocol_and_te( +SubGhzProtocolStatus subghz_txrx_gen_data_protocol_and_te( SubGhzTxRx* instance, const char* preset_name, uint32_t frequency, @@ -73,18 +72,18 @@ bool subghz_txrx_gen_data_protocol_and_te( uint32_t bit, uint32_t te) { furi_assert(instance); - bool ret = false; - if(subghz_txrx_gen_data_protocol(instance, preset_name, frequency, protocol_name, key, bit)) { + SubGhzProtocolStatus ret = + subghz_txrx_gen_data_protocol(instance, preset_name, frequency, protocol_name, key, bit); + if(ret == SubGhzProtocolStatusOk) { if(!flipper_format_update_uint32(instance->fff_data, "TE", (uint32_t*)&te, 1)) { + ret = SubGhzProtocolStatusErrorParserOthers; FURI_LOG_E(TAG, "Unable to update Te"); - } else { - ret = true; } } return ret; } -bool subghz_txrx_gen_keeloq_protocol( +SubGhzProtocolStatus subghz_txrx_gen_keeloq_protocol( SubGhzTxRx* instance, const char* name_preset, uint32_t frequency, @@ -94,7 +93,7 @@ bool subghz_txrx_gen_keeloq_protocol( uint16_t cnt) { furi_assert(instance); - bool ret = false; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; serial &= 0x0FFFFFFF; instance->transmitter = subghz_transmitter_alloc_init(instance->environment, SUBGHZ_PROTOCOL_KEELOQ_NAME); @@ -108,13 +107,13 @@ bool subghz_txrx_gen_keeloq_protocol( cnt, name_sysmem, instance->preset); - ret = true; + ret = SubGhzProtocolStatusOk; } subghz_transmitter_free(instance->transmitter); return ret; } -bool subghz_txrx_gen_secplus_v2_protocol( +SubGhzProtocolStatus subghz_txrx_gen_secplus_v2_protocol( SubGhzTxRx* instance, const char* name_preset, uint32_t frequency, @@ -123,10 +122,11 @@ bool subghz_txrx_gen_secplus_v2_protocol( uint32_t cnt) { furi_assert(instance); - bool ret = false; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; instance->transmitter = subghz_transmitter_alloc_init(instance->environment, SUBGHZ_PROTOCOL_SECPLUS_V2_NAME); subghz_txrx_set_preset(instance, name_preset, frequency, NULL, 0); + if(instance->transmitter) { subghz_protocol_secplus_v2_create_data( subghz_transmitter_get_protocol_instance(instance->transmitter), @@ -135,30 +135,27 @@ bool subghz_txrx_gen_secplus_v2_protocol( btn, cnt, instance->preset); - ret = true; + ret = SubGhzProtocolStatusOk; } return ret; } -bool subghz_txrx_gen_secplus_v1_protocol( +SubGhzProtocolStatus subghz_txrx_gen_secplus_v1_protocol( SubGhzTxRx* instance, const char* name_preset, uint32_t frequency) { furi_assert(instance); - bool ret = false; uint32_t serial = (uint32_t)rand(); while(!subghz_protocol_secplus_v1_check_fixed(serial)) { serial = (uint32_t)rand(); } - if(subghz_txrx_gen_data_protocol( - instance, - name_preset, - frequency, - SUBGHZ_PROTOCOL_SECPLUS_V1_NAME, - (uint64_t)serial << 32 | 0xE6000000, - 42)) { - ret = true; - } - return ret; -} \ No newline at end of file + + return subghz_txrx_gen_data_protocol( + instance, + name_preset, + frequency, + SUBGHZ_PROTOCOL_SECPLUS_V1_NAME, + (uint64_t)serial << 32 | 0xE6000000, + 42); +} diff --git a/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.h b/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.h index 514a5733c79..edc6e541557 100644 --- a/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.h +++ b/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.h @@ -11,9 +11,9 @@ * @param protocol_name Name of protocol * @param key Key * @param bit Bit - * @return bool True if success + * @return SubGhzProtocolStatus */ -bool subghz_txrx_gen_data_protocol( +SubGhzProtocolStatus subghz_txrx_gen_data_protocol( void* context, const char* preset_name, uint32_t frequency, @@ -31,9 +31,9 @@ bool subghz_txrx_gen_data_protocol( * @param key Key * @param bit Bit * @param te Te - * @return bool True if success + * @return SubGhzProtocolStatus */ -bool subghz_txrx_gen_data_protocol_and_te( +SubGhzProtocolStatus subghz_txrx_gen_data_protocol_and_te( SubGhzTxRx* instance, const char* preset_name, uint32_t frequency, @@ -52,9 +52,9 @@ bool subghz_txrx_gen_data_protocol_and_te( * @param serial Serial number * @param btn Button * @param cnt Counter - * @return bool True if success + * @return SubGhzProtocolStatus */ -bool subghz_txrx_gen_keeloq_protocol( +SubGhzProtocolStatus subghz_txrx_gen_keeloq_protocol( SubGhzTxRx* instance, const char* name_preset, uint32_t frequency, @@ -72,9 +72,9 @@ bool subghz_txrx_gen_keeloq_protocol( * @param serial Serial number * @param btn Button * @param cnt Counter - * @return bool True if success + * @return SubGhzProtocolStatus */ -bool subghz_txrx_gen_secplus_v2_protocol( +SubGhzProtocolStatus subghz_txrx_gen_secplus_v2_protocol( SubGhzTxRx* instance, const char* name_preset, uint32_t frequency, @@ -88,9 +88,9 @@ bool subghz_txrx_gen_secplus_v2_protocol( * @param instance Pointer to a SubGhzTxRx * @param name_preset Name of preset * @param frequency Frequency in Hz - * @return bool True if success + * @return SubGhzProtocolStatus */ -bool subghz_txrx_gen_secplus_v1_protocol( +SubGhzProtocolStatus subghz_txrx_gen_secplus_v1_protocol( SubGhzTxRx* instance, const char* name_preset, uint32_t frequency); \ No newline at end of file diff --git a/applications/main/subghz/scenes/subghz_scene_radio_setting.c b/applications/main/subghz/scenes/subghz_scene_radio_setting.c index de05417ca86..1f8e4d83d6a 100644 --- a/applications/main/subghz/scenes/subghz_scene_radio_setting.c +++ b/applications/main/subghz/scenes/subghz_scene_radio_setting.c @@ -1,6 +1,7 @@ #include "../subghz_i.h" #include #include +#include enum SubGhzRadioSettingIndex { SubGhzRadioSettingIndexDevice, @@ -17,16 +18,29 @@ const uint32_t radio_device_value[RADIO_DEVICE_COUNT] = { SubGhzRadioDeviceTypeExternalCC1101, }; -static void subghz_scene_radio_settings_set_device(VariableItem* item) { - SubGhz* subghz = variable_item_get_context(item); - uint8_t index = variable_item_get_current_value_index(item); +const char* const radio_device_name[RADIO_DEVICE_COUNT] = { + SUBGHZ_DEVICE_CC1101_INT_NAME, + SUBGHZ_DEVICE_CC1101_EXT_NAME, +}; - if(!subghz_txrx_radio_device_is_external_connected( - subghz->txrx, SUBGHZ_DEVICE_CC1101_EXT_NAME) && - radio_device_value[index] == SubGhzRadioDeviceTypeExternalCC1101) { - // TODO FL-3501: correct if there is more than 1 module - index = 0; +static uint8_t subghz_scene_radio_settings_next_index_connect_ext_device( + SubGhz* subghz, + uint8_t current_index) { + uint8_t index = 0; + for(index = current_index; index < RADIO_DEVICE_COUNT; index++) { + if(subghz_txrx_radio_device_is_external_connected(subghz->txrx, radio_device_name[index])) { + break; + } } + if(index == RADIO_DEVICE_COUNT) index = 0; + return index; +} + +static void subghz_scene_radio_settings_set_device(VariableItem* item) { + SubGhz* subghz = variable_item_get_context(item); + uint8_t current_index = variable_item_get_current_value_index(item); + uint8_t index = + subghz_scene_radio_settings_next_index_connect_ext_device(subghz, current_index); variable_item_set_current_value_text(item, radio_device_text[index]); subghz_txrx_radio_device_set(subghz->txrx, radio_device_value[index]); } @@ -37,9 +51,11 @@ void subghz_scene_radio_settings_on_enter(void* context) { uint8_t value_index; uint8_t value_count_device = RADIO_DEVICE_COUNT; + if(subghz_txrx_radio_device_get(subghz->txrx) == SubGhzRadioDeviceTypeInternal && - !subghz_txrx_radio_device_is_external_connected(subghz->txrx, SUBGHZ_DEVICE_CC1101_EXT_NAME)) + !subghz_scene_radio_settings_next_index_connect_ext_device(subghz, 1)) value_count_device = 1; + item = variable_item_list_add( subghz->variable_item_list, "Module", diff --git a/applications/main/subghz/scenes/subghz_scene_set_type.c b/applications/main/subghz/scenes/subghz_scene_set_type.c index d0571f1b13e..8c040cc9cf2 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_type.c +++ b/applications/main/subghz/scenes/subghz_scene_set_type.c @@ -118,7 +118,7 @@ void subghz_scene_set_type_on_enter(void* context) { bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { SubGhz* subghz = context; - bool generated_protocol = false; + SubGhzProtocolStatus generated_protocol = SubGhzProtocolStatusError; if(event.type == SceneManagerEventTypeCustom) { uint32_t key = (uint32_t)rand(); @@ -174,7 +174,7 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { case SubmenuIndexDoorHan_433_92: generated_protocol = subghz_txrx_gen_keeloq_protocol( subghz->txrx, "AM650", 433920000, "DoorHan", key, 0x2, 0x0003); - if(!generated_protocol) { + if(generated_protocol != SubGhzProtocolStatusOk) { furi_string_set( subghz->error_str, "Function requires\nan SD card with\nfresh databases."); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError); @@ -183,7 +183,7 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { case SubmenuIndexDoorHan_315_00: generated_protocol = subghz_txrx_gen_keeloq_protocol( subghz->txrx, "AM650", 315000000, "DoorHan", key, 0x2, 0x0003); - if(!generated_protocol) { + if(generated_protocol != SubGhzProtocolStatusOk) { furi_string_set( subghz->error_str, "Function requires\nan SD card with\nfresh databases."); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError); @@ -216,7 +216,7 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { scene_manager_set_scene_state(subghz->scene_manager, SubGhzSceneSetType, event.event); - if(generated_protocol) { + if(generated_protocol == SubGhzProtocolStatusOk) { subghz_file_name_clear(subghz); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveName); return true; diff --git a/applications/main/subghz/subghz_i.c b/applications/main/subghz/subghz_i.c index 55f33d200ba..9ff61837120 100644 --- a/applications/main/subghz/subghz_i.c +++ b/applications/main/subghz/subghz_i.c @@ -289,10 +289,13 @@ bool subghz_save_protocol_to_file( if(!storage_simply_remove(storage, dev_file_name)) { break; } - //TODO FL-3552: check Write stream_seek(flipper_format_stream, 0, StreamOffsetFromStart); stream_save_to_file(flipper_format_stream, storage, dev_file_name, FSOM_CREATE_ALWAYS); + if(storage_common_stat(storage, dev_file_name, NULL) != FSE_OK) { + break; + } + saved = true; } while(0); furi_string_free(file_dir); diff --git a/firmware/targets/f7/furi_hal/furi_hal_subghz.c b/firmware/targets/f7/furi_hal/furi_hal_subghz.c index 327e42a870a..bd724f0bf46 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_subghz.c +++ b/firmware/targets/f7/furi_hal/furi_hal_subghz.c @@ -207,8 +207,7 @@ bool furi_hal_subghz_rx_pipe_not_empty() { cc1101_read_reg( &furi_hal_spi_bus_handle_subghz, (CC1101_STATUS_RXBYTES) | CC1101_BURST, (uint8_t*)status); furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); - // TODO FL-3503: you can add a buffer overflow flag if needed - if(status->NUM_RXBYTES > 0) { + if((status->NUM_RXBYTES > 0) || (status->RXFIFO_OVERFLOW == 0)) { return true; } else { return false; diff --git a/lib/subghz/protocols/bin_raw.c b/lib/subghz/protocols/bin_raw.c index c3dbf84fe1a..9509280c2e9 100644 --- a/lib/subghz/protocols/bin_raw.c +++ b/lib/subghz/protocols/bin_raw.c @@ -744,7 +744,6 @@ static bool bin_raw_debug("\r\n\r\n"); #endif - //TODO FL-3557: can be optimized BinRAW_Markup markup_temp[BIN_RAW_MAX_MARKUP_COUNT]; memcpy( markup_temp, @@ -770,7 +769,6 @@ static bool } } } - //TODO FL-3557: can be optimized if(bin_raw_type == BinRAWTypeGap) { if(data_temp != 0) { //there are sequences with the same number of bits diff --git a/lib/subghz/protocols/keeloq.c b/lib/subghz/protocols/keeloq.c index 8789a0f2001..cfd8388799f 100644 --- a/lib/subghz/protocols/keeloq.c +++ b/lib/subghz/protocols/keeloq.c @@ -122,7 +122,7 @@ static bool subghz_protocol_keeloq_gen_data(SubGhzProtocolEncoderKeeloq* instanc uint32_t fix = (uint32_t)btn << 28 | instance->generic.serial; uint32_t decrypt = (uint32_t)btn << 28 | (instance->generic.serial & 0x3FF) - << 16 | //TODO FL-3558: in some protocols the discriminator is 0 + << 16 | // In some protocols the discriminator is 0 instance->generic.cnt; uint32_t hop = 0; uint64_t man = 0; @@ -149,7 +149,8 @@ static bool subghz_protocol_keeloq_gen_data(SubGhzProtocolEncoderKeeloq* instanc hop = subghz_protocol_keeloq_common_encrypt(decrypt, man); break; case KEELOQ_LEARNING_UNKNOWN: - hop = 0; //TODO FL-3559 + //Invalid or missing encoding type in keeloq_mfcodes + hop = 0; break; } break; @@ -199,9 +200,7 @@ static bool furi_assert(instance); //gen new key - if(subghz_protocol_keeloq_gen_data(instance, btn)) { - //TODO FL-3560: if you need to add a callback to automatically update the data on the display - } else { + if(!subghz_protocol_keeloq_gen_data(instance, btn)) { return false; } diff --git a/lib/subghz/protocols/secplus_v2.c b/lib/subghz/protocols/secplus_v2.c index 48ee6698202..12d2fac74e6 100644 --- a/lib/subghz/protocols/secplus_v2.c +++ b/lib/subghz/protocols/secplus_v2.c @@ -380,7 +380,6 @@ static void subghz_protocol_secplus_v2_encode(SubGhzProtocolEncoderSecPlus_v2* i uint8_t roll_2[9] = {0}; instance->generic.cnt++; - //TODO Fl-3548: 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); diff --git a/lib/subghz/subghz_tx_rx_worker.c b/lib/subghz/subghz_tx_rx_worker.c index 833d90bfdc5..daf046f4431 100644 --- a/lib/subghz/subghz_tx_rx_worker.c +++ b/lib/subghz/subghz_tx_rx_worker.c @@ -165,7 +165,6 @@ static int32_t subghz_tx_rx_worker_thread(void* context) { SUBGHZ_TXRX_WORKER_TIMEOUT_READ_WRITE_BUF); subghz_tx_rx_worker_tx(instance, data, SUBGHZ_TXRX_WORKER_MAX_TXRX_SIZE); } else { - //TODO FL-3554: checking that it managed to write all the data to the TX buffer furi_stream_buffer_receive( instance->stream_tx, &data, size_tx, SUBGHZ_TXRX_WORKER_TIMEOUT_READ_WRITE_BUF); subghz_tx_rx_worker_tx(instance, data, size_tx); @@ -178,7 +177,6 @@ static int32_t subghz_tx_rx_worker_thread(void* context) { furi_stream_buffer_bytes_available(instance->stream_rx) == 0) { callback_rx = true; } - //TODO FL-3554: checking that it managed to write all the data to the RX buffer furi_stream_buffer_send( instance->stream_rx, &data, @@ -189,7 +187,7 @@ static int32_t subghz_tx_rx_worker_thread(void* context) { callback_rx = false; } } else { - //TODO FL-3555: RX buffer overflow + FURI_LOG_E(TAG, "Receive buffer overflow, over-the-air transmission too fast"); } } } diff --git a/lib/subghz/types.h b/lib/subghz/types.h index d87a0dc760c..cccf70bad94 100644 --- a/lib/subghz/types.h +++ b/lib/subghz/types.h @@ -57,6 +57,7 @@ typedef enum { // Encoder issue SubGhzProtocolStatusErrorEncoderGetUpload = (-12), ///< Payload encoder failure // Special Values + SubGhzProtocolStatusErrorProtocolNotFound = (-13), ///< Protocol not found SubGhzProtocolStatusReserved = 0x7FFFFFFF, ///< Prevents enum down-size compiler optimization. } SubGhzProtocolStatus; From c3aa15171244856327558b48085d538f2f315189 Mon Sep 17 00:00:00 2001 From: hedger Date: Fri, 25 Aug 2023 17:38:41 +0300 Subject: [PATCH 726/824] github: Check for todos (#3011) --- .../workflows/lint_and_submodule_check.yml | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/.github/workflows/lint_and_submodule_check.yml b/.github/workflows/lint_and_submodule_check.yml index 22ca7d893fb..b85409ecd14 100644 --- a/.github/workflows/lint_and_submodule_check.yml +++ b/.github/workflows/lint_and_submodule_check.yml @@ -23,9 +23,8 @@ jobs: - name: 'Checkout code' uses: actions/checkout@v3 with: - fetch-depth: 1 - ref: ${{ github.event.pull_request.head.sha }} - + fetch-depth: 2 + ref: ${{ github.sha }} - name: 'Check protobuf branch' run: | @@ -48,8 +47,26 @@ jobs: exit 1; fi + - name: 'Check for new TODOs' + id: check_todos + if: github.event_name == 'pull_request' + run: | + set +e; + git diff --unified=0 --no-color ${{ github.event.pull_request.base.sha }} ${{ github.sha }} | grep -E '^\+' | grep -i -E '(TODO|HACK|FIXME|XXX)[ :]' | grep -v -- '-nofl' > lines.log; + MISSING_TICKETS=$( grep -v -E '\[FL-[0-9]+\]' lines.log ); + if [ -n "$MISSING_TICKETS" ]; then + echo "Error: Missing ticket number in \`TODO\` comment(s)" >> $GITHUB_STEP_SUMMARY; + echo "\`\`\`" >> $GITHUB_STEP_SUMMARY; + echo "$MISSING_TICKETS" >> $GITHUB_STEP_SUMMARY; + echo "\`\`\`" >> $GITHUB_STEP_SUMMARY; + exit 1; + else + echo "No new TODOs without tickets found" >> $GITHUB_STEP_SUMMARY; + fi + - name: 'Check Python code formatting' id: syntax_check_py + if: always() run: | set +e; ./fbt -s lint_py 2>&1 | tee lint-py.log; From 66d26c16cd012dc83e699d3903e3aea3843dc972 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Sat, 26 Aug 2023 01:09:40 +0900 Subject: [PATCH 727/824] [FL-3580] AC OFF button (#3010) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- .../infrared/scenes/infrared_scene_universal_ac.c | 6 +++--- assets/icons/Infrared/off_19x20.png | Bin 0 -> 193 bytes assets/icons/Infrared/off_hover_19x20.png | Bin 0 -> 160 bytes assets/icons/Infrared/off_text_12x5.png | Bin 0 -> 130 bytes 4 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 assets/icons/Infrared/off_19x20.png create mode 100644 assets/icons/Infrared/off_hover_19x20.png create mode 100644 assets/icons/Infrared/off_text_12x5.png diff --git a/applications/main/infrared/scenes/infrared_scene_universal_ac.c b/applications/main/infrared/scenes/infrared_scene_universal_ac.c index 8914e5ad000..cbb09a525fc 100644 --- a/applications/main/infrared/scenes/infrared_scene_universal_ac.c +++ b/applications/main/infrared/scenes/infrared_scene_universal_ac.c @@ -21,11 +21,11 @@ void infrared_scene_universal_ac_on_enter(void* context) { 0, 6, 15, - &I_power_19x20, - &I_power_hover_19x20, + &I_off_19x20, + &I_off_hover_19x20, infrared_scene_universal_common_item_callback, context); - button_panel_add_icon(button_panel, 4, 37, &I_power_text_24x5); + button_panel_add_icon(button_panel, 10, 37, &I_off_text_12x5); infrared_brute_force_add_record(brute_force, i++, "Off"); button_panel_add_item( button_panel, diff --git a/assets/icons/Infrared/off_19x20.png b/assets/icons/Infrared/off_19x20.png new file mode 100644 index 0000000000000000000000000000000000000000..6d68d7e6e1527c309de8e19e764dbcb4e68a5584 GIT binary patch literal 193 zcmeAS@N?(olHy`uVBq!ia0vp^!ayv-!2~29m`_v&Qk(@Ik;M!Q+&~F#VMc~ob0mO* z#hxyXAr`$?Cm-ZGV8G$*`||()P1Y>Cd@m>N)iqc2&)!f_Zp@sJDme2DF1jQH+wBmKZB>MpUXO@geCx>bSJI= literal 0 HcmV?d00001 From cf74dd259966df4b1772d7f398bb80c2fda991ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Sat, 26 Aug 2023 02:00:00 +0900 Subject: [PATCH 728/824] Rfid: fix crash on broken key launch from archive (#3012) --- applications/main/lfrfid/lfrfid.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/applications/main/lfrfid/lfrfid.c b/applications/main/lfrfid/lfrfid.c index edde23804c2..aa7510a90e8 100644 --- a/applications/main/lfrfid/lfrfid.c +++ b/applications/main/lfrfid/lfrfid.c @@ -186,13 +186,15 @@ int32_t lfrfid_app(void* p) { dolphin_deed(DolphinDeedRfidEmulate); } else { furi_string_set(app->file_path, args); - lfrfid_load_key_data(app, app->file_path, true); - view_dispatcher_attach_to_gui( - app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); - scene_manager_next_scene(app->scene_manager, LfRfidSceneEmulate); - dolphin_deed(DolphinDeedRfidEmulate); + if(lfrfid_load_key_data(app, app->file_path, true)) { + view_dispatcher_attach_to_gui( + app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + scene_manager_next_scene(app->scene_manager, LfRfidSceneEmulate); + dolphin_deed(DolphinDeedRfidEmulate); + } else { + view_dispatcher_stop(app->view_dispatcher); + } } - } else { view_dispatcher_attach_to_gui( app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); From 5eb045e25fbc0385f2a0296458e6b698ea06002f Mon Sep 17 00:00:00 2001 From: gornekich Date: Tue, 29 Aug 2023 08:31:40 +0400 Subject: [PATCH 729/824] nfc: add rfal wrong state error handling (#3017) --- firmware/targets/f7/furi_hal/furi_hal_nfc.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc.c b/firmware/targets/f7/furi_hal/furi_hal_nfc.c index b249c865899..baffde1ebf7 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_nfc.c +++ b/firmware/targets/f7/furi_hal/furi_hal_nfc.c @@ -701,7 +701,9 @@ bool furi_hal_nfc_tx_rx(FuriHalNfcTxRxContext* tx_rx, uint16_t timeout_ms) { rfalNfcWorker(); state = rfalNfcGetState(); ret = rfalNfcDataExchangeGetStatus(); - if(ret == ERR_BUSY) { + if(ret == ERR_WRONG_STATE) { + return false; + } else if(ret == ERR_BUSY) { if(DWT->CYCCNT - start > timeout_ms * clocks_in_ms) { FURI_LOG_D(TAG, "Timeout during data exchange"); return false; From c6be6f487a1ec0d8e8b7f4af7016665d8dde0440 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Tue, 29 Aug 2023 13:39:34 +0900 Subject: [PATCH 730/824] [FL-3495] Remove the TODO for GPIO settings save/load (#3015) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- applications/main/gpio/scenes/gpio_scene_usb_uart.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/main/gpio/scenes/gpio_scene_usb_uart.c b/applications/main/gpio/scenes/gpio_scene_usb_uart.c index 52b2142dc6b..c5e085192b2 100644 --- a/applications/main/gpio/scenes/gpio_scene_usb_uart.c +++ b/applications/main/gpio/scenes/gpio_scene_usb_uart.c @@ -19,7 +19,7 @@ void gpio_scene_usb_uart_on_enter(void* context) { uint32_t prev_state = scene_manager_get_scene_state(app->scene_manager, GpioAppViewUsbUart); if(prev_state == 0) { scene_usb_uart = malloc(sizeof(SceneUsbUartBridge)); - scene_usb_uart->cfg.vcp_ch = 0; // TODO FL-3495: settings load + scene_usb_uart->cfg.vcp_ch = 0; scene_usb_uart->cfg.uart_ch = 0; scene_usb_uart->cfg.flow_pins = 0; scene_usb_uart->cfg.baudrate_mode = 0; From aa1c1fd905225b02b218eb5b93aa91d6b9e9ac3e Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Tue, 29 Aug 2023 15:55:36 +0400 Subject: [PATCH 731/824] [FL-3582] SubGhz: heap overflow text error (#3021) --- applications/main/subghz/scenes/subghz_scene_receiver.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/applications/main/subghz/scenes/subghz_scene_receiver.c b/applications/main/subghz/scenes/subghz_scene_receiver.c index 6ab443579bb..497938feebf 100644 --- a/applications/main/subghz/scenes/subghz_scene_receiver.c +++ b/applications/main/subghz/scenes/subghz_scene_receiver.c @@ -123,7 +123,7 @@ void subghz_scene_receiver_on_enter(void* context) { subghz_rx_key_state_set(subghz, SubGhzRxKeyStateAddKey); } furi_string_free(str_buff); - subghz_scene_receiver_update_statusbar(subghz); + subghz_view_receiver_set_callback( subghz->subghz_receiver, subghz_scene_receiver_callback, subghz); subghz_txrx_set_rx_calback(subghz->txrx, subghz_scene_add_to_history_callback, subghz); @@ -136,6 +136,8 @@ void subghz_scene_receiver_on_enter(void* context) { furi_check( subghz_txrx_load_decoder_by_name_protocol(subghz->txrx, SUBGHZ_PROTOCOL_BIN_RAW_NAME)); + subghz_scene_receiver_update_statusbar(subghz); + view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdReceiver); } From 809418b9dabef8914f4f56badd12ae7a0d34f19c Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Fri, 1 Sep 2023 04:23:37 +0300 Subject: [PATCH 732/824] [FL-3563] StorageListRequest: size filter (#3018) * Protobuf: size filter * Update protobuf * Scripts: types for fwflash.py * RPC: handle fliter for StorageListRequest * RPC: StorageListRequest tests for filtering * Fix unit tests configuration * Assets: sync protobuf with upstream Co-authored-by: Aleksandr Kutuzov --- applications/debug/unit_tests/rpc/rpc_test.c | 208 +++++++++++-------- applications/services/rpc/rpc_storage.c | 33 ++- assets/protobuf | 2 +- fbt_options.py | 1 + scripts/fwflash.py | 15 +- 5 files changed, 154 insertions(+), 105 deletions(-) diff --git a/applications/debug/unit_tests/rpc/rpc_test.c b/applications/debug/unit_tests/rpc/rpc_test.c index 533a8a9ca6a..645e75e8448 100644 --- a/applications/debug/unit_tests/rpc/rpc_test.c +++ b/applications/debug/unit_tests/rpc/rpc_test.c @@ -67,7 +67,6 @@ static RpcSessionContext rpc_session[TEST_RPC_SESSIONS]; } while(0) static void output_bytes_callback(void* ctx, uint8_t* got_bytes, size_t got_size); -static void clean_directory(Storage* fs_api, const char* clean_dir); static void test_rpc_add_empty_to_list(MsgList_t msg_list, PB_CommandStatus status, uint32_t command_id); static void test_rpc_encode_and_feed(MsgList_t msg_list, uint8_t session); @@ -149,11 +148,41 @@ static void test_rpc_teardown_second_session(void) { rpc_session[1].session = NULL; } +static void test_rpc_storage_clean_directory(Storage* fs_api, const char* clean_dir) { + furi_check(fs_api); + furi_check(clean_dir); + storage_simply_remove_recursive(fs_api, clean_dir); + FS_Error error = storage_common_mkdir(fs_api, clean_dir); + furi_check(error == FSE_OK); +} + +static void test_rpc_storage_create_file(Storage* fs_api, const char* path, size_t size) { + File* file = storage_file_alloc(fs_api); + + bool success = false; + do { + if(!storage_file_open(file, path, FSAM_WRITE, FSOM_CREATE_ALWAYS)) break; + if(!storage_file_seek(file, size, true)) break; + success = true; + } while(false); + + storage_file_close(file); + storage_file_free(file); + + furi_check(success); +} + static void test_rpc_storage_setup(void) { test_rpc_setup(); Storage* fs_api = furi_record_open(RECORD_STORAGE); - clean_directory(fs_api, TEST_DIR_NAME); + test_rpc_storage_clean_directory(fs_api, TEST_DIR_NAME); + test_rpc_storage_create_file(fs_api, TEST_DIR_NAME "/file100", 100); + test_rpc_storage_create_file(fs_api, TEST_DIR_NAME "/file250", 250); + test_rpc_storage_create_file(fs_api, TEST_DIR_NAME "/file500", 200); + test_rpc_storage_create_file(fs_api, TEST_DIR_NAME "/file1000", 1000); + test_rpc_storage_create_file(fs_api, TEST_DIR_NAME "/file2500", 2500); + test_rpc_storage_create_file(fs_api, TEST_DIR_NAME "/file5000", 5000); furi_record_close(RECORD_STORAGE); } @@ -161,7 +190,7 @@ static void test_rpc_storage_teardown(void) { test_rpc_teardown(); Storage* fs_api = furi_record_open(RECORD_STORAGE); - clean_directory(fs_api, TEST_DIR_NAME); + test_rpc_storage_clean_directory(fs_api, TEST_DIR_NAME); furi_record_close(RECORD_STORAGE); } @@ -179,36 +208,6 @@ static void test_rpc_session_terminated_callback(void* context) { xSemaphoreGive(callbacks_context->terminate_semaphore); } -static void clean_directory(Storage* fs_api, const char* clean_dir) { - furi_check(fs_api); - furi_check(clean_dir); - - File* dir = storage_file_alloc(fs_api); - if(storage_dir_open(dir, clean_dir)) { - FileInfo fileinfo; - char* name = malloc(MAX_NAME_LENGTH + 1); - while(storage_dir_read(dir, &fileinfo, name, MAX_NAME_LENGTH)) { - size_t size = strlen(clean_dir) + strlen(name) + 1 + 1; - char* fullname = malloc(size); - snprintf(fullname, size, "%s/%s", clean_dir, name); - if(file_info_is_dir(&fileinfo)) { - clean_directory(fs_api, fullname); - } - FS_Error error = storage_common_remove(fs_api, fullname); - furi_check(error == FSE_OK); - free(fullname); - } - free(name); - } else { - FS_Error error = storage_common_mkdir(fs_api, clean_dir); - (void)error; - furi_check(error == FSE_OK); - } - - storage_dir_close(dir); - storage_file_free(dir); -} - static void test_rpc_print_message_list(MsgList_t msg_list) { #if DEBUG_PRINT MsgList_reverse(msg_list); @@ -282,24 +281,40 @@ static void test_rpc_add_ping_to_list(MsgList_t msg_list, bool request, uint32_t response->which_content = (request == PING_REQUEST) ? PB_Main_system_ping_request_tag : PB_Main_system_ping_response_tag; } +static void test_rpc_fill_basic_message(PB_Main* message, uint16_t tag, uint32_t command_id) { + message->command_id = command_id; + message->command_status = PB_CommandStatus_OK; + message->cb_content.funcs.encode = NULL; + message->which_content = tag; + message->has_next = false; +} + +static void test_rpc_create_storage_list_request( + PB_Main* message, + const char* path, + bool include_md5, + uint32_t command_id, + uint32_t filter_max_size) { + furi_check(message); + furi_check(path); + test_rpc_fill_basic_message(message, PB_Main_storage_list_request_tag, command_id); + message->content.storage_list_request.path = strdup(path); + message->content.storage_list_request.include_md5 = include_md5; + message->content.storage_list_request.filter_max_size = filter_max_size; +} static void test_rpc_create_simple_message( PB_Main* message, uint16_t tag, const char* str, - uint32_t command_id, - bool flag) { + uint32_t command_id) { furi_check(message); char* str_copy = NULL; if(str) { str_copy = strdup(str); } - message->command_id = command_id; - message->command_status = PB_CommandStatus_OK; - message->cb_content.funcs.encode = NULL; - message->which_content = tag; - message->has_next = false; + test_rpc_fill_basic_message(message, tag, command_id); switch(tag) { case PB_Main_storage_info_request_tag: message->content.storage_info_request.path = str_copy; @@ -307,10 +322,6 @@ static void test_rpc_create_simple_message( case PB_Main_storage_stat_request_tag: message->content.storage_stat_request.path = str_copy; break; - case PB_Main_storage_list_request_tag: - message->content.storage_list_request.path = str_copy; - message->content.storage_list_request.include_md5 = flag; - break; case PB_Main_storage_mkdir_request_tag: message->content.storage_mkdir_request.path = str_copy; break; @@ -573,11 +584,29 @@ static void message->content.storage_list_response.file[2].name = str; } +static bool test_rpc_system_storage_list_filter( + const FileInfo* fileinfo, + const char* name, + size_t filter_max_size) { + bool result = false; + + do { + if(!path_contains_only_ascii(name)) break; + if(filter_max_size) { + if(fileinfo->size > filter_max_size) break; + } + result = true; + } while(false); + + return result; +} + static void test_rpc_storage_list_create_expected_list( MsgList_t msg_list, const char* path, uint32_t command_id, - bool append_md5) { + bool append_md5, + size_t filter_max_size) { Storage* fs_api = furi_record_open(RECORD_STORAGE); File* dir = storage_file_alloc(fs_api); @@ -615,7 +644,7 @@ static void test_rpc_storage_list_create_expected_list( i = 0; } - if(path_contains_only_ascii(name)) { + if(test_rpc_system_storage_list_filter(&fileinfo, name, filter_max_size)) { list->file[i].type = file_info_is_dir(&fileinfo) ? PB_Storage_File_FileType_DIR : PB_Storage_File_FileType_FILE; list->file[i].size = fileinfo.size; @@ -698,17 +727,21 @@ static void test_rpc_free_msg_list(MsgList_t msg_list) { MsgList_clear(msg_list); } -static void test_rpc_storage_list_run(const char* path, uint32_t command_id, bool md5) { +static void test_rpc_storage_list_run( + const char* path, + uint32_t command_id, + bool md5, + size_t filter_max_size) { PB_Main request; MsgList_t expected_msg_list; MsgList_init(expected_msg_list); - test_rpc_create_simple_message( - &request, PB_Main_storage_list_request_tag, path, command_id, md5); + test_rpc_create_storage_list_request(&request, path, md5, command_id, filter_max_size); if(!strcmp(path, "/")) { test_rpc_storage_list_create_expected_list_root(expected_msg_list, command_id); } else { - test_rpc_storage_list_create_expected_list(expected_msg_list, path, command_id, md5); + test_rpc_storage_list_create_expected_list( + expected_msg_list, path, command_id, md5, filter_max_size); } test_rpc_encode_and_feed_one(&request, 0); test_rpc_decode_and_compare(expected_msg_list, 0); @@ -718,25 +751,32 @@ static void test_rpc_storage_list_run(const char* path, uint32_t command_id, boo } MU_TEST(test_storage_list) { - test_rpc_storage_list_run("/", ++command_id, false); - test_rpc_storage_list_run(EXT_PATH("nfc"), ++command_id, false); - test_rpc_storage_list_run(STORAGE_INT_PATH_PREFIX, ++command_id, false); - test_rpc_storage_list_run(STORAGE_EXT_PATH_PREFIX, ++command_id, false); - test_rpc_storage_list_run(EXT_PATH("infrared"), ++command_id, false); - test_rpc_storage_list_run(EXT_PATH("ibutton"), ++command_id, false); - test_rpc_storage_list_run(EXT_PATH("lfrfid"), ++command_id, false); - test_rpc_storage_list_run("error_path", ++command_id, false); + test_rpc_storage_list_run("/", ++command_id, false, 0); + test_rpc_storage_list_run(EXT_PATH("nfc"), ++command_id, false, 0); + test_rpc_storage_list_run(STORAGE_INT_PATH_PREFIX, ++command_id, false, 0); + test_rpc_storage_list_run(STORAGE_EXT_PATH_PREFIX, ++command_id, false, 0); + test_rpc_storage_list_run(EXT_PATH("infrared"), ++command_id, false, 0); + test_rpc_storage_list_run(EXT_PATH("ibutton"), ++command_id, false, 0); + test_rpc_storage_list_run(EXT_PATH("lfrfid"), ++command_id, false, 0); + test_rpc_storage_list_run("error_path", ++command_id, false, 0); } MU_TEST(test_storage_list_md5) { - test_rpc_storage_list_run("/", ++command_id, true); - test_rpc_storage_list_run(EXT_PATH("nfc"), ++command_id, true); - test_rpc_storage_list_run(STORAGE_INT_PATH_PREFIX, ++command_id, true); - test_rpc_storage_list_run(STORAGE_EXT_PATH_PREFIX, ++command_id, true); - test_rpc_storage_list_run(EXT_PATH("infrared"), ++command_id, true); - test_rpc_storage_list_run(EXT_PATH("ibutton"), ++command_id, true); - test_rpc_storage_list_run(EXT_PATH("lfrfid"), ++command_id, true); - test_rpc_storage_list_run("error_path", ++command_id, true); + test_rpc_storage_list_run("/", ++command_id, true, 0); + test_rpc_storage_list_run(EXT_PATH("nfc"), ++command_id, true, 0); + test_rpc_storage_list_run(STORAGE_INT_PATH_PREFIX, ++command_id, true, 0); + test_rpc_storage_list_run(STORAGE_EXT_PATH_PREFIX, ++command_id, true, 0); + test_rpc_storage_list_run(EXT_PATH("infrared"), ++command_id, true, 0); + test_rpc_storage_list_run(EXT_PATH("ibutton"), ++command_id, true, 0); + test_rpc_storage_list_run(EXT_PATH("lfrfid"), ++command_id, true, 0); + test_rpc_storage_list_run("error_path", ++command_id, true, 0); +} + +MU_TEST(test_storage_list_size) { + test_rpc_storage_list_run(TEST_DIR_NAME, ++command_id, false, 0); + test_rpc_storage_list_run(TEST_DIR_NAME, ++command_id, false, 1); + test_rpc_storage_list_run(TEST_DIR_NAME, ++command_id, false, 1000); + test_rpc_storage_list_run(TEST_DIR_NAME, ++command_id, false, 2500); } static void @@ -804,8 +844,7 @@ static void test_storage_read_run(const char* path, uint32_t command_id) { MsgList_init(expected_msg_list); test_rpc_add_read_to_list_by_reading_real_file(expected_msg_list, path, command_id); - test_rpc_create_simple_message( - &request, PB_Main_storage_read_request_tag, path, command_id, false); + test_rpc_create_simple_message(&request, PB_Main_storage_read_request_tag, path, command_id); test_rpc_encode_and_feed_one(&request, 0); test_rpc_decode_and_compare(expected_msg_list, 0); @@ -859,8 +898,7 @@ static void test_rpc_storage_info_run(const char* path, uint32_t command_id) { MsgList_t expected_msg_list; MsgList_init(expected_msg_list); - test_rpc_create_simple_message( - &request, PB_Main_storage_info_request_tag, path, command_id, false); + test_rpc_create_simple_message(&request, PB_Main_storage_info_request_tag, path, command_id); PB_Main* response = MsgList_push_new(expected_msg_list); response->command_id = command_id; @@ -892,8 +930,7 @@ static void test_rpc_storage_stat_run(const char* path, uint32_t command_id) { MsgList_t expected_msg_list; MsgList_init(expected_msg_list); - test_rpc_create_simple_message( - &request, PB_Main_storage_stat_request_tag, path, command_id, false); + test_rpc_create_simple_message(&request, PB_Main_storage_stat_request_tag, path, command_id); Storage* fs_api = furi_record_open(RECORD_STORAGE); FileInfo fileinfo; @@ -1005,11 +1042,7 @@ static void test_storage_write_read_run( test_rpc_add_empty_to_list(expected_msg_list, PB_CommandStatus_OK, *command_id); test_rpc_create_simple_message( - MsgList_push_raw(input_msg_list), - PB_Main_storage_read_request_tag, - path, - ++*command_id, - false); + MsgList_push_raw(input_msg_list), PB_Main_storage_read_request_tag, path, ++*command_id); test_rpc_add_read_or_write_to_list( expected_msg_list, READ_RESPONSE, @@ -1082,8 +1115,7 @@ MU_TEST(test_storage_interrupt_continuous_same_system) { MsgList_push_new(input_msg_list), PB_Main_storage_mkdir_request_tag, TEST_DIR "dir1", - command_id + 1, - false); + command_id + 1); test_rpc_add_read_or_write_to_list( input_msg_list, WRITE_REQUEST, @@ -1163,8 +1195,7 @@ static void test_storage_delete_run( MsgList_t expected_msg_list; MsgList_init(expected_msg_list); - test_rpc_create_simple_message( - &request, PB_Main_storage_delete_request_tag, path, command_id, false); + test_rpc_create_simple_message(&request, PB_Main_storage_delete_request_tag, path, command_id); request.content.storage_delete_request.recursive = recursive; test_rpc_add_empty_to_list(expected_msg_list, status, command_id); @@ -1245,8 +1276,7 @@ static void test_storage_mkdir_run(const char* path, size_t command_id, PB_Comma MsgList_t expected_msg_list; MsgList_init(expected_msg_list); - test_rpc_create_simple_message( - &request, PB_Main_storage_mkdir_request_tag, path, command_id, false); + test_rpc_create_simple_message(&request, PB_Main_storage_mkdir_request_tag, path, command_id); test_rpc_add_empty_to_list(expected_msg_list, status, command_id); test_rpc_encode_and_feed_one(&request, 0); @@ -1297,12 +1327,11 @@ static void test_storage_md5sum_run( MsgList_t expected_msg_list; MsgList_init(expected_msg_list); - test_rpc_create_simple_message( - &request, PB_Main_storage_md5sum_request_tag, path, command_id, false); + test_rpc_create_simple_message(&request, PB_Main_storage_md5sum_request_tag, path, command_id); if(status == PB_CommandStatus_OK) { PB_Main* response = MsgList_push_new(expected_msg_list); test_rpc_create_simple_message( - response, PB_Main_storage_md5sum_response_tag, md5sum, command_id, false); + response, PB_Main_storage_md5sum_response_tag, md5sum, command_id); response->command_status = status; } else { test_rpc_add_empty_to_list(expected_msg_list, status, command_id); @@ -1461,6 +1490,7 @@ MU_TEST_SUITE(test_rpc_storage) { MU_RUN_TEST(test_storage_stat); MU_RUN_TEST(test_storage_list); MU_RUN_TEST(test_storage_list_md5); + MU_RUN_TEST(test_storage_list_size); MU_RUN_TEST(test_storage_read); MU_RUN_TEST(test_storage_write_read); MU_RUN_TEST(test_storage_write); @@ -1759,8 +1789,7 @@ MU_TEST(test_rpc_multisession_storage) { MsgList_push_raw(input_0), PB_Main_storage_read_request_tag, TEST_DIR "file0.txt", - ++command_id, - false); + ++command_id); test_rpc_add_read_or_write_to_list( expected_0, READ_RESPONSE, TEST_DIR "file0.txt", pattern, sizeof(pattern), 1, command_id); @@ -1768,8 +1797,7 @@ MU_TEST(test_rpc_multisession_storage) { MsgList_push_raw(input_1), PB_Main_storage_read_request_tag, TEST_DIR "file1.txt", - ++command_id, - false); + ++command_id); test_rpc_add_read_or_write_to_list( expected_1, READ_RESPONSE, TEST_DIR "file1.txt", pattern, sizeof(pattern), 1, command_id); diff --git a/applications/services/rpc/rpc_storage.c b/applications/services/rpc/rpc_storage.c index 93c7043e8a9..ee024b823a1 100644 --- a/applications/services/rpc/rpc_storage.c +++ b/applications/services/rpc/rpc_storage.c @@ -242,6 +242,23 @@ static void rpc_system_storage_list_root(const PB_Main* request, void* context) rpc_send_and_release(session, &response); } +static bool rpc_system_storage_list_filter( + const PB_Storage_ListRequest* request, + const FileInfo* fileinfo, + const char* name) { + bool result = false; + + do { + if(!path_contains_only_ascii(name)) break; + if(request->filter_max_size) { + if(fileinfo->size > request->filter_max_size) break; + } + result = true; + } while(false); + + return result; +} + static void rpc_system_storage_list_process(const PB_Main* request, void* context) { furi_assert(request); furi_assert(context); @@ -253,9 +270,11 @@ static void rpc_system_storage_list_process(const PB_Main* request, void* contex RpcSession* session = rpc_storage->session; furi_assert(session); + const PB_Storage_ListRequest* list_request = &request->content.storage_list_request; + rpc_system_storage_reset_state(rpc_storage, session, true); - if(!strcmp(request->content.storage_list_request.path, "/")) { + if(!strcmp(list_request->path, "/")) { rpc_system_storage_list_root(request, context); return; } @@ -271,7 +290,7 @@ static void rpc_system_storage_list_process(const PB_Main* request, void* contex }; PB_Storage_ListResponse* list = &response.content.storage_list_response; - bool include_md5 = request->content.storage_list_request.include_md5; + bool include_md5 = list_request->include_md5; FuriString* md5 = furi_string_alloc(); FuriString* md5_path = furi_string_alloc(); File* file = storage_file_alloc(fs_api); @@ -279,7 +298,7 @@ static void rpc_system_storage_list_process(const PB_Main* request, void* contex bool finish = false; int i = 0; - if(!storage_dir_open(dir, request->content.storage_list_request.path)) { + if(!storage_dir_open(dir, list_request->path)) { response.command_status = rpc_system_storage_get_file_error(dir); response.which_content = PB_Main_empty_tag; finish = true; @@ -289,7 +308,7 @@ static void rpc_system_storage_list_process(const PB_Main* request, void* contex FileInfo fileinfo; char* name = malloc(MAX_NAME_LENGTH + 1); if(storage_dir_read(dir, &fileinfo, name, MAX_NAME_LENGTH)) { - if(path_contains_only_ascii(name)) { + if(rpc_system_storage_list_filter(list_request, &fileinfo, name)) { if(i == COUNT_OF(list->file)) { list->file_count = i; response.has_next = true; @@ -303,11 +322,7 @@ static void rpc_system_storage_list_process(const PB_Main* request, void* contex list->file[i].name = name; if(include_md5 && !file_info_is_dir(&fileinfo)) { - furi_string_printf( //-V576 - md5_path, - "%s/%s", - request->content.storage_list_request.path, - name); + furi_string_printf(md5_path, "%s/%s", list_request->path, name); //-V576 if(md5_string_calc_file(file, furi_string_get_cstr(md5_path), md5, NULL)) { char* md5sum = list->file[i].md5sum; diff --git a/assets/protobuf b/assets/protobuf index 7e011a95863..327163d5867 160000 --- a/assets/protobuf +++ b/assets/protobuf @@ -1 +1 @@ -Subproject commit 7e011a95863716e72e7c6b5d552bca241d688304 +Subproject commit 327163d5867c7aa3051334c93ced718d15bfe4da diff --git a/fbt_options.py b/fbt_options.py index b6fcc70f233..bd804fc8b57 100644 --- a/fbt_options.py +++ b/fbt_options.py @@ -72,6 +72,7 @@ "unit_tests": [ "basic_services", "updater_app", + "radio_device_cc1101_ext", "unit_tests", ], } diff --git a/scripts/fwflash.py b/scripts/fwflash.py index c119aaf800f..6948bd7f51b 100755 --- a/scripts/fwflash.py +++ b/scripts/fwflash.py @@ -10,6 +10,7 @@ from dataclasses import dataclass, field from flipper.app import App +from serial.tools.list_ports_common import ListPortInfo # When adding an interface, also add it to SWD_TRANSPORT in fbt/ufbt options @@ -88,8 +89,9 @@ def flash(self, file_path: str, do_verify: bool) -> bool: self._add_file(openocd_launch_params, self.interface.config_file) if self.serial: self._add_serial(openocd_launch_params, self.serial) - for additional_arg in self.interface.additional_args: - self._add_command(openocd_launch_params, additional_arg) + if self.interface.additional_args: + for additional_arg in self.interface.additional_args: + self._add_command(openocd_launch_params, additional_arg) self._add_file(openocd_launch_params, "target/stm32wbx.cfg") self._add_command(openocd_launch_params, "init") program_params = [ @@ -124,8 +126,9 @@ def probe(self) -> bool: self._add_file(openocd_launch_params, self.interface.config_file) if self.serial: self._add_serial(openocd_launch_params, self.serial) - for additional_arg in self.interface.additional_args: - self._add_command(openocd_launch_params, additional_arg) + if self.interface.additional_args: + for additional_arg in self.interface.additional_args: + self._add_command(openocd_launch_params, additional_arg) self._add_file(openocd_launch_params, "target/stm32wbx.cfg") self._add_command(openocd_launch_params, "init") self._add_command(openocd_launch_params, "exit") @@ -167,7 +170,9 @@ def blackmagic_find_serial(serial: str): if not serial.startswith("\\\\.\\"): serial = f"\\\\.\\{serial}" - ports = list(list_ports.grep("blackmagic")) + # idk why, but python thinks that list_ports.grep returns tuple[str, str, str] + ports: list[ListPortInfo] = list(list_ports.grep("blackmagic")) # type: ignore + if len(ports) == 0: return None elif len(ports) > 2: From 7aa55ebc6c2a68afcba1392baae416d13837bab0 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Fri, 1 Sep 2023 10:47:02 +0900 Subject: [PATCH 733/824] [FL-3543] Check the filetype of the update manifest (#3025) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- lib/update_util/update_manifest.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/update_util/update_manifest.c b/lib/update_util/update_manifest.c index 47b2cc0b985..42ab073b04c 100644 --- a/lib/update_util/update_manifest.c +++ b/lib/update_util/update_manifest.c @@ -54,10 +54,10 @@ static bool FuriString* filetype; - // TODO FL-3543: compare filetype? filetype = furi_string_alloc(); update_manifest->valid = flipper_format_read_header(flipper_file, filetype, &update_manifest->manifest_version) && + furi_string_cmp_str(filetype, "Flipper firmware upgrade configuration") == 0 && flipper_format_read_string(flipper_file, MANIFEST_KEY_INFO, update_manifest->version) && flipper_format_read_uint32( flipper_file, MANIFEST_KEY_TARGET, &update_manifest->target, 1) && From f218c41d835354e382806ed6c44f1582a322279f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Fri, 1 Sep 2023 10:54:52 +0900 Subject: [PATCH 734/824] Undo some TODO (#3024) * TODO FL-3497: impossible to fix with current memory allocator * TODO FL-3497: removed, requires radically different settings approach * TODO FL-3514: removed, yes we should * TODO FL-3498: implemented, guarding view port access with mutex. * TODO FL-3515: removed, questionable but ok * TODO FL-3510: removed, comment added * TODO FL-3500: refactored, store pin numbers in GpioPinRecord, fix gpio app crash caused by incorrect gpio_pins traversal. * Format Sources * TODO FL-3505: removed, mutex alone is not going to fix issue with WPAN architecture * TODO FL-3506: removed, change ownership by copy is good * TODO FL-3519: documentation and link to source added * Lib: remove unneded total sum from comment in bq27220 --------- Co-authored-by: hedger --- .../debug/unit_tests/furi/furi_memmgr_test.c | 1 - applications/main/gpio/gpio_items.c | 8 +- applications/services/desktop/desktop.c | 3 - applications/services/gui/gui.c | 7 +- applications/services/gui/view_dispatcher.c | 1 - applications/services/gui/view_port.c | 46 +++++++-- applications/services/gui/view_port_i.h | 1 + .../services/power/power_service/power.c | 2 +- .../targets/f18/furi_hal/furi_hal_resources.c | 97 ++++++++++--------- .../targets/f18/furi_hal/furi_hal_resources.h | 1 + firmware/targets/f7/ble_glue/ble_glue.c | 1 - .../targets/f7/ble_glue/services/gatt_char.c | 1 - .../targets/f7/furi_hal/furi_hal_resources.c | 58 ++++++----- .../targets/f7/furi_hal/furi_hal_resources.h | 1 + lib/drivers/bq27220.c | 9 +- 15 files changed, 138 insertions(+), 99 deletions(-) diff --git a/applications/debug/unit_tests/furi/furi_memmgr_test.c b/applications/debug/unit_tests/furi/furi_memmgr_test.c index 05d967a9918..a28632cf4da 100644 --- a/applications/debug/unit_tests/furi/furi_memmgr_test.c +++ b/applications/debug/unit_tests/furi/furi_memmgr_test.c @@ -26,7 +26,6 @@ void test_furi_memmgr() { mu_assert_int_eq(66, ((uint8_t*)ptr)[i]); } - // TODO FL-3492: fix realloc to copy only old size, and write testcase that leftover of reallocated memory is zero-initialized free(ptr); // allocate and zero-initialize array (calloc) diff --git a/applications/main/gpio/gpio_items.c b/applications/main/gpio/gpio_items.c index 02f7d95b0e6..746abe032ae 100644 --- a/applications/main/gpio/gpio_items.c +++ b/applications/main/gpio/gpio_items.c @@ -18,10 +18,12 @@ GPIOItems* gpio_items_alloc() { } items->pins = malloc(sizeof(GpioPinRecord) * items->count); - for(size_t i = 0; i < items->count; i++) { + size_t index = 0; + for(size_t i = 0; i < gpio_pins_count; i++) { if(!gpio_pins[i].debug) { - items->pins[i].pin = gpio_pins[i].pin; - items->pins[i].name = gpio_pins[i].name; + items->pins[index].pin = gpio_pins[i].pin; + items->pins[index].name = gpio_pins[i].name; + index++; } } return items; diff --git a/applications/services/desktop/desktop.c b/applications/services/desktop/desktop.c index 0627652ab53..547883e9a74 100644 --- a/applications/services/desktop/desktop.c +++ b/applications/services/desktop/desktop.c @@ -101,7 +101,6 @@ static void desktop_clock_draw_callback(Canvas* canvas, void* context) { char buffer[20]; snprintf(buffer, sizeof(buffer), "%02u:%02u", hour, desktop->time_minute); - // TODO FL-3515: never do that, may cause visual glitches view_port_set_width( desktop->clock_viewport, canvas_string_width(canvas, buffer) - 1 + (desktop->time_minute % 10 == 1)); @@ -126,8 +125,6 @@ static bool desktop_custom_event_callback(void* context, uint32_t event) { return true; case DesktopGlobalAfterAppFinished: animation_manager_load_and_continue_animation(desktop->animation_manager); - // TODO FL-3497: Implement a message mechanism for loading settings and (optionally) - // locking and unlocking DESKTOP_SETTINGS_LOAD(&desktop->settings); desktop_clock_reconfigure(desktop); diff --git a/applications/services/gui/gui.c b/applications/services/gui/gui.c index b96f89db9fd..0bdc999b774 100644 --- a/applications/services/gui/gui.c +++ b/applications/services/gui/gui.c @@ -361,10 +361,11 @@ void gui_add_view_port(Gui* gui, ViewPort* view_port, GuiLayer layer) { furi_assert(view_port); furi_check(layer < GuiLayerMAX); // Only fullscreen supports Vertical orientation for now - furi_assert( + ViewPortOrientation view_port_orientation = view_port_get_orientation(view_port); + furi_check( (layer == GuiLayerFullscreen) || - ((view_port->orientation != ViewPortOrientationVertical) && - (view_port->orientation != ViewPortOrientationVerticalFlip))); + ((view_port_orientation != ViewPortOrientationVertical) && + (view_port_orientation != ViewPortOrientationVerticalFlip))); gui_lock(gui); // Verify that view port is not yet added diff --git a/applications/services/gui/view_dispatcher.c b/applications/services/gui/view_dispatcher.c index 83f0edbeae9..0119abc2002 100644 --- a/applications/services/gui/view_dispatcher.c +++ b/applications/services/gui/view_dispatcher.c @@ -272,7 +272,6 @@ void view_dispatcher_handle_input(ViewDispatcher* view_dispatcher, InputEvent* e } else if(view_dispatcher->navigation_event_callback) { // Dispatch navigation event if(!view_dispatcher->navigation_event_callback(view_dispatcher->event_context)) { - // TODO FL-3514: should we allow view_dispatcher to stop without navigation_event_callback? view_dispatcher_stop(view_dispatcher); return; } diff --git a/applications/services/gui/view_port.c b/applications/services/gui/view_port.c index 57c0fddb413..0f300c16887 100644 --- a/applications/services/gui/view_port.c +++ b/applications/services/gui/view_port.c @@ -7,8 +7,6 @@ #include "gui.h" #include "gui_i.h" -// TODO FL-3498: add mutex to view_port ops - _Static_assert(ViewPortOrientationMAX == 4, "Incorrect ViewPortOrientation count"); _Static_assert( (ViewPortOrientationHorizontal == 0 && ViewPortOrientationHorizontalFlip == 1 && @@ -95,52 +93,73 @@ ViewPort* view_port_alloc() { ViewPort* view_port = malloc(sizeof(ViewPort)); view_port->orientation = ViewPortOrientationHorizontal; view_port->is_enabled = true; + view_port->mutex = furi_mutex_alloc(FuriMutexTypeRecursive); return view_port; } void view_port_free(ViewPort* view_port) { furi_assert(view_port); + furi_check(furi_mutex_acquire(view_port->mutex, FuriWaitForever) == FuriStatusOk); furi_check(view_port->gui == NULL); + furi_check(furi_mutex_release(view_port->mutex) == FuriStatusOk); + furi_mutex_free(view_port->mutex); free(view_port); } void view_port_set_width(ViewPort* view_port, uint8_t width) { furi_assert(view_port); + furi_check(furi_mutex_acquire(view_port->mutex, FuriWaitForever) == FuriStatusOk); view_port->width = width; + furi_check(furi_mutex_release(view_port->mutex) == FuriStatusOk); } uint8_t view_port_get_width(const ViewPort* view_port) { furi_assert(view_port); - return view_port->width; + furi_check(furi_mutex_acquire(view_port->mutex, FuriWaitForever) == FuriStatusOk); + uint8_t width = view_port->width; + furi_check(furi_mutex_release(view_port->mutex) == FuriStatusOk); + return width; } void view_port_set_height(ViewPort* view_port, uint8_t height) { furi_assert(view_port); + furi_check(furi_mutex_acquire(view_port->mutex, FuriWaitForever) == FuriStatusOk); view_port->height = height; + furi_check(furi_mutex_release(view_port->mutex) == FuriStatusOk); } uint8_t view_port_get_height(const ViewPort* view_port) { furi_assert(view_port); - return view_port->height; + furi_check(furi_mutex_acquire(view_port->mutex, FuriWaitForever) == FuriStatusOk); + uint8_t height = view_port->height; + furi_check(furi_mutex_release(view_port->mutex) == FuriStatusOk); + return height; } void view_port_enabled_set(ViewPort* view_port, bool enabled) { furi_assert(view_port); + furi_check(furi_mutex_acquire(view_port->mutex, FuriWaitForever) == FuriStatusOk); if(view_port->is_enabled != enabled) { view_port->is_enabled = enabled; if(view_port->gui) gui_update(view_port->gui); } + furi_check(furi_mutex_release(view_port->mutex) == FuriStatusOk); } bool view_port_is_enabled(const ViewPort* view_port) { furi_assert(view_port); - return view_port->is_enabled; + furi_check(furi_mutex_acquire(view_port->mutex, FuriWaitForever) == FuriStatusOk); + bool is_enabled = view_port->is_enabled; + furi_check(furi_mutex_release(view_port->mutex) == FuriStatusOk); + return is_enabled; } void view_port_draw_callback_set(ViewPort* view_port, ViewPortDrawCallback callback, void* context) { furi_assert(view_port); + furi_check(furi_mutex_acquire(view_port->mutex, FuriWaitForever) == FuriStatusOk); view_port->draw_callback = callback; view_port->draw_callback_context = context; + furi_check(furi_mutex_release(view_port->mutex) == FuriStatusOk); } void view_port_input_callback_set( @@ -148,34 +167,43 @@ void view_port_input_callback_set( ViewPortInputCallback callback, void* context) { furi_assert(view_port); + furi_check(furi_mutex_acquire(view_port->mutex, FuriWaitForever) == FuriStatusOk); view_port->input_callback = callback; view_port->input_callback_context = context; + furi_check(furi_mutex_release(view_port->mutex) == FuriStatusOk); } void view_port_update(ViewPort* view_port) { furi_assert(view_port); + furi_check(furi_mutex_acquire(view_port->mutex, FuriWaitForever) == FuriStatusOk); if(view_port->gui && view_port->is_enabled) gui_update(view_port->gui); + furi_check(furi_mutex_release(view_port->mutex) == FuriStatusOk); } void view_port_gui_set(ViewPort* view_port, Gui* gui) { furi_assert(view_port); + furi_check(furi_mutex_acquire(view_port->mutex, FuriWaitForever) == FuriStatusOk); view_port->gui = gui; + furi_check(furi_mutex_release(view_port->mutex) == FuriStatusOk); } void view_port_draw(ViewPort* view_port, Canvas* canvas) { furi_assert(view_port); furi_assert(canvas); + furi_check(furi_mutex_acquire(view_port->mutex, FuriWaitForever) == FuriStatusOk); furi_check(view_port->gui); if(view_port->draw_callback) { view_port_setup_canvas_orientation(view_port, canvas); view_port->draw_callback(canvas, view_port->draw_callback_context); } + furi_check(furi_mutex_release(view_port->mutex) == FuriStatusOk); } void view_port_input(ViewPort* view_port, InputEvent* event) { furi_assert(view_port); furi_assert(event); + furi_check(furi_mutex_acquire(view_port->mutex, FuriWaitForever) == FuriStatusOk); furi_check(view_port->gui); if(view_port->input_callback) { @@ -183,13 +211,19 @@ void view_port_input(ViewPort* view_port, InputEvent* event) { view_port_map_input(event, orientation); view_port->input_callback(event, view_port->input_callback_context); } + furi_check(furi_mutex_release(view_port->mutex) == FuriStatusOk); } void view_port_set_orientation(ViewPort* view_port, ViewPortOrientation orientation) { furi_assert(view_port); + furi_check(furi_mutex_acquire(view_port->mutex, FuriWaitForever) == FuriStatusOk); view_port->orientation = orientation; + furi_check(furi_mutex_release(view_port->mutex) == FuriStatusOk); } ViewPortOrientation view_port_get_orientation(const ViewPort* view_port) { - return view_port->orientation; + furi_check(furi_mutex_acquire(view_port->mutex, FuriWaitForever) == FuriStatusOk); + ViewPortOrientation orientation = view_port->orientation; + furi_check(furi_mutex_release(view_port->mutex) == FuriStatusOk); + return orientation; } diff --git a/applications/services/gui/view_port_i.h b/applications/services/gui/view_port_i.h index 90f48ac930a..444e1a27c18 100644 --- a/applications/services/gui/view_port_i.h +++ b/applications/services/gui/view_port_i.h @@ -10,6 +10,7 @@ struct ViewPort { Gui* gui; + FuriMutex* mutex; bool is_enabled; ViewPortOrientation orientation; diff --git a/applications/services/power/power_service/power.c b/applications/services/power/power_service/power.c index e39a84eafcf..ad3a5114dca 100644 --- a/applications/services/power/power_service/power.c +++ b/applications/services/power/power_service/power.c @@ -30,7 +30,7 @@ void power_draw_battery_callback(Canvas* canvas, void* context) { if(power->state == PowerStateCharging) { canvas_set_bitmap_mode(canvas, 1); canvas_set_color(canvas, ColorWhite); - // TODO FL-3510: replace -1 magic for uint8_t with re-framing + // -1 used here to overflow u8 number and render is outside of the area canvas_draw_icon(canvas, 8, -1, &I_Charging_lightning_mask_9x10); canvas_set_color(canvas, ColorBlack); canvas_draw_icon(canvas, 8, -1, &I_Charging_lightning_9x10); diff --git a/firmware/targets/f18/furi_hal/furi_hal_resources.c b/firmware/targets/f18/furi_hal/furi_hal_resources.c index 63da03e04ea..efd39977b4d 100644 --- a/firmware/targets/f18/furi_hal/furi_hal_resources.c +++ b/firmware/targets/f18/furi_hal/furi_hal_resources.c @@ -67,35 +67,53 @@ const GpioPin gpio_usb_dm = {.port = GPIOA, .pin = LL_GPIO_PIN_11}; const GpioPin gpio_usb_dp = {.port = GPIOA, .pin = LL_GPIO_PIN_12}; const GpioPinRecord gpio_pins[] = { - {.pin = &gpio_ext_pa7, .name = "PA7", .debug = false}, - {.pin = &gpio_ext_pa6, .name = "PA6", .debug = false}, - {.pin = &gpio_ext_pa4, .name = "PA4", .debug = false}, - {.pin = &gpio_ext_pb3, .name = "PB3", .debug = false}, - {.pin = &gpio_ext_pb2, .name = "PB2", .debug = false}, - {.pin = &gpio_ext_pc3, .name = "PC3", .debug = false}, - {.pin = &gpio_ext_pc1, .name = "PC1", .debug = false}, - {.pin = &gpio_ext_pc0, .name = "PC0", .debug = false}, - - {.pin = &gpio_ext_pc5, .name = "PC5", .debug = false}, - {.pin = &gpio_ext_pc4, .name = "PC4", .debug = false}, - {.pin = &gpio_ext_pa5, .name = "PA5", .debug = false}, - {.pin = &gpio_ext_pb9, .name = "PB9", .debug = false}, - {.pin = &gpio_ext_pa0, .name = "PA0", .debug = false}, - {.pin = &gpio_ext_pa1, .name = "PA1", .debug = false}, - {.pin = &gpio_ext_pa15, .name = "PA15", .debug = false}, - {.pin = &gpio_ext_pe4, .name = "PE4", .debug = false}, - {.pin = &gpio_ext_pa2, .name = "PA2", .debug = false}, - {.pin = &gpio_ext_pb4, .name = "PB4", .debug = false}, - {.pin = &gpio_ext_pb5, .name = "PB5", .debug = false}, - {.pin = &gpio_ext_pd0, .name = "PD0", .debug = false}, - {.pin = &gpio_ext_pb13, .name = "PB13", .debug = false}, + // 5V: 1 + {.pin = &gpio_ext_pa7, .name = "PA7", .number = 2, .debug = false}, + {.pin = &gpio_ext_pa6, .name = "PA6", .number = 3, .debug = false}, + {.pin = &gpio_ext_pa4, .name = "PA4", .number = 4, .debug = false}, + {.pin = &gpio_ext_pb3, .name = "PB3", .number = 5, .debug = false}, + {.pin = &gpio_ext_pb2, .name = "PB2", .number = 6, .debug = false}, + {.pin = &gpio_ext_pc3, .name = "PC3", .number = 7, .debug = false}, + // GND: 8 + // Space + // 3v3: 9 + {.pin = &gpio_swclk, .name = "PA14", .number = 10, .debug = true}, + // GND: 11 + {.pin = &gpio_swdio, .name = "PA13", .number = 12, .debug = true}, + {.pin = &gpio_usart_tx, .name = "PB6", .number = 13, .debug = true}, + {.pin = &gpio_usart_rx, .name = "PB7", .number = 14, .debug = true}, + {.pin = &gpio_ext_pc1, .name = "PC1", .number = 15, .debug = false}, + {.pin = &gpio_ext_pc0, .name = "PC0", .number = 16, .debug = false}, + {.pin = &gpio_ibutton, .name = "PB14", .number = 17, .debug = true}, + // GND: 18 + + // 2nd column + // 5V: 19 + {.pin = &gpio_ext_pc5, .name = "PC5", .number = 20, .debug = false}, + {.pin = &gpio_ext_pc4, .name = "PC4", .number = 21, .debug = false}, + {.pin = &gpio_ext_pa5, .name = "PA5", .number = 22, .debug = false}, + {.pin = &gpio_ext_pb9, .name = "PB9", .number = 23, .debug = false}, + {.pin = &gpio_ext_pa0, .name = "PA0", .number = 24, .debug = false}, + {.pin = &gpio_ext_pa1, .name = "PA1", .number = 25, .debug = false}, + // KEY: 26 + // Space + // 3v3: 27 + {.pin = &gpio_ext_pa15, .name = "PA15", .number = 28, .debug = false}, + // GND: 29 + {.pin = &gpio_ext_pe4, .name = "PE4", .number = 30, .debug = false}, + {.pin = &gpio_ext_pa2, .name = "PA2", .number = 31, .debug = false}, + {.pin = &gpio_ext_pb4, .name = "PB4", .number = 32, .debug = false}, + {.pin = &gpio_ext_pb5, .name = "PB5", .number = 33, .debug = false}, + {.pin = &gpio_ext_pd0, .name = "PD0", .number = 34, .debug = false}, + {.pin = &gpio_ext_pb13, .name = "PB13", .number = 35, .debug = false}, + // GND: 36 /* Dangerous pins, may damage hardware */ - {.pin = &gpio_usart_rx, .name = "PB7", .debug = true}, - {.pin = &gpio_speaker, .name = "PB8", .debug = true}, + {.pin = &gpio_usart_rx, .name = "PB7", .number = 0, .debug = true}, + {.pin = &gpio_speaker, .name = "PB8", .number = 0, .debug = true}, }; -const size_t gpio_pins_count = sizeof(gpio_pins) / sizeof(GpioPinRecord); +const size_t gpio_pins_count = COUNT_OF(gpio_pins); const InputPin input_pins[] = { {.gpio = &gpio_button_up, .key = InputKeyUp, .inverted = true, .name = "Up"}, @@ -106,7 +124,7 @@ const InputPin input_pins[] = { {.gpio = &gpio_button_back, .key = InputKeyBack, .inverted = true, .name = "Back"}, }; -const size_t input_pins_count = sizeof(input_pins) / sizeof(InputPin); +const size_t input_pins_count = COUNT_OF(input_pins); static void furi_hal_resources_init_input_pins(GpioMode mode) { for(size_t i = 0; i < input_pins_count; i++) { @@ -216,25 +234,10 @@ void furi_hal_resources_init() { } int32_t furi_hal_resources_get_ext_pin_number(const GpioPin* gpio) { - // TODO FL-3500: describe second ROW - if(gpio == &gpio_ext_pa7) - return 2; - else if(gpio == &gpio_ext_pa6) - return 3; - else if(gpio == &gpio_ext_pa4) - return 4; - else if(gpio == &gpio_ext_pb3) - return 5; - else if(gpio == &gpio_ext_pb2) - return 6; - else if(gpio == &gpio_ext_pc3) - return 7; - else if(gpio == &gpio_ext_pc1) - return 15; - else if(gpio == &gpio_ext_pc0) - return 16; - else if(gpio == &gpio_ibutton) - return 17; - else - return -1; + for(size_t i = 0; i < gpio_pins_count; i++) { + if(gpio_pins[i].pin == gpio) { + return gpio_pins[i].number; + } + } + return -1; } diff --git a/firmware/targets/f18/furi_hal/furi_hal_resources.h b/firmware/targets/f18/furi_hal/furi_hal_resources.h index 7d2caab3682..fed7802a585 100644 --- a/firmware/targets/f18/furi_hal/furi_hal_resources.h +++ b/firmware/targets/f18/furi_hal/furi_hal_resources.h @@ -41,6 +41,7 @@ typedef struct { typedef struct { const GpioPin* pin; const char* name; + const uint8_t number; const bool debug; } GpioPinRecord; diff --git a/firmware/targets/f7/ble_glue/ble_glue.c b/firmware/targets/f7/ble_glue/ble_glue.c index 746df71c7b6..2c30612b54e 100644 --- a/firmware/targets/f7/ble_glue/ble_glue.c +++ b/firmware/targets/f7/ble_glue/ble_glue.c @@ -222,7 +222,6 @@ bool ble_glue_wait_for_c2_start(int32_t timeout) { bool started = false; do { - // TODO FL-3505: use mutex? started = ble_glue->status == BleGlueStatusC2Started; if(!started) { timeout--; diff --git a/firmware/targets/f7/ble_glue/services/gatt_char.c b/firmware/targets/f7/ble_glue/services/gatt_char.c index e0539c34f30..f6e27f53e68 100644 --- a/firmware/targets/f7/ble_glue/services/gatt_char.c +++ b/firmware/targets/f7/ble_glue/services/gatt_char.c @@ -14,7 +14,6 @@ void flipper_gatt_characteristic_init( furi_assert(char_instance); // Copy the descriptor to the instance, since it may point to stack memory - // TODO FL-3506: only copy if really comes from stack char_instance->characteristic = malloc(sizeof(FlipperGattCharacteristicParams)); memcpy( (void*)char_instance->characteristic, diff --git a/firmware/targets/f7/furi_hal/furi_hal_resources.c b/firmware/targets/f7/furi_hal/furi_hal_resources.c index 4d52960d875..d519484d100 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_resources.c +++ b/firmware/targets/f7/furi_hal/furi_hal_resources.c @@ -69,22 +69,32 @@ const GpioPin gpio_usb_dm = {.port = GPIOA, .pin = LL_GPIO_PIN_11}; const GpioPin gpio_usb_dp = {.port = GPIOA, .pin = LL_GPIO_PIN_12}; const GpioPinRecord gpio_pins[] = { - {.pin = &gpio_ext_pa7, .name = "PA7", .debug = false}, - {.pin = &gpio_ext_pa6, .name = "PA6", .debug = false}, - {.pin = &gpio_ext_pa4, .name = "PA4", .debug = false}, - {.pin = &gpio_ext_pb3, .name = "PB3", .debug = false}, - {.pin = &gpio_ext_pb2, .name = "PB2", .debug = false}, - {.pin = &gpio_ext_pc3, .name = "PC3", .debug = false}, - {.pin = &gpio_ext_pc1, .name = "PC1", .debug = false}, - {.pin = &gpio_ext_pc0, .name = "PC0", .debug = false}, + // 5V: 1 + {.pin = &gpio_ext_pa7, .name = "PA7", .number = 2, .debug = false}, + {.pin = &gpio_ext_pa6, .name = "PA6", .number = 3, .debug = false}, + {.pin = &gpio_ext_pa4, .name = "PA4", .number = 4, .debug = false}, + {.pin = &gpio_ext_pb3, .name = "PB3", .number = 5, .debug = false}, + {.pin = &gpio_ext_pb2, .name = "PB2", .number = 6, .debug = false}, + {.pin = &gpio_ext_pc3, .name = "PC3", .number = 7, .debug = false}, + // GND: 8 + // Space + // 3v3: 9 + {.pin = &gpio_swclk, .name = "PA14", .number = 10, .debug = true}, + // GND: 11 + {.pin = &gpio_swdio, .name = "PA13", .number = 12, .debug = true}, + {.pin = &gpio_usart_tx, .name = "PB6", .number = 13, .debug = true}, + {.pin = &gpio_usart_rx, .name = "PB7", .number = 14, .debug = true}, + {.pin = &gpio_ext_pc1, .name = "PC1", .number = 15, .debug = false}, + {.pin = &gpio_ext_pc0, .name = "PC0", .number = 16, .debug = false}, + {.pin = &gpio_ibutton, .name = "PB14", .number = 17, .debug = true}, + // GND: 18 /* Dangerous pins, may damage hardware */ - {.pin = &gpio_usart_rx, .name = "PB7", .debug = true}, {.pin = &gpio_speaker, .name = "PB8", .debug = true}, {.pin = &gpio_infrared_tx, .name = "PB9", .debug = true}, }; -const size_t gpio_pins_count = sizeof(gpio_pins) / sizeof(GpioPinRecord); +const size_t gpio_pins_count = COUNT_OF(gpio_pins); const InputPin input_pins[] = { {.gpio = &gpio_button_up, .key = InputKeyUp, .inverted = true, .name = "Up"}, @@ -95,7 +105,7 @@ const InputPin input_pins[] = { {.gpio = &gpio_button_back, .key = InputKeyBack, .inverted = true, .name = "Back"}, }; -const size_t input_pins_count = sizeof(input_pins) / sizeof(InputPin); +const size_t input_pins_count = COUNT_OF(input_pins); static void furi_hal_resources_init_input_pins(GpioMode mode) { for(size_t i = 0; i < input_pins_count; i++) { @@ -210,24 +220,10 @@ void furi_hal_resources_init() { } int32_t furi_hal_resources_get_ext_pin_number(const GpioPin* gpio) { - if(gpio == &gpio_ext_pa7) - return 2; - else if(gpio == &gpio_ext_pa6) - return 3; - else if(gpio == &gpio_ext_pa4) - return 4; - else if(gpio == &gpio_ext_pb3) - return 5; - else if(gpio == &gpio_ext_pb2) - return 6; - else if(gpio == &gpio_ext_pc3) - return 7; - else if(gpio == &gpio_ext_pc1) - return 15; - else if(gpio == &gpio_ext_pc0) - return 16; - else if(gpio == &gpio_ibutton) - return 17; - else - return -1; + for(size_t i = 0; i < gpio_pins_count; i++) { + if(gpio_pins[i].pin == gpio) { + return gpio_pins[i].number; + } + } + return -1; } diff --git a/firmware/targets/f7/furi_hal/furi_hal_resources.h b/firmware/targets/f7/furi_hal/furi_hal_resources.h index 6e585c51822..6ca6f9df015 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_resources.h +++ b/firmware/targets/f7/furi_hal/furi_hal_resources.h @@ -41,6 +41,7 @@ typedef struct { typedef struct { const GpioPin* pin; const char* name; + const uint8_t number; const bool debug; } GpioPinRecord; diff --git a/lib/drivers/bq27220.c b/lib/drivers/bq27220.c index 4a9feed9be0..a3a88603d72 100644 --- a/lib/drivers/bq27220.c +++ b/lib/drivers/bq27220.c @@ -54,6 +54,10 @@ static bool bq27220_parameter_check( } if(update) { + // Datasheet contains incorrect procedure for memory update, more info: + // https://e2e.ti.com/support/power-management-group/power-management/f/power-management-forum/719878/bq27220-technical-reference-manual-sluubd4-is-missing-extended-data-commands-chapter + + // 2. Write the address AND the parameter data to 0x3E+ (auto increment) if(!furi_hal_i2c_write_mem( handle, BQ27220_ADDRESS, @@ -67,9 +71,12 @@ static bool bq27220_parameter_check( furi_delay_us(10000); + // 3. Calculate the check sum: 0xFF - (sum of address and data) OR 0xFF uint8_t checksum = bq27220_get_checksum(buffer, size + 2); + // 4. Write the check sum to 0x60 and the total length of (address + parameter data + check sum + length) to 0x61 buffer[0] = checksum; - buffer[1] = 4 + size; // TODO FL-3519: why 4? + // 2 bytes address, `size` bytes data, 1 byte check sum, 1 byte length + buffer[1] = 2 + size + 1 + 1; if(!furi_hal_i2c_write_mem( handle, BQ27220_ADDRESS, CommandMACDataSum, buffer, 2, BQ27220_I2C_TIMEOUT)) { FURI_LOG_I(TAG, "CRC write failed"); From 7531e18020b4f2b67b60950c841dc8e282d97d12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Fri, 1 Sep 2023 11:00:40 +0900 Subject: [PATCH 735/824] Move roadmap link to readme (#3014) Co-authored-by: hedger --- ReadMe.md | 4 ++++ documentation/RoadMap.md | 46 ---------------------------------------- 2 files changed, 4 insertions(+), 46 deletions(-) delete mode 100644 documentation/RoadMap.md diff --git a/ReadMe.md b/ReadMe.md index bbaf9603e28..dcf39c3ae7f 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -36,6 +36,10 @@ Finally, open a [Pull Request](https://github.com/flipperdevices/flipperzero-fir Flipper Zero Firmware is written in C, with some bits and pieces written in C++ and armv7m assembly languages. An intermediate level of C knowledge is recommended for comfortable programming. C, C++, and armv7m assembly languages are supported for Flipper applications. +# Firmware RoadMap + +[Firmware RoadMap Miro Board](https://miro.com/app/board/uXjVO_3D6xU=/) + ## Requirements Supported development platforms: diff --git a/documentation/RoadMap.md b/documentation/RoadMap.md deleted file mode 100644 index 658bb20862c..00000000000 --- a/documentation/RoadMap.md +++ /dev/null @@ -1,46 +0,0 @@ -# RoadMap - -# Where we are now (0.x.x branch) - -Our goal for the 0.x.x branch is to build an API and apps that are stable and usable. The first public release in this branch is 0.43.1. - -## What's already implemented - -**System and HAL** - -- Furi Core -- Furi HAL -- Loading applications from SD - -**Applications** - -- SubGhz: all most common protocols, reading RAW for everything else -- 125kHz RFID: all most common protocols -- NFC: reading/emulating MIFARE Ultralight, reading MIFARE Classic and DESFire, basic EMV, and basic NFC-B/F/V -- Infrared: all most common RC protocols, RAW format for everything else -- GPIO: UART bridge, basic GPIO controls -- iButton: DS1990, Cyfral, Metakom -- Bad USB: full USB Rubber Ducky support, some extras for Windows Alt codes -- U2F: full U2F specification support - -**External applications** - -- Bluetooth -- Snake game - -# Where we're going (Version 1) - -The main goal for 1.0.0 is to provide the first stable version for both Users and Developers. - -## What we're planning to implement in 1.0.0 - -- More protocols (gathering feedback) -- User documentation (work in progress) -- FuriHal: deep sleep mode, stable API, examples, documentation (work in progress) -- Application improvements (a ton of things that we want to add and improve that are too numerous to list here) - -## When will it happen, and where can I see the progress? - -Release 1.0.0 will likely happen around the end of 2023Q1. - -You can track the development progress in our public Miro board: https://miro.com/app/board/uXjVO_3D6xU=/?moveToWidget=3458764522498020058&cot=14 From d8d2b360cb819fedef198427b9d22adab5e0da8d Mon Sep 17 00:00:00 2001 From: suaveolent <2163625+suaveolent@users.noreply.github.com> Date: Fri, 1 Sep 2023 04:22:29 +0200 Subject: [PATCH 736/824] Add support for Mifare Classic 4k SAK 0x38 ATQA 0x02, 0x04, 0x08 (#3009) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: suaveolent Co-authored-by: あく --- lib/nfc/protocols/mifare_classic.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/nfc/protocols/mifare_classic.c b/lib/nfc/protocols/mifare_classic.c index 011747d59fd..a72d4aac0c1 100644 --- a/lib/nfc/protocols/mifare_classic.c +++ b/lib/nfc/protocols/mifare_classic.c @@ -381,7 +381,9 @@ bool mf_classic_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK) { } else if((ATQA0 == 0x01) && (ATQA1 == 0x0F) && (SAK == 0x01)) { //skylanders support return true; - } else if((ATQA0 == 0x42 || ATQA0 == 0x02) && (SAK == 0x18)) { + } else if( + ((ATQA0 == 0x42 || ATQA0 == 0x02) && (SAK == 0x18)) || + ((ATQA0 == 0x02 || ATQA0 == 0x04 || ATQA0 == 0x08) && (SAK == 0x38))) { return true; } else { return false; @@ -393,13 +395,17 @@ MfClassicType mf_classic_get_classic_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t if((ATQA0 == 0x44 || ATQA0 == 0x04)) { if((SAK == 0x08 || SAK == 0x88)) { return MfClassicType1k; + } else if((SAK == 0x38)) { + return MfClassicType4k; } else if(SAK == 0x09) { return MfClassicTypeMini; } } else if((ATQA0 == 0x01) && (ATQA1 == 0x0F) && (SAK == 0x01)) { //skylanders support return MfClassicType1k; - } else if((ATQA0 == 0x42 || ATQA0 == 0x02) && (SAK == 0x18)) { + } else if( + ((ATQA0 == 0x42 || ATQA0 == 0x02) && (SAK == 0x18)) || + ((ATQA0 == 0x02 || ATQA0 == 0x08) && (SAK == 0x38))) { return MfClassicType4k; } return MfClassicType1k; From 5eeb672dd454659ea2f1d218e136ebb9f321cebf Mon Sep 17 00:00:00 2001 From: hedger Date: Fri, 1 Sep 2023 06:09:48 +0300 Subject: [PATCH 737/824] github: workflow trigger optimizations (#3030) --- .github/workflows/build.yml | 72 +----------------- .github/workflows/build_compact.yml | 75 +++++++++++++++++++ .../workflows/lint_and_submodule_check.yml | 7 +- .github/workflows/merge_report.yml | 2 +- .github/workflows/pvs_studio.yml | 2 - .github/workflows/reindex.yml | 2 +- 6 files changed, 79 insertions(+), 81 deletions(-) create mode 100644 .github/workflows/build_compact.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bd85858a314..07b219712d7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,7 +15,7 @@ env: jobs: main: - runs-on: [self-hosted,FlipperZeroShell] + runs-on: [self-hosted, FlipperZeroShell] steps: - name: 'Wipe workspace' run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; @@ -37,7 +37,6 @@ jobs: TYPE="other" fi python3 scripts/get_env.py "--event_file=${{ github.event_path }}" "--type=$TYPE" || cat "${{ github.event_path }}" - echo random_hash=$(openssl rand -base64 40 | shasum -a 256 | awk '{print $1}') >> $GITHUB_OUTPUT echo "event_type=$TYPE" >> $GITHUB_OUTPUT - name: 'Check API versions' @@ -149,72 +148,3 @@ jobs: - [📥 DFU file](https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.branch_name}}/flipper-z-${{steps.names.outputs.default_target}}-full-${{steps.names.outputs.suffix}}.dfu) - [☁️ Web/App updater](https://lab.flipper.net/?url=https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.branch_name}}/flipper-z-${{steps.names.outputs.default_target}}-update-${{steps.names.outputs.suffix}}.tgz&channel=${{steps.names.outputs.branch_name}}&version=${{steps.names.outputs.commit_sha}}) edit-mode: replace - - compact: - if: ${{ !startsWith(github.ref, 'refs/tags') }} - runs-on: [self-hosted,FlipperZeroShell] - strategy: - matrix: - target: [f7, f18] - steps: - - name: 'Wipe workspace' - run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; - - - name: 'Checkout code' - uses: actions/checkout@v3 - with: - fetch-depth: 1 - submodules: false - ref: ${{ github.event.pull_request.head.sha }} - - - name: 'Get commit details' - run: | - if [[ ${{ github.event_name }} == 'pull_request' ]]; then - TYPE="pull" - elif [[ "${{ github.ref }}" == "refs/tags/"* ]]; then - TYPE="tag" - else - TYPE="other" - fi - python3 scripts/get_env.py "--event_file=${{ github.event_path }}" "--type=$TYPE" || cat "${{ github.event_path }}" - - - name: 'Build the firmware' - id: build-fw - run: | - set -e - TARGET="$(echo '${{ matrix.target }}' | sed 's/f//')"; \ - ./fbt TARGET_HW=$TARGET DEBUG=0 COMPACT=1 fap_dist updater_package - echo "sdk-file=$(ls dist/${{ matrix.target }}-*/flipper-z-${{ matrix.target }}-sdk-*.zip)" >> $GITHUB_OUTPUT - echo "hw-target-code=$TARGET" >> $GITHUB_OUTPUT - - - name: Deploy uFBT with SDK - uses: flipperdevices/flipperzero-ufbt-action@v0.1.0 - with: - task: setup - sdk-file: ${{ steps.build-fw.outputs.sdk-file }} - sdk-hw-target: ${{ steps.build-fw.outputs.hw-target-code }} - - - name: Build test app with SDK - run: | - mkdir testapp - cd testapp - ufbt create APPID=testapp - ufbt - - - name: Build example & external apps with uFBT - run: | - for appdir in 'applications/examples'; do - for app in $(find "$appdir" -maxdepth 1 -mindepth 1 -type d); do - pushd $app - TARGETS_FAM=$(grep "targets" application.fam || echo "${{ matrix.target }}") - if ! grep -q "${{ matrix.target }}" <<< $TARGETS_FAM ; then - echo Skipping unsupported app: $app - popd - continue - fi - echo Building $app - ufbt - popd - done - done - diff --git a/.github/workflows/build_compact.yml b/.github/workflows/build_compact.yml new file mode 100644 index 00000000000..705888a41ba --- /dev/null +++ b/.github/workflows/build_compact.yml @@ -0,0 +1,75 @@ +name: 'Compact build' + +on: + pull_request: + +env: + FBT_TOOLCHAIN_PATH: /runner/_work + +jobs: + compact: + runs-on: [self-hosted, FlipperZeroShell] + strategy: + matrix: + target: [f7, f18] + steps: + - name: 'Wipe workspace' + run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; + + - name: 'Checkout code' + uses: actions/checkout@v3 + with: + fetch-depth: 1 + submodules: false + ref: ${{ github.event.pull_request.head.sha }} + + - name: 'Get commit details' + run: | + if [[ ${{ github.event_name }} == 'pull_request' ]]; then + TYPE="pull" + elif [[ "${{ github.ref }}" == "refs/tags/"* ]]; then + TYPE="tag" + else + TYPE="other" + fi + python3 scripts/get_env.py "--event_file=${{ github.event_path }}" "--type=$TYPE" || cat "${{ github.event_path }}" + + - name: 'Build the firmware' + id: build-fw + run: | + set -e + TARGET="$(echo '${{ matrix.target }}' | sed 's/f//')"; \ + ./fbt TARGET_HW=$TARGET DEBUG=0 COMPACT=1 fap_dist updater_package + echo "sdk-file=$(ls dist/${{ matrix.target }}-*/flipper-z-${{ matrix.target }}-sdk-*.zip)" >> $GITHUB_OUTPUT + echo "hw-target-code=$TARGET" >> $GITHUB_OUTPUT + + - name: Deploy uFBT with SDK + uses: flipperdevices/flipperzero-ufbt-action@v0.1.0 + with: + task: setup + sdk-file: ${{ steps.build-fw.outputs.sdk-file }} + sdk-hw-target: ${{ steps.build-fw.outputs.hw-target-code }} + + - name: Build test app with SDK + run: | + mkdir testapp + cd testapp + ufbt create APPID=testapp + ufbt + + - name: Build example & external apps with uFBT + run: | + for appdir in 'applications/examples'; do + for app in $(find "$appdir" -maxdepth 1 -mindepth 1 -type d); do + pushd $app + TARGETS_FAM=$(grep "targets" application.fam || echo "${{ matrix.target }}") + if ! grep -q "${{ matrix.target }}" <<< $TARGETS_FAM ; then + echo Skipping unsupported app: $app + popd + continue + fi + echo Building $app + ufbt + popd + done + done diff --git a/.github/workflows/lint_and_submodule_check.yml b/.github/workflows/lint_and_submodule_check.yml index b85409ecd14..62e02b8a403 100644 --- a/.github/workflows/lint_and_submodule_check.yml +++ b/.github/workflows/lint_and_submodule_check.yml @@ -1,11 +1,6 @@ name: 'Lint sources & check submodule integrity' on: - push: - branches: - - dev - tags: - - '*' pull_request: env: @@ -15,7 +10,7 @@ env: jobs: lint_sources_check_submodules: - runs-on: [self-hosted,FlipperZeroShell] + runs-on: [self-hosted, FlipperZeroShell] steps: - name: 'Wipe workspace' run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; diff --git a/.github/workflows/merge_report.yml b/.github/workflows/merge_report.yml index 02016666659..fedc4b87fb3 100644 --- a/.github/workflows/merge_report.yml +++ b/.github/workflows/merge_report.yml @@ -10,7 +10,7 @@ env: jobs: merge_report: - runs-on: [self-hosted,FlipperZeroShell] + runs-on: [self-hosted, FlipperZeroShell] steps: - name: 'Wipe workspace' run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; diff --git a/.github/workflows/pvs_studio.yml b/.github/workflows/pvs_studio.yml index cb5b50278a6..24964a3094d 100644 --- a/.github/workflows/pvs_studio.yml +++ b/.github/workflows/pvs_studio.yml @@ -4,8 +4,6 @@ on: push: branches: - dev - tags: - - '*' pull_request: env: diff --git a/.github/workflows/reindex.yml b/.github/workflows/reindex.yml index 5645f609bad..82cb0468077 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,FlipperZeroShell] + runs-on: [self-hosted, FlipperZeroShell] steps: - name: Trigger reindex run: | From e5fdb2e069698c441af69e03d876034f9c05c559 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Fri, 1 Sep 2023 13:54:12 +0900 Subject: [PATCH 738/824] [FL-3314] Disconnect from BLE on protobuf error (#2955) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Disconnect from BLE on protobuf error * Set profile instead of disconnecting and add logging Co-authored-by: あく --- applications/services/rpc/rpc.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/applications/services/rpc/rpc.c b/applications/services/rpc/rpc.c index b3ed4417cc9..3e9665ad8da 100644 --- a/applications/services/rpc/rpc.c +++ b/applications/services/rpc/rpc.c @@ -15,6 +15,8 @@ #include #include +#include + #define TAG "RpcSrv" typedef enum { @@ -316,6 +318,15 @@ static int32_t rpc_session_worker(void* context) { session->closed_callback(session->context); } furi_mutex_release(session->callbacks_mutex); + + if(session->owner == RpcOwnerBle) { + // Disconnect BLE session + FURI_LOG_E("RPC", "BLE session closed due to a decode error"); + Bt* bt = furi_record_open(RECORD_BT); + bt_set_profile(bt, BtProfileSerial); + furi_record_close(RECORD_BT); + FURI_LOG_E("RPC", "Finished disconnecting the BLE session"); + } } } From 52b59662627254ae5ccde6201b5c4921573fc769 Mon Sep 17 00:00:00 2001 From: Max <66625166+m1-ins@users.noreply.github.com> Date: Fri, 1 Sep 2023 06:57:49 +0100 Subject: [PATCH 739/824] Add File Naming setting for more detailed naming (#3002) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * added filename mode setting * added furi_flag checks for when filename_mode is set * changed naming for ibutton, lfrfid and subghz * requested changes from PR * Lib: gather all naming bits and pieces under name generator module. Properly bump api version. FuriHal: fix RTC flag enum. * PR requested changes * bug fix for arg type * added functionality for other application scenes * Lib: cleanup name generator API, simplify usage. Sync API symbols. * Lib: proper size type in name_generator. Cleanup. * FuriHal: cleanup rtc api usage across firmware Co-authored-by: あく --- applications/main/ibutton/ibutton.c | 3 +- applications/main/ibutton/ibutton_i.h | 3 +- .../ibutton/scenes/ibutton_scene_save_name.c | 11 +-- .../scenes/infrared_scene_universal_ac.c | 1 - applications/main/lfrfid/lfrfid.c | 10 ++- applications/main/lfrfid/lfrfid_i.h | 5 +- .../lfrfid/scenes/lfrfid_scene_save_name.c | 9 ++- applications/main/nfc/nfc.c | 6 +- .../main/nfc/scenes/nfc_scene_save_name.c | 8 +- .../subghz/scenes/subghz_scene_read_raw.c | 6 +- .../subghz/scenes/subghz_scene_save_name.c | 13 +++- applications/main/subghz/subghz_i.c | 9 ++- applications/services/gui/canvas.c | 1 - applications/services/gui/view_port.c | 1 - .../settings/system/system_settings.c | 21 ++++++ firmware/targets/f18/api_symbols.csv | 8 +- firmware/targets/f7/api_symbols.csv | 8 +- .../targets/furi_hal_include/furi_hal_rtc.h | 1 + lib/nfc/nfc_device.c | 8 +- lib/nfc/nfc_device.h | 3 +- lib/subghz/protocols/raw.c | 3 +- lib/subghz/types.h | 3 +- lib/toolbox/SConscript | 2 +- lib/toolbox/name_generator.c | 75 +++++++++++++++++++ lib/toolbox/name_generator.h | 35 +++++++++ lib/toolbox/random_name.c | 35 --------- lib/toolbox/random_name.h | 17 ----- 27 files changed, 207 insertions(+), 98 deletions(-) create mode 100644 lib/toolbox/name_generator.c create mode 100644 lib/toolbox/name_generator.h delete mode 100644 lib/toolbox/random_name.c delete mode 100644 lib/toolbox/random_name.h diff --git a/applications/main/ibutton/ibutton.c b/applications/main/ibutton/ibutton.c index ad5b233b56e..67873690ce2 100644 --- a/applications/main/ibutton/ibutton.c +++ b/applications/main/ibutton/ibutton.c @@ -195,7 +195,8 @@ bool ibutton_load_key(iButton* ibutton) { bool ibutton_select_and_load_key(iButton* ibutton) { DialogsFileBrowserOptions browser_options; - dialog_file_browser_set_basic_options(&browser_options, IBUTTON_APP_EXTENSION, &I_ibutt_10px); + dialog_file_browser_set_basic_options( + &browser_options, IBUTTON_APP_FILENAME_EXTENSION, &I_ibutt_10px); browser_options.base_path = IBUTTON_APP_FOLDER; if(furi_string_empty(ibutton->file_path)) { diff --git a/applications/main/ibutton/ibutton_i.h b/applications/main/ibutton/ibutton_i.h index 509279210f6..077b148076a 100644 --- a/applications/main/ibutton/ibutton_i.h +++ b/applications/main/ibutton/ibutton_i.h @@ -29,7 +29,8 @@ #include "scenes/ibutton_scene.h" #define IBUTTON_APP_FOLDER ANY_PATH("ibutton") -#define IBUTTON_APP_EXTENSION ".ibtn" +#define IBUTTON_APP_FILENAME_PREFIX "iBtn" +#define IBUTTON_APP_FILENAME_EXTENSION ".ibtn" #define IBUTTON_KEY_NAME_SIZE 22 diff --git a/applications/main/ibutton/scenes/ibutton_scene_save_name.c b/applications/main/ibutton/scenes/ibutton_scene_save_name.c index 7bd49df8337..e6236dc3592 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_save_name.c +++ b/applications/main/ibutton/scenes/ibutton_scene_save_name.c @@ -1,6 +1,6 @@ #include "../ibutton_i.h" -#include +#include #include #include @@ -17,7 +17,8 @@ void ibutton_scene_save_name_on_enter(void* context) { const bool is_new_file = furi_string_empty(ibutton->file_path); if(is_new_file) { - set_random_name(ibutton->key_name, IBUTTON_KEY_NAME_SIZE); + name_generator_make_auto( + ibutton->key_name, IBUTTON_KEY_NAME_SIZE, IBUTTON_APP_FILENAME_PREFIX); } text_input_set_header_text(text_input, "Name the key"); @@ -29,8 +30,8 @@ void ibutton_scene_save_name_on_enter(void* context) { IBUTTON_KEY_NAME_SIZE, is_new_file); - ValidatorIsFile* validator_is_file = - validator_is_file_alloc_init(IBUTTON_APP_FOLDER, IBUTTON_APP_EXTENSION, ibutton->key_name); + ValidatorIsFile* validator_is_file = validator_is_file_alloc_init( + IBUTTON_APP_FOLDER, IBUTTON_APP_FILENAME_EXTENSION, ibutton->key_name); text_input_set_validator(text_input, validator_is_file_callback, validator_is_file); view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewTextInput); @@ -48,7 +49,7 @@ bool ibutton_scene_save_name_on_event(void* context, SceneManagerEvent event) { "%s/%s%s", IBUTTON_APP_FOLDER, ibutton->key_name, - IBUTTON_APP_EXTENSION); + IBUTTON_APP_FILENAME_EXTENSION); if(ibutton_save_key(ibutton)) { scene_manager_next_scene(ibutton->scene_manager, iButtonSceneSaveSuccess); diff --git a/applications/main/infrared/scenes/infrared_scene_universal_ac.c b/applications/main/infrared/scenes/infrared_scene_universal_ac.c index cbb09a525fc..5f762d122fd 100644 --- a/applications/main/infrared/scenes/infrared_scene_universal_ac.c +++ b/applications/main/infrared/scenes/infrared_scene_universal_ac.c @@ -1,7 +1,6 @@ #include "../infrared_i.h" #include "common/infrared_scene_universal_common.h" -#include void infrared_scene_universal_ac_on_enter(void* context) { infrared_scene_universal_common_on_enter(context); diff --git a/applications/main/lfrfid/lfrfid.c b/applications/main/lfrfid/lfrfid.c index aa7510a90e8..2bef08df21f 100644 --- a/applications/main/lfrfid/lfrfid.c +++ b/applications/main/lfrfid/lfrfid.c @@ -215,13 +215,16 @@ bool lfrfid_save_key(LfRfid* app) { lfrfid_make_app_folder(app); - if(furi_string_end_with(app->file_path, LFRFID_APP_EXTENSION)) { + if(furi_string_end_with(app->file_path, LFRFID_APP_FILENAME_EXTENSION)) { size_t filename_start = furi_string_search_rchar(app->file_path, '/'); furi_string_left(app->file_path, filename_start); } furi_string_cat_printf( - app->file_path, "/%s%s", furi_string_get_cstr(app->file_name), LFRFID_APP_EXTENSION); + app->file_path, + "/%s%s", + furi_string_get_cstr(app->file_name), + LFRFID_APP_FILENAME_EXTENSION); result = lfrfid_save_key_data(app, app->file_path); return result; @@ -231,7 +234,8 @@ bool lfrfid_load_key_from_file_select(LfRfid* app) { furi_assert(app); DialogsFileBrowserOptions browser_options; - dialog_file_browser_set_basic_options(&browser_options, LFRFID_APP_EXTENSION, &I_125_10px); + dialog_file_browser_set_basic_options( + &browser_options, LFRFID_APP_FILENAME_EXTENSION, &I_125_10px); browser_options.base_path = LFRFID_APP_FOLDER; // Input events and views are managed by file_browser diff --git a/applications/main/lfrfid/lfrfid_i.h b/applications/main/lfrfid/lfrfid_i.h index 72b0619304f..d94a5f865a8 100644 --- a/applications/main/lfrfid/lfrfid_i.h +++ b/applications/main/lfrfid/lfrfid_i.h @@ -40,8 +40,9 @@ #define LFRFID_APP_FOLDER ANY_PATH("lfrfid") #define LFRFID_SD_FOLDER EXT_PATH("lfrfid") -#define LFRFID_APP_EXTENSION ".rfid" -#define LFRFID_APP_SHADOW_EXTENSION ".shd" +#define LFRFID_APP_FILENAME_PREFIX "RFID" +#define LFRFID_APP_FILENAME_EXTENSION ".rfid" +#define LFRFID_APP_SHADOW_FILENAME_EXTENSION ".shd" #define LFRFID_APP_RAW_ASK_EXTENSION ".ask.raw" #define LFRFID_APP_RAW_PSK_EXTENSION ".psk.raw" diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_save_name.c b/applications/main/lfrfid/scenes/lfrfid_scene_save_name.c index 771f2f60387..3a38e213de1 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_save_name.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_save_name.c @@ -1,6 +1,6 @@ -#include #include "../lfrfid_i.h" #include +#include void lfrfid_scene_save_name_on_enter(void* context) { LfRfid* app = context; @@ -11,7 +11,10 @@ void lfrfid_scene_save_name_on_enter(void* context) { bool key_name_is_empty = furi_string_empty(app->file_name); if(key_name_is_empty) { furi_string_set(app->file_path, LFRFID_APP_FOLDER); - set_random_name(app->text_store, LFRFID_TEXT_STORE_SIZE); + + name_generator_make_auto( + app->text_store, LFRFID_TEXT_STORE_SIZE, LFRFID_APP_FILENAME_PREFIX); + furi_string_set(folder_path, LFRFID_APP_FOLDER); } else { lfrfid_text_store_set(app, "%s", furi_string_get_cstr(app->file_name)); @@ -31,7 +34,7 @@ void lfrfid_scene_save_name_on_enter(void* context) { ValidatorIsFile* validator_is_file = validator_is_file_alloc_init( furi_string_get_cstr(folder_path), - LFRFID_APP_EXTENSION, + LFRFID_APP_FILENAME_EXTENSION, furi_string_get_cstr(app->file_name)); text_input_set_validator(text_input, validator_is_file_callback, validator_is_file); diff --git a/applications/main/nfc/nfc.c b/applications/main/nfc/nfc.c index 56d98a8c61a..4ac793b5b12 100644 --- a/applications/main/nfc/nfc.c +++ b/applications/main/nfc/nfc.c @@ -223,7 +223,11 @@ void nfc_blink_stop(Nfc* nfc) { bool nfc_save_file(Nfc* nfc) { furi_string_printf( - nfc->dev->load_path, "%s/%s%s", NFC_APP_FOLDER, nfc->dev->dev_name, NFC_APP_EXTENSION); + nfc->dev->load_path, + "%s/%s%s", + NFC_APP_FOLDER, + nfc->dev->dev_name, + NFC_APP_FILENAME_EXTENSION); bool file_saved = nfc_device_save(nfc->dev, furi_string_get_cstr(nfc->dev->load_path)); return file_saved; } diff --git a/applications/main/nfc/scenes/nfc_scene_save_name.c b/applications/main/nfc/scenes/nfc_scene_save_name.c index a7b97aac06b..b18e176333e 100644 --- a/applications/main/nfc/scenes/nfc_scene_save_name.c +++ b/applications/main/nfc/scenes/nfc_scene_save_name.c @@ -1,5 +1,5 @@ #include "../nfc_i.h" -#include +#include #include #include #include @@ -17,7 +17,7 @@ void nfc_scene_save_name_on_enter(void* context) { TextInput* text_input = nfc->text_input; bool dev_name_empty = false; if(!strcmp(nfc->dev->dev_name, "")) { - set_random_name(nfc->text_store, sizeof(nfc->text_store)); + name_generator_make_auto(nfc->text_store, NFC_DEV_NAME_MAX_LEN, NFC_APP_FILENAME_PREFIX); dev_name_empty = true; } else { nfc_text_store_set(nfc, nfc->dev->dev_name); @@ -34,14 +34,14 @@ void nfc_scene_save_name_on_enter(void* context) { FuriString* folder_path; folder_path = furi_string_alloc(); - if(furi_string_end_with(nfc->dev->load_path, NFC_APP_EXTENSION)) { + if(furi_string_end_with(nfc->dev->load_path, NFC_APP_FILENAME_EXTENSION)) { path_extract_dirname(furi_string_get_cstr(nfc->dev->load_path), folder_path); } else { furi_string_set(folder_path, NFC_APP_FOLDER); } ValidatorIsFile* validator_is_file = validator_is_file_alloc_init( - furi_string_get_cstr(folder_path), NFC_APP_EXTENSION, nfc->dev->dev_name); + furi_string_get_cstr(folder_path), NFC_APP_FILENAME_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); diff --git a/applications/main/subghz/scenes/subghz_scene_read_raw.c b/applications/main/subghz/scenes/subghz_scene_read_raw.c index 58e4b042954..bbcc4325662 100644 --- a/applications/main/subghz/scenes/subghz_scene_read_raw.c +++ b/applications/main/subghz/scenes/subghz_scene_read_raw.c @@ -239,7 +239,11 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { FuriString* temp_str = furi_string_alloc(); furi_string_printf( - temp_str, "%s/%s%s", SUBGHZ_RAW_FOLDER, RAW_FILE_NAME, SUBGHZ_APP_EXTENSION); + temp_str, + "%s/%s%s", + SUBGHZ_RAW_FOLDER, + RAW_FILE_NAME, + SUBGHZ_APP_FILENAME_EXTENSION); subghz_protocol_raw_gen_fff_data( subghz_txrx_get_fff_data(subghz->txrx), furi_string_get_cstr(temp_str), diff --git a/applications/main/subghz/scenes/subghz_scene_save_name.c b/applications/main/subghz/scenes/subghz_scene_save_name.c index 86eddfe8e97..394dda89e51 100644 --- a/applications/main/subghz/scenes/subghz_scene_save_name.c +++ b/applications/main/subghz/scenes/subghz_scene_save_name.c @@ -1,10 +1,10 @@ #include "../subghz_i.h" #include "subghz/types.h" -#include #include "../helpers/subghz_custom_event.h" #include #include #include +#include #define MAX_TEXT_INPUT_LEN 22 @@ -40,7 +40,9 @@ void subghz_scene_save_name_on_enter(void* context) { 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); + + name_generator_make_auto(file_name_buf, SUBGHZ_MAX_LEN_NAME, SUBGHZ_APP_FILENAME_PREFIX); + furi_string_set(file_name, file_name_buf); furi_string_set(subghz->file_path, SUBGHZ_APP_FOLDER); //highlighting the entire filename by default @@ -71,7 +73,7 @@ void subghz_scene_save_name_on_enter(void* context) { dev_name_empty); ValidatorIsFile* validator_is_file = validator_is_file_alloc_init( - furi_string_get_cstr(subghz->file_path), SUBGHZ_APP_EXTENSION, ""); + furi_string_get_cstr(subghz->file_path), SUBGHZ_APP_FILENAME_EXTENSION, ""); text_input_set_validator(text_input, validator_is_file_callback, validator_is_file); furi_string_free(file_name); @@ -94,7 +96,10 @@ bool subghz_scene_save_name_on_event(void* context, SceneManagerEvent event) { if(event.event == SubGhzCustomEventSceneSaveName) { if(strcmp(subghz->file_name_tmp, "") != 0) { furi_string_cat_printf( - subghz->file_path, "/%s%s", subghz->file_name_tmp, SUBGHZ_APP_EXTENSION); + subghz->file_path, + "/%s%s", + subghz->file_name_tmp, + SUBGHZ_APP_FILENAME_EXTENSION); if(subghz_path_is_file(subghz->file_path_tmp)) { if(!subghz_rename_file(subghz)) { return false; diff --git a/applications/main/subghz/subghz_i.c b/applications/main/subghz/subghz_i.c index 9ff61837120..c03efe5e568 100644 --- a/applications/main/subghz/subghz_i.c +++ b/applications/main/subghz/subghz_i.c @@ -238,7 +238,7 @@ bool subghz_get_next_name_file(SubGhz* subghz, uint8_t max_len) { storage, furi_string_get_cstr(file_path), furi_string_get_cstr(file_name), - SUBGHZ_APP_EXTENSION, + SUBGHZ_APP_FILENAME_EXTENSION, file_name, max_len); @@ -247,7 +247,7 @@ bool subghz_get_next_name_file(SubGhz* subghz, uint8_t max_len) { "%s/%s%s", furi_string_get_cstr(file_path), furi_string_get_cstr(file_name), - SUBGHZ_APP_EXTENSION); + SUBGHZ_APP_FILENAME_EXTENSION); furi_string_set(subghz->file_path, temp_str); res = true; } @@ -320,7 +320,8 @@ bool subghz_load_protocol_from_file(SubGhz* subghz) { FuriString* file_path = furi_string_alloc(); DialogsFileBrowserOptions browser_options; - dialog_file_browser_set_basic_options(&browser_options, SUBGHZ_APP_EXTENSION, &I_sub1_10px); + dialog_file_browser_set_basic_options( + &browser_options, SUBGHZ_APP_FILENAME_EXTENSION, &I_sub1_10px); browser_options.base_path = SUBGHZ_APP_FOLDER; // Input events and views are managed by file_select @@ -394,7 +395,7 @@ void subghz_file_name_clear(SubGhz* subghz) { } bool subghz_path_is_file(FuriString* path) { - return furi_string_end_with(path, SUBGHZ_APP_EXTENSION); + return furi_string_end_with(path, SUBGHZ_APP_FILENAME_EXTENSION); } void subghz_lock(SubGhz* subghz) { diff --git a/applications/services/gui/canvas.c b/applications/services/gui/canvas.c index 6e35dcffb01..37edc5d33d8 100644 --- a/applications/services/gui/canvas.c +++ b/applications/services/gui/canvas.c @@ -4,7 +4,6 @@ #include #include -#include #include #include diff --git a/applications/services/gui/view_port.c b/applications/services/gui/view_port.c index 0f300c16887..6723a777bbb 100644 --- a/applications/services/gui/view_port.c +++ b/applications/services/gui/view_port.c @@ -2,7 +2,6 @@ #include #include -#include #include "gui.h" #include "gui_i.h" diff --git a/applications/settings/system/system_settings.c b/applications/settings/system/system_settings.c index dd3c0dc6bd2..d19b4747b9c 100644 --- a/applications/settings/system/system_settings.c +++ b/applications/settings/system/system_settings.c @@ -153,6 +153,21 @@ static void sleep_method_changed(VariableItem* item) { } } +const char* const filename_scheme[] = { + "Default", + "Detailed", +}; + +static void filename_scheme_changed(VariableItem* item) { + uint8_t index = variable_item_get_current_value_index(item); + variable_item_set_current_value_text(item, filename_scheme[index]); + if(index) { + furi_hal_rtc_set_flag(FuriHalRtcFlagDetailedFilename); + } else { + furi_hal_rtc_reset_flag(FuriHalRtcFlagDetailedFilename); + } +} + static uint32_t system_settings_exit(void* context) { UNUSED(context); return VIEW_NONE; @@ -236,6 +251,12 @@ SystemSettings* system_settings_alloc() { variable_item_set_current_value_index(item, value_index); variable_item_set_current_value_text(item, sleep_method[value_index]); + item = variable_item_list_add( + app->var_item_list, "File Naming", COUNT_OF(filename_scheme), filename_scheme_changed, app); + value_index = furi_hal_rtc_is_flag_set(FuriHalRtcFlagDetailedFilename) ? 1 : 0; + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, filename_scheme[value_index]); + view_set_previous_callback( variable_item_list_get_view(app->var_item_list), system_settings_exit); view_dispatcher_add_view( diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index a61679bca67..a26db88232a 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,35.1,, +Version,+,36.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -172,10 +172,10 @@ Header,+,lib/toolbox/hex.h,, Header,+,lib/toolbox/manchester_decoder.h,, Header,+,lib/toolbox/manchester_encoder.h,, Header,+,lib/toolbox/md5.h,, +Header,+,lib/toolbox/name_generator.h,, Header,+,lib/toolbox/path.h,, Header,+,lib/toolbox/pretty_format.h,, Header,+,lib/toolbox/protocols/protocol_dict.h,, -Header,+,lib/toolbox/random_name.h,, Header,+,lib/toolbox/saved_struct.h,, Header,+,lib/toolbox/sha256.h,, Header,+,lib/toolbox/stream/buffered_file_stream.h,, @@ -1705,6 +1705,9 @@ Function,-,music_worker_set_callback,void,"MusicWorker*, MusicWorkerCallback, vo Function,-,music_worker_set_volume,void,"MusicWorker*, float" Function,-,music_worker_start,void,MusicWorker* Function,-,music_worker_stop,void,MusicWorker* +Function,+,name_generator_make_auto,void,"char*, size_t, const char*" +Function,+,name_generator_make_detailed,void,"char*, size_t, const char*" +Function,+,name_generator_make_random,void,"char*, size_t" Function,-,nan,double,const char* Function,-,nanf,float,const char* Function,-,nanl,long double,const char* @@ -1925,7 +1928,6 @@ Function,-,serial_svc_set_rpc_status,void,SerialServiceRpcStatus Function,-,serial_svc_start,void, Function,-,serial_svc_stop,void, Function,-,serial_svc_update_tx,_Bool,"uint8_t*, uint16_t" -Function,+,set_random_name,void,"char*, uint8_t" Function,-,setbuf,void,"FILE*, char*" Function,-,setbuffer,void,"FILE*, char*, int" Function,-,setenv,int,"const char*, const char*, int" diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 8baec5a8dcb..6ad88d9b834 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,35.1,, +Version,+,36.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -213,10 +213,10 @@ Header,+,lib/toolbox/hex.h,, Header,+,lib/toolbox/manchester_decoder.h,, Header,+,lib/toolbox/manchester_encoder.h,, Header,+,lib/toolbox/md5.h,, +Header,+,lib/toolbox/name_generator.h,, Header,+,lib/toolbox/path.h,, Header,+,lib/toolbox/pretty_format.h,, Header,+,lib/toolbox/protocols/protocol_dict.h,, -Header,+,lib/toolbox/random_name.h,, Header,+,lib/toolbox/saved_struct.h,, Header,+,lib/toolbox/sha256.h,, Header,+,lib/toolbox/stream/buffered_file_stream.h,, @@ -2090,6 +2090,9 @@ Function,-,music_worker_set_callback,void,"MusicWorker*, MusicWorkerCallback, vo Function,-,music_worker_set_volume,void,"MusicWorker*, float" Function,-,music_worker_start,void,MusicWorker* Function,-,music_worker_stop,void,MusicWorker* +Function,+,name_generator_make_auto,void,"char*, size_t, const char*" +Function,+,name_generator_make_detailed,void,"char*, size_t, const char*" +Function,+,name_generator_make_random,void,"char*, size_t" Function,-,nan,double,const char* Function,-,nanf,float,const char* Function,-,nanl,long double,const char* @@ -2535,7 +2538,6 @@ Function,-,serial_svc_set_rpc_status,void,SerialServiceRpcStatus Function,-,serial_svc_start,void, Function,-,serial_svc_stop,void, Function,-,serial_svc_update_tx,_Bool,"uint8_t*, uint16_t" -Function,+,set_random_name,void,"char*, uint8_t" Function,-,setbuf,void,"FILE*, char*" Function,-,setbuffer,void,"FILE*, char*, int" Function,-,setenv,int,"const char*, const char*, int" diff --git a/firmware/targets/furi_hal_include/furi_hal_rtc.h b/firmware/targets/furi_hal_include/furi_hal_rtc.h index 186d22f0798..c457b690347 100644 --- a/firmware/targets/furi_hal_include/furi_hal_rtc.h +++ b/firmware/targets/furi_hal_include/furi_hal_rtc.h @@ -32,6 +32,7 @@ typedef enum { FuriHalRtcFlagHandOrient = (1 << 4), FuriHalRtcFlagLegacySleep = (1 << 5), FuriHalRtcFlagStealthMode = (1 << 6), + FuriHalRtcFlagDetailedFilename = (1 << 7), } FuriHalRtcFlag; typedef enum { diff --git a/lib/nfc/nfc_device.c b/lib/nfc/nfc_device.c index 2c92b5bf31e..c5c8b433845 100644 --- a/lib/nfc/nfc_device.c +++ b/lib/nfc/nfc_device.c @@ -1360,7 +1360,7 @@ void nfc_device_set_name(NfcDevice* dev, const char* name) { static void nfc_device_get_path_without_ext(FuriString* orig_path, FuriString* shadow_path) { // TODO: this won't work if there is ".nfc" anywhere in the path other than // at the end - size_t ext_start = furi_string_search(orig_path, NFC_APP_EXTENSION); + size_t ext_start = furi_string_search(orig_path, NFC_APP_FILENAME_EXTENSION); furi_string_set_n(shadow_path, orig_path, 0, ext_start); } @@ -1587,7 +1587,7 @@ bool nfc_file_select(NfcDevice* dev) { // Input events and views are managed by file_browser const DialogsFileBrowserOptions browser_options = { - .extension = NFC_APP_EXTENSION, + .extension = NFC_APP_FILENAME_EXTENSION, .skip_assets = true, .hide_dot_files = true, .icon = &I_Nfc_10px, @@ -1659,7 +1659,7 @@ bool nfc_device_delete(NfcDevice* dev, bool use_load_path) { "%s/%s%s", furi_string_get_cstr(dev->folder), dev->dev_name, - NFC_APP_EXTENSION); + NFC_APP_FILENAME_EXTENSION); } if(!storage_simply_remove(dev->storage, furi_string_get_cstr(file_path))) break; // Delete shadow file if it exists @@ -1717,7 +1717,7 @@ bool nfc_device_restore(NfcDevice* dev, bool use_load_path) { "%s/%s%s", furi_string_get_cstr(dev->folder), dev->dev_name, - NFC_APP_EXTENSION); + NFC_APP_FILENAME_EXTENSION); } if(!nfc_device_load_data(dev, path, true)) break; restored = true; diff --git a/lib/nfc/nfc_device.h b/lib/nfc/nfc_device.h index 20df4f89181..76de0b61c64 100644 --- a/lib/nfc/nfc_device.h +++ b/lib/nfc/nfc_device.h @@ -21,7 +21,8 @@ extern "C" { #define NFC_READER_DATA_MAX_SIZE 64 #define NFC_DICT_KEY_BATCH_SIZE 10 -#define NFC_APP_EXTENSION ".nfc" +#define NFC_APP_FILENAME_PREFIX "NFC" +#define NFC_APP_FILENAME_EXTENSION ".nfc" #define NFC_APP_SHADOW_EXTENSION ".shd" typedef void (*NfcLoadingCallback)(void* context, bool state); diff --git a/lib/subghz/protocols/raw.c b/lib/subghz/protocols/raw.c index c9d2b3017bf..4a8ae1d5edf 100644 --- a/lib/subghz/protocols/raw.c +++ b/lib/subghz/protocols/raw.c @@ -108,7 +108,8 @@ bool subghz_protocol_raw_save_to_file_init( furi_string_set(instance->file_name, dev_name); // First remove subghz device file if it was saved - furi_string_printf(temp_str, "%s/%s%s", SUBGHZ_RAW_FOLDER, dev_name, SUBGHZ_APP_EXTENSION); + furi_string_printf( + temp_str, "%s/%s%s", SUBGHZ_RAW_FOLDER, dev_name, SUBGHZ_APP_FILENAME_EXTENSION); if(!storage_simply_remove(instance->storage, furi_string_get_cstr(temp_str))) { break; diff --git a/lib/subghz/types.h b/lib/subghz/types.h index cccf70bad94..93f94cc7483 100644 --- a/lib/subghz/types.h +++ b/lib/subghz/types.h @@ -13,7 +13,8 @@ #define SUBGHZ_APP_FOLDER ANY_PATH("subghz") #define SUBGHZ_RAW_FOLDER EXT_PATH("subghz") -#define SUBGHZ_APP_EXTENSION ".sub" +#define SUBGHZ_APP_FILENAME_PREFIX "SubGHz" +#define SUBGHZ_APP_FILENAME_EXTENSION ".sub" #define SUBGHZ_KEY_FILE_VERSION 1 #define SUBGHZ_KEY_FILE_TYPE "Flipper SubGhz Key File" diff --git a/lib/toolbox/SConscript b/lib/toolbox/SConscript index 761755d2de6..04fd8567fcb 100644 --- a/lib/toolbox/SConscript +++ b/lib/toolbox/SConscript @@ -13,7 +13,7 @@ env.Append( File("manchester_decoder.h"), File("manchester_encoder.h"), File("path.h"), - File("random_name.h"), + File("name_generator.h"), File("sha256.h"), File("crc32_calc.h"), File("dir_walk.h"), diff --git a/lib/toolbox/name_generator.c b/lib/toolbox/name_generator.c new file mode 100644 index 00000000000..36366485acc --- /dev/null +++ b/lib/toolbox/name_generator.c @@ -0,0 +1,75 @@ +#include "name_generator.h" + +#include +#include +#include +#include +#include +#include + +const char* const name_generator_left[] = { + "ancient", "hollow", "strange", "disappeared", "unknown", "unthinkable", "unnameable", + "nameless", "my", "concealed", "forgotten", "hidden", "mysterious", "obscure", + "random", "remote", "uncharted", "undefined", "untraveled", "untold", +}; + +const char* const name_generator_right[] = { + "door", + "entrance", + "doorway", + "entry", + "portal", + "entree", + "opening", + "crack", + "access", + "corridor", + "passage", + "port", +}; + +void name_generator_make_auto(char* name, size_t max_name_size, const char* prefix) { + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDetailedFilename)) { + name_generator_make_detailed(name, max_name_size, prefix); + } else { + name_generator_make_random(name, max_name_size); + } +} + +void name_generator_make_random(char* name, size_t max_name_size) { + furi_assert(name); + furi_assert(max_name_size); + + uint8_t name_generator_left_i = rand() % COUNT_OF(name_generator_left); + uint8_t name_generator_right_i = rand() % COUNT_OF(name_generator_right); + + snprintf( + name, + max_name_size, + "%s_%s", + name_generator_left[name_generator_left_i], + name_generator_right[name_generator_right_i]); + + // Set first symbol to upper case + name[0] = name[0] - 0x20; +} + +void name_generator_make_detailed(char* name, size_t max_name_size, const char* prefix) { + furi_assert(name); + furi_assert(max_name_size); + furi_assert(prefix); + + FuriHalRtcDateTime dateTime; + furi_hal_rtc_get_datetime(&dateTime); + + snprintf( + name, + max_name_size, + "%s-%.4d_%.2d_%.2d-%.2d_%.2d", + prefix, + dateTime.year, + dateTime.month, + dateTime.day, + dateTime.hour, + dateTime.minute); +} diff --git a/lib/toolbox/name_generator.h b/lib/toolbox/name_generator.h new file mode 100644 index 00000000000..bc17d54cd57 --- /dev/null +++ b/lib/toolbox/name_generator.h @@ -0,0 +1,35 @@ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** Generates detailed/random name based on furi_hal flags + * + * @param name buffer to write random name + * @param max_name_size length of given buffer + * @param[in] prefix The prefix of the name + */ +void name_generator_make_auto(char* name, size_t max_name_size, const char* prefix); + +/** Generates random name + * + * @param name buffer to write random name + * @param max_name_size length of given buffer + */ +void name_generator_make_random(char* name, size_t max_name_size); + +/** Generates detailed name + * + * @param name buffer to write random name + * @param max_name_size length of given buffer + * @param[in] prefix The prefix of the name + */ +void name_generator_make_detailed(char* name, size_t max_name_size, const char* prefix); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/toolbox/random_name.c b/lib/toolbox/random_name.c deleted file mode 100644 index 64e712c7ca0..00000000000 --- a/lib/toolbox/random_name.c +++ /dev/null @@ -1,35 +0,0 @@ -#include "random_name.h" -#include -#include -#include -#include - -void set_random_name(char* name, uint8_t max_name_size) { - const char* prefix[] = { - "ancient", "hollow", "strange", "disappeared", "unknown", - "unthinkable", "unnamable", "nameless", "my", "concealed", - "forgotten", "hidden", "mysterious", "obscure", "random", - "remote", "uncharted", "undefined", "untravelled", "untold", - }; - - const char* suffix[] = { - "door", - "entrance", - "doorway", - "entry", - "portal", - "entree", - "opening", - "crack", - "access", - "corridor", - "passage", - "port", - }; - uint8_t prefix_i = rand() % COUNT_OF(prefix); - uint8_t suffix_i = rand() % COUNT_OF(suffix); - - snprintf(name, max_name_size, "%s_%s", prefix[prefix_i], suffix[suffix_i]); - // Set first symbol to upper case - name[0] = name[0] - 0x20; -} diff --git a/lib/toolbox/random_name.h b/lib/toolbox/random_name.h deleted file mode 100644 index 358ea685d8c..00000000000 --- a/lib/toolbox/random_name.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/** Generates random name - * @param name buffer to write random name - * @param max_name_size length of given buffer - */ -void set_random_name(char* name, uint8_t max_name_size); - -#ifdef __cplusplus -} -#endif From ce89240f6f41ce725421794ad71b99471be0a4d1 Mon Sep 17 00:00:00 2001 From: Linn Dahlgren Date: Mon, 4 Sep 2023 05:37:12 +0200 Subject: [PATCH 740/824] [DOC] Remove defunct link in ReadMe.md (#3036) --- ReadMe.md | 1 - 1 file changed, 1 deletion(-) diff --git a/ReadMe.md b/ReadMe.md index dcf39c3ae7f..387ac2de78b 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -95,7 +95,6 @@ Make sure your Flipper is on, and your firmware is functioning. Connect your Fli - [Hardware combos and Un-bricking](/documentation/KeyCombo.md) - recovering your Flipper from the most nasty situations - [Flipper File Formats](/documentation/file_formats) - everything about how Flipper stores your data and how you can work with it - [Universal Remotes](/documentation/UniversalRemotes.md) - contributing your infrared remote to the universal remote database -- [Firmware Roadmap](/documentation/RoadMap.md) - And much more in the [documentation](/documentation) folder # Project structure From 0b806c23600ebe3c179f92e0883448e4e46950bd Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Mon, 4 Sep 2023 08:10:07 +0300 Subject: [PATCH 741/824] Storage: force mount (#3033) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Storage: count opened files * Storage: sd mount * Storage: prompt to mount SD card if not mounted * F18: update API * F18: update API version * Fix logger naming scheme * Storage: storage_files_count -> storage_open_files_count Co-authored-by: あく --- .../debug/unit_tests/subghz/subghz_test.c | 2 +- .../drivers/subghz/cc1101_ext/cc1101_ext.c | 2 +- .../cc1101_ext/cc1101_ext_interconnect.c | 2 +- .../example_apps_assets/example_apps_assets.c | 2 +- .../example_apps_data/example_apps_data.c | 2 +- .../example_plugins/example_plugins.c | 2 +- .../example_plugins/example_plugins_multi.c | 2 +- .../example_advanced_plugins.c | 2 +- .../main/bad_usb/helpers/ducky_script.c | 2 +- .../bad_usb/helpers/ducky_script_commands.c | 2 +- applications/main/ibutton/ibutton.c | 2 +- .../subghz/scenes/subghz_scene_read_raw.c | 2 +- applications/main/subghz/subghz_cli.c | 2 +- .../main/subghz/views/subghz_read_raw.c | 2 +- applications/main/u2f/u2f.c | 2 +- applications/main/u2f/u2f_data.c | 2 +- applications/main/u2f/u2f_hid.c | 2 +- applications/services/storage/storage.c | 2 +- applications/services/storage/storage.h | 10 +++- .../services/storage/storage_external_api.c | 10 +++- applications/services/storage/storage_glue.c | 7 +++ applications/services/storage/storage_glue.h | 2 + .../services/storage/storage_message.h | 1 + .../services/storage/storage_processing.c | 41 +++++++++++++--- .../services/storage/storages/storage_ext.c | 47 +++++++++++-------- .../services/storage/storages/storage_ext.h | 1 + .../scenes/storage_settings_scene_start.c | 24 +++++++--- .../storage_settings_scene_unmount_confirm.c | 14 ++++-- .../scenes/storage_settings_scene_unmounted.c | 41 +++++++++++----- .../updater/util/update_task_worker_flasher.c | 2 +- firmware/targets/f18/api_symbols.csv | 3 +- firmware/targets/f7/api_symbols.csv | 3 +- firmware/targets/f7/furi_hal/furi_hal_i2c.c | 2 +- .../api_hashtable/api_hashtable.cpp | 2 +- lib/flipper_application/application_assets.c | 2 +- lib/flipper_application/elf/elf_file.c | 2 +- .../plugins/plugin_manager.c | 2 +- lib/lfrfid/lfrfid_raw_file.c | 2 +- lib/lfrfid/lfrfid_raw_worker.c | 2 +- lib/lfrfid/lfrfid_worker_modes.c | 2 +- lib/nfc/protocols/slix.c | 2 +- .../cc1101_int/cc1101_int_interconnect.c | 2 +- lib/subghz/protocols/alutech_at_4n.c | 2 +- lib/subghz/protocols/bett.c | 2 +- lib/subghz/protocols/bin_raw.c | 2 +- lib/subghz/protocols/came.c | 2 +- lib/subghz/protocols/came_twee.c | 2 +- lib/subghz/protocols/chamberlain_code.c | 2 +- lib/subghz/protocols/faac_slh.c | 2 +- lib/subghz/protocols/holtek_ht12x.c | 2 +- lib/subghz/protocols/honeywell_wdb.c | 2 +- lib/subghz/protocols/hormann.c | 2 +- lib/subghz/protocols/ido.c | 2 +- lib/subghz/protocols/kia.c | 2 +- lib/subghz/protocols/kinggates_stylo_4k.c | 2 +- lib/subghz/protocols/nice_flo.c | 2 +- lib/subghz/protocols/phoenix_v2.c | 2 +- lib/subghz/protocols/raw.c | 2 +- lib/subghz/protocols/secplus_v1.c | 2 +- lib/subghz/protocols/secplus_v2.c | 2 +- lib/subghz/protocols/smc5326.c | 2 +- 61 files changed, 201 insertions(+), 99 deletions(-) diff --git a/applications/debug/unit_tests/subghz/subghz_test.c b/applications/debug/unit_tests/subghz/subghz_test.c index e32a5748296..1900f204554 100644 --- a/applications/debug/unit_tests/subghz/subghz_test.c +++ b/applications/debug/unit_tests/subghz/subghz_test.c @@ -10,7 +10,7 @@ #include #include -#define TAG "SubGhz TEST" +#define TAG "SubGhzTest" #define KEYSTORE_DIR_NAME EXT_PATH("subghz/assets/keeloq_mfcodes") #define CAME_ATOMO_DIR_NAME EXT_PATH("subghz/assets/came_atomo") #define NICE_FLOR_S_DIR_NAME EXT_PATH("subghz/assets/nice_flor_s") diff --git a/applications/drivers/subghz/cc1101_ext/cc1101_ext.c b/applications/drivers/subghz/cc1101_ext/cc1101_ext.c index 0a9599d03f0..4fb249c6ddc 100644 --- a/applications/drivers/subghz/cc1101_ext/cc1101_ext.c +++ b/applications/drivers/subghz/cc1101_ext/cc1101_ext.c @@ -16,7 +16,7 @@ #include #include -#define TAG "SubGhz_Device_CC1101_Ext" +#define TAG "SubGhzDeviceCc1101Ext" #define SUBGHZ_DEVICE_CC1101_EXT_TX_GPIO &gpio_ext_pb2 diff --git a/applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.c b/applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.c index 51f5a0d1ddb..1f119315436 100644 --- a/applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.c +++ b/applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.c @@ -2,7 +2,7 @@ #include "cc1101_ext.h" #include -#define TAG "SubGhzDeviceCC1101Ext" +#define TAG "SubGhzDeviceCc1101Ext" static bool subghz_device_cc1101_ext_interconnect_is_frequency_valid(uint32_t frequency) { bool ret = subghz_device_cc1101_ext_is_frequency_valid(frequency); diff --git a/applications/examples/example_apps_assets/example_apps_assets.c b/applications/examples/example_apps_assets/example_apps_assets.c index f2d0272f0b7..2c2cc8a8747 100644 --- a/applications/examples/example_apps_assets/example_apps_assets.c +++ b/applications/examples/example_apps_assets/example_apps_assets.c @@ -4,7 +4,7 @@ #include // Define log tag -#define TAG "example_apps_assets" +#define TAG "ExampleAppsAssets" static void example_apps_data_print_file_content(Storage* storage, const char* path) { Stream* stream = file_stream_alloc(storage); diff --git a/applications/examples/example_apps_data/example_apps_data.c b/applications/examples/example_apps_data/example_apps_data.c index d6104c1374c..7a297b01cfd 100644 --- a/applications/examples/example_apps_data/example_apps_data.c +++ b/applications/examples/example_apps_data/example_apps_data.c @@ -2,7 +2,7 @@ #include // Define log tag -#define TAG "example_apps_data" +#define TAG "ExampleAppsData" // Application entry point int32_t example_apps_data_main(void* p) { diff --git a/applications/examples/example_plugins/example_plugins.c b/applications/examples/example_plugins/example_plugins.c index acc5903ad81..7e71e0d2eb5 100644 --- a/applications/examples/example_plugins/example_plugins.c +++ b/applications/examples/example_plugins/example_plugins.c @@ -11,7 +11,7 @@ #include #include -#define TAG "example_plugins" +#define TAG "ExamplePlugins" int32_t example_plugins_app(void* p) { UNUSED(p); diff --git a/applications/examples/example_plugins/example_plugins_multi.c b/applications/examples/example_plugins/example_plugins_multi.c index 12eba01c10f..3525b39ea4a 100644 --- a/applications/examples/example_plugins/example_plugins_multi.c +++ b/applications/examples/example_plugins/example_plugins_multi.c @@ -11,7 +11,7 @@ #include -#define TAG "example_plugins" +#define TAG "ExamplePlugins" int32_t example_plugins_multi_app(void* p) { UNUSED(p); diff --git a/applications/examples/example_plugins_advanced/example_advanced_plugins.c b/applications/examples/example_plugins_advanced/example_advanced_plugins.c index f27b0a08416..2b137e1d484 100644 --- a/applications/examples/example_plugins_advanced/example_advanced_plugins.c +++ b/applications/examples/example_plugins_advanced/example_advanced_plugins.c @@ -8,7 +8,7 @@ #include -#define TAG "example_advanced_plugins" +#define TAG "ExampleAdvancedPlugins" int32_t example_advanced_plugins_app(void* p) { UNUSED(p); diff --git a/applications/main/bad_usb/helpers/ducky_script.c b/applications/main/bad_usb/helpers/ducky_script.c index f194178a067..11c74c010f2 100644 --- a/applications/main/bad_usb/helpers/ducky_script.c +++ b/applications/main/bad_usb/helpers/ducky_script.c @@ -9,7 +9,7 @@ #include "ducky_script_i.h" #include -#define TAG "BadUSB" +#define TAG "BadUsb" #define WORKER_TAG TAG "Worker" #define BADUSB_ASCII_TO_KEY(script, x) \ diff --git a/applications/main/bad_usb/helpers/ducky_script_commands.c b/applications/main/bad_usb/helpers/ducky_script_commands.c index d073b4c8d62..cc713135eef 100644 --- a/applications/main/bad_usb/helpers/ducky_script_commands.c +++ b/applications/main/bad_usb/helpers/ducky_script_commands.c @@ -171,7 +171,7 @@ static const DuckyCmd ducky_commands[] = { {"WAIT_FOR_BUTTON_PRESS", ducky_fnc_waitforbutton, -1}, }; -#define TAG "BadUSB" +#define TAG "BadUsb" #define WORKER_TAG TAG "Worker" int32_t ducky_execute_cmd(BadUsbScript* bad_usb, const char* line) { diff --git a/applications/main/ibutton/ibutton.c b/applications/main/ibutton/ibutton.c index 67873690ce2..0e4c621b20f 100644 --- a/applications/main/ibutton/ibutton.c +++ b/applications/main/ibutton/ibutton.c @@ -3,7 +3,7 @@ #include #include -#define TAG "iButtonApp" +#define TAG "IButtonApp" static const NotificationSequence sequence_blink_set_yellow = { &message_blink_set_color_yellow, diff --git a/applications/main/subghz/scenes/subghz_scene_read_raw.c b/applications/main/subghz/scenes/subghz_scene_read_raw.c index bbcc4325662..f2ab6577026 100644 --- a/applications/main/subghz/scenes/subghz_scene_read_raw.c +++ b/applications/main/subghz/scenes/subghz_scene_read_raw.c @@ -5,7 +5,7 @@ #include #define RAW_FILE_NAME "Raw_signal_" -#define TAG "SubGhzSceneReadRAW" +#define TAG "SubGhzSceneReadRaw" bool subghz_scene_read_raw_update_filename(SubGhz* subghz) { bool ret = false; diff --git a/applications/main/subghz/subghz_cli.c b/applications/main/subghz/subghz_cli.c index fe97c8a0671..f7d6b3a1c54 100644 --- a/applications/main/subghz/subghz_cli.c +++ b/applications/main/subghz/subghz_cli.c @@ -28,7 +28,7 @@ #define SUBGHZ_REGION_FILENAME "/int/.region_data" -#define TAG "SubGhz CLI" +#define TAG "SubGhzCli" static void subghz_cli_radio_device_power_on() { uint8_t attempts = 5; diff --git a/applications/main/subghz/views/subghz_read_raw.c b/applications/main/subghz/views/subghz_read_raw.c index 88ac129ca9e..b074cdc9ff3 100644 --- a/applications/main/subghz/views/subghz_read_raw.c +++ b/applications/main/subghz/views/subghz_read_raw.c @@ -9,7 +9,7 @@ #include #define SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE 100 -#define TAG "SubGhzReadRAW" +#define TAG "SubGhzReadRaw" struct SubGhzReadRAW { View* view; diff --git a/applications/main/u2f/u2f.c b/applications/main/u2f/u2f.c index 0ed5ebb2966..ce70212a9fa 100644 --- a/applications/main/u2f/u2f.c +++ b/applications/main/u2f/u2f.c @@ -10,7 +10,7 @@ #include "hmac_sha256.h" #include "micro-ecc/uECC.h" -#define TAG "U2F" +#define TAG "U2f" #define WORKER_TAG TAG "Worker" #define U2F_CMD_REGISTER 0x01 diff --git a/applications/main/u2f/u2f_data.c b/applications/main/u2f/u2f_data.c index 8489ed91e3b..34360f3d639 100644 --- a/applications/main/u2f/u2f_data.c +++ b/applications/main/u2f/u2f_data.c @@ -5,7 +5,7 @@ #include #include -#define TAG "U2F" +#define TAG "U2f" #define U2F_DATA_FOLDER EXT_PATH("u2f/") #define U2F_CERT_FILE U2F_DATA_FOLDER "assets/cert.der" diff --git a/applications/main/u2f/u2f_hid.c b/applications/main/u2f/u2f_hid.c index 9b625c1f393..d7d7e6cf413 100644 --- a/applications/main/u2f/u2f_hid.c +++ b/applications/main/u2f/u2f_hid.c @@ -10,7 +10,7 @@ #include -#define TAG "U2FHID" +#define TAG "U2fHid" #define WORKER_TAG TAG "Worker" #define U2F_HID_MAX_PAYLOAD_LEN ((HID_U2F_PACKET_LEN - 7) + 128 * (HID_U2F_PACKET_LEN - 5)) diff --git a/applications/services/storage/storage.c b/applications/services/storage/storage.c index 1816bf921cc..a6229af8191 100644 --- a/applications/services/storage/storage.c +++ b/applications/services/storage/storage.c @@ -12,7 +12,7 @@ #define ICON_SD_MOUNTED &I_SDcardMounted_11x8 #define ICON_SD_ERROR &I_SDcardFail_11x8 -#define TAG RECORD_STORAGE +#define TAG "Storage" static void storage_app_sd_icon_draw_callback(Canvas* canvas, void* context) { furi_assert(canvas); diff --git a/applications/services/storage/storage.h b/applications/services/storage/storage.h index dccf29592c6..1abc8ed0ebd 100644 --- a/applications/services/storage/storage.h +++ b/applications/services/storage/storage.h @@ -334,12 +334,20 @@ const char* storage_file_get_error_desc(File* file); */ FS_Error storage_sd_format(Storage* api); -/** Will unmount the SD card +/** Will unmount the SD card. + * Will return FSE_NOT_READY if the SD card is not mounted. + * Will return FSE_DENIED if there are open files on the SD card. * @param api pointer to the api * @return FS_Error operation result */ FS_Error storage_sd_unmount(Storage* api); +/** Will mount the SD card + * @param api pointer to the api + * @return FS_Error operation result + */ +FS_Error storage_sd_mount(Storage* api); + /** Retrieves SD card information * @param api pointer to the api * @param info pointer to the info diff --git a/applications/services/storage/storage_external_api.c b/applications/services/storage/storage_external_api.c index 585ded41444..2ba58f9c665 100644 --- a/applications/services/storage/storage_external_api.c +++ b/applications/services/storage/storage_external_api.c @@ -11,7 +11,7 @@ #define MAX_EXT_LEN 16 #define FILE_BUFFER_SIZE 512 -#define TAG "StorageAPI" +#define TAG "StorageApi" #define S_API_PROLOGUE FuriApiLock lock = api_lock_alloc_locked(); @@ -781,6 +781,14 @@ FS_Error storage_sd_unmount(Storage* storage) { return S_RETURN_ERROR; } +FS_Error storage_sd_mount(Storage* storage) { + S_API_PROLOGUE; + SAData data = {}; + S_API_MESSAGE(StorageCommandSDMount); + S_API_EPILOGUE; + return S_RETURN_ERROR; +} + FS_Error storage_sd_info(Storage* storage, SDInfo* info) { S_API_PROLOGUE; SAData data = { diff --git a/applications/services/storage/storage_glue.c b/applications/services/storage/storage_glue.c index 63e44c9d7d7..41da6c3f487 100644 --- a/applications/services/storage/storage_glue.c +++ b/applications/services/storage/storage_glue.c @@ -1,6 +1,8 @@ #include "storage_glue.h" #include +#define TAG "StorageGlue" + /****************** storage file ******************/ void storage_file_init(StorageFile* obj) { @@ -149,3 +151,8 @@ bool storage_pop_storage_file(File* file, StorageData* storage) { return result; } + +size_t storage_open_files_count(StorageData* storage) { + size_t count = StorageFileList_size(storage->files); + return count; +} diff --git a/applications/services/storage/storage_glue.h b/applications/services/storage/storage_glue.h index f10640345d7..4323296cfb3 100644 --- a/applications/services/storage/storage_glue.h +++ b/applications/services/storage/storage_glue.h @@ -68,6 +68,8 @@ void* storage_get_storage_file_data(const File* file, StorageData* storage); void storage_push_storage_file(File* file, FuriString* path, StorageData* storage); bool storage_pop_storage_file(File* file, StorageData* storage); +size_t storage_open_files_count(StorageData* storage); + #ifdef __cplusplus } #endif diff --git a/applications/services/storage/storage_message.h b/applications/services/storage/storage_message.h index 9e13bf83d58..01bc2038010 100644 --- a/applications/services/storage/storage_message.h +++ b/applications/services/storage/storage_message.h @@ -141,6 +141,7 @@ typedef enum { StorageCommandSDInfo, StorageCommandSDStatus, StorageCommandCommonResolvePath, + StorageCommandSDMount, } StorageCommand; typedef struct { diff --git a/applications/services/storage/storage_processing.c b/applications/services/storage/storage_processing.c index 70cb7b92f91..00126af6f37 100644 --- a/applications/services/storage/storage_processing.c +++ b/applications/services/storage/storage_processing.c @@ -418,12 +418,38 @@ static FS_Error storage_process_sd_format(Storage* app) { static FS_Error storage_process_sd_unmount(Storage* app) { FS_Error ret = FSE_OK; - if(storage_data_status(&app->storage[ST_EXT]) == StorageStatusNotReady) { - ret = FSE_NOT_READY; - } else { - sd_unmount_card(&app->storage[ST_EXT]); - storage_data_timestamp(&app->storage[ST_EXT]); - } + do { + StorageData* storage = &app->storage[ST_EXT]; + if(storage_data_status(storage) == StorageStatusNotReady) { + ret = FSE_NOT_READY; + break; + } + + if(storage_open_files_count(storage)) { + ret = FSE_DENIED; + break; + } + + sd_unmount_card(storage); + storage_data_timestamp(storage); + } while(false); + + return ret; +} + +static FS_Error storage_process_sd_mount(Storage* app) { + FS_Error ret = FSE_OK; + + do { + StorageData* storage = &app->storage[ST_EXT]; + if(storage_data_status(storage) != StorageStatusNotReady) { + ret = FSE_NOT_READY; + break; + } + + ret = sd_mount_card(storage, true); + storage_data_timestamp(storage); + } while(false); return ret; } @@ -630,6 +656,9 @@ void storage_process_message_internal(Storage* app, StorageMessage* message) { case StorageCommandSDUnmount: message->return_data->error_value = storage_process_sd_unmount(app); break; + case StorageCommandSDMount: + message->return_data->error_value = storage_process_sd_mount(app); + break; case StorageCommandSDInfo: message->return_data->error_value = storage_process_sd_info(app, message->data->sdinfo.info); diff --git a/applications/services/storage/storages/storage_ext.c b/applications/services/storage/storages/storage_ext.c index 35b3ee253a3..080ac4faf84 100644 --- a/applications/services/storage/storages/storage_ext.c +++ b/applications/services/storage/storages/storage_ext.c @@ -24,7 +24,7 @@ static FS_Error storage_ext_parse_error(SDError error); /******************* Core Functions *******************/ -static bool sd_mount_card(StorageData* storage, bool notify) { +static bool sd_mount_card_internal(StorageData* storage, bool notify) { bool result = false; uint8_t counter = sd_max_mount_retry_count(); uint8_t bsp_result; @@ -106,6 +106,32 @@ FS_Error sd_unmount_card(StorageData* storage) { return storage_ext_parse_error(error); } +FS_Error sd_mount_card(StorageData* storage, bool notify) { + sd_mount_card_internal(storage, notify); + FS_Error error; + + if(storage->status != StorageStatusOK) { + FURI_LOG_E(TAG, "sd init error: %s", storage_data_status_text(storage)); + if(notify) { + NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); + sd_notify_error(notification); + furi_record_close(RECORD_NOTIFICATION); + } + error = FSE_INTERNAL; + } else { + FURI_LOG_I(TAG, "card mounted"); + if(notify) { + NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); + sd_notify_success(notification); + furi_record_close(RECORD_NOTIFICATION); + } + + error = FSE_OK; + } + + return error; +} + FS_Error sd_format_card(StorageData* storage) { #ifdef FURI_RAM_EXEC UNUSED(storage); @@ -222,25 +248,8 @@ static void storage_ext_tick_internal(StorageData* storage, bool notify) { if(sd_data->sd_was_present) { if(hal_sd_detect()) { FURI_LOG_I(TAG, "card detected"); - sd_mount_card(storage, notify); - - if(storage->status != StorageStatusOK) { - FURI_LOG_E(TAG, "sd init error: %s", storage_data_status_text(storage)); - if(notify) { - NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); - sd_notify_error(notification); - furi_record_close(RECORD_NOTIFICATION); - } - } else { - FURI_LOG_I(TAG, "card mounted"); - if(notify) { - NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); - sd_notify_success(notification); - furi_record_close(RECORD_NOTIFICATION); - } - } - sd_data->sd_was_present = false; + sd_mount_card(storage, notify); if(!hal_sd_detect()) { FURI_LOG_I(TAG, "card removed while mounting"); diff --git a/applications/services/storage/storages/storage_ext.h b/applications/services/storage/storages/storage_ext.h index 07ddbcf2f4e..18d1f714373 100644 --- a/applications/services/storage/storages/storage_ext.h +++ b/applications/services/storage/storages/storage_ext.h @@ -8,6 +8,7 @@ extern "C" { #endif void storage_ext_init(StorageData* storage); +FS_Error sd_mount_card(StorageData* storage, bool notify); FS_Error sd_unmount_card(StorageData* storage); FS_Error sd_format_card(StorageData* storage); FS_Error sd_card_info(StorageData* storage, SDInfo* sd_info); diff --git a/applications/settings/storage_settings/scenes/storage_settings_scene_start.c b/applications/settings/storage_settings/scenes/storage_settings_scene_start.c index 9f41061b893..0e667024f17 100644 --- a/applications/settings/storage_settings/scenes/storage_settings_scene_start.c +++ b/applications/settings/storage_settings/scenes/storage_settings_scene_start.c @@ -31,12 +31,24 @@ void storage_settings_scene_start_on_enter(void* context) { StorageSettingsStartSubmenuIndexSDInfo, storage_settings_scene_start_submenu_callback, app); - submenu_add_item( - submenu, - "Unmount SD Card", - StorageSettingsStartSubmenuIndexUnmount, - storage_settings_scene_start_submenu_callback, - app); + + FS_Error sd_status = storage_sd_status(app->fs_api); + if(sd_status != FSE_OK) { + submenu_add_item( + submenu, + "Mount SD Card", + StorageSettingsStartSubmenuIndexUnmount, + storage_settings_scene_start_submenu_callback, + app); + } else { + submenu_add_item( + submenu, + "Unmount SD Card", + StorageSettingsStartSubmenuIndexUnmount, + storage_settings_scene_start_submenu_callback, + app); + } + submenu_add_item( submenu, "Format SD Card", diff --git a/applications/settings/storage_settings/scenes/storage_settings_scene_unmount_confirm.c b/applications/settings/storage_settings/scenes/storage_settings_scene_unmount_confirm.c index 0c15116be1b..1b9970f9f60 100644 --- a/applications/settings/storage_settings/scenes/storage_settings_scene_unmount_confirm.c +++ b/applications/settings/storage_settings/scenes/storage_settings_scene_unmount_confirm.c @@ -12,13 +12,17 @@ void storage_settings_scene_unmount_confirm_on_enter(void* context) { DialogEx* dialog_ex = app->dialog_ex; FS_Error sd_status = storage_sd_status(app->fs_api); - if(sd_status == FSE_NOT_READY) { - dialog_ex_set_icon(dialog_ex, 72, 17, &I_DolphinCommon_56x48); - dialog_ex_set_header(dialog_ex, "SD Card Not Mounted", 64, 3, AlignCenter, AlignTop); + dialog_ex_set_header(dialog_ex, "Mount SD Card?", 64, 10, AlignCenter, AlignCenter); dialog_ex_set_text( - dialog_ex, "Try to reinsert\nor format SD\ncard.", 3, 19, AlignLeft, AlignTop); - dialog_ex_set_center_button_text(dialog_ex, "Ok"); + dialog_ex, + "This may turn off power\nfor external modules", + 64, + 32, + AlignCenter, + AlignCenter); + dialog_ex_set_left_button_text(dialog_ex, "Cancel"); + dialog_ex_set_right_button_text(dialog_ex, "Mount"); } else { dialog_ex_set_header(dialog_ex, "Unmount SD Card?", 64, 10, AlignCenter, AlignCenter); dialog_ex_set_text( diff --git a/applications/settings/storage_settings/scenes/storage_settings_scene_unmounted.c b/applications/settings/storage_settings/scenes/storage_settings_scene_unmounted.c index 486f07603ad..33bb9552297 100644 --- a/applications/settings/storage_settings/scenes/storage_settings_scene_unmounted.c +++ b/applications/settings/storage_settings/scenes/storage_settings_scene_unmounted.c @@ -9,22 +9,41 @@ static void void storage_settings_scene_unmounted_on_enter(void* context) { StorageSettings* app = context; - FS_Error error = storage_sd_unmount(app->fs_api); DialogEx* dialog_ex = app->dialog_ex; - dialog_ex_set_center_button_text(dialog_ex, "OK"); - dialog_ex_set_icon(dialog_ex, 72, 17, &I_DolphinCommon_56x48); - - if(error == FSE_OK) { - dialog_ex_set_header(dialog_ex, "SD Card Unmounted", 64, 3, AlignCenter, AlignTop); - dialog_ex_set_text(dialog_ex, "You can remove\nSD card now.", 3, 22, AlignLeft, AlignTop); - notification_message(app->notification, &sequence_blink_green_100); + FS_Error sd_status = storage_sd_status(app->fs_api); + if(sd_status == FSE_NOT_READY) { + FS_Error error = storage_sd_mount(app->fs_api); + if(error == FSE_OK) { + dialog_ex_set_header(dialog_ex, "SD Card Mounted", 64, 3, AlignCenter, AlignTop); + dialog_ex_set_text( + dialog_ex, "Flipper can use\nSD card now.", 3, 22, AlignLeft, AlignTop); + notification_message(app->notification, &sequence_blink_green_100); + } else { + dialog_ex_set_header(dialog_ex, "Cannot Mount SD Card", 64, 3, AlignCenter, AlignTop); + dialog_ex_set_text( + dialog_ex, storage_error_get_desc(error), 3, 22, AlignLeft, AlignTop); + notification_message(app->notification, &sequence_blink_red_100); + } } else { - dialog_ex_set_header(dialog_ex, "Cannot Unmount SD Card", 64, 3, AlignCenter, AlignTop); - dialog_ex_set_text(dialog_ex, storage_error_get_desc(error), 3, 22, AlignLeft, AlignTop); - notification_message(app->notification, &sequence_blink_red_100); + FS_Error error = storage_sd_unmount(app->fs_api); + if(error == FSE_OK) { + dialog_ex_set_header(dialog_ex, "SD Card Unmounted", 64, 3, AlignCenter, AlignTop); + dialog_ex_set_text( + dialog_ex, "You can remove\nSD card now.", 3, 22, AlignLeft, AlignTop); + notification_message(app->notification, &sequence_blink_green_100); + } else { + dialog_ex_set_header( + dialog_ex, "Cannot Unmount SD Card", 64, 3, AlignCenter, AlignTop); + dialog_ex_set_text( + dialog_ex, storage_error_get_desc(error), 3, 22, AlignLeft, AlignTop); + notification_message(app->notification, &sequence_blink_red_100); + } } + dialog_ex_set_center_button_text(dialog_ex, "OK"); + dialog_ex_set_icon(dialog_ex, 72, 17, &I_DolphinCommon_56x48); + dialog_ex_set_context(dialog_ex, app); dialog_ex_set_result_callback(dialog_ex, storage_settings_scene_unmounted_dialog_callback); diff --git a/applications/system/updater/util/update_task_worker_flasher.c b/applications/system/updater/util/update_task_worker_flasher.c index d6dc13e378d..c560319928b 100644 --- a/applications/system/updater/util/update_task_worker_flasher.c +++ b/applications/system/updater/util/update_task_worker_flasher.c @@ -11,7 +11,7 @@ #include #include -#define TAG "UpdWorkerRAM" +#define TAG "UpdWorkerRam" #define STM_DFU_VENDOR_ID 0x0483 #define STM_DFU_PRODUCT_ID 0xDF11 diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index a26db88232a..52f9a4d90aa 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,36.0,, +Version,+,36.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -2002,6 +2002,7 @@ Function,+,storage_int_backup,FS_Error,"Storage*, const char*" Function,+,storage_int_restore,FS_Error,"Storage*, const char*, Storage_name_converter" Function,+,storage_sd_format,FS_Error,Storage* Function,+,storage_sd_info,FS_Error,"Storage*, SDInfo*" +Function,+,storage_sd_mount,FS_Error,Storage* Function,+,storage_sd_status,FS_Error,Storage* Function,+,storage_sd_unmount,FS_Error,Storage* Function,+,storage_simply_mkdir,_Bool,"Storage*, const char*" diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 6ad88d9b834..2222d17021b 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,36.0,, +Version,+,36.1,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -2612,6 +2612,7 @@ Function,+,storage_int_backup,FS_Error,"Storage*, const char*" Function,+,storage_int_restore,FS_Error,"Storage*, const char*, Storage_name_converter" Function,+,storage_sd_format,FS_Error,Storage* Function,+,storage_sd_info,FS_Error,"Storage*, SDInfo*" +Function,+,storage_sd_mount,FS_Error,Storage* Function,+,storage_sd_status,FS_Error,Storage* Function,+,storage_sd_unmount,FS_Error,Storage* Function,+,storage_simply_mkdir,_Bool,"Storage*, const char*" diff --git a/firmware/targets/f7/furi_hal/furi_hal_i2c.c b/firmware/targets/f7/furi_hal/furi_hal_i2c.c index 6c17d6ade90..bdfe0b0a3ee 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_i2c.c +++ b/firmware/targets/f7/furi_hal/furi_hal_i2c.c @@ -8,7 +8,7 @@ #include #include -#define TAG "FuriHalI2C" +#define TAG "FuriHalI2c" void furi_hal_i2c_init_early() { furi_hal_i2c_bus_power.callback(&furi_hal_i2c_bus_power, FuriHalI2cBusEventInit); diff --git a/lib/flipper_application/api_hashtable/api_hashtable.cpp b/lib/flipper_application/api_hashtable/api_hashtable.cpp index 6db5fb5fde6..ef22ee9ad98 100644 --- a/lib/flipper_application/api_hashtable/api_hashtable.cpp +++ b/lib/flipper_application/api_hashtable/api_hashtable.cpp @@ -3,7 +3,7 @@ #include #include -#define TAG "hashtable_api" +#define TAG "ApiHashtable" bool elf_resolve_from_hashtable( const ElfApiInterface* interface, diff --git a/lib/flipper_application/application_assets.c b/lib/flipper_application/application_assets.c index 083c3ca197e..ec3fc22ee69 100644 --- a/lib/flipper_application/application_assets.c +++ b/lib/flipper_application/application_assets.c @@ -17,7 +17,7 @@ #define BUFFER_SIZE 512 -#define TAG "fap_assets" +#define TAG "FapAssets" #pragma pack(push, 1) diff --git a/lib/flipper_application/elf/elf_file.c b/lib/flipper_application/elf/elf_file.c index bea7c1231ff..7ac4c655dcb 100644 --- a/lib/flipper_application/elf/elf_file.c +++ b/lib/flipper_application/elf/elf_file.c @@ -5,7 +5,7 @@ #include "elf_api_interface.h" #include "../api_hashtable/api_hashtable.h" -#define TAG "elf" +#define TAG "Elf" #define ELF_NAME_BUFFER_LEN 32 #define SECTION_OFFSET(e, n) ((e)->section_table + (n) * sizeof(Elf32_Shdr)) diff --git a/lib/flipper_application/plugins/plugin_manager.c b/lib/flipper_application/plugins/plugin_manager.c index 101471dc5e5..e2a7b83f422 100644 --- a/lib/flipper_application/plugins/plugin_manager.c +++ b/lib/flipper_application/plugins/plugin_manager.c @@ -9,7 +9,7 @@ #include -#define TAG "libmgr" +#define TAG "PluginManager" ARRAY_DEF(FlipperApplicationList, FlipperApplication*, M_PTR_OPLIST) #define M_OPL_FlipperApplicationList_t() ARRAY_OPLIST(FlipperApplicationList, M_PTR_OPLIST) diff --git a/lib/lfrfid/lfrfid_raw_file.c b/lib/lfrfid/lfrfid_raw_file.c index 27c6f24754e..ca29770f189 100644 --- a/lib/lfrfid/lfrfid_raw_file.c +++ b/lib/lfrfid/lfrfid_raw_file.c @@ -6,7 +6,7 @@ #define LFRFID_RAW_FILE_MAGIC 0x4C464952 #define LFRFID_RAW_FILE_VERSION 1 -#define TAG "RFID RAW File" +#define TAG "LfRfidRawFile" typedef struct { uint32_t magic; diff --git a/lib/lfrfid/lfrfid_raw_worker.c b/lib/lfrfid/lfrfid_raw_worker.c index aa962a47d69..344c2afa240 100644 --- a/lib/lfrfid/lfrfid_raw_worker.c +++ b/lib/lfrfid/lfrfid_raw_worker.c @@ -10,7 +10,7 @@ #define RFID_DATA_BUFFER_SIZE 2048 #define READ_DATA_BUFFER_COUNT 4 -#define TAG_EMULATE "RAW EMULATE" +#define TAG_EMULATE "RawEmulate" // emulate mode typedef struct { diff --git a/lib/lfrfid/lfrfid_worker_modes.c b/lib/lfrfid/lfrfid_worker_modes.c index 8a26677746d..32e2532590c 100644 --- a/lib/lfrfid/lfrfid_worker_modes.c +++ b/lib/lfrfid/lfrfid_worker_modes.c @@ -7,7 +7,7 @@ #include "tools/varint_pair.h" #include "tools/bit_lib.h" -#define TAG "LFRFIDWorker" +#define TAG "LfRfidWorker" /** * if READ_DEBUG_GPIO is defined: diff --git a/lib/nfc/protocols/slix.c b/lib/nfc/protocols/slix.c index 68937d161a6..dbff2f21884 100644 --- a/lib/nfc/protocols/slix.c +++ b/lib/nfc/protocols/slix.c @@ -7,7 +7,7 @@ #include "furi_hal_nfc.h" #include -#define TAG "SLIX" +#define TAG "Slix" ReturnCode slix2_read_nxp_sysinfo(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data) { furi_assert(nfc_data); diff --git a/lib/subghz/devices/cc1101_int/cc1101_int_interconnect.c b/lib/subghz/devices/cc1101_int/cc1101_int_interconnect.c index 41a0609df00..284c717fd13 100644 --- a/lib/subghz/devices/cc1101_int/cc1101_int_interconnect.c +++ b/lib/subghz/devices/cc1101_int/cc1101_int_interconnect.c @@ -2,7 +2,7 @@ #include #include "../cc1101_configs.h" -#define TAG "SubGhzDeviceCC1101Int" +#define TAG "SubGhzDeviceCc1101Int" static bool subghz_device_cc1101_int_interconnect_is_frequency_valid(uint32_t frequency) { bool ret = furi_hal_subghz_is_frequency_valid(frequency); diff --git a/lib/subghz/protocols/alutech_at_4n.c b/lib/subghz/protocols/alutech_at_4n.c index e8bb87055eb..16c715f807d 100644 --- a/lib/subghz/protocols/alutech_at_4n.c +++ b/lib/subghz/protocols/alutech_at_4n.c @@ -5,7 +5,7 @@ #include "../blocks/generic.h" #include "../blocks/math.h" -#define TAG "SubGhzProtocoAlutech_at_4n" +#define TAG "SubGhzProtocoAlutechAt4n" #define SUBGHZ_NO_ALUTECH_AT_4N_RAINBOW_TABLE 0xFFFFFFFF diff --git a/lib/subghz/protocols/bett.c b/lib/subghz/protocols/bett.c index de13472ac73..7fce94448e1 100644 --- a/lib/subghz/protocols/bett.c +++ b/lib/subghz/protocols/bett.c @@ -7,7 +7,7 @@ #include "../blocks/math.h" // protocol BERNER / ELKA / TEDSEN / TELETASTER -#define TAG "SubGhzProtocolBETT" +#define TAG "SubGhzProtocolBett" #define DIP_P 0b11 //(+) #define DIP_O 0b10 //(0) diff --git a/lib/subghz/protocols/bin_raw.c b/lib/subghz/protocols/bin_raw.c index 9509280c2e9..03f4c5bd4b8 100644 --- a/lib/subghz/protocols/bin_raw.c +++ b/lib/subghz/protocols/bin_raw.c @@ -9,7 +9,7 @@ #include #include -#define TAG "SubGhzProtocolBinRAW" +#define TAG "SubGhzProtocolBinRaw" //change very carefully, RAM ends at the most inopportune moment #define BIN_RAW_BUF_RAW_SIZE 2048 diff --git a/lib/subghz/protocols/came.c b/lib/subghz/protocols/came.c index 14b2e0101cd..40ae05bade3 100644 --- a/lib/subghz/protocols/came.c +++ b/lib/subghz/protocols/came.c @@ -12,7 +12,7 @@ * */ -#define TAG "SubGhzProtocolCAME" +#define TAG "SubGhzProtocolCame" #define CAME_12_COUNT_BIT 12 #define CAME_24_COUNT_BIT 24 #define PRASTEL_COUNT_BIT 25 diff --git a/lib/subghz/protocols/came_twee.c b/lib/subghz/protocols/came_twee.c index 6fe6158139d..1d79d2201e2 100644 --- a/lib/subghz/protocols/came_twee.c +++ b/lib/subghz/protocols/came_twee.c @@ -13,7 +13,7 @@ * */ -#define TAG "SubGhzProtocolCAME_Twee" +#define TAG "SubGhzProtocolCameTwee" #define DIP_PATTERN "%c%c%c%c%c%c%c%c%c%c" #define CNT_TO_DIP(dip) \ diff --git a/lib/subghz/protocols/chamberlain_code.c b/lib/subghz/protocols/chamberlain_code.c index be0877fb5c6..0dd0d2b0b79 100644 --- a/lib/subghz/protocols/chamberlain_code.c +++ b/lib/subghz/protocols/chamberlain_code.c @@ -6,7 +6,7 @@ #include "../blocks/generic.h" #include "../blocks/math.h" -#define TAG "SubGhzProtocolChamb_Code" +#define TAG "SubGhzProtocolChambCode" #define CHAMBERLAIN_CODE_BIT_STOP 0b0001 #define CHAMBERLAIN_CODE_BIT_1 0b0011 diff --git a/lib/subghz/protocols/faac_slh.c b/lib/subghz/protocols/faac_slh.c index d9dd6840811..cdee7cd9f27 100644 --- a/lib/subghz/protocols/faac_slh.c +++ b/lib/subghz/protocols/faac_slh.c @@ -6,7 +6,7 @@ #include "../blocks/generic.h" #include "../blocks/math.h" -#define TAG "SubGhzProtocolFaacSHL" +#define TAG "SubGhzProtocolFaacShl" static const SubGhzBlockConst subghz_protocol_faac_slh_const = { .te_short = 255, diff --git a/lib/subghz/protocols/holtek_ht12x.c b/lib/subghz/protocols/holtek_ht12x.c index 831f824dd64..302b78598b9 100644 --- a/lib/subghz/protocols/holtek_ht12x.c +++ b/lib/subghz/protocols/holtek_ht12x.c @@ -12,7 +12,7 @@ * */ -#define TAG "SubGhzProtocolHoltek_HT12X" +#define TAG "SubGhzProtocolHoltekHt12x" #define DIP_PATTERN "%c%c%c%c%c%c%c%c" #define CNT_TO_DIP(dip) \ diff --git a/lib/subghz/protocols/honeywell_wdb.c b/lib/subghz/protocols/honeywell_wdb.c index 7fd8d66d6f2..fcf2822011c 100644 --- a/lib/subghz/protocols/honeywell_wdb.c +++ b/lib/subghz/protocols/honeywell_wdb.c @@ -6,7 +6,7 @@ #include "../blocks/generic.h" #include "../blocks/math.h" -#define TAG "SubGhzProtocolHoneywellWDB" +#define TAG "SubGhzProtocolHoneywellWdb" /* * diff --git a/lib/subghz/protocols/hormann.c b/lib/subghz/protocols/hormann.c index 4c5c68cc449..fc490e9d1c7 100644 --- a/lib/subghz/protocols/hormann.c +++ b/lib/subghz/protocols/hormann.c @@ -6,7 +6,7 @@ #include "../blocks/generic.h" #include "../blocks/math.h" -#define TAG "SubGhzProtocolHormannHSM" +#define TAG "SubGhzProtocolHormannHsm" #define HORMANN_HSM_PATTERN 0xFF000000003 diff --git a/lib/subghz/protocols/ido.c b/lib/subghz/protocols/ido.c index 90c0fb0e38c..e96e6566b5f 100644 --- a/lib/subghz/protocols/ido.c +++ b/lib/subghz/protocols/ido.c @@ -6,7 +6,7 @@ #include "../blocks/generic.h" #include "../blocks/math.h" -#define TAG "SubGhzProtocol_iDo_117/111" +#define TAG "SubGhzProtocolIdo117/111" static const SubGhzBlockConst subghz_protocol_ido_const = { .te_short = 450, diff --git a/lib/subghz/protocols/kia.c b/lib/subghz/protocols/kia.c index 1d134f7babe..9b63271b49d 100644 --- a/lib/subghz/protocols/kia.c +++ b/lib/subghz/protocols/kia.c @@ -6,7 +6,7 @@ #include "../blocks/generic.h" #include "../blocks/math.h" -#define TAG "SubGhzProtocoKIA" +#define TAG "SubGhzProtocoKia" static const SubGhzBlockConst subghz_protocol_kia_const = { .te_short = 250, diff --git a/lib/subghz/protocols/kinggates_stylo_4k.c b/lib/subghz/protocols/kinggates_stylo_4k.c index cd027b99c7a..0b2a102c49a 100644 --- a/lib/subghz/protocols/kinggates_stylo_4k.c +++ b/lib/subghz/protocols/kinggates_stylo_4k.c @@ -8,7 +8,7 @@ #include "../blocks/generic.h" #include "../blocks/math.h" -#define TAG "SubGhzProtocoKingGates_stylo_4k" +#define TAG "SubGhzProtocoKingGatesStylo4k" static const SubGhzBlockConst subghz_protocol_kinggates_stylo_4k_const = { .te_short = 400, diff --git a/lib/subghz/protocols/nice_flo.c b/lib/subghz/protocols/nice_flo.c index af81d9f9056..f60e07fb846 100644 --- a/lib/subghz/protocols/nice_flo.c +++ b/lib/subghz/protocols/nice_flo.c @@ -5,7 +5,7 @@ #include "../blocks/generic.h" #include "../blocks/math.h" -#define TAG "SubGhzProtocolNiceFLO" +#define TAG "SubGhzProtocolNiceFlo" static const SubGhzBlockConst subghz_protocol_nice_flo_const = { .te_short = 700, diff --git a/lib/subghz/protocols/phoenix_v2.c b/lib/subghz/protocols/phoenix_v2.c index 4ed9766ef65..2416a9d0109 100644 --- a/lib/subghz/protocols/phoenix_v2.c +++ b/lib/subghz/protocols/phoenix_v2.c @@ -6,7 +6,7 @@ #include "../blocks/generic.h" #include "../blocks/math.h" -#define TAG "SubGhzProtocolPhoenix_V2" +#define TAG "SubGhzProtocolPhoenixV2" //transmission only static mode diff --git a/lib/subghz/protocols/raw.c b/lib/subghz/protocols/raw.c index 4a8ae1d5edf..95dff309355 100644 --- a/lib/subghz/protocols/raw.c +++ b/lib/subghz/protocols/raw.c @@ -11,7 +11,7 @@ #include #include -#define TAG "SubGhzProtocolRAW" +#define TAG "SubGhzProtocolRaw" #define SUBGHZ_DOWNLOAD_MAX_SIZE 512 static const SubGhzBlockConst subghz_protocol_raw_const = { diff --git a/lib/subghz/protocols/secplus_v1.c b/lib/subghz/protocols/secplus_v1.c index 783351c6b41..644b2fba9cf 100644 --- a/lib/subghz/protocols/secplus_v1.c +++ b/lib/subghz/protocols/secplus_v1.c @@ -11,7 +11,7 @@ * https://github.com/merbanan/rtl_433/blob/master/src/devices/secplus_v1.c */ -#define TAG "SubGhzProtocoSecPlus_v1" +#define TAG "SubGhzProtocoSecPlusV1" #define SECPLUS_V1_BIT_ERR -1 //0b0000 #define SECPLUS_V1_BIT_0 0 //0b0001 diff --git a/lib/subghz/protocols/secplus_v2.c b/lib/subghz/protocols/secplus_v2.c index 12d2fac74e6..374c407b034 100644 --- a/lib/subghz/protocols/secplus_v2.c +++ b/lib/subghz/protocols/secplus_v2.c @@ -13,7 +13,7 @@ * https://github.com/merbanan/rtl_433/blob/master/src/devices/secplus_v2.c */ -#define TAG "SubGhzProtocoSecPlus_v2" +#define TAG "SubGhzProtocoSecPlusV2" #define SECPLUS_V2_HEADER 0x3C0000000000 #define SECPLUS_V2_HEADER_MASK 0xFFFF3C0000000000 diff --git a/lib/subghz/protocols/smc5326.c b/lib/subghz/protocols/smc5326.c index bfb36b76a93..0b9755b8cba 100644 --- a/lib/subghz/protocols/smc5326.c +++ b/lib/subghz/protocols/smc5326.c @@ -12,7 +12,7 @@ * */ -#define TAG "SubGhzProtocolSMC5326" +#define TAG "SubGhzProtocolSmc5326" #define DIP_P 0b11 //(+) #define DIP_O 0b10 //(0) From 452e27b05ed26fb76f0d3f3ff14d4dcb2e46aa7e Mon Sep 17 00:00:00 2001 From: hedger Date: Tue, 5 Sep 2023 14:49:39 +0300 Subject: [PATCH 742/824] github: workflow improvements (#3032) * github: compact build: status reporting step * github: build: matrix strategy * debugging * github: added version_token to /uploadfiles request * github: reworked main build flow * github: suppressed non-zero cp status * github: build: fixed comment lookup; experimental changes to apps build order * github: removed summary step for compact builds; united map analyzer steps * fbt: added get_apiversion target; moved ext apps processing logic to AppBuildset * ufbt: added missing global * fbt: Moved incompatible app list to firmware config output * fbt: cleaner extapps processing * github: build: added automation for SDK publishing --- .github/workflows/build.yml | 124 +++++++++++++++++----------- .github/workflows/build_compact.yml | 9 ++ firmware.scons | 14 +++- scripts/fbt/appmanifest.py | 49 ++++++++++- scripts/fbt/sdk/cache.py | 15 ++++ scripts/fbt_tools/fbt_apps.py | 9 +- scripts/ufbt/SConstruct | 1 + site_scons/extapps.scons | 30 +------ 8 files changed, 165 insertions(+), 86 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 07b219712d7..510542c3a90 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,13 +9,15 @@ on: pull_request: env: - TARGETS: f7 f18 DEFAULT_TARGET: f7 FBT_TOOLCHAIN_PATH: /runner/_work jobs: main: runs-on: [self-hosted, FlipperZeroShell] + strategy: + matrix: + target: [f7, f18] steps: - name: 'Wipe workspace' run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; @@ -33,68 +35,69 @@ jobs: TYPE="pull" elif [[ "${{ github.ref }}" == "refs/tags/"* ]]; then TYPE="tag" + echo 'FBT_BUILD_TYPE="DEBUG=0 COMPACT=1"' >> $GITHUB_ENV else TYPE="other" fi python3 scripts/get_env.py "--event_file=${{ github.event_path }}" "--type=$TYPE" || cat "${{ github.event_path }}" echo "event_type=$TYPE" >> $GITHUB_OUTPUT + echo "TARGET=${{ matrix.target }}" >> $GITHUB_ENV + echo "TARGET_HW=$(echo "${{ matrix.target }}" | sed 's/f//')" >> $GITHUB_ENV - - name: 'Check API versions' + - name: 'Check API versions for consistency between targets' run: | set -e N_API_HEADER_SIGNATURES=`ls -1 firmware/targets/f*/api_symbols.csv | xargs -I {} sh -c "head -n2 {} | md5sum" | sort -u | wc -l` if [ $N_API_HEADER_SIGNATURES != 1 ] ; then echo API versions aren\'t matching for available targets. Please update! + echo API versions are: head -n2 firmware/targets/f*/api_symbols.csv exit 1 fi - - name: 'Make artifacts directory' + - name: 'Build the firmware and apps' run: | - rm -rf artifacts map_analyser_files - mkdir artifacts map_analyser_files + ./fbt TARGET_HW=$TARGET_HW $FBT_BUILD_TYPE copro_dist updater_package fap_dist - - name: 'Bundle scripts' - if: ${{ !github.event.pull_request.head.repo.fork }} + - name: 'Check for uncommitted changes' run: | - tar czpf "artifacts/flipper-z-any-scripts-${SUFFIX}.tgz" scripts + git diff --exit-code - - name: 'Build the firmware' + - name: 'Copy build output' run: | set -e - for TARGET in ${TARGETS}; do - TARGET_HW="$(echo "${TARGET}" | sed 's/f//')"; \ - ./fbt TARGET_HW=$TARGET_HW copro_dist updater_package \ - ${{ startsWith(github.ref, 'refs/tags') && 'DEBUG=0 COMPACT=1' || '' }} - mv dist/${TARGET}-*/* artifacts/ - tar czpf "artifacts/flipper-z-${TARGET}-resources-${SUFFIX}.tgz" \ - -C assets resources - ./fbt TARGET_HW=$TARGET_HW fap_dist - tar czpf "artifacts/flipper-z-${TARGET}-debugapps-${SUFFIX}.tgz" \ - -C dist/${TARGET}-*/apps/Debug . - tar czpf "artifacts/flipper-z-${TARGET}-appsymbols-${SUFFIX}.tgz" \ - -C dist/${TARGET}-*/debug_elf . - done - - - name: "Check for uncommitted changes" + rm -rf artifacts map_analyser_files || true + mkdir artifacts map_analyser_files + cp dist/${TARGET}-*/* artifacts/ || true + tar czpf "artifacts/flipper-z-${TARGET}-resources-${SUFFIX}.tgz" \ + -C assets resources + tar czpf "artifacts/flipper-z-${TARGET}-debugapps-${SUFFIX}.tgz" \ + -C dist/${TARGET}-*/apps/Debug . + tar czpf "artifacts/flipper-z-${TARGET}-appsymbols-${SUFFIX}.tgz" \ + -C dist/${TARGET}-*/debug_elf . + + - name: 'Copy universal artifacts' + if: ${{ !github.event.pull_request.head.repo.fork && matrix.target == env.DEFAULT_TARGET }} run: | - git diff --exit-code + tar czpf "artifacts/flipper-z-any-scripts-${SUFFIX}.tgz" scripts + cp build/core2_firmware.tgz "artifacts/flipper-z-any-core2_firmware-${SUFFIX}.tgz" - - name: 'Bundle core2 firmware' + - name: 'Upload artifacts to update server' if: ${{ !github.event.pull_request.head.repo.fork }} run: | - cp build/core2_firmware.tgz "artifacts/flipper-z-any-core2_firmware-${SUFFIX}.tgz" + FILES=$(for ARTIFACT in $(find artifacts -maxdepth 1 -not -type d); do echo "-F files=@${ARTIFACT}"; done) + curl --fail -L -H "Token: ${{ secrets.INDEXER_TOKEN }}" \ + -F "branch=${BRANCH_NAME}" \ + -F "version_token=${COMMIT_SHA}" \ + ${FILES[@]} \ + "${{ secrets.INDEXER_URL }}"/firmware/uploadfiles - - name: 'Copy map analyser files' - if: ${{ !github.event.pull_request.head.repo.fork }} + - name: 'Copy & analyse map analyser files' + if: ${{ !github.event.pull_request.head.repo.fork && matrix.target == env.DEFAULT_TARGET }} run: | cp build/${DEFAULT_TARGET}-firmware-*/firmware.elf.map map_analyser_files/firmware.elf.map cp build/${DEFAULT_TARGET}-firmware-*/firmware.elf map_analyser_files/firmware.elf cp ${{ github.event_path }} map_analyser_files/event.json - - - name: 'Analyse map file' - if: ${{ !github.event.pull_request.head.repo.fork }} - run: | source scripts/toolchain/fbtenv.sh get_size() { @@ -118,33 +121,56 @@ jobs: ${{ secrets.AMAP_MARIADB_DATABASE }} \ map_analyser_files/firmware.elf.map.all - - name: 'Upload artifacts to update server' - if: ${{ !github.event.pull_request.head.repo.fork }} - run: | - FILES=$(for CUR in $(ls artifacts/); do echo "-F files=@artifacts/$CUR"; done) - curl --fail -L -H "Token: ${{ secrets.INDEXER_TOKEN }}" \ - -F "branch=${BRANCH_NAME}" \ - ${FILES[@]} \ - "${{ secrets.INDEXER_URL }}"/firmware/uploadfiles - - - name: 'Find Previous Comment' - if: ${{ !github.event.pull_request.head.repo.fork && github.event.pull_request }} + - name: 'Find previous comment' + if: ${{ !github.event.pull_request.head.repo.fork && matrix.target == env.DEFAULT_TARGET && github.event.pull_request }} uses: peter-evans/find-comment@v2 - id: fc + id: find-comment with: issue-number: ${{ github.event.pull_request.number }} comment-author: 'github-actions[bot]' - body-includes: 'Compiled firmware for commit' + body-includes: 'Compiled ${{ matrix.target }} firmware for commit' - name: 'Create or update comment' - if: ${{ !github.event.pull_request.head.repo.fork && github.event.pull_request}} + if: ${{ !github.event.pull_request.head.repo.fork && matrix.target == env.DEFAULT_TARGET && github.event.pull_request }} uses: peter-evans/create-or-update-comment@v3 with: - comment-id: ${{ steps.fc.outputs.comment-id }} + comment-id: ${{ steps.find-comment.outputs.comment-id }} issue-number: ${{ github.event.pull_request.number }} body: | - **Compiled firmware for commit `${{steps.names.outputs.commit_sha}}`:** + **Compiled ${{ matrix.target }} firmware for commit `${{steps.names.outputs.commit_sha}}`:** - [📦 Update package](https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.branch_name}}/flipper-z-${{steps.names.outputs.default_target}}-update-${{steps.names.outputs.suffix}}.tgz) - [📥 DFU file](https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.branch_name}}/flipper-z-${{steps.names.outputs.default_target}}-full-${{steps.names.outputs.suffix}}.dfu) - [☁️ Web/App updater](https://lab.flipper.net/?url=https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.branch_name}}/flipper-z-${{steps.names.outputs.default_target}}-update-${{steps.names.outputs.suffix}}.tgz&channel=${{steps.names.outputs.branch_name}}&version=${{steps.names.outputs.commit_sha}}) edit-mode: replace + + - name: 'Check if API version exists' + if: ${{ steps.names.outputs.event_type == 'tag' && matrix.target == env.DEFAULT_TARGET }} + run: | + FIRMWARE_API=$(./fbt TARGET_HW=$TARGET_HW get_apiversion) + curl -sX 'GET' \ + '${{ secrets.CATALOG_URL }}/api/v0/0/sdk?length=200' \ + -H 'Accept: application/json' > sdk_versions.json + if jq -r -e ".[] | select((.api == \"${FIRMWARE_API}\") and .target == \"f${TARGET_HW}\")" sdk_versions.json > found_sdk.json ; then + echo "API version $FIRMWARE_API already exists in catalog" + if [ $(jq -r -e ".released_at" found_sdk.json) != "null" ] ; then + echo "API version is already released" + exit 0 + fi + if ! echo "$SUFFIX" | grep -q "-rc" ; then + SDK_ID=$(jq -r ._id found_sdk.json) + echo "Marking SDK $SDK_ID as released" + curl -X 'POST' \ + "${{ secrets.CATALOG_URL }}/api/v0/0/sdk/${SDK_ID}/release" \ + -H 'Accept: application/json' \ + -H 'Authorization: Bearer ${{ secrets.CATALOG_API_TOKEN }}' \ + -d '' + fi + else + echo "API version $FIRMWARE_API doesn't exist in catalog, adding" + curl -X 'POST' \ + '${{ secrets.CATALOG_URL }}/api/v0/0/sdk' \ + -H 'Accept: application/json' \ + -H 'Authorization: Bearer ${{ secrets.CATALOG_API_TOKEN }}' \ + -H 'Content-Type: application/json' \ + -d "{\"name\": \"${SUFFIX}\", \"target\": \"f${TARGET_HW}\", \"api\": \"${FIRMWARE_API}\"}\" + fi diff --git a/.github/workflows/build_compact.yml b/.github/workflows/build_compact.yml index 705888a41ba..37ebc0ca181 100644 --- a/.github/workflows/build_compact.yml +++ b/.github/workflows/build_compact.yml @@ -73,3 +73,12 @@ jobs: popd done done + +## Uncomment this for a single job that will run only if all targets are built successfully +# report-status: +# name: Report status +# needs: [compact] +# if: always() && !contains(needs.*.result, 'failure') +# runs-on: [self-hosted, FlipperZeroShell] +# steps: +# - run: echo "All good ✨" ; diff --git a/firmware.scons b/firmware.scons index a91b7cdfc61..2a82db3717e 100644 --- a/firmware.scons +++ b/firmware.scons @@ -8,6 +8,9 @@ from fbt_extra.util import ( link_elf_dir_as_latest, ) +from fbt.sdk.cache import LazySdkVersionLoader + + Import("ENV", "fw_build_meta") # Building initial C environment for libs @@ -71,6 +74,8 @@ env = ENV.Clone( }, FW_API_TABLE=None, _APP_ICONS=None, + APPS=_.split(",") if (_ := GetOption("extra_int_apps")) else [], + EXTRA_EXT_APPS=_.split(",") if (_ := GetOption("extra_ext_apps")) else [], ) env.PreConfigureFwEnvionment() @@ -125,9 +130,6 @@ if env["IS_BASE_FIRMWARE"]: else: fwenv.Append(APPS=["updater"]) -if extra_int_apps := GetOption("extra_int_apps"): - fwenv.Append(APPS=extra_int_apps.split(",")) - for app_dir, _ in fwenv["APPDIRS"]: app_dir_node = env.Dir("#").Dir(app_dir) @@ -136,7 +138,6 @@ for app_dir, _ in fwenv["APPDIRS"]: if isinstance(entry, FS.Dir) and not str(entry).startswith("."): fwenv.LoadAppManifest(entry) - fwenv.PrepareApplicationsBuild() # Build external apps + configure SDK @@ -148,6 +149,11 @@ if env["IS_BASE_FIRMWARE"]: ) fw_artifacts.append(fwenv["FW_EXTAPPS"].sdk_tree) + fwenv.Append(FBT_API_VERSION=LazySdkVersionLoader(fwenv.subst("$SDK_DEFINITION"))) + fwenv.PhonyTarget( + "get_apiversion", + "@echo $( ${FBT_API_VERSION} $)", + ) # Add preprocessor definitions for current set of apps fwenv.Append( diff --git a/scripts/fbt/appmanifest.py b/scripts/fbt/appmanifest.py index b8b8a8d6870..7bb8e40b2d1 100644 --- a/scripts/fbt/appmanifest.py +++ b/scripts/fbt/appmanifest.py @@ -193,8 +193,19 @@ def _add_known_app(self, app: FlipperApplication): raise FlipperManifestException(f"Duplicate app declaration: {app.appid}") self.known_apps[app.appid] = app - def filter_apps(self, applist: List[str], hw_target: str): - return AppBuildset(self, applist, hw_target) + def filter_apps( + self, + *, + applist: List[str], + ext_applist: List[str], + hw_target: str, + ): + return AppBuildset( + self, + hw_target=hw_target, + appnames=applist, + extra_ext_appnames=ext_applist, + ) class AppBuilderException(Exception): @@ -211,6 +222,12 @@ class AppBuildset: FlipperAppType.SETTINGS, FlipperAppType.STARTUP, ) + EXTERNAL_APP_TYPES = ( + FlipperAppType.EXTERNAL, + FlipperAppType.MENUEXTERNAL, + FlipperAppType.PLUGIN, + FlipperAppType.DEBUG, + ) @staticmethod def print_writer(message): @@ -219,16 +236,21 @@ def print_writer(message): def __init__( self, appmgr: AppManager, - appnames: List[str], hw_target: str, + appnames: List[str], + *, + extra_ext_appnames: List[str], message_writer: Callable | None = None, ): self.appmgr = appmgr self.appnames = set(appnames) + self.incompatible_extapps, self.extapps = [], [] + self._extra_ext_appnames = extra_ext_appnames self._orig_appnames = appnames self.hw_target = hw_target self._writer = message_writer if message_writer else self.print_writer self._process_deps() + self._process_ext_apps() self._check_conflicts() self._check_unsatisfied() # unneeded? self._check_target_match() @@ -271,6 +293,27 @@ def _process_deps(self): break self.appnames.update(provided) + def _process_ext_apps(self): + extapps = [ + app + for apptype in self.EXTERNAL_APP_TYPES + for app in self.get_apps_of_type(apptype, True) + ] + extapps.extend(map(self.appmgr.get, self._extra_ext_appnames)) + + for app in extapps: + ( + self.extapps + if app.supports_hardware_target(self.hw_target) + else self.incompatible_extapps + ).append(app) + + def get_ext_apps(self): + return self.extapps + + def get_incompatible_ext_apps(self): + return self.incompatible_extapps + def _check_conflicts(self): conflicts = [] for app in self.appnames: diff --git a/scripts/fbt/sdk/cache.py b/scripts/fbt/sdk/cache.py index 074cac6b918..e3d93a319a3 100644 --- a/scripts/fbt/sdk/cache.py +++ b/scripts/fbt/sdk/cache.py @@ -255,3 +255,18 @@ def validate_api(self, api: ApiEntries) -> None: self.sync_sets(self.sdk.headers, api.headers, False) self.sync_sets(self.sdk.functions, api.functions) self.sync_sets(self.sdk.variables, api.variables) + + +class LazySdkVersionLoader: + def __init__(self, sdk_path: str): + self.sdk_path = sdk_path + self._version = None + + @property + def version(self) -> SdkVersion: + if self._version is None: + self._version = SdkCache(self.sdk_path, load_version_only=True).version + return self._version + + def __str__(self) -> str: + return str(self.version) diff --git a/scripts/fbt_tools/fbt_apps.py b/scripts/fbt_tools/fbt_apps.py index cbb3bf726ad..9a071805520 100644 --- a/scripts/fbt_tools/fbt_apps.py +++ b/scripts/fbt_tools/fbt_apps.py @@ -36,7 +36,9 @@ def LoadAppManifest(env, entry): def PrepareApplicationsBuild(env): try: appbuild = env["APPBUILD"] = env["APPMGR"].filter_apps( - env["APPS"], env.subst("f${TARGET_HW}") + applist=env["APPS"], + ext_applist=env["EXTRA_EXT_APPS"], + hw_target=env.subst("f${TARGET_HW}"), ) except Exception as e: raise StopError(e) @@ -56,6 +58,11 @@ def DumpApplicationConfig(target, source, env): fg.green(f"{apptype.value}:\n\t"), ", ".join(app.appid for app in app_sublist), ) + if incompatible_ext_apps := env["APPBUILD"].get_incompatible_ext_apps(): + print( + fg.blue("Incompatible apps (skipped):\n\t"), + ", ".join(app.appid for app in incompatible_ext_apps), + ) def build_apps_c(target, source, env): diff --git a/scripts/ufbt/SConstruct b/scripts/ufbt/SConstruct index 1630135c2bc..98e6b638f36 100644 --- a/scripts/ufbt/SConstruct +++ b/scripts/ufbt/SConstruct @@ -107,6 +107,7 @@ env = core_env.Clone( SINGLEQUOTEFUNC=single_quote, ABSPATHGETTERFUNC=resolve_real_dir_node, APPS=[], + EXTRA_EXT_APPS=[], UFBT_API_VERSION=SdkCache( core_env.subst("$SDK_DEFINITION"), load_version_only=True ).version, diff --git a/site_scons/extapps.scons b/site_scons/extapps.scons index 97b7ac095e8..f9227ed37b2 100644 --- a/site_scons/extapps.scons +++ b/site_scons/extapps.scons @@ -60,40 +60,12 @@ class FlipperExtAppBuildArtifacts: sdk_tree: NodeList = field(default_factory=NodeList) -apps_to_build_as_faps = [ - FlipperAppType.PLUGIN, - FlipperAppType.EXTERNAL, - FlipperAppType.MENUEXTERNAL, - FlipperAppType.DEBUG, -] - -known_extapps = [ - app - for apptype in apps_to_build_as_faps - for app in appenv["APPBUILD"].get_apps_of_type(apptype, True) -] - -# Ugly access to global option -if extra_app_list := GetOption("extra_ext_apps"): - known_extapps.extend(map(appenv["APPMGR"].get, extra_app_list.split(","))) - -incompatible_apps = [] -for app in known_extapps: - if not app.supports_hardware_target(appenv.subst("f${TARGET_HW}")): - incompatible_apps.append(app) - continue - +for app in appenv["APPBUILD"].get_ext_apps(): appenv.BuildAppElf(app) extapps = FlipperExtAppBuildArtifacts() extapps.application_map = appenv["EXT_APPS"] -if incompatible_apps: - warn( - WarningOnByDefault, - f"Skipping build of {len(incompatible_apps)} incompatible app(s): " - + ", ".join(f"'{app.name}' (id '{app.appid}')" for app in incompatible_apps), - ) if appenv["FORCE"]: appenv.AlwaysBuild( From 8cbb757533a093fcb61109fb92f145808a43ac5f Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Tue, 5 Sep 2023 22:13:50 +0900 Subject: [PATCH 743/824] [FL-3583]Account for the "-" in line carry-over (#3038) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- applications/services/gui/elements.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/applications/services/gui/elements.c b/applications/services/gui/elements.c index 37ecfde4c67..891435c961b 100644 --- a/applications/services/gui/elements.c +++ b/applications/services/gui/elements.c @@ -290,7 +290,8 @@ void elements_multiline_text_aligned( } else if((y + font_height) > canvas_height(canvas)) { line = furi_string_alloc_printf("%.*s...\n", chars_fit, start); } else { - line = furi_string_alloc_printf("%.*s-\n", chars_fit, start); + // Account for the added "-" in length + line = furi_string_alloc_printf("%.*s-\n", chars_fit - 1, start); } canvas_draw_str_aligned(canvas, x, y, horizontal, vertical, furi_string_get_cstr(line)); furi_string_free(line); From 600b2ce627a2b01bd3581e996763d8777d066cd2 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Tue, 5 Sep 2023 22:24:50 +0900 Subject: [PATCH 744/824] [FL-3566] iButton: Return to the file selection if file is corrupted (#3040) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- applications/main/ibutton/ibutton.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/applications/main/ibutton/ibutton.c b/applications/main/ibutton/ibutton.c index 0e4c621b20f..760918097f1 100644 --- a/applications/main/ibutton/ibutton.c +++ b/applications/main/ibutton/ibutton.c @@ -195,6 +195,7 @@ bool ibutton_load_key(iButton* ibutton) { bool ibutton_select_and_load_key(iButton* ibutton) { DialogsFileBrowserOptions browser_options; + bool success = false; dialog_file_browser_set_basic_options( &browser_options, IBUTTON_APP_FILENAME_EXTENSION, &I_ibutt_10px); browser_options.base_path = IBUTTON_APP_FOLDER; @@ -203,9 +204,14 @@ bool ibutton_select_and_load_key(iButton* ibutton) { furi_string_set(ibutton->file_path, browser_options.base_path); } - return dialog_file_browser_show( - ibutton->dialogs, ibutton->file_path, ibutton->file_path, &browser_options) && - ibutton_load_key(ibutton); + do { + if(!dialog_file_browser_show( + ibutton->dialogs, ibutton->file_path, ibutton->file_path, &browser_options)) + break; + success = ibutton_load_key(ibutton); + } while(!success); + + return success; } bool ibutton_save_key(iButton* ibutton) { From dfd52337605a8cfa3f35940abbc7bc929e3f0825 Mon Sep 17 00:00:00 2001 From: hedger Date: Tue, 5 Sep 2023 17:17:16 +0300 Subject: [PATCH 745/824] github: don't cancel jobs if parallel ones failed (#3044) --- .github/workflows/build.yml | 1 + .github/workflows/build_compact.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 510542c3a90..3736816a60f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -16,6 +16,7 @@ jobs: main: runs-on: [self-hosted, FlipperZeroShell] strategy: + fail-fast: false matrix: target: [f7, f18] steps: diff --git a/.github/workflows/build_compact.yml b/.github/workflows/build_compact.yml index 37ebc0ca181..7556c15acda 100644 --- a/.github/workflows/build_compact.yml +++ b/.github/workflows/build_compact.yml @@ -10,6 +10,7 @@ jobs: compact: runs-on: [self-hosted, FlipperZeroShell] strategy: + fail-fast: false matrix: target: [f7, f18] steps: From 1c0276a0be7f2fcf9aad802f364d3df6b68c9709 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Mon, 11 Sep 2023 15:23:00 +0900 Subject: [PATCH 746/824] Various Fixes and Improvements (#3059) * FuriHal: lower MGG display contrast by 4 points * FuriHal: unify external gpio resources initialization * Infrared: parse raw in universal TV file, cleanup it with new ifrared script. Other minor changes. * Gui: fix elements_multiline_text_aligned eating symbols * Lib: human readable errors in flipper application package * Special request from marketing team --- applications/services/gui/elements.c | 3 +- applications/system/hid_app/application.fam | 28 ++ .../system/hid_app/assets/Alt_11x7.png | Bin 0 -> 2417 bytes .../system/hid_app/assets/Arr_dwn_7x9.png | Bin 0 -> 3602 bytes .../system/hid_app/assets/Arr_up_7x9.png | Bin 0 -> 3605 bytes .../hid_app/assets/Ble_connected_15x15.png | Bin 0 -> 3634 bytes .../hid_app/assets/Ble_disconnected_15x15.png | Bin 0 -> 657 bytes .../system/hid_app/assets/ButtonDown_7x4.png | Bin 0 -> 102 bytes .../system/hid_app/assets/ButtonF10_5x8.png | Bin 0 -> 172 bytes .../system/hid_app/assets/ButtonF11_5x8.png | Bin 0 -> 173 bytes .../system/hid_app/assets/ButtonF12_5x8.png | Bin 0 -> 180 bytes .../system/hid_app/assets/ButtonF1_5x8.png | Bin 0 -> 177 bytes .../system/hid_app/assets/ButtonF2_5x8.png | Bin 0 -> 179 bytes .../system/hid_app/assets/ButtonF3_5x8.png | Bin 0 -> 178 bytes .../system/hid_app/assets/ButtonF4_5x8.png | Bin 0 -> 177 bytes .../system/hid_app/assets/ButtonF5_5x8.png | Bin 0 -> 178 bytes .../system/hid_app/assets/ButtonF6_5x8.png | Bin 0 -> 177 bytes .../system/hid_app/assets/ButtonF7_5x8.png | Bin 0 -> 176 bytes .../system/hid_app/assets/ButtonF8_5x8.png | Bin 0 -> 176 bytes .../system/hid_app/assets/ButtonF9_5x8.png | Bin 0 -> 179 bytes .../system/hid_app/assets/ButtonLeft_4x7.png | Bin 0 -> 1415 bytes .../system/hid_app/assets/ButtonRight_4x7.png | Bin 0 -> 1839 bytes .../system/hid_app/assets/ButtonUp_7x4.png | Bin 0 -> 102 bytes .../system/hid_app/assets/Button_18x18.png | Bin 0 -> 3609 bytes .../system/hid_app/assets/Circles_47x47.png | Bin 0 -> 3712 bytes .../system/hid_app/assets/Cmd_15x7.png | Bin 0 -> 2426 bytes .../system/hid_app/assets/Ctrl_15x7.png | Bin 0 -> 2433 bytes .../system/hid_app/assets/Del_12x7.png | Bin 0 -> 2417 bytes .../system/hid_app/assets/Esc_14x7.png | Bin 0 -> 2430 bytes .../hid_app/assets/Left_mouse_icon_9x9.png | Bin 0 -> 3622 bytes .../system/hid_app/assets/Like_def_11x9.png | Bin 0 -> 3616 bytes .../hid_app/assets/Like_pressed_17x17.png | Bin 0 -> 3643 bytes .../system/hid_app/assets/Ok_btn_9x9.png | Bin 0 -> 3605 bytes .../hid_app/assets/Ok_btn_pressed_13x13.png | Bin 0 -> 3625 bytes .../hid_app/assets/Pin_arrow_down_7x9.png | Bin 0 -> 3607 bytes .../hid_app/assets/Pin_arrow_left_9x7.png | Bin 0 -> 3603 bytes .../hid_app/assets/Pin_arrow_right_9x7.png | Bin 0 -> 3602 bytes .../hid_app/assets/Pin_arrow_up_7x9.png | Bin 0 -> 3603 bytes .../hid_app/assets/Pin_back_arrow_10x8.png | Bin 0 -> 3606 bytes .../hid_app/assets/Pressed_Button_13x13.png | Bin 0 -> 3606 bytes .../hid_app/assets/Right_mouse_icon_9x9.png | Bin 0 -> 3622 bytes .../system/hid_app/assets/Space_60x18.png | Bin 0 -> 2871 bytes .../system/hid_app/assets/Space_65x18.png | Bin 0 -> 3619 bytes .../system/hid_app/assets/Tab_15x7.png | Bin 0 -> 2419 bytes .../system/hid_app/assets/Voldwn_6x6.png | Bin 0 -> 3593 bytes .../system/hid_app/assets/Volup_8x6.png | Bin 0 -> 3595 bytes applications/system/hid_app/hid.c | 452 ++++++++++++++++++ applications/system/hid_app/hid.h | 67 +++ applications/system/hid_app/hid_ble_10px.png | Bin 0 -> 151 bytes applications/system/hid_app/hid_usb_10px.png | Bin 0 -> 969 bytes applications/system/hid_app/views.h | 11 + .../system/hid_app/views/hid_keyboard.c | 411 ++++++++++++++++ .../system/hid_app/views/hid_keyboard.h | 14 + .../system/hid_app/views/hid_keynote.c | 312 ++++++++++++ .../system/hid_app/views/hid_keynote.h | 16 + applications/system/hid_app/views/hid_media.c | 218 +++++++++ applications/system/hid_app/views/hid_media.h | 13 + applications/system/hid_app/views/hid_mouse.c | 226 +++++++++ applications/system/hid_app/views/hid_mouse.h | 17 + .../system/hid_app/views/hid_mouse_clicker.c | 214 +++++++++ .../system/hid_app/views/hid_mouse_clicker.h | 14 + .../system/hid_app/views/hid_mouse_jiggler.c | 159 ++++++ .../system/hid_app/views/hid_mouse_jiggler.h | 17 + .../system/hid_app/views/hid_tiktok.c | 241 ++++++++++ .../system/hid_app/views/hid_tiktok.h | 14 + .../system/snake_game/application.fam | 13 + applications/system/snake_game/snake_10px.png | Bin 0 -> 158 bytes applications/system/snake_game/snake_game.c | 434 +++++++++++++++++ assets/resources/infrared/assets/tv.ir | 110 ----- firmware/targets/f18/api_symbols.csv | 5 +- .../targets/f18/furi_hal/furi_hal_resources.c | 17 +- firmware/targets/f7/api_symbols.csv | 5 +- .../targets/f7/furi_hal/furi_hal_resources.c | 17 +- .../application_manifest.c | 15 +- .../application_manifest.h | 25 +- lib/flipper_application/flipper_application.c | 14 +- lib/flipper_application/flipper_application.h | 3 +- lib/u8g2/u8g2_glue.c | 2 +- scripts/flipper/utils/fff.py | 5 +- scripts/infrared.py | 79 +++ scripts/ob.py | 3 +- 81 files changed, 3045 insertions(+), 149 deletions(-) create mode 100644 applications/system/hid_app/application.fam create mode 100644 applications/system/hid_app/assets/Alt_11x7.png create mode 100644 applications/system/hid_app/assets/Arr_dwn_7x9.png create mode 100644 applications/system/hid_app/assets/Arr_up_7x9.png create mode 100644 applications/system/hid_app/assets/Ble_connected_15x15.png create mode 100644 applications/system/hid_app/assets/Ble_disconnected_15x15.png create mode 100644 applications/system/hid_app/assets/ButtonDown_7x4.png create mode 100644 applications/system/hid_app/assets/ButtonF10_5x8.png create mode 100644 applications/system/hid_app/assets/ButtonF11_5x8.png create mode 100644 applications/system/hid_app/assets/ButtonF12_5x8.png create mode 100644 applications/system/hid_app/assets/ButtonF1_5x8.png create mode 100644 applications/system/hid_app/assets/ButtonF2_5x8.png create mode 100644 applications/system/hid_app/assets/ButtonF3_5x8.png create mode 100644 applications/system/hid_app/assets/ButtonF4_5x8.png create mode 100644 applications/system/hid_app/assets/ButtonF5_5x8.png create mode 100644 applications/system/hid_app/assets/ButtonF6_5x8.png create mode 100644 applications/system/hid_app/assets/ButtonF7_5x8.png create mode 100644 applications/system/hid_app/assets/ButtonF8_5x8.png create mode 100644 applications/system/hid_app/assets/ButtonF9_5x8.png create mode 100644 applications/system/hid_app/assets/ButtonLeft_4x7.png create mode 100644 applications/system/hid_app/assets/ButtonRight_4x7.png create mode 100644 applications/system/hid_app/assets/ButtonUp_7x4.png create mode 100644 applications/system/hid_app/assets/Button_18x18.png create mode 100644 applications/system/hid_app/assets/Circles_47x47.png create mode 100644 applications/system/hid_app/assets/Cmd_15x7.png create mode 100644 applications/system/hid_app/assets/Ctrl_15x7.png create mode 100644 applications/system/hid_app/assets/Del_12x7.png create mode 100644 applications/system/hid_app/assets/Esc_14x7.png create mode 100644 applications/system/hid_app/assets/Left_mouse_icon_9x9.png create mode 100644 applications/system/hid_app/assets/Like_def_11x9.png create mode 100644 applications/system/hid_app/assets/Like_pressed_17x17.png create mode 100644 applications/system/hid_app/assets/Ok_btn_9x9.png create mode 100644 applications/system/hid_app/assets/Ok_btn_pressed_13x13.png create mode 100644 applications/system/hid_app/assets/Pin_arrow_down_7x9.png create mode 100644 applications/system/hid_app/assets/Pin_arrow_left_9x7.png create mode 100644 applications/system/hid_app/assets/Pin_arrow_right_9x7.png create mode 100644 applications/system/hid_app/assets/Pin_arrow_up_7x9.png create mode 100644 applications/system/hid_app/assets/Pin_back_arrow_10x8.png create mode 100644 applications/system/hid_app/assets/Pressed_Button_13x13.png create mode 100644 applications/system/hid_app/assets/Right_mouse_icon_9x9.png create mode 100644 applications/system/hid_app/assets/Space_60x18.png create mode 100644 applications/system/hid_app/assets/Space_65x18.png create mode 100644 applications/system/hid_app/assets/Tab_15x7.png create mode 100644 applications/system/hid_app/assets/Voldwn_6x6.png create mode 100644 applications/system/hid_app/assets/Volup_8x6.png create mode 100644 applications/system/hid_app/hid.c create mode 100644 applications/system/hid_app/hid.h create mode 100644 applications/system/hid_app/hid_ble_10px.png create mode 100644 applications/system/hid_app/hid_usb_10px.png create mode 100644 applications/system/hid_app/views.h create mode 100644 applications/system/hid_app/views/hid_keyboard.c create mode 100644 applications/system/hid_app/views/hid_keyboard.h create mode 100644 applications/system/hid_app/views/hid_keynote.c create mode 100644 applications/system/hid_app/views/hid_keynote.h create mode 100644 applications/system/hid_app/views/hid_media.c create mode 100644 applications/system/hid_app/views/hid_media.h create mode 100644 applications/system/hid_app/views/hid_mouse.c create mode 100644 applications/system/hid_app/views/hid_mouse.h create mode 100644 applications/system/hid_app/views/hid_mouse_clicker.c create mode 100644 applications/system/hid_app/views/hid_mouse_clicker.h create mode 100644 applications/system/hid_app/views/hid_mouse_jiggler.c create mode 100644 applications/system/hid_app/views/hid_mouse_jiggler.h create mode 100644 applications/system/hid_app/views/hid_tiktok.c create mode 100644 applications/system/hid_app/views/hid_tiktok.h create mode 100644 applications/system/snake_game/application.fam create mode 100644 applications/system/snake_game/snake_10px.png create mode 100644 applications/system/snake_game/snake_game.c create mode 100755 scripts/infrared.py diff --git a/applications/services/gui/elements.c b/applications/services/gui/elements.c index 891435c961b..37ecfde4c67 100644 --- a/applications/services/gui/elements.c +++ b/applications/services/gui/elements.c @@ -290,8 +290,7 @@ void elements_multiline_text_aligned( } else if((y + font_height) > canvas_height(canvas)) { line = furi_string_alloc_printf("%.*s...\n", chars_fit, start); } else { - // Account for the added "-" in length - line = furi_string_alloc_printf("%.*s-\n", chars_fit - 1, start); + line = furi_string_alloc_printf("%.*s-\n", chars_fit, start); } canvas_draw_str_aligned(canvas, x, y, horizontal, vertical, furi_string_get_cstr(line)); furi_string_free(line); diff --git a/applications/system/hid_app/application.fam b/applications/system/hid_app/application.fam new file mode 100644 index 00000000000..a1fb314b82c --- /dev/null +++ b/applications/system/hid_app/application.fam @@ -0,0 +1,28 @@ +App( + appid="hid_usb", + name="Remote", + apptype=FlipperAppType.EXTERNAL, + entry_point="hid_usb_app", + stack_size=1 * 1024, + fap_description="Use Flipper as a HID remote control over USB", + fap_version="1.0", + fap_category="USB", + fap_icon="hid_usb_10px.png", + fap_icon_assets="assets", + fap_icon_assets_symbol="hid", +) + + +App( + appid="hid_ble", + name="Remote", + apptype=FlipperAppType.EXTERNAL, + entry_point="hid_ble_app", + stack_size=1 * 1024, + fap_description="Use Flipper as a HID remote control over Bluetooth", + fap_version="1.0", + fap_category="Bluetooth", + fap_icon="hid_ble_10px.png", + fap_icon_assets="assets", + fap_icon_assets_symbol="hid", +) diff --git a/applications/system/hid_app/assets/Alt_11x7.png b/applications/system/hid_app/assets/Alt_11x7.png new file mode 100644 index 0000000000000000000000000000000000000000..3e4bf320ee313c725e228356b6bf58989e804441 GIT binary patch literal 2417 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 JTZ;Aw|2NmVF(Lo} literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/Arr_up_7x9.png b/applications/system/hid_app/assets/Arr_up_7x9.png new file mode 100644 index 0000000000000000000000000000000000000000..28b4236a292708b412629ffafe30f6d011491505 GIT binary patch literal 3605 zcmaJ@c{r47|9>2^ZpP~Xl@BrVPMsS}}K`)IgU>xHk zuQ{^ZlqErKm`jmL$vXO)Qi=!THE;DRyVh>Cu@O^m&WRUIOpLs&>}nu;QTm<4xaRG| z^LOGewyt~#yA#k?we+cd{qb9i$>Mo_d8b5;q-?6ak*i6hYyoEX*7xU|8X7;0L#(2t zwb_88WI07MXiZB5SdK6^-v_Rdcn*jJ_sB>BHTbL=*siz@g)f+lqau+PL~6Ln`yC}C zl>n>IM9e+F%2p(jpRVH$QdDDFIb(FP#G03|=i1|;y#5P&&&`q={yo&Yr+iZW$@q$~h)jgQ$2h=l<@&01Q) zz=aGz$#%}u{EvO5ij(@nN@bLpS7;+`qP!&y10_5?A-nZD98~uynUa1XWm-Y%LNe44 zQN{}I=U)LpPO`Ev+xfNN4*AlK4%0+|{0YM^FT^*%zP@AY6P-nDD**Vwjp$l8fR^u! zJRly)SiikzM$G@XOwQ@0OMYbvR*!+4sR7S<_GWEtZe6M9@1GbSe|N9}<4tPy3}2_! zov86#JN0LT`RdZ*`{y6EqY%fU?8KJe*S%VB%H7p@RqBH8(5EE3)h99=s~SDv1_$2? zqQ26Y>$bo|T;}C@L@qc1b9L{_J>46WkD~@Fq86hjz=M+(B4Npf`Nznj-yC%niQJlx zO8_ue$*O&$Cn*}~fBr)!Z)4VS%`RsT5b5V|H4p%f?2-LmfsDBTb3i#qrr&9F5V7ZGWJl?*n~frD0s->K~iJmWR}N zJe5bY6~2=svupLLqNK#En)?ZviT(gwA}E4hLllTGa5 zZWjq44||O{H0Kv&+)>+S$p@MNMD%KGl^y(ARGBOKjqGD=MZVe23%0jqUQ@X6%p{eZ ztk;}JJJFX-Z%w`~@>dv0vcNXMYCi9fFlsmjgEZD-9_}}gN+GvB1Q*K|HSTvGJ3i8Rw)M}3 z9li*79MRrDt8ZJ>$ZH0mea$iB{PFs6qjB|d%{gyrzOPl_-DUTWdTy;J52{TlP8d&!Q_~UF9(OX` zhVyR`wwfdz!Iaz*xZQV+%inH%IuqG`Ud6#Nx8(Nqo}K=x{!8@xpSjPr4qxBxoc7wY zyKTzubJ}Oo1)i*2tn&G$c$%JC)((jsG&SCi`{_>i)Os$dH4$KD@UQ8U844LJ52C(6 z|EzLytMv7Q*LAL|>q7|zh4%_a3S~UzJ=zFK1;^dPOKm-j+{X%}-lP_J6!H&!bys(% z6&%QqE2QPK2$pvvyw(!Lz3QFnU9fjua~_@;t7-(vkk!hA4KxGfiegVknKbA;Z0|pN zM!zzBO{4M>y0G9D5^HqO$g|vS{+geq#8`UZ@(r%D)TCZs+I+;t5vAF^ANQ)?Gj^(g zQ;!A|rlzG5i|mVBi|oEuo0d-J@$XgJRC=vM$y+xa)IF+eM@#D1!k={ScOTA^&Qrmo zQH!OJ!hl@$Ta`H83ufL-diL|*U{8* z#DBrhWV+!i?(MyI!0CWfQ~Rs-+wFZBCRu3sTf}76WY*iP(I-Aff{z#o@&!++4rSv< z?s?4!s+ciHkY2e&k0Zy*ZAL2_eXb}`VQF}1)PJFOb zzz~F!XuhhnCofCuXHu$D!k>lzwuY9Fi|dy!(m0|K5%h?oggT5G$?Ui>V;TN(A$1B$ zBX%lwzB3vVY;W7!K_1Mu=X%#`|=i@IWI7YWY(kviZ>W#zA)#C@bi-E^Jgmy3T zv&ysTrt=5y&zR28XX1u#zB0bKH`~i7=yiQF_Py&wm!-_j>#%^);s_V4OBC(#q!yG6 zP4+B#``}3~uW*Spt7`Ghf^&1sV$9rZ1To@u;+0v=ljbLFF7>SJ6EUOMb6OjejnIuQ zATM%{2u(C0$~wyXmzCwvvzjjwEm4EiZ)N?{)|YcCtd*^kqD!JDYD+Zzn}5GjqPaAg z-jUovmybCV@wxA{1nCp$QhkK1ZcJQ^XRKu+JD#|+3!Y}e>l(rajpDxJQgI_$G`I`$ zzTrU=eTzcKN%H}-XU5Mg8zFvPuX>4mqQfc2T}X(2sVVc+^U>Am`M8h#k1}Ins_D?? zW9*Py9d!#ac`5~vZ3d`RE2ntp{n!3wt*D=`a(U0(cHW*u>5w{&IvN<-W!e@04trF8 zxAUC6K0fs7@5xmrA=)pEat$UbF6b6qsdAEY8qPvxt7M)5F%W1}HT?Y5i5-kDcSBkfI8A=N<_dXMj=)KjKD5Ft5{a&;uv?5cB zviG%5zbbDXykd4^_U6X)wz_Q}t_pHv9X$;-h@Yy9Pa@0A149O-$CS71i#;q}Z2t73 zK%dd;QZ((ERvJ;Q6N(RrI$qlvUHe!h;H!*>^h8Yf*P*x5$6Sa|uhGY(@3DM!3+051 zrAmXUY0Br`=?w)>sK>EdUt|njdsI-=P(kVR>-L-aG-8 z_YQhjEv;F!JRkHB@xb@`^-@oYq zy3qu;q`rM$?c|$&eZJ10~S zzMj(K(o}h)GPAVeXh6kGX!YYTzojYlY_pExh3b$$R5tp0vytfG>iJOC(#xgAQI+8c zj_z7VTV+2_cc!GurRv0j)wFd#b~vur(tCaA-R#i0lQq1Y`K}?mCGnW^o$JYqNeb94 zNf}9Pv2w9rv-evdksmENYg4Ov*iK5PPPXd$?e(@&RTXH&a_`r-9bM^Nx6(?43~sm+`Zpb9x*8e?DAvf1S6IqLz}f zAtstWzdCDjEn4_rsm8S-a@|>eTpo!-1*|D7UnJ$N^L?$d^i^GtuDL$`@b|oq`5?n&4r0HkRs7w-4n| z-9w!Tzk^;800GS7)gaQmImjnuCoMHx{g3;i=bWy_frWpzb{RQC$puztMiikf1 z!m>D2kQoGSNQS{+ATuO{N+BV9jr>St0}uj+fJ5QJ$IK9JhC&#j;7HKl11xmNq4=TP zaJGND6YkJpe=e7eftxd%|(NS!Tu);2KygbX3*c264neFOkzXf5ZGo`KY)1r|AsOc|Dc1o zZq)zA`~M0D5klBhs2eqib(%vKo}Hi8rYklI%b}9EEDnLiI`yNFhx}PwR**l74MG?} z;2=FbiA-m1TK4`$!Q)X5%pfj_Nv1mB&|skmgifcR%;2U*FcU1!2#Z0&;WoJaSgaY= z2xEf7?Z;qEk(eJ`9E*IKL1l7(a4G-g+WeHe*$@o2&@+z8p`W2rY&k3j=&!6%^q)gyir`390UNO7E~>RB=X1PyRqDR|dedGy-I3dS}z{FW`l zMNSyxg1Htho2ag(A|ib>R^?v5oOA7N3kw0I=B!x$`1tVaa?aY~S4I1TCROgoUw#mK zwRK}G^nw3}s#n;`x{ZyFXoSYG@prgqTH$sxbj+ z;W8hUz)e*?U_A_lIt;E6dIj(W^@s@rHTD@bu>CRHQeQA>C-}mz@YS#rjctX)WdXC0 zcuWppX2}=MO;vXVvIGFHHj?)Q;G_e1X7n-P(cap^a)mB5Az^)lz1AwJU zM(uk|Vg7Kx%VV9K?M2f~tE_`SxUbF40020JQ-k1J%S@Yu0RWd3q4n5YX{C0rc8%cv z+Fe7nV&AM+t6QJ?VrEU!aFkr>VB_Q%RvUeNbu%KA0Ve$h!xNl2aB3rRFn z>KjowvsSYzLPWs4S$GdoWgwQ%`zk>-URWV5YF(w)T0rKS8mJ{!)){P@XkZO@xrzt5 zSt~E0SwA6SPFTK7Jkkv4Mt+a3vVz}=D0N1^7k`GW$TQk^#qz$`J0CVYJwZMz;~nei zKJ<0Ndo%9}{iFsGOt4L`n$LTM^cv2>AdU5yC&t<$Nu;(X;3DzD#(j^E74cWbt&%#Q za0Fx`ENVmy1vnTG@qoEC!H(e2XPpPyucp6yK*UId|B7>+1~@6t_Nn^I-M=^N_11;Q z5UjOTKgcBPfl7zQVjGOqWa6;88WlHwvU&0l-!0Q^*-dv*oz>3I(6`>Fn$$Aj<6kO- zxTOs`+#EH@ovfeKn^c-qS@IO+dYc72Tz4JUbZI?vRB=jrN`Fd_oT_W?_8{G5IPV^Q zw?V>jO!2*Pmq*Sqd3*HFr6bxe%iGvy7vI0#v(Hb#Z;krsGyCQ4;oAosQr@|Dx6N98 zPWjBg!V#B&r?OXhRAIn@@G9vcyo=1oU6PH0$B5;}HqXI%SThjT@9U7hhidWfLtV5z{YOsC-;GEbu8y7I_RglHPG=!Sv#rmE>6{h0rP8 z*{3&AzNhU_1C{HV(PKqXpi~52UXHyMXB*iDNil(BC^Zf@S5F>guLhhP3+Z0vW|U>r z&F2k1S}P^O1o;Jf-}>?h}`E>p3)w_*OHMPZIu#|X-^8C56=n&@8q z@$vI)PQe;+QNiS^3G42J$pp%1M0dpF^jo8v=grUC9P1gGr=v!(msGcXwnMhNfZXtd zd=&n;2=fTfpElM*E~vbYH$@JTzn1pTn_thWFqbn=h%Anrsx4OWYyR~{vC7&^YDZ!R zRWiyc?DL0rLd0p}wfZn|ji{I?_h{32W-MV}7d*v)(=~(*9L0UZCF4diC~!x_Bb}oL zS|$aMGpGThm-;VF8zH_PZ+i(`g3Vdm{RoIwi6Q;$tI_ZC%Q55Jaj}U|g;Z$sNoMf9 zj=GhoT={&6j5ada%r4f!_}0J7rM2?puOD36!#Nl)8eFGbM*%~-47+0cuqU(*I4oIf z*@xWxHL=PdSnZ8ow)RxT6^;BGRdy0~!x_j-`SkN3nl2hy4ZnOd@kRiqK*c_(obrV- z?R&nhh#XbA^@e`!IrPA7p%(wL8%4W3bVSQBIiK;zH9u+zl~Ty=zOUQkS`o>GnTOlw z-hs$i-bPksVY> zk-OBVITSRd6vJqJoi=pqX?|ftg-@q%x9{xqh)$-bWO6~ubc!ThqJQA2#OSf7^Q&Ji z2B9hKnuC>>%dr&?UZY-Ak#k!*+K-sxAL3W=-|&VD-NVm_AJ^$!3re9?U-f_O9rUbP z+car;HR#6YX5Z`EOWv^AC|ffvi7S|0Pu`%NEOwv;%s26O^KS~NN|t}Dc;BnsjmEnq zd^kL3CE4`zt1a##M@Pa?!tIwkjpM3JT=3-Vn#kzd0SV;5`Rk!YV?sSYpI4?RL(gE+ zm(ndWT+=r^y**z#zBTFk@MR?AyVc;&Qg`%G9>GVK@h#MW*~p$G%2MZb?rrYHFv#yi zUW50`LuW`Gqi3WTi!Y_wW8D_p*Jh4X9qBl+^n$%qIykk*{e^q_Bjjn?7xov_R#J~+ zQ{|n?^pc7b{uK)$)z3nG*JhP6jXH)`s)K)%-~P~>i9iomFNZMJ-mI;T$`6OJG&Vch zD*HJa3&mBARi{_X=FR)D!!f<4o?AnGi$j;r)NrzvyN0aR1fwo@ZY8cJNMUy+q$RXP zOGM9Q8k-;x^jrm&65J!3O!Kjqu1UA9m4oPCr zAjBOXNDz(5LjwTHG>Azg`IFfoZ!(2SM}rqDUxPtZA2itAz#eAL#FG7})*&piYls7$ z6yi@p_<&7KK&T)jkAOyI6G1_=v-Ch@5E}dkFOs+3F+;(iKU~=UXz-t+2=-1OEQ3V` z8A0GWBp3_^GD1MeK15w_JzpY88>9=W8Df{r`8R(f;-hWV?|6 zqxT<)1M$I3GSr0}$T-I$@y^aybte=PiDi+AYz7O@V4VF?NGCrAn-S>8V1jh@AaIbT zJ&{DE?^q7~0kOA7+Ry{pL^_FVgF}OPBoHdq2Wbp5#~AYlILs0bhg;yx4UCM<4Y3%w z0TyRyY-#=ji(`<^(a3c653J9Bu$cde-DwCKlNT9BW>L?ReJoiF8t9L#k<@?CVri+5 z(>FGv;F0+i-FzIXDyH_N{#SL z^YvxW03Nin1C!rAXaP7WMBc(j6m!G#0-up`AMk?0U7xv`NbLe1qw#SdWH%b zzKO}1c_0x@pc3WdxE(xJ7xz zP+tN4r(cm+pl_&Wpbs}0sL=-KM=R%|)WnkfqLBRj96LgRY@?5^1L_1DeUQ75+zAN; zuqZGT?6`nBVIgYAb%S||8!(VPJY5_^IAl}%*|``Lc$i=Rx6f4*(G6O!%$6?Eu2`g+ z_E)I!3HFqj;YoHDIHH2#}J9|(o>FH3<^BV2haYO z-y5_sM4;GPjq%Ck6>60csmUj6EiNa>ORduPH4*)h!w|e3sE@(Z)z4*}Q$iC10Gods AV*mgE literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/ButtonF10_5x8.png b/applications/system/hid_app/assets/ButtonF10_5x8.png new file mode 100644 index 0000000000000000000000000000000000000000..d1a7a04f06dcc4b5446aad5a40a9863038bf56b5 GIT binary patch literal 172 zcmeAS@N?(olHy`uVBq!ia0vp^tU%1c!2~4tO5(ej@)Wnk16ovB4k_-iRPv3y>Mm}+% zA~jDJ#}JO|$tewu|Ns9tHZU+a6e)0^L#NYq9;5F(PTxtKzKT}z3_A)0e;QviHwNlp N@O1TaS?83{1OPU#E%g8Z literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/ButtonF11_5x8.png b/applications/system/hid_app/assets/ButtonF11_5x8.png new file mode 100644 index 0000000000000000000000000000000000000000..7e177358e81695342f2a283a220c7cacc7bda939 GIT binary patch literal 173 zcmeAS@N?(olHy`uVBq!ia0vp^tU%1c!2~4tOpBPSmNg(OQ{BTAg}b8}PkN*J7rQWHy3QxwWGOEMJPJ$(bh8~Mb6 ziqt(_978y+C#N(t{{R2q*ucQxP^7?t4xLU{IYmyznHN-MN(3-k$uq=X7x{LAUCkJ% Og~8L+&t;ucLK6T7Y%hHP literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/ButtonF12_5x8.png b/applications/system/hid_app/assets/ButtonF12_5x8.png new file mode 100644 index 0000000000000000000000000000000000000000..50d2a7dc63b9d366ccfbacbc05e6bb0d9e335b5b GIT binary patch literal 180 zcmeAS@N?(olHy`uVBq!ia0vp^tU%1c!2~4tO zk)EfEV+hCf+#W|R1_Pc$J^%l2ww|&zRcE|OcGEe{j literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/ButtonF1_5x8.png b/applications/system/hid_app/assets/ButtonF1_5x8.png new file mode 100644 index 0000000000000000000000000000000000000000..7394d27105fd0a27067803bfd633a26bedd0f1d5 GIT binary patch literal 177 zcmeAS@N?(olHy`uVBq!ia0vp^tU%1c!2~4tO5(ej@)Wnk16ovB4k_-iRPv3y>Mm}+% zB5h9>#}JO|$tewu|Ns9tHZU+a6e)0^L+9jy0|#0HPM+X+s?4mGa`wiPi$55Iw(~PB TTpxE5sExtX)z4*}Q$iB}`k6I% literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/ButtonF2_5x8.png b/applications/system/hid_app/assets/ButtonF2_5x8.png new file mode 100644 index 0000000000000000000000000000000000000000..9d922a3858147116d65b6f03e2b36ea846b2f60c GIT binary patch literal 179 zcmeAS@N?(olHy`uVBq!ia0vp^tU%1c!2~4tO zk*=qUV+hCf)NV&U1_ho&w~qX;m*hWYIaS!#v1}4USoB8kx*gxNJa@^Tf4zarr_o#d UH)s04hd_-Cp00i_>zopr0BX-NbN~PV literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/ButtonF3_5x8.png b/applications/system/hid_app/assets/ButtonF3_5x8.png new file mode 100644 index 0000000000000000000000000000000000000000..95c2dd4f4198e182a1a62927c4d3627400a7b883 GIT binary patch literal 178 zcmeAS@N?(olHy`uVBq!ia0vp^tU%1c!2~4tO zk&dT}V+hCf)GkXd1_g%0LLdIeuWPOlF*aSY$&;z#+9C5#-hV?Uh3H=_oX_AhhhO~n UO!>#_f%+IcUHx3vIVCg!073*Z7ytkO literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/ButtonF4_5x8.png b/applications/system/hid_app/assets/ButtonF4_5x8.png new file mode 100644 index 0000000000000000000000000000000000000000..602466f4b667b6df4d5335517bd9d43e5f0b6e69 GIT binary patch literal 177 zcmeAS@N?(olHy`uVBq!ia0vp^tU%1c!2~4tO5(ej@)Wnk16ovB4k_-iRPv3y>Mm}+% zB5h9>#}JO|vE3Va85}s64*o6Qr{DAJBNr3n8pa7vN_+nsOQj%x4ZT`lf}PS TtvtgA)W+cH>gTe~DWM4fb!sy& literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/ButtonF5_5x8.png b/applications/system/hid_app/assets/ButtonF5_5x8.png new file mode 100644 index 0000000000000000000000000000000000000000..d73b5405275f6c53f7a2f157df063db1ca351caa GIT binary patch literal 178 zcmeAS@N?(olHy`uVBq!ia0vp^tU%1c!2~4tOFVdQ&MBb@0Fom!%>V!Z literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/ButtonF6_5x8.png b/applications/system/hid_app/assets/ButtonF6_5x8.png new file mode 100644 index 0000000000000000000000000000000000000000..c50748257ab8e06f90007e93b913d5be4999d096 GIT binary patch literal 177 zcmeAS@N?(olHy`uVBq!ia0vp^tU%1c!2~4tOA;}Wgh!W@g+}zZ>5(ej@)Wnk16ovB4k_-iRPv3y>Mm}+% zB5h9>#}JO|shy5|3<^BWD?a{@Kh_*L{p89i1p$qBwtDvdG5Wpwnq5@=JeG)jb@A^; T#rkJ}+88`t{an^LB{Ts5$FwwN literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/ButtonF7_5x8.png b/applications/system/hid_app/assets/ButtonF7_5x8.png new file mode 100644 index 0000000000000000000000000000000000000000..396c98f5104f94b6310593ce6c7e6ce3d2369ef3 GIT binary patch literal 176 zcmeAS@N?(olHy`uVBq!ia0vp^tU%1c!2~4tO+nf~Hxmr&P}udD~(3jVRo RuLQY`!PC{xWt~$(69B*FH3|R# literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/ButtonF8_5x8.png b/applications/system/hid_app/assets/ButtonF8_5x8.png new file mode 100644 index 0000000000000000000000000000000000000000..6304d7fb888b2cf38c54e7124aaa604d1610629c GIT binary patch literal 176 zcmeAS@N?(olHy`uVBq!ia0vp^tU%1c!2~4tO5(ej@)Wnk16ovB4k_-iRPv3y>Mm}+% zA}voB#}JO|wVjT93<^BWEB^mCmh0Jd#>H=GOE1@xHLh7tre2KydVRe*qgvi_@vq`% Sf29C*F?hQAxvXEZzkxv|` zNY~TFF@)oKa!Nzv|NsAu4GatpMG73~&^g&~GSNx=@BjbyU3DUS%F5hxSQ8l-I!}xL U>{@7g2&j?4)78&qol`;+0Ic0IyZ`_I literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/ButtonLeft_4x7.png b/applications/system/hid_app/assets/ButtonLeft_4x7.png new file mode 100644 index 0000000000000000000000000000000000000000..0b4655d43247083aa705620e9836ac415b42ca46 GIT binary patch literal 1415 zcmbVM+iKK67*5rq)>aU2M7$VM1Vxif;vTv~W2u`S7ED{V3s&&L*<`XiG|9wd+THd> z5CnY!sdyuJtrvQyAo>KpiLcV|{Tkc)riAbluXfwSZCApL`ztB&p zx6LGKvks4K_4~)qD&oGa-YdJlW)hAKMNJd7<=t?6c^RI1>c$ifyjaM>^|&8!ey zB4!nh9u>5uen6Ve@<H5rru6h<2Ef#GQdQ*CmZOlQi~N!?9H`Rp;C% zU}CB21#?;r`&0|6C0}b-=jODa5|nEJ#ntxQ&{~jpgtwDta4hftr~G=#p@V36e4Zjh zq%J~{y26Jjn=1Nw-l*3%QW5YFE*v4z3gt0$&(*xf2en34c?JpH8+FYldo+Alvg8af-pG4(=!fyUi-Wsg z`g#n9VUcf(DFr{poMSNzw-lz>w+HV+n1ELr&SLA#LHUb0p(xWQ(1*vJ-i+1!`swxZ Z!O7;c$;lT_->m1Ovaz)0yuI`A$q$F8u*d)a literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/ButtonRight_4x7.png b/applications/system/hid_app/assets/ButtonRight_4x7.png new file mode 100644 index 0000000000000000000000000000000000000000..8e1c74c1c0038ea55172f19ac875003fc80c2d06 GIT binary patch literal 1839 zcmcIlO>f*p7#Yw)M6zw!O+@VZ{?d|D~WYi~8rHRY?X-&T}Yen`g$^+EJ;z+|RV zE@PoDvZ9%#+_}3bC_5Cj8jDGq541mi{7F+&KF}W65sr$Xn5H|YrMQ2(J7%Yc%;(zO z57ax000=TsQ+1Ke@+w#iw3au3cGGQWY740k2ijH>P(6tD)S)be>gX6Tj7`<`b>di- zgWp$8Y+?i31~CzF0&E4uRlA=C(Mp~K`{74jEchB|)4DDK!ZVhSwdFyw0YIZ1cDh0S{OvfO-U_~ zvmRF*m9sWDXNH)GOyqS1Skhxbr6}s*7t&@~kFM(NW5}qh?Lu@lJ}HE;FDiLdGO>LO z5pS*%E2grR)l^;|?O5b_?u0me&c1U}%jrk8*%=Wk%i)8yp2P|kuxmKg<=(u_`oQRI_0 zS`-DNysBx=#3&qSkgA@hJP>~D+ZM(s5jI6Owp`?yE=3e`YGUqkVOp#Cp=3wR3O4hX zX6BLsN3UBzV(vI5;|SZHgOb=HD0VFjpTyfFW}GnQuh>2*Q`k>*cAmA#iUT7EXSpo# zkPm5~#I-o^cpgfe#P$=4-Pi*SpT!-@nJgp8L347xe>5EKl`=_ZFc8XGy+_j=_R_7! z@vZZMowS1GJ?Zw)eetks%~G{BTR>T}9|jt0j3Btyb*C3-`C?fwY3EY`q*oYZ39DpM z&uJ;PCZPLs4QO1Jd_|A1PF)azZJ)RZ`^-VMWr6e#XUOA%3eLG_Ch@BDOHzMk*MF0G zCo7xMd?Mg*HMIXw%nNz?%60fZiZPlqb?GqUpXO`F&Yi!okZl(n>P@r1P2i)yk3DgRwbHeNn6e|;J^SK4TM LH~i+q&mR8;k>NTA literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/ButtonUp_7x4.png b/applications/system/hid_app/assets/ButtonUp_7x4.png new file mode 100644 index 0000000000000000000000000000000000000000..1be79328b40a93297a5609756328406565c437c0 GIT binary patch literal 102 zcmeAS@N?(olHy`uVBq!ia0vp^>_E)I!3HFqj;YoHDIHH2#}J8d-yTOk1_O>mFaFD) zeWb+ZHz{mGZZ1QpXe09^4tcYT#4oe=UbmGC^A-KE*|F&zP#=S*tDnm{r-UX30HgpM AM*si- literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/Button_18x18.png b/applications/system/hid_app/assets/Button_18x18.png new file mode 100644 index 0000000000000000000000000000000000000000..30a5b4fab236d8b57242559ef94fb1c5dbb5d10a GIT binary patch literal 3609 zcmaJ@c{r49`+jVNvSba(81Yt?8Cx+K+gL`~8rw)>jKN@*W(G4tN=nI=Eo(wa4Q%8vkx{u?zYHw>PBq%Eg0DzDc z(hS9!#kL=Q9?lyh8i4}IOAwh8Gn{vj+=2WcHX}wv6 z{-S5$q3oHN^-t@S6WJ3RZH#u2$UR~zN#ptcfIceP0M?_BV27-0s*2>6L=N(TM8}(7 z`|{NTz#I>Q9zlC#w88a|1aJf7E{y|X4MV@8D(qEU08kPz2o{^z#g&Kx8Z{gnC4k1g zz$1sJ-hx0100c6^Ou@i?Az=E4l_4L{Q=Hr{4fN#iE9M8{xPXj4 zwXcCZrZHH9x3-ik()GEPC3j>M9}pamP82cr1R^s`)mi|M9yfs4FW$-nvgXNycGe6Q zdyu19NG_nZIkh$YM5nd{EA_o>$im#H=N(jFoW#zri2 zzHaq}&H-mLjWbGW3!*m9Vu-<|sQ8IyUQrUuD^Y zZ5kLaP)TNrO{v3TljpVO71A~Zl0$?5=4HED+vhu;fXTOrK ztd-`*>@YLleW2Dr)O5#aVKIBW;(Net{L&fmykHDc=SE~9Xfj6PB)GnjQpjCw>YwC} zR9aA{Na)9%HeO5YYXoUs+qhO~shM)&$w{7%+(E`K?kUJ#dz(k?py`OXN2cWmbjX(N zhetloFX}k)Erp$Yef^5L=T)?gtyCsf!S5mvbxHH}AK>JBc4f+;Vyks@FWBQm zv;|XTR&l>#uJV~bgvC9Qkq3mEZj9OrDk>*xS?#h4K=vWk3mpm#J4Nx?)+$qpgr={f z{7)j8p!B5jM3F?h8|zJPM$08&^)bWN0{I6}g(+gkb#X>xymxMCnP%kOKiOKG`;q^C z4D8k^D?(ndJ;dQkvA9l9rgCeR6r#CMy`bxTCf*mn;s=?eRS0~E+HaozKD{&G+s?^} z$*3P8yM-F2nbx$W4+H`tb7MFv+BM zVyUoH=hTSQiTjRDR41b@#{FH651d3EoN*4nYvJ_Nexz97qtt`0VtJ>R#YalpP$8%U z`}UI_1=Sv#7uT>tPcBDWD+@7pXTL<&4 z%LPNuSvw%8_kEZ?Nj^E_XIr_1-##9k)Bl`(yiKu9sO_9OkGhfi<8J>FpOT1@qrIWM z)xBOblo_d+sa|#vImb9hEoTWvfUN`xR2-=|SrJ{)7u5dU@B?;=F)6V0Zb^9ZONZqW z;YY!e^mleQyF=k9REPgaqD-Ks9(JxJ5&JFRCZ5$XcWLO}o@T#_q&mNX4y%GcSSqtu zd`EQY(uO`v(mpSy&R1N2fC0t}uhmyrS6DwQhyL`(& zG5PLev}0iuT2M=HAh~j?a7gD(ab5A7Nf%!^-`mujMP2E;ClZ^*(u32b9SB9&iio#D zn^VVRXDd3NeOM~UdYRQ<@|p1QOAEX{{K2}7MwVQY`x`jhf8pan$LN{4B@!7wn-ktw}#xeLT_EEzFQ3*fLAL; zbVp=F?A*v*KepDqneek_h_N6wZ_DS&^@?kZtLlR6g{M3LJPN!Symxl$^2PDJ+yU8b zC~3M|K*&{rl1!?VUXWYGYWMr9Wp+ruL) z{+L0_z!;VSUM53&HC*D*VXgZb-%pk~(9Y6U)Vi6YuIs*4@$(7A*Iyj#^M6hW_GS79 zq5`qgS*%Fbebxo~m7nJG>0&hT0|GNwN9%g(;8#be+!KMB+S#L-j%hS(=~#dM3+eI6 zw&vUr16N(w#4x?+n_}rtjK-osruLA%c4I|E8+q}COIgu&=GFOe`6nNjvyL0w7|(G| zUDo?@EF7`sciGM&=&iPZ9ZHpvBy;11(xQ#CS@&0F`{%Qt)%8=dQ?d(CLin^Y)lbm! zgXMNUs;bFCql|IFJGta5?^Z^YR;i19l7Z3I9R+2mQhQ-3YsfuSy4zkiIty8aJoQm~ zz-R0Gs?x5DQejnzkL+2Gp7yZluJeQ78uOP@O0f>oAsU+Qs0wd7ey%gT*{}IY+NS+5 z8s)U$&*)!>M@4nsxr0!>=%SNaoYK@xEd6on1y&N1>g~k#Pw#SbK7Uv`)q_c9-Yfn2 z$bvOK>|*QD6}H46^!9!|UjA-o3OQ9cMP#nH);v63nqiQIhLn4AaU_*dHP zQ2(X)*0R=jtvtFI-5Ix*=ghu^+eZqPLvzl%H#={ZJSeaJtkT5nouAA$Ik-3Fq#d+qrDcp7N)W0{b7<)I1R& zppL}tN5aTsS&^jPteMP^XXI0dgjgRTXXGub%YQ|%HAk>P4Y~;~xp_GU;q$Ab7n4Vdyo+*kY>nU_ zGx`}T)*BfC?kC-=d=c%rM$)ud>vE5krp2!l3GQ>1E|a6_gjoA_Sa-zzYeJtgQrJupeGtwb~ zv)29Yp$YVd8`Zs=-*>Kwd_P~d^%z%682ss3>)HOsRfH`pa3yyu<=2NRL!Fi_mR(8~ zN^uD}3JP*UvQ-P-ZOKDLPm09b-$gk8VoXsVObl!eub*f~Z}iOVT8(Y5DP-hN@s|B!#~QYw=)K*F;Y8Th24v;Z;(DaM z@*d7#r3}p+O>-dm&_Xa29AM&2^1^|v2pC@+3WxD#oNdAx0055)-Vseh+gQV}B!UKJ z+ed>=Aal?FU|>WiW3T}@8psRhizmXt?3XoQ5Z)UOcG0zg+K>@AKRhy&f^!J9b;O1S zVD-JhMus2*I*da=z|k-uIw6oqh0)>QKY3xC^|l!T2L0(m3xI?F5{0(02O&rl9O$Tq zraBf1g@TUiYj|V4Fjy}yHINomOA`XsfoSTeL!mHjeVC38=&Lss+)~Qs;Q6QyD}WhOSPeD*a|K!%?vmJeh_k z5kcFG7%x%~4G!i={VN9o`5#&$_3v}yoEU_TAwx7ZpxZh9cC@ki|6K`$f4r$Q6z;!z z|CN~P$ROh&C>)g(M8R?@=cBY8iVQ=&_Npv z7Ej!^9QqStV*|4yQfU|>7H4G!2Xja?@OW<+RM*_}sEH{;SI0tMQ_~z_s%xQZenj8Y z#MBIGc2r;QH`a`V4IPoKl5_wQQ%!g~LUmcOwk{}T)0h=FX^_W#uSw~5n0+sl7im$Uh&`Ef)}$5S}1 zy?{b{ajwM@~ literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/Circles_47x47.png b/applications/system/hid_app/assets/Circles_47x47.png new file mode 100644 index 0000000000000000000000000000000000000000..6a16ebf7bbe999fa143c683bef713a6e2f466cbb GIT binary patch literal 3712 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 biU<_Yn+9Io=xs3K`2!qm58ISjpSt)z2v?8| literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/Cmd_15x7.png b/applications/system/hid_app/assets/Cmd_15x7.png new file mode 100644 index 0000000000000000000000000000000000000000..a63a4be747a89745cbb32b5c9fb55d754f600dae GIT binary patch literal 2426 zcmbtVdsGu=79So3iM+POA}pt*Ns))-i6kTqu|NWi-5{nsTv^1B3}k?0(##-1d9^^x z3RnxPRtt@fibYsq1q2#h1rb5^6xgQ15d`EVT@a)$LKR^r5rI9}?jJjIX6DYFdw=)d z-}l`+e+~%_vcfsy006KOyw4M1PaEtq-(ZH_yL0Sa0l?H63J3^E2nY&*6JgbdO0fhC z0u`VNl8A!*0l=rAAS_De6lTZ$s-b%8JKtyNN=pl^7rp;dlPK!SzB|4MR?GW5ixO-6 zjNxQQJvW-~>2@K1vAH+b`~mUF9#eDVQSV`n_qTs_C$#h1 z809B}i9cM;dX{mSe=niAH*4qT)zmb*0h9VC$30ZX?tf;h2^;A{b1TVE^-bF)umQ4| zAp%j_D>*9M47XVlcJPYRI9hgr*>!#EdEJRP+kq@~QX}Vf%M@>+mKI%%OG+0NTaMYh z1q=>(kg5*M5#Op3LLRy81rajFt)Fw}>&M=VHBOr%#X;=uPS&~DLw=r9dBZ>dF8n;V zIil?A zGMe%EP+zOCx9GN!=9ADjBX}DqHFDqC^Ek(G z+3E3fveWZF4o(gtoiG)>FGh*vDrQ)J~yofff5qK+n@ z2nw_2od`uEREM|E;tO5dK9uH5yVyIdy>A^OAkxPTUAhz_qDaHcyOKAa?ANjwC;f7; zrsL)g4b!%#qo*U`_R(&|UUr%yB2OC_uF>8>enQ`gbBrgUt#@@Ls*a_m=m*_Z(GQq= zEEmnwO)@5W!|S{!w*4a{(;Te?5}1K+!qAGaVgiJaGBQlnG1aqYG@ zDYF~~^8UVSaz>~6=~BD(P(D;rQ+x7Xhi{8I_1(EWmsvK06(*g}WbKU1xZ$hQKn1r{{Y0d8ZH^f* z_W3dEMq|WI$BuI6iP=cC!lfVRNxiyv>DluGX^%~G13>+$C6gr=^~Rf$JMGwj$Om@| zVueD$1CuuZCPuFVCYWS|oq!PyFvesKcF{kB08@h=02J60@RPzZAU;F}qc9?am3X>0 znTmI#Q|Vq*9~zS^C)4RHDxT&ojg`=8?s&YLP%MEIdSiD0Fg|6xf@X8dWJNaDBS$ge zkFkkGfkJnqyM=#XOJF%!tdB-k%awWqfTLz(q8voUc(puHfw0xS1cL<|ll5u}0dFut z6MPA~g(3I=SOwx4WGb0T;NtLjJVzysWs7)$%kJ2lFF}T)N;ZX(oSaNf_9DZoI0}u$ zVo|7c3Y|{EEJ#R-0u`%C3WWFqy^GhUgNro zyu$8ksfKoVu}k&zvIA707==}#Fr3K!KU&-n>A%3%WTD?P?42qI+Ztkh*KlbZ$~xV$ zJ%^%isWmP3qJIrqtbw^WEYiziz{SOuRSshFAymNg3*GEca5xVK*xH(wEG$tZI^C|J zL$f^%z2nt{Ga--b{~ecAmiV6MR}0tG@eKBfEez4@=ryZ5nJ0OpR!6h}7pY?r@<$Fc huhluV`!L-O&R7cBE}ON;j!OLi67Yk07yb8r@*g!;qu&4k literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/Ctrl_15x7.png b/applications/system/hid_app/assets/Ctrl_15x7.png new file mode 100644 index 0000000000000000000000000000000000000000..b15c249b2e897430a8062c07937f058f7e73208c GIT binary patch literal 2433 zcmbtVdsGu=79R-9BO*|XrLt^ElOhkv6CorGaRUK1?FNzZP+7!~3}l34(wRYm501vCmP2ndL{yrdq5DkeKY1oo)gKX&HK%$+;;{_gMI z@4I*Y9vTw7oajgd0ARW3FTybVX^USLmge}qGsnRd0L*NlfPm1%fZzZ)2}V9rNu*#f zs00y68W!vi0KNqU5ixS72z&mwH5F^#`z1?PQc`H6_x?v^Qdmd!tr0y~q3H1}N~#p> zX&$?M5L)duIC~6o=i3YH>c8(V>NiB!yS}@vbUcrGq%!n~S(e*O`;*|q*Bq=yYrHCI zi>ebZwD$ZqTs)a^uNR%@UACv%GkRaIi>asMY3kkU0~J??wOTG#pWePay`8$|%e@|Z zSDXBCu;JpZ@kvr+Z|CFl(}sl6)1kp8xwmt=%6wbM10bMZdF5=(PBX#NCacc487uAk zNyVgr6_=}ksJFkfeQ>tzz*nr?*g#LWQ~7#rcdo@f%Hz#u7U+ZSFL~bI|KLt;4Q%!) z{U(HR{X*8$%yWUa6Sdu0>vvZ$((U^Q)sOdkAp7p@veQHi^@gT602oAi=ILOPyTLYiXPi&Q>vFi;2k@Va3+Nw(kJ_ z10K}!9n+L|%EgdJZfil5+~?}Axii(nZ^fIYPf!yeZf85^c>EUv&xyRjU*Cv4!PiEe z{BFPhvsmhR`$iYP&$kp?V@-_Y*+);dM;$z1IZ()PSmv1TLXWJP@^8wFs&YMM7kduN z+&$3KBw@M#*Jr1Wtz>L{Q@Dp@3#CPGJ$i!ZI3hnh za$J6R=0^WmUxWK^dfz{dOPJ~AqMSks|PHKBxQ*m-q%BPoJLeS+-k{>rv1 zA86QiU2R=$i7k6Om0P&!%BZ~Bph!*aP4qtYUV`H`D%NyMcMxeaG{oNPERVg%-fXS6 z$RK2nc1ND~9$ou$Br*L*I9eP0&@V*BKTeHEl2jZ$vdKI0iK%ldkY1y%wM;ONo*kNQ zURRjdJYDF#I&-jhh=1o7RD)8o%ys8ol4^UAucU8Z7tt^U4trX zRbSpu8^)$|$o;deHUs(4!OE&{{{6+}=Apqj;u)65Th@0k9daA}airF%qr|3azx*yx z{t3&s0T$_>IdrEU2T_f=@2u zM%Lx}sP&F*rOu;|qSZ>5UZ5-O!WP5RXFJj#5_Ek)^&tbn;G$l3MS8Op9}xNAMp3+2 z40zzOB|tD)0T6J>1U~^2CSZ!oJp5wqf&jBQJpd^2Cm@i)aUdyF4r4GXhgBq&H;qAZ zV=-7>3|}Ujrl7G{90rN$EsK}3nC>K!n^+=+ltyEB05Cmdx`1YTh_E1A7?ESx@Td61 zqC?>uv5n%ta;30>CNV~%sTC@t0l-spaZv$c5|Ubxq(r%DKk}Rf7nhA{I+-+Qf+hNq zH;O|^0WboRd}s_BgUlzANF*L2i|2+3gXZ1wH$So*!&F>4JtZZDmf}T&kpw!E!{N{w zEINxt#Vx34suGi^sY;ac9OMO#5JaU2q{1LrNiyO};^1V=k4!cOS~%u;DpX?eLMSCV zpBJ7XT`f`3nKTAnp`gFY6UD;7MUt=dM8i{6AUzC3;bcS#;^vfB0pW%F-vnOeghBDI z32yu{$9thBc*;CHh*-=O!qQ{~sKi7k$aw8WLmI@R>aNOS*Y`9^KedOIqxC|ITUg z2Idp-NH2x~pLi|e*bRI>M2m!i@V8gx>=vxJM6x9084PH0_rF5MbS?FO2pDzR5MjZ* zwk&l7d}deg9ccNd!iI}=F85dO%9S@&l&}v#b~bM?zl!cVm9q)ib$xhxQ?%EK84a&h c-wD*%=rG#yo8xYw#yKPk3=y98-}>2q0D4cRIRF3v literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/Del_12x7.png b/applications/system/hid_app/assets/Del_12x7.png new file mode 100644 index 0000000000000000000000000000000000000000..fd6b3947f7d40cc0abfea296147923d198cd129a GIT binary patch literal 2417 zcmbtVdsGu=79R-Z5fRuHOJ!}DCL#~XlUEW$Tu1<;4N%H!Wif_iAR{D`P6h~;hb_?3 z0@Xs*+Qr64#VS-;c?g1QfrvbM3T&xx1OWk&E(lVOLJ?snh`=6n`^V0lnYnZ4-rxP* z`+fJ$r$GV!>xs5R007ntcL;*T1E?xm8=0AOSR^Z7w>e1AR?kD%|VBvQy9 zQbH&!4fgi|09IaJXq0?Ys5S5J)n!{Z|CG^LRFrQq@BU?Zd~iqRtAM6VKY)s_wgif`Ns|I)}IS72nUHoGK4GWt8DK(?08e@?SP)W7V!@ zH3e01S6h33A1RznyVs|l=v#NF$|dqhpS_`r?FGu+p9jlsjOcV6tS+^EeQG;p>*t4^ z4{tH}^HBY@TlXiy#=fq{m!}tE$1Vi<8)VMmimkOm=Oe#7-kQ3s5?pEQ|uMbDV& zw#65MgReGJ0O4aI^2t8U!=li*R2+yYS?Uw z3`Et`W}(C>o=zJ2LG7kd?A&?Jjvuz3?K~E1)t{kBsN=RYP6*~I7~(==LRxU4`H1CP zz`&q0rF8!^`K?kB?3~@27cTd>@muyx)yV5HhN%;jSeVn*&OQ_KnYYVC&d_gv3Omcw zg`fN8sLxX|<+637z1PQk3eB-5dcxeJ*~IYU$4m$F**5EJbM0wi6;nP<>ERU)-&%<) zvGjw3y-lK?f))eqHLdO4paQ#s`+wuc81Jul`h6a_9I|h|X>7EE?o+fte$alteo`Jvt~F0fmKP_qo5iTeoeUvO zn4dATiBvF5-}v@vFyEp1Jz1`-gR|4Z{l*cJTK2HIqchon9IW-ssm&Qp@@|}um3=i( z-gbSb*3+uHt-CF*<#3mB4<}U_o>Su&rmeZD{uSFCYrBtvHQnkwjLxr-)PBc-GtN zRT&%mbN>16_*5tQ>!ntU!Cd%wdBxZN{;Z*Scqp29k?H)p`Q0B*IF4Nzt=ZI3WKnTc zeitbHfXS*yg!4~rdXmpTYOOJA&SBg1?n<`hKnbD!iM-Y0Kc1EIfI-KlLG@vmdBBu|Qa`*Q~oPz1fNnh+HT_ z7$Xt^&bVv}5DZ=g2)JZ`pMU`aFw~C_{9=9r14fH_08rvjz*~kyLtu~`!4S0^QGrZ% zDjjrW(wVMw7Q>UOpfZ_kI>>OB#YmY9ClGWLNu;n+Z|npBh9?Y{(acW}mSqz?vc>mq zuiz7l28Tpo5u$fEQba+O=%Z0J3YFdf;A%LysDLmDs8Pf#)f|l%Y0-j%%X&491TLCj zabBbdQ4q*SPzdy((y4S3j|hSw7nQ|uf(3p{?)aM*NseJE4vm(SltfK(r6Oo7jlpKK zX>=xy$)w;G6m_x^lV~VPHTfCHa~uJrmZGoAzP zIOZk6^~q?|AvW>%nwBri_55z zplV28QZLf;ny$`KpXTDCaNxCkJiX~`7Tufmaz1NZSNN$a$P}QuI@^z zi|z1im+H&Q;iHfQL(mWeiRb+vExs7(zra?p(C-;eAPVDKL!$2*9)n9;qg%4)()2C0 zs>Po5@1hoOU>*^V^n4ibh&vkWJn{LkODOOTd1GVN!44t-niAY+7sQnTK`9ZNcc54D zdVd^D3=VgLXD literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/Esc_14x7.png b/applications/system/hid_app/assets/Esc_14x7.png new file mode 100644 index 0000000000000000000000000000000000000000..3be4691d8a3d050bf14f5bd29099705d984eead7 GIT binary patch literal 2430 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 literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/Like_def_11x9.png b/applications/system/hid_app/assets/Like_def_11x9.png new file mode 100644 index 0000000000000000000000000000000000000000..555bea3d4b0239ae1be630d0306316d3e9494c4c GIT binary patch literal 3616 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 literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/Pin_arrow_down_7x9.png b/applications/system/hid_app/assets/Pin_arrow_down_7x9.png new file mode 100644 index 0000000000000000000000000000000000000000..9687397afa81c92fb727bd40542830392fabdccb GIT binary patch literal 3607 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 QX&eA7&1_6djNPvM5BL~PlmGw# literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/Pin_arrow_left_9x7.png b/applications/system/hid_app/assets/Pin_arrow_left_9x7.png new file mode 100644 index 0000000000000000000000000000000000000000..fb4ded78fde8d1bf4f053ba0b353e7acf14f031a GIT binary patch literal 3603 zcmaJ@c{r3^8-HwB%91Q0na1)~mNA23GPbdd8kxp6Dlx`jFiT@FLrJ8RY}v9Vl+@6s zNVZC$u|$zjb`ly(NS40wes6u>A79_Op65B|+~@xN?)6;Pa}jgcMqEr$3;+OeTa+c1 zH;eLKVG#k|ciJkQClEuDkVuRz5(%QwsotajA^U+M~gKPM$^_A)v~%vnZuYc|TMKC)8`l@l|Rx4Xi}{8G%(Sf}HLUsd{w z9-R*5PEW7AU#S|;9$#%`wMj;7mDWfa%l89}u+hfwZj}UkRDDx*1ivh5KoBG~#(C}| z^b!DO1X#>)#y!(jzPnU_AE0&Ws7W^r{*0=`Xt)5NBwzq6J-(SQ5eqcxI5x@vjoX2H z4iCM=fD`}-V4bo61GmM2sc*I>LO^$Ma-TfVoxh`41c>7UGIraj@tZvbJeJ(-HsH;N&1GXq1rhMou9x4_Hqk@6ND0cWRYscu7!3!q!K0D$6h z`?GaJ)5P(yk-;(V@c{0(m-*}dGgPq2uG#+es>}R>fYjkOZjbxuXqN!3f$v^Wt$*<` zpvM{T?O%4&>lMvAD)uIHIhJL(YPK`?I;PQBd575M&C}|h*Q<4hV@-bQ4N?bU!xwp{ z>%E~fz{yOrjFP&7sI`-LN^mJQew-s{0i`UBtFAXhpIM9F(>|ns|G1XyrCHp?3Jln; zf%OENWVx#;bx3;R3~W{yqBx34?=Sojeqpf3C?AAhU_t|J&Q3!m4%thhM| zkn+)ov6cWJxpq0hOp_02NiQ4*fU3{ikKam>N52vQ0L#3yd+(VGZ+Rxeu9L`qrd(Ag z&yU|^X|_eJ&REJ~(@4Y)vFqE@%oQB#;N60c?g=R7ZOt5%DtiVs6dxauK7MwRCcnvJ zd+zh?Rp&(o%^O9w;djAfwtB{QgIh)9GvWooc$EH?h(gdrjLZ@6%SL)3f3byMk{e2O zPMa=c6nEV0M`CXy2zF`pQk4xf7o}b3P-xO2Mao8NOeT_>K8=Vx zh+u=#lgbk%6Ya08G`$!pmw~^G8A6NZt6>XMqz@VpO-BW9T!UF;b0g`6iR(Lt65MOfV`%KSu4eN`I5y;s059VtgX% zTgVpi^WsqrD9_yr{t96VMcd02AQ|YJLT}SE8Xa}t!;~_7u1a2|I^p&%?mZ=&^jbO< zp6Z+$o;rTp(J9c$w3Bsvv*R5n$vY>UPv5k5dWab=7JVmor?Xhu>1px4(pGE;HUZOi z#J!-#eJ%0_LHxn_XzRT5r~*eq`74FEU2?Br#95q07u{K4Qp^9Uo#(L!%TwrJp%tZI zNEq4y8F<^9?VaSEGj_6tPvX`6ff=I@*#}#9wTicfX$xqZYTxhjEAcJ~FWKJ{+Edfx zIZdCIo1X092GMfNaCO)>?EReqy zEXaT1c5&NP_Ur14>`PP#fEp5JniC11{jZWL+GoxU-rCCXtxT%-Eoiqb_^U$W>jj@- z1E#!*H=DY{ldb=W*ynGI_awo33+oGCj@0aFN%7D0u52%R%V=(H)aqk*vzw;kjXJaa zbMZAFs(M%BqHkDbzdRVbFSa4AC+!qRD9tWyiG9`C#F^#1;QXF#+jV?WYm(gM5`a;1 z$=Z?y&*D73RgzUwADl(*ml={t*we9R!GY2Pom!m|o64NpG;OqqUsPWtFSaQ+?~qpR zI>0z^ip~gX4i2DIO%@L7zbLLRelg+VqvUfvFlXLC{^p@Xj&yo(y1WCq=u#2oS|}%V zRPk$N$D_9k1zAtC`bs{K-+gRGygYqp#ZD(nsmbjHf@}V5W(hZRvUxbCD68oCeBwCd zMDPjM6D!p_?H^`q;*Zt|0h3oI{MSOSU8uQP1MWxEsD^ii zXM_u{=B^z0!C6cAUOUK|lbby(dZ<{-p6>V=-lOLCV9`ttI0}Ixbj4G-p<*w>l3@}!^scYMk(1T*#%f}Qd*hjd)@Ng z<@Vm1n#tlLtTFOyrQ{2*mqt{V1Lu2X1ESIG1!dS$jD#E-a!ZqWZ2K{01*#f#^qpS6 z_xhJ*)yYb0VJhxD?5<$C&JKWUt)9xM#yZG{=s?}Dm0nEJOvh=CFXutp8fFNG zb(-^I_07d&qdIQfKx#(1=%*H^G;t`U-;O>Z$l_DIoVb4JoyVNd?3GV-XVciXO26N; zt{59~IqcqfYJo-W>G^c9{PpxCYO-*W!d`N%y?e0Q&%E=^`5EyNrP;VqC3o_{PmJrK zehcv}Wi78;1Pt&7)5n@0vwP>R?<-gg%{k-7ab7FAQ(p5yqo=F(V@TM%M3l1Zflu6& zsj5esOc(!ZtJ4dVj<1m)6BIp_Dr?8WKUUa;*uTt82)hv`ylBOp^kYy1`tH`&J`g2i z_r>i*!D*ve5!9Zn>CBKvw4-|^o|}(8`>X%vsjy+p=j*L6`d+m3XPhZt5Sc`=G&|t6 zL2T^;avtJ(HTU!7f*j=&$~HCSKf}4uVM0)YL4r$eUe0dB?D9xt@^Fz?QEtv*Q^dQB zKGqU?HN)TSh+DM}vMtwCp79l3?!MGC|7kqIZKjI$4ZP&pt6qMn1W}5x38$?MqV67} zP7;?m(=NuPjBj?62im!B&;0PK>kNGV{k@LcHC8qE)s#{>MdRa+3iZl`@4<`H@*!eh z(S2^A3Cz2zH9c!zgnvkWIa9WNpIAp8`0i2X(e}bsk}Dy4A$L9H=i3W|9X8E2ovPNV zaS1spDoWyt)pK60$%91?ing`A4tM^^nhd-%-oG}qa;Ocr+C8&*Ikv5~lvO-W=iVv4 z3vW8Sg{H67gQFlTAcp01((sa>Oxkc4#<(O4h+| z=;$!XG#(lNj7^y|Ji(vH0C^I9NE8H^`?MAeB6%UeE(UhGb~Gf>mxKzX6CFYiI}$?u z2}WLEQxlLe6V4+b6B&3AlN>+^gfkJ~zj@)j^@bP%2K}wV@JE3E?G(-q142^iM9_X6 zs5U`YR~NM3NQdZ!hk5FG;|W?Im@W(of%2aH+R*)Qm>wKz1o~%yc?RiT-f*m?^*`o# zI|SI5!Jxq*kdTlNoe(`8D%}SHH8L`S=)xc{m^M#CJCH?T;F;Q#K-FIimc&2;okU}h zs1(o!Bi@r5#6W;~&i*?JGVM1lCGek2@p1-X;%N}5j_yWOzZC84{=X`j{98MafhGRO z-~UM*=*XfGAy{G{HHc2&)y`XW!xRmUq!aNBD&3Jv4fvHvj4zcz4fLhbKrlTWC}_7G zoAi2(CRbVwvGxS_eFk);LHdcQdm3WZuBEzI>Tjm!y{|A^ga2r`Xl*^)>n1rxoj=~Oc4@2KIVKl@_& zN4|fsUVrojYV}7fgy#%oqqhH5>t7;X18ppSH!pAVyZwn2UeD8c&3%!xU6OY(Het|? zRzJfx?uf8Cx`a1@Y%R?lnLVB!yx|qWZ*6TTz(26XS`Dfeq+1Q}Z2|=k0D!O! z$^yd~1vwAD01xLqYnje52qB3`q=O9-38K;{KEyx*05JM;97D0mE7Hb;D+Ey&^WM2f z>4E0~unJ3{Nz5%@>^gwEC?;;&5FI1rA}O^y8|7Sop<4)*6El*xzrxq-YRvIi=aUBC zl?IBQo(*Hq&aQu4ubRxB+-PTZh(_)fS4*16_Xi9y(MIrIr38CaeRFjrw-joK7bG^( z^2(R50RZNBn2ZSeLz4}z2NZxCpmuBR6K@>;6;_FM1cHhlqjI-kdA zaM!&8@>r%|E#A6Pu1L3MFl+9}YCa$&9-Am?>Ip<E0B0hBvHm{VnDVQ8846rWQ*V#Sef7%jQ7xA5oJ5~hS6#|$>ENWhp z-?%G#pBxb&2EOL*~E!i|PIj1^!FYnWbJo0(FGl#{>UP29oCx^sOo}Z@5 z?C_M$eI;9UNs!m9Nk9Up43F9E72gYP7m&$_=LO?Xy4NEMK~pi3$G{Cuv_kG;bN?iF zl*)o8P0}##r0H5>e-j9Hb>nK4H8kb?<6}G@xPwif-&K;o`X(=^lddc39+{RO&?#TG z7ZLd^zo_%**I+tu_G&ynvJ)!ebL|uE#E)YK_wPajc$8f*xKGs~;kzP?w8i z3+&^Ljg*)XICW9%Rp5ohL~AS>i@d8kqf#bbDc~v?brJgN4{-8b`!dxq@zr{U7yMBo z){3R}U3sr^uIi~jL?k?tQTs%iuaDUYDXS*JYBU1G#+wAyqcsrk#8 zz~e|3C_Sk>Q8dy1`g-&0v2saxL(B+TFn=GWFh%@`9>HXs_x4Sgc}Cv7V{OH`9|Z2j zz;7P6A?1ZQKpZa@OXvn?sW%H`}GE9WN;qs4+Br0;hZD>}a@K2+L{3B@Eh zbR6?2sPWjmu!a|Yd@0&0?-HuO319w3E>2nc4U904HSeLh@Jwq2+_3dJ@pyFx9m2P+ z5CS=ac0>l<^I`cU`Q%KTZsQVp^Jr+!@Kg4YcI9^A_A{D1nkJf$di+a#N+L@1`@;Ha z`n+aov(mHEee7Urj%kiY&JvsiUkMhhJXCqCGP<%qxZ|7gd;BzWN^t4zlE~EOPU|Jo zkAfwcZ|oj+r;@(5uE3#0xj?7^ey%kU|25zSv7&SC;_%(wEq;|r^?n7NHU)oFsC~ce zJF3T!G4^3m_IR;$zYqojjBs8=Sbt%CVZ&I>fwq)@OrOfmviJ1X)+UVsRxhi0Cf=|+ zJ0KTV^Qo$TBQE;3Wp=}n*h8_6X?7ZQ4@sACBo$pPBHs*a zNgbE}UfK2Z{Zc{Ji>!f?Poxi@TM-Rs@2}fxWhpefzecdle$1_4M^3kn<`iWWy;@A1 zgq#XF<#uYldawPHY_;4TZBkQz{fVLKmNTAkV+3KXeTv8UjWPGlu$z}_?$m$>5j83i zJrNlZ{2RIJhu2y*6MohXGZ&=i?f5*oUUH3dRiBqX|AZ%iM~OFs_cp&CUmV|y9gtnd zQs%n^h24~B$&@;o1%*|-&Va8*W~bC!fgGvh3TxV}YUsT^yW=l)2n>ovQ0}avr&^y0 z#0*&n##AT~?QsI@x2HPHA*}>G(kYbD4>$ z_LkgGBR4&_#BhV?8{+AYO~#`@<_-{9`|%>Ot)j%j#jI$1%bNVS{9}*GD~=dlpU81Z zT{if9_$+eG?~=V$@EaXLdyG0WN$&b{l|@?@i=Hp6j!&mQX&RL0bs z_m|uIsH-Onk1;1mZxxa+zg-zqSq)n3mkNwVcNUakN*zR`(U809j1#ga7!{~$)bS5G zgFai|R#kRhkPfd-eCSZ|@JVk4!)<;DTxWO- z1+NWeX%>+35Vxw?U#}J9D4tTZt||W&!G@0FgB$e{Tyyhs_9Nz3$1Ws~7I_!t=Gd7a zK4c6qSI`?70q)1#t9_9jxh697@91)mmFC4SlL_u~Rn#Bg6|a8P@}nh)QiOE`b#oZ? z-~?rwu+lQ?YE(-9VLN@ell}hOntxq)(8r%2wcKwqtJ!a66w1kJpZ8R#RxbSvS)P>% z75a`Ia1TphJlLq|+x*7ACi?AM+14XM9ck#NXPsxqYd2B0h~VYit(0HyFAsNFw_10r zSgFJ%=(Zs%%jM{Oyyc#+1w zU;F^xsM4rZ)y_oB-`OZ>??20~U{?+{Rx4%f-!R>BSnOQGHx|9KUooBx-`aqzTwGj_ zG*sQq`Ky$pTVm;s6d!shjz$2?yeVD;kPQjvOTZ9t-ptd@1S0_8*-v!B(y_K^IG#e% z!fpF#F-TMn8UTz;7*rfSfItU%5qybc1epDz77QYKBfzeDw%WE-B*Bk}3ZoGm!|a^! zVF7qUZ?K6m$cO>w5ReFT9Ed>*BnQD62=Jf0aL#<&3;~1wbfE_zz<-It+B$%c6dD1f zuLae_YinzR^bNHL-Z+?-jt>s60fK46pb#kM*4KpU!(lpbs3GX@3(N^f^Y(#bEUf+x z$5|o3esnq&4uOP*hH8cCXi;ds5U8P{Aw(Mnfx$F69-2W+G9AazBnPSdX0RXx;b}xF zok$^rwi$6=lwdjn%n|$7E=bgWXvsl;XNr?E2m?ojK((~DclF!R*7pB*C6WH|4x(cS z|JD1i#6eC>DglBa1W|%%cuwtnRJKD=;Yb<*N2k!7D3rk8iFELz&?!NF6ejDGqc}V3kp7%L?F|DW4-^2)%%~=?S>#xIgu?0G-3$B+lodZf&SbzocJ$V z49qMHEzDt1eKREV-?jXO_5K$ve`8_)6AR&pfo#|I|J3@oiPJ#a(|?+mv-qd|31m*s z(>Tq2$mG5>=V0t`Ks#Cfir79Q{ATD9&Y)ytVdli>^YR3^taiwHdh<$%MS4QPSCl`z cT;k@H1$d(Xkd?@;%58{^rJY5ox#xxd05mR2AOHXW literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/Pin_arrow_up_7x9.png b/applications/system/hid_app/assets/Pin_arrow_up_7x9.png new file mode 100644 index 0000000000000000000000000000000000000000..a91a6fd5e99a72112e28865cd8a004c7d1933fff GIT binary patch literal 3603 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 TFC+3}9cE|kU{!4CefEC<&8td2 literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/Pressed_Button_13x13.png b/applications/system/hid_app/assets/Pressed_Button_13x13.png new file mode 100644 index 0000000000000000000000000000000000000000..823926b842b8f9868fd70d6f1434dff071c04d5d GIT binary patch literal 3606 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 VN_%sSbR7Ty002ovPDHLkV1hy!X)pi) literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/Space_65x18.png b/applications/system/hid_app/assets/Space_65x18.png new file mode 100644 index 0000000000000000000000000000000000000000..b60ae50970b8be827ae32ddbd9e1b0d28c8b3a9a GIT binary patch literal 3619 zcmaJ@c{r5q+kR|?vSeS9G2*Q(Gqz$f_GOf18r!JE7=ytq%?xHFO-U))vSm#u)X=6# zwu*&|8!^APw~9?k(a6Vz_{`1J?VwO%hlm80mweJ_2Ll%+w5Jal|B#ZToHj zkX!A1wWV(yKRGcrJmE7L$o^5EyA?1;0vjpK4{lZ7;N}HH?K{|g9^>OZJmdzhM?p0K zMW=v17r<|D)m^7wAm^muyU^8WhW>`hzU({5Mni?Yg1dIjs(9V0f{sQT{n8mG4Mm49 zbG~l%ht2_K(@oNfYx5#D&tizdC8*fR7G5(g;>x7*Rzu{4&DevTBf5`It4m&=M_(&P zg6$d@FHi{BFL>ue9`qCWpjMUz{dO z@9>n#el1gZMS$0|kzX961dH0^726AL=a){4^e8+sFE>V1@t?G01xkRvR~uHtV6d@Jy=*+_LjJ^<;I%HkfZ+ zJ{WS&*3q1L--qRs;FC3Rwv9{p?cw*6_FMFK^@Q9yZ8!?f0Ei>znMIVlCNa;%nYvD_=OIcyvaxrpYxGcGRWZCqbo>reG^tc8h zWbb>x%$fc-l1kK>Pg=_9^WFC8k{QaNGP~oK)fB= zk~}W=y`t;c`=z{$ml*@ap9mj5x5DesKUlZZ%#d$#e*hBLJ2$e|kFK?B#{H}rW-Lg}+w*yHz2X|@s=6q5@hMLLk0Ngx@77A0z{8^GG<=3FCsOHJ6w{_pD*!j4k8!wLb`#+}y`?CB4 zQGwW*jB;lA{ql?St3NI0Q^jcF`vqpNjn(zm!LN-{xhDhDbu!1&ol`GQ%#aWnhw%cUor3tn<%~!N%j(>i+!K$>%8wb|oXB!X zUe^D7^t}0+-xUX|ptm{#4k$H7g6z!~%8Pa`7Cm2B9iPsA(lAKMOv=nd3E@*p)jmSY z4wO0gsHr6ijWH$&&GLy?n^(q^SE-Brl7W%7oq46G5~Q${Eu>J5eoE#Py&O@6IQcl%pM`Lo~JAQ5D{F{9M=h7QdD!DVxX< zG|G9wpE0lyi;C#Fd)Hj;lB;fVQBqS2vE;|e7g$M5vbQtaKehXm%Y{SI$sQ~+tFYwf zBdhX>5m$SU?yw~Wp|9`Dv9jjbX~cB?G?BI9R`c*!mA`5CyDM`-#q#qpezqJMP@wb32zU+0*_sQsBVDnwlp9 z1k~Y}eFzwNJcCK<%a~0Mc}6~YNcgqs_^ZDL?}eQkMSi{0{$}7!+hE#-vL*g$1VgP0 zRujb1$Rp&y?^LnB-pI>RIHO=)UG^)Stu=}bYS4>w&Cba>0H0qSyOcOu;9ZcNWp51s zkT$?rvE4`ua6jQ*ND(zN(xGR}RjlKca_;?=KGcDxu~0=Et)Zw@0K zo+3@-R$69V4NGW0?52-)vfp1=^RMlue*F1S)BQH1iv4y*zKp2)d2hK&#nR8<o8NY>iF~_Iy7d@WOBnj;S?k&H#!ZAREO0e@E9uw!tHWK^t=8Sj zR?0DPS&EACLUL6L-tCFQ1y2gZJDS5?ele!04<-jUN7j#bpf`HwcCAKt)RZua7Afop zMGs*O$_4u!*bGtM^Q3;}>g74L+mq3vv8SQ0@K zvyIWD6UZDk02mt6$rx+^jt26=`QnLiF#BZ<7=-tRgI)FPpmt<)oF5($O2IjX+B;!G z1F#0(U}GbYAsxmMAmC^i5S7-)K9yf9cVFLjVMR9g!I)rDy3YCxed9RrxIF6f^D=D4GH`@m2ZR{uET z?BHNO8jTEtKte)7G(&VWNfcj*mVto*1gZ_u*4E%4G^h+B4MW!;Qk8!zSm3Bw3Z6{E zlZc>gMT{3Ihz199LjBJf2;_fdiPV4c#K{#)rmpIK~Oj6!<%hNIw#dMD-()LE1W+P|yK8 z3>Ht^wjBJMVrK`lAyR1=A{J+30S9wLH1T+En5mAg1yo=Eh@P&MzLu7yxtX4op5xA}2IPRCO?t!+X9zvoGZ%D;b1(Y*s+gO-7(fhnSIU^r{GM99afXqQXz`L$cAW!v1IOaB9=s#hui literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/Tab_15x7.png b/applications/system/hid_app/assets/Tab_15x7.png new file mode 100644 index 0000000000000000000000000000000000000000..06ccccee79d0f0833e2b1cc31f899a88c2ea1ff5 GIT binary patch literal 2419 zcmbtVdsGu=79Sqv5fP|GsnjFWq{uT*X71d%_jiBy ze&4Z;t|yP45ZTAFV?=k--}LTG#D?a?`?O5Wp9kWkG% z+&q5g7`(}IaP~Xcm1D=Xt^dBipnpE9-ud0VW%qN)C#yqFnr67nv^@zr@h^MJu{zHx zT|sU9m6o30hl?lEMtaeS-c^TdJ)(+wos2yk&XMo^JWzFgSg&Vc^_sTTnl`fgp+dL9 zO-AnwHvD+|{v_Dg+xht7^nBddxsV{E>^oUq<-RwF0}wE`_S*UA{ifWfO_rT8GnV@I z6Nd2qh<=wBJxq5uz zeG^Rj`AWvq^a}xZ;`QAb+rF%#Y3%w;Y9AkQQx)C)+*TVt)PrSLP~fU-w)0ROY&k^& zqIH+ERD>yxUK0N4kBOYY-`E4qNC ze>u?8Bu@imz_H-|pE>%qU*|P{P@kDRzLvJ;CH2 zYaeQTY`a>!TH|jNb}DwWG>XU^U0{S(cMJU$doRvmFBxmP-EmCSI^Q4~>8uou`0TQp zGfy)~AM1{|=ry+apAiJjB_v88^w2L@$|)g-Cy1*~o&4A<;)$_i3!te}>n!5TqGpGt zo44f0H&5p~Zb~2Q9pc=*iE35aPx|&ZMfX!wB9-V9M^qcMw)vxDUtOs)8W7Rd^{QKb5=ApqDLIuNZoz=acj=PLq8r5xRFSV{YBD)7v ze#-D|Km>Eo?7NdoAXIDSJL~-Z^sWm`oBnc>wx_Zd@BMLuS0;gSPO18FsM=W{(`W4a zOUBju$ZZa-WsYNyqSOkfUZ5-W%I^86&r&oGO*;C3+T-&k^G@nj*Ce-E@Bxtv?G(fc zg@79_TL30TYXB2mGQv;5hz=OzG8?}bpTmIZf*t@A_!Hntkr)UJks%m@$`B>U@S@N_ z7Y2>tN%N)qP~;Q_gGmGFUeZ_zgYF7~E<&*cRv3(30l@gU@e-QNag!z4+<+YIgB-vo z78MTLiR~1A#F8L#ir5g1qLwQS1^`>l!bLfRi9xkIL4mT=e#8X}7A_mqR3f-wg2nq0 zcM3y59-@LkZwieRfCgM~43dcX5s8LCOUEKlxl$-x3Z+07 z^TIQvs>MnwokF9^<oW3Eb(d3JXou&!)KFd) zPX&oFL=}c037r3<#TO#|7uX6GhCRdDrh@UUAvSakht8(Hq+7ISQw=S(qQ#!~?}8R@ zU=9I~^kNur2&4j+L3}<41$=JU+v`gXZ6^Sr1<&i!eBlpoisu6aj&a4RFF$vo+%;~I;v${HQzOU^(&);(W?&G+xtM;~*LV|LF000PCq0G>n ze#iF1&%=3t6jKZV06`=HiL|#uB0&@?*_#l62LMK2wnH!`X+_F#a0M^oY}z~bI4$4; z09I!4H;KCDiQWLPmqf*k8=|5Goh2mqWTBkuFLn!}vZF_G50v|uT#G&#<8=DScg2Ci zXJH}i+1d4v>y?vPlN;^K4v~mGVycM~d47OCI?4dvs~B&Gs&B4};Fd%U@q$DrTIziG z8USF9hsg-1KQh|jdPoMi0ZO;#ezC^kUy&8|sxAO15f}oCP441KKm$#hj!hCklML|4 z;i;D(kPH9;%urJ>a9;?R`C(A&8=ml<3}F9g$lLOtBZCc<<_EVbuXFPPqP89EKKJqQ9v(^~*Q3B1|Dsbs zpEKY)xay|eFOYju@LkAi4D-l_@xGkf_Du!~dj)sxnpN?XETh`i)-^EH_u{8K_%$8$rfHyEz-)Q@>XNi`OUb4og+GrPpeB_o5x%&w+Gua zGGCw*&6Ju`M#QGh!{!xJHwBV{g#gxNyIR}lJD;@#)P{fO;*Jr<_Z8L)vU%Ft8oEsX$7MIQ2ABn^u1(h>o@!WV3vE~&?A$byI)DLYK602DOA=< zb7Oay8Sma-YanX6V=Q8?;BA>y6IsVvcrWj>M?7-5doqSaOJ8Xn5ty zCZ|rO^0EN0NfW;~RtX-x$1|=M+|DnZ9>)vDqI7OV6o96pB~E}Fny3ZbMW%j}lh*g#IQF?Ape)N=vQe3r|k)eBcf=esNDx?%JDNS|?pc#4RE<&%aZybRQz( zd0t`X@vnh&AnaNkE}~OQ*!%h??CI-Q%ssAR@MYYg(9%8YWUSOvd}K;$K@y1&3l_v}hlLc~_<8J_UR2^b5O z>UX7mN;xWL{t^~}fJP*kF%bt@hlqr*iq+8$Rd!Lrxtyl^? z#W^KBW%9nG6V1t}n|Xhi;{zv=2WOna?pioKwI3}K_#pM5yGX(5WszPhod`Dc_8`)STsW&kEJjS$#>dZ5(?tjz9^VE~o8S5avb@?F3 zIcorq;L$MBc--Sx>|GpQe7G;9ue#53 zmO3jnJKe_)q+}ast7k94iSU&`feO8f6BSVv{ed0d4Bz9XnNtEwZ}vf?{k}$y{IdF_jp2!SXxk;v;(p5S|RCHNK4AN z-1myEXYZHtGhb#76n`Rq_}q$U2z#(@qnRn+?DiVLHu*8Pf*Cp6I+|UWSy;E2FbO#m zbjJ0}deuI=r&+2wJy2p(fBmVUs+Myea6<%st$m8e@Qoq&t&m$+s_#~V2NBiE;XUE$ z;X5~S){m~WY{vhr8D=g>&D-*MaJ}Lh=c>9Oci}0IKaV1BI`5sGx_q&GFLyw88%mn) z77%h(q$ZJTr5EH^aoPhu>KUDqZ~3z&Ps*=BTUD+1_3Vke+`&I68cx2uYCYBZoIiTV zG9bEKkszBcy&5KQ@DS|2=C>224)nA174;t0nCrSvRor}h(e)Qc`~99%gM3(i0q6kS zOlEmR`Tg<>j4MCQ=hMXK;`;?=ua4FC)+4Tt(zquBGPJYCG8|LsxRUXKycg0FQ|&D| z!3M6nt_h(>qHc<%Juw=O1ew}HWbDQZNj3`N3zssZ?98k4V)ITsE-OD~aAP9dIc53C z=c8fBHQ&p27J+ZH1?KVN0a0?-xJE%X!LI)J%kbF1HM}YsiT|cjw&BWpnnlADtX9@UW)li2xC; z7rPGyr;KMtkoz)cGlHK{P974jGZ}yN*WlgIbEEcOZ@0f5c-=Obe!gspe;UP9>w?z= zvNZCExrp0U?624JvlY%LSXP()3TJDL;sP6W<6UxcvkxHVSH~_UjTU+p=49I%AwHxJ zFjuTM(*4~|xK;TeJ93Pq>EEr(+*g_xzf8uv%~euGRmzSRBT5jK;gro`)WcKc zY5YpdtcyVj{fEu;(N6aJ^J{*!-L#KCKWe(&Vpg%=%*dCKR6p-6SE*R~8MHhr9W40W zdcZ9tp7C&_x^MH_&NY#5=S#O9<7){YQd{LX}Iu7p?JsJaOYplY1)Iy!OfBN;~kid-nm_?F&#A}%%Vjq`$5q| zc%yQoVr4rMF@JZXxV=A&UCyo;Y^+jDKd@oEWxv?DhHET*XSZTF8M?IrS-G^h9-*(Y zhx1n{OE<^R9mwAFU@R36n0S#r@gOTA)(4NqW4)MXoACw!z@tQP#LzJ|)^Hq|sEOUi zXflWt4jTXrj2ILw&L2+)dE$KtBm|iKvIYzycp<8kl2^>g5ebn_2v0i!(!j zed%-x90Car4%Q6T)+AGXAX@tR`Vc4#0)uIA5E?WliH>DxkZ8)k70mE79F;(!6UZdc zwj$P(97soiIiCI}1R~{MSrYA^G;tCJVPGi`EluclNWXzLHvd1ANcI{NyD^|bY% zhhY}Kxn^WsAQ4ZZ|K@uAm#h0n?sg>*DICjYcq$aNAlv8qzs~vh5~p~!hyPYBXYy~|<4K%ir*f)VECG~N3t`XAZG70Mn#!Kq>|l0^MC=h$Nt(>}1N6~R2Jk+G1UpniOLYXdBx;x!Bs$qz z@59#!0P{RdMmYVU(I(deGQbT`dNdA*HI4j?th85g0YFK>Fj#DA7gr)0Xx4CSmH?Xf z0uLRYcnJb201&_oH3b9rgn-%aR)%~)UvcuFG|-p7ub3Z*;{q}cS{~pwegSwmT|ldG z*VO}gEMu?+Z(S)@gzGa+OYVqjJ|HL_lPF^B0Yqe&sk6{&A!gBRzAM-@lw10I=Tr4NaE3yg!a)3cPsQByqD9lHTQ zcCG8>ww_Vq)a3Zcr1w++`+H;lw*NdCY^b;}v|V+Ln->tZ?PT}6PfYakP@1?N2G;r) zp91=w0pFoDH?0AIypw`&L)K!MdYi`kb8p!<8_4ey+_h^?+4EL4bS&2Jr`8C0I5vER z^K^S4WF9!1X`E3~R}i^%7E1~$MaNII@|wa(t5ZtbO;P8!;tzF=YCk%yCV6!MbEU!_ zY}3Sij!rUDY)Kszn?A3(ppdpDkQ^)ourAxx**@F(v^AhE{2Lc{tT3iK2rv#`Qokm< zD+v(w(bi3-FpW^NV8@;W2wWX+n( zQd(4}O6bR(HeOF0Xa;Fs-Mm_52}`-~_yo^;?m*+`cNJu>zRsg{(X~a~BGU5xyJXAu zBO;#V7j+%~5=aNauEygcx?sZI*FIuTUyC;PxPp;YX_CTCV04@lba3*RBSDgKb-7qJ z{{imU2=Q6|GnYi`11=^eT4Jm*$h*q3N@Ze|{4N5KmtggOfs^mrl_`gatu-(_;g1qA z7A%!-iu)CFmCyVoEbg9+Iw0I~ecV=1Q8`i5YL}HiY5=8P=ul|bElS9?R+&j8wtODv ze;mOAr6-jqiX_@y-)MO?UM>M|j2X2S$UlHCOc6V#gEyMsy?s;DG$ZfciT2{$_x$%_ z;5ScN5%YrVAr8^S;@W|k%I#TF$ksyjf}XdT1RuhxFJzitDex(Bzj^xG^ltwzJEy0n zBfkgl7P>4H*@W^uDB~}4PNryYxeO%3`VQZ_^o(Xl=m$-?44)e!H^@$y!z+hFC6nHW zrNUF4Q^QlI?m0TqoQ!&y_jWnncM`dO#yRYch0_!Jv0{PuQulj`<(*y>>y~z)gV720 zohRH2YTUOjuH%FrUyicKyNoJu#Ff96iBpt%t%+a2nD$bgd1lo7Z`gRAdb~Dk9mKaG z7X&$H?SQ1+^JaM`dFM=?ZRZkx{b+bz|6}&C4#f_kj&tff>PG61di_egOTtTz^oR7< z^n1=x=cMLl`q_b$9OE3doMku>z8WY{satuXGOBVQu=A_oJKPL&T44Fjvheh$F3V-& z_kv~Vuk2oSm%4ZT_9eV#1s&-g)q1FR=ObD*%HuyMTRPOwa+dFmm;`m(&(c@bdRgPH8$Q+X3kk*7o*y0XdqxfNVfh81 z18}oh6%iHpDlRahf0!?%i_ygo2+Um>Z|G}4Tp6QrPX%OZWshe%rqOYw6NCBBr6;F5 zT62R9Tyfl3jonBBYh6et?!A zEVuJkRZSKeXHF8|$R$U=Sshneqb&_c21HqR6_lY%?S-YRA$L_7r}my=RG_L+C*Nxg zd2fGRQ`&V=DzrNBp?$@}Cw&zR*M(tlt@#TnrC0~)U=5fXy3&h5nC}j2^=*Bewq-wx zK|3w_F$Wjp(UIM^ZzEMNx@e~sr?j+^O240cj+4ZudO5NE(tA!hpFb>}>dvCD?w0;| zXi+ga>SF8O6S~YK_V<52R{myg1~pSSLt?GE);>5^?Pt>S_VTBsM6nC`ziR`l5nKFnEPUyKY`!BaTUJbr#AIdmizRW*^Vybq- zYXe#81;jkWt!nm{YXv#-XXGtw%72ElVPm+!CY=PA+`OEFh=sNBi^*d}UPZY%wnm8e z8H3DK>&*;*w-avFKFH2oBWe0K>vH$imZi^A32yUMl<(kG&jID~<0Xhvgk?BoYXtS+ z6nO@}+B)ZAP)h%9Gjp_y{qFp_UtJIF!;cRdZa10L?ANn$se?ML`J;_wfTI*-m*t|DwE@OB)gT z%6m9pl`?d54Bdh3O%KLW@qmdJ*%J@4B4T~;Xgt=7dA0>_002CS1V;=VV`B}+k%=1E zUl-#D&8T)))5!t zkJI-88ySKO7;ugN5l_d07{mY)4bDJ-|JH?b#=n*!V9?(Xx<3N^pQJE0_8=sgiU;Xv z=&Ivj+M1vv`Wi4@sJ^DQ8b}igI|6|ofxxuXp)fd97p|ob`lo?8(WqYDaI~4lKe0G7 z1lX5Or@$eQ;NW15U@Z+Y)dvF8*Vl(YH6fas>KueRjY*q!ozBfy+Y|FZ=m@Pfn4Om+33P^1o0%LE29N1AFQ&CK=nkWfu? z3z&tD_HV8k85c;zljy&>UjOBq{gM022}BAfvKgLA2*P_=P{~Bl-#dmA{+x@+ANBs> zdi^;U(?4<{oMa%s>iWOx{CkOGo?pX%UCWvL>w7$jV|FUX)$L7+A2@Hs4tr}y^PfL| za)wUz@4`8qf|Z$xBctEbgVT7GKri_xq1;?NN}4 +#include + +#define TAG "HidApp" + +enum HidDebugSubmenuIndex { + HidSubmenuIndexKeynote, + HidSubmenuIndexKeynoteVertical, + HidSubmenuIndexKeyboard, + HidSubmenuIndexMedia, + HidSubmenuIndexTikTok, + HidSubmenuIndexMouse, + HidSubmenuIndexMouseClicker, + HidSubmenuIndexMouseJiggler, +}; + +static void hid_submenu_callback(void* context, uint32_t index) { + furi_assert(context); + Hid* app = context; + if(index == HidSubmenuIndexKeynote) { + app->view_id = HidViewKeynote; + hid_keynote_set_orientation(app->hid_keynote, false); + view_dispatcher_switch_to_view(app->view_dispatcher, HidViewKeynote); + } else if(index == HidSubmenuIndexKeynoteVertical) { + app->view_id = HidViewKeynote; + hid_keynote_set_orientation(app->hid_keynote, true); + view_dispatcher_switch_to_view(app->view_dispatcher, HidViewKeynote); + } else if(index == HidSubmenuIndexKeyboard) { + app->view_id = HidViewKeyboard; + view_dispatcher_switch_to_view(app->view_dispatcher, HidViewKeyboard); + } else if(index == HidSubmenuIndexMedia) { + app->view_id = HidViewMedia; + view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMedia); + } else if(index == HidSubmenuIndexMouse) { + app->view_id = HidViewMouse; + view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMouse); + } else if(index == HidSubmenuIndexTikTok) { + app->view_id = BtHidViewTikTok; + view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewTikTok); + } else if(index == HidSubmenuIndexMouseClicker) { + app->view_id = HidViewMouseClicker; + view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMouseClicker); + } else if(index == HidSubmenuIndexMouseJiggler) { + app->view_id = HidViewMouseJiggler; + view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMouseJiggler); + } +} + +static void bt_hid_connection_status_changed_callback(BtStatus status, void* context) { + furi_assert(context); + Hid* hid = context; + bool connected = (status == BtStatusConnected); + if(hid->transport == HidTransportBle) { + if(connected) { + notification_internal_message(hid->notifications, &sequence_set_blue_255); + } else { + notification_internal_message(hid->notifications, &sequence_reset_blue); + } + } + hid_keynote_set_connected_status(hid->hid_keynote, connected); + hid_keyboard_set_connected_status(hid->hid_keyboard, connected); + hid_media_set_connected_status(hid->hid_media, connected); + hid_mouse_set_connected_status(hid->hid_mouse, connected); + hid_mouse_clicker_set_connected_status(hid->hid_mouse_clicker, connected); + hid_mouse_jiggler_set_connected_status(hid->hid_mouse_jiggler, connected); + hid_tiktok_set_connected_status(hid->hid_tiktok, connected); +} + +static void hid_dialog_callback(DialogExResult result, void* context) { + furi_assert(context); + Hid* app = context; + if(result == DialogExResultLeft) { + view_dispatcher_stop(app->view_dispatcher); + } else if(result == DialogExResultRight) { + view_dispatcher_switch_to_view(app->view_dispatcher, app->view_id); // Show last view + } else if(result == DialogExResultCenter) { + view_dispatcher_switch_to_view(app->view_dispatcher, HidViewSubmenu); + } +} + +static uint32_t hid_exit_confirm_view(void* context) { + UNUSED(context); + return HidViewExitConfirm; +} + +static uint32_t hid_exit(void* context) { + UNUSED(context); + return VIEW_NONE; +} + +Hid* hid_alloc(HidTransport transport) { + Hid* app = malloc(sizeof(Hid)); + app->transport = transport; + + // Gui + app->gui = furi_record_open(RECORD_GUI); + + // Bt + app->bt = furi_record_open(RECORD_BT); + + // Notifications + app->notifications = furi_record_open(RECORD_NOTIFICATION); + + // View dispatcher + app->view_dispatcher = view_dispatcher_alloc(); + view_dispatcher_enable_queue(app->view_dispatcher); + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + // Device Type Submenu view + app->device_type_submenu = submenu_alloc(); + submenu_add_item( + app->device_type_submenu, "Keynote", HidSubmenuIndexKeynote, hid_submenu_callback, app); + submenu_add_item( + app->device_type_submenu, + "Keynote Vertical", + HidSubmenuIndexKeynoteVertical, + hid_submenu_callback, + app); + submenu_add_item( + app->device_type_submenu, "Keyboard", HidSubmenuIndexKeyboard, hid_submenu_callback, app); + submenu_add_item( + app->device_type_submenu, "Media", HidSubmenuIndexMedia, hid_submenu_callback, app); + submenu_add_item( + app->device_type_submenu, "Mouse", HidSubmenuIndexMouse, hid_submenu_callback, app); + if(app->transport == HidTransportBle) { + submenu_add_item( + app->device_type_submenu, + "TikTok Controller", + HidSubmenuIndexTikTok, + hid_submenu_callback, + app); + } + submenu_add_item( + app->device_type_submenu, + "Mouse Clicker", + HidSubmenuIndexMouseClicker, + hid_submenu_callback, + app); + submenu_add_item( + app->device_type_submenu, + "Mouse Jiggler", + HidSubmenuIndexMouseJiggler, + hid_submenu_callback, + app); + view_set_previous_callback(submenu_get_view(app->device_type_submenu), hid_exit); + view_dispatcher_add_view( + app->view_dispatcher, HidViewSubmenu, submenu_get_view(app->device_type_submenu)); + app->view_id = HidViewSubmenu; + view_dispatcher_switch_to_view(app->view_dispatcher, app->view_id); + return app; +} + +Hid* hid_app_alloc_view(void* context) { + furi_assert(context); + Hid* app = context; + // Dialog view + app->dialog = dialog_ex_alloc(); + dialog_ex_set_result_callback(app->dialog, hid_dialog_callback); + dialog_ex_set_context(app->dialog, app); + dialog_ex_set_left_button_text(app->dialog, "Exit"); + dialog_ex_set_right_button_text(app->dialog, "Stay"); + dialog_ex_set_center_button_text(app->dialog, "Menu"); + dialog_ex_set_header(app->dialog, "Close Current App?", 16, 12, AlignLeft, AlignTop); + view_dispatcher_add_view( + app->view_dispatcher, HidViewExitConfirm, dialog_ex_get_view(app->dialog)); + + // Keynote view + app->hid_keynote = hid_keynote_alloc(app); + view_set_previous_callback(hid_keynote_get_view(app->hid_keynote), hid_exit_confirm_view); + view_dispatcher_add_view( + app->view_dispatcher, HidViewKeynote, hid_keynote_get_view(app->hid_keynote)); + + // Keyboard view + app->hid_keyboard = hid_keyboard_alloc(app); + view_set_previous_callback(hid_keyboard_get_view(app->hid_keyboard), hid_exit_confirm_view); + view_dispatcher_add_view( + app->view_dispatcher, HidViewKeyboard, hid_keyboard_get_view(app->hid_keyboard)); + + // Media view + app->hid_media = hid_media_alloc(app); + view_set_previous_callback(hid_media_get_view(app->hid_media), hid_exit_confirm_view); + view_dispatcher_add_view( + app->view_dispatcher, HidViewMedia, hid_media_get_view(app->hid_media)); + + // TikTok view + app->hid_tiktok = hid_tiktok_alloc(app); + view_set_previous_callback(hid_tiktok_get_view(app->hid_tiktok), hid_exit_confirm_view); + view_dispatcher_add_view( + app->view_dispatcher, BtHidViewTikTok, hid_tiktok_get_view(app->hid_tiktok)); + + // Mouse view + app->hid_mouse = hid_mouse_alloc(app); + view_set_previous_callback(hid_mouse_get_view(app->hid_mouse), hid_exit_confirm_view); + view_dispatcher_add_view( + app->view_dispatcher, HidViewMouse, hid_mouse_get_view(app->hid_mouse)); + + // Mouse clicker view + app->hid_mouse_clicker = hid_mouse_clicker_alloc(app); + view_set_previous_callback( + hid_mouse_clicker_get_view(app->hid_mouse_clicker), hid_exit_confirm_view); + view_dispatcher_add_view( + app->view_dispatcher, + HidViewMouseClicker, + hid_mouse_clicker_get_view(app->hid_mouse_clicker)); + + // Mouse jiggler view + app->hid_mouse_jiggler = hid_mouse_jiggler_alloc(app); + view_set_previous_callback( + hid_mouse_jiggler_get_view(app->hid_mouse_jiggler), hid_exit_confirm_view); + view_dispatcher_add_view( + app->view_dispatcher, + HidViewMouseJiggler, + hid_mouse_jiggler_get_view(app->hid_mouse_jiggler)); + + return app; +} + +void hid_free(Hid* app) { + furi_assert(app); + + // Reset notification + if(app->transport == HidTransportBle) { + notification_internal_message(app->notifications, &sequence_reset_blue); + } + + // Free views + view_dispatcher_remove_view(app->view_dispatcher, HidViewSubmenu); + submenu_free(app->device_type_submenu); + view_dispatcher_remove_view(app->view_dispatcher, HidViewExitConfirm); + dialog_ex_free(app->dialog); + view_dispatcher_remove_view(app->view_dispatcher, HidViewKeynote); + hid_keynote_free(app->hid_keynote); + view_dispatcher_remove_view(app->view_dispatcher, HidViewKeyboard); + hid_keyboard_free(app->hid_keyboard); + view_dispatcher_remove_view(app->view_dispatcher, HidViewMedia); + hid_media_free(app->hid_media); + view_dispatcher_remove_view(app->view_dispatcher, HidViewMouse); + hid_mouse_free(app->hid_mouse); + view_dispatcher_remove_view(app->view_dispatcher, HidViewMouseClicker); + hid_mouse_clicker_free(app->hid_mouse_clicker); + view_dispatcher_remove_view(app->view_dispatcher, HidViewMouseJiggler); + hid_mouse_jiggler_free(app->hid_mouse_jiggler); + view_dispatcher_remove_view(app->view_dispatcher, BtHidViewTikTok); + hid_tiktok_free(app->hid_tiktok); + view_dispatcher_free(app->view_dispatcher); + + // Close records + furi_record_close(RECORD_GUI); + app->gui = NULL; + furi_record_close(RECORD_NOTIFICATION); + app->notifications = NULL; + furi_record_close(RECORD_BT); + app->bt = NULL; + + // Free rest + free(app); +} + +void hid_hal_keyboard_press(Hid* instance, uint16_t event) { + furi_assert(instance); + if(instance->transport == HidTransportBle) { + furi_hal_bt_hid_kb_press(event); + } else if(instance->transport == HidTransportUsb) { + furi_hal_hid_kb_press(event); + } else { + furi_crash(NULL); + } +} + +void hid_hal_keyboard_release(Hid* instance, uint16_t event) { + furi_assert(instance); + if(instance->transport == HidTransportBle) { + furi_hal_bt_hid_kb_release(event); + } else if(instance->transport == HidTransportUsb) { + furi_hal_hid_kb_release(event); + } else { + furi_crash(NULL); + } +} + +void hid_hal_keyboard_release_all(Hid* instance) { + furi_assert(instance); + if(instance->transport == HidTransportBle) { + furi_hal_bt_hid_kb_release_all(); + } else if(instance->transport == HidTransportUsb) { + furi_hal_hid_kb_release_all(); + } else { + furi_crash(NULL); + } +} + +void hid_hal_consumer_key_press(Hid* instance, uint16_t event) { + furi_assert(instance); + if(instance->transport == HidTransportBle) { + furi_hal_bt_hid_consumer_key_press(event); + } else if(instance->transport == HidTransportUsb) { + furi_hal_hid_consumer_key_press(event); + } else { + furi_crash(NULL); + } +} + +void hid_hal_consumer_key_release(Hid* instance, uint16_t event) { + furi_assert(instance); + if(instance->transport == HidTransportBle) { + furi_hal_bt_hid_consumer_key_release(event); + } else if(instance->transport == HidTransportUsb) { + furi_hal_hid_consumer_key_release(event); + } else { + furi_crash(NULL); + } +} + +void hid_hal_consumer_key_release_all(Hid* instance) { + furi_assert(instance); + if(instance->transport == HidTransportBle) { + furi_hal_bt_hid_consumer_key_release_all(); + } else if(instance->transport == HidTransportUsb) { + furi_hal_hid_kb_release_all(); + } else { + furi_crash(NULL); + } +} + +void hid_hal_mouse_move(Hid* instance, int8_t dx, int8_t dy) { + furi_assert(instance); + if(instance->transport == HidTransportBle) { + furi_hal_bt_hid_mouse_move(dx, dy); + } else if(instance->transport == HidTransportUsb) { + furi_hal_hid_mouse_move(dx, dy); + } else { + furi_crash(NULL); + } +} + +void hid_hal_mouse_scroll(Hid* instance, int8_t delta) { + furi_assert(instance); + if(instance->transport == HidTransportBle) { + furi_hal_bt_hid_mouse_scroll(delta); + } else if(instance->transport == HidTransportUsb) { + furi_hal_hid_mouse_scroll(delta); + } else { + furi_crash(NULL); + } +} + +void hid_hal_mouse_press(Hid* instance, uint16_t event) { + furi_assert(instance); + if(instance->transport == HidTransportBle) { + furi_hal_bt_hid_mouse_press(event); + } else if(instance->transport == HidTransportUsb) { + furi_hal_hid_mouse_press(event); + } else { + furi_crash(NULL); + } +} + +void hid_hal_mouse_release(Hid* instance, uint16_t event) { + furi_assert(instance); + if(instance->transport == HidTransportBle) { + furi_hal_bt_hid_mouse_release(event); + } else if(instance->transport == HidTransportUsb) { + furi_hal_hid_mouse_release(event); + } else { + furi_crash(NULL); + } +} + +void hid_hal_mouse_release_all(Hid* instance) { + furi_assert(instance); + if(instance->transport == HidTransportBle) { + furi_hal_bt_hid_mouse_release_all(); + } else if(instance->transport == HidTransportUsb) { + furi_hal_hid_mouse_release(HID_MOUSE_BTN_LEFT); + furi_hal_hid_mouse_release(HID_MOUSE_BTN_RIGHT); + } else { + furi_crash(NULL); + } +} + +int32_t hid_usb_app(void* p) { + UNUSED(p); + Hid* app = hid_alloc(HidTransportUsb); + app = hid_app_alloc_view(app); + FuriHalUsbInterface* usb_mode_prev = furi_hal_usb_get_config(); + furi_hal_usb_unlock(); + furi_check(furi_hal_usb_set_config(&usb_hid, NULL) == true); + + bt_hid_connection_status_changed_callback(BtStatusConnected, app); + + dolphin_deed(DolphinDeedPluginStart); + + view_dispatcher_run(app->view_dispatcher); + + furi_hal_usb_set_config(usb_mode_prev, NULL); + + hid_free(app); + + return 0; +} + +int32_t hid_ble_app(void* p) { + UNUSED(p); + Hid* app = hid_alloc(HidTransportBle); + app = hid_app_alloc_view(app); + + bt_disconnect(app->bt); + + // Wait 2nd core to update nvm storage + furi_delay_ms(200); + + // Migrate data from old sd-card folder + Storage* storage = furi_record_open(RECORD_STORAGE); + + storage_common_migrate( + storage, + EXT_PATH("apps/Tools/" HID_BT_KEYS_STORAGE_NAME), + APP_DATA_PATH(HID_BT_KEYS_STORAGE_NAME)); + + bt_keys_storage_set_storage_path(app->bt, APP_DATA_PATH(HID_BT_KEYS_STORAGE_NAME)); + + furi_record_close(RECORD_STORAGE); + + if(!bt_set_profile(app->bt, BtProfileHidKeyboard)) { + FURI_LOG_E(TAG, "Failed to switch to HID profile"); + } + + furi_hal_bt_start_advertising(); + bt_set_status_changed_callback(app->bt, bt_hid_connection_status_changed_callback, app); + + dolphin_deed(DolphinDeedPluginStart); + + view_dispatcher_run(app->view_dispatcher); + + bt_set_status_changed_callback(app->bt, NULL, NULL); + + bt_disconnect(app->bt); + + // Wait 2nd core to update nvm storage + furi_delay_ms(200); + + bt_keys_storage_set_default_path(app->bt); + + if(!bt_set_profile(app->bt, BtProfileSerial)) { + FURI_LOG_E(TAG, "Failed to switch to Serial profile"); + } + + hid_free(app); + + return 0; +} diff --git a/applications/system/hid_app/hid.h b/applications/system/hid_app/hid.h new file mode 100644 index 00000000000..49d8b4e045d --- /dev/null +++ b/applications/system/hid_app/hid.h @@ -0,0 +1,67 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "views/hid_keynote.h" +#include "views/hid_keyboard.h" +#include "views/hid_media.h" +#include "views/hid_mouse.h" +#include "views/hid_mouse_clicker.h" +#include "views/hid_mouse_jiggler.h" +#include "views/hid_tiktok.h" + +#define HID_BT_KEYS_STORAGE_NAME ".bt_hid.keys" + +typedef enum { + HidTransportUsb, + HidTransportBle, +} HidTransport; + +typedef struct Hid Hid; + +struct Hid { + Bt* bt; + Gui* gui; + NotificationApp* notifications; + ViewDispatcher* view_dispatcher; + Submenu* device_type_submenu; + DialogEx* dialog; + HidKeynote* hid_keynote; + HidKeyboard* hid_keyboard; + HidMedia* hid_media; + HidMouse* hid_mouse; + HidMouseClicker* hid_mouse_clicker; + HidMouseJiggler* hid_mouse_jiggler; + HidTikTok* hid_tiktok; + + HidTransport transport; + uint32_t view_id; +}; + +void hid_hal_keyboard_press(Hid* instance, uint16_t event); +void hid_hal_keyboard_release(Hid* instance, uint16_t event); +void hid_hal_keyboard_release_all(Hid* instance); + +void hid_hal_consumer_key_press(Hid* instance, uint16_t event); +void hid_hal_consumer_key_release(Hid* instance, uint16_t event); +void hid_hal_consumer_key_release_all(Hid* instance); + +void hid_hal_mouse_move(Hid* instance, int8_t dx, int8_t dy); +void hid_hal_mouse_scroll(Hid* instance, int8_t delta); +void hid_hal_mouse_press(Hid* instance, uint16_t event); +void hid_hal_mouse_release(Hid* instance, uint16_t event); +void hid_hal_mouse_release_all(Hid* instance); \ No newline at end of file diff --git a/applications/system/hid_app/hid_ble_10px.png b/applications/system/hid_app/hid_ble_10px.png new file mode 100644 index 0000000000000000000000000000000000000000..d4d30afe0465e3bd0d87355a09bbdfc6c44aa5d9 GIT binary patch literal 151 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2VGmzZ%#=aj&u?6^qxc>kDAIJlDSNs& zhE&W+PH5C8xG literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/hid_usb_10px.png b/applications/system/hid_app/hid_usb_10px.png new file mode 100644 index 0000000000000000000000000000000000000000..415de7d2304fe982c025b2b9a942abbf0a2b6dd0 GIT binary patch literal 969 zcmaJ=J#W)M7(OY0prTdS3e(9IQYsOjefe;0)l|h!Xha;MG(g5)>`P;{_8I$1oJb(V z#>Co{9{@i91|(QuX5%*?v9pwOnxqT_55D(az0dQ0J@>lZy1%+|YXtzX+Ss!@;>_%o zt2y!i@N?&lIBxPQduOaNrjU+e?;YX%)UR2L%LyN@}>atRF6-9xXE~}dAVr@YBcOX_U zM#>gat3`~BQpG5%aP~Eh*gj89{x|#<%&i_M$ zU=f}04!x-NpTtRb98uJv2|I~hvAe-WmMSu=m=ez7E@Q{@LAHmCvt-C3h|9793l4Gp zF!O9qA&z4-!i1C1r48GZ1c~hXo`JDuS-aJ0wOrd$)taqWN;ON>tZH4WV;fs@tj*k$ zfQEdI^)9g5QfwxOAQG8v8vD3;Z_1q-{ zl$i_hipxU&G!&YTg}8q|eDGX6j4SPCw{~`RCd@~lzrPU2?S{SEO@H(cJnv<$o(G-l ph0_~rZ>7^_`EovpzT_W+OY7j;8rXcd{ +#include +#include +#include "../hid.h" +#include "hid_icons.h" + +#define TAG "HidKeyboard" + +struct HidKeyboard { + View* view; + Hid* hid; +}; + +typedef struct { + bool shift; + bool alt; + bool ctrl; + bool gui; + uint8_t x; + uint8_t y; + uint8_t last_key_code; + uint16_t modifier_code; + bool ok_pressed; + bool back_pressed; + bool connected; + char key_string[5]; + HidTransport transport; +} HidKeyboardModel; + +typedef struct { + uint8_t width; + char* key; + const Icon* icon; + char* shift_key; + uint8_t value; +} HidKeyboardKey; + +typedef struct { + int8_t x; + int8_t y; +} HidKeyboardPoint; +// 4 BY 12 +#define MARGIN_TOP 0 +#define MARGIN_LEFT 4 +#define KEY_WIDTH 9 +#define KEY_HEIGHT 12 +#define KEY_PADDING 1 +#define ROW_COUNT 7 +#define COLUMN_COUNT 12 + +// 0 width items are not drawn, but there value is used +const HidKeyboardKey hid_keyboard_keyset[ROW_COUNT][COLUMN_COUNT] = { + { + {.width = 1, .icon = &I_ButtonF1_5x8, .value = HID_KEYBOARD_F1}, + {.width = 1, .icon = &I_ButtonF2_5x8, .value = HID_KEYBOARD_F2}, + {.width = 1, .icon = &I_ButtonF3_5x8, .value = HID_KEYBOARD_F3}, + {.width = 1, .icon = &I_ButtonF4_5x8, .value = HID_KEYBOARD_F4}, + {.width = 1, .icon = &I_ButtonF5_5x8, .value = HID_KEYBOARD_F5}, + {.width = 1, .icon = &I_ButtonF6_5x8, .value = HID_KEYBOARD_F6}, + {.width = 1, .icon = &I_ButtonF7_5x8, .value = HID_KEYBOARD_F7}, + {.width = 1, .icon = &I_ButtonF8_5x8, .value = HID_KEYBOARD_F8}, + {.width = 1, .icon = &I_ButtonF9_5x8, .value = HID_KEYBOARD_F9}, + {.width = 1, .icon = &I_ButtonF10_5x8, .value = HID_KEYBOARD_F10}, + {.width = 1, .icon = &I_ButtonF11_5x8, .value = HID_KEYBOARD_F11}, + {.width = 1, .icon = &I_ButtonF12_5x8, .value = HID_KEYBOARD_F12}, + }, + { + {.width = 1, .icon = NULL, .key = "1", .shift_key = "!", .value = HID_KEYBOARD_1}, + {.width = 1, .icon = NULL, .key = "2", .shift_key = "@", .value = HID_KEYBOARD_2}, + {.width = 1, .icon = NULL, .key = "3", .shift_key = "#", .value = HID_KEYBOARD_3}, + {.width = 1, .icon = NULL, .key = "4", .shift_key = "$", .value = HID_KEYBOARD_4}, + {.width = 1, .icon = NULL, .key = "5", .shift_key = "%", .value = HID_KEYBOARD_5}, + {.width = 1, .icon = NULL, .key = "6", .shift_key = "^", .value = HID_KEYBOARD_6}, + {.width = 1, .icon = NULL, .key = "7", .shift_key = "&", .value = HID_KEYBOARD_7}, + {.width = 1, .icon = NULL, .key = "8", .shift_key = "*", .value = HID_KEYBOARD_8}, + {.width = 1, .icon = NULL, .key = "9", .shift_key = "(", .value = HID_KEYBOARD_9}, + {.width = 1, .icon = NULL, .key = "0", .shift_key = ")", .value = HID_KEYBOARD_0}, + {.width = 2, .icon = &I_Pin_arrow_left_9x7, .value = HID_KEYBOARD_DELETE}, + {.width = 0, .value = HID_KEYBOARD_DELETE}, + }, + { + {.width = 1, .icon = NULL, .key = "q", .shift_key = "Q", .value = HID_KEYBOARD_Q}, + {.width = 1, .icon = NULL, .key = "w", .shift_key = "W", .value = HID_KEYBOARD_W}, + {.width = 1, .icon = NULL, .key = "e", .shift_key = "E", .value = HID_KEYBOARD_E}, + {.width = 1, .icon = NULL, .key = "r", .shift_key = "R", .value = HID_KEYBOARD_R}, + {.width = 1, .icon = NULL, .key = "t", .shift_key = "T", .value = HID_KEYBOARD_T}, + {.width = 1, .icon = NULL, .key = "y", .shift_key = "Y", .value = HID_KEYBOARD_Y}, + {.width = 1, .icon = NULL, .key = "u", .shift_key = "U", .value = HID_KEYBOARD_U}, + {.width = 1, .icon = NULL, .key = "i", .shift_key = "I", .value = HID_KEYBOARD_I}, + {.width = 1, .icon = NULL, .key = "o", .shift_key = "O", .value = HID_KEYBOARD_O}, + {.width = 1, .icon = NULL, .key = "p", .shift_key = "P", .value = HID_KEYBOARD_P}, + {.width = 1, .icon = NULL, .key = "[", .shift_key = "{", .value = HID_KEYBOARD_OPEN_BRACKET}, + {.width = 1, + .icon = NULL, + .key = "]", + .shift_key = "}", + .value = HID_KEYBOARD_CLOSE_BRACKET}, + }, + { + {.width = 1, .icon = NULL, .key = "a", .shift_key = "A", .value = HID_KEYBOARD_A}, + {.width = 1, .icon = NULL, .key = "s", .shift_key = "S", .value = HID_KEYBOARD_S}, + {.width = 1, .icon = NULL, .key = "d", .shift_key = "D", .value = HID_KEYBOARD_D}, + {.width = 1, .icon = NULL, .key = "f", .shift_key = "F", .value = HID_KEYBOARD_F}, + {.width = 1, .icon = NULL, .key = "g", .shift_key = "G", .value = HID_KEYBOARD_G}, + {.width = 1, .icon = NULL, .key = "h", .shift_key = "H", .value = HID_KEYBOARD_H}, + {.width = 1, .icon = NULL, .key = "j", .shift_key = "J", .value = HID_KEYBOARD_J}, + {.width = 1, .icon = NULL, .key = "k", .shift_key = "K", .value = HID_KEYBOARD_K}, + {.width = 1, .icon = NULL, .key = "l", .shift_key = "L", .value = HID_KEYBOARD_L}, + {.width = 1, .icon = NULL, .key = ";", .shift_key = ":", .value = HID_KEYBOARD_SEMICOLON}, + {.width = 2, .icon = &I_Pin_arrow_right_9x7, .value = HID_KEYBOARD_RETURN}, + {.width = 0, .value = HID_KEYBOARD_RETURN}, + }, + { + {.width = 1, .icon = NULL, .key = "z", .shift_key = "Z", .value = HID_KEYBOARD_Z}, + {.width = 1, .icon = NULL, .key = "x", .shift_key = "X", .value = HID_KEYBOARD_X}, + {.width = 1, .icon = NULL, .key = "c", .shift_key = "C", .value = HID_KEYBOARD_C}, + {.width = 1, .icon = NULL, .key = "v", .shift_key = "V", .value = HID_KEYBOARD_V}, + {.width = 1, .icon = NULL, .key = "b", .shift_key = "B", .value = HID_KEYBOARD_B}, + {.width = 1, .icon = NULL, .key = "n", .shift_key = "N", .value = HID_KEYBOARD_N}, + {.width = 1, .icon = NULL, .key = "m", .shift_key = "M", .value = HID_KEYBOARD_M}, + {.width = 1, .icon = NULL, .key = "/", .shift_key = "?", .value = HID_KEYBOARD_SLASH}, + {.width = 1, .icon = NULL, .key = "\\", .shift_key = "|", .value = HID_KEYBOARD_BACKSLASH}, + {.width = 1, .icon = NULL, .key = "`", .shift_key = "~", .value = HID_KEYBOARD_GRAVE_ACCENT}, + {.width = 1, .icon = &I_ButtonUp_7x4, .value = HID_KEYBOARD_UP_ARROW}, + {.width = 1, .icon = NULL, .key = "-", .shift_key = "_", .value = HID_KEYBOARD_MINUS}, + }, + { + {.width = 1, .icon = &I_Pin_arrow_up_7x9, .value = HID_KEYBOARD_L_SHIFT}, + {.width = 1, .icon = NULL, .key = ",", .shift_key = "<", .value = HID_KEYBOARD_COMMA}, + {.width = 1, .icon = NULL, .key = ".", .shift_key = ">", .value = HID_KEYBOARD_DOT}, + {.width = 4, .icon = NULL, .key = " ", .value = HID_KEYBOARD_SPACEBAR}, + {.width = 0, .value = HID_KEYBOARD_SPACEBAR}, + {.width = 0, .value = HID_KEYBOARD_SPACEBAR}, + {.width = 0, .value = HID_KEYBOARD_SPACEBAR}, + {.width = 1, .icon = NULL, .key = "'", .shift_key = "\"", .value = HID_KEYBOARD_APOSTROPHE}, + {.width = 1, .icon = NULL, .key = "=", .shift_key = "+", .value = HID_KEYBOARD_EQUAL_SIGN}, + {.width = 1, .icon = &I_ButtonLeft_4x7, .value = HID_KEYBOARD_LEFT_ARROW}, + {.width = 1, .icon = &I_ButtonDown_7x4, .value = HID_KEYBOARD_DOWN_ARROW}, + {.width = 1, .icon = &I_ButtonRight_4x7, .value = HID_KEYBOARD_RIGHT_ARROW}, + }, + { + {.width = 2, .icon = &I_Ctrl_15x7, .value = HID_KEYBOARD_L_CTRL}, + {.width = 0, .value = HID_KEYBOARD_L_CTRL}, + {.width = 2, .icon = &I_Alt_11x7, .value = HID_KEYBOARD_L_ALT}, + {.width = 0, .value = HID_KEYBOARD_L_ALT}, + {.width = 2, .icon = &I_Cmd_15x7, .value = HID_KEYBOARD_L_GUI}, + {.width = 0, .value = HID_KEYBOARD_L_GUI}, + {.width = 2, .icon = &I_Tab_15x7, .value = HID_KEYBOARD_TAB}, + {.width = 0, .value = HID_KEYBOARD_TAB}, + {.width = 2, .icon = &I_Esc_14x7, .value = HID_KEYBOARD_ESCAPE}, + {.width = 0, .value = HID_KEYBOARD_ESCAPE}, + {.width = 2, .icon = &I_Del_12x7, .value = HID_KEYBOARD_DELETE_FORWARD}, + {.width = 0, .value = HID_KEYBOARD_DELETE_FORWARD}, + }, +}; + +static void hid_keyboard_to_upper(char* str) { + while(*str) { + *str = toupper((unsigned char)*str); + str++; + } +} + +static void hid_keyboard_draw_key( + Canvas* canvas, + HidKeyboardModel* model, + uint8_t x, + uint8_t y, + HidKeyboardKey key, + bool selected) { + if(!key.width) return; + + canvas_set_color(canvas, ColorBlack); + uint8_t keyWidth = KEY_WIDTH * key.width + KEY_PADDING * (key.width - 1); + if(selected) { + // Draw a filled box + elements_slightly_rounded_box( + canvas, + MARGIN_LEFT + x * (KEY_WIDTH + KEY_PADDING), + MARGIN_TOP + y * (KEY_HEIGHT + KEY_PADDING), + keyWidth, + KEY_HEIGHT); + canvas_set_color(canvas, ColorWhite); + } else { + // Draw a framed box + elements_slightly_rounded_frame( + canvas, + MARGIN_LEFT + x * (KEY_WIDTH + KEY_PADDING), + MARGIN_TOP + y * (KEY_HEIGHT + KEY_PADDING), + keyWidth, + KEY_HEIGHT); + } + if(key.icon != NULL) { + // Draw the icon centered on the button + canvas_draw_icon( + canvas, + MARGIN_LEFT + x * (KEY_WIDTH + KEY_PADDING) + keyWidth / 2 - key.icon->width / 2, + MARGIN_TOP + y * (KEY_HEIGHT + KEY_PADDING) + KEY_HEIGHT / 2 - key.icon->height / 2, + key.icon); + } else { + // If shift is toggled use the shift key when available + strcpy(model->key_string, (model->shift && key.shift_key != 0) ? key.shift_key : key.key); + // Upper case if ctrl or alt was toggled true + if((model->ctrl && key.value == HID_KEYBOARD_L_CTRL) || + (model->alt && key.value == HID_KEYBOARD_L_ALT) || + (model->gui && key.value == HID_KEYBOARD_L_GUI)) { + hid_keyboard_to_upper(model->key_string); + } + canvas_draw_str_aligned( + canvas, + MARGIN_LEFT + x * (KEY_WIDTH + KEY_PADDING) + keyWidth / 2 + 1, + MARGIN_TOP + y * (KEY_HEIGHT + KEY_PADDING) + KEY_HEIGHT / 2, + AlignCenter, + AlignCenter, + model->key_string); + } +} + +static void hid_keyboard_draw_callback(Canvas* canvas, void* context) { + furi_assert(context); + HidKeyboardModel* model = context; + + // Header + if((!model->connected) && (model->transport == HidTransportBle)) { + canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); + canvas_set_font(canvas, FontPrimary); + elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Keyboard"); + + canvas_draw_icon(canvas, 68, 3, &I_Pin_back_arrow_10x8); + canvas_set_font(canvas, FontSecondary); + elements_multiline_text_aligned(canvas, 127, 4, AlignRight, AlignTop, "Hold to exit"); + + elements_multiline_text_aligned( + canvas, 4, 60, AlignLeft, AlignBottom, "Waiting for Connection..."); + return; // Dont render the keyboard if we are not yet connected + } + + canvas_set_font(canvas, FontKeyboard); + // Start shifting the all keys up if on the next row (Scrolling) + uint8_t initY = model->y == 0 ? 0 : 1; + + if(model->y > 5) { + initY = model->y - 4; + } + + for(uint8_t y = initY; y < ROW_COUNT; y++) { + const HidKeyboardKey* keyboardKeyRow = hid_keyboard_keyset[y]; + uint8_t x = 0; + for(uint8_t i = 0; i < COLUMN_COUNT; i++) { + HidKeyboardKey key = keyboardKeyRow[i]; + // Select when the button is hovered + // Select if the button is hovered within its width + // Select if back is clicked and its the backspace key + // Deselect when the button clicked or not hovered + bool keySelected = (x <= model->x && model->x < (x + key.width)) && y == model->y; + bool backSelected = model->back_pressed && key.value == HID_KEYBOARD_DELETE; + hid_keyboard_draw_key( + canvas, + model, + x, + y - initY, + key, + (!model->ok_pressed && keySelected) || backSelected); + x += key.width; + } + } +} + +static uint8_t hid_keyboard_get_selected_key(HidKeyboardModel* model) { + HidKeyboardKey key = hid_keyboard_keyset[model->y][model->x]; + return key.value; +} + +static void hid_keyboard_get_select_key(HidKeyboardModel* model, HidKeyboardPoint delta) { + // Keep going until a valid spot is found, this allows for nulls and zero width keys in the map + do { + const int delta_sum = model->y + delta.y; + model->y = delta_sum < 0 ? ROW_COUNT - 1 : delta_sum % ROW_COUNT; + } while(delta.y != 0 && hid_keyboard_keyset[model->y][model->x].value == 0); + + do { + const int delta_sum = model->x + delta.x; + model->x = delta_sum < 0 ? COLUMN_COUNT - 1 : delta_sum % COLUMN_COUNT; + } while(delta.x != 0 && hid_keyboard_keyset[model->y][model->x].width == + 0); // Skip zero width keys, pretend they are one key +} + +static void hid_keyboard_process(HidKeyboard* hid_keyboard, InputEvent* event) { + with_view_model( + hid_keyboard->view, + HidKeyboardModel * model, + { + if(event->key == InputKeyOk) { + if(event->type == InputTypePress) { + model->ok_pressed = true; + } else if(event->type == InputTypeLong || event->type == InputTypeShort) { + model->last_key_code = hid_keyboard_get_selected_key(model); + + // Toggle the modifier key when clicked, and click the key + if(model->last_key_code == HID_KEYBOARD_L_SHIFT) { + model->shift = !model->shift; + if(model->shift) + model->modifier_code |= KEY_MOD_LEFT_SHIFT; + else + model->modifier_code &= ~KEY_MOD_LEFT_SHIFT; + } else if(model->last_key_code == HID_KEYBOARD_L_ALT) { + model->alt = !model->alt; + if(model->alt) + model->modifier_code |= KEY_MOD_LEFT_ALT; + else + model->modifier_code &= ~KEY_MOD_LEFT_ALT; + } else if(model->last_key_code == HID_KEYBOARD_L_CTRL) { + model->ctrl = !model->ctrl; + if(model->ctrl) + model->modifier_code |= KEY_MOD_LEFT_CTRL; + else + model->modifier_code &= ~KEY_MOD_LEFT_CTRL; + } else if(model->last_key_code == HID_KEYBOARD_L_GUI) { + model->gui = !model->gui; + if(model->gui) + model->modifier_code |= KEY_MOD_LEFT_GUI; + else + model->modifier_code &= ~KEY_MOD_LEFT_GUI; + } + hid_hal_keyboard_press( + hid_keyboard->hid, model->modifier_code | model->last_key_code); + } else if(event->type == InputTypeRelease) { + // Release happens after short and long presses + hid_hal_keyboard_release( + hid_keyboard->hid, model->modifier_code | model->last_key_code); + model->ok_pressed = false; + } + } else if(event->key == InputKeyBack) { + // If back is pressed for a short time, backspace + if(event->type == InputTypePress) { + model->back_pressed = true; + } else if(event->type == InputTypeShort) { + hid_hal_keyboard_press(hid_keyboard->hid, HID_KEYBOARD_DELETE); + hid_hal_keyboard_release(hid_keyboard->hid, HID_KEYBOARD_DELETE); + } else if(event->type == InputTypeRelease) { + model->back_pressed = false; + } + } else if(event->type == InputTypePress || event->type == InputTypeRepeat) { + // Cycle the selected keys + if(event->key == InputKeyUp) { + hid_keyboard_get_select_key(model, (HidKeyboardPoint){.x = 0, .y = -1}); + } else if(event->key == InputKeyDown) { + hid_keyboard_get_select_key(model, (HidKeyboardPoint){.x = 0, .y = 1}); + } else if(event->key == InputKeyLeft) { + hid_keyboard_get_select_key(model, (HidKeyboardPoint){.x = -1, .y = 0}); + } else if(event->key == InputKeyRight) { + hid_keyboard_get_select_key(model, (HidKeyboardPoint){.x = 1, .y = 0}); + } + } + }, + true); +} + +static bool hid_keyboard_input_callback(InputEvent* event, void* context) { + furi_assert(context); + HidKeyboard* hid_keyboard = context; + bool consumed = false; + + if(event->type == InputTypeLong && event->key == InputKeyBack) { + hid_hal_keyboard_release_all(hid_keyboard->hid); + } else { + hid_keyboard_process(hid_keyboard, event); + consumed = true; + } + + return consumed; +} + +HidKeyboard* hid_keyboard_alloc(Hid* bt_hid) { + HidKeyboard* hid_keyboard = malloc(sizeof(HidKeyboard)); + hid_keyboard->view = view_alloc(); + hid_keyboard->hid = bt_hid; + view_set_context(hid_keyboard->view, hid_keyboard); + view_allocate_model(hid_keyboard->view, ViewModelTypeLocking, sizeof(HidKeyboardModel)); + view_set_draw_callback(hid_keyboard->view, hid_keyboard_draw_callback); + view_set_input_callback(hid_keyboard->view, hid_keyboard_input_callback); + + with_view_model( + hid_keyboard->view, + HidKeyboardModel * model, + { + model->transport = bt_hid->transport; + model->y = 1; + }, + true); + + return hid_keyboard; +} + +void hid_keyboard_free(HidKeyboard* hid_keyboard) { + furi_assert(hid_keyboard); + view_free(hid_keyboard->view); + free(hid_keyboard); +} + +View* hid_keyboard_get_view(HidKeyboard* hid_keyboard) { + furi_assert(hid_keyboard); + return hid_keyboard->view; +} + +void hid_keyboard_set_connected_status(HidKeyboard* hid_keyboard, bool connected) { + furi_assert(hid_keyboard); + with_view_model( + hid_keyboard->view, HidKeyboardModel * model, { model->connected = connected; }, true); +} diff --git a/applications/system/hid_app/views/hid_keyboard.h b/applications/system/hid_app/views/hid_keyboard.h new file mode 100644 index 00000000000..7127713643b --- /dev/null +++ b/applications/system/hid_app/views/hid_keyboard.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +typedef struct Hid Hid; +typedef struct HidKeyboard HidKeyboard; + +HidKeyboard* hid_keyboard_alloc(Hid* bt_hid); + +void hid_keyboard_free(HidKeyboard* hid_keyboard); + +View* hid_keyboard_get_view(HidKeyboard* hid_keyboard); + +void hid_keyboard_set_connected_status(HidKeyboard* hid_keyboard, bool connected); diff --git a/applications/system/hid_app/views/hid_keynote.c b/applications/system/hid_app/views/hid_keynote.c new file mode 100644 index 00000000000..543363bf67b --- /dev/null +++ b/applications/system/hid_app/views/hid_keynote.c @@ -0,0 +1,312 @@ +#include "hid_keynote.h" +#include +#include "../hid.h" + +#include "hid_icons.h" + +#define TAG "HidKeynote" + +struct HidKeynote { + View* view; + Hid* hid; +}; + +typedef struct { + bool left_pressed; + bool up_pressed; + bool right_pressed; + bool down_pressed; + bool ok_pressed; + bool back_pressed; + bool connected; + HidTransport transport; +} HidKeynoteModel; + +static void hid_keynote_draw_arrow(Canvas* canvas, uint8_t x, uint8_t y, CanvasDirection dir) { + canvas_draw_triangle(canvas, x, y, 5, 3, dir); + if(dir == CanvasDirectionBottomToTop) { + canvas_draw_line(canvas, x, y + 6, x, y - 1); + } else if(dir == CanvasDirectionTopToBottom) { + canvas_draw_line(canvas, x, y - 6, x, y + 1); + } else if(dir == CanvasDirectionRightToLeft) { + canvas_draw_line(canvas, x + 6, y, x - 1, y); + } else if(dir == CanvasDirectionLeftToRight) { + canvas_draw_line(canvas, x - 6, y, x + 1, y); + } +} + +static void hid_keynote_draw_callback(Canvas* canvas, void* context) { + furi_assert(context); + HidKeynoteModel* model = context; + + // Header + if(model->transport == HidTransportBle) { + if(model->connected) { + canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); + } else { + canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); + } + } + + canvas_set_font(canvas, FontPrimary); + elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Keynote"); + + canvas_draw_icon(canvas, 68, 2, &I_Pin_back_arrow_10x8); + canvas_set_font(canvas, FontSecondary); + elements_multiline_text_aligned(canvas, 127, 3, AlignRight, AlignTop, "Hold to exit"); + + // Up + canvas_draw_icon(canvas, 21, 24, &I_Button_18x18); + if(model->up_pressed) { + elements_slightly_rounded_box(canvas, 24, 26, 13, 13); + canvas_set_color(canvas, ColorWhite); + } + hid_keynote_draw_arrow(canvas, 30, 30, CanvasDirectionBottomToTop); + canvas_set_color(canvas, ColorBlack); + + // Down + canvas_draw_icon(canvas, 21, 45, &I_Button_18x18); + if(model->down_pressed) { + elements_slightly_rounded_box(canvas, 24, 47, 13, 13); + canvas_set_color(canvas, ColorWhite); + } + hid_keynote_draw_arrow(canvas, 30, 55, CanvasDirectionTopToBottom); + canvas_set_color(canvas, ColorBlack); + + // Left + canvas_draw_icon(canvas, 0, 45, &I_Button_18x18); + if(model->left_pressed) { + elements_slightly_rounded_box(canvas, 3, 47, 13, 13); + canvas_set_color(canvas, ColorWhite); + } + hid_keynote_draw_arrow(canvas, 7, 53, CanvasDirectionRightToLeft); + canvas_set_color(canvas, ColorBlack); + + // Right + canvas_draw_icon(canvas, 42, 45, &I_Button_18x18); + if(model->right_pressed) { + elements_slightly_rounded_box(canvas, 45, 47, 13, 13); + canvas_set_color(canvas, ColorWhite); + } + hid_keynote_draw_arrow(canvas, 53, 53, CanvasDirectionLeftToRight); + canvas_set_color(canvas, ColorBlack); + + // Ok + canvas_draw_icon(canvas, 63, 25, &I_Space_65x18); + if(model->ok_pressed) { + elements_slightly_rounded_box(canvas, 66, 27, 60, 13); + canvas_set_color(canvas, ColorWhite); + } + canvas_draw_icon(canvas, 74, 29, &I_Ok_btn_9x9); + elements_multiline_text_aligned(canvas, 91, 36, AlignLeft, AlignBottom, "Space"); + canvas_set_color(canvas, ColorBlack); + + // Back + canvas_draw_icon(canvas, 63, 45, &I_Space_65x18); + if(model->back_pressed) { + elements_slightly_rounded_box(canvas, 66, 47, 60, 13); + canvas_set_color(canvas, ColorWhite); + } + canvas_draw_icon(canvas, 74, 49, &I_Pin_back_arrow_10x8); + elements_multiline_text_aligned(canvas, 91, 57, AlignLeft, AlignBottom, "Back"); +} + +static void hid_keynote_draw_vertical_callback(Canvas* canvas, void* context) { + furi_assert(context); + HidKeynoteModel* model = context; + + // Header + if(model->transport == HidTransportBle) { + if(model->connected) { + canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); + } else { + canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); + } + canvas_set_font(canvas, FontPrimary); + elements_multiline_text_aligned(canvas, 20, 3, AlignLeft, AlignTop, "Keynote"); + } else { + canvas_set_font(canvas, FontPrimary); + elements_multiline_text_aligned(canvas, 12, 3, AlignLeft, AlignTop, "Keynote"); + } + + canvas_draw_icon(canvas, 2, 18, &I_Pin_back_arrow_10x8); + canvas_set_font(canvas, FontSecondary); + elements_multiline_text_aligned(canvas, 15, 19, AlignLeft, AlignTop, "Hold to exit"); + + const uint8_t x_2 = 23; + const uint8_t x_1 = 2; + const uint8_t x_3 = 44; + + const uint8_t y_1 = 44; + const uint8_t y_2 = 65; + + // Up + canvas_draw_icon(canvas, x_2, y_1, &I_Button_18x18); + if(model->up_pressed) { + elements_slightly_rounded_box(canvas, x_2 + 3, y_1 + 2, 13, 13); + canvas_set_color(canvas, ColorWhite); + } + hid_keynote_draw_arrow(canvas, x_2 + 9, y_1 + 6, CanvasDirectionBottomToTop); + canvas_set_color(canvas, ColorBlack); + + // Down + canvas_draw_icon(canvas, x_2, y_2, &I_Button_18x18); + if(model->down_pressed) { + elements_slightly_rounded_box(canvas, x_2 + 3, y_2 + 2, 13, 13); + canvas_set_color(canvas, ColorWhite); + } + hid_keynote_draw_arrow(canvas, x_2 + 9, y_2 + 10, CanvasDirectionTopToBottom); + canvas_set_color(canvas, ColorBlack); + + // Left + canvas_draw_icon(canvas, x_1, y_2, &I_Button_18x18); + if(model->left_pressed) { + elements_slightly_rounded_box(canvas, x_1 + 3, y_2 + 2, 13, 13); + canvas_set_color(canvas, ColorWhite); + } + hid_keynote_draw_arrow(canvas, x_1 + 7, y_2 + 8, CanvasDirectionRightToLeft); + canvas_set_color(canvas, ColorBlack); + + // Right + canvas_draw_icon(canvas, x_3, y_2, &I_Button_18x18); + if(model->right_pressed) { + elements_slightly_rounded_box(canvas, x_3 + 3, y_2 + 2, 13, 13); + canvas_set_color(canvas, ColorWhite); + } + hid_keynote_draw_arrow(canvas, x_3 + 11, y_2 + 8, CanvasDirectionLeftToRight); + canvas_set_color(canvas, ColorBlack); + + // Ok + canvas_draw_icon(canvas, 2, 86, &I_Space_60x18); + if(model->ok_pressed) { + elements_slightly_rounded_box(canvas, 5, 88, 55, 13); + canvas_set_color(canvas, ColorWhite); + } + canvas_draw_icon(canvas, 11, 90, &I_Ok_btn_9x9); + elements_multiline_text_aligned(canvas, 26, 98, AlignLeft, AlignBottom, "Space"); + canvas_set_color(canvas, ColorBlack); + + // Back + canvas_draw_icon(canvas, 2, 107, &I_Space_60x18); + if(model->back_pressed) { + elements_slightly_rounded_box(canvas, 5, 109, 55, 13); + canvas_set_color(canvas, ColorWhite); + } + canvas_draw_icon(canvas, 11, 111, &I_Pin_back_arrow_10x8); + elements_multiline_text_aligned(canvas, 26, 119, AlignLeft, AlignBottom, "Back"); +} + +static void hid_keynote_process(HidKeynote* hid_keynote, InputEvent* event) { + with_view_model( + hid_keynote->view, + HidKeynoteModel * model, + { + if(event->type == InputTypePress) { + if(event->key == InputKeyUp) { + model->up_pressed = true; + hid_hal_keyboard_press(hid_keynote->hid, HID_KEYBOARD_UP_ARROW); + } else if(event->key == InputKeyDown) { + model->down_pressed = true; + hid_hal_keyboard_press(hid_keynote->hid, HID_KEYBOARD_DOWN_ARROW); + } else if(event->key == InputKeyLeft) { + model->left_pressed = true; + hid_hal_keyboard_press(hid_keynote->hid, HID_KEYBOARD_LEFT_ARROW); + } else if(event->key == InputKeyRight) { + model->right_pressed = true; + hid_hal_keyboard_press(hid_keynote->hid, HID_KEYBOARD_RIGHT_ARROW); + } else if(event->key == InputKeyOk) { + model->ok_pressed = true; + hid_hal_keyboard_press(hid_keynote->hid, HID_KEYBOARD_SPACEBAR); + } else if(event->key == InputKeyBack) { + model->back_pressed = true; + } + } else if(event->type == InputTypeRelease) { + if(event->key == InputKeyUp) { + model->up_pressed = false; + hid_hal_keyboard_release(hid_keynote->hid, HID_KEYBOARD_UP_ARROW); + } else if(event->key == InputKeyDown) { + model->down_pressed = false; + hid_hal_keyboard_release(hid_keynote->hid, HID_KEYBOARD_DOWN_ARROW); + } else if(event->key == InputKeyLeft) { + model->left_pressed = false; + hid_hal_keyboard_release(hid_keynote->hid, HID_KEYBOARD_LEFT_ARROW); + } else if(event->key == InputKeyRight) { + model->right_pressed = false; + hid_hal_keyboard_release(hid_keynote->hid, HID_KEYBOARD_RIGHT_ARROW); + } else if(event->key == InputKeyOk) { + model->ok_pressed = false; + hid_hal_keyboard_release(hid_keynote->hid, HID_KEYBOARD_SPACEBAR); + } else if(event->key == InputKeyBack) { + model->back_pressed = false; + } + } else if(event->type == InputTypeShort) { + if(event->key == InputKeyBack) { + hid_hal_keyboard_press(hid_keynote->hid, HID_KEYBOARD_DELETE); + hid_hal_keyboard_release(hid_keynote->hid, HID_KEYBOARD_DELETE); + hid_hal_consumer_key_press(hid_keynote->hid, HID_CONSUMER_AC_BACK); + hid_hal_consumer_key_release(hid_keynote->hid, HID_CONSUMER_AC_BACK); + } + } + }, + true); +} + +static bool hid_keynote_input_callback(InputEvent* event, void* context) { + furi_assert(context); + HidKeynote* hid_keynote = context; + bool consumed = false; + + if(event->type == InputTypeLong && event->key == InputKeyBack) { + hid_hal_keyboard_release_all(hid_keynote->hid); + } else { + hid_keynote_process(hid_keynote, event); + consumed = true; + } + + return consumed; +} + +HidKeynote* hid_keynote_alloc(Hid* hid) { + HidKeynote* hid_keynote = malloc(sizeof(HidKeynote)); + hid_keynote->view = view_alloc(); + hid_keynote->hid = hid; + view_set_context(hid_keynote->view, hid_keynote); + view_allocate_model(hid_keynote->view, ViewModelTypeLocking, sizeof(HidKeynoteModel)); + view_set_draw_callback(hid_keynote->view, hid_keynote_draw_callback); + view_set_input_callback(hid_keynote->view, hid_keynote_input_callback); + + with_view_model( + hid_keynote->view, HidKeynoteModel * model, { model->transport = hid->transport; }, true); + + return hid_keynote; +} + +void hid_keynote_free(HidKeynote* hid_keynote) { + furi_assert(hid_keynote); + view_free(hid_keynote->view); + free(hid_keynote); +} + +View* hid_keynote_get_view(HidKeynote* hid_keynote) { + furi_assert(hid_keynote); + return hid_keynote->view; +} + +void hid_keynote_set_connected_status(HidKeynote* hid_keynote, bool connected) { + furi_assert(hid_keynote); + with_view_model( + hid_keynote->view, HidKeynoteModel * model, { model->connected = connected; }, true); +} + +void hid_keynote_set_orientation(HidKeynote* hid_keynote, bool vertical) { + furi_assert(hid_keynote); + + if(vertical) { + view_set_draw_callback(hid_keynote->view, hid_keynote_draw_vertical_callback); + view_set_orientation(hid_keynote->view, ViewOrientationVerticalFlip); + + } else { + view_set_draw_callback(hid_keynote->view, hid_keynote_draw_callback); + view_set_orientation(hid_keynote->view, ViewOrientationHorizontal); + } +} diff --git a/applications/system/hid_app/views/hid_keynote.h b/applications/system/hid_app/views/hid_keynote.h new file mode 100644 index 00000000000..84bfed4ce41 --- /dev/null +++ b/applications/system/hid_app/views/hid_keynote.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +typedef struct Hid Hid; +typedef struct HidKeynote HidKeynote; + +HidKeynote* hid_keynote_alloc(Hid* bt_hid); + +void hid_keynote_free(HidKeynote* hid_keynote); + +View* hid_keynote_get_view(HidKeynote* hid_keynote); + +void hid_keynote_set_connected_status(HidKeynote* hid_keynote, bool connected); + +void hid_keynote_set_orientation(HidKeynote* hid_keynote, bool vertical); diff --git a/applications/system/hid_app/views/hid_media.c b/applications/system/hid_app/views/hid_media.c new file mode 100644 index 00000000000..468529d56a8 --- /dev/null +++ b/applications/system/hid_app/views/hid_media.c @@ -0,0 +1,218 @@ +#include "hid_media.h" +#include +#include +#include +#include +#include "../hid.h" + +#include "hid_icons.h" + +#define TAG "HidMedia" + +struct HidMedia { + View* view; + Hid* hid; +}; + +typedef struct { + bool left_pressed; + bool up_pressed; + bool right_pressed; + bool down_pressed; + bool ok_pressed; + bool connected; + HidTransport transport; +} HidMediaModel; + +static void hid_media_draw_arrow(Canvas* canvas, uint8_t x, uint8_t y, CanvasDirection dir) { + canvas_draw_triangle(canvas, x, y, 5, 3, dir); + if(dir == CanvasDirectionBottomToTop) { + canvas_draw_dot(canvas, x, y - 1); + } else if(dir == CanvasDirectionTopToBottom) { + canvas_draw_dot(canvas, x, y + 1); + } else if(dir == CanvasDirectionRightToLeft) { + canvas_draw_dot(canvas, x - 1, y); + } else if(dir == CanvasDirectionLeftToRight) { + canvas_draw_dot(canvas, x + 1, y); + } +} + +static void hid_media_draw_callback(Canvas* canvas, void* context) { + furi_assert(context); + HidMediaModel* model = context; + + // Header + if(model->transport == HidTransportBle) { + if(model->connected) { + canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); + } else { + canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); + } + } + + canvas_set_font(canvas, FontPrimary); + elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Media"); + canvas_set_font(canvas, FontSecondary); + + // Keypad circles + canvas_draw_icon(canvas, 76, 8, &I_Circles_47x47); + + // Up + if(model->up_pressed) { + canvas_set_bitmap_mode(canvas, 1); + canvas_draw_icon(canvas, 93, 9, &I_Pressed_Button_13x13); + canvas_set_bitmap_mode(canvas, 0); + canvas_set_color(canvas, ColorWhite); + } + canvas_draw_icon(canvas, 96, 12, &I_Volup_8x6); + canvas_set_color(canvas, ColorBlack); + + // Down + if(model->down_pressed) { + canvas_set_bitmap_mode(canvas, 1); + canvas_draw_icon(canvas, 93, 41, &I_Pressed_Button_13x13); + canvas_set_bitmap_mode(canvas, 0); + canvas_set_color(canvas, ColorWhite); + } + canvas_draw_icon(canvas, 96, 45, &I_Voldwn_6x6); + canvas_set_color(canvas, ColorBlack); + + // Left + if(model->left_pressed) { + canvas_set_bitmap_mode(canvas, 1); + canvas_draw_icon(canvas, 77, 25, &I_Pressed_Button_13x13); + canvas_set_bitmap_mode(canvas, 0); + canvas_set_color(canvas, ColorWhite); + } + hid_media_draw_arrow(canvas, 82, 31, CanvasDirectionRightToLeft); + hid_media_draw_arrow(canvas, 86, 31, CanvasDirectionRightToLeft); + canvas_set_color(canvas, ColorBlack); + + // Right + if(model->right_pressed) { + canvas_set_bitmap_mode(canvas, 1); + canvas_draw_icon(canvas, 109, 25, &I_Pressed_Button_13x13); + canvas_set_bitmap_mode(canvas, 0); + canvas_set_color(canvas, ColorWhite); + } + hid_media_draw_arrow(canvas, 112, 31, CanvasDirectionLeftToRight); + hid_media_draw_arrow(canvas, 116, 31, CanvasDirectionLeftToRight); + canvas_set_color(canvas, ColorBlack); + + // Ok + if(model->ok_pressed) { + canvas_draw_icon(canvas, 93, 25, &I_Pressed_Button_13x13); + canvas_set_color(canvas, ColorWhite); + } + hid_media_draw_arrow(canvas, 96, 31, CanvasDirectionLeftToRight); + canvas_draw_line(canvas, 100, 29, 100, 33); + canvas_draw_line(canvas, 102, 29, 102, 33); + canvas_set_color(canvas, ColorBlack); + + // Exit + canvas_draw_icon(canvas, 0, 54, &I_Pin_back_arrow_10x8); + canvas_set_font(canvas, FontSecondary); + elements_multiline_text_aligned(canvas, 13, 62, AlignLeft, AlignBottom, "Hold to exit"); +} + +static void hid_media_process_press(HidMedia* hid_media, InputEvent* event) { + with_view_model( + hid_media->view, + HidMediaModel * model, + { + if(event->key == InputKeyUp) { + model->up_pressed = true; + hid_hal_consumer_key_press(hid_media->hid, HID_CONSUMER_VOLUME_INCREMENT); + } else if(event->key == InputKeyDown) { + model->down_pressed = true; + hid_hal_consumer_key_press(hid_media->hid, HID_CONSUMER_VOLUME_DECREMENT); + } else if(event->key == InputKeyLeft) { + model->left_pressed = true; + hid_hal_consumer_key_press(hid_media->hid, HID_CONSUMER_SCAN_PREVIOUS_TRACK); + } else if(event->key == InputKeyRight) { + model->right_pressed = true; + hid_hal_consumer_key_press(hid_media->hid, HID_CONSUMER_SCAN_NEXT_TRACK); + } else if(event->key == InputKeyOk) { + model->ok_pressed = true; + hid_hal_consumer_key_press(hid_media->hid, HID_CONSUMER_PLAY_PAUSE); + } + }, + true); +} + +static void hid_media_process_release(HidMedia* hid_media, InputEvent* event) { + with_view_model( + hid_media->view, + HidMediaModel * model, + { + if(event->key == InputKeyUp) { + model->up_pressed = false; + hid_hal_consumer_key_release(hid_media->hid, HID_CONSUMER_VOLUME_INCREMENT); + } else if(event->key == InputKeyDown) { + model->down_pressed = false; + hid_hal_consumer_key_release(hid_media->hid, HID_CONSUMER_VOLUME_DECREMENT); + } else if(event->key == InputKeyLeft) { + model->left_pressed = false; + hid_hal_consumer_key_release(hid_media->hid, HID_CONSUMER_SCAN_PREVIOUS_TRACK); + } else if(event->key == InputKeyRight) { + model->right_pressed = false; + hid_hal_consumer_key_release(hid_media->hid, HID_CONSUMER_SCAN_NEXT_TRACK); + } else if(event->key == InputKeyOk) { + model->ok_pressed = false; + hid_hal_consumer_key_release(hid_media->hid, HID_CONSUMER_PLAY_PAUSE); + } + }, + true); +} + +static bool hid_media_input_callback(InputEvent* event, void* context) { + furi_assert(context); + HidMedia* hid_media = context; + bool consumed = false; + + if(event->type == InputTypePress) { + hid_media_process_press(hid_media, event); + consumed = true; + } else if(event->type == InputTypeRelease) { + hid_media_process_release(hid_media, event); + consumed = true; + } else if(event->type == InputTypeShort) { + if(event->key == InputKeyBack) { + hid_hal_consumer_key_release_all(hid_media->hid); + } + } + + return consumed; +} + +HidMedia* hid_media_alloc(Hid* hid) { + HidMedia* hid_media = malloc(sizeof(HidMedia)); + hid_media->view = view_alloc(); + hid_media->hid = hid; + view_set_context(hid_media->view, hid_media); + view_allocate_model(hid_media->view, ViewModelTypeLocking, sizeof(HidMediaModel)); + view_set_draw_callback(hid_media->view, hid_media_draw_callback); + view_set_input_callback(hid_media->view, hid_media_input_callback); + + with_view_model( + hid_media->view, HidMediaModel * model, { model->transport = hid->transport; }, true); + + return hid_media; +} + +void hid_media_free(HidMedia* hid_media) { + furi_assert(hid_media); + view_free(hid_media->view); + free(hid_media); +} + +View* hid_media_get_view(HidMedia* hid_media) { + furi_assert(hid_media); + return hid_media->view; +} + +void hid_media_set_connected_status(HidMedia* hid_media, bool connected) { + furi_assert(hid_media); + with_view_model( + hid_media->view, HidMediaModel * model, { model->connected = connected; }, true); +} diff --git a/applications/system/hid_app/views/hid_media.h b/applications/system/hid_app/views/hid_media.h new file mode 100644 index 00000000000..4aa51dc173b --- /dev/null +++ b/applications/system/hid_app/views/hid_media.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +typedef struct HidMedia HidMedia; + +HidMedia* hid_media_alloc(); + +void hid_media_free(HidMedia* hid_media); + +View* hid_media_get_view(HidMedia* hid_media); + +void hid_media_set_connected_status(HidMedia* hid_media, bool connected); diff --git a/applications/system/hid_app/views/hid_mouse.c b/applications/system/hid_app/views/hid_mouse.c new file mode 100644 index 00000000000..30a9d9d0635 --- /dev/null +++ b/applications/system/hid_app/views/hid_mouse.c @@ -0,0 +1,226 @@ +#include "hid_mouse.h" +#include +#include "../hid.h" + +#include "hid_icons.h" + +#define TAG "HidMouse" + +struct HidMouse { + View* view; + Hid* hid; +}; + +typedef struct { + bool left_pressed; + bool up_pressed; + bool right_pressed; + bool down_pressed; + bool left_mouse_pressed; + bool left_mouse_held; + bool right_mouse_pressed; + bool connected; + HidTransport transport; +} HidMouseModel; + +static void hid_mouse_draw_callback(Canvas* canvas, void* context) { + furi_assert(context); + HidMouseModel* model = context; + + // Header + if(model->transport == HidTransportBle) { + if(model->connected) { + canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); + } else { + canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); + } + } + + canvas_set_font(canvas, FontPrimary); + elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Mouse"); + canvas_set_font(canvas, FontSecondary); + + if(model->left_mouse_held == true) { + elements_multiline_text_aligned(canvas, 0, 62, AlignLeft, AlignBottom, "Selecting..."); + } else { + canvas_draw_icon(canvas, 0, 54, &I_Pin_back_arrow_10x8); + canvas_set_font(canvas, FontSecondary); + elements_multiline_text_aligned(canvas, 13, 62, AlignLeft, AlignBottom, "Hold to exit"); + } + + // Keypad circles + canvas_draw_icon(canvas, 64, 8, &I_Circles_47x47); + + // Up + if(model->up_pressed) { + canvas_set_bitmap_mode(canvas, 1); + canvas_draw_icon(canvas, 81, 9, &I_Pressed_Button_13x13); + canvas_set_bitmap_mode(canvas, 0); + canvas_set_color(canvas, ColorWhite); + } + canvas_draw_icon(canvas, 84, 10, &I_Pin_arrow_up_7x9); + canvas_set_color(canvas, ColorBlack); + + // Down + if(model->down_pressed) { + canvas_set_bitmap_mode(canvas, 1); + canvas_draw_icon(canvas, 81, 41, &I_Pressed_Button_13x13); + canvas_set_bitmap_mode(canvas, 0); + canvas_set_color(canvas, ColorWhite); + } + canvas_draw_icon(canvas, 84, 43, &I_Pin_arrow_down_7x9); + canvas_set_color(canvas, ColorBlack); + + // Left + if(model->left_pressed) { + canvas_set_bitmap_mode(canvas, 1); + canvas_draw_icon(canvas, 65, 25, &I_Pressed_Button_13x13); + canvas_set_bitmap_mode(canvas, 0); + canvas_set_color(canvas, ColorWhite); + } + canvas_draw_icon(canvas, 67, 28, &I_Pin_arrow_left_9x7); + canvas_set_color(canvas, ColorBlack); + + // Right + if(model->right_pressed) { + canvas_set_bitmap_mode(canvas, 1); + canvas_draw_icon(canvas, 97, 25, &I_Pressed_Button_13x13); + canvas_set_bitmap_mode(canvas, 0); + canvas_set_color(canvas, ColorWhite); + } + canvas_draw_icon(canvas, 99, 28, &I_Pin_arrow_right_9x7); + canvas_set_color(canvas, ColorBlack); + + // Ok + if(model->left_mouse_pressed) { + canvas_draw_icon(canvas, 81, 25, &I_Ok_btn_pressed_13x13); + } else { + canvas_draw_icon(canvas, 83, 27, &I_Left_mouse_icon_9x9); + } + + // Back + if(model->right_mouse_pressed) { + canvas_draw_icon(canvas, 108, 48, &I_Ok_btn_pressed_13x13); + } else { + canvas_draw_icon(canvas, 110, 50, &I_Right_mouse_icon_9x9); + } +} + +static void hid_mouse_process(HidMouse* hid_mouse, InputEvent* event) { + with_view_model( + hid_mouse->view, + HidMouseModel * model, + { + if(event->key == InputKeyBack) { + if(event->type == InputTypeShort) { + hid_hal_mouse_press(hid_mouse->hid, HID_MOUSE_BTN_RIGHT); + hid_hal_mouse_release(hid_mouse->hid, HID_MOUSE_BTN_RIGHT); + } else if(event->type == InputTypePress) { + model->right_mouse_pressed = true; + } else if(event->type == InputTypeRelease) { + model->right_mouse_pressed = false; + } + } else if(event->key == InputKeyOk) { + if(event->type == InputTypeShort) { + // Just release if it was being held before + if(!model->left_mouse_held) + hid_hal_mouse_press(hid_mouse->hid, HID_MOUSE_BTN_LEFT); + hid_hal_mouse_release(hid_mouse->hid, HID_MOUSE_BTN_LEFT); + model->left_mouse_held = false; + } else if(event->type == InputTypeLong) { + hid_hal_mouse_press(hid_mouse->hid, HID_MOUSE_BTN_LEFT); + model->left_mouse_held = true; + model->left_mouse_pressed = true; + } else if(event->type == InputTypePress) { + model->left_mouse_pressed = true; + } else if(event->type == InputTypeRelease) { + // Only release if it wasn't a long press + if(!model->left_mouse_held) model->left_mouse_pressed = false; + } + } else if(event->key == InputKeyRight) { + if(event->type == InputTypePress) { + model->right_pressed = true; + hid_hal_mouse_move(hid_mouse->hid, MOUSE_MOVE_SHORT, 0); + } else if(event->type == InputTypeRepeat) { + hid_hal_mouse_move(hid_mouse->hid, MOUSE_MOVE_LONG, 0); + } else if(event->type == InputTypeRelease) { + model->right_pressed = false; + } + } else if(event->key == InputKeyLeft) { + if(event->type == InputTypePress) { + model->left_pressed = true; + hid_hal_mouse_move(hid_mouse->hid, -MOUSE_MOVE_SHORT, 0); + } else if(event->type == InputTypeRepeat) { + hid_hal_mouse_move(hid_mouse->hid, -MOUSE_MOVE_LONG, 0); + } else if(event->type == InputTypeRelease) { + model->left_pressed = false; + } + } else if(event->key == InputKeyDown) { + if(event->type == InputTypePress) { + model->down_pressed = true; + hid_hal_mouse_move(hid_mouse->hid, 0, MOUSE_MOVE_SHORT); + } else if(event->type == InputTypeRepeat) { + hid_hal_mouse_move(hid_mouse->hid, 0, MOUSE_MOVE_LONG); + } else if(event->type == InputTypeRelease) { + model->down_pressed = false; + } + } else if(event->key == InputKeyUp) { + if(event->type == InputTypePress) { + model->up_pressed = true; + hid_hal_mouse_move(hid_mouse->hid, 0, -MOUSE_MOVE_SHORT); + } else if(event->type == InputTypeRepeat) { + hid_hal_mouse_move(hid_mouse->hid, 0, -MOUSE_MOVE_LONG); + } else if(event->type == InputTypeRelease) { + model->up_pressed = false; + } + } + }, + true); +} + +static bool hid_mouse_input_callback(InputEvent* event, void* context) { + furi_assert(context); + HidMouse* hid_mouse = context; + bool consumed = false; + + if(event->type == InputTypeLong && event->key == InputKeyBack) { + hid_hal_mouse_release_all(hid_mouse->hid); + } else { + hid_mouse_process(hid_mouse, event); + consumed = true; + } + + return consumed; +} + +HidMouse* hid_mouse_alloc(Hid* hid) { + HidMouse* hid_mouse = malloc(sizeof(HidMouse)); + hid_mouse->view = view_alloc(); + hid_mouse->hid = hid; + view_set_context(hid_mouse->view, hid_mouse); + view_allocate_model(hid_mouse->view, ViewModelTypeLocking, sizeof(HidMouseModel)); + view_set_draw_callback(hid_mouse->view, hid_mouse_draw_callback); + view_set_input_callback(hid_mouse->view, hid_mouse_input_callback); + + with_view_model( + hid_mouse->view, HidMouseModel * model, { model->transport = hid->transport; }, true); + + return hid_mouse; +} + +void hid_mouse_free(HidMouse* hid_mouse) { + furi_assert(hid_mouse); + view_free(hid_mouse->view); + free(hid_mouse); +} + +View* hid_mouse_get_view(HidMouse* hid_mouse) { + furi_assert(hid_mouse); + return hid_mouse->view; +} + +void hid_mouse_set_connected_status(HidMouse* hid_mouse, bool connected) { + furi_assert(hid_mouse); + with_view_model( + hid_mouse->view, HidMouseModel * model, { model->connected = connected; }, true); +} diff --git a/applications/system/hid_app/views/hid_mouse.h b/applications/system/hid_app/views/hid_mouse.h new file mode 100644 index 00000000000..d9fb2fd88a7 --- /dev/null +++ b/applications/system/hid_app/views/hid_mouse.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +#define MOUSE_MOVE_SHORT 5 +#define MOUSE_MOVE_LONG 20 + +typedef struct Hid Hid; +typedef struct HidMouse HidMouse; + +HidMouse* hid_mouse_alloc(Hid* bt_hid); + +void hid_mouse_free(HidMouse* hid_mouse); + +View* hid_mouse_get_view(HidMouse* hid_mouse); + +void hid_mouse_set_connected_status(HidMouse* hid_mouse, bool connected); diff --git a/applications/system/hid_app/views/hid_mouse_clicker.c b/applications/system/hid_app/views/hid_mouse_clicker.c new file mode 100644 index 00000000000..d85affc4338 --- /dev/null +++ b/applications/system/hid_app/views/hid_mouse_clicker.c @@ -0,0 +1,214 @@ +#include "hid_mouse_clicker.h" +#include +#include "../hid.h" + +#include "hid_icons.h" + +#define TAG "HidMouseClicker" +#define DEFAULT_CLICK_RATE 1 +#define MAXIMUM_CLICK_RATE 60 + +struct HidMouseClicker { + View* view; + Hid* hid; + FuriTimer* timer; +}; + +typedef struct { + bool connected; + bool running; + int rate; + HidTransport transport; +} HidMouseClickerModel; + +static void hid_mouse_clicker_start_or_restart_timer(void* context) { + furi_assert(context); + HidMouseClicker* hid_mouse_clicker = context; + + if(furi_timer_is_running(hid_mouse_clicker->timer)) { + furi_timer_stop(hid_mouse_clicker->timer); + } + + with_view_model( + hid_mouse_clicker->view, + HidMouseClickerModel * model, + { + furi_timer_start( + hid_mouse_clicker->timer, furi_kernel_get_tick_frequency() / model->rate); + }, + true); +} + +static void hid_mouse_clicker_draw_callback(Canvas* canvas, void* context) { + furi_assert(context); + HidMouseClickerModel* model = context; + + // Header + if(model->transport == HidTransportBle) { + if(model->connected) { + canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); + } else { + canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); + } + } + + canvas_set_font(canvas, FontPrimary); + elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Mouse Clicker"); + + // Ok + canvas_draw_icon(canvas, 63, 25, &I_Space_65x18); + if(model->running) { + canvas_set_font(canvas, FontPrimary); + + FuriString* rate_label = furi_string_alloc(); + furi_string_printf(rate_label, "%d clicks/s\n\nUp / Down", model->rate); + elements_multiline_text(canvas, AlignLeft, 35, furi_string_get_cstr(rate_label)); + canvas_set_font(canvas, FontSecondary); + furi_string_free(rate_label); + + elements_slightly_rounded_box(canvas, 66, 27, 60, 13); + canvas_set_color(canvas, ColorWhite); + } else { + canvas_set_font(canvas, FontPrimary); + elements_multiline_text(canvas, AlignLeft, 35, "Press Start\nto start\nclicking"); + canvas_set_font(canvas, FontSecondary); + } + canvas_draw_icon(canvas, 74, 29, &I_Ok_btn_9x9); + if(model->running) { + elements_multiline_text_aligned(canvas, 91, 36, AlignLeft, AlignBottom, "Stop"); + } else { + elements_multiline_text_aligned(canvas, 91, 36, AlignLeft, AlignBottom, "Start"); + } + canvas_set_color(canvas, ColorBlack); + + // Back + canvas_draw_icon(canvas, 74, 49, &I_Pin_back_arrow_10x8); + elements_multiline_text_aligned(canvas, 91, 57, AlignLeft, AlignBottom, "Quit"); +} + +static void hid_mouse_clicker_timer_callback(void* context) { + furi_assert(context); + HidMouseClicker* hid_mouse_clicker = context; + with_view_model( + hid_mouse_clicker->view, + HidMouseClickerModel * model, + { + if(model->running) { + hid_hal_mouse_press(hid_mouse_clicker->hid, HID_MOUSE_BTN_LEFT); + hid_hal_mouse_release(hid_mouse_clicker->hid, HID_MOUSE_BTN_LEFT); + } + }, + false); +} + +static void hid_mouse_clicker_enter_callback(void* context) { + hid_mouse_clicker_start_or_restart_timer(context); +} + +static void hid_mouse_clicker_exit_callback(void* context) { + furi_assert(context); + HidMouseClicker* hid_mouse_clicker = context; + furi_timer_stop(hid_mouse_clicker->timer); +} + +static bool hid_mouse_clicker_input_callback(InputEvent* event, void* context) { + furi_assert(context); + HidMouseClicker* hid_mouse_clicker = context; + + bool consumed = false; + bool rate_changed = false; + + if(event->type != InputTypeShort && event->type != InputTypeRepeat) { + return false; + } + + with_view_model( + hid_mouse_clicker->view, + HidMouseClickerModel * model, + { + switch(event->key) { + case InputKeyOk: + model->running = !model->running; + consumed = true; + break; + case InputKeyUp: + if(model->rate < MAXIMUM_CLICK_RATE) { + model->rate++; + } + rate_changed = true; + consumed = true; + break; + case InputKeyDown: + if(model->rate > 1) { + model->rate--; + } + rate_changed = true; + consumed = true; + break; + default: + consumed = true; + break; + } + }, + true); + + if(rate_changed) { + hid_mouse_clicker_start_or_restart_timer(context); + } + + return consumed; +} + +HidMouseClicker* hid_mouse_clicker_alloc(Hid* hid) { + HidMouseClicker* hid_mouse_clicker = malloc(sizeof(HidMouseClicker)); + + hid_mouse_clicker->view = view_alloc(); + view_set_context(hid_mouse_clicker->view, hid_mouse_clicker); + view_allocate_model( + hid_mouse_clicker->view, ViewModelTypeLocking, sizeof(HidMouseClickerModel)); + view_set_draw_callback(hid_mouse_clicker->view, hid_mouse_clicker_draw_callback); + view_set_input_callback(hid_mouse_clicker->view, hid_mouse_clicker_input_callback); + view_set_enter_callback(hid_mouse_clicker->view, hid_mouse_clicker_enter_callback); + view_set_exit_callback(hid_mouse_clicker->view, hid_mouse_clicker_exit_callback); + + hid_mouse_clicker->hid = hid; + + hid_mouse_clicker->timer = furi_timer_alloc( + hid_mouse_clicker_timer_callback, FuriTimerTypePeriodic, hid_mouse_clicker); + + with_view_model( + hid_mouse_clicker->view, + HidMouseClickerModel * model, + { + model->transport = hid->transport; + model->rate = DEFAULT_CLICK_RATE; + }, + true); + + return hid_mouse_clicker; +} + +void hid_mouse_clicker_free(HidMouseClicker* hid_mouse_clicker) { + furi_assert(hid_mouse_clicker); + + furi_timer_stop(hid_mouse_clicker->timer); + furi_timer_free(hid_mouse_clicker->timer); + + view_free(hid_mouse_clicker->view); + + free(hid_mouse_clicker); +} + +View* hid_mouse_clicker_get_view(HidMouseClicker* hid_mouse_clicker) { + furi_assert(hid_mouse_clicker); + return hid_mouse_clicker->view; +} + +void hid_mouse_clicker_set_connected_status(HidMouseClicker* hid_mouse_clicker, bool connected) { + furi_assert(hid_mouse_clicker); + with_view_model( + hid_mouse_clicker->view, + HidMouseClickerModel * model, + { model->connected = connected; }, + true); +} diff --git a/applications/system/hid_app/views/hid_mouse_clicker.h b/applications/system/hid_app/views/hid_mouse_clicker.h new file mode 100644 index 00000000000..d72847baa70 --- /dev/null +++ b/applications/system/hid_app/views/hid_mouse_clicker.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +typedef struct Hid Hid; +typedef struct HidMouseClicker HidMouseClicker; + +HidMouseClicker* hid_mouse_clicker_alloc(Hid* bt_hid); + +void hid_mouse_clicker_free(HidMouseClicker* hid_mouse_clicker); + +View* hid_mouse_clicker_get_view(HidMouseClicker* hid_mouse_clicker); + +void hid_mouse_clicker_set_connected_status(HidMouseClicker* hid_mouse_clicker, bool connected); diff --git a/applications/system/hid_app/views/hid_mouse_jiggler.c b/applications/system/hid_app/views/hid_mouse_jiggler.c new file mode 100644 index 00000000000..15547eb26b5 --- /dev/null +++ b/applications/system/hid_app/views/hid_mouse_jiggler.c @@ -0,0 +1,159 @@ +#include "hid_mouse_jiggler.h" +#include +#include "../hid.h" + +#include "hid_icons.h" + +#define TAG "HidMouseJiggler" + +struct HidMouseJiggler { + View* view; + Hid* hid; + FuriTimer* timer; +}; + +typedef struct { + bool connected; + bool running; + uint8_t counter; + HidTransport transport; +} HidMouseJigglerModel; + +static void hid_mouse_jiggler_draw_callback(Canvas* canvas, void* context) { + furi_assert(context); + HidMouseJigglerModel* model = context; + + // Header + if(model->transport == HidTransportBle) { + if(model->connected) { + canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); + } else { + canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); + } + } + + canvas_set_font(canvas, FontPrimary); + elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Mouse Jiggler"); + + canvas_set_font(canvas, FontPrimary); + elements_multiline_text(canvas, AlignLeft, 35, "Press Start\nto jiggle"); + canvas_set_font(canvas, FontSecondary); + + // Ok + canvas_draw_icon(canvas, 63, 25, &I_Space_65x18); + if(model->running) { + elements_slightly_rounded_box(canvas, 66, 27, 60, 13); + canvas_set_color(canvas, ColorWhite); + } + canvas_draw_icon(canvas, 74, 29, &I_Ok_btn_9x9); + if(model->running) { + elements_multiline_text_aligned(canvas, 91, 36, AlignLeft, AlignBottom, "Stop"); + } else { + elements_multiline_text_aligned(canvas, 91, 36, AlignLeft, AlignBottom, "Start"); + } + canvas_set_color(canvas, ColorBlack); + + // Back + canvas_draw_icon(canvas, 74, 49, &I_Pin_back_arrow_10x8); + elements_multiline_text_aligned(canvas, 91, 57, AlignLeft, AlignBottom, "Quit"); +} + +static void hid_mouse_jiggler_timer_callback(void* context) { + furi_assert(context); + HidMouseJiggler* hid_mouse_jiggler = context; + with_view_model( + hid_mouse_jiggler->view, + HidMouseJigglerModel * model, + { + if(model->running) { + model->counter++; + hid_hal_mouse_move( + hid_mouse_jiggler->hid, + (model->counter % 2 == 0) ? MOUSE_MOVE_SHORT : -MOUSE_MOVE_SHORT, + 0); + } + }, + false); +} + +static void hid_mouse_jiggler_enter_callback(void* context) { + furi_assert(context); + HidMouseJiggler* hid_mouse_jiggler = context; + + furi_timer_start(hid_mouse_jiggler->timer, 500); +} + +static void hid_mouse_jiggler_exit_callback(void* context) { + furi_assert(context); + HidMouseJiggler* hid_mouse_jiggler = context; + furi_timer_stop(hid_mouse_jiggler->timer); +} + +static bool hid_mouse_jiggler_input_callback(InputEvent* event, void* context) { + furi_assert(context); + HidMouseJiggler* hid_mouse_jiggler = context; + + bool consumed = false; + + if(event->type == InputTypeShort && event->key == InputKeyOk) { + with_view_model( + hid_mouse_jiggler->view, + HidMouseJigglerModel * model, + { model->running = !model->running; }, + true); + consumed = true; + } + + return consumed; +} + +HidMouseJiggler* hid_mouse_jiggler_alloc(Hid* hid) { + HidMouseJiggler* hid_mouse_jiggler = malloc(sizeof(HidMouseJiggler)); + + hid_mouse_jiggler->view = view_alloc(); + view_set_context(hid_mouse_jiggler->view, hid_mouse_jiggler); + view_allocate_model( + hid_mouse_jiggler->view, ViewModelTypeLocking, sizeof(HidMouseJigglerModel)); + view_set_draw_callback(hid_mouse_jiggler->view, hid_mouse_jiggler_draw_callback); + view_set_input_callback(hid_mouse_jiggler->view, hid_mouse_jiggler_input_callback); + view_set_enter_callback(hid_mouse_jiggler->view, hid_mouse_jiggler_enter_callback); + view_set_exit_callback(hid_mouse_jiggler->view, hid_mouse_jiggler_exit_callback); + + hid_mouse_jiggler->hid = hid; + + hid_mouse_jiggler->timer = furi_timer_alloc( + hid_mouse_jiggler_timer_callback, FuriTimerTypePeriodic, hid_mouse_jiggler); + + with_view_model( + hid_mouse_jiggler->view, + HidMouseJigglerModel * model, + { model->transport = hid->transport; }, + true); + + return hid_mouse_jiggler; +} + +void hid_mouse_jiggler_free(HidMouseJiggler* hid_mouse_jiggler) { + furi_assert(hid_mouse_jiggler); + + furi_timer_stop(hid_mouse_jiggler->timer); + furi_timer_free(hid_mouse_jiggler->timer); + + view_free(hid_mouse_jiggler->view); + + free(hid_mouse_jiggler); +} + +View* hid_mouse_jiggler_get_view(HidMouseJiggler* hid_mouse_jiggler) { + furi_assert(hid_mouse_jiggler); + return hid_mouse_jiggler->view; +} + +void hid_mouse_jiggler_set_connected_status(HidMouseJiggler* hid_mouse_jiggler, bool connected) { + furi_assert(hid_mouse_jiggler); + with_view_model( + hid_mouse_jiggler->view, + HidMouseJigglerModel * model, + { model->connected = connected; }, + true); +} diff --git a/applications/system/hid_app/views/hid_mouse_jiggler.h b/applications/system/hid_app/views/hid_mouse_jiggler.h new file mode 100644 index 00000000000..0813b4351e1 --- /dev/null +++ b/applications/system/hid_app/views/hid_mouse_jiggler.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +#define MOUSE_MOVE_SHORT 5 +#define MOUSE_MOVE_LONG 20 + +typedef struct Hid Hid; +typedef struct HidMouseJiggler HidMouseJiggler; + +HidMouseJiggler* hid_mouse_jiggler_alloc(Hid* bt_hid); + +void hid_mouse_jiggler_free(HidMouseJiggler* hid_mouse_jiggler); + +View* hid_mouse_jiggler_get_view(HidMouseJiggler* hid_mouse_jiggler); + +void hid_mouse_jiggler_set_connected_status(HidMouseJiggler* hid_mouse_jiggler, bool connected); diff --git a/applications/system/hid_app/views/hid_tiktok.c b/applications/system/hid_app/views/hid_tiktok.c new file mode 100644 index 00000000000..e1f9f4bed4c --- /dev/null +++ b/applications/system/hid_app/views/hid_tiktok.c @@ -0,0 +1,241 @@ +#include "hid_tiktok.h" +#include "../hid.h" +#include + +#include "hid_icons.h" + +#define TAG "HidTikTok" + +struct HidTikTok { + View* view; + Hid* hid; +}; + +typedef struct { + bool left_pressed; + bool up_pressed; + bool right_pressed; + bool down_pressed; + bool ok_pressed; + bool connected; + bool is_cursor_set; + HidTransport transport; +} HidTikTokModel; + +static void hid_tiktok_draw_callback(Canvas* canvas, void* context) { + furi_assert(context); + HidTikTokModel* model = context; + + // Header + if(model->transport == HidTransportBle) { + if(model->connected) { + canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); + } else { + canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); + } + } + + canvas_set_font(canvas, FontPrimary); + elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "TikTok"); + canvas_set_font(canvas, FontSecondary); + + // Keypad circles + canvas_draw_icon(canvas, 76, 8, &I_Circles_47x47); + + // Up + if(model->up_pressed) { + canvas_set_bitmap_mode(canvas, 1); + canvas_draw_icon(canvas, 93, 9, &I_Pressed_Button_13x13); + canvas_set_bitmap_mode(canvas, 0); + canvas_set_color(canvas, ColorWhite); + } + canvas_draw_icon(canvas, 96, 11, &I_Arr_up_7x9); + canvas_set_color(canvas, ColorBlack); + + // Down + if(model->down_pressed) { + canvas_set_bitmap_mode(canvas, 1); + canvas_draw_icon(canvas, 93, 41, &I_Pressed_Button_13x13); + canvas_set_bitmap_mode(canvas, 0); + canvas_set_color(canvas, ColorWhite); + } + canvas_draw_icon(canvas, 96, 44, &I_Arr_dwn_7x9); + canvas_set_color(canvas, ColorBlack); + + // Left + if(model->left_pressed) { + canvas_set_bitmap_mode(canvas, 1); + canvas_draw_icon(canvas, 77, 25, &I_Pressed_Button_13x13); + canvas_set_bitmap_mode(canvas, 0); + canvas_set_color(canvas, ColorWhite); + } + canvas_draw_icon(canvas, 81, 29, &I_Voldwn_6x6); + canvas_set_color(canvas, ColorBlack); + + // Right + if(model->right_pressed) { + canvas_set_bitmap_mode(canvas, 1); + canvas_draw_icon(canvas, 109, 25, &I_Pressed_Button_13x13); + canvas_set_bitmap_mode(canvas, 0); + canvas_set_color(canvas, ColorWhite); + } + canvas_draw_icon(canvas, 111, 29, &I_Volup_8x6); + canvas_set_color(canvas, ColorBlack); + + // Ok + if(model->ok_pressed) { + canvas_draw_icon(canvas, 91, 23, &I_Like_pressed_17x17); + } else { + canvas_draw_icon(canvas, 94, 27, &I_Like_def_11x9); + } + // Exit + canvas_draw_icon(canvas, 0, 54, &I_Pin_back_arrow_10x8); + canvas_set_font(canvas, FontSecondary); + elements_multiline_text_aligned(canvas, 13, 62, AlignLeft, AlignBottom, "Hold to exit"); +} + +static void hid_tiktok_reset_cursor(HidTikTok* hid_tiktok) { + // Set cursor to the phone's left up corner + // Delays to guarantee one packet per connection interval + for(size_t i = 0; i < 8; i++) { + hid_hal_mouse_move(hid_tiktok->hid, -127, -127); + furi_delay_ms(50); + } + // Move cursor from the corner + hid_hal_mouse_move(hid_tiktok->hid, 20, 120); + furi_delay_ms(50); +} + +static void + hid_tiktok_process_press(HidTikTok* hid_tiktok, HidTikTokModel* model, InputEvent* event) { + if(event->key == InputKeyUp) { + model->up_pressed = true; + } else if(event->key == InputKeyDown) { + model->down_pressed = true; + } else if(event->key == InputKeyLeft) { + model->left_pressed = true; + hid_hal_consumer_key_press(hid_tiktok->hid, HID_CONSUMER_VOLUME_DECREMENT); + } else if(event->key == InputKeyRight) { + model->right_pressed = true; + hid_hal_consumer_key_press(hid_tiktok->hid, HID_CONSUMER_VOLUME_INCREMENT); + } else if(event->key == InputKeyOk) { + model->ok_pressed = true; + } +} + +static void + hid_tiktok_process_release(HidTikTok* hid_tiktok, HidTikTokModel* model, InputEvent* event) { + if(event->key == InputKeyUp) { + model->up_pressed = false; + } else if(event->key == InputKeyDown) { + model->down_pressed = false; + } else if(event->key == InputKeyLeft) { + model->left_pressed = false; + hid_hal_consumer_key_release(hid_tiktok->hid, HID_CONSUMER_VOLUME_DECREMENT); + } else if(event->key == InputKeyRight) { + model->right_pressed = false; + hid_hal_consumer_key_release(hid_tiktok->hid, HID_CONSUMER_VOLUME_INCREMENT); + } else if(event->key == InputKeyOk) { + model->ok_pressed = false; + } +} + +static bool hid_tiktok_input_callback(InputEvent* event, void* context) { + furi_assert(context); + HidTikTok* hid_tiktok = context; + bool consumed = false; + + with_view_model( + hid_tiktok->view, + HidTikTokModel * model, + { + if(event->type == InputTypePress) { + hid_tiktok_process_press(hid_tiktok, model, event); + if(model->connected && !model->is_cursor_set) { + hid_tiktok_reset_cursor(hid_tiktok); + model->is_cursor_set = true; + } + consumed = true; + } else if(event->type == InputTypeRelease) { + hid_tiktok_process_release(hid_tiktok, model, event); + consumed = true; + } else if(event->type == InputTypeShort) { + if(event->key == InputKeyOk) { + hid_hal_mouse_press(hid_tiktok->hid, HID_MOUSE_BTN_LEFT); + furi_delay_ms(50); + hid_hal_mouse_release(hid_tiktok->hid, HID_MOUSE_BTN_LEFT); + furi_delay_ms(50); + hid_hal_mouse_press(hid_tiktok->hid, HID_MOUSE_BTN_LEFT); + furi_delay_ms(50); + hid_hal_mouse_release(hid_tiktok->hid, HID_MOUSE_BTN_LEFT); + consumed = true; + } else if(event->key == InputKeyUp) { + // Emulate up swipe + hid_hal_mouse_scroll(hid_tiktok->hid, -6); + hid_hal_mouse_scroll(hid_tiktok->hid, -12); + hid_hal_mouse_scroll(hid_tiktok->hid, -19); + hid_hal_mouse_scroll(hid_tiktok->hid, -12); + hid_hal_mouse_scroll(hid_tiktok->hid, -6); + consumed = true; + } else if(event->key == InputKeyDown) { + // Emulate down swipe + hid_hal_mouse_scroll(hid_tiktok->hid, 6); + hid_hal_mouse_scroll(hid_tiktok->hid, 12); + hid_hal_mouse_scroll(hid_tiktok->hid, 19); + hid_hal_mouse_scroll(hid_tiktok->hid, 12); + hid_hal_mouse_scroll(hid_tiktok->hid, 6); + consumed = true; + } else if(event->key == InputKeyBack) { + hid_hal_consumer_key_release_all(hid_tiktok->hid); + consumed = true; + } + } else if(event->type == InputTypeLong) { + if(event->key == InputKeyBack) { + hid_hal_consumer_key_release_all(hid_tiktok->hid); + model->is_cursor_set = false; + consumed = false; + } + } + }, + true); + + return consumed; +} + +HidTikTok* hid_tiktok_alloc(Hid* bt_hid) { + HidTikTok* hid_tiktok = malloc(sizeof(HidTikTok)); + hid_tiktok->hid = bt_hid; + hid_tiktok->view = view_alloc(); + view_set_context(hid_tiktok->view, hid_tiktok); + view_allocate_model(hid_tiktok->view, ViewModelTypeLocking, sizeof(HidTikTokModel)); + view_set_draw_callback(hid_tiktok->view, hid_tiktok_draw_callback); + view_set_input_callback(hid_tiktok->view, hid_tiktok_input_callback); + + with_view_model( + hid_tiktok->view, HidTikTokModel * model, { model->transport = bt_hid->transport; }, true); + + return hid_tiktok; +} + +void hid_tiktok_free(HidTikTok* hid_tiktok) { + furi_assert(hid_tiktok); + view_free(hid_tiktok->view); + free(hid_tiktok); +} + +View* hid_tiktok_get_view(HidTikTok* hid_tiktok) { + furi_assert(hid_tiktok); + return hid_tiktok->view; +} + +void hid_tiktok_set_connected_status(HidTikTok* hid_tiktok, bool connected) { + furi_assert(hid_tiktok); + with_view_model( + hid_tiktok->view, + HidTikTokModel * model, + { + model->connected = connected; + model->is_cursor_set = false; + }, + true); +} diff --git a/applications/system/hid_app/views/hid_tiktok.h b/applications/system/hid_app/views/hid_tiktok.h new file mode 100644 index 00000000000..b2efc3692d3 --- /dev/null +++ b/applications/system/hid_app/views/hid_tiktok.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +typedef struct Hid Hid; +typedef struct HidTikTok HidTikTok; + +HidTikTok* hid_tiktok_alloc(Hid* bt_hid); + +void hid_tiktok_free(HidTikTok* hid_tiktok); + +View* hid_tiktok_get_view(HidTikTok* hid_tiktok); + +void hid_tiktok_set_connected_status(HidTikTok* hid_tiktok, bool connected); diff --git a/applications/system/snake_game/application.fam b/applications/system/snake_game/application.fam new file mode 100644 index 00000000000..9a99ebac840 --- /dev/null +++ b/applications/system/snake_game/application.fam @@ -0,0 +1,13 @@ +App( + appid="snake_game", + name="Snake Game", + apptype=FlipperAppType.EXTERNAL, + entry_point="snake_game_app", + requires=["gui"], + stack_size=1 * 1024, + targets=["f7"], + fap_version="1.0", + fap_description="Classic Snake Game", + fap_icon="snake_10px.png", + fap_category="Games", +) diff --git a/applications/system/snake_game/snake_10px.png b/applications/system/snake_game/snake_10px.png new file mode 100644 index 0000000000000000000000000000000000000000..52d9fa7e0e1b884774a6e58abb1965b1b1905767 GIT binary patch literal 158 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2VGmzZ%#=aj&u?6^qxc>kDAIJlX?eOh zhE&W+PDn^dVNp_G*rbrd!ONE5$kL$2+HH6!MA=j6#)(e`V#>-4Tp0`o%bUNP1L~43 zag8Vm&QB{TPb^AhaL6gmODsst%q!6^$V=Bv&QD2A{^~3#2UN)5>FVdQ&MBb@0GSOf APyhe` literal 0 HcmV?d00001 diff --git a/applications/system/snake_game/snake_game.c b/applications/system/snake_game/snake_game.c new file mode 100644 index 00000000000..6852cb215b2 --- /dev/null +++ b/applications/system/snake_game/snake_game.c @@ -0,0 +1,434 @@ +#include +#include +#include +#include +#include +#include +#include + +typedef struct { + // +-----x + // | + // | + // y + uint8_t x; + uint8_t y; +} Point; + +typedef enum { + GameStateLife, + + // https://melmagazine.com/en-us/story/snake-nokia-6110-oral-history-taneli-armanto + // Armanto: While testing the early versions of the game, I noticed it was hard + // to control the snake upon getting close to and edge but not crashing — especially + // in the highest speed levels. I wanted the highest level to be as fast as I could + // possibly make the device "run," but on the other hand, I wanted to be friendly + // and help the player manage that level. Otherwise it might not be fun to play. So + // I implemented a little delay. A few milliseconds of extra time right before + // the player crashes, during which she can still change the directions. And if + // she does, the game continues. + GameStateLastChance, + + GameStateGameOver, +} GameState; + +// Note: do not change without purpose. Current values are used in smart +// orthogonality calculation in `snake_game_get_turn_snake`. +typedef enum { + DirectionUp, + DirectionRight, + DirectionDown, + DirectionLeft, +} Direction; + +#define MAX_SNAKE_LEN 253 + +typedef struct { + Point points[MAX_SNAKE_LEN]; + uint16_t len; + Direction currentMovement; + Direction nextMovement; // if backward of currentMovement, ignore + Point fruit; + GameState state; + FuriMutex* mutex; +} SnakeState; + +typedef enum { + EventTypeTick, + EventTypeKey, +} EventType; + +typedef struct { + EventType type; + InputEvent input; +} SnakeEvent; + +const NotificationSequence sequence_fail = { + &message_vibro_on, + + &message_note_ds4, + &message_delay_10, + &message_sound_off, + &message_delay_10, + + &message_note_ds4, + &message_delay_10, + &message_sound_off, + &message_delay_10, + + &message_note_ds4, + &message_delay_10, + &message_sound_off, + &message_delay_10, + + &message_vibro_off, + NULL, +}; + +const NotificationSequence sequence_eat = { + &message_note_c7, + &message_delay_50, + &message_sound_off, + NULL, +}; + +static void snake_game_render_callback(Canvas* const canvas, void* ctx) { + furi_assert(ctx); + const SnakeState* snake_state = ctx; + + furi_mutex_acquire(snake_state->mutex, FuriWaitForever); + + // Frame + canvas_draw_frame(canvas, 0, 0, 128, 64); + + // Fruit + Point f = snake_state->fruit; + f.x = f.x * 4 + 1; + f.y = f.y * 4 + 1; + canvas_draw_rframe(canvas, f.x, f.y, 6, 6, 2); + + // Snake + for(uint16_t i = 0; i < snake_state->len; i++) { + Point p = snake_state->points[i]; + p.x = p.x * 4 + 2; + p.y = p.y * 4 + 2; + canvas_draw_box(canvas, p.x, p.y, 4, 4); + } + + // Game Over banner + if(snake_state->state == GameStateGameOver) { + // Screen is 128x64 px + canvas_set_color(canvas, ColorWhite); + canvas_draw_box(canvas, 34, 20, 62, 24); + + canvas_set_color(canvas, ColorBlack); + canvas_draw_frame(canvas, 34, 20, 62, 24); + + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 37, 31, "Game Over"); + + canvas_set_font(canvas, FontSecondary); + char buffer[12]; + snprintf(buffer, sizeof(buffer), "Score: %u", snake_state->len - 7U); + canvas_draw_str_aligned(canvas, 64, 41, AlignCenter, AlignBottom, buffer); + } + + furi_mutex_release(snake_state->mutex); +} + +static void snake_game_input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) { + furi_assert(event_queue); + + SnakeEvent event = {.type = EventTypeKey, .input = *input_event}; + furi_message_queue_put(event_queue, &event, FuriWaitForever); +} + +static void snake_game_update_timer_callback(FuriMessageQueue* event_queue) { + furi_assert(event_queue); + + SnakeEvent event = {.type = EventTypeTick}; + furi_message_queue_put(event_queue, &event, 0); +} + +static void snake_game_init_game(SnakeState* const snake_state) { + Point p[] = {{8, 6}, {7, 6}, {6, 6}, {5, 6}, {4, 6}, {3, 6}, {2, 6}}; + memcpy(snake_state->points, p, sizeof(p)); //-V1086 + + snake_state->len = 7; + + snake_state->currentMovement = DirectionRight; + + snake_state->nextMovement = DirectionRight; + + Point f = {18, 6}; + snake_state->fruit = f; + + snake_state->state = GameStateLife; +} + +static Point snake_game_get_new_fruit(SnakeState const* const snake_state) { + // 1 bit for each point on the playing field where the snake can turn + // and where the fruit can appear + uint16_t buffer[8]; + memset(buffer, 0, sizeof(buffer)); + uint8_t empty = 8 * 16; + + for(uint16_t i = 0; i < snake_state->len; i++) { + Point p = snake_state->points[i]; + + if(p.x % 2 != 0 || p.y % 2 != 0) { + continue; + } + p.x /= 2; + p.y /= 2; + + buffer[p.y] |= 1 << p.x; + empty--; + } + // Bit set if snake use that playing field + + uint16_t newFruit = rand() % empty; + + // Skip random number of _empty_ fields + for(uint8_t y = 0; y < 8; y++) { + for(uint16_t x = 0, mask = 1; x < 16; x += 1, mask <<= 1) { + if((buffer[y] & mask) == 0) { + if(newFruit == 0) { + Point p = { + .x = x * 2, + .y = y * 2, + }; + return p; + } + newFruit--; + } + } + } + // We will never be here + Point p = {0, 0}; + return p; +} + +static bool snake_game_collision_with_frame(Point const next_step) { + // if x == 0 && currentMovement == left then x - 1 == 255 , + // so check only x > right border + return next_step.x > 30 || next_step.y > 14; +} + +static bool + snake_game_collision_with_tail(SnakeState const* const snake_state, Point const next_step) { + for(uint16_t i = 0; i < snake_state->len; i++) { + Point p = snake_state->points[i]; + if(p.x == next_step.x && p.y == next_step.y) { + return true; + } + } + + return false; +} + +static Direction snake_game_get_turn_snake(SnakeState const* const snake_state) { + // Sum of two `Direction` lies between 0 and 6, odd values indicate orthogonality. + bool is_orthogonal = (snake_state->currentMovement + snake_state->nextMovement) % 2 == 1; + return is_orthogonal ? snake_state->nextMovement : snake_state->currentMovement; +} + +static Point snake_game_get_next_step(SnakeState const* const snake_state) { + Point next_step = snake_state->points[0]; + switch(snake_state->currentMovement) { + // +-----x + // | + // | + // y + case DirectionUp: + next_step.y--; + break; + case DirectionRight: + next_step.x++; + break; + case DirectionDown: + next_step.y++; + break; + case DirectionLeft: + next_step.x--; + break; + } + return next_step; +} + +static void snake_game_move_snake(SnakeState* const snake_state, Point const next_step) { + memmove(snake_state->points + 1, snake_state->points, snake_state->len * sizeof(Point)); + snake_state->points[0] = next_step; +} + +static void + snake_game_process_game_step(SnakeState* const snake_state, NotificationApp* notification) { + if(snake_state->state == GameStateGameOver) { + return; + } + + bool can_turn = (snake_state->points[0].x % 2 == 0) && (snake_state->points[0].y % 2 == 0); + if(can_turn) { + snake_state->currentMovement = snake_game_get_turn_snake(snake_state); + } + + Point next_step = snake_game_get_next_step(snake_state); + + bool crush = snake_game_collision_with_frame(next_step); + if(crush) { + if(snake_state->state == GameStateLife) { + snake_state->state = GameStateLastChance; + return; + } else if(snake_state->state == GameStateLastChance) { + snake_state->state = GameStateGameOver; + notification_message_block(notification, &sequence_fail); + return; + } + } else { + if(snake_state->state == GameStateLastChance) { + snake_state->state = GameStateLife; + } + } + + crush = snake_game_collision_with_tail(snake_state, next_step); + if(crush) { + snake_state->state = GameStateGameOver; + notification_message_block(notification, &sequence_fail); + return; + } + + bool eatFruit = (next_step.x == snake_state->fruit.x) && (next_step.y == snake_state->fruit.y); + if(eatFruit) { + snake_state->len++; + if(snake_state->len >= MAX_SNAKE_LEN) { + snake_state->state = GameStateGameOver; + notification_message_block(notification, &sequence_fail); + return; + } + } + + snake_game_move_snake(snake_state, next_step); + + if(eatFruit) { + snake_state->fruit = snake_game_get_new_fruit(snake_state); + notification_message(notification, &sequence_eat); + } +} + +int32_t snake_game_app(void* p) { + UNUSED(p); + + FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(SnakeEvent)); + + SnakeState* snake_state = malloc(sizeof(SnakeState)); + snake_game_init_game(snake_state); + + snake_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal); + + if(!snake_state->mutex) { + FURI_LOG_E("SnakeGame", "cannot create mutex\r\n"); + free(snake_state); + return 255; + } + + ViewPort* view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, snake_game_render_callback, snake_state); + view_port_input_callback_set(view_port, snake_game_input_callback, event_queue); + + FuriTimer* timer = + furi_timer_alloc(snake_game_update_timer_callback, FuriTimerTypePeriodic, event_queue); + furi_timer_start(timer, furi_kernel_get_tick_frequency() / 4); + + // Open GUI and register view_port + Gui* gui = furi_record_open(RECORD_GUI); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); + NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); + + notification_message_block(notification, &sequence_display_backlight_enforce_on); + + dolphin_deed(DolphinDeedPluginGameStart); + + SnakeEvent event; + for(bool processing = true; processing;) { + FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100); + + furi_mutex_acquire(snake_state->mutex, FuriWaitForever); + + if(event_status == FuriStatusOk) { + // press events + if(event.type == EventTypeKey) { + if(event.input.type == InputTypePress) { + switch(event.input.key) { + case InputKeyUp: + snake_state->nextMovement = DirectionUp; + break; + case InputKeyDown: + snake_state->nextMovement = DirectionDown; + break; + case InputKeyRight: + snake_state->nextMovement = DirectionRight; + break; + case InputKeyLeft: + snake_state->nextMovement = DirectionLeft; + break; + case InputKeyOk: + if(snake_state->state == GameStateGameOver) { + snake_game_init_game(snake_state); + } + break; + case InputKeyBack: + processing = false; + break; + default: + break; + } + } + } else if(event.type == EventTypeTick) { + snake_game_process_game_step(snake_state, notification); + } + } else { + // event timeout + } + + view_port_update(view_port); + furi_mutex_release(snake_state->mutex); + } + + // Return backlight to normal state + notification_message(notification, &sequence_display_backlight_enforce_auto); + + furi_timer_free(timer); + view_port_enabled_set(view_port, false); + gui_remove_view_port(gui, view_port); + furi_record_close(RECORD_GUI); + furi_record_close(RECORD_NOTIFICATION); + view_port_free(view_port); + furi_message_queue_free(event_queue); + furi_mutex_free(snake_state->mutex); + free(snake_state); + + return 0; +} + +// Screen is 128x64 px +// (4 + 4) * 16 - 4 + 2 + 2border == 128 +// (4 + 4) * 8 - 4 + 2 + 2border == 64 +// Game field from point{x: 0, y: 0} to point{x: 30, y: 14}. +// The snake turns only in even cells - intersections. +// ┌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┐ +// ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎ +// ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎ +// ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎ +// ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎ +// ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎ +// ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎ +// ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎ +// ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎ +// ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎ +// ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎ +// ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎ +// ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎ +// ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎ +// ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎ +// ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎ +// └╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘ diff --git a/assets/resources/infrared/assets/tv.ir b/assets/resources/infrared/assets/tv.ir index a640b9a57c7..86b11e8a793 100644 --- a/assets/resources/infrared/assets/tv.ir +++ b/assets/resources/infrared/assets/tv.ir @@ -267,12 +267,6 @@ command: 13 00 00 00 # name: Power type: parsed -protocol: Samsung32 -address: 0E 00 00 00 -command: 14 00 00 00 -# -name: Power -type: parsed protocol: NECext address: 80 7E 00 00 command: 18 00 00 00 @@ -332,12 +326,6 @@ address: 00 00 00 00 command: 01 00 00 00 # name: Power -type: parsed -protocol: NEC -address: 00 00 00 00 -command: 01 00 00 00 -# -name: Power type: raw frequency: 38000 duty_cycle: 0.330000 @@ -351,12 +339,6 @@ data: 525 1955 449 1999 476 4545 446 4544 478 2032 443 2006 469 2011 444 4577 44 # name: Power type: parsed -protocol: SIRC -address: 01 00 00 00 -command: 15 00 00 00 -# -name: Power -type: parsed protocol: Kaseikyo address: 80 02 20 00 command: D0 03 00 00 @@ -470,12 +452,6 @@ duty_cycle: 0.330000 data: 533 1356 437 3474 427 3483 429 3455 436 1454 430 1459 405 28168 510 1379 434 3477 434 3476 425 3459 432 1457 427 1462 402 # name: Power -type: parsed -protocol: RC5 -address: 00 00 00 00 -command: 0C 00 00 00 -# -name: Power type: raw frequency: 38000 duty_cycle: 0.330000 @@ -890,12 +866,6 @@ duty_cycle: 0.330000 data: 8042 3979 513 510 508 541 487 1559 509 515 513 1560 508 515 513 510 508 541 477 3981 532 1568 510 1563 515 1558 510 1564 514 1559 509 514 514 535 483 540 488 24151 8042 3979 513 536 482 541 487 1560 508 541 488 1560 508 541 487 536 482 541 477 3980 533 1566 512 1561 507 1566 512 1562 516 1557 511 538 491 533 485 538 490 # name: Power -type: parsed -protocol: NECext -address: 83 7A 00 00 -command: 08 00 00 00 -# -name: Power type: raw frequency: 38000 duty_cycle: 0.330000 @@ -1064,36 +1034,18 @@ address: 86 02 00 00 command: 49 00 00 00 # name: Power -type: parsed -protocol: SIRC -address: 01 00 00 00 -command: 15 00 00 00 -# -name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 178 7761 176 11308 546 957 540 1958 538 970 537 1955 541 962 545 1953 543 964 543 962 545 957 540 970 548 960 547 1945 541 1950 546 965 542 1953 543 962 545 1945 540 970 537 1958 538 7881 172 7744 172 11318 536 971 536 1956 540 963 534 1964 542 966 541 1951 535 968 539 971 536 971 536 969 538 964 533 1965 541 1954 542 963 534 1956 540 971 536 1959 537 968 539 1951 535 # name: Power -type: parsed -protocol: Kaseikyo -address: 80 02 20 00 -command: D0 03 00 00 -# -name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 278 1845 274 808 271 806 273 812 278 805 275 805 274 1840 279 1844 275 809 281 1836 272 806 274 812 278 805 274 1842 277 802 277 44956 279 1842 277 804 275 802 277 808 271 811 279 1838 281 798 271 814 276 1844 275 806 273 1841 278 1845 274 1846 273 808 271 1843 276 44959 275 1845 274 807 272 805 275 811 279 804 275 805 274 1839 280 1844 275 808 271 1845 274 805 274 811 279 804 275 1841 278 801 278 44955 280 1841 278 802 277 801 278 807 272 810 280 1837 271 807 272 813 277 1843 276 805 274 1839 280 1843 276 1845 274 807 272 1842 277 # name: Power -type: parsed -protocol: RC5 -address: 00 00 00 00 -command: 0C 00 00 00 -# -name: Power type: raw frequency: 38000 duty_cycle: 0.330000 @@ -1208,12 +1160,6 @@ address: 00 00 00 00 command: 26 00 00 00 # name: Power -type: parsed -protocol: RC5 -address: 00 00 00 00 -command: 0C 00 00 00 -# -name: Power type: raw frequency: 38000 duty_cycle: 0.330000 @@ -1503,12 +1449,6 @@ data: 879 901 871 1796 1770 903 869 917 876 916 877 913 880 906 877 918 875 914 # name: Power type: parsed -protocol: RC5 -address: 00 00 00 00 -command: 0C 00 00 00 -# -name: Power -type: parsed protocol: Kaseikyo address: 90 02 20 00 command: D0 03 00 00 @@ -1582,12 +1522,6 @@ data: 3429 3445 875 2555 868 875 867 871 871 2560 873 868 874 2551 872 874 868 8 name: Power type: parsed protocol: NEC -address: 71 00 00 00 -command: 08 00 00 00 -# -name: Power -type: parsed -protocol: NEC address: 83 00 00 00 command: FF 00 00 00 # @@ -1683,44 +1617,6 @@ frequency: 38000 duty_cycle: 0.330000 data: 3462 1592 490 332 513 1200 489 331 514 1201 489 355 490 1201 489 356 512 1178 489 356 512 1178 512 334 487 1202 488 1202 488 357 512 1178 512 334 486 1203 487 1202 488 1203 487 1204 486 383 461 1228 488 357 488 357 487 357 487 1203 486 1204 486 359 485 1205 485 360 485 361 484 360 485 360 485 361 484 361 484 361 484 1206 484 360 484 361 484 361 484 1206 484 361 484 361 484 361 484 1206 484 361 484 1206 484 361 484 71543 3434 1620 486 359 485 1205 485 360 485 1206 484 360 485 1206 484 360 485 1206 484 360 485 1206 484 360 485 1205 485 1206 484 360 485 1206 484 360 485 1206 484 1206 484 1206 484 1206 484 361 484 1206 484 360 485 360 485 361 484 1206 484 1206 484 360 484 1206 484 360 485 361 484 361 484 360 485 361 484 361 484 361 484 1206 484 361 484 361 484 361 484 1206 484 361 484 361 484 361 484 1207 483 361 484 1206 484 361 484 71543 3435 1619 486 358 486 1204 486 359 486 1205 485 360 485 1205 485 360 485 1205 485 360 485 1205 485 360 484 1205 485 1205 485 360 485 1205 485 360 485 1205 485 1205 485 1205 485 1206 484 360 485 1205 485 360 485 360 485 360 485 1205 485 1206 484 360 485 1206 484 360 485 360 485 360 485 360 485 360 485 360 485 360 485 1206 484 360 485 360 485 360 485 1206 484 360 485 360 485 360 485 1205 485 360 485 1206 484 360 485 71542 3436 1619 486 358 487 1204 486 359 485 1205 485 360 485 1205 485 360 485 1205 485 360 485 1205 485 360 485 1205 485 1205 485 360 485 1205 485 360 485 1206 484 1206 484 1206 484 1206 484 360 485 1206 484 360 485 360 485 361 484 1206 484 1206 484 361 484 1206 484 361 484 361 484 361 484 361 484 361 484 361 484 360 485 1206 484 361 484 361 484 361 484 1206 484 361 484 361 484 360 485 1206 484 361 484 1206 484 361 484 71542 3437 1618 487 358 486 1204 486 359 486 1205 485 360 485 1205 485 360 485 1205 485 360 485 1205 485 360 485 1205 485 1206 484 360 485 1205 485 360 485 1206 484 1205 485 1206 484 1205 485 360 485 1205 485 360 485 360 485 360 485 1205 485 1205 485 360 485 1205 485 360 485 360 485 360 485 360 485 360 485 360 485 360 485 1205 485 360 485 360 485 360 485 1205 485 360 485 360 485 360 485 1205 485 360 485 1205 485 360 485 # -# TCL -# -name: Power -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 3977 3993 494 1994 495 1995 496 1997 494 1996 495 1004 496 1004 496 1995 496 1004 496 1997 494 1005 495 1995 495 1007 493 1006 494 1006 494 1005 495 1007 493 1997 494 1995 496 1004 496 1995 496 1005 495 1995 496 1003 497 1995 496 8467 3980 3993 494 1994 495 1996 495 1997 494 1995 496 1004 496 1006 494 1995 496 1004 496 1996 495 1004 496 1996 495 1005 495 1005 495 1004 496 1005 495 1005 495 1996 495 1995 496 1005 495 1996 495 1004 496 1995 496 1006 569 1920 571 8393 3980 3993 571 1918 572 1922 569 1920 571 1920 571 929 572 929 571 1920 571 929 571 1920 571 929 571 1921 570 930 570 929 571 929 571 929 571 928 572 1920 571 1921 570 930 571 1920 572 930 571 1921 571 929 571 1923 569 8396 3980 3994 570 1921 569 1923 569 1921 571 1920 572 929 572 930 571 1921 570 930 571 1922 570 930 571 1921 570 930 570 930 571 929 571 929 571 929 572 1922 570 1921 570 931 570 1922 570 930 571 1922 569 931 570 1921 570 -# -name: Vol_up -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 3979 3995 493 1994 496 1997 495 1999 492 1999 492 1006 495 1008 493 1998 493 1006 494 1997 495 1999 493 1997 494 1998 493 1006 495 1007 493 1005 495 1006 495 2025 467 1998 494 1006 494 1996 495 1006 494 1005 495 1006 494 1007 493 8468 3979 3995 492 1995 495 1999 492 1997 494 1997 494 1007 493 1006 494 1997 494 1006 494 1997 494 1996 571 1923 569 1921 570 930 570 929 571 931 569 931 575 1916 570 1920 571 930 570 1922 570 930 571 930 570 930 576 924 576 -# -name: Vol_dn -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 3951 3994 494 1997 493 1998 494 1998 493 1998 493 1005 496 1005 496 1996 495 1005 495 1997 495 1996 495 1996 495 1006 494 1005 495 1006 494 1005 495 1006 494 1996 495 1998 493 1005 495 1997 494 1008 492 1006 494 1006 494 1997 494 8471 3977 3996 493 1996 493 1997 494 1998 493 1997 494 1006 494 1007 493 1997 494 1009 492 1996 495 1996 495 1997 494 1006 494 1006 494 1006 494 1006 494 1006 494 1997 493 1997 494 1006 494 1996 494 1005 495 1004 495 1006 494 1996 494 -# -name: Ch_next -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 3978 3994 494 1995 495 1996 495 1996 495 1996 495 1006 494 1004 497 1997 494 1005 495 1997 520 1970 577 923 578 1914 577 924 576 924 576 925 575 924 576 1914 577 1915 576 924 576 1914 577 924 576 923 577 1915 576 926 574 8388 3978 3993 576 1913 576 1915 576 1915 576 1917 574 923 577 923 577 1943 548 925 575 1916 576 1915 575 924 576 1915 576 925 575 927 573 925 575 926 574 1916 574 1918 573 927 573 1918 573 928 572 927 573 1918 573 926 574 8389 4006 3966 572 1918 571 1919 572 1918 573 1920 570 929 571 929 571 1922 569 928 571 1920 571 1921 570 928 572 1919 572 929 571 929 571 929 571 929 571 1921 570 1921 521 980 569 1921 521 981 519 979 521 1971 520 979 521 -# -name: Ch_prev -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 3979 3994 494 1995 495 1997 494 1996 495 1998 493 1005 495 1006 494 1997 494 1005 495 1996 495 1995 496 1005 495 1005 495 1006 494 1005 495 1004 496 1005 495 1997 494 1997 494 1004 496 1996 495 1005 495 1005 495 1997 494 1996 495 8467 3976 3991 496 1995 495 1996 494 1994 496 1996 494 1005 495 1005 495 1996 495 1005 495 1995 495 1995 496 1006 494 1005 495 1006 494 1005 495 1004 496 1006 494 1994 496 1996 494 1005 495 1995 495 1004 496 1004 496 1995 495 1996 494 -# -name: Mute -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 3981 3992 495 1994 495 1995 496 1996 494 1996 495 1005 495 1006 494 1995 495 1997 494 1996 495 1996 494 1997 494 1996 495 1006 494 1005 495 1004 496 1005 495 1995 496 1994 496 1005 495 1004 496 1005 495 1006 494 1004 496 1006 494 8466 3978 3991 495 1994 495 1997 493 1994 496 1995 495 1004 496 1004 496 1996 494 1997 493 1996 494 1995 495 1995 495 1997 493 1004 495 1004 495 1006 494 1005 494 1998 491 1996 494 1006 494 1004 496 1006 494 1006 493 1005 495 1005 571 -# # Thomson RC3000E02 # name: Power @@ -1773,12 +1669,6 @@ protocol: NEC address: 40 00 00 00 command: 13 00 00 00 # -name: Vol_dn -type: parsed -protocol: NEC -address: 40 00 00 00 -command: 12 00 00 00 -# name: Mute type: parsed protocol: NEC diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index 52f9a4d90aa..2803af5c393 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,36.1,, +Version,+,37.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -827,8 +827,9 @@ Function,+,flipper_application_get_manifest,const FlipperApplicationManifest*,Fl Function,+,flipper_application_is_plugin,_Bool,FlipperApplication* Function,+,flipper_application_load_name_and_icon,_Bool,"FuriString*, Storage*, uint8_t**, FuriString*" Function,+,flipper_application_load_status_to_string,const char*,FlipperApplicationLoadStatus -Function,+,flipper_application_manifest_is_compatible,_Bool,"const FlipperApplicationManifest*, const ElfApiInterface*" Function,+,flipper_application_manifest_is_target_compatible,_Bool,const FlipperApplicationManifest* +Function,+,flipper_application_manifest_is_too_new,_Bool,"const FlipperApplicationManifest*, const ElfApiInterface*" +Function,+,flipper_application_manifest_is_too_old,_Bool,"const FlipperApplicationManifest*, const ElfApiInterface*" Function,+,flipper_application_manifest_is_valid,_Bool,const FlipperApplicationManifest* Function,+,flipper_application_map_to_memory,FlipperApplicationLoadStatus,FlipperApplication* Function,+,flipper_application_plugin_get_descriptor,const FlipperAppPluginDescriptor*,FlipperApplication* diff --git a/firmware/targets/f18/furi_hal/furi_hal_resources.c b/firmware/targets/f18/furi_hal/furi_hal_resources.c index efd39977b4d..11893587485 100644 --- a/firmware/targets/f18/furi_hal/furi_hal_resources.c +++ b/firmware/targets/f18/furi_hal/furi_hal_resources.c @@ -136,6 +136,14 @@ static void furi_hal_resources_init_input_pins(GpioMode mode) { } } +static void furi_hal_resources_init_gpio_pins(GpioMode mode) { + for(size_t i = 0; i < gpio_pins_count; i++) { + if(!gpio_pins[i].debug) { + furi_hal_gpio_init(gpio_pins[i].pin, mode, GpioPullNo, GpioSpeedLow); + } + } +} + void furi_hal_resources_init_early() { furi_hal_bus_enable(FuriHalBusGPIOA); furi_hal_bus_enable(FuriHalBusGPIOB); @@ -179,14 +187,7 @@ void furi_hal_resources_init_early() { furi_hal_gpio_write(&gpio_usb_dp, 0); // External header pins - furi_hal_gpio_init(&gpio_ext_pc0, GpioModeAnalog, GpioPullNo, GpioSpeedLow); - furi_hal_gpio_init(&gpio_ext_pc1, GpioModeAnalog, GpioPullNo, GpioSpeedLow); - furi_hal_gpio_init(&gpio_ext_pc3, GpioModeAnalog, GpioPullNo, GpioSpeedLow); - furi_hal_gpio_init(&gpio_ext_pb2, GpioModeAnalog, GpioPullNo, GpioSpeedLow); - furi_hal_gpio_init(&gpio_ext_pb3, GpioModeAnalog, GpioPullNo, GpioSpeedLow); - furi_hal_gpio_init(&gpio_ext_pa4, GpioModeAnalog, GpioPullNo, GpioSpeedLow); - furi_hal_gpio_init(&gpio_ext_pa6, GpioModeAnalog, GpioPullNo, GpioSpeedLow); - furi_hal_gpio_init(&gpio_ext_pa7, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + furi_hal_resources_init_gpio_pins(GpioModeAnalog); } void furi_hal_resources_deinit_early() { diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 2222d17021b..7a38df4932d 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,36.1,, +Version,+,37.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -898,8 +898,9 @@ Function,+,flipper_application_get_manifest,const FlipperApplicationManifest*,Fl Function,+,flipper_application_is_plugin,_Bool,FlipperApplication* Function,+,flipper_application_load_name_and_icon,_Bool,"FuriString*, Storage*, uint8_t**, FuriString*" Function,+,flipper_application_load_status_to_string,const char*,FlipperApplicationLoadStatus -Function,+,flipper_application_manifest_is_compatible,_Bool,"const FlipperApplicationManifest*, const ElfApiInterface*" Function,+,flipper_application_manifest_is_target_compatible,_Bool,const FlipperApplicationManifest* +Function,+,flipper_application_manifest_is_too_new,_Bool,"const FlipperApplicationManifest*, const ElfApiInterface*" +Function,+,flipper_application_manifest_is_too_old,_Bool,"const FlipperApplicationManifest*, const ElfApiInterface*" Function,+,flipper_application_manifest_is_valid,_Bool,const FlipperApplicationManifest* Function,+,flipper_application_map_to_memory,FlipperApplicationLoadStatus,FlipperApplication* Function,+,flipper_application_plugin_get_descriptor,const FlipperAppPluginDescriptor*,FlipperApplication* diff --git a/firmware/targets/f7/furi_hal/furi_hal_resources.c b/firmware/targets/f7/furi_hal/furi_hal_resources.c index d519484d100..fe4640d5bb1 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_resources.c +++ b/firmware/targets/f7/furi_hal/furi_hal_resources.c @@ -117,6 +117,14 @@ static void furi_hal_resources_init_input_pins(GpioMode mode) { } } +static void furi_hal_resources_init_gpio_pins(GpioMode mode) { + for(size_t i = 0; i < gpio_pins_count; i++) { + if(!gpio_pins[i].debug) { + furi_hal_gpio_init(gpio_pins[i].pin, mode, GpioPullNo, GpioSpeedLow); + } + } +} + void furi_hal_resources_init_early() { furi_hal_bus_enable(FuriHalBusGPIOA); furi_hal_bus_enable(FuriHalBusGPIOB); @@ -161,14 +169,7 @@ void furi_hal_resources_init_early() { furi_hal_gpio_write(&gpio_usb_dp, 0); // External header pins - furi_hal_gpio_init(&gpio_ext_pc0, GpioModeAnalog, GpioPullNo, GpioSpeedLow); - furi_hal_gpio_init(&gpio_ext_pc1, GpioModeAnalog, GpioPullNo, GpioSpeedLow); - furi_hal_gpio_init(&gpio_ext_pc3, GpioModeAnalog, GpioPullNo, GpioSpeedLow); - furi_hal_gpio_init(&gpio_ext_pb2, GpioModeAnalog, GpioPullNo, GpioSpeedLow); - furi_hal_gpio_init(&gpio_ext_pb3, GpioModeAnalog, GpioPullNo, GpioSpeedLow); - furi_hal_gpio_init(&gpio_ext_pa4, GpioModeAnalog, GpioPullNo, GpioSpeedLow); - furi_hal_gpio_init(&gpio_ext_pa6, GpioModeAnalog, GpioPullNo, GpioSpeedLow); - furi_hal_gpio_init(&gpio_ext_pa7, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + furi_hal_resources_init_gpio_pins(GpioModeAnalog); } void furi_hal_resources_deinit_early() { diff --git a/lib/flipper_application/application_manifest.c b/lib/flipper_application/application_manifest.c index fea92c26227..addbd5e4cb1 100644 --- a/lib/flipper_application/application_manifest.c +++ b/lib/flipper_application/application_manifest.c @@ -11,10 +11,21 @@ bool flipper_application_manifest_is_valid(const FlipperApplicationManifest* man return true; } -bool flipper_application_manifest_is_compatible( +bool flipper_application_manifest_is_too_old( const FlipperApplicationManifest* manifest, const ElfApiInterface* api_interface) { - if(manifest->base.api_version.major != api_interface->api_version_major /* || + if(manifest->base.api_version.major < api_interface->api_version_major /* || + manifest->base.api_version.minor > app->api_interface->api_version_minor */) { + return false; + } + + return true; +} + +bool flipper_application_manifest_is_too_new( + const FlipperApplicationManifest* manifest, + const ElfApiInterface* api_interface) { + if(manifest->base.api_version.major > api_interface->api_version_major /* || manifest->base.api_version.minor > app->api_interface->api_version_minor */) { return false; } diff --git a/lib/flipper_application/application_manifest.h b/lib/flipper_application/application_manifest.h index d09ec90044c..5b87b811c5f 100644 --- a/lib/flipper_application/application_manifest.h +++ b/lib/flipper_application/application_manifest.h @@ -54,14 +54,25 @@ typedef FlipperApplicationManifestV1 FlipperApplicationManifest; */ bool flipper_application_manifest_is_valid(const FlipperApplicationManifest* manifest); -/** - * @brief Check if manifest is compatible with current ELF API interface - * - * @param manifest - * @param api_interface - * @return bool +/** Check if API Version declared in manifest is older than firmware ELF API interface + * + * @param manifest The manifest + * @param api_interface The api interface + * + * @return bool + */ +bool flipper_application_manifest_is_too_old( + const FlipperApplicationManifest* manifest, + const ElfApiInterface* api_interface); + +/** Check if API Version declared in manifest is newer than firmware ELF API interface + * + * @param manifest The manifest + * @param api_interface The api interface + * + * @return bool */ -bool flipper_application_manifest_is_compatible( +bool flipper_application_manifest_is_too_new( const FlipperApplicationManifest* manifest, const ElfApiInterface* api_interface); diff --git a/lib/flipper_application/flipper_application.c b/lib/flipper_application/flipper_application.c index fbcf2973dbe..d56a8a7ef06 100644 --- a/lib/flipper_application/flipper_application.c +++ b/lib/flipper_application/flipper_application.c @@ -101,9 +101,14 @@ static FlipperApplicationPreloadStatus return FlipperApplicationPreloadStatusTargetMismatch; } - if(!flipper_application_manifest_is_compatible( + if(!flipper_application_manifest_is_too_old( &app->manifest, elf_file_get_api_interface(app->elf))) { - return FlipperApplicationPreloadStatusApiMismatch; + return FlipperApplicationPreloadStatusApiTooOld; + } + + if(!flipper_application_manifest_is_too_new( + &app->manifest, elf_file_get_api_interface(app->elf))) { + return FlipperApplicationPreloadStatusApiTooNew; } return FlipperApplicationPreloadStatusSuccess; @@ -257,7 +262,8 @@ static const char* preload_status_strings[] = { [FlipperApplicationPreloadStatusUnspecifiedError] = "Unknown error", [FlipperApplicationPreloadStatusInvalidFile] = "Invalid file", [FlipperApplicationPreloadStatusInvalidManifest] = "Invalid file manifest", - [FlipperApplicationPreloadStatusApiMismatch] = "API version mismatch", + [FlipperApplicationPreloadStatusApiTooOld] = "Update Application to use with this Firmware (ApiTooOld)", + [FlipperApplicationPreloadStatusApiTooNew] = "Update Firmware to use with this Application (ApiTooNew)", [FlipperApplicationPreloadStatusTargetMismatch] = "Hardware target mismatch", }; @@ -265,7 +271,7 @@ static const char* load_status_strings[] = { [FlipperApplicationLoadStatusSuccess] = "Success", [FlipperApplicationLoadStatusUnspecifiedError] = "Unknown error", [FlipperApplicationLoadStatusNoFreeMemory] = "Out of memory", - [FlipperApplicationLoadStatusMissingImports] = "Found unsatisfied imports", + [FlipperApplicationLoadStatusMissingImports] = "Update Firmware to use with this Application (MissingImports)", }; const char* flipper_application_preload_status_to_string(FlipperApplicationPreloadStatus status) { diff --git a/lib/flipper_application/flipper_application.h b/lib/flipper_application/flipper_application.h index 20baae8264f..a119cf530de 100644 --- a/lib/flipper_application/flipper_application.h +++ b/lib/flipper_application/flipper_application.h @@ -21,7 +21,8 @@ typedef enum { FlipperApplicationPreloadStatusUnspecifiedError, FlipperApplicationPreloadStatusInvalidFile, FlipperApplicationPreloadStatusInvalidManifest, - FlipperApplicationPreloadStatusApiMismatch, + FlipperApplicationPreloadStatusApiTooOld, + FlipperApplicationPreloadStatusApiTooNew, FlipperApplicationPreloadStatusTargetMismatch, } FlipperApplicationPreloadStatus; diff --git a/lib/u8g2/u8g2_glue.c b/lib/u8g2/u8g2_glue.c index 0142e3e2fdc..9463d131838 100644 --- a/lib/u8g2/u8g2_glue.c +++ b/lib/u8g2/u8g2_glue.c @@ -3,7 +3,7 @@ #include #define CONTRAST_ERC 31 -#define CONTRAST_MGG 31 +#define CONTRAST_MGG 27 uint8_t u8g2_gpio_and_delay_stm32(u8x8_t* u8x8, uint8_t msg, uint8_t arg_int, void* arg_ptr) { UNUSED(u8x8); diff --git a/scripts/flipper/utils/fff.py b/scripts/flipper/utils/fff.py index fa689b0168d..3175a1b0041 100644 --- a/scripts/flipper/utils/fff.py +++ b/scripts/flipper/utils/fff.py @@ -67,7 +67,10 @@ def writeEmptyLine(self): self.writeLine("") def writeComment(self, text: str): - self.writeLine(f"# {text}") + if text: + self.writeLine(f"# {text}") + else: + self.writeLine("#") def getHeader(self): if self.cursor != 0 and len(self.lines) == 0: diff --git a/scripts/infrared.py b/scripts/infrared.py new file mode 100755 index 00000000000..9fa44a90aa0 --- /dev/null +++ b/scripts/infrared.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 + +from os import path + +from flipper.app import App +from flipper.utils.fff import * + + +class Main(App): + def init(self): + # Subparsers + self.subparsers = self.parser.add_subparsers(help="sub-command help") + + self.parser_cleanup = self.subparsers.add_parser( + "cleanup", help="Cleanup duplicate remotes" + ) + self.parser_cleanup.add_argument("filename", type=str) + self.parser_cleanup.set_defaults(func=self.cleanup) + + def cleanup(self): + f = FlipperFormatFile() + f.load(self.args.filename) + + filetype, version = f.getHeader() + if filetype != "IR library file" or version != 1: + self.logger.error(f"Incorrect file type({filetype}) or version({version})") + return 1 + + data = [] + unique = {} + while True: + try: + d = {} + d["name"] = f.readKey("name") + d["type"] = f.readKey("type") + key = None + if d["type"] == "parsed": + d["protocol"] = f.readKey("protocol") + d["address"] = f.readKey("address") + d["command"] = f.readKey("command") + key = f'{d["protocol"]}{d["address"]}{d["command"]}' + elif d["type"] == "raw": + d["frequency"] = f.readKey("frequency") + d["duty_cycle"] = f.readKey("duty_cycle") + d["data"] = f.readKey("data") + key = f'{d["frequency"]}{d["duty_cycle"]}{d["data"]}' + else: + raise Exception(f'Unknown type: {d["type"]}') + if not key in unique: + unique[key] = d + data.append(d) + else: + self.logger.warn(f"Duplicate key: {key}") + except EOFError: + break + # Form new file + f = FlipperFormatFile() + f.setHeader(filetype, version) + for i in data: + f.writeComment(None) + f.writeKey("name", i["name"]) + f.writeKey("type", i["type"]) + if i["type"] == "parsed": + f.writeKey("protocol", i["protocol"]) + f.writeKey("address", i["address"]) + f.writeKey("command", i["command"]) + elif i["type"] == "raw": + f.writeKey("frequency", i["frequency"]) + f.writeKey("duty_cycle", i["duty_cycle"]) + f.writeKey("data", i["data"]) + else: + raise Exception(f'Unknown type: {i["type"]}') + f.save(self.args.filename) + + return 0 + + +if __name__ == "__main__": + Main()() diff --git a/scripts/ob.py b/scripts/ob.py index b7a601612b3..3480f275e7e 100755 --- a/scripts/ob.py +++ b/scripts/ob.py @@ -22,7 +22,8 @@ def init(self): self.parser_set = self.subparsers.add_parser("set", help="Set Option Bytes") self._add_args(self.parser_set) self.parser_set.set_defaults(func=self.set) - # Set command + + # Recover command self.parser_recover = self.subparsers.add_parser( "recover", help="Recover Option Bytes" ) From 4705812d244fdb4521cbc3574a66332d6957ef2d Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Mon, 11 Sep 2023 12:30:56 +0300 Subject: [PATCH 747/824] SD-Card: proper HAL (#3058) * Storage: remove unused error from SDInfo * FatFS: remove sd_spi_io * HAL: sd card api * Update: use sd hal * FatFS: use sd hal * Storage: use sd hal * API: sd hal * Fix TODO workflow * SD Hal: Fix source buffer overflow * fix for fix! * HAL: cleanup sd detection api * HAL: FURI_HAL_SD_SPI_DEBUG flag * HAL: FuriHalSdStatus -> FuriStatus * API: downgrade * Change define logic * HAL: presence --- .../workflows/lint_and_submodule_check.yml | 2 +- .../services/storage/storage_sd_api.h | 2 - .../services/storage/storages/storage_ext.c | 40 +- documentation/FuriHalDebuging.md | 4 + firmware/targets/f18/api_symbols.csv | 14 +- firmware/targets/f7/api_symbols.csv | 14 +- firmware/targets/f7/fatfs/sd_spi_io.c | 843 ------------- firmware/targets/f7/fatfs/sd_spi_io.h | 158 --- firmware/targets/f7/fatfs/user_diskio.c | 188 +-- firmware/targets/f7/fatfs/user_diskio.h | 1 - firmware/targets/f7/furi_hal/furi_hal_sd.c | 1081 ++++++++++++++++- firmware/targets/f7/src/update.c | 6 +- .../targets/furi_hal_include/furi_hal_sd.h | 78 +- 13 files changed, 1207 insertions(+), 1224 deletions(-) delete mode 100644 firmware/targets/f7/fatfs/sd_spi_io.c delete mode 100644 firmware/targets/f7/fatfs/sd_spi_io.h diff --git a/.github/workflows/lint_and_submodule_check.yml b/.github/workflows/lint_and_submodule_check.yml index 62e02b8a403..d24422b7cbd 100644 --- a/.github/workflows/lint_and_submodule_check.yml +++ b/.github/workflows/lint_and_submodule_check.yml @@ -48,7 +48,7 @@ jobs: run: | set +e; git diff --unified=0 --no-color ${{ github.event.pull_request.base.sha }} ${{ github.sha }} | grep -E '^\+' | grep -i -E '(TODO|HACK|FIXME|XXX)[ :]' | grep -v -- '-nofl' > lines.log; - MISSING_TICKETS=$( grep -v -E '\[FL-[0-9]+\]' lines.log ); + MISSING_TICKETS=$( grep -v -E 'FL-[0-9]+' lines.log ); if [ -n "$MISSING_TICKETS" ]; then echo "Error: Missing ticket number in \`TODO\` comment(s)" >> $GITHUB_STEP_SUMMARY; echo "\`\`\`" >> $GITHUB_STEP_SUMMARY; diff --git a/applications/services/storage/storage_sd_api.h b/applications/services/storage/storage_sd_api.h index 88064039456..842334d50db 100644 --- a/applications/services/storage/storage_sd_api.h +++ b/applications/services/storage/storage_sd_api.h @@ -32,8 +32,6 @@ typedef struct { uint32_t product_serial_number; uint8_t manufacturing_month; uint16_t manufacturing_year; - - FS_Error error; } SDInfo; const char* sd_api_get_fs_type_text(SDFsType fs_type); diff --git a/applications/services/storage/storages/storage_ext.c b/applications/services/storage/storages/storage_ext.c index 080ac4faf84..4630d99ea74 100644 --- a/applications/services/storage/storages/storage_ext.c +++ b/applications/services/storage/storages/storage_ext.c @@ -26,11 +26,11 @@ static FS_Error storage_ext_parse_error(SDError error); static bool sd_mount_card_internal(StorageData* storage, bool notify) { bool result = false; - uint8_t counter = sd_max_mount_retry_count(); + uint8_t counter = furi_hal_sd_max_mount_retry_count(); uint8_t bsp_result; SDData* sd_data = storage->data; - while(result == false && counter > 0 && hal_sd_detect()) { + while(result == false && counter > 0 && furi_hal_sd_is_present()) { if(notify) { NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); sd_notify_wait(notification); @@ -39,9 +39,9 @@ static bool sd_mount_card_internal(StorageData* storage, bool notify) { if((counter % 2) == 0) { // power reset sd card - bsp_result = sd_init(true); + bsp_result = furi_hal_sd_init(true); } else { - bsp_result = sd_init(false); + bsp_result = furi_hal_sd_init(false); } if(bsp_result) { @@ -225,18 +225,18 @@ FS_Error sd_card_info(StorageData* storage, SDInfo* sd_info) { #endif } - SD_CID cid; - SdSpiStatus status = sd_get_cid(&cid); - - if(status == SdSpiStatusOK) { - sd_info->manufacturer_id = cid.ManufacturerID; - memcpy(sd_info->oem_id, cid.OEM_AppliID, sizeof(cid.OEM_AppliID)); - memcpy(sd_info->product_name, cid.ProdName, sizeof(cid.ProdName)); - sd_info->product_revision_major = cid.ProdRev >> 4; - sd_info->product_revision_minor = cid.ProdRev & 0x0F; - sd_info->product_serial_number = cid.ProdSN; - sd_info->manufacturing_year = 2000 + cid.ManufactYear; - sd_info->manufacturing_month = cid.ManufactMonth; + FuriHalSdInfo info; + FuriStatus status = furi_hal_sd_info(&info); + + if(status == FuriStatusOk) { + sd_info->manufacturer_id = info.manufacturer_id; + memcpy(sd_info->oem_id, info.oem_id, sizeof(info.oem_id)); + memcpy(sd_info->product_name, info.product_name, sizeof(info.product_name)); + sd_info->product_revision_major = info.product_revision_major; + sd_info->product_revision_minor = info.product_revision_minor; + sd_info->product_serial_number = info.product_serial_number; + sd_info->manufacturing_year = info.manufacturing_year; + sd_info->manufacturing_month = info.manufacturing_month; } return storage_ext_parse_error(error); @@ -246,19 +246,19 @@ static void storage_ext_tick_internal(StorageData* storage, bool notify) { SDData* sd_data = storage->data; if(sd_data->sd_was_present) { - if(hal_sd_detect()) { + if(furi_hal_sd_is_present()) { FURI_LOG_I(TAG, "card detected"); sd_data->sd_was_present = false; sd_mount_card(storage, notify); - if(!hal_sd_detect()) { + if(!furi_hal_sd_is_present()) { FURI_LOG_I(TAG, "card removed while mounting"); sd_unmount_card(storage); sd_data->sd_was_present = true; } } } else { - if(!hal_sd_detect()) { + if(!furi_hal_sd_is_present()) { FURI_LOG_I(TAG, "card removed"); sd_data->sd_was_present = true; @@ -639,7 +639,7 @@ void storage_ext_init(StorageData* storage) { storage->api.tick = storage_ext_tick; storage->fs_api = &fs_api; - hal_sd_detect_init(); + furi_hal_sd_presence_init(); // do not notify on first launch, notifications app is waiting for our thread to read settings storage_ext_tick_internal(storage, false); diff --git a/documentation/FuriHalDebuging.md b/documentation/FuriHalDebuging.md index e7f2d8f2abe..da00cbdfb73 100644 --- a/documentation/FuriHalDebuging.md +++ b/documentation/FuriHalDebuging.md @@ -24,3 +24,7 @@ There are 2 signals that will be exposed to external GPIO pins: - `WFI` - `PB2` - Light sleep (wait for interrupt) used. Basically this is lightest and most non-breaking things power save mode. All function and debug should work correctly in this mode. - `STOP` - `PC3` - STOP mode used. Platform deep sleep mode. Extremely fragile mode where most of the silicon is disabled or in unusable state. Debugging MCU in this mode is nearly impossible. + +## FuriHalSD + +`--extra-define=FURI_HAL_SD_SPI_DEBUG` enables SD card SPI bus logging. diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index 2803af5c393..0c20649318a 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,37.0,, +Version,+,38.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -1224,6 +1224,14 @@ Function,+,furi_hal_rtc_set_pin_fails,void,uint32_t Function,+,furi_hal_rtc_set_register,void,"FuriHalRtcRegister, uint32_t" Function,+,furi_hal_rtc_sync_shadow,void, Function,+,furi_hal_rtc_validate_datetime,_Bool,FuriHalRtcDateTime* +Function,+,furi_hal_sd_get_card_state,FuriStatus, +Function,+,furi_hal_sd_info,FuriStatus,FuriHalSdInfo* +Function,+,furi_hal_sd_init,FuriStatus,_Bool +Function,+,furi_hal_sd_is_present,_Bool, +Function,+,furi_hal_sd_max_mount_retry_count,uint8_t, +Function,+,furi_hal_sd_presence_init,void, +Function,+,furi_hal_sd_read_blocks,FuriStatus,"uint32_t*, uint32_t, uint32_t" +Function,+,furi_hal_sd_write_blocks,FuriStatus,"const uint32_t*, uint32_t, uint32_t" Function,+,furi_hal_speaker_acquire,_Bool,uint32_t Function,-,furi_hal_speaker_deinit,void, Function,-,furi_hal_speaker_init,void, @@ -1479,9 +1487,6 @@ Function,+,gui_remove_view_port,void,"Gui*, ViewPort*" Function,+,gui_set_lockdown,void,"Gui*, _Bool" Function,-,gui_view_port_send_to_back,void,"Gui*, ViewPort*" Function,+,gui_view_port_send_to_front,void,"Gui*, ViewPort*" -Function,+,hal_sd_detect,_Bool, -Function,+,hal_sd_detect_init,void, -Function,+,hal_sd_detect_set_low,void, Function,+,hex_char_to_hex_nibble,_Bool,"char, uint8_t*" Function,+,hex_char_to_uint8,_Bool,"char, char, uint8_t*" Function,+,hex_chars_to_uint64,_Bool,"const char*, uint64_t*" @@ -2425,7 +2430,6 @@ Variable,+,furi_hal_i2c_bus_external,FuriHalI2cBus, Variable,+,furi_hal_i2c_bus_power,FuriHalI2cBus, Variable,+,furi_hal_i2c_handle_external,FuriHalI2cBusHandle, Variable,+,furi_hal_i2c_handle_power,FuriHalI2cBusHandle, -Variable,+,furi_hal_sd_spi_handle,FuriHalSpiBusHandle*, Variable,+,furi_hal_spi_bus_d,FuriHalSpiBus, Variable,+,furi_hal_spi_bus_handle_display,FuriHalSpiBusHandle, Variable,+,furi_hal_spi_bus_handle_external,FuriHalSpiBusHandle, diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 7a38df4932d..0819851543b 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,37.0,, +Version,+,38.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -1365,6 +1365,14 @@ Function,+,furi_hal_rtc_set_pin_fails,void,uint32_t Function,+,furi_hal_rtc_set_register,void,"FuriHalRtcRegister, uint32_t" Function,+,furi_hal_rtc_sync_shadow,void, Function,+,furi_hal_rtc_validate_datetime,_Bool,FuriHalRtcDateTime* +Function,+,furi_hal_sd_get_card_state,FuriStatus, +Function,+,furi_hal_sd_info,FuriStatus,FuriHalSdInfo* +Function,+,furi_hal_sd_init,FuriStatus,_Bool +Function,+,furi_hal_sd_is_present,_Bool, +Function,+,furi_hal_sd_max_mount_retry_count,uint8_t, +Function,+,furi_hal_sd_presence_init,void, +Function,+,furi_hal_sd_read_blocks,FuriStatus,"uint32_t*, uint32_t, uint32_t" +Function,+,furi_hal_sd_write_blocks,FuriStatus,"const uint32_t*, uint32_t, uint32_t" Function,+,furi_hal_speaker_acquire,_Bool,uint32_t Function,-,furi_hal_speaker_deinit,void, Function,-,furi_hal_speaker_init,void, @@ -1650,9 +1658,6 @@ Function,+,gui_remove_view_port,void,"Gui*, ViewPort*" Function,+,gui_set_lockdown,void,"Gui*, _Bool" Function,-,gui_view_port_send_to_back,void,"Gui*, ViewPort*" Function,+,gui_view_port_send_to_front,void,"Gui*, ViewPort*" -Function,+,hal_sd_detect,_Bool, -Function,+,hal_sd_detect_init,void, -Function,+,hal_sd_detect_set_low,void, Function,+,hex_char_to_hex_nibble,_Bool,"char, uint8_t*" Function,+,hex_char_to_uint8,_Bool,"char, char, uint8_t*" Function,+,hex_chars_to_uint64,_Bool,"const char*, uint64_t*" @@ -3196,7 +3201,6 @@ Variable,+,furi_hal_i2c_bus_external,FuriHalI2cBus, Variable,+,furi_hal_i2c_bus_power,FuriHalI2cBus, Variable,+,furi_hal_i2c_handle_external,FuriHalI2cBusHandle, Variable,+,furi_hal_i2c_handle_power,FuriHalI2cBusHandle, -Variable,+,furi_hal_sd_spi_handle,FuriHalSpiBusHandle*, Variable,+,furi_hal_spi_bus_d,FuriHalSpiBus, Variable,+,furi_hal_spi_bus_handle_display,FuriHalSpiBusHandle, Variable,+,furi_hal_spi_bus_handle_external,FuriHalSpiBusHandle, diff --git a/firmware/targets/f7/fatfs/sd_spi_io.c b/firmware/targets/f7/fatfs/sd_spi_io.c deleted file mode 100644 index d420524df56..00000000000 --- a/firmware/targets/f7/fatfs/sd_spi_io.c +++ /dev/null @@ -1,843 +0,0 @@ -#include "sd_spi_io.h" -#include "sector_cache.h" -#include -#include -#include - -// #define SD_SPI_DEBUG 1 -#define TAG "SdSpi" - -#ifdef SD_SPI_DEBUG -#define sd_spi_debug(...) FURI_LOG_I(TAG, __VA_ARGS__) -#else -#define sd_spi_debug(...) -#endif - -#define SD_CMD_LENGTH 6 -#define SD_DUMMY_BYTE 0xFF -#define SD_ANSWER_RETRY_COUNT 8 -#define SD_IDLE_RETRY_COUNT 100 - -#define FLAG_SET(x, y) (((x) & (y)) == (y)) - -static bool sd_high_capacity = false; - -typedef enum { - SdSpiDataResponceOK = 0x05, - SdSpiDataResponceCRCError = 0x0B, - SdSpiDataResponceWriteError = 0x0D, - SdSpiDataResponceOtherError = 0xFF, -} SdSpiDataResponce; - -typedef struct { - uint8_t r1; - uint8_t r2; - uint8_t r3; - uint8_t r4; - uint8_t r5; -} SdSpiCmdAnswer; - -typedef enum { - SdSpiCmdAnswerTypeR1, - SdSpiCmdAnswerTypeR1B, - SdSpiCmdAnswerTypeR2, - SdSpiCmdAnswerTypeR3, - SdSpiCmdAnswerTypeR4R5, - SdSpiCmdAnswerTypeR7, -} SdSpiCmdAnswerType; - -/* - SdSpiCmd and SdSpiToken use non-standard enum value names convention, - because it is more convenient to look for documentation on a specific command. - For example, to find out what the SD_CMD23_SET_BLOCK_COUNT command does, you need to look for - SET_BLOCK_COUNT or CMD23 in the "Part 1 Physical Layer Simplified Specification". - - Do not use that naming convention in other places. -*/ - -typedef enum { - SD_CMD0_GO_IDLE_STATE = 0, - SD_CMD1_SEND_OP_COND = 1, - SD_CMD8_SEND_IF_COND = 8, - SD_CMD9_SEND_CSD = 9, - SD_CMD10_SEND_CID = 10, - SD_CMD12_STOP_TRANSMISSION = 12, - SD_CMD13_SEND_STATUS = 13, - SD_CMD16_SET_BLOCKLEN = 16, - SD_CMD17_READ_SINGLE_BLOCK = 17, - SD_CMD18_READ_MULT_BLOCK = 18, - SD_CMD23_SET_BLOCK_COUNT = 23, - SD_CMD24_WRITE_SINGLE_BLOCK = 24, - SD_CMD25_WRITE_MULT_BLOCK = 25, - SD_CMD27_PROG_CSD = 27, - SD_CMD28_SET_WRITE_PROT = 28, - SD_CMD29_CLR_WRITE_PROT = 29, - SD_CMD30_SEND_WRITE_PROT = 30, - SD_CMD32_SD_ERASE_GRP_START = 32, - SD_CMD33_SD_ERASE_GRP_END = 33, - SD_CMD34_UNTAG_SECTOR = 34, - SD_CMD35_ERASE_GRP_START = 35, - SD_CMD36_ERASE_GRP_END = 36, - SD_CMD37_UNTAG_ERASE_GROUP = 37, - SD_CMD38_ERASE = 38, - SD_CMD41_SD_APP_OP_COND = 41, - SD_CMD55_APP_CMD = 55, - SD_CMD58_READ_OCR = 58, -} SdSpiCmd; - -/** Data tokens */ -typedef enum { - SD_TOKEN_START_DATA_SINGLE_BLOCK_READ = 0xFE, - SD_TOKEN_START_DATA_MULTIPLE_BLOCK_READ = 0xFE, - SD_TOKEN_START_DATA_SINGLE_BLOCK_WRITE = 0xFE, - SD_TOKEN_START_DATA_MULTIPLE_BLOCK_WRITE = 0xFC, - SD_TOKEN_STOP_DATA_MULTIPLE_BLOCK_WRITE = 0xFD, -} SdSpiToken; - -/** R1 answer value */ -typedef enum { - SdSpi_R1_NO_ERROR = 0x00, - SdSpi_R1_IN_IDLE_STATE = 0x01, - SdSpi_R1_ERASE_RESET = 0x02, - SdSpi_R1_ILLEGAL_COMMAND = 0x04, - SdSpi_R1_COM_CRC_ERROR = 0x08, - SdSpi_R1_ERASE_SEQUENCE_ERROR = 0x10, - SdSpi_R1_ADDRESS_ERROR = 0x20, - SdSpi_R1_PARAMETER_ERROR = 0x40, -} SdSpiR1; - -/** R2 answer value */ -typedef enum { - /* R2 answer value */ - SdSpi_R2_NO_ERROR = 0x00, - SdSpi_R2_CARD_LOCKED = 0x01, - SdSpi_R2_LOCKUNLOCK_ERROR = 0x02, - SdSpi_R2_ERROR = 0x04, - SdSpi_R2_CC_ERROR = 0x08, - SdSpi_R2_CARD_ECC_FAILED = 0x10, - SdSpi_R2_WP_VIOLATION = 0x20, - SdSpi_R2_ERASE_PARAM = 0x40, - SdSpi_R2_OUTOFRANGE = 0x80, -} SdSpiR2; - -static inline void sd_spi_select_card() { - furi_hal_gpio_write(furi_hal_sd_spi_handle->cs, false); - furi_delay_us(10); // Entry guard time for some SD cards -} - -static inline void sd_spi_deselect_card() { - furi_delay_us(10); // Exit guard time for some SD cards - furi_hal_gpio_write(furi_hal_sd_spi_handle->cs, true); -} - -static void sd_spi_bus_to_ground() { - furi_hal_gpio_init_ex( - furi_hal_sd_spi_handle->miso, - GpioModeOutputPushPull, - GpioPullNo, - GpioSpeedVeryHigh, - GpioAltFnUnused); - furi_hal_gpio_init_ex( - furi_hal_sd_spi_handle->mosi, - GpioModeOutputPushPull, - GpioPullNo, - GpioSpeedVeryHigh, - GpioAltFnUnused); - furi_hal_gpio_init_ex( - furi_hal_sd_spi_handle->sck, - GpioModeOutputPushPull, - GpioPullNo, - GpioSpeedVeryHigh, - GpioAltFnUnused); - - sd_spi_select_card(); - furi_hal_gpio_write(furi_hal_sd_spi_handle->miso, false); - furi_hal_gpio_write(furi_hal_sd_spi_handle->mosi, false); - furi_hal_gpio_write(furi_hal_sd_spi_handle->sck, false); -} - -static void sd_spi_bus_rise_up() { - sd_spi_deselect_card(); - - furi_hal_gpio_init_ex( - furi_hal_sd_spi_handle->miso, - GpioModeAltFunctionPushPull, - GpioPullUp, - GpioSpeedVeryHigh, - GpioAltFn5SPI2); - furi_hal_gpio_init_ex( - furi_hal_sd_spi_handle->mosi, - GpioModeAltFunctionPushPull, - GpioPullUp, - GpioSpeedVeryHigh, - GpioAltFn5SPI2); - furi_hal_gpio_init_ex( - furi_hal_sd_spi_handle->sck, - GpioModeAltFunctionPushPull, - GpioPullUp, - GpioSpeedVeryHigh, - GpioAltFn5SPI2); -} - -static inline uint8_t sd_spi_read_byte(void) { - uint8_t responce; - furi_check(furi_hal_spi_bus_trx(furi_hal_sd_spi_handle, NULL, &responce, 1, SD_TIMEOUT_MS)); - return responce; -} - -static inline void sd_spi_write_byte(uint8_t data) { - furi_check(furi_hal_spi_bus_trx(furi_hal_sd_spi_handle, &data, NULL, 1, SD_TIMEOUT_MS)); -} - -static inline uint8_t sd_spi_write_and_read_byte(uint8_t data) { - uint8_t responce; - furi_check(furi_hal_spi_bus_trx(furi_hal_sd_spi_handle, &data, &responce, 1, SD_TIMEOUT_MS)); - return responce; -} - -static inline void sd_spi_write_bytes(uint8_t* data, uint32_t size) { - furi_check(furi_hal_spi_bus_trx(furi_hal_sd_spi_handle, data, NULL, size, SD_TIMEOUT_MS)); -} - -static inline void sd_spi_read_bytes(uint8_t* data, uint32_t size) { - furi_check(furi_hal_spi_bus_trx(furi_hal_sd_spi_handle, NULL, data, size, SD_TIMEOUT_MS)); -} - -static inline void sd_spi_write_bytes_dma(uint8_t* data, uint32_t size) { - uint32_t timeout_mul = (size / 512) + 1; - furi_check(furi_hal_spi_bus_trx_dma( - furi_hal_sd_spi_handle, data, NULL, size, SD_TIMEOUT_MS * timeout_mul)); -} - -static inline void sd_spi_read_bytes_dma(uint8_t* data, uint32_t size) { - uint32_t timeout_mul = (size / 512) + 1; - furi_check(furi_hal_spi_bus_trx_dma( - furi_hal_sd_spi_handle, NULL, data, size, SD_TIMEOUT_MS * timeout_mul)); -} - -static uint8_t sd_spi_wait_for_data_and_read(void) { - uint8_t retry_count = SD_ANSWER_RETRY_COUNT; - uint8_t responce; - - // Wait until we get a valid data - do { - responce = sd_spi_read_byte(); - retry_count--; - - } while((responce == SD_DUMMY_BYTE) && retry_count); - - return responce; -} - -static SdSpiStatus sd_spi_wait_for_data(uint8_t data, uint32_t timeout_ms) { - FuriHalCortexTimer timer = furi_hal_cortex_timer_get(timeout_ms * 1000); - uint8_t byte; - - do { - byte = sd_spi_read_byte(); - if(furi_hal_cortex_timer_is_expired(timer)) { - return SdSpiStatusTimeout; - } - } while((byte != data)); - - return SdSpiStatusOK; -} - -static inline void sd_spi_deselect_card_and_purge() { - sd_spi_deselect_card(); - sd_spi_read_byte(); -} - -static inline void sd_spi_purge_crc() { - sd_spi_read_byte(); - sd_spi_read_byte(); -} - -static SdSpiCmdAnswer - sd_spi_send_cmd(SdSpiCmd cmd, uint32_t arg, uint8_t crc, SdSpiCmdAnswerType answer_type) { - uint8_t frame[SD_CMD_LENGTH]; - SdSpiCmdAnswer cmd_answer = { - .r1 = SD_DUMMY_BYTE, - .r2 = SD_DUMMY_BYTE, - .r3 = SD_DUMMY_BYTE, - .r4 = SD_DUMMY_BYTE, - .r5 = SD_DUMMY_BYTE, - }; - - // R1 Length = NCS(0)+ 6 Bytes command + NCR(min1 max8) + 1 Bytes answer + NEC(0) = 15bytes - // R1b identical to R1 + Busy information - // R2 Length = NCS(0)+ 6 Bytes command + NCR(min1 max8) + 2 Bytes answer + NEC(0) = 16bytes - - frame[0] = ((uint8_t)cmd | 0x40); - frame[1] = (uint8_t)(arg >> 24); - frame[2] = (uint8_t)(arg >> 16); - frame[3] = (uint8_t)(arg >> 8); - frame[4] = (uint8_t)(arg); - frame[5] = (crc | 0x01); - - sd_spi_select_card(); - sd_spi_write_bytes(frame, sizeof(frame)); - - switch(answer_type) { - case SdSpiCmdAnswerTypeR1: - cmd_answer.r1 = sd_spi_wait_for_data_and_read(); - break; - case SdSpiCmdAnswerTypeR1B: - // TODO FL-3507: can be wrong, at least for SD_CMD12_STOP_TRANSMISSION you need to purge one byte before reading R1 - cmd_answer.r1 = sd_spi_wait_for_data_and_read(); - - // In general this shenenigans seems suspicious, please double check SD specs if you are using SdSpiCmdAnswerTypeR1B - // reassert card - sd_spi_deselect_card(); - furi_delay_us(1000); - sd_spi_deselect_card(); - - // and wait for it to be ready - while(sd_spi_read_byte() != 0xFF) { - }; - - break; - case SdSpiCmdAnswerTypeR2: - cmd_answer.r1 = sd_spi_wait_for_data_and_read(); - cmd_answer.r2 = sd_spi_read_byte(); - break; - case SdSpiCmdAnswerTypeR3: - case SdSpiCmdAnswerTypeR7: - cmd_answer.r1 = sd_spi_wait_for_data_and_read(); - cmd_answer.r2 = sd_spi_read_byte(); - cmd_answer.r3 = sd_spi_read_byte(); - cmd_answer.r4 = sd_spi_read_byte(); - cmd_answer.r5 = sd_spi_read_byte(); - break; - default: - break; - } - return cmd_answer; -} - -static SdSpiDataResponce sd_spi_get_data_response(uint32_t timeout_ms) { - SdSpiDataResponce responce = sd_spi_read_byte(); - // read busy response byte - sd_spi_read_byte(); - - switch(responce & 0x1F) { - case SdSpiDataResponceOK: - // TODO FL-3508: check timings - sd_spi_deselect_card(); - sd_spi_select_card(); - - // wait for 0xFF - if(sd_spi_wait_for_data(0xFF, timeout_ms) == SdSpiStatusOK) { - return SdSpiDataResponceOK; - } else { - return SdSpiDataResponceOtherError; - } - case SdSpiDataResponceCRCError: - return SdSpiDataResponceCRCError; - case SdSpiDataResponceWriteError: - return SdSpiDataResponceWriteError; - default: - return SdSpiDataResponceOtherError; - } -} - -static SdSpiStatus sd_spi_init_spi_mode_v1(void) { - SdSpiCmdAnswer response; - uint8_t retry_count = 0; - - sd_spi_debug("Init SD card in SPI mode v1"); - - do { - retry_count++; - - // CMD55 (APP_CMD) before any ACMD command: R1 response (0x00: no errors) - sd_spi_send_cmd(SD_CMD55_APP_CMD, 0, 0xFF, SdSpiCmdAnswerTypeR1); - sd_spi_deselect_card_and_purge(); - - // ACMD41 (SD_APP_OP_COND) to initialize SDHC or SDXC cards: R1 response (0x00: no errors) - response = sd_spi_send_cmd(SD_CMD41_SD_APP_OP_COND, 0, 0xFF, SdSpiCmdAnswerTypeR1); - sd_spi_deselect_card_and_purge(); - - if(retry_count >= SD_IDLE_RETRY_COUNT) { - return SdSpiStatusError; - } - } while(response.r1 == SdSpi_R1_IN_IDLE_STATE); - - sd_spi_debug("Init SD card in SPI mode v1 done"); - - return SdSpiStatusOK; -} - -static SdSpiStatus sd_spi_init_spi_mode_v2(void) { - SdSpiCmdAnswer response; - uint8_t retry_count = 0; - - sd_spi_debug("Init SD card in SPI mode v2"); - - do { - retry_count++; - // CMD55 (APP_CMD) before any ACMD command: R1 response (0x00: no errors) - sd_spi_send_cmd(SD_CMD55_APP_CMD, 0, 0xFF, SdSpiCmdAnswerTypeR1); - sd_spi_deselect_card_and_purge(); - - // ACMD41 (APP_OP_COND) to initialize SDHC or SDXC cards: R1 response (0x00: no errors) - response = - sd_spi_send_cmd(SD_CMD41_SD_APP_OP_COND, 0x40000000, 0xFF, SdSpiCmdAnswerTypeR1); - sd_spi_deselect_card_and_purge(); - - if(retry_count >= SD_IDLE_RETRY_COUNT) { - sd_spi_debug("ACMD41 failed"); - return SdSpiStatusError; - } - } while(response.r1 == SdSpi_R1_IN_IDLE_STATE); - - if(FLAG_SET(response.r1, SdSpi_R1_ILLEGAL_COMMAND)) { - sd_spi_debug("ACMD41 is illegal command"); - retry_count = 0; - do { - retry_count++; - // CMD55 (APP_CMD) before any ACMD command: R1 response (0x00: no errors) - response = sd_spi_send_cmd(SD_CMD55_APP_CMD, 0, 0xFF, SdSpiCmdAnswerTypeR1); - sd_spi_deselect_card_and_purge(); - - if(response.r1 != SdSpi_R1_IN_IDLE_STATE) { - sd_spi_debug("CMD55 failed"); - return SdSpiStatusError; - } - // ACMD41 (SD_APP_OP_COND) to initialize SDHC or SDXC cards: R1 response (0x00: no errors) - response = sd_spi_send_cmd(SD_CMD41_SD_APP_OP_COND, 0, 0xFF, SdSpiCmdAnswerTypeR1); - sd_spi_deselect_card_and_purge(); - - if(retry_count >= SD_IDLE_RETRY_COUNT) { - sd_spi_debug("ACMD41 failed"); - return SdSpiStatusError; - } - } while(response.r1 == SdSpi_R1_IN_IDLE_STATE); - } - - sd_spi_debug("Init SD card in SPI mode v2 done"); - - return SdSpiStatusOK; -} - -static SdSpiStatus sd_spi_init_spi_mode(void) { - SdSpiCmdAnswer response; - uint8_t retry_count; - - // CMD0 (GO_IDLE_STATE) to put SD in SPI mode and - // wait for In Idle State Response (R1 Format) equal to 0x01 - retry_count = 0; - do { - retry_count++; - response = sd_spi_send_cmd(SD_CMD0_GO_IDLE_STATE, 0, 0x95, SdSpiCmdAnswerTypeR1); - sd_spi_deselect_card_and_purge(); - - if(retry_count >= SD_IDLE_RETRY_COUNT) { - sd_spi_debug("CMD0 failed"); - return SdSpiStatusError; - } - } while(response.r1 != SdSpi_R1_IN_IDLE_STATE); - - // CMD8 (SEND_IF_COND) to check the power supply status - // and wait until response (R7 Format) equal to 0xAA and - response = sd_spi_send_cmd(SD_CMD8_SEND_IF_COND, 0x1AA, 0x87, SdSpiCmdAnswerTypeR7); - sd_spi_deselect_card_and_purge(); - - if(FLAG_SET(response.r1, SdSpi_R1_ILLEGAL_COMMAND)) { - if(sd_spi_init_spi_mode_v1() != SdSpiStatusOK) { - sd_spi_debug("Init mode v1 failed"); - return SdSpiStatusError; - } - sd_high_capacity = 0; - } else if(response.r1 == SdSpi_R1_IN_IDLE_STATE) { - if(sd_spi_init_spi_mode_v2() != SdSpiStatusOK) { - sd_spi_debug("Init mode v2 failed"); - return SdSpiStatusError; - } - - // CMD58 (READ_OCR) to initialize SDHC or SDXC cards: R3 response - response = sd_spi_send_cmd(SD_CMD58_READ_OCR, 0, 0xFF, SdSpiCmdAnswerTypeR3); - sd_spi_deselect_card_and_purge(); - - if(response.r1 != SdSpi_R1_NO_ERROR) { - sd_spi_debug("CMD58 failed"); - return SdSpiStatusError; - } - sd_high_capacity = (response.r2 & 0x40) >> 6; - } else { - return SdSpiStatusError; - } - - sd_spi_debug("SD card is %s", sd_high_capacity ? "SDHC or SDXC" : "SDSC"); - return SdSpiStatusOK; -} - -static SdSpiStatus sd_spi_get_csd(SD_CSD* csd) { - uint16_t counter = 0; - uint8_t csd_data[16]; - SdSpiStatus ret = SdSpiStatusError; - SdSpiCmdAnswer response; - - // CMD9 (SEND_CSD): R1 format (0x00 is no errors) - response = sd_spi_send_cmd(SD_CMD9_SEND_CSD, 0, 0xFF, SdSpiCmdAnswerTypeR1); - - if(response.r1 == SdSpi_R1_NO_ERROR) { - if(sd_spi_wait_for_data(SD_TOKEN_START_DATA_SINGLE_BLOCK_READ, SD_TIMEOUT_MS) == - SdSpiStatusOK) { - // read CSD data - for(counter = 0; counter < 16; counter++) { - csd_data[counter] = sd_spi_read_byte(); - } - - sd_spi_purge_crc(); - - /************************************************************************* - CSD header decoding - *************************************************************************/ - - csd->CSDStruct = (csd_data[0] & 0xC0) >> 6; - csd->Reserved1 = csd_data[0] & 0x3F; - csd->TAAC = csd_data[1]; - csd->NSAC = csd_data[2]; - csd->MaxBusClkFrec = csd_data[3]; - csd->CardComdClasses = (csd_data[4] << 4) | ((csd_data[5] & 0xF0) >> 4); - csd->RdBlockLen = csd_data[5] & 0x0F; - csd->PartBlockRead = (csd_data[6] & 0x80) >> 7; - csd->WrBlockMisalign = (csd_data[6] & 0x40) >> 6; - csd->RdBlockMisalign = (csd_data[6] & 0x20) >> 5; - csd->DSRImpl = (csd_data[6] & 0x10) >> 4; - - /************************************************************************* - CSD v1/v2 decoding - *************************************************************************/ - - if(sd_high_capacity == 0) { - csd->version.v1.Reserved1 = ((csd_data[6] & 0x0C) >> 2); - csd->version.v1.DeviceSize = ((csd_data[6] & 0x03) << 10) | (csd_data[7] << 2) | - ((csd_data[8] & 0xC0) >> 6); - csd->version.v1.MaxRdCurrentVDDMin = (csd_data[8] & 0x38) >> 3; - csd->version.v1.MaxRdCurrentVDDMax = (csd_data[8] & 0x07); - csd->version.v1.MaxWrCurrentVDDMin = (csd_data[9] & 0xE0) >> 5; - csd->version.v1.MaxWrCurrentVDDMax = (csd_data[9] & 0x1C) >> 2; - csd->version.v1.DeviceSizeMul = ((csd_data[9] & 0x03) << 1) | - ((csd_data[10] & 0x80) >> 7); - } else { - csd->version.v2.Reserved1 = ((csd_data[6] & 0x0F) << 2) | - ((csd_data[7] & 0xC0) >> 6); - csd->version.v2.DeviceSize = ((csd_data[7] & 0x3F) << 16) | (csd_data[8] << 8) | - csd_data[9]; - csd->version.v2.Reserved2 = ((csd_data[10] & 0x80) >> 8); - } - - csd->EraseSingleBlockEnable = (csd_data[10] & 0x40) >> 6; - csd->EraseSectorSize = ((csd_data[10] & 0x3F) << 1) | ((csd_data[11] & 0x80) >> 7); - csd->WrProtectGrSize = (csd_data[11] & 0x7F); - csd->WrProtectGrEnable = (csd_data[12] & 0x80) >> 7; - csd->Reserved2 = (csd_data[12] & 0x60) >> 5; - csd->WrSpeedFact = (csd_data[12] & 0x1C) >> 2; - csd->MaxWrBlockLen = ((csd_data[12] & 0x03) << 2) | ((csd_data[13] & 0xC0) >> 6); - csd->WriteBlockPartial = (csd_data[13] & 0x20) >> 5; - csd->Reserved3 = (csd_data[13] & 0x1F); - csd->FileFormatGrouop = (csd_data[14] & 0x80) >> 7; - csd->CopyFlag = (csd_data[14] & 0x40) >> 6; - csd->PermWrProtect = (csd_data[14] & 0x20) >> 5; - csd->TempWrProtect = (csd_data[14] & 0x10) >> 4; - csd->FileFormat = (csd_data[14] & 0x0C) >> 2; - csd->Reserved4 = (csd_data[14] & 0x03); - csd->crc = (csd_data[15] & 0xFE) >> 1; - csd->Reserved5 = (csd_data[15] & 0x01); - - ret = SdSpiStatusOK; - } - } - - sd_spi_deselect_card_and_purge(); - - return ret; -} - -static SdSpiStatus sd_spi_get_cid(SD_CID* Cid) { - uint16_t counter = 0; - uint8_t cid_data[16]; - SdSpiStatus ret = SdSpiStatusError; - SdSpiCmdAnswer response; - - // CMD10 (SEND_CID): R1 format (0x00 is no errors) - response = sd_spi_send_cmd(SD_CMD10_SEND_CID, 0, 0xFF, SdSpiCmdAnswerTypeR1); - - if(response.r1 == SdSpi_R1_NO_ERROR) { - if(sd_spi_wait_for_data(SD_TOKEN_START_DATA_SINGLE_BLOCK_READ, SD_TIMEOUT_MS) == - SdSpiStatusOK) { - // read CID data - for(counter = 0; counter < 16; counter++) { - cid_data[counter] = sd_spi_read_byte(); - } - - sd_spi_purge_crc(); - - Cid->ManufacturerID = cid_data[0]; - memcpy(Cid->OEM_AppliID, cid_data + 1, 2); - memcpy(Cid->ProdName, cid_data + 3, 5); - Cid->ProdRev = cid_data[8]; - Cid->ProdSN = cid_data[9] << 24; - Cid->ProdSN |= cid_data[10] << 16; - Cid->ProdSN |= cid_data[11] << 8; - Cid->ProdSN |= cid_data[12]; - Cid->Reserved1 = (cid_data[13] & 0xF0) >> 4; - Cid->ManufactYear = (cid_data[13] & 0x0F) << 4; - Cid->ManufactYear |= (cid_data[14] & 0xF0) >> 4; - Cid->ManufactMonth = (cid_data[14] & 0x0F); - Cid->CID_CRC = (cid_data[15] & 0xFE) >> 1; - Cid->Reserved2 = 1; - - ret = SdSpiStatusOK; - } - } - - sd_spi_deselect_card_and_purge(); - - return ret; -} - -static SdSpiStatus - sd_spi_cmd_read_blocks(uint32_t* data, uint32_t address, uint32_t blocks, uint32_t timeout_ms) { - uint32_t block_address = address; - uint32_t offset = 0; - - // CMD16 (SET_BLOCKLEN): R1 response (0x00: no errors) - SdSpiCmdAnswer response = - sd_spi_send_cmd(SD_CMD16_SET_BLOCKLEN, SD_BLOCK_SIZE, 0xFF, SdSpiCmdAnswerTypeR1); - sd_spi_deselect_card_and_purge(); - - if(response.r1 != SdSpi_R1_NO_ERROR) { - return SdSpiStatusError; - } - - if(!sd_high_capacity) { - block_address = address * SD_BLOCK_SIZE; - } - - while(blocks--) { - // CMD17 (READ_SINGLE_BLOCK): R1 response (0x00: no errors) - response = - sd_spi_send_cmd(SD_CMD17_READ_SINGLE_BLOCK, block_address, 0xFF, SdSpiCmdAnswerTypeR1); - if(response.r1 != SdSpi_R1_NO_ERROR) { - sd_spi_deselect_card_and_purge(); - return SdSpiStatusError; - } - - // Wait for the data start token - if(sd_spi_wait_for_data(SD_TOKEN_START_DATA_SINGLE_BLOCK_READ, timeout_ms) == - SdSpiStatusOK) { - // Read the data block - sd_spi_read_bytes_dma((uint8_t*)data + offset, SD_BLOCK_SIZE); - sd_spi_purge_crc(); - - // increase offset - offset += SD_BLOCK_SIZE; - - // increase block address - if(sd_high_capacity) { - block_address += 1; - } else { - block_address += SD_BLOCK_SIZE; - } - } else { - sd_spi_deselect_card_and_purge(); - return SdSpiStatusError; - } - - sd_spi_deselect_card_and_purge(); - } - - return SdSpiStatusOK; -} - -static SdSpiStatus sd_spi_cmd_write_blocks( - uint32_t* data, - uint32_t address, - uint32_t blocks, - uint32_t timeout_ms) { - uint32_t block_address = address; - uint32_t offset = 0; - - // CMD16 (SET_BLOCKLEN): R1 response (0x00: no errors) - SdSpiCmdAnswer response = - sd_spi_send_cmd(SD_CMD16_SET_BLOCKLEN, SD_BLOCK_SIZE, 0xFF, SdSpiCmdAnswerTypeR1); - sd_spi_deselect_card_and_purge(); - - if(response.r1 != SdSpi_R1_NO_ERROR) { - return SdSpiStatusError; - } - - if(!sd_high_capacity) { - block_address = address * SD_BLOCK_SIZE; - } - - while(blocks--) { - // CMD24 (WRITE_SINGLE_BLOCK): R1 response (0x00: no errors) - response = sd_spi_send_cmd( - SD_CMD24_WRITE_SINGLE_BLOCK, block_address, 0xFF, SdSpiCmdAnswerTypeR1); - if(response.r1 != SdSpi_R1_NO_ERROR) { - sd_spi_deselect_card_and_purge(); - return SdSpiStatusError; - } - - // Send dummy byte for NWR timing : one byte between CMD_WRITE and TOKEN - // TODO FL-3509: check bytes count - sd_spi_write_byte(SD_DUMMY_BYTE); - sd_spi_write_byte(SD_DUMMY_BYTE); - - // Send the data start token - sd_spi_write_byte(SD_TOKEN_START_DATA_SINGLE_BLOCK_WRITE); - sd_spi_write_bytes_dma((uint8_t*)data + offset, SD_BLOCK_SIZE); - sd_spi_purge_crc(); - - // Read data response - SdSpiDataResponce data_responce = sd_spi_get_data_response(timeout_ms); - sd_spi_deselect_card_and_purge(); - - if(data_responce != SdSpiDataResponceOK) { - return SdSpiStatusError; - } - - // increase offset - offset += SD_BLOCK_SIZE; - - // increase block address - if(sd_high_capacity) { - block_address += 1; - } else { - block_address += SD_BLOCK_SIZE; - } - } - - return SdSpiStatusOK; -} - -uint8_t sd_max_mount_retry_count() { - return 10; -} - -SdSpiStatus sd_init(bool power_reset) { - // Slow speed init - furi_hal_spi_acquire(&furi_hal_spi_bus_handle_sd_slow); - furi_hal_sd_spi_handle = &furi_hal_spi_bus_handle_sd_slow; - - // We reset card in spi_lock context, so it is safe to disturb spi bus - if(power_reset) { - sd_spi_debug("Power reset"); - - // disable power and set low on all bus pins - furi_hal_power_disable_external_3_3v(); - sd_spi_bus_to_ground(); - hal_sd_detect_set_low(); - furi_delay_ms(250); - - // reinit bus and enable power - sd_spi_bus_rise_up(); - hal_sd_detect_init(); - furi_hal_power_enable_external_3_3v(); - furi_delay_ms(100); - } - - SdSpiStatus status = SdSpiStatusError; - - // Send 80 dummy clocks with CS high - sd_spi_deselect_card(); - for(uint8_t i = 0; i < 80; i++) { - sd_spi_write_byte(SD_DUMMY_BYTE); - } - - for(uint8_t i = 0; i < 128; i++) { - status = sd_spi_init_spi_mode(); - if(status == SdSpiStatusOK) { - // SD initialized and init to SPI mode properly - sd_spi_debug("SD init OK after %d retries", i); - break; - } - } - - furi_hal_sd_spi_handle = NULL; - furi_hal_spi_release(&furi_hal_spi_bus_handle_sd_slow); - - // Init sector cache - sector_cache_init(); - - return status; -} - -SdSpiStatus sd_get_card_state(void) { - SdSpiCmdAnswer response; - - // Send CMD13 (SEND_STATUS) to get SD status - response = sd_spi_send_cmd(SD_CMD13_SEND_STATUS, 0, 0xFF, SdSpiCmdAnswerTypeR2); - sd_spi_deselect_card_and_purge(); - - // Return status OK if response is valid - if((response.r1 == SdSpi_R1_NO_ERROR) && (response.r2 == SdSpi_R2_NO_ERROR)) { - return SdSpiStatusOK; - } - - return SdSpiStatusError; -} - -SdSpiStatus sd_get_card_info(SD_CardInfo* card_info) { - SdSpiStatus status; - - status = sd_spi_get_csd(&(card_info->Csd)); - - if(status != SdSpiStatusOK) { - return status; - } - - status = sd_spi_get_cid(&(card_info->Cid)); - - if(status != SdSpiStatusOK) { - return status; - } - - if(sd_high_capacity == 1) { - card_info->LogBlockSize = 512; - card_info->CardBlockSize = 512; - card_info->CardCapacity = ((uint64_t)card_info->Csd.version.v2.DeviceSize + 1UL) * 1024UL * - (uint64_t)card_info->LogBlockSize; - card_info->LogBlockNbr = (card_info->CardCapacity) / (card_info->LogBlockSize); - } else { - card_info->CardCapacity = (card_info->Csd.version.v1.DeviceSize + 1); - card_info->CardCapacity *= (1UL << (card_info->Csd.version.v1.DeviceSizeMul + 2)); - card_info->LogBlockSize = 512; - card_info->CardBlockSize = 1UL << (card_info->Csd.RdBlockLen); - card_info->CardCapacity *= card_info->CardBlockSize; - card_info->LogBlockNbr = (card_info->CardCapacity) / (card_info->LogBlockSize); - } - - return status; -} - -SdSpiStatus - sd_read_blocks(uint32_t* data, uint32_t address, uint32_t blocks, uint32_t timeout_ms) { - SdSpiStatus status = sd_spi_cmd_read_blocks(data, address, blocks, timeout_ms); - return status; -} - -SdSpiStatus - sd_write_blocks(uint32_t* data, uint32_t address, uint32_t blocks, uint32_t timeout_ms) { - SdSpiStatus status = sd_spi_cmd_write_blocks(data, address, blocks, timeout_ms); - return status; -} - -SdSpiStatus sd_get_cid(SD_CID* cid) { - SdSpiStatus status; - - furi_hal_spi_acquire(&furi_hal_spi_bus_handle_sd_fast); - furi_hal_sd_spi_handle = &furi_hal_spi_bus_handle_sd_fast; - - memset(cid, 0, sizeof(SD_CID)); - status = sd_spi_get_cid(cid); - - furi_hal_sd_spi_handle = NULL; - furi_hal_spi_release(&furi_hal_spi_bus_handle_sd_fast); - - return status; -} \ No newline at end of file diff --git a/firmware/targets/f7/fatfs/sd_spi_io.h b/firmware/targets/f7/fatfs/sd_spi_io.h deleted file mode 100644 index 954c78c4085..00000000000 --- a/firmware/targets/f7/fatfs/sd_spi_io.h +++ /dev/null @@ -1,158 +0,0 @@ -#pragma once -#include -#include - -#define __IO volatile - -#define SD_TIMEOUT_MS (1000) -#define SD_BLOCK_SIZE 512 - -typedef enum { - SdSpiStatusOK, - SdSpiStatusError, - SdSpiStatusTimeout, -} SdSpiStatus; - -/** - * @brief Card Specific Data: CSD Register - */ -typedef struct { - /* Header part */ - uint8_t CSDStruct : 2; /* CSD structure */ - uint8_t Reserved1 : 6; /* Reserved */ - uint8_t TAAC : 8; /* Data read access-time 1 */ - uint8_t NSAC : 8; /* Data read access-time 2 in CLK cycles */ - uint8_t MaxBusClkFrec : 8; /* Max. bus clock frequency */ - uint16_t CardComdClasses : 12; /* Card command classes */ - uint8_t RdBlockLen : 4; /* Max. read data block length */ - uint8_t PartBlockRead : 1; /* Partial blocks for read allowed */ - uint8_t WrBlockMisalign : 1; /* Write block misalignment */ - uint8_t RdBlockMisalign : 1; /* Read block misalignment */ - uint8_t DSRImpl : 1; /* DSR implemented */ - - /* v1 or v2 struct */ - union csd_version { - struct { - uint8_t Reserved1 : 2; /* Reserved */ - uint16_t DeviceSize : 12; /* Device Size */ - uint8_t MaxRdCurrentVDDMin : 3; /* Max. read current @ VDD min */ - uint8_t MaxRdCurrentVDDMax : 3; /* Max. read current @ VDD max */ - uint8_t MaxWrCurrentVDDMin : 3; /* Max. write current @ VDD min */ - uint8_t MaxWrCurrentVDDMax : 3; /* Max. write current @ VDD max */ - uint8_t DeviceSizeMul : 3; /* Device size multiplier */ - } v1; - struct { - uint8_t Reserved1 : 6; /* Reserved */ - uint32_t DeviceSize : 22; /* Device Size */ - uint8_t Reserved2 : 1; /* Reserved */ - } v2; - } version; - - uint8_t EraseSingleBlockEnable : 1; /* Erase single block enable */ - uint8_t EraseSectorSize : 7; /* Erase group size multiplier */ - uint8_t WrProtectGrSize : 7; /* Write protect group size */ - uint8_t WrProtectGrEnable : 1; /* Write protect group enable */ - uint8_t Reserved2 : 2; /* Reserved */ - uint8_t WrSpeedFact : 3; /* Write speed factor */ - uint8_t MaxWrBlockLen : 4; /* Max. write data block length */ - uint8_t WriteBlockPartial : 1; /* Partial blocks for write allowed */ - uint8_t Reserved3 : 5; /* Reserved */ - uint8_t FileFormatGrouop : 1; /* File format group */ - uint8_t CopyFlag : 1; /* Copy flag (OTP) */ - uint8_t PermWrProtect : 1; /* Permanent write protection */ - uint8_t TempWrProtect : 1; /* Temporary write protection */ - uint8_t FileFormat : 2; /* File Format */ - uint8_t Reserved4 : 2; /* Reserved */ - uint8_t crc : 7; /* Reserved */ - uint8_t Reserved5 : 1; /* always 1*/ - -} SD_CSD; - -/** - * @brief Card Identification Data: CID Register - */ -typedef struct { - uint8_t ManufacturerID; /* ManufacturerID */ - char OEM_AppliID[2]; /* OEM/Application ID */ - char ProdName[5]; /* Product Name */ - uint8_t ProdRev; /* Product Revision */ - uint32_t ProdSN; /* Product Serial Number */ - uint8_t Reserved1; /* Reserved1 */ - uint8_t ManufactYear; /* Manufacturing Year */ - uint8_t ManufactMonth; /* Manufacturing Month */ - uint8_t CID_CRC; /* CID CRC */ - uint8_t Reserved2; /* always 1 */ -} SD_CID; - -/** - * @brief SD Card information structure - */ -typedef struct { - SD_CSD Csd; - SD_CID Cid; - uint64_t CardCapacity; /*!< Card Capacity */ - uint32_t CardBlockSize; /*!< Card Block Size */ - uint32_t LogBlockNbr; /*!< Specifies the Card logical Capacity in blocks */ - uint32_t LogBlockSize; /*!< Specifies logical block size in bytes */ -} SD_CardInfo; - -/** - * @brief SD card max mount retry count - * - * @return uint8_t - */ -uint8_t sd_max_mount_retry_count(); - -/** - * @brief Init sd card - * - * @param power_reset reset card power - * @return SdSpiStatus - */ -SdSpiStatus sd_init(bool power_reset); - -/** - * @brief Get card state - * - * @return SdSpiStatus - */ -SdSpiStatus sd_get_card_state(void); - -/** - * @brief Get card info - * - * @param card_info - * @return SdSpiStatus - */ -SdSpiStatus sd_get_card_info(SD_CardInfo* card_info); - -/** - * @brief Read blocks - * - * @param data - * @param address - * @param blocks - * @param timeout_ms - * @return SdSpiStatus - */ -SdSpiStatus sd_read_blocks(uint32_t* data, uint32_t address, uint32_t blocks, uint32_t timeout_ms); - -/** - * @brief Write blocks - * - * @param data - * @param address - * @param blocks - * @param timeout_ms - * @return SdSpiStatus - */ -SdSpiStatus - sd_write_blocks(uint32_t* data, uint32_t address, uint32_t blocks, uint32_t timeout_ms); - -/** - * @brief Get card CSD register - * - * @param Cid - * @return SdSpiStatus - */ -SdSpiStatus sd_get_cid(SD_CID* cid); \ No newline at end of file diff --git a/firmware/targets/f7/fatfs/user_diskio.c b/firmware/targets/f7/fatfs/user_diskio.c index 6663d119cdb..85e5cad4f74 100644 --- a/firmware/targets/f7/fatfs/user_diskio.c +++ b/firmware/targets/f7/fatfs/user_diskio.c @@ -1,17 +1,8 @@ -#include "user_diskio.h" +#include #include +#include "user_diskio.h" #include "sector_cache.h" -static DSTATUS driver_check_status(BYTE lun) { - UNUSED(lun); - DSTATUS status = 0; - if(sd_get_card_state() != SdSpiStatusOK) { - status = STA_NOINIT; - } - - return status; -} - static DSTATUS driver_initialize(BYTE pdrv); static DSTATUS driver_status(BYTE pdrv); static DRESULT driver_read(BYTE pdrv, BYTE* buff, DWORD sector, UINT count); @@ -26,79 +17,6 @@ Diskio_drvTypeDef sd_fatfs_driver = { driver_ioctl, }; -static inline bool sd_cache_get(uint32_t address, uint32_t* data) { - uint8_t* cached_data = sector_cache_get(address); - if(cached_data) { - memcpy(data, cached_data, SD_BLOCK_SIZE); - return true; - } - return false; -} - -static inline void sd_cache_put(uint32_t address, uint32_t* data) { - sector_cache_put(address, (uint8_t*)data); -} - -static inline void sd_cache_invalidate_range(uint32_t start_sector, uint32_t end_sector) { - sector_cache_invalidate_range(start_sector, end_sector); -} - -static inline void sd_cache_invalidate_all() { - sector_cache_init(); -} - -static bool sd_device_read(uint32_t* buff, uint32_t sector, uint32_t count) { - bool result = false; - - furi_hal_spi_acquire(&furi_hal_spi_bus_handle_sd_fast); - furi_hal_sd_spi_handle = &furi_hal_spi_bus_handle_sd_fast; - - if(sd_read_blocks(buff, sector, count, SD_TIMEOUT_MS) == SdSpiStatusOK) { - FuriHalCortexTimer timer = furi_hal_cortex_timer_get(SD_TIMEOUT_MS * 1000); - - /* wait until the read operation is finished */ - result = true; - while(sd_get_card_state() != SdSpiStatusOK) { - if(furi_hal_cortex_timer_is_expired(timer)) { - result = false; - break; - } - } - } - - furi_hal_sd_spi_handle = NULL; - furi_hal_spi_release(&furi_hal_spi_bus_handle_sd_fast); - - return result; -} - -static bool sd_device_write(uint32_t* buff, uint32_t sector, uint32_t count) { - bool result = false; - - furi_hal_spi_acquire(&furi_hal_spi_bus_handle_sd_fast); - furi_hal_sd_spi_handle = &furi_hal_spi_bus_handle_sd_fast; - - if(sd_write_blocks(buff, sector, count, SD_TIMEOUT_MS) == SdSpiStatusOK) { - FuriHalCortexTimer timer = furi_hal_cortex_timer_get(SD_TIMEOUT_MS * 1000); - - /* wait until the Write operation is finished */ - result = true; - while(sd_get_card_state() != SdSpiStatusOK) { - if(furi_hal_cortex_timer_is_expired(timer)) { - sd_cache_invalidate_all(); - - result = false; - break; - } - } - } - - furi_hal_sd_spi_handle = NULL; - furi_hal_spi_release(&furi_hal_spi_bus_handle_sd_fast); - - return result; -} - /** * @brief Initializes a Drive * @param pdrv: Physical drive number (0..) @@ -115,13 +33,11 @@ static DSTATUS driver_initialize(BYTE pdrv) { * @retval DSTATUS: Operation status */ static DSTATUS driver_status(BYTE pdrv) { - furi_hal_spi_acquire(&furi_hal_spi_bus_handle_sd_fast); - furi_hal_sd_spi_handle = &furi_hal_spi_bus_handle_sd_fast; - - DSTATUS status = driver_check_status(pdrv); - - furi_hal_sd_spi_handle = NULL; - furi_hal_spi_release(&furi_hal_spi_bus_handle_sd_fast); + UNUSED(pdrv); + DSTATUS status = 0; + if(furi_hal_sd_get_card_state() != FuriStatusOk) { + status = STA_NOINIT; + } return status; } @@ -136,43 +52,8 @@ static DSTATUS driver_status(BYTE pdrv) { */ static DRESULT driver_read(BYTE pdrv, BYTE* buff, DWORD sector, UINT count) { UNUSED(pdrv); - - bool result; - bool single_sector = count == 1; - - if(single_sector) { - if(sd_cache_get(sector, (uint32_t*)buff)) { - return RES_OK; - } - } - - result = sd_device_read((uint32_t*)buff, (uint32_t)(sector), count); - - if(!result) { - uint8_t counter = sd_max_mount_retry_count(); - - while(result == false && counter > 0 && hal_sd_detect()) { - SdSpiStatus status; - - if((counter % 2) == 0) { - // power reset sd card - status = sd_init(true); - } else { - status = sd_init(false); - } - - if(status == SdSpiStatusOK) { - result = sd_device_read((uint32_t*)buff, (uint32_t)(sector), count); - } - counter--; - } - } - - if(single_sector && result == true) { - sd_cache_put(sector, (uint32_t*)buff); - } - - return result ? RES_OK : RES_ERROR; + FuriStatus status = furi_hal_sd_read_blocks((uint32_t*)buff, (uint32_t)(sector), count); + return status == FuriStatusOk ? RES_OK : RES_ERROR; } /** @@ -185,33 +66,8 @@ static DRESULT driver_read(BYTE pdrv, BYTE* buff, DWORD sector, UINT count) { */ static DRESULT driver_write(BYTE pdrv, const BYTE* buff, DWORD sector, UINT count) { UNUSED(pdrv); - bool result; - - sd_cache_invalidate_range(sector, sector + count); - - result = sd_device_write((uint32_t*)buff, (uint32_t)(sector), count); - - if(!result) { - uint8_t counter = sd_max_mount_retry_count(); - - while(result == false && counter > 0 && hal_sd_detect()) { - SdSpiStatus status; - - if((counter % 2) == 0) { - // power reset sd card - status = sd_init(true); - } else { - status = sd_init(false); - } - - if(status == SdSpiStatusOK) { - result = sd_device_write((uint32_t*)buff, (uint32_t)(sector), count); - } - counter--; - } - } - - return result ? RES_OK : RES_ERROR; + FuriStatus status = furi_hal_sd_write_blocks((uint32_t*)buff, (uint32_t)(sector), count); + return status == FuriStatusOk ? RES_OK : RES_ERROR; } /** @@ -223,12 +79,9 @@ static DRESULT driver_write(BYTE pdrv, const BYTE* buff, DWORD sector, UINT coun */ static DRESULT driver_ioctl(BYTE pdrv, BYTE cmd, void* buff) { DRESULT res = RES_ERROR; - SD_CardInfo CardInfo; - - furi_hal_spi_acquire(&furi_hal_spi_bus_handle_sd_fast); - furi_hal_sd_spi_handle = &furi_hal_spi_bus_handle_sd_fast; + FuriHalSdInfo sd_info; - DSTATUS status = driver_check_status(pdrv); + DSTATUS status = driver_status(pdrv); if(status & STA_NOINIT) return RES_NOTRDY; switch(cmd) { @@ -239,22 +92,22 @@ static DRESULT driver_ioctl(BYTE pdrv, BYTE cmd, void* buff) { /* Get number of sectors on the disk (DWORD) */ case GET_SECTOR_COUNT: - sd_get_card_info(&CardInfo); - *(DWORD*)buff = CardInfo.LogBlockNbr; + furi_hal_sd_info(&sd_info); + *(DWORD*)buff = sd_info.logical_block_count; res = RES_OK; break; /* Get R/W sector size (WORD) */ case GET_SECTOR_SIZE: - sd_get_card_info(&CardInfo); - *(WORD*)buff = CardInfo.LogBlockSize; + furi_hal_sd_info(&sd_info); + *(WORD*)buff = sd_info.logical_block_size; res = RES_OK; break; /* Get erase block size in unit of sector (DWORD) */ case GET_BLOCK_SIZE: - sd_get_card_info(&CardInfo); - *(DWORD*)buff = CardInfo.LogBlockSize; + furi_hal_sd_info(&sd_info); + *(DWORD*)buff = sd_info.logical_block_size; res = RES_OK; break; @@ -262,8 +115,5 @@ static DRESULT driver_ioctl(BYTE pdrv, BYTE cmd, void* buff) { res = RES_PARERR; } - furi_hal_sd_spi_handle = NULL; - furi_hal_spi_release(&furi_hal_spi_bus_handle_sd_fast); - return res; } diff --git a/firmware/targets/f7/fatfs/user_diskio.h b/firmware/targets/f7/fatfs/user_diskio.h index 7b3f2bb9ec0..db636fbb950 100644 --- a/firmware/targets/f7/fatfs/user_diskio.h +++ b/firmware/targets/f7/fatfs/user_diskio.h @@ -4,7 +4,6 @@ extern "C" { #endif -#include "sd_spi_io.h" #include "fatfs/ff_gen_drv.h" extern Diskio_drvTypeDef sd_fatfs_driver; diff --git a/firmware/targets/f7/furi_hal/furi_hal_sd.c b/firmware/targets/f7/furi_hal/furi_hal_sd.c index 1b0de562876..619f6f890df 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_sd.c +++ b/firmware/targets/f7/furi_hal/furi_hal_sd.c @@ -2,21 +2,1094 @@ #include #include #include +#include "../fatfs/sector_cache.h" +#define TAG "SdSpi" -void hal_sd_detect_init(void) { +#ifdef FURI_HAL_SD_SPI_DEBUG +#define sd_spi_debug(...) FURI_LOG_I(TAG, __VA_ARGS__) +#else +#define sd_spi_debug(...) +#endif + +#define SD_CMD_LENGTH (6) +#define SD_DUMMY_BYTE (0xFF) +#define SD_ANSWER_RETRY_COUNT (8) +#define SD_IDLE_RETRY_COUNT (100) +#define SD_TIMEOUT_MS (1000) +#define SD_BLOCK_SIZE (512) + +#define FLAG_SET(x, y) (((x) & (y)) == (y)) + +static bool sd_high_capacity = false; + +typedef enum { + SdSpiDataResponceOK = 0x05, + SdSpiDataResponceCRCError = 0x0B, + SdSpiDataResponceWriteError = 0x0D, + SdSpiDataResponceOtherError = 0xFF, +} SdSpiDataResponce; + +typedef struct { + uint8_t r1; + uint8_t r2; + uint8_t r3; + uint8_t r4; + uint8_t r5; +} SdSpiCmdAnswer; + +typedef enum { + SdSpiCmdAnswerTypeR1, + SdSpiCmdAnswerTypeR1B, + SdSpiCmdAnswerTypeR2, + SdSpiCmdAnswerTypeR3, + SdSpiCmdAnswerTypeR4R5, + SdSpiCmdAnswerTypeR7, +} SdSpiCmdAnswerType; + +/* + SdSpiCmd and SdSpiToken use non-standard enum value names convention, + because it is more convenient to look for documentation on a specific command. + For example, to find out what the SD_CMD23_SET_BLOCK_COUNT command does, you need to look for + SET_BLOCK_COUNT or CMD23 in the "Part 1 Physical Layer Simplified Specification". + + Do not use that naming convention in other places. +*/ + +typedef enum { + SD_CMD0_GO_IDLE_STATE = 0, + SD_CMD1_SEND_OP_COND = 1, + SD_CMD8_SEND_IF_COND = 8, + SD_CMD9_SEND_CSD = 9, + SD_CMD10_SEND_CID = 10, + SD_CMD12_STOP_TRANSMISSION = 12, + SD_CMD13_SEND_STATUS = 13, + SD_CMD16_SET_BLOCKLEN = 16, + SD_CMD17_READ_SINGLE_BLOCK = 17, + SD_CMD18_READ_MULT_BLOCK = 18, + SD_CMD23_SET_BLOCK_COUNT = 23, + SD_CMD24_WRITE_SINGLE_BLOCK = 24, + SD_CMD25_WRITE_MULT_BLOCK = 25, + SD_CMD27_PROG_CSD = 27, + SD_CMD28_SET_WRITE_PROT = 28, + SD_CMD29_CLR_WRITE_PROT = 29, + SD_CMD30_SEND_WRITE_PROT = 30, + SD_CMD32_SD_ERASE_GRP_START = 32, + SD_CMD33_SD_ERASE_GRP_END = 33, + SD_CMD34_UNTAG_SECTOR = 34, + SD_CMD35_ERASE_GRP_START = 35, + SD_CMD36_ERASE_GRP_END = 36, + SD_CMD37_UNTAG_ERASE_GROUP = 37, + SD_CMD38_ERASE = 38, + SD_CMD41_SD_APP_OP_COND = 41, + SD_CMD55_APP_CMD = 55, + SD_CMD58_READ_OCR = 58, +} SdSpiCmd; + +/** Data tokens */ +typedef enum { + SD_TOKEN_START_DATA_SINGLE_BLOCK_READ = 0xFE, + SD_TOKEN_START_DATA_MULTIPLE_BLOCK_READ = 0xFE, + SD_TOKEN_START_DATA_SINGLE_BLOCK_WRITE = 0xFE, + SD_TOKEN_START_DATA_MULTIPLE_BLOCK_WRITE = 0xFC, + SD_TOKEN_STOP_DATA_MULTIPLE_BLOCK_WRITE = 0xFD, +} SdSpiToken; + +/** R1 answer value */ +typedef enum { + SdSpi_R1_NO_ERROR = 0x00, + SdSpi_R1_IN_IDLE_STATE = 0x01, + SdSpi_R1_ERASE_RESET = 0x02, + SdSpi_R1_ILLEGAL_COMMAND = 0x04, + SdSpi_R1_COM_CRC_ERROR = 0x08, + SdSpi_R1_ERASE_SEQUENCE_ERROR = 0x10, + SdSpi_R1_ADDRESS_ERROR = 0x20, + SdSpi_R1_PARAMETER_ERROR = 0x40, +} SdSpiR1; + +/** R2 answer value */ +typedef enum { + /* R2 answer value */ + SdSpi_R2_NO_ERROR = 0x00, + SdSpi_R2_CARD_LOCKED = 0x01, + SdSpi_R2_LOCKUNLOCK_ERROR = 0x02, + SdSpi_R2_ERROR = 0x04, + SdSpi_R2_CC_ERROR = 0x08, + SdSpi_R2_CARD_ECC_FAILED = 0x10, + SdSpi_R2_WP_VIOLATION = 0x20, + SdSpi_R2_ERASE_PARAM = 0x40, + SdSpi_R2_OUTOFRANGE = 0x80, +} SdSpiR2; + +/** + * @brief Card Specific Data: CSD Register + */ +typedef struct { + /* Header part */ + uint8_t CSDStruct : 2; /* CSD structure */ + uint8_t Reserved1 : 6; /* Reserved */ + uint8_t TAAC : 8; /* Data read access-time 1 */ + uint8_t NSAC : 8; /* Data read access-time 2 in CLK cycles */ + uint8_t MaxBusClkFreq : 8; /* Max. bus clock frequency */ + uint16_t CardComdClasses : 12; /* Card command classes */ + uint8_t RdBlockLen : 4; /* Max. read data block length */ + uint8_t PartBlockRead : 1; /* Partial blocks for read allowed */ + uint8_t WrBlockMisalign : 1; /* Write block misalignment */ + uint8_t RdBlockMisalign : 1; /* Read block misalignment */ + uint8_t DSRImpl : 1; /* DSR implemented */ + + /* v1 or v2 struct */ + union csd_version { + struct { + uint8_t Reserved1 : 2; /* Reserved */ + uint16_t DeviceSize : 12; /* Device Size */ + uint8_t MaxRdCurrentVDDMin : 3; /* Max. read current @ VDD min */ + uint8_t MaxRdCurrentVDDMax : 3; /* Max. read current @ VDD max */ + uint8_t MaxWrCurrentVDDMin : 3; /* Max. write current @ VDD min */ + uint8_t MaxWrCurrentVDDMax : 3; /* Max. write current @ VDD max */ + uint8_t DeviceSizeMul : 3; /* Device size multiplier */ + } v1; + struct { + uint8_t Reserved1 : 6; /* Reserved */ + uint32_t DeviceSize : 22; /* Device Size */ + uint8_t Reserved2 : 1; /* Reserved */ + } v2; + } version; + + uint8_t EraseSingleBlockEnable : 1; /* Erase single block enable */ + uint8_t EraseSectorSize : 7; /* Erase group size multiplier */ + uint8_t WrProtectGrSize : 7; /* Write protect group size */ + uint8_t WrProtectGrEnable : 1; /* Write protect group enable */ + uint8_t Reserved2 : 2; /* Reserved */ + uint8_t WrSpeedFact : 3; /* Write speed factor */ + uint8_t MaxWrBlockLen : 4; /* Max. write data block length */ + uint8_t WriteBlockPartial : 1; /* Partial blocks for write allowed */ + uint8_t Reserved3 : 5; /* Reserved */ + uint8_t FileFormatGrouop : 1; /* File format group */ + uint8_t CopyFlag : 1; /* Copy flag (OTP) */ + uint8_t PermWrProtect : 1; /* Permanent write protection */ + uint8_t TempWrProtect : 1; /* Temporary write protection */ + uint8_t FileFormat : 2; /* File Format */ + uint8_t Reserved4 : 2; /* Reserved */ + uint8_t crc : 7; /* Reserved */ + uint8_t Reserved5 : 1; /* always 1*/ + +} SD_CSD; + +/** + * @brief Card Identification Data: CID Register + */ +typedef struct { + uint8_t ManufacturerID; /* ManufacturerID */ + char OEM_AppliID[2]; /* OEM/Application ID */ + char ProdName[5]; /* Product Name */ + uint8_t ProdRev; /* Product Revision */ + uint32_t ProdSN; /* Product Serial Number */ + uint8_t Reserved1; /* Reserved1 */ + uint8_t ManufactYear; /* Manufacturing Year */ + uint8_t ManufactMonth; /* Manufacturing Month */ + uint8_t CID_CRC; /* CID CRC */ + uint8_t Reserved2; /* always 1 */ +} SD_CID; + +/** + * @brief SD Card information structure + */ +typedef struct { + SD_CSD Csd; + SD_CID Cid; + uint64_t CardCapacity; /*!< Card Capacity */ + uint32_t CardBlockSize; /*!< Card Block Size */ + uint32_t LogBlockNbr; /*!< Specifies the Card logical Capacity in blocks */ + uint32_t LogBlockSize; /*!< Specifies logical block size in bytes */ +} SD_CardInfo; + +/** Pointer to currently used SPI Handle */ +FuriHalSpiBusHandle* furi_hal_sd_spi_handle = NULL; + +static inline void sd_spi_select_card() { + furi_hal_gpio_write(furi_hal_sd_spi_handle->cs, false); + furi_delay_us(10); // Entry guard time for some SD cards +} + +static inline void sd_spi_deselect_card() { + furi_delay_us(10); // Exit guard time for some SD cards + furi_hal_gpio_write(furi_hal_sd_spi_handle->cs, true); +} + +static void sd_spi_bus_to_ground() { + furi_hal_gpio_init_ex( + furi_hal_sd_spi_handle->miso, + GpioModeOutputPushPull, + GpioPullNo, + GpioSpeedVeryHigh, + GpioAltFnUnused); + furi_hal_gpio_init_ex( + furi_hal_sd_spi_handle->mosi, + GpioModeOutputPushPull, + GpioPullNo, + GpioSpeedVeryHigh, + GpioAltFnUnused); + furi_hal_gpio_init_ex( + furi_hal_sd_spi_handle->sck, + GpioModeOutputPushPull, + GpioPullNo, + GpioSpeedVeryHigh, + GpioAltFnUnused); + + sd_spi_select_card(); + furi_hal_gpio_write(furi_hal_sd_spi_handle->miso, false); + furi_hal_gpio_write(furi_hal_sd_spi_handle->mosi, false); + furi_hal_gpio_write(furi_hal_sd_spi_handle->sck, false); +} + +static void sd_spi_bus_rise_up() { + sd_spi_deselect_card(); + + furi_hal_gpio_init_ex( + furi_hal_sd_spi_handle->miso, + GpioModeAltFunctionPushPull, + GpioPullUp, + GpioSpeedVeryHigh, + GpioAltFn5SPI2); + furi_hal_gpio_init_ex( + furi_hal_sd_spi_handle->mosi, + GpioModeAltFunctionPushPull, + GpioPullUp, + GpioSpeedVeryHigh, + GpioAltFn5SPI2); + furi_hal_gpio_init_ex( + furi_hal_sd_spi_handle->sck, + GpioModeAltFunctionPushPull, + GpioPullUp, + GpioSpeedVeryHigh, + GpioAltFn5SPI2); +} + +static inline uint8_t sd_spi_read_byte(void) { + uint8_t responce; + furi_check(furi_hal_spi_bus_trx(furi_hal_sd_spi_handle, NULL, &responce, 1, SD_TIMEOUT_MS)); + return responce; +} + +static inline void sd_spi_write_byte(uint8_t data) { + furi_check(furi_hal_spi_bus_trx(furi_hal_sd_spi_handle, &data, NULL, 1, SD_TIMEOUT_MS)); +} + +static inline uint8_t sd_spi_write_and_read_byte(uint8_t data) { + uint8_t responce; + furi_check(furi_hal_spi_bus_trx(furi_hal_sd_spi_handle, &data, &responce, 1, SD_TIMEOUT_MS)); + return responce; +} + +static inline void sd_spi_write_bytes(uint8_t* data, uint32_t size) { + furi_check(furi_hal_spi_bus_trx(furi_hal_sd_spi_handle, data, NULL, size, SD_TIMEOUT_MS)); +} + +static inline void sd_spi_read_bytes(uint8_t* data, uint32_t size) { + furi_check(furi_hal_spi_bus_trx(furi_hal_sd_spi_handle, NULL, data, size, SD_TIMEOUT_MS)); +} + +static inline void sd_spi_write_bytes_dma(uint8_t* data, uint32_t size) { + uint32_t timeout_mul = (size / 512) + 1; + furi_check(furi_hal_spi_bus_trx_dma( + furi_hal_sd_spi_handle, data, NULL, size, SD_TIMEOUT_MS * timeout_mul)); +} + +static inline void sd_spi_read_bytes_dma(uint8_t* data, uint32_t size) { + uint32_t timeout_mul = (size / 512) + 1; + furi_check(furi_hal_spi_bus_trx_dma( + furi_hal_sd_spi_handle, NULL, data, size, SD_TIMEOUT_MS * timeout_mul)); +} + +static uint8_t sd_spi_wait_for_data_and_read(void) { + uint8_t retry_count = SD_ANSWER_RETRY_COUNT; + uint8_t responce; + + // Wait until we get a valid data + do { + responce = sd_spi_read_byte(); + retry_count--; + + } while((responce == SD_DUMMY_BYTE) && retry_count); + + return responce; +} + +static FuriStatus sd_spi_wait_for_data(uint8_t data, uint32_t timeout_ms) { + FuriHalCortexTimer timer = furi_hal_cortex_timer_get(timeout_ms * 1000); + uint8_t byte; + + do { + byte = sd_spi_read_byte(); + if(furi_hal_cortex_timer_is_expired(timer)) { + return FuriStatusErrorTimeout; + } + } while((byte != data)); + + return FuriStatusOk; +} + +static inline void sd_spi_deselect_card_and_purge() { + sd_spi_deselect_card(); + sd_spi_read_byte(); +} + +static inline void sd_spi_purge_crc() { + sd_spi_read_byte(); + sd_spi_read_byte(); +} + +static SdSpiCmdAnswer + sd_spi_send_cmd(SdSpiCmd cmd, uint32_t arg, uint8_t crc, SdSpiCmdAnswerType answer_type) { + uint8_t frame[SD_CMD_LENGTH]; + SdSpiCmdAnswer cmd_answer = { + .r1 = SD_DUMMY_BYTE, + .r2 = SD_DUMMY_BYTE, + .r3 = SD_DUMMY_BYTE, + .r4 = SD_DUMMY_BYTE, + .r5 = SD_DUMMY_BYTE, + }; + + // R1 Length = NCS(0)+ 6 Bytes command + NCR(min1 max8) + 1 Bytes answer + NEC(0) = 15bytes + // R1b identical to R1 + Busy information + // R2 Length = NCS(0)+ 6 Bytes command + NCR(min1 max8) + 2 Bytes answer + NEC(0) = 16bytes + + frame[0] = ((uint8_t)cmd | 0x40); + frame[1] = (uint8_t)(arg >> 24); + frame[2] = (uint8_t)(arg >> 16); + frame[3] = (uint8_t)(arg >> 8); + frame[4] = (uint8_t)(arg); + frame[5] = (crc | 0x01); + + sd_spi_select_card(); + sd_spi_write_bytes(frame, sizeof(frame)); + + switch(answer_type) { + case SdSpiCmdAnswerTypeR1: + cmd_answer.r1 = sd_spi_wait_for_data_and_read(); + break; + case SdSpiCmdAnswerTypeR1B: + // TODO FL-3507: can be wrong, at least for SD_CMD12_STOP_TRANSMISSION you need to purge one byte before reading R1 + cmd_answer.r1 = sd_spi_wait_for_data_and_read(); + + // In general this shenenigans seems suspicious, please double check SD specs if you are using SdSpiCmdAnswerTypeR1B + // reassert card + sd_spi_deselect_card(); + furi_delay_us(1000); + sd_spi_deselect_card(); + + // and wait for it to be ready + while(sd_spi_read_byte() != 0xFF) { + }; + + break; + case SdSpiCmdAnswerTypeR2: + cmd_answer.r1 = sd_spi_wait_for_data_and_read(); + cmd_answer.r2 = sd_spi_read_byte(); + break; + case SdSpiCmdAnswerTypeR3: + case SdSpiCmdAnswerTypeR7: + cmd_answer.r1 = sd_spi_wait_for_data_and_read(); + cmd_answer.r2 = sd_spi_read_byte(); + cmd_answer.r3 = sd_spi_read_byte(); + cmd_answer.r4 = sd_spi_read_byte(); + cmd_answer.r5 = sd_spi_read_byte(); + break; + default: + break; + } + return cmd_answer; +} + +static SdSpiDataResponce sd_spi_get_data_response(uint32_t timeout_ms) { + SdSpiDataResponce responce = sd_spi_read_byte(); + // read busy response byte + sd_spi_read_byte(); + + switch(responce & 0x1F) { + case SdSpiDataResponceOK: + // TODO FL-3508: check timings + sd_spi_deselect_card(); + sd_spi_select_card(); + + // wait for 0xFF + if(sd_spi_wait_for_data(0xFF, timeout_ms) == FuriStatusOk) { + return SdSpiDataResponceOK; + } else { + return SdSpiDataResponceOtherError; + } + case SdSpiDataResponceCRCError: + return SdSpiDataResponceCRCError; + case SdSpiDataResponceWriteError: + return SdSpiDataResponceWriteError; + default: + return SdSpiDataResponceOtherError; + } +} + +static FuriStatus sd_spi_init_spi_mode_v1(void) { + SdSpiCmdAnswer response; + uint8_t retry_count = 0; + + sd_spi_debug("Init SD card in SPI mode v1"); + + do { + retry_count++; + + // CMD55 (APP_CMD) before any ACMD command: R1 response (0x00: no errors) + sd_spi_send_cmd(SD_CMD55_APP_CMD, 0, 0xFF, SdSpiCmdAnswerTypeR1); + sd_spi_deselect_card_and_purge(); + + // ACMD41 (SD_APP_OP_COND) to initialize SDHC or SDXC cards: R1 response (0x00: no errors) + response = sd_spi_send_cmd(SD_CMD41_SD_APP_OP_COND, 0, 0xFF, SdSpiCmdAnswerTypeR1); + sd_spi_deselect_card_and_purge(); + + if(retry_count >= SD_IDLE_RETRY_COUNT) { + return FuriStatusError; + } + } while(response.r1 == SdSpi_R1_IN_IDLE_STATE); + + sd_spi_debug("Init SD card in SPI mode v1 done"); + + return FuriStatusOk; +} + +static FuriStatus sd_spi_init_spi_mode_v2(void) { + SdSpiCmdAnswer response; + uint8_t retry_count = 0; + + sd_spi_debug("Init SD card in SPI mode v2"); + + do { + retry_count++; + // CMD55 (APP_CMD) before any ACMD command: R1 response (0x00: no errors) + sd_spi_send_cmd(SD_CMD55_APP_CMD, 0, 0xFF, SdSpiCmdAnswerTypeR1); + sd_spi_deselect_card_and_purge(); + + // ACMD41 (APP_OP_COND) to initialize SDHC or SDXC cards: R1 response (0x00: no errors) + response = + sd_spi_send_cmd(SD_CMD41_SD_APP_OP_COND, 0x40000000, 0xFF, SdSpiCmdAnswerTypeR1); + sd_spi_deselect_card_and_purge(); + + if(retry_count >= SD_IDLE_RETRY_COUNT) { + sd_spi_debug("ACMD41 failed"); + return FuriStatusError; + } + } while(response.r1 == SdSpi_R1_IN_IDLE_STATE); + + if(FLAG_SET(response.r1, SdSpi_R1_ILLEGAL_COMMAND)) { + sd_spi_debug("ACMD41 is illegal command"); + retry_count = 0; + do { + retry_count++; + // CMD55 (APP_CMD) before any ACMD command: R1 response (0x00: no errors) + response = sd_spi_send_cmd(SD_CMD55_APP_CMD, 0, 0xFF, SdSpiCmdAnswerTypeR1); + sd_spi_deselect_card_and_purge(); + + if(response.r1 != SdSpi_R1_IN_IDLE_STATE) { + sd_spi_debug("CMD55 failed"); + return FuriStatusError; + } + // ACMD41 (SD_APP_OP_COND) to initialize SDHC or SDXC cards: R1 response (0x00: no errors) + response = sd_spi_send_cmd(SD_CMD41_SD_APP_OP_COND, 0, 0xFF, SdSpiCmdAnswerTypeR1); + sd_spi_deselect_card_and_purge(); + + if(retry_count >= SD_IDLE_RETRY_COUNT) { + sd_spi_debug("ACMD41 failed"); + return FuriStatusError; + } + } while(response.r1 == SdSpi_R1_IN_IDLE_STATE); + } + + sd_spi_debug("Init SD card in SPI mode v2 done"); + + return FuriStatusOk; +} + +static FuriStatus sd_spi_init_spi_mode(void) { + SdSpiCmdAnswer response; + uint8_t retry_count; + + // CMD0 (GO_IDLE_STATE) to put SD in SPI mode and + // wait for In Idle State Response (R1 Format) equal to 0x01 + retry_count = 0; + do { + retry_count++; + response = sd_spi_send_cmd(SD_CMD0_GO_IDLE_STATE, 0, 0x95, SdSpiCmdAnswerTypeR1); + sd_spi_deselect_card_and_purge(); + + if(retry_count >= SD_IDLE_RETRY_COUNT) { + sd_spi_debug("CMD0 failed"); + return FuriStatusError; + } + } while(response.r1 != SdSpi_R1_IN_IDLE_STATE); + + // CMD8 (SEND_IF_COND) to check the power supply status + // and wait until response (R7 Format) equal to 0xAA and + response = sd_spi_send_cmd(SD_CMD8_SEND_IF_COND, 0x1AA, 0x87, SdSpiCmdAnswerTypeR7); + sd_spi_deselect_card_and_purge(); + + if(FLAG_SET(response.r1, SdSpi_R1_ILLEGAL_COMMAND)) { + if(sd_spi_init_spi_mode_v1() != FuriStatusOk) { + sd_spi_debug("Init mode v1 failed"); + return FuriStatusError; + } + sd_high_capacity = 0; + } else if(response.r1 == SdSpi_R1_IN_IDLE_STATE) { + if(sd_spi_init_spi_mode_v2() != FuriStatusOk) { + sd_spi_debug("Init mode v2 failed"); + return FuriStatusError; + } + + // CMD58 (READ_OCR) to initialize SDHC or SDXC cards: R3 response + response = sd_spi_send_cmd(SD_CMD58_READ_OCR, 0, 0xFF, SdSpiCmdAnswerTypeR3); + sd_spi_deselect_card_and_purge(); + + if(response.r1 != SdSpi_R1_NO_ERROR) { + sd_spi_debug("CMD58 failed"); + return FuriStatusError; + } + sd_high_capacity = (response.r2 & 0x40) >> 6; + } else { + return FuriStatusError; + } + + sd_spi_debug("SD card is %s", sd_high_capacity ? "SDHC or SDXC" : "SDSC"); + return FuriStatusOk; +} + +static FuriStatus sd_spi_get_csd(SD_CSD* csd) { + uint16_t counter = 0; + uint8_t csd_data[16]; + FuriStatus ret = FuriStatusError; + SdSpiCmdAnswer response; + + // CMD9 (SEND_CSD): R1 format (0x00 is no errors) + response = sd_spi_send_cmd(SD_CMD9_SEND_CSD, 0, 0xFF, SdSpiCmdAnswerTypeR1); + + if(response.r1 == SdSpi_R1_NO_ERROR) { + if(sd_spi_wait_for_data(SD_TOKEN_START_DATA_SINGLE_BLOCK_READ, SD_TIMEOUT_MS) == + FuriStatusOk) { + // read CSD data + for(counter = 0; counter < 16; counter++) { + csd_data[counter] = sd_spi_read_byte(); + } + + sd_spi_purge_crc(); + + /************************************************************************* + CSD header decoding + *************************************************************************/ + + csd->CSDStruct = (csd_data[0] & 0xC0) >> 6; + csd->Reserved1 = csd_data[0] & 0x3F; + csd->TAAC = csd_data[1]; + csd->NSAC = csd_data[2]; + csd->MaxBusClkFreq = csd_data[3]; + csd->CardComdClasses = (csd_data[4] << 4) | ((csd_data[5] & 0xF0) >> 4); + csd->RdBlockLen = csd_data[5] & 0x0F; + csd->PartBlockRead = (csd_data[6] & 0x80) >> 7; + csd->WrBlockMisalign = (csd_data[6] & 0x40) >> 6; + csd->RdBlockMisalign = (csd_data[6] & 0x20) >> 5; + csd->DSRImpl = (csd_data[6] & 0x10) >> 4; + + /************************************************************************* + CSD v1/v2 decoding + *************************************************************************/ + + if(sd_high_capacity == 0) { + csd->version.v1.Reserved1 = ((csd_data[6] & 0x0C) >> 2); + csd->version.v1.DeviceSize = ((csd_data[6] & 0x03) << 10) | (csd_data[7] << 2) | + ((csd_data[8] & 0xC0) >> 6); + csd->version.v1.MaxRdCurrentVDDMin = (csd_data[8] & 0x38) >> 3; + csd->version.v1.MaxRdCurrentVDDMax = (csd_data[8] & 0x07); + csd->version.v1.MaxWrCurrentVDDMin = (csd_data[9] & 0xE0) >> 5; + csd->version.v1.MaxWrCurrentVDDMax = (csd_data[9] & 0x1C) >> 2; + csd->version.v1.DeviceSizeMul = ((csd_data[9] & 0x03) << 1) | + ((csd_data[10] & 0x80) >> 7); + } else { + csd->version.v2.Reserved1 = ((csd_data[6] & 0x0F) << 2) | + ((csd_data[7] & 0xC0) >> 6); + csd->version.v2.DeviceSize = ((csd_data[7] & 0x3F) << 16) | (csd_data[8] << 8) | + csd_data[9]; + csd->version.v2.Reserved2 = ((csd_data[10] & 0x80) >> 8); + } + + csd->EraseSingleBlockEnable = (csd_data[10] & 0x40) >> 6; + csd->EraseSectorSize = ((csd_data[10] & 0x3F) << 1) | ((csd_data[11] & 0x80) >> 7); + csd->WrProtectGrSize = (csd_data[11] & 0x7F); + csd->WrProtectGrEnable = (csd_data[12] & 0x80) >> 7; + csd->Reserved2 = (csd_data[12] & 0x60) >> 5; + csd->WrSpeedFact = (csd_data[12] & 0x1C) >> 2; + csd->MaxWrBlockLen = ((csd_data[12] & 0x03) << 2) | ((csd_data[13] & 0xC0) >> 6); + csd->WriteBlockPartial = (csd_data[13] & 0x20) >> 5; + csd->Reserved3 = (csd_data[13] & 0x1F); + csd->FileFormatGrouop = (csd_data[14] & 0x80) >> 7; + csd->CopyFlag = (csd_data[14] & 0x40) >> 6; + csd->PermWrProtect = (csd_data[14] & 0x20) >> 5; + csd->TempWrProtect = (csd_data[14] & 0x10) >> 4; + csd->FileFormat = (csd_data[14] & 0x0C) >> 2; + csd->Reserved4 = (csd_data[14] & 0x03); + csd->crc = (csd_data[15] & 0xFE) >> 1; + csd->Reserved5 = (csd_data[15] & 0x01); + + ret = FuriStatusOk; + } + } + + sd_spi_deselect_card_and_purge(); + + return ret; +} + +static FuriStatus sd_spi_get_cid(SD_CID* Cid) { + uint16_t counter = 0; + uint8_t cid_data[16]; + FuriStatus ret = FuriStatusError; + SdSpiCmdAnswer response; + + // CMD10 (SEND_CID): R1 format (0x00 is no errors) + response = sd_spi_send_cmd(SD_CMD10_SEND_CID, 0, 0xFF, SdSpiCmdAnswerTypeR1); + + if(response.r1 == SdSpi_R1_NO_ERROR) { + if(sd_spi_wait_for_data(SD_TOKEN_START_DATA_SINGLE_BLOCK_READ, SD_TIMEOUT_MS) == + FuriStatusOk) { + // read CID data + for(counter = 0; counter < 16; counter++) { + cid_data[counter] = sd_spi_read_byte(); + } + + sd_spi_purge_crc(); + + Cid->ManufacturerID = cid_data[0]; + memcpy(Cid->OEM_AppliID, cid_data + 1, 2); + memcpy(Cid->ProdName, cid_data + 3, 5); + Cid->ProdRev = cid_data[8]; + Cid->ProdSN = cid_data[9] << 24; + Cid->ProdSN |= cid_data[10] << 16; + Cid->ProdSN |= cid_data[11] << 8; + Cid->ProdSN |= cid_data[12]; + Cid->Reserved1 = (cid_data[13] & 0xF0) >> 4; + Cid->ManufactYear = (cid_data[13] & 0x0F) << 4; + Cid->ManufactYear |= (cid_data[14] & 0xF0) >> 4; + Cid->ManufactMonth = (cid_data[14] & 0x0F); + Cid->CID_CRC = (cid_data[15] & 0xFE) >> 1; + Cid->Reserved2 = 1; + + ret = FuriStatusOk; + } + } + + sd_spi_deselect_card_and_purge(); + + return ret; +} + +static FuriStatus + sd_spi_cmd_read_blocks(uint32_t* data, uint32_t address, uint32_t blocks, uint32_t timeout_ms) { + uint32_t block_address = address; + uint32_t offset = 0; + + // CMD16 (SET_BLOCKLEN): R1 response (0x00: no errors) + SdSpiCmdAnswer response = + sd_spi_send_cmd(SD_CMD16_SET_BLOCKLEN, SD_BLOCK_SIZE, 0xFF, SdSpiCmdAnswerTypeR1); + sd_spi_deselect_card_and_purge(); + + if(response.r1 != SdSpi_R1_NO_ERROR) { + return FuriStatusError; + } + + if(!sd_high_capacity) { + block_address = address * SD_BLOCK_SIZE; + } + + while(blocks--) { + // CMD17 (READ_SINGLE_BLOCK): R1 response (0x00: no errors) + response = + sd_spi_send_cmd(SD_CMD17_READ_SINGLE_BLOCK, block_address, 0xFF, SdSpiCmdAnswerTypeR1); + if(response.r1 != SdSpi_R1_NO_ERROR) { + sd_spi_deselect_card_and_purge(); + return FuriStatusError; + } + + // Wait for the data start token + if(sd_spi_wait_for_data(SD_TOKEN_START_DATA_SINGLE_BLOCK_READ, timeout_ms) == + FuriStatusOk) { + // Read the data block + sd_spi_read_bytes_dma((uint8_t*)data + offset, SD_BLOCK_SIZE); + sd_spi_purge_crc(); + + // increase offset + offset += SD_BLOCK_SIZE; + + // increase block address + if(sd_high_capacity) { + block_address += 1; + } else { + block_address += SD_BLOCK_SIZE; + } + } else { + sd_spi_deselect_card_and_purge(); + return FuriStatusError; + } + + sd_spi_deselect_card_and_purge(); + } + + return FuriStatusOk; +} + +static FuriStatus sd_spi_cmd_write_blocks( + const uint32_t* data, + uint32_t address, + uint32_t blocks, + uint32_t timeout_ms) { + uint32_t block_address = address; + uint32_t offset = 0; + + // CMD16 (SET_BLOCKLEN): R1 response (0x00: no errors) + SdSpiCmdAnswer response = + sd_spi_send_cmd(SD_CMD16_SET_BLOCKLEN, SD_BLOCK_SIZE, 0xFF, SdSpiCmdAnswerTypeR1); + sd_spi_deselect_card_and_purge(); + + if(response.r1 != SdSpi_R1_NO_ERROR) { + return FuriStatusError; + } + + if(!sd_high_capacity) { + block_address = address * SD_BLOCK_SIZE; + } + + while(blocks--) { + // CMD24 (WRITE_SINGLE_BLOCK): R1 response (0x00: no errors) + response = sd_spi_send_cmd( + SD_CMD24_WRITE_SINGLE_BLOCK, block_address, 0xFF, SdSpiCmdAnswerTypeR1); + if(response.r1 != SdSpi_R1_NO_ERROR) { + sd_spi_deselect_card_and_purge(); + return FuriStatusError; + } + + // Send dummy byte for NWR timing : one byte between CMD_WRITE and TOKEN + // TODO FL-3509: check bytes count + sd_spi_write_byte(SD_DUMMY_BYTE); + sd_spi_write_byte(SD_DUMMY_BYTE); + + // Send the data start token + sd_spi_write_byte(SD_TOKEN_START_DATA_SINGLE_BLOCK_WRITE); + sd_spi_write_bytes_dma((uint8_t*)data + offset, SD_BLOCK_SIZE); + sd_spi_purge_crc(); + + // Read data response + SdSpiDataResponce data_responce = sd_spi_get_data_response(timeout_ms); + sd_spi_deselect_card_and_purge(); + + if(data_responce != SdSpiDataResponceOK) { + return FuriStatusError; + } + + // increase offset + offset += SD_BLOCK_SIZE; + + // increase block address + if(sd_high_capacity) { + block_address += 1; + } else { + block_address += SD_BLOCK_SIZE; + } + } + + return FuriStatusOk; +} + +static FuriStatus sd_spi_get_card_state(void) { + SdSpiCmdAnswer response; + + // Send CMD13 (SEND_STATUS) to get SD status + response = sd_spi_send_cmd(SD_CMD13_SEND_STATUS, 0, 0xFF, SdSpiCmdAnswerTypeR2); + sd_spi_deselect_card_and_purge(); + + // Return status OK if response is valid + if((response.r1 == SdSpi_R1_NO_ERROR) && (response.r2 == SdSpi_R2_NO_ERROR)) { + return FuriStatusOk; + } + + return FuriStatusError; +} + +static inline bool sd_cache_get(uint32_t address, uint32_t* data) { + uint8_t* cached_data = sector_cache_get(address); + if(cached_data) { + memcpy(data, cached_data, SD_BLOCK_SIZE); + return true; + } + return false; +} + +static inline void sd_cache_put(uint32_t address, uint32_t* data) { + sector_cache_put(address, (uint8_t*)data); +} + +static inline void sd_cache_invalidate_range(uint32_t start_sector, uint32_t end_sector) { + sector_cache_invalidate_range(start_sector, end_sector); +} + +static inline void sd_cache_invalidate_all() { + sector_cache_init(); +} + +static FuriStatus sd_device_read(uint32_t* buff, uint32_t sector, uint32_t count) { + FuriStatus status = FuriStatusError; + + furi_hal_spi_acquire(&furi_hal_spi_bus_handle_sd_fast); + furi_hal_sd_spi_handle = &furi_hal_spi_bus_handle_sd_fast; + + if(sd_spi_cmd_read_blocks(buff, sector, count, SD_TIMEOUT_MS) == FuriStatusOk) { + FuriHalCortexTimer timer = furi_hal_cortex_timer_get(SD_TIMEOUT_MS * 1000); + + /* wait until the read operation is finished */ + do { + status = sd_spi_get_card_state(); + + if(furi_hal_cortex_timer_is_expired(timer)) { + status = FuriStatusErrorTimeout; + break; + } + } while(status != FuriStatusOk); + } + + furi_hal_sd_spi_handle = NULL; + furi_hal_spi_release(&furi_hal_spi_bus_handle_sd_fast); + + return status; +} + +static FuriStatus sd_device_write(const uint32_t* buff, uint32_t sector, uint32_t count) { + FuriStatus status = FuriStatusError; + + furi_hal_spi_acquire(&furi_hal_spi_bus_handle_sd_fast); + furi_hal_sd_spi_handle = &furi_hal_spi_bus_handle_sd_fast; + + if(sd_spi_cmd_write_blocks(buff, sector, count, SD_TIMEOUT_MS) == FuriStatusOk) { + FuriHalCortexTimer timer = furi_hal_cortex_timer_get(SD_TIMEOUT_MS * 1000); + + /* wait until the Write operation is finished */ + do { + status = sd_spi_get_card_state(); + + if(furi_hal_cortex_timer_is_expired(timer)) { + sd_cache_invalidate_all(); + + status = FuriStatusErrorTimeout; + break; + } + } while(status != FuriStatusOk); + } + + furi_hal_sd_spi_handle = NULL; + furi_hal_spi_release(&furi_hal_spi_bus_handle_sd_fast); + + return status; +} + +void furi_hal_sd_presence_init(void) { // low speed input with pullup furi_hal_gpio_init(&gpio_sdcard_cd, GpioModeInput, GpioPullUp, GpioSpeedLow); } -void hal_sd_detect_set_low(void) { +static void furi_hal_sd_present_pin_set_low(void) { // low speed input with pullup furi_hal_gpio_init_simple(&gpio_sdcard_cd, GpioModeOutputOpenDrain); furi_hal_gpio_write(&gpio_sdcard_cd, 0); } -bool hal_sd_detect(void) { +bool furi_hal_sd_is_present(void) { bool result = !furi_hal_gpio_read(&gpio_sdcard_cd); return result; } -FuriHalSpiBusHandle* furi_hal_sd_spi_handle = NULL; +uint8_t furi_hal_sd_max_mount_retry_count() { + return 10; +} + +FuriStatus furi_hal_sd_init(bool power_reset) { + // Slow speed init + furi_hal_spi_acquire(&furi_hal_spi_bus_handle_sd_slow); + furi_hal_sd_spi_handle = &furi_hal_spi_bus_handle_sd_slow; + + // We reset card in spi_lock context, so it is safe to disturb spi bus + if(power_reset) { + sd_spi_debug("Power reset"); + + // disable power and set low on all bus pins + furi_hal_power_disable_external_3_3v(); + sd_spi_bus_to_ground(); + furi_hal_sd_present_pin_set_low(); + furi_delay_ms(250); + + // reinit bus and enable power + sd_spi_bus_rise_up(); + furi_hal_sd_presence_init(); + furi_hal_power_enable_external_3_3v(); + furi_delay_ms(100); + } + + FuriStatus status = FuriStatusError; + + // Send 80 dummy clocks with CS high + sd_spi_deselect_card(); + for(uint8_t i = 0; i < 80; i++) { + sd_spi_write_byte(SD_DUMMY_BYTE); + } + + for(uint8_t i = 0; i < 128; i++) { + status = sd_spi_init_spi_mode(); + if(status == FuriStatusOk) { + // SD initialized and init to SPI mode properly + sd_spi_debug("SD init OK after %d retries", i); + break; + } + } + + furi_hal_sd_spi_handle = NULL; + furi_hal_spi_release(&furi_hal_spi_bus_handle_sd_slow); + + // Init sector cache + sector_cache_init(); + + return status; +} + +FuriStatus furi_hal_sd_get_card_state(void) { + furi_hal_spi_acquire(&furi_hal_spi_bus_handle_sd_fast); + furi_hal_sd_spi_handle = &furi_hal_spi_bus_handle_sd_fast; + + FuriStatus status = sd_spi_get_card_state(); + + furi_hal_sd_spi_handle = NULL; + furi_hal_spi_release(&furi_hal_spi_bus_handle_sd_fast); + + return status; +} + +FuriStatus furi_hal_sd_read_blocks(uint32_t* buff, uint32_t sector, uint32_t count) { + FuriStatus status; + bool single_sector = count == 1; + + if(single_sector) { + if(sd_cache_get(sector, buff)) { + return FuriStatusOk; + } + } + + status = sd_device_read(buff, sector, count); + + if(status != FuriStatusOk) { + uint8_t counter = furi_hal_sd_max_mount_retry_count(); + + while(status != FuriStatusOk && counter > 0 && furi_hal_sd_is_present()) { + if((counter % 2) == 0) { + // power reset sd card + status = furi_hal_sd_init(true); + } else { + status = furi_hal_sd_init(false); + } + + if(status == FuriStatusOk) { + status = sd_device_read(buff, sector, count); + } + counter--; + } + } + + if(single_sector && status == FuriStatusOk) { + sd_cache_put(sector, buff); + } + + return status; +} + +FuriStatus furi_hal_sd_write_blocks(const uint32_t* buff, uint32_t sector, uint32_t count) { + FuriStatus status; + + sd_cache_invalidate_range(sector, sector + count); + + status = sd_device_write(buff, sector, count); + + if(status != FuriStatusOk) { + uint8_t counter = furi_hal_sd_max_mount_retry_count(); + + while(status != FuriStatusOk && counter > 0 && furi_hal_sd_is_present()) { + if((counter % 2) == 0) { + // power reset sd card + status = furi_hal_sd_init(true); + } else { + status = furi_hal_sd_init(false); + } + + if(status == FuriStatusOk) { + status = sd_device_write(buff, sector, count); + } + counter--; + } + } + + return status; +} + +FuriStatus furi_hal_sd_info(FuriHalSdInfo* info) { + FuriStatus status; + SD_CSD csd; + SD_CID cid; + + furi_hal_spi_acquire(&furi_hal_spi_bus_handle_sd_fast); + furi_hal_sd_spi_handle = &furi_hal_spi_bus_handle_sd_fast; + + do { + status = sd_spi_get_csd(&csd); + + if(status != FuriStatusOk) { + break; + } + + status = sd_spi_get_cid(&cid); + + if(status != FuriStatusOk) { + break; + } + + if(sd_high_capacity == 1) { + info->logical_block_size = 512; + info->block_size = 512; + info->capacity = ((uint64_t)csd.version.v2.DeviceSize + 1UL) * 1024UL * + (uint64_t)info->logical_block_size; + info->logical_block_count = (info->capacity) / (info->logical_block_size); + } else { + info->capacity = (csd.version.v1.DeviceSize + 1); + info->capacity *= (1UL << (csd.version.v1.DeviceSizeMul + 2)); + info->logical_block_size = 512; + info->block_size = 1UL << (csd.RdBlockLen); + info->capacity *= info->block_size; + info->logical_block_count = (info->capacity) / (info->logical_block_size); + } + + info->manufacturer_id = cid.ManufacturerID; + + memcpy(info->oem_id, cid.OEM_AppliID, sizeof(info->oem_id) - 1); + info->oem_id[sizeof(info->oem_id) - 1] = '\0'; + + memcpy(info->product_name, cid.ProdName, sizeof(info->product_name) - 1); + info->product_name[sizeof(info->product_name) - 1] = '\0'; + + info->product_revision_major = cid.ProdRev >> 4; + info->product_revision_minor = cid.ProdRev & 0x0F; + info->product_serial_number = cid.ProdSN; + info->manufacturing_year = 2000 + cid.ManufactYear; + info->manufacturing_month = cid.ManufactMonth; + + } while(false); + + furi_hal_sd_spi_handle = NULL; + furi_hal_spi_release(&furi_hal_spi_bus_handle_sd_fast); + + return status; +} \ No newline at end of file diff --git a/firmware/targets/f7/src/update.c b/firmware/targets/f7/src/update.c index 520305410e2..378e74a5cc3 100644 --- a/firmware/targets/f7/src/update.c +++ b/firmware/targets/f7/src/update.c @@ -23,8 +23,8 @@ static FATFS* pfs = NULL; } static bool flipper_update_mount_sd() { - for(int i = 0; i < sd_max_mount_retry_count(); ++i) { - if(sd_init((i % 2) == 0) != SdSpiStatusOK) { + for(int i = 0; i < furi_hal_sd_max_mount_retry_count(); ++i) { + if(furi_hal_sd_init((i % 2) == 0) != FuriStatusOk) { /* Next attempt will be without card reset, let it settle */ furi_delay_ms(1000); continue; @@ -51,7 +51,7 @@ static bool flipper_update_init() { furi_hal_spi_config_init(); fatfs_init(); - if(!hal_sd_detect()) { + if(!furi_hal_sd_is_present()) { return false; } diff --git a/firmware/targets/furi_hal_include/furi_hal_sd.h b/firmware/targets/furi_hal_include/furi_hal_sd.h index e1c08a35ca1..645403b7f54 100644 --- a/firmware/targets/furi_hal_include/furi_hal_sd.h +++ b/firmware/targets/furi_hal_include/furi_hal_sd.h @@ -4,30 +4,82 @@ * SD Card HAL API */ -#include -#include -#include +#include #ifdef __cplusplus extern "C" { #endif -/** Init SD card detect +typedef struct { + uint64_t capacity; /*!< total capacity in bytes */ + uint32_t block_size; /*!< block size */ + uint32_t logical_block_count; /*!< logical capacity in blocks */ + uint32_t logical_block_size; /*!< logical block size in bytes */ + + uint8_t manufacturer_id; /*!< manufacturer ID */ + char oem_id[3]; /*!< OEM ID, 2 characters + null terminator */ + char product_name[6]; /*!< product name, 5 characters + null terminator */ + uint8_t product_revision_major; /*!< product revision major */ + uint8_t product_revision_minor; /*!< product revision minor */ + uint32_t product_serial_number; /*!< product serial number */ + uint8_t manufacturing_month; /*!< manufacturing month */ + uint16_t manufacturing_year; /*!< manufacturing year */ +} FuriHalSdInfo; + +/** + * @brief Init SD card presence detection */ -void hal_sd_detect_init(void); +void furi_hal_sd_presence_init(); -/** Set SD card detect pin to low +/** + * @brief Get SD card status + * @return true if SD card is present + */ +bool furi_hal_sd_is_present(); + +/** + * @brief SD card max mount retry count + * @return uint8_t */ -void hal_sd_detect_set_low(void); +uint8_t furi_hal_sd_max_mount_retry_count(); -/** Get SD card status - * - * @return true if SD card present, false if SD card not present +/** + * @brief Init SD card + * @param power_reset reset card power + * @return FuriStatus + */ +FuriStatus furi_hal_sd_init(bool power_reset); + +/** + * @brief Read blocks from SD card + * @param buff + * @param sector + * @param count + * @return FuriStatus */ -bool hal_sd_detect(void); +FuriStatus furi_hal_sd_read_blocks(uint32_t* buff, uint32_t sector, uint32_t count); -/** Pointer to currently used SPI Handle */ -extern FuriHalSpiBusHandle* furi_hal_sd_spi_handle; +/** + * @brief Write blocks to SD card + * @param buff + * @param sector + * @param count + * @return FuriStatus + */ +FuriStatus furi_hal_sd_write_blocks(const uint32_t* buff, uint32_t sector, uint32_t count); + +/** + * @brief Get SD card info + * @param info + * @return FuriStatus + */ +FuriStatus furi_hal_sd_info(FuriHalSdInfo* info); + +/** + * @brief Get SD card state + * @return FuriStatus + */ +FuriStatus furi_hal_sd_get_card_state(); #ifdef __cplusplus } From 77d6c419147d710c98a92d23d1cc7e8e38d05da9 Mon Sep 17 00:00:00 2001 From: hedger Date: Mon, 11 Sep 2023 13:51:03 +0300 Subject: [PATCH 748/824] github: submit SDKs to prod & dev (#3060) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * github: submit SDKs to prod & dev * github: increased limit of queried SDKs Co-authored-by: あく --- .github/actions/submit_sdk/action.yml | 54 +++++++++++++++++++++++++++ .github/workflows/build.yml | 50 ++++++++++--------------- 2 files changed, 74 insertions(+), 30 deletions(-) create mode 100644 .github/actions/submit_sdk/action.yml diff --git a/.github/actions/submit_sdk/action.yml b/.github/actions/submit_sdk/action.yml new file mode 100644 index 00000000000..05ed20b02fb --- /dev/null +++ b/.github/actions/submit_sdk/action.yml @@ -0,0 +1,54 @@ +name: Submit SDK to Catalog +author: hedger +description: | + This action checks if SDK exists in the catalog and if not, adds and/or publishes it. + +inputs: + catalog-url: + description: The URL of the Catalog API + required: true + catalog-token: + description: The token to use to authenticate with the Catalog API + required: true + firmware-api: + description: Fimware's API version, major.minor + required: true + firmware-target: + description: Firmware's target, e.g. f7/f18 + required: true + firmware-version: + description: Firmware's version, e.g. 0.13.37-rc3 + required: true + +runs: + using: composite + steps: + - name: Submit SDK + run: | + curl -sX 'GET' \ + '${{ inputs.catalog-url }}/api/v0/0/sdk?length=500' \ + -H 'Accept: application/json' > sdk_versions.json + if jq -r -e ".[] | select((.api == \"${{ inputs.firmware-api }}\") and .target == \"${{ inputs.firmware-target }}\")" sdk_versions.json > found_sdk.json ; then + echo "API version ${{ inputs.firmware-api }} already exists in catalog" + if [ $(jq -r -e ".released_at" found_sdk.json) != "null" ] ; then + echo "API version is already released" + exit 0 + fi + if ! echo "${{ inputs.firmware-version }}" | grep -q "-rc" ; then + SDK_ID=$(jq -r ._id found_sdk.json) + echo "Marking SDK $SDK_ID as released" + curl -X 'POST' \ + "${{ inputs.catalog-url }}/api/v0/0/sdk/${SDK_ID}/release" \ + -H 'Accept: application/json' \ + -H 'Authorization: Bearer ${{ inputs.catalog-token }}' \ + -d '' + fi + else + echo "API version ${{ inputs.firmware-api }} doesn't exist in catalog, adding" + curl -X 'POST' \ + '${{ inputs.catalog-url }}/api/v0/0/sdk' \ + -H 'Accept: application/json' \ + -H 'Authorization: Bearer ${{ inputs.catalog-token }}' \ + -H 'Content-Type: application/json' \ + -d "{\"name\": \"${{ inputs.firmware-version }}\", \"target\": \"${{ inputs.firmware-target }}\", \"api\": \"${{ inputs.firmware-api }}\"}\" + fi diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3736816a60f..810b70b01f8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -57,8 +57,10 @@ jobs: fi - name: 'Build the firmware and apps' + id: build-fw run: | ./fbt TARGET_HW=$TARGET_HW $FBT_BUILD_TYPE copro_dist updater_package fap_dist + echo "firmware_api=$(./fbt TARGET_HW=$TARGET_HW get_apiversion)" >> $GITHUB_OUTPUT - name: 'Check for uncommitted changes' run: | @@ -144,34 +146,22 @@ jobs: - [☁️ Web/App updater](https://lab.flipper.net/?url=https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.branch_name}}/flipper-z-${{steps.names.outputs.default_target}}-update-${{steps.names.outputs.suffix}}.tgz&channel=${{steps.names.outputs.branch_name}}&version=${{steps.names.outputs.commit_sha}}) edit-mode: replace - - name: 'Check if API version exists' + - name: 'SDK submission to dev catalog' if: ${{ steps.names.outputs.event_type == 'tag' && matrix.target == env.DEFAULT_TARGET }} - run: | - FIRMWARE_API=$(./fbt TARGET_HW=$TARGET_HW get_apiversion) - curl -sX 'GET' \ - '${{ secrets.CATALOG_URL }}/api/v0/0/sdk?length=200' \ - -H 'Accept: application/json' > sdk_versions.json - if jq -r -e ".[] | select((.api == \"${FIRMWARE_API}\") and .target == \"f${TARGET_HW}\")" sdk_versions.json > found_sdk.json ; then - echo "API version $FIRMWARE_API already exists in catalog" - if [ $(jq -r -e ".released_at" found_sdk.json) != "null" ] ; then - echo "API version is already released" - exit 0 - fi - if ! echo "$SUFFIX" | grep -q "-rc" ; then - SDK_ID=$(jq -r ._id found_sdk.json) - echo "Marking SDK $SDK_ID as released" - curl -X 'POST' \ - "${{ secrets.CATALOG_URL }}/api/v0/0/sdk/${SDK_ID}/release" \ - -H 'Accept: application/json' \ - -H 'Authorization: Bearer ${{ secrets.CATALOG_API_TOKEN }}' \ - -d '' - fi - else - echo "API version $FIRMWARE_API doesn't exist in catalog, adding" - curl -X 'POST' \ - '${{ secrets.CATALOG_URL }}/api/v0/0/sdk' \ - -H 'Accept: application/json' \ - -H 'Authorization: Bearer ${{ secrets.CATALOG_API_TOKEN }}' \ - -H 'Content-Type: application/json' \ - -d "{\"name\": \"${SUFFIX}\", \"target\": \"f${TARGET_HW}\", \"api\": \"${FIRMWARE_API}\"}\" - fi + uses: ./.github/actions/submit_sdk + with: + catalog-url: ${{ secrets.CATALOG_STAGING_URL }} + catalog-api-token: ${{ secrets.CATALOG_STAGING_API_TOKEN }} + firmware-api: ${{ steps.build-fw.outputs.firmware_api }} + firwmare-target: ${{ matrix.target }} + firmware-version: ${{ steps.names.outputs.suffix }} + + - name: 'SDK submission to prod catalog' + if: ${{ steps.names.outputs.event_type == 'tag' && matrix.target == env.DEFAULT_TARGET }} + uses: ./.github/actions/submit_sdk + with: + catalog-url: ${{ secrets.CATALOG_URL }} + catalog-api-token: ${{ secrets.CATALOG_API_TOKEN }} + firmware-api: ${{ steps.build-fw.outputs.firmware_api }} + firwmare-target: ${{ matrix.target }} + firmware-version: ${{ steps.names.outputs.suffix }} From 9f6fc6fe794d79e1a709ccabf767b2a8d2908f53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Mon, 11 Sep 2023 21:08:09 +0900 Subject: [PATCH 749/824] Snake: fix deadlock caused by use of view_port_update while locking model (#3063) --- applications/system/snake_game/snake_game.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/system/snake_game/snake_game.c b/applications/system/snake_game/snake_game.c index 6852cb215b2..bd7f1ce1647 100644 --- a/applications/system/snake_game/snake_game.c +++ b/applications/system/snake_game/snake_game.c @@ -390,8 +390,8 @@ int32_t snake_game_app(void* p) { // event timeout } - view_port_update(view_port); furi_mutex_release(snake_state->mutex); + view_port_update(view_port); } // Return backlight to normal state From 15894235a9c84cf41d32c857b1489855f24efac5 Mon Sep 17 00:00:00 2001 From: hedger Date: Mon, 11 Sep 2023 16:34:41 +0300 Subject: [PATCH 750/824] [FL-3596] fbt: added FW_CFG_ with build configuration (#3062) --- firmware.scons | 1 + 1 file changed, 1 insertion(+) diff --git a/firmware.scons b/firmware.scons index 2a82db3717e..657822700d5 100644 --- a/firmware.scons +++ b/firmware.scons @@ -93,6 +93,7 @@ else: "FURI_RAM_EXEC", ], ) +env.AppendUnique(CPPDEFINES=["FW_CFG_${FIRMWARE_APP_SET}"]) env.ConfigureForTarget(env.subst("${TARGET_HW}")) From ac3bd337a1db4228303e8ff3b945157ffbd7c0d2 Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Mon, 11 Sep 2023 19:30:16 +0400 Subject: [PATCH 751/824] [FL-3589] Sub-GHz: incorrect key parsing crash (#3066) --- applications/main/subghz/helpers/subghz_txrx.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/applications/main/subghz/helpers/subghz_txrx.c b/applications/main/subghz/helpers/subghz_txrx.c index 747ed73272f..e3e06695173 100644 --- a/applications/main/subghz/helpers/subghz_txrx.c +++ b/applications/main/subghz/helpers/subghz_txrx.c @@ -185,10 +185,11 @@ static uint32_t subghz_txrx_rx(SubGhzTxRx* instance, uint32_t frequency) { static void subghz_txrx_idle(SubGhzTxRx* instance) { furi_assert(instance); - furi_assert(instance->txrx_state != SubGhzTxRxStateSleep); - subghz_devices_idle(instance->radio_device); - subghz_txrx_speaker_off(instance); - instance->txrx_state = SubGhzTxRxStateIDLE; + if(instance->txrx_state != SubGhzTxRxStateSleep) { + subghz_devices_idle(instance->radio_device); + subghz_txrx_speaker_off(instance); + instance->txrx_state = SubGhzTxRxStateIDLE; + } } static void subghz_txrx_rx_end(SubGhzTxRx* instance) { From c657eb8a40a6b35137f23ce285ab7524030fbc47 Mon Sep 17 00:00:00 2001 From: hedger Date: Mon, 11 Sep 2023 19:18:04 +0300 Subject: [PATCH 752/824] github: potential fix for compact builds (#3067) * github: potential fix for compact builds * github: explicit build mode --- .github/workflows/build.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 810b70b01f8..12d22f3509c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -32,16 +32,18 @@ jobs: - name: 'Get commit details' id: names run: | + BUILD_TYPE='DEBUG=1 COMPACT=0' if [[ ${{ github.event_name }} == 'pull_request' ]]; then TYPE="pull" elif [[ "${{ github.ref }}" == "refs/tags/"* ]]; then TYPE="tag" - echo 'FBT_BUILD_TYPE="DEBUG=0 COMPACT=1"' >> $GITHUB_ENV + BUILD_TYPE='DEBUG=0 COMPACT=1' else TYPE="other" fi python3 scripts/get_env.py "--event_file=${{ github.event_path }}" "--type=$TYPE" || cat "${{ github.event_path }}" echo "event_type=$TYPE" >> $GITHUB_OUTPUT + echo "FBT_BUILD_TYPE=$BUILD_TYPE" >> $GITHUB_ENV echo "TARGET=${{ matrix.target }}" >> $GITHUB_ENV echo "TARGET_HW=$(echo "${{ matrix.target }}" | sed 's/f//')" >> $GITHUB_ENV From 91813831c61ba88887e6bd2a55a4abc2ef182f7d Mon Sep 17 00:00:00 2001 From: hedger Date: Mon, 11 Sep 2023 19:36:13 +0300 Subject: [PATCH 753/824] github: specified shell for SDK submission action (#3068) --- .github/actions/submit_sdk/action.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/actions/submit_sdk/action.yml b/.github/actions/submit_sdk/action.yml index 05ed20b02fb..2428c32d53a 100644 --- a/.github/actions/submit_sdk/action.yml +++ b/.github/actions/submit_sdk/action.yml @@ -24,6 +24,7 @@ runs: using: composite steps: - name: Submit SDK + shell: bash run: | curl -sX 'GET' \ '${{ inputs.catalog-url }}/api/v0/0/sdk?length=500' \ From 8bfa9898e32d8ac7474fa2422849667969c973b5 Mon Sep 17 00:00:00 2001 From: hedger Date: Tue, 12 Sep 2023 04:20:45 +0300 Subject: [PATCH 754/824] github: final fixes for SDK publishing (#3069) * Fixes 4 fixes 4 fixes * gh: proper step naming * github: Restored SDK processing logic --- .github/actions/submit_sdk/action.yml | 8 ++++---- .github/workflows/build.yml | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/actions/submit_sdk/action.yml b/.github/actions/submit_sdk/action.yml index 2428c32d53a..5de5d3d8fb2 100644 --- a/.github/actions/submit_sdk/action.yml +++ b/.github/actions/submit_sdk/action.yml @@ -7,7 +7,7 @@ inputs: catalog-url: description: The URL of the Catalog API required: true - catalog-token: + catalog-api-token: description: The token to use to authenticate with the Catalog API required: true firmware-api: @@ -41,7 +41,7 @@ runs: curl -X 'POST' \ "${{ inputs.catalog-url }}/api/v0/0/sdk/${SDK_ID}/release" \ -H 'Accept: application/json' \ - -H 'Authorization: Bearer ${{ inputs.catalog-token }}' \ + -H 'Authorization: Bearer ${{ inputs.catalog-api-token }}' \ -d '' fi else @@ -49,7 +49,7 @@ runs: curl -X 'POST' \ '${{ inputs.catalog-url }}/api/v0/0/sdk' \ -H 'Accept: application/json' \ - -H 'Authorization: Bearer ${{ inputs.catalog-token }}' \ + -H 'Authorization: Bearer ${{ inputs.catalog-api-token }}' \ -H 'Content-Type: application/json' \ - -d "{\"name\": \"${{ inputs.firmware-version }}\", \"target\": \"${{ inputs.firmware-target }}\", \"api\": \"${{ inputs.firmware-api }}\"}\" + -d "{\"name\": \"${{ inputs.firmware-version }}\", \"target\": \"${{ inputs.firmware-target }}\", \"api\": \"${{ inputs.firmware-api }}\"}" fi diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 12d22f3509c..84e89059efb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -148,14 +148,14 @@ jobs: - [☁️ Web/App updater](https://lab.flipper.net/?url=https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.branch_name}}/flipper-z-${{steps.names.outputs.default_target}}-update-${{steps.names.outputs.suffix}}.tgz&channel=${{steps.names.outputs.branch_name}}&version=${{steps.names.outputs.commit_sha}}) edit-mode: replace - - name: 'SDK submission to dev catalog' + - name: 'SDK submission to staging catalog' if: ${{ steps.names.outputs.event_type == 'tag' && matrix.target == env.DEFAULT_TARGET }} uses: ./.github/actions/submit_sdk with: catalog-url: ${{ secrets.CATALOG_STAGING_URL }} catalog-api-token: ${{ secrets.CATALOG_STAGING_API_TOKEN }} firmware-api: ${{ steps.build-fw.outputs.firmware_api }} - firwmare-target: ${{ matrix.target }} + firmware-target: ${{ matrix.target }} firmware-version: ${{ steps.names.outputs.suffix }} - name: 'SDK submission to prod catalog' From f0f2a6c11fb6a7819c0a57b59b2e713d8fea493b Mon Sep 17 00:00:00 2001 From: hedger Date: Tue, 12 Sep 2023 12:24:44 +0300 Subject: [PATCH 755/824] github: typo fix (#3070) * github: typo fix * github: sdk action: added validation for action inputs * github: sdk action: 10+ rcs --- .github/actions/submit_sdk/action.yml | 29 ++++++++++++++++++++++++--- .github/workflows/build.yml | 2 +- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/.github/actions/submit_sdk/action.yml b/.github/actions/submit_sdk/action.yml index 5de5d3d8fb2..269185d5ad3 100644 --- a/.github/actions/submit_sdk/action.yml +++ b/.github/actions/submit_sdk/action.yml @@ -5,10 +5,10 @@ description: | inputs: catalog-url: - description: The URL of the Catalog API + description: The URL of the Catalog API. Must not be empty or end with a /. required: true catalog-api-token: - description: The token to use to authenticate with the Catalog API + description: The token to use to authenticate with the Catalog API. Must not be empty. required: true firmware-api: description: Fimware's API version, major.minor @@ -17,12 +17,35 @@ inputs: description: Firmware's target, e.g. f7/f18 required: true firmware-version: - description: Firmware's version, e.g. 0.13.37-rc3 + description: Firmware's version, e.g. 0.13.37-rc3, or 0.13.37 required: true runs: using: composite steps: + - name: Check inputs + shell: bash + run: | + if [ -z "${{ inputs.catalog-url }}" ] ; then + echo "Invalid catalog-url: ${{ inputs.catalog-url }}" + exit 1 + fi + if [ -z "${{ inputs.catalog-api-token }}" ] ; then + echo "Invalid catalog-api-token: ${{ inputs.catalog-api-token }}" + exit 1 + fi + if ! echo "${{ inputs.firmware-api }}" | grep -q "^[0-9]\+\.[0-9]\+$" ; then + echo "Invalid firmware-api: ${{ inputs.firmware-api }}" + exit 1 + fi + if ! echo "${{ inputs.firmware-target }}" | grep -q "^f[0-9]\+$" ; then + echo "Invalid firmware-target: ${{ inputs.firmware-target }}" + exit 1 + fi + if ! echo "${{ inputs.firmware-version }}" | grep -q "^[0-9]\+\.[0-9]\+\.[0-9]\+\(-rc\)\?\([0-9]\+\)\?$" ; then + echo "Invalid firmware-version: ${{ inputs.firmware-version }}" + exit 1 + fi - name: Submit SDK shell: bash run: | diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 84e89059efb..7a718883790 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -165,5 +165,5 @@ jobs: catalog-url: ${{ secrets.CATALOG_URL }} catalog-api-token: ${{ secrets.CATALOG_API_TOKEN }} firmware-api: ${{ steps.build-fw.outputs.firmware_api }} - firwmare-target: ${{ matrix.target }} + firmware-target: ${{ matrix.target }} firmware-version: ${{ steps.names.outputs.suffix }} From ac892f3d03f4e1b2b5a280805f34f23dc8d8f41a Mon Sep 17 00:00:00 2001 From: Max Andreev Date: Thu, 14 Sep 2023 12:15:20 +0300 Subject: [PATCH 756/824] Fix DMA SPI memory increment define (#3075) --- firmware/targets/f7/furi_hal/furi_hal_spi.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/firmware/targets/f7/furi_hal/furi_hal_spi.c b/firmware/targets/f7/furi_hal/furi_hal_spi.c index 17769832b6a..a8884105aae 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_spi.c +++ b/firmware/targets/f7/furi_hal/furi_hal_spi.c @@ -283,7 +283,7 @@ bool furi_hal_spi_bus_trx_dma( if(tx_buffer == NULL) { // RX mode, use dummy data instead of TX buffer tx_buffer = (uint8_t*)&dma_dummy_u32; - tx_mem_increase_mode = LL_DMA_PERIPH_NOINCREMENT; + tx_mem_increase_mode = LL_DMA_MEMORY_NOINCREMENT; } else { tx_mem_increase_mode = LL_DMA_MEMORY_INCREMENT; } @@ -373,4 +373,4 @@ bool furi_hal_spi_bus_trx_dma( furi_check(furi_semaphore_release(spi_dma_lock) == FuriStatusOk); return ret; -} \ No newline at end of file +} From 25af13e9988e1d7db0afa184381a0bc40d0b3b3e Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Thu, 14 Sep 2023 13:27:01 +0400 Subject: [PATCH 757/824] SubGhz: Fix CLI subghz chat (#3073) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- applications/drivers/subghz/cc1101_ext/cc1101_ext.c | 2 +- firmware/targets/f7/furi_hal/furi_hal_subghz.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/applications/drivers/subghz/cc1101_ext/cc1101_ext.c b/applications/drivers/subghz/cc1101_ext/cc1101_ext.c index 4fb249c6ddc..c7083162889 100644 --- a/applications/drivers/subghz/cc1101_ext/cc1101_ext.c +++ b/applications/drivers/subghz/cc1101_ext/cc1101_ext.c @@ -333,7 +333,7 @@ bool subghz_device_cc1101_ext_rx_pipe_not_empty() { (CC1101_STATUS_RXBYTES) | CC1101_BURST, (uint8_t*)status); furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); - if((status->NUM_RXBYTES > 0) || (status->RXFIFO_OVERFLOW == 0)) { + if(status->NUM_RXBYTES > 0) { return true; } else { return false; diff --git a/firmware/targets/f7/furi_hal/furi_hal_subghz.c b/firmware/targets/f7/furi_hal/furi_hal_subghz.c index bd724f0bf46..ac71b5f6c73 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_subghz.c +++ b/firmware/targets/f7/furi_hal/furi_hal_subghz.c @@ -207,7 +207,7 @@ bool furi_hal_subghz_rx_pipe_not_empty() { cc1101_read_reg( &furi_hal_spi_bus_handle_subghz, (CC1101_STATUS_RXBYTES) | CC1101_BURST, (uint8_t*)status); furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); - if((status->NUM_RXBYTES > 0) || (status->RXFIFO_OVERFLOW == 0)) { + if(status->NUM_RXBYTES > 0) { return true; } else { return false; From 338fc3afeac2bdb17630ab1ad4234972dbaccc7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Tue, 19 Sep 2023 23:22:21 +0900 Subject: [PATCH 758/824] New clock switch schema, fixes random core2 crashes (#3008) * Updated stack to 1.17.0 * hal: ble: Fixed stack config * Bumped stack version in config * scripts: added validation of copro stack version in update bundles * Copro: update to 1.17.2 * FuriHal: adjust tick frequency for HSE as sys clk * FuriHal: adjust systick reload on sys clock change * Sync api and format sources * scripts: updated ob.data for newer stack * FuriHal: return core2 hse pll transition on deep sleep * FuriHal: cleanup ble glue * FuriHal: rework ble glue, allow shci_send in critical section * FuriHal: sync api symbols * FuriHal: cleanup BLE glue, remove unused garbage and duplicate declarations * FuriHal: BLE glue cleanup, 2nd iteration * FuriHal: hide tick drift reports under FURI_HAL_OS_DEBUG * Lib: sync stm32wb_copro with latest dev * FuriHal: ble-glue, slightly less editable device name and duplicate definition cleanup * FuriHal: update ble config options, enable some optimizations and ext adv * FuriHal: update clock switch method documentation * FuriHal: better SNBRSA bug workaround fix * FuriHal: complete comment about tick skew * FuriHal: proper condition in clock hsi2hse transition * FuriHal: move PLL start to hse2pll routine, fix lockup caused by core2 switching to HSE before us * FuriHal: explicit HSE start before switch * FuriHal: fix documentation and move flash latency change to later stage, remove duplicate LL_RCC_SetRFWKPClockSource call --------- Co-authored-by: hedger Co-authored-by: hedger --- SConstruct | 2 + fbt_options.py | 2 +- firmware/targets/f18/api_symbols.csv | 6 +- firmware/targets/f7/api_symbols.csv | 6 +- firmware/targets/f7/ble_glue/app_common.h | 30 +- firmware/targets/f7/ble_glue/app_conf.h | 286 +--------- firmware/targets/f7/ble_glue/app_debug.c | 17 +- firmware/targets/f7/ble_glue/app_debug.h | 28 +- firmware/targets/f7/ble_glue/ble_app.c | 11 +- firmware/targets/f7/ble_glue/ble_app.h | 6 +- firmware/targets/f7/ble_glue/ble_conf.h | 61 +- firmware/targets/f7/ble_glue/ble_const.h | 22 +- firmware/targets/f7/ble_glue/ble_dbg_conf.h | 200 +------ firmware/targets/f7/ble_glue/ble_glue.c | 19 - firmware/targets/f7/ble_glue/compiler.h | 22 +- firmware/targets/f7/ble_glue/hsem_map.h | 81 +++ firmware/targets/f7/ble_glue/hw_conf.h | 231 -------- firmware/targets/f7/ble_glue/hw_if.h | 102 ---- firmware/targets/f7/ble_glue/hw_ipcc.c | 529 ++---------------- firmware/targets/f7/ble_glue/osal.h | 25 +- firmware/targets/f7/ble_glue/tl_dbg_conf.h | 39 +- firmware/targets/f7/ble_glue/utilities_conf.h | 68 --- firmware/targets/f7/furi_hal/furi_hal_bt.c | 4 + firmware/targets/f7/furi_hal/furi_hal_clock.c | 97 +++- firmware/targets/f7/furi_hal/furi_hal_clock.h | 25 +- .../targets/f7/furi_hal/furi_hal_console.c | 2 - firmware/targets/f7/furi_hal/furi_hal_flash.c | 3 + .../targets/f7/furi_hal/furi_hal_memory.c | 16 +- firmware/targets/f7/furi_hal/furi_hal_os.c | 38 +- firmware/targets/f7/furi_hal/furi_hal_power.c | 17 +- .../targets/f7/furi_hal/furi_hal_random.c | 2 +- lib/stm32wb.scons | 1 - lib/stm32wb_copro | 2 +- scripts/ob.data | 6 +- scripts/update.py | 10 + 35 files changed, 327 insertions(+), 1689 deletions(-) create mode 100644 firmware/targets/f7/ble_glue/hsem_map.h delete mode 100644 firmware/targets/f7/ble_glue/hw_conf.h delete mode 100644 firmware/targets/f7/ble_glue/hw_if.h delete mode 100644 firmware/targets/f7/ble_glue/utilities_conf.h diff --git a/SConstruct b/SConstruct index 44ee7746731..36e0600235a 100644 --- a/SConstruct +++ b/SConstruct @@ -77,6 +77,8 @@ if GetOption("fullenv") or any( "${COPRO_DISCLAIMER}", "--obdata", '"${ROOT_DIR.abspath}/${COPRO_OB_DATA}"', + "--stackversion", + "${COPRO_CUBE_VERSION}", ] dist_resource_arguments = [ "-r", diff --git a/fbt_options.py b/fbt_options.py index bd804fc8b57..d13abbe5e29 100644 --- a/fbt_options.py +++ b/fbt_options.py @@ -22,7 +22,7 @@ COPRO_OB_DATA = "scripts/ob.data" # Must match lib/stm32wb_copro version -COPRO_CUBE_VERSION = "1.15.0" +COPRO_CUBE_VERSION = "1.17.2" COPRO_CUBE_DIR = "lib/stm32wb_copro" diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index 0c20649318a..bc17ff2fa8a 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1020,8 +1020,10 @@ Function,+,furi_hal_clock_mco_disable,void, Function,+,furi_hal_clock_mco_enable,void,"FuriHalClockMcoSourceId, FuriHalClockMcoDivisorId" Function,-,furi_hal_clock_resume_tick,void, Function,-,furi_hal_clock_suspend_tick,void, -Function,-,furi_hal_clock_switch_to_hsi,void, -Function,-,furi_hal_clock_switch_to_pll,void, +Function,-,furi_hal_clock_switch_hse2hsi,void, +Function,-,furi_hal_clock_switch_hse2pll,_Bool, +Function,-,furi_hal_clock_switch_hsi2hse,void, +Function,-,furi_hal_clock_switch_pll2hse,_Bool, Function,+,furi_hal_console_disable,void, Function,+,furi_hal_console_enable,void, Function,+,furi_hal_console_init,void, diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 0819851543b..26fad6b58fd 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1091,8 +1091,10 @@ Function,+,furi_hal_clock_mco_disable,void, Function,+,furi_hal_clock_mco_enable,void,"FuriHalClockMcoSourceId, FuriHalClockMcoDivisorId" Function,-,furi_hal_clock_resume_tick,void, Function,-,furi_hal_clock_suspend_tick,void, -Function,-,furi_hal_clock_switch_to_hsi,void, -Function,-,furi_hal_clock_switch_to_pll,void, +Function,-,furi_hal_clock_switch_hse2hsi,void, +Function,-,furi_hal_clock_switch_hse2pll,_Bool, +Function,-,furi_hal_clock_switch_hsi2hse,void, +Function,-,furi_hal_clock_switch_pll2hse,_Bool, Function,+,furi_hal_console_disable,void, Function,+,furi_hal_console_enable,void, Function,+,furi_hal_console_init,void, diff --git a/firmware/targets/f7/ble_glue/app_common.h b/firmware/targets/f7/ble_glue/app_common.h index 8eaf2308593..e969636d279 100644 --- a/firmware/targets/f7/ble_glue/app_common.h +++ b/firmware/targets/f7/ble_glue/app_common.h @@ -1,30 +1,4 @@ -/* USER CODE BEGIN Header */ -/** - ****************************************************************************** - * File Name : app_common.h - * Description : App Common application configuration file for STM32WPAN Middleware. - * - ****************************************************************************** - * @attention - * - *